1use super::app::{CONFIG_FIELDS, CONFIG_GLOBAL_FIELDS, ChatApp, ChatMode, config_total_fields};
2use super::model::{ModelProvider, save_agent_config, save_chat_session};
3use super::render::copy_to_clipboard;
4use super::theme::ThemeName;
5use super::ui::draw_chat_ui;
6use crate::{error, info};
7use crossterm::{
8 event::{
9 self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers,
10 MouseEventKind,
11 },
12 execute,
13 terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
14};
15use ratatui::{Terminal, backend::CrosstermBackend};
16use std::io;
17
18pub fn run_chat_tui() {
19 match run_chat_tui_internal() {
20 Ok(_) => {}
21 Err(e) => {
22 error!("❌ Chat TUI 启动失败: {}", e);
23 }
24 }
25}
26
27pub fn run_chat_tui_internal() -> io::Result<()> {
28 terminal::enable_raw_mode()?;
29 let mut stdout = io::stdout();
30 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
31
32 let backend = CrosstermBackend::new(stdout);
33 let mut terminal = Terminal::new(backend)?;
34
35 let mut app = ChatApp::new();
36
37 if app.agent_config.providers.is_empty() {
38 terminal::disable_raw_mode()?;
39 execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
40 info!("⚠️ 尚未配置 LLM 模型提供方,请先运行 j chat 查看配置说明。");
41 return Ok(());
42 }
43
44 let mut needs_redraw = true; loop {
47 let had_toast = app.toast.is_some();
49 app.tick_toast();
50 if had_toast && app.toast.is_none() {
51 needs_redraw = true;
52 }
53
54 let was_loading = app.is_loading;
56 app.poll_stream();
57 if app.is_loading {
59 let current_len = app.streaming_content.lock().unwrap().len();
60 let bytes_delta = current_len.saturating_sub(app.last_rendered_streaming_len);
61 let time_elapsed = app.last_stream_render_time.elapsed();
62 if bytes_delta >= 200
64 || time_elapsed >= std::time::Duration::from_millis(200)
65 || current_len == 0
66 {
67 needs_redraw = true;
68 }
69 } else if was_loading {
70 needs_redraw = true;
72 }
73
74 if needs_redraw {
76 terminal.draw(|f| draw_chat_ui(f, &mut app))?;
77 needs_redraw = false;
78 if app.is_loading {
80 app.last_rendered_streaming_len = app.streaming_content.lock().unwrap().len();
81 app.last_stream_render_time = std::time::Instant::now();
82 }
83 }
84
85 let poll_timeout = if app.is_loading {
87 std::time::Duration::from_millis(150)
88 } else {
89 std::time::Duration::from_millis(1000)
90 };
91
92 if event::poll(poll_timeout)? {
93 let mut should_break = false;
95 loop {
96 let evt = event::read()?;
97 match evt {
98 Event::Key(key) => {
99 needs_redraw = true;
100 match app.mode {
101 ChatMode::Chat => {
102 if handle_chat_mode(&mut app, key) {
103 should_break = true;
104 break;
105 }
106 }
107 ChatMode::SelectModel => handle_select_model(&mut app, key),
108 ChatMode::Browse => handle_browse_mode(&mut app, key),
109 ChatMode::Help => {
110 app.mode = ChatMode::Chat;
111 }
112 ChatMode::Config => handle_config_mode(&mut app, key),
113 ChatMode::ArchiveConfirm => handle_archive_confirm_mode(&mut app, key),
114 ChatMode::ArchiveList => handle_archive_list_mode(&mut app, key),
115 }
116 }
117 Event::Mouse(mouse) => match mouse.kind {
118 MouseEventKind::ScrollUp => {
119 app.scroll_up();
120 needs_redraw = true;
121 }
122 MouseEventKind::ScrollDown => {
123 app.scroll_down();
124 needs_redraw = true;
125 }
126 _ => {}
127 },
128 Event::Resize(_, _) => {
129 needs_redraw = true;
130 }
131 _ => {}
132 }
133 if !event::poll(std::time::Duration::ZERO)? {
135 break;
136 }
137 }
138 if should_break {
139 break;
140 }
141 }
142 }
143
144 let _ = save_chat_session(&app.session);
146
147 terminal::disable_raw_mode()?;
148 execute!(
149 terminal.backend_mut(),
150 LeaveAlternateScreen,
151 DisableMouseCapture
152 )?;
153 Ok(())
154}
155
156pub fn handle_chat_mode(app: &mut ChatApp, key: KeyEvent) -> bool {
159 if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') {
161 return true;
162 }
163
164 if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('t') {
166 if !app.agent_config.providers.is_empty() {
167 app.mode = ChatMode::SelectModel;
168 app.model_list_state
169 .select(Some(app.agent_config.active_index));
170 }
171 return false;
172 }
173
174 if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('l') {
176 if app.session.messages.is_empty() {
177 app.show_toast("当前对话为空,无法归档", true);
178 } else {
179 app.start_archive_confirm();
180 }
181 return false;
182 }
183
184 if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('r') {
186 app.start_archive_list();
187 return false;
188 }
189
190 if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('y') {
192 if let Some(last_ai) = app
193 .session
194 .messages
195 .iter()
196 .rev()
197 .find(|m| m.role == "assistant")
198 {
199 if copy_to_clipboard(&last_ai.content) {
200 app.show_toast("已复制最后一条 AI 回复", false);
201 } else {
202 app.show_toast("复制到剪切板失败", true);
203 }
204 } else {
205 app.show_toast("暂无 AI 回复可复制", true);
206 }
207 return false;
208 }
209
210 if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('b') {
212 if !app.session.messages.is_empty() {
213 app.browse_msg_index = app.session.messages.len() - 1;
215 app.mode = ChatMode::Browse;
216 app.msg_lines_cache = None; let _ = execute!(io::stdout(), DisableMouseCapture);
219 } else {
220 app.show_toast("暂无消息可浏览", true);
221 }
222 return false;
223 }
224
225 if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('e') {
227 app.config_provider_idx = app
229 .agent_config
230 .active_index
231 .min(app.agent_config.providers.len().saturating_sub(1));
232 app.config_field_idx = 0;
233 app.config_editing = false;
234 app.config_edit_buf.clear();
235 app.mode = ChatMode::Config;
236 return false;
237 }
238
239 if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('s') {
241 app.agent_config.stream_mode = !app.agent_config.stream_mode;
242 let _ = save_agent_config(&app.agent_config);
243 let mode_str = if app.agent_config.stream_mode {
244 "流式输出"
245 } else {
246 "整体输出"
247 };
248 app.show_toast(&format!("已切换为: {}", mode_str), false);
249 return false;
250 }
251
252 let char_count = app.input.chars().count();
253
254 match key.code {
255 KeyCode::Esc => return true,
256
257 KeyCode::Enter => {
258 if !app.is_loading {
259 app.send_message();
260 }
261 }
262
263 KeyCode::Up => app.scroll_up(),
265 KeyCode::Down => app.scroll_down(),
266 KeyCode::PageUp => {
267 for _ in 0..10 {
268 app.scroll_up();
269 }
270 }
271 KeyCode::PageDown => {
272 for _ in 0..10 {
273 app.scroll_down();
274 }
275 }
276
277 KeyCode::Left => {
279 if app.cursor_pos > 0 {
280 app.cursor_pos -= 1;
281 }
282 }
283 KeyCode::Right => {
284 if app.cursor_pos < char_count {
285 app.cursor_pos += 1;
286 }
287 }
288 KeyCode::Home => app.cursor_pos = 0,
289 KeyCode::End => app.cursor_pos = char_count,
290
291 KeyCode::Backspace => {
293 if app.cursor_pos > 0 {
294 let start = app
295 .input
296 .char_indices()
297 .nth(app.cursor_pos - 1)
298 .map(|(i, _)| i)
299 .unwrap_or(0);
300 let end = app
301 .input
302 .char_indices()
303 .nth(app.cursor_pos)
304 .map(|(i, _)| i)
305 .unwrap_or(app.input.len());
306 app.input.drain(start..end);
307 app.cursor_pos -= 1;
308 }
309 }
310 KeyCode::Delete => {
311 if app.cursor_pos < char_count {
312 let start = app
313 .input
314 .char_indices()
315 .nth(app.cursor_pos)
316 .map(|(i, _)| i)
317 .unwrap_or(app.input.len());
318 let end = app
319 .input
320 .char_indices()
321 .nth(app.cursor_pos + 1)
322 .map(|(i, _)| i)
323 .unwrap_or(app.input.len());
324 app.input.drain(start..end);
325 }
326 }
327
328 KeyCode::F(1) => {
330 app.mode = ChatMode::Help;
331 }
332 KeyCode::Char('?') if app.input.is_empty() => {
334 app.mode = ChatMode::Help;
335 }
336 KeyCode::Char(c) => {
337 let byte_idx = app
338 .input
339 .char_indices()
340 .nth(app.cursor_pos)
341 .map(|(i, _)| i)
342 .unwrap_or(app.input.len());
343 app.input.insert_str(byte_idx, &c.to_string());
344 app.cursor_pos += 1;
345 }
346
347 _ => {}
348 }
349
350 false
351}
352
353pub fn handle_browse_mode(app: &mut ChatApp, key: KeyEvent) {
355 let msg_count = app.session.messages.len();
356 if msg_count == 0 {
357 app.mode = ChatMode::Chat;
358 app.msg_lines_cache = None;
359 let _ = execute!(io::stdout(), EnableMouseCapture);
361 return;
362 }
363
364 match key.code {
365 KeyCode::Esc => {
366 app.mode = ChatMode::Chat;
367 app.msg_lines_cache = None; let _ = execute!(io::stdout(), EnableMouseCapture);
370 }
371 KeyCode::Up | KeyCode::Char('k') => {
372 if app.browse_msg_index > 0 {
373 app.browse_msg_index -= 1;
374 app.msg_lines_cache = None; }
376 }
377 KeyCode::Down | KeyCode::Char('j') => {
378 if app.browse_msg_index < msg_count - 1 {
379 app.browse_msg_index += 1;
380 app.msg_lines_cache = None; }
382 }
383 KeyCode::Enter | KeyCode::Char('y') => {
384 if let Some(msg) = app.session.messages.get(app.browse_msg_index) {
386 let content = msg.content.clone();
387 let role_label = if msg.role == "assistant" {
388 "AI"
389 } else if msg.role == "user" {
390 "用户"
391 } else {
392 "系统"
393 };
394 if copy_to_clipboard(&content) {
395 app.show_toast(
396 &format!("已复制第 {} 条{}消息", app.browse_msg_index + 1, role_label),
397 false,
398 );
399 } else {
400 app.show_toast("复制到剪切板失败", true);
401 }
402 }
403 }
404 _ => {}
405 }
406}
407
408pub fn config_field_label(idx: usize) -> &'static str {
410 let total_provider = CONFIG_FIELDS.len();
411 if idx < total_provider {
412 match CONFIG_FIELDS[idx] {
413 "name" => "显示名称",
414 "api_base" => "API Base",
415 "api_key" => "API Key",
416 "model" => "模型名称",
417 _ => CONFIG_FIELDS[idx],
418 }
419 } else {
420 let gi = idx - total_provider;
421 match CONFIG_GLOBAL_FIELDS[gi] {
422 "system_prompt" => "系统提示词",
423 "stream_mode" => "流式输出",
424 "max_history_messages" => "历史消息数",
425 "theme" => "主题风格",
426 _ => CONFIG_GLOBAL_FIELDS[gi],
427 }
428 }
429}
430
431pub fn config_field_value(app: &ChatApp, field_idx: usize) -> String {
433 let total_provider = CONFIG_FIELDS.len();
434 if field_idx < total_provider {
435 if app.agent_config.providers.is_empty() {
436 return String::new();
437 }
438 let p = &app.agent_config.providers[app.config_provider_idx];
439 match CONFIG_FIELDS[field_idx] {
440 "name" => p.name.clone(),
441 "api_base" => p.api_base.clone(),
442 "api_key" => {
443 if p.api_key.len() > 8 {
445 format!(
446 "{}****{}",
447 &p.api_key[..4],
448 &p.api_key[p.api_key.len() - 4..]
449 )
450 } else {
451 p.api_key.clone()
452 }
453 }
454 "model" => p.model.clone(),
455 _ => String::new(),
456 }
457 } else {
458 let gi = field_idx - total_provider;
459 match CONFIG_GLOBAL_FIELDS[gi] {
460 "system_prompt" => app.agent_config.system_prompt.clone().unwrap_or_default(),
461 "stream_mode" => {
462 if app.agent_config.stream_mode {
463 "开启".into()
464 } else {
465 "关闭".into()
466 }
467 }
468 "max_history_messages" => app.agent_config.max_history_messages.to_string(),
469 "theme" => app.agent_config.theme.display_name().to_string(),
470 _ => String::new(),
471 }
472 }
473}
474
475pub fn config_field_raw_value(app: &ChatApp, field_idx: usize) -> String {
477 let total_provider = CONFIG_FIELDS.len();
478 if field_idx < total_provider {
479 if app.agent_config.providers.is_empty() {
480 return String::new();
481 }
482 let p = &app.agent_config.providers[app.config_provider_idx];
483 match CONFIG_FIELDS[field_idx] {
484 "name" => p.name.clone(),
485 "api_base" => p.api_base.clone(),
486 "api_key" => p.api_key.clone(),
487 "model" => p.model.clone(),
488 _ => String::new(),
489 }
490 } else {
491 let gi = field_idx - total_provider;
492 match CONFIG_GLOBAL_FIELDS[gi] {
493 "system_prompt" => app.agent_config.system_prompt.clone().unwrap_or_default(),
494 "stream_mode" => {
495 if app.agent_config.stream_mode {
496 "true".into()
497 } else {
498 "false".into()
499 }
500 }
501 "theme" => app.agent_config.theme.to_str().to_string(),
502 _ => String::new(),
503 }
504 }
505}
506
507pub fn config_field_set(app: &mut ChatApp, field_idx: usize, value: &str) {
509 let total_provider = CONFIG_FIELDS.len();
510 if field_idx < total_provider {
511 if app.agent_config.providers.is_empty() {
512 return;
513 }
514 let p = &mut app.agent_config.providers[app.config_provider_idx];
515 match CONFIG_FIELDS[field_idx] {
516 "name" => p.name = value.to_string(),
517 "api_base" => p.api_base = value.to_string(),
518 "api_key" => p.api_key = value.to_string(),
519 "model" => p.model = value.to_string(),
520 _ => {}
521 }
522 } else {
523 let gi = field_idx - total_provider;
524 match CONFIG_GLOBAL_FIELDS[gi] {
525 "system_prompt" => {
526 if value.is_empty() {
527 app.agent_config.system_prompt = None;
528 } else {
529 app.agent_config.system_prompt = Some(value.to_string());
530 }
531 }
532 "stream_mode" => {
533 app.agent_config.stream_mode = matches!(
534 value.trim().to_lowercase().as_str(),
535 "true" | "1" | "开启" | "on" | "yes"
536 );
537 }
538 "max_history_messages" => {
539 if let Ok(num) = value.trim().parse::<usize>() {
540 app.agent_config.max_history_messages = num;
541 }
542 }
543 "theme" => {
544 app.agent_config.theme = ThemeName::from_str(value.trim());
545 app.theme = super::theme::Theme::from_name(&app.agent_config.theme);
546 app.msg_lines_cache = None;
547 }
548 _ => {}
549 }
550 }
551}
552
553pub fn handle_config_mode(app: &mut ChatApp, key: KeyEvent) {
555 let total_fields = config_total_fields();
556
557 if app.config_editing {
558 match key.code {
560 KeyCode::Esc => {
561 app.config_editing = false;
563 }
564 KeyCode::Enter => {
565 let val = app.config_edit_buf.clone();
567 config_field_set(app, app.config_field_idx, &val);
568 app.config_editing = false;
569 }
570 KeyCode::Backspace => {
571 if app.config_edit_cursor > 0 {
572 let idx = app
573 .config_edit_buf
574 .char_indices()
575 .nth(app.config_edit_cursor - 1)
576 .map(|(i, _)| i)
577 .unwrap_or(0);
578 let end_idx = app
579 .config_edit_buf
580 .char_indices()
581 .nth(app.config_edit_cursor)
582 .map(|(i, _)| i)
583 .unwrap_or(app.config_edit_buf.len());
584 app.config_edit_buf = format!(
585 "{}{}",
586 &app.config_edit_buf[..idx],
587 &app.config_edit_buf[end_idx..]
588 );
589 app.config_edit_cursor -= 1;
590 }
591 }
592 KeyCode::Left => {
593 app.config_edit_cursor = app.config_edit_cursor.saturating_sub(1);
594 }
595 KeyCode::Right => {
596 let char_count = app.config_edit_buf.chars().count();
597 if app.config_edit_cursor < char_count {
598 app.config_edit_cursor += 1;
599 }
600 }
601 KeyCode::Char(c) => {
602 let byte_idx = app
603 .config_edit_buf
604 .char_indices()
605 .nth(app.config_edit_cursor)
606 .map(|(i, _)| i)
607 .unwrap_or(app.config_edit_buf.len());
608 app.config_edit_buf.insert(byte_idx, c);
609 app.config_edit_cursor += 1;
610 }
611 _ => {}
612 }
613 return;
614 }
615
616 match key.code {
618 KeyCode::Esc => {
619 let _ = save_agent_config(&app.agent_config);
621 app.show_toast("配置已保存 ✅", false);
622 app.mode = ChatMode::Chat;
623 }
624 KeyCode::Up | KeyCode::Char('k') => {
625 if total_fields > 0 {
626 if app.config_field_idx == 0 {
627 app.config_field_idx = total_fields - 1;
628 } else {
629 app.config_field_idx -= 1;
630 }
631 }
632 }
633 KeyCode::Down | KeyCode::Char('j') => {
634 if total_fields > 0 {
635 app.config_field_idx = (app.config_field_idx + 1) % total_fields;
636 }
637 }
638 KeyCode::Tab | KeyCode::Right => {
639 let count = app.agent_config.providers.len();
641 if count > 1 {
642 app.config_provider_idx = (app.config_provider_idx + 1) % count;
643 }
645 }
646 KeyCode::BackTab | KeyCode::Left => {
647 let count = app.agent_config.providers.len();
649 if count > 1 {
650 if app.config_provider_idx == 0 {
651 app.config_provider_idx = count - 1;
652 } else {
653 app.config_provider_idx -= 1;
654 }
655 }
656 }
657 KeyCode::Enter => {
658 let total_provider = CONFIG_FIELDS.len();
660 if app.config_field_idx < total_provider && app.agent_config.providers.is_empty() {
661 app.show_toast("还没有 Provider,按 a 新增", true);
662 return;
663 }
664 let gi = app.config_field_idx.checked_sub(total_provider);
666 if let Some(gi) = gi {
667 if CONFIG_GLOBAL_FIELDS[gi] == "stream_mode" {
668 app.agent_config.stream_mode = !app.agent_config.stream_mode;
669 return;
670 }
671 if CONFIG_GLOBAL_FIELDS[gi] == "theme" {
673 app.switch_theme();
674 return;
675 }
676 }
677 app.config_edit_buf = config_field_raw_value(app, app.config_field_idx);
678 app.config_edit_cursor = app.config_edit_buf.chars().count();
679 app.config_editing = true;
680 }
681 KeyCode::Char('a') => {
682 let new_provider = ModelProvider {
684 name: format!("Provider-{}", app.agent_config.providers.len() + 1),
685 api_base: "https://api.openai.com/v1".to_string(),
686 api_key: String::new(),
687 model: String::new(),
688 };
689 app.agent_config.providers.push(new_provider);
690 app.config_provider_idx = app.agent_config.providers.len() - 1;
691 app.config_field_idx = 0; app.show_toast("已新增 Provider,请填写配置", false);
693 }
694 KeyCode::Char('d') => {
695 let count = app.agent_config.providers.len();
697 if count == 0 {
698 app.show_toast("没有可删除的 Provider", true);
699 } else {
700 let removed_name = app.agent_config.providers[app.config_provider_idx]
701 .name
702 .clone();
703 app.agent_config.providers.remove(app.config_provider_idx);
704 if app.config_provider_idx >= app.agent_config.providers.len()
706 && app.config_provider_idx > 0
707 {
708 app.config_provider_idx -= 1;
709 }
710 if app.agent_config.active_index >= app.agent_config.providers.len()
712 && app.agent_config.active_index > 0
713 {
714 app.agent_config.active_index -= 1;
715 }
716 app.show_toast(format!("已删除 Provider: {}", removed_name), false);
717 }
718 }
719 KeyCode::Char('s') => {
720 if !app.agent_config.providers.is_empty() {
722 app.agent_config.active_index = app.config_provider_idx;
723 let name = app.agent_config.providers[app.config_provider_idx]
724 .name
725 .clone();
726 app.show_toast(format!("已设为活跃模型: {}", name), false);
727 }
728 }
729 _ => {}
730 }
731}
732
733pub fn handle_select_model(app: &mut ChatApp, key: KeyEvent) {
735 let count = app.agent_config.providers.len();
736 match key.code {
737 KeyCode::Esc => {
738 app.mode = ChatMode::Chat;
739 }
740 KeyCode::Up | KeyCode::Char('k') => {
741 if count > 0 {
742 let i = app
743 .model_list_state
744 .selected()
745 .map(|i| if i == 0 { count - 1 } else { i - 1 })
746 .unwrap_or(0);
747 app.model_list_state.select(Some(i));
748 }
749 }
750 KeyCode::Down | KeyCode::Char('j') => {
751 if count > 0 {
752 let i = app
753 .model_list_state
754 .selected()
755 .map(|i| if i >= count - 1 { 0 } else { i + 1 })
756 .unwrap_or(0);
757 app.model_list_state.select(Some(i));
758 }
759 }
760 KeyCode::Enter => {
761 app.switch_model();
762 }
763 _ => {}
764 }
765}
766
767pub fn handle_archive_confirm_mode(app: &mut ChatApp, key: KeyEvent) {
769 if app.archive_editing_name {
770 match key.code {
772 KeyCode::Esc => {
773 app.archive_editing_name = false;
774 app.archive_custom_name.clear();
775 app.archive_edit_cursor = 0;
776 }
777 KeyCode::Enter => {
778 let name = if app.archive_custom_name.is_empty() {
779 app.archive_default_name.clone()
780 } else {
781 app.archive_custom_name.clone()
782 };
783 if let Err(e) = super::archive::validate_archive_name(&name) {
785 app.show_toast(e, true);
786 return;
787 }
788 if super::archive::archive_exists(&name) {
790 let _ = super::archive::delete_archive(&name);
792 }
793 app.do_archive(&name);
794 }
795 KeyCode::Backspace => {
796 if app.archive_edit_cursor > 0 {
797 let chars: Vec<char> = app.archive_custom_name.chars().collect();
798 app.archive_custom_name = chars[..app.archive_edit_cursor - 1]
799 .iter()
800 .chain(chars[app.archive_edit_cursor..].iter())
801 .collect();
802 app.archive_edit_cursor -= 1;
803 }
804 }
805 KeyCode::Left => {
806 app.archive_edit_cursor = app.archive_edit_cursor.saturating_sub(1);
807 }
808 KeyCode::Right => {
809 let char_count = app.archive_custom_name.chars().count();
810 if app.archive_edit_cursor < char_count {
811 app.archive_edit_cursor += 1;
812 }
813 }
814 KeyCode::Char(c) => {
815 let chars: Vec<char> = app.archive_custom_name.chars().collect();
816 app.archive_custom_name = chars[..app.archive_edit_cursor]
817 .iter()
818 .chain(std::iter::once(&c))
819 .chain(chars[app.archive_edit_cursor..].iter())
820 .collect();
821 app.archive_edit_cursor += 1;
822 }
823 _ => {}
824 }
825 } else {
826 match key.code {
828 KeyCode::Esc => {
829 app.mode = ChatMode::Chat;
830 }
831 KeyCode::Enter => {
832 let name = app.archive_default_name.clone();
834 if super::archive::archive_exists(&name) {
836 let _ = super::archive::delete_archive(&name);
837 }
838 app.do_archive(&name);
839 }
840 KeyCode::Char('n') | KeyCode::Char('N') => {
841 app.archive_editing_name = true;
843 app.archive_custom_name.clear();
844 app.archive_edit_cursor = 0;
845 }
846 KeyCode::Char('d') | KeyCode::Char('D') => {
847 app.clear_session();
849 app.mode = ChatMode::Chat;
850 }
851 _ => {}
852 }
853 }
854}
855
856pub fn handle_archive_list_mode(app: &mut ChatApp, key: KeyEvent) {
858 let count = app.archives.len();
859
860 if app.restore_confirm_needed {
862 match key.code {
863 KeyCode::Esc => {
864 app.restore_confirm_needed = false;
865 }
866 KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
867 app.do_restore();
868 }
869 _ => {}
870 }
871 return;
872 }
873
874 match key.code {
875 KeyCode::Esc => {
876 app.mode = ChatMode::Chat;
877 }
878 KeyCode::Up | KeyCode::Char('k') => {
879 if count > 0 {
880 app.archive_list_index = if app.archive_list_index == 0 {
881 count - 1
882 } else {
883 app.archive_list_index - 1
884 };
885 }
886 }
887 KeyCode::Down | KeyCode::Char('j') => {
888 if count > 0 {
889 app.archive_list_index = if app.archive_list_index >= count - 1 {
890 0
891 } else {
892 app.archive_list_index + 1
893 };
894 }
895 }
896 KeyCode::Enter => {
897 if count > 0 {
898 if !app.session.messages.is_empty() {
900 app.restore_confirm_needed = true;
901 } else {
902 app.do_restore();
903 }
904 }
905 }
906 KeyCode::Char('d') | KeyCode::Char('D') => {
907 if count > 0 {
909 app.do_delete_archive();
910 }
911 }
912 _ => {}
913 }
914}