Spaces:
Running
Running
| import { | |
| Trace, | |
| KnowledgeGraph, | |
| Entity, | |
| Relation, | |
| GraphComparisonOptions, | |
| GraphComparisonResults, | |
| GraphListResponse, | |
| GraphDetailsResponse, | |
| PerturbationConfig, | |
| } from "@/types"; | |
| import { UpdateContextRequest, ContextDocumentResponse } from "@/types/context"; | |
| const API_BASE = "/api"; | |
| class ApiError extends Error { | |
| constructor(public status: number, message: string) { | |
| super(message); | |
| this.name = "ApiError"; | |
| } | |
| } | |
| async function fetchApi<T>( | |
| endpoint: string, | |
| options?: RequestInit, | |
| retryCount = 0 | |
| ): Promise<T> { | |
| const response = await fetch(`${API_BASE}${endpoint}`, { | |
| headers: { | |
| "Content-Type": "application/json", | |
| ...options?.headers, | |
| }, | |
| credentials: "include", // Ensure cookies are sent with requests | |
| ...options, | |
| }); | |
| if (!response.ok) { | |
| // Handle 401 (Unauthorized) - session expired, redirect to login | |
| if (response.status === 401) { | |
| console.warn("🔐 Session expired - redirecting to login..."); | |
| // Redirect to login page | |
| window.location.href = "/auth/login-page"; | |
| throw new ApiError(401, "Session expired. Please log in again."); | |
| } | |
| // Handle 429 (Too Many Requests) with exponential backoff | |
| if (response.status === 429 && retryCount < 3) { | |
| const backoffDelay = Math.pow(2, retryCount) * 1000; // 1s, 2s, 4s | |
| console.warn( | |
| `🚨 Rate limit hit (429), retrying in ${backoffDelay}ms... (attempt ${ | |
| retryCount + 1 | |
| }/3)` | |
| ); | |
| await new Promise((resolve) => setTimeout(resolve, backoffDelay)); | |
| return fetchApi(endpoint, options, retryCount + 1); | |
| } | |
| throw new ApiError(response.status, `API Error: ${response.statusText}`); | |
| } | |
| return response.json(); | |
| } | |
| export const api = { | |
| // Traces | |
| traces: { | |
| list: async () => { | |
| const response = await fetchApi<{ status: string; traces: Trace[] }>( | |
| "/traces/" | |
| ); | |
| return response.traces; | |
| }, | |
| get: (id: string) => fetchApi<Trace>(`/traces/${id}`), | |
| getEnhancedStatistics: async (id: string) => { | |
| const response = await fetchApi<{ | |
| status: string; | |
| enhanced_statistics: any; | |
| has_schema_analytics: boolean; | |
| }>(`/traces/${id}/enhanced-statistics`); | |
| return response; | |
| }, | |
| getContent: async (id: string) => { | |
| const response = await fetchApi<{ content: string }>( | |
| `/traces/${id}/content` | |
| ); | |
| return response.content; | |
| }, | |
| getNumberedContent: async (id: string) => { | |
| const response = await fetchApi<{ content: string }>( | |
| `/traces/${id}/content-numbered` | |
| ); | |
| return response.content; | |
| }, | |
| extractSegment: async ( | |
| traceId: string, | |
| startChar: number, | |
| endChar: number | |
| ) => { | |
| console.log("Extracting segment:", { traceId, startChar, endChar }); | |
| const response = await fetchApi<{ content: string }>( | |
| `/traces/${traceId}/content` | |
| ); | |
| const fullContent = response.content || ""; | |
| console.log("Full content length:", fullContent.length); | |
| console.log("Full content preview:", fullContent.substring(0, 200)); | |
| // Extract segment using character positions | |
| const segmentContent = fullContent.substring(startChar, endChar); | |
| console.log("Segment content length:", segmentContent.length); | |
| console.log("Segment content preview:", segmentContent.substring(0, 200)); | |
| return { | |
| content: segmentContent, | |
| fullContent, | |
| startChar, | |
| endChar, | |
| segmentLength: segmentContent.length, | |
| }; | |
| }, | |
| delete: (id: string) => | |
| fetchApi<{ message: string }>(`/traces/${id}`, { | |
| method: "DELETE", | |
| }), | |
| upload: (file: File, onProgress?: (progress: number) => void) => { | |
| return new Promise<Trace>((resolve, reject) => { | |
| const formData = new FormData(); | |
| formData.append("trace_file", file); | |
| const xhr = new XMLHttpRequest(); | |
| if (onProgress) { | |
| xhr.upload.addEventListener("progress", (e) => { | |
| if (e.lengthComputable) { | |
| const progress = (e.loaded / e.total) * 100; | |
| onProgress(progress); | |
| } | |
| }); | |
| } | |
| xhr.addEventListener("load", () => { | |
| if (xhr.status >= 200 && xhr.status < 300) { | |
| const response = JSON.parse(xhr.responseText); | |
| // Transform the backend response to match the Trace interface | |
| if (response.status === "success" && response.trace_id) { | |
| const trace: Trace = { | |
| id: 0, // Will be updated when we fetch the full trace | |
| trace_id: response.trace_id, | |
| filename: file.name, | |
| title: response.title || file.name, | |
| character_count: response.character_count, | |
| turn_count: response.turn_count, | |
| status: "uploaded", // Default status for newly uploaded traces | |
| upload_timestamp: new Date().toISOString(), | |
| trace_type: "user_upload", | |
| trace_source: "react_app", | |
| tags: ["uploaded"], | |
| knowledge_graphs: [], | |
| }; | |
| resolve(trace); | |
| } else { | |
| reject( | |
| new ApiError(xhr.status, response.message || "Upload failed") | |
| ); | |
| } | |
| } else { | |
| reject(new ApiError(xhr.status, xhr.statusText)); | |
| } | |
| }); | |
| xhr.addEventListener("error", () => { | |
| reject(new ApiError(0, "Network error")); | |
| }); | |
| xhr.open("POST", `${API_BASE}/traces/`); | |
| xhr.send(formData); | |
| }); | |
| }, | |
| process: (id: string, splitterType: string = "agent_semantic") => | |
| fetchApi<{ task_id: string }>(`/traces/${id}/process`, { | |
| method: "POST", | |
| body: JSON.stringify({ splitter_type: splitterType }), | |
| }), | |
| generateKnowledgeGraph: ( | |
| id: string, | |
| splitterType: string = "agent_semantic", | |
| forceRegenerate: boolean = true, | |
| methodName: string = "production", | |
| model: string = "gpt-5-mini", | |
| chunkingConfig?: { min_chunk_size?: number; max_chunk_size?: number } | |
| ) => { | |
| const requestBody = { | |
| splitter_type: splitterType, | |
| force_regenerate: forceRegenerate, | |
| method_name: methodName, | |
| model: model, | |
| chunking_config: chunkingConfig, | |
| }; | |
| console.log("API: generateKnowledgeGraph called with:", { | |
| id, | |
| splitterType, | |
| forceRegenerate, | |
| methodName, | |
| model, | |
| chunkingConfig, | |
| }); | |
| console.log("API: Request body:", requestBody); | |
| return fetchApi<{ task_id: string }>(`/traces/${id}/process`, { | |
| method: "POST", | |
| body: JSON.stringify(requestBody), | |
| }); | |
| }, | |
| // Context Documents | |
| context: { | |
| list: (traceId: string) => fetchApi<any[]>(`/traces/${traceId}/context`), | |
| create: (traceId: string, data: any) => | |
| fetchApi<any>(`/traces/${traceId}/context`, { | |
| method: "POST", | |
| body: JSON.stringify(data), | |
| }), | |
| update: ( | |
| traceId: string, | |
| contextId: string, | |
| data: UpdateContextRequest | |
| ) => | |
| fetchApi<ContextDocumentResponse>( | |
| `/traces/${traceId}/context/${contextId}`, | |
| { | |
| method: "PUT", | |
| body: JSON.stringify(data), | |
| } | |
| ), | |
| delete: (traceId: string, contextId: string) => | |
| fetchApi<any>(`/traces/${traceId}/context/${contextId}`, { | |
| method: "DELETE", | |
| }), | |
| upload: (traceId: string, formData: FormData) => | |
| fetchApi<any>(`/traces/${traceId}/context/upload`, { | |
| method: "POST", | |
| headers: {}, | |
| body: formData, | |
| }), | |
| }, | |
| }, | |
| // Knowledge Graphs | |
| knowledgeGraphs: { | |
| list: () => fetchApi<KnowledgeGraph[]>("/knowledge-graphs"), | |
| get: (id: string) => fetchApi<KnowledgeGraph>(`/knowledge-graphs/${id}`), | |
| getData: (id: string) => | |
| fetchApi<{ | |
| entities: Entity[]; | |
| relations: Relation[]; | |
| metadata?: Record<string, any>; | |
| }>(`/knowledge-graphs/${id}`), | |
| enrich: (id: string) => | |
| fetchApi<{ task_id: string }>(`/knowledge-graphs/${id}/enrich`, { | |
| method: "POST", | |
| }), | |
| perturb: (id: string, config?: PerturbationConfig) => | |
| fetchApi<{ task_id: string; config?: PerturbationConfig }>( | |
| `/knowledge-graphs/${id}/perturb`, | |
| { | |
| method: "POST", | |
| headers: config ? { "Content-Type": "application/json" } : undefined, | |
| body: config ? JSON.stringify(config) : undefined, | |
| } | |
| ), | |
| analyze: (id: string) => | |
| fetchApi<{ task_id: string }>(`/knowledge-graphs/${id}/analyze`, { | |
| method: "POST", | |
| }), | |
| getStatus: (id: string) => | |
| fetchApi<KnowledgeGraph>(`/knowledge-graphs/${id}/status`), | |
| getStageResults: (id: string, stage: string) => | |
| fetchApi<any>(`/knowledge-graphs/${id}/stage-results/${stage}`), | |
| reset: (id: string) => | |
| fetchApi<{ message: string }>(`/reset-knowledge-graph/${id}`, { | |
| method: "POST", | |
| }), | |
| }, | |
| // Tasks | |
| tasks: { | |
| getStatus: (id: string) => fetchApi<any>(`/tasks/${id}/status`), | |
| get: (id: string) => fetchApi<any>(`/tasks/${id}`), | |
| }, | |
| // Graph Comparison | |
| graphComparison: { | |
| listAvailableGraphs: () => | |
| fetchApi<GraphListResponse>("/graph-comparison/graphs"), | |
| compareGraphs: ( | |
| graph1Id: number, | |
| graph2Id: number, | |
| options?: GraphComparisonOptions | |
| ) => | |
| fetchApi<GraphComparisonResults>("/graph-comparison/compare", { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| graph1_id: graph1Id, | |
| graph2_id: graph2Id, | |
| ...options, | |
| }), | |
| }), | |
| getComparison: ( | |
| graph1Id: number, | |
| graph2Id: number, | |
| similarityThreshold?: number | |
| ) => | |
| fetchApi<GraphComparisonResults>( | |
| `/graph-comparison/compare/${graph1Id}/${graph2Id}${ | |
| similarityThreshold | |
| ? `?similarity_threshold=${similarityThreshold}` | |
| : "" | |
| }` | |
| ), | |
| getGraphDetails: (graphId: number) => | |
| fetchApi<GraphDetailsResponse>(`/graph-comparison/graphs/${graphId}`), | |
| getCacheInfo: () => fetchApi<any>("/graph-comparison/cache/info"), | |
| clearCache: () => | |
| fetchApi<{ message: string }>("/graph-comparison/cache/clear", { | |
| method: "DELETE", | |
| }), | |
| }, | |
| // Example Traces | |
| exampleTraces: { | |
| list: (subset?: string) => | |
| fetchApi<import("@/types").ExampleTraceLite[]>( | |
| `/example-traces/${ | |
| subset ? `?subset=${encodeURIComponent(subset)}` : "" | |
| }` | |
| ), | |
| get: (subset: string, id: number) => | |
| fetchApi<import("@/types").ExampleTrace>( | |
| `/example-traces/${encodeURIComponent(subset)}/${id}` | |
| ), | |
| import: (subset: string, id: number) => | |
| fetchApi<import("@/types").Trace>(`/example-traces/import`, { | |
| method: "POST", | |
| body: JSON.stringify({ subset, id }), | |
| }), | |
| }, | |
| // Methods | |
| methods: { | |
| getAvailable: () => | |
| fetchApi<{ methods: Record<string, any> }>("/methods/available"), | |
| }, | |
| // AI Observability | |
| observability: { | |
| connect: (connectionData: { | |
| platform: string; | |
| publicKey: string; | |
| secretKey: string; | |
| host?: string; | |
| }) => | |
| fetchApi<{ status: string; message: string; connection_id: string }>( | |
| "/observability/connect", | |
| { | |
| method: "POST", | |
| body: JSON.stringify(connectionData), | |
| } | |
| ), | |
| updateConnection: ( | |
| connectionId: string, | |
| connectionData: { | |
| platform: string; | |
| publicKey: string; | |
| secretKey: string; | |
| host?: string; | |
| } | |
| ) => | |
| fetchApi<{ status: string; message: string }>( | |
| `/observability/connections/${connectionId}`, | |
| { | |
| method: "PUT", | |
| body: JSON.stringify(connectionData), | |
| } | |
| ), | |
| deleteConnection: (connectionId: string) => | |
| fetchApi<{ status: string; message: string }>( | |
| `/observability/connections/${connectionId}`, | |
| { | |
| method: "DELETE", | |
| } | |
| ), | |
| getConnections: () => | |
| fetchApi<{ | |
| connections: Array<{ | |
| id: string; | |
| platform: string; | |
| status: string; | |
| connected_at: string; | |
| }>; | |
| }>("/observability/connections"), | |
| // Connection-specific methods | |
| getFetchedTracesByConnection: (connectionId: string) => | |
| fetchApi<{ traces: any[]; total: number }>( | |
| `/observability/connections/${connectionId}/fetched-traces` | |
| ), | |
| fetchTracesByConnection: ( | |
| connectionId: string, | |
| limit: number, | |
| projectId?: string, | |
| projectName?: string | |
| ) => | |
| fetchApi<{ traces: any[]; total: number }>( | |
| `/observability/connections/${connectionId}/fetch`, | |
| { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| limit, | |
| project_id: projectId, | |
| project_name: projectName, | |
| }), | |
| } | |
| ), | |
| downloadTrace: (traceId: string) => | |
| fetchApi<{ data: any }>(`/observability/traces/${traceId}/download`), | |
| importTracesByConnection: ( | |
| connectionId: string, | |
| traceIds: string[], | |
| preprocessing?: { | |
| max_char?: number | null; | |
| topk?: number; | |
| raw?: boolean; | |
| hierarchy?: boolean; | |
| replace?: boolean; | |
| } | |
| ) => | |
| fetchApi<{ imported: number; errors: string[] }>( | |
| `/observability/connections/${connectionId}/import`, | |
| { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| trace_ids: traceIds, | |
| preprocessing: preprocessing, | |
| }), | |
| } | |
| ), | |
| }, | |
| }; | |