"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { motion } from "framer-motion"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { SimpleThemeToggle } from "@/components/ui/simple-theme-toggle"; import { Clock, Info, Lock, MessageCircle, Plus, Upload } from "lucide-react"; const API_ENDPOINT = "https://n8n.jonasbomfim.store/webhook/cd7d10e6-bcfc-4f3a-b649-351d12b714f1"; const FALLBACK_RESPONSE = "Tive um problema para responder agora. Tente novamente em alguns instantes."; export interface ChatMessage { id: string; sender: "user" | "assistant"; content: string; createdAt: string; } export interface ChatSession { id: string; startedAt: string; updatedAt: string; topic: string; messages: ChatMessage[]; } interface AIAssistantInterfaceProps { onOpenDocuments?: () => void; onOpenChat?: () => void; history?: ChatSession[]; onAddHistory?: (session: ChatSession) => void; onClearHistory?: () => void; } export function AIAssistantInterface({ onOpenDocuments, onOpenChat, history: externalHistory, onAddHistory, onClearHistory, }: AIAssistantInterfaceProps) { const [question, setQuestion] = useState(""); const [internalHistory, setInternalHistory] = useState(externalHistory ?? []); const [activeSessionId, setActiveSessionId] = useState(null); const [manualSelection, setManualSelection] = useState(false); const [historyPanelOpen, setHistoryPanelOpen] = useState(false); const messageListRef = useRef(null); const pdfInputRef = useRef(null); const [pdfFile, setPdfFile] = useState(null); // arquivo PDF selecionado const history = internalHistory; const historyRef = useRef(history); const baseGreeting = "Olá, eu sou Zoe. Como posso ajudar hoje?"; const greetingWords = useMemo(() => baseGreeting.split(" "), [baseGreeting]); const [typedGreeting, setTypedGreeting] = useState(""); const [typedIndex, setTypedIndex] = useState(0); const [isTypingGreeting, setIsTypingGreeting] = useState(true); const [gradientGreeting, plainGreeting] = useMemo(() => { if (!typedGreeting) return ["", ""] as const; const separatorIndex = typedGreeting.indexOf("Como"); if (separatorIndex === -1) { return [typedGreeting, ""] as const; } const gradientPart = typedGreeting.slice(0, separatorIndex).trimEnd(); const plainPart = typedGreeting.slice(separatorIndex).trimStart(); return [gradientPart, plainPart] as const; }, [typedGreeting]); useEffect(() => { if (externalHistory) { setInternalHistory(externalHistory); } }, [externalHistory]); useEffect(() => { historyRef.current = history; }, [history]); const activeSession = useMemo( () => history.find((session) => session.id === activeSessionId) ?? null, [history, activeSessionId] ); const activeMessages = activeSession?.messages ?? []; const formatTime = useCallback( (value: string) => new Date(value).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit", }), [] ); const upsertSession = useCallback( (session: ChatSession) => { if (onAddHistory) { onAddHistory(session); } else { setInternalHistory((prev) => { const index = prev.findIndex((s) => s.id === session.id); if (index >= 0) { const updated = [...prev]; updated[index] = session; return updated; } return [...prev, session]; }); } setActiveSessionId(session.id); setManualSelection(false); }, [onAddHistory] ); const sendMessageToAssistant = useCallback( async (prompt: string, baseSession: ChatSession) => { const sessionId = baseSession.id; const appendAssistantMessage = (content: string) => { const createdAt = new Date().toISOString(); const assistantMessage: ChatMessage = { id: `msg-assistant-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, sender: "assistant", content, createdAt, }; const latestSession = historyRef.current.find((s) => s.id === sessionId) ?? baseSession; const updatedSession: ChatSession = { ...latestSession, updatedAt: createdAt, messages: [...latestSession.messages, assistantMessage], }; upsertSession(updatedSession); }; try { let replyText = ""; let response: Response; if (pdfFile) { // Monta FormData conforme especificação: campos 'pdf' e 'message' const formData = new FormData(); formData.append("pdf", pdfFile); formData.append("message", prompt); response = await fetch(API_ENDPOINT, { method: "POST", body: formData, // multipart/form-data gerenciado pelo browser }); } else { response = await fetch(API_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ message: prompt }), }); } const rawPayload = await response.text(); if (rawPayload.trim()) { try { const parsed = JSON.parse(rawPayload) as { message?: unknown; reply?: unknown }; if (typeof parsed.reply === "string") { replyText = parsed.reply.trim(); } else if (typeof parsed.message === "string") { replyText = parsed.message.trim(); } } catch (error) { console.error("[ZoeIA] Resposta JSON inválida", error, rawPayload); } } appendAssistantMessage(replyText || FALLBACK_RESPONSE); } catch (error) { console.error("[ZoeIA] Erro ao buscar resposta da API", error); appendAssistantMessage(FALLBACK_RESPONSE); } }, [upsertSession, pdfFile] ); const handleSendMessage = () => { const trimmed = question.trim(); if (!trimmed) return; const now = new Date(); const userMessage: ChatMessage = { id: `msg-user-${now.getTime()}`, sender: "user", content: trimmed, createdAt: now.toISOString(), }; const session = history.find((s) => s.id === activeSessionId); const sessionToUse: ChatSession = session ? { ...session, updatedAt: userMessage.createdAt, messages: [...session.messages, userMessage], } : { id: `session-${now.getTime()}`, startedAt: now.toISOString(), updatedAt: userMessage.createdAt, topic: trimmed.length > 60 ? `${trimmed.slice(0, 57)}…` : trimmed, messages: [userMessage], }; upsertSession(sessionToUse); setQuestion(""); setHistoryPanelOpen(false); void sendMessageToAssistant(trimmed, sessionToUse); }; const handleSelectPdf = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file && file.type === "application/pdf") { setPdfFile(file); } // Permite re-selecionar o mesmo arquivo e.target.value = ""; }; const removePdf = () => setPdfFile(null); return (
{/* Área superior exibindo PDF selecionado */} {pdfFile && (

{pdfFile.name}

PDF anexado • {(pdfFile.size / 1024).toFixed(1)} KB

)} {/* Lista de mensagens */}
{activeMessages.length === 0 && (

Nenhuma mensagem ainda. Envie uma pergunta.

)} {activeMessages.map((m) => (
{m.content}
{formatTime(m.createdAt)}
))}
{/* Input & ações */}
setQuestion(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } }} />
{pdfFile ? "A próxima mensagem será enviada junto ao PDF como multipart/form-data." : "Selecione um PDF para anexar ao próximo envio."}
); }