codexmate 0.0.38 → 0.0.39
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/builtin-proxy.js +626 -207
- package/cli/openai-bridge.js +541 -210
- package/cli.js +19 -1
- package/package.json +1 -1
- package/web-ui/app.js +12 -3
- package/web-ui/modules/app.computed.main-tabs.mjs +37 -30
- package/web-ui/modules/app.methods.claude-config.mjs +111 -9
- package/web-ui/modules/app.methods.openclaw-editing.mjs +48 -0
- package/web-ui/modules/app.methods.openclaw-persist.mjs +13 -7
- package/web-ui/modules/app.methods.providers.mjs +36 -10
- package/web-ui/modules/app.methods.runtime.mjs +76 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +1 -0
- package/web-ui/modules/config-mode.computed.mjs +3 -3
- package/web-ui/modules/i18n.dict.mjs +13 -0
- package/web-ui/modules/i18n.mjs +65 -16
- package/web-ui/partials/index/layout-header.html +16 -46
- package/web-ui/partials/index/modal-openclaw-config.html +135 -71
- package/web-ui/partials/index/modal-webhook.html +8 -8
- package/web-ui/partials/index/modals-basic.html +56 -16
- package/web-ui/partials/index/panel-config-claude.html +20 -20
- package/web-ui/partials/index/panel-config-codex.html +5 -5
- package/web-ui/partials/index/panel-config-openclaw.html +70 -64
- package/web-ui/partials/index/panel-dashboard.html +62 -77
- package/web-ui/partials/index/panel-settings.html +28 -7
- package/web-ui/partials/index/panel-trash.html +14 -14
- package/web-ui/res/web-ui-render.precompiled.js +846 -539
- package/web-ui/styles/controls-forms.css +6 -0
- package/web-ui/styles/dashboard.css +46 -14
- package/web-ui/styles/layout-shell.css +45 -0
- package/web-ui/styles/navigation-panels.css +3 -3
- package/web-ui/styles/openclaw-structured.css +383 -33
- package/web-ui/styles/responsive.css +68 -0
- package/web-ui/styles/sessions-usage.css +105 -9
- package/web-ui/styles/settings-panel.css +4 -0
|
@@ -33,86 +33,150 @@
|
|
|
33
33
|
<button class="btn-mini" @click="resetOpenclawQuick">{{ t('common.clear') }}</button>
|
|
34
34
|
</div>
|
|
35
35
|
</div>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<div
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
36
|
+
|
|
37
|
+
<!-- Accordion Stepper -->
|
|
38
|
+
<div class="quick-accordion">
|
|
39
|
+
<!-- Step 1: Provider -->
|
|
40
|
+
<div :class="['accordion-panel', { active: openclawAccordionStep === 1, completed: openclawAccordionStep > 1 }]">
|
|
41
|
+
<button class="accordion-trigger" @click="toggleAccordionStep(1)" type="button">
|
|
42
|
+
<span class="accordion-step-badge">
|
|
43
|
+
<template v-if="openclawAccordionStep > 1">✓</template>
|
|
44
|
+
<template v-else>1</template>
|
|
45
|
+
</span>
|
|
46
|
+
<span class="accordion-title">Provider</span>
|
|
47
|
+
<span class="accordion-status" v-if="openclawQuick.providerName">{{ openclawQuick.providerName }}</span>
|
|
48
|
+
<svg class="accordion-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
49
|
+
<path d="M6 9l6 6 6-6"/>
|
|
50
|
+
</svg>
|
|
51
|
+
</button>
|
|
52
|
+
<div class="accordion-content" v-show="openclawAccordionStep === 1">
|
|
53
|
+
<div class="form-group">
|
|
54
|
+
<label class="form-label">{{ t('field.providerName') }}</label>
|
|
55
|
+
<input v-model="openclawQuick.providerName" class="form-input" placeholder="例如: custom-myapi" @input="validateProviderName">
|
|
56
|
+
<div class="form-hint" :class="{ 'hint-error': !openclawValidation.providerName.valid }">
|
|
57
|
+
{{ openclawValidation.providerName.message || t('modal.openclaw.quick.providerHint') }}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="form-group">
|
|
61
|
+
<label class="form-label">{{ t('field.baseUrl') }}</label>
|
|
62
|
+
<input v-model="openclawQuick.baseUrl" class="form-input" placeholder="https://api.example.com/v1" :readonly="openclawQuick.baseUrlReadOnly">
|
|
63
|
+
<div v-if="openclawQuick.baseUrlDisplayKind === 'builtin-default'" class="form-hint">{{ t('modal.openclaw.quick.baseUrlHintDefault') }}</div>
|
|
64
|
+
<div v-else-if="openclawQuick.baseUrlReadOnly" class="form-hint">{{ t('modal.openclaw.quick.baseUrlHintReadonly') }}</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="form-group">
|
|
67
|
+
<label class="form-label">API Key</label>
|
|
68
|
+
<div class="list-row">
|
|
69
|
+
<input v-model="openclawQuick.apiKey" class="form-input" :readonly="openclawQuick.apiKeyReadOnly" :type="(openclawQuick.apiKeyReadOnly || openclawQuick.showKey) ? 'text' : 'password'" placeholder="sk-...">
|
|
70
|
+
<button v-if="!openclawQuick.apiKeyReadOnly" class="btn-mini" @click="toggleOpenclawQuickKey">
|
|
71
|
+
{{ openclawQuick.showKey ? t('common.hide') : t('common.show') }}
|
|
72
|
+
</button>
|
|
73
|
+
</div>
|
|
74
|
+
<div v-if="openclawQuick.apiKeyDisplayKind === 'auth-profile-value'" class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintFromAuth') }}</div>
|
|
75
|
+
<div v-else-if="openclawQuick.apiKeyReadOnly" class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintReadonly') }}</div>
|
|
76
|
+
<div v-else class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintKeep') }}</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="form-group">
|
|
79
|
+
<label class="form-label">{{ t('field.apiType') }}</label>
|
|
80
|
+
<input v-model="openclawQuick.apiType" class="form-input" list="openclawApiTypeList" :placeholder="t('placeholder.apiTypeExample')">
|
|
81
|
+
<datalist id="openclawApiTypeList">
|
|
82
|
+
<option value="openai-responses"></option>
|
|
83
|
+
<option value="openai-chat"></option>
|
|
84
|
+
<option value="anthropic"></option>
|
|
85
|
+
<option value="custom"></option>
|
|
86
|
+
</datalist>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="accordion-actions">
|
|
89
|
+
<button class="btn btn-confirm btn-sm" @click="nextAccordionStep" :disabled="!openclawQuick.providerName">
|
|
90
|
+
下一步 →
|
|
61
91
|
</button>
|
|
62
92
|
</div>
|
|
63
|
-
<div v-if="openclawQuick.apiKeyDisplayKind === 'auth-profile-value'" class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintFromAuth') }}</div>
|
|
64
|
-
<div v-else-if="openclawQuick.apiKeyReadOnly" class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintReadonly') }}</div>
|
|
65
|
-
<div v-else class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintKeep') }}</div>
|
|
66
|
-
</div>
|
|
67
|
-
<div class="form-group">
|
|
68
|
-
<label class="form-label">{{ t('field.apiType') }}</label>
|
|
69
|
-
<input v-model="openclawQuick.apiType" class="form-input" list="openclawApiTypeList" :placeholder="t('placeholder.apiTypeExample')">
|
|
70
|
-
<datalist id="openclawApiTypeList">
|
|
71
|
-
<option value="openai-responses"></option>
|
|
72
|
-
<option value="openai-chat"></option>
|
|
73
|
-
<option value="anthropic"></option>
|
|
74
|
-
<option value="custom"></option>
|
|
75
|
-
</datalist>
|
|
76
93
|
</div>
|
|
77
94
|
</div>
|
|
78
95
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
<!-- Step 2: Model -->
|
|
97
|
+
<div :class="['accordion-panel', { active: openclawAccordionStep === 2, completed: openclawAccordionStep > 2 }]">
|
|
98
|
+
<button class="accordion-trigger" @click="toggleAccordionStep(2)" type="button">
|
|
99
|
+
<span class="accordion-step-badge">
|
|
100
|
+
<template v-if="openclawAccordionStep > 2">✓</template>
|
|
101
|
+
<template v-else>2</template>
|
|
102
|
+
</span>
|
|
103
|
+
<span class="accordion-title">{{ t('modal.openclaw.quick.modelTitle') }}</span>
|
|
104
|
+
<span class="accordion-status" v-if="openclawQuick.modelId">{{ openclawQuick.modelName || openclawQuick.modelId }}</span>
|
|
105
|
+
<svg class="accordion-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
106
|
+
<path d="M6 9l6 6 6-6"/>
|
|
107
|
+
</svg>
|
|
108
|
+
</button>
|
|
109
|
+
<div class="accordion-content" v-show="openclawAccordionStep === 2">
|
|
110
|
+
<div class="form-group">
|
|
111
|
+
<label class="form-label">{{ t('field.modelId') }}</label>
|
|
112
|
+
<input v-model="openclawQuick.modelId" class="form-input" :placeholder="t('placeholder.modelIdExample')" @input="validateModelId">
|
|
113
|
+
<div class="form-hint" :class="{ 'hint-error': !openclawValidation.modelId.valid }">
|
|
114
|
+
{{ openclawValidation.modelId.message || '必填,例如: gpt-4' }}
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="form-group">
|
|
118
|
+
<label class="form-label">{{ t('field.displayName') }}</label>
|
|
119
|
+
<input v-model="openclawQuick.modelName" class="form-input" :placeholder="t('placeholder.modelNameOptional')">
|
|
120
|
+
<div class="form-hint">可选,用于显示</div>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="form-group">
|
|
123
|
+
<label class="form-label">{{ t('field.contextAndMaxOutput') }}</label>
|
|
124
|
+
<div class="list-row">
|
|
125
|
+
<input v-model="openclawQuick.contextWindow" class="form-input" :placeholder="t('field.contextWindow')">
|
|
126
|
+
<input v-model="openclawQuick.maxTokens" class="form-input" :placeholder="t('field.maxOutput')">
|
|
127
|
+
</div>
|
|
128
|
+
<div class="form-hint">{{ t('hint.emptyNoChange') }}</div>
|
|
129
|
+
</div>
|
|
130
|
+
<div class="accordion-actions">
|
|
131
|
+
<button class="btn btn-cancel btn-sm" @click="prevAccordionStep">
|
|
132
|
+
← 上一步
|
|
133
|
+
</button>
|
|
134
|
+
<button class="btn btn-confirm btn-sm" @click="nextAccordionStep" :disabled="!openclawQuick.modelId">
|
|
135
|
+
下一步 →
|
|
136
|
+
</button>
|
|
94
137
|
</div>
|
|
95
|
-
<div class="form-hint">{{ t('hint.emptyNoChange') }}</div>
|
|
96
138
|
</div>
|
|
97
139
|
</div>
|
|
98
140
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
141
|
+
<!-- Step 3: Options -->
|
|
142
|
+
<div :class="['accordion-panel', { active: openclawAccordionStep === 3, completed: openclawAccordionStep > 3 }]">
|
|
143
|
+
<button class="accordion-trigger" @click="toggleAccordionStep(3)" type="button">
|
|
144
|
+
<span class="accordion-step-badge">
|
|
145
|
+
<template v-if="openclawAccordionStep > 3">✓</template>
|
|
146
|
+
<template v-else>3</template>
|
|
147
|
+
</span>
|
|
148
|
+
<span class="accordion-title">{{ t('modal.openclaw.quick.optionsTitle') }}</span>
|
|
149
|
+
<span class="accordion-status">高级选项</span>
|
|
150
|
+
<svg class="accordion-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
151
|
+
<path d="M6 9l6 6 6-6"/>
|
|
152
|
+
</svg>
|
|
153
|
+
</button>
|
|
154
|
+
<div class="accordion-content" v-show="openclawAccordionStep === 3">
|
|
155
|
+
<label class="quick-option">
|
|
156
|
+
<input type="checkbox" v-model="openclawQuick.setPrimary">
|
|
157
|
+
{{ t('modal.openclaw.quick.setPrimary') }}
|
|
158
|
+
</label>
|
|
159
|
+
<label class="quick-option">
|
|
160
|
+
<input type="checkbox" v-model="openclawQuick.overrideProvider">
|
|
161
|
+
{{ t('modal.openclaw.quick.overrideProvider') }}
|
|
162
|
+
</label>
|
|
163
|
+
<label class="quick-option">
|
|
164
|
+
<input type="checkbox" v-model="openclawQuick.overrideModels">
|
|
165
|
+
{{ t('modal.openclaw.quick.overrideModels') }}
|
|
166
|
+
</label>
|
|
167
|
+
<div class="form-hint">{{ t('modal.openclaw.quick.optionsHint') }}</div>
|
|
168
|
+
<div class="accordion-actions">
|
|
169
|
+
<button class="btn btn-cancel btn-sm" @click="prevAccordionStep">
|
|
170
|
+
← 上一步
|
|
171
|
+
</button>
|
|
172
|
+
<button class="btn btn-confirm btn-sm" @click="finishAccordionStep">
|
|
173
|
+
完成 ✓
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
114
177
|
</div>
|
|
115
178
|
</div>
|
|
179
|
+
|
|
116
180
|
<div class="btn-group">
|
|
117
181
|
<button class="btn btn-confirm" @click="applyOpenclawQuickToText">{{ t('modal.openclaw.quick.writeToEditor') }}</button>
|
|
118
182
|
</div>
|
|
@@ -262,14 +326,14 @@
|
|
|
262
326
|
:readonly="openclawSaving || openclawApplying"
|
|
263
327
|
placeholder="在这里编辑 OpenClaw 配置(JSON5)"></textarea>
|
|
264
328
|
<div class="template-editor-warning">
|
|
265
|
-
<span v-if="openclawEditing.lockName && openclawEditing.name
|
|
329
|
+
<span v-if="openclawEditing.lockName && isDefaultOpenclawConfig(openclawEditing.name)">默认配置始终映射当前 openclaw.json,请直接使用“保存并应用”。</span>
|
|
266
330
|
<span v-else>保存仅写入本地配置库。点击“保存并应用”后会写入 openclaw.json。</span>
|
|
267
331
|
</div>
|
|
268
332
|
</div>
|
|
269
333
|
|
|
270
334
|
<div class="btn-group">
|
|
271
335
|
<button class="btn btn-cancel" @click="closeOpenclawConfigModal" :disabled="openclawSaving || openclawApplying">取消</button>
|
|
272
|
-
<button class="btn btn-confirm" @click="saveOpenclawConfig" :disabled="openclawSaving || openclawApplying || (openclawEditing.lockName && openclawEditing.name
|
|
336
|
+
<button class="btn btn-confirm" @click="saveOpenclawConfig" :disabled="openclawSaving || openclawApplying || (openclawEditing.lockName && isDefaultOpenclawConfig(openclawEditing.name))">
|
|
273
337
|
{{ openclawSaving ? '保存中...' : '保存' }}
|
|
274
338
|
</button>
|
|
275
339
|
<button class="btn btn-confirm secondary" @click="saveAndApplyOpenclawConfig" :disabled="openclawSaving || openclawApplying">
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
<!-- Webhook
|
|
1
|
+
<!-- Webhook settings modal -->
|
|
2
2
|
<div v-if="showWebhookModal" class="modal-overlay" @click.self="closeWebhookModal">
|
|
3
3
|
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="webhook-modal-title">
|
|
4
|
-
<div class="modal-title" id="webhook-modal-title">
|
|
4
|
+
<div class="modal-title" id="webhook-modal-title">{{ t('settings.webhook.modalTitle') }}</div>
|
|
5
5
|
|
|
6
6
|
<div class="form-group">
|
|
7
|
-
<label class="form-label"
|
|
7
|
+
<label class="form-label">{{ t('settings.webhook.enabledLabel') }}</label>
|
|
8
8
|
<label class="settings-toggle">
|
|
9
9
|
<input type="checkbox" v-model="webhookConfig.enabled">
|
|
10
|
-
<span
|
|
10
|
+
<span>{{ t('settings.webhook.enableToggle') }}</span>
|
|
11
11
|
</label>
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
14
|
<div class="form-group">
|
|
15
|
-
<label class="form-label">
|
|
15
|
+
<label class="form-label">{{ t('settings.webhook.urlLabel') }}</label>
|
|
16
16
|
<input
|
|
17
17
|
v-model="webhookConfig.url"
|
|
18
18
|
class="form-input"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
25
|
<div class="form-group">
|
|
26
|
-
<label class="form-label"
|
|
26
|
+
<label class="form-label">{{ t('settings.webhook.eventsLabel') }}</label>
|
|
27
27
|
<div class="webhook-events-checkbox-list">
|
|
28
28
|
<label v-for="ev in webhookEventOptions" :key="ev" class="webhook-event-checkbox-item">
|
|
29
29
|
<input type="checkbox" :checked="webhookConfig.events.includes(ev)" @change="toggleWebhookEvent(ev)">
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
</div>
|
|
34
34
|
|
|
35
35
|
<div class="btn-group">
|
|
36
|
-
<button class="btn btn-cancel" @click="closeWebhookModal"
|
|
36
|
+
<button class="btn btn-cancel" @click="closeWebhookModal">{{ t('common.cancel') }}</button>
|
|
37
37
|
<button class="btn btn-confirm" @click="saveWebhookSettings" :disabled="webhookSaving">
|
|
38
|
-
{{ webhookSaving ? '
|
|
38
|
+
{{ webhookSaving ? t('common.saving') : t('common.save') }}
|
|
39
39
|
</button>
|
|
40
40
|
</div>
|
|
41
41
|
</div>
|
|
@@ -25,9 +25,27 @@
|
|
|
25
25
|
@blur="normalizeProviderDraft('add')">
|
|
26
26
|
<div v-if="providerFieldError('add', 'url')" class="form-hint form-error">{{ providerFieldError('add', 'url') }}</div>
|
|
27
27
|
</div>
|
|
28
|
+
<div class="form-group">
|
|
29
|
+
<label class="form-label">{{ t('field.modelName') }}</label>
|
|
30
|
+
<input
|
|
31
|
+
v-model="newProvider.model"
|
|
32
|
+
:class="['form-input', { invalid: !!providerFieldError('add', 'model') }]"
|
|
33
|
+
:placeholder="t('placeholder.modelExample')"
|
|
34
|
+
autocomplete="off"
|
|
35
|
+
spellcheck="false"
|
|
36
|
+
@blur="normalizeProviderDraft('add')">
|
|
37
|
+
<div v-if="providerFieldError('add', 'model')" class="form-hint form-error">{{ providerFieldError('add', 'model') }}</div>
|
|
38
|
+
</div>
|
|
28
39
|
<div class="form-group">
|
|
29
40
|
<label class="form-label">{{ t('field.apiKey') }}</label>
|
|
30
|
-
<
|
|
41
|
+
<div class="input-with-toggle">
|
|
42
|
+
<input v-model="newProvider.key" :class="['form-input', { invalid: !!providerFieldError('add', 'key') }]" :type="showAddProviderKey ? 'text' : 'password'" placeholder="sk-..." autocomplete="off" spellcheck="false" @blur="normalizeProviderDraft('add')">
|
|
43
|
+
<button type="button" class="input-toggle-btn" @click="toggleAddProviderKey" :title="showAddProviderKey ? t('common.hide') : t('common.show')" :aria-label="showAddProviderKey ? t('common.hide') : t('common.show')">
|
|
44
|
+
<svg v-if="!showAddProviderKey" 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>
|
|
45
|
+
<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>
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
<div v-if="providerFieldError('add', 'key')" class="form-hint form-error">{{ providerFieldError('add', 'key') }}</div>
|
|
31
49
|
</div>
|
|
32
50
|
<div class="form-group">
|
|
33
51
|
<label class="form-label">
|
|
@@ -67,7 +85,7 @@
|
|
|
67
85
|
<label class="form-label">{{ t('field.apiKey') }}</label>
|
|
68
86
|
<div class="input-with-toggle">
|
|
69
87
|
<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')">
|
|
88
|
+
<button type="button" class="input-toggle-btn" @click="toggleEditProviderKey" :title="showEditProviderKey ? t('common.hide') : t('common.show')" :aria-label="showEditProviderKey ? t('common.hide') : t('common.show')">
|
|
71
89
|
<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
90
|
<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
91
|
</button>
|
|
@@ -123,20 +141,34 @@
|
|
|
123
141
|
|
|
124
142
|
<div class="form-group">
|
|
125
143
|
<label class="form-label">{{ t('field.configName') }}</label>
|
|
126
|
-
<input v-model="newClaudeConfig.name" class="form-input" :placeholder="t('placeholder.configNameExample')">
|
|
144
|
+
<input v-model="newClaudeConfig.name" :class="['form-input', { invalid: !!claudeConfigFieldError('add', 'name') }]" :placeholder="t('placeholder.configNameExample')">
|
|
145
|
+
<div v-if="claudeConfigFieldError('add', 'name')" class="form-hint form-error">{{ claudeConfigFieldError('add', 'name') }}</div>
|
|
127
146
|
</div>
|
|
128
147
|
<div class="form-group">
|
|
129
148
|
<label class="form-label">API Key</label>
|
|
130
|
-
<
|
|
149
|
+
<div class="input-with-toggle">
|
|
150
|
+
<input v-model="newClaudeConfig.apiKey" :class="['form-input', { invalid: !!claudeConfigFieldError('add', 'apiKey') }]" :type="showAddClaudeConfigKey ? 'text' : 'password'" autocomplete="off" spellcheck="false" :placeholder="t('placeholder.apiKeyExampleClaude')">
|
|
151
|
+
<button type="button" class="input-toggle-btn" @click="toggleAddClaudeConfigKey" :title="showAddClaudeConfigKey ? t('common.hide') : t('common.show')" :aria-label="showAddClaudeConfigKey ? t('common.hide') : t('common.show')">
|
|
152
|
+
<svg v-if="!showAddClaudeConfigKey" 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>
|
|
153
|
+
<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>
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
<div v-if="claudeConfigFieldError('add', 'apiKey')" class="form-hint form-error">{{ claudeConfigFieldError('add', 'apiKey') }}</div>
|
|
131
157
|
</div>
|
|
132
158
|
<div class="form-group">
|
|
133
159
|
<label class="form-label">{{ t('field.baseUrl') }}</label>
|
|
134
|
-
<input v-model="newClaudeConfig.baseUrl" class="form-input" :placeholder="t('placeholder.baseUrlExampleClaude')">
|
|
160
|
+
<input v-model="newClaudeConfig.baseUrl" :class="['form-input', { invalid: !!claudeConfigFieldError('add', 'baseUrl') }]" :placeholder="t('placeholder.baseUrlExampleClaude')">
|
|
161
|
+
<div v-if="claudeConfigFieldError('add', 'baseUrl')" class="form-hint form-error">{{ claudeConfigFieldError('add', 'baseUrl') }}</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="form-group">
|
|
164
|
+
<label class="form-label">{{ t('field.modelName') }}</label>
|
|
165
|
+
<input v-model="newClaudeConfig.model" :class="['form-input', { invalid: !!claudeConfigFieldError('add', 'model') }]" :placeholder="t('placeholder.modelExample')" autocomplete="off" spellcheck="false">
|
|
166
|
+
<div v-if="claudeConfigFieldError('add', 'model')" class="form-hint form-error">{{ claudeConfigFieldError('add', 'model') }}</div>
|
|
135
167
|
</div>
|
|
136
168
|
|
|
137
169
|
<div class="btn-group">
|
|
138
170
|
<button class="btn btn-cancel" @click="closeClaudeConfigModal">{{ t('common.cancel') }}</button>
|
|
139
|
-
<button class="btn btn-confirm" @click="addClaudeConfig">{{ t('common.add') }}</button>
|
|
171
|
+
<button class="btn btn-confirm" @click="addClaudeConfig" :disabled="!canSubmitClaudeConfig('add')">{{ t('common.add') }}</button>
|
|
140
172
|
</div>
|
|
141
173
|
</div>
|
|
142
174
|
</div>
|
|
@@ -148,46 +180,54 @@
|
|
|
148
180
|
|
|
149
181
|
<div class="form-group">
|
|
150
182
|
<label class="form-label">{{ t('field.configName') }}</label>
|
|
151
|
-
<input v-model="editingConfig.name" class="form-input" :placeholder="t('field.configName')" readonly>
|
|
183
|
+
<input v-model="editingConfig.name" :class="['form-input', { invalid: !!claudeConfigFieldError('edit', 'name') }]" :placeholder="t('field.configName')" readonly>
|
|
184
|
+
<div v-if="claudeConfigFieldError('edit', 'name')" class="form-hint form-error">{{ claudeConfigFieldError('edit', 'name') }}</div>
|
|
152
185
|
</div>
|
|
153
186
|
<div class="form-group">
|
|
154
187
|
<label class="form-label">API Key</label>
|
|
155
188
|
<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')">
|
|
189
|
+
<input v-model="editingConfig.apiKey" :class="['form-input', { invalid: !!claudeConfigFieldError('edit', 'apiKey') }]" :type="showEditClaudeConfigKey ? 'text' : 'password'" autocomplete="off" spellcheck="false" :placeholder="t('placeholder.apiKeyExampleClaude')">
|
|
190
|
+
<button type="button" class="input-toggle-btn" @click="toggleEditClaudeConfigKey" :title="showEditClaudeConfigKey ? t('common.hide') : t('common.show')" :aria-label="showEditClaudeConfigKey ? t('common.hide') : t('common.show')">
|
|
158
191
|
<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
192
|
<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
193
|
</button>
|
|
161
194
|
</div>
|
|
195
|
+
<div v-if="claudeConfigFieldError('edit', 'apiKey')" class="form-hint form-error">{{ claudeConfigFieldError('edit', 'apiKey') }}</div>
|
|
162
196
|
</div>
|
|
163
197
|
<div class="form-group">
|
|
164
198
|
<label class="form-label">{{ t('field.baseUrl') }}</label>
|
|
165
|
-
<input v-model="editingConfig.baseUrl" class="form-input" :placeholder="t('placeholder.baseUrlExampleClaude')">
|
|
199
|
+
<input v-model="editingConfig.baseUrl" :class="['form-input', { invalid: !!claudeConfigFieldError('edit', 'baseUrl') }]" :placeholder="t('placeholder.baseUrlExampleClaude')">
|
|
200
|
+
<div v-if="claudeConfigFieldError('edit', 'baseUrl')" class="form-hint form-error">{{ claudeConfigFieldError('edit', 'baseUrl') }}</div>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="form-group">
|
|
203
|
+
<label class="form-label">{{ t('field.modelName') }}</label>
|
|
204
|
+
<input v-model="editingConfig.model" :class="['form-input', { invalid: !!claudeConfigFieldError('edit', 'model') }]" :placeholder="t('placeholder.modelExample')" autocomplete="off" spellcheck="false">
|
|
205
|
+
<div v-if="claudeConfigFieldError('edit', 'model')" class="form-hint form-error">{{ claudeConfigFieldError('edit', 'model') }}</div>
|
|
166
206
|
</div>
|
|
167
207
|
|
|
168
208
|
<div class="btn-group">
|
|
169
209
|
<button class="btn btn-cancel" @click="closeEditConfigModal">{{ t('common.cancel') }}</button>
|
|
170
|
-
<button class="btn btn-confirm" @click="saveAndApplyConfig">{{ t('common.saveApply') }}</button>
|
|
210
|
+
<button class="btn btn-confirm" @click="saveAndApplyConfig" :disabled="!canSubmitClaudeConfig('edit')">{{ t('common.saveApply') }}</button>
|
|
171
211
|
</div>
|
|
172
212
|
</div>
|
|
173
213
|
</div>
|
|
174
214
|
|
|
175
|
-
<!-- Codex
|
|
215
|
+
<!-- Codex bridge pool modal -->
|
|
176
216
|
<div v-if="showCodexBridgePoolModal" class="modal-overlay" @click.self="showCodexBridgePoolModal = false">
|
|
177
217
|
<div class="modal modal-bridge-pool" role="dialog" aria-modal="true" aria-labelledby="codex-bridge-pool-modal-title">
|
|
178
218
|
<div class="modal-title" id="codex-bridge-pool-modal-title">
|
|
179
219
|
<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
|
-
|
|
220
|
+
{{ t('config.localBridge.poolSettings') }}
|
|
181
221
|
</div>
|
|
182
|
-
<div class="bridge-pool-modal-hint"
|
|
222
|
+
<div class="bridge-pool-modal-hint">{{ t('config.localBridge.poolHint') }}</div>
|
|
183
223
|
<div v-if="localBridgeCandidateProviders().length === 0" class="bridge-pool-empty">
|
|
184
224
|
<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
|
|
225
|
+
<span>{{ t('config.localBridge.noProviders') }}</span>
|
|
186
226
|
</div>
|
|
187
227
|
<div v-else class="bridge-pool-list">
|
|
188
228
|
<label v-for="cp in localBridgeCandidateProviders()" :key="cp.name" class="bridge-pool-item">
|
|
189
229
|
<span class="bridge-pool-item-name">{{ cp.name }}</span>
|
|
190
|
-
<span class="bridge-pool-item-status" :class="{ active: !isLocalBridgeExcluded(cp.name) }">{{ isLocalBridgeExcluded(cp.name) ? '
|
|
230
|
+
<span class="bridge-pool-item-status" :class="{ active: !isLocalBridgeExcluded(cp.name) }">{{ isLocalBridgeExcluded(cp.name) ? t('common.disabled') : t('common.enabled') }}</span>
|
|
191
231
|
<input type="checkbox" :checked="!isLocalBridgeExcluded(cp.name)" @change="toggleLocalBridgeExcluded(cp.name)" />
|
|
192
232
|
</label>
|
|
193
233
|
</div>
|
|
@@ -35,26 +35,26 @@
|
|
|
35
35
|
<div class="selector-section">
|
|
36
36
|
<div class="selector-header"><span class="selector-title">{{ t('claude.presetProviders') }}</span></div>
|
|
37
37
|
<div class="btn-group" style="flex-wrap: wrap; gap: 8px; margin-top: 0;">
|
|
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>
|
|
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>
|
|
40
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Zhipu GLM'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://open.bigmodel.cn/api/anthropic'; newClaudeConfig.model = 'glm-5'; showClaudeConfigModal = true">Zhipu GLM</button>
|
|
41
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Z.ai GLM'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.z.ai/api/anthropic'; newClaudeConfig.model = 'glm-5'; showClaudeConfigModal = true">Z.ai GLM</button>
|
|
42
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Qwen Coder'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://coding.dashscope.aliyuncs.com/apps/anthropic'; newClaudeConfig.model = 'qwen3-coder'; showClaudeConfigModal = true">Qwen Coder</button>
|
|
43
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Kimi k2'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.moonshot.cn/anthropic'; newClaudeConfig.model = 'kimi-k2.5'; showClaudeConfigModal = true">Kimi k2</button>
|
|
44
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Kimi For Coding'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.kimi.com/coding/'; newClaudeConfig.model = 'kimi-k2.5'; showClaudeConfigModal = true">Kimi For Coding</button>
|
|
45
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'KAT-Coder'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://vanchin.streamlake.ai/api/gateway/v1/endpoints/${ENDPOINT_ID}/claude-code-proxy'; newClaudeConfig.model = 'KAT-Coder-Pro V1'; showClaudeConfigModal = true">KAT-Coder</button>
|
|
46
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Longcat'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.longcat.chat/anthropic'; newClaudeConfig.model = 'LongCat-Flash-Chat'; showClaudeConfigModal = true">Longcat</button>
|
|
47
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'MiniMax'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.minimaxi.com/anthropic'; newClaudeConfig.model = 'MiniMax-M2.7'; showClaudeConfigModal = true">MiniMax</button>
|
|
48
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'MiniMax en'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.minimax.io/anthropic'; newClaudeConfig.model = 'MiniMax-M2.7'; showClaudeConfigModal = true">MiniMax en</button>
|
|
49
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'DouBaoSeed'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://ark.cn-beijing.volces.com/api/coding'; newClaudeConfig.model = 'doubao-seed-2-0-code-preview-latest'; showClaudeConfigModal = true">DouBaoSeed</button>
|
|
50
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'BaiLing'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.tbox.cn/api/anthropic'; newClaudeConfig.model = 'Ling-2.5-1T'; showClaudeConfigModal = true">BaiLing</button>
|
|
51
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'ModelScope'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api-inference.modelscope.cn'; newClaudeConfig.model = 'ZhipuAI/GLM-5'; showClaudeConfigModal = true">ModelScope</button>
|
|
52
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'AiHubMix'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://aihubmix.com'; newClaudeConfig.model = 'glm-4.7'; showClaudeConfigModal = true">AiHubMix</button>
|
|
53
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'DMXAPI'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://www.dmxapi.cn'; newClaudeConfig.model = 'glm-4.7'; showClaudeConfigModal = true">DMXAPI</button>
|
|
54
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'PackyCode'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://www.packyapi.com'; newClaudeConfig.model = 'glm-4.7'; showClaudeConfigModal = true">PackyCode</button>
|
|
55
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'AnyRouter'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://anyrouter.top'; newClaudeConfig.model = 'claude-opus-4-7[1m]'; showClaudeConfigModal = true">AnyRouter</button>
|
|
56
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Xiaomi MiMo'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.xiaomimimo.com/anthropic'; newClaudeConfig.model = 'mimo-v2.5-pro'; showClaudeConfigModal = true">Xiaomi MiMo</button>
|
|
57
|
-
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Xiaomi Token Plan'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://token-plan-cn.xiaomimimo.com/anthropic'; newClaudeConfig.model = 'mimo-v2.5-pro'; showClaudeConfigModal = true">Xiaomi Token Plan</button>
|
|
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'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">Claude Official</button>
|
|
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'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">DeepSeek</button>
|
|
40
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Zhipu GLM'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://open.bigmodel.cn/api/anthropic'; newClaudeConfig.model = 'glm-5'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">Zhipu GLM</button>
|
|
41
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Z.ai GLM'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.z.ai/api/anthropic'; newClaudeConfig.model = 'glm-5'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">Z.ai GLM</button>
|
|
42
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Qwen Coder'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://coding.dashscope.aliyuncs.com/apps/anthropic'; newClaudeConfig.model = 'qwen3-coder'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">Qwen Coder</button>
|
|
43
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Kimi k2'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.moonshot.cn/anthropic'; newClaudeConfig.model = 'kimi-k2.5'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">Kimi k2</button>
|
|
44
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Kimi For Coding'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.kimi.com/coding/'; newClaudeConfig.model = 'kimi-k2.5'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">Kimi For Coding</button>
|
|
45
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'KAT-Coder'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://vanchin.streamlake.ai/api/gateway/v1/endpoints/${ENDPOINT_ID}/claude-code-proxy'; newClaudeConfig.model = 'KAT-Coder-Pro V1'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">KAT-Coder</button>
|
|
46
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Longcat'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.longcat.chat/anthropic'; newClaudeConfig.model = 'LongCat-Flash-Chat'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">Longcat</button>
|
|
47
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'MiniMax'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.minimaxi.com/anthropic'; newClaudeConfig.model = 'MiniMax-M2.7'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">MiniMax</button>
|
|
48
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'MiniMax en'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.minimax.io/anthropic'; newClaudeConfig.model = 'MiniMax-M2.7'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">MiniMax en</button>
|
|
49
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'DouBaoSeed'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://ark.cn-beijing.volces.com/api/coding'; newClaudeConfig.model = 'doubao-seed-2-0-code-preview-latest'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">DouBaoSeed</button>
|
|
50
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'BaiLing'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.tbox.cn/api/anthropic'; newClaudeConfig.model = 'Ling-2.5-1T'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">BaiLing</button>
|
|
51
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'ModelScope'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api-inference.modelscope.cn'; newClaudeConfig.model = 'ZhipuAI/GLM-5'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">ModelScope</button>
|
|
52
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'AiHubMix'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://aihubmix.com'; newClaudeConfig.model = 'glm-4.7'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">AiHubMix</button>
|
|
53
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'DMXAPI'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://www.dmxapi.cn'; newClaudeConfig.model = 'glm-4.7'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">DMXAPI</button>
|
|
54
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'PackyCode'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://www.packyapi.com'; newClaudeConfig.model = 'glm-4.7'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">PackyCode</button>
|
|
55
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'AnyRouter'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://anyrouter.top'; newClaudeConfig.model = 'claude-opus-4-7[1m]'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">AnyRouter</button>
|
|
56
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Xiaomi MiMo'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.xiaomimimo.com/anthropic'; newClaudeConfig.model = 'mimo-v2.5-pro'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">Xiaomi MiMo</button>
|
|
57
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Xiaomi Token Plan'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://token-plan-cn.xiaomimimo.com/anthropic'; newClaudeConfig.model = 'mimo-v2.5-pro'; showAddClaudeConfigKey = false; showClaudeConfigModal = true">Xiaomi Token Plan</button>
|
|
58
58
|
</div>
|
|
59
59
|
</div>
|
|
60
60
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
</div>
|
|
27
27
|
</template>
|
|
28
28
|
<template v-else>
|
|
29
|
-
<button class="btn-add" @click="showAddModal = true" v-if="!loading && !initError">
|
|
29
|
+
<button class="btn-add" @click="showAddProviderKey = false; showAddModal = true" v-if="!loading && !initError">
|
|
30
30
|
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 4v12M4 10h12"/></svg>
|
|
31
31
|
{{ t('config.addProvider') }}
|
|
32
32
|
</button>
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<span class="selector-title">{{ t('config.providerTemplate.title') }}</span>
|
|
37
37
|
</div>
|
|
38
38
|
<div class="btn-group" style="flex-wrap: wrap; gap: 8px; margin-top: 0;">
|
|
39
|
-
<button v-for="tpl in codexProviderTemplates" :key="tpl.name" type="button" class="btn-mini" @click="newProvider.name = tpl.name; newProvider.url = tpl.url; newProvider.
|
|
39
|
+
<button v-for="tpl in codexProviderTemplates" :key="tpl.name" type="button" class="btn-mini" @click="newProvider.name = tpl.name; newProvider.url = tpl.url; newProvider.model = tpl.model || ''; newProvider.useTransform = !!tpl.useTransform; showAddProviderKey = false; showAddModal = true">{{ tpl.label }}</button>
|
|
40
40
|
</div>
|
|
41
41
|
</div>
|
|
42
42
|
|
|
@@ -122,11 +122,11 @@
|
|
|
122
122
|
<div v-if="!loading && !initError" class="card-list">
|
|
123
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
|
-
<div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}<span v-if="isTransformProvider(provider)" class="card-icon-dot" title="
|
|
125
|
+
<div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}<span v-if="isTransformProvider(provider)" class="card-icon-dot" :title="t('config.transformProvider.title')"></span></div>
|
|
126
126
|
<div class="card-content">
|
|
127
127
|
<div v-if="provider.name === 'local'" class="bridge-pool-summary">
|
|
128
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"
|
|
129
|
+
<span class="bridge-pool-summary-text">{{ t('config.localBridge.enabledCount', { enabled: localBridgeCandidateProviders().filter(cp => !isLocalBridgeExcluded(cp.name)).length, total: localBridgeCandidateProviders().length }) }}</span>
|
|
130
130
|
</div>
|
|
131
131
|
<div class="card-title">
|
|
132
132
|
<span>{{ provider.name }}</span>
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
<span v-if="speedResults[provider.name]" :class="['latency', speedResults[provider.name].ok ? 'ok' : 'error']">{{ formatLatency(speedResults[provider.name]) }}</span>
|
|
141
141
|
<span :class="['pill', providerPillConfigured(provider) ? 'configured' : 'empty']">{{ providerPillText(provider) }}</span>
|
|
142
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="'
|
|
143
|
+
<button v-if="provider.name === 'local'" class="card-action-btn bridge-pool-trigger" @click="showCodexBridgePoolModal = true" :aria-label="t('config.localBridge.poolSettings')" :title="t('config.localBridge.poolSettings')">
|
|
144
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
145
|
</button>
|
|
146
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')">
|