fix: Expertenblick-Dialog auf Vorschau-Ton, deutsche Anführungszeichen & sequenzielle TTS
- Dialog-Prompt verwendet Zukunftsformen statt Vergangenheit (Vorschau, nicht Nachbetrachtung) - Regex für parseDialogTurns erkennt jetzt deutsche Anführungszeichen (typografisch) - ElevenLabs TTS sequenziell statt parallel (Free Tier max 2 concurrent) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+24
-19
@@ -234,16 +234,20 @@ router.post('/insight', async (req: Request, res: Response): Promise<void> => {
|
|||||||
NETZER_STYLE +
|
NETZER_STYLE +
|
||||||
DELLING_STYLE +
|
DELLING_STYLE +
|
||||||
'Schreibe einen kurzen Expertenblick als Dialog zwischen Delling und Netzer über das folgende Spiel.\n\n' +
|
'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' +
|
'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' +
|
'**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 hat mit dem hier nichts zu tun. Das sind fundamentale Dinge."\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."\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."\n\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' +
|
'Beispiel 2 (Ausgeglichenes Spiel):\n' +
|
||||||
'**Delling:** "Herr Netzer, das könnte ja ein enges Spiel werden. Beide Mannschaften liegen dicht beieinander."\n' +
|
'**Delling:** "Herr Netzer, das könnte ja ein enges Spiel werden. Beide Mannschaften liegen nah 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' +
|
'**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 erläutern könnten."\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' +
|
'**Netzer:** "Was bleibt mir noch übrig jetzt zu sagen. Ich tippe auf ein 1:1."\n\n' +
|
||||||
'JETZT das echte Spiel:\n' +
|
'JETZT das echte Spiel:\n' +
|
||||||
'Spiel: **' + homeTeam + '** vs. **' + awayTeam + '** (' + stageLabel + ')\n\n' +
|
'Spiel: **' + homeTeam + '** vs. **' + awayTeam + '** (' + stageLabel + ')\n\n' +
|
||||||
@@ -339,7 +343,7 @@ function parseDialogTurns(
|
|||||||
const turns: Array<{ speaker: 'Delling' | 'Netzer'; text: string }> = [];
|
const turns: Array<{ speaker: 'Delling' | 'Netzer'; text: string }> = [];
|
||||||
const lines = dialogText.split('\n');
|
const lines = dialogText.split('\n');
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const m = line.match(/^\*\*(Delling|Netzer):\*\*\s*[""]?(.+?)[""]?\s*$/);
|
const m = line.match(/^\*\*(Delling|Netzer):\*\*\s*[„""]?(.+?)["""]?\s*$/);
|
||||||
if (m) {
|
if (m) {
|
||||||
turns.push({
|
turns.push({
|
||||||
speaker: m[1] as 'Delling' | 'Netzer',
|
speaker: m[1] as 'Delling' | 'Netzer',
|
||||||
@@ -365,22 +369,23 @@ router.post('/insight-audio', async (req: Request, res: Response): Promise<void>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const turns = parseDialogTurns(dialogText);
|
const turns = parseDialogTurns(dialogText);
|
||||||
|
logger.info('Audio: Dialog geparst', { turns: turns.length, preview: dialogText.slice(0, 200) });
|
||||||
if (turns.length === 0) {
|
if (turns.length === 0) {
|
||||||
res.status(400).json({ error: 'Kein Dialog-Format erkannt' });
|
res.status(400).json({ error: 'Kein Dialog-Format erkannt' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Alle Turns parallel synthetisieren
|
// Turns sequenziell synthetisieren (Free Tier: max 2 concurrent)
|
||||||
const audioBuffers = await Promise.all(
|
const audioBuffers: Buffer[] = [];
|
||||||
turns.map((turn) =>
|
for (const turn of turns) {
|
||||||
synthesizeTurn(
|
const buf = await synthesizeTurn(
|
||||||
turn.text,
|
turn.text,
|
||||||
turn.speaker === 'Netzer' ? ELEVENLABS_VOICE_NETZER : ELEVENLABS_VOICE_DELLING,
|
turn.speaker === 'Netzer' ? ELEVENLABS_VOICE_NETZER : ELEVENLABS_VOICE_DELLING,
|
||||||
apiKey
|
apiKey
|
||||||
)
|
);
|
||||||
)
|
audioBuffers.push(buf);
|
||||||
);
|
}
|
||||||
|
|
||||||
// MP3-Chunks zusammenführen (einfaches Aneinanderhängen reicht für MP3)
|
// MP3-Chunks zusammenführen (einfaches Aneinanderhängen reicht für MP3)
|
||||||
const combined = Buffer.concat(audioBuffers);
|
const combined = Buffer.concat(audioBuffers);
|
||||||
|
|||||||
Reference in New Issue
Block a user