chatccc 0.2.234 → 0.2.236

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/src/web-ui.ts CHANGED
@@ -18,6 +18,13 @@ import {
18
18
  createInternalRestartEnv,
19
19
  openWebUiInDefaultBrowser,
20
20
  } from "./startup-lifecycle.ts";
21
+ import {
22
+ getClaudeSdkInstalledVersion,
23
+ getLastInstallProgress,
24
+ installClaudeSdk,
25
+ isClaudeSdkInstalled,
26
+ isInstallRunning,
27
+ } from "./claude-sdk-installer.ts";
21
28
 
22
29
  const __dirname = dirname(fileURLToPath(import.meta.url));
23
30
  const PROJECT_ROOT = join(__dirname, "..");
@@ -629,6 +636,36 @@ async function handleForgetIlink(_req: IncomingMessage, res: ServerResponse): Pr
629
636
  }
630
637
  }
631
638
 
639
+ async function handleClaudeSdkStatus(_req: IncomingMessage, res: ServerResponse): Promise<void> {
640
+ const progress = getLastInstallProgress();
641
+ jsonReply(res, 200, {
642
+ installed: isClaudeSdkInstalled(),
643
+ version: getClaudeSdkInstalledVersion(),
644
+ running: isInstallRunning(),
645
+ // 展开到顶层:前端轮询 JS 读顶层 phase/message/percent/error(历史契约),
646
+ // 若只放在 progress 嵌套里,前端 s.phase 恒为 undefined → 进度条永不渲染。
647
+ ...progress,
648
+ // 保留嵌套结构,兼容其它调用方。
649
+ progress,
650
+ });
651
+ }
652
+
653
+ async function handleClaudeSdkInstall(_req: IncomingMessage, res: ServerResponse): Promise<void> {
654
+ if (isInstallRunning()) {
655
+ jsonReply(res, 200, { ok: true, alreadyRunning: true });
656
+ return;
657
+ }
658
+ try {
659
+ // 后台执行,不阻塞响应;前端通过 /api/claude-sdk/status 轮询进度
660
+ installClaudeSdk().catch((err: unknown) => {
661
+ console.error(`[WEB-UI] Claude SDK 安装失败: ${(err as Error).message}`);
662
+ });
663
+ jsonReply(res, 200, { ok: true });
664
+ } catch (err) {
665
+ jsonReply(res, 500, { ok: false, error: (err as Error).message });
666
+ }
667
+ }
668
+
632
669
  // ---------------------------------------------------------------------------
633
670
  // HTML page (embedded template)
634
671
  // ---------------------------------------------------------------------------
@@ -854,10 +891,56 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
854
891
 
855
892
  <div class="agent-cards">
856
893
 
894
+ <!-- CCC Agent 卡片(置顶:ChatCCC 内置 Agent,开箱即用) -->
895
+ <div class="agent-card" id="agent-card-ccc">
896
+ <div class="agent-card-header">
897
+ <input type="checkbox" class="agent-toggle" id="agent-enable-ccc" onchange="onAgentToggle('ccc', this.checked)">
898
+ <div class="meta">
899
+ <div class="name">CCC Agent</div>
900
+ <div class="desc">ChatCCC 内置 Agent<br>OpenAI 兼容 API(不限于 DeepSeek)</div>
901
+ </div>
902
+ </div>
903
+ <label class="agent-default-row">
904
+ <input type="checkbox" id="agent-default-ccc" onchange="onDefaultAgentToggle('ccc', this.checked)">
905
+ 设为默认 Agent
906
+ </label>
907
+ <fieldset class="agent-body" id="agent-body-ccc" disabled>
908
+ <div class="form-group">
909
+ <label>API Key</label>
910
+ <input type="password" id="field-CHATCCC_CCC_API_KEY" placeholder="OpenAI 兼容 API Key(如 DeepSeek)">
911
+ </div>
912
+ <div class="form-group">
913
+ <label>Base URL</label>
914
+ <input type="text" id="field-CHATCCC_CCC_BASE_URL" placeholder="https://api.deepseek.com/v1(可填任意 OpenAI 兼容端点)">
915
+ </div>
916
+ <div class="form-group">
917
+ <label>模型</label>
918
+ <input type="text" id="field-CHATCCC_CCC_MODEL" placeholder="deepseek-v4-pro">
919
+ </div>
920
+ <div class="form-group">
921
+ <label>备选模型(选填)</label>
922
+ <input type="text" id="field-CHATCCC_CCC_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
923
+ </div>
924
+ <div class="form-group">
925
+ <label>Effort(推理强度,选填)</label>
926
+ <select id="field-CHATCCC_CCC_EFFORT">
927
+ <option value="">(留空/默认,服务端 medium)</option>
928
+ <option value="none">none - 直接作答,最省 token</option>
929
+ <option value="minimal">minimal</option>
930
+ <option value="low">low</option>
931
+ <option value="medium">medium</option>
932
+ <option value="high">high</option>
933
+ <option value="xhigh">xhigh</option>
934
+ <option value="max">max - 最强推理</option>
935
+ </select>
936
+ </div>
937
+ </fieldset>
938
+ </div>
939
+
857
940
  <!-- Claude 卡片 -->
858
941
  <div class="agent-card" id="agent-card-claude">
859
942
  <div class="agent-card-header">
860
- <input type="checkbox" class="agent-toggle" id="agent-enable-claude" onchange="onAgentToggle('claude', this.checked)">
943
+ <input type="checkbox" class="agent-toggle" id="agent-enable-claude" onchange="onClaudeToggle(this)">
861
944
  <div class="meta">
862
945
  <div class="name">Claude Code</div>
863
946
  <div class="desc">Anthropic Claude Code CLI<br>模型、effort 均为选填</div>
@@ -888,6 +971,18 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
888
971
  <label>Base URL(选填)</label>
889
972
  <input type="text" id="field-CHATCCC_ANTHROPIC_BASE_URL" placeholder="留空使用默认端点">
890
973
  </div>
974
+ <div class="form-group" style="border-top:1px solid #e2e8f0;padding-top:12px;margin-top:4px">
975
+ <label>Claude Code 引擎(Agent SDK)</label>
976
+ <div id="claude-engine-status" style="font-size:13px;color:#64748b;margin-bottom:8px">检测中...</div>
977
+ <div id="claude-engine-progress-wrap" style="display:none;margin-bottom:8px">
978
+ <div style="background:#e2e8f0;border-radius:6px;height:8px;overflow:hidden">
979
+ <div id="claude-engine-progress-bar" style="width:0%;height:100%;background:#3b82f6;transition:width .3s"></div>
980
+ </div>
981
+ <div id="claude-engine-progress-text" style="font-size:12px;color:#64748b;margin-top:4px"></div>
982
+ </div>
983
+ <button class="btn btn-outline" id="claude-engine-install-btn" onclick="installClaudeEngine()">安装 Claude Code SDK</button>
984
+ <div class="hint" style="margin-top:6px">ChatCCC 通过 Claude Agent SDK 调用 Claude Code;SDK 引擎按需下载到本机(仅启用 Claude Code 时需要),安装期间请保持网络畅通。</div>
985
+ </div>
891
986
  </fieldset>
892
987
  </div>
893
988
 
@@ -974,52 +1069,6 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
974
1069
  </fieldset>
975
1070
  </div>
976
1071
 
977
- <!-- CCC Agent 卡片 -->
978
- <div class="agent-card" id="agent-card-ccc">
979
- <div class="agent-card-header">
980
- <input type="checkbox" class="agent-toggle" id="agent-enable-ccc" onchange="onAgentToggle('ccc', this.checked)">
981
- <div class="meta">
982
- <div class="name">CCC Agent</div>
983
- <div class="desc">ChatCCC 内置 Agent<br>使用 DeepSeek 兼容 API</div>
984
- </div>
985
- </div>
986
- <label class="agent-default-row">
987
- <input type="checkbox" id="agent-default-ccc" onchange="onDefaultAgentToggle('ccc', this.checked)">
988
- 设为默认 Agent
989
- </label>
990
- <fieldset class="agent-body" id="agent-body-ccc" disabled>
991
- <div class="form-group">
992
- <label>API Key</label>
993
- <input type="password" id="field-CHATCCC_CCC_API_KEY" placeholder="DeepSeek 兼容 API Key">
994
- </div>
995
- <div class="form-group">
996
- <label>Base URL</label>
997
- <input type="text" id="field-CHATCCC_CCC_BASE_URL" placeholder="https://api.deepseek.com/v1">
998
- </div>
999
- <div class="form-group">
1000
- <label>模型</label>
1001
- <input type="text" id="field-CHATCCC_CCC_MODEL" placeholder="deepseek-v4-pro">
1002
- </div>
1003
- <div class="form-group">
1004
- <label>备选模型(选填)</label>
1005
- <input type="text" id="field-CHATCCC_CCC_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
1006
- </div>
1007
- <div class="form-group">
1008
- <label>Effort(推理强度,选填)</label>
1009
- <select id="field-CHATCCC_CCC_EFFORT">
1010
- <option value="">(留空/默认,服务端 medium)</option>
1011
- <option value="none">none - 直接作答,最省 token</option>
1012
- <option value="minimal">minimal</option>
1013
- <option value="low">low</option>
1014
- <option value="medium">medium</option>
1015
- <option value="high">high</option>
1016
- <option value="xhigh">xhigh</option>
1017
- <option value="max">max - 最强推理</option>
1018
- </select>
1019
- </div>
1020
- </fieldset>
1021
- </div>
1022
-
1023
1072
  </div>
1024
1073
 
1025
1074
  <div class="btn-group" style="justify-content:space-between">
@@ -1552,6 +1601,10 @@ function renderStep2() {
1552
1601
  var cursorOn = isAgentEnabled(c.cursor, CURSOR_FALLBACK_KEYS);
1553
1602
  var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
1554
1603
  var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
1604
+ // 全新用户:四个 Agent 均无启用/配置痕迹时,只默认勾选 DeepCCC(ccc),其余不勾
1605
+ if (!claudeOn && !cursorOn && !codexOn && !cccOn) {
1606
+ cccOn = true;
1607
+ }
1555
1608
  state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
1556
1609
  document.getElementById('agent-enable-claude').checked = claudeOn;
1557
1610
  document.getElementById('agent-enable-cursor').checked = cursorOn;
@@ -2182,8 +2235,124 @@ function validateCli(tool) {
2182
2235
  });
2183
2236
  }
2184
2237
 
2238
+ // ---- Claude Code 引擎(Agent SDK)按需安装 ----
2239
+ var claudeEnginePollTimer = null;
2240
+ var claudeEngineState = null;
2241
+
2242
+ // Claude Code 开关:从关闭 → 打开时弹窗确认(SDK 必装才能使用)。
2243
+ // 页面初始化(按已有 config 设置开关)直接调 onAgentToggle,不经由此函数,不会误弹窗。
2244
+ function onClaudeToggle(el) {
2245
+ var enabled = el.checked;
2246
+ if (!enabled) {
2247
+ onAgentToggle('claude', false);
2248
+ return;
2249
+ }
2250
+ // 实时查询后端安装状态(不依赖可能过期的 claudeEngineState 缓存),
2251
+ // 已安装/正在安装时直接打开开关,不重复安装。
2252
+ api('/api/claude-sdk/status', 'GET').then(function(s){
2253
+ if (!s || !s.phase) { el.checked = false; return; }
2254
+ claudeEngineState = s;
2255
+ var sdkReady = s.installed === true || s.phase === 'done' ||
2256
+ s.phase === 'downloading' || s.phase === 'installing' || s.phase === 'detecting';
2257
+ if (sdkReady) {
2258
+ // 已安装 / 正在安装 / 正在检测:直接打开开关,不再触发安装
2259
+ onAgentToggle('claude', true);
2260
+ return;
2261
+ }
2262
+ if (!confirm('Claude Code 必须先安装 Claude Code SDK 才能使用(必装)。是否立即安装?')) {
2263
+ el.checked = false; // 用户取消 → 回滚开关为关闭
2264
+ return;
2265
+ }
2266
+ onAgentToggle('claude', true);
2267
+ installClaudeEngine();
2268
+ }).catch(function(){
2269
+ el.checked = false; // 状态查询失败 → 回滚开关,避免误判
2270
+ });
2271
+ }
2272
+
2273
+ function claudeEngineEl(id) { return document.getElementById(id); }
2274
+
2275
+ function claudeEngineRenderStatus(s) {
2276
+ var el = claudeEngineEl('claude-engine-status');
2277
+ if (!el) return;
2278
+ var phase = s.phase;
2279
+ var text = s.message || '';
2280
+ var color = '#64748b';
2281
+ if (phase === 'done') color = '#16a34a';
2282
+ else if (phase === 'error') color = '#ef4444';
2283
+ else if (phase === 'downloading' || phase === 'installing') color = '#3b82f6';
2284
+ if (!text && phase === 'idle') {
2285
+ // 初始/空闲状态给出明确文案,而不是“未知状态”
2286
+ text = s.installed ? ('已安装 v' + (s.version || '未知版本')) : '未安装';
2287
+ }
2288
+ el.innerHTML = '<span style="color:' + color + '">' + (text ? text : '未知状态') + '</span>';
2289
+ if (s.error) el.innerHTML += '<br><span style="color:#ef4444;font-size:12px">' + s.error + '</span>';
2290
+ }
2291
+
2292
+ function claudeEngineRenderProgress(p) {
2293
+ var wrap = claudeEngineEl('claude-engine-progress-wrap');
2294
+ if (!wrap) return;
2295
+ if (p && (p.phase === 'downloading' || p.phase === 'installing' || p.phase === 'detecting')) {
2296
+ wrap.style.display = 'block';
2297
+ claudeEngineEl('claude-engine-progress-bar').style.width = (p.percent || 0) + '%';
2298
+ claudeEngineEl('claude-engine-progress-text').textContent = p.message || '';
2299
+ } else {
2300
+ wrap.style.display = 'none';
2301
+ }
2302
+ }
2303
+
2304
+ function claudeEngineRefreshStatus() {
2305
+ api('/api/claude-sdk/status', 'GET').then(function(s){
2306
+ if (!s || !s.phase) return;
2307
+ claudeEngineState = s;
2308
+ claudeEngineRenderStatus(s);
2309
+ claudeEngineRenderProgress(s);
2310
+ var btn = claudeEngineEl('claude-engine-install-btn');
2311
+ if (btn) {
2312
+ if (s.phase === 'done' || s.installed) {
2313
+ btn.textContent = '重新安装 Claude Code SDK';
2314
+ btn.disabled = false;
2315
+ } else if (s.phase === 'downloading' || s.phase === 'installing' || s.phase === 'detecting') {
2316
+ btn.textContent = '安装中…';
2317
+ btn.disabled = true;
2318
+ } else {
2319
+ btn.textContent = '安装 Claude Code SDK';
2320
+ btn.disabled = false;
2321
+ }
2322
+ }
2323
+ if (s.phase === 'downloading' || s.phase === 'installing' || s.phase === 'detecting') {
2324
+ claudeEnginePollTimer = setTimeout(claudeEngineRefreshStatus, 600);
2325
+ } else if (claudeEnginePollTimer) {
2326
+ clearTimeout(claudeEnginePollTimer);
2327
+ claudeEnginePollTimer = null;
2328
+ }
2329
+ }).catch(function(){
2330
+ // 网络/服务异常时停止轮询,避免无限重试
2331
+ if (claudeEnginePollTimer) { clearTimeout(claudeEnginePollTimer); claudeEnginePollTimer = null; }
2332
+ });
2333
+ }
2334
+
2335
+ function installClaudeEngine() {
2336
+ var btn = claudeEngineEl('claude-engine-install-btn');
2337
+ if (btn) btn.disabled = true;
2338
+ api('/api/claude-sdk/install', 'POST').then(function(r){
2339
+ if (r.ok) {
2340
+ claudeEngineRefreshStatus();
2341
+ } else {
2342
+ claudeEngineRenderStatus({ phase: 'error', message: '启动安装失败', error: r.error || '' });
2343
+ if (btn) btn.disabled = false;
2344
+ }
2345
+ }).catch(function(e){
2346
+ claudeEngineRenderStatus({ phase: 'error', message: '请求失败', error: String(e) });
2347
+ if (btn) btn.disabled = false;
2348
+ });
2349
+ }
2350
+
2185
2351
  // ---- Start ----
2186
2352
  init();
2353
+
2354
+ // 进入 dashboard/向导后初始化 Claude 引擎状态(页面元素此时已存在)
2355
+ setTimeout(claudeEngineRefreshStatus, 300);
2187
2356
  </script>
2188
2357
  </body>
2189
2358
  </html>`;
@@ -2209,6 +2378,8 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise
2209
2378
  if (url === "/api/restart" && method === "POST") return handleRestartService(req, res);
2210
2379
  if (url === "/api/validate" && method === "POST") return handleValidate(req, res);
2211
2380
  if (url === "/api/ilink/forget" && method === "POST") return handleForgetIlink(req, res);
2381
+ if (url === "/api/claude-sdk/status" && method === "GET") return handleClaudeSdkStatus(req, res);
2382
+ if (url === "/api/claude-sdk/install" && method === "POST") return handleClaudeSdkInstall(req, res);
2212
2383
 
2213
2384
  if (method === "GET" && (pathname === "/agent-team" || pathname === "/agent-team/")) {
2214
2385
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });