@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.
- package/.eslintrc.js +1 -0
- package/dev/index.tsx +232 -0
- package/dist/api/DevAiHubClient.esm.js +76 -0
- package/dist/api/DevAiHubClient.esm.js.map +1 -0
- package/dist/components/AssetCard/AssetCard.esm.js +194 -0
- package/dist/components/AssetCard/AssetCard.esm.js.map +1 -0
- package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +324 -0
- package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +1 -0
- package/dist/components/AssetFilters/AssetFilters.esm.js +216 -0
- package/dist/components/AssetFilters/AssetFilters.esm.js.map +1 -0
- package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +194 -0
- package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +1 -0
- package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +320 -0
- package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +1 -0
- package/dist/components/DevAiHubPage/index.esm.js +6 -0
- package/dist/components/DevAiHubPage/index.esm.js.map +1 -0
- package/dist/components/McpConfigDialog/McpConfigDialog.esm.js +274 -0
- package/dist/components/McpConfigDialog/McpConfigDialog.esm.js.map +1 -0
- package/dist/components/ToolIcon/ToolIcon.esm.js +41 -0
- package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -0
- package/dist/hooks/index.esm.js +114 -0
- package/dist/hooks/index.esm.js.map +1 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.esm.js +5 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/plugin.esm.js +40 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/pluginLegacy.esm.js +27 -0
- package/dist/pluginLegacy.esm.js.map +1 -0
- package/dist/routes.esm.js +6 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +80 -0
- package/package.json-prepack +75 -0
- package/src/api/DevAiHubClient.ts +110 -0
- package/src/components/AssetCard/AssetCard.test.tsx +182 -0
- package/src/components/AssetCard/AssetCard.tsx +221 -0
- package/src/components/AssetCard/index.ts +1 -0
- package/src/components/AssetDetailPanel/AssetDetailPanel.tsx +377 -0
- package/src/components/AssetDetailPanel/index.ts +1 -0
- package/src/components/AssetFilters/AssetFilters.tsx +261 -0
- package/src/components/AssetFilters/index.ts +2 -0
- package/src/components/AssetInstallDialog/AssetInstallDialog.tsx +216 -0
- package/src/components/AssetInstallDialog/index.ts +1 -0
- package/src/components/DevAiHubPage/DevAiHubPage.tsx +346 -0
- package/src/components/DevAiHubPage/index.ts +1 -0
- package/src/components/McpConfigDialog/McpConfigDialog.tsx +301 -0
- package/src/components/McpConfigDialog/index.ts +1 -0
- package/src/components/ToolIcon/ToolIcon.tsx +49 -0
- package/src/components/ToolIcon/index.ts +1 -0
- package/src/hooks/index.ts +152 -0
- package/src/index.ts +10 -0
- package/src/plugin.tsx +47 -0
- package/src/pluginLegacy.ts +42 -0
- package/src/routes.ts +3 -0
- 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