/* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/no-static-element-interactions */ import { createFileRoute, useRouterState } from '@tanstack/react-router' import { Send, Target, Lightbulb, FileText, GraduationCap, BookOpen, Check, X, MessageSquarePlus, Archive, RotateCcw, } from 'lucide-react' import { useState, useEffect, useRef, useMemo } from 'react' import type { UploadedFile } from '@/components/planes/wizard/PasoDetallesPanel/FileDropZone' import { ImprovementCard } from '@/components/planes/detalle/Ia/ImprovementCard' import ReferenciasParaIA from '@/components/planes/wizard/PasoDetallesPanel/ReferenciasParaIA' import { Button } from '@/components/ui/button' import { Drawer, DrawerContent } from '@/components/ui/drawer' import { ScrollArea } from '@/components/ui/scroll-area' import { Textarea } from '@/components/ui/textarea' import { usePlan } from '@/data/hooks/usePlans' const PRESETS = [ { id: 'objetivo', label: 'Mejorar objetivo general', icon: Target, prompt: 'Mejora la redacción del objetivo general...', }, { id: 'perfil-egreso', label: 'Redactar perfil de egreso', icon: GraduationCap, prompt: 'Genera un perfil de egreso detallado...', }, { id: 'competencias', label: 'Sugerir competencias', icon: BookOpen, prompt: 'Genera una lista de competencias...', }, { id: 'pertinencia', label: 'Justificar pertinencia', icon: FileText, prompt: 'Redacta una justificación de pertinencia...', }, ] // --- Tipado y Helpers --- interface SelectedField { key: string label: string value: string } export const Route = createFileRoute('/planes/$planId/_detalle/iaplan')({ component: RouteComponent, }) function RouteComponent() { const { planId } = Route.useParams() const { data } = usePlan('0e0aea4d-b8b4-4e75-8279-6224c3ac769f') const routerState = useRouterState() const [openIA, setOpenIA] = useState(false) // archivos const [selectedArchivoIds, setSelectedArchivoIds] = useState>( [], ) const [selectedRepositorioIds, setSelectedRepositorioIds] = useState< Array >([]) const [uploadedFiles, setUploadedFiles] = useState>([]) const [messages, setMessages] = useState>([ { id: '1', role: 'assistant', content: '¡Hola! Soy tu asistente de IA. ¿Qué campos deseas mejorar? Puedes escribir ":" para seleccionar uno.', }, ]) const [input, setInput] = useState('') const [selectedFields, setSelectedFields] = useState>([]) const [showSuggestions, setShowSuggestions] = useState(false) const [isLoading, setIsLoading] = useState(false) const [pendingSuggestion, setPendingSuggestion] = useState(null) const scrollRef = useRef(null) const [activeChatId, setActiveChatId] = useState('1') const [chatHistory, setChatHistory] = useState([ { id: '1', title: 'Chat inicial' }, ]) const [showArchived, setShowArchived] = useState(false) const [archivedHistory, setArchivedHistory] = useState>([]) const [allMessages, setAllMessages] = useState<{ [key: string]: Array }>( { '1': [ { id: 'm1', role: 'assistant', content: '¡Hola! Soy tu asistente de IA en este chat inicial.', }, ], }, ) const createNewChat = () => { const newId = Date.now().toString() const newChat = { id: newId, title: `Nuevo chat ${chatHistory.length + 1}` } setChatHistory([newChat, ...chatHistory]) setAllMessages({ ...allMessages, [newId]: [ { id: '1', role: 'assistant', content: '¡Nuevo chat creado! ¿En qué puedo ayudarte?', }, ], }) setActiveChatId(newId) } const archiveChat = (e: React.MouseEvent, id: string) => { e.stopPropagation() const chatToArchive = chatHistory.find((chat) => chat.id === id) if (chatToArchive) { setArchivedHistory([chatToArchive, ...archivedHistory]) const newHistory = chatHistory.filter((chat) => chat.id !== id) setChatHistory(newHistory) if (activeChatId === id && newHistory.length > 0) { setActiveChatId(newHistory[0].id) } } } const unarchiveChat = (e: React.MouseEvent, id: string) => { e.stopPropagation() const chatToRestore = archivedHistory.find((chat) => chat.id === id) if (chatToRestore) { setChatHistory([chatToRestore, ...chatHistory]) setArchivedHistory(archivedHistory.filter((chat) => chat.id !== id)) } } // 1. Transformar datos de la API para el menú de selección const availableFields = useMemo(() => { if (!data?.estructuras_plan?.definicion?.properties) return [] return Object.entries(data.estructuras_plan.definicion.properties).map( ([key, value]) => ({ key, label: value.title, value: String(value.description || ''), }), ) }, [data]) // 2. Manejar el estado inicial si viene de "Datos Generales" useEffect(() => { const state = routerState.location.state as any if (!state?.campo_edit || availableFields.length === 0) return const field = availableFields.find( (f) => f.value === state.campo_edit.label || f.key === state.campo_edit.clave, ) if (!field) return setSelectedFields([field]) setInput((prev) => injectFieldsIntoInput(prev || 'Mejora este campo:', [field]), ) }, [availableFields]) // 3. Lógica para el disparador ":" const handleInputChange = (e: React.ChangeEvent) => { const val = e.target.value setInput(val) // Si el último carácter es ':', mostramos sugerencias if (val.endsWith(':')) { setShowSuggestions(true) } else { setShowSuggestions(false) } } const injectFieldsIntoInput = ( input: string, fields: Array, ) => { // Quita cualquier bloque previo de campos const cleaned = input.replace(/\n?\[Campos:[^\]]*]/g, '').trim() if (fields.length === 0) return cleaned const fieldLabels = fields.map((f) => f.label).join(', ') return `${cleaned}\n[Campos: ${fieldLabels}]` } const toggleField = (field: SelectedField) => { // 1. Actualizamos los campos seleccionados (para los badges y la lógica de la IA) setSelectedFields((prev) => { const isSelected = prev.find((f) => f.key === field.key) return isSelected ? prev : [...prev, field] }) // 2. Insertamos el nombre del campo en el texto y quitamos el ":" setInput((prevInput) => { // Buscamos la última posición del ":" const lastColonIndex = prevInput.lastIndexOf(':') if (lastColonIndex !== -1) { // Tomamos lo que está antes del ":" y le concatenamos el nombre del campo const textBefore = prevInput.substring(0, lastColonIndex) const textAfter = prevInput.substring(lastColonIndex + 1) // Retornamos el texto con el nombre del campo (puedes añadir espacio si prefieres) return `${textBefore} ${field.label}${textAfter}` } return prevInput }) setShowSuggestions(false) } const buildPrompt = (userInput: string) => { // Si no hay campos, enviamos solo el texto if (selectedFields.length === 0) return userInput const fieldsText = selectedFields .map( (f) => `### CAMPO: ${f.label}\nCONTENIDO ACTUAL: ${f.value || '(vacío)'}`, ) .join('\n\n') return `Instrucción del usuario: ${userInput || 'Mejora los campos seleccionados.'} A continuación se detallan los campos a procesar: ${fieldsText}`.trim() } const handleSend = async (promptOverride?: string) => { const rawText = promptOverride || input if (!rawText.trim() && selectedFields.length === 0) return const finalPrompt = buildPrompt(rawText) const userMsg = { id: Date.now().toString(), role: 'user', content: finalPrompt, } setInput('') setIsLoading(true) setSelectedArchivoIds([]) setSelectedRepositorioIds([]) setUploadedFiles([]) setTimeout(() => { const suggestions = selectedFields.map((field) => ({ key: field.key, label: field.label, newValue: field.value, })) setMessages((prev) => [ ...prev, { id: Date.now().toString(), role: 'assistant', type: 'improvement-card', content: 'He analizado los campos seleccionados. Aquí tienes mis sugerencias de mejora:', suggestions: suggestions, }, ]) setIsLoading(false) }, 1200) } const totalReferencias = useMemo(() => { return ( selectedArchivoIds.length + selectedRepositorioIds.length + uploadedFiles.length ) }, [selectedArchivoIds, selectedRepositorioIds, uploadedFiles]) return (
{/* --- PANEL IZQUIERDO: HISTORIAL --- */}

Chats

{/* Botón de toggle archivados movido aquí arriba */}
{/* Lógica de renderizado condicional */} {!showArchived ? ( // LISTA DE CHATS ACTIVOS chatHistory.map((chat) => (
setActiveChatId(chat.id)} className={`group relative flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-3 text-sm transition-colors ${ activeChatId === chat.id ? 'bg-slate-100 font-medium text-slate-900' : 'text-slate-600 hover:bg-slate-50' }`} > {chat.title}
)) ) : ( // LISTA DE CHATS ARCHIVADOS

Archivados

{archivedHistory.map((chat) => (
{chat.title}
))} {archivedHistory.length === 0 && (

No hay archivados

)}
)}
{/* PANEL DE CHAT PRINCIPAL */}
{/* NUEVO: Barra superior de campos seleccionados */}
Mejorar con IA
{/* CONTENIDO DEL CHAT */}
{messages.map((msg) => (
{msg.content} {msg.type === 'improvement-card' && ( { setSelectedFields((prev) => prev.filter((f) => f.key !== key), ) console.log(`Aplicando ${val} al campo ${key}`) // Aquí llamarías a tu función de actualización de datos real }} /> )}
))} {isLoading && (
)}
{/* Botones flotantes de aplicación */} {pendingSuggestion && !isLoading && (
)}
{/* INPUT FIJO AL FONDO CON SUGERENCIAS : */}
{/* MENÚ DE SUGERENCIAS FLOTANTE */} {showSuggestions && (
Seleccionar campo para IA
{availableFields.map((field) => ( ))}
)} {/* CONTENEDOR DEL INPUT TRANSFORMADO */}
{/* 1. Visualización de campos dentro del input ) */} {selectedFields.length > 0 && (
{selectedFields.map((field) => (
Campo: {field.label}
))}
)} {/* 2. Área de escritura */}