chatccc 0.2.177 → 0.2.179

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
@@ -33,7 +33,15 @@ interface AppConfig {
33
33
  gitTimeoutSeconds?: number;
34
34
  claude?: { enabled?: boolean; defaultAgent?: boolean; model?: string; subagentModel?: string; effort?: string; apiKey?: string; baseUrl?: string; maxTurn?: number };
35
35
  // `command` 是已废弃的旧字段名,保留只读以兼容升级前的 config.json
36
- cursor?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string };
36
+ cursor?: {
37
+ enabled?: boolean;
38
+ defaultAgent?: boolean;
39
+ path?: string;
40
+ command?: string;
41
+ model?: string;
42
+ avatarBatteryMode?: string;
43
+ onDemandMonthlyBudget?: number;
44
+ };
37
45
  codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; effort?: string };
38
46
  }
39
47
 
@@ -362,6 +370,12 @@ export function unflattenConfig(flat: Record<string, unknown>): Record<string, u
362
370
  } else if (key === "CHATCCC_CURSOR_MODEL") {
363
371
  result.cursor = result.cursor || {};
364
372
  (result.cursor as Record<string, unknown>).model = val;
373
+ } else if (key === "CHATCCC_CURSOR_AVATAR_BATTERY_MODE") {
374
+ result.cursor = result.cursor || {};
375
+ (result.cursor as Record<string, unknown>).avatarBatteryMode = val;
376
+ } else if (key === "CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET") {
377
+ result.cursor = result.cursor || {};
378
+ (result.cursor as Record<string, unknown>).onDemandMonthlyBudget = Number(val);
365
379
  } else if (key === "CHATCCC_CURSOR_ENABLED") {
366
380
  result.cursor = result.cursor || {};
367
381
  (result.cursor as Record<string, unknown>).enabled = val === true || val === "true";
@@ -480,7 +494,7 @@ async function handleForgetIlink(_req: IncomingMessage, res: ServerResponse): Pr
480
494
  // HTML page (embedded template)
481
495
  // ---------------------------------------------------------------------------
482
496
 
483
- export const PAGE_HTML = `<!DOCTYPE html>
497
+ export const PAGE_HTML = `<!DOCTYPE html>
484
498
  <html lang="zh-CN">
485
499
  <head>
486
500
  <meta charset="UTF-8">
@@ -697,6 +711,17 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
697
711
  <label>模型</label>
698
712
  <input type="text" id="field-CHATCCC_CURSOR_MODEL" placeholder="留空表示不传 --model">
699
713
  </div>
714
+ <div class="form-group">
715
+ <label>头像电池电量</label>
716
+ <select id="field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE" onchange="onCursorBatteryModeChange('field-', this.value)" style="width:100%;padding:8px 12px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;outline:none">
717
+ <option value="apiPercent">API 使用比例</option>
718
+ <option value="onDemandUse">On demand use 金额</option>
719
+ </select>
720
+ </div>
721
+ <div class="form-group" id="field-cursor-on-demand-budget-row" style="display:none">
722
+ <label>每月On demand use预算</label>
723
+ <input type="number" id="field-CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET" min="1" step="1" placeholder="1000">
724
+ </div>
700
725
  <button class="btn btn-outline" onclick="validateCli('cursor')" style="margin-bottom:12px">检测 Cursor CLI</button>
701
726
  <div id="cursor-validate-result"></div>
702
727
  </fieldset>
@@ -819,6 +844,8 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
819
844
  <div class="section-detail">
820
845
  <div class="config-row"><span class="key">CLI 路径</span><span class="val" id="cfg-CURSOR_PATH">-</span></div>
821
846
  <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CURSOR_MODEL">-</span></div>
847
+ <div class="config-row"><span class="key">头像电池电量</span><span class="val" id="cfg-CURSOR_AVATAR_BATTERY_MODE">-</span></div>
848
+ <div class="config-row" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET_ROW"><span class="key">每月On demand use预算</span><span class="val" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET">-</span></div>
822
849
  <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-cursor" onchange="setDashboardDefaultAgent('cursor', this.checked)"> 设为默认 Agent</label>
823
850
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('cursor')">编辑</button>
824
851
  </div>
@@ -876,11 +903,21 @@ var step2InputBound = false;
876
903
 
877
904
  const AGENT_FIELDS = {
878
905
  claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
879
- cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL'],
906
+ cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
880
907
  codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_EFFORT']
881
908
  };
882
909
  const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
883
910
 
911
+ function cursorBatteryModeLabel(value) {
912
+ return value === 'onDemandUse' ? 'On demand use 金额' : 'API 使用比例';
913
+ }
914
+
915
+ function onCursorBatteryModeChange(prefix, value) {
916
+ var rowId = prefix === 'edit-' ? 'edit-cursor-on-demand-budget-row' : 'field-cursor-on-demand-budget-row';
917
+ var row = document.getElementById(rowId);
918
+ if (row) row.style.display = value === 'onDemandUse' ? '' : 'none';
919
+ }
920
+
884
921
  function onWizardPlatformToggle(platform, enabled) {
885
922
  state.platformsEnabled[platform] = enabled;
886
923
  // 飞书凭证字段跟随开关显示/隐藏
@@ -1127,7 +1164,18 @@ function renderStep2() {
1127
1164
  if (c.cursor) {
1128
1165
  prefillNested('field-CHATCCC_CURSOR_PATH', c.cursor.path || c.cursor.command);
1129
1166
  prefillNested('field-CHATCCC_CURSOR_MODEL', c.cursor.model);
1167
+ var cursorMode = c.cursor.avatarBatteryMode || 'apiPercent';
1168
+ var cursorModeInput = document.getElementById('field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
1169
+ if (cursorModeInput) cursorModeInput.value = cursorMode;
1170
+ prefillNested('field-CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET', c.cursor.onDemandMonthlyBudget || 1000);
1171
+ } else {
1172
+ var defaultCursorModeInput = document.getElementById('field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
1173
+ if (defaultCursorModeInput) defaultCursorModeInput.value = 'apiPercent';
1174
+ prefillNested('field-CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET', 1000);
1130
1175
  }
1176
+ var cursorBatteryModeEl = document.getElementById('field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
1177
+ if (cursorBatteryModeEl && !cursorBatteryModeEl.value) cursorBatteryModeEl.value = 'apiPercent';
1178
+ onCursorBatteryModeChange('field-', cursorBatteryModeEl ? cursorBatteryModeEl.value : 'apiPercent');
1131
1179
  if (c.codex) {
1132
1180
  prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
1133
1181
  prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
@@ -1256,6 +1304,10 @@ function renderStep3() {
1256
1304
  lines.push('<h4 style="margin:10px 0 4px;color:#334155">Cursor</h4>');
1257
1305
  if (vars.CHATCCC_CURSOR_PATH) lines.push('<div class="config-row"><span class="key">CLI 路径</span><span class="val">' + vars.CHATCCC_CURSOR_PATH + '</span></div>');
1258
1306
  lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CURSOR_MODEL || '(留空)') + '</span></div>');
1307
+ lines.push('<div class="config-row"><span class="key">头像电池电量</span><span class="val">' + cursorBatteryModeLabel(vars.CHATCCC_CURSOR_AVATAR_BATTERY_MODE) + '</span></div>');
1308
+ if (vars.CHATCCC_CURSOR_AVATAR_BATTERY_MODE === 'onDemandUse') {
1309
+ lines.push('<div class="config-row"><span class="key">每月On demand use预算</span><span class="val">' + (vars.CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET || '1000') + '</span></div>');
1310
+ }
1259
1311
  } else if (t === 'codex') {
1260
1312
  lines.push('<h4 style="margin:10px 0 4px;color:#334155">Codex</h4>');
1261
1313
  if (vars.CHATCCC_CODEX_PATH) lines.push('<div class="config-row"><span class="key">CLI 路径</span><span class="val">' + vars.CHATCCC_CODEX_PATH + '</span></div>');
@@ -1435,6 +1487,11 @@ function updateDashboardUI() {
1435
1487
  document.getElementById('cfg-ANTHROPIC_MAX_TURN').textContent = (c.claude && c.claude.maxTurn != null) ? String(c.claude.maxTurn) : '0';
1436
1488
  document.getElementById('cfg-CURSOR_PATH').textContent = (c.cursor && (c.cursor.path || c.cursor.command)) || '-';
1437
1489
  document.getElementById('cfg-CURSOR_MODEL').textContent = (c.cursor && c.cursor.model) || '(留空)';
1490
+ var cursorBatteryMode = (c.cursor && c.cursor.avatarBatteryMode) || 'apiPercent';
1491
+ document.getElementById('cfg-CURSOR_AVATAR_BATTERY_MODE').textContent = cursorBatteryModeLabel(cursorBatteryMode);
1492
+ var cursorBudgetRow = document.getElementById('cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET_ROW');
1493
+ if (cursorBudgetRow) cursorBudgetRow.style.display = cursorBatteryMode === 'onDemandUse' ? '' : 'none';
1494
+ document.getElementById('cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET').textContent = String((c.cursor && c.cursor.onDemandMonthlyBudget) || 1000);
1438
1495
  document.getElementById('cfg-CODEX_PATH').textContent = (c.codex && (c.codex.path || c.codex.command)) || 'codex';
1439
1496
  document.getElementById('cfg-CODEX_MODEL').textContent = (c.codex && c.codex.model) || '(留空)';
1440
1497
  document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
@@ -1500,6 +1557,8 @@ function editSection(section) {
1500
1557
  'CHATCCC_ANTHROPIC_MODEL': '模型', 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL': 'Subagent 模型', 'CHATCCC_ANTHROPIC_EFFORT': 'Effort',
1501
1558
  'CHATCCC_ANTHROPIC_API_KEY': 'API Key', 'CHATCCC_ANTHROPIC_BASE_URL': 'Base URL', 'CHATCCC_ANTHROPIC_MAX_TURN': 'Max Turns (0=无限制)',
1502
1559
  'CHATCCC_CURSOR_PATH': 'CLI 路径', 'CHATCCC_CURSOR_MODEL': '模型',
1560
+ 'CHATCCC_CURSOR_AVATAR_BATTERY_MODE': '头像电池电量',
1561
+ 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET': '每月On demand use预算',
1503
1562
  'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_EFFORT': 'Effort'
1504
1563
  };
1505
1564
 
@@ -1520,6 +1579,8 @@ function editSection(section) {
1520
1579
  } else if (section === 'cursor' && state.config.cursor) {
1521
1580
  if (key === 'CHATCCC_CURSOR_PATH') val = state.config.cursor.path || state.config.cursor.command || '';
1522
1581
  else if (key === 'CHATCCC_CURSOR_MODEL') val = state.config.cursor.model || '';
1582
+ else if (key === 'CHATCCC_CURSOR_AVATAR_BATTERY_MODE') val = state.config.cursor.avatarBatteryMode || 'apiPercent';
1583
+ else if (key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET') val = (state.config.cursor.onDemandMonthlyBudget != null) ? String(state.config.cursor.onDemandMonthlyBudget) : '1000';
1523
1584
  } else if (section === 'codex' && state.config.codex) {
1524
1585
  if (key === 'CHATCCC_CODEX_PATH') val = state.config.codex.path || state.config.codex.command || '';
1525
1586
  else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
@@ -1527,9 +1588,21 @@ function editSection(section) {
1527
1588
  }
1528
1589
  }
1529
1590
  var isSecret = key.includes('SECRET') || key.includes('API_KEY');
1530
- html += '<div class="form-group"><label>' + (labelMap[key] || key) + '</label>';
1531
- html += '<input type="' + (isSecret ? 'password' : 'text') + '" id="edit-' + key + '" value="' + String(val).replace(/"/g,'&quot;') + '">';
1532
- html += '</div>';
1591
+ if (key === 'CHATCCC_CURSOR_AVATAR_BATTERY_MODE') {
1592
+ var modeVal = val || 'apiPercent';
1593
+ html += '<div class="form-group"><label>' + (labelMap[key] || key) + '</label>';
1594
+ html += '<select id="edit-' + key + '" onchange="onCursorBatteryModeChange(\\'edit-\\', this.value)" style="width:100%;padding:8px 12px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;outline:none">';
1595
+ html += '<option value="apiPercent"' + (modeVal === 'apiPercent' ? ' selected' : '') + '>API 使用比例</option>';
1596
+ html += '<option value="onDemandUse"' + (modeVal === 'onDemandUse' ? ' selected' : '') + '>On demand use 金额</option>';
1597
+ html += '</select></div>';
1598
+ } else {
1599
+ var rowId = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET' ? ' id="edit-cursor-on-demand-budget-row"' : '';
1600
+ var inputType = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET' ? 'number' : (isSecret ? 'password' : 'text');
1601
+ var attrs = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET' ? ' min="1" step="1"' : '';
1602
+ html += '<div class="form-group"' + rowId + '><label>' + (labelMap[key] || key) + '</label>';
1603
+ html += '<input type="' + inputType + '" id="edit-' + key + '"' + attrs + ' value="' + String(val).replace(/"/g,'&quot;') + '">';
1604
+ html += '</div>';
1605
+ }
1533
1606
  });
1534
1607
  // 平台类型:feishu 编辑时额外渲染下拉选择框
1535
1608
  if (section === 'feishu') {
@@ -1539,11 +1612,15 @@ function editSection(section) {
1539
1612
  html += '<option value="feishu"' + (ptVal === 'feishu' ? ' selected' : '') + '>飞书 (open.feishu.cn)</option>';
1540
1613
  html += '<option value="lark"' + (ptVal === 'lark' ? ' selected' : '') + '>Lark (open.larksuite.com)</option>';
1541
1614
  html += '</select></div>';
1542
- }
1543
- document.getElementById('edit-modal-fields').innerHTML = html;
1544
- document.getElementById('edit-modal').classList.remove('hidden');
1545
- document.getElementById('edit-overlay').classList.remove('hidden');
1546
- }
1615
+ }
1616
+ document.getElementById('edit-modal-fields').innerHTML = html;
1617
+ if (section === 'cursor') {
1618
+ var editModeEl = document.getElementById('edit-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
1619
+ onCursorBatteryModeChange('edit-', editModeEl ? editModeEl.value : 'apiPercent');
1620
+ }
1621
+ document.getElementById('edit-modal').classList.remove('hidden');
1622
+ document.getElementById('edit-overlay').classList.remove('hidden');
1623
+ }
1547
1624
 
1548
1625
  function closeEditModal() {
1549
1626
  document.getElementById('edit-modal').classList.add('hidden');
@@ -1561,12 +1638,19 @@ async function saveEdit() {
1561
1638
  var el = document.getElementById('edit-' + key);
1562
1639
  if (el) vars[key] = el.value.trim();
1563
1640
  });
1641
+ if (editSectionType === 'cursor' && vars.CHATCCC_CURSOR_AVATAR_BATTERY_MODE === 'onDemandUse' && !vars.CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET) {
1642
+ vars.CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET = '1000';
1643
+ }
1564
1644
  // 平台类型:feishu 编辑时额外采集下拉选择框的值
1565
1645
  if (editSectionType === 'feishu') {
1566
1646
  var ptEl = document.getElementById('edit-CHATCCC_FEISHU_PLATFORM_TYPE');
1567
1647
  if (ptEl && ptEl.value.trim()) vars['CHATCCC_FEISHU_PLATFORM_TYPE'] = ptEl.value.trim();
1568
1648
  }
1569
1649
  await saveConfig(vars);
1650
+ try {
1651
+ var fresh = await api('/api/config');
1652
+ state.config = fresh.vars || state.config;
1653
+ } catch(e) {}
1570
1654
  closeEditModal();
1571
1655
  updateDashboardUI();
1572
1656
  toast('修改已保存。若服务正在运行,需重启生效。');