use crate::api::{BankProfileResponse, RecallResult, RecallResponse, ReflectResponse}; use colored::*; use hindsight_client::types::ChunkData; use indicatif::{ProgressBar, ProgressStyle}; use std::io::{self, Write}; pub fn print_section_header(title: &str) { println!(); println!("{}", format!("━━━ {} ━━━", title).bright_yellow().bold()); println!(); } pub fn print_fact(fact: &RecallResult, show_activation: bool) { let fact_type = fact.type_.as_deref().unwrap_or("unknown"); let type_color = match fact_type { "world" => "cyan", "agent" => "magenta", "opinion" => "yellow", _ => "white", }; let prefix = match fact_type { "world" => "🌍", "agent" => "🤖", "opinion" => "💭", _ => "📝", }; print!("{} ", prefix); print!("{}", format!("[{}]", fact_type.to_uppercase()).color(type_color).bold()); // Note: activation field not available in generated SearchResult // The API doesn't return it in the current schema if show_activation { // Placeholder for when activation is added to the API schema } println!(); println!(" {}", fact.text); // Show context if available if let Some(context) = &fact.context { println!(" {}: {}", "Context".bright_black(), context.bright_black()); } // Show temporal information if let Some(occurred_start) = &fact.occurred_start { if let Some(occurred_end) = &fact.occurred_end { println!(" {}: {} - {}", "Date".bright_black(), occurred_start.bright_black(), occurred_end.bright_black()); } else { println!(" {}: {}", "Date".bright_black(), occurred_start.bright_black()); } } // Show document ID if available if let Some(document_id) = &fact.document_id { println!(" {}: {}", "Document".bright_black(), document_id.bright_black()); } println!(); } pub fn print_chunk(chunk: &ChunkData) { println!(" {}", "─── Source Chunk ───".bright_blue()); // Split text into lines and indent each line for line in chunk.text.lines() { println!(" {}", line.bright_white()); } if chunk.truncated { println!(" {}", "[Truncated due to token limit]".bright_yellow()); } println!(" {}: {} | {}: {}", "Chunk ID".bright_black(), chunk.id.bright_black(), "Index".bright_black(), chunk.chunk_index.to_string().bright_black() ); println!(); } pub fn print_search_results(response: &RecallResponse, show_trace: bool, show_chunks: bool) { let results = &response.results; print_section_header(&format!("Search Results ({})", results.len())); if results.is_empty() { println!("{}", " No results found.".bright_black()); } else { for (i, fact) in results.iter().enumerate() { println!("{}", format!(" Result #{}", i + 1).bright_black()); print_fact(fact, true); // Show chunk if available and requested if show_chunks { if let Some(chunk_id) = &fact.chunk_id { if let Some(chunks) = &response.chunks { if let Some(chunk) = chunks.get(chunk_id) { print_chunk(chunk); } } } } } } if show_trace { if let Some(trace) = &response.trace { print_trace_info(trace); } } } pub fn print_think_response(response: &ReflectResponse) { println!(); println!("{}", response.text.bright_white()); println!(); if !response.based_on.is_empty() { println!("{}", format!("Based on {} memory units", response.based_on.len()).bright_black()); } } pub fn print_trace_info(trace: &serde_json::Map) { print_section_header("Trace Information"); if let Some(time) = trace.get("total_time").and_then(|v| v.as_f64()) { println!(" ⏱️ Total time: {}", format!("{:.2}ms", time).bright_green()); } if let Some(count) = trace.get("activation_count").and_then(|v| v.as_i64()) { println!(" 📊 Activation count: {}", count.to_string().bright_green()); } println!(); } pub fn print_success(message: &str) { println!("{} {}", "✓".bright_green().bold(), message.bright_white()); } pub fn print_error(message: &str) { eprintln!("{} {}", "✗".bright_red().bold(), message.bright_red()); } pub fn print_warning(message: &str) { println!("{} {}", "⚠".bright_yellow().bold(), message.bright_yellow()); } pub fn print_info(message: &str) { println!("{} {}", "ℹ".bright_blue().bold(), message.bright_white()); } pub fn create_spinner(message: &str) -> ProgressBar { let pb = ProgressBar::new_spinner(); pb.set_style( ProgressStyle::default_spinner() .template("{spinner:.cyan} {msg}") .unwrap() .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]), ); pb.set_message(message.to_string()); pb.enable_steady_tick(std::time::Duration::from_millis(80)); pb } pub fn create_progress_bar(total: u64, message: &str) -> ProgressBar { let pb = ProgressBar::new(total); pb.set_style( ProgressStyle::default_bar() .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({percent}%)") .unwrap() .progress_chars("█▓▒░ "), ); pb.set_message(message.to_string()); pb } pub fn prompt_confirmation(message: &str) -> io::Result { print!("{} {} [y/N]: ", "?".bright_blue().bold(), message); io::stdout().flush()?; let mut input = String::new(); io::stdin().read_line(&mut input)?; Ok(input.trim().eq_ignore_ascii_case("y") || input.trim().eq_ignore_ascii_case("yes")) } pub fn print_profile(profile: &BankProfileResponse) { print_section_header(&format!("Bank Profile: {}", profile.bank_id)); // Print name println!("{} {}", "Name:".bright_cyan().bold(), profile.name.bright_white()); println!(); // Print background if available if !profile.background.is_empty() { println!("{}", "Background:".bright_yellow()); for line in profile.background.lines() { println!("{}", line); } println!(); } // Print personality traits println!("{}", "─── Personality Traits ───".bright_yellow()); println!(); let traits = [ ("Openness", profile.personality.openness, "🔓", "green"), ("Conscientiousness", profile.personality.conscientiousness, "📋", "yellow"), ("Extraversion", profile.personality.extraversion, "🗣️", "cyan"), ("Agreeableness", profile.personality.agreeableness, "🤝", "magenta"), ("Neuroticism", profile.personality.neuroticism, "😰", "yellow"), ]; for (name, value, emoji, color) in &traits { let bar_length = 40; let filled = (*value * bar_length as f64) as usize; let empty = bar_length - filled; let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty)); let colored_bar = match *color { "green" => bar.bright_green(), "yellow" => bar.bright_yellow(), "cyan" => bar.bright_cyan(), "magenta" => bar.bright_magenta(), _ => bar.bright_white(), }; println!(" {} {:<20} [{}] {:.0}%", emoji, name, colored_bar, value * 100.0 ); } println!(); println!("{}", "Bias Strength:".bright_yellow()); let bias = profile.personality.bias_strength; let bar_length = 40; let filled = (bias * bar_length as f64) as usize; let empty = bar_length - filled; let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty)); println!(" 💪 {:<20} [{}] {:.0}%", "Personality Influence", bar.bright_green(), bias * 100.0 ); println!(" {}", "(how much personality shapes opinions)".bright_black()); println!(); }