454 lines
13 KiB
TypeScript
454 lines
13 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';
|
|
data: any;
|
|
timestamp?: 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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|