@julianpedro/plugin-dev-ai-hub 0.2.0 → 0.4.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 (50) hide show
  1. package/dist/api/DevAiHubClient.esm.js +15 -2
  2. package/dist/api/DevAiHubClient.esm.js.map +1 -1
  3. package/dist/components/AdminPage/AdminPage.esm.js +106 -0
  4. package/dist/components/AdminPage/AdminPage.esm.js.map +1 -0
  5. package/dist/components/AdminPage/index.esm.js +6 -0
  6. package/dist/components/AdminPage/index.esm.js.map +1 -0
  7. package/dist/components/AssetCard/AssetCard.esm.js +117 -26
  8. package/dist/components/AssetCard/AssetCard.esm.js.map +1 -1
  9. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +271 -87
  10. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +1 -1
  11. package/dist/components/AssetFilters/AssetFilters.esm.js +105 -44
  12. package/dist/components/AssetFilters/AssetFilters.esm.js.map +1 -1
  13. package/dist/components/AssetHelpDialog/AssetHelpDialog.esm.js +87 -0
  14. package/dist/components/AssetHelpDialog/AssetHelpDialog.esm.js.map +1 -0
  15. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +328 -108
  16. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +1 -1
  17. package/dist/components/AssetsTab/AssetsTab.esm.js +221 -0
  18. package/dist/components/AssetsTab/AssetsTab.esm.js.map +1 -0
  19. package/dist/components/AssetsTab/index.esm.js +6 -0
  20. package/dist/components/AssetsTab/index.esm.js.map +1 -0
  21. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +266 -134
  22. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +1 -1
  23. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js +20 -297
  24. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js.map +1 -1
  25. package/dist/components/McpPage/McpPage.esm.js +478 -0
  26. package/dist/components/McpPage/McpPage.esm.js.map +1 -0
  27. package/dist/components/McpPage/index.esm.js +6 -0
  28. package/dist/components/McpPage/index.esm.js.map +1 -0
  29. package/dist/components/ModelIcon/ModelBadge.esm.js +73 -0
  30. package/dist/components/ModelIcon/ModelBadge.esm.js.map +1 -0
  31. package/dist/components/ModelIcon/ModelIcon.esm.js +45 -0
  32. package/dist/components/ModelIcon/ModelIcon.esm.js.map +1 -0
  33. package/dist/components/ToolIcon/ToolIcon.esm.js +4 -1
  34. package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -1
  35. package/dist/context/UiConfigContext.esm.js +79 -0
  36. package/dist/context/UiConfigContext.esm.js.map +1 -0
  37. package/dist/hooks/index.esm.js +36 -1
  38. package/dist/hooks/index.esm.js.map +1 -1
  39. package/dist/index.d.ts +146 -23
  40. package/dist/index.esm.js +1 -0
  41. package/dist/index.esm.js.map +1 -1
  42. package/dist/locales/es.esm.js +121 -0
  43. package/dist/locales/es.esm.js.map +1 -0
  44. package/dist/locales/pt-BR.esm.js +121 -0
  45. package/dist/locales/pt-BR.esm.js.map +1 -0
  46. package/dist/plugin.esm.js +35 -6
  47. package/dist/plugin.esm.js.map +1 -1
  48. package/dist/translation.esm.js +151 -0
  49. package/dist/translation.esm.js.map +1 -0
  50. package/package.json +15 -5
@@ -0,0 +1,87 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import ReactMarkdown from 'react-markdown';
3
+ import remarkGfm from 'remark-gfm';
4
+ import Box from '@mui/material/Box';
5
+ import Button from '@mui/material/Button';
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 IconButton from '@mui/material/IconButton';
11
+ import Typography from '@mui/material/Typography';
12
+ import CloseIcon from '@mui/icons-material/Close';
13
+ import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
14
+ import { useTranslationRef } from '@backstage/frontend-plugin-api';
15
+ import { devAiHubTranslationRef } from '../../translation.esm.js';
16
+
17
+ function AssetHelpDialog({ asset, onClose }) {
18
+ const { t } = useTranslationRef(devAiHubTranslationRef);
19
+ return /* @__PURE__ */ jsxs(Dialog, { open: !!asset, onClose, maxWidth: "md", fullWidth: true, children: [
20
+ /* @__PURE__ */ jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1, pr: 6 }, children: [
21
+ /* @__PURE__ */ jsx(HelpOutlineIcon, { sx: { color: "text.secondary", fontSize: "1.3rem", flexShrink: 0 } }),
22
+ /* @__PURE__ */ jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [
23
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", fontWeight: 700, noWrap: true, children: asset?.label ?? asset?.name ?? "" }),
24
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { fontWeight: 400 }, children: t("assetCard.helpTooltip") })
25
+ ] }),
26
+ /* @__PURE__ */ jsx(
27
+ IconButton,
28
+ {
29
+ size: "small",
30
+ onClick: onClose,
31
+ sx: { position: "absolute", right: 12, top: 12 },
32
+ children: /* @__PURE__ */ jsx(CloseIcon, { fontSize: "small" })
33
+ }
34
+ )
35
+ ] }),
36
+ /* @__PURE__ */ jsx(DialogContent, { dividers: true, children: /* @__PURE__ */ jsx(
37
+ Box,
38
+ {
39
+ sx: {
40
+ "& h1,h2,h3,h4,h5,h6": { mt: 2, mb: 1, fontWeight: 700 },
41
+ "& h1": { mt: 0 },
42
+ "& p": { mb: 1, lineHeight: 1.7 },
43
+ "& ul, & ol": { pl: 2.5, mb: 1 },
44
+ "& li": { mb: 0.5 },
45
+ "& blockquote": {
46
+ borderLeft: "3px solid",
47
+ borderColor: "primary.main",
48
+ pl: 1.5,
49
+ color: "text.secondary",
50
+ my: 1,
51
+ ml: 0
52
+ },
53
+ "& table": { width: "100%", borderCollapse: "collapse", mb: 1 },
54
+ "& th, & td": {
55
+ border: "1px solid",
56
+ borderColor: "divider",
57
+ px: 1.5,
58
+ py: 0.75,
59
+ fontSize: "0.875rem"
60
+ },
61
+ "& th": { backgroundColor: "action.hover", fontWeight: 700 },
62
+ "& code": {
63
+ bgcolor: "action.hover",
64
+ px: 0.5,
65
+ py: 0.2,
66
+ borderRadius: 0.5,
67
+ fontFamily: "monospace",
68
+ fontSize: "0.875em"
69
+ },
70
+ "& pre": {
71
+ bgcolor: "action.hover",
72
+ borderRadius: 1,
73
+ p: 1.5,
74
+ overflow: "auto",
75
+ my: 1,
76
+ "& code": { bgcolor: "transparent", px: 0, py: 0 }
77
+ }
78
+ },
79
+ children: /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: asset?.helpText ?? "" })
80
+ }
81
+ ) }),
82
+ /* @__PURE__ */ jsx(DialogActions, { sx: { px: 2, py: 1.5 }, children: /* @__PURE__ */ jsx(Button, { onClick: onClose, sx: { ml: "auto" }, children: t("assetInstallDialog.close") }) })
83
+ ] });
84
+ }
85
+
86
+ export { AssetHelpDialog };
87
+ //# sourceMappingURL=AssetHelpDialog.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssetHelpDialog.esm.js","sources":["../../../src/components/AssetHelpDialog/AssetHelpDialog.tsx"],"sourcesContent":["import ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport IconButton from '@mui/material/IconButton';\nimport Typography from '@mui/material/Typography';\nimport CloseIcon from '@mui/icons-material/Close';\nimport HelpOutlineIcon from '@mui/icons-material/HelpOutline';\nimport { useTranslationRef } from '@backstage/frontend-plugin-api';\nimport type { AiAssetSummary } from '@julianpedro/plugin-dev-ai-hub-common';\nimport { devAiHubTranslationRef } from '../../translation';\n\ninterface AssetHelpDialogProps {\n asset: AiAssetSummary | null;\n onClose: () => void;\n}\n\nexport function AssetHelpDialog({ asset, onClose }: AssetHelpDialogProps) {\n const { t } = useTranslationRef(devAiHubTranslationRef);\n\n return (\n <Dialog open={!!asset} onClose={onClose} maxWidth=\"md\" fullWidth>\n <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1, pr: 6 }}>\n <HelpOutlineIcon sx={{ color: 'text.secondary', fontSize: '1.3rem', flexShrink: 0 }} />\n <Box sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"h6\" fontWeight={700} noWrap>\n {asset?.label ?? asset?.name ?? ''}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ fontWeight: 400 }}>\n {t('assetCard.helpTooltip')}\n </Typography>\n </Box>\n <IconButton\n size=\"small\"\n onClick={onClose}\n sx={{ position: 'absolute', right: 12, top: 12 }}\n >\n <CloseIcon fontSize=\"small\" />\n </IconButton>\n </DialogTitle>\n\n <DialogContent dividers>\n <Box\n sx={{\n '& h1,h2,h3,h4,h5,h6': { mt: 2, mb: 1, fontWeight: 700 },\n '& h1': { mt: 0 },\n '& p': { mb: 1, lineHeight: 1.7 },\n '& ul, & ol': { pl: 2.5, mb: 1 },\n '& li': { mb: 0.5 },\n '& blockquote': {\n borderLeft: '3px solid',\n borderColor: 'primary.main',\n pl: 1.5,\n color: 'text.secondary',\n my: 1,\n ml: 0,\n },\n '& table': { width: '100%', borderCollapse: 'collapse', mb: 1 },\n '& th, & td': {\n border: '1px solid',\n borderColor: 'divider',\n px: 1.5,\n py: 0.75,\n fontSize: '0.875rem',\n },\n '& th': { backgroundColor: 'action.hover', fontWeight: 700 },\n '& code': {\n bgcolor: 'action.hover',\n px: 0.5,\n py: 0.2,\n borderRadius: 0.5,\n fontFamily: 'monospace',\n fontSize: '0.875em',\n },\n '& pre': {\n bgcolor: 'action.hover',\n borderRadius: 1,\n p: 1.5,\n overflow: 'auto',\n my: 1,\n '& code': { bgcolor: 'transparent', px: 0, py: 0 },\n },\n }}\n >\n <ReactMarkdown remarkPlugins={[remarkGfm]}>\n {asset?.helpText ?? ''}\n </ReactMarkdown>\n </Box>\n </DialogContent>\n\n <DialogActions sx={{ px: 2, py: 1.5 }}>\n <Button onClick={onClose} sx={{ ml: 'auto' }}>\n {t('assetInstallDialog.close')}\n </Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAqBO,SAAS,eAAA,CAAgB,EAAE,KAAA,EAAO,OAAA,EAAQ,EAAyB;AACxE,EAAA,MAAM,EAAE,CAAA,EAAE,GAAI,iBAAA,CAAkB,sBAAsB,CAAA;AAEtD,EAAA,uBACE,IAAA,CAAC,MAAA,EAAA,EAAO,IAAA,EAAM,CAAC,CAAC,OAAO,OAAA,EAAkB,QAAA,EAAS,IAAA,EAAK,SAAA,EAAS,IAAA,EAC9D,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,WAAA,EAAA,EAAY,EAAA,EAAI,EAAE,OAAA,EAAS,MAAA,EAAQ,UAAA,EAAY,QAAA,EAAU,GAAA,EAAK,CAAA,EAAG,EAAA,EAAI,CAAA,EAAE,EACtE,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,eAAA,EAAA,EAAgB,IAAI,EAAE,KAAA,EAAO,kBAAkB,QAAA,EAAU,QAAA,EAAU,UAAA,EAAY,CAAA,EAAE,EAAG,CAAA;AAAA,sBACrF,IAAA,CAAC,OAAI,EAAA,EAAI,EAAE,MAAM,CAAA,EAAG,QAAA,EAAU,GAAE,EAC9B,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,UAAA,EAAA,EAAW,OAAA,EAAQ,IAAA,EAAK,UAAA,EAAY,GAAA,EAAK,MAAA,EAAM,IAAA,EAC7C,QAAA,EAAA,KAAA,EAAO,KAAA,IAAS,KAAA,EAAO,IAAA,IAAQ,EAAA,EAClC,CAAA;AAAA,wBACA,GAAA,CAAC,UAAA,EAAA,EAAW,OAAA,EAAQ,OAAA,EAAQ,KAAA,EAAM,gBAAA,EAAiB,EAAA,EAAI,EAAE,UAAA,EAAY,GAAA,EAAI,EACtE,QAAA,EAAA,CAAA,CAAE,uBAAuB,CAAA,EAC5B;AAAA,OAAA,EACF,CAAA;AAAA,sBACA,GAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,OAAA;AAAA,UACL,OAAA,EAAS,OAAA;AAAA,UACT,IAAI,EAAE,QAAA,EAAU,YAAY,KAAA,EAAO,EAAA,EAAI,KAAK,EAAA,EAAG;AAAA,UAE/C,QAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAU,QAAA,EAAS,OAAA,EAAQ;AAAA;AAAA;AAC9B,KAAA,EACF,CAAA;AAAA,oBAEA,GAAA,CAAC,aAAA,EAAA,EAAc,QAAA,EAAQ,IAAA,EACrB,QAAA,kBAAA,GAAA;AAAA,MAAC,GAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI;AAAA,UACF,uBAAuB,EAAE,EAAA,EAAI,GAAG,EAAA,EAAI,CAAA,EAAG,YAAY,GAAA,EAAI;AAAA,UACvD,MAAA,EAAQ,EAAE,EAAA,EAAI,CAAA,EAAE;AAAA,UAChB,KAAA,EAAO,EAAE,EAAA,EAAI,CAAA,EAAG,YAAY,GAAA,EAAI;AAAA,UAChC,YAAA,EAAc,EAAE,EAAA,EAAI,GAAA,EAAK,IAAI,CAAA,EAAE;AAAA,UAC/B,MAAA,EAAQ,EAAE,EAAA,EAAI,GAAA,EAAI;AAAA,UAClB,cAAA,EAAgB;AAAA,YACd,UAAA,EAAY,WAAA;AAAA,YACZ,WAAA,EAAa,cAAA;AAAA,YACb,EAAA,EAAI,GAAA;AAAA,YACJ,KAAA,EAAO,gBAAA;AAAA,YACP,EAAA,EAAI,CAAA;AAAA,YACJ,EAAA,EAAI;AAAA,WACN;AAAA,UACA,WAAW,EAAE,KAAA,EAAO,QAAQ,cAAA,EAAgB,UAAA,EAAY,IAAI,CAAA,EAAE;AAAA,UAC9D,YAAA,EAAc;AAAA,YACZ,MAAA,EAAQ,WAAA;AAAA,YACR,WAAA,EAAa,SAAA;AAAA,YACb,EAAA,EAAI,GAAA;AAAA,YACJ,EAAA,EAAI,IAAA;AAAA,YACJ,QAAA,EAAU;AAAA,WACZ;AAAA,UACA,MAAA,EAAQ,EAAE,eAAA,EAAiB,cAAA,EAAgB,YAAY,GAAA,EAAI;AAAA,UAC3D,QAAA,EAAU;AAAA,YACR,OAAA,EAAS,cAAA;AAAA,YACT,EAAA,EAAI,GAAA;AAAA,YACJ,EAAA,EAAI,GAAA;AAAA,YACJ,YAAA,EAAc,GAAA;AAAA,YACd,UAAA,EAAY,WAAA;AAAA,YACZ,QAAA,EAAU;AAAA,WACZ;AAAA,UACA,OAAA,EAAS;AAAA,YACP,OAAA,EAAS,cAAA;AAAA,YACT,YAAA,EAAc,CAAA;AAAA,YACd,CAAA,EAAG,GAAA;AAAA,YACH,QAAA,EAAU,MAAA;AAAA,YACV,EAAA,EAAI,CAAA;AAAA,YACJ,UAAU,EAAE,OAAA,EAAS,eAAe,EAAA,EAAI,CAAA,EAAG,IAAI,CAAA;AAAE;AACnD,SACF;AAAA,QAEA,QAAA,kBAAA,GAAA,CAAC,iBAAc,aAAA,EAAe,CAAC,SAAS,CAAA,EACrC,QAAA,EAAA,KAAA,EAAO,YAAY,EAAA,EACtB;AAAA;AAAA,KACF,EACF,CAAA;AAAA,oBAEA,GAAA,CAAC,iBAAc,EAAA,EAAI,EAAE,IAAI,CAAA,EAAG,EAAA,EAAI,KAAI,EAClC,QAAA,kBAAA,GAAA,CAAC,UAAO,OAAA,EAAS,OAAA,EAAS,IAAI,EAAE,EAAA,EAAI,QAAO,EACxC,QAAA,EAAA,CAAA,CAAE,0BAA0B,CAAA,EAC/B,CAAA,EACF;AAAA,GAAA,EACF,CAAA;AAEJ;;;;"}
@@ -1,24 +1,31 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { useState } from 'react';
3
3
  import Box from '@mui/material/Box';
4
- import Chip from '@mui/material/Chip';
5
4
  import Button from '@mui/material/Button';
5
+ import Chip from '@mui/material/Chip';
6
6
  import CircularProgress from '@mui/material/CircularProgress';
7
7
  import Dialog from '@mui/material/Dialog';
8
8
  import DialogActions from '@mui/material/DialogActions';
9
9
  import DialogContent from '@mui/material/DialogContent';
10
10
  import DialogTitle from '@mui/material/DialogTitle';
11
+ import LinearProgress from '@mui/material/LinearProgress';
11
12
  import Tooltip from '@mui/material/Tooltip';
12
13
  import Typography from '@mui/material/Typography';
13
14
  import ContentCopyIcon from '@mui/icons-material/ContentCopy';
14
15
  import DownloadIcon from '@mui/icons-material/Download';
15
16
  import CheckIcon from '@mui/icons-material/Check';
16
17
  import FolderZipIcon from '@mui/icons-material/FolderZip';
18
+ import OpenInBrowserIcon from '@mui/icons-material/OpenInBrowser';
19
+ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
20
+ import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
21
+ import Inventory2Icon from '@mui/icons-material/Inventory2';
17
22
  import { getInstallPathsForAsset } from '@julianpedro/plugin-dev-ai-hub-common';
18
- import { useApi } from '@backstage/core-plugin-api';
23
+ import { useApi, discoveryApiRef } from '@backstage/core-plugin-api';
24
+ import { useTranslationRef } from '@backstage/frontend-plugin-api';
19
25
  import { devAiHubApiRef } from '../../api/DevAiHubClient.esm.js';
20
26
  import { useAssetDetail } from '../../hooks/index.esm.js';
21
27
  import { ToolIcon } from '../ToolIcon/ToolIcon.esm.js';
28
+ import { devAiHubTranslationRef } from '../../translation.esm.js';
22
29
 
23
30
  const TOOL_LABELS = {
24
31
  "claude-code": "Claude Code",
@@ -26,16 +33,31 @@ const TOOL_LABELS = {
26
33
  "google-gemini": "Google Gemini",
27
34
  "cursor": "Cursor"
28
35
  };
29
- function AssetInstallDialog({ assetId, onClose }) {
36
+ function canUseVscodeDeepLink(type, tools, installPath) {
37
+ if (type !== "agent" && type !== "instruction") return false;
38
+ if (!tools.includes("github-copilot")) return false;
39
+ if (installPath && !installPath.startsWith(".github/")) return false;
40
+ return true;
41
+ }
42
+ function buildVscodeRedirectUrl(asset, backendUrl) {
43
+ const safeName = (asset.label ?? asset.name).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
44
+ const filename = asset.type === "instruction" ? `${safeName}.instructions.md` : `${safeName}.agent.md`;
45
+ const fileUrl = `${backendUrl}/assets/${encodeURIComponent(asset.id)}/agent-md/${filename}`;
46
+ const vscodeUri = `vscode:chat-agent/install?url=${encodeURIComponent(fileUrl)}`;
47
+ return `https://vscode.dev/redirect?url=${encodeURIComponent(vscodeUri)}`;
48
+ }
49
+ function SingleAssetView({ asset }) {
30
50
  const [copiedTool, setCopiedTool] = useState(null);
31
51
  const api = useApi(devAiHubApiRef);
32
- const { asset, loading } = useAssetDetail(assetId);
33
- const handleClose = () => {
34
- onClose();
35
- setCopiedTool(null);
36
- };
52
+ const discoveryApi = useApi(discoveryApiRef);
53
+ const { t } = useTranslationRef(devAiHubTranslationRef);
54
+ const resourcePaths = asset.resourcesContent ? Object.keys(asset.resourcesContent) : [];
55
+ const isZipSkill = asset.type === "skill" && resourcePaths.length > 0;
56
+ const installPaths = getInstallPathsForAsset(asset.type, asset.tools, asset.name, {
57
+ installPath: asset.installPath,
58
+ installPaths: asset.installPaths
59
+ });
37
60
  const handleCopy = (tool) => {
38
- if (!asset) return;
39
61
  navigator.clipboard.writeText(asset.content).then(() => {
40
62
  setCopiedTool(tool);
41
63
  setTimeout(() => setCopiedTool(null), 2e3);
@@ -43,10 +65,7 @@ function AssetInstallDialog({ assetId, onClose }) {
43
65
  api.trackInstall(asset.id).catch(() => {
44
66
  });
45
67
  };
46
- const resourcePaths = asset?.resourcesContent ? Object.keys(asset.resourcesContent) : [];
47
- const isZipSkill = asset?.type === "skill" && resourcePaths.length > 0;
48
68
  const handleDownload = async (_tool, installPath) => {
49
- if (!asset) return;
50
69
  if (isZipSkill) {
51
70
  const url = await api.getDownloadUrl(asset.id);
52
71
  const a = document.createElement("a");
@@ -66,98 +85,86 @@ function AssetInstallDialog({ assetId, onClose }) {
66
85
  api.trackInstall(asset.id).catch(() => {
67
86
  });
68
87
  };
69
- const installPaths = asset ? getInstallPathsForAsset(asset.type, asset.tools, asset.name, {
70
- installPath: asset.installPath,
71
- installPaths: asset.installPaths
72
- }) : {};
73
- return /* @__PURE__ */ jsxs(Dialog, { open: !!assetId, onClose: handleClose, maxWidth: "sm", fullWidth: true, children: [
74
- /* @__PURE__ */ jsxs(DialogTitle, { sx: { pb: 1 }, children: [
75
- asset ? `Install: ${asset.name}` : "Install",
76
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mt: 0.5, fontWeight: 400 }, children: "Copy the content and place the file at the path shown for your tool." })
77
- ] }),
78
- /* @__PURE__ */ jsxs(DialogContent, { sx: { display: "flex", flexDirection: "column", gap: 1.5, pt: "8px !important" }, children: [
79
- loading && /* @__PURE__ */ jsx(Box, { sx: { display: "flex", justifyContent: "center", py: 3 }, children: /* @__PURE__ */ jsx(CircularProgress, {}) }),
80
- !loading && asset && isZipSkill && /* @__PURE__ */ jsxs(
81
- Box,
82
- {
83
- sx: {
84
- border: "1px solid",
85
- borderColor: "info.main",
86
- borderRadius: 2,
87
- p: 1.5,
88
- backgroundColor: "info.main",
89
- backgroundImage: "none",
90
- bgcolor: (theme) => `${theme.palette.info.main}12`
91
- },
92
- children: [
93
- /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1 }, children: [
94
- /* @__PURE__ */ jsx(FolderZipIcon, { sx: { fontSize: "1rem", color: "info.main" } }),
95
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", fontWeight: 700, color: "info.main", children: "Bundled skill \u2014 downloads as .zip" })
96
- ] }),
97
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "text.secondary", sx: { display: "block", mb: 1 }, children: [
98
- "This skill includes resource files alongside ",
99
- /* @__PURE__ */ jsx("code", { children: "SKILL.md" }),
100
- ". Extract the zip and place all files in the skill directory."
101
- ] }),
102
- /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" }, children: [
103
- /* @__PURE__ */ jsx(
104
- Chip,
105
- {
106
- label: "SKILL.md",
107
- size: "small",
108
- sx: { fontFamily: "monospace", fontSize: "0.7rem", height: 20 }
109
- }
110
- ),
111
- resourcePaths.map((p) => /* @__PURE__ */ jsx(
112
- Chip,
113
- {
114
- label: p,
115
- size: "small",
116
- variant: "outlined",
117
- sx: { fontFamily: "monospace", fontSize: "0.7rem", height: 20 }
118
- },
119
- p
120
- ))
121
- ] })
122
- ]
123
- }
124
- ),
125
- !loading && asset && Object.entries(installPaths).map(([tool, installPath]) => /* @__PURE__ */ jsxs(
126
- Box,
127
- {
128
- sx: {
129
- border: "1px solid",
130
- borderColor: "divider",
131
- borderRadius: 2,
132
- p: 1.5
133
- },
134
- children: [
135
- /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1 }, children: [
136
- /* @__PURE__ */ jsx(ToolIcon, { tool, sx: { fontSize: "1rem" } }),
137
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", fontWeight: 700, children: TOOL_LABELS[tool] ?? tool })
138
- ] }),
139
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { display: "block", mb: 0.5 }, children: "Install path" }),
140
- /* @__PURE__ */ jsx(
141
- Box,
88
+ const handleInstallInVscode = async () => {
89
+ const backendUrl = await discoveryApi.getBaseUrl("dev-ai-hub");
90
+ const a = document.createElement("a");
91
+ a.href = buildVscodeRedirectUrl(asset, backendUrl);
92
+ a.target = "_blank";
93
+ a.rel = "noopener noreferrer";
94
+ document.body.appendChild(a);
95
+ a.click();
96
+ document.body.removeChild(a);
97
+ api.trackInstall(asset.id).catch(() => {
98
+ });
99
+ };
100
+ const showVscodeButton = canUseVscodeDeepLink(asset.type, asset.tools, installPaths["github-copilot"]);
101
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
102
+ isZipSkill && /* @__PURE__ */ jsxs(
103
+ Box,
104
+ {
105
+ sx: {
106
+ border: "1px solid",
107
+ borderColor: "info.main",
108
+ borderRadius: 2,
109
+ p: 1.5,
110
+ bgcolor: (theme) => `${theme.palette.info.main}12`,
111
+ mb: 0.5
112
+ },
113
+ children: [
114
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1 }, children: [
115
+ /* @__PURE__ */ jsx(FolderZipIcon, { sx: { fontSize: "1rem", color: "info.main" } }),
116
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", fontWeight: 700, color: "info.main", children: t("assetInstallDialog.bundledSkillTitle") })
117
+ ] }),
118
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { display: "block", mb: 1 }, children: t("assetInstallDialog.bundledSkillDescription") }),
119
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" }, children: [
120
+ /* @__PURE__ */ jsx(Chip, { label: "SKILL.md", size: "small", sx: { fontFamily: "monospace", fontSize: "0.7rem", height: 20 } }),
121
+ resourcePaths.map((p) => /* @__PURE__ */ jsx(Chip, { label: p, size: "small", variant: "outlined", sx: { fontFamily: "monospace", fontSize: "0.7rem", height: 20 } }, p))
122
+ ] })
123
+ ]
124
+ }
125
+ ),
126
+ Object.entries(installPaths).map(([tool, installPath]) => /* @__PURE__ */ jsxs(
127
+ Box,
128
+ {
129
+ sx: { border: "1px solid", borderColor: "divider", borderRadius: 2, p: 1.5 },
130
+ children: [
131
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1 }, children: [
132
+ /* @__PURE__ */ jsx(ToolIcon, { tool, sx: { fontSize: "1rem" } }),
133
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", fontWeight: 700, children: TOOL_LABELS[tool] ?? tool })
134
+ ] }),
135
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { display: "block", mb: 0.5 }, children: t("assetInstallDialog.installPathLabel") }),
136
+ /* @__PURE__ */ jsx(
137
+ Box,
138
+ {
139
+ sx: {
140
+ bgcolor: "action.hover",
141
+ border: "1px solid",
142
+ borderColor: "divider",
143
+ borderRadius: 1,
144
+ px: 1.5,
145
+ py: 0.75,
146
+ fontFamily: "monospace",
147
+ fontSize: "0.78rem",
148
+ wordBreak: "break-all",
149
+ mb: 1.25
150
+ },
151
+ children: installPath
152
+ }
153
+ ),
154
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 0.75 }, children: [
155
+ showVscodeButton && tool === "github-copilot" && /* @__PURE__ */ jsx(
156
+ Button,
142
157
  {
143
- sx: {
144
- bgcolor: "action.hover",
145
- border: "1px solid",
146
- borderColor: "divider",
147
- borderRadius: 1,
148
- px: 1.5,
149
- py: 0.75,
150
- fontFamily: "monospace",
151
- fontSize: "0.78rem",
152
- color: "text.primary",
153
- wordBreak: "break-all",
154
- mb: 1.25
155
- },
156
- children: installPath
158
+ size: "small",
159
+ variant: "contained",
160
+ startIcon: /* @__PURE__ */ jsx(OpenInBrowserIcon, {}),
161
+ onClick: handleInstallInVscode,
162
+ fullWidth: true,
163
+ children: t("assetInstallDialog.installInVscode")
157
164
  }
158
165
  ),
159
166
  /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 1 }, children: [
160
- /* @__PURE__ */ jsx(Tooltip, { title: copiedTool === tool ? "Copied!" : "Copy markdown content", children: /* @__PURE__ */ jsx(
167
+ /* @__PURE__ */ jsx(Tooltip, { title: copiedTool === tool ? t("assetInstallDialog.copied") : t("assetInstallDialog.copyTooltip"), children: /* @__PURE__ */ jsx(
161
168
  Button,
162
169
  {
163
170
  size: "small",
@@ -166,10 +173,10 @@ function AssetInstallDialog({ assetId, onClose }) {
166
173
  onClick: () => handleCopy(tool),
167
174
  color: copiedTool === tool ? "success" : "primary",
168
175
  sx: { flex: 1 },
169
- children: copiedTool === tool ? "Copied!" : "Copy Content"
176
+ children: copiedTool === tool ? t("assetInstallDialog.copied") : t("assetInstallDialog.copyContent")
170
177
  }
171
178
  ) }),
172
- /* @__PURE__ */ jsx(Tooltip, { title: isZipSkill ? "Download as .zip with all bundled files" : "Download file with correct name", children: /* @__PURE__ */ jsx(
179
+ /* @__PURE__ */ jsx(Tooltip, { title: isZipSkill ? t("assetInstallDialog.downloadZipTooltip") : t("assetInstallDialog.downloadTooltip"), children: /* @__PURE__ */ jsx(
173
180
  Button,
174
181
  {
175
182
  size: "small",
@@ -177,16 +184,229 @@ function AssetInstallDialog({ assetId, onClose }) {
177
184
  startIcon: isZipSkill ? /* @__PURE__ */ jsx(FolderZipIcon, {}) : /* @__PURE__ */ jsx(DownloadIcon, {}),
178
185
  onClick: () => handleDownload(tool, installPath),
179
186
  sx: { flex: 1 },
180
- children: isZipSkill ? "Download .zip" : "Download"
187
+ children: isZipSkill ? t("assetInstallDialog.downloadZip") : t("assetInstallDialog.download")
181
188
  }
182
189
  ) })
183
190
  ] })
184
- ]
191
+ ] })
192
+ ]
193
+ },
194
+ tool
195
+ ))
196
+ ] });
197
+ }
198
+ function BundleItemStep({ item, stepIndex, total }) {
199
+ const [copied, setCopied] = useState(false);
200
+ const api = useApi(devAiHubApiRef);
201
+ const discoveryApi = useApi(discoveryApiRef);
202
+ const { asset, loading } = useAssetDetail(item.assetId ?? null);
203
+ const { t } = useTranslationRef(devAiHubTranslationRef);
204
+ const installPaths = asset ? getInstallPathsForAsset(asset.type, asset.tools, asset.name, {
205
+ installPath: asset.installPath,
206
+ installPaths: asset.installPaths
207
+ }) : {};
208
+ const firstInstallPath = Object.values(installPaths)[0];
209
+ const handleCopy = () => {
210
+ if (!asset) return;
211
+ navigator.clipboard.writeText(asset.content).then(() => {
212
+ setCopied(true);
213
+ setTimeout(() => setCopied(false), 2e3);
214
+ });
215
+ api.trackInstall(asset.id).catch(() => {
216
+ });
217
+ };
218
+ const handleDownload = async () => {
219
+ if (!asset) return;
220
+ const filename = firstInstallPath?.split("/").pop() ?? `${asset.name}.md`;
221
+ const blob = new Blob([asset.content], { type: "text/markdown" });
222
+ const url = URL.createObjectURL(blob);
223
+ const a = document.createElement("a");
224
+ a.href = url;
225
+ a.download = filename;
226
+ a.click();
227
+ URL.revokeObjectURL(url);
228
+ api.trackInstall(asset.id).catch(() => {
229
+ });
230
+ };
231
+ const handleInstallInVscode = async () => {
232
+ if (!asset) return;
233
+ const backendUrl = await discoveryApi.getBaseUrl("dev-ai-hub");
234
+ const a = document.createElement("a");
235
+ a.href = buildVscodeRedirectUrl(asset, backendUrl);
236
+ a.target = "_blank";
237
+ a.rel = "noopener noreferrer";
238
+ document.body.appendChild(a);
239
+ a.click();
240
+ document.body.removeChild(a);
241
+ api.trackInstall(asset.id).catch(() => {
242
+ });
243
+ };
244
+ const showVscodeButton = asset && canUseVscodeDeepLink(asset.type, asset.tools, installPaths["github-copilot"]);
245
+ return /* @__PURE__ */ jsxs(Box, { children: [
246
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1.5 }, children: [
247
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", children: t("assetInstallDialog.stepProgress", { current: String(stepIndex + 1), total: String(total) }) }),
248
+ item.type && /* @__PURE__ */ jsx(Chip, { label: item.type, size: "small", variant: "outlined", sx: { height: 20, fontSize: "0.65rem" } })
249
+ ] }),
250
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", fontWeight: 700, sx: { mb: 0.25 }, children: item.label ?? item.name ?? item.ref }),
251
+ item.description && /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 1.5 }, children: item.description }),
252
+ loading && /* @__PURE__ */ jsx(Box, { sx: { display: "flex", justifyContent: "center", py: 2 }, children: /* @__PURE__ */ jsx(CircularProgress, { size: 24 }) }),
253
+ !loading && !item.assetId && /* @__PURE__ */ jsxs(
254
+ Box,
255
+ {
256
+ sx: {
257
+ border: "1px dashed",
258
+ borderColor: "warning.main",
259
+ borderRadius: 2,
260
+ p: 1.5,
261
+ bgcolor: (theme) => `${theme.palette.warning.main}10`
185
262
  },
186
- tool
187
- ))
263
+ children: [
264
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "warning.main", fontWeight: 600, children: t("assetInstallDialog.notSyncedTitle") }),
265
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", children: t("assetInstallDialog.notSyncedRef", { ref: item.ref }) })
266
+ ]
267
+ }
268
+ ),
269
+ !loading && asset && /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 1 }, children: [
270
+ firstInstallPath && /* @__PURE__ */ jsx(
271
+ Box,
272
+ {
273
+ sx: {
274
+ bgcolor: "action.hover",
275
+ border: "1px solid",
276
+ borderColor: "divider",
277
+ borderRadius: 1,
278
+ px: 1.5,
279
+ py: 0.75,
280
+ fontFamily: "monospace",
281
+ fontSize: "0.78rem",
282
+ wordBreak: "break-all"
283
+ },
284
+ children: firstInstallPath
285
+ }
286
+ ),
287
+ showVscodeButton && /* @__PURE__ */ jsx(
288
+ Button,
289
+ {
290
+ size: "small",
291
+ variant: "contained",
292
+ startIcon: /* @__PURE__ */ jsx(OpenInBrowserIcon, {}),
293
+ onClick: handleInstallInVscode,
294
+ fullWidth: true,
295
+ children: t("assetInstallDialog.installInVscode")
296
+ }
297
+ ),
298
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 1 }, children: [
299
+ /* @__PURE__ */ jsx(
300
+ Button,
301
+ {
302
+ size: "small",
303
+ variant: "outlined",
304
+ startIcon: copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(ContentCopyIcon, {}),
305
+ onClick: handleCopy,
306
+ color: copied ? "success" : "primary",
307
+ sx: { flex: 1 },
308
+ children: copied ? t("assetInstallDialog.copied") : t("assetInstallDialog.copyContent")
309
+ }
310
+ ),
311
+ /* @__PURE__ */ jsx(
312
+ Button,
313
+ {
314
+ size: "small",
315
+ variant: "outlined",
316
+ startIcon: /* @__PURE__ */ jsx(DownloadIcon, {}),
317
+ onClick: handleDownload,
318
+ sx: { flex: 1 },
319
+ children: t("assetInstallDialog.download")
320
+ }
321
+ )
322
+ ] })
323
+ ] })
324
+ ] });
325
+ }
326
+ function AssetInstallDialog({ assetId, onClose }) {
327
+ const [step, setStep] = useState(0);
328
+ const api = useApi(devAiHubApiRef);
329
+ const { asset, loading } = useAssetDetail(assetId);
330
+ const { t } = useTranslationRef(devAiHubTranslationRef);
331
+ const handleClose = () => {
332
+ setStep(0);
333
+ onClose();
334
+ };
335
+ const handleDownloadBundle = async () => {
336
+ if (!asset) return;
337
+ const url = await api.getDownloadUrl(asset.id);
338
+ const a = document.createElement("a");
339
+ a.href = url;
340
+ a.download = `${asset.name.replace(/\s+/g, "-").toLowerCase()}-bundle.zip`;
341
+ a.click();
342
+ api.trackInstall(asset.id).catch(() => {
343
+ });
344
+ };
345
+ const isBundle = asset?.type === "bundle";
346
+ const bundleItems = isBundle ? asset.items ?? [] : [];
347
+ const totalSteps = bundleItems.length;
348
+ const progress = totalSteps > 0 ? (step + 1) / totalSteps * 100 : 0;
349
+ return /* @__PURE__ */ jsxs(Dialog, { open: !!assetId, onClose: handleClose, maxWidth: "sm", fullWidth: true, children: [
350
+ /* @__PURE__ */ jsx(DialogTitle, { sx: { pb: 1 }, children: isBundle ? /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
351
+ /* @__PURE__ */ jsx(Inventory2Icon, { sx: { color: "#8B5CF6", fontSize: "1.4rem" } }),
352
+ /* @__PURE__ */ jsxs(Box, { children: [
353
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", fontWeight: 700, children: t("assetInstallDialog.dialogTitleBundle", { name: asset?.name ?? "" }) }),
354
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { fontWeight: 400 }, children: t("assetInstallDialog.dialogSubtitleBundle") })
355
+ ] })
356
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
357
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", fontWeight: 700, children: asset ? t("assetInstallDialog.dialogTitle", { name: asset.name }) : t("assetInstallDialog.dialogTitle", { name: "" }) }),
358
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mt: 0.5, fontWeight: 400 }, children: t("assetInstallDialog.dialogSubtitle") })
359
+ ] }) }),
360
+ isBundle && totalSteps > 0 && /* @__PURE__ */ jsxs(Box, { sx: { px: 3, pb: 1 }, children: [
361
+ /* @__PURE__ */ jsx(LinearProgress, { variant: "determinate", value: progress, sx: { borderRadius: 1, height: 6 } }),
362
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.5, textAlign: "right" }, children: t("assetInstallDialog.bundleProgress", { current: String(step + 1), total: String(totalSteps) }) })
363
+ ] }),
364
+ /* @__PURE__ */ jsxs(DialogContent, { sx: { display: "flex", flexDirection: "column", gap: 1.5, pt: "8px !important" }, children: [
365
+ loading && /* @__PURE__ */ jsx(Box, { sx: { display: "flex", justifyContent: "center", py: 3 }, children: /* @__PURE__ */ jsx(CircularProgress, {}) }),
366
+ !loading && asset && !isBundle && /* @__PURE__ */ jsx(SingleAssetView, { asset }),
367
+ !loading && isBundle && bundleItems.length === 0 && /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { textAlign: "center", py: 2 }, children: t("assetInstallDialog.bundleEmpty") }),
368
+ !loading && isBundle && bundleItems.length > 0 && /* @__PURE__ */ jsx(
369
+ BundleItemStep,
370
+ {
371
+ item: bundleItems[step],
372
+ stepIndex: step,
373
+ total: totalSteps
374
+ }
375
+ )
188
376
  ] }),
189
- /* @__PURE__ */ jsx(DialogActions, { children: /* @__PURE__ */ jsx(Button, { onClick: handleClose, children: "Close" }) })
377
+ /* @__PURE__ */ jsx(DialogActions, { sx: { justifyContent: "space-between", px: 2, py: 1.5 }, children: isBundle ? /* @__PURE__ */ jsxs(Fragment, { children: [
378
+ /* @__PURE__ */ jsx(
379
+ Button,
380
+ {
381
+ startIcon: /* @__PURE__ */ jsx(FolderZipIcon, {}),
382
+ variant: "outlined",
383
+ size: "small",
384
+ onClick: handleDownloadBundle,
385
+ disabled: !asset,
386
+ children: t("assetInstallDialog.downloadBundle")
387
+ }
388
+ ),
389
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 1 }, children: [
390
+ /* @__PURE__ */ jsx(
391
+ Button,
392
+ {
393
+ startIcon: /* @__PURE__ */ jsx(ArrowBackIcon, {}),
394
+ onClick: () => setStep((s) => Math.max(0, s - 1)),
395
+ disabled: step === 0,
396
+ children: t("assetInstallDialog.back")
397
+ }
398
+ ),
399
+ step < totalSteps - 1 ? /* @__PURE__ */ jsx(
400
+ Button,
401
+ {
402
+ endIcon: /* @__PURE__ */ jsx(ArrowForwardIcon, {}),
403
+ variant: "contained",
404
+ onClick: () => setStep((s) => Math.min(totalSteps - 1, s + 1)),
405
+ children: t("assetInstallDialog.next")
406
+ }
407
+ ) : /* @__PURE__ */ jsx(Button, { variant: "contained", color: "success", onClick: handleClose, children: t("assetInstallDialog.finish") })
408
+ ] })
409
+ ] }) : /* @__PURE__ */ jsx(Button, { onClick: handleClose, sx: { ml: "auto" }, children: t("assetInstallDialog.close") }) })
190
410
  ] });
191
411
  }
192
412