@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.
Files changed (55) hide show
  1. package/.eslintrc.js +1 -0
  2. package/dev/index.tsx +232 -0
  3. package/dist/api/DevAiHubClient.esm.js +76 -0
  4. package/dist/api/DevAiHubClient.esm.js.map +1 -0
  5. package/dist/components/AssetCard/AssetCard.esm.js +194 -0
  6. package/dist/components/AssetCard/AssetCard.esm.js.map +1 -0
  7. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +324 -0
  8. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +1 -0
  9. package/dist/components/AssetFilters/AssetFilters.esm.js +216 -0
  10. package/dist/components/AssetFilters/AssetFilters.esm.js.map +1 -0
  11. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +194 -0
  12. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +1 -0
  13. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +320 -0
  14. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +1 -0
  15. package/dist/components/DevAiHubPage/index.esm.js +6 -0
  16. package/dist/components/DevAiHubPage/index.esm.js.map +1 -0
  17. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js +274 -0
  18. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js.map +1 -0
  19. package/dist/components/ToolIcon/ToolIcon.esm.js +41 -0
  20. package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -0
  21. package/dist/hooks/index.esm.js +114 -0
  22. package/dist/hooks/index.esm.js.map +1 -0
  23. package/dist/index.d.ts +104 -0
  24. package/dist/index.esm.js +5 -0
  25. package/dist/index.esm.js.map +1 -0
  26. package/dist/plugin.esm.js +40 -0
  27. package/dist/plugin.esm.js.map +1 -0
  28. package/dist/pluginLegacy.esm.js +27 -0
  29. package/dist/pluginLegacy.esm.js.map +1 -0
  30. package/dist/routes.esm.js +6 -0
  31. package/dist/routes.esm.js.map +1 -0
  32. package/package.json +80 -0
  33. package/package.json-prepack +75 -0
  34. package/src/api/DevAiHubClient.ts +110 -0
  35. package/src/components/AssetCard/AssetCard.test.tsx +182 -0
  36. package/src/components/AssetCard/AssetCard.tsx +221 -0
  37. package/src/components/AssetCard/index.ts +1 -0
  38. package/src/components/AssetDetailPanel/AssetDetailPanel.tsx +377 -0
  39. package/src/components/AssetDetailPanel/index.ts +1 -0
  40. package/src/components/AssetFilters/AssetFilters.tsx +261 -0
  41. package/src/components/AssetFilters/index.ts +2 -0
  42. package/src/components/AssetInstallDialog/AssetInstallDialog.tsx +216 -0
  43. package/src/components/AssetInstallDialog/index.ts +1 -0
  44. package/src/components/DevAiHubPage/DevAiHubPage.tsx +346 -0
  45. package/src/components/DevAiHubPage/index.ts +1 -0
  46. package/src/components/McpConfigDialog/McpConfigDialog.tsx +301 -0
  47. package/src/components/McpConfigDialog/index.ts +1 -0
  48. package/src/components/ToolIcon/ToolIcon.tsx +49 -0
  49. package/src/components/ToolIcon/index.ts +1 -0
  50. package/src/hooks/index.ts +152 -0
  51. package/src/index.ts +10 -0
  52. package/src/plugin.tsx +47 -0
  53. package/src/pluginLegacy.ts +42 -0
  54. package/src/routes.ts +3 -0
  55. 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,2 @@
1
+ export { AssetFilters } from './AssetFilters';
2
+ export type { AssetFiltersValue } from './AssetFilters';
@@ -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';