1use chrono::DateTime;
4use chrono::Utc;
5pub use agcodex_core::models::ContentItem;
7pub use agcodex_core::models::ResponseItem;
8pub use agcodex_core::modes::OperatingMode;
9use serde::Deserialize;
10use serde::Serialize;
11use std::collections::HashMap;
12use std::path::PathBuf;
13use uuid::Uuid;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SessionMetadata {
18 pub id: Uuid,
19 pub title: String,
20 pub created_at: DateTime<Utc>,
21 pub updated_at: DateTime<Utc>,
22 pub last_accessed: DateTime<Utc>,
23 pub message_count: usize,
24 pub turn_count: usize,
25 pub current_mode: OperatingMode,
26 pub model: String,
27 pub tags: Vec<String>,
28 pub is_favorite: bool,
29 pub file_size: u64,
30 pub compression_ratio: f32,
31 pub format_version: u16,
32 pub checkpoints: Vec<CheckpointMetadata>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct CheckpointMetadata {
38 pub id: Uuid,
39 pub name: String,
40 pub created_at: DateTime<Utc>,
41 pub message_index: usize,
42 pub description: Option<String>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct Checkpoint {
48 pub metadata: CheckpointMetadata,
49 pub conversation: ConversationSnapshot,
50 pub state: SessionState,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct ConversationSnapshot {
56 pub id: Uuid,
57 pub messages: Vec<MessageSnapshot>,
58 pub context: ConversationContext,
59 pub mode_history: Vec<(OperatingMode, DateTime<Utc>)>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct MessageSnapshot {
65 pub item: ResponseItem,
66 pub timestamp: DateTime<Utc>,
67 pub turn_index: usize,
68 pub metadata: MessageMetadata,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, Default)]
73pub struct MessageMetadata {
74 pub edited: bool,
75 pub edit_history: Vec<(DateTime<Utc>, ResponseItem)>,
76 pub branch_point: bool,
77 pub branch_id: Option<Uuid>,
78 pub parent_message_id: Option<Uuid>,
79 pub message_id: Uuid,
80 pub file_context: Vec<FileContext>,
81 pub tool_calls: Vec<ToolCallMetadata>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct FileContext {
87 pub path: PathBuf,
88 pub line_range: Option<(usize, usize)>,
89 pub snippet: Option<String>,
90 pub language: Option<String>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ToolCallMetadata {
96 pub tool_name: String,
97 pub execution_time_ms: u64,
98 pub success: bool,
99 pub error: Option<String>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct ConversationContext {
105 pub working_directory: PathBuf,
106 pub environment_variables: HashMap<String, String>,
107 pub open_files: Vec<PathBuf>,
108 pub ast_index_state: Option<AstIndexState>,
109 pub embedding_cache: Option<EmbeddingCacheState>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct AstIndexState {
115 pub indexed_files: Vec<PathBuf>,
116 pub total_symbols: usize,
117 pub cache_size_bytes: u64,
118 pub last_update: DateTime<Utc>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct EmbeddingCacheState {
124 pub cached_chunks: usize,
125 pub model: String,
126 pub cache_size_bytes: u64,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, Default)]
131pub struct SessionState {
132 pub cursor_position: usize,
133 pub scroll_offset: usize,
134 pub selected_message: Option<Uuid>,
135 pub expanded_messages: Vec<Uuid>,
136 #[serde(default)]
137 pub active_panel: String,
138 #[serde(default)]
139 pub panel_sizes: HashMap<String, f32>,
140 pub search_query: Option<String>,
141 pub filter_settings: FilterSettings,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize, Default)]
146pub struct FilterSettings {
147 pub show_system: bool,
148 pub show_tool_calls: bool,
149 pub show_reasoning: bool,
150 pub role_filter: Option<String>,
151 pub date_range: Option<(DateTime<Utc>, DateTime<Utc>)>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize, Default)]
156pub struct SessionIndex {
157 pub sessions: HashMap<Uuid, SessionMetadata>,
158 pub recent_sessions: Vec<Uuid>,
159 pub favorite_sessions: Vec<Uuid>,
160 pub tag_index: HashMap<String, Vec<Uuid>>,
161 pub last_updated: DateTime<Utc>,
162 pub total_size_bytes: u64,
163}
164
165impl SessionIndex {
166 pub fn new() -> Self {
167 Self {
168 sessions: HashMap::new(),
169 recent_sessions: Vec::new(),
170 favorite_sessions: Vec::new(),
171 tag_index: HashMap::new(),
172 last_updated: Utc::now(),
173 total_size_bytes: 0,
174 }
175 }
176
177 pub fn add_session(&mut self, metadata: SessionMetadata) {
178 let id = metadata.id;
179
180 if metadata.is_favorite && !self.favorite_sessions.contains(&id) {
182 self.favorite_sessions.push(id);
183 }
184
185 for tag in &metadata.tags {
187 self.tag_index.entry(tag.clone()).or_default().push(id);
188 }
189
190 self.recent_sessions.retain(|&sid| sid != id);
192 self.recent_sessions.insert(0, id);
193 if self.recent_sessions.len() > 20 {
194 self.recent_sessions.truncate(20);
195 }
196
197 self.total_size_bytes += metadata.file_size;
199
200 self.sessions.insert(id, metadata);
202 self.last_updated = Utc::now();
203 }
204
205 pub fn remove_session(&mut self, id: &Uuid) -> Option<SessionMetadata> {
206 if let Some(metadata) = self.sessions.remove(id) {
207 self.recent_sessions.retain(|sid| sid != id);
209
210 self.favorite_sessions.retain(|sid| sid != id);
212
213 for tag in &metadata.tags {
215 if let Some(sessions) = self.tag_index.get_mut(tag) {
216 sessions.retain(|sid| sid != id);
217 if sessions.is_empty() {
218 self.tag_index.remove(tag);
219 }
220 }
221 }
222
223 self.total_size_bytes -= metadata.file_size;
225 self.last_updated = Utc::now();
226
227 Some(metadata)
228 } else {
229 None
230 }
231 }
232
233 pub fn search(&self, query: &str) -> Vec<&SessionMetadata> {
234 let query_lower = query.to_lowercase();
235 self.sessions
236 .values()
237 .filter(|metadata| {
238 metadata.title.to_lowercase().contains(&query_lower)
239 || metadata
240 .tags
241 .iter()
242 .any(|tag| tag.to_lowercase().contains(&query_lower))
243 })
244 .collect()
245 }
246}