@wener/mcps 1.0.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.
- package/LICENSE +21 -0
- package/dist/index.mjs +15 -0
- package/dist/mcps-cli.mjs +174727 -0
- package/lib/chat/agent.js +187 -0
- package/lib/chat/agent.js.map +1 -0
- package/lib/chat/audit.js +238 -0
- package/lib/chat/audit.js.map +1 -0
- package/lib/chat/converters.js +467 -0
- package/lib/chat/converters.js.map +1 -0
- package/lib/chat/handler.js +1068 -0
- package/lib/chat/handler.js.map +1 -0
- package/lib/chat/index.js +12 -0
- package/lib/chat/index.js.map +1 -0
- package/lib/chat/types.js +35 -0
- package/lib/chat/types.js.map +1 -0
- package/lib/contracts/AuditContract.js +85 -0
- package/lib/contracts/AuditContract.js.map +1 -0
- package/lib/contracts/McpsContract.js +113 -0
- package/lib/contracts/McpsContract.js.map +1 -0
- package/lib/contracts/index.js +3 -0
- package/lib/contracts/index.js.map +1 -0
- package/lib/dev.server.js +7 -0
- package/lib/dev.server.js.map +1 -0
- package/lib/entities/ChatRequestEntity.js +318 -0
- package/lib/entities/ChatRequestEntity.js.map +1 -0
- package/lib/entities/McpRequestEntity.js +271 -0
- package/lib/entities/McpRequestEntity.js.map +1 -0
- package/lib/entities/RequestLogEntity.js +177 -0
- package/lib/entities/RequestLogEntity.js.map +1 -0
- package/lib/entities/ResponseEntity.js +150 -0
- package/lib/entities/ResponseEntity.js.map +1 -0
- package/lib/entities/index.js +11 -0
- package/lib/entities/index.js.map +1 -0
- package/lib/entities/types.js +11 -0
- package/lib/entities/types.js.map +1 -0
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -0
- package/lib/mcps-cli.js +44 -0
- package/lib/mcps-cli.js.map +1 -0
- package/lib/providers/McpServerHandlerDef.js +40 -0
- package/lib/providers/McpServerHandlerDef.js.map +1 -0
- package/lib/providers/findMcpServerDef.js +26 -0
- package/lib/providers/findMcpServerDef.js.map +1 -0
- package/lib/providers/prometheus/def.js +24 -0
- package/lib/providers/prometheus/def.js.map +1 -0
- package/lib/providers/prometheus/index.js +2 -0
- package/lib/providers/prometheus/index.js.map +1 -0
- package/lib/providers/relay/def.js +32 -0
- package/lib/providers/relay/def.js.map +1 -0
- package/lib/providers/relay/index.js +2 -0
- package/lib/providers/relay/index.js.map +1 -0
- package/lib/providers/sql/def.js +31 -0
- package/lib/providers/sql/def.js.map +1 -0
- package/lib/providers/sql/index.js +2 -0
- package/lib/providers/sql/index.js.map +1 -0
- package/lib/providers/tencent-cls/def.js +44 -0
- package/lib/providers/tencent-cls/def.js.map +1 -0
- package/lib/providers/tencent-cls/index.js +2 -0
- package/lib/providers/tencent-cls/index.js.map +1 -0
- package/lib/scripts/bundle.js +90 -0
- package/lib/scripts/bundle.js.map +1 -0
- package/lib/server/api-routes.js +96 -0
- package/lib/server/api-routes.js.map +1 -0
- package/lib/server/audit.js +274 -0
- package/lib/server/audit.js.map +1 -0
- package/lib/server/chat-routes.js +82 -0
- package/lib/server/chat-routes.js.map +1 -0
- package/lib/server/config.js +223 -0
- package/lib/server/config.js.map +1 -0
- package/lib/server/db.js +97 -0
- package/lib/server/db.js.map +1 -0
- package/lib/server/index.js +2 -0
- package/lib/server/index.js.map +1 -0
- package/lib/server/mcp-handler.js +167 -0
- package/lib/server/mcp-handler.js.map +1 -0
- package/lib/server/mcp-routes.js +112 -0
- package/lib/server/mcp-routes.js.map +1 -0
- package/lib/server/mcps-router.js +119 -0
- package/lib/server/mcps-router.js.map +1 -0
- package/lib/server/schema.js +129 -0
- package/lib/server/schema.js.map +1 -0
- package/lib/server/server.js +166 -0
- package/lib/server/server.js.map +1 -0
- package/lib/web/ChatPage.js +827 -0
- package/lib/web/ChatPage.js.map +1 -0
- package/lib/web/McpInspectorPage.js +214 -0
- package/lib/web/McpInspectorPage.js.map +1 -0
- package/lib/web/ServersPage.js +93 -0
- package/lib/web/ServersPage.js.map +1 -0
- package/lib/web/main.js +541 -0
- package/lib/web/main.js.map +1 -0
- package/package.json +83 -0
- package/src/chat/agent.ts +240 -0
- package/src/chat/audit.ts +377 -0
- package/src/chat/converters.test.ts +325 -0
- package/src/chat/converters.ts +459 -0
- package/src/chat/handler.test.ts +137 -0
- package/src/chat/handler.ts +1233 -0
- package/src/chat/index.ts +16 -0
- package/src/chat/types.ts +72 -0
- package/src/contracts/AuditContract.ts +93 -0
- package/src/contracts/McpsContract.ts +141 -0
- package/src/contracts/index.ts +18 -0
- package/src/dev.server.ts +7 -0
- package/src/entities/ChatRequestEntity.ts +157 -0
- package/src/entities/McpRequestEntity.ts +149 -0
- package/src/entities/RequestLogEntity.ts +78 -0
- package/src/entities/ResponseEntity.ts +75 -0
- package/src/entities/index.ts +12 -0
- package/src/entities/types.ts +188 -0
- package/src/index.ts +1 -0
- package/src/mcps-cli.ts +59 -0
- package/src/providers/McpServerHandlerDef.ts +105 -0
- package/src/providers/findMcpServerDef.ts +31 -0
- package/src/providers/prometheus/def.ts +21 -0
- package/src/providers/prometheus/index.ts +1 -0
- package/src/providers/relay/def.ts +31 -0
- package/src/providers/relay/index.ts +1 -0
- package/src/providers/relay/relay.test.ts +47 -0
- package/src/providers/sql/def.ts +33 -0
- package/src/providers/sql/index.ts +1 -0
- package/src/providers/tencent-cls/def.ts +38 -0
- package/src/providers/tencent-cls/index.ts +1 -0
- package/src/scripts/bundle.ts +82 -0
- package/src/server/api-routes.ts +98 -0
- package/src/server/audit.ts +310 -0
- package/src/server/chat-routes.ts +95 -0
- package/src/server/config.test.ts +162 -0
- package/src/server/config.ts +198 -0
- package/src/server/db.ts +115 -0
- package/src/server/index.ts +1 -0
- package/src/server/mcp-handler.ts +209 -0
- package/src/server/mcp-routes.ts +133 -0
- package/src/server/mcps-router.ts +133 -0
- package/src/server/schema.ts +175 -0
- package/src/server/server.ts +163 -0
- package/src/web/ChatPage.tsx +1005 -0
- package/src/web/McpInspectorPage.tsx +254 -0
- package/src/web/ServersPage.tsx +139 -0
- package/src/web/main.tsx +600 -0
- package/src/web/styles.css +15 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/web/ChatPage.tsx"],"sourcesContent":["'use client';\n\nimport { Combobox } from '@base-ui/react/combobox';\nimport { cjk } from '@streamdown/cjk';\nimport { code } from '@streamdown/code';\nimport { math } from '@streamdown/math';\nimport {\n\tBrain,\n\tCheck,\n\tChevronDown,\n\tChevronUp,\n\tClock,\n\tCopy,\n\tEdit2,\n\tImagePlus,\n\tMenu,\n\tMessageSquarePlus,\n\tRefreshCw,\n\tSend,\n\tSettings,\n\tSquare,\n\tTrash2,\n\tWrench,\n\tX,\n\tXCircle,\n\tZap,\n} from 'lucide-react';\nimport { useCallback, useEffect, useRef, useState, memo, type KeyboardEvent } from 'react';\nimport { Streamdown } from 'streamdown';\n\n// Types\ninterface ToolCall {\n\tid: string;\n\tname: string;\n\targuments: Record<string, unknown>;\n\tresult?: unknown;\n\terror?: string;\n\tstatus: 'pending' | 'running' | 'completed' | 'error';\n}\n\ninterface ImageContent {\n\ttype: 'image';\n\turl: string;\n\tbase64?: string;\n}\n\ninterface Message {\n\tid: string;\n\trole: 'user' | 'assistant' | 'system';\n\tcontent: string;\n\timages?: ImageContent[];\n\treasoning?: string;\n\ttoolCalls?: ToolCall[];\n\tcreatedAt?: Date;\n\terror?: string;\n\tusage?: {\n\t\tpromptTokens?: number;\n\t\tcompletionTokens?: number;\n\t\ttotalTokens?: number;\n\t};\n\tdurationMs?: number;\n}\n\ninterface ChatSession {\n\tid: string;\n\ttitle: string;\n\tmodel: string;\n\tmessages: Message[];\n\tcreatedAt: Date;\n\tupdatedAt: Date;\n}\n\ninterface ModelItem {\n\tid: string;\n\tvalue: string;\n\tadapter?: string;\n\tbaseUrl?: string;\n}\n\ninterface McpServer {\n\tname: string;\n\ttype: string;\n}\n\ninterface ChatSettings {\n\ttemperature: number;\n\ttopP: number;\n\ttopK: number;\n\tmaxTokens: number;\n\tmcpServers: string[];\n}\n\n// Streamdown Markdown component\nconst MarkdownContent = memo(\n\t({ children, className }: { children: string; className?: string }) => (\n\t\t<Streamdown className={className} plugins={{ code, math, cjk }}>\n\t\t\t{children}\n\t\t</Streamdown>\n\t),\n\t(prev, next) => prev.children === next.children,\n);\nMarkdownContent.displayName = 'MarkdownContent';\n\n// Tool Call Display\nfunction ToolCallDisplay({ toolCall }: { toolCall: ToolCall }) {\n\tconst [isOpen, setIsOpen] = useState(false);\n\n\treturn (\n\t\t<div className='border border-base-300 rounded-lg my-2 overflow-hidden bg-base-100'>\n\t\t\t<button\n\t\t\t\ttype='button'\n\t\t\t\tclassName='w-full flex items-center justify-between p-2 hover:bg-base-200'\n\t\t\t\tonClick={() => setIsOpen(!isOpen)}\n\t\t\t>\n\t\t\t\t<div className='flex items-center gap-2'>\n\t\t\t\t\t{toolCall.status === 'completed' ? (\n\t\t\t\t\t\t<Check className='w-3.5 h-3.5 text-success' />\n\t\t\t\t\t) : toolCall.status === 'error' ? (\n\t\t\t\t\t\t<XCircle className='w-3.5 h-3.5 text-error' />\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<Clock className='w-3.5 h-3.5 text-warning animate-pulse' />\n\t\t\t\t\t)}\n\t\t\t\t\t<Wrench className='w-3.5 h-3.5 text-base-content/60' />\n\t\t\t\t\t<span className='font-mono text-sm'>{toolCall.name}</span>\n\t\t\t\t</div>\n\t\t\t\t{isOpen ? <ChevronUp className='w-4 h-4' /> : <ChevronDown className='w-4 h-4' />}\n\t\t\t</button>\n\t\t\t{isOpen && (\n\t\t\t\t<div className='p-2 space-y-2 text-xs border-t border-base-300'>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<div className='font-semibold text-base-content/70 mb-1'>Arguments:</div>\n\t\t\t\t\t\t<pre className='bg-base-200 p-2 rounded overflow-auto max-h-32'>\n\t\t\t\t\t\t\t{JSON.stringify(toolCall.arguments, null, 2)}\n\t\t\t\t\t\t</pre>\n\t\t\t\t\t</div>\n\t\t\t\t\t{toolCall.result !== undefined && (\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<div className='font-semibold text-base-content/70 mb-1'>Result:</div>\n\t\t\t\t\t\t\t<pre className='bg-base-200 p-2 rounded overflow-auto max-h-32'>\n\t\t\t\t\t\t\t\t{typeof toolCall.result === 'string' ? toolCall.result : JSON.stringify(toolCall.result, null, 2)}\n\t\t\t\t\t\t\t</pre>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{toolCall.error && (\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<div className='font-semibold text-error mb-1'>Error:</div>\n\t\t\t\t\t\t\t<pre className='bg-error/10 text-error p-2 rounded'>{toolCall.error}</pre>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// Reasoning/Thinking Display\nfunction ReasoningDisplay({ content, isStreaming }: { content: string; isStreaming?: boolean }) {\n\tconst [isOpen, setIsOpen] = useState(true);\n\n\treturn (\n\t\t<div className='border border-base-300 rounded-lg my-2 overflow-hidden bg-base-100'>\n\t\t\t<button\n\t\t\t\ttype='button'\n\t\t\t\tclassName='w-full flex items-center justify-between p-2 hover:bg-base-200'\n\t\t\t\tonClick={() => setIsOpen(!isOpen)}\n\t\t\t>\n\t\t\t\t<div className='flex items-center gap-2 text-base-content/70'>\n\t\t\t\t\t<Brain className={`w-4 h-4 ${isStreaming ? 'animate-pulse' : ''}`} />\n\t\t\t\t\t<span className='text-sm'>{isStreaming ? 'Thinking...' : 'Reasoning'}</span>\n\t\t\t\t</div>\n\t\t\t\t{isOpen ? <ChevronUp className='w-4 h-4' /> : <ChevronDown className='w-4 h-4' />}\n\t\t\t</button>\n\t\t\t{isOpen && (\n\t\t\t\t<div className='p-3 text-sm text-base-content/80 prose prose-sm max-w-none border-t border-base-300'>\n\t\t\t\t\t<MarkdownContent>{content || '...'}</MarkdownContent>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// Helper functions\nfunction generateSessionId() {\n\treturn `chat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction generateTitle(messages: Message[]): string {\n\tconst firstUserMessage = messages.find((m) => m.role === 'user');\n\tif (firstUserMessage) {\n\t\tconst content = firstUserMessage.content.slice(0, 50);\n\t\treturn content.length < firstUserMessage.content.length ? `${content}...` : content;\n\t}\n\treturn 'New Chat';\n}\n\nfunction loadSessions(): ChatSession[] {\n\ttry {\n\t\tconst data = localStorage.getItem('mcps-chat-sessions');\n\t\tif (data) return JSON.parse(data);\n\t} catch (e) {\n\t\tconsole.error('Failed to load sessions:', e);\n\t}\n\treturn [];\n}\n\nfunction saveSessions(sessions: ChatSession[]) {\n\ttry {\n\t\tlocalStorage.setItem('mcps-chat-sessions', JSON.stringify(sessions));\n\t} catch (e) {\n\t\tconsole.error('Failed to save sessions:', e);\n\t}\n}\n\nfunction loadSettings(): ChatSettings {\n\ttry {\n\t\tconst data = localStorage.getItem('mcps-chat-settings');\n\t\tif (data) return { ...defaultSettings, ...JSON.parse(data) };\n\t} catch (e) {\n\t\tconsole.error('Failed to load settings:', e);\n\t}\n\treturn defaultSettings;\n}\n\nfunction saveSettings(settings: ChatSettings) {\n\ttry {\n\t\tlocalStorage.setItem('mcps-chat-settings', JSON.stringify(settings));\n\t} catch (e) {\n\t\tconsole.error('Failed to save settings:', e);\n\t}\n}\n\nconst defaultSettings: ChatSettings = {\n\ttemperature: 0.7,\n\ttopP: 1.0,\n\ttopK: 40,\n\tmaxTokens: 4096,\n\tmcpServers: [],\n};\n\nexport function ChatPage() {\n\tconst [models, setModels] = useState<ModelItem[]>([]);\n\tconst [selectedModel, setSelectedModel] = useState<string>('');\n\tconst [input, setInput] = useState('');\n\tconst [images, setImages] = useState<ImageContent[]>([]);\n\tconst [isLoading, setIsLoading] = useState(false);\n\tconst [sessions, setSessions] = useState<ChatSession[]>(() => loadSessions());\n\tconst [currentSessionId, setCurrentSessionId] = useState<string | null>(null);\n\tconst [showSidebar, setShowSidebar] = useState(true);\n\tconst [showSettings, setShowSettings] = useState(false);\n\tconst [editingMessageId, setEditingMessageId] = useState<string | null>(null);\n\tconst [editContent, setEditContent] = useState('');\n\tconst [mcpServers, setMcpServers] = useState<McpServer[]>([]);\n\tconst [settings, setSettings] = useState<ChatSettings>(() => loadSettings());\n\tconst [inputHistory, setInputHistory] = useState<string[]>([]);\n\tconst [historyIndex, setHistoryIndex] = useState(-1);\n\n\tconst messagesEndRef = useRef<HTMLDivElement>(null);\n\tconst abortControllerRef = useRef<AbortController | null>(null);\n\tconst fileInputRef = useRef<HTMLInputElement>(null);\n\tconst inputRef = useRef<HTMLInputElement>(null);\n\n\tconst currentSession = sessions.find((s) => s.id === currentSessionId);\n\tconst messages = currentSession?.messages || [];\n\n\tuseEffect(() => {\n\t\tsaveSessions(sessions);\n\t}, [sessions]);\n\n\tuseEffect(() => {\n\t\tsaveSettings(settings);\n\t}, [settings]);\n\n\tuseEffect(() => {\n\t\tmessagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n\t}, [messages]);\n\n\tuseEffect(() => {\n\t\tfetch('/api/mcps/models')\n\t\t\t.then((res) => res.json())\n\t\t\t.then((data) => {\n\t\t\t\tconst modelList = (data.models || []) as { name: string; adapter?: string; baseUrl?: string }[];\n\t\t\t\tconst validModels: ModelItem[] = modelList\n\t\t\t\t\t.filter((m) => !m.name.includes('*'))\n\t\t\t\t\t.map((m) => ({\n\t\t\t\t\t\tid: m.name,\n\t\t\t\t\t\tvalue: m.name,\n\t\t\t\t\t\tadapter: m.adapter || undefined,\n\t\t\t\t\t\tbaseUrl: m.baseUrl || undefined,\n\t\t\t\t\t}));\n\t\t\t\tsetModels(validModels);\n\t\t\t\tif (validModels.length > 0 && !selectedModel) {\n\t\t\t\t\tsetSelectedModel(validModels[0].value);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(console.error);\n\n\t\tfetch('/api/mcps/servers')\n\t\t\t.then((res) => res.json())\n\t\t\t.then((data) => {\n\t\t\t\tsetMcpServers(data.servers || []);\n\t\t\t})\n\t\t\t.catch(console.error);\n\t}, []);\n\n\tconst updateSession = useCallback((sessionId: string, updater: (s: ChatSession) => ChatSession) => {\n\t\tsetSessions((prev) => prev.map((s) => (s.id === sessionId ? updater(s) : s)));\n\t}, []);\n\n\tconst sendMessage = async (\n\t\tcontent: string,\n\t\tsessionId: string,\n\t\texistingMessages: Message[],\n\t\tmsgImages?: ImageContent[],\n\t) => {\n\t\tconst startTime = Date.now();\n\t\tconst userMessage: Message = {\n\t\t\tid: `user-${Date.now()}`,\n\t\t\trole: 'user',\n\t\t\tcontent,\n\t\t\timages: msgImages,\n\t\t\tcreatedAt: new Date(),\n\t\t};\n\n\t\tconst assistantMessage: Message = {\n\t\t\tid: `assistant-${Date.now()}`,\n\t\t\trole: 'assistant',\n\t\t\tcontent: '',\n\t\t\tcreatedAt: new Date(),\n\t\t};\n\n\t\tupdateSession(sessionId, (s) => ({\n\t\t\t...s,\n\t\t\tmessages: [...existingMessages, userMessage, assistantMessage],\n\t\t\ttitle: generateTitle([...existingMessages, userMessage]),\n\t\t\tupdatedAt: new Date(),\n\t\t}));\n\n\t\tsetIsLoading(true);\n\t\tabortControllerRef.current = new AbortController();\n\n\t\ttry {\n\t\t\t// Build messages with image support\n\t\t\tconst apiMessages = [...existingMessages, userMessage].map((m) => {\n\t\t\t\tif (m.images && m.images.length > 0) {\n\t\t\t\t\t// Multi-modal message\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: m.role,\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{ type: 'text', text: m.content },\n\t\t\t\t\t\t\t...m.images.map((img) => ({\n\t\t\t\t\t\t\t\ttype: 'image_url',\n\t\t\t\t\t\t\t\timage_url: { url: img.base64 || img.url },\n\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t],\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn { role: m.role, content: m.content };\n\t\t\t});\n\n\t\t\t// Always use agent endpoint for tool support\n\t\t\tconst requestBody: Record<string, unknown> = {\n\t\t\t\tmodel: selectedModel,\n\t\t\t\tmessages: apiMessages,\n\t\t\t\tstream: true,\n\t\t\t\ttemperature: settings.temperature,\n\t\t\t\ttop_p: settings.topP,\n\t\t\t\tmax_tokens: settings.maxTokens,\n\t\t\t};\n\n\t\t\tif (settings.mcpServers.length > 0) {\n\t\t\t\trequestBody.mcpServers = settings.mcpServers;\n\t\t\t}\n\n\t\t\tconst response = await fetch('/v1/agent/chat', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify(requestBody),\n\t\t\t\tsignal: abortControllerRef.current.signal,\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorData = await response.json().catch(() => ({}));\n\t\t\t\tthrow new Error(errorData.error?.message || `HTTP ${response.status}`);\n\t\t\t}\n\n\t\t\tconst reader = response.body?.getReader();\n\t\t\tif (!reader) throw new Error('No response body');\n\n\t\t\tconst decoder = new TextDecoder();\n\t\t\tlet buffer = '';\n\t\t\tlet fullContent = '';\n\t\t\tlet reasoning = '';\n\t\t\tlet usage: Message['usage'] | undefined;\n\n\t\t\twhile (true) {\n\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\tif (done) break;\n\n\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t\tconst lines = buffer.split('\\n');\n\t\t\t\tbuffer = lines.pop() || '';\n\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\tif (!line.trim() || !line.startsWith('data: ')) continue;\n\t\t\t\t\tconst data = line.slice(6);\n\t\t\t\t\tif (data === '[DONE]') continue;\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst parsed = JSON.parse(data);\n\n\t\t\t\t\t\t// Handle agent streaming format\n\t\t\t\t\t\tif (parsed.type === 'text') {\n\t\t\t\t\t\t\tfullContent += parsed.content || '';\n\t\t\t\t\t\t} else if (parsed.type === 'usage') {\n\t\t\t\t\t\t\tusage = {\n\t\t\t\t\t\t\t\tpromptTokens: parsed.usage?.promptTokens,\n\t\t\t\t\t\t\t\tcompletionTokens: parsed.usage?.completionTokens,\n\t\t\t\t\t\t\t\ttotalTokens: parsed.usage?.totalTokens,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t} else if (parsed.type === 'step') {\n\t\t\t\t\t\t\t// Handle step with reasoning\n\t\t\t\t\t\t\tif (parsed.text) fullContent = parsed.text;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Handle OpenAI format\n\t\t\t\t\t\tconst delta = parsed.choices?.[0]?.delta;\n\t\t\t\t\t\tif (delta?.content) {\n\t\t\t\t\t\t\tfullContent += delta.content;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (delta?.reasoning_content) {\n\t\t\t\t\t\t\treasoning += delta.reasoning_content;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (parsed.usage) {\n\t\t\t\t\t\t\tusage = {\n\t\t\t\t\t\t\t\tpromptTokens: parsed.usage.prompt_tokens,\n\t\t\t\t\t\t\t\tcompletionTokens: parsed.usage.completion_tokens,\n\t\t\t\t\t\t\t\ttotalTokens: parsed.usage.total_tokens,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tupdateSession(sessionId, (s) => ({\n\t\t\t\t\t\t\t...s,\n\t\t\t\t\t\t\tmessages: s.messages.map((m) =>\n\t\t\t\t\t\t\t\tm.id === assistantMessage.id\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t...m,\n\t\t\t\t\t\t\t\t\t\t\tcontent: fullContent,\n\t\t\t\t\t\t\t\t\t\t\treasoning: reasoning || undefined,\n\t\t\t\t\t\t\t\t\t\t\tusage,\n\t\t\t\t\t\t\t\t\t\t\tdurationMs: Date.now() - startTime,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: m,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t}));\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Skip invalid JSON\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tupdateSession(sessionId, (s) => ({\n\t\t\t\t...s,\n\t\t\t\tmessages: s.messages.map((m) =>\n\t\t\t\t\tm.id === assistantMessage.id ? { ...m, durationMs: Date.now() - startTime } : m,\n\t\t\t\t),\n\t\t\t}));\n\t\t} catch (err) {\n\t\t\tif ((err as Error).name === 'AbortError') return;\n\t\t\tconst errorMsg = err instanceof Error ? err.message : 'Failed to send message';\n\t\t\tupdateSession(sessionId, (s) => ({\n\t\t\t\t...s,\n\t\t\t\tmessages: s.messages.map((m) => (m.id === assistantMessage.id ? { ...m, content: '', error: errorMsg } : m)),\n\t\t\t}));\n\t\t} finally {\n\t\t\tsetIsLoading(false);\n\t\t\tabortControllerRef.current = null;\n\t\t}\n\t};\n\n\tconst handleSubmit = async (e: React.FormEvent) => {\n\t\te.preventDefault();\n\t\tif (!input.trim() || !selectedModel || isLoading) return;\n\n\t\t// Add to history\n\t\tsetInputHistory((prev) => [input.trim(), ...prev.slice(0, 49)]);\n\t\tsetHistoryIndex(-1);\n\n\t\tlet sessionId = currentSessionId;\n\t\tlet existingMessages = messages;\n\n\t\tif (!sessionId) {\n\t\t\tconst newSession: ChatSession = {\n\t\t\t\tid: generateSessionId(),\n\t\t\t\ttitle: 'New Chat',\n\t\t\t\tmodel: selectedModel,\n\t\t\t\tmessages: [],\n\t\t\t\tcreatedAt: new Date(),\n\t\t\t\tupdatedAt: new Date(),\n\t\t\t};\n\t\t\tsetSessions((prev) => [newSession, ...prev]);\n\t\t\tsetCurrentSessionId(newSession.id);\n\t\t\tsessionId = newSession.id;\n\t\t\texistingMessages = [];\n\t\t}\n\n\t\tconst content = input.trim();\n\t\tconst msgImages = images.length > 0 ? [...images] : undefined;\n\t\tsetInput('');\n\t\tsetImages([]);\n\t\tawait sendMessage(content, sessionId, existingMessages, msgImages);\n\t};\n\n\tconst handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n\t\tif (e.key === 'ArrowUp' && !input && inputHistory.length > 0) {\n\t\t\te.preventDefault();\n\t\t\tconst newIndex = Math.min(historyIndex + 1, inputHistory.length - 1);\n\t\t\tsetHistoryIndex(newIndex);\n\t\t\tsetInput(inputHistory[newIndex]);\n\t\t} else if (e.key === 'ArrowDown' && historyIndex >= 0) {\n\t\t\te.preventDefault();\n\t\t\tconst newIndex = historyIndex - 1;\n\t\t\tsetHistoryIndex(newIndex);\n\t\t\tsetInput(newIndex >= 0 ? inputHistory[newIndex] : '');\n\t\t}\n\t};\n\n\tconst handleRetry = async (messageId: string) => {\n\t\tif (!currentSessionId || isLoading) return;\n\t\tconst msgIndex = messages.findIndex((m) => m.id === messageId);\n\t\tif (msgIndex === -1) return;\n\t\tconst message = messages[msgIndex];\n\t\tif (message.role !== 'assistant') return;\n\t\tconst userMsgIndex = msgIndex - 1;\n\t\tif (userMsgIndex < 0) return;\n\t\tconst userMessage = messages[userMsgIndex];\n\t\tif (userMessage.role !== 'user') return;\n\t\tconst existingMessages = messages.slice(0, userMsgIndex);\n\t\tupdateSession(currentSessionId, (s) => ({ ...s, messages: existingMessages }));\n\t\tawait sendMessage(userMessage.content, currentSessionId, existingMessages, userMessage.images);\n\t};\n\n\tconst handleEditMessage = (messageId: string) => {\n\t\tconst message = messages.find((m) => m.id === messageId);\n\t\tif (!message || message.role !== 'user') return;\n\t\tsetEditingMessageId(messageId);\n\t\tsetEditContent(message.content);\n\t};\n\n\tconst handleSaveEdit = async () => {\n\t\tif (!editingMessageId || !currentSessionId || !editContent.trim() || isLoading) return;\n\t\tconst msgIndex = messages.findIndex((m) => m.id === editingMessageId);\n\t\tif (msgIndex === -1) return;\n\t\tconst existingMessages = messages.slice(0, msgIndex);\n\t\tsetEditingMessageId(null);\n\t\tsetEditContent('');\n\t\tupdateSession(currentSessionId, (s) => ({ ...s, messages: existingMessages }));\n\t\tawait sendMessage(editContent.trim(), currentSessionId, existingMessages);\n\t};\n\n\tconst handleCancelEdit = () => {\n\t\tsetEditingMessageId(null);\n\t\tsetEditContent('');\n\t};\n\n\tconst handleStop = () => {\n\t\tabortControllerRef.current?.abort();\n\t};\n\n\tconst handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {\n\t\tconst files = e.target.files;\n\t\tif (!files) return;\n\t\tfor (const file of files) {\n\t\t\tconst reader = new FileReader();\n\t\t\treader.onload = (ev) => {\n\t\t\t\tconst base64 = ev.target?.result as string;\n\t\t\t\tsetImages((prev) => [...prev, { type: 'image', url: file.name, base64 }]);\n\t\t\t};\n\t\t\treader.readAsDataURL(file);\n\t\t}\n\t\tif (fileInputRef.current) fileInputRef.current.value = '';\n\t};\n\n\tconst removeImage = (index: number) => {\n\t\tsetImages((prev) => prev.filter((_, i) => i !== index));\n\t};\n\n\tconst copyToClipboard = (text: string) => {\n\t\tnavigator.clipboard.writeText(text);\n\t};\n\n\tconst createSession = useCallback(() => {\n\t\tconst newSession: ChatSession = {\n\t\t\tid: generateSessionId(),\n\t\t\ttitle: 'New Chat',\n\t\t\tmodel: selectedModel,\n\t\t\tmessages: [],\n\t\t\tcreatedAt: new Date(),\n\t\t\tupdatedAt: new Date(),\n\t\t};\n\t\tsetSessions((prev) => [newSession, ...prev]);\n\t\tsetCurrentSessionId(newSession.id);\n\t}, [selectedModel]);\n\n\tconst deleteSession = useCallback(\n\t\t(sessionId: string) => {\n\t\t\tsetSessions((prev) => prev.filter((s) => s.id !== sessionId));\n\t\t\tif (currentSessionId === sessionId) setCurrentSessionId(null);\n\t\t},\n\t\t[currentSessionId],\n\t);\n\n\treturn (\n\t\t<div className='flex h-full min-h-[600px]'>\n\t\t\t{/* Sessions Sidebar */}\n\t\t\t{showSidebar && (\n\t\t\t\t<div className='w-56 border-r border-base-300 bg-base-100 flex flex-col flex-shrink-0'>\n\t\t\t\t\t<div className='p-2 border-b border-base-300'>\n\t\t\t\t\t\t<button type='button' className='btn btn-primary btn-sm w-full gap-1' onClick={createSession}>\n\t\t\t\t\t\t\t<MessageSquarePlus className='w-4 h-4' /> New Chat\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className='flex-1 overflow-y-auto'>\n\t\t\t\t\t\t{sessions.length === 0 ? (\n\t\t\t\t\t\t\t<div className='p-4 text-center text-base-content/50 text-sm'>No chat history</div>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<ul className='menu p-1 gap-0.5'>\n\t\t\t\t\t\t\t\t{sessions.map((session) => (\n\t\t\t\t\t\t\t\t\t<li key={session.id}>\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\t\t\t\t\tclassName={`flex justify-between items-center w-full text-left py-2 px-2 ${currentSessionId === session.id ? 'active' : ''}`}\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => setCurrentSessionId(session.id)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span className='flex-1 truncate text-xs'>{session.title}</span>\n\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\t\t\t\t\t\tclassName='btn btn-ghost btn-xs opacity-50 hover:opacity-100'\n\t\t\t\t\t\t\t\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\t\t\t\t\t\t\tdeleteSession(session.id);\n\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<Trash2 className='w-3 h-3' />\n\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Main Chat Area */}\n\t\t\t<div className='flex-1 flex flex-col min-w-0'>\n\t\t\t\t{/* Header */}\n\t\t\t\t<div className='h-12 px-3 border-b border-base-300 bg-base-100 flex items-center gap-2 flex-shrink-0'>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\tclassName='btn btn-ghost btn-sm btn-square'\n\t\t\t\t\t\tonClick={() => setShowSidebar(!showSidebar)}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Menu className='w-4 h-4' />\n\t\t\t\t\t</button>\n\n\t\t\t\t\t{/* Model Selector */}\n\t\t\t\t\t<div className='flex-1 max-w-xs'>\n\t\t\t\t\t\t<Combobox.Root\n\t\t\t\t\t\t\titems={models}\n\t\t\t\t\t\t\titemToStringValue={(item: ModelItem) => item.value}\n\t\t\t\t\t\t\tvalue={models.find((m) => m.value === selectedModel) || null}\n\t\t\t\t\t\t\tonValueChange={(item: ModelItem | null) => {\n\t\t\t\t\t\t\t\tif (item) setSelectedModel(item.value);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonInputValueChange={(value) => {\n\t\t\t\t\t\t\t\tif (value) setSelectedModel(value);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Combobox.Input placeholder='Select model...' className='input input-bordered input-sm w-full' />\n\t\t\t\t\t\t\t<Combobox.Portal>\n\t\t\t\t\t\t\t\t<Combobox.Positioner sideOffset={4}>\n\t\t\t\t\t\t\t\t\t<Combobox.Popup className='bg-base-100 rounded-box shadow-lg border border-base-300 max-h-60 overflow-auto z-50'>\n\t\t\t\t\t\t\t\t\t\t<Combobox.Empty className='p-2 text-sm text-base-content/50'>No models</Combobox.Empty>\n\t\t\t\t\t\t\t\t\t\t<Combobox.List className='p-1'>\n\t\t\t\t\t\t\t\t\t\t\t{(item: ModelItem) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<Combobox.Item\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={item.id}\n\t\t\t\t\t\t\t\t\t\t\t\t\tvalue={item}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName='p-2 rounded cursor-pointer hover:bg-base-200 data-[highlighted]:bg-base-200 text-sm'\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{item.value}\n\t\t\t\t\t\t\t\t\t\t\t\t</Combobox.Item>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</Combobox.List>\n\t\t\t\t\t\t\t\t\t</Combobox.Popup>\n\t\t\t\t\t\t\t\t</Combobox.Positioner>\n\t\t\t\t\t\t\t</Combobox.Portal>\n\t\t\t\t\t\t</Combobox.Root>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className='flex-1' />\n\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\tclassName={`btn btn-ghost btn-sm btn-square ${showSettings ? 'btn-active' : ''}`}\n\t\t\t\t\t\tonClick={() => setShowSettings(!showSettings)}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Settings className='w-4 h-4' />\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\n\t\t\t\t<div className='flex-1 flex overflow-hidden'>\n\t\t\t\t\t{/* Messages Area */}\n\t\t\t\t\t<div className='flex-1 overflow-y-auto p-4'>\n\t\t\t\t\t\t{messages.length === 0 && (\n\t\t\t\t\t\t\t<div className='text-center text-base-content/50 py-8'>\n\t\t\t\t\t\t\t\t<p>Start a conversation by sending a message.</p>\n\t\t\t\t\t\t\t\t{selectedModel && <p className='text-sm mt-2 opacity-70'>Model: {selectedModel}</p>}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t<div className='space-y-6 max-w-3xl mx-auto'>\n\t\t\t\t\t\t\t{messages.map((message) => (\n\t\t\t\t\t\t\t\t<div key={message.id}>\n\t\t\t\t\t\t\t\t\t{message.role === 'user' ? (\n\t\t\t\t\t\t\t\t\t\t// User message\n\t\t\t\t\t\t\t\t\t\t<div className='flex justify-end'>\n\t\t\t\t\t\t\t\t\t\t\t<div className='max-w-[80%]'>\n\t\t\t\t\t\t\t\t\t\t\t\t{editingMessageId === message.id ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div className='bg-base-200 rounded-lg p-3'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<textarea\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName='textarea textarea-bordered w-full min-w-64'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tvalue={editContent}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => setEditContent(e.target.value)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className='flex gap-2 mt-2'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<button type='button' className='btn btn-primary btn-xs' onClick={handleSaveEdit}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tSave & Send\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<button type='button' className='btn btn-ghost btn-xs' onClick={handleCancelEdit}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className='bg-primary text-primary-content rounded-2xl rounded-br-md px-4 py-2'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{message.images && message.images.length > 0 && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className='flex flex-wrap gap-2 mb-2'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{message.images.map((img, i) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsrc={img.base64 || img.url}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\talt='uploaded'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName='max-h-32 rounded'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p className='whitespace-pre-wrap'>{message.content}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className='flex justify-end gap-1 mt-1'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName='btn btn-ghost btn-xs opacity-50 hover:opacity-100'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => handleEditMessage(message.id)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Edit2 className='w-3 h-3' />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t// Assistant message - flat display\n\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t{message.error ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<div className='text-error flex items-center gap-2'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<XCircle className='w-4 h-4' />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span>Error: {message.error}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName='btn btn-ghost btn-xs'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => handleRetry(message.id)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<RefreshCw className='w-3 h-3' />\n\t\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{message.reasoning && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<ReasoningDisplay content={message.reasoning} isStreaming={isLoading} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t{message.toolCalls?.map((tc) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<ToolCallDisplay key={tc.id} toolCall={tc} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div className='prose prose-sm max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<MarkdownContent>{message.content || (isLoading ? '...' : '')}</MarkdownContent>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t{/* Actions and metadata */}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div className='flex items-center gap-3 mt-2 text-xs text-base-content/50'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{message.usage && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className='flex items-center gap-1'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Zap className='w-3 h-3' />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{message.usage.totalTokens} tokens\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{message.usage.promptTokens != null && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className='opacity-70'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t({message.usage.promptTokens}/{message.usage.completionTokens})\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{message.durationMs && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className='flex items-center gap-1'>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Clock className='w-3 h-3' />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{(message.durationMs / 1000).toFixed(1)}s\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{message.usage?.completionTokens && message.durationMs && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{Math.round((message.usage.completionTokens / message.durationMs) * 1000)} tok/s\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName='btn btn-ghost btn-xs opacity-50 hover:opacity-100'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => copyToClipboard(message.content)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Copy className='w-3 h-3' />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName='btn btn-ghost btn-xs opacity-50 hover:opacity-100'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => handleRetry(message.id)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<RefreshCw className='w-3 h-3' />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div ref={messagesEndRef} />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Settings Panel */}\n\t\t\t\t\t{showSettings && (\n\t\t\t\t\t\t<div className='w-64 border-l border-base-300 bg-base-100 p-4 overflow-y-auto flex-shrink-0'>\n\t\t\t\t\t\t\t<h3 className='font-semibold mb-4'>Settings</h3>\n\n\t\t\t\t\t\t\t<div className='space-y-4'>\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<label className='text-xs font-medium'>Temperature: {settings.temperature}</label>\n\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\ttype='range'\n\t\t\t\t\t\t\t\t\t\tmin='0'\n\t\t\t\t\t\t\t\t\t\tmax='2'\n\t\t\t\t\t\t\t\t\t\tstep='0.1'\n\t\t\t\t\t\t\t\t\t\tvalue={settings.temperature}\n\t\t\t\t\t\t\t\t\t\tonChange={(e) => setSettings((s) => ({ ...s, temperature: parseFloat(e.target.value) }))}\n\t\t\t\t\t\t\t\t\t\tclassName='range range-xs range-primary w-full'\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<label className='text-xs font-medium'>Top P: {settings.topP}</label>\n\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\ttype='range'\n\t\t\t\t\t\t\t\t\t\tmin='0'\n\t\t\t\t\t\t\t\t\t\tmax='1'\n\t\t\t\t\t\t\t\t\t\tstep='0.05'\n\t\t\t\t\t\t\t\t\t\tvalue={settings.topP}\n\t\t\t\t\t\t\t\t\t\tonChange={(e) => setSettings((s) => ({ ...s, topP: parseFloat(e.target.value) }))}\n\t\t\t\t\t\t\t\t\t\tclassName='range range-xs range-primary w-full'\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<label className='text-xs font-medium'>Top K: {settings.topK}</label>\n\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\ttype='range'\n\t\t\t\t\t\t\t\t\t\tmin='1'\n\t\t\t\t\t\t\t\t\t\tmax='100'\n\t\t\t\t\t\t\t\t\t\tstep='1'\n\t\t\t\t\t\t\t\t\t\tvalue={settings.topK}\n\t\t\t\t\t\t\t\t\t\tonChange={(e) => setSettings((s) => ({ ...s, topK: parseInt(e.target.value, 10) }))}\n\t\t\t\t\t\t\t\t\t\tclassName='range range-xs range-primary w-full'\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<label className='text-xs font-medium'>Max Tokens: {settings.maxTokens}</label>\n\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\ttype='range'\n\t\t\t\t\t\t\t\t\t\tmin='256'\n\t\t\t\t\t\t\t\t\t\tmax='16384'\n\t\t\t\t\t\t\t\t\t\tstep='256'\n\t\t\t\t\t\t\t\t\t\tvalue={settings.maxTokens}\n\t\t\t\t\t\t\t\t\t\tonChange={(e) => setSettings((s) => ({ ...s, maxTokens: parseInt(e.target.value, 10) }))}\n\t\t\t\t\t\t\t\t\t\tclassName='range range-xs range-primary w-full'\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t<div className='divider text-xs'>MCP Servers</div>\n\n\t\t\t\t\t\t\t\t{mcpServers.length === 0 ? (\n\t\t\t\t\t\t\t\t\t<p className='text-xs text-base-content/50'>No servers configured</p>\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t<div className='space-y-2'>\n\t\t\t\t\t\t\t\t\t\t{mcpServers.map((server) => (\n\t\t\t\t\t\t\t\t\t\t\t<label key={server.name} className='flex items-center gap-2 cursor-pointer'>\n\t\t\t\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype='checkbox'\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName='checkbox checkbox-xs checkbox-primary'\n\t\t\t\t\t\t\t\t\t\t\t\t\tchecked={settings.mcpServers.includes(server.name)}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetSettings((s) => ({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t...s,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmcpServers: e.target.checked\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? [...s.mcpServers, server.name]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: s.mcpServers.filter((n) => n !== server.name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<span className='text-xs'>{server.name}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t<span className='text-xs text-base-content/50'>({server.type})</span>\n\t\t\t\t\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Input Area */}\n\t\t\t\t<div className='p-3 border-t border-base-300 bg-base-100 flex-shrink-0'>\n\t\t\t\t\t{/* Image Previews */}\n\t\t\t\t\t{images.length > 0 && (\n\t\t\t\t\t\t<div className='flex flex-wrap gap-2 mb-2'>\n\t\t\t\t\t\t\t{images.map((img, i) => (\n\t\t\t\t\t\t\t\t<div key={i} className='relative'>\n\t\t\t\t\t\t\t\t\t<img src={img.base64 || img.url} alt='preview' className='h-16 rounded' />\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\t\t\t\tclassName='btn btn-circle btn-xs absolute -top-1 -right-1 btn-error'\n\t\t\t\t\t\t\t\t\t\tonClick={() => removeImage(i)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<X className='w-3 h-3' />\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\n\t\t\t\t\t<form onSubmit={handleSubmit} className='flex gap-2'>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype='file'\n\t\t\t\t\t\t\tref={fileInputRef}\n\t\t\t\t\t\t\taccept='image/*'\n\t\t\t\t\t\t\tmultiple\n\t\t\t\t\t\t\tclassName='hidden'\n\t\t\t\t\t\t\tonChange={handleImageUpload}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\tclassName='btn btn-ghost btn-sm btn-square'\n\t\t\t\t\t\t\tonClick={() => fileInputRef.current?.click()}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<ImagePlus className='w-4 h-4' />\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={inputRef}\n\t\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\t\tclassName='input input-bordered flex-1 input-sm'\n\t\t\t\t\t\t\tvalue={input}\n\t\t\t\t\t\t\tonChange={(e) => setInput(e.target.value)}\n\t\t\t\t\t\t\tonKeyDown={handleKeyDown}\n\t\t\t\t\t\t\tplaceholder={selectedModel ? 'Type a message... (Up arrow for history)' : 'Select a model first'}\n\t\t\t\t\t\t\tdisabled={!selectedModel || isLoading}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{isLoading ? (\n\t\t\t\t\t\t\t<button type='button' className='btn btn-error btn-sm' onClick={handleStop}>\n\t\t\t\t\t\t\t\t<Square className='w-4 h-4' />\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<button type='submit' className='btn btn-primary btn-sm' disabled={!input.trim() || !selectedModel}>\n\t\t\t\t\t\t\t\t<Send className='w-4 h-4' />\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["Combobox","cjk","code","math","Brain","Check","ChevronDown","ChevronUp","Clock","Copy","Edit2","ImagePlus","Menu","MessageSquarePlus","RefreshCw","Send","Settings","Square","Trash2","Wrench","X","XCircle","Zap","useCallback","useEffect","useRef","useState","memo","Streamdown","MarkdownContent","children","className","plugins","prev","next","displayName","ToolCallDisplay","toolCall","isOpen","setIsOpen","div","button","type","onClick","status","span","name","pre","JSON","stringify","arguments","result","undefined","error","ReasoningDisplay","content","isStreaming","generateSessionId","Date","now","Math","random","toString","slice","generateTitle","messages","firstUserMessage","find","m","role","length","loadSessions","data","localStorage","getItem","parse","e","console","saveSessions","sessions","setItem","loadSettings","defaultSettings","saveSettings","settings","temperature","topP","topK","maxTokens","mcpServers","ChatPage","models","setModels","selectedModel","setSelectedModel","input","setInput","images","setImages","isLoading","setIsLoading","setSessions","currentSessionId","setCurrentSessionId","showSidebar","setShowSidebar","showSettings","setShowSettings","editingMessageId","setEditingMessageId","editContent","setEditContent","setMcpServers","setSettings","inputHistory","setInputHistory","historyIndex","setHistoryIndex","messagesEndRef","abortControllerRef","fileInputRef","inputRef","currentSession","s","id","current","scrollIntoView","behavior","fetch","then","res","json","modelList","validModels","filter","includes","map","value","adapter","baseUrl","catch","servers","updateSession","sessionId","updater","sendMessage","existingMessages","msgImages","startTime","userMessage","createdAt","assistantMessage","title","updatedAt","AbortController","apiMessages","text","img","image_url","url","base64","requestBody","model","stream","top_p","max_tokens","response","method","headers","body","signal","ok","errorData","Error","message","reader","getReader","decoder","TextDecoder","buffer","fullContent","reasoning","usage","done","read","decode","lines","split","pop","line","trim","startsWith","parsed","promptTokens","completionTokens","totalTokens","delta","choices","reasoning_content","prompt_tokens","completion_tokens","total_tokens","durationMs","err","errorMsg","handleSubmit","preventDefault","newSession","handleKeyDown","key","newIndex","min","handleRetry","messageId","msgIndex","findIndex","userMsgIndex","handleEditMessage","handleSaveEdit","handleCancelEdit","handleStop","abort","handleImageUpload","files","target","file","FileReader","onload","ev","readAsDataURL","removeImage","index","_","i","copyToClipboard","navigator","clipboard","writeText","createSession","deleteSession","ul","session","li","stopPropagation","Root","items","itemToStringValue","item","onValueChange","onInputValueChange","Input","placeholder","Portal","Positioner","sideOffset","Popup","Empty","List","Item","p","textarea","onChange","rows","src","alt","toolCalls","tc","toFixed","round","ref","h3","label","max","step","parseFloat","parseInt","server","checked","n","form","onSubmit","accept","multiple","click","onKeyDown","disabled"],"mappings":"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"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
export function McpInspectorPage() {
|
|
3
|
+
const [servers, setServers] = useState([]);
|
|
4
|
+
const [selectedServer, setSelectedServer] = useState('');
|
|
5
|
+
const [tools, setTools] = useState([]);
|
|
6
|
+
const [selectedTool, setSelectedTool] = useState(null);
|
|
7
|
+
const [toolInput, setToolInput] = useState('{}');
|
|
8
|
+
const [toolResult, setToolResult] = useState(null);
|
|
9
|
+
const [loading, setLoading] = useState(true);
|
|
10
|
+
const [connecting, setConnecting] = useState(false);
|
|
11
|
+
const [executing, setExecuting] = useState(false);
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
// Fetch servers on mount
|
|
14
|
+
useEffect(()=>{
|
|
15
|
+
fetch('/api/mcps/servers').then((r)=>r.json()).then((data)=>setServers(data.servers || [])).catch(console.error).finally(()=>setLoading(false));
|
|
16
|
+
}, []);
|
|
17
|
+
// Connect to server when selected
|
|
18
|
+
const connectServer = useCallback(async (serverName)=>{
|
|
19
|
+
if (!serverName) {
|
|
20
|
+
setTools([]);
|
|
21
|
+
setSelectedTool(null);
|
|
22
|
+
setError(null);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
setConnecting(true);
|
|
26
|
+
setError(null);
|
|
27
|
+
setTools([]);
|
|
28
|
+
setSelectedTool(null);
|
|
29
|
+
setToolResult(null);
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(`/mcp/${serverName}`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json'
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
jsonrpc: '2.0',
|
|
38
|
+
id: 1,
|
|
39
|
+
method: 'tools/list',
|
|
40
|
+
params: {}
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
const data = await res.json();
|
|
44
|
+
if (data.error) {
|
|
45
|
+
throw new Error(data.error.message || 'Failed to list tools');
|
|
46
|
+
}
|
|
47
|
+
setTools(data.result?.tools || []);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
setError(e instanceof Error ? e.message : 'Connection failed');
|
|
50
|
+
} finally{
|
|
51
|
+
setConnecting(false);
|
|
52
|
+
}
|
|
53
|
+
}, []);
|
|
54
|
+
// Handle server selection
|
|
55
|
+
const handleServerChange = (e)=>{
|
|
56
|
+
const serverName = e.target.value;
|
|
57
|
+
setSelectedServer(serverName);
|
|
58
|
+
connectServer(serverName);
|
|
59
|
+
};
|
|
60
|
+
// Execute a tool
|
|
61
|
+
const executeTool = useCallback(async ()=>{
|
|
62
|
+
if (!selectedServer || !selectedTool) return;
|
|
63
|
+
setExecuting(true);
|
|
64
|
+
setToolResult(null);
|
|
65
|
+
try {
|
|
66
|
+
const args = JSON.parse(toolInput);
|
|
67
|
+
const res = await fetch(`/mcp/${selectedServer}`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json'
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
jsonrpc: '2.0',
|
|
74
|
+
id: 2,
|
|
75
|
+
method: 'tools/call',
|
|
76
|
+
params: {
|
|
77
|
+
name: selectedTool.name,
|
|
78
|
+
arguments: args
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
const data = await res.json();
|
|
83
|
+
setToolResult(JSON.stringify(data, null, 2));
|
|
84
|
+
} catch (e) {
|
|
85
|
+
setToolResult(`Error: ${e instanceof Error ? e.message : 'Execution failed'}`);
|
|
86
|
+
} finally{
|
|
87
|
+
setExecuting(false);
|
|
88
|
+
}
|
|
89
|
+
}, [
|
|
90
|
+
selectedServer,
|
|
91
|
+
selectedTool,
|
|
92
|
+
toolInput
|
|
93
|
+
]);
|
|
94
|
+
// Generate default input from schema
|
|
95
|
+
const generateDefaultInput = useCallback((tool)=>{
|
|
96
|
+
if (!tool.inputSchema?.properties) return '{}';
|
|
97
|
+
const defaultObj = {};
|
|
98
|
+
for (const [key, prop] of Object.entries(tool.inputSchema.properties)){
|
|
99
|
+
if (prop.type === 'string') defaultObj[key] = '';
|
|
100
|
+
else if (prop.type === 'number' || prop.type === 'integer') defaultObj[key] = 0;
|
|
101
|
+
else if (prop.type === 'boolean') defaultObj[key] = false;
|
|
102
|
+
else if (prop.type === 'array') defaultObj[key] = [];
|
|
103
|
+
else if (prop.type === 'object') defaultObj[key] = {};
|
|
104
|
+
}
|
|
105
|
+
return JSON.stringify(defaultObj, null, 2);
|
|
106
|
+
}, []);
|
|
107
|
+
// Handle tool selection
|
|
108
|
+
const handleToolSelect = (tool)=>{
|
|
109
|
+
setSelectedTool(tool);
|
|
110
|
+
setToolInput(generateDefaultInput(tool));
|
|
111
|
+
setToolResult(null);
|
|
112
|
+
};
|
|
113
|
+
if (loading) {
|
|
114
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
115
|
+
className: "flex justify-center items-center p-8"
|
|
116
|
+
}, /*#__PURE__*/ React.createElement("span", {
|
|
117
|
+
className: "loading loading-spinner loading-lg"
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
121
|
+
className: "flex flex-col h-full min-h-[600px]"
|
|
122
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
123
|
+
className: "p-3 border-b border-base-300 bg-base-100 flex items-center gap-4"
|
|
124
|
+
}, /*#__PURE__*/ React.createElement("h3", {
|
|
125
|
+
className: "font-semibold"
|
|
126
|
+
}, "MCP Inspector"), /*#__PURE__*/ React.createElement("select", {
|
|
127
|
+
className: "select select-bordered select-sm w-64",
|
|
128
|
+
value: selectedServer,
|
|
129
|
+
onChange: handleServerChange
|
|
130
|
+
}, /*#__PURE__*/ React.createElement("option", {
|
|
131
|
+
value: ""
|
|
132
|
+
}, "Select a server..."), servers.map((server)=>/*#__PURE__*/ React.createElement("option", {
|
|
133
|
+
key: server.name,
|
|
134
|
+
value: server.name
|
|
135
|
+
}, server.name, " (", server.type, ")"))), connecting && /*#__PURE__*/ React.createElement("span", {
|
|
136
|
+
className: "loading loading-spinner loading-sm"
|
|
137
|
+
}), error && /*#__PURE__*/ React.createElement("span", {
|
|
138
|
+
className: "text-error text-sm"
|
|
139
|
+
}, error)), /*#__PURE__*/ React.createElement("div", {
|
|
140
|
+
className: "flex flex-1 overflow-hidden"
|
|
141
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
142
|
+
className: "w-64 border-r border-base-300 bg-base-100 flex flex-col flex-shrink-0"
|
|
143
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
144
|
+
className: "p-3 border-b border-base-300"
|
|
145
|
+
}, /*#__PURE__*/ React.createElement("h4", {
|
|
146
|
+
className: "font-medium text-sm"
|
|
147
|
+
}, "Tools ", tools.length > 0 && `(${tools.length})`)), /*#__PURE__*/ React.createElement("div", {
|
|
148
|
+
className: "flex-1 overflow-y-auto"
|
|
149
|
+
}, !selectedServer ? /*#__PURE__*/ React.createElement("div", {
|
|
150
|
+
className: "p-4 text-center text-base-content/50 text-sm"
|
|
151
|
+
}, "Select a server above") : tools.length === 0 && !connecting ? /*#__PURE__*/ React.createElement("div", {
|
|
152
|
+
className: "p-4 text-center text-base-content/50 text-sm"
|
|
153
|
+
}, "No tools available") : /*#__PURE__*/ React.createElement("ul", {
|
|
154
|
+
className: "menu p-2 gap-1"
|
|
155
|
+
}, tools.map((tool)=>/*#__PURE__*/ React.createElement("li", {
|
|
156
|
+
key: tool.name
|
|
157
|
+
}, /*#__PURE__*/ React.createElement("button", {
|
|
158
|
+
type: "button",
|
|
159
|
+
className: `flex flex-col items-start text-left ${selectedTool?.name === tool.name ? 'active' : ''}`,
|
|
160
|
+
onClick: ()=>handleToolSelect(tool)
|
|
161
|
+
}, /*#__PURE__*/ React.createElement("span", {
|
|
162
|
+
className: "font-mono text-xs"
|
|
163
|
+
}, tool.name), tool.description && /*#__PURE__*/ React.createElement("span", {
|
|
164
|
+
className: "text-xs text-base-content/50 truncate w-full"
|
|
165
|
+
}, tool.description))))))), /*#__PURE__*/ React.createElement("div", {
|
|
166
|
+
className: "flex-1 flex flex-col overflow-hidden"
|
|
167
|
+
}, !selectedTool ? /*#__PURE__*/ React.createElement("div", {
|
|
168
|
+
className: "flex-1 flex items-center justify-center text-base-content/50"
|
|
169
|
+
}, "Select a tool to execute") : /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("div", {
|
|
170
|
+
className: "p-4 border-b border-base-300 bg-base-100"
|
|
171
|
+
}, /*#__PURE__*/ React.createElement("h3", {
|
|
172
|
+
className: "font-semibold mb-2"
|
|
173
|
+
}, selectedTool.name), selectedTool.description && /*#__PURE__*/ React.createElement("p", {
|
|
174
|
+
className: "text-sm mb-3"
|
|
175
|
+
}, selectedTool.description), selectedTool.inputSchema?.properties && /*#__PURE__*/ React.createElement("div", {
|
|
176
|
+
className: "text-xs text-base-content/70"
|
|
177
|
+
}, /*#__PURE__*/ React.createElement("strong", null, "Parameters:"), /*#__PURE__*/ React.createElement("ul", {
|
|
178
|
+
className: "ml-4 mt-1 space-y-0.5"
|
|
179
|
+
}, Object.entries(selectedTool.inputSchema.properties).map(([name, prop])=>/*#__PURE__*/ React.createElement("li", {
|
|
180
|
+
key: name
|
|
181
|
+
}, /*#__PURE__*/ React.createElement("code", {
|
|
182
|
+
className: "bg-base-200 px-1 rounded"
|
|
183
|
+
}, name), /*#__PURE__*/ React.createElement("span", {
|
|
184
|
+
className: "ml-1 text-base-content/50"
|
|
185
|
+
}, "(", prop.type || 'any', ")"), selectedTool.inputSchema?.required?.includes(name) && /*#__PURE__*/ React.createElement("span", {
|
|
186
|
+
className: "text-error ml-1"
|
|
187
|
+
}, "*"), prop.description && /*#__PURE__*/ React.createElement("span", {
|
|
188
|
+
className: "ml-2 text-base-content/60"
|
|
189
|
+
}, "- ", prop.description)))))), /*#__PURE__*/ React.createElement("div", {
|
|
190
|
+
className: "p-4 border-b border-base-300 bg-base-200"
|
|
191
|
+
}, /*#__PURE__*/ React.createElement("label", {
|
|
192
|
+
className: "text-sm font-medium mb-2 block"
|
|
193
|
+
}, "Arguments (JSON):"), /*#__PURE__*/ React.createElement("textarea", {
|
|
194
|
+
className: "textarea textarea-bordered w-full font-mono text-xs",
|
|
195
|
+
rows: 5,
|
|
196
|
+
value: toolInput,
|
|
197
|
+
onChange: (e)=>setToolInput(e.target.value)
|
|
198
|
+
}), /*#__PURE__*/ React.createElement("button", {
|
|
199
|
+
type: "button",
|
|
200
|
+
className: "btn btn-primary btn-sm mt-2",
|
|
201
|
+
onClick: executeTool,
|
|
202
|
+
disabled: executing
|
|
203
|
+
}, executing ? /*#__PURE__*/ React.createElement("span", {
|
|
204
|
+
className: "loading loading-spinner loading-sm"
|
|
205
|
+
}) : 'Execute')), /*#__PURE__*/ React.createElement("div", {
|
|
206
|
+
className: "flex-1 overflow-auto p-4 bg-base-200"
|
|
207
|
+
}, /*#__PURE__*/ React.createElement("label", {
|
|
208
|
+
className: "text-sm font-medium mb-2 block"
|
|
209
|
+
}, "Result:"), /*#__PURE__*/ React.createElement("pre", {
|
|
210
|
+
className: "bg-base-100 p-4 rounded-box text-xs overflow-auto max-h-[400px] border border-base-300"
|
|
211
|
+
}, toolResult || 'No result yet'))))));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
//# sourceMappingURL=McpInspectorPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/web/McpInspectorPage.tsx"],"sourcesContent":["import { useState, useCallback, useEffect } from 'react';\nimport type { ServerInfo } from '../contracts';\n\ninterface McpTool {\n\tname: string;\n\tdescription?: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\nexport function McpInspectorPage() {\n\tconst [servers, setServers] = useState<ServerInfo[]>([]);\n\tconst [selectedServer, setSelectedServer] = useState<string>('');\n\tconst [tools, setTools] = useState<McpTool[]>([]);\n\tconst [selectedTool, setSelectedTool] = useState<McpTool | null>(null);\n\tconst [toolInput, setToolInput] = useState('{}');\n\tconst [toolResult, setToolResult] = useState<string | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\tconst [connecting, setConnecting] = useState(false);\n\tconst [executing, setExecuting] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\n\t// Fetch servers on mount\n\tuseEffect(() => {\n\t\tfetch('/api/mcps/servers')\n\t\t\t.then((r) => r.json())\n\t\t\t.then((data) => setServers(data.servers || []))\n\t\t\t.catch(console.error)\n\t\t\t.finally(() => setLoading(false));\n\t}, []);\n\n\t// Connect to server when selected\n\tconst connectServer = useCallback(async (serverName: string) => {\n\t\tif (!serverName) {\n\t\t\tsetTools([]);\n\t\t\tsetSelectedTool(null);\n\t\t\tsetError(null);\n\t\t\treturn;\n\t\t}\n\n\t\tsetConnecting(true);\n\t\tsetError(null);\n\t\tsetTools([]);\n\t\tsetSelectedTool(null);\n\t\tsetToolResult(null);\n\n\t\ttry {\n\t\t\tconst res = await fetch(`/mcp/${serverName}`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid: 1,\n\t\t\t\t\tmethod: 'tools/list',\n\t\t\t\t\tparams: {},\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tconst data = await res.json();\n\t\t\tif (data.error) {\n\t\t\t\tthrow new Error(data.error.message || 'Failed to list tools');\n\t\t\t}\n\t\t\tsetTools(data.result?.tools || []);\n\t\t} catch (e) {\n\t\t\tsetError(e instanceof Error ? e.message : 'Connection failed');\n\t\t} finally {\n\t\t\tsetConnecting(false);\n\t\t}\n\t}, []);\n\n\t// Handle server selection\n\tconst handleServerChange = (e: React.ChangeEvent<HTMLSelectElement>) => {\n\t\tconst serverName = e.target.value;\n\t\tsetSelectedServer(serverName);\n\t\tconnectServer(serverName);\n\t};\n\n\t// Execute a tool\n\tconst executeTool = useCallback(async () => {\n\t\tif (!selectedServer || !selectedTool) return;\n\n\t\tsetExecuting(true);\n\t\tsetToolResult(null);\n\n\t\ttry {\n\t\t\tconst args = JSON.parse(toolInput);\n\n\t\t\tconst res = await fetch(`/mcp/${selectedServer}`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid: 2,\n\t\t\t\t\tmethod: 'tools/call',\n\t\t\t\t\tparams: {\n\t\t\t\t\t\tname: selectedTool.name,\n\t\t\t\t\t\targuments: args,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tconst data = await res.json();\n\t\t\tsetToolResult(JSON.stringify(data, null, 2));\n\t\t} catch (e) {\n\t\t\tsetToolResult(`Error: ${e instanceof Error ? e.message : 'Execution failed'}`);\n\t\t} finally {\n\t\t\tsetExecuting(false);\n\t\t}\n\t}, [selectedServer, selectedTool, toolInput]);\n\n\t// Generate default input from schema\n\tconst generateDefaultInput = useCallback((tool: McpTool) => {\n\t\tif (!tool.inputSchema?.properties) return '{}';\n\t\tconst defaultObj: Record<string, unknown> = {};\n\t\tfor (const [key, prop] of Object.entries(tool.inputSchema.properties)) {\n\t\t\tif (prop.type === 'string') defaultObj[key] = '';\n\t\t\telse if (prop.type === 'number' || prop.type === 'integer') defaultObj[key] = 0;\n\t\t\telse if (prop.type === 'boolean') defaultObj[key] = false;\n\t\t\telse if (prop.type === 'array') defaultObj[key] = [];\n\t\t\telse if (prop.type === 'object') defaultObj[key] = {};\n\t\t}\n\t\treturn JSON.stringify(defaultObj, null, 2);\n\t}, []);\n\n\t// Handle tool selection\n\tconst handleToolSelect = (tool: McpTool) => {\n\t\tsetSelectedTool(tool);\n\t\tsetToolInput(generateDefaultInput(tool));\n\t\tsetToolResult(null);\n\t};\n\n\tif (loading) {\n\t\treturn (\n\t\t\t<div className='flex justify-center items-center p-8'>\n\t\t\t\t<span className='loading loading-spinner loading-lg' />\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className='flex flex-col h-full min-h-[600px]'>\n\t\t\t{/* Header with server dropdown */}\n\t\t\t<div className='p-3 border-b border-base-300 bg-base-100 flex items-center gap-4'>\n\t\t\t\t<h3 className='font-semibold'>MCP Inspector</h3>\n\t\t\t\t<select className='select select-bordered select-sm w-64' value={selectedServer} onChange={handleServerChange}>\n\t\t\t\t\t<option value=''>Select a server...</option>\n\t\t\t\t\t{servers.map((server) => (\n\t\t\t\t\t\t<option key={server.name} value={server.name}>\n\t\t\t\t\t\t\t{server.name} ({server.type})\n\t\t\t\t\t\t</option>\n\t\t\t\t\t))}\n\t\t\t\t</select>\n\t\t\t\t{connecting && <span className='loading loading-spinner loading-sm' />}\n\t\t\t\t{error && <span className='text-error text-sm'>{error}</span>}\n\t\t\t</div>\n\n\t\t\t{/* Main content */}\n\t\t\t<div className='flex flex-1 overflow-hidden'>\n\t\t\t\t{/* Tools List */}\n\t\t\t\t<div className='w-64 border-r border-base-300 bg-base-100 flex flex-col flex-shrink-0'>\n\t\t\t\t\t<div className='p-3 border-b border-base-300'>\n\t\t\t\t\t\t<h4 className='font-medium text-sm'>Tools {tools.length > 0 && `(${tools.length})`}</h4>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className='flex-1 overflow-y-auto'>\n\t\t\t\t\t\t{!selectedServer ? (\n\t\t\t\t\t\t\t<div className='p-4 text-center text-base-content/50 text-sm'>Select a server above</div>\n\t\t\t\t\t\t) : tools.length === 0 && !connecting ? (\n\t\t\t\t\t\t\t<div className='p-4 text-center text-base-content/50 text-sm'>No tools available</div>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<ul className='menu p-2 gap-1'>\n\t\t\t\t\t\t\t\t{tools.map((tool) => (\n\t\t\t\t\t\t\t\t\t<li key={tool.name}>\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\t\t\t\t\tclassName={`flex flex-col items-start text-left ${selectedTool?.name === tool.name ? 'active' : ''}`}\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => handleToolSelect(tool)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span className='font-mono text-xs'>{tool.name}</span>\n\t\t\t\t\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className='text-xs text-base-content/50 truncate w-full'>{tool.description}</span>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t{/* Tool Execution Panel */}\n\t\t\t\t<div className='flex-1 flex flex-col overflow-hidden'>\n\t\t\t\t\t{!selectedTool ? (\n\t\t\t\t\t\t<div className='flex-1 flex items-center justify-center text-base-content/50'>Select a tool to execute</div>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{/* Tool info */}\n\t\t\t\t\t\t\t<div className='p-4 border-b border-base-300 bg-base-100'>\n\t\t\t\t\t\t\t\t<h3 className='font-semibold mb-2'>{selectedTool.name}</h3>\n\t\t\t\t\t\t\t\t{selectedTool.description && <p className='text-sm mb-3'>{selectedTool.description}</p>}\n\t\t\t\t\t\t\t\t{selectedTool.inputSchema?.properties && (\n\t\t\t\t\t\t\t\t\t<div className='text-xs text-base-content/70'>\n\t\t\t\t\t\t\t\t\t\t<strong>Parameters:</strong>\n\t\t\t\t\t\t\t\t\t\t<ul className='ml-4 mt-1 space-y-0.5'>\n\t\t\t\t\t\t\t\t\t\t\t{Object.entries(selectedTool.inputSchema.properties).map(([name, prop]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<li key={name}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<code className='bg-base-200 px-1 rounded'>{name}</code>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className='ml-1 text-base-content/50'>({prop.type || 'any'})</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{selectedTool.inputSchema?.required?.includes(name) && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className='text-error ml-1'>*</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t{prop.description && <span className='ml-2 text-base-content/60'>- {prop.description}</span>}\n\t\t\t\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t{/* Input */}\n\t\t\t\t\t\t\t<div className='p-4 border-b border-base-300 bg-base-200'>\n\t\t\t\t\t\t\t\t<label className='text-sm font-medium mb-2 block'>Arguments (JSON):</label>\n\t\t\t\t\t\t\t\t<textarea\n\t\t\t\t\t\t\t\t\tclassName='textarea textarea-bordered w-full font-mono text-xs'\n\t\t\t\t\t\t\t\t\trows={5}\n\t\t\t\t\t\t\t\t\tvalue={toolInput}\n\t\t\t\t\t\t\t\t\tonChange={(e) => setToolInput(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\t\t\tclassName='btn btn-primary btn-sm mt-2'\n\t\t\t\t\t\t\t\t\tonClick={executeTool}\n\t\t\t\t\t\t\t\t\tdisabled={executing}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{executing ? <span className='loading loading-spinner loading-sm' /> : 'Execute'}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t{/* Result */}\n\t\t\t\t\t\t\t<div className='flex-1 overflow-auto p-4 bg-base-200'>\n\t\t\t\t\t\t\t\t<label className='text-sm font-medium mb-2 block'>Result:</label>\n\t\t\t\t\t\t\t\t<pre className='bg-base-100 p-4 rounded-box text-xs overflow-auto max-h-[400px] border border-base-300'>\n\t\t\t\t\t\t\t\t\t{toolResult || 'No result yet'}\n\t\t\t\t\t\t\t\t</pre>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["useState","useCallback","useEffect","McpInspectorPage","servers","setServers","selectedServer","setSelectedServer","tools","setTools","selectedTool","setSelectedTool","toolInput","setToolInput","toolResult","setToolResult","loading","setLoading","connecting","setConnecting","executing","setExecuting","error","setError","fetch","then","r","json","data","catch","console","finally","connectServer","serverName","res","method","headers","body","JSON","stringify","jsonrpc","id","params","Error","message","result","e","handleServerChange","target","value","executeTool","args","parse","name","arguments","generateDefaultInput","tool","inputSchema","properties","defaultObj","key","prop","Object","entries","type","handleToolSelect","div","className","span","h3","select","onChange","option","map","server","h4","length","ul","li","button","onClick","description","p","strong","code","required","includes","label","textarea","rows","disabled","pre"],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
export function ServersPage() {
|
|
3
|
+
const [servers, setServers] = useState([]);
|
|
4
|
+
const [tools, setTools] = useState([]);
|
|
5
|
+
const [selectedServer, setSelectedServer] = useState(null);
|
|
6
|
+
const [loading, setLoading] = useState(true);
|
|
7
|
+
const [error, setError] = useState(null);
|
|
8
|
+
useEffect(()=>{
|
|
9
|
+
Promise.all([
|
|
10
|
+
fetch('/api/mcps/servers').then((r)=>r.json()),
|
|
11
|
+
fetch('/api/mcps/tools').then((r)=>r.json())
|
|
12
|
+
]).then(([serversData, toolsData])=>{
|
|
13
|
+
setServers(serversData.servers || []);
|
|
14
|
+
setTools(toolsData.tools || []);
|
|
15
|
+
}).catch((e)=>setError(e.message)).finally(()=>setLoading(false));
|
|
16
|
+
}, []);
|
|
17
|
+
const filteredTools = selectedServer ? tools.filter((t)=>t.serverName === selectedServer) : tools;
|
|
18
|
+
if (loading) {
|
|
19
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
20
|
+
className: "flex justify-center items-center p-8"
|
|
21
|
+
}, /*#__PURE__*/ React.createElement("span", {
|
|
22
|
+
className: "loading loading-spinner loading-lg"
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
if (error) {
|
|
26
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
27
|
+
className: "alert alert-error"
|
|
28
|
+
}, /*#__PURE__*/ React.createElement("span", null, error));
|
|
29
|
+
}
|
|
30
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
31
|
+
className: "space-y-6"
|
|
32
|
+
}, /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("h2", {
|
|
33
|
+
className: "text-lg font-semibold mb-3"
|
|
34
|
+
}, "MCP Servers (", servers.length, ")"), /*#__PURE__*/ React.createElement("div", {
|
|
35
|
+
className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
|
|
36
|
+
}, servers.map((server)=>/*#__PURE__*/ React.createElement("div", {
|
|
37
|
+
key: server.name,
|
|
38
|
+
className: `card bg-base-100 shadow-sm border cursor-pointer transition-all ${selectedServer === server.name ? 'border-primary ring-2 ring-primary/30' : 'border-base-300'}`,
|
|
39
|
+
onClick: ()=>setSelectedServer(selectedServer === server.name ? null : server.name),
|
|
40
|
+
onKeyDown: (e)=>e.key === 'Enter' && setSelectedServer(selectedServer === server.name ? null : server.name),
|
|
41
|
+
role: "button",
|
|
42
|
+
tabIndex: 0
|
|
43
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
44
|
+
className: "card-body p-4"
|
|
45
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
46
|
+
className: "flex justify-between items-start"
|
|
47
|
+
}, /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
|
|
48
|
+
className: "font-semibold"
|
|
49
|
+
}, server.name), /*#__PURE__*/ React.createElement("span", {
|
|
50
|
+
className: `badge badge-sm ${getServerTypeBadgeClass(server.type)}`
|
|
51
|
+
}, server.type)), server.disabled && /*#__PURE__*/ React.createElement("span", {
|
|
52
|
+
className: "badge badge-error badge-sm"
|
|
53
|
+
}, "disabled"))))))), /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
|
|
54
|
+
className: "flex justify-between items-center mb-3"
|
|
55
|
+
}, /*#__PURE__*/ React.createElement("h2", {
|
|
56
|
+
className: "text-lg font-semibold"
|
|
57
|
+
}, "Tools ", selectedServer ? `(${selectedServer})` : '', " (", filteredTools.length, ")"), selectedServer && /*#__PURE__*/ React.createElement("button", {
|
|
58
|
+
type: "button",
|
|
59
|
+
className: "btn btn-ghost btn-xs",
|
|
60
|
+
onClick: ()=>setSelectedServer(null)
|
|
61
|
+
}, "Show all")), filteredTools.length === 0 ? /*#__PURE__*/ React.createElement("div", {
|
|
62
|
+
className: "card bg-base-100 shadow-sm border border-base-300"
|
|
63
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
64
|
+
className: "card-body text-center text-base-content/50"
|
|
65
|
+
}, /*#__PURE__*/ React.createElement("p", null, "No tools available."), /*#__PURE__*/ React.createElement("p", {
|
|
66
|
+
className: "text-sm"
|
|
67
|
+
}, "Tools will appear here when MCP servers are connected and their tools are listed."))) : /*#__PURE__*/ React.createElement("div", {
|
|
68
|
+
className: "overflow-x-auto bg-base-100 rounded-box shadow-sm border border-base-300"
|
|
69
|
+
}, /*#__PURE__*/ React.createElement("table", {
|
|
70
|
+
className: "table table-zebra"
|
|
71
|
+
}, /*#__PURE__*/ React.createElement("thead", null, /*#__PURE__*/ React.createElement("tr", null, /*#__PURE__*/ React.createElement("th", null, "Tool"), /*#__PURE__*/ React.createElement("th", null, "Server"), /*#__PURE__*/ React.createElement("th", null, "Description"), /*#__PURE__*/ React.createElement("th", null, "Schema"))), /*#__PURE__*/ React.createElement("tbody", null, filteredTools.map((tool)=>/*#__PURE__*/ React.createElement("tr", {
|
|
72
|
+
key: `${tool.serverName}/${tool.name}`
|
|
73
|
+
}, /*#__PURE__*/ React.createElement("td", null, /*#__PURE__*/ React.createElement("span", {
|
|
74
|
+
className: "font-mono text-sm"
|
|
75
|
+
}, tool.name)), /*#__PURE__*/ React.createElement("td", null, /*#__PURE__*/ React.createElement("span", {
|
|
76
|
+
className: "badge badge-sm"
|
|
77
|
+
}, tool.serverName)), /*#__PURE__*/ React.createElement("td", {
|
|
78
|
+
className: "text-sm text-base-content/70"
|
|
79
|
+
}, tool.description || '-'), /*#__PURE__*/ React.createElement("td", null, tool.inputSchemaCompact && /*#__PURE__*/ React.createElement("code", {
|
|
80
|
+
className: "text-xs bg-base-200 px-1 py-0.5 rounded"
|
|
81
|
+
}, tool.inputSchemaCompact)))))))));
|
|
82
|
+
}
|
|
83
|
+
function getServerTypeBadgeClass(type) {
|
|
84
|
+
const classes = {
|
|
85
|
+
'tencent-cls': 'badge-info',
|
|
86
|
+
sql: 'badge-success',
|
|
87
|
+
prometheus: 'badge-secondary',
|
|
88
|
+
relay: 'badge-warning'
|
|
89
|
+
};
|
|
90
|
+
return classes[type] || 'badge-primary';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=ServersPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/web/ServersPage.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport type { ServerInfo, ToolInfo } from '../contracts';\n\nexport function ServersPage() {\n\tconst [servers, setServers] = useState<ServerInfo[]>([]);\n\tconst [tools, setTools] = useState<ToolInfo[]>([]);\n\tconst [selectedServer, setSelectedServer] = useState<string | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\tconst [error, setError] = useState<string | null>(null);\n\n\tuseEffect(() => {\n\t\tPromise.all([fetch('/api/mcps/servers').then((r) => r.json()), fetch('/api/mcps/tools').then((r) => r.json())])\n\t\t\t.then(([serversData, toolsData]) => {\n\t\t\t\tsetServers(serversData.servers || []);\n\t\t\t\tsetTools(toolsData.tools || []);\n\t\t\t})\n\t\t\t.catch((e) => setError(e.message))\n\t\t\t.finally(() => setLoading(false));\n\t}, []);\n\n\tconst filteredTools = selectedServer ? tools.filter((t) => t.serverName === selectedServer) : tools;\n\n\tif (loading) {\n\t\treturn (\n\t\t\t<div className='flex justify-center items-center p-8'>\n\t\t\t\t<span className='loading loading-spinner loading-lg' />\n\t\t\t</div>\n\t\t);\n\t}\n\n\tif (error) {\n\t\treturn (\n\t\t\t<div className='alert alert-error'>\n\t\t\t\t<span>{error}</span>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className='space-y-6'>\n\t\t\t{/* Server List */}\n\t\t\t<div>\n\t\t\t\t<h2 className='text-lg font-semibold mb-3'>MCP Servers ({servers.length})</h2>\n\t\t\t\t<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4'>\n\t\t\t\t\t{servers.map((server) => (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tkey={server.name}\n\t\t\t\t\t\t\tclassName={`card bg-base-100 shadow-sm border cursor-pointer transition-all ${\n\t\t\t\t\t\t\t\tselectedServer === server.name ? 'border-primary ring-2 ring-primary/30' : 'border-base-300'\n\t\t\t\t\t\t\t}`}\n\t\t\t\t\t\t\tonClick={() => setSelectedServer(selectedServer === server.name ? null : server.name)}\n\t\t\t\t\t\t\tonKeyDown={(e) =>\n\t\t\t\t\t\t\t\te.key === 'Enter' && setSelectedServer(selectedServer === server.name ? null : server.name)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\trole='button'\n\t\t\t\t\t\t\ttabIndex={0}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div className='card-body p-4'>\n\t\t\t\t\t\t\t\t<div className='flex justify-between items-start'>\n\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t<div className='font-semibold'>{server.name}</div>\n\t\t\t\t\t\t\t\t\t\t<span className={`badge badge-sm ${getServerTypeBadgeClass(server.type)}`}>{server.type}</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t{server.disabled && <span className='badge badge-error badge-sm'>disabled</span>}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Tools List */}\n\t\t\t<div>\n\t\t\t\t<div className='flex justify-between items-center mb-3'>\n\t\t\t\t\t<h2 className='text-lg font-semibold'>\n\t\t\t\t\t\tTools {selectedServer ? `(${selectedServer})` : ''} ({filteredTools.length})\n\t\t\t\t\t</h2>\n\t\t\t\t\t{selectedServer && (\n\t\t\t\t\t\t<button type='button' className='btn btn-ghost btn-xs' onClick={() => setSelectedServer(null)}>\n\t\t\t\t\t\t\tShow all\n\t\t\t\t\t\t</button>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{filteredTools.length === 0 ? (\n\t\t\t\t\t<div className='card bg-base-100 shadow-sm border border-base-300'>\n\t\t\t\t\t\t<div className='card-body text-center text-base-content/50'>\n\t\t\t\t\t\t\t<p>No tools available.</p>\n\t\t\t\t\t\t\t<p className='text-sm'>\n\t\t\t\t\t\t\t\tTools will appear here when MCP servers are connected and their tools are listed.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t) : (\n\t\t\t\t\t<div className='overflow-x-auto bg-base-100 rounded-box shadow-sm border border-base-300'>\n\t\t\t\t\t\t<table className='table table-zebra'>\n\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<th>Tool</th>\n\t\t\t\t\t\t\t\t\t<th>Server</th>\n\t\t\t\t\t\t\t\t\t<th>Description</th>\n\t\t\t\t\t\t\t\t\t<th>Schema</th>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t{filteredTools.map((tool) => (\n\t\t\t\t\t\t\t\t\t<tr key={`${tool.serverName}/${tool.name}`}>\n\t\t\t\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t\t\t\t<span className='font-mono text-sm'>{tool.name}</span>\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t\t\t\t<span className='badge badge-sm'>{tool.serverName}</span>\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t<td className='text-sm text-base-content/70'>{tool.description || '-'}</td>\n\t\t\t\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t\t\t\t{tool.inputSchemaCompact && (\n\t\t\t\t\t\t\t\t\t\t\t\t<code className='text-xs bg-base-200 px-1 py-0.5 rounded'>{tool.inputSchemaCompact}</code>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction getServerTypeBadgeClass(type: string) {\n\tconst classes: Record<string, string> = {\n\t\t'tencent-cls': 'badge-info',\n\t\tsql: 'badge-success',\n\t\tprometheus: 'badge-secondary',\n\t\trelay: 'badge-warning',\n\t};\n\treturn classes[type] || 'badge-primary';\n}\n"],"names":["useEffect","useState","ServersPage","servers","setServers","tools","setTools","selectedServer","setSelectedServer","loading","setLoading","error","setError","Promise","all","fetch","then","r","json","serversData","toolsData","catch","e","message","finally","filteredTools","filter","t","serverName","div","className","span","h2","length","map","server","key","name","onClick","onKeyDown","role","tabIndex","getServerTypeBadgeClass","type","disabled","button","p","table","thead","tr","th","tbody","tool","td","description","inputSchemaCompact","code","classes","sql","prometheus","relay"],"mappings":"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"}
|