/** * api.ts - Centralized API client for the Kastack FastAPI backend. * * All fetch calls to the backend go through here so the base URL * and error handling are in one place. */ export const API_BASE_URL = (typeof window !== "undefined" && (window as Record).__KASTACK_API_URL__) as string || (import.meta.env?.PROD ? "" : "http://localhost:8000"); // Backend response shapes /** POST /chat response */ export interface ChatApiResponse { query: string; answer: string; sources: { topics_used: { id: number; range: string; summary: string; score: number; }[]; checkpoints_used?: { id: number; range: string; summary: string; score: number; }[]; messages_used: { msg_id: number; text: string; sender: string; day: number; score: number; }[]; jobs_found?: { job: string; text: string; day: number; sender: string; msg_id: number; }[]; }; is_persona_query: boolean; is_job_query?: boolean; no_results?: boolean; } /** GET /persona response - entire persona.json */ export interface PersonaApiResponse { meta: { total_messages: number; total_messages_user_1: number; total_messages_user_2: number; total_conversations_analyzed: number; data_description: string; [key: string]: unknown; }; persona_user_1: PersonaApiUser; persona_user_2: PersonaApiUser; } export interface PersonaApiUser { total_messages_analyzed: number; communication_style: { avg_message_length: number; emoji_usage_rate: number; question_rate: number; exclamation_rate: number; caps_rate?: number; [key: string]: unknown; }; personality_traits: Record; personal_facts: { job_mentions: Record | string[]; location_mentions: Record | string[]; age_mentions?: (number | string)[]; pet_mentions?: Record; relationship_mentions?: Record; [key: string]: unknown; }; habits?: Record; top_emojis?: Record; } /** GET /topics response */ export interface TopicsApiResponse { total: number; topics: TopicApiItem[]; } export interface TopicApiItem { topic_id: number; start_msg_id: number; end_msg_id: number; start_day: number; end_day: number; num_messages: number; summary?: string; dominant_sender?: string; [key: string]: unknown; } /** GET /health response */ export interface HealthApiResponse { status: string; ready: boolean; total_topics: number; total_messages: number; checkpoints_loaded: number; } // Fetch helpers async function apiFetch(path: string, init?: RequestInit): Promise { const url = `${API_BASE_URL}${path}`; const res = await fetch(url, init); if (!res.ok) { const body = await res.text().catch(() => ""); throw new Error(`API ${res.status}: ${body || res.statusText}`); } return res.json() as Promise; } export function fetchHealth(): Promise { return apiFetch("/health"); } export function fetchPersona(): Promise { return apiFetch("/persona"); } export function fetchTopics(): Promise { return apiFetch("/topics"); } export function fetchChat(message: string, target_user?: string, target_topic?: string): Promise { return apiFetch("/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message, target_user, target_topic }), }); } // Round 2 Endpoints export interface IntentApiResponse { label: string; confidence: number; } export function fetchIntent(text: string): Promise { return apiFetch("/intent", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text }), }); } export interface AffectApiResponse { valence: number; curiosity: number; frustration: number; playfulness: number; formality: number; intensity: number; } export function fetchAffect(text: string): Promise { return apiFetch("/affect", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text }), }); } export interface DriftApiResponse { segments: { day_range: string; dominant_tone: string; dominant_mood: string; trigger?: string; }[]; } export function fetchDrift(view: "real" | "demo"): Promise { return apiFetch(`/drift?view=${view}`); } export function driftChartUrl(view: "real" | "demo"): string { return `${API_BASE_URL}/drift/chart?view=${view}`; } export interface ConflictApiResponse { subject: string; before: { day: number; text: string }[]; after: { day: number; text: string }[]; resolution: { emotional_contradiction: boolean; factual_contradictions: any[]; merged_answer: string; }; } export function fetchConflict(query: string, subject?: string): Promise { return apiFetch("/conflict", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, subject }), }); }