@nospt/plugin-dev-ai-hub 0.1.0
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/.eslintrc.js +1 -0
- package/dev/index.tsx +232 -0
- package/dist/api/DevAiHubClient.esm.js +76 -0
- package/dist/api/DevAiHubClient.esm.js.map +1 -0
- package/dist/components/AssetCard/AssetCard.esm.js +194 -0
- package/dist/components/AssetCard/AssetCard.esm.js.map +1 -0
- package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +324 -0
- package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +1 -0
- package/dist/components/AssetFilters/AssetFilters.esm.js +216 -0
- package/dist/components/AssetFilters/AssetFilters.esm.js.map +1 -0
- package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +194 -0
- package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +1 -0
- package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +320 -0
- package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +1 -0
- package/dist/components/DevAiHubPage/index.esm.js +6 -0
- package/dist/components/DevAiHubPage/index.esm.js.map +1 -0
- package/dist/components/McpConfigDialog/McpConfigDialog.esm.js +274 -0
- package/dist/components/McpConfigDialog/McpConfigDialog.esm.js.map +1 -0
- package/dist/components/ToolIcon/ToolIcon.esm.js +41 -0
- package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -0
- package/dist/hooks/index.esm.js +114 -0
- package/dist/hooks/index.esm.js.map +1 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.esm.js +5 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/plugin.esm.js +40 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/pluginLegacy.esm.js +27 -0
- package/dist/pluginLegacy.esm.js.map +1 -0
- package/dist/routes.esm.js +6 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +80 -0
- package/package.json-prepack +75 -0
- package/src/api/DevAiHubClient.ts +110 -0
- package/src/components/AssetCard/AssetCard.test.tsx +182 -0
- package/src/components/AssetCard/AssetCard.tsx +221 -0
- package/src/components/AssetCard/index.ts +1 -0
- package/src/components/AssetDetailPanel/AssetDetailPanel.tsx +377 -0
- package/src/components/AssetDetailPanel/index.ts +1 -0
- package/src/components/AssetFilters/AssetFilters.tsx +261 -0
- package/src/components/AssetFilters/index.ts +2 -0
- package/src/components/AssetInstallDialog/AssetInstallDialog.tsx +216 -0
- package/src/components/AssetInstallDialog/index.ts +1 -0
- package/src/components/DevAiHubPage/DevAiHubPage.tsx +346 -0
- package/src/components/DevAiHubPage/index.ts +1 -0
- package/src/components/McpConfigDialog/McpConfigDialog.tsx +301 -0
- package/src/components/McpConfigDialog/index.ts +1 -0
- package/src/components/ToolIcon/ToolIcon.tsx +49 -0
- package/src/components/ToolIcon/index.ts +1 -0
- package/src/hooks/index.ts +152 -0
- package/src/index.ts +10 -0
- package/src/plugin.tsx +47 -0
- package/src/pluginLegacy.ts +42 -0
- package/src/routes.ts +3 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { ElementType } from 'react';
|
|
2
|
+
import Box from '@mui/material/Box';
|
|
3
|
+
import Chip from '@mui/material/Chip';
|
|
4
|
+
import InputAdornment from '@mui/material/InputAdornment';
|
|
5
|
+
import TextField from '@mui/material/TextField';
|
|
6
|
+
import Typography from '@mui/material/Typography';
|
|
7
|
+
import SearchIcon from '@mui/icons-material/Search';
|
|
8
|
+
import StorageIcon from '@mui/icons-material/Storage';
|
|
9
|
+
import ArticleIcon from '@mui/icons-material/Article';
|
|
10
|
+
import SmartToyIcon from '@mui/icons-material/SmartToy';
|
|
11
|
+
import BuildIcon from '@mui/icons-material/Build';
|
|
12
|
+
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
|
13
|
+
import AppsIcon from '@mui/icons-material/Apps';
|
|
14
|
+
import type { AssetType, AiTool, AiHubProvider } from '@nospt/plugin-dev-ai-hub-common';
|
|
15
|
+
import { ToolIcon } from '../ToolIcon';
|
|
16
|
+
|
|
17
|
+
const ASSET_TYPES: { value: AssetType | 'all'; label: string; color: string; Icon: ElementType }[] = [
|
|
18
|
+
{ value: 'all', label: 'All', color: '#64748b', Icon: AppsIcon },
|
|
19
|
+
{ value: 'instruction', label: 'Instructions', color: '#2563EB', Icon: ArticleIcon },
|
|
20
|
+
{ value: 'agent', label: 'Agents', color: '#7C3AED', Icon: SmartToyIcon },
|
|
21
|
+
{ value: 'skill', label: 'Skills', color: '#059669', Icon: BuildIcon },
|
|
22
|
+
{ value: 'workflow', label: 'Workflows', color: '#D97706', Icon: AccountTreeIcon },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const AI_TOOLS: { value: AiTool | 'all'; label: string }[] = [
|
|
26
|
+
{ value: 'all', label: 'All Tools' },
|
|
27
|
+
{ value: 'claude-code', label: 'Claude Code' },
|
|
28
|
+
{ value: 'github-copilot', label: 'GitHub Copilot' },
|
|
29
|
+
{ value: 'google-gemini', label: 'Google Gemini' },
|
|
30
|
+
{ value: 'cursor', label: 'Cursor' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export interface AssetFiltersValue {
|
|
34
|
+
types: AssetType[];
|
|
35
|
+
tools: AiTool[];
|
|
36
|
+
search: string;
|
|
37
|
+
tags: string[];
|
|
38
|
+
providerId?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface AssetFiltersProps {
|
|
42
|
+
value: AssetFiltersValue;
|
|
43
|
+
onChange: (value: AssetFiltersValue) => void;
|
|
44
|
+
availableTags?: string[];
|
|
45
|
+
providers?: AiHubProvider[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function AssetFilters({ value, onChange, availableTags = [], providers }: AssetFiltersProps) {
|
|
49
|
+
const selectedType = value.types.length === 1 ? value.types[0] : 'all';
|
|
50
|
+
const selectedTool = value.tools.length === 1 ? value.tools[0] : 'all';
|
|
51
|
+
const showProviderFilter = providers && providers.length > 1;
|
|
52
|
+
|
|
53
|
+
const handleTypeClick = (type: AssetType | 'all') => {
|
|
54
|
+
onChange({ ...value, types: type === 'all' ? [] : [type] });
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleToolClick = (tool: AiTool | 'all') => {
|
|
58
|
+
onChange({ ...value, tools: tool === 'all' ? [] : [tool] });
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handleTagToggle = (tag: string) => {
|
|
62
|
+
const next = value.tags.includes(tag)
|
|
63
|
+
? value.tags.filter(t => t !== tag)
|
|
64
|
+
: [...value.tags, tag];
|
|
65
|
+
onChange({ ...value, tags: next });
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handleProviderClick = (id: string | undefined) => {
|
|
69
|
+
onChange({ ...value, providerId: id });
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mb: 3 }}>
|
|
74
|
+
<TextField
|
|
75
|
+
fullWidth
|
|
76
|
+
size="small"
|
|
77
|
+
placeholder="Search assets by name, description or content…"
|
|
78
|
+
value={value.search}
|
|
79
|
+
onChange={e => onChange({ ...value, search: e.target.value })}
|
|
80
|
+
InputProps={{
|
|
81
|
+
startAdornment: (
|
|
82
|
+
<InputAdornment position="start">
|
|
83
|
+
<SearchIcon fontSize="small" />
|
|
84
|
+
</InputAdornment>
|
|
85
|
+
),
|
|
86
|
+
}}
|
|
87
|
+
sx={{
|
|
88
|
+
'& .MuiOutlinedInput-root': {
|
|
89
|
+
borderRadius: 2,
|
|
90
|
+
},
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
<Box sx={{ display: 'flex', gap: 3, flexWrap: 'wrap', alignItems: 'flex-start' }}>
|
|
95
|
+
{/* Type filter */}
|
|
96
|
+
<Box>
|
|
97
|
+
<Typography variant="caption" fontWeight={600} color="text.secondary" sx={{ mb: 1, display: 'block', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
|
98
|
+
Type
|
|
99
|
+
</Typography>
|
|
100
|
+
<Box sx={{ display: 'flex', gap: 0.75, flexWrap: 'wrap' }}>
|
|
101
|
+
{ASSET_TYPES.map(t => {
|
|
102
|
+
const isSelected = selectedType === t.value;
|
|
103
|
+
const TypeIcon = t.Icon;
|
|
104
|
+
return (
|
|
105
|
+
<Chip
|
|
106
|
+
key={t.value}
|
|
107
|
+
icon={<TypeIcon sx={{ fontSize: '0.9rem !important', color: isSelected ? '#fff !important' : t.color }} />}
|
|
108
|
+
label={t.label}
|
|
109
|
+
size="small"
|
|
110
|
+
clickable
|
|
111
|
+
onClick={() => handleTypeClick(t.value as AssetType | 'all')}
|
|
112
|
+
sx={{
|
|
113
|
+
fontWeight: 600,
|
|
114
|
+
fontSize: '0.75rem',
|
|
115
|
+
borderRadius: 2,
|
|
116
|
+
border: '1.5px solid',
|
|
117
|
+
borderColor: isSelected ? t.color : `${t.color}60`,
|
|
118
|
+
backgroundColor: isSelected ? t.color : 'transparent',
|
|
119
|
+
color: isSelected ? '#fff' : t.color,
|
|
120
|
+
transition: 'all 0.15s ease',
|
|
121
|
+
'&:hover': {
|
|
122
|
+
backgroundColor: isSelected ? t.color : `${t.color}18`,
|
|
123
|
+
borderColor: t.color,
|
|
124
|
+
},
|
|
125
|
+
}}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
})}
|
|
129
|
+
</Box>
|
|
130
|
+
</Box>
|
|
131
|
+
|
|
132
|
+
{/* AI Tool filter */}
|
|
133
|
+
<Box>
|
|
134
|
+
<Typography variant="caption" fontWeight={600} color="text.secondary" sx={{ mb: 1, display: 'block', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
|
135
|
+
AI Tool
|
|
136
|
+
</Typography>
|
|
137
|
+
<Box sx={{ display: 'flex', gap: 0.75, flexWrap: 'wrap' }}>
|
|
138
|
+
{AI_TOOLS.map(t => {
|
|
139
|
+
const isSelected = selectedTool === t.value;
|
|
140
|
+
const iconEl = t.value !== 'all'
|
|
141
|
+
? <ToolIcon tool={t.value as AiTool} branded={!isSelected} sx={{ fontSize: '0.85rem !important', color: isSelected ? 'background.paper' : 'inherit' }} />
|
|
142
|
+
: undefined;
|
|
143
|
+
return (
|
|
144
|
+
<Chip
|
|
145
|
+
key={t.value}
|
|
146
|
+
icon={iconEl}
|
|
147
|
+
label={t.label}
|
|
148
|
+
size="small"
|
|
149
|
+
clickable
|
|
150
|
+
onClick={() => handleToolClick(t.value as AiTool | 'all')}
|
|
151
|
+
sx={{
|
|
152
|
+
fontWeight: 600,
|
|
153
|
+
fontSize: '0.75rem',
|
|
154
|
+
borderRadius: 2,
|
|
155
|
+
border: '1.5px solid',
|
|
156
|
+
borderColor: isSelected ? 'text.primary' : 'divider',
|
|
157
|
+
backgroundColor: isSelected ? 'text.primary' : 'transparent',
|
|
158
|
+
color: isSelected ? 'background.paper' : 'text.secondary',
|
|
159
|
+
transition: 'all 0.15s ease',
|
|
160
|
+
'&:hover': {
|
|
161
|
+
borderColor: 'text.primary',
|
|
162
|
+
backgroundColor: isSelected ? 'text.primary' : 'action.hover',
|
|
163
|
+
},
|
|
164
|
+
}}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
})}
|
|
168
|
+
</Box>
|
|
169
|
+
</Box>
|
|
170
|
+
|
|
171
|
+
{/* Provider filter — only shown when there are 2+ providers */}
|
|
172
|
+
{showProviderFilter && (
|
|
173
|
+
<Box>
|
|
174
|
+
<Typography variant="caption" fontWeight={600} color="text.secondary" sx={{ mb: 1, display: 'block', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
|
175
|
+
Provider
|
|
176
|
+
</Typography>
|
|
177
|
+
<Box sx={{ display: 'flex', gap: 0.75, flexWrap: 'wrap' }}>
|
|
178
|
+
<Chip
|
|
179
|
+
icon={<AppsIcon sx={{ fontSize: '0.9rem !important' }} />}
|
|
180
|
+
label="All"
|
|
181
|
+
size="small"
|
|
182
|
+
clickable
|
|
183
|
+
onClick={() => handleProviderClick(undefined)}
|
|
184
|
+
sx={{
|
|
185
|
+
fontWeight: 600,
|
|
186
|
+
fontSize: '0.75rem',
|
|
187
|
+
borderRadius: 2,
|
|
188
|
+
border: '1.5px solid',
|
|
189
|
+
borderColor: !value.providerId ? 'text.primary' : 'divider',
|
|
190
|
+
backgroundColor: !value.providerId ? 'text.primary' : 'transparent',
|
|
191
|
+
color: !value.providerId ? 'background.paper' : 'text.secondary',
|
|
192
|
+
transition: 'all 0.15s ease',
|
|
193
|
+
'&:hover': {
|
|
194
|
+
borderColor: 'text.primary',
|
|
195
|
+
backgroundColor: !value.providerId ? 'text.primary' : 'action.hover',
|
|
196
|
+
},
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
{providers.map(p => {
|
|
200
|
+
const isSelected = value.providerId === p.id;
|
|
201
|
+
const label = p.target.split('/').slice(-1)[0]?.replace(/\.git$/, '') ?? p.id;
|
|
202
|
+
return (
|
|
203
|
+
<Chip
|
|
204
|
+
key={p.id}
|
|
205
|
+
icon={<StorageIcon sx={{ fontSize: '0.85rem !important', color: isSelected ? 'background.paper' : 'inherit' }} />}
|
|
206
|
+
label={label}
|
|
207
|
+
size="small"
|
|
208
|
+
clickable
|
|
209
|
+
onClick={() => handleProviderClick(p.id)}
|
|
210
|
+
sx={{
|
|
211
|
+
fontWeight: 600,
|
|
212
|
+
fontSize: '0.75rem',
|
|
213
|
+
borderRadius: 2,
|
|
214
|
+
border: '1.5px solid',
|
|
215
|
+
borderColor: isSelected ? 'text.primary' : 'divider',
|
|
216
|
+
backgroundColor: isSelected ? 'text.primary' : 'transparent',
|
|
217
|
+
color: isSelected ? 'background.paper' : 'text.secondary',
|
|
218
|
+
transition: 'all 0.15s ease',
|
|
219
|
+
'&:hover': {
|
|
220
|
+
borderColor: 'text.primary',
|
|
221
|
+
backgroundColor: isSelected ? 'text.primary' : 'action.hover',
|
|
222
|
+
},
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
);
|
|
226
|
+
})}
|
|
227
|
+
</Box>
|
|
228
|
+
</Box>
|
|
229
|
+
)}
|
|
230
|
+
</Box>
|
|
231
|
+
|
|
232
|
+
{availableTags.length > 0 && (
|
|
233
|
+
<Box>
|
|
234
|
+
<Typography variant="caption" fontWeight={600} color="text.secondary" sx={{ mb: 1, display: 'block', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
|
235
|
+
Tags
|
|
236
|
+
</Typography>
|
|
237
|
+
<Box sx={{ display: 'flex', gap: 0.75, flexWrap: 'wrap' }}>
|
|
238
|
+
{availableTags.map(tag => (
|
|
239
|
+
<Chip
|
|
240
|
+
key={tag}
|
|
241
|
+
label={`#${tag}`}
|
|
242
|
+
size="small"
|
|
243
|
+
clickable
|
|
244
|
+
onClick={() => handleTagToggle(tag)}
|
|
245
|
+
sx={{
|
|
246
|
+
fontSize: '0.7rem',
|
|
247
|
+
fontWeight: value.tags.includes(tag) ? 700 : 400,
|
|
248
|
+
borderRadius: 1.5,
|
|
249
|
+
backgroundColor: value.tags.includes(tag) ? 'primary.main' : 'transparent',
|
|
250
|
+
color: value.tags.includes(tag) ? '#fff' : 'text.secondary',
|
|
251
|
+
border: '1px solid',
|
|
252
|
+
borderColor: value.tags.includes(tag) ? 'primary.main' : 'divider',
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
))}
|
|
256
|
+
</Box>
|
|
257
|
+
</Box>
|
|
258
|
+
)}
|
|
259
|
+
</Box>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import Box from '@mui/material/Box';
|
|
3
|
+
import Chip from '@mui/material/Chip';
|
|
4
|
+
import Button from '@mui/material/Button';
|
|
5
|
+
import CircularProgress from '@mui/material/CircularProgress';
|
|
6
|
+
import Dialog from '@mui/material/Dialog';
|
|
7
|
+
import DialogActions from '@mui/material/DialogActions';
|
|
8
|
+
import DialogContent from '@mui/material/DialogContent';
|
|
9
|
+
import DialogTitle from '@mui/material/DialogTitle';
|
|
10
|
+
import Tooltip from '@mui/material/Tooltip';
|
|
11
|
+
import Typography from '@mui/material/Typography';
|
|
12
|
+
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
|
13
|
+
import DownloadIcon from '@mui/icons-material/Download';
|
|
14
|
+
import CheckIcon from '@mui/icons-material/Check';
|
|
15
|
+
import FolderZipIcon from '@mui/icons-material/FolderZip';
|
|
16
|
+
import type { AiTool } from '@nospt/plugin-dev-ai-hub-common';
|
|
17
|
+
import { getInstallPathsForAsset } from '@nospt/plugin-dev-ai-hub-common';
|
|
18
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
19
|
+
import { devAiHubApiRef } from '../../api/DevAiHubClient';
|
|
20
|
+
import { useAssetDetail } from '../../hooks';
|
|
21
|
+
import { ToolIcon } from '../ToolIcon';
|
|
22
|
+
|
|
23
|
+
const TOOL_LABELS: Record<string, string> = {
|
|
24
|
+
'claude-code': 'Claude Code',
|
|
25
|
+
'github-copilot': 'GitHub Copilot',
|
|
26
|
+
'google-gemini': 'Google Gemini',
|
|
27
|
+
'cursor': 'Cursor',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
interface AssetInstallDialogProps {
|
|
31
|
+
assetId: string | null;
|
|
32
|
+
onClose: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function AssetInstallDialog({ assetId, onClose }: AssetInstallDialogProps) {
|
|
36
|
+
const [copiedTool, setCopiedTool] = useState<string | null>(null);
|
|
37
|
+
const api = useApi(devAiHubApiRef);
|
|
38
|
+
const { asset, loading } = useAssetDetail(assetId);
|
|
39
|
+
|
|
40
|
+
const handleClose = () => {
|
|
41
|
+
onClose();
|
|
42
|
+
setCopiedTool(null);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleCopy = (tool: string) => {
|
|
46
|
+
if (!asset) return;
|
|
47
|
+
navigator.clipboard.writeText(asset.content).then(() => {
|
|
48
|
+
setCopiedTool(tool);
|
|
49
|
+
setTimeout(() => setCopiedTool(null), 2000);
|
|
50
|
+
});
|
|
51
|
+
api.trackInstall(asset.id).catch(() => {});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const resourcePaths = asset?.resourcesContent
|
|
55
|
+
? Object.keys(asset.resourcesContent)
|
|
56
|
+
: [];
|
|
57
|
+
const isZipSkill = asset?.type === 'skill' && resourcePaths.length > 0;
|
|
58
|
+
|
|
59
|
+
const handleDownload = async (_tool: string, installPath: string) => {
|
|
60
|
+
if (!asset) return;
|
|
61
|
+
if (isZipSkill) {
|
|
62
|
+
const url = await api.getDownloadUrl(asset.id);
|
|
63
|
+
const a = document.createElement('a');
|
|
64
|
+
a.href = url;
|
|
65
|
+
a.download = `${asset.name.replace(/\s+/g, '-').toLowerCase()}.zip`;
|
|
66
|
+
a.click();
|
|
67
|
+
} else {
|
|
68
|
+
const filename = installPath.split('/').pop() ?? `${asset.name}.md`;
|
|
69
|
+
const blob = new Blob([asset.content], { type: 'text/markdown' });
|
|
70
|
+
const url = URL.createObjectURL(blob);
|
|
71
|
+
const a = document.createElement('a');
|
|
72
|
+
a.href = url;
|
|
73
|
+
a.download = filename;
|
|
74
|
+
a.click();
|
|
75
|
+
URL.revokeObjectURL(url);
|
|
76
|
+
}
|
|
77
|
+
api.trackInstall(asset.id).catch(() => {});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const installPaths = asset
|
|
81
|
+
? getInstallPathsForAsset(asset.type, asset.tools, asset.name, {
|
|
82
|
+
installPath: asset.installPath,
|
|
83
|
+
installPaths: asset.installPaths,
|
|
84
|
+
})
|
|
85
|
+
: {};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Dialog open={!!assetId} onClose={handleClose} maxWidth="sm" fullWidth>
|
|
89
|
+
<DialogTitle sx={{ pb: 1 }}>
|
|
90
|
+
{asset ? `Install: ${asset.name}` : 'Install'}
|
|
91
|
+
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5, fontWeight: 400 }}>
|
|
92
|
+
Copy the content and place the file at the path shown for your tool.
|
|
93
|
+
</Typography>
|
|
94
|
+
</DialogTitle>
|
|
95
|
+
|
|
96
|
+
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: '8px !important' }}>
|
|
97
|
+
{loading && (
|
|
98
|
+
<Box sx={{ display: 'flex', justifyContent: 'center', py: 3 }}>
|
|
99
|
+
<CircularProgress />
|
|
100
|
+
</Box>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{!loading && asset && isZipSkill && (
|
|
104
|
+
<Box
|
|
105
|
+
sx={{
|
|
106
|
+
border: '1px solid',
|
|
107
|
+
borderColor: 'info.main',
|
|
108
|
+
borderRadius: 2,
|
|
109
|
+
p: 1.5,
|
|
110
|
+
backgroundColor: 'info.main',
|
|
111
|
+
backgroundImage: 'none',
|
|
112
|
+
bgcolor: theme => `${theme.palette.info.main}12`,
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
|
|
116
|
+
<FolderZipIcon sx={{ fontSize: '1rem', color: 'info.main' }} />
|
|
117
|
+
<Typography variant="subtitle2" fontWeight={700} color="info.main">
|
|
118
|
+
Bundled skill — downloads as .zip
|
|
119
|
+
</Typography>
|
|
120
|
+
</Box>
|
|
121
|
+
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1 }}>
|
|
122
|
+
This skill includes resource files alongside <code>SKILL.md</code>.
|
|
123
|
+
Extract the zip and place all files in the skill directory.
|
|
124
|
+
</Typography>
|
|
125
|
+
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
|
|
126
|
+
<Chip
|
|
127
|
+
label="SKILL.md"
|
|
128
|
+
size="small"
|
|
129
|
+
sx={{ fontFamily: 'monospace', fontSize: '0.7rem', height: 20 }}
|
|
130
|
+
/>
|
|
131
|
+
{resourcePaths.map(p => (
|
|
132
|
+
<Chip
|
|
133
|
+
key={p}
|
|
134
|
+
label={p}
|
|
135
|
+
size="small"
|
|
136
|
+
variant="outlined"
|
|
137
|
+
sx={{ fontFamily: 'monospace', fontSize: '0.7rem', height: 20 }}
|
|
138
|
+
/>
|
|
139
|
+
))}
|
|
140
|
+
</Box>
|
|
141
|
+
</Box>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
{!loading && asset && Object.entries(installPaths).map(([tool, installPath]) => (
|
|
145
|
+
<Box
|
|
146
|
+
key={tool}
|
|
147
|
+
sx={{
|
|
148
|
+
border: '1px solid',
|
|
149
|
+
borderColor: 'divider',
|
|
150
|
+
borderRadius: 2,
|
|
151
|
+
p: 1.5,
|
|
152
|
+
}}
|
|
153
|
+
>
|
|
154
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
|
|
155
|
+
<ToolIcon tool={tool as AiTool} sx={{ fontSize: '1rem' }} />
|
|
156
|
+
<Typography variant="subtitle2" fontWeight={700}>
|
|
157
|
+
{TOOL_LABELS[tool] ?? tool}
|
|
158
|
+
</Typography>
|
|
159
|
+
</Box>
|
|
160
|
+
|
|
161
|
+
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 0.5 }}>
|
|
162
|
+
Install path
|
|
163
|
+
</Typography>
|
|
164
|
+
<Box
|
|
165
|
+
sx={{
|
|
166
|
+
bgcolor: 'action.hover',
|
|
167
|
+
border: '1px solid',
|
|
168
|
+
borderColor: 'divider',
|
|
169
|
+
borderRadius: 1,
|
|
170
|
+
px: 1.5,
|
|
171
|
+
py: 0.75,
|
|
172
|
+
fontFamily: 'monospace',
|
|
173
|
+
fontSize: '0.78rem',
|
|
174
|
+
color: 'text.primary',
|
|
175
|
+
wordBreak: 'break-all',
|
|
176
|
+
mb: 1.25,
|
|
177
|
+
}}
|
|
178
|
+
>
|
|
179
|
+
{installPath}
|
|
180
|
+
</Box>
|
|
181
|
+
|
|
182
|
+
<Box sx={{ display: 'flex', gap: 1 }}>
|
|
183
|
+
<Tooltip title={copiedTool === tool ? 'Copied!' : 'Copy markdown content'}>
|
|
184
|
+
<Button
|
|
185
|
+
size="small"
|
|
186
|
+
variant="outlined"
|
|
187
|
+
startIcon={copiedTool === tool ? <CheckIcon /> : <ContentCopyIcon />}
|
|
188
|
+
onClick={() => handleCopy(tool)}
|
|
189
|
+
color={copiedTool === tool ? 'success' : 'primary'}
|
|
190
|
+
sx={{ flex: 1 }}
|
|
191
|
+
>
|
|
192
|
+
{copiedTool === tool ? 'Copied!' : 'Copy Content'}
|
|
193
|
+
</Button>
|
|
194
|
+
</Tooltip>
|
|
195
|
+
<Tooltip title={isZipSkill ? 'Download as .zip with all bundled files' : 'Download file with correct name'}>
|
|
196
|
+
<Button
|
|
197
|
+
size="small"
|
|
198
|
+
variant="outlined"
|
|
199
|
+
startIcon={isZipSkill ? <FolderZipIcon /> : <DownloadIcon />}
|
|
200
|
+
onClick={() => handleDownload(tool, installPath)}
|
|
201
|
+
sx={{ flex: 1 }}
|
|
202
|
+
>
|
|
203
|
+
{isZipSkill ? 'Download .zip' : 'Download'}
|
|
204
|
+
</Button>
|
|
205
|
+
</Tooltip>
|
|
206
|
+
</Box>
|
|
207
|
+
</Box>
|
|
208
|
+
))}
|
|
209
|
+
</DialogContent>
|
|
210
|
+
|
|
211
|
+
<DialogActions>
|
|
212
|
+
<Button onClick={handleClose}>Close</Button>
|
|
213
|
+
</DialogActions>
|
|
214
|
+
</Dialog>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AssetInstallDialog } from './AssetInstallDialog';
|