frontend_nyla/src/api/teamsbotApi.ts
2026-04-25 01:13:13 +02:00

576 lines
16 KiB
TypeScript

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<VoiceOption[]> {
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<TestVariantInfo[]> {
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<AuthTestResult> {
const payload: Record<string, string> = { 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<AuthTestResults> {
const payload: Record<string, string> = { 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<UserAccountStatus> {
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;
}