"use client"; import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; import { Switch } from '@/components/ui/switch'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; import { AlertCircle, AlertTriangle, CheckCircle, TestTube, RefreshCw, Wifi, WifiOff, ExternalLink, Clock, Plus, Trash2 } from 'lucide-react'; import { useToast } from '@/components/ui/use-toast'; import { Alert, AlertDescription } from '@/components/ui/alert'; interface ProviderConfig { provider: string; name: string; endpoint: string; enabled: boolean; health_status: 'healthy' | 'unhealthy' | 'degraded' | 'testing' | 'unknown'; description: string; is_external: boolean; requires_api_key: boolean; last_test?: string; last_latency_ms?: number; is_custom?: boolean; model_type?: 'llm' | 'embedding' | 'both'; is_local_mode?: boolean; external_endpoint?: string; } interface AddEndpointForm { name: string; provider: string; endpoint: string; description: string; model_type: 'llm' | 'embedding' | 'both'; is_external: boolean; requires_api_key: boolean; } interface EndpointConfiguratorProps { showAddDialog?: boolean; onShowAddDialogChange?: (show: boolean) => void; } export default function EndpointConfigurator({ showAddDialog: externalShowAddDialog, onShowAddDialogChange }: EndpointConfiguratorProps = {}) { const [providers, setProviders] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(null); const [testing, setTesting] = useState(null); const [internalShowAddDialog, setInternalShowAddDialog] = useState(false); // Use external state if provided, otherwise use internal state const showAddDialog = externalShowAddDialog !== undefined ? externalShowAddDialog : internalShowAddDialog; const setShowAddDialog = onShowAddDialogChange || setInternalShowAddDialog; const [addForm, setAddForm] = useState({ name: '', provider: '', endpoint: '', description: '', model_type: 'llm', is_external: false, requires_api_key: true, }); const { toast } = useToast(); // Initialize with default configurations useEffect(() => { const defaultProviders: ProviderConfig[] = [ { provider: 'nvidia', name: 'NVIDIA NIM (build.nvidia.com)', endpoint: 'https://integrate.api.nvidia.com/v1/chat/completions', enabled: true, health_status: 'unknown', description: 'NVIDIA NIM microservices - GPU-accelerated inference on DGX Cloud', is_external: false, requires_api_key: true, is_custom: false, model_type: 'llm' }, { provider: 'ollama-dgx-x86', name: 'Local Ollama (Ubuntu x86 / DGX ARM)', endpoint: 'http://ollama-host:11434/v1/chat/completions', enabled: true, health_status: 'unknown', description: 'Local Ollama instance for Ubuntu x86_64 and NVIDIA DGX ARM deployments', is_external: false, requires_api_key: false, is_custom: false, model_type: 'llm' }, { provider: 'ollama-macos', name: 'Local Ollama (macOS Apple Silicon)', endpoint: 'http://host.docker.internal:11434/v1/chat/completions', enabled: true, health_status: 'unknown', description: 'Local Ollama instance for macOS Apple Silicon (M1/M2/M3/M4) deployments', is_external: false, requires_api_key: false, is_custom: false, model_type: 'llm' }, { provider: 'groq', name: 'Groq (api.groq.com)', endpoint: 'https://api.groq.com/openai/v1/chat/completions', enabled: true, health_status: 'healthy', description: 'Groq Cloud - Ultra-fast LLM inference', is_external: false, requires_api_key: true, last_test: '2025-01-21T10:30:00Z', is_custom: false, model_type: 'llm' }, { provider: 'bge_m3', name: 'BGE-M3 Embeddings', endpoint: 'http://gentwo-vllm-embeddings:8000/v1/embeddings', enabled: true, health_status: 'healthy', description: 'Multilingual embedding model - Local GT Edge deployment', is_external: false, requires_api_key: false, last_test: '2025-01-21T10:32:00Z', is_custom: false, model_type: 'embedding', is_local_mode: true, external_endpoint: 'http://10.0.1.50:8080' } ]; // Load custom endpoints from localStorage or API const savedCustomEndpoints = localStorage.getItem('custom_endpoints'); if (savedCustomEndpoints) { try { const customEndpoints = JSON.parse(savedCustomEndpoints); defaultProviders.push(...customEndpoints); } catch (error) { console.error('Failed to load custom endpoints:', error); } } setProviders(defaultProviders); setLoading(false); }, []); // Load BGE-M3 configuration from API useEffect(() => { const loadBGEConfig = async () => { try { const response = await fetch('/api/v1/models/BAAI%2Fbge-m3', { headers: { 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, } }); if (response.ok) { const bgeModel = await response.json(); const isLocalMode = bgeModel.config?.is_local_mode ?? true; const externalEndpoint = bgeModel.config?.external_endpoint || 'http://10.0.1.50:8080'; setProviders(prev => prev.map(p => { if (p.provider === 'bge_m3') { return { ...p, endpoint: bgeModel.endpoint, enabled: bgeModel.status?.is_active ?? true, health_status: bgeModel.status?.health_status || 'healthy', description: isLocalMode ? 'Multilingual embedding model - Local GT Edge deployment' : 'Multilingual embedding model - External API deployment', is_external: !isLocalMode, is_local_mode: isLocalMode, external_endpoint: externalEndpoint }; } return p; })); } } catch (error) { console.error('Error loading BGE-M3 config from API:', error); } }; loadBGEConfig(); }, []); const getStatusIcon = (status: string) => { switch (status) { case 'healthy': return ; case 'degraded': return ; case 'unhealthy': return ; case 'testing': return ; default: return ; } }; const getConnectionIcon = (isExternal: boolean) => { return isExternal ? ( ) : ( ); }; const handleEndpointChange = (provider: string, newEndpoint: string) => { setProviders(prev => prev.map(p => p.provider === provider ? { ...p, endpoint: newEndpoint } : p )); }; const handleToggleProvider = async (provider: string, enabled: boolean) => { setProviders(prev => prev.map(p => p.provider === provider ? { ...p, enabled: !enabled } : p )); toast({ title: `Provider ${!enabled ? 'Enabled' : 'Disabled'}`, description: `${provider} has been ${!enabled ? 'enabled' : 'disabled'}`, }); }; const handleTestEndpoint = async (providerConfig: ProviderConfig) => { setTesting(providerConfig.provider); setProviders(prev => prev.map(p => p.provider === providerConfig.provider ? { ...p, health_status: 'testing' } : p )); try { // Determine which endpoint to test based on mode const endpointToTest = providerConfig.is_local_mode ? providerConfig.endpoint : (providerConfig.external_endpoint || providerConfig.endpoint); // Test endpoint connectivity via backend API const response = await fetch('/api/v1/models/test-endpoint', { method: 'POST', headers: { 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ endpoint: endpointToTest, provider: providerConfig.provider }) }); const result = await response.json(); const healthy = result.healthy || false; const status = result.status || (healthy ? 'healthy' : 'unhealthy'); setProviders(prev => prev.map(p => p.provider === providerConfig.provider ? { ...p, health_status: status as 'healthy' | 'unhealthy' | 'degraded' | 'testing' | 'unknown', last_test: new Date().toISOString(), last_latency_ms: result.latency_ms } : p )); // Build toast message based on status let toastTitle = "Endpoint Healthy"; let toastDescription = `${providerConfig.name} is responding correctly`; let toastVariant: "default" | "destructive" = "default"; if (status === 'degraded') { toastTitle = "Endpoint Degraded"; toastDescription = result.error || `${providerConfig.name} responding with high latency`; if (result.latency_ms) { toastDescription += ` (${result.latency_ms.toFixed(0)}ms)`; } } else if (status === 'unhealthy' || !healthy) { toastTitle = "Endpoint Unhealthy"; toastDescription = result.error || `${providerConfig.name} is not responding`; toastVariant = "destructive"; } else if (result.latency_ms) { toastDescription += ` (${result.latency_ms.toFixed(0)}ms)`; } toast({ title: toastTitle, description: toastDescription, variant: toastVariant, }); } catch (error) { setProviders(prev => prev.map(p => p.provider === providerConfig.provider ? { ...p, health_status: 'unhealthy' } : p )); toast({ title: "Test Failed", description: "Failed to test endpoint", variant: "destructive", }); } setTesting(null); }; const handleAddCustomEndpoint = async () => { if (!addForm.name || !addForm.provider || !addForm.endpoint) { toast({ title: "Missing Information", description: "Please fill in all required fields", variant: "destructive", }); return; } const newEndpoint: ProviderConfig = { provider: addForm.provider.toLowerCase().replace(/\s+/g, '_'), name: addForm.name, endpoint: addForm.endpoint, enabled: true, health_status: 'unknown', description: addForm.description, is_external: addForm.is_external, requires_api_key: addForm.requires_api_key, is_custom: true, model_type: addForm.model_type, }; const updatedProviders = [...providers, newEndpoint]; setProviders(updatedProviders); // Save custom endpoints to localStorage // Security Note: This stores endpoint URLs and configuration, not API keys or secrets. // API keys are managed server-side in the Control Panel backend. const customEndpoints = updatedProviders.filter(p => p.is_custom); localStorage.setItem('custom_endpoints', JSON.stringify(customEndpoints)); toast({ title: "Endpoint Added", description: `Successfully added ${addForm.name}`, }); // Reset form and close dialog setAddForm({ name: '', provider: '', endpoint: '', description: '', model_type: 'llm', is_external: false, requires_api_key: true, }); setShowAddDialog(false); }; const handleRemoveCustomEndpoint = (provider: string) => { const updatedProviders = providers.filter(p => p.provider !== provider); setProviders(updatedProviders); // Update localStorage const customEndpoints = updatedProviders.filter(p => p.is_custom); localStorage.setItem('custom_endpoints', JSON.stringify(customEndpoints)); toast({ title: "Endpoint Removed", description: "Custom endpoint has been removed", }); }; const handleToggleBGEM3Mode = async (isLocal: boolean) => { const provider = providers.find(p => p.provider === 'bge_m3'); if (!provider) return; try { // Get current BGE-M3 model config const modelResponse = await fetch('/api/v1/models/BAAI%2Fbge-m3', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, } }); if (!modelResponse.ok) { throw new Error('Failed to get current BGE-M3 model config'); } const currentModel = await modelResponse.json(); const externalEndpoint = provider.external_endpoint || 'http://10.0.1.50:8080'; // Update the model config with new mode const response = await fetch(`/api/v1/models/${encodeURIComponent('BAAI/bge-m3')}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, }, body: JSON.stringify({ endpoint: isLocal ? 'http://gentwo-vllm-embeddings:8000/v1/embeddings' : externalEndpoint, config: { ...currentModel.config, is_local_mode: isLocal, external_endpoint: externalEndpoint } }) }); if (response.ok) { const result = await response.json(); // Sync configuration to tenant backend try { const syncResponse = await fetch('http://localhost:8002/api/embeddings/config/bge-m3', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ is_local_mode: isLocal, external_endpoint: externalEndpoint }) }); if (syncResponse.ok) { console.log('Configuration synced to tenant backend'); } else { console.warn('Failed to sync configuration to tenant backend'); } } catch (syncError) { console.warn('Error syncing to tenant backend:', syncError); } setProviders(prev => prev.map(p => { if (p.provider === 'bge_m3') { const updatedProvider = { ...p, is_local_mode: isLocal, endpoint: isLocal ? 'http://gentwo-vllm-embeddings:8000/v1/embeddings' : p.external_endpoint || 'http://10.0.1.50:8080', is_external: !isLocal, description: isLocal ? 'Multilingual embedding model - Local GT Edge deployment' : 'Multilingual embedding model - External API deployment' }; // Save BGE-M3 configuration to localStorage as backup localStorage.setItem('bge_m3_config', JSON.stringify({ is_local_mode: isLocal, external_endpoint: p.external_endpoint || 'http://10.0.1.50:8080' })); return updatedProvider; } return p; })); // Show success message with sync status const syncStatus = result.sync_status; if (syncStatus === 'success') { toast({ title: "BGE-M3 Mode Updated", description: `Switched to ${isLocal ? 'Local GT Edge' : 'External API'} deployment. Configuration synced to all services.`, }); } else { toast({ title: "BGE-M3 Mode Updated", description: `Switched to ${isLocal ? 'Local GT Edge' : 'External API'} deployment. Warning: Some services may not have received the update.`, variant: "destructive", }); } } else { throw new Error('Failed to update configuration'); } } catch (error) { console.error('Error updating BGE-M3 config:', error); toast({ title: "Configuration Update Failed", description: "Failed to save BGE-M3 configuration to server", variant: "destructive", }); } }; // Immediate state update for UI responsiveness const handleExternalEndpointChange = (newEndpoint: string) => { // Update UI immediately setProviders(prev => prev.map(p => { if (p.provider === 'bge_m3') { return { ...p, external_endpoint: newEndpoint, endpoint: !p.is_local_mode ? newEndpoint : p.endpoint }; } return p; })); }; // Debounced API call to persist configuration const handleUpdateExternalEndpoint = async (newEndpoint: string) => { const provider = providers.find(p => p.provider === 'bge_m3'); if (!provider) return; try { // Update the BGE-M3 model config using standard model API const modelResponse = await fetch('/api/v1/models/BAAI%2Fbge-m3', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, } }); if (!modelResponse.ok) { throw new Error('Failed to get current BGE-M3 model config'); } const currentModel = await modelResponse.json(); // Update the model config with new endpoint configuration const response = await fetch(`/api/v1/models/${encodeURIComponent('BAAI/bge-m3')}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, }, body: JSON.stringify({ endpoint: provider.is_local_mode ? 'http://gentwo-vllm-embeddings:8000/v1/embeddings' : newEndpoint, config: { ...currentModel.config, is_local_mode: provider.is_local_mode, external_endpoint: newEndpoint } }) }); if (response.ok) { const result = await response.json(); console.log('BGE-M3 configuration updated:', result); // Sync configuration to tenant backend try { const syncResponse = await fetch('http://localhost:8002/api/embeddings/config/bge-m3', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ is_local_mode: provider.is_local_mode, external_endpoint: newEndpoint }) }); if (syncResponse.ok) { console.log('Configuration synced to tenant backend'); } else { console.warn('Failed to sync configuration to tenant backend'); } } catch (syncError) { console.warn('Error syncing to tenant backend:', syncError); } // Update localStorage as backup localStorage.setItem('bge_m3_config', JSON.stringify({ is_local_mode: provider.is_local_mode, external_endpoint: newEndpoint })); toast({ title: "Configuration Updated", description: "BGE-M3 external endpoint updated successfully", }); } } catch (error) { console.error('Error updating BGE-M3 external endpoint:', error); // Still update locally even if API call fails setProviders(prev => prev.map(p => { if (p.provider === 'bge_m3') { const updatedProvider = { ...p, external_endpoint: newEndpoint, endpoint: !p.is_local_mode ? newEndpoint : p.endpoint }; localStorage.setItem('bge_m3_config', JSON.stringify({ is_local_mode: p.is_local_mode, external_endpoint: newEndpoint })); return updatedProvider; } return p; })); } }; const handleSaveConfiguration = async () => { setSaving('all'); try { // TODO: API call to save all configurations await new Promise(resolve => setTimeout(resolve, 1000)); toast({ title: "Configuration Saved", description: "All endpoint configurations have been saved and synced to resource clusters", }); } catch (error) { toast({ title: "Save Failed", description: "Failed to save configuration changes", variant: "destructive", }); } setSaving(null); }; if (loading) { return
Loading configurations...
; } return (
{/* Provider Configurations */}
{providers.map((provider) => (
{getConnectionIcon(provider.is_external)} {provider.name}
{getStatusIcon(provider.health_status)} {provider.enabled ? "Enabled" : "Disabled"} {provider.requires_api_key && ( API Key Required )} {provider.model_type && ( {provider.model_type.toUpperCase()} )}
handleToggleProvider(provider.provider, provider.enabled)} /> {provider.is_custom && ( )}
{provider.description}
{provider.provider === 'bge_m3' ? ( // Special BGE-M3 configuration with local/external toggle <>
{provider.is_local_mode ? (
) : (
)} {provider.is_local_mode ? 'Local GT Edge' : 'External API'}
{provider.is_local_mode ? 'Docker Internal' : 'External Endpoint'}
handleToggleBGEM3Mode(!checked)} disabled={!provider.enabled} />
{ if (provider.is_local_mode) { handleEndpointChange(provider.provider, e.target.value); } else { // Update UI immediately handleExternalEndpointChange(e.target.value); } }} onBlur={(e) => { // Call API when user finishes editing (loses focus) if (!provider.is_local_mode) { handleUpdateExternalEndpoint(e.target.value); } }} placeholder={provider.is_local_mode ? 'http://gentwo-vllm-embeddings:8000/v1/embeddings' : 'http://10.0.1.50:8080'} disabled={!provider.enabled || provider.is_local_mode} className={provider.is_local_mode ? "bg-gray-100" : "border-blue-200 bg-blue-50"} /> {provider.is_local_mode && (

🏠 Uses local Docker container for embeddings

)} {!provider.is_local_mode && (

🌐 External BGE-M3 API endpoint (same model, external deployment)

)}
{provider.endpoint && ( )}
) : ( // Regular provider configuration <>
provider.is_custom && handleEndpointChange(provider.provider, e.target.value)} placeholder="https://api.example.com/v1" disabled={!provider.enabled || !provider.is_custom} readOnly={!provider.is_custom} className={`${provider.is_external ? "border-blue-200 bg-blue-50" : ""} ${!provider.is_custom ? "bg-muted cursor-not-allowed" : ""}`} />
{provider.endpoint && ( )}
)} {provider.last_test && (
Last tested: {new Date(provider.last_test).toLocaleString()} {provider.last_latency_ms && ( ({provider.last_latency_ms.toFixed(0)}ms) )}
)}
))}
{/* Global Actions */}
Changes will be automatically synced to all resource clusters
{/* Add Custom Endpoint Dialog */} Add Custom Endpoint Create a custom endpoint that can be used when adding models to the registry.
setAddForm(prev => ({ ...prev, name: e.target.value }))} />
setAddForm(prev => ({ ...prev, provider: e.target.value }))} />

Used as identifier - lowercase letters, numbers, and underscores only

setAddForm(prev => ({ ...prev, endpoint: e.target.value }))} />