'use client' /** * LLM Comparison Tool * * Vergleicht Antworten von verschiedenen LLM-Providern: * - OpenAI/ChatGPT * - Claude * - Self-hosted + Tavily * - Self-hosted + EduSearch * * Ermoeglicht Parameter-Tuning und System Prompt Management */ import AdminLayout from '@/components/admin/AdminLayout' import { useState, useEffect } from 'react' interface LLMResponse { provider: string model: string response: string latency_ms: number tokens_used?: number search_results?: Array<{ title: string url: string content: string score?: number }> error?: string timestamp: string } interface ComparisonResult { comparison_id: string prompt: string system_prompt?: string responses: LLMResponse[] created_at: string } interface SystemPrompt { id: string name: string prompt: string created_at: string updated_at?: string } const providerColors: Record = { openai: { bg: 'bg-emerald-50', border: 'border-emerald-300', text: 'text-emerald-700' }, claude: { bg: 'bg-orange-50', border: 'border-orange-300', text: 'text-orange-700' }, selfhosted_tavily: { bg: 'bg-blue-50', border: 'border-blue-300', text: 'text-blue-700' }, selfhosted_edusearch: { bg: 'bg-purple-50', border: 'border-purple-300', text: 'text-purple-700' }, } const providerLabels: Record = { openai: 'OpenAI GPT-4o-mini', claude: 'Claude 3.5 Sonnet', selfhosted_tavily: 'Self-hosted + Tavily', selfhosted_edusearch: 'Self-hosted + EduSearch', } export default function LLMComparePage() { // State const [prompt, setPrompt] = useState('') const [systemPrompt, setSystemPrompt] = useState('') const [selectedSystemPromptId, setSelectedSystemPromptId] = useState('default') const [systemPrompts, setSystemPrompts] = useState([]) // Provider toggles const [enableOpenAI, setEnableOpenAI] = useState(true) const [enableClaude, setEnableClaude] = useState(true) const [enableTavily, setEnableTavily] = useState(true) const [enableEduSearch, setEnableEduSearch] = useState(true) // Parameters const [model, setModel] = useState('llama3.2:3b') const [temperature, setTemperature] = useState(0.7) const [topP, setTopP] = useState(0.9) const [maxTokens, setMaxTokens] = useState(2048) const [searchResultsCount, setSearchResultsCount] = useState(5) // EduSearch Filters const [eduSearchLanguage, setEduSearchLanguage] = useState('de') const [eduSearchDocType, setEduSearchDocType] = useState('') const [eduSearchSchoolLevel, setEduSearchSchoolLevel] = useState('') // Results const [isLoading, setIsLoading] = useState(false) const [result, setResult] = useState(null) const [history, setHistory] = useState([]) const [error, setError] = useState(null) // UI State const [showSettings, setShowSettings] = useState(false) const [showHistory, setShowHistory] = useState(false) const [editingPrompt, setEditingPrompt] = useState(null) // API Base URL const API_URL = process.env.NEXT_PUBLIC_LLM_GATEWAY_URL || 'http://localhost:8082' const API_KEY = process.env.NEXT_PUBLIC_LLM_API_KEY || 'dev-key' // Load system prompts useEffect(() => { loadSystemPrompts() loadHistory() }, []) // Update system prompt when selection changes useEffect(() => { const selected = systemPrompts.find((p) => p.id === selectedSystemPromptId) if (selected) { setSystemPrompt(selected.prompt) } }, [selectedSystemPromptId, systemPrompts]) const loadSystemPrompts = async () => { try { const response = await fetch(`${API_URL}/v1/comparison/prompts`, { headers: { Authorization: `Bearer ${API_KEY}` }, }) if (response.ok) { const data = await response.json() setSystemPrompts(data.prompts || []) } } catch (e) { console.error('Failed to load system prompts:', e) // Fallback prompts setSystemPrompts([ { id: 'default', name: 'Standard Lehrer-Assistent', prompt: 'Du bist ein hilfreicher Assistent fuer Lehrkraefte in Deutschland.', created_at: new Date().toISOString(), }, ]) } } const loadHistory = async () => { try { const response = await fetch(`${API_URL}/v1/comparison/history?limit=20`, { headers: { Authorization: `Bearer ${API_KEY}` }, }) if (response.ok) { const data = await response.json() setHistory(data.comparisons || []) } } catch (e) { console.error('Failed to load history:', e) } } const runComparison = async () => { if (!prompt.trim()) { setError('Bitte geben Sie einen Prompt ein') return } setIsLoading(true) setError(null) setResult(null) try { const filters: Record = {} if (eduSearchLanguage) filters.language = [eduSearchLanguage] if (eduSearchDocType) filters.doc_type = [eduSearchDocType] if (eduSearchSchoolLevel) filters.school_level = [eduSearchSchoolLevel] const response = await fetch(`${API_URL}/v1/comparison/run`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_KEY}`, }, body: JSON.stringify({ prompt, system_prompt: systemPrompt || undefined, enable_openai: enableOpenAI, enable_claude: enableClaude, enable_selfhosted_tavily: enableTavily, enable_selfhosted_edusearch: enableEduSearch, selfhosted_model: model, temperature, top_p: topP, max_tokens: maxTokens, search_results_count: searchResultsCount, edu_search_filters: Object.keys(filters).length > 0 ? filters : undefined, }), }) if (!response.ok) { throw new Error(`API Error: ${response.status}`) } const data = await response.json() setResult(data) // Refresh history loadHistory() } catch (e) { setError(e instanceof Error ? e.message : 'Unbekannter Fehler') } finally { setIsLoading(false) } } const saveSystemPrompt = async () => { if (!editingPrompt) return try { const isNew = !editingPrompt.id || editingPrompt.id.startsWith('new-') const url = isNew ? `${API_URL}/v1/comparison/prompts` : `${API_URL}/v1/comparison/prompts/${editingPrompt.id}` const response = await fetch( `${url}?name=${encodeURIComponent(editingPrompt.name)}&prompt=${encodeURIComponent(editingPrompt.prompt)}`, { method: isNew ? 'POST' : 'PUT', headers: { Authorization: `Bearer ${API_KEY}` }, } ) if (response.ok) { setEditingPrompt(null) loadSystemPrompts() } } catch (e) { console.error('Failed to save prompt:', e) } } const ResponseCard = ({ response }: { response: LLMResponse }) => { const colors = providerColors[response.provider] || { bg: 'bg-slate-50', border: 'border-slate-300', text: 'text-slate-700', } const label = providerLabels[response.provider] || response.provider return (
{/* Header */}

{label}

{response.model}

{response.latency_ms}ms
{response.tokens_used &&
{response.tokens_used} tokens
}
{/* Content */}
{response.error ? (
Fehler: {response.error}
) : (
                {response.response}
              
)}
{/* Search Results (for self-hosted) */} {response.search_results && response.search_results.length > 0 && (
{response.search_results.length} Suchergebnisse anzeigen
    {response.search_results.map((sr, idx) => (
  • {sr.title || 'Untitled'}

    {sr.content}

    {sr.score !== undefined && ( Score: {sr.score.toFixed(2)} )}
  • ))}
)}
) } return (
{/* Left Column: Input & Settings */}
{/* Prompt Input */}

Prompt

{/* System Prompt Selector */}
{/* System Prompt Preview */}