import { useEffect, useRef, useState } from 'react'; import { Nav } from '~/components/nav'; const API_URL = import.meta.env.VITE_API_URL ?? 'https://api.t4t-gateway.com'; type Model = { id: string; object: 'model'; created: number; owned_by: string }; type Message = { role: 'user' | 'assistant' | 'system'; content: string }; type ChoiceMessage = { role: 'assistant'; content: string }; type Completion = { id: string; choices: { index: number; message: ChoiceMessage; finish_reason: string }[]; usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number }; }; export function meta() { return [{ title: 'Chat · t4t Gateway' }]; } async function api(path: string, init: RequestInit = {}): Promise { const token = typeof window !== 'undefined' ? localStorage.getItem('jwt') : null; if (!token) throw new Error('Not authenticated'); const res = await fetch(`${API_URL}${path}`, { ...init, headers: { ...(init.headers ?? {}), Authorization: `Bearer ${token}`, ...(init.body ? { 'Content-Type': 'application/json' } : {}), }, }); if (res.status === 401) { localStorage.removeItem('jwt'); window.location.href = '/login'; throw new Error('Unauthorized'); } const body = await res.json(); if (!res.ok) throw new Error(body?.message ?? body?.error ?? `Request failed (${res.status})`); return body as T; } export default function Chat() { const [models, setModels] = useState([]); const [model, setModel] = useState(''); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [sending, setSending] = useState(false); const [error, setError] = useState(null); const [loadingModels, setLoadingModels] = useState(true); const scrollRef = useRef(null); useEffect(() => { if (typeof window === 'undefined') return; if (!localStorage.getItem('jwt')) { window.location.href = '/login'; return; } (async () => { try { const list = await api<{ data: Model[] }>('/v1/models'); setModels(list.data); if (list.data.length > 0) setModel(list.data[0].id); } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setLoadingModels(false); } })(); }, []); useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [messages]); async function send(e: React.FormEvent) { e.preventDefault(); if (!input.trim() || !model || sending) return; setError(null); const userMsg: Message = { role: 'user', content: input }; const next = [...messages, userMsg]; setMessages(next); setInput(''); setSending(true); try { const completion = await api('/v1/chat/completions', { method: 'POST', body: JSON.stringify({ model, messages: next }), }); const reply = completion.choices[0]?.message; if (reply) { setMessages([...next, { role: reply.role, content: reply.content }]); } else { throw new Error('No reply from model'); } } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setSending(false); } } return (
); }