Files

1 line
247 KiB
JSON

{"repo": "aubakirovarman/dsx", "files": {"crates/dsx-tui/src/draw_settings.rs": {"02ab671513ed25839db232846a1913627e488a1b": {"t": 1785745838.2137685, "n": 8012, "h": "022fd0ffbb4811e7", "c": "//! Settings screen rendering.\n\nuse crate::App;\nuse crate::i18n::tr;\nuse crate::types::Language;\nuse ratatui::{\n Frame,\n layout::Rect,\n style::{Color, Modifier, Style},\n text::{Line, Span, Text},\n widgets::{Block, BorderType, Borders, Paragraph},\n};\n\nimpl App {\n pub fn draw_settings(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = vec![\n Line::from(\"\"),\n banner(tr(self.lang, \"settings_header_banner\")),\n Line::from(\"\"),\n Line::from(tr(self.lang, \"settings_header_desc\")),\n separator(),\n Line::from(\"\"),\n ];\n\n self.push_setting(\n &mut lines,\n 0,\n tr(self.lang, \"settings_opt_security\"),\n &self.mode,\n );\n self.push_setting(\n &mut lines,\n 1,\n tr(self.lang, \"settings_opt_model\"),\n &self.model,\n );\n self.push_setting(\n &mut lines,\n 2,\n tr(self.lang, \"settings_opt_sidebar\"),\n self.tree_label(),\n );\n self.push_setting(\n &mut lines,\n 3,\n tr(self.lang, \"settings_opt_language\"),\n self.lang.display_name(),\n );\n self.push_setting(&mut lines, 4, self.api_base_label(), &self.api_base);\n self.push_setting(&mut lines, 5, self.api_key_label(), &self.masked_key());\n self.push_action(&mut lines, 6, tr(self.lang, \"settings_opt_clear\"));\n\n lines.push(separator());\n lines.push(Line::from(tr(self.lang, \"telemetry_title\")));\n lines.push(Line::from(tr(self.lang, \"telemetry_db\")));\n lines.push(metric_line(\n tr(self.lang, \"telemetry_cost\"),\n format!(\"${:.4}\", self.cost),\n Color::LightGreen,\n ));\n lines.push(metric_line(\n tr(self.lang, \"telemetry_tokens\"),\n self.tokens.to_string(),\n Color::White,\n ));\n\n let paragraph = Paragraph::new(Text::from(lines)).block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Cyan))\n .title(Span::styled(\n tr(self.lang, \"settings_title\"),\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD),\n )),\n );\n\n frame.render_widget(paragraph, area);\n }\n\n fn push_setting(&self, lines: &mut Vec<Line<'_>>, idx: usize, label: &str, value: &str) {\n let selected = self.settings_cursor == idx;\n let prefix = if selected { \" ▸ \" } else { \" \" };\n let style = selected_style(selected);\n lines.push(Line::from(vec![\n Span::styled(prefix, Style::default().fg(Color::Cyan)),\n Span::styled(format!(\"{label}: \"), style),\n chip(value.to_string(), Color::White),\n Span::styled(\n format!(\" {}\", setting_hint(self.lang, idx)),\n Style::default().fg(Color::DarkGray),\n ),\n ]));\n lines.push(Line::from(\"\"));\n }\n\n fn push_action(&self, lines: &mut Vec<Line<'_>>, idx: usize, label: &str) {\n let selected = self.settings_cursor == idx;\n lines.push(Line::from(vec![\n Span::styled(\n if selected { \" ▸ \" } else { \" \" },\n Style::default().fg(Color::Cyan),\n ),\n Span::styled(label.to_string(), selected_style(selected)),\n chip(\n tr(self.lang, \"settings_clear_action\").to_string(),\n Color::LightRed,\n ),\n ]));\n lines.push(Line::from(\"\"));\n }\n\n fn tree_label(&self) -> &'static str {\n match (self.show_file_tree, self.lang) {\n (true, Language::Russian) => \"ПОКАЗАТЬ\",\n (false, Language::Russian) => \"СКРЫТЬ\",\n (true, Language::Kazakh) => \"КӨРСЕТУ\",\n (false, Language::Kazakh) => \"ЖАСЫРУ\",\n (true, Language::Chinese) => \"显示\",\n (false, Language::Chinese) => \"隐藏\",\n (true, Language::English) => \"SHOW\",\n (false, Language::English) => \"HIDE\",\n }\n }\n\n fn api_base_label(&self) -> &'static str {\n match self.lang {\n Language::Russian => \"АДРЕС API ENDPOINT BASE\",\n Language::Kazakh => \"API ENDPOINT НЕГІЗГІ МЕКЕНІ\",\n Language::Chinese => \"API 接口服务基准地址\",\n Language::English => \"API ENDPOINT BASE URL\",\n }\n }\n\n fn api_key_label(&self) -> &'static str {\n match self.lang {\n Language::Russian => \"КЛЮЧ АВТОРИЗАЦИИ API KEY\",\n Language::Kazakh => \"API АВТОРИЗАЦИЯ КЛЮЧІ\",\n Language::Chinese => \"API 授权验证密钥\",\n Language::English => \"API AUTHORIZATION KEY\",\n }\n }\n\n fn masked_key(&self) -> String {\n if self.api_key.is_empty() {\n return match self.lang {\n Language::Russian => \"⚠ КЛЮЧ НЕ НАЙДЕН\",\n Language::Kazakh => \"⚠ КЛЮЧ ТАБЫЛМАДЫ\",\n Language::Chinese => \"⚠ 未检测到密钥\",\n Language::English => \"⚠ KEY NOT DETECTED\",\n }\n .into();\n }\n if self.api_key.len() > 10 {\n format!(\n \"{}...{}\",\n &self.api_key[..5],\n &self.api_key[self.api_key.len() - 4..]\n )\n } else {\n \"✓ Loaded\".into()\n }\n }\n}\n\nfn setting_hint(lang: Language, idx: usize) -> &'static str {\n match (lang, idx) {\n (Language::Russian, 0) => \"(Спросить / Авто / Yolo / План)\",\n (Language::Russian, 1) => \"(v4-pro Думающая / v4-flash Быстрая)\",\n (Language::Russian, 2) => \"(Панель файлов)\",\n (Language::Russian, 3) => \"(Язык интерфейса)\",\n (Language::Russian, 4) => \"(Официальный API / локальные endpoints)\",\n (Language::Russian, 5) => \"(DEEPSEEK_API_KEY)\",\n (Language::Kazakh, 0) => \"(Сұрау / Авто / Yolo / Жоспар)\",\n (Language::Kazakh, 1) => \"(v4-pro Ойлайтын / v4-flash Жылдам)\",\n (Language::Kazakh, 2) => \"(Файлдар панелі)\",\n (Language::Kazakh, 3) => \"(Интерфейс тілі)\",\n (Language::Chinese, 0) => \"(提示确认 / 自动运行 / YOLO / 仅规划)\",\n (Language::Chinese, 1) => \"(v4-pro 深度思考 / v4-flash 高速响应)\",\n (Language::Chinese, 2) => \"(文件侧边栏)\",\n (Language::Chinese, 3) => \"(界面语言)\",\n (Language::English, 0) => \"(Ask / Auto / Yolo / Plan)\",\n (Language::English, 1) => \"(v4-pro Thinking / v4-flash Fast)\",\n (Language::English, 2) => \"(File tree panel)\",\n (Language::English, 3) => \"(Interface language)\",\n (_, 4) => \"(API endpoint)\",\n (_, 5) => \"(DEEPSEEK_API_KEY)\",\n _ => \"\",\n }\n}\n\nfn banner(label: &'static str) -> Line<'static> {\n Line::from(vec![\n Span::raw(\" \"),\n Span::styled(\n label,\n Style::default()\n .fg(Color::Black)\n .bg(Color::Cyan)\n .add_modifier(Modifier::BOLD),\n ),\n ])\n}\n\nfn separator() -> Line<'static> {\n Line::from(\" ─────────────────────────────────────────────────────────────────────────────\")\n}\n\nfn selected_style(selected: bool) -> Style {\n if selected {\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD)\n } else {\n Style::default().fg(Color::Gray)\n }\n}\n\nfn chip(value: String, bg: Color) -> Span<'static> {\n Span::styled(\n format!(\"[ {value} ]\"),\n Style::default()\n .fg(Color::Black)\n .bg(bg)\n .add_modifier(Modifier::BOLD),\n )\n}\n\nfn metric_line(label: &'static str, value: String, color: Color) -> Line<'static> {\n Line::from(vec![\n Span::raw(label),\n Span::styled(\n value,\n Style::default().fg(color).add_modifier(Modifier::BOLD),\n ),\n ])\n}\n"}, "896f8496431eaccf1ef108f3b935b5f088cedcde": {"t": 1785757086.1784477, "n": 8012, "h": "022fd0ffbb4811e7", "c": "//! Settings screen rendering.\n\nuse crate::App;\nuse crate::i18n::tr;\nuse crate::types::Language;\nuse ratatui::{\n Frame,\n layout::Rect,\n style::{Color, Modifier, Style},\n text::{Line, Span, Text},\n widgets::{Block, BorderType, Borders, Paragraph},\n};\n\nimpl App {\n pub fn draw_settings(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = vec![\n Line::from(\"\"),\n banner(tr(self.lang, \"settings_header_banner\")),\n Line::from(\"\"),\n Line::from(tr(self.lang, \"settings_header_desc\")),\n separator(),\n Line::from(\"\"),\n ];\n\n self.push_setting(\n &mut lines,\n 0,\n tr(self.lang, \"settings_opt_security\"),\n &self.mode,\n );\n self.push_setting(\n &mut lines,\n 1,\n tr(self.lang, \"settings_opt_model\"),\n &self.model,\n );\n self.push_setting(\n &mut lines,\n 2,\n tr(self.lang, \"settings_opt_sidebar\"),\n self.tree_label(),\n );\n self.push_setting(\n &mut lines,\n 3,\n tr(self.lang, \"settings_opt_language\"),\n self.lang.display_name(),\n );\n self.push_setting(&mut lines, 4, self.api_base_label(), &self.api_base);\n self.push_setting(&mut lines, 5, self.api_key_label(), &self.masked_key());\n self.push_action(&mut lines, 6, tr(self.lang, \"settings_opt_clear\"));\n\n lines.push(separator());\n lines.push(Line::from(tr(self.lang, \"telemetry_title\")));\n lines.push(Line::from(tr(self.lang, \"telemetry_db\")));\n lines.push(metric_line(\n tr(self.lang, \"telemetry_cost\"),\n format!(\"${:.4}\", self.cost),\n Color::LightGreen,\n ));\n lines.push(metric_line(\n tr(self.lang, \"telemetry_tokens\"),\n self.tokens.to_string(),\n Color::White,\n ));\n\n let paragraph = Paragraph::new(Text::from(lines)).block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Cyan))\n .title(Span::styled(\n tr(self.lang, \"settings_title\"),\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD),\n )),\n );\n\n frame.render_widget(paragraph, area);\n }\n\n fn push_setting(&self, lines: &mut Vec<Line<'_>>, idx: usize, label: &str, value: &str) {\n let selected = self.settings_cursor == idx;\n let prefix = if selected { \" ▸ \" } else { \" \" };\n let style = selected_style(selected);\n lines.push(Line::from(vec![\n Span::styled(prefix, Style::default().fg(Color::Cyan)),\n Span::styled(format!(\"{label}: \"), style),\n chip(value.to_string(), Color::White),\n Span::styled(\n format!(\" {}\", setting_hint(self.lang, idx)),\n Style::default().fg(Color::DarkGray),\n ),\n ]));\n lines.push(Line::from(\"\"));\n }\n\n fn push_action(&self, lines: &mut Vec<Line<'_>>, idx: usize, label: &str) {\n let selected = self.settings_cursor == idx;\n lines.push(Line::from(vec![\n Span::styled(\n if selected { \" ▸ \" } else { \" \" },\n Style::default().fg(Color::Cyan),\n ),\n Span::styled(label.to_string(), selected_style(selected)),\n chip(\n tr(self.lang, \"settings_clear_action\").to_string(),\n Color::LightRed,\n ),\n ]));\n lines.push(Line::from(\"\"));\n }\n\n fn tree_label(&self) -> &'static str {\n match (self.show_file_tree, self.lang) {\n (true, Language::Russian) => \"ПОКАЗАТЬ\",\n (false, Language::Russian) => \"СКРЫТЬ\",\n (true, Language::Kazakh) => \"КӨРСЕТУ\",\n (false, Language::Kazakh) => \"ЖАСЫРУ\",\n (true, Language::Chinese) => \"显示\",\n (false, Language::Chinese) => \"隐藏\",\n (true, Language::English) => \"SHOW\",\n (false, Language::English) => \"HIDE\",\n }\n }\n\n fn api_base_label(&self) -> &'static str {\n match self.lang {\n Language::Russian => \"АДРЕС API ENDPOINT BASE\",\n Language::Kazakh => \"API ENDPOINT НЕГІЗГІ МЕКЕНІ\",\n Language::Chinese => \"API 接口服务基准地址\",\n Language::English => \"API ENDPOINT BASE URL\",\n }\n }\n\n fn api_key_label(&self) -> &'static str {\n match self.lang {\n Language::Russian => \"КЛЮЧ АВТОРИЗАЦИИ API KEY\",\n Language::Kazakh => \"API АВТОРИЗАЦИЯ КЛЮЧІ\",\n Language::Chinese => \"API 授权验证密钥\",\n Language::English => \"API AUTHORIZATION KEY\",\n }\n }\n\n fn masked_key(&self) -> String {\n if self.api_key.is_empty() {\n return match self.lang {\n Language::Russian => \"⚠ КЛЮЧ НЕ НАЙДЕН\",\n Language::Kazakh => \"⚠ КЛЮЧ ТАБЫЛМАДЫ\",\n Language::Chinese => \"⚠ 未检测到密钥\",\n Language::English => \"⚠ KEY NOT DETECTED\",\n }\n .into();\n }\n if self.api_key.len() > 10 {\n format!(\n \"{}...{}\",\n &self.api_key[..5],\n &self.api_key[self.api_key.len() - 4..]\n )\n } else {\n \"✓ Loaded\".into()\n }\n }\n}\n\nfn setting_hint(lang: Language, idx: usize) -> &'static str {\n match (lang, idx) {\n (Language::Russian, 0) => \"(Спросить / Авто / Yolo / План)\",\n (Language::Russian, 1) => \"(v4-pro Думающая / v4-flash Быстрая)\",\n (Language::Russian, 2) => \"(Панель файлов)\",\n (Language::Russian, 3) => \"(Язык интерфейса)\",\n (Language::Russian, 4) => \"(Официальный API / локальные endpoints)\",\n (Language::Russian, 5) => \"(DEEPSEEK_API_KEY)\",\n (Language::Kazakh, 0) => \"(Сұрау / Авто / Yolo / Жоспар)\",\n (Language::Kazakh, 1) => \"(v4-pro Ойлайтын / v4-flash Жылдам)\",\n (Language::Kazakh, 2) => \"(Файлдар панелі)\",\n (Language::Kazakh, 3) => \"(Интерфейс тілі)\",\n (Language::Chinese, 0) => \"(提示确认 / 自动运行 / YOLO / 仅规划)\",\n (Language::Chinese, 1) => \"(v4-pro 深度思考 / v4-flash 高速响应)\",\n (Language::Chinese, 2) => \"(文件侧边栏)\",\n (Language::Chinese, 3) => \"(界面语言)\",\n (Language::English, 0) => \"(Ask / Auto / Yolo / Plan)\",\n (Language::English, 1) => \"(v4-pro Thinking / v4-flash Fast)\",\n (Language::English, 2) => \"(File tree panel)\",\n (Language::English, 3) => \"(Interface language)\",\n (_, 4) => \"(API endpoint)\",\n (_, 5) => \"(DEEPSEEK_API_KEY)\",\n _ => \"\",\n }\n}\n\nfn banner(label: &'static str) -> Line<'static> {\n Line::from(vec![\n Span::raw(\" \"),\n Span::styled(\n label,\n Style::default()\n .fg(Color::Black)\n .bg(Color::Cyan)\n .add_modifier(Modifier::BOLD),\n ),\n ])\n}\n\nfn separator() -> Line<'static> {\n Line::from(\" ─────────────────────────────────────────────────────────────────────────────\")\n}\n\nfn selected_style(selected: bool) -> Style {\n if selected {\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD)\n } else {\n Style::default().fg(Color::Gray)\n }\n}\n\nfn chip(value: String, bg: Color) -> Span<'static> {\n Span::styled(\n format!(\"[ {value} ]\"),\n Style::default()\n .fg(Color::Black)\n .bg(bg)\n .add_modifier(Modifier::BOLD),\n )\n}\n\nfn metric_line(label: &'static str, value: String, color: Color) -> Line<'static> {\n Line::from(vec![\n Span::raw(label),\n Span::styled(\n value,\n Style::default().fg(color).add_modifier(Modifier::BOLD),\n ),\n ])\n}\n"}, "b3009bdfdfbd992cce2b48975fec541eda708582": {"t": 1785757088.2177944, "n": 34832, "h": "5eb4a117982156cf", "c": "//! DSX TUI — rendering for settings, chat, input prompt, and status bar.\n\nuse crate::App;\nuse crate::i18n::tr;\nuse crate::types::{AgentTask, Language};\nuse ratatui::{\n Frame,\n layout::Rect,\n style::{Color, Modifier, Style},\n text::{Line, Span, Text},\n widgets::{Block, BorderType, Borders, Paragraph, Wrap},\n};\n\nimpl App {\n pub fn draw_settings(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = vec![\n Line::from(\"\"),\n Line::from(vec![\n Span::raw(\" \"),\n Span::styled(\n tr(self.lang, \"settings_header_banner\"),\n Style::default()\n .fg(Color::Black)\n .bg(Color::Cyan)\n .add_modifier(Modifier::BOLD),\n ),\n ]),\n Line::from(\"\"),\n Line::from(tr(self.lang, \"settings_header_desc\")),\n Line::from(\n \" ─────────────────────────────────────────────────────────────────────────────\",\n ),\n Line::from(\"\"),\n ];\n\n // Option 1: Permission Mode\n let is_sel0 = self.settings_cursor == 0;\n let prefix0 = if is_sel0 { \" ▸ \" } else { \" \" };\n let style0 = if is_sel0 {\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD)\n } else {\n Style::default().fg(Color::Gray)\n };\n lines.push(Line::from(vec![\n Span::styled(\n prefix0,\n Style::default()\n .fg(Color::Cyan)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(tr(self.lang, \"settings_opt_security\"), style0),\n Span::styled(\n format!(\"[ {} ]\", self.mode.to_uppercase()),\n Style::default()\n .fg(Color::Black)\n .bg(Color::White)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(\n match self.lang {\n Language::Russian => \" (Политика: Спросить / Авто / Yolo / План)\",\n Language::Kazakh => \" (Саясат: Сұрау / Авто / Yolo / Жоспар)\",\n Language::Chinese => {\n \" (授权模式: 提示确认 / 自动运行 / YOLO免确认 / 仅作规划)\"\n }\n Language::English => \" (Approval Policy: Ask / Auto / Yolo / Plan)\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 2: Model Choice\n let is_sel1 = self.settings_cursor == 1;\n let prefix1 = if is_sel1 { \" ▸ \" } else { \" \" };\n let style1 = if is_sel1 {\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD)\n } else {\n Style::default().fg(Color::Gray)\n };\n lines.push(Line::from(vec![\n Span::styled(\n prefix1,\n Style::default()\n .fg(Color::Cyan)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(tr(self.lang, \"settings_opt_model\"), style1),\n Span::styled(\n format!(\"[ {} ]\", self.model.to_uppercase()),\n Style::default()\n .fg(Color::Black)\n .bg(Color::White)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(\n match self.lang {\n Language::Russian => \" (v4-pro Думающая / v4-flash Быстрая)\",\n Language::Kazakh => \" (v4-pro Ойлайтын / v4-flash Жылдам)\",\n Language::Chinese => \" (v4-pro 深度思考 / v4-flash 高速响应)\",\n Language::English => \" (v4-pro Thinking / v4-flash Non-Thinking)\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 3: Toggle Tree Explorer\n let is_sel2 = self.settings_cursor == 2;\n let prefix2 = if is_sel2 { \" ▸ \" } else { \" \" };\n let style2 = if is_sel2 {\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD)\n } else {\n Style::default().fg(Color::Gray)\n };\n let tree_status = if self.show_file_tree {\n match self.lang {\n Language::Russian => \"ПОКАЗАТЬ\",\n Language::Kazakh => \"КӨРСЕТУ\",\n Language::Chinese => \"显示\",\n Language::English => \"SHOW\",\n }\n } else {\n match self.lang {\n Language::Russian => \"СКРЫТЬ\",\n Language::Kazakh => \"ЖАСЫРУ\",\n Language::Chinese => \"隐藏\",\n Language::English => \"HIDE\",\n }\n };\n lines.push(Line::from(vec![\n Span::styled(\n prefix2,\n Style::default()\n .fg(Color::Cyan)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(tr(self.lang, \"settings_opt_sidebar\"), style2),\n Span::styled(\n format!(\"[ {} ]\", tree_status),\n Style::default()\n .fg(Color::Black)\n .bg(Color::White)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(\n match self.lang {\n Language::Russian => \" (Показать боковую панель проводника файлов)\",\n Language::Kazakh => \" (Бүйірлік файлдар панелін қосу)\",\n Language::Chinese => \" (开启或关闭工作区文件资源管理器侧边栏)\",\n Language::English => \" (Toggle workspace tree layout)\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 4: Language Selection\n let is_sel3 = self.settings_cursor == 3;\n let prefix3 = if is_sel3 { \" ▸ \" } else { \" \" };\n let style3 = if is_sel3 {\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD)\n } else {\n Style::default().fg(Color::Gray)\n };\n lines.push(Line::from(vec![\n Span::styled(\n prefix3,\n Style::default()\n .fg(Color::Cyan)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(tr(self.lang, \"settings_opt_language\"), style3),\n Span::styled(\n format!(\"[ {} ]\", self.lang.display_name()),\n Style::default()\n .fg(Color::Black)\n .bg(Color::White)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(\n match self.lang {\n Language::Russian => \" (Смена языка интерфейса TUI)\",\n Language::Kazakh => \" (TUI интерфейсі тілін ауыстыру)\",\n Language::Chinese => \" (切换系统 TUI 界面显示语言)\",\n Language::English => \" (Switch TUI interface language)\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 5: API Base URL\n let is_sel4 = self.settings_cursor == 4;\n let prefix4 = if is_sel4 { \" ▸ \" } else { \" \" };\n let style4 = if is_sel4 {\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD)\n } else {\n Style::default().fg(Color::Gray)\n };\n let api_base_label = match self.lang {\n Language::Russian => \"АДРЕС API ENDPOINT BASE: \",\n Language::Kazakh => \"API ENDPOINT НЕГІЗГІ МЕКЕНІ: \",\n Language::Chinese => \"API 接口服务基准地址: \",\n Language::English => \"API ENDPOINT BASE URL: \",\n };\n lines.push(Line::from(vec![\n Span::styled(\n prefix4,\n Style::default()\n .fg(Color::Cyan)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(api_base_label, style4),\n Span::styled(\n format!(\"[ {} ]\", self.api_base),\n Style::default()\n .fg(Color::Black)\n .bg(Color::White)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(\n match self.lang {\n Language::Russian => \" (Официальный API / Локальный Ollama / vLLM / OpenAI)\",\n Language::Kazakh => \" (Ресми API / Жергілікті Ollama / vLLM / OpenAI)\",\n Language::Chinese => \" (官方接口 / 本地 Ollama / vLLM / OpenAI 代理)\",\n Language::English => \" (Official API / Local Ollama / vLLM / OpenAI proxy)\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 6: API Authorization Key\n let is_sel5 = self.settings_cursor == 5;\n let prefix5 = if is_sel5 { \" ▸ \" } else { \" \" };\n let style5 = if is_sel5 {\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD)\n } else {\n Style::default().fg(Color::Gray)\n };\n let key_label = match self.lang {\n Language::Russian => \"КЛЮЧ АВТОРИЗАЦИИ API KEY: \",\n Language::Kazakh => \"API АВТОРИЗАЦИЯ КЛЮЧІ: \",\n Language::Chinese => \"API 授权验证密钥: \",\n Language::English => \"API AUTHORIZATION KEY: \",\n };\n\n let masked_key = if self.api_key.is_empty() {\n match self.lang {\n Language::Russian => \"⚠ КЛЮЧ НЕ НАЙДЕН\".to_string(),\n Language::Kazakh => \"⚠ КЛЮЧ ТАБЫЛМАДЫ\".to_string(),\n Language::Chinese => \"⚠ 未检测到密钥\".to_string(),\n Language::English => \"⚠ KEY NOT DETECTED\".to_string(),\n }\n } else {\n if self.api_key.len() > 10 {\n format!(\n \"{}...{}\",\n &self.api_key[..5],\n &self.api_key[self.api_key.len() - 4..]\n )\n } else {\n \"✓ Loaded\".to_string()\n }\n };\n\n lines.push(Line::from(vec![\n Span::styled(\n prefix5,\n Style::default()\n .fg(Color::Cyan)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(key_label, style5),\n Span::styled(\n format!(\"[ {} ]\", masked_key),\n Style::default()\n .fg(Color::Black)\n .bg(Color::White)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(\n match self.lang {\n Language::Russian => \" (Считывается из переменной DEEPSEEK_API_KEY)\",\n Language::Kazakh => \" (DEEPSEEK_API_KEY айнымалысынан оқылады)\",\n Language::Chinese => \" (读取自系统环境变量 DEEPSEEK_API_KEY)\",\n Language::English => \" (Loaded from DEEPSEEK_API_KEY env var)\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 7: Clear Chat History\n let is_sel6 = self.settings_cursor == 6;\n let prefix6 = if is_sel6 { \" ▸ \" } else { \" \" };\n let style6 = if is_sel6 {\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD)\n } else {\n Style::default().fg(Color::Gray)\n };\n lines.push(Line::from(vec![\n Span::styled(\n prefix6,\n Style::default()\n .fg(Color::Cyan)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(tr(self.lang, \"settings_opt_clear\"), style6),\n Span::styled(\n tr(self.lang, \"settings_clear_action\"),\n Style::default()\n .fg(Color::White)\n .bg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n ),\n ]));\n lines.push(Line::from(\"\"));\n\n // Metadata System Information\n lines.push(Line::from(\n \" ─────────────────────────────────────────────────────────────────────────────\",\n ));\n lines.push(Line::from(tr(self.lang, \"telemetry_title\")));\n lines.push(Line::from(tr(self.lang, \"telemetry_db\")));\n lines.push(Line::from(vec![\n Span::raw(tr(self.lang, \"telemetry_cost\")),\n Span::styled(\n format!(\"${:.4}\", self.cost),\n Style::default()\n .fg(Color::LightGreen)\n .add_modifier(Modifier::BOLD),\n ),\n ]));\n lines.push(Line::from(vec![\n Span::raw(tr(self.lang, \"telemetry_tokens\")),\n Span::styled(\n self.tokens.to_string(),\n Style::default()\n .fg(Color::White)\n .add_modifier(Modifier::BOLD),\n ),\n ]));\n\n let paragraph = Paragraph::new(Text::from(lines)).block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Cyan))\n .title(Span::styled(\n tr(self.lang, \"settings_title\"),\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD),\n )),\n );\n\n frame.render_widget(paragraph, area);\n }\n\n pub fn draw_chat(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = Vec::new();\n\n for msg in &self.messages {\n let (role_label, role_style) = match msg.role.as_str() {\n \"user\" => (\n match self.lang {\n Language::Russian => \"▸ Пользователь\",\n Language::Kazakh => \"▸ Пайдаланушы\",\n Language::Chinese => \"▸ 用户\",\n Language::English => \"▸ User\",\n },\n Style::default()\n .fg(Color::LightCyan)\n .add_modifier(Modifier::BOLD),\n ),\n \"assistant\" => (\n match self.lang {\n Language::Russian => \"▸ Ассистент\",\n Language::Kazakh => \"▸ Ассистент\",\n Language::Chinese => \"▸ 助手\",\n Language::English => \"▸ Assistant\",\n },\n Style::default()\n .fg(Color::LightGreen)\n .add_modifier(Modifier::BOLD),\n ),\n \"system\" => (\n match self.lang {\n Language::Russian => \"⚙ Система\",\n Language::Kazakh => \"⚙ Жүйе\",\n Language::Chinese => \"⚙ 系统\",\n Language::English => \"⚙ System\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n \"error\" => (\n match self.lang {\n Language::Russian => \"⚠ Ошибка\",\n Language::Kazakh => \"⚠ Қате\",\n Language::Chinese => \"⚠ 错误\",\n Language::English => \"⚠ Error\",\n },\n Style::default()\n .fg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n ),\n \"tool\" => (\n match self.lang {\n Language::Russian => \"🛠 Вызов инструмента\",\n Language::Kazakh => \"🛠 Құрал шақыру\",\n Language::Chinese => \"🛠 工具执行\",\n Language::English => \"🛠 Tool Execution\",\n },\n Style::default().fg(Color::LightYellow),\n ),\n _ => (\"▸ Message\", Style::default().fg(Color::White)),\n };\n\n if !lines.is_empty() {\n lines.push(Line::from(\"\"));\n }\n\n lines.push(Line::from(vec![Span::styled(role_label, role_style)]));\n\n for content_line in msg.content.lines() {\n if content_line.is_empty() {\n lines.push(Line::from(\"\"));\n } else {\n let text_style = match msg.role.as_str() {\n \"assistant\" => Style::default().fg(Color::Green),\n \"user\" => Style::default().fg(Color::LightCyan),\n \"tool\" => Style::default().fg(Color::White),\n _ => Style::default().fg(Color::Gray),\n };\n lines.push(Line::from(vec![\n Span::raw(\" \"),\n Span::styled(content_line, text_style),\n ]));\n }\n }\n }\n\n let total_lines = lines.len();\n let height = area.height.saturating_sub(2) as usize; // subtract border heights\n\n let max_scroll = total_lines.saturating_sub(height);\n let current_scroll = if self.scroll_offset == 0 && max_scroll > 0 {\n max_scroll\n } else {\n (self.scroll_offset as usize).min(max_scroll)\n };\n\n let end = (current_scroll + height).min(total_lines);\n let visible_lines = if current_scroll < total_lines {\n lines[current_scroll..end].to_vec()\n } else {\n lines\n };\n\n let text = Text::from(visible_lines);\n let paragraph = Paragraph::new(text)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Green))\n .title(Span::styled(\n tr(self.lang, \"chat_title\"),\n Style::default()\n .fg(Color::LightGreen)\n .add_modifier(Modifier::BOLD),\n )),\n )\n .wrap(Wrap { trim: false });\n\n frame.render_widget(paragraph, area);\n }\n\n pub fn draw_input(&self, frame: &mut Frame, area: Rect) {\n let input_widget = if let Some(ref approval) = self.pending_approval {\n let format_body = match self.lang {\n Language::Russian => {\n \" ⚠️ ТРЕБУЕТСЯ ПОДТВЕРЖДЕНИЕ БЕЗОПАСНОСТИ:\\n Инструмент: [{}] хочет запуститься в вашей рабочей области.\\n Аргументы: {}\\n ▸ Нажмите [Y] для ОДОБРЕНИЯ (Разрешить) | [N] для ОТКЛОНЕНИЯ (Заблокировать)\"\n }\n Language::Kazakh => {\n \" ⚠️ ҚАУІПСІЗДІК АВТОРИЗАЦИЯСЫ ТАЛАП ЕТІЛЕДІ:\\n Құрал: [{}] жұмыс аймағында іске қосылғысы келеді.\\n Аргументтер: {}\\n ▸ Рұқсат беру үшін [Y] басыңыз | Бас тарту үшін [N] басыңыз\"\n }\n Language::Chinese => {\n \" ⚠️ 需要安全授权验证:\\n 工具: [{}] 申请在您的工作区运行。\\n 参数: {}\\n ▸ 按 [Y] 同意授权 (允许) | 按 [N] 拒绝请求 (拒绝)\"\n }\n Language::English => {\n \" ⚠️ SECURITY AUTHORIZATION REQUIRED:\\n Tool: [{}] wants to run in your workspace.\\n Arguments: {}\\n ▸ Press [Y] to APPROVE (Allow) | [N] to DENY (Reject)\"\n }\n };\n let alert_text = format_body.replacen(\"{}\", &approval.tool_name, 1).replacen(\n \"{}\",\n &approval.arguments,\n 1,\n );\n\n Paragraph::new(alert_text)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::LightRed))\n .title(Span::styled(\n tr(self.lang, \"input_auth_title\"),\n Style::default()\n .fg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n )),\n )\n .style(\n Style::default()\n .fg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n )\n .wrap(Wrap { trim: false })\n } else {\n let (prompt, block_title, style) = match &self.agent_task {\n AgentTask::Idle => (\n format!(\" {}█\", self.input),\n tr(self.lang, \"input_title_idle\"),\n Style::default().fg(Color::LightCyan),\n ),\n AgentTask::Running(desc) => {\n let desc_loc = match self.lang {\n Language::Russian => format!(\"Выполнение: {}...\", desc),\n Language::Kazakh => format!(\"Орындау: {}...\", desc),\n Language::Chinese => format!(\"处理中: {}...\", desc),\n Language::English => format!(\"Processing stream: {}...\", desc),\n };\n (\n format!(\" ⟳ {}\", desc_loc),\n tr(self.lang, \"input_title_running\"),\n Style::default().fg(Color::LightYellow),\n )\n }\n AgentTask::Done(summary) => (\n format!(\" ✓ {} | {}█\", summary, self.input),\n tr(self.lang, \"input_title_done\"),\n Style::default().fg(Color::LightGreen),\n ),\n AgentTask::Error(err) => (\n format!(\" ✗ {} | {}█\", err, self.input),\n tr(self.lang, \"input_title_error\"),\n Style::default().fg(Color::LightRed),\n ),\n };\n\n Paragraph::new(prompt)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(style)\n .title(Span::styled(\n block_title,\n style.add_modifier(Modifier::BOLD),\n )),\n )\n .style(Style::default().fg(Color::White))\n .wrap(Wrap { trim: false })\n };\n\n frame.render_widget(input_widget, area);\n }\n\n pub fn draw_status(&self, frame: &mut Frame, area: Rect) {\n let task_indicator = match &self.agent_task {\n AgentTask::Idle => match self.lang {\n Language::Russian => \"⏸ ОЖИДАНИЕ\",\n Language::Kazakh => \"⏸ КҮТУ\",\n Language::Chinese => \"⏸ 空闲\",\n Language::English => \"⏸ IDLE\",\n },\n AgentTask::Running(_) => match self.lang {\n Language::Russian => \"⚡ МЫШЛЕНИЕ\",\n Language::Kazakh => \"⚡ ОЙЛАУ\",\n Language::Chinese => \"⚡ 推理中\",\n Language::English => \"⚡ THINKING\",\n },\n AgentTask::Done(_) => match self.lang {\n Language::Russian => \"✓ УСПЕХ\",\n Language::Kazakh => \"✓ СӘТТІ\",\n Language::Chinese => \"✓ 成功\",\n Language::English => \"✓ SUCCESS\",\n },\n AgentTask::Error(_) => match self.lang {\n Language::Russian => \"✗ ПРЕРВАНО\",\n Language::Kazakh => \"✗ ҮЗІЛДІ\",\n Language::Chinese => \"✗ 中断\",\n Language::English => \"✗ INTERRUPTED\",\n },\n };\n\n let mode_color = match self.mode.as_str() {\n \"yolo\" => Color::LightRed,\n \"auto\" => Color::LightYellow,\n \"ask\" => Color::LightCyan,\n \"plan-only\" => Color::LightBlue,\n _ => Color::DarkGray,\n };\n\n let cost_str = if self.cost > 0.0 {\n format!(\"${:.4}\", self.cost)\n } else {\n \"$0\".into()\n };\n\n let spans = if self.show_diff {\n vec![\n Span::styled(\n format!(\" {} \", task_indicator),\n Style::default()\n .fg(Color::Black)\n .bg(Color::LightGreen)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" \"),\n Span::styled(\n format!(\" [{}] \", self.mode.to_uppercase()),\n Style::default().fg(mode_color).add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" | \"),\n Span::styled(\n \"Ctrl+D\",\n Style::default()\n .fg(Color::Yellow)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\n \"Ctrl+C\",\n Style::default()\n .fg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else if self.show_settings {\n vec![\n Span::styled(\n format!(\" {} \", task_indicator),\n Style::default()\n .fg(Color::Black)\n .bg(Color::LightGreen)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" \"),\n Span::styled(\n format!(\" [{}] \", self.mode.to_uppercase()),\n Style::default().fg(mode_color).add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" | \"),\n Span::styled(\n \"↑/↓\",\n Style::default()\n .fg(Color::Yellow)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(match self.lang {\n Language::Russian => \":нав \",\n Language::Kazakh => \":бағ \",\n Language::Chinese => \":选 \",\n Language::English => \":nav \",\n }),\n Span::styled(\n \"←/→\",\n Style::default()\n .fg(Color::Yellow)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(match self.lang {\n Language::Russian => \":изм \",\n Language::Kazakh => \":өзг \",\n Language::Chinese => \":改 \",\n Language::English => \":mod \",\n }),\n Span::styled(\n \"Enter\",\n Style::default()\n .fg(Color::Yellow)\n .add_modifier(Modifier::BOLD),\n ),\n Span::styled(\n \" Esc\",\n Style::default()\n .fg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(match self.lang {\n Language::Russian => \":вых \",\n Language::Kazakh => \":қайту \",\n Language::Chinese => \":返 \",\n Language::English => \":exit \",\n }),\n Span::styled(\n \"Ctrl+C\",\n Style::default()\n .fg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else {\n let width = area.width;\n if width >= 110 {\n // Full gorgeous layout\n vec![\n Span::styled(\n format!(\" {} \", task_indicator),\n Style::default()\n .fg(Color::Black)\n .bg(Color::LightGreen)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" \"),\n Span::styled(\n format!(\" [{}] \", self.mode.to_uppercase()),\n Style::default().fg(mode_color).add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" | model: \"),\n Span::styled(\n self.model.as_str(),\n Style::default()\n .fg(Color::White)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" | tokens: \"),\n Span::styled(self.tokens.to_string(), Style::default().fg(Color::White)),\n Span::raw(\" | cost: \"),\n Span::styled(\n cost_str,\n Style::default()\n .fg(Color::LightGreen)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" | \"),\n Span::styled(\n \"Ctrl+S\",\n Style::default()\n .fg(Color::Yellow)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_settings_toggle\")),\n Span::styled(\n \"Ctrl+T\",\n Style::default()\n .fg(Color::Yellow)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_tree_toggle\")),\n Span::styled(\n \"Ctrl+D\",\n Style::default()\n .fg(Color::Yellow)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\n \"Ctrl+U\",\n Style::default()\n .fg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_undo_toggle\")),\n Span::styled(\n \"Ctrl+C\",\n Style::default()\n .fg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else if width >= 80 {\n // Compact responsive layout\n vec![\n Span::styled(\n format!(\" {} \", task_indicator),\n Style::default()\n .fg(Color::Black)\n .bg(Color::LightGreen)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" \"),\n Span::styled(\n format!(\" [{}] \", self.mode.to_uppercase()),\n Style::default().fg(mode_color).add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" | cost: \"),\n Span::styled(\n cost_str,\n Style::default()\n .fg(Color::LightGreen)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" | \"),\n Span::styled(\n \"Ctrl+S\",\n Style::default()\n .fg(Color::Yellow)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_settings_toggle\")),\n Span::styled(\n \"Ctrl+D\",\n Style::default()\n .fg(Color::Yellow)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\n \"Ctrl+U\",\n Style::default()\n .fg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_undo_toggle\")),\n ]\n } else {\n // Minimal responsive layout for narrow screens\n vec![\n Span::styled(\n format!(\" {} \", task_indicator),\n Style::default()\n .fg(Color::Black)\n .bg(Color::LightGreen)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" \"),\n Span::styled(\n format!(\" [{}] \", self.mode.to_uppercase()),\n Style::default().fg(mode_color).add_modifier(Modifier::BOLD),\n ),\n Span::raw(\" | \"),\n Span::styled(\n \"Ctrl+S\",\n Style::default()\n .fg(Color::Yellow)\n .add_modifier(Modifier::BOLD),\n ),\n Span::raw(tr(self.lang, \"status_settings_toggle\")),\n Span::styled(\n \"Ctrl+C\",\n Style::default()\n .fg(Color::LightRed)\n .add_modifier(Modifier::BOLD),\n ),\n ]\n }\n };\n\n let status_bar = Paragraph::new(Line::from(spans))\n .style(Style::default().bg(Color::Black).fg(Color::Gray));\n\n frame.render_widget(status_bar, area);\n }\n}\n"}, "8c8e430d015e3d0176270b8b31493288307763aa": {"t": 1785757089.7395391, "n": 27782, "h": "b3d1a2c69a7a451c", "c": "//! DSX TUI — rendering for settings, chat, input prompt, and status bar.\n\nuse crate::types::{Language, AgentTask};\nuse crate::App;\nuse crate::i18n::tr;\nuse ratatui::{\n layout::Rect,\n style::{Color, Modifier, Style},\n text::{Line, Span, Text},\n widgets::{Block, BorderType, Borders, Paragraph, Wrap},\n Frame,\n};\n\nimpl App {\n pub fn draw_settings(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = Vec::new();\n\n lines.push(Line::from(\"\"));\n lines.push(Line::from(vec![\n Span::raw(\" \"),\n Span::styled(tr(self.lang, \"settings_header_banner\"), Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(\"\"));\n lines.push(Line::from(tr(self.lang, \"settings_header_desc\")));\n lines.push(Line::from(\" ─────────────────────────────────────────────────────────────────────────────\"));\n lines.push(Line::from(\"\"));\n\n // Option 1: Permission Mode\n let is_sel0 = self.settings_cursor == 0;\n let prefix0 = if is_sel0 { \" ▸ \" } else { \" \" };\n let style0 = if is_sel0 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix0, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_security\"), style0),\n Span::styled(format!(\"[ {} ]\", self.mode.to_uppercase()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Политика: Спросить / Авто / Yolo / План)\",\n Language::Kazakh => \" (Саясат: Сұрау / Авто / Yolo / Жоспар)\",\n Language::Chinese => \" (授权模式: 提示确认 / 自动运行 / YOLO免确认 / 仅作规划)\",\n Language::English => \" (Approval Policy: Ask / Auto / Yolo / Plan)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 2: Model Choice\n let is_sel1 = self.settings_cursor == 1;\n let prefix1 = if is_sel1 { \" ▸ \" } else { \" \" };\n let style1 = if is_sel1 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix1, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_model\"), style1),\n Span::styled(format!(\"[ {} ]\", self.model.to_uppercase()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (v4-pro Думающая / v4-flash Быстрая)\",\n Language::Kazakh => \" (v4-pro Ойлайтын / v4-flash Жылдам)\",\n Language::Chinese => \" (v4-pro 深度思考 / v4-flash 高速响应)\",\n Language::English => \" (v4-pro Thinking / v4-flash Non-Thinking)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 3: Toggle Tree Explorer\n let is_sel2 = self.settings_cursor == 2;\n let prefix2 = if is_sel2 { \" ▸ \" } else { \" \" };\n let style2 = if is_sel2 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let tree_status = if self.show_file_tree {\n match self.lang {\n Language::Russian => \"ПОКАЗАТЬ\",\n Language::Kazakh => \"КӨРСЕТУ\",\n Language::Chinese => \"显示\",\n Language::English => \"SHOW\",\n }\n } else {\n match self.lang {\n Language::Russian => \"СКРЫТЬ\",\n Language::Kazakh => \"ЖАСЫРУ\",\n Language::Chinese => \"隐藏\",\n Language::English => \"HIDE\",\n }\n };\n lines.push(Line::from(vec![\n Span::styled(prefix2, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_sidebar\"), style2),\n Span::styled(format!(\"[ {} ]\", tree_status), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Показать боковую панель проводника файлов)\",\n Language::Kazakh => \" (Бүйірлік файлдар панелін қосу)\",\n Language::Chinese => \" (开启或关闭工作区文件资源管理器侧边栏)\",\n Language::English => \" (Toggle workspace tree layout)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 4: Language Selection\n let is_sel3 = self.settings_cursor == 3;\n let prefix3 = if is_sel3 { \" ▸ \" } else { \" \" };\n let style3 = if is_sel3 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix3, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_language\"), style3),\n Span::styled(format!(\"[ {} ]\", self.lang.display_name()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Смена языка интерфейса TUI)\",\n Language::Kazakh => \" (TUI интерфейсі тілін ауыстыру)\",\n Language::Chinese => \" (切换系统 TUI 界面显示语言)\",\n Language::English => \" (Switch TUI interface language)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 5: API Base URL\n let is_sel4 = self.settings_cursor == 4;\n let prefix4 = if is_sel4 { \" ▸ \" } else { \" \" };\n let style4 = if is_sel4 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let api_base_label = match self.lang {\n Language::Russian => \"АДРЕС API ENDPOINT BASE: \",\n Language::Kazakh => \"API ENDPOINT НЕГІЗГІ МЕКЕНІ: \",\n Language::Chinese => \"API 接口服务基准地址: \",\n Language::English => \"API ENDPOINT BASE URL: \",\n };\n lines.push(Line::from(vec![\n Span::styled(prefix4, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(api_base_label, style4),\n Span::styled(format!(\"[ {} ]\", self.api_base), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Официальный API / Локальный Ollama / vLLM / OpenAI)\",\n Language::Kazakh => \" (Ресми API / Жергілікті Ollama / vLLM / OpenAI)\",\n Language::Chinese => \" (官方接口 / 本地 Ollama / vLLM / OpenAI 代理)\",\n Language::English => \" (Official API / Local Ollama / vLLM / OpenAI proxy)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 6: API Authorization Key\n let is_sel5 = self.settings_cursor == 5;\n let prefix5 = if is_sel5 { \" ▸ \" } else { \" \" };\n let style5 = if is_sel5 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let key_label = match self.lang {\n Language::Russian => \"КЛЮЧ АВТОРИЗАЦИИ API KEY: \",\n Language::Kazakh => \"API АВТОРИЗАЦИЯ КЛЮЧІ: \",\n Language::Chinese => \"API 授权验证密钥: \",\n Language::English => \"API AUTHORIZATION KEY: \",\n };\n\n let masked_key = if self.api_key.is_empty() {\n match self.lang {\n Language::Russian => \"⚠ КЛЮЧ НЕ НАЙДЕН\".to_string(),\n Language::Kazakh => \"⚠ КЛЮЧ ТАБЫЛМАДЫ\".to_string(),\n Language::Chinese => \"⚠ 未检测到密钥\".to_string(),\n Language::English => \"⚠ KEY NOT DETECTED\".to_string(),\n }\n } else {\n if self.api_key.len() > 10 {\n format!(\"{}...{}\", &self.api_key[..5], &self.api_key[self.api_key.len()-4..])\n } else {\n \"✓ Loaded\".to_string()\n }\n };\n\n lines.push(Line::from(vec![\n Span::styled(prefix5, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(key_label, style5),\n Span::styled(format!(\"[ {} ]\", masked_key), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Считывается из переменной DEEPSEEK_API_KEY)\",\n Language::Kazakh => \" (DEEPSEEK_API_KEY айнымалысынан оқылады)\",\n Language::Chinese => \" (读取自系统环境变量 DEEPSEEK_API_KEY)\",\n Language::English => \" (Loaded from DEEPSEEK_API_KEY env var)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 7: Clear Chat History\n let is_sel6 = self.settings_cursor == 6;\n let prefix6 = if is_sel6 { \" ▸ \" } else { \" \" };\n let style6 = if is_sel6 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix6, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_clear\"), style6),\n Span::styled(tr(self.lang, \"settings_clear_action\"), Style::default().fg(Color::White).bg(Color::LightRed).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Metadata System Information\n lines.push(Line::from(\" ─────────────────────────────────────────────────────────────────────────────\"));\n lines.push(Line::from(tr(self.lang, \"telemetry_title\")));\n lines.push(Line::from(tr(self.lang, \"telemetry_db\")));\n lines.push(Line::from(vec![\n Span::raw(tr(self.lang, \"telemetry_cost\")),\n Span::styled(format!(\"${:.4}\", self.cost), Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(vec![\n Span::raw(tr(self.lang, \"telemetry_tokens\")),\n Span::styled(self.tokens.to_string(), Style::default().fg(Color::White).add_modifier(Modifier::BOLD)),\n ]));\n\n let paragraph = Paragraph::new(Text::from(lines))\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Cyan))\n .title(Span::styled(tr(self.lang, \"settings_title\"), Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD)))\n );\n\n frame.render_widget(paragraph, area);\n }\n\n pub fn draw_chat(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = Vec::new();\n\n for msg in &self.messages {\n let (role_label, role_style) = match msg.role.as_str() {\n \"user\" => (\n match self.lang {\n Language::Russian => \"▸ Пользователь\",\n Language::Kazakh => \"▸ Пайдаланушы\",\n Language::Chinese => \"▸ 用户\",\n Language::English => \"▸ User\",\n },\n Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD),\n ),\n \"assistant\" => (\n match self.lang {\n Language::Russian => \"▸ Ассистент\",\n Language::Kazakh => \"▸ Ассистент\",\n Language::Chinese => \"▸ 助手\",\n Language::English => \"▸ Assistant\",\n },\n Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD),\n ),\n \"system\" => (\n match self.lang {\n Language::Russian => \"⚙ Система\",\n Language::Kazakh => \"⚙ Жүйе\",\n Language::Chinese => \"⚙ 系统\",\n Language::English => \"⚙ System\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n \"error\" => (\n match self.lang {\n Language::Russian => \"⚠ Ошибка\",\n Language::Kazakh => \"⚠ Қате\",\n Language::Chinese => \"⚠ 错误\",\n Language::English => \"⚠ Error\",\n },\n Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD),\n ),\n \"tool\" => (\n match self.lang {\n Language::Russian => \"🛠 Вызов инструмента\",\n Language::Kazakh => \"🛠 Құрал шақыру\",\n Language::Chinese => \"🛠 工具执行\",\n Language::English => \"🛠 Tool Execution\",\n },\n Style::default().fg(Color::LightYellow),\n ),\n _ => (\n \"▸ Message\",\n Style::default().fg(Color::White),\n ),\n };\n\n if !lines.is_empty() {\n lines.push(Line::from(\"\"));\n }\n\n lines.push(Line::from(vec![\n Span::styled(role_label, role_style),\n ]));\n\n for content_line in msg.content.lines() {\n if content_line.is_empty() {\n lines.push(Line::from(\"\"));\n } else {\n let text_style = match msg.role.as_str() {\n \"assistant\" => Style::default().fg(Color::Green),\n \"user\" => Style::default().fg(Color::LightCyan),\n \"tool\" => Style::default().fg(Color::White),\n _ => Style::default().fg(Color::Gray),\n };\n lines.push(Line::from(vec![\n Span::raw(\" \"),\n Span::styled(content_line, text_style),\n ]));\n }\n }\n }\n\n let total_lines = lines.len();\n let height = area.height.saturating_sub(2) as usize; // subtract border heights\n \n let max_scroll = total_lines.saturating_sub(height);\n let current_scroll = if self.scroll_offset == 0 && max_scroll > 0 {\n max_scroll\n } else {\n (self.scroll_offset as usize).min(max_scroll)\n };\n\n let end = (current_scroll + height).min(total_lines);\n let visible_lines = if current_scroll < total_lines {\n lines[current_scroll..end].to_vec()\n } else {\n lines\n };\n\n let text = Text::from(visible_lines);\n let paragraph = Paragraph::new(text)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Green))\n .title(Span::styled(tr(self.lang, \"chat_title\"), Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)))\n )\n .wrap(Wrap { trim: false });\n\n frame.render_widget(paragraph, area);\n }\n\n pub fn draw_input(&self, frame: &mut Frame, area: Rect) {\n let input_widget = if let Some(ref approval) = self.pending_approval {\n let format_body = match self.lang {\n Language::Russian => \" ⚠️ ТРЕБУЕТСЯ ПОДТВЕРЖДЕНИЕ БЕЗОПАСНОСТИ:\\n Инструмент: [{}] хочет запуститься в вашей рабочей области.\\n Аргументы: {}\\n ▸ Нажмите [Y] для ОДОБРЕНИЯ (Разрешить) | [N] для ОТКЛОНЕНИЯ (Заблокировать)\",\n Language::Kazakh => \" ⚠️ ҚАУІПСІЗДІК АВТОРИЗАЦИЯСЫ ТАЛАП ЕТІЛЕДІ:\\n Құрал: [{}] жұмыс аймағында іске қосылғысы келеді.\\n Аргументтер: {}\\n ▸ Рұқсат беру үшін [Y] басыңыз | Бас тарту үшін [N] басыңыз\",\n Language::Chinese => \" ⚠️ 需要安全授权验证:\\n 工具: [{}] 申请在您的工作区运行。\\n 参数: {}\\n ▸ 按 [Y] 同意授权 (允许) | 按 [N] 拒绝请求 (拒绝)\",\n Language::English => \" ⚠️ SECURITY AUTHORIZATION REQUIRED:\\n Tool: [{}] wants to run in your workspace.\\n Arguments: {}\\n ▸ Press [Y] to APPROVE (Allow) | [N] to DENY (Reject)\",\n };\n let alert_text = format_body\n .replacen(\"{}\", &approval.tool_name, 1)\n .replacen(\"{}\", &approval.arguments, 1);\n\n Paragraph::new(alert_text)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::LightRed))\n .title(Span::styled(tr(self.lang, \"input_auth_title\"), Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)))\n )\n .style(Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD))\n .wrap(Wrap { trim: false })\n } else {\n let (prompt, block_title, style) = match &self.agent_task {\n AgentTask::Idle => (\n format!(\" {}█\", self.input),\n tr(self.lang, \"input_title_idle\"),\n Style::default().fg(Color::LightCyan),\n ),\n AgentTask::Running(desc) => {\n let desc_loc = match self.lang {\n Language::Russian => format!(\"Выполнение: {}...\", desc),\n Language::Kazakh => format!(\"Орындау: {}...\", desc),\n Language::Chinese => format!(\"处理中: {}...\", desc),\n Language::English => format!(\"Processing stream: {}...\", desc),\n };\n (\n format!(\" ⟳ {}\", desc_loc),\n tr(self.lang, \"input_title_running\"),\n Style::default().fg(Color::LightYellow),\n )\n }\n AgentTask::Done(summary) => (\n format!(\" ✓ {} | {}█\", summary, self.input),\n tr(self.lang, \"input_title_done\"),\n Style::default().fg(Color::LightGreen),\n ),\n AgentTask::Error(err) => (\n format!(\" ✗ {} | {}█\", err, self.input),\n tr(self.lang, \"input_title_error\"),\n Style::default().fg(Color::LightRed),\n ),\n };\n\n Paragraph::new(prompt)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(style)\n .title(Span::styled(block_title, style.add_modifier(Modifier::BOLD)))\n )\n .style(Style::default().fg(Color::White))\n .wrap(Wrap { trim: false })\n };\n\n frame.render_widget(input_widget, area);\n }\n\n pub fn draw_status(&self, frame: &mut Frame, area: Rect) {\n let task_indicator = match &self.agent_task {\n AgentTask::Idle => match self.lang {\n Language::Russian => \"⏸ ОЖИДАНИЕ\",\n Language::Kazakh => \"⏸ КҮТУ\",\n Language::Chinese => \"⏸ 空闲\",\n Language::English => \"⏸ IDLE\",\n },\n AgentTask::Running(_) => match self.lang {\n Language::Russian => \"⚡ МЫШЛЕНИЕ\",\n Language::Kazakh => \"⚡ ОЙЛАУ\",\n Language::Chinese => \"⚡ 推理中\",\n Language::English => \"⚡ THINKING\",\n },\n AgentTask::Done(_) => match self.lang {\n Language::Russian => \"✓ УСПЕХ\",\n Language::Kazakh => \"✓ СӘТТІ\",\n Language::Chinese => \"✓ 成功\",\n Language::English => \"✓ SUCCESS\",\n },\n AgentTask::Error(_) => match self.lang {\n Language::Russian => \"✗ ПРЕРВАНО\",\n Language::Kazakh => \"✗ ҮЗІЛДІ\",\n Language::Chinese => \"✗ 中断\",\n Language::English => \"✗ INTERRUPTED\",\n },\n };\n\n let mode_color = match self.mode.as_str() {\n \"yolo\" => Color::LightRed,\n \"auto\" => Color::LightYellow,\n \"ask\" => Color::LightCyan,\n \"plan-only\" => Color::LightBlue,\n _ => Color::DarkGray,\n };\n\n let cost_str = if self.cost > 0.0 {\n format!(\"${:.4}\", self.cost)\n } else {\n \"$0\".into()\n };\n\n let spans = if self.show_diff {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"Ctrl+D\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else if self.show_settings {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"↑/↓\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":нав \",\n Language::Kazakh => \":бағ \",\n Language::Chinese => \":选 \",\n Language::English => \":nav \",\n }),\n Span::styled(\"←/→\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":изм \",\n Language::Kazakh => \":өзг \",\n Language::Chinese => \":改 \",\n Language::English => \":mod \",\n }),\n Span::styled(\"Enter\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::styled(\" Esc\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":вых \",\n Language::Kazakh => \":қайту \",\n Language::Chinese => \":返 \",\n Language::English => \":exit \",\n }),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else {\n let width = area.width;\n if width >= 110 {\n // Full gorgeous layout\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | model: \"),\n Span::styled(self.model.as_str(), Style::default().fg(Color::White).add_modifier(Modifier::BOLD)),\n Span::raw(\" | tokens: \"),\n Span::styled(self.tokens.to_string(), Style::default().fg(Color::White)),\n Span::raw(\" | cost: \"),\n Span::styled(cost_str, Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"Ctrl+S\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_settings_toggle\")),\n Span::styled(\"Ctrl+T\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_tree_toggle\")),\n Span::styled(\"Ctrl+D\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\"Ctrl+U\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_undo_toggle\")),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else if width >= 80 {\n // Compact responsive layout\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | cost: \"),\n Span::styled(cost_str, Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"Ctrl+S\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_settings_toggle\")),\n Span::styled(\"Ctrl+D\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\"Ctrl+U\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_undo_toggle\")),\n ]\n } else {\n // Minimal responsive layout for narrow screens\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"Ctrl+S\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_settings_toggle\")),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n ]\n }\n };\n\n let status_bar = Paragraph::new(Line::from(spans))\n .style(Style::default().bg(Color::Black).fg(Color::Gray));\n\n frame.render_widget(status_bar, area);\n }\n}\n"}, "f81af79d664142f1eafe197a972a519e01ca7cd5": {"t": 1785757092.5438085, "n": 25918, "h": "bd88c960f2bd8b0d", "c": "//! DSX TUI — rendering for settings, chat, input prompt, and status bar.\n\nuse crate::types::{Language, AgentTask};\nuse crate::App;\nuse crate::i18n::tr;\nuse ratatui::{\n layout::Rect,\n style::{Color, Modifier, Style},\n text::{Line, Span, Text},\n widgets::{Block, BorderType, Borders, Paragraph, Wrap},\n Frame,\n};\n\nimpl App {\n pub fn draw_settings(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = Vec::new();\n\n lines.push(Line::from(\"\"));\n lines.push(Line::from(vec![\n Span::raw(\" \"),\n Span::styled(tr(self.lang, \"settings_header_banner\"), Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(\"\"));\n lines.push(Line::from(tr(self.lang, \"settings_header_desc\")));\n lines.push(Line::from(\" ─────────────────────────────────────────────────────────────────────────────\"));\n lines.push(Line::from(\"\"));\n\n // Option 1: Permission Mode\n let is_sel0 = self.settings_cursor == 0;\n let prefix0 = if is_sel0 { \" ▸ \" } else { \" \" };\n let style0 = if is_sel0 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix0, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_security\"), style0),\n Span::styled(format!(\"[ {} ]\", self.mode.to_uppercase()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Политика: Спросить / Авто / Yolo / План)\",\n Language::Kazakh => \" (Саясат: Сұрау / Авто / Yolo / Жоспар)\",\n Language::Chinese => \" (授权模式: 提示确认 / 自动运行 / YOLO免确认 / 仅作规划)\",\n Language::English => \" (Approval Policy: Ask / Auto / Yolo / Plan)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 2: Model Choice\n let is_sel1 = self.settings_cursor == 1;\n let prefix1 = if is_sel1 { \" ▸ \" } else { \" \" };\n let style1 = if is_sel1 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix1, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_model\"), style1),\n Span::styled(format!(\"[ {} ]\", self.model.to_uppercase()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (v4-pro Думающая / v4-flash Быстрая)\",\n Language::Kazakh => \" (v4-pro Ойлайтын / v4-flash Жылдам)\",\n Language::Chinese => \" (v4-pro 深度思考 / v4-flash 高速响应)\",\n Language::English => \" (v4-pro Thinking / v4-flash Non-Thinking)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 3: Toggle Tree Explorer\n let is_sel2 = self.settings_cursor == 2;\n let prefix2 = if is_sel2 { \" ▸ \" } else { \" \" };\n let style2 = if is_sel2 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let tree_status = if self.show_file_tree {\n match self.lang {\n Language::Russian => \"ПОКАЗАТЬ\",\n Language::Kazakh => \"КӨРСЕТУ\",\n Language::Chinese => \"显示\",\n Language::English => \"SHOW\",\n }\n } else {\n match self.lang {\n Language::Russian => \"СКРЫТЬ\",\n Language::Kazakh => \"ЖАСЫРУ\",\n Language::Chinese => \"隐藏\",\n Language::English => \"HIDE\",\n }\n };\n lines.push(Line::from(vec![\n Span::styled(prefix2, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_sidebar\"), style2),\n Span::styled(format!(\"[ {} ]\", tree_status), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Показать боковую панель проводника файлов)\",\n Language::Kazakh => \" (Бүйірлік файлдар панелін қосу)\",\n Language::Chinese => \" (开启或关闭工作区文件资源管理器侧边栏)\",\n Language::English => \" (Toggle workspace tree layout)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 4: Language Selection\n let is_sel3 = self.settings_cursor == 3;\n let prefix3 = if is_sel3 { \" ▸ \" } else { \" \" };\n let style3 = if is_sel3 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix3, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_language\"), style3),\n Span::styled(format!(\"[ {} ]\", self.lang.display_name()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Смена языка интерфейса TUI)\",\n Language::Kazakh => \" (TUI интерфейсі тілін ауыстыру)\",\n Language::Chinese => \" (切换系统 TUI 界面显示语言)\",\n Language::English => \" (Switch TUI interface language)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 5: API Base URL\n let is_sel4 = self.settings_cursor == 4;\n let prefix4 = if is_sel4 { \" ▸ \" } else { \" \" };\n let style4 = if is_sel4 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let api_base_label = match self.lang {\n Language::Russian => \"АДРЕС API ENDPOINT BASE: \",\n Language::Kazakh => \"API ENDPOINT НЕГІЗГІ МЕКЕНІ: \",\n Language::Chinese => \"API 接口服务基准地址: \",\n Language::English => \"API ENDPOINT BASE URL: \",\n };\n lines.push(Line::from(vec![\n Span::styled(prefix4, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(api_base_label, style4),\n Span::styled(format!(\"[ {} ]\", self.api_base), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Официальный API / Локальный Ollama / vLLM / OpenAI)\",\n Language::Kazakh => \" (Ресми API / Жергілікті Ollama / vLLM / OpenAI)\",\n Language::Chinese => \" (官方接口 / 本地 Ollama / vLLM / OpenAI 代理)\",\n Language::English => \" (Official API / Local Ollama / vLLM / OpenAI proxy)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 6: API Authorization Key\n let is_sel5 = self.settings_cursor == 5;\n let prefix5 = if is_sel5 { \" ▸ \" } else { \" \" };\n let style5 = if is_sel5 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let key_label = match self.lang {\n Language::Russian => \"КЛЮЧ АВТОРИЗАЦИИ API KEY: \",\n Language::Kazakh => \"API АВТОРИЗАЦИЯ КЛЮЧІ: \",\n Language::Chinese => \"API 授权验证密钥: \",\n Language::English => \"API AUTHORIZATION KEY: \",\n };\n\n let masked_key = if self.api_key.is_empty() {\n match self.lang {\n Language::Russian => \"⚠ КЛЮЧ НЕ НАЙДЕН\".to_string(),\n Language::Kazakh => \"⚠ КЛЮЧ ТАБЫЛМАДЫ\".to_string(),\n Language::Chinese => \"⚠ 未检测到密钥\".to_string(),\n Language::English => \"⚠ KEY NOT DETECTED\".to_string(),\n }\n } else {\n if self.api_key.len() > 10 {\n format!(\"{}...{}\", &self.api_key[..5], &self.api_key[self.api_key.len()-4..])\n } else {\n \"✓ Loaded\".to_string()\n }\n };\n\n lines.push(Line::from(vec![\n Span::styled(prefix5, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(key_label, style5),\n Span::styled(format!(\"[ {} ]\", masked_key), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Считывается из переменной DEEPSEEK_API_KEY)\",\n Language::Kazakh => \" (DEEPSEEK_API_KEY айнымалысынан оқылады)\",\n Language::Chinese => \" (读取自系统环境变量 DEEPSEEK_API_KEY)\",\n Language::English => \" (Loaded from DEEPSEEK_API_KEY env var)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 7: Clear Chat History\n let is_sel6 = self.settings_cursor == 6;\n let prefix6 = if is_sel6 { \" ▸ \" } else { \" \" };\n let style6 = if is_sel6 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix6, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_clear\"), style6),\n Span::styled(tr(self.lang, \"settings_clear_action\"), Style::default().fg(Color::White).bg(Color::LightRed).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Metadata System Information\n lines.push(Line::from(\" ─────────────────────────────────────────────────────────────────────────────\"));\n lines.push(Line::from(tr(self.lang, \"telemetry_title\")));\n lines.push(Line::from(tr(self.lang, \"telemetry_db\")));\n lines.push(Line::from(vec![\n Span::raw(tr(self.lang, \"telemetry_cost\")),\n Span::styled(format!(\"${:.4}\", self.cost), Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(vec![\n Span::raw(tr(self.lang, \"telemetry_tokens\")),\n Span::styled(self.tokens.to_string(), Style::default().fg(Color::White).add_modifier(Modifier::BOLD)),\n ]));\n\n let paragraph = Paragraph::new(Text::from(lines))\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Cyan))\n .title(Span::styled(tr(self.lang, \"settings_title\"), Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD)))\n );\n\n frame.render_widget(paragraph, area);\n }\n\n pub fn draw_chat(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = Vec::new();\n\n for msg in &self.messages {\n let (role_label, role_style) = match msg.role.as_str() {\n \"user\" => (\n match self.lang {\n Language::Russian => \"▸ Пользователь\",\n Language::Kazakh => \"▸ Пайдаланушы\",\n Language::Chinese => \"▸ 用户\",\n Language::English => \"▸ User\",\n },\n Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD),\n ),\n \"assistant\" => (\n match self.lang {\n Language::Russian => \"▸ Ассистент\",\n Language::Kazakh => \"▸ Ассистент\",\n Language::Chinese => \"▸ 助手\",\n Language::English => \"▸ Assistant\",\n },\n Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD),\n ),\n \"system\" => (\n match self.lang {\n Language::Russian => \"⚙ Система\",\n Language::Kazakh => \"⚙ Жүйе\",\n Language::Chinese => \"⚙ 系统\",\n Language::English => \"⚙ System\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n \"error\" => (\n match self.lang {\n Language::Russian => \"⚠ Ошибка\",\n Language::Kazakh => \"⚠ Қате\",\n Language::Chinese => \"⚠ 错误\",\n Language::English => \"⚠ Error\",\n },\n Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD),\n ),\n \"tool\" => (\n match self.lang {\n Language::Russian => \"🛠 Вызов инструмента\",\n Language::Kazakh => \"🛠 Құрал шақыру\",\n Language::Chinese => \"🛠 工具执行\",\n Language::English => \"🛠 Tool Execution\",\n },\n Style::default().fg(Color::LightYellow),\n ),\n _ => (\n \"▸ Message\",\n Style::default().fg(Color::White),\n ),\n };\n\n if !lines.is_empty() {\n lines.push(Line::from(\"\"));\n }\n\n lines.push(Line::from(vec![\n Span::styled(role_label, role_style),\n ]));\n\n for content_line in msg.content.lines() {\n if content_line.is_empty() {\n lines.push(Line::from(\"\"));\n } else {\n let text_style = match msg.role.as_str() {\n \"assistant\" => Style::default().fg(Color::Green),\n \"user\" => Style::default().fg(Color::LightCyan),\n \"tool\" => Style::default().fg(Color::White),\n _ => Style::default().fg(Color::Gray),\n };\n lines.push(Line::from(vec![\n Span::raw(\" \"),\n Span::styled(content_line, text_style),\n ]));\n }\n }\n }\n\n let total_lines = lines.len();\n let height = area.height.saturating_sub(2) as usize; // subtract border heights\n \n let max_scroll = total_lines.saturating_sub(height);\n let current_scroll = if self.scroll_offset == 0 && max_scroll > 0 {\n max_scroll\n } else {\n (self.scroll_offset as usize).min(max_scroll)\n };\n\n let end = (current_scroll + height).min(total_lines);\n let visible_lines = if current_scroll < total_lines {\n lines[current_scroll..end].to_vec()\n } else {\n lines\n };\n\n let text = Text::from(visible_lines);\n let paragraph = Paragraph::new(text)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Green))\n .title(Span::styled(tr(self.lang, \"chat_title\"), Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)))\n )\n .wrap(Wrap { trim: false });\n\n frame.render_widget(paragraph, area);\n }\n\n pub fn draw_input(&self, frame: &mut Frame, area: Rect) {\n let input_widget = if let Some(ref approval) = self.pending_approval {\n let format_body = match self.lang {\n Language::Russian => \" ⚠️ ТРЕБУЕТСЯ ПОДТВЕРЖДЕНИЕ БЕЗОПАСНОСТИ:\\n Инструмент: [{}] хочет запуститься в вашей рабочей области.\\n Аргументы: {}\\n ▸ Нажмите [Y] для ОДОБРЕНИЯ (Разрешить) | [N] для ОТКЛОНЕНИЯ (Заблокировать)\",\n Language::Kazakh => \" ⚠️ ҚАУІПСІЗДІК АВТОРИЗАЦИЯСЫ ТАЛАП ЕТІЛЕДІ:\\n Құрал: [{}] жұмыс аймағында іске қосылғысы келеді.\\n Аргументтер: {}\\n ▸ Рұқсат беру үшін [Y] басыңыз | Бас тарту үшін [N] басыңыз\",\n Language::Chinese => \" ⚠️ 需要安全授权验证:\\n 工具: [{}] 申请在您的工作区运行。\\n 参数: {}\\n ▸ 按 [Y] 同意授权 (允许) | 按 [N] 拒绝请求 (拒绝)\",\n Language::English => \" ⚠️ SECURITY AUTHORIZATION REQUIRED:\\n Tool: [{}] wants to run in your workspace.\\n Arguments: {}\\n ▸ Press [Y] to APPROVE (Allow) | [N] to DENY (Reject)\",\n };\n let alert_text = format_body\n .replacen(\"{}\", &approval.tool_name, 1)\n .replacen(\"{}\", &approval.arguments, 1);\n\n Paragraph::new(alert_text)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::LightRed))\n .title(Span::styled(tr(self.lang, \"input_auth_title\"), Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)))\n )\n .style(Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD))\n .wrap(Wrap { trim: false })\n } else {\n let (prompt, block_title, style) = match &self.agent_task {\n AgentTask::Idle => (\n format!(\" {}█\", self.input),\n tr(self.lang, \"input_title_idle\"),\n Style::default().fg(Color::LightCyan),\n ),\n AgentTask::Running(desc) => {\n let desc_loc = match self.lang {\n Language::Russian => format!(\"Выполнение: {}...\", desc),\n Language::Kazakh => format!(\"Орындау: {}...\", desc),\n Language::Chinese => format!(\"处理中: {}...\", desc),\n Language::English => format!(\"Processing stream: {}...\", desc),\n };\n (\n format!(\" ⟳ {}\", desc_loc),\n tr(self.lang, \"input_title_running\"),\n Style::default().fg(Color::LightYellow),\n )\n }\n AgentTask::Done(summary) => (\n format!(\" ✓ {} | {}█\", summary, self.input),\n tr(self.lang, \"input_title_done\"),\n Style::default().fg(Color::LightGreen),\n ),\n AgentTask::Error(err) => (\n format!(\" ✗ {} | {}█\", err, self.input),\n tr(self.lang, \"input_title_error\"),\n Style::default().fg(Color::LightRed),\n ),\n };\n\n Paragraph::new(prompt)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(style)\n .title(Span::styled(block_title, style.add_modifier(Modifier::BOLD)))\n )\n .style(Style::default().fg(Color::White))\n .wrap(Wrap { trim: false })\n };\n\n frame.render_widget(input_widget, area);\n }\n\n pub fn draw_status(&self, frame: &mut Frame, area: Rect) {\n let task_indicator = match &self.agent_task {\n AgentTask::Idle => match self.lang {\n Language::Russian => \"⏸ ОЖИДАНИЕ\",\n Language::Kazakh => \"⏸ КҮТУ\",\n Language::Chinese => \"⏸ 空闲\",\n Language::English => \"⏸ IDLE\",\n },\n AgentTask::Running(_) => match self.lang {\n Language::Russian => \"⚡ МЫШЛЕНИЕ\",\n Language::Kazakh => \"⚡ ОЙЛАУ\",\n Language::Chinese => \"⚡ 推理中\",\n Language::English => \"⚡ THINKING\",\n },\n AgentTask::Done(_) => match self.lang {\n Language::Russian => \"✓ УСПЕХ\",\n Language::Kazakh => \"✓ СӘТТІ\",\n Language::Chinese => \"✓ 成功\",\n Language::English => \"✓ SUCCESS\",\n },\n AgentTask::Error(_) => match self.lang {\n Language::Russian => \"✗ ПРЕРВАНО\",\n Language::Kazakh => \"✗ ҮЗІЛДІ\",\n Language::Chinese => \"✗ 中断\",\n Language::English => \"✗ INTERRUPTED\",\n },\n };\n\n let mode_color = match self.mode.as_str() {\n \"yolo\" => Color::LightRed,\n \"auto\" => Color::LightYellow,\n \"ask\" => Color::LightCyan,\n \"plan-only\" => Color::LightBlue,\n _ => Color::DarkGray,\n };\n\n let cost_str = if self.cost > 0.0 {\n format!(\"${:.4}\", self.cost)\n } else {\n \"$0\".into()\n };\n\n let spans = if self.show_diff {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"Ctrl+D\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else if self.show_settings {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"↑/↓\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":навигация \",\n Language::Kazakh => \":бағыттау \",\n Language::Chinese => \":选择 \",\n Language::English => \":navigate \",\n }),\n Span::styled(\"←/→\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":изменить \",\n Language::Kazakh => \":өзгерту \",\n Language::Chinese => \":修改 \",\n Language::English => \":modify \",\n }),\n Span::styled(\"Enter\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":действие \",\n Language::Kazakh => \":әрекет \",\n Language::Chinese => \":执行 \",\n Language::English => \":action \",\n }),\n Span::styled(\"Esc\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":выход \",\n Language::Kazakh => \":қайту \",\n Language::Chinese => \":返回 \",\n Language::English => \":exit_config \",\n }),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | model: \"),\n Span::styled(self.model.as_str(), Style::default().fg(Color::White).add_modifier(Modifier::BOLD)),\n Span::raw(\" | tokens: \"),\n Span::styled(self.tokens.to_string(), Style::default().fg(Color::White)),\n Span::raw(\" | cost: \"),\n Span::styled(cost_str, Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"Ctrl+S\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_settings_toggle\")),\n Span::styled(\"Ctrl+T\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_tree_toggle\")),\n Span::styled(\"Ctrl+D\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\"Ctrl+U\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_undo_toggle\")),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n };\n\n let status_bar = Paragraph::new(Line::from(spans))\n .style(Style::default().bg(Color::Black).fg(Color::Gray));\n\n frame.render_widget(status_bar, area);\n }\n}\n"}, "7ce5f84be0ac60948865331a246e372111e8b821": {"t": 1785757094.1269448, "n": 25304, "h": "c71d56acbc499ae0", "c": "//! DSX TUI — rendering for settings, chat, input prompt, and status bar.\n\nuse crate::types::{Language, AgentTask};\nuse crate::App;\nuse crate::i18n::tr;\nuse ratatui::{\n layout::Rect,\n style::{Color, Modifier, Style},\n text::{Line, Span, Text},\n widgets::{Block, BorderType, Borders, Paragraph, Wrap},\n Frame,\n};\n\nimpl App {\n pub fn draw_settings(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = Vec::new();\n\n lines.push(Line::from(\"\"));\n lines.push(Line::from(vec![\n Span::raw(\" \"),\n Span::styled(tr(self.lang, \"settings_header_banner\"), Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(\"\"));\n lines.push(Line::from(tr(self.lang, \"settings_header_desc\")));\n lines.push(Line::from(\" ─────────────────────────────────────────────────────────────────────────────\"));\n lines.push(Line::from(\"\"));\n\n // Option 1: Permission Mode\n let is_sel0 = self.settings_cursor == 0;\n let prefix0 = if is_sel0 { \" ▸ \" } else { \" \" };\n let style0 = if is_sel0 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix0, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_security\"), style0),\n Span::styled(format!(\"[ {} ]\", self.mode.to_uppercase()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Политика: Спросить / Авто / Yolo / План)\",\n Language::Kazakh => \" (Саясат: Сұрау / Авто / Yolo / Жоспар)\",\n Language::Chinese => \" (授权模式: 提示确认 / 自动运行 / YOLO免确认 / 仅作规划)\",\n Language::English => \" (Approval Policy: Ask / Auto / Yolo / Plan)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 2: Model Choice\n let is_sel1 = self.settings_cursor == 1;\n let prefix1 = if is_sel1 { \" ▸ \" } else { \" \" };\n let style1 = if is_sel1 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix1, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_model\"), style1),\n Span::styled(format!(\"[ {} ]\", self.model.to_uppercase()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (v4-pro Думающая / v4-flash Быстрая)\",\n Language::Kazakh => \" (v4-pro Ойлайтын / v4-flash Жылдам)\",\n Language::Chinese => \" (v4-pro 深度思考 / v4-flash 高速响应)\",\n Language::English => \" (v4-pro Thinking / v4-flash Non-Thinking)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 3: Toggle Tree Explorer\n let is_sel2 = self.settings_cursor == 2;\n let prefix2 = if is_sel2 { \" ▸ \" } else { \" \" };\n let style2 = if is_sel2 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let tree_status = if self.show_file_tree {\n match self.lang {\n Language::Russian => \"ПОКАЗАТЬ\",\n Language::Kazakh => \"КӨРСЕТУ\",\n Language::Chinese => \"显示\",\n Language::English => \"SHOW\",\n }\n } else {\n match self.lang {\n Language::Russian => \"СКРЫТЬ\",\n Language::Kazakh => \"ЖАСЫРУ\",\n Language::Chinese => \"隐藏\",\n Language::English => \"HIDE\",\n }\n };\n lines.push(Line::from(vec![\n Span::styled(prefix2, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_sidebar\"), style2),\n Span::styled(format!(\"[ {} ]\", tree_status), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Показать боковую панель проводника файлов)\",\n Language::Kazakh => \" (Бүйірлік файлдар панелін қосу)\",\n Language::Chinese => \" (开启或关闭工作区文件资源管理器侧边栏)\",\n Language::English => \" (Toggle workspace tree layout)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 4: Language Selection\n let is_sel3 = self.settings_cursor == 3;\n let prefix3 = if is_sel3 { \" ▸ \" } else { \" \" };\n let style3 = if is_sel3 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix3, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_language\"), style3),\n Span::styled(format!(\"[ {} ]\", self.lang.display_name()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Смена языка интерфейса TUI)\",\n Language::Kazakh => \" (TUI интерфейсі тілін ауыстыру)\",\n Language::Chinese => \" (切换系统 TUI 界面显示语言)\",\n Language::English => \" (Switch TUI interface language)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 5: API Base URL\n let is_sel4 = self.settings_cursor == 4;\n let prefix4 = if is_sel4 { \" ▸ \" } else { \" \" };\n let style4 = if is_sel4 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let api_base_label = match self.lang {\n Language::Russian => \"АДРЕС API ENDPOINT BASE: \",\n Language::Kazakh => \"API ENDPOINT НЕГІЗГІ МЕКЕНІ: \",\n Language::Chinese => \"API 接口服务基准地址: \",\n Language::English => \"API ENDPOINT BASE URL: \",\n };\n lines.push(Line::from(vec![\n Span::styled(prefix4, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(api_base_label, style4),\n Span::styled(format!(\"[ {} ]\", self.api_base), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Официальный API / Локальный Ollama / vLLM / OpenAI)\",\n Language::Kazakh => \" (Ресми API / Жергілікті Ollama / vLLM / OpenAI)\",\n Language::Chinese => \" (官方接口 / 本地 Ollama / vLLM / OpenAI 代理)\",\n Language::English => \" (Official API / Local Ollama / vLLM / OpenAI proxy)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 6: API Authorization Key\n let is_sel5 = self.settings_cursor == 5;\n let prefix5 = if is_sel5 { \" ▸ \" } else { \" \" };\n let style5 = if is_sel5 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let key_label = match self.lang {\n Language::Russian => \"КЛЮЧ АВТОРИЗАЦИИ API KEY: \",\n Language::Kazakh => \"API АВТОРИЗАЦИЯ КЛЮЧІ: \",\n Language::Chinese => \"API 授权验证密钥: \",\n Language::English => \"API AUTHORIZATION KEY: \",\n };\n\n let masked_key = if self.api_key.is_empty() {\n match self.lang {\n Language::Russian => \"⚠ КЛЮЧ НЕ НАЙДЕН\".to_string(),\n Language::Kazakh => \"⚠ КЛЮЧ ТАБЫЛМАДЫ\".to_string(),\n Language::Chinese => \"⚠ 未检测到密钥\".to_string(),\n Language::English => \"⚠ KEY NOT DETECTED\".to_string(),\n }\n } else {\n if self.api_key.len() > 10 {\n format!(\"{}...{}\", &self.api_key[..5], &self.api_key[self.api_key.len()-4..])\n } else {\n \"✓ Loaded\".to_string()\n }\n };\n\n lines.push(Line::from(vec![\n Span::styled(prefix5, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(key_label, style5),\n Span::styled(format!(\"[ {} ]\", masked_key), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Считывается из переменной DEEPSEEK_API_KEY)\",\n Language::Kazakh => \" (DEEPSEEK_API_KEY айнымалысынан оқылады)\",\n Language::Chinese => \" (读取自系统环境变量 DEEPSEEK_API_KEY)\",\n Language::English => \" (Loaded from DEEPSEEK_API_KEY env var)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 7: Clear Chat History\n let is_sel6 = self.settings_cursor == 6;\n let prefix6 = if is_sel6 { \" ▸ \" } else { \" \" };\n let style6 = if is_sel6 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix6, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_clear\"), style6),\n Span::styled(tr(self.lang, \"settings_clear_action\"), Style::default().fg(Color::White).bg(Color::LightRed).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Metadata System Information\n lines.push(Line::from(\" ─────────────────────────────────────────────────────────────────────────────\"));\n lines.push(Line::from(tr(self.lang, \"telemetry_title\")));\n lines.push(Line::from(tr(self.lang, \"telemetry_db\")));\n lines.push(Line::from(vec![\n Span::raw(tr(self.lang, \"telemetry_cost\")),\n Span::styled(format!(\"${:.4}\", self.cost), Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(vec![\n Span::raw(tr(self.lang, \"telemetry_tokens\")),\n Span::styled(self.tokens.to_string(), Style::default().fg(Color::White).add_modifier(Modifier::BOLD)),\n ]));\n\n let paragraph = Paragraph::new(Text::from(lines))\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Cyan))\n .title(Span::styled(tr(self.lang, \"settings_title\"), Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD)))\n );\n\n frame.render_widget(paragraph, area);\n }\n\n pub fn draw_chat(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = Vec::new();\n\n for msg in &self.messages {\n let (role_label, role_style) = match msg.role.as_str() {\n \"user\" => (\n match self.lang {\n Language::Russian => \"▸ Пользователь\",\n Language::Kazakh => \"▸ Пайдаланушы\",\n Language::Chinese => \"▸ 用户\",\n Language::English => \"▸ User\",\n },\n Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD),\n ),\n \"assistant\" => (\n match self.lang {\n Language::Russian => \"▸ Ассистент\",\n Language::Kazakh => \"▸ Ассистент\",\n Language::Chinese => \"▸ 助手\",\n Language::English => \"▸ Assistant\",\n },\n Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD),\n ),\n \"system\" => (\n match self.lang {\n Language::Russian => \"⚙ Система\",\n Language::Kazakh => \"⚙ Жүйе\",\n Language::Chinese => \"⚙ 系统\",\n Language::English => \"⚙ System\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n \"error\" => (\n match self.lang {\n Language::Russian => \"⚠ Ошибка\",\n Language::Kazakh => \"⚠ Қате\",\n Language::Chinese => \"⚠ 错误\",\n Language::English => \"⚠ Error\",\n },\n Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD),\n ),\n \"tool\" => (\n match self.lang {\n Language::Russian => \"🛠 Вызов инструмента\",\n Language::Kazakh => \"🛠 Құрал шақыру\",\n Language::Chinese => \"🛠 工具执行\",\n Language::English => \"🛠 Tool Execution\",\n },\n Style::default().fg(Color::LightYellow),\n ),\n _ => (\n \"▸ Message\",\n Style::default().fg(Color::White),\n ),\n };\n\n if !lines.is_empty() {\n lines.push(Line::from(\"\"));\n }\n\n lines.push(Line::from(vec![\n Span::styled(role_label, role_style),\n ]));\n\n for content_line in msg.content.lines() {\n if content_line.is_empty() {\n lines.push(Line::from(\"\"));\n } else {\n let text_style = match msg.role.as_str() {\n \"assistant\" => Style::default().fg(Color::Green),\n \"user\" => Style::default().fg(Color::LightCyan),\n \"tool\" => Style::default().fg(Color::White),\n _ => Style::default().fg(Color::Gray),\n };\n lines.push(Line::from(vec![\n Span::raw(\" \"),\n Span::styled(content_line, text_style),\n ]));\n }\n }\n }\n\n let text = Text::from(lines);\n let paragraph = Paragraph::new(text)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Green))\n .title(Span::styled(tr(self.lang, \"chat_title\"), Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)))\n )\n .wrap(Wrap { trim: false });\n\n frame.render_widget(paragraph, area);\n }\n\n pub fn draw_input(&self, frame: &mut Frame, area: Rect) {\n let input_widget = if let Some(ref approval) = self.pending_approval {\n let format_body = match self.lang {\n Language::Russian => \" ⚠️ ТРЕБУЕТСЯ ПОДТВЕРЖДЕНИЕ БЕЗОПАСНОСТИ:\\n Инструмент: [{}] хочет запуститься в вашей рабочей области.\\n Аргументы: {}\\n ▸ Нажмите [Y] для ОДОБРЕНИЯ (Разрешить) | [N] для ОТКЛОНЕНИЯ (Заблокировать)\",\n Language::Kazakh => \" ⚠️ ҚАУІПСІЗДІК АВТОРИЗАЦИЯСЫ ТАЛАП ЕТІЛЕДІ:\\n Құрал: [{}] жұмыс аймағында іске қосылғысы келеді.\\n Аргументтер: {}\\n ▸ Рұқсат беру үшін [Y] басыңыз | Бас тарту үшін [N] басыңыз\",\n Language::Chinese => \" ⚠️ 需要安全授权验证:\\n 工具: [{}] 申请在您的工作区运行。\\n 参数: {}\\n ▸ 按 [Y] 同意授权 (允许) | 按 [N] 拒绝请求 (拒绝)\",\n Language::English => \" ⚠️ SECURITY AUTHORIZATION REQUIRED:\\n Tool: [{}] wants to run in your workspace.\\n Arguments: {}\\n ▸ Press [Y] to APPROVE (Allow) | [N] to DENY (Reject)\",\n };\n let alert_text = format_body\n .replace(\"{}\", &approval.tool_name)\n .replacen(\"{}\", &approval.arguments, 1);\n\n Paragraph::new(alert_text)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::LightRed))\n .title(Span::styled(tr(self.lang, \"input_auth_title\"), Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)))\n )\n .style(Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD))\n .wrap(Wrap { trim: false })\n } else {\n let (prompt, block_title, style) = match &self.agent_task {\n AgentTask::Idle => (\n format!(\" {}█\", self.input),\n tr(self.lang, \"input_title_idle\"),\n Style::default().fg(Color::LightCyan),\n ),\n AgentTask::Running(desc) => {\n let desc_loc = match self.lang {\n Language::Russian => format!(\"Выполнение: {}...\", desc),\n Language::Kazakh => format!(\"Орындау: {}...\", desc),\n Language::Chinese => format!(\"处理中: {}...\", desc),\n Language::English => format!(\"Processing stream: {}...\", desc),\n };\n (\n format!(\" ⟳ {}\", desc_loc),\n tr(self.lang, \"input_title_running\"),\n Style::default().fg(Color::LightYellow),\n )\n }\n AgentTask::Done(summary) => (\n format!(\" ✓ {} | {}█\", summary, self.input),\n tr(self.lang, \"input_title_done\"),\n Style::default().fg(Color::LightGreen),\n ),\n AgentTask::Error(err) => (\n format!(\" ✗ {} | {}█\", err, self.input),\n tr(self.lang, \"input_title_error\"),\n Style::default().fg(Color::LightRed),\n ),\n };\n\n Paragraph::new(prompt)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(style)\n .title(Span::styled(block_title, style.add_modifier(Modifier::BOLD)))\n )\n .style(Style::default().fg(Color::White))\n .wrap(Wrap { trim: false })\n };\n\n frame.render_widget(input_widget, area);\n }\n\n pub fn draw_status(&self, frame: &mut Frame, area: Rect) {\n let task_indicator = match &self.agent_task {\n AgentTask::Idle => match self.lang {\n Language::Russian => \"⏸ ОЖИДАНИЕ\",\n Language::Kazakh => \"⏸ КҮТУ\",\n Language::Chinese => \"⏸ 空闲\",\n Language::English => \"⏸ IDLE\",\n },\n AgentTask::Running(_) => match self.lang {\n Language::Russian => \"⚡ МЫШЛЕНИЕ\",\n Language::Kazakh => \"⚡ ОЙЛАУ\",\n Language::Chinese => \"⚡ 推理中\",\n Language::English => \"⚡ THINKING\",\n },\n AgentTask::Done(_) => match self.lang {\n Language::Russian => \"✓ УСПЕХ\",\n Language::Kazakh => \"✓ СӘТТІ\",\n Language::Chinese => \"✓ 成功\",\n Language::English => \"✓ SUCCESS\",\n },\n AgentTask::Error(_) => match self.lang {\n Language::Russian => \"✗ ПРЕРВАНО\",\n Language::Kazakh => \"✗ ҮЗІЛДІ\",\n Language::Chinese => \"✗ 中断\",\n Language::English => \"✗ INTERRUPTED\",\n },\n };\n\n let mode_color = match self.mode.as_str() {\n \"yolo\" => Color::LightRed,\n \"auto\" => Color::LightYellow,\n \"ask\" => Color::LightCyan,\n \"plan-only\" => Color::LightBlue,\n _ => Color::DarkGray,\n };\n\n let cost_str = if self.cost > 0.0 {\n format!(\"${:.4}\", self.cost)\n } else {\n \"$0\".into()\n };\n\n let spans = if self.show_diff {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"Ctrl+D\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else if self.show_settings {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"↑/↓\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":навигация \",\n Language::Kazakh => \":бағыттау \",\n Language::Chinese => \":选择 \",\n Language::English => \":navigate \",\n }),\n Span::styled(\"←/→\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":изменить \",\n Language::Kazakh => \":өзгерту \",\n Language::Chinese => \":修改 \",\n Language::English => \":modify \",\n }),\n Span::styled(\"Enter\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":действие \",\n Language::Kazakh => \":әрекет \",\n Language::Chinese => \":执行 \",\n Language::English => \":action \",\n }),\n Span::styled(\"Esc\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":выход \",\n Language::Kazakh => \":қайту \",\n Language::Chinese => \":返回 \",\n Language::English => \":exit_config \",\n }),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | model: \"),\n Span::styled(self.model.as_str(), Style::default().fg(Color::White).add_modifier(Modifier::BOLD)),\n Span::raw(\" | tokens: \"),\n Span::styled(self.tokens.to_string(), Style::default().fg(Color::White)),\n Span::raw(\" | cost: \"),\n Span::styled(cost_str, Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"Ctrl+S\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_settings_toggle\")),\n Span::styled(\"Ctrl+T\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_tree_toggle\")),\n Span::styled(\"Ctrl+D\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\"Ctrl+U\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_undo_toggle\")),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n };\n\n let status_bar = Paragraph::new(Line::from(spans))\n .style(Style::default().bg(Color::Black).fg(Color::Gray));\n\n frame.render_widget(status_bar, area);\n }\n}\n"}, "5989c370341ec6537adfaff6c1e292469fd5410f": {"t": 1785757096.0481412, "n": 25216, "h": "3dff2591dd4c4cd3", "c": "//! DSX TUI — rendering for settings, chat, input prompt, and status bar.\n\nuse crate::types::{Language, AgentTask};\nuse crate::App;\nuse crate::i18n::tr;\nuse ratatui::{\n layout::Rect,\n style::{Color, Modifier, Style},\n text::{Line, Span, Text},\n widgets::{Block, BorderType, Borders, Paragraph, Wrap},\n Frame,\n};\n\nimpl App {\n pub fn draw_settings(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = Vec::new();\n\n lines.push(Line::from(\"\"));\n lines.push(Line::from(vec![\n Span::raw(\" \"),\n Span::styled(tr(self.lang, \"settings_header_banner\"), Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(\"\"));\n lines.push(Line::from(tr(self.lang, \"settings_header_desc\")));\n lines.push(Line::from(\" ─────────────────────────────────────────────────────────────────────────────\"));\n lines.push(Line::from(\"\"));\n\n // Option 1: Permission Mode\n let is_sel0 = self.settings_cursor == 0;\n let prefix0 = if is_sel0 { \" ▸ \" } else { \" \" };\n let style0 = if is_sel0 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix0, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_security\"), style0),\n Span::styled(format!(\"[ {} ]\", self.mode.to_uppercase()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Политика: Спросить / Авто / Yolo / План)\",\n Language::Kazakh => \" (Саясат: Сұрау / Авто / Yolo / Жоспар)\",\n Language::Chinese => \" (授权模式: 提示确认 / 自动运行 / YOLO免确认 / 仅作规划)\",\n Language::English => \" (Approval Policy: Ask / Auto / Yolo / Plan)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 2: Model Choice\n let is_sel1 = self.settings_cursor == 1;\n let prefix1 = if is_sel1 { \" ▸ \" } else { \" \" };\n let style1 = if is_sel1 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix1, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_model\"), style1),\n Span::styled(format!(\"[ {} ]\", self.model.to_uppercase()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (v4-pro Думающая / v4-flash Быстрая)\",\n Language::Kazakh => \" (v4-pro Ойлайтын / v4-flash Жылдам)\",\n Language::Chinese => \" (v4-pro 深度思考 / v4-flash 高速响应)\",\n Language::English => \" (v4-pro Thinking / v4-flash Non-Thinking)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 3: Toggle Tree Explorer\n let is_sel2 = self.settings_cursor == 2;\n let prefix2 = if is_sel2 { \" ▸ \" } else { \" \" };\n let style2 = if is_sel2 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let tree_status = if self.show_file_tree {\n match self.lang {\n Language::Russian => \"ПОКАЗАТЬ\",\n Language::Kazakh => \"КӨРСЕТУ\",\n Language::Chinese => \"显示\",\n Language::English => \"SHOW\",\n }\n } else {\n match self.lang {\n Language::Russian => \"СКРЫТЬ\",\n Language::Kazakh => \"ЖАСЫРУ\",\n Language::Chinese => \"隐藏\",\n Language::English => \"HIDE\",\n }\n };\n lines.push(Line::from(vec![\n Span::styled(prefix2, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_sidebar\"), style2),\n Span::styled(format!(\"[ {} ]\", tree_status), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Показать боковую панель проводника файлов)\",\n Language::Kazakh => \" (Бүйірлік файлдар панелін қосу)\",\n Language::Chinese => \" (开启或关闭工作区文件资源管理器侧边栏)\",\n Language::English => \" (Toggle workspace tree layout)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 4: Language Selection\n let is_sel3 = self.settings_cursor == 3;\n let prefix3 = if is_sel3 { \" ▸ \" } else { \" \" };\n let style3 = if is_sel3 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix3, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_language\"), style3),\n Span::styled(format!(\"[ {} ]\", self.lang.display_name()), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Смена языка интерфейса TUI)\",\n Language::Kazakh => \" (TUI интерфейсі тілін ауыстыру)\",\n Language::Chinese => \" (切换系统 TUI 界面显示语言)\",\n Language::English => \" (Switch TUI interface language)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 5: API Base URL\n let is_sel4 = self.settings_cursor == 4;\n let prefix4 = if is_sel4 { \" ▸ \" } else { \" \" };\n let style4 = if is_sel4 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let api_base_label = match self.lang {\n Language::Russian => \"АДРЕС API ENDPOINT BASE: \",\n Language::Kazakh => \"API ENDPOINT НЕГІЗГІ МЕКЕНІ: \",\n Language::Chinese => \"API 接口服务基准地址: \",\n Language::English => \"API ENDPOINT BASE URL: \",\n };\n lines.push(Line::from(vec![\n Span::styled(prefix4, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(api_base_label, style4),\n Span::styled(format!(\"[ {} ]\", self.api_base), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Официальный API / Локальный Ollama / vLLM / OpenAI)\",\n Language::Kazakh => \" (Ресми API / Жергілікті Ollama / vLLM / OpenAI)\",\n Language::Chinese => \" (官方接口 / 本地 Ollama / vLLM / OpenAI 代理)\",\n Language::English => \" (Official API / Local Ollama / vLLM / OpenAI proxy)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 6: API Authorization Key\n let is_sel5 = self.settings_cursor == 5;\n let prefix5 = if is_sel5 { \" ▸ \" } else { \" \" };\n let style5 = if is_sel5 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n let key_label = match self.lang {\n Language::Russian => \"КЛЮЧ АВТОРИЗАЦИИ API KEY: \",\n Language::Kazakh => \"API АВТОРИЗАЦИЯ КЛЮЧІ: \",\n Language::Chinese => \"API 授权验证密钥: \",\n Language::English => \"API AUTHORIZATION KEY: \",\n };\n\n let masked_key = if self.api_key.is_empty() {\n match self.lang {\n Language::Russian => \"⚠ КЛЮЧ НЕ НАЙДЕН\".to_string(),\n Language::Kazakh => \"⚠ КЛЮЧ ТАБЫЛМАДЫ\".to_string(),\n Language::Chinese => \"⚠ 未检测到密钥\".to_string(),\n Language::English => \"⚠ KEY NOT DETECTED\".to_string(),\n }\n } else {\n if self.api_key.len() > 10 {\n format!(\"{}...{}\", &self.api_key[..5], &self.api_key[self.api_key.len()-4..])\n } else {\n \"✓ Loaded\".to_string()\n }\n };\n\n lines.push(Line::from(vec![\n Span::styled(prefix5, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(key_label, style5),\n Span::styled(format!(\"[ {} ]\", masked_key), Style::default().fg(Color::Black).bg(Color::White).add_modifier(Modifier::BOLD)),\n Span::styled(match self.lang {\n Language::Russian => \" (Считывается из переменной DEEPSEEK_API_KEY)\",\n Language::Kazakh => \" (DEEPSEEK_API_KEY айнымалысынан оқылады)\",\n Language::Chinese => \" (读取自系统环境变量 DEEPSEEK_API_KEY)\",\n Language::English => \" (Loaded from DEEPSEEK_API_KEY env var)\",\n }, Style::default().fg(Color::DarkGray)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Option 7: Clear Chat History\n let is_sel6 = self.settings_cursor == 6;\n let prefix6 = if is_sel6 { \" ▸ \" } else { \" \" };\n let style6 = if is_sel6 { Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::Gray) };\n lines.push(Line::from(vec![\n Span::styled(prefix6, Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),\n Span::styled(tr(self.lang, \"settings_opt_clear\"), style6),\n Span::styled(tr(self.lang, \"settings_clear_action\"), Style::default().fg(Color::White).bg(Color::LightRed).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(\"\"));\n\n // Metadata System Information\n lines.push(Line::from(\" ─────────────────────────────────────────────────────────────────────────────\"));\n lines.push(Line::from(tr(self.lang, \"telemetry_title\")));\n lines.push(Line::from(tr(self.lang, \"telemetry_db\")));\n lines.push(Line::from(vec![\n Span::raw(tr(self.lang, \"telemetry_cost\")),\n Span::styled(format!(\"${:.4}\", self.cost), Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n ]));\n lines.push(Line::from(vec![\n Span::raw(tr(self.lang, \"telemetry_tokens\")),\n Span::styled(self.tokens.to_string(), Style::default().fg(Color::White).add_modifier(Modifier::BOLD)),\n ]));\n\n let paragraph = Paragraph::new(Text::from(lines))\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Cyan))\n .title(Span::styled(tr(self.lang, \"settings_title\"), Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD)))\n );\n\n frame.render_widget(paragraph, area);\n }\n\n pub fn draw_chat(&self, frame: &mut Frame, area: Rect) {\n let mut lines: Vec<Line> = Vec::new();\n\n for msg in &self.messages {\n let (role_label, role_style) = match msg.role.as_str() {\n \"user\" => (\n match self.lang {\n Language::Russian => \"▸ Пользователь\",\n Language::Kazakh => \"▸ Пайдаланушы\",\n Language::Chinese => \"▸ 用户\",\n Language::English => \"▸ User\",\n },\n Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD),\n ),\n \"assistant\" => (\n match self.lang {\n Language::Russian => \"▸ Ассистент\",\n Language::Kazakh => \"▸ Ассистент\",\n Language::Chinese => \"▸ 助手\",\n Language::English => \"▸ Assistant\",\n },\n Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD),\n ),\n \"system\" => (\n match self.lang {\n Language::Russian => \"⚙ Система\",\n Language::Kazakh => \"⚙ Жүйе\",\n Language::Chinese => \"⚙ 系统\",\n Language::English => \"⚙ System\",\n },\n Style::default().fg(Color::DarkGray),\n ),\n \"error\" => (\n match self.lang {\n Language::Russian => \"⚠ Ошибка\",\n Language::Kazakh => \"⚠ Қате\",\n Language::Chinese => \"⚠ 错误\",\n Language::English => \"⚠ Error\",\n },\n Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD),\n ),\n \"tool\" => (\n match self.lang {\n Language::Russian => \"🛠 Вызов инструмента\",\n Language::Kazakh => \"🛠 Құрал шақыру\",\n Language::Chinese => \"🛠 工具执行\",\n Language::English => \"🛠 Tool Execution\",\n },\n Style::default().fg(Color::LightYellow),\n ),\n _ => (\n \"▸ Message\",\n Style::default().fg(Color::White),\n ),\n };\n\n if !lines.is_empty() {\n lines.push(Line::from(\"\"));\n }\n\n lines.push(Line::from(vec![\n Span::styled(role_label, role_style),\n ]));\n\n for content_line in msg.content.lines() {\n if content_line.is_empty() {\n lines.push(Line::from(\"\"));\n } else {\n let text_style = match msg.role.as_str() {\n \"assistant\" => Style::default().fg(Color::Green),\n \"user\" => Style::default().fg(Color::LightCyan),\n \"tool\" => Style::default().fg(Color::White),\n _ => Style::default().fg(Color::Gray),\n };\n lines.push(Line::from(vec![\n Span::raw(\" \"),\n Span::styled(content_line, text_style),\n ]));\n }\n }\n }\n\n let text = Text::from(lines);\n let paragraph = Paragraph::new(text)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::Green))\n .title(Span::styled(tr(self.lang, \"chat_title\"), Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)))\n )\n .wrap(Wrap { trim: false });\n\n frame.render_widget(paragraph, area);\n }\n\n pub fn draw_input(&self, frame: &mut Frame, area: Rect) {\n let input_widget = if let Some(ref approval) = self.pending_approval {\n let format_body = match self.lang {\n Language::Russian => \" ⚠️ ТРЕБУЕТСЯ ПОДТВЕРЖДЕНИЕ БЕЗОПАСНОСТИ:\\n Инструмент: [{}] хочет запуститься в вашей рабочей области.\\n Аргументы: {}\\n ▸ Нажмите [Y] для ОДОБРЕНИЯ (Разрешить) | [N] для ОТКЛОНЕНИЯ (Заблокировать)\",\n Language::Kazakh => \" ⚠️ ҚАУІПСІЗДІК АВТОРИЗАЦИЯСЫ ТАЛАП ЕТІЛЕДІ:\\n Құрал: [{}] жұмыс аймағында іске қосылғысы келеді.\\n Аргументтер: {}\\n ▸ Рұқсат беру үшін [Y] басыңыз | Бас тарту үшін [N] басыңыз\",\n Language::Chinese => \" ⚠️ 需要安全授权验证:\\n 工具: [{}] 申请在您的工作区运行。\\n 参数: {}\\n ▸ 按 [Y] 同意授权 (允许) | 按 [N] 拒绝请求 (拒绝)\",\n Language::English => \" ⚠️ SECURITY AUTHORIZATION REQUIRED:\\n Tool: [{}] wants to run in your workspace.\\n Arguments: {}\\n ▸ Press [Y] to APPROVE (Allow) | [N] to DENY (Reject)\",\n };\n let alert_text = format_body\n .replace(\"{}\", &approval.tool_name)\n .replacen(\"{}\", &approval.arguments, 1);\n\n Paragraph::new(alert_text)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(Style::default().fg(Color::LightRed))\n .title(Span::styled(tr(self.lang, \"input_auth_title\"), Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)))\n )\n .style(Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD))\n } else {\n let (prompt, block_title, style) = match &self.agent_task {\n AgentTask::Idle => (\n format!(\" {}█\", self.input),\n tr(self.lang, \"input_title_idle\"),\n Style::default().fg(Color::LightCyan),\n ),\n AgentTask::Running(desc) => {\n let desc_loc = match self.lang {\n Language::Russian => format!(\"Выполнение: {}...\", desc),\n Language::Kazakh => format!(\"Орындау: {}...\", desc),\n Language::Chinese => format!(\"处理中: {}...\", desc),\n Language::English => format!(\"Processing stream: {}...\", desc),\n };\n (\n format!(\" ⟳ {}\", desc_loc),\n tr(self.lang, \"input_title_running\"),\n Style::default().fg(Color::LightYellow),\n )\n }\n AgentTask::Done(summary) => (\n format!(\" ✓ {} | {}█\", summary, self.input),\n tr(self.lang, \"input_title_done\"),\n Style::default().fg(Color::LightGreen),\n ),\n AgentTask::Error(err) => (\n format!(\" ✗ {} | {}█\", err, self.input),\n tr(self.lang, \"input_title_error\"),\n Style::default().fg(Color::LightRed),\n ),\n };\n\n Paragraph::new(prompt)\n .block(\n Block::default()\n .borders(Borders::ALL)\n .border_type(BorderType::Rounded)\n .border_style(style)\n .title(Span::styled(block_title, style.add_modifier(Modifier::BOLD)))\n )\n .style(Style::default().fg(Color::White))\n };\n\n frame.render_widget(input_widget, area);\n }\n\n pub fn draw_status(&self, frame: &mut Frame, area: Rect) {\n let task_indicator = match &self.agent_task {\n AgentTask::Idle => match self.lang {\n Language::Russian => \"⏸ ОЖИДАНИЕ\",\n Language::Kazakh => \"⏸ КҮТУ\",\n Language::Chinese => \"⏸ 空闲\",\n Language::English => \"⏸ IDLE\",\n },\n AgentTask::Running(_) => match self.lang {\n Language::Russian => \"⚡ МЫШЛЕНИЕ\",\n Language::Kazakh => \"⚡ ОЙЛАУ\",\n Language::Chinese => \"⚡ 推理中\",\n Language::English => \"⚡ THINKING\",\n },\n AgentTask::Done(_) => match self.lang {\n Language::Russian => \"✓ УСПЕХ\",\n Language::Kazakh => \"✓ СӘТТІ\",\n Language::Chinese => \"✓ 成功\",\n Language::English => \"✓ SUCCESS\",\n },\n AgentTask::Error(_) => match self.lang {\n Language::Russian => \"✗ ПРЕРВАНО\",\n Language::Kazakh => \"✗ ҮЗІЛДІ\",\n Language::Chinese => \"✗ 中断\",\n Language::English => \"✗ INTERRUPTED\",\n },\n };\n\n let mode_color = match self.mode.as_str() {\n \"yolo\" => Color::LightRed,\n \"auto\" => Color::LightYellow,\n \"ask\" => Color::LightCyan,\n \"plan-only\" => Color::LightBlue,\n _ => Color::DarkGray,\n };\n\n let cost_str = if self.cost > 0.0 {\n format!(\"${:.4}\", self.cost)\n } else {\n \"$0\".into()\n };\n\n let spans = if self.show_diff {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"Ctrl+D\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else if self.show_settings {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"↑/↓\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":навигация \",\n Language::Kazakh => \":бағыттау \",\n Language::Chinese => \":选择 \",\n Language::English => \":navigate \",\n }),\n Span::styled(\"←/→\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":изменить \",\n Language::Kazakh => \":өзгерту \",\n Language::Chinese => \":修改 \",\n Language::English => \":modify \",\n }),\n Span::styled(\"Enter\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":действие \",\n Language::Kazakh => \":әрекет \",\n Language::Chinese => \":执行 \",\n Language::English => \":action \",\n }),\n Span::styled(\"Esc\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(match self.lang {\n Language::Russian => \":выход \",\n Language::Kazakh => \":қайту \",\n Language::Chinese => \":返回 \",\n Language::English => \":exit_config \",\n }),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n } else {\n vec![\n Span::styled(format!(\" {} \", task_indicator), Style::default().fg(Color::Black).bg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" \"),\n Span::styled(format!(\" [{}] \", self.mode.to_uppercase()), Style::default().fg(mode_color).add_modifier(Modifier::BOLD)),\n Span::raw(\" | model: \"),\n Span::styled(self.model.as_str(), Style::default().fg(Color::White).add_modifier(Modifier::BOLD)),\n Span::raw(\" | tokens: \"),\n Span::styled(self.tokens.to_string(), Style::default().fg(Color::White)),\n Span::raw(\" | cost: \"),\n Span::styled(cost_str, Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD)),\n Span::raw(\" | \"),\n Span::styled(\"Ctrl+S\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_settings_toggle\")),\n Span::styled(\"Ctrl+T\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_tree_toggle\")),\n Span::styled(\"Ctrl+D\", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_diff_toggle\")),\n Span::styled(\"Ctrl+U\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_undo_toggle\")),\n Span::styled(\"Ctrl+C\", Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD)),\n Span::raw(tr(self.lang, \"status_quit\")),\n ]\n };\n\n let status_bar = Paragraph::new(Line::from(spans))\n .style(Style::default().bg(Color::Black).fg(Color::Gray));\n\n frame.render_widget(status_bar, area);\n }\n}\n"}}, "src/handlers_agent.rs": {"02ab671513ed25839db232846a1913627e488a1b": {"t": 1785745870.5893085, "n": 4238, "h": "7fd975fb45c3f7fd", "c": "//! CLI handlers that run the agent or eval suite.\n\nuse std::path::PathBuf;\n\npub async fn run_plan(\n project_root: PathBuf,\n api_key: String,\n api_base: String,\n task: &str,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n) -> anyhow::Result<()> {\n let scope =\n crate::agent_preflight::prepare_agent_start_scope(&project_root, task, allow_wide_scope)?;\n crate::cli_context_budget::preflight_cli_context_budget(&project_root, task).await?;\n let config = agent_config(project_root, api_key, api_base, 3, mode);\n println!(\"Planning agent executing...\");\n print_cli_scope(&scope);\n let outcome = dsx_agent::run(task, &config).await?;\n println!();\n println!(\n \"── Plan summary ({iterations} iterations) ──\",\n iterations = outcome.iterations\n );\n if let Some(ref ans) = outcome.answer {\n println!(\"{ans}\");\n }\n Ok(())\n}\n\npub async fn run_edit(\n project_root: PathBuf,\n api_key: String,\n api_base: String,\n task: &str,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n) -> anyhow::Result<()> {\n let scope =\n crate::agent_preflight::prepare_agent_start_scope(&project_root, task, allow_wide_scope)?;\n crate::cli_context_budget::preflight_cli_context_budget(&project_root, task).await?;\n let config = agent_config(project_root, api_key, api_base, 15, mode);\n println!(\"Running agent...\");\n print_cli_scope(&scope);\n let outcome = dsx_agent::run(task, &config).await?;\n println!();\n println!(\n \"── Answer ({iterations} iterations) ──\",\n iterations = outcome.iterations\n );\n if let Some(ref ans) = outcome.answer {\n println!(\"{ans}\");\n }\n Ok(())\n}\n\npub async fn run_eval(\n project_root: PathBuf,\n api_key: Option<String>,\n api_base: String,\n tasks_file: PathBuf,\n mode: dsx_core::types::PermissionMode,\n no_agent: bool,\n) -> anyhow::Result<()> {\n let content = std::fs::read_to_string(&tasks_file)?;\n let tasks = parse_eval_tasks(&content)?;\n let run_agent = !no_agent;\n let key = api_key.unwrap_or_default();\n if run_agent && key.is_empty() {\n anyhow::bail!(\n \"Set DEEPSEEK_API_KEY/use --api-key or pass --no-agent for verification-only eval\"\n );\n }\n\n let runner = dsx_eval::EvalRunner::with_config(dsx_eval::EvalConfig {\n project_root,\n api_key: key,\n api_base,\n mode,\n max_iterations: 15,\n run_agent,\n command_timeout_secs: 300,\n });\n let summary = runner.run_suite(&tasks).await?;\n\n println!(\n \"Eval summary: {}/{} passed, {} failed, cost ${:.4}\",\n summary.passed, summary.total, summary.failed, summary.total_cost_usd\n );\n for result in &summary.results {\n let status = if result.success { \"PASS\" } else { \"FAIL\" };\n println!(\n \" {status} {} iter={} tests={:?} cost=${:.4}\",\n result.task_id, result.iterations, result.tests_passed, result.cost_usd\n );\n for missing in &result.missing_expected {\n println!(\" - {missing}\");\n }\n }\n\n if summary.failed > 0 {\n anyhow::bail!(\"eval failed: {} task(s) failed\", summary.failed);\n }\n Ok(())\n}\n\nfn agent_config(\n project_root: PathBuf,\n api_key: String,\n api_base: String,\n max_iterations: usize,\n mode: dsx_core::types::PermissionMode,\n) -> dsx_agent::AgentConfig {\n dsx_agent::AgentConfig {\n project_root,\n api_key,\n api_base,\n max_iterations,\n mode,\n approval_tx: None,\n }\n}\n\nfn print_cli_scope(scope: &crate::task_scope::ResolvedTaskScope) {\n if scope.narrowed {\n println!(\"Scope: {}\", scope.active_label);\n }\n}\n\nfn parse_eval_tasks(content: &str) -> anyhow::Result<Vec<dsx_eval::EvalTask>> {\n match serde_json::from_str::<Vec<dsx_eval::EvalTask>>(content) {\n Ok(tasks) => Ok(tasks),\n Err(vec_err) => match serde_json::from_str::<dsx_eval::EvalTask>(content) {\n Ok(task) => Ok(vec![task]),\n Err(task_err) => Err(anyhow::anyhow!(\n \"failed to parse eval tasks as array ({vec_err}) or object ({task_err})\"\n )),\n },\n }\n}\n"}, "8d4f3df6d4860335bff9df8c26431bc78a45ce1b": {"t": 1785757086.3400795, "n": 4238, "h": "7fd975fb45c3f7fd", "c": "//! CLI handlers that run the agent or eval suite.\n\nuse std::path::PathBuf;\n\npub async fn run_plan(\n project_root: PathBuf,\n api_key: String,\n api_base: String,\n task: &str,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n) -> anyhow::Result<()> {\n let scope =\n crate::agent_preflight::prepare_agent_start_scope(&project_root, task, allow_wide_scope)?;\n crate::cli_context_budget::preflight_cli_context_budget(&project_root, task).await?;\n let config = agent_config(project_root, api_key, api_base, 3, mode);\n println!(\"Planning agent executing...\");\n print_cli_scope(&scope);\n let outcome = dsx_agent::run(task, &config).await?;\n println!();\n println!(\n \"── Plan summary ({iterations} iterations) ──\",\n iterations = outcome.iterations\n );\n if let Some(ref ans) = outcome.answer {\n println!(\"{ans}\");\n }\n Ok(())\n}\n\npub async fn run_edit(\n project_root: PathBuf,\n api_key: String,\n api_base: String,\n task: &str,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n) -> anyhow::Result<()> {\n let scope =\n crate::agent_preflight::prepare_agent_start_scope(&project_root, task, allow_wide_scope)?;\n crate::cli_context_budget::preflight_cli_context_budget(&project_root, task).await?;\n let config = agent_config(project_root, api_key, api_base, 15, mode);\n println!(\"Running agent...\");\n print_cli_scope(&scope);\n let outcome = dsx_agent::run(task, &config).await?;\n println!();\n println!(\n \"── Answer ({iterations} iterations) ──\",\n iterations = outcome.iterations\n );\n if let Some(ref ans) = outcome.answer {\n println!(\"{ans}\");\n }\n Ok(())\n}\n\npub async fn run_eval(\n project_root: PathBuf,\n api_key: Option<String>,\n api_base: String,\n tasks_file: PathBuf,\n mode: dsx_core::types::PermissionMode,\n no_agent: bool,\n) -> anyhow::Result<()> {\n let content = std::fs::read_to_string(&tasks_file)?;\n let tasks = parse_eval_tasks(&content)?;\n let run_agent = !no_agent;\n let key = api_key.unwrap_or_default();\n if run_agent && key.is_empty() {\n anyhow::bail!(\n \"Set DEEPSEEK_API_KEY/use --api-key or pass --no-agent for verification-only eval\"\n );\n }\n\n let runner = dsx_eval::EvalRunner::with_config(dsx_eval::EvalConfig {\n project_root,\n api_key: key,\n api_base,\n mode,\n max_iterations: 15,\n run_agent,\n command_timeout_secs: 300,\n });\n let summary = runner.run_suite(&tasks).await?;\n\n println!(\n \"Eval summary: {}/{} passed, {} failed, cost ${:.4}\",\n summary.passed, summary.total, summary.failed, summary.total_cost_usd\n );\n for result in &summary.results {\n let status = if result.success { \"PASS\" } else { \"FAIL\" };\n println!(\n \" {status} {} iter={} tests={:?} cost=${:.4}\",\n result.task_id, result.iterations, result.tests_passed, result.cost_usd\n );\n for missing in &result.missing_expected {\n println!(\" - {missing}\");\n }\n }\n\n if summary.failed > 0 {\n anyhow::bail!(\"eval failed: {} task(s) failed\", summary.failed);\n }\n Ok(())\n}\n\nfn agent_config(\n project_root: PathBuf,\n api_key: String,\n api_base: String,\n max_iterations: usize,\n mode: dsx_core::types::PermissionMode,\n) -> dsx_agent::AgentConfig {\n dsx_agent::AgentConfig {\n project_root,\n api_key,\n api_base,\n max_iterations,\n mode,\n approval_tx: None,\n }\n}\n\nfn print_cli_scope(scope: &crate::task_scope::ResolvedTaskScope) {\n if scope.narrowed {\n println!(\"Scope: {}\", scope.active_label);\n }\n}\n\nfn parse_eval_tasks(content: &str) -> anyhow::Result<Vec<dsx_eval::EvalTask>> {\n match serde_json::from_str::<Vec<dsx_eval::EvalTask>>(content) {\n Ok(tasks) => Ok(tasks),\n Err(vec_err) => match serde_json::from_str::<dsx_eval::EvalTask>(content) {\n Ok(task) => Ok(vec![task]),\n Err(task_err) => Err(anyhow::anyhow!(\n \"failed to parse eval tasks as array ({vec_err}) or object ({task_err})\"\n )),\n },\n }\n}\n"}}, "src/main_runtime.rs": {"02ab671513ed25839db232846a1913627e488a1b": {"t": 1785745878.156683, "n": 6624, "h": "d018448ca668f473", "c": "//! Main binary runtime helpers kept out of the entrypoint.\n\nuse crate::cli::{CliArgs, IndexAction, McpAction, WorkspaceAction};\nuse crate::handlers::{\n list_sessions, run_index_build, run_index_search, run_mcp_call, run_mcp_list,\n};\nuse crate::workspace_notes::list_workspace_notes;\nuse crate::workspace_runs::list_agent_runs;\nuse crate::workspace_stale_runs::close_stale_runs;\n\npub fn load_config_or_default(project_root: &std::path::Path) -> dsx_config::AppConfig {\n match dsx_config::load_for_project(project_root) {\n Ok(config) => config,\n Err(e) => {\n eprintln!(\"Warning: failed to load config: {e}\");\n dsx_config::AppConfig::default()\n }\n }\n}\n\npub fn initial_mode(\n cli: &CliArgs,\n app_config: &dsx_config::AppConfig,\n) -> dsx_core::types::PermissionMode {\n let mode_name = cli\n .mode\n .as_deref()\n .unwrap_or(app_config.app.default_mode.as_str());\n dsx_core::types::PermissionMode::parse(mode_name)\n .unwrap_or(dsx_core::types::PermissionMode::Ask)\n}\n\npub fn api_key(\n cli: &CliArgs,\n app_config: &dsx_config::AppConfig,\n project_root: &std::path::Path,\n) -> Option<String> {\n cli.api_key\n .clone()\n .or_else(|| std::env::var(&app_config.provider.api_key_env).ok())\n .or_else(|| std::env::var(\"DEEPSEEK_API_KEY\").ok())\n .or_else(|| api_key_file(project_root))\n}\n\npub fn require_api_key(api_key: Option<String>) -> Option<String> {\n if api_key.is_none() {\n println!(\"(Set DEEPSEEK_API_KEY or use --api-key)\");\n }\n api_key\n}\n\npub(crate) fn api_key_file(project_root: &std::path::Path) -> Option<String> {\n for dir in project_root.ancestors() {\n let path = dir.join(\".deepseek\");\n if path.is_file()\n && let Ok(content) = std::fs::read_to_string(path)\n && let Some(key) = content.lines().map(str::trim).find(|line| !line.is_empty())\n {\n return Some(key.to_string());\n }\n }\n None\n}\n\npub async fn load_or_create_session(\n project_root: &std::path::Path,\n mode: dsx_core::types::PermissionMode,\n) -> (Option<sqlx::SqlitePool>, Option<String>, std::path::PathBuf) {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n match dsx_memory::open(&db_path).await {\n Ok(pool) => {\n let sm = dsx_session::SessionManager::new(pool.clone());\n // Try to load the latest session automatically\n if let Ok(sessions) = sm.list(1).await {\n if !sessions.is_empty() {\n let latest = &sessions[0];\n let loaded_path = std::path::PathBuf::from(&latest.project_root);\n return (Some(pool), Some(latest.id.clone()), loaded_path);\n }\n }\n // Fallback to creating a new one\n match sm\n .create(&project_root.display().to_string(), mode.as_str())\n .await\n {\n Ok(session) => (Some(pool), Some(session.id), project_root.to_path_buf()),\n Err(_) => (Some(pool), None, project_root.to_path_buf()),\n }\n }\n Err(_) => (None, None, project_root.to_path_buf()),\n }\n}\n\npub async fn run_index_action(\n project_root: &std::path::Path,\n action: IndexAction,\n) -> anyhow::Result<()> {\n match action {\n IndexAction::Build => run_index_build(project_root).await,\n IndexAction::Search { query, limit } => run_index_search(project_root, &query, limit).await,\n }\n}\n\npub async fn run_mcp_action(action: McpAction) -> anyhow::Result<()> {\n match action {\n McpAction::List { command, args } => run_mcp_list(&command, &args).await,\n McpAction::Call {\n tool,\n arguments_json,\n command,\n args,\n } => run_mcp_call(&command, &args, &tool, &arguments_json).await,\n }\n}\n\npub async fn run_workspace_action(\n project_root: std::path::PathBuf,\n api_key: Option<String>,\n api_base: String,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n action: Option<WorkspaceAction>,\n) -> anyhow::Result<()> {\n match action {\n None | Some(WorkspaceAction::List) => list_sessions(&project_root).await,\n Some(WorkspaceAction::Runs { limit, all, json }) => {\n list_agent_runs(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Audit { limit, all, json }) => {\n crate::workspace_audit::run_workspace_audit(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Notes { limit, all, json }) => {\n list_workspace_notes(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Mission {\n limit,\n all,\n json,\n check,\n }) => {\n crate::workspace_mission::run_workspace_mission(&project_root, limit, all, json, check)\n .await?\n }\n Some(WorkspaceAction::CloseStaleRuns {\n older_than_minutes,\n dry_run,\n }) => close_stale_runs(&project_root, older_than_minutes, dry_run).await,\n Some(WorkspaceAction::Resume { id }) => {\n let Some(key) = require_api_key(api_key) else {\n return Ok(());\n };\n resume_session(project_root, key, api_base, mode, allow_wide_scope, id).await?;\n }\n }\n Ok(())\n}\n\nasync fn resume_session(\n project_root: std::path::PathBuf,\n api_key: String,\n api_base: String,\n fallback_mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n id: String,\n) -> anyhow::Result<()> {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n let pool = match dsx_memory::open(&db_path).await {\n Ok(pool) => pool,\n Err(e) => {\n println!(\"Error: Failed to open sessions database: {e}\");\n return Ok(());\n }\n };\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm.get(&id).await {\n Ok(Some(session)) => {\n let mode =\n dsx_core::types::PermissionMode::parse(&session.mode).unwrap_or(fallback_mode);\n println!(\"Resuming session {}...\", session.id);\n crate::tui_runner::run_tui(\n project_root,\n api_key,\n api_base,\n mode,\n allow_wide_scope,\n Some(session.id),\n Some(pool),\n )\n .await?;\n }\n _ => println!(\"Error: Session with ID '{}' not found.\", id),\n }\n Ok(())\n}\n"}, "1acaf5f3cf642df8e0563ebcec4e5ea34217ad12": {"t": 1785757086.7147167, "n": 6624, "h": "d018448ca668f473", "c": "//! Main binary runtime helpers kept out of the entrypoint.\n\nuse crate::cli::{CliArgs, IndexAction, McpAction, WorkspaceAction};\nuse crate::handlers::{\n list_sessions, run_index_build, run_index_search, run_mcp_call, run_mcp_list,\n};\nuse crate::workspace_notes::list_workspace_notes;\nuse crate::workspace_runs::list_agent_runs;\nuse crate::workspace_stale_runs::close_stale_runs;\n\npub fn load_config_or_default(project_root: &std::path::Path) -> dsx_config::AppConfig {\n match dsx_config::load_for_project(project_root) {\n Ok(config) => config,\n Err(e) => {\n eprintln!(\"Warning: failed to load config: {e}\");\n dsx_config::AppConfig::default()\n }\n }\n}\n\npub fn initial_mode(\n cli: &CliArgs,\n app_config: &dsx_config::AppConfig,\n) -> dsx_core::types::PermissionMode {\n let mode_name = cli\n .mode\n .as_deref()\n .unwrap_or(app_config.app.default_mode.as_str());\n dsx_core::types::PermissionMode::parse(mode_name)\n .unwrap_or(dsx_core::types::PermissionMode::Ask)\n}\n\npub fn api_key(\n cli: &CliArgs,\n app_config: &dsx_config::AppConfig,\n project_root: &std::path::Path,\n) -> Option<String> {\n cli.api_key\n .clone()\n .or_else(|| std::env::var(&app_config.provider.api_key_env).ok())\n .or_else(|| std::env::var(\"DEEPSEEK_API_KEY\").ok())\n .or_else(|| api_key_file(project_root))\n}\n\npub fn require_api_key(api_key: Option<String>) -> Option<String> {\n if api_key.is_none() {\n println!(\"(Set DEEPSEEK_API_KEY or use --api-key)\");\n }\n api_key\n}\n\npub(crate) fn api_key_file(project_root: &std::path::Path) -> Option<String> {\n for dir in project_root.ancestors() {\n let path = dir.join(\".deepseek\");\n if path.is_file()\n && let Ok(content) = std::fs::read_to_string(path)\n && let Some(key) = content.lines().map(str::trim).find(|line| !line.is_empty())\n {\n return Some(key.to_string());\n }\n }\n None\n}\n\npub async fn load_or_create_session(\n project_root: &std::path::Path,\n mode: dsx_core::types::PermissionMode,\n) -> (Option<sqlx::SqlitePool>, Option<String>, std::path::PathBuf) {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n match dsx_memory::open(&db_path).await {\n Ok(pool) => {\n let sm = dsx_session::SessionManager::new(pool.clone());\n // Try to load the latest session automatically\n if let Ok(sessions) = sm.list(1).await {\n if !sessions.is_empty() {\n let latest = &sessions[0];\n let loaded_path = std::path::PathBuf::from(&latest.project_root);\n return (Some(pool), Some(latest.id.clone()), loaded_path);\n }\n }\n // Fallback to creating a new one\n match sm\n .create(&project_root.display().to_string(), mode.as_str())\n .await\n {\n Ok(session) => (Some(pool), Some(session.id), project_root.to_path_buf()),\n Err(_) => (Some(pool), None, project_root.to_path_buf()),\n }\n }\n Err(_) => (None, None, project_root.to_path_buf()),\n }\n}\n\npub async fn run_index_action(\n project_root: &std::path::Path,\n action: IndexAction,\n) -> anyhow::Result<()> {\n match action {\n IndexAction::Build => run_index_build(project_root).await,\n IndexAction::Search { query, limit } => run_index_search(project_root, &query, limit).await,\n }\n}\n\npub async fn run_mcp_action(action: McpAction) -> anyhow::Result<()> {\n match action {\n McpAction::List { command, args } => run_mcp_list(&command, &args).await,\n McpAction::Call {\n tool,\n arguments_json,\n command,\n args,\n } => run_mcp_call(&command, &args, &tool, &arguments_json).await,\n }\n}\n\npub async fn run_workspace_action(\n project_root: std::path::PathBuf,\n api_key: Option<String>,\n api_base: String,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n action: Option<WorkspaceAction>,\n) -> anyhow::Result<()> {\n match action {\n None | Some(WorkspaceAction::List) => list_sessions(&project_root).await,\n Some(WorkspaceAction::Runs { limit, all, json }) => {\n list_agent_runs(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Audit { limit, all, json }) => {\n crate::workspace_audit::run_workspace_audit(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Notes { limit, all, json }) => {\n list_workspace_notes(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Mission {\n limit,\n all,\n json,\n check,\n }) => {\n crate::workspace_mission::run_workspace_mission(&project_root, limit, all, json, check)\n .await?\n }\n Some(WorkspaceAction::CloseStaleRuns {\n older_than_minutes,\n dry_run,\n }) => close_stale_runs(&project_root, older_than_minutes, dry_run).await,\n Some(WorkspaceAction::Resume { id }) => {\n let Some(key) = require_api_key(api_key) else {\n return Ok(());\n };\n resume_session(project_root, key, api_base, mode, allow_wide_scope, id).await?;\n }\n }\n Ok(())\n}\n\nasync fn resume_session(\n project_root: std::path::PathBuf,\n api_key: String,\n api_base: String,\n fallback_mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n id: String,\n) -> anyhow::Result<()> {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n let pool = match dsx_memory::open(&db_path).await {\n Ok(pool) => pool,\n Err(e) => {\n println!(\"Error: Failed to open sessions database: {e}\");\n return Ok(());\n }\n };\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm.get(&id).await {\n Ok(Some(session)) => {\n let mode =\n dsx_core::types::PermissionMode::parse(&session.mode).unwrap_or(fallback_mode);\n println!(\"Resuming session {}...\", session.id);\n crate::tui_runner::run_tui(\n project_root,\n api_key,\n api_base,\n mode,\n allow_wide_scope,\n Some(session.id),\n Some(pool),\n )\n .await?;\n }\n _ => println!(\"Error: Session with ID '{}' not found.\", id),\n }\n Ok(())\n}\n"}, "810740d62ba87596d1679239f050e65a4bb1017e": {"t": 1785757088.3056638, "n": 6067, "h": "7964d61dd7499bec", "c": "//! Main binary runtime helpers kept out of the entrypoint.\n\nuse crate::cli::{CliArgs, IndexAction, McpAction, WorkspaceAction};\nuse crate::handlers::{\n list_sessions, run_index_build, run_index_search, run_mcp_call, run_mcp_list,\n};\nuse crate::workspace_notes::list_workspace_notes;\nuse crate::workspace_runs::list_agent_runs;\nuse crate::workspace_stale_runs::close_stale_runs;\n\npub fn load_config_or_default(project_root: &std::path::Path) -> dsx_config::AppConfig {\n match dsx_config::load_for_project(project_root) {\n Ok(config) => config,\n Err(e) => {\n eprintln!(\"Warning: failed to load config: {e}\");\n dsx_config::AppConfig::default()\n }\n }\n}\n\npub fn initial_mode(\n cli: &CliArgs,\n app_config: &dsx_config::AppConfig,\n) -> dsx_core::types::PermissionMode {\n let mode_name = cli\n .mode\n .as_deref()\n .unwrap_or(app_config.app.default_mode.as_str());\n dsx_core::types::PermissionMode::parse(mode_name)\n .unwrap_or(dsx_core::types::PermissionMode::Ask)\n}\n\npub fn api_key(\n cli: &CliArgs,\n app_config: &dsx_config::AppConfig,\n project_root: &std::path::Path,\n) -> Option<String> {\n cli.api_key\n .clone()\n .or_else(|| std::env::var(&app_config.provider.api_key_env).ok())\n .or_else(|| std::env::var(\"DEEPSEEK_API_KEY\").ok())\n .or_else(|| api_key_file(project_root))\n}\n\npub fn require_api_key(api_key: Option<String>) -> Option<String> {\n if api_key.is_none() {\n println!(\"(Set DEEPSEEK_API_KEY or use --api-key)\");\n }\n api_key\n}\n\npub(crate) fn api_key_file(project_root: &std::path::Path) -> Option<String> {\n for dir in project_root.ancestors() {\n let path = dir.join(\".deepseek\");\n if path.is_file()\n && let Ok(content) = std::fs::read_to_string(path)\n && let Some(key) = content.lines().map(str::trim).find(|line| !line.is_empty())\n {\n return Some(key.to_string());\n }\n }\n None\n}\n\npub async fn create_session(\n project_root: &std::path::Path,\n mode: dsx_core::types::PermissionMode,\n) -> (Option<sqlx::SqlitePool>, Option<String>) {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n match dsx_memory::open(&db_path).await {\n Ok(pool) => {\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm\n .create(&project_root.display().to_string(), mode.as_str())\n .await\n {\n Ok(session) => (Some(pool), Some(session.id)),\n Err(_) => (Some(pool), None),\n }\n }\n Err(_) => (None, None),\n }\n}\n\npub async fn run_index_action(\n project_root: &std::path::Path,\n action: IndexAction,\n) -> anyhow::Result<()> {\n match action {\n IndexAction::Build => run_index_build(project_root).await,\n IndexAction::Search { query, limit } => run_index_search(project_root, &query, limit).await,\n }\n}\n\npub async fn run_mcp_action(action: McpAction) -> anyhow::Result<()> {\n match action {\n McpAction::List { command, args } => run_mcp_list(&command, &args).await,\n McpAction::Call {\n tool,\n arguments_json,\n command,\n args,\n } => run_mcp_call(&command, &args, &tool, &arguments_json).await,\n }\n}\n\npub async fn run_workspace_action(\n project_root: std::path::PathBuf,\n api_key: Option<String>,\n api_base: String,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n action: Option<WorkspaceAction>,\n) -> anyhow::Result<()> {\n match action {\n None | Some(WorkspaceAction::List) => list_sessions(&project_root).await,\n Some(WorkspaceAction::Runs { limit, all, json }) => {\n list_agent_runs(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Audit { limit, all, json }) => {\n crate::workspace_audit::run_workspace_audit(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Notes { limit, all, json }) => {\n list_workspace_notes(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Mission {\n limit,\n all,\n json,\n check,\n }) => {\n crate::workspace_mission::run_workspace_mission(&project_root, limit, all, json, check)\n .await?\n }\n Some(WorkspaceAction::CloseStaleRuns {\n older_than_minutes,\n dry_run,\n }) => close_stale_runs(&project_root, older_than_minutes, dry_run).await,\n Some(WorkspaceAction::Resume { id }) => {\n let Some(key) = require_api_key(api_key) else {\n return Ok(());\n };\n resume_session(project_root, key, api_base, mode, allow_wide_scope, id).await?;\n }\n }\n Ok(())\n}\n\nasync fn resume_session(\n project_root: std::path::PathBuf,\n api_key: String,\n api_base: String,\n fallback_mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n id: String,\n) -> anyhow::Result<()> {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n let pool = match dsx_memory::open(&db_path).await {\n Ok(pool) => pool,\n Err(e) => {\n println!(\"Error: Failed to open sessions database: {e}\");\n return Ok(());\n }\n };\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm.get(&id).await {\n Ok(Some(session)) => {\n let mode =\n dsx_core::types::PermissionMode::parse(&session.mode).unwrap_or(fallback_mode);\n println!(\"Resuming session {}...\", session.id);\n crate::tui_runner::run_tui(\n project_root,\n api_key,\n api_base,\n mode,\n allow_wide_scope,\n Some(session.id),\n Some(pool),\n )\n .await?;\n }\n _ => println!(\"Error: Session with ID '{}' not found.\", id),\n }\n Ok(())\n}\n"}, "fed5cc0b381bfbc2e4ca9b51e6ca2add33e433aa": {"t": 1785757090.981886, "n": 5548, "h": "4f1e5fe9a2ab2f44", "c": "//! Main binary runtime helpers kept out of the entrypoint.\n\nuse crate::cli::{CliArgs, IndexAction, McpAction, WorkspaceAction};\nuse crate::handlers::{\n list_sessions, run_index_build, run_index_search, run_mcp_call, run_mcp_list,\n};\nuse crate::workspace_notes::list_workspace_notes;\nuse crate::workspace_runs::list_agent_runs;\nuse crate::workspace_stale_runs::close_stale_runs;\n\npub fn load_config_or_default(project_root: &std::path::Path) -> dsx_config::AppConfig {\n match dsx_config::load_for_project(project_root) {\n Ok(config) => config,\n Err(e) => {\n eprintln!(\"Warning: failed to load config: {e}\");\n dsx_config::AppConfig::default()\n }\n }\n}\n\npub fn initial_mode(\n cli: &CliArgs,\n app_config: &dsx_config::AppConfig,\n) -> dsx_core::types::PermissionMode {\n let mode_name = cli\n .mode\n .as_deref()\n .unwrap_or(app_config.app.default_mode.as_str());\n dsx_core::types::PermissionMode::parse(mode_name)\n .unwrap_or(dsx_core::types::PermissionMode::Ask)\n}\n\npub fn api_key(cli: &CliArgs, app_config: &dsx_config::AppConfig) -> Option<String> {\n cli.api_key\n .clone()\n .or_else(|| std::env::var(&app_config.provider.api_key_env).ok())\n .or_else(|| std::env::var(\"DEEPSEEK_API_KEY\").ok())\n}\n\npub fn require_api_key(api_key: Option<String>) -> Option<String> {\n if api_key.is_none() {\n println!(\"(Set DEEPSEEK_API_KEY or use --api-key)\");\n }\n api_key\n}\n\npub async fn create_session(\n project_root: &std::path::Path,\n mode: dsx_core::types::PermissionMode,\n) -> (Option<sqlx::SqlitePool>, Option<String>) {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n match dsx_memory::open(&db_path).await {\n Ok(pool) => {\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm\n .create(&project_root.display().to_string(), mode.as_str())\n .await\n {\n Ok(session) => (Some(pool), Some(session.id)),\n Err(_) => (Some(pool), None),\n }\n }\n Err(_) => (None, None),\n }\n}\n\npub async fn run_index_action(\n project_root: &std::path::Path,\n action: IndexAction,\n) -> anyhow::Result<()> {\n match action {\n IndexAction::Build => run_index_build(project_root).await,\n IndexAction::Search { query, limit } => run_index_search(project_root, &query, limit).await,\n }\n}\n\npub async fn run_mcp_action(action: McpAction) -> anyhow::Result<()> {\n match action {\n McpAction::List { command, args } => run_mcp_list(&command, &args).await,\n McpAction::Call {\n tool,\n arguments_json,\n command,\n args,\n } => run_mcp_call(&command, &args, &tool, &arguments_json).await,\n }\n}\n\npub async fn run_workspace_action(\n project_root: std::path::PathBuf,\n api_key: Option<String>,\n api_base: String,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n action: Option<WorkspaceAction>,\n) -> anyhow::Result<()> {\n match action {\n None | Some(WorkspaceAction::List) => list_sessions(&project_root).await,\n Some(WorkspaceAction::Runs { limit, all, json }) => {\n list_agent_runs(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Audit { limit, all, json }) => {\n crate::workspace_audit::run_workspace_audit(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Notes { limit, all, json }) => {\n list_workspace_notes(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Mission {\n limit,\n all,\n json,\n check,\n }) => {\n crate::workspace_mission::run_workspace_mission(&project_root, limit, all, json, check)\n .await?\n }\n Some(WorkspaceAction::CloseStaleRuns {\n older_than_minutes,\n dry_run,\n }) => close_stale_runs(&project_root, older_than_minutes, dry_run).await,\n Some(WorkspaceAction::Resume { id }) => {\n let Some(key) = require_api_key(api_key) else {\n return Ok(());\n };\n resume_session(project_root, key, api_base, mode, allow_wide_scope, id).await?;\n }\n }\n Ok(())\n}\n\nasync fn resume_session(\n project_root: std::path::PathBuf,\n api_key: String,\n api_base: String,\n fallback_mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n id: String,\n) -> anyhow::Result<()> {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n let pool = match dsx_memory::open(&db_path).await {\n Ok(pool) => pool,\n Err(e) => {\n println!(\"Error: Failed to open sessions database: {e}\");\n return Ok(());\n }\n };\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm.get(&id).await {\n Ok(Some(session)) => {\n let mode =\n dsx_core::types::PermissionMode::parse(&session.mode).unwrap_or(fallback_mode);\n println!(\"Resuming session {}...\", session.id);\n crate::tui_runner::run_tui(\n project_root,\n api_key,\n api_base,\n mode,\n allow_wide_scope,\n Some(session.id),\n Some(pool),\n )\n .await?;\n }\n _ => println!(\"Error: Session with ID '{}' not found.\", id),\n }\n Ok(())\n}\n"}, "8fde0ea7d8665219fcc37b396052e4e8dfe1a7df": {"t": 1785757092.5398176, "n": 5536, "h": "f4b9f32f9cb59d66", "c": "//! Main binary runtime helpers kept out of the entrypoint.\n\nuse crate::cli::{CliArgs, IndexAction, McpAction, WorkspaceAction};\nuse crate::handlers::{\n list_sessions, run_index_build, run_index_search, run_mcp_call, run_mcp_list,\n};\nuse crate::workspace_notes::list_workspace_notes;\nuse crate::workspace_runs::list_agent_runs;\nuse crate::workspace_stale_runs::close_stale_runs;\n\npub fn load_config_or_default(project_root: &std::path::Path) -> dsx_config::AppConfig {\n match dsx_config::load_for_project(project_root) {\n Ok(config) => config,\n Err(e) => {\n eprintln!(\"Warning: failed to load config: {e}\");\n dsx_config::AppConfig::default()\n }\n }\n}\n\npub fn initial_mode(\n cli: &CliArgs,\n app_config: &dsx_config::AppConfig,\n) -> dsx_core::types::PermissionMode {\n let mode_name = cli\n .mode\n .as_deref()\n .unwrap_or(app_config.app.default_mode.as_str());\n dsx_core::types::PermissionMode::parse(mode_name)\n .unwrap_or(dsx_core::types::PermissionMode::Ask)\n}\n\npub fn api_key(cli: &CliArgs, app_config: &dsx_config::AppConfig) -> Option<String> {\n cli.api_key\n .clone()\n .or_else(|| std::env::var(&app_config.provider.api_key_env).ok())\n .or_else(|| std::env::var(\"DEEPSEEK_API_KEY\").ok())\n}\n\npub fn require_api_key(api_key: Option<String>) -> Option<String> {\n if api_key.is_none() {\n println!(\"(Set DEEPSEEK_API_KEY or use --api-key)\");\n }\n api_key\n}\n\npub async fn create_session(\n project_root: &std::path::Path,\n mode: dsx_core::types::PermissionMode,\n) -> (Option<sqlx::SqlitePool>, Option<String>) {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n match dsx_memory::open(&db_path).await {\n Ok(pool) => {\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm\n .create(&project_root.display().to_string(), mode.as_str())\n .await\n {\n Ok(session) => (Some(pool), Some(session.id)),\n Err(_) => (Some(pool), None),\n }\n }\n Err(_) => (None, None),\n }\n}\n\npub async fn run_index_action(\n project_root: &std::path::Path,\n action: IndexAction,\n) -> anyhow::Result<()> {\n match action {\n IndexAction::Build => run_index_build(project_root).await,\n IndexAction::Search { query, limit } => run_index_search(project_root, &query, limit).await,\n }\n}\n\npub async fn run_mcp_action(action: McpAction) -> anyhow::Result<()> {\n match action {\n McpAction::List { command, args } => run_mcp_list(&command, &args).await,\n McpAction::Call {\n tool,\n arguments_json,\n command,\n args,\n } => run_mcp_call(&command, &args, &tool, &arguments_json).await,\n }\n}\n\npub async fn run_workspace_action(\n project_root: std::path::PathBuf,\n api_key: Option<String>,\n api_base: String,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n action: Option<WorkspaceAction>,\n) -> anyhow::Result<()> {\n match action {\n None | Some(WorkspaceAction::List) => list_sessions(&project_root).await,\n Some(WorkspaceAction::Runs { limit, all }) => {\n list_agent_runs(&project_root, limit, all).await\n }\n Some(WorkspaceAction::Audit { limit, all, json }) => {\n crate::workspace_audit::run_workspace_audit(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Notes { limit, all, json }) => {\n list_workspace_notes(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Mission {\n limit,\n all,\n json,\n check,\n }) => {\n crate::workspace_mission::run_workspace_mission(&project_root, limit, all, json, check)\n .await?\n }\n Some(WorkspaceAction::CloseStaleRuns {\n older_than_minutes,\n dry_run,\n }) => close_stale_runs(&project_root, older_than_minutes, dry_run).await,\n Some(WorkspaceAction::Resume { id }) => {\n let Some(key) = require_api_key(api_key) else {\n return Ok(());\n };\n resume_session(project_root, key, api_base, mode, allow_wide_scope, id).await?;\n }\n }\n Ok(())\n}\n\nasync fn resume_session(\n project_root: std::path::PathBuf,\n api_key: String,\n api_base: String,\n fallback_mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n id: String,\n) -> anyhow::Result<()> {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n let pool = match dsx_memory::open(&db_path).await {\n Ok(pool) => pool,\n Err(e) => {\n println!(\"Error: Failed to open sessions database: {e}\");\n return Ok(());\n }\n };\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm.get(&id).await {\n Ok(Some(session)) => {\n let mode =\n dsx_core::types::PermissionMode::parse(&session.mode).unwrap_or(fallback_mode);\n println!(\"Resuming session {}...\", session.id);\n crate::tui_runner::run_tui(\n project_root,\n api_key,\n api_base,\n mode,\n allow_wide_scope,\n Some(session.id),\n Some(pool),\n )\n .await?;\n }\n _ => println!(\"Error: Session with ID '{}' not found.\", id),\n }\n Ok(())\n}\n"}, "b455b0027abfb7fa5fb99bcc569e3e8ae042a044": {"t": 1785757094.1218066, "n": 5447, "h": "63cba5fc531b3624", "c": "//! Main binary runtime helpers kept out of the entrypoint.\n\nuse crate::cli::{CliArgs, IndexAction, McpAction, WorkspaceAction};\nuse crate::handlers::{\n list_sessions, run_index_build, run_index_search, run_mcp_call, run_mcp_list,\n};\nuse crate::workspace_notes::list_workspace_notes;\nuse crate::workspace_runs::list_agent_runs;\nuse crate::workspace_stale_runs::close_stale_runs;\n\npub fn load_config_or_default(project_root: &std::path::Path) -> dsx_config::AppConfig {\n match dsx_config::load_for_project(project_root) {\n Ok(config) => config,\n Err(e) => {\n eprintln!(\"Warning: failed to load config: {e}\");\n dsx_config::AppConfig::default()\n }\n }\n}\n\npub fn initial_mode(\n cli: &CliArgs,\n app_config: &dsx_config::AppConfig,\n) -> dsx_core::types::PermissionMode {\n let mode_name = cli\n .mode\n .as_deref()\n .unwrap_or(app_config.app.default_mode.as_str());\n dsx_core::types::PermissionMode::parse(mode_name)\n .unwrap_or(dsx_core::types::PermissionMode::Ask)\n}\n\npub fn api_key(cli: &CliArgs, app_config: &dsx_config::AppConfig) -> Option<String> {\n cli.api_key\n .clone()\n .or_else(|| std::env::var(&app_config.provider.api_key_env).ok())\n .or_else(|| std::env::var(\"DEEPSEEK_API_KEY\").ok())\n}\n\npub fn require_api_key(api_key: Option<String>) -> Option<String> {\n if api_key.is_none() {\n println!(\"(Set DEEPSEEK_API_KEY or use --api-key)\");\n }\n api_key\n}\n\npub async fn create_session(\n project_root: &std::path::Path,\n mode: dsx_core::types::PermissionMode,\n) -> (Option<sqlx::SqlitePool>, Option<String>) {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n match dsx_memory::open(&db_path).await {\n Ok(pool) => {\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm\n .create(&project_root.display().to_string(), mode.as_str())\n .await\n {\n Ok(session) => (Some(pool), Some(session.id)),\n Err(_) => (Some(pool), None),\n }\n }\n Err(_) => (None, None),\n }\n}\n\npub async fn run_index_action(\n project_root: &std::path::Path,\n action: IndexAction,\n) -> anyhow::Result<()> {\n match action {\n IndexAction::Build => run_index_build(project_root).await,\n IndexAction::Search { query, limit } => run_index_search(project_root, &query, limit).await,\n }\n}\n\npub async fn run_mcp_action(action: McpAction) -> anyhow::Result<()> {\n match action {\n McpAction::List { command, args } => run_mcp_list(&command, &args).await,\n McpAction::Call {\n tool,\n arguments_json,\n command,\n args,\n } => run_mcp_call(&command, &args, &tool, &arguments_json).await,\n }\n}\n\npub async fn run_workspace_action(\n project_root: std::path::PathBuf,\n api_key: Option<String>,\n api_base: String,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n action: Option<WorkspaceAction>,\n) -> anyhow::Result<()> {\n match action {\n None | Some(WorkspaceAction::List) => list_sessions(&project_root).await,\n Some(WorkspaceAction::Runs { limit, all }) => {\n list_agent_runs(&project_root, limit, all).await\n }\n Some(WorkspaceAction::Audit { limit, all, json }) => {\n crate::workspace_audit::run_workspace_audit(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Notes { limit, all, json }) => {\n list_workspace_notes(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Mission { limit, all, json }) => {\n crate::workspace_mission::run_workspace_mission(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::CloseStaleRuns {\n older_than_minutes,\n dry_run,\n }) => close_stale_runs(&project_root, older_than_minutes, dry_run).await,\n Some(WorkspaceAction::Resume { id }) => {\n let Some(key) = require_api_key(api_key) else {\n return Ok(());\n };\n resume_session(project_root, key, api_base, mode, allow_wide_scope, id).await?;\n }\n }\n Ok(())\n}\n\nasync fn resume_session(\n project_root: std::path::PathBuf,\n api_key: String,\n api_base: String,\n fallback_mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n id: String,\n) -> anyhow::Result<()> {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n let pool = match dsx_memory::open(&db_path).await {\n Ok(pool) => pool,\n Err(e) => {\n println!(\"Error: Failed to open sessions database: {e}\");\n return Ok(());\n }\n };\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm.get(&id).await {\n Ok(Some(session)) => {\n let mode =\n dsx_core::types::PermissionMode::parse(&session.mode).unwrap_or(fallback_mode);\n println!(\"Resuming session {}...\", session.id);\n crate::tui_runner::run_tui(\n project_root,\n api_key,\n api_base,\n mode,\n allow_wide_scope,\n Some(session.id),\n Some(pool),\n )\n .await?;\n }\n _ => println!(\"Error: Session with ID '{}' not found.\", id),\n }\n Ok(())\n}\n"}, "c2891385cb0593edb9281c544c3bf652c4b35c7a": {"t": 1785757095.8946807, "n": 5273, "h": "9ebe0557267d7c6e", "c": "//! Main binary runtime helpers kept out of the entrypoint.\n\nuse crate::cli::{CliArgs, IndexAction, McpAction, WorkspaceAction};\nuse crate::handlers::{\n list_sessions, run_index_build, run_index_search, run_mcp_call, run_mcp_list,\n};\nuse crate::workspace_notes::list_workspace_notes;\nuse crate::workspace_runs::list_agent_runs;\nuse crate::workspace_stale_runs::close_stale_runs;\n\npub fn load_config_or_default(project_root: &std::path::Path) -> dsx_config::AppConfig {\n match dsx_config::load_for_project(project_root) {\n Ok(config) => config,\n Err(e) => {\n eprintln!(\"Warning: failed to load config: {e}\");\n dsx_config::AppConfig::default()\n }\n }\n}\n\npub fn initial_mode(\n cli: &CliArgs,\n app_config: &dsx_config::AppConfig,\n) -> dsx_core::types::PermissionMode {\n let mode_name = cli\n .mode\n .as_deref()\n .unwrap_or(app_config.app.default_mode.as_str());\n dsx_core::types::PermissionMode::parse(mode_name)\n .unwrap_or(dsx_core::types::PermissionMode::Ask)\n}\n\npub fn api_key(cli: &CliArgs, app_config: &dsx_config::AppConfig) -> Option<String> {\n cli.api_key\n .clone()\n .or_else(|| std::env::var(&app_config.provider.api_key_env).ok())\n .or_else(|| std::env::var(\"DEEPSEEK_API_KEY\").ok())\n}\n\npub fn require_api_key(api_key: Option<String>) -> Option<String> {\n if api_key.is_none() {\n println!(\"(Set DEEPSEEK_API_KEY or use --api-key)\");\n }\n api_key\n}\n\npub async fn create_session(\n project_root: &std::path::Path,\n mode: dsx_core::types::PermissionMode,\n) -> (Option<sqlx::SqlitePool>, Option<String>) {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n match dsx_memory::open(&db_path).await {\n Ok(pool) => {\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm\n .create(&project_root.display().to_string(), mode.as_str())\n .await\n {\n Ok(session) => (Some(pool), Some(session.id)),\n Err(_) => (Some(pool), None),\n }\n }\n Err(_) => (None, None),\n }\n}\n\npub async fn run_index_action(\n project_root: &std::path::Path,\n action: IndexAction,\n) -> anyhow::Result<()> {\n match action {\n IndexAction::Build => run_index_build(project_root).await,\n IndexAction::Search { query, limit } => run_index_search(project_root, &query, limit).await,\n }\n}\n\npub async fn run_mcp_action(action: McpAction) -> anyhow::Result<()> {\n match action {\n McpAction::List { command, args } => run_mcp_list(&command, &args).await,\n McpAction::Call {\n tool,\n arguments_json,\n command,\n args,\n } => run_mcp_call(&command, &args, &tool, &arguments_json).await,\n }\n}\n\npub async fn run_workspace_action(\n project_root: std::path::PathBuf,\n api_key: Option<String>,\n api_base: String,\n mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n action: Option<WorkspaceAction>,\n) -> anyhow::Result<()> {\n match action {\n None | Some(WorkspaceAction::List) => list_sessions(&project_root).await,\n Some(WorkspaceAction::Runs { limit, all }) => {\n list_agent_runs(&project_root, limit, all).await\n }\n Some(WorkspaceAction::Audit { limit, all, json }) => {\n crate::workspace_audit::run_workspace_audit(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::Notes { limit, all, json }) => {\n list_workspace_notes(&project_root, limit, all, json).await\n }\n Some(WorkspaceAction::CloseStaleRuns {\n older_than_minutes,\n dry_run,\n }) => close_stale_runs(&project_root, older_than_minutes, dry_run).await,\n Some(WorkspaceAction::Resume { id }) => {\n let Some(key) = require_api_key(api_key) else {\n return Ok(());\n };\n resume_session(project_root, key, api_base, mode, allow_wide_scope, id).await?;\n }\n }\n Ok(())\n}\n\nasync fn resume_session(\n project_root: std::path::PathBuf,\n api_key: String,\n api_base: String,\n fallback_mode: dsx_core::types::PermissionMode,\n allow_wide_scope: bool,\n id: String,\n) -> anyhow::Result<()> {\n let db_path = project_root.join(\".dsx\").join(\"sessions.db\");\n let pool = match dsx_memory::open(&db_path).await {\n Ok(pool) => pool,\n Err(e) => {\n println!(\"Error: Failed to open sessions database: {e}\");\n return Ok(());\n }\n };\n let sm = dsx_session::SessionManager::new(pool.clone());\n match sm.get(&id).await {\n Ok(Some(session)) => {\n let mode =\n dsx_core::types::PermissionMode::parse(&session.mode).unwrap_or(fallback_mode);\n println!(\"Resuming session {}...\", session.id);\n crate::tui_runner::run_tui(\n project_root,\n api_key,\n api_base,\n mode,\n allow_wide_scope,\n Some(session.id),\n Some(pool),\n )\n .await?;\n }\n _ => println!(\"Error: Session with ID '{}' not found.\", id),\n }\n Ok(())\n}\n"}}, "src/tui_state_tests.rs": {"02ab671513ed25839db232846a1913627e488a1b": {"t": 1785746252.729293, "n": 4974, "h": "3f5d337afc5b7c64", "c": "//! Tests for TUI launch and active-scope indexing behavior.\n\n#[cfg(test)]\nmod tests {\n use crate::tui_state::{configure_initial_app, index_active_scope, load_startup_audit};\n use std::path::Path;\n use std::sync::{Arc, Mutex};\n\n #[test]\n fn initial_file_tree_is_shallow_launch_listing() {\n let root = temp_root(\"dsx_tui_shallow_tree\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(root.join(\"1234/src\")).unwrap();\n std::fs::write(root.join(\"top.txt\"), \"top\").unwrap();\n std::fs::write(root.join(\"1234/src/main.rs\"), \"fn main() {}\").unwrap();\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n false,\n None,\n );\n\n let file_tree = app.lock().unwrap().file_tree.clone();\n assert!(file_tree.contains(&\"top.txt\".into()));\n assert!(file_tree.contains(&\"1234/\".into()));\n assert!(!file_tree.iter().any(|path| path.contains(\"main.rs\")));\n assert!(app.lock().unwrap().run_budget.max_tokens > 0);\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn active_scope_indexing_ignores_sibling_projects() {\n let root = temp_root(\"dsx_tui_active_index\");\n let active = root.join(\"1234\");\n let sibling = root.join(\"other\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(active.join(\"src\")).unwrap();\n std::fs::create_dir_all(sibling.join(\"src\")).unwrap();\n std::fs::write(active.join(\"src/lib.rs\"), \"pub fn active_only() {}\\n\").unwrap();\n std::fs::write(sibling.join(\"src/lib.rs\"), \"pub fn sibling_only() {}\\n\").unwrap();\n\n index_active_scope(&active).await.unwrap();\n let pool = dsx_memory::open(&active.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let active_hits = dsx_index::search_symbols(&active, &pool, \"active_only\", 10)\n .await\n .unwrap();\n let sibling_hits = dsx_index::search_symbols(&active, &pool, \"sibling_only\", 10)\n .await\n .unwrap();\n\n assert_eq!(active_hits.len(), 1);\n assert!(sibling_hits.is_empty());\n\n let _ = pool.close().await;\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn startup_audit_surfaces_scope_escape_warning() {\n let root = temp_root(\"dsx_tui_startup_audit\");\n let child = root.join(\"1234\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(&child).unwrap();\n seed_scoped_run(&root, &child).await;\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n false,\n None,\n );\n load_startup_audit(&app, &root).await;\n\n let app = app.lock().unwrap();\n assert!(\n app.messages\n .iter()\n .any(|message| message.content.contains(\"Workspace audit:\"))\n );\n assert!(app.scope_lock.warning.contains(\"scope escape\"));\n assert_eq!(app.run_ledger.total, 1);\n assert_eq!(app.run_ledger.scope_violations, 2);\n assert_eq!(app.run_ledger.recent[0].scope, \"1234\");\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n async fn seed_scoped_run(root: &Path, child: &Path) {\n let pool = dsx_memory::open(&child.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let id = dsx_memory::start_scoped_agent_run(\n &pool,\n &dsx_memory::AgentRunStart {\n session_id: None,\n project_root: &child.display().to_string(),\n task: \"use only child scope\",\n launch_scope: &root.display().to_string(),\n active_scope: &child.display().to_string(),\n scope_status: \"Narrowed\",\n scope_reason: \"User selected a child project.\",\n },\n )\n .await\n .unwrap();\n dsx_memory::finish_agent_run(\n &pool,\n &id,\n &dsx_memory::AgentRunUpdate {\n status: \"completed\".into(),\n scope_violations: 2,\n last_scope_violation: \"read_file denied outside active scope\".into(),\n ..Default::default()\n },\n )\n .await\n .unwrap();\n }\n\n fn temp_root(name: &str) -> std::path::PathBuf {\n let nanos = std::time::SystemTime::now()\n .duration_since(std::time::UNIX_EPOCH)\n .unwrap()\n .as_nanos();\n std::env::temp_dir().join(format!(\"{name}_{nanos}\"))\n }\n}\n"}, "513c3e0e80eec525810d7f4aede090f9f7c2de22": {"t": 1785757086.0855887, "n": 4974, "h": "3f5d337afc5b7c64", "c": "//! Tests for TUI launch and active-scope indexing behavior.\n\n#[cfg(test)]\nmod tests {\n use crate::tui_state::{configure_initial_app, index_active_scope, load_startup_audit};\n use std::path::Path;\n use std::sync::{Arc, Mutex};\n\n #[test]\n fn initial_file_tree_is_shallow_launch_listing() {\n let root = temp_root(\"dsx_tui_shallow_tree\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(root.join(\"1234/src\")).unwrap();\n std::fs::write(root.join(\"top.txt\"), \"top\").unwrap();\n std::fs::write(root.join(\"1234/src/main.rs\"), \"fn main() {}\").unwrap();\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n false,\n None,\n );\n\n let file_tree = app.lock().unwrap().file_tree.clone();\n assert!(file_tree.contains(&\"top.txt\".into()));\n assert!(file_tree.contains(&\"1234/\".into()));\n assert!(!file_tree.iter().any(|path| path.contains(\"main.rs\")));\n assert!(app.lock().unwrap().run_budget.max_tokens > 0);\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn active_scope_indexing_ignores_sibling_projects() {\n let root = temp_root(\"dsx_tui_active_index\");\n let active = root.join(\"1234\");\n let sibling = root.join(\"other\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(active.join(\"src\")).unwrap();\n std::fs::create_dir_all(sibling.join(\"src\")).unwrap();\n std::fs::write(active.join(\"src/lib.rs\"), \"pub fn active_only() {}\\n\").unwrap();\n std::fs::write(sibling.join(\"src/lib.rs\"), \"pub fn sibling_only() {}\\n\").unwrap();\n\n index_active_scope(&active).await.unwrap();\n let pool = dsx_memory::open(&active.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let active_hits = dsx_index::search_symbols(&active, &pool, \"active_only\", 10)\n .await\n .unwrap();\n let sibling_hits = dsx_index::search_symbols(&active, &pool, \"sibling_only\", 10)\n .await\n .unwrap();\n\n assert_eq!(active_hits.len(), 1);\n assert!(sibling_hits.is_empty());\n\n let _ = pool.close().await;\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn startup_audit_surfaces_scope_escape_warning() {\n let root = temp_root(\"dsx_tui_startup_audit\");\n let child = root.join(\"1234\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(&child).unwrap();\n seed_scoped_run(&root, &child).await;\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n false,\n None,\n );\n load_startup_audit(&app, &root).await;\n\n let app = app.lock().unwrap();\n assert!(\n app.messages\n .iter()\n .any(|message| message.content.contains(\"Workspace audit:\"))\n );\n assert!(app.scope_lock.warning.contains(\"scope escape\"));\n assert_eq!(app.run_ledger.total, 1);\n assert_eq!(app.run_ledger.scope_violations, 2);\n assert_eq!(app.run_ledger.recent[0].scope, \"1234\");\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n async fn seed_scoped_run(root: &Path, child: &Path) {\n let pool = dsx_memory::open(&child.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let id = dsx_memory::start_scoped_agent_run(\n &pool,\n &dsx_memory::AgentRunStart {\n session_id: None,\n project_root: &child.display().to_string(),\n task: \"use only child scope\",\n launch_scope: &root.display().to_string(),\n active_scope: &child.display().to_string(),\n scope_status: \"Narrowed\",\n scope_reason: \"User selected a child project.\",\n },\n )\n .await\n .unwrap();\n dsx_memory::finish_agent_run(\n &pool,\n &id,\n &dsx_memory::AgentRunUpdate {\n status: \"completed\".into(),\n scope_violations: 2,\n last_scope_violation: \"read_file denied outside active scope\".into(),\n ..Default::default()\n },\n )\n .await\n .unwrap();\n }\n\n fn temp_root(name: &str) -> std::path::PathBuf {\n let nanos = std::time::SystemTime::now()\n .duration_since(std::time::UNIX_EPOCH)\n .unwrap()\n .as_nanos();\n std::env::temp_dir().join(format!(\"{name}_{nanos}\"))\n }\n}\n"}, "99eebc2b5e75c96abdaebd0f270c5b668fe748d9": {"t": 1785757087.5572033, "n": 4910, "h": "20aac033dfec1e20", "c": "//! Tests for TUI launch and active-scope indexing behavior.\n\n#[cfg(test)]\nmod tests {\n use crate::tui_state::{configure_initial_app, index_active_scope, load_startup_audit};\n use std::path::Path;\n use std::sync::{Arc, Mutex};\n\n #[test]\n fn initial_file_tree_is_shallow_launch_listing() {\n let root = temp_root(\"dsx_tui_shallow_tree\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(root.join(\"1234/src\")).unwrap();\n std::fs::write(root.join(\"top.txt\"), \"top\").unwrap();\n std::fs::write(root.join(\"1234/src/main.rs\"), \"fn main() {}\").unwrap();\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n false,\n None,\n );\n\n let file_tree = app.lock().unwrap().file_tree.clone();\n assert!(file_tree.contains(&\"top.txt\".into()));\n assert!(file_tree.contains(&\"1234/\".into()));\n assert!(!file_tree.iter().any(|path| path.contains(\"main.rs\")));\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn active_scope_indexing_ignores_sibling_projects() {\n let root = temp_root(\"dsx_tui_active_index\");\n let active = root.join(\"1234\");\n let sibling = root.join(\"other\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(active.join(\"src\")).unwrap();\n std::fs::create_dir_all(sibling.join(\"src\")).unwrap();\n std::fs::write(active.join(\"src/lib.rs\"), \"pub fn active_only() {}\\n\").unwrap();\n std::fs::write(sibling.join(\"src/lib.rs\"), \"pub fn sibling_only() {}\\n\").unwrap();\n\n index_active_scope(&active).await.unwrap();\n let pool = dsx_memory::open(&active.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let active_hits = dsx_index::search_symbols(&active, &pool, \"active_only\", 10)\n .await\n .unwrap();\n let sibling_hits = dsx_index::search_symbols(&active, &pool, \"sibling_only\", 10)\n .await\n .unwrap();\n\n assert_eq!(active_hits.len(), 1);\n assert!(sibling_hits.is_empty());\n\n let _ = pool.close().await;\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn startup_audit_surfaces_scope_escape_warning() {\n let root = temp_root(\"dsx_tui_startup_audit\");\n let child = root.join(\"1234\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(&child).unwrap();\n seed_scoped_run(&root, &child).await;\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n false,\n None,\n );\n load_startup_audit(&app, &root).await;\n\n let app = app.lock().unwrap();\n assert!(\n app.messages\n .iter()\n .any(|message| message.content.contains(\"Workspace audit:\"))\n );\n assert!(app.scope_lock.warning.contains(\"scope escape\"));\n assert_eq!(app.run_ledger.total, 1);\n assert_eq!(app.run_ledger.scope_violations, 2);\n assert_eq!(app.run_ledger.recent[0].scope, \"1234\");\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n async fn seed_scoped_run(root: &Path, child: &Path) {\n let pool = dsx_memory::open(&child.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let id = dsx_memory::start_scoped_agent_run(\n &pool,\n &dsx_memory::AgentRunStart {\n session_id: None,\n project_root: &child.display().to_string(),\n task: \"use only child scope\",\n launch_scope: &root.display().to_string(),\n active_scope: &child.display().to_string(),\n scope_status: \"Narrowed\",\n scope_reason: \"User selected a child project.\",\n },\n )\n .await\n .unwrap();\n dsx_memory::finish_agent_run(\n &pool,\n &id,\n &dsx_memory::AgentRunUpdate {\n status: \"completed\".into(),\n scope_violations: 2,\n last_scope_violation: \"read_file denied outside active scope\".into(),\n ..Default::default()\n },\n )\n .await\n .unwrap();\n }\n\n fn temp_root(name: &str) -> std::path::PathBuf {\n let nanos = std::time::SystemTime::now()\n .duration_since(std::time::UNIX_EPOCH)\n .unwrap()\n .as_nanos();\n std::env::temp_dir().join(format!(\"{name}_{nanos}\"))\n }\n}\n"}, "10c05f0a0b4c93045646f5317323adc0a8dc37e8": {"t": 1785757089.5154974, "n": 4749, "h": "c699c3aceca8da96", "c": "//! Tests for TUI launch and active-scope indexing behavior.\n\n#[cfg(test)]\nmod tests {\n use crate::tui_state::{configure_initial_app, index_active_scope, load_startup_audit};\n use std::path::Path;\n use std::sync::{Arc, Mutex};\n\n #[test]\n fn initial_file_tree_is_shallow_launch_listing() {\n let root = temp_root(\"dsx_tui_shallow_tree\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(root.join(\"1234/src\")).unwrap();\n std::fs::write(root.join(\"top.txt\"), \"top\").unwrap();\n std::fs::write(root.join(\"1234/src/main.rs\"), \"fn main() {}\").unwrap();\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n false,\n None,\n );\n\n let file_tree = app.lock().unwrap().file_tree.clone();\n assert!(file_tree.contains(&\"top.txt\".into()));\n assert!(file_tree.contains(&\"1234/\".into()));\n assert!(!file_tree.iter().any(|path| path.contains(\"main.rs\")));\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn active_scope_indexing_ignores_sibling_projects() {\n let root = temp_root(\"dsx_tui_active_index\");\n let active = root.join(\"1234\");\n let sibling = root.join(\"other\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(active.join(\"src\")).unwrap();\n std::fs::create_dir_all(sibling.join(\"src\")).unwrap();\n std::fs::write(active.join(\"src/lib.rs\"), \"pub fn active_only() {}\\n\").unwrap();\n std::fs::write(sibling.join(\"src/lib.rs\"), \"pub fn sibling_only() {}\\n\").unwrap();\n\n index_active_scope(&active).await.unwrap();\n let pool = dsx_memory::open(&active.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let active_hits = dsx_index::search_symbols(&active, &pool, \"active_only\", 10)\n .await\n .unwrap();\n let sibling_hits = dsx_index::search_symbols(&active, &pool, \"sibling_only\", 10)\n .await\n .unwrap();\n\n assert_eq!(active_hits.len(), 1);\n assert!(sibling_hits.is_empty());\n\n let _ = pool.close().await;\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn startup_audit_surfaces_scope_escape_warning() {\n let root = temp_root(\"dsx_tui_startup_audit\");\n let child = root.join(\"1234\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(&child).unwrap();\n seed_scoped_run(&root, &child).await;\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n false,\n None,\n );\n load_startup_audit(&app, &root).await;\n\n let app = app.lock().unwrap();\n assert!(\n app.messages\n .iter()\n .any(|message| message.content.contains(\"Workspace audit:\"))\n );\n assert!(app.scope_lock.warning.contains(\"scope escape\"));\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n async fn seed_scoped_run(root: &Path, child: &Path) {\n let pool = dsx_memory::open(&child.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let id = dsx_memory::start_scoped_agent_run(\n &pool,\n &dsx_memory::AgentRunStart {\n session_id: None,\n project_root: &child.display().to_string(),\n task: \"use only child scope\",\n launch_scope: &root.display().to_string(),\n active_scope: &child.display().to_string(),\n scope_status: \"Narrowed\",\n scope_reason: \"User selected a child project.\",\n },\n )\n .await\n .unwrap();\n dsx_memory::finish_agent_run(\n &pool,\n &id,\n &dsx_memory::AgentRunUpdate {\n status: \"completed\".into(),\n scope_violations: 2,\n last_scope_violation: \"read_file denied outside active scope\".into(),\n ..Default::default()\n },\n )\n .await\n .unwrap();\n }\n\n fn temp_root(name: &str) -> std::path::PathBuf {\n let nanos = std::time::SystemTime::now()\n .duration_since(std::time::UNIX_EPOCH)\n .unwrap()\n .as_nanos();\n std::env::temp_dir().join(format!(\"{name}_{nanos}\"))\n }\n}\n"}, "b9bc15135ccb3dcd3dbd38fb2a9367068f91fcce": {"t": 1785757092.1202886, "n": 2622, "h": "84326a7176a12534", "c": "//! Tests for TUI launch and active-scope indexing behavior.\n\n#[cfg(test)]\nmod tests {\n use crate::tui_state::{configure_initial_app, index_active_scope};\n use std::sync::{Arc, Mutex};\n\n #[test]\n fn initial_file_tree_is_shallow_launch_listing() {\n let root = temp_root(\"dsx_tui_shallow_tree\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(root.join(\"1234/src\")).unwrap();\n std::fs::write(root.join(\"top.txt\"), \"top\").unwrap();\n std::fs::write(root.join(\"1234/src/main.rs\"), \"fn main() {}\").unwrap();\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n false,\n None,\n );\n\n let file_tree = app.lock().unwrap().file_tree.clone();\n assert!(file_tree.contains(&\"top.txt\".into()));\n assert!(file_tree.contains(&\"1234/\".into()));\n assert!(!file_tree.iter().any(|path| path.contains(\"main.rs\")));\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn active_scope_indexing_ignores_sibling_projects() {\n let root = temp_root(\"dsx_tui_active_index\");\n let active = root.join(\"1234\");\n let sibling = root.join(\"other\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(active.join(\"src\")).unwrap();\n std::fs::create_dir_all(sibling.join(\"src\")).unwrap();\n std::fs::write(active.join(\"src/lib.rs\"), \"pub fn active_only() {}\\n\").unwrap();\n std::fs::write(sibling.join(\"src/lib.rs\"), \"pub fn sibling_only() {}\\n\").unwrap();\n\n index_active_scope(&active).await.unwrap();\n let pool = dsx_memory::open(&active.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let active_hits = dsx_index::search_symbols(&active, &pool, \"active_only\", 10)\n .await\n .unwrap();\n let sibling_hits = dsx_index::search_symbols(&active, &pool, \"sibling_only\", 10)\n .await\n .unwrap();\n\n assert_eq!(active_hits.len(), 1);\n assert!(sibling_hits.is_empty());\n\n let _ = pool.close().await;\n let _ = std::fs::remove_dir_all(root);\n }\n\n fn temp_root(name: &str) -> std::path::PathBuf {\n let nanos = std::time::SystemTime::now()\n .duration_since(std::time::UNIX_EPOCH)\n .unwrap()\n .as_nanos();\n std::env::temp_dir().join(format!(\"{name}_{nanos}\"))\n }\n}\n"}, "9009dc65680a17506250d95a6beae5a90ed3679a": {"t": 1785757094.5734923, "n": 2603, "h": "5aa958d10b05fe2f", "c": "//! Tests for TUI launch and active-scope indexing behavior.\n\n#[cfg(test)]\nmod tests {\n use crate::tui_state::{configure_initial_app, index_active_scope};\n use std::sync::{Arc, Mutex};\n\n #[test]\n fn initial_file_tree_is_shallow_launch_listing() {\n let root = temp_root(\"dsx_tui_shallow_tree\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(root.join(\"1234/src\")).unwrap();\n std::fs::write(root.join(\"top.txt\"), \"top\").unwrap();\n std::fs::write(root.join(\"1234/src/main.rs\"), \"fn main() {}\").unwrap();\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n None,\n );\n\n let file_tree = app.lock().unwrap().file_tree.clone();\n assert!(file_tree.contains(&\"top.txt\".into()));\n assert!(file_tree.contains(&\"1234/\".into()));\n assert!(!file_tree.iter().any(|path| path.contains(\"main.rs\")));\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn active_scope_indexing_ignores_sibling_projects() {\n let root = temp_root(\"dsx_tui_active_index\");\n let active = root.join(\"1234\");\n let sibling = root.join(\"other\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(active.join(\"src\")).unwrap();\n std::fs::create_dir_all(sibling.join(\"src\")).unwrap();\n std::fs::write(active.join(\"src/lib.rs\"), \"pub fn active_only() {}\\n\").unwrap();\n std::fs::write(sibling.join(\"src/lib.rs\"), \"pub fn sibling_only() {}\\n\").unwrap();\n\n index_active_scope(&active).await.unwrap();\n let pool = dsx_memory::open(&active.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let active_hits = dsx_index::search_symbols(&active, &pool, \"active_only\", 10)\n .await\n .unwrap();\n let sibling_hits = dsx_index::search_symbols(&active, &pool, \"sibling_only\", 10)\n .await\n .unwrap();\n\n assert_eq!(active_hits.len(), 1);\n assert!(sibling_hits.is_empty());\n\n let _ = pool.close().await;\n let _ = std::fs::remove_dir_all(root);\n }\n\n fn temp_root(name: &str) -> std::path::PathBuf {\n let nanos = std::time::SystemTime::now()\n .duration_since(std::time::UNIX_EPOCH)\n .unwrap()\n .as_nanos();\n std::env::temp_dir().join(format!(\"{name}_{nanos}\"))\n }\n}\n"}, "2ea5c22679b1a890375fc9a0ec630025eb6f6614": {"t": 1785757096.2219877, "n": 2549, "h": "dddef0ebd09d4f50", "c": "//! Tests for TUI launch and active-scope indexing behavior.\n\n#[cfg(test)]\nmod tests {\n use crate::tui_state::{configure_initial_app, index_active_scope};\n use std::sync::{Arc, Mutex};\n\n #[test]\n fn initial_file_tree_is_shallow_launch_listing() {\n let root = temp_root(\"dsx_tui_shallow_tree\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(root.join(\"1234/src\")).unwrap();\n std::fs::write(root.join(\"top.txt\"), \"top\").unwrap();\n std::fs::write(root.join(\"1234/src/main.rs\"), \"fn main() {}\").unwrap();\n\n let app = Arc::new(Mutex::new(dsx_tui::App::new()));\n configure_initial_app(\n &app,\n &root,\n dsx_core::types::PermissionMode::Ask,\n \"https://api.deepseek.com\".into(),\n String::new(),\n None,\n );\n\n let file_tree = app.lock().unwrap().file_tree.clone();\n assert!(file_tree.contains(&\"top.txt\".into()));\n assert!(!file_tree.iter().any(|path| path.contains(\"main.rs\")));\n\n let _ = std::fs::remove_dir_all(root);\n }\n\n #[tokio::test]\n async fn active_scope_indexing_ignores_sibling_projects() {\n let root = temp_root(\"dsx_tui_active_index\");\n let active = root.join(\"1234\");\n let sibling = root.join(\"other\");\n let _ = std::fs::remove_dir_all(&root);\n std::fs::create_dir_all(active.join(\"src\")).unwrap();\n std::fs::create_dir_all(sibling.join(\"src\")).unwrap();\n std::fs::write(active.join(\"src/lib.rs\"), \"pub fn active_only() {}\\n\").unwrap();\n std::fs::write(sibling.join(\"src/lib.rs\"), \"pub fn sibling_only() {}\\n\").unwrap();\n\n index_active_scope(&active).await.unwrap();\n let pool = dsx_memory::open(&active.join(\".dsx\").join(\"sessions.db\"))\n .await\n .unwrap();\n let active_hits = dsx_index::search_symbols(&active, &pool, \"active_only\", 10)\n .await\n .unwrap();\n let sibling_hits = dsx_index::search_symbols(&active, &pool, \"sibling_only\", 10)\n .await\n .unwrap();\n\n assert_eq!(active_hits.len(), 1);\n assert!(sibling_hits.is_empty());\n\n let _ = pool.close().await;\n let _ = std::fs::remove_dir_all(root);\n }\n\n fn temp_root(name: &str) -> std::path::PathBuf {\n let nanos = std::time::SystemTime::now()\n .duration_since(std::time::UNIX_EPOCH)\n .unwrap()\n .as_nanos();\n std::env::temp_dir().join(format!(\"{name}_{nanos}\"))\n }\n}\n"}}}}