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; } else {
218 app.show_toast("暂无消息可浏览", true);
219 }
220 return false;
221 }
222
223 if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('e') {
225 app.config_provider_idx = app
227 .agent_config
228 .active_index
229 .min(app.agent_config.providers.len().saturating_sub(1));
230 app.config_field_idx = 0;
231 app.config_editing = false;
232 app.config_edit_buf.clear();
233 app.mode = ChatMode::Config;
234 return false;
235 }
236
237 if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('s') {
239 app.agent_config.stream_mode = !app.agent_config.stream_mode;
240 let _ = save_agent_config(&app.agent_config);
241 let mode_str = if app.agent_config.stream_mode {
242 "流式输出"
243 } else {
244 "整体输出"
245 };
246 app.show_toast(&format!("已切换为: {}", mode_str), false);
247 return false;
248 }
249
250 let char_count = app.input.chars().count();
251
252 match key.code {
253 KeyCode::Esc => return true,
254
255 KeyCode::Enter => {
256 if !app.is_loading {
257 app.send_message();
258 }
259 }
260
261 KeyCode::Up => app.scroll_up(),
263 KeyCode::Down => app.scroll_down(),
264 KeyCode::PageUp => {
265 for _ in 0..10 {
266 app.scroll_up();
267 }
268 }
269 KeyCode::PageDown => {
270 for _ in 0..10 {
271 app.scroll_down();
272 }
273 }
274
275 KeyCode::Left => {
277 if app.cursor_pos > 0 {
278 app.cursor_pos -= 1;
279 }
280 }
281 KeyCode::Right => {
282 if app.cursor_pos < char_count {
283 app.cursor_pos += 1;
284 }
285 }
286 KeyCode::Home => app.cursor_pos = 0,
287 KeyCode::End => app.cursor_pos = char_count,
288
289 KeyCode::Backspace => {
291 if app.cursor_pos > 0 {
292 let start = app
293 .input
294 .char_indices()
295 .nth(app.cursor_pos - 1)
296 .map(|(i, _)| i)
297 .unwrap_or(0);
298 let end = app
299 .input
300 .char_indices()
301 .nth(app.cursor_pos)
302 .map(|(i, _)| i)
303 .unwrap_or(app.input.len());
304 app.input.drain(start..end);
305 app.cursor_pos -= 1;
306 }
307 }
308 KeyCode::Delete => {
309 if app.cursor_pos < char_count {
310 let start = app
311 .input
312 .char_indices()
313 .nth(app.cursor_pos)
314 .map(|(i, _)| i)
315 .unwrap_or(app.input.len());
316 let end = app
317 .input
318 .char_indices()
319 .nth(app.cursor_pos + 1)
320 .map(|(i, _)| i)
321 .unwrap_or(app.input.len());
322 app.input.drain(start..end);
323 }
324 }
325
326 KeyCode::F(1) => {
328 app.mode = ChatMode::Help;
329 }
330 KeyCode::Char('?') if app.input.is_empty() => {
332 app.mode = ChatMode::Help;
333 }
334 KeyCode::Char(c) => {
335 let byte_idx = app
336 .input
337 .char_indices()
338 .nth(app.cursor_pos)
339 .map(|(i, _)| i)
340 .unwrap_or(app.input.len());
341 app.input.insert_str(byte_idx, &c.to_string());
342 app.cursor_pos += 1;
343 }
344
345 _ => {}
346 }
347
348 false
349}
350
351pub fn handle_browse_mode(app: &mut ChatApp, key: KeyEvent) {
353 let msg_count = app.session.messages.len();
354 if msg_count == 0 {
355 app.mode = ChatMode::Chat;
356 app.msg_lines_cache = None;
357 return;
358 }
359
360 match key.code {
361 KeyCode::Esc => {
362 app.mode = ChatMode::Chat;
363 app.msg_lines_cache = None; }
365 KeyCode::Up | KeyCode::Char('k') => {
366 if app.browse_msg_index > 0 {
367 app.browse_msg_index -= 1;
368 app.msg_lines_cache = None; }
370 }
371 KeyCode::Down | KeyCode::Char('j') => {
372 if app.browse_msg_index < msg_count - 1 {
373 app.browse_msg_index += 1;
374 app.msg_lines_cache = None; }
376 }
377 KeyCode::Enter | KeyCode::Char('y') => {
378 if let Some(msg) = app.session.messages.get(app.browse_msg_index) {
380 let content = msg.content.clone();
381 let role_label = if msg.role == "assistant" {
382 "AI"
383 } else if msg.role == "user" {
384 "用户"
385 } else {
386 "系统"
387 };
388 if copy_to_clipboard(&content) {
389 app.show_toast(
390 &format!("已复制第 {} 条{}消息", app.browse_msg_index + 1, role_label),
391 false,
392 );
393 } else {
394 app.show_toast("复制到剪切板失败", true);
395 }
396 }
397 }
398 _ => {}
399 }
400}
401
402pub fn config_field_label(idx: usize) -> &'static str {
404 let total_provider = CONFIG_FIELDS.len();
405 if idx < total_provider {
406 match CONFIG_FIELDS[idx] {
407 "name" => "显示名称",
408 "api_base" => "API Base",
409 "api_key" => "API Key",
410 "model" => "模型名称",
411 _ => CONFIG_FIELDS[idx],
412 }
413 } else {
414 let gi = idx - total_provider;
415 match CONFIG_GLOBAL_FIELDS[gi] {
416 "system_prompt" => "系统提示词",
417 "stream_mode" => "流式输出",
418 "max_history_messages" => "历史消息数",
419 "theme" => "主题风格",
420 _ => CONFIG_GLOBAL_FIELDS[gi],
421 }
422 }
423}
424
425pub fn config_field_value(app: &ChatApp, field_idx: usize) -> String {
427 let total_provider = CONFIG_FIELDS.len();
428 if field_idx < total_provider {
429 if app.agent_config.providers.is_empty() {
430 return String::new();
431 }
432 let p = &app.agent_config.providers[app.config_provider_idx];
433 match CONFIG_FIELDS[field_idx] {
434 "name" => p.name.clone(),
435 "api_base" => p.api_base.clone(),
436 "api_key" => {
437 if p.api_key.len() > 8 {
439 format!(
440 "{}****{}",
441 &p.api_key[..4],
442 &p.api_key[p.api_key.len() - 4..]
443 )
444 } else {
445 p.api_key.clone()
446 }
447 }
448 "model" => p.model.clone(),
449 _ => String::new(),
450 }
451 } else {
452 let gi = field_idx - total_provider;
453 match CONFIG_GLOBAL_FIELDS[gi] {
454 "system_prompt" => app.agent_config.system_prompt.clone().unwrap_or_default(),
455 "stream_mode" => {
456 if app.agent_config.stream_mode {
457 "开启".into()
458 } else {
459 "关闭".into()
460 }
461 }
462 "max_history_messages" => app.agent_config.max_history_messages.to_string(),
463 "theme" => app.agent_config.theme.display_name().to_string(),
464 _ => String::new(),
465 }
466 }
467}
468
469pub fn config_field_raw_value(app: &ChatApp, field_idx: usize) -> String {
471 let total_provider = CONFIG_FIELDS.len();
472 if field_idx < total_provider {
473 if app.agent_config.providers.is_empty() {
474 return String::new();
475 }
476 let p = &app.agent_config.providers[app.config_provider_idx];
477 match CONFIG_FIELDS[field_idx] {
478 "name" => p.name.clone(),
479 "api_base" => p.api_base.clone(),
480 "api_key" => p.api_key.clone(),
481 "model" => p.model.clone(),
482 _ => String::new(),
483 }
484 } else {
485 let gi = field_idx - total_provider;
486 match CONFIG_GLOBAL_FIELDS[gi] {
487 "system_prompt" => app.agent_config.system_prompt.clone().unwrap_or_default(),
488 "stream_mode" => {
489 if app.agent_config.stream_mode {
490 "true".into()
491 } else {
492 "false".into()
493 }
494 }
495 "theme" => app.agent_config.theme.to_str().to_string(),
496 _ => String::new(),
497 }
498 }
499}
500
501pub fn config_field_set(app: &mut ChatApp, field_idx: usize, value: &str) {
503 let total_provider = CONFIG_FIELDS.len();
504 if field_idx < total_provider {
505 if app.agent_config.providers.is_empty() {
506 return;
507 }
508 let p = &mut app.agent_config.providers[app.config_provider_idx];
509 match CONFIG_FIELDS[field_idx] {
510 "name" => p.name = value.to_string(),
511 "api_base" => p.api_base = value.to_string(),
512 "api_key" => p.api_key = value.to_string(),
513 "model" => p.model = value.to_string(),
514 _ => {}
515 }
516 } else {
517 let gi = field_idx - total_provider;
518 match CONFIG_GLOBAL_FIELDS[gi] {
519 "system_prompt" => {
520 if value.is_empty() {
521 app.agent_config.system_prompt = None;
522 } else {
523 app.agent_config.system_prompt = Some(value.to_string());
524 }
525 }
526 "stream_mode" => {
527 app.agent_config.stream_mode = matches!(
528 value.trim().to_lowercase().as_str(),
529 "true" | "1" | "开启" | "on" | "yes"
530 );
531 }
532 "max_history_messages" => {
533 if let Ok(num) = value.trim().parse::<usize>() {
534 app.agent_config.max_history_messages = num;
535 }
536 }
537 "theme" => {
538 app.agent_config.theme = ThemeName::from_str(value.trim());
539 app.theme = super::theme::Theme::from_name(&app.agent_config.theme);
540 app.msg_lines_cache = None;
541 }
542 _ => {}
543 }
544 }
545}
546
547pub fn handle_config_mode(app: &mut ChatApp, key: KeyEvent) {
549 let total_fields = config_total_fields();
550
551 if app.config_editing {
552 match key.code {
554 KeyCode::Esc => {
555 app.config_editing = false;
557 }
558 KeyCode::Enter => {
559 let val = app.config_edit_buf.clone();
561 config_field_set(app, app.config_field_idx, &val);
562 app.config_editing = false;
563 }
564 KeyCode::Backspace => {
565 if app.config_edit_cursor > 0 {
566 let idx = app
567 .config_edit_buf
568 .char_indices()
569 .nth(app.config_edit_cursor - 1)
570 .map(|(i, _)| i)
571 .unwrap_or(0);
572 let end_idx = app
573 .config_edit_buf
574 .char_indices()
575 .nth(app.config_edit_cursor)
576 .map(|(i, _)| i)
577 .unwrap_or(app.config_edit_buf.len());
578 app.config_edit_buf = format!(
579 "{}{}",
580 &app.config_edit_buf[..idx],
581 &app.config_edit_buf[end_idx..]
582 );
583 app.config_edit_cursor -= 1;
584 }
585 }
586 KeyCode::Left => {
587 app.config_edit_cursor = app.config_edit_cursor.saturating_sub(1);
588 }
589 KeyCode::Right => {
590 let char_count = app.config_edit_buf.chars().count();
591 if app.config_edit_cursor < char_count {
592 app.config_edit_cursor += 1;
593 }
594 }
595 KeyCode::Char(c) => {
596 let byte_idx = app
597 .config_edit_buf
598 .char_indices()
599 .nth(app.config_edit_cursor)
600 .map(|(i, _)| i)
601 .unwrap_or(app.config_edit_buf.len());
602 app.config_edit_buf.insert(byte_idx, c);
603 app.config_edit_cursor += 1;
604 }
605 _ => {}
606 }
607 return;
608 }
609
610 match key.code {
612 KeyCode::Esc => {
613 let _ = save_agent_config(&app.agent_config);
615 app.show_toast("配置已保存 ✅", false);
616 app.mode = ChatMode::Chat;
617 }
618 KeyCode::Up | KeyCode::Char('k') => {
619 if total_fields > 0 {
620 if app.config_field_idx == 0 {
621 app.config_field_idx = total_fields - 1;
622 } else {
623 app.config_field_idx -= 1;
624 }
625 }
626 }
627 KeyCode::Down | KeyCode::Char('j') => {
628 if total_fields > 0 {
629 app.config_field_idx = (app.config_field_idx + 1) % total_fields;
630 }
631 }
632 KeyCode::Tab | KeyCode::Right => {
633 let count = app.agent_config.providers.len();
635 if count > 1 {
636 app.config_provider_idx = (app.config_provider_idx + 1) % count;
637 }
639 }
640 KeyCode::BackTab | KeyCode::Left => {
641 let count = app.agent_config.providers.len();
643 if count > 1 {
644 if app.config_provider_idx == 0 {
645 app.config_provider_idx = count - 1;
646 } else {
647 app.config_provider_idx -= 1;
648 }
649 }
650 }
651 KeyCode::Enter => {
652 let total_provider = CONFIG_FIELDS.len();
654 if app.config_field_idx < total_provider && app.agent_config.providers.is_empty() {
655 app.show_toast("还没有 Provider,按 a 新增", true);
656 return;
657 }
658 let gi = app.config_field_idx.checked_sub(total_provider);
660 if let Some(gi) = gi {
661 if CONFIG_GLOBAL_FIELDS[gi] == "stream_mode" {
662 app.agent_config.stream_mode = !app.agent_config.stream_mode;
663 return;
664 }
665 if CONFIG_GLOBAL_FIELDS[gi] == "theme" {
667 app.switch_theme();
668 return;
669 }
670 }
671 app.config_edit_buf = config_field_raw_value(app, app.config_field_idx);
672 app.config_edit_cursor = app.config_edit_buf.chars().count();
673 app.config_editing = true;
674 }
675 KeyCode::Char('a') => {
676 let new_provider = ModelProvider {
678 name: format!("Provider-{}", app.agent_config.providers.len() + 1),
679 api_base: "https://api.openai.com/v1".to_string(),
680 api_key: String::new(),
681 model: String::new(),
682 };
683 app.agent_config.providers.push(new_provider);
684 app.config_provider_idx = app.agent_config.providers.len() - 1;
685 app.config_field_idx = 0; app.show_toast("已新增 Provider,请填写配置", false);
687 }
688 KeyCode::Char('d') => {
689 let count = app.agent_config.providers.len();
691 if count == 0 {
692 app.show_toast("没有可删除的 Provider", true);
693 } else {
694 let removed_name = app.agent_config.providers[app.config_provider_idx]
695 .name
696 .clone();
697 app.agent_config.providers.remove(app.config_provider_idx);
698 if app.config_provider_idx >= app.agent_config.providers.len()
700 && app.config_provider_idx > 0
701 {
702 app.config_provider_idx -= 1;
703 }
704 if app.agent_config.active_index >= app.agent_config.providers.len()
706 && app.agent_config.active_index > 0
707 {
708 app.agent_config.active_index -= 1;
709 }
710 app.show_toast(format!("已删除 Provider: {}", removed_name), false);
711 }
712 }
713 KeyCode::Char('s') => {
714 if !app.agent_config.providers.is_empty() {
716 app.agent_config.active_index = app.config_provider_idx;
717 let name = app.agent_config.providers[app.config_provider_idx]
718 .name
719 .clone();
720 app.show_toast(format!("已设为活跃模型: {}", name), false);
721 }
722 }
723 _ => {}
724 }
725}
726
727pub fn handle_select_model(app: &mut ChatApp, key: KeyEvent) {
729 let count = app.agent_config.providers.len();
730 match key.code {
731 KeyCode::Esc => {
732 app.mode = ChatMode::Chat;
733 }
734 KeyCode::Up | KeyCode::Char('k') => {
735 if count > 0 {
736 let i = app
737 .model_list_state
738 .selected()
739 .map(|i| if i == 0 { count - 1 } else { i - 1 })
740 .unwrap_or(0);
741 app.model_list_state.select(Some(i));
742 }
743 }
744 KeyCode::Down | KeyCode::Char('j') => {
745 if count > 0 {
746 let i = app
747 .model_list_state
748 .selected()
749 .map(|i| if i >= count - 1 { 0 } else { i + 1 })
750 .unwrap_or(0);
751 app.model_list_state.select(Some(i));
752 }
753 }
754 KeyCode::Enter => {
755 app.switch_model();
756 }
757 _ => {}
758 }
759}
760
761pub fn handle_archive_confirm_mode(app: &mut ChatApp, key: KeyEvent) {
763 if app.archive_editing_name {
764 match key.code {
766 KeyCode::Esc => {
767 app.archive_editing_name = false;
768 app.archive_custom_name.clear();
769 app.archive_edit_cursor = 0;
770 }
771 KeyCode::Enter => {
772 let name = if app.archive_custom_name.is_empty() {
773 app.archive_default_name.clone()
774 } else {
775 app.archive_custom_name.clone()
776 };
777 if let Err(e) = super::archive::validate_archive_name(&name) {
779 app.show_toast(e, true);
780 return;
781 }
782 if super::archive::archive_exists(&name) {
784 let _ = super::archive::delete_archive(&name);
786 }
787 app.do_archive(&name);
788 }
789 KeyCode::Backspace => {
790 if app.archive_edit_cursor > 0 {
791 let chars: Vec<char> = app.archive_custom_name.chars().collect();
792 app.archive_custom_name = chars[..app.archive_edit_cursor - 1]
793 .iter()
794 .chain(chars[app.archive_edit_cursor..].iter())
795 .collect();
796 app.archive_edit_cursor -= 1;
797 }
798 }
799 KeyCode::Left => {
800 app.archive_edit_cursor = app.archive_edit_cursor.saturating_sub(1);
801 }
802 KeyCode::Right => {
803 let char_count = app.archive_custom_name.chars().count();
804 if app.archive_edit_cursor < char_count {
805 app.archive_edit_cursor += 1;
806 }
807 }
808 KeyCode::Char(c) => {
809 let chars: Vec<char> = app.archive_custom_name.chars().collect();
810 app.archive_custom_name = chars[..app.archive_edit_cursor]
811 .iter()
812 .chain(std::iter::once(&c))
813 .chain(chars[app.archive_edit_cursor..].iter())
814 .collect();
815 app.archive_edit_cursor += 1;
816 }
817 _ => {}
818 }
819 } else {
820 match key.code {
822 KeyCode::Esc => {
823 app.mode = ChatMode::Chat;
824 }
825 KeyCode::Enter => {
826 let name = app.archive_default_name.clone();
828 if super::archive::archive_exists(&name) {
830 let _ = super::archive::delete_archive(&name);
831 }
832 app.do_archive(&name);
833 }
834 KeyCode::Char('n') | KeyCode::Char('N') => {
835 app.archive_editing_name = true;
837 app.archive_custom_name.clear();
838 app.archive_edit_cursor = 0;
839 }
840 KeyCode::Char('d') | KeyCode::Char('D') => {
841 app.clear_session();
843 app.mode = ChatMode::Chat;
844 }
845 _ => {}
846 }
847 }
848}
849
850pub fn handle_archive_list_mode(app: &mut ChatApp, key: KeyEvent) {
852 let count = app.archives.len();
853
854 if app.restore_confirm_needed {
856 match key.code {
857 KeyCode::Esc => {
858 app.restore_confirm_needed = false;
859 }
860 KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
861 app.do_restore();
862 }
863 _ => {}
864 }
865 return;
866 }
867
868 match key.code {
869 KeyCode::Esc => {
870 app.mode = ChatMode::Chat;
871 }
872 KeyCode::Up | KeyCode::Char('k') => {
873 if count > 0 {
874 app.archive_list_index = if app.archive_list_index == 0 {
875 count - 1
876 } else {
877 app.archive_list_index - 1
878 };
879 }
880 }
881 KeyCode::Down | KeyCode::Char('j') => {
882 if count > 0 {
883 app.archive_list_index = if app.archive_list_index >= count - 1 {
884 0
885 } else {
886 app.archive_list_index + 1
887 };
888 }
889 }
890 KeyCode::Enter => {
891 if count > 0 {
892 if !app.session.messages.is_empty() {
894 app.restore_confirm_needed = true;
895 } else {
896 app.do_restore();
897 }
898 }
899 }
900 KeyCode::Char('d') | KeyCode::Char('D') => {
901 if count > 0 {
903 app.do_delete_archive();
904 }
905 }
906 _ => {}
907 }
908}