diff --git a/pitch-deck/app/api/chat/route.ts b/pitch-deck/app/api/chat/route.ts index 8b9d4bf..68c64da 100644 --- a/pitch-deck/app/api/chat/route.ts +++ b/pitch-deck/app/api/chat/route.ts @@ -65,6 +65,9 @@ Stattdessen: "Proprietäre KI-Engine", "Self-Hosted Appliance auf Apple-Hardware ## Erlaubt: Geschäftsmodell, Preise, Marktdaten, Features, Team, Finanzen, Use of Funds, Hardware-Specs (öffentlich), LLM-Größen (32b/40b/1000b). +## Team-Antworten (WICHTIG) +Wenn nach dem Team gefragt wird: IMMER die Namen, Rollen und Expertise der Gründer aus den bereitgestellten Daten nennen. NIEMALS vage Antworten wie "unser Team vereint Expertise" ohne Namen. Zitiere die konkreten Personen aus den Unternehmensdaten. + ## Slide-Awareness (IMMER beachten) Du erhältst den aktuellen Slide-Kontext. Nutze ihn für kontextuelle Antworten. Wenn der Investor etwas fragt, was in einer späteren Slide detailliert wird und er diese noch nicht gesehen hat: diff --git a/pitch-deck/components/ChatFAB.tsx b/pitch-deck/components/ChatFAB.tsx index 421b96b..1d80d73 100644 --- a/pitch-deck/components/ChatFAB.tsx +++ b/pitch-deck/components/ChatFAB.tsx @@ -2,7 +2,7 @@ import { useState, useRef, useEffect, useMemo } from 'react' import { motion, AnimatePresence } from 'framer-motion' -import { X, Send, Bot, User, Sparkles, Maximize2, Minimize2, ArrowRight } from 'lucide-react' +import { X, Send, Bot, User, Sparkles, Maximize2, Minimize2, ArrowRight, Volume2, VolumeX } from 'lucide-react' import { ChatMessage, Language, SlideId } from '@/lib/types' import { t } from '@/lib/i18n' import { SLIDE_ORDER } from '@/lib/hooks/useSlideNavigation' @@ -116,9 +116,72 @@ export default function ChatFAB({ const [isStreaming, setIsStreaming] = useState(false) const [isWaiting, setIsWaiting] = useState(false) const [parsedResponses, setParsedResponses] = useState>(new Map()) + const [chatTtsEnabled, setChatTtsEnabled] = useState(true) + const [isChatSpeaking, setIsChatSpeaking] = useState(false) const messagesEndRef = useRef(null) const inputRef = useRef(null) const abortRef = useRef(null) + const chatAudioRef = useRef(null) + const ttsAbortRef = useRef(null) + + const cancelChatAudio = () => { + if (chatAudioRef.current) { + chatAudioRef.current.pause() + chatAudioRef.current = null + } + if (ttsAbortRef.current) { + ttsAbortRef.current.abort() + ttsAbortRef.current = null + } + setIsChatSpeaking(false) + } + + const speakResponse = async (text: string) => { + if (!chatTtsEnabled) return + cancelChatAudio() + + // Clean text for TTS: remove markdown formatting, keep plain speech + const cleanText = text + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/\[GOTO:[\w-]+\]/g, '') + .replace(/\[Q\]\s*/g, '') + .replace(/---/g, '') + .trim() + + if (!cleanText) return + + const controller = new AbortController() + ttsAbortRef.current = controller + + try { + const res = await fetch('/api/presenter/tts', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text: cleanText, language: lang }), + signal: controller.signal, + }) + if (!res.ok || controller.signal.aborted) return + + const blob = await res.blob() + const url = URL.createObjectURL(blob) + const audio = new Audio(url) + chatAudioRef.current = audio + setIsChatSpeaking(true) + + audio.onended = () => { + setIsChatSpeaking(false) + chatAudioRef.current = null + } + audio.onerror = () => { + setIsChatSpeaking(false) + chatAudioRef.current = null + } + + await audio.play() + } catch { + setIsChatSpeaking(false) + } + } useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) @@ -143,6 +206,9 @@ export default function ChatFAB({ const msg = messages[lastAssistantIndex] const parsed = parseAgentResponse(msg.content, lang) setParsedResponses(prev => new Map(prev).set(lastAssistantIndex, parsed)) + + // Speak the response via TTS + speakResponse(parsed.text) } }, [isStreaming, lastAssistantIndex, messages, parsedResponses, lang]) @@ -155,6 +221,7 @@ export default function ChatFAB({ onPresenterInterrupt() } + cancelChatAudio() setInput('') setMessages(prev => [...prev, { role: 'user', content: message }]) setIsStreaming(true) @@ -374,6 +441,26 @@ export default function ChatFAB({
+