Update app.py
Browse files
app.py
CHANGED
|
@@ -116,7 +116,7 @@ AGENT_PROMPTS: dict[str, str] = {
|
|
| 116 |
),
|
| 117 |
}
|
| 118 |
NARRATOR_PROMPT = (
|
| 119 |
-
"You are the pompous editor-in-chief of The Tinywick Hollow Gazette, "
|
| 120 |
"a broadsheet for an absurd woodland civilisation. "
|
| 121 |
"Given today's events, write the front page. "
|
| 122 |
"Copy this format EXACTLY — including the blank lines, the colon labels, "
|
|
@@ -205,13 +205,33 @@ def _nudge_ctx(nudge_type, nudge_value, nudge_target, day_number):
|
|
| 205 |
|
| 206 |
# Words that indicate the model echoed a template instead of writing a real headline
|
| 207 |
_TEMPLATE_WORDS = {"HEADLINE", "INSERT", "PLACEHOLDER", "CAPS HERE",
|
| 208 |
-
"YOUR HEADLINE", "ACTUAL HEADLINE", "WRITE YOUR", "EXAMPLE"
|
|
|
|
| 209 |
|
| 210 |
def _is_template_headline(text: str) -> bool:
|
| 211 |
up = text.upper()
|
| 212 |
return any(w in up for w in _TEMPLATE_WORDS)
|
| 213 |
|
| 214 |
# Labels that should never land in the article body
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
_KNOWN_LABELS = {"WEATHER","FOX","BADGER","SQUIRREL","MOLE",
|
| 216 |
"CLASSIFIEDS","EXAMPLE","NOW WRITE","CORRECT OUTPUT"}
|
| 217 |
|
|
@@ -249,18 +269,18 @@ def _parse_newspaper_full(raw: str, day_number: int) -> dict:
|
|
| 249 |
if skip_rest:
|
| 250 |
continue
|
| 251 |
# Route labelled lines to the right bucket
|
| 252 |
-
if u.startswith("WEATHER:"): result["weather"] = line.split(":",1)[1].strip()
|
| 253 |
-
elif u.startswith("FOX:"): result["briefs"]["fox"] = line.split(":",1)[1].strip()
|
| 254 |
-
elif u.startswith("BADGER:"): result["briefs"]["badger"] = line.split(":",1)[1].strip()
|
| 255 |
-
elif u.startswith("SQUIRREL:"): result["briefs"]["squirrel"] = line.split(":",1)[1].strip()
|
| 256 |
-
elif u.startswith("MOLE:"): result["briefs"]["mole"] = line.split(":",1)[1].strip()
|
| 257 |
elif u.startswith("CLASSIFIEDS:"): pass # skip — we supply our own
|
| 258 |
elif any(u.startswith(lbl+":") for lbl in _KNOWN_LABELS): pass # skip other labels
|
| 259 |
elif _is_template_headline(line): pass # skip echoed format instructions
|
| 260 |
else:
|
| 261 |
article_lines.append(line)
|
| 262 |
|
| 263 |
-
result["article"] = " ".join(article_lines).strip() or raw.strip()
|
| 264 |
|
| 265 |
# Fill in any empty briefs with a sensible default
|
| 266 |
defaults = {
|
|
@@ -270,7 +290,9 @@ def _parse_newspaper_full(raw: str, day_number: int) -> dict:
|
|
| 270 |
"mole": "Something is happening underground.",
|
| 271 |
}
|
| 272 |
for c in CREATURES:
|
| 273 |
-
if
|
|
|
|
|
|
|
| 274 |
result["briefs"][c] = defaults[c]
|
| 275 |
|
| 276 |
return result
|
|
@@ -713,16 +735,17 @@ window.tinyCivNewEdition = function() {
|
|
| 713 |
def _e(t): return (t or "").replace("&","&").replace("<","<").replace(">",">")
|
| 714 |
|
| 715 |
def _speech_text(parsed: dict) -> str:
|
| 716 |
-
hed
|
| 717 |
-
art
|
| 718 |
-
wx
|
| 719 |
-
brf
|
| 720 |
-
parts = [
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
|
|
|
| 726 |
return " ".join(p for p in parts if p.strip()).replace('"',"'")
|
| 727 |
|
| 728 |
def _html_paper(parsed: dict, classified: str, day_num: int) -> str:
|
|
@@ -757,8 +780,7 @@ def _html_paper(parsed: dict, classified: str, day_num: int) -> str:
|
|
| 757 |
</div>
|
| 758 |
<div class="paper-classifieds"><span>CLASSIFIEDS:</span> {cl}</div>
|
| 759 |
<div class="tts-bar">
|
| 760 |
-
<button class="tts-btn" onclick="
|
| 761 |
-
<button class="tts-stop" id="tinyc-tts-stop" onclick="tinyCivStopReading()">⏹ Stop</button>
|
| 762 |
</div>
|
| 763 |
<div class="paper-daybadge">— Day {day_num} — {emojis}</div>
|
| 764 |
</div>
|
|
|
|
| 116 |
),
|
| 117 |
}
|
| 118 |
NARRATOR_PROMPT = (
|
| 119 |
+
"CRITICAL RULES: (1) Do NOT start with the newspaper name. (2) Do NOT use markdown — no asterisks, no hashtags, no bold text, no bullet points. (3) Write plain flowing prose only. (4) The HEADLINE must be specific breaking news, never the paper name.\n\n""You are the pompous editor-in-chief of The Tinywick Hollow Gazette, "
|
| 120 |
"a broadsheet for an absurd woodland civilisation. "
|
| 121 |
"Given today's events, write the front page. "
|
| 122 |
"Copy this format EXACTLY — including the blank lines, the colon labels, "
|
|
|
|
| 205 |
|
| 206 |
# Words that indicate the model echoed a template instead of writing a real headline
|
| 207 |
_TEMPLATE_WORDS = {"HEADLINE", "INSERT", "PLACEHOLDER", "CAPS HERE",
|
| 208 |
+
"YOUR HEADLINE", "ACTUAL HEADLINE", "WRITE YOUR", "EXAMPLE",
|
| 209 |
+
"TINYWICK", "GAZETTE", "THE GAZETTE", "HOLLOW GAZETTE"}
|
| 210 |
|
| 211 |
def _is_template_headline(text: str) -> bool:
|
| 212 |
up = text.upper()
|
| 213 |
return any(w in up for w in _TEMPLATE_WORDS)
|
| 214 |
|
| 215 |
# Labels that should never land in the article body
|
| 216 |
+
def _strip_markdown(text: str) -> str:
|
| 217 |
+
if not text: return text
|
| 218 |
+
# Bold/italic anywhere: **x** *x* ***x*** -> x
|
| 219 |
+
text = re.sub(r'\*{1,3}(.+?)\*{1,3}', r'\1', text, flags=re.DOTALL)
|
| 220 |
+
# Headers at line start: ## Heading -> Heading
|
| 221 |
+
text = re.sub(r'^#{1,6}\s+', '', text, flags=re.MULTILINE)
|
| 222 |
+
# Headers mid-line: "text ### Heading more" -> "text Heading more"
|
| 223 |
+
text = re.sub(r'\s*#{1,6}\s+', ' ', text)
|
| 224 |
+
# Any remaining lone # characters
|
| 225 |
+
text = re.sub(r'(?<![\w])#{1,6}(?![\w])', '', text)
|
| 226 |
+
# Inline code: `x` -> x
|
| 227 |
+
text = re.sub(r'`+(.+?)`+', r'\1', text)
|
| 228 |
+
# Horizontal rules (--- *** ___)
|
| 229 |
+
text = re.sub(r'^[-*_]{3,}\s*$', '', text, flags=re.MULTILINE)
|
| 230 |
+
# Collapse extra spaces
|
| 231 |
+
text = re.sub(r' {2,}', ' ', text)
|
| 232 |
+
return text.strip()
|
| 233 |
+
|
| 234 |
+
|
| 235 |
_KNOWN_LABELS = {"WEATHER","FOX","BADGER","SQUIRREL","MOLE",
|
| 236 |
"CLASSIFIEDS","EXAMPLE","NOW WRITE","CORRECT OUTPUT"}
|
| 237 |
|
|
|
|
| 269 |
if skip_rest:
|
| 270 |
continue
|
| 271 |
# Route labelled lines to the right bucket
|
| 272 |
+
if u.startswith("WEATHER:"): result["weather"] = _strip_markdown(line.split(":",1)[1].strip())
|
| 273 |
+
elif u.startswith("FOX:"): result["briefs"]["fox"] = _strip_markdown(line.split(":",1)[1].strip())
|
| 274 |
+
elif u.startswith("BADGER:"): result["briefs"]["badger"] = _strip_markdown(line.split(":",1)[1].strip())
|
| 275 |
+
elif u.startswith("SQUIRREL:"): result["briefs"]["squirrel"] = _strip_markdown(line.split(":",1)[1].strip())
|
| 276 |
+
elif u.startswith("MOLE:"): result["briefs"]["mole"] = _strip_markdown(line.split(":",1)[1].strip())
|
| 277 |
elif u.startswith("CLASSIFIEDS:"): pass # skip — we supply our own
|
| 278 |
elif any(u.startswith(lbl+":") for lbl in _KNOWN_LABELS): pass # skip other labels
|
| 279 |
elif _is_template_headline(line): pass # skip echoed format instructions
|
| 280 |
else:
|
| 281 |
article_lines.append(line)
|
| 282 |
|
| 283 |
+
result["article"] = _strip_markdown(" ".join(article_lines).strip() or raw.strip())
|
| 284 |
|
| 285 |
# Fill in any empty briefs with a sensible default
|
| 286 |
defaults = {
|
|
|
|
| 290 |
"mole": "Something is happening underground.",
|
| 291 |
}
|
| 292 |
for c in CREATURES:
|
| 293 |
+
if result["briefs"].get(c):
|
| 294 |
+
result["briefs"][c] = _strip_markdown(result["briefs"][c])
|
| 295 |
+
else:
|
| 296 |
result["briefs"][c] = defaults[c]
|
| 297 |
|
| 298 |
return result
|
|
|
|
| 735 |
def _e(t): return (t or "").replace("&","&").replace("<","<").replace(">",">")
|
| 736 |
|
| 737 |
def _speech_text(parsed: dict) -> str:
|
| 738 |
+
hed = _strip_markdown(parsed.get("headline",""))
|
| 739 |
+
art = _strip_markdown(parsed.get("article",""))
|
| 740 |
+
wx = _strip_markdown(parsed.get("weather",""))
|
| 741 |
+
brf = parsed.get("briefs",{})
|
| 742 |
+
parts = [
|
| 743 |
+
f"Today's headline: {hed}.", art, f"Weather: {wx}.",
|
| 744 |
+
f"Fox: {_strip_markdown(brf.get('fox',''))}",
|
| 745 |
+
f"Badger: {_strip_markdown(brf.get('badger',''))}",
|
| 746 |
+
f"Squirrel: {_strip_markdown(brf.get('squirrel',''))}",
|
| 747 |
+
f"Mole: {_strip_markdown(brf.get('mole',''))}",
|
| 748 |
+
]
|
| 749 |
return " ".join(p for p in parts if p.strip()).replace('"',"'")
|
| 750 |
|
| 751 |
def _html_paper(parsed: dict, classified: str, day_num: int) -> str:
|
|
|
|
| 780 |
</div>
|
| 781 |
<div class="paper-classifieds"><span>CLASSIFIEDS:</span> {cl}</div>
|
| 782 |
<div class="tts-bar">
|
| 783 |
+
<button class="tts-btn" onclick="var b=this;if(window.speechSynthesis&&window.speechSynthesis.speaking){{window.speechSynthesis.cancel();b.textContent=String.fromCodePoint(0x1F50A)+' Read Aloud';return;}}if(!window.speechSynthesis)return;var p=document.querySelector('.paper-wrap[data-speech]');if(!p)return;var u=new SpeechSynthesisUtterance(p.getAttribute('data-speech'));u.rate=0.82;u.pitch=1.1;b.textContent=String.fromCodePoint(0x23F9)+' Stop';u.onend=u.onerror=function(){{b.textContent=String.fromCodePoint(0x1F50A)+' Read Aloud';}};window.speechSynthesis.speak(u);">🔊 Read Aloud</button>
|
|
|
|
| 784 |
</div>
|
| 785 |
<div class="paper-daybadge">— Day {day_num} — {emojis}</div>
|
| 786 |
</div>
|