1use super::super::handler::{config_field_label, config_field_value};
2use crate::command::chat::app::ChatApp;
3use crate::constants::{CONFIG_FIELDS, CONFIG_GLOBAL_FIELDS};
4use ratatui::{
5 layout::Rect,
6 style::{Modifier, Style},
7 text::{Line, Span},
8 widgets::{Block, Borders, Paragraph},
9};
10
11pub fn draw_config_screen(f: &mut ratatui::Frame, area: Rect, app: &mut ChatApp) {
12 let t = &app.theme;
13 let bg = t.bg_title;
14 let total_provider_fields = CONFIG_FIELDS.len();
15
16 let mut lines: Vec<Line> = Vec::new();
17 lines.push(Line::from(""));
18
19 lines.push(Line::from(vec![Span::styled(
20 " ⚙️ 模型配置",
21 Style::default()
22 .fg(t.config_title)
23 .add_modifier(Modifier::BOLD),
24 )]));
25 lines.push(Line::from(""));
26
27 let provider_count = app.agent_config.providers.len();
28 if provider_count > 0 {
29 let mut tab_spans: Vec<Span> = vec![Span::styled(" ", Style::default())];
30 for (i, p) in app.agent_config.providers.iter().enumerate() {
31 let is_current = i == app.config_provider_idx;
32 let is_active = i == app.agent_config.active_index;
33 let marker = if is_active { "● " } else { "○ " };
34 let label = format!(" {}{} ", marker, p.name);
35 if is_current {
36 tab_spans.push(Span::styled(
37 label,
38 Style::default()
39 .fg(t.config_tab_active_fg)
40 .bg(t.config_tab_active_bg)
41 .add_modifier(Modifier::BOLD),
42 ));
43 } else {
44 tab_spans.push(Span::styled(
45 label,
46 Style::default().fg(t.config_tab_inactive),
47 ));
48 }
49 if i < provider_count - 1 {
50 tab_spans.push(Span::styled(" │ ", Style::default().fg(t.separator)));
51 }
52 }
53 tab_spans.push(Span::styled(
54 " (● = 活跃模型, Tab 切换, s 设为活跃)",
55 Style::default().fg(t.config_dim),
56 ));
57 lines.push(Line::from(tab_spans));
58 } else {
59 lines.push(Line::from(Span::styled(
60 " (无 Provider,按 a 新增)",
61 Style::default().fg(t.config_toggle_off),
62 )));
63 }
64 lines.push(Line::from(""));
65
66 lines.push(Line::from(Span::styled(
67 " ─────────────────────────────────────────",
68 Style::default().fg(t.separator),
69 )));
70 lines.push(Line::from(""));
71
72 if provider_count > 0 {
73 lines.push(Line::from(Span::styled(
74 " 📦 Provider 配置",
75 Style::default()
76 .fg(t.config_section)
77 .add_modifier(Modifier::BOLD),
78 )));
79 lines.push(Line::from(""));
80
81 for i in 0..total_provider_fields {
82 let is_selected = app.config_field_idx == i;
83 let label = config_field_label(i);
84 let value = if app.config_editing && is_selected {
85 app.config_edit_buf.clone()
86 } else {
87 config_field_value(app, i)
88 };
89
90 let pointer = if is_selected { " ▸ " } else { " " };
91 let pointer_style = if is_selected {
92 Style::default().fg(t.config_pointer)
93 } else {
94 Style::default()
95 };
96 let label_style = if is_selected {
97 Style::default()
98 .fg(t.config_label_selected)
99 .add_modifier(Modifier::BOLD)
100 } else {
101 Style::default().fg(t.config_label)
102 };
103 let value_style = if app.config_editing && is_selected {
104 Style::default().fg(t.text_white).bg(t.config_edit_bg)
105 } else if is_selected {
106 Style::default().fg(t.text_white)
107 } else if CONFIG_FIELDS[i] == "api_key" {
108 Style::default().fg(t.config_api_key)
109 } else {
110 Style::default().fg(t.config_value)
111 };
112
113 lines.push(Line::from(if app.config_editing && is_selected {
114 let mut spans = vec![
116 Span::styled(pointer, pointer_style),
117 Span::styled(format!("{:<10}", label), label_style),
118 Span::styled(" ", Style::default()),
119 ];
120 let chars: Vec<char> = value.chars().collect();
121 let cursor = app.config_edit_cursor;
122 let before: String = chars[..cursor.min(chars.len())].iter().collect();
123 let cursor_ch = if cursor < chars.len() {
124 chars[cursor].to_string()
125 } else {
126 " ".to_string()
127 };
128 let after: String = if cursor < chars.len() {
129 chars[cursor + 1..].iter().collect()
130 } else {
131 String::new()
132 };
133 spans.push(Span::styled(before, value_style));
134 spans.push(Span::styled(
135 cursor_ch,
136 Style::default().fg(t.cursor_fg).bg(t.cursor_bg),
137 ));
138 spans.push(Span::styled(after, value_style));
139 spans.push(Span::styled(" ✏️", Style::default()));
140 spans
141 } else {
142 vec![
143 Span::styled(pointer, pointer_style),
144 Span::styled(format!("{:<10}", label), label_style),
145 Span::styled(" ", Style::default()),
146 Span::styled(
147 if value.is_empty() {
148 "(空)".to_string()
149 } else {
150 value
151 },
152 value_style,
153 ),
154 ]
155 }));
156 }
157 }
158
159 lines.push(Line::from(""));
160
161 lines.push(Line::from(Span::styled(
162 " 🌐 全局配置",
163 Style::default()
164 .fg(t.config_section)
165 .add_modifier(Modifier::BOLD),
166 )));
167 lines.push(Line::from(""));
168
169 for i in 0..CONFIG_GLOBAL_FIELDS.len() {
170 let field_idx = total_provider_fields + i;
171 let is_selected = app.config_field_idx == field_idx;
172 let label = config_field_label(field_idx);
173 let value = if app.config_editing && is_selected {
174 app.config_edit_buf.clone()
175 } else {
176 config_field_value(app, field_idx)
177 };
178
179 let pointer = if is_selected { " ▸ " } else { " " };
180 let pointer_style = if is_selected {
181 Style::default().fg(t.config_pointer)
182 } else {
183 Style::default()
184 };
185 let label_style = if is_selected {
186 Style::default()
187 .fg(t.config_label_selected)
188 .add_modifier(Modifier::BOLD)
189 } else {
190 Style::default().fg(t.config_label)
191 };
192 let value_style = if app.config_editing && is_selected {
193 Style::default().fg(t.text_white).bg(t.config_edit_bg)
194 } else if is_selected {
195 Style::default().fg(t.text_white)
196 } else {
197 Style::default().fg(t.config_value)
198 };
199
200 if CONFIG_GLOBAL_FIELDS[i] == "stream_mode" {
201 let toggle_on = app.agent_config.stream_mode;
202 let toggle_style = if toggle_on {
203 Style::default()
204 .fg(t.config_toggle_on)
205 .add_modifier(Modifier::BOLD)
206 } else {
207 Style::default().fg(t.config_toggle_off)
208 };
209 let toggle_text = if toggle_on {
210 "● 开启"
211 } else {
212 "○ 关闭"
213 };
214 lines.push(Line::from(vec![
215 Span::styled(pointer, pointer_style),
216 Span::styled(format!("{:<10}", label), label_style),
217 Span::styled(" ", Style::default()),
218 Span::styled(toggle_text, toggle_style),
219 Span::styled(
220 if is_selected { " (Enter 切换)" } else { "" },
221 Style::default().fg(t.config_dim),
222 ),
223 ]));
224 } else if CONFIG_GLOBAL_FIELDS[i] == "theme" {
225 let theme_name = app.agent_config.theme.display_name();
226 lines.push(Line::from(vec![
227 Span::styled(pointer, pointer_style),
228 Span::styled(format!("{:<10}", label), label_style),
229 Span::styled(" ", Style::default()),
230 Span::styled(
231 format!("🎨 {}", theme_name),
232 Style::default()
233 .fg(t.config_toggle_on)
234 .add_modifier(Modifier::BOLD),
235 ),
236 Span::styled(
237 if is_selected { " (Enter 切换)" } else { "" },
238 Style::default().fg(t.config_dim),
239 ),
240 ]));
241 } else if CONFIG_GLOBAL_FIELDS[i] == "tools_enabled" {
242 let toggle_on = app.agent_config.tools_enabled;
243 let toggle_style = if toggle_on {
244 Style::default()
245 .fg(t.config_toggle_on)
246 .add_modifier(Modifier::BOLD)
247 } else {
248 Style::default().fg(t.config_toggle_off)
249 };
250 let toggle_text = if toggle_on {
251 "● 开启"
252 } else {
253 "○ 关闭"
254 };
255 lines.push(Line::from(vec![
256 Span::styled(pointer, pointer_style),
257 Span::styled(format!("{:<10}", label), label_style),
258 Span::styled(" ", Style::default()),
259 Span::styled(toggle_text, toggle_style),
260 Span::styled(
261 if is_selected { " (Enter 切换)" } else { "" },
262 Style::default().fg(t.config_dim),
263 ),
264 ]));
265 } else if CONFIG_GLOBAL_FIELDS[i] == "system_prompt" {
266 let display_value = if value.is_empty() {
268 "(空)".to_string()
269 } else {
270 let flat: String = value
272 .chars()
273 .map(|c| if c == '\n' { ' ' } else { c })
274 .collect();
275 if flat.chars().count() > 40 {
276 let truncated: String = flat.chars().take(40).collect();
277 format!("{}...", truncated)
278 } else {
279 flat
280 }
281 };
282 lines.push(Line::from(vec![
283 Span::styled(pointer, pointer_style),
284 Span::styled(format!("{:<10}", label), label_style),
285 Span::styled(" ", Style::default()),
286 Span::styled(display_value, value_style),
287 Span::styled(
288 if is_selected { " (Enter 编辑)" } else { "" },
289 Style::default().fg(t.config_dim),
290 ),
291 ]));
292 } else if CONFIG_GLOBAL_FIELDS[i] == "style" {
293 let display_value = if value.is_empty() {
295 "(空)".to_string()
296 } else {
297 let flat: String = value
298 .chars()
299 .map(|c| if c == '\n' { ' ' } else { c })
300 .collect();
301 if flat.chars().count() > 40 {
302 let truncated: String = flat.chars().take(40).collect();
303 format!("{}...", truncated)
304 } else {
305 flat
306 }
307 };
308 lines.push(Line::from(vec![
309 Span::styled(pointer, pointer_style),
310 Span::styled(format!("{:<10}", label), label_style),
311 Span::styled(" ", Style::default()),
312 Span::styled(display_value, value_style),
313 Span::styled(
314 if is_selected { " (Enter 编辑)" } else { "" },
315 Style::default().fg(t.config_dim),
316 ),
317 ]));
318 } else {
319 lines.push(Line::from(if app.config_editing && is_selected {
320 let mut spans = vec![
322 Span::styled(pointer, pointer_style),
323 Span::styled(format!("{:<10}", label), label_style),
324 Span::styled(" ", Style::default()),
325 ];
326 let chars: Vec<char> = value.chars().collect();
327 let cursor = app.config_edit_cursor;
328 let before: String = chars[..cursor.min(chars.len())].iter().collect();
329 let cursor_ch = if cursor < chars.len() {
330 chars[cursor].to_string()
331 } else {
332 " ".to_string()
333 };
334 let after: String = if cursor < chars.len() {
335 chars[cursor + 1..].iter().collect()
336 } else {
337 String::new()
338 };
339 spans.push(Span::styled(before, value_style));
340 spans.push(Span::styled(
341 cursor_ch,
342 Style::default().fg(t.cursor_fg).bg(t.cursor_bg),
343 ));
344 spans.push(Span::styled(after, value_style));
345 spans.push(Span::styled(" ✏️", Style::default()));
346 spans
347 } else {
348 vec![
349 Span::styled(pointer, pointer_style),
350 Span::styled(format!("{:<10}", label), label_style),
351 Span::styled(" ", Style::default()),
352 Span::styled(
353 if value.is_empty() {
354 "(空)".to_string()
355 } else {
356 value
357 },
358 value_style,
359 ),
360 ]
361 }));
362 }
363 }
364
365 lines.push(Line::from(""));
366 lines.push(Line::from(""));
367
368 lines.push(Line::from(Span::styled(
369 " ─────────────────────────────────────────",
370 Style::default().fg(t.separator),
371 )));
372 lines.push(Line::from(""));
373 lines.push(Line::from(vec![
374 Span::styled(" ", Style::default()),
375 Span::styled(
376 "↑↓/jk",
377 Style::default()
378 .fg(t.config_hint_key)
379 .add_modifier(Modifier::BOLD),
380 ),
381 Span::styled(" 切换字段 ", Style::default().fg(t.config_hint_desc)),
382 Span::styled(
383 "Enter",
384 Style::default()
385 .fg(t.config_hint_key)
386 .add_modifier(Modifier::BOLD),
387 ),
388 Span::styled(" 编辑 ", Style::default().fg(t.config_hint_desc)),
389 Span::styled(
390 "Tab/←→",
391 Style::default()
392 .fg(t.config_hint_key)
393 .add_modifier(Modifier::BOLD),
394 ),
395 Span::styled(" 切换 Provider ", Style::default().fg(t.config_hint_desc)),
396 Span::styled(
397 "a",
398 Style::default()
399 .fg(t.config_hint_key)
400 .add_modifier(Modifier::BOLD),
401 ),
402 Span::styled(" 新增 ", Style::default().fg(t.config_hint_desc)),
403 Span::styled(
404 "d",
405 Style::default()
406 .fg(t.config_hint_key)
407 .add_modifier(Modifier::BOLD),
408 ),
409 Span::styled(" 删除 ", Style::default().fg(t.config_hint_desc)),
410 Span::styled(
411 "s",
412 Style::default()
413 .fg(t.config_hint_key)
414 .add_modifier(Modifier::BOLD),
415 ),
416 Span::styled(" 设为活跃 ", Style::default().fg(t.config_hint_desc)),
417 Span::styled(
418 "Esc",
419 Style::default()
420 .fg(t.config_hint_key)
421 .add_modifier(Modifier::BOLD),
422 ),
423 Span::styled(" 保存返回", Style::default().fg(t.config_hint_desc)),
424 ]));
425
426 let content = Paragraph::new(lines)
427 .block(
428 Block::default()
429 .borders(Borders::ALL)
430 .border_type(ratatui::widgets::BorderType::Rounded)
431 .border_style(Style::default().fg(t.border_config))
432 .title(Span::styled(
433 " ⚙️ 模型配置编辑 ",
434 Style::default()
435 .fg(t.config_label_selected)
436 .add_modifier(Modifier::BOLD),
437 ))
438 .style(Style::default().bg(bg)),
439 )
440 .scroll((0, 0));
441 f.render_widget(content, area);
442}