@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.
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 +22 -4
  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 +21 -1
  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,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 '@mui/material/Box';
4
- import Button from '@mui/material/Button';
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' as AssetType, label: 'Instructions', Icon: ArticleIcon,
58
- gradient: 'linear-gradient(135deg, #2563EB 0%, #1D4ED8 100%)', shadow: '#2563EB40' },
59
- { key: 'agent' as AssetType, label: 'Agents', Icon: SmartToyIcon,
60
- gradient: 'linear-gradient(135deg, #7C3AED 0%, #6D28D9 100%)', shadow: '#7C3AED40' },
61
- { key: 'skill' as AssetType, label: 'Skills', Icon: BuildIcon,
62
- gradient: 'linear-gradient(135deg, #059669 0%, #047857 100%)', shadow: '#05966940' },
63
- { key: 'workflow' as AssetType, label: 'Workflows', Icon: AccountTreeIcon,
64
- gradient: 'linear-gradient(135deg, #D97706 0%, #B45309 100%)', shadow: '#D9770640' },
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
- <Page themeId="tool">
122
- <Header
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
- <Grid container spacing={2} sx={{ mb: 3 }}>
223
- {STATS_CONFIG.map(({ key, label, Icon, gradient, shadow }) => (
224
- <Grid item xs={6} sm={3} key={key}>
225
- <Box
226
- onClick={() => handleFiltersChange({ ...filters, types: filters.types[0] === key ? [] : [key] })}
227
- sx={{
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
- borderRadius: 3,
230
- p: 2,
231
- cursor: 'pointer',
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
- <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
110
+ <div className={styles.statCardInner}>
244
111
  <Box>
245
- <Typography variant="h4" fontWeight={800} sx={{ color: '#fff', lineHeight: 1 }}>
246
- {stats ? (stats.byType[key] ?? 0) : <Skeleton width={32} sx={{ bgcolor: 'rgba(255,255,255,0.3)' }} />}
247
- </Typography>
248
- <Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.85)', fontWeight: 500, mt: 0.5 }}>
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
- </Typography>
117
+ </Text>
251
118
  </Box>
252
- <Box
253
- sx={{
254
- width: 40,
255
- height: 40,
256
- borderRadius: 2,
257
- backgroundColor: 'rgba(255,255,255,0.2)',
258
- display: 'flex',
259
- alignItems: 'center',
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
- <Typography variant="caption" color="text.secondary" sx={{ mb: 2, display: 'block' }}>
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
- </Typography>
140
+ </Text>
284
141
  )}
285
142
 
286
143
  {/* Asset grid — 4 columns on large screens */}
287
- <Grid container spacing={1.5}>
144
+ <div className={styles.assetGrid}>
288
145
  {loading
289
146
  ? Array.from({ length: 8 }).map((_, i) => (
290
- <Grid item xs={12} sm={6} md={4} lg={3} key={i}>
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
- <Grid item xs={12} sm={6} md={4} lg={3} key={asset.id}>
296
- <AssetCard
297
- asset={asset}
298
- onView={handleViewAsset}
299
- onInstall={handleInstallAsset}
300
- />
301
- </Grid>
150
+ <AssetCard
151
+ key={asset.id}
152
+ asset={asset}
153
+ onView={handleViewAsset}
154
+ onInstall={handleInstallAsset}
155
+ />
302
156
  ))}
303
- </Grid>
157
+ </div>
304
158
 
305
159
  {/* Empty state */}
306
160
  {!loading && result?.items.length === 0 && (
307
- <Box sx={{ py: 12, textAlign: 'center' }}>
308
- <Typography variant="h1" sx={{ mb: 2, opacity: 0.15, fontSize: '5rem' }}>🤖</Typography>
309
- <Typography variant="h6" color="text.secondary" fontWeight={600}>No assets found</Typography>
310
- <Typography variant="body2" color="text.disabled" sx={{ mt: 0.5 }}>
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
- </Typography>
313
- </Box>
166
+ </Text>
167
+ </div>
314
168
  )}
315
169
 
316
170
  {/* Pagination */}
317
171
  {totalPages > 1 && (
318
- <Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
319
- <Pagination
320
- count={totalPages}
321
- page={page}
322
- onChange={(_, p) => setPage(p)}
323
- color="primary"
324
- shape="rounded"
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
- </Box>
182
+ </Flex>
327
183
  )}
328
- </Content>
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
+ }