st/mcp/
dashboard_bridge.rs1use crate::web_dashboard::state_sync::{AccessType, McpActivityState, UserHint, UserHintsQueue};
7use std::path::PathBuf;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11#[derive(Clone)]
13pub struct DashboardBridge {
14 activity: Arc<RwLock<McpActivityState>>,
16 hints: Arc<RwLock<UserHintsQueue>>,
18}
19
20impl DashboardBridge {
21 pub fn new(
23 activity: Arc<RwLock<McpActivityState>>,
24 hints: Arc<RwLock<UserHintsQueue>>,
25 ) -> Self {
26 Self { activity, hints }
27 }
28
29 pub async fn tool_started(&self, tool_name: &str, parameters: serde_json::Value) {
31 let mut activity = self.activity.write().await;
32 activity.start_tool(tool_name, parameters);
33 }
34
35 pub async fn tool_progress(&self, progress: f32) {
37 let mut activity = self.activity.write().await;
38 activity.update_progress(progress);
39 }
40
41 pub async fn file_accessed(&self, path: PathBuf, access_type: AccessType, tool_name: &str) {
43 let mut activity = self.activity.write().await;
44 activity.record_file_access(path, access_type, tool_name);
45 }
46
47 pub async fn file_read(&self, path: impl Into<PathBuf>, tool_name: &str) {
49 self.file_accessed(path.into(), AccessType::Read, tool_name)
50 .await;
51 }
52
53 pub async fn file_written(&self, path: impl Into<PathBuf>, tool_name: &str) {
55 self.file_accessed(path.into(), AccessType::Write, tool_name)
56 .await;
57 }
58
59 pub async fn file_analyzed(&self, path: impl Into<PathBuf>, tool_name: &str) {
61 self.file_accessed(path.into(), AccessType::Analyze, tool_name)
62 .await;
63 }
64
65 pub async fn path_searched(&self, path: impl Into<PathBuf>, tool_name: &str) {
67 self.file_accessed(path.into(), AccessType::Search, tool_name)
68 .await;
69 }
70
71 pub async fn tool_completed(&self, success: bool, summary: &str) {
73 let mut activity = self.activity.write().await;
74 activity.complete_tool(success, summary);
75 }
76
77 pub async fn set_operation(&self, operation: &str) {
79 let mut activity = self.activity.write().await;
80 activity.current_operation = operation.to_string();
81 }
82
83 pub async fn consume_hint(&self) -> Option<UserHint> {
85 let mut hints = self.hints.write().await;
86 hints.consume_next()
87 }
88
89 pub async fn peek_hints(&self) -> Vec<UserHint> {
91 let hints = self.hints.read().await;
92 hints.peek_unconsumed().into_iter().cloned().collect()
93 }
94
95 pub async fn has_pending_hints(&self) -> bool {
97 let hints = self.hints.read().await;
98 hints.unconsumed_count() > 0
99 }
100
101 pub async fn get_activity_snapshot(&self) -> McpActivitySnapshot {
103 let activity = self.activity.read().await;
104 McpActivitySnapshot {
105 active_tool: activity.active_tool.as_ref().map(|t| t.name.clone()),
106 current_operation: activity.current_operation.clone(),
107 files_touched_count: activity.files_touched.len(),
108 directories_explored_count: activity.directories_explored.len(),
109 tools_executed_count: activity.tool_history.len(),
110 }
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct McpActivitySnapshot {
117 pub active_tool: Option<String>,
118 pub current_operation: String,
119 pub files_touched_count: usize,
120 pub directories_explored_count: usize,
121 pub tools_executed_count: usize,
122}
123
124#[macro_export]
126macro_rules! with_dashboard_bridge {
127 ($ctx:expr, $tool_name:expr, $params:expr, $body:expr) => {{
128 if let Some(ref bridge) = $ctx.dashboard_bridge {
130 bridge.tool_started($tool_name, $params.clone()).await;
131 }
132
133 let result = $body;
135
136 if let Some(ref bridge) = $ctx.dashboard_bridge {
138 match &result {
139 Ok(val) => {
140 let summary = match val.as_str() {
141 Some(s) if s.len() > 100 => format!("{}...", &s[..100]),
142 Some(s) => s.to_string(),
143 None => "OK".to_string(),
144 };
145 bridge.tool_completed(true, &summary).await;
146 }
147 Err(e) => {
148 bridge.tool_completed(false, &e.to_string()).await;
149 }
150 }
151 }
152
153 result
154 }};
155}