'use client' import * as React from 'react' import { useState } from 'react' import { useQuery } from '@tanstack/react-query' import { Bot, Loader2, CheckCircle2, XCircle, ChevronDown, ChevronRight, Server, Wrench, AlertTriangle } from 'lucide-react' import { Button } from '@open-mercato/ui/primitives/button' import { CommandPaletteProvider, CommandPalette, useCommandPaletteContext, } from '../../../../frontend' // OpenCode health response type type OpenCodeHealthResponse = { status: 'ok' | 'error' opencode?: { healthy: boolean version: string } mcp?: Record search?: { available: boolean driver: string | null } url: string message?: string } // Provider config type from settings API type ProviderConfig = { id: string name: string model: string defaultModel: string envKey: string configured: boolean } type SettingsResponse = { provider: ProviderConfig availableProviders: ProviderConfig[] } // Tool info type type ToolInfo = { name: string description: string module: string inputSchema: Record } // API fetch functions async function fetchHealth(): Promise { const res = await fetch('/api/ai_assistant/health') if (!res.ok) throw new Error('Failed to fetch health') return res.json() } async function fetchSettings(): Promise { const res = await fetch('/api/ai_assistant/settings') if (!res.ok) throw new Error('Failed to fetch settings') return res.json() } async function fetchTools(): Promise<{ tools: ToolInfo[] }> { const res = await fetch('/api/ai_assistant/tools') if (!res.ok) throw new Error('Failed to fetch tools') return res.json() } function AiAssistantSettingsContent() { const [toolsExpanded, setToolsExpanded] = useState(false) const { setIsOpen } = useCommandPaletteContext() // Health query - polls every 10 seconds const healthQuery = useQuery({ queryKey: ['ai-assistant', 'health'], queryFn: fetchHealth, refetchInterval: 10000, staleTime: 5000, }) // Settings query - no polling needed (static config) const settingsQuery = useQuery({ queryKey: ['ai-assistant', 'settings'], queryFn: fetchSettings, staleTime: 60000, }) // Tools query - no polling needed const toolsQuery = useQuery({ queryKey: ['ai-assistant', 'tools'], queryFn: fetchTools, staleTime: 60000, }) // Open AI Assistant palette const openAiAssistant = () => { setIsOpen(true) } const isLoading = healthQuery.isLoading || settingsQuery.isLoading || toolsQuery.isLoading if (isLoading) { return (
Loading settings...
) } const health = healthQuery.data const settings = settingsQuery.data const tools = toolsQuery.data?.tools || [] // Group tools by module const toolsByModule = tools.reduce>((acc, tool) => { const module = tool.module || 'other' if (!acc[module]) acc[module] = [] acc[module].push(tool) return acc }, {}) const provider = settings?.provider return (
{/* Header */}

AI Assistant Settings

Configure and monitor the AI assistant

{/* Test AI Assistant Section */}

Test AI Assistant

Click the button to open the AI Assistant command palette.

{/* Configuration Section */}

Configuration

Provider: {provider?.name || 'Anthropic'} {provider?.configured ? ( Configured ) : ( Not configured )}
Model: {provider?.model || 'claude-haiku-4-5-20251001'}
Required: Set {provider?.envKey || 'ANTHROPIC_API_KEY'} in .env
{/* Available Providers */} {settings?.availableProviders && settings.availableProviders.length > 1 && (

Available Providers:

{settings.availableProviders.map((p) => (
{p.name} {p.id === provider?.id && ' (active)'} {p.id !== provider?.id && p.configured && ' (ready)'}
))}
)}

Set OM_AI_PROVIDER in .env to change provider (anthropic, openai, google).

{/* Requirements Section */}

Requirements

Full-Text Search: {health?.search?.available ? ( Meilisearch connected ) : ( Not available )}

A full-text search driver (Meilisearch) is required for API endpoint discovery. Endpoints are indexed automatically when the MCP server starts.

{/* OpenCode Connection Section */}

OpenCode Connection

{healthQuery.isFetching && !healthQuery.isLoading && ( )}
{/* OpenCode Server Status */}

OpenCode Server

{health?.status === 'ok' && health.opencode?.healthy ? ( Connected ) : ( {health?.message || 'Disconnected'} )}

{health?.opencode?.version && (

Version: {health.opencode.version}

)}

{health?.url || 'http://localhost:4096'}

{health?.status === 'ok' && health.opencode?.healthy && (
)}
{/* MCP Server Status */} {health?.mcp && Object.entries(health.mcp).map(([name, mcpStatus]) => (

MCP Server

{mcpStatus.status === 'connected' ? ( Connected ) : mcpStatus.status === 'connecting' ? ( Connecting... ) : ( {mcpStatus.error || 'Failed'} )}

{name}

localhost:3001

{mcpStatus.status === 'connected' && (
)}
))} {/* Show placeholder if no MCP info */} {(!health?.mcp || Object.keys(health.mcp).length === 0) && (

MCP Server

Not connected

localhost:3001

)}
{/* MCP Tools Section */}
{toolsExpanded && (
{Object.entries(toolsByModule).map(([module, moduleTools]) => (

{module}

{moduleTools.map((tool) => (

{tool.name}

{tool.description}

))}
))}
)}
) } export function AiAssistantSettingsPageClient() { return ( ) } export default AiAssistantSettingsPageClient