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