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.
This commit is contained in:
2025-10-06 21:43:31 +02:00
parent 67381b02db
commit a909455f97
12 changed files with 58 additions and 10 deletions

View File

@@ -44,6 +44,11 @@ pub struct Theme {
#[serde(serialize_with = "serialize_color")] #[serde(serialize_with = "serialize_color")]
pub assistant_message_role: 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 /// Color for thinking panel title
#[serde(deserialize_with = "deserialize_color")] #[serde(deserialize_with = "deserialize_color")]
#[serde(serialize_with = "serialize_color")] #[serde(serialize_with = "serialize_color")]
@@ -268,6 +273,7 @@ fn default_dark() -> Theme {
unfocused_panel_border: Color::Rgb(95, 20, 135), unfocused_panel_border: Color::Rgb(95, 20, 135),
user_message_role: Color::LightBlue, user_message_role: Color::LightBlue,
assistant_message_role: Color::Yellow, assistant_message_role: Color::Yellow,
tool_output: Color::Gray,
thinking_panel_title: Color::LightMagenta, thinking_panel_title: Color::LightMagenta,
command_bar_background: Color::Black, command_bar_background: Color::Black,
status_background: Color::Black, status_background: Color::Black,
@@ -297,6 +303,7 @@ fn default_light() -> Theme {
unfocused_panel_border: Color::Rgb(221, 221, 221), unfocused_panel_border: Color::Rgb(221, 221, 221),
user_message_role: Color::Rgb(0, 85, 164), user_message_role: Color::Rgb(0, 85, 164),
assistant_message_role: Color::Rgb(142, 68, 173), assistant_message_role: Color::Rgb(142, 68, 173),
tool_output: Color::Gray,
thinking_panel_title: Color::Rgb(142, 68, 173), thinking_panel_title: Color::Rgb(142, 68, 173),
command_bar_background: Color::White, command_bar_background: Color::White,
status_background: Color::White, status_background: Color::White,
@@ -326,6 +333,7 @@ fn gruvbox() -> Theme {
unfocused_panel_border: Color::Rgb(124, 111, 100), // #7c6f64 unfocused_panel_border: Color::Rgb(124, 111, 100), // #7c6f64
user_message_role: Color::Rgb(184, 187, 38), // #b8bb26 (green) user_message_role: Color::Rgb(184, 187, 38), // #b8bb26 (green)
assistant_message_role: Color::Rgb(131, 165, 152), // #83a598 (blue) assistant_message_role: Color::Rgb(131, 165, 152), // #83a598 (blue)
tool_output: Color::Rgb(146, 131, 116),
thinking_panel_title: Color::Rgb(211, 134, 155), // #d3869b (purple) thinking_panel_title: Color::Rgb(211, 134, 155), // #d3869b (purple)
command_bar_background: Color::Rgb(60, 56, 54), // #3c3836 command_bar_background: Color::Rgb(60, 56, 54), // #3c3836
status_background: Color::Rgb(60, 56, 54), status_background: Color::Rgb(60, 56, 54),
@@ -355,6 +363,7 @@ fn dracula() -> Theme {
unfocused_panel_border: Color::Rgb(68, 71, 90), // #44475a unfocused_panel_border: Color::Rgb(68, 71, 90), // #44475a
user_message_role: Color::Rgb(139, 233, 253), // #8be9fd (cyan) user_message_role: Color::Rgb(139, 233, 253), // #8be9fd (cyan)
assistant_message_role: Color::Rgb(255, 121, 198), // #ff79c6 (pink) assistant_message_role: Color::Rgb(255, 121, 198), // #ff79c6 (pink)
tool_output: Color::Rgb(98, 114, 164),
thinking_panel_title: Color::Rgb(189, 147, 249), // #bd93f9 (purple) thinking_panel_title: Color::Rgb(189, 147, 249), // #bd93f9 (purple)
command_bar_background: Color::Rgb(68, 71, 90), command_bar_background: Color::Rgb(68, 71, 90),
status_background: Color::Rgb(68, 71, 90), status_background: Color::Rgb(68, 71, 90),
@@ -384,6 +393,7 @@ fn solarized() -> Theme {
unfocused_panel_border: Color::Rgb(7, 54, 66), // #073642 (base02) unfocused_panel_border: Color::Rgb(7, 54, 66), // #073642 (base02)
user_message_role: Color::Rgb(42, 161, 152), // #2aa198 (cyan) user_message_role: Color::Rgb(42, 161, 152), // #2aa198 (cyan)
assistant_message_role: Color::Rgb(203, 75, 22), // #cb4b16 (orange) 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) thinking_panel_title: Color::Rgb(108, 113, 196), // #6c71c4 (violet)
command_bar_background: Color::Rgb(7, 54, 66), command_bar_background: Color::Rgb(7, 54, 66),
status_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), unfocused_panel_border: Color::Rgb(48, 54, 61),
user_message_role: Color::Rgb(121, 192, 255), user_message_role: Color::Rgb(121, 192, 255),
assistant_message_role: Color::Rgb(137, 221, 255), assistant_message_role: Color::Rgb(137, 221, 255),
tool_output: Color::Rgb(84, 110, 122),
thinking_panel_title: Color::Rgb(158, 206, 106), thinking_panel_title: Color::Rgb(158, 206, 106),
command_bar_background: Color::Rgb(22, 27, 34), command_bar_background: Color::Rgb(22, 27, 34),
status_background: Color::Rgb(22, 27, 34), status_background: Color::Rgb(22, 27, 34),
@@ -442,6 +453,7 @@ fn rose_pine() -> Theme {
unfocused_panel_border: Color::Rgb(38, 35, 58), // #26233a unfocused_panel_border: Color::Rgb(38, 35, 58), // #26233a
user_message_role: Color::Rgb(49, 116, 143), // #31748f (foam) user_message_role: Color::Rgb(49, 116, 143), // #31748f (foam)
assistant_message_role: Color::Rgb(156, 207, 216), // #9ccfd8 (foam light) assistant_message_role: Color::Rgb(156, 207, 216), // #9ccfd8 (foam light)
tool_output: Color::Rgb(110, 106, 134),
thinking_panel_title: Color::Rgb(196, 167, 231), // #c4a7e7 (iris) thinking_panel_title: Color::Rgb(196, 167, 231), // #c4a7e7 (iris)
command_bar_background: Color::Rgb(38, 35, 58), command_bar_background: Color::Rgb(38, 35, 58),
status_background: Color::Rgb(38, 35, 58), status_background: Color::Rgb(38, 35, 58),
@@ -471,6 +483,7 @@ fn monokai() -> Theme {
unfocused_panel_border: Color::Rgb(117, 113, 94), // #75715e unfocused_panel_border: Color::Rgb(117, 113, 94), // #75715e
user_message_role: Color::Rgb(102, 217, 239), // #66d9ef (cyan) user_message_role: Color::Rgb(102, 217, 239), // #66d9ef (cyan)
assistant_message_role: Color::Rgb(174, 129, 255), // #ae81ff (purple) assistant_message_role: Color::Rgb(174, 129, 255), // #ae81ff (purple)
tool_output: Color::Rgb(117, 113, 94),
thinking_panel_title: Color::Rgb(230, 219, 116), // #e6db74 (yellow) thinking_panel_title: Color::Rgb(230, 219, 116), // #e6db74 (yellow)
command_bar_background: Color::Rgb(39, 40, 34), command_bar_background: Color::Rgb(39, 40, 34),
status_background: Color::Rgb(39, 40, 34), status_background: Color::Rgb(39, 40, 34),
@@ -500,6 +513,7 @@ fn material_dark() -> Theme {
unfocused_panel_border: Color::Rgb(84, 110, 122), // #546e7a unfocused_panel_border: Color::Rgb(84, 110, 122), // #546e7a
user_message_role: Color::Rgb(130, 170, 255), // #82aaff (blue) user_message_role: Color::Rgb(130, 170, 255), // #82aaff (blue)
assistant_message_role: Color::Rgb(199, 146, 234), // #c792ea (purple) assistant_message_role: Color::Rgb(199, 146, 234), // #c792ea (purple)
tool_output: Color::Rgb(84, 110, 122),
thinking_panel_title: Color::Rgb(255, 203, 107), // #ffcb6b (yellow) thinking_panel_title: Color::Rgb(255, 203, 107), // #ffcb6b (yellow)
command_bar_background: Color::Rgb(33, 43, 48), command_bar_background: Color::Rgb(33, 43, 48),
status_background: Color::Rgb(33, 43, 48), status_background: Color::Rgb(33, 43, 48),
@@ -529,6 +543,7 @@ fn material_light() -> Theme {
unfocused_panel_border: Color::Rgb(176, 190, 197), unfocused_panel_border: Color::Rgb(176, 190, 197),
user_message_role: Color::Rgb(68, 138, 255), user_message_role: Color::Rgb(68, 138, 255),
assistant_message_role: Color::Rgb(124, 77, 255), assistant_message_role: Color::Rgb(124, 77, 255),
tool_output: Color::Rgb(144, 164, 174),
thinking_panel_title: Color::Rgb(245, 124, 0), thinking_panel_title: Color::Rgb(245, 124, 0),
command_bar_background: Color::Rgb(255, 255, 255), command_bar_background: Color::Rgb(255, 255, 255),
status_background: Color::Rgb(255, 255, 255), status_background: Color::Rgb(255, 255, 255),

View File

@@ -670,7 +670,13 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
let chunks_len = chunks.len(); let chunks_len = chunks.len();
for (i, seg) in chunks.into_iter().enumerate() { 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 { if i == chunks_len - 1 && is_streaming {
spans.push(Span::styled("", Style::default().fg(theme.cursor))); 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 = wrap(&content, content_width as usize);
let chunks_len = chunks.len(); let chunks_len = chunks.len();
for (i, seg) in chunks.into_iter().enumerate() { 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 { if i == chunks_len - 1 && is_streaming {
spans.push(Span::styled("", Style::default().fg(theme.cursor))); 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 { match role {
Role::User => Style::default().fg(theme.user_message_role), Role::User => Style::default().fg(theme.user_message_role),
Role::Assistant => Style::default().fg(theme.assistant_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), Role::Tool => Style::default().fg(theme.info),
} }
} }
@@ -2085,14 +2097,17 @@ fn format_tool_output(content: &str) -> String {
// Try to parse as JSON // Try to parse as JSON
if let Ok(json) = serde_json::from_str::<serde_json::Value>(content) { if let Ok(json) = serde_json::from_str::<serde_json::Value>(content) {
let mut output = String::new(); let mut output = String::new();
let mut content_found = false;
// Extract query if present // Extract query if present
if let Some(query) = json.get("query").and_then(|v| v.as_str()) { if let Some(query) = json.get("query").and_then(|v| v.as_str()) {
output.push_str(&format!("Query: \"{}\"\n\n", query)); output.push_str(&format!("Query: \"{}\"\n\n", query));
content_found = true;
} }
// Extract results array // Extract results array
if let Some(results) = json.get("results").and_then(|v| v.as_array()) { if let Some(results) = json.get("results").and_then(|v| v.as_array()) {
content_found = true;
if results.is_empty() { if results.is_empty() {
output.push_str("No results found"); output.push_str("No results found");
return output; 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()) { if let Some(total) = json.get("total_found").and_then(|v| v.as_u64()) {
output.push_str(&format!("Found {} result(s)", total)); 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()) { } else if let Some(error) = json.get("error").and_then(|v| v.as_str()) {
content_found = true;
// Handle error results // Handle error results
output.push_str(&format!("❌ Error: {}", error)); output.push_str(&format!("❌ Error: {}", error));
} }
if content_found {
output output
} else {
content.to_string()
}
} else { } else {
// If not JSON, return as-is // If not JSON, return as-is
content.to_string() content.to_string()

View File

@@ -5,6 +5,7 @@ focused_panel_border = "lightmagenta"
unfocused_panel_border = "#5f1487" unfocused_panel_border = "#5f1487"
user_message_role = "lightblue" user_message_role = "lightblue"
assistant_message_role = "yellow" assistant_message_role = "yellow"
tool_output = "gray"
thinking_panel_title = "lightmagenta" thinking_panel_title = "lightmagenta"
command_bar_background = "black" command_bar_background = "black"
status_background = "black" status_background = "black"

View File

@@ -5,6 +5,7 @@ focused_panel_border = "#4a90e2"
unfocused_panel_border = "#dddddd" unfocused_panel_border = "#dddddd"
user_message_role = "#0055a4" user_message_role = "#0055a4"
assistant_message_role = "#8e44ad" assistant_message_role = "#8e44ad"
tool_output = "gray"
thinking_panel_title = "#8e44ad" thinking_panel_title = "#8e44ad"
command_bar_background = "white" command_bar_background = "white"
status_background = "white" status_background = "white"

View File

@@ -5,6 +5,7 @@ focused_panel_border = "#ff79c6"
unfocused_panel_border = "#44475a" unfocused_panel_border = "#44475a"
user_message_role = "#8be9fd" user_message_role = "#8be9fd"
assistant_message_role = "#ff79c6" assistant_message_role = "#ff79c6"
tool_output = "#6272a4"
thinking_panel_title = "#bd93f9" thinking_panel_title = "#bd93f9"
command_bar_background = "#44475a" command_bar_background = "#44475a"
status_background = "#44475a" status_background = "#44475a"

View File

@@ -5,6 +5,7 @@ focused_panel_border = "#fe8019"
unfocused_panel_border = "#7c6f64" unfocused_panel_border = "#7c6f64"
user_message_role = "#b8bb26" user_message_role = "#b8bb26"
assistant_message_role = "#83a598" assistant_message_role = "#83a598"
tool_output = "#928374"
thinking_panel_title = "#d3869b" thinking_panel_title = "#d3869b"
command_bar_background = "#3c3836" command_bar_background = "#3c3836"
status_background = "#3c3836" status_background = "#3c3836"

View File

@@ -5,6 +5,7 @@ focused_panel_border = "#80cbc4"
unfocused_panel_border = "#546e7a" unfocused_panel_border = "#546e7a"
user_message_role = "#82aaff" user_message_role = "#82aaff"
assistant_message_role = "#c792ea" assistant_message_role = "#c792ea"
tool_output = "#546e7a"
thinking_panel_title = "#ffcb6b" thinking_panel_title = "#ffcb6b"
command_bar_background = "#212b30" command_bar_background = "#212b30"
status_background = "#212b30" status_background = "#212b30"

View File

@@ -5,6 +5,7 @@ focused_panel_border = "#009688"
unfocused_panel_border = "#b0bec5" unfocused_panel_border = "#b0bec5"
user_message_role = "#448aff" user_message_role = "#448aff"
assistant_message_role = "#7c4dff" assistant_message_role = "#7c4dff"
tool_output = "#90a4ae"
thinking_panel_title = "#f57c00" thinking_panel_title = "#f57c00"
command_bar_background = "#ffffff" command_bar_background = "#ffffff"
status_background = "#ffffff" status_background = "#ffffff"

View File

@@ -5,6 +5,7 @@ focused_panel_border = "#58a6ff"
unfocused_panel_border = "#30363d" unfocused_panel_border = "#30363d"
user_message_role = "#79c0ff" user_message_role = "#79c0ff"
assistant_message_role = "#89ddff" assistant_message_role = "#89ddff"
tool_output = "#546e7a"
thinking_panel_title = "#9ece6a" thinking_panel_title = "#9ece6a"
command_bar_background = "#161b22" command_bar_background = "#161b22"
status_background = "#161b22" status_background = "#161b22"

View File

@@ -5,6 +5,7 @@ focused_panel_border = "#f92672"
unfocused_panel_border = "#75715e" unfocused_panel_border = "#75715e"
user_message_role = "#66d9ef" user_message_role = "#66d9ef"
assistant_message_role = "#ae81ff" assistant_message_role = "#ae81ff"
tool_output = "#75715e"
thinking_panel_title = "#e6db74" thinking_panel_title = "#e6db74"
command_bar_background = "#272822" command_bar_background = "#272822"
status_background = "#272822" status_background = "#272822"

View File

@@ -5,6 +5,7 @@ focused_panel_border = "#eb6f92"
unfocused_panel_border = "#26233a" unfocused_panel_border = "#26233a"
user_message_role = "#31748f" user_message_role = "#31748f"
assistant_message_role = "#9ccfd8" assistant_message_role = "#9ccfd8"
tool_output = "#6e6a86"
thinking_panel_title = "#c4a7e7" thinking_panel_title = "#c4a7e7"
command_bar_background = "#26233a" command_bar_background = "#26233a"
status_background = "#26233a" status_background = "#26233a"

View File

@@ -5,6 +5,7 @@ focused_panel_border = "#268bd2"
unfocused_panel_border = "#073642" unfocused_panel_border = "#073642"
user_message_role = "#2aa198" user_message_role = "#2aa198"
assistant_message_role = "#cb4b16" assistant_message_role = "#cb4b16"
tool_output = "#657b83"
thinking_panel_title = "#6c71c4" thinking_panel_title = "#6c71c4"
command_bar_background = "#073642" command_bar_background = "#073642"
status_background = "#073642" status_background = "#073642"