From 2460b3f46c4e02bd7e980f7653192d1aa9e69001 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Mon, 29 Jun 2026 11:04:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20=E9=A1=B9=E7=9B=AE=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E7=8A=B6=E6=80=81=E6=91=98=E8=A6=81=E2=80=94=E2=80=94?= =?UTF-8?q?=E4=BE=A7=E6=A0=8F=E7=8A=B6=E6=80=81=E7=82=B9=20+=20=E9=9C=80?= =?UTF-8?q?=E4=BA=BA=E5=B7=A5=E5=85=B3=E6=B3=A8=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit projectOut 此前不返 summary,前端拿到空 {} → 所有项目恒灰、无关注数。新增 store.projectSummary(projectId):running(executor+planner 在跑)/blocked/attention(待审批闸 +needs_attention)/executing/alive 计数,projectOut 带上。前端 adaptProject 用 running 判青点、 attention 进紫徽标。 Co-Authored-By: Claude Opus 4.8 --- src/api/server.ts | 14 +++++++------- src/store/store.ts | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/api/server.ts b/src/api/server.ts index 2a6b26a..fa6ccad 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -26,8 +26,8 @@ function dataDir(): string { } /** Project 出参:附加 hasTodoJson(/todo/todo.json 是否存在,每次序列化时算) */ -function projectOut(p: Project): Project & { hasTodoJson: boolean } { - return { ...p, hasTodoJson: hasTodoJson(p.repoPath) }; +function projectOut(p: Project, store: Store): Project & { hasTodoJson: boolean; summary: ReturnType } { + return { ...p, hasTodoJson: hasTodoJson(p.repoPath), summary: store.projectSummary(p.id) }; } export interface ApiOptions { @@ -59,7 +59,7 @@ export function buildServer(opts: ApiOptions): FastifyInstance { }); // ---------- Projects ---------- - app.get('/api/projects', () => store.listProjects().map(projectOut)); + app.get('/api/projects', () => store.listProjects().map((p) => projectOut(p, store))); app.post('/api/projects', (req) => { const b = req.body as Record; @@ -83,14 +83,14 @@ export function buildServer(opts: ApiOptions): FastifyInstance { autoApproveExec: Boolean(pick('autoApproveExec', s.autoApproveExec)), budgetUsd: ((): number | null => { const v = pick('budgetUsd', s.budgetUsd); return v === null ? null : Number(v); })(), budgetPeriod: pick('budgetPeriod', s.budgetPeriod) as 'day' | 'month', - })); + }), store); }); app.get('/api/projects/:id', (req) => { const { id } = req.params as { id: string }; const p = store.getProject(id); if (!p) throw new StoreError(`项目不存在: ${id}`); - return projectOut(p); + return projectOut(p, store); }); // 部分更新项目配置(校验在 Store.patchProject) @@ -113,14 +113,14 @@ export function buildServer(opts: ApiOptions): FastifyInstance { if (b.checks !== undefined) patch.checks = b.checks === null || b.checks === '' ? null : String(b.checks); if (b.agentRules !== undefined) patch.agentRules = b.agentRules === null || b.agentRules === '' ? null : String(b.agentRules); if (b.models !== undefined) patch.models = b.models === null || b.models === '' ? null : (b.models as PatchProjectInput['models']); - return projectOut(store.patchProject(id, patch)); + return projectOut(store.patchProject(id, patch), store); }); // 项目重排序(侧栏拖动):body { order: [projectId, ...] } app.post('/api/projects/reorder', (req) => { const b = (req.body ?? {}) as { order?: unknown }; if (!Array.isArray(b.order)) throw new StoreError('order 必须是项目 id 数组'); - return store.reorderProjects(b.order.map(String)).map(projectOut); + return store.reorderProjects(b.order.map(String)).map((p) => projectOut(p, store)); }); // ---------- 用户级全局默认配置(per-user,新建项目默认值来源)---------- diff --git a/src/store/store.ts b/src/store/store.ts index c9065c8..14a366b 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -218,6 +218,21 @@ export class Store { return rows.map(rowToProject); } + /** 项目任务状态摘要(供侧栏状态点 + 关注数徽标):running=agent 在跑、attention=需人工。 */ + projectSummary(projectId: string): { executing: number; running: number; blocked: number; alive: number; attention: number } { + const rows = this.db.prepare(`SELECT status, COUNT(*) AS n FROM tasks WHERE project_id = ? GROUP BY status`).all(projectId) as Array<{ status: string; n: number }>; + const by: Record = {}; + for (const r of rows) by[r.status] = r.n; + const g = (s: string): number => by[s] || 0; + const executing = g('executing'); + const running = executing + g('analyzing') + g('speccing'); // agent 在跑:executor + planner + const attention = g('plan_review') + g('spec_review') + g('exec_review') + g('needs_attention'); // 待审批闸 + 需人工 + const TERMINAL = new Set(['done', 'cancelled']); + let alive = 0; + for (const [s, n] of Object.entries(by)) if (!TERMINAL.has(s)) alive += n; + return { executing, running, blocked: g('blocked'), alive, attention }; + } + /** 重排项目(侧栏拖动):按给定 id 顺序写 sort_order;未列出的排在后面、相对顺序不变。 */ reorderProjects(orderedIds: string[]): Project[] { const txn = this.db.transaction(() => {