feat: v0.3.0 re-release - add dashboard & sessions, hide pi option
- Usage dashboard: KPIs, model split, daily bars, year heatmap, recent requests - Sessions management: list/preview/archive/delete with streaming preview - Double-click daily bar opens per-model breakdown modal - Paginated recent requests (30/page) - Hide pi option from nav (kept code, just removed from UI) - Timezone fixes: today/day_key/heatmap now use local calendar day - Streaming preview with 20MB byte cap + 500-char frontend clip - README attribution to kimicode-dashboard (MIT, JochenYang)
This commit is contained in:
1 parent
139af18826
commit
32de98bff5
19 files changed
+3648
-35
No files matched your search
@@ -0,0 +1,54 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { SummaryResult } from "../types/dashboard";
|
||||
|
||||
export type DashboardRange = "today" | "7d" | "30d" | "all";
|
||||
|
||||
const RANGE_STORAGE_KEY = "kimi-switch-dashboard-range";
|
||||
|
||||
export function useDashboard() {
|
||||
const [range, setRange] = useState<DashboardRange>(() => {
|
||||
try {
|
||||
const stored = localStorage.getItem(RANGE_STORAGE_KEY) as DashboardRange | null;
|
||||
if (stored === "today" || stored === "7d" || stored === "30d" || stored === "all") {
|
||||
return stored;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return "30d";
|
||||
});
|
||||
|
||||
const [data, setData] = useState<SummaryResult | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await invoke<SummaryResult>("get_summary", { range });
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
setError(msg);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [range]);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const changeRange = useCallback((next: DashboardRange) => {
|
||||
setRange(next);
|
||||
try {
|
||||
localStorage.setItem(RANGE_STORAGE_KEY, next);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { range, changeRange, data, loading, error, refresh };
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type {
|
||||
ActionResponse,
|
||||
PreviewResult,
|
||||
SessionStatusFilter,
|
||||
SessionsResult,
|
||||
} from "../types/sessions";
|
||||
|
||||
export function useSessions() {
|
||||
const [status, setStatus] = useState<SessionStatusFilter>("active");
|
||||
const [workspace, setWorkspace] = useState<string>("all");
|
||||
const [data, setData] = useState<SessionsResult | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await invoke<SessionsResult>("list_sessions", {
|
||||
status,
|
||||
workspace: workspace === "all" ? null : workspace,
|
||||
});
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
setData(null);
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [status, workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const archiveSession = useCallback(
|
||||
async (workspaceId: string, sessionId: string) => {
|
||||
await invoke<ActionResponse>("archive_session", { workspaceId, sessionId });
|
||||
await refresh();
|
||||
},
|
||||
[refresh]
|
||||
);
|
||||
|
||||
const unarchiveSession = useCallback(
|
||||
async (workspaceId: string, sessionId: string) => {
|
||||
await invoke<ActionResponse>("unarchive_session", {
|
||||
workspaceId,
|
||||
sessionId,
|
||||
});
|
||||
await refresh();
|
||||
},
|
||||
[refresh]
|
||||
);
|
||||
|
||||
const deleteSession = useCallback(
|
||||
async (workspaceId: string, sessionId: string, sessionStatus?: string) => {
|
||||
await invoke<ActionResponse>("delete_session", {
|
||||
workspaceId,
|
||||
sessionId,
|
||||
status: sessionStatus ?? null,
|
||||
});
|
||||
await refresh();
|
||||
},
|
||||
[refresh]
|
||||
);
|
||||
|
||||
const deleteWorkspace = useCallback(
|
||||
async (workspaceId: string) => {
|
||||
await invoke<ActionResponse>("delete_workspace", {
|
||||
workspaceId,
|
||||
confirm: true,
|
||||
});
|
||||
await refresh();
|
||||
},
|
||||
[refresh]
|
||||
);
|
||||
|
||||
const getPreview = useCallback(
|
||||
async (workspaceId: string, sessionId: string, sessionStatus?: string) => {
|
||||
return invoke<PreviewResult>("get_session_preview", {
|
||||
workspaceId,
|
||||
sessionId,
|
||||
status: sessionStatus ?? null,
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
status,
|
||||
setStatus,
|
||||
workspace,
|
||||
setWorkspace,
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
setError,
|
||||
refresh,
|
||||
archiveSession,
|
||||
unarchiveSession,
|
||||
deleteSession,
|
||||
deleteWorkspace,
|
||||
getPreview,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user