diff --git a/app/src/adapt.js b/app/src/adapt.js index 3e16b6f..91dc877 100644 --- a/app/src/adapt.js +++ b/app/src/adapt.js @@ -28,6 +28,7 @@ export function adaptProject(p) { state, pending: s.pending || 0, agents: s.executing || 0, // 透传给 ConfigPanel 用的原始字段 model: p.model, verifyCmd: p.verifyCmd, maxRetries: p.maxRetries, repoPath: p.repoPath, + logo: p.logo, budgetUsd: p.budgetUsd ?? null, budgetPeriod: p.budgetPeriod ?? 'month', }; } @@ -135,14 +136,20 @@ export function deriveGlobal(projects, agentsResp) { }; } -// 跨项目 agent 概览。token/成本属 ⑧ 标 ★ 的新 /api/usage(预算特性),暂留 0。 -export function deriveAgentSummary(projects, agentsResp) { +// 跨项目 agent 概览。token/成本来自 /api/usage 的 cost 明细(按当期窗口聚合)。 +export function deriveAgentSummary(projects, agentsResp, cost) { + const byPid = new Map((cost?.byProject || []).map((c) => [c.projectId, c])); + const tokensTotal = (cost?.byProject || []).reduce((sum, c) => sum + (c.tokens || 0), 0); return { - tokensWeek: 0, runsWeek: 0, costWeek: 0, activeNow: agentsResp?.totalActive ?? 0, - byProject: projects.map((p) => ({ - id: p.id, name: p.name, hue: p.hue, - active: p.agents || 0, runs: 0, tokens: 0, - })), + tokensWeek: tokensTotal, runsWeek: 0, costWeek: cost?.total || 0, activeNow: agentsResp?.totalActive ?? 0, + byProject: projects.map((p) => { + const c = byPid.get(p.id); + return { + id: p.id, name: p.name, hue: p.hue, + active: p.agents || 0, runs: 0, + tokens: c?.tokens || 0, cost: c?.costUsd || 0, + }; + }), }; } diff --git a/app/src/app.jsx b/app/src/app.jsx index 16f736e..eaeb96c 100644 --- a/app/src/app.jsx +++ b/app/src/app.jsx @@ -154,6 +154,10 @@ export function App() { if (!evt.projectId || evt.projectId === currentId) { setEventsRaw((list) => [evt, ...list].slice(0, 60)); } + if (evt.type === 'budget.exceeded') { + const pay = evt.payload || {}; + toast('warn', `⚠ 项目超预算已暂停 · 已用 $${(pay.spend ?? 0).toFixed?.(2) ?? pay.spend} / $${pay.budget}`); + } clearTimeout(refreshTimer.current); refreshTimer.current = setTimeout(() => { loadProject(currentId); @@ -178,7 +182,7 @@ export function App() { const events = eventsRaw.map(adaptEvent); const quota = adaptQuota(usage); const global = deriveGlobal(projects, agentsResp); - const agentSummary = deriveAgentSummary(projects, agentsResp); + const agentSummary = deriveAgentSummary(projects, agentsResp, usage?.cost); const NONTERMINAL = (s) => !['done', 'cancelled', 'decomposed'].includes(s); const counts = { gate: gates.length, @@ -240,7 +244,7 @@ export function App() { counts={counts} onSync={onSync} onToggleConfig={() => setShowConfig(!showConfig)} /> : null} - {showConfig && project ? setShowConfig(false)} /> : null} diff --git a/design/ui_kits/console/Topbar.jsx b/design/ui_kits/console/Topbar.jsx index 3db99ee..c8922ff 100644 --- a/design/ui_kits/console/Topbar.jsx +++ b/design/ui_kits/console/Topbar.jsx @@ -107,16 +107,23 @@ function Topbar({ project, counts, onSync, onToggleConfig, t, theme, onToggleThe ); } -function ConfigPanel({ project, onSave, onClose, onSync, t }) { +function ConfigPanel({ project, onSave, onClose, onSync, t, lang }) { const { Button, Input, Select } = window.MaestroDesignSystem_a6a290; const [concurrency, setConcurrency] = React.useState(String(project.concurrency)); const [autonomy, setAutonomy] = React.useState(project.autonomy); const [logo, setLogo] = React.useState(project.logo || ''); const [verifyCmd, setVerifyCmd] = React.useState(project.verifyCmd || ''); const [model, setModel] = React.useState(project.model || ''); + const [budgetUsd, setBudgetUsd] = React.useState(project.budgetUsd != null ? String(project.budgetUsd) : ''); + const [budgetPeriod, setBudgetPeriod] = React.useState(project.budgetPeriod || 'month'); + const BL = lang === 'en' + ? { budget: 'Budget $ (empty=∞)', period: 'Period', day: 'Daily', month: 'Monthly', ph: 'e.g. 20' } + : { budget: '预算 $(空=不限)', period: '周期', day: '按天', month: '按月', ph: '如 20' }; const save = () => onSave({ concurrency: Number(concurrency), autonomy, logo: logo || null, verifyCmd: verifyCmd || null, model: model || null, + budgetUsd: budgetUsd.trim() === '' ? null : Number(budgetUsd), + budgetPeriod, }); return (
@@ -134,6 +141,11 @@ function ConfigPanel({ project, onSave, onClose, onSync, t }) { +
+ +