import React, { useState, useEffect, useRef } from 'react'; import ReactMarkdown from 'react-markdown'; import { Play, Square, FileText, CheckCircle2, AlertCircle, Loader2, Sparkles, ChevronDown, ChevronRight } from 'lucide-react'; import toast from 'react-hot-toast'; interface CompletedSection { title: string; content: string; index: number; } interface AIGeneratorPanelProps { projectId: string; onCompleted: () => void; } const BACKEND_URL = import.meta.env.VITE_API_URL || 'http://localhost:8001'; const AIGeneratorPanel: React.FC = ({ projectId, onCompleted }) => { const [isGenerating, setIsGenerating] = useState(false); const [currentStatus, setCurrentStatus] = useState('Gotowy do rozpoczęcia automatycznego generowania wniosku.'); const [currentSection, setCurrentSection] = useState(null); const [completedSections, setCompletedSections] = useState([]); const [expandedSection, setExpandedSection] = useState(null); const [fullDocument, setFullDocument] = useState(null); const [error, setError] = useState(null); const [progress, setProgress] = useState(0); const [missingDataQuestion, setMissingDataQuestion] = useState(null); const [userResponse, setUserResponse] = useState(''); const eventSourceRef = useRef(null); const endRef = useRef(null); useEffect(() => { if (endRef.current && isGenerating) { endRef.current.scrollIntoView({ behavior: 'smooth' }); } }, [completedSections, isGenerating]); useEffect(() => { return () => { eventSourceRef.current?.close(); }; }, []); const handleStartGeneration = () => { if (eventSourceRef.current) eventSourceRef.current.close(); // Użyj toast zamiast window.confirm dla spójnego UX toast((t) => (
🚀 Uruchomić Autopilota AI?
Agent wygeneruje wszystkie sekcje wniosku. Proces zajmie kilka minut — nie zamykaj karty.
), { duration: Infinity, style: { background: '#1e1b4b', border: '1px solid rgba(139,92,246,0.3)' } }); }; const startGeneration = (resume: boolean = false) => { setIsGenerating(true); if (!resume) { setError(null); setCompletedSections([]); setFullDocument(null); setCurrentSection(null); setProgress(0); } setCurrentStatus(resume ? 'Wznawianie połączenia z agentem LangGraph...' : 'Nawiązywanie połączenia z agentem LangGraph...'); const token = localStorage.getItem('token') || ''; const url = `${BACKEND_URL}/api/generator/stream?project_id=${projectId}&resume=${resume}&token=${encodeURIComponent(token)}`; const source = new EventSource(url); eventSourceRef.current = source; // Agent zaczął pisać konkretną sekcję source.addEventListener('section_started', (event) => { const title = event.data; setCurrentSection(title); setCurrentStatus(`Agent pisze: „${title}"...`); }); // Sekcja ukończona — dodaj do listy source.addEventListener('section_completed', (event) => { try { const data: CompletedSection = JSON.parse(event.data); setCompletedSections(prev => { const updated = [...prev, data]; // Szacunkowy postęp (SMART ma 16 sekcji) setProgress(Math.min(95, Math.round((updated.length / 16) * 95))); return updated; }); setCurrentSection(null); toast.success(`✅ Ukończono: ${data.title}`, { duration: 3000 }); } catch { console.error('SSE parse error: section_completed', event.data); } }); // Wszystko gotowe source.addEventListener('document_done', (event) => { try { const data = JSON.parse(event.data); setFullDocument(data.full_content); setProgress(100); setCurrentStatus('Wniosek gotowy! Sekcje zostały zapisane w projekcie.'); setIsGenerating(false); setCurrentSection(null); source.close(); toast.success('🎉 Autopilot AI zakończył generowanie wniosku!', { duration: 6000 }); // Odśwież dane projektu (sekcje zapisane przez backend) setTimeout(() => onCompleted(), 500); } catch { console.error('SSE parse error: document_done', event.data); } }); // Utrata połączenia source.addEventListener('waiting_for_user_input', (event) => { try { const data = JSON.parse(event.data); if (data.status === 'WAITING_FOR_USER_INPUT') { setMissingDataQuestion(data.missing_data_question); setCurrentStatus('Wstrzymano — oczekiwanie na Twoją odpowiedź'); source.close(); } } catch { console.error('SSE parse error: waiting_for_user_input', event.data); } }); // Błąd z backendu source.addEventListener('error', (event: any) => { let errMsg = 'Nieoczekiwany błąd agenta.'; if (event.data) { try { const parsed = JSON.parse(event.data); errMsg = typeof parsed.detail === 'string' ? parsed.detail : JSON.stringify(parsed.detail || parsed); } catch { errMsg = typeof event.data === 'string' ? event.data : JSON.stringify(event.data); } } if (typeof errMsg !== 'string') { errMsg = String(errMsg); } setError(errMsg); toast.error(`Zatrzymano: ${errMsg}`); setIsGenerating(false); setCurrentSection(null); source.close(); setCurrentStatus('Generowanie zatrzymane z powodu błędu.'); }); // Utrata połączenia source.onerror = () => { if (isGenerating && !fullDocument) { // EventSource automatycznie próbuje się ponownie — nie przeszkadzaj setCurrentStatus('Łączenie ponowne ze strumieniem...'); } }; }; const handleStop = () => { eventSourceRef.current?.close(); setIsGenerating(false); setCurrentSection(null); setCurrentStatus('Przerwano przez użytkownika.'); toast('Zatrzymano Agenta AI.', { icon: '🛑' }); }; const submitUserResponse = async () => { try { const token = localStorage.getItem('token') || ''; const res = await fetch(`${BACKEND_URL}/api/generator/resume`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ project_id: projectId, user_response: userResponse }) }); if (!res.ok) throw new Error('Błąd wysyłania odpowiedzi'); setMissingDataQuestion(null); setUserResponse(''); // Wznów strumień startGeneration(true); } catch (err: any) { toast.error(err.message || 'Wystąpił błąd'); } }; const statusColor = error ? 'var(--accent-red)' : isGenerating ? 'var(--accent-purple)' : fullDocument ? 'var(--accent-green)' : 'var(--text-secondary)'; return (
{/* === HEADER CARD === */}

Autopilot AI — Generator Wniosku

Agent LangGraph napisze wszystkie sekcje wniosku jednocześnie, korzystając z bazy wiedzy RAG.

{!isGenerating ? ( ) : ( )}
{/* Status bar */}
{isGenerating ? : error ? : } {currentStatus}
{/* Progress bar */} {(isGenerating || progress > 0) && (
{currentSection ? `Pisze: ${currentSection}` : completedSections.length > 0 ? `${completedSections.length} sekcji gotowych` : 'Inicjalizacja...'} {progress}%
)} {/* Error box */} {error && (
Błąd agenta: {error}
)}
{/* === COMPLETED SECTIONS === */} {completedSections.length > 0 && (

Wygenerowane sekcje ({completedSections.length})

{isGenerating && ( Agent pracuje... )}
{completedSections.map((sec, idx) => (
{/* Accordion header */} {/* Accordion content */} {expandedSection === idx && (
{sec.content}
)}
))} {/* Placeholder sekcji w trakcie pisania */} {isGenerating && currentSection && (
Pisze: {currentSection}...
)}
)} {/* === SUCCESS BANNER === */} {fullDocument && (

Wniosek wygenerowany pomyślnie!

Sekcje zostały zapisane w projekcie. Przejdź do zakładki Sekcje wniosku, aby je przejrzeć i edytować, lub do Wniosku Końcowego, by skompilować i wyeksportować.

)} {/* === INFO CARD (gdy nic nie uruchomiono) === */} {!isGenerating && !fullDocument && completedSections.length === 0 && !error && (

Jak działa Autopilot AI?

{[ { n: '1', t: 'Planowanie', d: 'Agent dobiera odpowiednie sekcje dla Twojego programu dotacyjnego.' }, { n: '2', t: 'Pobieranie kontekstu', d: 'Dla każdej sekcji system przeszukuje bazę wiedzy RAG z regulaminami i wytycznymi.' }, { n: '3', t: 'Generowanie', d: 'LLM pisze treść każdej sekcji w formacie gotowym do edycji i eksportu.' }, { n: '4', t: 'Zapis do projektu', d: 'Wygenerowane sekcje są automatycznie zapisywane — możesz je od razu edytować.' }, ].map(step => (
{step.n}
{step.t}
{step.d}
))}
⚠️ Treść wygenerowana przez AI na podstawie bazy wiedzy. Zalecana weryfikacja przez doradcę lub prawnika przed wysłaniem wniosku.
)} {/* === MISSING DATA MODAL (HIL) === */} {missingDataQuestion && (

Agent potrzebuje informacji

{missingDataQuestion}