@nospt/plugin-dev-ai-hub 0.1.0 → 0.2.1

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 (71) hide show
  1. package/dev/index.tsx +17 -217
  2. package/dist/api/DevAiHubClient.esm.js +2 -0
  3. package/dist/api/DevAiHubClient.esm.js.map +1 -1
  4. package/dist/components/AssetCard/AssetCard.esm.js +60 -134
  5. package/dist/components/AssetCard/AssetCard.esm.js.map +1 -1
  6. package/dist/components/AssetCard/AssetCard.module.css.esm.js +8 -0
  7. package/dist/components/AssetCard/AssetCard.module.css.esm.js.map +1 -0
  8. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +175 -281
  9. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +1 -1
  10. package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js +8 -0
  11. package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js.map +1 -0
  12. package/dist/components/AssetFilters/AssetFilters.esm.js +94 -134
  13. package/dist/components/AssetFilters/AssetFilters.esm.js.map +1 -1
  14. package/dist/components/AssetFilters/AssetFilters.module.css.esm.js +8 -0
  15. package/dist/components/AssetFilters/AssetFilters.module.css.esm.js.map +1 -0
  16. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +70 -126
  17. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +1 -1
  18. package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js +8 -0
  19. package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js.map +1 -0
  20. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +64 -189
  21. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +1 -1
  22. package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js +8 -0
  23. package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js.map +1 -0
  24. package/dist/components/McpConfigPage/McpConfigPage.esm.js +200 -0
  25. package/dist/components/McpConfigPage/McpConfigPage.esm.js.map +1 -0
  26. package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js +8 -0
  27. package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js.map +1 -0
  28. package/dist/components/McpConfigPage/index.esm.js +6 -0
  29. package/dist/components/McpConfigPage/index.esm.js.map +1 -0
  30. package/dist/components/ToolIcon/ToolIcon.esm.js +25 -13
  31. package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -1
  32. package/dist/index.d.ts +10 -5
  33. package/dist/index.esm.js +1 -1
  34. package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
  35. package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
  36. package/dist/plugin.esm.js +24 -6
  37. package/dist/plugin.esm.js.map +1 -1
  38. package/dist/routes.esm.js +2 -1
  39. package/dist/routes.esm.js.map +1 -1
  40. package/package.json +24 -14
  41. package/package.json-prepack +24 -14
  42. package/src/components/AssetCard/AssetCard.module.css +153 -0
  43. package/src/components/AssetCard/AssetCard.tsx +79 -148
  44. package/src/components/AssetDetailPanel/AssetDetailPanel.module.css +207 -0
  45. package/src/components/AssetDetailPanel/AssetDetailPanel.tsx +228 -292
  46. package/src/components/AssetFilters/AssetFilters.module.css +67 -0
  47. package/src/components/AssetFilters/AssetFilters.tsx +136 -178
  48. package/src/components/AssetInstallDialog/AssetInstallDialog.module.css +72 -0
  49. package/src/components/AssetInstallDialog/AssetInstallDialog.tsx +95 -135
  50. package/src/components/DevAiHubPage/DevAiHubPage.module.css +105 -0
  51. package/src/components/DevAiHubPage/DevAiHubPage.tsx +73 -222
  52. package/src/components/McpConfigDialog/McpConfigDialog.module.css +111 -0
  53. package/src/components/McpConfigDialog/McpConfigDialog.tsx +178 -208
  54. package/src/components/McpConfigPage/McpConfigPage.module.css +121 -0
  55. package/src/components/McpConfigPage/McpConfigPage.tsx +242 -0
  56. package/src/components/McpConfigPage/index.ts +1 -0
  57. package/src/components/ToolIcon/ToolIcon.tsx +31 -15
  58. package/src/css-modules.d.ts +4 -0
  59. package/src/index.ts +1 -1
  60. package/src/plugin.tsx +23 -3
  61. package/src/routes.ts +1 -0
  62. package/tests/e2e/.auth.json +14 -0
  63. package/tests/e2e/asset-detail.spec.ts +90 -0
  64. package/tests/e2e/asset-filtering.spec.ts +145 -0
  65. package/tests/e2e/asset-installation.spec.ts +74 -0
  66. package/tests/e2e/dev-ai-hub-page.spec.ts +89 -0
  67. package/tests/e2e/fixtures/base.ts +61 -0
  68. package/tests/e2e/fixtures/mock-api.ts +221 -0
  69. package/tests/e2e/mcp-config.spec.ts +88 -0
  70. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js +0 -274
  71. 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 Alert from '@mui/material/Alert';
5
- import Box from '@mui/material/Box';
6
- import Button from '@mui/material/Button';
7
- import Chip from '@mui/material/Chip';
8
- import CircularProgress from '@mui/material/CircularProgress';
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
- <Drawer
56
- anchor="right"
57
- open={!!assetId}
58
- onClose={onClose}
59
- PaperProps={{ sx: { width: { xs: '100vw', md: 640 } } }}
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
- <Box
64
- sx={{
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
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
76
- <CircularProgress size={16} />
77
- <Typography variant="body2" color="text.secondary">
55
+ <Flex className={styles.loadingRow}>
56
+ <Skeleton style={{ width: 16, height: 16 }} />
57
+ <Text variant="body-small" color="secondary">
78
58
  Loading...
79
- </Typography>
80
- </Box>
59
+ </Text>
60
+ </Flex>
81
61
  ) : (
82
62
  <>
83
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
84
- <Typography variant="h6" fontWeight={700}>
63
+ <Flex className={styles.nameRow}>
64
+ <Text variant="title-small" weight="bold">
85
65
  {asset.name}
86
- </Typography>
87
- <Chip
88
- label={asset.type}
89
- size="small"
90
- sx={{
91
- backgroundColor: `${TYPE_COLORS[asset.type]}20`,
92
- color: TYPE_COLORS[asset.type],
93
- fontWeight: 600,
94
- }}
95
- />
96
- </Box>
97
- <Typography variant="body2" color="text.secondary">
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
- </Typography>
83
+ </Text>
100
84
  </>
101
85
  )}
102
86
  </Box>
103
- <IconButton onClick={onClose} size="small">
104
- <CloseIcon />
105
- </IconButton>
106
- </Box>
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
- value={tab}
111
- onChange={(_, v) => setTab(v)}
112
- sx={{ borderBottom: 1, borderColor: 'divider', px: 2 }}
113
- >
114
- <Tab label="Preview" />
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
- {/* Content */}
120
- <Box sx={{ flex: 1, overflow: 'auto', p: 2 }}>
121
- {loading && (
122
- <Box sx={{ display: 'flex', justifyContent: 'center', pt: 4 }}>
123
- <CircularProgress />
124
- </Box>
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
- {tab === 1 && (
198
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
199
- <MetaRow label="Author" value={asset.author} />
200
- <MetaRow label="Version" value={asset.version} />
201
- <MetaRow label="Provider" value={asset.providerId} />
202
- {asset.commitSha && (
203
- <MetaRow label="Commit" value={asset.commitSha.slice(0, 8)} />
204
- )}
205
- <MetaRow label="Last synced" value={new Date(asset.syncedAt).toLocaleString()} />
206
- <MetaRow label="Branch" value={asset.branch} />
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
- <Divider />
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
- <Box>
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
- <Typography variant="caption" color="text.secondary">
224
- Tags
225
- </Typography>
226
- <Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mt: 0.5 }}>
227
- {asset.tags.map(t => (
228
- <Chip key={t} label={t} size="small" variant="outlined" />
229
- ))}
230
- </Box>
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
- {asset.type === 'skill' && (
235
- <>
236
- <Divider />
173
+ {asset.tags.length > 0 && (
237
174
  <Box>
238
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: 0.5 }}>
239
- <FolderZipIcon sx={{ fontSize: '0.85rem', color: 'text.secondary' }} />
240
- <Typography variant="caption" color="text.secondary">
241
- Bundled files
242
- </Typography>
243
- </Box>
244
- {asset.resourcesContent && Object.keys(asset.resourcesContent).length > 0 ? (
245
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, mt: 0.5 }}>
246
- <Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
247
- <Chip
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
- <Divider />
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
- <Link
287
- href={asset.repoUrl}
288
- target="_blank"
289
- rel="noopener noreferrer"
290
- variant="body2"
291
- >
292
- {asset.repoUrl} <OpenInNewIcon sx={{ fontSize: 12 }} />
293
- </Link>
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
- </Box>
296
- </Box>
297
- )}
246
+ </Flex>
247
+ </TabPanel>
298
248
 
299
- {tab === 2 && (
300
- <Box
301
- component="pre"
302
- sx={{
303
- bgcolor: 'action.hover',
304
- p: 2,
305
- borderRadius: 1,
306
- overflow: 'auto',
307
- fontSize: '0.8rem',
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
- <Box
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="contained"
335
- startIcon={<ContentCopyIcon />}
262
+ variant="primary"
336
263
  onClick={handleCopy}
337
- disabled={!asset}
264
+ isDisabled={!asset}
338
265
  >
266
+ <RiFileCopyLine size={16} />
339
267
  Copy Markdown
340
268
  </Button>
341
269
 
342
270
  <Button
343
- variant="outlined"
344
- startIcon={<OpenInNewIcon />}
271
+ variant="secondary"
345
272
  onClick={() => asset && window.open(asset.repoUrl, '_blank')}
346
- disabled={!asset}
273
+ isDisabled={!asset}
347
274
  >
275
+ <RiExternalLinkLine size={16} />
348
276
  Open in Repo
349
277
  </Button>
350
- </Box>
351
- </Box>
352
- </Drawer>
278
+ </Flex>
279
+ </Flex>
280
+ </div>
353
281
 
354
- <Snackbar
355
- open={!!snackbar}
356
- autoHideDuration={2500}
357
- onClose={() => setSnackbar(null)}
358
- anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
359
- >
360
- <Alert severity="success" onClose={() => setSnackbar(null)}>
361
- {snackbar}
362
- </Alert>
363
- </Snackbar>
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
- <Box>
371
- <Typography variant="caption" color="text.secondary">
372
- {label}
373
- </Typography>
374
- <Typography variant="body2">{value}</Typography>
375
- </Box>
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
+ }