use crate::agent::{SessionInfo, SessionStatus, ToolCall};
#[derive(Debug, Clone)]
pub enum UiMessage {
Chat(String),
NewSession,
ResumeSession(String),
ApproveTools(Vec<ToolCall>),
DenyTools,
RefreshSessions,
RefreshStatus,
SetModel(String),
Compact,
SearchMemory(String),
Save,
ShowHelp,
ShowStatus,
}
#[derive(Debug, Clone)]
pub enum WorkerMessage {
Ready {
model: String,
memory_chunks: usize,
has_embeddings: bool,
},
ContentChunk(String),
ToolCallStart {
name: String,
id: String,
detail: Option<String>,
},
ToolCallEnd {
name: String,
id: String,
output: String,
warnings: Vec<String>,
},
ToolsPendingApproval(Vec<ToolCall>),
Done,
Error(String),
Status(SessionStatus),
Sessions(Vec<SessionInfo>),
SessionChanged { id: String, message_count: usize },
SystemMessage(String),
}
#[derive(Debug, Clone)]
pub struct ChatMessage {
pub role: MessageRole,
pub content: String,
pub tool_info: Option<ToolInfo>,
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum MessageRole {
User,
Assistant,
System,
}
#[derive(Debug, Clone)]
pub struct ToolInfo {
pub name: String,
pub detail: Option<String>,
pub status: ToolStatus,
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum ToolStatus {
Running,
Completed(String), Error(String),
}
#[derive(Default)]
pub struct UiState {
pub messages: Vec<ChatMessage>,
pub input: String,
pub is_loading: bool,
pub streaming_content: String,
pub active_tools: Vec<ToolInfo>,
pub pending_approval: Option<Vec<ToolCall>>,
pub error: Option<String>,
pub sessions: Vec<SessionInfo>,
pub current_session: Option<SessionInfo>,
pub model: String,
pub memory_chunks: usize,
pub has_embeddings: bool,
pub status: Option<SessionStatus>,
pub active_panel: Panel,
pub scroll_to_bottom: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Panel {
#[default]
Chat,
Sessions,
Status,
}
impl UiState {
pub fn new() -> Self {
Self::default()
}
pub fn handle_worker_message(&mut self, msg: WorkerMessage) {
match msg {
WorkerMessage::Ready {
model,
memory_chunks,
has_embeddings,
} => {
self.model = model;
self.memory_chunks = memory_chunks;
self.has_embeddings = has_embeddings;
self.is_loading = false;
}
WorkerMessage::ContentChunk(content) => {
self.streaming_content.push_str(&content);
self.scroll_to_bottom = true;
}
WorkerMessage::ToolCallStart {
name,
id: _,
detail,
} => {
self.active_tools.push(ToolInfo {
name,
detail,
status: ToolStatus::Running,
});
}
WorkerMessage::ToolCallEnd {
name,
output,
id: _,
warnings: _,
} => {
if let Some(tool) = self.active_tools.iter_mut().find(|t| t.name == name) {
let preview = if output.len() > 100 {
format!("{}...", &output[..100])
} else {
output
};
tool.status = ToolStatus::Completed(preview);
}
}
WorkerMessage::ToolsPendingApproval(calls) => {
self.pending_approval = Some(calls);
self.is_loading = false;
}
WorkerMessage::Done => {
if !self.streaming_content.is_empty() {
self.messages.push(ChatMessage {
role: MessageRole::Assistant,
content: std::mem::take(&mut self.streaming_content),
tool_info: None,
});
}
self.active_tools.clear();
self.is_loading = false;
self.scroll_to_bottom = true;
}
WorkerMessage::Error(err) => {
self.error = Some(err);
self.is_loading = false;
self.streaming_content.clear();
}
WorkerMessage::Status(status) => {
self.status = Some(status);
}
WorkerMessage::Sessions(sessions) => {
self.sessions = sessions;
}
WorkerMessage::SessionChanged { id, message_count } => {
self.current_session = Some(SessionInfo {
id,
message_count,
created_at: chrono::Utc::now(),
file_size: 0,
});
self.messages.clear();
self.streaming_content.clear();
}
WorkerMessage::SystemMessage(text) => {
self.messages.push(ChatMessage {
role: MessageRole::System,
content: text,
tool_info: None,
});
self.scroll_to_bottom = true;
}
}
}
pub fn add_user_message(&mut self, content: String) {
self.messages.push(ChatMessage {
role: MessageRole::User,
content,
tool_info: None,
});
self.scroll_to_bottom = true;
}
pub fn clear_error(&mut self) {
self.error = None;
}
}