@nospt/plugin-dev-ai-hub 0.2.4 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/api/DevAiHubResourceClient.esm.js +97 -0
  2. package/dist/api/DevAiHubResourceClient.esm.js.map +1 -0
  3. package/dist/components/AiResourcesPage/AiResourcesPage.esm.js +150 -0
  4. package/dist/components/AiResourcesPage/AiResourcesPage.esm.js.map +1 -0
  5. package/dist/components/AiResourcesPage/AiResourcesPage.module.css.esm.js +8 -0
  6. package/dist/components/AiResourcesPage/AiResourcesPage.module.css.esm.js.map +1 -0
  7. package/dist/components/AiResourcesPage/ResourceCard.esm.js +112 -0
  8. package/dist/components/AiResourcesPage/ResourceCard.esm.js.map +1 -0
  9. package/dist/components/AiResourcesPage/ResourceCard.module.css.esm.js +8 -0
  10. package/dist/components/AiResourcesPage/ResourceCard.module.css.esm.js.map +1 -0
  11. package/dist/components/AiResourcesPage/ResourceDetailPanel.esm.js +238 -0
  12. package/dist/components/AiResourcesPage/ResourceDetailPanel.esm.js.map +1 -0
  13. package/dist/components/AiResourcesPage/ResourceDetailPanel.module.css.esm.js +8 -0
  14. package/dist/components/AiResourcesPage/ResourceDetailPanel.module.css.esm.js.map +1 -0
  15. package/dist/components/AiResourcesPage/ResourceFilters.esm.js +242 -0
  16. package/dist/components/AiResourcesPage/ResourceFilters.esm.js.map +1 -0
  17. package/dist/components/AiResourcesPage/ResourceFilters.module.css.esm.js +8 -0
  18. package/dist/components/AiResourcesPage/ResourceFilters.module.css.esm.js.map +1 -0
  19. package/dist/components/AiResourcesPage/ResourceInstallDialog.esm.js +366 -0
  20. package/dist/components/AiResourcesPage/ResourceInstallDialog.esm.js.map +1 -0
  21. package/dist/components/AiResourcesPage/ResourceInstallDialog.module.css.esm.js +8 -0
  22. package/dist/components/AiResourcesPage/ResourceInstallDialog.module.css.esm.js.map +1 -0
  23. package/dist/components/AiResourcesPage/StatTiles.aurora.module.css.esm.js +8 -0
  24. package/dist/components/AiResourcesPage/StatTiles.aurora.module.css.esm.js.map +1 -0
  25. package/dist/components/AiResourcesPage/StatTiles.console.module.css.esm.js +8 -0
  26. package/dist/components/AiResourcesPage/StatTiles.console.module.css.esm.js.map +1 -0
  27. package/dist/components/AiResourcesPage/StatTiles.esm.js +82 -0
  28. package/dist/components/AiResourcesPage/StatTiles.esm.js.map +1 -0
  29. package/dist/components/AiResourcesPage/StatTiles.module.css.esm.js +8 -0
  30. package/dist/components/AiResourcesPage/StatTiles.module.css.esm.js.map +1 -0
  31. package/dist/components/AiResourcesPage/index.esm.js +6 -0
  32. package/dist/components/AiResourcesPage/typeMeta.esm.js +34 -0
  33. package/dist/components/AiResourcesPage/typeMeta.esm.js.map +1 -0
  34. package/dist/components/ToolIcon/ToolIcon.esm.js +28 -10
  35. package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -1
  36. package/dist/hooks/useResourceBody.esm.js +39 -0
  37. package/dist/hooks/useResourceBody.esm.js.map +1 -0
  38. package/dist/hooks/useResources.esm.js +26 -0
  39. package/dist/hooks/useResources.esm.js.map +1 -0
  40. package/dist/hooks/useTelemetryCounts.esm.js +24 -0
  41. package/dist/hooks/useTelemetryCounts.esm.js.map +1 -0
  42. package/dist/index.d.ts +1 -38
  43. package/dist/index.esm.js +1 -3
  44. package/dist/index.esm.js.map +1 -1
  45. package/dist/plugin.esm.js +15 -24
  46. package/dist/plugin.esm.js.map +1 -1
  47. package/dist/routes.esm.js +1 -2
  48. package/dist/routes.esm.js.map +1 -1
  49. package/package.json +17 -10
  50. package/.eslintrc.js +0 -1
  51. package/dev/index.tsx +0 -32
  52. package/dist/api/DevAiHubClient.esm.js +0 -78
  53. package/dist/api/DevAiHubClient.esm.js.map +0 -1
  54. package/dist/components/AssetCard/AssetCard.esm.js +0 -120
  55. package/dist/components/AssetCard/AssetCard.esm.js.map +0 -1
  56. package/dist/components/AssetCard/AssetCard.module.css.esm.js +0 -8
  57. package/dist/components/AssetCard/AssetCard.module.css.esm.js.map +0 -1
  58. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +0 -218
  59. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +0 -1
  60. package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js +0 -8
  61. package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js.map +0 -1
  62. package/dist/components/AssetFilters/AssetFilters.esm.js +0 -176
  63. package/dist/components/AssetFilters/AssetFilters.esm.js.map +0 -1
  64. package/dist/components/AssetFilters/AssetFilters.module.css.esm.js +0 -8
  65. package/dist/components/AssetFilters/AssetFilters.module.css.esm.js.map +0 -1
  66. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +0 -138
  67. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +0 -1
  68. package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js +0 -8
  69. package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js.map +0 -1
  70. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +0 -195
  71. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +0 -1
  72. package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js +0 -8
  73. package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js.map +0 -1
  74. package/dist/components/DevAiHubPage/index.esm.js +0 -6
  75. package/dist/components/McpConfigPage/McpConfigPage.esm.js +0 -200
  76. package/dist/components/McpConfigPage/McpConfigPage.esm.js.map +0 -1
  77. package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js +0 -8
  78. package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js.map +0 -1
  79. package/dist/components/McpConfigPage/index.esm.js +0 -6
  80. package/dist/components/McpConfigPage/index.esm.js.map +0 -1
  81. package/dist/hooks/index.esm.js +0 -114
  82. package/dist/hooks/index.esm.js.map +0 -1
  83. package/dist/pluginLegacy.esm.js +0 -27
  84. package/dist/pluginLegacy.esm.js.map +0 -1
  85. package/package.json-prepack +0 -85
  86. package/src/api/DevAiHubClient.ts +0 -110
  87. package/src/components/AssetCard/AssetCard.module.css +0 -153
  88. package/src/components/AssetCard/AssetCard.test.tsx +0 -182
  89. package/src/components/AssetCard/AssetCard.tsx +0 -152
  90. package/src/components/AssetCard/index.ts +0 -1
  91. package/src/components/AssetDetailPanel/AssetDetailPanel.module.css +0 -207
  92. package/src/components/AssetDetailPanel/AssetDetailPanel.tsx +0 -313
  93. package/src/components/AssetDetailPanel/index.ts +0 -1
  94. package/src/components/AssetFilters/AssetFilters.module.css +0 -67
  95. package/src/components/AssetFilters/AssetFilters.tsx +0 -219
  96. package/src/components/AssetFilters/index.ts +0 -2
  97. package/src/components/AssetInstallDialog/AssetInstallDialog.module.css +0 -72
  98. package/src/components/AssetInstallDialog/AssetInstallDialog.tsx +0 -176
  99. package/src/components/AssetInstallDialog/index.ts +0 -1
  100. package/src/components/DevAiHubPage/DevAiHubPage.module.css +0 -105
  101. package/src/components/DevAiHubPage/DevAiHubPage.tsx +0 -197
  102. package/src/components/DevAiHubPage/index.ts +0 -1
  103. package/src/components/McpConfigDialog/McpConfigDialog.module.css +0 -111
  104. package/src/components/McpConfigDialog/McpConfigDialog.tsx +0 -271
  105. package/src/components/McpConfigDialog/index.ts +0 -1
  106. package/src/components/McpConfigPage/McpConfigPage.module.css +0 -121
  107. package/src/components/McpConfigPage/McpConfigPage.tsx +0 -242
  108. package/src/components/McpConfigPage/index.ts +0 -1
  109. package/src/components/ToolIcon/ToolIcon.tsx +0 -65
  110. package/src/components/ToolIcon/index.ts +0 -1
  111. package/src/css-modules.d.ts +0 -4
  112. package/src/hooks/index.ts +0 -152
  113. package/src/index.ts +0 -10
  114. package/src/plugin.tsx +0 -60
  115. package/src/pluginLegacy.ts +0 -42
  116. package/src/routes.ts +0 -4
  117. package/tests/e2e/.auth.json +0 -14
  118. package/tests/e2e/asset-detail.spec.ts +0 -90
  119. package/tests/e2e/asset-filtering.spec.ts +0 -145
  120. package/tests/e2e/asset-installation.spec.ts +0 -74
  121. package/tests/e2e/dev-ai-hub-page.spec.ts +0 -89
  122. package/tests/e2e/fixtures/base.ts +0 -61
  123. package/tests/e2e/fixtures/mock-api.ts +0 -221
  124. package/tests/e2e/mcp-config.spec.ts +0 -88
  125. package/tsconfig.json +0 -10
  126. /package/dist/components/{DevAiHubPage → AiResourcesPage}/index.esm.js.map +0 -0
@@ -1,219 +0,0 @@
1
- import type { ElementType } from 'react';
2
- import { Box, Flex, Text, SearchField, ToggleButton, ToggleButtonGroup } from '@backstage/ui';
3
- import { RiAppsLine, RiArticleLine, RiRobot2Line, RiToolsLine, RiGitBranchLine, RiDatabase2Line } from '@remixicon/react';
4
- import type { AssetType, AiTool, AiHubProvider } from '@nospt/plugin-dev-ai-hub-common';
5
- import { ToolIcon } from '../ToolIcon';
6
- import styles from './AssetFilters.module.css';
7
-
8
- const ASSET_TYPES: { value: AssetType | 'all'; label: string; color: string; Icon: ElementType }[] = [
9
- { value: 'all', label: 'All', color: '#DCDDE1', Icon: RiAppsLine },
10
- { value: 'instruction', label: 'Instructions', color: '#54A0FF', Icon: RiArticleLine },
11
- { value: 'agent', label: 'Agents', color: '#FF6B9D', Icon: RiRobot2Line },
12
- { value: 'skill', label: 'Skills', color: '#6AB04C', Icon: RiToolsLine },
13
- { value: 'workflow', label: 'Workflows', color: '#F9CA24', Icon: RiGitBranchLine },
14
- ];
15
-
16
- const AI_TOOLS: { value: AiTool | 'all'; label: string }[] = [
17
- { value: 'all', label: 'All Tools' },
18
- { value: 'claude-code', label: 'Claude Code' },
19
- { value: 'github-copilot', label: 'GitHub Copilot' },
20
- { value: 'google-gemini', label: 'Google Gemini' },
21
- { value: 'cursor', label: 'Cursor' },
22
- ];
23
-
24
- export interface AssetFiltersValue {
25
- types: AssetType[];
26
- tools: AiTool[];
27
- search: string;
28
- tags: string[];
29
- providerId?: string;
30
- }
31
-
32
- interface AssetFiltersProps {
33
- value: AssetFiltersValue;
34
- onChange: (value: AssetFiltersValue) => void;
35
- availableTags?: string[];
36
- providers?: AiHubProvider[];
37
- }
38
-
39
- export function AssetFilters({ value, onChange, availableTags = [], providers }: AssetFiltersProps) {
40
- const selectedType = value.types.length === 1 ? value.types[0] : 'all';
41
- const selectedTool = value.tools.length === 1 ? value.tools[0] : 'all';
42
- const showProviderFilter = providers && providers.length > 1;
43
-
44
- const handleTypeClick = (type: AssetType | 'all') => {
45
- onChange({ ...value, types: type === 'all' ? [] : [type] });
46
- };
47
-
48
- const handleToolClick = (tool: AiTool | 'all') => {
49
- onChange({ ...value, tools: tool === 'all' ? [] : [tool] });
50
- };
51
-
52
- const handleTagToggle = (tag: string) => {
53
- const next = value.tags.includes(tag)
54
- ? value.tags.filter(t => t !== tag)
55
- : [...value.tags, tag];
56
- onChange({ ...value, tags: next });
57
- };
58
-
59
- const handleProviderClick = (id: string | undefined) => {
60
- onChange({ ...value, providerId: id });
61
- };
62
-
63
- return (
64
- <Flex className={styles.container}>
65
- <SearchField
66
- aria-label="Search assets"
67
- className={styles.searchField}
68
- placeholder="Search assets by name, description or content…"
69
- value={value.search}
70
- onChange={v => onChange({ ...value, search: v })}
71
- />
72
-
73
- <Flex className={styles.filtersRow}>
74
- {/* Type filter */}
75
- <Box>
76
- <Text variant="body-x-small" color="secondary" className={styles.filterLabel}>
77
- Type
78
- </Text>
79
- <Flex className={styles.filterChips}>
80
- {ASSET_TYPES.map(t => {
81
- const isSelected = selectedType === t.value;
82
- const TypeIcon = t.Icon;
83
- return (
84
- <ToggleButton
85
- key={t.value}
86
- id={t.value}
87
- size="small"
88
- className={`${styles.filterChip} ${isSelected ? styles.filterChipSelected : ''}`}
89
- iconStart={<TypeIcon size={14} style={{ color: isSelected ? '#fff' : t.color }} />}
90
- isSelected={isSelected}
91
- onChange={() => handleTypeClick(t.value as AssetType | 'all')}
92
- style={{
93
- borderColor: isSelected ? t.color : `${t.color}30`,
94
- backgroundColor: isSelected ? t.color : `${t.color}10`,
95
- color: isSelected ? '#fff' : t.color,
96
- }}
97
- >
98
- {t.label}
99
- </ToggleButton>
100
- );
101
- })}
102
- </Flex>
103
- </Box>
104
-
105
- {/* AI Tool filter */}
106
- <Box>
107
- <Text variant="body-x-small" color="secondary" className={styles.filterLabel}>
108
- AI Tool
109
- </Text>
110
- <Flex className={styles.filterChips}>
111
- {AI_TOOLS.map(t => {
112
- const isSelected = selectedTool === t.value;
113
- const iconEl = t.value !== 'all'
114
- ? <ToolIcon tool={t.value as AiTool} branded size={14} />
115
- : undefined;
116
- return (
117
- <ToggleButton
118
- key={t.value}
119
- id={t.value}
120
- size="small"
121
- className={`${styles.filterChip} ${isSelected ? styles.toolChipSelected : ''}`}
122
- iconStart={iconEl}
123
- isSelected={isSelected}
124
- onChange={() => handleToolClick(t.value as AiTool | 'all')}
125
- style={{
126
- borderColor: isSelected ? 'var(--bui-fg-link)' : 'var(--bui-border-1)',
127
- backgroundColor: isSelected ? 'var(--bui-bg-accent-1)' : 'transparent',
128
- color: isSelected ? 'var(--bui-fg-link)' : 'var(--bui-fg-secondary)',
129
- }}
130
- >
131
- {t.label}
132
- </ToggleButton>
133
- );
134
- })}
135
- </Flex>
136
- </Box>
137
-
138
- {/* Provider filter — only shown when there are 2+ providers */}
139
- {showProviderFilter && (
140
- <Box>
141
- <Text variant="body-x-small" color="secondary" className={styles.filterLabel}>
142
- Provider
143
- </Text>
144
- <Flex className={styles.filterChips}>
145
- <ToggleButton
146
- id="all-providers"
147
- size="small"
148
- className={`${styles.filterChip} ${!value.providerId ? styles.filterChipSelected : ''}`}
149
- iconStart={<RiAppsLine size={14} />}
150
- isSelected={!value.providerId}
151
- onChange={() => handleProviderClick(undefined)}
152
- style={{
153
- borderColor: !value.providerId ? 'var(--bui-fg-primary)' : 'var(--bui-border-1)',
154
- backgroundColor: !value.providerId ? 'var(--bui-fg-primary)' : 'transparent',
155
- color: !value.providerId ? 'var(--bui-bg-neutral-1)' : 'var(--bui-fg-secondary)',
156
- }}
157
- >
158
- All
159
- </ToggleButton>
160
- {providers.map(p => {
161
- const isSelected = value.providerId === p.id;
162
- const label = p.target.split('/').slice(-1)[0]?.replace(/\.git$/, '') ?? p.id;
163
- return (
164
- <ToggleButton
165
- key={p.id}
166
- id={p.id}
167
- size="small"
168
- className={`${styles.filterChip} ${isSelected ? styles.filterChipSelected : ''}`}
169
- iconStart={<RiDatabase2Line size={14} style={{ color: isSelected ? 'var(--bui-bg-neutral-1)' : 'inherit' }} />}
170
- isSelected={isSelected}
171
- onChange={() => handleProviderClick(isSelected ? undefined : p.id)}
172
- style={{
173
- borderColor: isSelected ? 'var(--bui-fg-primary)' : 'var(--bui-border-1)',
174
- backgroundColor: isSelected ? 'var(--bui-fg-primary)' : 'transparent',
175
- color: isSelected ? 'var(--bui-bg-neutral-1)' : 'var(--bui-fg-secondary)',
176
- }}
177
- >
178
- {label}
179
- </ToggleButton>
180
- );
181
- })}
182
- </Flex>
183
- </Box>
184
- )}
185
- </Flex>
186
-
187
- {availableTags.length > 0 && (
188
- <Box>
189
- <Text variant="body-x-small" color="secondary" className={styles.filterLabel}>
190
- Tags
191
- </Text>
192
- <Flex className={styles.filterChips}>
193
- {availableTags.map(tag => {
194
- const isActive = value.tags.includes(tag);
195
- return (
196
- <ToggleButton
197
- key={tag}
198
- id={tag}
199
- size="small"
200
- className={`${styles.tagChip} ${isActive ? styles.tagChipSelected : ''}`}
201
- isSelected={isActive}
202
- onChange={() => handleTagToggle(tag)}
203
- style={{
204
- fontWeight: isActive ? 700 : 400,
205
- backgroundColor: isActive ? 'var(--bui-bg-solid)' : 'transparent',
206
- color: isActive ? '#fff' : 'var(--bui-fg-secondary)',
207
- borderColor: isActive ? 'var(--bui-bg-solid)' : 'var(--bui-border-1)',
208
- }}
209
- >
210
- #{tag}
211
- </ToggleButton>
212
- );
213
- })}
214
- </Flex>
215
- </Box>
216
- )}
217
- </Flex>
218
- );
219
- }
@@ -1,2 +0,0 @@
1
- export { AssetFilters } from './AssetFilters';
2
- export type { AssetFiltersValue } from './AssetFilters';
@@ -1,72 +0,0 @@
1
- @layer components {
2
- .subtitle {
3
- margin-top: var(--bui-space-1);
4
- font-weight: 400;
5
- }
6
-
7
- .loadingContainer {
8
- display: flex;
9
- justify-content: center;
10
- padding: var(--bui-space-6) 0;
11
- }
12
-
13
- .zipInfo {
14
- border: 1px solid #00D2D3;
15
- border-radius: var(--bui-radius-2);
16
- padding: var(--bui-space-3);
17
- background-color: color-mix(in srgb, #00D2D3 7%, transparent);
18
- }
19
-
20
- .zipHeader {
21
- display: flex;
22
- align-items: center;
23
- gap: var(--bui-space-2);
24
- margin-bottom: var(--bui-space-2);
25
- }
26
-
27
- .zipFiles {
28
- display: flex;
29
- gap: var(--bui-space-1);
30
- flex-wrap: wrap;
31
- }
32
-
33
- .monoTag {
34
- font-family: monospace;
35
- font-size: 0.7rem;
36
- height: 20px;
37
- }
38
-
39
- .toolSection {
40
- border: 1px solid var(--bui-border-1);
41
- border-radius: var(--bui-radius-2);
42
- padding: var(--bui-space-3);
43
- }
44
-
45
- .toolHeader {
46
- display: flex;
47
- align-items: center;
48
- gap: var(--bui-space-2);
49
- margin-bottom: var(--bui-space-2);
50
- }
51
-
52
- .installPathBox {
53
- background-color: var(--bui-bg-neutral-1-hover);
54
- border: 1px solid var(--bui-border-1);
55
- border-radius: var(--bui-radius-2);
56
- padding: var(--bui-space-2) var(--bui-space-3);
57
- font-family: monospace;
58
- font-size: 0.78rem;
59
- color: var(--bui-fg-primary);
60
- word-break: break-all;
61
- margin-bottom: 10px;
62
- }
63
-
64
- .toolActions {
65
- display: flex;
66
- gap: var(--bui-space-2);
67
- }
68
-
69
- .toolActions > * {
70
- flex: 1;
71
- }
72
- }
@@ -1,176 +0,0 @@
1
- import { useState } from 'react';
2
- import {
3
- Box, Flex, Text, Button, Tag, TagGroup, Skeleton,
4
- Dialog, DialogTrigger, DialogHeader, DialogBody, DialogFooter,
5
- Tooltip, TooltipTrigger,
6
- } from '@backstage/ui';
7
- import { RiFileCopyLine, RiDownloadLine, RiCheckLine, RiFolderZipLine } from '@remixicon/react';
8
- import type { AiTool } from '@nospt/plugin-dev-ai-hub-common';
9
- import { getInstallPathsForAsset } from '@nospt/plugin-dev-ai-hub-common';
10
- import { useApi } from '@backstage/core-plugin-api';
11
- import { devAiHubApiRef } from '../../api/DevAiHubClient';
12
- import { useAssetDetail } from '../../hooks';
13
- import { ToolIcon } from '../ToolIcon';
14
- import styles from './AssetInstallDialog.module.css';
15
-
16
- const TOOL_LABELS: Record<string, string> = {
17
- 'claude-code': 'Claude Code',
18
- 'github-copilot': 'GitHub Copilot',
19
- 'google-gemini': 'Google Gemini',
20
- 'cursor': 'Cursor',
21
- };
22
-
23
- interface AssetInstallDialogProps {
24
- assetId: string | null;
25
- onClose: () => void;
26
- }
27
-
28
- export function AssetInstallDialog({ assetId, onClose }: AssetInstallDialogProps) {
29
- const [copiedTool, setCopiedTool] = useState<string | null>(null);
30
- const api = useApi(devAiHubApiRef);
31
- const { asset, loading } = useAssetDetail(assetId);
32
-
33
- const handleClose = () => {
34
- onClose();
35
- setCopiedTool(null);
36
- };
37
-
38
- const handleCopy = (tool: string) => {
39
- if (!asset) return;
40
- navigator.clipboard.writeText(asset.content).then(() => {
41
- setCopiedTool(tool);
42
- setTimeout(() => setCopiedTool(null), 2000);
43
- });
44
- api.trackInstall(asset.id).catch(() => {});
45
- };
46
-
47
- const resourcePaths = asset?.resourcesContent
48
- ? Object.keys(asset.resourcesContent)
49
- : [];
50
- const isZipSkill = asset?.type === 'skill' && resourcePaths.length > 0;
51
-
52
- const handleDownload = async (_tool: string, installPath: string) => {
53
- if (!asset) return;
54
- if (isZipSkill) {
55
- const url = await api.getDownloadUrl(asset.id);
56
- const a = document.createElement('a');
57
- a.href = url;
58
- a.download = `${asset.name.replace(/\s+/g, '-').toLowerCase()}.zip`;
59
- a.click();
60
- } else {
61
- const filename = installPath.split('/').pop() ?? `${asset.name}.md`;
62
- const blob = new Blob([asset.content], { type: 'text/markdown' });
63
- const url = URL.createObjectURL(blob);
64
- const a = document.createElement('a');
65
- a.href = url;
66
- a.download = filename;
67
- a.click();
68
- URL.revokeObjectURL(url);
69
- }
70
- api.trackInstall(asset.id).catch(() => {});
71
- };
72
-
73
- const installPaths = asset
74
- ? getInstallPathsForAsset(asset.type, asset.tools, asset.name, {
75
- installPath: asset.installPath,
76
- installPaths: asset.installPaths,
77
- })
78
- : {};
79
-
80
- return (
81
- <DialogTrigger>
82
- <Dialog
83
- isOpen={!!assetId}
84
- isDismissable
85
- onOpenChange={open => { if (!open) handleClose(); }}
86
- >
87
- <DialogHeader>
88
- {asset ? `Install: ${asset.name}` : 'Install'}
89
- <Text variant="body-small" color="secondary" className={styles.subtitle}>
90
- Copy the content and place the file at the path shown for your tool.
91
- </Text>
92
- </DialogHeader>
93
-
94
- <DialogBody>
95
- <Flex direction="column" style={{ gap: 'var(--bui-space-3)' }}>
96
- {loading && (
97
- <Flex className={styles.loadingContainer}>
98
- <Skeleton style={{ width: '100%', height: 80 }} />
99
- </Flex>
100
- )}
101
-
102
- {!loading && asset && isZipSkill && (
103
- <Box className={styles.zipInfo}>
104
- <Flex className={styles.zipHeader}>
105
- <RiFolderZipLine size={16} style={{ color: 'var(--bui-fg-info)' }} />
106
- <Text variant="body-small" weight="bold" style={{ color: 'var(--bui-fg-info)' }}>
107
- Bundled skill — downloads as .zip
108
- </Text>
109
- </Flex>
110
- <Text variant="body-x-small" color="secondary" style={{ display: 'block', marginBottom: 'var(--bui-space-2)' }}>
111
- This skill includes resource files alongside <code>SKILL.md</code>.
112
- Extract the zip and place all files in the skill directory.
113
- </Text>
114
- <TagGroup aria-label="Bundled files">
115
- <Flex className={styles.zipFiles}>
116
- <Tag id="skill-md" size="small" className={styles.monoTag}>SKILL.md</Tag>
117
- {resourcePaths.map(p => (
118
- <Tag key={p} id={p} size="small" className={styles.monoTag}>{p}</Tag>
119
- ))}
120
- </Flex>
121
- </TagGroup>
122
- </Box>
123
- )}
124
-
125
- {!loading && asset && Object.entries(installPaths).map(([tool, installPath]) => (
126
- <Box key={tool} className={styles.toolSection}>
127
- <Flex className={styles.toolHeader}>
128
- <ToolIcon tool={tool as AiTool} size={16} />
129
- <Text variant="body-small" weight="bold">
130
- {TOOL_LABELS[tool] ?? tool}
131
- </Text>
132
- </Flex>
133
-
134
- <Text variant="body-x-small" color="secondary" style={{ display: 'block', marginBottom: 'var(--bui-space-1)' }}>
135
- Install path
136
- </Text>
137
- <Box className={styles.installPathBox}>
138
- {installPath}
139
- </Box>
140
-
141
- <Flex className={styles.toolActions}>
142
- <TooltipTrigger>
143
- <Button
144
- variant="secondary"
145
- onClick={() => handleCopy(tool)}
146
- >
147
- {copiedTool === tool ? <RiCheckLine size={14} /> : <RiFileCopyLine size={14} />}
148
- {copiedTool === tool ? 'Copied!' : 'Copy Content'}
149
- </Button>
150
- <Tooltip>{copiedTool === tool ? 'Copied!' : 'Copy markdown content'}</Tooltip>
151
- </TooltipTrigger>
152
- <TooltipTrigger>
153
- <Button
154
- variant="secondary"
155
- onClick={() => handleDownload(tool, installPath)}
156
- >
157
- {isZipSkill ? <RiFolderZipLine size={14} /> : <RiDownloadLine size={14} />}
158
- {isZipSkill ? 'Download .zip' : 'Download'}
159
- </Button>
160
- <Tooltip>{isZipSkill ? 'Download as .zip with all bundled files' : 'Download file with correct name'}</Tooltip>
161
- </TooltipTrigger>
162
- </Flex>
163
- </Box>
164
- ))}
165
- </Flex>
166
- </DialogBody>
167
-
168
- <DialogFooter>
169
- <Button onClick={handleClose} variant="secondary" slot="close">
170
- Close
171
- </Button>
172
- </DialogFooter>
173
- </Dialog>
174
- </DialogTrigger>
175
- );
176
- }
@@ -1 +0,0 @@
1
- export { AssetInstallDialog } from './AssetInstallDialog';
@@ -1,105 +0,0 @@
1
- @layer components {
2
- .pageRoot {
3
- display: flex;
4
- flex-direction: column;
5
- height: 100%;
6
- }
7
-
8
- .content {
9
- flex: 1;
10
- padding: var(--bui-space-6);
11
- overflow-y: auto;
12
- }
13
-
14
- .statsGrid {
15
- display: grid;
16
- grid-template-columns: repeat(4, 1fr);
17
- gap: var(--bui-space-4);
18
- margin-bottom: var(--bui-space-6);
19
- }
20
-
21
- @media (max-width: 600px) {
22
- .statsGrid {
23
- grid-template-columns: repeat(2, 1fr);
24
- }
25
- }
26
-
27
- .statCard {
28
- border-radius: var(--bui-radius-3);
29
- padding: var(--bui-space-4);
30
- cursor: pointer;
31
- transition: all 0.2s ease;
32
- }
33
-
34
- .statCard:hover {
35
- transform: translateY(-2px);
36
- }
37
-
38
- .statCardInner {
39
- display: flex;
40
- justify-content: space-between;
41
- align-items: center;
42
- }
43
-
44
- .statValue {
45
- color: #fff;
46
- line-height: 1;
47
- }
48
-
49
- .statLabel {
50
- color: rgba(255, 255, 255, 0.85);
51
- font-weight: 500;
52
- margin-top: var(--bui-space-1);
53
- }
54
-
55
- .statIconBox {
56
- width: 40px;
57
- height: 40px;
58
- border-radius: var(--bui-radius-2);
59
- background-color: rgba(255, 255, 255, 0.2);
60
- display: flex;
61
- align-items: center;
62
- justify-content: center;
63
- }
64
-
65
- .assetGrid {
66
- display: grid;
67
- grid-template-columns: repeat(4, 1fr);
68
- gap: var(--bui-space-3);
69
- }
70
-
71
- @media (max-width: 1200px) {
72
- .assetGrid {
73
- grid-template-columns: repeat(3, 1fr);
74
- }
75
- }
76
-
77
- @media (max-width: 900px) {
78
- .assetGrid {
79
- grid-template-columns: repeat(2, 1fr);
80
- }
81
- }
82
-
83
- @media (max-width: 600px) {
84
- .assetGrid {
85
- grid-template-columns: 1fr;
86
- }
87
- }
88
-
89
- .emptyState {
90
- padding: 96px 0;
91
- text-align: center;
92
- }
93
-
94
- .emptyEmoji {
95
- margin-bottom: var(--bui-space-4);
96
- opacity: 0.15;
97
- font-size: 5rem;
98
- }
99
-
100
- .paginationRow {
101
- display: flex;
102
- justify-content: center;
103
- margin-top: var(--bui-space-8);
104
- }
105
- }