import api from '../api'; import type { VoiceOption } from './voiceCatalogApi'; // ============================================================================ // TYPES & INTERFACES // ============================================================================ export interface TeamsbotSession { id: string; instanceId: string; mandateId: string; meetingLink: string; botName: string; status: 'pending' | 'joining' | 'active' | 'leaving' | 'ended' | 'error'; startedAt?: string; endedAt?: string; startedByUserId: string; bridgeSessionId?: string; meetingChatId?: string; summary?: string; errorMessage?: string; transcriptSegmentCount: number; botResponseCount: number; creationDate?: string; lastModified?: string; } export interface TeamsbotTranscript { id: string; sessionId: string; speaker?: string; text: string; timestamp: string; confidence: number; language?: string; isFinal: boolean; isContinuation?: boolean; source?: string; } export interface TeamsbotBotResponse { id: string; sessionId: string; responseText: string; responseType: 'audio' | 'chat' | 'both'; detectedIntent: 'addressed' | 'question' | 'proactive' | 'none'; reasoning?: string; triggeredByTranscriptId?: string; modelName?: string; processingTime: number; priceCHF: number; timestamp?: string; } export type TeamsbotResponseChannel = 'voice' | 'chat' | 'both'; export type TeamsbotJoinMode = 'systemBot' | 'anonymous' | 'userAccount'; export type TeamsbotTransferMode = 'caption' | 'audio' | 'auto'; export interface TeamsbotConfig { botName: string; aiSystemPrompt: string; responseMode: 'auto' | 'manual' | 'transcribeOnly'; responseChannel: TeamsbotResponseChannel; transferMode: TeamsbotTransferMode; language: string; voiceId?: string; browserBotUrl?: string; triggerIntervalSeconds: number; triggerCooldownSeconds: number; contextWindowSegments: number; debugMode?: boolean; } export interface TeamsbotSessionStats { transcriptSegments: number; botResponses: number; totalCostCHF: number; totalProcessingTime: number; speakers: string[]; } export interface StartSessionRequest { meetingLink: string; botName?: string; connectionId?: string; joinMode?: TeamsbotJoinMode; sessionContext?: string; } export interface ConfigUpdateRequest { botName?: string; aiSystemPrompt?: string; responseMode?: 'auto' | 'manual' | 'transcribeOnly'; responseChannel?: TeamsbotResponseChannel; transferMode?: TeamsbotTransferMode; language?: string; voiceId?: string; browserBotUrl?: string; triggerIntervalSeconds?: number; triggerCooldownSeconds?: number; contextWindowSegments?: number; debugMode?: boolean; } // Voice option type re-exported from the central voice catalog API // (imported above so it's also in scope for local signatures below). // The legacy teamsbot-specific {code,name} language type is gone — consumers // should use VoiceLanguage from voiceCatalogApi (catalog SSOT). export type { VoiceOption }; // Auth Detection Test Types export interface StepScreenshot { label: string; data: string; } export interface AuthTestResult { variantId: string; variantName: string; success: boolean; pageType: 'v2' | 'lightMeetings' | 'error' | 'unknown'; finalUrl: string; hasSignInLink: boolean; hasNameInput: boolean; hasJoinButton: boolean; authAttempted: boolean; authSuccess: boolean | null; screenshot?: string; screenshots?: StepScreenshot[]; durationMs: number; error?: string; detectedSignals: string[]; logs: string[]; } export interface AuthTestResults { meetingUrl: string; timestamp: string; variants: AuthTestResult[]; recommendation: string; credentialsReceived?: { hasEmail: boolean; hasPassword: boolean }; credentialDebug?: { mandateId?: string; botFound?: boolean; botEmail?: string; botMandateId?: string; searchStrategy?: string; allBotsCount?: number; passwordDecrypted?: boolean; passwordError?: string; }; } // User Account (Mein Account) Types export interface UserAccountStatus { hasSavedCredentials: boolean; email?: string; displayName?: string; } // MFA Types export interface MfaChallengeEvent { mfaType: 'numberMatch' | 'pushApproval' | 'smsCode' | 'totpCode' | 'timeout' | 'unknown'; displayNumber?: string; prompt: string; timestamp?: string; } // SSE Event Types export interface TeamsbotSSEEvent { type: | 'transcript' | 'botResponse' | 'analysis' | 'suggestedResponse' | 'statusChange' | 'error' | 'ping' | 'sessionState' | 'ttsDeliveryStatus' | 'mfaChallenge' | 'mfaResolved' | 'chatSendFailed' | 'directorPrompt' | 'agentRun' | 'botConnectionState'; data: any; timestamp?: string; } // ========================================================================= // Director Prompts (private operator instructions during a live meeting) // ========================================================================= export type DirectorPromptMode = 'oneShot' | 'persistent'; export type DirectorPromptStatus = | 'queued' | 'running' | 'succeeded' | 'failed' | 'consumed'; export const DIRECTOR_PROMPT_TEXT_LIMIT = 8000; export const DIRECTOR_PROMPT_FILE_LIMIT = 10; export interface DirectorPrompt { id: string; sessionId: string; instanceId: string; operatorUserId: string; text: string; mode: DirectorPromptMode; fileIds: string[]; status: DirectorPromptStatus; statusMessage?: string; createdAt: string; consumedAt?: string; agentRunId?: string; responseText?: string; } export interface DirectorPromptCreateRequest { text: string; mode: DirectorPromptMode; fileIds?: string[]; } // ============================================================================ // API FUNCTIONS // ============================================================================ /** * Start a new Teams Bot session. */ export async function startSession(instanceId: string, request: StartSessionRequest): Promise<{ session: TeamsbotSession }> { const response = await api.post(`/api/teamsbot/${instanceId}/sessions`, request); return response.data; } /** * List all sessions for a feature instance. */ export async function listSessions(instanceId: string, includeEnded = true): Promise<{ sessions: TeamsbotSession[] }> { const response = await api.get(`/api/teamsbot/${instanceId}/sessions`, { params: { includeEnded }, }); return response.data; } /** * Get session details with transcripts and bot responses. */ export async function getSession( instanceId: string, sessionId: string, includeTranscripts = true, includeResponses = true, ): Promise<{ session: TeamsbotSession; transcripts?: TeamsbotTranscript[]; botResponses?: TeamsbotBotResponse[]; stats?: TeamsbotSessionStats; }> { const response = await api.get(`/api/teamsbot/${instanceId}/sessions/${sessionId}`, { params: { includeTranscripts, includeResponses }, }); return response.data; } /** * Stop an active session. */ export async function stopSession(instanceId: string, sessionId: string): Promise<{ status: string; sessionId: string }> { const response = await api.post(`/api/teamsbot/${instanceId}/sessions/${sessionId}/stop`); return response.data; } /** * Delete a session and all related data. */ export async function deleteSession(instanceId: string, sessionId: string): Promise<{ deleted: boolean }> { const response = await api.delete(`/api/teamsbot/${instanceId}/sessions/${sessionId}`); return response.data; } /** * Get teamsbot configuration (instance-level defaults). */ export async function getConfig(instanceId: string): Promise<{ config: TeamsbotConfig }> { const response = await api.get(`/api/teamsbot/${instanceId}/config`); return response.data; } /** * Update teamsbot configuration (instance-level defaults). */ export async function updateConfig(instanceId: string, updates: ConfigUpdateRequest): Promise<{ config: TeamsbotConfig }> { const response = await api.put(`/api/teamsbot/${instanceId}/config`, updates); return response.data; } /** * Get per-user settings merged with instance defaults. */ export async function getUserSettings(instanceId: string): Promise<{ settings: any; effectiveConfig: TeamsbotConfig }> { const response = await api.get(`/api/teamsbot/${instanceId}/settings`); return response.data; } /** * Update per-user settings. */ export async function updateUserSettings(instanceId: string, updates: ConfigUpdateRequest): Promise<{ settings: any; effectiveConfig: TeamsbotConfig }> { const response = await api.put(`/api/teamsbot/${instanceId}/settings`, updates); return response.data; } /** * Reset per-user settings to instance defaults. */ export async function resetUserSettings(instanceId: string): Promise<{ settings: null; effectiveConfig: TeamsbotConfig }> { const response = await api.delete(`/api/teamsbot/${instanceId}/settings`); return response.data; } export interface SystemBot { id: string; mandateId: string; name: string; email: string; isActive: boolean; creationDate?: string; } /** * List system bot accounts for this mandate. */ export async function listSystemBots(instanceId: string): Promise<{ bots: SystemBot[] }> { const response = await api.get(`/api/teamsbot/${instanceId}/system-bots`); return response.data; } /** * Create a new system bot account. The password is encrypted server-side * before storage; the API never returns the password back. SysAdmin only. */ export async function createSystemBot( instanceId: string, payload: { email: string; password: string; name?: string }, ): Promise<{ bot: SystemBot }> { const response = await api.post(`/api/teamsbot/${instanceId}/system-bots`, payload); return response.data; } /** * Delete a system bot account. SysAdmin only. */ export async function deleteSystemBot( instanceId: string, botId: string, ): Promise<{ deleted: boolean }> { const response = await api.delete(`/api/teamsbot/${instanceId}/system-bots/${botId}`); return response.data; } /** * Test TTS voice with AI-generated sample text. Returns base64-encoded audio. */ export async function testVoice( instanceId: string, botName: string, language: string, voiceId?: string, ): Promise<{ success: boolean; audio?: string; format?: string; text?: string; error?: string }> { const response = await api.post(`/api/teamsbot/${instanceId}/voice/test`, { botName, language, voiceId, }); return response.data; } /** * Fetch the curated voice/language catalog (single source of truth). * Re-exports the central voiceCatalogApi.fetchVoiceCatalog so legacy * teamsbot consumers stay on one import surface. */ export { fetchVoiceCatalog as fetchLanguages } from './voiceCatalogApi'; /** * Fetch available TTS voices for a language from Google Cloud. */ export async function fetchVoices(languageCode: string): Promise { try { const response = await api.get('/api/voice/voices', { params: { language: languageCode }, }); return response.data?.voices || []; } catch { return []; } } /** * Get list of available test variants from the Browser Bot. */ export interface TestVariantInfo { id: string; name: string; description: string; } export async function getTestAuthVariants(instanceId: string): Promise { const response = await api.get(`/api/teamsbot/${instanceId}/test-auth/variants`, { timeout: 30000, }); return response.data; } /** * Run a single test variant. Call this once per variant sequentially. * Each call stays within Azure's 240s timeout. */ export async function testAuthSingleVariant( instanceId: string, variantId: string, meetingUrl: string, botEmail?: string, botPassword?: string, ): Promise { const payload: Record = { variantId, meetingUrl }; if (botEmail) payload.botEmail = botEmail; if (botPassword) payload.botPassword = botPassword; const response = await api.post(`/api/teamsbot/${instanceId}/test-auth/variant`, payload, { timeout: 200000, // 3+ minutes per variant }); return response.data; } /** * Run ALL auth detection tests in one request (legacy — may timeout). */ export async function testAuth(instanceId: string, meetingUrl: string, botEmail?: string, botPassword?: string): Promise { const payload: Record = { meetingUrl }; if (botEmail) payload.botEmail = botEmail; if (botPassword) payload.botPassword = botPassword; const response = await api.post(`/api/teamsbot/${instanceId}/test-auth`, payload, { timeout: 900000, }); return response.data; } /** * Create an SSE EventSource for live session streaming. * Returns the EventSource instance for the caller to manage. */ export function createSessionStream(instanceId: string, sessionId: string): EventSource { const baseUrl = api.defaults.baseURL || ''; const url = `${baseUrl}/api/teamsbot/${instanceId}/sessions/${sessionId}/stream`; return new EventSource(url, { withCredentials: true }); } // ========================================================================= // Debug Screenshots (SysAdmin only) // ========================================================================= export interface ScreenshotInfo { name: string; step: string; timestamp: number; sizeBytes: number; url: string; } export async function listScreenshots(instanceId: string, sessionId: string): Promise<{ screenshots: ScreenshotInfo[] }> { const response = await api.get(`/api/teamsbot/${instanceId}/sessions/${sessionId}/screenshots`); return response.data; } export function getScreenshotUrl(instanceId: string, filename: string): string { const baseUrl = api.defaults.baseURL || ''; return `${baseUrl}/api/teamsbot/${instanceId}/screenshots/${filename}`; } // ========================================================================= // User Account (Mein Account) // ========================================================================= export async function getUserAccount(instanceId: string): Promise { const response = await api.get(`/api/teamsbot/${instanceId}/user-account`); return response.data; } export async function saveUserAccount( instanceId: string, email: string, password: string, displayName?: string, ): Promise<{ saved: boolean; email: string }> { const response = await api.post(`/api/teamsbot/${instanceId}/user-account`, { email, password, displayName, }); return response.data; } export async function deleteUserAccount(instanceId: string): Promise<{ deleted: boolean }> { const response = await api.delete(`/api/teamsbot/${instanceId}/user-account`); return response.data; } // ========================================================================= // MFA // ========================================================================= export async function submitMfaCode( instanceId: string, sessionId: string, code: string, action: 'code' | 'confirmed' = 'code', ): Promise<{ submitted: boolean }> { const response = await api.post(`/api/teamsbot/${instanceId}/sessions/${sessionId}/mfa`, { code, action, }); return response.data; } // ========================================================================= // Director Prompts // ========================================================================= /** * Submit a private director prompt to the running bot. Triggers the full * agent path (web, mail, RAG, etc.) and delivers the answer into the meeting. */ export async function submitDirectorPrompt( instanceId: string, sessionId: string, body: DirectorPromptCreateRequest, ): Promise<{ prompt: DirectorPrompt }> { const response = await api.post( `/api/teamsbot/${instanceId}/sessions/${sessionId}/directorPrompts`, body, ); return response.data; } /** * List director prompts for a session (operator's own prompts only). */ export async function listDirectorPrompts( instanceId: string, sessionId: string, ): Promise<{ prompts: DirectorPrompt[] }> { const response = await api.get( `/api/teamsbot/${instanceId}/sessions/${sessionId}/directorPrompts`, ); return response.data; } /** * Remove a (typically persistent) director prompt. */ export async function deleteDirectorPrompt( instanceId: string, sessionId: string, promptId: string, ): Promise<{ deleted: boolean; promptId: string }> { const response = await api.delete( `/api/teamsbot/${instanceId}/sessions/${sessionId}/directorPrompts/${promptId}`, ); return response.data; }