Skip to main content

graphrag_cli/
workspace.rs

1//! Workspace management
2
3use color_eyre::eyre::{eyre, Result};
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6use uuid::Uuid;
7
8/// Workspace metadata
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct WorkspaceMetadata {
11    /// Workspace ID
12    pub id: String,
13    /// Workspace name
14    pub name: String,
15    /// Creation timestamp
16    pub created_at: chrono::DateTime<chrono::Utc>,
17    /// Last accessed timestamp
18    pub last_accessed: chrono::DateTime<chrono::Utc>,
19    /// Configuration file path (if any)
20    pub config_path: Option<PathBuf>,
21}
22
23impl WorkspaceMetadata {
24    /// Create a new workspace
25    pub fn new(name: String) -> Self {
26        let now = chrono::Utc::now();
27        Self {
28            id: Uuid::new_v4().to_string(),
29            name,
30            created_at: now,
31            last_accessed: now,
32            config_path: None,
33        }
34    }
35
36    /// Update last accessed time
37    pub fn touch(&mut self) {
38        self.last_accessed = chrono::Utc::now();
39    }
40}
41
42/// Workspace manager
43pub struct WorkspaceManager {
44    /// Base directory for workspaces
45    base_dir: PathBuf,
46}
47
48impl WorkspaceManager {
49    /// Create a new workspace manager
50    pub fn new() -> Result<Self> {
51        let base_dir = dirs::home_dir()
52            .ok_or_else(|| eyre!("Could not determine home directory"))?
53            .join(".graphrag")
54            .join("workspaces");
55
56        Ok(Self { base_dir })
57    }
58
59    /// Get workspace directory
60    pub fn workspace_dir(&self, id: &str) -> PathBuf {
61        self.base_dir.join(id)
62    }
63
64    /// Get workspace metadata file path
65    pub fn metadata_path(&self, id: &str) -> PathBuf {
66        self.workspace_dir(id).join("metadata.json")
67    }
68
69    /// Get query history file path
70    pub fn query_history_path(&self, id: &str) -> PathBuf {
71        self.workspace_dir(id).join("query_history.json")
72    }
73
74    /// Create a new workspace
75    pub async fn create_workspace(&self, name: String) -> Result<WorkspaceMetadata> {
76        let metadata = WorkspaceMetadata::new(name);
77
78        // Create workspace directory
79        let workspace_dir = self.workspace_dir(&metadata.id);
80        tokio::fs::create_dir_all(&workspace_dir).await?;
81
82        // Save metadata
83        self.save_metadata(&metadata).await?;
84
85        Ok(metadata)
86    }
87
88    /// Load workspace metadata
89    pub async fn load_metadata(&self, id: &str) -> Result<WorkspaceMetadata> {
90        let path = self.metadata_path(id);
91        let content = tokio::fs::read_to_string(&path).await?;
92        let mut metadata: WorkspaceMetadata = serde_json::from_str(&content)?;
93        metadata.touch();
94        self.save_metadata(&metadata).await?;
95        Ok(metadata)
96    }
97
98    /// Save workspace metadata
99    pub async fn save_metadata(&self, metadata: &WorkspaceMetadata) -> Result<()> {
100        let path = self.metadata_path(&metadata.id);
101        let json = serde_json::to_string_pretty(metadata)?;
102        tokio::fs::write(&path, json).await?;
103        Ok(())
104    }
105
106    /// List all workspaces
107    pub async fn list_workspaces(&self) -> Result<Vec<WorkspaceMetadata>> {
108        let mut workspaces = Vec::new();
109
110        if !self.base_dir.exists() {
111            return Ok(workspaces);
112        }
113
114        let mut entries = tokio::fs::read_dir(&self.base_dir).await?;
115
116        while let Some(entry) = entries.next_entry().await? {
117            if entry.file_type().await?.is_dir() {
118                if let Some(id) = entry.file_name().to_str() {
119                    if let Ok(metadata) = self.load_metadata(id).await {
120                        workspaces.push(metadata);
121                    }
122                }
123            }
124        }
125
126        // Sort by last accessed (most recent first)
127        workspaces.sort_by(|a, b| b.last_accessed.cmp(&a.last_accessed));
128
129        Ok(workspaces)
130    }
131
132    /// Delete a workspace
133    pub async fn delete_workspace(&self, id: &str) -> Result<()> {
134        let workspace_dir = self.workspace_dir(id);
135        if workspace_dir.exists() {
136            tokio::fs::remove_dir_all(&workspace_dir).await?;
137        }
138        Ok(())
139    }
140}
141
142impl Default for WorkspaceManager {
143    fn default() -> Self {
144        Self::new().expect("Failed to create workspace manager")
145    }
146}