codexmate 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +429 -0
- package/README.md +241 -203
- package/cli.js +10210 -0
- package/doc/CHANGELOG.md +14 -1
- package/doc/CHANGELOG.zh-CN.md +13 -0
- package/lib/cli-utils.js +16 -0
- package/lib/mcp-stdio.js +440 -0
- package/lib/workflow-engine.js +340 -0
- package/package.json +63 -53
- package/web-ui/app.js +1417 -14
- package/web-ui/index.html +585 -67
- package/web-ui/logic.mjs +147 -1
- package/web-ui/styles.css +1049 -66
- package/README.zh-CN.md +0 -397
- package/src/cli.js +0 -5464
- package/src/lib/cli-file-utils.js +0 -151
- package/src/lib/cli-models-utils.js +0 -152
- package/src/lib/cli-network-utils.js +0 -148
- package/src/lib/cli-session-utils.js +0 -121
- package/src/lib/cli-utils.js +0 -139
- package/src/res/json5.min.js +0 -1
- package/src/res/logo.png +0 -0
- package/src/res/screenshot.png +0 -0
- package/src/res/vue.global.js +0 -18552
- package/src/web-ui/app.js +0 -2970
- package/src/web-ui/index.html +0 -1310
- package/src/web-ui/logic.mjs +0 -157
- package/src/web-ui/styles.css +0 -2868
- /package/{src/web-ui.html → web-ui.html} +0 -0
package/web-ui/index.html
CHANGED
|
@@ -13,7 +13,12 @@
|
|
|
13
13
|
<body>
|
|
14
14
|
<div id="app" class="container" v-cloak>
|
|
15
15
|
<button class="fab-install" @click="openInstallModal" aria-label="安装 CLI">
|
|
16
|
-
|
|
16
|
+
<span class="fab-install-icon" aria-hidden="true">
|
|
17
|
+
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
18
|
+
<path d="M10 4v8M6.5 8.5 10 12l3.5-3.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
19
|
+
<path d="M4 14.5h12" stroke-linecap="round"/>
|
|
20
|
+
</svg>
|
|
21
|
+
</span>
|
|
17
22
|
</button>
|
|
18
23
|
|
|
19
24
|
<div class="hero" v-if="!sessionStandalone">
|
|
@@ -26,6 +31,26 @@
|
|
|
26
31
|
<span class="sr-only">本地配置中枢,统一管理 Codex / Claude Code / OpenClaw / 会话。</span>
|
|
27
32
|
</div>
|
|
28
33
|
</div>
|
|
34
|
+
<div v-if="!sessionStandalone" class="hero-github">
|
|
35
|
+
<a
|
|
36
|
+
class="github-badge github-badge-mobile"
|
|
37
|
+
href="https://github.com/SakuraByteCore/codexmate"
|
|
38
|
+
target="_blank"
|
|
39
|
+
rel="noopener"
|
|
40
|
+
title="打开 GitHub 仓库">
|
|
41
|
+
<span class="github-badge-left">
|
|
42
|
+
<svg class="github-badge-icon" viewBox="0 0 24 24" aria-hidden="true">
|
|
43
|
+
<path fill="currentColor" d="M12 2C6.48 2 2 6.58 2 12.26c0 4.5 2.87 8.32 6.84 9.67.5.1.68-.22.68-.49 0-.24-.01-.88-.01-1.73-2.78.62-3.37-1.38-3.37-1.38-.45-1.19-1.11-1.5-1.11-1.5-.91-.64.07-.63.07-.63 1 .07 1.53 1.06 1.53 1.06.9 1.57 2.36 1.12 2.94.86.09-.66.35-1.12.63-1.38-2.22-.26-4.56-1.14-4.56-5.09 0-1.13.39-2.06 1.03-2.79-.1-.26-.45-1.31.1-2.73 0 0 .84-.28 2.75 1.06A9.36 9.36 0 0 1 12 6.8c.85 0 1.7.12 2.5.34 1.9-1.34 2.74-1.06 2.74-1.06.55 1.42.2 2.47.1 2.73.64.73 1.03 1.66 1.03 2.79 0 3.96-2.35 4.83-4.58 5.08.36.32.68.95.68 1.92 0 1.38-.01 2.5-.01 2.84 0 .27.18.59.69.49A10.04 10.04 0 0 0 22 12.26C22 6.58 17.52 2 12 2z"/>
|
|
44
|
+
</svg>
|
|
45
|
+
<span class="github-badge-label">GitHub</span>
|
|
46
|
+
</span>
|
|
47
|
+
<span class="github-badge-text" title="SakuraByteCore/codexmate">
|
|
48
|
+
<span class="github-owner">SakuraByteCore</span>
|
|
49
|
+
<span class="github-sep">/</span>
|
|
50
|
+
<span class="github-repo">codexmate</span>
|
|
51
|
+
</span>
|
|
52
|
+
</a>
|
|
53
|
+
</div>
|
|
29
54
|
|
|
30
55
|
<div v-if="!sessionStandalone" class="top-tabs" role="tablist" aria-label="主导航">
|
|
31
56
|
<button class="top-tab"
|
|
@@ -64,6 +89,15 @@
|
|
|
64
89
|
aria-controls="panel-sessions"
|
|
65
90
|
:class="{ active: mainTab === 'sessions' }"
|
|
66
91
|
@click="switchMainTab('sessions')">会话浏览</button>
|
|
92
|
+
<button class="top-tab"
|
|
93
|
+
id="tab-settings"
|
|
94
|
+
role="tab"
|
|
95
|
+
:tabindex="mainTab === 'settings' ? 0 : -1"
|
|
96
|
+
:aria-selected="mainTab === 'settings'"
|
|
97
|
+
:aria-pressed="mainTab === 'settings'"
|
|
98
|
+
aria-controls="panel-settings"
|
|
99
|
+
:class="{ active: mainTab === 'settings' }"
|
|
100
|
+
@click="switchMainTab('settings')">设置</button>
|
|
67
101
|
</div>
|
|
68
102
|
|
|
69
103
|
<div :class="['app-shell', { standalone: sessionStandalone }]">
|
|
@@ -78,6 +112,24 @@
|
|
|
78
112
|
<div class="brand-subtitle">
|
|
79
113
|
配置 / 会话切换器
|
|
80
114
|
</div>
|
|
115
|
+
<a
|
|
116
|
+
class="github-badge github-badge-rail"
|
|
117
|
+
href="https://github.com/SakuraByteCore/codexmate"
|
|
118
|
+
target="_blank"
|
|
119
|
+
rel="noopener"
|
|
120
|
+
title="打开 GitHub 仓库">
|
|
121
|
+
<span class="github-badge-left">
|
|
122
|
+
<svg class="github-badge-icon" viewBox="0 0 24 24" aria-hidden="true">
|
|
123
|
+
<path fill="currentColor" d="M12 2C6.48 2 2 6.58 2 12.26c0 4.5 2.87 8.32 6.84 9.67.5.1.68-.22.68-.49 0-.24-.01-.88-.01-1.73-2.78.62-3.37-1.38-3.37-1.38-.45-1.19-1.11-1.5-1.11-1.5-.91-.64.07-.63.07-.63 1 .07 1.53 1.06 1.53 1.06.9 1.57 2.36 1.12 2.94.86.09-.66.35-1.12.63-1.38-2.22-.26-4.56-1.14-4.56-5.09 0-1.13.39-2.06 1.03-2.79-.1-.26-.45-1.31.1-2.73 0 0 .84-.28 2.75 1.06A9.36 9.36 0 0 1 12 6.8c.85 0 1.7.12 2.5.34 1.9-1.34 2.74-1.06 2.74-1.06.55 1.42.2 2.47.1 2.73.64.73 1.03 1.66 1.03 2.79 0 3.96-2.35 4.83-4.58 5.08.36.32.68.95.68 1.92 0 1.38-.01 2.5-.01 2.84 0 .27.18.59.69.49A10.04 10.04 0 0 0 22 12.26C22 6.58 17.52 2 12 2z"/>
|
|
124
|
+
</svg>
|
|
125
|
+
<span class="github-badge-label">GitHub</span>
|
|
126
|
+
</span>
|
|
127
|
+
<span class="github-badge-text" title="SakuraByteCore/codexmate">
|
|
128
|
+
<span class="github-owner">SakuraByteCore</span>
|
|
129
|
+
<span class="github-sep">/</span>
|
|
130
|
+
<span class="github-repo">codexmate</span>
|
|
131
|
+
</span>
|
|
132
|
+
</a>
|
|
81
133
|
</div>
|
|
82
134
|
|
|
83
135
|
<div class="side-section" role="tablist" aria-label="配置管理">
|
|
@@ -147,17 +199,35 @@
|
|
|
147
199
|
</div>
|
|
148
200
|
</button>
|
|
149
201
|
</div>
|
|
202
|
+
|
|
203
|
+
<div class="side-section" role="tablist" aria-label="设置">
|
|
204
|
+
<div class="side-section-title">设置</div>
|
|
205
|
+
<button
|
|
206
|
+
role="tab"
|
|
207
|
+
id="side-tab-settings"
|
|
208
|
+
aria-controls="panel-settings"
|
|
209
|
+
:tabindex="mainTab === 'settings' ? 0 : -1"
|
|
210
|
+
:aria-selected="mainTab === 'settings'"
|
|
211
|
+
:aria-pressed="mainTab === 'settings'"
|
|
212
|
+
:class="['side-item', { active: mainTab === 'settings' }]"
|
|
213
|
+
@click="switchMainTab('settings')">
|
|
214
|
+
<div class="side-item-title">设置</div>
|
|
215
|
+
<div class="side-item-meta">
|
|
216
|
+
<span>数据管理 / 下载</span>
|
|
217
|
+
</div>
|
|
218
|
+
</button>
|
|
219
|
+
</div>
|
|
150
220
|
</aside>
|
|
151
221
|
<main class="main-panel">
|
|
152
222
|
<div class="panel-header" v-if="!sessionStandalone">
|
|
153
223
|
<h1 class="main-title">
|
|
154
|
-
{{ mainTab === 'config' ? '配置中心' : '会话浏览' }}
|
|
224
|
+
{{ mainTab === 'config' ? '配置中心' : (mainTab === 'sessions' ? '会话浏览' : '设置') }}
|
|
155
225
|
</h1>
|
|
156
226
|
<p class="subtitle" v-if="mainTab === 'config'">
|
|
157
227
|
配置中枢:管理 Codex / Claude / OpenClaw
|
|
158
228
|
<span class="sr-only">本地配置中枢,统一管理 Codex / Claude Code / OpenClaw。</span>
|
|
159
229
|
</p>
|
|
160
|
-
<p class="subtitle" v-else>
|
|
230
|
+
<p class="subtitle" v-else-if="mainTab === 'sessions'">
|
|
161
231
|
浏览、导出或独立查看 Codex / Claude 会话记录。
|
|
162
232
|
</p>
|
|
163
233
|
</div>
|
|
@@ -318,10 +388,11 @@
|
|
|
318
388
|
|
|
319
389
|
<div class="selector-section">
|
|
320
390
|
<div class="selector-header">
|
|
321
|
-
<span class="selector-title"
|
|
391
|
+
<span class="selector-title">Skills 管理</span>
|
|
322
392
|
</div>
|
|
323
|
-
<
|
|
324
|
-
|
|
393
|
+
<div class="config-template-hint skills-hint-line">管理 <code>~/.codex/skills</code> 自定义 skills,弹窗提供统计概览、筛选检索、多选删除与跨应用导入。</div>
|
|
394
|
+
<button class="btn-tool" @click="openSkillsManager" :disabled="loading || !!initError || skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting">
|
|
395
|
+
{{ skillsLoading ? '加载中...' : '打开 Skills 管理' }}
|
|
325
396
|
</button>
|
|
326
397
|
</div>
|
|
327
398
|
|
|
@@ -335,6 +406,112 @@
|
|
|
335
406
|
</button>
|
|
336
407
|
</div>
|
|
337
408
|
|
|
409
|
+
<div class="selector-section">
|
|
410
|
+
<div class="selector-header">
|
|
411
|
+
<span class="selector-title">Codex 认证文件</span>
|
|
412
|
+
</div>
|
|
413
|
+
<div class="config-template-hint hint-single-line" title="上传 JSON 切换账号(写入 ~/.codex/auth.json)。">
|
|
414
|
+
上传 JSON 切换账号(写入 <code>~/.codex/auth.json</code>)。
|
|
415
|
+
</div>
|
|
416
|
+
<button class="btn-tool" @click="triggerCodexAuthUpload" :disabled="codexAuthImportLoading || loading || !!initError">
|
|
417
|
+
{{ codexAuthImportLoading ? '上传中...' : '上传认证文件' }}
|
|
418
|
+
</button>
|
|
419
|
+
<input
|
|
420
|
+
ref="codexAuthImportInput"
|
|
421
|
+
type="file"
|
|
422
|
+
accept=".json,application/json"
|
|
423
|
+
style="display:none"
|
|
424
|
+
@change="handleCodexAuthImportChange">
|
|
425
|
+
<div v-if="codexAuthProfiles.length === 0" class="form-hint">暂无认证文件。</div>
|
|
426
|
+
<div v-else class="auth-profile-list">
|
|
427
|
+
<div class="auth-profile-item" v-for="profile in codexAuthProfiles" :key="'auth-' + profile.name">
|
|
428
|
+
<div class="auth-profile-header">
|
|
429
|
+
<div class="auth-profile-main">
|
|
430
|
+
<div class="auth-profile-title">{{ profile.name }}</div>
|
|
431
|
+
<div class="auth-profile-meta">
|
|
432
|
+
<span class="provider-source">{{ profile.type || 'unknown' }}</span>
|
|
433
|
+
<span :class="['pill', profile.current ? 'configured' : 'empty']">
|
|
434
|
+
{{ profile.current ? '当前' : '备用' }}
|
|
435
|
+
</span>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
<div class="auth-profile-actions">
|
|
439
|
+
<button
|
|
440
|
+
class="btn-mini"
|
|
441
|
+
:disabled="profile.current || codexAuthSwitching[profile.name]"
|
|
442
|
+
@click="switchCodexAuthProfile(profile.name)">
|
|
443
|
+
{{ codexAuthSwitching[profile.name] ? '切换中...' : (profile.current ? '当前使用' : '切换账号') }}
|
|
444
|
+
</button>
|
|
445
|
+
<button
|
|
446
|
+
class="btn-mini delete"
|
|
447
|
+
:disabled="codexAuthDeleting[profile.name]"
|
|
448
|
+
@click="deleteCodexAuthProfile(profile.name)">
|
|
449
|
+
{{ codexAuthDeleting[profile.name] ? '删除中...' : '删除' }}
|
|
450
|
+
</button>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
<div class="auth-profile-grid">
|
|
454
|
+
<div v-if="profile.email" class="auth-profile-row">
|
|
455
|
+
<span class="auth-profile-key">email</span>
|
|
456
|
+
<span class="auth-profile-value">{{ profile.email }}</span>
|
|
457
|
+
</div>
|
|
458
|
+
<div v-if="profile.accountId" class="auth-profile-row">
|
|
459
|
+
<span class="auth-profile-key">account_id</span>
|
|
460
|
+
<span class="auth-profile-value">{{ profile.accountId }}</span>
|
|
461
|
+
</div>
|
|
462
|
+
<div v-if="profile.expired" class="auth-profile-row">
|
|
463
|
+
<span class="auth-profile-key">expired</span>
|
|
464
|
+
<span class="auth-profile-value">{{ profile.expired }}</span>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
|
|
471
|
+
<div class="selector-section">
|
|
472
|
+
<div class="selector-header">
|
|
473
|
+
<span class="selector-title">内建代理</span>
|
|
474
|
+
</div>
|
|
475
|
+
<button class="btn-mini" @click="showProxyAdvanced = !showProxyAdvanced">
|
|
476
|
+
{{ showProxyAdvanced ? '收起高级设置' : '展开高级设置(端口/鉴权)' }}
|
|
477
|
+
</button>
|
|
478
|
+
<div v-if="showProxyAdvanced">
|
|
479
|
+
<div class="list-row">
|
|
480
|
+
<label class="form-label">上游 Provider</label>
|
|
481
|
+
<select class="form-input" v-model="proxySettings.provider" @change="saveProxySettings({ silent: true })">
|
|
482
|
+
<option value="">自动(当前 provider)</option>
|
|
483
|
+
<option v-for="name in proxyProviderOptions" :key="'proxy-provider-' + name" :value="name">{{ name }}</option>
|
|
484
|
+
</select>
|
|
485
|
+
<label class="form-label">端口</label>
|
|
486
|
+
<input v-model.number="proxySettings.port" class="form-input" type="number" min="1" max="65535" placeholder="8318" @change="saveProxySettings({ silent: true })">
|
|
487
|
+
</div>
|
|
488
|
+
<div class="list-row">
|
|
489
|
+
<label class="form-label">鉴权来源</label>
|
|
490
|
+
<select class="form-input" v-model="proxySettings.authSource" @change="saveProxySettings({ silent: true })">
|
|
491
|
+
<option value="provider">provider 优先</option>
|
|
492
|
+
<option value="profile">仅当前认证文件</option>
|
|
493
|
+
<option value="none">不注入鉴权头</option>
|
|
494
|
+
</select>
|
|
495
|
+
<label class="form-label">超时(ms)</label>
|
|
496
|
+
<input v-model.number="proxySettings.timeoutMs" class="form-input" type="number" min="1000" step="500" placeholder="30000" @change="saveProxySettings({ silent: true })">
|
|
497
|
+
</div>
|
|
498
|
+
<div class="form-hint">高级参数修改后自动保存。</div>
|
|
499
|
+
</div>
|
|
500
|
+
<div class="config-template-hint" v-if="proxyRuntime">
|
|
501
|
+
运行中:入口 <code>{{ proxyRuntimeDisplayProvider }}</code>
|
|
502
|
+
<span v-if="proxyRuntime.upstreamProvider">(上游 <code>{{ proxyRuntime.upstreamProvider }}</code>)</span>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<div class="selector-section">
|
|
507
|
+
<div class="selector-header">
|
|
508
|
+
<span class="selector-title">配置健康检查</span>
|
|
509
|
+
</div>
|
|
510
|
+
<button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">
|
|
511
|
+
{{ healthCheckLoading ? '检查中...' : '运行检查' }}
|
|
512
|
+
</button>
|
|
513
|
+
</div>
|
|
514
|
+
|
|
338
515
|
<div v-if="!loading && !initError" class="card-list">
|
|
339
516
|
<div v-for="provider in providersList" :key="provider.name"
|
|
340
517
|
:class="['card', { active: currentProvider === provider.name }]"
|
|
@@ -342,13 +519,18 @@
|
|
|
342
519
|
<div class="card-leading">
|
|
343
520
|
<div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}</div>
|
|
344
521
|
<div class="card-content">
|
|
345
|
-
<div class="card-title">
|
|
346
|
-
|
|
522
|
+
<div class="card-title">
|
|
523
|
+
<span>{{ provider.name }}</span>
|
|
524
|
+
<span v-if="provider.readOnly" class="provider-readonly-badge">内建</span>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="card-subtitle">
|
|
527
|
+
{{ provider.readOnly ? '系统内建本地代理(自动维护)' : (provider.url || '未设置 URL') }}
|
|
528
|
+
</div>
|
|
347
529
|
</div>
|
|
348
530
|
</div>
|
|
349
531
|
<div class="card-trailing">
|
|
350
|
-
<span :class="['pill', provider
|
|
351
|
-
{{ provider
|
|
532
|
+
<span :class="['pill', providerPillConfigured(provider) ? 'configured' : 'empty']">
|
|
533
|
+
{{ providerPillText(provider) }}
|
|
352
534
|
</span>
|
|
353
535
|
<span v-if="speedResults[provider.name]" :class="['latency', speedResults[provider.name].ok ? 'ok' : 'error']">
|
|
354
536
|
{{ formatLatency(speedResults[provider.name]) }}
|
|
@@ -359,20 +541,38 @@
|
|
|
359
541
|
<path d="M13 2L3 14h7l-1 8 12-14h-7l-1-6z"/>
|
|
360
542
|
</svg>
|
|
361
543
|
</button>
|
|
362
|
-
<button
|
|
544
|
+
<button
|
|
545
|
+
v-if="!provider.readOnly"
|
|
546
|
+
class="card-action-btn"
|
|
547
|
+
:class="{ loading: providerShareLoading[provider.name], disabled: !shouldAllowProviderShare(provider) }"
|
|
548
|
+
:disabled="!shouldAllowProviderShare(provider)"
|
|
549
|
+
@click="copyProviderShareCommand(provider)"
|
|
550
|
+
:title="shouldAllowProviderShare(provider) ? '分享导入命令' : '本地入口不可分享'">
|
|
363
551
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
364
552
|
<path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
|
|
365
553
|
<path d="M16 6l-4-4-4 4"/>
|
|
366
554
|
<path d="M12 2v14"/>
|
|
367
555
|
</svg>
|
|
368
556
|
</button>
|
|
369
|
-
<button
|
|
557
|
+
<button
|
|
558
|
+
v-if="!provider.readOnly"
|
|
559
|
+
class="card-action-btn"
|
|
560
|
+
:class="{ disabled: !shouldShowProviderEdit(provider) }"
|
|
561
|
+
:disabled="!shouldShowProviderEdit(provider)"
|
|
562
|
+
@click="openEditModal(provider)"
|
|
563
|
+
:title="shouldShowProviderEdit(provider) ? '编辑' : '不可编辑'">
|
|
370
564
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
371
565
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
372
566
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
373
567
|
</svg>
|
|
374
568
|
</button>
|
|
375
|
-
<button
|
|
569
|
+
<button
|
|
570
|
+
v-if="!provider.readOnly"
|
|
571
|
+
class="card-action-btn delete"
|
|
572
|
+
:class="{ disabled: !shouldShowProviderDelete(provider) }"
|
|
573
|
+
:disabled="!shouldShowProviderDelete(provider)"
|
|
574
|
+
@click="deleteProvider(provider.name)"
|
|
575
|
+
:title="shouldShowProviderDelete(provider) ? '删除' : '不可删除'">
|
|
376
576
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
377
577
|
<path d="M3 6h18"/>
|
|
378
578
|
<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"/>
|
|
@@ -698,10 +898,10 @@
|
|
|
698
898
|
</div>
|
|
699
899
|
</div>
|
|
700
900
|
|
|
701
|
-
<div :class="['session-preview', { active: !!activeSession }]">
|
|
901
|
+
<div :class="['session-preview', { active: !!activeSession }]" :ref="setSessionPreviewContainerRef">
|
|
702
902
|
<template v-if="activeSession">
|
|
703
|
-
<div class="session-preview-scroll">
|
|
704
|
-
<div class="session-preview-header">
|
|
903
|
+
<div class="session-preview-scroll" :ref="setSessionPreviewScrollRef" @scroll="onSessionPreviewScroll">
|
|
904
|
+
<div class="session-preview-header" :ref="setSessionPreviewHeaderRef">
|
|
705
905
|
<div>
|
|
706
906
|
<div class="session-preview-title">{{ activeSession.title || activeSession.sessionId }}</div>
|
|
707
907
|
<div class="session-preview-meta">
|
|
@@ -716,13 +916,6 @@
|
|
|
716
916
|
<button class="btn-session-refresh" @click="loadActiveSessionDetail" :disabled="sessionDetailLoading || !activeSession">
|
|
717
917
|
{{ sessionDetailLoading ? '加载中...' : '刷新内容' }}
|
|
718
918
|
</button>
|
|
719
|
-
<button
|
|
720
|
-
v-if="isCloneAvailable(activeSession)"
|
|
721
|
-
class="btn-session-clone"
|
|
722
|
-
@click="cloneSession(activeSession)"
|
|
723
|
-
:disabled="!activeSession || sessionsLoading || sessionCloning[getSessionExportKey(activeSession)]">
|
|
724
|
-
{{ (activeSession && sessionCloning[getSessionExportKey(activeSession)]) ? '克隆中...' : '克隆会话' }}
|
|
725
|
-
</button>
|
|
726
919
|
<button
|
|
727
920
|
v-if="isDeleteAvailable(activeSession)"
|
|
728
921
|
class="btn-session-delete"
|
|
@@ -759,23 +952,44 @@
|
|
|
759
952
|
</div>
|
|
760
953
|
|
|
761
954
|
<div v-else class="session-preview-body">
|
|
762
|
-
<div
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
955
|
+
<div class="session-preview-messages">
|
|
956
|
+
<div v-if="activeSessionDetailClipped" class="session-item-sub session-item-wrap">
|
|
957
|
+
仅展示最近 {{ activeSessionMessages.length }} 条消息。
|
|
958
|
+
</div>
|
|
959
|
+
<div
|
|
960
|
+
v-for="(msg, idx) in activeSessionMessages"
|
|
961
|
+
:key="getRecordRenderKey(msg, idx)"
|
|
962
|
+
:data-message-key="getRecordRenderKey(msg, idx)"
|
|
963
|
+
:ref="(el) => bindSessionMessageRef(getRecordRenderKey(msg, idx), el)"
|
|
964
|
+
:class="['session-msg', msg.normalizedRole === 'user' ? 'user' : (msg.normalizedRole === 'system' ? 'system' : 'assistant')]">
|
|
965
|
+
<div class="session-msg-header">
|
|
966
|
+
<div class="session-msg-meta">
|
|
967
|
+
<span class="session-msg-role">{{ msg.roleLabel || (msg.normalizedRole === 'user' ? 'User' : (msg.normalizedRole === 'system' ? 'System' : 'Assistant')) }}</span>
|
|
968
|
+
<span class="session-msg-time">{{ msg.timestamp || '' }}</span>
|
|
969
|
+
</div>
|
|
773
970
|
</div>
|
|
971
|
+
<div class="session-msg-content">{{ msg.text || '' }}</div>
|
|
774
972
|
</div>
|
|
775
|
-
<div class="session-msg-content">{{ msg.text || '' }}</div>
|
|
776
973
|
</div>
|
|
777
974
|
</div>
|
|
778
975
|
</div>
|
|
976
|
+
<aside v-if="sessionTimelineNodes.length" class="session-timeline" aria-label="会话时间轴">
|
|
977
|
+
<div class="session-timeline-track"></div>
|
|
978
|
+
<button
|
|
979
|
+
v-for="node in sessionTimelineNodes"
|
|
980
|
+
:key="'timeline-' + node.key"
|
|
981
|
+
type="button"
|
|
982
|
+
:class="['session-timeline-node', { active: sessionTimelineActiveKey === node.key }]"
|
|
983
|
+
:aria-current="sessionTimelineActiveKey === node.key ? 'true' : null"
|
|
984
|
+
:style="{ top: `${node.safePercent}%` }"
|
|
985
|
+
:title="node.title"
|
|
986
|
+
@click="jumpToSessionTimelineNode(node.key)">
|
|
987
|
+
<span class="sr-only">{{ node.title }}</span>
|
|
988
|
+
</button>
|
|
989
|
+
<div class="session-timeline-current" v-if="sessionTimelineActiveTitle">
|
|
990
|
+
{{ sessionTimelineActiveTitle }}
|
|
991
|
+
</div>
|
|
992
|
+
</aside>
|
|
779
993
|
</template>
|
|
780
994
|
|
|
781
995
|
<div v-else class="session-preview-empty">
|
|
@@ -787,6 +1001,49 @@
|
|
|
787
1001
|
</div>
|
|
788
1002
|
</div>
|
|
789
1003
|
|
|
1004
|
+
<!-- 设置面板 -->
|
|
1005
|
+
<div
|
|
1006
|
+
v-show="mainTab === 'settings'"
|
|
1007
|
+
class="mode-content"
|
|
1008
|
+
id="panel-settings"
|
|
1009
|
+
role="tabpanel"
|
|
1010
|
+
:aria-labelledby="'tab-settings'">
|
|
1011
|
+
<div class="selector-section">
|
|
1012
|
+
<div class="selector-header">
|
|
1013
|
+
<span class="selector-title">Claude 配置</span>
|
|
1014
|
+
</div>
|
|
1015
|
+
<button class="btn-tool" @click="downloadClaudeDirectory" :disabled="claudeDownloadLoading">
|
|
1016
|
+
{{ claudeDownloadLoading ? ('备份中 ' + claudeDownloadProgress + '%') : '一键备份 ~/.claude' }}
|
|
1017
|
+
</button>
|
|
1018
|
+
<button class="btn-tool" @click="triggerClaudeImport" :disabled="claudeImportLoading">
|
|
1019
|
+
{{ claudeImportLoading ? '导入中...' : '导入 ~/.claude 备份' }}
|
|
1020
|
+
</button>
|
|
1021
|
+
<input
|
|
1022
|
+
ref="claudeImportInput"
|
|
1023
|
+
class="sr-only"
|
|
1024
|
+
type="file"
|
|
1025
|
+
accept=".zip"
|
|
1026
|
+
@change="handleClaudeImportChange">
|
|
1027
|
+
</div>
|
|
1028
|
+
<div class="selector-section">
|
|
1029
|
+
<div class="selector-header">
|
|
1030
|
+
<span class="selector-title">Codex 配置</span>
|
|
1031
|
+
</div>
|
|
1032
|
+
<button class="btn-tool" @click="downloadCodexDirectory" :disabled="codexDownloadLoading">
|
|
1033
|
+
{{ codexDownloadLoading ? ('备份中 ' + codexDownloadProgress + '%') : '一键备份 ~/.codex' }}
|
|
1034
|
+
</button>
|
|
1035
|
+
<button class="btn-tool" @click="triggerCodexImport" :disabled="codexImportLoading">
|
|
1036
|
+
{{ codexImportLoading ? '导入中...' : '导入 ~/.codex 备份' }}
|
|
1037
|
+
</button>
|
|
1038
|
+
<input
|
|
1039
|
+
ref="codexImportInput"
|
|
1040
|
+
class="sr-only"
|
|
1041
|
+
type="file"
|
|
1042
|
+
accept=".zip"
|
|
1043
|
+
@change="handleCodexImportChange">
|
|
1044
|
+
</div>
|
|
1045
|
+
</div>
|
|
1046
|
+
|
|
790
1047
|
<!-- 加载状态 -->
|
|
791
1048
|
<div v-if="loading" class="state-message">
|
|
792
1049
|
加载配置中...
|
|
@@ -799,6 +1056,64 @@
|
|
|
799
1056
|
</div>
|
|
800
1057
|
|
|
801
1058
|
</main>
|
|
1059
|
+
<aside class="status-inspector" v-if="!sessionStandalone" aria-label="当前状态检查器">
|
|
1060
|
+
<div class="inspector-head">
|
|
1061
|
+
<div class="inspector-title">当前状态</div>
|
|
1062
|
+
<div class="inspector-subtitle">固定可见 · 实时同步</div>
|
|
1063
|
+
</div>
|
|
1064
|
+
|
|
1065
|
+
<section class="inspector-group" aria-label="当前上下文">
|
|
1066
|
+
<div class="inspector-group-title">当前上下文</div>
|
|
1067
|
+
<div class="inspector-kv">
|
|
1068
|
+
<span class="key">主标签</span>
|
|
1069
|
+
<span class="value">{{ inspectorMainTabLabel }}</span>
|
|
1070
|
+
<span class="key">配置模式</span>
|
|
1071
|
+
<span class="value">{{ inspectorConfigModeLabel }}</span>
|
|
1072
|
+
<span class="key">当前配置</span>
|
|
1073
|
+
<span class="value">{{ inspectorCurrentConfigLabel }}</span>
|
|
1074
|
+
<span class="key">当前模型</span>
|
|
1075
|
+
<span class="value">{{ inspectorCurrentModelLabel }}</span>
|
|
1076
|
+
</div>
|
|
1077
|
+
</section>
|
|
1078
|
+
|
|
1079
|
+
<section class="inspector-group" aria-label="生效与一致性">
|
|
1080
|
+
<div class="inspector-group-title">生效与一致性</div>
|
|
1081
|
+
<div class="inspector-kv">
|
|
1082
|
+
<span class="key">模板状态</span>
|
|
1083
|
+
<span class="value">{{ inspectorTemplateStatus }}</span>
|
|
1084
|
+
<span class="key">运行状态</span>
|
|
1085
|
+
<span class="value">{{ inspectorBusyStatus }}</span>
|
|
1086
|
+
<span class="key">最近提示</span>
|
|
1087
|
+
<span class="value">{{ inspectorMessageSummary }}</span>
|
|
1088
|
+
</div>
|
|
1089
|
+
</section>
|
|
1090
|
+
|
|
1091
|
+
<section class="inspector-group" aria-label="会话摘要">
|
|
1092
|
+
<div class="inspector-group-title">会话摘要</div>
|
|
1093
|
+
<div class="inspector-kv">
|
|
1094
|
+
<span class="key">当前来源</span>
|
|
1095
|
+
<span class="value">{{ inspectorSessionSourceLabel }}</span>
|
|
1096
|
+
<span class="key">路径过滤</span>
|
|
1097
|
+
<span class="value">{{ inspectorSessionPathLabel }}</span>
|
|
1098
|
+
<span class="key">检索条件</span>
|
|
1099
|
+
<span class="value">{{ inspectorSessionQueryLabel }}</span>
|
|
1100
|
+
<span class="key">结果数量</span>
|
|
1101
|
+
<span class="value">{{ sessionsList.length }}</span>
|
|
1102
|
+
</div>
|
|
1103
|
+
</section>
|
|
1104
|
+
|
|
1105
|
+
<section class="inspector-group" aria-label="健康提示">
|
|
1106
|
+
<div class="inspector-group-title">健康提示</div>
|
|
1107
|
+
<div class="inspector-kv">
|
|
1108
|
+
<span class="key">配置读取</span>
|
|
1109
|
+
<span :class="['value', 'tone-' + inspectorHealthTone]">{{ inspectorHealthStatus }}</span>
|
|
1110
|
+
<span class="key">模型加载</span>
|
|
1111
|
+
<span class="value">{{ inspectorModelLoadStatus }}</span>
|
|
1112
|
+
<span class="key">代理状态</span>
|
|
1113
|
+
<span class="value">{{ inspectorProxyStatus }}</span>
|
|
1114
|
+
</div>
|
|
1115
|
+
</section>
|
|
1116
|
+
</aside>
|
|
802
1117
|
</div>
|
|
803
1118
|
|
|
804
1119
|
<!-- 添加提供商模态框 -->
|
|
@@ -830,17 +1145,60 @@
|
|
|
830
1145
|
<div class="modal">
|
|
831
1146
|
<div class="modal-header">
|
|
832
1147
|
<div class="modal-title">安装 CLI</div>
|
|
833
|
-
<button class="btn-mini" @click="closeInstallModal">关闭</button>
|
|
834
1148
|
</div>
|
|
1149
|
+
|
|
1150
|
+
<div class="list-row">
|
|
1151
|
+
<label class="form-label">包管理器</label>
|
|
1152
|
+
<select class="form-input" v-model="installPackageManager">
|
|
1153
|
+
<option value="npm">npm</option>
|
|
1154
|
+
<option value="pnpm">pnpm</option>
|
|
1155
|
+
<option value="bun">bun</option>
|
|
1156
|
+
</select>
|
|
1157
|
+
<label class="form-label">镜像源</label>
|
|
1158
|
+
<div class="install-action-tabs">
|
|
1159
|
+
<button class="btn-mini" :class="{ active: installRegistryPreset === 'default' }" @click="setInstallRegistryPreset('default')">官方</button>
|
|
1160
|
+
<button class="btn-mini" :class="{ active: installRegistryPreset === 'npmmirror' }" @click="setInstallRegistryPreset('npmmirror')">npmmirror</button>
|
|
1161
|
+
<button class="btn-mini" :class="{ active: installRegistryPreset === 'tencent' }" @click="setInstallRegistryPreset('tencent')">腾讯云</button>
|
|
1162
|
+
<button class="btn-mini" :class="{ active: installRegistryPreset === 'custom' }" @click="setInstallRegistryPreset('custom')">自定义</button>
|
|
1163
|
+
</div>
|
|
1164
|
+
<input
|
|
1165
|
+
v-if="installRegistryPreset === 'custom'"
|
|
1166
|
+
v-model="installRegistryCustom"
|
|
1167
|
+
class="form-input install-registry-input"
|
|
1168
|
+
placeholder="https://registry.example.com">
|
|
1169
|
+
<div class="form-hint install-registry-hint" v-if="installRegistryPreview">
|
|
1170
|
+
当前命令会附加:--registry={{ installRegistryPreview }}
|
|
1171
|
+
</div>
|
|
1172
|
+
<div class="form-hint install-registry-hint" v-else-if="installRegistryPreset === 'custom'">
|
|
1173
|
+
请输入完整 URL(含 http/https)后将自动附加到安装/升级命令。
|
|
1174
|
+
</div>
|
|
1175
|
+
<label class="form-label">操作</label>
|
|
1176
|
+
<div class="install-action-tabs">
|
|
1177
|
+
<button class="btn-mini" :class="{ active: installCommandAction === 'install' }" @click="setInstallCommandAction('install')">安装</button>
|
|
1178
|
+
<button class="btn-mini" :class="{ active: installCommandAction === 'update' }" @click="setInstallCommandAction('update')">升级</button>
|
|
1179
|
+
<button class="btn-mini" :class="{ active: installCommandAction === 'uninstall' }" @click="setInstallCommandAction('uninstall')">卸载</button>
|
|
1180
|
+
</div>
|
|
1181
|
+
</div>
|
|
1182
|
+
|
|
835
1183
|
<div class="install-list">
|
|
836
1184
|
<div
|
|
837
1185
|
class="install-row"
|
|
838
|
-
v-for="
|
|
839
|
-
:key="
|
|
840
|
-
<
|
|
841
|
-
|
|
1186
|
+
v-for="target in installTargetCards"
|
|
1187
|
+
:key="'install-command-' + target.id + '-' + installCommandAction">
|
|
1188
|
+
<div class="install-row-main">
|
|
1189
|
+
<div class="install-row-title">{{ target.name }}</div>
|
|
1190
|
+
<code class="install-command">{{ target.command }}</code>
|
|
1191
|
+
</div>
|
|
1192
|
+
<button class="btn-mini" :disabled="!target.command" @click="copyInstallCommand(target.command)">复制</button>
|
|
842
1193
|
</div>
|
|
843
1194
|
</div>
|
|
1195
|
+
|
|
1196
|
+
<div class="install-help">
|
|
1197
|
+
<div class="form-label">常见失败自救</div>
|
|
1198
|
+
<ul class="install-help-list">
|
|
1199
|
+
<li v-for="tip in installTroubleshootingTips" :key="tip">{{ tip }}</li>
|
|
1200
|
+
</ul>
|
|
1201
|
+
</div>
|
|
844
1202
|
</div>
|
|
845
1203
|
</div>
|
|
846
1204
|
|
|
@@ -1256,41 +1614,51 @@
|
|
|
1256
1614
|
</div>
|
|
1257
1615
|
|
|
1258
1616
|
<div v-if="showAgentsModal" class="modal-overlay" @click.self="closeAgentsModal">
|
|
1259
|
-
<div class="modal modal-wide">
|
|
1260
|
-
<div class="modal-header">
|
|
1617
|
+
<div class="modal modal-wide modal-editor">
|
|
1618
|
+
<div class="modal-header modal-editor-header">
|
|
1261
1619
|
<div class="modal-title">{{ agentsModalTitle }}</div>
|
|
1262
|
-
<
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1620
|
+
<div class="modal-header-actions">
|
|
1621
|
+
<button
|
|
1622
|
+
class="btn-mini btn-modal-copy"
|
|
1623
|
+
@click="exportAgentsContent"
|
|
1624
|
+
:disabled="agentsLoading">
|
|
1625
|
+
导出
|
|
1626
|
+
</button>
|
|
1627
|
+
<button
|
|
1628
|
+
class="btn-mini btn-modal-copy"
|
|
1629
|
+
@click="copyAgentsContent"
|
|
1630
|
+
:disabled="agentsLoading">
|
|
1631
|
+
复制
|
|
1632
|
+
</button>
|
|
1633
|
+
</div>
|
|
1268
1634
|
</div>
|
|
1269
1635
|
|
|
1270
|
-
<div class="
|
|
1271
|
-
<
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1636
|
+
<div class="modal-editor-body">
|
|
1637
|
+
<div class="form-group">
|
|
1638
|
+
<label class="form-label">目标文件</label>
|
|
1639
|
+
<div class="form-hint">
|
|
1640
|
+
{{ agentsPath || '未加载' }}
|
|
1641
|
+
<span v-if="agentsPath">
|
|
1642
|
+
({{ agentsExists ? '已存在' : '不存在,将在保存时创建' }})
|
|
1643
|
+
</span>
|
|
1644
|
+
</div>
|
|
1277
1645
|
</div>
|
|
1278
|
-
</div>
|
|
1279
1646
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1647
|
+
<div class="form-group">
|
|
1648
|
+
<label class="form-label">AGENTS.md 内容</label>
|
|
1649
|
+
<textarea
|
|
1650
|
+
v-model="agentsContent"
|
|
1651
|
+
class="form-input template-editor"
|
|
1652
|
+
spellcheck="false"
|
|
1653
|
+
:readonly="agentsLoading"
|
|
1654
|
+
placeholder="在这里编辑 AGENTS.md 内容"></textarea>
|
|
1655
|
+
<div class="template-editor-warning">
|
|
1656
|
+
{{ agentsModalHint }}
|
|
1657
|
+
</div>
|
|
1290
1658
|
</div>
|
|
1291
1659
|
</div>
|
|
1292
1660
|
|
|
1293
|
-
<div class="btn-group">
|
|
1661
|
+
<div class="btn-group modal-editor-footer">
|
|
1294
1662
|
<button class="btn btn-cancel" @click="closeAgentsModal">取消</button>
|
|
1295
1663
|
<button class="btn btn-confirm" @click="applyAgentsContent" :disabled="agentsSaving || agentsLoading">
|
|
1296
1664
|
{{ agentsSaving ? '保存中...' : '保存' }}
|
|
@@ -1299,6 +1667,156 @@
|
|
|
1299
1667
|
</div>
|
|
1300
1668
|
</div>
|
|
1301
1669
|
|
|
1670
|
+
<div v-if="showSkillsModal" class="modal-overlay" @click.self="closeSkillsModal">
|
|
1671
|
+
<div class="modal modal-wide skills-modal">
|
|
1672
|
+
<div class="modal-header skills-modal-header">
|
|
1673
|
+
<div>
|
|
1674
|
+
<div class="modal-title">Skills 管理</div>
|
|
1675
|
+
<div class="skills-modal-subtitle">集中管理本地技能目录,支持检索筛选、多选删除与跨应用导入。</div>
|
|
1676
|
+
</div>
|
|
1677
|
+
<div class="modal-header-actions skills-modal-actions">
|
|
1678
|
+
<button class="btn-mini" @click="refreshSkillsList({ silent: false })" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting">
|
|
1679
|
+
{{ skillsLoading ? '刷新中...' : '刷新' }}
|
|
1680
|
+
</button>
|
|
1681
|
+
</div>
|
|
1682
|
+
</div>
|
|
1683
|
+
|
|
1684
|
+
<div class="form-group skills-root-group">
|
|
1685
|
+
<label class="form-label">Skills 目录</label>
|
|
1686
|
+
<div class="skills-root-box">{{ skillsRootPath || '~/.codex/skills' }}</div>
|
|
1687
|
+
</div>
|
|
1688
|
+
|
|
1689
|
+
<div class="skills-summary-strip">
|
|
1690
|
+
<div class="skills-summary-item">
|
|
1691
|
+
<span class="skills-summary-label">本地总数</span>
|
|
1692
|
+
<strong class="skills-summary-value">{{ skillsList.length }}</strong>
|
|
1693
|
+
</div>
|
|
1694
|
+
<div class="skills-summary-item">
|
|
1695
|
+
<span class="skills-summary-label">含 SKILL.md</span>
|
|
1696
|
+
<strong class="skills-summary-value">{{ skillsConfiguredCount }}</strong>
|
|
1697
|
+
</div>
|
|
1698
|
+
<div class="skills-summary-item">
|
|
1699
|
+
<span class="skills-summary-label">缺少 SKILL.md</span>
|
|
1700
|
+
<strong class="skills-summary-value">{{ skillsMissingSkillFileCount }}</strong>
|
|
1701
|
+
</div>
|
|
1702
|
+
<div class="skills-summary-item">
|
|
1703
|
+
<span class="skills-summary-label">可导入</span>
|
|
1704
|
+
<strong class="skills-summary-value">{{ skillsImportList.length }}</strong>
|
|
1705
|
+
</div>
|
|
1706
|
+
</div>
|
|
1707
|
+
|
|
1708
|
+
<div class="skills-panel">
|
|
1709
|
+
<div class="skills-panel-header">
|
|
1710
|
+
<div class="skills-panel-title-wrap">
|
|
1711
|
+
<div class="skills-panel-title">本地 Skills</div>
|
|
1712
|
+
<div class="skills-panel-note">支持关键词检索与状态筛选,勾选后可批量删除。</div>
|
|
1713
|
+
</div>
|
|
1714
|
+
<button
|
|
1715
|
+
class="btn-mini"
|
|
1716
|
+
@click="resetSkillsFilters"
|
|
1717
|
+
:disabled="skillsLoading || skillsDeleting || !skillsFilterDirty">
|
|
1718
|
+
重置筛选
|
|
1719
|
+
</button>
|
|
1720
|
+
</div>
|
|
1721
|
+
|
|
1722
|
+
<div class="skills-filter-row">
|
|
1723
|
+
<input
|
|
1724
|
+
class="form-input"
|
|
1725
|
+
type="text"
|
|
1726
|
+
v-model.trim="skillsKeyword"
|
|
1727
|
+
aria-label="按名称或描述筛选 skill"
|
|
1728
|
+
placeholder="按目录名/显示名/描述检索">
|
|
1729
|
+
<select class="form-select skills-status-select" v-model="skillsStatusFilter" aria-label="按 SKILL.md 状态筛选 skill">
|
|
1730
|
+
<option value="all">全部状态</option>
|
|
1731
|
+
<option value="with-skill-file">仅含 SKILL.md</option>
|
|
1732
|
+
<option value="missing-skill-file">仅缺少 SKILL.md</option>
|
|
1733
|
+
</select>
|
|
1734
|
+
</div>
|
|
1735
|
+
|
|
1736
|
+
<div class="skill-toolbar">
|
|
1737
|
+
<label class="skill-select-all">
|
|
1738
|
+
<input type="checkbox" :checked="skillsAllSelected" @change="toggleAllSkillsSelection" :disabled="skillsLoading || skillsDeleting || skillsSelectableNames.length === 0">
|
|
1739
|
+
<span>{{ skillsAllSelected ? '取消全选' : '全选' }}</span>
|
|
1740
|
+
</label>
|
|
1741
|
+
<span class="skill-toolbar-count">已选 {{ skillsSelectedCount }}(筛选命中 {{ filteredSkillsList.length }} / {{ skillsList.length }},筛选内已选 {{ skillsVisibleSelectedCount }})</span>
|
|
1742
|
+
</div>
|
|
1743
|
+
|
|
1744
|
+
<div v-if="skillsList.length === 0" class="skills-empty-state">暂无可管理的 skill。</div>
|
|
1745
|
+
<div v-else-if="filteredSkillsList.length === 0" class="skills-empty-state">当前筛选条件下没有匹配的 skill。</div>
|
|
1746
|
+
<div v-else class="skill-list">
|
|
1747
|
+
<label
|
|
1748
|
+
class="skill-item"
|
|
1749
|
+
:class="{ selected: skillsSelectedNames.includes(skill.name) }"
|
|
1750
|
+
v-for="skill in filteredSkillsList"
|
|
1751
|
+
:key="'skill-' + skill.name">
|
|
1752
|
+
<input type="checkbox" v-model="skillsSelectedNames" :value="skill.name" :disabled="skillsDeleting">
|
|
1753
|
+
<div class="skill-item-main">
|
|
1754
|
+
<div class="skill-item-title">{{ skill.displayName || skill.name }}</div>
|
|
1755
|
+
<div v-if="skill.description" class="skill-item-description">{{ skill.description }}</div>
|
|
1756
|
+
<div class="skill-item-meta">
|
|
1757
|
+
<span class="skill-item-path" :title="skill.path">{{ skill.path }}</span>
|
|
1758
|
+
<span :class="['pill', skill.hasSkillFile ? 'configured' : 'empty']">
|
|
1759
|
+
{{ skill.hasSkillFile ? '含 SKILL.md' : '缺少 SKILL.md' }}
|
|
1760
|
+
</span>
|
|
1761
|
+
<span class="pill source">{{ skill.sourceType === 'symlink' ? '符号链接' : '目录' }}</span>
|
|
1762
|
+
</div>
|
|
1763
|
+
</div>
|
|
1764
|
+
</label>
|
|
1765
|
+
</div>
|
|
1766
|
+
</div>
|
|
1767
|
+
|
|
1768
|
+
<div class="skills-panel skills-import-block">
|
|
1769
|
+
<div class="skills-panel-header">
|
|
1770
|
+
<div class="skills-panel-title-wrap">
|
|
1771
|
+
<div class="skills-import-title">跨应用导入(对齐 cc-switch 能力)</div>
|
|
1772
|
+
<div class="skills-panel-note">从其他应用扫描并导入未托管技能,支持多选批量导入。</div>
|
|
1773
|
+
</div>
|
|
1774
|
+
<button class="btn-mini" @click="scanImportableSkills" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting">
|
|
1775
|
+
{{ skillsScanningImports ? '扫描中...' : '扫描可导入' }}
|
|
1776
|
+
</button>
|
|
1777
|
+
</div>
|
|
1778
|
+
<div class="skill-toolbar">
|
|
1779
|
+
<label class="skill-select-all">
|
|
1780
|
+
<input type="checkbox" :checked="skillsImportAllSelected" @change="toggleAllSkillsImportSelection" :disabled="skillsScanningImports || skillsImporting || skillsImportSelectableKeys.length === 0">
|
|
1781
|
+
<span>{{ skillsImportAllSelected ? '取消全选' : '全选' }}</span>
|
|
1782
|
+
</label>
|
|
1783
|
+
<span class="skill-toolbar-count">已选 {{ skillsImportSelectedCount }} / {{ skillsImportSelectableKeys.length }},含 SKILL.md {{ skillsImportConfiguredCount }},缺失 {{ skillsImportMissingSkillFileCount }}</span>
|
|
1784
|
+
</div>
|
|
1785
|
+
<div v-if="skillsImportList.length === 0" class="skills-empty-state skills-import-empty">暂无可导入 skill,点击“扫描可导入”。</div>
|
|
1786
|
+
<div v-else class="skill-list skills-import-list">
|
|
1787
|
+
<label
|
|
1788
|
+
class="skill-item"
|
|
1789
|
+
:class="{ selected: skillsImportSelectedKeys.includes(buildSkillImportKey(skill)) }"
|
|
1790
|
+
v-for="skill in skillsImportList"
|
|
1791
|
+
:key="'import-skill-' + buildSkillImportKey(skill)">
|
|
1792
|
+
<input type="checkbox" v-model="skillsImportSelectedKeys" :value="buildSkillImportKey(skill)" :disabled="skillsImporting">
|
|
1793
|
+
<div class="skill-item-main">
|
|
1794
|
+
<div class="skill-item-title">{{ skill.displayName || skill.name }}</div>
|
|
1795
|
+
<div v-if="skill.description" class="skill-item-description">{{ skill.description }}</div>
|
|
1796
|
+
<div class="skill-item-meta">
|
|
1797
|
+
<span class="skill-item-path" :title="skill.sourcePath">{{ skill.sourcePath }}</span>
|
|
1798
|
+
<span class="pill source">{{ skill.sourceLabel }}</span>
|
|
1799
|
+
<span :class="['pill', skill.hasSkillFile ? 'configured' : 'empty']">
|
|
1800
|
+
{{ skill.hasSkillFile ? '含 SKILL.md' : '缺少 SKILL.md' }}
|
|
1801
|
+
</span>
|
|
1802
|
+
</div>
|
|
1803
|
+
</div>
|
|
1804
|
+
</label>
|
|
1805
|
+
</div>
|
|
1806
|
+
</div>
|
|
1807
|
+
|
|
1808
|
+
<div class="btn-group">
|
|
1809
|
+
<button class="btn btn-cancel" @click="closeSkillsModal" :disabled="skillsDeleting || skillsImporting || skillsScanningImports">关闭</button>
|
|
1810
|
+
<button class="btn btn-confirm" @click="importSelectedSkills" :disabled="skillsImporting || skillsImportSelectedCount === 0">
|
|
1811
|
+
{{ skillsImporting ? '导入中...' : '导入选中' }}
|
|
1812
|
+
</button>
|
|
1813
|
+
<button class="btn btn-confirm btn-danger" @click="deleteSelectedSkills" :disabled="skillsDeleting || skillsSelectedCount === 0">
|
|
1814
|
+
{{ skillsDeleting ? '删除中...' : '删除选中' }}
|
|
1815
|
+
</button>
|
|
1816
|
+
</div>
|
|
1817
|
+
</div>
|
|
1818
|
+
</div>
|
|
1819
|
+
|
|
1302
1820
|
<!-- Toast通知 -->
|
|
1303
1821
|
|
|
1304
1822
|
<!-- Toast -->
|