From 3f6d7d56f6a350b3fc52a487bcf527c23193a713 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Fri, 24 Oct 2025 02:54:19 +0200 Subject: [PATCH] feat(ui): add glass modals and theme preview AC:\n- Theme, help, command, and model modals share the glass chrome.\n- Theme selector shows a live preview for the highlighted palette.\n- Updated docs and screenshots explain the refreshed cockpit.\n\nTests:\n- cargo test -p owlen-tui --- README.md | 3 +- crates/owlen-tui/src/chat_app.rs | 14 +- crates/owlen-tui/src/glass.rs | 144 ++ crates/owlen-tui/src/lib.rs | 1 + crates/owlen-tui/src/ui.rs | 1407 ++++++++++++++---- crates/owlen-tui/src/widgets/model_picker.rs | 76 +- docs/troubleshooting.md | 1 + images/layout.png | Bin 27033 -> 65891 bytes themes/default_dark.toml | 58 +- themes/default_light.toml | 58 +- 10 files changed, 1397 insertions(+), 365 deletions(-) create mode 100644 crates/owlen-tui/src/glass.rs diff --git a/README.md b/README.md index 050807e..ba6f14b 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This project is currently in **alpha** and under active development. Core featur ![OWLEN TUI Layout](images/layout.png) -The OWLEN interface features a clean, multi-panel layout with vim-inspired navigation. See more screenshots in the [`images/`](images/) directory. +The refreshed chrome introduces a cockpit-style header with live gradient gauges for context and cloud usage, plus glassy panels that keep vim-inspired navigation easy to follow. See more screenshots in the [`images/`](images/) directory. ## Features @@ -32,6 +32,7 @@ The OWLEN interface features a clean, multi-panel layout with vim-inspired navig - **Advanced Text Editing**: Multi-line input, history, and clipboard support. - **Session Management**: Save, load, and manage conversations. - **Code Side Panel**: Switch to code mode (`:mode code`) and open files inline with `:open ` for LLM-assisted coding. +- **Cockpit Header**: Gradient context and cloud usage bars with live quota bands and provider fallbacks. - **Theming System**: 10 built-in themes and support for custom themes. - **Modular Architecture**: Extensible provider system orchestrated by the new `ProviderManager`, ready for additional MCP-backed providers. - **Dual-Source Model Picker**: Merge local and cloud catalogues with real-time availability badges powered by the background health worker. diff --git a/crates/owlen-tui/src/chat_app.rs b/crates/owlen-tui/src/chat_app.rs index 7742e19..f90bd89 100644 --- a/crates/owlen-tui/src/chat_app.rs +++ b/crates/owlen-tui/src/chat_app.rs @@ -115,6 +115,7 @@ pub struct ContextUsage { pub(crate) struct LayoutSnapshot { pub(crate) frame: Rect, pub(crate) content: Rect, + pub(crate) header_panel: Option, pub(crate) file_panel: Option, pub(crate) chat_panel: Option, pub(crate) thinking_panel: Option, @@ -131,6 +132,7 @@ impl LayoutSnapshot { Self { frame, content, + header_panel: None, file_panel: None, chat_panel: None, thinking_panel: None, @@ -155,6 +157,11 @@ impl LayoutSnapshot { return Some(UiRegion::ModelInfo); } } + if let Some(rect) = self.header_panel { + if Self::contains(rect, column, row) { + return Some(UiRegion::Header); + } + } if let Some(rect) = self.code_panel { if Self::contains(rect, column, row) { return Some(UiRegion::Code); @@ -213,6 +220,7 @@ impl Default for LayoutSnapshot { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum UiRegion { + Header, Frame, Content, FileTree, @@ -7992,7 +8000,8 @@ impl ChatApp { | UiRegion::Status | UiRegion::Chat | UiRegion::Content - | UiRegion::Frame => { + | UiRegion::Frame + | UiRegion::Header => { if self.focus_panel(FocusedPanel::Chat) { self.auto_scroll .on_user_scroll(amount, self.viewport_height); @@ -8036,7 +8045,8 @@ impl ChatApp { | UiRegion::Status | UiRegion::Chat | UiRegion::Content - | UiRegion::Frame => { + | UiRegion::Frame + | UiRegion::Header => { self.focus_panel(FocusedPanel::Chat); self.set_input_mode(InputMode::Normal); } diff --git a/crates/owlen-tui/src/glass.rs b/crates/owlen-tui/src/glass.rs new file mode 100644 index 0000000..9f23b5c --- /dev/null +++ b/crates/owlen-tui/src/glass.rs @@ -0,0 +1,144 @@ +use owlen_core::theme::Theme; +use ratatui::style::Color; + +#[derive(Clone, Copy)] +pub struct GlassPalette { + pub active: Color, + pub inactive: Color, + pub highlight: Color, + pub track: Color, + pub label: Color, + pub shadow: Color, + pub context_stops: [Color; 3], + pub usage_stops: [Color; 3], +} + +impl GlassPalette { + pub fn for_theme(theme: &Theme) -> Self { + let luminance = color_luminance(theme.background); + if luminance < 0.5 { + Self { + active: Color::Rgb(26, 28, 40), + inactive: Color::Rgb(18, 20, 30), + highlight: Color::Rgb(32, 35, 48), + track: Color::Rgb(35, 38, 50), + label: Color::Rgb(241, 245, 249), + shadow: Color::Rgb(8, 9, 16), + context_stops: [ + Color::Rgb(56, 189, 248), + Color::Rgb(250, 204, 21), + Color::Rgb(248, 113, 113), + ], + usage_stops: [ + Color::Rgb(34, 211, 238), + Color::Rgb(250, 204, 21), + Color::Rgb(248, 113, 113), + ], + } + } else { + Self { + active: Color::Rgb(242, 247, 255), + inactive: Color::Rgb(229, 235, 250), + highlight: Color::Rgb(224, 230, 248), + track: Color::Rgb(203, 210, 230), + label: Color::Rgb(31, 41, 55), + shadow: Color::Rgb(200, 205, 220), + context_stops: [ + Color::Rgb(59, 130, 246), + Color::Rgb(234, 179, 8), + Color::Rgb(239, 68, 68), + ], + usage_stops: [ + Color::Rgb(20, 184, 166), + Color::Rgb(245, 158, 11), + Color::Rgb(239, 68, 68), + ], + } + } + } +} + +pub fn gradient_color(stops: &[Color; 3], t: f64) -> Color { + let clamped = t.clamp(0.0, 1.0); + let segments = stops.len() - 1; + let scaled = clamped * segments as f64; + let index = scaled.floor() as usize; + let frac = scaled - index as f64; + let start = stops[index.min(stops.len() - 1)]; + let end = stops[(index + 1).min(stops.len() - 1)]; + let (sr, sg, sb) = color_to_rgb(start); + let (er, eg, eb) = color_to_rgb(end); + let mix = |a: u8, b: u8| -> u8 { (a as f64 + (b as f64 - a as f64) * frac).round() as u8 }; + Color::Rgb(mix(sr, er), mix(sg, eg), mix(sb, eb)) +} + +fn color_luminance(color: Color) -> f64 { + let (r, g, b) = color_to_rgb(color); + let r = r as f64 / 255.0; + let g = g as f64 / 255.0; + let b = b as f64 / 255.0; + 0.2126 * r + 0.7152 * g + 0.0722 * b +} + +fn color_to_rgb(color: Color) -> (u8, u8, u8) { + match color { + Color::Reset => (0, 0, 0), + Color::Black => (0, 0, 0), + Color::Red => (205, 49, 49), + Color::Green => (49, 205, 49), + Color::Yellow => (205, 198, 49), + Color::Blue => (49, 49, 205), + Color::Magenta => (205, 49, 205), + Color::Cyan => (49, 205, 205), + Color::Gray => (170, 170, 170), + Color::DarkGray => (100, 100, 100), + Color::LightRed => (255, 128, 128), + Color::LightGreen => (144, 238, 144), + Color::LightYellow => (255, 255, 170), + Color::LightBlue => (173, 216, 230), + Color::LightMagenta => (255, 182, 255), + Color::LightCyan => (175, 238, 238), + Color::White => (255, 255, 255), + Color::Rgb(r, g, b) => (r, g, b), + Color::Indexed(idx) => indexed_to_rgb(idx), + } +} + +fn indexed_to_rgb(idx: u8) -> (u8, u8, u8) { + match idx { + 0 => (0, 0, 0), + 1 => (128, 0, 0), + 2 => (0, 128, 0), + 3 => (128, 128, 0), + 4 => (0, 0, 128), + 5 => (128, 0, 128), + 6 => (0, 128, 128), + 7 => (192, 192, 192), + 8 => (128, 128, 128), + 9 => (255, 0, 0), + 10 => (0, 255, 0), + 11 => (255, 255, 0), + 12 => (92, 92, 255), + 13 => (255, 0, 255), + 14 => (0, 255, 255), + 15 => (255, 255, 255), + 16..=231 => { + let idx = idx - 16; + let r = idx / 36; + let g = (idx % 36) / 6; + let b = idx % 6; + let convert = |component: u8| { + if component == 0 { + 0 + } else { + component.saturating_mul(40).saturating_add(55) + } + }; + (convert(r), convert(g), convert(b)) + } + 232..=255 => { + let shade = 8 + (idx - 232) * 10; + (shade, shade, shade) + } + } +} diff --git a/crates/owlen-tui/src/lib.rs b/crates/owlen-tui/src/lib.rs index e0ce4e8..4db986c 100644 --- a/crates/owlen-tui/src/lib.rs +++ b/crates/owlen-tui/src/lib.rs @@ -20,6 +20,7 @@ pub mod code_app; pub mod commands; pub mod config; pub mod events; +pub(crate) mod glass; pub mod highlight; pub mod model_info_panel; pub mod slash; diff --git a/crates/owlen-tui/src/ui.rs b/crates/owlen-tui/src/ui.rs index 31bb77d..05c8667 100644 --- a/crates/owlen-tui/src/ui.rs +++ b/crates/owlen-tui/src/ui.rs @@ -4,6 +4,7 @@ use ratatui::Frame; use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span}; +use ratatui::widgets::block::Padding; use ratatui::widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Wrap}; use serde_json; use std::collections::{HashMap, HashSet}; @@ -16,6 +17,7 @@ use crate::chat_app::{ ChatApp, ContextUsage, HELP_TAB_COUNT, LayoutSnapshot, MIN_MESSAGE_CARD_WIDTH, MessageRenderContext, }; +use crate::glass::{GlassPalette, gradient_color}; use crate::highlight; use crate::state::{ CodePane, EditorTab, FileFilterMode, FileNode, KeymapProfile, LayoutNode, PaletteGroup, PaneId, @@ -26,12 +28,125 @@ use crate::widgets::model_picker::render_model_picker; use owlen_core::theme::Theme; use owlen_core::types::Role; use owlen_core::ui::{FocusedPanel, InputMode, RoleLabelDisplay}; -use owlen_core::usage::{UsageBand, UsageSnapshot, UsageWindow, WindowMetrics}; +use owlen_core::usage::{UsageBand, UsageSnapshot, UsageWindow}; use textwrap::wrap; const PRIVACY_TAB_INDEX: usize = HELP_TAB_COUNT - 1; const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ProgressBand { + Normal, + Warning, + Critical, +} + +impl ProgressBand { + fn color(self, theme: &Theme) -> Color { + match self { + ProgressBand::Normal => theme.info, + ProgressBand::Warning => Color::Yellow, + ProgressBand::Critical => theme.error, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +struct GaugeDescriptor { + title: String, + detail: String, + percent_label: String, + ratio: f64, + band: ProgressBand, +} + +fn progress_band_color(band: ProgressBand, theme: &Theme) -> Color { + band.color(theme) +} + +fn context_progress_band(ratio: f64) -> ProgressBand { + if ratio >= 0.85 { + ProgressBand::Critical + } else if ratio >= 0.60 { + ProgressBand::Warning + } else { + ProgressBand::Normal + } +} + +fn usage_progress_band(band: UsageBand) -> ProgressBand { + match band { + UsageBand::Normal => ProgressBand::Normal, + UsageBand::Warning => ProgressBand::Warning, + UsageBand::Critical => ProgressBand::Critical, + } +} + +fn context_usage_descriptor(usage: ContextUsage) -> Option { + if usage.context_window == 0 { + return None; + } + + let window = usage.context_window as f64; + let ratio = (usage.prompt_tokens as f64 / window).clamp(0.0, 1.0); + let percent = (ratio * 100.0).round(); + let used = format_token_short(usage.prompt_tokens as u64); + let capacity = format_token_short(usage.context_window as u64); + Some(GaugeDescriptor { + title: "Context".to_string(), + detail: format!("{used} / {capacity}"), + percent_label: format!("{percent:.0}%"), + ratio, + band: context_progress_band(ratio), + }) +} + +fn usage_gauge_descriptor( + snapshot: &UsageSnapshot, + window: UsageWindow, +) -> Option { + let metrics = snapshot.window(window); + if metrics.total_tokens == 0 && metrics.quota_tokens.is_none() { + return None; + } + + let (title, shorthand) = match window { + UsageWindow::Hour => ("Cloud hour", "hr"), + UsageWindow::Week => ("Cloud week", "wk"), + }; + + let detail; + let percent_label; + let ratio; + + if let Some(quota) = metrics.quota_tokens { + if quota == 0 { + ratio = 0.0; + detail = "No quota".to_string(); + percent_label = "0%".to_string(); + } else { + ratio = (metrics.total_tokens as f64 / quota as f64).clamp(0.0, 1.0); + let used = format_token_short(metrics.total_tokens); + let quota_text = format_token_short(quota); + let percent = (ratio * 100.0).round(); + detail = format!("{used} / {quota_text}"); + percent_label = format!("{percent:.0}%"); + } + } else { + ratio = 0.0; + detail = format!("{} tokens", format_token_short(metrics.total_tokens)); + percent_label = "–".to_string(); + } + + Some(GaugeDescriptor { + title: format!("{title}"), + detail: format!("{detail} · {shorthand}"), + percent_label, + ratio, + band: usage_progress_band(metrics.band()), + }) +} + fn focus_beacon_span(is_active: bool, is_focused: bool, theme: &Theme) -> Span<'static> { if !is_active { return Span::styled(" ", Style::default().fg(theme.unfocused_beacon_fg)); @@ -188,69 +303,503 @@ mod focus_tests { #[cfg(test)] mod context_usage_tests { use super::*; - use ratatui::style::{Color, Modifier}; - - fn theme() -> Theme { - Theme::default() - } - #[test] - fn context_badge_formats_label_and_highlights() { - let theme = theme(); + fn context_descriptor_formats_values() { let usage = ContextUsage { prompt_tokens: 2600, completion_tokens: 0, - total_tokens: 2600, context_window: 8000, }; - let (label, style) = context_usage_badge(usage, &theme).expect("badge should render"); - assert_eq!(label, "Context 2.6k / 8k (33%)"); - assert_eq!(style.fg, Some(theme.info)); - assert!(style.add_modifier.contains(Modifier::BOLD)); + let descriptor = context_usage_descriptor(usage).expect("descriptor should render"); + assert_eq!(descriptor.title, "Context"); + assert_eq!(descriptor.detail, "2.6k / 8k"); + assert_eq!(descriptor.percent_label, "33%"); + assert!((descriptor.ratio - 0.325).abs() < 1e-6); + assert_eq!(descriptor.band, ProgressBand::Normal); } #[test] - fn context_badge_warns_near_limits() { - let theme = theme(); + fn context_descriptor_warns_near_limits() { let usage = ContextUsage { prompt_tokens: 7000, completion_tokens: 0, - total_tokens: 7000, context_window: 10000, }; - let (_, style) = context_usage_badge(usage, &theme).expect("badge should render"); - assert_eq!(style.fg, Some(Color::Yellow)); + let descriptor = context_usage_descriptor(usage).expect("descriptor should render"); + assert_eq!(descriptor.band, ProgressBand::Warning); + assert_eq!(descriptor.percent_label, "70%"); } #[test] - fn context_badge_flags_danger_zone() { - let theme = theme(); + fn context_descriptor_flags_danger_zone() { let usage = ContextUsage { prompt_tokens: 9000, completion_tokens: 0, - total_tokens: 9000, context_window: 10000, }; - let (_, style) = context_usage_badge(usage, &theme).expect("badge should render"); - assert_eq!(style.fg, Some(theme.error)); + let descriptor = context_usage_descriptor(usage).expect("descriptor should render"); + assert_eq!(descriptor.band, ProgressBand::Critical); + assert_eq!(descriptor.percent_label, "90%"); } #[test] - fn context_badge_handles_zero_usage() { - let theme = theme(); + fn context_descriptor_handles_zero_usage() { let usage = ContextUsage { prompt_tokens: 0, completion_tokens: 0, - total_tokens: 0, context_window: 32000, }; - let (label, style) = context_usage_badge(usage, &theme).expect("badge should render"); - assert_eq!(label, "Context 0 / 32k (0%)"); - assert_eq!(style.fg, Some(theme.info)); + let descriptor = context_usage_descriptor(usage).expect("descriptor should render"); + assert_eq!(descriptor.detail, "0 / 32k"); + assert_eq!(descriptor.percent_label, "0%"); + assert_eq!(descriptor.band, ProgressBand::Normal); + } +} + +fn render_body_container(frame: &mut Frame<'_>, area: Rect, palette: &GlassPalette) -> Rect { + if area.width == 0 || area.height == 0 { + return area; + } + + if area.width > 2 && area.height > 2 { + let shadow_area = Rect::new( + area.x.saturating_add(1), + area.y.saturating_add(1), + area.width.saturating_sub(1), + area.height.saturating_sub(1), + ); + if shadow_area.width > 0 && shadow_area.height > 0 { + frame.render_widget( + Block::default().style(Style::default().bg(palette.shadow)), + shadow_area, + ); + } + } + + frame.render_widget(Clear, area); + + let block = Block::default() + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .style(Style::default().bg(palette.active)); + + let inner = block.inner(area); + frame.render_widget(block, area); + inner +} + +fn render_chat_header( + frame: &mut Frame<'_>, + area: Rect, + app: &ChatApp, + palette: &GlassPalette, + theme: &Theme, +) { + if area.width == 0 || area.height == 0 { + return; + } + + frame.render_widget(Clear, area); + + let header_block = Block::default() + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 0)) + .style(Style::default().bg(palette.highlight)); + let highlight_area = header_block.inner(area); + frame.render_widget(header_block, area); + + if highlight_area.width == 0 || highlight_area.height == 0 { + return; + } + + let mut constraints = vec![Constraint::Length(2)]; + if highlight_area.height > 2 { + constraints.push(Constraint::Min(2)); + } + + let rows = Layout::default() + .direction(Direction::Vertical) + .constraints(constraints) + .split(highlight_area); + + render_header_top(frame, rows[0], app, palette, theme); + + if rows.len() > 1 { + render_header_bars(frame, rows[1], app, palette, theme); + } +} + +fn render_header_top( + frame: &mut Frame<'_>, + area: Rect, + app: &ChatApp, + palette: &GlassPalette, + theme: &Theme, +) { + if area.width == 0 || area.height == 0 { + return; + } + + let columns = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(60), Constraint::Percentage(40)]) + .split(area); + + let mut left_spans = Vec::new(); + left_spans.push(Span::styled( + format!(" 🦉 OWLEN v{APP_VERSION} "), + Style::default() + .fg(theme.focused_panel_border) + .add_modifier(Modifier::BOLD), + )); + + let mode_label = match app.get_mode() { + owlen_core::mode::Mode::Chat => "Chat", + owlen_core::mode::Mode::Code => "Code", + }; + left_spans.push(Span::styled( + format!("· Mode {mode_label} "), + Style::default().fg(palette.label), + )); + + let focus_label = match app.focused_panel() { + FocusedPanel::Files => "Files", + FocusedPanel::Chat => "Chat", + FocusedPanel::Thinking => "Thinking", + FocusedPanel::Input => "Input", + FocusedPanel::Code => "Code", + }; + left_spans.push(Span::styled( + format!("· Focus {focus_label}"), + Style::default() + .fg(palette.label) + .add_modifier(Modifier::ITALIC), + )); + + if app.is_agent_running() { + left_spans.push(Span::raw(" ")); + left_spans.push(Span::styled( + "🤖 RUN", + Style::default() + .fg(theme.agent_badge_running_fg) + .bg(theme.agent_badge_running_bg) + .add_modifier(Modifier::BOLD), + )); + } else if app.is_agent_mode() { + left_spans.push(Span::raw(" ")); + left_spans.push(Span::styled( + "🤖 ARM", + Style::default() + .fg(theme.agent_badge_idle_fg) + .bg(theme.agent_badge_idle_bg) + .add_modifier(Modifier::BOLD), + )); + } + + let left_line = spans_within_width(left_spans, columns[0].width); + frame.render_widget( + Paragraph::new(left_line).style(Style::default().bg(palette.highlight).fg(palette.label)), + columns[0], + ); + + let mut right_spans = Vec::new(); + let provider_display = truncate_with_ellipsis(app.current_provider(), 18); + right_spans.push(Span::styled( + provider_display, + Style::default() + .fg(palette.label) + .add_modifier(Modifier::BOLD), + )); + + let model_label = app.active_model_label(); + if !model_label.is_empty() { + let model_display = truncate_with_ellipsis(&model_label, 24); + right_spans.push(Span::styled( + format!(" · {model_display}"), + Style::default().fg(palette.label), + )); + } + + if app.is_loading() || app.is_streaming() { + let spinner = app.get_loading_indicator(); + let spinner = if spinner.is_empty() { "…" } else { spinner }; + right_spans.push(Span::styled( + format!(" · {spinner} streaming"), + Style::default().fg(progress_band_color(ProgressBand::Normal, theme)), + )); + } + + let right_line = spans_within_width(right_spans, columns[1].width); + frame.render_widget( + Paragraph::new(right_line) + .style(Style::default().bg(palette.highlight).fg(palette.label)) + .alignment(Alignment::Right), + columns[1], + ); +} + +fn render_header_bars( + frame: &mut Frame<'_>, + area: Rect, + app: &ChatApp, + palette: &GlassPalette, + theme: &Theme, +) { + if area.width == 0 || area.height == 0 { + return; + } + + if area.height < 2 { + render_context_column(frame, area, app, palette, theme); + return; + } + + let columns = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(45), Constraint::Percentage(55)]) + .split(area); + + render_context_column(frame, columns[0], app, palette, theme); + render_usage_column(frame, columns[1], app, palette, theme); +} + +fn render_context_column( + frame: &mut Frame<'_>, + area: Rect, + app: &ChatApp, + palette: &GlassPalette, + theme: &Theme, +) { + if area.width == 0 || area.height == 0 { + return; + } + + let descriptor = app + .context_usage_with_fallback() + .and_then(context_usage_descriptor); + + match descriptor { + Some(descriptor) => { + if area.height < 2 { + render_gauge_compact(frame, area, &descriptor, palette); + } else { + render_gauge( + frame, + area, + &descriptor, + &palette.context_stops, + palette, + theme, + ); + } + } + None => { + frame.render_widget( + Paragraph::new("Context metrics not available") + .style(Style::default().bg(palette.highlight).fg(palette.label)) + .wrap(Wrap { trim: true }), + area, + ); + } + } +} + +fn render_usage_column( + frame: &mut Frame<'_>, + area: Rect, + app: &ChatApp, + palette: &GlassPalette, + theme: &Theme, +) { + if area.width == 0 || area.height == 0 { + return; + } + + let descriptors: Vec = app + .usage_snapshot() + .into_iter() + .flat_map(|snapshot| { + [ + usage_gauge_descriptor(snapshot, UsageWindow::Hour), + usage_gauge_descriptor(snapshot, UsageWindow::Week), + ] + }) + .flatten() + .collect(); + + if descriptors.is_empty() { + frame.render_widget( + Paragraph::new("Cloud usage pending") + .style(Style::default().bg(palette.highlight).fg(palette.label)) + .wrap(Wrap { trim: true }), + area, + ); + return; + } + + let bottom = area.y.saturating_add(area.height); + let mut cursor_y = area.y; + + for descriptor in descriptors { + if cursor_y >= bottom { + break; + } + let remaining = bottom - cursor_y; + if remaining < 2 { + frame.render_widget( + Paragraph::new(format!("{} {}", descriptor.title, descriptor.percent_label)) + .style(Style::default().bg(palette.highlight).fg(palette.label)) + .wrap(Wrap { trim: true }), + Rect::new(area.x, cursor_y, area.width, remaining), + ); + break; + } + + let gauge_area = Rect::new(area.x, cursor_y, area.width, 2); + render_gauge( + frame, + gauge_area, + &descriptor, + &palette.usage_stops, + palette, + theme, + ); + cursor_y = cursor_y.saturating_add(2); + } +} + +fn render_gauge( + frame: &mut Frame<'_>, + area: Rect, + descriptor: &GaugeDescriptor, + stops: &[Color; 3], + palette: &GlassPalette, + theme: &Theme, +) { + if area.height < 2 || area.width < 4 { + render_gauge_compact(frame, area, descriptor, palette); + return; + } + + let label_color = progress_band_color(descriptor.band, theme); + + let label_area = Rect::new(area.x, area.y, area.width, 1); + let bar_area = Rect::new(area.x, area.y + 1, area.width, 1); + + let label_line = spans_within_width( + vec![ + Span::styled( + descriptor.title.clone(), + Style::default() + .fg(label_color) + .add_modifier(Modifier::BOLD), + ), + Span::styled( + descriptor.detail.clone(), + Style::default().fg(palette.label), + ), + Span::styled( + descriptor.percent_label.clone(), + Style::default() + .fg(label_color) + .add_modifier(Modifier::BOLD), + ), + ], + label_area.width, + ); + + frame.render_widget( + Paragraph::new(label_line).style(Style::default().bg(palette.highlight).fg(palette.label)), + label_area, + ); + + draw_gradient_bar( + frame, + bar_area, + descriptor.ratio, + stops, + palette, + &descriptor.percent_label, + ); +} + +fn render_gauge_compact( + frame: &mut Frame<'_>, + area: Rect, + descriptor: &GaugeDescriptor, + palette: &GlassPalette, +) { + if area.width == 0 || area.height == 0 { + return; + } + + frame.render_widget( + Paragraph::new(format!( + "{} · {} ({})", + descriptor.title, descriptor.detail, descriptor.percent_label + )) + .style(Style::default().bg(palette.highlight).fg(palette.label)) + .wrap(Wrap { trim: true }), + area, + ); +} + +fn draw_gradient_bar( + frame: &mut Frame<'_>, + area: Rect, + ratio: f64, + stops: &[Color; 3], + palette: &GlassPalette, + percent_label: &str, +) { + if area.width == 0 || area.height == 0 { + return; + } + + let ratio = ratio.clamp(0.0, 1.0); + let width = area.width; + let mut filled_width = ((width as f64) * ratio).round() as u16; + filled_width = filled_width.min(width); + + { + let buffer = frame.buffer_mut(); + for offset in 0..width { + let x = area.x + offset; + let is_filled = offset < filled_width; + let color = if filled_width == 0 || !is_filled { + palette.track + } else if filled_width <= 1 { + gradient_color(stops, ratio) + } else { + let segment = offset.min(filled_width - 1); + let pos = segment as f64 / (filled_width - 1) as f64; + gradient_color(stops, pos) + }; + + buffer[(x, area.y)] + .set_symbol(" ") + .set_bg(color) + .set_fg(color); + } + } + + let buffer = frame.buffer_mut(); + let label_chars: Vec = percent_label.chars().collect(); + if !label_chars.is_empty() && label_chars.len() as u16 <= width { + let start = area.x + (width - label_chars.len() as u16) / 2; + for (idx, ch) in label_chars.iter().enumerate() { + let x = start + idx as u16; + if x >= area.x + width { + break; + } + buffer[(x, area.y)] + .set_symbol(&ch.to_string()) + .set_fg(palette.label); + } } } @@ -260,25 +809,53 @@ pub fn render_chat(frame: &mut Frame<'_>, app: &mut ChatApp) { // Set terminal background color let theme = app.theme().clone(); - let background_block = Block::default().style(Style::default().bg(theme.background)); + let palette = GlassPalette::for_theme(&theme); let frame_area = frame.area(); - frame.render_widget(background_block, frame_area); + frame.render_widget( + Block::default().style(Style::default().bg(theme.background)), + frame_area, + ); - let title_line = Line::from(vec![Span::styled( - format!(" 🦉 OWLEN v{} – AI Assistant ", APP_VERSION), - Style::default() - .fg(theme.focused_panel_border) - .add_modifier(Modifier::BOLD), - )]); - let main_block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.unfocused_panel_border)) - .style(Style::default().bg(theme.background).fg(theme.text)) - .title(title_line); + let mut header_height = if frame_area.height >= 14 { 6 } else { 4 }; + if frame_area.height <= header_height { + header_height = frame_area.height.saturating_sub(1); + } + let header_height = header_height.max(3).min(frame_area.height); - let content_area = main_block.inner(frame_area); + let segments = if frame_area.height <= header_height { + Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(frame_area.height)]) + .split(frame_area) + } else { + Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(header_height), Constraint::Min(1)]) + .split(frame_area) + }; + + if segments.is_empty() { + app.set_layout_snapshot(LayoutSnapshot::new(frame_area, frame_area)); + return; + } + + let (header_area, body_area) = if segments.len() == 1 { + (Rect::new(0, 0, 0, 0), segments[0]) + } else { + (segments[0], segments[1]) + }; + + if header_area.width > 0 && header_area.height > 0 { + render_chat_header(frame, header_area, app, &palette, &theme); + } + + let content_area = render_body_container(frame, body_area, &palette); let mut snapshot = LayoutSnapshot::new(frame_area, content_area); - frame.render_widget(main_block, frame_area); + snapshot.header_panel = if header_area.width > 0 && header_area.height > 0 { + Some(header_area) + } else { + None + }; if content_area.width == 0 || content_area.height == 0 { app.set_layout_snapshot(snapshot); @@ -1583,27 +2160,38 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { )); } - if let Some(usage) = app.context_usage_with_fallback() { - if let Some((label, style)) = context_usage_badge(usage, &theme) { - title_spans.push(Span::raw(" · ")); - title_spans.push(Span::styled(label, style)); - } - } - title_spans.push(Span::raw(" ")); title_spans.push(Span::styled( "PgUp/PgDn scroll · g/G jump · s save · Ctrl+2 focus", panel_hint_style(has_focus, &theme), )); + let palette = GlassPalette::for_theme(&theme); let chat_block = Block::default() - .borders(Borders::ALL) - .border_style(panel_border_style(true, has_focus, &theme)) - .style(Style::default().bg(theme.background).fg(theme.text)) - .title(Line::from(title_spans)); + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .style( + Style::default() + .bg(if has_focus { + palette.active + } else { + palette.inactive + }) + .fg(theme.text), + ) + .title(Line::from(title_spans)) + .title_style(Style::default().fg(theme.pane_header_active)); let paragraph = Paragraph::new(lines) - .style(Style::default().bg(theme.background).fg(theme.text)) + .style( + Style::default() + .bg(if has_focus { + palette.active + } else { + palette.inactive + }) + .fg(theme.text), + ) .block(chat_block) .scroll((scroll_position, 0)); @@ -1711,14 +2299,32 @@ fn render_thinking(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { panel_hint_style(has_focus, &theme), )); + let palette = GlassPalette::for_theme(&theme); let paragraph = Paragraph::new(lines) - .style(Style::default().bg(theme.background)) + .style( + Style::default() + .bg(if has_focus { + palette.active + } else { + palette.inactive + }) + .fg(theme.placeholder), + ) .block( Block::default() .title(Line::from(title_spans)) - .borders(Borders::ALL) - .border_style(panel_border_style(true, has_focus, &theme)) - .style(Style::default().bg(theme.background).fg(theme.text)), + .title_style(Style::default().fg(theme.pane_header_active)) + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .style( + Style::default() + .bg(if has_focus { + palette.active + } else { + palette.inactive + }) + .fg(theme.text), + ), ) .scroll((scroll_position, 0)) .wrap(Wrap { trim: false }); @@ -1917,14 +2523,32 @@ fn render_agent_actions(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { panel_hint_style(has_focus, &theme), )); + let palette = GlassPalette::for_theme(&theme); let paragraph = Paragraph::new(lines) - .style(Style::default().bg(theme.background)) + .style( + Style::default() + .bg(if has_focus { + palette.active + } else { + palette.inactive + }) + .fg(theme.text), + ) .block( Block::default() .title(Line::from(title_spans)) - .borders(Borders::ALL) - .border_style(panel_border_style(true, has_focus, &theme)) - .style(Style::default().bg(theme.background).fg(theme.text)), + .title_style(Style::default().fg(theme.pane_header_active)) + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .style( + Style::default() + .bg(if has_focus { + palette.active + } else { + palette.inactive + }) + .fg(theme.text), + ), ) .wrap(Wrap { trim: false }); @@ -1975,11 +2599,21 @@ fn render_input(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { )); } + let palette = GlassPalette::for_theme(&theme); + let base_style = Style::default() + .bg(if has_focus { + palette.active + } else { + palette.inactive + }) + .fg(theme.text); + let input_block = Block::default() .title(Line::from(title_spans)) - .borders(Borders::ALL) - .border_style(panel_border_style(is_active, has_focus, &theme)) - .style(Style::default().bg(theme.background).fg(theme.text)); + .title_style(Style::default().fg(theme.pane_header_active)) + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .style(base_style); if matches!(app.mode(), InputMode::Editing) { // Use the textarea directly to preserve selection state @@ -1987,6 +2621,7 @@ fn render_input(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { let textarea = app.textarea_mut(); textarea.set_block(input_block.clone()); textarea.set_hard_tab_indent(false); + textarea.set_style(base_style); render_editable_textarea(frame, area, textarea, true, show_cursor, &theme); } else if matches!(app.mode(), InputMode::Visual) { // In visual mode, render textarea in read-only mode with selection @@ -1994,6 +2629,7 @@ fn render_input(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { let textarea = app.textarea_mut(); textarea.set_block(input_block.clone()); textarea.set_hard_tab_indent(false); + textarea.set_style(base_style); render_editable_textarea(frame, area, textarea, true, show_cursor, &theme); } else if matches!(app.mode(), InputMode::Command) { // In command mode, show the command buffer with : prefix @@ -2006,7 +2642,7 @@ fn render_input(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { ))]; let paragraph = Paragraph::new(lines) - .style(Style::default().bg(theme.background)) + .style(base_style) .block(input_block) .wrap(Wrap { trim: false }); @@ -2027,7 +2663,7 @@ fn render_input(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { }; let paragraph = Paragraph::new(lines) - .style(Style::default().bg(theme.background)) + .style(base_style) .block(input_block) .wrap(Wrap { trim: false }); @@ -2054,6 +2690,7 @@ fn system_status_message(app: &ChatApp) -> String { fn render_system_output(frame: &mut Frame<'_>, area: Rect, app: &ChatApp, message: &str) { let theme = app.theme(); + let palette = GlassPalette::for_theme(theme); let color = if message.starts_with("Error:") { theme.error @@ -2074,16 +2711,17 @@ fn render_system_output(frame: &mut Frame<'_>, area: Rect, app: &ChatApp, messag }; let paragraph = Paragraph::new(text_lines) - .style(Style::default().bg(theme.background)) + .style(Style::default().bg(palette.highlight).fg(color)) .block( Block::default() .title(Span::styled( " System/Status ", Style::default().fg(theme.info).add_modifier(Modifier::BOLD), )) - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.unfocused_panel_border)) - .style(Style::default().bg(theme.background).fg(theme.text)), + .title_style(Style::default().fg(theme.info)) + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .style(Style::default().bg(palette.highlight).fg(theme.text)), ) .wrap(Wrap { trim: false }); @@ -2092,6 +2730,7 @@ fn render_system_output(frame: &mut Frame<'_>, area: Rect, app: &ChatApp, messag fn render_debug_log_panel(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { let theme = app.theme(); + let palette = GlassPalette::for_theme(theme); frame.render_widget(Clear, area); let title = Line::from(vec![ @@ -2110,10 +2749,11 @@ fn render_debug_log_panel(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { ]); let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.focused_panel_border)) - .style(Style::default().bg(theme.background).fg(theme.text)) - .title(title); + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .style(Style::default().bg(palette.active).fg(theme.text)) + .title(title) + .title_style(Style::default().fg(theme.pane_header_active)); let inner = block.inner(area); frame.render_widget(block, area); @@ -2184,7 +2824,7 @@ fn render_debug_log_panel(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { let paragraph = Paragraph::new(lines) .wrap(Wrap { trim: true }) .alignment(Alignment::Left) - .style(Style::default().bg(theme.background)); + .style(Style::default().bg(palette.active).fg(theme.text)); frame.render_widget(paragraph, inner); } @@ -2222,7 +2862,7 @@ fn calculate_wrapped_line_count<'a, I>(lines: I, available_width: u16) -> usize where I: IntoIterator, { - let content_width = available_width.saturating_sub(2) as usize; // subtract block borders + let content_width = available_width.saturating_sub(4) as usize; // account for padded panel chrome let mut total = 0usize; let mut seen = false; @@ -2241,10 +2881,14 @@ where fn render_status(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { let theme = app.theme(); + let palette = GlassPalette::for_theme(theme); + + frame.render_widget(Clear, area); + let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.unfocused_panel_border)) - .style(Style::default().bg(theme.status_background)); + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 0, 0)) + .style(Style::default().bg(palette.highlight)); let inner = block.inner(area); frame.render_widget(block, area); @@ -2343,7 +2987,7 @@ fn render_status(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { let left_paragraph = Paragraph::new(Line::from(left_spans)) .alignment(Alignment::Left) - .style(Style::default().bg(theme.status_background).fg(theme.text)); + .style(Style::default().bg(palette.highlight).fg(palette.label)); frame.render_widget(left_paragraph, columns[0]); let file_tree = app.file_tree(); @@ -2379,7 +3023,7 @@ fn render_status(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { let mid_paragraph = Paragraph::new(mid_parts.join(" · ")) .alignment(Alignment::Center) - .style(Style::default().bg(theme.status_background).fg(theme.text)); + .style(Style::default().bg(palette.highlight).fg(palette.label)); frame.render_widget(mid_paragraph, columns[1]); let provider = app.current_provider(); @@ -2388,7 +3032,9 @@ fn render_status(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { let model_display = truncate_with_ellipsis(&model_label, 24); let mut right_spans = vec![Span::styled( format!("{} ▸ {}", provider_display, model_display), - Style::default().fg(theme.text).add_modifier(Modifier::BOLD), + Style::default() + .fg(palette.label) + .add_modifier(Modifier::BOLD), )]; if app.is_loading() || app.is_streaming() { @@ -2396,7 +3042,7 @@ fn render_status(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { let spinner = if spinner.is_empty() { "…" } else { spinner }; right_spans.push(Span::styled( format!(" · {} streaming", spinner), - Style::default().fg(theme.info), + Style::default().fg(progress_band_color(ProgressBand::Normal, theme)), )); right_spans.push(Span::styled( " · p:Pause r:Resume s:Stop", @@ -2406,18 +3052,6 @@ fn render_status(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { )); } - if let Some(usage) = app.context_usage_with_fallback() { - if let Some((label, style)) = context_usage_badge(usage, theme) { - right_spans.push(Span::styled(format!(" · {}", label), style)); - } - } - - if let Some(snapshot) = app.usage_snapshot() { - for span in usage_badge_spans(snapshot, theme) { - right_spans.push(span); - } - } - right_spans.push(Span::styled( " · LSP:✓", Style::default() @@ -2428,110 +3062,10 @@ fn render_status(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { let right_line = spans_within_width(right_spans, columns[2].width); let right_paragraph = Paragraph::new(right_line) .alignment(Alignment::Right) - .style(Style::default().bg(theme.status_background).fg(theme.text)); + .style(Style::default().bg(palette.highlight).fg(palette.label)); frame.render_widget(right_paragraph, columns[2]); } -fn context_usage_badge(usage: ContextUsage, theme: &Theme) -> Option<(String, Style)> { - let context_window = usage.context_window.max(1); - let ratio = usage.prompt_tokens as f64 / context_window as f64; - let percent = ((ratio * 100.0).round() as u32).min(100); - let used = format_token_short(usage.prompt_tokens as u64); - let window = format_token_short(context_window as u64); - let label = format!("Context {} / {} ({}%)", used, window, percent); - - let style = if ratio < 0.60 { - Style::default().fg(theme.info).add_modifier(Modifier::BOLD) - } else if ratio < 0.85 { - Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD) - } else { - Style::default() - .fg(theme.error) - .add_modifier(Modifier::BOLD) - }; - - Some((label, style)) -} - -fn usage_badge_spans(snapshot: &UsageSnapshot, theme: &Theme) -> Vec> { - let mut spans = Vec::new(); - let provider_label = ChatApp::provider_display_name(&snapshot.provider); - - let hour_metrics = snapshot.window(UsageWindow::Hour); - let week_metrics = snapshot.window(UsageWindow::Week); - - if hour_metrics.quota_tokens.is_none() - && hour_metrics.total_tokens == 0 - && week_metrics.quota_tokens.is_none() - && week_metrics.total_tokens == 0 - { - return spans; - } - - spans.push(Span::styled( - format!(" · {} usage", provider_label), - Style::default() - .fg(theme.placeholder) - .add_modifier(Modifier::DIM), - )); - - if let Some(span) = usage_window_span("hr", hour_metrics, theme) { - spans.push(span); - } - if let Some(span) = usage_window_span("wk", week_metrics, theme) { - spans.push(span); - } - - spans -} - -fn usage_window_span(label: &str, metrics: &WindowMetrics, theme: &Theme) -> Option> { - if metrics.quota_tokens.is_none() && metrics.total_tokens == 0 { - return None; - } - - let used = format_token_short(metrics.total_tokens); - let text = if let Some(quota) = metrics.quota_tokens { - if quota == 0 { - format!(" · {} {} tokens", label, used) - } else if let Some(percent_ratio) = metrics.percent_of_quota() { - let quota_text = format_token_short(quota); - let percent_text = format_percent_short((percent_ratio * 100.0).min(999.9)); - format!(" · {} {}/{} ({}%)", label, used, quota_text, percent_text) - } else { - let quota_text = format_token_short(quota); - format!(" · {} {}/{}", label, used, quota_text) - } - } else { - format!(" · {} {} tokens", label, used) - }; - - let style = match metrics.band() { - UsageBand::Normal => Style::default().fg(theme.info).add_modifier(Modifier::BOLD), - UsageBand::Warning => Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD), - UsageBand::Critical => Style::default() - .fg(theme.error) - .add_modifier(Modifier::BOLD), - }; - - Some(Span::styled(text, style)) -} - -fn format_percent_short(percent: f64) -> String { - let clamped = percent.max(0.0); - if clamped >= 100.0 || clamped == 0.0 { - format!("{clamped:.0}") - } else if clamped >= 10.0 { - format!("{clamped:.0}") - } else { - format!("{clamped:.1}") - } -} - pub(crate) fn format_token_short(value: u64) -> String { if value >= 1_000_000_000 { format_compact(value as f64 / 1_000_000_000.0, "B") @@ -3283,15 +3817,13 @@ fn render_consent_dialog(frame: &mut Frame<'_>, app: &ChatApp) { fn render_privacy_settings(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { let theme = app.theme(); + let palette = GlassPalette::for_theme(theme); let config = app.config(); + if area.width == 0 || area.height == 0 { + return; + } - let block = Block::default() - .title("Privacy Settings") - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.unfocused_panel_border)) - .style(Style::default().bg(theme.background).fg(theme.text)); - let inner = block.inner(area); - frame.render_widget(block, area); + frame.render_widget(Clear, area); let remote_search_enabled = config.privacy.enable_remote_search && config.tools.web_search.enabled; @@ -3345,16 +3877,46 @@ fn render_privacy_settings(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { let paragraph = Paragraph::new(lines) .wrap(Wrap { trim: true }) - .style(Style::default().bg(theme.background).fg(theme.text)); - frame.render_widget(paragraph, inner); + .style(Style::default().bg(palette.active).fg(palette.label)); + frame.render_widget(paragraph, area); } fn render_help(frame: &mut Frame<'_>, app: &ChatApp) { let theme = app.theme(); + let palette = GlassPalette::for_theme(theme); let profile = app.current_keymap_profile(); let area = centered_rect(75, 70, frame.area()); + if area.width == 0 || area.height == 0 { + return; + } + + if area.width > 2 && area.height > 2 { + let shadow = Rect::new( + area.x.saturating_add(1), + area.y.saturating_add(1), + area.width.saturating_sub(1), + area.height.saturating_sub(1), + ); + if shadow.width > 0 && shadow.height > 0 { + frame.render_widget( + Block::default().style(Style::default().bg(palette.shadow)), + shadow, + ); + } + } + frame.render_widget(Clear, area); + let container = Block::default() + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .style(Style::default().bg(palette.active).fg(palette.label)); + let inner = container.inner(area); + frame.render_widget(container, area); + if inner.width == 0 || inner.height == 0 { + return; + } + let tab_index = app.help_tab_index(); let tabs = [ "Navigation", @@ -3863,29 +4425,20 @@ fn render_help(frame: &mut Frame<'_>, app: &ChatApp) { Constraint::Min(0), // Content Constraint::Length(2), // Navigation hint ]) - .split(area); + .split(inner); // Render tabs - let tabs_block = Block::default() - .borders(Borders::TOP | Borders::LEFT | Borders::RIGHT) - .border_style(Style::default().fg(theme.unfocused_panel_border)) - .style(Style::default().bg(theme.background).fg(theme.text)); let tabs_para = Paragraph::new(Line::from(tab_spans)) - .style(Style::default().bg(theme.background)) - .block(tabs_block); + .style(Style::default().bg(palette.highlight).fg(palette.label)) + .alignment(Alignment::Center); frame.render_widget(tabs_para, layout[0]); // Render content if tab_index == PRIVACY_TAB_INDEX { render_privacy_settings(frame, layout[1], app); } else { - let content_block = Block::default() - .borders(Borders::LEFT | Borders::RIGHT) - .border_style(Style::default().fg(theme.unfocused_panel_border)) - .style(Style::default().bg(theme.background).fg(theme.text)); - let content_para = Paragraph::new(help_text) - .style(Style::default().bg(theme.background).fg(theme.text)) - .block(content_block); + let content_para = + Paragraph::new(help_text).style(Style::default().bg(palette.active).fg(palette.label)); frame.render_widget(content_para, layout[1]); } @@ -3895,32 +4448,27 @@ fn render_help(frame: &mut Frame<'_>, app: &ChatApp) { Span::styled( "Tab/h/l", Style::default() - .fg(theme.focused_panel_border) + .fg(palette.label) .add_modifier(Modifier::BOLD), ), Span::raw(":Switch "), Span::styled( format!("1-{}", HELP_TAB_COUNT), Style::default() - .fg(theme.focused_panel_border) + .fg(palette.label) .add_modifier(Modifier::BOLD), ), Span::raw(":Jump "), Span::styled( "Esc", Style::default() - .fg(theme.focused_panel_border) + .fg(palette.label) .add_modifier(Modifier::BOLD), ), Span::raw(":Close "), ]); - let nav_block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.unfocused_panel_border)) - .style(Style::default().bg(theme.background).fg(theme.text)); let nav_para = Paragraph::new(nav_hint) - .style(Style::default().bg(theme.background)) - .block(nav_block) + .style(Style::default().bg(palette.highlight).fg(palette.label)) .alignment(Alignment::Center); frame.render_widget(nav_para, layout[2]); } @@ -4065,13 +4613,14 @@ fn render_session_browser(frame: &mut Frame<'_>, app: &ChatApp) { fn render_theme_browser(frame: &mut Frame<'_>, app: &ChatApp) { let theme = app.theme(); + let palette = GlassPalette::for_theme(theme); let area = frame.area(); if area.width == 0 || area.height == 0 { return; } let themes = app.available_themes(); - let current_theme_name = &app.theme().name; + let current_theme_name = &theme.name; let max_width: u16 = 80; let min_width: u16 = 40; @@ -4084,8 +4633,8 @@ fn render_theme_browser(frame: &mut Frame<'_>, app: &ChatApp) { width = width.max(1); let visible_rows = themes.len().clamp(1, 12) as u16; - let mut height = visible_rows.saturating_mul(2).saturating_add(6); - height = height.clamp(6, area.height); + let mut height = visible_rows.saturating_mul(2).saturating_add(8); + height = height.clamp(8, area.height); let x = area.x + (area.width.saturating_sub(width)) / 2; let mut y = area.y + (area.height.saturating_sub(height)) / 3; @@ -4094,29 +4643,47 @@ fn render_theme_browser(frame: &mut Frame<'_>, app: &ChatApp) { } let popup_area = Rect::new(x, y, width, height); + if popup_area.width > 2 && popup_area.height > 2 { + let shadow_area = Rect::new( + popup_area.x.saturating_add(1), + popup_area.y.saturating_add(1), + popup_area.width.saturating_sub(1), + popup_area.height.saturating_sub(1), + ); + if shadow_area.width > 0 && shadow_area.height > 0 { + frame.render_widget( + Block::default().style(Style::default().bg(palette.shadow)), + shadow_area, + ); + } + } + frame.render_widget(Clear, popup_area); - let title_line = Line::from(vec![ + let title_spans = vec![ Span::styled( " Theme Selector ", - Style::default().fg(theme.info).add_modifier(Modifier::BOLD), + Style::default() + .fg(palette.label) + .add_modifier(Modifier::BOLD), ), Span::styled( format!("· Current: {}", current_theme_name), Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::DIM), ), - ]); + ]; - let block = Block::default() - .title(title_line) - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.info)) - .style(Style::default().bg(theme.background).fg(theme.text)); + let container = Block::default() + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .title(Line::from(title_spans)) + .title_style(Style::default().fg(palette.label)) + .style(Style::default().bg(palette.active).fg(palette.label)); - let inner = block.inner(popup_area); - frame.render_widget(block, popup_area); + let inner = container.inner(popup_area); + frame.render_widget(container, popup_area); if inner.width == 0 || inner.height == 0 { return; } @@ -4125,21 +4692,122 @@ fn render_theme_browser(frame: &mut Frame<'_>, app: &ChatApp) { let empty = Paragraph::new(Line::from(Span::styled( "No themes available · Press Esc to close", Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::DIM | Modifier::ITALIC), ))) .alignment(Alignment::Center) - .style(Style::default().bg(theme.background).fg(theme.placeholder)); + .style(Style::default().bg(palette.active).fg(palette.label)); frame.render_widget(empty, inner); return; } + let all_themes = owlen_core::theme::load_all_themes(); + let built_in = owlen_core::theme::built_in_themes(); + let layout = Layout::default() .direction(Direction::Vertical) - .constraints([Constraint::Min(4), Constraint::Length(2)]) + .constraints([Constraint::Min(6), Constraint::Length(2)]) .split(inner); - let built_in = owlen_core::theme::built_in_themes(); + let columns = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(58), Constraint::Percentage(42)]) + .split(layout[0]); + + let left_sections = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(3), Constraint::Min(3)]) + .split(columns[0]); + + let search_query = app.model_search_query().trim().to_string(); + let search_active = !search_query.is_empty(); + let matches = app.visible_model_count(); + + let search_prefix = Style::default() + .fg(palette.label) + .add_modifier(Modifier::DIM); + let bracket_style = Style::default() + .fg(palette.label) + .add_modifier(Modifier::DIM); + let caret_style = if search_active { + Style::default() + .fg(theme.selection_fg) + .add_modifier(Modifier::BOLD) + } else { + Style::default() + .fg(palette.label) + .add_modifier(Modifier::DIM) + }; + + let mut search_spans = Vec::new(); + search_spans.push(Span::styled("Search ▸ ", search_prefix)); + search_spans.push(Span::styled("[", bracket_style)); + search_spans.push(Span::styled(" ", bracket_style)); + + if search_active { + search_spans.push(Span::styled( + search_query.clone(), + Style::default() + .fg(theme.selection_fg) + .add_modifier(Modifier::BOLD), + )); + } else { + search_spans.push(Span::styled( + "Type to search…", + Style::default() + .fg(palette.label) + .add_modifier(Modifier::DIM | Modifier::ITALIC), + )); + } + + search_spans.push(Span::styled(" ", bracket_style)); + search_spans.push(Span::styled("▎", caret_style)); + search_spans.push(Span::styled(" ", bracket_style)); + search_spans.push(Span::styled("]", bracket_style)); + search_spans.push(Span::raw(" ")); + let suffix_label = if search_active { "match" } else { "model" }; + search_spans.push(Span::styled( + format!( + "({} {}{})", + matches, + suffix_label, + if matches == 1 { "" } else { "s" } + ), + Style::default() + .fg(palette.label) + .add_modifier(Modifier::DIM), + )); + + let search_line = Line::from(search_spans); + + let instruction_line = if search_active { + Line::from(vec![ + Span::styled("Backspace", Style::default().fg(palette.label)), + Span::raw(": delete "), + Span::styled("Ctrl+U", Style::default().fg(palette.label)), + Span::raw(": clear "), + Span::styled("Enter", Style::default().fg(palette.label)), + Span::raw(": select "), + Span::styled("Esc", Style::default().fg(palette.label)), + Span::raw(": close"), + ]) + } else { + Line::from(vec![ + Span::styled("Enter", Style::default().fg(palette.label)), + Span::raw(": select "), + Span::styled("Space", Style::default().fg(palette.label)), + Span::raw(": toggle provider "), + Span::styled("Esc", Style::default().fg(palette.label)), + Span::raw(": close"), + ]) + }; + + if left_sections.len() >= 1 { + let search_paragraph = Paragraph::new(vec![search_line, instruction_line]) + .style(Style::default().bg(palette.highlight).fg(palette.label)); + frame.render_widget(search_paragraph, left_sections[0]); + } + let mut items: Vec = Vec::with_capacity(themes.len()); for theme_name in themes.iter() { let is_current = theme_name == current_theme_name; @@ -4154,7 +4822,7 @@ fn render_theme_browser(frame: &mut Frame<'_>, app: &ChatApp) { title.push_str(" ✓"); } - let mut title_style = Style::default().fg(theme.text); + let mut title_style = Style::default().fg(palette.label); if is_current { title_style = title_style .fg(theme.focused_panel_border) @@ -4162,7 +4830,7 @@ fn render_theme_browser(frame: &mut Frame<'_>, app: &ChatApp) { } let metadata_style = Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::DIM); let lines = vec![ @@ -4173,7 +4841,9 @@ fn render_theme_browser(frame: &mut Frame<'_>, app: &ChatApp) { ]), ]; - items.push(ListItem::new(lines).style(Style::default().bg(theme.background))); + items.push( + ListItem::new(lines).style(Style::default().bg(palette.active).fg(palette.label)), + ); } let highlight_style = Style::default() @@ -4187,25 +4857,187 @@ fn render_theme_browser(frame: &mut Frame<'_>, app: &ChatApp) { .min(themes.len().saturating_sub(1)); state.select(Some(selected_index)); - let list = List::new(items) - .highlight_style(highlight_style) - .highlight_symbol(" ") - .style(Style::default().bg(theme.background).fg(theme.text)); + if left_sections.len() >= 2 { + let list = List::new(items) + .highlight_style(highlight_style) + .highlight_symbol(" ") + .style(Style::default().bg(palette.active).fg(palette.label)); - frame.render_stateful_widget(list, layout[0], &mut state); + frame.render_stateful_widget(list, left_sections[1], &mut state); + } + + if columns.len() >= 2 { + let preview_area = columns[1]; + if preview_area.width > 0 && preview_area.height > 0 { + if let Some(selected_name) = themes.get(selected_index) { + let preview_theme = all_themes + .get(selected_name.as_str()) + .cloned() + .or_else(|| all_themes.get(current_theme_name).cloned()) + .unwrap_or_else(|| theme.clone()); + render_theme_preview( + frame, + preview_area, + &preview_theme, + preview_theme.name == theme.name, + ); + } + } + } let footer = Paragraph::new(Line::from(Span::styled( "↑/↓ or j/k: Navigate · Enter: Apply theme · g/G: Top/Bottom · Esc: Cancel", - Style::default().fg(theme.placeholder), + Style::default().fg(palette.label), ))) .alignment(Alignment::Center) - .style(Style::default().bg(theme.background).fg(theme.placeholder)); + .style(Style::default().bg(palette.highlight).fg(palette.label)); frame.render_widget(footer, layout[1]); } +fn render_theme_preview(frame: &mut Frame<'_>, area: Rect, preview_theme: &Theme, is_active: bool) { + if area.width < 10 || area.height < 5 { + return; + } + + frame.render_widget(Clear, area); + let preview_palette = GlassPalette::for_theme(preview_theme); + let mut title = format!("Preview · {}", preview_theme.name); + if is_active { + title.push_str(" (active)"); + } + + let block = Block::default() + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .title(Line::from(Span::styled( + title, + Style::default() + .fg(preview_palette.label) + .add_modifier(Modifier::BOLD), + ))) + .style( + Style::default() + .bg(preview_palette.active) + .fg(preview_palette.label), + ); + + let inner = block.inner(area); + frame.render_widget(block, area); + if inner.width == 0 || inner.height == 0 { + return; + } + + let mut lines: Vec = Vec::new(); + lines.push(Line::from(vec![ + Span::styled( + "You", + Style::default() + .fg(preview_theme.user_message_role) + .add_modifier(Modifier::BOLD), + ), + Span::raw(" » "), + Span::styled( + "Let's try this palette.", + Style::default().fg(preview_theme.text), + ), + ])); + lines.push(Line::from(vec![ + Span::styled( + "Owlen", + Style::default() + .fg(preview_theme.assistant_message_role) + .add_modifier(Modifier::BOLD), + ), + Span::raw(" » "), + Span::styled( + "Looks sharp and legible!", + Style::default().fg(preview_theme.text), + ), + ])); + lines.push(Line::raw("")); + + for (label, fg, bg) in [ + ( + "Focus border", + preview_theme.background, + preview_theme.focused_panel_border, + ), + ( + "Selection", + preview_theme.selection_fg, + preview_theme.selection_bg, + ), + ("Info", preview_theme.background, preview_theme.info), + ("Error", preview_theme.background, preview_theme.error), + ] { + lines.push(Line::from(vec![ + Span::styled(" ", Style::default().bg(bg).fg(fg)), + Span::raw(" "), + Span::styled( + label.to_string(), + Style::default() + .fg(preview_palette.label) + .add_modifier(Modifier::DIM), + ), + ])); + } + + let gauge_line = Line::from(vec![ + Span::styled( + "Context ", + Style::default() + .fg(preview_theme.info) + .add_modifier(Modifier::BOLD), + ), + Span::styled( + "██████", + Style::default() + .bg(preview_theme.info) + .fg(preview_theme.background), + ), + Span::styled( + "──", + Style::default() + .fg(preview_palette.label) + .add_modifier(Modifier::DIM), + ), + Span::raw(" "), + Span::styled( + "Usage ", + Style::default() + .fg(preview_theme.mode_help) + .add_modifier(Modifier::BOLD), + ), + Span::styled( + "████", + Style::default() + .bg(preview_theme.mode_help) + .fg(preview_theme.background), + ), + Span::styled( + "────", + Style::default() + .fg(preview_palette.label) + .add_modifier(Modifier::DIM), + ), + ]); + lines.push(Line::raw("")); + lines.push(gauge_line); + + let paragraph = Paragraph::new(lines) + .style( + Style::default() + .bg(preview_palette.active) + .fg(preview_palette.label), + ) + .wrap(Wrap { trim: true }); + frame.render_widget(paragraph, inner); +} + fn render_command_suggestions(frame: &mut Frame<'_>, app: &ChatApp) { let theme = app.theme(); + let palette = GlassPalette::for_theme(theme); let suggestions = app.command_suggestions(); let buffer = app.command_buffer(); let area = frame.area(); @@ -4235,26 +5067,44 @@ fn render_command_suggestions(frame: &mut Frame<'_>, app: &ChatApp) { } let popup_area = Rect::new(x, y, width, height); + if popup_area.width > 2 && popup_area.height > 2 { + let shadow = Rect::new( + popup_area.x.saturating_add(1), + popup_area.y.saturating_add(1), + popup_area.width.saturating_sub(1), + popup_area.height.saturating_sub(1), + ); + if shadow.width > 0 && shadow.height > 0 { + frame.render_widget( + Block::default().style(Style::default().bg(palette.shadow)), + shadow, + ); + } + } + frame.render_widget(Clear, popup_area); let header = Line::from(vec![ Span::styled( " Command Palette ", - Style::default().fg(theme.info).add_modifier(Modifier::BOLD), + Style::default() + .fg(palette.label) + .add_modifier(Modifier::BOLD), ), Span::styled( "Ctrl+P", Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::DIM), ), ]); let block = Block::default() .title(header) - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.info)) - .style(Style::default().bg(theme.background).fg(theme.text)); + .title_style(Style::default().fg(palette.label)) + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .style(Style::default().bg(palette.active).fg(palette.label)); let inner = block.inner(popup_area); frame.render_widget(block, popup_area); @@ -4276,12 +5126,12 @@ fn render_command_suggestions(frame: &mut Frame<'_>, app: &ChatApp) { Span::styled( ":", Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::BOLD), ), Span::raw(buffer), ])) - .style(Style::default().bg(theme.background).fg(theme.text)); + .style(Style::default().bg(palette.highlight).fg(palette.label)); frame.render_widget(input, layout[0]); let selected_index = if suggestions.is_empty() { @@ -4297,11 +5147,11 @@ fn render_command_suggestions(frame: &mut Frame<'_>, app: &ChatApp) { let placeholder = Paragraph::new(Line::from(Span::styled( "No matches — keep typing", Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::ITALIC), ))) .alignment(Alignment::Center) - .style(Style::default().bg(theme.background).fg(theme.placeholder)); + .style(Style::default().bg(palette.active).fg(palette.label)); frame.render_widget(placeholder, layout[1]); } else { let highlight = Style::default() @@ -4317,8 +5167,8 @@ fn render_command_suggestions(frame: &mut Frame<'_>, app: &ChatApp) { lines.push(Line::from(Span::styled( palette_group_label(suggestion.group), Style::default() - .fg(theme.placeholder) - .add_modifier(Modifier::BOLD), + .fg(palette.label) + .add_modifier(Modifier::BOLD | Modifier::DIM), ))); previous_group = Some(suggestion.group); } @@ -4330,7 +5180,9 @@ fn render_command_suggestions(frame: &mut Frame<'_>, app: &ChatApp) { } else { " " }, - Style::default().fg(theme.placeholder), + Style::default() + .fg(palette.label) + .add_modifier(Modifier::DIM), ), Span::raw(" "), Span::styled( @@ -4344,12 +5196,13 @@ fn render_command_suggestions(frame: &mut Frame<'_>, app: &ChatApp) { lines.push(Line::from(Span::styled( format!(" {}", detail), Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::DIM), ))); } - let item = ListItem::new(lines); + let item = + ListItem::new(lines).style(Style::default().bg(palette.active).fg(palette.label)); items.push(item); } @@ -4358,7 +5211,7 @@ fn render_command_suggestions(frame: &mut Frame<'_>, app: &ChatApp) { let list = List::new(items) .highlight_style(highlight) - .style(Style::default().bg(theme.background).fg(theme.text)); + .style(Style::default().bg(palette.active).fg(palette.label)); frame.render_stateful_widget(list, layout[1], &mut list_state); } @@ -4372,10 +5225,10 @@ fn render_command_suggestions(frame: &mut Frame<'_>, app: &ChatApp) { let footer = Paragraph::new(Line::from(Span::styled( detail_text, - Style::default().fg(theme.placeholder), + Style::default().fg(palette.label), ))) .alignment(Alignment::Center) - .style(Style::default().bg(theme.background).fg(theme.placeholder)); + .style(Style::default().bg(palette.highlight).fg(palette.label)); frame.render_widget(footer, layout[2]); } diff --git a/crates/owlen-tui/src/widgets/model_picker.rs b/crates/owlen-tui/src/widgets/model_picker.rs index 1da2e9f..2350746 100644 --- a/crates/owlen-tui/src/widgets/model_picker.rs +++ b/crates/owlen-tui/src/widgets/model_picker.rs @@ -7,7 +7,7 @@ use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span}, - widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph}, + widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, block::Padding}, }; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; @@ -16,6 +16,7 @@ use crate::chat_app::{ ChatApp, HighlightMask, ModelAvailabilityState, ModelScope, ModelSearchInfo, ModelSelectorItemKind, }; +use crate::glass::GlassPalette; /// Filtering modes for the model picker popup. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] @@ -29,6 +30,7 @@ pub enum FilterMode { pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { let theme = app.theme(); + let palette = GlassPalette::for_theme(theme); let area = frame.area(); if area.width == 0 || area.height == 0 { return; @@ -62,17 +64,33 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { } let popup_area = Rect::new(x, y, width, height); + if popup_area.width > 2 && popup_area.height > 2 { + let shadow_area = Rect::new( + popup_area.x.saturating_add(1), + popup_area.y.saturating_add(1), + popup_area.width.saturating_sub(1), + popup_area.height.saturating_sub(1), + ); + if shadow_area.width > 0 && shadow_area.height > 0 { + frame.render_widget( + Block::default().style(Style::default().bg(palette.shadow)), + shadow_area, + ); + } + } frame.render_widget(Clear, popup_area); let mut title_spans = vec![ Span::styled( " Model Selector ", - Style::default().fg(theme.info).add_modifier(Modifier::BOLD), + Style::default() + .fg(palette.label) + .add_modifier(Modifier::BOLD), ), Span::styled( format!("· Provider: {}", app.selected_provider), Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::DIM), ), ]; @@ -83,9 +101,10 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { let block = Block::default() .title(Line::from(title_spans)) - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.info)) - .style(Style::default().bg(theme.background).fg(theme.text)); + .title_style(Style::default().fg(palette.label)) + .borders(Borders::NONE) + .padding(Padding::new(2, 2, 1, 1)) + .style(Style::default().bg(palette.active).fg(palette.label)); let inner = block.inner(popup_area); frame.render_widget(block, popup_area); @@ -104,10 +123,10 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { let matches = app.visible_model_count(); let search_prefix = Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::DIM); let bracket_style = Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::DIM); let caret_style = if search_active { Style::default() @@ -115,7 +134,7 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { .add_modifier(Modifier::BOLD) } else { Style::default() - .fg(theme.placeholder) + .fg(palette.label) .add_modifier(Modifier::DIM) }; @@ -135,8 +154,8 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { search_spans.push(Span::styled( "Type to search…", Style::default() - .fg(theme.placeholder) - .add_modifier(Modifier::DIM), + .fg(palette.label) + .add_modifier(Modifier::DIM | Modifier::ITALIC), )); } @@ -153,35 +172,37 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { suffix_label, if matches == 1 { "" } else { "s" } ), - Style::default().fg(theme.placeholder), + Style::default() + .fg(palette.label) + .add_modifier(Modifier::DIM), )); let search_line = Line::from(search_spans); let instruction_line = if search_active { Line::from(vec![ - Span::styled("Backspace", Style::default().fg(theme.placeholder)), + Span::styled("Backspace", Style::default().fg(palette.label)), Span::raw(": delete "), - Span::styled("Ctrl+U", Style::default().fg(theme.placeholder)), + Span::styled("Ctrl+U", Style::default().fg(palette.label)), Span::raw(": clear "), - Span::styled("Enter", Style::default().fg(theme.placeholder)), + Span::styled("Enter", Style::default().fg(palette.label)), Span::raw(": select "), - Span::styled("Esc", Style::default().fg(theme.placeholder)), + Span::styled("Esc", Style::default().fg(palette.label)), Span::raw(": close"), ]) } else { Line::from(vec![ - Span::styled("Enter", Style::default().fg(theme.placeholder)), + Span::styled("Enter", Style::default().fg(palette.label)), Span::raw(": select "), - Span::styled("Space", Style::default().fg(theme.placeholder)), + Span::styled("Space", Style::default().fg(palette.label)), Span::raw(": toggle provider "), - Span::styled("Esc", Style::default().fg(theme.placeholder)), + Span::styled("Esc", Style::default().fg(palette.label)), Span::raw(": close"), ]) }; let search_paragraph = Paragraph::new(vec![search_line, instruction_line]) - .style(Style::default().bg(theme.background).fg(theme.text)); + .style(Style::default().bg(palette.highlight).fg(palette.label)); frame.render_widget(search_paragraph, layout[0]); let highlight_style = Style::default() @@ -236,7 +257,7 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { )); let line = clip_line_to_width(Line::from(spans), max_line_width); - items.push(ListItem::new(vec![line]).style(Style::default().bg(theme.background))); + items.push(ListItem::new(vec![line]).style(Style::default().bg(palette.active))); } ModelSelectorItemKind::Scope { label, status, .. } => { let (style, icon) = scope_status_style(*status, theme); @@ -248,7 +269,7 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { ]), max_line_width, ); - items.push(ListItem::new(vec![line]).style(Style::default().bg(theme.background))); + items.push(ListItem::new(vec![line]).style(Style::default().bg(palette.active))); } ModelSelectorItemKind::Model { model_index, .. } => { let mut lines: Vec> = Vec::new(); @@ -286,7 +307,7 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { max_line_width, )); } - items.push(ListItem::new(lines).style(Style::default().bg(theme.background))); + items.push(ListItem::new(lines).style(Style::default().bg(palette.active))); } ModelSelectorItemKind::Empty { message, status, .. @@ -299,7 +320,7 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { let mut spans = vec![Span::styled(icon, style), Span::raw(" ")]; spans.push(Span::styled(format!(" {}", msg), style)); let line = clip_line_to_width(Line::from(spans), max_line_width); - items.push(ListItem::new(vec![line]).style(Style::default().bg(theme.background))); + items.push(ListItem::new(vec![line]).style(Style::default().bg(palette.active))); } } } @@ -311,7 +332,8 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { .fg(theme.selection_fg) .add_modifier(Modifier::BOLD), ) - .highlight_symbol(" "); + .highlight_symbol(" ") + .style(Style::default().bg(palette.active).fg(palette.label)); let mut state = ListState::default(); state.select(app.selected_model_item()); @@ -325,10 +347,10 @@ pub fn render_model_picker(frame: &mut Frame<'_>, app: &ChatApp) { let footer = Paragraph::new(Line::from(Span::styled( footer_text, - Style::default().fg(theme.placeholder), + Style::default().fg(palette.label), ))) .alignment(ratatui::layout::Alignment::Center) - .style(Style::default().bg(theme.background).fg(theme.placeholder)); + .style(Style::default().bg(palette.highlight).fg(palette.label)); frame.render_widget(footer, layout[2]); } diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index b82ff86..918b801 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -121,5 +121,6 @@ If you are experiencing performance issues, you can try the following: - **Reduce context size:** A smaller context size will result in faster responses from the LLM. - **Use a less resource-intensive model:** Some models are faster but less capable than others. +- **Watch the header gauges:** The cockpit header now shows live context usage and cloud quota bands—if either bar turns amber or red, trim the prompt or switch providers before retrying. If you are still having trouble, please [open an issue](https://github.com/Owlibou/owlen/issues) on our GitHub repository. diff --git a/images/layout.png b/images/layout.png index 2100e6e956f0e8584624142bd3a68ed6013799d8..4f3166ad6f610906b8cb2ce5acb03f0f578535b4 100644 GIT binary patch literal 65891 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

-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 literal 27033 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 AsQ>@~ diff --git a/themes/default_dark.toml b/themes/default_dark.toml index 90308df..0a63f83 100644 --- a/themes/default_dark.toml +++ b/themes/default_dark.toml @@ -1,30 +1,30 @@ name = "default_dark" -text = "white" -background = "black" -focused_panel_border = "lightmagenta" -unfocused_panel_border = "#5f1487" -user_message_role = "lightblue" -assistant_message_role = "yellow" -tool_output = "gray" -thinking_panel_title = "lightmagenta" -command_bar_background = "black" -status_background = "black" -mode_normal = "lightblue" -mode_editing = "lightgreen" -mode_model_selection = "lightyellow" -mode_provider_selection = "lightcyan" -mode_help = "lightmagenta" -mode_visual = "magenta" -mode_command = "yellow" -selection_bg = "lightblue" -selection_fg = "black" -cursor = "magenta" -code_block_background = "#191919" -code_block_border = "lightmagenta" -code_block_text = "white" -code_block_keyword = "yellow" -code_block_string = "lightgreen" -code_block_comment = "gray" -placeholder = "darkgray" -error = "red" -info = "lightgreen" +text = "#e2e8f0" +background = "#020617" +focused_panel_border = "#7dd3fc" +unfocused_panel_border = "#1e293b" +user_message_role = "#38bdf8" +assistant_message_role = "#fbbf24" +tool_output = "#94a3b8" +thinking_panel_title = "#a855f7" +command_bar_background = "#0f172a" +status_background = "#111827" +mode_normal = "#38bdf8" +mode_editing = "#34d399" +mode_model_selection = "#fbbf24" +mode_provider_selection = "#22d3ee" +mode_help = "#a855f7" +mode_visual = "#f472b6" +mode_command = "#facc15" +selection_bg = "#1d4ed8" +selection_fg = "#f8fafc" +cursor = "#f472b6" +code_block_background = "#111827" +code_block_border = "#2563eb" +code_block_text = "#e2e8f0" +code_block_keyword = "#fbbf24" +code_block_string = "#34d399" +code_block_comment = "#64748b" +placeholder = "#64748b" +error = "#f87171" +info = "#38bdf8" diff --git a/themes/default_light.toml b/themes/default_light.toml index eaba091..d9408b4 100644 --- a/themes/default_light.toml +++ b/themes/default_light.toml @@ -1,30 +1,30 @@ name = "default_light" -text = "black" -background = "white" -focused_panel_border = "#4a90e2" -unfocused_panel_border = "#dddddd" -user_message_role = "#0055a4" -assistant_message_role = "#8e44ad" -tool_output = "gray" -thinking_panel_title = "#8e44ad" -command_bar_background = "white" -status_background = "white" -mode_normal = "#0055a4" -mode_editing = "#2e8b57" -mode_model_selection = "#b58900" -mode_provider_selection = "#008b8b" -mode_help = "#8e44ad" -mode_visual = "#8e44ad" -mode_command = "#b58900" -selection_bg = "#a4c8f0" -selection_fg = "black" -cursor = "#d95f02" -code_block_background = "#f5f5f5" -code_block_border = "#009688" -code_block_text = "black" -code_block_keyword = "#b58900" -code_block_string = "#388e3c" -code_block_comment = "#90a4ae" -placeholder = "gray" -error = "#c0392b" -info = "green" +text = "#0f172a" +background = "#f8fafc" +focused_panel_border = "#2563eb" +unfocused_panel_border = "#c7d2fe" +user_message_role = "#2563eb" +assistant_message_role = "#9333ea" +tool_output = "#64748b" +thinking_panel_title = "#7c3aed" +command_bar_background = "#e2e8f0" +status_background = "#e0e7ff" +mode_normal = "#2563eb" +mode_editing = "#0ea5e9" +mode_model_selection = "#facc15" +mode_provider_selection = "#0ea5e9" +mode_help = "#7c3aed" +mode_visual = "#7c3aed" +mode_command = "#f97316" +selection_bg = "#bfdbfe" +selection_fg = "#0f172a" +cursor = "#f97316" +code_block_background = "#e2e8f0" +code_block_border = "#2563eb" +code_block_text = "#0f172a" +code_block_keyword = "#b45309" +code_block_string = "#15803d" +code_block_comment = "#94a3b8" +placeholder = "#64748b" +error = "#dc2626" +info = "#2563eb"