From 7f512f146108ac328c76e0b6b3df8b3f83beecdc Mon Sep 17 00:00:00 2001 From: claude_dev Date: Fri, 9 Oct 2026 13:01:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend+api):=20=E5=9B=A0=E5=AD=90?= =?UTF-8?q?=E6=B8=85=E5=8D=95=E5=8A=A0=E6=89=B9=E6=AC=A1=E5=88=86=E5=89=B2?= =?UTF-8?q?=E7=BA=BF=E2=80=94=E2=80=94=E5=8F=B0=E8=B4=A6=E5=8F=8D=E6=9F=A5?= =?UTF-8?q?=E5=87=BA=E7=94=9F=E6=89=B9=E6=AC=A1=E5=88=86=E7=BB=84=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=20[vps]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户复验追加小需求:21 条因子平铺看不出批次归属。 - 后端:hypotheses_list 每条 factor 附 batch(台账 registered name→job 映射,新→旧先见者胜;查不到=null 归「早期」); 清单排序改按批次新→旧,无批次垫底 - 前端:批次切换处插虚线行「── 第 MM-DD HH:MM 批 · job-… ──」, 无批次段前插「── 早期(无批次记录) ──」 测试:factors[0].batch.jobId 断言;后端 28 绿+前端 76 绿+构建过 --- ...2026-10-08-pipeline-console-ux-redesign.md | 1 + frontend/src/api/pipeline.ts | 1 + .../src/views/pipeline/HypothesisPool.vue | 25 ++++++++++++++----- sanguo_api/routes_pipeline.py | 15 ++++++++++- tests/api/test_routes_pipeline_hypotheses.py | 5 +++- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/docs/superpowers/specs/2026-10-08-pipeline-console-ux-redesign.md b/docs/superpowers/specs/2026-10-08-pipeline-console-ux-redesign.md index c3ef64c4..5b6ea836 100644 --- a/docs/superpowers/specs/2026-10-08-pipeline-console-ux-redesign.md +++ b/docs/superpowers/specs/2026-10-08-pipeline-console-ux-redesign.md @@ -123,3 +123,4 @@ spec 逐节拍板 → writing-plans 出实施计划 → 分域派单:②权重 - 2026-10-09:LLM 链两连修上产(端点错配 85d14a1 + client 全流式化 acc273b,QA 同款);**生产首批 origin:decomposer 因子落册**(高管增持卡,D7 零实绩破局);终局诊断=响应孤儿化(服务端成功+浏览器断连无感知),治本=**分解 job 化**(QA 任务字典范式:202 立返+后台任务+卡片 decomposeJob 轮询+decompose_jobs 台账表)——§7 件④地基落,总设计 §13 P5.3 同步;生产全程验证通过(job-20261009101513:202 秒回→3 轮 2m43s→8 因子 0 失败);件①扩为三件套(产物清单+批次历史+轮次进度,QA 调研:progress 字段结构/tasks-list 端点照抄,currentRound 摆设我们用真轮数,WS 直播归 D2) - 2026-10-09 晚:**D7 真实现落地**(8823 体验稿逐条过审拍板后动工):件①1a=列表项内联 `factors` 清单(registry hypothesis 血统反查:名/表达式/origin/落成日;卡片单因子名 factorId 前端退役);1b=新端点 `GET /pipeline/hypotheses/{id}/decompose-jobs`(台账新→旧,QA tasks/list 形状)+卡片折叠区按需拉取;1c=`run_decompose` 每轮 `on_progress` 回调→内存 job `progress`(currentRound/totalRounds/passed/regen/message;台账仍只记起跑+终态),worker 协议改收 job_id;件②=factors 端点带 origin/hypothesis+工厂「来源」列(⚡decomposer/✍manual 徽标+回链假设池);件③=todos 聚合加 queued/data_check 卡琥珀待办(分解转 building 即消行,人工卡点②显性化);件④=前端一次性结果弹层退役,结果持久落卡。测试=后端 TestD7Visibility 六件+前端三件套三测 - 2026-10-09 深夜:**D7 VPS 验收反馈四条处理**(877723a 首班上产后用户实测):①因子详情页落地——`GET /pipeline/factors/{name}/detail`(出生档案:origin/来源假设/出生批次=台账含该名最新 job/表达式/落成日)+前端 `FactorDetail.vue`(`/pipeline/factors/:name` 路由),卡片因子点名与工厂行点击均进详情;②总览无变化=数据驱动非缺陷(唯一卡已 building,无 queued 卡时琥珀项不出现——落新卡即现;地铁图属 D1-D6);③工厂来源列改 min-width 200+徽标/回链竖排;④轮次进度起跑即报「第 1/3 轮 · 生成候选中…」(初代生成期不带轮次字样的体验缺陷) +- 2026-10-09 深夜补:件①1a 因子清单加**批次分割线**(用户复验追加)——每条因子附出生批次(台账 registered 反查 name→job 映射,查不到=null 归「早期」),清单按批次新→旧分组,批间虚线行「── 第 MM-DD HH:MM 批 · job-… ──」 diff --git a/frontend/src/api/pipeline.ts b/frontend/src/api/pipeline.ts index c4c916aa..9adf5119 100644 --- a/frontend/src/api/pipeline.ts +++ b/frontend/src/api/pipeline.ts @@ -34,6 +34,7 @@ export interface HypothesisFactor { origin: string createdAt: string | null status: string + batch?: { jobId: string; startedAt: string } | null // 出生批次(台账反查,null=早期/无记录) } export type FactorStatus = 'incubating' | 'assessable' | 'promoted' | 'decaying' | 'retired' diff --git a/frontend/src/views/pipeline/HypothesisPool.vue b/frontend/src/views/pipeline/HypothesisPool.vue index a4904829..f880d3b7 100644 --- a/frontend/src/views/pipeline/HypothesisPool.vue +++ b/frontend/src/views/pipeline/HypothesisPool.vue @@ -250,12 +250,22 @@ function progText(h: Hypothesis): string { 点名进工厂 · 血统=registry 反查
-
- {{ f.name }} - {{ f.expression }} - {{ f.createdAt }} -
+
@@ -359,6 +369,9 @@ function progText(h: Hypothesis): string { .fac-name:hover { text-decoration: underline; } .fac-expr { flex: 1; min-width: 0; font-family: var(--mono); font-size: 10px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .fac-date { color: var(--text-3); font-size: 10px; white-space: nowrap; } +.fac-batch { font-family: var(--mono); font-size: 9.5px; color: var(--cyan-dim); border-top: 1px dashed rgba(0, 184, 204, 0.35); margin-top: 4px; padding-top: 3px; } +.fac-batch:first-child { border-top: none; margin-top: 0; padding-top: 0; } +.fac-batch.early { color: var(--text-3); border-top-color: var(--border); } .batch { border: 1px solid var(--border); border-radius: var(--r-sm); margin-bottom: 5px; } .batch-h { display: flex; align-items: center; gap: 7px; padding: 5px 8px; cursor: pointer; font-size: 11px; flex-wrap: wrap; } .batch-h:hover { background: var(--cyan-soft); } diff --git a/sanguo_api/routes_pipeline.py b/sanguo_api/routes_pipeline.py index b34a71c9..50594446 100644 --- a/sanguo_api/routes_pipeline.py +++ b/sanguo_api/routes_pipeline.py @@ -955,9 +955,22 @@ def hypotheses_list() -> dict: "status": e.get("status")}) except Exception: # noqa: BLE001 registry 读失败不拖垮卡片列表 logger.warning("hypotheses factors 反查失败,如实空清单", exc_info=True) + by_hyp = {} + # 批次归属(验收追加:清单加批次分割线)——台账新→旧先见者胜, + # 每条因子附 birth batch;台账查不到(超 limit/远古)=None 归「早期」 + batch_of: dict[tuple[str, str], dict[str, Any]] = {} + for hyp_id in by_hyp: + for job in pipeline_store.list_decompose_jobs(_pipeline_db(), hyp_id): + for c in job.get("registered") or []: + batch_of.setdefault((hyp_id, c["name"]), + {"jobId": job["jobId"], + "startedAt": job["startedAt"]}) for it in items: fs = by_hyp.get(it["id"]) or [] - fs.sort(key=lambda f: f["createdAt"] or "", reverse=True) + for f in fs: + f["batch"] = batch_of.get((it["id"], f["name"])) + fs.sort(key=lambda f: (f["batch"] or {}).get("startedAt") or "", # noqa: E501 批次新→旧,无批次垫底 + reverse=True) it["factors"] = fs return {"items": items} diff --git a/tests/api/test_routes_pipeline_hypotheses.py b/tests/api/test_routes_pipeline_hypotheses.py index eda1b0cf..2cd6c079 100644 --- a/tests/api/test_routes_pipeline_hypotheses.py +++ b/tests/api/test_routes_pipeline_hypotheses.py @@ -382,13 +382,16 @@ class TestD7Visibility: _, queue = fake_llm queue.append(DECOMP_GOOD) hyp = _seed_card(client) - assert _wait_job(client, hyp)["status"] == "completed" + job = _wait_job(client, hyp) + assert job["status"] == "completed" items = client.get("/api/v1/pipeline/hypotheses").json()["items"] item = next(i for i in items if i["id"] == hyp) assert [f["name"] for f in item["factors"]] == ["llm_mom20"] assert item["factors"][0]["origin"] == "decomposer" assert item["factors"][0]["expression"].startswith("cs_rank") assert item["factors"][0]["createdAt"] # 落成日=首版 effective_from + # 批次归属(分割线数据源):registered 含该名的最新 job + assert item["factors"][0]["batch"]["jobId"] == job["jobId"] def test_progress_reports_rounds_while_running(self, client, fake_llm, tmp_path, monkeypatch):