add redis backend

This commit is contained in:
Anibal Angulo
2025-11-06 16:24:05 -06:00
parent 86e5c955c5
commit c5e0a451c0
13 changed files with 1302 additions and 650 deletions

View File

@@ -324,22 +324,22 @@ export function Dashboard({ onProcessingChange }: DashboardProps = {}) {
</div>
<Tabs defaultValue="files" className="flex flex-col flex-1">
<div className="border-b border-gray-200 px-6 py-2">
<TabsList className="flex h-10 items-center gap-2 bg-transparent p-0 justify-start">
<TabsList className="flex h-10 w-full items-center gap-2 bg-transparent p-0 justify-start">
<TabsTrigger
value="dashboard"
className="rounded-md px-4 py-2 text-sm font-medium text-gray-600 transition data-[state=active]:bg-black data-[state=active]:text-white data-[state=active]:shadow-lg data-[state=active]:ring-0 data-[state=active]:shadow-black/30"
className="rounded-md px-4 py-2 text-sm font-medium text-gray-600 transition data-[state=active]:bg-gray-900 data-[state=active]:text-white data-[state=active]:shadow"
>
Dashboard
</TabsTrigger>
<TabsTrigger
value="files"
className="rounded-md px-4 py-2 text-sm font-medium text-gray-600 transition data-[state=active]:bg-black data-[state=active]:text-white data-[state=active]:shadow-lg data-[state=active]:ring-0 data-[state=active]:shadow-black/30"
className="rounded-md px-4 py-2 text-sm font-medium text-gray-600 transition data-[state=active]:bg-gray-900 data-[state=active]:text-white data-[state=active]:shadow"
>
Files
</TabsTrigger>
<TabsTrigger
value="chat"
className="rounded-md px-4 py-2 text-sm font-medium text-gray-600 transition data-[state=active]:bg-black data-[state=active]:text-white data-[state=active]:shadow-lg data-[state=active]:ring-0 data-[state=active]:shadow-black/30"
className="rounded-md px-4 py-2 text-sm font-medium text-gray-600 transition data-[state=active]:bg-gray-900 data-[state=active]:text-white data-[state=active]:shadow"
>
Chat
</TabsTrigger>

View File

@@ -10,6 +10,7 @@ import {
ChevronLeft,
ChevronRight,
RefreshCcw,
Plus,
} from "lucide-react";
import { cn } from "@/lib/utils";
import {
@@ -18,6 +19,16 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
interface SidebarProps {
onNavigateToSchemas?: () => void;
@@ -42,6 +53,10 @@ export function Sidebar({
} = useFileStore();
const [deletingTema, setDeletingTema] = useState<string | null>(null);
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [newDataroomName, setNewDataroomName] = useState("");
const [creatingDataroom, setCreatingDataroom] = useState(false);
const [createError, setCreateError] = useState<string | null>(null);
const renderWithTooltip = (label: string, element: ReactElement) => {
if (!collapsed) {
@@ -58,6 +73,51 @@ export function Sidebar({
);
};
const handleCreateDialogOpenChange = (open: boolean) => {
setCreateDialogOpen(open);
if (!open) {
setNewDataroomName("");
setCreateError(null);
}
};
const handleCreateDataroom = async () => {
const trimmed = newDataroomName.trim();
if (!trimmed) {
setCreateError("El nombre es obligatorio");
return;
}
setCreatingDataroom(true);
setCreateError(null);
try {
console.log("Creating dataroom:", trimmed);
const result = await api.createDataroom({ name: trimmed });
console.log("Dataroom created successfully:", result);
// Refresh the datarooms list (this will load all datarooms including the new one)
console.log("Refreshing dataroom list...");
await loadTemas();
console.log("Dataroom list refreshed");
// Select the newly created dataroom
setSelectedTema(trimmed);
// Close dialog and show success
handleCreateDialogOpenChange(false);
} catch (error) {
console.error("Error creating dataroom:", error);
setCreateError(
error instanceof Error
? error.message
: "No se pudo crear el dataroom. Inténtalo nuevamente.",
);
} finally {
setCreatingDataroom(false);
}
};
useEffect(() => {
loadTemas();
}, []);
@@ -65,10 +125,35 @@ export function Sidebar({
const loadTemas = async () => {
try {
setLoading(true);
const response = await api.getTemas();
setTemas(response.temas);
const response = await api.getDatarooms();
console.log("Raw datarooms response:", response);
// Extract dataroom names from the response with better error handling
let dataroomNames: string[] = [];
if (response && response.datarooms && Array.isArray(response.datarooms)) {
dataroomNames = response.datarooms
.filter((dataroom) => dataroom && dataroom.name)
.map((dataroom) => dataroom.name);
}
setTemas(dataroomNames);
console.log("Loaded datarooms:", dataroomNames);
} catch (error) {
console.error("Error loading temas:", error);
console.error("Error loading datarooms:", error);
// Fallback to legacy getTemas if dataroom endpoint fails
try {
console.log("Falling back to legacy getTemas endpoint");
const legacyResponse = await api.getTemas();
const legacyTemas = Array.isArray(legacyResponse?.temas)
? legacyResponse.temas.filter(Boolean)
: [];
setTemas(legacyTemas);
console.log("Loaded legacy temas:", legacyTemas);
} catch (legacyError) {
console.error("Error loading legacy temas:", legacyError);
// Ensure we always set an array, never undefined or null
setTemas([]);
}
} finally {
setLoading(false);
}
@@ -85,8 +170,9 @@ export function Sidebar({
e.stopPropagation(); // Evitar que se seleccione el tema al hacer clic en el icono
const confirmed = window.confirm(
`¿Estás seguro de que deseas eliminar el tema "${tema}"?\n\n` +
`¿Estás seguro de que deseas eliminar el dataroom "${tema}"?\n\n` +
`Esto eliminará:\n` +
`• El dataroom de la base de datos\n` +
`• Todos los archivos del tema en Azure Blob Storage\n` +
`• La colección "${tema}" en Qdrant (si existe)\n\n` +
`Esta acción no se puede deshacer.`,
@@ -97,35 +183,44 @@ export function Sidebar({
try {
setDeletingTema(tema);
// 1. Eliminar todos los archivos del tema en Azure Blob Storage
await api.deleteTema(tema);
// 2. Intentar eliminar la colección en Qdrant (si existe)
// 1. Delete the dataroom (this will also delete the vector collection)
try {
const collectionExists = await api.checkCollectionExists(tema);
if (collectionExists.exists) {
await api.deleteCollection(tema);
console.log(`Colección "${tema}" eliminada de Qdrant`);
}
await api.deleteDataroom(tema);
console.log(`Dataroom "${tema}" deleted successfully`);
} catch (error) {
console.warn(
`No se pudo eliminar la colección "${tema}" de Qdrant:`,
error,
);
// Continuar aunque falle la eliminación de la colección
console.error(`Error deleting dataroom "${tema}":`, error);
// If dataroom deletion fails, fall back to legacy deletion
console.log("Falling back to legacy deletion methods");
// Eliminar todos los archivos del tema en Azure Blob Storage
await api.deleteTema(tema);
// Intentar eliminar la colección en Qdrant (si existe)
try {
const collectionExists = await api.checkCollectionExists(tema);
if (collectionExists.exists) {
await api.deleteCollection(tema);
console.log(`Colección "${tema}" eliminada de Qdrant`);
}
} catch (collectionError) {
console.warn(
`No se pudo eliminar la colección "${tema}" de Qdrant:`,
collectionError,
);
}
}
// 3. Actualizar la lista de temas
// 2. Actualizar la lista de temas
await loadTemas();
// 4. Si el tema eliminado estaba seleccionado, deseleccionar
// 3. Si el tema eliminado estaba seleccionado, deseleccionar
if (selectedTema === tema) {
setSelectedTema(null);
}
} catch (error) {
console.error(`Error eliminando tema "${tema}":`, error);
console.error(`Error eliminando dataroom "${tema}":`, error);
alert(
`Error al eliminar el tema: ${error instanceof Error ? error.message : "Error desconocido"}`,
`Error al eliminar el dataroom: ${error instanceof Error ? error.message : "Error desconocido"}`,
);
} finally {
setDeletingTema(null);
@@ -174,14 +269,39 @@ export function Sidebar({
{/* Temas List */}
<div className={cn("flex-1 overflow-y-auto p-4", collapsed && "px-2")}>
<div className="space-y-1">
<h2
<div
className={cn(
"text-sm font-medium text-gray-500 mb-3",
collapsed && "text-xs text-center",
"mb-3 flex items-center",
collapsed ? "justify-center" : "justify-between",
)}
>
{collapsed ? "Coll." : "Collections"}
</h2>
<h2
className={cn(
"text-sm font-medium text-gray-500",
collapsed && "text-xs text-center",
)}
>
{collapsed ? "Rooms" : "Datarooms"}
</h2>
{renderWithTooltip(
"Crear dataroom",
<Button
variant="outline"
size="sm"
className={cn(
"gap-2",
collapsed
? "h-10 w-10 p-0 justify-center rounded-full"
: "",
)}
onClick={() => handleCreateDialogOpenChange(true)}
disabled={disabled || creatingDataroom}
>
<Plus className="h-4 w-4" />
{!collapsed && <span>Crear dataroom</span>}
</Button>,
)}
</div>
{/* Todos los archivos */}
{renderWithTooltip(
@@ -207,7 +327,7 @@ export function Sidebar({
<div className="text-sm text-gray-500 px-3 py-2 text-center">
{collapsed ? "..." : "Cargando..."}
</div>
) : (
) : Array.isArray(temas) && temas.length > 0 ? (
temas.map((tema) => (
<div key={tema} className="relative group">
{renderWithTooltip(
@@ -234,13 +354,19 @@ export function Sidebar({
onClick={(e) => handleDeleteTema(tema, e)}
disabled={deletingTema === tema || disabled}
className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 rounded hover:bg-red-100 opacity-0 group-hover:opacity-100 transition-opacity disabled:opacity-50"
title="Eliminar tema y colección"
title="Eliminar dataroom y colección"
>
<Trash2 className="h-4 w-4 text-red-600" />
</button>
)}
</div>
))
) : (
<div className="text-sm text-gray-500 px-3 py-2 text-center">
{Array.isArray(temas) && temas.length === 0
? "No hay datarooms"
: "Cargando datarooms..."}
</div>
)}
</div>
</div>
@@ -272,7 +398,7 @@ export function Sidebar({
</Button>,
)}
{renderWithTooltip(
"Actualizar temas",
"Actualizar datarooms",
<Button
variant="outline"
size="sm"
@@ -285,12 +411,63 @@ export function Sidebar({
>
<RefreshCcw className={cn("mr-2 h-4 w-4", collapsed && "mr-0")} />
<span className={cn(collapsed && "sr-only")}>
Actualizar temas
Actualizar datarooms
</span>
</Button>,
)}
</div>
</div>
<Dialog
open={createDialogOpen}
onOpenChange={handleCreateDialogOpenChange}
>
<DialogContent
className="max-w-sm"
aria-describedby="create-dataroom-description"
>
<DialogHeader>
<DialogTitle>Crear dataroom</DialogTitle>
<DialogDescription id="create-dataroom-description">
Define un nombre único para organizar tus archivos.
</DialogDescription>
</DialogHeader>
<div className="space-y-3">
<div className="space-y-2">
<Label htmlFor="dataroom-name">Nombre del dataroom</Label>
<Input
id="dataroom-name"
value={newDataroomName}
onChange={(e) => {
setNewDataroomName(e.target.value);
if (createError) {
setCreateError(null);
}
}}
placeholder="Ej: normativa, contratos, fiscal..."
autoFocus
/>
{createError && (
<p className="text-sm text-red-500">{createError}</p>
)}
</div>
</div>
<DialogFooter className="mt-4">
<Button
variant="outline"
onClick={() => handleCreateDialogOpenChange(false)}
disabled={creatingDataroom}
>
Cancelar
</Button>
<Button
onClick={handleCreateDataroom}
disabled={creatingDataroom || newDataroomName.trim() === ""}
>
{creatingDataroom ? "Creando..." : "Crear dataroom"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</TooltipProvider>
);
}

View File

@@ -1,191 +1,275 @@
const API_BASE_URL = 'http://localhost:8000/api/v1'
const API_BASE_URL = "/api/v1";
interface FileUploadResponse {
success: boolean
message: string
success: boolean;
message: string;
file?: {
name: string
full_path: string
tema: string
size: number
last_modified: string
url?: string
}
name: string;
full_path: string;
tema: string;
size: number;
last_modified: string;
url?: string;
};
}
interface FileListResponse {
files: Array<{
name: string
full_path: string
tema: string
size: number
last_modified: string
content_type?: string
}>
total: number
tema?: string
name: string;
full_path: string;
tema: string;
size: number;
last_modified: string;
content_type?: string;
}>;
total: number;
tema?: string;
}
interface TemasResponse {
temas: string[]
total: number
temas: string[];
total: number;
}
interface DataroomsResponse {
datarooms: Array<{
name: string;
collection: string;
storage: string;
}>;
}
interface CreateDataroomRequest {
name: string;
collection?: string;
storage?: string;
}
// API calls
export const api = {
// Obtener todos los temas
// Obtener todos los temas (legacy)
getTemas: async (): Promise<TemasResponse> => {
const response = await fetch(`${API_BASE_URL}/files/temas`)
if (!response.ok) throw new Error('Error fetching temas')
return response.json()
const response = await fetch(`${API_BASE_URL}/files/temas`);
if (!response.ok) throw new Error("Error fetching temas");
return response.json();
},
// Obtener todos los datarooms
getDatarooms: async (): Promise<DataroomsResponse> => {
console.log("Fetching datarooms from:", `${API_BASE_URL}/dataroom/`);
const response = await fetch(`${API_BASE_URL}/dataroom/`);
console.log("Datarooms response status:", response.status);
if (!response.ok) {
const errorText = await response.text();
console.error("Datarooms fetch error:", errorText);
throw new Error("Error fetching datarooms");
}
const data = await response.json();
console.log("Datarooms API response:", data);
return data;
},
// Crear un nuevo dataroom
createDataroom: async (
data: CreateDataroomRequest,
): Promise<{
message: string;
dataroom: {
name: string;
collection: string;
storage: string;
};
}> => {
const response = await fetch(`${API_BASE_URL}/dataroom/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (!response.ok) throw new Error("Error creating dataroom");
return response.json();
},
// Eliminar un dataroom
deleteDataroom: async (
dataroomName: string,
): Promise<{
message: string;
dataroom_name: string;
}> => {
const response = await fetch(
`${API_BASE_URL}/dataroom/${encodeURIComponent(dataroomName)}`,
{
method: "DELETE",
},
);
if (!response.ok) throw new Error("Error deleting dataroom");
return response.json();
},
// Obtener archivos (todos o por tema)
getFiles: async (tema?: string): Promise<FileListResponse> => {
const url = tema
const url = tema
? `${API_BASE_URL}/files/?tema=${encodeURIComponent(tema)}`
: `${API_BASE_URL}/files/`
const response = await fetch(url)
if (!response.ok) throw new Error('Error fetching files')
return response.json()
: `${API_BASE_URL}/files/`;
const response = await fetch(url);
if (!response.ok) throw new Error("Error fetching files");
return response.json();
},
// Subir archivo
uploadFile: async (file: File, tema?: string): Promise<FileUploadResponse> => {
const formData = new FormData()
formData.append('file', file)
if (tema) formData.append('tema', tema)
uploadFile: async (
file: File,
tema?: string,
): Promise<FileUploadResponse> => {
const formData = new FormData();
formData.append("file", file);
if (tema) formData.append("tema", tema);
const response = await fetch(`${API_BASE_URL}/files/upload`, {
method: 'POST',
method: "POST",
body: formData,
})
if (!response.ok) throw new Error('Error uploading file')
return response.json()
});
if (!response.ok) throw new Error("Error uploading file");
return response.json();
},
// Eliminar archivo
deleteFile: async (filename: string, tema?: string): Promise<void> => {
const url = tema
const url = tema
? `${API_BASE_URL}/files/${encodeURIComponent(filename)}?tema=${encodeURIComponent(tema)}`
: `${API_BASE_URL}/files/${encodeURIComponent(filename)}`
const response = await fetch(url, { method: 'DELETE' })
if (!response.ok) throw new Error('Error deleting file')
: `${API_BASE_URL}/files/${encodeURIComponent(filename)}`;
const response = await fetch(url, { method: "DELETE" });
if (!response.ok) throw new Error("Error deleting file");
},
// Eliminar múltiples archivos
deleteFiles: async (filenames: string[], tema?: string): Promise<void> => {
const response = await fetch(`${API_BASE_URL}/files/delete-batch`, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
files: filenames,
tema: tema
}),
})
if (!response.ok) throw new Error('Error deleting files')
},
// Eliminar tema completo
deleteTema: async (tema: string): Promise<void> => {
const response = await fetch(`${API_BASE_URL}/files/tema/${encodeURIComponent(tema)}/delete-all`, {
method: 'DELETE'
})
if (!response.ok) throw new Error('Error deleting tema')
},
// Descargar archivo individual
downloadFile: async (filename: string, tema?: string): Promise<void> => {
const url = tema
? `${API_BASE_URL}/files/${encodeURIComponent(filename)}/download?tema=${encodeURIComponent(tema)}`
: `${API_BASE_URL}/files/${encodeURIComponent(filename)}/download`
const response = await fetch(url)
if (!response.ok) throw new Error('Error downloading file')
// Crear blob y descargar
const blob = await response.blob()
const downloadUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(downloadUrl)
},
// Descargar múltiples archivos como ZIP
downloadMultipleFiles: async (filenames: string[], tema?: string, zipName?: string): Promise<void> => {
const response = await fetch(`${API_BASE_URL}/files/download-batch`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify({
files: filenames,
tema: tema,
zip_name: zipName || 'archivos'
}),
})
if (!response.ok) throw new Error('Error downloading files')
});
if (!response.ok) throw new Error("Error deleting files");
},
// Eliminar tema completo
deleteTema: async (tema: string): Promise<void> => {
const response = await fetch(
`${API_BASE_URL}/files/tema/${encodeURIComponent(tema)}/delete-all`,
{
method: "DELETE",
},
);
if (!response.ok) throw new Error("Error deleting tema");
},
// Descargar archivo individual
downloadFile: async (filename: string, tema?: string): Promise<void> => {
const url = tema
? `${API_BASE_URL}/files/${encodeURIComponent(filename)}/download?tema=${encodeURIComponent(tema)}`
: `${API_BASE_URL}/files/${encodeURIComponent(filename)}/download`;
const response = await fetch(url);
if (!response.ok) throw new Error("Error downloading file");
// Crear blob y descargar
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
},
// Descargar múltiples archivos como ZIP
downloadMultipleFiles: async (
filenames: string[],
tema?: string,
zipName?: string,
): Promise<void> => {
const response = await fetch(`${API_BASE_URL}/files/download-batch`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
files: filenames,
tema: tema,
zip_name: zipName || "archivos",
}),
});
if (!response.ok) throw new Error("Error downloading files");
// Crear blob y descargar ZIP
const blob = await response.blob()
const downloadUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
// Obtener nombre del archivo del header Content-Disposition
const contentDisposition = response.headers.get('Content-Disposition')
const filename = contentDisposition?.split('filename=')[1]?.replace(/"/g, '') || 'archivos.zip'
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(downloadUrl)
const contentDisposition = response.headers.get("Content-Disposition");
const filename =
contentDisposition?.split("filename=")[1]?.replace(/"/g, "") ||
"archivos.zip";
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
},
// Descargar tema completo
downloadTema: async (tema: string): Promise<void> => {
const response = await fetch(`${API_BASE_URL}/files/tema/${encodeURIComponent(tema)}/download-all`)
if (!response.ok) throw new Error('Error downloading tema')
const response = await fetch(
`${API_BASE_URL}/files/tema/${encodeURIComponent(tema)}/download-all`,
);
if (!response.ok) throw new Error("Error downloading tema");
const blob = await response.blob()
const downloadUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
const contentDisposition = response.headers.get('Content-Disposition')
const filename = contentDisposition?.split('filename=')[1]?.replace(/"/g, '') || `${tema}.zip`
const contentDisposition = response.headers.get("Content-Disposition");
const filename =
contentDisposition?.split("filename=")[1]?.replace(/"/g, "") ||
`${tema}.zip`;
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(downloadUrl)
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
},
// Obtener URL temporal para preview de archivos
getPreviewUrl: async (filename: string, tema?: string): Promise<string> => {
const url = tema
? `${API_BASE_URL}/files/${encodeURIComponent(filename)}/preview-url?tema=${encodeURIComponent(tema)}`
: `${API_BASE_URL}/files/${encodeURIComponent(filename)}/preview-url`
: `${API_BASE_URL}/files/${encodeURIComponent(filename)}/preview-url`;
const response = await fetch(url)
if (!response.ok) throw new Error('Error getting preview URL')
const response = await fetch(url);
if (!response.ok) throw new Error("Error getting preview URL");
const data = await response.json()
return data.url
const data = await response.json();
return data.url;
},
// ============================================================================
@@ -193,139 +277,178 @@ export const api = {
// ============================================================================
// Health check de la base de datos vectorial
vectorHealthCheck: async (): Promise<{ status: string; db_type: string; message: string }> => {
const response = await fetch(`${API_BASE_URL}/vectors/health`)
if (!response.ok) throw new Error('Error checking vector DB health')
return response.json()
vectorHealthCheck: async (): Promise<{
status: string;
db_type: string;
message: string;
}> => {
const response = await fetch(`${API_BASE_URL}/vectors/health`);
if (!response.ok) throw new Error("Error checking vector DB health");
return response.json();
},
// Verificar si una colección existe
checkCollectionExists: async (collectionName: string): Promise<{ exists: boolean; collection_name: string }> => {
checkCollectionExists: async (
collectionName: string,
): Promise<{ exists: boolean; collection_name: string }> => {
const response = await fetch(`${API_BASE_URL}/vectors/collections/exists`, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify({ collection_name: collectionName }),
})
if (!response.ok) throw new Error('Error checking collection')
return response.json()
});
if (!response.ok) throw new Error("Error checking collection");
return response.json();
},
// Crear una nueva colección
createCollection: async (
collectionName: string,
vectorSize: number = 3072,
distance: string = 'Cosine'
): Promise<{ success: boolean; collection_name: string; message: string }> => {
distance: string = "Cosine",
): Promise<{
success: boolean;
collection_name: string;
message: string;
}> => {
const response = await fetch(`${API_BASE_URL}/vectors/collections/create`, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify({
collection_name: collectionName,
vector_size: vectorSize,
distance: distance,
}),
})
});
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || 'Error creating collection')
const error = await response.json();
throw new Error(error.detail || "Error creating collection");
}
return response.json()
return response.json();
},
// Eliminar una colección
deleteCollection: async (collectionName: string): Promise<{ success: boolean; collection_name: string; message: string }> => {
const response = await fetch(`${API_BASE_URL}/vectors/collections/${encodeURIComponent(collectionName)}`, {
method: 'DELETE',
})
if (!response.ok) throw new Error('Error deleting collection')
return response.json()
deleteCollection: async (
collectionName: string,
): Promise<{
success: boolean;
collection_name: string;
message: string;
}> => {
const response = await fetch(
`${API_BASE_URL}/vectors/collections/${encodeURIComponent(collectionName)}`,
{
method: "DELETE",
},
);
if (!response.ok) throw new Error("Error deleting collection");
return response.json();
},
// Obtener información de una colección
getCollectionInfo: async (collectionName: string): Promise<{
name: string
vectors_count: number
vectors_config: { size: number; distance: string }
status: string
getCollectionInfo: async (
collectionName: string,
): Promise<{
name: string;
vectors_count: number;
vectors_config: { size: number; distance: string };
status: string;
}> => {
const response = await fetch(`${API_BASE_URL}/vectors/collections/${encodeURIComponent(collectionName)}/info`)
if (!response.ok) throw new Error('Error getting collection info')
return response.json()
const response = await fetch(
`${API_BASE_URL}/vectors/collections/${encodeURIComponent(collectionName)}/info`,
);
if (!response.ok) throw new Error("Error getting collection info");
return response.json();
},
// Verificar si un archivo existe en una colección
checkFileExistsInCollection: async (
collectionName: string,
fileName: string
): Promise<{ exists: boolean; collection_name: string; file_name: string; chunk_count?: number }> => {
fileName: string,
): Promise<{
exists: boolean;
collection_name: string;
file_name: string;
chunk_count?: number;
}> => {
const response = await fetch(`${API_BASE_URL}/vectors/files/exists`, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify({
collection_name: collectionName,
file_name: fileName,
}),
})
if (!response.ok) throw new Error('Error checking file in collection')
return response.json()
});
if (!response.ok) throw new Error("Error checking file in collection");
return response.json();
},
// Obtener chunks de un archivo
getChunksByFile: async (
collectionName: string,
fileName: string,
limit?: number
limit?: number,
): Promise<{
collection_name: string
file_name: string
chunks: Array<{ id: string; payload: any; vector?: number[] }>
total_chunks: number
collection_name: string;
file_name: string;
chunks: Array<{ id: string; payload: any; vector?: number[] }>;
total_chunks: number;
}> => {
const url = limit
? `${API_BASE_URL}/vectors/collections/${encodeURIComponent(collectionName)}/files/${encodeURIComponent(fileName)}/chunks?limit=${limit}`
: `${API_BASE_URL}/vectors/collections/${encodeURIComponent(collectionName)}/files/${encodeURIComponent(fileName)}/chunks`
: `${API_BASE_URL}/vectors/collections/${encodeURIComponent(collectionName)}/files/${encodeURIComponent(fileName)}/chunks`;
const response = await fetch(url)
if (!response.ok) throw new Error('Error getting chunks')
return response.json()
const response = await fetch(url);
if (!response.ok) throw new Error("Error getting chunks");
return response.json();
},
// Eliminar archivo de colección
deleteFileFromCollection: async (
collectionName: string,
fileName: string
): Promise<{ success: boolean; collection_name: string; file_name: string; chunks_deleted: number; message: string }> => {
fileName: string,
): Promise<{
success: boolean;
collection_name: string;
file_name: string;
chunks_deleted: number;
message: string;
}> => {
const response = await fetch(
`${API_BASE_URL}/vectors/collections/${encodeURIComponent(collectionName)}/files/${encodeURIComponent(fileName)}`,
{ method: 'DELETE' }
)
if (!response.ok) throw new Error('Error deleting file from collection')
return response.json()
{ method: "DELETE" },
);
if (!response.ok) throw new Error("Error deleting file from collection");
return response.json();
},
// Agregar chunks a una colección
addChunks: async (
collectionName: string,
chunks: Array<{ id: string; vector: number[]; payload: any }>
): Promise<{ success: boolean; collection_name: string; chunks_added: number; message: string }> => {
chunks: Array<{ id: string; vector: number[]; payload: any }>,
): Promise<{
success: boolean;
collection_name: string;
chunks_added: number;
message: string;
}> => {
const response = await fetch(`${API_BASE_URL}/vectors/chunks/add`, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify({
collection_name: collectionName,
chunks: chunks,
}),
})
if (!response.ok) throw new Error('Error adding chunks')
return response.json()
});
if (!response.ok) throw new Error("Error adding chunks");
return response.json();
},
// ============================================================================
@@ -335,89 +458,89 @@ export const api = {
// Obtener perfiles de chunking predefinidos
getChunkingProfiles: async (): Promise<{
profiles: Array<{
id: string
name: string
description: string
max_tokens: number
target_tokens: number
chunk_size: number
chunk_overlap: number
use_llm: boolean
}>
id: string;
name: string;
description: string;
max_tokens: number;
target_tokens: number;
chunk_size: number;
chunk_overlap: number;
use_llm: boolean;
}>;
}> => {
const response = await fetch(`${API_BASE_URL}/chunking/profiles`)
if (!response.ok) throw new Error('Error fetching chunking profiles')
return response.json()
const response = await fetch(`${API_BASE_URL}/chunking/profiles`);
if (!response.ok) throw new Error("Error fetching chunking profiles");
return response.json();
},
// Generar preview de chunks (hasta 3 chunks)
generateChunkPreview: async (config: {
file_name: string
tema: string
max_tokens?: number
target_tokens?: number
chunk_size?: number
chunk_overlap?: number
use_llm?: boolean
custom_instructions?: string
file_name: string;
tema: string;
max_tokens?: number;
target_tokens?: number;
chunk_size?: number;
chunk_overlap?: number;
use_llm?: boolean;
custom_instructions?: string;
}): Promise<{
success: boolean
file_name: string
tema: string
success: boolean;
file_name: string;
tema: string;
chunks: Array<{
index: number
text: string
page: number
file_name: string
tokens: number
}>
message: string
index: number;
text: string;
page: number;
file_name: string;
tokens: number;
}>;
message: string;
}> => {
const response = await fetch(`${API_BASE_URL}/chunking/preview`, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify(config),
})
});
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || 'Error generating preview')
const error = await response.json();
throw new Error(error.detail || "Error generating preview");
}
return response.json()
return response.json();
},
// Procesar PDF completo
processChunkingFull: async (config: {
file_name: string
tema: string
collection_name: string
max_tokens?: number
target_tokens?: number
chunk_size?: number
chunk_overlap?: number
use_llm?: boolean
custom_instructions?: string
file_name: string;
tema: string;
collection_name: string;
max_tokens?: number;
target_tokens?: number;
chunk_size?: number;
chunk_overlap?: number;
use_llm?: boolean;
custom_instructions?: string;
}): Promise<{
success: boolean
collection_name: string
file_name: string
total_chunks: number
chunks_added: number
message: string
success: boolean;
collection_name: string;
file_name: string;
total_chunks: number;
chunks_added: number;
message: string;
}> => {
const response = await fetch(`${API_BASE_URL}/chunking/process`, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify(config),
})
});
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || 'Error processing PDF')
const error = await response.json();
throw new Error(error.detail || "Error processing PDF");
}
return response.json()
return response.json();
},
// ============================================================================
@@ -427,62 +550,62 @@ export const api = {
// Crear schema
createSchema: async (schema: any): Promise<any> => {
const response = await fetch(`${API_BASE_URL}/schemas/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(schema)
})
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(schema),
});
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail?.message || 'Error creando schema')
const error = await response.json();
throw new Error(error.detail?.message || "Error creando schema");
}
return response.json()
return response.json();
},
// Listar schemas
listSchemas: async (tema?: string): Promise<any[]> => {
const url = tema
? `${API_BASE_URL}/schemas/?tema=${encodeURIComponent(tema)}`
: `${API_BASE_URL}/schemas/`
const response = await fetch(url)
if (!response.ok) throw new Error('Error listando schemas')
return response.json()
: `${API_BASE_URL}/schemas/`;
const response = await fetch(url);
if (!response.ok) throw new Error("Error listando schemas");
return response.json();
},
// Obtener schema por ID
getSchema: async (schema_id: string): Promise<any> => {
const response = await fetch(`${API_BASE_URL}/schemas/${schema_id}`)
if (!response.ok) throw new Error('Error obteniendo schema')
return response.json()
const response = await fetch(`${API_BASE_URL}/schemas/${schema_id}`);
if (!response.ok) throw new Error("Error obteniendo schema");
return response.json();
},
// Actualizar schema
updateSchema: async (schema_id: string, schema: any): Promise<any> => {
const response = await fetch(`${API_BASE_URL}/schemas/${schema_id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(schema)
})
if (!response.ok) throw new Error('Error actualizando schema')
return response.json()
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(schema),
});
if (!response.ok) throw new Error("Error actualizando schema");
return response.json();
},
// Eliminar schema
deleteSchema: async (schema_id: string): Promise<void> => {
const response = await fetch(`${API_BASE_URL}/schemas/${schema_id}`, {
method: 'DELETE'
})
if (!response.ok) throw new Error('Error eliminando schema')
method: "DELETE",
});
if (!response.ok) throw new Error("Error eliminando schema");
},
// Validar schema
validateSchema: async (schema: any): Promise<any> => {
const response = await fetch(`${API_BASE_URL}/schemas/validate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(schema)
})
if (!response.ok) throw new Error('Error validando schema')
return response.json()
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(schema),
});
if (!response.ok) throw new Error("Error validando schema");
return response.json();
},
// ============================================================================
@@ -491,25 +614,24 @@ export const api = {
// Procesar con LandingAI
processWithLandingAI: async (config: {
file_name: string
tema: string
collection_name: string
mode: 'quick' | 'extract'
schema_id?: string
include_chunk_types?: string[]
max_tokens_per_chunk?: number
merge_small_chunks?: boolean
file_name: string;
tema: string;
collection_name: string;
mode: "quick" | "extract";
schema_id?: string;
include_chunk_types?: string[];
max_tokens_per_chunk?: number;
merge_small_chunks?: boolean;
}): Promise<any> => {
const response = await fetch(`${API_BASE_URL}/chunking-landingai/process`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
})
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(config),
});
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || 'Error procesando con LandingAI')
const error = await response.json();
throw new Error(error.detail || "Error procesando con LandingAI");
}
return response.json()
return response.json();
},
}
};

View File

@@ -1,12 +1,21 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
"@": path.resolve(__dirname, "./src"),
},
},
})
server: {
proxy: {
"/api": {
target: "http://backend:8000",
changeOrigin: true,
secure: false,
},
},
},
});