@nospt/plugin-dev-ai-hub 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.eslintrc.js +1 -0
  2. package/dev/index.tsx +232 -0
  3. package/dist/api/DevAiHubClient.esm.js +76 -0
  4. package/dist/api/DevAiHubClient.esm.js.map +1 -0
  5. package/dist/components/AssetCard/AssetCard.esm.js +194 -0
  6. package/dist/components/AssetCard/AssetCard.esm.js.map +1 -0
  7. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +324 -0
  8. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +1 -0
  9. package/dist/components/AssetFilters/AssetFilters.esm.js +216 -0
  10. package/dist/components/AssetFilters/AssetFilters.esm.js.map +1 -0
  11. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +194 -0
  12. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +1 -0
  13. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +320 -0
  14. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +1 -0
  15. package/dist/components/DevAiHubPage/index.esm.js +6 -0
  16. package/dist/components/DevAiHubPage/index.esm.js.map +1 -0
  17. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js +274 -0
  18. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js.map +1 -0
  19. package/dist/components/ToolIcon/ToolIcon.esm.js +41 -0
  20. package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -0
  21. package/dist/hooks/index.esm.js +114 -0
  22. package/dist/hooks/index.esm.js.map +1 -0
  23. package/dist/index.d.ts +104 -0
  24. package/dist/index.esm.js +5 -0
  25. package/dist/index.esm.js.map +1 -0
  26. package/dist/plugin.esm.js +40 -0
  27. package/dist/plugin.esm.js.map +1 -0
  28. package/dist/pluginLegacy.esm.js +27 -0
  29. package/dist/pluginLegacy.esm.js.map +1 -0
  30. package/dist/routes.esm.js +6 -0
  31. package/dist/routes.esm.js.map +1 -0
  32. package/package.json +80 -0
  33. package/package.json-prepack +75 -0
  34. package/src/api/DevAiHubClient.ts +110 -0
  35. package/src/components/AssetCard/AssetCard.test.tsx +182 -0
  36. package/src/components/AssetCard/AssetCard.tsx +221 -0
  37. package/src/components/AssetCard/index.ts +1 -0
  38. package/src/components/AssetDetailPanel/AssetDetailPanel.tsx +377 -0
  39. package/src/components/AssetDetailPanel/index.ts +1 -0
  40. package/src/components/AssetFilters/AssetFilters.tsx +261 -0
  41. package/src/components/AssetFilters/index.ts +2 -0
  42. package/src/components/AssetInstallDialog/AssetInstallDialog.tsx +216 -0
  43. package/src/components/AssetInstallDialog/index.ts +1 -0
  44. package/src/components/DevAiHubPage/DevAiHubPage.tsx +346 -0
  45. package/src/components/DevAiHubPage/index.ts +1 -0
  46. package/src/components/McpConfigDialog/McpConfigDialog.tsx +301 -0
  47. package/src/components/McpConfigDialog/index.ts +1 -0
  48. package/src/components/ToolIcon/ToolIcon.tsx +49 -0
  49. package/src/components/ToolIcon/index.ts +1 -0
  50. package/src/hooks/index.ts +152 -0
  51. package/src/index.ts +10 -0
  52. package/src/plugin.tsx +47 -0
  53. package/src/pluginLegacy.ts +42 -0
  54. package/src/routes.ts +3 -0
  55. package/tsconfig.json +10 -0
@@ -0,0 +1,49 @@
1
+ import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
2
+ import AllInclusiveIcon from '@mui/icons-material/AllInclusive';
3
+ import NearMeIcon from '@mui/icons-material/NearMe';
4
+ import { siAnthropic, siGithub, siGooglegemini } from 'simple-icons';
5
+ import type { AiTool } from '@nospt/plugin-dev-ai-hub-common';
6
+
7
+ type SvgTool = Exclude<AiTool, 'all' | 'cursor'>;
8
+
9
+ const TOOL_ICON: Record<SvgTool, { path: string; hex: string; label: string }> = {
10
+ 'claude-code': { ...siAnthropic, label: 'Claude Code' },
11
+ 'github-copilot': { ...siGithub, label: 'GitHub Copilot' },
12
+ 'google-gemini': { ...siGooglegemini, label: 'Google Gemini' },
13
+ };
14
+
15
+ interface ToolIconProps extends Omit<SvgIconProps, 'color'> {
16
+ tool: AiTool;
17
+ /** Use the brand's official color. Defaults to true. */
18
+ branded?: boolean;
19
+ }
20
+
21
+ export function ToolIcon({ tool, branded = true, sx, ...props }: ToolIconProps) {
22
+ if (tool === 'all') {
23
+ return <AllInclusiveIcon {...props} sx={{ color: 'text.secondary', ...sx }} titleAccess="Universal" />;
24
+ }
25
+
26
+ if (tool === 'cursor') {
27
+ return (
28
+ <NearMeIcon
29
+ {...props}
30
+ sx={{ color: branded ? 'text.primary' : 'inherit', ...sx }}
31
+ titleAccess="Cursor"
32
+ />
33
+ );
34
+ }
35
+
36
+ const cfg = TOOL_ICON[tool as SvgTool];
37
+ if (!cfg) return null;
38
+
39
+ return (
40
+ <SvgIcon
41
+ {...props}
42
+ sx={{ color: branded ? `#${cfg.hex}` : 'inherit', ...sx }}
43
+ titleAccess={cfg.label}
44
+ viewBox="0 0 24 24"
45
+ >
46
+ <path d={cfg.path} />
47
+ </SvgIcon>
48
+ );
49
+ }
@@ -0,0 +1 @@
1
+ export { ToolIcon } from './ToolIcon';
@@ -0,0 +1,152 @@
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 ADDED
@@ -0,0 +1,10 @@
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 } from './routes';
package/src/plugin.tsx ADDED
@@ -0,0 +1,47 @@
1
+ import {
2
+ createFrontendPlugin,
3
+ PageBlueprint,
4
+ ApiBlueprint,
5
+ NavItemBlueprint,
6
+ createApiFactory,
7
+ discoveryApiRef,
8
+ fetchApiRef,
9
+ } from '@backstage/frontend-plugin-api';
10
+ import HubIcon from '@mui/icons-material/Hub';
11
+ import { devAiHubApiRef, DevAiHubClient } from './api/DevAiHubClient';
12
+ import { rootRouteRef } from './routes';
13
+
14
+ export const devAiHubPlugin = createFrontendPlugin({
15
+ pluginId: 'dev-ai-hub',
16
+ routes: {
17
+ root: rootRouteRef,
18
+ },
19
+ extensions: [
20
+ ApiBlueprint.make({
21
+ params: defineParams =>
22
+ defineParams(
23
+ createApiFactory({
24
+ api: devAiHubApiRef,
25
+ deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
26
+ factory: ({ discoveryApi, fetchApi }) =>
27
+ new DevAiHubClient(discoveryApi, fetchApi),
28
+ }),
29
+ ),
30
+ }),
31
+ PageBlueprint.make({
32
+ params: {
33
+ path: '/dev-ai-hub',
34
+ routeRef: rootRouteRef,
35
+ loader: () =>
36
+ import('./components/DevAiHubPage').then(m => <m.DevAiHubPage />),
37
+ },
38
+ }),
39
+ NavItemBlueprint.make({
40
+ params: {
41
+ title: 'AI Hub',
42
+ routeRef: rootRouteRef,
43
+ icon: HubIcon,
44
+ },
45
+ }),
46
+ ],
47
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Legacy Backstage frontend system support.
3
+ *
4
+ * For New Frontend System (NFS) use `devAiHubPlugin` from `./plugin` instead.
5
+ */
6
+ import type { ComponentType } from 'react';
7
+ import {
8
+ createPlugin,
9
+ createRoutableExtension,
10
+ createRouteRef,
11
+ createApiFactory,
12
+ discoveryApiRef,
13
+ fetchApiRef,
14
+ } from '@backstage/core-plugin-api';
15
+ import { devAiHubApiRef, DevAiHubClient } from './api/DevAiHubClient';
16
+
17
+ const rootRouteRef = createRouteRef({ id: 'dev-ai-hub' });
18
+
19
+ const devAiHubLegacyPlugin = createPlugin({
20
+ id: 'dev-ai-hub',
21
+ routes: {
22
+ root: rootRouteRef,
23
+ },
24
+ apis: [
25
+ createApiFactory({
26
+ api: devAiHubApiRef,
27
+ deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
28
+ factory: ({ discoveryApi, fetchApi }) =>
29
+ new DevAiHubClient(discoveryApi, fetchApi),
30
+ }),
31
+ ],
32
+ });
33
+
34
+ /** Routable page extension for the legacy frontend system. */
35
+ export const DevAiHubPage: ComponentType = devAiHubLegacyPlugin.provide(
36
+ createRoutableExtension({
37
+ name: 'DevAiHubPage',
38
+ component: () =>
39
+ import('./components/DevAiHubPage').then(m => m.DevAiHubPage),
40
+ mountPoint: rootRouteRef,
41
+ }),
42
+ );
package/src/routes.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { createRouteRef } from '@backstage/frontend-plugin-api';
2
+
3
+ export const rootRouteRef = createRouteRef();
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "lib": ["ES2020", "DOM"],
7
+ "jsx": "react-jsx"
8
+ },
9
+ "include": ["src"]
10
+ }