/// Application state management via Dioxus signals. /// /// Analogous to Svelte 5 runes ($state, $derived) — Dioxus signals provide /// automatic dependency tracking and targeted re-renders. use serde::{Deserialize, Serialize}; use uuid::Uuid; /// Agent activity status — mirrors the Svelte health store's ActivityState. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum AgentStatus { Idle, Running, Done, Stalled, Error, } impl AgentStatus { pub fn css_class(&self) -> &'static str { match self { AgentStatus::Idle => "idle", AgentStatus::Running => "running", AgentStatus::Done => "done", AgentStatus::Stalled => "stalled", AgentStatus::Error => "error", } } pub fn label(&self) -> &'static str { match self { AgentStatus::Idle => "Idle", AgentStatus::Running => "Running", AgentStatus::Done => "Done", AgentStatus::Stalled => "Stalled", AgentStatus::Error => "Error", } } } /// A single message in an agent conversation. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct AgentMessage { pub id: String, pub role: MessageRole, pub content: String, /// For tool calls: tool name pub tool_name: Option, /// For tool results: output text pub tool_output: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum MessageRole { User, Assistant, Tool, } impl MessageRole { pub fn css_class(&self) -> &'static str { match self { MessageRole::User => "user", MessageRole::Assistant => "assistant", MessageRole::Tool => "tool", } } pub fn label(&self) -> &'static str { match self { MessageRole::User => "You", MessageRole::Assistant => "Claude", MessageRole::Tool => "Tool", } } } /// Which tab is active in a ProjectBox. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProjectTab { Model, Docs, Files, } impl ProjectTab { pub fn label(&self) -> &'static str { match self { ProjectTab::Model => "Model", ProjectTab::Docs => "Docs", ProjectTab::Files => "Files", } } pub fn all() -> &'static [ProjectTab] { &[ProjectTab::Model, ProjectTab::Docs, ProjectTab::Files] } } /// A project configuration — corresponds to GroupsFile ProjectConfig in the Svelte app. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ProjectConfig { pub id: String, pub name: String, pub cwd: String, pub provider: String, pub accent: String, } impl ProjectConfig { pub fn new(name: &str, cwd: &str, provider: &str, accent: &str) -> Self { Self { id: Uuid::new_v4().to_string(), name: name.to_string(), cwd: cwd.to_string(), provider: provider.to_string(), accent: accent.to_string(), } } } /// Per-project agent session state. #[derive(Debug, Clone)] pub struct AgentSession { pub session_id: String, pub status: AgentStatus, pub messages: Vec, pub cost_usd: f64, pub tokens_used: u64, pub model: String, } impl AgentSession { pub fn new() -> Self { Self { session_id: Uuid::new_v4().to_string(), status: AgentStatus::Idle, messages: Vec::new(), cost_usd: 0.0, tokens_used: 0, model: "claude-sonnet-4-20250514".to_string(), } } } /// Global app state — sidebar, command palette visibility, etc. /// (Used when wiring backend; prototype uses individual signals.) #[derive(Debug, Clone, PartialEq)] #[allow(dead_code)] pub struct AppState { pub settings_open: bool, pub palette_open: bool, } impl Default for AppState { fn default() -> Self { Self { settings_open: false, palette_open: false, } } } /// Terminal output line for the mock terminal display. #[derive(Debug, Clone, PartialEq)] pub struct TerminalLine { pub kind: TerminalLineKind, pub text: String, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TerminalLineKind { Prompt, Output, } /// Create demo projects for the prototype. pub fn demo_projects() -> Vec { vec![ ProjectConfig::new( "agent-orchestrator", "~/code/ai/agent-orchestrator", "claude", "#89b4fa", // blue ), ProjectConfig::new( "quanta-discord-bot", "~/code/bots/quanta-discord-bot", "claude", "#a6e3a1", // green ), ] } /// Create demo messages for a project agent pane. pub fn demo_messages() -> Vec { vec![ AgentMessage { id: "m1".to_string(), role: MessageRole::User, content: "Add error handling to the WebSocket reconnection logic. It should use \ exponential backoff with a 30s cap." .to_string(), tool_name: None, tool_output: None, }, AgentMessage { id: "m2".to_string(), role: MessageRole::Assistant, content: "I'll add exponential backoff to the WebSocket reconnection in \ `remote.rs`. Let me first read the current implementation." .to_string(), tool_name: None, tool_output: None, }, AgentMessage { id: "m3".to_string(), role: MessageRole::Tool, content: "Read src-tauri/src/remote.rs".to_string(), tool_name: Some("Read".to_string()), tool_output: Some( "pub struct RemoteManager {\n connections: HashMap,\n \ reconnect_attempts: HashMap,\n}" .to_string(), ), }, AgentMessage { id: "m4".to_string(), role: MessageRole::Assistant, content: "I can see the current implementation tracks reconnect attempts but \ doesn't implement backoff. I'll add the exponential backoff logic \ with a 30-second cap." .to_string(), tool_name: None, tool_output: None, }, AgentMessage { id: "m5".to_string(), role: MessageRole::Tool, content: "Edit src-tauri/src/remote.rs".to_string(), tool_name: Some("Edit".to_string()), tool_output: Some( "Added reconnect_with_backoff() method:\n\ - Base delay: 1s\n\ - Multiplier: 2x per attempt\n\ - Cap: 30s\n\ - Max retries: 5" .to_string(), ), }, AgentMessage { id: "m6".to_string(), role: MessageRole::Assistant, content: "Done. The WebSocket reconnection now uses exponential backoff \ (1s -> 2s -> 4s -> 8s -> 16s -> 30s cap). Added \ `reconnect_with_backoff()` method with 5 max retries before \ giving up and emitting a `remote-machine-failed` event." .to_string(), tool_name: None, tool_output: None, }, ] } /// Demo terminal output lines. pub fn demo_terminal_lines() -> Vec { vec![ TerminalLine { kind: TerminalLineKind::Prompt, text: "~/code/ai/agent-orchestrator $ ".to_string(), }, TerminalLine { kind: TerminalLineKind::Output, text: "cargo test --workspace".to_string(), }, TerminalLine { kind: TerminalLineKind::Output, text: " Compiling agor-core v0.1.0".to_string(), }, TerminalLine { kind: TerminalLineKind::Output, text: " Compiling agor-dioxus v0.1.0".to_string(), }, TerminalLine { kind: TerminalLineKind::Output, text: " Running tests/unit.rs".to_string(), }, TerminalLine { kind: TerminalLineKind::Output, text: "test result: ok. 47 passed; 0 failed".to_string(), }, TerminalLine { kind: TerminalLineKind::Prompt, text: "~/code/ai/agent-orchestrator $ ".to_string(), }, ] } /// Command palette command entries. #[derive(Debug, Clone)] pub struct PaletteCommand { pub label: String, pub shortcut: Option, pub icon: String, } pub fn palette_commands() -> Vec { vec![ PaletteCommand { label: "New Agent Session".to_string(), shortcut: Some("Ctrl+N".to_string()), icon: "\u{25B6}".to_string(), // play }, PaletteCommand { label: "Stop Agent".to_string(), shortcut: Some("Ctrl+C".to_string()), icon: "\u{25A0}".to_string(), // stop }, PaletteCommand { label: "Toggle Settings".to_string(), shortcut: Some("Ctrl+,".to_string()), icon: "\u{2699}".to_string(), // gear }, PaletteCommand { label: "Switch Project Group".to_string(), shortcut: Some("Ctrl+G".to_string()), icon: "\u{25A3}".to_string(), // box }, PaletteCommand { label: "Focus Next Project".to_string(), shortcut: Some("Ctrl+]".to_string()), icon: "\u{2192}".to_string(), // arrow right }, PaletteCommand { label: "Toggle Terminal".to_string(), shortcut: Some("Ctrl+`".to_string()), icon: "\u{2588}".to_string(), // terminal }, PaletteCommand { label: "Search Everything".to_string(), shortcut: Some("Ctrl+Shift+F".to_string()), icon: "\u{1F50D}".to_string(), // magnifying glass (will render as text) }, PaletteCommand { label: "Reload Agent Config".to_string(), shortcut: None, icon: "\u{21BB}".to_string(), // reload }, ] }