Skip to main content

st/web_dashboard/
mod.rs

1//! Web Dashboard - Browser-based terminal + file browser
2//!
3//! A lightweight alternative to egui that runs in any browser.
4//! Features:
5//! - Real PTY terminal (bash/zsh with colors, vim support)
6//! - File browser with navigation
7//! - Markdown preview
8//! - Cool terminal aesthetic
9//! - Real-time MCP activity visualization (Wave Compass)
10//! - User hints/nudges for AI collaboration
11//! - HTTP MCP endpoints (The Custodian watches all operations)
12
13mod api;
14mod assets;
15mod collab;
16pub mod mcp_http;
17mod pty;
18mod server;
19pub mod state_sync;
20pub mod voice;
21mod websocket;
22
23pub use server::start_server;
24pub use state_sync::{McpActivityState, UserHintsQueue};
25pub use mcp_http::{SharedMcpContext, create_mcp_context, mcp_router};
26
27use crate::collaboration::{create_hub, SharedCollabHub};
28use crate::in_memory_logger::InMemoryLogStore;
29use serde::{Deserialize, Serialize};
30use std::collections::HashMap;
31use std::path::PathBuf;
32use std::sync::atomic::AtomicUsize;
33use std::sync::Arc;
34use tokio::sync::{oneshot, RwLock};
35
36#[derive(Debug)]
37pub struct PromptManager {
38    pub next_id: AtomicUsize,
39    pub pending: Arc<RwLock<HashMap<String, oneshot::Sender<String>>>>,
40    pub active_prompts: Arc<RwLock<HashMap<String, String>>>, // id -> question
41}
42
43impl PromptManager {
44    pub fn new() -> Self {
45        Self {
46            next_id: AtomicUsize::new(1),
47            pending: Arc::new(RwLock::new(HashMap::new())),
48            active_prompts: Arc::new(RwLock::new(HashMap::new())),
49        }
50    }
51}
52
53/// Shared MCP activity state (thread-safe access from MCP and dashboard)
54pub type SharedMcpActivity = Arc<RwLock<McpActivityState>>;
55
56/// Shared user hints queue (browser → MCP)
57pub type SharedUserHints = Arc<RwLock<UserHintsQueue>>;
58
59/// Shared state for the web dashboard
60#[derive(Debug)]
61pub struct DashboardState {
62    /// Current working directory for file browser
63    pub cwd: PathBuf,
64    /// Active PTY sessions
65    pub pty_sessions: HashMap<String, pty::PtyHandle>,
66    /// Number of active WebSocket connections
67    pub connections: usize,
68    /// In-memory store for recent log entries
69    pub log_store: InMemoryLogStore,
70    /// Real-time MCP activity tracking (for Wave Compass)
71    pub mcp_activity: SharedMcpActivity,
72    /// User hints queue (from browser to AI)
73    pub user_hints: SharedUserHints,
74    /// Collaboration hub for dashboard sessions
75    pub collab_hub: SharedCollabHub,
76    /// Pending AI prompts waiting for user answers
77    pub prompt_manager: PromptManager,
78}
79
80impl DashboardState {
81    pub fn new(cwd: PathBuf, log_store: InMemoryLogStore) -> Self {
82        Self {
83            cwd,
84            pty_sessions: HashMap::new(),
85            connections: 0,
86            log_store,
87            mcp_activity: Arc::new(RwLock::new(McpActivityState::default())),
88            user_hints: Arc::new(RwLock::new(UserHintsQueue::default())),
89            collab_hub: create_hub(),
90            prompt_manager: PromptManager::new(),
91        }
92    }
93
94    /// Get a clone of the MCP activity state for sharing with MCP context
95    pub fn mcp_activity_handle(&self) -> SharedMcpActivity {
96        Arc::clone(&self.mcp_activity)
97    }
98
99    /// Get a clone of the user hints queue for sharing with MCP context
100    pub fn user_hints_handle(&self) -> SharedUserHints {
101        Arc::clone(&self.user_hints)
102    }
103}
104
105/// Message types for terminal WebSocket
106#[derive(Debug, Serialize, Deserialize)]
107#[serde(tag = "type", rename_all = "snake_case")]
108pub enum TerminalMessage {
109    /// Input from client to PTY
110    Input { data: String },
111    /// Resize terminal
112    Resize { cols: u16, rows: u16 },
113    /// Output from PTY to client
114    Output { data: String },
115    /// A system message (e.g., connection info)
116    System { message: String },
117    /// PTY process exited
118    Exit { code: i32 },
119    /// Error occurred
120    Error { message: String },
121    /// Keepalive ping
122    Ping,
123    /// Keepalive pong
124    Pong,
125}
126
127/// File tree node for API responses
128#[derive(Debug, Serialize, Deserialize)]
129pub struct FileTreeNode {
130    pub name: String,
131    pub path: String,
132    pub is_dir: bool,
133    pub size: u64,
134    pub modified: i64,
135    pub file_type: String,
136}
137
138pub type SharedState = Arc<RwLock<DashboardState>>;