Implementar conversación con HOOKS #111
This commit is contained in:
@@ -1,81 +1,177 @@
|
||||
import { invokeEdge } from "../supabase/invokeEdge";
|
||||
import type { InteraccionIA, UUID } from "../types/domain";
|
||||
import { supabaseBrowser } from '../supabase/client'
|
||||
import { invokeEdge } from '../supabase/invokeEdge'
|
||||
|
||||
import type { InteraccionIA, UUID } from '../types/domain'
|
||||
|
||||
const EDGE = {
|
||||
ai_plan_improve: "ai_plan_improve",
|
||||
ai_plan_chat: "ai_plan_chat",
|
||||
ai_subject_improve: "ai_subject_improve",
|
||||
ai_subject_chat: "ai_subject_chat",
|
||||
ai_plan_improve: 'ai_plan_improve',
|
||||
ai_plan_chat: 'ai_plan_chat',
|
||||
ai_subject_improve: 'ai_subject_improve',
|
||||
ai_subject_chat: 'ai_subject_chat',
|
||||
|
||||
library_search: "library_search",
|
||||
} as const;
|
||||
library_search: 'library_search',
|
||||
} as const
|
||||
|
||||
export async function ai_plan_improve(payload: {
|
||||
planId: UUID;
|
||||
sectionKey: string; // ej: "perfil_de_egreso" o tu key interna
|
||||
prompt: string;
|
||||
context?: Record<string, any>;
|
||||
planId: UUID
|
||||
sectionKey: string // ej: "perfil_de_egreso" o tu key interna
|
||||
prompt: string
|
||||
context?: Record<string, any>
|
||||
fuentes?: {
|
||||
archivosIds?: UUID[];
|
||||
vectorStoresIds?: UUID[];
|
||||
usarMCP?: boolean;
|
||||
conversacionId?: string;
|
||||
};
|
||||
archivosIds?: Array<UUID>
|
||||
vectorStoresIds?: Array<UUID>
|
||||
usarMCP?: boolean
|
||||
conversacionId?: string
|
||||
}
|
||||
}): Promise<{ interaccion: InteraccionIA; propuesta: any }> {
|
||||
return invokeEdge<{ interaccion: InteraccionIA; propuesta: any }>(EDGE.ai_plan_improve, payload);
|
||||
return invokeEdge<{ interaccion: InteraccionIA; propuesta: any }>(
|
||||
EDGE.ai_plan_improve,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export async function ai_plan_chat(payload: {
|
||||
planId: UUID;
|
||||
messages: Array<{ role: "system" | "user" | "assistant"; content: string }>;
|
||||
planId: UUID
|
||||
messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>
|
||||
fuentes?: {
|
||||
archivosIds?: UUID[];
|
||||
vectorStoresIds?: UUID[];
|
||||
usarMCP?: boolean;
|
||||
conversacionId?: string;
|
||||
};
|
||||
archivosIds?: Array<UUID>
|
||||
vectorStoresIds?: Array<UUID>
|
||||
usarMCP?: boolean
|
||||
conversacionId?: string
|
||||
}
|
||||
}): Promise<{ interaccion: InteraccionIA; reply: string; meta?: any }> {
|
||||
return invokeEdge<{ interaccion: InteraccionIA; reply: string; meta?: any }>(EDGE.ai_plan_chat, payload);
|
||||
return invokeEdge<{ interaccion: InteraccionIA; reply: string; meta?: any }>(
|
||||
EDGE.ai_plan_chat,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export async function ai_subject_improve(payload: {
|
||||
subjectId: UUID;
|
||||
sectionKey: string;
|
||||
prompt: string;
|
||||
context?: Record<string, any>;
|
||||
subjectId: UUID
|
||||
sectionKey: string
|
||||
prompt: string
|
||||
context?: Record<string, any>
|
||||
fuentes?: {
|
||||
archivosIds?: UUID[];
|
||||
vectorStoresIds?: UUID[];
|
||||
usarMCP?: boolean;
|
||||
conversacionId?: string;
|
||||
};
|
||||
archivosIds?: Array<UUID>
|
||||
vectorStoresIds?: Array<UUID>
|
||||
usarMCP?: boolean
|
||||
conversacionId?: string
|
||||
}
|
||||
}): Promise<{ interaccion: InteraccionIA; propuesta: any }> {
|
||||
return invokeEdge<{ interaccion: InteraccionIA; propuesta: any }>(EDGE.ai_subject_improve, payload);
|
||||
return invokeEdge<{ interaccion: InteraccionIA; propuesta: any }>(
|
||||
EDGE.ai_subject_improve,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export async function ai_subject_chat(payload: {
|
||||
subjectId: UUID;
|
||||
messages: Array<{ role: "system" | "user" | "assistant"; content: string }>;
|
||||
subjectId: UUID
|
||||
messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>
|
||||
fuentes?: {
|
||||
archivosIds?: UUID[];
|
||||
vectorStoresIds?: UUID[];
|
||||
usarMCP?: boolean;
|
||||
conversacionId?: string;
|
||||
};
|
||||
archivosIds?: Array<UUID>
|
||||
vectorStoresIds?: Array<UUID>
|
||||
usarMCP?: boolean
|
||||
conversacionId?: string
|
||||
}
|
||||
}): Promise<{ interaccion: InteraccionIA; reply: string; meta?: any }> {
|
||||
return invokeEdge<{ interaccion: InteraccionIA; reply: string; meta?: any }>(EDGE.ai_subject_chat, payload);
|
||||
return invokeEdge<{ interaccion: InteraccionIA; reply: string; meta?: any }>(
|
||||
EDGE.ai_subject_chat,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
/** Biblioteca (Edge; adapta a tu API real) */
|
||||
export type LibraryItem = {
|
||||
id: string;
|
||||
titulo: string;
|
||||
autor?: string;
|
||||
isbn?: string;
|
||||
citaSugerida?: string;
|
||||
disponibilidad?: string;
|
||||
};
|
||||
|
||||
export async function library_search(payload: { query: string; limit?: number }): Promise<LibraryItem[]> {
|
||||
return invokeEdge<LibraryItem[]>(EDGE.library_search, payload);
|
||||
id: string
|
||||
titulo: string
|
||||
autor?: string
|
||||
isbn?: string
|
||||
citaSugerida?: string
|
||||
disponibilidad?: string
|
||||
}
|
||||
|
||||
export async function library_search(payload: {
|
||||
query: string
|
||||
limit?: number
|
||||
}): Promise<Array<LibraryItem>> {
|
||||
return invokeEdge<Array<LibraryItem>>(EDGE.library_search, payload)
|
||||
}
|
||||
|
||||
export async function create_conversation(planId: string) {
|
||||
const supabase = supabaseBrowser()
|
||||
const { data, error } = await supabase.functions.invoke(
|
||||
'create-chat-conversation/conversations',
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
plan_estudio_id: planId, // O el nombre que confirmamos que funciona
|
||||
instanciador: 'alex',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (error) throw error
|
||||
|
||||
// LOG de depuración: Mira qué estructura trae 'data'
|
||||
console.log('Respuesta creación conv:', data)
|
||||
|
||||
// Si data es { id: "..." }, devolvemos data.
|
||||
// Si data viene envuelto, asegúrate de retornar el objeto con el id.
|
||||
return data
|
||||
}
|
||||
|
||||
export async function get_chat_history(conversacionId: string) {
|
||||
const supabase = supabaseBrowser()
|
||||
const { data, error } = await supabase.functions.invoke(
|
||||
`create-chat-conversation/conversations/${conversacionId}/messages`,
|
||||
{ method: 'GET' },
|
||||
)
|
||||
if (error) throw error
|
||||
return data // Retorna Array de mensajes
|
||||
}
|
||||
|
||||
export async function archive_conversation(conversacionId: string) {
|
||||
const supabase = supabaseBrowser()
|
||||
const { data, error } = await supabase.functions.invoke(
|
||||
`create-chat-conversation/conversations/${conversacionId}/archive`,
|
||||
{ method: 'DELETE' },
|
||||
)
|
||||
if (error) throw error
|
||||
return data
|
||||
}
|
||||
|
||||
// Modificamos la función de chat para que use la ruta de mensajes
|
||||
export async function ai_plan_chat_v2(payload: {
|
||||
conversacionId: string
|
||||
content: string
|
||||
campos?: Array<string>
|
||||
}): Promise<{ reply: string; meta?: any }> {
|
||||
const supabase = supabaseBrowser()
|
||||
const { data, error } = await supabase.functions.invoke(
|
||||
`create-chat-conversation/conversations/${payload.conversacionId}/messages`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
content: payload.content,
|
||||
campos: payload.campos || [],
|
||||
},
|
||||
},
|
||||
)
|
||||
if (error) throw error
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getConversationByPlan(planId: string) {
|
||||
const supabase = supabaseBrowser()
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('conversaciones_plan')
|
||||
.select('*')
|
||||
.eq('plan_estudio_id', planId)
|
||||
.eq('estado', 'ACTIVA')
|
||||
.order('creado_en', { ascending: true }) // Añade un orden para que el último sea el más nuevo
|
||||
|
||||
if (error) throw error
|
||||
|
||||
return data ?? [] // Devuelve un array vacío en lugar de null para evitar el "undefined"
|
||||
}
|
||||
|
||||
@@ -1,29 +1,94 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
import {
|
||||
ai_plan_chat,
|
||||
ai_plan_chat_v2,
|
||||
ai_plan_improve,
|
||||
ai_subject_chat,
|
||||
ai_subject_improve,
|
||||
archive_conversation,
|
||||
create_conversation,
|
||||
get_chat_history,
|
||||
getConversationByPlan,
|
||||
library_search,
|
||||
} from "../api/ai.api";
|
||||
} from '../api/ai.api'
|
||||
|
||||
export function useAIPlanImprove() {
|
||||
return useMutation({ mutationFn: ai_plan_improve });
|
||||
return useMutation({ mutationFn: ai_plan_improve })
|
||||
}
|
||||
|
||||
export function useAIPlanChat() {
|
||||
return useMutation({ mutationFn: ai_plan_chat });
|
||||
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
|
||||
|
||||
console.log('Nuevo ID extraído:', currentId)
|
||||
}
|
||||
|
||||
// 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 () => {
|
||||
console.log('--- EJECUTANDO QUERY FN ---')
|
||||
console.log('ID RECIBIDO:', conversacionId)
|
||||
return get_chat_history(conversacionId!)
|
||||
},
|
||||
// Simplificamos el enabled para probar
|
||||
enabled: Boolean(conversacionId),
|
||||
})
|
||||
}
|
||||
|
||||
export function useArchiveConversation() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => archive_conversation(id),
|
||||
onSuccess: () => {
|
||||
// Opcional: limpiar datos viejos de la caché
|
||||
queryClient.invalidateQueries({ queryKey: ['chat-history'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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 useAISubjectImprove() {
|
||||
return useMutation({ mutationFn: ai_subject_improve });
|
||||
return useMutation({ mutationFn: ai_subject_improve })
|
||||
}
|
||||
|
||||
export function useAISubjectChat() {
|
||||
return useMutation({ mutationFn: ai_subject_chat });
|
||||
return useMutation({ mutationFn: ai_subject_chat })
|
||||
}
|
||||
|
||||
export function useLibrarySearch() {
|
||||
return useMutation({ mutationFn: library_search });
|
||||
return useMutation({ mutationFn: library_search })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user