diff --git a/backend/src/routes/agent.ts b/backend/src/routes/agent.ts index 24e1c3c..2481d06 100644 --- a/backend/src/routes/agent.ts +++ b/backend/src/routes/agent.ts @@ -234,16 +234,20 @@ router.post('/insight', async (req: Request, res: Response): Promise => { NETZER_STYLE + DELLING_STYLE + 'Schreibe einen kurzen Expertenblick als Dialog zwischen Delling und Netzer über das folgende Spiel.\n\n' + - 'BEISPIELE (so klingt das Duo - diese Authentizität ist entscheidend):\n\n' + + 'WICHTIG: Das Spiel hat noch NICHT stattgefunden. Es ist eine Vorschau, keine Nachbetrachtung.\n' + + 'Verwende ausschließlich Zukunftsformen und Konjunktiv: "wird", "könnte", "dürfte", "ist zu erwarten".\n' + + 'Du darfst auf vergangene Begegnungen, Qualifikation oder historische Statistiken referenzieren - aber nur als Argument für die Prognose.\n' + + 'VERBOTEN: Phrasen wie "hat mir nicht gefallen", "da war nichts", "das war" bezogen auf das aktuelle Spiel.\n\n' + + 'BEISPIELE (Vorschau-Ton - diese Authentizität ist entscheidend):\n\n' + 'Beispiel 1 (Gruppenspiel mit klarem Favoriten):\n' + - '**Delling:** "Nun, Herr Netzer, wir haben hier ja doch einen veritablen Favoriten. Fanden Sie nicht, dass immerhin die Leistungsdaten der letzten Qualifikation für den Außenseiter sprechen könnten?"\n' + - '**Netzer:** "Nein. Das waren Qualifikationsspiele. Das hat mit dem hier nichts zu tun. Das sind fundamentale Dinge."\n' + - '**Delling:** "Seien wir doch mal großzügig - auch der Außenseiter hat Qualitäten."\n' + - '**Netzer:** "Das nennen Sie Qualitäten. Ich nenne das ein Minimalisten-Dasein."\n\n' + + '**Delling:** "Nun, Herr Netzer, wir haben hier ja doch einen veritablen Favoriten. Könnte der Außenseiter nicht von der Qualifikationsform profitieren?"\n' + + '**Netzer:** "Nein. Das waren Qualifikationsspiele. Das wird mit dem hier nichts zu tun haben. Das sind fundamentale Dinge."\n' + + '**Delling:** "Seien wir doch mal großzügig - auch der Außenseiter hat Qualitäten, die sich zeigen könnten."\n' + + '**Netzer:** "Das nennen Sie Qualitäten. Ich nenne das ein Minimalisten-Dasein. Ich tippe auf einen klaren Sieg des Favoriten."\n\n' + 'Beispiel 2 (Ausgeglichenes Spiel):\n' + - '**Delling:** "Herr Netzer, das könnte ja ein enges Spiel werden. Beide Mannschaften liegen dicht beieinander."\n' + - '**Netzer:** "Das war dezent ausgedrückt. Beiden fehlt, was Beckenbauer damals selbstverständlich war - diese Überlegenheit, dieses Selbstverständnis. Aus der Tiefe des Raumes heraus, verstehen Sie?"\n' + - '**Delling:** "Ich glaube, die Spieler würden sich bedanken, wenn Sie ihnen das erläutern könnten."\n' + + '**Delling:** "Herr Netzer, das könnte ja ein enges Spiel werden. Beide Mannschaften liegen nah beieinander."\n' + + '**Netzer:** "Das ist dezent ausgedrückt. Beiden fehlt, was Beckenbauer damals selbstverständlich war - diese Überlegenheit. Aus der Tiefe des Raumes heraus, verstehen Sie?"\n' + + '**Delling:** "Ich glaube, die Spieler würden sich bedanken, wenn Sie ihnen das vor dem Anpfiff erläutern könnten."\n' + '**Netzer:** "Was bleibt mir noch übrig jetzt zu sagen. Ich tippe auf ein 1:1."\n\n' + 'JETZT das echte Spiel:\n' + 'Spiel: **' + homeTeam + '** vs. **' + awayTeam + '** (' + stageLabel + ')\n\n' + @@ -339,7 +343,7 @@ function parseDialogTurns( const turns: Array<{ speaker: 'Delling' | 'Netzer'; text: string }> = []; const lines = dialogText.split('\n'); for (const line of lines) { - const m = line.match(/^\*\*(Delling|Netzer):\*\*\s*[""]?(.+?)[""]?\s*$/); + const m = line.match(/^\*\*(Delling|Netzer):\*\*\s*[„""]?(.+?)["""]?\s*$/); if (m) { turns.push({ speaker: m[1] as 'Delling' | 'Netzer', @@ -365,22 +369,23 @@ router.post('/insight-audio', async (req: Request, res: Response): Promise } const turns = parseDialogTurns(dialogText); + logger.info('Audio: Dialog geparst', { turns: turns.length, preview: dialogText.slice(0, 200) }); if (turns.length === 0) { res.status(400).json({ error: 'Kein Dialog-Format erkannt' }); return; } try { - // Alle Turns parallel synthetisieren - const audioBuffers = await Promise.all( - turns.map((turn) => - synthesizeTurn( - turn.text, - turn.speaker === 'Netzer' ? ELEVENLABS_VOICE_NETZER : ELEVENLABS_VOICE_DELLING, - apiKey - ) - ) - ); + // Turns sequenziell synthetisieren (Free Tier: max 2 concurrent) + const audioBuffers: Buffer[] = []; + for (const turn of turns) { + const buf = await synthesizeTurn( + turn.text, + turn.speaker === 'Netzer' ? ELEVENLABS_VOICE_NETZER : ELEVENLABS_VOICE_DELLING, + apiKey + ); + audioBuffers.push(buf); + } // MP3-Chunks zusammenführen (einfaches Aneinanderhängen reicht für MP3) const combined = Buffer.concat(audioBuffers);