From f7a29ad510f3fc71653189a2aee317cf339684a0 Mon Sep 17 00:00:00 2001 From: "Roberto.silva" Date: Tue, 18 Nov 2025 15:17:11 -0600 Subject: [PATCH] Version estable conversacion normal --- src/components/ai/AIChatModal.jsx | 375 ++++++++++++++++++++ src/components/ai/AIChatModal.tsx | 371 ------------------- src/components/planes/academic-sections.tsx | 4 +- tsconfig.json | 2 +- 4 files changed, 379 insertions(+), 373 deletions(-) create mode 100644 src/components/ai/AIChatModal.jsx delete mode 100644 src/components/ai/AIChatModal.tsx diff --git a/src/components/ai/AIChatModal.jsx b/src/components/ai/AIChatModal.jsx new file mode 100644 index 0000000..b97769b --- /dev/null +++ b/src/components/ai/AIChatModal.jsx @@ -0,0 +1,375 @@ +import React, { useEffect, useRef, useState } from "react"; +import { supabase } from "@/auth/supabase"; + +// ---------------- UI MOCKS ---------------- // +// Puedes reemplazarlos por tus propios componentes UI +const Paperclip = (props) => ( + + + +); + +const Dialog = ({ open, onOpenChange, children }) => + open ?
{children}
: null; + +const DialogContent = ({ className, children }) => +
e.stopPropagation()}> + {children} +
; + +const DialogHeader = ({ children }) =>
{children}
; +const DialogTitle = ({ className, children }) =>

{children}

; + +const Button = ({ onClick, disabled, className, variant, children }) => ( + +); + +const Card = ({ className, children }) =>
{children}
; +const CardContent = ({ className, children }) =>
{children}
; +const ScrollArea = ({ className, children }) =>
{children}
; + +// ---------------- COMPONENTE ---------------- // +export default function AIChatModal({ open, onClose, context, onAccept }) { + const [vectorStores, setVectorStores] = useState([]); + const [vectorFiles, setVectorFiles] = useState([]); + + const [attachedFile, setAttachedFile] = useState(null); + const [attachedPreview, setAttachedPreview] = useState(null); + + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(""); + + const [loading, setLoading] = useState(false); + const [loadingFiles, setLoadingFiles] = useState(false); + const [selectedVector, setSelectedVector] = useState(null); + + const messagesEndRef = useRef(null); + const scrollToBottom = () => messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + + useEffect(scrollToBottom, [messages]); + + // ------------------------------------ + // Reset al abrir o cerrar modal + // ------------------------------------ + useEffect(() => { + if (!open) { + setMessages([]); + setInput(""); + setAttachedFile(null); + setAttachedPreview(null); + setVectorStores([]); + setVectorFiles([]); + setSelectedVector(null); + return; + } + + if (context) { + setMessages([ + { + role: "system", + content: `Contexto: ${context.section}\nTexto original:\n${context.originalText || "—"}`, + }, + ]); + } + }, [open, context]); + + // ------------------------------------ + // Cargar vector stores + // ------------------------------------ + useEffect(() => { + if (!open) return; + + const fetchVectorStores = async () => { + try { + setLoading(true); + const { data: { session } } = await supabase.auth.getSession(); + const token = session?.access_token; + + const { data, error } = await supabase.functions.invoke( + "files-and-vector-stores-api", + { + headers: { Authorization: `Bearer ${token}` }, + body: { module: "vectorStores", action: "list" }, + } + ); + + if (error) throw error; + setVectorStores(Array.isArray(data) ? data : []); + } catch (err) { + console.error("Error al obtener vector stores:", err); + setVectorStores([]); + } finally { + setLoading(false); + } + }; + + fetchVectorStores(); + }, [open]); + + // ------------------------------------ + // Cargar archivos del vector seleccionado + // ------------------------------------ + const loadFilesForVector = async (vectorStoreId) => { + try { + setLoadingFiles(true); + const { data: { session } } = await supabase.auth.getSession(); + const token = session?.access_token; + + const { data, error } = await supabase.functions.invoke( + "files-and-vector-stores-api", + { + headers: { Authorization: `Bearer ${token}` }, + body: { + module: "vectorStoreFiles", + action: "list", + params: { vector_store_id: vectorStoreId }, + }, + } + ); + + if (error) throw error; + setVectorFiles(Array.isArray(data) ? data : []); + } catch (err) { + console.error("Error al obtener archivos del vector store:", err); + setVectorFiles([]); + } finally { + setLoadingFiles(false); + } + }; + + // ------------------------------------ + // Adjuntar archivo + // ------------------------------------ + const handleAttach = (e) => { + const file = e.target.files?.[0]; + if (!file) return; + setAttachedFile(file); + setAttachedPreview(file.name); + }; + + // ------------------------------------ + // handleSend — versión final para Supabase Edge Function + // ------------------------------------ + const handleSend = async () => { + if (!input.trim() && !attachedFile) return; + + // Construir texto del mensaje del usuario + const userMessage = input.trim() + ? input.trim() + : attachedFile + ? `Consulta sobre archivo: ${attachedFile.name}` + : ""; + + // Agregar mensaje al chat + setMessages(prev => [...prev, { role: "user", content: userMessage }]); + setInput(""); + + setLoading(true); + + try { + const formData = new FormData(); + + const fullPrompt = ` +${context?.section ? `Sección: ${context.section}` : ""} +${context?.fieldKey ? `Campo: ${context.fieldKey}` : ""} + +Texto original: +${context?.originalText || "Sin texto original"} + +Solicitud del usuario: +${userMessage} + +Responde con una versión mejorada en texto directo, sin explicaciones. + `.trim(); + + formData.append("prompt", fullPrompt); + if (attachedFile) formData.append("file", attachedFile); + + const { data, error } = await supabase.functions.invoke( + "simple-chat", + { body: formData } + ); + + if (error) throw error; + + // Respuesta de la IA + setMessages(prev => [ + ...prev, + { role: "assistant", content: data?.text || "Sin respuesta del modelo." } + ]); + + } catch (err) { + console.error("Error enviando mensaje:", err); + setMessages(prev => [...prev, { role: "assistant", content: "Ocurrió un error al conectar con la IA." }]); + } + + setLoading(false); + setAttachedFile(null); + setAttachedPreview(null); + }; + + // ------------------------------------ + // UI + // ------------------------------------ + return ( + + + + Asistente Inteligente + + +
+
+ + {/* LEFT: VECTOR STORES */} + + +

Vector Stores

+ + + {loading ? ( +

Cargando...

+ ) : vectorStores.length === 0 ? ( +

No hay vector stores

+ ) : ( +
    + {vectorStores.map(store => ( +
  • { + setSelectedVector(store); + loadFilesForVector(store.id); + }} + className={`border p-2 rounded-lg cursor-pointer + ${selectedVector?.id === store.id ? "bg-blue-100" : "bg-white"}`} + > + {store.name || store.id} +

    {store.description}

    +
  • + ))} +
+ )} +
+ +

Archivos

+ + {loadingFiles ? ( +

Cargando archivos...

+ ) : vectorFiles.length === 0 ? ( +

No hay archivos

+ ) : ( +
    + {vectorFiles.map(f => ( +
  • + {f.id} +
  • + ))} +
+ )} +
+
+
+ + {/* RIGHT: CHAT */} + + +

Chat

+ +
+ {messages.length === 0 ? ( +

Inicia la conversación

+ ) : ( + messages.map((msg, idx) => ( +
+ {msg.role === "user" ? "Tú:" : "IA:"} +

{msg.content}

+
+ )) + )} + + {loading && ( +
+ La IA está respondiendo... +
+ )} + +
+
+ + {attachedPreview && ( +
+ + {attachedPreview} + +
+ )} + + {/* Input */} +
+ + +