@@ -255,6 +256,8 @@ export default function App() {
) : view === "memory" ? (
+ ) : view === "usage" ? (
+
) : (
)}
diff --git a/sundynix-desktop/frontend/src/lib/api.ts b/sundynix-desktop/frontend/src/lib/api.ts
index ffaf16f..2d8b47b 100644
--- a/sundynix-desktop/frontend/src/lib/api.ts
+++ b/sundynix-desktop/frontend/src/lib/api.ts
@@ -139,6 +139,48 @@ export async function tenantCurrent(): Promise
{
};
}
+// ---- 我的用量明细(当前用户自己租户:余额 + 按天趋势 + 最近消耗)----
+export interface UsageDay {
+ day: string; // YYYYMMDD
+ total_tok: number;
+ credits_micro: number;
+ cost_micros: number;
+ task_count: number;
+}
+export interface UsageRecent {
+ task_id: string;
+ model: string;
+ total_tok: number;
+ credits_micro: number;
+ cost_micros: number;
+ currency: string;
+ ts: number;
+}
+export interface MyUsage {
+ from: string;
+ to: string;
+ balance_micro: number;
+ credit_enforce: boolean;
+ trend: UsageDay[];
+ totals: { total_tok: number; credits_micro: number; cost_micros: number; task_count: number };
+ recent: UsageRecent[];
+}
+
+export async function myUsage(days = 30): Promise {
+ const res = guard401(await fetch(`${GATEWAY}/api/v1/me/usage?days=${days}`, { headers: bearer() }));
+ if (!res.ok) throw new Error(`usage failed: ${res.status}`);
+ const d = (await res.json()) as Partial;
+ return {
+ from: d.from ?? "",
+ to: d.to ?? "",
+ balance_micro: d.balance_micro ?? 0,
+ credit_enforce: !!d.credit_enforce,
+ trend: d.trend ?? [],
+ totals: d.totals ?? { total_tok: 0, credits_micro: 0, cost_micros: 0, task_count: 0 },
+ recent: d.recent ?? [],
+ };
+}
+
// taskStatus: GET /api/v1/tasks/:id —— 任务生命周期状态(submitted/running/waiting/done/failed/timeout/rejected)。
// 轮询它来可靠捕获 waiting(审批中断)—— 不依赖易抢跑的实时 exec 事件。
export async function taskStatus(taskId: string): Promise<{ status: string; detail: string }> {
diff --git a/sundynix-desktop/frontend/src/shell/LeftNav.tsx b/sundynix-desktop/frontend/src/shell/LeftNav.tsx
index 98beac8..943aed1 100644
--- a/sundynix-desktop/frontend/src/shell/LeftNav.tsx
+++ b/sundynix-desktop/frontend/src/shell/LeftNav.tsx
@@ -7,11 +7,12 @@ import {
Bookmark,
Boxes,
Settings,
+ Coins,
type LucideIcon,
} from "lucide-react";
import { cn } from "../ui";
-export type ViewKey = "home" | "studio" | "kb" | "report" | "runs" | "memory" | "market" | "admin";
+export type ViewKey = "home" | "studio" | "kb" | "report" | "runs" | "memory" | "usage" | "market" | "admin";
interface Item {
key: ViewKey;
@@ -28,6 +29,7 @@ const ITEMS: Item[] = [
{ key: "report", label: "报告", icon: FileText, group: "BUILD", ready: true },
{ key: "runs", label: "运行", icon: Activity, group: "RUN", ready: true },
{ key: "memory", label: "记忆", icon: Bookmark, group: "MANAGE", ready: true },
+ { key: "usage", label: "用量", icon: Coins, group: "MANAGE", ready: true },
{ key: "market", label: "市场", icon: Boxes, group: "MANAGE" },
{ key: "admin", label: "管理", icon: Settings, group: "MANAGE" },
];
diff --git a/sundynix-desktop/frontend/src/shell/TopBar.tsx b/sundynix-desktop/frontend/src/shell/TopBar.tsx
index 946efa2..3bfafb0 100644
--- a/sundynix-desktop/frontend/src/shell/TopBar.tsx
+++ b/sundynix-desktop/frontend/src/shell/TopBar.tsx
@@ -20,16 +20,17 @@ function Light({ on, label }: { on: boolean; label: string }) {
);
}
-// 积分余额芯片:让用户在主产品里随时看到自己租户剩多少积分。
+// 积分余额芯片:让用户在主产品里随时看到自己租户剩多少积分。点击进「用量」页。
// 硬拦截开启且余额≤0 → 红色「余额不足」;偏低 → 橙;否则常态。
-function CreditChip({ tenant }: { tenant: TenantCtx }) {
+function CreditChip({ tenant, onClick }: { tenant: TenantCtx; onClick?: () => void }) {
const credits = tenant.credit_balance_micro / 1_000_000;
const empty = tenant.credit_enforce && credits <= 0;
const low = !empty && credits > 0 && credits < 10;
const tone = empty ? "border-danger/50 text-danger" : low ? "border-warn/50 text-warn" : "border-line text-slate-300";
return (
-
{empty ? "余额不足" : credits.toLocaleString("zh-CN", { maximumFractionDigits: credits < 100 ? 1 : 0 })}
-
+
);
}
// 顶栏:品牌 · 垂直切换 · 健康灯 · 积分余额 · 登录用户 + 登出(深色 + 毛玻璃)。
-export function TopBar({ user, tenant, onLogout, onCommand }: { user: AuthUser; tenant?: TenantCtx | null; onLogout: () => void; onCommand?: () => void }) {
+export function TopBar({ user, tenant, onLogout, onCommand, onOpenUsage }: { user: AuthUser; tenant?: TenantCtx | null; onLogout: () => void; onCommand?: () => void; onOpenUsage?: () => void }) {
const h = useHealth();
const { theme, toggle } = useTheme();
return (
@@ -85,7 +86,7 @@ export function TopBar({ user, tenant, onLogout, onCommand }: { user: AuthUser;