CoolFace
Apppublic

xXArjunXx/MetaHackathon

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
api.ts430 linesDownload Raw Back to lib
1/**2 * Email Triage API Client with Mock Fallback3 */4 5import { mockAPI } from './mock/mockAPI';6import { MOCK_EMAILS, MOCK_TASKS } from './fixtures';7 8const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";9 10// Check if backend is reachable11let isBackendAvailable = true;12 13const checkBackendAvailability = async () => {14  try {15    const response = await fetch(`${API_BASE_URL}/health`, {16      method: "GET",17      mode: "no-cors",18    });19    isBackendAvailable = true;20    return true;21  } catch {22    isBackendAvailable = false;23    return false;24  }25};26 27export interface Email {28  id: string;29  sender: string;30  subject: string;31  preview: string;32  priority: "high" | "medium" | "low";33  has_attachment: boolean;34  is_read: boolean;35  action_taken?: string;36}37 38export interface Observation {39  inbox_count: number;40  unread_count: number;41  current_email: Email | null;42  email_list: Email[];43  action_taken?: string;44  message: string;45}46 47export interface Reward {48  score: number;49  reason: string;50  cumulative_score: number;51}52 53export interface State {54  task: string;55  task_description: string;56  step_count: number;57  cumulative_score: number;58  actions_taken: Record<string, string>;59  progress: string;60  correct_action_count?: number;61  normalized_score?: number;62  max_steps?: number;63  done?: boolean;64}65 66export interface StepResponse {67  success: boolean;68  session_id?: string;69  observation: Observation;70  reward: Reward;71  done: boolean;72  state: State;73}74 75export interface ResetResponse {76  success: boolean;77  session_id?: string;78  observation: Observation;79  state: State;80}81 82export interface GradeResponse {83  status: "complete" | "incomplete";84  message: string;85  score: number;86  total_actions?: number;87  correct_actions?: number;88}89 90export interface Task {91  id: string;92  name: string;93  description: string;94  email_count: number;95}96 97export interface TasksResponse {98  tasks: Task[];99}100 101type StateLike = Partial<State> & {102  cumulative_reward?: number;103  normalized_score?: number;104  max_steps?: number;105  done?: boolean;106};107 108const normalizeState = (rawState: StateLike | null | undefined): State => {109  const actionsTaken = rawState?.actions_taken ?? {};110  const stepCount = rawState?.step_count ?? 0;111 112  // Backend exposes cumulative_reward/normalized_score while frontend expects cumulative_score.113  const cumulativeScore =114    typeof rawState?.cumulative_score === "number"115      ? rawState.cumulative_score116      : typeof rawState?.cumulative_reward === "number"117        ? rawState.cumulative_reward118        : 0;119 120  // Preserve normalized_score from backend121  const normalizedScore = typeof rawState?.normalized_score === "number"122    ? rawState.normalized_score123    : undefined;124 125  return {126    task: rawState?.task ?? "easy",127    task_description: rawState?.task_description ?? "",128    step_count: stepCount,129    cumulative_score: cumulativeScore,130    actions_taken: actionsTaken,131    progress: rawState?.progress ?? `${Object.keys(actionsTaken).length}/${Math.max(stepCount, Object.keys(actionsTaken).length)}`,132    normalized_score: normalizedScore,133    correct_action_count: rawState?.correct_action_count,134    max_steps: rawState?.max_steps,135    done: rawState?.done,136  };137};138 139class EmailTriageAPI {140  private baseUrl: string;141  private useMockAPI: boolean = false;142  private sessionId: string | null = null;143  private readonly sessionStorageKey = "email-triage-session-id";144 145  constructor(baseUrl = API_BASE_URL) {146    this.baseUrl = baseUrl;147    this.useMockAPI = false;148 149    if (typeof window !== "undefined") {150      this.sessionId = window.localStorage.getItem(this.sessionStorageKey);151    }152  }153 154  isUsingMockAPI(): boolean {155    return this.useMockAPI;156  }157 158  getSessionId(): string | null {159    return this.sessionId;160  }161 162  private buildSessionId(): string {163    if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {164      return crypto.randomUUID();165    }166    return `session-${Date.now()}`;167  }168 169  private ensureSessionId(): string {170    if (!this.sessionId) {171      this.sessionId = this.buildSessionId();172      if (typeof window !== "undefined") {173        window.localStorage.setItem(this.sessionStorageKey, this.sessionId);174      }175    }176    return this.sessionId;177  }178 179  private updateSessionIdFromResponse(data: { session_id?: string } | null | undefined): void {180    if (!data?.session_id) {181      return;182    }183    this.sessionId = data.session_id;184    if (typeof window !== "undefined") {185      window.localStorage.setItem(this.sessionStorageKey, this.sessionId);186    }187  }188 189  private withSessionQuery(path: string): string {190    const sid = this.ensureSessionId();191    const separator = path.includes("?") ? "&" : "?";192    return `${path}${separator}session_id=${encodeURIComponent(sid)}`;193  }194 195  async clearSession(): Promise<void> {196    this.sessionId = null;197    if (typeof window !== "undefined") {198      window.localStorage.removeItem(this.sessionStorageKey);199    }200  }201 202  async restoreSession(): Promise<ResetResponse | null> {203    if (this.useMockAPI) {204      return null;205    }206 207    const sid = this.sessionId;208    if (!sid) {209      return null;210    }211 212    try {213      const [stateResponse, obsResponse] = await Promise.all([214        fetch(`${this.baseUrl}/state?session_id=${encodeURIComponent(sid)}`),215        fetch(`${this.baseUrl}/observation?session_id=${encodeURIComponent(sid)}`),216      ]);217 218      if (!stateResponse.ok || !obsResponse.ok) {219        return null;220      }221 222      const [stateData, obsData] = await Promise.all([223        stateResponse.json(),224        obsResponse.json(),225      ]);226 227      return {228        success: true,229        session_id: sid,230        observation: obsData,231        state: normalizeState(stateData),232      };233    } catch {234      return null;235    }236  }237 238  async reset(task: string = "easy"): Promise<ResetResponse> {239    try {240      const sid = this.ensureSessionId();241      const response = await fetch(`${this.baseUrl}/reset`, {242        method: "POST",243        headers: { "Content-Type": "application/json" },244        body: JSON.stringify({ task, session_id: sid }),245      });246 247      if (!response.ok) {248        throw new Error(`Reset failed: ${response.statusText}`);249      }250 251      const data = await response.json();252      this.updateSessionIdFromResponse(data);253      return {254        ...data,255        state: normalizeState(data?.state),256      };257    } catch (error) {258      console.log("[v0] Backend unavailable, using mock API");259      this.useMockAPI = true;260      return mockAPI.reset(task);261    }262  }263 264  async step(265    action: string,266    emailId: string,267    details: Record<string, unknown> = {}268  ): Promise<StepResponse> {269    if (this.useMockAPI) {270      return mockAPI.step(action, emailId);271    }272 273    try {274      const response = await fetch(`${this.baseUrl}/step`, {275        method: "POST",276        headers: { "Content-Type": "application/json" },277        body: JSON.stringify({278          action,279          email_id: emailId,280          details,281          session_id: this.ensureSessionId(),282        }),283      });284 285      if (!response.ok) {286        throw new Error(`Step failed: ${response.statusText}`);287      }288 289      const data = await response.json();290      this.updateSessionIdFromResponse(data);291      return {292        ...data,293        state: normalizeState(data?.state),294      };295    } catch (error) {296      console.log("[v0] Backend unavailable, using mock API");297      this.useMockAPI = true;298      return mockAPI.step(action, emailId);299    }300  }301 302  async getState(): Promise<State> {303    if (this.useMockAPI) {304      return {305        task: "easy",306        task_description: "Sort emails",307        step_count: 0,308        cumulative_score: 0,309        actions_taken: {},310        progress: "0%",311      };312    }313 314    try {315      const response = await fetch(`${this.baseUrl}${this.withSessionQuery("/state")}`);316 317      if (!response.ok) {318        throw new Error(`Get state failed: ${response.statusText}`);319      }320 321      const data = await response.json();322      return normalizeState(data);323    } catch (error) {324      this.useMockAPI = true;325      return {326        task: "easy",327        task_description: "Sort emails",328        step_count: 0,329        cumulative_score: 0,330        actions_taken: {},331        progress: "0%",332      };333    }334  }335 336  async getObservation(): Promise<Observation> {337    if (this.useMockAPI) {338      const emails = MOCK_EMAILS.easy;339      return {340        inbox_count: emails.length,341        unread_count: emails.length,342        current_email: emails[0],343        email_list: emails,344        message: "Demo mode",345      };346    }347 348    try {349      const response = await fetch(`${this.baseUrl}${this.withSessionQuery("/observation")}`);350 351      if (!response.ok) {352        throw new Error(`Get observation failed: ${response.statusText}`);353      }354 355      return response.json();356    } catch (error) {357      this.useMockAPI = true;358      const emails = MOCK_EMAILS.easy;359      return {360        inbox_count: emails.length,361        unread_count: emails.length,362        current_email: emails[0],363        email_list: emails,364        message: "Demo mode",365      };366    }367  }368 369  async grade(): Promise<GradeResponse> {370    if (this.useMockAPI) {371      return mockAPI.grade();372    }373 374    try {375      const response = await fetch(`${this.baseUrl}${this.withSessionQuery("/grade")}`);376 377      if (!response.ok) {378        throw new Error(`Grade failed: ${response.statusText}`);379      }380 381      return response.json();382    } catch (error) {383      this.useMockAPI = true;384      return mockAPI.grade();385    }386  }387 388  async getTasks(): Promise<TasksResponse> {389    try {390      const controller = new AbortController();391      const timeoutId = setTimeout(() => controller.abort(), 3000);392 393      const response = await fetch(`${this.baseUrl}/tasks`, {394        signal: controller.signal,395      });396 397      clearTimeout(timeoutId);398 399      if (!response.ok) {400        throw new Error(`Get tasks failed: ${response.statusText}`);401      }402 403      return response.json();404    } catch (error) {405      console.log("[v0] Backend unavailable, using mock tasks", error);406      this.useMockAPI = true;407      return mockAPI.getTasks();408    }409  }410 411  // Real-time observation polling for Hugging Face model updates412  subscribeToUpdates(interval: number = 2000, onUpdate?: (obs: Observation) => void): () => void {413    const pollInterval = setInterval(async () => {414      try {415        const obs = await this.getObservation();416        if (onUpdate) {417          onUpdate(obs);418        }419      } catch (err) {420        console.error("[API] Polling error:", err);421      }422    }, interval);423 424    // Return cleanup function425    return () => clearInterval(pollInterval);426  }427}428 429export const emailTriageAPI = new EmailTriageAPI();430