Spaces:
Build error
Build error
| import React, { useState } from "react"; | |
| import { useTranslation } from "react-i18next"; | |
| import { MCPConfig } from "#/types/settings"; | |
| import { I18nKey } from "#/i18n/declaration"; | |
| import { BrandButton } from "../brand-button"; | |
| interface MCPJsonEditorProps { | |
| mcpConfig?: MCPConfig; | |
| onChange: (config: MCPConfig) => void; | |
| } | |
| export function MCPJsonEditor({ mcpConfig, onChange }: MCPJsonEditorProps) { | |
| const { t } = useTranslation(); | |
| const [configText, setConfigText] = useState(() => | |
| mcpConfig | |
| ? JSON.stringify(mcpConfig, null, 2) | |
| : t(I18nKey.SETTINGS$MCP_DEFAULT_CONFIG), | |
| ); | |
| const [error, setError] = useState<string | null>(null); | |
| const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | |
| setConfigText(e.target.value); | |
| }; | |
| const handleSave = () => { | |
| try { | |
| const newConfig = JSON.parse(configText); | |
| // Validate the structure | |
| if (!newConfig.sse_servers || !Array.isArray(newConfig.sse_servers)) { | |
| throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_SSE_ARRAY)); | |
| } | |
| if (!newConfig.stdio_servers || !Array.isArray(newConfig.stdio_servers)) { | |
| throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_STDIO_ARRAY)); | |
| } | |
| // Validate SSE servers | |
| for (const server of newConfig.sse_servers) { | |
| if ( | |
| typeof server !== "string" && | |
| (!server.url || typeof server.url !== "string") | |
| ) { | |
| throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_SSE_URL)); | |
| } | |
| } | |
| // Validate stdio servers | |
| for (const server of newConfig.stdio_servers) { | |
| if (!server.name || !server.command) { | |
| throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_STDIO_PROPS)); | |
| } | |
| } | |
| onChange(newConfig); | |
| setError(null); | |
| } catch (e) { | |
| setError( | |
| e instanceof Error | |
| ? e.message | |
| : t(I18nKey.SETTINGS$MCP_ERROR_INVALID_JSON), | |
| ); | |
| } | |
| }; | |
| return ( | |
| <div> | |
| <div className="mb-2 text-sm text-gray-400"> | |
| {t(I18nKey.SETTINGS$MCP_CONFIG_DESCRIPTION)} | |
| </div> | |
| <textarea | |
| className="w-full h-64 p-2 text-sm font-mono bg-base-tertiary rounded-md focus:border-blue-500 focus:outline-none" | |
| value={configText} | |
| onChange={handleTextChange} | |
| spellCheck="false" | |
| /> | |
| {error && ( | |
| <div className="mt-2 p-2 bg-red-100 border border-red-300 rounded-md text-sm text-red-700"> | |
| <strong>{t(I18nKey.SETTINGS$MCP_CONFIG_ERROR)}</strong> {error} | |
| </div> | |
| )} | |
| <div className="mt-2 text-sm text-gray-400"> | |
| <strong>{t(I18nKey.SETTINGS$MCP_CONFIG_EXAMPLE)}</strong>{" "} | |
| <code> | |
| { | |
| '{ "sse_servers": ["https://example-mcp-server.com/sse"], "stdio_servers": [{ "name": "fetch", "command": "uvx", "args": ["mcp-server-fetch"] }] }' | |
| } | |
| </code> | |
| </div> | |
| <div className="mt-4 flex justify-end"> | |
| <BrandButton type="button" variant="primary" onClick={handleSave}> | |
| {t(I18nKey.SETTINGS$MCP_APPLY_CHANGES)} | |
| </BrandButton> | |
| </div> | |
| </div> | |
| ); | |
| } | |