import { useState, useRef, useEffect } from 'react' import { useQuery } from '@tanstack/react-query' import { apiFetch } from '@/lib/api' import { Button } from '@/components/ui/button' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { PageHeader } from '@/components/page-header' import { Markdown } from '@/components/markdown' interface FallbackEntry { modelDbId: number priority: number enabled: boolean platform: string modelId: string displayName: string sizeLabel: string keyCount: number } interface ChatMessage { role: 'user' | 'assistant' content: string meta?: { platform?: string model?: string latency?: number fallbackAttempts?: number } } export default function PlaygroundPage() { const [messages, setMessages] = useState([]) const [input, setInput] = useState('') const [loading, setLoading] = useState(false) const [selectedModel, setSelectedModel] = useState('auto') const messagesEndRef = useRef(null) const inputRef = useRef(null) const { data: keyData } = useQuery<{ apiKey: string }>({ queryKey: ['unified-key'], queryFn: () => apiFetch('/api/settings/api-key'), }) const { data: fallbackEntries = [] } = useQuery({ queryKey: ['fallback'], queryFn: () => apiFetch('/api/fallback'), }) const availableModels = fallbackEntries.filter(e => e.keyCount > 0 && e.enabled) useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages]) const handleSend = async () => { const text = input.trim() if (!text || loading) return const userMsg: ChatMessage = { role: 'user', content: text } const newMessages = [...messages, userMsg] setMessages(newMessages) setInput('') setLoading(true) inputRef.current?.focus() try { const headers: Record = { 'Content-Type': 'application/json' } if (keyData?.apiKey) headers['Authorization'] = `Bearer ${keyData.apiKey}` const body: any = { messages: newMessages.map(m => ({ role: m.role, content: m.content })), } if (selectedModel !== 'auto') body.model = selectedModel const base = import.meta.env.BASE_URL.replace(/\/$/, '') const start = Date.now() const res = await fetch(`${base}/v1/chat/completions`, { method: 'POST', headers, body: JSON.stringify(body), }) const latency = Date.now() - start const routedVia = res.headers.get('X-Routed-Via') const fallbackAttempts = res.headers.get('X-Fallback-Attempts') if (!res.ok) { const err = await res.json().catch(() => ({ error: { message: `HTTP ${res.status}` } })) setMessages([...newMessages, { role: 'assistant', content: `Error: ${err.error?.message ?? 'Unknown error'}`, }]) return } const data = await res.json() const content = data.choices?.[0]?.message?.content ?? JSON.stringify(data, null, 2) const via = data._routed_via ?? (routedVia ? { platform: routedVia.split('/')[0], model: routedVia.split('/').slice(1).join('/'), } : undefined) setMessages([...newMessages, { role: 'assistant', content, meta: { platform: via?.platform, model: via?.model, latency, fallbackAttempts: fallbackAttempts ? parseInt(fallbackAttempts) : undefined, }, }]) } catch (err: any) { setMessages([...newMessages, { role: 'assistant', content: `Error: ${err.message}`, }]) } finally { setLoading(false) setTimeout(() => inputRef.current?.focus(), 0) } } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSend() } } const handleClear = () => { setMessages([]) inputRef.current?.focus() } const activeModelLabel = selectedModel === 'auto' ? 'Auto (fallback chain)' : availableModels.find(m => m.modelId === selectedModel)?.displayName ?? selectedModel return (
{messages.length > 0 && ( )} } />
{messages.length === 0 ? (

Send a message to get started.

Using {activeModelLabel}. Switch models in the selector above.

) : ( <> {messages.map((msg, i) => (
{msg.role === 'assistant' ? ( {msg.content} ) : (
{msg.content}
)} {msg.meta && (
{msg.meta.platform && {msg.meta.platform}} {msg.meta.model && · {msg.meta.model}} {msg.meta.latency != null && · {msg.meta.latency} ms} {msg.meta.fallbackAttempts != null && msg.meta.fallbackAttempts > 0 && ( · {msg.meta.fallbackAttempts} fallback{msg.meta.fallbackAttempts > 1 ? 's' : ''} )}
)}
))} {loading && (
)}
)}