codexmate 0.0.33 → 0.0.34
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 +65 -9
- package/lib/cli-webhook.js +29 -1
- package/package.json +1 -1
- package/web-ui/app.js +4 -0
- package/web-ui/index.html +1 -0
- package/web-ui/logic.claude.mjs +4 -0
- package/web-ui/modules/app.methods.claude-config.mjs +37 -0
- package/web-ui/modules/app.methods.codex-config.mjs +11 -3
- package/web-ui/modules/app.methods.navigation.mjs +14 -0
- package/web-ui/modules/app.methods.session-browser.mjs +5 -6
- 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 +3 -0
- 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 +14 -25
- package/web-ui/partials/index/panel-config-codex.html +7 -21
- package/web-ui/partials/index/panel-settings.html +117 -149
- package/web-ui/styles/bridge-pool.css +69 -0
- package/web-ui/styles/settings-panel.css +300 -234
- package/web-ui/styles/webhook.css +38 -4
|
@@ -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,18 +100,25 @@
|
|
|
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' }]" @click="applyClaudeLocalBridge()" @keydown.enter.self.prevent="applyClaudeLocalBridge()" @keydown.space.self.prevent="applyClaudeLocalBridge()" tabindex="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">
|
|
104
|
-
<div class="
|
|
105
|
-
<
|
|
106
|
-
<span class="
|
|
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>
|
|
107
110
|
</div>
|
|
111
|
+
<div class="card-title">claude-local</div>
|
|
112
|
+
<div class="card-subtitle card-subtitle-model">{{ t('claude.localBridge.poolHint') }}</div>
|
|
108
113
|
</div>
|
|
109
114
|
</div>
|
|
110
115
|
<div class="card-trailing">
|
|
111
116
|
<span :class="['pill', claudeLocalBridgeConfigured() ? 'configured' : 'empty']">{{ claudeLocalBridgeConfigured() ? t('claude.configured') : t('claude.notConfigured') }}</span>
|
|
117
|
+
<div class="card-actions" @click.stop>
|
|
118
|
+
<button class="card-action-btn bridge-pool-trigger" @click="showClaudeBridgePoolModal = true" :aria-label="t('claude.localBridge.poolTitle')" :title="t('claude.localBridge.poolTitle')">
|
|
119
|
+
<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>
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
112
122
|
</div>
|
|
113
123
|
</div>
|
|
114
124
|
<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 +151,5 @@
|
|
|
141
151
|
</div>
|
|
142
152
|
</div>
|
|
143
153
|
|
|
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
154
|
</template>
|
|
166
155
|
</div>
|
|
@@ -124,6 +124,10 @@
|
|
|
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>
|
|
@@ -1,31 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- 设置面板 -->
|
|
2
2
|
<div
|
|
3
3
|
v-show="mainTab === 'settings'"
|
|
4
4
|
class="mode-content"
|
|
5
5
|
id="panel-settings"
|
|
6
6
|
role="tabpanel"
|
|
7
7
|
:aria-labelledby="'tab-settings'">
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
:aria-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
8
|
+
|
|
9
|
+
<div class="settings-tab-header">
|
|
10
|
+
<div
|
|
11
|
+
class="segmented-control"
|
|
12
|
+
role="tablist"
|
|
13
|
+
:aria-label="t('settings.tabs.aria')">
|
|
14
|
+
<button
|
|
15
|
+
id="settings-tab-general"
|
|
16
|
+
type="button"
|
|
17
|
+
role="tab"
|
|
18
|
+
aria-controls="settings-panel-general"
|
|
19
|
+
:aria-selected="settingsTab === 'general'"
|
|
20
|
+
:tabindex="settingsTab === 'general' ? 0 : -1"
|
|
21
|
+
:class="['segmented-option', { active: settingsTab === 'general' }]"
|
|
22
|
+
@click="onSettingsTabClick('general')">{{ t('settings.tab.general') }}</button>
|
|
23
|
+
<button
|
|
24
|
+
id="settings-tab-data"
|
|
25
|
+
type="button"
|
|
26
|
+
role="tab"
|
|
27
|
+
aria-controls="settings-panel-data"
|
|
28
|
+
:aria-selected="settingsTab === 'data'"
|
|
29
|
+
:tabindex="settingsTab === 'data' ? 0 : -1"
|
|
30
|
+
:class="['segmented-option', { active: settingsTab === 'data' }]"
|
|
31
|
+
@click="onSettingsTabClick('data')">{{ t('settings.tab.data') }}</button>
|
|
32
|
+
</div>
|
|
29
33
|
</div>
|
|
30
34
|
|
|
31
35
|
<div
|
|
@@ -33,73 +37,60 @@
|
|
|
33
37
|
id="settings-panel-general"
|
|
34
38
|
role="tabpanel"
|
|
35
39
|
aria-labelledby="settings-tab-general">
|
|
36
|
-
<div class="settings-
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
<div class="settings-card-
|
|
40
|
+
<div class="settings-grid">
|
|
41
|
+
<section class="settings-card" :aria-label="t('settings.sharePrefix.title')">
|
|
42
|
+
<div class="settings-card-main">
|
|
43
|
+
<div class="settings-card-content">
|
|
40
44
|
<div class="settings-card-title">{{ t('settings.sharePrefix.title') }}</div>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<option value="codexmate">codexmate</option>
|
|
53
|
-
</select>
|
|
54
|
-
</div>
|
|
55
|
-
<div class="settings-card-hint">
|
|
56
|
-
{{ t('settings.sharePrefix.hint') }}
|
|
57
|
-
</div>
|
|
45
|
+
<p class="settings-card-desc">{{ t('settings.sharePrefix.meta') }}</p>
|
|
46
|
+
<label class="selector-label" for="settings-share-prefix">{{ t('settings.sharePrefix.label') }}</label>
|
|
47
|
+
<select
|
|
48
|
+
id="settings-share-prefix"
|
|
49
|
+
class="model-select"
|
|
50
|
+
:value="shareCommandPrefix"
|
|
51
|
+
@change="setShareCommandPrefix($event.target.value)">
|
|
52
|
+
<option value="npm start">npm start</option>
|
|
53
|
+
<option value="codexmate">codexmate</option>
|
|
54
|
+
</select>
|
|
55
|
+
<p class="settings-card-hint">{{ t('settings.sharePrefix.hint') }}</p>
|
|
58
56
|
</div>
|
|
59
|
-
</
|
|
57
|
+
</div>
|
|
58
|
+
</section>
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
<section class="settings-card" :aria-label="t('settings.templateConfirm.title')">
|
|
61
|
+
<div class="settings-card-main">
|
|
62
|
+
<div class="settings-card-content">
|
|
63
63
|
<div class="settings-card-title">{{ t('settings.templateConfirm.title') }}</div>
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
:checked="configTemplateDiffConfirmEnabled"
|
|
71
|
-
@change="setConfigTemplateDiffConfirmEnabled($event.target.checked)">
|
|
64
|
+
<p class="settings-card-desc">{{ t('settings.templateConfirm.meta') }}</p>
|
|
65
|
+
<label class="settings-toggle-row">
|
|
66
|
+
<input type="checkbox" :checked="configTemplateDiffConfirmEnabled" @change="setConfigTemplateDiffConfirmEnabled($event.target.checked)">
|
|
67
|
+
<span class="toggle-track">
|
|
68
|
+
<span class="toggle-thumb"></span>
|
|
69
|
+
</span>
|
|
72
70
|
<span>{{ t('settings.templateConfirm.toggle') }}</span>
|
|
73
71
|
</label>
|
|
74
|
-
<
|
|
75
|
-
{{ t('settings.templateConfirm.hint') }}
|
|
76
|
-
</div>
|
|
72
|
+
<p class="settings-card-hint">{{ t('settings.templateConfirm.hint') }}</p>
|
|
77
73
|
</div>
|
|
78
|
-
</
|
|
74
|
+
</div>
|
|
75
|
+
</section>
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<div v-if="webhookConfig.url" class="webhook-readonly-row">
|
|
90
|
-
<span class="webhook-readonly-label">URL</span>
|
|
91
|
-
<code class="webhook-readonly-value">{{ webhookConfig.url }}</code>
|
|
92
|
-
</div>
|
|
93
|
-
<div class="webhook-readonly-row">
|
|
94
|
-
<span class="webhook-readonly-label">事件</span>
|
|
95
|
-
<div class="webhook-readonly-tags">
|
|
96
|
-
<span v-for="ev in webhookConfig.events" :key="ev" class="webhook-event-tag">{{ ev }}</span>
|
|
97
|
-
<span v-if="!webhookConfig.events.length" class="settings-card-hint">无</span>
|
|
98
|
-
</div>
|
|
77
|
+
<section class="settings-card" :aria-label="'Webhook'">
|
|
78
|
+
<div class="settings-card-main">
|
|
79
|
+
<div class="settings-card-content">
|
|
80
|
+
<div class="settings-card-title">Webhook</div>
|
|
81
|
+
<p class="settings-card-desc">配置变更时外发通知</p>
|
|
82
|
+
<div class="webhook-status">
|
|
83
|
+
<span class="webhook-status-dot" :class="{ active: webhookConfig.enabled }"></span>
|
|
84
|
+
<span class="webhook-status-label">{{ webhookConfig.enabled ? '已启用' : '已禁用' }}</span>
|
|
85
|
+
<code v-if="webhookConfig.url" class="webhook-url">{{ webhookConfig.url }}</code>
|
|
99
86
|
</div>
|
|
100
87
|
</div>
|
|
101
|
-
</
|
|
102
|
-
|
|
88
|
+
</div>
|
|
89
|
+
<button class="settings-card-action" @click="openWebhookModal" :class="{ 'settings-card-action--active': webhookConfig.enabled }">
|
|
90
|
+
<span v-if="webhookConfig.enabled">{{ webhookConfig.url ? '编辑' : '配置' }}</span>
|
|
91
|
+
<span v-else>启用</span>
|
|
92
|
+
</button>
|
|
93
|
+
</section>
|
|
103
94
|
</div>
|
|
104
95
|
</div>
|
|
105
96
|
|
|
@@ -108,83 +99,60 @@
|
|
|
108
99
|
id="settings-panel-data"
|
|
109
100
|
role="tabpanel"
|
|
110
101
|
aria-labelledby="settings-tab-data">
|
|
111
|
-
<div class="settings-
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
<div class="settings-card-
|
|
115
|
-
<div>
|
|
116
|
-
|
|
117
|
-
<div class="settings-card-meta">{{ t('settings.backup.meta') }}</div>
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
<div class="settings-card-body">
|
|
121
|
-
<div class="settings-backup-grid">
|
|
122
|
-
<div class="settings-backup-slot">
|
|
123
|
-
<div class="settings-backup-slot-label">Claude</div>
|
|
124
|
-
<div class="settings-actions">
|
|
125
|
-
<button class="btn-tool" @click="downloadClaudeDirectory" :disabled="claudeDownloadLoading">
|
|
126
|
-
{{ claudeDownloadLoading ? t('settings.backup.progress', { percent: claudeDownloadProgress }) : t('settings.backup.oneClickClaude') }}
|
|
127
|
-
</button>
|
|
128
|
-
<button class="btn-tool" @click="triggerClaudeImport" :disabled="claudeImportLoading">
|
|
129
|
-
{{ claudeImportLoading ? t('settings.importing') : t('settings.backup.importClaude') }}
|
|
130
|
-
</button>
|
|
131
|
-
</div>
|
|
132
|
-
<input ref="claudeImportInput" class="sr-only" type="file" accept=".zip" @change="handleClaudeImportChange">
|
|
133
|
-
</div>
|
|
134
|
-
<div class="settings-backup-slot">
|
|
135
|
-
<div class="settings-backup-slot-label">Codex</div>
|
|
136
|
-
<div class="settings-actions">
|
|
137
|
-
<button class="btn-tool" @click="downloadCodexDirectory" :disabled="codexDownloadLoading">
|
|
138
|
-
{{ codexDownloadLoading ? t('settings.backup.progress', { percent: codexDownloadProgress }) : t('settings.backup.oneClickCodex') }}
|
|
139
|
-
</button>
|
|
140
|
-
<button class="btn-tool" @click="triggerCodexImport" :disabled="codexImportLoading">
|
|
141
|
-
{{ codexImportLoading ? t('settings.importing') : t('settings.backup.importCodex') }}
|
|
142
|
-
</button>
|
|
143
|
-
</div>
|
|
144
|
-
<input ref="codexImportInput" class="sr-only" type="file" accept=".zip" @change="handleCodexImportChange">
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
102
|
+
<div class="settings-grid">
|
|
103
|
+
<section class="settings-card" :aria-label="t('settings.backup.title')">
|
|
104
|
+
<div class="settings-card-main">
|
|
105
|
+
<div class="settings-card-content">
|
|
106
|
+
<div class="settings-card-title">{{ t('settings.backup.title') }}</div>
|
|
107
|
+
<p class="settings-card-desc">{{ t('settings.backup.meta') }}</p>
|
|
147
108
|
</div>
|
|
148
|
-
</
|
|
109
|
+
</div>
|
|
110
|
+
<div class="settings-card-actions">
|
|
111
|
+
<button class="settings-card-action" @click="downloadClaudeDirectory" :disabled="claudeDownloadLoading">
|
|
112
|
+
<span>{{ claudeDownloadLoading ? t('settings.importing') : t('settings.backup.oneClickClaude') }}</span>
|
|
113
|
+
</button>
|
|
114
|
+
<button class="settings-card-action" @click="downloadCodexDirectory" :disabled="codexDownloadLoading">
|
|
115
|
+
<span>{{ codexDownloadLoading ? t('settings.importing') : t('settings.backup.oneClickCodex') }}</span>
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
<input ref="claudeImportInput" class="sr-only" type="file" accept=".zip" @change="handleClaudeImportChange">
|
|
119
|
+
<input ref="codexImportInput" class="sr-only" type="file" accept=".zip" @change="handleCodexImportChange">
|
|
120
|
+
</section>
|
|
149
121
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
<label
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
</label>
|
|
122
|
+
<section class="settings-card" :aria-label="t('settings.trashConfig.title')">
|
|
123
|
+
<div class="settings-card-main">
|
|
124
|
+
<div class="settings-card-content">
|
|
125
|
+
<div class="settings-card-title">{{ t('settings.trashConfig.title') }}</div>
|
|
126
|
+
<p class="settings-card-desc">{{ t('settings.trashConfig.meta') }}</p>
|
|
127
|
+
<label class="settings-toggle-row">
|
|
128
|
+
<input type="checkbox" :checked="sessionTrashEnabled" @change="setSessionTrashEnabled($event.target.checked)">
|
|
129
|
+
<span class="toggle-track">
|
|
130
|
+
<span class="toggle-thumb"></span>
|
|
131
|
+
</span>
|
|
132
|
+
<span>{{ t('settings.deleteBehavior.toggle') }}</span>
|
|
133
|
+
</label>
|
|
134
|
+
<div class="settings-retention">
|
|
135
|
+
<label for="settings-trash-retention-days">{{ t('settings.trash.retentionLabel') }}</label>
|
|
136
|
+
<input id="settings-trash-retention-days" type="number" min="1" max="365" :value="sessionTrashRetentionDays" @change="setSessionTrashRetentionDays(Number($event.target.value))" class="settings-retention-input" />
|
|
137
|
+
<span>天</span>
|
|
167
138
|
</div>
|
|
168
|
-
<
|
|
139
|
+
<p class="settings-card-hint">{{ t('settings.trash.retentionHint') }}</p>
|
|
169
140
|
</div>
|
|
170
|
-
</
|
|
171
|
-
|
|
141
|
+
</div>
|
|
142
|
+
</section>
|
|
172
143
|
|
|
173
|
-
|
|
174
|
-
|
|
144
|
+
<section class="settings-card settings-card--destructive" :aria-label="t('settings.reset.title')">
|
|
145
|
+
<div class="settings-card-main">
|
|
146
|
+
<div class="settings-card-content">
|
|
175
147
|
<div class="settings-card-title">{{ t('settings.reset.title') }}</div>
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
<div class="settings-card-body">
|
|
179
|
-
<div class="settings-card-hint">{{ t('settings.reset.hint') }}</div>
|
|
180
|
-
<div class="settings-actions">
|
|
181
|
-
<button class="btn-tool" @click="resetConfig" :disabled="resetConfigLoading || loading || !!initError">
|
|
182
|
-
{{ resetConfigLoading ? t('settings.reset.loading') : t('settings.reset.button') }}
|
|
183
|
-
</button>
|
|
184
|
-
</div>
|
|
148
|
+
<p class="settings-card-desc">{{ t('settings.reset.meta') }}</p>
|
|
149
|
+
<p class="settings-card-hint">{{ t('settings.reset.hint') }}</p>
|
|
185
150
|
</div>
|
|
186
|
-
</
|
|
187
|
-
|
|
151
|
+
</div>
|
|
152
|
+
<button class="settings-card-action settings-card-action--danger" @click="resetConfig" :disabled="resetConfigLoading || loading || !!initError">
|
|
153
|
+
{{ resetConfigLoading ? t('settings.reset.loading') : t('settings.reset.button') }}
|
|
154
|
+
</button>
|
|
155
|
+
</section>
|
|
188
156
|
</div>
|
|
189
157
|
</div>
|
|
190
158
|
</div>
|
|
@@ -3,6 +3,43 @@
|
|
|
3
3
|
============================================ */
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
/* ---- 摘要状态(在卡片标题上方) ---- */
|
|
7
|
+
.bridge-pool-summary {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: 5px;
|
|
11
|
+
margin-bottom: 4px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.bridge-pool-summary-icon {
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
width: 18px;
|
|
19
|
+
height: 18px;
|
|
20
|
+
border-radius: 5px;
|
|
21
|
+
background: var(--color-brand-light);
|
|
22
|
+
color: var(--color-brand-dark);
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.bridge-pool-summary-text {
|
|
27
|
+
font-size: 11px;
|
|
28
|
+
font-weight: 600;
|
|
29
|
+
color: var(--color-text-muted);
|
|
30
|
+
letter-spacing: -0.01em;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* ---- 轮询池触发按钮 ---- */
|
|
34
|
+
.bridge-pool-trigger {
|
|
35
|
+
color: var(--color-brand);
|
|
36
|
+
background: rgba(200, 121, 99, 0.08);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.bridge-pool-trigger:hover {
|
|
40
|
+
background: rgba(200, 121, 99, 0.16);
|
|
41
|
+
}
|
|
42
|
+
|
|
6
43
|
.bridge-pool-panel {
|
|
7
44
|
margin-top: 18px;
|
|
8
45
|
padding: 18px 20px 16px;
|
|
@@ -195,3 +232,35 @@
|
|
|
195
232
|
padding: 10px 12px 10px 14px;
|
|
196
233
|
}
|
|
197
234
|
}
|
|
235
|
+
|
|
236
|
+
/* ---- 模态框中的轮询池 ---- */
|
|
237
|
+
.modal-bridge-pool {
|
|
238
|
+
max-width: 380px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.modal-title-icon {
|
|
242
|
+
display: inline-flex;
|
|
243
|
+
align-items: center;
|
|
244
|
+
justify-content: center;
|
|
245
|
+
width: 24px;
|
|
246
|
+
height: 24px;
|
|
247
|
+
border-radius: 6px;
|
|
248
|
+
background: var(--color-brand-light);
|
|
249
|
+
color: var(--color-brand-dark);
|
|
250
|
+
margin-right: 8px;
|
|
251
|
+
vertical-align: middle;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.bridge-pool-modal-hint {
|
|
255
|
+
font-size: 12px;
|
|
256
|
+
color: var(--color-text-muted);
|
|
257
|
+
margin-bottom: 14px;
|
|
258
|
+
padding-bottom: 12px;
|
|
259
|
+
border-bottom: 1px solid var(--color-border-soft);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.modal-bridge-pool .bridge-pool-list {
|
|
263
|
+
max-height: 320px;
|
|
264
|
+
overflow-y: auto;
|
|
265
|
+
margin-bottom: 16px;
|
|
266
|
+
}
|