codexmate 0.0.33 → 0.0.36
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/cli/agents-files.js +6 -0
- package/cli/archive-helpers.js +11 -4
- package/cli/local-bridge.js +9 -4
- package/cli/openai-bridge.js +1 -1
- package/cli/update.js +11 -2
- package/cli.js +133 -64
- package/lib/cli-webhook.js +29 -1
- package/package.json +2 -1
- package/web-ui/app.js +37 -2
- package/web-ui/index.html +2 -1
- package/web-ui/logic.claude.mjs +4 -0
- package/web-ui/logic.sessions.mjs +6 -5
- package/web-ui/modules/app.computed.dashboard.mjs +4 -0
- package/web-ui/modules/app.computed.session.mjs +147 -6
- package/web-ui/modules/app.methods.claude-config.mjs +41 -0
- package/web-ui/modules/app.methods.codex-config.mjs +11 -3
- package/web-ui/modules/app.methods.navigation.mjs +32 -2
- package/web-ui/modules/app.methods.session-browser.mjs +12 -6
- package/web-ui/modules/app.methods.session-trash.mjs +30 -0
- package/web-ui/modules/app.methods.startup-claude.mjs +9 -0
- package/web-ui/modules/app.methods.webhook.mjs +8 -0
- package/web-ui/modules/i18n.dict.mjs +8 -0
- package/web-ui/modules/sessions-filters-url.mjs +65 -12
- package/web-ui/modules/skills.methods.mjs +31 -0
- package/web-ui/partials/index/layout-header.html +17 -12
- package/web-ui/partials/index/modal-webhook.html +42 -0
- package/web-ui/partials/index/modals-basic.html +50 -0
- package/web-ui/partials/index/panel-config-claude.html +13 -22
- package/web-ui/partials/index/panel-config-codex.html +8 -22
- package/web-ui/partials/index/panel-market.html +76 -149
- package/web-ui/partials/index/panel-sessions.html +2 -2
- package/web-ui/partials/index/panel-settings.html +119 -149
- package/web-ui/partials/index/panel-usage.html +115 -68
- package/web-ui/res/vue.runtime.global.prod.js +7 -0
- package/web-ui/res/web-ui-render.precompiled.js +7274 -0
- package/web-ui/session-helpers.mjs +15 -4
- package/web-ui/source-bundle.cjs +73 -1
- package/web-ui/styles/base-theme.css +10 -0
- package/web-ui/styles/bridge-pool.css +69 -0
- package/web-ui/styles/layout-shell.css +66 -27
- package/web-ui/styles/navigation-panels.css +8 -0
- package/web-ui/styles/responsive.css +50 -9
- package/web-ui/styles/sessions-usage.css +336 -319
- package/web-ui/styles/settings-panel.css +300 -234
- package/web-ui/styles/skills-market.css +294 -0
- package/web-ui/styles/titles-cards.css +14 -0
- package/web-ui/styles/webhook.css +38 -4
|
@@ -477,6 +477,37 @@ export function createSkillsMethods({ api }) {
|
|
|
477
477
|
this.skillsDeleting = false;
|
|
478
478
|
await this.scanImportableSkills({ silent: true });
|
|
479
479
|
}
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
openSkillsMenu() {
|
|
483
|
+
// Open skills manager modal as menu
|
|
484
|
+
this.openSkillsManager();
|
|
485
|
+
},
|
|
486
|
+
|
|
487
|
+
async importSingleSkill(skill) {
|
|
488
|
+
if (this.skillsImporting || this.skillsZipImporting) return;
|
|
489
|
+
const key = this.buildSkillImportKey(skill);
|
|
490
|
+
this.skillsImporting = true;
|
|
491
|
+
try {
|
|
492
|
+
const res = await api('import-skills', {
|
|
493
|
+
targetApp: this.skillsTargetApp,
|
|
494
|
+
imports: [skill]
|
|
495
|
+
});
|
|
496
|
+
if (res && res.error) {
|
|
497
|
+
this.showMessage(res.error, 'error');
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const imported = Array.isArray(res && res.imported) ? res.imported : [];
|
|
501
|
+
if (imported.length > 0) {
|
|
502
|
+
this.showMessage(`已导入 ${skill.displayName || skill.name}`, 'success');
|
|
503
|
+
await this.refreshSkillsList({ silent: true });
|
|
504
|
+
await this.scanImportableSkills({ silent: true });
|
|
505
|
+
}
|
|
506
|
+
} catch (e) {
|
|
507
|
+
this.showMessage('导入失败', 'error');
|
|
508
|
+
} finally {
|
|
509
|
+
this.skillsImporting = false;
|
|
510
|
+
}
|
|
480
511
|
}
|
|
481
512
|
};
|
|
482
513
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div id="app" class="container" v-cloak>
|
|
2
2
|
<div v-if="!sessionStandalone" class="top-tabs" role="tablist" :aria-label="t('nav.topTabs.aria')">
|
|
3
|
-
<button class="top-tab"
|
|
3
|
+
<button type="button" class="top-tab"
|
|
4
4
|
id="tab-dashboard"
|
|
5
5
|
role="tab"
|
|
6
6
|
data-main-tab="dashboard"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
:class="{ active: isMainTabNavActive('dashboard') }"
|
|
11
11
|
@pointerdown="onMainTabPointerDown('dashboard', $event)"
|
|
12
12
|
@click="onMainTabClick('dashboard', $event)">{{ t('tab.dashboard') }}</button>
|
|
13
|
-
<button class="top-tab"
|
|
13
|
+
<button type="button" class="top-tab"
|
|
14
14
|
id="tab-docs"
|
|
15
15
|
role="tab"
|
|
16
16
|
data-main-tab="docs"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
:class="{ active: isMainTabNavActive('docs') }"
|
|
21
21
|
@pointerdown="onMainTabPointerDown('docs', $event)"
|
|
22
22
|
@click="onMainTabClick('docs', $event)">{{ t('tab.docs') }}</button>
|
|
23
|
-
<button class="top-tab"
|
|
23
|
+
<button type="button" class="top-tab"
|
|
24
24
|
id="tab-config"
|
|
25
25
|
role="tab"
|
|
26
26
|
data-main-tab="config"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
:class="{ active: isMainTabNavActive('config') }"
|
|
32
32
|
@pointerdown="onMainTabPointerDown('config', $event)"
|
|
33
33
|
@click="onMainTabClick('config', $event)">{{ t('tab.config') }}</button>
|
|
34
|
-
<button class="top-tab"
|
|
34
|
+
<button type="button" class="top-tab"
|
|
35
35
|
id="tab-sessions"
|
|
36
36
|
role="tab"
|
|
37
37
|
data-main-tab="sessions"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
:class="{ active: isMainTabNavActive('sessions') }"
|
|
42
42
|
@pointerdown="onMainTabPointerDown('sessions', $event)"
|
|
43
43
|
@click="onMainTabClick('sessions', $event)">{{ t('tab.sessions') }}</button>
|
|
44
|
-
<button class="top-tab"
|
|
44
|
+
<button type="button" class="top-tab"
|
|
45
45
|
id="tab-usage"
|
|
46
46
|
role="tab"
|
|
47
47
|
data-main-tab="usage"
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
:class="{ active: isMainTabNavActive('usage') }"
|
|
52
52
|
@pointerdown="onMainTabPointerDown('usage', $event)"
|
|
53
53
|
@click="onMainTabClick('usage', $event)">{{ t('tab.usage') }}</button>
|
|
54
|
-
<button v-if="taskOrchestrationTabEnabled" class="top-tab"
|
|
54
|
+
<button v-if="taskOrchestrationTabEnabled" type="button" class="top-tab"
|
|
55
55
|
id="tab-orchestration"
|
|
56
56
|
role="tab"
|
|
57
57
|
data-main-tab="orchestration"
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
:class="{ active: isMainTabNavActive('orchestration') }"
|
|
62
62
|
@pointerdown="onMainTabPointerDown('orchestration', $event)"
|
|
63
63
|
@click="onMainTabClick('orchestration', $event)">{{ t('tab.orchestration') }}</button>
|
|
64
|
-
<button class="top-tab"
|
|
64
|
+
<button type="button" class="top-tab"
|
|
65
65
|
id="tab-market"
|
|
66
66
|
role="tab"
|
|
67
67
|
data-main-tab="market"
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
:class="{ active: isMainTabNavActive('market') }"
|
|
72
72
|
@pointerdown="onMainTabPointerDown('market', $event)"
|
|
73
73
|
@click="onMainTabClick('market', $event)">{{ t('tab.market') }}</button>
|
|
74
|
-
<button class="top-tab"
|
|
74
|
+
<button type="button" class="top-tab"
|
|
75
75
|
id="tab-plugins"
|
|
76
76
|
role="tab"
|
|
77
77
|
data-main-tab="plugins"
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
:class="{ active: isMainTabNavActive('plugins') }"
|
|
82
82
|
@pointerdown="onMainTabPointerDown('plugins', $event)"
|
|
83
83
|
@click="onMainTabClick('plugins', $event)">{{ t('tab.plugins') }}</button>
|
|
84
|
-
<button class="top-tab"
|
|
84
|
+
<button type="button" class="top-tab"
|
|
85
85
|
id="tab-settings"
|
|
86
86
|
role="tab"
|
|
87
87
|
data-main-tab="settings"
|
|
@@ -118,14 +118,13 @@
|
|
|
118
118
|
|
|
119
119
|
<div :class="['app-shell', { standalone: sessionStandalone }]">
|
|
120
120
|
<aside class="side-rail" v-if="!sessionStandalone">
|
|
121
|
-
<div class="brand-block">
|
|
121
|
+
<div class="brand-block" tabindex="0" @mouseenter="brandHovered = true" @mouseleave="brandHovered = false" @focus="brandHovered = true" @blur="brandHovered = false">
|
|
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">Codex Mate
|
|
125
|
+
<div class="brand-kicker">Codex Mate<transition name="brand-version-fade"><span v-if="appVersion && brandHovered" class="brand-version"> v{{ appVersion }}</span></transition></div>
|
|
126
126
|
</div>
|
|
127
127
|
</div>
|
|
128
|
-
<div class="brand-subtitle">{{ t('brand.subtitle.localConfigSessionsWorkspace') }}</div>
|
|
129
128
|
</div>
|
|
130
129
|
|
|
131
130
|
<div class="side-rail-nav">
|
|
@@ -479,6 +478,12 @@
|
|
|
479
478
|
<span class="value">{{ installRegistryPreview || t('common.defaultOfficial') }}</span>
|
|
480
479
|
</div>
|
|
481
480
|
</div>
|
|
481
|
+
<div class="status-strip status-strip-placeholder" v-else-if="!sessionStandalone" aria-hidden="true">
|
|
482
|
+
<div class="status-chip">
|
|
483
|
+
<span class="label"> </span>
|
|
484
|
+
<span class="value"> </span>
|
|
485
|
+
</div>
|
|
486
|
+
</div>
|
|
482
487
|
<div
|
|
483
488
|
v-if="!sessionStandalone && mainTab === 'config' && isProviderConfigMode && forceCompactLayout && !loading && !initError && displayProvidersList.length > 1"
|
|
484
489
|
class="provider-fast-switch">
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!-- Webhook 配置模态框 -->
|
|
2
|
+
<div v-if="showWebhookModal" class="modal-overlay" @click.self="closeWebhookModal">
|
|
3
|
+
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="webhook-modal-title">
|
|
4
|
+
<div class="modal-title" id="webhook-modal-title">Webhook 配置</div>
|
|
5
|
+
|
|
6
|
+
<div class="form-group">
|
|
7
|
+
<label class="form-label">启用状态</label>
|
|
8
|
+
<label class="settings-toggle">
|
|
9
|
+
<input type="checkbox" v-model="webhookConfig.enabled">
|
|
10
|
+
<span>启用 Webhook</span>
|
|
11
|
+
</label>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="form-group">
|
|
15
|
+
<label class="form-label">URL</label>
|
|
16
|
+
<input
|
|
17
|
+
v-model="webhookConfig.url"
|
|
18
|
+
class="form-input"
|
|
19
|
+
type="url"
|
|
20
|
+
placeholder="https://example.com/webhook"
|
|
21
|
+
autocomplete="off"
|
|
22
|
+
spellcheck="false">
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="form-group">
|
|
26
|
+
<label class="form-label">事件</label>
|
|
27
|
+
<div class="webhook-events-checkbox-list">
|
|
28
|
+
<label v-for="ev in webhookEventOptions" :key="ev" class="webhook-event-checkbox-item">
|
|
29
|
+
<input type="checkbox" :checked="webhookConfig.events.includes(ev)" @change="toggleWebhookEvent(ev)">
|
|
30
|
+
<span>{{ ev }}</span>
|
|
31
|
+
</label>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="btn-group">
|
|
36
|
+
<button class="btn btn-cancel" @click="closeWebhookModal">取消</button>
|
|
37
|
+
<button class="btn btn-confirm" @click="saveWebhookSettings" :disabled="webhookSaving">
|
|
38
|
+
{{ webhookSaving ? '保存中...' : '保存' }}
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
@@ -171,3 +171,53 @@
|
|
|
171
171
|
</div>
|
|
172
172
|
</div>
|
|
173
173
|
</div>
|
|
174
|
+
|
|
175
|
+
<!-- Codex 轮询池控制模态框 -->
|
|
176
|
+
<div v-if="showCodexBridgePoolModal" class="modal-overlay" @click.self="showCodexBridgePoolModal = false">
|
|
177
|
+
<div class="modal modal-bridge-pool" role="dialog" aria-modal="true" aria-labelledby="codex-bridge-pool-modal-title">
|
|
178
|
+
<div class="modal-title" id="codex-bridge-pool-modal-title">
|
|
179
|
+
<svg class="modal-title-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><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>
|
|
180
|
+
轮询池设置
|
|
181
|
+
</div>
|
|
182
|
+
<div class="bridge-pool-modal-hint">勾选参与负载均衡的提供商</div>
|
|
183
|
+
<div v-if="localBridgeCandidateProviders().length === 0" class="bridge-pool-empty">
|
|
184
|
+
<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>
|
|
185
|
+
<span>暂无可用上游 provider,请先添加直连 provider</span>
|
|
186
|
+
</div>
|
|
187
|
+
<div v-else class="bridge-pool-list">
|
|
188
|
+
<label v-for="cp in localBridgeCandidateProviders()" :key="cp.name" class="bridge-pool-item">
|
|
189
|
+
<span class="bridge-pool-item-name">{{ cp.name }}</span>
|
|
190
|
+
<span class="bridge-pool-item-status" :class="{ active: !isLocalBridgeExcluded(cp.name) }">{{ isLocalBridgeExcluded(cp.name) ? '未启用' : '已启用' }}</span>
|
|
191
|
+
<input type="checkbox" :checked="!isLocalBridgeExcluded(cp.name)" @change="toggleLocalBridgeExcluded(cp.name)" />
|
|
192
|
+
</label>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="btn-group">
|
|
195
|
+
<button class="btn btn-confirm" @click="showCodexBridgePoolModal = false">{{ t('common.close') }}</button>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div v-if="showClaudeBridgePoolModal" class="modal-overlay" @click.self="showClaudeBridgePoolModal = false">
|
|
201
|
+
<div class="modal modal-bridge-pool" role="dialog" aria-modal="true" aria-labelledby="claude-bridge-pool-modal-title">
|
|
202
|
+
<div class="modal-title" id="claude-bridge-pool-modal-title">
|
|
203
|
+
<svg class="modal-title-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><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>
|
|
204
|
+
{{ t('claude.localBridge.poolTitle') }}
|
|
205
|
+
</div>
|
|
206
|
+
<div class="bridge-pool-modal-hint">{{ t('claude.localBridge.poolHint') }}</div>
|
|
207
|
+
<div v-if="claudeLocalBridgeCandidateProviders().length === 0" class="bridge-pool-empty">
|
|
208
|
+
<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>
|
|
209
|
+
<span>{{ t('claude.localBridge.noProviders') }}</span>
|
|
210
|
+
</div>
|
|
211
|
+
<div v-else class="bridge-pool-list">
|
|
212
|
+
<label v-for="cp in claudeLocalBridgeCandidateProviders()" :key="cp.name" class="bridge-pool-item">
|
|
213
|
+
<span class="bridge-pool-item-name">{{ cp.name }}</span>
|
|
214
|
+
<span class="bridge-pool-item-status" :class="{ active: !isClaudeLocalBridgeExcluded(cp.name) }">{{ isClaudeLocalBridgeExcluded(cp.name) ? t('claude.localBridge.disabled') : t('claude.localBridge.enabled') }}</span>
|
|
215
|
+
<input type="checkbox" :checked="!isClaudeLocalBridgeExcluded(cp.name)" @change="toggleClaudeLocalBridgeExcluded(cp.name)" />
|
|
216
|
+
</label>
|
|
217
|
+
</div>
|
|
218
|
+
<div class="btn-group">
|
|
219
|
+
<button class="btn btn-confirm" @click="showClaudeBridgePoolModal = false">{{ t('common.close') }}</button>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
@blur="onClaudeModelChange"
|
|
69
69
|
@keyup.enter="onClaudeModelChange"
|
|
70
70
|
:placeholder="t('claude.model.placeholder')"
|
|
71
|
+
:readonly="currentClaudeConfig === 'claude-local'"
|
|
71
72
|
list="claude-model-options"
|
|
72
73
|
>
|
|
73
74
|
<datalist v-if="claudeModelHasList" id="claude-model-options">
|
|
@@ -80,8 +81,10 @@
|
|
|
80
81
|
@blur="onClaudeModelChange"
|
|
81
82
|
@keyup.enter="onClaudeModelChange"
|
|
82
83
|
:placeholder="t('claude.model.placeholder')"
|
|
84
|
+
:readonly="currentClaudeConfig === 'claude-local'"
|
|
83
85
|
>
|
|
84
86
|
<div class="config-template-hint">{{ t('claude.model.hint') }}</div>
|
|
87
|
+
<button class="btn-tool btn-template-editor" @click="openClaudeConfigTemplateEditor" :disabled="loading || !!initError">{{ t('config.template.openEditor') }}</button>
|
|
85
88
|
</div>
|
|
86
89
|
|
|
87
90
|
<div class="selector-section">
|
|
@@ -97,10 +100,14 @@
|
|
|
97
100
|
</div>
|
|
98
101
|
|
|
99
102
|
<div class="card-list">
|
|
100
|
-
<div :class="['card', { active: currentClaudeConfig === 'claude-local' }]" @click="
|
|
103
|
+
<div :class="['card', { active: currentClaudeConfig === 'claude-local', disabled: isClaudeLocalBridgeDisabled() }]" @click="isClaudeLocalBridgeDisabled() ? null : applyClaudeLocalBridge()" @keydown.enter.self.prevent="isClaudeLocalBridgeDisabled() ? null : applyClaudeLocalBridge()" @keydown.space.self.prevent="isClaudeLocalBridgeDisabled() ? null : applyClaudeLocalBridge()" :tabindex="isClaudeLocalBridgeDisabled() ? -1 : 0" role="button" :aria-current="currentClaudeConfig === 'claude-local' ? 'true' : null">
|
|
101
104
|
<div class="card-leading">
|
|
102
105
|
<div class="card-icon">L</div>
|
|
103
106
|
<div class="card-content">
|
|
107
|
+
<div class="bridge-pool-summary">
|
|
108
|
+
<svg class="bridge-pool-summary-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12"><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>
|
|
109
|
+
<span class="bridge-pool-summary-text">{{ t('claude.localBridge.enabled') }} {{ claudeLocalBridgeCandidateProviders().filter(cp => !isClaudeLocalBridgeExcluded(cp.name)).length }} / {{ claudeLocalBridgeCandidateProviders().length }}</span>
|
|
110
|
+
</div>
|
|
104
111
|
<div class="card-title">
|
|
105
112
|
<span>local</span>
|
|
106
113
|
<span class="provider-readonly-badge">{{ t('config.badge.system') }}</span>
|
|
@@ -109,6 +116,11 @@
|
|
|
109
116
|
</div>
|
|
110
117
|
<div class="card-trailing">
|
|
111
118
|
<span :class="['pill', claudeLocalBridgeConfigured() ? 'configured' : 'empty']">{{ claudeLocalBridgeConfigured() ? t('claude.configured') : t('claude.notConfigured') }}</span>
|
|
119
|
+
<div class="card-actions" @click.stop>
|
|
120
|
+
<button class="card-action-btn bridge-pool-trigger" @click="showClaudeBridgePoolModal = true" :aria-label="t('claude.localBridge.poolTitle')" :title="t('claude.localBridge.poolTitle')">
|
|
121
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
112
124
|
</div>
|
|
113
125
|
</div>
|
|
114
126
|
<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">
|
|
@@ -141,26 +153,5 @@
|
|
|
141
153
|
</div>
|
|
142
154
|
</div>
|
|
143
155
|
|
|
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
|
-
|
|
165
156
|
</template>
|
|
166
157
|
</div>
|
|
@@ -120,10 +120,14 @@
|
|
|
120
120
|
</template>
|
|
121
121
|
|
|
122
122
|
<div v-if="!loading && !initError" class="card-list">
|
|
123
|
-
<div v-for="provider in displayProvidersList" :key="provider.name" :class="['card', { active: displayCurrentProvider === provider.name }]" @click="switchProvider(provider.name)" @keydown.enter.self.prevent="switchProvider(provider.name)" @keydown.space.self.prevent="switchProvider(provider.name)" tabindex="0" role="button" :aria-current="displayCurrentProvider === provider.name ? 'true' : null">
|
|
123
|
+
<div v-for="provider in displayProvidersList" :key="provider.name" :class="['card', { active: displayCurrentProvider === provider.name, disabled: provider.name === 'local' && isLocalProviderDisabled }]" @click="(provider.name === 'local' && isLocalProviderDisabled) ? null : switchProvider(provider.name)" @keydown.enter.self.prevent="(provider.name === 'local' && isLocalProviderDisabled) ? null : switchProvider(provider.name)" @keydown.space.self.prevent="(provider.name === 'local' && isLocalProviderDisabled) ? null : switchProvider(provider.name)" :tabindex="provider.name === 'local' && isLocalProviderDisabled ? -1 : 0" role="button" :aria-current="displayCurrentProvider === provider.name ? 'true' : null">
|
|
124
124
|
<div class="card-leading">
|
|
125
125
|
<div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}<span v-if="isTransformProvider(provider)" class="card-icon-dot" title="通过内建转换适配"></span></div>
|
|
126
126
|
<div class="card-content">
|
|
127
|
+
<div v-if="provider.name === 'local'" class="bridge-pool-summary">
|
|
128
|
+
<svg class="bridge-pool-summary-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12"><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>
|
|
129
|
+
<span class="bridge-pool-summary-text">已启用 {{ localBridgeCandidateProviders().filter(cp => !isLocalBridgeExcluded(cp.name)).length }} / {{ localBridgeCandidateProviders().length }}</span>
|
|
130
|
+
</div>
|
|
127
131
|
<div class="card-title">
|
|
128
132
|
<span>{{ provider.name }}</span>
|
|
129
133
|
<span v-if="provider.readOnly" class="provider-readonly-badge">{{ t('config.badge.system') }}</span>
|
|
@@ -136,6 +140,9 @@
|
|
|
136
140
|
<span v-if="speedResults[provider.name]" :class="['latency', speedResults[provider.name].ok ? 'ok' : 'error']">{{ formatLatency(speedResults[provider.name]) }}</span>
|
|
137
141
|
<span :class="['pill', providerPillConfigured(provider) ? 'configured' : 'empty']">{{ providerPillText(provider) }}</span>
|
|
138
142
|
<div class="card-actions" @click.stop>
|
|
143
|
+
<button v-if="provider.name === 'local'" class="card-action-btn bridge-pool-trigger" @click="showCodexBridgePoolModal = true" :aria-label="'轮询池设置'" :title="'轮询池设置'">
|
|
144
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>
|
|
145
|
+
</button>
|
|
139
146
|
<button class="card-action-btn" :class="{ loading: speedLoading[provider.name] }" :disabled="!!speedLoading[provider.name]" @click="runSpeedTest(provider.name, { silent: true })" :aria-label="t('config.availabilityTestAria', { name: provider.name })" :title="t('config.availabilityTest')">
|
|
140
147
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
|
|
141
148
|
</button>
|
|
@@ -166,26 +173,5 @@
|
|
|
166
173
|
</div>
|
|
167
174
|
</div>
|
|
168
175
|
</div>
|
|
169
|
-
|
|
170
|
-
<div v-if="displayCurrentProvider === 'local'" class="bridge-pool-panel">
|
|
171
|
-
<div class="bridge-pool-header">
|
|
172
|
-
<span class="bridge-pool-icon">
|
|
173
|
-
<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>
|
|
174
|
-
</span>
|
|
175
|
-
<span class="bridge-pool-title">轮询池</span>
|
|
176
|
-
<span class="bridge-pool-hint">勾选参与负载均衡的提供商</span>
|
|
177
|
-
</div>
|
|
178
|
-
<div v-if="localBridgeCandidateProviders().length === 0" class="bridge-pool-empty">
|
|
179
|
-
<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>
|
|
180
|
-
<span>暂无可用上游 provider,请先添加直连 provider</span>
|
|
181
|
-
</div>
|
|
182
|
-
<div v-else class="bridge-pool-list">
|
|
183
|
-
<label v-for="cp in localBridgeCandidateProviders()" :key="cp.name" class="bridge-pool-item">
|
|
184
|
-
<span class="bridge-pool-item-name">{{ cp.name }}</span>
|
|
185
|
-
<span class="bridge-pool-item-status" :class="{ active: !isLocalBridgeExcluded(cp.name) }">{{ isLocalBridgeExcluded(cp.name) ? '未启用' : '已启用' }}</span>
|
|
186
|
-
<input type="checkbox" :checked="!isLocalBridgeExcluded(cp.name)" @change="toggleLocalBridgeExcluded(cp.name)" />
|
|
187
|
-
</label>
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
176
|
</template>
|
|
191
177
|
</div>
|