Stats history with year filter, folding and XLSX/CSV export; snippets, read-only links, commit suggestions, weekly review, offline copies, server speech-to-text, planning model, job reports; fal.ai health fix

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Sonnet 5.5 committed 2026-11-15 08:20:52 +01:00
1 parent c08c6c7436
commit 388ca96419
31 files changed
+1518 -82

No files matched your search

+99
View File
@@ -163,6 +163,14 @@ impl Store {
created_at INTEGER NOT NULL,
PRIMARY KEY (cwd, key)
);
CREATE TABLE IF NOT EXISTS share_links (
token TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
owner_id TEXT,
created_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS share_links_session ON share_links(session_id);
CREATE TABLE IF NOT EXISTS project_defaults (
cwd TEXT PRIMARY KEY,
data TEXT NOT NULL
@@ -744,6 +752,9 @@ impl Store {
let day_sql = "strftime('%Y-%m-%d', (ts + ?3) / 1000, 'unixepoch')";
let by_day = grouped(day_sql, "day")?;
// Long ranges are shown per month.
let by_month = grouped("strftime('%Y-%m', (ts + ?3) / 1000, 'unixepoch')", "month")?;
let first_ts: Option<i64> = conn.query_row("SELECT MIN(ts) FROM usage WHERE (?1 IS NULL OR user_id = ?1)", params![user], |r| r.get(0))?;
let by_model = grouped("provider || '/' || model", "model")?;
let by_provider = grouped("provider", "provider")?;
let by_agent = grouped("agent", "agent")?;
@@ -795,6 +806,8 @@ impl Store {
Ok(json!({
"total": total,
"by_day": by_day,
"by_month": by_month,
"first_ts": first_ts,
"by_day_model": by_day_model,
"by_model": by_model,
"by_provider": by_provider,
@@ -1526,6 +1539,59 @@ impl Store {
}
}
// --- weekly overview ----------------------------------------------------------------------
impl Store {
/// (chats with activity, failed checks, checks run) of a user's own top-level chats since `since` (ms).
pub fn weekly_counts(&self, user_id: &str, since: i64) -> Result<(i64, i64, i64)> {
let conn = self.conn.lock().unwrap();
let chats: i64 = conn.query_row("SELECT COUNT(*) FROM sessions WHERE owner_id = ?1 AND parent_id IS NULL AND updated_at >= ?2", params![user_id, since], |r| r.get(0))?;
let (failed, total): (i64, i64) = conn.query_row(
"SELECT COALESCE(SUM(c.status IN ('failed','timeout')),0), COUNT(*) FROM turn_checks c JOIN sessions s ON s.id = c.session_id WHERE s.owner_id = ?1 AND c.ts >= ?2",
params![user_id, since],
|r| Ok((r.get(0)?, r.get(1)?)),
)?;
Ok((chats, failed, total))
}
}
// --- read-only links ----------------------------------------------------------------------
impl Store {
pub fn add_share_link(&self, token: &str, session_id: &str, owner_id: &str, created_at: i64, expires_at: i64) -> Result<()> {
self.conn.lock().unwrap().execute(
"INSERT INTO share_links (token, session_id, owner_id, created_at, expires_at) VALUES (?1, ?2, ?3, ?4, ?5)",
params![token, session_id, owner_id, created_at, expires_at],
)?;
Ok(())
}
/// (token, created_at, expires_at) of the links of a chat, newest first.
pub fn share_links(&self, session_id: &str) -> Result<Vec<(String, i64, i64)>> {
let conn = self.conn.lock().unwrap();
let mut stmt = conn.prepare("SELECT token, created_at, expires_at FROM share_links WHERE session_id = ?1 ORDER BY created_at DESC")?;
let rows = stmt.query_map([session_id], |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)))?;
Ok(rows.collect::<std::result::Result<_, _>>()?)
}
/// The chat a link points to while it is valid.
pub fn share_link_session(&self, token: &str, now: i64) -> Result<Option<String>> {
let conn = self.conn.lock().unwrap();
Ok(conn.query_row("SELECT session_id FROM share_links WHERE token = ?1 AND expires_at > ?2", params![token, now], |r| r.get(0)).optional()?)
}
pub fn delete_share_link(&self, session_id: &str, token: &str) -> Result<()> {
self.conn.lock().unwrap().execute("DELETE FROM share_links WHERE session_id = ?1 AND token = ?2", params![session_id, token])?;
Ok(())
}
/// Links are dropped with their chat and when expired.
pub fn purge_share_links(&self, now: i64) -> Result<()> {
self.conn.lock().unwrap().execute("DELETE FROM share_links WHERE expires_at <= ?1 OR session_id NOT IN (SELECT id FROM sessions)", params![now])?;
Ok(())
}
}
// --- defaults of new chats in a project -------------------------------------------------------
impl Store {
@@ -1710,3 +1776,36 @@ fn snippet_in(m: &Message, needle: &str) -> Option<String> {
}
None
}
#[cfg(test)]
mod link_tests {
use super::*;
#[test]
fn share_links_expire_and_can_be_revoked() {
let dir = std::env::temp_dir().join(format!("cc-links-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let store = Store::open(&dir.join("t.db")).unwrap();
store.add_share_link("tok1", "s1", "u", 1_000, 5_000).unwrap();
store.add_share_link("tok2", "s1", "u", 2_000, 9_000).unwrap();
// Valid until the expiry time, then gone.
assert_eq!(store.share_link_session("tok1", 4_999).unwrap().as_deref(), Some("s1"));
assert_eq!(store.share_link_session("tok1", 5_000).unwrap(), None);
assert_eq!(store.share_link_session("nope", 1).unwrap(), None);
// Revoking removes only the named link of that chat.
store.delete_share_link("other", "tok2").unwrap();
assert!(store.share_link_session("tok2", 3_000).unwrap().is_some());
store.delete_share_link("s1", "tok2").unwrap();
assert_eq!(store.share_link_session("tok2", 3_000).unwrap(), None);
std::fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn weekly_counts_of_an_empty_user_are_zero() {
let dir = std::env::temp_dir().join(format!("cc-weekly-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let store = Store::open(&dir.join("t.db")).unwrap();
assert_eq!(store.weekly_counts("nobody", 0).unwrap(), (0, 0, 0));
std::fs::remove_dir_all(&dir).unwrap();
}
}