From a909455f975a43a3c0eb63ddd85eaf3986d9952a Mon Sep 17 00:00:00 2001 From: vikingowl Date: Mon, 6 Oct 2025 21:43:31 +0200 Subject: [PATCH] feat(theme): add tool_output color to themes - Added a `tool_output` color to the `Theme` struct. - Updated all built-in themes to include the new color. - Modified the TUI to use the `tool_output` color for rendering tool output. --- crates/owlen-core/src/theme.rs | 27 +++++++++++++++++++++------ crates/owlen-tui/src/ui.rs | 31 +++++++++++++++++++++++++++---- themes/default_dark.toml | 1 + themes/default_light.toml | 1 + themes/dracula.toml | 1 + themes/gruvbox.toml | 1 + themes/material-dark.toml | 1 + themes/material-light.toml | 1 + themes/midnight-ocean.toml | 1 + themes/monokai.toml | 1 + themes/rose-pine.toml | 1 + themes/solarized.toml | 1 + 12 files changed, 58 insertions(+), 10 deletions(-) diff --git a/crates/owlen-core/src/theme.rs b/crates/owlen-core/src/theme.rs index aa9bd1f..0ec51ee 100644 --- a/crates/owlen-core/src/theme.rs +++ b/crates/owlen-core/src/theme.rs @@ -44,6 +44,11 @@ pub struct Theme { #[serde(serialize_with = "serialize_color")] pub assistant_message_role: Color, + /// Color for tool output messages + #[serde(deserialize_with = "deserialize_color")] + #[serde(serialize_with = "serialize_color")] + pub tool_output: Color, + /// Color for thinking panel title #[serde(deserialize_with = "deserialize_color")] #[serde(serialize_with = "serialize_color")] @@ -268,6 +273,7 @@ fn default_dark() -> Theme { unfocused_panel_border: Color::Rgb(95, 20, 135), user_message_role: Color::LightBlue, assistant_message_role: Color::Yellow, + tool_output: Color::Gray, thinking_panel_title: Color::LightMagenta, command_bar_background: Color::Black, status_background: Color::Black, @@ -297,6 +303,7 @@ fn default_light() -> Theme { unfocused_panel_border: Color::Rgb(221, 221, 221), user_message_role: Color::Rgb(0, 85, 164), assistant_message_role: Color::Rgb(142, 68, 173), + tool_output: Color::Gray, thinking_panel_title: Color::Rgb(142, 68, 173), command_bar_background: Color::White, status_background: Color::White, @@ -326,8 +333,9 @@ fn gruvbox() -> Theme { unfocused_panel_border: Color::Rgb(124, 111, 100), // #7c6f64 user_message_role: Color::Rgb(184, 187, 38), // #b8bb26 (green) assistant_message_role: Color::Rgb(131, 165, 152), // #83a598 (blue) - thinking_panel_title: Color::Rgb(211, 134, 155), // #d3869b (purple) - command_bar_background: Color::Rgb(60, 56, 54), // #3c3836 + tool_output: Color::Rgb(146, 131, 116), + thinking_panel_title: Color::Rgb(211, 134, 155), // #d3869b (purple) + command_bar_background: Color::Rgb(60, 56, 54), // #3c3836 status_background: Color::Rgb(60, 56, 54), mode_normal: Color::Rgb(131, 165, 152), // blue mode_editing: Color::Rgb(184, 187, 38), // green @@ -355,7 +363,8 @@ fn dracula() -> Theme { unfocused_panel_border: Color::Rgb(68, 71, 90), // #44475a user_message_role: Color::Rgb(139, 233, 253), // #8be9fd (cyan) assistant_message_role: Color::Rgb(255, 121, 198), // #ff79c6 (pink) - thinking_panel_title: Color::Rgb(189, 147, 249), // #bd93f9 (purple) + tool_output: Color::Rgb(98, 114, 164), + thinking_panel_title: Color::Rgb(189, 147, 249), // #bd93f9 (purple) command_bar_background: Color::Rgb(68, 71, 90), status_background: Color::Rgb(68, 71, 90), mode_normal: Color::Rgb(139, 233, 253), @@ -384,6 +393,7 @@ fn solarized() -> Theme { unfocused_panel_border: Color::Rgb(7, 54, 66), // #073642 (base02) user_message_role: Color::Rgb(42, 161, 152), // #2aa198 (cyan) assistant_message_role: Color::Rgb(203, 75, 22), // #cb4b16 (orange) + tool_output: Color::Rgb(101, 123, 131), thinking_panel_title: Color::Rgb(108, 113, 196), // #6c71c4 (violet) command_bar_background: Color::Rgb(7, 54, 66), status_background: Color::Rgb(7, 54, 66), @@ -413,6 +423,7 @@ fn midnight_ocean() -> Theme { unfocused_panel_border: Color::Rgb(48, 54, 61), user_message_role: Color::Rgb(121, 192, 255), assistant_message_role: Color::Rgb(137, 221, 255), + tool_output: Color::Rgb(84, 110, 122), thinking_panel_title: Color::Rgb(158, 206, 106), command_bar_background: Color::Rgb(22, 27, 34), status_background: Color::Rgb(22, 27, 34), @@ -442,7 +453,8 @@ fn rose_pine() -> Theme { unfocused_panel_border: Color::Rgb(38, 35, 58), // #26233a user_message_role: Color::Rgb(49, 116, 143), // #31748f (foam) assistant_message_role: Color::Rgb(156, 207, 216), // #9ccfd8 (foam light) - thinking_panel_title: Color::Rgb(196, 167, 231), // #c4a7e7 (iris) + tool_output: Color::Rgb(110, 106, 134), + thinking_panel_title: Color::Rgb(196, 167, 231), // #c4a7e7 (iris) command_bar_background: Color::Rgb(38, 35, 58), status_background: Color::Rgb(38, 35, 58), mode_normal: Color::Rgb(156, 207, 216), @@ -471,7 +483,8 @@ fn monokai() -> Theme { unfocused_panel_border: Color::Rgb(117, 113, 94), // #75715e user_message_role: Color::Rgb(102, 217, 239), // #66d9ef (cyan) assistant_message_role: Color::Rgb(174, 129, 255), // #ae81ff (purple) - thinking_panel_title: Color::Rgb(230, 219, 116), // #e6db74 (yellow) + tool_output: Color::Rgb(117, 113, 94), + thinking_panel_title: Color::Rgb(230, 219, 116), // #e6db74 (yellow) command_bar_background: Color::Rgb(39, 40, 34), status_background: Color::Rgb(39, 40, 34), mode_normal: Color::Rgb(102, 217, 239), @@ -500,7 +513,8 @@ fn material_dark() -> Theme { unfocused_panel_border: Color::Rgb(84, 110, 122), // #546e7a user_message_role: Color::Rgb(130, 170, 255), // #82aaff (blue) assistant_message_role: Color::Rgb(199, 146, 234), // #c792ea (purple) - thinking_panel_title: Color::Rgb(255, 203, 107), // #ffcb6b (yellow) + tool_output: Color::Rgb(84, 110, 122), + thinking_panel_title: Color::Rgb(255, 203, 107), // #ffcb6b (yellow) command_bar_background: Color::Rgb(33, 43, 48), status_background: Color::Rgb(33, 43, 48), mode_normal: Color::Rgb(130, 170, 255), @@ -529,6 +543,7 @@ fn material_light() -> Theme { unfocused_panel_border: Color::Rgb(176, 190, 197), user_message_role: Color::Rgb(68, 138, 255), assistant_message_role: Color::Rgb(124, 77, 255), + tool_output: Color::Rgb(144, 164, 174), thinking_panel_title: Color::Rgb(245, 124, 0), command_bar_background: Color::Rgb(255, 255, 255), status_background: Color::Rgb(255, 255, 255), diff --git a/crates/owlen-tui/src/ui.rs b/crates/owlen-tui/src/ui.rs index 2d4072b..362bb7f 100644 --- a/crates/owlen-tui/src/ui.rs +++ b/crates/owlen-tui/src/ui.rs @@ -670,7 +670,13 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { let chunks_len = chunks.len(); for (i, seg) in chunks.into_iter().enumerate() { - let mut spans = vec![Span::raw(format!("{indent}{}", seg))]; + let style = if matches!(role, Role::Tool) { + Style::default().fg(theme.tool_output) + } else { + Style::default() + }; + + let mut spans = vec![Span::styled(format!("{indent}{}", seg), style)]; if i == chunks_len - 1 && is_streaming { spans.push(Span::styled(" ▌", Style::default().fg(theme.cursor))); } @@ -682,7 +688,13 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { let chunks = wrap(&content, content_width as usize); let chunks_len = chunks.len(); for (i, seg) in chunks.into_iter().enumerate() { - let mut spans = vec![Span::raw(seg.into_owned())]; + let style = if matches!(role, Role::Tool) { + Style::default().fg(theme.tool_output) + } else { + Style::default() + }; + + let mut spans = vec![Span::styled(seg.into_owned(), style)]; if i == chunks_len - 1 && is_streaming { spans.push(Span::styled(" ▌", Style::default().fg(theme.cursor))); } @@ -2075,7 +2087,7 @@ fn role_color(role: &Role, theme: &owlen_core::theme::Theme) -> Style { match role { Role::User => Style::default().fg(theme.user_message_role), Role::Assistant => Style::default().fg(theme.assistant_message_role), - Role::System => Style::default().fg(theme.info), + Role::System => Style::default().fg(theme.unfocused_panel_border), Role::Tool => Style::default().fg(theme.info), } } @@ -2085,14 +2097,17 @@ fn format_tool_output(content: &str) -> String { // Try to parse as JSON if let Ok(json) = serde_json::from_str::(content) { let mut output = String::new(); + let mut content_found = false; // Extract query if present if let Some(query) = json.get("query").and_then(|v| v.as_str()) { output.push_str(&format!("Query: \"{}\"\n\n", query)); + content_found = true; } // Extract results array if let Some(results) = json.get("results").and_then(|v| v.as_array()) { + content_found = true; if results.is_empty() { output.push_str("No results found"); return output; @@ -2158,12 +2173,20 @@ fn format_tool_output(content: &str) -> String { if let Some(total) = json.get("total_found").and_then(|v| v.as_u64()) { output.push_str(&format!("Found {} result(s)", total)); } + } else if let Some(result) = json.get("result").and_then(|v| v.as_str()) { + content_found = true; + output.push_str(result); } else if let Some(error) = json.get("error").and_then(|v| v.as_str()) { + content_found = true; // Handle error results output.push_str(&format!("❌ Error: {}", error)); } - output + if content_found { + output + } else { + content.to_string() + } } else { // If not JSON, return as-is content.to_string() diff --git a/themes/default_dark.toml b/themes/default_dark.toml index cae394c..346c007 100644 --- a/themes/default_dark.toml +++ b/themes/default_dark.toml @@ -5,6 +5,7 @@ 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" diff --git a/themes/default_light.toml b/themes/default_light.toml index 47ba764..63b20d3 100644 --- a/themes/default_light.toml +++ b/themes/default_light.toml @@ -5,6 +5,7 @@ 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" diff --git a/themes/dracula.toml b/themes/dracula.toml index 45faeb2..fe47e1d 100644 --- a/themes/dracula.toml +++ b/themes/dracula.toml @@ -5,6 +5,7 @@ focused_panel_border = "#ff79c6" unfocused_panel_border = "#44475a" user_message_role = "#8be9fd" assistant_message_role = "#ff79c6" +tool_output = "#6272a4" thinking_panel_title = "#bd93f9" command_bar_background = "#44475a" status_background = "#44475a" diff --git a/themes/gruvbox.toml b/themes/gruvbox.toml index 35ba865..b5467fd 100644 --- a/themes/gruvbox.toml +++ b/themes/gruvbox.toml @@ -5,6 +5,7 @@ focused_panel_border = "#fe8019" unfocused_panel_border = "#7c6f64" user_message_role = "#b8bb26" assistant_message_role = "#83a598" +tool_output = "#928374" thinking_panel_title = "#d3869b" command_bar_background = "#3c3836" status_background = "#3c3836" diff --git a/themes/material-dark.toml b/themes/material-dark.toml index 0c859be..1b9c3bd 100644 --- a/themes/material-dark.toml +++ b/themes/material-dark.toml @@ -5,6 +5,7 @@ focused_panel_border = "#80cbc4" unfocused_panel_border = "#546e7a" user_message_role = "#82aaff" assistant_message_role = "#c792ea" +tool_output = "#546e7a" thinking_panel_title = "#ffcb6b" command_bar_background = "#212b30" status_background = "#212b30" diff --git a/themes/material-light.toml b/themes/material-light.toml index ca7d609..5da41be 100644 --- a/themes/material-light.toml +++ b/themes/material-light.toml @@ -5,6 +5,7 @@ focused_panel_border = "#009688" unfocused_panel_border = "#b0bec5" user_message_role = "#448aff" assistant_message_role = "#7c4dff" +tool_output = "#90a4ae" thinking_panel_title = "#f57c00" command_bar_background = "#ffffff" status_background = "#ffffff" diff --git a/themes/midnight-ocean.toml b/themes/midnight-ocean.toml index c23297f..5b72566 100644 --- a/themes/midnight-ocean.toml +++ b/themes/midnight-ocean.toml @@ -5,6 +5,7 @@ focused_panel_border = "#58a6ff" unfocused_panel_border = "#30363d" user_message_role = "#79c0ff" assistant_message_role = "#89ddff" +tool_output = "#546e7a" thinking_panel_title = "#9ece6a" command_bar_background = "#161b22" status_background = "#161b22" diff --git a/themes/monokai.toml b/themes/monokai.toml index bd83cf5..6a5226d 100644 --- a/themes/monokai.toml +++ b/themes/monokai.toml @@ -5,6 +5,7 @@ focused_panel_border = "#f92672" unfocused_panel_border = "#75715e" user_message_role = "#66d9ef" assistant_message_role = "#ae81ff" +tool_output = "#75715e" thinking_panel_title = "#e6db74" command_bar_background = "#272822" status_background = "#272822" diff --git a/themes/rose-pine.toml b/themes/rose-pine.toml index a8400b4..cf8b1bb 100644 --- a/themes/rose-pine.toml +++ b/themes/rose-pine.toml @@ -5,6 +5,7 @@ focused_panel_border = "#eb6f92" unfocused_panel_border = "#26233a" user_message_role = "#31748f" assistant_message_role = "#9ccfd8" +tool_output = "#6e6a86" thinking_panel_title = "#c4a7e7" command_bar_background = "#26233a" status_background = "#26233a" diff --git a/themes/solarized.toml b/themes/solarized.toml index ef40542..2b8b8fe 100644 --- a/themes/solarized.toml +++ b/themes/solarized.toml @@ -5,6 +5,7 @@ focused_panel_border = "#268bd2" unfocused_panel_border = "#073642" user_message_role = "#2aa198" assistant_message_role = "#cb4b16" +tool_output = "#657b83" thinking_panel_title = "#6c71c4" command_bar_background = "#073642" status_background = "#073642"