'use client' import { useState, useCallback, useRef, useEffect } from 'react' import { useTheme } from '@/lib/ThemeContext' import { useWorksheet } from '@/lib/worksheet-editor/WorksheetContext' interface AIPromptBarProps { className?: string } export function AIPromptBar({ className = '' }: AIPromptBarProps) { const { isDark } = useTheme() const { canvas, saveToHistory } = useWorksheet() const [prompt, setPrompt] = useState('') const [isLoading, setIsLoading] = useState(false) const [lastResult, setLastResult] = useState(null) const [showHistory, setShowHistory] = useState(false) const [promptHistory, setPromptHistory] = useState([]) const inputRef = useRef(null) // Load prompt history from localStorage useEffect(() => { const stored = localStorage.getItem('worksheet_prompt_history') if (stored) { try { setPromptHistory(JSON.parse(stored)) } catch (e) { // Ignore parse errors } } }, []) // Save prompt to history const addToHistory = (newPrompt: string) => { const updated = [newPrompt, ...promptHistory.filter(p => p !== newPrompt)].slice(0, 10) setPromptHistory(updated) localStorage.setItem('worksheet_prompt_history', JSON.stringify(updated)) } // Handle AI prompt submission const handleSubmit = useCallback(async () => { if (!prompt.trim() || !canvas || isLoading) return setIsLoading(true) setLastResult(null) addToHistory(prompt.trim()) try { // Get current canvas state const canvasJSON = JSON.stringify(canvas.toJSON()) // Get API base URL (use same protocol as page) const { hostname, protocol } = window.location const apiBase = hostname === 'localhost' ? 'http://localhost:8086' : `${protocol}//${hostname}:8086` // Send prompt to AI endpoint with timeout (3 minutes for large models) const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 180000) // 3 minutes try { const response = await fetch(`${apiBase}/api/v1/worksheet/ai-modify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: prompt.trim(), canvas_json: canvasJSON, model: 'qwen2.5vl:32b' }), signal: controller.signal }) clearTimeout(timeoutId) if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })) throw new Error(error.detail || `HTTP ${response.status}`) } const result = await response.json() // Apply changes to canvas if we got new JSON if (result.modified_canvas_json) { const parsedJSON = JSON.parse(result.modified_canvas_json) // Preserve current background if not specified in response const currentBg = canvas.backgroundColor canvas.loadFromJSON(parsedJSON, () => { // Ensure white background is set (Fabric.js sometimes loses it) if (!canvas.backgroundColor || canvas.backgroundColor === 'transparent' || canvas.backgroundColor === '#000000') { canvas.backgroundColor = parsedJSON.background || currentBg || '#ffffff' } canvas.renderAll() saveToHistory(`AI: ${prompt.trim().substring(0, 30)}`) }) setLastResult(result.message || 'Aenderungen angewendet') } else if (result.message) { setLastResult(result.message) } setPrompt('') } catch (fetchError) { clearTimeout(timeoutId) throw fetchError } } catch (error) { console.error('AI prompt error:', error) if (error instanceof Error && error.name === 'AbortError') { setLastResult('Zeitüberschreitung - das KI-Modell braucht zu lange. Bitte versuchen Sie es erneut.') } else { setLastResult(`Fehler: ${error instanceof Error ? error.message : 'Unbekannter Fehler'}`) } } finally { setIsLoading(false) } }, [prompt, canvas, isLoading, saveToHistory]) // Handle keyboard shortcuts const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSubmit() } if (e.key === 'ArrowUp' && promptHistory.length > 0 && !prompt) { e.preventDefault() setPrompt(promptHistory[0]) } if (e.key === 'Escape') { setPrompt('') setShowHistory(false) inputRef.current?.blur() } } // Example prompts const examplePrompts = [ 'Fuege eine Ueberschrift "Arbeitsblatt" oben hinzu', 'Erstelle ein 3x4 Raster fuer Aufgaben', 'Fuege Linien fuer Schueler-Antworten hinzu', 'Mache alle Texte groesser', 'Zentriere alle Elemente', 'Fuege Nummerierung 1-10 hinzu' ] // Glassmorphism styles const barStyle = isDark ? 'backdrop-blur-xl bg-white/10 border border-white/20' : 'backdrop-blur-xl bg-white/70 border border-black/10 shadow-lg' const inputStyle = isDark ? 'bg-white/10 border-white/20 text-white placeholder-white/40 focus:border-purple-400 focus:ring-purple-400/30' : 'bg-white/50 border-black/10 text-slate-900 placeholder-slate-400 focus:border-purple-500 focus:ring-purple-500/30' const buttonStyle = isDark ? 'bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:shadow-lg hover:shadow-purple-500/30' : 'bg-gradient-to-r from-purple-600 to-pink-600 text-white hover:shadow-lg hover:shadow-purple-600/30' return (
{/* Prompt Input */}
✨
setPrompt(e.target.value)} onKeyDown={handleKeyDown} onFocus={() => setShowHistory(true)} onBlur={() => setTimeout(() => setShowHistory(false), 200)} placeholder="Beschreibe, was du aendern moechtest... (z.B. 'Fuege eine Ueberschrift hinzu')" disabled={isLoading} className={`w-full px-4 py-3 rounded-xl border text-sm transition-all focus:outline-none focus:ring-2 ${inputStyle} ${ isLoading ? 'opacity-50 cursor-not-allowed' : '' }`} /> {/* History Dropdown */} {showHistory && promptHistory.length > 0 && !prompt && (
Letzte Prompts
{promptHistory.map((historyItem, idx) => ( ))}
)}
{/* Result Message */} {lastResult && (
{lastResult}
)} {/* Example Prompts */} {!prompt && !isLoading && (
{examplePrompts.slice(0, 4).map((example, idx) => ( ))}
)}
) }