codexmate 0.0.10 → 0.0.12

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.
Files changed (63) hide show
  1. package/README.md +29 -11
  2. package/README.zh-CN.md +29 -11
  3. package/package.json +53 -36
  4. package/res/logo.png +0 -0
  5. package/{cli.js → src/cli.js} +822 -327
  6. package/src/lib/cli-file-utils.js +151 -0
  7. package/src/lib/cli-models-utils.js +152 -0
  8. package/src/lib/cli-network-utils.js +148 -0
  9. package/src/lib/cli-session-utils.js +121 -0
  10. package/src/lib/cli-utils.js +139 -0
  11. package/src/res/json5.min.js +1 -0
  12. package/src/res/logo.png +0 -0
  13. package/src/res/screenshot.png +0 -0
  14. package/src/res/vue.global.js +18552 -0
  15. package/src/web-ui/app.js +2970 -0
  16. package/src/web-ui/index.html +1310 -0
  17. package/src/web-ui/logic.mjs +157 -0
  18. package/src/web-ui/styles.css +2868 -0
  19. package/src/web-ui.html +17 -0
  20. package/web-ui/app.js +273 -144
  21. package/web-ui/index.html +1310 -0
  22. package/web-ui/logic.mjs +21 -21
  23. package/web-ui/styles.css +2868 -0
  24. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
  25. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  26. package/.github/workflows/ci.yml +0 -26
  27. package/.github/workflows/release.yml +0 -159
  28. package/.planning/.fix-attempts +0 -1
  29. package/.planning/.lock +0 -6
  30. package/.planning/.verify-cache.json +0 -14
  31. package/.planning/CHECKPOINT.json +0 -46
  32. package/.planning/DESIGN.md +0 -26
  33. package/.planning/HISTORY.json +0 -124
  34. package/.planning/PLAN.md +0 -69
  35. package/.planning/REVIEW.md +0 -41
  36. package/.planning/STATE.md +0 -12
  37. package/.planning/STATS.json +0 -13
  38. package/.planning/VERIFICATION.md +0 -70
  39. package/.planning/daude-code-plan.md +0 -51
  40. package/.planning/research/architecture.md +0 -32
  41. package/.planning/research/conventions.md +0 -36
  42. package/.planning/task_1-REVIEW.md +0 -29
  43. package/.planning/task_1-SUMMARY.md +0 -32
  44. package/.planning/task_2-REVIEW.md +0 -24
  45. package/.planning/task_2-SUMMARY.md +0 -37
  46. package/.planning/task_3-REVIEW.md +0 -25
  47. package/.planning/task_3-SUMMARY.md +0 -31
  48. package/cmd/publish-npm.cmd +0 -65
  49. package/tests/e2e/helpers.js +0 -214
  50. package/tests/e2e/recent-health.e2e.js +0 -142
  51. package/tests/e2e/run.js +0 -154
  52. package/tests/e2e/test-claude.js +0 -21
  53. package/tests/e2e/test-config.js +0 -124
  54. package/tests/e2e/test-health-speed.js +0 -79
  55. package/tests/e2e/test-openclaw.js +0 -47
  56. package/tests/e2e/test-session-search.js +0 -114
  57. package/tests/e2e/test-sessions.js +0 -69
  58. package/tests/e2e/test-setup.js +0 -159
  59. package/tests/unit/run.mjs +0 -29
  60. package/tests/unit/web-ui-logic.test.mjs +0 -186
  61. package/web-ui.html +0 -3977
  62. /package/{CHANGELOG.md → doc/CHANGELOG.md} +0 -0
  63. /package/{CHANGELOG.zh-CN.md → doc/CHANGELOG.zh-CN.md} +0 -0
package/web-ui.html DELETED
@@ -1,3977 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Codex Mate</title>
7
- <script src="res/vue.global.js"></script>
8
- <script src="res/json5.min.js"></script>
9
- <style>
10
- @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Source+Sans+3:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap');
11
-
12
- /* ============================================
13
- 设计系统 - Design Tokens
14
- ============================================ */
15
- :root {
16
- /* 色彩系统:去除杂纹,强调干净留白与温柔橙红 */
17
- --color-brand: #D0583A;
18
- --color-brand-dark: #B8442B;
19
- --color-brand-light: rgba(208, 88, 58, 0.14);
20
- --color-brand-subtle: rgba(201, 94, 75, 0.2);
21
-
22
- --color-bg: #F6EFE6;
23
- --color-surface: #FFFDF9;
24
- --color-surface-alt: #FFF7F0;
25
- --color-surface-elevated: #FFFFFF;
26
- --color-surface-tint: rgba(255, 255, 255, 0.78);
27
- --color-text-primary: #1B1714;
28
- --color-text-secondary: #4B4038;
29
- --color-text-tertiary: #7A6A5D;
30
- --color-text-muted: #6C5B50;
31
- --color-border: #D8C9B8;
32
- --color-border-soft: rgba(216, 201, 184, 0.45);
33
- --color-border-strong: rgba(216, 201, 184, 0.8);
34
-
35
- --color-success: #4B8B6A;
36
- --color-error: #C44536;
37
-
38
- --bg-warm-gradient:
39
- radial-gradient(circle at 16% 10%, rgba(201, 94, 75, 0.18), transparent 45%),
40
- radial-gradient(circle at 90% 6%, rgba(255, 255, 255, 0.85), transparent 40%),
41
- linear-gradient(135deg, #F6EFE6 0%, #EFE1D4 45%, #F6EFE6 100%);
42
-
43
- /* 字体系统 */
44
- --font-family-body: 'Source Sans 3', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
45
- --font-family-display: 'Space Grotesk', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
46
- --font-family-mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
47
- --font-family: var(--font-family-body);
48
-
49
- --font-size-display: 52px;
50
- --font-size-title: 18px;
51
- --font-size-body: 15px;
52
- --font-size-secondary: 13px;
53
- --font-size-caption: 11px;
54
-
55
- --font-weight-display: 600;
56
- --font-weight-title: 600;
57
- --font-weight-body: 400;
58
- --font-weight-secondary: 500;
59
- --font-weight-caption: 500;
60
-
61
- --line-height-tight: 1.12;
62
- --line-height-normal: 1.5;
63
-
64
- /* 间距系统 */
65
- --spacing-xs: 8px;
66
- --spacing-sm: 16px;
67
- --spacing-md: 24px;
68
- --spacing-lg: 40px;
69
- --spacing-xl: 64px;
70
-
71
- /* 圆角系统 */
72
- --radius-sm: 8px;
73
- --radius-lg: 12px;
74
- --radius-xl: 18px;
75
- --radius-full: 50px;
76
-
77
- /* 阴影系统 - 多层叠加提升真实感 */
78
- --shadow-subtle:
79
- 0 1px 2px rgba(31, 26, 23, 0.04),
80
- 0 1px 3px rgba(31, 26, 23, 0.02);
81
- --shadow-card:
82
- 0 1px 3px rgba(31, 26, 23, 0.04),
83
- 0 4px 12px rgba(31, 26, 23, 0.03);
84
- --shadow-card-hover:
85
- 0 2px 4px rgba(31, 26, 23, 0.04),
86
- 0 8px 20px rgba(31, 26, 23, 0.06);
87
- --shadow-float:
88
- 0 6px 16px rgba(31, 26, 23, 0.08),
89
- 0 18px 36px rgba(31, 26, 23, 0.06);
90
- --shadow-raised:
91
- 0 4px 12px rgba(31, 26, 23, 0.06),
92
- 0 12px 32px rgba(31, 26, 23, 0.04);
93
- --shadow-modal:
94
- 0 8px 24px rgba(31, 26, 23, 0.08),
95
- 0 24px 64px rgba(31, 26, 23, 0.06);
96
- --shadow-input-focus:
97
- 0 0 0 3px var(--color-brand-light),
98
- 0 1px 3px rgba(31, 26, 23, 0.04);
99
-
100
- /* 动画 - 更细腻的曲线 */
101
- --transition-instant: 100ms;
102
- --transition-fast: 150ms;
103
- --transition-normal: 250ms;
104
- --transition-slow: 350ms;
105
- --ease-spring: cubic-bezier(0.16, 1, 0.3, 1);
106
- --ease-spring-soft: cubic-bezier(0.25, 1, 0.5, 1);
107
- --ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
108
- --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
109
- }
110
-
111
- /* ============================================
112
- 基础重置
113
- ============================================ */
114
- * {
115
- margin: 0;
116
- padding: 0;
117
- box-sizing: border-box;
118
- }
119
-
120
- body {
121
- font-family: var(--font-family-body);
122
- background-color: var(--color-bg);
123
- background: var(--bg-warm-gradient);
124
- color: var(--color-text-primary);
125
- display: flex;
126
- justify-content: center;
127
- align-items: center;
128
- min-height: 100vh;
129
- padding: var(--spacing-lg) var(--spacing-md);
130
- -webkit-font-smoothing: antialiased;
131
- -moz-osx-font-smoothing: grayscale;
132
- position: relative;
133
- overflow-x: hidden;
134
- }
135
-
136
- /* ============================================
137
- 容器
138
- ============================================ */
139
- body::before {
140
- content: "";
141
- position: fixed;
142
- inset: 0;
143
- background-image:
144
- radial-gradient(circle at 12% 18%, rgba(201, 94, 75, 0.1), transparent 38%),
145
- repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.4) 0 1px, transparent 1px 120px),
146
- repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.35) 0 1px, transparent 1px 120px);
147
- opacity: 0.45;
148
- pointer-events: none;
149
- z-index: 0;
150
- }
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
-
166
- /* ============================================
167
- 容器
168
- ============================================ */
169
- .container {
170
- width: 100%;
171
- max-width: 1280px;
172
- margin: 0 auto;
173
- padding: 16px 12px 28px;
174
- position: relative;
175
- z-index: 1;
176
- }
177
-
178
- /* ============================================
179
- 布局:双列(侧栏 + 主区)
180
- ============================================ */
181
- .app-shell {
182
- display: grid;
183
- grid-template-columns: 320px minmax(0, 1fr);
184
- gap: var(--spacing-md);
185
- align-items: flex-start;
186
- }
187
-
188
- .app-shell.standalone {
189
- grid-template-columns: 1fr;
190
- }
191
-
192
- .side-rail {
193
- position: sticky;
194
- top: var(--spacing-md);
195
- align-self: start;
196
- display: flex;
197
- flex-direction: column;
198
- gap: var(--spacing-sm);
199
- padding: var(--spacing-md) var(--spacing-sm);
200
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 250, 245, 0.9) 100%);
201
- border: 1px solid rgba(216, 201, 184, 0.65);
202
- border-radius: var(--radius-xl);
203
- box-shadow: var(--shadow-card);
204
- min-height: 420px;
205
- }
206
-
207
- .side-rail .brand-title {
208
- font-size: 24px;
209
- margin-bottom: 2px;
210
- }
211
-
212
- .side-section {
213
- display: flex;
214
- flex-direction: column;
215
- gap: 10px;
216
- }
217
-
218
- .side-section-title {
219
- font-size: var(--font-size-secondary);
220
- font-weight: var(--font-weight-secondary);
221
- color: var(--color-text-tertiary);
222
- letter-spacing: 0.01em;
223
- padding: 0 var(--spacing-xs);
224
- }
225
-
226
- .side-item {
227
- width: 100%;
228
- text-align: left;
229
- padding: 12px var(--spacing-sm);
230
- border-radius: var(--radius-lg);
231
- border: 1px solid var(--color-border-soft);
232
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(255, 247, 240, 0.95) 100%);
233
- color: var(--color-text-secondary);
234
- cursor: pointer;
235
- transition: all var(--transition-normal) var(--ease-spring);
236
- display: flex;
237
- flex-direction: column;
238
- gap: 6px;
239
- box-shadow: var(--shadow-subtle);
240
- }
241
-
242
- .side-item:hover {
243
- border-color: var(--color-brand);
244
- color: var(--color-text-primary);
245
- transform: translateY(-1px);
246
- box-shadow: var(--shadow-card-hover);
247
- }
248
-
249
- .side-item.active {
250
- border-color: var(--color-brand);
251
- background: linear-gradient(135deg, rgba(201, 94, 75, 0.14), rgba(255, 255, 255, 0.96));
252
- color: var(--color-text-primary);
253
- box-shadow: var(--shadow-float);
254
- }
255
-
256
- .side-item-title {
257
- font-size: var(--font-size-body);
258
- font-weight: var(--font-weight-secondary);
259
- letter-spacing: -0.01em;
260
- }
261
-
262
- .side-item-meta {
263
- font-size: var(--font-size-caption);
264
- color: var(--color-text-tertiary);
265
- display: flex;
266
- gap: 8px;
267
- flex-wrap: wrap;
268
- }
269
-
270
- .side-item-meta > span {
271
- min-width: 0;
272
- overflow-wrap: anywhere;
273
- word-break: break-word;
274
- }
275
-
276
- .top-tabs {
277
- display: none !important;
278
- }
279
-
280
- .brand-block {
281
- margin-bottom: var(--spacing-md);
282
- }
283
-
284
- .brand-title {
285
- font-size: 30px;
286
- line-height: 1.05;
287
- font-family: var(--font-family-display);
288
- color: var(--color-text-primary);
289
- letter-spacing: -0.02em;
290
- }
291
-
292
- .brand-title .accent {
293
- color: var(--color-brand);
294
- }
295
-
296
- .brand-subtitle {
297
- margin-top: 8px;
298
- font-size: var(--font-size-secondary);
299
- color: var(--color-text-tertiary);
300
- line-height: 1.45;
301
- }
302
-
303
- .main-tabs {
304
- display: flex;
305
- gap: 10px;
306
- }
307
-
308
- .main-tab-btn {
309
- flex: 1;
310
- text-align: center;
311
- border: 1px solid rgba(216, 201, 184, 0.55);
312
- background: rgba(255, 255, 255, 0.95);
313
- border-radius: var(--radius-lg);
314
- padding: 12px 14px;
315
- cursor: pointer;
316
- color: var(--color-text-secondary);
317
- font-size: var(--font-size-body);
318
- font-weight: var(--font-weight-secondary);
319
- box-shadow: var(--shadow-subtle);
320
- transition: all var(--transition-normal) var(--ease-spring);
321
- }
322
-
323
- .main-tab-btn:hover {
324
- border-color: var(--color-brand);
325
- color: var(--color-text-primary);
326
- transform: translateY(-1px);
327
- }
328
-
329
- .main-tab-btn.active {
330
- border-color: var(--color-brand);
331
- box-shadow: 0 10px 24px rgba(27, 23, 20, 0.08);
332
- color: var(--color-text-primary);
333
- background: linear-gradient(135deg, rgba(201, 94, 75, 0.12), rgba(255, 255, 255, 0.95));
334
- }
335
-
336
- .status-strip {
337
- display: flex;
338
- flex-wrap: wrap;
339
- gap: var(--spacing-xs);
340
- margin-bottom: var(--spacing-sm);
341
- margin-top: 6px;
342
- }
343
-
344
- .status-chip {
345
- min-width: 200px;
346
- padding: 10px 12px;
347
- border-radius: var(--radius-lg);
348
- border: 1px solid var(--color-border-soft);
349
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(255, 247, 240, 0.96) 100%);
350
- box-shadow: var(--shadow-subtle);
351
- }
352
-
353
- .status-chip .label {
354
- display: block;
355
- font-size: var(--font-size-caption);
356
- color: var(--color-text-tertiary);
357
- margin-bottom: 4px;
358
- }
359
-
360
- .status-chip .value {
361
- font-size: var(--font-size-body);
362
- font-weight: var(--font-weight-secondary);
363
- color: var(--color-text-primary);
364
- letter-spacing: -0.01em;
365
- white-space: normal;
366
- overflow-wrap: anywhere;
367
- word-break: break-word;
368
- }
369
-
370
- .main-panel {
371
- min-width: 0;
372
- background: rgba(255, 255, 255, 0.9);
373
- border: 1px solid rgba(255, 255, 255, 0.65);
374
- border-radius: 18px;
375
- box-shadow: 0 12px 30px rgba(27, 23, 20, 0.08);
376
- padding: var(--spacing-md) var(--spacing-lg);
377
- backdrop-filter: blur(8px);
378
- position: relative;
379
- overflow-x: hidden;
380
- overflow-y: visible;
381
- }
382
-
383
- .panel-header {
384
- margin-bottom: 12px;
385
- text-align: left;
386
- }
387
-
388
- .hero {
389
- margin-bottom: var(--spacing-sm);
390
- }
391
-
392
- .hero-title {
393
- font-size: 48px;
394
- line-height: 1.05;
395
- font-family: var(--font-family-display);
396
- color: var(--color-text-primary);
397
- letter-spacing: -0.02em;
398
- }
399
-
400
- .hero-title .accent {
401
- color: var(--color-brand);
402
- }
403
-
404
- .hero-subtitle {
405
- margin-top: 8px;
406
- font-size: var(--font-size-body);
407
- color: var(--color-text-tertiary);
408
- line-height: 1.5;
409
- }
410
-
411
- .top-tabs {
412
- margin: 14px 0 18px;
413
- background: rgba(255, 255, 255, 0.92);
414
- border: 1px solid rgba(255, 255, 255, 0.7);
415
- border-radius: 14px;
416
- padding: 6px;
417
- box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.06);
418
- display: grid;
419
- grid-template-columns: repeat(4, 1fr);
420
- gap: 8px;
421
- backdrop-filter: blur(6px);
422
- }
423
-
424
- .top-tab {
425
- border: 1px solid rgba(216, 201, 184, 0.55);
426
- border-radius: 12px;
427
- background: rgba(255, 255, 255, 0.96);
428
- padding: 11px 10px;
429
- font-size: var(--font-size-body);
430
- color: var(--color-text-secondary);
431
- text-align: center;
432
- cursor: pointer;
433
- transition: all var(--transition-normal) var(--ease-spring);
434
- box-shadow: var(--shadow-subtle);
435
- }
436
-
437
- .top-tab:hover {
438
- border-color: var(--color-brand);
439
- color: var(--color-text-primary);
440
- transform: translateY(-1px);
441
- }
442
-
443
- .top-tab.active {
444
- border-color: var(--color-brand);
445
- color: var(--color-text-primary);
446
- background: linear-gradient(135deg, rgba(201, 94, 75, 0.12), rgba(255, 255, 255, 0.95));
447
- box-shadow: 0 10px 24px rgba(27, 23, 20, 0.08);
448
- }
449
-
450
- .config-subtabs {
451
- display: flex;
452
- gap: 8px;
453
- margin-bottom: 16px;
454
- padding: 6px;
455
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0.7));
456
- border-radius: var(--radius-lg);
457
- border: 1px solid rgba(255, 255, 255, 0.7);
458
- box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.05);
459
- }
460
-
461
- .config-subtab {
462
- border: 1px solid var(--color-border-soft);
463
- border-radius: var(--radius-lg);
464
- padding: 10px 14px;
465
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(255, 250, 245, 0.9));
466
- color: var(--color-text-secondary);
467
- cursor: pointer;
468
- font-size: var(--font-size-body);
469
- font-weight: var(--font-weight-secondary);
470
- transition: all var(--transition-normal) var(--ease-spring);
471
- box-shadow: var(--shadow-subtle);
472
- }
473
-
474
- .config-subtab:hover {
475
- border-color: var(--color-border-strong);
476
- color: var(--color-text-primary);
477
- }
478
-
479
- .config-subtab.active {
480
- border-color: var(--color-brand);
481
- color: var(--color-text-primary);
482
- background: linear-gradient(135deg, rgba(201, 94, 75, 0.18), rgba(255, 255, 255, 0.95));
483
- box-shadow: var(--shadow-card);
484
- }
485
-
486
- .content-wrapper {
487
- background: rgba(255, 255, 255, 0.9);
488
- border: 1px solid rgba(255, 255, 255, 0.7);
489
- border-radius: var(--radius-lg);
490
- box-shadow: var(--shadow-card);
491
- padding: 0;
492
- }
493
-
494
- .mode-content {
495
- border-radius: var(--radius-lg);
496
- background: rgba(255, 255, 255, 0.85);
497
- box-shadow: var(--shadow-subtle);
498
- padding: 10px;
499
- }
500
-
501
- /* ============================================
502
- 主标题
503
- ============================================ */
504
- .main-title {
505
- font-size: var(--font-size-display);
506
- font-weight: var(--font-weight-display);
507
- line-height: var(--line-height-tight);
508
- letter-spacing: -0.03em;
509
- margin-bottom: 10px;
510
- color: var(--color-text-primary);
511
- font-family: var(--font-family-display);
512
- background: linear-gradient(135deg, var(--color-text-primary) 0%, rgba(27, 23, 20, 0.78) 100%);
513
- -webkit-background-clip: text;
514
- -webkit-text-fill-color: transparent;
515
- background-clip: text;
516
- }
517
-
518
- .main-title .accent {
519
- color: var(--color-brand);
520
- -webkit-text-fill-color: var(--color-brand);
521
- position: relative;
522
- }
523
-
524
- .subtitle {
525
- font-size: var(--font-size-body);
526
- color: var(--color-text-tertiary);
527
- line-height: var(--line-height-normal);
528
- margin-bottom: 20px;
529
- max-width: 640px;
530
- letter-spacing: 0.01em;
531
- }
532
-
533
- /* ============================================
534
- 模式切换器 - Segmented Control
535
- ============================================ */
536
- .segmented-control {
537
- display: flex;
538
- background: rgba(255, 255, 255, 0.92);
539
- border-radius: var(--radius-xl);
540
- padding: 6px;
541
- margin-bottom: 20px;
542
- position: relative;
543
- box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.06);
544
- border: 1px solid rgba(255, 255, 255, 0.7);
545
- backdrop-filter: blur(6px);
546
- }
547
-
548
- .segment {
549
- flex: 1;
550
- padding: 11px 16px;
551
- border: none;
552
- background: transparent;
553
- font-size: var(--font-size-body);
554
- font-weight: var(--font-weight-secondary);
555
- color: var(--color-text-secondary);
556
- cursor: pointer;
557
- border-radius: 10px;
558
- transition: all var(--transition-normal) var(--ease-spring);
559
- position: relative;
560
- z-index: 2;
561
- letter-spacing: 0.01em;
562
- }
563
-
564
- .segment:hover {
565
- color: var(--color-text-primary);
566
- }
567
-
568
- .segment.active {
569
- color: var(--color-text-primary);
570
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.8) 100%);
571
- box-shadow: var(--shadow-subtle), inset 0 1px 0 rgba(255, 255, 255, 0.85);
572
- }
573
-
574
- /* ============================================
575
- 卡片列表
576
- ============================================ */
577
- .card-list {
578
- display: flex;
579
- flex-direction: column;
580
- gap: 12px;
581
- margin-bottom: 12px;
582
- }
583
-
584
- /* ============================================
585
- 卡片
586
- ============================================ */
587
- .card {
588
- background: linear-gradient(180deg, #fffdf9 0%, #fff8f2 100%);
589
- border-radius: var(--radius-lg);
590
- padding: 10px;
591
- display: flex;
592
- align-items: center;
593
- justify-content: space-between;
594
- cursor: pointer;
595
- transition:
596
- transform var(--transition-normal) var(--ease-spring),
597
- box-shadow var(--transition-normal) var(--ease-spring),
598
- background-color var(--transition-fast) var(--ease-smooth);
599
- box-shadow: 0 10px 24px rgba(27, 23, 20, 0.08);
600
- user-select: none;
601
- will-change: transform;
602
- border: 1px solid rgba(216, 201, 184, 0.55);
603
- position: relative;
604
- overflow: hidden;
605
- }
606
-
607
- .card:hover {
608
- transform: translateY(-2px) scale(1.005);
609
- box-shadow: 0 16px 32px rgba(27, 23, 20, 0.1);
610
- }
611
-
612
- .card::before,
613
- .card::after {
614
- content: "";
615
- position: absolute;
616
- pointer-events: none;
617
- }
618
-
619
- .card::before {
620
- left: 0;
621
- top: 10px;
622
- bottom: 10px;
623
- width: 3px;
624
- border-radius: 999px;
625
- background: transparent;
626
- transition: background var(--transition-fast) var(--ease-smooth);
627
- }
628
-
629
- .card::after {
630
- inset: 0;
631
- border-radius: inherit;
632
- background: linear-gradient(120deg, rgba(255, 255, 255, 0.7) 0%, transparent 55%);
633
- opacity: 0;
634
- transition: opacity var(--transition-normal) var(--ease-smooth);
635
- }
636
-
637
- .card:active {
638
- transform: translateY(0) scale(0.995);
639
- transition: transform var(--transition-instant) var(--ease-smooth);
640
- }
641
-
642
- .card.active {
643
- background: linear-gradient(to bottom, rgba(210, 107, 90, 0.14) 0%, rgba(255, 255, 255, 0.98) 100%);
644
- border-color: rgba(201, 94, 75, 0.55);
645
- box-shadow: 0 10px 28px rgba(210, 107, 90, 0.14);
646
- }
647
-
648
- .card.active::before {
649
- background: linear-gradient(180deg, rgba(201, 94, 75, 0.95) 0%, rgba(201, 94, 75, 0.35) 100%);
650
- }
651
-
652
- .card:hover::after {
653
- opacity: 0.6;
654
- }
655
-
656
- .card.active .card-icon {
657
- transform: scale(1.05);
658
- }
659
-
660
- .card-leading {
661
- display: flex;
662
- align-items: center;
663
- gap: var(--spacing-sm);
664
- flex: 1;
665
- min-width: 0;
666
- }
667
-
668
- .card-icon {
669
- width: 40px;
670
- height: 40px;
671
- border-radius: var(--radius-sm);
672
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(247, 241, 232, 0.65) 100%);
673
- display: flex;
674
- align-items: center;
675
- justify-content: center;
676
- font-size: var(--font-size-title);
677
- font-weight: var(--font-weight-title);
678
- color: var(--color-text-secondary);
679
- flex-shrink: 0;
680
- transition: all var(--transition-normal) var(--ease-spring-soft);
681
- box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.7);
682
- }
683
-
684
- .card.active .card-icon {
685
- background: linear-gradient(135deg, var(--color-brand) 0%, var(--color-brand-dark) 100%);
686
- color: white;
687
- box-shadow: 0 2px 8px rgba(210, 107, 90, 0.3);
688
- }
689
-
690
- .card-content {
691
- display: flex;
692
- flex-direction: column;
693
- gap: 2px;
694
- min-width: 0;
695
- }
696
-
697
- .card-title {
698
- font-size: var(--font-size-body);
699
- font-weight: var(--font-weight-secondary);
700
- color: var(--color-text-primary);
701
- white-space: nowrap;
702
- overflow: hidden;
703
- text-overflow: ellipsis;
704
- letter-spacing: -0.01em;
705
- }
706
-
707
- .card-subtitle {
708
- font-size: var(--font-size-secondary);
709
- color: var(--color-text-tertiary);
710
- white-space: nowrap;
711
- overflow: hidden;
712
- text-overflow: ellipsis;
713
- opacity: 0.8;
714
- }
715
-
716
- .card-trailing {
717
- display: flex;
718
- align-items: center;
719
- gap: var(--spacing-xs);
720
- }
721
-
722
- /* 卡片操作按钮 - hover 显示 */
723
- .card-actions {
724
- display: flex;
725
- gap: 8px;
726
- opacity: 0;
727
- transform: translateX(4px);
728
- transition: all var(--transition-normal) var(--ease-spring);
729
- }
730
-
731
- .card:hover .card-actions {
732
- opacity: 1;
733
- transform: translateX(0);
734
- }
735
-
736
- .mode-cards .card-actions {
737
- opacity: 1;
738
- transform: translateX(0);
739
- }
740
-
741
- .card-action-btn {
742
- width: 40px;
743
- height: 40px;
744
- border-radius: 10px;
745
- border: 1px solid rgba(70, 86, 110, 0.22);
746
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(255, 255, 255, 0.9));
747
- color: var(--color-text-secondary);
748
- cursor: pointer;
749
- display: flex;
750
- align-items: center;
751
- justify-content: center;
752
- transition: all var(--transition-fast) var(--ease-spring);
753
- box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.04);
754
- }
755
-
756
- .card-action-btn:hover {
757
- background: linear-gradient(135deg, rgba(210, 107, 90, 0.08) 0%, rgba(255, 255, 255, 0.95) 100%);
758
- color: var(--color-text-primary);
759
- transform: translateY(-1px);
760
- }
761
-
762
- .card-action-btn.delete:hover {
763
- background: linear-gradient(135deg, rgba(200, 74, 58, 0.1) 0%, rgba(200, 74, 58, 0.05) 100%);
764
- color: var(--color-error);
765
- }
766
-
767
- .card-action-btn svg {
768
- width: 18px;
769
- height: 18px;
770
- }
771
-
772
- /* ============================================
773
- 状态徽章
774
- ============================================ */
775
- .pill {
776
- padding: 5px 11px;
777
- border-radius: var(--radius-full);
778
- font-size: var(--font-size-caption);
779
- font-weight: var(--font-weight-caption);
780
- background-color: rgba(255, 255, 255, 0.8);
781
- color: var(--color-text-tertiary);
782
- text-transform: uppercase;
783
- letter-spacing: 0.06em;
784
- transition: all var(--transition-fast) var(--ease-smooth);
785
- box-shadow: inset 0 0.5px 1px rgba(0, 0, 0, 0.04);
786
- }
787
-
788
- .pill.configured {
789
- background: linear-gradient(135deg, rgba(90, 139, 106, 0.15) 0%, rgba(90, 139, 106, 0.08) 100%);
790
- color: var(--color-success);
791
- box-shadow: inset 0 0.5px 1px rgba(90, 139, 106, 0.2);
792
- }
793
-
794
- .pill.empty {
795
- background: linear-gradient(135deg, rgba(200, 74, 58, 0.1) 0%, rgba(200, 74, 58, 0.05) 100%);
796
- color: var(--color-error);
797
- box-shadow: inset 0 0.5px 1px rgba(200, 74, 58, 0.15);
798
- }
799
-
800
- .latency {
801
- padding: 4px 10px;
802
- border-radius: var(--radius-full);
803
- font-size: var(--font-size-caption);
804
- font-weight: var(--font-weight-caption);
805
- background: var(--color-bg);
806
- color: var(--color-text-tertiary);
807
- letter-spacing: 0.02em;
808
- }
809
-
810
- .latency.ok {
811
- color: var(--color-success);
812
- background: rgba(90, 139, 106, 0.1);
813
- }
814
-
815
- .latency.error {
816
- color: var(--color-error);
817
- background: rgba(200, 74, 58, 0.08);
818
- }
819
-
820
- .card-action-btn.loading svg {
821
- animation: spin 0.9s linear infinite;
822
- }
823
-
824
- /* ============================================
825
- 图标 - SVG 优化
826
- ============================================ */
827
- .icon {
828
- width: 20px;
829
- height: 20px;
830
- flex-shrink: 0;
831
- stroke-linecap: round;
832
- stroke-linejoin: round;
833
- }
834
-
835
- .icon-chevron-right {
836
- color: var(--color-text-tertiary);
837
- opacity: 0.5;
838
- }
839
-
840
- /* ============================================
841
- 选择器 - 用于模型选择
842
- ============================================ */
843
- .selector-section {
844
- background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.92) 100%);
845
- border-radius: var(--radius-lg);
846
- padding: calc(var(--spacing-sm) + 2px);
847
- margin-bottom: 16px;
848
- box-shadow: var(--shadow-card);
849
- border: 1px solid var(--color-border-soft);
850
- }
851
-
852
- .selector-header {
853
- display: flex;
854
- justify-content: space-between;
855
- align-items: center;
856
- margin-bottom: var(--spacing-xs);
857
- }
858
-
859
- .selector-title {
860
- font-size: var(--font-size-caption);
861
- font-weight: var(--font-weight-secondary);
862
- color: var(--color-text-muted);
863
- text-transform: none;
864
- letter-spacing: 0.04em;
865
- opacity: 0.85;
866
- }
867
-
868
- .selector-actions {
869
- display: flex;
870
- gap: var(--spacing-xs);
871
- }
872
-
873
- .health-report {
874
- margin-top: 10px;
875
- padding: 10px 12px;
876
- border-radius: var(--radius-md);
877
- border: 1px solid var(--color-border-soft);
878
- background: var(--color-surface-alt);
879
- display: grid;
880
- gap: 8px;
881
- }
882
-
883
- .health-remote-toggle {
884
- display: inline-flex;
885
- align-items: center;
886
- gap: 8px;
887
- font-size: var(--font-size-caption);
888
- color: var(--color-text-secondary);
889
- }
890
-
891
- .health-remote-toggle input {
892
- accent-color: var(--color-brand);
893
- }
894
-
895
- .health-ok {
896
- color: var(--color-success);
897
- font-weight: var(--font-weight-secondary);
898
- }
899
-
900
- .health-issue {
901
- background: #fff6f5;
902
- border-left: 3px solid var(--color-error);
903
- padding: 8px 10px;
904
- border-radius: 10px;
905
- }
906
-
907
- .health-issue-title {
908
- font-size: var(--font-size-caption);
909
- font-weight: var(--font-weight-secondary);
910
- color: var(--color-text-primary);
911
- margin-bottom: 4px;
912
- }
913
-
914
- .health-issue-suggestion {
915
- font-size: var(--font-size-caption);
916
- color: var(--color-text-secondary);
917
- line-height: 1.4;
918
- }
919
-
920
- .btn-icon {
921
- width: 28px;
922
- height: 28px;
923
- border-radius: var(--radius-sm);
924
- border: none;
925
- background: linear-gradient(135deg, var(--color-brand) 0%, var(--color-brand-dark) 100%);
926
- color: white;
927
- cursor: pointer;
928
- font-size: 16px;
929
- display: flex;
930
- align-items: center;
931
- justify-content: center;
932
- transition: all var(--transition-fast) var(--ease-spring);
933
- box-shadow: 0 2px 4px rgba(210, 107, 90, 0.2);
934
- }
935
-
936
- .btn-icon:hover {
937
- transform: translateY(-1px) scale(1.05);
938
- box-shadow: 0 4px 8px rgba(210, 107, 90, 0.25);
939
- }
940
-
941
- .btn-icon:active {
942
- transform: translateY(0) scale(0.98);
943
- }
944
-
945
- .model-select {
946
- width: 100%;
947
- padding: 12px var(--spacing-sm);
948
- padding-right: 40px;
949
- border: 1px solid var(--color-border-soft);
950
- border-radius: var(--radius-sm);
951
- font-size: var(--font-size-body);
952
- font-weight: var(--font-weight-body);
953
- background-color: var(--color-surface-alt);
954
- color: var(--color-text-primary);
955
- outline: none;
956
- cursor: pointer;
957
- appearance: none;
958
- 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");
959
- background-repeat: no-repeat;
960
- background-position: right 14px center;
961
- background-size: 12px;
962
- transition: all var(--transition-fast) var(--ease-smooth);
963
- box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.04);
964
- }
965
-
966
- .model-select:hover {
967
- border-color: var(--color-border-strong);
968
- background-color: var(--color-surface);
969
- }
970
-
971
- .model-select:focus {
972
- background-color: var(--color-surface);
973
- border-color: var(--color-brand);
974
- box-shadow: var(--shadow-input-focus);
975
- }
976
-
977
- .model-input {
978
- width: 100%;
979
- padding: 12px var(--spacing-sm);
980
- border: 1px solid var(--color-border-soft);
981
- border-radius: var(--radius-sm);
982
- font-size: var(--font-size-body);
983
- font-weight: var(--font-weight-body);
984
- background-color: var(--color-surface-alt);
985
- color: var(--color-text-primary);
986
- outline: none;
987
- transition: all var(--transition-fast) var(--ease-smooth);
988
- box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.04);
989
- }
990
-
991
- .model-input:hover {
992
- border-color: var(--color-border-strong);
993
- background-color: var(--color-surface);
994
- }
995
-
996
- .model-input:focus {
997
- background-color: var(--color-surface);
998
- border-color: var(--color-brand);
999
- box-shadow: var(--shadow-input-focus);
1000
- }
1001
-
1002
- .config-template-hint {
1003
- margin-top: 8px;
1004
- margin-bottom: 10px;
1005
- font-size: var(--font-size-caption);
1006
- color: var(--color-text-tertiary);
1007
- line-height: 1.4;
1008
- }
1009
-
1010
- .btn-template-editor {
1011
- width: 100%;
1012
- margin-top: 2px;
1013
- }
1014
-
1015
- /* ============================================
1016
- 按钮
1017
- ============================================ */
1018
- .btn-add {
1019
- width: 100%;
1020
- padding: 14px var(--spacing-sm);
1021
- border: 1.5px dashed rgba(208, 196, 182, 0.6);
1022
- border-radius: var(--radius-lg);
1023
- background: linear-gradient(to bottom, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0.15) 100%);
1024
- font-size: var(--font-size-body);
1025
- font-weight: var(--font-weight-secondary);
1026
- color: var(--color-text-tertiary);
1027
- cursor: pointer;
1028
- transition: all var(--transition-normal) var(--ease-spring);
1029
- display: flex;
1030
- align-items: center;
1031
- justify-content: center;
1032
- gap: var(--spacing-xs);
1033
- }
1034
-
1035
- .btn-add + .selector-section,
1036
- .selector-section + .btn-add,
1037
- .btn-add + .card-list,
1038
- .card-list + .btn-add {
1039
- margin-top: 12px;
1040
- }
1041
-
1042
- .btn-add:hover {
1043
- border-color: var(--color-brand);
1044
- color: var(--color-brand);
1045
- background: linear-gradient(to bottom, rgba(210, 107, 90, 0.05) 0%, rgba(210, 107, 90, 0.02) 100%);
1046
- transform: translateY(-1px);
1047
- }
1048
-
1049
- .btn-add:active {
1050
- transform: translateY(0) scale(0.99);
1051
- }
1052
-
1053
- .btn-add .icon {
1054
- width: 18px;
1055
- height: 18px;
1056
- transition: transform var(--transition-normal) var(--ease-spring);
1057
- }
1058
-
1059
- .btn-add:hover .icon {
1060
- transform: rotate(90deg);
1061
- }
1062
-
1063
- .btn-tool {
1064
- padding: 12px var(--spacing-sm);
1065
- border-radius: var(--radius-sm);
1066
- border: 1px solid var(--color-border-soft);
1067
- background: linear-gradient(to bottom, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.85) 100%);
1068
- font-size: var(--font-size-body);
1069
- font-weight: var(--font-weight-secondary);
1070
- color: var(--color-text-secondary);
1071
- cursor: pointer;
1072
- transition: all var(--transition-fast) var(--ease-spring);
1073
- box-shadow: var(--shadow-subtle);
1074
- letter-spacing: -0.01em;
1075
- }
1076
-
1077
- .btn-tool:hover {
1078
- border-color: var(--color-brand);
1079
- color: var(--color-brand);
1080
- transform: translateY(-1px);
1081
- box-shadow: 0 4px 8px rgba(210, 107, 90, 0.12);
1082
- }
1083
-
1084
- .session-toolbar {
1085
- display: grid;
1086
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
1087
- gap: var(--spacing-xs);
1088
- margin-bottom: var(--spacing-sm);
1089
- align-items: end;
1090
- }
1091
-
1092
- .session-toolbar-group {
1093
- display: flex;
1094
- align-items: center;
1095
- gap: var(--spacing-xs);
1096
- flex-wrap: wrap;
1097
- min-width: 0;
1098
- }
1099
-
1100
- .session-toolbar-grow {
1101
- grid-column: span 2;
1102
- }
1103
-
1104
- .session-toolbar-actions {
1105
- justify-content: flex-end;
1106
- }
1107
-
1108
- .session-toolbar-footer {
1109
- display: flex;
1110
- align-items: center;
1111
- justify-content: flex-end;
1112
- gap: var(--spacing-xs);
1113
- margin-top: -2px;
1114
- padding-top: 6px;
1115
- margin-bottom: 12px;
1116
- border-top: 1px dashed var(--color-border-soft);
1117
- }
1118
-
1119
- .session-toolbar-footer .quick-option {
1120
- margin: 0;
1121
- padding: 6px 10px;
1122
- border-radius: var(--radius-sm);
1123
- border: 1px solid var(--color-border-soft);
1124
- background: linear-gradient(to bottom, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.8) 100%);
1125
- box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.04);
1126
- transition: all var(--transition-fast) var(--ease-spring);
1127
- line-height: 1.2;
1128
- }
1129
-
1130
- .session-toolbar-footer .quick-option:hover {
1131
- border-color: var(--color-border-strong);
1132
- }
1133
-
1134
- .session-source-select,
1135
- .session-path-select,
1136
- .session-query-input,
1137
- .session-role-select,
1138
- .session-time-select {
1139
- flex: 1;
1140
- min-width: 160px;
1141
- padding: 10px 12px;
1142
- border-radius: var(--radius-sm);
1143
- border: 1px solid var(--color-border-soft);
1144
- background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.92) 100%);
1145
- color: var(--color-text-secondary);
1146
- font-size: var(--font-size-body);
1147
- font-family: var(--font-family);
1148
- outline: none;
1149
- transition: all var(--transition-fast) var(--ease-spring);
1150
- box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.04);
1151
- }
1152
-
1153
- .session-query-input {
1154
- flex: 2;
1155
- min-width: 220px;
1156
- }
1157
-
1158
- .session-source-select:hover,
1159
- .session-path-select:hover,
1160
- .session-query-input:hover,
1161
- .session-role-select:hover,
1162
- .session-time-select:hover {
1163
- border-color: var(--color-border-strong);
1164
- }
1165
-
1166
- .session-source-select:focus,
1167
- .session-path-select:focus,
1168
- .session-query-input:focus,
1169
- .session-role-select:focus,
1170
- .session-time-select:focus {
1171
- border-color: var(--color-brand);
1172
- box-shadow: var(--shadow-input-focus);
1173
- }
1174
-
1175
- .session-hint {
1176
- font-size: var(--font-size-secondary);
1177
- color: var(--color-text-tertiary);
1178
- margin-bottom: 12px;
1179
- line-height: 1.45;
1180
- }
1181
-
1182
- .session-card {
1183
- align-items: flex-start;
1184
- cursor: default;
1185
- }
1186
-
1187
- .session-card:hover {
1188
- transform: none;
1189
- box-shadow: var(--shadow-card);
1190
- }
1191
-
1192
- .session-card .card-leading {
1193
- align-items: flex-start;
1194
- }
1195
-
1196
- .session-meta {
1197
- margin-top: 6px;
1198
- font-size: var(--font-size-caption);
1199
- color: var(--color-text-tertiary);
1200
- line-height: 1.4;
1201
- word-break: break-all;
1202
- }
1203
-
1204
- .session-actions {
1205
- display: flex;
1206
- gap: var(--spacing-xs);
1207
- align-items: center;
1208
- margin-left: var(--spacing-sm);
1209
- flex-shrink: 0;
1210
- }
1211
-
1212
- .session-source {
1213
- font-size: var(--font-size-caption);
1214
- color: var(--color-brand);
1215
- border: 1px solid rgba(210, 107, 90, 0.25);
1216
- border-radius: 999px;
1217
- padding: 2px 8px;
1218
- background: rgba(210, 107, 90, 0.08);
1219
- white-space: nowrap;
1220
- }
1221
-
1222
- .session-count-pill {
1223
- display: inline-flex;
1224
- align-items: center;
1225
- justify-content: center;
1226
- font-size: var(--font-size-caption);
1227
- color: var(--color-text-secondary);
1228
- border: 1px solid var(--color-border-soft);
1229
- border-radius: 999px;
1230
- padding: 0 8px;
1231
- height: 22px;
1232
- line-height: 1;
1233
- background: rgba(247, 241, 232, 0.7);
1234
- white-space: nowrap;
1235
- margin-left: auto;
1236
- }
1237
-
1238
- .btn-session-export,
1239
- .btn-session-open,
1240
- .btn-session-clone,
1241
- .btn-session-refresh {
1242
- border: 1px solid var(--color-border-soft);
1243
- border-radius: var(--radius-sm);
1244
- background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.9) 100%);
1245
- color: var(--color-text-secondary);
1246
- padding: 8px 12px;
1247
- font-size: var(--font-size-secondary);
1248
- font-weight: var(--font-weight-secondary);
1249
- cursor: pointer;
1250
- transition: all var(--transition-fast) var(--ease-spring);
1251
- white-space: nowrap;
1252
- box-shadow: var(--shadow-subtle);
1253
- letter-spacing: -0.01em;
1254
- }
1255
-
1256
- .btn-session-delete {
1257
- border: 1px solid rgba(189, 70, 68, 0.45);
1258
- border-radius: var(--radius-sm);
1259
- background: linear-gradient(to bottom, rgba(255, 245, 245, 0.95) 0%, rgba(255, 255, 255, 0.9) 100%);
1260
- color: #b74545;
1261
- padding: 8px 12px;
1262
- font-size: var(--font-size-secondary);
1263
- font-weight: var(--font-weight-secondary);
1264
- cursor: pointer;
1265
- transition: all var(--transition-fast) var(--ease-spring);
1266
- white-space: nowrap;
1267
- box-shadow: var(--shadow-subtle);
1268
- letter-spacing: -0.01em;
1269
- }
1270
-
1271
- .btn-session-refresh:hover {
1272
- border-color: var(--color-brand);
1273
- color: var(--color-brand);
1274
- transform: translateY(-1px);
1275
- }
1276
-
1277
- .btn-session-refresh:disabled {
1278
- opacity: 0.5;
1279
- cursor: not-allowed;
1280
- transform: none;
1281
- }
1282
-
1283
- .btn-session-export:hover,
1284
- .btn-session-open:hover {
1285
- border-color: var(--color-brand);
1286
- color: var(--color-brand);
1287
- transform: translateY(-1px);
1288
- }
1289
-
1290
- .btn-session-export:disabled,
1291
- .btn-session-open:disabled {
1292
- opacity: 0.5;
1293
- cursor: not-allowed;
1294
- transform: none;
1295
- }
1296
-
1297
- .btn-session-clone:hover {
1298
- border-color: var(--color-brand);
1299
- color: var(--color-brand);
1300
- transform: translateY(-1px);
1301
- }
1302
-
1303
- .btn-session-clone:disabled {
1304
- opacity: 0.5;
1305
- cursor: not-allowed;
1306
- transform: none;
1307
- }
1308
-
1309
- .btn-session-delete:hover {
1310
- border-color: rgba(189, 70, 68, 0.8);
1311
- color: #9f3b3b;
1312
- transform: translateY(-1px);
1313
- }
1314
-
1315
- .btn-session-delete:disabled {
1316
- opacity: 0.5;
1317
- cursor: not-allowed;
1318
- transform: none;
1319
- }
1320
-
1321
- .session-empty {
1322
- padding: 28px var(--spacing-sm);
1323
- text-align: center;
1324
- border: 1px dashed var(--color-border-soft);
1325
- border-radius: var(--radius-lg);
1326
- color: var(--color-text-tertiary);
1327
- background: var(--bg-warm-gradient);
1328
- position: relative;
1329
- box-shadow: var(--shadow-subtle);
1330
- }
1331
-
1332
- .session-empty::before {
1333
- content: "";
1334
- display: block;
1335
- width: 36px;
1336
- height: 36px;
1337
- border-radius: 50%;
1338
- margin: 0 auto 10px;
1339
- background: rgba(210, 107, 90, 0.12);
1340
- box-shadow: inset 0 0 0 6px rgba(255, 255, 255, 0.7);
1341
- }
1342
-
1343
- .session-layout {
1344
- display: grid;
1345
- grid-template-columns: minmax(215px, 295px) minmax(0, 1fr);
1346
- gap: var(--spacing-sm);
1347
- align-items: start;
1348
- height: min(72vh, 760px);
1349
- min-height: 520px;
1350
- }
1351
-
1352
- .session-layout.session-standalone {
1353
- grid-template-columns: minmax(0, 1fr);
1354
- }
1355
-
1356
- .session-standalone-page {
1357
- max-width: 960px;
1358
- margin: 0 auto;
1359
- padding: var(--spacing-sm) 0;
1360
- }
1361
-
1362
- .session-standalone-title {
1363
- font-size: var(--font-size-title);
1364
- font-weight: var(--font-weight-title);
1365
- color: var(--color-text-primary);
1366
- margin-bottom: var(--spacing-sm);
1367
- letter-spacing: -0.01em;
1368
- }
1369
-
1370
- .session-standalone-text {
1371
- white-space: pre-wrap;
1372
- font-family: var(--font-family-body);
1373
- font-size: var(--font-size-body);
1374
- line-height: 1.7;
1375
- color: var(--color-text-primary);
1376
- word-break: break-word;
1377
- }
1378
-
1379
- .session-list {
1380
- display: flex;
1381
- flex-direction: column;
1382
- gap: var(--spacing-xs);
1383
- position: sticky;
1384
- top: 12px;
1385
- height: 100%;
1386
- max-height: none;
1387
- overflow-y: auto;
1388
- overflow-x: hidden;
1389
- padding-right: 4px;
1390
- min-width: 0;
1391
- scrollbar-width: thin;
1392
- scrollbar-color: rgba(166, 149, 130, 0.85) transparent;
1393
- }
1394
-
1395
- .session-list::-webkit-scrollbar,
1396
- .session-preview-scroll::-webkit-scrollbar {
1397
- width: 10px;
1398
- height: 10px;
1399
- }
1400
-
1401
- .session-list::-webkit-scrollbar-track,
1402
- .session-preview-scroll::-webkit-scrollbar-track {
1403
- background: transparent;
1404
- border-radius: 999px;
1405
- }
1406
-
1407
- .session-list::-webkit-scrollbar-thumb,
1408
- .session-preview-scroll::-webkit-scrollbar-thumb {
1409
- background: linear-gradient(to bottom, rgba(191, 174, 154, 0.95) 0%, rgba(160, 141, 121, 0.95) 100%);
1410
- border-radius: 999px;
1411
- border: 2px solid rgba(255, 255, 255, 0.9);
1412
- }
1413
-
1414
- .session-list::-webkit-scrollbar-thumb:hover,
1415
- .session-preview-scroll::-webkit-scrollbar-thumb:hover {
1416
- background: linear-gradient(to bottom, rgba(175, 156, 136, 0.95) 0%, rgba(145, 126, 107, 0.95) 100%);
1417
- }
1418
-
1419
- .session-item {
1420
- border: 1px solid var(--color-border-soft);
1421
- border-radius: var(--radius-sm);
1422
- background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.92) 100%);
1423
- padding: 14px 16px;
1424
- cursor: pointer;
1425
- transition: all var(--transition-fast) var(--ease-spring);
1426
- user-select: none;
1427
- min-width: 0;
1428
- position: relative;
1429
- overflow: hidden;
1430
- min-height: 88px;
1431
- }
1432
-
1433
- .session-item-header {
1434
- display: flex;
1435
- align-items: flex-start;
1436
- justify-content: space-between;
1437
- gap: 10px;
1438
- margin-bottom: 4px;
1439
- }
1440
-
1441
- .session-item-main {
1442
- min-width: 0;
1443
- flex: 1;
1444
- display: flex;
1445
- align-items: flex-start;
1446
- gap: 8px;
1447
- }
1448
-
1449
- .session-item:hover {
1450
- border-color: var(--color-brand);
1451
- background: linear-gradient(to bottom, rgba(210, 107, 90, 0.08) 0%, rgba(255, 255, 255, 0.98) 100%);
1452
- transform: translateY(-1px);
1453
- }
1454
-
1455
- .session-item::before {
1456
- content: "";
1457
- position: absolute;
1458
- left: 0;
1459
- top: 10px;
1460
- bottom: 10px;
1461
- width: 3px;
1462
- border-radius: 999px;
1463
- background: rgba(210, 107, 90, 0.15);
1464
- transition: background var(--transition-fast) var(--ease-spring);
1465
- }
1466
-
1467
- .session-item:active {
1468
- transform: scale(0.99);
1469
- }
1470
-
1471
- .session-item.active {
1472
- border-color: var(--color-brand);
1473
- background: linear-gradient(to bottom, rgba(210, 107, 90, 0.1) 0%, rgba(255, 255, 255, 0.98) 100%);
1474
- box-shadow: 0 6px 16px rgba(210, 107, 90, 0.12);
1475
- }
1476
-
1477
- .session-item.active::before {
1478
- background: linear-gradient(180deg, rgba(201, 94, 75, 0.9), rgba(201, 94, 75, 0.4));
1479
- }
1480
-
1481
- .session-item-title {
1482
- font-size: var(--font-size-body);
1483
- font-weight: var(--font-weight-secondary);
1484
- color: var(--color-text-primary);
1485
- line-height: 1.5;
1486
- display: block;
1487
- white-space: nowrap;
1488
- overflow: hidden;
1489
- text-overflow: ellipsis;
1490
- flex: 1;
1491
- max-width: 75%;
1492
- }
1493
-
1494
- .session-item-actions {
1495
- display: inline-flex;
1496
- align-items: center;
1497
- gap: 6px;
1498
- flex-shrink: 0;
1499
- }
1500
-
1501
- .session-item-copy {
1502
- border: 1px solid rgba(70, 86, 110, 0.35);
1503
- background: rgba(70, 86, 110, 0.08);
1504
- color: var(--color-text-secondary);
1505
- width: 28px;
1506
- height: 28px;
1507
- border-radius: 8px;
1508
- display: inline-flex;
1509
- align-items: center;
1510
- justify-content: center;
1511
- cursor: pointer;
1512
- flex-shrink: 0;
1513
- transition: all var(--transition-fast) var(--ease-spring);
1514
- }
1515
-
1516
- .session-item-copy:hover {
1517
- border-color: rgba(70, 86, 110, 0.7);
1518
- background: rgba(70, 86, 110, 0.16);
1519
- color: var(--color-text-primary);
1520
- transform: translateY(-1px);
1521
- }
1522
-
1523
- .session-item-copy:disabled {
1524
- opacity: 0.5;
1525
- cursor: not-allowed;
1526
- transform: none;
1527
- }
1528
-
1529
- .session-item-copy svg {
1530
- width: 16px;
1531
- height: 16px;
1532
- }
1533
-
1534
- .session-item-sub {
1535
- font-size: var(--font-size-caption);
1536
- color: var(--color-text-tertiary);
1537
- line-height: 1.35;
1538
- white-space: nowrap;
1539
- overflow: hidden;
1540
- text-overflow: ellipsis;
1541
- }
1542
-
1543
- .session-item-sub.session-item-wrap {
1544
- white-space: normal;
1545
- }
1546
-
1547
- .session-item-meta {
1548
- display: flex;
1549
- flex-wrap: wrap;
1550
- align-items: center;
1551
- gap: 6px;
1552
- margin-top: 2px;
1553
- margin-bottom: 2px;
1554
- }
1555
-
1556
- .session-item-time {
1557
- font-size: var(--font-size-caption);
1558
- color: var(--color-text-tertiary);
1559
- white-space: nowrap;
1560
- }
1561
-
1562
- .session-item-snippet {
1563
- color: var(--color-text-secondary);
1564
- white-space: nowrap;
1565
- overflow: hidden;
1566
- text-overflow: ellipsis;
1567
- display: block;
1568
- max-width: 100%;
1569
- background: rgba(210, 107, 90, 0.08);
1570
- border-radius: 8px;
1571
- padding: 6px 8px;
1572
- border: 1px solid rgba(210, 107, 90, 0.15);
1573
- }
1574
-
1575
- .session-preview {
1576
- border: 1px solid var(--color-border-soft);
1577
- border-radius: var(--radius-xl);
1578
- background: linear-gradient(to bottom, var(--color-surface-elevated) 0%, rgba(255, 255, 255, 0.96) 100%);
1579
- box-shadow: var(--shadow-card);
1580
- min-height: 0;
1581
- max-height: none;
1582
- height: 100%;
1583
- display: flex;
1584
- flex-direction: column;
1585
- overflow: hidden;
1586
- transform: translateX(4px);
1587
- transition: transform var(--transition-normal) var(--ease-spring-soft), box-shadow var(--transition-normal) var(--ease-spring-soft);
1588
- }
1589
-
1590
- .session-preview.active {
1591
- box-shadow: var(--shadow-float);
1592
- transform: translateX(0);
1593
- }
1594
-
1595
- .session-preview-scroll {
1596
- flex: 1;
1597
- min-height: 0;
1598
- overflow-y: auto;
1599
- overflow-x: hidden;
1600
- display: flex;
1601
- flex-direction: column;
1602
- scrollbar-width: thin;
1603
- scrollbar-color: rgba(166, 149, 130, 0.85) transparent;
1604
- }
1605
-
1606
- .session-preview-header {
1607
- padding: 12px var(--spacing-sm);
1608
- border-bottom: 1px solid var(--color-border-soft);
1609
- display: flex;
1610
- align-items: flex-start;
1611
- justify-content: space-between;
1612
- gap: var(--spacing-sm);
1613
- position: sticky;
1614
- top: 0;
1615
- z-index: 2;
1616
- background: linear-gradient(to bottom, rgba(255, 255, 255, 0.98) 0%, rgba(255, 255, 255, 0.92) 100%);
1617
- backdrop-filter: blur(6px);
1618
- }
1619
-
1620
- .session-preview-header > div:first-child {
1621
- min-width: 0;
1622
- flex: 1;
1623
- }
1624
-
1625
- .session-preview-title {
1626
- font-size: var(--font-size-body);
1627
- font-weight: var(--font-weight-title);
1628
- color: var(--color-text-primary);
1629
- line-height: 1.4;
1630
- display: -webkit-box;
1631
- -webkit-line-clamp: 2;
1632
- -webkit-box-orient: vertical;
1633
- overflow: hidden;
1634
- word-break: break-word;
1635
- }
1636
-
1637
- .session-preview-sub {
1638
- margin-top: 4px;
1639
- font-size: var(--font-size-caption);
1640
- color: var(--color-text-tertiary);
1641
- line-height: 1.35;
1642
- white-space: nowrap;
1643
- overflow: hidden;
1644
- text-overflow: ellipsis;
1645
- }
1646
-
1647
- .session-preview-meta {
1648
- display: flex;
1649
- flex-wrap: wrap;
1650
- align-items: center;
1651
- margin-top: 4px;
1652
- }
1653
-
1654
- .session-preview-meta-item {
1655
- font-size: var(--font-size-caption);
1656
- color: var(--color-text-tertiary);
1657
- line-height: 1.35;
1658
- white-space: nowrap;
1659
- overflow: hidden;
1660
- text-overflow: ellipsis;
1661
- }
1662
-
1663
- .session-preview-meta-item:not(:last-child)::after {
1664
- content: "·";
1665
- margin: 0 6px;
1666
- color: var(--color-text-tertiary);
1667
- opacity: 0.7;
1668
- }
1669
-
1670
- .session-actions {
1671
- display: flex;
1672
- align-items: center;
1673
- gap: 8px;
1674
- flex: 0 1 auto;
1675
- max-width: 100%;
1676
- margin-left: 0;
1677
- flex-wrap: wrap;
1678
- justify-content: flex-end;
1679
- }
1680
-
1681
- .session-preview-body {
1682
- flex: 1;
1683
- min-height: 0;
1684
- padding: var(--spacing-sm);
1685
- display: flex;
1686
- flex-direction: column;
1687
- gap: 10px;
1688
- }
1689
-
1690
- .session-msg {
1691
- border-radius: 10px;
1692
- padding: 10px 12px 10px 18px;
1693
- border: 1px solid rgba(208, 196, 182, 0.45);
1694
- background: rgba(255, 255, 255, 0.75);
1695
- position: relative;
1696
- box-shadow: 0 2px 6px rgba(31, 26, 23, 0.04);
1697
- }
1698
-
1699
- .session-msg.user {
1700
- border-color: rgba(210, 107, 90, 0.35);
1701
- background: rgba(210, 107, 90, 0.08);
1702
- }
1703
-
1704
- .session-msg::before {
1705
- content: "";
1706
- position: absolute;
1707
- left: 8px;
1708
- top: 10px;
1709
- bottom: 10px;
1710
- width: 3px;
1711
- border-radius: 999px;
1712
- background: rgba(139, 118, 104, 0.45);
1713
- }
1714
-
1715
- .session-msg.user::before {
1716
- background: rgba(210, 107, 90, 0.85);
1717
- }
1718
-
1719
- .session-msg.assistant::before {
1720
- background: rgba(90, 139, 106, 0.6);
1721
- }
1722
-
1723
- .session-msg-header {
1724
- display: flex;
1725
- align-items: flex-start;
1726
- justify-content: space-between;
1727
- gap: var(--spacing-xs);
1728
- margin-bottom: 6px;
1729
- font-size: var(--font-size-caption);
1730
- color: var(--color-text-tertiary);
1731
- }
1732
-
1733
- .session-msg-meta {
1734
- min-width: 0;
1735
- flex: 1;
1736
- display: flex;
1737
- align-items: center;
1738
- gap: 8px;
1739
- }
1740
-
1741
- .session-msg-role {
1742
- font-weight: var(--font-weight-secondary);
1743
- color: var(--color-text-secondary);
1744
- }
1745
-
1746
- .session-msg-time {
1747
- white-space: nowrap;
1748
- overflow: hidden;
1749
- text-overflow: ellipsis;
1750
- color: var(--color-text-tertiary);
1751
- }
1752
-
1753
- .session-msg-content {
1754
- font-size: var(--font-size-secondary);
1755
- line-height: 1.55;
1756
- color: var(--color-text-primary);
1757
- white-space: pre-wrap;
1758
- word-break: break-word;
1759
- }
1760
-
1761
- .session-preview-empty {
1762
- flex: 1;
1763
- display: flex;
1764
- align-items: center;
1765
- justify-content: center;
1766
- color: var(--color-text-tertiary);
1767
- font-size: var(--font-size-secondary);
1768
- padding: var(--spacing-md);
1769
- text-align: center;
1770
- flex-direction: column;
1771
- gap: 8px;
1772
- }
1773
-
1774
- .session-preview-empty::before {
1775
- content: "";
1776
- width: 34px;
1777
- height: 34px;
1778
- border-radius: 50%;
1779
- background: rgba(210, 107, 90, 0.12);
1780
- box-shadow: inset 0 0 0 6px rgba(255, 255, 255, 0.7);
1781
- }
1782
-
1783
- @media (max-width: 1100px) {
1784
- .session-layout {
1785
- grid-template-columns: 1fr;
1786
- height: auto;
1787
- min-height: 0;
1788
- }
1789
-
1790
- .session-toolbar {
1791
- grid-template-columns: 1fr;
1792
- }
1793
-
1794
- .session-toolbar-actions {
1795
- justify-content: flex-start;
1796
- }
1797
-
1798
- .session-toolbar-footer {
1799
- justify-content: flex-start;
1800
- }
1801
-
1802
- .session-list {
1803
- position: static;
1804
- max-height: 300px;
1805
- height: auto;
1806
- }
1807
-
1808
- .session-preview {
1809
- min-height: 360px;
1810
- max-height: none;
1811
- height: auto;
1812
- position: relative;
1813
- transform: none;
1814
- box-shadow: var(--shadow-card);
1815
- }
1816
-
1817
- .session-preview-header {
1818
- flex-direction: column;
1819
- align-items: stretch;
1820
- }
1821
-
1822
- .session-actions {
1823
- justify-content: flex-start;
1824
- }
1825
-
1826
- .session-preview.active {
1827
- box-shadow: var(--shadow-float);
1828
- }
1829
- }
1830
-
1831
- @media (max-width: 520px) {
1832
- .session-item-header {
1833
- flex-direction: column;
1834
- align-items: stretch;
1835
- }
1836
-
1837
- .session-item-actions {
1838
- justify-content: flex-end;
1839
- }
1840
-
1841
- .session-actions {
1842
- width: 100%;
1843
- flex-direction: column;
1844
- align-items: stretch;
1845
- }
1846
-
1847
- .btn-session-refresh,
1848
- .btn-session-export {
1849
- width: 100%;
1850
- }
1851
-
1852
- .session-toolbar-group.session-toolbar-actions {
1853
- flex-direction: column;
1854
- align-items: stretch;
1855
- }
1856
-
1857
- .session-toolbar-group.session-toolbar-actions .btn-tool {
1858
- width: 100%;
1859
- }
1860
- }
1861
-
1862
- .btn[disabled] {
1863
- opacity: 0.5;
1864
- cursor: not-allowed;
1865
- transform: none;
1866
- box-shadow: none;
1867
- }
1868
-
1869
- /* ============================================
1870
- 模态框
1871
- ============================================ */
1872
- .modal-overlay {
1873
- position: fixed;
1874
- top: 0;
1875
- left: 0;
1876
- right: 0;
1877
- bottom: 0;
1878
- background: linear-gradient(to bottom, rgba(31, 26, 23, 0.3) 0%, rgba(31, 26, 23, 0.5) 100%);
1879
- display: flex;
1880
- justify-content: center;
1881
- align-items: center;
1882
- z-index: 100;
1883
- backdrop-filter: blur(8px) saturate(180%);
1884
- -webkit-backdrop-filter: blur(8px) saturate(180%);
1885
- animation: fadeIn var(--transition-normal) var(--ease-out-expo);
1886
- }
1887
-
1888
- .modal {
1889
- background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.98) 100%);
1890
- width: 90%;
1891
- max-width: 400px;
1892
- max-height: 90vh;
1893
- overflow-y: auto;
1894
- overscroll-behavior: contain;
1895
- border-radius: var(--radius-lg);
1896
- padding: var(--spacing-md);
1897
- box-shadow: var(--shadow-modal);
1898
- border: 1px solid rgba(255, 255, 255, 0.8);
1899
- animation: slideUp var(--transition-slow) var(--ease-spring);
1900
- }
1901
-
1902
- .modal-wide {
1903
- max-width: 980px;
1904
- }
1905
-
1906
- .modal-title {
1907
- font-size: var(--font-size-title);
1908
- font-weight: var(--font-weight-title);
1909
- margin-bottom: var(--spacing-md);
1910
- color: var(--color-text-primary);
1911
- letter-spacing: -0.01em;
1912
- }
1913
-
1914
- .modal-header {
1915
- display: flex;
1916
- align-items: center;
1917
- justify-content: space-between;
1918
- gap: var(--spacing-sm);
1919
- margin-bottom: var(--spacing-md);
1920
- flex-wrap: wrap;
1921
- }
1922
-
1923
- .modal-header .modal-title {
1924
- margin-bottom: 0;
1925
- }
1926
-
1927
- .btn-modal-copy {
1928
- padding: 6px 12px;
1929
- white-space: nowrap;
1930
- flex-shrink: 0;
1931
- }
1932
-
1933
- .form-group {
1934
- margin-bottom: var(--spacing-sm);
1935
- }
1936
-
1937
- .form-label {
1938
- display: block;
1939
- font-size: var(--font-size-secondary);
1940
- font-weight: var(--font-weight-secondary);
1941
- color: var(--color-text-secondary);
1942
- margin-bottom: 7px;
1943
- letter-spacing: 0.01em;
1944
- }
1945
-
1946
- .form-input {
1947
- width: 100%;
1948
- padding: 13px var(--spacing-sm);
1949
- border: 1.5px solid var(--color-border-soft);
1950
- border-radius: var(--radius-sm);
1951
- font-size: var(--font-size-body);
1952
- background-color: var(--color-surface-alt);
1953
- color: var(--color-text-primary);
1954
- outline: none;
1955
- transition: all var(--transition-fast) var(--ease-spring);
1956
- font-family: var(--font-family-body);
1957
- box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.04);
1958
- }
1959
-
1960
- .form-input:hover {
1961
- border-color: var(--color-border-strong);
1962
- }
1963
-
1964
- .form-input:focus {
1965
- border-color: var(--color-brand);
1966
- background-color: var(--color-surface);
1967
- box-shadow: var(--shadow-input-focus);
1968
- }
1969
-
1970
- .form-input::placeholder {
1971
- color: var(--color-text-tertiary);
1972
- opacity: 0.7;
1973
- }
1974
-
1975
- .template-editor {
1976
- min-height: min(60vh, 520px);
1977
- max-height: min(65vh, 620px);
1978
- resize: vertical;
1979
- overflow: auto;
1980
- white-space: pre;
1981
- font-family: var(--font-family-mono);
1982
- font-size: 13px;
1983
- line-height: 1.45;
1984
- }
1985
-
1986
- .template-editor-warning {
1987
- margin-top: 8px;
1988
- color: #8d5b31;
1989
- font-size: var(--font-size-caption);
1990
- line-height: 1.4;
1991
- }
1992
-
1993
- .form-input:disabled,
1994
- .form-input[readonly] {
1995
- background: linear-gradient(to right, var(--color-bg) 0%, rgba(247, 241, 232, 0.5) 100%);
1996
- color: var(--color-text-tertiary);
1997
- cursor: not-allowed;
1998
- border-color: transparent;
1999
- }
2000
-
2001
- .form-hint {
2002
- font-size: var(--font-size-caption);
2003
- color: var(--color-text-tertiary);
2004
- margin-top: 5px;
2005
- opacity: 0.8;
2006
- }
2007
-
2008
- .quick-section {
2009
- margin-top: var(--spacing-md);
2010
- padding: var(--spacing-sm);
2011
- border-radius: var(--radius-lg);
2012
- border: 1px solid var(--color-border-soft);
2013
- background: linear-gradient(140deg, rgba(255, 252, 247, 0.95), rgba(255, 255, 255, 0.6));
2014
- }
2015
-
2016
- .quick-header {
2017
- display: flex;
2018
- flex-wrap: wrap;
2019
- gap: var(--spacing-xs);
2020
- align-items: flex-start;
2021
- justify-content: space-between;
2022
- margin-bottom: var(--spacing-sm);
2023
- }
2024
-
2025
- .quick-title {
2026
- font-size: var(--font-size-secondary);
2027
- font-weight: var(--font-weight-secondary);
2028
- color: var(--color-text-secondary);
2029
- }
2030
-
2031
- .quick-actions {
2032
- display: flex;
2033
- flex-wrap: wrap;
2034
- gap: var(--spacing-xs);
2035
- }
2036
-
2037
- .quick-steps {
2038
- display: flex;
2039
- flex-wrap: wrap;
2040
- gap: var(--spacing-xs);
2041
- margin-bottom: var(--spacing-sm);
2042
- }
2043
-
2044
- .quick-step {
2045
- display: inline-flex;
2046
- align-items: center;
2047
- gap: 6px;
2048
- padding: 4px 10px;
2049
- border-radius: 999px;
2050
- border: 1px dashed var(--color-border-soft);
2051
- background: var(--color-surface);
2052
- font-size: var(--font-size-caption);
2053
- color: var(--color-text-secondary);
2054
- }
2055
-
2056
- .step-badge {
2057
- width: 20px;
2058
- height: 20px;
2059
- border-radius: 999px;
2060
- display: inline-flex;
2061
- align-items: center;
2062
- justify-content: center;
2063
- background: var(--color-brand);
2064
- color: #fff;
2065
- font-size: 12px;
2066
- font-weight: var(--font-weight-secondary);
2067
- }
2068
-
2069
- .quick-grid {
2070
- display: grid;
2071
- gap: var(--spacing-sm);
2072
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
2073
- }
2074
-
2075
- .quick-card {
2076
- background: var(--color-surface);
2077
- border: 1px solid var(--color-border-soft);
2078
- border-radius: var(--radius-sm);
2079
- padding: var(--spacing-sm);
2080
- box-shadow: var(--shadow-subtle);
2081
- }
2082
-
2083
- .quick-option {
2084
- display: flex;
2085
- align-items: center;
2086
- gap: 8px;
2087
- font-size: var(--font-size-caption);
2088
- color: var(--color-text-secondary);
2089
- margin-bottom: 6px;
2090
- }
2091
-
2092
- .quick-option input {
2093
- accent-color: var(--color-brand);
2094
- }
2095
-
2096
- .structured-section {
2097
- margin-top: var(--spacing-md);
2098
- padding: var(--spacing-sm);
2099
- border-radius: var(--radius-lg);
2100
- border: 1px solid var(--color-border-soft);
2101
- background: rgba(255, 255, 255, 0.6);
2102
- }
2103
-
2104
- .structured-header {
2105
- display: flex;
2106
- flex-wrap: wrap;
2107
- gap: var(--spacing-xs);
2108
- align-items: baseline;
2109
- justify-content: space-between;
2110
- margin-bottom: var(--spacing-sm);
2111
- }
2112
-
2113
- .structured-title {
2114
- font-size: var(--font-size-secondary);
2115
- font-weight: var(--font-weight-secondary);
2116
- color: var(--color-text-secondary);
2117
- }
2118
-
2119
- .structured-grid {
2120
- display: grid;
2121
- gap: var(--spacing-sm);
2122
- grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
2123
- }
2124
-
2125
- .structured-card {
2126
- background: var(--color-surface);
2127
- border: 1px solid var(--color-border-soft);
2128
- border-radius: var(--radius-sm);
2129
- padding: var(--spacing-sm);
2130
- box-shadow: var(--shadow-subtle);
2131
- }
2132
-
2133
- .structured-card-title {
2134
- font-size: var(--font-size-body);
2135
- font-weight: var(--font-weight-secondary);
2136
- color: var(--color-text-secondary);
2137
- margin-bottom: 8px;
2138
- }
2139
-
2140
- .provider-list {
2141
- display: flex;
2142
- flex-direction: column;
2143
- gap: var(--spacing-xs);
2144
- }
2145
-
2146
- .provider-item {
2147
- border: 1px dashed var(--color-border-soft);
2148
- border-radius: var(--radius-sm);
2149
- padding: var(--spacing-xs);
2150
- background: var(--color-surface-alt);
2151
- }
2152
-
2153
- .provider-header {
2154
- display: flex;
2155
- flex-wrap: wrap;
2156
- gap: var(--spacing-xs);
2157
- align-items: center;
2158
- margin-bottom: 6px;
2159
- }
2160
-
2161
- .provider-name {
2162
- font-weight: var(--font-weight-secondary);
2163
- color: var(--color-text-secondary);
2164
- }
2165
-
2166
- .provider-source {
2167
- font-size: var(--font-size-caption);
2168
- color: var(--color-text-tertiary);
2169
- }
2170
-
2171
- .provider-fields {
2172
- display: grid;
2173
- gap: 6px;
2174
- }
2175
-
2176
- .provider-field {
2177
- display: flex;
2178
- flex-wrap: wrap;
2179
- gap: 6px;
2180
- align-items: baseline;
2181
- }
2182
-
2183
- .provider-field-key {
2184
- font-family: var(--font-family-mono);
2185
- font-size: var(--font-size-caption);
2186
- color: var(--color-text-muted);
2187
- min-width: 110px;
2188
- }
2189
-
2190
- .provider-field-value {
2191
- font-family: var(--font-family-mono);
2192
- font-size: var(--font-size-caption);
2193
- color: var(--color-text-secondary);
2194
- word-break: break-all;
2195
- }
2196
-
2197
- .agent-list {
2198
- display: flex;
2199
- flex-direction: column;
2200
- gap: var(--spacing-xs);
2201
- }
2202
-
2203
- .agent-item {
2204
- border: 1px dashed var(--color-border-soft);
2205
- border-radius: var(--radius-sm);
2206
- padding: var(--spacing-xs);
2207
- background: var(--color-surface-alt);
2208
- }
2209
-
2210
- .agent-header {
2211
- display: flex;
2212
- flex-wrap: wrap;
2213
- gap: var(--spacing-xs);
2214
- align-items: center;
2215
- margin-bottom: 6px;
2216
- }
2217
-
2218
- .agent-name {
2219
- font-weight: var(--font-weight-secondary);
2220
- color: var(--color-text-secondary);
2221
- }
2222
-
2223
- .agent-id {
2224
- font-size: var(--font-size-caption);
2225
- color: var(--color-text-tertiary);
2226
- }
2227
-
2228
- .agent-meta {
2229
- display: flex;
2230
- flex-wrap: wrap;
2231
- gap: 8px;
2232
- font-size: var(--font-size-caption);
2233
- color: var(--color-text-secondary);
2234
- }
2235
-
2236
- .list-row {
2237
- display: flex;
2238
- flex-wrap: wrap;
2239
- gap: var(--spacing-xs);
2240
- align-items: center;
2241
- margin-bottom: var(--spacing-xs);
2242
- }
2243
-
2244
- .list-row:last-child {
2245
- margin-bottom: 0;
2246
- }
2247
-
2248
- .list-row .form-input {
2249
- flex: 1;
2250
- min-width: 140px;
2251
- }
2252
-
2253
- .btn-mini {
2254
- padding: 6px 10px;
2255
- border-radius: var(--radius-sm);
2256
- border: 1px solid var(--color-border-soft);
2257
- background: linear-gradient(to bottom, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.85) 100%);
2258
- font-size: var(--font-size-caption);
2259
- font-weight: var(--font-weight-secondary);
2260
- color: var(--color-text-secondary);
2261
- cursor: pointer;
2262
- transition: all var(--transition-fast) var(--ease-spring);
2263
- box-shadow: var(--shadow-subtle);
2264
- }
2265
-
2266
- .btn-mini:hover {
2267
- border-color: var(--color-brand);
2268
- color: var(--color-brand);
2269
- transform: translateY(-1px);
2270
- }
2271
-
2272
- .btn-mini.delete {
2273
- color: var(--color-error);
2274
- border-color: rgba(193, 72, 59, 0.35);
2275
- }
2276
-
2277
- .btn-mini.delete:hover {
2278
- border-color: rgba(193, 72, 59, 0.7);
2279
- color: var(--color-error);
2280
- }
2281
-
2282
- .btn-group {
2283
- display: flex;
2284
- gap: var(--spacing-sm);
2285
- margin-top: var(--spacing-md);
2286
- }
2287
-
2288
- .btn {
2289
- flex: 1;
2290
- padding: 14px var(--spacing-sm);
2291
- border-radius: var(--radius-sm);
2292
- font-size: var(--font-size-body);
2293
- font-weight: var(--font-weight-secondary);
2294
- cursor: pointer;
2295
- transition: all var(--transition-fast) var(--ease-spring);
2296
- border: 1px solid var(--color-border-soft);
2297
- background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.95) 100%);
2298
- color: var(--color-text-secondary);
2299
- box-shadow: var(--shadow-subtle);
2300
- letter-spacing: -0.01em;
2301
- }
2302
-
2303
- .btn:active {
2304
- transform: scale(0.97);
2305
- }
2306
-
2307
- .btn-cancel {
2308
- background: linear-gradient(to bottom, var(--color-bg) 0%, rgba(247, 241, 232, 0.8) 100%);
2309
- color: var(--color-text-primary);
2310
- border: 1px solid var(--color-border-soft);
2311
- }
2312
-
2313
- .btn-cancel:hover {
2314
- background: linear-gradient(to bottom, var(--color-border) 0%, rgba(208, 196, 182, 0.5) 100%);
2315
- }
2316
-
2317
- .btn-confirm {
2318
- background: linear-gradient(135deg, var(--color-brand) 0%, var(--color-brand-dark) 100%);
2319
- color: white;
2320
- box-shadow: 0 2px 4px rgba(210, 107, 90, 0.2);
2321
- border: none;
2322
- }
2323
-
2324
- .btn-confirm:hover {
2325
- box-shadow: 0 4px 8px rgba(210, 107, 90, 0.25);
2326
- filter: brightness(1.05);
2327
- }
2328
-
2329
- .btn-confirm.secondary {
2330
- background: linear-gradient(135deg, var(--color-success) 0%, rgba(90, 139, 106, 0.85) 100%);
2331
- box-shadow: 0 2px 4px rgba(90, 139, 106, 0.2);
2332
- border: none;
2333
- }
2334
-
2335
- .btn-confirm.secondary:hover {
2336
- box-shadow: 0 4px 8px rgba(90, 139, 106, 0.25);
2337
- filter: brightness(1.05);
2338
- }
2339
-
2340
- /* ============================================
2341
- 模型列表
2342
- ============================================ */
2343
- .model-list {
2344
- max-height: 200px;
2345
- overflow-y: auto;
2346
- border: 1px solid rgba(208, 196, 182, 0.4);
2347
- border-radius: var(--radius-sm);
2348
- margin-bottom: var(--spacing-sm);
2349
- scrollbar-width: none;
2350
- background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.8) 100%);
2351
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.02);
2352
- }
2353
-
2354
- .model-list::-webkit-scrollbar {
2355
- display: none;
2356
- }
2357
-
2358
- .model-item {
2359
- display: flex;
2360
- justify-content: space-between;
2361
- align-items: center;
2362
- padding: 11px var(--spacing-sm);
2363
- border-bottom: 1px solid rgba(208, 196, 182, 0.3);
2364
- font-size: var(--font-size-body);
2365
- color: var(--color-text-primary);
2366
- transition: all var(--transition-fast) var(--ease-spring);
2367
- letter-spacing: -0.005em;
2368
- }
2369
-
2370
- .model-item:last-child {
2371
- border-bottom: none;
2372
- }
2373
-
2374
- .model-item:hover {
2375
- background: linear-gradient(to right, rgba(247, 241, 232, 0.6) 0%, rgba(247, 241, 232, 0.3) 100%);
2376
- }
2377
-
2378
- .btn-remove-model {
2379
- font-size: var(--font-size-caption);
2380
- font-weight: var(--font-weight-caption);
2381
- color: var(--color-text-tertiary);
2382
- cursor: pointer;
2383
- padding: 5px 10px;
2384
- border-radius: var(--radius-full);
2385
- transition: all var(--transition-fast) var(--ease-spring);
2386
- background: transparent;
2387
- border: 1px solid rgba(139, 118, 104, 0.2);
2388
- letter-spacing: 0.03em;
2389
- }
2390
-
2391
- .btn-remove-model:hover {
2392
- background: linear-gradient(135deg, var(--color-error) 0%, rgba(200, 74, 58, 0.9) 100%);
2393
- color: white;
2394
- transform: scale(1.08);
2395
- box-shadow: 0 2px 6px rgba(200, 74, 58, 0.25);
2396
- border-color: transparent;
2397
- }
2398
-
2399
- /* ============================================
2400
- Toast - 顶部横幅
2401
- ============================================ */
2402
- .toast {
2403
- position: fixed;
2404
- top: 16px;
2405
- left: 50%;
2406
- transform: translateX(-50%);
2407
- background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.95) 100%);
2408
- padding: 12px 24px;
2409
- border-radius: var(--radius-full);
2410
- box-shadow: var(--shadow-raised);
2411
- z-index: 200;
2412
- animation: slideDown var(--transition-slow) var(--ease-spring);
2413
- display: flex;
2414
- align-items: center;
2415
- gap: var(--spacing-xs);
2416
- font-size: var(--font-size-body);
2417
- font-weight: var(--font-weight-body);
2418
- border: 1px solid rgba(255, 255, 255, 0.8);
2419
- backdrop-filter: blur(8px);
2420
- -webkit-backdrop-filter: blur(8px);
2421
- }
2422
-
2423
- .toast.error {
2424
- border-left: 3px solid var(--color-error);
2425
- }
2426
-
2427
- .toast.success {
2428
- border-left: 3px solid var(--color-success);
2429
- }
2430
-
2431
- /* ============================================
2432
- 状态消息
2433
- ============================================ */
2434
- .state-message {
2435
- text-align: center;
2436
- padding: 60px var(--spacing-md);
2437
- color: var(--color-text-tertiary);
2438
- font-size: var(--font-size-body);
2439
- opacity: 0.7;
2440
- letter-spacing: -0.005em;
2441
- }
2442
-
2443
- .state-message.error {
2444
- color: var(--color-error);
2445
- }
2446
-
2447
- /* 空状态 */
2448
- .empty-state {
2449
- text-align: center;
2450
- padding: 48px var(--spacing-md);
2451
- color: var(--color-text-tertiary);
2452
- }
2453
-
2454
- .empty-state-icon {
2455
- width: 48px;
2456
- height: 48px;
2457
- margin: 0 auto var(--spacing-sm);
2458
- opacity: 0.3;
2459
- }
2460
-
2461
- .empty-state-title {
2462
- font-size: var(--font-size-body);
2463
- font-weight: var(--font-weight-secondary);
2464
- color: var(--color-text-secondary);
2465
- margin-bottom: 4px;
2466
- }
2467
-
2468
- .empty-state-subtitle {
2469
- font-size: var(--font-size-secondary);
2470
- color: var(--color-text-tertiary);
2471
- opacity: 0.8;
2472
- }
2473
-
2474
- /* ============================================
2475
- 动画
2476
- ============================================ */
2477
- @keyframes fadeIn {
2478
- from { opacity: 0; }
2479
- to { opacity: 1; }
2480
- }
2481
-
2482
- @keyframes slideUp {
2483
- from { transform: translateY(24px); opacity: 0; }
2484
- to { transform: translateY(0); opacity: 1; }
2485
- }
2486
-
2487
- @keyframes slideDown {
2488
- from { transform: translateX(-50%) translateY(-100%); opacity: 0; }
2489
- to { transform: translateX(-50%) translateY(0); opacity: 1; }
2490
- }
2491
-
2492
- @keyframes spin {
2493
- from { transform: rotate(0deg); }
2494
- to { transform: rotate(360deg); }
2495
- }
2496
-
2497
- [v-cloak] {
2498
- display: none !important;
2499
- }
2500
-
2501
- /* 模式内容容器 */
2502
- .mode-content {
2503
- animation: fadeIn var(--transition-normal) var(--ease-spring);
2504
- }
2505
-
2506
- /* 内容区域包裹器 - 稳定高度 */
2507
- .content-wrapper {
2508
- min-height: 300px;
2509
- position: relative;
2510
- }
2511
-
2512
- button:focus-visible,
2513
- select:focus-visible,
2514
- input:focus-visible,
2515
- textarea:focus-visible {
2516
- outline: 3px solid rgba(201, 94, 75, 0.25);
2517
- outline-offset: 2px;
2518
- }
2519
-
2520
- @media (max-width: 960px) {
2521
- .container {
2522
- padding: 12px;
2523
- }
2524
- .app-shell {
2525
- grid-template-columns: 1fr;
2526
- }
2527
- .side-rail {
2528
- display: none;
2529
- }
2530
- .main-panel {
2531
- padding: var(--spacing-sm) var(--spacing-sm);
2532
- border-radius: 14px;
2533
- }
2534
- .top-tabs {
2535
- display: grid !important;
2536
- grid-template-columns: repeat(2, minmax(0, 1fr));
2537
- }
2538
- .status-strip {
2539
- gap: var(--spacing-sm);
2540
- margin-top: 4px;
2541
- }
2542
- .status-chip {
2543
- flex: 1 1 calc(50% - var(--spacing-sm));
2544
- min-width: 0;
2545
- }
2546
- }
2547
-
2548
- @media (max-width: 720px) {
2549
- .main-title {
2550
- font-size: 40px;
2551
- }
2552
-
2553
- .hero-title {
2554
- font-size: 32px;
2555
- }
2556
-
2557
- .subtitle {
2558
- font-size: var(--font-size-secondary);
2559
- margin-bottom: 16px;
2560
- }
2561
-
2562
- .segmented-control {
2563
- flex-direction: column;
2564
- gap: 6px;
2565
- }
2566
-
2567
- .status-strip {
2568
- flex-direction: row;
2569
- flex-wrap: wrap;
2570
- }
2571
-
2572
- .status-chip {
2573
- flex: 1 1 100%;
2574
- }
2575
- }
2576
-
2577
- @media (max-width: 540px) {
2578
- body {
2579
- padding: var(--spacing-md) var(--spacing-sm);
2580
- }
2581
- .container {
2582
- padding: 0 var(--spacing-sm) var(--spacing-md);
2583
- }
2584
- .hero-title {
2585
- font-size: 32px;
2586
- }
2587
- .hero-subtitle {
2588
- font-size: var(--font-size-secondary);
2589
- }
2590
- .top-tabs {
2591
- grid-template-columns: repeat(1, minmax(0, 1fr));
2592
- }
2593
- .main-panel {
2594
- padding: var(--spacing-sm);
2595
- }
2596
- .card {
2597
- padding: 12px;
2598
- }
2599
- .session-layout {
2600
- grid-template-columns: 1fr;
2601
- height: auto;
2602
- min-height: 0;
2603
- }
2604
-
2605
- .status-strip {
2606
- gap: var(--spacing-xs);
2607
- }
2608
-
2609
- .status-chip {
2610
- flex: 1 1 100%;
2611
- min-width: 100%;
2612
- }
2613
-
2614
- .btn-add,
2615
- .btn-tool,
2616
- .card-action-btn,
2617
- .btn-session-export,
2618
- .btn-session-open,
2619
- .btn-session-clone,
2620
- .btn-session-refresh,
2621
- .btn-session-delete,
2622
- .btn-icon,
2623
- .session-item-copy {
2624
- min-height: 44px;
2625
- padding-top: 12px;
2626
- padding-bottom: 12px;
2627
- }
2628
-
2629
- .btn-icon,
2630
- .session-item-copy {
2631
- min-width: 44px;
2632
- }
2633
-
2634
- .session-item {
2635
- min-height: 75px;
2636
- height: 75px;
2637
- padding: 12px 14px;
2638
- }
2639
-
2640
- .session-item-header {
2641
- flex-direction: row;
2642
- align-items: center;
2643
- gap: 8px;
2644
- }
2645
-
2646
- .session-item-main {
2647
- align-items: center;
2648
- }
2649
-
2650
- .session-item-copy {
2651
- width: 20px;
2652
- height: 20px;
2653
- min-width: 20px;
2654
- min-height: 20px;
2655
- border-radius: 6px;
2656
- padding: 2px;
2657
- display: inline-flex;
2658
- align-items: center;
2659
- justify-content: center;
2660
- transform: translate(-3px, 0);
2661
- }
2662
-
2663
- .session-item-copy svg {
2664
- width: 12px;
2665
- height: 12px;
2666
- }
2667
-
2668
- .session-item-title {
2669
- -webkit-line-clamp: 1;
2670
- max-height: none;
2671
- white-space: nowrap;
2672
- text-overflow: ellipsis;
2673
- overflow: hidden;
2674
- }
2675
-
2676
- .session-item-actions {
2677
- margin-top: 0;
2678
- }
2679
-
2680
- .session-item-meta {
2681
- margin-top: -2px;
2682
- margin-bottom: 0;
2683
- gap: 4px;
2684
- align-items: center;
2685
- }
2686
-
2687
- .session-count-pill {
2688
- transform: translateY(-6px);
2689
- }
2690
-
2691
- .card {
2692
- padding: 8px;
2693
- }
2694
-
2695
- .card-list {
2696
- gap: 4px;
2697
- margin-bottom: 4px;
2698
- }
2699
-
2700
- .card-actions {
2701
- gap: 8px;
2702
- }
2703
-
2704
- .card-action-btn {
2705
- width: 40px;
2706
- height: 40px;
2707
- border-radius: 10px;
2708
- }
2709
-
2710
- .card-action-btn svg {
2711
- width: 18px;
2712
- height: 18px;
2713
- }
2714
-
2715
- /* 移动端不显示配置状态 pill,节省空间 */
2716
- .card-trailing .pill {
2717
- display: none;
2718
- }
2719
-
2720
- .session-preview {
2721
- border-radius: var(--radius-lg);
2722
- }
2723
- }
2724
- </style>
2725
- </head>
2726
- <body>
2727
- <div id="app" class="container" v-cloak>
2728
- <div class="hero" v-if="!sessionStandalone">
2729
- <div class="hero-title">
2730
- Codex <span class="accent">Mate.</span>
2731
- </div>
2732
- <div class="hero-subtitle">
2733
- 本地配置中枢,统一管理 Codex / Claude Code / OpenClaw / 会话。
2734
- </div>
2735
- </div>
2736
-
2737
- <div v-if="!sessionStandalone" class="top-tabs" role="tablist" aria-label="主导航">
2738
- <button class="top-tab"
2739
- id="tab-config-codex"
2740
- role="tab"
2741
- :tabindex="mainTab === 'config' && configMode === 'codex' ? 0 : -1"
2742
- :aria-selected="mainTab === 'config' && configMode === 'codex'"
2743
- :aria-pressed="mainTab === 'config' && configMode === 'codex'"
2744
- aria-controls="panel-config-codex"
2745
- :class="{ active: mainTab === 'config' && configMode === 'codex' }"
2746
- @click="switchConfigMode('codex')">Codex 配置</button>
2747
- <button class="top-tab"
2748
- id="tab-config-claude"
2749
- role="tab"
2750
- :tabindex="mainTab === 'config' && configMode === 'claude' ? 0 : -1"
2751
- :aria-selected="mainTab === 'config' && configMode === 'claude'"
2752
- :aria-pressed="mainTab === 'config' && configMode === 'claude'"
2753
- aria-controls="panel-config-claude"
2754
- :class="{ active: mainTab === 'config' && configMode === 'claude' }"
2755
- @click="switchConfigMode('claude')">Claude Code 配置</button>
2756
- <button class="top-tab"
2757
- id="tab-config-openclaw"
2758
- role="tab"
2759
- :tabindex="mainTab === 'config' && configMode === 'openclaw' ? 0 : -1"
2760
- :aria-selected="mainTab === 'config' && configMode === 'openclaw'"
2761
- :aria-pressed="mainTab === 'config' && configMode === 'openclaw'"
2762
- aria-controls="panel-config-openclaw"
2763
- :class="{ active: mainTab === 'config' && configMode === 'openclaw' }"
2764
- @click="switchConfigMode('openclaw')">OpenClaw 配置</button>
2765
- <button class="top-tab"
2766
- id="tab-sessions"
2767
- role="tab"
2768
- :tabindex="mainTab === 'sessions' ? 0 : -1"
2769
- :aria-selected="mainTab === 'sessions'"
2770
- :aria-pressed="mainTab === 'sessions'"
2771
- aria-controls="panel-sessions"
2772
- :class="{ active: mainTab === 'sessions' }"
2773
- @click="switchMainTab('sessions')">会话浏览</button>
2774
- </div>
2775
-
2776
- <div :class="['app-shell', { standalone: sessionStandalone }]">
2777
- <aside class="side-rail" v-if="!sessionStandalone">
2778
- <div class="brand-block">
2779
- <div class="brand-title">
2780
- Codex <span class="accent">Mate</span>
2781
- </div>
2782
- <div class="brand-subtitle">
2783
- 配置 / 会话切换器
2784
- </div>
2785
- </div>
2786
-
2787
- <div class="side-section" role="tablist" aria-label="配置管理">
2788
- <div class="side-section-title">配置管理</div>
2789
- <button
2790
- role="tab"
2791
- id="side-tab-config-codex"
2792
- aria-controls="panel-config-codex"
2793
- :tabindex="mainTab === 'config' && configMode === 'codex' ? 0 : -1"
2794
- :aria-selected="mainTab === 'config' && configMode === 'codex'"
2795
- :aria-pressed="mainTab === 'config' && configMode === 'codex'"
2796
- :class="['side-item', { active: mainTab === 'config' && configMode === 'codex' }]"
2797
- @click="switchConfigMode('codex')">
2798
- <div class="side-item-title">Codex 配置</div>
2799
- <div class="side-item-meta">
2800
- <span>提供商 / 模型</span>
2801
- <span v-if="currentProvider">当前 {{ currentProvider }}</span>
2802
- </div>
2803
- </button>
2804
- <button
2805
- role="tab"
2806
- id="side-tab-config-claude"
2807
- aria-controls="panel-config-claude"
2808
- :tabindex="mainTab === 'config' && configMode === 'claude' ? 0 : -1"
2809
- :aria-selected="mainTab === 'config' && configMode === 'claude'"
2810
- :aria-pressed="mainTab === 'config' && configMode === 'claude'"
2811
- :class="['side-item', { active: mainTab === 'config' && configMode === 'claude' }]"
2812
- @click="switchConfigMode('claude')">
2813
- <div class="side-item-title">Claude Code 配置</div>
2814
- <div class="side-item-meta">
2815
- <span>Base URL / Key</span>
2816
- <span v-if="currentClaudeConfig">当前 {{ currentClaudeConfig }}</span>
2817
- </div>
2818
- </button>
2819
- <button
2820
- role="tab"
2821
- id="side-tab-config-openclaw"
2822
- aria-controls="panel-config-openclaw"
2823
- :tabindex="mainTab === 'config' && configMode === 'openclaw' ? 0 : -1"
2824
- :aria-selected="mainTab === 'config' && configMode === 'openclaw'"
2825
- :aria-pressed="mainTab === 'config' && configMode === 'openclaw'"
2826
- :class="['side-item', { active: mainTab === 'config' && configMode === 'openclaw' }]"
2827
- @click="switchConfigMode('openclaw')">
2828
- <div class="side-item-title">OpenClaw 配置</div>
2829
- <div class="side-item-meta">
2830
- <span>JSON5 / Workspace</span>
2831
- <span v-if="currentOpenclawConfig">当前 {{ currentOpenclawConfig }}</span>
2832
- </div>
2833
- </button>
2834
- </div>
2835
-
2836
- <div class="side-section" role="tablist" aria-label="会话管理">
2837
- <div class="side-section-title">会话管理</div>
2838
- <button
2839
- role="tab"
2840
- id="side-tab-sessions"
2841
- aria-controls="panel-sessions"
2842
- :tabindex="mainTab === 'sessions' ? 0 : -1"
2843
- :aria-selected="mainTab === 'sessions'"
2844
- :aria-pressed="mainTab === 'sessions'"
2845
- :class="['side-item', { active: mainTab === 'sessions' }]"
2846
- @click="switchMainTab('sessions')">
2847
- <div class="side-item-title">会话浏览</div>
2848
- <div class="side-item-meta">
2849
- <span>快速预览 / 导出</span>
2850
- <span>来源:{{ sessionFilterSource === 'all' ? '全部' : (sessionFilterSource === 'codex' ? 'Codex' : 'Claude') }}</span>
2851
- </div>
2852
- </button>
2853
- </div>
2854
- </aside>
2855
- <main class="main-panel">
2856
- <div class="panel-header" v-if="!sessionStandalone">
2857
- <h1 class="main-title">
2858
- {{ mainTab === 'config' ? '配置中心' : '会话浏览' }}
2859
- </h1>
2860
- <p class="subtitle" v-if="mainTab === 'config'">
2861
- 本地配置中枢,统一管理 Codex / Claude Code / OpenClaw。
2862
- </p>
2863
- <p class="subtitle" v-else>
2864
- 浏览、导出或独立查看 Codex / Claude 会话记录。
2865
- </p>
2866
- </div>
2867
-
2868
- <div class="status-strip" v-if="!sessionStandalone && mainTab === 'config'">
2869
- <template v-if="configMode === 'codex'">
2870
- <div class="status-chip">
2871
- <span class="label">Codex 提供商</span>
2872
- <span class="value">{{ currentProvider || '未选择' }}</span>
2873
- </div>
2874
- <div class="status-chip">
2875
- <span class="label">Codex 模型</span>
2876
- <span class="value">{{ currentModel || '未选择' }}</span>
2877
- </div>
2878
- </template>
2879
- <template v-else-if="configMode === 'claude'">
2880
- <div class="status-chip">
2881
- <span class="label">Claude 配置</span>
2882
- <span class="value">{{ currentClaudeConfig || '未选择' }}</span>
2883
- </div>
2884
- <div class="status-chip">
2885
- <span class="label">Claude 模型</span>
2886
- <span class="value">{{ currentClaudeModel || '未选择' }}</span>
2887
- </div>
2888
- </template>
2889
- <template v-else>
2890
- <div class="status-chip">
2891
- <span class="label">OpenClaw 配置</span>
2892
- <span class="value">{{ currentOpenclawConfig || '未选择' }}</span>
2893
- </div>
2894
- <div class="status-chip">
2895
- <span class="label">工作区文件</span>
2896
- <span class="value">{{ openclawWorkspaceFileName || '未选择' }}</span>
2897
- </div>
2898
- </template>
2899
- </div>
2900
- <div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'sessions'">
2901
- <div class="status-chip">
2902
- <span class="label">当前来源</span>
2903
- <span class="value">{{ sessionFilterSource === 'claude' ? 'Claude Code' : 'Codex' }}</span>
2904
- </div>
2905
- <div class="status-chip">
2906
- <span class="label">会话数</span>
2907
- <span class="value">{{ sessionsList.length }}</span>
2908
- </div>
2909
- </div>
2910
-
2911
- <div v-if="false && mainTab === 'config' && !sessionStandalone" class="config-subtabs">
2912
- <button :class="['config-subtab', { active: configMode === 'codex' }]" @click="switchConfigMode('codex')">
2913
- Codex 配置
2914
- </button>
2915
- <button :class="['config-subtab', { active: configMode === 'claude' }]" @click="switchConfigMode('claude')">
2916
- Claude Code 配置
2917
- </button>
2918
- <button :class="['config-subtab', { active: configMode === 'openclaw' }]" @click="switchConfigMode('openclaw')">
2919
- OpenClaw 配置
2920
- </button>
2921
- </div>
2922
-
2923
- <!-- 内容包裹器 - 稳定布局 -->
2924
- <div class="content-wrapper">
2925
- <!-- Codex 配置模式 -->
2926
- <div
2927
- v-show="mainTab === 'config' && configMode === 'codex'"
2928
- class="mode-content mode-cards"
2929
- id="panel-config-codex"
2930
- role="tabpanel"
2931
- :aria-labelledby="'tab-config-codex'">
2932
- <!-- 添加提供商按钮 -->
2933
- <button class="btn-add" @click="showAddModal = true" v-if="!loading && !initError">
2934
- <svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
2935
- <path d="M10 4v12M4 10h12"/>
2936
- </svg>
2937
- 添加提供商
2938
- </button>
2939
-
2940
- <!-- 模型选择器 -->
2941
- <div class="selector-section">
2942
- <div class="selector-header">
2943
- <span class="selector-title">模型</span>
2944
- <div class="selector-actions">
2945
- <button class="btn-icon" @click="showModelModal = true" title="添加模型" v-if="modelsSource === 'legacy'">+</button>
2946
- <button class="btn-icon" @click="showModelListModal = true" title="管理模型" v-if="modelsSource === 'legacy'">≡</button>
2947
- </div>
2948
- </div>
2949
- <select
2950
- v-if="codexModelsLoading || modelsSource === 'remote'"
2951
- class="model-select"
2952
- v-model="currentModel"
2953
- @change="onModelChange"
2954
- :disabled="codexModelsLoading"
2955
- >
2956
- <option v-if="codexModelsLoading" value="">加载中...</option>
2957
- <option v-else v-for="model in models" :key="model" :value="model">{{ model }}</option>
2958
- </select>
2959
- <input
2960
- v-if="!codexModelsLoading && (modelsSource !== 'remote' || !modelsHasCurrent)"
2961
- class="model-input"
2962
- v-model="currentModel"
2963
- @blur="onModelChange"
2964
- placeholder="例如: gpt-5.3-codex"
2965
- >
2966
- <div class="config-template-hint" v-if="modelsSource === 'unlimited'">
2967
- 当前提供商未提供模型列表,视为不限。模型可手动输入。
2968
- </div>
2969
- <div class="config-template-hint" v-if="modelsSource === 'error'">
2970
- 模型列表获取失败,请检查接口或手动输入。
2971
- </div>
2972
- <div class="config-template-hint" v-if="modelsSource === 'remote' && !modelsHasCurrent">
2973
- 当前模型不在接口列表中,请手动输入或在模板中调整。
2974
- </div>
2975
- <div class="config-template-hint">
2976
- Codex 配置需先改模板,再手动应用。
2977
- </div>
2978
- <button class="btn-tool btn-template-editor" @click="openConfigTemplateEditor" :disabled="loading || !!initError">
2979
- 打开 Config 模板编辑器
2980
- </button>
2981
- </div>
2982
-
2983
- <div class="selector-section">
2984
- <div class="selector-header">
2985
- <span class="selector-title">服务档位</span>
2986
- </div>
2987
- <select class="model-select" v-model="serviceTier" @change="onServiceTierChange">
2988
- <option value="fast">fast(默认)</option>
2989
- <option value="standard">standard</option>
2990
- </select>
2991
- <div class="config-template-hint">
2992
- 仅 fast 会写入 <code>service_tier</code>。
2993
- </div>
2994
- </div>
2995
-
2996
- <div class="selector-section">
2997
- <div class="selector-header">
2998
- <span class="selector-title">AGENTS.md</span>
2999
- </div>
3000
- <div class="config-template-hint">
3001
- Codex 指令:<code>~/.codex/AGENTS.md</code>(同级 <code>config.toml</code>)。
3002
- </div>
3003
- <button class="btn-tool" @click="openAgentsEditor" :disabled="loading || !!initError || agentsLoading">
3004
- {{ agentsLoading ? '加载中...' : '打开 AGENTS.md 编辑器' }}
3005
- </button>
3006
- </div>
3007
-
3008
- <div class="selector-section">
3009
- <div class="selector-header">
3010
- <span class="selector-title">配置健康检查</span>
3011
- </div>
3012
- <button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">
3013
- {{ healthCheckLoading ? '检查中...' : '运行检查' }}
3014
- </button>
3015
- </div>
3016
-
3017
- <div v-if="!loading && !initError" class="card-list">
3018
- <div v-for="provider in providersList" :key="provider.name"
3019
- :class="['card', { active: currentProvider === provider.name }]"
3020
- @click="switchProvider(provider.name)">
3021
- <div class="card-leading">
3022
- <div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}</div>
3023
- <div class="card-content">
3024
- <div class="card-title">{{ provider.name }}</div>
3025
- <div class="card-subtitle">{{ provider.url || '未设置 URL' }}</div>
3026
- </div>
3027
- </div>
3028
- <div class="card-trailing">
3029
- <span :class="['pill', provider.hasKey ? 'configured' : 'empty']">
3030
- {{ provider.hasKey ? '已配置' : '未配置' }}
3031
- </span>
3032
- <span v-if="speedResults[provider.name]" :class="['latency', speedResults[provider.name].ok ? 'ok' : 'error']">
3033
- {{ formatLatency(speedResults[provider.name]) }}
3034
- </span>
3035
- <div class="card-actions" @click.stop>
3036
- <button class="card-action-btn" :class="{ loading: speedLoading[provider.name] }" @click="runSpeedTest(provider.name)" title="Speed Test">
3037
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3038
- <path d="M13 2L3 14h7l-1 8 12-14h-7l-1-6z"/>
3039
- </svg>
3040
- </button>
3041
- <button class="card-action-btn" :class="{ loading: providerShareLoading[provider.name] }" @click="copyProviderShareCommand(provider)" title="分享导入命令">
3042
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3043
- <path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
3044
- <path d="M16 6l-4-4-4 4"/>
3045
- <path d="M12 2v14"/>
3046
- </svg>
3047
- </button>
3048
- <button class="card-action-btn" @click="openEditModal(provider)" title="编辑">
3049
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3050
- <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
3051
- <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
3052
- </svg>
3053
- </button>
3054
- <button class="card-action-btn delete" @click="deleteProvider(provider.name)" title="删除">
3055
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3056
- <path d="M3 6h18"/>
3057
- <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
3058
- </svg>
3059
- </button>
3060
- </div>
3061
- </div>
3062
- </div>
3063
- </div>
3064
- </div>
3065
-
3066
- <!-- Claude Code 配置模式 -->
3067
- <div
3068
- v-show="mainTab === 'config' && configMode === 'claude'"
3069
- class="mode-content mode-cards"
3070
- id="panel-config-claude"
3071
- role="tabpanel"
3072
- :aria-labelledby="'tab-config-claude'">
3073
- <!-- 添加提供商按钮 -->
3074
- <button class="btn-add" @click="openClaudeConfigModal" v-if="!loading && !initError">
3075
- <svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
3076
- <path d="M10 4v12M4 10h12"/>
3077
- </svg>
3078
- 添加提供商
3079
- </button>
3080
- <div class="config-template-hint">
3081
- 默认应用到 <code>~/.claude/settings.json</code>。
3082
- </div>
3083
-
3084
- <div class="selector-section">
3085
- <div class="selector-header">
3086
- <span class="selector-title">模型</span>
3087
- </div>
3088
- <select
3089
- v-if="claudeModelHasList"
3090
- class="model-select"
3091
- v-model="currentClaudeModel"
3092
- @change="onClaudeModelChange"
3093
- >
3094
- <option v-for="model in claudeModelOptions" :key="model" :value="model">{{ model }}</option>
3095
- </select>
3096
- <input
3097
- v-else
3098
- class="model-input"
3099
- v-model="currentClaudeModel"
3100
- @blur="onClaudeModelChange"
3101
- @keyup.enter="onClaudeModelChange"
3102
- placeholder="例如: claude-3-7-sonnet"
3103
- >
3104
- <div class="config-template-hint">
3105
- 模型修改后会自动保存并应用到当前配置。
3106
- </div>
3107
- </div>
3108
-
3109
- <div class="selector-section">
3110
- <div class="selector-header">
3111
- <span class="selector-title">配置健康检查</span>
3112
- </div>
3113
- <button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">
3114
- {{ healthCheckLoading ? '检查中...' : '运行检查' }}
3115
- </button>
3116
- </div>
3117
-
3118
- <div class="card-list">
3119
- <div v-for="(config, name) in claudeConfigs" :key="name"
3120
- :class="['card', { active: currentClaudeConfig === name }]"
3121
- @click="applyClaudeConfig(name)">
3122
- <div class="card-leading">
3123
- <div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
3124
- <div class="card-content">
3125
- <div class="card-title">{{ name }}</div>
3126
- <div class="card-subtitle">{{ config.model || '未设置模型' }}</div>
3127
- </div>
3128
- </div>
3129
- <div class="card-trailing">
3130
- <span :class="['pill', config.hasKey ? 'configured' : 'empty']">
3131
- {{ config.hasKey ? '已配置' : '未配置' }}
3132
- </span>
3133
- <span v-if="claudeSpeedResults[name]" :class="['latency', claudeSpeedResults[name].ok ? 'ok' : 'error']">
3134
- {{ formatLatency(claudeSpeedResults[name]) }}
3135
- </span>
3136
- <div class="card-actions" @click.stop>
3137
- <button class="card-action-btn" @click="openEditConfigModal(name)" title="编辑">
3138
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3139
- <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
3140
- <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
3141
- </svg>
3142
- </button>
3143
- <button class="card-action-btn" :class="{ loading: claudeShareLoading[name] }" @click="copyClaudeShareCommand(name)" title="分享导入命令" aria-label="Share import command">
3144
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3145
- <path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
3146
- <path d="M16 6l-4-4-4 4"/>
3147
- <path d="M12 2v14"/>
3148
- </svg>
3149
- </button>
3150
- <button class="card-action-btn delete" @click="deleteClaudeConfig(name)" title="删除">
3151
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3152
- <path d="M3 6h18"/>
3153
- <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
3154
- </svg>
3155
- </button>
3156
- </div>
3157
- </div>
3158
- </div>
3159
- </div>
3160
- </div>
3161
-
3162
- <!-- OpenClaw 配置模式 -->
3163
- <div
3164
- v-show="mainTab === 'config' && configMode === 'openclaw'"
3165
- class="mode-content mode-cards"
3166
- id="panel-config-openclaw"
3167
- role="tabpanel"
3168
- :aria-labelledby="'tab-config-openclaw'">
3169
- <button class="btn-add" @click="openOpenclawAddModal" v-if="!loading && !initError">
3170
- <svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
3171
- <path d="M10 4v12M4 10h12"/>
3172
- </svg>
3173
- 添加 OpenClaw 配置
3174
- </button>
3175
- <div class="config-template-hint">
3176
- 默认应用到 <code>~/.openclaw/openclaw.json</code>。支持 JSON5(注释/尾逗号)。
3177
- </div>
3178
-
3179
- <div class="selector-section">
3180
- <div class="selector-header">
3181
- <span class="selector-title">AGENTS.md</span>
3182
- </div>
3183
- <div class="config-template-hint">
3184
- 管理 OpenClaw Workspace 指令文件,默认读写 <code>~/.openclaw/workspace/AGENTS.md</code>。
3185
- </div>
3186
- <button class="btn-tool" @click="openOpenclawAgentsEditor" :disabled="loading || !!initError || agentsLoading">
3187
- {{ agentsLoading ? '加载中...' : '打开 AGENTS.md 编辑器' }}
3188
- </button>
3189
- </div>
3190
-
3191
- <div class="selector-section">
3192
- <div class="selector-header">
3193
- <span class="selector-title">工作区文件</span>
3194
- </div>
3195
- <input
3196
- class="form-input"
3197
- v-model="openclawWorkspaceFileName"
3198
- placeholder="例如: SOUL.md">
3199
- <div class="config-template-hint">
3200
- 仅支持 OpenClaw Workspace 内的 <code>.md</code> 文件。
3201
- </div>
3202
- <button class="btn-tool" @click="openOpenclawWorkspaceEditor" :disabled="loading || !!initError || agentsLoading">
3203
- {{ agentsLoading ? '加载中...' : '打开工作区文件' }}
3204
- </button>
3205
- </div>
3206
-
3207
- <div class="card-list">
3208
- <div v-for="(config, name) in openclawConfigs" :key="name"
3209
- :class="['card', { active: currentOpenclawConfig === name }]"
3210
- @click="applyOpenclawConfig(name)">
3211
- <div class="card-leading">
3212
- <div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
3213
- <div class="card-content">
3214
- <div class="card-title">{{ name }}</div>
3215
- <div class="card-subtitle">{{ openclawSubtitle(config) }}</div>
3216
- </div>
3217
- </div>
3218
- <div class="card-trailing">
3219
- <span :class="['pill', openclawHasContent(config) ? 'configured' : 'empty']">
3220
- {{ openclawHasContent(config) ? '已配置' : '未配置' }}
3221
- </span>
3222
- <div class="card-actions" @click.stop>
3223
- <button class="card-action-btn" @click="openOpenclawEditModal(name)" title="编辑">
3224
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3225
- <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
3226
- <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
3227
- </svg>
3228
- </button>
3229
- <button class="card-action-btn delete" @click="deleteOpenclawConfig(name)" title="删除">
3230
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3231
- <path d="M3 6h18"/>
3232
- <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
3233
- </svg>
3234
- </button>
3235
- </div>
3236
- </div>
3237
- </div>
3238
- </div>
3239
- </div>
3240
-
3241
- <!-- 会话浏览模式 -->
3242
- <div
3243
- v-show="mainTab === 'sessions'"
3244
- class="mode-content"
3245
- id="panel-sessions"
3246
- role="tabpanel"
3247
- :aria-labelledby="'tab-sessions'">
3248
- <div v-if="sessionStandalone" class="session-standalone-page">
3249
- <div v-if="sessionStandaloneLoading" class="state-message">
3250
- 加载中...
3251
- </div>
3252
- <div v-else-if="sessionStandaloneError" class="state-message error">
3253
- {{ sessionStandaloneError }}
3254
- </div>
3255
- <div v-else>
3256
- <div class="session-standalone-title">
3257
- {{ sessionStandaloneTitle }}
3258
- <span v-if="sessionStandaloneSourceLabel"> · {{ sessionStandaloneSourceLabel }}</span>
3259
- </div>
3260
- <pre class="session-standalone-text">{{ sessionStandaloneText }}</pre>
3261
- </div>
3262
- </div>
3263
-
3264
- <div v-else>
3265
- <div v-if="!sessionStandalone" class="selector-section">
3266
- <div class="selector-header">
3267
- <span class="selector-title">会话来源</span>
3268
- </div>
3269
- <div class="session-toolbar">
3270
- <div class="session-toolbar-group">
3271
- <select class="session-source-select" v-model="sessionFilterSource" @change="onSessionSourceChange" :disabled="sessionsLoading">
3272
- <option value="codex">Codex</option>
3273
- <option value="claude">Claude Code</option>
3274
- </select>
3275
- <select
3276
- class="session-path-select"
3277
- v-model="sessionPathFilter"
3278
- @change="onSessionPathFilterChange"
3279
- :disabled="sessionsLoading">
3280
- <option value="">全部路径</option>
3281
- <option v-for="cwd in sessionPathOptions" :key="cwd" :value="cwd">{{ cwd }}</option>
3282
- </select>
3283
- </div>
3284
- <div class="session-toolbar-group session-toolbar-grow">
3285
- <input
3286
- class="session-query-input"
3287
- v-model="sessionQuery"
3288
- @keyup.enter="loadSessions"
3289
- :disabled="sessionsLoading || !isSessionQueryEnabled"
3290
- :placeholder="sessionQueryPlaceholder">
3291
- </div>
3292
- <div class="session-toolbar-group">
3293
- <select
3294
- class="session-role-select"
3295
- v-model="sessionRoleFilter"
3296
- @change="onSessionFilterChange"
3297
- disabled>
3298
- <option value="all">全部角色</option>
3299
- <option value="user">仅 User</option>
3300
- <option value="assistant">仅 Assistant</option>
3301
- <option value="system">仅 System</option>
3302
- </select>
3303
- <select
3304
- class="session-time-select"
3305
- v-model="sessionTimePreset"
3306
- @change="onSessionFilterChange"
3307
- disabled>
3308
- <option value="all">全部时间</option>
3309
- <option value="7d">近 7 天</option>
3310
- <option value="30d">近 30 天</option>
3311
- <option value="90d">近 90 天</option>
3312
- </select>
3313
- </div>
3314
- <div class="session-toolbar-group session-toolbar-actions">
3315
- <button class="btn-tool" @click="loadSessions" :disabled="sessionsLoading">
3316
- {{ sessionsLoading ? '刷新中...' : '刷新会话' }}
3317
- </button>
3318
- <button class="btn-tool" @click="clearSessionFilters" :disabled="sessionsLoading">
3319
- 清空筛选
3320
- </button>
3321
- </div>
3322
- </div>
3323
- <div class="session-toolbar-footer">
3324
- <label class="quick-option">
3325
- <input
3326
- type="checkbox"
3327
- v-model="sessionResumeWithYolo"
3328
- @change="onSessionResumeYoloChange"
3329
- >
3330
- 复制恢复命令附带 --yolo
3331
- </label>
3332
- </div>
3333
- </div>
3334
-
3335
- <div v-if="!sessionStandalone && sessionsLoading" class="state-message">
3336
- 会话加载中...
3337
- </div>
3338
-
3339
- <div v-else-if="!sessionStandalone && sessionsList.length === 0" class="session-empty">
3340
- 暂无可用会话记录
3341
- </div>
3342
-
3343
- <div v-else :class="['session-layout', { 'session-standalone': sessionStandalone }]">
3344
- <div v-if="!sessionStandalone" class="session-list">
3345
- <div
3346
- v-for="session in sessionsList"
3347
- :key="session.source + '-' + session.sessionId + '-' + session.filePath"
3348
- :class="[
3349
- 'session-item',
3350
- {
3351
- active: activeSession && getSessionExportKey(activeSession) === getSessionExportKey(session)
3352
- }
3353
- ]"
3354
- @click="selectSession(session)">
3355
- <div class="session-item-header">
3356
- <div class="session-item-main">
3357
- <div class="session-item-title">{{ session.title || session.sessionId }}</div>
3358
- </div>
3359
- <div class="session-item-actions">
3360
- <button
3361
- v-if="isResumeCommandAvailable(session)"
3362
- class="session-item-copy"
3363
- @click.stop="copyResumeCommand(session)"
3364
- :disabled="sessionsLoading"
3365
- aria-label="复制恢复命令"
3366
- title="复制恢复命令">
3367
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3368
- <rect x="8" y="8" width="12" height="12" rx="2"></rect>
3369
- <path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"></path>
3370
- </svg>
3371
- </button>
3372
- </div>
3373
- </div>
3374
- <div class="session-item-meta">
3375
- <span class="session-source">{{ session.sourceLabel }}</span>
3376
- <span class="session-item-time">{{ session.updatedAt || 'unknown time' }}</span>
3377
- <span class="session-count-pill">{{ session.messageCount || 0 }} 条</span>
3378
- </div>
3379
- <div v-if="session.match && session.match.hit" class="session-item-sub session-item-snippet">
3380
- <span v-if="session.match.snippets && session.match.snippets.length">{{ session.match.snippets.join(' · ') }}</span>
3381
- <span v-else>命中 {{ session.match.count || 1 }} 处</span>
3382
- </div>
3383
- </div>
3384
- </div>
3385
-
3386
- <div :class="['session-preview', { active: !!activeSession }]">
3387
- <template v-if="activeSession">
3388
- <div class="session-preview-scroll">
3389
- <div class="session-preview-header">
3390
- <div>
3391
- <div class="session-preview-title">{{ activeSession.title || activeSession.sessionId }}</div>
3392
- <div class="session-preview-meta">
3393
- <span class="session-preview-meta-item">{{ activeSession.sourceLabel }}</span>
3394
- <span class="session-preview-meta-item">{{ activeSession.updatedAt || 'unknown time' }}</span>
3395
- </div>
3396
- <div class="session-preview-meta" v-if="activeSession.cwd">
3397
- <span class="session-preview-meta-item">{{ activeSession.cwd }}</span>
3398
- </div>
3399
- </div>
3400
- <div v-if="!sessionStandalone" class="session-actions">
3401
- <button class="btn-session-refresh" @click="loadActiveSessionDetail" :disabled="sessionDetailLoading || !activeSession">
3402
- {{ sessionDetailLoading ? '加载中...' : '刷新内容' }}
3403
- </button>
3404
- <button
3405
- v-if="isCloneAvailable(activeSession)"
3406
- class="btn-session-clone"
3407
- @click="cloneSession(activeSession)"
3408
- :disabled="!activeSession || sessionsLoading || sessionCloning[getSessionExportKey(activeSession)]">
3409
- {{ (activeSession && sessionCloning[getSessionExportKey(activeSession)]) ? '克隆中...' : '克隆会话' }}
3410
- </button>
3411
- <button
3412
- v-if="isDeleteAvailable(activeSession)"
3413
- class="btn-session-delete"
3414
- @click="deleteSession(activeSession)"
3415
- :disabled="!activeSession || sessionsLoading || sessionDeleting[getSessionExportKey(activeSession)]">
3416
- {{ (activeSession && sessionDeleting[getSessionExportKey(activeSession)]) ? '删除中...' : '删除会话' }}
3417
- </button>
3418
- <button
3419
- class="btn-session-export"
3420
- @click="exportSession(activeSession)"
3421
- :disabled="!activeSession || sessionExporting[getSessionExportKey(activeSession)]">
3422
- {{ (activeSession && sessionExporting[getSessionExportKey(activeSession)]) ? '导出中...' : '导出记录' }}
3423
- </button>
3424
- <button
3425
- v-if="!sessionStandalone"
3426
- class="btn-session-open"
3427
- @click="openSessionStandalone(activeSession)"
3428
- :disabled="!activeSession">
3429
- 新页查看
3430
- </button>
3431
- </div>
3432
- </div>
3433
-
3434
- <div v-if="sessionDetailLoading" class="session-preview-empty">
3435
- 正在加载会话内容...
3436
- </div>
3437
-
3438
- <div v-else-if="activeSessionDetailError" class="session-preview-empty">
3439
- {{ activeSessionDetailError }}
3440
- </div>
3441
-
3442
- <div v-else-if="!activeSessionMessages.length" class="session-preview-empty">
3443
- 当前会话暂无可展示消息
3444
- </div>
3445
-
3446
- <div v-else class="session-preview-body">
3447
- <div v-if="activeSessionDetailClipped" class="session-item-sub session-item-wrap">
3448
- 仅展示最近 {{ activeSessionMessages.length }} 条消息。
3449
- </div>
3450
- <div
3451
- v-for="(msg, idx) in activeSessionMessages"
3452
- :key="getRecordRenderKey(msg, idx)"
3453
- :class="['session-msg', msg.role === 'user' ? 'user' : (msg.role === 'system' ? 'system' : 'assistant')]">
3454
- <div class="session-msg-header">
3455
- <div class="session-msg-meta">
3456
- <span class="session-msg-role">{{ msg.role === 'user' ? 'User' : (msg.role === 'system' ? 'System' : 'Assistant') }}</span>
3457
- <span class="session-msg-time">{{ msg.timestamp || '' }}</span>
3458
- </div>
3459
- </div>
3460
- <div class="session-msg-content">{{ msg.text || '' }}</div>
3461
- </div>
3462
- </div>
3463
- </div>
3464
- </template>
3465
-
3466
- <div v-else class="session-preview-empty">
3467
- <span v-if="sessionStandaloneError">{{ sessionStandaloneError }}</span>
3468
- <span v-else>请先在左侧选择一个会话</span>
3469
- </div>
3470
- </div>
3471
- </div>
3472
- </div>
3473
- </div>
3474
-
3475
- <!-- 加载状态 -->
3476
- <div v-if="loading" class="state-message">
3477
- 加载配置中...
3478
- </div>
3479
-
3480
- <!-- 错误状态 -->
3481
- <div v-else-if="initError" class="state-message error">
3482
- {{ initError }}
3483
- </div>
3484
- </div>
3485
-
3486
- </main>
3487
- </div>
3488
-
3489
- <!-- 添加提供商模态框 -->
3490
- <div v-if="showAddModal" class="modal-overlay" @click.self="showAddModal = false">
3491
- <div class="modal">
3492
- <div class="modal-title">添加提供商</div>
3493
-
3494
- <div class="form-group">
3495
- <label class="form-label">名称</label>
3496
- <input v-model="newProvider.name" class="form-input" placeholder="例如: myapi">
3497
- </div>
3498
- <div class="form-group">
3499
- <label class="form-label">API 端点</label>
3500
- <input v-model="newProvider.url" class="form-input" placeholder="https://api.example.com/v1">
3501
- </div>
3502
- <div class="form-group">
3503
- <label class="form-label">认证密钥</label>
3504
- <input v-model="newProvider.key" class="form-input" placeholder="sk-...">
3505
- </div>
3506
-
3507
- <div class="btn-group">
3508
- <button class="btn btn-cancel" @click="closeAddModal">取消</button>
3509
- <button class="btn btn-confirm" @click="addProvider">添加</button>
3510
- </div>
3511
- </div>
3512
- </div>
3513
-
3514
- <!-- 编辑提供商模态框 -->
3515
- <div v-if="showEditModal" class="modal-overlay" @click.self="closeEditModal">
3516
- <div class="modal">
3517
- <div class="modal-title">编辑提供商</div>
3518
-
3519
- <div class="form-group">
3520
- <label class="form-label">名称</label>
3521
- <input v-model="editingProvider.name" class="form-input" placeholder="提供商名称" readonly>
3522
- </div>
3523
- <div class="form-group">
3524
- <label class="form-label">API 端点</label>
3525
- <input v-model="editingProvider.url" class="form-input" placeholder="https://api.example.com/v1">
3526
- </div>
3527
- <div class="form-group">
3528
- <label class="form-label">认证密钥</label>
3529
- <input v-model="editingProvider.key" class="form-input" placeholder="留空则保持不变">
3530
- <div class="form-hint">留空表示不修改密钥</div>
3531
- </div>
3532
-
3533
- <div class="btn-group">
3534
- <button class="btn btn-cancel" @click="closeEditModal">取消</button>
3535
- <button class="btn btn-confirm" @click="updateProvider">保存</button>
3536
- </div>
3537
- </div>
3538
- </div>
3539
-
3540
- <!-- 添加模型模态框 -->
3541
- <div v-if="showModelModal" class="modal-overlay" @click.self="showModelModal = false">
3542
- <div class="modal">
3543
- <div class="modal-title">添加模型</div>
3544
-
3545
- <div class="form-group">
3546
- <label class="form-label">模型名称</label>
3547
- <input v-model="newModelName" class="form-input" placeholder="例如: gpt-5">
3548
- </div>
3549
-
3550
- <div class="btn-group">
3551
- <button class="btn btn-cancel" @click="closeModelModal">取消</button>
3552
- <button class="btn btn-confirm" @click="addModel">添加</button>
3553
- </div>
3554
- </div>
3555
- </div>
3556
-
3557
- <!-- 模型列表模态框 -->
3558
- <div v-if="showModelListModal" class="modal-overlay" @click.self="showModelListModal = false">
3559
- <div class="modal">
3560
- <div class="modal-title">管理模型</div>
3561
-
3562
- <div class="model-list">
3563
- <div v-for="model in models" :key="model" class="model-item">
3564
- <span>{{ model }}</span>
3565
- <span class="btn-remove-model" @click="removeModel(model)">删除</span>
3566
- </div>
3567
- </div>
3568
-
3569
- <div class="btn-group">
3570
- <button class="btn btn-confirm" @click="showModelListModal = false">关闭</button>
3571
- </div>
3572
- </div>
3573
- </div>
3574
-
3575
- <!-- 添加Claude配置模态框 -->
3576
- <div v-if="showClaudeConfigModal" class="modal-overlay" @click.self="showClaudeConfigModal = false">
3577
- <div class="modal">
3578
- <div class="modal-title">添加 Claude Code 配置</div>
3579
-
3580
- <div class="form-group">
3581
- <label class="form-label">配置名称</label>
3582
- <input v-model="newClaudeConfig.name" class="form-input" placeholder="例如: 智谱GLM">
3583
- </div>
3584
- <div class="form-group">
3585
- <label class="form-label">API Key</label>
3586
- <input v-model="newClaudeConfig.apiKey" class="form-input" placeholder="sk-ant-...">
3587
- </div>
3588
- <div class="form-group">
3589
- <label class="form-label">Base URL</label>
3590
- <input v-model="newClaudeConfig.baseUrl" class="form-input" placeholder="https://open.bigmodel.cn/api/anthropic">
3591
- </div>
3592
-
3593
- <div class="btn-group">
3594
- <button class="btn btn-cancel" @click="closeClaudeConfigModal">取消</button>
3595
- <button class="btn btn-confirm" @click="addClaudeConfig">添加</button>
3596
- </div>
3597
- </div>
3598
- </div>
3599
-
3600
- <!-- 编辑Claude配置模态框 -->
3601
- <div v-if="showEditConfigModal" class="modal-overlay" @click.self="closeEditConfigModal">
3602
- <div class="modal">
3603
- <div class="modal-title">编辑 Claude Code 配置</div>
3604
-
3605
- <div class="form-group">
3606
- <label class="form-label">配置名称</label>
3607
- <input v-model="editingConfig.name" class="form-input" placeholder="配置名称" readonly>
3608
- </div>
3609
- <div class="form-group">
3610
- <label class="form-label">API Key</label>
3611
- <input v-model="editingConfig.apiKey" class="form-input" placeholder="sk-ant-...">
3612
- </div>
3613
- <div class="form-group">
3614
- <label class="form-label">Base URL</label>
3615
- <input v-model="editingConfig.baseUrl" class="form-input" placeholder="https://open.bigmodel.cn/api/anthropic">
3616
- </div>
3617
-
3618
- <div class="btn-group">
3619
- <button class="btn btn-cancel" @click="closeEditConfigModal">取消</button>
3620
- <button class="btn btn-confirm" @click="saveAndApplyConfig">保存并应用</button>
3621
- </div>
3622
- </div>
3623
- </div>
3624
-
3625
- <div v-if="showOpenclawConfigModal" class="modal-overlay" @click.self="closeOpenclawConfigModal">
3626
- <div class="modal modal-wide">
3627
- <div class="modal-title">{{ openclawEditorTitle }}</div>
3628
-
3629
- <div class="form-group">
3630
- <label class="form-label">配置名称</label>
3631
- <input v-model="openclawEditing.name" class="form-input" :readonly="openclawEditing.lockName" placeholder="例如: 默认配置">
3632
- </div>
3633
-
3634
- <div class="form-group">
3635
- <label class="form-label">目标文件</label>
3636
- <div class="form-hint">
3637
- {{ openclawConfigPath || '未加载' }}
3638
- <span v-if="openclawConfigPath">
3639
- ({{ openclawConfigExists ? '已存在' : '不存在,将在应用时创建' }})
3640
- </span>
3641
- </div>
3642
- <div class="btn-group" style="justify-content:flex-start;">
3643
- <button class="btn btn-confirm secondary" @click="loadOpenclawConfigFromFile" :disabled="openclawFileLoading">
3644
- {{ openclawFileLoading ? '加载中...' : '加载当前配置' }}
3645
- </button>
3646
- </div>
3647
- </div>
3648
-
3649
- <div class="quick-section">
3650
- <div class="quick-header">
3651
- <div>
3652
- <div class="quick-title">新手快速配置</div>
3653
- <div class="form-hint">按 3 步完成:填 Provider 和模型,写入编辑器,保存并应用。</div>
3654
- </div>
3655
- <div class="quick-actions">
3656
- <button class="btn-mini" @click="syncOpenclawQuickFromText">从编辑器读取</button>
3657
- <button class="btn-mini" @click="resetOpenclawQuick">清空</button>
3658
- </div>
3659
- </div>
3660
- <div class="quick-steps">
3661
- <div class="quick-step"><span class="step-badge">1</span><span>填写 Provider 与模型</span></div>
3662
- <div class="quick-step"><span class="step-badge">2</span><span>点击写入编辑器</span></div>
3663
- <div class="quick-step"><span class="step-badge">3</span><span>保存并应用</span></div>
3664
- </div>
3665
- <div class="quick-grid">
3666
- <div class="quick-card">
3667
- <div class="structured-card-title">Provider</div>
3668
- <div class="form-group">
3669
- <label class="form-label">Provider 名称</label>
3670
- <input v-model="openclawQuick.providerName" class="form-input" placeholder="例如: custom-myapi">
3671
- <div class="form-hint">会拼成 provider/model 作为主模型标识。</div>
3672
- </div>
3673
- <div class="form-group">
3674
- <label class="form-label">Base URL</label>
3675
- <input v-model="openclawQuick.baseUrl" class="form-input" placeholder="https://api.example.com/v1">
3676
- </div>
3677
- <div class="form-group">
3678
- <label class="form-label">API Key</label>
3679
- <div class="list-row">
3680
- <input v-model="openclawQuick.apiKey" class="form-input" :type="openclawQuick.showKey ? 'text' : 'password'" placeholder="sk-...">
3681
- <button class="btn-mini" @click="toggleOpenclawQuickKey">
3682
- {{ openclawQuick.showKey ? '隐藏' : '显示' }}
3683
- </button>
3684
- </div>
3685
- <div class="form-hint">留空表示不覆盖现有 key。</div>
3686
- </div>
3687
- <div class="form-group">
3688
- <label class="form-label">API 类型</label>
3689
- <input v-model="openclawQuick.apiType" class="form-input" list="openclawApiTypeList" placeholder="例如: openai-responses">
3690
- <datalist id="openclawApiTypeList">
3691
- <option value="openai-responses"></option>
3692
- <option value="openai-chat"></option>
3693
- <option value="anthropic"></option>
3694
- <option value="custom"></option>
3695
- </datalist>
3696
- </div>
3697
- </div>
3698
-
3699
- <div class="quick-card">
3700
- <div class="structured-card-title">模型</div>
3701
- <div class="form-group">
3702
- <label class="form-label">模型 ID</label>
3703
- <input v-model="openclawQuick.modelId" class="form-input" placeholder="例如: gpt-4.1">
3704
- </div>
3705
- <div class="form-group">
3706
- <label class="form-label">展示名称</label>
3707
- <input v-model="openclawQuick.modelName" class="form-input" placeholder="留空则使用模型 ID">
3708
- </div>
3709
- <div class="form-group">
3710
- <label class="form-label">上下文与最大输出</label>
3711
- <div class="list-row">
3712
- <input v-model="openclawQuick.contextWindow" class="form-input" placeholder="上下文长度">
3713
- <input v-model="openclawQuick.maxTokens" class="form-input" placeholder="最大输出">
3714
- </div>
3715
- <div class="form-hint">留空表示不改动已有配置。</div>
3716
- </div>
3717
- </div>
3718
-
3719
- <div class="quick-card">
3720
- <div class="structured-card-title">选项</div>
3721
- <label class="quick-option">
3722
- <input type="checkbox" v-model="openclawQuick.setPrimary">
3723
- 设为主模型
3724
- </label>
3725
- <label class="quick-option">
3726
- <input type="checkbox" v-model="openclawQuick.overrideProvider">
3727
- 覆盖同名 Provider 基础信息
3728
- </label>
3729
- <label class="quick-option">
3730
- <input type="checkbox" v-model="openclawQuick.overrideModels">
3731
- 覆盖同名模型列表
3732
- </label>
3733
- <div class="form-hint">关闭覆盖会只补空缺字段。</div>
3734
- </div>
3735
- </div>
3736
- <div class="btn-group">
3737
- <button class="btn btn-confirm" @click="applyOpenclawQuickToText">写入编辑器</button>
3738
- </div>
3739
- </div>
3740
-
3741
- <div class="structured-section">
3742
- <div class="structured-header">
3743
- <span class="structured-title">结构化配置(高级)</span>
3744
- <span class="form-hint">写入编辑器会重排 JSON,注释可能丢失。</span>
3745
- </div>
3746
- <div class="structured-grid">
3747
- <div class="structured-card">
3748
- <div class="structured-card-title">Agents Defaults</div>
3749
- <div class="form-group">
3750
- <label class="form-label">主模型</label>
3751
- <input v-model="openclawStructured.agentPrimary" class="form-input" placeholder="例如: provider/model">
3752
- </div>
3753
- <div class="form-group">
3754
- <label class="form-label">备选模型</label>
3755
- <div class="list-row" v-for="(item, index) in openclawStructured.agentFallbacks" :key="'fallback-' + index">
3756
- <input v-model="openclawStructured.agentFallbacks[index]" class="form-input" placeholder="例如: provider/model">
3757
- <button class="btn-mini delete" @click="removeOpenclawFallback(index)">删除</button>
3758
- </div>
3759
- <button class="btn-mini" @click="addOpenclawFallback">添加备选</button>
3760
- </div>
3761
- <div class="form-group">
3762
- <label class="form-label">Workspace</label>
3763
- <input v-model="openclawStructured.workspace" class="form-input" placeholder="例如: ~/.openclaw/workspace">
3764
- </div>
3765
- <div class="form-group">
3766
- <label class="form-label">Timeout(s)</label>
3767
- <input v-model="openclawStructured.timeout" class="form-input" placeholder="例如: 600">
3768
- </div>
3769
- <div class="form-group">
3770
- <label class="form-label">Context Tokens</label>
3771
- <input v-model="openclawStructured.contextTokens" class="form-input" placeholder="例如: 4096">
3772
- </div>
3773
- <div class="form-group">
3774
- <label class="form-label">Max Concurrent</label>
3775
- <input v-model="openclawStructured.maxConcurrent" class="form-input" placeholder="例如: 2">
3776
- </div>
3777
- </div>
3778
-
3779
- <div class="structured-card">
3780
- <div class="structured-card-title">Env</div>
3781
- <div class="form-group">
3782
- <label class="form-label">环境变量</label>
3783
- <div class="list-row" v-for="(item, index) in openclawStructured.envItems" :key="'env-' + index">
3784
- <input v-model="item.key" class="form-input" placeholder="KEY">
3785
- <input v-model="item.value" class="form-input" :type="item.show ? 'text' : 'password'" placeholder="VALUE">
3786
- <button class="btn-mini" @click="toggleOpenclawEnvItem(index)">
3787
- {{ item.show ? '隐藏' : '显示' }}
3788
- </button>
3789
- <button class="btn-mini delete" @click="removeOpenclawEnvItem(index)">删除</button>
3790
- </div>
3791
- <button class="btn-mini" @click="addOpenclawEnvItem">添加变量</button>
3792
- </div>
3793
- </div>
3794
-
3795
- <div class="structured-card">
3796
- <div class="structured-card-title">Tools</div>
3797
- <div class="form-group">
3798
- <label class="form-label">Profile</label>
3799
- <select v-model="openclawStructured.toolsProfile" class="form-input">
3800
- <option value="default">default</option>
3801
- <option value="strict">strict</option>
3802
- <option value="permissive">permissive</option>
3803
- <option value="custom">custom</option>
3804
- </select>
3805
- </div>
3806
- <div class="form-group">
3807
- <label class="form-label">Allow</label>
3808
- <div class="list-row" v-for="(item, index) in openclawStructured.toolsAllow" :key="'allow-' + index">
3809
- <input v-model="openclawStructured.toolsAllow[index]" class="form-input" placeholder="例如: fs.read*">
3810
- <button class="btn-mini delete" @click="removeOpenclawToolsAllow(index)">删除</button>
3811
- </div>
3812
- <button class="btn-mini" @click="addOpenclawToolsAllow">添加 allow</button>
3813
- </div>
3814
- <div class="form-group">
3815
- <label class="form-label">Deny</label>
3816
- <div class="list-row" v-for="(item, index) in openclawStructured.toolsDeny" :key="'deny-' + index">
3817
- <input v-model="openclawStructured.toolsDeny[index]" class="form-input" placeholder="例如: net.*">
3818
- <button class="btn-mini delete" @click="removeOpenclawToolsDeny(index)">删除</button>
3819
- </div>
3820
- <button class="btn-mini" @click="addOpenclawToolsDeny">添加 deny</button>
3821
- </div>
3822
- </div>
3823
-
3824
- <div class="structured-card">
3825
- <div class="structured-card-title">Providers(只读)</div>
3826
- <div v-if="openclawProviders.length === 0" class="form-hint">
3827
- 未发现 providers 配置(可能使用内置 provider 或 auth profiles)。
3828
- </div>
3829
- <div v-else class="provider-list">
3830
- <div class="provider-item" v-for="(provider, index) in openclawProviders" :key="provider.key + '-' + provider.source + '-' + index">
3831
- <div class="provider-header">
3832
- <span class="provider-name">{{ provider.key }}</span>
3833
- <span class="provider-source">来源: {{ provider.source }}</span>
3834
- <span v-if="provider.isActive" class="pill configured">使用中</span>
3835
- </div>
3836
- <div v-if="provider.fields.length === 0" class="form-hint">未配置字段</div>
3837
- <div v-else class="provider-fields">
3838
- <div class="provider-field" v-for="field in provider.fields" :key="provider.key + '-' + field.key">
3839
- <span class="provider-field-key">{{ field.key }}</span>
3840
- <span class="provider-field-value">{{ field.value }}</span>
3841
- </div>
3842
- </div>
3843
- </div>
3844
- </div>
3845
- <div v-if="openclawMissingProviders.length" class="form-hint">
3846
- 使用中的 provider 未在配置中显示:{{ openclawMissingProviders.join(', ') }}。
3847
- </div>
3848
- </div>
3849
-
3850
- <div class="structured-card">
3851
- <div class="structured-card-title">Agents(只读)</div>
3852
- <div v-if="openclawAgentsList.length === 0" class="form-hint">
3853
- 未发现 agents.list 配置。
3854
- </div>
3855
- <div v-else class="agent-list">
3856
- <div class="agent-item" v-for="(agent, index) in openclawAgentsList" :key="agent.key + '-' + index">
3857
- <div class="agent-header">
3858
- <span class="agent-name">{{ agent.name }}</span>
3859
- <span class="agent-id">ID: {{ agent.id }}</span>
3860
- </div>
3861
- <div class="agent-meta" v-if="agent.theme || agent.emoji || agent.avatar">
3862
- <span v-if="agent.theme">主题: {{ agent.theme }}</span>
3863
- <span v-if="agent.emoji">表情: {{ agent.emoji }}</span>
3864
- <span v-if="agent.avatar">头像: {{ agent.avatar }}</span>
3865
- </div>
3866
- </div>
3867
- </div>
3868
- </div>
3869
- </div>
3870
- <div class="btn-group">
3871
- <button class="btn btn-confirm secondary" @click="syncOpenclawStructuredFromText">从文本刷新</button>
3872
- <button class="btn btn-confirm" @click="applyOpenclawStructuredToText">写入编辑器</button>
3873
- </div>
3874
- </div>
3875
-
3876
- <div class="form-group">
3877
- <label class="form-label">OpenClaw 配置(JSON5)</label>
3878
- <textarea
3879
- v-model="openclawEditing.content"
3880
- class="form-input template-editor"
3881
- spellcheck="false"
3882
- placeholder="在这里编辑 OpenClaw 配置(JSON5)"></textarea>
3883
- <div class="template-editor-warning">
3884
- 保存仅写入本地配置库。点击“保存并应用”后会写入 openclaw.json。
3885
- </div>
3886
- </div>
3887
-
3888
- <div class="btn-group">
3889
- <button class="btn btn-cancel" @click="closeOpenclawConfigModal">取消</button>
3890
- <button class="btn btn-confirm" @click="saveOpenclawConfig" :disabled="openclawSaving">
3891
- {{ openclawSaving ? '保存中...' : '保存' }}
3892
- </button>
3893
- <button class="btn btn-confirm secondary" @click="saveAndApplyOpenclawConfig" :disabled="openclawApplying">
3894
- {{ openclawApplying ? '应用中...' : '保存并应用' }}
3895
- </button>
3896
- </div>
3897
- </div>
3898
- </div>
3899
-
3900
- <div v-if="showConfigTemplateModal" class="modal-overlay" @click.self="closeConfigTemplateModal">
3901
- <div class="modal modal-wide">
3902
- <div class="modal-title">Config 模板编辑器(手动确认应用)</div>
3903
-
3904
- <div class="form-group">
3905
- <label class="form-label">config.toml 模板</label>
3906
- <textarea
3907
- v-model="configTemplateContent"
3908
- class="form-input template-editor"
3909
- spellcheck="false"
3910
- placeholder="在这里编辑 config.toml 模板内容"></textarea>
3911
- <div class="template-editor-warning">
3912
- 工具不会自动改动 `config.toml`。只有点击“确认应用模板”后才写入。
3913
- </div>
3914
- </div>
3915
-
3916
- <div class="btn-group">
3917
- <button class="btn btn-cancel" @click="closeConfigTemplateModal">取消</button>
3918
- <button class="btn btn-confirm" @click="applyConfigTemplate" :disabled="configTemplateApplying">
3919
- {{ configTemplateApplying ? '应用中...' : '确认应用模板' }}
3920
- </button>
3921
- </div>
3922
- </div>
3923
- </div>
3924
-
3925
- <div v-if="showAgentsModal" class="modal-overlay" @click.self="closeAgentsModal">
3926
- <div class="modal modal-wide">
3927
- <div class="modal-header">
3928
- <div class="modal-title">{{ agentsModalTitle }}</div>
3929
- <button
3930
- class="btn-mini btn-modal-copy"
3931
- @click="copyAgentsContent"
3932
- :disabled="agentsLoading">
3933
- 复制
3934
- </button>
3935
- </div>
3936
-
3937
- <div class="form-group">
3938
- <label class="form-label">目标文件</label>
3939
- <div class="form-hint">
3940
- {{ agentsPath || '未加载' }}
3941
- <span v-if="agentsPath">
3942
- ({{ agentsExists ? '已存在' : '不存在,将在保存时创建' }})
3943
- </span>
3944
- </div>
3945
- </div>
3946
-
3947
- <div class="form-group">
3948
- <label class="form-label">AGENTS.md 内容</label>
3949
- <textarea
3950
- v-model="agentsContent"
3951
- class="form-input template-editor"
3952
- spellcheck="false"
3953
- :readonly="agentsLoading"
3954
- placeholder="在这里编辑 AGENTS.md 内容"></textarea>
3955
- <div class="template-editor-warning">
3956
- {{ agentsModalHint }}
3957
- </div>
3958
- </div>
3959
-
3960
- <div class="btn-group">
3961
- <button class="btn btn-cancel" @click="closeAgentsModal">取消</button>
3962
- <button class="btn btn-confirm" @click="applyAgentsContent" :disabled="agentsSaving || agentsLoading">
3963
- {{ agentsSaving ? '保存中...' : '保存' }}
3964
- </button>
3965
- </div>
3966
- </div>
3967
- </div>
3968
-
3969
- <!-- Toast通知 -->
3970
-
3971
- <!-- Toast -->
3972
- <div v-if="message" :class="['toast', messageType]">{{ message }}</div>
3973
- </div>
3974
-
3975
- <script type="module" src="web-ui/app.js"></script>
3976
- </body>
3977
- </html>