@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,221 @@
|
|
|
1
|
+
import type { ElementType } from 'react';
|
|
2
|
+
import Box from '@mui/material/Box';
|
|
3
|
+
import Card from '@mui/material/Card';
|
|
4
|
+
import CardActions from '@mui/material/CardActions';
|
|
5
|
+
import CardContent from '@mui/material/CardContent';
|
|
6
|
+
import Chip from '@mui/material/Chip';
|
|
7
|
+
import IconButton from '@mui/material/IconButton';
|
|
8
|
+
import Tooltip from '@mui/material/Tooltip';
|
|
9
|
+
import Typography from '@mui/material/Typography';
|
|
10
|
+
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
|
11
|
+
import DownloadIcon from '@mui/icons-material/Download';
|
|
12
|
+
import ArticleIcon from '@mui/icons-material/Article';
|
|
13
|
+
import SmartToyIcon from '@mui/icons-material/SmartToy';
|
|
14
|
+
import BuildIcon from '@mui/icons-material/Build';
|
|
15
|
+
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
|
16
|
+
import type { AiAssetSummary, AssetType, AiTool } from '@nospt/plugin-dev-ai-hub-common';
|
|
17
|
+
import { ToolIcon } from '../ToolIcon';
|
|
18
|
+
|
|
19
|
+
const POPULAR_THRESHOLD = 5;
|
|
20
|
+
const NEW_DAYS_MS = 14 * 24 * 60 * 60 * 1000;
|
|
21
|
+
|
|
22
|
+
const TOOL_LABELS: Record<AiTool, string> = {
|
|
23
|
+
'all': 'Universal',
|
|
24
|
+
'claude-code': 'Claude Code',
|
|
25
|
+
'github-copilot': 'GitHub Copilot',
|
|
26
|
+
'google-gemini': 'Google Gemini',
|
|
27
|
+
'cursor': 'Cursor',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const TYPE_CONFIG: Record<AssetType, { label: string; color: string; bg: string; Icon: ElementType }> = {
|
|
31
|
+
instruction: { label: 'Instruction', color: '#2563EB', bg: '#EFF6FF', Icon: ArticleIcon },
|
|
32
|
+
agent: { label: 'Agent', color: '#7C3AED', bg: '#F5F3FF', Icon: SmartToyIcon },
|
|
33
|
+
skill: { label: 'Skill', color: '#059669', bg: '#ECFDF5', Icon: BuildIcon },
|
|
34
|
+
workflow: { label: 'Workflow', color: '#D97706', bg: '#FFFBEB', Icon: AccountTreeIcon },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
interface AssetCardProps {
|
|
38
|
+
asset: AiAssetSummary;
|
|
39
|
+
onView: (id: string) => void;
|
|
40
|
+
onInstall: (id: string) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function AssetCard({ asset, onView, onInstall }: AssetCardProps) {
|
|
44
|
+
const cfg = TYPE_CONFIG[asset.type];
|
|
45
|
+
const TypeIcon = cfg.Icon;
|
|
46
|
+
const isPopular = asset.installCount >= POPULAR_THRESHOLD;
|
|
47
|
+
const isNew = Date.now() - new Date(asset.updatedAt).getTime() < NEW_DAYS_MS;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Card
|
|
51
|
+
variant="outlined"
|
|
52
|
+
sx={{
|
|
53
|
+
height: '100%',
|
|
54
|
+
display: 'flex',
|
|
55
|
+
flexDirection: 'column',
|
|
56
|
+
borderRadius: 2,
|
|
57
|
+
border: '1px solid',
|
|
58
|
+
borderColor: 'divider',
|
|
59
|
+
borderLeft: `3px solid ${cfg.color}`,
|
|
60
|
+
transition: 'all 0.18s ease',
|
|
61
|
+
'&:hover': {
|
|
62
|
+
boxShadow: `0 6px 24px ${cfg.color}30`,
|
|
63
|
+
borderColor: cfg.color,
|
|
64
|
+
transform: 'translateY(-2px)',
|
|
65
|
+
},
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<CardContent sx={{ p: 1.5, pb: '0 !important', flex: 1 }}>
|
|
69
|
+
{/* Header */}
|
|
70
|
+
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, mb: 1 }}>
|
|
71
|
+
<Box
|
|
72
|
+
sx={{
|
|
73
|
+
width: 36,
|
|
74
|
+
height: 36,
|
|
75
|
+
borderRadius: 1.5,
|
|
76
|
+
backgroundColor: cfg.bg,
|
|
77
|
+
display: 'flex',
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
justifyContent: 'center',
|
|
80
|
+
flexShrink: 0,
|
|
81
|
+
boxShadow: `0 2px 8px ${cfg.color}25`,
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{asset.icon ? (
|
|
85
|
+
<Box
|
|
86
|
+
component="img"
|
|
87
|
+
src={asset.icon}
|
|
88
|
+
alt={asset.label ?? asset.name}
|
|
89
|
+
sx={{ width: 20, height: 20, objectFit: 'contain' }}
|
|
90
|
+
onError={e => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
|
91
|
+
/>
|
|
92
|
+
) : (
|
|
93
|
+
<TypeIcon sx={{ color: cfg.color, fontSize: '1.1rem' }} />
|
|
94
|
+
)}
|
|
95
|
+
</Box>
|
|
96
|
+
|
|
97
|
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
98
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.2 }}>
|
|
99
|
+
<Typography variant="body2" fontWeight={700} noWrap title={asset.label ?? asset.name} sx={{ lineHeight: 1.2, flex: 1 }}>
|
|
100
|
+
{asset.label ?? asset.name}
|
|
101
|
+
</Typography>
|
|
102
|
+
{isNew && (
|
|
103
|
+
<Chip
|
|
104
|
+
label="New"
|
|
105
|
+
size="small"
|
|
106
|
+
sx={{
|
|
107
|
+
height: 16,
|
|
108
|
+
fontSize: '0.58rem',
|
|
109
|
+
fontWeight: 700,
|
|
110
|
+
backgroundColor: '#059669',
|
|
111
|
+
color: '#fff',
|
|
112
|
+
borderRadius: 1,
|
|
113
|
+
flexShrink: 0,
|
|
114
|
+
'& .MuiChip-label': { px: '5px' },
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
)}
|
|
118
|
+
</Box>
|
|
119
|
+
<Typography variant="caption" sx={{ color: cfg.color, fontWeight: 600 }}>
|
|
120
|
+
{cfg.label}
|
|
121
|
+
</Typography>
|
|
122
|
+
</Box>
|
|
123
|
+
</Box>
|
|
124
|
+
|
|
125
|
+
{/* Description */}
|
|
126
|
+
<Typography
|
|
127
|
+
variant="caption"
|
|
128
|
+
color="text.secondary"
|
|
129
|
+
sx={{
|
|
130
|
+
mb: 1,
|
|
131
|
+
display: '-webkit-box',
|
|
132
|
+
WebkitLineClamp: 2,
|
|
133
|
+
WebkitBoxOrient: 'vertical',
|
|
134
|
+
overflow: 'hidden',
|
|
135
|
+
lineHeight: 1.4,
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
{asset.description}
|
|
139
|
+
</Typography>
|
|
140
|
+
|
|
141
|
+
{/* Tools */}
|
|
142
|
+
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mb: asset.tags.length > 0 ? 0.75 : 0 }}>
|
|
143
|
+
{asset.tools.map(tool => (
|
|
144
|
+
<Chip
|
|
145
|
+
key={tool}
|
|
146
|
+
icon={<ToolIcon tool={tool as AiTool} sx={{ fontSize: '0.75rem !important' }} />}
|
|
147
|
+
label={TOOL_LABELS[tool as AiTool] ?? tool}
|
|
148
|
+
size="small"
|
|
149
|
+
sx={{
|
|
150
|
+
height: 18,
|
|
151
|
+
fontSize: '0.65rem',
|
|
152
|
+
fontWeight: 600,
|
|
153
|
+
backgroundColor: 'action.hover',
|
|
154
|
+
color: 'text.secondary',
|
|
155
|
+
borderRadius: 1,
|
|
156
|
+
'& .MuiChip-icon': { ml: '4px' },
|
|
157
|
+
}}
|
|
158
|
+
/>
|
|
159
|
+
))}
|
|
160
|
+
</Box>
|
|
161
|
+
|
|
162
|
+
{/* Tags */}
|
|
163
|
+
{asset.tags.length > 0 && (
|
|
164
|
+
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
|
|
165
|
+
{asset.tags.slice(0, 3).map(tag => (
|
|
166
|
+
<Chip
|
|
167
|
+
key={tag}
|
|
168
|
+
label={`#${tag}`}
|
|
169
|
+
size="small"
|
|
170
|
+
sx={{
|
|
171
|
+
height: 16,
|
|
172
|
+
fontSize: '0.6rem',
|
|
173
|
+
color: 'text.disabled',
|
|
174
|
+
backgroundColor: 'transparent',
|
|
175
|
+
border: '1px solid',
|
|
176
|
+
borderColor: 'divider',
|
|
177
|
+
borderRadius: 1,
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
))}
|
|
181
|
+
{asset.tags.length > 3 && (
|
|
182
|
+
<Typography variant="caption" color="text.disabled" sx={{ alignSelf: 'center' }}>
|
|
183
|
+
+{asset.tags.length - 3}
|
|
184
|
+
</Typography>
|
|
185
|
+
)}
|
|
186
|
+
</Box>
|
|
187
|
+
)}
|
|
188
|
+
</CardContent>
|
|
189
|
+
|
|
190
|
+
<CardActions sx={{ px: 1.5, py: 1, justifyContent: 'space-between', mt: 'auto', borderTop: '1px solid', borderColor: 'divider' }}>
|
|
191
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
192
|
+
<Typography variant="caption" color="text.disabled" sx={{ fontSize: '0.65rem' }}>
|
|
193
|
+
v{asset.version} · {asset.author}
|
|
194
|
+
</Typography>
|
|
195
|
+
{asset.installCount > 0 && (
|
|
196
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
|
|
197
|
+
<Typography sx={{ fontSize: '0.65rem', lineHeight: 1 }}>
|
|
198
|
+
{isPopular ? '🔥' : '↓'}
|
|
199
|
+
</Typography>
|
|
200
|
+
<Typography variant="caption" color="text.disabled" sx={{ fontSize: '0.65rem' }}>
|
|
201
|
+
{asset.installCount}
|
|
202
|
+
</Typography>
|
|
203
|
+
</Box>
|
|
204
|
+
)}
|
|
205
|
+
</Box>
|
|
206
|
+
<Box sx={{ display: 'flex', gap: 0.25 }}>
|
|
207
|
+
<Tooltip title="Install in editor">
|
|
208
|
+
<IconButton size="small" onClick={() => onInstall(asset.id)} color="primary">
|
|
209
|
+
<DownloadIcon fontSize="small" />
|
|
210
|
+
</IconButton>
|
|
211
|
+
</Tooltip>
|
|
212
|
+
<Tooltip title="View details">
|
|
213
|
+
<IconButton size="small" onClick={() => onView(asset.id)}>
|
|
214
|
+
<OpenInNewIcon fontSize="small" />
|
|
215
|
+
</IconButton>
|
|
216
|
+
</Tooltip>
|
|
217
|
+
</Box>
|
|
218
|
+
</CardActions>
|
|
219
|
+
</Card>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AssetCard } from './AssetCard';
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import ReactMarkdown from 'react-markdown';
|
|
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';
|
|
21
|
+
import type { AssetType } from '@nospt/plugin-dev-ai-hub-common';
|
|
22
|
+
import { useAssetDetail } from '../../hooks';
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
|
+
const SyntaxHighlighter = require('react-syntax-highlighter/dist/esm/prism').default;
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
|
+
const { oneLight } = require('react-syntax-highlighter/dist/esm/styles/prism');
|
|
28
|
+
|
|
29
|
+
const TYPE_COLORS: Record<AssetType, string> = {
|
|
30
|
+
instruction: '#1976d2',
|
|
31
|
+
agent: '#7b1fa2',
|
|
32
|
+
skill: '#388e3c',
|
|
33
|
+
workflow: '#f57c00',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
interface AssetDetailPanelProps {
|
|
37
|
+
assetId: string | null;
|
|
38
|
+
onClose: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function AssetDetailPanel({ assetId, onClose }: AssetDetailPanelProps) {
|
|
42
|
+
const [tab, setTab] = useState(0);
|
|
43
|
+
const [snackbar, setSnackbar] = useState<string | null>(null);
|
|
44
|
+
const { asset, loading } = useAssetDetail(assetId);
|
|
45
|
+
|
|
46
|
+
const handleCopy = () => {
|
|
47
|
+
if (!asset) return;
|
|
48
|
+
navigator.clipboard.writeText(asset.content).then(() =>
|
|
49
|
+
setSnackbar('Markdown copied to clipboard!'),
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
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%' }}>
|
|
62
|
+
{/* 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 }}>
|
|
74
|
+
{loading || !asset ? (
|
|
75
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
76
|
+
<CircularProgress size={16} />
|
|
77
|
+
<Typography variant="body2" color="text.secondary">
|
|
78
|
+
Loading...
|
|
79
|
+
</Typography>
|
|
80
|
+
</Box>
|
|
81
|
+
) : (
|
|
82
|
+
<>
|
|
83
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
|
|
84
|
+
<Typography variant="h6" fontWeight={700}>
|
|
85
|
+
{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">
|
|
98
|
+
{asset.description}
|
|
99
|
+
</Typography>
|
|
100
|
+
</>
|
|
101
|
+
)}
|
|
102
|
+
</Box>
|
|
103
|
+
<IconButton onClick={onClose} size="small">
|
|
104
|
+
<CloseIcon />
|
|
105
|
+
</IconButton>
|
|
106
|
+
</Box>
|
|
107
|
+
|
|
108
|
+
{/* 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>
|
|
118
|
+
|
|
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
|
+
)}
|
|
196
|
+
|
|
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} />
|
|
207
|
+
|
|
208
|
+
<Divider />
|
|
209
|
+
|
|
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>
|
|
220
|
+
|
|
221
|
+
{asset.tags.length > 0 && (
|
|
222
|
+
<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>
|
|
231
|
+
</Box>
|
|
232
|
+
)}
|
|
233
|
+
|
|
234
|
+
{asset.type === 'skill' && (
|
|
235
|
+
<>
|
|
236
|
+
<Divider />
|
|
237
|
+
<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
|
+
)}
|
|
275
|
+
</Box>
|
|
276
|
+
</>
|
|
277
|
+
)}
|
|
278
|
+
|
|
279
|
+
<Divider />
|
|
280
|
+
|
|
281
|
+
<Box>
|
|
282
|
+
<Typography variant="caption" color="text.secondary">
|
|
283
|
+
Repository
|
|
284
|
+
</Typography>
|
|
285
|
+
<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>
|
|
294
|
+
</Box>
|
|
295
|
+
</Box>
|
|
296
|
+
</Box>
|
|
297
|
+
)}
|
|
298
|
+
|
|
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>
|
|
321
|
+
|
|
322
|
+
{/* 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
|
+
>
|
|
333
|
+
<Button
|
|
334
|
+
variant="contained"
|
|
335
|
+
startIcon={<ContentCopyIcon />}
|
|
336
|
+
onClick={handleCopy}
|
|
337
|
+
disabled={!asset}
|
|
338
|
+
>
|
|
339
|
+
Copy Markdown
|
|
340
|
+
</Button>
|
|
341
|
+
|
|
342
|
+
<Button
|
|
343
|
+
variant="outlined"
|
|
344
|
+
startIcon={<OpenInNewIcon />}
|
|
345
|
+
onClick={() => asset && window.open(asset.repoUrl, '_blank')}
|
|
346
|
+
disabled={!asset}
|
|
347
|
+
>
|
|
348
|
+
Open in Repo
|
|
349
|
+
</Button>
|
|
350
|
+
</Box>
|
|
351
|
+
</Box>
|
|
352
|
+
</Drawer>
|
|
353
|
+
|
|
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>
|
|
364
|
+
</>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function MetaRow({ label, value }: { label: string; value: string }) {
|
|
369
|
+
return (
|
|
370
|
+
<Box>
|
|
371
|
+
<Typography variant="caption" color="text.secondary">
|
|
372
|
+
{label}
|
|
373
|
+
</Typography>
|
|
374
|
+
<Typography variant="body2">{value}</Typography>
|
|
375
|
+
</Box>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AssetDetailPanel } from './AssetDetailPanel';
|