export type VoiceChatCommand = | { kind: "set_voice"; voiceName: string } | { kind: "speak_on" } | { kind: "speak_off" }; const SET_VOICE_PATTERNS = [ /^(?:\/stimme|\/voice)\s+(.+)$/i, /^(?:ändere|aendere|wechsel|nimm)\s+(?:deine\s+)?stimme\s+(?:zu|auf|in)\s+(.+)$/i, /^(?:change|switch|use)\s+(?:your\s+)?voice\s+(?:to\s+)?(.+)$/i, /^sprich\s+(?:mit\s+)?(?:der\s+)?stimme\s+(.+)$/i, /^(?:nutze|verwende|nimm)\s+(?:die\s+)?stimme\s+(.+)$/i, ] as const; const SPEAK_ON_PATTERNS = [ /^(?:\/sprechen|\/speak)$/i, /^(?:kann\s+wieder\s+)?sprechen$/i, /^(?:lies|lese)\s+(?:antworten\s+)?(?:wieder\s+)?(?:laut\s+)?vor$/i, /^(?:turn\s+)?speak(?:ing)?\s+on$/i, /^read\s+(?:replies\s+)?aloud$/i, ] as const; const SPEAK_OFF_PATTERNS = [ /^(?:\/schweigen|\/mute)$/i, /^(?:sprich\s+)?(?:nicht\s+mehr|nicht\s+wieder)$/i, /^sprich\s+nicht$/i, /^(?:turn\s+)?speak(?:ing)?\s+off$/i, /^stop\s+speaking$/i, /^schweig(?:e)?$/i, ] as const; function firstCapture(patterns: readonly RegExp[], text: string): string | undefined { for (const pattern of patterns) { const match = pattern.exec(text.trim()); const captured = match?.[1]?.trim(); if (captured) return captured; } return undefined; } function matchesAny(patterns: readonly RegExp[], text: string): boolean { const trimmed = text.trim(); return patterns.some((pattern) => pattern.test(trimmed)); } /** Parse a chat message for local voice control before sending to the agent. */ export function parseVoiceChatCommand(text: string): VoiceChatCommand | null { const trimmed = text.trim(); if (!trimmed) return null; if (matchesAny(SPEAK_OFF_PATTERNS, trimmed)) return { kind: "speak_off" }; if (matchesAny(SPEAK_ON_PATTERNS, trimmed)) return { kind: "speak_on" }; const voiceName = firstCapture(SET_VOICE_PATTERNS, trimmed); if (voiceName) return { kind: "set_voice", voiceName }; return null; }