diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..d9d8d3d --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,45 @@ +interface SpeechRecognition extends EventTarget { + continuous: boolean; + interimResults: boolean; + lang: string; + onstart: ((this: SpeechRecognition, ev: Event) => void) | null; + onend: ((this: SpeechRecognition, ev: Event) => void) | null; + onerror: ((this: SpeechRecognition, ev: SpeechRecognitionErrorEvent) => void) | null; + onresult: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null; + onspeechstart: ((this: SpeechRecognition, ev: Event) => void) | null; + onspeechend: ((this: SpeechRecognition, ev: Event) => void) | null; + start(): void; + stop(): void; + abort(): void; +} + +interface SpeechRecognitionErrorEvent extends Event { + error: string; + message: string; +} + +interface SpeechRecognitionEvent extends Event { + resultIndex: number; + results: SpeechRecognitionResultList; +} + +interface SpeechRecognitionResultList { + length: number; + [index: number]: SpeechRecognitionResult; +} + +interface SpeechRecognitionResult { + isFinal: boolean; + length: number; + [index: number]: SpeechRecognitionAlternative; +} + +interface SpeechRecognitionAlternative { + transcript: string; + confidence: number; +} + +interface Window { + SpeechRecognition: new () => SpeechRecognition; + webkitSpeechRecognition: new () => SpeechRecognition; +} diff --git a/src/hooks/useCommcoach.ts b/src/hooks/useCommcoach.ts index d25fc73..98db6ee 100644 --- a/src/hooks/useCommcoach.ts +++ b/src/hooks/useCommcoach.ts @@ -9,10 +9,9 @@ import { useApiRequest } from './useApi'; import { useInstanceId } from './useCurrentInstance'; import { getContextsApi, createContextApi, getContextDetailApi, - startSessionStreamApi, getSessionApi, completeSessionApi, cancelSessionApi, + startSessionStreamApi, completeSessionApi, cancelSessionApi, sendMessageStreamApi, sendAudioStreamApi, - getTasksApi, createTaskApi, updateTaskStatusApi, deleteTaskApi, - getProfileApi, testVoiceApi, + createTaskApi, updateTaskStatusApi, deleteTaskApi, type CoachingContext, type CoachingSession, type CoachingMessage, type CoachingTask, type CoachingScore, type SSEEvent, } from '../api/commcoachApi'; @@ -84,45 +83,9 @@ export function useCommcoach(): CommcoachHookReturn { const isMountedRef = useRef(true); const currentAudioRef = useRef(null); const isTtsPlayingRef = useRef(false); - const profileRef = useRef<{ preferredLanguage?: string; preferredVoice?: string } | null>(null); useEffect(() => { isMountedRef.current = true; return () => { isMountedRef.current = false; }; }, []); - const _speakText = useCallback(async (text: string) => { - if (!instanceId) return; - const plain = _stripMarkdownForTts(text); - if (!plain.trim()) return; - if (currentAudioRef.current) { - currentAudioRef.current.pause(); - currentAudioRef.current = null; - } - try { - let profile = profileRef.current; - if (!profile) { - const p = await getProfileApi(request, instanceId); - profile = { preferredLanguage: p?.preferredLanguage || 'de-DE', preferredVoice: p?.preferredVoice }; - profileRef.current = profile; - } - const lang = profile?.preferredLanguage || 'de-DE'; - const voiceId = profile?.preferredVoice || undefined; - const result = await testVoiceApi(request, instanceId, { text: plain, language: lang, voiceId }); - if (result?.success && result?.audio && isMountedRef.current) { - const audio = new Audio(`data:audio/mp3;base64,${result.audio}`); - currentAudioRef.current = audio; - audio.onended = () => { currentAudioRef.current = null; }; - try { - await audio.play(); - } catch (playErr: any) { - if (playErr?.name === 'NotAllowedError') { - console.warn('CommCoach TTS: Browser blocked audio. Click "Session starten" or "Senden" first.'); - } - } - } - } catch { - // TTS failed silently, text is still visible - } - }, [request, instanceId]); - const refreshContexts = useCallback(async () => { if (!instanceId) return; setLoadingContexts(true); @@ -520,8 +483,6 @@ export function useCommcoach(): CommcoachHookReturn { useEffect(() => { if (instanceId) refreshContexts(); }, [instanceId, refreshContexts]); - useEffect(() => { profileRef.current = null; }, [instanceId]); - return { contexts, selectedContextId, selectedContext, loadingContexts, session, messages, isStreaming, streamingStatus, @@ -536,16 +497,6 @@ export function useCommcoach(): CommcoachHookReturn { }; } -function _stripMarkdownForTts(text: string): string { - return text - .replace(/\*\*(.+?)\*\*/g, '$1') - .replace(/\*(.+?)\*/g, '$1') - .replace(/\[(.+?)\]\(.+?\)/g, '$1') - .replace(/^#+\s*/gm, '') - .replace(/`[^`]+`/g, (m) => m.slice(1, -1)) - .trim(); -} - async function _unlockAudioForTts(): Promise { try { const ctx = new (window.AudioContext || (window as any).webkitAudioContext)(); diff --git a/src/pages/views/commcoach/CommcoachDashboardView.tsx b/src/pages/views/commcoach/CommcoachDashboardView.tsx index 05df578..02a2b12 100644 --- a/src/pages/views/commcoach/CommcoachDashboardView.tsx +++ b/src/pages/views/commcoach/CommcoachDashboardView.tsx @@ -13,7 +13,7 @@ import styles from './CommcoachDashboardView.module.css'; export const CommcoachDashboardView: React.FC = () => { const navigate = useNavigate(); const { mandateId, instanceId } = useCurrentInstance(); - const { dashboard, profile, loading, error, refresh } = useCommcoachDashboard(); + const { dashboard, loading, error } = useCommcoachDashboard(); const handleContextClick = (contextId: string) => { if (mandateId && instanceId) { diff --git a/src/pages/views/commcoach/CommcoachDossierView.tsx b/src/pages/views/commcoach/CommcoachDossierView.tsx index df289e3..d0ac345 100644 --- a/src/pages/views/commcoach/CommcoachDossierView.tsx +++ b/src/pages/views/commcoach/CommcoachDossierView.tsx @@ -209,7 +209,7 @@ export const CommcoachDossierView: React.FC = () => { interface ScoreGroup { dimension: string; - latest: { score: number; trend: string; evidence?: string }; + latest: { score: number; trend: string; evidence?: string; createdAt?: string }; history: Array<{ score: number; createdAt?: string }>; }