codexmate 0.0.17 → 0.0.19
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 +65 -33
- package/README.md +61 -37
- package/cli.js +1877 -408
- package/lib/text-diff.js +303 -0
- package/package.json +1 -1
- package/web-ui/app.js +1749 -447
- package/web-ui/index.html +580 -199
- package/web-ui/logic.mjs +390 -0
- package/web-ui/modules/config-mode.computed.mjs +1 -0
- package/web-ui/modules/skills.computed.mjs +26 -1
- package/web-ui/modules/skills.methods.mjs +160 -23
- package/web-ui/session-helpers.mjs +362 -0
- package/web-ui/styles.css +652 -13
- package/doc/CHANGELOG.md +0 -32
- package/doc/CHANGELOG.zh-CN.md +0 -34
package/web-ui/index.html
CHANGED
|
@@ -56,48 +56,66 @@
|
|
|
56
56
|
<button class="top-tab"
|
|
57
57
|
id="tab-config-codex"
|
|
58
58
|
role="tab"
|
|
59
|
+
data-main-tab="config"
|
|
60
|
+
data-config-mode="codex"
|
|
59
61
|
:tabindex="mainTab === 'config' && configMode === 'codex' ? 0 : -1"
|
|
60
62
|
:aria-selected="mainTab === 'config' && configMode === 'codex'"
|
|
61
|
-
:aria-pressed="mainTab === 'config' && configMode === 'codex'"
|
|
62
63
|
aria-controls="panel-config-provider"
|
|
63
|
-
:class="{ active:
|
|
64
|
-
@
|
|
64
|
+
:class="{ active: isConfigModeNavActive('codex') }"
|
|
65
|
+
@pointerdown="onConfigTabPointerDown('codex', $event)"
|
|
66
|
+
@click="onConfigTabClick('codex', $event)">Codex 配置</button>
|
|
65
67
|
<button class="top-tab"
|
|
66
68
|
id="tab-config-claude"
|
|
67
69
|
role="tab"
|
|
70
|
+
data-main-tab="config"
|
|
71
|
+
data-config-mode="claude"
|
|
68
72
|
:tabindex="mainTab === 'config' && configMode === 'claude' ? 0 : -1"
|
|
69
73
|
:aria-selected="mainTab === 'config' && configMode === 'claude'"
|
|
70
|
-
:aria-pressed="mainTab === 'config' && configMode === 'claude'"
|
|
71
74
|
aria-controls="panel-config-claude"
|
|
72
|
-
:class="{ active:
|
|
73
|
-
@
|
|
75
|
+
:class="{ active: isConfigModeNavActive('claude') }"
|
|
76
|
+
@pointerdown="onConfigTabPointerDown('claude', $event)"
|
|
77
|
+
@click="onConfigTabClick('claude', $event)">Claude Code 配置</button>
|
|
74
78
|
<button class="top-tab"
|
|
75
79
|
id="tab-config-openclaw"
|
|
76
80
|
role="tab"
|
|
81
|
+
data-main-tab="config"
|
|
82
|
+
data-config-mode="openclaw"
|
|
77
83
|
:tabindex="mainTab === 'config' && configMode === 'openclaw' ? 0 : -1"
|
|
78
84
|
:aria-selected="mainTab === 'config' && configMode === 'openclaw'"
|
|
79
|
-
:aria-pressed="mainTab === 'config' && configMode === 'openclaw'"
|
|
80
85
|
aria-controls="panel-config-openclaw"
|
|
81
|
-
:class="{ active:
|
|
82
|
-
@
|
|
86
|
+
:class="{ active: isConfigModeNavActive('openclaw') }"
|
|
87
|
+
@pointerdown="onConfigTabPointerDown('openclaw', $event)"
|
|
88
|
+
@click="onConfigTabClick('openclaw', $event)">OpenClaw 配置</button>
|
|
83
89
|
<button class="top-tab"
|
|
84
90
|
id="tab-sessions"
|
|
85
91
|
role="tab"
|
|
92
|
+
data-main-tab="sessions"
|
|
86
93
|
:tabindex="mainTab === 'sessions' ? 0 : -1"
|
|
87
94
|
:aria-selected="mainTab === 'sessions'"
|
|
88
|
-
:aria-pressed="mainTab === 'sessions'"
|
|
89
95
|
aria-controls="panel-sessions"
|
|
90
|
-
:class="{ active:
|
|
91
|
-
@
|
|
96
|
+
:class="{ active: isMainTabNavActive('sessions') }"
|
|
97
|
+
@pointerdown="onMainTabPointerDown('sessions', $event)"
|
|
98
|
+
@click="onMainTabClick('sessions', $event)">会话浏览</button>
|
|
99
|
+
<button class="top-tab"
|
|
100
|
+
id="tab-market"
|
|
101
|
+
role="tab"
|
|
102
|
+
data-main-tab="market"
|
|
103
|
+
:tabindex="mainTab === 'market' ? 0 : -1"
|
|
104
|
+
:aria-selected="mainTab === 'market'"
|
|
105
|
+
aria-controls="panel-market"
|
|
106
|
+
:class="{ active: isMainTabNavActive('market') }"
|
|
107
|
+
@pointerdown="onMainTabPointerDown('market', $event)"
|
|
108
|
+
@click="onMainTabClick('market', $event)">技能市场</button>
|
|
92
109
|
<button class="top-tab"
|
|
93
110
|
id="tab-settings"
|
|
94
111
|
role="tab"
|
|
112
|
+
data-main-tab="settings"
|
|
95
113
|
:tabindex="mainTab === 'settings' ? 0 : -1"
|
|
96
114
|
:aria-selected="mainTab === 'settings'"
|
|
97
|
-
:aria-pressed="mainTab === 'settings'"
|
|
98
115
|
aria-controls="panel-settings"
|
|
99
|
-
:class="{ active:
|
|
100
|
-
@
|
|
116
|
+
:class="{ active: isMainTabNavActive('settings') }"
|
|
117
|
+
@pointerdown="onMainTabPointerDown('settings', $event)"
|
|
118
|
+
@click="onMainTabClick('settings', $event)">设置</button>
|
|
101
119
|
</div>
|
|
102
120
|
|
|
103
121
|
<div :class="['app-shell', { standalone: sessionStandalone }]">
|
|
@@ -137,12 +155,14 @@
|
|
|
137
155
|
<button
|
|
138
156
|
role="tab"
|
|
139
157
|
id="side-tab-config-codex"
|
|
158
|
+
data-main-tab="config"
|
|
159
|
+
data-config-mode="codex"
|
|
140
160
|
aria-controls="panel-config-provider"
|
|
141
161
|
:tabindex="mainTab === 'config' && configMode === 'codex' ? 0 : -1"
|
|
142
162
|
:aria-selected="mainTab === 'config' && configMode === 'codex'"
|
|
143
|
-
:
|
|
144
|
-
|
|
145
|
-
@click="
|
|
163
|
+
:class="['side-item', { active: isConfigModeNavActive('codex') }]"
|
|
164
|
+
@pointerdown="onConfigTabPointerDown('codex', $event)"
|
|
165
|
+
@click="onConfigTabClick('codex', $event)">
|
|
146
166
|
<div class="side-item-title">Codex 配置</div>
|
|
147
167
|
<div class="side-item-meta">
|
|
148
168
|
<span>提供商 / 模型</span>
|
|
@@ -152,12 +172,14 @@
|
|
|
152
172
|
<button
|
|
153
173
|
role="tab"
|
|
154
174
|
id="side-tab-config-claude"
|
|
175
|
+
data-main-tab="config"
|
|
176
|
+
data-config-mode="claude"
|
|
155
177
|
aria-controls="panel-config-claude"
|
|
156
178
|
:tabindex="mainTab === 'config' && configMode === 'claude' ? 0 : -1"
|
|
157
179
|
:aria-selected="mainTab === 'config' && configMode === 'claude'"
|
|
158
|
-
:
|
|
159
|
-
|
|
160
|
-
@click="
|
|
180
|
+
:class="['side-item', { active: isConfigModeNavActive('claude') }]"
|
|
181
|
+
@pointerdown="onConfigTabPointerDown('claude', $event)"
|
|
182
|
+
@click="onConfigTabClick('claude', $event)">
|
|
161
183
|
<div class="side-item-title">Claude Code 配置</div>
|
|
162
184
|
<div class="side-item-meta">
|
|
163
185
|
<span>Base URL / Key</span>
|
|
@@ -167,12 +189,14 @@
|
|
|
167
189
|
<button
|
|
168
190
|
role="tab"
|
|
169
191
|
id="side-tab-config-openclaw"
|
|
192
|
+
data-main-tab="config"
|
|
193
|
+
data-config-mode="openclaw"
|
|
170
194
|
aria-controls="panel-config-openclaw"
|
|
171
195
|
:tabindex="mainTab === 'config' && configMode === 'openclaw' ? 0 : -1"
|
|
172
196
|
:aria-selected="mainTab === 'config' && configMode === 'openclaw'"
|
|
173
|
-
:
|
|
174
|
-
|
|
175
|
-
@click="
|
|
197
|
+
:class="['side-item', { active: isConfigModeNavActive('openclaw') }]"
|
|
198
|
+
@pointerdown="onConfigTabPointerDown('openclaw', $event)"
|
|
199
|
+
@click="onConfigTabClick('openclaw', $event)">
|
|
176
200
|
<div class="side-item-title">OpenClaw 配置</div>
|
|
177
201
|
<div class="side-item-meta">
|
|
178
202
|
<span>JSON5 / Workspace</span>
|
|
@@ -186,12 +210,13 @@
|
|
|
186
210
|
<button
|
|
187
211
|
role="tab"
|
|
188
212
|
id="side-tab-sessions"
|
|
213
|
+
data-main-tab="sessions"
|
|
189
214
|
aria-controls="panel-sessions"
|
|
190
215
|
:tabindex="mainTab === 'sessions' ? 0 : -1"
|
|
191
216
|
:aria-selected="mainTab === 'sessions'"
|
|
192
|
-
:
|
|
193
|
-
|
|
194
|
-
@click="
|
|
217
|
+
:class="['side-item', { active: isMainTabNavActive('sessions') }]"
|
|
218
|
+
@pointerdown="onMainTabPointerDown('sessions', $event)"
|
|
219
|
+
@click="onMainTabClick('sessions', $event)">
|
|
195
220
|
<div class="side-item-title">会话浏览</div>
|
|
196
221
|
<div class="side-item-meta">
|
|
197
222
|
<span>快速预览 / 导出</span>
|
|
@@ -200,17 +225,38 @@
|
|
|
200
225
|
</button>
|
|
201
226
|
</div>
|
|
202
227
|
|
|
228
|
+
<div class="side-section" role="tablist" aria-label="技能市场">
|
|
229
|
+
<div class="side-section-title">技能市场</div>
|
|
230
|
+
<button
|
|
231
|
+
role="tab"
|
|
232
|
+
id="side-tab-market"
|
|
233
|
+
data-main-tab="market"
|
|
234
|
+
aria-controls="panel-market"
|
|
235
|
+
:tabindex="mainTab === 'market' ? 0 : -1"
|
|
236
|
+
:aria-selected="mainTab === 'market'"
|
|
237
|
+
:class="['side-item', { active: isMainTabNavActive('market') }]"
|
|
238
|
+
@pointerdown="onMainTabPointerDown('market', $event)"
|
|
239
|
+
@click="onMainTabClick('market', $event)">
|
|
240
|
+
<div class="side-item-title">市场</div>
|
|
241
|
+
<div class="side-item-meta">
|
|
242
|
+
<span>{{ skillsTargetLabel }} / 本地 Skills</span>
|
|
243
|
+
<span>已装 {{ skillsList.length }} · 可导入 {{ skillsImportList.length }}</span>
|
|
244
|
+
</div>
|
|
245
|
+
</button>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
203
248
|
<div class="side-section" role="tablist" aria-label="设置">
|
|
204
249
|
<div class="side-section-title">设置</div>
|
|
205
250
|
<button
|
|
206
251
|
role="tab"
|
|
207
252
|
id="side-tab-settings"
|
|
253
|
+
data-main-tab="settings"
|
|
208
254
|
aria-controls="panel-settings"
|
|
209
255
|
:tabindex="mainTab === 'settings' ? 0 : -1"
|
|
210
256
|
:aria-selected="mainTab === 'settings'"
|
|
211
|
-
:
|
|
212
|
-
|
|
213
|
-
@click="
|
|
257
|
+
:class="['side-item', { active: isMainTabNavActive('settings') }]"
|
|
258
|
+
@pointerdown="onMainTabPointerDown('settings', $event)"
|
|
259
|
+
@click="onMainTabClick('settings', $event)">
|
|
214
260
|
<div class="side-item-title">设置</div>
|
|
215
261
|
<div class="side-item-meta">
|
|
216
262
|
<span>数据管理 / 下载</span>
|
|
@@ -221,7 +267,7 @@
|
|
|
221
267
|
<main class="main-panel">
|
|
222
268
|
<div class="panel-header" v-if="!sessionStandalone">
|
|
223
269
|
<h1 class="main-title">
|
|
224
|
-
{{ mainTab === 'config' ? '配置中心' : (mainTab === 'sessions' ? '会话浏览' : '设置') }}
|
|
270
|
+
{{ mainTab === 'config' ? '配置中心' : (mainTab === 'sessions' ? '会话浏览' : (mainTab === 'market' ? '技能市场' : '设置')) }}
|
|
225
271
|
</h1>
|
|
226
272
|
<p class="subtitle" v-if="mainTab === 'config'">
|
|
227
273
|
配置中枢:管理 Codex / Claude / OpenClaw
|
|
@@ -230,6 +276,9 @@
|
|
|
230
276
|
<p class="subtitle" v-else-if="mainTab === 'sessions'">
|
|
231
277
|
浏览、导出或独立查看 Codex / Claude 会话记录。
|
|
232
278
|
</p>
|
|
279
|
+
<p class="subtitle" v-else-if="mainTab === 'market'">
|
|
280
|
+
统一管理 Codex / Claude Skills,并聚焦本地导入与分发。
|
|
281
|
+
</p>
|
|
233
282
|
</div>
|
|
234
283
|
|
|
235
284
|
<div class="status-strip" v-if="!sessionStandalone && mainTab === 'config'">
|
|
@@ -282,29 +331,47 @@
|
|
|
282
331
|
<span class="value">{{ sessionsList.length }}</span>
|
|
283
332
|
</div>
|
|
284
333
|
</div>
|
|
334
|
+
<div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'market'">
|
|
335
|
+
<div class="status-chip">
|
|
336
|
+
<span class="label">当前目标</span>
|
|
337
|
+
<span class="value">{{ skillsTargetLabel }}</span>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="status-chip">
|
|
340
|
+
<span class="label">本地 Skills</span>
|
|
341
|
+
<span class="value">{{ skillsList.length }}</span>
|
|
342
|
+
</div>
|
|
343
|
+
<div class="status-chip">
|
|
344
|
+
<span class="label">可导入</span>
|
|
345
|
+
<span class="value">{{ skillsImportList.length }}</span>
|
|
346
|
+
</div>
|
|
347
|
+
<div class="status-chip">
|
|
348
|
+
<span class="label">可直接导入</span>
|
|
349
|
+
<span class="value">{{ skillsImportConfiguredCount }}</span>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
285
352
|
<div
|
|
286
|
-
v-if="!sessionStandalone && mainTab === 'config' && isProviderConfigMode && forceCompactLayout && !loading && !initError &&
|
|
353
|
+
v-if="!sessionStandalone && mainTab === 'config' && isProviderConfigMode && forceCompactLayout && !loading && !initError && displayProvidersList.length > 1"
|
|
287
354
|
class="provider-fast-switch">
|
|
288
355
|
<label class="provider-fast-switch-label" for="provider-fast-switch-select">快速切换提供商</label>
|
|
289
356
|
<select
|
|
290
357
|
id="provider-fast-switch-select"
|
|
291
358
|
class="provider-fast-switch-select"
|
|
292
|
-
:value="
|
|
359
|
+
:value="displayCurrentProvider"
|
|
293
360
|
@change="quickSwitchProvider($event.target.value)">
|
|
294
|
-
<option v-for="provider in
|
|
361
|
+
<option v-for="provider in displayProvidersList" :key="'quick-switch-' + provider.name" :value="provider.name">
|
|
295
362
|
{{ provider.name }}
|
|
296
363
|
</option>
|
|
297
364
|
</select>
|
|
298
365
|
</div>
|
|
299
366
|
|
|
300
367
|
<div v-if="false && mainTab === 'config' && !sessionStandalone" class="config-subtabs">
|
|
301
|
-
<button :class="['config-subtab', { active: configMode === 'codex' }]" @click="
|
|
368
|
+
<button :class="['config-subtab', { active: configMode === 'codex' }]" @click="onConfigTabClick('codex', $event)">
|
|
302
369
|
Codex 配置
|
|
303
370
|
</button>
|
|
304
|
-
<button :class="['config-subtab', { active: configMode === 'claude' }]" @click="
|
|
371
|
+
<button :class="['config-subtab', { active: configMode === 'claude' }]" @click="onConfigTabClick('claude', $event)">
|
|
305
372
|
Claude Code 配置
|
|
306
373
|
</button>
|
|
307
|
-
<button :class="['config-subtab', { active: configMode === 'openclaw' }]" @click="
|
|
374
|
+
<button :class="['config-subtab', { active: configMode === 'openclaw' }]" @click="onConfigTabClick('openclaw', $event)">
|
|
308
375
|
OpenClaw 配置
|
|
309
376
|
</button>
|
|
310
377
|
</div>
|
|
@@ -365,7 +432,7 @@
|
|
|
365
432
|
Codex 配置需先改模板,再手动应用。
|
|
366
433
|
</div>
|
|
367
434
|
<div class="config-template-hint" v-else-if="activeProviderBridgeHint">
|
|
368
|
-
{{ activeProviderBridgeHint }}
|
|
435
|
+
{{ activeProviderBridgeHint }} 模板仅在 Codex 模式下可编辑。
|
|
369
436
|
</div>
|
|
370
437
|
<button class="btn-tool btn-template-editor" v-if="isCodexConfigMode" @click="openConfigTemplateEditor" :disabled="loading || !!initError">
|
|
371
438
|
打开 Config 模板编辑器
|
|
@@ -414,8 +481,8 @@
|
|
|
414
481
|
<div class="selector-header">
|
|
415
482
|
<span class="selector-title">Skills 管理</span>
|
|
416
483
|
</div>
|
|
417
|
-
<div class="config-template-hint skills-hint-line"
|
|
418
|
-
<button class="btn-tool" @click="openSkillsManager" :disabled="loading || !!initError || skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
484
|
+
<div class="config-template-hint skills-hint-line">默认打开 Codex skills 管理;市场页已支持切换到 Claude Code 并查看本地概览与导入来源。</div>
|
|
485
|
+
<button class="btn-tool" @click="openSkillsManager({ targetApp: 'codex' })" :disabled="loading || !!initError || skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
419
486
|
{{ skillsLoading ? '加载中...' : '打开 Skills 管理' }}
|
|
420
487
|
</button>
|
|
421
488
|
</div>
|
|
@@ -430,102 +497,6 @@
|
|
|
430
497
|
</button>
|
|
431
498
|
</div>
|
|
432
499
|
|
|
433
|
-
<div class="selector-section">
|
|
434
|
-
<div class="selector-header">
|
|
435
|
-
<span class="selector-title">Codex 认证文件</span>
|
|
436
|
-
</div>
|
|
437
|
-
<div class="config-template-hint hint-single-line" title="上传 JSON 切换账号(写入 ~/.codex/auth.json)。">
|
|
438
|
-
上传 JSON 切换账号(写入 <code>~/.codex/auth.json</code>)。
|
|
439
|
-
</div>
|
|
440
|
-
<button class="btn-tool" @click="triggerCodexAuthUpload" :disabled="codexAuthImportLoading || loading || !!initError">
|
|
441
|
-
{{ codexAuthImportLoading ? '上传中...' : '上传认证文件' }}
|
|
442
|
-
</button>
|
|
443
|
-
<input
|
|
444
|
-
ref="codexAuthImportInput"
|
|
445
|
-
type="file"
|
|
446
|
-
accept=".json,application/json"
|
|
447
|
-
style="display:none"
|
|
448
|
-
@change="handleCodexAuthImportChange">
|
|
449
|
-
<div v-if="codexAuthProfiles.length === 0" class="form-hint">暂无认证文件。</div>
|
|
450
|
-
<div v-else class="auth-profile-list">
|
|
451
|
-
<div class="auth-profile-item" v-for="profile in codexAuthProfiles" :key="'auth-' + profile.name">
|
|
452
|
-
<div class="auth-profile-header">
|
|
453
|
-
<div class="auth-profile-main">
|
|
454
|
-
<div class="auth-profile-title">{{ profile.name }}</div>
|
|
455
|
-
<div class="auth-profile-meta">
|
|
456
|
-
<span class="provider-source">{{ profile.type || 'unknown' }}</span>
|
|
457
|
-
<span :class="['pill', profile.current ? 'configured' : 'empty']">
|
|
458
|
-
{{ profile.current ? '当前' : '备用' }}
|
|
459
|
-
</span>
|
|
460
|
-
</div>
|
|
461
|
-
</div>
|
|
462
|
-
<div class="auth-profile-actions">
|
|
463
|
-
<button
|
|
464
|
-
class="btn-mini"
|
|
465
|
-
:disabled="profile.current || codexAuthSwitching[profile.name]"
|
|
466
|
-
@click="switchCodexAuthProfile(profile.name)">
|
|
467
|
-
{{ codexAuthSwitching[profile.name] ? '切换中...' : (profile.current ? '当前使用' : '切换账号') }}
|
|
468
|
-
</button>
|
|
469
|
-
<button
|
|
470
|
-
class="btn-mini delete"
|
|
471
|
-
:disabled="codexAuthDeleting[profile.name]"
|
|
472
|
-
@click="deleteCodexAuthProfile(profile.name)">
|
|
473
|
-
{{ codexAuthDeleting[profile.name] ? '删除中...' : '删除' }}
|
|
474
|
-
</button>
|
|
475
|
-
</div>
|
|
476
|
-
</div>
|
|
477
|
-
<div class="auth-profile-grid">
|
|
478
|
-
<div v-if="profile.email" class="auth-profile-row">
|
|
479
|
-
<span class="auth-profile-key">email</span>
|
|
480
|
-
<span class="auth-profile-value">{{ profile.email }}</span>
|
|
481
|
-
</div>
|
|
482
|
-
<div v-if="profile.accountId" class="auth-profile-row">
|
|
483
|
-
<span class="auth-profile-key">account_id</span>
|
|
484
|
-
<span class="auth-profile-value">{{ profile.accountId }}</span>
|
|
485
|
-
</div>
|
|
486
|
-
<div v-if="profile.expired" class="auth-profile-row">
|
|
487
|
-
<span class="auth-profile-key">expired</span>
|
|
488
|
-
<span class="auth-profile-value">{{ profile.expired }}</span>
|
|
489
|
-
</div>
|
|
490
|
-
</div>
|
|
491
|
-
</div>
|
|
492
|
-
</div>
|
|
493
|
-
</div>
|
|
494
|
-
|
|
495
|
-
<div class="selector-section">
|
|
496
|
-
<div class="selector-header">
|
|
497
|
-
<span class="selector-title">内建代理</span>
|
|
498
|
-
</div>
|
|
499
|
-
<button class="btn-mini" @click="showProxyAdvanced = !showProxyAdvanced">
|
|
500
|
-
{{ showProxyAdvanced ? '收起高级设置' : '展开高级设置(端口/鉴权)' }}
|
|
501
|
-
</button>
|
|
502
|
-
<div v-if="showProxyAdvanced">
|
|
503
|
-
<div class="list-row">
|
|
504
|
-
<label class="form-label">上游 Provider</label>
|
|
505
|
-
<select class="form-input" v-model="proxySettings.provider" @change="saveProxySettings({ silent: true })">
|
|
506
|
-
<option value="">自动(当前 provider)</option>
|
|
507
|
-
<option v-for="name in proxyProviderOptions" :key="'proxy-provider-' + name" :value="name">{{ name }}</option>
|
|
508
|
-
</select>
|
|
509
|
-
<label class="form-label">端口</label>
|
|
510
|
-
<input v-model.number="proxySettings.port" class="form-input" type="number" min="1" max="65535" placeholder="8318" @change="saveProxySettings({ silent: true })">
|
|
511
|
-
</div>
|
|
512
|
-
<div class="list-row">
|
|
513
|
-
<label class="form-label">鉴权来源</label>
|
|
514
|
-
<select class="form-input" v-model="proxySettings.authSource" @change="saveProxySettings({ silent: true })">
|
|
515
|
-
<option value="provider">provider 优先</option>
|
|
516
|
-
<option value="profile">仅当前认证文件</option>
|
|
517
|
-
<option value="none">不注入鉴权头</option>
|
|
518
|
-
</select>
|
|
519
|
-
<label class="form-label">超时(ms)</label>
|
|
520
|
-
<input v-model.number="proxySettings.timeoutMs" class="form-input" type="number" min="1000" step="500" placeholder="30000" @change="saveProxySettings({ silent: true })">
|
|
521
|
-
</div>
|
|
522
|
-
<div class="form-hint">高级参数修改后自动保存。</div>
|
|
523
|
-
</div>
|
|
524
|
-
<div class="config-template-hint" v-if="proxyRuntime">
|
|
525
|
-
运行中:入口 <code>{{ proxyRuntimeDisplayProvider }}</code>
|
|
526
|
-
<span v-if="proxyRuntime.upstreamProvider">(上游 <code>{{ proxyRuntime.upstreamProvider }}</code>)</span>
|
|
527
|
-
</div>
|
|
528
|
-
</div>
|
|
529
500
|
</template>
|
|
530
501
|
|
|
531
502
|
<div class="selector-section">
|
|
@@ -538,18 +509,18 @@
|
|
|
538
509
|
</div>
|
|
539
510
|
|
|
540
511
|
<div v-if="!loading && !initError" class="card-list">
|
|
541
|
-
<div v-for="provider in
|
|
542
|
-
:class="['card', { active:
|
|
512
|
+
<div v-for="provider in displayProvidersList" :key="provider.name"
|
|
513
|
+
:class="['card', { active: displayCurrentProvider === provider.name }]"
|
|
543
514
|
@click="switchProvider(provider.name)">
|
|
544
515
|
<div class="card-leading">
|
|
545
516
|
<div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}</div>
|
|
546
517
|
<div class="card-content">
|
|
547
518
|
<div class="card-title">
|
|
548
519
|
<span>{{ provider.name }}</span>
|
|
549
|
-
<span v-if="provider.readOnly" class="provider-readonly-badge"
|
|
520
|
+
<span v-if="provider.readOnly" class="provider-readonly-badge">系统</span>
|
|
550
521
|
</div>
|
|
551
522
|
<div class="card-subtitle">
|
|
552
|
-
{{ provider.
|
|
523
|
+
{{ provider.url || '未设置 URL' }}
|
|
553
524
|
</div>
|
|
554
525
|
</div>
|
|
555
526
|
</div>
|
|
@@ -569,10 +540,11 @@
|
|
|
569
540
|
<button
|
|
570
541
|
v-if="!provider.readOnly"
|
|
571
542
|
class="card-action-btn"
|
|
572
|
-
:class="{ loading: providerShareLoading[provider.name]
|
|
573
|
-
|
|
543
|
+
:class="{ loading: providerShareLoading[provider.name] }"
|
|
544
|
+
disabled
|
|
574
545
|
@click="copyProviderShareCommand(provider)"
|
|
575
|
-
|
|
546
|
+
title="分享导入命令(暂时禁用)"
|
|
547
|
+
aria-label="Share import command">
|
|
576
548
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
577
549
|
<path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
|
|
578
550
|
<path d="M16 6l-4-4-4 4"/>
|
|
@@ -885,14 +857,16 @@
|
|
|
885
857
|
</div>
|
|
886
858
|
|
|
887
859
|
<div v-else :class="['session-layout', { 'session-standalone': sessionStandalone }]">
|
|
888
|
-
<div v-if="!sessionStandalone" class="session-list">
|
|
860
|
+
<div v-if="!sessionStandalone && sessionListRenderEnabled" class="session-list">
|
|
889
861
|
<div
|
|
890
|
-
v-for="session in
|
|
862
|
+
v-for="session in sortedSessionsList"
|
|
891
863
|
:key="session.source + '-' + session.sessionId + '-' + session.filePath"
|
|
864
|
+
v-memo="[activeSessionExportKey === getSessionExportKey(session), session.messageCount, session.updatedAt, session.title, session.sourceLabel, isSessionPinned(session), sessionsLoading]"
|
|
892
865
|
:class="[
|
|
893
866
|
'session-item',
|
|
894
867
|
{
|
|
895
|
-
active:
|
|
868
|
+
active: activeSessionExportKey === getSessionExportKey(session),
|
|
869
|
+
pinned: isSessionPinned(session)
|
|
896
870
|
}
|
|
897
871
|
]"
|
|
898
872
|
@click="selectSession(session)">
|
|
@@ -902,6 +876,20 @@
|
|
|
902
876
|
<span class="session-count-badge">{{ session.messageCount ?? 0 }}</span>
|
|
903
877
|
</div>
|
|
904
878
|
<div class="session-item-actions">
|
|
879
|
+
<button
|
|
880
|
+
class="session-item-copy session-item-pin"
|
|
881
|
+
@click.stop="toggleSessionPin(session)"
|
|
882
|
+
:disabled="sessionsLoading"
|
|
883
|
+
:aria-label="isSessionPinned(session) ? '取消置顶' : '置顶'"
|
|
884
|
+
:title="isSessionPinned(session) ? '取消置顶' : '置顶'"
|
|
885
|
+
:aria-pressed="isSessionPinned(session)">
|
|
886
|
+
<svg v-if="isSessionPinned(session)" class="pin-icon" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.6">
|
|
887
|
+
<path d="M12 22s8-6 8-12a8 8 0 1 0-16 0c0 6 8 12 8 12z"></path>
|
|
888
|
+
</svg>
|
|
889
|
+
<svg v-else class="pin-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
|
|
890
|
+
<path d="M12 22s8-6 8-12a8 8 0 1 0-16 0c0 6 8 12 8 12z"></path>
|
|
891
|
+
</svg>
|
|
892
|
+
</button>
|
|
905
893
|
<button
|
|
906
894
|
v-if="isResumeCommandAvailable(session)"
|
|
907
895
|
class="session-item-copy"
|
|
@@ -922,6 +910,7 @@
|
|
|
922
910
|
</div>
|
|
923
911
|
</div>
|
|
924
912
|
</div>
|
|
913
|
+
<div v-else-if="!sessionStandalone" class="session-list session-list-placeholder"></div>
|
|
925
914
|
|
|
926
915
|
<div :class="['session-preview', { active: !!activeSession }]" :ref="setSessionPreviewContainerRef">
|
|
927
916
|
<template v-if="activeSession">
|
|
@@ -946,7 +935,7 @@
|
|
|
946
935
|
class="btn-session-delete"
|
|
947
936
|
@click="deleteSession(activeSession)"
|
|
948
937
|
:disabled="!activeSession || sessionsLoading || sessionDeleting[getSessionExportKey(activeSession)]">
|
|
949
|
-
{{ (activeSession && sessionDeleting[getSessionExportKey(activeSession)]) ? '
|
|
938
|
+
{{ (activeSession && sessionDeleting[getSessionExportKey(activeSession)]) ? '移入中...' : '移入回收站' }}
|
|
950
939
|
</button>
|
|
951
940
|
<button
|
|
952
941
|
class="btn-session-export"
|
|
@@ -964,7 +953,7 @@
|
|
|
964
953
|
</div>
|
|
965
954
|
</div>
|
|
966
955
|
|
|
967
|
-
<div v-if="sessionDetailLoading" class="session-preview-empty">
|
|
956
|
+
<div v-if="sessionDetailLoading && !sessionPreviewLoadingMore" class="session-preview-empty">
|
|
968
957
|
正在加载会话内容...
|
|
969
958
|
</div>
|
|
970
959
|
|
|
@@ -976,16 +965,42 @@
|
|
|
976
965
|
当前会话暂无可展示消息
|
|
977
966
|
</div>
|
|
978
967
|
|
|
968
|
+
<div v-else-if="sessionPreviewRenderEnabled && !activeSessionVisibleMessages.length" class="session-preview-empty">
|
|
969
|
+
<span>正在渲染会话内容...</span>
|
|
970
|
+
<button class="btn-session-refresh" @click="primeSessionPreviewMessageRender" :disabled="sessionDetailLoading">
|
|
971
|
+
重新渲染
|
|
972
|
+
</button>
|
|
973
|
+
</div>
|
|
974
|
+
|
|
975
|
+
<div v-else-if="!sessionPreviewRenderEnabled" class="session-preview-empty">
|
|
976
|
+
正在准备会话内容...
|
|
977
|
+
</div>
|
|
978
|
+
|
|
979
979
|
<div v-else class="session-preview-body">
|
|
980
980
|
<div class="session-preview-messages">
|
|
981
981
|
<div v-if="activeSessionDetailClipped" class="session-item-sub session-item-wrap">
|
|
982
982
|
仅展示最近 {{ activeSessionMessages.length }} 条消息。
|
|
983
983
|
</div>
|
|
984
984
|
<div
|
|
985
|
-
v-
|
|
985
|
+
v-if="canLoadMoreSessionMessages"
|
|
986
|
+
class="session-item-sub session-item-wrap"
|
|
987
|
+
style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
|
|
988
|
+
<span>已显示 {{ activeSessionVisibleMessages.length }} / {{ activeSessionMessages.length }} 条</span>
|
|
989
|
+
<button class="btn-session-refresh" @click="loadMoreSessionMessages()" :disabled="sessionDetailLoading || sessionPreviewLoadingMore">
|
|
990
|
+
{{ sessionPreviewLoadingMore ? '加载中...' : ('加载更多(剩余 ' + sessionPreviewRemainingCount + ')') }}
|
|
991
|
+
</button>
|
|
992
|
+
</div>
|
|
993
|
+
<div
|
|
994
|
+
v-if="sessionPreviewLoadingMore"
|
|
995
|
+
class="session-item-sub session-item-wrap">
|
|
996
|
+
正在加载更早消息...
|
|
997
|
+
</div>
|
|
998
|
+
<div
|
|
999
|
+
v-for="(msg, idx) in activeSessionVisibleMessages"
|
|
986
1000
|
:key="getRecordRenderKey(msg, idx)"
|
|
1001
|
+
v-memo="[msg.text, msg.timestamp, msg.roleLabel, msg.normalizedRole]"
|
|
987
1002
|
:data-message-key="getRecordRenderKey(msg, idx)"
|
|
988
|
-
:ref="(
|
|
1003
|
+
:ref="getSessionMessageRefBinder(getRecordRenderKey(msg, idx))"
|
|
989
1004
|
:class="['session-msg', msg.normalizedRole === 'user' ? 'user' : (msg.normalizedRole === 'system' ? 'system' : 'assistant')]">
|
|
990
1005
|
<div class="session-msg-header">
|
|
991
1006
|
<div class="session-msg-meta">
|
|
@@ -998,11 +1013,12 @@
|
|
|
998
1013
|
</div>
|
|
999
1014
|
</div>
|
|
1000
1015
|
</div>
|
|
1001
|
-
<aside v-if="sessionTimelineNodes.length" class="session-timeline" aria-label="会话时间轴">
|
|
1016
|
+
<aside v-if="sessionPreviewRenderEnabled && sessionTimelineNodes.length" class="session-timeline" aria-label="会话时间轴">
|
|
1002
1017
|
<div class="session-timeline-track"></div>
|
|
1003
1018
|
<button
|
|
1004
1019
|
v-for="node in sessionTimelineNodes"
|
|
1005
1020
|
:key="'timeline-' + node.key"
|
|
1021
|
+
v-memo="[sessionTimelineActiveKey === node.key, node.safePercent, node.title]"
|
|
1006
1022
|
type="button"
|
|
1007
1023
|
:class="['session-timeline-node', { active: sessionTimelineActiveKey === node.key }]"
|
|
1008
1024
|
:aria-current="sessionTimelineActiveKey === node.key ? 'true' : null"
|
|
@@ -1033,39 +1049,315 @@
|
|
|
1033
1049
|
id="panel-settings"
|
|
1034
1050
|
role="tabpanel"
|
|
1035
1051
|
:aria-labelledby="'tab-settings'">
|
|
1036
|
-
<div class="
|
|
1037
|
-
<
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1052
|
+
<div class="config-subtabs settings-subtabs" role="tablist" aria-label="设置子标签">
|
|
1053
|
+
<button
|
|
1054
|
+
id="settings-tab-backup"
|
|
1055
|
+
role="tab"
|
|
1056
|
+
aria-controls="settings-panel-backup"
|
|
1057
|
+
:aria-selected="settingsTab === 'backup'"
|
|
1058
|
+
tabindex="0"
|
|
1059
|
+
:class="['config-subtab', { active: settingsTab === 'backup' }]"
|
|
1060
|
+
@click="onSettingsTabClick('backup')">
|
|
1061
|
+
备份与导入
|
|
1042
1062
|
</button>
|
|
1043
|
-
<button
|
|
1044
|
-
|
|
1063
|
+
<button
|
|
1064
|
+
id="settings-tab-trash"
|
|
1065
|
+
role="tab"
|
|
1066
|
+
aria-controls="settings-panel-trash"
|
|
1067
|
+
:aria-selected="settingsTab === 'trash'"
|
|
1068
|
+
tabindex="0"
|
|
1069
|
+
:class="['config-subtab', { active: settingsTab === 'trash' }]"
|
|
1070
|
+
@click="onSettingsTabClick('trash')">
|
|
1071
|
+
回收站
|
|
1072
|
+
<span class="settings-tab-badge">{{ sessionTrashCount }}</span>
|
|
1045
1073
|
</button>
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1074
|
+
</div>
|
|
1075
|
+
|
|
1076
|
+
<div
|
|
1077
|
+
v-show="settingsTab === 'backup'"
|
|
1078
|
+
id="settings-panel-backup"
|
|
1079
|
+
role="tabpanel"
|
|
1080
|
+
aria-labelledby="settings-tab-backup">
|
|
1081
|
+
<div class="selector-section">
|
|
1082
|
+
<div class="selector-header">
|
|
1083
|
+
<span class="selector-title">Claude 配置</span>
|
|
1084
|
+
</div>
|
|
1085
|
+
<button class="btn-tool" @click="downloadClaudeDirectory" :disabled="claudeDownloadLoading">
|
|
1086
|
+
{{ claudeDownloadLoading ? ('备份中 ' + claudeDownloadProgress + '%') : '一键备份 ~/.claude' }}
|
|
1087
|
+
</button>
|
|
1088
|
+
<button class="btn-tool" @click="triggerClaudeImport" :disabled="claudeImportLoading">
|
|
1089
|
+
{{ claudeImportLoading ? '导入中...' : '导入 ~/.claude 备份' }}
|
|
1090
|
+
</button>
|
|
1091
|
+
<input
|
|
1092
|
+
ref="claudeImportInput"
|
|
1093
|
+
class="sr-only"
|
|
1094
|
+
type="file"
|
|
1095
|
+
accept=".zip"
|
|
1096
|
+
@change="handleClaudeImportChange">
|
|
1097
|
+
</div>
|
|
1098
|
+
<div class="selector-section">
|
|
1099
|
+
<div class="selector-header">
|
|
1100
|
+
<span class="selector-title">Codex 配置</span>
|
|
1101
|
+
</div>
|
|
1102
|
+
<button class="btn-tool" @click="downloadCodexDirectory" :disabled="codexDownloadLoading">
|
|
1103
|
+
{{ codexDownloadLoading ? ('备份中 ' + codexDownloadProgress + '%') : '一键备份 ~/.codex' }}
|
|
1104
|
+
</button>
|
|
1105
|
+
<button class="btn-tool" @click="triggerCodexImport" :disabled="codexImportLoading">
|
|
1106
|
+
{{ codexImportLoading ? '导入中...' : '导入 ~/.codex 备份' }}
|
|
1107
|
+
</button>
|
|
1108
|
+
<input
|
|
1109
|
+
ref="codexImportInput"
|
|
1110
|
+
class="sr-only"
|
|
1111
|
+
type="file"
|
|
1112
|
+
accept=".zip"
|
|
1113
|
+
@change="handleCodexImportChange">
|
|
1114
|
+
</div>
|
|
1115
|
+
</div>
|
|
1116
|
+
|
|
1117
|
+
<div
|
|
1118
|
+
v-show="settingsTab === 'trash'"
|
|
1119
|
+
id="settings-panel-trash"
|
|
1120
|
+
role="tabpanel"
|
|
1121
|
+
aria-labelledby="settings-tab-trash">
|
|
1122
|
+
<div class="selector-section">
|
|
1123
|
+
<div class="selector-header settings-tab-header">
|
|
1124
|
+
<div>
|
|
1125
|
+
<span class="selector-title">会话回收站</span>
|
|
1126
|
+
</div>
|
|
1127
|
+
<div class="settings-tab-actions">
|
|
1128
|
+
<button class="btn-tool btn-tool-compact" @click="loadSessionTrash({ forceRefresh: true })" :disabled="sessionTrashLoading || sessionTrashClearing">
|
|
1129
|
+
{{ sessionTrashLoading ? '刷新中...' : '刷新列表' }}
|
|
1130
|
+
</button>
|
|
1131
|
+
<button class="btn-tool btn-tool-compact" @click="clearSessionTrash" :disabled="sessionTrashClearing || sessionTrashLoading || !(Number(sessionTrashCount) > 0)">
|
|
1132
|
+
{{ sessionTrashClearing ? '清空中...' : '清空回收站' }}
|
|
1133
|
+
</button>
|
|
1134
|
+
</div>
|
|
1135
|
+
</div>
|
|
1136
|
+
|
|
1137
|
+
<div v-if="getSessionTrashViewState() === 'loading'" class="session-empty">
|
|
1138
|
+
正在加载回收站...
|
|
1139
|
+
</div>
|
|
1140
|
+
<div v-else-if="getSessionTrashViewState() === 'empty'" class="session-empty">
|
|
1141
|
+
回收站为空
|
|
1142
|
+
</div>
|
|
1143
|
+
<div v-else-if="getSessionTrashViewState() === 'retry'" class="session-empty">
|
|
1144
|
+
回收站列表加载失败,请刷新重试
|
|
1145
|
+
</div>
|
|
1146
|
+
<div v-else class="trash-list">
|
|
1147
|
+
<div v-for="item in visibleSessionTrashItems" :key="item.trashId" class="trash-item session-item session-card">
|
|
1148
|
+
<div class="trash-item-header session-item-header">
|
|
1149
|
+
<div class="trash-item-main">
|
|
1150
|
+
<div class="trash-item-mainline">
|
|
1151
|
+
<div class="trash-item-title">{{ item.title || item.sessionId }}</div>
|
|
1152
|
+
<span class="session-count-badge">{{ item.messageCount ?? 0 }}</span>
|
|
1153
|
+
</div>
|
|
1154
|
+
<div class="trash-item-meta session-item-meta">
|
|
1155
|
+
<span class="session-source">{{ item.sourceLabel }}</span>
|
|
1156
|
+
</div>
|
|
1157
|
+
</div>
|
|
1158
|
+
<div class="trash-item-side">
|
|
1159
|
+
<div class="trash-item-actions session-item-actions">
|
|
1160
|
+
<button class="btn-mini" @click="restoreSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)">
|
|
1161
|
+
{{ sessionTrashRestoring[getSessionTrashActionKey(item)] ? '恢复中...' : '恢复' }}
|
|
1162
|
+
</button>
|
|
1163
|
+
<button class="btn-mini delete" @click="purgeSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)">
|
|
1164
|
+
{{ sessionTrashPurging[getSessionTrashActionKey(item)] ? '删除中...' : '彻底删除' }}
|
|
1165
|
+
</button>
|
|
1166
|
+
</div>
|
|
1167
|
+
<div class="trash-item-time session-item-time">{{ item.deletedAt || item.updatedAt || 'unknown time' }}</div>
|
|
1168
|
+
</div>
|
|
1169
|
+
</div>
|
|
1170
|
+
<div v-if="item.cwd" class="trash-item-path session-item-sub session-item-wrap">
|
|
1171
|
+
<span class="trash-item-label">工作区</span>
|
|
1172
|
+
<span>{{ item.cwd }}</span>
|
|
1173
|
+
</div>
|
|
1174
|
+
<div class="trash-item-path session-item-sub session-item-wrap">
|
|
1175
|
+
<span class="trash-item-label">原文件</span>
|
|
1176
|
+
<span>{{ item.originalFilePath }}</span>
|
|
1177
|
+
</div>
|
|
1178
|
+
</div>
|
|
1179
|
+
<div v-if="sessionTrashHasMoreItems" class="trash-list-footer">
|
|
1180
|
+
<button class="btn-tool btn-tool-compact" @click="loadMoreSessionTrashItems" :disabled="sessionTrashLoading || sessionTrashClearing">
|
|
1181
|
+
加载更多(剩余 {{ sessionTrashHiddenCount }} 项)
|
|
1182
|
+
</button>
|
|
1183
|
+
</div>
|
|
1184
|
+
</div>
|
|
1185
|
+
</div>
|
|
1186
|
+
</div>
|
|
1187
|
+
</div>
|
|
1188
|
+
|
|
1189
|
+
<div
|
|
1190
|
+
v-show="mainTab === 'market'"
|
|
1191
|
+
class="mode-content"
|
|
1192
|
+
id="panel-market"
|
|
1193
|
+
role="tabpanel"
|
|
1194
|
+
aria-labelledby="tab-market">
|
|
1195
|
+
<div class="selector-section market-overview-section">
|
|
1196
|
+
<div class="selector-header market-overview-header">
|
|
1197
|
+
<div>
|
|
1198
|
+
<span class="selector-title">Skills 市场概览</span>
|
|
1199
|
+
<div class="skills-panel-note">当前市场页只保留本地 skills 能力:切换 Codex / Claude Code 安装目标,查看已安装项,执行跨应用导入与 ZIP 分发。</div>
|
|
1200
|
+
</div>
|
|
1201
|
+
<div class="settings-tab-actions market-header-actions">
|
|
1202
|
+
<button class="btn-tool btn-tool-compact" @click="loadSkillsMarketOverview({ forceRefresh: true, silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1203
|
+
{{ skillsMarketLoading ? '刷新中...' : '刷新概览' }}
|
|
1204
|
+
</button>
|
|
1205
|
+
<button class="btn-tool btn-tool-compact" @click="openSkillsManager" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1206
|
+
打开 Skills 管理
|
|
1207
|
+
</button>
|
|
1208
|
+
</div>
|
|
1209
|
+
</div>
|
|
1210
|
+
|
|
1211
|
+
<div class="market-target-switch" role="group" aria-label="选择 Skills 安装目标">
|
|
1212
|
+
<button
|
|
1213
|
+
type="button"
|
|
1214
|
+
:class="['market-target-chip', { active: skillsTargetApp === 'codex' }]"
|
|
1215
|
+
:aria-pressed="skillsTargetApp === 'codex'"
|
|
1216
|
+
:disabled="loading || !!initError || skillsMarketBusy"
|
|
1217
|
+
@click="setSkillsTargetApp('codex', { silent: false })">
|
|
1218
|
+
Codex
|
|
1219
|
+
</button>
|
|
1220
|
+
<button
|
|
1221
|
+
type="button"
|
|
1222
|
+
:class="['market-target-chip', { active: skillsTargetApp === 'claude' }]"
|
|
1223
|
+
:aria-pressed="skillsTargetApp === 'claude'"
|
|
1224
|
+
:disabled="loading || !!initError || skillsMarketBusy"
|
|
1225
|
+
@click="setSkillsTargetApp('claude', { silent: false })">
|
|
1226
|
+
Claude Code
|
|
1227
|
+
</button>
|
|
1228
|
+
</div>
|
|
1229
|
+
|
|
1230
|
+
<div class="skills-root-box market-root-box">{{ skillsRootPath || skillsDefaultRootPath }}</div>
|
|
1231
|
+
|
|
1232
|
+
<div class="skills-summary-strip market-summary-strip">
|
|
1233
|
+
<div class="skills-summary-item">
|
|
1234
|
+
<span class="skills-summary-label">安装目标</span>
|
|
1235
|
+
<strong class="skills-summary-value">{{ skillsTargetLabel }}</strong>
|
|
1236
|
+
</div>
|
|
1237
|
+
<div class="skills-summary-item">
|
|
1238
|
+
<span class="skills-summary-label">本地总数</span>
|
|
1239
|
+
<strong class="skills-summary-value">{{ skillsList.length }}</strong>
|
|
1240
|
+
</div>
|
|
1241
|
+
<div class="skills-summary-item">
|
|
1242
|
+
<span class="skills-summary-label">含 SKILL.md</span>
|
|
1243
|
+
<strong class="skills-summary-value">{{ skillsConfiguredCount }}</strong>
|
|
1244
|
+
</div>
|
|
1245
|
+
<div class="skills-summary-item">
|
|
1246
|
+
<span class="skills-summary-label">缺少 SKILL.md</span>
|
|
1247
|
+
<strong class="skills-summary-value">{{ skillsMissingSkillFileCount }}</strong>
|
|
1248
|
+
</div>
|
|
1249
|
+
<div class="skills-summary-item">
|
|
1250
|
+
<span class="skills-summary-label">可导入</span>
|
|
1251
|
+
<strong class="skills-summary-value">{{ skillsImportList.length }}</strong>
|
|
1252
|
+
</div>
|
|
1253
|
+
<div class="skills-summary-item">
|
|
1254
|
+
<span class="skills-summary-label">可直接导入</span>
|
|
1255
|
+
<strong class="skills-summary-value">{{ skillsImportConfiguredCount }}</strong>
|
|
1256
|
+
</div>
|
|
1257
|
+
</div>
|
|
1258
|
+
</div>
|
|
1259
|
+
|
|
1260
|
+
<div class="market-grid">
|
|
1261
|
+
<div class="skills-panel market-panel">
|
|
1262
|
+
<div class="skills-panel-header">
|
|
1263
|
+
<div class="skills-panel-title-wrap">
|
|
1264
|
+
<div class="skills-panel-title">已安装 Skills</div>
|
|
1265
|
+
<div class="skills-panel-note">展示当前已落地到 <code>{{ skillsRootPath || skillsDefaultRootPath }}</code> 的前 6 个目录,可继续进入管理弹窗做筛选、导出和删除。</div>
|
|
1266
|
+
</div>
|
|
1267
|
+
<button class="btn-mini" @click="refreshSkillsList({ silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1268
|
+
{{ skillsLoading ? '刷新中...' : '刷新本地' }}
|
|
1269
|
+
</button>
|
|
1270
|
+
</div>
|
|
1271
|
+
<div v-if="skillsLoading && !skillsMarketLocalLoadedOnce" class="skills-empty-state">正在加载本地 Skills...</div>
|
|
1272
|
+
<div v-else-if="skillsList.length === 0" class="skills-empty-state">当前还没有已安装 skill,可通过 ZIP 或跨应用导入补充。</div>
|
|
1273
|
+
<div v-else class="market-preview-list">
|
|
1274
|
+
<div v-for="skill in skillsMarketInstalledPreview" :key="'market-local-' + skill.name" class="market-preview-item">
|
|
1275
|
+
<div class="market-preview-main">
|
|
1276
|
+
<div class="market-preview-title">{{ skill.displayName || skill.name }}</div>
|
|
1277
|
+
<div class="market-preview-meta">{{ skill.description || skill.path }}</div>
|
|
1278
|
+
</div>
|
|
1279
|
+
<span :class="['pill', skill.hasSkillFile ? 'configured' : 'empty']">
|
|
1280
|
+
{{ skill.hasSkillFile ? '已验证' : '待补 SKILL.md' }}
|
|
1281
|
+
</span>
|
|
1282
|
+
</div>
|
|
1283
|
+
</div>
|
|
1284
|
+
</div>
|
|
1285
|
+
|
|
1286
|
+
<div class="skills-panel market-panel">
|
|
1287
|
+
<div class="skills-panel-header">
|
|
1288
|
+
<div class="skills-panel-title-wrap">
|
|
1289
|
+
<div class="skills-panel-title">可导入来源</div>
|
|
1290
|
+
<div class="skills-panel-note">扫描其他应用下未托管的 skill,先确认来源和目录,再批量导入到当前 {{ skillsTargetLabel }} skills 目录。</div>
|
|
1291
|
+
</div>
|
|
1292
|
+
<button class="btn-mini" @click="scanImportableSkills({ silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1293
|
+
{{ skillsScanningImports ? '扫描中...' : '扫描来源' }}
|
|
1294
|
+
</button>
|
|
1295
|
+
</div>
|
|
1296
|
+
<div v-if="skillsScanningImports && !skillsMarketImportLoadedOnce" class="skills-empty-state">正在扫描可导入 skill...</div>
|
|
1297
|
+
<div v-else-if="skillsImportList.length === 0" class="skills-empty-state">还没有扫描到可导入 skill,可点击“扫描来源”重新读取。</div>
|
|
1298
|
+
<div v-else class="market-preview-list">
|
|
1299
|
+
<div v-for="skill in skillsMarketImportPreview" :key="'market-import-' + buildSkillImportKey(skill)" class="market-preview-item">
|
|
1300
|
+
<div class="market-preview-main">
|
|
1301
|
+
<div class="market-preview-title">{{ skill.displayName || skill.name }}</div>
|
|
1302
|
+
<div class="market-preview-meta">{{ skill.sourceLabel }} · {{ skill.sourcePath }}</div>
|
|
1303
|
+
</div>
|
|
1304
|
+
<span :class="['pill', skill.hasSkillFile ? 'configured' : 'empty']">
|
|
1305
|
+
{{ skill.hasSkillFile ? '可直接导入' : '缺少 SKILL.md' }}
|
|
1306
|
+
</span>
|
|
1307
|
+
</div>
|
|
1308
|
+
</div>
|
|
1309
|
+
</div>
|
|
1310
|
+
|
|
1311
|
+
<div class="skills-panel market-panel market-actions-panel">
|
|
1312
|
+
<div class="skills-panel-header">
|
|
1313
|
+
<div class="skills-panel-title-wrap">
|
|
1314
|
+
<div class="skills-panel-title">分发入口</div>
|
|
1315
|
+
<div class="skills-panel-note">市场页聚焦本地落地:本地管理、跨应用导入、ZIP 分发,全部作用到当前安装目标。</div>
|
|
1316
|
+
</div>
|
|
1317
|
+
</div>
|
|
1318
|
+
<div class="market-action-grid">
|
|
1319
|
+
<button class="market-action-card" @click="openSkillsManager" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1320
|
+
<span class="market-action-title">本地 Skills 管理</span>
|
|
1321
|
+
<span class="market-action-copy">查看、筛选、导出、删除当前 {{ skillsTargetLabel }} 已安装 skills</span>
|
|
1322
|
+
</button>
|
|
1323
|
+
<button class="market-action-card" @click="scanImportableSkills({ silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1324
|
+
<span class="market-action-title">跨应用导入</span>
|
|
1325
|
+
<span class="market-action-copy">扫描其他应用目录并导入到当前 {{ skillsTargetLabel }}</span>
|
|
1326
|
+
</button>
|
|
1327
|
+
<button class="market-action-card" @click="triggerSkillsZipImport" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1328
|
+
<span class="market-action-title">ZIP 导入</span>
|
|
1329
|
+
<span class="market-action-copy">从压缩包分发并安装到当前目标宿主</span>
|
|
1330
|
+
</button>
|
|
1331
|
+
</div>
|
|
1332
|
+
</div>
|
|
1333
|
+
|
|
1334
|
+
<div class="skills-panel market-panel market-panel-wide">
|
|
1335
|
+
<div class="skills-panel-header">
|
|
1336
|
+
<div class="skills-panel-title-wrap">
|
|
1337
|
+
<div class="skills-panel-title">市场说明</div>
|
|
1338
|
+
</div>
|
|
1339
|
+
</div>
|
|
1340
|
+
<div class="market-preview-list">
|
|
1341
|
+
<div class="market-preview-item">
|
|
1342
|
+
<div class="market-preview-main">
|
|
1343
|
+
<div class="market-preview-title">目标宿主切换</div>
|
|
1344
|
+
<div class="market-preview-meta">在 Codex 和 Claude Code 之间切换后,后续扫描、导入、导出、删除都会落到当前 {{ skillsTargetLabel }} 目录。</div>
|
|
1345
|
+
</div>
|
|
1346
|
+
</div>
|
|
1347
|
+
<div class="market-preview-item">
|
|
1348
|
+
<div class="market-preview-main">
|
|
1349
|
+
<div class="market-preview-title">跨应用导入</div>
|
|
1350
|
+
<div class="market-preview-meta">扫描 `Codex`、`Claude Code` 与 `Agents` 目录里的未托管 skills,筛选后批量导入到当前宿主。</div>
|
|
1351
|
+
</div>
|
|
1352
|
+
</div>
|
|
1353
|
+
<div class="market-preview-item">
|
|
1354
|
+
<div class="market-preview-main">
|
|
1355
|
+
<div class="market-preview-title">ZIP 分发</div>
|
|
1356
|
+
<div class="market-preview-meta">通过压缩包在不同环境间分发技能目录,保持本地可控,不依赖外部目录服务。</div>
|
|
1357
|
+
</div>
|
|
1358
|
+
</div>
|
|
1359
|
+
</div>
|
|
1056
1360
|
</div>
|
|
1057
|
-
<button class="btn-tool" @click="downloadCodexDirectory" :disabled="codexDownloadLoading">
|
|
1058
|
-
{{ codexDownloadLoading ? ('备份中 ' + codexDownloadProgress + '%') : '一键备份 ~/.codex' }}
|
|
1059
|
-
</button>
|
|
1060
|
-
<button class="btn-tool" @click="triggerCodexImport" :disabled="codexImportLoading">
|
|
1061
|
-
{{ codexImportLoading ? '导入中...' : '导入 ~/.codex 备份' }}
|
|
1062
|
-
</button>
|
|
1063
|
-
<input
|
|
1064
|
-
ref="codexImportInput"
|
|
1065
|
-
class="sr-only"
|
|
1066
|
-
type="file"
|
|
1067
|
-
accept=".zip"
|
|
1068
|
-
@change="handleCodexImportChange">
|
|
1069
1361
|
</div>
|
|
1070
1362
|
</div>
|
|
1071
1363
|
|
|
@@ -1134,8 +1426,6 @@
|
|
|
1134
1426
|
<span :class="['value', 'tone-' + inspectorHealthTone]">{{ inspectorHealthStatus }}</span>
|
|
1135
1427
|
<span class="key">模型加载</span>
|
|
1136
1428
|
<span class="value">{{ inspectorModelLoadStatus }}</span>
|
|
1137
|
-
<span class="key">代理状态</span>
|
|
1138
|
-
<span class="value">{{ inspectorProxyStatus }}</span>
|
|
1139
1429
|
</div>
|
|
1140
1430
|
</section>
|
|
1141
1431
|
</aside>
|
|
@@ -1540,7 +1830,7 @@
|
|
|
1540
1830
|
<div class="structured-card">
|
|
1541
1831
|
<div class="structured-card-title">Providers(只读)</div>
|
|
1542
1832
|
<div v-if="openclawProviders.length === 0" class="form-hint">
|
|
1543
|
-
未发现 providers
|
|
1833
|
+
未发现 providers 配置(可能使用环境变量或外部配置)。
|
|
1544
1834
|
</div>
|
|
1545
1835
|
<div v-else class="provider-list">
|
|
1546
1836
|
<div class="provider-item" v-for="(provider, index) in openclawProviders" :key="provider.key + '-' + provider.source + '-' + index">
|
|
@@ -1669,24 +1959,70 @@
|
|
|
1669
1959
|
</div>
|
|
1670
1960
|
</div>
|
|
1671
1961
|
|
|
1962
|
+
|
|
1672
1963
|
<div class="form-group">
|
|
1673
1964
|
<label class="form-label">AGENTS.md 内容</label>
|
|
1965
|
+
<div
|
|
1966
|
+
v-if="!agentsLoading && (hasAgentsContentChanged() || agentsDiffVisible)"
|
|
1967
|
+
class="agents-diff-save-alert">
|
|
1968
|
+
{{ agentsDiffVisible ? '预览模式:当前改动尚未保存,只有点击“应用”后才会写入文件。' : '检测到未保存改动:关闭页面或应用前请先保存。' }}
|
|
1969
|
+
</div>
|
|
1970
|
+
<div v-if="agentsDiffVisible">
|
|
1971
|
+
<div
|
|
1972
|
+
v-if="!agentsDiffLoading && !agentsDiffError && !agentsDiffTruncated && (agentsDiffStats.added || agentsDiffStats.removed)"
|
|
1973
|
+
class="agents-diff-summary">
|
|
1974
|
+
<span class="agents-diff-stat add">+{{ agentsDiffStats.added }}</span>
|
|
1975
|
+
<span class="agents-diff-stat del">-{{ agentsDiffStats.removed }}</span>
|
|
1976
|
+
</div>
|
|
1977
|
+
<div v-if="agentsDiffLoading" class="state-message">生成差异中...</div>
|
|
1978
|
+
<div v-else-if="agentsDiffError" class="state-message error">{{ agentsDiffError }}</div>
|
|
1979
|
+
<div v-else-if="agentsDiffTruncated" class="agents-diff-empty">内容过大,已跳过逐行差异预览</div>
|
|
1980
|
+
<div v-else-if="!agentsDiffHasChanges" class="agents-diff-empty">未检测到改动</div>
|
|
1981
|
+
<div v-else class="agents-diff-view agents-diff-editor">
|
|
1982
|
+
<div
|
|
1983
|
+
v-for="(line, index) in agentsDiffLines"
|
|
1984
|
+
:key="line.key || (line.type + '-' + index)"
|
|
1985
|
+
:class="['agents-diff-line', line.type]">
|
|
1986
|
+
<span class="agents-diff-line-sign">
|
|
1987
|
+
{{ line.type === 'add' ? '+' : (line.type === 'del' ? '-' : ' ') }}
|
|
1988
|
+
</span>
|
|
1989
|
+
<span class="agents-diff-line-text">{{ line.value }}</span>
|
|
1990
|
+
</div>
|
|
1991
|
+
</div>
|
|
1992
|
+
</div>
|
|
1674
1993
|
<textarea
|
|
1994
|
+
v-else
|
|
1675
1995
|
v-model="agentsContent"
|
|
1676
1996
|
class="form-input template-editor"
|
|
1677
1997
|
spellcheck="false"
|
|
1678
1998
|
:readonly="agentsLoading"
|
|
1999
|
+
@input="onAgentsContentInput"
|
|
1679
2000
|
placeholder="在这里编辑 AGENTS.md 内容"></textarea>
|
|
1680
2001
|
<div class="template-editor-warning">
|
|
1681
2002
|
{{ agentsModalHint }}
|
|
2003
|
+
<div class="agents-diff-hint">快捷键:Esc(差异预览时返回编辑,编辑时关闭窗口)。</div>
|
|
2004
|
+
<div v-if="!agentsDiffVisible" class="agents-diff-hint">保存需两步:先点击“确认”预览差异,再点击“应用”保存。</div>
|
|
2005
|
+
<div v-else-if="agentsDiffLoading || agentsSaving" class="agents-diff-hint">正在生成差异或应用中,操作暂不可用。</div>
|
|
2006
|
+
<div v-else-if="agentsDiffError" class="agents-diff-hint">差异预览失败,请返回编辑后重试。</div>
|
|
2007
|
+
<div v-else-if="!agentsDiffHasChanges" class="agents-diff-hint">未检测到改动,可返回编辑继续修改或取消退出。</div>
|
|
2008
|
+
<div v-else-if="agentsDiffTruncated" class="agents-diff-hint">内容过大,已跳过预览,可点击“应用”保存或“返回编辑”继续修改。</div>
|
|
2009
|
+
<div v-else class="agents-diff-hint">当前为预览模式,可点击“应用”保存或“返回编辑”继续修改。</div>
|
|
1682
2010
|
</div>
|
|
1683
2011
|
</div>
|
|
2012
|
+
|
|
1684
2013
|
</div>
|
|
1685
2014
|
|
|
1686
2015
|
<div class="btn-group modal-editor-footer">
|
|
1687
|
-
<button class="btn btn-cancel" @click="closeAgentsModal">取消</button>
|
|
1688
|
-
<button
|
|
1689
|
-
|
|
2016
|
+
<button class="btn btn-cancel" @click="closeAgentsModal" :disabled="agentsSaving || agentsDiffLoading">取消</button>
|
|
2017
|
+
<button
|
|
2018
|
+
v-if="agentsDiffVisible"
|
|
2019
|
+
class="btn"
|
|
2020
|
+
@click="resetAgentsDiffState"
|
|
2021
|
+
:disabled="agentsSaving || agentsDiffLoading">
|
|
2022
|
+
返回编辑
|
|
2023
|
+
</button>
|
|
2024
|
+
<button class="btn btn-confirm" @click="applyAgentsContent" :disabled="agentsSaving || agentsLoading || agentsDiffLoading || (agentsDiffVisible && !agentsDiffHasChanges)">
|
|
2025
|
+
{{ agentsSaving ? (agentsDiffVisible ? '应用中...' : '确认中...') : (agentsDiffVisible ? '应用' : '确认') }}
|
|
1690
2026
|
</button>
|
|
1691
2027
|
</div>
|
|
1692
2028
|
</div>
|
|
@@ -1697,9 +2033,27 @@
|
|
|
1697
2033
|
<div class="modal-header skills-modal-header">
|
|
1698
2034
|
<div>
|
|
1699
2035
|
<div class="modal-title">Skills 管理</div>
|
|
1700
|
-
<div class="skills-modal-subtitle"
|
|
2036
|
+
<div class="skills-modal-subtitle">集中管理当前宿主的本地技能目录,支持检索筛选、多选删除、跨应用导入、ZIP 导入与导出。</div>
|
|
1701
2037
|
</div>
|
|
1702
2038
|
<div class="modal-header-actions skills-modal-actions">
|
|
2039
|
+
<div class="market-target-switch market-target-switch-compact" role="group" aria-label="选择 Skills 管理目标">
|
|
2040
|
+
<button
|
|
2041
|
+
type="button"
|
|
2042
|
+
:class="['market-target-chip', { active: skillsTargetApp === 'codex' }]"
|
|
2043
|
+
:aria-pressed="skillsTargetApp === 'codex'"
|
|
2044
|
+
:disabled="loading || !!initError || skillsMarketBusy"
|
|
2045
|
+
@click="setSkillsTargetApp('codex', { silent: false })">
|
|
2046
|
+
Codex
|
|
2047
|
+
</button>
|
|
2048
|
+
<button
|
|
2049
|
+
type="button"
|
|
2050
|
+
:class="['market-target-chip', { active: skillsTargetApp === 'claude' }]"
|
|
2051
|
+
:aria-pressed="skillsTargetApp === 'claude'"
|
|
2052
|
+
:disabled="loading || !!initError || skillsMarketBusy"
|
|
2053
|
+
@click="setSkillsTargetApp('claude', { silent: false })">
|
|
2054
|
+
Claude Code
|
|
2055
|
+
</button>
|
|
2056
|
+
</div>
|
|
1703
2057
|
<button class="btn-mini" @click="refreshSkillsList({ silent: false })" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
1704
2058
|
{{ skillsLoading ? '刷新中...' : '刷新' }}
|
|
1705
2059
|
</button>
|
|
@@ -1707,11 +2061,15 @@
|
|
|
1707
2061
|
</div>
|
|
1708
2062
|
|
|
1709
2063
|
<div class="form-group skills-root-group">
|
|
1710
|
-
<label class="form-label">Skills
|
|
1711
|
-
<div class="skills-root-box">{{ skillsRootPath ||
|
|
2064
|
+
<label class="form-label">Skills 目录({{ skillsTargetLabel }})</label>
|
|
2065
|
+
<div class="skills-root-box">{{ skillsRootPath || skillsDefaultRootPath }}</div>
|
|
1712
2066
|
</div>
|
|
1713
2067
|
|
|
1714
2068
|
<div class="skills-summary-strip">
|
|
2069
|
+
<div class="skills-summary-item">
|
|
2070
|
+
<span class="skills-summary-label">安装目标</span>
|
|
2071
|
+
<strong class="skills-summary-value">{{ skillsTargetLabel }}</strong>
|
|
2072
|
+
</div>
|
|
1715
2073
|
<div class="skills-summary-item">
|
|
1716
2074
|
<span class="skills-summary-label">本地总数</span>
|
|
1717
2075
|
<strong class="skills-summary-value">{{ skillsList.length }}</strong>
|
|
@@ -1734,7 +2092,7 @@
|
|
|
1734
2092
|
<div class="skills-panel-header">
|
|
1735
2093
|
<div class="skills-panel-title-wrap">
|
|
1736
2094
|
<div class="skills-panel-title">本地 Skills</div>
|
|
1737
|
-
<div class="skills-panel-note"
|
|
2095
|
+
<div class="skills-panel-note">支持关键词检索与状态筛选,勾选后可批量删除当前 {{ skillsTargetLabel }} 本地 skill。</div>
|
|
1738
2096
|
</div>
|
|
1739
2097
|
<button
|
|
1740
2098
|
class="btn-mini"
|
|
@@ -1793,8 +2151,8 @@
|
|
|
1793
2151
|
<div class="skills-panel skills-import-block">
|
|
1794
2152
|
<div class="skills-panel-header">
|
|
1795
2153
|
<div class="skills-panel-title-wrap">
|
|
1796
|
-
<div class="skills-import-title"
|
|
1797
|
-
<div class="skills-panel-note"
|
|
2154
|
+
<div class="skills-import-title">跨应用导入</div>
|
|
2155
|
+
<div class="skills-panel-note">从其他应用扫描并导入未托管技能,支持多选批量导入到当前 {{ skillsTargetLabel }}。</div>
|
|
1798
2156
|
</div>
|
|
1799
2157
|
<button class="btn-mini" @click="scanImportableSkills" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
1800
2158
|
{{ skillsScanningImports ? '扫描中...' : '扫描可导入' }}
|
|
@@ -1845,12 +2203,35 @@
|
|
|
1845
2203
|
{{ skillsDeleting ? '删除中...' : '删除选中' }}
|
|
1846
2204
|
</button>
|
|
1847
2205
|
</div>
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
2206
|
+
</div>
|
|
2207
|
+
</div>
|
|
2208
|
+
|
|
2209
|
+
<input
|
|
2210
|
+
ref="skillsZipImportInput"
|
|
2211
|
+
type="file"
|
|
2212
|
+
accept=".zip,application/zip"
|
|
2213
|
+
style="display:none"
|
|
2214
|
+
@change="handleSkillsZipImportChange">
|
|
2215
|
+
|
|
2216
|
+
<div v-if="showConfirmDialog" class="modal-overlay" @click.self="closeConfirmDialog">
|
|
2217
|
+
<div
|
|
2218
|
+
class="modal confirm-dialog"
|
|
2219
|
+
role="alertdialog"
|
|
2220
|
+
aria-modal="true"
|
|
2221
|
+
aria-describedby="confirm-dialog-message"
|
|
2222
|
+
:aria-labelledby="confirmDialogTitle ? 'confirm-dialog-title' : null"
|
|
2223
|
+
:aria-label="confirmDialogTitle ? null : '确认操作'">
|
|
2224
|
+
<div id="confirm-dialog-title" class="modal-title">{{ confirmDialogTitle }}</div>
|
|
2225
|
+
<div id="confirm-dialog-message" class="confirm-dialog-message">{{ confirmDialogMessage }}</div>
|
|
2226
|
+
<div class="btn-group confirm-dialog-actions">
|
|
2227
|
+
<button class="btn btn-cancel" @click="closeConfirmDialog">{{ confirmDialogCancelText }}</button>
|
|
2228
|
+
<button
|
|
2229
|
+
:class="['btn', 'btn-confirm', confirmDialogDanger ? 'btn-danger' : '']"
|
|
2230
|
+
:disabled="isConfirmDialogDisabled()"
|
|
2231
|
+
@click="resolveConfirmDialog(true)">
|
|
2232
|
+
{{ confirmDialogConfirmText }}
|
|
2233
|
+
</button>
|
|
2234
|
+
</div>
|
|
1854
2235
|
</div>
|
|
1855
2236
|
</div>
|
|
1856
2237
|
|