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:
1 parent
c08c6c7436
commit
388ca96419
31 files changed
+1518
-82
No files matched your search
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user