codexmate 0.0.6 → 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 +784 -1165
- 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 -421
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,7 +3494,9 @@
|
|
|
3278
3494
|
|
|
3279
3495
|
<script>
|
|
3280
3496
|
const { createApp } = Vue;
|
|
3281
|
-
const API_BASE = '
|
|
3497
|
+
const API_BASE = (location && location.origin && location.origin !== 'null')
|
|
3498
|
+
? location.origin
|
|
3499
|
+
: 'http://localhost:3737';
|
|
3282
3500
|
const DEFAULT_OPENCLAW_TEMPLATE = `{
|
|
3283
3501
|
// OpenClaw config (JSON5)
|
|
3284
3502
|
agent: {
|
|
@@ -3303,9 +3521,11 @@
|
|
|
3303
3521
|
const app = createApp({
|
|
3304
3522
|
data() {
|
|
3305
3523
|
return {
|
|
3524
|
+
mainTab: 'config',
|
|
3306
3525
|
configMode: 'codex',
|
|
3307
3526
|
currentProvider: '',
|
|
3308
3527
|
currentModel: '',
|
|
3528
|
+
serviceTier: 'fast',
|
|
3309
3529
|
providersList: [],
|
|
3310
3530
|
models: [],
|
|
3311
3531
|
codexModelsLoading: false,
|
|
@@ -3346,6 +3566,7 @@
|
|
|
3346
3566
|
sessionQuery: '',
|
|
3347
3567
|
sessionRoleFilter: 'all',
|
|
3348
3568
|
sessionTimePreset: 'all',
|
|
3569
|
+
sessionResumeWithYolo: true,
|
|
3349
3570
|
sessionPathOptions: [],
|
|
3350
3571
|
sessionPathOptionsLoading: false,
|
|
3351
3572
|
sessionPathOptionsMap: {
|
|
@@ -3366,17 +3587,21 @@
|
|
|
3366
3587
|
activeSessionMessages: [],
|
|
3367
3588
|
activeSessionDetailError: '',
|
|
3368
3589
|
activeSessionDetailClipped: false,
|
|
3369
|
-
sessionDetailLoading: false,
|
|
3370
|
-
sessionDetailRequestSeq: 0,
|
|
3371
|
-
sessionStandalone: false,
|
|
3372
|
-
sessionStandaloneError: '',
|
|
3373
|
-
sessionStandaloneText: '',
|
|
3374
|
-
sessionStandaloneTitle: '',
|
|
3375
|
-
sessionStandaloneSourceLabel: '',
|
|
3376
|
-
sessionStandaloneLoading: false,
|
|
3377
|
-
sessionStandaloneRequestSeq: 0,
|
|
3378
|
-
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: {},
|
|
3379
3600
|
speedLoading: {},
|
|
3601
|
+
claudeSpeedResults: {},
|
|
3602
|
+
claudeSpeedLoading: {},
|
|
3603
|
+
claudeShareLoading: {},
|
|
3604
|
+
providerShareLoading: {},
|
|
3380
3605
|
newProvider: { name: '', url: '', key: '' },
|
|
3381
3606
|
editingProvider: { name: '', url: '', key: '' },
|
|
3382
3607
|
newModelName: '',
|
|
@@ -3442,8 +3667,6 @@
|
|
|
3442
3667
|
openclawAgentsList: [],
|
|
3443
3668
|
openclawProviders: [],
|
|
3444
3669
|
openclawMissingProviders: [],
|
|
3445
|
-
recentConfigs: [],
|
|
3446
|
-
recentLoading: false,
|
|
3447
3670
|
healthCheckLoading: false,
|
|
3448
3671
|
healthCheckResult: null,
|
|
3449
3672
|
healthCheckRemote: false
|
|
@@ -3451,6 +3674,12 @@
|
|
|
3451
3674
|
},
|
|
3452
3675
|
mounted() {
|
|
3453
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
|
+
}
|
|
3454
3683
|
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
3455
3684
|
if (savedConfigs) {
|
|
3456
3685
|
try {
|
|
@@ -3462,22 +3691,11 @@
|
|
|
3462
3691
|
}
|
|
3463
3692
|
}
|
|
3464
3693
|
localStorage.setItem('claudeConfigs', JSON.stringify(this.claudeConfigs));
|
|
3465
|
-
|
|
3466
|
-
const configNames = Object.keys(this.claudeConfigs);
|
|
3467
|
-
if (configNames.length > 0) {
|
|
3468
|
-
this.currentClaudeConfig = configNames[0];
|
|
3469
|
-
}
|
|
3470
3694
|
} catch (e) {
|
|
3471
3695
|
console.error('加载 Claude 配置失败:', e);
|
|
3472
3696
|
}
|
|
3473
3697
|
}
|
|
3474
|
-
|
|
3475
|
-
const configNames = Object.keys(this.claudeConfigs);
|
|
3476
|
-
if (configNames.length > 0) {
|
|
3477
|
-
this.currentClaudeConfig = configNames[0];
|
|
3478
|
-
}
|
|
3479
|
-
}
|
|
3480
|
-
this.syncClaudeModelFromConfig();
|
|
3698
|
+
void this.refreshClaudeSelectionFromSettings({ silent: true });
|
|
3481
3699
|
const savedOpenclawConfigs = localStorage.getItem('openclawConfigs');
|
|
3482
3700
|
if (savedOpenclawConfigs) {
|
|
3483
3701
|
try {
|
|
@@ -3504,6 +3722,17 @@
|
|
|
3504
3722
|
},
|
|
3505
3723
|
sessionQueryPlaceholder() {
|
|
3506
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;
|
|
3507
3736
|
}
|
|
3508
3737
|
},
|
|
3509
3738
|
methods: {
|
|
@@ -3519,6 +3748,12 @@
|
|
|
3519
3748
|
} else {
|
|
3520
3749
|
this.currentProvider = statusRes.provider;
|
|
3521
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
|
+
}
|
|
3522
3757
|
this.providersList = listRes.providers;
|
|
3523
3758
|
await this.loadModelsForProvider(this.currentProvider);
|
|
3524
3759
|
if (statusRes.configReady === false) {
|
|
@@ -3529,7 +3764,6 @@
|
|
|
3529
3764
|
}
|
|
3530
3765
|
this.maybeShowStarPrompt();
|
|
3531
3766
|
}
|
|
3532
|
-
await this.loadRecentConfigs();
|
|
3533
3767
|
} catch (e) {
|
|
3534
3768
|
this.initError = '连接失败: ' + e.message;
|
|
3535
3769
|
} finally {
|
|
@@ -3580,6 +3814,109 @@
|
|
|
3580
3814
|
return this.claudeConfigs[this.currentClaudeConfig] || null;
|
|
3581
3815
|
},
|
|
3582
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
|
+
|
|
3583
3920
|
syncClaudeModelFromConfig() {
|
|
3584
3921
|
const config = this.getCurrentClaudeConfig();
|
|
3585
3922
|
this.currentClaudeModel = config && config.model ? config.model : '';
|
|
@@ -3604,6 +3941,10 @@
|
|
|
3604
3941
|
|
|
3605
3942
|
async loadClaudeModels() {
|
|
3606
3943
|
const config = this.getCurrentClaudeConfig();
|
|
3944
|
+
if (!config) {
|
|
3945
|
+
this.resetClaudeModelsState();
|
|
3946
|
+
return;
|
|
3947
|
+
}
|
|
3607
3948
|
const baseUrl = (config.baseUrl || '').trim();
|
|
3608
3949
|
const apiKey = (config.apiKey || '').trim();
|
|
3609
3950
|
|
|
@@ -3656,13 +3997,21 @@
|
|
|
3656
3997
|
},
|
|
3657
3998
|
|
|
3658
3999
|
switchConfigMode(mode) {
|
|
4000
|
+
this.mainTab = 'config';
|
|
3659
4001
|
this.configMode = mode;
|
|
3660
4002
|
if (mode === 'claude') {
|
|
3661
4003
|
this.refreshClaudeModelContext();
|
|
3662
4004
|
}
|
|
3663
|
-
|
|
4005
|
+
},
|
|
4006
|
+
|
|
4007
|
+
switchMainTab(tab) {
|
|
4008
|
+
this.mainTab = tab;
|
|
4009
|
+
if (tab === 'sessions' && this.sessionsList.length === 0) {
|
|
3664
4010
|
this.loadSessions();
|
|
3665
4011
|
}
|
|
4012
|
+
if (tab === 'config' && this.configMode === 'claude') {
|
|
4013
|
+
this.refreshClaudeModelContext();
|
|
4014
|
+
}
|
|
3666
4015
|
},
|
|
3667
4016
|
|
|
3668
4017
|
getSessionStandaloneContext() {
|
|
@@ -3708,7 +4057,7 @@
|
|
|
3708
4057
|
if (!context.requested) return;
|
|
3709
4058
|
|
|
3710
4059
|
this.sessionStandalone = true;
|
|
3711
|
-
this.
|
|
4060
|
+
this.mainTab = 'sessions';
|
|
3712
4061
|
|
|
3713
4062
|
if (context.error || !context.params) {
|
|
3714
4063
|
this.sessionStandaloneError = `会话链接参数不完整:${context.error || '参数解析失败'}`;
|
|
@@ -3716,22 +4065,22 @@
|
|
|
3716
4065
|
}
|
|
3717
4066
|
|
|
3718
4067
|
const sourceLabel = context.params.source === 'codex' ? 'Codex' : 'Claude Code';
|
|
3719
|
-
this.activeSession = {
|
|
3720
|
-
source: context.params.source,
|
|
3721
|
-
sourceLabel,
|
|
3722
|
-
sessionId: context.params.sessionId,
|
|
3723
|
-
filePath: context.params.filePath,
|
|
3724
|
-
title: context.params.sessionId || context.params.filePath || '会话'
|
|
3725
|
-
};
|
|
3726
|
-
this.activeSessionMessages = [];
|
|
3727
|
-
this.activeSessionDetailError = '';
|
|
3728
|
-
this.activeSessionDetailClipped = false;
|
|
3729
|
-
this.sessionStandaloneError = '';
|
|
3730
|
-
this.sessionStandaloneText = '';
|
|
3731
|
-
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
3732
|
-
this.sessionStandaloneSourceLabel = sourceLabel;
|
|
3733
|
-
this.loadSessionStandalonePlain();
|
|
3734
|
-
},
|
|
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
|
+
},
|
|
3735
4084
|
|
|
3736
4085
|
buildSessionStandaloneUrl(session) {
|
|
3737
4086
|
if (!session) return '';
|
|
@@ -3789,10 +4138,14 @@
|
|
|
3789
4138
|
|
|
3790
4139
|
buildResumeCommand(session) {
|
|
3791
4140
|
const sessionId = session && session.sessionId ? String(session.sessionId).trim() : '';
|
|
3792
|
-
|
|
4141
|
+
const arg = this.quoteResumeArg(sessionId);
|
|
4142
|
+
if (this.sessionResumeWithYolo) {
|
|
4143
|
+
return `codex --yolo resume ${arg}`;
|
|
4144
|
+
}
|
|
4145
|
+
return `codex resume ${arg}`;
|
|
3793
4146
|
},
|
|
3794
4147
|
|
|
3795
|
-
|
|
4148
|
+
quoteShellArg(value) {
|
|
3796
4149
|
const text = typeof value === 'string' ? value : String(value || '');
|
|
3797
4150
|
if (!text) return "''";
|
|
3798
4151
|
if (/^[a-zA-Z0-9._-]+$/.test(text)) return text;
|
|
@@ -3800,6 +4153,10 @@
|
|
|
3800
4153
|
return `'${escaped}'`;
|
|
3801
4154
|
},
|
|
3802
4155
|
|
|
4156
|
+
quoteResumeArg(value) {
|
|
4157
|
+
return this.quoteShellArg(value);
|
|
4158
|
+
},
|
|
4159
|
+
|
|
3803
4160
|
fallbackCopyText(text) {
|
|
3804
4161
|
let textarea = null;
|
|
3805
4162
|
try {
|
|
@@ -3860,6 +4217,121 @@
|
|
|
3860
4217
|
this.showMessage('复制失败,请手动复制命令', 'error');
|
|
3861
4218
|
},
|
|
3862
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
|
+
|
|
3863
4335
|
async cloneSession(session) {
|
|
3864
4336
|
if (!this.isCloneAvailable(session)) {
|
|
3865
4337
|
this.showMessage('当前会话不支持克隆', 'error');
|
|
@@ -4034,6 +4506,11 @@
|
|
|
4034
4506
|
}
|
|
4035
4507
|
},
|
|
4036
4508
|
|
|
4509
|
+
onSessionResumeYoloChange() {
|
|
4510
|
+
const value = this.sessionResumeWithYolo ? '1' : '0';
|
|
4511
|
+
localStorage.setItem('codexmateSessionResumeYolo', value);
|
|
4512
|
+
},
|
|
4513
|
+
|
|
4037
4514
|
async onSessionSourceChange() {
|
|
4038
4515
|
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
4039
4516
|
await this.loadSessions();
|
|
@@ -4138,66 +4615,66 @@
|
|
|
4138
4615
|
}
|
|
4139
4616
|
},
|
|
4140
4617
|
|
|
4141
|
-
async selectSession(session) {
|
|
4142
|
-
if (!session) return;
|
|
4143
|
-
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
4144
|
-
this.activeSession = session;
|
|
4145
|
-
this.activeSessionMessages = [];
|
|
4146
|
-
this.activeSessionDetailError = '';
|
|
4147
|
-
this.activeSessionDetailClipped = false;
|
|
4148
|
-
await this.loadActiveSessionDetail();
|
|
4149
|
-
},
|
|
4150
|
-
|
|
4151
|
-
async loadSessionStandalonePlain() {
|
|
4152
|
-
if (!this.activeSession) {
|
|
4153
|
-
this.sessionStandaloneText = '';
|
|
4154
|
-
this.sessionStandaloneTitle = '会话';
|
|
4155
|
-
this.sessionStandaloneSourceLabel = '';
|
|
4156
|
-
this.sessionStandaloneError = '';
|
|
4157
|
-
return;
|
|
4158
|
-
}
|
|
4159
|
-
|
|
4160
|
-
const requestSeq = ++this.sessionStandaloneRequestSeq;
|
|
4161
|
-
this.sessionStandaloneLoading = true;
|
|
4162
|
-
this.sessionStandaloneError = '';
|
|
4163
|
-
try {
|
|
4164
|
-
const res = await api('session-plain', {
|
|
4165
|
-
source: this.activeSession.source,
|
|
4166
|
-
sessionId: this.activeSession.sessionId,
|
|
4167
|
-
filePath: this.activeSession.filePath
|
|
4168
|
-
});
|
|
4169
|
-
|
|
4170
|
-
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
4171
|
-
return;
|
|
4172
|
-
}
|
|
4173
|
-
|
|
4174
|
-
if (res.error) {
|
|
4175
|
-
this.sessionStandaloneText = '';
|
|
4176
|
-
this.sessionStandaloneError = res.error;
|
|
4177
|
-
return;
|
|
4178
|
-
}
|
|
4179
|
-
|
|
4180
|
-
this.sessionStandaloneSourceLabel = res.sourceLabel || this.activeSession.sourceLabel || '';
|
|
4181
|
-
this.sessionStandaloneTitle = res.sessionId || this.activeSession.title || '会话';
|
|
4182
|
-
this.sessionStandaloneText = typeof res.text === 'string' ? res.text : '';
|
|
4183
|
-
} catch (e) {
|
|
4184
|
-
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
4185
|
-
return;
|
|
4186
|
-
}
|
|
4187
|
-
this.sessionStandaloneText = '';
|
|
4188
|
-
this.sessionStandaloneError = '加载会话内容失败: ' + e.message;
|
|
4189
|
-
} finally {
|
|
4190
|
-
if (requestSeq === this.sessionStandaloneRequestSeq) {
|
|
4191
|
-
this.sessionStandaloneLoading = false;
|
|
4192
|
-
}
|
|
4193
|
-
}
|
|
4194
|
-
},
|
|
4195
|
-
|
|
4196
|
-
async loadActiveSessionDetail() {
|
|
4197
|
-
if (!this.activeSession) {
|
|
4198
|
-
this.activeSessionMessages = [];
|
|
4199
|
-
this.activeSessionDetailError = '';
|
|
4200
|
-
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;
|
|
4201
4678
|
return;
|
|
4202
4679
|
}
|
|
4203
4680
|
|
|
@@ -4283,22 +4760,22 @@
|
|
|
4283
4760
|
sessionId: session.sessionId,
|
|
4284
4761
|
filePath: session.filePath
|
|
4285
4762
|
});
|
|
4286
|
-
if (res.error) {
|
|
4287
|
-
this.showMessage(res.error, 'error');
|
|
4288
|
-
return;
|
|
4289
|
-
}
|
|
4290
|
-
|
|
4291
|
-
const fileName = res.fileName || `${session.source || 'session'}-${session.sessionId || Date.now()}.md`;
|
|
4292
|
-
this.downloadTextFile(fileName, res.content || '');
|
|
4293
|
-
if (res.truncated) {
|
|
4294
|
-
const maxLabel = res.maxMessages === 'all' ? 'all' : res.maxMessages;
|
|
4295
|
-
this.showMessage(`会话导出完成(已截断:最多 ${maxLabel} 条消息)`, 'info');
|
|
4296
|
-
} else {
|
|
4297
|
-
this.showMessage('会话导出完成', 'success');
|
|
4298
|
-
}
|
|
4299
|
-
} catch (e) {
|
|
4300
|
-
this.showMessage('导出失败: ' + e.message, 'error');
|
|
4301
|
-
} 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 {
|
|
4302
4779
|
this.sessionExporting[key] = false;
|
|
4303
4780
|
}
|
|
4304
4781
|
},
|
|
@@ -4313,32 +4790,8 @@
|
|
|
4313
4790
|
await this.openConfigTemplateEditor();
|
|
4314
4791
|
},
|
|
4315
4792
|
|
|
4316
|
-
async
|
|
4317
|
-
this.
|
|
4318
|
-
try {
|
|
4319
|
-
const res = await api('get-recent-configs');
|
|
4320
|
-
if (res && Array.isArray(res.items)) {
|
|
4321
|
-
this.recentConfigs = res.items;
|
|
4322
|
-
} else {
|
|
4323
|
-
this.recentConfigs = [];
|
|
4324
|
-
}
|
|
4325
|
-
} catch (e) {
|
|
4326
|
-
this.recentConfigs = [];
|
|
4327
|
-
} finally {
|
|
4328
|
-
this.recentLoading = false;
|
|
4329
|
-
}
|
|
4330
|
-
},
|
|
4331
|
-
|
|
4332
|
-
async applyRecentConfig(item) {
|
|
4333
|
-
if (!item || !item.provider || !item.model) {
|
|
4334
|
-
this.showMessage('最近配置无效,无法应用', 'error');
|
|
4335
|
-
return;
|
|
4336
|
-
}
|
|
4337
|
-
this.currentProvider = item.provider;
|
|
4338
|
-
this.currentModel = item.model;
|
|
4339
|
-
await this.openConfigTemplateEditor({
|
|
4340
|
-
appendHint: '最近使用配置,确认后将写入 config.toml'
|
|
4341
|
-
});
|
|
4793
|
+
async onServiceTierChange() {
|
|
4794
|
+
await this.openConfigTemplateEditor();
|
|
4342
4795
|
},
|
|
4343
4796
|
|
|
4344
4797
|
async runHealthCheck() {
|
|
@@ -4393,6 +4846,12 @@
|
|
|
4393
4846
|
this.healthCheckResult = null;
|
|
4394
4847
|
this.showMessage('健康检查失败: ' + e.message, 'error');
|
|
4395
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
|
+
}
|
|
4396
4855
|
this.healthCheckLoading = false;
|
|
4397
4856
|
}
|
|
4398
4857
|
},
|
|
@@ -4407,7 +4866,8 @@
|
|
|
4407
4866
|
try {
|
|
4408
4867
|
const res = await api('get-config-template', {
|
|
4409
4868
|
provider: this.currentProvider,
|
|
4410
|
-
model: this.currentModel
|
|
4869
|
+
model: this.currentModel,
|
|
4870
|
+
serviceTier: this.serviceTier
|
|
4411
4871
|
});
|
|
4412
4872
|
if (res.error) {
|
|
4413
4873
|
this.showMessage(res.error, 'error');
|
|
@@ -4816,6 +5276,10 @@
|
|
|
4816
5276
|
if (this.claudeConfigs[name]) {
|
|
4817
5277
|
return this.showMessage('配置名称已存在', 'error');
|
|
4818
5278
|
}
|
|
5279
|
+
const duplicateName = this.findDuplicateClaudeConfigName(this.newClaudeConfig);
|
|
5280
|
+
if (duplicateName) {
|
|
5281
|
+
return this.showMessage('已存在相同配置,已忽略添加', 'info');
|
|
5282
|
+
}
|
|
4819
5283
|
|
|
4820
5284
|
this.claudeConfigs[name] = {
|
|
4821
5285
|
apiKey: this.newClaudeConfig.apiKey,
|
|
@@ -5910,6 +6374,33 @@
|
|
|
5910
6374
|
}
|
|
5911
6375
|
},
|
|
5912
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
|
+
|
|
5913
6404
|
showMessage(text, type) {
|
|
5914
6405
|
this.message = text;
|
|
5915
6406
|
this.messageType = type || 'info';
|