diff --git a/CHANGELOG.md b/CHANGELOG.md index 4be8c8d..7af5821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi --- +## [1.14.15] — 2026-07-09 + +### Added +- **Two new attribution rules for the casting prompt**: a mid-quote " - " (em-dash pause) no longer causes the model to treat it as the end of a quotation and hand the rest to a wrong/new speaker; and narration that names whose voice is about to speak (e.g. "Marcians Stimme wirkte...") now resolves the following unattributed line to that character instead of leaving it 'Unknown'. Applied to the default prompt and auto-migrated into already-saved custom prompts (same mechanism as the earlier Doppelpunkt-Regel upgrade). + +--- + ## [1.14.14] — 2026-07-09 ### Fixed diff --git a/VERSION b/VERSION index 569a91f..32f1112 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.14.14 +1.14.15 diff --git a/routes/conversation.py b/routes/conversation.py index 65a93c5..30e0155 100644 --- a/routes/conversation.py +++ b/routes/conversation.py @@ -1172,7 +1172,13 @@ def _attribution_prepare(data: dict) -> dict: "5. Ping-Pong: Zwei Personen im Gespräch wechseln sich strikt ab, auch über viele Zitate ohne Tags. " "In einer Zwei-Personen-Szene ist 'Unknown' fast immer falsch.\n" "6. Rollenbezeichnungen sind gültige Sprecher ('Ork', 'Der Fremde', 'Nachbar', 'Wächter') — nutze sie statt 'Unknown'.\n" - "7. 'Unknown' NUR, wenn eine Zuordnung trotz aller Regeln absolut unmöglich ist." + "7. 'Unknown' NUR, wenn eine Zuordnung trotz aller Regeln absolut unmöglich ist.\n" + "8. Gedankenstrich-Pause-Regel: Ein \" - \" MITTEN in einem Zitat ist eine Sprechpause DESSELBEN Sprechers, " + "kein Zitatende — die Rede desselben Charakters geht danach unverändert weiter, bis das tatsächliche " + "schließende Anführungszeichen erscheint.\n" + "9. Stimm-Ankündigung: Erwähnt ein Erzählersatz kurz vor einer noch nicht zugeordneten Zeile explizit die " + "Stimme oder das (beginnende) Sprechen einer bestimmten Person (z.B. \"Marcians Stimme wirkte nicht mehr so " + "fest\", \"X setzte zum Sprechen an\"), gehört diese Zeile dieser Person — nicht 'Unknown'." ) if not base_prompt.strip(): base_prompt = ( @@ -1214,7 +1220,13 @@ def _attribution_prepare(data: dict) -> dict: "- If a quote is interrupted by a tag (»Die Pause«, sagte Peter, »ist vorbei.«), stitch the spoken parts " "into ONE dialogue segment ('Die Pause ist vorbei.') with the tag as a separate narration segment.\n" "- Strip the quotation marks/guillemets from dialogue text. Keep every word otherwise, in order.\n" - "- DO NOT hallucinate, summarize, or alter the text. The combined text of your segments MUST exactly match the original passage, word for word, except for dropped quotation marks." + "- DO NOT hallucinate, summarize, or alter the text. The combined text of your segments MUST exactly match the original passage, word for word, except for dropped quotation marks.\n" + "- MID-QUOTE DASH RULE: a \" - \" (em-dash/hyphen used as a pause) in the MIDDLE of a quotation does NOT " + "end it — the SAME speaker's line continues unchanged after the dash, until the actual closing quotation " + "mark appears. Do not split it into narration or a new speaker at the dash.\n" + "- VOICE-ANNOUNCEMENT RULE: if narration explicitly mentions a specific character's voice or that they " + "are about to speak, shortly before an unattributed line (e.g. \"Marcian's voice sounded...\", " + "\"X began to say\"), attribute that line to that character instead of 'Unknown'." ) else: if lang_hint: diff --git a/static/index.html b/static/index.html index 94ee58b..304c778 100644 --- a/static/index.html +++ b/static/index.html @@ -10,7 +10,7 @@ - + @@ -27,7 +27,7 @@ - + @@ -365,7 +365,7 @@ window.toggleNavTree = function(treeId, chevronId) { - + diff --git a/static/js/audiobook.js b/static/js/audiobook.js index c3856f6..bf014bf 100644 --- a/static/js/audiobook.js +++ b/static/js/audiobook.js @@ -1229,6 +1229,8 @@ ANALYSE-REGELN FÜR DIE ZUORDNUNG DES SPRECHERS (Sei deduktiv — arbeite wie ei 8. Namenlose Sprecher: Auch Rollenbezeichnungen aus dem Text sind gültige Sprecher — nutze sie statt 'Unknown' (z.B. 'Ork', 'Der Fremde', 'Nachbar', 'Wächter', 'Junge'). 9. Wiederverwendung: Nutze EXAKT die Namen aus der Liste der bekannten Charaktere, FALLS der Name dort aufgeführt ist. Wenn ein neuer Charakter spricht, extrahiere seinen Namen direkt aus dem Text (z.B. 'Karyla', 'Uriens'). 10. 'Unknown' NUR, wenn eine Zuordnung trotz aller obigen Regeln absolut unmöglich ist. Bevorzuge immer einen genannten Charakter oder eine Rollenbezeichnung gegenüber 'Unknown'. +11. Gedankenstrich-Pause-Regel: Ein " - " MITTEN in einem Zitat ist eine Sprechpause DESSELBEN Sprechers, kein Zitatende — die Rede geht danach unverändert weiter, bis das tatsächliche schließende Anführungszeichen erscheint. +12. Stimm-Ankündigung: Erwähnt ein Erzählersatz kurz vor einer noch nicht zugeordneten Zeile explizit die Stimme oder das beginnende Sprechen einer bestimmten Person (z.B. "Marcians Stimme wirkte nicht mehr so fest", "X setzte zum Sprechen an"), gehört diese Zeile dieser Person — nicht 'Unknown'. FÜR JEDES SEGMENT GIBST DU FOLGENDES AUS: - speaker: 'Narrator' für Narration/Erzählertext, oder den EXAKTEN Namen des Charakters für gesprochene Dialoge. @@ -1297,6 +1299,20 @@ STRIKTE FORMAT- UND TEXTREGELN: 'Markiere Text NIEMALS als Dialog, nur weil der Name eines Charakters erwähnt wird! (z.B. "Karyla rannte zur Tür." ist Narration, KEIN Dialog).\n\nABSATZ- UND KAPITELSTRUKTUR:\nEine Leerzeile im Text markiert einen echten Absatzwechsel (oder einen Kapitel-/Szenenanfang). Eine sehr kurze, alleinstehende Zeile direkt vor einer Leerzeile (z.B. "1. Kapitel", "Prolog", ein Zahlwort) ist eine Kapitelüberschrift — immer \'narration\'/\'Narrator\', niemals Dialog. Behalte Leerzeilen als eigenständige narration-Segmente oder als Teil des umgebenden Erzähler-Segments bei; erfinde daraus keinen Dialog und lösche sie nicht aus dem rekonstruierten Text.' ); } + // Add the mid-quote-dash and voice-announcement rules to prompts saved + // before these existed — a passage like '»...« - und weiter...' was + // getting cut into a new (wrong) speaker at the dash, and narration like + // "Marcians Stimme wirkte nicht mehr so fest" right before an unattributed + // line wasn't being used to resolve it away from 'Unknown'. + if (!/Gedankenstrich-Pause-Regel/.test(p)) { + const newRules = '11. Gedankenstrich-Pause-Regel: Ein " - " MITTEN in einem Zitat ist eine Sprechpause DESSELBEN Sprechers, kein Zitatende — die Rede geht danach unverändert weiter, bis das tatsächliche schließende Anführungszeichen erscheint.\n12. Stimm-Ankündigung: Erwähnt ein Erzählersatz kurz vor einer noch nicht zugeordneten Zeile explizit die Stimme oder das beginnende Sprechen einer bestimmten Person (z.B. "Marcians Stimme wirkte nicht mehr so fest", "X setzte zum Sprechen an"), gehört diese Zeile dieser Person — nicht \'Unknown\'.'; + const unknownRuleRe = /(\n\d+\. 'Unknown' NUR,[^\n]*'Unknown'\.)/; + if (unknownRuleRe.test(p)) { + p = p.replace(unknownRuleRe, `$1\n${newRules}`); + } else { + p += `\n${newRules}`; + } + } return p; };