From c36eb7d1cda27a3886d02ec850153a32867bdec8 Mon Sep 17 00:00:00 2001 From: Sopaco Date: Fri, 2 Jan 2026 04:08:05 +0800 Subject: [PATCH 1/7] Add CLI argument parsing with clap Replace manual argument parsing with clap's derive macro for the enhance-memory-saver flag. This adds proper CLI structure, automatic help generation, and version information display. --- Cargo.lock | 1 + examples/cortex-mem-tars/Cargo.toml | 3 +++ examples/cortex-mem-tars/src/main.rs | 19 +++++++++++++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21d624a..d704de0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -701,6 +701,7 @@ dependencies = [ "async-stream", "async-trait", "chrono", + "clap", "clipboard", "cortex-mem-config", "cortex-mem-core", diff --git a/examples/cortex-mem-tars/Cargo.toml b/examples/cortex-mem-tars/Cargo.toml index 19d70e7..1cdc736 100644 --- a/examples/cortex-mem-tars/Cargo.toml +++ b/examples/cortex-mem-tars/Cargo.toml @@ -53,3 +53,6 @@ reqwest = { version = "0.12", features = ["json"] } # Utilities uuid = { version = "1.10", features = ["v4"] } clipboard = "0.5" + +# CLI +clap = { version = "4.5", features = ["derive"] } diff --git a/examples/cortex-mem-tars/src/main.rs b/examples/cortex-mem-tars/src/main.rs index b42b4c8..2dd46f4 100644 --- a/examples/cortex-mem-tars/src/main.rs +++ b/examples/cortex-mem-tars/src/main.rs @@ -7,18 +7,29 @@ mod ui; use anyhow::{Context, Result}; use app::{create_default_bots, App}; +use clap::Parser; use config::ConfigManager; use infrastructure::Infrastructure; use logger::init_logger; use std::sync::Arc; +#[derive(Parser, Debug)] +#[command(name = "cortex-mem-tars")] +#[command(about = "TARS, An Interactive Demonstration Program Based on Cortex Memory")] +#[command(author = "Sopaco")] +#[command(version)] +struct Args { + /// 启用增强记忆保存功能,退出时自动保存对话到记忆系统 + #[arg(long, action)] + enhance_memory_saver: bool, +} + #[tokio::main] async fn main() -> Result<()> { // 解析命令行参数 - let args: Vec = std::env::args().collect(); - let enhance_memory_saver = args.contains(&"--enhance-memory-saver".to_string()); + let args = Args::parse(); - if enhance_memory_saver { + if args.enhance_memory_saver { log::info!("已启用增强记忆保存功能"); } @@ -63,7 +74,7 @@ async fn main() -> Result<()> { app.run().await.context("应用运行失败")?; // 退出时保存对话到记忆系统(仅在启用增强记忆保存功能时) - if enhance_memory_saver { + if args.enhance_memory_saver { if let Some(_inf) = infrastructure { println!("\n╔══════════════════════════════════════════════════════════════════════════════╗"); println!("║ 🧠 Cortex Memory - 退出流程 ║"); From dc284c5b4aa917c0e7e55ca53928bd9ba97d724f Mon Sep 17 00:00:00 2001 From: Sopaco Date: Fri, 2 Jan 2026 04:14:35 +0800 Subject: [PATCH 2/7] Replace help chat message with scrollable modal The help system now displays content in a dedicated modal window instead of adding it as a chat message. The modal includes proper scrolling with keyboard navigation (arrows, page up/down, home/end) and closes with the Escape key. --- examples/cortex-mem-tars/src/app.rs | 5 +- examples/cortex-mem-tars/src/ui.rs | 171 +++++++++++++++++++++++++++- 2 files changed, 170 insertions(+), 6 deletions(-) diff --git a/examples/cortex-mem-tars/src/app.rs b/examples/cortex-mem-tars/src/app.rs index 1046ca8..efe005e 100644 --- a/examples/cortex-mem-tars/src/app.rs +++ b/examples/cortex-mem-tars/src/app.rs @@ -462,9 +462,8 @@ impl App { /// 显示帮助信息 fn show_help(&mut self) { log::info!("显示帮助信息"); - let help_message = ChatMessage::assistant(AppUi::get_help_message()); - self.ui.messages.push(help_message); - self.ui.auto_scroll = true; + self.ui.help_modal_visible = true; + self.ui.help_scroll_offset = 0; } /// 导出会话到剪贴板 diff --git a/examples/cortex-mem-tars/src/ui.rs b/examples/cortex-mem-tars/src/ui.rs index ef2d02f..4a4cb03 100644 --- a/examples/cortex-mem-tars/src/ui.rs +++ b/examples/cortex-mem-tars/src/ui.rs @@ -62,6 +62,10 @@ pub struct AppUi { pub cursor_position: (usize, usize), // 当前光标位置 (line_index, char_index) // 消息显示区域位置 pub messages_area: Option, + // 帮助弹窗相关字段 + pub help_modal_visible: bool, + pub help_content: Vec>, + pub help_scroll_offset: usize, } /// 键盘事件处理结果 @@ -86,6 +90,8 @@ impl AppUi { .title("输入消息或命令 (Enter 发送, 输入 /help 查看命令)")); let _ = input_textarea.set_cursor_line_style(Style::default()); + let help_content = Self::parse_help_content(); + Self { state: AppState::BotSelection, service_status: ServiceStatus::Initing, @@ -106,6 +112,9 @@ impl AppUi { selection_end: None, cursor_position: (0, 0), messages_area: None, + help_modal_visible: false, + help_content, + help_scroll_offset: 0, } } @@ -198,6 +207,11 @@ impl AppUi { fn handle_chat_key(&mut self, key: KeyEvent) -> KeyAction { use ratatui::crossterm::event::{KeyCode, KeyModifiers}; + // 如果帮助弹窗打开,只处理弹窗相关的按键 + if self.help_modal_visible { + return self.handle_help_modal_key(key); + } + if self.log_panel_visible { log::debug!("日志面板打开,处理日志面板键盘事件"); if self.handle_log_panel_key(key) { @@ -421,6 +435,52 @@ impl AppUi { } } + /// 处理帮助弹窗的键盘事件 + fn handle_help_modal_key(&mut self, key: KeyEvent) -> KeyAction { + use ratatui::crossterm::event::KeyCode; + match key.code { + KeyCode::Esc => { + log::debug!("关闭帮助弹窗"); + self.help_modal_visible = false; + KeyAction::Continue + } + KeyCode::Up | KeyCode::Char('k') => { + if self.help_scroll_offset > 0 { + self.help_scroll_offset -= 1; + } + KeyAction::Continue + } + KeyCode::Down | KeyCode::Char('j') => { + let visible_lines = 20; // 弹窗可见行数 + if self.help_scroll_offset < self.help_content.len().saturating_sub(visible_lines) { + self.help_scroll_offset += 1; + } + KeyAction::Continue + } + KeyCode::PageUp => { + self.help_scroll_offset = self.help_scroll_offset.saturating_sub(10); + KeyAction::Continue + } + KeyCode::PageDown => { + let visible_lines = 20; // 弹窗可见行数 + self.help_scroll_offset = self.help_scroll_offset + .saturating_add(10) + .min(self.help_content.len().saturating_sub(visible_lines)); + KeyAction::Continue + } + KeyCode::Home => { + self.help_scroll_offset = 0; + KeyAction::Continue + } + KeyCode::End => { + let visible_lines = 20; // 弹窗可见行数 + self.help_scroll_offset = self.help_content.len().saturating_sub(visible_lines); + KeyAction::Continue + } + _ => KeyAction::Continue, + } + } + /// 复制选中的内容到剪贴板 fn copy_selection(&mut self) { if let (Some(start), Some(end)) = (self.selection_start, self.selection_end) { @@ -690,6 +750,11 @@ impl AppUi { } else { self.render_chat_normal(frame, area); } + + // 如果帮助弹窗可见,渲染弹窗 + if self.help_modal_visible { + self.render_help_modal(frame); + } } /// 渲染普通聊天界面 @@ -1112,6 +1177,74 @@ impl AppUi { ); } + /// 渲染帮助弹窗 + fn render_help_modal(&mut self, frame: &mut Frame) { + // 计算弹窗大小(居中显示) + let area = frame.area(); + let modal_width = area.width.saturating_sub(20).min(80); + let modal_height = area.height.saturating_sub(10).min(25); + + let x = (area.width - modal_width) / 2; + let y = (area.height - modal_height) / 2; + + let modal_area = Rect::new(x, y, modal_width, modal_height); + + // 创建半透明背景遮罩 + let overlay_area = area; + let overlay_block = Block::default() + .style(Style::default().bg(Color::Rgb(0, 0, 0))); + + frame.render_widget(overlay_block, overlay_area); + + // 渲染弹窗内容 + let visible_lines = modal_height.saturating_sub(4) as usize; // 减去边框和标题 + let max_scroll = self.help_content.len().saturating_sub(visible_lines); + self.help_scroll_offset = self.help_scroll_offset.min(max_scroll); + + let display_lines: Vec = self + .help_content + .iter() + .skip(self.help_scroll_offset) + .take(visible_lines) + .cloned() + .collect(); + + let paragraph = Paragraph::new(display_lines) + .block( + Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(Color::Cyan)) + .border_type(ratatui::widgets::BorderType::Double) + .title_style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)) + .title(" 帮助信息 (Esc 关闭) ") + ) + .wrap(Wrap { trim: false }) + .alignment(Alignment::Left); + + frame.render_widget(paragraph, modal_area); + + // 渲染滚动条 + if self.help_content.len() > visible_lines { + let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight) + .begin_symbol(Some("↑")) + .end_symbol(Some("↓")); + + let mut scrollbar_state = ScrollbarState::new(self.help_content.len()) + .position(self.help_scroll_offset); + + let scrollbar_area = modal_area.inner(Margin { + vertical: 1, + horizontal: 0, + }); + + frame.render_stateful_widget( + scrollbar, + scrollbar_area, + &mut scrollbar_state, + ); + } + } + /// Get input text, filtering out auto-wrap newlines /// Heuristic: next line starts with whitespace = user newline (Shift+Enter) /// next line starts without whitespace = auto-wrap continuation @@ -1193,9 +1326,41 @@ impl AppUi { } } - /// 获取帮助信息 - pub fn get_help_message() -> String { - "# Cortex TARS AI Program - 帮助信息\n\n欢迎使用TARS演示程序,我是由Cortex Memory技术驱动的人工智能程序,作为你的第二大脑,我能够作为你的外脑与你的记忆深度链接。\n\n## 可用命令\n\n| 命令 | 说明 |\n|------|------|\n| `/quit` | 退出程序 |\n| `/cls` 或 `/clear` | 清空会话区域 |\n| `/help` | 显示此帮助信息 |\n| `/dump-chats` | 复制会话区域的所有内容到剪贴板 |\n\n## 快捷键\n\n- **Enter**: 发送消息\n- **Shift+Enter**: 换行\n- **Ctrl+L**: 打开/关闭日志面板\n- **Esc**: 关闭日志面板\n\n---\n\n*Powered by TARS AI*".to_string() + /// 解析帮助内容为 Line 列表 + fn parse_help_content() -> Vec> { + let help_text = "# Cortex TARS AI Program - 帮助信息 + +欢迎使用TARS演示程序,我是由Cortex Memory技术驱动的人工智能程序,作为你的第二大脑,我能够作为你的外脑与你的记忆深度链接。 + +## 可用命令 + +| 命令 | 说明 | +|------|------| +| `/quit` | 退出程序 | +| `/cls` 或 `/clear` | 清空会话区域 | +| `/help` | 显示此帮助信息 | +| `/dump-chats` | 复制会话区域的所有内容到剪贴板 | + +## 快捷键 + +- Enter: 发送消息 +- Shift+Enter: 换行 +- Ctrl+L: 打开/关闭日志面板 +- Esc: 关闭弹窗 + +--- + +Powered by Cortex Memory"; + + // 使用 tui-markdown 渲染帮助文本 + let markdown_text = from_str(help_text); + + // 转换为 Line 列表 + markdown_text.lines.into_iter().map(|line| { + Line::from(line.spans.iter().map(|s| { + Span::raw(s.content.clone()) + }).collect::>()) + }).collect() } /// 导出所有会话内容到剪贴板 From 471952b9d1c824a99ea4d739f072720e53e9a151 Mon Sep 17 00:00:00 2001 From: Sopaco Date: Fri, 2 Jan 2026 05:08:52 +0800 Subject: [PATCH 3/7] Format help menu to use bullet lists --- examples/cortex-mem-tars/src/ui.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/cortex-mem-tars/src/ui.rs b/examples/cortex-mem-tars/src/ui.rs index 4a4cb03..8341d89 100644 --- a/examples/cortex-mem-tars/src/ui.rs +++ b/examples/cortex-mem-tars/src/ui.rs @@ -1334,19 +1334,17 @@ impl AppUi { ## 可用命令 -| 命令 | 说明 | -|------|------| -| `/quit` | 退出程序 | -| `/cls` 或 `/clear` | 清空会话区域 | -| `/help` | 显示此帮助信息 | -| `/dump-chats` | 复制会话区域的所有内容到剪贴板 | + - /quit 退出程序 + - /cls /clear 清空会话区域 + - /help 显示此帮助信息 + - /dump-chats 复制会话区域的所有内容到剪贴板 ## 快捷键 -- Enter: 发送消息 -- Shift+Enter: 换行 -- Ctrl+L: 打开/关闭日志面板 -- Esc: 关闭弹窗 + - Enter 发送消息 + - Shift+Enter 换行 + - Ctrl+L 打开/关闭日志面板 + - Esc 关闭弹窗 --- From 8d16244fd0b33475d87cef5f635bc54be10733f2 Mon Sep 17 00:00:00 2001 From: Sopaco Date: Fri, 2 Jan 2026 05:21:30 +0800 Subject: [PATCH 4/7] Add scroll support for help modal --- examples/cortex-mem-tars/src/ui.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/examples/cortex-mem-tars/src/ui.rs b/examples/cortex-mem-tars/src/ui.rs index 8341d89..298c622 100644 --- a/examples/cortex-mem-tars/src/ui.rs +++ b/examples/cortex-mem-tars/src/ui.rs @@ -585,6 +585,29 @@ impl AppUi { return true; } + // 如果帮助弹窗打开,处理帮助弹窗的滚轮事件 + if self.help_modal_visible { + // 动态计算弹窗高度和可见行数(与 render_help_modal 保持一致) + let modal_height = _area.height.saturating_sub(10).min(25); + let visible_lines = modal_height.saturating_sub(4) as usize; + let max_scroll = self.help_content.len().saturating_sub(visible_lines); + + match event.kind { + MouseEventKind::ScrollUp => { + if self.help_scroll_offset > 0 { + self.help_scroll_offset = self.help_scroll_offset.saturating_sub(3); + } + } + MouseEventKind::ScrollDown => { + if self.help_scroll_offset < max_scroll { + self.help_scroll_offset = self.help_scroll_offset.saturating_add(3).min(max_scroll); + } + } + _ => {} + } + return true; + } + // 使用保存的消息区域 let messages_area = match self.messages_area { Some(area) => area, From d9bd8de534cc2bdc99f3f7c4c735fce3bda2c18b Mon Sep 17 00:00:00 2001 From: Sopaco Date: Fri, 2 Jan 2026 05:29:46 +0800 Subject: [PATCH 5/7] Update modal overlay colors for better visibility Change overlay background from pure black to dark gray (20,20,20) and modal background to dark blue-gray (30,30,40) for improved visual hierarchy and readability. --- examples/cortex-mem-tars/src/ui.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/cortex-mem-tars/src/ui.rs b/examples/cortex-mem-tars/src/ui.rs index 298c622..d9f2adc 100644 --- a/examples/cortex-mem-tars/src/ui.rs +++ b/examples/cortex-mem-tars/src/ui.rs @@ -1212,10 +1212,10 @@ impl AppUi { let modal_area = Rect::new(x, y, modal_width, modal_height); - // 创建半透明背景遮罩 + // 创建半透明背景遮罩(使用深灰色) let overlay_area = area; let overlay_block = Block::default() - .style(Style::default().bg(Color::Rgb(0, 0, 0))); + .style(Style::default().bg(Color::Rgb(20, 20, 20))); frame.render_widget(overlay_block, overlay_area); @@ -1240,6 +1240,7 @@ impl AppUi { .border_type(ratatui::widgets::BorderType::Double) .title_style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)) .title(" 帮助信息 (Esc 关闭) ") + .style(Style::default().bg(Color::Rgb(30, 30, 40))) ) .wrap(Wrap { trim: false }) .alignment(Alignment::Left); From 445db1ab8a7af61c23222bfc97acb057d606a0b3 Mon Sep 17 00:00:00 2001 From: Sopaco Date: Fri, 2 Jan 2026 06:11:42 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E6=A3=80=E6=9F=A5=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/cortex-mem-tars/src/app.rs | 20 +++ examples/cortex-mem-tars/src/ui.rs | 234 ++++++++++++++++++++++++++-- 2 files changed, 241 insertions(+), 13 deletions(-) diff --git a/examples/cortex-mem-tars/src/app.rs b/examples/cortex-mem-tars/src/app.rs index efe005e..f0c5b82 100644 --- a/examples/cortex-mem-tars/src/app.rs +++ b/examples/cortex-mem-tars/src/app.rs @@ -248,6 +248,16 @@ impl App { self.show_help(); } } + crate::ui::KeyAction::ShowThemes => { + log::info!("收到 ShowThemes 动作,当前状态: {:?}", self.ui.state); + if self.ui.state == AppState::Chat { + log::info!("调用 show_themes()"); + self.show_themes(); + log::info!("show_themes() 调用完成,theme_modal_visible: {}", self.ui.theme_modal_visible); + } else { + log::warn!("不在 Chat 状态,无法显示主题"); + } + } crate::ui::KeyAction::DumpChats => { if self.ui.state == AppState::Chat { self.dump_chats(); @@ -322,6 +332,9 @@ impl App { crate::ui::KeyAction::ShowHelp => { self.show_help(); } + crate::ui::KeyAction::ShowThemes => { + self.show_themes(); + } crate::ui::KeyAction::DumpChats => { self.dump_chats(); } @@ -466,6 +479,13 @@ impl App { self.ui.help_scroll_offset = 0; } + /// 显示主题选择 + fn show_themes(&mut self) { + log::info!("显示主题选择"); + self.ui.theme_modal_visible = true; + log::info!("主题弹窗可见性已设置为: {}", self.ui.theme_modal_visible); + } + /// 导出会话到剪贴板 fn dump_chats(&mut self) { match self.ui.dump_chats_to_clipboard() { diff --git a/examples/cortex-mem-tars/src/ui.rs b/examples/cortex-mem-tars/src/ui.rs index d9f2adc..33ce691 100644 --- a/examples/cortex-mem-tars/src/ui.rs +++ b/examples/cortex-mem-tars/src/ui.rs @@ -37,6 +37,75 @@ pub enum ChatState { Selection, } +/// 主题定义 +#[derive(Debug, Clone, Copy)] +pub struct Theme { + pub name: &'static str, + pub primary_color: Color, + pub secondary_color: Color, + pub accent_color: Color, + pub background_color: Color, + pub text_color: Color, + pub border_color: Color, +} + +/// 预设主题 +impl Theme { + pub const DEFAULT: Theme = Theme { + name: "默认", + primary_color: Color::Cyan, + secondary_color: Color::Blue, + accent_color: Color::Green, + background_color: Color::Rgb(20, 30, 40), + text_color: Color::White, + border_color: Color::Cyan, + }; + + pub const DARK: Theme = Theme { + name: "暗黑", + primary_color: Color::Gray, + secondary_color: Color::DarkGray, + accent_color: Color::LightCyan, + background_color: Color::Rgb(10, 10, 15), + text_color: Color::Rgb(220, 220, 220), + border_color: Color::Gray, + }; + + pub const FOREST: Theme = Theme { + name: "森林", + primary_color: Color::Green, + secondary_color: Color::Rgb(0, 100, 0), + accent_color: Color::LightGreen, + background_color: Color::Rgb(20, 40, 20), + text_color: Color::Rgb(200, 255, 200), + border_color: Color::Green, + }; + + pub const OCEAN: Theme = Theme { + name: "海洋", + primary_color: Color::Blue, + secondary_color: Color::Rgb(0, 0, 100), + accent_color: Color::LightBlue, + background_color: Color::Rgb(20, 30, 50), + text_color: Color::Rgb(200, 220, 255), + border_color: Color::Blue, + }; + + pub const SUNSET: Theme = Theme { + name: "日落", + primary_color: Color::Rgb(255, 165, 0), + secondary_color: Color::Rgb(200, 100, 0), + accent_color: Color::Rgb(255, 200, 100), + background_color: Color::Rgb(40, 20, 10), + text_color: Color::Rgb(255, 240, 200), + border_color: Color::Rgb(255, 165, 0), + }; + + pub fn all() -> &'static [Theme; 5] { + &[Self::DEFAULT, Self::DARK, Self::FOREST, Self::OCEAN, Self::SUNSET] + } +} + /// 应用 UI 状态 pub struct AppUi { pub state: AppState, @@ -66,6 +135,10 @@ pub struct AppUi { pub help_modal_visible: bool, pub help_content: Vec>, pub help_scroll_offset: usize, + // 主题相关字段 + pub current_theme: Theme, + pub theme_modal_visible: bool, + pub theme_list_state: ListState, } /// 键盘事件处理结果 @@ -76,6 +149,7 @@ pub enum KeyAction { SendMessage, // 发送消息 ClearChat, // 清空会话 ShowHelp, // 显示帮助 + ShowThemes, // 显示主题选择 DumpChats, // 导出会话到剪贴板 } @@ -87,11 +161,15 @@ impl AppUi { let mut input_textarea = TextArea::default(); let _ = input_textarea.set_block(Block::default() .borders(Borders::ALL) + .border_style(Style::default().fg(Theme::DEFAULT.border_color)) .title("输入消息或命令 (Enter 发送, 输入 /help 查看命令)")); let _ = input_textarea.set_cursor_line_style(Style::default()); let help_content = Self::parse_help_content(); + let mut theme_list_state = ListState::default(); + theme_list_state.select(Some(0)); + Self { state: AppState::BotSelection, service_status: ServiceStatus::Initing, @@ -115,6 +193,9 @@ impl AppUi { help_modal_visible: false, help_content, help_scroll_offset: 0, + current_theme: Theme::DEFAULT, + theme_modal_visible: false, + theme_list_state, } } @@ -212,6 +293,11 @@ impl AppUi { return self.handle_help_modal_key(key); } + // 如果主题弹窗打开,只处理主题弹窗相关的按键 + if self.theme_modal_visible { + return self.handle_theme_modal_key(key); + } + if self.log_panel_visible { log::debug!("日志面板打开,处理日志面板键盘事件"); if self.handle_log_panel_key(key) { @@ -481,6 +567,52 @@ impl AppUi { } } + /// 处理主题弹窗的键盘事件 + fn handle_theme_modal_key(&mut self, key: KeyEvent) -> KeyAction { + use ratatui::crossterm::event::KeyCode; + match key.code { + KeyCode::Esc => { + log::debug!("关闭主题弹窗"); + self.theme_modal_visible = false; + KeyAction::Continue + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(selected) = self.theme_list_state.selected() { + if selected > 0 { + self.theme_list_state.select(Some(selected - 1)); + } + } + KeyAction::Continue + } + KeyCode::Down | KeyCode::Char('j') => { + if let Some(selected) = self.theme_list_state.selected() { + if selected < Theme::all().len().saturating_sub(1) { + self.theme_list_state.select(Some(selected + 1)); + } + } + KeyAction::Continue + } + KeyCode::Enter => { + if let Some(index) = self.theme_list_state.selected() { + if let Some(theme) = Theme::all().get(index) { + self.current_theme = *theme; + log::info!("切换主题: {}", theme.name); + + // 更新输入框样式 + let _ = self.input_textarea.set_block(Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(self.current_theme.border_color)) + .title("输入消息或命令 (Enter 发送, 输入 /help 查看命令)")); + + self.theme_modal_visible = false; + } + } + KeyAction::Continue + } + _ => KeyAction::Continue, + } + } + /// 复制选中的内容到剪贴板 fn copy_selection(&mut self) { if let (Some(start), Some(end)) = (self.selection_start, self.selection_end) { @@ -778,6 +910,13 @@ impl AppUi { if self.help_modal_visible { self.render_help_modal(frame); } + + // 如果主题弹窗可见,渲染弹窗 + log::debug!("render_chat: 检查主题弹窗可见性: {}", self.theme_modal_visible); + if self.theme_modal_visible { + log::debug!("render_chat: 开始渲染主题弹窗"); + self.render_theme_modal(frame); + } } /// 渲染普通聊天界面 @@ -797,7 +936,7 @@ impl AppUi { Span::styled( "Cortex TARS AI Program", Style::default() - .fg(Color::Cyan) + .fg(self.current_theme.primary_color) .add_modifier(Modifier::BOLD), ), ]); @@ -808,7 +947,7 @@ impl AppUi { .borders(Borders::ALL) .border_style( Style::default() - .fg(Color::Cyan) + .fg(self.current_theme.primary_color) .add_modifier(Modifier::BOLD) ) .border_type(ratatui::widgets::BorderType::Double) @@ -830,8 +969,8 @@ impl AppUi { .alignment(Alignment::Center) .style( Style::default() - .fg(Color::White) - .bg(Color::Rgb(20, 30, 40)) + .fg(self.current_theme.text_color) + .bg(self.current_theme.background_color) ); frame.render_widget(title, chunks[0]); @@ -862,7 +1001,7 @@ impl AppUi { Span::styled( "Cortex TARS AI Program", Style::default() - .fg(Color::Cyan) + .fg(self.current_theme.primary_color) .add_modifier(Modifier::BOLD), ), ]); @@ -873,7 +1012,7 @@ impl AppUi { .borders(Borders::ALL) .border_style( Style::default() - .fg(Color::Cyan) + .fg(self.current_theme.primary_color) .add_modifier(Modifier::BOLD) ) .border_type(ratatui::widgets::BorderType::Double) @@ -895,8 +1034,8 @@ impl AppUi { .alignment(Alignment::Center) .style( Style::default() - .fg(Color::White) - .bg(Color::Rgb(20, 30, 40)) + .fg(self.current_theme.text_color) + .bg(self.current_theme.background_color) ); frame.render_widget(title, chunks[0]); @@ -930,8 +1069,8 @@ impl AppUi { let role_color = match message.role { crate::agent::MessageRole::System => Color::Yellow, - crate::agent::MessageRole::User => Color::Green, - crate::agent::MessageRole::Assistant => Color::Cyan, + crate::agent::MessageRole::User => self.current_theme.accent_color, + crate::agent::MessageRole::Assistant => self.current_theme.primary_color, }; // 格式化时间戳 @@ -1236,11 +1375,11 @@ impl AppUi { .block( Block::default() .borders(Borders::ALL) - .border_style(Style::default().fg(Color::Cyan)) + .border_style(Style::default().fg(self.current_theme.border_color)) .border_type(ratatui::widgets::BorderType::Double) - .title_style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)) + .title_style(Style::default().fg(self.current_theme.primary_color).add_modifier(Modifier::BOLD)) .title(" 帮助信息 (Esc 关闭) ") - .style(Style::default().bg(Color::Rgb(30, 30, 40))) + .style(Style::default().bg(self.current_theme.background_color)) ) .wrap(Wrap { trim: false }) .alignment(Alignment::Left); @@ -1269,6 +1408,69 @@ impl AppUi { } } + /// 渲染主题选择弹窗 + fn render_theme_modal(&mut self, frame: &mut Frame) { + log::debug!("渲染主题弹窗,可见性: {}", self.theme_modal_visible); + // 计算弹窗大小(居中显示) + let area = frame.area(); + let modal_width = 50; + let modal_height = 15; + + let x = (area.width - modal_width) / 2; + let y = (area.height - modal_height) / 2; + + let modal_area = Rect::new(x, y, modal_width, modal_height); + + // 创建半透明背景遮罩(使用深灰色) + let overlay_area = area; + let overlay_block = Block::default() + .style(Style::default().bg(Color::Rgb(20, 20, 20))); + + frame.render_widget(overlay_block, overlay_area); + + // 创建主题列表项 + let items: Vec = Theme::all() + .iter() + .map(|theme| { + ListItem::new(Line::from(vec![ + Span::styled( + theme.name, + Style::default() + .fg(theme.primary_color) + .add_modifier(Modifier::BOLD), + ), + Span::raw(" - "), + Span::styled( + "●", + Style::default().fg(theme.accent_color), + ), + ])) + }) + .collect(); + + let list = List::new(items) + .block( + Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(self.current_theme.primary_color)) + .border_type(ratatui::widgets::BorderType::Double) + .title_style( + Style::default() + .fg(self.current_theme.primary_color) + .add_modifier(Modifier::BOLD) + ) + .title(" 选择主题 (Esc 关闭, Enter 确认) ") + .style(Style::default().bg(self.current_theme.background_color)) + ) + .highlight_style( + Style::default() + .bg(self.current_theme.secondary_color) + .add_modifier(Modifier::REVERSED) + ); + + frame.render_stateful_widget(list, modal_area, &mut self.theme_list_state); + } + /// Get input text, filtering out auto-wrap newlines /// Heuristic: next line starts with whitespace = user newline (Shift+Enter) /// next line starts without whitespace = auto-wrap continuation @@ -1310,6 +1512,7 @@ impl AppUi { self.input_textarea = TextArea::default(); let _ = self.input_textarea.set_block(Block::default() .borders(Borders::ALL) + .border_style(Style::default().fg(self.current_theme.border_color)) .title("输入消息或命令 (Enter 发送, 输入 /help 查看命令)")); let _ = self.input_textarea.set_cursor_line_style(Style::default()); } @@ -1339,6 +1542,10 @@ impl AppUi { log::info!("执行命令: /help"); Some(KeyAction::ShowHelp) } + "/themes" => { + log::info!("执行命令: /themes"); + Some(KeyAction::ShowThemes) + } "/dump-chats" => { log::info!("执行命令: /dump-chats"); Some(KeyAction::DumpChats) @@ -1361,6 +1568,7 @@ impl AppUi { - /quit 退出程序 - /cls /clear 清空会话区域 - /help 显示此帮助信息 + - /themes 切换主题 - /dump-chats 复制会话区域的所有内容到剪贴板 ## 快捷键 From ceb926f656b507696ac36823bd222c14ef5f32d3 Mon Sep 17 00:00:00 2001 From: Sopaco Date: Fri, 2 Jan 2026 06:14:27 +0800 Subject: [PATCH 7/7] Remove debug logging statements --- examples/cortex-mem-tars/src/ui.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/examples/cortex-mem-tars/src/ui.rs b/examples/cortex-mem-tars/src/ui.rs index 33ce691..6ff13ee 100644 --- a/examples/cortex-mem-tars/src/ui.rs +++ b/examples/cortex-mem-tars/src/ui.rs @@ -319,7 +319,6 @@ impl AppUi { } } else { // Shift+Enter 换行 - log::debug!("Shift+Enter: 换行"); self.input_textarea.input(key); KeyAction::Continue } @@ -329,12 +328,10 @@ impl AppUi { KeyAction::Quit } KeyCode::Char('l') if key.modifiers.contains(KeyModifiers::CONTROL) => { - log::debug!("Ctrl-L: 切换日志面板"); self.log_panel_visible = !self.log_panel_visible; KeyAction::Continue } KeyCode::Esc => { - log::debug!("关闭日志面板"); self.log_panel_visible = false; // 清除选择 self.selection_active = false; @@ -912,9 +909,7 @@ impl AppUi { } // 如果主题弹窗可见,渲染弹窗 - log::debug!("render_chat: 检查主题弹窗可见性: {}", self.theme_modal_visible); if self.theme_modal_visible { - log::debug!("render_chat: 开始渲染主题弹窗"); self.render_theme_modal(frame); } } @@ -1226,9 +1221,6 @@ impl AppUi { let selected: String = chars[safe_start_col..safe_end_col].iter().collect(); let after: String = chars[safe_end_col..].iter().collect(); - log::debug!("单行高亮: original_idx={}, before_len={}, selected_len={}, after_len={}", - original_idx, before.len(), selected.len(), after.len()); - Line::from(vec![ Span::raw(before), Span::styled(selected, highlight_style), @@ -1410,7 +1402,6 @@ impl AppUi { /// 渲染主题选择弹窗 fn render_theme_modal(&mut self, frame: &mut Frame) { - log::debug!("渲染主题弹窗,可见性: {}", self.theme_modal_visible); // 计算弹窗大小(居中显示) let area = frame.area(); let modal_width = 50;