diff --git a/src/routes/planes/$planId/_detalle/iaplan.tsx b/src/routes/planes/$planId/_detalle/iaplan.tsx index 07087ab..78bd195 100644 --- a/src/routes/planes/$planId/_detalle/iaplan.tsx +++ b/src/routes/planes/$planId/_detalle/iaplan.tsx @@ -128,16 +128,15 @@ function RouteComponent() { ) const availableFields = useMemo(() => { - // 1. Hacemos un cast de la definición a nuestra interfaz const definicion = data?.estructuras_plan ?.definicion as EstructuraDefinicion + // Encadenamiento opcional para evitar errores si data es null if (!definicion.properties) return [] return Object.entries(definicion.properties).map(([key, value]) => ({ key, label: value.title, - // 2. Aquí value ya no es unknown, es parte de nuestra interfaz value: String(value.description || ''), })) }, [data]) @@ -146,18 +145,32 @@ function RouteComponent() { }, [lastConversation, activeChatId]) const chatMessages = useMemo(() => { - // Forzamos el cast a Array de nuestra interfaz - const json = (activeChatData?.conversacion_json || + // 1. Si no hay ID o no hay data del chat, retornamos vacío + if (!activeChatId || !activeChatData) return [] + + const json = (activeChatData.conversacion_json || []) as unknown as Array - // Ahora .map() funcionará sin errores + // 2. Verificamos que 'json' sea realmente un array antes de mapear + if (!Array.isArray(json)) return [] + return json.map((msg, index: number) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!msg?.user) { + return { + id: `err-${index}`, + role: 'assistant', + content: '', + suggestions: [], + } + } + const isAssistant = msg.user === 'assistant' return { id: `${activeChatId}-${index}`, role: isAssistant ? 'assistant' : 'user', - content: isAssistant ? msg.message : msg.prompt, + content: isAssistant ? msg.message || '' : msg.prompt || '', // Agregamos fallback a string vacío isRefusal: isAssistant && msg.refusal === true, suggestions: isAssistant && msg.recommendations @@ -178,6 +191,7 @@ function RouteComponent() { } }) }, [activeChatData, activeChatId, availableFields]) + const scrollToBottom = () => { if (scrollRef.current) { // Buscamos el viewport interno del ScrollArea de Radix @@ -192,25 +206,45 @@ function RouteComponent() { } } } + const { activeChats, archivedChats } = useMemo(() => { + const allChats = lastConversation || [] + return { + activeChats: allChats.filter((chat: any) => chat.estado === 'ACTIVA'), + archivedChats: allChats.filter( + (chat: any) => chat.estado === 'ARCHIVADA', + ), + } + }, [lastConversation]) - // Auto-scroll cuando cambian los mensajes o cuando la IA está cargando useEffect(() => { scrollToBottom() }, [chatMessages, isLoading]) useEffect(() => { - // Si no hay un chat seleccionado manualmente y la API nos devuelve chats existentes - const isCreationMode = messages.length === 1 && messages[0].id === 'welcome' - if ( - !activeChatId && - lastConversation && - lastConversation.length > 0 && - !isCreationMode - ) { - setActiveChatId(lastConversation[0].id) - } - }, [lastConversation, activeChatId]) + if (isLoadingConv || !lastConversation) return + const isChatStillActive = activeChats.some( + (chat) => chat.id === activeChatId, + ) + const isCreationMode = messages.length === 1 && messages[0].id === 'welcome' + + // Caso A: El chat actual ya no es válido (fue archivado o borrado) + if (activeChatId && !isChatStillActive && !isCreationMode) { + setActiveChatId(undefined) + setMessages([]) + return // Salimos para evitar ejecuciones extra en este render + } + + // Caso B: No hay chat seleccionado y hay chats disponibles (Auto-selección al cargar) + if (!activeChatId && activeChats.length > 0 && !isCreationMode) { + setActiveChatId(activeChats[0].id) + } + + // Caso C: Si la lista de chats está vacía y no estamos creando uno, limpiar por si acaso + if (activeChats.length === 0 && activeChatId && !isCreationMode) { + setActiveChatId(undefined) + } + }, [activeChats, activeChatId, isLoadingConv, messages.length]) useEffect(() => { const state = routerState.location.state as any if (!state?.campo_edit || availableFields.length === 0) return @@ -252,6 +286,9 @@ function RouteComponent() { if (activeChatId === id) { setActiveChatId(undefined) setMessages([]) + setOptimisticMessage(null) + setInput('') + setSelectedFields([]) } }, }, @@ -331,6 +368,9 @@ function RouteComponent() { setIsSending(true) setOptimisticMessage(rawText) setInput('') + setSelectedArchivoIds([]) + setSelectedRepositorioIds([]) + setUploadedFiles([]) try { const payload: any = { planId: planId, @@ -370,16 +410,6 @@ function RouteComponent() { ) }, [selectedArchivoIds, selectedRepositorioIds, uploadedFiles]) - const { activeChats, archivedChats } = useMemo(() => { - const allChats = lastConversation || [] - return { - activeChats: allChats.filter((chat: any) => chat.estado === 'ACTIVA'), - archivedChats: allChats.filter( - (chat: any) => chat.estado === 'ARCHIVADA', - ), - } - }, [lastConversation]) - const removeSelectedField = (fieldKey: string) => { setSelectedFields((prev) => prev.filter((f) => f.key !== fieldKey)) } @@ -555,72 +585,98 @@ function RouteComponent() {
- {chatMessages.map((msg: any) => ( -
-
- {/* Icono opcional de advertencia si es refusal */} - {msg.isRefusal && ( -
- Aviso del Asistente -
- )} - - {msg.content} - - {!msg.isRefusal && - msg.suggestions && - msg.suggestions.length > 0 && ( -
- removeSelectedField(key)} - /> -
- )} -
+ {!activeChatId && + chatMessages.length === 0 && + !optimisticMessage ? ( +
+ +

+ No hay un chat seleccionado +

+

+ Selecciona un chat del historial o crea uno nuevo para + empezar. +

- ))} - {optimisticMessage && ( -
-
- {optimisticMessage} -
-
- )} - {isSending && ( -
-
-
-
- - - + ) : ( + <> + {chatMessages.map((msg: any) => ( +
+
+ {/* Icono opcional de advertencia si es refusal */} + {msg.isRefusal && ( +
+ Aviso del Asistente +
+ )} + + {msg.content} + + {!msg.isRefusal && + msg.suggestions && + msg.suggestions.length > 0 && ( +
+ + removeSelectedField(key) + } + /> +
+ )}
- - Esperando respuesta... -
-
-
+ ))} + + {optimisticMessage && ( +
+
+ {optimisticMessage} +
+
+ )} + + {isSending && ( +
+
+
+
+ + + +
+ + Esperando respuesta... + +
+
+
+ )} + )}
@@ -651,25 +707,42 @@ function RouteComponent() {
{/* MENÚ DE SUGERENCIAS FLOTANTE */} {showSuggestions && ( -
-
- Seleccionar campo para IA -
+
+
Seleccionar campo para IA
- {availableFields.map((field) => ( - - ))} + {availableFields.map((field) => { + // 1. Verificamos si el campo ya está en la lista de seleccionados + const isAlreadySelected = selectedFields.some( + (f) => f.key === field.key, + ) + + return ( + + ) + })}
)}