@yancyyu/openhermit 1.6.37 → 1.6.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist-renderer/assets/{ProjectEditorOverlay-Va_Vz-zz.js → ProjectEditorOverlay-lJZi-9Hp.js} +1 -1
  2. package/dist-renderer/assets/{TeamGraphOverlay-DYT3bwFR.js → TeamGraphOverlay-ZEDfZyHb.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-Dbt_EU-e.js → _basePickBy-CIhniz70.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DWo68sXI.js → _baseUniq-cKAW4Q8I.js} +1 -1
  5. package/dist-renderer/assets/{arc-DXH1iZQK.js → arc-YmNsoDXW.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-cjffS2Qr.js → architectureDiagram-VXUJARFQ-DHEls2sX.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BKdZF02Y.js → blockDiagram-VD42YOAC-Bpwf1Sbg.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CN27pqaI.js → c4Diagram-YG6GDRKO-B0IaQ4w5.js} +1 -1
  9. package/dist-renderer/assets/channel-yIlSKy0e.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CXPCI7g_.js → chunk-4BX2VUAB-DLk-hcFc.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-BGAXQZRC.js → chunk-55IACEB6-1XRmX_Zm.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-TPDaA_KQ.js → chunk-B4BG7PRW-1waH1DAD.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-D1ADe_tq.js → chunk-DI55MBZ5-BqpZBtrN.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-Beimtg3a.js → chunk-FMBD7UC4-Bly7vVym.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-OjNBu854.js → chunk-QN33PNHL-Ci2QWBAs.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DinqvbH8.js → chunk-QZHKN3VN-YCqFW7d-.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BfFtlPSZ.js → chunk-TZMSLE5B-B0xGXInl.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-24fHez0s.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-24fHez0s.js +1 -0
  20. package/dist-renderer/assets/clone-BTNuUva-.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-D9z9Dgt7.js → cose-bilkent-S5V4N54A-DxcFNQKT.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-n1g-DhEE.js → dagre-6UL2VRFP-DPo_RfZY.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-BvxFq-BE.js → diagram-PSM6KHXK-U3hQsFe4.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-wVnJuwza.js → diagram-QEK2KX5R-OrwrAy0V.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-B707WJQw.js → diagram-S2PKOQOG-CXATPWVw.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-C-_1dGHs.js → erDiagram-Q2GNP2WA-B0e8AfMF.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-CMTSi3H6.js → flowDiagram-NV44I4VS-CXfzA4jJ.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DZ0bNrAA.js → ganttDiagram-JELNMOA3-CMr08qVl.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DNVfGooQ.js → gitGraphDiagram-V2S2FVAM-vYFHpPmy.js} +1 -1
  30. package/dist-renderer/assets/{graph-865j_tM_.js → graph-DOe5j8dH.js} +1 -1
  31. package/dist-renderer/assets/{index-2EW-eu3q.js → index-B2Dy7M2G.js} +1 -1
  32. package/dist-renderer/assets/index-Bi6nrZ4z.css +1 -0
  33. package/dist-renderer/assets/{index-C_F9N5x-.js → index-BySQS7AB.js} +1 -1
  34. package/dist-renderer/assets/{index-4dEMStJj.js → index-C_okzZXP.js} +1 -1
  35. package/dist-renderer/assets/{index-DuUaf8at.js → index-CzWxVCRL.js} +1 -1
  36. package/dist-renderer/assets/{index-LwDIsXJN.js → index-V7dAKPqd.js} +571 -607
  37. package/dist-renderer/assets/{index-BTx1nc4T.js → index-VJ-MM9xa.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-CyqtElLq.js → infoDiagram-HS3SLOUP-D_WubR0B.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BvjQ0Hm0.js → journeyDiagram-XKPGCS4Q-w9ca-1TI.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CJJ-k0zT.js → kanban-definition-3W4ZIXB7-Jg9p6_pN.js} +1 -1
  41. package/dist-renderer/assets/{layout-CnV6rQAG.js → layout-B-z3y17c.js} +1 -1
  42. package/dist-renderer/assets/{linear-Cw3UQgyX.js → linear-D-RTX5UW.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-C5tDaGSK.js → mindmap-definition-VGOIOE7T-CDQmHOYP.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-CiIpPsau.js → pieDiagram-ADFJNKIX-D_odsQL7.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-C3gtowNj.js → quadrantDiagram-AYHSOK5B-BRsmYWSA.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CXBTrAnU.js → requirementDiagram-UZGBJVZJ-ChNE_BOV.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-wziX77xG.js → sankeyDiagram-TZEHDZUN-C8FtpwKc.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-sYqopcrj.js → sequenceDiagram-WL72ISMW-DmLCzNcc.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bl1-0_Cp.js → stateDiagram-FKZM4ZOC-WJBm4bhu.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-DOYYvDbi.js → stateDiagram-v2-4FDKWEC3-_m6iPPUR.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CIRjJUBo.js → timeline-definition-IT6M3QCI-BXs_hOJs.js} +1 -1
  52. package/dist-renderer/assets/{treemap-GDKQZRPO-CVPuNe1n.js → treemap-GDKQZRPO-o04MA0G9.js} +1 -1
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-3nT9yHwp.js → xychartDiagram-PRI3JC2R-Czj69XRd.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +1 -1
  56. package/src/main/ipc/extensions.ts +29 -50
  57. package/src/main/server.ts +17 -26
  58. package/src/main/services/extensions/ExtensionFacadeService.ts +2 -51
  59. package/src/main/services/extensions/library/McpLibraryService.ts +243 -0
  60. package/src/main/services/session-intelligence/UsageTelemetryService.ts +14 -1
  61. package/src/main/services/teams-mvp/TaskDispatchService.ts +32 -7
  62. package/src/renderer/api/httpClient.ts +108 -22
  63. package/src/renderer/components/extensions/ExtensionStoreView.tsx +6 -96
  64. package/src/renderer/components/extensions/plugins/PluginCard.tsx +8 -0
  65. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +13 -8
  66. package/src/renderer/components/team/TeamDetailView.tsx +15 -0
  67. package/src/renderer/components/team/tools/AddMcpInline.tsx +47 -0
  68. package/src/renderer/components/team/tools/AddSkillInline.tsx +61 -0
  69. package/src/renderer/components/team/tools/McpChip.tsx +42 -0
  70. package/src/renderer/components/team/tools/SkillChip.tsx +35 -0
  71. package/src/renderer/components/team/tools/ToolsSection.tsx +208 -0
  72. package/src/shared/types/extensions/api.ts +9 -0
  73. package/src/shared/types/extensions/index.ts +4 -0
  74. package/src/shared/types/extensions/mcp.ts +41 -0
  75. package/src/shared/utils/extensionNormalizers.ts +22 -0
  76. package/dist-renderer/assets/channel-5dJIx68e.js +0 -1
  77. package/dist-renderer/assets/classDiagram-2ON5EDUG-BMGXWJ2d.js +0 -1
  78. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BMGXWJ2d.js +0 -1
  79. package/dist-renderer/assets/clone-D7FWfGY9.js +0 -1
  80. package/dist-renderer/assets/index-B2z_IyRH.css +0 -1
  81. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +0 -190
  82. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +0 -150
  83. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +0 -381
  84. package/src/main/services/extensions/install/McpInstallService.ts +0 -407
  85. package/src/main/services/extensions/state/McpInstallationStateService.ts +0 -42
  86. package/src/renderer/components/extensions/mcp/McpServerCard.tsx +0 -314
  87. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +0 -765
  88. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +0 -593
  89. package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +0 -372
  90. package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +0 -343
  91. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +0 -659
@@ -1,659 +0,0 @@
1
- import { useEffect, useMemo, useRef, useState } from 'react';
2
-
3
- import { api } from '@renderer/api';
4
- import { Badge } from '@renderer/components/ui/badge';
5
- import { Button } from '@renderer/components/ui/button';
6
- import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover';
7
- import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
8
- import { useStore } from '@renderer/store';
9
- import {
10
- formatSkillRootKind,
11
- getSkillAudience,
12
- getSkillAudienceLabel,
13
- } from '@shared/utils/skillRoots';
14
- import {
15
- AlertTriangle,
16
- ArrowUpAZ,
17
- ArrowUpDown,
18
- BookOpen,
19
- Check,
20
- CheckCircle2,
21
- Clock3,
22
- Download,
23
- Info,
24
- Plus,
25
- Search,
26
- } from 'lucide-react';
27
- import { useShallow } from 'zustand/react/shallow';
28
-
29
- import { SearchInput } from '../common/SearchInput';
30
-
31
- import { SkillDetailDialog } from './SkillDetailDialog';
32
- import { SkillEditorDialog } from './SkillEditorDialog';
33
- import { SkillImportDialog } from './SkillImportDialog';
34
- import { resolveSkillProjectPath } from './skillProjectUtils';
35
-
36
- import type { SkillsSortState } from '@renderer/hooks/useExtensionsTabState';
37
- import type { SkillCatalogItem, SkillDetail, SkillValidationIssue } from '@shared/types/extensions';
38
-
39
- const SUCCESS_BANNER_MS = 2500;
40
- const NEW_SKILL_HIGHLIGHT_MS = 4000;
41
- const USER_SKILLS_CATALOG_KEY = '__user__';
42
- type SkillsQuickFilter = 'all' | 'project' | 'personal';
43
-
44
- interface SkillsPanelProps {
45
- projectPath: string | null;
46
- projectLabel: string | null;
47
- skillsSearchQuery: string;
48
- setSkillsSearchQuery: (value: string) => void;
49
- skillsSort: SkillsSortState;
50
- setSkillsSort: (value: SkillsSortState) => void;
51
- selectedSkillId: string | null;
52
- setSelectedSkillId: (id: string | null) => void;
53
- }
54
-
55
- function sortSkills(skills: SkillCatalogItem[], sort: SkillsSortState): SkillCatalogItem[] {
56
- const next = [...skills];
57
- next.sort((a, b) => {
58
- if (sort === 'recent-desc') {
59
- return b.modifiedAt - a.modifiedAt || a.name.localeCompare(b.name);
60
- }
61
- return a.name.localeCompare(b.name) || b.modifiedAt - a.modifiedAt;
62
- });
63
- return next;
64
- }
65
-
66
- function getScopeLabel(skill: SkillCatalogItem): string {
67
- return skill.scope === 'project' ? '当前项目' : '个人';
68
- }
69
-
70
- function getInvocationLabel(skill: SkillCatalogItem): string {
71
- return skill.invocationMode === 'manual-only' ? '仅在你明确要求时运行' : '匹配任务时自动运行';
72
- }
73
-
74
- function getSkillStatus(skill: SkillCatalogItem): string {
75
- if (!skill.isValid) {
76
- return '使用前需要处理';
77
- }
78
- if (skill.flags.hasScripts) {
79
- return '包含脚本,请仔细检查';
80
- }
81
- return '可使用';
82
- }
83
-
84
- function getPrimarySkillIssue(skill: SkillCatalogItem): SkillValidationIssue | null {
85
- return (
86
- skill.issues.find((issue) => issue.severity === 'error') ??
87
- skill.issues.find((issue) => issue.severity === 'warning') ??
88
- skill.issues[0] ??
89
- null
90
- );
91
- }
92
-
93
- function getSkillIssueTone(issue: SkillValidationIssue | null): {
94
- className: string;
95
- Icon: typeof AlertTriangle;
96
- } {
97
- if (issue?.severity === 'info') {
98
- return {
99
- className: 'border-blue-500/20 bg-blue-500/10 text-blue-700 dark:text-blue-300',
100
- Icon: Info,
101
- };
102
- }
103
-
104
- return {
105
- className: 'border-amber-500/20 bg-amber-500/5 text-amber-700 dark:text-amber-300',
106
- Icon: AlertTriangle,
107
- };
108
- }
109
-
110
- export const SkillsPanel = ({
111
- projectPath,
112
- projectLabel,
113
- skillsSearchQuery,
114
- setSkillsSearchQuery,
115
- skillsSort,
116
- setSkillsSort,
117
- selectedSkillId,
118
- setSelectedSkillId,
119
- }: SkillsPanelProps): React.JSX.Element => {
120
- const catalogKey = projectPath ?? USER_SKILLS_CATALOG_KEY;
121
- const fetchSkillsCatalog = useStore((s) => s.fetchSkillsCatalog);
122
- const fetchSkillDetail = useStore((s) => s.fetchSkillDetail);
123
- const skillsLoading = useStore((s) => s.skillsCatalogLoadingByProjectPath[catalogKey] ?? false);
124
- const skillsError = useStore((s) => s.skillsCatalogErrorByProjectPath[catalogKey] ?? null);
125
- const detailById = useStore(useShallow((s) => s.skillsDetailsById));
126
- const userSkills = useStore(useShallow((s) => s.skillsUserCatalog));
127
- const projectSkills = useStore(
128
- useShallow((s) => (projectPath ? (s.skillsProjectCatalogByProjectPath[projectPath] ?? []) : []))
129
- );
130
- const [createOpen, setCreateOpen] = useState(false);
131
- const [editOpen, setEditOpen] = useState(false);
132
- const [editingDetail, setEditingDetail] = useState<SkillDetail | null>(null);
133
- const [importOpen, setImportOpen] = useState(false);
134
- const [sortMenuOpen, setSortMenuOpen] = useState(false);
135
- const [quickFilter, setQuickFilter] = useState<SkillsQuickFilter>('all');
136
- const [successMessage, setSuccessMessage] = useState<string | null>(null);
137
- const [highlightedSkillId, setHighlightedSkillId] = useState<string | null>(null);
138
- const selectedSkillIdRef = useRef<string | null>(selectedSkillId);
139
- const selectedSkillItemRef = useRef<SkillCatalogItem | null>(null);
140
- selectedSkillIdRef.current = selectedSkillId;
141
-
142
- const mergedSkills = useMemo(
143
- () => [...projectSkills, ...userSkills],
144
- [projectSkills, userSkills]
145
- );
146
- const selectedDetail = selectedSkillId ? (detailById[selectedSkillId] ?? null) : null;
147
- selectedSkillItemRef.current = selectedSkillId
148
- ? (selectedDetail?.item ?? mergedSkills.find((skill) => skill.id === selectedSkillId) ?? null)
149
- : null;
150
-
151
- useEffect(() => {
152
- if (!selectedSkillId) return;
153
- if (mergedSkills.some((skill) => skill.id === selectedSkillId)) return;
154
- setSelectedSkillId(null);
155
- }, [mergedSkills, selectedSkillId, setSelectedSkillId]);
156
-
157
- useEffect(() => {
158
- if (!successMessage) return;
159
- const timeoutId = window.setTimeout(() => setSuccessMessage(null), SUCCESS_BANNER_MS);
160
- return () => window.clearTimeout(timeoutId);
161
- }, [successMessage]);
162
-
163
- useEffect(() => {
164
- if (!highlightedSkillId) return;
165
- const timeoutId = window.setTimeout(() => setHighlightedSkillId(null), NEW_SKILL_HIGHLIGHT_MS);
166
- return () => window.clearTimeout(timeoutId);
167
- }, [highlightedSkillId]);
168
-
169
- useEffect(() => {
170
- const skillsApi = api.skills;
171
- if (!skillsApi) return;
172
-
173
- let watchId: string | null = null;
174
- let disposed = false;
175
- void skillsApi.startWatching(projectPath ?? undefined).then((id) => {
176
- if (disposed) {
177
- void skillsApi.stopWatching(id);
178
- return;
179
- }
180
- watchId = id;
181
- });
182
- const changeCleanup = skillsApi.onChanged((event) => {
183
- const shouldRefresh =
184
- event.scope === 'user' ||
185
- (event.scope === 'project' && event.projectPath === (projectPath ?? null));
186
- if (!shouldRefresh) return;
187
-
188
- void fetchSkillsCatalog(projectPath ?? undefined);
189
- const selectedSkillId = selectedSkillIdRef.current;
190
- const selectedSkillItem = selectedSkillItemRef.current;
191
- if (selectedSkillId) {
192
- void fetchSkillDetail(
193
- selectedSkillId,
194
- selectedSkillItem
195
- ? resolveSkillProjectPath(
196
- selectedSkillItem.scope,
197
- projectPath,
198
- selectedSkillItem.projectRoot
199
- )
200
- : (projectPath ?? undefined)
201
- ).catch(() => undefined);
202
- }
203
- });
204
-
205
- return () => {
206
- disposed = true;
207
- changeCleanup();
208
- if (watchId) {
209
- void skillsApi.stopWatching(watchId);
210
- }
211
- };
212
- }, [fetchSkillDetail, fetchSkillsCatalog, projectPath]);
213
-
214
- const visibleSkills = useMemo(() => {
215
- const q = skillsSearchQuery.trim().toLowerCase();
216
- const filteredByQuery = q
217
- ? mergedSkills.filter(
218
- (skill) =>
219
- skill.name.toLowerCase().includes(q) ||
220
- skill.description.toLowerCase().includes(q) ||
221
- skill.folderName.toLowerCase().includes(q)
222
- )
223
- : mergedSkills;
224
- const filtered =
225
- quickFilter === 'all'
226
- ? filteredByQuery
227
- : filteredByQuery.filter((skill) => {
228
- switch (quickFilter) {
229
- case 'project':
230
- return skill.scope === 'project';
231
- case 'personal':
232
- return skill.scope === 'user';
233
- default:
234
- return true;
235
- }
236
- });
237
- return sortSkills(filtered, skillsSort);
238
- }, [mergedSkills, quickFilter, skillsSearchQuery, skillsSort]);
239
- const visibleProjectSkills = useMemo(
240
- () => visibleSkills.filter((skill) => skill.scope === 'project'),
241
- [visibleSkills]
242
- );
243
- const visibleUserSkills = useMemo(
244
- () => visibleSkills.filter((skill) => skill.scope === 'user'),
245
- [visibleSkills]
246
- );
247
- const isRefreshing = skillsLoading && mergedSkills.length > 0;
248
-
249
- return (
250
- <div className="flex flex-col gap-4">
251
- <div className="bg-surface-raised/20 rounded-xl border border-border p-4">
252
- <div className="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
253
- <div className="min-w-0 flex-1 space-y-1 xl:max-w-2xl">
254
- <div className="flex items-center gap-2">
255
- <BookOpen className="size-4 text-text-muted" />
256
- <h2 className="text-sm font-semibold text-text">教授可重复工作</h2>
257
- </div>
258
- <p className="max-w-2xl text-sm leading-5 text-text-muted">
259
- 技能是可复用指令,能帮助运行时更稳定地处理同一类任务。{' '}
260
- {projectPath
261
- ? `当前显示 ${projectLabel ?? projectPath} 的项目技能,以及你的个人技能。`
262
- : '当前只显示你的个人技能。'}
263
- </p>
264
- <p className="max-w-2xl text-xs leading-5 text-text-muted">
265
- 需要到处生效的习惯请使用个人技能;只对当前代码库有意义的流程请使用项目技能。
266
- </p>
267
- </div>
268
-
269
- <div className="flex w-full flex-col gap-3 xl:w-auto xl:min-w-[32rem] xl:max-w-[40rem]">
270
- <div className="flex flex-col gap-3 lg:flex-row lg:flex-wrap lg:items-center xl:justify-end">
271
- <div className="w-full lg:min-w-72 lg:flex-1 xl:w-80 xl:flex-none">
272
- <SearchInput
273
- value={skillsSearchQuery}
274
- onChange={setSkillsSearchQuery}
275
- placeholder="按技能名称或用途搜索..."
276
- />
277
- </div>
278
- <div className="flex flex-wrap gap-2">
279
- <Button variant="outline" size="sm" onClick={() => setCreateOpen(true)}>
280
- <Plus className="mr-1.5 size-3.5" />
281
- 创建技能
282
- </Button>
283
- <Button variant="outline" size="sm" onClick={() => setImportOpen(true)}>
284
- <Download className="mr-1.5 size-3.5" />
285
- 导入
286
- </Button>
287
- <Popover open={sortMenuOpen} onOpenChange={setSortMenuOpen}>
288
- <Tooltip>
289
- <TooltipTrigger asChild>
290
- <PopoverTrigger asChild>
291
- <Button
292
- variant="outline"
293
- size="icon"
294
- className="size-9 shrink-0"
295
- aria-label="排序技能"
296
- >
297
- <ArrowUpDown className="size-4" />
298
- </Button>
299
- </PopoverTrigger>
300
- </TooltipTrigger>
301
- <TooltipContent>排序技能</TooltipContent>
302
- </Tooltip>
303
- <PopoverContent align="end" className="w-44 p-1">
304
- <button
305
- type="button"
306
- className="flex w-full items-center rounded-sm px-2 py-1.5 text-sm text-text hover:bg-surface-raised"
307
- onClick={() => {
308
- setSkillsSort('name-asc');
309
- setSortMenuOpen(false);
310
- }}
311
- >
312
- <ArrowUpAZ className="mr-2 size-3.5" />
313
- 名称
314
- {skillsSort === 'name-asc' && <Check className="ml-auto size-3.5" />}
315
- </button>
316
- <button
317
- type="button"
318
- className="flex w-full items-center rounded-sm px-2 py-1.5 text-sm text-text hover:bg-surface-raised"
319
- onClick={() => {
320
- setSkillsSort('recent-desc');
321
- setSortMenuOpen(false);
322
- }}
323
- >
324
- <Clock3 className="mr-2 size-3.5" />
325
- 最近
326
- {skillsSort === 'recent-desc' && <Check className="ml-auto size-3.5" />}
327
- </button>
328
- </PopoverContent>
329
- </Popover>
330
- </div>
331
- </div>
332
-
333
- <div className="flex flex-wrap gap-2 text-[11px] text-text-muted xl:justify-end">
334
- <Badge variant="secondary" className="font-normal">
335
- 共 {mergedSkills.length} 个
336
- </Badge>
337
- <Badge variant="secondary" className="font-normal">
338
- {projectSkills.length} 个项目技能
339
- </Badge>
340
- <Badge variant="secondary" className="font-normal">
341
- {userSkills.length} 个个人技能
342
- </Badge>
343
- </div>
344
- </div>
345
- </div>
346
- </div>
347
-
348
- <div className="flex flex-wrap gap-2">
349
- {(
350
- [
351
- ['all', '全部技能'],
352
- ['project', '项目'],
353
- ['personal', '个人'],
354
- ] as [SkillsQuickFilter, string][]
355
- ).map(([value, label]) => (
356
- <Button
357
- key={value}
358
- variant={quickFilter === value ? 'secondary' : 'outline'}
359
- size="sm"
360
- onClick={() => setQuickFilter(value)}
361
- className="rounded-full"
362
- >
363
- {label}
364
- </Button>
365
- ))}
366
- </div>
367
-
368
- {skillsError && (
369
- <div className="rounded-md border border-red-500/30 bg-red-500/5 p-4 text-sm text-red-400">
370
- {skillsError}
371
- </div>
372
- )}
373
-
374
- {successMessage && (
375
- <div className="flex items-center gap-2 rounded-md border border-green-500/20 bg-green-500/10 p-4 text-sm text-green-700 dark:text-green-400">
376
- <CheckCircle2 className="size-4 shrink-0" />
377
- <span>{successMessage}</span>
378
- </div>
379
- )}
380
-
381
- {isRefreshing && (
382
- <div className="rounded-md border border-blue-500/20 bg-blue-500/10 p-3 text-sm text-blue-700 dark:text-blue-300">
383
- 正在刷新技能...
384
- </div>
385
- )}
386
-
387
- {skillsLoading && visibleSkills.length === 0 && (
388
- <div className="rounded-lg border border-border p-6 text-sm text-text-muted">
389
- 正在加载技能...
390
- </div>
391
- )}
392
-
393
- {!skillsLoading && !skillsError && visibleSkills.length === 0 && (
394
- <div className="flex flex-col items-center gap-3 rounded-sm border border-dashed border-border px-8 py-16">
395
- <div className="flex size-10 items-center justify-center rounded-lg border border-border bg-surface-raised">
396
- <Search className="size-5 text-text-muted" />
397
- </div>
398
- <p className="text-sm text-text-secondary">
399
- {skillsSearchQuery ? '没有匹配搜索的技能' : '暂无技能'}
400
- </p>
401
- <p className="text-xs text-text-muted">
402
- {skillsSearchQuery
403
- ? '请尝试其他搜索词或切换筛选条件。'
404
- : '创建第一个技能来教授可重复工作流,或导入你已有的技能。'}
405
- </p>
406
- </div>
407
- )}
408
-
409
- {visibleSkills.length > 0 && (
410
- <div className="space-y-6">
411
- {visibleProjectSkills.length > 0 && (
412
- <section className="space-y-3">
413
- <div className="flex items-center justify-between gap-3">
414
- <div>
415
- <h3 className="text-sm font-semibold text-text">项目技能</h3>
416
- <p className="text-xs text-text-muted">只对当前代码库有意义的工作流。</p>
417
- </div>
418
- <Badge variant="secondary" className="font-normal">
419
- {visibleProjectSkills.length}
420
- </Badge>
421
- </div>
422
- <div className="skills-grid grid grid-cols-1 gap-3 xl:grid-cols-2">
423
- {visibleProjectSkills.map((skill) => {
424
- const primaryIssue = getPrimarySkillIssue(skill);
425
- const issueTone = getSkillIssueTone(primaryIssue);
426
- const IssueIcon = issueTone.Icon;
427
- return (
428
- <button
429
- key={skill.id}
430
- type="button"
431
- onClick={() => setSelectedSkillId(skill.id)}
432
- className={`rounded-xl border p-4 text-left transition-colors ${
433
- highlightedSkillId === skill.id
434
- ? 'border-green-500/50 bg-green-500/10 shadow-[0_0_0_1px_rgba(34,197,94,0.18)]'
435
- : 'bg-surface-raised/10 border-border hover:border-border-emphasis'
436
- }`}
437
- >
438
- <div className="flex items-start justify-between gap-3">
439
- <div className="min-w-0 space-y-1">
440
- <div className="flex flex-wrap items-center gap-2">
441
- <h3 className="truncate text-sm font-semibold text-text">
442
- {skill.name}
443
- </h3>
444
- {!skill.isValid && (
445
- <Badge
446
- variant="outline"
447
- className="border-amber-500/40 text-amber-700 dark:text-amber-300"
448
- >
449
- 需要处理
450
- </Badge>
451
- )}
452
- </div>
453
- <p className="line-clamp-2 text-sm text-text-secondary">
454
- {skill.description}
455
- </p>
456
- </div>
457
- <Badge variant="outline">{getScopeLabel(skill)}</Badge>
458
- </div>
459
-
460
- <div className="mt-3 space-y-2 text-xs text-text-muted">
461
- <p>{getInvocationLabel(skill)}</p>
462
- <p>{getSkillStatus(skill)}</p>
463
- </div>
464
-
465
- <div className="mt-3 flex flex-wrap gap-2">
466
- <Badge variant="secondary" className="font-normal">
467
- 存储于 {formatSkillRootKind(skill.rootKind)}
468
- </Badge>
469
- <Badge variant="outline" className="font-normal">
470
- {getSkillAudienceLabel(skill.rootKind)}
471
- </Badge>
472
- {skill.flags.hasScripts && (
473
- <Badge variant="destructive" className="font-normal">
474
- 包含脚本
475
- </Badge>
476
- )}
477
- {skill.flags.hasReferences && (
478
- <Badge variant="secondary" className="font-normal">
479
- 引用
480
- </Badge>
481
- )}
482
- {skill.flags.hasAssets && (
483
- <Badge variant="secondary" className="font-normal">
484
- 资源
485
- </Badge>
486
- )}
487
- </div>
488
-
489
- {primaryIssue && (
490
- <div
491
- className={`mt-3 flex items-start gap-2 rounded-md border px-3 py-2 text-xs ${issueTone.className}`}
492
- >
493
- <IssueIcon className="mt-0.5 size-3.5 shrink-0" />
494
- <span>{primaryIssue.message}</span>
495
- </div>
496
- )}
497
- </button>
498
- );
499
- })}
500
- </div>
501
- </section>
502
- )}
503
-
504
- {visibleUserSkills.length > 0 && (
505
- <section className="space-y-3">
506
- <div className="flex items-center justify-between gap-3">
507
- <div>
508
- <h3 className="text-sm font-semibold text-text">个人技能</h3>
509
- <p className="text-xs text-text-muted">需要在所有地方可用的习惯和指令。</p>
510
- </div>
511
- <Badge variant="secondary" className="font-normal">
512
- {visibleUserSkills.length}
513
- </Badge>
514
- </div>
515
- <div className="skills-grid grid grid-cols-1 gap-3 xl:grid-cols-2">
516
- {visibleUserSkills.map((skill) => {
517
- const primaryIssue = getPrimarySkillIssue(skill);
518
- const issueTone = getSkillIssueTone(primaryIssue);
519
- const IssueIcon = issueTone.Icon;
520
- return (
521
- <button
522
- key={skill.id}
523
- type="button"
524
- onClick={() => setSelectedSkillId(skill.id)}
525
- className={`rounded-xl border p-4 text-left transition-colors ${
526
- highlightedSkillId === skill.id
527
- ? 'border-green-500/50 bg-green-500/10 shadow-[0_0_0_1px_rgba(34,197,94,0.18)]'
528
- : 'bg-surface-raised/10 border-border hover:border-border-emphasis'
529
- }`}
530
- >
531
- <div className="flex items-start justify-between gap-3">
532
- <div className="min-w-0 space-y-1">
533
- <div className="flex flex-wrap items-center gap-2">
534
- <h3 className="truncate text-sm font-semibold text-text">
535
- {skill.name}
536
- </h3>
537
- {!skill.isValid && (
538
- <Badge
539
- variant="outline"
540
- className="border-amber-500/40 text-amber-700 dark:text-amber-300"
541
- >
542
- 需要处理
543
- </Badge>
544
- )}
545
- </div>
546
- <p className="line-clamp-2 text-sm text-text-secondary">
547
- {skill.description}
548
- </p>
549
- </div>
550
- <Badge variant="outline">{getScopeLabel(skill)}</Badge>
551
- </div>
552
-
553
- <div className="mt-3 space-y-2 text-xs text-text-muted">
554
- <p>{getInvocationLabel(skill)}</p>
555
- <p>{getSkillStatus(skill)}</p>
556
- </div>
557
-
558
- <div className="mt-3 flex flex-wrap gap-2">
559
- <Badge variant="secondary" className="font-normal">
560
- 存储于 {formatSkillRootKind(skill.rootKind)}
561
- </Badge>
562
- <Badge variant="outline" className="font-normal">
563
- {getSkillAudienceLabel(skill.rootKind)}
564
- </Badge>
565
- {skill.flags.hasScripts && (
566
- <Badge variant="destructive" className="font-normal">
567
- 包含脚本
568
- </Badge>
569
- )}
570
- {skill.flags.hasReferences && (
571
- <Badge variant="secondary" className="font-normal">
572
- 引用
573
- </Badge>
574
- )}
575
- {skill.flags.hasAssets && (
576
- <Badge variant="secondary" className="font-normal">
577
- 资源
578
- </Badge>
579
- )}
580
- </div>
581
-
582
- {primaryIssue && (
583
- <div
584
- className={`mt-3 flex items-start gap-2 rounded-md border px-3 py-2 text-xs ${issueTone.className}`}
585
- >
586
- <IssueIcon className="mt-0.5 size-3.5 shrink-0" />
587
- <span>{primaryIssue.message}</span>
588
- </div>
589
- )}
590
- </button>
591
- );
592
- })}
593
- </div>
594
- </section>
595
- )}
596
- </div>
597
- )}
598
-
599
- <SkillDetailDialog
600
- skillId={selectedSkillId}
601
- open={selectedSkillId !== null}
602
- onClose={() => setSelectedSkillId(null)}
603
- projectPath={projectPath}
604
- onEdit={() => {
605
- if (!selectedDetail) return;
606
- setEditingDetail(selectedDetail);
607
- setSelectedSkillId(null);
608
- setEditOpen(true);
609
- }}
610
- onDeleted={() => setSelectedSkillId(null)}
611
- />
612
-
613
- <SkillEditorDialog
614
- open={createOpen}
615
- mode="create"
616
- projectPath={projectPath}
617
- projectLabel={projectLabel}
618
- detail={null}
619
- onClose={() => setCreateOpen(false)}
620
- onSaved={(skillId) => {
621
- setCreateOpen(false);
622
- setSuccessMessage('技能创建成功。');
623
- setHighlightedSkillId(skillId);
624
- setSelectedSkillId(null);
625
- }}
626
- />
627
-
628
- <SkillEditorDialog
629
- open={editOpen}
630
- mode="edit"
631
- projectPath={projectPath}
632
- projectLabel={projectLabel}
633
- detail={editingDetail}
634
- onClose={() => {
635
- setEditOpen(false);
636
- setEditingDetail(null);
637
- }}
638
- onSaved={(skillId) => {
639
- setEditOpen(false);
640
- setEditingDetail(null);
641
- setSuccessMessage('技能保存成功。');
642
- setSelectedSkillId(skillId);
643
- }}
644
- />
645
-
646
- <SkillImportDialog
647
- open={importOpen}
648
- projectPath={projectPath}
649
- projectLabel={projectLabel}
650
- onClose={() => setImportOpen(false)}
651
- onImported={(skillId) => {
652
- setImportOpen(false);
653
- setSuccessMessage('技能导入成功。');
654
- setSelectedSkillId(skillId);
655
- }}
656
- />
657
- </div>
658
- );
659
- };