1use super::super::app::ChatApp;
2use ratatui::{
3 layout::Rect,
4 style::{Modifier, Style},
5 text::{Line, Span},
6 widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
7};
8
9pub fn draw_archive_confirm(f: &mut ratatui::Frame, area: Rect, app: &ChatApp) {
11 let t = &app.theme;
12 let mut lines: Vec<Line> = Vec::new();
13
14 lines.push(Line::from(""));
15 lines.push(Line::from(Span::styled(
16 " 📦 归档当前对话",
17 Style::default()
18 .fg(t.help_title)
19 .add_modifier(Modifier::BOLD),
20 )));
21 lines.push(Line::from(""));
22 lines.push(Line::from(Span::styled(
23 " ─────────────────────────────────────────",
24 Style::default().fg(t.separator),
25 )));
26 lines.push(Line::from(""));
27 lines.push(Line::from(Span::styled(
28 " 即将归档当前对话,归档后当前会话将被清空。",
29 Style::default().fg(t.text_dim),
30 )));
31 lines.push(Line::from(""));
32
33 if app.archive_editing_name {
34 lines.push(Line::from(Span::styled(
35 " 请输入归档名称:",
36 Style::default().fg(t.text_white),
37 )));
38 lines.push(Line::from(""));
39
40 let name_with_cursor = if app.archive_custom_name.is_empty() {
41 vec![Span::styled(
42 " ",
43 Style::default().fg(t.cursor_fg).bg(t.cursor_bg),
44 )]
45 } else {
46 let chars: Vec<char> = app.archive_custom_name.chars().collect();
47 let mut spans: Vec<Span> = Vec::new();
48 for (i, &ch) in chars.iter().enumerate() {
49 if i == app.archive_edit_cursor {
50 spans.push(Span::styled(
51 ch.to_string(),
52 Style::default().fg(t.cursor_fg).bg(t.cursor_bg),
53 ));
54 } else {
55 spans.push(Span::styled(
56 ch.to_string(),
57 Style::default().fg(t.text_white),
58 ));
59 }
60 }
61 if app.archive_edit_cursor >= chars.len() {
62 spans.push(Span::styled(
63 " ",
64 Style::default().fg(t.cursor_fg).bg(t.cursor_bg),
65 ));
66 }
67 spans
68 };
69
70 lines.push(Line::from(vec![
71 Span::styled(" ", Style::default()),
72 Span::styled(
73 format!("archive-{}", chrono::Local::now().format("%Y-%m-%d")),
74 Style::default().fg(t.text_dim),
75 ),
76 ]));
77 lines.push(Line::from(
78 std::iter::once(Span::styled(" ", Style::default()))
79 .chain(name_with_cursor.into_iter())
80 .collect::<Vec<_>>(),
81 ));
82 lines.push(Line::from(""));
83 lines.push(Line::from(Span::styled(
84 " 提示:留空则使用默认名称(如 archive-2026-02-25)",
85 Style::default().fg(t.text_dim),
86 )));
87 lines.push(Line::from(""));
88 lines.push(Line::from(Span::styled(
89 " ─────────────────────────────────────────",
90 Style::default().fg(t.separator),
91 )));
92 lines.push(Line::from(""));
93 lines.push(Line::from(vec![
94 Span::styled(" ", Style::default()),
95 Span::styled(
96 "Enter",
97 Style::default().fg(t.help_key).add_modifier(Modifier::BOLD),
98 ),
99 Span::styled(" 确认归档", Style::default().fg(t.help_desc)),
100 ]));
101 lines.push(Line::from(vec![
102 Span::styled(" ", Style::default()),
103 Span::styled(
104 "Esc",
105 Style::default().fg(t.help_key).add_modifier(Modifier::BOLD),
106 ),
107 Span::styled(" 取消", Style::default().fg(t.help_desc)),
108 ]));
109 } else {
110 lines.push(Line::from(vec![
111 Span::styled(" 默认名称:", Style::default().fg(t.text_dim)),
112 Span::styled(
113 &app.archive_default_name,
114 Style::default()
115 .fg(t.config_toggle_on)
116 .add_modifier(Modifier::BOLD),
117 ),
118 ]));
119 lines.push(Line::from(""));
120 lines.push(Line::from(Span::styled(
121 " ─────────────────────────────────────────",
122 Style::default().fg(t.separator),
123 )));
124 lines.push(Line::from(""));
125 lines.push(Line::from(vec![
126 Span::styled(" ", Style::default()),
127 Span::styled(
128 "Enter",
129 Style::default().fg(t.help_key).add_modifier(Modifier::BOLD),
130 ),
131 Span::styled(" 使用默认名称归档", Style::default().fg(t.help_desc)),
132 ]));
133 lines.push(Line::from(vec![
134 Span::styled(" ", Style::default()),
135 Span::styled(
136 "n",
137 Style::default().fg(t.help_key).add_modifier(Modifier::BOLD),
138 ),
139 Span::styled(" 自定义名称", Style::default().fg(t.help_desc)),
140 ]));
141 lines.push(Line::from(vec![
142 Span::styled(" ", Style::default()),
143 Span::styled(
144 "d",
145 Style::default().fg(t.help_key).add_modifier(Modifier::BOLD),
146 ),
147 Span::styled(" 仅清空不归档", Style::default().fg(t.help_desc)),
148 ]));
149 lines.push(Line::from(vec![
150 Span::styled(" ", Style::default()),
151 Span::styled(
152 "Esc",
153 Style::default().fg(t.help_key).add_modifier(Modifier::BOLD),
154 ),
155 Span::styled(" 取消", Style::default().fg(t.help_desc)),
156 ]));
157 }
158
159 let block = Block::default()
160 .borders(Borders::ALL)
161 .border_type(ratatui::widgets::BorderType::Rounded)
162 .border_style(Style::default().fg(t.border_title))
163 .title(Span::styled(" 归档确认 ", Style::default().fg(t.text_dim)))
164 .style(Style::default().bg(t.help_bg));
165 let widget = Paragraph::new(lines).block(block);
166 f.render_widget(widget, area);
167}
168
169pub fn draw_archive_list(f: &mut ratatui::Frame, area: Rect, app: &ChatApp) {
171 let t = &app.theme;
172
173 if app.restore_confirm_needed {
174 let mut lines: Vec<Line> = Vec::new();
175 lines.push(Line::from(""));
176 lines.push(Line::from(Span::styled(
177 " ⚠️ 确认还原",
178 Style::default()
179 .fg(t.toast_error_text)
180 .add_modifier(Modifier::BOLD),
181 )));
182 lines.push(Line::from(""));
183 lines.push(Line::from(Span::styled(
184 " 当前对话未归档,还原将丢失当前对话内容!",
185 Style::default().fg(t.text_white),
186 )));
187 lines.push(Line::from(""));
188 lines.push(Line::from(Span::styled(
189 " ─────────────────────────────────────────",
190 Style::default().fg(t.separator),
191 )));
192 lines.push(Line::from(""));
193 if let Some(archive) = app.archives.get(app.archive_list_index) {
194 lines.push(Line::from(vec![
195 Span::styled(" 将还原归档:", Style::default().fg(t.text_dim)),
196 Span::styled(
197 &archive.name,
198 Style::default()
199 .fg(t.config_toggle_on)
200 .add_modifier(Modifier::BOLD),
201 ),
202 ]));
203 }
204 lines.push(Line::from(""));
205 lines.push(Line::from(vec![
206 Span::styled(" ", Style::default()),
207 Span::styled(
208 "y/Enter",
209 Style::default().fg(t.help_key).add_modifier(Modifier::BOLD),
210 ),
211 Span::styled(" 确认还原", Style::default().fg(t.help_desc)),
212 ]));
213 lines.push(Line::from(vec![
214 Span::styled(" ", Style::default()),
215 Span::styled(
216 "Esc",
217 Style::default().fg(t.help_key).add_modifier(Modifier::BOLD),
218 ),
219 Span::styled(" 取消", Style::default().fg(t.help_desc)),
220 ]));
221
222 let block = Block::default()
223 .borders(Borders::ALL)
224 .border_type(ratatui::widgets::BorderType::Rounded)
225 .border_style(Style::default().fg(t.toast_error_border))
226 .title(Span::styled(" 还原确认 ", Style::default().fg(t.text_dim)))
227 .style(Style::default().bg(t.help_bg));
228 let widget = Paragraph::new(lines).block(block);
229 f.render_widget(widget, area);
230 return;
231 }
232
233 if app.archives.is_empty() {
234 let lines = vec![
235 Line::from(""),
236 Line::from(""),
237 Line::from(Span::styled(
238 " 📦 暂无归档对话",
239 Style::default().fg(t.text_dim).add_modifier(Modifier::BOLD),
240 )),
241 Line::from(""),
242 Line::from(Span::styled(
243 " 按 Ctrl+L 归档当前对话",
244 Style::default().fg(t.text_dim),
245 )),
246 Line::from(""),
247 Line::from(Span::styled(
248 " 按 Esc 返回聊天",
249 Style::default().fg(t.text_dim),
250 )),
251 ];
252
253 let block = Block::default()
254 .borders(Borders::ALL)
255 .border_type(ratatui::widgets::BorderType::Rounded)
256 .border_style(Style::default().fg(t.border_title))
257 .title(Span::styled(" 归档列表 ", Style::default().fg(t.text_dim)))
258 .style(Style::default().bg(t.help_bg));
259 let widget = Paragraph::new(lines).block(block);
260 f.render_widget(widget, area);
261 return;
262 }
263
264 let items: Vec<ListItem> = app
265 .archives
266 .iter()
267 .enumerate()
268 .map(|(i, archive)| {
269 let is_selected = i == app.archive_list_index;
270 let marker = if is_selected { " ▸ " } else { " " };
271 let msg_count = archive.messages.len();
272 let created_at = archive
273 .created_at
274 .split('T')
275 .next()
276 .unwrap_or(&archive.created_at);
277 let style = if is_selected {
278 Style::default()
279 .fg(t.model_sel_active)
280 .add_modifier(Modifier::BOLD)
281 } else {
282 Style::default().fg(t.model_sel_inactive)
283 };
284 let detail = format!(
285 "{}{} 📨 {} 条消息 📅 {}",
286 marker, archive.name, msg_count, created_at
287 );
288 ListItem::new(Line::from(Span::styled(detail, style)))
289 })
290 .collect();
291
292 let list = List::new(items)
293 .block(
294 Block::default()
295 .borders(Borders::ALL)
296 .border_type(ratatui::widgets::BorderType::Rounded)
297 .border_style(Style::default().fg(t.model_sel_border))
298 .title(Span::styled(
299 " 📦 归档列表 (Enter 还原, d 删除, Esc 返回) ",
300 Style::default()
301 .fg(t.model_sel_title)
302 .add_modifier(Modifier::BOLD),
303 ))
304 .style(Style::default().bg(t.bg_title)),
305 )
306 .highlight_style(
307 Style::default()
308 .bg(t.model_sel_highlight_bg)
309 .fg(t.text_white)
310 .add_modifier(Modifier::BOLD),
311 )
312 .highlight_symbol("");
313
314 let mut list_state = ListState::default();
315 list_state.select(Some(app.archive_list_index));
316 f.render_stateful_widget(list, area, &mut list_state);
317}