mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
Extract common renderings (#432)
This commit is contained in:
@@ -2,6 +2,7 @@ use anyhow::Result;
|
||||
use goose::models::message::Message;
|
||||
|
||||
pub mod cliclack;
|
||||
pub mod renderer;
|
||||
pub mod rustyline;
|
||||
pub mod thinking;
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{self, Write},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use bat::WrappingMode;
|
||||
use cliclack::{input, set_theme, spinner, Theme as CliclackTheme, ThemeState};
|
||||
use goose::models::message::{Message, MessageContent, ToolRequest, ToolResponse};
|
||||
use goose::models::message::Message;
|
||||
|
||||
use super::{thinking::get_random_thinking_message, Input, InputType, Prompt, Theme};
|
||||
use super::{
|
||||
renderer::{render, BashDeveloperSystemRenderer, DefaultRenderer, ToolRenderer},
|
||||
thinking::get_random_thinking_message,
|
||||
Input, InputType, Prompt, Theme,
|
||||
};
|
||||
|
||||
pub struct CliclackPrompt {
|
||||
spinner: cliclack::ProgressBar,
|
||||
@@ -24,164 +24,38 @@ enum InputMode {
|
||||
|
||||
impl CliclackPrompt {
|
||||
pub fn new() -> Self {
|
||||
// // Load highlighting assets
|
||||
// let assets = HighlightingAssets::from_binary();
|
||||
|
||||
// // Fetch and list all available themes
|
||||
// let themes = assets.themes();
|
||||
// for theme_name in themes {
|
||||
// println!("{}", theme_name);
|
||||
// }
|
||||
|
||||
// // List all available syntaxes (languages)
|
||||
// let syntax_set = assets.get_syntaxes().unwrap();
|
||||
// for syntax in syntax_set {
|
||||
// println!("{}", syntax.name);
|
||||
// }
|
||||
|
||||
set_theme(PromptTheme);
|
||||
|
||||
let mut renderers: HashMap<String, Box<dyn ToolRenderer>> = HashMap::new();
|
||||
let default_renderer = DefaultRenderer;
|
||||
renderers.insert(default_renderer.tool_name(), Box::new(default_renderer));
|
||||
let bash_dev_system_renderer = BashDeveloperSystemRenderer;
|
||||
renderers.insert(
|
||||
bash_dev_system_renderer.tool_name(),
|
||||
Box::new(bash_dev_system_renderer),
|
||||
);
|
||||
|
||||
CliclackPrompt {
|
||||
spinner: spinner(),
|
||||
input_mode: InputMode::Multiline,
|
||||
theme: Theme::Dark,
|
||||
input_mode: InputMode::Singleline,
|
||||
theme: std::env::var("GOOSE_CLI_THEME")
|
||||
.ok()
|
||||
.map(|val| {
|
||||
if val.eq_ignore_ascii_case("light") {
|
||||
Theme::Light
|
||||
} else {
|
||||
Theme::Dark
|
||||
}
|
||||
})
|
||||
.unwrap_or(Theme::Dark),
|
||||
renderers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement the ToolRenderer trait for each tool that you want to render in the prompt.
|
||||
trait ToolRenderer {
|
||||
fn tool_name(&self) -> String;
|
||||
fn request(&self, tool_request: &ToolRequest, theme: &str);
|
||||
fn response(&self, tool_response: &ToolResponse, theme: &str);
|
||||
}
|
||||
|
||||
struct DefaultRenderer;
|
||||
|
||||
impl ToolRenderer for DefaultRenderer {
|
||||
fn tool_name(&self) -> String {
|
||||
"default".to_string()
|
||||
}
|
||||
|
||||
fn request(&self, tool_request: &ToolRequest, theme: &str) {
|
||||
match &tool_request.tool_call {
|
||||
Ok(call) => {
|
||||
print_tool_request(
|
||||
&serde_json::to_string_pretty(&call.arguments).unwrap(),
|
||||
theme,
|
||||
&call.name,
|
||||
);
|
||||
}
|
||||
Err(e) => print(&e.to_string(), theme),
|
||||
}
|
||||
}
|
||||
|
||||
fn response(&self, tool_response: &ToolResponse, theme: &str) {
|
||||
match &tool_response.tool_result {
|
||||
Ok(output) => {
|
||||
let output_value = serde_json::to_string_pretty(output).unwrap();
|
||||
|
||||
// For pure text responses, strip the quotes and replace escaped newlines. Eg. bash responses
|
||||
let unquoted = output_value.trim_matches('"');
|
||||
let formatted = unquoted.replace("\\n", "\n");
|
||||
|
||||
let language = if formatted.starts_with("{") {
|
||||
"JSON"
|
||||
} else {
|
||||
"Markdown"
|
||||
};
|
||||
print_tool_response(&formatted, theme, language);
|
||||
}
|
||||
Err(e) => print(&e.to_string(), theme),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_tool_request(content: &str, theme: &str, tool_name: &str) {
|
||||
bat::PrettyPrinter::new()
|
||||
.input(
|
||||
bat::Input::from_bytes(content.as_bytes()).name(format!("Tool Request: {}", tool_name)),
|
||||
)
|
||||
.theme(theme)
|
||||
.language("JSON")
|
||||
.grid(true)
|
||||
.header(true)
|
||||
.wrapping_mode(WrappingMode::Character)
|
||||
.print()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn print_tool_response(content: &str, theme: &str, language: &str) {
|
||||
bat::PrettyPrinter::new()
|
||||
.input(bat::Input::from_bytes(content.as_bytes()).name("Tool Response:"))
|
||||
.theme(theme)
|
||||
.language(language)
|
||||
.grid(true)
|
||||
.header(true)
|
||||
.wrapping_mode(WrappingMode::Character)
|
||||
.print()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn print(content: &str, theme: &str) {
|
||||
bat::PrettyPrinter::new()
|
||||
.input(bat::Input::from_bytes(content.as_bytes()))
|
||||
.theme(theme)
|
||||
.language("Markdown")
|
||||
.wrapping_mode(WrappingMode::Character)
|
||||
.print()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn print_newline() {
|
||||
println!();
|
||||
}
|
||||
|
||||
impl Prompt for CliclackPrompt {
|
||||
fn render(&mut self, message: Box<Message>) {
|
||||
let theme = match self.theme {
|
||||
Theme::Light => "GitHub",
|
||||
Theme::Dark => "zenburn",
|
||||
};
|
||||
|
||||
let mut last_tool_name: &str = "default";
|
||||
for message_content in &message.content {
|
||||
match message_content {
|
||||
MessageContent::Text(text) => print(&text.text, theme),
|
||||
MessageContent::ToolRequest(tool_request) => match &tool_request.tool_call {
|
||||
Ok(call) => {
|
||||
last_tool_name = &call.name;
|
||||
self.renderers
|
||||
.get(&call.name)
|
||||
.or_else(|| self.renderers.get("default"))
|
||||
.unwrap()
|
||||
.request(tool_request, theme);
|
||||
}
|
||||
Err(_) => self
|
||||
.renderers
|
||||
.get("default")
|
||||
.unwrap()
|
||||
.request(tool_request, theme),
|
||||
},
|
||||
MessageContent::ToolResponse(tool_response) => self
|
||||
.renderers
|
||||
.get(last_tool_name)
|
||||
.or_else(|| self.renderers.get("default"))
|
||||
.unwrap()
|
||||
.response(tool_response, theme),
|
||||
MessageContent::Image(image) => {
|
||||
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_newline();
|
||||
io::stdout().flush().expect("Failed to flush stdout");
|
||||
render(message, &self.theme, self.renderers.clone());
|
||||
}
|
||||
|
||||
fn show_busy(&mut self) {
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::{self, Write};
|
||||
|
||||
use bat::WrappingMode;
|
||||
use console::style;
|
||||
use goose::models::message::{Message, MessageContent, ToolRequest, ToolResponse};
|
||||
use goose::models::role::Role;
|
||||
use goose::models::{content::Content, tool::ToolCall};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::Theme;
|
||||
|
||||
const MAX_STRING_LENGTH: usize = 40;
|
||||
const INDENT: &str = " ";
|
||||
|
||||
/// Implement the ToolRenderer trait for each tool that you want to render in the prompt.
|
||||
pub trait ToolRenderer: ToolRendererClone {
|
||||
fn tool_name(&self) -> String;
|
||||
fn request(&self, tool_request: &ToolRequest, theme: &str);
|
||||
fn response(&self, tool_response: &ToolResponse, theme: &str);
|
||||
}
|
||||
|
||||
// Helper trait for cloning boxed ToolRenderer objects
|
||||
pub trait ToolRendererClone {
|
||||
fn clone_box(&self) -> Box<dyn ToolRenderer>;
|
||||
}
|
||||
|
||||
// Implement the helper trait for any type that implements ToolRenderer and Clone
|
||||
impl<T> ToolRendererClone for T
|
||||
where
|
||||
T: 'static + ToolRenderer + Clone,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn ToolRenderer> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// Make Box<dyn ToolRenderer> clonable
|
||||
impl Clone for Box<dyn ToolRenderer> {
|
||||
fn clone(&self) -> Box<dyn ToolRenderer> {
|
||||
self.clone_box()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DefaultRenderer;
|
||||
|
||||
impl ToolRenderer for DefaultRenderer {
|
||||
fn tool_name(&self) -> String {
|
||||
"default".to_string()
|
||||
}
|
||||
|
||||
fn request(&self, tool_request: &ToolRequest, theme: &str) {
|
||||
match &tool_request.tool_call {
|
||||
Ok(call) => {
|
||||
default_print_request_header(call);
|
||||
|
||||
// Format and print the parameters
|
||||
print_params(&call.arguments, 0);
|
||||
print_newline();
|
||||
}
|
||||
Err(e) => print_markdown(&e.to_string(), theme),
|
||||
}
|
||||
}
|
||||
|
||||
fn response(&self, tool_response: &ToolResponse, theme: &str) {
|
||||
default_response_renderer(tool_response, theme);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BashDeveloperSystemRenderer;
|
||||
|
||||
impl ToolRenderer for BashDeveloperSystemRenderer {
|
||||
fn tool_name(&self) -> String {
|
||||
"DeveloperSystem__bash".to_string()
|
||||
}
|
||||
|
||||
fn request(&self, tool_request: &ToolRequest, theme: &str) {
|
||||
match &tool_request.tool_call {
|
||||
Ok(call) => {
|
||||
default_print_request_header(call);
|
||||
|
||||
match call.arguments.get("command") {
|
||||
Some(Value::String(s)) => {
|
||||
println!("{}: {}", style("command").dim(), style(s).green());
|
||||
}
|
||||
_ => print_params(&call.arguments, 0),
|
||||
}
|
||||
print_newline();
|
||||
}
|
||||
Err(e) => print_markdown(&e.to_string(), theme),
|
||||
}
|
||||
}
|
||||
|
||||
fn response(&self, tool_response: &ToolResponse, theme: &str) {
|
||||
default_response_renderer(tool_response, theme);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
message: Box<Message>,
|
||||
theme: &Theme,
|
||||
renderers: HashMap<String, Box<dyn ToolRenderer>>,
|
||||
) {
|
||||
let theme = match theme {
|
||||
Theme::Light => "GitHub",
|
||||
Theme::Dark => "zenburn",
|
||||
};
|
||||
|
||||
let mut last_tool_name: &str = "default";
|
||||
for message_content in &message.content {
|
||||
match message_content {
|
||||
MessageContent::Text(text) => print_markdown(&text.text, theme),
|
||||
MessageContent::ToolRequest(tool_request) => match &tool_request.tool_call {
|
||||
Ok(call) => {
|
||||
last_tool_name = &call.name;
|
||||
renderers
|
||||
.get(&call.name)
|
||||
.or_else(|| renderers.get("default"))
|
||||
.unwrap()
|
||||
.request(tool_request, theme);
|
||||
}
|
||||
Err(_) => renderers
|
||||
.get("default")
|
||||
.unwrap()
|
||||
.request(tool_request, theme),
|
||||
},
|
||||
MessageContent::ToolResponse(tool_response) => renderers
|
||||
.get(last_tool_name)
|
||||
.or_else(|| renderers.get("default"))
|
||||
.unwrap()
|
||||
.response(tool_response, theme),
|
||||
MessageContent::Image(image) => {
|
||||
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_newline();
|
||||
io::stdout().flush().expect("Failed to flush stdout");
|
||||
}
|
||||
|
||||
pub fn default_response_renderer(tool_response: &ToolResponse, theme: &str) {
|
||||
match &tool_response.tool_result {
|
||||
Ok(contents) => {
|
||||
for content in contents {
|
||||
if content
|
||||
.audience()
|
||||
.is_some_and(|audience| !audience.contains(&Role::User))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let min_priority = std::env::var("GOOSE_CLI_MIN_PRIORITY")
|
||||
.ok()
|
||||
.and_then(|val| val.parse::<f32>().ok())
|
||||
.unwrap_or(0.0);
|
||||
|
||||
if content
|
||||
.priority()
|
||||
.is_some_and(|priority| priority <= min_priority)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Content::Text(text) = content {
|
||||
print_markdown(&text.text, theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => print_markdown(&e.to_string(), theme),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_print_request_header(call: &ToolCall) {
|
||||
// Print the tool name with an emoji
|
||||
let parts: Vec<_> = call.name.split("__").collect();
|
||||
|
||||
let tool_header = format!(
|
||||
"─── {} | {} ──────────────────────────",
|
||||
style(parts.get(1).unwrap_or(&"unknown")),
|
||||
style(parts.first().unwrap_or(&"unknown")).magenta().dim(),
|
||||
);
|
||||
print_newline();
|
||||
println!("{}", tool_header);
|
||||
}
|
||||
|
||||
pub fn print_markdown(content: &str, theme: &str) {
|
||||
bat::PrettyPrinter::new()
|
||||
.input(bat::Input::from_bytes(content.as_bytes()))
|
||||
.theme(theme)
|
||||
.language("Markdown")
|
||||
.wrapping_mode(WrappingMode::Character)
|
||||
.print()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Format and print parameters recursively with proper indentation and colors
|
||||
pub fn print_params(value: &Value, depth: usize) {
|
||||
let indent = INDENT.repeat(depth);
|
||||
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
for (key, val) in map {
|
||||
match val {
|
||||
Value::Object(_) => {
|
||||
println!("{}{}:", indent, style(key).dim());
|
||||
print_params(val, depth + 1);
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
println!("{}{}:", indent, style(key).dim());
|
||||
for item in arr.iter() {
|
||||
println!("{}{}- ", indent, INDENT);
|
||||
print_params(item, depth + 2);
|
||||
}
|
||||
}
|
||||
Value::String(s) => {
|
||||
if s.len() > MAX_STRING_LENGTH {
|
||||
println!("{}{}: {}", indent, style(key).dim(), style("...").dim());
|
||||
} else {
|
||||
println!("{}{}: {}", indent, style(key).dim(), style(s).green());
|
||||
}
|
||||
}
|
||||
Value::Number(n) => {
|
||||
println!("{}{}: {}", indent, style(key).dim(), style(n).blue());
|
||||
}
|
||||
Value::Bool(b) => {
|
||||
println!("{}{}: {}", indent, style(key).dim(), style(b).blue());
|
||||
}
|
||||
Value::Null => {
|
||||
println!("{}{}: {}", indent, style(key).dim(), style("null").dim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
for (i, item) in arr.iter().enumerate() {
|
||||
println!("{}{}.", indent, i + 1);
|
||||
print_params(item, depth + 1);
|
||||
}
|
||||
}
|
||||
Value::String(s) => {
|
||||
if s.len() > MAX_STRING_LENGTH {
|
||||
println!(
|
||||
"{}{}",
|
||||
indent,
|
||||
style(format!("[REDACTED: {} chars]", s.len())).yellow()
|
||||
);
|
||||
} else {
|
||||
println!("{}{}", indent, style(s).green());
|
||||
}
|
||||
}
|
||||
Value::Number(n) => {
|
||||
println!("{}{}", indent, style(n).yellow());
|
||||
}
|
||||
Value::Bool(b) => {
|
||||
println!("{}{}", indent, style(b).yellow());
|
||||
}
|
||||
Value::Null => {
|
||||
println!("{}{}", indent, style("null").dim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_newline() {
|
||||
println!();
|
||||
}
|
||||
@@ -1,22 +1,16 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{self, Write},
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{
|
||||
renderer::{render, BashDeveloperSystemRenderer, DefaultRenderer, ToolRenderer},
|
||||
thinking::get_random_thinking_message,
|
||||
Input, InputType, Prompt, Theme,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use bat::WrappingMode;
|
||||
use cliclack::spinner;
|
||||
use console::style;
|
||||
use goose::models::message::{Message, MessageContent, ToolRequest, ToolResponse};
|
||||
use goose::models::role::Role;
|
||||
use goose::models::{content::Content, tool::ToolCall};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{thinking::get_random_thinking_message, Input, InputType, Prompt, Theme};
|
||||
use goose::models::message::Message;
|
||||
|
||||
const PROMPT: &str = "\x1b[1m\x1b[38;5;30m( O)> \x1b[0m";
|
||||
const MAX_STRING_LENGTH: usize = 40;
|
||||
const INDENT: &str = " ";
|
||||
|
||||
pub struct RustylinePrompt {
|
||||
spinner: cliclack::ProgressBar,
|
||||
@@ -26,21 +20,6 @@ pub struct RustylinePrompt {
|
||||
|
||||
impl RustylinePrompt {
|
||||
pub fn new() -> Self {
|
||||
// // Load highlighting assets
|
||||
// let assets = HighlightingAssets::from_binary();
|
||||
|
||||
// // Fetch and list all available themes
|
||||
// let themes = assets.themes();
|
||||
// for theme_name in themes {
|
||||
// println!("{}", theme_name);
|
||||
// }
|
||||
|
||||
// // List all available syntaxes (languages)
|
||||
// let syntax_set = assets.get_syntaxes().unwrap();
|
||||
// for syntax in syntax_set {
|
||||
// println!("{}", syntax.name);
|
||||
// }
|
||||
|
||||
let mut renderers: HashMap<String, Box<dyn ToolRenderer>> = HashMap::new();
|
||||
let default_renderer = DefaultRenderer;
|
||||
renderers.insert(default_renderer.tool_name(), Box::new(default_renderer));
|
||||
@@ -52,240 +31,24 @@ impl RustylinePrompt {
|
||||
|
||||
RustylinePrompt {
|
||||
spinner: spinner(),
|
||||
theme: Theme::Dark,
|
||||
theme: std::env::var("GOOSE_CLI_THEME")
|
||||
.ok()
|
||||
.map(|val| {
|
||||
if val.eq_ignore_ascii_case("light") {
|
||||
Theme::Light
|
||||
} else {
|
||||
Theme::Dark
|
||||
}
|
||||
})
|
||||
.unwrap_or(Theme::Dark),
|
||||
renderers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement the ToolRenderer trait for each tool that you want to render in the prompt.
|
||||
trait ToolRenderer {
|
||||
fn tool_name(&self) -> String;
|
||||
fn request(&self, tool_request: &ToolRequest, theme: &str);
|
||||
fn response(&self, tool_response: &ToolResponse, theme: &str);
|
||||
}
|
||||
|
||||
struct DefaultRenderer;
|
||||
|
||||
impl ToolRenderer for DefaultRenderer {
|
||||
fn tool_name(&self) -> String {
|
||||
"default".to_string()
|
||||
}
|
||||
|
||||
fn request(&self, tool_request: &ToolRequest, theme: &str) {
|
||||
match &tool_request.tool_call {
|
||||
Ok(call) => {
|
||||
default_print_request_header(call);
|
||||
|
||||
// Format and print the parameters
|
||||
print_params(&call.arguments, 0);
|
||||
print_newline();
|
||||
}
|
||||
Err(e) => print(&e.to_string(), theme),
|
||||
}
|
||||
}
|
||||
|
||||
fn response(&self, tool_response: &ToolResponse, theme: &str) {
|
||||
default_response_renderer(tool_response, theme);
|
||||
}
|
||||
}
|
||||
|
||||
fn default_response_renderer(tool_response: &ToolResponse, theme: &str) {
|
||||
match &tool_response.tool_result {
|
||||
Ok(contents) => {
|
||||
for content in contents {
|
||||
if content
|
||||
.audience()
|
||||
.is_some_and(|audience| !audience.contains(&Role::User))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if content.priority().is_some_and(|priority| priority == 0.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Content::Text(text) = content {
|
||||
print_markdown(&text.text, theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => print(&e.to_string(), theme),
|
||||
}
|
||||
}
|
||||
|
||||
fn default_print_request_header(call: &ToolCall) {
|
||||
// Print the tool name with an emoji
|
||||
let parts: Vec<_> = call.name.split("__").collect();
|
||||
|
||||
let tool_header = format!(
|
||||
"─── {} | {} ──────────────────────────",
|
||||
style(parts.get(1).unwrap_or(&"unknown")),
|
||||
style(parts.first().unwrap_or(&"unknown")).magenta().dim(),
|
||||
);
|
||||
print_newline();
|
||||
println!("{}", tool_header);
|
||||
}
|
||||
struct BashDeveloperSystemRenderer;
|
||||
|
||||
impl ToolRenderer for BashDeveloperSystemRenderer {
|
||||
fn tool_name(&self) -> String {
|
||||
"DeveloperSystem__bash".to_string()
|
||||
}
|
||||
|
||||
fn request(&self, tool_request: &ToolRequest, theme: &str) {
|
||||
match &tool_request.tool_call {
|
||||
Ok(call) => {
|
||||
default_print_request_header(call);
|
||||
|
||||
match call.arguments.get("command") {
|
||||
Some(Value::String(s)) => {
|
||||
println!("{}: {}", style("command").dim(), style(s).green());
|
||||
}
|
||||
_ => print_params(&call.arguments, 0),
|
||||
}
|
||||
print_newline();
|
||||
}
|
||||
Err(e) => print(&e.to_string(), theme),
|
||||
}
|
||||
}
|
||||
|
||||
fn response(&self, tool_response: &ToolResponse, theme: &str) {
|
||||
default_response_renderer(tool_response, theme);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_markdown(content: &str, theme: &str) {
|
||||
bat::PrettyPrinter::new()
|
||||
.input(bat::Input::from_bytes(content.as_bytes()))
|
||||
.theme(theme)
|
||||
.language("Markdown")
|
||||
.wrapping_mode(WrappingMode::Character)
|
||||
.print()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn print(content: &str, theme: &str) {
|
||||
bat::PrettyPrinter::new()
|
||||
.input(bat::Input::from_bytes(content.as_bytes()))
|
||||
.theme(theme)
|
||||
.language("Markdown")
|
||||
.wrapping_mode(WrappingMode::Character)
|
||||
.print()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Format and print parameters recursively with proper indentation and colors
|
||||
fn print_params(value: &Value, depth: usize) {
|
||||
let indent = INDENT.repeat(depth);
|
||||
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
for (key, val) in map {
|
||||
match val {
|
||||
Value::Object(_) => {
|
||||
println!("{}{}:", indent, style(key).dim());
|
||||
print_params(val, depth + 1);
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
println!("{}{}:", indent, style(key).dim());
|
||||
for item in arr.iter() {
|
||||
println!("{}{}- ", indent, INDENT);
|
||||
print_params(item, depth + 2);
|
||||
}
|
||||
}
|
||||
Value::String(s) => {
|
||||
if s.len() > MAX_STRING_LENGTH {
|
||||
println!("{}{}: {}", indent, style(key).dim(), style("...").dim());
|
||||
} else {
|
||||
println!("{}{}: {}", indent, style(key).dim(), style(s).green());
|
||||
}
|
||||
}
|
||||
Value::Number(n) => {
|
||||
println!("{}{}: {}", indent, style(key).dim(), style(n).blue());
|
||||
}
|
||||
Value::Bool(b) => {
|
||||
println!("{}{}: {}", indent, style(key).dim(), style(b).blue());
|
||||
}
|
||||
Value::Null => {
|
||||
println!("{}{}: {}", indent, style(key).dim(), style("null").dim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
for (i, item) in arr.iter().enumerate() {
|
||||
println!("{}{}.", indent, i + 1);
|
||||
print_params(item, depth + 1);
|
||||
}
|
||||
}
|
||||
Value::String(s) => {
|
||||
if s.len() > MAX_STRING_LENGTH {
|
||||
println!(
|
||||
"{}{}",
|
||||
indent,
|
||||
style(format!("[REDACTED: {} chars]", s.len())).yellow()
|
||||
);
|
||||
} else {
|
||||
println!("{}{}", indent, style(s).green());
|
||||
}
|
||||
}
|
||||
Value::Number(n) => {
|
||||
println!("{}{}", indent, style(n).yellow());
|
||||
}
|
||||
Value::Bool(b) => {
|
||||
println!("{}{}", indent, style(b).yellow());
|
||||
}
|
||||
Value::Null => {
|
||||
println!("{}{}", indent, style("null").dim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_newline() {
|
||||
println!();
|
||||
}
|
||||
|
||||
impl Prompt for RustylinePrompt {
|
||||
fn render(&mut self, message: Box<Message>) {
|
||||
let theme = match self.theme {
|
||||
Theme::Light => "GitHub",
|
||||
Theme::Dark => "zenburn",
|
||||
};
|
||||
|
||||
let mut last_tool_name: &str = "default";
|
||||
for message_content in &message.content {
|
||||
match message_content {
|
||||
MessageContent::Text(text) => print_markdown(&text.text, theme),
|
||||
MessageContent::ToolRequest(tool_request) => match &tool_request.tool_call {
|
||||
Ok(call) => {
|
||||
last_tool_name = &call.name;
|
||||
self.renderers
|
||||
.get(&call.name)
|
||||
.or_else(|| self.renderers.get("default"))
|
||||
.unwrap()
|
||||
.request(tool_request, theme);
|
||||
}
|
||||
Err(_) => self
|
||||
.renderers
|
||||
.get("default")
|
||||
.unwrap()
|
||||
.request(tool_request, theme),
|
||||
},
|
||||
MessageContent::ToolResponse(tool_response) => self
|
||||
.renderers
|
||||
.get(last_tool_name)
|
||||
.or_else(|| self.renderers.get("default"))
|
||||
.unwrap()
|
||||
.response(tool_response, theme),
|
||||
MessageContent::Image(image) => {
|
||||
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_newline();
|
||||
io::stdout().flush().expect("Failed to flush stdout");
|
||||
render(message, &self.theme, self.renderers.clone());
|
||||
}
|
||||
|
||||
fn show_busy(&mut self) {
|
||||
|
||||
Reference in New Issue
Block a user