codexmate 0.0.31 → 0.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -308
- package/README.zh.md +94 -318
- package/cli/local-bridge.js +227 -0
- package/cli/update.js +162 -0
- package/cli.js +357 -112
- package/lib/cli-sessions.js +16 -6
- package/lib/win-tray.js +119 -0
- package/package.json +2 -2
- package/web-ui/app.js +4 -0
- package/web-ui/logic.sessions.mjs +17 -1
- package/web-ui/modules/app.computed.session.mjs +51 -315
- package/web-ui/modules/app.methods.agents.mjs +19 -0
- package/web-ui/modules/app.methods.claude-config.mjs +71 -2
- package/web-ui/modules/app.methods.codex-config.mjs +20 -0
- package/web-ui/modules/app.methods.providers.mjs +53 -7
- package/web-ui/modules/app.methods.session-actions.mjs +1 -1
- package/web-ui/modules/app.methods.session-browser.mjs +29 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +4 -0
- package/web-ui/modules/i18n.dict.mjs +21 -3
- package/web-ui/partials/index/layout-header.html +1 -2
- package/web-ui/partials/index/modal-config-template-agents.html +12 -1
- package/web-ui/partials/index/modals-basic.html +14 -3
- package/web-ui/partials/index/panel-config-claude.html +57 -85
- package/web-ui/partials/index/panel-config-codex.html +60 -226
- package/web-ui/partials/index/panel-dashboard.html +0 -33
- package/web-ui/partials/index/panel-docs.html +21 -53
- package/web-ui/partials/index/panel-sessions.html +37 -20
- package/web-ui/partials/index/panel-trash.html +33 -38
- package/web-ui/partials/index/panel-usage.html +71 -304
- package/web-ui/styles/controls-forms.css +11 -0
- package/web-ui/styles/docs-panel.css +57 -83
- package/web-ui/styles/layout-shell.css +26 -24
- package/web-ui/styles/modals-core.css +33 -0
- package/web-ui/styles/responsive.css +5 -67
- package/web-ui/styles/sessions-list.css +274 -8
- package/web-ui/styles/sessions-toolbar-trash.css +185 -15
- package/web-ui/styles/sessions-usage.css +336 -788
|
@@ -55,6 +55,12 @@ function maskKeyLocal(key) {
|
|
|
55
55
|
return key.substring(0, 4) + '...' + key.substring(key.length - 4);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function maskKeyForEdit(key) {
|
|
59
|
+
if (!key) return '';
|
|
60
|
+
if (key.length <= 12) return key.substring(0, 4) + '...' + key.substring(key.length - 4);
|
|
61
|
+
return key.substring(0, 8) + '...' + key.substring(key.length - 4);
|
|
62
|
+
}
|
|
63
|
+
|
|
58
64
|
function getProviderValidationForContext(vm, mode = 'add') {
|
|
59
65
|
const draft = mode === 'edit' ? vm.editingProvider : vm.newProvider;
|
|
60
66
|
const editingName = mode === 'edit' ? normalizeText(draft && draft.name) : '';
|
|
@@ -286,11 +292,15 @@ export function createProvidersMethods(options = {}) {
|
|
|
286
292
|
},
|
|
287
293
|
|
|
288
294
|
openCloneProviderModal(provider) {
|
|
295
|
+
const isTransform = !!(provider.codexmate_bridge || '').trim() || /\/bridge\/openai\//.test(provider.url || '');
|
|
296
|
+
const cloneUrl = isTransform && provider.upstreamUrl
|
|
297
|
+
? normalizeProviderUrl(provider.upstreamUrl)
|
|
298
|
+
: normalizeProviderUrl(provider.url || '');
|
|
289
299
|
this.newProvider = {
|
|
290
300
|
name: '',
|
|
291
|
-
url:
|
|
301
|
+
url: cloneUrl,
|
|
292
302
|
key: '',
|
|
293
|
-
useTransform:
|
|
303
|
+
useTransform: isTransform
|
|
294
304
|
};
|
|
295
305
|
this.showAddModal = true;
|
|
296
306
|
},
|
|
@@ -312,15 +322,39 @@ export function createProvidersMethods(options = {}) {
|
|
|
312
322
|
this.editingProvider = {
|
|
313
323
|
name: provider.name,
|
|
314
324
|
url: normalizeProviderUrl(provider.url || ''),
|
|
315
|
-
key: '',
|
|
325
|
+
key: maskKeyForEdit(provider.key || ''),
|
|
316
326
|
readOnly: !!provider.readOnly,
|
|
317
327
|
nonEditable: typeof provider.nonEditable === 'boolean'
|
|
318
328
|
? provider.nonEditable
|
|
319
329
|
: this.isNonDeletableProvider(provider),
|
|
320
330
|
useTransform: isTransformProvider
|
|
321
331
|
};
|
|
332
|
+
this._editProviderOriginalKey = '';
|
|
333
|
+
this._editProviderRealKeyLoaded = false;
|
|
334
|
+
this.showEditProviderKey = false;
|
|
322
335
|
this.showEditModal = true;
|
|
323
336
|
|
|
337
|
+
// 后台加载真实密钥
|
|
338
|
+
try {
|
|
339
|
+
const res = await api('get-provider-key', { name: provider.name });
|
|
340
|
+
if (
|
|
341
|
+
this._openEditModalRequestId === requestId
|
|
342
|
+
&& this.showEditModal
|
|
343
|
+
&& this.editingProvider
|
|
344
|
+
&& this.editingProvider.name === provider.name
|
|
345
|
+
&& res && !res.error
|
|
346
|
+
) {
|
|
347
|
+
this._editProviderOriginalKey = typeof res.key === 'string' ? res.key : '';
|
|
348
|
+
this._editProviderRealKeyLoaded = true;
|
|
349
|
+
// 如果用户未修改输入框,替换为真实密钥
|
|
350
|
+
if (this.editingProvider.key === maskKeyForEdit(provider.key || '')) {
|
|
351
|
+
this.editingProvider.key = this._editProviderOriginalKey;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
} catch (_) {
|
|
355
|
+
// ignore
|
|
356
|
+
}
|
|
357
|
+
|
|
324
358
|
if (isTransformProvider) {
|
|
325
359
|
try {
|
|
326
360
|
const res = await api('openai-bridge-get-provider', { name: provider.name });
|
|
@@ -357,8 +391,12 @@ export function createProvidersMethods(options = {}) {
|
|
|
357
391
|
if (this.editingProvider && this.editingProvider.useTransform) {
|
|
358
392
|
params.useTransform = true;
|
|
359
393
|
}
|
|
360
|
-
if (
|
|
361
|
-
|
|
394
|
+
if (this._editProviderRealKeyLoaded) {
|
|
395
|
+
const currentKey = typeof this.editingProvider.key === 'string' ? this.editingProvider.key : '';
|
|
396
|
+
const originalKey = typeof this._editProviderOriginalKey === 'string' ? this._editProviderOriginalKey : '';
|
|
397
|
+
if (currentKey !== originalKey) {
|
|
398
|
+
params.key = currentKey;
|
|
399
|
+
}
|
|
362
400
|
}
|
|
363
401
|
try {
|
|
364
402
|
const res = await api('update-provider', params);
|
|
@@ -370,11 +408,12 @@ export function createProvidersMethods(options = {}) {
|
|
|
370
408
|
// 本地更新:更新列表中对应 provider 的 url 和 key
|
|
371
409
|
this.providersList = this.providersList.map(p => {
|
|
372
410
|
if (p.name === validation.name) {
|
|
411
|
+
const keyUpdated = typeof params.key === 'string';
|
|
373
412
|
return {
|
|
374
413
|
...p,
|
|
375
414
|
url: validation.url,
|
|
376
|
-
key:
|
|
377
|
-
hasKey: params.key
|
|
415
|
+
key: keyUpdated ? maskKeyLocal(params.key) : p.key,
|
|
416
|
+
hasKey: keyUpdated ? !!params.key : p.hasKey
|
|
378
417
|
};
|
|
379
418
|
}
|
|
380
419
|
return p;
|
|
@@ -389,9 +428,16 @@ export function createProvidersMethods(options = {}) {
|
|
|
389
428
|
|
|
390
429
|
closeEditModal() {
|
|
391
430
|
this.showEditModal = false;
|
|
431
|
+
this.showEditProviderKey = false;
|
|
432
|
+
this._editProviderOriginalKey = '';
|
|
433
|
+
this._editProviderRealKeyLoaded = false;
|
|
392
434
|
this.editingProvider = { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false };
|
|
393
435
|
},
|
|
394
436
|
|
|
437
|
+
toggleEditProviderKey() {
|
|
438
|
+
this.showEditProviderKey = !this.showEditProviderKey;
|
|
439
|
+
},
|
|
440
|
+
|
|
395
441
|
async resetConfig() {
|
|
396
442
|
if (this.resetConfigLoading) return;
|
|
397
443
|
this.resetConfigLoading = true;
|
|
@@ -203,7 +203,7 @@ export function createSessionActionMethods(options = {}) {
|
|
|
203
203
|
quoteShellArg(value) {
|
|
204
204
|
const text = typeof value === 'string' ? value : String(value || '');
|
|
205
205
|
if (!text) return "''";
|
|
206
|
-
if (/^[a-zA-Z0-9._
|
|
206
|
+
if (/^[a-zA-Z0-9._/:@~+=-]+$/.test(text)) return text;
|
|
207
207
|
const escaped = text.replace(/'/g, "'\\''");
|
|
208
208
|
return `'${escaped}'`;
|
|
209
209
|
},
|
|
@@ -466,6 +466,29 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
466
466
|
this.persistSessionPinnedMap();
|
|
467
467
|
},
|
|
468
468
|
|
|
469
|
+
setSessionSource(value) {
|
|
470
|
+
if (this.sessionsLoading) return;
|
|
471
|
+
this.sessionFilterSource = value;
|
|
472
|
+
this.refreshSessionPathOptions(value);
|
|
473
|
+
this.persistSessionFilterCache();
|
|
474
|
+
syncSessionsFilterUrl(this);
|
|
475
|
+
this.loadSessions();
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
highlightQueryText(text) {
|
|
479
|
+
if (typeof text !== 'string' || !text) return text;
|
|
480
|
+
var tokens = this.queryTokens;
|
|
481
|
+
if (!tokens || tokens.length === 0) return text;
|
|
482
|
+
var result = text;
|
|
483
|
+
for (var i = 0; i < tokens.length; i++) {
|
|
484
|
+
var token = tokens[i];
|
|
485
|
+
var escaped = token.replace(/[.*+?^${}()|[\]\\]/g, '\\ async onSessionSourceChange(event) {');
|
|
486
|
+
var re = new RegExp('(' + escaped + ')', 'gi');
|
|
487
|
+
result = result.replace(re, '<mark>$1</mark>');
|
|
488
|
+
}
|
|
489
|
+
return result;
|
|
490
|
+
},
|
|
491
|
+
|
|
469
492
|
async onSessionSourceChange(event) {
|
|
470
493
|
const rawValue = event && event.target && typeof event.target.value === 'string'
|
|
471
494
|
? event.target.value
|
|
@@ -817,7 +840,11 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
817
840
|
? Math.max(1, Math.min(rawLimit, 2000))
|
|
818
841
|
: compareBoost;
|
|
819
842
|
const loadedLimit = Number(this.sessionsUsageLoadedLimit || 0);
|
|
820
|
-
|
|
843
|
+
const lastRange = typeof this.sessionsUsageLastLoadedRange === 'string'
|
|
844
|
+
? this.sessionsUsageLastLoadedRange
|
|
845
|
+
: '';
|
|
846
|
+
const rangeChanged = lastRange && lastRange !== range;
|
|
847
|
+
if (this.sessionsUsageLoadedOnce && !options.forceRefresh && !rangeChanged && loadedLimit >= limit) {
|
|
821
848
|
return;
|
|
822
849
|
}
|
|
823
850
|
this.sessionsUsageLoading = true;
|
|
@@ -844,6 +871,7 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
844
871
|
if (loadSucceeded) {
|
|
845
872
|
this.sessionsUsageLoadedOnce = true;
|
|
846
873
|
this.sessionsUsageLoadedLimit = limit;
|
|
874
|
+
this.sessionsUsageLastLoadedRange = range;
|
|
847
875
|
if (!this.sessionsUsageSelectedDayKey && Array.isArray(this.sessionUsageDailyTableRows) && this.sessionUsageDailyTableRows.length > 0) {
|
|
848
876
|
this.sessionsUsageSelectedDayKey = this.sessionUsageDailyTableRows[0].key;
|
|
849
877
|
}
|
|
@@ -60,6 +60,9 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
60
60
|
return false;
|
|
61
61
|
}
|
|
62
62
|
this.currentProvider = statusRes.provider;
|
|
63
|
+
if (statusRes.version) {
|
|
64
|
+
this.appVersion = statusRes.version;
|
|
65
|
+
}
|
|
63
66
|
this.currentModels = statusRes.currentModels && typeof statusRes.currentModels === 'object'
|
|
64
67
|
? { ...statusRes.currentModels }
|
|
65
68
|
: {};
|
|
@@ -119,6 +122,7 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
119
122
|
}
|
|
120
123
|
this.providersList = listRes.providers;
|
|
121
124
|
if (typeof this.loadLocalBridgeExcluded === 'function') { this.loadLocalBridgeExcluded(); }
|
|
125
|
+
if (typeof this.loadClaudeLocalBridgeStatus === 'function') { this.loadClaudeLocalBridgeStatus(); }
|
|
122
126
|
if (statusRes.configReady === false) {
|
|
123
127
|
this.showMessage('配置已加载', 'info');
|
|
124
128
|
}
|
|
@@ -9,6 +9,7 @@ const DICT = Object.freeze({
|
|
|
9
9
|
// Common
|
|
10
10
|
'common.all': '全部',
|
|
11
11
|
'common.copy': '复制',
|
|
12
|
+
'common.paste': '粘贴',
|
|
12
13
|
'common.edit': '编辑',
|
|
13
14
|
'common.install': '安装',
|
|
14
15
|
'common.update': '升级',
|
|
@@ -1036,11 +1037,16 @@ const DICT = Object.freeze({
|
|
|
1036
1037
|
'claude.notConfigured': '未配置',
|
|
1037
1038
|
'claude.action.edit': '编辑',
|
|
1038
1039
|
'claude.action.delete': '删除',
|
|
1039
|
-
'claude.action.shareDisabled': '
|
|
1040
|
+
'claude.action.shareDisabled': '分享导入命令',
|
|
1040
1041
|
'claude.action.editAria': '编辑 Claude 配置:{name}',
|
|
1041
1042
|
'claude.action.deleteAria': '删除 Claude 配置:{name}',
|
|
1042
1043
|
'claude.action.clone': '克隆',
|
|
1043
1044
|
'claude.action.cloneAria': '克隆 Claude 配置:{name}',
|
|
1045
|
+
'claude.localBridge.poolTitle': '轮询池',
|
|
1046
|
+
'claude.localBridge.poolHint': '勾选参与负载均衡的提供商',
|
|
1047
|
+
'claude.localBridge.noProviders': '暂无可用提供商,请先添加直连提供商',
|
|
1048
|
+
'claude.localBridge.disabled': '未启用',
|
|
1049
|
+
'claude.localBridge.enabled': '已启用',
|
|
1044
1050
|
|
|
1045
1051
|
// OpenClaw config panel
|
|
1046
1052
|
'openclaw.applyHint': '写入 ~/.openclaw/openclaw.json,支持 JSON5。',
|
|
@@ -1071,6 +1077,7 @@ const DICT = Object.freeze({
|
|
|
1071
1077
|
// Common
|
|
1072
1078
|
'common.all': 'すべて',
|
|
1073
1079
|
'common.copy': 'コピー',
|
|
1080
|
+
'common.paste': 'ペースト',
|
|
1074
1081
|
'common.edit': '編集',
|
|
1075
1082
|
'common.install': 'インストール',
|
|
1076
1083
|
'common.update': 'アップグレード',
|
|
@@ -2085,11 +2092,16 @@ const DICT = Object.freeze({
|
|
|
2085
2092
|
'claude.notConfigured': '未設定',
|
|
2086
2093
|
'claude.action.edit': '編集',
|
|
2087
2094
|
'claude.action.delete': '削除',
|
|
2088
|
-
'claude.action.shareDisabled': '
|
|
2095
|
+
'claude.action.shareDisabled': 'インポートコマンド共有',
|
|
2089
2096
|
'claude.action.editAria': 'Claude 設定を編集:{name}',
|
|
2090
2097
|
'claude.action.deleteAria': 'Claude 設定を削除:{name}',
|
|
2091
2098
|
'claude.action.clone': 'クローン',
|
|
2092
2099
|
'claude.action.cloneAria': 'Claude 設定をクローン:{name}',
|
|
2100
|
+
'claude.localBridge.poolTitle': 'ラウンドロビンプール',
|
|
2101
|
+
'claude.localBridge.poolHint': '負荷分散に参加するプロバイダを選択',
|
|
2102
|
+
'claude.localBridge.noProviders': '利用可能なプロバイダがありません。まずプロバイダを追加してください。',
|
|
2103
|
+
'claude.localBridge.disabled': '無効',
|
|
2104
|
+
'claude.localBridge.enabled': '有効',
|
|
2093
2105
|
|
|
2094
2106
|
// OpenClaw config panel
|
|
2095
2107
|
'openclaw.applyHint': '~/.openclaw/openclaw.json に書き込みます。JSON5 対応。',
|
|
@@ -2121,6 +2133,7 @@ const DICT = Object.freeze({
|
|
|
2121
2133
|
// Common
|
|
2122
2134
|
'common.all': 'All',
|
|
2123
2135
|
'common.copy': 'Copy',
|
|
2136
|
+
'common.paste': 'Paste',
|
|
2124
2137
|
'common.edit': 'Edit',
|
|
2125
2138
|
'common.install': 'Install',
|
|
2126
2139
|
'common.update': 'Update',
|
|
@@ -3144,11 +3157,16 @@ const DICT = Object.freeze({
|
|
|
3144
3157
|
'claude.notConfigured': 'Not configured',
|
|
3145
3158
|
'claude.action.edit': 'Edit',
|
|
3146
3159
|
'claude.action.delete': 'Delete',
|
|
3147
|
-
'claude.action.shareDisabled': 'Share import command
|
|
3160
|
+
'claude.action.shareDisabled': 'Share import command',
|
|
3148
3161
|
'claude.action.editAria': 'Edit Claude config: {name}',
|
|
3149
3162
|
'claude.action.deleteAria': 'Delete Claude config: {name}',
|
|
3150
3163
|
'claude.action.clone': 'Clone',
|
|
3151
3164
|
'claude.action.cloneAria': 'Clone Claude config: {name}',
|
|
3165
|
+
'claude.localBridge.poolTitle': 'Round-robin pool',
|
|
3166
|
+
'claude.localBridge.poolHint': 'Select providers for load balancing',
|
|
3167
|
+
'claude.localBridge.noProviders': 'No providers available. Add a provider first.',
|
|
3168
|
+
'claude.localBridge.disabled': 'Disabled',
|
|
3169
|
+
'claude.localBridge.enabled': 'Enabled',
|
|
3152
3170
|
|
|
3153
3171
|
// OpenClaw config panel
|
|
3154
3172
|
'openclaw.applyHint': 'Writes to ~/.openclaw/openclaw.json (JSON5 supported).',
|
|
@@ -122,8 +122,7 @@
|
|
|
122
122
|
<div class="brand-head">
|
|
123
123
|
<img class="brand-logo" src="/res/logo-pack.webp" alt="Codex Mate logo">
|
|
124
124
|
<div class="brand-copy">
|
|
125
|
-
<div class="brand-kicker">{{
|
|
126
|
-
<div class="brand-title">Codex Mate</div>
|
|
125
|
+
<div class="brand-kicker">Codex Mate <span v-if="appVersion" class="brand-version">v{{ appVersion }}</span></div>
|
|
127
126
|
</div>
|
|
128
127
|
</div>
|
|
129
128
|
<div class="brand-subtitle">{{ t('brand.subtitle.localConfigSessionsWorkspace') }}</div>
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<div v-if="showConfigTemplateModal" class="modal-overlay" @click.self="!configTemplateApplying && closeConfigTemplateModal()">
|
|
2
2
|
<div class="modal modal-wide" role="dialog" aria-modal="true" aria-labelledby="config-template-modal-title">
|
|
3
|
-
<div class="modal-
|
|
3
|
+
<div class="modal-header modal-editor-header">
|
|
4
|
+
<div class="modal-title" id="config-template-modal-title">{{ t('modal.configTemplate.title') }}</div>
|
|
5
|
+
<div class="modal-header-actions">
|
|
6
|
+
<button class="btn-mini btn-modal-copy" @click="pasteConfigTemplateContent" :disabled="configTemplateApplying || configTemplateDiffLoading || configTemplateDiffVisible">{{ t('common.paste') }}</button>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
4
9
|
|
|
5
10
|
<div class="form-group">
|
|
6
11
|
<label class="form-label">{{ t('modal.configTemplate.label') }}</label>
|
|
@@ -91,6 +96,12 @@
|
|
|
91
96
|
:disabled="agentsLoading">
|
|
92
97
|
{{ t('modal.agents.copy') }}
|
|
93
98
|
</button>
|
|
99
|
+
<button
|
|
100
|
+
class="btn-mini btn-modal-copy"
|
|
101
|
+
@click="pasteAgentsContent"
|
|
102
|
+
:disabled="agentsLoading || agentsSaving || agentsDiffVisible">
|
|
103
|
+
{{ t('common.paste') }}
|
|
104
|
+
</button>
|
|
94
105
|
</div>
|
|
95
106
|
</div>
|
|
96
107
|
|
|
@@ -65,8 +65,13 @@
|
|
|
65
65
|
</div>
|
|
66
66
|
<div class="form-group">
|
|
67
67
|
<label class="form-label">{{ t('field.apiKey') }}</label>
|
|
68
|
-
<
|
|
69
|
-
|
|
68
|
+
<div class="input-with-toggle">
|
|
69
|
+
<input v-model="editingProvider.key" class="form-input" :type="showEditProviderKey ? 'text' : 'password'" placeholder="sk-..." autocomplete="off" spellcheck="false">
|
|
70
|
+
<button type="button" class="input-toggle-btn" @click="toggleEditProviderKey" :title="showEditProviderKey ? t('common.hide') : t('common.show')">
|
|
71
|
+
<svg v-if="!showEditProviderKey" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M10 4C5 4 1.73 8.11 1 10c.73 1.89 4 6 9 6s8.27-4.11 9-6c-.73-1.89-4-6-9-6z"/><circle cx="10" cy="10" r="3"/></svg>
|
|
72
|
+
<svg v-else viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M2 2l16 16M8.2 4.2A9.9 9.9 0 0 1 10 4c5 0 8.27 4.11 9 6-.44.94-1.5 2.7-3.2 4.2M14.5 14.5A5.9 5.9 0 0 1 10 16c-5 0-8.27-4.11-9-6 .76-1.66 2.2-3.6 4.3-5"/></svg>
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
70
75
|
</div>
|
|
71
76
|
|
|
72
77
|
<div class="btn-group">
|
|
@@ -147,7 +152,13 @@
|
|
|
147
152
|
</div>
|
|
148
153
|
<div class="form-group">
|
|
149
154
|
<label class="form-label">API Key</label>
|
|
150
|
-
<
|
|
155
|
+
<div class="input-with-toggle">
|
|
156
|
+
<input v-model="editingConfig.apiKey" class="form-input" :type="showEditClaudeConfigKey ? 'text' : 'password'" autocomplete="off" spellcheck="false" :placeholder="t('placeholder.apiKeyExampleClaude')">
|
|
157
|
+
<button type="button" class="input-toggle-btn" @click="toggleEditClaudeConfigKey" :title="showEditClaudeConfigKey ? t('common.hide') : t('common.show')">
|
|
158
|
+
<svg v-if="!showEditClaudeConfigKey" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M10 4C5 4 1.73 8.11 1 10c.73 1.89 4 6 9 6s8.27-4.11 9-6c-.73-1.89-4-6-9-6z"/><circle cx="10" cy="10" r="3"/></svg>
|
|
159
|
+
<svg v-else viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M2 2l16 16M8.2 4.2A9.9 9.9 0 0 1 10 4c5 0 8.27 4.11 9 6-.44.94-1.5 2.7-3.2 4.2M14.5 14.5A5.9 5.9 0 0 1 10 16c-5 0-8.27-4.11-9-6 .76-1.66 2.2-3.6 4.3-5"/></svg>
|
|
160
|
+
</button>
|
|
161
|
+
</div>
|
|
151
162
|
</div>
|
|
152
163
|
<div class="form-group">
|
|
153
164
|
<label class="form-label">{{ t('field.baseUrl') }}</label>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- Claude Code
|
|
1
|
+
<!-- Claude Code 配置 -->
|
|
2
2
|
<div
|
|
3
3
|
v-show="mainTab === 'config' && configMode === 'claude'"
|
|
4
4
|
class="mode-content mode-cards"
|
|
@@ -18,11 +18,7 @@
|
|
|
18
18
|
<div class="docs-command-row">
|
|
19
19
|
<div class="docs-command-box" role="group" :aria-label="t('cli.missing.commandAria', { name: 'Claude' })">
|
|
20
20
|
<code class="install-command">{{ getInstallCommand('claude', 'install') }}</code>
|
|
21
|
-
<button
|
|
22
|
-
type="button"
|
|
23
|
-
class="btn-mini docs-copy-btn"
|
|
24
|
-
:disabled="!getInstallCommand('claude', 'install')"
|
|
25
|
-
@click="copyInstallCommand(getInstallCommand('claude', 'install'))">{{ t('common.copy') }}</button>
|
|
21
|
+
<button type="button" class="btn-mini docs-copy-btn" :disabled="!getInstallCommand('claude', 'install')" @click="copyInstallCommand(getInstallCommand('claude', 'install'))">{{ t('common.copy') }}</button>
|
|
26
22
|
</div>
|
|
27
23
|
</div>
|
|
28
24
|
<button type="button" class="btn-tool btn-tool-compact" @click="mainTab = 'docs'; setInstallCommandAction('install')">{{ t('cli.missing.openDocs') }}</button>
|
|
@@ -30,21 +26,14 @@
|
|
|
30
26
|
</div>
|
|
31
27
|
</template>
|
|
32
28
|
<template v-else>
|
|
33
|
-
<!-- 添加提供商按钮 -->
|
|
34
29
|
<button class="btn-add" @click="openClaudeConfigModal" v-if="!loading && !initError">
|
|
35
|
-
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
|
|
36
|
-
<path d="M10 4v12M4 10h12"/>
|
|
37
|
-
</svg>
|
|
30
|
+
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 4v12M4 10h12"/></svg>
|
|
38
31
|
{{ t('claude.addProvider') }}
|
|
39
32
|
</button>
|
|
40
|
-
<div class="config-template-hint">
|
|
41
|
-
{{ t('claude.applyDefault') }}
|
|
42
|
-
</div>
|
|
33
|
+
<div class="config-template-hint">{{ t('claude.applyDefault') }}</div>
|
|
43
34
|
|
|
44
35
|
<div class="selector-section">
|
|
45
|
-
<div class="selector-header">
|
|
46
|
-
<span class="selector-title">{{ t('claude.presetProviders') }}</span>
|
|
47
|
-
</div>
|
|
36
|
+
<div class="selector-header"><span class="selector-title">{{ t('claude.presetProviders') }}</span></div>
|
|
48
37
|
<div class="btn-group" style="flex-wrap: wrap; gap: 8px; margin-top: 0;">
|
|
49
38
|
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Claude Official'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.anthropic.com'; newClaudeConfig.model = 'claude-sonnet-4'; showClaudeConfigModal = true">Claude Official</button>
|
|
50
39
|
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'DeepSeek'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.deepseek.com/anthropic'; newClaudeConfig.model = 'DeepSeek-V3.2'; showClaudeConfigModal = true">DeepSeek</button>
|
|
@@ -70,9 +59,7 @@
|
|
|
70
59
|
</div>
|
|
71
60
|
|
|
72
61
|
<div class="selector-section">
|
|
73
|
-
<div class="selector-header">
|
|
74
|
-
<span class="selector-title">{{ t('claude.model') }}</span>
|
|
75
|
-
</div>
|
|
62
|
+
<div class="selector-header"><span class="selector-title">{{ t('claude.model') }}</span></div>
|
|
76
63
|
<input
|
|
77
64
|
v-if="claudeModelHasList"
|
|
78
65
|
class="model-input"
|
|
@@ -94,57 +81,37 @@
|
|
|
94
81
|
@keyup.enter="onClaudeModelChange"
|
|
95
82
|
:placeholder="t('claude.model.placeholder')"
|
|
96
83
|
>
|
|
97
|
-
<div class="config-template-hint">
|
|
98
|
-
{{ t('claude.model.hint') }}
|
|
99
|
-
</div>
|
|
84
|
+
<div class="config-template-hint">{{ t('claude.model.hint') }}</div>
|
|
100
85
|
</div>
|
|
101
86
|
|
|
102
87
|
<div class="selector-section">
|
|
103
|
-
<div class="selector-header">
|
|
104
|
-
|
|
105
|
-
</div>
|
|
106
|
-
<button class="btn-tool" @click="openClaudeMdEditor" :disabled="loading || !!initError || agentsLoading">
|
|
107
|
-
{{ agentsLoading ? t('config.modelLoading') : t('claude.md.open') }}
|
|
108
|
-
</button>
|
|
109
|
-
<div class="config-template-hint">
|
|
110
|
-
{{ t('claude.md.hint') }}
|
|
111
|
-
</div>
|
|
88
|
+
<div class="selector-header"><span class="selector-title">CLAUDE.md</span></div>
|
|
89
|
+
<button class="btn-tool" @click="openClaudeMdEditor" :disabled="loading || !!initError || agentsLoading">{{ agentsLoading ? t('config.modelLoading') : t('claude.md.open') }}</button>
|
|
90
|
+
<div class="config-template-hint">{{ t('claude.md.hint') }}</div>
|
|
112
91
|
</div>
|
|
113
92
|
|
|
114
93
|
<div class="selector-section">
|
|
115
|
-
<div class="selector-header">
|
|
116
|
-
|
|
117
|
-
</div>
|
|
118
|
-
<button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">
|
|
119
|
-
{{ healthCheckLoading ? t('claude.health.running') : t('claude.health.run') }}
|
|
120
|
-
</button>
|
|
121
|
-
<div class="config-template-hint">{{ t('claude.health.hint') }}</div>
|
|
122
|
-
<div v-if="healthCheckLoading && healthCheckBatchTotal" class="config-template-hint">
|
|
123
|
-
{{ t('claude.health.progress', { done: healthCheckBatchDone, total: healthCheckBatchTotal, failed: healthCheckBatchFailed }) }}
|
|
124
|
-
</div>
|
|
125
|
-
<div v-if="healthCheckResult && !healthCheckLoading" class="config-template-hint">
|
|
126
|
-
{{ healthCheckResult.ok ? t('config.health.ok') : t('config.health.fail') }} · {{ t('config.health.issues', { count: (healthCheckResult.issues || []).length }) }}
|
|
127
|
-
</div>
|
|
128
|
-
<button v-if="healthCheckResult && !healthCheckLoading" type="button" class="btn-mini" @click="showHealthCheckModal = true">
|
|
129
|
-
{{ t('common.detail') }}
|
|
130
|
-
</button>
|
|
131
|
-
<div v-if="healthCheckResult && !healthCheckLoading && (healthCheckResult.issues || []).length">
|
|
132
|
-
<div v-for="(issue, index) in healthCheckResult.issues" :key="issue.code || ('issue-' + index)" class="config-template-hint">
|
|
133
|
-
{{ issue.message || issue.code || '' }}<span v-if="issue.suggestion"> · {{ issue.suggestion }}</span>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
94
|
+
<div class="selector-header"><span class="selector-title">{{ t('config.health.title') }}</span></div>
|
|
95
|
+
<button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">{{ healthCheckLoading ? t('config.health.running') : t('config.health.run') }}</button>
|
|
96
|
+
<div class="config-template-hint">{{ t('config.health.hint') }}</div>
|
|
136
97
|
</div>
|
|
137
98
|
|
|
138
|
-
|
|
139
99
|
<div class="card-list">
|
|
140
|
-
<div
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
100
|
+
<div :class="['card', { active: currentClaudeConfig === 'claude-local' }]" @click="currentClaudeConfig = 'claude-local'" @keydown.enter.self.prevent="currentClaudeConfig = 'claude-local'" @keydown.space.self.prevent="currentClaudeConfig = 'claude-local'" tabindex="0" role="button" :aria-current="currentClaudeConfig === 'claude-local' ? 'true' : null">
|
|
101
|
+
<div class="card-leading">
|
|
102
|
+
<div class="card-icon">L</div>
|
|
103
|
+
<div class="card-content">
|
|
104
|
+
<div class="card-title">
|
|
105
|
+
<span>local</span>
|
|
106
|
+
<span class="provider-readonly-badge">{{ t('config.badge.system') }}</span>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="card-trailing">
|
|
111
|
+
<span :class="['pill', claudeLocalBridgeConfigured() ? 'configured' : 'empty']">{{ claudeLocalBridgeConfigured() ? t('claude.configured') : t('claude.notConfigured') }}</span>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
<div v-for="(config, name) in claudeConfigs" :key="name" :class="['card', { active: currentClaudeConfig === name }]" @click="applyClaudeConfig(name)" @keydown.enter.self.prevent="applyClaudeConfig(name)" @keydown.space.self.prevent="applyClaudeConfig(name)" tabindex="0" role="button" :aria-current="currentClaudeConfig === name ? 'true' : null">
|
|
148
115
|
<div class="card-leading">
|
|
149
116
|
<div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
|
|
150
117
|
<div class="card-content">
|
|
@@ -154,41 +121,46 @@
|
|
|
154
121
|
</div>
|
|
155
122
|
</div>
|
|
156
123
|
<div class="card-trailing">
|
|
157
|
-
<span v-if="claudeSpeedResults[name]" :class="['latency', claudeSpeedResults[name].ok ? 'ok' : 'error']">
|
|
158
|
-
|
|
159
|
-
</span>
|
|
160
|
-
<span :class="['pill', config.hasKey ? 'configured' : 'empty']">
|
|
161
|
-
{{ config.hasKey ? t('claude.configured') : t('claude.notConfigured') }}
|
|
162
|
-
</span>
|
|
124
|
+
<span v-if="claudeSpeedResults[name]" :class="['latency', claudeSpeedResults[name].ok ? 'ok' : 'error']">{{ formatLatency(claudeSpeedResults[name]) }}</span>
|
|
125
|
+
<span :class="['pill', config.hasKey ? 'configured' : 'empty']">{{ config.hasKey ? t('claude.configured') : t('claude.notConfigured') }}</span>
|
|
163
126
|
<div class="card-actions" @click.stop>
|
|
164
127
|
<button class="card-action-btn" @click="openEditConfigModal(name)" :aria-label="t('claude.action.editAria', { name })" :title="t('claude.action.edit')">
|
|
165
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
166
|
-
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
167
|
-
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
168
|
-
</svg>
|
|
128
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
|
169
129
|
</button>
|
|
170
130
|
<button class="card-action-btn" @click="openCloneClaudeConfigModal(name, config)" :aria-label="t('claude.action.cloneAria', { name })" :title="t('claude.action.clone')">
|
|
171
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
172
|
-
<rect x="9" y="9" width="13" height="13" rx="2"/>
|
|
173
|
-
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
|
174
|
-
</svg>
|
|
131
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
175
132
|
</button>
|
|
176
|
-
<button class="card-action-btn" :class="{ loading: claudeShareLoading[name] }" @click="copyClaudeShareCommand(name)"
|
|
177
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
178
|
-
<path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
|
|
179
|
-
<path d="M16 6l-4-4-4 4"/>
|
|
180
|
-
<path d="M12 2v14"/>
|
|
181
|
-
</svg>
|
|
133
|
+
<button class="card-action-btn" :class="{ loading: claudeShareLoading[name] }" @click="copyClaudeShareCommand(name)" :title="t('config.shareCommand')" :aria-label="t('config.shareCommand.aria')">
|
|
134
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/><path d="M16 6l-4-4-4 4"/><path d="M12 2v14"/></svg>
|
|
182
135
|
</button>
|
|
183
136
|
<button class="card-action-btn delete" @click="deleteClaudeConfig(name)" :aria-label="t('claude.action.deleteAria', { name })" :title="t('claude.action.delete')">
|
|
184
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
185
|
-
<path d="M3 6h18"/>
|
|
186
|
-
<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"/>
|
|
187
|
-
</svg>
|
|
137
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><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"/></svg>
|
|
188
138
|
</button>
|
|
189
139
|
</div>
|
|
190
140
|
</div>
|
|
191
141
|
</div>
|
|
192
142
|
</div>
|
|
143
|
+
|
|
144
|
+
<div v-if="currentClaudeConfig === 'claude-local'" class="bridge-pool-panel">
|
|
145
|
+
<div class="bridge-pool-header">
|
|
146
|
+
<span class="bridge-pool-icon">
|
|
147
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><circle cx="6" cy="6" r="2"/><circle cx="18" cy="6" r="2"/><circle cx="12" cy="18" r="2"/><path d="M6 8v4h6v4"/><path d="M18 8v4h-6v4"/></svg>
|
|
148
|
+
</span>
|
|
149
|
+
<span class="bridge-pool-title">{{ t('claude.localBridge.poolTitle') }}</span>
|
|
150
|
+
<span class="bridge-pool-hint">{{ t('claude.localBridge.poolHint') }}</span>
|
|
151
|
+
</div>
|
|
152
|
+
<div v-if="Object.keys(claudeConfigs || {}).length === 0" class="bridge-pool-empty">
|
|
153
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>
|
|
154
|
+
<span>{{ t('claude.localBridge.noProviders') }}</span>
|
|
155
|
+
</div>
|
|
156
|
+
<div v-else class="bridge-pool-list">
|
|
157
|
+
<label v-for="(config, name) in claudeConfigs" :key="name" class="bridge-pool-item">
|
|
158
|
+
<span class="bridge-pool-item-name">{{ name }}</span>
|
|
159
|
+
<span class="bridge-pool-item-status" :class="{ active: !isClaudeLocalBridgeExcluded(name) }">{{ isClaudeLocalBridgeExcluded(name) ? t('claude.localBridge.disabled') : t('claude.localBridge.enabled') }}</span>
|
|
160
|
+
<input type="checkbox" :checked="!isClaudeLocalBridgeExcluded(name)" @change="toggleClaudeLocalBridgeExcluded(name)" />
|
|
161
|
+
</label>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
193
165
|
</template>
|
|
194
166
|
</div>
|