Merge pull request 'Chats de ia en segundo plano para asignaturas #160' (#161) from issue/160-chats-de-ia-en-segundo-plano-para-asignaturas into main
Reviewed-on: #161
This commit was merged in pull request #161.
This commit is contained in:
@@ -76,6 +76,17 @@ export function IAAsignaturaTab({
|
|||||||
const [isCreatingNewChat, setIsCreatingNewChat] = useState(false)
|
const [isCreatingNewChat, setIsCreatingNewChat] = useState(false)
|
||||||
const hasInitialSelected = useRef(false)
|
const hasInitialSelected = useRef(false)
|
||||||
|
|
||||||
|
const isAiThinking = useMemo(() => {
|
||||||
|
if (isSending) return true
|
||||||
|
if (!rawMessages || rawMessages.length === 0) return false
|
||||||
|
|
||||||
|
// Verificamos si el último mensaje está en estado de procesamiento
|
||||||
|
const lastMessage = rawMessages[rawMessages.length - 1]
|
||||||
|
return (
|
||||||
|
lastMessage.estado === 'PROCESANDO' || lastMessage.estado === 'PENDIENTE'
|
||||||
|
)
|
||||||
|
}, [isSending, rawMessages])
|
||||||
|
|
||||||
// --- AUTO-SCROLL ---
|
// --- AUTO-SCROLL ---
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const viewport = scrollRef.current?.querySelector(
|
const viewport = scrollRef.current?.querySelector(
|
||||||
@@ -392,11 +403,23 @@ export function IAAsignaturaTab({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{isSending && (
|
{isAiThinking && (
|
||||||
<div className="flex animate-pulse gap-2 p-4">
|
<div className="animate-in fade-in flex flex-row items-start gap-3 duration-300">
|
||||||
<div className="h-2 w-2 rounded-full bg-teal-400" />
|
<Avatar className="h-8 w-8 shrink-0 border bg-teal-50">
|
||||||
<div className="h-2 w-2 rounded-full bg-teal-400" />
|
<AvatarFallback>
|
||||||
<div className="h-2 w-2 rounded-full bg-teal-400" />
|
<Sparkles
|
||||||
|
size={14}
|
||||||
|
className="animate-pulse text-teal-600"
|
||||||
|
/>
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="rounded-2xl rounded-tl-none border bg-white p-4 shadow-sm">
|
||||||
|
<div className="flex gap-1.5">
|
||||||
|
<div className="h-2 w-2 animate-bounce rounded-full bg-teal-400 [animation-delay:-0.3s]" />
|
||||||
|
<div className="h-2 w-2 animate-bounce rounded-full bg-teal-400 [animation-delay:-0.15s]" />
|
||||||
|
<div className="h-2 w-2 animate-bounce rounded-full bg-teal-400" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ export async function getConversationBySubject(subjectId: string) {
|
|||||||
export async function getMessagesBySubjectConversation(conversationId: string) {
|
export async function getMessagesBySubjectConversation(conversationId: string) {
|
||||||
const supabase = supabaseBrowser()
|
const supabase = supabaseBrowser()
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('asignatura_mensajes_ia') // Tabla corregida
|
.from('asignatura_mensajes_ia' as any)
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('conversacion_asignatura_id', conversationId)
|
.eq('conversacion_asignatura_id', conversationId)
|
||||||
.order('fecha_creacion', { ascending: true })
|
.order('fecha_creacion', { ascending: true })
|
||||||
|
|||||||
@@ -243,15 +243,54 @@ export function useConversationBySubject(subjectId: string | null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useMessagesBySubjectChat(conversationId: string | null) {
|
export function useMessagesBySubjectChat(conversationId: string | null) {
|
||||||
return useQuery({
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const query = useQuery({
|
||||||
queryKey: ['subject-messages', conversationId],
|
queryKey: ['subject-messages', conversationId],
|
||||||
queryFn: () => {
|
queryFn: async () => {
|
||||||
if (!conversationId) throw new Error('Conversation ID is required')
|
if (!conversationId) throw new Error('Conversation ID is required')
|
||||||
return getMessagesBySubjectConversation(conversationId)
|
return getMessagesBySubjectConversation(conversationId)
|
||||||
},
|
},
|
||||||
enabled: !!conversationId,
|
enabled: !!conversationId,
|
||||||
placeholderData: (previousData) => previousData,
|
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() {
|
export function useUpdateSubjectRecommendation() {
|
||||||
|
|||||||
Reference in New Issue
Block a user