@nospt/plugin-dev-ai-hub 0.1.0 → 0.2.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 (71) hide show
  1. package/dev/index.tsx +17 -217
  2. package/dist/api/DevAiHubClient.esm.js +2 -0
  3. package/dist/api/DevAiHubClient.esm.js.map +1 -1
  4. package/dist/components/AssetCard/AssetCard.esm.js +60 -134
  5. package/dist/components/AssetCard/AssetCard.esm.js.map +1 -1
  6. package/dist/components/AssetCard/AssetCard.module.css.esm.js +8 -0
  7. package/dist/components/AssetCard/AssetCard.module.css.esm.js.map +1 -0
  8. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +175 -281
  9. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +1 -1
  10. package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js +8 -0
  11. package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js.map +1 -0
  12. package/dist/components/AssetFilters/AssetFilters.esm.js +94 -134
  13. package/dist/components/AssetFilters/AssetFilters.esm.js.map +1 -1
  14. package/dist/components/AssetFilters/AssetFilters.module.css.esm.js +8 -0
  15. package/dist/components/AssetFilters/AssetFilters.module.css.esm.js.map +1 -0
  16. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +70 -126
  17. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +1 -1
  18. package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js +8 -0
  19. package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js.map +1 -0
  20. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +64 -189
  21. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +1 -1
  22. package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js +8 -0
  23. package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js.map +1 -0
  24. package/dist/components/McpConfigPage/McpConfigPage.esm.js +200 -0
  25. package/dist/components/McpConfigPage/McpConfigPage.esm.js.map +1 -0
  26. package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js +8 -0
  27. package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js.map +1 -0
  28. package/dist/components/McpConfigPage/index.esm.js +6 -0
  29. package/dist/components/McpConfigPage/index.esm.js.map +1 -0
  30. package/dist/components/ToolIcon/ToolIcon.esm.js +25 -13
  31. package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -1
  32. package/dist/index.d.ts +10 -5
  33. package/dist/index.esm.js +1 -1
  34. package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
  35. package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
  36. package/dist/plugin.esm.js +24 -6
  37. package/dist/plugin.esm.js.map +1 -1
  38. package/dist/routes.esm.js +2 -1
  39. package/dist/routes.esm.js.map +1 -1
  40. package/package.json +24 -14
  41. package/package.json-prepack +24 -14
  42. package/src/components/AssetCard/AssetCard.module.css +153 -0
  43. package/src/components/AssetCard/AssetCard.tsx +79 -148
  44. package/src/components/AssetDetailPanel/AssetDetailPanel.module.css +207 -0
  45. package/src/components/AssetDetailPanel/AssetDetailPanel.tsx +228 -292
  46. package/src/components/AssetFilters/AssetFilters.module.css +67 -0
  47. package/src/components/AssetFilters/AssetFilters.tsx +136 -178
  48. package/src/components/AssetInstallDialog/AssetInstallDialog.module.css +72 -0
  49. package/src/components/AssetInstallDialog/AssetInstallDialog.tsx +95 -135
  50. package/src/components/DevAiHubPage/DevAiHubPage.module.css +105 -0
  51. package/src/components/DevAiHubPage/DevAiHubPage.tsx +73 -222
  52. package/src/components/McpConfigDialog/McpConfigDialog.module.css +111 -0
  53. package/src/components/McpConfigDialog/McpConfigDialog.tsx +178 -208
  54. package/src/components/McpConfigPage/McpConfigPage.module.css +121 -0
  55. package/src/components/McpConfigPage/McpConfigPage.tsx +242 -0
  56. package/src/components/McpConfigPage/index.ts +1 -0
  57. package/src/components/ToolIcon/ToolIcon.tsx +31 -15
  58. package/src/css-modules.d.ts +4 -0
  59. package/src/index.ts +1 -1
  60. package/src/plugin.tsx +23 -3
  61. package/src/routes.ts +1 -0
  62. package/tests/e2e/.auth.json +14 -0
  63. package/tests/e2e/asset-detail.spec.ts +90 -0
  64. package/tests/e2e/asset-filtering.spec.ts +145 -0
  65. package/tests/e2e/asset-installation.spec.ts +74 -0
  66. package/tests/e2e/dev-ai-hub-page.spec.ts +89 -0
  67. package/tests/e2e/fixtures/base.ts +61 -0
  68. package/tests/e2e/fixtures/mock-api.ts +221 -0
  69. package/tests/e2e/mcp-config.spec.ts +88 -0
  70. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js +0 -274
  71. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js.map +0 -1
@@ -1,26 +1,16 @@
1
1
  import { useEffect, useState } from 'react';
2
- import Box from '@mui/material/Box';
3
- import Button from '@mui/material/Button';
4
- import Chip from '@mui/material/Chip';
5
- import Dialog from '@mui/material/Dialog';
6
- import DialogActions from '@mui/material/DialogActions';
7
- import DialogContent from '@mui/material/DialogContent';
8
- import DialogTitle from '@mui/material/DialogTitle';
9
- import FormControlLabel from '@mui/material/FormControlLabel';
10
- import IconButton from '@mui/material/IconButton';
11
- import Switch from '@mui/material/Switch';
12
- import Tab from '@mui/material/Tab';
13
- import Tabs from '@mui/material/Tabs';
14
- import Tooltip from '@mui/material/Tooltip';
15
- import Typography from '@mui/material/Typography';
16
- import { useTheme } from '@mui/material/styles';
17
- import ContentCopyIcon from '@mui/icons-material/ContentCopy';
18
- import CheckIcon from '@mui/icons-material/Check';
19
- import StorageIcon from '@mui/icons-material/Storage';
2
+ import {
3
+ Box, Flex, Text, Button, ButtonIcon, ToggleButton, Switch,
4
+ Dialog, DialogTrigger, DialogHeader, DialogBody, DialogFooter,
5
+ Tabs, TabList, Tab, TabPanel,
6
+ Tooltip, TooltipTrigger,
7
+ } from '@backstage/ui';
8
+ import { RiFileCopyLine, RiCheckLine, RiDatabase2Line } from '@remixicon/react';
20
9
  import { useApi, discoveryApiRef } from '@backstage/core-plugin-api';
21
10
  import { ToolIcon } from '../ToolIcon';
22
11
  import { useCopyToClipboard, useProviders } from '../../hooks';
23
12
  import type { AiTool } from '@nospt/plugin-dev-ai-hub-common';
13
+ import styles from './McpConfigDialog.module.css';
24
14
 
25
15
  interface ToolConfig {
26
16
  tool: AiTool;
@@ -70,11 +60,10 @@ interface McpConfigDialogProps {
70
60
  }
71
61
 
72
62
  export function McpConfigDialog({ open, onClose }: McpConfigDialogProps) {
73
- const theme = useTheme();
74
63
  const discoveryApi = useApi(discoveryApiRef);
75
64
  const { copy: copyUrl, copied: copiedUrl } = useCopyToClipboard();
76
65
  const { copy: copySnippet, copied: copiedSnippet } = useCopyToClipboard();
77
- const [tab, setTab] = useState(0);
66
+ const [selectedToolKey, setSelectedToolKey] = useState<string>('claude-code');
78
67
  const [baseUrl, setBaseUrl] = useState('');
79
68
  const [selectedProvider, setSelectedProvider] = useState<string>('');
80
69
  const [proactiveEnabled, setProactiveEnabled] = useState(false);
@@ -88,7 +77,7 @@ export function McpConfigDialog({ open, onClose }: McpConfigDialogProps) {
88
77
  }
89
78
  }, [open, discoveryApi]);
90
79
 
91
- const cfg = TOOL_CONFIGS[tab];
80
+ const cfg = TOOL_CONFIGS.find(t => t.tool === selectedToolKey) ?? TOOL_CONFIGS[0];
92
81
 
93
82
  const buildMcpUrl = () => {
94
83
  if (!baseUrl) return 'loading...';
@@ -102,200 +91,181 @@ export function McpConfigDialog({ open, onClose }: McpConfigDialogProps) {
102
91
  const mcpUrl = buildMcpUrl();
103
92
  const configSnippet = baseUrl ? cfg.buildConfig(mcpUrl) : '';
104
93
 
94
+ // Detect dark mode by observing BUI's data-theme-mode attribute, which
95
+ // reflects the Backstage theme toggle (not the OS preference).
96
+ const [isDark, setIsDark] = useState(() => {
97
+ if (typeof document === 'undefined') return false;
98
+ return document.documentElement.getAttribute('data-theme-mode') === 'dark' ||
99
+ document.body.getAttribute('data-theme-mode') === 'dark';
100
+ });
101
+
102
+ useEffect(() => {
103
+ const getIsDark = () =>
104
+ document.documentElement.getAttribute('data-theme-mode') === 'dark' ||
105
+ document.body.getAttribute('data-theme-mode') === 'dark';
106
+
107
+ const observer = new MutationObserver(() => setIsDark(getIsDark()));
108
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme-mode'] });
109
+ observer.observe(document.body, { attributes: true, attributeFilter: ['data-theme-mode'] });
110
+ return () => observer.disconnect();
111
+ }, []);
112
+
105
113
  return (
106
- <Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
107
- <DialogTitle sx={{ pb: 0 }}>
108
- <Typography variant="h6" fontWeight={700}>Configure MCP Server</Typography>
109
- <Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
110
- Connect your AI tool to the Dev AI Hub via Model Context Protocol.
111
- </Typography>
112
- </DialogTitle>
114
+ <DialogTrigger>
115
+ <Dialog
116
+ isOpen={open}
117
+ isDismissable
118
+ onOpenChange={o => { if (!o) onClose(); }}
119
+ >
120
+ <DialogHeader>
121
+ <Text variant="title-small" weight="bold">Configure MCP Server</Text>
122
+ <Text variant="body-small" color="secondary" className={styles.subtitle}>
123
+ Connect your AI tool to the Dev AI Hub via Model Context Protocol.
124
+ </Text>
125
+ </DialogHeader>
113
126
 
114
- <DialogContent sx={{ pt: 1 }}>
115
- <Tabs
116
- value={tab}
117
- onChange={(_, v) => setTab(v)}
118
- sx={{ mb: 2, borderBottom: 1, borderColor: 'divider' }}
119
- >
120
- {TOOL_CONFIGS.map((t, i) => (
121
- <Tab
122
- key={t.tool}
123
- value={i}
124
- label={
125
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
126
- <ToolIcon
127
- tool={t.tool}
128
- sx={{ fontSize: '1rem', color: theme.palette.mode === 'dark' ? '#fff' : undefined }}
129
- />
130
- <span>{t.label}</span>
131
- </Box>
132
- }
133
- />
134
- ))}
135
- </Tabs>
127
+ <DialogBody>
128
+ <Tabs selectedKey={selectedToolKey} onSelectionChange={key => setSelectedToolKey(key as string)}>
129
+ <TabList className={styles.tabBar}>
130
+ {TOOL_CONFIGS.map(t => (
131
+ <Tab key={t.tool} id={t.tool}>
132
+ <span className={styles.tabContent}>
133
+ <ToolIcon tool={t.tool} size={16} />
134
+ <span>{t.label}</span>
135
+ </span>
136
+ </Tab>
137
+ ))}
138
+ </TabList>
136
139
 
137
- {/* Provider filter — only shown when there are 2+ providers */}
138
- {showProviderFilter && (
139
- <Box sx={{ mb: 2 }}>
140
- <Typography variant="caption" fontWeight={600} color="text.secondary" sx={{ textTransform: 'uppercase', letterSpacing: 0.5, display: 'block', mb: 0.75 }}>
141
- Scope to Provider
142
- </Typography>
143
- <Box sx={{ display: 'flex', gap: 0.75, flexWrap: 'wrap' }}>
144
- <Chip
145
- label="All providers"
146
- size="small"
147
- clickable
148
- onClick={() => setSelectedProvider('')}
149
- sx={{
150
- fontWeight: 600,
151
- fontSize: '0.75rem',
152
- borderRadius: 2,
153
- border: '1.5px solid',
154
- borderColor: !selectedProvider ? 'text.primary' : 'divider',
155
- backgroundColor: !selectedProvider ? 'text.primary' : 'transparent',
156
- color: !selectedProvider ? 'background.paper' : 'text.secondary',
157
- transition: 'all 0.15s ease',
158
- }}
159
- />
160
- {providers.map(p => {
161
- const isSelected = selectedProvider === p.id;
162
- const label = providerLabel(p.target);
163
- return (
164
- <Chip
165
- key={p.id}
166
- icon={<StorageIcon sx={{ fontSize: '0.8rem !important', color: isSelected ? 'background.paper' : 'inherit' }} />}
167
- label={label}
168
- size="small"
169
- clickable
170
- onClick={() => setSelectedProvider(isSelected ? '' : p.id)}
171
- sx={{
172
- fontWeight: 600,
173
- fontSize: '0.75rem',
174
- borderRadius: 2,
175
- border: '1.5px solid',
176
- borderColor: isSelected ? 'text.primary' : 'divider',
177
- backgroundColor: isSelected ? 'text.primary' : 'transparent',
178
- color: isSelected ? 'background.paper' : 'text.secondary',
179
- transition: 'all 0.15s ease',
180
- }}
181
- />
182
- );
183
- })}
184
- </Box>
185
- </Box>
186
- )}
140
+ {/* All tab panels share the same content below */}
141
+ {TOOL_CONFIGS.map(t => (
142
+ <TabPanel key={t.tool} id={t.tool}>
143
+ {/* Provider filter — only shown when there are 2+ providers */}
144
+ {showProviderFilter && (
145
+ <Box className={styles.providerSection}>
146
+ <Text variant="body-x-small" color="secondary" className={styles.sectionLabel}>
147
+ Scope to Provider
148
+ </Text>
149
+ <Flex className={styles.chipsRow}>
150
+ <ToggleButton
151
+ id="all-providers"
152
+ size="small"
153
+ className={styles.filterChip}
154
+ isSelected={!selectedProvider}
155
+ onChange={() => setSelectedProvider('')}
156
+ style={{
157
+ borderColor: !selectedProvider ? 'var(--bui-fg-primary)' : 'var(--bui-border-1)',
158
+ backgroundColor: !selectedProvider ? 'var(--bui-fg-primary)' : 'transparent',
159
+ color: !selectedProvider ? 'var(--bui-bg-neutral-1)' : 'var(--bui-fg-secondary)',
160
+ }}
161
+ >
162
+ All providers
163
+ </ToggleButton>
164
+ {providers.map(p => {
165
+ const isSelected = selectedProvider === p.id;
166
+ const label = providerLabel(p.target);
167
+ return (
168
+ <ToggleButton
169
+ key={p.id}
170
+ id={p.id}
171
+ size="small"
172
+ className={styles.filterChip}
173
+ iconStart={<RiDatabase2Line size={12} style={{ color: isSelected ? 'var(--bui-bg-neutral-1)' : 'inherit' }} />}
174
+ isSelected={isSelected}
175
+ onChange={() => setSelectedProvider(isSelected ? '' : p.id)}
176
+ style={{
177
+ borderColor: isSelected ? 'var(--bui-fg-primary)' : 'var(--bui-border-1)',
178
+ backgroundColor: isSelected ? 'var(--bui-fg-primary)' : 'transparent',
179
+ color: isSelected ? 'var(--bui-bg-neutral-1)' : 'var(--bui-fg-secondary)',
180
+ }}
181
+ >
182
+ {label}
183
+ </ToggleButton>
184
+ );
185
+ })}
186
+ </Flex>
187
+ </Box>
188
+ )}
187
189
 
188
- {/* Proactive suggestions toggle */}
189
- <Box sx={{ mb: 2 }}>
190
- <FormControlLabel
191
- control={
192
- <Switch
193
- size="small"
194
- checked={proactiveEnabled}
195
- onChange={e => setProactiveEnabled(e.target.checked)}
196
- />
197
- }
198
- label={
199
- <Box>
200
- <Typography variant="body2" fontWeight={600}>Proactive suggestions</Typography>
201
- <Typography variant="caption" color="text.secondary">
202
- The AI will automatically suggest relevant assets based on your project context.
203
- Disable if you prefer to search manually.
204
- </Typography>
205
- </Box>
206
- }
207
- sx={{ alignItems: 'flex-start', ml: 0, gap: 1 }}
208
- />
209
- </Box>
190
+ {/* Proactive suggestions toggle */}
191
+ <Flex className={styles.proactiveSection}>
192
+ <Flex className={styles.proactiveRow}>
193
+ <Switch
194
+ isSelected={proactiveEnabled}
195
+ onChange={setProactiveEnabled}
196
+ aria-label="Proactive suggestions"
197
+ />
198
+ <Box>
199
+ <Text variant="body-small" weight="bold">Proactive suggestions</Text>
200
+ <Text variant="body-x-small" color="secondary">
201
+ The AI will automatically suggest relevant assets based on your project context.
202
+ Disable if you prefer to search manually.
203
+ </Text>
204
+ </Box>
205
+ </Flex>
206
+ </Flex>
210
207
 
211
- {/* MCP URL */}
212
- <Typography variant="caption" fontWeight={600} color="text.secondary" sx={{ textTransform: 'uppercase', letterSpacing: 0.5 }}>
213
- MCP Endpoint
214
- </Typography>
215
- <Box
216
- sx={{
217
- display: 'flex',
218
- alignItems: 'center',
219
- gap: 1,
220
- mt: 0.5,
221
- mb: 2,
222
- px: 1.5,
223
- py: 1,
224
- borderRadius: 1.5,
225
- border: '1px solid',
226
- borderColor: 'divider',
227
- backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.03)',
228
- }}
229
- >
230
- <Typography
231
- variant="body2"
232
- sx={{ flex: 1, fontFamily: 'monospace', fontSize: '0.8rem', wordBreak: 'break-all' }}
233
- >
234
- {mcpUrl}
235
- </Typography>
236
- <Tooltip title={copiedUrl ? 'Copied!' : 'Copy URL'}>
237
- <IconButton size="small" onClick={() => copyUrl(mcpUrl)}>
238
- {copiedUrl ? <CheckIcon fontSize="small" color="success" /> : <ContentCopyIcon fontSize="small" />}
239
- </IconButton>
240
- </Tooltip>
241
- </Box>
208
+ {/* MCP URL */}
209
+ <Box className={styles.urlSection}>
210
+ <Text variant="body-x-small" color="secondary" className={styles.sectionLabel}>
211
+ MCP Endpoint
212
+ </Text>
213
+ <Flex className={styles.urlBox}>
214
+ <Text variant="body-small" className={styles.urlText}>
215
+ {mcpUrl}
216
+ </Text>
217
+ <TooltipTrigger>
218
+ <ButtonIcon
219
+ aria-label="Copy URL"
220
+ icon={copiedUrl ? <RiCheckLine size={14} style={{ color: 'var(--bui-fg-success)' }} /> : <RiFileCopyLine size={14} />}
221
+ variant="tertiary"
222
+ onPress={() => copyUrl(mcpUrl)}
223
+ />
224
+ <Tooltip>{copiedUrl ? 'Copied!' : 'Copy URL'}</Tooltip>
225
+ </TooltipTrigger>
226
+ </Flex>
227
+ </Box>
242
228
 
243
- {/* Config snippet */}
244
- <Typography variant="caption" fontWeight={600} color="text.secondary" sx={{ textTransform: 'uppercase', letterSpacing: 0.5 }}>
245
- {cfg.file}
246
- </Typography>
247
- <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1, mt: 0.5 }}>
248
- {cfg.description}
249
- </Typography>
250
- <Box sx={{ position: 'relative' }}>
251
- <Box
252
- component="pre"
253
- sx={{
254
- m: 0,
255
- p: 2,
256
- borderRadius: 2,
257
- border: '1px solid',
258
- borderColor: 'divider',
259
- backgroundColor: theme.palette.mode === 'dark' ? '#0d1117' : '#f6f8fa',
260
- color: theme.palette.mode === 'dark' ? '#e6edf3' : '#24292f',
261
- fontFamily: 'monospace',
262
- fontSize: '0.8rem',
263
- overflowX: 'auto',
264
- whiteSpace: 'pre',
265
- }}
266
- >
267
- {configSnippet}
268
- </Box>
269
- <Tooltip title={copiedSnippet ? 'Copied!' : 'Copy config'}>
270
- <IconButton
271
- size="small"
272
- onClick={() => copySnippet(configSnippet)}
273
- sx={{
274
- position: 'absolute',
275
- top: 8,
276
- right: 8,
277
- backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.06)',
278
- '&:hover': {
279
- backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.12)',
280
- },
281
- }}
282
- >
283
- {copiedSnippet ? <CheckIcon fontSize="small" color="success" /> : <ContentCopyIcon fontSize="small" />}
284
- </IconButton>
285
- </Tooltip>
286
- </Box>
229
+ {/* Config snippet */}
230
+ <Text variant="body-x-small" color="secondary" className={styles.sectionLabel}>
231
+ {t.file}
232
+ </Text>
233
+ <Text variant="body-x-small" color="secondary" style={{ display: 'block', marginBottom: 'var(--bui-space-2)', marginTop: 'var(--bui-space-1)' }}>
234
+ {t.description}
235
+ </Text>
236
+ <div className={styles.snippetContainer}>
237
+ <pre className={`${styles.snippetPre} ${isDark ? styles.snippetPreDark : styles.snippetPreLight}`}>
238
+ {configSnippet}
239
+ </pre>
240
+ <TooltipTrigger>
241
+ <ButtonIcon
242
+ aria-label="Copy config"
243
+ className={styles.copySnippetButton}
244
+ icon={copiedSnippet ? <RiCheckLine size={14} style={{ color: 'var(--bui-fg-success)' }} /> : <RiFileCopyLine size={14} />}
245
+ variant="tertiary"
246
+ onPress={() => copySnippet(configSnippet)}
247
+ />
248
+ <Tooltip>{copiedSnippet ? 'Copied!' : 'Copy config'}</Tooltip>
249
+ </TooltipTrigger>
250
+ </div>
287
251
 
288
- <Typography variant="caption" color="text.disabled" sx={{ display: 'block', mt: 1.5 }}>
289
- 💡 Omit <code>?tool=</code> from the URL to receive assets for all AI tools.
290
- {showProviderFilter && ' Omit ?provider= to receive assets from all repositories.'}
291
- {' '}Proactive suggestions add <code>?proactive=true</code> and register the{' '}
292
- <code>suggest_assets</code> tool and <code>check_for_assets</code> prompt.
293
- </Typography>
294
- </DialogContent>
252
+ <Text variant="body-x-small" color="secondary" className={styles.hint}>
253
+ 💡 Omit <code>?tool=</code> from the URL to receive assets for all AI tools.
254
+ {showProviderFilter && ' Omit ?provider= to receive assets from all repositories.'}
255
+ {' '}Proactive suggestions add <code>?proactive=true</code> and register the{' '}
256
+ <code>suggest_assets</code> tool and <code>check_for_assets</code> prompt.
257
+ </Text>
258
+ </TabPanel>
259
+ ))}
260
+ </Tabs>
261
+ </DialogBody>
295
262
 
296
- <DialogActions>
297
- <Button onClick={onClose}>Close</Button>
298
- </DialogActions>
299
- </Dialog>
263
+ <DialogFooter>
264
+ <Button onClick={onClose} variant="secondary" slot="close">
265
+ Close
266
+ </Button>
267
+ </DialogFooter>
268
+ </Dialog>
269
+ </DialogTrigger>
300
270
  );
301
271
  }
@@ -0,0 +1,121 @@
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
+ }