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