@yancyyu/openhermit 1.6.36 → 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.
- package/dist-renderer/assets/{ProjectEditorOverlay-B_QAoeaA.js → ProjectEditorOverlay-lJZi-9Hp.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-PB9luAZU.js → TeamGraphOverlay-ZEDfZyHb.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-Dfzcp_Ry.js → _basePickBy-CIhniz70.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-B5u2Yiq2.js → _baseUniq-cKAW4Q8I.js} +1 -1
- package/dist-renderer/assets/{arc-DElOI7qz.js → arc-YmNsoDXW.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-Cf6f4tCu.js → architectureDiagram-VXUJARFQ-DHEls2sX.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-FJUdo9Ry.js → blockDiagram-VD42YOAC-Bpwf1Sbg.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-BvJQS9lb.js → c4Diagram-YG6GDRKO-B0IaQ4w5.js} +1 -1
- package/dist-renderer/assets/channel-yIlSKy0e.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-n-SGLbin.js → chunk-4BX2VUAB-DLk-hcFc.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-Dwle9tlA.js → chunk-55IACEB6-1XRmX_Zm.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-Dic8YxQz.js → chunk-B4BG7PRW-1waH1DAD.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-3n5jC1jk.js → chunk-DI55MBZ5-BqpZBtrN.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-BqizUB3O.js → chunk-FMBD7UC4-Bly7vVym.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-JRDmD8o9.js → chunk-QN33PNHL-Ci2QWBAs.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-BxFpQw92.js → chunk-QZHKN3VN-YCqFW7d-.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-ByqPwtW9.js → chunk-TZMSLE5B-B0xGXInl.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-24fHez0s.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-24fHez0s.js +1 -0
- package/dist-renderer/assets/clone-BTNuUva-.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CVztr86T.js → cose-bilkent-S5V4N54A-DxcFNQKT.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-CIui920O.js → dagre-6UL2VRFP-DPo_RfZY.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-CyL8-bgb.js → diagram-PSM6KHXK-U3hQsFe4.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-CM_67YoY.js → diagram-QEK2KX5R-OrwrAy0V.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DtrtPSGg.js → diagram-S2PKOQOG-CXATPWVw.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-bOICzF9d.js → erDiagram-Q2GNP2WA-B0e8AfMF.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-CJV1g9Hr.js → flowDiagram-NV44I4VS-CXfzA4jJ.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-CXbhDo09.js → ganttDiagram-JELNMOA3-CMr08qVl.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-vbXopTpS.js → gitGraphDiagram-V2S2FVAM-vYFHpPmy.js} +1 -1
- package/dist-renderer/assets/{graph-CY2T-j4q.js → graph-DOe5j8dH.js} +1 -1
- package/dist-renderer/assets/{index-Dv7q-OB0.js → index-B2Dy7M2G.js} +1 -1
- package/dist-renderer/assets/index-Bi6nrZ4z.css +1 -0
- package/dist-renderer/assets/{index-CpyChjme.js → index-BySQS7AB.js} +1 -1
- package/dist-renderer/assets/{index-S-i9egm8.js → index-C_okzZXP.js} +1 -1
- package/dist-renderer/assets/{index-Mrh4pTHw.js → index-CzWxVCRL.js} +1 -1
- package/dist-renderer/assets/{index-Dn-BpzSm.js → index-V7dAKPqd.js} +571 -607
- package/dist-renderer/assets/{index-C9ONRXVI.js → index-VJ-MM9xa.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-BNg14AdU.js → infoDiagram-HS3SLOUP-D_WubR0B.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-2_PkPCiu.js → journeyDiagram-XKPGCS4Q-w9ca-1TI.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DjTx7qoU.js → kanban-definition-3W4ZIXB7-Jg9p6_pN.js} +1 -1
- package/dist-renderer/assets/{layout-DZlHGGN0.js → layout-B-z3y17c.js} +1 -1
- package/dist-renderer/assets/{linear-DnlOm48z.js → linear-D-RTX5UW.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-B-nrgt7V.js → mindmap-definition-VGOIOE7T-CDQmHOYP.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BToJFvWR.js → pieDiagram-ADFJNKIX-D_odsQL7.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-C0qoxXvH.js → quadrantDiagram-AYHSOK5B-BRsmYWSA.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-DCKGwsGN.js → requirementDiagram-UZGBJVZJ-ChNE_BOV.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CS6JCcu7.js → sankeyDiagram-TZEHDZUN-C8FtpwKc.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-C9pAWoSR.js → sequenceDiagram-WL72ISMW-DmLCzNcc.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BTTX_v1m.js → stateDiagram-FKZM4ZOC-WJBm4bhu.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-BHk97lQJ.js → stateDiagram-v2-4FDKWEC3-_m6iPPUR.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CSWCEzCQ.js → timeline-definition-IT6M3QCI-BXs_hOJs.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-CmiIc68g.js → treemap-GDKQZRPO-o04MA0G9.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-DhwSTphI.js → xychartDiagram-PRI3JC2R-Czj69XRd.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +2 -2
- package/src/main/ipc/extensions.ts +29 -50
- package/src/main/server.ts +17 -26
- package/src/main/services/extensions/ExtensionFacadeService.ts +2 -51
- package/src/main/services/extensions/library/McpLibraryService.ts +243 -0
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +14 -1
- package/src/main/services/teams-mvp/TaskDispatchService.ts +32 -7
- package/src/renderer/api/httpClient.ts +108 -22
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +6 -96
- package/src/renderer/components/extensions/plugins/PluginCard.tsx +8 -0
- package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +13 -8
- package/src/renderer/components/team/TeamDetailView.tsx +15 -0
- package/src/renderer/components/team/tools/AddMcpInline.tsx +47 -0
- package/src/renderer/components/team/tools/AddSkillInline.tsx +61 -0
- package/src/renderer/components/team/tools/McpChip.tsx +42 -0
- package/src/renderer/components/team/tools/SkillChip.tsx +35 -0
- package/src/renderer/components/team/tools/ToolsSection.tsx +208 -0
- package/src/shared/types/extensions/api.ts +9 -0
- package/src/shared/types/extensions/index.ts +4 -0
- package/src/shared/types/extensions/mcp.ts +41 -0
- package/src/shared/utils/extensionNormalizers.ts +22 -0
- package/dist-renderer/assets/channel-DnbgZg0A.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-BAD4p014.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BAD4p014.js +0 -1
- package/dist-renderer/assets/clone-CRX5ZTPd.js +0 -1
- package/dist-renderer/assets/index-B2z_IyRH.css +0 -1
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +0 -190
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +0 -150
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +0 -381
- package/src/main/services/extensions/install/McpInstallService.ts +0 -407
- package/src/main/services/extensions/state/McpInstallationStateService.ts +0 -42
- package/src/renderer/components/extensions/mcp/McpServerCard.tsx +0 -314
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +0 -765
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +0 -593
- package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +0 -372
- package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +0 -343
- 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
|
-
};
|