@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.
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 +22 -4
  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 +21 -1
  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,24 +1,17 @@
1
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';
2
+ import {
3
+ Box, Flex, Text, Button, Tag, TagGroup, Skeleton,
4
+ Dialog, DialogTrigger, DialogHeader, DialogBody, DialogFooter,
5
+ Tooltip, TooltipTrigger,
6
+ } from '@backstage/ui';
7
+ import { RiFileCopyLine, RiDownloadLine, RiCheckLine, RiFolderZipLine } from '@remixicon/react';
16
8
  import type { AiTool } from '@nospt/plugin-dev-ai-hub-common';
17
9
  import { getInstallPathsForAsset } from '@nospt/plugin-dev-ai-hub-common';
18
10
  import { useApi } from '@backstage/core-plugin-api';
19
11
  import { devAiHubApiRef } from '../../api/DevAiHubClient';
20
12
  import { useAssetDetail } from '../../hooks';
21
13
  import { ToolIcon } from '../ToolIcon';
14
+ import styles from './AssetInstallDialog.module.css';
22
15
 
23
16
  const TOOL_LABELS: Record<string, string> = {
24
17
  'claude-code': 'Claude Code',
@@ -85,132 +78,99 @@ export function AssetInstallDialog({ assetId, onClose }: AssetInstallDialogProps
85
78
  : {};
86
79
 
87
80
  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>
81
+ <DialogTrigger>
82
+ <Dialog
83
+ isOpen={!!assetId}
84
+ isDismissable
85
+ onOpenChange={open => { if (!open) handleClose(); }}
86
+ >
87
+ <DialogHeader>
88
+ {asset ? `Install: ${asset.name}` : 'Install'}
89
+ <Text variant="body-small" color="secondary" className={styles.subtitle}>
90
+ Copy the content and place the file at the path shown for your tool.
91
+ </Text>
92
+ </DialogHeader>
95
93
 
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
- )}
94
+ <DialogBody>
95
+ <Flex direction="column" style={{ gap: 'var(--bui-space-3)' }}>
96
+ {loading && (
97
+ <Flex className={styles.loadingContainer}>
98
+ <Skeleton style={{ width: '100%', height: 80 }} />
99
+ </Flex>
100
+ )}
102
101
 
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
- )}
102
+ {!loading && asset && isZipSkill && (
103
+ <Box className={styles.zipInfo}>
104
+ <Flex className={styles.zipHeader}>
105
+ <RiFolderZipLine size={16} style={{ color: 'var(--bui-fg-info)' }} />
106
+ <Text variant="body-small" weight="bold" style={{ color: 'var(--bui-fg-info)' }}>
107
+ Bundled skill — downloads as .zip
108
+ </Text>
109
+ </Flex>
110
+ <Text variant="body-x-small" color="secondary" style={{ display: 'block', marginBottom: 'var(--bui-space-2)' }}>
111
+ This skill includes resource files alongside <code>SKILL.md</code>.
112
+ Extract the zip and place all files in the skill directory.
113
+ </Text>
114
+ <TagGroup aria-label="Bundled files">
115
+ <Flex className={styles.zipFiles}>
116
+ <Tag id="skill-md" size="small" className={styles.monoTag}>SKILL.md</Tag>
117
+ {resourcePaths.map(p => (
118
+ <Tag key={p} id={p} size="small" className={styles.monoTag}>{p}</Tag>
119
+ ))}
120
+ </Flex>
121
+ </TagGroup>
122
+ </Box>
123
+ )}
143
124
 
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>
125
+ {!loading && asset && Object.entries(installPaths).map(([tool, installPath]) => (
126
+ <Box key={tool} className={styles.toolSection}>
127
+ <Flex className={styles.toolHeader}>
128
+ <ToolIcon tool={tool as AiTool} size={16} />
129
+ <Text variant="body-small" weight="bold">
130
+ {TOOL_LABELS[tool] ?? tool}
131
+ </Text>
132
+ </Flex>
160
133
 
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>
134
+ <Text variant="body-x-small" color="secondary" style={{ display: 'block', marginBottom: 'var(--bui-space-1)' }}>
135
+ Install path
136
+ </Text>
137
+ <Box className={styles.installPathBox}>
138
+ {installPath}
139
+ </Box>
181
140
 
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>
141
+ <Flex className={styles.toolActions}>
142
+ <TooltipTrigger>
143
+ <Button
144
+ variant="secondary"
145
+ onClick={() => handleCopy(tool)}
146
+ >
147
+ {copiedTool === tool ? <RiCheckLine size={14} /> : <RiFileCopyLine size={14} />}
148
+ {copiedTool === tool ? 'Copied!' : 'Copy Content'}
149
+ </Button>
150
+ <Tooltip>{copiedTool === tool ? 'Copied!' : 'Copy markdown content'}</Tooltip>
151
+ </TooltipTrigger>
152
+ <TooltipTrigger>
153
+ <Button
154
+ variant="secondary"
155
+ onClick={() => handleDownload(tool, installPath)}
156
+ >
157
+ {isZipSkill ? <RiFolderZipLine size={14} /> : <RiDownloadLine size={14} />}
158
+ {isZipSkill ? 'Download .zip' : 'Download'}
159
+ </Button>
160
+ <Tooltip>{isZipSkill ? 'Download as .zip with all bundled files' : 'Download file with correct name'}</Tooltip>
161
+ </TooltipTrigger>
162
+ </Flex>
163
+ </Box>
164
+ ))}
165
+ </Flex>
166
+ </DialogBody>
210
167
 
211
- <DialogActions>
212
- <Button onClick={handleClose}>Close</Button>
213
- </DialogActions>
214
- </Dialog>
168
+ <DialogFooter>
169
+ <Button onClick={handleClose} variant="secondary" slot="close">
170
+ Close
171
+ </Button>
172
+ </DialogFooter>
173
+ </Dialog>
174
+ </DialogTrigger>
215
175
  );
216
176
  }
@@ -0,0 +1,105 @@
1
+ @layer components {
2
+ .pageRoot {
3
+ display: flex;
4
+ flex-direction: column;
5
+ height: 100%;
6
+ }
7
+
8
+ .content {
9
+ flex: 1;
10
+ padding: var(--bui-space-6);
11
+ overflow-y: auto;
12
+ }
13
+
14
+ .statsGrid {
15
+ display: grid;
16
+ grid-template-columns: repeat(4, 1fr);
17
+ gap: var(--bui-space-4);
18
+ margin-bottom: var(--bui-space-6);
19
+ }
20
+
21
+ @media (max-width: 600px) {
22
+ .statsGrid {
23
+ grid-template-columns: repeat(2, 1fr);
24
+ }
25
+ }
26
+
27
+ .statCard {
28
+ border-radius: var(--bui-radius-3);
29
+ padding: var(--bui-space-4);
30
+ cursor: pointer;
31
+ transition: all 0.2s ease;
32
+ }
33
+
34
+ .statCard:hover {
35
+ transform: translateY(-2px);
36
+ }
37
+
38
+ .statCardInner {
39
+ display: flex;
40
+ justify-content: space-between;
41
+ align-items: center;
42
+ }
43
+
44
+ .statValue {
45
+ color: #fff;
46
+ line-height: 1;
47
+ }
48
+
49
+ .statLabel {
50
+ color: rgba(255, 255, 255, 0.85);
51
+ font-weight: 500;
52
+ margin-top: var(--bui-space-1);
53
+ }
54
+
55
+ .statIconBox {
56
+ width: 40px;
57
+ height: 40px;
58
+ border-radius: var(--bui-radius-2);
59
+ background-color: rgba(255, 255, 255, 0.2);
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ }
64
+
65
+ .assetGrid {
66
+ display: grid;
67
+ grid-template-columns: repeat(4, 1fr);
68
+ gap: var(--bui-space-3);
69
+ }
70
+
71
+ @media (max-width: 1200px) {
72
+ .assetGrid {
73
+ grid-template-columns: repeat(3, 1fr);
74
+ }
75
+ }
76
+
77
+ @media (max-width: 900px) {
78
+ .assetGrid {
79
+ grid-template-columns: repeat(2, 1fr);
80
+ }
81
+ }
82
+
83
+ @media (max-width: 600px) {
84
+ .assetGrid {
85
+ grid-template-columns: 1fr;
86
+ }
87
+ }
88
+
89
+ .emptyState {
90
+ padding: 96px 0;
91
+ text-align: center;
92
+ }
93
+
94
+ .emptyEmoji {
95
+ margin-bottom: var(--bui-space-4);
96
+ opacity: 0.15;
97
+ font-size: 5rem;
98
+ }
99
+
100
+ .paginationRow {
101
+ display: flex;
102
+ justify-content: center;
103
+ margin-top: var(--bui-space-8);
104
+ }
105
+ }