Que la conversación se obtenga de conversation_json de supabase #136

This commit is contained in:
2026-02-25 13:58:12 -06:00
parent cfc2153fa2
commit 4a8a9e1857
4 changed files with 151 additions and 118 deletions

View File

@@ -27,7 +27,6 @@ import { ScrollArea } from '@/components/ui/scroll-area'
import { Textarea } from '@/components/ui/textarea'
import {
useAIPlanChat,
useChatHistory,
useConversationByPlan,
useUpdateConversationStatus,
} from '@/data'
@@ -84,8 +83,9 @@ function RouteComponent() {
undefined,
)
const { data: historyMessages, isLoading: isLoadingHistory } =
useChatHistory(activeChatId)
/* const { data: historyMessages, isLoading: isLoadingHistory } =
useChatHistory(activeChatId) */
const { data: lastConversation, isLoading: isLoadingConv } =
useConversationByPlan(planId)
// archivos
@@ -106,61 +106,48 @@ function RouteComponent() {
const scrollRef = useRef<HTMLDivElement>(null)
const [showArchived, setShowArchived] = useState(false)
const [editingChatId, setEditingChatId] = useState<string | null>(null)
const editableRef = useRef<HTMLSpanElement>(null)
const { mutate: updateTitleMutation } = useUpdateConversationTitle()
useEffect(() => {
// 1. Si no hay ID o está cargando el historial, no hacemos nada
if (!activeChatId || isLoadingHistory) return
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])
const activeChatData = useMemo(() => {
return lastConversation?.find((chat: any) => chat.id === activeChatId)
}, [lastConversation, activeChatId])
const messagesFromApi = historyMessages?.items || historyMessages
const conversacionJson = activeChatData?.conversacion_json || []
const chatMessages = useMemo(() => {
const json = activeChatData?.conversacion_json
if (!Array.isArray(json)) return []
if (Array.isArray(messagesFromApi)) {
const flattened = messagesFromApi.map((msg) => {
let content = msg.content
let suggestions: Array<any> = []
return json.map((msg: any, index: number) => {
const isAssistant = msg.user === 'assistant'
if (typeof content === 'object' && content !== null) {
suggestions = Object.entries(content)
.filter(([key]) => key !== 'ai-message')
.map(([key, value]) => ({
key,
label: key.replace(/_/g, ' '),
newValue: value as string,
}))
content = content['ai-message'] || JSON.stringify(content)
}
// Si el content es un string que parece JSON (caso común en respuestas RAW)
else if (typeof content === 'string' && content.startsWith('{')) {
try {
const parsed = JSON.parse(content)
suggestions = Object.entries(parsed)
.filter(([key]) => key !== 'ai-message')
.map(([key, value]) => ({
key,
label: key.replace(/_/g, ' '),
newValue: value as string,
return {
id: `${activeChatId}-${index}-${msg.timestamp}`, // ID estable
role: isAssistant ? 'assistant' : 'user',
content: isAssistant ? msg.message : msg.prompt,
suggestions:
isAssistant && msg.recommendations
? msg.recommendations.map((rec: any) => ({
key: rec.campo_afectado,
label: rec.campo_afectado.replace(/_/g, ' '),
newValue: rec.texto_mejora,
applied: rec.aplicada,
}))
content = parsed['ai-message'] || content
} catch (e) {
/* no es json */
}
}
return {
...msg,
content,
suggestions,
type: suggestions.length > 0 ? 'improvement-card' : 'text',
}
})
// Solo actualizamos si no estamos esperando la respuesta de un POST
// para evitar saltos visuales
if (!isLoading) {
setMessages(flattened.reverse())
: [],
}
}
}, [historyMessages, activeChatId, isLoadingHistory, isLoading])
})
}, [activeChatData, activeChatId])
useEffect(() => {
// Si no hay un chat seleccionado manualmente y la API nos devuelve chats existentes
@@ -321,13 +308,6 @@ function RouteComponent() {
const currentFields = [...selectedFields]
const finalPrompt = buildPrompt(rawText, currentFields)
const userMsg = {
id: Date.now().toString(),
role: 'user',
content: rawText,
}
setMessages((prev) => [...prev, userMsg])
setInput('')
// setSelectedFields([])
@@ -346,58 +326,14 @@ function RouteComponent() {
if (response.conversacionId && response.conversacionId !== activeChatId) {
setActiveChatId(response.conversacionId)
// Esto obliga a 'useConversationByPlan' a buscar en la DB el nuevo chat creado
queryClient.invalidateQueries({
queryKey: ['conversation-by-plan', planId],
})
}
// --- NUEVA LÓGICA DE PARSEO ---
let aiText = 'Sin respuesta del asistente'
let suggestions: Array<any> = []
if (response.raw) {
try {
const rawData = JSON.parse(response.raw)
// Extraemos el mensaje conversacional
aiText = rawData['ai-message'] || 'Cambios aplicados con éxito.'
// Filtramos todo lo que no sea el mensaje para crear las sugerencias
suggestions = Object.entries(rawData)
.filter(([key]) => key !== 'ai-message')
.map(([key, value]) => ({
key,
label: key.replace(/_/g, ' '),
newValue: value as string,
}))
} catch (e) {
console.error('Error parseando el campo raw:', e)
aiText = response.raw // Fallback si no es JSON
}
}
setMessages((prev) => [
...prev,
{
id: Date.now().toString(),
role: 'assistant',
content: aiText,
type: suggestions.length > 0 ? 'improvement-card' : 'text',
suggestions: suggestions,
},
])
await queryClient.invalidateQueries({
queryKey: ['conversation-by-plan', planId],
})
} catch (error) {
console.error('Error en el chat:', error)
setMessages((prev) => [
...prev,
{
id: 'error',
role: 'assistant',
content: 'Lo siento, hubo un error al procesar tu solicitud.',
},
])
// Aquí sí podrías usar un toast o un mensaje de error temporal
}
}
@@ -543,7 +479,7 @@ function RouteComponent() {
<div className="relative min-h-0 flex-1">
<ScrollArea ref={scrollRef} className="h-full w-full">
<div className="mx-auto max-w-3xl space-y-6 p-6">
{messages.map((msg) => (
{chatMessages.map((msg) => (
<div
key={msg.id}
className={`flex max-w-[85%] flex-col ${msg.role === 'user' ? 'ml-auto items-end' : 'items-start'}`}
@@ -563,14 +499,11 @@ function RouteComponent() {
<div className="mt-4">
<ImprovementCard
suggestions={msg.suggestions}
planId={planId} // Del useParams()
currentDatos={data?.datos} // De tu query usePlan(planId)
onApply={(key, val) => {
// Esto es opcional, si quieres hacer algo más en la UI del chat
console.log(
'Evento onApply disparado desde el chat',
)
}}
planId={planId}
currentDatos={data?.datos}
activeChatId={activeChatId}
// Puedes pasar una prop nueva si tu ImprovementCard la soporta:
// isReadOnly={msg.suggestions.every(s => s.applied)}
/>
</div>
)}