@templmf/temp-solf-lmf 0.0.107 → 0.0.108

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.
@@ -1,2322 +0,0 @@
1
- <template>
2
- <div class="skill-panel">
3
-
4
- <!-- ① 第一栏:导航菜单 -->
5
- <div class="skill-nav">
6
- <div class="skill-nav__header">
7
- <span class="skill-nav__title font-mono">Config</span>
8
- </div>
9
- <div class="skill-nav__list">
10
- <button
11
- v-for="item in navItems"
12
- :key="item.key"
13
- class="skill-nav__item"
14
- :class="{ active: activeNav === item.key }"
15
- @click="activeNav = item.key"
16
- >
17
- <component :is="item.icon" :size="14" :stroke-width="1.7" />
18
- <span>{{ item.label }}</span>
19
- <span v-if="item.badge != null" class="skill-nav__badge">{{ item.badge }}</span>
20
- </button>
21
- </div>
22
- </div>
23
-
24
- <!-- ② 第二栏:列表 -->
25
- <div class="skill-list-col">
26
- <!-- Skills 列表 -->
27
- <template v-if="activeNav === 'skills'">
28
- <div class="skill-list-col__header">
29
- <span class="skill-list-col__title font-mono">Skills</span>
30
- <el-tooltip content="新增自定义 Skill" placement="bottom" :show-after="400">
31
- <button class="skill-col-action-btn" @click="createNewSkill">
32
- <Plus :size="13" />
33
- </button>
34
- </el-tooltip>
35
- </div>
36
-
37
- <el-scrollbar style="flex:1;">
38
- <div class="skill-list-body">
39
- <!-- 系统内置 -->
40
- <div v-if="builtinSkills.length > 0" class="skill-list-group">
41
- <p class="skill-list-group__label font-mono">系统内置</p>
42
- <div
43
- v-for="s in builtinSkills"
44
- :key="s.name"
45
- class="skill-list-item"
46
- :class="{ active: selectedSkill?.name === s.name, disabled: !s.enabled }"
47
- @click="selectSkill(s)"
48
- >
49
- <div class="skill-list-item__icon">
50
- <Cpu :size="12" :stroke-width="1.8" />
51
- </div>
52
- <div class="skill-list-item__body">
53
- <span class="skill-list-item__name">{{ s.name }}</span>
54
- <span class="skill-list-item__desc">{{ s.description }}</span>
55
- </div>
56
- <div class="skill-list-item__status" :class="s.enabled ? 'on' : 'off'" />
57
- </div>
58
- </div>
59
-
60
- <!-- 自定义 -->
61
- <div v-if="userSkills.length > 0" class="skill-list-group">
62
- <p class="skill-list-group__label font-mono">自定义</p>
63
- <div
64
- v-for="s in userSkills"
65
- :key="s.name"
66
- class="skill-list-item"
67
- :class="{ active: selectedSkill?.name === s.name, disabled: !s.enabled }"
68
- @click="selectSkill(s)"
69
- >
70
- <div class="skill-list-item__icon user">
71
- <User :size="12" :stroke-width="1.8" />
72
- </div>
73
- <div class="skill-list-item__body">
74
- <span class="skill-list-item__name">{{ s.name }}</span>
75
- <span class="skill-list-item__desc">{{ s.description }}</span>
76
- </div>
77
- <div class="skill-list-item__status" :class="s.enabled ? 'on' : 'off'" />
78
- </div>
79
- </div>
80
-
81
- <!-- 空态 -->
82
- <div v-if="allSkills.length === 0" class="skill-list-empty">
83
- <BookOpen :size="24" style="opacity:0.25;margin-bottom:8px;" />
84
- <p>暂无 Skill</p>
85
- <button class="skill-list-empty__btn" @click="createNewSkill">+ 新增自定义 Skill</button>
86
- </div>
87
- </div>
88
- </el-scrollbar>
89
- </template>
90
-
91
- <!-- MCP -->
92
- <template v-else-if="activeNav === 'mcp'">
93
- <div class="skill-list-col__header">
94
- <span class="skill-list-col__title font-mono">MCP Servers</span>
95
- <el-tooltip content="新增 MCP Server" placement="bottom" :show-after="400">
96
- <button class="skill-col-action-btn" @click="openAddMcp">
97
- <Plus :size="13" />
98
- </button>
99
- </el-tooltip>
100
- </div>
101
-
102
- <el-scrollbar style="flex:1;">
103
- <div class="skill-list-body">
104
- <div v-if="mcpServers.length === 0" class="skill-list-empty">
105
- <Plug :size="24" style="opacity:0.25;margin-bottom:8px;" />
106
- <p>暂无 MCP Server</p>
107
- <button class="skill-list-empty__btn" @click="openAddMcp">+ 新增 MCP Server</button>
108
- </div>
109
-
110
- <div class="skill-list-group">
111
- <div
112
- v-for="srv in mcpServers"
113
- :key="srv.id"
114
- class="skill-list-item"
115
- :class="{ active: selectedMcp?.id === srv.id, disabled: !srv.enabled }"
116
- @click="selectMcp(srv)"
117
- >
118
- <div class="skill-list-item__icon" :class="srv.type === 'gitlab' ? 'mcp-gitlab' : ''">
119
- <component :is="getMcpIcon(srv.type)" :size="12" :stroke-width="1.8" />
120
- </div>
121
- <div class="skill-list-item__body">
122
- <span class="skill-list-item__name">{{ srv.name }}</span>
123
- <span class="skill-list-item__desc">{{ srv.tools.length }} tools · {{ srv.transport }}</span>
124
- </div>
125
- <div class="skill-list-item__status" :class="srv.connected && srv.enabled ? 'on' : 'off'" />
126
- </div>
127
- </div>
128
- </div>
129
- </el-scrollbar>
130
- </template>
131
- </div>
132
-
133
- <!-- ③ 第三栏:详情 / 编辑 -->
134
- <div class="skill-detail-col">
135
-
136
- <!-- ══ MCP 模式 ══ -->
137
- <template v-if="activeNav === 'mcp'">
138
-
139
- <!-- MCP 无选中 -->
140
- <template v-if="!selectedMcp && !mcpIsAdding">
141
- <div class="skill-detail-empty">
142
- <Server :size="28" style="opacity:0.2;margin-bottom:10px;" />
143
- <p>选择一个 MCP Server 查看详情</p>
144
- <button class="skill-list-empty__btn" style="margin-top:12px;" @click="openAddMcp">
145
- + 新增 MCP Server
146
- </button>
147
- </div>
148
- </template>
149
-
150
- <!-- MCP 新增表单 -->
151
- <template v-else-if="mcpIsAdding">
152
- <div class="skill-detail-header">
153
- <span class="skill-detail-header__title font-mono">新增 MCP Server</span>
154
- <div style="display:flex;gap:6px;">
155
- <button class="skill-detail-action cancel" @click="mcpIsAdding = false">取消</button>
156
- <button class="skill-detail-action save" @click="confirmAddMcp">保存</button>
157
- </div>
158
- </div>
159
- <el-scrollbar style="flex:1;">
160
- <div class="skill-form">
161
- <div class="skill-form__field">
162
- <label class="skill-form__label font-mono">name <span class="skill-form__required">*</span></label>
163
- <input v-model="mcpAddForm.name" class="skill-form__input" placeholder="my-mcp-server(英文、短横线)" />
164
- </div>
165
- <div class="skill-form__field">
166
- <label class="skill-form__label font-mono">description</label>
167
- <textarea v-model="mcpAddForm.description" class="skill-form__textarea" rows="2" placeholder="简短描述该 MCP Server 的用途..." />
168
- </div>
169
- <div class="skill-form__field">
170
- <label class="skill-form__label font-mono">transport <span class="skill-form__required">*</span></label>
171
- <select v-model="mcpAddForm.transport" class="skill-form__input">
172
- <option value="sse">SSE</option>
173
- <option value="stdio">stdio</option>
174
- <option value="websocket">WebSocket</option>
175
- </select>
176
- <p class="skill-form__hint">SSE 适合远程服务,stdio 适合本地进程</p>
177
- </div>
178
- <div class="skill-form__field">
179
- <label class="skill-form__label font-mono">endpoint</label>
180
- <input v-model="mcpAddForm.endpoint" class="skill-form__input font-mono" placeholder="https://..." />
181
- <p class="skill-form__hint">SSE / WebSocket 模式下填写服务地址</p>
182
- </div>
183
- </div>
184
- </el-scrollbar>
185
- </template>
186
-
187
- <!-- MCP 详情 -->
188
- <template v-else-if="selectedMcp">
189
- <!-- 头部 -->
190
- <div class="skill-detail-header">
191
- <div style="display:flex;align-items:center;gap:8px;flex:1;min-width:0;">
192
- <div class="skill-detail-header__icon" :class="selectedMcp.type === 'gitlab' ? 'mcp-gitlab' : ''">
193
- <component :is="getMcpIcon(selectedMcp.type)" :size="13" :stroke-width="1.8" />
194
- </div>
195
- <span class="skill-detail-header__name">{{ selectedMcp.name }}</span>
196
- <span class="skill-detail-header__type font-mono">{{ selectedMcp.transport }}</span>
197
- </div>
198
- <div style="display:flex;gap:4px;flex-shrink:0;">
199
- <el-tooltip :content="selectedMcp.enabled ? '禁用' : '启用'" placement="bottom" :show-after="400">
200
- <button
201
- class="skill-detail-action"
202
- :class="selectedMcp.enabled ? 'disable' : 'enable'"
203
- @click="toggleMcp(selectedMcp)"
204
- >
205
- <component :is="selectedMcp.enabled ? EyeOff : Eye" :size="12" style="margin-right:4px;" />
206
- {{ selectedMcp.enabled ? '禁用' : '启用' }}
207
- </button>
208
- </el-tooltip>
209
- <el-tooltip content="删除" placement="bottom" :show-after="400">
210
- <button class="skill-detail-action delete" @click="deleteMcp(selectedMcp)">
211
- <Trash2 :size="12" />
212
- </button>
213
- </el-tooltip>
214
- </div>
215
- </div>
216
-
217
- <!-- 描述 -->
218
- <div class="skill-detail-desc">{{ selectedMcp.description }}</div>
219
-
220
- <!-- Tabs -->
221
- <div class="skill-detail-tabs">
222
- <button class="skill-detail-tab" :class="{ active: mcpDetailTab === 'overview' }" @click="mcpDetailTab = 'overview'">
223
- <LayoutGrid :size="12" style="margin-right:4px;" />概览
224
- </button>
225
- <button class="skill-detail-tab" :class="{ active: mcpDetailTab === 'tools' }" @click="mcpDetailTab = 'tools'">
226
- <Wrench :size="12" style="margin-right:4px;" />工具
227
- <span class="mcp-tab-count font-mono">{{ selectedMcp.tools.length }}</span>
228
- </button>
229
- <button class="skill-detail-tab" :class="{ active: mcpDetailTab === 'config' }" @click="mcpDetailTab = 'config'">
230
- <SlidersHorizontal :size="12" style="margin-right:4px;" />配置
231
- </button>
232
- <button class="skill-detail-tab" :class="{ active: mcpDetailTab === 'debug' }" @click="mcpDetailTab = 'debug'">
233
- <TerminalSquare :size="12" style="margin-right:4px;" />调试
234
- </button>
235
- </div>
236
-
237
- <!-- Tab 内容 -->
238
- <el-scrollbar style="flex:1;">
239
-
240
- <!-- 概览 -->
241
- <div v-if="mcpDetailTab === 'overview'" class="mcp-overview">
242
- <div class="mcp-info-grid">
243
- <div class="mcp-info-card">
244
- <span class="mcp-info-card__label font-mono">endpoint</span>
245
- <span class="mcp-info-card__value font-mono">{{ selectedMcp.endpoint || '—' }}</span>
246
- </div>
247
- <div class="mcp-info-card">
248
- <span class="mcp-info-card__label font-mono">transport</span>
249
- <span class="mcp-info-card__value font-mono">{{ selectedMcp.transport }}</span>
250
- </div>
251
- <div class="mcp-info-card">
252
- <span class="mcp-info-card__label font-mono">status</span>
253
- <span class="mcp-info-card__value">
254
- <i class="mcp-dot" :class="selectedMcp.connected ? 'on' : 'off'" />
255
- {{ selectedMcp.connected ? '已连接' : '未连接' }}
256
- </span>
257
- </div>
258
- <div class="mcp-info-card">
259
- <span class="mcp-info-card__label font-mono">tools</span>
260
- <span class="mcp-info-card__value font-mono">{{ selectedMcp.tools.length }} 个</span>
261
- </div>
262
- </div>
263
-
264
- <p class="mcp-section-label font-mono">工具速览</p>
265
- <div class="mcp-chip-list">
266
- <span
267
- v-for="t in selectedMcp.tools"
268
- :key="t.name"
269
- class="mcp-chip font-mono"
270
- @click="mcpDetailTab = 'tools'; mcpSelectedTool = t"
271
- >{{ t.name }}</span>
272
- </div>
273
- </div>
274
-
275
- <!-- 工具列表 -->
276
- <div v-else-if="mcpDetailTab === 'tools'" class="mcp-tools-layout">
277
- <!-- 左:工具名列表 -->
278
- <div class="mcp-tool-list">
279
- <div
280
- v-for="t in selectedMcp.tools"
281
- :key="t.name"
282
- class="mcp-tool-row"
283
- :class="{ active: mcpSelectedTool?.name === t.name }"
284
- @click="mcpSelectedTool = t"
285
- >
286
- <Wrench :size="11" style="flex-shrink:0;opacity:0.5;" />
287
- <span class="font-mono">{{ t.name }}</span>
288
- </div>
289
- </div>
290
- <!-- 右:工具详情 -->
291
- <div class="mcp-tool-detail">
292
- <template v-if="mcpSelectedTool">
293
- <p class="mcp-tool-detail__name font-mono">{{ mcpSelectedTool.name }}</p>
294
- <p class="mcp-tool-detail__desc">{{ mcpSelectedTool.description }}</p>
295
-
296
- <p class="mcp-section-label font-mono" style="margin-top:14px;">Parameters</p>
297
- <div class="mcp-param-list">
298
- <div v-for="(param, key) in mcpSelectedTool.params" :key="key" class="mcp-param-item">
299
- <div class="mcp-param-item__row">
300
- <span class="mcp-param-name font-mono">{{ key }}</span>
301
- <span class="mcp-param-type font-mono">{{ param.type }}</span>
302
- <span v-if="param.required" class="mcp-param-required font-mono">required</span>
303
- </div>
304
- <p class="mcp-param-desc">{{ param.description }}</p>
305
- <div v-if="param.enum" class="mcp-param-enums">
306
- <span v-for="v in param.enum" :key="v" class="mcp-param-enum font-mono">{{ v }}</span>
307
- </div>
308
- </div>
309
- </div>
310
-
311
- <template v-if="mcpSelectedTool.returns">
312
- <p class="mcp-section-label font-mono" style="margin-top:14px;">Returns</p>
313
- <p class="mcp-tool-detail__desc">{{ mcpSelectedTool.returns }}</p>
314
- </template>
315
- </template>
316
- <div v-else class="mcp-tool-detail__empty">
317
- <Wrench :size="20" style="opacity:0.18;margin-bottom:6px;" />
318
- <p>选择左侧工具查看详情</p>
319
- </div>
320
- </div>
321
- </div>
322
-
323
- <!-- 配置 -->
324
- <div v-else-if="mcpDetailTab === 'config'" class="mcp-config-pane">
325
- <div v-for="field in selectedMcp.configFields" :key="field.key" class="skill-form__field">
326
- <label class="skill-form__label font-mono">
327
- {{ field.key }}<span v-if="field.required" class="skill-form__required"> *</span>
328
- </label>
329
- <p class="skill-form__hint" style="margin-bottom:4px;">{{ field.description }}</p>
330
- <div class="mcp-secret-wrap">
331
- <input
332
- v-model="mcpConfigValues[selectedMcp.id][field.key]"
333
- :type="field.secret && !mcpRevealed.has(field.key) ? 'password' : 'text'"
334
- :placeholder="field.placeholder"
335
- class="skill-form__input"
336
- :class="{ 'font-mono': field.mono }"
337
- />
338
- <button v-if="field.secret" class="mcp-reveal-btn" @click="toggleMcpReveal(field.key)">
339
- <component :is="mcpRevealed.has(field.key) ? EyeOff : Eye" :size="12" />
340
- </button>
341
- </div>
342
- </div>
343
- <div class="mcp-config-actions">
344
- <button class="skill-detail-action" :disabled="mcpTesting" @click="testMcpConnection">
345
- <Zap :size="11" style="margin-right:4px;" />{{ mcpTesting ? '连接中...' : '测试连接' }}
346
- </button>
347
- <button class="skill-detail-action save" @click="saveMcpConfig">
348
- <Check :size="11" style="margin-right:4px;" />保存配置
349
- </button>
350
- </div>
351
- </div>
352
-
353
- <!-- 调试 -->
354
- <div v-else-if="mcpDetailTab === 'debug'" class="mcp-debug-pane">
355
-
356
- <!-- 工具选择 -->
357
- <div class="skill-form__field">
358
- <label class="skill-form__label font-mono">tool</label>
359
- <el-select
360
- v-model="mcpDebugTool"
361
- placeholder="— 选择工具 —"
362
- class="mcp-debug-select"
363
- @change="onMcpDebugToolChange"
364
- >
365
- <el-option
366
- v-for="t in selectedMcp.tools"
367
- :key="t.name"
368
- :label="t.name"
369
- :value="t.name"
370
- >
371
- <span class="font-mono" style="font-size:12px;">{{ t.name }}</span>
372
- <span class="mcp-select-opt-desc">{{ t.description }}</span>
373
- </el-option>
374
- </el-select>
375
- </div>
376
-
377
- <!-- 逐字段参数表单 -->
378
- <template v-if="mcpDebugToolDef">
379
- <div class="mcp-debug-params">
380
- <div class="mcp-debug-params__header font-mono">
381
- <span>parameters</span>
382
- <span class="mcp-debug-params__count">{{ Object.keys(mcpDebugToolDef.params).length }} 个字段</span>
383
- </div>
384
- <div class="mcp-debug-params__body">
385
- <div
386
- v-for="(param, key) in mcpDebugToolDef.params"
387
- :key="key"
388
- class="mcp-debug-field"
389
- >
390
- <!-- 字段头:名称 + 类型 + 必填标记 -->
391
- <div class="mcp-debug-field__header">
392
- <span class="mcp-debug-field__name font-mono">{{ key }}</span>
393
- <span class="mcp-param-type font-mono">{{ param.type }}</span>
394
- <span v-if="param.required" class="mcp-param-required font-mono">required</span>
395
- <span v-else class="mcp-param-optional font-mono">optional</span>
396
- </div>
397
- <!-- 字段描述(常驻,不会被输入内容遮盖)-->
398
- <p class="mcp-debug-field__desc">{{ param.description }}</p>
399
- <!-- 输入控件 -->
400
- <el-select
401
- v-if="param.enum"
402
- v-model="mcpDebugParamValues[key]"
403
- placeholder="请选择..."
404
- clearable
405
- class="mcp-debug-select"
406
- >
407
- <el-option
408
- v-for="opt in param.enum"
409
- :key="opt"
410
- :label="opt"
411
- :value="opt"
412
- >
413
- <span class="font-mono" style="font-size:12px;">{{ opt }}</span>
414
- </el-option>
415
- </el-select>
416
- <el-input-number
417
- v-else-if="param.type === 'number'"
418
- v-model="mcpDebugParamValues[key]"
419
- :placeholder="param.required ? '必填' : '可选'"
420
- :controls="false"
421
- class="mcp-debug-number"
422
- />
423
- <el-switch
424
- v-else-if="param.type === 'boolean'"
425
- v-model="mcpDebugParamValues[key]"
426
- class="mcp-debug-switch"
427
- />
428
- <el-input
429
- v-else
430
- v-model="mcpDebugParamValues[key]"
431
- :placeholder="param.required ? '必填' : '可选'"
432
- clearable
433
- class="mcp-debug-input"
434
- />
435
- </div>
436
- </div>
437
- </div>
438
- </template>
439
-
440
- <!-- 无工具选中时占位 -->
441
- <div v-else class="mcp-debug-no-tool">
442
- <Wrench :size="16" style="opacity:0.2;" />
443
- <span>请先选择要调试的工具</span>
444
- </div>
445
-
446
- <!-- 执行按钮 -->
447
- <div class="mcp-config-actions">
448
- <button
449
- class="skill-detail-action save"
450
- :disabled="!mcpDebugTool || mcpDebugRunning"
451
- @click="runMcpDebug"
452
- >
453
- <component :is="mcpDebugRunning ? Loader2 : Play" :size="11" style="margin-right:4px;" :class="{ 'mcp-spin': mcpDebugRunning }" />
454
- {{ mcpDebugRunning ? '执行中...' : '执行' }}
455
- </button>
456
- <button v-if="mcpDebugResult || mcpDebugError" class="skill-detail-action" @click="mcpDebugResult = null; mcpDebugError = null">
457
- <RotateCcw :size="11" style="margin-right:4px;" />清空
458
- </button>
459
- </div>
460
-
461
- <!-- 输出区 -->
462
- <div class="mcp-debug-output">
463
- <div class="mcp-debug-output__header font-mono">
464
- <span>output</span>
465
- <span v-if="mcpDebugResult || mcpDebugError" :class="mcpDebugError ? 'mcp-out-err' : 'mcp-out-ok'">
466
- {{ mcpDebugError ? '✗ 错误' : '✓ 成功' }}
467
- </span>
468
- </div>
469
- <div class="mcp-debug-output__body">
470
- <div v-if="!mcpDebugResult && !mcpDebugError" class="mcp-debug-output__empty">
471
- <TerminalSquare :size="18" style="opacity:0.18;margin-bottom:5px;" />
472
- <span>执行工具后结果显示在此</span>
473
- </div>
474
- <pre v-else class="mcp-debug-output__pre" :class="{ error: mcpDebugError }">{{ mcpDebugError || mcpDebugResult }}</pre>
475
- </div>
476
- </div>
477
- </div>
478
-
479
- </el-scrollbar>
480
-
481
- <!-- 状态栏 -->
482
- <div class="skill-detail-statusbar font-mono">
483
- <span class="status-cmd">{{ selectedMcp.name }}</span>
484
- <span class="status-sep">·</span>
485
- <span>{{ selectedMcp.transport }}</span>
486
- <span class="status-sep">·</span>
487
- <i class="mcp-dot" :class="selectedMcp.connected ? 'on' : 'off'" style="display:inline-block;" />
488
- <span :class="selectedMcp.connected ? 'status-on' : 'status-off'">{{ selectedMcp.connected ? '已连接' : '未连接' }}</span>
489
- <span class="status-sep">·</span>
490
- <span :class="selectedMcp.enabled ? 'status-on' : 'status-off'">{{ selectedMcp.enabled ? '已启用' : '已禁用' }}</span>
491
- </div>
492
- </template>
493
-
494
- </template>
495
-
496
- <!-- ══ Skills 模式(原有逻辑完全不变)══ -->
497
- <template v-else>
498
-
499
- <!-- 无选中态 -->
500
- <template v-if="!selectedSkill && !isCreating">
501
- <div class="skill-detail-empty">
502
- <Layers :size="28" style="opacity:0.2;margin-bottom:10px;" />
503
- <p>选择一个 Skill 查看详情</p>
504
- <button class="skill-list-empty__btn" style="margin-top:12px;" @click="createNewSkill">
505
- + 新增自定义 Skill
506
- </button>
507
- </div>
508
- </template>
509
-
510
- <!-- 新增 / 编辑表单 -->
511
- <template v-else-if="isEditing || isCreating">
512
- <div class="skill-detail-header">
513
- <span class="skill-detail-header__title font-mono">{{ isCreating ? '新增 Skill' : '编辑 Skill' }}</span>
514
- <div style="display:flex;gap:6px;">
515
- <button class="skill-detail-action cancel" @click="cancelEdit">取消</button>
516
- <button class="skill-detail-action save" @click="saveSkill">保存</button>
517
- </div>
518
- </div>
519
-
520
- <el-scrollbar style="flex:1;">
521
- <div class="skill-form">
522
- <div class="skill-form__field">
523
- <label class="skill-form__label font-mono">
524
- name <span class="skill-form__required">*</span>
525
- </label>
526
- <input
527
- v-model="editForm.name"
528
- class="skill-form__input"
529
- placeholder="skill-name(英文、短横线)"
530
- />
531
- <p class="skill-form__hint">
532
- 用于快捷命令引用,如 <code>/{{ editForm.name || 'skill-name' }}</code>
533
- </p>
534
- </div>
535
-
536
- <div class="skill-form__field">
537
- <label class="skill-form__label font-mono">
538
- description <span class="skill-form__required">*</span>
539
- </label>
540
- <textarea
541
- v-model="editForm.description"
542
- class="skill-form__textarea"
543
- rows="3"
544
- placeholder="描述该 Skill 的用途,用于自动匹配意图..."
545
- />
546
- <p class="skill-form__hint">AI 将根据此描述判断是否自动注入该 Skill</p>
547
- </div>
548
-
549
- <div class="skill-form__field">
550
- <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:2px;">
551
- <label class="skill-form__label font-mono" style="margin-bottom:0;">
552
- content <span class="skill-form__required">*</span>
553
- </label>
554
- <el-tooltip content="AI 润色 Content" placement="bottom" :show-after="400">
555
- <button
556
- class="skill-detail-action"
557
- :class="{ loading: polishing }"
558
- :disabled="polishing || !editForm.content.trim()"
559
- @click="polishContent"
560
- >
561
- <template v-if="polishing">
562
- <span class="skill-polish-spinner" />
563
- <span>润色中...</span>
564
- </template>
565
- <template v-else>
566
- <Sparkles :size="11" />
567
- <span>AI 润色</span>
568
- </template>
569
- </button>
570
- </el-tooltip>
571
- </div>
572
-
573
- <!-- 润色进度条 -->
574
- <div v-if="polishing" class="skill-polish-progress">
575
- <div class="skill-polish-progress__bar" />
576
- </div>
577
-
578
- <!-- CodeMirror Markdown 编辑器 -->
579
- <div
580
- class="skill-content-editor"
581
- :class="{ 'is-polishing': polishing }"
582
- style="position:relative;"
583
- >
584
- <div ref="contentEditorEl" class="skill-cm-editor" />
585
- <!-- 润色蒙层 -->
586
- <div v-if="polishing" class="skill-polish-overlay">
587
- <div class="skill-polish-overlay__inner">
588
- <Sparkles :size="16" style="opacity:0.6;" />
589
- <span>AI 正在润色...</span>
590
- </div>
591
- </div>
592
- </div>
593
-
594
- <!-- 润色前后对比提示 -->
595
- <div v-if="polishOriginal && !polishing" class="skill-polish-diff-hint">
596
- <CheckCheck :size="11" style="color:#4ade80;flex-shrink:0;" />
597
- <span>已完成润色</span>
598
- <button class="skill-polish-revert" @click="revertPolish">撤销</button>
599
- </div>
600
- </div>
601
- </div>
602
- </el-scrollbar>
603
- </template>
604
-
605
- <!-- 详情查看 -->
606
- <template v-else-if="selectedSkill">
607
- <div class="skill-detail-header">
608
- <div style="display:flex;align-items:center;gap:8px;flex:1;min-width:0;">
609
- <div class="skill-detail-header__icon" :class="{ user: !selectedSkill.builtin }">
610
- <component :is="selectedSkill.builtin ? Cpu : User" :size="13" :stroke-width="1.8" />
611
- </div>
612
- <span class="skill-detail-header__name">{{ selectedSkill.name }}</span>
613
- <span class="skill-detail-header__type font-mono">
614
- {{ selectedSkill.builtin ? 'builtin' : 'custom' }}
615
- </span>
616
- </div>
617
- <div style="display:flex;gap:4px;flex-shrink:0;">
618
- <el-tooltip :content="selectedSkill.enabled ? '禁用此 Skill' : '启用此 Skill'" placement="bottom" :show-after="400">
619
- <button
620
- class="skill-detail-action"
621
- :class="selectedSkill.enabled ? 'disable' : 'enable'"
622
- @click="toggleSkill(selectedSkill)"
623
- >
624
- <component :is="selectedSkill.enabled ? EyeOff : Eye" :size="12" style="margin-right:4px;" />
625
- {{ selectedSkill.enabled ? '禁用' : '启用' }}
626
- </button>
627
- </el-tooltip>
628
- <el-tooltip v-if="!selectedSkill.builtin" content="编辑" placement="bottom" :show-after="400">
629
- <button class="skill-detail-action edit" @click="startEdit(selectedSkill)">
630
- <Pencil :size="12" style="margin-right:4px;" />编辑
631
- </button>
632
- </el-tooltip>
633
- <el-tooltip v-if="!selectedSkill.builtin" content="删除" placement="bottom" :show-after="400">
634
- <button class="skill-detail-action delete" @click="doDeleteSkill(selectedSkill)">
635
- <Trash2 :size="12" />
636
- </button>
637
- </el-tooltip>
638
- </div>
639
- </div>
640
-
641
- <!-- 描述 -->
642
- <div class="skill-detail-desc">{{ selectedSkill.description }}</div>
643
-
644
- <!-- 预览/代码 Tab -->
645
- <div class="skill-detail-tabs">
646
- <button
647
- class="skill-detail-tab"
648
- :class="{ active: detailView === 'preview' }"
649
- @click="detailView = 'preview'"
650
- >
651
- <Eye :size="12" style="margin-right:4px;" />预览
652
- </button>
653
- <button
654
- class="skill-detail-tab"
655
- :class="{ active: detailView === 'code' }"
656
- @click="detailView = 'code'"
657
- >
658
- <Code2 :size="12" style="margin-right:4px;" />源码
659
- </button>
660
- <div style="flex:1;" />
661
- <el-tooltip content="复制内容" placement="bottom" :show-after="400">
662
- <button class="skill-tab-action" @click="copyContent">
663
- <Check v-if="copied" :size="12" color="#4ade80" />
664
- <Copy v-else :size="12" />
665
- </button>
666
- </el-tooltip>
667
- </div>
668
-
669
- <!-- 内容区 -->
670
- <el-scrollbar style="flex:1;">
671
- <div v-if="selectedSkill.contentLoading" style="padding:20px;text-align:center;color:var(--text-muted);">
672
- 加载中...
673
- </div>
674
- <template v-else>
675
- <div
676
- v-if="detailView === 'preview'"
677
- class="skill-detail-preview"
678
- v-html="renderedContent"
679
- />
680
- <div v-else class="skill-detail-code">
681
- <pre><code>{{ selectedSkill.content }}</code></pre>
682
- </div>
683
- </template>
684
- </el-scrollbar>
685
-
686
- <!-- 底部状态栏 -->
687
- <div class="skill-detail-statusbar font-mono">
688
- <span class="status-cmd">/{{ selectedSkill.name }}</span>
689
- <span class="status-sep">·</span>
690
- <span>{{ (selectedSkill.content || '').split('\n').length }} 行</span>
691
- <span class="status-sep">·</span>
692
- <span :class="selectedSkill.enabled ? 'status-on' : 'status-off'">
693
- {{ selectedSkill.enabled ? '已启用' : '已禁用' }}
694
- </span>
695
- </div>
696
- </template>
697
-
698
- </template><!-- end skills mode -->
699
- </div>
700
- </div>
701
- </template>
702
-
703
- <script setup>
704
- import { ref, computed, onMounted, onUnmounted, watch, inject, reactive } from 'vue'
705
- import { marked } from 'marked'
706
- import {
707
- Layers, Cpu, User, Plus, BookOpen, Plug,
708
- Eye, EyeOff, Pencil, Trash2, Code2, Copy, Check, Server,
709
- Sparkles, CheckCheck,
710
- GitBranch, Wrench, SlidersHorizontal, LayoutGrid, TerminalSquare,
711
- Zap, Play, Loader2, RotateCcw, FileCode2, Bug, Merge,
712
- GitCommit, FolderOpen, Tag, Search
713
- } from 'lucide-vue-next'
714
- import { ElMessage, ElMessageBox } from 'element-plus'
715
-
716
- // ── Props ──────────────────────────────────────────────────────────
717
- const props = defineProps({
718
- store: { type: Object, required: true },
719
- })
720
-
721
-
722
- // ── 导航 ──────────────────────────────────────────────────────────
723
- const activeNav = ref('skills')
724
- const navItems = computed(() => [
725
- { key: 'skills', label: 'Skills', icon: Layers, badge: allSkills.value.length },
726
- { key: 'mcp', label: 'MCP', icon: Server, badge: mcpServers.value.length || null },
727
- ])
728
-
729
-
730
- // ── 状态 ──────────────────────────────────────────────────────────
731
- const loading = ref(false)
732
- const backendUserSkills = ref([])
733
- const backendSystemSkills = ref([])
734
-
735
-
736
- // ── 从后端加载 Skills ───────────────────────────────────────────
737
- async function loadUserSkillsFromApi() {
738
- try {
739
- loading.value = true
740
- const data = await props.store.loadSkills?.()
741
- backendSystemSkills.value = (data || []).filter(s => s.source === 'system').map(s => ({
742
- ...s,
743
- enabled: s.enabled !== false,
744
- }))
745
- backendUserSkills.value = (data || []).filter(s => s.source === 'user').map(s => ({
746
- ...s,
747
- enabled: s.enabled !== false,
748
- }))
749
- } catch (err) {
750
- console.error('[SkillConfigPanel] load skills error:', err)
751
- ElMessage.error('加载 Skills 失败')
752
- } finally {
753
- loading.value = false
754
- }
755
- }
756
-
757
- function syncToStore() {
758
- const enabled = backendUserSkills.value.filter(s => s.enabled !== false)
759
- props.store.setUserSkills?.(enabled.map(s => ({
760
- name: s.name,
761
- description: s.description,
762
- content: s.content || '',
763
- })))
764
- // 同步回 store.skillList,使 ChatBox 的 allSkillsForInput 能拿到最新数据
765
- const allLatest = [
766
- ...backendSystemSkills.value.map(s => ({ ...s, source: 'system' })),
767
- ...backendUserSkills.value.map(s => ({ ...s, source: 'user' })),
768
- ]
769
- if (props.store.skillList) {
770
- props.store.skillList.value = allLatest
771
- }
772
- }
773
-
774
- onMounted(() => {
775
- loadUserSkillsFromApi()
776
- })
777
-
778
- // ── 列表计算 ─────────────────────────────────────────────────────
779
- const builtinSkills = computed(() =>
780
- backendSystemSkills.value.map(s => ({ ...s, builtin: true }))
781
- )
782
- const userSkills = computed(() =>
783
- backendUserSkills.value.map(s => ({ ...s, builtin: false }))
784
- )
785
- const allSkills = computed(() => [...builtinSkills.value, ...userSkills.value])
786
-
787
- // ── 选中 / 详情 ──────────────────────────────────────────────────
788
- const selectedSkill = ref(null)
789
- const detailView = ref('preview')
790
- const isEditing = ref(false)
791
- const isCreating = ref(false)
792
- const copied = ref(false)
793
- const editForm = ref({ name: '', description: '', content: '' })
794
-
795
- // ── CodeMirror Markdown 编辑器 ──────────────────────────────────────
796
- const contentEditorEl = ref(null)
797
- let _cmView = null
798
-
799
- // 注入主题(对接项目主题系统,fallback dark)
800
- const themeCtx = inject('theme', null)
801
- const editorTheme = computed(() => themeCtx?.isDark?.value === false ? 'light' : 'dark')
802
-
803
- async function createContentEditor(initialCode) {
804
- if (!contentEditorEl.value) return
805
- if (_cmView) { _cmView.destroy(); _cmView = null }
806
-
807
- const [
808
- { EditorView, keymap, lineNumbers, highlightActiveLine, drawSelection, placeholder: cmPlaceholder },
809
- { defaultKeymap, historyKeymap, history, indentWithTab },
810
- { markdown, markdownLanguage },
811
- { oneDark },
812
- { syntaxHighlighting, defaultHighlightStyle },
813
- { closeBrackets, closeBracketsKeymap },
814
- ] = await Promise.all([
815
- import('@codemirror/view'),
816
- import('@codemirror/commands'),
817
- import('@codemirror/lang-markdown'),
818
- import('@codemirror/theme-one-dark'),
819
- import('@codemirror/language'),
820
- import('@codemirror/autocomplete'),
821
- ])
822
-
823
- const isDark = editorTheme.value === 'dark'
824
-
825
- const updateListener = EditorView.updateListener.of((update) => {
826
- if (!update.docChanged) return
827
- const code = update.state.doc.toString()
828
- if (editForm.value.content !== code) {
829
- editForm.value.content = code
830
- }
831
- })
832
-
833
- const baseTheme = EditorView.theme({
834
- '&': { height: '100%', fontSize: '12.5px' },
835
- '.cm-scroller': {
836
- fontFamily: "'Cascadia Code', 'JetBrains Mono', Consolas, monospace",
837
- overflow: 'auto',
838
- lineHeight: '1.7',
839
- },
840
- '.cm-content': { padding: '12px 0', whiteSpace: 'pre-wrap', wordBreak: 'break-word' },
841
- '.cm-line': { padding: '0 16px 0 0' },
842
- })
843
-
844
- const extensions = [
845
- lineNumbers(),
846
- highlightActiveLine(),
847
- drawSelection(),
848
- history(),
849
- closeBrackets(),
850
- keymap.of([...defaultKeymap, ...historyKeymap, ...closeBracketsKeymap, indentWithTab]),
851
- markdown({ base: markdownLanguage }),
852
- cmPlaceholder('输入 Skill 提示词内容(支持 Markdown 格式)...'),
853
- updateListener,
854
- baseTheme,
855
- isDark ? oneDark : syntaxHighlighting(defaultHighlightStyle),
856
- ]
857
-
858
- _cmView = new EditorView({
859
- doc: initialCode,
860
- extensions,
861
- parent: contentEditorEl.value,
862
- })
863
- }
864
-
865
- // 当润色流式写入 editForm.content 时,同步到 CodeMirror
866
- watch(() => editForm.value?.content, (newCode) => {
867
- if (!_cmView) return
868
- const current = _cmView.state.doc.toString()
869
- if (current === newCode) return
870
- _cmView.dispatch({
871
- changes: { from: 0, to: current.length, insert: newCode ?? '' },
872
- })
873
- })
874
-
875
- // 编辑/新增面板打开时初始化编辑器
876
- watch([() => isEditing.value, () => isCreating.value], async ([editing, creating]) => {
877
- if (editing || creating) {
878
- await new Promise(r => setTimeout(r, 20))
879
- await createContentEditor(editForm.value.content || '')
880
- } else {
881
- _cmView?.destroy()
882
- _cmView = null
883
- }
884
- })
885
-
886
- onUnmounted(() => {
887
- _cmView?.destroy()
888
- _cmView = null
889
- })
890
-
891
-
892
- async function selectSkill(s) {
893
- selectedSkill.value = { ...s, contentLoading: true }
894
- isEditing.value = false
895
- isCreating.value = false
896
- detailView.value = 'preview'
897
- const list = props.store.getSkills()
898
- const enabled = list.filter(n => n.name === s.name)[0].enabled
899
-
900
- // 尝试从后端获取 content
901
- if (!s.content && s.name) {
902
- try {
903
- const data = await props.store.fetchSkillContent?.(s.name)
904
- if (data?.data?.content) {
905
- selectedSkill.value = { ...s, content: data?.data?.content, contentLoading: false, enabled }
906
- } else {
907
- selectedSkill.value = { ...s, contentLoading: false, enabled }
908
- }
909
- } catch (err) {
910
- console.error('[SkillConfigPanel] fetch content error:', err)
911
- selectedSkill.value = { ...s, contentLoading: false, enabled }
912
- }
913
- } else {
914
- selectedSkill.value = { ...s, contentLoading: false, }
915
- }
916
- }
917
-
918
- const renderedContent = computed(() => {
919
- if (!selectedSkill.value?.content) return ''
920
- try { return marked.parse(selectedSkill.value.content) } catch { return selectedSkill.value.content }
921
- })
922
-
923
- // ── 启用 / 禁用 ──────────────────────────────────────────────────
924
- async function toggleSkill(s) {
925
- const newEnabled = !s.enabled
926
-
927
- try {
928
- await props.store.setSkillStatus?.(s.name, newEnabled)
929
- await loadUserSkillsFromApi()
930
- syncToStore()
931
- selectedSkill.value = { ...s, enabled: newEnabled }
932
- ElMessage.success(`已${newEnabled ? '启用' : '禁用'} ${s.name}`)
933
- } catch (err) {
934
- console.error('[SkillConfigPanel] toggle skill error:', err)
935
- ElMessage.error(err.message || '设置状态失败')
936
- } finally {
937
- loadUserSkillsFromApi()
938
- }
939
- }
940
-
941
- // ── 新增 / 编辑 ──────────────────────────────────────────────────
942
- function createNewSkill() {
943
- isCreating.value = true
944
- isEditing.value = false
945
- selectedSkill.value = null
946
- editForm.value = { name: '', description: '', content: '' }
947
- }
948
- function startEdit(s) {
949
- isEditing.value = true
950
- isCreating.value = false
951
- editForm.value = { name: s.name, description: s.description, content: s.content || '' }
952
- }
953
- function cancelEdit() {
954
- isEditing.value = false
955
- isCreating.value = false
956
- if (!selectedSkill.value) selectedSkill.value = allSkills.value[0] || null
957
- }
958
- async function saveSkill() {
959
- const { name, description, content } = editForm.value
960
- if (!name.trim()) { ElMessage.warning('Skill name 不能为空'); return }
961
- if (!description.trim()) { ElMessage.warning('description 不能为空'); return }
962
- if (!content.trim()) { ElMessage.warning('content 不能为空'); return }
963
- if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name.trim())) {
964
- ElMessage.warning('name 必须为英文字母开头,可含字母、数字、下划线、连字符')
965
- return
966
- }
967
- if (isCreating.value && allSkills.value.some(s => s.name === name.trim())) {
968
- ElMessage.warning(`已存在名为 "${name.trim()}" 的 Skill`)
969
- return
970
- }
971
- const payload = { name: name.trim(), description: description.trim(), content: content.trim() }
972
- let id = null
973
- try {
974
- loading.value = true
975
- if (isCreating.value) {
976
- const result = await props.store.createSkill?.(payload)
977
- if (result?.data?.id) {
978
- id = result.data.id
979
- backendUserSkills.value.push({ ...payload, id: result.data.id, enabled: true })
980
- }
981
- } else {
982
- const cur = selectedSkill.value
983
- if (cur?.id) {
984
- id = cur.id
985
- await props.store.updateSkill?.(cur.id, payload)
986
- const idx = backendUserSkills.value.findIndex(s => s.id === cur.id)
987
- if (idx !== -1) {
988
- backendUserSkills.value[idx] = { ...backendUserSkills.value[idx], ...payload }
989
- }
990
- }
991
- }
992
- syncToStore()
993
- selectedSkill.value = { ...payload, builtin: false, id, enabled: 1 }
994
- isEditing.value = false
995
- isCreating.value = false
996
- ElMessage.success('Skill 已保存')
997
- } catch (err) {
998
- console.error('[SkillConfigPanel] save skill error:', err)
999
- ElMessage.error(err.message || '保存失败')
1000
- } finally {
1001
- loading.value = false
1002
- loadUserSkillsFromApi()
1003
- }
1004
- }
1005
-
1006
- // ── 删除 ──────────────────────────────────────────────────────────
1007
- async function doDeleteSkill(s) {
1008
- if (!s?.id) return
1009
- try {
1010
- await ElMessageBox.confirm(
1011
- `确定要删除自定义 Skill "${s.name}" 吗?`,
1012
- '删除确认',
1013
- { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' }
1014
- )
1015
- loading.value = true
1016
- await props.store.deleteSkill?.(s.id)
1017
- backendUserSkills.value = backendUserSkills.value.filter(u => u.id !== s.id)
1018
- syncToStore()
1019
- selectSkill(allSkills.value[0])
1020
- ElMessage.success('已删除')
1021
- } catch (err) {
1022
- console.error('[SkillConfigPanel] delete skill error:', err)
1023
- ElMessage.error(err.message || '删除失败')
1024
- } finally {
1025
- loading.value = false
1026
- loadUserSkillsFromApi()
1027
- }
1028
- }
1029
-
1030
- // ── AI 润色 Content ───────────────────────────────────────────────
1031
- const polishing = ref(false)
1032
- const polishOriginal = ref('') // 用于撤销
1033
-
1034
- const PROXY_BASE = import.meta.env.VITE_PROXY_BASE_URL || ''
1035
-
1036
- function buildPolishPrompt(content) {
1037
- return `你是一位专业的 AI Prompt 工程师,请对以下 Skill 提示词内容进行润色和优化。
1038
-
1039
- **优化目标:**
1040
- - 提升语言的清晰度和专业性
1041
- - 使指令更加精确、无歧义
1042
- - 保持原有意图和结构不变
1043
- - 优化格式,使层次更清晰
1044
- - 如有必要,补充关键细节或边界条件
1045
-
1046
- **原始内容:**
1047
- ${content}
1048
-
1049
- **要求:**
1050
- - 直接返回润色后的完整内容
1051
- - 不要任何解释、前言或后记
1052
- - 不要用 markdown 代码块包裹
1053
- - 保持原有的 Markdown 格式风格`
1054
- }
1055
-
1056
- async function polishContent() {
1057
- const raw = editForm.value.content.trim()
1058
- if (!raw) return
1059
-
1060
- polishing.value = true
1061
- polishOriginal.value = editForm.value.content
1062
- editForm.value.content = ''
1063
-
1064
- // 获取当前模型 id(从 store 取,fallback 到通用模型)
1065
- const modelId = props.store?.currentModel?.id
1066
- || props.store?.modelId?.value
1067
- || 'qwen3-vl-8b-instruct'
1068
-
1069
- try {
1070
- const res = await fetch(`${PROXY_BASE}/api/chat`, {
1071
- method: 'POST',
1072
- headers: { 'Content-Type': 'application/json' },
1073
- body: JSON.stringify({
1074
- modelId,
1075
- messages: [{ role: 'user', content: buildPolishPrompt(raw) }],
1076
- skillIds: [],
1077
- sessionState: {},
1078
- enable_thinking: false,
1079
- stream: true,
1080
- }),
1081
- })
1082
-
1083
- if (!res.ok) {
1084
- throw new Error(`HTTP ${res.status}`)
1085
- }
1086
-
1087
- const reader = res.body.getReader()
1088
- const decoder = new TextDecoder()
1089
- let buffer = ''
1090
-
1091
- while (true) {
1092
- const { done, value } = await reader.read()
1093
- if (done) break
1094
-
1095
- buffer += decoder.decode(value, { stream: true })
1096
- const lines = buffer.split('\n')
1097
- buffer = lines.pop() ?? ''
1098
-
1099
- for (const line of lines) {
1100
- const trimmed = line.trim()
1101
- if (!trimmed || trimmed === 'data: [DONE]') continue
1102
- if (!trimmed.startsWith('data:')) continue
1103
-
1104
- try {
1105
- const json = JSON.parse(trimmed.slice(5).trim())
1106
- const delta = json.choices?.[0]?.delta?.content
1107
- if (delta) {
1108
- editForm.value.content += delta
1109
- }
1110
- } catch { /* skip malformed */ }
1111
- }
1112
- }
1113
-
1114
- // 润色结果为空则恢复
1115
- if (!editForm.value.content.trim()) {
1116
- editForm.value.content = polishOriginal.value
1117
- polishOriginal.value = ''
1118
- ElMessage.warning('润色结果为空,已恢复原内容')
1119
- } else {
1120
- ElMessage.success('润色完成')
1121
- }
1122
- } catch (err) {
1123
- console.error('[SkillConfigPanel] polish error:', err)
1124
- editForm.value.content = polishOriginal.value
1125
- polishOriginal.value = ''
1126
- ElMessage.error('润色失败:' + (err.message || '请求异常'))
1127
- } finally {
1128
- polishing.value = false
1129
- }
1130
- }
1131
-
1132
- function revertPolish() {
1133
- if (!polishOriginal.value) return
1134
- editForm.value.content = polishOriginal.value
1135
- polishOriginal.value = ''
1136
- ElMessage.info('已撤销润色')
1137
- }
1138
-
1139
- // ── 复制 ─────────────────────────────────────────────────────────
1140
- async function copyContent() {
1141
- if (!selectedSkill.value?.content) return
1142
- await navigator.clipboard.writeText(selectedSkill.value.content)
1143
- copied.value = true
1144
- setTimeout(() => { copied.value = false }, 1500)
1145
- }
1146
-
1147
- // ═══════════════════════════════════════════════════════════════════
1148
- // ── MCP ──────────────────────────────────────────────────────────
1149
- // ═══════════════════════════════════════════════════════════════════
1150
-
1151
- const selectedMcp = ref(null)
1152
- const mcpDetailTab = ref('overview')
1153
- const mcpSelectedTool = ref(null)
1154
- const mcpIsAdding = ref(false)
1155
- const mcpTesting = ref(false)
1156
- const mcpRevealed = reactive(new Set())
1157
- const mcpAddForm = ref({ name: '', description: '', transport: 'sse', endpoint: '' })
1158
-
1159
- // 调试
1160
- const mcpDebugTool = ref('')
1161
- const mcpDebugParamValues = reactive({}) // { [paramKey]: value }
1162
- const mcpDebugResult = ref(null)
1163
- const mcpDebugError = ref(null)
1164
- const mcpDebugRunning = ref(false)
1165
-
1166
- // ── MCP Server 数据(GitLab 为示例)──────────────────────────────
1167
- const mcpServers = ref([
1168
- {
1169
- id: 'gitlab-mcp',
1170
- name: 'GitLab MCP',
1171
- type: 'gitlab',
1172
- description: '通过 MCP 协议与 GitLab API 集成,支持仓库、Issue、MR、Pipeline 等操作',
1173
- transport: 'sse',
1174
- endpoint: 'https://gitlab.com/api/v4/mcp',
1175
- connected: true,
1176
- enabled: true,
1177
- configFields: [
1178
- {
1179
- key: 'GITLAB_PERSONAL_ACCESS_TOKEN',
1180
- type: 'text', secret: true, required: true, mono: true,
1181
- description: 'GitLab Personal Access Token(需要 api、read_repository 权限)',
1182
- placeholder: 'glpat-xxxxxxxxxxxxxxxxxxxx',
1183
- },
1184
- {
1185
- key: 'GITLAB_API_URL',
1186
- type: 'text', secret: false, required: false, mono: true,
1187
- description: 'GitLab 实例 API 地址,默认 https://gitlab.com/api/v4',
1188
- placeholder: 'https://gitlab.com/api/v4',
1189
- },
1190
- {
1191
- key: 'DEFAULT_NAMESPACE',
1192
- type: 'text', secret: false, required: false, mono: false,
1193
- description: '默认命名空间(用户名或组名),可省略',
1194
- placeholder: 'my-group',
1195
- },
1196
- ],
1197
- tools: [
1198
- {
1199
- name: 'list_projects',
1200
- description: '列出当前用户可访问的 GitLab 项目,支持关键字搜索和分组筛选',
1201
- returns: '返回项目数组,每项包含 id、name、namespace、http_url_to_repo、default_branch',
1202
- params: {
1203
- search: { type: 'string', required: false, description: '按项目名或路径关键字过滤' },
1204
- membership: { type: 'boolean', required: false, description: '仅返回当前用户有成员资格的项目,默认 true' },
1205
- page: { type: 'number', required: false, description: '页码,默认 1' },
1206
- per_page: { type: 'number', required: false, description: '每页数量,默认 20,最大 100' },
1207
- },
1208
- },
1209
- {
1210
- name: 'get_file_content',
1211
- description: '获取仓库中指定文件的内容,支持指定分支或 commit SHA',
1212
- returns: '返回文件 Base64 编码内容及元信息(大小、编码、最后提交等)',
1213
- params: {
1214
- project_id: { type: 'string', required: true, description: '项目 ID 或 URL 编码的路径,如 "group/repo"' },
1215
- file_path: { type: 'string', required: true, description: '文件在仓库中的路径,如 "src/main.js"' },
1216
- ref: { type: 'string', required: false, description: '分支名、标签或 commit SHA,默认为默认分支' },
1217
- },
1218
- },
1219
- {
1220
- name: 'create_issue',
1221
- description: '在指定 GitLab 项目中创建新的 Issue',
1222
- returns: '返回创建成功的 Issue 对象,包含 iid、title、web_url 等',
1223
- params: {
1224
- project_id: { type: 'string', required: true, description: '项目 ID 或路径' },
1225
- title: { type: 'string', required: true, description: 'Issue 标题' },
1226
- description: { type: 'string', required: false, description: 'Issue 描述,支持 Markdown' },
1227
- labels: { type: 'string', required: false, description: '标签,逗号分隔,如 "bug,help wanted"' },
1228
- assignee_ids: { type: 'array', required: false, description: '指派人的用户 ID 数组' },
1229
- milestone_id: { type: 'number', required: false, description: '里程碑 ID' },
1230
- },
1231
- },
1232
- {
1233
- name: 'list_issues',
1234
- description: '获取项目的 Issue 列表,支持状态、标签、指派人等多维度筛选',
1235
- returns: '返回 Issue 数组,包含 iid、title、state、labels、assignees 等信息',
1236
- params: {
1237
- project_id: { type: 'string', required: true, description: '项目 ID 或路径' },
1238
- state: { type: 'string', required: false, description: 'Issue 状态', enum: ['opened', 'closed', 'all'] },
1239
- labels: { type: 'string', required: false, description: '按标签筛选,逗号分隔' },
1240
- search: { type: 'string', required: false, description: '搜索关键字(标题和描述)' },
1241
- page: { type: 'number', required: false, description: '页码,默认 1' },
1242
- per_page: { type: 'number', required: false, description: '每页数量,默认 20' },
1243
- },
1244
- },
1245
- {
1246
- name: 'create_merge_request',
1247
- description: '创建合并请求(MR),将源分支合并到目标分支',
1248
- returns: '返回新建的 MR 对象,包含 iid、web_url、source_branch、target_branch 等',
1249
- params: {
1250
- project_id: { type: 'string', required: true, description: '项目 ID 或路径' },
1251
- source_branch: { type: 'string', required: true, description: '源分支名' },
1252
- target_branch: { type: 'string', required: true, description: '目标分支名,通常为 main 或 master' },
1253
- title: { type: 'string', required: true, description: 'MR 标题' },
1254
- description: { type: 'string', required: false, description: 'MR 描述,支持 Markdown' },
1255
- remove_source_branch: { type: 'boolean', required: false, description: '合并后删除源分支,默认 false' },
1256
- squash: { type: 'boolean', required: false, description: '是否 squash commits,默认 false' },
1257
- },
1258
- },
1259
- {
1260
- name: 'get_pipeline_status',
1261
- description: '获取指定项目最新或特定 Pipeline 的执行状态和各阶段详情',
1262
- returns: '返回 Pipeline 对象,包含 status、stages、duration 等字段',
1263
- params: {
1264
- project_id: { type: 'string', required: true, description: '项目 ID 或路径' },
1265
- pipeline_id: { type: 'number', required: false, description: 'Pipeline ID,省略则返回最新一条' },
1266
- ref: { type: 'string', required: false, description: '分支名,用于筛选最新 Pipeline' },
1267
- },
1268
- },
1269
- {
1270
- name: 'search_code',
1271
- description: '在 GitLab 实例或指定项目内全局搜索代码片段',
1272
- returns: '返回匹配的代码片段列表,包含文件路径、行号、匹配内容摘要',
1273
- params: {
1274
- query: { type: 'string', required: true, description: '搜索关键字或代码片段' },
1275
- project_id: { type: 'string', required: false, description: '限定在某个项目内搜索' },
1276
- ref: { type: 'string', required: false, description: '指定分支或标签' },
1277
- },
1278
- },
1279
- {
1280
- name: 'list_branches',
1281
- description: '列出仓库中的分支,支持按名称过滤',
1282
- returns: '返回分支数组,包含 name、commit(id、message、authored_date)等',
1283
- params: {
1284
- project_id: { type: 'string', required: true, description: '项目 ID 或路径' },
1285
- search: { type: 'string', required: false, description: '分支名关键字过滤' },
1286
- },
1287
- },
1288
- ],
1289
- },
1290
- ])
1291
-
1292
- // 每个 server 的配置值
1293
- const mcpConfigValues = reactive(
1294
- Object.fromEntries(
1295
- mcpServers.value.map(s => [
1296
- s.id,
1297
- Object.fromEntries(s.configFields.map(f => [f.key, f.key === 'GITLAB_API_URL' ? 'https://gitlab.com/api/v4' : '']))
1298
- ])
1299
- )
1300
- )
1301
-
1302
- // ── 操作 ──────────────────────────────────────────────────────────
1303
- function getMcpIcon(type) {
1304
- return type === 'gitlab' ? GitBranch : Server
1305
- }
1306
-
1307
- function selectMcp(srv) {
1308
- selectedMcp.value = srv
1309
- mcpDetailTab.value = 'overview'
1310
- mcpSelectedTool.value = null
1311
- mcpDebugTool.value = ''
1312
- Object.keys(mcpDebugParamValues).forEach(k => delete mcpDebugParamValues[k])
1313
- mcpDebugResult.value = null
1314
- mcpDebugError.value = null
1315
- }
1316
-
1317
- function toggleMcp(srv) {
1318
- srv.enabled = !srv.enabled
1319
- ElMessage.success(`已${srv.enabled ? '启用' : '禁用'} ${srv.name}`)
1320
- }
1321
-
1322
- async function deleteMcp(srv) {
1323
- try {
1324
- await ElMessageBox.confirm(`确定删除 "${srv.name}"?此操作不可撤销。`, '删除 MCP Server', {
1325
- confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning',
1326
- })
1327
- mcpServers.value = mcpServers.value.filter(s => s.id !== srv.id)
1328
- selectedMcp.value = null
1329
- ElMessage.success('已删除')
1330
- } catch {}
1331
- }
1332
-
1333
- function openAddMcp() {
1334
- selectedMcp.value = null
1335
- mcpIsAdding.value = true
1336
- mcpAddForm.value = { name: '', description: '', transport: 'sse', endpoint: '' }
1337
- }
1338
-
1339
- function confirmAddMcp() {
1340
- if (!mcpAddForm.value.name.trim()) { ElMessage.warning('请填写名称'); return }
1341
- const newSrv = {
1342
- id: `mcp-${Date.now()}`,
1343
- name: mcpAddForm.value.name.trim(),
1344
- type: 'custom',
1345
- description: mcpAddForm.value.description || '自定义 MCP Server',
1346
- transport: mcpAddForm.value.transport,
1347
- endpoint: mcpAddForm.value.endpoint,
1348
- connected: false, enabled: true,
1349
- configFields: [], tools: [],
1350
- }
1351
- mcpServers.value.push(newSrv)
1352
- mcpConfigValues[newSrv.id] = {}
1353
- mcpIsAdding.value = false
1354
- selectMcp(newSrv)
1355
- ElMessage.success('已添加 MCP Server')
1356
- }
1357
-
1358
- function toggleMcpReveal(key) {
1359
- if (mcpRevealed.has(key)) mcpRevealed.delete(key)
1360
- else mcpRevealed.add(key)
1361
- }
1362
-
1363
- async function testMcpConnection() {
1364
- mcpTesting.value = true
1365
- await new Promise(r => setTimeout(r, 1100))
1366
- mcpTesting.value = false
1367
- const token = mcpConfigValues[selectedMcp.value?.id]?.['GITLAB_PERSONAL_ACCESS_TOKEN']
1368
- if (!token) { ElMessage.warning('请先填写 Access Token'); return }
1369
- selectedMcp.value.connected = true
1370
- ElMessage.success('连接成功')
1371
- }
1372
-
1373
- function saveMcpConfig() {
1374
- ElMessage.success(`${selectedMcp.value?.name} 配置已保存`)
1375
- }
1376
-
1377
- // ── 调试 ─────────────────────────────────────────────────────────
1378
- const mcpDebugToolDef = computed(() =>
1379
- selectedMcp.value?.tools.find(t => t.name === mcpDebugTool.value)
1380
- )
1381
-
1382
- // 切换工具时:重置参数值并初始化各字段默认值
1383
- function onMcpDebugToolChange() {
1384
- Object.keys(mcpDebugParamValues).forEach(k => delete mcpDebugParamValues[k])
1385
- mcpDebugResult.value = null
1386
- mcpDebugError.value = null
1387
- if (!mcpDebugToolDef.value) return
1388
- for (const [key, param] of Object.entries(mcpDebugToolDef.value.params)) {
1389
- if (param.type === 'boolean') mcpDebugParamValues[key] = false
1390
- else if (param.type === 'number') mcpDebugParamValues[key] = undefined
1391
- else mcpDebugParamValues[key] = ''
1392
- }
1393
- }
1394
-
1395
- const MOCK_RESULTS = {
1396
- list_projects: [
1397
- { id: 123, name: 'my-project', namespace: 'mygroup', default_branch: 'main', http_url_to_repo: 'https://gitlab.com/mygroup/my-project.git' },
1398
- { id: 124, name: 'another-repo', namespace: 'mygroup', default_branch: 'main', http_url_to_repo: 'https://gitlab.com/mygroup/another-repo.git' },
1399
- ],
1400
- list_issues: [
1401
- { iid: 1, title: 'Fix login bug', state: 'opened', labels: ['bug'], assignees: [{ username: 'alice' }] },
1402
- { iid: 2, title: 'Add dark mode', state: 'opened', labels: ['feature'], assignees: [] },
1403
- ],
1404
- list_branches: [
1405
- { name: 'main', commit: { id: 'a1b2c3d', message: 'Merge MR !42', authored_date: '2025-10-01' } },
1406
- { name: 'feature/dark-mode', commit: { id: 'e4f5g6h', message: 'Add dark mode toggle', authored_date: '2025-10-15' } },
1407
- ],
1408
- get_pipeline_status: { id: 789, status: 'success', ref: 'main', duration: 124, stages: ['build', 'test', 'deploy'] },
1409
- search_code: [
1410
- { file_path: 'src/auth/login.js', line: 42, fragment: 'const token = await getToken(userId)' },
1411
- { file_path: 'src/utils/api.js', line: 17, fragment: 'Authorization: `Bearer ${token}`' },
1412
- ],
1413
- }
1414
-
1415
- async function runMcpDebug() {
1416
- // 校验 required 字段
1417
- if (mcpDebugToolDef.value) {
1418
- for (const [key, param] of Object.entries(mcpDebugToolDef.value.params)) {
1419
- const val = mcpDebugParamValues[key]
1420
- if (param.required && (val === '' || val === undefined || val === null)) {
1421
- ElMessage.warning(`参数 "${key}" 为必填项`)
1422
- return
1423
- }
1424
- }
1425
- }
1426
- mcpDebugRunning.value = true
1427
- mcpDebugResult.value = null
1428
- mcpDebugError.value = null
1429
- await new Promise(r => setTimeout(r, 900))
1430
- mcpDebugRunning.value = false
1431
- // 构建实际入参(过滤掉空的可选字段)
1432
- const payload = Object.fromEntries(
1433
- Object.entries(mcpDebugParamValues).filter(([, v]) => v !== '' && v !== undefined && v !== null)
1434
- )
1435
- const mock = MOCK_RESULTS[mcpDebugTool.value]
1436
- mcpDebugResult.value = JSON.stringify(
1437
- mock ?? { status: 'ok', params: payload, message: `Tool "${mcpDebugTool.value}" executed successfully` },
1438
- null, 2
1439
- )
1440
- }
1441
- </script>
1442
-
1443
- <style scoped>
1444
- /* ── 三栏容器 ── */
1445
- .skill-panel {
1446
- display: flex;
1447
- height: 100%;
1448
- overflow: hidden;
1449
- }
1450
-
1451
- /* ── 第一栏:导航 ── */
1452
- .skill-nav {
1453
- width: 175px;
1454
- flex-shrink: 0;
1455
- display: flex;
1456
- flex-direction: column;
1457
- border-right: 1px solid var(--border-subtle);
1458
- background: var(--bg-secondary);
1459
- }
1460
- .skill-nav__header {
1461
- padding: 17px 12px 10px;
1462
- flex-shrink: 0;
1463
- }
1464
- .skill-nav__title {
1465
- font-size: 10px;
1466
- font-weight: 600;
1467
- color: var(--text-muted);
1468
- text-transform: uppercase;
1469
- letter-spacing: 0.08em;
1470
- }
1471
- .skill-nav__list {
1472
- display: flex;
1473
- flex-direction: column;
1474
- gap: 2px;
1475
- padding: 0 8px;
1476
- }
1477
- .skill-nav__item {
1478
- display: flex;
1479
- align-items: center;
1480
- gap: 7px;
1481
- padding: 7px 9px;
1482
- border-radius: 8px;
1483
- border: none;
1484
- background: transparent;
1485
- color: var(--text-muted);
1486
- font-size: 12px;
1487
- font-family: Inter, 'PingFang SC', sans-serif;
1488
- cursor: pointer;
1489
- transition: all 0.12s;
1490
- width: 100%;
1491
- text-align: left;
1492
- }
1493
- .skill-nav__item:hover { background: var(--bg-tertiary); color: var(--text-secondary); }
1494
- .skill-nav__item.active { background: rgba(26,111,196,0.1); color: var(--brand-400); }
1495
- .skill-nav__badge {
1496
- margin-left: auto;
1497
- min-width: 16px; height: 16px; padding: 0 4px;
1498
- border-radius: 8px;
1499
- background: rgba(26,111,196,0.15);
1500
- color: var(--brand-400);
1501
- font-size: 10px;
1502
- font-family: 'Cascadia Code', 'Consolas', monospace;
1503
- font-weight: 600;
1504
- display: flex; align-items: center; justify-content: center;
1505
- }
1506
-
1507
- /* ── 第二栏:列表 ── */
1508
- .skill-list-col {
1509
- width: 200px;
1510
- flex-shrink: 0;
1511
- display: flex;
1512
- flex-direction: column;
1513
- border-right: 1px solid var(--border-subtle);
1514
- background: var(--bg-secondary);
1515
- overflow: hidden;
1516
- }
1517
- .skill-list-col__header {
1518
- display: flex;
1519
- align-items: center;
1520
- justify-content: space-between;
1521
- padding: 17px 12px 10px;
1522
- flex-shrink: 0;
1523
- }
1524
- .skill-list-col__title {
1525
- font-size: 10px; font-weight: 600; color: var(--text-muted);
1526
- text-transform: uppercase; letter-spacing: 0.08em;
1527
- }
1528
- .skill-col-action-btn {
1529
- width: 22px; height: 22px; border-radius: 5px;
1530
- border: 1px solid var(--border-default);
1531
- background: transparent; color: var(--text-muted);
1532
- display: flex; align-items: center; justify-content: center;
1533
- cursor: pointer; transition: all 0.12s;
1534
- }
1535
- .skill-col-action-btn:hover {
1536
- border-color: rgba(26,111,196,0.4); color: var(--brand-400); background: rgba(26,111,196,0.08);
1537
- }
1538
-
1539
- .skill-list-body { display: flex; flex-direction: column; gap: 12px; padding: 4px 8px 16px; }
1540
- .skill-list-group { display: flex; flex-direction: column; gap: 1px; }
1541
- .skill-list-group__label {
1542
- font-size: 9.5px; font-weight: 600; color: var(--text-muted);
1543
- text-transform: uppercase; letter-spacing: 0.08em;
1544
- padding: 4px 6px 5px; opacity: 0.7;
1545
- }
1546
- .skill-list-item {
1547
- display: flex; align-items: center; gap: 7px;
1548
- padding: 7px 8px; border-radius: 7px;
1549
- cursor: pointer; transition: background 0.12s; min-width: 0;
1550
- }
1551
- .skill-list-item:hover { background: var(--bg-tertiary); }
1552
- .skill-list-item.active { background: rgba(26,111,196,0.1); }
1553
- .skill-list-item.disabled { opacity: 0.5; }
1554
-
1555
- .skill-list-item__icon {
1556
- flex-shrink: 0;
1557
- width: 24px; height: 24px; border-radius: 6px;
1558
- background: rgba(26,111,196,0.1);
1559
- border: 1px solid rgba(26,111,196,0.15);
1560
- display: flex; align-items: center; justify-content: center;
1561
- color: var(--brand-400);
1562
- }
1563
- .skill-list-item__icon.user {
1564
- background: rgba(124,58,237,0.1);
1565
- border-color: rgba(124,58,237,0.2); color: #7c3aed;
1566
- }
1567
- .skill-list-item__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
1568
- .skill-list-item__name {
1569
- font-size: 12px; color: var(--text-primary); font-weight: 500;
1570
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1571
- font-family: Inter, 'PingFang SC', sans-serif;
1572
- }
1573
- .skill-list-item__desc {
1574
- font-size: 10px; color: var(--text-muted);
1575
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.3;
1576
- }
1577
- .skill-list-item__status {
1578
- flex-shrink: 0; width: 6px; height: 6px; border-radius: 50%;
1579
- }
1580
- .skill-list-item__status.on { background: #4ade80; }
1581
- .skill-list-item__status.off { background: var(--border-default); }
1582
-
1583
- .skill-list-empty {
1584
- display: flex; flex-direction: column; align-items: center; justify-content: center;
1585
- padding: 40px 16px; color: var(--text-muted); font-size: 12px;
1586
- font-family: Inter, 'PingFang SC', sans-serif; text-align: center;
1587
- }
1588
- .skill-list-empty__btn {
1589
- margin-top: 8px; padding: 5px 12px; border-radius: 6px;
1590
- border: 1px solid var(--border-default); background: transparent;
1591
- color: var(--brand-400); font-size: 11px; cursor: pointer; transition: all 0.12s;
1592
- }
1593
- .skill-list-empty__btn:hover { background: rgba(26,111,196,0.08); border-color: rgba(26,111,196,0.4); }
1594
-
1595
- /* ── 第三栏:详情 ── */
1596
- .skill-detail-col {
1597
- flex: 1; min-width: 0; display: flex; flex-direction: column;
1598
- background: var(--bg-primary); overflow: hidden;
1599
- }
1600
- .skill-detail-empty {
1601
- display: flex; flex-direction: column; align-items: center; justify-content: center;
1602
- height: 100%; color: var(--text-muted); font-size: 13px;
1603
- font-family: Inter, 'PingFang SC', sans-serif;
1604
- }
1605
-
1606
- /* 头部 */
1607
- .skill-detail-header {
1608
- display: flex; align-items: center; justify-content: space-between;
1609
- padding: 14px 16px 12px; gap: 10px;
1610
- border-bottom: 1px solid var(--border-subtle);
1611
- background: var(--bg-secondary); flex-shrink: 0; min-height: 50px;
1612
- }
1613
- .skill-detail-header__icon {
1614
- width: 26px; height: 26px; border-radius: 7px;
1615
- background: rgba(26,111,196,0.1); border: 1px solid rgba(26,111,196,0.15);
1616
- display: flex; align-items: center; justify-content: center;
1617
- color: var(--brand-400); flex-shrink: 0;
1618
- }
1619
- .skill-detail-header__icon.user {
1620
- background: rgba(124,58,237,0.1); border-color: rgba(124,58,237,0.2); color: #7c3aed;
1621
- }
1622
- .skill-detail-header__name {
1623
- font-size: 13px; font-weight: 600; color: var(--text-primary);
1624
- font-family: 'Cascadia Code', 'Consolas', monospace;
1625
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0;
1626
- }
1627
- .skill-detail-header__type {
1628
- font-size: 10px; color: var(--text-muted);
1629
- padding: 2px 7px; border-radius: 4px;
1630
- border: 1px solid var(--border-subtle); background: var(--bg-tertiary); flex-shrink: 0;
1631
- }
1632
- .skill-detail-header__title {
1633
- font-size: 12px; font-weight: 600; color: var(--text-secondary);
1634
- text-transform: uppercase; letter-spacing: 0.06em;
1635
- }
1636
-
1637
- /* 操作按钮 */
1638
- .skill-detail-action {
1639
- display: inline-flex; align-items: center;
1640
- padding: 4px 10px; border-radius: 6px;
1641
- border: 1px solid var(--border-default); background: transparent;
1642
- font-size: 11.5px; font-family: Inter, 'PingFang SC', sans-serif;
1643
- cursor: pointer; transition: all 0.12s; color: var(--text-muted);
1644
- height: 26px; white-space: nowrap;
1645
- }
1646
- .skill-detail-action:hover { background: var(--bg-tertiary); color: var(--text-primary); }
1647
- .skill-detail-action.enable { color: #4ade80; border-color: rgba(74,222,128,0.3); }
1648
- .skill-detail-action.enable:hover { background: rgba(74,222,128,0.08); }
1649
- .skill-detail-action.disable { color: var(--text-muted); }
1650
- .skill-detail-action.edit { color: var(--brand-400); border-color: rgba(26,111,196,0.3); }
1651
- .skill-detail-action.edit:hover { background: rgba(26,111,196,0.08); }
1652
- .skill-detail-action.delete { color: #f87171; border-color: rgba(248,113,113,0.3); padding: 4px 8px; }
1653
- .skill-detail-action.delete:hover { background: rgba(248,113,113,0.08); }
1654
- .skill-detail-action.save { color: #fff; background: var(--brand-500); border-color: var(--brand-500); }
1655
- .skill-detail-action.save:hover { filter: brightness(1.1); }
1656
- .skill-detail-action.cancel { color: var(--text-muted); }
1657
-
1658
- /* 描述 */
1659
- .skill-detail-desc {
1660
- padding: 10px 16px 8px; font-size: 12px; color: var(--text-muted); line-height: 1.6;
1661
- border-bottom: 1px solid var(--border-subtle); flex-shrink: 0;
1662
- font-family: Inter, 'PingFang SC', sans-serif;
1663
- }
1664
-
1665
- /* Tab */
1666
- .skill-detail-tabs {
1667
- display: flex; align-items: center; padding: 0 12px; gap: 2px;
1668
- border-bottom: 1px solid var(--border-subtle);
1669
- background: var(--bg-secondary); flex-shrink: 0; min-height: 36px;
1670
- }
1671
- .skill-detail-tab {
1672
- display: inline-flex; align-items: center;
1673
- height: 36px; padding: 0 10px;
1674
- border: none; border-bottom: 2px solid transparent;
1675
- background: transparent; color: var(--text-muted);
1676
- font-size: 12px; font-family: Inter, 'PingFang SC', sans-serif;
1677
- cursor: pointer; transition: all 0.12s;
1678
- }
1679
- .skill-detail-tab:hover { color: var(--text-secondary); }
1680
- .skill-detail-tab.active { color: var(--text-primary); border-bottom-color: var(--brand-500); }
1681
- .skill-tab-action {
1682
- width: 26px; height: 26px; border-radius: 5px;
1683
- border: none; background: transparent; color: var(--text-muted);
1684
- display: flex; align-items: center; justify-content: center;
1685
- cursor: pointer; transition: all 0.12s;
1686
- }
1687
- .skill-tab-action:hover { background: var(--bg-tertiary); color: var(--text-primary); }
1688
-
1689
- /* 预览 Markdown */
1690
- .skill-detail-preview {
1691
- padding: 20px 24px 32px;
1692
- font-size: 13.5px; line-height: 1.75;
1693
- color: var(--text-primary);
1694
- font-family: Inter, 'PingFang SC', sans-serif;
1695
- }
1696
- .skill-detail-preview :deep(h1),
1697
- .skill-detail-preview :deep(h2),
1698
- .skill-detail-preview :deep(h3) {
1699
- color: var(--text-primary); font-weight: 600; margin: 1.2em 0 0.5em; line-height: 1.4;
1700
- font-family: Inter, 'PingFang SC', sans-serif;
1701
- }
1702
- .skill-detail-preview :deep(h1) { font-size: 17px; }
1703
- .skill-detail-preview :deep(h2) { font-size: 15px; }
1704
- .skill-detail-preview :deep(h3) { font-size: 13.5px; }
1705
- .skill-detail-preview :deep(p) { margin: 0.6em 0; }
1706
- .skill-detail-preview :deep(code) {
1707
- background: var(--bg-tertiary); border: 1px solid var(--border-subtle); border-radius: 4px;
1708
- padding: 1px 5px; font-family: 'Cascadia Code', 'Consolas', monospace;
1709
- font-size: 12px; color: var(--brand-400);
1710
- }
1711
- .skill-detail-preview :deep(pre) {
1712
- background: var(--bg-secondary); border: 1px solid var(--border-subtle);
1713
- border-radius: 8px; padding: 12px 16px; overflow-x: auto; margin: 0.8em 0;
1714
- }
1715
- .skill-detail-preview :deep(pre code) { background: transparent; border: none; padding: 0; color: var(--text-secondary); }
1716
- .skill-detail-preview :deep(ul),
1717
- .skill-detail-preview :deep(ol) { margin: 0.5em 0; padding-left: 1.5em; }
1718
- .skill-detail-preview :deep(li) { margin: 0.25em 0; }
1719
- .skill-detail-preview :deep(strong) { color: var(--text-primary); font-weight: 600; }
1720
- .skill-detail-preview :deep(blockquote) {
1721
- border-left: 3px solid var(--brand-400); margin: 0.8em 0;
1722
- padding: 4px 12px; color: var(--text-muted);
1723
- background: rgba(26,111,196,0.04); border-radius: 0 6px 6px 0;
1724
- }
1725
- .skill-detail-preview :deep(hr) { border: none; border-top: 1px solid var(--border-subtle); margin: 1em 0; }
1726
- .skill-detail-preview :deep(a) { color: var(--brand-400); text-decoration: none; }
1727
- .skill-detail-preview :deep(a:hover) { text-decoration: underline; }
1728
-
1729
- /* 代码视图 */
1730
- .skill-detail-code { padding: 16px 20px; }
1731
- .skill-detail-code pre {
1732
- margin: 0; font-family: 'Cascadia Code', 'Consolas', monospace;
1733
- font-size: 12px; line-height: 1.6; color: var(--text-secondary);
1734
- white-space: pre-wrap; word-break: break-word;
1735
- }
1736
-
1737
- /* 状态栏 */
1738
- .skill-detail-statusbar {
1739
- display: flex; align-items: center; gap: 6px;
1740
- padding: 5px 14px; border-top: 1px solid var(--border-subtle);
1741
- background: var(--bg-secondary); flex-shrink: 0;
1742
- font-size: 11px; color: var(--text-muted); overflow: hidden;
1743
- }
1744
- .status-cmd { color: var(--brand-400); }
1745
- .status-sep { opacity: 0.4; }
1746
- .status-on { color: #4ade80; }
1747
- .status-off { color: var(--text-muted); }
1748
-
1749
- /* ── 表单 ── */
1750
- .skill-form {
1751
- display: flex; flex-direction: column; gap: 18px;
1752
- padding: 16px 20px 32px;
1753
- }
1754
- .skill-form__field { display: flex; flex-direction: column; gap: 5px; }
1755
- .skill-form__label {
1756
- font-size: 11px; font-weight: 600; color: var(--text-muted);
1757
- text-transform: uppercase; letter-spacing: 0.06em;
1758
- }
1759
- .skill-form__required { color: #f87171; }
1760
- .skill-form__input,
1761
- .skill-form__textarea {
1762
- width: 100%; padding: 8px 10px; border-radius: 8px;
1763
- border: 1px solid var(--border-default); background: var(--bg-secondary);
1764
- color: var(--text-primary); font-size: 13px;
1765
- font-family: Inter, 'PingFang SC', sans-serif;
1766
- outline: none; transition: border-color 0.15s, box-shadow 0.15s;
1767
- resize: vertical; box-sizing: border-box;
1768
- }
1769
- .skill-form__input:focus,
1770
- .skill-form__textarea:focus {
1771
- border-color: rgba(26,111,196,0.6);
1772
- box-shadow: 0 0 0 3px rgba(26,111,196,0.08);
1773
- }
1774
- .skill-form__textarea--code {
1775
- font-family: 'Cascadia Code', 'Consolas', monospace;
1776
- font-size: 12px; line-height: 1.6;
1777
- }
1778
-
1779
- /* ── CodeMirror Markdown 编辑器 ── */
1780
- .skill-content-editor {
1781
- border: 1px solid var(--border-default);
1782
- border-radius: 8px;
1783
- overflow: hidden;
1784
- height: 340px;
1785
- background: var(--bg-secondary);
1786
- transition: border-color 0.15s, box-shadow 0.15s;
1787
- }
1788
- .skill-content-editor:focus-within {
1789
- border-color: rgba(26,111,196,0.6);
1790
- box-shadow: 0 0 0 3px rgba(26,111,196,0.08);
1791
- }
1792
- .skill-content-editor.is-polishing {
1793
- border-color: rgba(124,58,237,0.4) !important;
1794
- box-shadow: 0 0 0 3px rgba(124,58,237,0.07) !important;
1795
- }
1796
- .skill-cm-editor {
1797
- width: 100%;
1798
- height: 100%;
1799
- }
1800
- :deep(.skill-cm-editor .cm-editor) {
1801
- height: 100%;
1802
- outline: none;
1803
- background: var(--bg-secondary);
1804
- }
1805
- :deep(.skill-cm-editor .cm-scroller) {
1806
- height: 100%;
1807
- }
1808
- :deep(.skill-cm-editor .cm-gutters) {
1809
- background: var(--bg-secondary) !important;
1810
- border-right: 1px solid var(--border-subtle) !important;
1811
- color: var(--text-faint, var(--text-muted)) !important;
1812
- font-size: 11px;
1813
- min-width: 3em;
1814
- user-select: none;
1815
- }
1816
- :deep(.skill-cm-editor .cm-activeLineGutter) {
1817
- background: var(--bg-tertiary) !important;
1818
- }
1819
- :deep(.skill-cm-editor .cm-activeLine) {
1820
- background: var(--bg-tertiary) !important;
1821
- }
1822
- :deep(.skill-cm-editor .cm-cursor) {
1823
- border-left-color: var(--brand-400) !important;
1824
- }
1825
- :deep(.skill-cm-editor .cm-selectionBackground) {
1826
- background: rgba(26,111,196,0.25) !important;
1827
- }
1828
- :deep(.skill-cm-editor .cm-placeholder) {
1829
- color: var(--text-muted);
1830
- font-style: italic;
1831
- }
1832
- .skill-form__hint {
1833
- font-size: 11px; color: var(--text-muted); margin: 0; line-height: 1.5;
1834
- font-family: Inter, 'PingFang SC', sans-serif;
1835
- }
1836
- .skill-form__hint code {
1837
- background: var(--bg-tertiary); border: 1px solid var(--border-subtle); border-radius: 3px;
1838
- padding: 0 4px; font-family: 'Cascadia Code', 'Consolas', monospace;
1839
- font-size: 10.5px; color: var(--brand-400);
1840
- }
1841
-
1842
- /* ── AI 润色按钮 ── */
1843
- .skill-polish-btn {
1844
- display: inline-flex; align-items: center; gap: 5px;
1845
- padding: 3px 9px; border-radius: 6px;
1846
- border: 1px solid rgba(124,58,237,0.35);
1847
- background: rgba(124,58,237,0.07);
1848
- color: #a78bfa;
1849
- font-size: 11px; font-family: Inter, 'PingFang SC', sans-serif;
1850
- cursor: pointer; transition: all 0.15s;
1851
- white-space: nowrap; height: 24px;
1852
- }
1853
- .skill-polish-btn:hover:not(:disabled) {
1854
- background: rgba(124,58,237,0.14); border-color: rgba(124,58,237,0.55);
1855
- color: #c4b5fd; box-shadow: 0 0 0 3px rgba(124,58,237,0.08);
1856
- }
1857
- .skill-polish-btn:disabled { opacity: 0.5; cursor: not-allowed; }
1858
- .skill-polish-btn.loading { color: #c4b5fd; border-color: rgba(124,58,237,0.5); }
1859
-
1860
- /* 小转圈 */
1861
- .skill-polish-spinner {
1862
- width: 10px; height: 10px; border-radius: 50%;
1863
- border: 1.5px solid rgba(167,139,250,0.3);
1864
- border-top-color: #a78bfa;
1865
- animation: skill-spin 0.7s linear infinite; flex-shrink: 0;
1866
- }
1867
- @keyframes skill-spin { to { transform: rotate(360deg); } }
1868
-
1869
- /* 进度条 */
1870
- .skill-polish-progress {
1871
- height: 2px; border-radius: 1px;
1872
- background: rgba(124,58,237,0.12);
1873
- overflow: hidden; margin-bottom: 4px;
1874
- }
1875
- .skill-polish-progress__bar {
1876
- height: 100%; width: 40%;
1877
- background: linear-gradient(90deg, transparent, #a78bfa, transparent);
1878
- animation: skill-shimmer 1.2s ease-in-out infinite;
1879
- }
1880
- @keyframes skill-shimmer {
1881
- 0% { transform: translateX(-150%); }
1882
- 100% { transform: translateX(400%); }
1883
- }
1884
-
1885
- /* textarea 润色中样式 */
1886
- .skill-form__textarea.is-polishing {
1887
- border-color: rgba(124,58,237,0.4) !important;
1888
- box-shadow: 0 0 0 3px rgba(124,58,237,0.07) !important;
1889
- }
1890
-
1891
- /* 润色蒙层 */
1892
- .skill-polish-overlay {
1893
- position: absolute; inset: 0; border-radius: 8px;
1894
- background: rgba(0,0,0,0.18);
1895
- backdrop-filter: blur(1px);
1896
- display: flex; align-items: center; justify-content: center;
1897
- pointer-events: none;
1898
- }
1899
- .skill-polish-overlay__inner {
1900
- display: flex; align-items: center; gap: 7px;
1901
- padding: 7px 14px; border-radius: 8px;
1902
- background: rgba(124,58,237,0.18);
1903
- border: 1px solid rgba(167,139,250,0.3);
1904
- color: #c4b5fd; font-size: 12px;
1905
- font-family: Inter, 'PingFang SC', sans-serif;
1906
- animation: skill-pulse 1.5s ease-in-out infinite;
1907
- }
1908
- @keyframes skill-pulse {
1909
- 0%, 100% { opacity: 0.8; }
1910
- 50% { opacity: 1; }
1911
- }
1912
-
1913
- /* 润色完成提示 */
1914
- .skill-polish-diff-hint {
1915
- display: flex; align-items: center; gap: 6px;
1916
- padding: 4px 8px; border-radius: 6px;
1917
- background: rgba(74,222,128,0.06);
1918
- border: 1px solid rgba(74,222,128,0.2);
1919
- font-size: 11px; color: var(--text-muted);
1920
- font-family: Inter, 'PingFang SC', sans-serif;
1921
- }
1922
- .skill-polish-revert {
1923
- margin-left: auto; padding: 1px 8px; border-radius: 4px;
1924
- border: 1px solid var(--border-default); background: transparent;
1925
- color: var(--text-muted); font-size: 10.5px; cursor: pointer;
1926
- transition: all 0.12s;
1927
- }
1928
- .skill-polish-revert:hover { color: var(--text-primary); background: var(--bg-tertiary); }
1929
-
1930
- /* ══════════════════════════════════════════════
1931
- MCP 专用样式
1932
- ══════════════════════════════════════════════ */
1933
-
1934
- /* 列表项图标:gitlab 色调 */
1935
- .skill-list-item__icon.mcp-gitlab {
1936
- background: rgba(226,67,41,0.1);
1937
- border-color: rgba(226,67,41,0.18);
1938
- color: #e24329;
1939
- }
1940
- .skill-detail-header__icon.mcp-gitlab {
1941
- background: rgba(226,67,41,0.1);
1942
- border-color: rgba(226,67,41,0.18);
1943
- color: #e24329;
1944
- }
1945
-
1946
- /* Tab 工具计数 */
1947
- .mcp-tab-count {
1948
- margin-left: 5px;
1949
- padding: 0 5px; height: 15px; border-radius: 7px;
1950
- background: rgba(26,111,196,0.12);
1951
- color: var(--brand-400);
1952
- font-size: 9.5px; font-weight: 600;
1953
- display: inline-flex; align-items: center; justify-content: center;
1954
- }
1955
-
1956
- /* ── 概览 ── */
1957
- .mcp-overview { padding: 16px 18px 24px; display: flex; flex-direction: column; gap: 16px; }
1958
-
1959
- .mcp-info-grid {
1960
- display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
1961
- }
1962
- .mcp-info-card {
1963
- display: flex; flex-direction: column; gap: 4px;
1964
- padding: 9px 11px; border-radius: 8px;
1965
- border: 1px solid var(--border-subtle);
1966
- background: var(--bg-secondary);
1967
- }
1968
- .mcp-info-card__label {
1969
- font-size: 9.5px; font-weight: 600; color: var(--text-muted);
1970
- text-transform: uppercase; letter-spacing: 0.06em;
1971
- }
1972
- .mcp-info-card__value {
1973
- font-size: 12px; color: var(--text-primary);
1974
- word-break: break-all; display: flex; align-items: center; gap: 5px;
1975
- line-height: 1.4;
1976
- }
1977
-
1978
- /* 连接状态点 */
1979
- .mcp-dot {
1980
- display: inline-block; width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
1981
- }
1982
- .mcp-dot.on { background: #4ade80; box-shadow: 0 0 0 2px rgba(74,222,128,0.18); }
1983
- .mcp-dot.off { background: var(--border-default); }
1984
-
1985
- .mcp-section-label {
1986
- font-size: 9.5px; font-weight: 600; color: var(--text-muted);
1987
- text-transform: uppercase; letter-spacing: 0.07em;
1988
- margin: 0 0 7px;
1989
- }
1990
-
1991
- .mcp-chip-list { display: flex; flex-wrap: wrap; gap: 5px; }
1992
- .mcp-chip {
1993
- display: inline-flex; align-items: center;
1994
- padding: 2px 9px; border-radius: 5px;
1995
- border: 1px solid var(--border-default);
1996
- background: var(--bg-secondary);
1997
- font-size: 11px; color: var(--text-secondary);
1998
- cursor: pointer; transition: all 0.12s; white-space: nowrap;
1999
- }
2000
- .mcp-chip:hover {
2001
- background: rgba(26,111,196,0.08);
2002
- border-color: rgba(26,111,196,0.35);
2003
- color: var(--brand-400);
2004
- }
2005
-
2006
- /* ── 工具列表布局(两列:名单 + 详情)── */
2007
- .mcp-tools-layout {
2008
- display: flex; height: 100%; overflow: hidden;
2009
- }
2010
- .mcp-tool-list {
2011
- width: 188px; flex-shrink: 0;
2012
- border-right: 1px solid var(--border-subtle);
2013
- padding: 6px; display: flex; flex-direction: column; gap: 1px;
2014
- overflow-y: auto;
2015
- }
2016
- .mcp-tool-row {
2017
- display: flex; align-items: center; gap: 7px;
2018
- padding: 6px 8px; border-radius: 6px;
2019
- cursor: pointer; transition: background 0.12s;
2020
- font-size: 11.5px; color: var(--text-secondary);
2021
- font-family: 'Cascadia Code', 'Consolas', monospace;
2022
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
2023
- }
2024
- .mcp-tool-row:hover { background: var(--bg-tertiary); color: var(--text-primary); }
2025
- .mcp-tool-row.active { background: rgba(26,111,196,0.1); color: var(--brand-400); }
2026
-
2027
- .mcp-tool-detail {
2028
- flex: 1; min-width: 0; padding: 14px 16px; overflow-y: auto;
2029
- }
2030
- .mcp-tool-detail__empty {
2031
- height: 100%; display: flex; flex-direction: column;
2032
- align-items: center; justify-content: center;
2033
- color: var(--text-muted); font-size: 12px; gap: 4px;
2034
- font-family: Inter, 'PingFang SC', sans-serif;
2035
- }
2036
- .mcp-tool-detail__name {
2037
- font-size: 13px; font-weight: 600; color: var(--brand-400);
2038
- margin: 0 0 6px;
2039
- }
2040
- .mcp-tool-detail__desc {
2041
- font-size: 12px; color: var(--text-secondary);
2042
- line-height: 1.65; margin: 0 0 4px;
2043
- font-family: Inter, 'PingFang SC', sans-serif;
2044
- }
2045
-
2046
- /* 参数列表 */
2047
- .mcp-param-list { display: flex; flex-direction: column; gap: 7px; }
2048
- .mcp-param-item {
2049
- padding: 8px 10px; border-radius: 7px;
2050
- border: 1px solid var(--border-subtle);
2051
- background: var(--bg-secondary);
2052
- display: flex; flex-direction: column; gap: 3px;
2053
- }
2054
- .mcp-param-item__row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
2055
- .mcp-param-name {
2056
- font-size: 11.5px; font-weight: 600; color: var(--text-primary);
2057
- }
2058
- .mcp-param-type {
2059
- font-size: 10px; padding: 1px 6px; border-radius: 4px;
2060
- background: rgba(26,111,196,0.08); color: var(--brand-400);
2061
- border: 1px solid rgba(26,111,196,0.15);
2062
- }
2063
- .mcp-param-required {
2064
- font-size: 9.5px; padding: 1px 5px; border-radius: 4px;
2065
- background: rgba(248,113,113,0.07); color: #f87171;
2066
- border: 1px solid rgba(248,113,113,0.18);
2067
- }
2068
- .mcp-param-desc {
2069
- font-size: 11px; color: var(--text-muted); margin: 0;
2070
- line-height: 1.5; font-family: Inter, 'PingFang SC', sans-serif;
2071
- }
2072
- .mcp-param-enums { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
2073
- .mcp-param-enum {
2074
- font-size: 10px; padding: 1px 7px; border-radius: 4px;
2075
- background: var(--bg-tertiary); border: 1px solid var(--border-subtle);
2076
- color: var(--text-secondary);
2077
- }
2078
-
2079
- /* ── 配置面板 ── */
2080
- .mcp-config-pane {
2081
- display: flex; flex-direction: column; gap: 16px;
2082
- padding: 16px 18px 32px;
2083
- }
2084
- .mcp-secret-wrap { position: relative; display: flex; }
2085
- .mcp-secret-wrap .skill-form__input { padding-right: 32px; }
2086
- .mcp-reveal-btn {
2087
- position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
2088
- display: flex; align-items: center; justify-content: center;
2089
- border: none; background: transparent; color: var(--text-muted);
2090
- cursor: pointer; border-radius: 4px; padding: 3px; transition: color 0.12s;
2091
- }
2092
- .mcp-reveal-btn:hover { color: var(--text-primary); }
2093
-
2094
- .mcp-config-actions {
2095
- display: flex; gap: 8px; padding-top: 2px;
2096
- }
2097
-
2098
- /* ── 调试面板 ── */
2099
- .mcp-debug-pane {
2100
- display: flex; flex-direction: column; gap: 14px;
2101
- padding: 16px 18px;
2102
- height: 100%; box-sizing: border-box;
2103
- }
2104
-
2105
- /* 输出区 */
2106
- .mcp-debug-output {
2107
- flex: 1; display: flex; flex-direction: column;
2108
- border: 1px solid var(--border-subtle); border-radius: 8px;
2109
- overflow: hidden; min-height: 140px;
2110
- }
2111
- .mcp-debug-output__header {
2112
- display: flex; align-items: center; justify-content: space-between;
2113
- padding: 5px 10px;
2114
- background: var(--bg-secondary); border-bottom: 1px solid var(--border-subtle);
2115
- font-size: 10px; color: var(--text-muted); flex-shrink: 0;
2116
- letter-spacing: 0.05em; text-transform: uppercase;
2117
- }
2118
- .mcp-out-ok { color: #4ade80; font-size: 10.5px; }
2119
- .mcp-out-err { color: #f87171; font-size: 10.5px; }
2120
-
2121
- .mcp-debug-output__body {
2122
- flex: 1; overflow-y: auto; background: var(--bg-primary);
2123
- }
2124
- .mcp-debug-output__empty {
2125
- display: flex; flex-direction: column; align-items: center; justify-content: center;
2126
- height: 100%; min-height: 100px;
2127
- color: var(--text-muted); font-size: 11.5px; gap: 4px;
2128
- font-family: Inter, 'PingFang SC', sans-serif;
2129
- }
2130
- .mcp-debug-output__pre {
2131
- margin: 0; padding: 12px 14px;
2132
- font-family: 'Cascadia Code', 'Consolas', monospace;
2133
- font-size: 11.5px; line-height: 1.65;
2134
- color: var(--text-secondary);
2135
- white-space: pre-wrap; word-break: break-word;
2136
- }
2137
- .mcp-debug-output__pre.error { color: #f87171; }
2138
-
2139
- /* spin 动画(调试执行中)*/
2140
- .mcp-spin { animation: mcp-spin 0.75s linear infinite; }
2141
- @keyframes mcp-spin { to { transform: rotate(360deg); } }
2142
-
2143
- /* ── 调试面板:el-select 覆盖 ── */
2144
- .mcp-debug-select {
2145
- width: 100%;
2146
- }
2147
- :deep(.mcp-debug-select .el-select__wrapper) {
2148
- background: var(--bg-secondary);
2149
- border-color: var(--border-default);
2150
- border-radius: 8px;
2151
- box-shadow: none !important;
2152
- font-family: 'Cascadia Code', 'Consolas', monospace;
2153
- font-size: 12px;
2154
- color: var(--text-primary);
2155
- min-height: 32px;
2156
- }
2157
- :deep(.mcp-debug-select .el-select__wrapper:hover) {
2158
- border-color: rgba(26,111,196,0.5);
2159
- }
2160
- :deep(.mcp-debug-select .el-select__wrapper.is-focused) {
2161
- border-color: rgba(26,111,196,0.6);
2162
- box-shadow: 0 0 0 3px rgba(26,111,196,0.08) !important;
2163
- }
2164
- :deep(.mcp-debug-select .el-select__placeholder),
2165
- :deep(.mcp-debug-select .el-select__selected-item) {
2166
- font-family: 'Cascadia Code', 'Consolas', monospace;
2167
- font-size: 12px;
2168
- color: var(--text-primary);
2169
- }
2170
- :deep(.mcp-debug-select .el-select__caret) {
2171
- color: var(--text-muted);
2172
- }
2173
-
2174
- /* select 下拉项中的描述 */
2175
- .mcp-select-opt-desc {
2176
- display: block;
2177
- font-size: 10.5px;
2178
- color: var(--text-muted);
2179
- font-family: Inter, 'PingFang SC', sans-serif;
2180
- white-space: nowrap;
2181
- overflow: hidden;
2182
- text-overflow: ellipsis;
2183
- max-width: 260px;
2184
- margin-top: 1px;
2185
- }
2186
-
2187
- /* ── 调试:参数表单区域 ── */
2188
- .mcp-debug-params {
2189
- border: 1px solid var(--border-subtle);
2190
- border-radius: 8px;
2191
- overflow: hidden;
2192
- flex-shrink: 0;
2193
- }
2194
- .mcp-debug-params__header {
2195
- display: flex;
2196
- align-items: center;
2197
- justify-content: space-between;
2198
- padding: 5px 10px;
2199
- background: var(--bg-secondary);
2200
- border-bottom: 1px solid var(--border-subtle);
2201
- font-size: 9.5px;
2202
- color: var(--text-muted);
2203
- text-transform: uppercase;
2204
- letter-spacing: 0.06em;
2205
- }
2206
- .mcp-debug-params__count {
2207
- font-size: 9.5px;
2208
- color: var(--text-muted);
2209
- opacity: 0.7;
2210
- }
2211
- .mcp-debug-params__body {
2212
- display: flex;
2213
- flex-direction: column;
2214
- gap: 0;
2215
- }
2216
-
2217
- /* 每个参数字段行 */
2218
- .mcp-debug-field {
2219
- display: flex;
2220
- flex-direction: column;
2221
- gap: 4px;
2222
- padding: 10px 12px;
2223
- border-bottom: 1px solid var(--border-subtle);
2224
- }
2225
- .mcp-debug-field:last-child {
2226
- border-bottom: none;
2227
- }
2228
- .mcp-debug-field__header {
2229
- display: flex;
2230
- align-items: center;
2231
- gap: 6px;
2232
- flex-wrap: wrap;
2233
- }
2234
- .mcp-debug-field__name {
2235
- font-size: 12px;
2236
- font-weight: 600;
2237
- color: var(--text-primary);
2238
- }
2239
- .mcp-debug-field__desc {
2240
- font-size: 11px;
2241
- color: var(--text-muted);
2242
- margin: 0;
2243
- line-height: 1.5;
2244
- font-family: Inter, 'PingFang SC', sans-serif;
2245
- }
2246
-
2247
- /* optional 标记(与 required 区分) */
2248
- .mcp-param-optional {
2249
- font-size: 9.5px;
2250
- padding: 1px 5px;
2251
- border-radius: 4px;
2252
- background: var(--bg-tertiary);
2253
- color: var(--text-muted);
2254
- border: 1px solid var(--border-subtle);
2255
- }
2256
-
2257
- /* el-input 覆盖 */
2258
- .mcp-debug-input { width: 100%; }
2259
- :deep(.mcp-debug-input .el-input__wrapper) {
2260
- background: var(--bg-secondary);
2261
- border-radius: 7px;
2262
- box-shadow: 0 0 0 1px var(--border-default) !important;
2263
- font-family: 'Cascadia Code', 'Consolas', monospace;
2264
- font-size: 12px;
2265
- padding: 0 10px;
2266
- min-height: 30px;
2267
- }
2268
- :deep(.mcp-debug-input .el-input__wrapper:hover) {
2269
- box-shadow: 0 0 0 1px rgba(26,111,196,0.45) !important;
2270
- }
2271
- :deep(.mcp-debug-input .el-input__wrapper.is-focus) {
2272
- box-shadow: 0 0 0 1px rgba(26,111,196,0.6), 0 0 0 3px rgba(26,111,196,0.08) !important;
2273
- }
2274
- :deep(.mcp-debug-input .el-input__inner) {
2275
- font-family: 'Cascadia Code', 'Consolas', monospace;
2276
- font-size: 12px;
2277
- color: var(--text-primary);
2278
- height: 30px;
2279
- }
2280
- :deep(.mcp-debug-input .el-input__inner::placeholder) {
2281
- color: var(--text-muted);
2282
- font-style: italic;
2283
- }
2284
-
2285
- /* el-input-number 覆盖 */
2286
- .mcp-debug-number { width: 100%; }
2287
- :deep(.mcp-debug-number .el-input__wrapper) {
2288
- background: var(--bg-secondary);
2289
- border-radius: 7px;
2290
- box-shadow: 0 0 0 1px var(--border-default) !important;
2291
- padding: 0 10px;
2292
- min-height: 30px;
2293
- }
2294
- :deep(.mcp-debug-number .el-input__wrapper:hover) {
2295
- box-shadow: 0 0 0 1px rgba(26,111,196,0.45) !important;
2296
- }
2297
- :deep(.mcp-debug-number .el-input__wrapper.is-focus) {
2298
- box-shadow: 0 0 0 1px rgba(26,111,196,0.6), 0 0 0 3px rgba(26,111,196,0.08) !important;
2299
- }
2300
- :deep(.mcp-debug-number .el-input__inner) {
2301
- font-family: 'Cascadia Code', 'Consolas', monospace;
2302
- font-size: 12px;
2303
- color: var(--text-primary);
2304
- height: 30px;
2305
- text-align: left;
2306
- }
2307
-
2308
- /* el-switch 对齐 */
2309
- .mcp-debug-switch { margin-top: 2px; }
2310
-
2311
- /* 无工具选中占位 */
2312
- .mcp-debug-no-tool {
2313
- display: flex;
2314
- align-items: center;
2315
- gap: 8px;
2316
- padding: 14px 12px;
2317
- border: 1px dashed var(--border-default);
2318
- border-radius: 8px;
2319
- color: var(--text-muted);
2320
- font-size: 12px;
2321
- font-family: Inter, 'PingFang SC', sans-serif;
2322
- }</style>