Se agrega modelo de respuestas y conversaciones archivos multiples y contexto de id plan de estudios
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { supabase } from "@/auth/supabase";
|
||||
import ReactMarkdown from "react-markdown"
|
||||
|
||||
/* ---------- UI Mocks (sin cambios) ---------- */
|
||||
const Paperclip = (props) => (
|
||||
@@ -32,13 +33,13 @@ const CardContent = ({ className, children }) => <div className={`p-4 ${classNam
|
||||
const ScrollArea = ({ className, children }) => <div className={`overflow-y-auto ${className}`}>{children}</div>;
|
||||
|
||||
/* ------------- COMPONENT ------------- */
|
||||
export default function AIChatModal({ open, onClose, context, onAccept,planId }) {
|
||||
export default function AIChatModal({ open, onClose, context, onAccept }) {
|
||||
const [vectorStores, setVectorStores] = useState([]);
|
||||
const [vectorFiles, setVectorFiles] = useState([]);
|
||||
const [selectedVectorFile, setSelectedVectorFile] = useState(null);
|
||||
|
||||
const [attachedFile, setAttachedFile] = useState(null);
|
||||
const [attachedPreview, setAttachedPreview] = useState(null);
|
||||
const [attachedFiles, setAttachedFiles] = useState([]);
|
||||
const [attachedPreviews, setAttachedPreviews] = useState([]);
|
||||
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [input, setInput] = useState("");
|
||||
@@ -79,7 +80,8 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
|
||||
// Al abrir: reset o crear conversación
|
||||
useEffect(() => {
|
||||
console.log(planId);
|
||||
console.log(context.cont_conversation);
|
||||
console.log(context);
|
||||
|
||||
if (!open) {
|
||||
// si ya existe una conversación la eliminamos
|
||||
@@ -89,8 +91,8 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
setMessages([]);
|
||||
setInput("");
|
||||
setSelectedVectorFile(null);
|
||||
setAttachedFile(null);
|
||||
setAttachedPreview(null);
|
||||
setAttachedFiles([]);
|
||||
setAttachedPreviews([]);
|
||||
setConversationId(null);
|
||||
return;
|
||||
}
|
||||
@@ -127,7 +129,7 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
// llamada
|
||||
const resp = await supabase.functions.invoke("modal-conversation", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
body: { action: "start" }
|
||||
body: { action: "start" , role:"system", content:context.cont_conversation, }
|
||||
});
|
||||
|
||||
console.log("createConversation -> raw resp:", resp);
|
||||
@@ -221,17 +223,17 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
const token = session?.access_token;
|
||||
|
||||
let filesInput = [];
|
||||
console.log(attachedFile);
|
||||
if (attachedFile) {
|
||||
const base64 = await fileToBase64(attachedFile);
|
||||
console.log(attachedFile);
|
||||
|
||||
filesInput.push({
|
||||
type: "input_file",
|
||||
filename: attachedFile.name,
|
||||
file_data: `data:application/pdf;base64,${base64}`
|
||||
});
|
||||
}
|
||||
if (attachedFiles.length > 0) {
|
||||
for (const file of attachedFiles) {
|
||||
const base64 = await fileToBase64(file);
|
||||
filesInput.push({
|
||||
type: "input_file",
|
||||
filename: file.name,
|
||||
file_data: `data:${file.type};base64,${base64}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedVectorFile) {
|
||||
// si el archivo del vector viene sólo con id
|
||||
@@ -297,8 +299,9 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
|
||||
setMessages(prev => [...prev, { role: "assistant", content: assistantText }]);
|
||||
|
||||
setAttachedFile(null);
|
||||
setAttachedPreview(null);
|
||||
setAttachedFiles([]);
|
||||
setAttachedPreviews([]);
|
||||
|
||||
|
||||
} catch (err) {
|
||||
console.error("Error en handleConversation:", err);
|
||||
@@ -356,14 +359,14 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
};
|
||||
|
||||
// ---------- UI helpers ----------
|
||||
const handleAttach = (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
console.log(file);
|
||||
const handleAttach = (e) => {
|
||||
const files = Array.from(e.target.files);
|
||||
if (!files.length) return;
|
||||
|
||||
setAttachedFiles(prev => [...prev, ...files]);
|
||||
setAttachedPreviews(prev => [...prev, ...files.map(f => f.name)]);
|
||||
};
|
||||
|
||||
if (!file) return;
|
||||
setAttachedFile(file);
|
||||
setAttachedPreview(file.name);
|
||||
};
|
||||
|
||||
const handleSelectVectorFile = (file) => {
|
||||
setSelectedVectorFile(file);
|
||||
@@ -371,7 +374,7 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
|
||||
// ---------- Send flow ----------
|
||||
const handleSend = async () => {
|
||||
if (!input.trim() && !attachedFile && !selectedVectorFile) return;
|
||||
if (!input.trim() && attachedFiles.length === 0 && !selectedVectorFile) return;
|
||||
|
||||
// esperar si aún se está creando la conversación
|
||||
if (creatingConversation) {
|
||||
@@ -411,8 +414,8 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
<DialogTitle>Asistente Inteligente</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 overflow-y-auto pt-4">
|
||||
<div className="flex gap-6 min-h-full">
|
||||
<div className="flex-1 pt-4 min-h-0">
|
||||
<div className="flex gap-6 h-full min-h-0">
|
||||
|
||||
{/* Left: vectors */}
|
||||
<Card className="w-1/3 min-w-[250px] max-w-sm flex flex-col bg-muted/20 border border-gray-200 rounded-2xl">
|
||||
@@ -471,17 +474,21 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
|
||||
{/* Right: Chat */}
|
||||
<Card className="flex-1 flex flex-col min-w-[350px] bg-background border border-gray-200 rounded-2xl">
|
||||
<CardContent className="flex flex-col flex-1 p-4">
|
||||
<CardContent className="flex flex-col flex-1 p-4 min-h-0">
|
||||
|
||||
<h3 className="font-semibold text-sm mb-3 flex-shrink-0">Chat con IA</h3>
|
||||
|
||||
<div className="flex-1 overflow-y-auto min-h-0 border border-gray-200 rounded-lg p-3 space-y-3 bg-gray-50 break-words whitespace-pre-wrap">
|
||||
{messages.length === 0 ? (
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
{/* CONTENEDOR SCROLL DE LOS MENSAJES */}
|
||||
<div className="flex-1 overflow-y-auto min-h-0 border border-gray-200 rounded-lg p-3 space-y-3 bg-gray-50 break-words whitespace-pre-wrap">
|
||||
{messages.length === 0 ? (
|
||||
<p className="text-gray-400 text-sm text-center mt-10">Inicia una conversación...</p>
|
||||
) : (
|
||||
messages.map((m, i) => (
|
||||
<div key={i} className={`break-words whitespace-pre-wrap p-3 rounded-xl shadow-sm max-w-[85%] ${m.role === "user" ? "bg-blue-50 text-blue-800 ml-auto" : m.role === "assistant" ? "bg-white text-gray-800 mr-auto border border-gray-200" : "bg-gray-100 text-gray-700 mr-auto"}`}>
|
||||
<strong className="font-bold">{m.role === "user" ? "Tú:" : m.role === "assistant" ? "IA:" : "Sistema:"}</strong>{" "}
|
||||
<div>{m.content}</div>
|
||||
<ReactMarkdown>{m.content}</ReactMarkdown>
|
||||
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
@@ -498,21 +505,20 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{attachedPreview && (
|
||||
<div className="flex items-center justify-between mt-2 p-3 border border-gray-300 rounded-xl text-sm bg-gray-100 shadow-inner flex-shrink-0">
|
||||
<span className="truncate flex items-center gap-2 text-gray-700">
|
||||
<Paperclip className="w-4 h-4 text-blue-500" />
|
||||
{attachedPreview}
|
||||
</span>
|
||||
<Button variant="outline" className="text-red-500" onClick={() => { setAttachedFile(null); setAttachedPreview(null); }}>Quitar</Button>
|
||||
</div>
|
||||
{attachedPreviews.length > 0 && (
|
||||
<ul className="text-xs text-gray-600 mt-2">
|
||||
{attachedPreviews.map((name, i) => (
|
||||
<li key={i}>📄 {name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2 mt-4 items-end flex-shrink-0">
|
||||
<label className="cursor-pointer text-gray-600 hover:text-blue-600 self-center">
|
||||
<Paperclip className="w-5 h-5" />
|
||||
<input type="file" accept=".pdf,.txt,.doc,.docx" className="hidden" onChange={handleAttach} />
|
||||
<input type="file" accept=".pdf,.txt,.doc,.docx" multiple className="hidden" onChange={handleAttach} />
|
||||
</label>
|
||||
|
||||
<textarea
|
||||
@@ -530,7 +536,7 @@ export default function AIChatModal({ open, onClose, context, onAccept,planId })
|
||||
style={{ minHeight: "38px" }}
|
||||
/>
|
||||
|
||||
<Button onClick={handleSend} disabled={loading || creatingConversation || (!input.trim() && !attachedFile && !selectedVectorFile)} className="shadow-md">
|
||||
<Button onClick={handleSend} disabled={loading || creatingConversation || (!input.trim() && attachedFiles.length === 0 && !selectedVectorFile)} className="shadow-md">
|
||||
{creatingConversation ? "Preparando..." : loading ? "Enviando..." : "Enviar"}
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -310,12 +310,12 @@ export function AcademicSections({ planId, color }: { planId: string; color?: st
|
||||
|
||||
<AIChatModal
|
||||
open={openModalIa}
|
||||
planId={planId}
|
||||
onClose={() => setopenModalIa(false)}
|
||||
context={{
|
||||
section: iaContext?.title,
|
||||
fieldKey: iaContext?.key,
|
||||
originalText: iaContext?.content,
|
||||
cont_conversation: `Eres un experto en craer planes de estudios basate en el id del plan ${planId} que se encuentra en la tabla plan_estudios con el mcp para realizar los cambios que se te soliciten`,
|
||||
}}
|
||||
onAccept={(newText: string) => {
|
||||
if (iaContext) {
|
||||
|
||||
Reference in New Issue
Block a user