@julianpedro/plugin-dev-ai-hub 0.2.0 → 0.3.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 (30) hide show
  1. package/dist/api/DevAiHubClient.esm.js +10 -2
  2. package/dist/api/DevAiHubClient.esm.js.map +1 -1
  3. package/dist/components/AssetCard/AssetCard.esm.js +85 -13
  4. package/dist/components/AssetCard/AssetCard.esm.js.map +1 -1
  5. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +272 -85
  6. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +1 -1
  7. package/dist/components/AssetFilters/AssetFilters.esm.js +103 -44
  8. package/dist/components/AssetFilters/AssetFilters.esm.js.map +1 -1
  9. package/dist/components/AssetHelpDialog/AssetHelpDialog.esm.js +87 -0
  10. package/dist/components/AssetHelpDialog/AssetHelpDialog.esm.js.map +1 -0
  11. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +328 -108
  12. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +1 -1
  13. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +93 -44
  14. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +1 -1
  15. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js +535 -234
  16. package/dist/components/McpConfigDialog/McpConfigDialog.esm.js.map +1 -1
  17. package/dist/components/ToolIcon/ToolIcon.esm.js +4 -1
  18. package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -1
  19. package/dist/hooks/index.esm.js +15 -1
  20. package/dist/hooks/index.esm.js.map +1 -1
  21. package/dist/index.d.ts +6 -3
  22. package/dist/index.esm.js +1 -0
  23. package/dist/index.esm.js.map +1 -1
  24. package/dist/locales/es.esm.js +112 -0
  25. package/dist/locales/es.esm.js.map +1 -0
  26. package/dist/locales/pt-BR.esm.js +112 -0
  27. package/dist/locales/pt-BR.esm.js.map +1 -0
  28. package/dist/translation.esm.js +135 -0
  29. package/dist/translation.esm.js.map +1 -0
  30. package/package.json +2 -2
@@ -1,13 +1,15 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { useState, useEffect } from 'react';
2
+ import { useState, useMemo, useEffect } from 'react';
3
3
  import Box from '@mui/material/Box';
4
4
  import Button from '@mui/material/Button';
5
5
  import Chip from '@mui/material/Chip';
6
6
  import Collapse from '@mui/material/Collapse';
7
7
  import Dialog from '@mui/material/Dialog';
8
+ import Grid from '@mui/material/Grid';
8
9
  import DialogActions from '@mui/material/DialogActions';
9
10
  import DialogContent from '@mui/material/DialogContent';
10
11
  import DialogTitle from '@mui/material/DialogTitle';
12
+ import Divider from '@mui/material/Divider';
11
13
  import FormControlLabel from '@mui/material/FormControlLabel';
12
14
  import IconButton from '@mui/material/IconButton';
13
15
  import Switch from '@mui/material/Switch';
@@ -20,53 +22,184 @@ import ContentCopyIcon from '@mui/icons-material/ContentCopy';
20
22
  import CheckIcon from '@mui/icons-material/Check';
21
23
  import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
22
24
  import StorageIcon from '@mui/icons-material/Storage';
25
+ import OpenInBrowserIcon from '@mui/icons-material/OpenInBrowser';
26
+ import AppsIcon from '@mui/icons-material/Apps';
27
+ import TuneIcon from '@mui/icons-material/Tune';
23
28
  import { useApi, discoveryApiRef } from '@backstage/core-plugin-api';
29
+ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
24
30
  import { ToolIcon } from '../ToolIcon/ToolIcon.esm.js';
25
- import { useCopyToClipboard, useProviders } from '../../hooks/index.esm.js';
31
+ import { useCopyToClipboard, useProviders, useMcpCatalog } from '../../hooks/index.esm.js';
32
+ import { devAiHubTranslationRef } from '../../translation.esm.js';
26
33
 
27
- const TOOL_CONFIGS = [
28
- {
29
- tool: "claude-code",
30
- label: "Claude Code",
31
- file: ".mcp.json",
32
- description: "Add to .mcp.json in your project root (or run via claude mcp add):",
33
- buildConfig: (url) => JSON.stringify({
34
- mcpServers: { "dev-ai-hub": { type: "http", url } }
35
- }, null, 2)
36
- },
37
- {
38
- tool: "github-copilot",
39
- label: "GitHub Copilot",
40
- file: ".vscode/settings.json",
41
- description: "Add to your VS Code settings (.vscode/settings.json or user settings):",
42
- buildConfig: (url) => JSON.stringify({
43
- "github.copilot.chat.mcp.servers": { "dev-ai-hub": { type: "http", url } }
44
- }, null, 2)
45
- },
46
- {
47
- tool: "google-gemini",
48
- label: "Google Gemini",
49
- file: "gemini-config.json",
50
- description: "Add to your Gemini CLI configuration:",
51
- buildConfig: (url) => JSON.stringify({
52
- mcpServers: { "dev-ai-hub": { url } }
53
- }, null, 2)
54
- },
55
- {
56
- tool: "cursor",
57
- label: "Cursor",
58
- file: ".cursor/mcp.json",
59
- description: "Add to .cursor/mcp.json in your project root:",
60
- buildConfig: (url) => JSON.stringify({
61
- mcpServers: { "dev-ai-hub": { type: "http", url } }
62
- }, null, 2)
63
- }
64
- ];
34
+ const TYPE_ACCENT = {
35
+ http: "#2563EB",
36
+ stdio: "#059669"
37
+ };
65
38
  function providerLabel(target) {
66
39
  return target.split("/").pop()?.replace(/\.git$/, "") ?? target;
67
40
  }
41
+ function CatalogEntryCard({ entry }) {
42
+ const { t } = useTranslationRef(devAiHubTranslationRef);
43
+ const accent = TYPE_ACCENT[entry.type] ?? "#64748b";
44
+ const handleInstallVscode = () => {
45
+ const config = { name: entry.id, type: entry.type };
46
+ if (entry.type === "http") config.url = entry.url;
47
+ if (entry.type === "stdio") {
48
+ config.command = entry.command;
49
+ if (entry.args?.length) config.args = entry.args;
50
+ if (entry.env && Object.keys(entry.env).length) config.env = entry.env;
51
+ }
52
+ window.location.href = `vscode:mcp/install?${encodeURIComponent(JSON.stringify(config))}`;
53
+ };
54
+ const handleInstallCursor = () => {
55
+ const config = { type: entry.type };
56
+ if (entry.type === "http") config.url = entry.url;
57
+ if (entry.type === "stdio") {
58
+ config.command = entry.command;
59
+ if (entry.args?.length) config.args = entry.args;
60
+ if (entry.env && Object.keys(entry.env).length) config.env = entry.env;
61
+ }
62
+ window.location.href = `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(entry.name)}&config=${btoa(JSON.stringify(config))}`;
63
+ };
64
+ const canInstall = entry.type === "http" && !!entry.url || entry.type === "stdio" && !!entry.command;
65
+ return /* @__PURE__ */ jsxs(
66
+ Box,
67
+ {
68
+ sx: {
69
+ border: "1px solid",
70
+ borderColor: "divider",
71
+ borderLeft: `3px solid ${accent}`,
72
+ borderRadius: 2,
73
+ p: 2,
74
+ display: "flex",
75
+ flexDirection: "column",
76
+ gap: 1.25,
77
+ height: "100%",
78
+ minHeight: 148,
79
+ transition: "all 0.15s ease",
80
+ "&:hover": {
81
+ boxShadow: `0 4px 16px ${accent}25`,
82
+ borderColor: `${accent}60`
83
+ }
84
+ },
85
+ children: [
86
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "flex-start", gap: 1.25 }, children: [
87
+ /* @__PURE__ */ jsx(
88
+ Box,
89
+ {
90
+ sx: {
91
+ width: 48,
92
+ height: 48,
93
+ borderRadius: 2,
94
+ backgroundColor: `${accent}18`,
95
+ display: "flex",
96
+ alignItems: "center",
97
+ justifyContent: "center",
98
+ flexShrink: 0,
99
+ boxShadow: `0 2px 8px ${accent}20`
100
+ },
101
+ children: entry.icon ? /* @__PURE__ */ jsx(
102
+ Box,
103
+ {
104
+ component: "img",
105
+ src: entry.icon,
106
+ alt: entry.name,
107
+ sx: { width: 32, height: 32, objectFit: "contain" },
108
+ onError: (e) => {
109
+ e.target.style.display = "none";
110
+ }
111
+ }
112
+ ) : /* @__PURE__ */ jsx(StorageIcon, { sx: { fontSize: "1.4rem", color: accent } })
113
+ }
114
+ ),
115
+ /* @__PURE__ */ jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [
116
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.75, mb: 0.25, flexWrap: "wrap" }, children: [
117
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", fontWeight: 700, noWrap: true, title: entry.name, sx: { flex: 1 }, children: entry.name }),
118
+ /* @__PURE__ */ jsx(
119
+ Chip,
120
+ {
121
+ label: entry.type,
122
+ size: "small",
123
+ sx: {
124
+ height: 18,
125
+ fontSize: "0.6rem",
126
+ fontFamily: "monospace",
127
+ fontWeight: 700,
128
+ bgcolor: `${accent}18`,
129
+ color: accent,
130
+ border: "1px solid",
131
+ borderColor: `${accent}40`
132
+ }
133
+ }
134
+ )
135
+ ] }),
136
+ entry.description && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", title: entry.description, sx: { display: "block", lineHeight: 1.4 }, children: entry.description }),
137
+ entry.type === "http" && entry.url && /* @__PURE__ */ jsx(
138
+ Typography,
139
+ {
140
+ variant: "caption",
141
+ sx: { display: "block", fontFamily: "monospace", fontSize: "0.68rem", color: "text.disabled", mt: 0.25, wordBreak: "break-all" },
142
+ children: entry.url
143
+ }
144
+ ),
145
+ entry.type === "stdio" && entry.command && /* @__PURE__ */ jsx(
146
+ Typography,
147
+ {
148
+ variant: "caption",
149
+ sx: { display: "block", fontFamily: "monospace", fontSize: "0.68rem", color: "text.disabled", mt: 0.25 },
150
+ children: [entry.command, ...entry.args ?? []].join(" ")
151
+ }
152
+ )
153
+ ] })
154
+ ] }),
155
+ canInstall && /* @__PURE__ */ jsx(Divider, { sx: { mt: "auto" } }),
156
+ canInstall && /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 0.75 }, children: [
157
+ /* @__PURE__ */ jsx(
158
+ Button,
159
+ {
160
+ size: "small",
161
+ variant: "outlined",
162
+ startIcon: /* @__PURE__ */ jsx(OpenInBrowserIcon, { sx: { fontSize: "0.85rem !important" } }),
163
+ onClick: handleInstallVscode,
164
+ fullWidth: true,
165
+ sx: {
166
+ fontSize: "0.75rem",
167
+ fontWeight: 700,
168
+ py: 0.5,
169
+ borderColor: `${accent}50`,
170
+ color: accent,
171
+ "&:hover": { borderColor: accent, bgcolor: `${accent}0a` }
172
+ },
173
+ children: t("mcpConfigDialog.installInVscode")
174
+ }
175
+ ),
176
+ /* @__PURE__ */ jsx(
177
+ Button,
178
+ {
179
+ size: "small",
180
+ variant: "outlined",
181
+ startIcon: /* @__PURE__ */ jsx(ToolIcon, { tool: "cursor", branded: false, sx: { fontSize: "0.85rem !important", color: `${accent} !important` } }),
182
+ onClick: handleInstallCursor,
183
+ fullWidth: true,
184
+ sx: {
185
+ fontSize: "0.75rem",
186
+ fontWeight: 700,
187
+ py: 0.5,
188
+ borderColor: `${accent}50`,
189
+ color: accent,
190
+ "&:hover": { borderColor: accent, bgcolor: `${accent}0a` }
191
+ },
192
+ children: t("mcpConfigDialog.installInCursor")
193
+ }
194
+ )
195
+ ] })
196
+ ]
197
+ }
198
+ );
199
+ }
68
200
  function McpConfigDialog({ open, onClose }) {
69
201
  const theme = useTheme();
202
+ const { t } = useTranslationRef(devAiHubTranslationRef);
70
203
  const discoveryApi = useApi(discoveryApiRef);
71
204
  const { copy: copyUrl, copied: copiedUrl } = useCopyToClipboard();
72
205
  const { copy: copySnippet, copied: copiedSnippet } = useCopyToClipboard();
@@ -74,15 +207,55 @@ function McpConfigDialog({ open, onClose }) {
74
207
  const [baseUrl, setBaseUrl] = useState("");
75
208
  const [selectedProvider, setSelectedProvider] = useState("");
76
209
  const [proactiveEnabled, setProactiveEnabled] = useState(false);
210
+ const [toolConfigExpanded, setToolConfigExpanded] = useState(false);
77
211
  const [manualExpanded, setManualExpanded] = useState(false);
78
212
  const { providers } = useProviders();
213
+ const { catalog } = useMcpCatalog();
79
214
  const showProviderFilter = providers.length > 1;
215
+ const toolConfigs = useMemo(() => [
216
+ {
217
+ tool: "claude-code",
218
+ label: "Claude Code",
219
+ file: ".mcp.json",
220
+ description: t("mcpConfigDialog.claudeConfigDesc"),
221
+ buildConfig: (url) => JSON.stringify({
222
+ mcpServers: { "dev-ai-hub": { type: "http", url } }
223
+ }, null, 2)
224
+ },
225
+ {
226
+ tool: "github-copilot",
227
+ label: "GitHub Copilot",
228
+ file: ".vscode/settings.json",
229
+ description: t("mcpConfigDialog.copilotConfigDesc"),
230
+ buildConfig: (url) => JSON.stringify({
231
+ "github.copilot.chat.mcp.servers": { "dev-ai-hub": { type: "http", url } }
232
+ }, null, 2)
233
+ },
234
+ {
235
+ tool: "google-gemini",
236
+ label: "Google Gemini",
237
+ file: "gemini-config.json",
238
+ description: t("mcpConfigDialog.geminiConfigDesc"),
239
+ buildConfig: (url) => JSON.stringify({
240
+ mcpServers: { "dev-ai-hub": { url } }
241
+ }, null, 2)
242
+ },
243
+ {
244
+ tool: "cursor",
245
+ label: "Cursor",
246
+ file: ".cursor/mcp.json",
247
+ description: t("mcpConfigDialog.cursorConfigDesc"),
248
+ buildConfig: (url) => JSON.stringify({
249
+ mcpServers: { "dev-ai-hub": { type: "http", url } }
250
+ }, null, 2)
251
+ }
252
+ ], [t]);
80
253
  useEffect(() => {
81
254
  if (open) {
82
255
  discoveryApi.getBaseUrl("dev-ai-hub").then((url) => setBaseUrl(url));
83
256
  }
84
257
  }, [open, discoveryApi]);
85
- const cfg = TOOL_CONFIGS[tab];
258
+ const cfg = toolConfigs[tab] ?? toolConfigs[0];
86
259
  const buildMcpUrl = () => {
87
260
  if (!baseUrl) return "loading...";
88
261
  const params = new URLSearchParams();
@@ -93,229 +266,357 @@ function McpConfigDialog({ open, onClose }) {
93
266
  };
94
267
  const mcpUrl = buildMcpUrl();
95
268
  const configSnippet = baseUrl ? cfg.buildConfig(mcpUrl) : "";
96
- return /* @__PURE__ */ jsxs(Dialog, { open, onClose, maxWidth: "sm", fullWidth: true, children: [
97
- /* @__PURE__ */ jsxs(DialogTitle, { sx: { pb: 0 }, children: [
98
- /* @__PURE__ */ jsx(Typography, { variant: "h6", fontWeight: 700, children: "Configure MCP Server" }),
99
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mt: 0.5 }, children: "Connect your AI tool to the Dev AI Hub via Model Context Protocol." })
100
- ] }),
101
- /* @__PURE__ */ jsxs(DialogContent, { sx: { pt: 1 }, children: [
269
+ const handleInstallInVscode = () => {
270
+ if (!baseUrl) return;
271
+ const config = JSON.stringify({ name: "dev-ai-hub", type: "http", url: mcpUrl });
272
+ window.location.href = `vscode:mcp/install?${encodeURIComponent(config)}`;
273
+ };
274
+ const handleInstallInCursor = () => {
275
+ if (!baseUrl) return;
276
+ const config = btoa(JSON.stringify({ type: "http", url: mcpUrl }));
277
+ window.location.href = `cursor://anysphere.cursor-deeplink/mcp/install?name=dev-ai-hub&config=${config}`;
278
+ };
279
+ const showVscodeButton = cfg.tool === "github-copilot";
280
+ const showCursorButton = cfg.tool === "cursor";
281
+ return /* @__PURE__ */ jsxs(Dialog, { open, onClose, maxWidth: "md", fullWidth: true, children: [
282
+ /* @__PURE__ */ jsx(DialogTitle, { sx: { pb: 1 }, children: /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1.5 }, children: [
102
283
  /* @__PURE__ */ jsx(
103
- Tabs,
284
+ Box,
104
285
  {
105
- value: tab,
106
- onChange: (_, v) => setTab(v),
107
- sx: { mb: 2, borderBottom: 1, borderColor: "divider" },
108
- children: TOOL_CONFIGS.map((t, i) => /* @__PURE__ */ jsx(
109
- Tab,
110
- {
111
- value: i,
112
- label: /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
113
- /* @__PURE__ */ jsx(
114
- ToolIcon,
115
- {
116
- tool: t.tool,
117
- sx: { fontSize: "1rem", color: theme.palette.mode === "dark" ? "#fff" : void 0 }
118
- }
119
- ),
120
- /* @__PURE__ */ jsx("span", { children: t.label })
121
- ] })
122
- },
123
- t.tool
124
- ))
286
+ sx: {
287
+ width: 36,
288
+ height: 36,
289
+ borderRadius: 1.5,
290
+ background: "linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)",
291
+ display: "flex",
292
+ alignItems: "center",
293
+ justifyContent: "center",
294
+ flexShrink: 0,
295
+ boxShadow: "0 2px 8px #6366f140"
296
+ },
297
+ children: /* @__PURE__ */ jsx(AppsIcon, { sx: { color: "#fff", fontSize: "1.2rem" } })
125
298
  }
126
299
  ),
127
- showProviderFilter && /* @__PURE__ */ jsxs(Box, { sx: { mb: 2 }, children: [
128
- /* @__PURE__ */ jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", sx: { textTransform: "uppercase", letterSpacing: 0.5, display: "block", mb: 0.75 }, children: "Scope to Provider" }),
129
- /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 0.75, flexWrap: "wrap" }, children: [
300
+ /* @__PURE__ */ jsxs(Box, { children: [
301
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", fontWeight: 700, sx: { lineHeight: 1.2 }, children: t("mcpConfigDialog.title") }),
302
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", children: t("mcpConfigDialog.subtitle") })
303
+ ] })
304
+ ] }) }),
305
+ /* @__PURE__ */ jsxs(DialogContent, { sx: { pt: 0 }, children: [
306
+ /* @__PURE__ */ jsxs(Box, { sx: { mb: 2 }, children: [
307
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.75, mb: 1, pt: 0.5 }, children: [
308
+ /* @__PURE__ */ jsx(AppsIcon, { sx: { fontSize: "0.95rem", color: "text.secondary" } }),
130
309
  /* @__PURE__ */ jsx(
310
+ Typography,
311
+ {
312
+ variant: "caption",
313
+ fontWeight: 700,
314
+ color: "text.secondary",
315
+ sx: { textTransform: "uppercase", letterSpacing: 0.8, flex: 1 },
316
+ children: t("mcpConfigDialog.catalogTab")
317
+ }
318
+ ),
319
+ catalog.length > 0 && /* @__PURE__ */ jsx(
131
320
  Chip,
132
321
  {
133
- label: "All providers",
322
+ label: catalog.length,
134
323
  size: "small",
135
- clickable: true,
136
- onClick: () => setSelectedProvider(""),
137
- sx: {
138
- fontWeight: 600,
139
- fontSize: "0.75rem",
140
- borderRadius: 2,
141
- border: "1.5px solid",
142
- borderColor: !selectedProvider ? "text.primary" : "divider",
143
- backgroundColor: !selectedProvider ? "text.primary" : "transparent",
144
- color: !selectedProvider ? "background.paper" : "text.secondary",
145
- transition: "all 0.15s ease"
146
- }
324
+ sx: { height: 18, fontSize: "0.65rem", fontWeight: 700, bgcolor: "action.selected", color: "text.secondary", border: "1px solid", borderColor: "divider" }
147
325
  }
148
- ),
149
- providers.map((p) => {
150
- const isSelected = selectedProvider === p.id;
151
- const label = providerLabel(p.target);
152
- return /* @__PURE__ */ jsx(
153
- Chip,
154
- {
155
- icon: /* @__PURE__ */ jsx(StorageIcon, { sx: { fontSize: "0.8rem !important", color: isSelected ? "background.paper" : "inherit" } }),
156
- label,
157
- size: "small",
158
- clickable: true,
159
- onClick: () => setSelectedProvider(isSelected ? "" : p.id),
160
- sx: {
161
- fontWeight: 600,
162
- fontSize: "0.75rem",
163
- borderRadius: 2,
164
- border: "1.5px solid",
165
- borderColor: isSelected ? "text.primary" : "divider",
166
- backgroundColor: isSelected ? "text.primary" : "transparent",
167
- color: isSelected ? "background.paper" : "text.secondary",
168
- transition: "all 0.15s ease"
169
- }
170
- },
171
- p.id
172
- );
173
- })
174
- ] })
326
+ )
327
+ ] }),
328
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 1.5, fontSize: "0.8rem" }, children: t("mcpConfigDialog.catalogDescription") }),
329
+ catalog.length === 0 ? /* @__PURE__ */ jsxs(
330
+ Box,
331
+ {
332
+ sx: {
333
+ border: "1px dashed",
334
+ borderColor: "divider",
335
+ borderRadius: 2,
336
+ p: 3,
337
+ textAlign: "center"
338
+ },
339
+ children: [
340
+ /* @__PURE__ */ jsx(AppsIcon, { sx: { fontSize: "2rem", color: "text.disabled", mb: 1 } }),
341
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: t("mcpConfigDialog.catalogEmpty") }),
342
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.disabled", children: t("mcpConfigDialog.catalogAddHint") })
343
+ ]
344
+ }
345
+ ) : /* @__PURE__ */ jsx(Grid, { container: true, spacing: 1.5, children: catalog.map((entry) => /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, sm: 6, children: /* @__PURE__ */ jsx(CatalogEntryCard, { entry }) }, entry.id)) })
175
346
  ] }),
176
- /* @__PURE__ */ jsx(Box, { sx: { mb: 2 }, children: /* @__PURE__ */ jsx(
177
- FormControlLabel,
178
- {
179
- control: /* @__PURE__ */ jsx(
180
- Switch,
347
+ /* @__PURE__ */ jsx(Divider, { sx: { mb: 0 } }),
348
+ /* @__PURE__ */ jsxs(Box, { children: [
349
+ /* @__PURE__ */ jsxs(
350
+ Box,
351
+ {
352
+ onClick: () => setToolConfigExpanded((v) => !v),
353
+ sx: {
354
+ display: "flex",
355
+ alignItems: "center",
356
+ gap: 0.75,
357
+ cursor: "pointer",
358
+ userSelect: "none",
359
+ py: 1.25,
360
+ "&:hover": { opacity: 0.8 }
361
+ },
362
+ children: [
363
+ /* @__PURE__ */ jsx(TuneIcon, { sx: { fontSize: "0.95rem", color: "text.secondary" } }),
364
+ /* @__PURE__ */ jsx(
365
+ Typography,
366
+ {
367
+ variant: "caption",
368
+ fontWeight: 700,
369
+ color: "text.secondary",
370
+ sx: { textTransform: "uppercase", letterSpacing: 0.8, flex: 1 },
371
+ children: t("mcpConfigDialog.toolConfigSection")
372
+ }
373
+ ),
374
+ /* @__PURE__ */ jsx(
375
+ ExpandMoreIcon,
376
+ {
377
+ fontSize: "small",
378
+ sx: {
379
+ color: "text.disabled",
380
+ transition: "transform 0.2s ease",
381
+ transform: toolConfigExpanded ? "rotate(180deg)" : "rotate(0deg)"
382
+ }
383
+ }
384
+ )
385
+ ]
386
+ }
387
+ ),
388
+ /* @__PURE__ */ jsxs(Collapse, { in: toolConfigExpanded, children: [
389
+ /* @__PURE__ */ jsx(
390
+ Tabs,
181
391
  {
182
- size: "small",
183
- checked: proactiveEnabled,
184
- onChange: (e) => setProactiveEnabled(e.target.checked)
392
+ value: tab,
393
+ onChange: (_, v) => setTab(v),
394
+ sx: { mb: 2, borderBottom: 1, borderColor: "divider" },
395
+ children: toolConfigs.map((toolCfg, i) => /* @__PURE__ */ jsx(
396
+ Tab,
397
+ {
398
+ value: i,
399
+ sx: {
400
+ color: "text.secondary",
401
+ minHeight: 40,
402
+ "&.Mui-selected": { color: "primary.main" },
403
+ "&:hover": {
404
+ backgroundColor: "transparent",
405
+ color: "text.primary"
406
+ },
407
+ "&.Mui-selected:hover": {
408
+ backgroundColor: "transparent"
409
+ }
410
+ },
411
+ label: /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.75 }, children: [
412
+ /* @__PURE__ */ jsx(ToolIcon, { tool: toolCfg.tool, branded: false, sx: { fontSize: "1rem" } }),
413
+ /* @__PURE__ */ jsx("span", { children: toolCfg.label })
414
+ ] })
415
+ },
416
+ toolCfg.tool
417
+ ))
185
418
  }
186
419
  ),
187
- label: /* @__PURE__ */ jsxs(Box, { children: [
188
- /* @__PURE__ */ jsx(Typography, { variant: "body2", fontWeight: 600, children: "Proactive suggestions" }),
189
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", children: "The AI will automatically suggest relevant assets based on your project context. Disable if you prefer to search manually." })
420
+ showProviderFilter && /* @__PURE__ */ jsxs(Box, { sx: { mb: 2 }, children: [
421
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", sx: { textTransform: "uppercase", letterSpacing: 0.5, display: "block", mb: 0.75 }, children: t("mcpConfigDialog.scopeToProvider") }),
422
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 0.75, flexWrap: "wrap" }, children: [
423
+ /* @__PURE__ */ jsx(
424
+ Chip,
425
+ {
426
+ label: t("mcpConfigDialog.allProviders"),
427
+ size: "small",
428
+ clickable: true,
429
+ onClick: () => setSelectedProvider(""),
430
+ sx: {
431
+ fontWeight: 600,
432
+ fontSize: "0.75rem",
433
+ borderRadius: 2,
434
+ border: "1.5px solid",
435
+ borderColor: !selectedProvider ? "text.primary" : "divider",
436
+ backgroundColor: !selectedProvider ? "text.primary" : "transparent",
437
+ color: !selectedProvider ? "background.paper" : "text.secondary",
438
+ transition: "all 0.15s ease"
439
+ }
440
+ }
441
+ ),
442
+ providers.map((p) => {
443
+ const isSelected = selectedProvider === p.id;
444
+ const label = providerLabel(p.target);
445
+ return /* @__PURE__ */ jsx(
446
+ Chip,
447
+ {
448
+ icon: /* @__PURE__ */ jsx(StorageIcon, { sx: { fontSize: "0.8rem !important", color: isSelected ? "background.paper" : "inherit" } }),
449
+ label,
450
+ size: "small",
451
+ clickable: true,
452
+ onClick: () => setSelectedProvider(isSelected ? "" : p.id),
453
+ sx: {
454
+ fontWeight: 600,
455
+ fontSize: "0.75rem",
456
+ borderRadius: 2,
457
+ border: "1.5px solid",
458
+ borderColor: isSelected ? "text.primary" : "divider",
459
+ backgroundColor: isSelected ? "text.primary" : "transparent",
460
+ color: isSelected ? "background.paper" : "text.secondary",
461
+ transition: "all 0.15s ease"
462
+ }
463
+ },
464
+ p.id
465
+ );
466
+ })
467
+ ] })
190
468
  ] }),
191
- sx: { alignItems: "flex-start", ml: 0, gap: 1 }
192
- }
193
- ) }),
194
- /* @__PURE__ */ jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", sx: { textTransform: "uppercase", letterSpacing: 0.5 }, children: "MCP Endpoint" }),
195
- /* @__PURE__ */ jsxs(
196
- Box,
197
- {
198
- sx: {
199
- display: "flex",
200
- alignItems: "center",
201
- gap: 1,
202
- mt: 0.5,
203
- mb: 2,
204
- px: 1.5,
205
- py: 1,
206
- borderRadius: 1.5,
207
- border: "1px solid",
208
- borderColor: "divider",
209
- backgroundColor: theme.palette.mode === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"
210
- },
211
- children: [
212
- /* @__PURE__ */ jsx(
213
- Typography,
214
- {
215
- variant: "body2",
216
- sx: { flex: 1, fontFamily: "monospace", fontSize: "0.8rem", wordBreak: "break-all" },
217
- children: mcpUrl
218
- }
219
- ),
220
- /* @__PURE__ */ jsx(Tooltip, { title: copiedUrl ? "Copied!" : "Copy URL", children: /* @__PURE__ */ jsx(IconButton, { size: "small", onClick: () => copyUrl(mcpUrl), children: copiedUrl ? /* @__PURE__ */ jsx(CheckIcon, { fontSize: "small", color: "success" }) : /* @__PURE__ */ jsx(ContentCopyIcon, { fontSize: "small" }) }) })
221
- ]
222
- }
223
- ),
224
- /* @__PURE__ */ jsxs(
225
- Box,
226
- {
227
- onClick: () => setManualExpanded((v) => !v),
228
- sx: {
229
- display: "flex",
230
- alignItems: "center",
231
- justifyContent: "space-between",
232
- cursor: "pointer",
233
- userSelect: "none",
234
- py: 0.75,
235
- borderTop: "1px solid",
236
- borderColor: "divider"
237
- },
238
- children: [
239
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", sx: { textTransform: "uppercase", letterSpacing: 0.5 }, children: [
240
- "Manual config \u2014 ",
241
- cfg.file
242
- ] }),
243
- /* @__PURE__ */ jsx(
244
- ExpandMoreIcon,
245
- {
246
- fontSize: "small",
247
- sx: {
248
- color: "text.disabled",
249
- transition: "transform 0.2s ease",
250
- transform: manualExpanded ? "rotate(180deg)" : "rotate(0deg)"
469
+ /* @__PURE__ */ jsx(Box, { sx: { mb: 2 }, children: /* @__PURE__ */ jsx(
470
+ FormControlLabel,
471
+ {
472
+ control: /* @__PURE__ */ jsx(
473
+ Switch,
474
+ {
475
+ size: "small",
476
+ checked: proactiveEnabled,
477
+ onChange: (e) => setProactiveEnabled(e.target.checked)
251
478
  }
252
- }
253
- )
254
- ]
255
- }
256
- ),
257
- /* @__PURE__ */ jsxs(Collapse, { in: manualExpanded, children: [
258
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { display: "block", mb: 1, mt: 0.5 }, children: cfg.description }),
259
- /* @__PURE__ */ jsxs(Box, { sx: { position: "relative" }, children: [
260
- /* @__PURE__ */ jsx(
479
+ ),
480
+ label: /* @__PURE__ */ jsxs(Box, { children: [
481
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", fontWeight: 600, children: t("mcpConfigDialog.proactiveSuggestions") }),
482
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", children: t("mcpConfigDialog.proactiveDescription") })
483
+ ] }),
484
+ sx: { alignItems: "flex-start", ml: 0, gap: 1 }
485
+ }
486
+ ) }),
487
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", sx: { textTransform: "uppercase", letterSpacing: 0.5 }, children: t("mcpConfigDialog.mcpEndpoint") }),
488
+ /* @__PURE__ */ jsxs(
261
489
  Box,
262
490
  {
263
- component: "pre",
264
491
  sx: {
265
- m: 0,
266
- p: 2,
267
- borderRadius: 2,
492
+ display: "flex",
493
+ alignItems: "center",
494
+ gap: 1,
495
+ mt: 0.5,
496
+ mb: showVscodeButton || showCursorButton ? 1.5 : 1.5,
497
+ px: 1.5,
498
+ py: 1,
499
+ borderRadius: 1.5,
268
500
  border: "1px solid",
269
501
  borderColor: "divider",
270
- backgroundColor: theme.palette.mode === "dark" ? "#0d1117" : "#f6f8fa",
271
- color: theme.palette.mode === "dark" ? "#e6edf3" : "#24292f",
272
- fontFamily: "monospace",
273
- fontSize: "0.8rem",
274
- overflowX: "auto",
275
- whiteSpace: "pre"
502
+ backgroundColor: theme.palette.mode === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"
276
503
  },
277
- children: configSnippet
504
+ children: [
505
+ /* @__PURE__ */ jsx(
506
+ Typography,
507
+ {
508
+ variant: "body2",
509
+ sx: { flex: 1, fontFamily: "monospace", fontSize: "0.8rem", wordBreak: "break-all" },
510
+ children: mcpUrl
511
+ }
512
+ ),
513
+ /* @__PURE__ */ jsx(Tooltip, { title: copiedUrl ? t("assetInstallDialog.copied") : t("mcpConfigDialog.copyUrl"), children: /* @__PURE__ */ jsx(IconButton, { size: "small", onClick: () => copyUrl(mcpUrl), children: copiedUrl ? /* @__PURE__ */ jsx(CheckIcon, { fontSize: "small", color: "success" }) : /* @__PURE__ */ jsx(ContentCopyIcon, { fontSize: "small" }) }) })
514
+ ]
278
515
  }
279
516
  ),
280
- /* @__PURE__ */ jsx(Tooltip, { title: copiedSnippet ? "Copied!" : "Copy config", children: /* @__PURE__ */ jsx(
281
- IconButton,
517
+ showVscodeButton && /* @__PURE__ */ jsx(
518
+ Button,
282
519
  {
283
- size: "small",
284
- onClick: (e) => {
285
- e.stopPropagation();
286
- copySnippet(configSnippet);
287
- },
520
+ variant: "contained",
521
+ startIcon: /* @__PURE__ */ jsx(OpenInBrowserIcon, {}),
522
+ onClick: handleInstallInVscode,
523
+ disabled: !baseUrl,
524
+ fullWidth: true,
525
+ sx: { mb: 2, fontSize: "0.8rem" },
526
+ children: t("mcpConfigDialog.installInVscode")
527
+ }
528
+ ),
529
+ showCursorButton && /* @__PURE__ */ jsx(
530
+ Button,
531
+ {
532
+ variant: "contained",
533
+ startIcon: /* @__PURE__ */ jsx(ToolIcon, { tool: "cursor", branded: false, sx: { fontSize: "1rem !important" } }),
534
+ onClick: handleInstallInCursor,
535
+ disabled: !baseUrl,
536
+ fullWidth: true,
537
+ sx: { mb: 2, fontSize: "0.8rem" },
538
+ children: t("mcpConfigDialog.installInCursor")
539
+ }
540
+ ),
541
+ /* @__PURE__ */ jsx(Divider, { sx: { mb: 1.5 } }),
542
+ /* @__PURE__ */ jsxs(
543
+ Box,
544
+ {
545
+ onClick: () => setManualExpanded((v) => !v),
288
546
  sx: {
289
- position: "absolute",
290
- top: 8,
291
- right: 8,
292
- backgroundColor: theme.palette.mode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.06)",
293
- "&:hover": {
294
- backgroundColor: theme.palette.mode === "dark" ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.12)"
295
- }
547
+ display: "flex",
548
+ alignItems: "center",
549
+ justifyContent: "space-between",
550
+ cursor: "pointer",
551
+ userSelect: "none",
552
+ py: 0.75
296
553
  },
297
- children: copiedSnippet ? /* @__PURE__ */ jsx(CheckIcon, { fontSize: "small", color: "success" }) : /* @__PURE__ */ jsx(ContentCopyIcon, { fontSize: "small" })
554
+ children: [
555
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", sx: { textTransform: "uppercase", letterSpacing: 0.5 }, children: t("mcpConfigDialog.manualConfig", { file: cfg.file }) }),
556
+ /* @__PURE__ */ jsx(
557
+ ExpandMoreIcon,
558
+ {
559
+ fontSize: "small",
560
+ sx: {
561
+ color: "text.disabled",
562
+ transition: "transform 0.2s ease",
563
+ transform: manualExpanded ? "rotate(180deg)" : "rotate(0deg)"
564
+ }
565
+ }
566
+ )
567
+ ]
298
568
  }
299
- ) })
300
- ] }),
301
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "text.disabled", sx: { display: "block", mt: 1.5 }, children: [
302
- "\u{1F4A1} Omit ",
303
- /* @__PURE__ */ jsx("code", { children: "?tool=" }),
304
- " from the URL to receive assets for all AI tools.",
305
- showProviderFilter && " Omit ?provider= to receive assets from all repositories.",
306
- " ",
307
- "Proactive suggestions add ",
308
- /* @__PURE__ */ jsx("code", { children: "?proactive=true" }),
309
- " and register the",
310
- " ",
311
- /* @__PURE__ */ jsx("code", { children: "suggest_assets" }),
312
- " tool and ",
313
- /* @__PURE__ */ jsx("code", { children: "check_for_assets" }),
314
- " prompt."
569
+ ),
570
+ /* @__PURE__ */ jsxs(Collapse, { in: manualExpanded, children: [
571
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { display: "block", mb: 1, mt: 0.5 }, children: cfg.description }),
572
+ /* @__PURE__ */ jsxs(Box, { sx: { position: "relative" }, children: [
573
+ /* @__PURE__ */ jsx(
574
+ Box,
575
+ {
576
+ component: "pre",
577
+ sx: {
578
+ m: 0,
579
+ p: 2,
580
+ borderRadius: 2,
581
+ border: "1px solid",
582
+ borderColor: "divider",
583
+ backgroundColor: theme.palette.mode === "dark" ? "#0d1117" : "#f6f8fa",
584
+ color: theme.palette.mode === "dark" ? "#e6edf3" : "#24292f",
585
+ fontFamily: "monospace",
586
+ fontSize: "0.8rem",
587
+ overflowX: "auto",
588
+ whiteSpace: "pre"
589
+ },
590
+ children: configSnippet
591
+ }
592
+ ),
593
+ /* @__PURE__ */ jsx(Tooltip, { title: copiedSnippet ? t("assetInstallDialog.copied") : t("mcpConfigDialog.copyUrl"), children: /* @__PURE__ */ jsx(
594
+ IconButton,
595
+ {
596
+ size: "small",
597
+ onClick: (e) => {
598
+ e.stopPropagation();
599
+ copySnippet(configSnippet);
600
+ },
601
+ sx: {
602
+ position: "absolute",
603
+ top: 8,
604
+ right: 8,
605
+ backgroundColor: theme.palette.mode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.06)",
606
+ "&:hover": {
607
+ backgroundColor: theme.palette.mode === "dark" ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.12)"
608
+ }
609
+ },
610
+ children: copiedSnippet ? /* @__PURE__ */ jsx(CheckIcon, { fontSize: "small", color: "success" }) : /* @__PURE__ */ jsx(ContentCopyIcon, { fontSize: "small" })
611
+ }
612
+ ) })
613
+ ] }),
614
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.disabled", sx: { display: "block", mt: 1.5 }, children: t("mcpConfigDialog.omitToolHint") })
615
+ ] })
315
616
  ] })
316
617
  ] })
317
618
  ] }),
318
- /* @__PURE__ */ jsx(DialogActions, { children: /* @__PURE__ */ jsx(Button, { onClick: onClose, children: "Close" }) })
619
+ /* @__PURE__ */ jsx(DialogActions, { children: /* @__PURE__ */ jsx(Button, { onClick: onClose, children: t("mcpConfigDialog.close") }) })
319
620
  ] });
320
621
  }
321
622