theo-agent-dashboard

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

api.ts (2345B)


      1 export class ApiError extends Error {
      2   constructor(
      3     public status: number,
      4     message: string,
      5   ) {
      6     super(message);
      7     this.name = "ApiError";
      8   }
      9 }
     10 
     11 const BASE_PATH = "/api";
     12 
     13 async function request<T>(
     14   method: string,
     15   path: string,
     16   body?: unknown,
     17 ): Promise<T> {
     18   const init: RequestInit = {
     19     method,
     20     credentials: "include",
     21     headers: {
     22       "Content-Type": "application/json",
     23     },
     24   };
     25 
     26   if (body !== undefined) {
     27     init.body = JSON.stringify(body);
     28   }
     29 
     30   const response = await fetch(`${BASE_PATH}${path}`, init);
     31 
     32   if (response.status === 401) {
     33     window.location.assign("/login");
     34     throw new ApiError(401, "Unauthorized");
     35   }
     36 
     37   if (!response.ok) {
     38     const data = await response.json().catch(() => ({ error: "Unknown error" }));
     39     throw new ApiError(response.status, data.error ?? "Request failed");
     40   }
     41 
     42   return response.json();
     43 }
     44 
     45 export const api = {
     46   get: <T>(path: string) => request<T>("GET", path),
     47   post: <T>(path: string, body?: unknown) => request<T>("POST", path, body),
     48   patch: <T>(path: string, body?: unknown) => request<T>("PATCH", path, body),
     49   delete: <T>(path: string) => request<T>("DELETE", path),
     50 };
     51 
     52 export interface AttachmentResponse {
     53   id: string;
     54   filename: string;
     55   content_type: string;
     56   size_bytes: number;
     57   created_at: number;
     58   data_url?: string;
     59 }
     60 
     61 export async function uploadAttachment(
     62   file: File,
     63   sessionId: string,
     64   onProgress?: (percent: number) => void,
     65 ): Promise<AttachmentResponse> {
     66   const formData = new FormData();
     67   formData.append("file", file);
     68   formData.append("session_id", sessionId);
     69 
     70   return new Promise<AttachmentResponse>((resolve, reject) => {
     71     const xhr = new XMLHttpRequest();
     72     xhr.open("POST", `${BASE_PATH}/attachments`);
     73 
     74     xhr.upload.addEventListener("progress", (e) => {
     75       if (e.lengthComputable && onProgress) {
     76         onProgress(Math.round((e.loaded / e.total) * 100));
     77       }
     78     });
     79 
     80     xhr.addEventListener("load", () => {
     81       if (xhr.status >= 200 && xhr.status < 300) {
     82         resolve(JSON.parse(xhr.responseText));
     83       } else {
     84         reject(new ApiError(xhr.status, "Upload failed"));
     85       }
     86     });
     87 
     88     xhr.addEventListener("error", () => {
     89       reject(new ApiError(0, "Network error during upload"));
     90     });
     91 
     92     xhr.withCredentials = true;
     93     xhr.send(formData);
     94   });
     95 }