// routes/_authenticated/archivos.tsx import { createFileRoute, useRouter } from "@tanstack/react-router" import { useEffect, useMemo, useState } from "react" import { supabase, useSupabaseAuth } from "@/auth/supabase" import * as Icons from "lucide-react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Label } from "@/components/ui/label" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Textarea } from "@/components/ui/textarea" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" type EdgeModule = "files" | "vectorStores" | "vectorStoreFiles" interface VectorStore { id: string object: "vector_store" created_at: number name: string | null description?: string | null usage_bytes: number file_counts: { in_progress: number completed: number failed: number cancelled: number total: number } status: string last_active_at?: number | null metadata?: Record | null } interface VectorStoreFile { id: string object: string created_at: number vector_store_id: string status: string usage_bytes: number last_error?: { code: string; message: string } | null } interface VectorStoreFileMeta { id: string user_id: string | null vector_store_id: string openai_file_id: string label: string | null tags: string[] | null created_at: string } type EdgeArgs = { module: EdgeModule action: string params?: Record } async function callFilesAndVectorStoresApi( args: EdgeArgs, ): Promise { const { data, error } = await supabase.functions.invoke( "files-and-vector-stores-api", { body: args, }, ) if (error) { console.error(error) throw error } const payload = data ?? {} if (payload.error) { const msg = typeof payload.error === "string" ? payload.error : payload.error.message ?? "Error en la función Edge" throw new Error(msg) } const result = payload.data !== undefined ? payload.data : payload return result as T } export const Route = createFileRoute("/_authenticated/archivos")({ component: RouteComponent, loader: async () => { const stores = await callFilesAndVectorStoresApi({ module: "vectorStores", action: "list", params: { limit: 10, }, }) return stores ?? [] }, }) /* ====== UI helpers ====== */ function StatusBadge({ status }: { status: string }) { const label = status === "completed" ? "Completado" : status === "in_progress" ? "Procesando" : status const base = "text-[10px] px-2 py-0.5 rounded-full border" if (status === "completed") { return ( {label} ) } if (status === "in_progress") { return ( {label} ) } return ( {label} ) } /* ====== Página principal: lista repositorios (Vector Stores) ====== */ function RouteComponent() { const router = useRouter() const vectorStores = Route.useLoaderData() as VectorStore[] const [q, setQ] = useState("") const [statusFilter, setStatusFilter] = useState<"all" | "completed" | "in_progress">("all") const [selected, setSelected] = useState(null) const [dialogOpen, setDialogOpen] = useState(false) const [createOpen, setCreateOpen] = useState(false) const [deletingId, setDeletingId] = useState(null) const filtered = useMemo(() => { const term = q.trim().toLowerCase() return vectorStores.filter((vs) => { if (statusFilter !== "all" && vs.status !== statusFilter) return false if (!term) return true return ( (vs.name ?? "").toLowerCase().includes(term) || (vs.description ?? "").toLowerCase().includes(term) ) }) }, [vectorStores, q, statusFilter]) function openDetails(vs: VectorStore) { setSelected(vs) setDialogOpen(true) } async function handleDelete(id: string) { if (!confirm("¿Eliminar este repositorio y sus archivos asociados en OpenAI?")) return setDeletingId(id) try { await callFilesAndVectorStoresApi({ module: "vectorStores", action: "delete", params: { vector_store_id: id }, }) await supabase .from("vector_store_files_meta") .delete() .eq("vector_store_id", id) router.invalidate() } catch (err: any) { alert(err?.message ?? "Error al eliminar el repositorio") } finally { setDeletingId(null) } } return (
Repositorios de archivos
setQ(e.target.value)} placeholder="Buscar por nombre o descripción…" className="pl-8" />
{filtered.length ? (
{filtered.map((vs) => (

{vs.name || "(Sin nombre)"}

Archivos: {vs.file_counts?.completed ?? 0} {typeof vs.usage_bytes === "number" && ( {(vs.usage_bytes / 1024 / 1024).toFixed(2)} MB )} {vs.last_active_at && ( {new Date(vs.last_active_at * 1000).toLocaleDateString()} )}
{vs.description && (

{vs.description}

)}
))}
) : (
No hay repositorios todavía. Crea uno nuevo para empezar 🚀
)}
router.invalidate()} /> { setDialogOpen(open) if (!open) setSelected(null) }} onUpdated={() => router.invalidate()} />
) } /* ====== Crear repositorio ====== */ function CreateVectorStoreDialog({ open, onOpenChange, onCreated, }: { open: boolean onOpenChange: (open: boolean) => void onCreated: () => void }) { const [name, setName] = useState("") const [description, setDescription] = useState("") const [creating, setCreating] = useState(false) async function handleCreate() { if (!name.trim()) { alert("Escribe un nombre para el repositorio") return } setCreating(true) try { await callFilesAndVectorStoresApi({ module: "vectorStores", action: "create", params: { name: name.trim(), description: description.trim() || undefined }, }) onOpenChange(false) setName("") setDescription("") onCreated() } catch (err: any) { alert(err?.message ?? "Error al crear el repositorio") } finally { setCreating(false) } } return ( Nuevo repositorio Crea un Vector Store para agrupar archivos relacionados.
setName(e.target.value)} placeholder="Planeación curricular, Entrevistas…" />