graphrag_cli/
workspace.rs1use color_eyre::eyre::{eyre, Result};
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct WorkspaceMetadata {
11 pub id: String,
13 pub name: String,
15 pub created_at: chrono::DateTime<chrono::Utc>,
17 pub last_accessed: chrono::DateTime<chrono::Utc>,
19 pub config_path: Option<PathBuf>,
21}
22
23impl WorkspaceMetadata {
24 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 pub fn touch(&mut self) {
38 self.last_accessed = chrono::Utc::now();
39 }
40}
41
42pub struct WorkspaceManager {
44 base_dir: PathBuf,
46}
47
48impl WorkspaceManager {
49 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 pub fn workspace_dir(&self, id: &str) -> PathBuf {
61 self.base_dir.join(id)
62 }
63
64 pub fn metadata_path(&self, id: &str) -> PathBuf {
66 self.workspace_dir(id).join("metadata.json")
67 }
68
69 pub fn query_history_path(&self, id: &str) -> PathBuf {
71 self.workspace_dir(id).join("query_history.json")
72 }
73
74 pub async fn create_workspace(&self, name: String) -> Result<WorkspaceMetadata> {
76 let metadata = WorkspaceMetadata::new(name);
77
78 let workspace_dir = self.workspace_dir(&metadata.id);
80 tokio::fs::create_dir_all(&workspace_dir).await?;
81
82 self.save_metadata(&metadata).await?;
84
85 Ok(metadata)
86 }
87
88 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 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 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 workspaces.sort_by(|a, b| b.last_accessed.cmp(&a.last_accessed));
128
129 Ok(workspaces)
130 }
131
132 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}