@yesdgq/claude-buddy 1.1.2000 → 1.1.3010
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/assets/config.html +59 -45
- package/assets/config.js +645 -47
- package/assets/editor.css +219 -0
- package/assets/editor.html +60 -0
- package/assets/editor.js +299 -0
- package/assets/images/workbuddy.png +0 -0
- package/assets/libs/style.css +318 -16
- package/dist/index.js +1 -1
- package/package.json +5 -4
package/assets/config.js
CHANGED
|
@@ -1,9 +1,349 @@
|
|
|
1
1
|
const { createApp } = Vue;
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const ENV_NODE_TYPES = ['String', 'Boolean', 'Number', 'Array', 'Dictionary'];
|
|
4
|
+
const CONFIG_FILE_ITEMS = Object.freeze({
|
|
5
|
+
Claude: [{ key: 'claude-settings', path: '~/.claude/settings.json' }],
|
|
6
|
+
Codex: [{ key: 'codex-config', path: '~/.codex/config.toml' }],
|
|
7
|
+
WorkBuddy: [{ key: 'workbuddy-models', path: '~/.workbuddy/models.json' }]
|
|
8
|
+
});
|
|
9
|
+
const CONFIG_FILE_REFRESH_STORAGE_KEY = 'ccby-config-file-refresh';
|
|
10
|
+
|
|
11
|
+
function getConfigFileItems(cliName) {
|
|
12
|
+
return CONFIG_FILE_ITEMS[cliName] || [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let envNodeSequence = 0;
|
|
16
|
+
|
|
17
|
+
function createEnvNodeId() {
|
|
18
|
+
envNodeSequence += 1;
|
|
19
|
+
return `env-node-${Date.now()}-${envNodeSequence}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function inferEnvNodeType(value) {
|
|
23
|
+
if (Array.isArray(value)) return 'Array';
|
|
24
|
+
if (value !== null && typeof value === 'object') return 'Dictionary';
|
|
25
|
+
if (typeof value === 'boolean') return 'Boolean';
|
|
26
|
+
if (typeof value === 'number') return 'Number';
|
|
27
|
+
return 'String';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function createEnvNode(type = 'String', key = '') {
|
|
31
|
+
const normalizedType = ENV_NODE_TYPES.includes(type) ? type : 'String';
|
|
32
|
+
return {
|
|
33
|
+
id: createEnvNodeId(),
|
|
34
|
+
key,
|
|
35
|
+
type: normalizedType,
|
|
36
|
+
valueKind: null,
|
|
37
|
+
value: normalizedType === 'Boolean' ? false : (normalizedType === 'Number' ? 0 : ''),
|
|
38
|
+
children: [],
|
|
39
|
+
// Expansion is presentation state only; serialization omits it.
|
|
40
|
+
expanded: false
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeEnvNode(node, fallbackKey = '') {
|
|
45
|
+
const source = node && typeof node === 'object' && !Array.isArray(node)
|
|
46
|
+
? node
|
|
47
|
+
: { value: node };
|
|
48
|
+
const rawValue = Object.prototype.hasOwnProperty.call(source, 'value') ? source.value : '';
|
|
49
|
+
const inferredType = inferEnvNodeType(rawValue);
|
|
50
|
+
const normalizedType = ENV_NODE_TYPES.includes(source.type) ? source.type : inferredType;
|
|
51
|
+
const normalized = createEnvNode(normalizedType, typeof source.key === 'string' ? source.key : fallbackKey);
|
|
52
|
+
normalized.valueKind = source.valueKind || (
|
|
53
|
+
rawValue === null ? 'null' : (typeof rawValue === 'number' ? 'number' : null)
|
|
54
|
+
);
|
|
55
|
+
// Loaded nodes start collapsed unless an explicit in-memory expanded state is provided.
|
|
56
|
+
normalized.expanded = source.expanded === true;
|
|
57
|
+
|
|
58
|
+
if (normalizedType === 'String') {
|
|
59
|
+
normalized.value = rawValue === undefined || rawValue === null ? '' : String(rawValue);
|
|
60
|
+
return normalized;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (normalizedType === 'Boolean') {
|
|
64
|
+
normalized.value = rawValue === true || rawValue === 'true' || rawValue === 'YES' || rawValue === 'yes';
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (normalizedType === 'Number') {
|
|
69
|
+
const numericValue = Number(rawValue);
|
|
70
|
+
normalized.value = rawValue === '' || rawValue === null || rawValue === undefined
|
|
71
|
+
? 0
|
|
72
|
+
: (Number.isFinite(numericValue) ? numericValue : 0);
|
|
73
|
+
return normalized;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let childSources = Array.isArray(source.children) ? source.children : null;
|
|
77
|
+
if (!childSources && normalizedType === 'Array' && Array.isArray(rawValue)) {
|
|
78
|
+
childSources = rawValue.map(value => ({ value }));
|
|
79
|
+
}
|
|
80
|
+
if (!childSources && normalizedType === 'Dictionary' && rawValue && typeof rawValue === 'object' && !Array.isArray(rawValue)) {
|
|
81
|
+
childSources = Object.entries(rawValue).map(([key, value]) => ({ key, value }));
|
|
82
|
+
}
|
|
83
|
+
normalized.value = '';
|
|
84
|
+
normalized.children = (childSources || []).map((child, index) =>
|
|
85
|
+
normalizeEnvNode(child, normalizedType === 'Array' ? `Item ${index}` : '')
|
|
86
|
+
);
|
|
87
|
+
return normalized;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function serializeEnvNode(node) {
|
|
91
|
+
const normalizedType = ENV_NODE_TYPES.includes(node && node.type) ? node.type : 'String';
|
|
92
|
+
|
|
93
|
+
if (normalizedType === 'String') {
|
|
94
|
+
if (node && node.valueKind === 'null' && (node.value === undefined || node.value === null || node.value === '')) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (node && node.valueKind === 'number') {
|
|
98
|
+
const numericValue = Number(node.value);
|
|
99
|
+
if (node.value !== undefined && node.value !== null && node.value !== '' && Number.isFinite(numericValue)) {
|
|
100
|
+
return numericValue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return node && node.value !== undefined && node.value !== null
|
|
104
|
+
? String(node.value)
|
|
105
|
+
: '';
|
|
106
|
+
}
|
|
107
|
+
if (normalizedType === 'Boolean') {
|
|
108
|
+
return node && (node.value === true || node.value === 'true' || node.value === 'YES' || node.value === 'yes');
|
|
109
|
+
}
|
|
110
|
+
if (normalizedType === 'Number') {
|
|
111
|
+
const numericValue = Number(node && node.value);
|
|
112
|
+
return Number.isFinite(numericValue) ? numericValue : 0;
|
|
113
|
+
}
|
|
114
|
+
if (normalizedType === 'Array') {
|
|
115
|
+
return Array.isArray(node && node.children)
|
|
116
|
+
? node.children.map(child => serializeEnvNode(child))
|
|
117
|
+
: [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return Array.isArray(node && node.children)
|
|
121
|
+
? node.children.reduce((result, child) => {
|
|
122
|
+
const key = child && typeof child.key === 'string' ? child.key.trim() : '';
|
|
123
|
+
if (key) {
|
|
124
|
+
result[key] = serializeEnvNode(child);
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}, {})
|
|
128
|
+
: {};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function normalizeEnvNodes(env) {
|
|
132
|
+
if (Array.isArray(env)) {
|
|
133
|
+
// Compatibility with the previous cfg.json node-array format.
|
|
134
|
+
return env.map(node => normalizeEnvNode(node));
|
|
135
|
+
}
|
|
136
|
+
if (env && typeof env === 'object') {
|
|
137
|
+
return Object.entries(env).map(([key, value]) => normalizeEnvNode({ key, value }));
|
|
138
|
+
}
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function serializeEnvVariables(nodes) {
|
|
143
|
+
if (!Array.isArray(nodes)) {
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return nodes.reduce((result, node) => {
|
|
148
|
+
const key = node && typeof node.key === 'string' ? node.key.trim() : '';
|
|
149
|
+
if (key) {
|
|
150
|
+
result[key] = serializeEnvNode(node);
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}, {});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const EnvNodeEditor = {
|
|
157
|
+
name: 'EnvNodeEditor',
|
|
158
|
+
props: {
|
|
159
|
+
node: { type: Object, required: true },
|
|
160
|
+
index: { type: Number, required: true },
|
|
161
|
+
depth: { type: Number, required: true },
|
|
162
|
+
labels: { type: Object, required: true },
|
|
163
|
+
canDelete: { type: Boolean, default: true },
|
|
164
|
+
parentType: { type: String, default: 'Dictionary' },
|
|
165
|
+
confirmDelete: { type: Function, default: null }
|
|
166
|
+
},
|
|
167
|
+
data() {
|
|
168
|
+
return {
|
|
169
|
+
typeOptions: ENV_NODE_TYPES
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
emits: ['delete-node'],
|
|
173
|
+
computed: {
|
|
174
|
+
isContainer() {
|
|
175
|
+
return this.node.type === 'Array' || this.node.type === 'Dictionary';
|
|
176
|
+
},
|
|
177
|
+
isArrayItem() {
|
|
178
|
+
return this.parentType === 'Array';
|
|
179
|
+
},
|
|
180
|
+
itemLabel() {
|
|
181
|
+
return `${this.labels.arrayItem || 'Item'} ${this.index}`;
|
|
182
|
+
},
|
|
183
|
+
childParentType() {
|
|
184
|
+
return this.node.type;
|
|
185
|
+
},
|
|
186
|
+
isExpanded() {
|
|
187
|
+
return this.node.expanded !== false;
|
|
188
|
+
},
|
|
189
|
+
disclosureLabel() {
|
|
190
|
+
return this.isExpanded
|
|
191
|
+
? (this.labels.collapse || 'Collapse')
|
|
192
|
+
: (this.labels.expand || 'Expand');
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
methods: {
|
|
196
|
+
toggleExpanded() {
|
|
197
|
+
this.node.expanded = !this.isExpanded;
|
|
198
|
+
},
|
|
199
|
+
changeType(event) {
|
|
200
|
+
const nextType = event.target.value;
|
|
201
|
+
this.node.type = nextType;
|
|
202
|
+
this.node.valueKind = null;
|
|
203
|
+
if (nextType === 'Boolean') {
|
|
204
|
+
this.node.value = false;
|
|
205
|
+
this.node.children = [];
|
|
206
|
+
} else if (nextType === 'Number') {
|
|
207
|
+
this.node.value = 0;
|
|
208
|
+
this.node.children = [];
|
|
209
|
+
} else if (nextType === 'String') {
|
|
210
|
+
this.node.value = '';
|
|
211
|
+
this.node.children = [];
|
|
212
|
+
} else {
|
|
213
|
+
this.node.value = '';
|
|
214
|
+
this.node.children = [];
|
|
215
|
+
}
|
|
216
|
+
// A newly selected container starts open so its add-child action is immediately visible.
|
|
217
|
+
if (nextType === 'Array' || nextType === 'Dictionary') {
|
|
218
|
+
this.node.expanded = true;
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
addChild() {
|
|
222
|
+
const childKey = this.node.type === 'Array' ? `Item ${this.node.children.length}` : '';
|
|
223
|
+
this.node.expanded = true;
|
|
224
|
+
this.node.children.push(createEnvNode('String', childKey));
|
|
225
|
+
},
|
|
226
|
+
requestDelete() {
|
|
227
|
+
const removeNode = () => this.$emit('delete-node', []);
|
|
228
|
+
if (this.confirmDelete) {
|
|
229
|
+
this.confirmDelete(this.node, removeNode);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
removeNode();
|
|
233
|
+
},
|
|
234
|
+
handleChildDelete(childIndex, childPath) {
|
|
235
|
+
const normalizedPath = Array.isArray(childPath) ? childPath : [];
|
|
236
|
+
this.$emit('delete-node', [childIndex, ...normalizedPath]);
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
template: `
|
|
240
|
+
<div class="env-node" :class="{ 'env-node-container': isContainer }">
|
|
241
|
+
<div class="env-item env-node-row" :style="{ '--env-depth': depth }">
|
|
242
|
+
<button
|
|
243
|
+
v-if="isContainer"
|
|
244
|
+
type="button"
|
|
245
|
+
class="env-node-action env-node-disclosure"
|
|
246
|
+
:class="{ 'is-expanded': isExpanded }"
|
|
247
|
+
:title="disclosureLabel"
|
|
248
|
+
:aria-label="disclosureLabel"
|
|
249
|
+
:aria-expanded="isExpanded"
|
|
250
|
+
@click.stop="toggleExpanded"
|
|
251
|
+
>
|
|
252
|
+
<i aria-hidden="true" :class="isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'"></i>
|
|
253
|
+
</button>
|
|
254
|
+
<span v-else class="env-node-disclosure-spacer" aria-hidden="true"></span>
|
|
255
|
+
<span v-if="isArrayItem" class="env-array-key">{{ itemLabel }}</span>
|
|
256
|
+
<input
|
|
257
|
+
v-else
|
|
258
|
+
v-model="node.key"
|
|
259
|
+
class="env-key-input"
|
|
260
|
+
type="text"
|
|
261
|
+
:placeholder="labels.variableNamePlaceholder"
|
|
262
|
+
>
|
|
263
|
+
<select
|
|
264
|
+
:value="node.type"
|
|
265
|
+
class="env-type-select"
|
|
266
|
+
:aria-label="labels.valueType"
|
|
267
|
+
@change="changeType"
|
|
268
|
+
>
|
|
269
|
+
<option v-for="type in typeOptions" :key="type" :value="type">{{ type }}</option>
|
|
270
|
+
</select>
|
|
271
|
+
<input
|
|
272
|
+
v-if="node.type === 'String'"
|
|
273
|
+
:class="{ 'env-value-input': true, 'env-input-with-key': !isArrayItem }"
|
|
274
|
+
v-model="node.value"
|
|
275
|
+
type="text"
|
|
276
|
+
:placeholder="labels.variableValuePlaceholder"
|
|
277
|
+
>
|
|
278
|
+
<input
|
|
279
|
+
v-else-if="node.type === 'Number'"
|
|
280
|
+
:class="{ 'env-value-input': true, 'env-input-with-key': !isArrayItem }"
|
|
281
|
+
v-model.number="node.value"
|
|
282
|
+
type="number"
|
|
283
|
+
step="any"
|
|
284
|
+
:placeholder="labels.variableValuePlaceholder"
|
|
285
|
+
>
|
|
286
|
+
<select
|
|
287
|
+
v-else-if="node.type === 'Boolean'"
|
|
288
|
+
v-model="node.value"
|
|
289
|
+
:class="{ 'env-value-input': true, 'env-input-with-key': !isArrayItem, 'env-boolean-select': true }"
|
|
290
|
+
:aria-label="labels.variableValuePlaceholder"
|
|
291
|
+
>
|
|
292
|
+
<option :value="true">YES</option>
|
|
293
|
+
<option :value="false">NO</option>
|
|
294
|
+
</select>
|
|
295
|
+
<span v-else class="env-container-summary">
|
|
296
|
+
{{ node.children.length }} {{ node.children.length === 1 ? labels.item : labels.items }}
|
|
297
|
+
</span>
|
|
298
|
+
<button
|
|
299
|
+
v-if="isContainer"
|
|
300
|
+
type="button"
|
|
301
|
+
class="env-node-action env-node-add"
|
|
302
|
+
:title="labels.addChildItem"
|
|
303
|
+
@click="addChild"
|
|
304
|
+
>
|
|
305
|
+
<i class="fa fa-plus"></i>
|
|
306
|
+
</button>
|
|
307
|
+
<span v-else class="env-node-add-spacer" aria-hidden="true"></span>
|
|
308
|
+
<button
|
|
309
|
+
v-if="canDelete"
|
|
310
|
+
type="button"
|
|
311
|
+
class="env-node-action env-node-delete"
|
|
312
|
+
:title="labels.deleteThisEnvVar"
|
|
313
|
+
@click="requestDelete"
|
|
314
|
+
>
|
|
315
|
+
<i class="fa fa-times"></i>
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
<div
|
|
319
|
+
v-if="isContainer && isExpanded && node.children.length"
|
|
320
|
+
class="env-node-children"
|
|
321
|
+
:style="{ '--env-guide-depth': depth }"
|
|
322
|
+
>
|
|
323
|
+
<env-node-editor
|
|
324
|
+
v-for="(child, childIndex) in node.children"
|
|
325
|
+
:key="child.id"
|
|
326
|
+
:node="child"
|
|
327
|
+
:index="childIndex"
|
|
328
|
+
:depth="depth + 1"
|
|
329
|
+
:labels="labels"
|
|
330
|
+
:parent-type="childParentType"
|
|
331
|
+
:confirm-delete="confirmDelete"
|
|
332
|
+
@delete-node="handleChildDelete(childIndex, $event)"
|
|
333
|
+
></env-node-editor>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
`
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const app = createApp({
|
|
4
340
|
data() {
|
|
5
341
|
return {
|
|
6
342
|
config: { AUTH: [], activeIndex: 0 },
|
|
343
|
+
pendingDeletedItems: {
|
|
344
|
+
modelKeys: [],
|
|
345
|
+
envKeys: []
|
|
346
|
+
},
|
|
7
347
|
loading: true,
|
|
8
348
|
saving: false,
|
|
9
349
|
error: null,
|
|
@@ -25,7 +365,7 @@ createApp({
|
|
|
25
365
|
cliList: [
|
|
26
366
|
{ name: 'Claude', label: 'Claude', icon: 'images/claude.png' },
|
|
27
367
|
{ name: 'Codex', label: 'Codex', icon: 'images/gpt.png' },
|
|
28
|
-
{ name: '
|
|
368
|
+
{ name: 'WorkBuddy', label: 'WorkBuddy', icon: 'images/workbuddy.png' }
|
|
29
369
|
],
|
|
30
370
|
// 安装命令列表
|
|
31
371
|
installCommands: [
|
|
@@ -71,6 +411,8 @@ createApp({
|
|
|
71
411
|
translations: {
|
|
72
412
|
en: {
|
|
73
413
|
helpTitle: 'User Guide',
|
|
414
|
+
configFileLabel: 'Config file',
|
|
415
|
+
openConfigFile: 'Open file editor',
|
|
74
416
|
helpDescription: 'Claude Buddy helps you quickly configure API keys, models, and environment variables for Claude Code and other CLI tools through a visual interface.',
|
|
75
417
|
feature1Title: 'API Key Management',
|
|
76
418
|
feature1Desc: 'Store and switch between multiple API keys easily',
|
|
@@ -101,16 +443,28 @@ createApp({
|
|
|
101
443
|
modelManagement: 'Model Management',
|
|
102
444
|
defaultModelTitle: 'Default Model',
|
|
103
445
|
defaultModelPlaceholder: 'Enter default GPT model',
|
|
446
|
+
workbuddyModelSectionTitle: 'Model Management',
|
|
447
|
+
workbuddyModelName: 'Model Name',
|
|
448
|
+
workbuddyModelPlaceholder: 'Enter model parameter value, e.g. gpt-4o',
|
|
104
449
|
collapse: 'Collapse',
|
|
105
450
|
expand: 'Expand',
|
|
106
451
|
modelNamePlaceholder: 'Model name',
|
|
107
452
|
modelValuePlaceholder: 'Model value',
|
|
108
453
|
deleteThisModel: 'Delete this model',
|
|
454
|
+
confirmDeleteModel: 'Delete model "{name}"? This action cannot be undone.',
|
|
455
|
+
modelDeleted: 'Model deleted',
|
|
109
456
|
addModel: 'Add Model',
|
|
110
457
|
envVariables: 'Environment Variables',
|
|
111
458
|
variableNamePlaceholder: 'Variable name',
|
|
112
459
|
variableValuePlaceholder: 'Variable value',
|
|
460
|
+
valueType: 'Value type',
|
|
461
|
+
addChildItem: 'Add child item',
|
|
462
|
+
arrayItem: 'Item',
|
|
463
|
+
item: 'item',
|
|
464
|
+
items: 'items',
|
|
113
465
|
deleteThisEnvVar: 'Delete this environment variable',
|
|
466
|
+
confirmDeleteEnvVariable: 'Delete environment variable "{name}"? This action cannot be undone.',
|
|
467
|
+
envVariableDeleted: 'Environment variable deleted',
|
|
114
468
|
addEnvVariable: 'Add Environment Variable',
|
|
115
469
|
addNewConfig: 'Add New Configuration',
|
|
116
470
|
saveConfig: 'Save Configuration',
|
|
@@ -127,7 +481,6 @@ createApp({
|
|
|
127
481
|
configDeleted: 'Configuration deleted',
|
|
128
482
|
deleteFailed: 'Failed to delete',
|
|
129
483
|
configActivated: 'Configuration activated',
|
|
130
|
-
notAvailableYet: 'Not available yet, stay tuned',
|
|
131
484
|
saveFailed: 'Save failed',
|
|
132
485
|
cancel: 'Cancel',
|
|
133
486
|
confirm: 'Confirm',
|
|
@@ -143,6 +496,8 @@ createApp({
|
|
|
143
496
|
},
|
|
144
497
|
zh: {
|
|
145
498
|
helpTitle: '使用说明',
|
|
499
|
+
configFileLabel: '配置文件',
|
|
500
|
+
openConfigFile: '打开文件编辑器',
|
|
146
501
|
helpDescription: 'Claude Buddy 是一款简单易用的 CLI 工具,通过可视化界面帮助您快速配置 Claude Code 等 CLI 工具的 API 密钥、模型和环境变量。',
|
|
147
502
|
feature1Title: 'API 密钥管理',
|
|
148
503
|
feature1Desc: '轻松存储和切换多组 API 密钥',
|
|
@@ -173,16 +528,28 @@ createApp({
|
|
|
173
528
|
modelManagement: '模型管理',
|
|
174
529
|
defaultModelTitle: '默认模型',
|
|
175
530
|
defaultModelPlaceholder: '输入默认gpt模型',
|
|
531
|
+
workbuddyModelSectionTitle: '模型管理',
|
|
532
|
+
workbuddyModelName: '模型名称',
|
|
533
|
+
workbuddyModelPlaceholder: '输入模型参数值,例如gpt-4o',
|
|
176
534
|
collapse: '收起',
|
|
177
535
|
expand: '展开',
|
|
178
536
|
modelNamePlaceholder: '模型名称',
|
|
179
537
|
modelValuePlaceholder: '模型值',
|
|
180
538
|
deleteThisModel: '删除此模型',
|
|
539
|
+
confirmDeleteModel: '确定要删除模型“{name}”吗?此操作无法撤销。',
|
|
540
|
+
modelDeleted: '模型已删除',
|
|
181
541
|
addModel: '添加模型',
|
|
182
542
|
envVariables: '环境变量',
|
|
183
543
|
variableNamePlaceholder: '变量名',
|
|
184
544
|
variableValuePlaceholder: '变量值',
|
|
545
|
+
valueType: '值类型',
|
|
546
|
+
addChildItem: '添加下级项目',
|
|
547
|
+
arrayItem: 'Item',
|
|
548
|
+
item: '项',
|
|
549
|
+
items: '项',
|
|
185
550
|
deleteThisEnvVar: '删除此环境变量',
|
|
551
|
+
confirmDeleteEnvVariable: '确定要删除环境变量“{name}”吗?此操作无法撤销。',
|
|
552
|
+
envVariableDeleted: '环境变量已删除',
|
|
186
553
|
addEnvVariable: '添加环境变量',
|
|
187
554
|
addNewConfig: '添加新配置',
|
|
188
555
|
saveConfig: '保存配置',
|
|
@@ -199,7 +566,6 @@ createApp({
|
|
|
199
566
|
configDeleted: '配置已删除',
|
|
200
567
|
deleteFailed: '删除失败',
|
|
201
568
|
configActivated: '配置启用成功',
|
|
202
|
-
notAvailableYet: '暂未开放,敬请期待',
|
|
203
569
|
saveFailed: '保存失败',
|
|
204
570
|
cancel: '取消',
|
|
205
571
|
confirm: '确认',
|
|
@@ -220,6 +586,9 @@ createApp({
|
|
|
220
586
|
t() {
|
|
221
587
|
return this.translations[this.currentLang];
|
|
222
588
|
},
|
|
589
|
+
currentConfigFileItems() {
|
|
590
|
+
return getConfigFileItems(this.currentCli);
|
|
591
|
+
},
|
|
223
592
|
funLoadingMessage() {
|
|
224
593
|
const messages = {
|
|
225
594
|
en: [
|
|
@@ -365,6 +734,17 @@ createApp({
|
|
|
365
734
|
}
|
|
366
735
|
},
|
|
367
736
|
|
|
737
|
+
openConfigFile(fileItem) {
|
|
738
|
+
const editorLink = document.createElement('a');
|
|
739
|
+
editorLink.href = `/editor.html?fileKey=${encodeURIComponent(fileItem.key)}`;
|
|
740
|
+
editorLink.target = '_blank';
|
|
741
|
+
editorLink.rel = 'noopener noreferrer';
|
|
742
|
+
document.body.appendChild(editorLink);
|
|
743
|
+
editorLink.click();
|
|
744
|
+
editorLink.remove();
|
|
745
|
+
this.resetTimer();
|
|
746
|
+
},
|
|
747
|
+
|
|
368
748
|
// 动态调整输入框宽度
|
|
369
749
|
adjustInputWidth(event, index) {
|
|
370
750
|
const input = event.target;
|
|
@@ -468,14 +848,18 @@ createApp({
|
|
|
468
848
|
|
|
469
849
|
// 如果配置列表为空,自动添加一个空配置项
|
|
470
850
|
if (config.AUTH.length === 0) {
|
|
471
|
-
|
|
851
|
+
const emptyAuth = {
|
|
472
852
|
name: '',
|
|
473
853
|
BASE_URL: '',
|
|
474
854
|
TOKEN: '',
|
|
475
855
|
DEFAULT_MODEL: '',
|
|
476
856
|
MODELS: [],
|
|
477
857
|
ENV: []
|
|
478
|
-
}
|
|
858
|
+
};
|
|
859
|
+
if (cliType === 'workbuddy') {
|
|
860
|
+
emptyAuth.__workbuddyNew = true;
|
|
861
|
+
}
|
|
862
|
+
config.AUTH = [emptyAuth];
|
|
479
863
|
config.activeIndex = 0;
|
|
480
864
|
}
|
|
481
865
|
|
|
@@ -484,12 +868,24 @@ createApp({
|
|
|
484
868
|
if (!auth.ENV) {
|
|
485
869
|
auth.ENV = [];
|
|
486
870
|
}
|
|
871
|
+
auth.ENV = normalizeEnvNodes(auth.ENV);
|
|
487
872
|
if (!auth.MODELS) {
|
|
488
873
|
auth.MODELS = [];
|
|
489
874
|
}
|
|
490
|
-
if (cliType === 'codex' && typeof auth.DEFAULT_MODEL !== 'string') {
|
|
875
|
+
if ((cliType === 'codex' || cliType === 'workbuddy') && typeof auth.DEFAULT_MODEL !== 'string') {
|
|
491
876
|
auth.DEFAULT_MODEL = '';
|
|
492
877
|
}
|
|
878
|
+
if (cliType === 'workbuddy') {
|
|
879
|
+
// 这些字段只用于区分 models.json 中已有的模型和页面新建的卡片,页面不展示。
|
|
880
|
+
auth.__workbuddyModelExists = auth.__workbuddyModelExists === true;
|
|
881
|
+
auth.__workbuddyModelUrl = typeof auth.__workbuddyModelUrl === 'string'
|
|
882
|
+
? auth.__workbuddyModelUrl
|
|
883
|
+
: '';
|
|
884
|
+
auth.__workbuddyModelIndex = Number.isInteger(auth.__workbuddyModelIndex)
|
|
885
|
+
? auth.__workbuddyModelIndex
|
|
886
|
+
: -1;
|
|
887
|
+
auth.__workbuddyNew = auth.__workbuddyNew === true && !auth.__workbuddyModelExists;
|
|
888
|
+
}
|
|
493
889
|
});
|
|
494
890
|
|
|
495
891
|
// 先设置辅助数组,再设置 config,确保 Vue 响应式正确更新
|
|
@@ -500,6 +896,7 @@ createApp({
|
|
|
500
896
|
|
|
501
897
|
// 最后更新 config
|
|
502
898
|
this.config = config;
|
|
899
|
+
this.pendingDeletedItems = { modelKeys: [], envKeys: [] };
|
|
503
900
|
|
|
504
901
|
this.showToast(this.t.configLoadSuccess);
|
|
505
902
|
// 重置服务器倒计时
|
|
@@ -523,10 +920,17 @@ createApp({
|
|
|
523
920
|
try {
|
|
524
921
|
this.normalizeCodexDefaults();
|
|
525
922
|
// 创建配置副本,过滤掉空配置项
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
923
|
+
const saveAuthList = this.config.AUTH.filter(auth => !this.isEmptyConfig(auth));
|
|
924
|
+
const configToSave = this.prepareConfigForSave(
|
|
925
|
+
saveAuthList
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
// WorkBuddy 过滤空卡片后修正索引,确保同步到 models.json 的仍是当前卡片。
|
|
929
|
+
if (this.currentCli === 'WorkBuddy') {
|
|
930
|
+
const activeAuth = this.config.AUTH[this.config.activeIndex];
|
|
931
|
+
const nextActiveIndex = activeAuth ? saveAuthList.indexOf(activeAuth) : -1;
|
|
932
|
+
configToSave.activeIndex = nextActiveIndex >= 0 ? nextActiveIndex : 0;
|
|
933
|
+
}
|
|
530
934
|
|
|
531
935
|
// 如果过滤后没有配置项,确保至少有一个空配置项在前端显示
|
|
532
936
|
if (configToSave.AUTH.length === 0 && this.config.AUTH.length > 0) {
|
|
@@ -534,10 +938,14 @@ createApp({
|
|
|
534
938
|
return;
|
|
535
939
|
}
|
|
536
940
|
|
|
941
|
+
const deletedItems = this.getPendingDeletedItems();
|
|
537
942
|
const response = await axios.post('/api/config', {
|
|
538
943
|
config: configToSave,
|
|
539
|
-
cliType: this.currentCli.toLowerCase()
|
|
944
|
+
cliType: this.currentCli.toLowerCase(),
|
|
945
|
+
deletedItems
|
|
540
946
|
});
|
|
947
|
+
this.applyWorkBuddySaveResponse(response, this.currentCli === 'WorkBuddy' ? saveAuthList : configToSave.AUTH);
|
|
948
|
+
this.clearPendingDeletedItems(deletedItems);
|
|
541
949
|
this.showToast(this.t.configSaveSuccess);
|
|
542
950
|
// 彩带庆祝
|
|
543
951
|
this.showConfetti();
|
|
@@ -550,8 +958,32 @@ createApp({
|
|
|
550
958
|
}
|
|
551
959
|
},
|
|
552
960
|
|
|
961
|
+
// 保存后把后端确认的模型来源标记同步回当前页面,避免同一新卡片重复插入。
|
|
962
|
+
applyWorkBuddySaveResponse(response, submittedAuth = this.config.AUTH) {
|
|
963
|
+
if (this.currentCli !== 'WorkBuddy') {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
const savedAuth = response?.data?.config?.AUTH;
|
|
967
|
+
if (!Array.isArray(savedAuth)) {
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
submittedAuth.forEach((auth, index) => {
|
|
972
|
+
const match = savedAuth[index];
|
|
973
|
+
if (!auth || !match || typeof match !== 'object') {
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
auth.__workbuddyModelExists = match.__workbuddyModelExists === true;
|
|
977
|
+
auth.__workbuddyModelUrl = match.__workbuddyModelUrl || '';
|
|
978
|
+
auth.__workbuddyModelIndex = Number.isInteger(match.__workbuddyModelIndex)
|
|
979
|
+
? match.__workbuddyModelIndex
|
|
980
|
+
: -1;
|
|
981
|
+
auth.__workbuddyNew = match.__workbuddyNew === true;
|
|
982
|
+
});
|
|
983
|
+
},
|
|
984
|
+
|
|
553
985
|
normalizeCodexDefaults() {
|
|
554
|
-
if (this.currentCli !== 'Codex') {
|
|
986
|
+
if (this.currentCli !== 'Codex' && this.currentCli !== 'WorkBuddy') {
|
|
555
987
|
return;
|
|
556
988
|
}
|
|
557
989
|
this.config.AUTH.forEach(auth => {
|
|
@@ -559,15 +991,78 @@ createApp({
|
|
|
559
991
|
});
|
|
560
992
|
},
|
|
561
993
|
|
|
994
|
+
prepareConfigForSave(authList) {
|
|
995
|
+
const sourceAuth = Array.isArray(authList) ? authList : this.config.AUTH;
|
|
996
|
+
return {
|
|
997
|
+
...this.config,
|
|
998
|
+
AUTH: sourceAuth.map(auth => ({
|
|
999
|
+
...auth,
|
|
1000
|
+
ENV: serializeEnvVariables(auth.ENV)
|
|
1001
|
+
}))
|
|
1002
|
+
};
|
|
1003
|
+
},
|
|
1004
|
+
|
|
1005
|
+
getPendingDeletedItems() {
|
|
1006
|
+
return {
|
|
1007
|
+
modelKeys: Array.from(new Set(
|
|
1008
|
+
(this.pendingDeletedItems?.modelKeys || [])
|
|
1009
|
+
.filter(key => typeof key === 'string' && key.trim())
|
|
1010
|
+
.map(key => key.trim())
|
|
1011
|
+
)),
|
|
1012
|
+
envKeys: Array.from(new Set(
|
|
1013
|
+
(this.pendingDeletedItems?.envKeys || [])
|
|
1014
|
+
.filter(key => typeof key === 'string' && key.trim())
|
|
1015
|
+
.map(key => key.trim())
|
|
1016
|
+
))
|
|
1017
|
+
};
|
|
1018
|
+
},
|
|
1019
|
+
|
|
1020
|
+
recordDeletedItem(type, key) {
|
|
1021
|
+
const normalizedKey = typeof key === 'string' ? key.trim() : '';
|
|
1022
|
+
if (!normalizedKey || !['modelKeys', 'envKeys'].includes(type)) {
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
if (!Array.isArray(this.pendingDeletedItems[type])) {
|
|
1026
|
+
this.pendingDeletedItems[type] = [];
|
|
1027
|
+
}
|
|
1028
|
+
if (this.pendingDeletedItems[type].includes(normalizedKey)) {
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
this.pendingDeletedItems[type].push(normalizedKey);
|
|
1032
|
+
return true;
|
|
1033
|
+
},
|
|
1034
|
+
|
|
1035
|
+
removePendingDeletedItem(type, key) {
|
|
1036
|
+
if (!Array.isArray(this.pendingDeletedItems?.[type])) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
const normalizedKey = typeof key === 'string' ? key.trim() : '';
|
|
1040
|
+
this.pendingDeletedItems[type] = this.pendingDeletedItems[type].filter(item => item !== normalizedKey);
|
|
1041
|
+
},
|
|
1042
|
+
|
|
1043
|
+
clearPendingDeletedItems(deletedItems = {}) {
|
|
1044
|
+
['modelKeys', 'envKeys'].forEach(type => {
|
|
1045
|
+
const sentKeys = new Set(Array.isArray(deletedItems[type]) ? deletedItems[type] : []);
|
|
1046
|
+
if (sentKeys.size > 0) {
|
|
1047
|
+
this.pendingDeletedItems[type] = (this.pendingDeletedItems[type] || [])
|
|
1048
|
+
.filter(key => !sentKeys.has(key));
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
},
|
|
1052
|
+
|
|
562
1053
|
addAuth() {
|
|
563
|
-
|
|
1054
|
+
const auth = {
|
|
564
1055
|
name: '',
|
|
565
1056
|
BASE_URL: '',
|
|
566
1057
|
TOKEN: '',
|
|
567
1058
|
DEFAULT_MODEL: '',
|
|
568
1059
|
MODELS: [],
|
|
569
1060
|
ENV: []
|
|
570
|
-
}
|
|
1061
|
+
};
|
|
1062
|
+
if (this.currentCli === 'WorkBuddy') {
|
|
1063
|
+
auth.__workbuddyNew = true;
|
|
1064
|
+
}
|
|
1065
|
+
this.config.AUTH.push(auth);
|
|
571
1066
|
this.showTokens.push(false);
|
|
572
1067
|
this.showEnvSections.push(false);
|
|
573
1068
|
this.showModelSections.push(false);
|
|
@@ -617,14 +1112,18 @@ createApp({
|
|
|
617
1112
|
|
|
618
1113
|
// 如果删除后列表为空,添加一个空配置项
|
|
619
1114
|
if (this.config.AUTH.length === 0) {
|
|
620
|
-
|
|
1115
|
+
const emptyAuth = {
|
|
621
1116
|
name: '',
|
|
622
1117
|
BASE_URL: '',
|
|
623
1118
|
TOKEN: '',
|
|
624
1119
|
DEFAULT_MODEL: '',
|
|
625
1120
|
MODELS: [],
|
|
626
1121
|
ENV: []
|
|
627
|
-
}
|
|
1122
|
+
};
|
|
1123
|
+
if (this.currentCli === 'WorkBuddy') {
|
|
1124
|
+
emptyAuth.__workbuddyNew = true;
|
|
1125
|
+
}
|
|
1126
|
+
this.config.AUTH.push(emptyAuth);
|
|
628
1127
|
this.showTokens.push(false);
|
|
629
1128
|
this.showEnvSections.push(false);
|
|
630
1129
|
this.showModelSections.push(false);
|
|
@@ -636,15 +1135,20 @@ createApp({
|
|
|
636
1135
|
try {
|
|
637
1136
|
this.normalizeCodexDefaults();
|
|
638
1137
|
const filteredAuth = this.config.AUTH.filter(auth => !this.isEmptyConfig(auth));
|
|
639
|
-
const configToSave =
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
};
|
|
1138
|
+
const configToSave = this.prepareConfigForSave(
|
|
1139
|
+
filteredAuth.length > 0 ? filteredAuth : this.config.AUTH
|
|
1140
|
+
);
|
|
643
1141
|
|
|
644
|
-
await axios.post('/api/config', {
|
|
1142
|
+
const response = await axios.post('/api/config', {
|
|
645
1143
|
config: configToSave,
|
|
646
1144
|
cliType: this.currentCli.toLowerCase()
|
|
647
1145
|
});
|
|
1146
|
+
this.applyWorkBuddySaveResponse(
|
|
1147
|
+
response,
|
|
1148
|
+
this.currentCli === 'WorkBuddy'
|
|
1149
|
+
? (filteredAuth.length > 0 ? filteredAuth : this.config.AUTH)
|
|
1150
|
+
: configToSave.AUTH
|
|
1151
|
+
);
|
|
648
1152
|
this.showToast(this.t.configDeleted);
|
|
649
1153
|
// 重置服务器倒计时
|
|
650
1154
|
this.resetTimer();
|
|
@@ -673,15 +1177,15 @@ createApp({
|
|
|
673
1177
|
// 立即保存配置
|
|
674
1178
|
try {
|
|
675
1179
|
this.normalizeCodexDefaults();
|
|
676
|
-
const configToSave =
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
};
|
|
1180
|
+
const configToSave = this.prepareConfigForSave(
|
|
1181
|
+
this.config.AUTH.filter(auth => !this.isEmptyConfig(auth))
|
|
1182
|
+
);
|
|
680
1183
|
|
|
681
|
-
await axios.post('/api/config', {
|
|
1184
|
+
const response = await axios.post('/api/config', {
|
|
682
1185
|
config: configToSave,
|
|
683
1186
|
cliType: this.currentCli.toLowerCase()
|
|
684
1187
|
});
|
|
1188
|
+
this.applyWorkBuddySaveResponse(response, this.currentCli === 'WorkBuddy' ? this.config.AUTH.filter(auth => !this.isEmptyConfig(auth)) : configToSave.AUTH);
|
|
685
1189
|
this.showToast(this.t.configActivated);
|
|
686
1190
|
// 彩带庆祝
|
|
687
1191
|
this.showConfetti();
|
|
@@ -712,14 +1216,76 @@ createApp({
|
|
|
712
1216
|
},
|
|
713
1217
|
|
|
714
1218
|
addEnv(authIndex) {
|
|
715
|
-
this.config.AUTH[authIndex].ENV.push(
|
|
716
|
-
|
|
717
|
-
|
|
1219
|
+
this.config.AUTH[authIndex].ENV.push(createEnvNode());
|
|
1220
|
+
},
|
|
1221
|
+
|
|
1222
|
+
confirmDeleteEnvNode(node, onConfirm) {
|
|
1223
|
+
const nodeName = node && typeof node.key === 'string' && node.key.trim()
|
|
1224
|
+
? node.key.trim()
|
|
1225
|
+
: (this.t.arrayItem || 'Item');
|
|
1226
|
+
const message = this.t.confirmDeleteEnvVariable.replace('{name}', nodeName);
|
|
1227
|
+
this.showConfirmDialog(message, onConfirm);
|
|
1228
|
+
},
|
|
1229
|
+
|
|
1230
|
+
async persistInlineConfigChange(deletedItems = {}) {
|
|
1231
|
+
const configToSave = this.prepareConfigForSave(this.config.AUTH);
|
|
1232
|
+
const response = await axios.post('/api/config', {
|
|
1233
|
+
config: configToSave,
|
|
1234
|
+
cliType: this.currentCli.toLowerCase(),
|
|
1235
|
+
deletedItems
|
|
718
1236
|
});
|
|
1237
|
+
this.applyWorkBuddySaveResponse(response, configToSave.AUTH);
|
|
1238
|
+
this.resetTimer();
|
|
1239
|
+
return response;
|
|
719
1240
|
},
|
|
720
1241
|
|
|
721
|
-
deleteEnv(authIndex, envIndex) {
|
|
722
|
-
this.config.AUTH[authIndex]
|
|
1242
|
+
async deleteEnv(authIndex, envIndex, childPath = []) {
|
|
1243
|
+
const auth = this.config.AUTH[authIndex];
|
|
1244
|
+
const rootNode = auth && Array.isArray(auth.ENV) ? auth.ENV[envIndex] : null;
|
|
1245
|
+
const path = Array.isArray(childPath) ? childPath : [];
|
|
1246
|
+
if (!rootNode) {
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
let targetList = auth.ENV;
|
|
1251
|
+
let targetIndex = envIndex;
|
|
1252
|
+
if (path.length > 0) {
|
|
1253
|
+
let parentNode = rootNode;
|
|
1254
|
+
for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex += 1) {
|
|
1255
|
+
if (!parentNode || !Array.isArray(parentNode.children)) {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
parentNode = parentNode.children[path[pathIndex]];
|
|
1259
|
+
}
|
|
1260
|
+
if (!parentNode || !Array.isArray(parentNode.children)) {
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
targetList = parentNode.children;
|
|
1264
|
+
targetIndex = path[path.length - 1];
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const deletedNode = targetList[targetIndex];
|
|
1268
|
+
if (!deletedNode) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const rootKey = path.length === 0 && typeof rootNode.key === 'string'
|
|
1272
|
+
? rootNode.key.trim()
|
|
1273
|
+
: '';
|
|
1274
|
+
targetList.splice(targetIndex, 1);
|
|
1275
|
+
const recordedDeletion = this.recordDeletedItem('envKeys', rootKey);
|
|
1276
|
+
|
|
1277
|
+
try {
|
|
1278
|
+
const deletedItems = this.getPendingDeletedItems();
|
|
1279
|
+
await this.persistInlineConfigChange(deletedItems);
|
|
1280
|
+
this.clearPendingDeletedItems(deletedItems);
|
|
1281
|
+
this.showToast(this.t.envVariableDeleted);
|
|
1282
|
+
} catch (err) {
|
|
1283
|
+
targetList.splice(targetIndex, 0, deletedNode);
|
|
1284
|
+
if (recordedDeletion) {
|
|
1285
|
+
this.removePendingDeletedItem('envKeys', rootKey);
|
|
1286
|
+
}
|
|
1287
|
+
this.showToast(this.t.deleteFailed + ': ' + (err.response?.data?.error || err.message), 'error');
|
|
1288
|
+
}
|
|
723
1289
|
},
|
|
724
1290
|
|
|
725
1291
|
addModel(authIndex) {
|
|
@@ -729,14 +1295,43 @@ createApp({
|
|
|
729
1295
|
});
|
|
730
1296
|
},
|
|
731
1297
|
|
|
732
|
-
deleteModel(authIndex, modelIndex) {
|
|
733
|
-
this.config.AUTH[authIndex]
|
|
1298
|
+
async deleteModel(authIndex, modelIndex) {
|
|
1299
|
+
const auth = this.config.AUTH[authIndex];
|
|
1300
|
+
const model = auth && Array.isArray(auth.MODELS) ? auth.MODELS[modelIndex] : null;
|
|
1301
|
+
if (!model) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
const modelKey = typeof model.key === 'string' ? model.key.trim() : '';
|
|
1305
|
+
const modelName = modelKey || `#${modelIndex + 1}`;
|
|
1306
|
+
const message = this.t.confirmDeleteModel.replace('{name}', modelName);
|
|
1307
|
+
|
|
1308
|
+
this.showConfirmDialog(message, async () => {
|
|
1309
|
+
auth.MODELS.splice(modelIndex, 1);
|
|
1310
|
+
const recordedDeletion = this.recordDeletedItem('modelKeys', modelKey);
|
|
1311
|
+
try {
|
|
1312
|
+
const deletedItems = this.getPendingDeletedItems();
|
|
1313
|
+
await this.persistInlineConfigChange(deletedItems);
|
|
1314
|
+
this.clearPendingDeletedItems(deletedItems);
|
|
1315
|
+
this.showToast(this.t.modelDeleted);
|
|
1316
|
+
} catch (err) {
|
|
1317
|
+
auth.MODELS.splice(modelIndex, 0, model);
|
|
1318
|
+
if (recordedDeletion) {
|
|
1319
|
+
this.removePendingDeletedItem('modelKeys', modelKey);
|
|
1320
|
+
}
|
|
1321
|
+
this.showToast(this.t.deleteFailed + ': ' + (err.response?.data?.error || err.message), 'error');
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
734
1324
|
},
|
|
735
1325
|
|
|
736
1326
|
// 判断是否为空配置
|
|
737
1327
|
isEmptyConfig(auth) {
|
|
738
|
-
const hasDefaultModel = this.currentCli === 'Codex' && auth.DEFAULT_MODEL && auth.DEFAULT_MODEL.trim();
|
|
739
|
-
|
|
1328
|
+
const hasDefaultModel = (this.currentCli === 'Codex' || this.currentCli === 'WorkBuddy') && auth.DEFAULT_MODEL && auth.DEFAULT_MODEL.trim();
|
|
1329
|
+
const hasEnv = Array.isArray(auth.ENV) && auth.ENV.some(env => this.hasEnvRootKey(env));
|
|
1330
|
+
return !auth.name && !auth.BASE_URL && !auth.TOKEN && !hasDefaultModel && !hasEnv && (!auth.MODELS || auth.MODELS.length === 0);
|
|
1331
|
+
},
|
|
1332
|
+
|
|
1333
|
+
hasEnvRootKey(node) {
|
|
1334
|
+
return Boolean(node && typeof node.key === 'string' && node.key.trim());
|
|
740
1335
|
},
|
|
741
1336
|
|
|
742
1337
|
createAuthKey() {
|
|
@@ -785,11 +1380,6 @@ createApp({
|
|
|
785
1380
|
|
|
786
1381
|
// 选择 CLI 类型
|
|
787
1382
|
async selectCli(cliName) {
|
|
788
|
-
if (cliName === 'Gemini') {
|
|
789
|
-
this.showToast(this.t.notAvailableYet, 'error');
|
|
790
|
-
this.resetTimer();
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
1383
|
this.currentCli = cliName;
|
|
794
1384
|
await this.loadConfig(cliName.toLowerCase());
|
|
795
1385
|
},
|
|
@@ -977,15 +1567,15 @@ createApp({
|
|
|
977
1567
|
// 保存配置
|
|
978
1568
|
try {
|
|
979
1569
|
this.normalizeCodexDefaults();
|
|
980
|
-
const configToSave =
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
};
|
|
1570
|
+
const configToSave = this.prepareConfigForSave(
|
|
1571
|
+
this.config.AUTH.filter(auth => !this.isEmptyConfig(auth))
|
|
1572
|
+
);
|
|
984
1573
|
|
|
985
|
-
await axios.post('/api/config', {
|
|
1574
|
+
const response = await axios.post('/api/config', {
|
|
986
1575
|
config: configToSave,
|
|
987
1576
|
cliType: this.currentCli.toLowerCase()
|
|
988
1577
|
});
|
|
1578
|
+
this.applyWorkBuddySaveResponse(response, this.currentCli === 'WorkBuddy' ? this.config.AUTH.filter(auth => !this.isEmptyConfig(auth)) : configToSave.AUTH);
|
|
989
1579
|
// 重置服务器倒计时
|
|
990
1580
|
this.resetTimer();
|
|
991
1581
|
} catch (err) {
|
|
@@ -1001,6 +1591,11 @@ createApp({
|
|
|
1001
1591
|
}
|
|
1002
1592
|
},
|
|
1003
1593
|
mounted() {
|
|
1594
|
+
window.addEventListener('storage', event => {
|
|
1595
|
+
if (event.key === CONFIG_FILE_REFRESH_STORAGE_KEY && event.newValue) {
|
|
1596
|
+
window.location.reload();
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1004
1599
|
// 从 localStorage 读取语言设置
|
|
1005
1600
|
const savedLang = localStorage.getItem('ccby-lang');
|
|
1006
1601
|
if (savedLang && (savedLang === 'en' || savedLang === 'zh')) {
|
|
@@ -1025,4 +1620,7 @@ createApp({
|
|
|
1025
1620
|
});
|
|
1026
1621
|
});
|
|
1027
1622
|
}
|
|
1028
|
-
})
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
app.component('env-node-editor', EnvNodeEditor);
|
|
1626
|
+
app.mount('#app');
|