338 lines
9.7 KiB
TypeScript
338 lines
9.7 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|
import { useEffect } from 'react'
|
|
|
|
import {
|
|
ai_plan_chat_v2,
|
|
ai_plan_improve,
|
|
ai_subject_improve,
|
|
create_conversation,
|
|
get_chat_history,
|
|
getConversationByPlan,
|
|
library_search,
|
|
update_conversation_status,
|
|
update_recommendation_applied_status,
|
|
update_conversation_title,
|
|
getMessagesByConversation,
|
|
update_subject_conversation_status,
|
|
update_subject_recommendation_applied,
|
|
getMessagesBySubjectConversation,
|
|
getConversationBySubject,
|
|
ai_subject_chat_v2,
|
|
create_subject_conversation,
|
|
update_subject_conversation_name,
|
|
} from '../api/ai.api'
|
|
import { supabaseBrowser } from '../supabase/client'
|
|
|
|
import type { UUID } from 'node:crypto'
|
|
|
|
export function useAIPlanImprove() {
|
|
return useMutation({ mutationFn: ai_plan_improve })
|
|
}
|
|
|
|
export function useAIPlanChat() {
|
|
return useMutation({
|
|
mutationFn: async (payload: {
|
|
planId: UUID
|
|
content: string
|
|
campos?: Array<string>
|
|
conversacionId?: string
|
|
}) => {
|
|
let currentId = payload.conversacionId
|
|
|
|
// 1. Si no hay ID, creamos la conversación
|
|
if (!currentId) {
|
|
const response = await create_conversation(payload.planId)
|
|
|
|
// CAMBIO AQUÍ: Accedemos a la estructura correcta según tu consola
|
|
currentId = response.conversation_plan.id
|
|
}
|
|
|
|
// 2. Ahora enviamos el mensaje con el ID garantizado
|
|
const result = await ai_plan_chat_v2({
|
|
conversacionId: currentId!,
|
|
content: payload.content,
|
|
campos: payload.campos,
|
|
})
|
|
|
|
// Retornamos el resultado del chat y el ID para el estado del componente
|
|
return { ...result, conversacionId: currentId }
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useChatHistory(conversacionId?: string) {
|
|
return useQuery({
|
|
queryKey: ['chat-history', conversacionId],
|
|
queryFn: async () => {
|
|
return get_chat_history(conversacionId!)
|
|
},
|
|
enabled: Boolean(conversacionId),
|
|
})
|
|
}
|
|
|
|
export function useUpdateConversationStatus() {
|
|
const qc = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: ({
|
|
id,
|
|
estado,
|
|
}: {
|
|
id: string
|
|
estado: 'ARCHIVADA' | 'ACTIVA'
|
|
}) => update_conversation_status(id, estado),
|
|
onSuccess: () => {
|
|
// Esto refresca las listas automáticamente
|
|
qc.invalidateQueries({ queryKey: ['conversation-by-plan'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useConversationByPlan(planId: string | null) {
|
|
return useQuery({
|
|
queryKey: ['conversation-by-plan', planId],
|
|
queryFn: () => getConversationByPlan(planId!),
|
|
enabled: !!planId, // solo ejecuta si existe planId
|
|
})
|
|
}
|
|
|
|
export function useMessagesByChat(conversationId: string | null) {
|
|
const queryClient = useQueryClient()
|
|
const supabase = supabaseBrowser()
|
|
|
|
const query = useQuery({
|
|
queryKey: ['conversation-messages', conversationId],
|
|
queryFn: () => {
|
|
if (!conversationId) throw new Error('Conversation ID is required')
|
|
return getMessagesByConversation(conversationId)
|
|
},
|
|
enabled: !!conversationId,
|
|
placeholderData: (previousData) => previousData,
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (!conversationId) return
|
|
|
|
// Suscribirse a cambios en los mensajes de ESTA conversación
|
|
const channel = supabase
|
|
.channel(`realtime-messages-${conversationId}`)
|
|
.on(
|
|
'postgres_changes',
|
|
{
|
|
event: '*', // Escuchamos INSERT y UPDATE
|
|
schema: 'public',
|
|
table: 'plan_mensajes_ia',
|
|
filter: `conversacion_plan_id=eq.${conversationId}`,
|
|
},
|
|
(payload) => {
|
|
// Opción A: Invalidar la query para que React Query haga refetch (más seguro)
|
|
queryClient.invalidateQueries({
|
|
queryKey: ['conversation-messages', conversationId],
|
|
})
|
|
|
|
/* Opción B: Actualización manual del caché (más rápido/fluido)
|
|
if (payload.eventType === 'INSERT') {
|
|
queryClient.setQueryData(['conversation-messages', conversationId], (old: any) => [...old, payload.new])
|
|
} else if (payload.eventType === 'UPDATE') {
|
|
queryClient.setQueryData(['conversation-messages', conversationId], (old: any) =>
|
|
old.map((m: any) => m.id === payload.new.id ? payload.new : m)
|
|
)
|
|
}
|
|
*/
|
|
},
|
|
)
|
|
.subscribe()
|
|
|
|
return () => {
|
|
supabase.removeChannel(channel)
|
|
}
|
|
}, [conversationId, queryClient, supabase])
|
|
|
|
return query
|
|
}
|
|
|
|
export function useUpdateRecommendationApplied() {
|
|
const qc = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: ({
|
|
conversacionId,
|
|
campoAfectado,
|
|
}: {
|
|
conversacionId: string
|
|
campoAfectado: string
|
|
}) => update_recommendation_applied_status(conversacionId, campoAfectado),
|
|
|
|
onSuccess: (_, variables) => {
|
|
// Invalidamos la query para que useConversationByPlan refresque el JSON
|
|
qc.invalidateQueries({ queryKey: ['conversation-by-plan'] })
|
|
console.log(
|
|
`Recomendación ${variables.campoAfectado} marcada como aplicada.`,
|
|
)
|
|
},
|
|
onError: (error) => {
|
|
console.error('Error al actualizar el estado de la recomendación:', error)
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useAISubjectImprove() {
|
|
return useMutation({ mutationFn: ai_subject_improve })
|
|
}
|
|
|
|
export function useLibrarySearch() {
|
|
return useMutation({ mutationFn: library_search })
|
|
}
|
|
|
|
export function useUpdateConversationTitle() {
|
|
const qc = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: ({ id, nombre }: { id: string; nombre: string }) =>
|
|
update_conversation_title(id, nombre),
|
|
onSuccess: (_, variables) => {
|
|
// Invalidamos para que la lista de chats se refresque
|
|
qc.invalidateQueries({ queryKey: ['conversation-by-plan'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
// Asignaturas
|
|
|
|
export function useAISubjectChat() {
|
|
const qc = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async (payload: {
|
|
subjectId: UUID
|
|
content: string
|
|
campos?: Array<string>
|
|
conversacionId?: string
|
|
}) => {
|
|
let currentId = payload.conversacionId
|
|
|
|
// 1. Si no hay ID, creamos la conversación de asignatura
|
|
if (!currentId) {
|
|
const response = await create_subject_conversation(payload.subjectId)
|
|
currentId = response.conversation_asignatura.id
|
|
}
|
|
|
|
// 2. Enviamos mensaje al endpoint de asignatura
|
|
const result = await ai_subject_chat_v2({
|
|
conversacionId: currentId!,
|
|
content: payload.content,
|
|
campos: payload.campos,
|
|
})
|
|
|
|
return { ...result, conversacionId: currentId }
|
|
},
|
|
onSuccess: (data) => {
|
|
// Invalidamos mensajes para que se refresque el chat
|
|
qc.invalidateQueries({
|
|
queryKey: ['subject-messages', data.conversacionId],
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useConversationBySubject(subjectId: string | null) {
|
|
return useQuery({
|
|
queryKey: ['conversation-by-subject', subjectId],
|
|
queryFn: () => getConversationBySubject(subjectId!),
|
|
enabled: !!subjectId,
|
|
})
|
|
}
|
|
|
|
export function useMessagesBySubjectChat(conversationId: string | null) {
|
|
const queryClient = useQueryClient()
|
|
|
|
const query = useQuery({
|
|
queryKey: ['subject-messages', conversationId],
|
|
queryFn: async () => {
|
|
if (!conversationId) throw new Error('Conversation ID is required')
|
|
return getMessagesBySubjectConversation(conversationId)
|
|
},
|
|
enabled: !!conversationId,
|
|
placeholderData: (previousData) => previousData,
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (!conversationId) return
|
|
|
|
const supabase = supabaseBrowser()
|
|
|
|
// Suscripción a cambios en la tabla específica para esta conversación
|
|
const channel = supabase
|
|
.channel(`subject_messages_${conversationId}`)
|
|
.on(
|
|
'postgres_changes',
|
|
{
|
|
event: 'UPDATE', // Solo nos interesan las actualizaciones (cuando pasa de PROCESANDO a COMPLETADO)
|
|
schema: 'public',
|
|
table: 'asignatura_mensajes_ia',
|
|
filter: `conversacion_asignatura_id=eq.${conversationId}`,
|
|
},
|
|
(payload) => {
|
|
// Si el mensaje se completó o dio error, invalidamos la caché para traer los datos nuevos
|
|
if (
|
|
payload.new.estado === 'COMPLETADO' ||
|
|
payload.new.estado === 'ERROR'
|
|
) {
|
|
queryClient.invalidateQueries({
|
|
queryKey: ['subject-messages', conversationId],
|
|
})
|
|
}
|
|
},
|
|
)
|
|
.subscribe()
|
|
|
|
return () => {
|
|
supabase.removeChannel(channel)
|
|
}
|
|
}, [conversationId, queryClient])
|
|
|
|
return query
|
|
}
|
|
|
|
export function useUpdateSubjectRecommendation() {
|
|
const qc = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: (payload: { mensajeId: string; campoAfectado: string }) =>
|
|
update_subject_recommendation_applied(
|
|
payload.mensajeId,
|
|
payload.campoAfectado,
|
|
),
|
|
onSuccess: () => {
|
|
// Refrescamos los mensajes para ver el check de "aplicado"
|
|
qc.invalidateQueries({ queryKey: ['subject-messages'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useUpdateSubjectConversationStatus() {
|
|
const qc = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: (payload: { id: string; estado: 'ARCHIVADA' | 'ACTIVA' }) =>
|
|
update_subject_conversation_status(payload.id, payload.estado),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['conversation-by-subject'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useUpdateSubjectConversationName() {
|
|
const qc = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: (payload: { id: string; nombre: string }) =>
|
|
update_subject_conversation_name(payload.id, payload.nombre),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['conversation-by-subject'] })
|
|
// También invalidamos los mensajes si el título se muestra en la cabecera
|
|
qc.invalidateQueries({ queryKey: ['subject-messages'] })
|
|
},
|
|
})
|
|
}
|