codexmate 0.0.7 → 0.0.8
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/.github/workflows/release.yml +122 -8
- package/README.md +48 -40
- package/README.zh-CN.md +48 -40
- package/cli.js +791 -1211
- package/lib/cli-file-utils.js +149 -0
- package/lib/cli-models-utils.js +152 -0
- package/lib/cli-network-utils.js +148 -0
- package/lib/cli-session-utils.js +121 -0
- package/lib/cli-utils.js +139 -0
- package/package.json +3 -2
- package/tests/e2e/helpers.js +214 -0
- package/tests/e2e/recent-health.e2e.js +6 -0
- package/tests/e2e/run.js +84 -302
- package/tests/e2e/test-claude.js +21 -0
- package/tests/e2e/test-config.js +124 -0
- package/tests/e2e/test-health-speed.js +75 -0
- package/tests/e2e/test-openclaw.js +47 -0
- package/tests/e2e/test-sessions.js +60 -0
- package/tests/e2e/test-setup.js +90 -0
- package/web-ui.html +912 -423
package/web-ui.html
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
设计系统 - Design Tokens
|
|
14
14
|
============================================ */
|
|
15
15
|
:root {
|
|
16
|
-
/*
|
|
17
|
-
--color-brand: #
|
|
18
|
-
--color-brand-dark: #
|
|
19
|
-
--color-brand-light: rgba(
|
|
16
|
+
/* 色彩系统:去除杂纹,强调干净留白与温柔橙红 */
|
|
17
|
+
--color-brand: #D0583A;
|
|
18
|
+
--color-brand-dark: #B8442B;
|
|
19
|
+
--color-brand-light: rgba(208, 88, 58, 0.14);
|
|
20
20
|
--color-brand-subtle: rgba(201, 94, 75, 0.2);
|
|
21
21
|
|
|
22
22
|
--color-bg: #F6EFE6;
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
--color-border-soft: rgba(216, 201, 184, 0.45);
|
|
33
33
|
--color-border-strong: rgba(216, 201, 184, 0.8);
|
|
34
34
|
|
|
35
|
-
--color-success: #
|
|
36
|
-
--color-error: #
|
|
35
|
+
--color-success: #4B8B6A;
|
|
36
|
+
--color-error: #C44536;
|
|
37
37
|
|
|
38
38
|
--bg-warm-gradient:
|
|
39
39
|
radial-gradient(circle at 16% 10%, rgba(201, 94, 75, 0.18), transparent 45%),
|
|
@@ -133,6 +133,9 @@
|
|
|
133
133
|
overflow-x: hidden;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/* ============================================
|
|
137
|
+
容器
|
|
138
|
+
============================================ */
|
|
136
139
|
body::before {
|
|
137
140
|
content: "";
|
|
138
141
|
position: fixed;
|
|
@@ -146,18 +149,230 @@
|
|
|
146
149
|
z-index: 0;
|
|
147
150
|
}
|
|
148
151
|
|
|
152
|
+
/* 背景网格 */
|
|
153
|
+
body::after {
|
|
154
|
+
content: "";
|
|
155
|
+
position: fixed;
|
|
156
|
+
inset: 0;
|
|
157
|
+
background-image:
|
|
158
|
+
linear-gradient(90deg, rgba(255, 255, 255, 0.15) 1px, transparent 1px),
|
|
159
|
+
linear-gradient(0deg, rgba(255, 255, 255, 0.12) 1px, transparent 1px);
|
|
160
|
+
background-size: 120px 120px;
|
|
161
|
+
opacity: 0.5;
|
|
162
|
+
pointer-events: none;
|
|
163
|
+
z-index: 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
149
166
|
/* ============================================
|
|
150
167
|
容器
|
|
151
168
|
============================================ */
|
|
152
169
|
.container {
|
|
153
170
|
width: 100%;
|
|
154
|
-
max-width:
|
|
171
|
+
max-width: 1200px;
|
|
155
172
|
margin: 0 auto;
|
|
156
|
-
padding
|
|
173
|
+
padding: var(--spacing-md) var(--spacing-sm) var(--spacing-lg);
|
|
157
174
|
position: relative;
|
|
158
175
|
z-index: 1;
|
|
159
176
|
}
|
|
160
177
|
|
|
178
|
+
/* ============================================
|
|
179
|
+
布局:单列居中
|
|
180
|
+
============================================ */
|
|
181
|
+
.app-shell {
|
|
182
|
+
display: grid;
|
|
183
|
+
grid-template-columns: 1fr;
|
|
184
|
+
gap: var(--spacing-md);
|
|
185
|
+
align-items: flex-start;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.side-nav {
|
|
189
|
+
display: none;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.brand-block {
|
|
193
|
+
margin-bottom: var(--spacing-md);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.brand-title {
|
|
197
|
+
font-size: 30px;
|
|
198
|
+
line-height: 1.05;
|
|
199
|
+
font-family: var(--font-family-display);
|
|
200
|
+
color: var(--color-text-primary);
|
|
201
|
+
letter-spacing: -0.02em;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.brand-title .accent {
|
|
205
|
+
color: var(--color-brand);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.brand-subtitle {
|
|
209
|
+
margin-top: 8px;
|
|
210
|
+
font-size: var(--font-size-secondary);
|
|
211
|
+
color: var(--color-text-tertiary);
|
|
212
|
+
line-height: 1.45;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.main-tabs {
|
|
216
|
+
display: flex;
|
|
217
|
+
gap: 10px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.main-tab-btn {
|
|
221
|
+
flex: 1;
|
|
222
|
+
text-align: center;
|
|
223
|
+
border: 1px solid rgba(216, 201, 184, 0.55);
|
|
224
|
+
background: rgba(255, 255, 255, 0.95);
|
|
225
|
+
border-radius: var(--radius-lg);
|
|
226
|
+
padding: 12px 14px;
|
|
227
|
+
cursor: pointer;
|
|
228
|
+
color: var(--color-text-secondary);
|
|
229
|
+
font-size: var(--font-size-body);
|
|
230
|
+
font-weight: var(--font-weight-secondary);
|
|
231
|
+
box-shadow: var(--shadow-subtle);
|
|
232
|
+
transition: all var(--transition-normal) var(--ease-spring);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.main-tab-btn:hover {
|
|
236
|
+
border-color: var(--color-brand);
|
|
237
|
+
color: var(--color-text-primary);
|
|
238
|
+
transform: translateY(-1px);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.main-tab-btn.active {
|
|
242
|
+
border-color: var(--color-brand);
|
|
243
|
+
box-shadow: 0 10px 24px rgba(27, 23, 20, 0.08);
|
|
244
|
+
color: var(--color-text-primary);
|
|
245
|
+
background: linear-gradient(135deg, rgba(201, 94, 75, 0.12), rgba(255, 255, 255, 0.95));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.main-panel {
|
|
249
|
+
min-width: 0;
|
|
250
|
+
background: rgba(255, 255, 255, 0.9);
|
|
251
|
+
border: 1px solid rgba(255, 255, 255, 0.65);
|
|
252
|
+
border-radius: 18px;
|
|
253
|
+
box-shadow: 0 12px 30px rgba(27, 23, 20, 0.08);
|
|
254
|
+
padding: var(--spacing-md) var(--spacing-lg);
|
|
255
|
+
backdrop-filter: blur(8px);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.panel-header {
|
|
259
|
+
margin-bottom: 12px;
|
|
260
|
+
text-align: left;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.hero {
|
|
264
|
+
margin-bottom: var(--spacing-sm);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.hero-title {
|
|
268
|
+
font-size: 48px;
|
|
269
|
+
line-height: 1.05;
|
|
270
|
+
font-family: var(--font-family-display);
|
|
271
|
+
color: var(--color-text-primary);
|
|
272
|
+
letter-spacing: -0.02em;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.hero-title .accent {
|
|
276
|
+
color: var(--color-brand);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.hero-subtitle {
|
|
280
|
+
margin-top: 8px;
|
|
281
|
+
font-size: var(--font-size-body);
|
|
282
|
+
color: var(--color-text-tertiary);
|
|
283
|
+
line-height: 1.5;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.top-tabs {
|
|
287
|
+
margin: 14px 0 18px;
|
|
288
|
+
background: rgba(255, 255, 255, 0.92);
|
|
289
|
+
border: 1px solid rgba(255, 255, 255, 0.7);
|
|
290
|
+
border-radius: 14px;
|
|
291
|
+
padding: 6px;
|
|
292
|
+
box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.06);
|
|
293
|
+
display: grid;
|
|
294
|
+
grid-template-columns: repeat(4, 1fr);
|
|
295
|
+
gap: 8px;
|
|
296
|
+
backdrop-filter: blur(6px);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.top-tab {
|
|
300
|
+
border: 1px solid rgba(216, 201, 184, 0.55);
|
|
301
|
+
border-radius: 12px;
|
|
302
|
+
background: rgba(255, 255, 255, 0.96);
|
|
303
|
+
padding: 11px 10px;
|
|
304
|
+
font-size: var(--font-size-body);
|
|
305
|
+
color: var(--color-text-secondary);
|
|
306
|
+
text-align: center;
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
transition: all var(--transition-normal) var(--ease-spring);
|
|
309
|
+
box-shadow: var(--shadow-subtle);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.top-tab:hover {
|
|
313
|
+
border-color: var(--color-brand);
|
|
314
|
+
color: var(--color-text-primary);
|
|
315
|
+
transform: translateY(-1px);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.top-tab.active {
|
|
319
|
+
border-color: var(--color-brand);
|
|
320
|
+
color: var(--color-text-primary);
|
|
321
|
+
background: linear-gradient(135deg, rgba(201, 94, 75, 0.12), rgba(255, 255, 255, 0.95));
|
|
322
|
+
box-shadow: 0 10px 24px rgba(27, 23, 20, 0.08);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.config-subtabs {
|
|
326
|
+
display: flex;
|
|
327
|
+
gap: 8px;
|
|
328
|
+
margin-bottom: 16px;
|
|
329
|
+
padding: 6px;
|
|
330
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0.7));
|
|
331
|
+
border-radius: var(--radius-lg);
|
|
332
|
+
border: 1px solid rgba(255, 255, 255, 0.7);
|
|
333
|
+
box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.05);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.config-subtab {
|
|
337
|
+
border: 1px solid var(--color-border-soft);
|
|
338
|
+
border-radius: var(--radius-lg);
|
|
339
|
+
padding: 10px 14px;
|
|
340
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(255, 250, 245, 0.9));
|
|
341
|
+
color: var(--color-text-secondary);
|
|
342
|
+
cursor: pointer;
|
|
343
|
+
font-size: var(--font-size-body);
|
|
344
|
+
font-weight: var(--font-weight-secondary);
|
|
345
|
+
transition: all var(--transition-normal) var(--ease-spring);
|
|
346
|
+
box-shadow: var(--shadow-subtle);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.config-subtab:hover {
|
|
350
|
+
border-color: var(--color-border-strong);
|
|
351
|
+
color: var(--color-text-primary);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.config-subtab.active {
|
|
355
|
+
border-color: var(--color-brand);
|
|
356
|
+
color: var(--color-text-primary);
|
|
357
|
+
background: linear-gradient(135deg, rgba(201, 94, 75, 0.18), rgba(255, 255, 255, 0.95));
|
|
358
|
+
box-shadow: var(--shadow-card);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.content-wrapper {
|
|
362
|
+
background: rgba(255, 255, 255, 0.9);
|
|
363
|
+
border: 1px solid rgba(255, 255, 255, 0.7);
|
|
364
|
+
border-radius: var(--radius-lg);
|
|
365
|
+
box-shadow: var(--shadow-card);
|
|
366
|
+
padding: var(--spacing-md);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.mode-content {
|
|
370
|
+
border-radius: var(--radius-lg);
|
|
371
|
+
background: rgba(255, 255, 255, 0.85);
|
|
372
|
+
box-shadow: var(--shadow-subtle);
|
|
373
|
+
padding: var(--spacing-sm);
|
|
374
|
+
}
|
|
375
|
+
|
|
161
376
|
/* ============================================
|
|
162
377
|
主标题
|
|
163
378
|
============================================ */
|
|
@@ -195,14 +410,14 @@
|
|
|
195
410
|
============================================ */
|
|
196
411
|
.segmented-control {
|
|
197
412
|
display: flex;
|
|
198
|
-
background:
|
|
199
|
-
border-radius: var(--radius-
|
|
200
|
-
padding:
|
|
201
|
-
margin-bottom:
|
|
413
|
+
background: rgba(255, 255, 255, 0.92);
|
|
414
|
+
border-radius: var(--radius-xl);
|
|
415
|
+
padding: 6px;
|
|
416
|
+
margin-bottom: 20px;
|
|
202
417
|
position: relative;
|
|
203
|
-
box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.
|
|
418
|
+
box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.06);
|
|
204
419
|
border: 1px solid rgba(255, 255, 255, 0.7);
|
|
205
|
-
backdrop-filter: blur(
|
|
420
|
+
backdrop-filter: blur(6px);
|
|
206
421
|
}
|
|
207
422
|
|
|
208
423
|
.segment {
|
|
@@ -245,7 +460,7 @@
|
|
|
245
460
|
卡片
|
|
246
461
|
============================================ */
|
|
247
462
|
.card {
|
|
248
|
-
background: linear-gradient(
|
|
463
|
+
background: linear-gradient(180deg, #fffdf9 0%, #fff8f2 100%);
|
|
249
464
|
border-radius: var(--radius-lg);
|
|
250
465
|
padding: var(--spacing-sm);
|
|
251
466
|
display: flex;
|
|
@@ -256,17 +471,17 @@
|
|
|
256
471
|
transform var(--transition-normal) var(--ease-spring),
|
|
257
472
|
box-shadow var(--transition-normal) var(--ease-spring),
|
|
258
473
|
background-color var(--transition-fast) var(--ease-smooth);
|
|
259
|
-
box-shadow:
|
|
474
|
+
box-shadow: 0 10px 24px rgba(27, 23, 20, 0.08);
|
|
260
475
|
user-select: none;
|
|
261
476
|
will-change: transform;
|
|
262
|
-
border: 1px solid
|
|
477
|
+
border: 1px solid rgba(216, 201, 184, 0.55);
|
|
263
478
|
position: relative;
|
|
264
479
|
overflow: hidden;
|
|
265
480
|
}
|
|
266
481
|
|
|
267
482
|
.card:hover {
|
|
268
483
|
transform: translateY(-2px) scale(1.005);
|
|
269
|
-
box-shadow:
|
|
484
|
+
box-shadow: 0 16px 32px rgba(27, 23, 20, 0.1);
|
|
270
485
|
}
|
|
271
486
|
|
|
272
487
|
.card::before,
|
|
@@ -393,6 +608,11 @@
|
|
|
393
608
|
transform: translateX(0);
|
|
394
609
|
}
|
|
395
610
|
|
|
611
|
+
.mode-cards .card-actions {
|
|
612
|
+
opacity: 1;
|
|
613
|
+
transform: translateX(0);
|
|
614
|
+
}
|
|
615
|
+
|
|
396
616
|
.card-action-btn {
|
|
397
617
|
width: 28px;
|
|
398
618
|
height: 28px;
|
|
@@ -524,54 +744,6 @@
|
|
|
524
744
|
gap: var(--spacing-xs);
|
|
525
745
|
}
|
|
526
746
|
|
|
527
|
-
.recent-list {
|
|
528
|
-
display: flex;
|
|
529
|
-
flex-wrap: wrap;
|
|
530
|
-
gap: 8px;
|
|
531
|
-
margin-top: 8px;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
.recent-item {
|
|
535
|
-
border: 1px solid var(--color-border-soft);
|
|
536
|
-
background: var(--color-surface-elevated);
|
|
537
|
-
border-radius: var(--radius-md);
|
|
538
|
-
padding: 10px 12px;
|
|
539
|
-
min-width: 170px;
|
|
540
|
-
text-align: left;
|
|
541
|
-
cursor: pointer;
|
|
542
|
-
transition: all var(--transition-fast) var(--ease-spring);
|
|
543
|
-
box-shadow: 0 2px 6px rgba(27, 23, 20, 0.06);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
.recent-item:hover {
|
|
547
|
-
transform: translateY(-1px);
|
|
548
|
-
box-shadow: 0 4px 12px rgba(27, 23, 20, 0.12);
|
|
549
|
-
border-color: var(--color-border);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
.recent-item:disabled {
|
|
553
|
-
opacity: 0.6;
|
|
554
|
-
cursor: not-allowed;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
.recent-provider {
|
|
558
|
-
font-size: var(--font-size-body);
|
|
559
|
-
font-weight: var(--font-weight-secondary);
|
|
560
|
-
color: var(--color-text-primary);
|
|
561
|
-
margin-bottom: 4px;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
.recent-model {
|
|
565
|
-
font-size: var(--font-size-caption);
|
|
566
|
-
color: var(--color-text-tertiary);
|
|
567
|
-
line-height: 1.4;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
.recent-empty {
|
|
571
|
-
font-size: var(--font-size-caption);
|
|
572
|
-
color: var(--color-text-tertiary);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
747
|
.health-report {
|
|
576
748
|
margin-top: 10px;
|
|
577
749
|
padding: 10px 12px;
|
|
@@ -657,7 +829,7 @@
|
|
|
657
829
|
outline: none;
|
|
658
830
|
cursor: pointer;
|
|
659
831
|
appearance: none;
|
|
660
|
-
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%
|
|
832
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%23505A66' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M2 4l4 4 4-4'/%3E%3C/svg%3E");
|
|
661
833
|
background-repeat: no-repeat;
|
|
662
834
|
background-position: right 14px center;
|
|
663
835
|
background-size: 12px;
|
|
@@ -807,6 +979,32 @@
|
|
|
807
979
|
justify-content: flex-end;
|
|
808
980
|
}
|
|
809
981
|
|
|
982
|
+
.session-toolbar-footer {
|
|
983
|
+
display: flex;
|
|
984
|
+
align-items: center;
|
|
985
|
+
justify-content: flex-end;
|
|
986
|
+
gap: var(--spacing-xs);
|
|
987
|
+
margin-top: -2px;
|
|
988
|
+
padding-top: 6px;
|
|
989
|
+
margin-bottom: 12px;
|
|
990
|
+
border-top: 1px dashed var(--color-border-soft);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.session-toolbar-footer .quick-option {
|
|
994
|
+
margin: 0;
|
|
995
|
+
padding: 6px 10px;
|
|
996
|
+
border-radius: var(--radius-sm);
|
|
997
|
+
border: 1px solid var(--color-border-soft);
|
|
998
|
+
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.8) 100%);
|
|
999
|
+
box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.04);
|
|
1000
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
1001
|
+
line-height: 1.2;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
.session-toolbar-footer .quick-option:hover {
|
|
1005
|
+
border-color: var(--color-border-strong);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
810
1008
|
.session-source-select,
|
|
811
1009
|
.session-path-select,
|
|
812
1010
|
.session-query-input,
|
|
@@ -906,37 +1104,10 @@
|
|
|
906
1104
|
margin-left: auto;
|
|
907
1105
|
}
|
|
908
1106
|
|
|
909
|
-
.btn-session-export
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
color: var(--color-text-secondary);
|
|
914
|
-
padding: 8px 12px;
|
|
915
|
-
font-size: var(--font-size-secondary);
|
|
916
|
-
font-weight: var(--font-weight-secondary);
|
|
917
|
-
cursor: pointer;
|
|
918
|
-
transition: all var(--transition-fast) var(--ease-spring);
|
|
919
|
-
white-space: nowrap;
|
|
920
|
-
box-shadow: var(--shadow-subtle);
|
|
921
|
-
letter-spacing: -0.01em;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
.btn-session-open {
|
|
925
|
-
border: 1px solid var(--color-border-soft);
|
|
926
|
-
border-radius: var(--radius-sm);
|
|
927
|
-
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.9) 100%);
|
|
928
|
-
color: var(--color-text-secondary);
|
|
929
|
-
padding: 8px 12px;
|
|
930
|
-
font-size: var(--font-size-secondary);
|
|
931
|
-
font-weight: var(--font-weight-secondary);
|
|
932
|
-
cursor: pointer;
|
|
933
|
-
transition: all var(--transition-fast) var(--ease-spring);
|
|
934
|
-
white-space: nowrap;
|
|
935
|
-
box-shadow: var(--shadow-subtle);
|
|
936
|
-
letter-spacing: -0.01em;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
.btn-session-clone {
|
|
1107
|
+
.btn-session-export,
|
|
1108
|
+
.btn-session-open,
|
|
1109
|
+
.btn-session-clone,
|
|
1110
|
+
.btn-session-refresh {
|
|
940
1111
|
border: 1px solid var(--color-border-soft);
|
|
941
1112
|
border-radius: var(--radius-sm);
|
|
942
1113
|
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.9) 100%);
|
|
@@ -952,7 +1123,7 @@
|
|
|
952
1123
|
}
|
|
953
1124
|
|
|
954
1125
|
.btn-session-delete {
|
|
955
|
-
border: 1px solid rgba(189, 70, 68, 0.
|
|
1126
|
+
border: 1px solid rgba(189, 70, 68, 0.45);
|
|
956
1127
|
border-radius: var(--radius-sm);
|
|
957
1128
|
background: linear-gradient(to bottom, rgba(255, 245, 245, 0.95) 0%, rgba(255, 255, 255, 0.9) 100%);
|
|
958
1129
|
color: #b74545;
|
|
@@ -966,21 +1137,6 @@
|
|
|
966
1137
|
letter-spacing: -0.01em;
|
|
967
1138
|
}
|
|
968
1139
|
|
|
969
|
-
.btn-session-refresh {
|
|
970
|
-
border: 1px solid var(--color-border-soft);
|
|
971
|
-
border-radius: var(--radius-sm);
|
|
972
|
-
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.9) 100%);
|
|
973
|
-
color: var(--color-text-secondary);
|
|
974
|
-
padding: 8px 12px;
|
|
975
|
-
font-size: var(--font-size-secondary);
|
|
976
|
-
font-weight: var(--font-weight-secondary);
|
|
977
|
-
cursor: pointer;
|
|
978
|
-
transition: all var(--transition-fast) var(--ease-spring);
|
|
979
|
-
white-space: nowrap;
|
|
980
|
-
box-shadow: var(--shadow-subtle);
|
|
981
|
-
letter-spacing: -0.01em;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
1140
|
.btn-session-refresh:hover {
|
|
985
1141
|
border-color: var(--color-brand);
|
|
986
1142
|
color: var(--color-brand);
|
|
@@ -1062,38 +1218,38 @@
|
|
|
1062
1218
|
min-height: 520px;
|
|
1063
1219
|
}
|
|
1064
1220
|
|
|
1065
|
-
.session-layout.session-standalone {
|
|
1066
|
-
grid-template-columns: minmax(0, 1fr);
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
.session-standalone-page {
|
|
1070
|
-
max-width: 960px;
|
|
1071
|
-
margin: 0 auto;
|
|
1072
|
-
padding: var(--spacing-sm) 0;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
.session-standalone-title {
|
|
1076
|
-
font-size: var(--font-size-title);
|
|
1077
|
-
font-weight: var(--font-weight-title);
|
|
1078
|
-
color: var(--color-text-primary);
|
|
1079
|
-
margin-bottom: var(--spacing-sm);
|
|
1080
|
-
letter-spacing: -0.01em;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
.session-standalone-text {
|
|
1084
|
-
white-space: pre-wrap;
|
|
1085
|
-
font-family: var(--font-family-body);
|
|
1086
|
-
font-size: var(--font-size-body);
|
|
1087
|
-
line-height: 1.7;
|
|
1088
|
-
color: var(--color-text-primary);
|
|
1089
|
-
word-break: break-word;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
.session-list {
|
|
1093
|
-
display: flex;
|
|
1094
|
-
flex-direction: column;
|
|
1095
|
-
gap: var(--spacing-xs);
|
|
1096
|
-
position: sticky;
|
|
1221
|
+
.session-layout.session-standalone {
|
|
1222
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
.session-standalone-page {
|
|
1226
|
+
max-width: 960px;
|
|
1227
|
+
margin: 0 auto;
|
|
1228
|
+
padding: var(--spacing-sm) 0;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
.session-standalone-title {
|
|
1232
|
+
font-size: var(--font-size-title);
|
|
1233
|
+
font-weight: var(--font-weight-title);
|
|
1234
|
+
color: var(--color-text-primary);
|
|
1235
|
+
margin-bottom: var(--spacing-sm);
|
|
1236
|
+
letter-spacing: -0.01em;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
.session-standalone-text {
|
|
1240
|
+
white-space: pre-wrap;
|
|
1241
|
+
font-family: var(--font-family-body);
|
|
1242
|
+
font-size: var(--font-size-body);
|
|
1243
|
+
line-height: 1.7;
|
|
1244
|
+
color: var(--color-text-primary);
|
|
1245
|
+
word-break: break-word;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.session-list {
|
|
1249
|
+
display: flex;
|
|
1250
|
+
flex-direction: column;
|
|
1251
|
+
gap: var(--spacing-xs);
|
|
1252
|
+
position: sticky;
|
|
1097
1253
|
top: 12px;
|
|
1098
1254
|
height: 100%;
|
|
1099
1255
|
max-height: none;
|
|
@@ -1207,21 +1363,6 @@
|
|
|
1207
1363
|
transition: all var(--transition-fast) var(--ease-spring);
|
|
1208
1364
|
}
|
|
1209
1365
|
|
|
1210
|
-
.session-item-delete {
|
|
1211
|
-
border: 1px solid rgba(189, 70, 68, 0.35);
|
|
1212
|
-
background: rgba(189, 70, 68, 0.08);
|
|
1213
|
-
color: #b74545;
|
|
1214
|
-
width: 28px;
|
|
1215
|
-
height: 28px;
|
|
1216
|
-
border-radius: 8px;
|
|
1217
|
-
display: inline-flex;
|
|
1218
|
-
align-items: center;
|
|
1219
|
-
justify-content: center;
|
|
1220
|
-
cursor: pointer;
|
|
1221
|
-
flex-shrink: 0;
|
|
1222
|
-
transition: all var(--transition-fast) var(--ease-spring);
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
1366
|
.session-item-copy:hover {
|
|
1226
1367
|
border-color: rgba(70, 86, 110, 0.7);
|
|
1227
1368
|
background: rgba(70, 86, 110, 0.16);
|
|
@@ -1229,35 +1370,17 @@
|
|
|
1229
1370
|
transform: translateY(-1px);
|
|
1230
1371
|
}
|
|
1231
1372
|
|
|
1232
|
-
.session-item-delete:hover {
|
|
1233
|
-
border-color: rgba(189, 70, 68, 0.7);
|
|
1234
|
-
background: rgba(189, 70, 68, 0.18);
|
|
1235
|
-
color: #9f3b3b;
|
|
1236
|
-
transform: translateY(-1px);
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
1373
|
.session-item-copy:disabled {
|
|
1240
1374
|
opacity: 0.5;
|
|
1241
1375
|
cursor: not-allowed;
|
|
1242
1376
|
transform: none;
|
|
1243
1377
|
}
|
|
1244
1378
|
|
|
1245
|
-
.session-item-delete:disabled {
|
|
1246
|
-
opacity: 0.5;
|
|
1247
|
-
cursor: not-allowed;
|
|
1248
|
-
transform: none;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
1379
|
.session-item-copy svg {
|
|
1252
1380
|
width: 16px;
|
|
1253
1381
|
height: 16px;
|
|
1254
1382
|
}
|
|
1255
1383
|
|
|
1256
|
-
.session-item-delete svg {
|
|
1257
|
-
width: 16px;
|
|
1258
|
-
height: 16px;
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
1384
|
.session-item-sub {
|
|
1262
1385
|
font-size: var(--font-size-caption);
|
|
1263
1386
|
color: var(--color-text-tertiary);
|
|
@@ -1510,6 +1633,10 @@
|
|
|
1510
1633
|
justify-content: flex-start;
|
|
1511
1634
|
}
|
|
1512
1635
|
|
|
1636
|
+
.session-toolbar-footer {
|
|
1637
|
+
justify-content: flex-start;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1513
1640
|
.session-list {
|
|
1514
1641
|
position: static;
|
|
1515
1642
|
max-height: 300px;
|
|
@@ -2221,6 +2348,19 @@
|
|
|
2221
2348
|
outline-offset: 2px;
|
|
2222
2349
|
}
|
|
2223
2350
|
|
|
2351
|
+
@media (max-width: 960px) {
|
|
2352
|
+
.container {
|
|
2353
|
+
padding: var(--spacing-sm);
|
|
2354
|
+
}
|
|
2355
|
+
.main-panel {
|
|
2356
|
+
padding: var(--spacing-sm) var(--spacing-sm);
|
|
2357
|
+
border-radius: 14px;
|
|
2358
|
+
}
|
|
2359
|
+
.top-tabs {
|
|
2360
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2224
2364
|
@media (max-width: 720px) {
|
|
2225
2365
|
.main-title {
|
|
2226
2366
|
font-size: 40px;
|
|
@@ -2236,37 +2376,93 @@
|
|
|
2236
2376
|
gap: 6px;
|
|
2237
2377
|
}
|
|
2238
2378
|
}
|
|
2379
|
+
|
|
2380
|
+
@media (max-width: 540px) {
|
|
2381
|
+
body {
|
|
2382
|
+
padding: var(--spacing-md) var(--spacing-sm);
|
|
2383
|
+
}
|
|
2384
|
+
.container {
|
|
2385
|
+
padding: 0 var(--spacing-sm) var(--spacing-md);
|
|
2386
|
+
}
|
|
2387
|
+
.hero-title {
|
|
2388
|
+
font-size: 36px;
|
|
2389
|
+
}
|
|
2390
|
+
.hero-subtitle {
|
|
2391
|
+
font-size: var(--font-size-secondary);
|
|
2392
|
+
}
|
|
2393
|
+
.top-tabs {
|
|
2394
|
+
grid-template-columns: repeat(1, minmax(0, 1fr));
|
|
2395
|
+
}
|
|
2396
|
+
.main-panel {
|
|
2397
|
+
padding: var(--spacing-sm);
|
|
2398
|
+
}
|
|
2399
|
+
.card {
|
|
2400
|
+
padding: 12px;
|
|
2401
|
+
}
|
|
2402
|
+
.session-layout {
|
|
2403
|
+
grid-template-columns: 1fr;
|
|
2404
|
+
height: auto;
|
|
2405
|
+
min-height: 0;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2239
2408
|
</style>
|
|
2240
2409
|
</head>
|
|
2241
2410
|
<body>
|
|
2242
2411
|
<div id="app" class="container" v-cloak>
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2412
|
+
<div class="hero" v-if="!sessionStandalone">
|
|
2413
|
+
<div class="hero-title">
|
|
2414
|
+
Codex <span class="accent">Mate.</span>
|
|
2415
|
+
</div>
|
|
2416
|
+
<div class="hero-subtitle">
|
|
2417
|
+
本地配置中枢,统一管理 Codex / Claude Code / OpenClaw / 会话。
|
|
2418
|
+
</div>
|
|
2419
|
+
</div>
|
|
2420
|
+
|
|
2421
|
+
<div v-if="!sessionStandalone" class="top-tabs">
|
|
2422
|
+
<button class="top-tab"
|
|
2423
|
+
:class="{ active: mainTab === 'config' && configMode === 'codex' }"
|
|
2424
|
+
@click="switchConfigMode('codex')">Codex 配置</button>
|
|
2425
|
+
<button class="top-tab"
|
|
2426
|
+
:class="{ active: mainTab === 'config' && configMode === 'claude' }"
|
|
2427
|
+
@click="switchConfigMode('claude')">Claude Code 配置</button>
|
|
2428
|
+
<button class="top-tab"
|
|
2429
|
+
:class="{ active: mainTab === 'config' && configMode === 'openclaw' }"
|
|
2430
|
+
@click="switchConfigMode('openclaw')">OpenClaw 配置</button>
|
|
2431
|
+
<button class="top-tab"
|
|
2432
|
+
:class="{ active: mainTab === 'sessions' }"
|
|
2433
|
+
@click="switchMainTab('sessions')">会话浏览</button>
|
|
2264
2434
|
</div>
|
|
2265
2435
|
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2436
|
+
<div :class="['app-shell', { standalone: sessionStandalone }]">
|
|
2437
|
+
<main class="main-panel">
|
|
2438
|
+
<div class="panel-header" v-if="!sessionStandalone">
|
|
2439
|
+
<h1 class="main-title">
|
|
2440
|
+
{{ mainTab === 'config' ? '配置中心' : '会话浏览' }}
|
|
2441
|
+
</h1>
|
|
2442
|
+
<p class="subtitle" v-if="mainTab === 'config'">
|
|
2443
|
+
本地配置中枢,统一管理 Codex / Claude Code / OpenClaw。
|
|
2444
|
+
</p>
|
|
2445
|
+
<p class="subtitle" v-else>
|
|
2446
|
+
浏览、导出或独立查看 Codex / Claude 会话记录。
|
|
2447
|
+
</p>
|
|
2448
|
+
</div>
|
|
2449
|
+
|
|
2450
|
+
<div v-if="false && mainTab === 'config' && !sessionStandalone" class="config-subtabs">
|
|
2451
|
+
<button :class="['config-subtab', { active: configMode === 'codex' }]" @click="switchConfigMode('codex')">
|
|
2452
|
+
Codex 配置
|
|
2453
|
+
</button>
|
|
2454
|
+
<button :class="['config-subtab', { active: configMode === 'claude' }]" @click="switchConfigMode('claude')">
|
|
2455
|
+
Claude Code 配置
|
|
2456
|
+
</button>
|
|
2457
|
+
<button :class="['config-subtab', { active: configMode === 'openclaw' }]" @click="switchConfigMode('openclaw')">
|
|
2458
|
+
OpenClaw 配置
|
|
2459
|
+
</button>
|
|
2460
|
+
</div>
|
|
2461
|
+
|
|
2462
|
+
<!-- 内容包裹器 - 稳定布局 -->
|
|
2463
|
+
<div class="content-wrapper">
|
|
2464
|
+
<!-- Codex 配置模式 -->
|
|
2465
|
+
<div v-show="mainTab === 'config' && configMode === 'codex'" class="mode-content mode-cards">
|
|
2270
2466
|
<!-- 添加提供商按钮 -->
|
|
2271
2467
|
<button class="btn-add" @click="showAddModal = true" v-if="!loading && !initError">
|
|
2272
2468
|
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -2320,22 +2516,14 @@
|
|
|
2320
2516
|
|
|
2321
2517
|
<div class="selector-section">
|
|
2322
2518
|
<div class="selector-header">
|
|
2323
|
-
<span class="selector-title"
|
|
2324
|
-
<span v-if="recentLoading" class="selector-title">加载中...</span>
|
|
2325
|
-
</div>
|
|
2326
|
-
<div v-if="recentConfigs.length === 0" class="recent-empty">
|
|
2327
|
-
暂无记录
|
|
2519
|
+
<span class="selector-title">服务档位</span>
|
|
2328
2520
|
</div>
|
|
2329
|
-
<
|
|
2330
|
-
<
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
:disabled="loading || !!initError">
|
|
2336
|
-
<div class="recent-provider">{{ item.provider }}</div>
|
|
2337
|
-
<div class="recent-model">{{ item.model }}</div>
|
|
2338
|
-
</button>
|
|
2521
|
+
<select class="model-select" v-model="serviceTier" @change="onServiceTierChange">
|
|
2522
|
+
<option value="fast">fast(默认)</option>
|
|
2523
|
+
<option value="standard">standard</option>
|
|
2524
|
+
</select>
|
|
2525
|
+
<div class="config-template-hint">
|
|
2526
|
+
仅 fast 会写入 <code>service_tier</code>。
|
|
2339
2527
|
</div>
|
|
2340
2528
|
</div>
|
|
2341
2529
|
|
|
@@ -2384,6 +2572,13 @@
|
|
|
2384
2572
|
<path d="M13 2L3 14h7l-1 8 12-14h-7l-1-6z"/>
|
|
2385
2573
|
</svg>
|
|
2386
2574
|
</button>
|
|
2575
|
+
<button class="card-action-btn" :class="{ loading: providerShareLoading[provider.name] }" @click="copyProviderShareCommand(provider)" title="分享导入命令">
|
|
2576
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2577
|
+
<path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
|
|
2578
|
+
<path d="M16 6l-4-4-4 4"/>
|
|
2579
|
+
<path d="M12 2v14"/>
|
|
2580
|
+
</svg>
|
|
2581
|
+
</button>
|
|
2387
2582
|
<button class="card-action-btn" @click="openEditModal(provider)" title="编辑">
|
|
2388
2583
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2389
2584
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
@@ -2403,7 +2598,7 @@
|
|
|
2403
2598
|
</div>
|
|
2404
2599
|
|
|
2405
2600
|
<!-- Claude Code 配置模式 -->
|
|
2406
|
-
<div v-show="configMode === 'claude'" class="mode-content">
|
|
2601
|
+
<div v-show="mainTab === 'config' && configMode === 'claude'" class="mode-content mode-cards">
|
|
2407
2602
|
<!-- 添加提供商按钮 -->
|
|
2408
2603
|
<button class="btn-add" @click="openClaudeConfigModal" v-if="!loading && !initError">
|
|
2409
2604
|
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -2419,7 +2614,16 @@
|
|
|
2419
2614
|
<div class="selector-header">
|
|
2420
2615
|
<span class="selector-title">模型</span>
|
|
2421
2616
|
</div>
|
|
2617
|
+
<select
|
|
2618
|
+
v-if="claudeModelHasList"
|
|
2619
|
+
class="model-select"
|
|
2620
|
+
v-model="currentClaudeModel"
|
|
2621
|
+
@change="onClaudeModelChange"
|
|
2622
|
+
>
|
|
2623
|
+
<option v-for="model in claudeModelOptions" :key="model" :value="model">{{ model }}</option>
|
|
2624
|
+
</select>
|
|
2422
2625
|
<input
|
|
2626
|
+
v-else
|
|
2423
2627
|
class="model-input"
|
|
2424
2628
|
v-model="currentClaudeModel"
|
|
2425
2629
|
@blur="onClaudeModelChange"
|
|
@@ -2431,6 +2635,15 @@
|
|
|
2431
2635
|
</div>
|
|
2432
2636
|
</div>
|
|
2433
2637
|
|
|
2638
|
+
<div class="selector-section">
|
|
2639
|
+
<div class="selector-header">
|
|
2640
|
+
<span class="selector-title">配置健康检查</span>
|
|
2641
|
+
</div>
|
|
2642
|
+
<button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">
|
|
2643
|
+
{{ healthCheckLoading ? '检查中...' : '运行检查' }}
|
|
2644
|
+
</button>
|
|
2645
|
+
</div>
|
|
2646
|
+
|
|
2434
2647
|
<div class="card-list">
|
|
2435
2648
|
<div v-for="(config, name) in claudeConfigs" :key="name"
|
|
2436
2649
|
:class="['card', { active: currentClaudeConfig === name }]"
|
|
@@ -2446,6 +2659,9 @@
|
|
|
2446
2659
|
<span :class="['pill', config.hasKey ? 'configured' : 'empty']">
|
|
2447
2660
|
{{ config.hasKey ? '已配置' : '未配置' }}
|
|
2448
2661
|
</span>
|
|
2662
|
+
<span v-if="claudeSpeedResults[name]" :class="['latency', claudeSpeedResults[name].ok ? 'ok' : 'error']">
|
|
2663
|
+
{{ formatLatency(claudeSpeedResults[name]) }}
|
|
2664
|
+
</span>
|
|
2449
2665
|
<div class="card-actions" @click.stop>
|
|
2450
2666
|
<button class="card-action-btn" @click="openEditConfigModal(name)" title="编辑">
|
|
2451
2667
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -2453,6 +2669,13 @@
|
|
|
2453
2669
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
2454
2670
|
</svg>
|
|
2455
2671
|
</button>
|
|
2672
|
+
<button class="card-action-btn" :class="{ loading: claudeShareLoading[name] }" @click="copyClaudeShareCommand(name)" title="分享导入命令" aria-label="Share import command">
|
|
2673
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2674
|
+
<path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
|
|
2675
|
+
<path d="M16 6l-4-4-4 4"/>
|
|
2676
|
+
<path d="M12 2v14"/>
|
|
2677
|
+
</svg>
|
|
2678
|
+
</button>
|
|
2456
2679
|
<button class="card-action-btn delete" @click="deleteClaudeConfig(name)" title="删除">
|
|
2457
2680
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2458
2681
|
<path d="M3 6h18"/>
|
|
@@ -2466,7 +2689,7 @@
|
|
|
2466
2689
|
</div>
|
|
2467
2690
|
|
|
2468
2691
|
<!-- OpenClaw 配置模式 -->
|
|
2469
|
-
<div v-show="configMode === 'openclaw'" class="mode-content">
|
|
2692
|
+
<div v-show="mainTab === 'config' && configMode === 'openclaw'" class="mode-content mode-cards">
|
|
2470
2693
|
<button class="btn-add" @click="openOpenclawAddModal" v-if="!loading && !initError">
|
|
2471
2694
|
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
|
|
2472
2695
|
<path d="M10 4v12M4 10h12"/>
|
|
@@ -2539,29 +2762,29 @@
|
|
|
2539
2762
|
</div>
|
|
2540
2763
|
</div>
|
|
2541
2764
|
|
|
2542
|
-
<!-- 会话浏览模式 -->
|
|
2543
|
-
<div v-show="
|
|
2544
|
-
<div v-if="sessionStandalone" class="session-standalone-page">
|
|
2545
|
-
<div v-if="sessionStandaloneLoading" class="state-message">
|
|
2546
|
-
加载中...
|
|
2547
|
-
</div>
|
|
2548
|
-
<div v-else-if="sessionStandaloneError" class="state-message error">
|
|
2549
|
-
{{ sessionStandaloneError }}
|
|
2550
|
-
</div>
|
|
2551
|
-
<div v-else>
|
|
2552
|
-
<div class="session-standalone-title">
|
|
2553
|
-
{{ sessionStandaloneTitle }}
|
|
2554
|
-
<span v-if="sessionStandaloneSourceLabel"> · {{ sessionStandaloneSourceLabel }}</span>
|
|
2555
|
-
</div>
|
|
2556
|
-
<pre class="session-standalone-text">{{ sessionStandaloneText }}</pre>
|
|
2557
|
-
</div>
|
|
2558
|
-
</div>
|
|
2559
|
-
|
|
2560
|
-
<div v-else>
|
|
2561
|
-
<div v-if="!sessionStandalone" class="selector-section">
|
|
2562
|
-
<div class="selector-header">
|
|
2563
|
-
<span class="selector-title">会话来源</span>
|
|
2564
|
-
</div>
|
|
2765
|
+
<!-- 会话浏览模式 -->
|
|
2766
|
+
<div v-show="mainTab === 'sessions'" class="mode-content">
|
|
2767
|
+
<div v-if="sessionStandalone" class="session-standalone-page">
|
|
2768
|
+
<div v-if="sessionStandaloneLoading" class="state-message">
|
|
2769
|
+
加载中...
|
|
2770
|
+
</div>
|
|
2771
|
+
<div v-else-if="sessionStandaloneError" class="state-message error">
|
|
2772
|
+
{{ sessionStandaloneError }}
|
|
2773
|
+
</div>
|
|
2774
|
+
<div v-else>
|
|
2775
|
+
<div class="session-standalone-title">
|
|
2776
|
+
{{ sessionStandaloneTitle }}
|
|
2777
|
+
<span v-if="sessionStandaloneSourceLabel"> · {{ sessionStandaloneSourceLabel }}</span>
|
|
2778
|
+
</div>
|
|
2779
|
+
<pre class="session-standalone-text">{{ sessionStandaloneText }}</pre>
|
|
2780
|
+
</div>
|
|
2781
|
+
</div>
|
|
2782
|
+
|
|
2783
|
+
<div v-else>
|
|
2784
|
+
<div v-if="!sessionStandalone" class="selector-section">
|
|
2785
|
+
<div class="selector-header">
|
|
2786
|
+
<span class="selector-title">会话来源</span>
|
|
2787
|
+
</div>
|
|
2565
2788
|
<div class="session-toolbar">
|
|
2566
2789
|
<div class="session-toolbar-group">
|
|
2567
2790
|
<select class="session-source-select" v-model="sessionFilterSource" @change="onSessionSourceChange" :disabled="sessionsLoading">
|
|
@@ -2617,10 +2840,15 @@
|
|
|
2617
2840
|
</button>
|
|
2618
2841
|
</div>
|
|
2619
2842
|
</div>
|
|
2620
|
-
<div class="session-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2843
|
+
<div class="session-toolbar-footer">
|
|
2844
|
+
<label class="quick-option">
|
|
2845
|
+
<input
|
|
2846
|
+
type="checkbox"
|
|
2847
|
+
v-model="sessionResumeWithYolo"
|
|
2848
|
+
@change="onSessionResumeYoloChange"
|
|
2849
|
+
>
|
|
2850
|
+
复制恢复命令附带 --yolo
|
|
2851
|
+
</label>
|
|
2624
2852
|
</div>
|
|
2625
2853
|
</div>
|
|
2626
2854
|
|
|
@@ -2661,21 +2889,6 @@
|
|
|
2661
2889
|
<path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"></path>
|
|
2662
2890
|
</svg>
|
|
2663
2891
|
</button>
|
|
2664
|
-
<button
|
|
2665
|
-
v-if="isDeleteAvailable(session)"
|
|
2666
|
-
class="session-item-delete"
|
|
2667
|
-
@click.stop="deleteSession(session)"
|
|
2668
|
-
:disabled="sessionsLoading || sessionDeleting[getSessionExportKey(session)]"
|
|
2669
|
-
aria-label="删除会话"
|
|
2670
|
-
title="删除会话">
|
|
2671
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2672
|
-
<path d="M3 6h18"></path>
|
|
2673
|
-
<path d="M8 6V4h8v2"></path>
|
|
2674
|
-
<path d="M19 6l-1 14H6L5 6"></path>
|
|
2675
|
-
<path d="M10 11v6"></path>
|
|
2676
|
-
<path d="M14 11v6"></path>
|
|
2677
|
-
</svg>
|
|
2678
|
-
</button>
|
|
2679
2892
|
</div>
|
|
2680
2893
|
</div>
|
|
2681
2894
|
<div class="session-item-meta">
|
|
@@ -2704,7 +2917,7 @@
|
|
|
2704
2917
|
<span class="session-preview-meta-item">{{ activeSession.cwd }}</span>
|
|
2705
2918
|
</div>
|
|
2706
2919
|
</div>
|
|
2707
|
-
<div v-if="!sessionStandalone" class="session-actions">
|
|
2920
|
+
<div v-if="!sessionStandalone" class="session-actions">
|
|
2708
2921
|
<button class="btn-session-refresh" @click="loadActiveSessionDetail" :disabled="sessionDetailLoading || !activeSession">
|
|
2709
2922
|
{{ sessionDetailLoading ? '加载中...' : '刷新内容' }}
|
|
2710
2923
|
</button>
|
|
@@ -2770,14 +2983,14 @@
|
|
|
2770
2983
|
</div>
|
|
2771
2984
|
</template>
|
|
2772
2985
|
|
|
2773
|
-
<div v-else class="session-preview-empty">
|
|
2774
|
-
<span v-if="sessionStandaloneError">{{ sessionStandaloneError }}</span>
|
|
2775
|
-
<span v-else>请先在左侧选择一个会话</span>
|
|
2776
|
-
</div>
|
|
2777
|
-
</div>
|
|
2778
|
-
</div>
|
|
2779
|
-
</div>
|
|
2780
|
-
</div>
|
|
2986
|
+
<div v-else class="session-preview-empty">
|
|
2987
|
+
<span v-if="sessionStandaloneError">{{ sessionStandaloneError }}</span>
|
|
2988
|
+
<span v-else>请先在左侧选择一个会话</span>
|
|
2989
|
+
</div>
|
|
2990
|
+
</div>
|
|
2991
|
+
</div>
|
|
2992
|
+
</div>
|
|
2993
|
+
</div>
|
|
2781
2994
|
|
|
2782
2995
|
<!-- 加载状态 -->
|
|
2783
2996
|
<div v-if="loading" class="state-message">
|
|
@@ -2790,6 +3003,9 @@
|
|
|
2790
3003
|
</div>
|
|
2791
3004
|
</div>
|
|
2792
3005
|
|
|
3006
|
+
</main>
|
|
3007
|
+
</div>
|
|
3008
|
+
|
|
2793
3009
|
<!-- 添加提供商模态框 -->
|
|
2794
3010
|
<div v-if="showAddModal" class="modal-overlay" @click.self="showAddModal = false">
|
|
2795
3011
|
<div class="modal">
|
|
@@ -3278,9 +3494,9 @@
|
|
|
3278
3494
|
|
|
3279
3495
|
<script>
|
|
3280
3496
|
const { createApp } = Vue;
|
|
3281
|
-
const API_BASE = (location && location.origin && location.origin !== 'null')
|
|
3282
|
-
? location.origin
|
|
3283
|
-
: 'http://localhost:3737';
|
|
3497
|
+
const API_BASE = (location && location.origin && location.origin !== 'null')
|
|
3498
|
+
? location.origin
|
|
3499
|
+
: 'http://localhost:3737';
|
|
3284
3500
|
const DEFAULT_OPENCLAW_TEMPLATE = `{
|
|
3285
3501
|
// OpenClaw config (JSON5)
|
|
3286
3502
|
agent: {
|
|
@@ -3305,9 +3521,11 @@
|
|
|
3305
3521
|
const app = createApp({
|
|
3306
3522
|
data() {
|
|
3307
3523
|
return {
|
|
3524
|
+
mainTab: 'config',
|
|
3308
3525
|
configMode: 'codex',
|
|
3309
3526
|
currentProvider: '',
|
|
3310
3527
|
currentModel: '',
|
|
3528
|
+
serviceTier: 'fast',
|
|
3311
3529
|
providersList: [],
|
|
3312
3530
|
models: [],
|
|
3313
3531
|
codexModelsLoading: false,
|
|
@@ -3348,6 +3566,7 @@
|
|
|
3348
3566
|
sessionQuery: '',
|
|
3349
3567
|
sessionRoleFilter: 'all',
|
|
3350
3568
|
sessionTimePreset: 'all',
|
|
3569
|
+
sessionResumeWithYolo: true,
|
|
3351
3570
|
sessionPathOptions: [],
|
|
3352
3571
|
sessionPathOptionsLoading: false,
|
|
3353
3572
|
sessionPathOptionsMap: {
|
|
@@ -3368,17 +3587,21 @@
|
|
|
3368
3587
|
activeSessionMessages: [],
|
|
3369
3588
|
activeSessionDetailError: '',
|
|
3370
3589
|
activeSessionDetailClipped: false,
|
|
3371
|
-
sessionDetailLoading: false,
|
|
3372
|
-
sessionDetailRequestSeq: 0,
|
|
3373
|
-
sessionStandalone: false,
|
|
3374
|
-
sessionStandaloneError: '',
|
|
3375
|
-
sessionStandaloneText: '',
|
|
3376
|
-
sessionStandaloneTitle: '',
|
|
3377
|
-
sessionStandaloneSourceLabel: '',
|
|
3378
|
-
sessionStandaloneLoading: false,
|
|
3379
|
-
sessionStandaloneRequestSeq: 0,
|
|
3380
|
-
speedResults: {},
|
|
3590
|
+
sessionDetailLoading: false,
|
|
3591
|
+
sessionDetailRequestSeq: 0,
|
|
3592
|
+
sessionStandalone: false,
|
|
3593
|
+
sessionStandaloneError: '',
|
|
3594
|
+
sessionStandaloneText: '',
|
|
3595
|
+
sessionStandaloneTitle: '',
|
|
3596
|
+
sessionStandaloneSourceLabel: '',
|
|
3597
|
+
sessionStandaloneLoading: false,
|
|
3598
|
+
sessionStandaloneRequestSeq: 0,
|
|
3599
|
+
speedResults: {},
|
|
3381
3600
|
speedLoading: {},
|
|
3601
|
+
claudeSpeedResults: {},
|
|
3602
|
+
claudeSpeedLoading: {},
|
|
3603
|
+
claudeShareLoading: {},
|
|
3604
|
+
providerShareLoading: {},
|
|
3382
3605
|
newProvider: { name: '', url: '', key: '' },
|
|
3383
3606
|
editingProvider: { name: '', url: '', key: '' },
|
|
3384
3607
|
newModelName: '',
|
|
@@ -3444,8 +3667,6 @@
|
|
|
3444
3667
|
openclawAgentsList: [],
|
|
3445
3668
|
openclawProviders: [],
|
|
3446
3669
|
openclawMissingProviders: [],
|
|
3447
|
-
recentConfigs: [],
|
|
3448
|
-
recentLoading: false,
|
|
3449
3670
|
healthCheckLoading: false,
|
|
3450
3671
|
healthCheckResult: null,
|
|
3451
3672
|
healthCheckRemote: false
|
|
@@ -3453,6 +3674,12 @@
|
|
|
3453
3674
|
},
|
|
3454
3675
|
mounted() {
|
|
3455
3676
|
this.initSessionStandalone();
|
|
3677
|
+
const savedSessionYolo = localStorage.getItem('codexmateSessionResumeYolo');
|
|
3678
|
+
if (savedSessionYolo === '0' || savedSessionYolo === 'false') {
|
|
3679
|
+
this.sessionResumeWithYolo = false;
|
|
3680
|
+
} else if (savedSessionYolo === '1' || savedSessionYolo === 'true') {
|
|
3681
|
+
this.sessionResumeWithYolo = true;
|
|
3682
|
+
}
|
|
3456
3683
|
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
3457
3684
|
if (savedConfigs) {
|
|
3458
3685
|
try {
|
|
@@ -3464,22 +3691,11 @@
|
|
|
3464
3691
|
}
|
|
3465
3692
|
}
|
|
3466
3693
|
localStorage.setItem('claudeConfigs', JSON.stringify(this.claudeConfigs));
|
|
3467
|
-
|
|
3468
|
-
const configNames = Object.keys(this.claudeConfigs);
|
|
3469
|
-
if (configNames.length > 0) {
|
|
3470
|
-
this.currentClaudeConfig = configNames[0];
|
|
3471
|
-
}
|
|
3472
3694
|
} catch (e) {
|
|
3473
3695
|
console.error('加载 Claude 配置失败:', e);
|
|
3474
3696
|
}
|
|
3475
3697
|
}
|
|
3476
|
-
|
|
3477
|
-
const configNames = Object.keys(this.claudeConfigs);
|
|
3478
|
-
if (configNames.length > 0) {
|
|
3479
|
-
this.currentClaudeConfig = configNames[0];
|
|
3480
|
-
}
|
|
3481
|
-
}
|
|
3482
|
-
this.syncClaudeModelFromConfig();
|
|
3698
|
+
void this.refreshClaudeSelectionFromSettings({ silent: true });
|
|
3483
3699
|
const savedOpenclawConfigs = localStorage.getItem('openclawConfigs');
|
|
3484
3700
|
if (savedOpenclawConfigs) {
|
|
3485
3701
|
try {
|
|
@@ -3506,6 +3722,17 @@
|
|
|
3506
3722
|
},
|
|
3507
3723
|
sessionQueryPlaceholder() {
|
|
3508
3724
|
return this.isSessionQueryEnabled ? '关键词检索' : '仅 Codex 支持关键词检索';
|
|
3725
|
+
},
|
|
3726
|
+
claudeModelHasList() {
|
|
3727
|
+
return Array.isArray(this.claudeModels) && this.claudeModels.length > 0;
|
|
3728
|
+
},
|
|
3729
|
+
claudeModelOptions() {
|
|
3730
|
+
const list = Array.isArray(this.claudeModels) ? [...this.claudeModels] : [];
|
|
3731
|
+
const current = (this.currentClaudeModel || '').trim();
|
|
3732
|
+
if (current && !list.includes(current)) {
|
|
3733
|
+
list.unshift(current);
|
|
3734
|
+
}
|
|
3735
|
+
return list;
|
|
3509
3736
|
}
|
|
3510
3737
|
},
|
|
3511
3738
|
methods: {
|
|
@@ -3521,6 +3748,12 @@
|
|
|
3521
3748
|
} else {
|
|
3522
3749
|
this.currentProvider = statusRes.provider;
|
|
3523
3750
|
this.currentModel = statusRes.model;
|
|
3751
|
+
{
|
|
3752
|
+
const tier = typeof statusRes.serviceTier === 'string'
|
|
3753
|
+
? statusRes.serviceTier.trim().toLowerCase()
|
|
3754
|
+
: '';
|
|
3755
|
+
this.serviceTier = tier === 'fast' ? 'fast' : (tier ? 'standard' : 'fast');
|
|
3756
|
+
}
|
|
3524
3757
|
this.providersList = listRes.providers;
|
|
3525
3758
|
await this.loadModelsForProvider(this.currentProvider);
|
|
3526
3759
|
if (statusRes.configReady === false) {
|
|
@@ -3531,7 +3764,6 @@
|
|
|
3531
3764
|
}
|
|
3532
3765
|
this.maybeShowStarPrompt();
|
|
3533
3766
|
}
|
|
3534
|
-
await this.loadRecentConfigs();
|
|
3535
3767
|
} catch (e) {
|
|
3536
3768
|
this.initError = '连接失败: ' + e.message;
|
|
3537
3769
|
} finally {
|
|
@@ -3582,6 +3814,109 @@
|
|
|
3582
3814
|
return this.claudeConfigs[this.currentClaudeConfig] || null;
|
|
3583
3815
|
},
|
|
3584
3816
|
|
|
3817
|
+
normalizeClaudeValue(value) {
|
|
3818
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
3819
|
+
},
|
|
3820
|
+
|
|
3821
|
+
normalizeClaudeConfig(config) {
|
|
3822
|
+
const safe = config && typeof config === 'object' ? config : {};
|
|
3823
|
+
return {
|
|
3824
|
+
apiKey: this.normalizeClaudeValue(safe.apiKey),
|
|
3825
|
+
baseUrl: this.normalizeClaudeValue(safe.baseUrl),
|
|
3826
|
+
model: this.normalizeClaudeValue(safe.model)
|
|
3827
|
+
};
|
|
3828
|
+
},
|
|
3829
|
+
|
|
3830
|
+
normalizeClaudeSettingsEnv(env) {
|
|
3831
|
+
const safe = env && typeof env === 'object' ? env : {};
|
|
3832
|
+
return {
|
|
3833
|
+
apiKey: this.normalizeClaudeValue(safe.ANTHROPIC_API_KEY),
|
|
3834
|
+
baseUrl: this.normalizeClaudeValue(safe.ANTHROPIC_BASE_URL),
|
|
3835
|
+
model: this.normalizeClaudeValue(safe.ANTHROPIC_MODEL)
|
|
3836
|
+
};
|
|
3837
|
+
},
|
|
3838
|
+
|
|
3839
|
+
matchClaudeConfigFromSettings(env) {
|
|
3840
|
+
const normalizedSettings = this.normalizeClaudeSettingsEnv(env);
|
|
3841
|
+
if (!normalizedSettings.apiKey || !normalizedSettings.baseUrl || !normalizedSettings.model) {
|
|
3842
|
+
return '';
|
|
3843
|
+
}
|
|
3844
|
+
const entries = Object.entries(this.claudeConfigs || {});
|
|
3845
|
+
for (const [name, config] of entries) {
|
|
3846
|
+
const normalizedConfig = this.normalizeClaudeConfig(config);
|
|
3847
|
+
if (!normalizedConfig.apiKey || !normalizedConfig.baseUrl || !normalizedConfig.model) {
|
|
3848
|
+
continue;
|
|
3849
|
+
}
|
|
3850
|
+
if (normalizedConfig.apiKey === normalizedSettings.apiKey
|
|
3851
|
+
&& normalizedConfig.baseUrl === normalizedSettings.baseUrl
|
|
3852
|
+
&& normalizedConfig.model === normalizedSettings.model) {
|
|
3853
|
+
return name;
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
return '';
|
|
3857
|
+
},
|
|
3858
|
+
|
|
3859
|
+
findDuplicateClaudeConfigName(config) {
|
|
3860
|
+
const normalized = this.normalizeClaudeConfig(config);
|
|
3861
|
+
if (!normalized.apiKey || !normalized.baseUrl || !normalized.model) {
|
|
3862
|
+
return '';
|
|
3863
|
+
}
|
|
3864
|
+
const entries = Object.entries(this.claudeConfigs || {});
|
|
3865
|
+
for (const [name, existing] of entries) {
|
|
3866
|
+
const normalizedExisting = this.normalizeClaudeConfig(existing);
|
|
3867
|
+
if (!normalizedExisting.apiKey || !normalizedExisting.baseUrl || !normalizedExisting.model) {
|
|
3868
|
+
continue;
|
|
3869
|
+
}
|
|
3870
|
+
if (normalizedExisting.apiKey === normalized.apiKey
|
|
3871
|
+
&& normalizedExisting.baseUrl === normalized.baseUrl
|
|
3872
|
+
&& normalizedExisting.model === normalized.model) {
|
|
3873
|
+
return name;
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
return '';
|
|
3877
|
+
},
|
|
3878
|
+
|
|
3879
|
+
async refreshClaudeSelectionFromSettings(options = {}) {
|
|
3880
|
+
const configNames = Object.keys(this.claudeConfigs || {});
|
|
3881
|
+
if (configNames.length === 0) {
|
|
3882
|
+
this.currentClaudeConfig = '';
|
|
3883
|
+
this.currentClaudeModel = '';
|
|
3884
|
+
this.resetClaudeModelsState();
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3887
|
+
const silent = !!options.silent;
|
|
3888
|
+
try {
|
|
3889
|
+
const res = await api('get-claude-settings');
|
|
3890
|
+
if (res && res.error) {
|
|
3891
|
+
if (!silent) {
|
|
3892
|
+
this.showMessage('读取 Claude 配置失败: ' + res.error, 'error');
|
|
3893
|
+
}
|
|
3894
|
+
return;
|
|
3895
|
+
}
|
|
3896
|
+
const matchName = this.matchClaudeConfigFromSettings((res && res.env) || {});
|
|
3897
|
+
if (matchName) {
|
|
3898
|
+
if (this.currentClaudeConfig !== matchName) {
|
|
3899
|
+
this.currentClaudeConfig = matchName;
|
|
3900
|
+
}
|
|
3901
|
+
this.refreshClaudeModelContext();
|
|
3902
|
+
return;
|
|
3903
|
+
}
|
|
3904
|
+
this.currentClaudeConfig = '';
|
|
3905
|
+
this.currentClaudeModel = '';
|
|
3906
|
+
this.resetClaudeModelsState();
|
|
3907
|
+
if (!silent) {
|
|
3908
|
+
const tip = res && res.exists
|
|
3909
|
+
? '当前 Claude settings.json 与本地配置不匹配,已取消选中'
|
|
3910
|
+
: '未检测到 Claude settings.json,已取消选中';
|
|
3911
|
+
this.showMessage(tip, 'info');
|
|
3912
|
+
}
|
|
3913
|
+
} catch (e) {
|
|
3914
|
+
if (!silent) {
|
|
3915
|
+
this.showMessage('读取 Claude 配置失败: ' + e.message, 'error');
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
},
|
|
3919
|
+
|
|
3585
3920
|
syncClaudeModelFromConfig() {
|
|
3586
3921
|
const config = this.getCurrentClaudeConfig();
|
|
3587
3922
|
this.currentClaudeModel = config && config.model ? config.model : '';
|
|
@@ -3606,6 +3941,10 @@
|
|
|
3606
3941
|
|
|
3607
3942
|
async loadClaudeModels() {
|
|
3608
3943
|
const config = this.getCurrentClaudeConfig();
|
|
3944
|
+
if (!config) {
|
|
3945
|
+
this.resetClaudeModelsState();
|
|
3946
|
+
return;
|
|
3947
|
+
}
|
|
3609
3948
|
const baseUrl = (config.baseUrl || '').trim();
|
|
3610
3949
|
const apiKey = (config.apiKey || '').trim();
|
|
3611
3950
|
|
|
@@ -3658,13 +3997,21 @@
|
|
|
3658
3997
|
},
|
|
3659
3998
|
|
|
3660
3999
|
switchConfigMode(mode) {
|
|
4000
|
+
this.mainTab = 'config';
|
|
3661
4001
|
this.configMode = mode;
|
|
3662
4002
|
if (mode === 'claude') {
|
|
3663
4003
|
this.refreshClaudeModelContext();
|
|
3664
4004
|
}
|
|
3665
|
-
|
|
4005
|
+
},
|
|
4006
|
+
|
|
4007
|
+
switchMainTab(tab) {
|
|
4008
|
+
this.mainTab = tab;
|
|
4009
|
+
if (tab === 'sessions' && this.sessionsList.length === 0) {
|
|
3666
4010
|
this.loadSessions();
|
|
3667
4011
|
}
|
|
4012
|
+
if (tab === 'config' && this.configMode === 'claude') {
|
|
4013
|
+
this.refreshClaudeModelContext();
|
|
4014
|
+
}
|
|
3668
4015
|
},
|
|
3669
4016
|
|
|
3670
4017
|
getSessionStandaloneContext() {
|
|
@@ -3710,7 +4057,7 @@
|
|
|
3710
4057
|
if (!context.requested) return;
|
|
3711
4058
|
|
|
3712
4059
|
this.sessionStandalone = true;
|
|
3713
|
-
this.
|
|
4060
|
+
this.mainTab = 'sessions';
|
|
3714
4061
|
|
|
3715
4062
|
if (context.error || !context.params) {
|
|
3716
4063
|
this.sessionStandaloneError = `会话链接参数不完整:${context.error || '参数解析失败'}`;
|
|
@@ -3718,22 +4065,22 @@
|
|
|
3718
4065
|
}
|
|
3719
4066
|
|
|
3720
4067
|
const sourceLabel = context.params.source === 'codex' ? 'Codex' : 'Claude Code';
|
|
3721
|
-
this.activeSession = {
|
|
3722
|
-
source: context.params.source,
|
|
3723
|
-
sourceLabel,
|
|
3724
|
-
sessionId: context.params.sessionId,
|
|
3725
|
-
filePath: context.params.filePath,
|
|
3726
|
-
title: context.params.sessionId || context.params.filePath || '会话'
|
|
3727
|
-
};
|
|
3728
|
-
this.activeSessionMessages = [];
|
|
3729
|
-
this.activeSessionDetailError = '';
|
|
3730
|
-
this.activeSessionDetailClipped = false;
|
|
3731
|
-
this.sessionStandaloneError = '';
|
|
3732
|
-
this.sessionStandaloneText = '';
|
|
3733
|
-
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
3734
|
-
this.sessionStandaloneSourceLabel = sourceLabel;
|
|
3735
|
-
this.loadSessionStandalonePlain();
|
|
3736
|
-
},
|
|
4068
|
+
this.activeSession = {
|
|
4069
|
+
source: context.params.source,
|
|
4070
|
+
sourceLabel,
|
|
4071
|
+
sessionId: context.params.sessionId,
|
|
4072
|
+
filePath: context.params.filePath,
|
|
4073
|
+
title: context.params.sessionId || context.params.filePath || '会话'
|
|
4074
|
+
};
|
|
4075
|
+
this.activeSessionMessages = [];
|
|
4076
|
+
this.activeSessionDetailError = '';
|
|
4077
|
+
this.activeSessionDetailClipped = false;
|
|
4078
|
+
this.sessionStandaloneError = '';
|
|
4079
|
+
this.sessionStandaloneText = '';
|
|
4080
|
+
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
4081
|
+
this.sessionStandaloneSourceLabel = sourceLabel;
|
|
4082
|
+
this.loadSessionStandalonePlain();
|
|
4083
|
+
},
|
|
3737
4084
|
|
|
3738
4085
|
buildSessionStandaloneUrl(session) {
|
|
3739
4086
|
if (!session) return '';
|
|
@@ -3791,10 +4138,14 @@
|
|
|
3791
4138
|
|
|
3792
4139
|
buildResumeCommand(session) {
|
|
3793
4140
|
const sessionId = session && session.sessionId ? String(session.sessionId).trim() : '';
|
|
3794
|
-
|
|
4141
|
+
const arg = this.quoteResumeArg(sessionId);
|
|
4142
|
+
if (this.sessionResumeWithYolo) {
|
|
4143
|
+
return `codex --yolo resume ${arg}`;
|
|
4144
|
+
}
|
|
4145
|
+
return `codex resume ${arg}`;
|
|
3795
4146
|
},
|
|
3796
4147
|
|
|
3797
|
-
|
|
4148
|
+
quoteShellArg(value) {
|
|
3798
4149
|
const text = typeof value === 'string' ? value : String(value || '');
|
|
3799
4150
|
if (!text) return "''";
|
|
3800
4151
|
if (/^[a-zA-Z0-9._-]+$/.test(text)) return text;
|
|
@@ -3802,6 +4153,10 @@
|
|
|
3802
4153
|
return `'${escaped}'`;
|
|
3803
4154
|
},
|
|
3804
4155
|
|
|
4156
|
+
quoteResumeArg(value) {
|
|
4157
|
+
return this.quoteShellArg(value);
|
|
4158
|
+
},
|
|
4159
|
+
|
|
3805
4160
|
fallbackCopyText(text) {
|
|
3806
4161
|
let textarea = null;
|
|
3807
4162
|
try {
|
|
@@ -3862,6 +4217,121 @@
|
|
|
3862
4217
|
this.showMessage('复制失败,请手动复制命令', 'error');
|
|
3863
4218
|
},
|
|
3864
4219
|
|
|
4220
|
+
buildProviderShareCommand(payload) {
|
|
4221
|
+
if (!payload || typeof payload !== 'object') return '';
|
|
4222
|
+
const name = typeof payload.name === 'string' ? payload.name.trim() : '';
|
|
4223
|
+
const baseUrl = typeof payload.baseUrl === 'string' ? payload.baseUrl.trim() : '';
|
|
4224
|
+
const apiKey = typeof payload.apiKey === 'string' ? payload.apiKey : '';
|
|
4225
|
+
if (!name || !baseUrl) return '';
|
|
4226
|
+
|
|
4227
|
+
const nameArg = this.quoteShellArg(name);
|
|
4228
|
+
const urlArg = this.quoteShellArg(baseUrl);
|
|
4229
|
+
const keyArg = apiKey ? this.quoteShellArg(apiKey) : '';
|
|
4230
|
+
const switchCmd = `codexmate switch ${nameArg}`;
|
|
4231
|
+
const addCmd = apiKey
|
|
4232
|
+
? `codexmate add ${nameArg} ${urlArg} ${keyArg}`
|
|
4233
|
+
: `codexmate add ${nameArg} ${urlArg}`;
|
|
4234
|
+
return `${addCmd} && ${switchCmd}`;
|
|
4235
|
+
},
|
|
4236
|
+
|
|
4237
|
+
buildClaudeShareCommand(payload) {
|
|
4238
|
+
if (!payload || typeof payload !== 'object') return '';
|
|
4239
|
+
const baseUrl = typeof payload.baseUrl === 'string' ? payload.baseUrl.trim() : '';
|
|
4240
|
+
const apiKey = typeof payload.apiKey === 'string' ? payload.apiKey : '';
|
|
4241
|
+
const model = typeof payload.model === 'string' && payload.model.trim()
|
|
4242
|
+
? payload.model.trim()
|
|
4243
|
+
: 'glm-4.7';
|
|
4244
|
+
if (!baseUrl || !apiKey) return '';
|
|
4245
|
+
const urlArg = this.quoteShellArg(baseUrl);
|
|
4246
|
+
const keyArg = this.quoteShellArg(apiKey);
|
|
4247
|
+
const modelArg = this.quoteShellArg(model);
|
|
4248
|
+
return `codexmate claude ${urlArg} ${keyArg} ${modelArg}`;
|
|
4249
|
+
},
|
|
4250
|
+
|
|
4251
|
+
async copyProviderShareCommand(provider) {
|
|
4252
|
+
const name = provider && typeof provider.name === 'string' ? provider.name.trim() : '';
|
|
4253
|
+
if (!name) {
|
|
4254
|
+
this.showMessage('提供商名称无效', 'error');
|
|
4255
|
+
return;
|
|
4256
|
+
}
|
|
4257
|
+
if (this.providerShareLoading[name]) {
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
4260
|
+
this.providerShareLoading[name] = true;
|
|
4261
|
+
try {
|
|
4262
|
+
const res = await api('export-provider', { name });
|
|
4263
|
+
if (res && res.error) {
|
|
4264
|
+
this.showMessage(res.error, 'error');
|
|
4265
|
+
return;
|
|
4266
|
+
}
|
|
4267
|
+
const command = this.buildProviderShareCommand(res && res.payload ? res.payload : null);
|
|
4268
|
+
if (!command) {
|
|
4269
|
+
this.showMessage('分享命令生成失败', 'error');
|
|
4270
|
+
return;
|
|
4271
|
+
}
|
|
4272
|
+
const ok = this.fallbackCopyText(command);
|
|
4273
|
+
if (ok) {
|
|
4274
|
+
this.showMessage('已复制分享命令', 'success');
|
|
4275
|
+
return;
|
|
4276
|
+
}
|
|
4277
|
+
try {
|
|
4278
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
4279
|
+
await navigator.clipboard.writeText(command);
|
|
4280
|
+
this.showMessage('已复制分享命令', 'success');
|
|
4281
|
+
return;
|
|
4282
|
+
}
|
|
4283
|
+
} catch (e) {
|
|
4284
|
+
// keep fallback failure message
|
|
4285
|
+
}
|
|
4286
|
+
this.showMessage('复制失败,请手动复制命令', 'error');
|
|
4287
|
+
} catch (e) {
|
|
4288
|
+
this.showMessage('生成分享命令失败: ' + e.message, 'error');
|
|
4289
|
+
} finally {
|
|
4290
|
+
this.providerShareLoading[name] = false;
|
|
4291
|
+
}
|
|
4292
|
+
},
|
|
4293
|
+
|
|
4294
|
+
async copyClaudeShareCommand(name) {
|
|
4295
|
+
const config = this.claudeConfigs[name];
|
|
4296
|
+
if (!config) {
|
|
4297
|
+
this.showMessage('配置不存在', 'error');
|
|
4298
|
+
return;
|
|
4299
|
+
}
|
|
4300
|
+
if (this.claudeShareLoading[name]) return;
|
|
4301
|
+
this.claudeShareLoading[name] = true;
|
|
4302
|
+
try {
|
|
4303
|
+
const res = await api('export-claude-share', { config });
|
|
4304
|
+
if (res && res.error) {
|
|
4305
|
+
this.showMessage(res.error, 'error');
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
const command = this.buildClaudeShareCommand(res && res.payload ? res.payload : null);
|
|
4309
|
+
if (!command) {
|
|
4310
|
+
this.showMessage('分享命令生成失败', 'error');
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
const ok = this.fallbackCopyText(command);
|
|
4314
|
+
if (ok) {
|
|
4315
|
+
this.showMessage('已复制分享命令', 'success');
|
|
4316
|
+
return;
|
|
4317
|
+
}
|
|
4318
|
+
try {
|
|
4319
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
4320
|
+
await navigator.clipboard.writeText(command);
|
|
4321
|
+
this.showMessage('已复制分享命令', 'success');
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
} catch (e) {
|
|
4325
|
+
// fall through
|
|
4326
|
+
}
|
|
4327
|
+
this.showMessage('复制失败,请手动复制命令', 'error');
|
|
4328
|
+
} catch (e) {
|
|
4329
|
+
this.showMessage('生成分享命令失败: ' + e.message, 'error');
|
|
4330
|
+
} finally {
|
|
4331
|
+
this.claudeShareLoading[name] = false;
|
|
4332
|
+
}
|
|
4333
|
+
},
|
|
4334
|
+
|
|
3865
4335
|
async cloneSession(session) {
|
|
3866
4336
|
if (!this.isCloneAvailable(session)) {
|
|
3867
4337
|
this.showMessage('当前会话不支持克隆', 'error');
|
|
@@ -4036,6 +4506,11 @@
|
|
|
4036
4506
|
}
|
|
4037
4507
|
},
|
|
4038
4508
|
|
|
4509
|
+
onSessionResumeYoloChange() {
|
|
4510
|
+
const value = this.sessionResumeWithYolo ? '1' : '0';
|
|
4511
|
+
localStorage.setItem('codexmateSessionResumeYolo', value);
|
|
4512
|
+
},
|
|
4513
|
+
|
|
4039
4514
|
async onSessionSourceChange() {
|
|
4040
4515
|
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
4041
4516
|
await this.loadSessions();
|
|
@@ -4140,66 +4615,66 @@
|
|
|
4140
4615
|
}
|
|
4141
4616
|
},
|
|
4142
4617
|
|
|
4143
|
-
async selectSession(session) {
|
|
4144
|
-
if (!session) return;
|
|
4145
|
-
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
4146
|
-
this.activeSession = session;
|
|
4147
|
-
this.activeSessionMessages = [];
|
|
4148
|
-
this.activeSessionDetailError = '';
|
|
4149
|
-
this.activeSessionDetailClipped = false;
|
|
4150
|
-
await this.loadActiveSessionDetail();
|
|
4151
|
-
},
|
|
4152
|
-
|
|
4153
|
-
async loadSessionStandalonePlain() {
|
|
4154
|
-
if (!this.activeSession) {
|
|
4155
|
-
this.sessionStandaloneText = '';
|
|
4156
|
-
this.sessionStandaloneTitle = '会话';
|
|
4157
|
-
this.sessionStandaloneSourceLabel = '';
|
|
4158
|
-
this.sessionStandaloneError = '';
|
|
4159
|
-
return;
|
|
4160
|
-
}
|
|
4161
|
-
|
|
4162
|
-
const requestSeq = ++this.sessionStandaloneRequestSeq;
|
|
4163
|
-
this.sessionStandaloneLoading = true;
|
|
4164
|
-
this.sessionStandaloneError = '';
|
|
4165
|
-
try {
|
|
4166
|
-
const res = await api('session-plain', {
|
|
4167
|
-
source: this.activeSession.source,
|
|
4168
|
-
sessionId: this.activeSession.sessionId,
|
|
4169
|
-
filePath: this.activeSession.filePath
|
|
4170
|
-
});
|
|
4171
|
-
|
|
4172
|
-
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
4173
|
-
return;
|
|
4174
|
-
}
|
|
4175
|
-
|
|
4176
|
-
if (res.error) {
|
|
4177
|
-
this.sessionStandaloneText = '';
|
|
4178
|
-
this.sessionStandaloneError = res.error;
|
|
4179
|
-
return;
|
|
4180
|
-
}
|
|
4181
|
-
|
|
4182
|
-
this.sessionStandaloneSourceLabel = res.sourceLabel || this.activeSession.sourceLabel || '';
|
|
4183
|
-
this.sessionStandaloneTitle = res.sessionId || this.activeSession.title || '会话';
|
|
4184
|
-
this.sessionStandaloneText = typeof res.text === 'string' ? res.text : '';
|
|
4185
|
-
} catch (e) {
|
|
4186
|
-
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
4187
|
-
return;
|
|
4188
|
-
}
|
|
4189
|
-
this.sessionStandaloneText = '';
|
|
4190
|
-
this.sessionStandaloneError = '加载会话内容失败: ' + e.message;
|
|
4191
|
-
} finally {
|
|
4192
|
-
if (requestSeq === this.sessionStandaloneRequestSeq) {
|
|
4193
|
-
this.sessionStandaloneLoading = false;
|
|
4194
|
-
}
|
|
4195
|
-
}
|
|
4196
|
-
},
|
|
4197
|
-
|
|
4198
|
-
async loadActiveSessionDetail() {
|
|
4199
|
-
if (!this.activeSession) {
|
|
4200
|
-
this.activeSessionMessages = [];
|
|
4201
|
-
this.activeSessionDetailError = '';
|
|
4202
|
-
this.activeSessionDetailClipped = false;
|
|
4618
|
+
async selectSession(session) {
|
|
4619
|
+
if (!session) return;
|
|
4620
|
+
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
4621
|
+
this.activeSession = session;
|
|
4622
|
+
this.activeSessionMessages = [];
|
|
4623
|
+
this.activeSessionDetailError = '';
|
|
4624
|
+
this.activeSessionDetailClipped = false;
|
|
4625
|
+
await this.loadActiveSessionDetail();
|
|
4626
|
+
},
|
|
4627
|
+
|
|
4628
|
+
async loadSessionStandalonePlain() {
|
|
4629
|
+
if (!this.activeSession) {
|
|
4630
|
+
this.sessionStandaloneText = '';
|
|
4631
|
+
this.sessionStandaloneTitle = '会话';
|
|
4632
|
+
this.sessionStandaloneSourceLabel = '';
|
|
4633
|
+
this.sessionStandaloneError = '';
|
|
4634
|
+
return;
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
const requestSeq = ++this.sessionStandaloneRequestSeq;
|
|
4638
|
+
this.sessionStandaloneLoading = true;
|
|
4639
|
+
this.sessionStandaloneError = '';
|
|
4640
|
+
try {
|
|
4641
|
+
const res = await api('session-plain', {
|
|
4642
|
+
source: this.activeSession.source,
|
|
4643
|
+
sessionId: this.activeSession.sessionId,
|
|
4644
|
+
filePath: this.activeSession.filePath
|
|
4645
|
+
});
|
|
4646
|
+
|
|
4647
|
+
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
4648
|
+
return;
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4651
|
+
if (res.error) {
|
|
4652
|
+
this.sessionStandaloneText = '';
|
|
4653
|
+
this.sessionStandaloneError = res.error;
|
|
4654
|
+
return;
|
|
4655
|
+
}
|
|
4656
|
+
|
|
4657
|
+
this.sessionStandaloneSourceLabel = res.sourceLabel || this.activeSession.sourceLabel || '';
|
|
4658
|
+
this.sessionStandaloneTitle = res.sessionId || this.activeSession.title || '会话';
|
|
4659
|
+
this.sessionStandaloneText = typeof res.text === 'string' ? res.text : '';
|
|
4660
|
+
} catch (e) {
|
|
4661
|
+
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
4662
|
+
return;
|
|
4663
|
+
}
|
|
4664
|
+
this.sessionStandaloneText = '';
|
|
4665
|
+
this.sessionStandaloneError = '加载会话内容失败: ' + e.message;
|
|
4666
|
+
} finally {
|
|
4667
|
+
if (requestSeq === this.sessionStandaloneRequestSeq) {
|
|
4668
|
+
this.sessionStandaloneLoading = false;
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
},
|
|
4672
|
+
|
|
4673
|
+
async loadActiveSessionDetail() {
|
|
4674
|
+
if (!this.activeSession) {
|
|
4675
|
+
this.activeSessionMessages = [];
|
|
4676
|
+
this.activeSessionDetailError = '';
|
|
4677
|
+
this.activeSessionDetailClipped = false;
|
|
4203
4678
|
return;
|
|
4204
4679
|
}
|
|
4205
4680
|
|
|
@@ -4285,22 +4760,22 @@
|
|
|
4285
4760
|
sessionId: session.sessionId,
|
|
4286
4761
|
filePath: session.filePath
|
|
4287
4762
|
});
|
|
4288
|
-
if (res.error) {
|
|
4289
|
-
this.showMessage(res.error, 'error');
|
|
4290
|
-
return;
|
|
4291
|
-
}
|
|
4292
|
-
|
|
4293
|
-
const fileName = res.fileName || `${session.source || 'session'}-${session.sessionId || Date.now()}.md`;
|
|
4294
|
-
this.downloadTextFile(fileName, res.content || '');
|
|
4295
|
-
if (res.truncated) {
|
|
4296
|
-
const maxLabel = res.maxMessages === 'all' ? 'all' : res.maxMessages;
|
|
4297
|
-
this.showMessage(`会话导出完成(已截断:最多 ${maxLabel} 条消息)`, 'info');
|
|
4298
|
-
} else {
|
|
4299
|
-
this.showMessage('会话导出完成', 'success');
|
|
4300
|
-
}
|
|
4301
|
-
} catch (e) {
|
|
4302
|
-
this.showMessage('导出失败: ' + e.message, 'error');
|
|
4303
|
-
} finally {
|
|
4763
|
+
if (res.error) {
|
|
4764
|
+
this.showMessage(res.error, 'error');
|
|
4765
|
+
return;
|
|
4766
|
+
}
|
|
4767
|
+
|
|
4768
|
+
const fileName = res.fileName || `${session.source || 'session'}-${session.sessionId || Date.now()}.md`;
|
|
4769
|
+
this.downloadTextFile(fileName, res.content || '');
|
|
4770
|
+
if (res.truncated) {
|
|
4771
|
+
const maxLabel = res.maxMessages === 'all' ? 'all' : res.maxMessages;
|
|
4772
|
+
this.showMessage(`会话导出完成(已截断:最多 ${maxLabel} 条消息)`, 'info');
|
|
4773
|
+
} else {
|
|
4774
|
+
this.showMessage('会话导出完成', 'success');
|
|
4775
|
+
}
|
|
4776
|
+
} catch (e) {
|
|
4777
|
+
this.showMessage('导出失败: ' + e.message, 'error');
|
|
4778
|
+
} finally {
|
|
4304
4779
|
this.sessionExporting[key] = false;
|
|
4305
4780
|
}
|
|
4306
4781
|
},
|
|
@@ -4315,32 +4790,8 @@
|
|
|
4315
4790
|
await this.openConfigTemplateEditor();
|
|
4316
4791
|
},
|
|
4317
4792
|
|
|
4318
|
-
async
|
|
4319
|
-
this.
|
|
4320
|
-
try {
|
|
4321
|
-
const res = await api('get-recent-configs');
|
|
4322
|
-
if (res && Array.isArray(res.items)) {
|
|
4323
|
-
this.recentConfigs = res.items;
|
|
4324
|
-
} else {
|
|
4325
|
-
this.recentConfigs = [];
|
|
4326
|
-
}
|
|
4327
|
-
} catch (e) {
|
|
4328
|
-
this.recentConfigs = [];
|
|
4329
|
-
} finally {
|
|
4330
|
-
this.recentLoading = false;
|
|
4331
|
-
}
|
|
4332
|
-
},
|
|
4333
|
-
|
|
4334
|
-
async applyRecentConfig(item) {
|
|
4335
|
-
if (!item || !item.provider || !item.model) {
|
|
4336
|
-
this.showMessage('最近配置无效,无法应用', 'error');
|
|
4337
|
-
return;
|
|
4338
|
-
}
|
|
4339
|
-
this.currentProvider = item.provider;
|
|
4340
|
-
this.currentModel = item.model;
|
|
4341
|
-
await this.openConfigTemplateEditor({
|
|
4342
|
-
appendHint: '最近使用配置,确认后将写入 config.toml'
|
|
4343
|
-
});
|
|
4793
|
+
async onServiceTierChange() {
|
|
4794
|
+
await this.openConfigTemplateEditor();
|
|
4344
4795
|
},
|
|
4345
4796
|
|
|
4346
4797
|
async runHealthCheck() {
|
|
@@ -4395,6 +4846,12 @@
|
|
|
4395
4846
|
this.healthCheckResult = null;
|
|
4396
4847
|
this.showMessage('健康检查失败: ' + e.message, 'error');
|
|
4397
4848
|
} finally {
|
|
4849
|
+
if (this.configMode === 'claude') {
|
|
4850
|
+
try {
|
|
4851
|
+
const entries = Object.entries(this.claudeConfigs || {});
|
|
4852
|
+
await Promise.all(entries.map(([name, config]) => this.runClaudeSpeedTest(name, config)));
|
|
4853
|
+
} catch (e) {}
|
|
4854
|
+
}
|
|
4398
4855
|
this.healthCheckLoading = false;
|
|
4399
4856
|
}
|
|
4400
4857
|
},
|
|
@@ -4409,7 +4866,8 @@
|
|
|
4409
4866
|
try {
|
|
4410
4867
|
const res = await api('get-config-template', {
|
|
4411
4868
|
provider: this.currentProvider,
|
|
4412
|
-
model: this.currentModel
|
|
4869
|
+
model: this.currentModel,
|
|
4870
|
+
serviceTier: this.serviceTier
|
|
4413
4871
|
});
|
|
4414
4872
|
if (res.error) {
|
|
4415
4873
|
this.showMessage(res.error, 'error');
|
|
@@ -4818,6 +5276,10 @@
|
|
|
4818
5276
|
if (this.claudeConfigs[name]) {
|
|
4819
5277
|
return this.showMessage('配置名称已存在', 'error');
|
|
4820
5278
|
}
|
|
5279
|
+
const duplicateName = this.findDuplicateClaudeConfigName(this.newClaudeConfig);
|
|
5280
|
+
if (duplicateName) {
|
|
5281
|
+
return this.showMessage('已存在相同配置,已忽略添加', 'info');
|
|
5282
|
+
}
|
|
4821
5283
|
|
|
4822
5284
|
this.claudeConfigs[name] = {
|
|
4823
5285
|
apiKey: this.newClaudeConfig.apiKey,
|
|
@@ -5912,6 +6374,33 @@
|
|
|
5912
6374
|
}
|
|
5913
6375
|
},
|
|
5914
6376
|
|
|
6377
|
+
async runClaudeSpeedTest(name, config) {
|
|
6378
|
+
if (!name || this.claudeSpeedLoading[name]) return null;
|
|
6379
|
+
const baseUrl = config && typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
|
|
6380
|
+
this.claudeSpeedLoading[name] = true;
|
|
6381
|
+
try {
|
|
6382
|
+
if (!baseUrl) {
|
|
6383
|
+
const res = { ok: false, error: 'Missing base URL' };
|
|
6384
|
+
this.claudeSpeedResults[name] = res;
|
|
6385
|
+
return res;
|
|
6386
|
+
}
|
|
6387
|
+
const res = await api('speed-test', { url: baseUrl });
|
|
6388
|
+
if (res.error) {
|
|
6389
|
+
this.claudeSpeedResults[name] = { ok: false, error: res.error };
|
|
6390
|
+
return { ok: false, error: res.error };
|
|
6391
|
+
}
|
|
6392
|
+
this.claudeSpeedResults[name] = res;
|
|
6393
|
+
return res;
|
|
6394
|
+
} catch (e) {
|
|
6395
|
+
const message = e && e.message ? e.message : 'Speed test failed';
|
|
6396
|
+
const res = { ok: false, error: message };
|
|
6397
|
+
this.claudeSpeedResults[name] = res;
|
|
6398
|
+
return res;
|
|
6399
|
+
} finally {
|
|
6400
|
+
this.claudeSpeedLoading[name] = false;
|
|
6401
|
+
}
|
|
6402
|
+
},
|
|
6403
|
+
|
|
5915
6404
|
showMessage(text, type) {
|
|
5916
6405
|
this.message = text;
|
|
5917
6406
|
this.messageType = type || 'info';
|