1use anyhow::Result;
6use colored::*;
7
8use crate::models::Workspace;
9use crate::providers::{ProviderRegistry, ProviderType};
10use crate::workspace::{
11 discover_workspaces, find_workspace_by_path, get_chat_sessions_from_workspace,
12};
13
14pub fn detect_workspace(path: Option<&str>) -> Result<()> {
16 let project_path = path.map(|p| p.to_string()).unwrap_or_else(|| {
17 std::env::current_dir()
18 .map(|p| p.to_string_lossy().to_string())
19 .unwrap_or_else(|_| ".".to_string())
20 });
21
22 println!("\n{} Detecting Workspace", "[D]".blue().bold());
23 println!("{}", "=".repeat(60));
24 println!("{} Path: {}", "[*]".blue(), project_path.cyan());
25
26 match find_workspace_by_path(&project_path)? {
27 Some((ws_id, ws_dir, ws_name)) => {
28 println!("\n{} Workspace Found!", "[+]".green().bold());
29 println!(" {} ID: {}", "[*]".blue(), &ws_id[..16.min(ws_id.len())]);
30 println!(" {} Directory: {}", "[*]".blue(), ws_dir.display());
31 if let Some(name) = ws_name {
32 println!(" {} Name: {}", "[*]".blue(), name.cyan());
33 }
34
35 if let Ok(sessions) = get_chat_sessions_from_workspace(&ws_dir) {
37 println!(" {} Sessions: {}", "[*]".blue(), sessions.len());
38
39 if !sessions.is_empty() {
40 let total_messages: usize =
41 sessions.iter().map(|s| s.session.request_count()).sum();
42 println!(" {} Total Messages: {}", "[*]".blue(), total_messages);
43 }
44 }
45
46 println!("\n{} Provider Detection:", "[*]".blue());
48 println!(
49 " {} Provider: {}",
50 "[*]".blue(),
51 "GitHub Copilot (VS Code)".cyan()
52 );
53 }
54 None => {
55 println!("\n{} No workspace found for this path", "[X]".red());
56 println!(
57 "{} The project may not have been opened in VS Code yet",
58 "[i]".yellow()
59 );
60
61 let all_workspaces = discover_workspaces()?;
63 let path_lower = project_path.to_lowercase();
64 let similar: Vec<&Workspace> = all_workspaces
65 .iter()
66 .filter(|ws| {
67 ws.project_path
68 .as_ref()
69 .map(|p| {
70 p.to_lowercase().contains(&path_lower)
71 || path_lower.contains(&p.to_lowercase())
72 })
73 .unwrap_or(false)
74 })
75 .take(5)
76 .collect();
77
78 if !similar.is_empty() {
79 println!("\n{} Similar workspaces found:", "[i]".cyan());
80 for ws in similar {
81 if let Some(p) = &ws.project_path {
82 println!(" {} {}...", p.cyan(), &ws.hash[..8.min(ws.hash.len())]);
83 }
84 }
85 }
86 }
87 }
88
89 Ok(())
90}
91
92pub fn detect_providers(with_sessions: bool) -> Result<()> {
94 println!("\n{} Detecting Providers", "[D]".blue().bold());
95 println!("{}", "=".repeat(60));
96
97 let registry = ProviderRegistry::new();
98 let mut found_count = 0;
99 let mut with_sessions_count = 0;
100
101 let all_provider_types = vec![
102 ProviderType::Copilot,
103 ProviderType::Cursor,
104 ProviderType::Ollama,
105 ProviderType::Vllm,
106 ProviderType::Foundry,
107 ProviderType::LmStudio,
108 ProviderType::LocalAI,
109 ProviderType::TextGenWebUI,
110 ProviderType::Jan,
111 ProviderType::Gpt4All,
112 ProviderType::Llamafile,
113 ];
114
115 for provider_type in all_provider_types {
116 if let Some(provider) = registry.get_provider(provider_type) {
117 let available = provider.is_available();
118 let session_count = if available {
119 provider.list_sessions().map(|s| s.len()).unwrap_or(0)
120 } else {
121 0
122 };
123
124 if with_sessions && session_count == 0 {
125 continue;
126 }
127
128 found_count += 1;
129 if session_count > 0 {
130 with_sessions_count += 1;
131 }
132
133 let status = if available {
134 if session_count > 0 {
135 format!(
136 "{} ({} sessions)",
137 "+".green(),
138 session_count.to_string().cyan()
139 )
140 } else {
141 format!("{} (no sessions)", "+".green())
142 }
143 } else {
144 format!("{} not available", "x".red())
145 };
146
147 println!(" {} {}: {}", "[*]".blue(), provider.name().bold(), status);
148
149 if available {
151 if let Some(endpoint) = provider_type.default_endpoint() {
152 println!(" {} Endpoint: {}", "`".dimmed(), endpoint.dimmed());
153 }
154 if let Some(path) = provider.sessions_path() {
155 println!(
156 " {} Path: {}",
157 "`".dimmed(),
158 path.display().to_string().dimmed()
159 );
160 }
161 }
162 }
163 }
164
165 println!("\n{} Summary:", "[*]".green().bold());
166 println!(" {} providers available", found_count.to_string().cyan());
167 println!(
168 " {} providers with sessions",
169 with_sessions_count.to_string().cyan()
170 );
171
172 Ok(())
173}
174
175pub fn detect_session(session_id: &str, path: Option<&str>) -> Result<()> {
177 println!("\n{} Detecting Session Provider", "[D]".blue().bold());
178 println!("{}", "=".repeat(60));
179 println!("{} Session: {}", "[*]".blue(), session_id.cyan());
180
181 let registry = ProviderRegistry::new();
182 let session_lower = session_id.to_lowercase();
183 let mut found = false;
184
185 let project_path = path.map(|p| p.to_string()).unwrap_or_else(|| {
187 std::env::current_dir()
188 .map(|p| p.to_string_lossy().to_string())
189 .unwrap_or_else(|_| ".".to_string())
190 });
191
192 if let Ok(Some((_ws_id, ws_dir, ws_name))) = find_workspace_by_path(&project_path) {
194 if let Ok(sessions) = get_chat_sessions_from_workspace(&ws_dir) {
195 for swp in &sessions {
196 let sid = swp
197 .session
198 .session_id
199 .as_ref()
200 .map(|s| s.to_lowercase())
201 .unwrap_or_default();
202 let title = swp.session.title().to_lowercase();
203 let filename = swp
204 .path
205 .file_name()
206 .map(|f| f.to_string_lossy().to_lowercase())
207 .unwrap_or_default();
208
209 if sid.contains(&session_lower)
210 || title.contains(&session_lower)
211 || filename.contains(&session_lower)
212 {
213 found = true;
214 println!("\n{} Session Found!", "[+]".green().bold());
215 println!(" {} Provider: {}", "[*]".blue(), "GitHub Copilot".cyan());
216 println!(" {} Title: {}", "[*]".blue(), swp.session.title());
217 println!(" {} File: {}", "[*]".blue(), swp.path.display());
218 println!(
219 " {} Messages: {}",
220 "[*]".blue(),
221 swp.session.request_count()
222 );
223 if let Some(name) = &ws_name {
224 println!(" {} Workspace: {}", "[*]".blue(), name);
225 }
226 break;
227 }
228 }
229 }
230 }
231
232 if !found {
234 let provider_types = vec![
235 ProviderType::Cursor,
236 ProviderType::Ollama,
237 ProviderType::Jan,
238 ProviderType::Gpt4All,
239 ProviderType::LmStudio,
240 ];
241
242 for provider_type in provider_types {
243 if let Some(provider) = registry.get_provider(provider_type) {
244 if provider.is_available() {
245 if let Ok(sessions) = provider.list_sessions() {
246 for session in sessions {
247 let sid = session
248 .session_id
249 .as_ref()
250 .map(|s| s.to_lowercase())
251 .unwrap_or_default();
252 let title = session.title().to_lowercase();
253
254 if sid.contains(&session_lower) || title.contains(&session_lower) {
255 found = true;
256 println!("\n{} Session Found!", "[+]".green().bold());
257 println!(
258 " {} Provider: {}",
259 "[*]".blue(),
260 provider.name().cyan()
261 );
262 println!(" {} Title: {}", "[*]".blue(), session.title());
263 println!(
264 " {} Messages: {}",
265 "[*]".blue(),
266 session.request_count()
267 );
268 break;
269 }
270 }
271 }
272 }
273 }
274 if found {
275 break;
276 }
277 }
278 }
279
280 if !found {
281 println!("\n{} Session not found", "[X]".red());
282 println!(
283 "{} Try providing a more specific session ID or check the path",
284 "[i]".yellow()
285 );
286 }
287
288 Ok(())
289}
290
291pub fn detect_all(path: Option<&str>, verbose: bool) -> Result<()> {
293 let project_path = path.map(|p| p.to_string()).unwrap_or_else(|| {
294 std::env::current_dir()
295 .map(|p| p.to_string_lossy().to_string())
296 .unwrap_or_else(|_| ".".to_string())
297 });
298
299 println!("\n{} Auto-Detection Report", "[D]".blue().bold());
300 println!("{}", "=".repeat(70));
301 println!("{} Path: {}", "[*]".blue(), project_path.cyan());
302 println!();
303
304 println!("{} Workspace", "---".dimmed());
306 let workspace_info = find_workspace_by_path(&project_path)?;
307
308 match &workspace_info {
309 Some((ws_id, ws_dir, ws_name)) => {
310 println!(" {} Status: {}", "[+]".green(), "Found".green());
311 println!(
312 " {} ID: {}...",
313 "[*]".blue(),
314 &ws_id[..16.min(ws_id.len())]
315 );
316 if let Some(name) = ws_name {
317 println!(" {} Name: {}", "[*]".blue(), name.cyan());
318 }
319
320 if let Ok(sessions) = get_chat_sessions_from_workspace(ws_dir) {
322 println!(" {} Sessions: {}", "[*]".blue(), sessions.len());
323
324 if verbose && !sessions.is_empty() {
325 println!("\n {} Recent Sessions:", "[*]".blue());
326 for (i, swp) in sessions.iter().take(5).enumerate() {
327 println!(
328 " {}. {} ({} messages)",
329 i + 1,
330 truncate(&swp.session.title(), 40),
331 swp.session.request_count()
332 );
333 }
334 if sessions.len() > 5 {
335 println!(" ... and {} more", sessions.len() - 5);
336 }
337 }
338 }
339 }
340 None => {
341 println!(" {} Status: {}", "[X]".red(), "Not found".red());
342 println!(
343 " {} Open this project in VS Code to create a workspace",
344 "[i]".yellow()
345 );
346 }
347 }
348 println!();
349
350 println!("{} Available Providers", "---".dimmed());
352
353 let registry = ProviderRegistry::new();
354 let provider_types = vec![
355 ProviderType::Copilot,
356 ProviderType::Cursor,
357 ProviderType::Ollama,
358 ProviderType::Vllm,
359 ProviderType::Foundry,
360 ProviderType::LmStudio,
361 ProviderType::LocalAI,
362 ProviderType::TextGenWebUI,
363 ProviderType::Jan,
364 ProviderType::Gpt4All,
365 ProviderType::Llamafile,
366 ];
367
368 let mut total_sessions = 0;
369 let mut provider_summary: Vec<(String, usize)> = Vec::new();
370
371 for provider_type in provider_types {
372 if let Some(provider) = registry.get_provider(provider_type) {
373 if provider.is_available() {
374 let session_count = provider.list_sessions().map(|s| s.len()).unwrap_or(0);
375
376 if session_count > 0 || verbose {
377 let status = if session_count > 0 {
378 format!("{} sessions", session_count.to_string().cyan())
379 } else {
380 "ready".dimmed().to_string()
381 };
382 println!(" {} {}: {}", "[+]".green(), provider.name(), status);
383
384 total_sessions += session_count;
385 if session_count > 0 {
386 provider_summary.push((provider.name().to_string(), session_count));
387 }
388 }
389 }
390 }
391 }
392
393 if provider_summary.is_empty() && !verbose {
394 println!(" {} No providers with sessions found", "[i]".yellow());
395 println!(
396 " {} Use --verbose to see all available providers",
397 "[i]".dimmed()
398 );
399 }
400 println!();
401
402 println!("{} Summary", "---".dimmed());
404
405 let ws_status = if workspace_info.is_some() {
406 "Yes".green()
407 } else {
408 "No".red()
409 };
410 println!(" {} Workspace detected: {}", "[*]".blue(), ws_status);
411 println!(
412 " {} Total providers with sessions: {}",
413 "[*]".blue(),
414 provider_summary.len()
415 );
416 println!(
417 " {} Total sessions across providers: {}",
418 "[*]".blue(),
419 total_sessions
420 );
421
422 if workspace_info.is_none() || total_sessions == 0 {
424 println!();
425 println!("{} Recommendations", "---".dimmed());
426
427 if workspace_info.is_none() {
428 println!(
429 " {} Open this project in VS Code to enable chat history tracking",
430 "[->]".cyan()
431 );
432 }
433
434 if total_sessions == 0 {
435 println!(
436 " {} Start a chat session in your IDE to create history",
437 "[->]".cyan()
438 );
439 }
440 }
441
442 Ok(())
443}
444
445fn truncate(s: &str, max_len: usize) -> String {
447 if s.len() <= max_len {
448 s.to_string()
449 } else {
450 format!("{}...", &s[..max_len - 3])
451 }
452}