diff --git a/CHANGELOG.md b/CHANGELOG.md index a3fb2803..1f47f13d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ 个人长报告里的本命盘、婚盘、月亮盘和 Vargas I 不再叠在同一块:每张盘和它的标题进一张卡片,连续的卡片走原来的两栏网格;窄屏和打印仍是单栏。滚动目录高亮时,正文里的图不会被拆掉重画。导出的 `.md` 仍带引擎 SVG、不含网页用的 JSON 围栏。Skill 版本不变。 +## 2026-09-09 — 生时校正对照卡按候选分钟算经历和时段,交付旁白不再被裁成一句 + +生时校正给出范围后,对照卡上的经历吻合和未来时段按引擎里全部候选分钟来算,不再只算引擎分数最高的三个。卡片上的三列是后验可能性最高的三个分钟;某一列还没算出来时写灰字「这一分钟还没对照」,不会再出现「强相关 0 · 有关联 0 · 弱关联 0」,也不会把「没算」说成「未来一年没有明显的窗口」。 + +三列里相同的性格句只出现在卡片顶部一次,例如「三个时间的事业盘都在巨蟹座:…」。每列改成时间、相对可能性、有差别的性格句,再加上两行人话:经历对照(「8 件经历里 7 件对得上,最不合的是 2024 年 5 月那段感情」)和下一个值得留意的时段;没有窗就写「未来一年没有明显的时段」。列内不再有小标题。 + +交付轮助手正文在可以收口时最多留三句(范围、经历与吻合率、边界句),中间采集轮仍只留一句复述。 + +右侧当前盘面改成北印度菱形图,宫位表收进折叠;换升时刻默认折起来,摘要写「换升时刻 · N 个」。Skill 版本不变。 + + +## 2026-09-09 — 点「更像这个」后三列对照卡留在原处 + +生时校正点「更像这个」之后,三列对照卡不再整张消失。被点的列先显示「正在采用…」,成功后变成「已采用」,其它列仍可改选。下面那句「已采用 HH:MM,新建对话即按这个时间排盘。」跟卡片同一条左边线,不再单独贴到屏幕左边。Skill 版本不变。 + +## 2026-09-09 — 生时校正时间轴收到和对话同一列 + +常驻时间轴的读数和刻度改跟助手气泡、候选卡同一条左边线:桌面让出 `space-8` 和头像列,窄屏让出 `space-4` 和头像列。条的背景和底边仍拉满聊天列,高度不变。Skill 版本不变。 + +## 2026-09-09 — 采集题「没有 / 记不清」改到问题下面 + +口述采集时,「没有」「记不清」两个按钮改挂在同一条助手气泡的问题下方,不再浮在输入框上面。点选语义不变:没有仍跳过这方面,记不清仍先放着。Skill 版本不变。 + ## 2026-09-09 — 首页「深入看今日」算完会写出三节回答,思考区不再漏英文词渣 从首页点「深入看今日」时,按「时运」计算,不再被改成「综合」。正文按三节写:今日趋势、适合推进 / 需要避开、一个行动。某一节没写出字会再试一次;分段写作时不能再调排盘工具。思考区按整句过滤,不会再出现缺词英文或 `run-jyotish-` 残段。空回答仍然不扣点。Skill 版本不变。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 8999b95d..d2e81cfd 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -9526,6 +9526,38 @@ - 复发自:无 - 修复版本:待发布 +## BUG-614 | 三列卡把「还没对照」显示成 0 和没有窗口 + +- 状态:resolved +- 首次发现:2026-09-09 +- 最近更新:2026-09-09 +- 影响面:`scripts/rectification/refinement_packet.py`(`column_times_for_compare`)、`frontend/src/lib/rectification-agentic/v9/divergence-panel.ts`、`rectification-range-delivery.tsx`、`user-copy.ts` +- 用户现象:区间卡第 2、3 列写「强相关 0 · 有关联 0 · 弱关联 0」,三列都写「未来一年没有明显的窗口」;相同性格句每列重复一遍。 +- 触发条件:推断层后验前三与引擎分数前三不是同一组分钟;客户端按列时间查 `event_dasha_ledger_by_time` / `prospective_windows_by_time` 找不到键。 +- 根因:`column_times_for_compare` 只取引擎分数前三,卡片列按后验前三投影。缺键时投影把空表当成 0 次吻合、把缺窗口当成「没有窗」。 +- 修复:`columns` 改为引擎 `candidate_times` 去重全集(上限 9)。客户端缺键写 `fit: null` / `windows: null`,灰字「这一分钟还没对照」,不得回退成 0。经历对照改成「N 件里 M 件对得上」;无窗写「未来一年没有明显的时段」。三列共有的性格句只出现在卡顶一次。 +- 验证:`tests/test_rectification_v5_services.py::test_compare_ledgers_cover_all_engine_candidate_times`;`frontend/tests/rectification-range-delivery-20260907.test.ts` 后验前三与引擎前三不同时 `fit` 非 null,缺键 DOM 出现「这一分钟还没对照」、不出现 `0 · 0 · 0`。 +- 防复发:`set(ledgers) == set(candidate_times)`;用户可见文案禁用「强相关 / 有关联 / 弱关联」;列内不得再出现 `

`「经历对照 / 往后 12 个月」。 +- 相关记录:BUG-597、BUG-615、`TASK-rectification-compare-card-polish-20260909.md` +- 复发自:BUG-597(三列对照的 by_time 只覆盖引擎前三) +- 修复版本:待发布 + +## BUG-615 | 交付旁白被证据轮裁句裁成一句 + +- 状态:resolved +- 首次发现:2026-09-09 +- 最近更新:2026-09-09 +- 影响面:`frontend/src/lib/rectification-agentic/v9/collect-prompt.ts`、`agent-run.ts` +- 用户现象:交付轮助手正文只剩「校正到这里可以收口了。」一类单句,BUG-595 定的三句(范围、经历与吻合率、边界句)被裁掉。 +- 触发条件:访谈收口出区间卡;该轮 `action === "evidence"`,模型写了三句以上。 +- 根因:BUG-606 的 `trimEvidenceTurnBody`(超过两句只留首句)对所有 evidence 轮生效。交付也走 evidence 轮。 +- 修复:先跑 `persistNextInterviewIfIdle`。仍有下一问时继续裁到一句;该函数返回 `terminalNote` 时保留 ≤3 句。裁剪不得发生在 idle 之前,否则交付四句会先被裁掉。裁完与流式正文不同时发 `answer.delta replace`,气泡跟落库同一段。 +- 验证:`frontend/tests/rectification-v9-agent.test.ts` 交付轮模型写四句 → 落库三句;普通证据轮三句 → 一句。`frontend/tests/rectification-collect-prompt.test.ts` 锁 `trimSpokenTurnForInterview`。 +- 防复发:`agent-run` 必须在 idle 之后调用 `trimSpokenTurnForInterview(answerText, interviewIdle?.terminalNote === true)`。不得把交付轮重新裁成一句。 +- 相关记录:BUG-606、BUG-595、BUG-614、`TASK-rectification-compare-card-polish-20260909.md` +- 复发自:BUG-606(证据轮裁句没有把交付轮排除) +- 修复版本:待发布 + ## BUG-616 | 报告页 22 张北印盘叠在同一位置 - 状态:resolved @@ -9557,3 +9589,51 @@ - 相关记录:BUG-616、`TASK-report-md-page-20260906.md`、`TASK-report-chart-layout-fix-20260909.md` - 复发自:无(09-06 直渲结构在星盘进入后才变成可感知卡顿) - 修复版本:待发布 + +## BUG-618 | 采集题「没有 / 记不清」浮在输入框上方,离问题太远 + +- 状态:resolved +- 首次发现:2026-09-09 +- 最近更新:2026-09-09 +- 影响面:`rectification-agentic-chat.tsx` 口述采集快捷回答、`globals.css` `.rectification-collect-replies` +- 用户现象:口述采集时,「没有」「记不清」两个按钮贴在底部输入框上方,和问题气泡分开,看起来像多余的条。 +- 触发条件:`collect_spoken` 焦点激活且未 busy。 +- 根因:BUG-605 把快捷回答挂在 `composer-wrap` 输入框上方,没有跟选择题卡一样嵌进同一条助手气泡。 +- 修复:两个按钮改到问题 `afterAnswer` 里,紧挨题干;composer 不再渲染它们。点选仍走原来的「没有 / 记不清」短路。 +- 验证:`frontend/tests/rectification-spoken-collect.test.ts`(按钮在 afterAnswer,不在 composer-wrap)。 +- 防复发:采集快捷回答不得回到 composer。选择题卡继续用嵌入卡,口述采集用 `rectification-collect-reply`。 +- 相关记录:BUG-605 +- 复发自:无 +- 修复版本:待发布 + +## BUG-619 | 生时校正时间轴比对话内容更贴边 + +- 状态:resolved +- 首次发现:2026-09-09 +- 最近更新:2026-09-09 +- 影响面:`globals.css` `.rectification-timeline` 水平内边距 +- 用户现象:校正对话上方的时间轴比下面的候选卡、助手气泡更靠屏幕外边,左右对不齐。 +- 触发条件:打开生时校正,尤其是已出现三列候选卡时。 +- 根因:条用 `space-5` / 窄屏 `space-4`,对话列是 `space-8` / `space-4` 再加头像让位 `--assistant-content-inset`;条又不在 `.message-list` 里,继承不到该变量。 +- 修复:条的内边距改为对话沟槽加头像让位;背景和底边发丝线仍拉满。高度不变。 +- 验证:`frontend/tests/rectification-timeline-20260909.test.ts`。 +- 防复发:时间轴水平内边距必须含 `--assistant-content-inset`,不得写回贴边的 `space-5`。 +- 相关记录:无 +- 复发自:无 +- 修复版本:待发布 + +## BUG-620 | 点「更像这个」后三列卡消失,收尾句贴左不对齐 + +- 状态:resolved +- 首次发现:2026-09-09 +- 最近更新:2026-09-09 +- 影响面:`rectification-agentic-chat.tsx` 区间交付卡、`verified_idle` 收尾句、`globals.css` `.rectification-pending-note` +- 用户现象:点「更像这个」后三列时间卡忽然消失,底下出现未对齐的「已采用 HH:MM,新建对话即按这个时间排盘。」 +- 触发条件:交付卡出现后点其中一列「更像这个」;尤其是没有后续核对题、`next_user_action` 为 `start_consultation` 时。 +- 根因:`showSelectionCards` 用 `!busy` 整张卸卡;采用请求把对话标成 busy。收尾句用未定义样式的 `.rectification-pending-note` 直接挂在消息列表里,没有助手列让位。采用后若 live offer 锚点被清掉,卡也不再回到原消息。 +- 修复:采用中或已采用时 busy 不再卸卡;用 resultId 锁住首次出示卡片的那条消息。收尾句改到卡片下方,样式与卡片同一条左边线。已采用列按钮保持按下并禁用。不恢复输入框上方状态条。 +- 验证:`frontend/tests/rectification-adopt-cards-stay-20260909.test.ts`、`frontend/tests/rectification-candidate-offer-anchor.test.ts`。 +- 防复发:交付卡不得因采用 busy 卸载;`verified_idle` 不得使用无 inset 的裸段落;不得把采用状态写回 composer。 +- 相关记录:BUG-595、BUG-619 +- 复发自:无 +- 修复版本:待发布 diff --git a/docs/tasks/PROGRESS-rectification-compare-card-polish-20260909.md b/docs/tasks/PROGRESS-rectification-compare-card-polish-20260909.md new file mode 100644 index 00000000..a66654a7 --- /dev/null +++ b/docs/tasks/PROGRESS-rectification-compare-card-polish-20260909.md @@ -0,0 +1,52 @@ +# PROGRESS · 三列对比卡修正与减法(BUG-614/615,2026-09-09) + +工作树:`.worktrees/rectification-compare-card-polish-20260909` +分支:`codex/rectification-compare-card-polish-20260909` +任务书:`docs/tasks/TASK-rectification-compare-card-polish-20260909.md` +基线:任务书写 `origin/staging` @ `365ad799`;本 worktree 按开工时的 `origin/staging` `3005e36d`(任务书本身)切出。合入前 rebase 到当时的 `origin/staging`(含 `17b36f3a` 咨询 BUG-612/613、`584a56bd` 报告图布局 brief、`ca921c3f` 采集按钮/时间轴/已采用列,BUG-618~620)。已采用列继续 `disabled={busy || adopted}`,不把 BUG-618 冲掉。 + +本单状态:**待验收**。合入 staging 时更新落点 SHA。 + +预检:本机已跑 `python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45`(含 `diagnose_external_engine_adapters.py --json`),远端可见性 `verified`。前端用 worktree 里指向主仓的 `frontend/node_modules` 符号链接。 + +未改:采用 RPC、各道门槛、`SCORE_DELTA`、四选项合同、时间轴、Skill 版本 / `references/conversation-strategy.md`。 + +## 做了什么 + +- **BUG-614** `column_times_for_compare(..., limit=9)` 覆盖引擎 `candidate_times` 去重全集。客户端 `lookupByTime` 缺键 → `fit: null` / `windows: null`,灰字「这一分钟还没对照」,不再把空表渲染成 `0 · 0 · 0`。 +- **BUG-615** `trimSpokenTurnForInterview` 在 `persistNextInterviewIfIdle` **之后**执行:仍有下一问裁到一句;`terminalNote` 保留 ≤3 句。不得在 `streamAttempt` 里先裁。裁完若与流式正文不同,再发一条 `answer.delta` `replace`,气泡与落库一致。 +- **卡片减法** 两列及以上共有性格句提到卡顶并改写成「两个/三个时间的…都在…」;列内 `fit_line` / `window_line`;无 `

`;禁用「强相关 / 有关联 / 弱关联」。 +- **右栏** `VedicChartSvg` 北印星盘;宫位表与换升时刻默认 `
`(summary「换升时刻 · N 个」);显示层并入同一折叠。换分钟只高亮变化宫,无动画。 + +## 三栏 + +| 用例 | 原值 | 新值 | 理由 | +| --- | --- | --- | --- | +| 引擎 5 个候选、卡片后验前三与引擎前三不同 | 后验列查不到键 → `0 · 0 · 0` / 「没有窗口」 | `by_time` 含全部候选分钟;列 `fit`/`windows` 有数 | 决策 1:按引擎候选集算,不是按卡片前三 | +| `by_time` 缺某一列的键 | 空数组当 0、当没有窗 | `fit === null`,DOM「这一分钟还没对照」 | 没算 ≠ 没有 | +| 三列 D10 同在巨蟹 | 三列各写一遍 | 卡顶一句「三个时间的事业盘都在巨蟹座:…」 | 相同句只写一次 | +| 交付轮模型写 4 句 | BUG-606 裁成 1 句 | `terminalNote` 时留 3 句 | 决策 2;BUG-595 三句口径 | +| 普通证据轮 3 句 | 1 句 | 仍 1 句 | BUG-606 采集轮不变 | +| 右栏本命 | 宫位表常驻 | 北印星盘 + 宫位表折叠 | 读屏仍可打开表 | +| 右栏换升时刻 | 逐分钟常驻列表 | 默认折叠「换升时刻 · N 个」 | 时间轴已表达同一件事 | + +## 测试 + +- `frontend` `./node_modules/.bin/tsc --noEmit`:exit 0 +- `npm run lint`:0 error / 107 warning(仓库既有 warning,无新增 error) +- 任务书切片 `tsx --test tests/rectification-*.test.ts tests/agent-voice-copy-contract.test.ts`(排除 database):**1063 passed / 0 failed** +- 本单针对性:`rectification-v9-agent.test.ts` 交付 4→3 句、证据 3→1 句;`rectification-range-delivery-20260907.test.ts` 后验前三有 `fit`、缺键「还没对照」;`rectification-board-model.test.ts` 北印星盘 + 换升默认折叠 +- `.venv/bin/python -m pytest tests/test_rectification_v5_services.py::RectificationV5ServicesTest::test_compare_ledgers_cover_all_engine_candidate_times` 与 `test_compare_returns_ledger_and_windows_by_candidate_time`:**2 passed**。同文件 `test_block_scan_seven_events_finishes_within_fifteen_seconds` 本机 41s 超 15s 预算,与本单无关(既有壁钟断言,见 BUG-570 P2) +- 浏览器真人走查:无登录态,未见真实交付卡/右栏像素 + +## 偏离 + +1. 任务书基线 `365ad799`;worktree 从已推任务书的 `3005e36d` 切出。未把后续 staging 提交 rebase 进来。 +2. 代表分钟缺 `by_time` 键时,经历 ledger 仍可回退到该分钟自己的 `eventDashaLedger`(不是填 0);窗口没有回退。缺键列只要 `windows == null` 仍显示「还没对照」。 +3. 单列不把性格句提到卡顶(任务书写的是三列共有)。 +4. 浏览器真人走查:本会话无登录态,未见真实交付卡/右栏像素。合同测试覆盖 DOM 字符串,不把它们写成已经看见真实会话。 +5. 裁句后补一条 `answer.delta replace`。任务书只要求 idle 之后裁句;不补这条的话流式气泡会停在未裁正文。 + +## 环境缺口 + +无登录态、无生产/staging 走查。不把合同测试写成已经看见真实像素。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index a7002cf5..7a7b2fa2 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -87,7 +87,7 @@ | `TASK-rectification-timeline-fix-20260909.md` | `PROGRESS-rectification-timeline-fix-20260909.md` | 时间轴修复单:宽度读数改含两端分钟数(与 BUG-593 报告/卡片一致);标记改读推断层候选全集,被淘汰分钟留在原地变空心(客户端投影只含 active,DESIGN §10 描述在真实数据下画不出) | 已验收通过 | `92e3d5e7`(BUG-602~603) | | `TASK-rectification-conversation-economy-20260909.md` | `PROGRESS-rectification-conversation-economy-20260909.md` | 对照竞品后产品拍板三条:开场三句讲做法 + 一次收多件(推翻 opening brief「不要一次说完/不举例」与 SKILL L52);采集题「没有 / 记不清」按钮(不做示例骨架条);每轮只留一句(方法句进活动记录、点选旁白去领先落后、证据轮正文一句复述 + 服务端裁剪);Skill 10.0.19 | 已验收通过(2 条 P3 备注) | `aa7ccb30` + `1453fb16`(BUG-604~606,Skill 10.0.19) | | `TASK-consultation-daily-empty-answer-20260909.md` | `PROGRESS-consultation-daily-empty-answer-20260909.md` | 首页「深入看今日」计算完成却 `empty_answer`:入口定的 `timing` 被模型改成 `general`(`canonicalDomainPlan` 以模型为准),分段写作标题与今日格式错位,分段 `maxSteps=1` 且工具仍可调 → 模型在分段里再调工具、零正文;思考流按 chunk 过滤漏出缺词英文 | 待验收 | `codex/consultation-daily-empty-answer-20260909`(BUG-612~613);走查 `docs/testing/consultation-daily-empty-answer-20260909.md` | -| `TASK-rectification-compare-card-polish-20260909.md` | `PROGRESS-rectification-compare-card-polish-20260909.md` | 三列卡两列经历对照 0、三列无未来窗:引擎按引擎前三算 by_time,卡片按推断前三查表,键不匹配(BUG-614);交付旁白被 BUG-606 裁成一句(BUG-615);设计减法:相同性格句只写一次、经历对照说人话、删小标题、右栏本命宫位改北印星盘、换升时刻默认折叠 | 待执行 | `codex/rectification-compare-card-polish-20260909`(BUG-614~615) | +| `TASK-rectification-compare-card-polish-20260909.md` | `PROGRESS-rectification-compare-card-polish-20260909.md` | 三列卡两列经历对照 0、三列无未来窗:引擎按引擎前三算 by_time,卡片按推断前三查表,键不匹配(BUG-614);交付旁白被 BUG-606 裁成一句(BUG-615);设计减法:相同性格句只写一次、经历对照说人话、删小标题、右栏本命宫位改北印星盘、换升时刻默认折叠 | 待验收 | `codex/rectification-compare-card-polish-20260909`(BUG-614~615) | ### 聊天主链路与首页 diff --git a/docs/testing/rectification-scenarios-20260907.md b/docs/testing/rectification-scenarios-20260907.md index 45cc9e63..fb405c09 100644 --- a/docs/testing/rectification-scenarios-20260907.md +++ b/docs/testing/rectification-scenarios-20260907.md @@ -84,11 +84,13 @@ 期望: -- 交付轮只见一张卡:范围、对照了 N 件经历、至多三列并排(相对可能性、性格处事、经历对照、往后 12 个月、「更像这个」)、底部边界句 -- 点「更像这个」即采用;已采用列按钮显示「已采用」,其它列仍可点改选 +- 交付轮只见一张卡:范围、对照了 N 件经历、至多三列并排。相同性格句只出现在卡顶一次。每列是时间、相对可能性、有差别的性格句、人话经历对照(「N 件里 M 件对得上」)、下一个时段或「未来一年没有明显的时段」、「更像这个」;底部边界句。列内没有小标题。 +- 某一列还没对照时只见灰字「这一分钟还没对照」,不见「强相关 0 · 有关联 0 · 弱关联 0」,也不把「没算」写成「没有窗口」 +- 点「更像这个」即采用;卡片留在原处,被点的列显示「正在采用…」再变成「已采用」并禁用,其它列仍可点改选 +- 采用后的收尾句跟卡片同一列,不要单独贴左,也不要出现在输入框上方 - 卡片下方「查看验证报告」默认收起,打开后是真正的表格 - 助手正文只有三句(范围、经历与吻合率、边界句),不含「排盘用」「方法1」或 Technique Audit -- 不见「相对支持度」「当前推荐」「采用此时间」「按这个范围用」「再补一件经历」「都不像」「说不好」 +- 不见「相对支持度」「当前推荐」「采用此时间」「按这个范围用」「再补一件经历」「都不像」「说不好」「强相关」「有关联」「弱关联」 - 输入框上方没有「先这样,先看当前范围」,也没有采用后状态条 ## 6. 七领域问完后下一问必须是职业题或直接出区间卡 @@ -166,6 +168,6 @@ - 这一条消息只触发一次候选比较,账本写入三件 - 下一问不再是已经说过的那三个领域 - 只报一件时也只比较一次,下一问是既有逐领域采集,正文没有「还有吗 / 还能想起别的吗」;第一道逐领域题干末尾可以有一次「想到别的也可以一起说」 -- 口述采集输入框上方有「没有」「记不清」,没有「先这样」 +- 口述采集问题下方有「没有」「记不清」,没有「先这样」,输入框上方不再挂这两个按钮 - 每轮助手正文一句复述(可另接一句范围变化);气泡里没有「本轮对照了…」,也没有「很有帮助 / 很有价值 / 很有分量 / 特别有用」 diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index 895edaf0..65b522fa 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -229,7 +229,7 @@ The birth-time rectification session is the consultation transcript plus a house | `question-live` | the asking message carries the embedded card or the spoken stem | enabled, placeholder points at the card | | `question-gap`, retries left | one timeline live row “正在准备下一个问题…”, refetching on a 2s timer up to two retries | enabled | | `question-gap`, retries spent | “没有拿到下一个问题。” and a 44px “重新加载” | enabled | -| `verified_idle` | one closing line `postAdoptVerifyDone` after adopted reverse-verify is exhausted; no spinner, no reload | enabled | +| `verified_idle` | one closing line `postAdoptVerifyDone` under the still-visible range card (same assistant column); no spinner, no reload | enabled | | `choice-pending` | the answered card (`data-selected` fill, a top row “正在记录…”) and the same live row from “正在记录本次选择…” through the follow-up turn | enabled (typing queues), stop visible | | `candidates` | one range-delivery card of up to three compare columns (highest posterior first; “更像这个” adopts) under the offering message; a closed “查看验证报告” fold | enabled | | `adopting` | “正在采用 HH:MM…” through the follow-up turn | enabled (typing queues), stop visible | @@ -252,7 +252,7 @@ The birth-time rectification session is the consultation transcript plus a house - **Life-event evidence:** after deterministic questionnaire completion, render three structured event rows by default and allow up to six. Each row uses a domain select, a precision select, and a matching year/month/day control; free-form descriptions are not part of scoring. - **Candidate result:** keep the reported range, candidate interval, and active-time status visually separate. Delivery shows one range card: title (range + event count), up to three compare columns (time, relative likelihood, D9/D10/nakshatra traits, event-fit counts, next-12-month windows, “更像这个”), and the representative-minute boundary. An eight-method report sits in a closed `
` fold. Support numbers stay on the house board. Low confidence keeps evidence editing open; medium offers save or add evidence; high uses a separate confirmation action and never labels a column as the true birth time. - **Evidence accessibility:** every row keeps visible labels, validation errors use live regions, add/remove controls retain 44px targets, and scoring/confirmation loading states disable duplicate submission without hiding the existing evidence. -- **One-question guide:** the guided journey renders only the persisted `nextAction` and one server-selected question. A deterministic question is visible immediately; Agent wording may replace it without changing the question identity, domain, precision request, progress, or permissions. The composer explicitly permits an approximate year. Spoken collect, when the focus is live and the turn is not busy, shows two 44px secondary buttons above the input, class `rectification-collect-reply` (not `rectification-step-state`): 「没有」 posts the same declined path as typing 没有; 「记不清」 posts skipped. Stop on spoken collect is not a composer button: `CHOICE_STOP_LABEL` (“先这样,先看当前范围”) stays on choice cards, and generating turns keep “停止回答”. The readonly range line is a status sentence, not a stop control. Discriminator cards fold “为什么问这题” under the stem. Hovering or selecting an option does not reveal an `answer_impact` time line. The method sentence (`vargaSentence`) lives in the expanded activity timeline, not in the spoken bubble. The composer has no `rectification-step-state` status sentence and no `rectification-composer-meta`. +- **One-question guide:** the guided journey renders only the persisted `nextAction` and one server-selected question. A deterministic question is visible immediately; Agent wording may replace it without changing the question identity, domain, precision request, progress, or permissions. The composer explicitly permits an approximate year. Spoken collect, when the focus is live and the turn is not busy, shows two 44px secondary buttons under the spoken question in the same assistant bubble, class `rectification-collect-reply` (not `rectification-step-state`): 「没有」 posts the same declined path as typing 没有; 「记不清」 posts skipped. The composer never hosts those chips. Stop on spoken collect is not a composer button: `CHOICE_STOP_LABEL` (“先这样,先看当前范围”) stays on choice cards, and generating turns keep “停止回答”. The readonly range line is a status sentence, not a stop control. Discriminator cards fold “为什么问这题” under the stem. Hovering or selecting an option does not reveal an `answer_impact` time line. The method sentence (`vargaSentence`) lives in the expanded activity timeline, not in the spoken bubble. The composer has no `rectification-step-state` status sentence and no `rectification-composer-meta`. - **Draft review:** natural-language answers become one inline review card. The evidence domain is read-only and uses its Chinese label; precision controls which exact year, month, or day input is available. Incomplete drafts keep edit and skip paths visible, while confirmation is disabled until the structured date is valid. Status and errors use polite or assertive live regions without clearing the persisted journey. - **Scoring and retry:** `score_pending` is a quiet progress surface with cancellable bounded polling and no manual compare control. `retry_scoring` preserves the confirmed evidence and exposes one explicit retry action. Refresh and device changes resume from the persisted action rather than inferring progress from copy. - **Guided candidate states:** low confidence presents the saved candidate range and either another evidence question or a safe finish; medium confidence can save the range but never apply a representative minute; high confidence names both “候选时间” and “当前排盘使用时间” before explicit confirmation; ready states that the current chart time changed while the original report remains preserved. No state calls a candidate the true birth minute. @@ -475,7 +475,7 @@ or user IDs. Agent 的 live 标记只有 `InlineSpinner` 一种。曾经并存的 canvas 小球(`thinking-orbs`)已移除,不得再引入第二种 live 标记。 -校正面的所有等待复用行内等待:进入前的 hydration 在揭幕之前完成,进入后唯一的等待形态是时间线 live 行(含「正在准备下一个问题…」这一条独立 live 行)。区间交付卡只挂在最新那条采用旁白下面,不得留在更早的采集/区分题下;采集题还没答完时不出卡。卡上至多三列并排,点「更像这个」即采用该列分钟,按钮显示「正在采用…」或「已采用」。采用后前事核对结束走 `verified_idle`:一行收尾文案,没有 live 行、没有重载、没有采用状态条。 +校正面的所有等待复用行内等待:进入前的 hydration 在揭幕之前完成,进入后唯一的等待形态是时间线 live 行(含「正在准备下一个问题…」这一条独立 live 行)。区间交付卡只挂在最新那条采用旁白下面,不得留在更早的采集/区分题下;采集题还没答完时不出卡。卡上至多三列并排,相同性格句只写一次,点「更像这个」即采用该列分钟,按钮显示「正在采用…」或「已采用」。采用过程中整张卡留在原处,不得因 `busy` 卸掉。采用后前事核对结束走 `verified_idle`:一行收尾文案跟在卡片下面、与助手列对齐,没有 live 行、没有重载、没有采用状态条。卡片与右栏细则见 §11、§12。 首页只揭幕一次。揭幕前的加载屏分两阶段:先取账户、模型目录与会话列表,再并行取当前会话消息、推荐问题、今日星语与校正入口摘要,并预热校正分包;全部就绪或 4 秒预算到期(`BOOTSTRAP_PREPARE_TIMEOUT_MS`)才揭幕。揭幕后不得再出现任何阻塞等待或组件级 spinner:推荐问题未到显示安全默认问题,今日星语未到显示静态文案「今天的星语还没写出来。」(不带 `aria-busy`),校正卡用无摘要文案,内容到达后静默替换。切换到消息尚未缓存的会话时消息区留白并只给 `sr-only` 文案,不转圈;揭幕后按侧栏顺序后台预取最近 5 条会话(`SESSION_PREFETCH_COUNT`)让常见切换零等待。轨道环消失后不得再换一套动效继续等。 @@ -514,6 +514,8 @@ Admin 的 antd `` 是独立设计系统,不在此表。 **二、条高固定,从首次渲染起就占位。** 这是正确性要求,不是视觉偏好:条在滚动容器外,高度一变就改 `clientHeight`,而没有任何观察者会因此重新跟随——贴底的读者会看着内容悄悄滑出视野底部且无法恢复。同理,Case 未就绪时渲染**等高的空骨架**(`data-state="pending"`),不得用 spinner、不得用「正在加载」文案、不得让条晚一步挂载。桌面 64px,767px 以下 56px;56px 等于既有的 `--rectification-jump-clearance`(44px 触控尺寸 + `space-3`),不新造常数。 +读数和轴的水平内边距对齐助手列:桌面 `space-8 + --assistant-content-inset`,767px 以下 `space-4 + --assistant-content-inset`,与 `.message-list` 沟槽和头像让位相同。底边发丝线和背景仍拉满聊天列,不把条做成第二条滚动容器。 + **三、条上只有两个元素**:轴本体(区间带 + 候选标记 + 刻度)与区间读数(`05:07–05:09 · 3 分钟`)。以下一律不得加回来——每一条都是被明确砍掉的: | 不得出现 | 理由 | @@ -521,7 +523,7 @@ Admin 的 antd `` 是独立设计系统,不在此表。 | 范围变化句 | 服务端已写进落库助手消息,BUG-588 防复发要求它在那里;条上再写=同句同屏两次 | | 「仍在范围内 N / 已排除 N」计数与图例 | 实心=在内、空心=已排除,答完第一题即自明;图例是过度提示的典型形态 | | 阶段标签(时段 / 分钟) | `block_scan` / `minute` 是后台词汇,贴近 BUG-043 红线 | -| 「已对照 N 件经历」 | 归交付卡,那里每列已带经历对照计数 | +| 「已对照 N 件经历」 | 归交付卡;卡上是人话「N 件里 M 件对得上」,不是计数条 | | 「时间轴只读」注脚 | 在解释一个不存在的东西;没有元素看起来可点,就不需要声明不可点 | | 预计剩余时间、定时器插值动画 | 与报告进度条同一条原则:不得演出后端没做的动作 | @@ -533,3 +535,23 @@ Admin 的 antd `` 是独立设计系统,不在此表。 - 跨午夜的窗口(`late_night` 是 23:00–03:59,末端早于始端)按次日展开,轴保持单调。 - 条上不得出现 `rectification-step-state` 类名(BUG-575 防复发)。 +## 11. 区间交付卡 + +交付对象是区间里并排的候选分钟,不是支持度数字,也不是逐行空差异。 + +**相同的句子只写一次。** 两列或三列共有的 D9 / D10 / 月宿性格句提到卡片顶部(「三个时间的事业盘都在巨蟹座:做事以照顾人为主」)。列内只留有差别的句子;某列没有差别句就不显示性格块,不写「同上」。单列不提升共享句。 + +每列 = 时间 + 相对可能性 + 至多三行 + 「更像这个」。列内不再有 `

`。经历对照由服务端拼成一行:「8 件经历里 7 件对得上,最不合的是 2024 年 5 月那段感情」。未来窗一行:「下一个值得留意的时段:2027 年 3 月前后(事业)」;算过但没有窗写「未来一年没有明显的时段」;`by_time` 缺键写灰字「这一分钟还没对照」,不得回退成 `0 · 0 · 0` 或把「没算」说成「没有窗」。用户可见文案不得出现「强相关 / 有关联 / 弱关联」。 + +卡顶保留范围与经历数;卡底边界句保留;「查看验证报告」默认收起。不预标「排盘用」。点「更像这个」走现有 accept RPC。采用过程中整张卡留在原处;已采用列按钮禁用。收尾句跟卡片同一列。 + +引擎 `event_dasha_ledger_by_time` / `prospective_windows_by_time` 的键是引擎 `candidate_times` 去重全集(上限 9),不是引擎分数前三。卡片仍只投影后验前三。 + +## 12. 校正右栏盘面 + +右栏回答「当前按哪一分钟在看盘」,不回答「选哪一分钟」。顶部不加任何新说明文字。 + +**本命宫位改北印星盘。** 用 `VedicChartSvg` 渲染代表分钟的 `house_table`。宫位表退到星盘下方的 `
`(读屏与复制仍可用)。换分钟时只高亮变化的宫(`changedHouses` / `lagnaChanged`),不做动画。星盘组件在校正面 `dynamic()` 分包里,不进首页首屏。 + +**换升时刻默认折叠。** 时间轴已常驻显示范围与候选点,右栏「换升时刻」整段默认收进 `
`,summary 为「换升时刻 · N 个」;逐分钟 LayerChips 不变。「显示层」子折叠并入同一折叠。确认门折叠、两段本命上升折叠保留原样。 + diff --git a/frontend/docs/VOICE.md b/frontend/docs/VOICE.md index 8ec79964..d363f6ca 100644 --- a/frontend/docs/VOICE.md +++ b/frontend/docs/VOICE.md @@ -32,10 +32,10 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、 | 当前可信区间是 05:00–05:07,代表分钟 05:00。代表分钟只是代表性候选,不是已确认的唯一出生分钟。 | 已经从最初的 30 分钟收到 05:00–05:07 这 7 分钟。代表分钟是 05:00,这只是代表性候选,不是已确认的唯一出生分钟。 | 同一边界语义,带进度,少法务腔。 | | 剩下的题分不开当前候选。更站得住的范围是 05:15,代表分钟 05:15。 | 再问下去也分不出更准的时间了。眼下更站得住的是 05:15。 | 单分钟不要把范围和代表分钟念两遍;「当前候选」是内部词。 | | 可以从下面的时间里选一个采用。 / 下面的时间可以先用着。 | 我按你说的经历认真分析过了,下面是这次的结果。 | 这是认真分析后的结果,不是随便先用着;采用仍不是确认。区间交付卡必须就在这句下面。 | -| 相对支持度 16 · 采用此时间 | 一行至多三列。每列写相对可能性、性格处事、经历对照、往后 12 个月,点「更像这个」即采用。不预标「排盘用」。 | 交付对象是区间里并排的候选分钟,不是四张卡上的支持度数字。 | -| 候选 A 预测下一次事业变动更可能在 2027 年 3 月附近。 | 每列用服务器算好的 1–2 个事件窗;没有窗就写「未来一年没有明显的窗口」。 | 窗口按候选分钟各算,模型不写。 | +| 相对支持度 16 · 采用此时间 | 一行至多三列。相同性格句只写一次。每列:相对可能性、有差别的性格句、人话经历对照、下一个时段,点「更像这个」。缺数据写灰字「这一分钟还没对照」。不预标「排盘用」。 | 交付对象是区间里并排的候选分钟,不是四张卡上的支持度数字。 | +| 候选 A 预测下一次事业变动更可能在 2027 年 3 月附近。 | 每列用服务器拼好的一行时段;没有窗就写「未来一年没有明显的时段」;没算出来写「这一分钟还没对照」。 | 窗口按候选分钟各算,模型不写。不得把「没算」说成「没有窗」。 | | 「再补一件经历」后卡片消失、输入框上方空白 | 需要补经历时继续在输入框说下一件;交付卡不再提供「再补一件经历」按钮。 | 交付卡只负责选分钟。 | -| 采用后会拿盘外核对来验证。 / 已采用 04:53 · 范围 … / 改选 / 之后新建对话即按此时间排盘。 | 没有核对题:已采用 04:53,新建对话即按这个时间排盘。有核对题:前事核对到这里。之后新建对话即按已采用时间排盘;对不上随时改选。 | 采用状态在该列按钮上。`postAdoptVerifyDone` 在没有核对题时改短句。 | +| 采用后会拿盘外核对来验证。 / 已采用 04:53 · 范围 … / 改选 / 之后新建对话即按此时间排盘。 | 没有核对题:已采用 04:53,新建对话即按这个时间排盘。有核对题:前事核对到这里。之后新建对话即按已采用时间排盘;对不上随时改选。 | 采用状态在该列按钮上;整张卡留着。收尾句跟卡片同一列,不单独贴左,也不回输入框上方。 | | (采用后只念代表分钟)代表分钟是 05:12。 | 这 27 分钟里,事业方向、性格底色的判断是稳定的;婚恋(D9)会随分钟变,看盘时按范围读。这不是已确认的唯一出生分钟。 | 采用后先讲区间里什么稳、什么随分钟变;两句来自服务端,不是模型编的。 | | 当前问题已更新,请刷新后重新作答。 | 这一问刚换成新的,刷新后再答就行。 | 机器状态句改成下一步。 | | 好的,记下了。 | 记下了:2016 年 9 月入学、2020 年 6 月毕业。 | 只复述年-月和事件短语,不评价价值。 | @@ -45,7 +45,7 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、 | 生成中输入框变灰,回车没反应 | 生成中仍可打字;回车后显示「已排队,回答结束后发出」,可撤回 | 对标主流对话产品,焦点不丢。 | | 停止后出现红色告警条 | 已停止,已生成的内容保留;本次不会扣点。 | 停止是用户动作,不是出错。 | | 也可以再说一件你记得大概时间的事。 | 能分开候选的问题已经问完,先按现有材料给你结果。 / 还差带月份的经历,领域不限。 | 采集问完后交付或说清门槛,不再开放采集。 | -| 口述采集没有停止按钮,范围小字不可点 | 选择题卡内有「先这样,先看当前范围」;生成中有「停止回答」。口述采集输入框上方是「没有」「记不清」两个 44px 次要按钮,不是先这样。范围小字是状态句,不可点。 | 没有=拒答,记不清=本会话先放着。 | +| 口述采集没有停止按钮,范围小字不可点 | 选择题卡内有「先这样,先看当前范围」;生成中有「停止回答」。口述采集问题下方是「没有」「记不清」两个 44px 次要按钮,不是先这样,也不挂在输入框上方。范围小字是状态句,不可点。 | 没有=拒答,记不清=本会话先放着。 | | 只报一件后追问「还有吗,还能想起别的吗」 | 下一问仍是既有逐领域采集。第一道题干末尾由服务端加一次「想到别的也可以一起说。」 | 邀请不是要求;不插追问轮,也不重复开场。 | | 点选后旁白写「04:31–04:39 这段领先,05:00–05:07 这段落后」 | 已记录,范围收到 04:31–04:39。 | 旁白只说范围变没变。领先/落后留在卡内「答了会怎样」,不进气泡。 | | 每轮只有技法清单,不知道现在第几步 | 输入框上方有「第 N 步 · 原因 · 下一步」。 | 用户始终看得见该做什么。 | diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 3683f4b1..f14db588 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1518,18 +1518,6 @@ button:disabled { cursor: default; opacity: .45; } .composer-queue__recall:focus-visible { outline: 3px solid color-mix(in srgb, var(--color-focus) 56%, transparent); outline-offset: 2px; } .composer-wrap { position: sticky; bottom: 0; z-index: 2; min-width: 0; border-top: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent); padding: var(--space-3) var(--space-6) var(--space-4); background: var(--color-frosted); backdrop-filter: saturate(130%) blur(20px); } -.rectification-collect-replies { - width: min(760px, 100%); - display: flex; - flex-wrap: wrap; - gap: var(--space-2); - margin: 0 auto var(--space-2); -} -.rectification-collect-reply { - min-height: 44px; - min-width: 44px; - padding: 0 var(--space-4); -} /* Jump to latest: shared by both chat surfaces. Hangs off the composer wrap's top edge so it never reflows the transcript or the composer. */ .jump-to-latest { @@ -2698,6 +2686,9 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class The pending state keeps the same box for the same reason. */ .rectification-workspace__chat { --rectification-timeline-height: 64px; + /* Same token as `.message-list`: avatar 32px + gap. The bar sits outside + the transcript, so it cannot inherit that custom property from there. */ + --assistant-content-inset: calc(32px + var(--space-3)); } .rectification-timeline { height: var(--rectification-timeline-height); @@ -2706,7 +2697,9 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class flex-direction: column; justify-content: center; gap: var(--space-1); - padding: 0 var(--space-5); + /* Match the assistant column (message-list gutter + avatar inset), not the + chat-panel edge. The full-width background and hairline stay on the bar. */ + padding: 0 calc(var(--space-8) + var(--assistant-content-inset)); background: var(--color-canvas); border-bottom: 1px solid var(--color-border); overflow: hidden; @@ -2804,7 +2797,7 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class --rectification-timeline-height: 56px; } .rectification-timeline { - padding-inline: var(--space-4); + padding-inline: calc(var(--space-4) + var(--assistant-content-inset)); } } .rectification-workspace__chat .conversation { @@ -2965,6 +2958,17 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class border-radius: 0; background: transparent; } +.rectification-board__chart { + margin: 0; + min-width: 0; +} +.rectification-board__chart .personal-report-chart-svg { + margin-top: 0; +} +.rectification-board__chart .personal-report-chart-cell.is-changed { + fill: color-mix(in srgb, var(--color-action) 14%, transparent); + stroke: var(--color-action); +} .rectification-board .technique-audit { margin-top: 0; padding-top: 0; @@ -3089,6 +3093,18 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class font-weight: 600; line-height: 1.55; } +.rectification-collect-replies { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} +.rectification-collect-reply { + min-height: 44px; + min-width: 44px; + width: auto; + flex: 0 0 auto; + padding: 0 var(--space-4); +} /* The gap between a settled turn and its next question, as the last entry of the transcript: a live timeline row while refetching, then copy and a reload. */ .rectification-question-gap { @@ -3312,22 +3328,27 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class } .rectification-range-delivery__likelihood, .rectification-range-delivery__block p, -.rectification-range-delivery__more { +.rectification-range-delivery__more, +.rectification-range-delivery__shared, +.rectification-range-delivery__column > p { margin: 0; color: var(--color-ink-secondary); font-size: var(--type-caption); line-height: 1.5; } +.rectification-range-delivery__shared { + display: grid; + gap: var(--space-1); + padding: 0; + list-style: none; +} +.rectification-range-delivery__muted { + color: var(--color-ink-muted, var(--color-ink-tertiary)); +} .rectification-range-delivery__block { display: grid; gap: var(--space-1); } -.rectification-range-delivery__block h3 { - margin: 0; - font-size: var(--type-overline); - font-weight: 600; - color: var(--color-ink); -} .rectification-range-delivery__accept { justify-self: stretch; min-height: 44px; @@ -3382,6 +3403,14 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class text-align: left; font-size: var(--type-caption); } +.rectification-pending-note { + width: calc(100% - var(--assistant-content-inset)); + margin: var(--space-2) 0 var(--space-4); + margin-inline-start: var(--assistant-content-inset); + color: var(--color-ink-secondary); + font-size: var(--type-body-sm); + line-height: 1.55; +} .rectification-saved { width: calc(100% - var(--assistant-content-inset)); margin: 8px 0 16px; diff --git a/frontend/src/components/personal-report/vedic-chart-svg.tsx b/frontend/src/components/personal-report/vedic-chart-svg.tsx index 2ef692ba..d477a754 100644 --- a/frontend/src/components/personal-report/vedic-chart-svg.tsx +++ b/frontend/src/components/personal-report/vedic-chart-svg.tsx @@ -29,6 +29,7 @@ export type VedicChartModel = NorthIndianChartModel; interface VedicChartSvgProps { chart: NorthIndianChartModel; ariaLabel: string; + highlightHouseNumbers?: ReadonlySet; } function buildRetrogradeSet(chart: NorthIndianChartModel): Set { @@ -82,7 +83,7 @@ function OccupantStack({ ); } -export function VedicChartSvg({ chart, ariaLabel }: VedicChartSvgProps) { +export function VedicChartSvg({ chart, ariaLabel, highlightHouseNumbers }: VedicChartSvgProps) { const chartId = useId().replace(/:/g, ""); const linesByHouse = new Map(); for (const house of chart.houses) { @@ -117,7 +118,7 @@ export function VedicChartSvg({ chart, ariaLabel }: VedicChartSvgProps) { {ordinal !== null && ( void; +}>) { + return ( +
+ + +
+ ); +} + type RectificationAgenticChatProps = Readonly<{ caseId: string; @@ -489,6 +519,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { const stepStartedAtRef = useRef(0); const liveToolRef = useRef(null); const liveBaseLabelRef = useRef("正在处理…"); + const selectionOfferLockRef = useRef(null); const [compactBoard, setCompactBoard] = useState(false); const [boardOpen, setBoardOpen] = useState(false); const [boardDiff, setBoardDiff] = useState(() => diffRectificationBoard(null, null)); @@ -1425,12 +1456,29 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { stage: caseStage, }); const persistedOfferKey = [...messages].reverse().find((message) => message.candidateOffer)?.renderKey; - const selectionCardMessageKey = persistedOfferKey + const liveSelectionCardKey = persistedOfferKey ?? (canOfferCards && !candidateResult?.selectedTime ? latestSettledAssistant?.renderKey : undefined); + selectionOfferLockRef.current = nextSelectionCardLock(selectionOfferLockRef.current, { + resultId: candidateResult?.resultId, + key: liveSelectionCardKey, + }); + const selectionCardMessageKey = resolveSelectionCardMessageKey({ + persistedOfferKey, + fallbackKey: latestSettledAssistant?.renderKey, + locked: selectionOfferLockRef.current, + resultId: candidateResult?.resultId, + selectedTime: candidateResult?.selectedTime, + canOffer: canOfferCards, + }); + // Adopt already has in-card “正在采用…” / “已采用”; hiding the whole card + // for `busy` is what made the three columns vanish on 「更像这个」. + const keepSelectionCardsWhileBusy = Boolean( + acceptingCandidateId || candidateResult?.selectedTime, + ); const showSelectionCards = Boolean( candidateResult && caseSnapshotLoaded - && !busy + && (!busy || keepSelectionCardsWhileBusy) && selectionCardMessageKey && (canOfferCards || Boolean(candidateResult.selectedTime)) && !messages.some((message) => ( @@ -1472,6 +1520,12 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { retryAttempts: questionRetryAttempts, retryLimit: RECTIFICATION_QUESTION_RETRY_LIMIT, }); + const verifiedIdleCopy = questionGap === "verified_idle" + ? postAdoptVerifyDoneCopy( + savedTime, + messages.some((message) => message.question?.kind === "reverse_verify"), + ) + : null; const conversationState = rectificationConversationState({ messageCount: messages.length, busy, @@ -1642,6 +1696,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { onStop={submitStop} /> )} + {liveQuestion && question.kind === "collect_spoken" && ( + void send("message", text)} /> + )} {question.status === "skipped" && (

{savedTime ? `已跳过(已采用 ${savedTime})` : "已跳过"} @@ -1678,12 +1735,19 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { /> )} {showSelectionCards && candidateResult && message.renderKey === selectionCardMessageKey && ( - void acceptCandidate(candidateId)} - /> + <> + void acceptCandidate(candidateId)} + /> + {verifiedIdleCopy && ( +

+ {verifiedIdleCopy} +

+ )} + )} {showReadonlyRange && message.renderKey === latestSettledAssistant?.renderKey && candidateResult?.credibleRange && ( )} - {questionGap === "verified_idle" && ( + {questionGap === "verified_idle" && verifiedIdleCopy && !showSelectionCards && (

- {postAdoptVerifyDoneCopy( - savedTime, - messages.some((message) => message.question?.kind === "reverse_verify"), - )} + {verifiedIdleCopy}

)} {savedTime && savedStatus === "confirmed" && ( @@ -1733,26 +1794,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { {!conversationAnchor.anchored && ( )} - {collectSpokenPrompt && !busy && !readonly && regeneratingMessageKey === null ? ( -
- - -
- ) : null} - +
+ +
+
+ 本命宫位表 + +
{result && } {savedStatus === "accepted" && result && result.techniqueAudit.length > 0 && ( @@ -112,8 +127,10 @@ function RectificationBoardBody({ )} {(scoringMinutes.length > 0 || displayMinutes.length > 0) && ( -
-

换升时刻

+
+ + 换升时刻 · {grouped.length} 个 + {scoringMinutes.length > 0 && (
    {scoringMinutes.map((row) => { @@ -157,7 +174,7 @@ function RectificationBoardBody({
)} -
+
)} {contrast && (
diff --git a/frontend/src/components/rectification-range-delivery.tsx b/frontend/src/components/rectification-range-delivery.tsx index fd974271..c64c5e4b 100644 --- a/frontend/src/components/rectification-range-delivery.tsx +++ b/frontend/src/components/rectification-range-delivery.tsx @@ -5,13 +5,14 @@ import { RECTIFICATION_USER_COPY, REPRESENTATIVE_MINUTE_DISCLAIMER, rangeDeliveryEventCopy, - rangeDeliveryFitCopy, - rangeDeliveryWindowCopy, - rangeDeliveryWorstCopy, } from "@/lib/rectification-agentic/user-copy"; import type { RangeDeliveryProjection } from "@/lib/rectification-agentic/v9/divergence-panel"; import { RectificationVerificationReport } from "@/components/rectification-verification-report"; +function columnHasTraits(column: RangeDeliveryProjection["columns"][number]): boolean { + return Boolean(column.traits.d9 || column.traits.d10 || column.traits.nakshatra.length > 0); +} + export function RectificationRangeDelivery({ result, acceptingCandidateId, @@ -36,18 +37,25 @@ export function RectificationRangeDelivery({ : RECTIFICATION_USER_COPY.rangeDeliveryTitle; const markdown = delivery?.verification_markdown ?? result.verificationReportMarkdown; + const sharedTraits = delivery?.shared_traits ?? []; return (
{title}
+ {sharedTraits.length > 0 ? ( +
    + {sharedTraits.map((line) => ( +
  • {line}
  • + ))} +
+ ) : null}
    {columns.map((column) => { const adopted = selected === column.time; const adopting = acceptingCandidateId === column.candidate_id; - const worst = rangeDeliveryWorstCopy(column.fit.worst); - const windows = column.windows.slice(0, 2); + const missing = column.fit == null || column.windows == null; return (
  • -
    -

    {RECTIFICATION_USER_COPY.rangeDeliveryTraitsHeading}

    - {column.traits.d9 ?

    {column.traits.d9}

    : null} - {column.traits.d10 ?

    {column.traits.d10}

    : null} - {column.traits.nakshatra.map((line) => ( -

    {line}

    - ))} -
    -
    -

    {RECTIFICATION_USER_COPY.rangeDeliveryFitHeading}

    -

    {rangeDeliveryFitCopy(column.fit)}

    - {worst ?

    {worst}

    : null} -
    -
    -

    {RECTIFICATION_USER_COPY.rangeDeliveryWindowsHeading}

    - {windows.length > 0 - ? windows.map((window) => ( -

    {rangeDeliveryWindowCopy(window)}

    - )) - :

    {RECTIFICATION_USER_COPY.rangeDeliveryNoWindow}

    } -
    + {columnHasTraits(column) ? ( +
    + {column.traits.d9 ?

    {column.traits.d9}

    : null} + {column.traits.d10 ?

    {column.traits.d10}

    : null} + {column.traits.nakshatra.map((line) => ( +

    {line}

    + ))} +
    + ) : null} + {missing ? ( +

    + {RECTIFICATION_USER_COPY.rangeDeliveryNotCompared} +

    + ) : ( + <> +

    {column.fit_line}

    +

    {column.window_line}

    + + )}