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:
KimiSwitch Dev committed 2026-07-28 09:16:21 +08:00
1 parent 139af18826
commit 32de98bff5
19 files changed
+3648 -35

No files matched your search

+54
View File
@@ -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 };
}
+107
View File
@@ -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,
};
}