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/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- package/.github/workflows/release.yml +42 -42
- package/CHANGELOG.md +7 -0
- package/CHANGELOG.zh-CN.md +7 -0
- package/README.md +112 -58
- package/README.zh-CN.md +115 -57
- package/cli.js +1536 -182
- package/cmd/publish-npm.cmd +65 -65
- package/package.json +33 -25
- package/res/screenshot.png +0 -0
- package/tests/e2e/recent-health.e2e.js +135 -0
- package/tests/e2e/run.js +294 -0
- package/web-ui.html +865 -63
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
|
|
1706
|
-
|
|
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
|
-
|
|
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="
|
|
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"
|
|
2145
|
-
<input v-model="
|
|
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="
|
|
2150
|
-
<button class="btn btn-confirm" @click="
|
|
2151
|
-
|
|
2152
|
-
|
|
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">
|
|
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
|
-
|
|
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
|
|
2344
|
-
|
|
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.
|
|
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: '
|
|
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
|
|
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
|
-
|
|
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
|
-
|