@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,121 +0,0 @@
1
- @layer components {
2
- .pageRoot {
3
- padding: var(--bui-space-6);
4
- }
5
-
6
- .subtitle {
7
- margin-top: var(--bui-space-1);
8
- display: block;
9
- }
10
-
11
- .configContent {
12
- margin-top: var(--bui-space-6);
13
- max-width: 720px;
14
- }
15
-
16
- .tabBar {
17
- margin-bottom: var(--bui-space-4);
18
- border-bottom: 1px solid var(--bui-border-1);
19
- }
20
-
21
- .tabContent {
22
- display: flex;
23
- align-items: center;
24
- gap: var(--bui-space-2);
25
- }
26
-
27
- .sectionLabel {
28
- text-transform: uppercase;
29
- letter-spacing: 0.5px;
30
- display: block;
31
- margin-bottom: 6px;
32
- font-weight: 600;
33
- }
34
-
35
- .providerSection {
36
- margin-bottom: var(--bui-space-4);
37
- }
38
-
39
- .chipsRow {
40
- display: flex;
41
- gap: 6px;
42
- flex-wrap: wrap;
43
- }
44
-
45
- .filterChip {
46
- font-weight: 600;
47
- font-size: 0.75rem;
48
- border-radius: var(--bui-radius-2);
49
- border: 1.5px solid;
50
- transition: all 0.15s ease;
51
- cursor: pointer;
52
- }
53
-
54
- .proactiveSection {
55
- margin-bottom: var(--bui-space-4);
56
- }
57
-
58
- .proactiveRow {
59
- display: flex;
60
- align-items: flex-start;
61
- gap: var(--bui-space-2);
62
- }
63
-
64
- .urlSection {
65
- margin-bottom: var(--bui-space-4);
66
- }
67
-
68
- .urlBox {
69
- display: flex;
70
- align-items: center;
71
- gap: var(--bui-space-2);
72
- margin-top: var(--bui-space-1);
73
- padding: var(--bui-space-2) var(--bui-space-3);
74
- border-radius: var(--bui-radius-2);
75
- border: 1px solid var(--bui-border-1);
76
- background-color: var(--bui-bg-neutral-1-hover);
77
- }
78
-
79
- .urlText {
80
- flex: 1;
81
- font-family: monospace;
82
- font-size: 0.8rem;
83
- word-break: break-all;
84
- }
85
-
86
- .snippetContainer {
87
- position: relative;
88
- }
89
-
90
- .snippetPre {
91
- margin: 0;
92
- padding: var(--bui-space-4);
93
- border-radius: var(--bui-radius-2);
94
- border: 1px solid var(--bui-border-1);
95
- font-family: monospace;
96
- font-size: 0.8rem;
97
- overflow-x: auto;
98
- white-space: pre;
99
- }
100
-
101
- .snippetPreLight {
102
- background-color: #f6f8fa;
103
- color: #24292f;
104
- }
105
-
106
- .snippetPreDark {
107
- background-color: #1E1E1E;
108
- color: #DCDDE1;
109
- }
110
-
111
- .copySnippetButton {
112
- position: absolute;
113
- top: 8px;
114
- right: 8px;
115
- }
116
-
117
- .hint {
118
- display: block;
119
- margin-top: var(--bui-space-3);
120
- }
121
- }
@@ -1,242 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import {
3
- Box, Flex, Text, ButtonIcon, ToggleButton, Switch,
4
- Tabs, TabList, Tab, TabPanel,
5
- Tooltip, TooltipTrigger,
6
- } from '@backstage/ui';
7
- import { RiFileCopyLine, RiCheckLine, RiDatabase2Line } from '@remixicon/react';
8
- import { useApi, discoveryApiRef } from '@backstage/core-plugin-api';
9
- import { ToolIcon } from '../ToolIcon';
10
- import { useCopyToClipboard, useProviders } from '../../hooks';
11
- import type { AiTool } from '@nospt/plugin-dev-ai-hub-common';
12
- import styles from './McpConfigPage.module.css';
13
-
14
- interface ToolConfig {
15
- tool: AiTool;
16
- label: string;
17
- file: string;
18
- description: string;
19
- buildConfig: (mcpUrl: string) => string;
20
- }
21
-
22
- const TOOL_CONFIGS: ToolConfig[] = [
23
- {
24
- tool: 'claude-code',
25
- label: 'Claude Code',
26
- file: '.mcp.json',
27
- description: 'Add to .mcp.json in your project root (or run via claude mcp add):',
28
- buildConfig: url => JSON.stringify({
29
- mcpServers: { 'dev-ai-hub': { type: 'http', url } },
30
- }, null, 2),
31
- },
32
- {
33
- tool: 'github-copilot',
34
- label: 'GitHub Copilot',
35
- file: '.vscode/settings.json',
36
- description: 'Add to your VS Code settings (.vscode/settings.json or user settings):',
37
- buildConfig: url => JSON.stringify({
38
- 'github.copilot.chat.mcp.servers': { 'dev-ai-hub': { type: 'http', url } },
39
- }, null, 2),
40
- },
41
- {
42
- tool: 'google-gemini',
43
- label: 'Google Gemini',
44
- file: 'gemini-config.json',
45
- description: 'Add to your Gemini CLI configuration:',
46
- buildConfig: url => JSON.stringify({
47
- mcpServers: { 'dev-ai-hub': { url } },
48
- }, null, 2),
49
- },
50
- ];
51
-
52
- function providerLabel(target: string): string {
53
- return target.split('/').pop()?.replace(/\.git$/, '') ?? target;
54
- }
55
-
56
- export function McpConfigPage() {
57
- const discoveryApi = useApi(discoveryApiRef);
58
- const { copy: copyUrl, copied: copiedUrl } = useCopyToClipboard();
59
- const { copy: copySnippet, copied: copiedSnippet } = useCopyToClipboard();
60
- const [selectedToolKey, setSelectedToolKey] = useState<string>('claude-code');
61
- const [baseUrl, setBaseUrl] = useState('');
62
- const [selectedProvider, setSelectedProvider] = useState<string>('');
63
- const [proactiveEnabled, setProactiveEnabled] = useState(false);
64
-
65
- const { providers } = useProviders();
66
- const showProviderFilter = providers.length > 1;
67
-
68
- useEffect(() => {
69
- discoveryApi.getBaseUrl('dev-ai-hub').then(url => setBaseUrl(url));
70
- }, [discoveryApi]);
71
-
72
- const cfg = TOOL_CONFIGS.find(t => t.tool === selectedToolKey) ?? TOOL_CONFIGS[0];
73
-
74
- const buildMcpUrl = () => {
75
- if (!baseUrl) return 'loading...';
76
- const params = new URLSearchParams();
77
- params.set('tool', cfg.tool);
78
- if (selectedProvider) params.set('provider', selectedProvider);
79
- if (proactiveEnabled) params.set('proactive', 'true');
80
- return `${baseUrl}/mcp?${params.toString()}`;
81
- };
82
-
83
- const mcpUrl = buildMcpUrl();
84
- const configSnippet = baseUrl ? cfg.buildConfig(mcpUrl) : '';
85
-
86
- const [isDark, setIsDark] = useState(() => {
87
- if (typeof document === 'undefined') return false;
88
- return document.documentElement.getAttribute('data-theme-mode') === 'dark' ||
89
- document.body.getAttribute('data-theme-mode') === 'dark';
90
- });
91
-
92
- useEffect(() => {
93
- const getIsDark = () =>
94
- document.documentElement.getAttribute('data-theme-mode') === 'dark' ||
95
- document.body.getAttribute('data-theme-mode') === 'dark';
96
-
97
- const observer = new MutationObserver(() => setIsDark(getIsDark()));
98
- observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme-mode'] });
99
- observer.observe(document.body, { attributes: true, attributeFilter: ['data-theme-mode'] });
100
- return () => observer.disconnect();
101
- }, []);
102
-
103
- return (
104
- <div className={styles.pageRoot}>
105
- <Text variant="title-small" weight="bold">Configure MCP Server</Text>
106
- <Text variant="body-small" color="secondary" className={styles.subtitle}>
107
- Connect your AI tool to the Dev AI Hub via Model Context Protocol.
108
- </Text>
109
-
110
- <div className={styles.configContent}>
111
- <Tabs selectedKey={selectedToolKey} onSelectionChange={key => setSelectedToolKey(key as string)}>
112
- <TabList className={styles.tabBar}>
113
- {TOOL_CONFIGS.map(t => (
114
- <Tab key={t.tool} id={t.tool}>
115
- <span className={styles.tabContent}>
116
- <ToolIcon tool={t.tool} size={16} />
117
- <span>{t.label}</span>
118
- </span>
119
- </Tab>
120
- ))}
121
- </TabList>
122
-
123
- {TOOL_CONFIGS.map(t => (
124
- <TabPanel key={t.tool} id={t.tool}>
125
- {showProviderFilter && (
126
- <Box className={styles.providerSection}>
127
- <Text variant="body-x-small" color="secondary" className={styles.sectionLabel}>
128
- Scope to Provider
129
- </Text>
130
- <Flex className={styles.chipsRow}>
131
- <ToggleButton
132
- id="all-providers"
133
- size="small"
134
- className={styles.filterChip}
135
- isSelected={!selectedProvider}
136
- onChange={() => setSelectedProvider('')}
137
- style={{
138
- borderColor: !selectedProvider ? 'var(--bui-fg-primary)' : 'var(--bui-border-1)',
139
- backgroundColor: !selectedProvider ? 'var(--bui-fg-primary)' : 'transparent',
140
- color: !selectedProvider ? 'var(--bui-bg-neutral-1)' : 'var(--bui-fg-secondary)',
141
- }}
142
- >
143
- All providers
144
- </ToggleButton>
145
- {providers.map(p => {
146
- const isSelected = selectedProvider === p.id;
147
- const label = providerLabel(p.target);
148
- return (
149
- <ToggleButton
150
- key={p.id}
151
- id={p.id}
152
- size="small"
153
- className={styles.filterChip}
154
- iconStart={<RiDatabase2Line size={12} style={{ color: isSelected ? 'var(--bui-bg-neutral-1)' : 'inherit' }} />}
155
- isSelected={isSelected}
156
- onChange={() => setSelectedProvider(isSelected ? '' : p.id)}
157
- style={{
158
- borderColor: isSelected ? 'var(--bui-fg-primary)' : 'var(--bui-border-1)',
159
- backgroundColor: isSelected ? 'var(--bui-fg-primary)' : 'transparent',
160
- color: isSelected ? 'var(--bui-bg-neutral-1)' : 'var(--bui-fg-secondary)',
161
- }}
162
- >
163
- {label}
164
- </ToggleButton>
165
- );
166
- })}
167
- </Flex>
168
- </Box>
169
- )}
170
-
171
- <Flex className={styles.proactiveSection}>
172
- <Flex className={styles.proactiveRow}>
173
- <Switch
174
- isSelected={proactiveEnabled}
175
- onChange={setProactiveEnabled}
176
- aria-label="Proactive suggestions"
177
- />
178
- <Box>
179
- <Text variant="body-small" weight="bold">Proactive suggestions</Text>
180
- <Text variant="body-x-small" color="secondary">
181
- The AI will automatically suggest relevant assets based on your project context.
182
- Disable if you prefer to search manually.
183
- </Text>
184
- </Box>
185
- </Flex>
186
- </Flex>
187
-
188
- <Box className={styles.urlSection}>
189
- <Text variant="body-x-small" color="secondary" className={styles.sectionLabel}>
190
- MCP Endpoint
191
- </Text>
192
- <Flex className={styles.urlBox}>
193
- <Text variant="body-small" className={styles.urlText}>
194
- {mcpUrl}
195
- </Text>
196
- <TooltipTrigger>
197
- <ButtonIcon
198
- aria-label={copiedUrl ? 'Copied!' : 'Copy URL'}
199
- icon={copiedUrl ? <RiCheckLine size={14} style={{ color: 'var(--bui-fg-success)' }} /> : <RiFileCopyLine size={14} />}
200
- variant="tertiary"
201
- onPress={() => copyUrl(mcpUrl)}
202
- />
203
- <Tooltip>{copiedUrl ? 'Copied!' : 'Copy URL'}</Tooltip>
204
- </TooltipTrigger>
205
- </Flex>
206
- </Box>
207
-
208
- <Text variant="body-x-small" color="secondary" className={styles.sectionLabel}>
209
- {t.file}
210
- </Text>
211
- <Text variant="body-x-small" color="secondary" style={{ display: 'block', marginBottom: 'var(--bui-space-2)', marginTop: 'var(--bui-space-1)' }}>
212
- {t.description}
213
- </Text>
214
- <div className={styles.snippetContainer}>
215
- <pre className={`${styles.snippetPre} ${isDark ? styles.snippetPreDark : styles.snippetPreLight}`}>
216
- {configSnippet}
217
- </pre>
218
- <TooltipTrigger>
219
- <ButtonIcon
220
- aria-label="Copy config"
221
- className={styles.copySnippetButton}
222
- icon={copiedSnippet ? <RiCheckLine size={14} style={{ color: 'var(--bui-fg-success)' }} /> : <RiFileCopyLine size={14} />}
223
- variant="tertiary"
224
- onPress={() => copySnippet(configSnippet)}
225
- />
226
- <Tooltip>{copiedSnippet ? 'Copied!' : 'Copy config'}</Tooltip>
227
- </TooltipTrigger>
228
- </div>
229
-
230
- <Text variant="body-x-small" color="secondary" className={styles.hint}>
231
- 💡 Omit <code>?tool=</code> from the URL to receive assets for all AI tools.
232
- {showProviderFilter && ' Omit ?provider= to receive assets from all repositories.'}
233
- {' '}Proactive suggestions add <code>?proactive=true</code> and register the{' '}
234
- <code>suggest_assets</code> tool and <code>check_for_assets</code> prompt.
235
- </Text>
236
- </TabPanel>
237
- ))}
238
- </Tabs>
239
- </div>
240
- </div>
241
- );
242
- }
@@ -1 +0,0 @@
1
- export { McpConfigPage } from './McpConfigPage';
@@ -1,65 +0,0 @@
1
- import type { CSSProperties } from 'react';
2
- import { RiInfinityLine, RiNavigationLine } from '@remixicon/react';
3
- import { siAnthropic, siGithub, siGooglegemini } from 'simple-icons';
4
- import type { AiTool } from '@nospt/plugin-dev-ai-hub-common';
5
-
6
- type SvgTool = Exclude<AiTool, 'all' | 'cursor'>;
7
-
8
- const TOOL_ICON: Record<SvgTool, { path: string; hex: string; label: string }> = {
9
- 'claude-code': { ...siAnthropic, label: 'Claude Code' },
10
- 'github-copilot': { ...siGithub, label: 'GitHub Copilot' },
11
- 'google-gemini': { ...siGooglegemini, label: 'Google Gemini' },
12
- };
13
-
14
- interface ToolIconProps {
15
- tool: AiTool;
16
- /** Use the brand's official color. Defaults to true. */
17
- branded?: boolean;
18
- className?: string;
19
- style?: CSSProperties;
20
- /** Icon size in pixels. Defaults to 20. */
21
- size?: number;
22
- }
23
-
24
- export function ToolIcon({ tool, branded = true, className, style, size = 20 }: ToolIconProps) {
25
- if (tool === 'all') {
26
- return (
27
- <RiInfinityLine
28
- size={size}
29
- className={className}
30
- style={{ color: 'var(--bui-fg-secondary)', ...style }}
31
- aria-label="Universal"
32
- />
33
- );
34
- }
35
-
36
- if (tool === 'cursor') {
37
- return (
38
- <RiNavigationLine
39
- size={size}
40
- className={className}
41
- style={{ color: branded ? 'var(--bui-fg-primary)' : 'inherit', ...style }}
42
- aria-label="Cursor"
43
- />
44
- );
45
- }
46
-
47
- const cfg = TOOL_ICON[tool as SvgTool];
48
- if (!cfg) return null;
49
-
50
- return (
51
- <svg
52
- xmlns="http://www.w3.org/2000/svg"
53
- viewBox="0 0 24 24"
54
- width={size}
55
- height={size}
56
- fill="currentColor"
57
- className={className}
58
- style={{ color: branded ? `#${cfg.hex}` : 'inherit', ...style }}
59
- aria-label={cfg.label}
60
- role="img"
61
- >
62
- <path d={cfg.path} />
63
- </svg>
64
- );
65
- }
@@ -1 +0,0 @@
1
- export { ToolIcon } from './ToolIcon';
@@ -1,4 +0,0 @@
1
- declare module '*.module.css' {
2
- const classes: { readonly [key: string]: string };
3
- export default classes;
4
- }
@@ -1,152 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef } from 'react';
2
- import { useApi } from '@backstage/core-plugin-api';
3
- import { devAiHubApiRef } from '../api/DevAiHubClient';
4
- import type {
5
- AiAsset,
6
- AiAssetListResponse,
7
- AiHubProvider,
8
- AiHubStats,
9
- AssetListFilter,
10
- } from '@nospt/plugin-dev-ai-hub-common';
11
-
12
- function useDebounce<T>(value: T, delay: number): T {
13
- const [debounced, setDebounced] = useState(value);
14
- useEffect(() => {
15
- const timer = setTimeout(() => setDebounced(value), delay);
16
- return () => clearTimeout(timer);
17
- }, [value, delay]);
18
- return debounced;
19
- }
20
-
21
- export function useAssets(filter: AssetListFilter) {
22
- const api = useApi(devAiHubApiRef);
23
- const [result, setResult] = useState<AiAssetListResponse | null>(null);
24
- const [loading, setLoading] = useState(false);
25
- const [error, setError] = useState<Error | null>(null);
26
-
27
- const debouncedSearch = useDebounce(filter.search, 300);
28
-
29
- const effectiveFilter = { ...filter, search: debouncedSearch };
30
- const filterKey = JSON.stringify(effectiveFilter);
31
-
32
- useEffect(() => {
33
- let cancelled = false;
34
- setLoading(true);
35
- api
36
- .listAssets(effectiveFilter)
37
- .then(data => {
38
- if (!cancelled) {
39
- setResult(data);
40
- setError(null);
41
- }
42
- })
43
- .catch(err => {
44
- if (!cancelled) setError(err);
45
- })
46
- .finally(() => {
47
- if (!cancelled) setLoading(false);
48
- });
49
- return () => {
50
- cancelled = true;
51
- };
52
- // eslint-disable-next-line react-hooks/exhaustive-deps
53
- }, [filterKey, api]);
54
-
55
- return { result, loading, error };
56
- }
57
-
58
- export function useAssetDetail(id: string | null) {
59
- const api = useApi(devAiHubApiRef);
60
- const [asset, setAsset] = useState<AiAsset | null>(null);
61
- const [loading, setLoading] = useState(false);
62
- const [error, setError] = useState<Error | null>(null);
63
-
64
- useEffect(() => {
65
- if (!id) {
66
- setAsset(null);
67
- return () => {};
68
- }
69
- let cancelled = false;
70
- setLoading(true);
71
- api
72
- .getAsset(id)
73
- .then(data => {
74
- if (!cancelled) {
75
- setAsset(data);
76
- setError(null);
77
- }
78
- })
79
- .catch(err => {
80
- if (!cancelled) setError(err);
81
- })
82
- .finally(() => {
83
- if (!cancelled) setLoading(false);
84
- });
85
- return () => {
86
- cancelled = true;
87
- };
88
- }, [id, api]);
89
-
90
- return { asset, loading, error };
91
- }
92
-
93
- export function useProviders() {
94
- const api = useApi(devAiHubApiRef);
95
- const [providers, setProviders] = useState<AiHubProvider[]>([]);
96
- const [loading, setLoading] = useState(false);
97
- const [error, setError] = useState<Error | null>(null);
98
-
99
- const load = useCallback(() => {
100
- setLoading(true);
101
- api
102
- .listProviders()
103
- .then(data => {
104
- setProviders(data);
105
- setError(null);
106
- })
107
- .catch(err => setError(err))
108
- .finally(() => setLoading(false));
109
- }, [api]);
110
-
111
- useEffect(() => {
112
- load();
113
- }, [load]);
114
-
115
- return { providers, loading, error, reload: load };
116
- }
117
-
118
- export function useStats() {
119
- const api = useApi(devAiHubApiRef);
120
- const [stats, setStats] = useState<AiHubStats | null>(null);
121
- const [loading, setLoading] = useState(false);
122
- const [error, setError] = useState<Error | null>(null);
123
-
124
- useEffect(() => {
125
- setLoading(true);
126
- api
127
- .getStats()
128
- .then(data => {
129
- setStats(data);
130
- setError(null);
131
- })
132
- .catch(err => setError(err))
133
- .finally(() => setLoading(false));
134
- }, [api]);
135
-
136
- return { stats, loading, error };
137
- }
138
-
139
- export function useCopyToClipboard() {
140
- const [copied, setCopied] = useState(false);
141
- const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
142
-
143
- const copy = useCallback((text: string) => {
144
- navigator.clipboard.writeText(text).then(() => {
145
- setCopied(true);
146
- if (timerRef.current) clearTimeout(timerRef.current);
147
- timerRef.current = setTimeout(() => setCopied(false), 2000);
148
- });
149
- }, []);
150
-
151
- return { copy, copied };
152
- }
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- // New Frontend System (NFS)
2
- export { devAiHubPlugin } from './plugin';
3
-
4
- // Legacy frontend system
5
- export { DevAiHubPage } from './pluginLegacy';
6
-
7
- // Shared
8
- export { devAiHubApiRef } from './api/DevAiHubClient';
9
- export type { DevAiHubApi } from './api/DevAiHubClient';
10
- export { rootRouteRef, mcpConfigRouteRef } from './routes';
package/src/plugin.tsx DELETED
@@ -1,60 +0,0 @@
1
- import {
2
- createFrontendPlugin,
3
- PageBlueprint,
4
- SubPageBlueprint,
5
- ApiBlueprint,
6
- createApiFactory,
7
- discoveryApiRef,
8
- fetchApiRef,
9
- } from '@backstage/frontend-plugin-api';
10
- import { RiRobot3Fill } from '@remixicon/react';
11
- import { devAiHubApiRef, DevAiHubClient } from './api/DevAiHubClient';
12
- import { rootRouteRef, mcpConfigRouteRef } from './routes';
13
-
14
- export const devAiHubPlugin = createFrontendPlugin({
15
- pluginId: 'dev-ai-hub',
16
- routes: {
17
- root: rootRouteRef,
18
- mcpConfig: mcpConfigRouteRef,
19
- },
20
- extensions: [
21
- ApiBlueprint.make({
22
- params: defineParams =>
23
- defineParams(
24
- createApiFactory({
25
- api: devAiHubApiRef,
26
- deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
27
- factory: ({ discoveryApi, fetchApi }) =>
28
- new DevAiHubClient(discoveryApi, fetchApi),
29
- }),
30
- ),
31
- }),
32
- PageBlueprint.make({
33
- params: {
34
- path: '/dev-ai-hub',
35
- title: 'Dev AI Hub',
36
- icon: <RiRobot3Fill />,
37
- routeRef: rootRouteRef,
38
- },
39
- }),
40
- SubPageBlueprint.make({
41
- name: 'browse',
42
- params: {
43
- path: 'browse',
44
- title: 'Browse',
45
- loader: () =>
46
- import('./components/DevAiHubPage').then(m => <m.DevAiHubPage />),
47
- },
48
- }),
49
- SubPageBlueprint.make({
50
- name: 'mcp-config',
51
- params: {
52
- path: 'mcp-config',
53
- title: 'Configure MCP',
54
- routeRef: mcpConfigRouteRef,
55
- loader: () =>
56
- import('./components/McpConfigPage').then(m => <m.McpConfigPage />),
57
- },
58
- }),
59
- ],
60
- });