@nospt/plugin-dev-ai-hub 0.1.0 → 0.2.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/dev/index.tsx +17 -217
- package/dist/api/DevAiHubClient.esm.js +2 -0
- package/dist/api/DevAiHubClient.esm.js.map +1 -1
- package/dist/components/AssetCard/AssetCard.esm.js +60 -134
- package/dist/components/AssetCard/AssetCard.esm.js.map +1 -1
- package/dist/components/AssetCard/AssetCard.module.css.esm.js +8 -0
- package/dist/components/AssetCard/AssetCard.module.css.esm.js.map +1 -0
- package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +175 -281
- package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +1 -1
- package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js +8 -0
- package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js.map +1 -0
- package/dist/components/AssetFilters/AssetFilters.esm.js +94 -134
- package/dist/components/AssetFilters/AssetFilters.esm.js.map +1 -1
- package/dist/components/AssetFilters/AssetFilters.module.css.esm.js +8 -0
- package/dist/components/AssetFilters/AssetFilters.module.css.esm.js.map +1 -0
- package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +70 -126
- package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +1 -1
- package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js +8 -0
- package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js.map +1 -0
- package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +64 -189
- package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +1 -1
- package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js +8 -0
- package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js.map +1 -0
- package/dist/components/McpConfigPage/McpConfigPage.esm.js +200 -0
- package/dist/components/McpConfigPage/McpConfigPage.esm.js.map +1 -0
- package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js +8 -0
- package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js.map +1 -0
- package/dist/components/McpConfigPage/index.esm.js +6 -0
- package/dist/components/McpConfigPage/index.esm.js.map +1 -0
- package/dist/components/ToolIcon/ToolIcon.esm.js +25 -13
- package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.esm.js +1 -1
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
- package/dist/plugin.esm.js +22 -4
- package/dist/plugin.esm.js.map +1 -1
- package/dist/routes.esm.js +2 -1
- package/dist/routes.esm.js.map +1 -1
- package/package.json +24 -14
- package/package.json-prepack +24 -14
- package/src/components/AssetCard/AssetCard.module.css +153 -0
- package/src/components/AssetCard/AssetCard.tsx +79 -148
- package/src/components/AssetDetailPanel/AssetDetailPanel.module.css +207 -0
- package/src/components/AssetDetailPanel/AssetDetailPanel.tsx +228 -292
- package/src/components/AssetFilters/AssetFilters.module.css +67 -0
- package/src/components/AssetFilters/AssetFilters.tsx +136 -178
- package/src/components/AssetInstallDialog/AssetInstallDialog.module.css +72 -0
- package/src/components/AssetInstallDialog/AssetInstallDialog.tsx +95 -135
- package/src/components/DevAiHubPage/DevAiHubPage.module.css +105 -0
- package/src/components/DevAiHubPage/DevAiHubPage.tsx +73 -222
- package/src/components/McpConfigDialog/McpConfigDialog.module.css +111 -0
- package/src/components/McpConfigDialog/McpConfigDialog.tsx +178 -208
- package/src/components/McpConfigPage/McpConfigPage.module.css +121 -0
- package/src/components/McpConfigPage/McpConfigPage.tsx +242 -0
- package/src/components/McpConfigPage/index.ts +1 -0
- package/src/components/ToolIcon/ToolIcon.tsx +31 -15
- package/src/css-modules.d.ts +4 -0
- package/src/index.ts +1 -1
- package/src/plugin.tsx +21 -1
- package/src/routes.ts +1 -0
- package/tests/e2e/.auth.json +14 -0
- package/tests/e2e/asset-detail.spec.ts +90 -0
- package/tests/e2e/asset-filtering.spec.ts +145 -0
- package/tests/e2e/asset-installation.spec.ts +74 -0
- package/tests/e2e/dev-ai-hub-page.spec.ts +89 -0
- package/tests/e2e/fixtures/base.ts +61 -0
- package/tests/e2e/fixtures/mock-api.ts +221 -0
- package/tests/e2e/mcp-config.spec.ts +88 -0
- package/dist/components/McpConfigDialog/McpConfigDialog.esm.js +0 -274
- package/dist/components/McpConfigDialog/McpConfigDialog.esm.js.map +0 -1
|
@@ -1,47 +1,15 @@
|
|
|
1
|
-
import { useState, useMemo } from 'react';
|
|
1
|
+
import { useState, useMemo, type ElementType } from 'react';
|
|
2
2
|
import { useSearchParams } from 'react-router-dom';
|
|
3
|
-
import Box from '@
|
|
4
|
-
import
|
|
5
|
-
import Grid from '@mui/material/Grid';
|
|
6
|
-
import Pagination from '@mui/material/Pagination';
|
|
7
|
-
import Skeleton from '@mui/material/Skeleton';
|
|
8
|
-
import Tooltip from '@mui/material/Tooltip';
|
|
9
|
-
import Typography from '@mui/material/Typography';
|
|
10
|
-
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
|
|
11
|
-
import ArticleIcon from '@mui/icons-material/Article';
|
|
12
|
-
import SmartToyIcon from '@mui/icons-material/SmartToy';
|
|
13
|
-
import BuildIcon from '@mui/icons-material/Build';
|
|
14
|
-
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
|
15
|
-
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
|
|
16
|
-
import HubIcon from '@mui/icons-material/Hub';
|
|
17
|
-
import { Content, Header, Page } from '@backstage/core-components';
|
|
3
|
+
import { Box, Flex, Text, Skeleton, TablePagination } from '@backstage/ui';
|
|
4
|
+
import { RiArticleLine, RiRobot2Line, RiToolsLine, RiGitBranchLine } from '@remixicon/react';
|
|
18
5
|
import type { AssetType, AiTool } from '@nospt/plugin-dev-ai-hub-common';
|
|
19
6
|
import { AssetCard } from '../AssetCard';
|
|
20
7
|
import { AssetFilters } from '../AssetFilters';
|
|
21
8
|
import type { AssetFiltersValue } from '../AssetFilters';
|
|
22
9
|
import { AssetDetailPanel } from '../AssetDetailPanel';
|
|
23
10
|
import { AssetInstallDialog } from '../AssetInstallDialog';
|
|
24
|
-
import { McpConfigDialog } from '../McpConfigDialog';
|
|
25
|
-
import { ToolIcon } from '../ToolIcon';
|
|
26
11
|
import { useAssets, useStats, useProviders } from '../../hooks';
|
|
27
|
-
|
|
28
|
-
const SUPPORTED_TOOLS: AiTool[] = ['claude-code', 'github-copilot', 'google-gemini', 'cursor'];
|
|
29
|
-
|
|
30
|
-
const TOOL_LABELS: Record<AiTool, string> = {
|
|
31
|
-
'all': 'Universal',
|
|
32
|
-
'claude-code': 'Claude Code',
|
|
33
|
-
'github-copilot': 'GitHub Copilot',
|
|
34
|
-
'google-gemini': 'Google Gemini',
|
|
35
|
-
'cursor': 'Cursor',
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
function timeAgo(iso: string): string {
|
|
39
|
-
const diff = Math.floor((Date.now() - new Date(iso).getTime()) / 1000);
|
|
40
|
-
if (diff < 60) return 'just now';
|
|
41
|
-
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
42
|
-
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
|
43
|
-
return `${Math.floor(diff / 86400)}d ago`;
|
|
44
|
-
}
|
|
12
|
+
import styles from './DevAiHubPage.module.css';
|
|
45
13
|
|
|
46
14
|
const DEFAULT_FILTERS: AssetFiltersValue = {
|
|
47
15
|
types: [],
|
|
@@ -53,21 +21,20 @@ const DEFAULT_FILTERS: AssetFiltersValue = {
|
|
|
53
21
|
|
|
54
22
|
const PAGE_SIZE = 24;
|
|
55
23
|
|
|
56
|
-
const STATS_CONFIG = [
|
|
57
|
-
{ key: 'instruction'
|
|
58
|
-
gradient: 'linear-gradient(135deg, #
|
|
59
|
-
{ key: 'agent'
|
|
60
|
-
gradient: 'linear-gradient(135deg, #
|
|
61
|
-
{ key: 'skill'
|
|
62
|
-
gradient: 'linear-gradient(135deg, #
|
|
63
|
-
{ key: 'workflow'
|
|
64
|
-
gradient: 'linear-gradient(135deg, #
|
|
24
|
+
const STATS_CONFIG: { key: AssetType; label: string; Icon: ElementType; gradient: string; shadow: string }[] = [
|
|
25
|
+
{ key: 'instruction', label: 'Instructions', Icon: RiArticleLine,
|
|
26
|
+
gradient: 'linear-gradient(135deg, #54A0FF 0%, #2980FF 100%)', shadow: '#54A0FF40' },
|
|
27
|
+
{ key: 'agent', label: 'Agents', Icon: RiRobot2Line,
|
|
28
|
+
gradient: 'linear-gradient(135deg, #FF6B9D 0%, #E0507A 100%)', shadow: '#FF6B9D40' },
|
|
29
|
+
{ key: 'skill', label: 'Skills', Icon: RiToolsLine,
|
|
30
|
+
gradient: 'linear-gradient(135deg, #6AB04C 0%, #4A8F2E 100%)', shadow: '#6AB04C40' },
|
|
31
|
+
{ key: 'workflow', label: 'Workflows', Icon: RiGitBranchLine,
|
|
32
|
+
gradient: 'linear-gradient(135deg, #F9CA24 0%, #D4A800 100%)', shadow: '#F9CA2440' },
|
|
65
33
|
];
|
|
66
34
|
|
|
67
35
|
export function DevAiHubPage() {
|
|
68
36
|
const [filters, setFilters] = useState<AssetFiltersValue>(DEFAULT_FILTERS);
|
|
69
37
|
const [page, setPage] = useState(1);
|
|
70
|
-
const [mcpDialogOpen, setMcpDialogOpen] = useState(false);
|
|
71
38
|
|
|
72
39
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
73
40
|
const selectedAssetId = searchParams.get('assetId');
|
|
@@ -118,155 +85,45 @@ export function DevAiHubPage() {
|
|
|
118
85
|
}, [result]);
|
|
119
86
|
|
|
120
87
|
return (
|
|
121
|
-
<
|
|
122
|
-
<
|
|
123
|
-
title={
|
|
124
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
125
|
-
<Box
|
|
126
|
-
sx={{
|
|
127
|
-
display: 'flex',
|
|
128
|
-
alignItems: 'center',
|
|
129
|
-
justifyContent: 'center',
|
|
130
|
-
width: 44,
|
|
131
|
-
height: 44,
|
|
132
|
-
borderRadius: 2.5,
|
|
133
|
-
background: 'rgba(255,255,255,0.15)',
|
|
134
|
-
backdropFilter: 'blur(8px)',
|
|
135
|
-
border: '1px solid rgba(255,255,255,0.3)',
|
|
136
|
-
boxShadow: '0 2px 12px rgba(0,0,0,0.15)',
|
|
137
|
-
flexShrink: 0,
|
|
138
|
-
}}
|
|
139
|
-
>
|
|
140
|
-
<HubIcon sx={{ fontSize: '1.5rem', color: '#fff' }} />
|
|
141
|
-
</Box>
|
|
142
|
-
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.3 }}>
|
|
143
|
-
<Box sx={{ fontSize: '1.75rem', fontWeight: 700, lineHeight: 1, color: '#fff' }}>
|
|
144
|
-
Dev AI Hub
|
|
145
|
-
</Box>
|
|
146
|
-
<Box sx={{ fontSize: '0.85rem', fontWeight: 400, color: 'rgba(255,255,255,0.75)', lineHeight: 1 }}>
|
|
147
|
-
{stats
|
|
148
|
-
? `${stats.totalAssets} assets · ${Object.keys(stats.byProvider).length} provider${Object.keys(stats.byProvider).length !== 1 ? 's' : ''}`
|
|
149
|
-
: 'Centralized AI assets for your team'}
|
|
150
|
-
</Box>
|
|
151
|
-
</Box>
|
|
152
|
-
</Box>
|
|
153
|
-
}
|
|
154
|
-
pageTitleOverride="Dev AI Hub"
|
|
155
|
-
>
|
|
156
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
157
|
-
{/* Supported tools */}
|
|
158
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
|
|
159
|
-
{SUPPORTED_TOOLS.map(tool => (
|
|
160
|
-
<Tooltip key={tool} title={TOOL_LABELS[tool]} arrow>
|
|
161
|
-
<Box
|
|
162
|
-
sx={{
|
|
163
|
-
width: 32,
|
|
164
|
-
height: 32,
|
|
165
|
-
borderRadius: '50%',
|
|
166
|
-
backgroundColor: 'rgba(255,255,255,0.15)',
|
|
167
|
-
display: 'flex',
|
|
168
|
-
alignItems: 'center',
|
|
169
|
-
justifyContent: 'center',
|
|
170
|
-
backdropFilter: 'blur(4px)',
|
|
171
|
-
transition: 'background-color 0.15s',
|
|
172
|
-
'&:hover': { backgroundColor: 'rgba(255,255,255,0.25)' },
|
|
173
|
-
}}
|
|
174
|
-
>
|
|
175
|
-
<ToolIcon tool={tool} branded={false} sx={{ fontSize: '1rem', color: '#fff' }} />
|
|
176
|
-
</Box>
|
|
177
|
-
</Tooltip>
|
|
178
|
-
))}
|
|
179
|
-
</Box>
|
|
180
|
-
|
|
181
|
-
{/* Last sync status */}
|
|
182
|
-
{stats?.lastSync && (
|
|
183
|
-
<Tooltip title={`Last sync: ${new Date(stats.lastSync).toLocaleString()}`} arrow>
|
|
184
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, cursor: 'default' }}>
|
|
185
|
-
<FiberManualRecordIcon sx={{ fontSize: '0.6rem', color: '#4ade80' }} />
|
|
186
|
-
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.8)', whiteSpace: 'nowrap' }}>
|
|
187
|
-
{timeAgo(stats.lastSync)}
|
|
188
|
-
</Typography>
|
|
189
|
-
</Box>
|
|
190
|
-
</Tooltip>
|
|
191
|
-
)}
|
|
192
|
-
|
|
193
|
-
{/* Configure MCP button */}
|
|
194
|
-
<Button
|
|
195
|
-
variant="contained"
|
|
196
|
-
size="small"
|
|
197
|
-
startIcon={<SettingsEthernetIcon />}
|
|
198
|
-
onClick={() => setMcpDialogOpen(true)}
|
|
199
|
-
sx={{
|
|
200
|
-
borderRadius: 2,
|
|
201
|
-
fontWeight: 700,
|
|
202
|
-
whiteSpace: 'nowrap',
|
|
203
|
-
background: 'rgba(255,255,255,0.15)',
|
|
204
|
-
backdropFilter: 'blur(4px)',
|
|
205
|
-
border: '1px solid rgba(255,255,255,0.3)',
|
|
206
|
-
color: '#fff',
|
|
207
|
-
boxShadow: 'none',
|
|
208
|
-
'&:hover': {
|
|
209
|
-
background: 'rgba(255,255,255,0.25)',
|
|
210
|
-
boxShadow: 'none',
|
|
211
|
-
},
|
|
212
|
-
}}
|
|
213
|
-
>
|
|
214
|
-
Configure MCP
|
|
215
|
-
</Button>
|
|
216
|
-
</Box>
|
|
217
|
-
</Header>
|
|
218
|
-
|
|
219
|
-
<Content>
|
|
220
|
-
|
|
88
|
+
<div className={styles.pageRoot}>
|
|
89
|
+
<div className={styles.content}>
|
|
221
90
|
{/* Stats row */}
|
|
222
|
-
<
|
|
223
|
-
{STATS_CONFIG.map(({ key, label, Icon, gradient, shadow }) =>
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
91
|
+
<div className={styles.statsGrid}>
|
|
92
|
+
{STATS_CONFIG.map(({ key, label, Icon, gradient, shadow }) => {
|
|
93
|
+
const isActive = filters.types[0] === key;
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
key={key}
|
|
97
|
+
className={styles.statCard}
|
|
98
|
+
onClick={() => handleFiltersChange({ ...filters, types: isActive ? [] : [key] })}
|
|
99
|
+
style={{
|
|
228
100
|
background: gradient,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
boxShadow: filters.types[0] === key ? `0 8px 24px ${shadow}` : `0 2px 8px ${shadow}`,
|
|
233
|
-
transform: filters.types[0] === key ? 'translateY(-2px)' : 'none',
|
|
234
|
-
transition: 'all 0.2s ease',
|
|
235
|
-
outline: filters.types[0] === key ? '2px solid rgba(255,255,255,0.6)' : 'none',
|
|
101
|
+
boxShadow: isActive ? `0 8px 24px ${shadow}` : `0 2px 8px ${shadow}`,
|
|
102
|
+
transform: isActive ? 'translateY(-2px)' : 'none',
|
|
103
|
+
outline: isActive ? '2px solid rgba(255,255,255,0.6)' : 'none',
|
|
236
104
|
outlineOffset: 2,
|
|
237
|
-
'&:hover': {
|
|
238
|
-
boxShadow: `0 8px 24px ${shadow}`,
|
|
239
|
-
transform: 'translateY(-2px)',
|
|
240
|
-
},
|
|
241
105
|
}}
|
|
106
|
+
role="button"
|
|
107
|
+
tabIndex={0}
|
|
108
|
+
onKeyDown={e => { if (e.key === 'Enter') handleFiltersChange({ ...filters, types: isActive ? [] : [key] }); }}
|
|
242
109
|
>
|
|
243
|
-
<
|
|
110
|
+
<div className={styles.statCardInner}>
|
|
244
111
|
<Box>
|
|
245
|
-
<
|
|
246
|
-
{stats ? (stats.byType[key] ?? 0) : <Skeleton
|
|
247
|
-
</
|
|
248
|
-
<
|
|
112
|
+
<Text variant="title-large" weight="bold" className={styles.statValue}>
|
|
113
|
+
{stats ? (stats.byType[key] ?? 0) : <Skeleton style={{ width: 32 }} />}
|
|
114
|
+
</Text>
|
|
115
|
+
<Text variant="body-small" className={styles.statLabel}>
|
|
249
116
|
{label}
|
|
250
|
-
</
|
|
117
|
+
</Text>
|
|
251
118
|
</Box>
|
|
252
|
-
<
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
justifyContent: 'center',
|
|
261
|
-
}}
|
|
262
|
-
>
|
|
263
|
-
<Icon sx={{ color: '#fff', fontSize: '1.4rem' }} />
|
|
264
|
-
</Box>
|
|
265
|
-
</Box>
|
|
266
|
-
</Box>
|
|
267
|
-
</Grid>
|
|
268
|
-
))}
|
|
269
|
-
</Grid>
|
|
119
|
+
<div className={styles.statIconBox}>
|
|
120
|
+
<Icon size={22} style={{ color: '#fff' }} />
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
})}
|
|
126
|
+
</div>
|
|
270
127
|
|
|
271
128
|
{/* Filters */}
|
|
272
129
|
<AssetFilters
|
|
@@ -278,54 +135,53 @@ export function DevAiHubPage() {
|
|
|
278
135
|
|
|
279
136
|
{/* Results summary */}
|
|
280
137
|
{result && !loading && (
|
|
281
|
-
<
|
|
138
|
+
<Text variant="body-x-small" color="secondary" style={{ marginBottom: 'var(--bui-space-4)', display: 'block' }}>
|
|
282
139
|
{result.totalCount} asset{result.totalCount !== 1 ? 's' : ''} found
|
|
283
|
-
</
|
|
140
|
+
</Text>
|
|
284
141
|
)}
|
|
285
142
|
|
|
286
143
|
{/* Asset grid — 4 columns on large screens */}
|
|
287
|
-
<
|
|
144
|
+
<div className={styles.assetGrid}>
|
|
288
145
|
{loading
|
|
289
146
|
? Array.from({ length: 8 }).map((_, i) => (
|
|
290
|
-
<
|
|
291
|
-
<Skeleton variant="rectangular" height={150} sx={{ borderRadius: 2 }} />
|
|
292
|
-
</Grid>
|
|
147
|
+
<Skeleton key={i} style={{ height: 150, borderRadius: 'var(--bui-radius-2)' }} />
|
|
293
148
|
))
|
|
294
149
|
: result?.items.map(asset => (
|
|
295
|
-
<
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
</Grid>
|
|
150
|
+
<AssetCard
|
|
151
|
+
key={asset.id}
|
|
152
|
+
asset={asset}
|
|
153
|
+
onView={handleViewAsset}
|
|
154
|
+
onInstall={handleInstallAsset}
|
|
155
|
+
/>
|
|
302
156
|
))}
|
|
303
|
-
</
|
|
157
|
+
</div>
|
|
304
158
|
|
|
305
159
|
{/* Empty state */}
|
|
306
160
|
{!loading && result?.items.length === 0 && (
|
|
307
|
-
<
|
|
308
|
-
<
|
|
309
|
-
<
|
|
310
|
-
<
|
|
161
|
+
<div className={styles.emptyState}>
|
|
162
|
+
<div className={styles.emptyEmoji}>🤖</div>
|
|
163
|
+
<Text variant="title-small" color="secondary" weight="bold">No assets found</Text>
|
|
164
|
+
<Text variant="body-small" color="secondary" style={{ marginTop: 'var(--bui-space-1)' }}>
|
|
311
165
|
Try adjusting your filters or search terms.
|
|
312
|
-
</
|
|
313
|
-
</
|
|
166
|
+
</Text>
|
|
167
|
+
</div>
|
|
314
168
|
)}
|
|
315
169
|
|
|
316
170
|
{/* Pagination */}
|
|
317
171
|
{totalPages > 1 && (
|
|
318
|
-
<
|
|
319
|
-
<
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
172
|
+
<Flex className={styles.paginationRow}>
|
|
173
|
+
<TablePagination
|
|
174
|
+
pageSize={PAGE_SIZE}
|
|
175
|
+
offset={(page - 1) * PAGE_SIZE}
|
|
176
|
+
totalCount={result?.totalCount ?? 0}
|
|
177
|
+
hasNextPage={page < totalPages}
|
|
178
|
+
hasPreviousPage={page > 1}
|
|
179
|
+
onNextPage={() => setPage(p => p + 1)}
|
|
180
|
+
onPreviousPage={() => setPage(p => p - 1)}
|
|
325
181
|
/>
|
|
326
|
-
</
|
|
182
|
+
</Flex>
|
|
327
183
|
)}
|
|
328
|
-
</
|
|
184
|
+
</div>
|
|
329
185
|
|
|
330
186
|
<AssetDetailPanel
|
|
331
187
|
assetId={selectedAssetId}
|
|
@@ -336,11 +192,6 @@ export function DevAiHubPage() {
|
|
|
336
192
|
assetId={installAssetId}
|
|
337
193
|
onClose={handleCloseInstall}
|
|
338
194
|
/>
|
|
339
|
-
|
|
340
|
-
<McpConfigDialog
|
|
341
|
-
open={mcpDialogOpen}
|
|
342
|
-
onClose={() => setMcpDialogOpen(false)}
|
|
343
|
-
/>
|
|
344
|
-
</Page>
|
|
195
|
+
</div>
|
|
345
196
|
);
|
|
346
197
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.subtitle {
|
|
3
|
+
margin-top: var(--bui-space-1);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.tabBar {
|
|
7
|
+
margin-bottom: var(--bui-space-4);
|
|
8
|
+
border-bottom: 1px solid var(--bui-border-1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.tabContent {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: var(--bui-space-2);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.sectionLabel {
|
|
18
|
+
text-transform: uppercase;
|
|
19
|
+
letter-spacing: 0.5px;
|
|
20
|
+
display: block;
|
|
21
|
+
margin-bottom: 6px;
|
|
22
|
+
font-weight: 600;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.providerSection {
|
|
26
|
+
margin-bottom: var(--bui-space-4);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.chipsRow {
|
|
30
|
+
display: flex;
|
|
31
|
+
gap: 6px;
|
|
32
|
+
flex-wrap: wrap;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.filterChip {
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
font-size: 0.75rem;
|
|
38
|
+
border-radius: var(--bui-radius-2);
|
|
39
|
+
border: 1.5px solid;
|
|
40
|
+
transition: all 0.15s ease;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.proactiveSection {
|
|
45
|
+
margin-bottom: var(--bui-space-4);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.proactiveRow {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: flex-start;
|
|
51
|
+
gap: var(--bui-space-2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.urlSection {
|
|
55
|
+
margin-bottom: var(--bui-space-4);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.urlBox {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
gap: var(--bui-space-2);
|
|
62
|
+
margin-top: var(--bui-space-1);
|
|
63
|
+
padding: var(--bui-space-2) var(--bui-space-3);
|
|
64
|
+
border-radius: var(--bui-radius-2);
|
|
65
|
+
border: 1px solid var(--bui-border-1);
|
|
66
|
+
background-color: var(--bui-bg-neutral-1-hover);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.urlText {
|
|
70
|
+
flex: 1;
|
|
71
|
+
font-family: monospace;
|
|
72
|
+
font-size: 0.8rem;
|
|
73
|
+
word-break: break-all;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.snippetContainer {
|
|
77
|
+
position: relative;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.snippetPre {
|
|
81
|
+
margin: 0;
|
|
82
|
+
padding: var(--bui-space-4);
|
|
83
|
+
border-radius: var(--bui-radius-2);
|
|
84
|
+
border: 1px solid var(--bui-border-1);
|
|
85
|
+
font-family: monospace;
|
|
86
|
+
font-size: 0.8rem;
|
|
87
|
+
overflow-x: auto;
|
|
88
|
+
white-space: pre;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.snippetPreLight {
|
|
92
|
+
background-color: #f6f8fa;
|
|
93
|
+
color: #24292f;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.snippetPreDark {
|
|
97
|
+
background-color: #1E1E1E;
|
|
98
|
+
color: #DCDDE1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.copySnippetButton {
|
|
102
|
+
position: absolute;
|
|
103
|
+
top: 8px;
|
|
104
|
+
right: 8px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.hint {
|
|
108
|
+
display: block;
|
|
109
|
+
margin-top: var(--bui-space-3);
|
|
110
|
+
}
|
|
111
|
+
}
|