codexmate 0.0.3 → 0.0.5

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/web-ui.html CHANGED
@@ -523,6 +523,101 @@
523
523
  gap: var(--spacing-xs);
524
524
  }
525
525
 
526
+ .recent-list {
527
+ display: flex;
528
+ flex-wrap: wrap;
529
+ gap: 8px;
530
+ margin-top: 8px;
531
+ }
532
+
533
+ .recent-item {
534
+ border: 1px solid var(--color-border-soft);
535
+ background: var(--color-surface-elevated);
536
+ border-radius: var(--radius-md);
537
+ padding: 10px 12px;
538
+ min-width: 170px;
539
+ text-align: left;
540
+ cursor: pointer;
541
+ transition: all var(--transition-fast) var(--ease-spring);
542
+ box-shadow: 0 2px 6px rgba(27, 23, 20, 0.06);
543
+ }
544
+
545
+ .recent-item:hover {
546
+ transform: translateY(-1px);
547
+ box-shadow: 0 4px 12px rgba(27, 23, 20, 0.12);
548
+ border-color: var(--color-border);
549
+ }
550
+
551
+ .recent-item:disabled {
552
+ opacity: 0.6;
553
+ cursor: not-allowed;
554
+ }
555
+
556
+ .recent-provider {
557
+ font-size: var(--font-size-body);
558
+ font-weight: var(--font-weight-secondary);
559
+ color: var(--color-text-primary);
560
+ margin-bottom: 4px;
561
+ }
562
+
563
+ .recent-model {
564
+ font-size: var(--font-size-caption);
565
+ color: var(--color-text-tertiary);
566
+ line-height: 1.4;
567
+ }
568
+
569
+ .recent-empty {
570
+ font-size: var(--font-size-caption);
571
+ color: var(--color-text-tertiary);
572
+ }
573
+
574
+ .health-report {
575
+ margin-top: 10px;
576
+ padding: 10px 12px;
577
+ border-radius: var(--radius-md);
578
+ border: 1px solid var(--color-border-soft);
579
+ background: var(--color-surface-alt);
580
+ display: grid;
581
+ gap: 8px;
582
+ }
583
+
584
+ .health-remote-toggle {
585
+ display: inline-flex;
586
+ align-items: center;
587
+ gap: 8px;
588
+ font-size: var(--font-size-caption);
589
+ color: var(--color-text-secondary);
590
+ }
591
+
592
+ .health-remote-toggle input {
593
+ accent-color: var(--color-brand);
594
+ }
595
+
596
+ .health-ok {
597
+ color: var(--color-success);
598
+ font-weight: var(--font-weight-secondary);
599
+ }
600
+
601
+ .health-issue {
602
+ background: #fff6f5;
603
+ border-left: 3px solid var(--color-error);
604
+ padding: 8px 10px;
605
+ border-radius: 10px;
606
+ }
607
+
608
+ .health-issue-title {
609
+ font-size: var(--font-size-caption);
610
+ font-weight: var(--font-weight-secondary);
611
+ color: var(--color-text-primary);
612
+ margin-bottom: 4px;
613
+ }
614
+
615
+ .health-issue-suggestion {
616
+ font-size: var(--font-size-caption);
617
+ color: var(--color-text-secondary);
618
+ line-height: 1.4;
619
+ }
620
+
526
621
  .btn-icon {
527
622
  width: 28px;
528
623
  height: 28px;
@@ -580,6 +675,31 @@
580
675
  box-shadow: var(--shadow-input-focus);
581
676
  }
582
677
 
678
+ .model-input {
679
+ width: 100%;
680
+ padding: 12px var(--spacing-sm);
681
+ border: 1px solid var(--color-border-soft);
682
+ border-radius: var(--radius-sm);
683
+ font-size: var(--font-size-body);
684
+ font-weight: var(--font-weight-body);
685
+ background-color: var(--color-surface-alt);
686
+ color: var(--color-text-primary);
687
+ outline: none;
688
+ transition: all var(--transition-fast) var(--ease-smooth);
689
+ box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.04);
690
+ }
691
+
692
+ .model-input:hover {
693
+ border-color: var(--color-border-strong);
694
+ background-color: var(--color-surface);
695
+ }
696
+
697
+ .model-input:focus {
698
+ background-color: var(--color-surface);
699
+ border-color: var(--color-brand);
700
+ box-shadow: var(--shadow-input-focus);
701
+ }
702
+
583
703
  .config-template-hint {
584
704
  margin-top: 8px;
585
705
  margin-bottom: 10px;
@@ -1280,6 +1400,37 @@
1280
1400
  }
1281
1401
  }
1282
1402
 
1403
+ @media (max-width: 520px) {
1404
+ .session-item-header {
1405
+ flex-direction: column;
1406
+ align-items: stretch;
1407
+ }
1408
+
1409
+ .session-item-actions {
1410
+ justify-content: flex-end;
1411
+ }
1412
+
1413
+ .session-actions {
1414
+ width: 100%;
1415
+ flex-direction: column;
1416
+ align-items: stretch;
1417
+ }
1418
+
1419
+ .btn-session-refresh,
1420
+ .btn-session-export {
1421
+ width: 100%;
1422
+ }
1423
+
1424
+ .session-toolbar-group.session-toolbar-actions {
1425
+ flex-direction: column;
1426
+ align-items: stretch;
1427
+ }
1428
+
1429
+ .session-toolbar-group.session-toolbar-actions .btn-tool {
1430
+ width: 100%;
1431
+ }
1432
+ }
1433
+
1283
1434
  .btn[disabled] {
1284
1435
  opacity: 0.5;
1285
1436
  cursor: not-allowed;
@@ -1666,7 +1817,7 @@
1666
1817
  Codex<br>
1667
1818
  <span class="accent">Mate.</span>
1668
1819
  </h1>
1669
- <p class="subtitle">本地配置中枢,统一管理 Codex / Claude Code / 会话。</p>
1820
+ <p class="subtitle">本地配置中枢,统一管理 Codex / Claude Code / OpenClaw / 会话。</p>
1670
1821
 
1671
1822
  <!-- 模式切换器 -->
1672
1823
  <div class="segmented-control">
@@ -1676,6 +1827,9 @@
1676
1827
  <button :class="['segment', { active: configMode === 'claude' }]" @click="switchConfigMode('claude')">
1677
1828
  Claude Code 配置
1678
1829
  </button>
1830
+ <button :class="['segment', { active: configMode === 'openclaw' }]" @click="switchConfigMode('openclaw')">
1831
+ OpenClaw 配置
1832
+ </button>
1679
1833
  <button :class="['segment', { active: configMode === 'sessions' }]" @click="switchConfigMode('sessions')">
1680
1834
  会话浏览
1681
1835
  </button>
@@ -1698,33 +1852,107 @@
1698
1852
  <div class="selector-header">
1699
1853
  <span class="selector-title">模型</span>
1700
1854
  <div class="selector-actions">
1701
- <button class="btn-icon" @click="showModelModal = true" title="添加模型">+</button>
1702
- <button class="btn-icon" @click="showModelListModal = true" title="管理模型">≡</button>
1855
+ <button class="btn-icon" @click="showModelModal = true" title="添加模型" v-if="modelsSource === 'legacy'">+</button>
1856
+ <button class="btn-icon" @click="showModelListModal = true" title="管理模型" v-if="modelsSource === 'legacy'">≡</button>
1703
1857
  </div>
1704
1858
  </div>
1705
- <select class="model-select" v-model="currentModel" @change="onModelChange">
1706
- <option v-for="model in models" :key="model" :value="model">{{ model }}</option>
1859
+ <select
1860
+ v-if="codexModelsLoading || modelsSource === 'remote'"
1861
+ class="model-select"
1862
+ v-model="currentModel"
1863
+ @change="onModelChange"
1864
+ :disabled="codexModelsLoading"
1865
+ >
1866
+ <option v-if="codexModelsLoading" value="">加载中...</option>
1867
+ <option v-else v-for="model in models" :key="model" :value="model">{{ model }}</option>
1707
1868
  </select>
1869
+ <input
1870
+ v-if="!codexModelsLoading && (modelsSource !== 'remote' || !modelsHasCurrent)"
1871
+ class="model-input"
1872
+ v-model="currentModel"
1873
+ @blur="onModelChange"
1874
+ placeholder="例如: gpt-5.3-codex"
1875
+ >
1876
+ <div class="config-template-hint" v-if="modelsSource === 'unlimited'">
1877
+ 当前提供商未提供模型列表,视为不限;模型可手动输入。
1878
+ </div>
1879
+ <div class="config-template-hint" v-if="modelsSource === 'error'">
1880
+ 模型列表获取失败,请检查接口或手动输入。
1881
+ </div>
1882
+ <div class="config-template-hint" v-if="modelsSource === 'remote' && !modelsHasCurrent">
1883
+ 当前模型不在接口列表中,请手动输入或在模板中调整。
1884
+ </div>
1708
1885
  <div class="config-template-hint">
1709
- Codex 配置改动采用模板确认模式:请先编辑模板,再手动确认应用。
1886
+ Codex 配置需先改模板,再手动应用。
1710
1887
  </div>
1711
1888
  <button class="btn-tool btn-template-editor" @click="openConfigTemplateEditor" :disabled="loading || !!initError">
1712
1889
  打开 Config 模板编辑器
1713
1890
  </button>
1714
1891
  </div>
1715
1892
 
1893
+ <div class="selector-section">
1894
+ <div class="selector-header">
1895
+ <span class="selector-title">最近使用</span>
1896
+ <span v-if="recentLoading" class="selector-title">加载中...</span>
1897
+ </div>
1898
+ <div v-if="recentConfigs.length === 0" class="recent-empty">
1899
+ 暂无记录
1900
+ </div>
1901
+ <div v-else class="recent-list">
1902
+ <button
1903
+ v-for="item in recentConfigs"
1904
+ :key="item.provider + '::' + item.model + '::' + (item.usedAt || '')"
1905
+ class="recent-item"
1906
+ @click="applyRecentConfig(item)"
1907
+ :disabled="loading || !!initError">
1908
+ <div class="recent-provider">{{ item.provider }}</div>
1909
+ <div class="recent-model">{{ item.model }}</div>
1910
+ </button>
1911
+ </div>
1912
+ </div>
1913
+
1716
1914
  <div class="selector-section">
1717
1915
  <div class="selector-header">
1718
1916
  <span class="selector-title">AGENTS.md</span>
1719
1917
  </div>
1720
1918
  <div class="config-template-hint">
1721
- 管理 Codex 指令文件,默认读写 <code>~/.codex/AGENTS.md</code>(与 <code>config.toml</code> 同级)。
1919
+ Codex 指令:<code>~/.codex/AGENTS.md</code>(同级 <code>config.toml</code>)。
1722
1920
  </div>
1723
1921
  <button class="btn-tool" @click="openAgentsEditor" :disabled="loading || !!initError || agentsLoading">
1724
1922
  {{ agentsLoading ? '加载中...' : '打开 AGENTS.md 编辑器' }}
1725
1923
  </button>
1726
1924
  </div>
1727
1925
 
1926
+ <div class="selector-section">
1927
+ <div class="selector-header">
1928
+ <span class="selector-title">配置健康检查</span>
1929
+ </div>
1930
+ <div class="config-template-hint">
1931
+ 检测 base_url、API Key 与模型可用性(可选远程探测会发起真实请求,可能产生费用)。
1932
+ </div>
1933
+ <div class="config-template-hint">
1934
+ <label class="health-remote-toggle">
1935
+ <input type="checkbox" v-model="healthCheckRemote">
1936
+ 启用远程探测(请求 base_url 与 /v1/models)
1937
+ </label>
1938
+ </div>
1939
+ <button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">
1940
+ {{ healthCheckLoading ? '检查中...' : '运行检查' }}
1941
+ </button>
1942
+ <div v-if="healthCheckResult" class="health-report">
1943
+ <div v-if="healthCheckResult.ok" class="health-ok">未发现问题</div>
1944
+ <div v-else>
1945
+ <div
1946
+ v-for="(issue, index) in healthCheckResult.issues"
1947
+ :key="(issue.code || 'issue') + index"
1948
+ class="health-issue">
1949
+ <div class="health-issue-title">{{ issue.message }}</div>
1950
+ <div v-if="issue.suggestion" class="health-issue-suggestion">{{ issue.suggestion }}</div>
1951
+ </div>
1952
+ </div>
1953
+ </div>
1954
+ </div>
1955
+
1728
1956
  <div v-if="!loading && !initError" class="card-list">
1729
1957
  <div v-for="provider in providersList" :key="provider.name"
1730
1958
  :class="['card', { active: currentProvider === provider.name }]"
@@ -1770,14 +1998,30 @@
1770
1998
  <!-- Claude Code 配置模式 -->
1771
1999
  <div v-show="configMode === 'claude'" class="mode-content">
1772
2000
  <!-- 添加提供商按钮 -->
1773
- <button class="btn-add" @click="showClaudeConfigModal = true" v-if="!loading && !initError">
2001
+ <button class="btn-add" @click="openClaudeConfigModal" v-if="!loading && !initError">
1774
2002
  <svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
1775
2003
  <path d="M10 4v12M4 10h12"/>
1776
2004
  </svg>
1777
2005
  添加提供商
1778
2006
  </button>
1779
2007
  <div class="config-template-hint">
1780
- 默认应用到 <code>~/.claude/settings.json</code>;编辑弹窗中可使用兼容模式写入系统环境变量。
2008
+ 默认应用到 <code>~/.claude/settings.json</code>。
2009
+ </div>
2010
+
2011
+ <div class="selector-section">
2012
+ <div class="selector-header">
2013
+ <span class="selector-title">模型</span>
2014
+ </div>
2015
+ <input
2016
+ class="model-input"
2017
+ v-model="currentClaudeModel"
2018
+ @blur="onClaudeModelChange"
2019
+ @keyup.enter="onClaudeModelChange"
2020
+ placeholder="例如: claude-3-7-sonnet"
2021
+ >
2022
+ <div class="config-template-hint">
2023
+ 模型修改后会自动保存并应用到当前配置。
2024
+ </div>
1781
2025
  </div>
1782
2026
 
1783
2027
  <div class="card-list">
@@ -1814,6 +2058,64 @@
1814
2058
  </div>
1815
2059
  </div>
1816
2060
 
2061
+ <!-- OpenClaw 配置模式 -->
2062
+ <div v-show="configMode === 'openclaw'" class="mode-content">
2063
+ <button class="btn-add" @click="openOpenclawAddModal" v-if="!loading && !initError">
2064
+ <svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
2065
+ <path d="M10 4v12M4 10h12"/>
2066
+ </svg>
2067
+ 添加 OpenClaw 配置
2068
+ </button>
2069
+ <div class="config-template-hint">
2070
+ 默认应用到 <code>~/.openclaw/openclaw.json</code>;支持 JSON5(注释/尾逗号)。
2071
+ </div>
2072
+
2073
+ <div class="selector-section">
2074
+ <div class="selector-header">
2075
+ <span class="selector-title">AGENTS.md</span>
2076
+ </div>
2077
+ <div class="config-template-hint">
2078
+ 管理 OpenClaw Workspace 指令文件,默认读写 <code>~/.openclaw/workspace/AGENTS.md</code>。
2079
+ </div>
2080
+ <button class="btn-tool" @click="openOpenclawAgentsEditor" :disabled="loading || !!initError || agentsLoading">
2081
+ {{ agentsLoading ? '加载中...' : '打开 AGENTS.md 编辑器' }}
2082
+ </button>
2083
+ </div>
2084
+
2085
+ <div class="card-list">
2086
+ <div v-for="(config, name) in openclawConfigs" :key="name"
2087
+ :class="['card', { active: currentOpenclawConfig === name }]"
2088
+ @click="applyOpenclawConfig(name)">
2089
+ <div class="card-leading">
2090
+ <div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
2091
+ <div class="card-content">
2092
+ <div class="card-title">{{ name }}</div>
2093
+ <div class="card-subtitle">{{ openclawSubtitle(config) }}</div>
2094
+ </div>
2095
+ </div>
2096
+ <div class="card-trailing">
2097
+ <span :class="['pill', openclawHasContent(config) ? 'configured' : 'empty']">
2098
+ {{ openclawHasContent(config) ? '已配置' : '未配置' }}
2099
+ </span>
2100
+ <div class="card-actions" @click.stop>
2101
+ <button class="card-action-btn" @click="openOpenclawEditModal(name)" title="编辑">
2102
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2103
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
2104
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
2105
+ </svg>
2106
+ </button>
2107
+ <button class="card-action-btn delete" @click="deleteOpenclawConfig(name)" title="删除">
2108
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2109
+ <path d="M3 6h18"/>
2110
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
2111
+ </svg>
2112
+ </button>
2113
+ </div>
2114
+ </div>
2115
+ </div>
2116
+ </div>
2117
+ </div>
2118
+
1817
2119
  <!-- 会话浏览模式 -->
1818
2120
  <div v-show="configMode === 'sessions'" class="mode-content">
1819
2121
  <div class="selector-section">
@@ -1876,7 +2178,8 @@
1876
2178
  </div>
1877
2179
  </div>
1878
2180
  <div class="session-hint">
1879
- 关键词检索与角色/时间筛选暂时停用;当前仅支持来源与路径筛选会话列表。右侧预览区仅支持查看与导出。
2181
+ 关键词/角色/时间筛选暂不可用;<br>
2182
+ 仅支持来源与路径筛选,右侧仅查看/导出。
1880
2183
  </div>
1881
2184
  </div>
1882
2185
 
@@ -2111,10 +2414,6 @@
2111
2414
  <label class="form-label">Base URL</label>
2112
2415
  <input v-model="newClaudeConfig.baseUrl" class="form-input" placeholder="https://open.bigmodel.cn/api/anthropic">
2113
2416
  </div>
2114
- <div class="form-group">
2115
- <label class="form-label">模型</label>
2116
- <input v-model="newClaudeConfig.model" class="form-input" placeholder="例如: claude-sonnet-4-20250514">
2117
- </div>
2118
2417
 
2119
2418
  <div class="btn-group">
2120
2419
  <button class="btn btn-cancel" @click="closeClaudeConfigModal">取消</button>
@@ -2140,16 +2439,58 @@
2140
2439
  <label class="form-label">Base URL</label>
2141
2440
  <input v-model="editingConfig.baseUrl" class="form-input" placeholder="https://open.bigmodel.cn/api/anthropic">
2142
2441
  </div>
2442
+
2443
+ <div class="btn-group">
2444
+ <button class="btn btn-cancel" @click="closeEditConfigModal">取消</button>
2445
+ <button class="btn btn-confirm" @click="saveAndApplyConfig">保存并应用</button>
2446
+ </div>
2447
+ </div>
2448
+ </div>
2449
+
2450
+ <div v-if="showOpenclawConfigModal" class="modal-overlay" @click.self="closeOpenclawConfigModal">
2451
+ <div class="modal modal-wide">
2452
+ <div class="modal-title">{{ openclawEditorTitle }}</div>
2453
+
2143
2454
  <div class="form-group">
2144
- <label class="form-label">模型</label>
2145
- <input v-model="editingConfig.model" class="form-input" placeholder="例如: claude-sonnet-4-20250514">
2455
+ <label class="form-label">配置名称</label>
2456
+ <input v-model="openclawEditing.name" class="form-input" :readonly="openclawEditing.lockName" placeholder="例如: 默认配置">
2457
+ </div>
2458
+
2459
+ <div class="form-group">
2460
+ <label class="form-label">目标文件</label>
2461
+ <div class="form-hint">
2462
+ {{ openclawConfigPath || '未加载' }}
2463
+ <span v-if="openclawConfigPath">
2464
+ ({{ openclawConfigExists ? '已存在' : '不存在,将在应用时创建' }})
2465
+ </span>
2466
+ </div>
2467
+ <div class="btn-group" style="justify-content:flex-start;">
2468
+ <button class="btn btn-confirm secondary" @click="loadOpenclawConfigFromFile" :disabled="openclawFileLoading">
2469
+ {{ openclawFileLoading ? '加载中...' : '加载当前配置' }}
2470
+ </button>
2471
+ </div>
2472
+ </div>
2473
+
2474
+ <div class="form-group">
2475
+ <label class="form-label">OpenClaw 配置(JSON5)</label>
2476
+ <textarea
2477
+ v-model="openclawEditing.content"
2478
+ class="form-input template-editor"
2479
+ spellcheck="false"
2480
+ placeholder="在这里编辑 OpenClaw 配置(JSON5)"></textarea>
2481
+ <div class="template-editor-warning">
2482
+ 保存仅写入本地配置库;点击“保存并应用”后会写入 openclaw.json。
2483
+ </div>
2146
2484
  </div>
2147
2485
 
2148
2486
  <div class="btn-group">
2149
- <button class="btn btn-cancel" @click="closeEditConfigModal">取消</button>
2150
- <button class="btn btn-confirm" @click="updateConfig">保存</button>
2151
- <button class="btn btn-confirm secondary" @click="saveAndApplyConfig">保存并应用到 Claude 配置</button>
2152
- <button class="btn btn-confirm secondary" @click="saveAndApplyEnvCompat">兼容模式应用到环境变量</button>
2487
+ <button class="btn btn-cancel" @click="closeOpenclawConfigModal">取消</button>
2488
+ <button class="btn btn-confirm" @click="saveOpenclawConfig" :disabled="openclawSaving">
2489
+ {{ openclawSaving ? '保存中...' : '保存' }}
2490
+ </button>
2491
+ <button class="btn btn-confirm secondary" @click="saveAndApplyOpenclawConfig" :disabled="openclawApplying">
2492
+ {{ openclawApplying ? '应用中...' : '保存并应用' }}
2493
+ </button>
2153
2494
  </div>
2154
2495
  </div>
2155
2496
  </div>
@@ -2181,7 +2522,7 @@
2181
2522
 
2182
2523
  <div v-if="showAgentsModal" class="modal-overlay" @click.self="closeAgentsModal">
2183
2524
  <div class="modal modal-wide">
2184
- <div class="modal-title">AGENTS.md 编辑器</div>
2525
+ <div class="modal-title">{{ agentsModalTitle }}</div>
2185
2526
 
2186
2527
  <div class="form-group">
2187
2528
  <label class="form-label">目标文件</label>
@@ -2202,7 +2543,7 @@
2202
2543
  :readonly="agentsLoading"
2203
2544
  placeholder="在这里编辑 AGENTS.md 内容"></textarea>
2204
2545
  <div class="template-editor-warning">
2205
- 保存后会写入目标 AGENTS.md(与 config.toml 同级)。
2546
+ {{ agentsModalHint }}
2206
2547
  </div>
2207
2548
  </div>
2208
2549
 
@@ -2224,6 +2565,17 @@
2224
2565
  <script>
2225
2566
  const { createApp } = Vue;
2226
2567
  const API_BASE = 'http://localhost:3737';
2568
+ const DEFAULT_OPENCLAW_TEMPLATE = `{
2569
+ // OpenClaw config (JSON5)
2570
+ agent: {
2571
+ model: "gpt-4.1"
2572
+ },
2573
+ agents: {
2574
+ defaults: {
2575
+ workspace: "~/.openclaw/workspace"
2576
+ }
2577
+ }
2578
+ }`;
2227
2579
 
2228
2580
  async function api(action, params = {}) {
2229
2581
  const res = await fetch(`${API_BASE}/api`, {
@@ -2242,6 +2594,13 @@
2242
2594
  currentModel: '',
2243
2595
  providersList: [],
2244
2596
  models: [],
2597
+ codexModelsLoading: false,
2598
+ modelsSource: 'remote',
2599
+ modelsHasCurrent: true,
2600
+ claudeModels: [],
2601
+ claudeModelsSource: 'idle',
2602
+ claudeModelsHasCurrent: true,
2603
+ claudeModelsLoading: false,
2245
2604
  loading: true,
2246
2605
  initError: '',
2247
2606
  message: '',
@@ -2252,6 +2611,7 @@
2252
2611
  showModelListModal: false,
2253
2612
  showClaudeConfigModal: false,
2254
2613
  showEditConfigModal: false,
2614
+ showOpenclawConfigModal: false,
2255
2615
  showConfigTemplateModal: false,
2256
2616
  showAgentsModal: false,
2257
2617
  configTemplateContent: '',
@@ -2262,6 +2622,9 @@
2262
2622
  agentsLineEnding: '\n',
2263
2623
  agentsLoading: false,
2264
2624
  agentsSaving: false,
2625
+ agentsContext: 'codex',
2626
+ agentsModalTitle: 'AGENTS.md 编辑器',
2627
+ agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
2265
2628
  sessionsList: [],
2266
2629
  sessionsLoading: false,
2267
2630
  sessionFilterSource: 'all',
@@ -2295,6 +2658,7 @@
2295
2658
  editingProvider: { name: '', url: '', key: '' },
2296
2659
  newModelName: '',
2297
2660
  currentClaudeConfig: '',
2661
+ currentClaudeModel: '',
2298
2662
  editingConfig: { name: '', apiKey: '', baseUrl: '', model: '' },
2299
2663
  claudeConfigs: {
2300
2664
  '智谱GLM': {
@@ -2309,7 +2673,26 @@
2309
2673
  apiKey: '',
2310
2674
  baseUrl: 'https://open.bigmodel.cn/api/anthropic',
2311
2675
  model: 'glm-4.7'
2312
- }
2676
+ },
2677
+ currentOpenclawConfig: '',
2678
+ openclawConfigs: {
2679
+ '默认配置': {
2680
+ content: DEFAULT_OPENCLAW_TEMPLATE
2681
+ }
2682
+ },
2683
+ openclawEditing: { name: '', content: '', lockName: false },
2684
+ openclawEditorTitle: '添加 OpenClaw 配置',
2685
+ openclawConfigPath: '',
2686
+ openclawConfigExists: false,
2687
+ openclawLineEnding: '\n',
2688
+ openclawFileLoading: false,
2689
+ openclawSaving: false,
2690
+ openclawApplying: false,
2691
+ recentConfigs: [],
2692
+ recentLoading: false,
2693
+ healthCheckLoading: false,
2694
+ healthCheckResult: null,
2695
+ healthCheckRemote: false
2313
2696
  }
2314
2697
  },
2315
2698
  mounted() {
@@ -2333,6 +2716,30 @@
2333
2716
  console.error('加载 Claude 配置失败:', e);
2334
2717
  }
2335
2718
  }
2719
+ if (!this.currentClaudeConfig) {
2720
+ const configNames = Object.keys(this.claudeConfigs);
2721
+ if (configNames.length > 0) {
2722
+ this.currentClaudeConfig = configNames[0];
2723
+ }
2724
+ }
2725
+ this.syncClaudeModelFromConfig();
2726
+ const savedOpenclawConfigs = localStorage.getItem('openclawConfigs');
2727
+ if (savedOpenclawConfigs) {
2728
+ try {
2729
+ this.openclawConfigs = JSON.parse(savedOpenclawConfigs);
2730
+ const configNames = Object.keys(this.openclawConfigs);
2731
+ if (configNames.length > 0) {
2732
+ this.currentOpenclawConfig = configNames[0];
2733
+ }
2734
+ } catch (e) {
2735
+ console.error('加载 OpenClaw 配置失败:', e);
2736
+ }
2737
+ } else {
2738
+ const configNames = Object.keys(this.openclawConfigs);
2739
+ if (configNames.length > 0) {
2740
+ this.currentOpenclawConfig = configNames[0];
2741
+ }
2742
+ }
2336
2743
  this.loadAll();
2337
2744
  },
2338
2745
  methods: {
@@ -2340,11 +2747,8 @@
2340
2747
  this.loading = true;
2341
2748
  this.initError = '';
2342
2749
  try {
2343
- const [statusRes, listRes, modelsRes] = await Promise.all([
2344
- api('status'),
2345
- api('list'),
2346
- api('models')
2347
- ]);
2750
+ const statusRes = await api('status');
2751
+ const listRes = await api('list');
2348
2752
 
2349
2753
  if (statusRes.error) {
2350
2754
  this.initError = statusRes.error;
@@ -2352,14 +2756,16 @@
2352
2756
  this.currentProvider = statusRes.provider;
2353
2757
  this.currentModel = statusRes.model;
2354
2758
  this.providersList = listRes.providers;
2355
- this.models = modelsRes.models;
2759
+ await this.loadModelsForProvider(this.currentProvider);
2356
2760
  if (statusRes.configReady === false) {
2357
2761
  this.showMessage(statusRes.configNotice || '未检测到 config.toml,已加载默认模板;请在模板编辑器确认后创建。', 'info');
2358
2762
  }
2359
2763
  if (statusRes.initNotice) {
2360
2764
  this.showMessage(statusRes.initNotice, 'info');
2361
2765
  }
2766
+ this.maybeShowStarPrompt();
2362
2767
  }
2768
+ await this.loadRecentConfigs();
2363
2769
  } catch (e) {
2364
2770
  this.initError = '连接失败: ' + e.message;
2365
2771
  } finally {
@@ -2367,8 +2773,129 @@
2367
2773
  }
2368
2774
  },
2369
2775
 
2776
+ async loadModelsForProvider(providerName) {
2777
+ this.codexModelsLoading = true;
2778
+ if (!providerName) {
2779
+ this.models = [];
2780
+ this.modelsSource = 'unlimited';
2781
+ this.modelsHasCurrent = true;
2782
+ this.codexModelsLoading = false;
2783
+ return;
2784
+ }
2785
+ try {
2786
+ const res = await api('models', { provider: providerName });
2787
+ if (res.unlimited) {
2788
+ this.models = [];
2789
+ this.modelsSource = 'unlimited';
2790
+ this.modelsHasCurrent = true;
2791
+ return;
2792
+ }
2793
+ if (res.error) {
2794
+ this.showMessage('模型列表获取失败: ' + res.error, 'error');
2795
+ this.models = [];
2796
+ this.modelsSource = 'error';
2797
+ this.modelsHasCurrent = true;
2798
+ return;
2799
+ }
2800
+ const list = Array.isArray(res.models) ? res.models : [];
2801
+ this.models = list;
2802
+ this.modelsSource = res.source || 'remote';
2803
+ this.modelsHasCurrent = !!this.currentModel && list.includes(this.currentModel);
2804
+ } catch (e) {
2805
+ this.showMessage('模型列表获取失败: ' + e.message, 'error');
2806
+ this.models = [];
2807
+ this.modelsSource = 'error';
2808
+ this.modelsHasCurrent = true;
2809
+ } finally {
2810
+ this.codexModelsLoading = false;
2811
+ }
2812
+ },
2813
+
2814
+ getCurrentClaudeConfig() {
2815
+ if (!this.currentClaudeConfig) return null;
2816
+ return this.claudeConfigs[this.currentClaudeConfig] || null;
2817
+ },
2818
+
2819
+ syncClaudeModelFromConfig() {
2820
+ const config = this.getCurrentClaudeConfig();
2821
+ this.currentClaudeModel = config && config.model ? config.model : '';
2822
+ },
2823
+
2824
+ refreshClaudeModelContext() {
2825
+ this.syncClaudeModelFromConfig();
2826
+ this.loadClaudeModels();
2827
+ },
2828
+
2829
+ resetClaudeModelsState() {
2830
+ this.claudeModels = [];
2831
+ this.claudeModelsSource = 'idle';
2832
+ this.claudeModelsHasCurrent = true;
2833
+ this.claudeModelsLoading = false;
2834
+ },
2835
+
2836
+ updateClaudeModelsCurrent() {
2837
+ const currentModel = (this.currentClaudeModel || '').trim();
2838
+ this.claudeModelsHasCurrent = !!currentModel && this.claudeModels.includes(currentModel);
2839
+ },
2840
+
2841
+ async loadClaudeModels() {
2842
+ const config = this.getCurrentClaudeConfig();
2843
+ const baseUrl = (config.baseUrl || '').trim();
2844
+ const apiKey = (config.apiKey || '').trim();
2845
+
2846
+ if (!baseUrl) {
2847
+ this.resetClaudeModelsState();
2848
+ return;
2849
+ }
2850
+
2851
+ this.claudeModelsLoading = true;
2852
+ try {
2853
+ const res = await api('models-by-url', { baseUrl, apiKey });
2854
+ if (res.unlimited) {
2855
+ this.claudeModels = [];
2856
+ this.claudeModelsSource = 'unlimited';
2857
+ this.claudeModelsHasCurrent = true;
2858
+ return;
2859
+ }
2860
+ if (res.error) {
2861
+ this.showMessage('模型列表获取失败: ' + res.error, 'error');
2862
+ this.claudeModels = [];
2863
+ this.claudeModelsSource = 'error';
2864
+ this.claudeModelsHasCurrent = true;
2865
+ return;
2866
+ }
2867
+ const list = Array.isArray(res.models) ? res.models : [];
2868
+ this.claudeModels = list;
2869
+ this.claudeModelsSource = res.source || 'remote';
2870
+ this.updateClaudeModelsCurrent();
2871
+ } catch (e) {
2872
+ this.showMessage('模型列表获取失败: ' + e.message, 'error');
2873
+ this.claudeModels = [];
2874
+ this.claudeModelsSource = 'error';
2875
+ this.claudeModelsHasCurrent = true;
2876
+ } finally {
2877
+ this.claudeModelsLoading = false;
2878
+ }
2879
+ },
2880
+
2881
+ openClaudeConfigModal() {
2882
+ this.showClaudeConfigModal = true;
2883
+ },
2884
+
2885
+ maybeShowStarPrompt() {
2886
+ const storageKey = 'codexmateStarPrompted';
2887
+ if (localStorage.getItem(storageKey)) {
2888
+ return;
2889
+ }
2890
+ this.showMessage('如果 Codex Mate 对你有帮助,欢迎到 GitHub 点个 Star。', 'info');
2891
+ localStorage.setItem(storageKey, '1');
2892
+ },
2893
+
2370
2894
  switchConfigMode(mode) {
2371
2895
  this.configMode = mode;
2896
+ if (mode === 'claude') {
2897
+ this.refreshClaudeModelContext();
2898
+ }
2372
2899
  if (mode === 'sessions' && this.sessionsList.length === 0) {
2373
2900
  this.loadSessions();
2374
2901
  }
@@ -2614,7 +3141,8 @@
2614
3141
  pathFilter: this.sessionPathFilter,
2615
3142
  query,
2616
3143
  queryMode: 'and',
2617
- queryScope: 'summary',
3144
+ queryScope: 'content',
3145
+ contentScanLimit: 2,
2618
3146
  roleFilter: this.sessionRoleFilter,
2619
3147
  timeRangePreset: this.sessionTimePreset,
2620
3148
  limit: 200,
@@ -2759,6 +3287,7 @@
2759
3287
 
2760
3288
  async switchProvider(name) {
2761
3289
  this.currentProvider = name;
3290
+ await this.loadModelsForProvider(name);
2762
3291
  await this.openConfigTemplateEditor();
2763
3292
  },
2764
3293
 
@@ -2766,6 +3295,60 @@
2766
3295
  await this.openConfigTemplateEditor();
2767
3296
  },
2768
3297
 
3298
+ async loadRecentConfigs() {
3299
+ this.recentLoading = true;
3300
+ try {
3301
+ const res = await api('get-recent-configs');
3302
+ if (res && Array.isArray(res.items)) {
3303
+ this.recentConfigs = res.items;
3304
+ } else {
3305
+ this.recentConfigs = [];
3306
+ }
3307
+ } catch (e) {
3308
+ this.recentConfigs = [];
3309
+ } finally {
3310
+ this.recentLoading = false;
3311
+ }
3312
+ },
3313
+
3314
+ async applyRecentConfig(item) {
3315
+ if (!item || !item.provider || !item.model) {
3316
+ this.showMessage('最近配置无效,无法应用', 'error');
3317
+ return;
3318
+ }
3319
+ this.currentProvider = item.provider;
3320
+ this.currentModel = item.model;
3321
+ await this.openConfigTemplateEditor({
3322
+ appendHint: '最近使用配置,确认后将写入 config.toml'
3323
+ });
3324
+ },
3325
+
3326
+ async runHealthCheck() {
3327
+ this.healthCheckLoading = true;
3328
+ this.healthCheckResult = null;
3329
+ try {
3330
+ const res = await api('config-health-check', {
3331
+ remote: this.healthCheckRemote
3332
+ });
3333
+ if (res && typeof res === 'object') {
3334
+ this.healthCheckResult = res;
3335
+ if (res.ok) {
3336
+ this.showMessage('健康检查通过', 'success');
3337
+ } else {
3338
+ this.showMessage('发现配置问题,请查看详情', 'error');
3339
+ }
3340
+ } else {
3341
+ this.healthCheckResult = null;
3342
+ this.showMessage('健康检查失败:返回数据异常', 'error');
3343
+ }
3344
+ } catch (e) {
3345
+ this.healthCheckResult = null;
3346
+ this.showMessage('健康检查失败: ' + e.message, 'error');
3347
+ } finally {
3348
+ this.healthCheckLoading = false;
3349
+ }
3350
+ },
3351
+
2769
3352
  escapeTomlString(value) {
2770
3353
  return String(value || '')
2771
3354
  .replace(/\\/g, '\\\\')
@@ -2829,6 +3412,7 @@
2829
3412
  },
2830
3413
 
2831
3414
  async openAgentsEditor() {
3415
+ this.setAgentsModalContext('codex');
2832
3416
  this.agentsLoading = true;
2833
3417
  try {
2834
3418
  const res = await api('get-agents-file');
@@ -2848,6 +3432,41 @@
2848
3432
  }
2849
3433
  },
2850
3434
 
3435
+ async openOpenclawAgentsEditor() {
3436
+ this.setAgentsModalContext('openclaw');
3437
+ this.agentsLoading = true;
3438
+ try {
3439
+ const res = await api('get-openclaw-agents-file');
3440
+ if (res.error) {
3441
+ this.showMessage(res.error, 'error');
3442
+ return;
3443
+ }
3444
+ if (res.configError) {
3445
+ this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
3446
+ }
3447
+ this.agentsContent = res.content || '';
3448
+ this.agentsPath = res.path || '';
3449
+ this.agentsExists = !!res.exists;
3450
+ this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
3451
+ this.showAgentsModal = true;
3452
+ } catch (e) {
3453
+ this.showMessage('加载 OpenClaw AGENTS.md 失败: ' + e.message, 'error');
3454
+ } finally {
3455
+ this.agentsLoading = false;
3456
+ }
3457
+ },
3458
+
3459
+ setAgentsModalContext(context) {
3460
+ this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
3461
+ if (this.agentsContext === 'openclaw') {
3462
+ this.agentsModalTitle = 'OpenClaw AGENTS.md 编辑器';
3463
+ this.agentsModalHint = '保存后会写入 OpenClaw Workspace 下的 AGENTS.md。';
3464
+ } else {
3465
+ this.agentsModalTitle = 'AGENTS.md 编辑器';
3466
+ this.agentsModalHint = '保存后会写入目标 AGENTS.md(与 config.toml 同级)。';
3467
+ }
3468
+ },
3469
+
2851
3470
  closeAgentsModal() {
2852
3471
  this.showAgentsModal = false;
2853
3472
  this.agentsContent = '';
@@ -2855,12 +3474,16 @@
2855
3474
  this.agentsExists = false;
2856
3475
  this.agentsLineEnding = '\n';
2857
3476
  this.agentsSaving = false;
3477
+ this.setAgentsModalContext('codex');
2858
3478
  },
2859
3479
 
2860
3480
  async applyAgentsContent() {
2861
3481
  this.agentsSaving = true;
2862
3482
  try {
2863
- const res = await api('apply-agents-file', {
3483
+ const action = this.agentsContext === 'openclaw'
3484
+ ? 'apply-openclaw-agents-file'
3485
+ : 'apply-agents-file';
3486
+ const res = await api(action, {
2864
3487
  content: this.agentsContent,
2865
3488
  lineEnding: this.agentsLineEnding
2866
3489
  });
@@ -2990,6 +3613,35 @@
2990
3613
 
2991
3614
  switchClaudeConfig(name) {
2992
3615
  this.currentClaudeConfig = name;
3616
+ this.refreshClaudeModelContext();
3617
+ },
3618
+
3619
+ onClaudeModelChange() {
3620
+ const name = this.currentClaudeConfig;
3621
+ if (!name) {
3622
+ this.showMessage('请先选择配置', 'error');
3623
+ return;
3624
+ }
3625
+ const model = (this.currentClaudeModel || '').trim();
3626
+ if (!model) {
3627
+ this.showMessage('请输入模型', 'error');
3628
+ return;
3629
+ }
3630
+ const existing = this.claudeConfigs[name] || {};
3631
+ this.currentClaudeModel = model;
3632
+ this.claudeConfigs[name] = {
3633
+ apiKey: existing.apiKey || '',
3634
+ baseUrl: existing.baseUrl || '',
3635
+ model: model,
3636
+ hasKey: !!existing.apiKey
3637
+ };
3638
+ this.saveClaudeConfigs();
3639
+ this.updateClaudeModelsCurrent();
3640
+ if (!this.claudeConfigs[name].apiKey) {
3641
+ this.showMessage('该配置未设置 API Key,请先编辑', 'error');
3642
+ return;
3643
+ }
3644
+ this.applyClaudeConfig(name);
2993
3645
  },
2994
3646
 
2995
3647
  saveClaudeConfigs() {
@@ -3018,6 +3670,9 @@
3018
3670
  this.saveClaudeConfigs();
3019
3671
  this.showMessage('配置已更新', 'success');
3020
3672
  this.closeEditConfigModal();
3673
+ if (name === this.currentClaudeConfig) {
3674
+ this.refreshClaudeModelContext();
3675
+ }
3021
3676
  },
3022
3677
 
3023
3678
  closeEditConfigModal() {
@@ -3037,7 +3692,12 @@
3037
3692
 
3038
3693
  const config = this.claudeConfigs[name];
3039
3694
  if (!config.apiKey) {
3040
- return this.showMessage('请先输入 API Key', 'error');
3695
+ this.showMessage('已保存,未应用:请先输入 API Key', 'info');
3696
+ this.closeEditConfigModal();
3697
+ if (name === this.currentClaudeConfig) {
3698
+ this.refreshClaudeModelContext();
3699
+ }
3700
+ return;
3041
3701
  }
3042
3702
 
3043
3703
  const res = await api('apply-claude-config', { config });
@@ -3047,30 +3707,9 @@
3047
3707
  const targetTip = res.targetPath ? `(${res.targetPath})` : '';
3048
3708
  this.showMessage(`已保存并应用到 Claude 配置${targetTip}`, 'success');
3049
3709
  this.closeEditConfigModal();
3050
- }
3051
- },
3052
-
3053
- async saveAndApplyEnvCompat() {
3054
- const name = this.editingConfig.name;
3055
- this.claudeConfigs[name] = {
3056
- apiKey: this.editingConfig.apiKey,
3057
- baseUrl: this.editingConfig.baseUrl,
3058
- model: this.editingConfig.model,
3059
- hasKey: !!this.editingConfig.apiKey
3060
- };
3061
- this.saveClaudeConfigs();
3062
-
3063
- const config = this.claudeConfigs[name];
3064
- if (!config.apiKey) {
3065
- return this.showMessage('请先输入 API Key', 'error');
3066
- }
3067
-
3068
- const res = await api('apply-env', { config });
3069
- if (res.error || res.success === false) {
3070
- this.showMessage(res.error || '应用环境变量失败', 'error');
3071
- } else {
3072
- this.showMessage('已保存并应用到系统环境变量(兼容模式)', 'success');
3073
- this.closeEditConfigModal();
3710
+ if (name === this.currentClaudeConfig) {
3711
+ this.refreshClaudeModelContext();
3712
+ }
3074
3713
  }
3075
3714
  },
3076
3715
 
@@ -3094,6 +3733,7 @@
3094
3733
  this.saveClaudeConfigs();
3095
3734
  this.showMessage('配置已添加', 'success');
3096
3735
  this.closeClaudeConfigModal();
3736
+ this.refreshClaudeModelContext();
3097
3737
  },
3098
3738
 
3099
3739
  deleteClaudeConfig(name) {
@@ -3109,10 +3749,12 @@
3109
3749
  }
3110
3750
  this.saveClaudeConfigs();
3111
3751
  this.showMessage('配置已删除', 'success');
3752
+ this.refreshClaudeModelContext();
3112
3753
  },
3113
3754
 
3114
3755
  async applyClaudeConfig(name) {
3115
3756
  this.currentClaudeConfig = name;
3757
+ this.refreshClaudeModelContext();
3116
3758
  const config = this.claudeConfigs[name];
3117
3759
 
3118
3760
  if (!config.apiKey) {
@@ -3138,6 +3780,173 @@
3138
3780
  };
3139
3781
  },
3140
3782
 
3783
+ openclawHasContent(config) {
3784
+ return !!(config && typeof config.content === 'string' && config.content.trim());
3785
+ },
3786
+
3787
+ openclawSubtitle(config) {
3788
+ if (!this.openclawHasContent(config)) {
3789
+ return '未设置配置';
3790
+ }
3791
+ const length = config.content.trim().length;
3792
+ return `已保存 ${length} 字符`;
3793
+ },
3794
+
3795
+ saveOpenclawConfigs() {
3796
+ localStorage.setItem('openclawConfigs', JSON.stringify(this.openclawConfigs));
3797
+ },
3798
+
3799
+ openOpenclawAddModal() {
3800
+ this.openclawEditorTitle = '添加 OpenClaw 配置';
3801
+ this.openclawEditing = {
3802
+ name: '',
3803
+ content: DEFAULT_OPENCLAW_TEMPLATE,
3804
+ lockName: false
3805
+ };
3806
+ this.openclawConfigPath = '';
3807
+ this.openclawConfigExists = false;
3808
+ this.openclawLineEnding = '\n';
3809
+ this.showOpenclawConfigModal = true;
3810
+ },
3811
+
3812
+ openOpenclawEditModal(name) {
3813
+ const config = this.openclawConfigs[name];
3814
+ this.openclawEditorTitle = `编辑 OpenClaw 配置: ${name}`;
3815
+ this.openclawEditing = {
3816
+ name,
3817
+ content: (config && config.content) ? config.content : '',
3818
+ lockName: true
3819
+ };
3820
+ this.showOpenclawConfigModal = true;
3821
+ },
3822
+
3823
+ closeOpenclawConfigModal() {
3824
+ this.showOpenclawConfigModal = false;
3825
+ this.openclawEditing = { name: '', content: '', lockName: false };
3826
+ this.openclawSaving = false;
3827
+ this.openclawApplying = false;
3828
+ },
3829
+
3830
+ async loadOpenclawConfigFromFile() {
3831
+ this.openclawFileLoading = true;
3832
+ try {
3833
+ const res = await api('get-openclaw-config');
3834
+ if (res.error) {
3835
+ this.showMessage(res.error, 'error');
3836
+ return;
3837
+ }
3838
+ this.openclawConfigPath = res.path || '';
3839
+ this.openclawConfigExists = !!res.exists;
3840
+ this.openclawLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
3841
+ if (res.content && res.content.trim()) {
3842
+ this.openclawEditing.content = res.content;
3843
+ } else if (!this.openclawEditing.content) {
3844
+ this.openclawEditing.content = DEFAULT_OPENCLAW_TEMPLATE;
3845
+ }
3846
+ this.showMessage('已加载当前 OpenClaw 配置', 'success');
3847
+ } catch (e) {
3848
+ this.showMessage('加载 OpenClaw 配置失败: ' + e.message, 'error');
3849
+ } finally {
3850
+ this.openclawFileLoading = false;
3851
+ }
3852
+ },
3853
+
3854
+ persistOpenclawConfig({ closeModal = true } = {}) {
3855
+ if (!this.openclawEditing.name || !this.openclawEditing.name.trim()) {
3856
+ this.showMessage('请输入配置名称', 'error');
3857
+ return '';
3858
+ }
3859
+ const name = this.openclawEditing.name.trim();
3860
+ if (!this.openclawEditing.lockName && this.openclawConfigs[name]) {
3861
+ this.showMessage('配置名称已存在', 'error');
3862
+ return '';
3863
+ }
3864
+ if (!this.openclawEditing.content || !this.openclawEditing.content.trim()) {
3865
+ this.showMessage('配置内容不能为空', 'error');
3866
+ return '';
3867
+ }
3868
+
3869
+ this.openclawConfigs[name] = {
3870
+ content: this.openclawEditing.content
3871
+ };
3872
+ this.currentOpenclawConfig = name;
3873
+ this.saveOpenclawConfigs();
3874
+ if (closeModal) {
3875
+ this.closeOpenclawConfigModal();
3876
+ }
3877
+ return name;
3878
+ },
3879
+
3880
+ async saveOpenclawConfig() {
3881
+ this.openclawSaving = true;
3882
+ try {
3883
+ const name = this.persistOpenclawConfig();
3884
+ if (!name) return;
3885
+ this.showMessage('OpenClaw 配置已保存', 'success');
3886
+ } finally {
3887
+ this.openclawSaving = false;
3888
+ }
3889
+ },
3890
+
3891
+ async saveAndApplyOpenclawConfig() {
3892
+ this.openclawApplying = true;
3893
+ try {
3894
+ const name = this.persistOpenclawConfig({ closeModal: false });
3895
+ if (!name) return;
3896
+ const config = this.openclawConfigs[name];
3897
+ const res = await api('apply-openclaw-config', {
3898
+ content: config.content,
3899
+ lineEnding: this.openclawLineEnding
3900
+ });
3901
+ if (res.error || res.success === false) {
3902
+ this.showMessage(res.error || '应用 OpenClaw 配置失败', 'error');
3903
+ return;
3904
+ }
3905
+ this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
3906
+ this.openclawConfigExists = true;
3907
+ const targetTip = res.targetPath ? `(${res.targetPath})` : '';
3908
+ this.showMessage(`已保存并应用 OpenClaw 配置${targetTip}`, 'success');
3909
+ this.closeOpenclawConfigModal();
3910
+ } catch (e) {
3911
+ this.showMessage('应用 OpenClaw 配置失败: ' + e.message, 'error');
3912
+ } finally {
3913
+ this.openclawApplying = false;
3914
+ }
3915
+ },
3916
+
3917
+ deleteOpenclawConfig(name) {
3918
+ if (Object.keys(this.openclawConfigs).length <= 1) {
3919
+ return this.showMessage('至少保留一个配置', 'error');
3920
+ }
3921
+ if (!confirm(`确定删除配置 "${name}"?`)) return;
3922
+ delete this.openclawConfigs[name];
3923
+ if (this.currentOpenclawConfig === name) {
3924
+ this.currentOpenclawConfig = Object.keys(this.openclawConfigs)[0];
3925
+ }
3926
+ this.saveOpenclawConfigs();
3927
+ this.showMessage('OpenClaw 配置已删除', 'success');
3928
+ },
3929
+
3930
+ async applyOpenclawConfig(name) {
3931
+ this.currentOpenclawConfig = name;
3932
+ const config = this.openclawConfigs[name];
3933
+ if (!this.openclawHasContent(config)) {
3934
+ return this.showMessage('该配置为空,请先编辑', 'error');
3935
+ }
3936
+ const res = await api('apply-openclaw-config', {
3937
+ content: config.content,
3938
+ lineEnding: this.openclawLineEnding
3939
+ });
3940
+ if (res.error || res.success === false) {
3941
+ this.showMessage(res.error || '应用 OpenClaw 配置失败', 'error');
3942
+ } else {
3943
+ this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
3944
+ this.openclawConfigExists = true;
3945
+ const targetTip = res.targetPath ? `(${res.targetPath})` : '';
3946
+ this.showMessage(`已应用 OpenClaw 配置: ${name}${targetTip}`, 'success');
3947
+ }
3948
+ },
3949
+
3141
3950
  formatLatency(result) {
3142
3951
  if (!result) return '';
3143
3952
  if (!result.ok) return result.status ? `ERR ${result.status}` : 'ERR';
@@ -3174,10 +3983,3 @@
3174
3983
  </script>
3175
3984
  </body>
3176
3985
  </html>
3177
-
3178
-
3179
-
3180
-
3181
-
3182
-
3183
-