"use client"; import { useState, useEffect } from 'react'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; import { useToast } from '@/components/ui/use-toast'; import { Cpu, TestTube, RefreshCw, CheckCircle, AlertCircle, AlertTriangle, ExternalLink, Users, Info } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; interface CustomEndpoint { provider: string; name: string; endpoint: string; enabled: boolean; health_status: 'healthy' | 'unhealthy' | 'testing' | 'unknown'; description: string; is_external: boolean; requires_api_key: boolean; last_test?: string; is_custom?: boolean; model_type?: 'llm' | 'embedding' | 'both'; } interface AddModelDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onModelAdded?: () => void; } export default function AddModelDialog({ open, onOpenChange, onModelAdded }: AddModelDialogProps) { const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState<{ success: boolean; status?: 'healthy' | 'degraded' | 'unhealthy'; message: string; latency_ms?: number; error_type?: string; } | null>(null); const [customEndpoints, setCustomEndpoints] = useState([]); const [formData, setFormData] = useState({ model_id: '', name: '', provider: '', // No default provider model_type: '', endpoint: '', // No default endpoint description: '', context_window: '', max_tokens: '', dimensions: '', // For embedding models }); const { toast } = useToast(); // Load custom endpoints from localStorage useEffect(() => { const loadCustomEndpoints = () => { try { const stored = localStorage.getItem('custom_endpoints'); console.log('Raw stored endpoints from localStorage:', stored); if (stored) { const parsed = JSON.parse(stored); console.log('Parsed custom endpoints:', parsed); setCustomEndpoints(parsed); } else { console.log('No custom endpoints found in localStorage'); } } catch (error) { console.error('Failed to load custom endpoints:', error); } }; if (open) { loadCustomEndpoints(); } }, [open]); const handleProviderChange = (providerId: string) => { console.log('Provider changed to:', providerId); console.log('Available custom endpoints:', customEndpoints); // Check if this is a custom endpoint const customEndpoint = customEndpoints.find(ep => ep.provider === providerId); console.log('Found custom endpoint:', customEndpoint); if (customEndpoint) { // Selected a configured endpoint - keep the endpoint ID as provider for Select consistency console.log('Setting endpoint URL to:', customEndpoint.endpoint); setFormData(prev => ({ ...prev, provider: providerId, // Use the endpoint ID to maintain Select consistency endpoint: customEndpoint.endpoint, // Auto-fill the configured URL model_type: customEndpoint.model_type || prev.model_type })); } else { // Selected a default provider setFormData(prev => { const updated = { ...prev, provider: providerId }; // Auto-populate default endpoints switch (providerId) { case 'nvidia': updated.endpoint = 'https://integrate.api.nvidia.com/v1/chat/completions'; break; case 'groq': updated.endpoint = 'https://api.groq.com/openai/v1/chat/completions'; break; case 'ollama-dgx-x86': updated.endpoint = 'http://ollama-host:11434/v1/chat/completions'; break; case 'ollama-macos': updated.endpoint = 'http://host.docker.internal:11434/v1/chat/completions'; break; case 'local': updated.endpoint = 'http://localhost:8000/v1/chat/completions'; break; default: updated.endpoint = ''; } return updated; }); } }; const handleInputChange = (field: string, value: any) => { setFormData(prev => ({ ...prev, [field]: value })); }; const handleTestEndpoint = async () => { if (!formData.endpoint) { toast({ title: "Missing Endpoint", description: "Please enter an endpoint URL to test", variant: "destructive", }); return; } setTesting(true); setTestResult(null); try { // 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: formData.endpoint, provider: formData.provider }) }); const result = await response.json(); // Build message based on status let message = result.error || "Endpoint is responding correctly"; if (result.status === 'degraded' && !result.error) { message = "Endpoint responding but with high latency"; } setTestResult({ success: result.healthy || false, status: result.status || (result.healthy ? 'healthy' : 'unhealthy'), message: message, latency_ms: result.latency_ms, error_type: result.error_type }); } catch (error) { setTestResult({ success: false, status: 'unhealthy', message: "Connection test failed", error_type: 'connection_error' }); } finally { setTesting(false); } }; const handleSubmit = async () => { try { // Prepare submission data - resolve custom endpoint provider if needed const submissionData: Record = { ...formData }; // Check if the provider is actually a custom endpoint ID const customEndpoint = customEndpoints.find(ep => ep.provider === formData.provider); if (customEndpoint) { // Use the actual provider name from the custom endpoint submissionData.provider = customEndpoint.provider; } // Set default status (pricing is now managed on the Billing page) submissionData.status = { is_active: true, is_compound: false }; // Set default pricing (managed on Billing page) submissionData.cost_per_million_input = 0; submissionData.cost_per_million_output = 0; const apiUrl = '/api/v1/models/'; console.log('Making API request to:', apiUrl); console.log('Submission data:', submissionData); const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(submissionData) }); console.log('Response status:', response.status); console.log('Response headers:', Object.fromEntries(response.headers.entries())); if (response.ok) { const result = await response.json(); console.log('Success response:', result); toast({ title: "Model Added", description: `Successfully added ${formData.name} to the model registry`, }); // Reset form and close dialog setFormData({ model_id: '', name: '', provider: '', // No default provider model_type: '', endpoint: '', // No default endpoint description: '', context_window: '', max_tokens: '', dimensions: '', }); setTestResult(null); onOpenChange(false); // Notify parent to refresh data if (onModelAdded) { onModelAdded(); } } else { const errorData = await response.text(); console.error('API error response:', errorData); toast({ title: "Failed to Add Model", description: `Server returned ${response.status}: ${errorData.substring(0, 100)}`, variant: "destructive", }); } } catch (error) { console.error('Network error:', error); toast({ title: "Network Error", description: error instanceof Error ? error.message : "Could not connect to server", variant: "destructive", }); } }; return ( Add New Model Add a new AI model to the GT 2.0 registry. This will make it available across all clusters.
{/* Basic Information */}

Basic Information

handleInputChange('model_id', e.target.value)} placeholder="llama-3.3-70b-versatile" />
handleInputChange('name', e.target.value)} placeholder="Llama 3.3 70B Versatile" />
handleInputChange('endpoint', e.target.value)} placeholder={ formData.provider === 'groq' ? "https://api.groq.com/openai/v1/chat/completions" : "http://localhost:8000/v1/chat/completions" } className={formData.provider === 'local' ? "border-green-200 bg-green-50" : "border-purple-200 bg-purple-50"} />
{formData.provider === 'groq' && (

Groq Setup Steps:

  1. Create a Groq account {' '} and{' '} generate an API key
  2. Add your API key on the{' '} API Keys page
  3. Configure your model in the Control Panel:
    • Model ID: Use the exact Groq model name (e.g.,{' '} llama-3.3-70b-versatile)
    • Display Name: A friendly name (e.g., "Llama 3.3 70B")
    • Model Type: Select LLM for chat models (most common for AI agents)
    • Context Window: Check{' '} Groq docs {' '} (e.g., 128K for Llama 3.3)
    • Max Tokens: Typically 8192; check model docs
)} {formData.provider === 'nvidia' && (

NVIDIA NIM Setup Steps:

  1. Create an NVIDIA account {' '} and generate an API key
  2. Add your API key on the{' '} API Keys page
  3. Configure your model in the Control Panel:
    • Model ID: Use the NVIDIA model name (e.g.,{' '} meta/llama-3.1-70b-instruct)
    • Display Name: A friendly name (e.g., "Llama 3.1 70B")
    • Model Type: Select LLM for chat models (most common for AI agents)
    • Context Window: Check the{' '} model page {' '} (e.g., 128K for Llama 3.1)
    • Max Tokens: Typically 4096-8192; check model docs
)} {formData.provider === 'local' && (

Set this to any OpenAI Compatible API endpoint that doesn't require API authentication.

)} {(formData.provider === 'ollama-dgx-x86' || formData.provider === 'ollama-macos') && (

Ollama Setup Steps:

  1. Download and install {' '} Ollama
  2. Select and download your{' '} Model {' '} (run: ollama pull model-name)
  3. Configure your model in the Control Panel:
    • Model ID: Use the exact Ollama name with size tag (e.g., llama3.2:3b, mistral:7b)
    • Display Name: A friendly name (e.g., "Llama 3.2 3B")
    • Model Type: Select LLM for chat models (most common for AI agents)
    • Context Window: Find on the model's Ollama page (e.g., 128K for Llama 3.2)
    • Max Tokens: Typically 2048-4096 for responses; check model docs
)} {testResult && (
{testResult.status === 'healthy' && } {testResult.status === 'degraded' && } {testResult.status === 'unhealthy' && }
{testResult.message} {testResult.latency_ms && ( ({testResult.latency_ms.toFixed(0)}ms) )}
)}