@yesdgq/claude-buddy 1.1.3000 → 1.1.3011
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 +43 -36
- package/assets/config.js +575 -34
- package/assets/editor.css +219 -0
- package/assets/editor.html +60 -0
- package/assets/editor.js +299 -0
- package/assets/libs/style.css +279 -2
- package/dist/index.js +1 -1
- package/package.json +8 -5
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,
|
|
@@ -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,7 +443,7 @@ createApp({
|
|
|
101
443
|
modelManagement: 'Model Management',
|
|
102
444
|
defaultModelTitle: 'Default Model',
|
|
103
445
|
defaultModelPlaceholder: 'Enter default GPT model',
|
|
104
|
-
workbuddyModelSectionTitle: 'Model',
|
|
446
|
+
workbuddyModelSectionTitle: 'Model Management',
|
|
105
447
|
workbuddyModelName: 'Model Name',
|
|
106
448
|
workbuddyModelPlaceholder: 'Enter model parameter value, e.g. gpt-4o',
|
|
107
449
|
collapse: 'Collapse',
|
|
@@ -109,11 +451,20 @@ createApp({
|
|
|
109
451
|
modelNamePlaceholder: 'Model name',
|
|
110
452
|
modelValuePlaceholder: 'Model value',
|
|
111
453
|
deleteThisModel: 'Delete this model',
|
|
454
|
+
confirmDeleteModel: 'Delete model "{name}"? This action cannot be undone.',
|
|
455
|
+
modelDeleted: 'Model deleted',
|
|
112
456
|
addModel: 'Add Model',
|
|
113
457
|
envVariables: 'Environment Variables',
|
|
114
458
|
variableNamePlaceholder: 'Variable name',
|
|
115
459
|
variableValuePlaceholder: 'Variable value',
|
|
460
|
+
valueType: 'Value type',
|
|
461
|
+
addChildItem: 'Add child item',
|
|
462
|
+
arrayItem: 'Item',
|
|
463
|
+
item: 'item',
|
|
464
|
+
items: 'items',
|
|
116
465
|
deleteThisEnvVar: 'Delete this environment variable',
|
|
466
|
+
confirmDeleteEnvVariable: 'Delete environment variable "{name}"? This action cannot be undone.',
|
|
467
|
+
envVariableDeleted: 'Environment variable deleted',
|
|
117
468
|
addEnvVariable: 'Add Environment Variable',
|
|
118
469
|
addNewConfig: 'Add New Configuration',
|
|
119
470
|
saveConfig: 'Save Configuration',
|
|
@@ -145,6 +496,8 @@ createApp({
|
|
|
145
496
|
},
|
|
146
497
|
zh: {
|
|
147
498
|
helpTitle: '使用说明',
|
|
499
|
+
configFileLabel: '配置文件',
|
|
500
|
+
openConfigFile: '打开文件编辑器',
|
|
148
501
|
helpDescription: 'Claude Buddy 是一款简单易用的 CLI 工具,通过可视化界面帮助您快速配置 Claude Code 等 CLI 工具的 API 密钥、模型和环境变量。',
|
|
149
502
|
feature1Title: 'API 密钥管理',
|
|
150
503
|
feature1Desc: '轻松存储和切换多组 API 密钥',
|
|
@@ -175,7 +528,7 @@ createApp({
|
|
|
175
528
|
modelManagement: '模型管理',
|
|
176
529
|
defaultModelTitle: '默认模型',
|
|
177
530
|
defaultModelPlaceholder: '输入默认gpt模型',
|
|
178
|
-
workbuddyModelSectionTitle: '
|
|
531
|
+
workbuddyModelSectionTitle: '模型管理',
|
|
179
532
|
workbuddyModelName: '模型名称',
|
|
180
533
|
workbuddyModelPlaceholder: '输入模型参数值,例如gpt-4o',
|
|
181
534
|
collapse: '收起',
|
|
@@ -183,11 +536,20 @@ createApp({
|
|
|
183
536
|
modelNamePlaceholder: '模型名称',
|
|
184
537
|
modelValuePlaceholder: '模型值',
|
|
185
538
|
deleteThisModel: '删除此模型',
|
|
539
|
+
confirmDeleteModel: '确定要删除模型“{name}”吗?此操作无法撤销。',
|
|
540
|
+
modelDeleted: '模型已删除',
|
|
186
541
|
addModel: '添加模型',
|
|
187
542
|
envVariables: '环境变量',
|
|
188
543
|
variableNamePlaceholder: '变量名',
|
|
189
544
|
variableValuePlaceholder: '变量值',
|
|
545
|
+
valueType: '值类型',
|
|
546
|
+
addChildItem: '添加下级项目',
|
|
547
|
+
arrayItem: 'Item',
|
|
548
|
+
item: '项',
|
|
549
|
+
items: '项',
|
|
190
550
|
deleteThisEnvVar: '删除此环境变量',
|
|
551
|
+
confirmDeleteEnvVariable: '确定要删除环境变量“{name}”吗?此操作无法撤销。',
|
|
552
|
+
envVariableDeleted: '环境变量已删除',
|
|
191
553
|
addEnvVariable: '添加环境变量',
|
|
192
554
|
addNewConfig: '添加新配置',
|
|
193
555
|
saveConfig: '保存配置',
|
|
@@ -224,6 +586,9 @@ createApp({
|
|
|
224
586
|
t() {
|
|
225
587
|
return this.translations[this.currentLang];
|
|
226
588
|
},
|
|
589
|
+
currentConfigFileItems() {
|
|
590
|
+
return getConfigFileItems(this.currentCli);
|
|
591
|
+
},
|
|
227
592
|
funLoadingMessage() {
|
|
228
593
|
const messages = {
|
|
229
594
|
en: [
|
|
@@ -369,6 +734,17 @@ createApp({
|
|
|
369
734
|
}
|
|
370
735
|
},
|
|
371
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
|
+
|
|
372
748
|
// 动态调整输入框宽度
|
|
373
749
|
adjustInputWidth(event, index) {
|
|
374
750
|
const input = event.target;
|
|
@@ -492,6 +868,7 @@ createApp({
|
|
|
492
868
|
if (!auth.ENV) {
|
|
493
869
|
auth.ENV = [];
|
|
494
870
|
}
|
|
871
|
+
auth.ENV = normalizeEnvNodes(auth.ENV);
|
|
495
872
|
if (!auth.MODELS) {
|
|
496
873
|
auth.MODELS = [];
|
|
497
874
|
}
|
|
@@ -519,6 +896,7 @@ createApp({
|
|
|
519
896
|
|
|
520
897
|
// 最后更新 config
|
|
521
898
|
this.config = config;
|
|
899
|
+
this.pendingDeletedItems = { modelKeys: [], envKeys: [] };
|
|
522
900
|
|
|
523
901
|
this.showToast(this.t.configLoadSuccess);
|
|
524
902
|
// 重置服务器倒计时
|
|
@@ -542,15 +920,15 @@ createApp({
|
|
|
542
920
|
try {
|
|
543
921
|
this.normalizeCodexDefaults();
|
|
544
922
|
// 创建配置副本,过滤掉空配置项
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
923
|
+
const saveAuthList = this.config.AUTH.filter(auth => !this.isEmptyConfig(auth));
|
|
924
|
+
const configToSave = this.prepareConfigForSave(
|
|
925
|
+
saveAuthList
|
|
926
|
+
);
|
|
549
927
|
|
|
550
928
|
// WorkBuddy 过滤空卡片后修正索引,确保同步到 models.json 的仍是当前卡片。
|
|
551
929
|
if (this.currentCli === 'WorkBuddy') {
|
|
552
930
|
const activeAuth = this.config.AUTH[this.config.activeIndex];
|
|
553
|
-
const nextActiveIndex = activeAuth ?
|
|
931
|
+
const nextActiveIndex = activeAuth ? saveAuthList.indexOf(activeAuth) : -1;
|
|
554
932
|
configToSave.activeIndex = nextActiveIndex >= 0 ? nextActiveIndex : 0;
|
|
555
933
|
}
|
|
556
934
|
|
|
@@ -560,11 +938,14 @@ createApp({
|
|
|
560
938
|
return;
|
|
561
939
|
}
|
|
562
940
|
|
|
941
|
+
const deletedItems = this.getPendingDeletedItems();
|
|
563
942
|
const response = await axios.post('/api/config', {
|
|
564
943
|
config: configToSave,
|
|
565
|
-
cliType: this.currentCli.toLowerCase()
|
|
944
|
+
cliType: this.currentCli.toLowerCase(),
|
|
945
|
+
deletedItems
|
|
566
946
|
});
|
|
567
|
-
this.applyWorkBuddySaveResponse(response, configToSave.AUTH);
|
|
947
|
+
this.applyWorkBuddySaveResponse(response, this.currentCli === 'WorkBuddy' ? saveAuthList : configToSave.AUTH);
|
|
948
|
+
this.clearPendingDeletedItems(deletedItems);
|
|
568
949
|
this.showToast(this.t.configSaveSuccess);
|
|
569
950
|
// 彩带庆祝
|
|
570
951
|
this.showConfetti();
|
|
@@ -610,6 +991,65 @@ createApp({
|
|
|
610
991
|
});
|
|
611
992
|
},
|
|
612
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
|
+
|
|
613
1053
|
addAuth() {
|
|
614
1054
|
const auth = {
|
|
615
1055
|
name: '',
|
|
@@ -695,16 +1135,20 @@ createApp({
|
|
|
695
1135
|
try {
|
|
696
1136
|
this.normalizeCodexDefaults();
|
|
697
1137
|
const filteredAuth = this.config.AUTH.filter(auth => !this.isEmptyConfig(auth));
|
|
698
|
-
const configToSave =
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
};
|
|
1138
|
+
const configToSave = this.prepareConfigForSave(
|
|
1139
|
+
filteredAuth.length > 0 ? filteredAuth : this.config.AUTH
|
|
1140
|
+
);
|
|
702
1141
|
|
|
703
1142
|
const response = await axios.post('/api/config', {
|
|
704
1143
|
config: configToSave,
|
|
705
1144
|
cliType: this.currentCli.toLowerCase()
|
|
706
1145
|
});
|
|
707
|
-
this.applyWorkBuddySaveResponse(
|
|
1146
|
+
this.applyWorkBuddySaveResponse(
|
|
1147
|
+
response,
|
|
1148
|
+
this.currentCli === 'WorkBuddy'
|
|
1149
|
+
? (filteredAuth.length > 0 ? filteredAuth : this.config.AUTH)
|
|
1150
|
+
: configToSave.AUTH
|
|
1151
|
+
);
|
|
708
1152
|
this.showToast(this.t.configDeleted);
|
|
709
1153
|
// 重置服务器倒计时
|
|
710
1154
|
this.resetTimer();
|
|
@@ -733,16 +1177,15 @@ createApp({
|
|
|
733
1177
|
// 立即保存配置
|
|
734
1178
|
try {
|
|
735
1179
|
this.normalizeCodexDefaults();
|
|
736
|
-
const configToSave =
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
};
|
|
1180
|
+
const configToSave = this.prepareConfigForSave(
|
|
1181
|
+
this.config.AUTH.filter(auth => !this.isEmptyConfig(auth))
|
|
1182
|
+
);
|
|
740
1183
|
|
|
741
1184
|
const response = await axios.post('/api/config', {
|
|
742
1185
|
config: configToSave,
|
|
743
1186
|
cliType: this.currentCli.toLowerCase()
|
|
744
1187
|
});
|
|
745
|
-
this.applyWorkBuddySaveResponse(response, configToSave.AUTH);
|
|
1188
|
+
this.applyWorkBuddySaveResponse(response, this.currentCli === 'WorkBuddy' ? this.config.AUTH.filter(auth => !this.isEmptyConfig(auth)) : configToSave.AUTH);
|
|
746
1189
|
this.showToast(this.t.configActivated);
|
|
747
1190
|
// 彩带庆祝
|
|
748
1191
|
this.showConfetti();
|
|
@@ -773,14 +1216,76 @@ createApp({
|
|
|
773
1216
|
},
|
|
774
1217
|
|
|
775
1218
|
addEnv(authIndex) {
|
|
776
|
-
this.config.AUTH[authIndex].ENV.push(
|
|
777
|
-
|
|
778
|
-
|
|
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
|
|
779
1236
|
});
|
|
1237
|
+
this.applyWorkBuddySaveResponse(response, configToSave.AUTH);
|
|
1238
|
+
this.resetTimer();
|
|
1239
|
+
return response;
|
|
780
1240
|
},
|
|
781
1241
|
|
|
782
|
-
deleteEnv(authIndex, envIndex) {
|
|
783
|
-
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
|
+
}
|
|
784
1289
|
},
|
|
785
1290
|
|
|
786
1291
|
addModel(authIndex) {
|
|
@@ -790,14 +1295,43 @@ createApp({
|
|
|
790
1295
|
});
|
|
791
1296
|
},
|
|
792
1297
|
|
|
793
|
-
deleteModel(authIndex, modelIndex) {
|
|
794
|
-
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
|
+
});
|
|
795
1324
|
},
|
|
796
1325
|
|
|
797
1326
|
// 判断是否为空配置
|
|
798
1327
|
isEmptyConfig(auth) {
|
|
799
1328
|
const hasDefaultModel = (this.currentCli === 'Codex' || this.currentCli === 'WorkBuddy') && auth.DEFAULT_MODEL && auth.DEFAULT_MODEL.trim();
|
|
800
|
-
|
|
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());
|
|
801
1335
|
},
|
|
802
1336
|
|
|
803
1337
|
createAuthKey() {
|
|
@@ -1033,16 +1567,15 @@ createApp({
|
|
|
1033
1567
|
// 保存配置
|
|
1034
1568
|
try {
|
|
1035
1569
|
this.normalizeCodexDefaults();
|
|
1036
|
-
const configToSave =
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
};
|
|
1570
|
+
const configToSave = this.prepareConfigForSave(
|
|
1571
|
+
this.config.AUTH.filter(auth => !this.isEmptyConfig(auth))
|
|
1572
|
+
);
|
|
1040
1573
|
|
|
1041
1574
|
const response = await axios.post('/api/config', {
|
|
1042
1575
|
config: configToSave,
|
|
1043
1576
|
cliType: this.currentCli.toLowerCase()
|
|
1044
1577
|
});
|
|
1045
|
-
this.applyWorkBuddySaveResponse(response, configToSave.AUTH);
|
|
1578
|
+
this.applyWorkBuddySaveResponse(response, this.currentCli === 'WorkBuddy' ? this.config.AUTH.filter(auth => !this.isEmptyConfig(auth)) : configToSave.AUTH);
|
|
1046
1579
|
// 重置服务器倒计时
|
|
1047
1580
|
this.resetTimer();
|
|
1048
1581
|
} catch (err) {
|
|
@@ -1058,6 +1591,11 @@ createApp({
|
|
|
1058
1591
|
}
|
|
1059
1592
|
},
|
|
1060
1593
|
mounted() {
|
|
1594
|
+
window.addEventListener('storage', event => {
|
|
1595
|
+
if (event.key === CONFIG_FILE_REFRESH_STORAGE_KEY && event.newValue) {
|
|
1596
|
+
window.location.reload();
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1061
1599
|
// 从 localStorage 读取语言设置
|
|
1062
1600
|
const savedLang = localStorage.getItem('ccby-lang');
|
|
1063
1601
|
if (savedLang && (savedLang === 'en' || savedLang === 'zh')) {
|
|
@@ -1082,4 +1620,7 @@ createApp({
|
|
|
1082
1620
|
});
|
|
1083
1621
|
});
|
|
1084
1622
|
}
|
|
1085
|
-
})
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
app.component('env-node-editor', EnvNodeEditor);
|
|
1626
|
+
app.mount('#app');
|