475 lines
16 KiB
TypeScript
475 lines
16 KiB
TypeScript
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;
|
|
summary?: string;
|
|
durationSeconds: number;
|
|
messageCount: number;
|
|
competenceScore?: number;
|
|
emailSent: boolean;
|
|
startedAt?: string;
|
|
endedAt?: 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;
|
|
preferredLanguage: string;
|
|
preferredVoice?: 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;
|
|
contexts: Array<{ id: string; title: string; category: string; sessionCount: number; lastSessionAt?: string }>;
|
|
}
|
|
|
|
export interface SSEEvent {
|
|
type: string;
|
|
data?: any;
|
|
timestamp?: string;
|
|
}
|
|
|
|
export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<any>;
|
|
|
|
// ============================================================================
|
|
// Context API
|
|
// ============================================================================
|
|
|
|
export async function getContextsApi(request: ApiRequestFunction, instanceId: string): Promise<CoachingContext[]> {
|
|
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<CoachingContext> {
|
|
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<CoachingContext> {
|
|
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<void> {
|
|
await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}`, method: 'delete' });
|
|
}
|
|
|
|
export async function archiveContextApi(request: ApiRequestFunction, instanceId: string, contextId: string): Promise<CoachingContext> {
|
|
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<CoachingContext> {
|
|
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,
|
|
): Promise<void> {
|
|
try {
|
|
const baseURL = api.defaults.baseURL || '';
|
|
const url = `${baseURL}/api/commcoach/${instanceId}/contexts/${contextId}/sessions/start`;
|
|
|
|
const headers: Record<string, string> = { '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<CoachingSession> {
|
|
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<void> {
|
|
await request({ url: `/api/commcoach/${instanceId}/sessions/${sessionId}/cancel`, method: 'post' });
|
|
}
|
|
|
|
// ============================================================================
|
|
// Streaming Chat API
|
|
// ============================================================================
|
|
|
|
export async function sendMessageStreamApi(
|
|
instanceId: string,
|
|
sessionId: string,
|
|
content: string,
|
|
onEvent: (event: SSEEvent) => void,
|
|
onError?: (error: Error) => void,
|
|
onComplete?: () => void,
|
|
): Promise<void> {
|
|
try {
|
|
const baseURL = api.defaults.baseURL || '';
|
|
const url = `${baseURL}/api/commcoach/${instanceId}/sessions/${sessionId}/message/stream`;
|
|
|
|
const headers: Record<string, string> = { '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,
|
|
body: JSON.stringify({ content }),
|
|
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 sendAudioStreamApi(
|
|
instanceId: string,
|
|
sessionId: string,
|
|
audioBlob: Blob,
|
|
onEvent: (event: SSEEvent) => void,
|
|
onError?: (error: Error) => void,
|
|
onComplete?: () => void,
|
|
): Promise<void> {
|
|
try {
|
|
const baseURL = api.defaults.baseURL || '';
|
|
const url = `${baseURL}/api/commcoach/${instanceId}/sessions/${sessionId}/audio/stream`;
|
|
|
|
const headers: Record<string, string> = { '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<CoachingTask[]> {
|
|
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<CoachingTask> {
|
|
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<CoachingTask> {
|
|
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<CoachingTask> {
|
|
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<void> {
|
|
await request({ url: `/api/commcoach/${instanceId}/tasks/${taskId}`, method: 'delete' });
|
|
}
|
|
|
|
// ============================================================================
|
|
// Dashboard API
|
|
// ============================================================================
|
|
|
|
export async function getDashboardApi(request: ApiRequestFunction, instanceId: string): Promise<DashboardData> {
|
|
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<CoachingUserProfile> {
|
|
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<CoachingUserProfile> {
|
|
const data = await request({ url: `/api/commcoach/${instanceId}/profile`, method: 'put', data: body });
|
|
return data.profile;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Voice API
|
|
// ============================================================================
|
|
|
|
export async function getVoiceLanguagesApi(request: ApiRequestFunction, instanceId: string): Promise<any[]> {
|
|
const data = await request({ url: `/api/commcoach/${instanceId}/voice/languages`, method: 'get' });
|
|
return data.languages || [];
|
|
}
|
|
|
|
export async function getVoiceVoicesApi(request: ApiRequestFunction, instanceId: string, language: string = 'de-DE'): Promise<any[]> {
|
|
const data = await request({ url: `/api/commcoach/${instanceId}/voice/voices`, method: 'get', params: { language } });
|
|
return data.voices || [];
|
|
}
|
|
|
|
export async function testVoiceApi(request: ApiRequestFunction, instanceId: string, body: {
|
|
text?: string; language?: string; voiceId?: string;
|
|
}): Promise<{ success: boolean; audio?: string; format?: string; text?: string }> {
|
|
const data = await request({ url: `/api/commcoach/${instanceId}/voice/tts`, method: 'post', data: body });
|
|
return data;
|
|
}
|