@ai-agent-forge/agent-forge 0.88.5 → 0.88.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/bundled-plugins/agent-loop-policy/agent-forge.json +1 -1
- package/bundled-plugins/agent-loop-policy/plugin.json +1 -1
- package/bundled-plugins/alignment/agent-forge.json +19 -0
- package/bundled-plugins/alignment/dist/baseline.d.ts +12 -0
- package/bundled-plugins/alignment/dist/baseline.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/baseline.js +68 -0
- package/bundled-plugins/alignment/dist/baseline.js.map +1 -0
- package/bundled-plugins/alignment/dist/engine.d.ts +102 -0
- package/bundled-plugins/alignment/dist/engine.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/engine.js +654 -0
- package/bundled-plugins/alignment/dist/engine.js.map +1 -0
- package/bundled-plugins/alignment/dist/entry.d.ts +14 -0
- package/bundled-plugins/alignment/dist/entry.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/entry.js +23 -0
- package/bundled-plugins/alignment/dist/entry.js.map +1 -0
- package/bundled-plugins/alignment/dist/export.d.ts +15 -0
- package/bundled-plugins/alignment/dist/export.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/export.js +122 -0
- package/bundled-plugins/alignment/dist/export.js.map +1 -0
- package/bundled-plugins/alignment/dist/http-server.d.ts +33 -0
- package/bundled-plugins/alignment/dist/http-server.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/http-server.js +119 -0
- package/bundled-plugins/alignment/dist/http-server.js.map +1 -0
- package/bundled-plugins/alignment/dist/index.d.ts +24 -0
- package/bundled-plugins/alignment/dist/index.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/index.js +719 -0
- package/bundled-plugins/alignment/dist/index.js.map +1 -0
- package/bundled-plugins/alignment/dist/page.d.ts +15 -0
- package/bundled-plugins/alignment/dist/page.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/page.js +857 -0
- package/bundled-plugins/alignment/dist/page.js.map +1 -0
- package/bundled-plugins/alignment/dist/render.d.ts +40 -0
- package/bundled-plugins/alignment/dist/render.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/render.js +194 -0
- package/bundled-plugins/alignment/dist/render.js.map +1 -0
- package/bundled-plugins/alignment/dist/types.d.ts +217 -0
- package/bundled-plugins/alignment/dist/types.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/types.js +30 -0
- package/bundled-plugins/alignment/dist/types.js.map +1 -0
- package/bundled-plugins/alignment/plugin.json +12 -0
- package/bundled-plugins/browser-playwright/MANUAL.md +60 -0
- package/bundled-plugins/browser-playwright/agent-forge.json +25 -0
- package/bundled-plugins/browser-playwright/dist/capability.d.ts +25 -0
- package/bundled-plugins/browser-playwright/dist/capability.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/capability.js +612 -0
- package/bundled-plugins/browser-playwright/dist/capability.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/entry.d.ts +13 -0
- package/bundled-plugins/browser-playwright/dist/entry.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/entry.js +14 -0
- package/bundled-plugins/browser-playwright/dist/entry.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/index.d.ts +7 -0
- package/bundled-plugins/browser-playwright/dist/index.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/index.js +5 -0
- package/bundled-plugins/browser-playwright/dist/index.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/operations.d.ts +69 -0
- package/bundled-plugins/browser-playwright/dist/operations.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/operations.js +149 -0
- package/bundled-plugins/browser-playwright/dist/operations.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.d.ts +275 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.js +953 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/setup.d.ts +88 -0
- package/bundled-plugins/browser-playwright/dist/setup.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/setup.js +252 -0
- package/bundled-plugins/browser-playwright/dist/setup.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/types.d.ts +270 -0
- package/bundled-plugins/browser-playwright/dist/types.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/types.js +2 -0
- package/bundled-plugins/browser-playwright/dist/types.js.map +1 -0
- package/bundled-plugins/browser-playwright/plugin.json +20 -0
- package/bundled-plugins/coding-tools/agent-forge.json +1 -1
- package/bundled-plugins/coding-tools/dist/bash.d.ts +2 -7
- package/bundled-plugins/coding-tools/dist/bash.d.ts.map +1 -1
- package/bundled-plugins/coding-tools/dist/bash.js +2 -34
- package/bundled-plugins/coding-tools/dist/bash.js.map +1 -1
- package/bundled-plugins/coding-tools/dist/read.d.ts +2 -0
- package/bundled-plugins/coding-tools/dist/read.d.ts.map +1 -1
- package/bundled-plugins/coding-tools/dist/read.js +11 -0
- package/bundled-plugins/coding-tools/dist/read.js.map +1 -1
- package/bundled-plugins/coding-tools/plugin.json +1 -1
- package/bundled-plugins/compaction-policy/agent-forge.json +1 -1
- package/bundled-plugins/compaction-policy/plugin.json +1 -1
- package/bundled-plugins/mcp-http/dist/mcp-http.d.ts.map +1 -1
- package/bundled-plugins/mcp-http/dist/mcp-http.js +84 -31
- package/bundled-plugins/mcp-http/dist/mcp-http.js.map +1 -1
- package/bundled-plugins/overflow-policy/agent-forge.json +1 -1
- package/bundled-plugins/overflow-policy/plugin.json +1 -1
- package/bundled-plugins/provider-failover/agent-forge.json +1 -1
- package/bundled-plugins/provider-failover/plugin.json +1 -1
- package/bundled-plugins/retry-policy/agent-forge.json +1 -1
- package/bundled-plugins/retry-policy/plugin.json +1 -1
- package/dist/bundle/.build-manifest.json +60 -58
- package/dist/bundle/chunks/{anthropic-messages-3FJHRAJL.js → anthropic-messages-NL4Y6WPB.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-GCL47IDS.js → azure-openai-responses-CPXC42HO.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/chunk-5LRQFZFE.js +2 -0
- package/dist/bundle/chunks/{chunk-HTEVUY7F.js → chunk-7HSJPQKN.js} +1 -1
- package/dist/bundle/chunks/{chunk-AC2FIS4G.js → chunk-EDO7Q6XR.js} +1 -1
- package/dist/bundle/chunks/{chunk-755VNWIF.js → chunk-ISLHFRER.js} +6 -6
- package/dist/bundle/chunks/{chunk-HV64ASMT.js → chunk-K3S6GTL7.js} +1 -1
- package/dist/bundle/chunks/{chunk-HEL6TAWE.js → chunk-L2SXIPI5.js} +37 -36
- package/dist/bundle/chunks/{chunk-V3ACW7VD.js → chunk-WJSKB3FC.js} +1 -1
- package/dist/bundle/chunks/{chunk-32YDZI4J.js → chunk-ZS5JEI76.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-6BMXWA44.js → google-generative-ai-UMZ53EFA.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-6H7NLRTK.js → google-vertex-ZNR5NS6H.js} +1 -1
- package/dist/bundle/chunks/image-resize-worker.js +1 -1
- package/dist/bundle/chunks/{mistral-conversations-IXUHSFSJ.js → mistral-conversations-IT66WUNN.js} +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-YHUAEVCM.js → openai-codex-responses-FOVCVG6I.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-ULQCFC72.js → openai-completions-FLQ7CBOL.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-NLLAJDPS.js → openai-responses-ABGYHS2T.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-EJTI2EJ3.js → pi-messages-6K5W6LUN.js} +1 -1
- package/dist/bundle/chunks/{virtual-modules-TQWEGNNZ.js → virtual-modules-2QGILFV7.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/client.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/client/remote-session.d.ts.map +1 -1
- package/dist/client/remote-session.js +65 -6
- package/dist/client/remote-session.js.map +1 -1
- package/dist/core/agent-session-store.d.ts.map +1 -1
- package/dist/core/agent-session-store.js +20 -3
- package/dist/core/agent-session-store.js.map +1 -1
- package/dist/core/extensions/agent-composition.d.ts.map +1 -1
- package/dist/core/extensions/agent-composition.js +20 -1
- package/dist/core/extensions/agent-composition.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/core-errors.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/core-errors.js +1 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js +10 -3
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js.map +1 -1
- package/dist/core/keybindings.d.ts +35 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +34 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +62 -15
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +11 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +2 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/capability-mount.d.ts.map +1 -1
- package/dist/core/tools/capability-mount.js +4 -21
- package/dist/core/tools/capability-mount.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +2 -2
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +2 -0
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +25 -0
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/changelog-pager.d.ts +53 -0
- package/dist/modes/interactive/components/changelog-pager.d.ts.map +1 -0
- package/dist/modes/interactive/components/changelog-pager.js +239 -0
- package/dist/modes/interactive/components/changelog-pager.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts +6 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-run-group.d.ts +41 -11
- package/dist/modes/interactive/components/tool-run-group.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-run-group.js +126 -14
- package/dist/modes/interactive/components/tool-run-group.js.map +1 -1
- package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-commands.js +23 -13
- package/dist/modes/interactive/interactive-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-events.d.ts +14 -4
- package/dist/modes/interactive/interactive-events.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-events.js +94 -12
- package/dist/modes/interactive/interactive-events.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +6 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +21 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/plugins/ecosystem/node-local-package-source.d.ts +2 -1
- package/dist/plugins/ecosystem/node-local-package-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-local-package-source.js +110 -4
- package/dist/plugins/ecosystem/node-local-package-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.js +106 -39
- package/dist/plugins/ecosystem/node-package-install-host.js.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js +85 -2
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js.map +1 -1
- package/dist/suites/builtin-suites.d.ts.map +1 -1
- package/dist/suites/builtin-suites.js +2 -2
- package/dist/suites/builtin-suites.js.map +1 -1
- package/dist/utils/changelog.d.ts +27 -0
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +110 -0
- package/dist/utils/changelog.js.map +1 -1
- package/docs/keybindings.md +14 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/extensions/with-deps/plugin.json +1 -1
- package/package.json +1 -1
- package/dist/bundle/chunks/chunk-A3JYRWB6.js +0 -2
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fixed confirmation-page template (设计 §7 模板契约): a plugin-owned
|
|
3
|
+
* static asset compiled into the package. The model only ever supplies block
|
|
4
|
+
* DATA through alignment.state — it never produces markup, so the same state
|
|
5
|
+
* always renders the same page and there is no model-injection surface
|
|
6
|
+
* (block content is escaped text, never markup).
|
|
7
|
+
*
|
|
8
|
+
* Interaction contract (demo 定稿 2026-10-03): spotlight replays fully on
|
|
9
|
+
* every click, auto-restores after ~4.5s or any click; empty missing blocks
|
|
10
|
+
* show "fill first" instead of an approve button; the annotation popover is
|
|
11
|
+
* immune to its own trailing mouseup; polling never wipes in-progress input.
|
|
12
|
+
*/
|
|
13
|
+
const CSS = `
|
|
14
|
+
:root{--ink:#2a2f45;--ink2:#4c536e;--mut:#8a90a8;--faint:#b6bbce;--line:rgba(120,125,165,.16);
|
|
15
|
+
--grad:linear-gradient(135deg,#6366f1,#a855f7 55%,#ec4899);--green:#10b981;--greensoft:#e7f9f1;
|
|
16
|
+
--amber:#f59e0b;--ambersoft:#fef4e0;--blue:#3b82f6;--bluesoft:#eaf1fe;--pink:#ec4899;--pinksoft:#fdeaf3;
|
|
17
|
+
--card:rgba(255,255,255,.74)}
|
|
18
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
19
|
+
body{font-family:"SF Pro Display",-apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
|
|
20
|
+
background:#f4f6fd;color:var(--ink);font-size:14px;line-height:1.75}
|
|
21
|
+
::selection{background:#ddd6fe}
|
|
22
|
+
textarea{background:#fff;border:1.5px solid var(--line);border-radius:16px;color:var(--ink);
|
|
23
|
+
font:inherit;font-size:13.5px;transition:.15s;scrollbar-width:thin}
|
|
24
|
+
textarea:focus{outline:none;border-color:#a5b4fc;box-shadow:0 0 0 4px rgba(99,102,241,.12)}
|
|
25
|
+
textarea::placeholder{color:#b8bdd1}
|
|
26
|
+
textarea::-webkit-scrollbar{width:6px}textarea::-webkit-scrollbar-button{display:none}
|
|
27
|
+
textarea::-webkit-scrollbar-thumb{background:rgba(120,125,165,.25);border-radius:6px}
|
|
28
|
+
.blob{position:fixed;border-radius:50%;filter:blur(90px);opacity:.42;z-index:-1}
|
|
29
|
+
.b1{width:480px;height:480px;background:#c7d2fe;top:-160px;left:-90px}
|
|
30
|
+
.b2{width:430px;height:430px;background:#fbcfe8;top:-120px;right:-80px}
|
|
31
|
+
.b3{width:400px;height:400px;background:#fde8c8;bottom:-160px;left:28%}
|
|
32
|
+
.wrap{max-width:1020px;margin:0 auto;padding:32px 22px 190px}
|
|
33
|
+
header.task{text-align:center}
|
|
34
|
+
.eyebrow{display:inline-flex;align-items:center;gap:7px;font-size:12px;font-weight:700;color:#6366f1;
|
|
35
|
+
background:#eef0ff;border-radius:999px;padding:4px 16px}
|
|
36
|
+
.eyebrow::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--grad)}
|
|
37
|
+
h1{font-size:24px;font-weight:800;margin-top:12px}
|
|
38
|
+
.meta{font-size:12.5px;color:var(--mut);margin-top:6px}.meta b{color:var(--ink2);font-weight:650}
|
|
39
|
+
.steps{display:flex;gap:10px;justify-content:center;margin-top:18px}
|
|
40
|
+
.step{display:flex;align-items:center;gap:8px;padding:9px 20px;border-radius:999px;
|
|
41
|
+
background:rgba(255,255,255,.7);border:1px solid rgba(255,255,255,.9);
|
|
42
|
+
box-shadow:0 4px 18px rgba(99,102,241,.07);font-size:13.5px;font-weight:600;color:var(--mut)}
|
|
43
|
+
.step .no{width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;
|
|
44
|
+
font-size:11.5px;background:#edeff8;color:var(--mut)}
|
|
45
|
+
.step.done{color:#047857;background:var(--greensoft)}.step.done .no{background:var(--green);color:#fff}
|
|
46
|
+
.step.active{color:#4f46e5;border-color:transparent;
|
|
47
|
+
background:linear-gradient(#fff,#fff) padding-box,var(--grad) border-box;box-shadow:0 8px 30px rgba(99,102,241,.15)}
|
|
48
|
+
.step.active .no{background:var(--grad);color:#fff}
|
|
49
|
+
.step .tag{font-size:11px;font-weight:500;opacity:.75}
|
|
50
|
+
.cols{display:flex;gap:20px;align-items:flex-start;margin-top:30px}
|
|
51
|
+
aside.locked{width:250px;flex:none;position:sticky;top:14px}
|
|
52
|
+
.glass{background:var(--card);backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,.95);
|
|
53
|
+
border-radius:22px;box-shadow:0 12px 44px rgba(99,102,241,.08);padding:16px 18px}
|
|
54
|
+
.glass h3{font-size:13.5px;display:flex;align-items:center;gap:7px;color:var(--ink2)}
|
|
55
|
+
.glass .ic{width:22px;height:22px;border-radius:7px;background:var(--greensoft);color:var(--green);
|
|
56
|
+
display:inline-flex;align-items:center;justify-content:center;font-size:12px}
|
|
57
|
+
.lkgrp{margin-top:8px}
|
|
58
|
+
.lkgrp summary{cursor:pointer;font-size:12px;color:var(--mut);padding:4px 0}
|
|
59
|
+
.lkitem{font-size:12px;color:var(--mut);line-height:1.65;padding:5px 0 5px 12px;border-top:1px dashed var(--line)}
|
|
60
|
+
.lkitem .k{font-size:10px;font-weight:700;color:var(--green);margin-right:5px}
|
|
61
|
+
.glass .note{font-size:11px;color:var(--mut);background:#f4f2ff;border-radius:12px;padding:8px 12px;margin-top:10px;line-height:1.6}
|
|
62
|
+
#main{flex:1;min-width:0}
|
|
63
|
+
.stephead{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:16px;padding:0 6px}
|
|
64
|
+
.stephead h2{font-size:18px;font-weight:800}
|
|
65
|
+
.stephead .desc{font-size:12px;color:var(--mut);max-width:440px;text-align:right}
|
|
66
|
+
.block{position:relative;background:var(--card);backdrop-filter:blur(16px);
|
|
67
|
+
border:1px solid rgba(255,255,255,.95);border-radius:22px;padding:17px 22px 15px;margin-bottom:14px;
|
|
68
|
+
box-shadow:0 10px 36px rgba(99,102,241,.07);transition:opacity .35s,transform .35s,box-shadow .2s}
|
|
69
|
+
.block .top{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
|
|
70
|
+
.badge{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:700;padding:3px 12px;border-radius:999px}
|
|
71
|
+
.badge::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
|
|
72
|
+
.badge.decision{color:#4f46e5;background:#eef0ff}.badge.assumption{color:#b45309;background:var(--ambersoft)}
|
|
73
|
+
.badge.fact{color:#047857;background:var(--greensoft)}.badge.missing{color:#db2777;background:var(--pinksoft)}
|
|
74
|
+
.badge.visual{color:#0e7490;background:#e0f5f6}
|
|
75
|
+
.bid{font-size:11px;color:var(--mut);font-weight:600;letter-spacing:.5px}
|
|
76
|
+
.mod{font-size:11px;font-weight:700;color:#7c3aed;background:#f3ecff;border-radius:999px;padding:2px 10px}
|
|
77
|
+
.approvebtn{margin-left:auto;cursor:pointer;font-size:12.5px;font-weight:700;padding:6px 18px;border-radius:999px;
|
|
78
|
+
border:1.5px solid var(--green);background:#fff;color:var(--green);transition:.18s}
|
|
79
|
+
.approvebtn:hover{background:var(--greensoft)}
|
|
80
|
+
.block.approved .approvebtn{background:linear-gradient(135deg,#34d399,#10b981);border-color:transparent;color:#fff}
|
|
81
|
+
.skiphint{margin-left:auto;font-size:11px;color:var(--faint);background:#f4f5fa;border-radius:999px;padding:4px 13px}
|
|
82
|
+
.content{margin-top:9px;font-size:14.5px;line-height:1.8;white-space:pre-wrap;cursor:text}
|
|
83
|
+
.missinginput textarea{width:100%;margin-top:9px;background:var(--pinksoft);border:1.5px solid #f9c8de;
|
|
84
|
+
border-radius:14px;padding:9px 14px;font:inherit;font-size:13.5px;color:var(--ink);resize:none;min-height:40px}
|
|
85
|
+
.missinginput textarea:focus{outline:none;border-color:var(--pink);background:#fff}
|
|
86
|
+
.missinginput textarea::placeholder{color:#d98bb1}
|
|
87
|
+
details.meta{margin-top:6px;font-size:12px;color:var(--mut)}
|
|
88
|
+
details.meta summary{cursor:pointer;width:fit-content}details.meta summary:hover{color:var(--ink2)}
|
|
89
|
+
.prev{background:#f6f5fb;border-radius:10px;padding:7px 12px;margin-top:5px;color:var(--mut);font-size:12px;white-space:pre-wrap}
|
|
90
|
+
.avatar{width:24px;height:24px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;
|
|
91
|
+
font-size:10px;font-weight:800;flex:none;color:#fff}
|
|
92
|
+
.avatar.ai{background:linear-gradient(135deg,#f59e0b,#f97316)}.avatar.user{background:linear-gradient(135deg,#3b82f6,#6366f1)}
|
|
93
|
+
.comment{margin-top:11px;padding:11px 15px;font-size:13px}
|
|
94
|
+
.comment .who{font-size:11px;font-weight:800;margin-bottom:4px;display:flex;align-items:center;gap:7px}
|
|
95
|
+
.comment .quote{font-size:12px;opacity:.7;padding:1px 0 1px 9px;border-left:2.5px solid currentColor;margin:4px 0}
|
|
96
|
+
.comment.ai{background:#fff8ec;border:1px solid #fbe7c3;border-radius:18px 18px 18px 5px;color:#92580a}
|
|
97
|
+
.comment.ai .who{color:#c2680a}
|
|
98
|
+
.comment.user{background:var(--bluesoft);border:1px solid #cfe0fb;border-radius:18px 18px 5px 18px;color:#1e50af}
|
|
99
|
+
.comment.user .who{color:#2c63c8}
|
|
100
|
+
.comment .actions{margin-top:7px;display:flex;gap:8px}
|
|
101
|
+
.comment .actions button{cursor:pointer;font-size:12px;font-weight:700;padding:3px 14px;border-radius:999px;
|
|
102
|
+
border:1.5px solid currentColor;background:transparent;color:inherit;opacity:.85;transition:.15s}
|
|
103
|
+
.comment .actions button:hover{opacity:1;background:rgba(255,255,255,.6)}
|
|
104
|
+
.replybox{width:100%;margin-top:8px;background:#fff;border:1.5px solid #fbe7c3;border-radius:12px;
|
|
105
|
+
font:inherit;font-size:13px;padding:7px 11px;resize:vertical;min-height:40px;color:var(--ink)}
|
|
106
|
+
.replybox:focus{outline:none;border-color:var(--amber)}
|
|
107
|
+
.comment .draftreply{margin-top:6px;font-size:12px;background:rgba(255,255,255,.7);border-radius:10px;
|
|
108
|
+
padding:5px 11px;display:flex;justify-content:space-between;gap:10px;align-items:center}
|
|
109
|
+
.comment .draftreply a{cursor:pointer;text-decoration:underline;color:inherit;opacity:.7;white-space:nowrap}
|
|
110
|
+
.comment.resolved{background:rgba(255,255,255,.55);border:1px dashed var(--line);color:var(--mut);
|
|
111
|
+
font-size:12px;border-radius:14px;padding:8px 15px}
|
|
112
|
+
#blocks.focusing .block:not(.attention){opacity:.3;transform:scale(.985)}
|
|
113
|
+
.block.attention{animation:wiggle .65s ease;box-shadow:0 0 0 3px var(--kc,#6366f1),0 14px 44px rgba(99,102,241,.2)}
|
|
114
|
+
.block[data-kind=decision]{--kc:#6366f1}.block[data-kind=assumption]{--kc:#f59e0b}
|
|
115
|
+
.block[data-kind=fact]{--kc:#10b981}.block[data-kind=missing]{--kc:#ec4899}
|
|
116
|
+
@keyframes wiggle{0%,100%{transform:rotate(0)}25%{transform:rotate(-.8deg)}50%{transform:rotate(.7deg)}75%{transform:rotate(-.45deg)}}
|
|
117
|
+
.block.attention::after{content:attr(data-reason);position:absolute;top:-12px;right:18px;font-size:11px;
|
|
118
|
+
font-weight:800;color:#fff;background:var(--kc,#6366f1);padding:3px 13px;border-radius:999px;box-shadow:0 4px 12px rgba(0,0,0,.15)}
|
|
119
|
+
footer.dock{position:fixed;left:50%;transform:translateX(-50%);bottom:20px;width:min(1000px,calc(100% - 36px));
|
|
120
|
+
background:rgba(255,255,255,.88);backdrop-filter:blur(22px);border:1px solid #fff;border-radius:26px;
|
|
121
|
+
box-shadow:0 22px 70px rgba(80,80,160,.18);z-index:50}
|
|
122
|
+
footer.dock .inner{padding:14px 18px 12px;display:flex;gap:14px;align-items:flex-end}
|
|
123
|
+
.addbox{flex:1}.addbox label{font-size:11px;font-weight:700;color:var(--mut);display:block;margin-bottom:4px}
|
|
124
|
+
.awaitbar{background:linear-gradient(90deg,#eef2ff,#fdf2f8);border:1px solid #e0e7ff;border-radius:12px;padding:10px 16px;margin:0 0 14px;font-size:13px;color:#4f46e5;font-weight:600}
|
|
125
|
+
.cmask{position:fixed;inset:0;background:rgba(79,70,229,.18);backdrop-filter:blur(6px);z-index:60;display:flex;align-items:center;justify-content:center;animation:cmaskin .18s ease}
|
|
126
|
+
.ccard{background:rgba(255,255,255,.92);backdrop-filter:blur(18px);border:1px solid rgba(148,163,184,.35);border-radius:20px;box-shadow:0 24px 70px -18px rgba(79,70,229,.45);padding:22px 26px 18px;max-width:380px;width:calc(100vw - 48px)}
|
|
127
|
+
.ccard h3{margin:0 0 8px;font-size:15.5px;font-weight:800}
|
|
128
|
+
.ccard p{margin:0 0 16px;font-size:13px;color:var(--mut);line-height:1.7}
|
|
129
|
+
.cbtns{display:flex;gap:10px;justify-content:flex-end}
|
|
130
|
+
.ccancel{border:1px solid var(--line);background:#fff;color:var(--ink2);border-radius:12px;padding:8px 18px;font-size:13px;font-weight:600;cursor:pointer}
|
|
131
|
+
.cok{border:0;background:linear-gradient(135deg,#7c6ff0,#d946ef);color:#fff;border-radius:12px;padding:8px 18px;font-size:13px;font-weight:700;cursor:pointer;box-shadow:0 8px 20px -8px rgba(124,111,240,.6)}
|
|
132
|
+
@keyframes cmaskin{from{opacity:0}to{opacity:1}}
|
|
133
|
+
.botwrap{display:flex;justify-content:center;padding:34px 0 20px}
|
|
134
|
+
.botcard{background:rgba(255,255,255,.75);backdrop-filter:blur(14px);border:1px solid rgba(148,163,184,.3);border-radius:20px;padding:26px 44px 22px;text-align:center;box-shadow:0 18px 44px -20px rgba(99,102,241,.35);animation:botfloat 3.2s ease-in-out infinite}
|
|
135
|
+
.bot{width:64px;height:52px;margin:0 auto 12px;position:relative}
|
|
136
|
+
.bot .antenna{position:absolute;top:-12px;left:50%;transform:translateX(-50%);width:3px;height:12px;background:#c7d2fe;border-radius:2px}
|
|
137
|
+
.bot .antenna:after{content:"";position:absolute;top:-7px;left:50%;transform:translateX(-50%);width:9px;height:9px;border-radius:50%;background:#a78bfa;animation:botping 1.6s ease-in-out infinite}
|
|
138
|
+
.bot .face{width:64px;height:52px;border-radius:16px;background:linear-gradient(145deg,#eef2ff,#fdf2f8);border:1.5px solid #c7d2fe;display:flex;align-items:center;justify-content:center;gap:12px}
|
|
139
|
+
.bot .eye{width:8px;height:16px;border-radius:4px;background:#7c6ff0;animation:botblink 2.6s ease-in-out infinite}
|
|
140
|
+
.bot .eye:last-child{animation-delay:.15s}
|
|
141
|
+
.thinkdots{display:flex;gap:7px;justify-content:center;margin:2px 0 10px}
|
|
142
|
+
.thinkdots span{width:8px;height:8px;border-radius:50%;background:#a78bfa;opacity:.45;animation:botdot 1.4s ease-in-out infinite}
|
|
143
|
+
.thinkdots span:nth-child(2){animation-delay:.2s}
|
|
144
|
+
.thinkdots span:nth-child(3){animation-delay:.4s}
|
|
145
|
+
.bottext{font-size:13px;color:#6d28d9;font-weight:600}
|
|
146
|
+
@keyframes botfloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
|
|
147
|
+
@keyframes botblink{0%,88%,100%{transform:scaleY(1)}94%{transform:scaleY(.12)}}
|
|
148
|
+
@keyframes botping{0%,100%{opacity:.5;transform:translateX(-50%) scale(1)}50%{opacity:1;transform:translateX(-50%) scale(1.35)}}
|
|
149
|
+
@keyframes botdot{0%,100%{opacity:.35;transform:translateY(0)}45%{opacity:1;transform:translateY(-6px)}}
|
|
150
|
+
.verbar{font-size:12px;color:var(--mut);margin:0 0 12px}
|
|
151
|
+
.verbar a{cursor:pointer;text-decoration:underline;margin-right:2px}
|
|
152
|
+
.verbar b{color:var(--ink)}
|
|
153
|
+
.verbar .rollbackbtn{margin-left:10px;border:1px solid #fecaca;background:#fff1f2;color:#be123c;border-radius:8px;padding:3px 10px;font-size:12px;cursor:pointer}
|
|
154
|
+
.finhead{margin:6px 0 14px}.finhead h2{font-size:19px;margin:0 0 6px}.finhead p{font-size:13px;color:var(--mut);margin:0}
|
|
155
|
+
.finalview .fingrp{background:rgba(255,255,255,.72);border:1px solid rgba(148,163,184,.28);border-radius:14px;padding:14px 18px;margin-bottom:12px}
|
|
156
|
+
.finalview h3{font-size:14px;margin:0 0 8px}
|
|
157
|
+
.brow{display:flex;gap:8px;align-items:baseline;padding:5px 0;border-bottom:1px dashed rgba(148,163,184,.2);font-size:13px}
|
|
158
|
+
.brow:last-child{border-bottom:0}
|
|
159
|
+
.brow .k{font-size:11px;font-weight:700;flex:none}
|
|
160
|
+
.brow .bid{font-family:ui-monospace,monospace;font-size:12px;color:var(--mut);flex:none}
|
|
161
|
+
.addbox textarea{display:block;width:100%;height:46px;padding:10px 15px;resize:none}
|
|
162
|
+
.mainbtn{cursor:pointer;border:none;border-radius:18px;height:49px;display:inline-flex;align-items:center;
|
|
163
|
+
justify-content:center;padding:0 28px;font-size:14px;font-weight:800;white-space:nowrap;color:#fff;
|
|
164
|
+
background:var(--grad);box-shadow:0 10px 30px rgba(139,92,246,.28);transition:.18s}
|
|
165
|
+
.mainbtn:hover:not(:disabled){filter:brightness(1.08);transform:translateY(-1px)}
|
|
166
|
+
.mainbtn.confirm{background:linear-gradient(135deg,#34d399,#0ea573);box-shadow:0 10px 30px rgba(16,185,129,.28)}
|
|
167
|
+
.mainbtn:disabled{background:#e2e5f0;color:#a7adc4;box-shadow:none;cursor:not-allowed;transform:none}
|
|
168
|
+
.gatehint{padding:0 18px 12px;font-size:12px;color:var(--mut)}.gatehint b{color:#db2777}
|
|
169
|
+
.gatehint.ok{color:#047857}
|
|
170
|
+
.selbtn{position:absolute;z-index:40;cursor:pointer;border:none;background:var(--grad);color:#fff;
|
|
171
|
+
font-size:12.5px;font-weight:700;padding:6px 16px;border-radius:999px;box-shadow:0 8px 24px rgba(139,92,246,.32)}
|
|
172
|
+
.annotpop{position:absolute;z-index:50;width:340px;background:#fff;border:1px solid #e7e9fb;border-radius:20px;
|
|
173
|
+
box-shadow:0 24px 70px rgba(80,80,160,.22);padding:15px 17px}
|
|
174
|
+
.annotpop .sel{font-size:12px;color:var(--mut);border-left:3px solid var(--blue);padding-left:9px;
|
|
175
|
+
margin-bottom:9px;max-height:56px;overflow:hidden}
|
|
176
|
+
.annotpop textarea{width:100%;border:1.5px solid var(--line);border-radius:14px;padding:9px 12px;
|
|
177
|
+
resize:vertical;min-height:58px}
|
|
178
|
+
.annotpop .esc{display:flex;gap:7px;align-items:center;font-size:12px;color:var(--mut);margin-top:9px;cursor:pointer}
|
|
179
|
+
.annotpop .esc input{accent-color:#6366f1}
|
|
180
|
+
.annotpop .row{display:flex;gap:9px;justify-content:flex-end;margin-top:11px}
|
|
181
|
+
.annotpop .row button{cursor:pointer;font-size:12.5px;font-weight:700;padding:6px 18px;border-radius:999px;
|
|
182
|
+
border:1.5px solid var(--line);background:#fff;color:var(--ink2)}
|
|
183
|
+
.annotpop .row button.go{background:var(--grad);border:none;color:#fff;box-shadow:0 6px 18px rgba(139,92,246,.3)}
|
|
184
|
+
#toast{position:fixed;top:18px;left:50%;transform:translateX(-50%) translateY(-80px);color:#fff;
|
|
185
|
+
background:var(--grad);font-size:13px;font-weight:700;padding:10px 26px;border-radius:999px;
|
|
186
|
+
box-shadow:0 12px 36px rgba(139,92,246,.32);transition:transform .3s ease;z-index:100}
|
|
187
|
+
#toast.show{transform:translateX(-50%) translateY(0)}
|
|
188
|
+
.offline{position:fixed;inset:auto 0 0 0;top:0;display:flex;align-items:center;justify-content:center;
|
|
189
|
+
background:rgba(90,95,130,.92);color:#fff;font-size:14px;z-index:200;text-align:center;padding:20px}
|
|
190
|
+
.reviving{text-align:center;padding:60px 0;color:var(--mut)}
|
|
191
|
+
.reviving::before{content:"";display:block;width:34px;height:34px;margin:0 auto 14px;border-radius:50%;
|
|
192
|
+
background:conic-gradient(from 0deg,#6366f1,#ec4899,#6366f1);
|
|
193
|
+
-webkit-mask:radial-gradient(circle 11px,transparent 97%,#000) center/100% 100% no-repeat;
|
|
194
|
+
animation:spin 1.1s linear infinite}
|
|
195
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
196
|
+
.baseline{margin-top:16px;background:linear-gradient(135deg,#e7f9f1,#eef0ff);border:1px solid #fff;
|
|
197
|
+
border-radius:24px;padding:20px 24px}
|
|
198
|
+
.baseline h3{color:#047857;font-size:16px}.baseline p{font-size:12px;color:var(--mut);margin:5px 0 10px}
|
|
199
|
+
.brow{display:flex;gap:10px;font-size:13px;padding:6px 2px;border-top:1px dashed var(--line);align-items:baseline}
|
|
200
|
+
.brow .k{font-size:10px;font-weight:800;color:var(--ink2);flex:none}.brow .bid{font-size:10.5px;color:var(--mut);flex:none;font-weight:600}
|
|
201
|
+
@media(max-width:900px){.cols{flex-direction:column}aside.locked{width:100%;position:static}}
|
|
202
|
+
`;
|
|
203
|
+
const SCRIPT = `
|
|
204
|
+
"use strict";
|
|
205
|
+
const KIND_CN={fact:"取证事实",assumption:"假设",decision:"决策",missing:"待补充",visual:"视觉"};
|
|
206
|
+
const STEP_CN={objective:"目标",detail:"细节",design:"设计"};
|
|
207
|
+
const STEP_ORDER=["objective","detail","design"];
|
|
208
|
+
const esc=(s)=>String(s==null?"":s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
|
209
|
+
const el=(id)=>document.getElementById(id);
|
|
210
|
+
const firstLine=(s)=>String(s).split("\\n")[0].slice(0,80);
|
|
211
|
+
|
|
212
|
+
// page-local drafts: survive re-renders (轮询不丢输入) until submitted
|
|
213
|
+
let drafts={answers:{},replies:{},dismissed:{},additions:"",revised:{}};
|
|
214
|
+
// Version view (走查 2026-10-03): null = latest (interactive); number = archived
|
|
215
|
+
// snapshot revision (read-only).
|
|
216
|
+
let viewingVersion=null;
|
|
217
|
+
// 完成态的环节查看选择(走查 2026-10-03):null = 全部分组;只读,无回退。
|
|
218
|
+
let viewingStep=null;
|
|
219
|
+
function askConfirm(title,body,okText,onOk){
|
|
220
|
+
// 页内确认弹层(走查 2026-10-03):禁用浏览器原生 confirm。
|
|
221
|
+
const mask=document.createElement("div");
|
|
222
|
+
mask.className="cmask";
|
|
223
|
+
mask.innerHTML='<div class="ccard"><h3>'+esc(title)+'</h3><p>'+esc(body)+'</p>'
|
|
224
|
+
+'<div class="cbtns"><button class="ccancel">取消</button><button class="cok">'+esc(okText)+'</button></div></div>';
|
|
225
|
+
document.body.appendChild(mask);
|
|
226
|
+
const close=()=>mask.remove();
|
|
227
|
+
mask.querySelector(".ccancel").addEventListener("click",close);
|
|
228
|
+
mask.querySelector(".cok").addEventListener("click",()=>{close();onOk();});
|
|
229
|
+
mask.addEventListener("click",(e)=>{if(e.target===mask)close();});
|
|
230
|
+
}
|
|
231
|
+
function botThink(text){
|
|
232
|
+
return '<div class="botwrap"><div class="botcard"><div class="bot"><div class="antenna"></div>'
|
|
233
|
+
+'<div class="face"><span class="eye"></span><span class="eye"></span></div></div>'
|
|
234
|
+
+'<div class="thinkdots"><span></span><span></span><span></span></div>'
|
|
235
|
+
+'<div class="bottext">'+text+'</div></div></div>';
|
|
236
|
+
}
|
|
237
|
+
let view=null; // last state.json payload
|
|
238
|
+
let submitting=false;
|
|
239
|
+
|
|
240
|
+
let everConnected=false;
|
|
241
|
+
async function pull(){
|
|
242
|
+
try{
|
|
243
|
+
const res=await fetch("state.json",{cache:"no-store"});
|
|
244
|
+
if(res.status===404){offline(false);return null;}
|
|
245
|
+
if(!res.ok)throw new Error("state "+res.status);
|
|
246
|
+
everConnected=true;
|
|
247
|
+
offline(false);
|
|
248
|
+
return await res.json();
|
|
249
|
+
}catch(e){offline(true);return null;}
|
|
250
|
+
}
|
|
251
|
+
function offline(show){
|
|
252
|
+
const banner=el("offline");
|
|
253
|
+
banner.style.display=show?"flex":"none";
|
|
254
|
+
// 首次加载失败 ≠ 断开(2026-10-08 走查):服务尚未就绪时显示连接中文案,
|
|
255
|
+
// 不再用「会话已断开」的终态话术吓退用户;成功连过一次后再失败才是断开。
|
|
256
|
+
if(show)banner.textContent=everConnected
|
|
257
|
+
?"会话已断开——回到终端继续(插件服务已停止)"
|
|
258
|
+
:"正在连接对齐服务……若无响应,请从终端重新打开确认页";
|
|
259
|
+
}
|
|
260
|
+
// Local approval mirror: null = follow server state; object after the first
|
|
261
|
+
// local toggle. Every gate/visual read goes through effectiveApproved so a
|
|
262
|
+
// click reacts instantly instead of waiting for the next round trip.
|
|
263
|
+
let approvals=null; // null = mirror server state; object after local toggles
|
|
264
|
+
function effectiveApproved(b){
|
|
265
|
+
if(approvals&&Object.prototype.hasOwnProperty.call(approvals,b.id))return approvals[b.id];
|
|
266
|
+
return b.status==="approved";
|
|
267
|
+
}
|
|
268
|
+
function gatePendingCount(){
|
|
269
|
+
if(!view)return 0;
|
|
270
|
+
const s=activeStep();
|
|
271
|
+
if(!s)return view.gate.pending.length;
|
|
272
|
+
// Server pending list, discounted by local approvals the page can resolve
|
|
273
|
+
// right now. missing-empty stays pending until an answer exists locally.
|
|
274
|
+
return view.gate.pending.filter((item)=>{
|
|
275
|
+
const b=s.blocks.find((x)=>x.id===item.blockId);
|
|
276
|
+
if(!b)return true;
|
|
277
|
+
if(item.reason==="unapproved"||item.reason==="missing-empty"){
|
|
278
|
+
if(!effectiveApproved(b))return true;
|
|
279
|
+
return b.kind==="missing"&&!(b.draftAnswer||(drafts.answers[b.id]||"").trim());
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
}).length;
|
|
283
|
+
}
|
|
284
|
+
function hasContentDrafts(){
|
|
285
|
+
if(drafts.additions.trim())return true;
|
|
286
|
+
const s=activeStep();
|
|
287
|
+
if(!s)return false;
|
|
288
|
+
for(const b of s.blocks){
|
|
289
|
+
if(b.kind==="missing"&&!b.draftAnswer&&(drafts.answers[b.id]||"").trim())return true;
|
|
290
|
+
if(b.kind==="missing"&&b.draftAnswer)return false;
|
|
291
|
+
}
|
|
292
|
+
for(const b of s.blocks)for(const c of b.comments){
|
|
293
|
+
if(c.status==="open"&&c.author==="assistant"&&(drafts.replies[c.id]||drafts.dismissed[c.id]))return true;
|
|
294
|
+
}
|
|
295
|
+
return !!(selInfo&&selInfo.pendingComments&&selInfo.pendingComments.length);
|
|
296
|
+
}
|
|
297
|
+
function hasDrafts(){
|
|
298
|
+
if(hasContentDrafts())return true;
|
|
299
|
+
const s=activeStep();
|
|
300
|
+
if(!s)return false;
|
|
301
|
+
// A local approval that differs from the mirrored state is undelivered
|
|
302
|
+
// feedback and must route the dock button to submit, not spotlight.
|
|
303
|
+
for(const b of s.blocks){
|
|
304
|
+
if(effectiveApproved(b)!==(b.status==="approved"))return true;
|
|
305
|
+
}
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
function activeStep(){return view?view.steps.find((s)=>s.step===view.activeStep):null;}
|
|
309
|
+
|
|
310
|
+
async function refresh(){
|
|
311
|
+
const next=await pull();
|
|
312
|
+
if(!next)return;
|
|
313
|
+
const focusEditing=document.activeElement&&document.activeElement.tagName==="TEXTAREA";
|
|
314
|
+
// 轮询只在状态真正推进时整页重绘(走查 2026-10-03):无条件每 2s 重建
|
|
315
|
+
// innerHTML 会重放入场动画,还会吞掉恰好落在重建窗口里的点击。
|
|
316
|
+
const changed=!view||next.revision!==view.revision||next.round!==view.round
|
|
317
|
+
||next.status!==view.status||!!next.awaitingRevision!==!!view.awaitingRevision;
|
|
318
|
+
if(changed){approvals=null;viewingVersion=null;}
|
|
319
|
+
view=next;
|
|
320
|
+
if(changed&&!focusEditing)render();
|
|
321
|
+
else renderDock(); // keep the dock fresh while typing
|
|
322
|
+
}
|
|
323
|
+
function render(){renderSteps();renderLocked();renderMain();renderDock();}
|
|
324
|
+
|
|
325
|
+
function renderSteps(){
|
|
326
|
+
el("title").textContent=view.title;
|
|
327
|
+
el("meta").innerHTML=view.goalId
|
|
328
|
+
?("goal <b>"+esc(view.goalId)+"</b> · 第 <b>"+view.round+"</b> 轮 · 已确认 <b>"+view.gate.approvedCount+"/"+view.gate.blockCount+"</b>")
|
|
329
|
+
:("第 <b>"+view.round+"</b> 轮 · 已确认 <b>"+view.gate.approvedCount+"/"+view.gate.blockCount+"</b>");
|
|
330
|
+
let tabs=STEP_ORDER.map((id,i)=>{
|
|
331
|
+
const st=view.steps.find((s)=>s.step===id);
|
|
332
|
+
const locked=st.status==="locked";
|
|
333
|
+
const cur=view.status==="completed"?viewingStep:(view.status==="finalizing"?null:view.activeStep);
|
|
334
|
+
const cls=locked?"done":(cur===id?"active":"");
|
|
335
|
+
const tag=locked?(st.blocks.length+" 块已锁定"):(view.activeStep===id?"当前":"未解锁");
|
|
336
|
+
// 已锁定步可点击回退(走查 2026-10-03):后续环节作废重走。
|
|
337
|
+
const terminal=view.status==="completed"||view.status==="cancelled";
|
|
338
|
+
// 完成态:tab 点击=只读查看该环节(走查 2026-10-03),无回退。
|
|
339
|
+
const viewPick=terminal?' data-viewstep="'+id+'" style="cursor:pointer"':"";
|
|
340
|
+
const retreat=(locked&&!terminal)?' data-retreat="'+id+'" style="cursor:pointer" title="点击回退到此环节(后面的环节将作废)"':"";
|
|
341
|
+
return '<div class="step '+cls+'"'+viewPick+retreat+'><span class="no">'+(locked?"✓":(i+1))+"</span>"+STEP_CN[id]
|
|
342
|
+
+'<span class="tag">'+tag+"</span></div>";
|
|
343
|
+
}).join("");
|
|
344
|
+
const fin=view.status==="finalizing";
|
|
345
|
+
// 完成态:总览 tab = 返回「全部环节」分组视图(走查 2026-10-03)。
|
|
346
|
+
const viewAll=view.status==="completed"?' data-viewall="1" style="cursor:pointer"':"";
|
|
347
|
+
tabs+='<div class="step '+(fin?"active":view.status==="completed"&&viewingStep===null?"active":"")+'"'+(fin||view.status==="aligning"?' id="finalTab"':"")+viewAll
|
|
348
|
+
+'><span class="no">'+(fin?"◉":view.status==="completed"&&viewingStep===null?"◉":"4")+"</span>总览"+'<span class="tag">'+(fin?"终审":view.status==="completed"?"全部":"未到达")+"</span></div>";
|
|
349
|
+
el("steps").innerHTML=tabs;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function renderLocked(){
|
|
353
|
+
const aside=el("locked");
|
|
354
|
+
if(view.status==="finalizing"||view.status==="completed"){aside.style.display="none";return;}
|
|
355
|
+
const locked=view.steps.filter((s)=>s.status==="locked");
|
|
356
|
+
if(!locked.length){aside.style.display="none";return;}
|
|
357
|
+
aside.style.display="";
|
|
358
|
+
aside.innerHTML='<div class="glass"><h3><span class="ic">✓</span>已锁定基线 · '
|
|
359
|
+
+locked.map((s)=>STEP_CN[s.step]).join(" + ")+'</h3>'
|
|
360
|
+
+locked.map((s)=>'<div class="lkgrp"><details><summary>'+STEP_CN[s.step]+' · '+s.blocks.length+' 块</summary>'
|
|
361
|
+
+s.blocks.map((b)=>'<div class="lkitem"><span class="k">'+KIND_CN[b.kind]+'</span>'+esc(firstLine(b.content))+'</div>').join("")
|
|
362
|
+
+'</details></div>').join("")
|
|
363
|
+
+'<div class="note">锁定内容只读——要修改需走回退(批注时勾选「这是上游步骤的问题」)</div></div>';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function versionBarHtml(){
|
|
367
|
+
// 版本只作用单节点(走查 2026-10-03):只显示当前步的快照。
|
|
368
|
+
// 完成态跟随正在查看的环节;「全部环节」视图不显示版本条。
|
|
369
|
+
const cur=viewingStep??view.activeStep;
|
|
370
|
+
const versions=(view.versions||[]).filter((v)=>v.step===cur);
|
|
371
|
+
if(view.status==="completed"&&viewingStep===null&&viewingVersion===null)return "";
|
|
372
|
+
const active=!viewingVersion;
|
|
373
|
+
if(!versions.length)return active?"":"";
|
|
374
|
+
const chips=versions.slice().reverse().map((v)=>v.revision!==viewingVersion
|
|
375
|
+
?'<a data-viewver="'+v.revision+'">r'+v.revision+"</a>"
|
|
376
|
+
:'<b>r'+v.revision+"</b>").join(" · ");
|
|
377
|
+
const back=viewingVersion!==null?' — <a data-viewver="latest">返回最新版本</a>':"";
|
|
378
|
+
// 终态(completed/cancelled)不提供版本回退入口(走查 2026-10-03):对齐结束后只读。
|
|
379
|
+
const rollback=view.status==="aligning"&&viewingVersion!==null&&viewingVersion!==undefined
|
|
380
|
+
?' <button class="rollbackbtn" data-rollback="'+viewingVersion+'">回退到此版本</button>'
|
|
381
|
+
:"";
|
|
382
|
+
return '<div class="verbar">版本:'+(active?"<b>最新 r"+view.revision+"</b>":"")+(" · "+chips)+back+rollback+"</div>";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function renderMain(){
|
|
386
|
+
if(view.status==="finalizing"){
|
|
387
|
+
el("doneBanner").style.display="none";
|
|
388
|
+
const rows=view.steps.map((st)=>'<div class="fingrp"><h3>'+STEP_CN[st.step]
|
|
389
|
+
+"("+st.blocks.length+" 块,第 "+(st.baselineConfirmedAtRound??"-")+" 轮确认)</h3>"
|
|
390
|
+
+st.blocks.map((b)=>'<div class="brow"><span class="k">'+KIND_CN[b.kind]+'</span><span class="bid">'+esc(b.id)
|
|
391
|
+
+"</span><span>"+esc(firstLine(b.content))+"</span></div>").join("")+"</div>").join("");
|
|
392
|
+
el("main").innerHTML='<div class="finhead"><h2>终审总览——三步基线全部锁定</h2>'
|
|
393
|
+
+'<p>这是完整方案。确认后将生成最终基线并结束对齐;如有问题可点击上方任一已锁定环节回退修改。</p></div>'
|
|
394
|
+
+'<div class="finalview">'+rows+"</div>";
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if(view.status!=="aligning"){
|
|
398
|
+
el("main").innerHTML="";
|
|
399
|
+
const banner=el("doneBanner");
|
|
400
|
+
if(view.status==="completed"){
|
|
401
|
+
// 完成态(走查 2026-10-03):只读,但可切换环节、切换版本查看。
|
|
402
|
+
banner.style.display="";
|
|
403
|
+
banner.innerHTML='<div class="baseline"><h3>✓ 对齐完成——终审已确认,基线已锁定</h3><p>以下内容只读。可点击环节查看各步详情、切换历史版本;需要留档可在终端执行 <b>/alignment export</b> 导出只读 HTML。实施中改动任一决策须回本插件重新确认。</p></div>';
|
|
404
|
+
const head='<div id="pagehead">'+versionBarHtml()+"</div>";
|
|
405
|
+
if(viewingVersion!==null){
|
|
406
|
+
const snap=(view.versions||[]).find((v)=>v.revision===viewingVersion);
|
|
407
|
+
el("main").innerHTML=head+'<div class="stephead"><h2>历史版本 r'+viewingVersion+"</h2><span class='desc'>只读快照</span></div>"
|
|
408
|
+
+(snap&&!snap.truncated?snap.blocks.map((b,i)=>blockHtml(b,i,true)).join(""):'<div class="empty-hint">该版本仅存锚点或已不存在。</div>');
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if(viewingStep!==null){
|
|
412
|
+
const st=view.steps.find((cand)=>cand.step===viewingStep);
|
|
413
|
+
if(!st){viewingStep=null;render();return;}
|
|
414
|
+
el("main").innerHTML=head+'<div class="stephead"><h2>只读查看:'+(STEP_CN[viewingStep]??viewingStep)+'('+st.blocks.length+' 块)</h2><span class="desc">点击上方「总览」返回全部环节。</span></div>'
|
|
415
|
+
+'<div class="finalview"><div class="fingrp">'+st.blocks.map((b,i)=>blockHtml(b,i,true)).join("")+"</div></div>";
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const rows=view.steps.map((st2)=>'<div class="fingrp"><h3>'+(STEP_CN[st2.step]??st2.step)
|
|
419
|
+
+"("+st2.blocks.length+" 块,第 "+(st2.baselineConfirmedAtRound??"-")+" 轮确认)</h3>"
|
|
420
|
+
+'<div class="brows">'+st2.blocks.map((b)=>'<div class="brow"><span class="k">'+KIND_CN[b.kind]
|
|
421
|
+
+'</span><span class="bid">'+esc(b.id)+'</span><span>'+esc(firstLine(b.content))+"</span></div>").join("")+"</div></div>").join("");
|
|
422
|
+
el("main").innerHTML=head+'<div class="stephead"><h2>全部环节</h2><span class="desc">点击上方环节名可单独查看该步的完整块内容。</span></div>'
|
|
423
|
+
+'<div class="finalview">'+rows+"</div>";
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
banner.style.display="";
|
|
427
|
+
banner.innerHTML='<div class="baseline"><h3>对齐已取消</h3><p>现场保留,可在终端查看 /alignment log。</p></div>';
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
el("doneBanner").style.display="none";
|
|
431
|
+
const head='<div id="pagehead">'+versionBarHtml()+"</div>";
|
|
432
|
+
if(viewingVersion!==null){
|
|
433
|
+
// 历史版本只读(走查 2026-10-03):无确认/批注/填空入口。
|
|
434
|
+
const snap=(view.versions||[]).find((v)=>v.revision===viewingVersion);
|
|
435
|
+
if(!snap||snap.truncated){
|
|
436
|
+
el("main").innerHTML=head+"<div class='stephead'><h2>版本 r"+viewingVersion+"</h2><span class='desc'>"
|
|
437
|
+
+(snap?"该版本超出体积预算,仅保留锚点。":"该版本已不存在(可能被滚动淘汰)。")+"</span></div>";
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const desc="历史版本(只读)——确认与批注请返回最新版本。";
|
|
441
|
+
el("main").innerHTML=head+'<div class="stephead"><h2>'+STEP_CN[snap.step]+" · r"+snap.revision+"</h2><span class='desc'>"+desc+"</span></div>"
|
|
442
|
+
+snap.blocks.map((b,i)=>blockHtml(b,i,true)).join("");
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
// AI 修订中(走查 2026-10-03):内容区换机器人思考效果,顶部进度不动。
|
|
446
|
+
if(view.awaitingRevision){
|
|
447
|
+
el("main").innerHTML=head+botThink("AI 正在根据反馈修订……修订版发布后本页自动刷新");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const s=activeStep();
|
|
451
|
+
const desc={objective:"只对方向:干什么 / 不干什么 / 成功标准。禁止展开实现细节。",
|
|
452
|
+
detail:"业务规则、公式、数据流、异常路径。假设必须显式;缺信息挂「待补充」。",
|
|
453
|
+
design:"技术选型、模块、接口、文件改动、风险、验收。不得与已锁定基线矛盾。"}[s.step];
|
|
454
|
+
el("main").innerHTML=head+'<div class="stephead"><h2>'+STEP_CN[s.step]+'</h2><span class="desc">'+desc+"</span></div>"
|
|
455
|
+
+s.blocks.map((b,i)=>blockHtml(b,i)).join("");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function blockHtml(b,i,readonly){
|
|
459
|
+
if(readonly){
|
|
460
|
+
// 历史版本只读(走查 2026-10-03):无按钮、无输入、批注仅展示。
|
|
461
|
+
return '<article class="block '+(b.status==="approved"?"approved":"")+'" data-kind="'+b.kind
|
|
462
|
+
+'" data-block="'+esc(b.id)+'"><div class="top"><span class="badge '+b.kind+'">'+KIND_CN[b.kind]
|
|
463
|
+
+"</span>"+'<span class="bid">'+esc(b.id)+"</span>"
|
|
464
|
+
+(b.status==="approved"?'<span class="skiphint">已确认</span>':"")+"</div>"
|
|
465
|
+
+'<div class="content">'+esc(b.content)+"</div>"
|
|
466
|
+
+(b.rationale?'<details class="meta"><summary>理由</summary>'+esc(b.rationale)+"</details>":"")
|
|
467
|
+
+(b.comments||[]).map((c)=>commentHtml(b,c,true)).join("")+"</article>";
|
|
468
|
+
}
|
|
469
|
+
const modified=b.modifiedInRound===view.round?'<span class="mod">本轮已修改</span>':"";
|
|
470
|
+
return blockHtmlInteractive(b,i,modified);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function blockHtmlInteractive(b,i,modified){
|
|
474
|
+
const answered=(b.draftAnswer||"").trim()!=="";
|
|
475
|
+
const approvedNow=effectiveApproved(b);
|
|
476
|
+
const missingHtml=b.kind==="missing"
|
|
477
|
+
?(b.draftAnswer
|
|
478
|
+
?'<div class="missinginput"><div class="comment resolved">✓ 已收到你的答案:'+esc(b.draftAnswer)+'(提交后由 AI 并入本块)</div></div>'
|
|
479
|
+
:'<div class="missinginput"><textarea data-answer="'+esc(b.id)+'" placeholder="在这里补充你的答案……">'
|
|
480
|
+
+esc(drafts.answers[b.id]||"")+"</textarea></div>"):"";
|
|
481
|
+
const canApprove=b.kind!=="missing"||answered||(drafts.answers[b.id]||"").trim()!=="";
|
|
482
|
+
const approveHtml=canApprove
|
|
483
|
+
?'<button class="approvebtn" data-approve="'+esc(b.id)+'">'+(approvedNow?"已确认":"确认")+"</button>"
|
|
484
|
+
:'<span class="skiphint">填写后进入确认</span>';
|
|
485
|
+
const metaHtml=(b.rationale?'<details class="meta"><summary>理由</summary>'+esc(b.rationale)+"</details>":"")
|
|
486
|
+
+(b.impact?'<details class="meta"><summary>影响范围</summary>'+esc(b.impact.join(" · "))+"</details>":"");
|
|
487
|
+
return '<article class="block '+(approvedNow?"approved":"")+'" data-kind="'+b.kind
|
|
488
|
+
+'" data-block="'+esc(b.id)+'" style="animation-delay:'+Math.min(i*35,280)+'ms"><div class="top">'
|
|
489
|
+
+'<span class="badge '+b.kind+'">'+KIND_CN[b.kind]+"</span>"+'<span class="bid">'+esc(b.id)+"</span>"+modified
|
|
490
|
+
+approveHtml+"</div>"+'<div class="content" data-content="'+esc(b.id)+'">'+esc(b.content)+"</div>"
|
|
491
|
+
+missingHtml+metaHtml+(b.comments||[]).map((c)=>commentHtml(b,c,false)).join("")+"</article>";
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function commentHtml(b,c,readonly){
|
|
495
|
+
if(c.status==="resolved"){
|
|
496
|
+
const k={answered:"已回答",dismissed:"已驳回",incorporated:"已并入修订"}[c.resolution.kind];
|
|
497
|
+
return '<div class="comment resolved">✓ '+k+(c.resolution.text?(":"+esc(c.resolution.text)):"")
|
|
498
|
+
+' — '+(c.author==="assistant"?"AI 疑问":"你的批注")+": "+esc((c.note||"").slice(0,44))+'</div>';
|
|
499
|
+
}
|
|
500
|
+
if(c.author==="assistant"){
|
|
501
|
+
if(readonly)return '<div class="comment ai"><div class="who"><span class="avatar ai">AI</span>AI 疑问</div>'+esc(c.note)+'</div>';
|
|
502
|
+
let body;
|
|
503
|
+
if(drafts.dismissed[c.id])body='<div class="draftreply"><span>你标记了「不重要」(提交后生效)</span><a data-undodismiss="'+c.id+'">撤销</a></div>';
|
|
504
|
+
else if(drafts.revised[c.id])body='<textarea class="replybox" data-reply-input="'+c.id+'" placeholder="输入你的回答……">'
|
|
505
|
+
+esc(drafts.replies[c.id]||"")+'</textarea><div class="actions"><button data-reply-save="'+c.id+'">确认回答</button></div>';
|
|
506
|
+
else if(drafts.replies[c.id])body='<div class="draftreply"><span>你的回答:'+esc(drafts.replies[c.id])+'(提交后生效)</span><a data-reply-edit="'+c.id+'">修改</a></div>';
|
|
507
|
+
else body='<div class="actions"><button data-reply="'+c.id+'">回答</button><button data-dismiss="'+c.id+'">不重要</button></div>';
|
|
508
|
+
return '<div class="comment ai"><div class="who"><span class="avatar ai">AI</span>AI 疑问 · 拿不准,需要你裁决</div>'+esc(c.note)+body+'</div>';
|
|
509
|
+
}
|
|
510
|
+
if(readonly)return '<div class="comment user"><div class="who"><span class="avatar user">你</span>你的批注'
|
|
511
|
+
+(c.escalate?" · 上游问题":"")+'</div>'+(c.quote?'<div class="quote">'+esc(c.quote)+'</div>':"")+esc(c.note)+'</div>';
|
|
512
|
+
return '<div class="comment user"><div class="who"><span class="avatar user">你</span>你的批注 · 等待 AI 处理'
|
|
513
|
+
+(c.escalate?" · 上游问题":"")+'</div>'+(c.quote?'<div class="quote">'+esc(c.quote)+'</div>':"")+esc(c.note)+'</div>';
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function renderDock(){
|
|
517
|
+
const btn=el("mainBtn"),hint=el("gateHint");
|
|
518
|
+
const addbox=document.querySelector(".addbox");
|
|
519
|
+
if(!view||view.status!=="aligning"&&view.status!=="finalizing"){
|
|
520
|
+
// 终态:整个底部操作条隐藏(走查 2026-10-03),对齐结束后页面只读。
|
|
521
|
+
const dock=document.querySelector(".dock");
|
|
522
|
+
if(dock)dock.style.display="none";
|
|
523
|
+
return;}
|
|
524
|
+
if(view.status==="finalizing"){
|
|
525
|
+
if(addbox)addbox.style.display="none"; // 终审总览无补充输入(走查 2026-10-03)
|
|
526
|
+
btn.textContent="确认基线,结束对齐";btn.className="mainbtn confirm";btn.disabled=false;
|
|
527
|
+
hint.className="gatehint ok";hint.textContent="终审总览:查看完整方案后确认;如有问题,点击上方已锁定环节回退。";return;}
|
|
528
|
+
if(addbox)addbox.style.display="";
|
|
529
|
+
const dockEl=document.querySelector(".dock");
|
|
530
|
+
if(dockEl)dockEl.style.display="";
|
|
531
|
+
const n=gatePendingCount(),d=hasDrafts();
|
|
532
|
+
if(n===0&&!d){btn.textContent=view.activeStep==="design"?"确认方案,进入终审总览":"确认完成,进入下一步";
|
|
533
|
+
btn.className="mainbtn confirm";btn.disabled=false;
|
|
534
|
+
hint.className="gatehint ok";hint.textContent="门禁三条件满足——全部已确认、批注已清空、无补充。";return;}
|
|
535
|
+
btn.className="mainbtn";btn.disabled=false;
|
|
536
|
+
if(d){btn.textContent="提交本轮反馈";
|
|
537
|
+
hint.className="gatehint";
|
|
538
|
+
hint.innerHTML=n?("已捕获反馈,提交后 AI 修订;尚有 <b>"+n+"</b> 处待处理。"):"已捕获反馈,提交后 AI 修订并重新等待确认。";}
|
|
539
|
+
else{btn.textContent="定位 "+n+" 处待处理";hint.className="gatehint";
|
|
540
|
+
hint.textContent="没有待提交的反馈——点击按钮,页面会指出还有哪里没完成。";}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// ---------------------------------------------------------------------------
|
|
544
|
+
// interactions
|
|
545
|
+
// ---------------------------------------------------------------------------
|
|
546
|
+
document.addEventListener("click",(e)=>{
|
|
547
|
+
const t=e.target;
|
|
548
|
+
// 完成态「总览」tab:返回全部环节分组视图(走查 2026-10-03)。
|
|
549
|
+
if(t.dataset&&t.dataset.viewall){
|
|
550
|
+
viewingStep=null;viewingVersion=null;render();return;
|
|
551
|
+
}
|
|
552
|
+
// 已锁定环节 tab 点击回退(走查 2026-10-03):后续环节作废重走。
|
|
553
|
+
// 完成态环节查看(只读):点击 tab 切换查看该步。
|
|
554
|
+
if(t.dataset&&t.dataset.viewstep){
|
|
555
|
+
viewingStep=t.dataset.viewstep;
|
|
556
|
+
viewingVersion=null;
|
|
557
|
+
render();return;
|
|
558
|
+
}
|
|
559
|
+
if(t.dataset&&t.dataset.retreat){
|
|
560
|
+
const target=t.dataset.retreat;
|
|
561
|
+
askConfirm("回退到「"+STEP_CN[target]+"」","后面的环节将作废,需要重新确认。","确认回退",()=>{
|
|
562
|
+
approvals=null;viewingVersion=null;
|
|
563
|
+
postPageAction({action:"retreat",baseRevision:view.revision,targetStep:target,approvals:[],comments:[],
|
|
564
|
+
commentReplies:[],answers:[],additions:[],stepConfirmed:false},"正在回退……");
|
|
565
|
+
});
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
// 版本切换(走查 2026-10-03):历史版本只读查看。
|
|
569
|
+
if(t.dataset&&t.dataset.viewver){
|
|
570
|
+
viewingVersion=t.dataset.viewver==="latest"?null:Number(t.dataset.viewver);
|
|
571
|
+
render();return;
|
|
572
|
+
}
|
|
573
|
+
if(t.dataset&&t.dataset.rollback){
|
|
574
|
+
const targetRevision=Number(t.dataset.rollback);
|
|
575
|
+
askConfirm("把当前步回退到版本 r"+targetRevision,"当前未确认的修改将丢弃(历史版本中的批注会保留)。","确认回退",()=>{
|
|
576
|
+
approvals=null;
|
|
577
|
+
postPageAction({action:"rollback",baseRevision:view.revision,targetRevision,approvals:[],comments:[],
|
|
578
|
+
commentReplies:[],answers:[],additions:[],stepConfirmed:false},"正在回退版本……");
|
|
579
|
+
});
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if(t.dataset&&t.dataset.approve!==undefined&&t.dataset.approve!==""){toggleApprove(t.dataset.approve);return;}
|
|
583
|
+
if(t.dataset&&t.dataset.reply){drafts.revised[t.dataset.reply]=true;render();return;}
|
|
584
|
+
if(t.dataset&&t.dataset.replyEdit){drafts.revised[t.dataset.replyEdit]=true;render();return;}
|
|
585
|
+
if(t.dataset&&t.dataset.replySave){
|
|
586
|
+
const box=t.closest(".comment").querySelector(".replybox");
|
|
587
|
+
if(box&&!box.value.trim()){box.focus();return;}
|
|
588
|
+
drafts.replies[t.dataset.replySave]=box.value.trim();delete drafts.revised[t.dataset.replySave];render();return;}
|
|
589
|
+
if(t.dataset&&t.dataset.dismiss){drafts.dismissed[t.dataset.dismiss]=true;render();return;}
|
|
590
|
+
if(t.dataset&&t.dataset.undodismiss){delete drafts.dismissed[t.dataset.undodismiss];render();return;}
|
|
591
|
+
});
|
|
592
|
+
document.addEventListener("input",(e)=>{
|
|
593
|
+
const t=e.target;
|
|
594
|
+
if(t.dataset&&t.dataset.answer){drafts.answers[t.dataset.answer]=t.value;renderDock();return;}
|
|
595
|
+
if(t.dataset&&t.dataset.replyInput){drafts.replies[t.dataset.replyInput]=t.value;return;}
|
|
596
|
+
if(t.id==="additions"){drafts.additions=t.value;renderDock();}
|
|
597
|
+
});
|
|
598
|
+
function toggleApprove(id){
|
|
599
|
+
if(!approvals){
|
|
600
|
+
approvals={};
|
|
601
|
+
for(const b of activeStep().blocks)approvals[b.id]=b.status==="approved";
|
|
602
|
+
}
|
|
603
|
+
approvals[id]=!approvals[id];
|
|
604
|
+
// 局部更新(2026-10-08 流畅度走查):确认是最高频操作,全量 innerHTML 重建会
|
|
605
|
+
// 折叠已展开的 <details>、丢选区,体感就是「点一下整页闪一遍」。只改该块节点
|
|
606
|
+
// + steps 计数 + 底部门禁,与全量 render 的可见结果一致。
|
|
607
|
+
const s=activeStep();
|
|
608
|
+
const b=s&&s.blocks.find((x)=>x.id===id);
|
|
609
|
+
if(b){
|
|
610
|
+
const node=el("main").querySelector('[data-block="'+id+'"]');
|
|
611
|
+
if(node){
|
|
612
|
+
const now=effectiveApproved(b);
|
|
613
|
+
node.classList.toggle("approved",now);
|
|
614
|
+
const btn=node.querySelector(".approvebtn");
|
|
615
|
+
if(btn)btn.textContent=now?"已确认":"确认";
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
renderSteps();renderDock();
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// selection annotation (single-block selection only)
|
|
622
|
+
let selInfo=null,annotGuardUntil=0;
|
|
623
|
+
document.addEventListener("mouseup",(e)=>{
|
|
624
|
+
if(e.target.closest&&e.target.closest(".annotpop"))return;
|
|
625
|
+
if(e.target.closest&&e.target.closest(".selbtn"))return;
|
|
626
|
+
if(Date.now()<annotGuardUntil)return;
|
|
627
|
+
el("annotPop").style.display="none";
|
|
628
|
+
const btn=el("selBtn");
|
|
629
|
+
const sel=window.getSelection();
|
|
630
|
+
if(!sel||sel.isCollapsed){btn.style.display="none";return;}
|
|
631
|
+
const text=sel.toString().trim();
|
|
632
|
+
const range=sel.getRangeAt(0);
|
|
633
|
+
const owner=(range.startContainer.parentElement&&range.startContainer.parentElement.closest)
|
|
634
|
+
?range.startContainer.parentElement.closest(".content[data-content]"):null;
|
|
635
|
+
const ownerEnd=(range.endContainer.parentElement&&range.endContainer.parentElement.closest)
|
|
636
|
+
?range.endContainer.parentElement.closest(".content[data-content]"):null;
|
|
637
|
+
if(!owner||owner!==ownerEnd||!text){btn.style.display="none";return;}
|
|
638
|
+
const rect=range.getBoundingClientRect();
|
|
639
|
+
btn.style.display="";
|
|
640
|
+
btn.className="selbtn";
|
|
641
|
+
btn.style.left=(rect.left+window.scrollX+rect.width/2-46)+"px";
|
|
642
|
+
btn.style.top=(rect.top+window.scrollY-40)+"px";
|
|
643
|
+
selInfo={blockId:owner.dataset.content,text};
|
|
644
|
+
});
|
|
645
|
+
el("selBtn").addEventListener("mousedown",(e)=>{
|
|
646
|
+
e.preventDefault();
|
|
647
|
+
annotGuardUntil=Date.now()+400;
|
|
648
|
+
const pop=el("annotPop"),btn=el("selBtn");
|
|
649
|
+
el("annotQuote").textContent="「"+(selInfo.text.length>60?selInfo.text.slice(0,60)+"…":selInfo.text)+"」";
|
|
650
|
+
el("annotNote").value="";el("annotEsc").checked=false;
|
|
651
|
+
pop.style.display="";
|
|
652
|
+
pop.style.left=Math.min(parseFloat(btn.style.left),window.scrollX+window.innerWidth-360)+"px";
|
|
653
|
+
pop.style.top=(parseFloat(btn.style.top)+42)+"px";
|
|
654
|
+
el("annotNote").focus();
|
|
655
|
+
btn.style.display="none";
|
|
656
|
+
});
|
|
657
|
+
el("annotClose").addEventListener("click",()=>{el("annotPop").style.display="none";window.getSelection().removeAllRanges();});
|
|
658
|
+
el("annotGo").addEventListener("click",()=>{
|
|
659
|
+
const note=el("annotNote").value.trim();
|
|
660
|
+
if(!note){el("annotNote").focus();return;}
|
|
661
|
+
if(!selInfo.pendingComments)selInfo.pendingComments=[];
|
|
662
|
+
selInfo.pendingComments.push({blockId:selInfo.blockId,quote:selInfo.text,note,escalate:el("annotEsc").checked});
|
|
663
|
+
toast("批注已暂存("+selInfo.pendingComments.length+" 条)——随本轮反馈一起提交");
|
|
664
|
+
el("annotPop").style.display="none";window.getSelection().removeAllRanges();
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
// spotlight: every click replays a full cycle; auto restore ~4.5s or any click
|
|
668
|
+
let spotSession=0;
|
|
669
|
+
function clearSpot(){
|
|
670
|
+
const sec=el("main");
|
|
671
|
+
sec.classList.remove("focusing");
|
|
672
|
+
sec.querySelectorAll(".block.attention").forEach((node)=>{node.classList.remove("attention");node.removeAttribute("data-reason");});
|
|
673
|
+
}
|
|
674
|
+
function spotlight(msg){
|
|
675
|
+
if(!view||view.status!=="aligning")return;
|
|
676
|
+
const items=view.gate.pending;
|
|
677
|
+
if(!items.length){toast("当前没有待处理项");return;}
|
|
678
|
+
const session=++spotSession;
|
|
679
|
+
clearSpot();
|
|
680
|
+
const sec=el("main");
|
|
681
|
+
sec.classList.add("focusing");
|
|
682
|
+
const nodes=items.map((item)=>{
|
|
683
|
+
const node=sec.querySelector('[data-block="'+item.blockId+'"]');
|
|
684
|
+
if(node){
|
|
685
|
+
node.classList.add("attention");
|
|
686
|
+
node.dataset.reason={unapproved:"未确认",missing_empty:"待补充","missing-empty":"待补充",
|
|
687
|
+
assistant_question_open:"疑问待回应","assistant-question-open":"疑问待回应"}[item.reason]||item.reason;
|
|
688
|
+
}
|
|
689
|
+
return node;
|
|
690
|
+
}).filter(Boolean);
|
|
691
|
+
if(nodes[0])nodes[0].scrollIntoView({behavior:"smooth",block:"center"});
|
|
692
|
+
toast(msg||("还有 "+nodes.length+" 处待处理——已为你定位"));
|
|
693
|
+
const finish=()=>{if(session!==spotSession)return;clearSpot();document.removeEventListener("click",onDoc);};
|
|
694
|
+
const onDoc=(ev)=>{
|
|
695
|
+
if(session!==spotSession){document.removeEventListener("click",onDoc);return;}
|
|
696
|
+
if(ev.target.closest&&ev.target.closest("#mainBtn"))return;
|
|
697
|
+
finish();
|
|
698
|
+
};
|
|
699
|
+
setTimeout(()=>document.addEventListener("click",onDoc),80);
|
|
700
|
+
setTimeout(finish,4500);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
let toastTimer;
|
|
704
|
+
function toast(msg){
|
|
705
|
+
const t=el("toast");t.textContent=msg;t.classList.add("show");
|
|
706
|
+
clearTimeout(toastTimer);toastTimer=setTimeout(()=>t.classList.remove("show"),3200);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// ---------------------------------------------------------------------------
|
|
710
|
+
// submit
|
|
711
|
+
// ---------------------------------------------------------------------------
|
|
712
|
+
el("mainBtn").addEventListener("click",async()=>{
|
|
713
|
+
if(!view)return;
|
|
714
|
+
if(view.status==="finalizing"){
|
|
715
|
+
// 终审确认(走查 2026-10-03):唯一 completed 路径;文件留档走 /alignment export。
|
|
716
|
+
return postPageAction({action:"feedback",baseRevision:view.revision,approvals:[],comments:[],
|
|
717
|
+
commentReplies:[],answers:[],additions:[],stepConfirmed:true},"正在提交终审确认……");
|
|
718
|
+
}
|
|
719
|
+
if(view.status!=="aligning")return;
|
|
720
|
+
const n=gatePendingCount(),d=hasDrafts();
|
|
721
|
+
if(n===0&&!d){return submitRound(true);}
|
|
722
|
+
if(d){
|
|
723
|
+
// Pure approval diffs with a locally green gate confirm the step in the
|
|
724
|
+
// same round (approvals mirror + stepConfirmed); any content feedback
|
|
725
|
+
// goes as a plain revision round.
|
|
726
|
+
return submitRound(n===0&&!hasContentDrafts());
|
|
727
|
+
}
|
|
728
|
+
spotlight();
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
async function postPageAction(payload,submittingText){
|
|
732
|
+
if(submitting)return;
|
|
733
|
+
submitting=true;
|
|
734
|
+
el("mainBtn").disabled=true;
|
|
735
|
+
if(submittingText)el("main").innerHTML='<div class="reviving">'+submittingText+"</div>";
|
|
736
|
+
try{
|
|
737
|
+
const res=await fetch("round",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(payload)});
|
|
738
|
+
const out=await res.json().catch(()=>({}));
|
|
739
|
+
if(!res.ok){
|
|
740
|
+
if(out&&out.code==="stale-state"){toast("页面快照已过期——已刷新到最新状态,请重试");approvals=null;}
|
|
741
|
+
else toast("提交失败:"+((out&&out.error)||res.status));
|
|
742
|
+
// 失败也要离开 spinner(与 submitRound 的 view=null 同语义):否则 main
|
|
743
|
+
// 停在「正在提交/回退……」直到下一次 revision 变化才恢复——真实会话
|
|
744
|
+
// 里就是「点击后跟傻了一样」的来源(2026-10-08 走查)。
|
|
745
|
+
view=null;
|
|
746
|
+
}
|
|
747
|
+
}catch(e){toast("提交失败:网络错误,请重试");submitting=false;view=null;render();return;}
|
|
748
|
+
submitting=false;
|
|
749
|
+
view=null;
|
|
750
|
+
await refresh();
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
async function submitRound(confirmStep){
|
|
754
|
+
if(submitting)return;
|
|
755
|
+
submitting=true;
|
|
756
|
+
el("mainBtn").disabled=true;
|
|
757
|
+
const s=activeStep();
|
|
758
|
+
const approvalsSet={};
|
|
759
|
+
for(const b of s.blocks){
|
|
760
|
+
const locallyToggled=approvals&&Object.prototype.hasOwnProperty.call(approvals,b.id);
|
|
761
|
+
approvalsSet[b.id]=locallyToggled?approvals[b.id]:(b.status==="approved");
|
|
762
|
+
}
|
|
763
|
+
const body={
|
|
764
|
+
baseRevision:view.revision,
|
|
765
|
+
approvals:Object.keys(approvalsSet).filter((id)=>approvalsSet[id]),
|
|
766
|
+
comments:(selInfo&&selInfo.pendingComments)||[],
|
|
767
|
+
commentReplies:[],
|
|
768
|
+
answers:[],
|
|
769
|
+
additions:drafts.additions.trim()?[drafts.additions.trim()]:[],
|
|
770
|
+
stepConfirmed:!!confirmStep,
|
|
771
|
+
};
|
|
772
|
+
for(const b of s.blocks){
|
|
773
|
+
if(b.kind==="missing"&&!b.draftAnswer&&(drafts.answers[b.id]||"").trim())
|
|
774
|
+
body.answers.push({blockId:b.id,value:drafts.answers[b.id].trim()});
|
|
775
|
+
for(const c of b.comments){
|
|
776
|
+
if(c.status!=="open"||c.author!=="assistant")continue;
|
|
777
|
+
if(drafts.dismissed[c.id])body.commentReplies.push({commentId:c.id,blockId:b.id,dismiss:true});
|
|
778
|
+
else if(drafts.replies[c.id])body.commentReplies.push({commentId:c.id,blockId:b.id,answer:drafts.replies[c.id]});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
el("main").innerHTML='<div class="reviving">正在提交第 '+view.round+' 轮反馈……</div>';
|
|
782
|
+
try{
|
|
783
|
+
const res=await fetch("round",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body)});
|
|
784
|
+
const payload=await res.json().catch(()=>({}));
|
|
785
|
+
if(!res.ok){
|
|
786
|
+
if(payload&&payload.code==="stale-state"){toast("页面快照已过期——正在刷新,请基于新版重提");approvals=null;}
|
|
787
|
+
else toast("提交失败:"+((payload&&payload.error)||res.status));
|
|
788
|
+
}else{
|
|
789
|
+
drafts={answers:{},replies:{},dismissed:{},additions:"",revised:{}};
|
|
790
|
+
el("additions").value="";
|
|
791
|
+
if(selInfo)selInfo.pendingComments=[];
|
|
792
|
+
approvals=null;
|
|
793
|
+
toast(payload&&payload.stepConfirmed?"本步基线已锁定——进入下一步":"反馈已送达会话,AI 修订中");
|
|
794
|
+
}
|
|
795
|
+
}catch(e){toast("提交失败:网络错误,请重试");}
|
|
796
|
+
submitting=false;
|
|
797
|
+
view=null;
|
|
798
|
+
await refresh();
|
|
799
|
+
if(view&&view.status==="aligning"&&gatePendingCount()>0)spotlight("还有 "+gatePendingCount()+" 处待处理——已为你定位");
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
el("additions").addEventListener("input",(e)=>{drafts.additions=e.target.value;});
|
|
803
|
+
setInterval(()=>{void refresh();},2000);
|
|
804
|
+
void refresh();
|
|
805
|
+
`;
|
|
806
|
+
/** The fixed page template: no state, no model input — only the two endpoints above. */
|
|
807
|
+
export function renderAlignmentPage() {
|
|
808
|
+
return `<!DOCTYPE html>
|
|
809
|
+
<html lang="zh-CN">
|
|
810
|
+
<head>
|
|
811
|
+
<meta charset="UTF-8">
|
|
812
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
813
|
+
<meta name="color-scheme" content="light">
|
|
814
|
+
<title>需求对齐 · Alignment</title>
|
|
815
|
+
<style>${CSS}</style>
|
|
816
|
+
</head>
|
|
817
|
+
<body>
|
|
818
|
+
<div class="blob b1"></div><div class="blob b2"></div><div class="blob b3"></div>
|
|
819
|
+
<div id="offline" class="offline" style="display:none"></div>
|
|
820
|
+
<div class="wrap">
|
|
821
|
+
<header class="task">
|
|
822
|
+
<span class="eyebrow">需求对齐</span>
|
|
823
|
+
<h1 id="title">需求对齐</h1>
|
|
824
|
+
<div class="meta" id="meta"></div>
|
|
825
|
+
<div class="steps" id="steps"></div>
|
|
826
|
+
</header>
|
|
827
|
+
<div id="doneBanner" style="display:none"></div>
|
|
828
|
+
<div class="cols">
|
|
829
|
+
<section id="main"><div class="botwrap"><div class="botcard"><div class="bot"><div class="antenna"></div><div class="face"><span class="eye"></span><span class="eye"></span></div></div><div class="thinkdots"><span></span><span></span><span></span></div><div class="bottext">正在加载对齐状态……</div></div></div></section>
|
|
830
|
+
<aside class="locked" id="locked" style="display:none"></aside>
|
|
831
|
+
</div>
|
|
832
|
+
</div>
|
|
833
|
+
<footer class="dock">
|
|
834
|
+
<div class="inner">
|
|
835
|
+
<div class="addbox">
|
|
836
|
+
<label>补充——想到什么直接写,AI 会结构化为新块,再经你确认</label>
|
|
837
|
+
<textarea id="additions" placeholder="例如:对了,退款要支持退运费……"></textarea>
|
|
838
|
+
</div>
|
|
839
|
+
<button class="mainbtn" id="mainBtn" disabled>连接中</button>
|
|
840
|
+
</div>
|
|
841
|
+
<div class="gatehint" id="gateHint"></div>
|
|
842
|
+
</footer>
|
|
843
|
+
<button id="selBtn" style="display:none">批注此段</button>
|
|
844
|
+
<div class="annotpop" id="annotPop" style="display:none">
|
|
845
|
+
<div class="sel" id="annotQuote"></div>
|
|
846
|
+
<textarea id="annotNote" placeholder="这段有问题——指出哪里不对、你期望什么……"></textarea>
|
|
847
|
+
<label class="esc"><input type="checkbox" id="annotEsc"> 这是上游步骤的问题(触发回退评估)</label>
|
|
848
|
+
<div class="row"><button id="annotClose">取消</button><button id="annotGo" class="go">挂到该块</button></div>
|
|
849
|
+
</div>
|
|
850
|
+
<div id="toast"></div>
|
|
851
|
+
<script>
|
|
852
|
+
${SCRIPT}
|
|
853
|
+
</script>
|
|
854
|
+
</body>
|
|
855
|
+
</html>`;
|
|
856
|
+
}
|
|
857
|
+
//# sourceMappingURL=page.js.map
|