Skip to content

Commit 66f1d25

Browse files
committed
feat: dream run history persistence and dashboard visualization
- Add dream_runs table for structured run history storage - Persist completed dream runs with task breakdown, durations, output sizes, smart note counts, and memory change diffs - Add Tauri backend command to query dream run history per project - Replace basic dreamer page with per-project run history cards showing expandable task tables, memory changes, and relative timestamps - Add getMemoryCountsByStatus helper for pre/post-run diffing
1 parent c0d5f81 commit 66f1d25

File tree

17 files changed

+977
-114
lines changed

17 files changed

+977
-114
lines changed

packages/dashboard/src-tauri/src/commands.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
use crate::{config, db, log_parser, AppState};
12
use tauri::State;
2-
use crate::{db, log_parser, config, AppState};
33

44
// ── Memory commands ─────────────────────────────────────────
55

@@ -148,6 +148,17 @@ pub fn get_dream_state(state: State<'_, AppState>) -> Result<Vec<db::DreamStateE
148148
db::get_dream_state(&conn).map_err(|e| e.to_string())
149149
}
150150

151+
#[tauri::command]
152+
pub fn get_dream_runs(
153+
state: State<'_, AppState>,
154+
project_path: Option<String>,
155+
limit: Option<usize>,
156+
) -> Result<Vec<db::DreamRun>, String> {
157+
let path = state.get_db_path()?;
158+
let conn = db::open_readonly(&path).map_err(|e| e.to_string())?;
159+
db::get_dream_runs(&conn, project_path.as_deref(), limit.unwrap_or(20))
160+
}
161+
151162
#[tauri::command]
152163
pub fn enqueue_dream(
153164
state: State<'_, AppState>,
@@ -175,15 +186,21 @@ pub fn get_cache_events(max_lines: Option<usize>) -> Vec<log_parser::CacheEvent>
175186
}
176187

177188
#[tauri::command]
178-
pub fn get_session_cache_stats(max_lines: Option<usize>, limit: Option<usize>) -> Vec<log_parser::SessionCacheStats> {
189+
pub fn get_session_cache_stats(
190+
max_lines: Option<usize>,
191+
limit: Option<usize>,
192+
) -> Vec<log_parser::SessionCacheStats> {
179193
let log_path = log_parser::resolve_log_path();
180194
let entries = log_parser::read_log_tail(&log_path, max_lines.unwrap_or(5000));
181195
let events = log_parser::extract_cache_events(&entries);
182196
log_parser::aggregate_session_cache_stats(&events, limit.unwrap_or(5))
183197
}
184198

185199
#[tauri::command]
186-
pub fn get_cache_events_from_db(limit: Option<usize>, since_timestamp: Option<i64>) -> Vec<db::DbCacheEvent> {
200+
pub fn get_cache_events_from_db(
201+
limit: Option<usize>,
202+
since_timestamp: Option<i64>,
203+
) -> Vec<db::DbCacheEvent> {
187204
db::get_cache_events_from_db(limit.unwrap_or(200), since_timestamp)
188205
}
189206

@@ -253,7 +270,10 @@ pub async fn get_available_models() -> Vec<String> {
253270
} else {
254271
vec![
255272
"opencode".to_string(),
256-
format!("{}/.local/bin/opencode", std::env::var("HOME").unwrap_or_default()),
273+
format!(
274+
"{}/.local/bin/opencode",
275+
std::env::var("HOME").unwrap_or_default()
276+
),
257277
"/usr/local/bin/opencode".to_string(),
258278
"/opt/homebrew/bin/opencode".to_string(),
259279
]
@@ -286,10 +306,7 @@ pub async fn test_embedding_endpoint(
286306
model: String,
287307
api_key: Option<String>,
288308
) -> Result<String, String> {
289-
let url = format!(
290-
"{}/embeddings",
291-
endpoint.trim_end_matches('/')
292-
);
309+
let url = format!("{}/embeddings", endpoint.trim_end_matches('/'));
293310

294311
let body = serde_json::json!({
295312
"model": model,
@@ -305,7 +322,8 @@ pub async fn test_embedding_endpoint(
305322
.timeout(std::time::Duration::from_secs(10))
306323
.build()
307324
.map_err(|e| format!("Failed to create client: {}", e))?;
308-
let mut req = client.post(&url)
325+
let mut req = client
326+
.post(&url)
309327
.header("Content-Type", "application/json")
310328
.json(&body);
311329

packages/dashboard/src-tauri/src/config.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,8 @@ use std::path::PathBuf;
55
pub fn resolve_user_config_path() -> PathBuf {
66
let config_dir = std::env::var("XDG_CONFIG_HOME")
77
.map(PathBuf::from)
8-
.unwrap_or_else(|_| {
9-
dirs::home_dir()
10-
.unwrap_or_default()
11-
.join(".config")
12-
});
13-
config_dir
14-
.join("opencode")
15-
.join("magic-context.jsonc")
8+
.unwrap_or_else(|_| dirs::home_dir().unwrap_or_default().join(".config"));
9+
config_dir.join("opencode").join("magic-context.jsonc")
1610
}
1711

1812
pub fn resolve_project_config_path(project_path: &str) -> PathBuf {
@@ -48,8 +42,7 @@ pub fn write_config(path: &PathBuf, content: &str) -> Result<(), String> {
4842
std::fs::create_dir_all(parent)
4943
.map_err(|e| format!("Failed to create directory: {}", e))?;
5044
}
51-
std::fs::write(path, content)
52-
.map_err(|e| format!("Failed to write config: {}", e))?;
45+
std::fs::write(path, content).map_err(|e| format!("Failed to write config: {}", e))?;
5346
Ok(())
5447
}
5548

@@ -67,18 +60,26 @@ pub struct ProjectConfigEntry {
6760
pub fn discover_project_configs() -> Vec<ProjectConfigEntry> {
6861
let opencode_db = {
6962
let data_dir = if cfg!(target_os = "windows") {
70-
match dirs::data_dir() { Some(d) => d, None => return vec![] }
63+
match dirs::data_dir() {
64+
Some(d) => d,
65+
None => return vec![],
66+
}
7167
} else {
7268
std::env::var("XDG_DATA_HOME")
7369
.map(PathBuf::from)
7470
.unwrap_or_else(|_| {
75-
dirs::home_dir().unwrap_or_default().join(".local").join("share")
71+
dirs::home_dir()
72+
.unwrap_or_default()
73+
.join(".local")
74+
.join("share")
7675
})
7776
};
7877
data_dir.join("opencode").join("opencode.db")
7978
};
8079

81-
if !opencode_db.exists() { return vec![]; }
80+
if !opencode_db.exists() {
81+
return vec![];
82+
}
8283

8384
let conn = match rusqlite::Connection::open_with_flags(
8485
&opencode_db,
@@ -106,7 +107,9 @@ pub fn discover_project_configs() -> Vec<ProjectConfigEntry> {
106107
let mut entries = Vec::new();
107108
for (name, worktree) in rows {
108109
let root_config = PathBuf::from(&worktree).join("magic-context.jsonc");
109-
let alt_config = PathBuf::from(&worktree).join(".opencode").join("magic-context.jsonc");
110+
let alt_config = PathBuf::from(&worktree)
111+
.join(".opencode")
112+
.join("magic-context.jsonc");
110113
let root_exists = root_config.exists();
111114
let alt_exists = alt_config.exists();
112115

0 commit comments

Comments
 (0)