Skip to main content

j_cli/command/chat/ui/
config.rs

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                // 编辑模式:显示带光标的文本
115                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            // system_prompt 特殊处理:截断显示 + Enter 弹出全屏编辑器
267            let display_value = if value.is_empty() {
268                "(空)".to_string()
269            } else {
270                // 截断到 40 个字符,替换换行为空格
271                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            // style 特殊处理:同 system_prompt 模式
294            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                // 编辑模式:显示带光标的文本
321                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}