From 964e25bd7e4749485999c3aaad07d9ff30ff0186 Mon Sep 17 00:00:00 2001 From: Jonas Francisco Date: Fri, 21 Nov 2025 23:12:29 -0300 Subject: [PATCH] =?UTF-8?q?fix(ia)=20ajuste=20na=20integra=C3=A7=C3=A3o=20?= =?UTF-8?q?da=20IA=20para=20suportar=20pdf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ZoeIA/ai-assistant-interface.tsx | 133 ++++++++++++++++-- .../components/ui/file-upload-and-chat.tsx | 59 ++++---- 2 files changed, 148 insertions(+), 44 deletions(-) diff --git a/susconecta/components/ZoeIA/ai-assistant-interface.tsx b/susconecta/components/ZoeIA/ai-assistant-interface.tsx index 6aa58c2..b63b85d 100644 --- a/susconecta/components/ZoeIA/ai-assistant-interface.tsx +++ b/susconecta/components/ZoeIA/ai-assistant-interface.tsx @@ -46,6 +46,8 @@ export function AIAssistantInterface({ 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?"; @@ -138,16 +140,29 @@ export function AIAssistantInterface({ }; try { - const response = await fetch(API_ENDPOINT, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ message: prompt }), - }); + 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(); - let replyText = ""; if (rawPayload.trim()) { try { @@ -168,7 +183,7 @@ export function AIAssistantInterface({ appendAssistantMessage(FALLBACK_RESPONSE); } }, - [upsertSession] + [upsertSession, pdfFile] ); const handleSendMessage = () => { @@ -204,5 +219,103 @@ export function AIAssistantInterface({ void sendMessageToAssistant(trimmed, sessionToUse); }; - return
/* restante da interface (UI) omitida para focar na lógica */
; + 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."} +
+
+ ); } diff --git a/susconecta/components/ui/file-upload-and-chat.tsx b/susconecta/components/ui/file-upload-and-chat.tsx index c372c5c..f39b467 100644 --- a/susconecta/components/ui/file-upload-and-chat.tsx +++ b/susconecta/components/ui/file-upload-and-chat.tsx @@ -124,44 +124,28 @@ const FileUploadChat = ({ onOpenVoice }: { onOpenVoice?: () => void }) => { setUploadedFiles((prev) => prev.filter((file) => file.id !== fileId)); }; + const generateAIResponse = useCallback( async (userMessage: string, files: any[]) => { try { - const hasAudio = files.some((file) => - file.name.toLowerCase().endsWith(".mp3") - ); - const hasPdf = files.some((file) => - file.name.toLowerCase().endsWith(".pdf") - ); + const pdfFile = files.find((file) => file.name.toLowerCase().endsWith(".pdf")); - const formData = new FormData(); - - // Adiciona mensagem - formData.append("message", userMessage); - - // Adiciona arquivos corretamente - files.forEach((file) => { - const ext = file.name.toLowerCase().split(".").pop(); - if (ext === "mp3") { - formData.append("audio", file.file); // nome do campo deve ser 'audio' - } else if (ext === "pdf") { - formData.append("pdf", file.file); // nome do campo deve ser 'pdf' - } - }); - - const response = await fetch(API_ENDPOINT, { - method: "POST", - body: - hasAudio || hasPdf - ? formData - : JSON.stringify({ message: userMessage }), - headers: - hasAudio || hasPdf - ? undefined // Quando usar FormData, NÃO definir Content-Type, o navegador define com boundary correto - : { - "Content-Type": "application/json", - }, - }); + let response: Response; + if (pdfFile) { + const formData = new FormData(); + formData.append("pdf", pdfFile.file); // campo 'pdf' + formData.append("message", userMessage); // campo 'message' + response = await fetch(API_ENDPOINT, { + method: "POST", + body: formData, // multipart/form-data automático + }); + } else { + response = await fetch(API_ENDPOINT, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ message: userMessage }), + }); + } if (!response.ok) { throw new Error(`HTTP ${response.status}`); @@ -546,6 +530,13 @@ const FileUploadChat = ({ onOpenVoice }: { onOpenVoice?: () => void }) => { > + handleFileSelect(e.target.files)} + />