use std::collections::VecDeque;
const MAX_CHAT_MESSAGES: usize = 500;
const MAX_TOOL_HISTORY: usize = 20;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChatRole {
User,
Iris,
}
#[derive(Debug, Clone)]
pub struct ChatMessage {
pub role: ChatRole,
pub content: String,
}
impl ChatMessage {
pub fn user(content: impl Into<String>) -> Self {
Self {
role: ChatRole::User,
content: content.into(),
}
}
pub fn iris(content: impl Into<String>) -> Self {
Self {
role: ChatRole::Iris,
content: content.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct ChatState {
pub messages: VecDeque<ChatMessage>,
pub input: String,
pub scroll_offset: usize,
pub is_responding: bool,
pub streaming_response: Option<String>,
pub auto_scroll: bool,
pub current_tool: Option<String>,
pub tool_history: VecDeque<String>,
pub error: Option<String>,
}
impl Default for ChatState {
fn default() -> Self {
Self {
messages: VecDeque::new(),
input: String::new(),
scroll_offset: 0,
is_responding: false,
streaming_response: None,
auto_scroll: true,
current_tool: None,
tool_history: VecDeque::new(),
error: None,
}
}
}
impl ChatState {
pub fn new() -> Self {
Self::default()
}
pub fn with_context(mode_name: &str, current_content: Option<&str>) -> Self {
let mut state = Self::default();
let context_msg = if let Some(content) = current_content {
let preview = truncate_preview(content, 200);
format!(
"I'm ready to help with your {}. Here's what we have so far:\n\n```\n{}\n```\n\nWhat would you like to change?",
mode_name, preview
)
} else {
format!(
"I'm ready to help with your {}. What would you like to do?",
mode_name
)
};
state.messages.push_back(ChatMessage::iris(context_msg));
state
}
fn trim_messages(&mut self) {
while self.messages.len() > MAX_CHAT_MESSAGES {
self.messages.pop_front();
}
}
pub fn add_user_message(&mut self, content: &str) {
self.messages.push_back(ChatMessage::user(content));
self.trim_messages();
self.input.clear();
self.error = None; self.auto_scroll = true; }
pub fn set_error(&mut self, error: impl Into<String>) {
self.error = Some(error.into());
self.is_responding = false;
self.streaming_response = None;
self.current_tool = None;
}
pub fn clear_error(&mut self) {
self.error = None;
}
pub fn add_iris_response(&mut self, content: &str) {
self.messages.push_back(ChatMessage::iris(content));
self.trim_messages();
self.is_responding = false;
self.streaming_response = None;
self.current_tool = None;
self.tool_history.clear();
self.auto_scroll = true; }
pub fn scroll_up(&mut self, amount: usize) {
self.scroll_offset = self.scroll_offset.saturating_sub(amount);
self.auto_scroll = false; }
pub fn scroll_down(&mut self, amount: usize, max_scroll: usize) {
self.scroll_offset = (self.scroll_offset + amount).min(max_scroll);
if self.scroll_offset >= max_scroll {
self.auto_scroll = true;
}
}
pub fn estimated_max_scroll(&self) -> usize {
let mut total_lines = 0;
for msg in &self.messages {
total_lines += 2; total_lines += msg.content.lines().count().max(1);
}
if let Some(ref streaming) = self.streaming_response {
total_lines += 2 + streaming.lines().count().max(1);
}
total_lines.saturating_sub(10) }
pub fn add_tool_to_history(&mut self, tool: String) {
self.tool_history.push_back(tool);
while self.tool_history.len() > MAX_TOOL_HISTORY {
self.tool_history.pop_front();
}
}
pub fn clear(&mut self) {
self.messages.clear();
self.input.clear();
self.scroll_offset = 0;
self.is_responding = false;
self.streaming_response = None;
self.current_tool = None;
self.tool_history.clear();
self.error = None;
self.auto_scroll = true;
}
}
pub use crate::studio::utils::truncate_chars as truncate_preview;