Skip to main content

j_cli/command/chat/ui/
config.rs

1use super::super::app::{CONFIG_FIELDS, CONFIG_GLOBAL_FIELDS, ChatApp};
2use super::super::handler::{config_field_label, config_field_value};
3use ratatui::{
4    layout::Rect,
5    style::{Modifier, Style},
6    text::{Line, Span},
7    widgets::{Block, Borders, Paragraph},
8};
9
10pub fn draw_config_screen(f: &mut ratatui::Frame, area: Rect, app: &mut ChatApp) {
11    let t = &app.theme;
12    let bg = t.bg_title;
13    let total_provider_fields = CONFIG_FIELDS.len();
14
15    let mut lines: Vec<Line> = Vec::new();
16    lines.push(Line::from(""));
17
18    lines.push(Line::from(vec![Span::styled(
19        "  ⚙️  模型配置",
20        Style::default()
21            .fg(t.config_title)
22            .add_modifier(Modifier::BOLD),
23    )]));
24    lines.push(Line::from(""));
25
26    let provider_count = app.agent_config.providers.len();
27    if provider_count > 0 {
28        let mut tab_spans: Vec<Span> = vec![Span::styled("  ", Style::default())];
29        for (i, p) in app.agent_config.providers.iter().enumerate() {
30            let is_current = i == app.config_provider_idx;
31            let is_active = i == app.agent_config.active_index;
32            let marker = if is_active { "● " } else { "○ " };
33            let label = format!(" {}{} ", marker, p.name);
34            if is_current {
35                tab_spans.push(Span::styled(
36                    label,
37                    Style::default()
38                        .fg(t.config_tab_active_fg)
39                        .bg(t.config_tab_active_bg)
40                        .add_modifier(Modifier::BOLD),
41                ));
42            } else {
43                tab_spans.push(Span::styled(
44                    label,
45                    Style::default().fg(t.config_tab_inactive),
46                ));
47            }
48            if i < provider_count - 1 {
49                tab_spans.push(Span::styled(" │ ", Style::default().fg(t.separator)));
50            }
51        }
52        tab_spans.push(Span::styled(
53            "    (● = 活跃模型, Tab 切换, s 设为活跃)",
54            Style::default().fg(t.config_dim),
55        ));
56        lines.push(Line::from(tab_spans));
57    } else {
58        lines.push(Line::from(Span::styled(
59            "  (无 Provider,按 a 新增)",
60            Style::default().fg(t.config_toggle_off),
61        )));
62    }
63    lines.push(Line::from(""));
64
65    lines.push(Line::from(Span::styled(
66        "  ─────────────────────────────────────────",
67        Style::default().fg(t.separator),
68    )));
69    lines.push(Line::from(""));
70
71    if provider_count > 0 {
72        lines.push(Line::from(Span::styled(
73            "  📦 Provider 配置",
74            Style::default()
75                .fg(t.config_section)
76                .add_modifier(Modifier::BOLD),
77        )));
78        lines.push(Line::from(""));
79
80        for i in 0..total_provider_fields {
81            let is_selected = app.config_field_idx == i;
82            let label = config_field_label(i);
83            let value = if app.config_editing && is_selected {
84                app.config_edit_buf.clone()
85            } else {
86                config_field_value(app, i)
87            };
88
89            let pointer = if is_selected { "  ▸ " } else { "    " };
90            let pointer_style = if is_selected {
91                Style::default().fg(t.config_pointer)
92            } else {
93                Style::default()
94            };
95            let label_style = if is_selected {
96                Style::default()
97                    .fg(t.config_label_selected)
98                    .add_modifier(Modifier::BOLD)
99            } else {
100                Style::default().fg(t.config_label)
101            };
102            let value_style = if app.config_editing && is_selected {
103                Style::default().fg(t.text_white).bg(t.config_edit_bg)
104            } else if is_selected {
105                Style::default().fg(t.text_white)
106            } else if CONFIG_FIELDS[i] == "api_key" {
107                Style::default().fg(t.config_api_key)
108            } else {
109                Style::default().fg(t.config_value)
110            };
111            let edit_indicator = if app.config_editing && is_selected {
112                " ✏️"
113            } else {
114                ""
115            };
116
117            lines.push(Line::from(vec![
118                Span::styled(pointer, pointer_style),
119                Span::styled(format!("{:<10}", label), label_style),
120                Span::styled("  ", Style::default()),
121                Span::styled(
122                    if value.is_empty() {
123                        "(空)".to_string()
124                    } else {
125                        value
126                    },
127                    value_style,
128                ),
129                Span::styled(edit_indicator, Style::default()),
130            ]));
131        }
132    }
133
134    lines.push(Line::from(""));
135    lines.push(Line::from(Span::styled(
136        "  ─────────────────────────────────────────",
137        Style::default().fg(t.separator),
138    )));
139    lines.push(Line::from(""));
140
141    lines.push(Line::from(Span::styled(
142        "  🌐 全局配置",
143        Style::default()
144            .fg(t.config_section)
145            .add_modifier(Modifier::BOLD),
146    )));
147    lines.push(Line::from(""));
148
149    for i in 0..CONFIG_GLOBAL_FIELDS.len() {
150        let field_idx = total_provider_fields + i;
151        let is_selected = app.config_field_idx == field_idx;
152        let label = config_field_label(field_idx);
153        let value = if app.config_editing && is_selected {
154            app.config_edit_buf.clone()
155        } else {
156            config_field_value(app, field_idx)
157        };
158
159        let pointer = if is_selected { "  ▸ " } else { "    " };
160        let pointer_style = if is_selected {
161            Style::default().fg(t.config_pointer)
162        } else {
163            Style::default()
164        };
165        let label_style = if is_selected {
166            Style::default()
167                .fg(t.config_label_selected)
168                .add_modifier(Modifier::BOLD)
169        } else {
170            Style::default().fg(t.config_label)
171        };
172        let value_style = if app.config_editing && is_selected {
173            Style::default().fg(t.text_white).bg(t.config_edit_bg)
174        } else if is_selected {
175            Style::default().fg(t.text_white)
176        } else {
177            Style::default().fg(t.config_value)
178        };
179        let edit_indicator = if app.config_editing && is_selected {
180            " ✏️"
181        } else {
182            ""
183        };
184
185        if CONFIG_GLOBAL_FIELDS[i] == "stream_mode" {
186            let toggle_on = app.agent_config.stream_mode;
187            let toggle_style = if toggle_on {
188                Style::default()
189                    .fg(t.config_toggle_on)
190                    .add_modifier(Modifier::BOLD)
191            } else {
192                Style::default().fg(t.config_toggle_off)
193            };
194            let toggle_text = if toggle_on {
195                "● 开启"
196            } else {
197                "○ 关闭"
198            };
199            lines.push(Line::from(vec![
200                Span::styled(pointer, pointer_style),
201                Span::styled(format!("{:<10}", label), label_style),
202                Span::styled("  ", Style::default()),
203                Span::styled(toggle_text, toggle_style),
204                Span::styled(
205                    if is_selected { "  (Enter 切换)" } else { "" },
206                    Style::default().fg(t.config_dim),
207                ),
208            ]));
209        } else if CONFIG_GLOBAL_FIELDS[i] == "theme" {
210            let theme_name = app.agent_config.theme.display_name();
211            lines.push(Line::from(vec![
212                Span::styled(pointer, pointer_style),
213                Span::styled(format!("{:<10}", label), label_style),
214                Span::styled("  ", Style::default()),
215                Span::styled(
216                    format!("🎨 {}", theme_name),
217                    Style::default()
218                        .fg(t.config_toggle_on)
219                        .add_modifier(Modifier::BOLD),
220                ),
221                Span::styled(
222                    if is_selected { "  (Enter 切换)" } else { "" },
223                    Style::default().fg(t.config_dim),
224                ),
225            ]));
226        } else {
227            lines.push(Line::from(vec![
228                Span::styled(pointer, pointer_style),
229                Span::styled(format!("{:<10}", label), label_style),
230                Span::styled("  ", Style::default()),
231                Span::styled(
232                    if value.is_empty() {
233                        "(空)".to_string()
234                    } else {
235                        value
236                    },
237                    value_style,
238                ),
239                Span::styled(edit_indicator, Style::default()),
240            ]));
241        }
242    }
243
244    lines.push(Line::from(""));
245    lines.push(Line::from(""));
246
247    lines.push(Line::from(Span::styled(
248        "  ─────────────────────────────────────────",
249        Style::default().fg(t.separator),
250    )));
251    lines.push(Line::from(""));
252    lines.push(Line::from(vec![
253        Span::styled("    ", Style::default()),
254        Span::styled(
255            "↑↓/jk",
256            Style::default()
257                .fg(t.config_hint_key)
258                .add_modifier(Modifier::BOLD),
259        ),
260        Span::styled(" 切换字段  ", Style::default().fg(t.config_hint_desc)),
261        Span::styled(
262            "Enter",
263            Style::default()
264                .fg(t.config_hint_key)
265                .add_modifier(Modifier::BOLD),
266        ),
267        Span::styled(" 编辑  ", Style::default().fg(t.config_hint_desc)),
268        Span::styled(
269            "Tab/←→",
270            Style::default()
271                .fg(t.config_hint_key)
272                .add_modifier(Modifier::BOLD),
273        ),
274        Span::styled(" 切换 Provider  ", Style::default().fg(t.config_hint_desc)),
275        Span::styled(
276            "a",
277            Style::default()
278                .fg(t.config_hint_key)
279                .add_modifier(Modifier::BOLD),
280        ),
281        Span::styled(" 新增  ", Style::default().fg(t.config_hint_desc)),
282        Span::styled(
283            "d",
284            Style::default()
285                .fg(t.config_hint_key)
286                .add_modifier(Modifier::BOLD),
287        ),
288        Span::styled(" 删除  ", Style::default().fg(t.config_hint_desc)),
289        Span::styled(
290            "s",
291            Style::default()
292                .fg(t.config_hint_key)
293                .add_modifier(Modifier::BOLD),
294        ),
295        Span::styled(" 设为活跃  ", Style::default().fg(t.config_hint_desc)),
296        Span::styled(
297            "Esc",
298            Style::default()
299                .fg(t.config_hint_key)
300                .add_modifier(Modifier::BOLD),
301        ),
302        Span::styled(" 保存返回", Style::default().fg(t.config_hint_desc)),
303    ]));
304
305    let content = Paragraph::new(lines)
306        .block(
307            Block::default()
308                .borders(Borders::ALL)
309                .border_type(ratatui::widgets::BorderType::Rounded)
310                .border_style(Style::default().fg(t.border_config))
311                .title(Span::styled(
312                    " ⚙️  模型配置编辑 ",
313                    Style::default()
314                        .fg(t.config_label_selected)
315                        .add_modifier(Modifier::BOLD),
316                ))
317                .style(Style::default().bg(bg)),
318        )
319        .scroll((0, 0));
320    f.render_widget(content, area);
321}