import api from '../api'; import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from '../utils/csrfUtils'; import { ApiRequestOptions } from '../hooks/useApi'; // ============================================================================ // Types // ============================================================================ export interface CoachingContext { id: string; userId: string; mandateId: string; instanceId: string; title: string; description?: string; category: string; status: string; goals?: string; insights?: string; sessionCount: number; taskCount: number; lastSessionAt?: string; createdAt?: string; updatedAt?: string; } export interface CoachingSession { id: string; contextId: string; userId: string; status: string; personaId?: string; summary?: string; durationSeconds: number; messageCount: number; competenceScore?: number; emailSent: boolean; startedAt?: string; endedAt?: string; } export interface CoachingPersona { id: string; userId: string; key: string; label: string; description: string; gender?: string; category: string; isActive: boolean; } export interface CoachingBadge { id: string; userId: string; badgeKey: string; label?: string; description?: string; icon?: string; awardedAt?: string; } export interface CoachingMessage { id: string; sessionId: string; contextId: string; role: 'user' | 'assistant' | 'system'; content: string; contentType: string; audioRef?: string; createdAt?: string; } export interface CoachingTask { id: string; contextId: string; sessionId?: string; title: string; description?: string; status: string; priority: string; dueDate?: string; completedAt?: string; createdAt?: string; } export interface CoachingScore { id: string; contextId: string; sessionId: string; dimension: string; score: number; trend: string; evidence?: string; createdAt?: string; } export interface CoachingUserProfile { id: string; userId: string; dailyReminderTime?: string; dailyReminderEnabled: boolean; emailSummaryEnabled: boolean; streakDays: number; longestStreak: number; totalSessions: number; totalMinutes: number; lastSessionAt?: string; } export interface DashboardData { totalContexts: number; activeContexts: number; totalSessions: number; totalMinutes: number; streakDays: number; longestStreak: number; averageScore?: number; recentScores: CoachingScore[]; openTasks: number; completedTasks: number; goalProgress?: number; badges?: CoachingBadge[]; level?: { number: number; label: string; totalSessions: number }; contexts: Array<{ id: string; title: string; category: string; sessionCount: number; lastSessionAt?: string; goalProgress?: number }>; } export interface SSEEvent { type: string; data?: any; timestamp?: string; } export type ApiRequestFunction = (options: ApiRequestOptions) => Promise; // ============================================================================ // Context API // ============================================================================ export async function getContextsApi(request: ApiRequestFunction, instanceId: string): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/contexts`, method: 'get' }); return data.contexts || []; } export async function createContextApi(request: ApiRequestFunction, instanceId: string, body: { title: string; description?: string; category?: string; goals?: string[]; }): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/contexts`, method: 'post', data: body }); return data.context; } export async function getContextDetailApi(request: ApiRequestFunction, instanceId: string, contextId: string): Promise<{ context: CoachingContext; tasks: CoachingTask[]; scores: CoachingScore[]; sessions: CoachingSession[]; }> { const data = await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}`, method: 'get', params: { _t: Date.now() }, }); const ctx = data?.context ?? data; return { context: ctx, tasks: data?.tasks ?? [], scores: data?.scores ?? [], sessions: data?.sessions ?? [], }; } export async function updateContextApi(request: ApiRequestFunction, instanceId: string, contextId: string, body: any): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}`, method: 'put', data: body }); return data.context; } export async function deleteContextApi(request: ApiRequestFunction, instanceId: string, contextId: string): Promise { await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}`, method: 'delete' }); } export async function archiveContextApi(request: ApiRequestFunction, instanceId: string, contextId: string): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}/archive`, method: 'post' }); return data.context; } export async function activateContextApi(request: ApiRequestFunction, instanceId: string, contextId: string): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}/activate`, method: 'post' }); return data.context; } // ============================================================================ // Session API // ============================================================================ export async function startSessionApi(request: ApiRequestFunction, instanceId: string, contextId: string): Promise<{ session: CoachingSession; messages: CoachingMessage[]; resumed: boolean; }> { const data = await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}/sessions/start`, method: 'post' }); return data; } export async function startSessionStreamApi( instanceId: string, contextId: string, onEvent: (event: SSEEvent) => void, onError?: (error: Error) => void, onComplete?: () => void, personaId?: string, ): Promise { try { const baseURL = api.defaults.baseURL || ''; const personaParam = personaId ? `?personaId=${encodeURIComponent(personaId)}` : ''; const url = `${baseURL}/api/commcoach/${instanceId}/contexts/${contextId}/sessions/start${personaParam}`; const headers: Record = { 'Content-Type': 'application/json' }; const authToken = localStorage.getItem('authToken'); if (authToken) headers['Authorization'] = `Bearer ${authToken}`; if (!getCSRFToken()) generateAndStoreCSRFToken(); addCSRFTokenToHeaders(headers); const response = await fetch(url, { method: 'POST', headers, credentials: 'include', }); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText}`); } if (!response.body) throw new Error('Response body is null'); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { try { const jsonStr = line.slice(6); if (jsonStr.trim()) { const event: SSEEvent = JSON.parse(jsonStr); onEvent(event); } } catch { // skip malformed lines } } } } onComplete?.(); } finally { reader.releaseLock(); } } catch (error: any) { if (onError) onError(error instanceof Error ? error : new Error(String(error))); else throw error; } } export async function getSessionApi(request: ApiRequestFunction, instanceId: string, sessionId: string): Promise<{ session: CoachingSession; messages: CoachingMessage[]; }> { const data = await request({ url: `/api/commcoach/${instanceId}/sessions/${sessionId}`, method: 'get' }); return data; } export async function completeSessionApi(request: ApiRequestFunction, instanceId: string, sessionId: string): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/sessions/${sessionId}/complete`, method: 'post' }); return data.session; } export async function cancelSessionApi(request: ApiRequestFunction, instanceId: string, sessionId: string): Promise { await request({ url: `/api/commcoach/${instanceId}/sessions/${sessionId}/cancel`, method: 'post' }); } // ============================================================================ // Streaming Chat API // ============================================================================ export interface SendMessageOptions { fileIds?: string[]; dataSourceIds?: string[]; featureDataSourceIds?: string[]; allowedProviders?: string[]; } export async function sendMessageStreamApi( instanceId: string, sessionId: string, content: string, onEvent: (event: SSEEvent) => void, onError?: (error: Error) => void, onComplete?: () => void, signal?: AbortSignal, options?: SendMessageOptions, ): Promise { try { const baseURL = api.defaults.baseURL || ''; const url = `${baseURL}/api/commcoach/${instanceId}/sessions/${sessionId}/message/stream`; const headers: Record = { 'Content-Type': 'application/json' }; const authToken = localStorage.getItem('authToken'); if (authToken) headers['Authorization'] = `Bearer ${authToken}`; if (!getCSRFToken()) generateAndStoreCSRFToken(); addCSRFTokenToHeaders(headers); const body: Record = { content }; if (options?.fileIds?.length) body.fileIds = options.fileIds; if (options?.dataSourceIds?.length) body.dataSourceIds = options.dataSourceIds; if (options?.featureDataSourceIds?.length) body.featureDataSourceIds = options.featureDataSourceIds; if (options?.allowedProviders?.length) body.allowedProviders = options.allowedProviders; const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body), credentials: 'include', signal, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText}`); } if (!response.body) throw new Error('Response body is null'); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { try { const jsonStr = line.slice(6); if (jsonStr.trim()) { const event: SSEEvent = JSON.parse(jsonStr); onEvent(event); } } catch { // skip malformed lines } } } } onComplete?.(); } finally { reader.releaseLock(); } } catch (error: any) { if (onError) onError(error instanceof Error ? error : new Error(String(error))); else throw error; } } export async function sendAudioStreamApi( instanceId: string, sessionId: string, audioBlob: Blob, onEvent: (event: SSEEvent) => void, onError?: (error: Error) => void, onComplete?: () => void, ): Promise { try { const baseURL = api.defaults.baseURL || ''; const url = `${baseURL}/api/commcoach/${instanceId}/sessions/${sessionId}/audio/stream`; const headers: Record = { 'Content-Type': 'application/octet-stream' }; const authToken = localStorage.getItem('authToken'); if (authToken) headers['Authorization'] = `Bearer ${authToken}`; const pathMatch = window.location.pathname.match(/^\/mandates\/([^/]+)\/([^/]+)\/([^/]+)/); if (pathMatch) { headers['X-Mandate-Id'] = pathMatch[1]; headers['X-Instance-Id'] = pathMatch[3]; } if (!getCSRFToken()) generateAndStoreCSRFToken(); addCSRFTokenToHeaders(headers); const audioBuffer = await audioBlob.arrayBuffer(); const response = await fetch(url, { method: 'POST', headers, body: audioBuffer, credentials: 'include', }); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText}`); } if (!response.body) throw new Error('Response body is null'); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { try { const jsonStr = line.slice(6); if (jsonStr.trim()) onEvent(JSON.parse(jsonStr)); } catch { /* skip */ } } } } onComplete?.(); } finally { reader.releaseLock(); } } catch (error: any) { if (onError) onError(error instanceof Error ? error : new Error(String(error))); else throw error; } } // ============================================================================ // Task API // ============================================================================ export async function getTasksApi(request: ApiRequestFunction, instanceId: string, contextId: string): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}/tasks`, method: 'get' }); return data.tasks || []; } export async function createTaskApi(request: ApiRequestFunction, instanceId: string, contextId: string, body: { title: string; description?: string; priority?: string; dueDate?: string; }): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}/tasks`, method: 'post', data: body }); return data.task; } export async function updateTaskApi(request: ApiRequestFunction, instanceId: string, taskId: string, body: any): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/tasks/${taskId}`, method: 'put', data: body }); return data.task; } export async function updateTaskStatusApi(request: ApiRequestFunction, instanceId: string, taskId: string, status: string): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/tasks/${taskId}/status`, method: 'put', data: { status } }); return data.task; } export async function deleteTaskApi(request: ApiRequestFunction, instanceId: string, taskId: string): Promise { await request({ url: `/api/commcoach/${instanceId}/tasks/${taskId}`, method: 'delete' }); } // ============================================================================ // Dashboard API // ============================================================================ export async function getDashboardApi(request: ApiRequestFunction, instanceId: string): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/dashboard`, method: 'get' }); return data.dashboard; } // ============================================================================ // Profile API // ============================================================================ export async function getProfileApi(request: ApiRequestFunction, instanceId: string): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/profile`, method: 'get' }); return data.profile; } export async function updateProfileApi(request: ApiRequestFunction, instanceId: string, body: any): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/profile`, method: 'put', data: body }); return data.profile; } // ============================================================================ // Persona API (Iteration 2) // ============================================================================ export async function getPersonasApi(request: ApiRequestFunction, instanceId: string): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/personas`, method: 'get' }); return data.personas || []; } export async function createPersonaApi(request: ApiRequestFunction, instanceId: string, body: { label: string; description: string; gender?: string; systemPromptOverride?: string; }): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/personas`, method: 'post', data: body }); return data.persona; } export async function deletePersonaApi(request: ApiRequestFunction, instanceId: string, personaId: string): Promise { await request({ url: `/api/commcoach/${instanceId}/personas/${personaId}`, method: 'delete' }); } // ============================================================================ // Badge API (Iteration 2) // ============================================================================ export async function getBadgesApi(request: ApiRequestFunction, instanceId: string): Promise { const data = await request({ url: `/api/commcoach/${instanceId}/badges`, method: 'get' }); return data.badges || []; } // ============================================================================ // Export API (Iteration 2) // ============================================================================ export function getDossierExportUrl(instanceId: string, contextId: string, format: string = 'md'): string { const baseURL = api.defaults.baseURL || ''; return `${baseURL}/api/commcoach/${instanceId}/contexts/${contextId}/export?format=${format}`; } export function getSessionExportUrl(instanceId: string, sessionId: string, format: string = 'md'): string { const baseURL = api.defaults.baseURL || ''; return `${baseURL}/api/commcoach/${instanceId}/sessions/${sessionId}/export?format=${format}`; } // ============================================================================ // Score History API (Iteration 2) // ============================================================================ export async function getScoreHistoryApi(request: ApiRequestFunction, instanceId: string, contextId: string): Promise>> { const data = await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}/scores/history`, method: 'get' }); return data.history || {}; }