psyclaw 0.29.18 → 0.29.20
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 +4 -4
- package/dist/apps/panel/index.html +399 -154
- package/dist/src/adapters/pi/extension.js +12 -1
- package/dist/src/adapters/pi/extension.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 +2 -0
- package/dist/src/panel/server.js +120 -74
- 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/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 +4 -2
- package/scripts/rebrand-pi.mjs +74 -1
- package/skills/recommended/catalog.json +4 -2
- package/skills/recommended/mcp-catalog.json +13 -1
|
@@ -178,6 +178,22 @@
|
|
|
178
178
|
padding: 3px 9px;
|
|
179
179
|
border-radius: 999px;
|
|
180
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);
|
|
181
197
|
}
|
|
182
198
|
.pulse-dot {
|
|
183
199
|
width: 6px;
|
|
@@ -537,33 +553,109 @@
|
|
|
537
553
|
color: var(--text-secondary);
|
|
538
554
|
line-height: 1.4;
|
|
539
555
|
}
|
|
540
|
-
.help-
|
|
556
|
+
.help-whitepaper {
|
|
541
557
|
margin: 0;
|
|
542
|
-
padding:
|
|
543
|
-
list-style: none;
|
|
544
|
-
display: grid;
|
|
545
|
-
gap: 8px;
|
|
558
|
+
padding: 18px 20px 22px;
|
|
546
559
|
border-radius: 14px;
|
|
547
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;
|
|
548
576
|
background: var(--bg-surface-subtle);
|
|
577
|
+
padding: 0.1em 0.35em;
|
|
578
|
+
border-radius: 4px;
|
|
549
579
|
}
|
|
550
|
-
.help-
|
|
551
|
-
|
|
552
|
-
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);
|
|
553
593
|
color: var(--text-muted);
|
|
554
|
-
font-size: 12.5px;
|
|
555
|
-
line-height: 1.55;
|
|
556
594
|
}
|
|
557
|
-
.
|
|
558
|
-
|
|
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 {
|
|
559
614
|
position: absolute;
|
|
560
615
|
left: 0;
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
height:
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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;
|
|
567
659
|
}
|
|
568
660
|
@media (max-width: 960px) {
|
|
569
661
|
.help-grid, .help-cmd-list { grid-template-columns: 1fr; }
|
|
@@ -1836,28 +1928,14 @@
|
|
|
1836
1928
|
</div>
|
|
1837
1929
|
</div>
|
|
1838
1930
|
<div class="topbar-meta">
|
|
1839
|
-
<div class="topbar-cmds">
|
|
1840
|
-
<button id="cmd-init-btn" class="topbar-btn primary" title="初始化或配置社科研究工作区 (/init)">
|
|
1841
|
-
<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>
|
|
1842
|
-
/init 项目
|
|
1843
|
-
</button>
|
|
1844
|
-
<button id="cmd-plan-btn" class="topbar-btn" title="查看或确认统计分析方案 (/plan)">
|
|
1845
|
-
<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>
|
|
1846
|
-
/plan 方案
|
|
1847
|
-
</button>
|
|
1848
|
-
<button id="cmd-verify-btn" class="topbar-btn" title="AI 核查清单;人审在此核实 (/crosscheck)">
|
|
1849
|
-
<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>
|
|
1850
|
-
/crosscheck
|
|
1851
|
-
</button>
|
|
1852
|
-
</div>
|
|
1853
1931
|
<a href="https://github.com/Exekiel179/psyclaw" target="_blank" rel="noopener noreferrer" class="github-link" title="前往 PsyClaw GitHub 仓库">
|
|
1854
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>
|
|
1855
1933
|
<span>GitHub</span>
|
|
1856
1934
|
</a>
|
|
1857
|
-
<
|
|
1935
|
+
<button type="button" id="provider-status-badge" class="local-badge warn" title="查看模型与 Provider 配置">
|
|
1858
1936
|
<span class="pulse-dot"></span>
|
|
1859
|
-
|
|
1860
|
-
</
|
|
1937
|
+
<span id="provider-status-label">检测模型配置…</span>
|
|
1938
|
+
</button>
|
|
1861
1939
|
</div>
|
|
1862
1940
|
</header>
|
|
1863
1941
|
<div id="telemetry-notice" class="telemetry-notice hidden" role="status">
|
|
@@ -2005,8 +2083,9 @@
|
|
|
2005
2083
|
|
|
2006
2084
|
<div class="console-input-wrap">
|
|
2007
2085
|
<div class="console-input-container">
|
|
2008
|
-
<div class="console-input-box">
|
|
2086
|
+
<div class="console-input-box" style="position:relative">
|
|
2009
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>
|
|
2010
2089
|
<button id="console-send-btn" class="console-send-btn" title="发送消息 (Enter)">
|
|
2011
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>
|
|
2012
2091
|
</button>
|
|
@@ -2067,6 +2146,7 @@
|
|
|
2067
2146
|
<div class="segmented" aria-label="能力类型">
|
|
2068
2147
|
<button class="active" data-eco-kind="all">全部类型</button>
|
|
2069
2148
|
<button data-eco-kind="skill">Skill</button>
|
|
2149
|
+
<button data-eco-kind="external">外部工具</button>
|
|
2070
2150
|
<button data-eco-kind="plugin">Plugin</button>
|
|
2071
2151
|
<button data-eco-kind="mcp">MCP</button>
|
|
2072
2152
|
<button data-eco-kind="agent">Agent</button>
|
|
@@ -2148,7 +2228,6 @@
|
|
|
2148
2228
|
<label for="provider-key">API Key(留空不覆盖已有凭据)</label>
|
|
2149
2229
|
<input id="provider-key" type="password" autocomplete="off" placeholder="输入当前 Provider 的专属 Key">
|
|
2150
2230
|
</div>
|
|
2151
|
-
<p class="cost-note" style="margin:0">凭据委托 Pi 官方加密存储,不会在日志中明文打印。</p>
|
|
2152
2231
|
<button id="configure-provider" class="button" style="margin-top:4px">
|
|
2153
2232
|
保存当前 Provider 配置
|
|
2154
2233
|
</button>
|
|
@@ -2180,7 +2259,7 @@
|
|
|
2180
2259
|
<div class="help-hero">
|
|
2181
2260
|
<div>
|
|
2182
2261
|
<h1 id="help-title">使用速览</h1>
|
|
2183
|
-
<p id="help-subtitle">三种模式 ·
|
|
2262
|
+
<p id="help-subtitle">三种模式 · 白皮书全文 · 内置学术工作流</p>
|
|
2184
2263
|
</div>
|
|
2185
2264
|
<div id="help-thinking" class="help-kbd">加载中…</div>
|
|
2186
2265
|
</div>
|
|
@@ -2194,8 +2273,8 @@
|
|
|
2194
2273
|
<div id="help-commands" class="help-commands"></div>
|
|
2195
2274
|
</div>
|
|
2196
2275
|
<div class="help-block">
|
|
2197
|
-
<h2
|
|
2198
|
-
<
|
|
2276
|
+
<h2>使用白皮书</h2>
|
|
2277
|
+
<div id="help-whitepaper" class="help-whitepaper">正在加载白皮书…</div>
|
|
2199
2278
|
</div>
|
|
2200
2279
|
</section>
|
|
2201
2280
|
|
|
@@ -2203,7 +2282,6 @@
|
|
|
2203
2282
|
<div class="page-head">
|
|
2204
2283
|
<div>
|
|
2205
2284
|
<h1>交叉核验清单</h1>
|
|
2206
|
-
<p>AI 用 <code>/crosscheck</code> 核查字段;<strong>analysis / academic 收尾必须在此点「核实」</strong>(或终端 <code>/crosscheck <id> human</code>)。AI 已核与跳过都不算通过。</p>
|
|
2207
2285
|
</div>
|
|
2208
2286
|
<div style="display:flex;gap:8px">
|
|
2209
2287
|
<button id="refresh-checklist" class="button">刷新</button>
|
|
@@ -2245,42 +2323,6 @@
|
|
|
2245
2323
|
</div>
|
|
2246
2324
|
</div>
|
|
2247
2325
|
|
|
2248
|
-
<!-- /init 项目向导模态框 -->
|
|
2249
|
-
<div id="init-modal" class="modal-overlay hidden">
|
|
2250
|
-
<div class="modal-box">
|
|
2251
|
-
<div class="modal-head">
|
|
2252
|
-
<h3>🚀 初始化研究项目 (/init)</h3>
|
|
2253
|
-
<button id="init-modal-close" class="console-btn-icon" style="color:var(--text-muted)">
|
|
2254
|
-
<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>
|
|
2255
|
-
</button>
|
|
2256
|
-
</div>
|
|
2257
|
-
<div class="modal-body">
|
|
2258
|
-
<div class="field">
|
|
2259
|
-
<label for="init-paradigm">研究范式 (Paradigm)</label>
|
|
2260
|
-
<select id="init-paradigm" class="select">
|
|
2261
|
-
<option value="survey-observational">survey-observational (问卷与实证观察研究)</option>
|
|
2262
|
-
<option value="experimental">experimental (随机受控实验 / 心理学实验室)</option>
|
|
2263
|
-
<option value="quasi-experimental">quasi-experimental (准实验 / 双重差分 / 断点回归)</option>
|
|
2264
|
-
<option value="qualitative-thematic">qualitative-thematic (质性扎根理论与主题分析)</option>
|
|
2265
|
-
<option value="meta-analysis">meta-analysis (元分析 / 效应量合并)</option>
|
|
2266
|
-
<option value="longitudinal-panel">longitudinal-panel (追踪面板数据)</option>
|
|
2267
|
-
<option value="mixed-methods">mixed-methods (混合研究方法)</option>
|
|
2268
|
-
<option value="policy-legal">policy-legal (政策制度与法学文本分析)</option>
|
|
2269
|
-
</select>
|
|
2270
|
-
</div>
|
|
2271
|
-
<div class="field">
|
|
2272
|
-
<label for="init-goal">研究问题与目标 (Research Goal)</label>
|
|
2273
|
-
<textarea id="init-goal" class="textarea" rows="3" placeholder="例如:探究社交媒体主动使用与大学生学业倦怠之间的中介效应机制..."></textarea>
|
|
2274
|
-
</div>
|
|
2275
|
-
<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)">
|
|
2276
|
-
/init --paradigm survey-observational
|
|
2277
|
-
</div>
|
|
2278
|
-
</div>
|
|
2279
|
-
<div class="modal-foot">
|
|
2280
|
-
<button id="init-cancel-btn" class="button">取消</button>
|
|
2281
|
-
<button id="init-submit-btn" class="button primary">提交并生成指令</button>
|
|
2282
|
-
</div>
|
|
2283
|
-
</div>
|
|
2284
2326
|
</div>
|
|
2285
2327
|
|
|
2286
2328
|
<script>
|
|
@@ -2289,11 +2331,13 @@ const state = {
|
|
|
2289
2331
|
files: [],
|
|
2290
2332
|
eco: [],
|
|
2291
2333
|
ecoKind: "all",
|
|
2334
|
+
installing: {},
|
|
2292
2335
|
models: [],
|
|
2293
2336
|
active: {},
|
|
2294
2337
|
usage: null,
|
|
2295
2338
|
currency: "cny",
|
|
2296
|
-
currentContent: ""
|
|
2339
|
+
currentContent: "",
|
|
2340
|
+
slashCommands: null
|
|
2297
2341
|
};
|
|
2298
2342
|
|
|
2299
2343
|
const $ = id => document.getElementById(id);
|
|
@@ -2335,7 +2379,7 @@ async function loadHelp() {
|
|
|
2335
2379
|
try {
|
|
2336
2380
|
const doc = await json("/api/help");
|
|
2337
2381
|
$("help-title").textContent = doc.title || "使用速览";
|
|
2338
|
-
$("help-subtitle").textContent = doc.subtitle || "";
|
|
2382
|
+
$("help-subtitle").textContent = doc.subtitle || "三种模式 · 白皮书全文 · 内置学术工作流";
|
|
2339
2383
|
$("help-thinking").textContent = doc.thinking || "";
|
|
2340
2384
|
$("help-modes").innerHTML = (doc.modes || []).map(mode => `
|
|
2341
2385
|
<article class="help-mode-card">
|
|
@@ -2361,16 +2405,27 @@ async function loadHelp() {
|
|
|
2361
2405
|
</div>
|
|
2362
2406
|
</div>
|
|
2363
2407
|
`).join("");
|
|
2364
|
-
|
|
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
|
+
}
|
|
2365
2414
|
} catch (error) {
|
|
2366
2415
|
$("help-thinking").textContent = `加载失败: ${error.message}`;
|
|
2367
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
|
+
}
|
|
2368
2423
|
}
|
|
2369
2424
|
|
|
2370
2425
|
async function loadChecklist() {
|
|
2371
2426
|
const body = $("checklist-body");
|
|
2372
2427
|
if (!body) return;
|
|
2373
|
-
body.innerHTML = `<tr><td colspan="6" class="empty">正在读取核对清单...</td></tr>`;
|
|
2428
|
+
body.innerHTML = `<tr><td colspan="6"><div class="checklist-empty">正在读取核对清单...</div></td></tr>`;
|
|
2374
2429
|
try {
|
|
2375
2430
|
const data = await json("/api/crosscheck");
|
|
2376
2431
|
const items = data.checklist?.items || [];
|
|
@@ -2388,7 +2443,7 @@ async function loadChecklist() {
|
|
|
2388
2443
|
<button class="button" data-check-id="${esc(item.id)}" data-check-status="skipped">跳过</button>
|
|
2389
2444
|
</td>
|
|
2390
2445
|
</tr>`;
|
|
2391
|
-
}).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>`;
|
|
2392
2447
|
body.querySelectorAll("[data-check-id]").forEach(btn => {
|
|
2393
2448
|
btn.addEventListener("click", async () => {
|
|
2394
2449
|
await json("/api/crosscheck", {
|
|
@@ -2400,7 +2455,7 @@ async function loadChecklist() {
|
|
|
2400
2455
|
});
|
|
2401
2456
|
});
|
|
2402
2457
|
} catch (error) {
|
|
2403
|
-
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>`;
|
|
2404
2459
|
}
|
|
2405
2460
|
}
|
|
2406
2461
|
|
|
@@ -2673,20 +2728,42 @@ async function loadEcosystem() {
|
|
|
2673
2728
|
}
|
|
2674
2729
|
}
|
|
2675
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
|
+
|
|
2676
2748
|
function renderEcosystem() {
|
|
2677
2749
|
const query = $("eco-search").value.trim().toLowerCase();
|
|
2678
2750
|
const sourceKind = $("eco-kind").value;
|
|
2679
2751
|
const status = $("eco-status").value;
|
|
2680
2752
|
const primaryKind = state.ecoKind;
|
|
2681
|
-
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);
|
|
2682
2755
|
const items = state.eco.filter(item =>
|
|
2683
2756
|
primaryMatches(item) &&
|
|
2684
2757
|
(sourceKind === "all" || item.kind === sourceKind) &&
|
|
2685
2758
|
(status === "all" || (status === "enabled" ? item.enabled : !item.enabled)) &&
|
|
2686
|
-
`${item.name} ${item.description || ""}`.toLowerCase().includes(query)
|
|
2759
|
+
`${item.name} ${item.description || ""} ${itemPrereqs(item).join(" ")}`.toLowerCase().includes(query)
|
|
2687
2760
|
);
|
|
2688
2761
|
|
|
2689
|
-
$("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 `
|
|
2690
2767
|
<tr>
|
|
2691
2768
|
<td>
|
|
2692
2769
|
<div class="name">${esc(item.name)}</div>
|
|
@@ -2695,10 +2772,11 @@ function renderEcosystem() {
|
|
|
2695
2772
|
${item.kind === "plugin" && item.scope ? ` · ${item.scope === "project" ? "当前项目" : "所有项目"}${item.filtered ? " · 已限定资源" : ""}` : ""}
|
|
2696
2773
|
${item.kind === "agent" && item.slashCommand ? ` · <code style="font-family:var(--font-mono);font-size:11px">${esc(item.slashCommand)}</code>` : ""}
|
|
2697
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>` : ""}
|
|
2698
2776
|
</td>
|
|
2699
2777
|
<td>
|
|
2700
2778
|
<span class="tag" style="background:#f1f5f9;font-weight:600">
|
|
2701
|
-
${esc(item.kind
|
|
2779
|
+
${esc(kindLabel(item.kind))}
|
|
2702
2780
|
</span>
|
|
2703
2781
|
</td>
|
|
2704
2782
|
<td>
|
|
@@ -2708,21 +2786,22 @@ function renderEcosystem() {
|
|
|
2708
2786
|
</td>
|
|
2709
2787
|
<td>
|
|
2710
2788
|
<span class="tag ${item.enabled ? "ok" : item.configured ? "warn" : ""}">
|
|
2711
|
-
${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" ? "可安装外部工具" : "未启用"}
|
|
2712
2790
|
</span>
|
|
2713
2791
|
</td>
|
|
2714
2792
|
<td style="text-align:right">
|
|
2715
2793
|
${item.locked || item.kind === "agent" ? "—" :
|
|
2716
2794
|
item.kind === "plugin" && item.local ? "—" :
|
|
2717
|
-
item.kind === "plugin" ? `<button class="button small ${item.installed ? "" : "primary"}" onclick="installCapability('plugin','${esc(item.id)}')">${
|
|
2718
|
-
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>` :
|
|
2719
2797
|
item.enabled ? `<button class="button small" onclick="toggleCapability('${item.kind}','${esc(item.id)}',false)">停用</button>` :
|
|
2720
2798
|
item.kind === "local" ? `<button class="button small primary" onclick="toggleCapability('local','${esc(item.id)}',true)">启用</button>` :
|
|
2721
|
-
`<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>`
|
|
2722
2800
|
}
|
|
2723
2801
|
</td>
|
|
2724
2802
|
</tr>
|
|
2725
|
-
|
|
2803
|
+
`;
|
|
2804
|
+
}).join("") || '<tr><td colspan="5" class="empty" style="min-height:100px">未找到匹配的能力项</td></tr>';
|
|
2726
2805
|
}
|
|
2727
2806
|
|
|
2728
2807
|
async function toggleCapability(kind, id, enabled) {
|
|
@@ -2743,17 +2822,53 @@ async function installCapability(kind, id) {
|
|
|
2743
2822
|
await toggleCapability(kind, id, true);
|
|
2744
2823
|
return;
|
|
2745
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 || {}) : [];
|
|
2746
2828
|
const scope = kind === "skill" || kind === "plugin"
|
|
2747
2829
|
? (confirm("安装到系统全局还是当前项目?\n确定:全局所有项目可用;取消:仅当前项目。") ? "user" : "project")
|
|
2748
2830
|
: undefined;
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
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
|
+
}
|
|
2757
2872
|
}
|
|
2758
2873
|
|
|
2759
2874
|
[$("eco-search"), $("eco-kind"), $("eco-status")].forEach(control => {
|
|
@@ -2800,11 +2915,47 @@ async function loadProvider() {
|
|
|
2800
2915
|
}
|
|
2801
2916
|
updateModels();
|
|
2802
2917
|
renderCost();
|
|
2918
|
+
updateProviderStatusBadge(active, state.models);
|
|
2803
2919
|
} catch (error) {
|
|
2804
2920
|
$("provider-feedback").textContent = `加载配置失败: ${error.message}`;
|
|
2805
2921
|
}
|
|
2806
2922
|
}
|
|
2807
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
|
+
|
|
2808
2959
|
function currentModel() {
|
|
2809
2960
|
return providerModels($("provider-select").value).find(
|
|
2810
2961
|
model => (model.version || model.id.split("/").slice(1).join("/")) === $("model-select").value
|
|
@@ -2823,6 +2974,7 @@ $("save-provider").addEventListener("click", async () => {
|
|
|
2823
2974
|
body: JSON.stringify({ provider: $("provider-select").value, model: $("model-select").value })
|
|
2824
2975
|
});
|
|
2825
2976
|
feedback.textContent = "✓ 默认模型设置已保存,重启会话后生效。";
|
|
2977
|
+
await refreshProviderStatus();
|
|
2826
2978
|
setTimeout(() => { feedback.textContent = ""; }, 3000);
|
|
2827
2979
|
} catch (error) {
|
|
2828
2980
|
feedback.textContent = `保存失败: ${error.message}`;
|
|
@@ -3239,8 +3391,6 @@ function autoResizeConsoleInput() {
|
|
|
3239
3391
|
consoleInput.style.height = `${nextHeight}px`;
|
|
3240
3392
|
}
|
|
3241
3393
|
|
|
3242
|
-
consoleInput.addEventListener("input", autoResizeConsoleInput);
|
|
3243
|
-
|
|
3244
3394
|
// 代码块全局点击复制事件代理
|
|
3245
3395
|
document.addEventListener("click", async (e) => {
|
|
3246
3396
|
const btn = e.target.closest(".code-copy-btn");
|
|
@@ -3293,13 +3443,155 @@ async function sendConsoleMessage(msg) {
|
|
|
3293
3443
|
connectAssistantStream();
|
|
3294
3444
|
|
|
3295
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
|
+
|
|
3296
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
|
+
}
|
|
3297
3580
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
3298
3581
|
e.preventDefault();
|
|
3299
3582
|
sendConsoleMessage();
|
|
3300
3583
|
}
|
|
3301
3584
|
});
|
|
3302
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
|
+
|
|
3303
3595
|
document.querySelectorAll(".chip").forEach(chip => {
|
|
3304
3596
|
chip.addEventListener("click", () => {
|
|
3305
3597
|
const cmd = chip.dataset.cmd;
|
|
@@ -3366,61 +3658,14 @@ document.addEventListener("click", (e) => {
|
|
|
3366
3658
|
|
|
3367
3659
|
initWorkbenchCollapse();
|
|
3368
3660
|
|
|
3369
|
-
/* ---
|
|
3370
|
-
const initModal = $("init-modal");
|
|
3371
|
-
const initParadigm = $("init-paradigm");
|
|
3372
|
-
const initGoal = $("init-goal");
|
|
3373
|
-
const initPreviewCmd = $("init-preview-cmd");
|
|
3374
|
-
|
|
3375
|
-
function updateInitPreview() {
|
|
3376
|
-
const paradigm = initParadigm.value;
|
|
3377
|
-
const goal = initGoal.value.trim();
|
|
3378
|
-
initPreviewCmd.textContent = `/init --paradigm ${paradigm}${goal ? ` "${goal}"` : ""}`;
|
|
3379
|
-
}
|
|
3380
|
-
|
|
3381
|
-
initParadigm.addEventListener("change", updateInitPreview);
|
|
3382
|
-
initGoal.addEventListener("input", updateInitPreview);
|
|
3383
|
-
|
|
3384
|
-
$("cmd-init-btn").addEventListener("click", () => {
|
|
3385
|
-
updateInitPreview();
|
|
3386
|
-
initModal.classList.remove("hidden");
|
|
3387
|
-
});
|
|
3388
|
-
$("init-modal-close").addEventListener("click", () => initModal.classList.add("hidden"));
|
|
3389
|
-
$("init-cancel-btn").addEventListener("click", () => initModal.classList.add("hidden"));
|
|
3390
|
-
|
|
3391
|
-
$("init-submit-btn").addEventListener("click", async () => {
|
|
3392
|
-
const cmd = initPreviewCmd.textContent.trim();
|
|
3393
|
-
initModal.classList.add("hidden");
|
|
3394
|
-
openConsole();
|
|
3395
|
-
await sendConsoleMessage(cmd);
|
|
3396
|
-
loadProject();
|
|
3397
|
-
});
|
|
3398
|
-
|
|
3399
|
-
$("cmd-plan-btn").addEventListener("click", async () => {
|
|
3400
|
-
openConsole();
|
|
3401
|
-
await sendConsoleMessage("/plan status");
|
|
3402
|
-
});
|
|
3403
|
-
|
|
3404
|
-
$("cmd-verify-btn").addEventListener("click", async () => {
|
|
3405
|
-
setView("checklist");
|
|
3406
|
-
await loadChecklist();
|
|
3407
|
-
openConsole();
|
|
3408
|
-
await sendConsoleMessage("/crosscheck list");
|
|
3409
|
-
});
|
|
3410
|
-
|
|
3411
|
-
/* --- 加载项目状态并更新 Stepper 状态 --- */
|
|
3661
|
+
/* --- 项目状态(更新 Stepper) --- */
|
|
3412
3662
|
async function loadProjectStatus() {
|
|
3413
3663
|
try {
|
|
3414
3664
|
const status = await json("/api/project-status");
|
|
3415
|
-
if (
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
if (status.activePlan) {
|
|
3420
|
-
document.querySelectorAll(".step-item").forEach(item => {
|
|
3421
|
-
if (item.dataset.stage === "plan") item.classList.add("active");
|
|
3422
|
-
});
|
|
3423
|
-
}
|
|
3665
|
+
if (status.initialized && status.activePlan) {
|
|
3666
|
+
document.querySelectorAll(".step-item").forEach(item => {
|
|
3667
|
+
if (item.dataset.stage === "plan") item.classList.add("active");
|
|
3668
|
+
});
|
|
3424
3669
|
}
|
|
3425
3670
|
} catch { /* ignore */ }
|
|
3426
3671
|
}
|