@nospt/plugin-dev-ai-hub 0.1.0 → 0.2.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/dev/index.tsx +17 -217
- package/dist/api/DevAiHubClient.esm.js +2 -0
- package/dist/api/DevAiHubClient.esm.js.map +1 -1
- package/dist/components/AssetCard/AssetCard.esm.js +60 -134
- package/dist/components/AssetCard/AssetCard.esm.js.map +1 -1
- package/dist/components/AssetCard/AssetCard.module.css.esm.js +8 -0
- package/dist/components/AssetCard/AssetCard.module.css.esm.js.map +1 -0
- package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +175 -281
- package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +1 -1
- package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js +8 -0
- package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js.map +1 -0
- package/dist/components/AssetFilters/AssetFilters.esm.js +94 -134
- package/dist/components/AssetFilters/AssetFilters.esm.js.map +1 -1
- package/dist/components/AssetFilters/AssetFilters.module.css.esm.js +8 -0
- package/dist/components/AssetFilters/AssetFilters.module.css.esm.js.map +1 -0
- package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +70 -126
- package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +1 -1
- package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js +8 -0
- package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js.map +1 -0
- package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +64 -189
- package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +1 -1
- package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js +8 -0
- package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js.map +1 -0
- package/dist/components/McpConfigPage/McpConfigPage.esm.js +200 -0
- package/dist/components/McpConfigPage/McpConfigPage.esm.js.map +1 -0
- package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js +8 -0
- package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js.map +1 -0
- package/dist/components/McpConfigPage/index.esm.js +6 -0
- package/dist/components/McpConfigPage/index.esm.js.map +1 -0
- package/dist/components/ToolIcon/ToolIcon.esm.js +25 -13
- package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.esm.js +1 -1
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
- package/dist/plugin.esm.js +22 -4
- package/dist/plugin.esm.js.map +1 -1
- package/dist/routes.esm.js +2 -1
- package/dist/routes.esm.js.map +1 -1
- package/package.json +24 -14
- package/package.json-prepack +24 -14
- package/src/components/AssetCard/AssetCard.module.css +153 -0
- package/src/components/AssetCard/AssetCard.tsx +79 -148
- package/src/components/AssetDetailPanel/AssetDetailPanel.module.css +207 -0
- package/src/components/AssetDetailPanel/AssetDetailPanel.tsx +228 -292
- package/src/components/AssetFilters/AssetFilters.module.css +67 -0
- package/src/components/AssetFilters/AssetFilters.tsx +136 -178
- package/src/components/AssetInstallDialog/AssetInstallDialog.module.css +72 -0
- package/src/components/AssetInstallDialog/AssetInstallDialog.tsx +95 -135
- package/src/components/DevAiHubPage/DevAiHubPage.module.css +105 -0
- package/src/components/DevAiHubPage/DevAiHubPage.tsx +73 -222
- package/src/components/McpConfigDialog/McpConfigDialog.module.css +111 -0
- package/src/components/McpConfigDialog/McpConfigDialog.tsx +178 -208
- package/src/components/McpConfigPage/McpConfigPage.module.css +121 -0
- package/src/components/McpConfigPage/McpConfigPage.tsx +242 -0
- package/src/components/McpConfigPage/index.ts +1 -0
- package/src/components/ToolIcon/ToolIcon.tsx +31 -15
- package/src/css-modules.d.ts +4 -0
- package/src/index.ts +1 -1
- package/src/plugin.tsx +21 -1
- package/src/routes.ts +1 -0
- package/tests/e2e/.auth.json +14 -0
- package/tests/e2e/asset-detail.spec.ts +90 -0
- package/tests/e2e/asset-filtering.spec.ts +145 -0
- package/tests/e2e/asset-installation.spec.ts +74 -0
- package/tests/e2e/dev-ai-hub-page.spec.ts +89 -0
- package/tests/e2e/fixtures/base.ts +61 -0
- package/tests/e2e/fixtures/mock-api.ts +221 -0
- package/tests/e2e/mcp-config.spec.ts +88 -0
- package/dist/components/McpConfigDialog/McpConfigDialog.esm.js +0 -274
- package/dist/components/McpConfigDialog/McpConfigDialog.esm.js.map +0 -1
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import ReactMarkdown from 'react-markdown';
|
|
3
3
|
import remarkGfm from 'remark-gfm';
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import Divider from '@mui/material/Divider';
|
|
10
|
-
import Drawer from '@mui/material/Drawer';
|
|
11
|
-
import IconButton from '@mui/material/IconButton';
|
|
12
|
-
import Link from '@mui/material/Link';
|
|
13
|
-
import Snackbar from '@mui/material/Snackbar';
|
|
14
|
-
import Tab from '@mui/material/Tab';
|
|
15
|
-
import Tabs from '@mui/material/Tabs';
|
|
16
|
-
import Typography from '@mui/material/Typography';
|
|
17
|
-
import CloseIcon from '@mui/icons-material/Close';
|
|
18
|
-
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
|
19
|
-
import FolderZipIcon from '@mui/icons-material/FolderZip';
|
|
20
|
-
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
|
4
|
+
import {
|
|
5
|
+
Alert, Box, Flex, Text, Button, ButtonIcon, Tag, TagGroup, Skeleton, Link,
|
|
6
|
+
Tabs, TabList, Tab, TabPanel,
|
|
7
|
+
} from '@backstage/ui';
|
|
8
|
+
import { RiCloseLine, RiFileCopyLine, RiFolderZipLine, RiExternalLinkLine } from '@remixicon/react';
|
|
21
9
|
import type { AssetType } from '@nospt/plugin-dev-ai-hub-common';
|
|
22
10
|
import { useAssetDetail } from '../../hooks';
|
|
11
|
+
import styles from './AssetDetailPanel.module.css';
|
|
23
12
|
|
|
24
13
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
14
|
const SyntaxHighlighter = require('react-syntax-highlighter/dist/esm/prism').default;
|
|
@@ -39,7 +28,6 @@ interface AssetDetailPanelProps {
|
|
|
39
28
|
}
|
|
40
29
|
|
|
41
30
|
export function AssetDetailPanel({ assetId, onClose }: AssetDetailPanelProps) {
|
|
42
|
-
const [tab, setTab] = useState(0);
|
|
43
31
|
const [snackbar, setSnackbar] = useState<string | null>(null);
|
|
44
32
|
const { asset, loading } = useAssetDetail(assetId);
|
|
45
33
|
|
|
@@ -50,328 +38,276 @@ export function AssetDetailPanel({ assetId, onClose }: AssetDetailPanelProps) {
|
|
|
50
38
|
);
|
|
51
39
|
};
|
|
52
40
|
|
|
41
|
+
if (!assetId) return null;
|
|
42
|
+
|
|
53
43
|
return (
|
|
54
44
|
<>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
45
|
+
{/* Overlay */}
|
|
46
|
+
<div className={styles.overlay} onClick={onClose} role="presentation" />
|
|
47
|
+
|
|
48
|
+
{/* Drawer */}
|
|
49
|
+
<div className={styles.drawer}>
|
|
50
|
+
<Flex className={styles.panelContainer}>
|
|
62
51
|
{/* Header */}
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
p: 2,
|
|
66
|
-
display: 'flex',
|
|
67
|
-
alignItems: 'flex-start',
|
|
68
|
-
gap: 2,
|
|
69
|
-
borderBottom: 1,
|
|
70
|
-
borderColor: 'divider',
|
|
71
|
-
}}
|
|
72
|
-
>
|
|
73
|
-
<Box sx={{ flex: 1 }}>
|
|
52
|
+
<Flex className={styles.header}>
|
|
53
|
+
<Box className={styles.headerContent}>
|
|
74
54
|
{loading || !asset ? (
|
|
75
|
-
<
|
|
76
|
-
<
|
|
77
|
-
<
|
|
55
|
+
<Flex className={styles.loadingRow}>
|
|
56
|
+
<Skeleton style={{ width: 16, height: 16 }} />
|
|
57
|
+
<Text variant="body-small" color="secondary">
|
|
78
58
|
Loading...
|
|
79
|
-
</
|
|
80
|
-
</
|
|
59
|
+
</Text>
|
|
60
|
+
</Flex>
|
|
81
61
|
) : (
|
|
82
62
|
<>
|
|
83
|
-
<
|
|
84
|
-
<
|
|
63
|
+
<Flex className={styles.nameRow}>
|
|
64
|
+
<Text variant="title-small" weight="bold">
|
|
85
65
|
{asset.name}
|
|
86
|
-
</
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
66
|
+
</Text>
|
|
67
|
+
<TagGroup aria-label="Asset type">
|
|
68
|
+
<Tag
|
|
69
|
+
id={asset.type}
|
|
70
|
+
size="small"
|
|
71
|
+
className={styles.typeTag}
|
|
72
|
+
style={{
|
|
73
|
+
backgroundColor: `${TYPE_COLORS[asset.type]}20`,
|
|
74
|
+
color: TYPE_COLORS[asset.type],
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
{asset.type}
|
|
78
|
+
</Tag>
|
|
79
|
+
</TagGroup>
|
|
80
|
+
</Flex>
|
|
81
|
+
<Text variant="body-medium" color="secondary">
|
|
98
82
|
{asset.description}
|
|
99
|
-
</
|
|
83
|
+
</Text>
|
|
100
84
|
</>
|
|
101
85
|
)}
|
|
102
86
|
</Box>
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
87
|
+
<ButtonIcon
|
|
88
|
+
aria-label="Close"
|
|
89
|
+
icon={<RiCloseLine size={20} />}
|
|
90
|
+
variant="tertiary"
|
|
91
|
+
onPress={onClose}
|
|
92
|
+
/>
|
|
93
|
+
</Flex>
|
|
107
94
|
|
|
108
95
|
{/* Tabs */}
|
|
109
|
-
<Tabs
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<Tab label="Metadata" />
|
|
116
|
-
<Tab label="Raw YAML" />
|
|
117
|
-
</Tabs>
|
|
96
|
+
<Tabs defaultSelectedKey="preview">
|
|
97
|
+
<TabList className={styles.tabBar}>
|
|
98
|
+
<Tab id="preview">Preview</Tab>
|
|
99
|
+
<Tab id="metadata">Metadata</Tab>
|
|
100
|
+
<Tab id="raw">Raw YAML</Tab>
|
|
101
|
+
</TabList>
|
|
118
102
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)}
|
|
126
|
-
|
|
127
|
-
{!loading && asset && (
|
|
128
|
-
<>
|
|
129
|
-
{tab === 0 && (
|
|
130
|
-
<Box
|
|
131
|
-
sx={{
|
|
132
|
-
'& h1,h2,h3,h4,h5,h6': { mt: 2, mb: 1, fontWeight: 700 },
|
|
133
|
-
'& p': { mb: 1, lineHeight: 1.7 },
|
|
134
|
-
'& ul, & ol': { pl: 2.5, mb: 1 },
|
|
135
|
-
'& li': { mb: 0.5 },
|
|
136
|
-
'& blockquote': {
|
|
137
|
-
borderLeft: '3px solid',
|
|
138
|
-
borderColor: 'primary.main',
|
|
139
|
-
pl: 1.5,
|
|
140
|
-
color: 'text.secondary',
|
|
141
|
-
my: 1,
|
|
142
|
-
ml: 0,
|
|
143
|
-
},
|
|
144
|
-
'& table': { width: '100%', borderCollapse: 'collapse', mb: 1 },
|
|
145
|
-
'& th, & td': {
|
|
146
|
-
border: '1px solid',
|
|
147
|
-
borderColor: 'divider',
|
|
148
|
-
px: 1.5,
|
|
149
|
-
py: 0.75,
|
|
150
|
-
fontSize: '0.875rem',
|
|
151
|
-
},
|
|
152
|
-
'& th': { backgroundColor: 'action.hover', fontWeight: 700 },
|
|
153
|
-
'& code': {
|
|
154
|
-
bgcolor: 'action.hover',
|
|
155
|
-
px: 0.5,
|
|
156
|
-
py: 0.2,
|
|
157
|
-
borderRadius: 0.5,
|
|
158
|
-
fontFamily: 'monospace',
|
|
159
|
-
fontSize: '0.875em',
|
|
160
|
-
},
|
|
161
|
-
'& pre code': { bgcolor: 'transparent', px: 0, py: 0 },
|
|
162
|
-
}}
|
|
163
|
-
>
|
|
164
|
-
<ReactMarkdown
|
|
165
|
-
remarkPlugins={[remarkGfm]}
|
|
166
|
-
components={{
|
|
167
|
-
pre: ({ children }) => <Box sx={{ my: 1 }}>{children}</Box>,
|
|
168
|
-
code({ className, children }) {
|
|
169
|
-
const match = /language-(\w+)/.exec(className || '');
|
|
170
|
-
const code = String(children).replace(/\n$/, '');
|
|
171
|
-
const isBlock = code.includes('\n') || !!match;
|
|
172
|
-
return isBlock ? (
|
|
173
|
-
<SyntaxHighlighter
|
|
174
|
-
style={oneLight}
|
|
175
|
-
language={match?.[1] ?? 'text'}
|
|
176
|
-
PreTag="div"
|
|
177
|
-
customStyle={{
|
|
178
|
-
borderRadius: 8,
|
|
179
|
-
fontSize: '0.8rem',
|
|
180
|
-
margin: 0,
|
|
181
|
-
border: '1px solid rgba(0,0,0,0.08)',
|
|
182
|
-
}}
|
|
183
|
-
>
|
|
184
|
-
{code}
|
|
185
|
-
</SyntaxHighlighter>
|
|
186
|
-
) : (
|
|
187
|
-
<code className={className}>{children}</code>
|
|
188
|
-
);
|
|
189
|
-
},
|
|
190
|
-
}}
|
|
191
|
-
>
|
|
192
|
-
{asset.content}
|
|
193
|
-
</ReactMarkdown>
|
|
194
|
-
</Box>
|
|
195
|
-
)}
|
|
103
|
+
<Box className={styles.contentArea}>
|
|
104
|
+
{loading && (
|
|
105
|
+
<Flex style={{ justifyContent: 'center', paddingTop: 'var(--bui-space-8)' }}>
|
|
106
|
+
<Skeleton style={{ width: '100%', height: 200 }} />
|
|
107
|
+
</Flex>
|
|
108
|
+
)}
|
|
196
109
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
110
|
+
{!loading && asset && (
|
|
111
|
+
<>
|
|
112
|
+
<TabPanel id="preview">
|
|
113
|
+
<div className={styles.markdownContent}>
|
|
114
|
+
<ReactMarkdown
|
|
115
|
+
remarkPlugins={[remarkGfm]}
|
|
116
|
+
components={{
|
|
117
|
+
pre: ({ children }) => <div className={styles.codeBlockWrapper}>{children}</div>,
|
|
118
|
+
code({ className, children }) {
|
|
119
|
+
const match = /language-(\w+)/.exec(className || '');
|
|
120
|
+
const code = String(children).replace(/\n$/, '');
|
|
121
|
+
const isBlock = code.includes('\n') || !!match;
|
|
122
|
+
return isBlock ? (
|
|
123
|
+
<SyntaxHighlighter
|
|
124
|
+
style={oneLight}
|
|
125
|
+
language={match?.[1] ?? 'text'}
|
|
126
|
+
PreTag="div"
|
|
127
|
+
customStyle={{
|
|
128
|
+
borderRadius: 8,
|
|
129
|
+
fontSize: '0.8rem',
|
|
130
|
+
margin: 0,
|
|
131
|
+
border: '1px solid rgba(0,0,0,0.08)',
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
{code}
|
|
135
|
+
</SyntaxHighlighter>
|
|
136
|
+
) : (
|
|
137
|
+
<code className={className}>{children}</code>
|
|
138
|
+
);
|
|
139
|
+
},
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
{asset.content}
|
|
143
|
+
</ReactMarkdown>
|
|
144
|
+
</div>
|
|
145
|
+
</TabPanel>
|
|
207
146
|
|
|
208
|
-
|
|
147
|
+
<TabPanel id="metadata">
|
|
148
|
+
<Flex className={styles.metaSection}>
|
|
149
|
+
<MetaRow label="Author" value={asset.author} />
|
|
150
|
+
<MetaRow label="Version" value={asset.version} />
|
|
151
|
+
<MetaRow label="Provider" value={asset.providerId} />
|
|
152
|
+
{asset.commitSha && (
|
|
153
|
+
<MetaRow label="Commit" value={asset.commitSha.slice(0, 8)} />
|
|
154
|
+
)}
|
|
155
|
+
<MetaRow label="Last synced" value={new Date(asset.syncedAt).toLocaleString()} />
|
|
156
|
+
<MetaRow label="Branch" value={asset.branch} />
|
|
209
157
|
|
|
210
|
-
|
|
211
|
-
<Typography variant="caption" color="text.secondary">
|
|
212
|
-
Compatible tools
|
|
213
|
-
</Typography>
|
|
214
|
-
<Box sx={{ display: 'flex', gap: 1, mt: 0.5 }}>
|
|
215
|
-
{asset.tools.map(t => (
|
|
216
|
-
<Chip key={t} label={t} size="small" />
|
|
217
|
-
))}
|
|
218
|
-
</Box>
|
|
219
|
-
</Box>
|
|
158
|
+
<hr className={styles.divider} />
|
|
220
159
|
|
|
221
|
-
{asset.tags.length > 0 && (
|
|
222
160
|
<Box>
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
</
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
161
|
+
<Text variant="body-small" color="secondary">
|
|
162
|
+
Compatible tools
|
|
163
|
+
</Text>
|
|
164
|
+
<Flex className={styles.chipsRow}>
|
|
165
|
+
<TagGroup aria-label="Compatible tools">
|
|
166
|
+
{asset.tools.map(t => (
|
|
167
|
+
<Tag key={t} id={t} size="small">{t}</Tag>
|
|
168
|
+
))}
|
|
169
|
+
</TagGroup>
|
|
170
|
+
</Flex>
|
|
231
171
|
</Box>
|
|
232
|
-
)}
|
|
233
172
|
|
|
234
|
-
|
|
235
|
-
<>
|
|
236
|
-
<Divider />
|
|
173
|
+
{asset.tags.length > 0 && (
|
|
237
174
|
<Box>
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
label="SKILL.md"
|
|
249
|
-
size="small"
|
|
250
|
-
sx={{ fontFamily: 'monospace', fontSize: '0.7rem', height: 20 }}
|
|
251
|
-
/>
|
|
252
|
-
{Object.keys(asset.resourcesContent).map(p => (
|
|
253
|
-
<Chip
|
|
254
|
-
key={p}
|
|
255
|
-
label={p}
|
|
256
|
-
size="small"
|
|
257
|
-
variant="outlined"
|
|
258
|
-
sx={{ fontFamily: 'monospace', fontSize: '0.7rem', height: 20 }}
|
|
259
|
-
/>
|
|
260
|
-
))}
|
|
261
|
-
</Box>
|
|
262
|
-
<Typography variant="caption" color="text.disabled">
|
|
263
|
-
Downloads as .zip containing all files above.
|
|
264
|
-
</Typography>
|
|
265
|
-
</Box>
|
|
266
|
-
) : (
|
|
267
|
-
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mt: 0.5 }}>
|
|
268
|
-
<Chip
|
|
269
|
-
label="SKILL.md"
|
|
270
|
-
size="small"
|
|
271
|
-
sx={{ fontFamily: 'monospace', fontSize: '0.7rem', height: 20 }}
|
|
272
|
-
/>
|
|
273
|
-
</Box>
|
|
274
|
-
)}
|
|
175
|
+
<Text variant="body-small" color="secondary">
|
|
176
|
+
Tags
|
|
177
|
+
</Text>
|
|
178
|
+
<Flex className={styles.chipsRow}>
|
|
179
|
+
<TagGroup aria-label="Tags">
|
|
180
|
+
{asset.tags.map(t => (
|
|
181
|
+
<Tag key={t} id={t} size="small">{t}</Tag>
|
|
182
|
+
))}
|
|
183
|
+
</TagGroup>
|
|
184
|
+
</Flex>
|
|
275
185
|
</Box>
|
|
276
|
-
|
|
277
|
-
)}
|
|
186
|
+
)}
|
|
278
187
|
|
|
279
|
-
|
|
188
|
+
{asset.type === 'skill' && (
|
|
189
|
+
<>
|
|
190
|
+
<hr className={styles.divider} />
|
|
191
|
+
<Box>
|
|
192
|
+
<Flex className={styles.bundledHeader}>
|
|
193
|
+
<RiFolderZipLine size={14} style={{ color: 'var(--bui-fg-secondary)' }} />
|
|
194
|
+
<Text variant="body-small" color="secondary">
|
|
195
|
+
Bundled files
|
|
196
|
+
</Text>
|
|
197
|
+
</Flex>
|
|
198
|
+
{asset.resourcesContent && Object.keys(asset.resourcesContent).length > 0 ? (
|
|
199
|
+
<Flex direction="column" style={{ gap: 'var(--bui-space-1)', marginTop: 'var(--bui-space-1)' }}>
|
|
200
|
+
<TagGroup aria-label="Bundled files">
|
|
201
|
+
<Flex className={styles.chipsRow}>
|
|
202
|
+
<Tag id="skill-md" size="small" style={{ fontFamily: 'monospace', fontSize: '0.7rem', height: 20 }}>
|
|
203
|
+
SKILL.md
|
|
204
|
+
</Tag>
|
|
205
|
+
{Object.keys(asset.resourcesContent).map(p => (
|
|
206
|
+
<Tag key={p} id={p} size="small" style={{ fontFamily: 'monospace', fontSize: '0.7rem', height: 20 }}>
|
|
207
|
+
{p}
|
|
208
|
+
</Tag>
|
|
209
|
+
))}
|
|
210
|
+
</Flex>
|
|
211
|
+
</TagGroup>
|
|
212
|
+
<Text variant="body-small" color="secondary">
|
|
213
|
+
Downloads as .zip containing all files above.
|
|
214
|
+
</Text>
|
|
215
|
+
</Flex>
|
|
216
|
+
) : (
|
|
217
|
+
<TagGroup aria-label="Bundled files">
|
|
218
|
+
<Flex className={styles.chipsRow}>
|
|
219
|
+
<Tag id="skill-md-only" size="small" style={{ fontFamily: 'monospace', fontSize: '0.7rem', height: 20 }}>
|
|
220
|
+
SKILL.md
|
|
221
|
+
</Tag>
|
|
222
|
+
</Flex>
|
|
223
|
+
</TagGroup>
|
|
224
|
+
)}
|
|
225
|
+
</Box>
|
|
226
|
+
</>
|
|
227
|
+
)}
|
|
228
|
+
|
|
229
|
+
<hr className={styles.divider} />
|
|
280
230
|
|
|
281
|
-
<Box>
|
|
282
|
-
<Typography variant="caption" color="text.secondary">
|
|
283
|
-
Repository
|
|
284
|
-
</Typography>
|
|
285
231
|
<Box>
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
232
|
+
<Text variant="body-small" color="secondary">
|
|
233
|
+
Repository
|
|
234
|
+
</Text>
|
|
235
|
+
<Box>
|
|
236
|
+
<Link
|
|
237
|
+
href={asset.repoUrl}
|
|
238
|
+
target="_blank"
|
|
239
|
+
rel="noopener noreferrer"
|
|
240
|
+
className={styles.repoLink}
|
|
241
|
+
>
|
|
242
|
+
{asset.repoUrl} <RiExternalLinkLine size={12} />
|
|
243
|
+
</Link>
|
|
244
|
+
</Box>
|
|
294
245
|
</Box>
|
|
295
|
-
</
|
|
296
|
-
</
|
|
297
|
-
)}
|
|
246
|
+
</Flex>
|
|
247
|
+
</TabPanel>
|
|
298
248
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
fontFamily: 'monospace',
|
|
309
|
-
whiteSpace: 'pre-wrap',
|
|
310
|
-
wordBreak: 'break-word',
|
|
311
|
-
border: '1px solid',
|
|
312
|
-
borderColor: 'divider',
|
|
313
|
-
}}
|
|
314
|
-
>
|
|
315
|
-
{asset.yamlRaw}
|
|
316
|
-
</Box>
|
|
317
|
-
)}
|
|
318
|
-
</>
|
|
319
|
-
)}
|
|
320
|
-
</Box>
|
|
249
|
+
<TabPanel id="raw">
|
|
250
|
+
<pre className={styles.rawYaml}>
|
|
251
|
+
{asset.yamlRaw}
|
|
252
|
+
</pre>
|
|
253
|
+
</TabPanel>
|
|
254
|
+
</>
|
|
255
|
+
)}
|
|
256
|
+
</Box>
|
|
257
|
+
</Tabs>
|
|
321
258
|
|
|
322
259
|
{/* Actions */}
|
|
323
|
-
<
|
|
324
|
-
sx={{
|
|
325
|
-
p: 2,
|
|
326
|
-
borderTop: 1,
|
|
327
|
-
borderColor: 'divider',
|
|
328
|
-
display: 'flex',
|
|
329
|
-
gap: 1,
|
|
330
|
-
flexWrap: 'wrap',
|
|
331
|
-
}}
|
|
332
|
-
>
|
|
260
|
+
<Flex className={styles.actions}>
|
|
333
261
|
<Button
|
|
334
|
-
variant="
|
|
335
|
-
startIcon={<ContentCopyIcon />}
|
|
262
|
+
variant="primary"
|
|
336
263
|
onClick={handleCopy}
|
|
337
|
-
|
|
264
|
+
isDisabled={!asset}
|
|
338
265
|
>
|
|
266
|
+
<RiFileCopyLine size={16} />
|
|
339
267
|
Copy Markdown
|
|
340
268
|
</Button>
|
|
341
269
|
|
|
342
270
|
<Button
|
|
343
|
-
variant="
|
|
344
|
-
startIcon={<OpenInNewIcon />}
|
|
271
|
+
variant="secondary"
|
|
345
272
|
onClick={() => asset && window.open(asset.repoUrl, '_blank')}
|
|
346
|
-
|
|
273
|
+
isDisabled={!asset}
|
|
347
274
|
>
|
|
275
|
+
<RiExternalLinkLine size={16} />
|
|
348
276
|
Open in Repo
|
|
349
277
|
</Button>
|
|
350
|
-
</
|
|
351
|
-
</
|
|
352
|
-
</
|
|
278
|
+
</Flex>
|
|
279
|
+
</Flex>
|
|
280
|
+
</div>
|
|
353
281
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
282
|
+
{/* Snackbar replacement — simple toast */}
|
|
283
|
+
{snackbar && (
|
|
284
|
+
<div
|
|
285
|
+
style={{
|
|
286
|
+
position: 'fixed',
|
|
287
|
+
bottom: 24,
|
|
288
|
+
left: '50%',
|
|
289
|
+
transform: 'translateX(-50%)',
|
|
290
|
+
zIndex: 1400,
|
|
291
|
+
}}
|
|
292
|
+
>
|
|
293
|
+
<Alert
|
|
294
|
+
status="success"
|
|
295
|
+
icon
|
|
296
|
+
description={snackbar}
|
|
297
|
+
/>
|
|
298
|
+
</div>
|
|
299
|
+
)}
|
|
364
300
|
</>
|
|
365
301
|
);
|
|
366
302
|
}
|
|
367
303
|
|
|
368
304
|
function MetaRow({ label, value }: { label: string; value: string }) {
|
|
369
305
|
return (
|
|
370
|
-
<
|
|
371
|
-
<
|
|
372
|
-
{label}
|
|
373
|
-
</
|
|
374
|
-
<
|
|
375
|
-
</
|
|
306
|
+
<Flex className={styles.metaRow}>
|
|
307
|
+
<Text variant="body-medium" color="secondary" className={styles.metaLabel}>
|
|
308
|
+
{label}:
|
|
309
|
+
</Text>
|
|
310
|
+
<Text variant="body-medium" weight="bold">{value}</Text>
|
|
311
|
+
</Flex>
|
|
376
312
|
);
|
|
377
313
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.container {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: var(--bui-space-4);
|
|
6
|
+
margin-bottom: var(--bui-space-6);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.searchField {
|
|
10
|
+
width: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.filtersRow {
|
|
14
|
+
display: flex;
|
|
15
|
+
gap: var(--bui-space-6);
|
|
16
|
+
flex-wrap: wrap;
|
|
17
|
+
align-items: flex-start;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.filterLabel {
|
|
21
|
+
margin-bottom: var(--bui-space-2);
|
|
22
|
+
display: block;
|
|
23
|
+
text-transform: uppercase;
|
|
24
|
+
letter-spacing: 0.5px;
|
|
25
|
+
font-weight: 600;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.filterChips {
|
|
29
|
+
display: flex;
|
|
30
|
+
gap: 6px;
|
|
31
|
+
flex-wrap: wrap;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.filterChip {
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
font-size: 0.75rem;
|
|
37
|
+
border-radius: var(--bui-radius-2);
|
|
38
|
+
border: 1.5px solid;
|
|
39
|
+
transition: all 0.15s ease;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.filterChip :global(.bui-ToggleButtonContent) {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.filterChip svg {
|
|
49
|
+
display: block;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.toolChipSelected {
|
|
53
|
+
font-weight: 700;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.tagChip {
|
|
57
|
+
font-size: 0.7rem;
|
|
58
|
+
border-radius: var(--bui-radius-2);
|
|
59
|
+
border: 1px solid;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.tagChip :global(.bui-ToggleButtonContent) {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
}
|
|
67
|
+
}
|