use colored::*;
use std::env;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputStyle {
Success,
Error,
Warning,
Info,
Link,
Default,
}
#[derive(Debug, Clone)]
pub struct OutputFormatter {
colors_enabled: bool,
quiet: bool,
verbose: bool,
}
impl OutputFormatter {
pub fn new() -> Self {
Self {
colors_enabled: !Self::no_color_requested(),
quiet: false,
verbose: false,
}
}
pub fn quiet() -> Self {
Self {
colors_enabled: !Self::no_color_requested(),
quiet: true,
verbose: false,
}
}
pub fn with_verbose() -> Self {
Self {
colors_enabled: !Self::no_color_requested(),
quiet: false,
verbose: true,
}
}
fn no_color_requested() -> bool {
env::var("NO_COLOR").is_ok()
}
pub fn set_quiet(&mut self, quiet: bool) {
self.quiet = quiet;
if quiet {
self.verbose = false;
}
}
pub fn set_verbose(&mut self, verbose: bool) {
self.verbose = verbose;
if verbose {
self.quiet = false;
}
}
pub fn is_quiet(&self) -> bool {
self.quiet
}
pub fn is_verbose(&self) -> bool {
self.verbose
}
pub fn style(&self, text: &str, style: OutputStyle) -> String {
if !self.colors_enabled {
return text.to_string();
}
match style {
OutputStyle::Success => text.green().to_string(),
OutputStyle::Error => text.red().to_string(),
OutputStyle::Warning => text.yellow().to_string(),
OutputStyle::Info => text.blue().to_string(),
OutputStyle::Link => text.cyan().to_string(),
OutputStyle::Default => text.to_string(),
}
}
pub fn success(&self, message: &str) {
if !self.quiet {
println!("{} {}", self.style("✓", OutputStyle::Success), message);
}
}
pub fn error(&self, message: &str) {
eprintln!("{} {}", self.style("✗", OutputStyle::Error), message);
}
pub fn warning(&self, message: &str) {
if !self.quiet {
println!("{} {}", self.style("⚠", OutputStyle::Warning), message);
}
}
pub fn info(&self, message: &str) {
if !self.quiet {
println!("{} {}", self.style("ℹ", OutputStyle::Info), message);
}
}
pub fn verbose(&self, message: &str) {
if self.verbose {
println!("{}", self.style(message, OutputStyle::Default));
}
}
pub fn header(&self, title: &str) {
if self.quiet {
return;
}
let width = title.len() + 4;
let border = "═".repeat(width);
println!("┌{}┐", border);
println!("│ {} │", self.style(title, OutputStyle::Info));
println!("└{}┘", border);
}
pub fn section(&self, title: &str) {
if self.quiet {
return;
}
println!(
"\n{}",
self.style(&format!("━━ {} ━━", title), OutputStyle::Info)
);
}
pub fn box_message(&self, content: &[&str]) {
if self.quiet {
return;
}
let max_width = content.iter().map(|s| s.len()).max().unwrap_or(0);
let border = "─".repeat(max_width + 2);
println!("┌{}┐", border);
for line in content {
println!("│ {:<width$} │", line, width = max_width);
}
println!("└{}┘", border);
}
pub fn key_value(&self, key: &str, value: &str) -> String {
format!("{}: {}", self.style(key, OutputStyle::Info), value)
}
pub fn emoji_or<'a>(&self, emoji: &'a str, alt: &'a str) -> &'a str {
if self.colors_enabled { emoji } else { alt }
}
pub fn separator(&self) {
if !self.quiet {
println!("{}", "═".repeat(64));
}
}
pub fn blank_line(&self) {
if !self.quiet {
println!();
}
}
pub fn format_paladin_result(
&self,
result: &paladin_ports::output::paladin_port::PaladinResult,
verbose: bool,
) -> String {
use paladin_ports::output::paladin_port::StopReason;
let mut output = String::new();
output.push_str(&"═".repeat(80));
output.push_str(&format!(
"\n{} Paladin Execution Result\n",
self.style("📊", OutputStyle::Info)
));
output.push_str(&"═".repeat(80));
output.push('\n');
output.push_str(&format!(
"\n{} Output:\n",
self.style("→", OutputStyle::Info)
));
output.push_str(&format!("{}\n", result.output));
output.push_str(&format!(
"\n{} Statistics:\n",
self.style("→", OutputStyle::Info)
));
output.push_str(&format!(
" {} Execution Time: {:.2}s\n",
self.style("•", OutputStyle::Info),
result.execution_time_ms as f64 / 1000.0
));
output.push_str(&format!(
" {} Tokens Used: {}\n",
self.style("•", OutputStyle::Info),
result.token_count
));
let status_str = match &result.stop_reason {
StopReason::Completed => format!(
"{} {}",
self.style("Completed", OutputStyle::Success),
self.style("✓", OutputStyle::Success)
),
StopReason::StopWord(word) => format!(
"{} {} ({})",
self.style("Stopped", OutputStyle::Warning),
self.style("⚠", OutputStyle::Warning),
word
),
StopReason::MaxLoops => format!(
"{} {}",
self.style("Max Loops Reached", OutputStyle::Warning),
self.style("⚠", OutputStyle::Warning)
),
StopReason::Timeout => format!(
"{} {}",
self.style("Timeout", OutputStyle::Error),
self.style("✗", OutputStyle::Error)
),
};
output.push_str(&format!(
" {} Status: {}\n",
self.style("•", OutputStyle::Info),
status_str
));
if verbose {
output.push_str(&format!(
" {} Reasoning Loops: {}\n",
self.style("•", OutputStyle::Info),
result.loop_count
));
output.push_str(&format!(
" {} Stop Reason: {:?}\n",
self.style("•", OutputStyle::Info),
result.stop_reason
));
}
output.push_str(&"═".repeat(80));
output.push('\n');
output
}
pub fn format_paladin_result_json(
result: &paladin_ports::output::paladin_port::PaladinResult,
) -> serde_json::Value {
use serde_json::json;
json!({
"output": result.output,
"metadata": {
"token_count": result.token_count,
"execution_time_ms": result.execution_time_ms,
"execution_time_seconds": result.execution_time_ms as f64 / 1000.0,
"loop_count": result.loop_count,
"stop_reason": format!("{:?}", result.stop_reason),
"is_successful": result.stop_reason.is_successful(),
"is_limit_reached": result.stop_reason.is_limit(),
},
"timestamp": chrono::Utc::now().to_rfc3339(),
})
}
pub fn format_battalion_result(
&self,
result: &crate::core::platform::container::battalion::BattalionResult,
verbose: bool,
) -> String {
use crate::core::platform::container::battalion::BattalionStatus;
use paladin_ports::output::paladin_port::StopReason;
let mut output = String::new();
output.push_str(&"═".repeat(80));
output.push_str(&format!(
"\n{} Battalion Execution Result: {}\n",
self.style("🏰", OutputStyle::Info),
self.style(&result.battalion_name, OutputStyle::Info)
));
output.push_str(&"═".repeat(80));
output.push('\n');
output.push_str(&format!(
"\n{} Final Output:\n",
self.style("→", OutputStyle::Info)
));
output.push_str(&format!("{}\n", result.final_output));
output.push_str(&format!(
"\n{} Statistics:\n",
self.style("→", OutputStyle::Info)
));
output.push_str(&format!(
" {} Total Paladins: {}\n",
self.style("•", OutputStyle::Info),
result.paladin_results.len()
));
let success_style = if result.paladin_success_count == result.paladin_results.len() {
OutputStyle::Success
} else {
OutputStyle::Warning
};
output.push_str(&format!(
" {} Successful: {} {}\n",
self.style("•", OutputStyle::Info),
result.paladin_success_count,
self.style(
if success_style == OutputStyle::Success {
"✓"
} else {
"⚠"
},
success_style
)
));
if result.paladin_failure_count > 0 {
output.push_str(&format!(
" {} Failed: {} {}\n",
self.style("•", OutputStyle::Info),
result.paladin_failure_count,
self.style("✗", OutputStyle::Error)
));
}
let total_time = (result.completed_at - result.started_at)
.num_milliseconds()
.max(0) as u64;
output.push_str(&format!(
" {} Total Time: {:.2}s\n",
self.style("•", OutputStyle::Info),
total_time as f64 / 1000.0
));
output.push_str(&format!(
" {} Strategy: {:?}\n",
self.style("•", OutputStyle::Info),
result.strategy_used
));
if let Some(reasoning) = &result.strategy_selection_reasoning {
output.push_str(&format!(
" {} Strategy Selection: {}\n",
self.style("•", OutputStyle::Info),
reasoning
));
}
let status_str = match result.status {
BattalionStatus::Completed => format!(
"{} {}",
self.style("Completed", OutputStyle::Success),
self.style("✓", OutputStyle::Success)
),
BattalionStatus::Failed => format!(
"{} {}",
self.style("Failed", OutputStyle::Error),
self.style("✗", OutputStyle::Error)
),
BattalionStatus::Cancelled => format!(
"{} {}",
self.style("Cancelled", OutputStyle::Warning),
self.style("⚠", OutputStyle::Warning)
),
_ => format!("{:?}", result.status),
};
output.push_str(&format!(
" {} Status: {}\n",
self.style("•", OutputStyle::Info),
status_str
));
if verbose && !result.paladin_results.is_empty() {
output.push_str(&format!(
"\n{} Individual Paladin Results:\n",
self.style("→", OutputStyle::Info)
));
output.push_str(&"─".repeat(80));
output.push('\n');
for (idx, paladin_result) in result.paladin_results.iter().enumerate() {
let timing = result
.per_paladin_times
.get(&format!("paladin_{}", idx))
.copied()
.unwrap_or(paladin_result.execution_time_ms);
output.push_str(&format!(
"\n{} Paladin {} - {} loops, {:.2}s, {} tokens\n",
self.style(&format!("{}.", idx + 1), OutputStyle::Info),
idx + 1,
paladin_result.loop_count,
timing as f64 / 1000.0,
paladin_result.token_count
));
let (status_emoji, status_style) = match &paladin_result.stop_reason {
StopReason::Completed => ("✓", OutputStyle::Success),
StopReason::StopWord(_) => ("⚠", OutputStyle::Warning),
StopReason::MaxLoops => ("⚠", OutputStyle::Warning),
StopReason::Timeout => ("✗", OutputStyle::Error),
};
output.push_str(&format!(
" Status: {} {:?}\n",
self.style(status_emoji, status_style),
paladin_result.stop_reason
));
let preview = if paladin_result.output.len() > 200 {
format!("{}...", &paladin_result.output[..200])
} else {
paladin_result.output.clone()
};
output.push_str(&format!(" Output: {}\n", preview));
}
output.push_str(&"─".repeat(80));
output.push('\n');
}
output.push_str(&"═".repeat(80));
output.push('\n');
output
}
pub fn format_battalion_result_json(
result: &crate::core::platform::container::battalion::BattalionResult,
) -> serde_json::Value {
use serde_json::json;
json!({
"battalion_id": result.battalion_id,
"battalion_name": result.battalion_name,
"started_at": result.started_at.to_rfc3339(),
"completed_at": result.completed_at.to_rfc3339(),
"total_time_ms": (result.completed_at - result.started_at).num_milliseconds().max(0),
"final_output": result.final_output,
"status": format!("{:?}", result.status),
"strategy_used": format!("{:?}", result.strategy_used),
"strategy_selection_reasoning": result.strategy_selection_reasoning,
"strategy_selection_time_ms": result.strategy_selection_time_ms,
"paladin_results": result.paladin_results.iter().enumerate().map(|(idx, r)| {
let timing = result.per_paladin_times.get(&format!("paladin_{}", idx)).copied().unwrap_or(r.execution_time_ms);
json!({
"index": idx,
"output": r.output,
"token_count": r.token_count,
"execution_time_ms": timing,
"loop_count": r.loop_count,
"stop_reason": format!("{:?}", r.stop_reason),
"is_successful": r.stop_reason.is_successful(),
})
}).collect::<Vec<_>>(),
"summary": {
"total_paladins": result.paladin_results.len(),
"successful": result.paladin_success_count,
"failed": result.paladin_failure_count,
},
"timestamp": chrono::Utc::now().to_rfc3339(),
})
}
}
impl Default for OutputFormatter {
fn default() -> Self {
Self::new()
}
}