psyclaw 0.29.17 → 0.29.19
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/README.md +7 -4
- package/dist/apps/panel/index.html +441 -154
- package/dist/apps/panel/observability.js +87 -0
- package/dist/src/adapters/pi/extension.js +40 -2
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/chat.js +2 -0
- package/dist/src/chat.js.map +1 -1
- package/dist/src/cli.js +63 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/observability/config.d.ts +47 -0
- package/dist/src/observability/config.js +125 -0
- package/dist/src/observability/config.js.map +1 -0
- package/dist/src/observability/index.d.ts +29 -0
- package/dist/src/observability/index.js +75 -0
- package/dist/src/observability/index.js.map +1 -0
- package/dist/src/observability/node-sdks.d.ts +11 -0
- package/dist/src/observability/node-sdks.js +92 -0
- package/dist/src/observability/node-sdks.js.map +1 -0
- package/dist/src/observability/notice.d.ts +9 -0
- package/dist/src/observability/notice.js +43 -0
- package/dist/src/observability/notice.js.map +1 -0
- package/dist/src/observability/preference.d.ts +23 -0
- package/dist/src/observability/preference.js +102 -0
- package/dist/src/observability/preference.js.map +1 -0
- package/dist/src/observability/public-keys.d.ts +10 -0
- package/dist/src/observability/public-keys.js +11 -0
- package/dist/src/observability/public-keys.js.map +1 -0
- package/dist/src/orchestration/runner.js +21 -2
- package/dist/src/orchestration/runner.js.map +1 -1
- package/dist/src/panel/extension.js +5 -1
- package/dist/src/panel/extension.js.map +1 -1
- package/dist/src/panel/hub.d.ts +2 -0
- package/dist/src/panel/hub.js +12 -0
- package/dist/src/panel/hub.js.map +1 -1
- package/dist/src/panel/server.d.ts +4 -0
- package/dist/src/panel/server.js +188 -75
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/panel/workbench.js +15 -1
- package/dist/src/panel/workbench.js.map +1 -1
- package/dist/src/session/modes.d.ts +13 -0
- package/dist/src/session/modes.js +39 -3
- package/dist/src/session/modes.js.map +1 -1
- package/dist/src/style/cli-ui.js +2 -0
- package/dist/src/style/cli-ui.js.map +1 -1
- package/docs/PsyClaw/344/275/277/347/224/250/347/231/275/347/232/256/344/271/246_v0.29.15.md +907 -0
- package/docs//344/275/277/347/224/250/347/231/275/347/232/256/344/271/246.md +3 -0
- package/package.json +7 -2
- package/scripts/rebrand-pi.mjs +74 -1
- package/skills/recommended/catalog.json +4 -2
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
6
|
<title>PsyClaw Panel · 科研工作台</title>
|
|
7
|
+
<script id="psyclaw-obs-config">window.__PSYCLAW_OBS__={"surface":"panel","sentryDsn":"","posthogKey":"","posthogHost":"","release":""};</script>
|
|
8
|
+
<script src="/observability.js" defer></script>
|
|
7
9
|
<style>
|
|
8
10
|
:root {
|
|
9
11
|
color-scheme: light;
|
|
@@ -176,6 +178,22 @@
|
|
|
176
178
|
padding: 3px 9px;
|
|
177
179
|
border-radius: 999px;
|
|
178
180
|
border: 1px solid var(--border-subtle);
|
|
181
|
+
cursor: pointer;
|
|
182
|
+
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
|
183
|
+
}
|
|
184
|
+
.local-badge:hover {
|
|
185
|
+
background: var(--bg-surface-hover);
|
|
186
|
+
border-color: var(--border-default);
|
|
187
|
+
color: var(--text-main);
|
|
188
|
+
}
|
|
189
|
+
.local-badge.warn {
|
|
190
|
+
color: #92400e;
|
|
191
|
+
background: #fffbeb;
|
|
192
|
+
border-color: #fcd34d;
|
|
193
|
+
}
|
|
194
|
+
.local-badge.warn .pulse-dot {
|
|
195
|
+
background: #f59e0b;
|
|
196
|
+
box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.25);
|
|
179
197
|
}
|
|
180
198
|
.pulse-dot {
|
|
181
199
|
width: 6px;
|
|
@@ -535,33 +553,109 @@
|
|
|
535
553
|
color: var(--text-secondary);
|
|
536
554
|
line-height: 1.4;
|
|
537
555
|
}
|
|
538
|
-
.help-
|
|
556
|
+
.help-whitepaper {
|
|
539
557
|
margin: 0;
|
|
540
|
-
padding:
|
|
541
|
-
list-style: none;
|
|
542
|
-
display: grid;
|
|
543
|
-
gap: 8px;
|
|
558
|
+
padding: 18px 20px 22px;
|
|
544
559
|
border-radius: 14px;
|
|
545
560
|
border: 1px solid var(--border-subtle);
|
|
561
|
+
background: var(--bg-surface);
|
|
562
|
+
max-width: 78ch;
|
|
563
|
+
line-height: 1.65;
|
|
564
|
+
color: var(--text-main);
|
|
565
|
+
font-size: 13.5px;
|
|
566
|
+
}
|
|
567
|
+
.help-whitepaper h1 { font-size: 1.45rem; margin: 0 0 0.75em; }
|
|
568
|
+
.help-whitepaper h2 { font-size: 1.2rem; margin: 1.4em 0 0.55em; }
|
|
569
|
+
.help-whitepaper h3 { font-size: 1.05rem; margin: 1.2em 0 0.45em; }
|
|
570
|
+
.help-whitepaper p { margin: 0 0 0.85em; color: var(--text-secondary); }
|
|
571
|
+
.help-whitepaper ul, .help-whitepaper ol { margin: 0 0 0.9em; padding-left: 1.35em; color: var(--text-secondary); }
|
|
572
|
+
.help-whitepaper li { margin: 0.25em 0; }
|
|
573
|
+
.help-whitepaper code {
|
|
574
|
+
font-family: var(--font-mono);
|
|
575
|
+
font-size: 0.9em;
|
|
546
576
|
background: var(--bg-surface-subtle);
|
|
577
|
+
padding: 0.1em 0.35em;
|
|
578
|
+
border-radius: 4px;
|
|
547
579
|
}
|
|
548
|
-
.help-
|
|
549
|
-
|
|
550
|
-
padding
|
|
580
|
+
.help-whitepaper pre {
|
|
581
|
+
overflow: auto;
|
|
582
|
+
padding: 12px 14px;
|
|
583
|
+
border-radius: 10px;
|
|
584
|
+
background: var(--bg-surface-subtle);
|
|
585
|
+
border: 1px solid var(--border-subtle);
|
|
586
|
+
font-size: 12px;
|
|
587
|
+
}
|
|
588
|
+
.help-whitepaper a { color: var(--brand-accent); }
|
|
589
|
+
.help-whitepaper blockquote {
|
|
590
|
+
margin: 0 0 0.9em;
|
|
591
|
+
padding: 0.35em 0 0.35em 0.9em;
|
|
592
|
+
border-left: 3px solid var(--brand-primary-border);
|
|
551
593
|
color: var(--text-muted);
|
|
552
|
-
font-size: 12.5px;
|
|
553
|
-
line-height: 1.55;
|
|
554
594
|
}
|
|
555
|
-
.
|
|
556
|
-
|
|
595
|
+
.checklist-empty {
|
|
596
|
+
display: flex;
|
|
597
|
+
flex-direction: column;
|
|
598
|
+
align-items: center;
|
|
599
|
+
justify-content: center;
|
|
600
|
+
gap: 6px;
|
|
601
|
+
min-height: 140px;
|
|
602
|
+
padding: 28px 16px;
|
|
603
|
+
color: var(--text-muted);
|
|
604
|
+
font-size: 13px;
|
|
605
|
+
text-align: center;
|
|
606
|
+
background: var(--bg-surface-subtle);
|
|
607
|
+
border-radius: 10px;
|
|
608
|
+
}
|
|
609
|
+
.checklist-empty strong {
|
|
610
|
+
color: var(--text-secondary);
|
|
611
|
+
font-weight: 600;
|
|
612
|
+
}
|
|
613
|
+
.slash-ac {
|
|
557
614
|
position: absolute;
|
|
558
615
|
left: 0;
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
height:
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
616
|
+
right: 0;
|
|
617
|
+
bottom: calc(100% + 6px);
|
|
618
|
+
max-height: 220px;
|
|
619
|
+
overflow: auto;
|
|
620
|
+
margin: 0;
|
|
621
|
+
padding: 4px;
|
|
622
|
+
list-style: none;
|
|
623
|
+
background: var(--bg-surface);
|
|
624
|
+
border: 1px solid var(--border-default);
|
|
625
|
+
border-radius: 10px;
|
|
626
|
+
box-shadow: var(--shadow-md, 0 8px 24px rgba(15, 23, 42, 0.12));
|
|
627
|
+
z-index: 40;
|
|
628
|
+
}
|
|
629
|
+
.slash-ac li {
|
|
630
|
+
display: flex;
|
|
631
|
+
align-items: baseline;
|
|
632
|
+
gap: 10px;
|
|
633
|
+
padding: 7px 10px;
|
|
634
|
+
border-radius: 7px;
|
|
635
|
+
cursor: pointer;
|
|
636
|
+
font-size: 12.5px;
|
|
637
|
+
}
|
|
638
|
+
.slash-ac li:hover,
|
|
639
|
+
.slash-ac li.active {
|
|
640
|
+
background: var(--brand-primary-light);
|
|
641
|
+
}
|
|
642
|
+
.slash-ac code {
|
|
643
|
+
font-family: var(--font-mono);
|
|
644
|
+
font-weight: 600;
|
|
645
|
+
color: var(--brand-primary);
|
|
646
|
+
white-space: nowrap;
|
|
647
|
+
}
|
|
648
|
+
.slash-ac span {
|
|
649
|
+
color: var(--text-muted);
|
|
650
|
+
font-size: 11.5px;
|
|
651
|
+
overflow: hidden;
|
|
652
|
+
text-overflow: ellipsis;
|
|
653
|
+
white-space: nowrap;
|
|
654
|
+
}
|
|
655
|
+
.button:disabled {
|
|
656
|
+
opacity: 0.65;
|
|
657
|
+
cursor: not-allowed;
|
|
658
|
+
transform: none;
|
|
565
659
|
}
|
|
566
660
|
@media (max-width: 960px) {
|
|
567
661
|
.help-grid, .help-cmd-list { grid-template-columns: 1fr; }
|
|
@@ -1090,6 +1184,26 @@
|
|
|
1090
1184
|
text-align: center;
|
|
1091
1185
|
}
|
|
1092
1186
|
.hidden { display: none !important; }
|
|
1187
|
+
.telemetry-notice {
|
|
1188
|
+
display: flex;
|
|
1189
|
+
align-items: flex-start;
|
|
1190
|
+
justify-content: space-between;
|
|
1191
|
+
gap: 16px;
|
|
1192
|
+
padding: 10px 20px;
|
|
1193
|
+
background: var(--brand-primary-light);
|
|
1194
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
1195
|
+
font-size: 13px;
|
|
1196
|
+
color: var(--text-main);
|
|
1197
|
+
}
|
|
1198
|
+
.telemetry-notice.hidden { display: none !important; }
|
|
1199
|
+
.telemetry-notice p {
|
|
1200
|
+
margin: 0;
|
|
1201
|
+
color: var(--text-muted);
|
|
1202
|
+
max-width: 72ch;
|
|
1203
|
+
line-height: 1.5;
|
|
1204
|
+
}
|
|
1205
|
+
.telemetry-notice strong { color: var(--text-main); }
|
|
1206
|
+
.telemetry-notice .toolbar { display: flex; gap: 8px; align-items: center; flex-shrink: 0; }
|
|
1093
1207
|
|
|
1094
1208
|
/* Pipeline Stepper */
|
|
1095
1209
|
.pipeline-stepper {
|
|
@@ -1814,30 +1928,20 @@
|
|
|
1814
1928
|
</div>
|
|
1815
1929
|
</div>
|
|
1816
1930
|
<div class="topbar-meta">
|
|
1817
|
-
<div class="topbar-cmds">
|
|
1818
|
-
<button id="cmd-init-btn" class="topbar-btn primary" title="初始化或配置社科研究工作区 (/init)">
|
|
1819
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M2 12h20"/></svg>
|
|
1820
|
-
/init 项目
|
|
1821
|
-
</button>
|
|
1822
|
-
<button id="cmd-plan-btn" class="topbar-btn" title="查看或确认统计分析方案 (/plan)">
|
|
1823
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
|
|
1824
|
-
/plan 方案
|
|
1825
|
-
</button>
|
|
1826
|
-
<button id="cmd-verify-btn" class="topbar-btn" title="AI 核查清单;人审在此核实 (/crosscheck)">
|
|
1827
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
|
1828
|
-
/crosscheck
|
|
1829
|
-
</button>
|
|
1830
|
-
</div>
|
|
1831
1931
|
<a href="https://github.com/Exekiel179/psyclaw" target="_blank" rel="noopener noreferrer" class="github-link" title="前往 PsyClaw GitHub 仓库">
|
|
1832
1932
|
<svg viewBox="0 0 24 24"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"/></svg>
|
|
1833
1933
|
<span>GitHub</span>
|
|
1834
1934
|
</a>
|
|
1835
|
-
<
|
|
1935
|
+
<button type="button" id="provider-status-badge" class="local-badge warn" title="查看模型与 Provider 配置">
|
|
1836
1936
|
<span class="pulse-dot"></span>
|
|
1837
|
-
|
|
1838
|
-
</
|
|
1937
|
+
<span id="provider-status-label">检测模型配置…</span>
|
|
1938
|
+
</button>
|
|
1839
1939
|
</div>
|
|
1840
1940
|
</header>
|
|
1941
|
+
<div id="telemetry-notice" class="telemetry-notice hidden" role="status">
|
|
1942
|
+
<p><strong>匿名产品遥测默认开启。</strong> 用于改进 PsyClaw 的粗粒度使用与错误信息;不含研究正文、论文或个人身份。可随时关闭。</p>
|
|
1943
|
+
<div class="toolbar"><button type="button" class="button" id="telemetry-ack">知道了</button><button type="button" class="button" id="telemetry-off">关闭遥测</button></div>
|
|
1944
|
+
</div>
|
|
1841
1945
|
|
|
1842
1946
|
<!-- 主体工作区 Layout -->
|
|
1843
1947
|
<div class="layout nav-collapsed">
|
|
@@ -1979,8 +2083,9 @@
|
|
|
1979
2083
|
|
|
1980
2084
|
<div class="console-input-wrap">
|
|
1981
2085
|
<div class="console-input-container">
|
|
1982
|
-
<div class="console-input-box">
|
|
2086
|
+
<div class="console-input-box" style="position:relative">
|
|
1983
2087
|
<textarea id="console-input" class="console-textarea" placeholder="向智能体提问或输入指令(Enter 发送)…" rows="1"></textarea>
|
|
2088
|
+
<ul id="slash-ac" class="slash-ac hidden" role="listbox" aria-label="斜杠命令"></ul>
|
|
1984
2089
|
<button id="console-send-btn" class="console-send-btn" title="发送消息 (Enter)">
|
|
1985
2090
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
|
1986
2091
|
</button>
|
|
@@ -2041,6 +2146,7 @@
|
|
|
2041
2146
|
<div class="segmented" aria-label="能力类型">
|
|
2042
2147
|
<button class="active" data-eco-kind="all">全部类型</button>
|
|
2043
2148
|
<button data-eco-kind="skill">Skill</button>
|
|
2149
|
+
<button data-eco-kind="external">外部工具</button>
|
|
2044
2150
|
<button data-eco-kind="plugin">Plugin</button>
|
|
2045
2151
|
<button data-eco-kind="mcp">MCP</button>
|
|
2046
2152
|
<button data-eco-kind="agent">Agent</button>
|
|
@@ -2122,7 +2228,6 @@
|
|
|
2122
2228
|
<label for="provider-key">API Key(留空不覆盖已有凭据)</label>
|
|
2123
2229
|
<input id="provider-key" type="password" autocomplete="off" placeholder="输入当前 Provider 的专属 Key">
|
|
2124
2230
|
</div>
|
|
2125
|
-
<p class="cost-note" style="margin:0">凭据委托 Pi 官方加密存储,不会在日志中明文打印。</p>
|
|
2126
2231
|
<button id="configure-provider" class="button" style="margin-top:4px">
|
|
2127
2232
|
保存当前 Provider 配置
|
|
2128
2233
|
</button>
|
|
@@ -2154,7 +2259,7 @@
|
|
|
2154
2259
|
<div class="help-hero">
|
|
2155
2260
|
<div>
|
|
2156
2261
|
<h1 id="help-title">使用速览</h1>
|
|
2157
|
-
<p id="help-subtitle">三种模式 ·
|
|
2262
|
+
<p id="help-subtitle">三种模式 · 白皮书全文 · 内置学术工作流</p>
|
|
2158
2263
|
</div>
|
|
2159
2264
|
<div id="help-thinking" class="help-kbd">加载中…</div>
|
|
2160
2265
|
</div>
|
|
@@ -2168,8 +2273,8 @@
|
|
|
2168
2273
|
<div id="help-commands" class="help-commands"></div>
|
|
2169
2274
|
</div>
|
|
2170
2275
|
<div class="help-block">
|
|
2171
|
-
<h2
|
|
2172
|
-
<
|
|
2276
|
+
<h2>使用白皮书</h2>
|
|
2277
|
+
<div id="help-whitepaper" class="help-whitepaper">正在加载白皮书…</div>
|
|
2173
2278
|
</div>
|
|
2174
2279
|
</section>
|
|
2175
2280
|
|
|
@@ -2177,7 +2282,6 @@
|
|
|
2177
2282
|
<div class="page-head">
|
|
2178
2283
|
<div>
|
|
2179
2284
|
<h1>交叉核验清单</h1>
|
|
2180
|
-
<p>AI 用 <code>/crosscheck</code> 核查字段;<strong>analysis / academic 收尾必须在此点「核实」</strong>(或终端 <code>/crosscheck <id> human</code>)。AI 已核与跳过都不算通过。</p>
|
|
2181
2285
|
</div>
|
|
2182
2286
|
<div style="display:flex;gap:8px">
|
|
2183
2287
|
<button id="refresh-checklist" class="button">刷新</button>
|
|
@@ -2219,42 +2323,6 @@
|
|
|
2219
2323
|
</div>
|
|
2220
2324
|
</div>
|
|
2221
2325
|
|
|
2222
|
-
<!-- /init 项目向导模态框 -->
|
|
2223
|
-
<div id="init-modal" class="modal-overlay hidden">
|
|
2224
|
-
<div class="modal-box">
|
|
2225
|
-
<div class="modal-head">
|
|
2226
|
-
<h3>🚀 初始化研究项目 (/init)</h3>
|
|
2227
|
-
<button id="init-modal-close" class="console-btn-icon" style="color:var(--text-muted)">
|
|
2228
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
2229
|
-
</button>
|
|
2230
|
-
</div>
|
|
2231
|
-
<div class="modal-body">
|
|
2232
|
-
<div class="field">
|
|
2233
|
-
<label for="init-paradigm">研究范式 (Paradigm)</label>
|
|
2234
|
-
<select id="init-paradigm" class="select">
|
|
2235
|
-
<option value="survey-observational">survey-observational (问卷与实证观察研究)</option>
|
|
2236
|
-
<option value="experimental">experimental (随机受控实验 / 心理学实验室)</option>
|
|
2237
|
-
<option value="quasi-experimental">quasi-experimental (准实验 / 双重差分 / 断点回归)</option>
|
|
2238
|
-
<option value="qualitative-thematic">qualitative-thematic (质性扎根理论与主题分析)</option>
|
|
2239
|
-
<option value="meta-analysis">meta-analysis (元分析 / 效应量合并)</option>
|
|
2240
|
-
<option value="longitudinal-panel">longitudinal-panel (追踪面板数据)</option>
|
|
2241
|
-
<option value="mixed-methods">mixed-methods (混合研究方法)</option>
|
|
2242
|
-
<option value="policy-legal">policy-legal (政策制度与法学文本分析)</option>
|
|
2243
|
-
</select>
|
|
2244
|
-
</div>
|
|
2245
|
-
<div class="field">
|
|
2246
|
-
<label for="init-goal">研究问题与目标 (Research Goal)</label>
|
|
2247
|
-
<textarea id="init-goal" class="textarea" rows="3" placeholder="例如:探究社交媒体主动使用与大学生学业倦怠之间的中介效应机制..."></textarea>
|
|
2248
|
-
</div>
|
|
2249
|
-
<div id="init-preview-cmd" style="font-family:var(--font-mono);font-size:11.5px;background:var(--bg-surface-subtle);padding:8px 10px;border-radius:4px;border:1px solid var(--border-subtle);color:var(--text-secondary)">
|
|
2250
|
-
/init --paradigm survey-observational
|
|
2251
|
-
</div>
|
|
2252
|
-
</div>
|
|
2253
|
-
<div class="modal-foot">
|
|
2254
|
-
<button id="init-cancel-btn" class="button">取消</button>
|
|
2255
|
-
<button id="init-submit-btn" class="button primary">提交并生成指令</button>
|
|
2256
|
-
</div>
|
|
2257
|
-
</div>
|
|
2258
2326
|
</div>
|
|
2259
2327
|
|
|
2260
2328
|
<script>
|
|
@@ -2263,11 +2331,13 @@ const state = {
|
|
|
2263
2331
|
files: [],
|
|
2264
2332
|
eco: [],
|
|
2265
2333
|
ecoKind: "all",
|
|
2334
|
+
installing: {},
|
|
2266
2335
|
models: [],
|
|
2267
2336
|
active: {},
|
|
2268
2337
|
usage: null,
|
|
2269
2338
|
currency: "cny",
|
|
2270
|
-
currentContent: ""
|
|
2339
|
+
currentContent: "",
|
|
2340
|
+
slashCommands: null
|
|
2271
2341
|
};
|
|
2272
2342
|
|
|
2273
2343
|
const $ = id => document.getElementById(id);
|
|
@@ -2309,7 +2379,7 @@ async function loadHelp() {
|
|
|
2309
2379
|
try {
|
|
2310
2380
|
const doc = await json("/api/help");
|
|
2311
2381
|
$("help-title").textContent = doc.title || "使用速览";
|
|
2312
|
-
$("help-subtitle").textContent = doc.subtitle || "";
|
|
2382
|
+
$("help-subtitle").textContent = doc.subtitle || "三种模式 · 白皮书全文 · 内置学术工作流";
|
|
2313
2383
|
$("help-thinking").textContent = doc.thinking || "";
|
|
2314
2384
|
$("help-modes").innerHTML = (doc.modes || []).map(mode => `
|
|
2315
2385
|
<article class="help-mode-card">
|
|
@@ -2335,16 +2405,27 @@ async function loadHelp() {
|
|
|
2335
2405
|
</div>
|
|
2336
2406
|
</div>
|
|
2337
2407
|
`).join("");
|
|
2338
|
-
|
|
2408
|
+
if (Array.isArray(doc.commands)) {
|
|
2409
|
+
state.slashCommands = doc.commands.map(row => ({
|
|
2410
|
+
cmd: String(row.cmd || "").replace(/\s*\[.*$/, "").trim() || String(row.cmd || ""),
|
|
2411
|
+
blurb: row.blurb || "",
|
|
2412
|
+
})).filter(row => row.cmd.startsWith("/"));
|
|
2413
|
+
}
|
|
2339
2414
|
} catch (error) {
|
|
2340
2415
|
$("help-thinking").textContent = `加载失败: ${error.message}`;
|
|
2341
2416
|
}
|
|
2417
|
+
try {
|
|
2418
|
+
const paper = await json("/api/docs/whitepaper");
|
|
2419
|
+
$("help-whitepaper").innerHTML = renderMarkdown(paper.markdown || "");
|
|
2420
|
+
} catch (error) {
|
|
2421
|
+
$("help-whitepaper").innerHTML = `<p style="color:var(--bad-text)">白皮书加载失败:${esc(error.message)}</p>`;
|
|
2422
|
+
}
|
|
2342
2423
|
}
|
|
2343
2424
|
|
|
2344
2425
|
async function loadChecklist() {
|
|
2345
2426
|
const body = $("checklist-body");
|
|
2346
2427
|
if (!body) return;
|
|
2347
|
-
body.innerHTML = `<tr><td colspan="6" class="empty">正在读取核对清单...</td></tr>`;
|
|
2428
|
+
body.innerHTML = `<tr><td colspan="6"><div class="checklist-empty">正在读取核对清单...</div></td></tr>`;
|
|
2348
2429
|
try {
|
|
2349
2430
|
const data = await json("/api/crosscheck");
|
|
2350
2431
|
const items = data.checklist?.items || [];
|
|
@@ -2362,7 +2443,7 @@ async function loadChecklist() {
|
|
|
2362
2443
|
<button class="button" data-check-id="${esc(item.id)}" data-check-status="skipped">跳过</button>
|
|
2363
2444
|
</td>
|
|
2364
2445
|
</tr>`;
|
|
2365
|
-
}).join("") || `<tr><td colspan="6" class="empty">清单为空</td></tr>`;
|
|
2446
|
+
}).join("") || `<tr><td colspan="6"><div class="checklist-empty"><strong>清单为空</strong><span>暂无交叉核验项;可在对话中发起 /crosscheck</span></div></td></tr>`;
|
|
2366
2447
|
body.querySelectorAll("[data-check-id]").forEach(btn => {
|
|
2367
2448
|
btn.addEventListener("click", async () => {
|
|
2368
2449
|
await json("/api/crosscheck", {
|
|
@@ -2374,7 +2455,7 @@ async function loadChecklist() {
|
|
|
2374
2455
|
});
|
|
2375
2456
|
});
|
|
2376
2457
|
} catch (error) {
|
|
2377
|
-
body.innerHTML = `<tr><td colspan="6" class="empty">加载失败: ${esc(error.message)}</td></tr>`;
|
|
2458
|
+
body.innerHTML = `<tr><td colspan="6"><div class="checklist-empty" style="color:var(--bad-text)">加载失败: ${esc(error.message)}</div></td></tr>`;
|
|
2378
2459
|
}
|
|
2379
2460
|
}
|
|
2380
2461
|
|
|
@@ -2647,20 +2728,42 @@ async function loadEcosystem() {
|
|
|
2647
2728
|
}
|
|
2648
2729
|
}
|
|
2649
2730
|
|
|
2731
|
+
function itemPrereqs(item) {
|
|
2732
|
+
const a = Array.isArray(item.prerequisites) ? item.prerequisites : [];
|
|
2733
|
+
const b = Array.isArray(item.requiresExternalTools) ? item.requiresExternalTools : [];
|
|
2734
|
+
return [...new Set([...a, ...b].map(value => String(value)).filter(Boolean))];
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
function kindLabel(kind) {
|
|
2738
|
+
if (kind === "core") return "Core Skill";
|
|
2739
|
+
if (kind === "local") return "用户 Skill";
|
|
2740
|
+
if (kind === "agent") return "Subagent";
|
|
2741
|
+
if (kind === "external") return "外部工具";
|
|
2742
|
+
if (kind === "skill") return "Skill";
|
|
2743
|
+
if (kind === "plugin") return "Plugin";
|
|
2744
|
+
if (kind === "mcp") return "MCP";
|
|
2745
|
+
return String(kind || "").toUpperCase();
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2650
2748
|
function renderEcosystem() {
|
|
2651
2749
|
const query = $("eco-search").value.trim().toLowerCase();
|
|
2652
2750
|
const sourceKind = $("eco-kind").value;
|
|
2653
2751
|
const status = $("eco-status").value;
|
|
2654
2752
|
const primaryKind = state.ecoKind;
|
|
2655
|
-
const primaryMatches = item => primaryKind === "all"
|
|
2753
|
+
const primaryMatches = item => primaryKind === "all"
|
|
2754
|
+
|| (primaryKind === "skill" ? (item.kind === "skill" || item.kind === "core" || item.kind === "local" || item.kind === "external") : item.kind === primaryKind);
|
|
2656
2755
|
const items = state.eco.filter(item =>
|
|
2657
2756
|
primaryMatches(item) &&
|
|
2658
2757
|
(sourceKind === "all" || item.kind === sourceKind) &&
|
|
2659
2758
|
(status === "all" || (status === "enabled" ? item.enabled : !item.enabled)) &&
|
|
2660
|
-
`${item.name} ${item.description || ""}`.toLowerCase().includes(query)
|
|
2759
|
+
`${item.name} ${item.description || ""} ${itemPrereqs(item).join(" ")}`.toLowerCase().includes(query)
|
|
2661
2760
|
);
|
|
2662
2761
|
|
|
2663
|
-
$("ecosystem-body").innerHTML = items.map(item =>
|
|
2762
|
+
$("ecosystem-body").innerHTML = items.map(item => {
|
|
2763
|
+
const prereqs = itemPrereqs(item);
|
|
2764
|
+
const busy = Boolean(state.installing[item.id]);
|
|
2765
|
+
const installLabel = busy ? "正在安装…" : item.kind === "plugin" && item.installed ? "重新安装" : item.kind === "core" ? "启用" : "安装";
|
|
2766
|
+
return `
|
|
2664
2767
|
<tr>
|
|
2665
2768
|
<td>
|
|
2666
2769
|
<div class="name">${esc(item.name)}</div>
|
|
@@ -2669,10 +2772,11 @@ function renderEcosystem() {
|
|
|
2669
2772
|
${item.kind === "plugin" && item.scope ? ` · ${item.scope === "project" ? "当前项目" : "所有项目"}${item.filtered ? " · 已限定资源" : ""}` : ""}
|
|
2670
2773
|
${item.kind === "agent" && item.slashCommand ? ` · <code style="font-family:var(--font-mono);font-size:11px">${esc(item.slashCommand)}</code>` : ""}
|
|
2671
2774
|
</div>
|
|
2775
|
+
${prereqs.length ? `<div class="subtle" style="margin-top:4px;color:var(--text-muted)">前置依赖:${prereqs.map(id => `<code style="font-family:var(--font-mono);font-size:11px">${esc(id)}</code>`).join("、")}</div>` : ""}
|
|
2672
2776
|
</td>
|
|
2673
2777
|
<td>
|
|
2674
2778
|
<span class="tag" style="background:#f1f5f9;font-weight:600">
|
|
2675
|
-
${esc(item.kind
|
|
2779
|
+
${esc(kindLabel(item.kind))}
|
|
2676
2780
|
</span>
|
|
2677
2781
|
</td>
|
|
2678
2782
|
<td>
|
|
@@ -2682,21 +2786,22 @@ function renderEcosystem() {
|
|
|
2682
2786
|
</td>
|
|
2683
2787
|
<td>
|
|
2684
2788
|
<span class="tag ${item.enabled ? "ok" : item.configured ? "warn" : ""}">
|
|
2685
|
-
${item.locked ? "内置可用" : item.enabled ? (item.kind === "plugin" ? "已安装" : "已启用") : item.kind === "plugin" && item.configured ? "配置存在,文件缺失" : item.kind === "external" ? "可安装外部工具" : "未启用"}
|
|
2789
|
+
${busy ? "正在安装…" : item.locked ? "内置可用" : item.enabled ? (item.kind === "plugin" ? "已安装" : "已启用") : item.kind === "plugin" && item.configured ? "配置存在,文件缺失" : item.kind === "external" ? "可安装外部工具" : "未启用"}
|
|
2686
2790
|
</span>
|
|
2687
2791
|
</td>
|
|
2688
2792
|
<td style="text-align:right">
|
|
2689
2793
|
${item.locked || item.kind === "agent" ? "—" :
|
|
2690
2794
|
item.kind === "plugin" && item.local ? "—" :
|
|
2691
|
-
item.kind === "plugin" ? `<button class="button small ${item.installed ? "" : "primary"}" onclick="installCapability('plugin','${esc(item.id)}')">${
|
|
2692
|
-
item.kind === "external" ? `<button class="button small primary" onclick="installCapability('external','${esc(item.id)}')"
|
|
2795
|
+
item.kind === "plugin" ? `<button class="button small ${item.installed ? "" : "primary"}" ${busy ? "disabled" : ""} onclick="installCapability('plugin','${esc(item.id)}')">${installLabel}</button>` :
|
|
2796
|
+
item.kind === "external" ? `<button class="button small primary" ${busy ? "disabled" : ""} onclick="installCapability('external','${esc(item.id)}')">${installLabel}</button>` :
|
|
2693
2797
|
item.enabled ? `<button class="button small" onclick="toggleCapability('${item.kind}','${esc(item.id)}',false)">停用</button>` :
|
|
2694
2798
|
item.kind === "local" ? `<button class="button small primary" onclick="toggleCapability('local','${esc(item.id)}',true)">启用</button>` :
|
|
2695
|
-
`<button class="button small primary" onclick="installCapability('${item.kind}','${esc(item.id)}')">${
|
|
2799
|
+
`<button class="button small primary" ${busy ? "disabled" : ""} onclick="installCapability('${item.kind}','${esc(item.id)}')">${installLabel}</button>`
|
|
2696
2800
|
}
|
|
2697
2801
|
</td>
|
|
2698
2802
|
</tr>
|
|
2699
|
-
|
|
2803
|
+
`;
|
|
2804
|
+
}).join("") || '<tr><td colspan="5" class="empty" style="min-height:100px">未找到匹配的能力项</td></tr>';
|
|
2700
2805
|
}
|
|
2701
2806
|
|
|
2702
2807
|
async function toggleCapability(kind, id, enabled) {
|
|
@@ -2717,17 +2822,53 @@ async function installCapability(kind, id) {
|
|
|
2717
2822
|
await toggleCapability(kind, id, true);
|
|
2718
2823
|
return;
|
|
2719
2824
|
}
|
|
2825
|
+
if (state.installing[id]) return;
|
|
2826
|
+
const item = state.eco.find(entry => entry.id === id && (kind === "skill" ? entry.kind === "skill" : entry.kind === kind));
|
|
2827
|
+
const prereqs = kind === "skill" ? itemPrereqs(item || {}) : [];
|
|
2720
2828
|
const scope = kind === "skill" || kind === "plugin"
|
|
2721
2829
|
? (confirm("安装到系统全局还是当前项目?\n确定:全局所有项目可用;取消:仅当前项目。") ? "user" : "project")
|
|
2722
2830
|
: undefined;
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2831
|
+
let confirmText = `确认安装「${item?.name || id}」?`;
|
|
2832
|
+
if (kind === "external") {
|
|
2833
|
+
confirmText += "\n\n系统将按官方可信来源安装;任务会交给当前 PsyClaw 对话中的模型执行。请保持终端会话打开。";
|
|
2834
|
+
} else if (kind === "mcp" || kind === "skill") {
|
|
2835
|
+
if (prereqs.length) {
|
|
2836
|
+
confirmText += `\n\n前置依赖也将一并排队安装:${prereqs.join("、")}。\n安装任务会交给当前 PsyClaw 对话中的模型执行;请保持终端会话打开,完成后执行 /reload。`;
|
|
2837
|
+
} else {
|
|
2838
|
+
confirmText += "\n\n安装任务会交给当前 PsyClaw 对话中的模型执行;请保持终端会话打开,完成后执行 /reload。";
|
|
2839
|
+
}
|
|
2840
|
+
} else {
|
|
2841
|
+
confirmText += "\n\n安装完成后需要执行 /reload 生效。";
|
|
2842
|
+
}
|
|
2843
|
+
if (!confirm(confirmText)) return;
|
|
2844
|
+
|
|
2845
|
+
state.installing[id] = true;
|
|
2846
|
+
for (const prereqId of prereqs) state.installing[prereqId] = true;
|
|
2847
|
+
renderEcosystem();
|
|
2848
|
+
const messages = [];
|
|
2849
|
+
try {
|
|
2850
|
+
for (const prereqId of prereqs) {
|
|
2851
|
+
const result = await json("/api/install/execute", {
|
|
2852
|
+
method: "POST",
|
|
2853
|
+
headers: { "content-type": "application/json" },
|
|
2854
|
+
body: JSON.stringify({ kind: "external", id: prereqId, approved: true, actor: "researcher" })
|
|
2855
|
+
});
|
|
2856
|
+
messages.push(result.message || `前置 ${prereqId} 已提交`);
|
|
2857
|
+
}
|
|
2858
|
+
const result = await json("/api/install/execute", {
|
|
2859
|
+
method: "POST",
|
|
2860
|
+
headers: { "content-type": "application/json" },
|
|
2861
|
+
body: JSON.stringify({ kind, id, scope, approved: true, actor: "researcher" })
|
|
2862
|
+
});
|
|
2863
|
+
messages.push(result.message || (result.ok ? "安装任务已提交。" : "安装未能完成。"));
|
|
2864
|
+
alert(messages.join("\n\n") || "安装任务已提交。");
|
|
2865
|
+
} catch (error) {
|
|
2866
|
+
alert(`安装失败:${error && error.message ? error.message : String(error)}`);
|
|
2867
|
+
} finally {
|
|
2868
|
+
delete state.installing[id];
|
|
2869
|
+
for (const prereqId of prereqs) delete state.installing[prereqId];
|
|
2870
|
+
await loadEcosystem();
|
|
2871
|
+
}
|
|
2731
2872
|
}
|
|
2732
2873
|
|
|
2733
2874
|
[$("eco-search"), $("eco-kind"), $("eco-status")].forEach(control => {
|
|
@@ -2774,11 +2915,47 @@ async function loadProvider() {
|
|
|
2774
2915
|
}
|
|
2775
2916
|
updateModels();
|
|
2776
2917
|
renderCost();
|
|
2918
|
+
updateProviderStatusBadge(active, state.models);
|
|
2777
2919
|
} catch (error) {
|
|
2778
2920
|
$("provider-feedback").textContent = `加载配置失败: ${error.message}`;
|
|
2779
2921
|
}
|
|
2780
2922
|
}
|
|
2781
2923
|
|
|
2924
|
+
function updateProviderStatusBadge(active, models) {
|
|
2925
|
+
const badge = $("provider-status-badge");
|
|
2926
|
+
const label = $("provider-status-label");
|
|
2927
|
+
if (!badge || !label) return;
|
|
2928
|
+
const provider = active?.provider;
|
|
2929
|
+
const model = active?.model;
|
|
2930
|
+
const providerConfigured = Boolean(provider) && (models || []).some(entry =>
|
|
2931
|
+
entry.provider === provider && entry.configured
|
|
2932
|
+
);
|
|
2933
|
+
const ready = Boolean(provider && model && providerConfigured);
|
|
2934
|
+
badge.classList.toggle("warn", !ready);
|
|
2935
|
+
label.textContent = ready ? "模型已配置" : "未配置模型";
|
|
2936
|
+
badge.title = ready
|
|
2937
|
+
? `当前默认:${provider}/${model}(点击打开模型与成本)`
|
|
2938
|
+
: "尚未配置可用模型或凭据(点击打开模型与成本)";
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
async function refreshProviderStatus() {
|
|
2942
|
+
try {
|
|
2943
|
+
const [catalog, active] = await Promise.all([
|
|
2944
|
+
json("/api/catalog"),
|
|
2945
|
+
json("/api/active-provider"),
|
|
2946
|
+
]);
|
|
2947
|
+
state.models = catalog.models || state.models;
|
|
2948
|
+
state.active = active || {};
|
|
2949
|
+
updateProviderStatusBadge(active, state.models);
|
|
2950
|
+
} catch {
|
|
2951
|
+
updateProviderStatusBadge({}, []);
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
$("provider-status-badge")?.addEventListener("click", () => setView("provider"));
|
|
2956
|
+
refreshProviderStatus();
|
|
2957
|
+
setInterval(refreshProviderStatus, 30_000);
|
|
2958
|
+
|
|
2782
2959
|
function currentModel() {
|
|
2783
2960
|
return providerModels($("provider-select").value).find(
|
|
2784
2961
|
model => (model.version || model.id.split("/").slice(1).join("/")) === $("model-select").value
|
|
@@ -2797,6 +2974,7 @@ $("save-provider").addEventListener("click", async () => {
|
|
|
2797
2974
|
body: JSON.stringify({ provider: $("provider-select").value, model: $("model-select").value })
|
|
2798
2975
|
});
|
|
2799
2976
|
feedback.textContent = "✓ 默认模型设置已保存,重启会话后生效。";
|
|
2977
|
+
await refreshProviderStatus();
|
|
2800
2978
|
setTimeout(() => { feedback.textContent = ""; }, 3000);
|
|
2801
2979
|
} catch (error) {
|
|
2802
2980
|
feedback.textContent = `保存失败: ${error.message}`;
|
|
@@ -3213,8 +3391,6 @@ function autoResizeConsoleInput() {
|
|
|
3213
3391
|
consoleInput.style.height = `${nextHeight}px`;
|
|
3214
3392
|
}
|
|
3215
3393
|
|
|
3216
|
-
consoleInput.addEventListener("input", autoResizeConsoleInput);
|
|
3217
|
-
|
|
3218
3394
|
// 代码块全局点击复制事件代理
|
|
3219
3395
|
document.addEventListener("click", async (e) => {
|
|
3220
3396
|
const btn = e.target.closest(".code-copy-btn");
|
|
@@ -3267,13 +3443,155 @@ async function sendConsoleMessage(msg) {
|
|
|
3267
3443
|
connectAssistantStream();
|
|
3268
3444
|
|
|
3269
3445
|
$("console-send-btn").addEventListener("click", () => sendConsoleMessage());
|
|
3446
|
+
|
|
3447
|
+
const DEFAULT_SLASH_COMMANDS = [
|
|
3448
|
+
{ cmd: "/init", blurb: "初始化项目" },
|
|
3449
|
+
{ cmd: "/plan", blurb: "分析方案" },
|
|
3450
|
+
{ cmd: "/crosscheck", blurb: "过程性核对" },
|
|
3451
|
+
{ cmd: "/verify", blurb: "实质性验证" },
|
|
3452
|
+
{ cmd: "/handoff", blurb: "写入交接" },
|
|
3453
|
+
{ cmd: "/provider", blurb: "模型 Provider" },
|
|
3454
|
+
{ cmd: "/mcp", blurb: "MCP 管理" },
|
|
3455
|
+
{ cmd: "/skill", blurb: "Skill 管理" },
|
|
3456
|
+
{ cmd: "/plugin", blurb: "Plugin 管理" },
|
|
3457
|
+
{ cmd: "/panel", blurb: "打开工作台" },
|
|
3458
|
+
{ cmd: "/reload", blurb: "热更新能力" },
|
|
3459
|
+
{ cmd: "/telemetry", blurb: "遥测偏好" },
|
|
3460
|
+
{ cmd: "/help", blurb: "使用速览" },
|
|
3461
|
+
{ cmd: "/review", blurb: "模拟审稿" },
|
|
3462
|
+
{ cmd: "/brainstorm", blurb: "头脑风暴" },
|
|
3463
|
+
{ cmd: "/grill", blurb: "学术压力测试" },
|
|
3464
|
+
{ cmd: "/ars", blurb: "学术流程" },
|
|
3465
|
+
];
|
|
3466
|
+
|
|
3467
|
+
const slashAc = {
|
|
3468
|
+
open: false,
|
|
3469
|
+
index: 0,
|
|
3470
|
+
items: [],
|
|
3471
|
+
el: $("slash-ac"),
|
|
3472
|
+
};
|
|
3473
|
+
|
|
3474
|
+
function slashCommandList() {
|
|
3475
|
+
return (state.slashCommands && state.slashCommands.length) ? state.slashCommands : DEFAULT_SLASH_COMMANDS;
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
function detectSlashQuery(value, caret) {
|
|
3479
|
+
const before = String(value || "").slice(0, caret ?? String(value || "").length);
|
|
3480
|
+
const match = before.match(/(?:^|\s)(\/[^\s]*)$/);
|
|
3481
|
+
if (!match) return null;
|
|
3482
|
+
return { query: match[1], start: before.length - match[1].length };
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
function closeSlashAc() {
|
|
3486
|
+
slashAc.open = false;
|
|
3487
|
+
slashAc.index = 0;
|
|
3488
|
+
slashAc.items = [];
|
|
3489
|
+
if (slashAc.el) {
|
|
3490
|
+
slashAc.el.classList.add("hidden");
|
|
3491
|
+
slashAc.el.innerHTML = "";
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
function renderSlashAc() {
|
|
3496
|
+
if (!slashAc.el) return;
|
|
3497
|
+
if (!slashAc.open || !slashAc.items.length) {
|
|
3498
|
+
closeSlashAc();
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
slashAc.el.classList.remove("hidden");
|
|
3502
|
+
slashAc.el.innerHTML = slashAc.items.map((item, index) => `
|
|
3503
|
+
<li class="${index === slashAc.index ? "active" : ""}" data-slash-index="${index}" role="option">
|
|
3504
|
+
<code>${esc(item.cmd)}</code><span>${esc(item.blurb || "")}</span>
|
|
3505
|
+
</li>
|
|
3506
|
+
`).join("");
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
function updateSlashAc() {
|
|
3510
|
+
if (!consoleInput) return;
|
|
3511
|
+
const detected = detectSlashQuery(consoleInput.value, consoleInput.selectionStart);
|
|
3512
|
+
if (!detected) {
|
|
3513
|
+
closeSlashAc();
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
const q = detected.query.toLowerCase();
|
|
3517
|
+
slashAc.items = slashCommandList().filter(item => item.cmd.toLowerCase().startsWith(q)).slice(0, 12);
|
|
3518
|
+
slashAc.open = slashAc.items.length > 0;
|
|
3519
|
+
slashAc.index = Math.min(slashAc.index, Math.max(slashAc.items.length - 1, 0));
|
|
3520
|
+
renderSlashAc();
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
function applySlashAc(index = slashAc.index) {
|
|
3524
|
+
const item = slashAc.items[index];
|
|
3525
|
+
if (!item || !consoleInput) return;
|
|
3526
|
+
const detected = detectSlashQuery(consoleInput.value, consoleInput.selectionStart);
|
|
3527
|
+
if (!detected) return;
|
|
3528
|
+
const before = consoleInput.value.slice(0, detected.start);
|
|
3529
|
+
const after = consoleInput.value.slice(consoleInput.selectionStart);
|
|
3530
|
+
consoleInput.value = `${before}${item.cmd}${after.startsWith(" ") ? after : ` ${after}`}`;
|
|
3531
|
+
const caret = before.length + item.cmd.length + 1;
|
|
3532
|
+
consoleInput.setSelectionRange(caret, caret);
|
|
3533
|
+
closeSlashAc();
|
|
3534
|
+
autoResizeConsoleInput();
|
|
3535
|
+
consoleInput.focus();
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
slashAc.el?.addEventListener("mousedown", (e) => {
|
|
3539
|
+
const li = e.target.closest("[data-slash-index]");
|
|
3540
|
+
if (!li) return;
|
|
3541
|
+
e.preventDefault();
|
|
3542
|
+
applySlashAc(Number(li.dataset.slashIndex));
|
|
3543
|
+
});
|
|
3544
|
+
|
|
3545
|
+
consoleInput.addEventListener("input", () => {
|
|
3546
|
+
autoResizeConsoleInput();
|
|
3547
|
+
updateSlashAc();
|
|
3548
|
+
});
|
|
3549
|
+
|
|
3270
3550
|
consoleInput.addEventListener("keydown", (e) => {
|
|
3551
|
+
if (slashAc.open) {
|
|
3552
|
+
if (e.key === "ArrowDown") {
|
|
3553
|
+
e.preventDefault();
|
|
3554
|
+
slashAc.index = (slashAc.index + 1) % slashAc.items.length;
|
|
3555
|
+
renderSlashAc();
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
if (e.key === "ArrowUp") {
|
|
3559
|
+
e.preventDefault();
|
|
3560
|
+
slashAc.index = (slashAc.index - 1 + slashAc.items.length) % slashAc.items.length;
|
|
3561
|
+
renderSlashAc();
|
|
3562
|
+
return;
|
|
3563
|
+
}
|
|
3564
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
3565
|
+
e.preventDefault();
|
|
3566
|
+
applySlashAc();
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
if (e.key === "Escape") {
|
|
3570
|
+
e.preventDefault();
|
|
3571
|
+
closeSlashAc();
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3574
|
+
if (e.key === "Tab") {
|
|
3575
|
+
e.preventDefault();
|
|
3576
|
+
applySlashAc();
|
|
3577
|
+
return;
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3271
3580
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
3272
3581
|
e.preventDefault();
|
|
3273
3582
|
sendConsoleMessage();
|
|
3274
3583
|
}
|
|
3275
3584
|
});
|
|
3276
3585
|
|
|
3586
|
+
json("/api/help").then(doc => {
|
|
3587
|
+
if (Array.isArray(doc.commands)) {
|
|
3588
|
+
state.slashCommands = doc.commands.map(row => ({
|
|
3589
|
+
cmd: String(row.cmd || "").replace(/\s*\[.*$/, "").trim() || String(row.cmd || ""),
|
|
3590
|
+
blurb: row.blurb || "",
|
|
3591
|
+
})).filter(row => row.cmd.startsWith("/"));
|
|
3592
|
+
}
|
|
3593
|
+
}).catch(() => {});
|
|
3594
|
+
|
|
3277
3595
|
document.querySelectorAll(".chip").forEach(chip => {
|
|
3278
3596
|
chip.addEventListener("click", () => {
|
|
3279
3597
|
const cmd = chip.dataset.cmd;
|
|
@@ -3340,61 +3658,14 @@ document.addEventListener("click", (e) => {
|
|
|
3340
3658
|
|
|
3341
3659
|
initWorkbenchCollapse();
|
|
3342
3660
|
|
|
3343
|
-
/* ---
|
|
3344
|
-
const initModal = $("init-modal");
|
|
3345
|
-
const initParadigm = $("init-paradigm");
|
|
3346
|
-
const initGoal = $("init-goal");
|
|
3347
|
-
const initPreviewCmd = $("init-preview-cmd");
|
|
3348
|
-
|
|
3349
|
-
function updateInitPreview() {
|
|
3350
|
-
const paradigm = initParadigm.value;
|
|
3351
|
-
const goal = initGoal.value.trim();
|
|
3352
|
-
initPreviewCmd.textContent = `/init --paradigm ${paradigm}${goal ? ` "${goal}"` : ""}`;
|
|
3353
|
-
}
|
|
3354
|
-
|
|
3355
|
-
initParadigm.addEventListener("change", updateInitPreview);
|
|
3356
|
-
initGoal.addEventListener("input", updateInitPreview);
|
|
3357
|
-
|
|
3358
|
-
$("cmd-init-btn").addEventListener("click", () => {
|
|
3359
|
-
updateInitPreview();
|
|
3360
|
-
initModal.classList.remove("hidden");
|
|
3361
|
-
});
|
|
3362
|
-
$("init-modal-close").addEventListener("click", () => initModal.classList.add("hidden"));
|
|
3363
|
-
$("init-cancel-btn").addEventListener("click", () => initModal.classList.add("hidden"));
|
|
3364
|
-
|
|
3365
|
-
$("init-submit-btn").addEventListener("click", async () => {
|
|
3366
|
-
const cmd = initPreviewCmd.textContent.trim();
|
|
3367
|
-
initModal.classList.add("hidden");
|
|
3368
|
-
openConsole();
|
|
3369
|
-
await sendConsoleMessage(cmd);
|
|
3370
|
-
loadProject();
|
|
3371
|
-
});
|
|
3372
|
-
|
|
3373
|
-
$("cmd-plan-btn").addEventListener("click", async () => {
|
|
3374
|
-
openConsole();
|
|
3375
|
-
await sendConsoleMessage("/plan status");
|
|
3376
|
-
});
|
|
3377
|
-
|
|
3378
|
-
$("cmd-verify-btn").addEventListener("click", async () => {
|
|
3379
|
-
setView("checklist");
|
|
3380
|
-
await loadChecklist();
|
|
3381
|
-
openConsole();
|
|
3382
|
-
await sendConsoleMessage("/crosscheck list");
|
|
3383
|
-
});
|
|
3384
|
-
|
|
3385
|
-
/* --- 加载项目状态并更新 Stepper 状态 --- */
|
|
3661
|
+
/* --- 项目状态(更新 Stepper) --- */
|
|
3386
3662
|
async function loadProjectStatus() {
|
|
3387
3663
|
try {
|
|
3388
3664
|
const status = await json("/api/project-status");
|
|
3389
|
-
if (
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
if (status.activePlan) {
|
|
3394
|
-
document.querySelectorAll(".step-item").forEach(item => {
|
|
3395
|
-
if (item.dataset.stage === "plan") item.classList.add("active");
|
|
3396
|
-
});
|
|
3397
|
-
}
|
|
3665
|
+
if (status.initialized && status.activePlan) {
|
|
3666
|
+
document.querySelectorAll(".step-item").forEach(item => {
|
|
3667
|
+
if (item.dataset.stage === "plan") item.classList.add("active");
|
|
3668
|
+
});
|
|
3398
3669
|
}
|
|
3399
3670
|
} catch { /* ignore */ }
|
|
3400
3671
|
}
|
|
@@ -3404,6 +3675,22 @@ loadProject().catch(error => {
|
|
|
3404
3675
|
});
|
|
3405
3676
|
loadProjectStatus();
|
|
3406
3677
|
|
|
3678
|
+
(function initTelemetryNotice(){
|
|
3679
|
+
const obs = window.__PSYCLAW_OBS__ || {};
|
|
3680
|
+
const banner = $("telemetry-notice");
|
|
3681
|
+
if (!banner) return;
|
|
3682
|
+
if (obs.showTelemetryNotice) banner.classList.remove("hidden");
|
|
3683
|
+
$("telemetry-ack")?.addEventListener("click", async () => {
|
|
3684
|
+
try { await json("/api/telemetry", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "ack" }) }); } catch {}
|
|
3685
|
+
banner.classList.add("hidden");
|
|
3686
|
+
});
|
|
3687
|
+
$("telemetry-off")?.addEventListener("click", async () => {
|
|
3688
|
+
try { await json("/api/telemetry", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "disable" }) }); } catch {}
|
|
3689
|
+
banner.classList.add("hidden");
|
|
3690
|
+
location.reload();
|
|
3691
|
+
});
|
|
3692
|
+
})();
|
|
3693
|
+
|
|
3407
3694
|
const initialView = decodeURIComponent((location.hash || "").replace(/^#/, "").trim());
|
|
3408
3695
|
const knownViews = new Set(["project", "ecosystem", "provider", "checklist", "chat", "help"]);
|
|
3409
3696
|
if (knownViews.has(initialView)) setView(initialView);
|