@nospt/plugin-dev-ai-hub 1.0.1 → 1.0.3
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/README.md +1 -1
- package/dist/components/AiResourcesPage/AiResourcesPage.esm.js +9 -1
- package/dist/components/AiResourcesPage/AiResourcesPage.esm.js.map +1 -1
- package/dist/components/AiResourcesPage/CollapsibleSection.esm.js +37 -0
- package/dist/components/AiResourcesPage/CollapsibleSection.esm.js.map +1 -0
- package/dist/components/AiResourcesPage/CollapsibleSection.module.css.esm.js +8 -0
- package/dist/components/AiResourcesPage/CollapsibleSection.module.css.esm.js.map +1 -0
- package/dist/components/AiResourcesPage/JsonBody.esm.js +49 -0
- package/dist/components/AiResourcesPage/JsonBody.esm.js.map +1 -0
- package/dist/components/AiResourcesPage/ResourceCard.esm.js +17 -0
- package/dist/components/AiResourcesPage/ResourceCard.esm.js.map +1 -1
- package/dist/components/AiResourcesPage/ResourceCard.module.css.esm.js +2 -2
- package/dist/components/AiResourcesPage/ResourceDetailPanel.esm.js +55 -25
- package/dist/components/AiResourcesPage/ResourceDetailPanel.esm.js.map +1 -1
- package/dist/components/AiResourcesPage/ResourceInstallDialog.esm.js +4 -2
- package/dist/components/AiResourcesPage/ResourceInstallDialog.esm.js.map +1 -1
- package/dist/components/AiResourcesPage/ResourceRelationships.esm.js +77 -0
- package/dist/components/AiResourcesPage/ResourceRelationships.esm.js.map +1 -0
- package/dist/components/AiResourcesPage/ResourceRelationships.module.css.esm.js +8 -0
- package/dist/components/AiResourcesPage/ResourceRelationships.module.css.esm.js.map +1 -0
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The frontend half of **Dev AI Hub** — a catalog-backed browser for AI assets usable by GitHub Copilot, Claude Code, Google Gemini, Cursor and other AI coding tools.
|
|
4
4
|
|
|
5
|
-
> **Requires Backstage 1.
|
|
5
|
+
> **Requires Backstage 1.54 or later.** The `AiResource` kind comes from Backstage's own alpha catalog module, and these packages depend on `@backstage/catalog-model@^1.10.0` — first shipped in 1.54.0, along with the `plugin` and `marketplace` subtypes.
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
@@ -142,7 +142,15 @@ function AiResourcesPage() {
|
|
|
142
142
|
/* @__PURE__ */ jsx(Text, { variant: "title-small", color: "secondary", weight: "bold", as: "p", children: "No AI resources found" }),
|
|
143
143
|
/* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "p", children: items && items.length > 0 ? "No resources match the current filters." : "Register AiResource entities in the catalog to see them here." })
|
|
144
144
|
] }),
|
|
145
|
-
/* @__PURE__ */ jsx(
|
|
145
|
+
/* @__PURE__ */ jsx(
|
|
146
|
+
ResourceDetailPanel,
|
|
147
|
+
{
|
|
148
|
+
resource: selectedResource,
|
|
149
|
+
onClose: closeDetail,
|
|
150
|
+
items: items ?? [],
|
|
151
|
+
onOpen: openDetail
|
|
152
|
+
}
|
|
153
|
+
)
|
|
146
154
|
] });
|
|
147
155
|
}
|
|
148
156
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiResourcesPage.esm.js","sources":["../../../src/components/AiResourcesPage/AiResourcesPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Box, Flex, Skeleton, TablePagination, Text } from '@backstage/ui';\nimport {\n RESOURCE_TYPES,\n type ResourceSummary,\n type ResourceType,\n} from '@nospt/plugin-dev-ai-hub-common';\nimport { useResources } from '../../hooks/useResources';\nimport { StatTiles } from './StatTiles';\nimport { ResourceCard } from './ResourceCard';\nimport { ResourceFilters, type ResourceFiltersValue } from './ResourceFilters';\nimport { ResourceDetailPanel } from './ResourceDetailPanel';\nimport styles from './AiResourcesPage.module.css';\n\nconst PAGE_SIZE = 24;\n\nconst DEFAULT_FILTERS: ResourceFiltersValue = {\n search: '',\n framework: undefined,\n tags: [],\n};\n\nfunction matchesSearch(r: ResourceSummary, query: string): boolean {\n const q = query.trim().toLowerCase();\n if (!q) return true;\n return (\n r.name.toLowerCase().includes(q) ||\n (r.title ?? '').toLowerCase().includes(q) ||\n (r.description ?? '').toLowerCase().includes(q) ||\n r.tags.some(t => t.toLowerCase().includes(q))\n );\n}\n\nexport function AiResourcesPage() {\n const { items, error, loading } = useResources();\n const [typeFilter, setTypeFilter] = useState<ResourceType | undefined>(\n undefined,\n );\n const [filters, setFilters] = useState<ResourceFiltersValue>(DEFAULT_FILTERS);\n const [page, setPage] = useState(1);\n\n // Staggered card entrance plays once, on the initial load only — re-playing\n // it on every filter/search keystroke would be noisy rather than delightful\n // (\"add some flowers\").\n const [playEntrance, setPlayEntrance] = useState(true);\n useEffect(() => {\n if (loading || !playEntrance) return undefined;\n const timer = setTimeout(() => setPlayEntrance(false), 900);\n return () => clearTimeout(timer);\n }, [loading, playEntrance]);\n\n const [searchParams, setSearchParams] = useSearchParams();\n const selectedRef = searchParams.get('resource');\n\n const openDetail = (entityRef: string) =>\n setSearchParams(p => {\n const n = new URLSearchParams(p);\n n.set('resource', entityRef);\n return n;\n });\n\n const closeDetail = () =>\n setSearchParams(p => {\n const n = new URLSearchParams(p);\n n.delete('resource');\n return n;\n });\n\n const counts = useMemo(() => {\n const c = Object.fromEntries(RESOURCE_TYPES.map(t => [t, 0])) as Record<\n ResourceType,\n number\n >;\n (items ?? []).forEach(r => {\n c[r.type] += 1;\n });\n return c;\n }, [items]);\n\n const availableFrameworks = useMemo(\n () => Array.from(new Set((items ?? []).flatMap(r => r.frameworks))).sort(),\n [items],\n );\n\n const availableTags = useMemo(\n () => Array.from(new Set((items ?? []).flatMap(r => r.tags))).sort(),\n [items],\n );\n\n const visible = useMemo(\n () =>\n (items ?? []).filter(\n r =>\n (!typeFilter || r.type === typeFilter) &&\n (!filters.framework || r.frameworks.includes(filters.framework)) &&\n (filters.tags.length === 0 ||\n filters.tags.every(t => r.tags.includes(t))) &&\n matchesSearch(r, filters.search),\n ),\n [items, typeFilter, filters],\n );\n\n const totalPages = Math.ceil(visible.length / PAGE_SIZE);\n const pageItems = visible.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);\n\n const handleTileToggle = (type: ResourceType) => {\n setTypeFilter(current => (current === type ? undefined : type));\n setPage(1);\n };\n\n const handleFiltersChange = (next: ResourceFiltersValue) => {\n setFilters(next);\n setPage(1);\n };\n\n const selectedResource = selectedRef\n ? (items ?? []).find(r => r.entityRef === selectedRef)\n : undefined;\n\n return (\n <div className={styles.pageRoot}>\n <StatTiles\n counts={counts}\n activeType={typeFilter}\n onToggle={handleTileToggle}\n />\n\n <ResourceFilters\n value={filters}\n onChange={handleFiltersChange}\n availableFrameworks={availableFrameworks}\n availableTags={availableTags}\n />\n\n {loading && (\n <div className={styles.grid}>\n {Array.from({ length: 8 }).map((_, i) => (\n <Skeleton\n key={i}\n style={{ height: 170, borderRadius: 'var(--bui-radius-2)' }}\n />\n ))}\n </div>\n )}\n\n {error && (\n <div className={styles.emptyState}>\n <Text variant=\"title-small\" color=\"secondary\" weight=\"bold\" as=\"p\">\n Could not load resources\n </Text>\n <Text variant=\"body-small\" color=\"secondary\" as=\"p\">\n {error.message}\n </Text>\n </div>\n )}\n\n {!loading && !error && visible.length > 0 && (\n <>\n <Box mb=\"4\">\n <Text variant=\"body-x-small\" color=\"secondary\">\n {visible.length} resource{visible.length !== 1 ? 's' : ''} found\n </Text>\n </Box>\n <div className={styles.grid}>\n {pageItems.map((r, i) => (\n <ResourceCard\n key={r.entityRef}\n resource={r}\n onView={openDetail}\n className={playEntrance ? styles.cardEntrance : undefined}\n style={\n playEntrance\n ? ({ '--card-index': i } as React.CSSProperties)\n : undefined\n }\n />\n ))}\n </div>\n {totalPages > 1 && (\n <Flex className={styles.paginationRow}>\n <TablePagination\n pageSize={PAGE_SIZE}\n offset={(page - 1) * PAGE_SIZE}\n totalCount={visible.length}\n hasNextPage={page < totalPages}\n hasPreviousPage={page > 1}\n onNextPage={() => setPage(p => p + 1)}\n onPreviousPage={() => setPage(p => p - 1)}\n />\n </Flex>\n )}\n </>\n )}\n\n {!loading && !error && visible.length === 0 && (\n <div className={styles.emptyState}>\n <div className={styles.emptyEmoji}>🤖</div>\n <Text variant=\"title-small\" color=\"secondary\" weight=\"bold\" as=\"p\">\n No AI resources found\n </Text>\n <Text variant=\"body-small\" color=\"secondary\" as=\"p\">\n {items && items.length > 0\n ? 'No resources match the current filters.'\n : 'Register AiResource entities in the catalog to see them here.'}\n </Text>\n </div>\n )}\n\n <ResourceDetailPanel resource={selectedResource} onClose={closeDetail} />\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AiResourcesPage.esm.js","sources":["../../../src/components/AiResourcesPage/AiResourcesPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Box, Flex, Skeleton, TablePagination, Text } from '@backstage/ui';\nimport {\n RESOURCE_TYPES,\n type ResourceSummary,\n type ResourceType,\n} from '@nospt/plugin-dev-ai-hub-common';\nimport { useResources } from '../../hooks/useResources';\nimport { StatTiles } from './StatTiles';\nimport { ResourceCard } from './ResourceCard';\nimport { ResourceFilters, type ResourceFiltersValue } from './ResourceFilters';\nimport { ResourceDetailPanel } from './ResourceDetailPanel';\nimport styles from './AiResourcesPage.module.css';\n\nconst PAGE_SIZE = 24;\n\nconst DEFAULT_FILTERS: ResourceFiltersValue = {\n search: '',\n framework: undefined,\n tags: [],\n};\n\nfunction matchesSearch(r: ResourceSummary, query: string): boolean {\n const q = query.trim().toLowerCase();\n if (!q) return true;\n return (\n r.name.toLowerCase().includes(q) ||\n (r.title ?? '').toLowerCase().includes(q) ||\n (r.description ?? '').toLowerCase().includes(q) ||\n r.tags.some(t => t.toLowerCase().includes(q))\n );\n}\n\nexport function AiResourcesPage() {\n const { items, error, loading } = useResources();\n const [typeFilter, setTypeFilter] = useState<ResourceType | undefined>(\n undefined,\n );\n const [filters, setFilters] = useState<ResourceFiltersValue>(DEFAULT_FILTERS);\n const [page, setPage] = useState(1);\n\n // Staggered card entrance plays once, on the initial load only — re-playing\n // it on every filter/search keystroke would be noisy rather than delightful\n // (\"add some flowers\").\n const [playEntrance, setPlayEntrance] = useState(true);\n useEffect(() => {\n if (loading || !playEntrance) return undefined;\n const timer = setTimeout(() => setPlayEntrance(false), 900);\n return () => clearTimeout(timer);\n }, [loading, playEntrance]);\n\n const [searchParams, setSearchParams] = useSearchParams();\n const selectedRef = searchParams.get('resource');\n\n const openDetail = (entityRef: string) =>\n setSearchParams(p => {\n const n = new URLSearchParams(p);\n n.set('resource', entityRef);\n return n;\n });\n\n const closeDetail = () =>\n setSearchParams(p => {\n const n = new URLSearchParams(p);\n n.delete('resource');\n return n;\n });\n\n const counts = useMemo(() => {\n const c = Object.fromEntries(RESOURCE_TYPES.map(t => [t, 0])) as Record<\n ResourceType,\n number\n >;\n (items ?? []).forEach(r => {\n c[r.type] += 1;\n });\n return c;\n }, [items]);\n\n const availableFrameworks = useMemo(\n () => Array.from(new Set((items ?? []).flatMap(r => r.frameworks))).sort(),\n [items],\n );\n\n const availableTags = useMemo(\n () => Array.from(new Set((items ?? []).flatMap(r => r.tags))).sort(),\n [items],\n );\n\n const visible = useMemo(\n () =>\n (items ?? []).filter(\n r =>\n (!typeFilter || r.type === typeFilter) &&\n (!filters.framework || r.frameworks.includes(filters.framework)) &&\n (filters.tags.length === 0 ||\n filters.tags.every(t => r.tags.includes(t))) &&\n matchesSearch(r, filters.search),\n ),\n [items, typeFilter, filters],\n );\n\n const totalPages = Math.ceil(visible.length / PAGE_SIZE);\n const pageItems = visible.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);\n\n const handleTileToggle = (type: ResourceType) => {\n setTypeFilter(current => (current === type ? undefined : type));\n setPage(1);\n };\n\n const handleFiltersChange = (next: ResourceFiltersValue) => {\n setFilters(next);\n setPage(1);\n };\n\n const selectedResource = selectedRef\n ? (items ?? []).find(r => r.entityRef === selectedRef)\n : undefined;\n\n return (\n <div className={styles.pageRoot}>\n <StatTiles\n counts={counts}\n activeType={typeFilter}\n onToggle={handleTileToggle}\n />\n\n <ResourceFilters\n value={filters}\n onChange={handleFiltersChange}\n availableFrameworks={availableFrameworks}\n availableTags={availableTags}\n />\n\n {loading && (\n <div className={styles.grid}>\n {Array.from({ length: 8 }).map((_, i) => (\n <Skeleton\n key={i}\n style={{ height: 170, borderRadius: 'var(--bui-radius-2)' }}\n />\n ))}\n </div>\n )}\n\n {error && (\n <div className={styles.emptyState}>\n <Text variant=\"title-small\" color=\"secondary\" weight=\"bold\" as=\"p\">\n Could not load resources\n </Text>\n <Text variant=\"body-small\" color=\"secondary\" as=\"p\">\n {error.message}\n </Text>\n </div>\n )}\n\n {!loading && !error && visible.length > 0 && (\n <>\n <Box mb=\"4\">\n <Text variant=\"body-x-small\" color=\"secondary\">\n {visible.length} resource{visible.length !== 1 ? 's' : ''} found\n </Text>\n </Box>\n <div className={styles.grid}>\n {pageItems.map((r, i) => (\n <ResourceCard\n key={r.entityRef}\n resource={r}\n onView={openDetail}\n className={playEntrance ? styles.cardEntrance : undefined}\n style={\n playEntrance\n ? ({ '--card-index': i } as React.CSSProperties)\n : undefined\n }\n />\n ))}\n </div>\n {totalPages > 1 && (\n <Flex className={styles.paginationRow}>\n <TablePagination\n pageSize={PAGE_SIZE}\n offset={(page - 1) * PAGE_SIZE}\n totalCount={visible.length}\n hasNextPage={page < totalPages}\n hasPreviousPage={page > 1}\n onNextPage={() => setPage(p => p + 1)}\n onPreviousPage={() => setPage(p => p - 1)}\n />\n </Flex>\n )}\n </>\n )}\n\n {!loading && !error && visible.length === 0 && (\n <div className={styles.emptyState}>\n <div className={styles.emptyEmoji}>🤖</div>\n <Text variant=\"title-small\" color=\"secondary\" weight=\"bold\" as=\"p\">\n No AI resources found\n </Text>\n <Text variant=\"body-small\" color=\"secondary\" as=\"p\">\n {items && items.length > 0\n ? 'No resources match the current filters.'\n : 'Register AiResource entities in the catalog to see them here.'}\n </Text>\n </div>\n )}\n\n <ResourceDetailPanel\n resource={selectedResource}\n onClose={closeDetail}\n items={items ?? []}\n onOpen={openDetail}\n />\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Text } from '@backstage/ui';
|
|
4
|
+
import { RiArrowRightSLine } from '@remixicon/react';
|
|
5
|
+
import styles from './CollapsibleSection.module.css.esm.js';
|
|
6
|
+
|
|
7
|
+
function CollapsibleSection({
|
|
8
|
+
label,
|
|
9
|
+
defaultOpen = false,
|
|
10
|
+
children
|
|
11
|
+
}) {
|
|
12
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
13
|
+
return /* @__PURE__ */ jsxs(
|
|
14
|
+
"details",
|
|
15
|
+
{
|
|
16
|
+
className: styles.collapsible,
|
|
17
|
+
open,
|
|
18
|
+
onToggle: (e) => setOpen(e.currentTarget.open),
|
|
19
|
+
children: [
|
|
20
|
+
/* @__PURE__ */ jsxs("summary", { className: styles.summary, children: [
|
|
21
|
+
/* @__PURE__ */ jsxs("span", { className: styles.label, children: [
|
|
22
|
+
/* @__PURE__ */ jsx(RiArrowRightSLine, { size: 16, className: styles.chevron }),
|
|
23
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-small", weight: "bold", children: label })
|
|
24
|
+
] }),
|
|
25
|
+
/* @__PURE__ */ jsxs("span", { className: styles.toggle, children: [
|
|
26
|
+
/* @__PURE__ */ jsx("span", { className: styles.show, children: "Show" }),
|
|
27
|
+
/* @__PURE__ */ jsx("span", { className: styles.hide, children: "Hide" })
|
|
28
|
+
] })
|
|
29
|
+
] }),
|
|
30
|
+
/* @__PURE__ */ jsx("div", { className: styles.body, children })
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { CollapsibleSection };
|
|
37
|
+
//# sourceMappingURL=CollapsibleSection.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CollapsibleSection.esm.js","sources":["../../../src/components/AiResourcesPage/CollapsibleSection.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Text } from '@backstage/ui';\nimport { RiArrowRightSLine } from '@remixicon/react';\nimport styles from './CollapsibleSection.module.css';\n\ninterface CollapsibleSectionProps {\n label: string;\n /** Whether the section starts expanded. */\n defaultOpen?: boolean;\n children: React.ReactNode;\n}\n\n/**\n * A disclosure whose summary reads as a button — a bordered bar with a hover\n * state, a rotating chevron, and an explicit Show/Hide label — so the toggle is\n * discoverable without prior knowledge of the feature. Native `<details>`, so\n * it stays keyboard-toggleable.\n *\n * State-controlled: `defaultOpen` seeds the initial state and the open/closed\n * choice survives parent re-renders (the panel re-renders as the body and\n * telemetry counts load), which a bare `open={defaultOpen}` would fight.\n */\nexport function CollapsibleSection({\n label,\n defaultOpen = false,\n children,\n}: CollapsibleSectionProps) {\n const [open, setOpen] = useState(defaultOpen);\n return (\n <details\n className={styles.collapsible}\n open={open}\n onToggle={e => setOpen(e.currentTarget.open)}\n >\n <summary className={styles.summary}>\n <span className={styles.label}>\n <RiArrowRightSLine size={16} className={styles.chevron} />\n <Text variant=\"body-small\" weight=\"bold\">\n {label}\n </Text>\n </span>\n <span className={styles.toggle}>\n <span className={styles.show}>Show</span>\n <span className={styles.hide}>Hide</span>\n </span>\n </summary>\n <div className={styles.body}>{children}</div>\n </details>\n );\n}\n"],"names":[],"mappings":";;;;;;AAsBO,SAAS,kBAAA,CAAmB;AAAA,EACjC,KAAA;AAAA,EACA,WAAA,GAAc,KAAA;AAAA,EACd;AACF,CAAA,EAA4B;AAC1B,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,WAAW,CAAA;AAC5C,EAAA,uBACE,IAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,WAAW,MAAA,CAAO,WAAA;AAAA,MAClB,IAAA;AAAA,MACA,QAAA,EAAU,CAAA,CAAA,KAAK,OAAA,CAAQ,CAAA,CAAE,cAAc,IAAI,CAAA;AAAA,MAE3C,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAW,MAAA,CAAO,OAAA,EACzB,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,MAAA,CAAO,KAAA,EACtB,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,iBAAA,EAAA,EAAkB,IAAA,EAAM,EAAA,EAAI,SAAA,EAAW,OAAO,OAAA,EAAS,CAAA;AAAA,gCACvD,IAAA,EAAA,EAAK,OAAA,EAAQ,YAAA,EAAa,MAAA,EAAO,QAC/B,QAAA,EAAA,KAAA,EACH;AAAA,WAAA,EACF,CAAA;AAAA,0BACA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,MAAA,CAAO,MAAA,EACtB,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,MAAA,CAAO,IAAA,EAAM,QAAA,EAAA,MAAA,EAAI,CAAA;AAAA,4BAClC,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,MAAA,CAAO,MAAM,QAAA,EAAA,MAAA,EAAI;AAAA,WAAA,EACpC;AAAA,SAAA,EACF,CAAA;AAAA,wBACA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,MAAA,CAAO,MAAO,QAAA,EAAS;AAAA;AAAA;AAAA,GACzC;AAEJ;;;;"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import styleInject from '../../node_modules_dist/style-inject/dist/style-inject.es.esm.js';
|
|
2
|
+
|
|
3
|
+
var css_248z = "@layer components {\n .CollapsibleSection_collapsible__e79986fbf3 {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n }\n\n .CollapsibleSection_summary__e79986fbf3 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--bui-space-2);\n cursor: pointer;\n list-style: none;\n user-select: none;\n /* Reads as a button, not a heading: a filled, bordered bar with a hover\n state so the toggle is discoverable without knowing the feature. */\n padding: var(--bui-space-2) var(--bui-space-3);\n background: var(--bui-bg-neutral-2);\n border: 1px solid var(--bui-border-1);\n border-radius: var(--bui-radius-2);\n transition: border-color 120ms ease, background-color 120ms ease;\n }\n\n .CollapsibleSection_summary__e79986fbf3:hover {\n border-color: var(--drawer-accent, var(--bui-fg-info));\n background: var(--bui-bg-neutral-1);\n }\n\n .CollapsibleSection_summary__e79986fbf3:focus-visible {\n outline: 2px solid var(--drawer-accent, var(--bui-fg-info));\n outline-offset: 2px;\n }\n\n /* Hide the native disclosure triangle; the chevron below replaces it. */\n .CollapsibleSection_summary__e79986fbf3::-webkit-details-marker {\n display: none;\n }\n\n .CollapsibleSection_label__e79986fbf3 {\n display: inline-flex;\n align-items: center;\n gap: var(--bui-space-1);\n }\n\n /* The Show/Hide pill: the explicit call to action that names the interaction,\n swapped by open state below. */\n .CollapsibleSection_toggle__e79986fbf3 {\n display: inline-flex;\n align-items: center;\n font-size: 0.75rem;\n font-weight: 600;\n color: var(--drawer-accent, var(--bui-fg-info));\n }\n\n .CollapsibleSection_collapsible__e79986fbf3[open] .CollapsibleSection_show__e79986fbf3 {\n display: none;\n }\n\n .CollapsibleSection_collapsible__e79986fbf3:not([open]) .CollapsibleSection_hide__e79986fbf3 {\n display: none;\n }\n\n .CollapsibleSection_chevron__e79986fbf3 {\n color: var(--bui-fg-secondary);\n transition: transform 150ms ease;\n }\n\n .CollapsibleSection_collapsible__e79986fbf3[open] .CollapsibleSection_chevron__e79986fbf3 {\n transform: rotate(90deg);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .CollapsibleSection_chevron__e79986fbf3 {\n transition: none;\n }\n }\n\n .CollapsibleSection_body__e79986fbf3 {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n margin-top: var(--bui-space-2);\n }\n\n /* The UA stylesheet hides a closed <details>'s non-summary children via\n `display: none`, which the unconditional `display: flex` above\n overrides — leaving the body visible even while collapsed. Restore the\n closed-state hiding explicitly. */\n .CollapsibleSection_collapsible__e79986fbf3:not([open]) .CollapsibleSection_body__e79986fbf3 {\n display: none;\n }\n}\n";
|
|
4
|
+
var styles = {"collapsible":"CollapsibleSection_collapsible__e79986fbf3","summary":"CollapsibleSection_summary__e79986fbf3","label":"CollapsibleSection_label__e79986fbf3","toggle":"CollapsibleSection_toggle__e79986fbf3","show":"CollapsibleSection_show__e79986fbf3","hide":"CollapsibleSection_hide__e79986fbf3","chevron":"CollapsibleSection_chevron__e79986fbf3","body":"CollapsibleSection_body__e79986fbf3"};
|
|
5
|
+
styleInject(css_248z);
|
|
6
|
+
|
|
7
|
+
export { styles as default };
|
|
8
|
+
//# sourceMappingURL=CollapsibleSection.module.css.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CollapsibleSection.module.css.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { PrismLight } from 'react-syntax-highlighter';
|
|
4
|
+
import json from 'react-syntax-highlighter/dist/esm/languages/prism/json';
|
|
5
|
+
|
|
6
|
+
const SyntaxHighlighter = PrismLight;
|
|
7
|
+
PrismLight.registerLanguage("json", json);
|
|
8
|
+
const JSON_HIGHLIGHT_STYLE = {
|
|
9
|
+
'code[class*="language-"]': {
|
|
10
|
+
color: "var(--bui-fg-primary)",
|
|
11
|
+
background: "none"
|
|
12
|
+
},
|
|
13
|
+
'pre[class*="language-"]': {
|
|
14
|
+
color: "var(--bui-fg-primary)",
|
|
15
|
+
background: "none"
|
|
16
|
+
},
|
|
17
|
+
property: { color: "var(--bui-fg-announcement)" },
|
|
18
|
+
string: { color: "var(--bui-fg-warning)" },
|
|
19
|
+
number: { color: "var(--bui-fg-warning)" },
|
|
20
|
+
boolean: { color: "var(--bui-fg-warning)" },
|
|
21
|
+
null: { color: "var(--bui-fg-warning)" },
|
|
22
|
+
punctuation: { color: "var(--bui-fg-secondary)" },
|
|
23
|
+
operator: { color: "var(--bui-fg-secondary)" }
|
|
24
|
+
};
|
|
25
|
+
function JsonBody({ content, className }) {
|
|
26
|
+
const formatted = useMemo(() => {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.stringify(JSON.parse(content), null, 2);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}, [content]);
|
|
33
|
+
if (formatted === null) {
|
|
34
|
+
return /* @__PURE__ */ jsx("pre", { className, children: /* @__PURE__ */ jsx("code", { children: content }) });
|
|
35
|
+
}
|
|
36
|
+
return /* @__PURE__ */ jsx(
|
|
37
|
+
SyntaxHighlighter,
|
|
38
|
+
{
|
|
39
|
+
language: "json",
|
|
40
|
+
style: JSON_HIGHLIGHT_STYLE,
|
|
41
|
+
className,
|
|
42
|
+
customStyle: { margin: 0, padding: 0 },
|
|
43
|
+
children: formatted
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { JsonBody };
|
|
49
|
+
//# sourceMappingURL=JsonBody.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JsonBody.esm.js","sources":["../../../src/components/AiResourcesPage/JsonBody.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n PrismLight,\n type SyntaxHighlighterProps,\n} from 'react-syntax-highlighter';\nimport json from 'react-syntax-highlighter/dist/esm/languages/prism/json';\n\n// react-syntax-highlighter's bundled @types/react (v19) doesn't structurally\n// match this repo's React 18 types, so the class component fails the JSX\n// element-type check as-is; recast to a plain function-component shape.\nconst SyntaxHighlighter =\n PrismLight as unknown as React.ComponentType<SyntaxHighlighterProps>;\n\nPrismLight.registerLanguage('json', json);\n\n/**\n * Token colours are drawn from the app's own `--bui-fg-*` tokens, not a\n * canned Prism theme — those ship a single hardcoded palette and would go\n * unreadable in whichever of light/dark mode they weren't built for. Reusing\n * the app's own tokens means highlighting follows the active theme for free.\n */\nconst JSON_HIGHLIGHT_STYLE = {\n 'code[class*=\"language-\"]': {\n color: 'var(--bui-fg-primary)',\n background: 'none',\n },\n 'pre[class*=\"language-\"]': {\n color: 'var(--bui-fg-primary)',\n background: 'none',\n },\n property: { color: 'var(--bui-fg-announcement)' },\n string: { color: 'var(--bui-fg-warning)' },\n number: { color: 'var(--bui-fg-warning)' },\n boolean: { color: 'var(--bui-fg-warning)' },\n null: { color: 'var(--bui-fg-warning)' },\n punctuation: { color: 'var(--bui-fg-secondary)' },\n operator: { color: 'var(--bui-fg-secondary)' },\n};\n\ninterface JsonBodyProps {\n content: string;\n className: string;\n}\n\n/**\n * Pretty-prints and syntax-highlights a JSON resource body (ADR-0014). Falls\n * back to the raw text, unhighlighted, when the content doesn't parse as\n * JSON — a malformed or foreign file must stay visible, not blank the panel.\n */\nexport function JsonBody({ content, className }: JsonBodyProps) {\n const formatted = useMemo(() => {\n try {\n return JSON.stringify(JSON.parse(content), null, 2);\n } catch {\n return null;\n }\n }, [content]);\n\n if (formatted === null) {\n return (\n <pre className={className}>\n <code>{content}</code>\n </pre>\n );\n }\n\n return (\n <SyntaxHighlighter\n language=\"json\"\n style={JSON_HIGHLIGHT_STYLE}\n className={className}\n customStyle={{ margin: 0, padding: 0 }}\n >\n {formatted}\n </SyntaxHighlighter>\n );\n}\n"],"names":[],"mappings":";;;;;AAUA,MAAM,iBAAA,GACJ,UAAA;AAEF,UAAA,CAAW,gBAAA,CAAiB,QAAQ,IAAI,CAAA;AAQxC,MAAM,oBAAA,GAAuB;AAAA,EAC3B,0BAAA,EAA4B;AAAA,IAC1B,KAAA,EAAO,uBAAA;AAAA,IACP,UAAA,EAAY;AAAA,GACd;AAAA,EACA,yBAAA,EAA2B;AAAA,IACzB,KAAA,EAAO,uBAAA;AAAA,IACP,UAAA,EAAY;AAAA,GACd;AAAA,EACA,QAAA,EAAU,EAAE,KAAA,EAAO,4BAAA,EAA6B;AAAA,EAChD,MAAA,EAAQ,EAAE,KAAA,EAAO,uBAAA,EAAwB;AAAA,EACzC,MAAA,EAAQ,EAAE,KAAA,EAAO,uBAAA,EAAwB;AAAA,EACzC,OAAA,EAAS,EAAE,KAAA,EAAO,uBAAA,EAAwB;AAAA,EAC1C,IAAA,EAAM,EAAE,KAAA,EAAO,uBAAA,EAAwB;AAAA,EACvC,WAAA,EAAa,EAAE,KAAA,EAAO,yBAAA,EAA0B;AAAA,EAChD,QAAA,EAAU,EAAE,KAAA,EAAO,yBAAA;AACrB,CAAA;AAYO,SAAS,QAAA,CAAS,EAAE,OAAA,EAAS,SAAA,EAAU,EAAkB;AAC9D,EAAA,MAAM,SAAA,GAAY,QAAQ,MAAM;AAC9B,IAAA,IAAI;AACF,MAAA,OAAO,KAAK,SAAA,CAAU,IAAA,CAAK,MAAM,OAAO,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,IACpD,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,IAAI,cAAc,IAAA,EAAM;AACtB,IAAA,2BACG,KAAA,EAAA,EAAI,SAAA,EACH,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAM,mBAAQ,CAAA,EACjB,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,iBAAA;AAAA,IAAA;AAAA,MACC,QAAA,EAAS,MAAA;AAAA,MACT,KAAA,EAAO,oBAAA;AAAA,MACP,SAAA;AAAA,MACA,WAAA,EAAa,EAAE,MAAA,EAAQ,CAAA,EAAG,SAAS,CAAA,EAAE;AAAA,MAEpC,QAAA,EAAA;AAAA;AAAA,GACH;AAEJ;;;;"}
|
|
@@ -8,6 +8,15 @@ import styles from './ResourceCard.module.css.esm.js';
|
|
|
8
8
|
|
|
9
9
|
const MAX_VISIBLE_TAGS = 3;
|
|
10
10
|
const POPULAR_THRESHOLD = 5;
|
|
11
|
+
function containsLabel(resource) {
|
|
12
|
+
const count = resource.childCount ?? 0;
|
|
13
|
+
if (count <= 0) return void 0;
|
|
14
|
+
if (resource.type === "marketplace")
|
|
15
|
+
return `${count} ${count === 1 ? "plugin" : "plugins"}`;
|
|
16
|
+
if (resource.type === "plugin")
|
|
17
|
+
return `${count} ${count === 1 ? "item" : "items"}`;
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
11
20
|
function ResourceCard({
|
|
12
21
|
resource,
|
|
13
22
|
onView,
|
|
@@ -19,6 +28,7 @@ function ResourceCard({
|
|
|
19
28
|
const visibleTags = resource.tags.slice(0, MAX_VISIBLE_TAGS);
|
|
20
29
|
const hiddenTagCount = resource.tags.length - visibleTags.length;
|
|
21
30
|
const ownerLabel = resource.owner?.replace(/^(group|user):(default\/)?/, "");
|
|
31
|
+
const parentCount = resource.parents?.length ?? 0;
|
|
22
32
|
return /* @__PURE__ */ jsxs(
|
|
23
33
|
"div",
|
|
24
34
|
{
|
|
@@ -79,6 +89,13 @@ function ResourceCard({
|
|
|
79
89
|
}
|
|
80
90
|
)
|
|
81
91
|
] }),
|
|
92
|
+
(containsLabel(resource) || parentCount > 0) && /* @__PURE__ */ jsxs("div", { className: styles.relRow, children: [
|
|
93
|
+
containsLabel(resource) && /* @__PURE__ */ jsx("span", { className: styles.relChip, title: "Contains", children: containsLabel(resource) }),
|
|
94
|
+
parentCount > 0 && /* @__PURE__ */ jsxs("span", { className: styles.relChip, title: "Belongs to a container", children: [
|
|
95
|
+
"part of ",
|
|
96
|
+
parentCount
|
|
97
|
+
] })
|
|
98
|
+
] }),
|
|
82
99
|
(resource.version || ownerLabel || !!counts?.install || !!counts?.view) && /* @__PURE__ */ jsxs("div", { className: styles.footer, children: [
|
|
83
100
|
/* @__PURE__ */ jsx(Text, { variant: "body-x-small", color: "secondary", children: [
|
|
84
101
|
resource.version && `v${resource.version.replace(/^v/, "")}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceCard.esm.js","sources":["../../../src/components/AiResourcesPage/ResourceCard.tsx"],"sourcesContent":["import { Text } from '@backstage/ui';\nimport { RiDownloadLine, RiEyeLine } from '@remixicon/react';\nimport type { ResourceSummary } from '@nospt/plugin-dev-ai-hub-common';\nimport { useTelemetryCounts } from '../../hooks/useTelemetryCounts';\nimport { ToolIcon } from '../ToolIcon';\nimport type { FrameworkToken } from '@nospt/plugin-dev-ai-hub-common';\nimport { frameworkLabel, getTypeMeta } from './typeMeta';\nimport styles from './ResourceCard.module.css';\n\ninterface ResourceCardProps {\n resource: ResourceSummary;\n onView: (entityRef: string) => void;\n /** Extra class (e.g. the initial-load entrance animation) merged onto the\n * card's own root — kept off a wrapper div so this stays the grid's direct\n * child and CSS Grid's row-height stretch (the footer's margin-top: auto\n * relies on it for equal card heights) keeps working. */\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * The per-type card: type identity (colour/icon/label from the registry),\n * title, framework badges, tags, and a footer of version · owner plus the\n * view and install counts.\n *\n * Deliberately no description — clamped to two lines at card width it was\n * unreadable, and the drawer shows it in full the moment a card is opened.\n * Deliberately no \"View source\" link either: it competed with the card's own\n * click target and lives in the drawer, which is where someone deciding\n * whether to install is already looking.\n */\nconst MAX_VISIBLE_TAGS = 3;\nconst POPULAR_THRESHOLD = 5;\n\nexport function ResourceCard({\n resource,\n onView,\n className,\n style,\n}: ResourceCardProps) {\n const meta = getTypeMeta(resource.type);\n const counts = useTelemetryCounts(resource.entityRef);\n\n const visibleTags = resource.tags.slice(0, MAX_VISIBLE_TAGS);\n const hiddenTagCount = resource.tags.length - visibleTags.length;\n const ownerLabel = resource.owner?.replace(/^(group|user):(default\\/)?/, '');\n\n return (\n <div\n className={className ? `${styles.card} ${className}` : styles.card}\n style={\n {\n '--card-accent': meta.color,\n '--card-accent-bg': meta.colorBg,\n ...style,\n } as React.CSSProperties\n }\n onClick={() => onView(resource.entityRef)}\n onKeyDown={e => {\n if (e.key === 'Enter') onView(resource.entityRef);\n }}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${resource.title ?? resource.name}`}\n >\n <div className={styles.header}>\n <div className={styles.iconBox}>\n <meta.Icon size={18} />\n </div>\n <div className={styles.headerText}>\n {/* Type reads as an eyebrow above the title. Below it, the reserved\n second title line would leave a gap between a one-line title and\n its own label; above it, that space falls before the description\n where nothing looks detached. */}\n <Text variant=\"body-x-small\" className={styles.typeLabel}>\n {meta.label}\n </Text>\n <Text\n variant=\"title-x-small\"\n weight=\"bold\"\n as=\"h3\"\n className={styles.title}\n >\n {resource.title ?? resource.name}\n </Text>\n </div>\n </div>\n\n <div className={styles.body}>\n {resource.frameworks.length > 0 && (\n <div className={styles.badges}>\n {/* Icon only — the tool's mark is recognisable enough on a card,\n and dropping the label keeps the row narrow. The name stays\n reachable: ToolIcon carries it as an aria-label, and `title`\n surfaces it on hover. Every framework is shown: icons are\n narrow enough that the full set fits, so there is no overflow\n pill to hide the tail behind. */}\n {resource.frameworks.map(f => (\n <span\n key={f}\n className={styles.frameworkBadge}\n title={frameworkLabel(f)}\n >\n <ToolIcon tool={f as FrameworkToken} size={16} />\n </span>\n ))}\n </div>\n )}\n\n {resource.tags.length > 0 && (\n <div className={styles.tags}>\n {visibleTags.map(t => (\n <span key={t} className={styles.tag}>\n #{t}\n </span>\n ))}\n {hiddenTagCount > 0 && (\n <span\n className={styles.tag}\n title={resource.tags.slice(MAX_VISIBLE_TAGS).join(', ')}\n >\n +{hiddenTagCount}\n </span>\n )}\n </div>\n )}\n\n {(resource.version ||\n ownerLabel ||\n !!counts?.install ||\n !!counts?.view) && (\n <div className={styles.footer}>\n <Text variant=\"body-x-small\" color=\"secondary\">\n {[\n resource.version && `v${resource.version.replace(/^v/, '')}`,\n ownerLabel,\n ]\n .filter(Boolean)\n .join(' · ')}\n </Text>\n {/* Views and installs are the social proof someone browsing\n actually scans for, so they are chips at full foreground rather\n than grey footnotes beside the version. */}\n <span className={styles.countsRow}>\n {!!counts?.view && (\n <span className={styles.count} title={`${counts.view} views`}>\n <RiEyeLine size={14} />\n {counts.view}\n </span>\n )}\n {!!counts?.install && (\n <span\n className={\n counts.install >= POPULAR_THRESHOLD\n ? styles.countPopular\n : styles.count\n }\n title={`${counts.install} installs`}\n >\n {counts.install >= POPULAR_THRESHOLD ? (\n '\\u{1F525}'\n ) : (\n <RiDownloadLine size={14} />\n )}\n {counts.install}\n </span>\n )}\n </span>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ResourceCard.esm.js","sources":["../../../src/components/AiResourcesPage/ResourceCard.tsx"],"sourcesContent":["import { Text } from '@backstage/ui';\nimport { RiDownloadLine, RiEyeLine } from '@remixicon/react';\nimport type { ResourceSummary } from '@nospt/plugin-dev-ai-hub-common';\nimport { useTelemetryCounts } from '../../hooks/useTelemetryCounts';\nimport { ToolIcon } from '../ToolIcon';\nimport type { FrameworkToken } from '@nospt/plugin-dev-ai-hub-common';\nimport { frameworkLabel, getTypeMeta } from './typeMeta';\nimport styles from './ResourceCard.module.css';\n\ninterface ResourceCardProps {\n resource: ResourceSummary;\n onView: (entityRef: string) => void;\n /** Extra class (e.g. the initial-load entrance animation) merged onto the\n * card's own root — kept off a wrapper div so this stays the grid's direct\n * child and CSS Grid's row-height stretch (the footer's margin-top: auto\n * relies on it for equal card heights) keeps working. */\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * The per-type card: type identity (colour/icon/label from the registry),\n * title, framework badges, tags, and a footer of version · owner plus the\n * view and install counts.\n *\n * Deliberately no description — clamped to two lines at card width it was\n * unreadable, and the drawer shows it in full the moment a card is opened.\n * Deliberately no \"View source\" link either: it competed with the card's own\n * click target and lives in the drawer, which is where someone deciding\n * whether to install is already looking.\n */\nconst MAX_VISIBLE_TAGS = 3;\nconst POPULAR_THRESHOLD = 5;\n\n/**\n * The count chip a container card shows for its members (ADR-0015). Reads the\n * caller-visible `childCount`; leaf types and empty containers show nothing.\n */\nfunction containsLabel(resource: ResourceSummary): string | undefined {\n const count = resource.childCount ?? 0;\n if (count <= 0) return undefined;\n if (resource.type === 'marketplace')\n return `${count} ${count === 1 ? 'plugin' : 'plugins'}`;\n if (resource.type === 'plugin')\n return `${count} ${count === 1 ? 'item' : 'items'}`;\n return undefined;\n}\n\nexport function ResourceCard({\n resource,\n onView,\n className,\n style,\n}: ResourceCardProps) {\n const meta = getTypeMeta(resource.type);\n const counts = useTelemetryCounts(resource.entityRef);\n\n const visibleTags = resource.tags.slice(0, MAX_VISIBLE_TAGS);\n const hiddenTagCount = resource.tags.length - visibleTags.length;\n const ownerLabel = resource.owner?.replace(/^(group|user):(default\\/)?/, '');\n const parentCount = resource.parents?.length ?? 0;\n\n return (\n <div\n className={className ? `${styles.card} ${className}` : styles.card}\n style={\n {\n '--card-accent': meta.color,\n '--card-accent-bg': meta.colorBg,\n ...style,\n } as React.CSSProperties\n }\n onClick={() => onView(resource.entityRef)}\n onKeyDown={e => {\n if (e.key === 'Enter') onView(resource.entityRef);\n }}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${resource.title ?? resource.name}`}\n >\n <div className={styles.header}>\n <div className={styles.iconBox}>\n <meta.Icon size={18} />\n </div>\n <div className={styles.headerText}>\n {/* Type reads as an eyebrow above the title. Below it, the reserved\n second title line would leave a gap between a one-line title and\n its own label; above it, that space falls before the description\n where nothing looks detached. */}\n <Text variant=\"body-x-small\" className={styles.typeLabel}>\n {meta.label}\n </Text>\n <Text\n variant=\"title-x-small\"\n weight=\"bold\"\n as=\"h3\"\n className={styles.title}\n >\n {resource.title ?? resource.name}\n </Text>\n </div>\n </div>\n\n <div className={styles.body}>\n {resource.frameworks.length > 0 && (\n <div className={styles.badges}>\n {/* Icon only — the tool's mark is recognisable enough on a card,\n and dropping the label keeps the row narrow. The name stays\n reachable: ToolIcon carries it as an aria-label, and `title`\n surfaces it on hover. Every framework is shown: icons are\n narrow enough that the full set fits, so there is no overflow\n pill to hide the tail behind. */}\n {resource.frameworks.map(f => (\n <span\n key={f}\n className={styles.frameworkBadge}\n title={frameworkLabel(f)}\n >\n <ToolIcon tool={f as FrameworkToken} size={16} />\n </span>\n ))}\n </div>\n )}\n\n {resource.tags.length > 0 && (\n <div className={styles.tags}>\n {visibleTags.map(t => (\n <span key={t} className={styles.tag}>\n #{t}\n </span>\n ))}\n {hiddenTagCount > 0 && (\n <span\n className={styles.tag}\n title={resource.tags.slice(MAX_VISIBLE_TAGS).join(', ')}\n >\n +{hiddenTagCount}\n </span>\n )}\n </div>\n )}\n\n {(containsLabel(resource) || parentCount > 0) && (\n <div className={styles.relRow}>\n {containsLabel(resource) && (\n <span className={styles.relChip} title=\"Contains\">\n {containsLabel(resource)}\n </span>\n )}\n {parentCount > 0 && (\n <span className={styles.relChip} title=\"Belongs to a container\">\n part of {parentCount}\n </span>\n )}\n </div>\n )}\n\n {(resource.version ||\n ownerLabel ||\n !!counts?.install ||\n !!counts?.view) && (\n <div className={styles.footer}>\n <Text variant=\"body-x-small\" color=\"secondary\">\n {[\n resource.version && `v${resource.version.replace(/^v/, '')}`,\n ownerLabel,\n ]\n .filter(Boolean)\n .join(' · ')}\n </Text>\n {/* Views and installs are the social proof someone browsing\n actually scans for, so they are chips at full foreground rather\n than grey footnotes beside the version. */}\n <span className={styles.countsRow}>\n {!!counts?.view && (\n <span className={styles.count} title={`${counts.view} views`}>\n <RiEyeLine size={14} />\n {counts.view}\n </span>\n )}\n {!!counts?.install && (\n <span\n className={\n counts.install >= POPULAR_THRESHOLD\n ? styles.countPopular\n : styles.count\n }\n title={`${counts.install} installs`}\n >\n {counts.install >= POPULAR_THRESHOLD ? (\n '\\u{1F525}'\n ) : (\n <RiDownloadLine size={14} />\n )}\n {counts.install}\n </span>\n )}\n </span>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import styleInject from '../../node_modules_dist/style-inject/dist/style-inject.es.esm.js';
|
|
2
2
|
|
|
3
|
-
var css_248z = "@layer components {\n /*\n * One continuous surface, generous radius, hairline edges — the macOS card\n * idiom. What made the previous card read as clunky was structural rather\n * than stylistic: a two-tone header band with its own background and bottom\n * rule, a second rule above the footer, and 4px corners. Three hard lines\n * cutting a small card into three boxes.\n *\n * Separation is now done with space and type weight instead of lines and\n * fills, which is the same trade the rest of the plugin makes.\n */\n .
|
|
4
|
-
var styles = {"card":"
|
|
3
|
+
var css_248z = "@layer components {\n /*\n * One continuous surface, generous radius, hairline edges — the macOS card\n * idiom. What made the previous card read as clunky was structural rather\n * than stylistic: a two-tone header band with its own background and bottom\n * rule, a second rule above the footer, and 4px corners. Three hard lines\n * cutting a small card into three boxes.\n *\n * Separation is now done with space and type weight instead of lines and\n * fills, which is the same trade the rest of the plugin makes.\n */\n .ResourceCard_card__445ab09501 {\n background: var(--bui-bg-neutral-1);\n border: 1px solid var(--bui-border-1);\n /* The type's colour, kept as the card's identity at a glance in the grid.\n 2px rather than 3: on a 16px radius a thick stripe fights the curve. */\n border-left: 2px solid var(--card-accent);\n border-radius: var(--bui-radius-5);\n overflow: hidden;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n padding: var(--bui-space-4);\n /* Room for the tallest realistic card (2-line title, badges, tags,\n footer) so the grid reads as a grid. The footer's margin-top: auto\n absorbs the slack on lighter cards. */\n min-height: 176px;\n transition: box-shadow 160ms ease,\n transform 160ms cubic-bezier(0.16, 1, 0.3, 1), border-color 160ms ease;\n }\n\n .ResourceCard_card__445ab09501:hover {\n border-color: color-mix(\n in srgb,\n var(--card-accent) 40%,\n var(--bui-border-1)\n );\n border-left-color: var(--card-accent);\n /* Two-part shadow: a soft ambient one and a tighter contact one. A single\n large blur reads as a glow rather than as elevation. */\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06),\n 0 12px 28px -12px color-mix(in srgb, var(--card-accent) 40%, transparent);\n transform: translateY(-2px);\n }\n\n .ResourceCard_card__445ab09501:focus-visible {\n outline: 2px solid var(--bui-ring, var(--card-accent));\n outline-offset: 2px;\n }\n\n /* The header is a fixed-height zone with its contents centred, rather than\n a top-aligned block with a reserved second title line. Same outcome for\n alignment across the grid — everything below starts at one height — but a\n one-line title now sits centred in that zone instead of leaving an\n orphaned gap beneath it, which is what made short-titled cards read as\n detached from their own content. */\n .ResourceCard_header__445ab09501 {\n display: flex;\n align-items: center;\n gap: var(--bui-space-3);\n flex-shrink: 0;\n min-height: 3.5rem;\n }\n\n .ResourceCard_iconBox__445ab09501 {\n background: var(--card-accent-bg);\n color: var(--card-accent);\n border-radius: var(--bui-radius-4);\n padding: var(--bui-space-2);\n display: flex;\n flex-shrink: 0;\n }\n\n .ResourceCard_headerText__445ab09501 {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n }\n\n .ResourceCard_title__445ab09501 {\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n /* Tighter leading than body copy: a two-line title should read as one\n object rather than as two stacked lines. */\n line-height: 1.25;\n }\n\n .ResourceCard_typeLabel__445ab09501 {\n display: block;\n color: var(--bui-fg-secondary);\n font-weight: 600;\n font-size: 0.6875rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n margin-bottom: 2px;\n }\n\n .ResourceCard_body__445ab09501 {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-3);\n flex: 1;\n }\n\n .ResourceCard_badges__445ab09501 {\n display: flex;\n flex-wrap: wrap;\n gap: var(--bui-space-1);\n }\n\n /* Holds a single tool icon, so the padding is square and the badge reads as\n a circle rather than a pill. */\n .ResourceCard_frameworkBadge__445ab09501 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: var(--bui-bg-neutral-2);\n border-radius: var(--bui-radius-full, 999px);\n padding: var(--bui-space-1);\n font-size: 0.8125rem;\n line-height: 1;\n color: var(--bui-fg-primary);\n }\n\n .ResourceCard_tags__445ab09501 {\n display: flex;\n flex-wrap: wrap;\n gap: var(--bui-space-1);\n }\n\n /* Quiet chips rather than bare \"#text\": a chip reads as a discrete value,\n which is what a tag is, and the surface keeps them from competing with the\n description. Not interactive, so they stay low-contrast. */\n .ResourceCard_tag__445ab09501 {\n font-size: 0.6875rem;\n line-height: 1.6;\n padding: 0 var(--bui-space-2);\n border-radius: var(--bui-radius-full, 999px);\n background: var(--bui-bg-neutral-2);\n color: var(--bui-fg-secondary);\n white-space: nowrap;\n }\n\n /* Relationship chips: a compact signal that a card contains members or\n belongs to a container (ADR-0015). The navigable list lives in the detail\n drawer; here it is just a count, so the chips stay quiet and outlined. */\n .ResourceCard_relRow__445ab09501 {\n display: flex;\n flex-wrap: wrap;\n gap: var(--bui-space-1);\n }\n\n .ResourceCard_relChip__445ab09501 {\n font-size: 0.6875rem;\n line-height: 1.6;\n padding: 0 var(--bui-space-2);\n border-radius: var(--bui-radius-full, 999px);\n border: 1px solid var(--bui-border-1);\n color: var(--bui-fg-secondary);\n white-space: nowrap;\n }\n\n /* No rule above the footer — the gap does the separating, and the meta is\n already de-emphasised enough to read as a different tier. */\n .ResourceCard_footer__445ab09501 {\n margin-top: auto;\n padding-top: var(--bui-space-1);\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--bui-space-2);\n font-size: 0.6875rem;\n }\n\n .ResourceCard_countsRow__445ab09501 {\n display: flex;\n align-items: center;\n gap: var(--bui-space-2);\n flex-shrink: 0;\n }\n\n /* Chips, not footnotes: views and installs are the signal a browser scans\n for, so they carry a surface and the full foreground colour while the\n version/owner meta stays secondary beside them. */\n .ResourceCard_count__445ab09501 {\n display: inline-flex;\n align-items: center;\n gap: var(--bui-space-1);\n padding: 2px var(--bui-space-2);\n border-radius: var(--bui-radius-full, 999px);\n background: var(--bui-bg-neutral-2);\n font-size: 0.75rem;\n font-weight: 600;\n color: var(--bui-fg-primary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n /* The glyph takes the card's accent while the number stays foreground: a\n fully monochrome chip reads as detached on a card that is otherwise\n colour-coded, and colouring the number too would cost its legibility. */\n .ResourceCard_count__445ab09501 svg {\n color: var(--card-accent);\n flex-shrink: 0;\n }\n\n /* Past the popularity threshold the install chip takes the type's own\n colour — the one place on the card where the accent marks a value rather\n than an identity. */\n .ResourceCard_countPopular__445ab09501 {\n background: var(--card-accent-bg);\n color: var(--card-accent);\n }\n}\n";
|
|
4
|
+
var styles = {"card":"ResourceCard_card__445ab09501","header":"ResourceCard_header__445ab09501","iconBox":"ResourceCard_iconBox__445ab09501","headerText":"ResourceCard_headerText__445ab09501","title":"ResourceCard_title__445ab09501","typeLabel":"ResourceCard_typeLabel__445ab09501","body":"ResourceCard_body__445ab09501","badges":"ResourceCard_badges__445ab09501","frameworkBadge":"ResourceCard_frameworkBadge__445ab09501","tags":"ResourceCard_tags__445ab09501","tag":"ResourceCard_tag__445ab09501","relRow":"ResourceCard_relRow__445ab09501","relChip":"ResourceCard_relChip__445ab09501","footer":"ResourceCard_footer__445ab09501","countsRow":"ResourceCard_countsRow__445ab09501","count":"ResourceCard_count__445ab09501","countPopular":"ResourceCard_countPopular__445ab09501 ResourceCard_count__445ab09501"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
7
7
|
export { styles as default };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import ReactMarkdown from 'react-markdown';
|
|
4
|
-
import {
|
|
4
|
+
import { Text, Skeleton, Flex, Button, Box, ButtonIcon, Link } from '@backstage/ui';
|
|
5
5
|
import { RiCloseLine, RiEyeLine, RiInstallLine, RiFileCopyLine, RiDownloadLine, RiExternalLinkLine } from '@remixicon/react';
|
|
6
6
|
import { useApi } from '@backstage/core-plugin-api';
|
|
7
7
|
import { EntityRefLink } from '@backstage/plugin-catalog-react';
|
|
@@ -9,6 +9,9 @@ import { getBodyShape, hasCopyableBody, hasDownloadableArtifact, isParseableEnti
|
|
|
9
9
|
import { devAiHubResourceApiRef } from '../../api/DevAiHubResourceClient.esm.js';
|
|
10
10
|
import { useResourceBody } from '../../hooks/useResourceBody.esm.js';
|
|
11
11
|
import { useTelemetryCounts } from '../../hooks/useTelemetryCounts.esm.js';
|
|
12
|
+
import { CollapsibleSection } from './CollapsibleSection.esm.js';
|
|
13
|
+
import { JsonBody } from './JsonBody.esm.js';
|
|
14
|
+
import { ResourceRelationships } from './ResourceRelationships.esm.js';
|
|
12
15
|
import { ToolIcon } from '../ToolIcon/ToolIcon.esm.js';
|
|
13
16
|
import { ResourceInstallDialog } from './ResourceInstallDialog.esm.js';
|
|
14
17
|
import { stripFrontmatter } from './stripFrontmatter.esm.js';
|
|
@@ -18,7 +21,9 @@ import styles from './ResourceDetailPanel.module.css.esm.js';
|
|
|
18
21
|
const ENTITY_REF_ROWS = /* @__PURE__ */ new Set(["Owner", "Entity ref"]);
|
|
19
22
|
function ResourceDetailPanel({
|
|
20
23
|
resource: resourceProp,
|
|
21
|
-
onClose
|
|
24
|
+
onClose,
|
|
25
|
+
items = [],
|
|
26
|
+
onOpen
|
|
22
27
|
}) {
|
|
23
28
|
const isOpen = !!resourceProp;
|
|
24
29
|
const [displayResource, setDisplayResource] = useState(resourceProp);
|
|
@@ -61,6 +66,28 @@ function ResourceDetailPanel({
|
|
|
61
66
|
["Version", resource.version],
|
|
62
67
|
["Entity ref", resource.entityRef]
|
|
63
68
|
];
|
|
69
|
+
const collapsibleContent = bodyShape === "json";
|
|
70
|
+
const contentInner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
71
|
+
!actionable && /* @__PURE__ */ jsx(Text, { variant: "body-small", as: "p", color: "secondary", children: "No content location published for this resource." }),
|
|
72
|
+
actionable && bodyState.loading && /* @__PURE__ */ jsxs("div", { className: styles.bodyLoading, children: [
|
|
73
|
+
/* @__PURE__ */ jsx(Skeleton, { width: "100%", height: 14 }),
|
|
74
|
+
/* @__PURE__ */ jsx(Skeleton, { width: "85%", height: 14 }),
|
|
75
|
+
/* @__PURE__ */ jsx(Skeleton, { width: "60%", height: 14 })
|
|
76
|
+
] }),
|
|
77
|
+
actionable && bodyState.error === "not-found" && /* @__PURE__ */ jsx(Text, { variant: "body-small", as: "p", color: "secondary", children: "Content not available \u2014 it may have been removed, or you may not have access to it." }),
|
|
78
|
+
actionable && bodyState.error === "upstream" && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "2", children: [
|
|
79
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-small", as: "p", color: "secondary", children: "Couldn\u2019t fetch the content from its source." }),
|
|
80
|
+
/* @__PURE__ */ jsx(Button, { size: "small", variant: "tertiary", onPress: bodyState.retry, children: "Retry" })
|
|
81
|
+
] }),
|
|
82
|
+
bodyState.body && bodyShape === "json" && /* @__PURE__ */ jsx(
|
|
83
|
+
JsonBody,
|
|
84
|
+
{
|
|
85
|
+
content: bodyState.body.content,
|
|
86
|
+
className: styles.codeBlock
|
|
87
|
+
}
|
|
88
|
+
),
|
|
89
|
+
bodyState.body && bodyShape === "markdown" && /* @__PURE__ */ jsx("div", { className: styles.markdown, children: /* @__PURE__ */ jsx(ReactMarkdown, { children: stripFrontmatter(bodyState.body.content) }) })
|
|
90
|
+
] });
|
|
64
91
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
65
92
|
/* @__PURE__ */ jsx(
|
|
66
93
|
"div",
|
|
@@ -164,30 +191,33 @@ function ResourceDetailPanel({
|
|
|
164
191
|
}
|
|
165
192
|
)
|
|
166
193
|
] }),
|
|
167
|
-
/* @__PURE__ */
|
|
194
|
+
onOpen && /* @__PURE__ */ jsx(
|
|
195
|
+
ResourceRelationships,
|
|
196
|
+
{
|
|
197
|
+
resource,
|
|
198
|
+
items,
|
|
199
|
+
onOpen
|
|
200
|
+
}
|
|
201
|
+
),
|
|
202
|
+
/* @__PURE__ */ jsx("div", { className: styles.section, children: collapsibleContent ? (
|
|
203
|
+
// Keyed on entityRef: the drawer stays mounted across a
|
|
204
|
+
// relationship navigation (only `displayResource` swaps), so
|
|
205
|
+
// without a key React reuses this instance and carries a
|
|
206
|
+
// collapsed choice from the previous resource onto the next
|
|
207
|
+
// one, silently defeating "expanded by default".
|
|
208
|
+
/* @__PURE__ */ jsx(
|
|
209
|
+
CollapsibleSection,
|
|
210
|
+
{
|
|
211
|
+
label: "Content",
|
|
212
|
+
defaultOpen: true,
|
|
213
|
+
children: contentInner
|
|
214
|
+
},
|
|
215
|
+
resource.entityRef
|
|
216
|
+
)
|
|
217
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
168
218
|
/* @__PURE__ */ jsx(Text, { variant: "body-x-small", color: "secondary", weight: "bold", children: "Content" }),
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
/* @__PURE__ */ jsx(Skeleton, { width: "100%", height: 14 }),
|
|
172
|
-
/* @__PURE__ */ jsx(Skeleton, { width: "85%", height: 14 }),
|
|
173
|
-
/* @__PURE__ */ jsx(Skeleton, { width: "60%", height: 14 })
|
|
174
|
-
] }),
|
|
175
|
-
actionable && bodyState.error === "not-found" && /* @__PURE__ */ jsx(Text, { variant: "body-small", as: "p", color: "secondary", children: "Content not available \u2014 it may have been removed, or you may not have access to it." }),
|
|
176
|
-
actionable && bodyState.error === "upstream" && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "2", children: [
|
|
177
|
-
/* @__PURE__ */ jsx(Text, { variant: "body-small", as: "p", color: "secondary", children: "Couldn\u2019t fetch the content from its source." }),
|
|
178
|
-
/* @__PURE__ */ jsx(
|
|
179
|
-
Button,
|
|
180
|
-
{
|
|
181
|
-
size: "small",
|
|
182
|
-
variant: "tertiary",
|
|
183
|
-
onPress: bodyState.retry,
|
|
184
|
-
children: "Retry"
|
|
185
|
-
}
|
|
186
|
-
)
|
|
187
|
-
] }),
|
|
188
|
-
bodyState.body && bodyShape === "json" && /* @__PURE__ */ jsx("pre", { className: styles.codeBlock, children: /* @__PURE__ */ jsx("code", { children: bodyState.body.content }) }),
|
|
189
|
-
bodyState.body && bodyShape === "markdown" && /* @__PURE__ */ jsx("div", { className: styles.markdown, children: /* @__PURE__ */ jsx(ReactMarkdown, { children: stripFrontmatter(bodyState.body.content) }) })
|
|
190
|
-
] }),
|
|
219
|
+
contentInner
|
|
220
|
+
] }) }),
|
|
191
221
|
resource.frameworks.length > 0 && /* @__PURE__ */ jsxs("div", { className: styles.section, children: [
|
|
192
222
|
/* @__PURE__ */ jsx(Text, { variant: "body-x-small", color: "secondary", weight: "bold", children: "Works with" }),
|
|
193
223
|
/* @__PURE__ */ jsx("div", { className: styles.badges, children: resource.frameworks.map((f) => /* @__PURE__ */ jsxs("span", { className: styles.frameworkBadge, children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceDetailPanel.esm.js","sources":["../../../src/components/AiResourcesPage/ResourceDetailPanel.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport {\n Box,\n Button,\n ButtonIcon,\n Flex,\n Link,\n Skeleton,\n Text,\n} from '@backstage/ui';\nimport {\n RiCloseLine,\n RiDownloadLine,\n RiExternalLinkLine,\n RiEyeLine,\n RiFileCopyLine,\n RiInstallLine,\n} from '@remixicon/react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { EntityRefLink } from '@backstage/plugin-catalog-react';\nimport {\n getBodyShape,\n hasCopyableBody,\n hasDownloadableArtifact,\n isParseableEntityRef,\n type FrameworkToken,\n type ResourceSummary,\n} from '@nospt/plugin-dev-ai-hub-common';\nimport { devAiHubResourceApiRef } from '../../api/DevAiHubResourceClient';\nimport { useResourceBody } from '../../hooks/useResourceBody';\nimport { useTelemetryCounts } from '../../hooks/useTelemetryCounts';\nimport { ToolIcon } from '../ToolIcon';\nimport { ResourceInstallDialog } from './ResourceInstallDialog';\nimport { stripFrontmatter } from './stripFrontmatter';\nimport { frameworkLabel, getTypeMeta } from './typeMeta';\nimport styles from './ResourceDetailPanel.module.css';\n\ninterface ResourceDetailPanelProps {\n resource: ResourceSummary | undefined;\n onClose: () => void;\n}\n\n/**\n * Metadata rows that name a catalog entity and should link there. Rendering\n * still falls back to plain text per-row when the value isn't a parseable\n * ref (`isParseableEntityRef`) — Owner isn't guaranteed parseable, since\n * `toResourceSummary` deliberately passes a malformed one through rather\n * than dropping the resource, and `EntityRefLink` throws on a ref it can't\n * parse. Entity ref is always valid (built with `stringifyEntityRef`), so it\n * always renders as a link.\n */\nconst ENTITY_REF_ROWS = new Set(['Owner', 'Entity ref']);\n\n/**\n * The detail drawer (legacy pattern, URL-param driven). Metadata renders from\n * the catalog `ResourceSummary`; the body is fetched lazily on open from the\n * body resolver (issue #30) and rendered by shape. Resources without a\n * source-location are browsable but not actionable — no action buttons.\n */\nexport function ResourceDetailPanel({\n resource: resourceProp,\n onClose,\n}: ResourceDetailPanelProps) {\n const isOpen = !!resourceProp;\n // The drawer slides out on close (issue: \"add some flowers\"), which needs\n // content to stay rendered during the exit transition — the `resource` prop\n // itself goes undefined the instant the URL param clears. `displayResource`\n // mirrors the last real resource and is only ever updated while one is\n // present, so the drawer keeps showing it while animating out instead of\n // blanking.\n const [displayResource, setDisplayResource] = useState(resourceProp);\n useEffect(() => {\n if (resourceProp) setDisplayResource(resourceProp);\n }, [resourceProp]);\n\n const api = useApi(devAiHubResourceApiRef);\n\n // A `view` is a deliberate look at one resource, so it is recorded here —\n // when the drawer opens — and not on card render, where merely loading the\n // page counted a view for every card in the grid. Keyed on the *live* prop,\n // not `displayResource`: that one stays set through the exit animation, and\n // closing must not count a second view.\n const openedRef = resourceProp?.entityRef;\n useEffect(() => {\n if (openedRef) api.track(openedRef, 'view');\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [openedRef]);\n\n const actionable = !!displayResource?.sourceLocation;\n const bodyState = useResourceBody(displayResource?.entityRef, actionable);\n const counts = useTelemetryCounts(displayResource?.entityRef);\n const [installOpen, setInstallOpen] = useState(false);\n const [copied, setCopied] = useState(false);\n\n if (!displayResource) return null;\n\n const resource = displayResource;\n const meta = getTypeMeta(resource.type);\n const bodyShape = getBodyShape(resource.type);\n\n const handleCopy = async () => {\n if (!bodyState.body) return;\n await navigator.clipboard.writeText(bodyState.body.content);\n api.track(resource.entityRef, 'copy');\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n const handleDownload = () => {\n api.track(resource.entityRef, 'download');\n return api.downloadEntityBody(resource.entityRef);\n };\n\n const handleInstall = () => {\n api.track(resource.entityRef, 'install');\n setInstallOpen(true);\n };\n\n const metadataRows: [string, string | undefined][] = [\n ['Type', meta.label],\n ['Lifecycle', resource.lifecycle || undefined],\n ['Owner', resource.owner],\n ['Version', resource.version],\n ['Entity ref', resource.entityRef],\n ];\n\n return (\n <>\n <div\n className={styles.overlay}\n data-open={isOpen}\n onClick={onClose}\n role=\"presentation\"\n aria-hidden={!isOpen}\n />\n <div\n className={styles.drawer}\n data-open={isOpen}\n style={{ '--drawer-accent': meta.color } as React.CSSProperties}\n role=\"dialog\"\n aria-label={resource.title ?? resource.name}\n aria-hidden={!isOpen}\n >\n <Flex className={styles.header}>\n <div\n className={styles.iconBox}\n style={\n {\n '--card-accent': meta.color,\n '--card-accent-bg': meta.colorBg,\n } as React.CSSProperties\n }\n >\n <meta.Icon size={20} />\n </div>\n <Box className={styles.headerText}>\n <Text variant=\"title-small\" weight=\"bold\" as=\"h2\">\n {resource.title ?? resource.name}\n </Text>\n <Text\n variant=\"body-x-small\"\n style={{ color: meta.color, fontWeight: 600 }}\n >\n {meta.label}\n </Text>\n </Box>\n <ButtonIcon\n icon={<RiCloseLine />}\n variant=\"tertiary\"\n onClick={onClose}\n aria-label=\"Close\"\n />\n </Flex>\n\n <div className={styles.body}>\n {resource.description && (\n <Text variant=\"body-small\" as=\"p\">\n {resource.description}\n </Text>\n )}\n\n {(!!counts?.view || !!counts?.install) && (\n <div className={styles.statsRow}>\n {!!counts?.view && (\n <span className={styles.statItem}>\n <RiEyeLine size={14} /> {counts.view} views\n </span>\n )}\n {!!counts?.install && (\n <span className={styles.statItem}>\n <RiInstallLine size={14} /> {counts.install} installs\n </span>\n )}\n </div>\n )}\n\n {actionable && (\n <div className={styles.actions}>\n {hasCopyableBody(resource.type) && (\n <Button\n size=\"small\"\n variant=\"secondary\"\n iconStart={<RiFileCopyLine />}\n isDisabled={!bodyState.body}\n onPress={handleCopy}\n >\n {copied ? 'Copied!' : 'Copy'}\n </Button>\n )}\n {hasDownloadableArtifact(resource.type) && (\n <Button\n size=\"small\"\n variant=\"secondary\"\n iconStart={<RiDownloadLine />}\n onPress={handleDownload}\n >\n Download\n </Button>\n )}\n <Button\n size=\"small\"\n variant=\"primary\"\n iconStart={<RiInstallLine />}\n onPress={handleInstall}\n >\n Install\n </Button>\n </div>\n )}\n\n <div className={styles.section}>\n <Text variant=\"body-x-small\" color=\"secondary\" weight=\"bold\">\n Content\n </Text>\n {!actionable && (\n <Text variant=\"body-small\" as=\"p\" color=\"secondary\">\n No content location published for this resource.\n </Text>\n )}\n {actionable && bodyState.loading && (\n <div className={styles.bodyLoading}>\n <Skeleton width=\"100%\" height={14} />\n <Skeleton width=\"85%\" height={14} />\n <Skeleton width=\"60%\" height={14} />\n </div>\n )}\n {actionable && bodyState.error === 'not-found' && (\n <Text variant=\"body-small\" as=\"p\" color=\"secondary\">\n Content not available — it may have been removed, or you may not\n have access to it.\n </Text>\n )}\n {actionable && bodyState.error === 'upstream' && (\n <Flex align=\"center\" gap=\"2\">\n <Text variant=\"body-small\" as=\"p\" color=\"secondary\">\n Couldn’t fetch the content from its source.\n </Text>\n <Button\n size=\"small\"\n variant=\"tertiary\"\n onPress={bodyState.retry}\n >\n Retry\n </Button>\n </Flex>\n )}\n {bodyState.body && bodyShape === 'json' && (\n <pre className={styles.codeBlock}>\n <code>{bodyState.body.content}</code>\n </pre>\n )}\n {bodyState.body && bodyShape === 'markdown' && (\n <div className={styles.markdown}>\n <ReactMarkdown>\n {stripFrontmatter(bodyState.body.content)}\n </ReactMarkdown>\n </div>\n )}\n </div>\n\n {resource.frameworks.length > 0 && (\n <div className={styles.section}>\n <Text variant=\"body-x-small\" color=\"secondary\" weight=\"bold\">\n Works with\n </Text>\n <div className={styles.badges}>\n {resource.frameworks.map(f => (\n <span key={f} className={styles.frameworkBadge}>\n <ToolIcon tool={f as FrameworkToken} size={14} />\n {frameworkLabel(f)}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {resource.tags.length > 0 && (\n <div className={styles.section}>\n <Text variant=\"body-x-small\" color=\"secondary\" weight=\"bold\">\n Tags\n </Text>\n <div className={styles.badges}>\n {resource.tags.map(t => (\n <span key={t} className={styles.tag}>\n #{t}\n </span>\n ))}\n </div>\n </div>\n )}\n\n <div className={styles.section}>\n <Text variant=\"body-x-small\" color=\"secondary\" weight=\"bold\">\n Metadata\n </Text>\n <dl className={styles.metaList}>\n {metadataRows\n .filter(([, v]) => v)\n .map(([k, v]) => (\n <div key={k} className={styles.metaRow}>\n <dt>{k}</dt>\n <dd>\n {ENTITY_REF_ROWS.has(k) && isParseableEntityRef(v!) ? (\n <EntityRefLink entityRef={v!}>{v}</EntityRefLink>\n ) : (\n v\n )}\n </dd>\n </div>\n ))}\n </dl>\n </div>\n\n {resource.sourceLocation && (\n <div className={styles.section}>\n <Link\n href={resource.sourceLocation.replace(/^url:/, '')}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <span className={styles.sourceLinkContent}>\n <RiExternalLinkLine size={14} /> View source\n </span>\n </Link>\n </div>\n )}\n </div>\n </div>\n\n <ResourceInstallDialog\n resource={resource}\n body={bodyState.body}\n isOpen={installOpen}\n onOpenChange={setInstallOpen}\n />\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ResourceDetailPanel.esm.js","sources":["../../../src/components/AiResourcesPage/ResourceDetailPanel.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport {\n Box,\n Button,\n ButtonIcon,\n Flex,\n Link,\n Skeleton,\n Text,\n} from '@backstage/ui';\nimport {\n RiCloseLine,\n RiDownloadLine,\n RiExternalLinkLine,\n RiEyeLine,\n RiFileCopyLine,\n RiInstallLine,\n} from '@remixicon/react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { EntityRefLink } from '@backstage/plugin-catalog-react';\nimport {\n getBodyShape,\n hasCopyableBody,\n hasDownloadableArtifact,\n isParseableEntityRef,\n type FrameworkToken,\n type ResourceSummary,\n} from '@nospt/plugin-dev-ai-hub-common';\nimport { devAiHubResourceApiRef } from '../../api/DevAiHubResourceClient';\nimport { useResourceBody } from '../../hooks/useResourceBody';\nimport { useTelemetryCounts } from '../../hooks/useTelemetryCounts';\nimport { CollapsibleSection } from './CollapsibleSection';\nimport { JsonBody } from './JsonBody';\nimport { ResourceRelationships } from './ResourceRelationships';\nimport { ToolIcon } from '../ToolIcon';\nimport { ResourceInstallDialog } from './ResourceInstallDialog';\nimport { stripFrontmatter } from './stripFrontmatter';\nimport { frameworkLabel, getTypeMeta } from './typeMeta';\nimport styles from './ResourceDetailPanel.module.css';\n\ninterface ResourceDetailPanelProps {\n resource: ResourceSummary | undefined;\n onClose: () => void;\n /** Every caller-visible resource, so relationship refs can be resolved. */\n items?: ResourceSummary[];\n /** Opens another resource's detail (the existing `?resource=` swap). */\n onOpen?: (entityRef: string) => void;\n}\n\n/**\n * Metadata rows that name a catalog entity and should link there. Rendering\n * still falls back to plain text per-row when the value isn't a parseable\n * ref (`isParseableEntityRef`) — Owner isn't guaranteed parseable, since\n * `toResourceSummary` deliberately passes a malformed one through rather\n * than dropping the resource, and `EntityRefLink` throws on a ref it can't\n * parse. Entity ref is always valid (built with `stringifyEntityRef`), so it\n * always renders as a link.\n */\nconst ENTITY_REF_ROWS = new Set(['Owner', 'Entity ref']);\n\n/**\n * The detail drawer (legacy pattern, URL-param driven). Metadata renders from\n * the catalog `ResourceSummary`; the body is fetched lazily on open from the\n * body resolver (issue #30) and rendered by shape. Resources without a\n * source-location are browsable but not actionable — no action buttons.\n */\nexport function ResourceDetailPanel({\n resource: resourceProp,\n onClose,\n items = [],\n onOpen,\n}: ResourceDetailPanelProps) {\n const isOpen = !!resourceProp;\n // The drawer slides out on close (issue: \"add some flowers\"), which needs\n // content to stay rendered during the exit transition — the `resource` prop\n // itself goes undefined the instant the URL param clears. `displayResource`\n // mirrors the last real resource and is only ever updated while one is\n // present, so the drawer keeps showing it while animating out instead of\n // blanking.\n const [displayResource, setDisplayResource] = useState(resourceProp);\n useEffect(() => {\n if (resourceProp) setDisplayResource(resourceProp);\n }, [resourceProp]);\n\n const api = useApi(devAiHubResourceApiRef);\n\n // A `view` is a deliberate look at one resource, so it is recorded here —\n // when the drawer opens — and not on card render, where merely loading the\n // page counted a view for every card in the grid. Keyed on the *live* prop,\n // not `displayResource`: that one stays set through the exit animation, and\n // closing must not count a second view.\n const openedRef = resourceProp?.entityRef;\n useEffect(() => {\n if (openedRef) api.track(openedRef, 'view');\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [openedRef]);\n\n const actionable = !!displayResource?.sourceLocation;\n const bodyState = useResourceBody(displayResource?.entityRef, actionable);\n const counts = useTelemetryCounts(displayResource?.entityRef);\n const [installOpen, setInstallOpen] = useState(false);\n const [copied, setCopied] = useState(false);\n\n if (!displayResource) return null;\n\n const resource = displayResource;\n const meta = getTypeMeta(resource.type);\n const bodyShape = getBodyShape(resource.type);\n\n const handleCopy = async () => {\n if (!bodyState.body) return;\n await navigator.clipboard.writeText(bodyState.body.content);\n api.track(resource.entityRef, 'copy');\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n const handleDownload = () => {\n api.track(resource.entityRef, 'download');\n return api.downloadEntityBody(resource.entityRef);\n };\n\n const handleInstall = () => {\n api.track(resource.entityRef, 'install');\n setInstallOpen(true);\n };\n\n const metadataRows: [string, string | undefined][] = [\n ['Type', meta.label],\n ['Lifecycle', resource.lifecycle || undefined],\n ['Owner', resource.owner],\n ['Version', resource.version],\n ['Entity ref', resource.entityRef],\n ];\n\n // `plugin`/`marketplace` bodies are the full manifest JSON (ADR-0014) —\n // long enough to bury everything below them, so their Content collapses.\n const collapsibleContent = bodyShape === 'json';\n\n const contentInner = (\n <>\n {!actionable && (\n <Text variant=\"body-small\" as=\"p\" color=\"secondary\">\n No content location published for this resource.\n </Text>\n )}\n {actionable && bodyState.loading && (\n <div className={styles.bodyLoading}>\n <Skeleton width=\"100%\" height={14} />\n <Skeleton width=\"85%\" height={14} />\n <Skeleton width=\"60%\" height={14} />\n </div>\n )}\n {actionable && bodyState.error === 'not-found' && (\n <Text variant=\"body-small\" as=\"p\" color=\"secondary\">\n Content not available — it may have been removed, or you may not have\n access to it.\n </Text>\n )}\n {actionable && bodyState.error === 'upstream' && (\n <Flex align=\"center\" gap=\"2\">\n <Text variant=\"body-small\" as=\"p\" color=\"secondary\">\n Couldn’t fetch the content from its source.\n </Text>\n <Button size=\"small\" variant=\"tertiary\" onPress={bodyState.retry}>\n Retry\n </Button>\n </Flex>\n )}\n {bodyState.body && bodyShape === 'json' && (\n <JsonBody\n content={bodyState.body.content}\n className={styles.codeBlock}\n />\n )}\n {bodyState.body && bodyShape === 'markdown' && (\n <div className={styles.markdown}>\n <ReactMarkdown>\n {stripFrontmatter(bodyState.body.content)}\n </ReactMarkdown>\n </div>\n )}\n </>\n );\n\n return (\n <>\n <div\n className={styles.overlay}\n data-open={isOpen}\n onClick={onClose}\n role=\"presentation\"\n aria-hidden={!isOpen}\n />\n <div\n className={styles.drawer}\n data-open={isOpen}\n style={{ '--drawer-accent': meta.color } as React.CSSProperties}\n role=\"dialog\"\n aria-label={resource.title ?? resource.name}\n aria-hidden={!isOpen}\n >\n <Flex className={styles.header}>\n <div\n className={styles.iconBox}\n style={\n {\n '--card-accent': meta.color,\n '--card-accent-bg': meta.colorBg,\n } as React.CSSProperties\n }\n >\n <meta.Icon size={20} />\n </div>\n <Box className={styles.headerText}>\n <Text variant=\"title-small\" weight=\"bold\" as=\"h2\">\n {resource.title ?? resource.name}\n </Text>\n <Text\n variant=\"body-x-small\"\n style={{ color: meta.color, fontWeight: 600 }}\n >\n {meta.label}\n </Text>\n </Box>\n <ButtonIcon\n icon={<RiCloseLine />}\n variant=\"tertiary\"\n onClick={onClose}\n aria-label=\"Close\"\n />\n </Flex>\n\n <div className={styles.body}>\n {resource.description && (\n <Text variant=\"body-small\" as=\"p\">\n {resource.description}\n </Text>\n )}\n\n {(!!counts?.view || !!counts?.install) && (\n <div className={styles.statsRow}>\n {!!counts?.view && (\n <span className={styles.statItem}>\n <RiEyeLine size={14} /> {counts.view} views\n </span>\n )}\n {!!counts?.install && (\n <span className={styles.statItem}>\n <RiInstallLine size={14} /> {counts.install} installs\n </span>\n )}\n </div>\n )}\n\n {actionable && (\n <div className={styles.actions}>\n {hasCopyableBody(resource.type) && (\n <Button\n size=\"small\"\n variant=\"secondary\"\n iconStart={<RiFileCopyLine />}\n isDisabled={!bodyState.body}\n onPress={handleCopy}\n >\n {copied ? 'Copied!' : 'Copy'}\n </Button>\n )}\n {hasDownloadableArtifact(resource.type) && (\n <Button\n size=\"small\"\n variant=\"secondary\"\n iconStart={<RiDownloadLine />}\n onPress={handleDownload}\n >\n Download\n </Button>\n )}\n <Button\n size=\"small\"\n variant=\"primary\"\n iconStart={<RiInstallLine />}\n onPress={handleInstall}\n >\n Install\n </Button>\n </div>\n )}\n\n {onOpen && (\n <ResourceRelationships\n resource={resource}\n items={items}\n onOpen={onOpen}\n />\n )}\n\n <div className={styles.section}>\n {collapsibleContent ? (\n // Keyed on entityRef: the drawer stays mounted across a\n // relationship navigation (only `displayResource` swaps), so\n // without a key React reuses this instance and carries a\n // collapsed choice from the previous resource onto the next\n // one, silently defeating \"expanded by default\".\n <CollapsibleSection\n key={resource.entityRef}\n label=\"Content\"\n defaultOpen\n >\n {contentInner}\n </CollapsibleSection>\n ) : (\n <>\n <Text variant=\"body-x-small\" color=\"secondary\" weight=\"bold\">\n Content\n </Text>\n {contentInner}\n </>\n )}\n </div>\n\n {resource.frameworks.length > 0 && (\n <div className={styles.section}>\n <Text variant=\"body-x-small\" color=\"secondary\" weight=\"bold\">\n Works with\n </Text>\n <div className={styles.badges}>\n {resource.frameworks.map(f => (\n <span key={f} className={styles.frameworkBadge}>\n <ToolIcon tool={f as FrameworkToken} size={14} />\n {frameworkLabel(f)}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {resource.tags.length > 0 && (\n <div className={styles.section}>\n <Text variant=\"body-x-small\" color=\"secondary\" weight=\"bold\">\n Tags\n </Text>\n <div className={styles.badges}>\n {resource.tags.map(t => (\n <span key={t} className={styles.tag}>\n #{t}\n </span>\n ))}\n </div>\n </div>\n )}\n\n <div className={styles.section}>\n <Text variant=\"body-x-small\" color=\"secondary\" weight=\"bold\">\n Metadata\n </Text>\n <dl className={styles.metaList}>\n {metadataRows\n .filter(([, v]) => v)\n .map(([k, v]) => (\n <div key={k} className={styles.metaRow}>\n <dt>{k}</dt>\n <dd>\n {ENTITY_REF_ROWS.has(k) && isParseableEntityRef(v!) ? (\n <EntityRefLink entityRef={v!}>{v}</EntityRefLink>\n ) : (\n v\n )}\n </dd>\n </div>\n ))}\n </dl>\n </div>\n\n {resource.sourceLocation && (\n <div className={styles.section}>\n <Link\n href={resource.sourceLocation.replace(/^url:/, '')}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <span className={styles.sourceLinkContent}>\n <RiExternalLinkLine size={14} /> View source\n </span>\n </Link>\n </div>\n )}\n </div>\n </div>\n\n <ResourceInstallDialog\n resource={resource}\n body={bodyState.body}\n isOpen={installOpen}\n onOpenChange={setInstallOpen}\n />\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,8 @@ import { useApi } from '@backstage/core-plugin-api';
|
|
|
8
8
|
import { getBodyShape, getMarketplaceRepoSlug, getMarketplaceAddCommands, getInstallSteps, expandInstallFrameworks, getResourceInstallTarget, hasCopyableBody, hasDownloadableArtifact, getAgentInstallLinks, getMcpInstallLinks, getMarketplaceInstallTemplate, getMarketplaceTeamSnippet } from '@nospt/plugin-dev-ai-hub-common';
|
|
9
9
|
import { devAiHubResourceApiRef } from '../../api/DevAiHubResourceClient.esm.js';
|
|
10
10
|
import { ToolIcon } from '../ToolIcon/ToolIcon.esm.js';
|
|
11
|
+
import { CollapsibleSection } from './CollapsibleSection.esm.js';
|
|
12
|
+
import { JsonBody } from './JsonBody.esm.js';
|
|
11
13
|
import { frameworkLabel } from './typeMeta.esm.js';
|
|
12
14
|
import styles from './ResourceInstallDialog.module.css.esm.js';
|
|
13
15
|
|
|
@@ -191,7 +193,7 @@ function ResourceInstallDialog({
|
|
|
191
193
|
const marketplaceRepoSlug = resource.type === "marketplace" ? getMarketplaceRepoSlug(resource.sourceLocation) : void 0;
|
|
192
194
|
const marketplaceAddCommands = marketplaceRepoSlug ? getMarketplaceAddCommands(resource.frameworks, marketplaceRepoSlug) : [];
|
|
193
195
|
const hasMarketplaceJourney = marketplaceAddCommands.length > 0;
|
|
194
|
-
const showBodyDoc = resource.type === "plugin" || resource.type === "marketplace" && !hasMarketplaceJourney;
|
|
196
|
+
const showBodyDoc = shape === "markdown" && (resource.type === "plugin" || resource.type === "marketplace" && !hasMarketplaceJourney);
|
|
195
197
|
const usesPromptInstall = resource.type === "skill" || resource.type === "hook";
|
|
196
198
|
const installSteps = usesPromptInstall ? getInstallSteps(
|
|
197
199
|
resource.type,
|
|
@@ -240,7 +242,7 @@ function ResourceInstallDialog({
|
|
|
240
242
|
] }),
|
|
241
243
|
/* @__PURE__ */ jsx(DialogBody, { children: /* @__PURE__ */ jsxs("div", { className: styles.content, children: [
|
|
242
244
|
/* @__PURE__ */ jsx(Text, { variant: "body-small", as: "p", children: TYPE_HINTS[resource.type] }),
|
|
243
|
-
body && shape === "json" && /* @__PURE__ */ jsx(
|
|
245
|
+
body && shape === "json" && /* @__PURE__ */ jsx(CollapsibleSection, { label: "Content", children: /* @__PURE__ */ jsx(JsonBody, { content: body.content, className: styles.codeBlock }) }),
|
|
244
246
|
hasMarketplaceJourney && marketplaceRepoSlug && /* @__PURE__ */ jsx(
|
|
245
247
|
MarketplaceJourney,
|
|
246
248
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceInstallDialog.esm.js","sources":["../../../src/components/AiResourcesPage/ResourceInstallDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport {\n Button,\n ButtonIcon,\n ButtonLink,\n Dialog,\n DialogBody,\n DialogHeader,\n Text,\n} from '@backstage/ui';\nimport {\n RiCheckLine,\n RiDownloadLine,\n RiExternalLinkLine,\n RiFileCopyLine,\n} from '@remixicon/react';\nimport { siClaude } from 'simple-icons';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n expandInstallFrameworks,\n getAgentInstallLinks,\n getBodyShape,\n getMarketplaceAddCommands,\n getMcpInstallLinks,\n getMarketplaceInstallTemplate,\n getMarketplaceRepoSlug,\n getMarketplaceTeamSnippet,\n getInstallSteps,\n getResourceInstallTarget,\n hasCopyableBody,\n hasDownloadableArtifact,\n type FrameworkToken,\n type MarketplaceAddCommand,\n type ResourceInstallTarget,\n type ResourceSummary,\n} from '@nospt/plugin-dev-ai-hub-common';\nimport {\n devAiHubResourceApiRef,\n type ResourceBody,\n} from '../../api/DevAiHubResourceClient';\nimport { ToolIcon } from '../ToolIcon';\nimport { frameworkLabel } from './typeMeta';\nimport styles from './ResourceInstallDialog.module.css';\n\ninterface ResourceInstallDialogProps {\n resource: ResourceSummary;\n body?: ResourceBody;\n isOpen: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nconst TYPE_HINTS: Record<ResourceSummary['type'], string> = {\n skill:\n 'Download the skill (multi-file skills arrive as one zip) and extract it into the path for your framework.',\n agent:\n 'Download or copy the agent definition into the path for your framework.',\n hook: 'Register the hook definition at the path for your framework — most hosts merge it into a shared settings file.',\n 'mcp-config': 'Add this server entry to your MCP configuration file.',\n plugin:\n 'This plugin installs through its framework — follow the instructions below.',\n marketplace:\n 'Register this marketplace with your AI tool once, then install any of its plugins from it.',\n};\n\n/** The VS Code chevron mark (devicon's mono path), for a button's iconStart slot. */\nfunction VsCodeIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 128 128\"\n width={16}\n height={16}\n fill=\"currentColor\"\n aria-hidden\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M90.767 127.126a7.968 7.968 0 0 0 6.35-.244l26.353-12.681a8 8 0 0 0 4.53-7.209V21.009a8 8 0 0 0-4.53-7.21L97.117 1.12a7.97 7.97 0 0 0-9.093 1.548l-50.45 46.026L15.6 32.013a5.328 5.328 0 0 0-6.807.302l-7.048 6.411a5.335 5.335 0 0 0-.006 7.888L20.796 64 1.74 81.387a5.336 5.336 0 0 0 .006 7.887l7.048 6.411a5.327 5.327 0 0 0 6.807.303l21.974-16.68 50.45 46.025a7.96 7.96 0 0 0 2.743 1.793Zm5.252-92.183L57.74 64l38.28 29.058V34.943Z\"\n />\n </svg>\n );\n}\n\n/** The Claude starburst, sized for a button's iconStart slot. */\nfunction ClaudeIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n width={16}\n height={16}\n fill=\"currentColor\"\n aria-hidden\n >\n <path d={siClaude.path} />\n </svg>\n );\n}\n\n/**\n * The one-click install links for the types with a *native* install route,\n * rendered once in the actions row. `skill` and `hook` are absent on purpose:\n * their launchers are per-host and live in their own rows, so returning them\n * here too would render every button twice.\n */\nfunction getInstallLinks(resource: ResourceSummary, body?: ResourceBody) {\n if (resource.type === 'agent') {\n return getAgentInstallLinks(\n resource.sourceLocation,\n resource.name,\n resource.frameworks,\n );\n }\n if (resource.type === 'mcp-config') {\n return getMcpInstallLinks(\n resource.frameworks,\n resource.name,\n body?.content,\n );\n }\n return [];\n}\n\n/** Brand styling for the known deep-link hosts, keyed by link label. */\nfunction launchBrand(label: string): {\n className?: string;\n icon: React.ReactElement;\n} {\n switch (label) {\n case 'Claude':\n return { className: styles.claudeLaunch, icon: <ClaudeIcon /> };\n case 'VS Code':\n return { className: styles.vscodeLaunch, icon: <VsCodeIcon /> };\n case 'VS Code Insiders':\n return { className: styles.insidersLaunch, icon: <VsCodeIcon /> };\n case 'Cursor':\n return {\n className: styles.cursorLaunch,\n icon: <ToolIcon tool=\"cursor\" branded={false} size={16} />,\n };\n default:\n return { icon: <RiExternalLinkLine /> };\n }\n}\n\n/**\n * Icon-only copy button for a single generated command (marketplace journey\n * rows); flashes a check mark as the copied feedback.\n */\nfunction CopyCommandButton({ text }: { text: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <ButtonIcon\n size=\"small\"\n variant=\"tertiary\"\n icon={copied ? <RiCheckLine /> : <RiFileCopyLine />}\n aria-label={copied ? 'Copied' : 'Copy command'}\n onPress={async () => {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }}\n />\n );\n}\n\n/**\n * Clipboard fallback for a host with no prompt URI (Copilot, Gemini). It\n * carries the identical instruction a launcher would pre-fill, so the only\n * difference is the delivery — the user pastes it into their own agent.\n */\nfunction CopyPromptButton({ text, label }: { text: string; label: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <Button\n size=\"small\"\n variant=\"secondary\"\n iconStart={copied ? <RiCheckLine /> : <RiFileCopyLine />}\n onPress={async () => {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }}\n >\n {copied ? 'Copied!' : `Copy prompt for ${label}`}\n </Button>\n );\n}\n\n/**\n * The two-step marketplace journey (ADR-0010): ① register the catalog with\n * the AI tool, ② install plugins from it — plus a team-distribution snippet.\n * Whether this can render at all is decided by the caller, which needs the\n * same answer to know if the body doc is still required as a fallback.\n */\nfunction MarketplaceJourney({\n resource,\n repoSlug,\n addCommands,\n}: {\n resource: ResourceSummary;\n repoSlug: string;\n addCommands: MarketplaceAddCommand[];\n}) {\n return (\n <div className={styles.journey}>\n <Text variant=\"body-small\" as=\"p\" className={styles.stepTitle}>\n 1. Add the marketplace to your AI tool\n </Text>\n <dl className={styles.commandList}>\n {addCommands.map(({ framework, command, deepLinks }) => (\n <div key={framework} className={styles.commandRow}>\n <dt className={styles.commandLabel}>\n <ToolIcon tool={framework as FrameworkToken} size={16} />\n {frameworkLabel(framework)}\n </dt>\n <dd className={styles.commandCell}>\n <code>{command}</code>\n <CopyCommandButton text={command} />\n </dd>\n {deepLinks.length > 0 && (\n <dd className={styles.deepLinks}>\n {deepLinks.map(link => {\n const brand = launchBrand(link.label);\n return (\n <ButtonLink\n key={link.label}\n size=\"small\"\n variant=\"secondary\"\n className={brand.className}\n iconStart={brand.icon}\n href={link.href}\n >\n Add in {link.label}\n </ButtonLink>\n );\n })}\n </dd>\n )}\n </div>\n ))}\n </dl>\n\n <Text variant=\"body-small\" as=\"p\" className={styles.stepTitle}>\n 2. Install plugins from it\n </Text>\n <div className={styles.commandCell}>\n <code>{getMarketplaceInstallTemplate(resource.name)}</code>\n </div>\n\n <details className={styles.teamSection}>\n <summary>For teams: auto-install via project settings</summary>\n <Text variant=\"body-x-small\" as=\"p\">\n Commit this to your repository’s <code>.claude/settings.json</code>{' '}\n and collaborators are prompted to install the marketplace when they\n trust the folder.\n </Text>\n <pre className={styles.codeBlock}>\n <code>{getMarketplaceTeamSnippet(resource.name, repoSlug)}</code>\n </pre>\n <CopyCommandButton\n text={getMarketplaceTeamSnippet(resource.name, repoSlug)}\n />\n </details>\n </div>\n );\n}\n\n/**\n * Install guidance wired to the body resolver (issue #30): copy the body,\n * download the artifact (file or zip, ADR-0009), and per-framework install\n * paths. The body is canonical — nothing here is reconstructed from\n * annotations (CONTEXT.md: body).\n */\nexport function ResourceInstallDialog({\n resource,\n body,\n isOpen,\n onOpenChange,\n}: ResourceInstallDialogProps) {\n const api = useApi(devAiHubResourceApiRef);\n const [copied, setCopied] = useState(false);\n const [downloadError, setDownloadError] = useState(false);\n\n const shape = getBodyShape(resource.type);\n const installLinks = getInstallLinks(resource, body);\n\n // A marketplace body doc repeats the generated journey almost verbatim —\n // same two steps, same commands — so rendering both doubles the dialog's\n // height to say one thing twice. The journey wins when it can be derived;\n // the body stays as ADR-0010's fallback for when the slug cannot be.\n const marketplaceRepoSlug =\n resource.type === 'marketplace'\n ? getMarketplaceRepoSlug(resource.sourceLocation)\n : undefined;\n const marketplaceAddCommands = marketplaceRepoSlug\n ? getMarketplaceAddCommands(resource.frameworks, marketplaceRepoSlug)\n : [];\n const hasMarketplaceJourney = marketplaceAddCommands.length > 0;\n const showBodyDoc =\n resource.type === 'plugin' ||\n (resource.type === 'marketplace' && !hasMarketplaceJourney);\n\n // skill/hook install by instructing the agent, so each host gets a row that\n // is actionable on its own: a launcher where the host has a prompt URI, a\n // copyable prompt where it does not. Other types keep the plain path list —\n // their launchers are native routes, rendered once in the actions row.\n const usesPromptInstall =\n resource.type === 'skill' || resource.type === 'hook';\n const installSteps = usesPromptInstall\n ? getInstallSteps(\n resource.type,\n resource.sourceLocation,\n resource.name,\n resource.frameworks,\n )\n : [];\n const pathRows = expandInstallFrameworks(resource.frameworks)\n .map(f => ({\n framework: f,\n target: getResourceInstallTarget(resource.type, f, resource.name),\n }))\n .filter(\n (row): row is { framework: string; target: ResourceInstallTarget } =>\n !!row.target,\n );\n const hasMergeTarget = (\n installSteps.length > 0\n ? installSteps.map(s => s.target)\n : pathRows.map(r => r.target)\n ).some(t => t.mode === 'merge');\n\n const handleCopy = async () => {\n if (!body) return;\n await navigator.clipboard.writeText(body.content);\n api.track(resource.entityRef, 'copy');\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n const handleDownload = async () => {\n setDownloadError(false);\n try {\n await api.downloadEntityBody(resource.entityRef);\n api.track(resource.entityRef, 'download');\n } catch {\n setDownloadError(true);\n }\n };\n\n return (\n <Dialog\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n /* Size to the widest command line so generated commands stay unwrapped,\n clamped so prose-heavy bodies can't balloon the dialog. The height cap\n keeps a long fallback body scrolling inside the dialog rather than\n running off-screen — the install actions must always stay reachable. */\n style={{\n width: 'fit-content',\n minWidth: 'min(480px, calc(100vw - 3rem))',\n maxWidth: 'min(760px, calc(100vw - 3rem))',\n maxHeight: 'min(80vh, calc(100vh - 4rem))',\n }}\n >\n <DialogHeader>Install {resource.title ?? resource.name}</DialogHeader>\n <DialogBody>\n <div className={styles.content}>\n <Text variant=\"body-small\" as=\"p\">\n {TYPE_HINTS[resource.type]}\n </Text>\n\n {body && shape === 'json' && (\n <pre className={styles.codeBlock}>\n <code>{body.content}</code>\n </pre>\n )}\n\n {hasMarketplaceJourney && marketplaceRepoSlug && (\n <MarketplaceJourney\n resource={resource}\n repoSlug={marketplaceRepoSlug}\n addCommands={marketplaceAddCommands}\n />\n )}\n\n {body && showBodyDoc && (\n <div className={styles.markdown}>\n <ReactMarkdown>{body.content}</ReactMarkdown>\n </div>\n )}\n\n {installSteps.length > 0 && (\n <dl className={styles.commandList}>\n {installSteps.map(({ framework, target, prompt, link }) => (\n <div key={framework} className={styles.commandRow}>\n <dt className={styles.commandLabel}>\n <ToolIcon tool={framework as FrameworkToken} size={16} />\n {frameworkLabel(framework)}\n </dt>\n <dd className={styles.pathRow}>\n <code>{target.path}</code>\n {target.mode === 'merge' && (\n <span className={styles.mergeBadge}>merge into</span>\n )}\n </dd>\n <dd className={styles.deepLinks}>\n {link ? (\n (() => {\n const brand = launchBrand(link.label);\n return (\n <ButtonLink\n size=\"small\"\n variant=\"secondary\"\n className={brand.className}\n iconStart={brand.icon}\n href={link.href}\n onPress={() =>\n api.track(\n resource.entityRef,\n 'install',\n framework,\n )\n }\n >\n Install in {link.label}\n </ButtonLink>\n );\n })()\n ) : (\n /* No prompt URI for this host — the same instruction,\n delivered by clipboard so the row is still actionable. */\n <CopyPromptButton\n text={prompt}\n label={frameworkLabel(framework)}\n />\n )}\n </dd>\n </div>\n ))}\n </dl>\n )}\n\n {installSteps.length === 0 && pathRows.length > 0 && (\n <>\n <dl className={styles.pathList}>\n {pathRows.map(({ framework, target }) => (\n <div key={framework} className={styles.pathRow}>\n <dt>\n {framework === 'default'\n ? 'Any framework'\n : frameworkLabel(framework)}\n </dt>\n <dd>\n <code>{target.path}</code>\n {/* A merge target is a file the user already owns —\n flagged inline so the path is never mistaken for a\n drop-in destination to overwrite. */}\n {target.mode === 'merge' && (\n <span className={styles.mergeBadge}>merge into</span>\n )}\n </dd>\n </div>\n ))}\n </dl>\n {hasMergeTarget && (\n <Text\n variant=\"body-x-small\"\n as=\"p\"\n className={styles.mergeNote}\n >\n Paths marked <strong>merge into</strong> are shared\n configuration files. Add this entry to the existing file —\n replacing it discards your other settings.\n </Text>\n )}\n </>\n )}\n\n <div className={styles.actions}>\n {installLinks.map(link => {\n const brand = launchBrand(link.label);\n return (\n <ButtonLink\n key={link.label}\n size=\"small\"\n variant=\"secondary\"\n className={brand.className}\n iconStart={brand.icon}\n href={link.href}\n >\n Install in {link.label}\n </ButtonLink>\n );\n })}\n {hasCopyableBody(resource.type) && (\n <Button\n size=\"small\"\n variant=\"secondary\"\n iconStart={<RiFileCopyLine />}\n isDisabled={!body}\n onPress={handleCopy}\n >\n {copied ? 'Copied!' : 'Copy content'}\n </Button>\n )}\n {hasDownloadableArtifact(resource.type) && (\n <Button\n size=\"small\"\n variant=\"primary\"\n iconStart={<RiDownloadLine />}\n onPress={handleDownload}\n >\n Download\n </Button>\n )}\n </div>\n {downloadError && (\n <Text variant=\"body-x-small\" as=\"p\">\n Download failed — the source may be unreachable. Try again or use\n “View source”.\n </Text>\n )}\n </div>\n </DialogBody>\n </Dialog>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ResourceInstallDialog.esm.js","sources":["../../../src/components/AiResourcesPage/ResourceInstallDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport {\n Button,\n ButtonIcon,\n ButtonLink,\n Dialog,\n DialogBody,\n DialogHeader,\n Text,\n} from '@backstage/ui';\nimport {\n RiCheckLine,\n RiDownloadLine,\n RiExternalLinkLine,\n RiFileCopyLine,\n} from '@remixicon/react';\nimport { siClaude } from 'simple-icons';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n expandInstallFrameworks,\n getAgentInstallLinks,\n getBodyShape,\n getMarketplaceAddCommands,\n getMcpInstallLinks,\n getMarketplaceInstallTemplate,\n getMarketplaceRepoSlug,\n getMarketplaceTeamSnippet,\n getInstallSteps,\n getResourceInstallTarget,\n hasCopyableBody,\n hasDownloadableArtifact,\n type FrameworkToken,\n type MarketplaceAddCommand,\n type ResourceInstallTarget,\n type ResourceSummary,\n} from '@nospt/plugin-dev-ai-hub-common';\nimport {\n devAiHubResourceApiRef,\n type ResourceBody,\n} from '../../api/DevAiHubResourceClient';\nimport { ToolIcon } from '../ToolIcon';\nimport { CollapsibleSection } from './CollapsibleSection';\nimport { JsonBody } from './JsonBody';\nimport { frameworkLabel } from './typeMeta';\nimport styles from './ResourceInstallDialog.module.css';\n\ninterface ResourceInstallDialogProps {\n resource: ResourceSummary;\n body?: ResourceBody;\n isOpen: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nconst TYPE_HINTS: Record<ResourceSummary['type'], string> = {\n skill:\n 'Download the skill (multi-file skills arrive as one zip) and extract it into the path for your framework.',\n agent:\n 'Download or copy the agent definition into the path for your framework.',\n hook: 'Register the hook definition at the path for your framework — most hosts merge it into a shared settings file.',\n 'mcp-config': 'Add this server entry to your MCP configuration file.',\n plugin:\n 'This plugin installs through its framework — follow the instructions below.',\n marketplace:\n 'Register this marketplace with your AI tool once, then install any of its plugins from it.',\n};\n\n/** The VS Code chevron mark (devicon's mono path), for a button's iconStart slot. */\nfunction VsCodeIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 128 128\"\n width={16}\n height={16}\n fill=\"currentColor\"\n aria-hidden\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M90.767 127.126a7.968 7.968 0 0 0 6.35-.244l26.353-12.681a8 8 0 0 0 4.53-7.209V21.009a8 8 0 0 0-4.53-7.21L97.117 1.12a7.97 7.97 0 0 0-9.093 1.548l-50.45 46.026L15.6 32.013a5.328 5.328 0 0 0-6.807.302l-7.048 6.411a5.335 5.335 0 0 0-.006 7.888L20.796 64 1.74 81.387a5.336 5.336 0 0 0 .006 7.887l7.048 6.411a5.327 5.327 0 0 0 6.807.303l21.974-16.68 50.45 46.025a7.96 7.96 0 0 0 2.743 1.793Zm5.252-92.183L57.74 64l38.28 29.058V34.943Z\"\n />\n </svg>\n );\n}\n\n/** The Claude starburst, sized for a button's iconStart slot. */\nfunction ClaudeIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n width={16}\n height={16}\n fill=\"currentColor\"\n aria-hidden\n >\n <path d={siClaude.path} />\n </svg>\n );\n}\n\n/**\n * The one-click install links for the types with a *native* install route,\n * rendered once in the actions row. `skill` and `hook` are absent on purpose:\n * their launchers are per-host and live in their own rows, so returning them\n * here too would render every button twice.\n */\nfunction getInstallLinks(resource: ResourceSummary, body?: ResourceBody) {\n if (resource.type === 'agent') {\n return getAgentInstallLinks(\n resource.sourceLocation,\n resource.name,\n resource.frameworks,\n );\n }\n if (resource.type === 'mcp-config') {\n return getMcpInstallLinks(\n resource.frameworks,\n resource.name,\n body?.content,\n );\n }\n return [];\n}\n\n/** Brand styling for the known deep-link hosts, keyed by link label. */\nfunction launchBrand(label: string): {\n className?: string;\n icon: React.ReactElement;\n} {\n switch (label) {\n case 'Claude':\n return { className: styles.claudeLaunch, icon: <ClaudeIcon /> };\n case 'VS Code':\n return { className: styles.vscodeLaunch, icon: <VsCodeIcon /> };\n case 'VS Code Insiders':\n return { className: styles.insidersLaunch, icon: <VsCodeIcon /> };\n case 'Cursor':\n return {\n className: styles.cursorLaunch,\n icon: <ToolIcon tool=\"cursor\" branded={false} size={16} />,\n };\n default:\n return { icon: <RiExternalLinkLine /> };\n }\n}\n\n/**\n * Icon-only copy button for a single generated command (marketplace journey\n * rows); flashes a check mark as the copied feedback.\n */\nfunction CopyCommandButton({ text }: { text: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <ButtonIcon\n size=\"small\"\n variant=\"tertiary\"\n icon={copied ? <RiCheckLine /> : <RiFileCopyLine />}\n aria-label={copied ? 'Copied' : 'Copy command'}\n onPress={async () => {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }}\n />\n );\n}\n\n/**\n * Clipboard fallback for a host with no prompt URI (Copilot, Gemini). It\n * carries the identical instruction a launcher would pre-fill, so the only\n * difference is the delivery — the user pastes it into their own agent.\n */\nfunction CopyPromptButton({ text, label }: { text: string; label: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <Button\n size=\"small\"\n variant=\"secondary\"\n iconStart={copied ? <RiCheckLine /> : <RiFileCopyLine />}\n onPress={async () => {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }}\n >\n {copied ? 'Copied!' : `Copy prompt for ${label}`}\n </Button>\n );\n}\n\n/**\n * The two-step marketplace journey (ADR-0010): ① register the catalog with\n * the AI tool, ② install plugins from it — plus a team-distribution snippet.\n * Whether this can render at all is decided by the caller, which needs the\n * same answer to know if the body doc is still required as a fallback.\n */\nfunction MarketplaceJourney({\n resource,\n repoSlug,\n addCommands,\n}: {\n resource: ResourceSummary;\n repoSlug: string;\n addCommands: MarketplaceAddCommand[];\n}) {\n return (\n <div className={styles.journey}>\n <Text variant=\"body-small\" as=\"p\" className={styles.stepTitle}>\n 1. Add the marketplace to your AI tool\n </Text>\n <dl className={styles.commandList}>\n {addCommands.map(({ framework, command, deepLinks }) => (\n <div key={framework} className={styles.commandRow}>\n <dt className={styles.commandLabel}>\n <ToolIcon tool={framework as FrameworkToken} size={16} />\n {frameworkLabel(framework)}\n </dt>\n <dd className={styles.commandCell}>\n <code>{command}</code>\n <CopyCommandButton text={command} />\n </dd>\n {deepLinks.length > 0 && (\n <dd className={styles.deepLinks}>\n {deepLinks.map(link => {\n const brand = launchBrand(link.label);\n return (\n <ButtonLink\n key={link.label}\n size=\"small\"\n variant=\"secondary\"\n className={brand.className}\n iconStart={brand.icon}\n href={link.href}\n >\n Add in {link.label}\n </ButtonLink>\n );\n })}\n </dd>\n )}\n </div>\n ))}\n </dl>\n\n <Text variant=\"body-small\" as=\"p\" className={styles.stepTitle}>\n 2. Install plugins from it\n </Text>\n <div className={styles.commandCell}>\n <code>{getMarketplaceInstallTemplate(resource.name)}</code>\n </div>\n\n <details className={styles.teamSection}>\n <summary>For teams: auto-install via project settings</summary>\n <Text variant=\"body-x-small\" as=\"p\">\n Commit this to your repository’s <code>.claude/settings.json</code>{' '}\n and collaborators are prompted to install the marketplace when they\n trust the folder.\n </Text>\n <pre className={styles.codeBlock}>\n <code>{getMarketplaceTeamSnippet(resource.name, repoSlug)}</code>\n </pre>\n <CopyCommandButton\n text={getMarketplaceTeamSnippet(resource.name, repoSlug)}\n />\n </details>\n </div>\n );\n}\n\n/**\n * Install guidance wired to the body resolver (issue #30): copy the body,\n * download the artifact (file or zip, ADR-0009), and per-framework install\n * paths. The body is canonical — nothing here is reconstructed from\n * annotations (CONTEXT.md: body).\n */\nexport function ResourceInstallDialog({\n resource,\n body,\n isOpen,\n onOpenChange,\n}: ResourceInstallDialogProps) {\n const api = useApi(devAiHubResourceApiRef);\n const [copied, setCopied] = useState(false);\n const [downloadError, setDownloadError] = useState(false);\n\n const shape = getBodyShape(resource.type);\n const installLinks = getInstallLinks(resource, body);\n\n // A marketplace body doc repeats the generated journey almost verbatim —\n // same two steps, same commands — so rendering both doubles the dialog's\n // height to say one thing twice. The journey wins when it can be derived;\n // the body stays as ADR-0010's fallback for when the slug cannot be.\n const marketplaceRepoSlug =\n resource.type === 'marketplace'\n ? getMarketplaceRepoSlug(resource.sourceLocation)\n : undefined;\n const marketplaceAddCommands = marketplaceRepoSlug\n ? getMarketplaceAddCommands(resource.frameworks, marketplaceRepoSlug)\n : [];\n const hasMarketplaceJourney = marketplaceAddCommands.length > 0;\n // plugin/marketplace bodies are the real manifest JSON (ADR-0014), so this\n // markdown fallback can only fire if shape is still 'markdown' — kept for\n // the (currently unreachable) case bodyShape ever becomes content-driven.\n const showBodyDoc =\n shape === 'markdown' &&\n (resource.type === 'plugin' ||\n (resource.type === 'marketplace' && !hasMarketplaceJourney));\n\n // skill/hook install by instructing the agent, so each host gets a row that\n // is actionable on its own: a launcher where the host has a prompt URI, a\n // copyable prompt where it does not. Other types keep the plain path list —\n // their launchers are native routes, rendered once in the actions row.\n const usesPromptInstall =\n resource.type === 'skill' || resource.type === 'hook';\n const installSteps = usesPromptInstall\n ? getInstallSteps(\n resource.type,\n resource.sourceLocation,\n resource.name,\n resource.frameworks,\n )\n : [];\n const pathRows = expandInstallFrameworks(resource.frameworks)\n .map(f => ({\n framework: f,\n target: getResourceInstallTarget(resource.type, f, resource.name),\n }))\n .filter(\n (row): row is { framework: string; target: ResourceInstallTarget } =>\n !!row.target,\n );\n const hasMergeTarget = (\n installSteps.length > 0\n ? installSteps.map(s => s.target)\n : pathRows.map(r => r.target)\n ).some(t => t.mode === 'merge');\n\n const handleCopy = async () => {\n if (!body) return;\n await navigator.clipboard.writeText(body.content);\n api.track(resource.entityRef, 'copy');\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n const handleDownload = async () => {\n setDownloadError(false);\n try {\n await api.downloadEntityBody(resource.entityRef);\n api.track(resource.entityRef, 'download');\n } catch {\n setDownloadError(true);\n }\n };\n\n return (\n <Dialog\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n /* Size to the widest command line so generated commands stay unwrapped,\n clamped so prose-heavy bodies can't balloon the dialog. The height cap\n keeps a long fallback body scrolling inside the dialog rather than\n running off-screen — the install actions must always stay reachable. */\n style={{\n width: 'fit-content',\n minWidth: 'min(480px, calc(100vw - 3rem))',\n maxWidth: 'min(760px, calc(100vw - 3rem))',\n maxHeight: 'min(80vh, calc(100vh - 4rem))',\n }}\n >\n <DialogHeader>Install {resource.title ?? resource.name}</DialogHeader>\n <DialogBody>\n <div className={styles.content}>\n <Text variant=\"body-small\" as=\"p\">\n {TYPE_HINTS[resource.type]}\n </Text>\n\n {body && shape === 'json' && (\n <CollapsibleSection label=\"Content\">\n <JsonBody content={body.content} className={styles.codeBlock} />\n </CollapsibleSection>\n )}\n\n {hasMarketplaceJourney && marketplaceRepoSlug && (\n <MarketplaceJourney\n resource={resource}\n repoSlug={marketplaceRepoSlug}\n addCommands={marketplaceAddCommands}\n />\n )}\n\n {body && showBodyDoc && (\n <div className={styles.markdown}>\n <ReactMarkdown>{body.content}</ReactMarkdown>\n </div>\n )}\n\n {installSteps.length > 0 && (\n <dl className={styles.commandList}>\n {installSteps.map(({ framework, target, prompt, link }) => (\n <div key={framework} className={styles.commandRow}>\n <dt className={styles.commandLabel}>\n <ToolIcon tool={framework as FrameworkToken} size={16} />\n {frameworkLabel(framework)}\n </dt>\n <dd className={styles.pathRow}>\n <code>{target.path}</code>\n {target.mode === 'merge' && (\n <span className={styles.mergeBadge}>merge into</span>\n )}\n </dd>\n <dd className={styles.deepLinks}>\n {link ? (\n (() => {\n const brand = launchBrand(link.label);\n return (\n <ButtonLink\n size=\"small\"\n variant=\"secondary\"\n className={brand.className}\n iconStart={brand.icon}\n href={link.href}\n onPress={() =>\n api.track(\n resource.entityRef,\n 'install',\n framework,\n )\n }\n >\n Install in {link.label}\n </ButtonLink>\n );\n })()\n ) : (\n /* No prompt URI for this host — the same instruction,\n delivered by clipboard so the row is still actionable. */\n <CopyPromptButton\n text={prompt}\n label={frameworkLabel(framework)}\n />\n )}\n </dd>\n </div>\n ))}\n </dl>\n )}\n\n {installSteps.length === 0 && pathRows.length > 0 && (\n <>\n <dl className={styles.pathList}>\n {pathRows.map(({ framework, target }) => (\n <div key={framework} className={styles.pathRow}>\n <dt>\n {framework === 'default'\n ? 'Any framework'\n : frameworkLabel(framework)}\n </dt>\n <dd>\n <code>{target.path}</code>\n {/* A merge target is a file the user already owns —\n flagged inline so the path is never mistaken for a\n drop-in destination to overwrite. */}\n {target.mode === 'merge' && (\n <span className={styles.mergeBadge}>merge into</span>\n )}\n </dd>\n </div>\n ))}\n </dl>\n {hasMergeTarget && (\n <Text\n variant=\"body-x-small\"\n as=\"p\"\n className={styles.mergeNote}\n >\n Paths marked <strong>merge into</strong> are shared\n configuration files. Add this entry to the existing file —\n replacing it discards your other settings.\n </Text>\n )}\n </>\n )}\n\n <div className={styles.actions}>\n {installLinks.map(link => {\n const brand = launchBrand(link.label);\n return (\n <ButtonLink\n key={link.label}\n size=\"small\"\n variant=\"secondary\"\n className={brand.className}\n iconStart={brand.icon}\n href={link.href}\n >\n Install in {link.label}\n </ButtonLink>\n );\n })}\n {hasCopyableBody(resource.type) && (\n <Button\n size=\"small\"\n variant=\"secondary\"\n iconStart={<RiFileCopyLine />}\n isDisabled={!body}\n onPress={handleCopy}\n >\n {copied ? 'Copied!' : 'Copy content'}\n </Button>\n )}\n {hasDownloadableArtifact(resource.type) && (\n <Button\n size=\"small\"\n variant=\"primary\"\n iconStart={<RiDownloadLine />}\n onPress={handleDownload}\n >\n Download\n </Button>\n )}\n </div>\n {downloadError && (\n <Text variant=\"body-x-small\" as=\"p\">\n Download failed — the source may be unreachable. Try again or use\n “View source”.\n </Text>\n )}\n </div>\n </DialogBody>\n </Dialog>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { Text } from '@backstage/ui';
|
|
3
|
+
import { RiArrowRightSLine } from '@remixicon/react';
|
|
4
|
+
import { isRenderableContainment } from '@nospt/plugin-dev-ai-hub-common';
|
|
5
|
+
import { getTypeMeta } from './typeMeta.esm.js';
|
|
6
|
+
import styles from './ResourceRelationships.module.css.esm.js';
|
|
7
|
+
|
|
8
|
+
function resolve(refs, byRef, keep) {
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const ref of refs) {
|
|
11
|
+
const found = byRef.get(ref);
|
|
12
|
+
if (found && keep(found)) out.push(found);
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
function RelationshipRow({
|
|
17
|
+
related,
|
|
18
|
+
onOpen
|
|
19
|
+
}) {
|
|
20
|
+
const meta = getTypeMeta(related.type);
|
|
21
|
+
return /* @__PURE__ */ jsxs(
|
|
22
|
+
"button",
|
|
23
|
+
{
|
|
24
|
+
type: "button",
|
|
25
|
+
className: styles.row,
|
|
26
|
+
onClick: () => onOpen(related.entityRef),
|
|
27
|
+
style: {
|
|
28
|
+
"--card-accent": meta.color,
|
|
29
|
+
"--card-accent-bg": meta.colorBg
|
|
30
|
+
},
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ jsx("span", { className: styles.rowIcon, children: /* @__PURE__ */ jsx(meta.Icon, { size: 16 }) }),
|
|
33
|
+
/* @__PURE__ */ jsxs("span", { className: styles.rowText, children: [
|
|
34
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-small", weight: "bold", className: styles.rowTitle, children: related.title ?? related.name }),
|
|
35
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-x-small", color: "secondary", children: meta.label })
|
|
36
|
+
] }),
|
|
37
|
+
/* @__PURE__ */ jsx(RiArrowRightSLine, { size: 16, className: styles.rowChevron })
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function Section({
|
|
43
|
+
label,
|
|
44
|
+
related,
|
|
45
|
+
onOpen
|
|
46
|
+
}) {
|
|
47
|
+
return /* @__PURE__ */ jsxs("div", { className: styles.section, children: [
|
|
48
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-x-small", color: "secondary", weight: "bold", children: label }),
|
|
49
|
+
/* @__PURE__ */ jsx("div", { className: styles.rows, children: related.map((r) => /* @__PURE__ */ jsx(RelationshipRow, { related: r, onOpen }, r.entityRef)) })
|
|
50
|
+
] });
|
|
51
|
+
}
|
|
52
|
+
function ResourceRelationships({
|
|
53
|
+
resource,
|
|
54
|
+
items,
|
|
55
|
+
onOpen
|
|
56
|
+
}) {
|
|
57
|
+
const byRef = new Map(items.map((i) => [i.entityRef, i]));
|
|
58
|
+
const parents = resolve(
|
|
59
|
+
resource.parents,
|
|
60
|
+
byRef,
|
|
61
|
+
(parent) => isRenderableContainment(parent.type, resource.type)
|
|
62
|
+
);
|
|
63
|
+
const children = resolve(
|
|
64
|
+
resource.children,
|
|
65
|
+
byRef,
|
|
66
|
+
(child) => isRenderableContainment(resource.type, child.type)
|
|
67
|
+
);
|
|
68
|
+
const childrenLabel = resource.type === "marketplace" ? "Plugins in this marketplace" : "Includes";
|
|
69
|
+
if (parents.length === 0 && children.length === 0) return null;
|
|
70
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
71
|
+
parents.length > 0 && /* @__PURE__ */ jsx(Section, { label: "Part of", related: parents, onOpen }),
|
|
72
|
+
children.length > 0 && /* @__PURE__ */ jsx(Section, { label: childrenLabel, related: children, onOpen })
|
|
73
|
+
] });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { ResourceRelationships };
|
|
77
|
+
//# sourceMappingURL=ResourceRelationships.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResourceRelationships.esm.js","sources":["../../../src/components/AiResourcesPage/ResourceRelationships.tsx"],"sourcesContent":["import { Text } from '@backstage/ui';\nimport { RiArrowRightSLine } from '@remixicon/react';\nimport {\n isRenderableContainment,\n type ResourceSummary,\n} from '@nospt/plugin-dev-ai-hub-common';\nimport { getTypeMeta } from './typeMeta';\nimport styles from './ResourceRelationships.module.css';\n\ninterface ResourceRelationshipsProps {\n resource: ResourceSummary;\n /** Every resource the caller can see — used to resolve related refs. */\n items: ResourceSummary[];\n /** Opens another resource's detail (the existing `?resource=` swap). */\n onOpen: (entityRef: string) => void;\n}\n\n/**\n * Resolve declared refs against the caller-visible set and keep only pairings\n * the rendering convention allows (ADR-0015). A ref absent from `items`\n * (hidden or an unsupported type) or of the wrong type is silently dropped —\n * a section never leaks that an entity exists.\n */\nfunction resolve(\n refs: string[],\n byRef: Map<string, ResourceSummary>,\n keep: (other: ResourceSummary) => boolean,\n): ResourceSummary[] {\n const out: ResourceSummary[] = [];\n for (const ref of refs) {\n const found = byRef.get(ref);\n if (found && keep(found)) out.push(found);\n }\n return out;\n}\n\n/** One navigable relationship row: type icon, title, and the type label. */\nfunction RelationshipRow({\n related,\n onOpen,\n}: {\n related: ResourceSummary;\n onOpen: (entityRef: string) => void;\n}) {\n const meta = getTypeMeta(related.type);\n return (\n <button\n type=\"button\"\n className={styles.row}\n onClick={() => onOpen(related.entityRef)}\n style={\n {\n '--card-accent': meta.color,\n '--card-accent-bg': meta.colorBg,\n } as React.CSSProperties\n }\n >\n <span className={styles.rowIcon}>\n <meta.Icon size={16} />\n </span>\n <span className={styles.rowText}>\n <Text variant=\"body-small\" weight=\"bold\" className={styles.rowTitle}>\n {related.title ?? related.name}\n </Text>\n <Text variant=\"body-x-small\" color=\"secondary\">\n {meta.label}\n </Text>\n </span>\n <RiArrowRightSLine size={16} className={styles.rowChevron} />\n </button>\n );\n}\n\nfunction Section({\n label,\n related,\n onOpen,\n}: {\n label: string;\n related: ResourceSummary[];\n onOpen: (entityRef: string) => void;\n}) {\n return (\n <div className={styles.section}>\n <Text variant=\"body-x-small\" color=\"secondary\" weight=\"bold\">\n {label}\n </Text>\n <div className={styles.rows}>\n {related.map(r => (\n <RelationshipRow key={r.entityRef} related={r} onOpen={onOpen} />\n ))}\n </div>\n </div>\n );\n}\n\n/**\n * The relationship sections of the detail panel (ADR-0015): \"Part of\" (the\n * containers this resource belongs to, upward) and, for containers, the\n * members it declares (downward). Renders nothing when neither resolves to a\n * visible, correctly-typed relation, so the panel never shows an empty\n * heading.\n */\nexport function ResourceRelationships({\n resource,\n items,\n onOpen,\n}: ResourceRelationshipsProps) {\n const byRef = new Map(items.map(i => [i.entityRef, i]));\n\n // A parent is kept only if it legitimately contains this resource's type.\n const parents = resolve(resource.parents, byRef, parent =>\n isRenderableContainment(parent.type, resource.type),\n );\n // A child is kept only if this resource's type legitimately contains it.\n const children = resolve(resource.children, byRef, child =>\n isRenderableContainment(resource.type, child.type),\n );\n\n const childrenLabel =\n resource.type === 'marketplace'\n ? 'Plugins in this marketplace'\n : 'Includes';\n\n if (parents.length === 0 && children.length === 0) return null;\n\n return (\n <>\n {parents.length > 0 && (\n <Section label=\"Part of\" related={parents} onOpen={onOpen} />\n )}\n {children.length > 0 && (\n <Section label={childrenLabel} related={children} onOpen={onOpen} />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import styleInject from '../../node_modules_dist/style-inject/dist/style-inject.es.esm.js';
|
|
2
|
+
|
|
3
|
+
var css_248z = "@layer components {\n .ResourceRelationships_section__4c1b4bc5de {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n }\n\n .ResourceRelationships_rows__4c1b4bc5de {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-1);\n }\n\n .ResourceRelationships_row__4c1b4bc5de {\n display: flex;\n align-items: center;\n gap: var(--bui-space-2);\n width: 100%;\n text-align: left;\n padding: var(--bui-space-2);\n background: var(--bui-bg-neutral-2);\n border: 1px solid var(--bui-border-1);\n border-radius: var(--bui-radius-2);\n cursor: pointer;\n transition: border-color 120ms ease, background-color 120ms ease;\n }\n\n .ResourceRelationships_row__4c1b4bc5de:hover {\n border-color: var(--card-accent, var(--bui-fg-info));\n background: var(--bui-bg-neutral-1);\n }\n\n .ResourceRelationships_rowIcon__4c1b4bc5de {\n display: flex;\n flex-shrink: 0;\n color: var(--card-accent);\n background: var(--card-accent-bg);\n border-radius: var(--bui-radius-1);\n padding: 4px;\n }\n\n .ResourceRelationships_rowText__4c1b4bc5de {\n display: flex;\n flex-direction: column;\n min-width: 0;\n flex: 1;\n }\n\n .ResourceRelationships_rowTitle__4c1b4bc5de {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .ResourceRelationships_rowChevron__4c1b4bc5de {\n flex-shrink: 0;\n color: var(--bui-fg-secondary);\n }\n}\n";
|
|
4
|
+
var styles = {"section":"ResourceRelationships_section__4c1b4bc5de","rows":"ResourceRelationships_rows__4c1b4bc5de","row":"ResourceRelationships_row__4c1b4bc5de","rowIcon":"ResourceRelationships_rowIcon__4c1b4bc5de","rowText":"ResourceRelationships_rowText__4c1b4bc5de","rowTitle":"ResourceRelationships_rowTitle__4c1b4bc5de","rowChevron":"ResourceRelationships_rowChevron__4c1b4bc5de"};
|
|
5
|
+
styleInject(css_248z);
|
|
6
|
+
|
|
7
|
+
export { styles as default };
|
|
8
|
+
//# sourceMappingURL=ResourceRelationships.module.css.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResourceRelationships.module.css.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nospt/plugin-dev-ai-hub",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Frontend plugin for Dev AI Hub",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./dist/index.esm.js",
|
|
@@ -55,12 +55,12 @@
|
|
|
55
55
|
"tsc": "tsc --noEmit"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@backstage/core-components": "^0.18.
|
|
59
|
-
"@backstage/core-plugin-api": "^1.12.
|
|
60
|
-
"@backstage/frontend-plugin-api": "^0.
|
|
61
|
-
"@backstage/plugin-catalog-react": "^3.
|
|
62
|
-
"@backstage/ui": "^0.
|
|
63
|
-
"@nospt/plugin-dev-ai-hub-common": "^1.0.
|
|
58
|
+
"@backstage/core-components": "^0.18.13",
|
|
59
|
+
"@backstage/core-plugin-api": "^1.12.9",
|
|
60
|
+
"@backstage/frontend-plugin-api": "^0.18.0",
|
|
61
|
+
"@backstage/plugin-catalog-react": "^3.2.2",
|
|
62
|
+
"@backstage/ui": "^0.17.1",
|
|
63
|
+
"@nospt/plugin-dev-ai-hub-common": "^1.0.3",
|
|
64
64
|
"@remixicon/react": "^4.6.0",
|
|
65
65
|
"react-markdown": "^9.0.0",
|
|
66
66
|
"react-syntax-highlighter": "^16.1.1",
|
|
@@ -68,12 +68,12 @@
|
|
|
68
68
|
"simple-icons": "^13.0.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@backstage/cli": "^0.36.
|
|
72
|
-
"@backstage/dev-utils": "^1.1.
|
|
73
|
-
"@backstage/frontend-defaults": "^0.5.
|
|
74
|
-
"@backstage/plugin-app-react": "^0.2.
|
|
75
|
-
"@backstage/plugin-catalog": "^2.0.
|
|
76
|
-
"@backstage/plugin-user-settings": "^0.9.
|
|
71
|
+
"@backstage/cli": "^0.36.5",
|
|
72
|
+
"@backstage/dev-utils": "^1.1.26",
|
|
73
|
+
"@backstage/frontend-defaults": "^0.5.5",
|
|
74
|
+
"@backstage/plugin-app-react": "^0.2.6",
|
|
75
|
+
"@backstage/plugin-catalog": "^2.0.8",
|
|
76
|
+
"@backstage/plugin-user-settings": "^0.9.6",
|
|
77
77
|
"@playwright/test": "^1.60.0",
|
|
78
78
|
"@testing-library/dom": "^10.0.0",
|
|
79
79
|
"@testing-library/jest-dom": "^6.0.0",
|