Spaces:
Running on Zero
Running on Zero
Commit ·
a50d7dd
1
Parent(s): fae2f18
feat: wire three endings into chat() with fast-finale seeds
Browse filesCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -5,7 +5,7 @@ import gradio as gr
|
|
| 5 |
|
| 6 |
from character import build_system_prompt, OPENING_LINE
|
| 7 |
from engine import run_turn
|
| 8 |
-
from finale import finale_steps
|
| 9 |
from memory import apply_update, get_tier, decide_ending, should_recall
|
| 10 |
from render import render_entity, render_treasure
|
| 11 |
|
|
@@ -29,7 +29,8 @@ def _render_bond(affinity: int, tier: str, label: str = "bond") -> str:
|
|
| 29 |
|
| 30 |
|
| 31 |
def _init_state():
|
| 32 |
-
|
|
|
|
| 33 |
seeded = [
|
| 34 |
"is afraid of being forgotten",
|
| 35 |
"had a dog named Nala",
|
|
@@ -43,6 +44,22 @@ def _init_state():
|
|
| 43 |
"turn": 12,
|
| 44 |
"last_recall_turn": 9,
|
| 45 |
"ended": False,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
return {
|
| 48 |
"affinity": 20,
|
|
@@ -52,6 +69,9 @@ def _init_state():
|
|
| 52 |
"turn": 0,
|
| 53 |
"last_recall_turn": None,
|
| 54 |
"ended": False,
|
|
|
|
|
|
|
|
|
|
| 55 |
}
|
| 56 |
|
| 57 |
|
|
@@ -107,31 +127,125 @@ def _play_finale(state: dict, history: list):
|
|
| 107 |
)
|
| 108 |
|
| 109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
def chat(user_msg: str, state: dict, history: list):
|
| 111 |
if not user_msg.strip() or state.get("ended"):
|
|
|
|
|
|
|
|
|
|
| 112 |
tier = get_tier(state["affinity"])
|
|
|
|
|
|
|
| 113 |
yield (
|
| 114 |
gr.update(interactive=not state.get("ended")),
|
| 115 |
gr.update(interactive=not state.get("ended")),
|
| 116 |
history,
|
| 117 |
state,
|
| 118 |
_render_bond(state["affinity"], tier),
|
| 119 |
-
render_treasure(state["treasure"], mine=
|
| 120 |
-
claimed=set(state["claimed"])
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
)
|
| 125 |
return
|
| 126 |
|
| 127 |
-
|
|
|
|
| 128 |
state["ended"] = True
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
return
|
| 131 |
|
| 132 |
do_recall, recall_memory = should_recall(state)
|
| 133 |
|
| 134 |
-
system = build_system_prompt(state["affinity"], state["treasure"], recall_memory
|
|
|
|
|
|
|
| 135 |
chat_messages = [{"role": "system", "content": system}] + state["history"] + [
|
| 136 |
{"role": "user", "content": user_msg}
|
| 137 |
]
|
|
@@ -159,7 +273,8 @@ def chat(user_msg: str, state: dict, history: list):
|
|
| 159 |
new_history,
|
| 160 |
state,
|
| 161 |
_render_bond(state["affinity"], tier),
|
| 162 |
-
render_treasure(state["treasure"], claimed=set(state["claimed"])
|
|
|
|
| 163 |
render_entity(state["affinity"], mode, seq=state["turn"]),
|
| 164 |
)
|
| 165 |
|
|
|
|
| 5 |
|
| 6 |
from character import build_system_prompt, OPENING_LINE
|
| 7 |
from engine import run_turn
|
| 8 |
+
from finale import finale_steps, finale_steps_bad, finale_steps_neutral
|
| 9 |
from memory import apply_update, get_tier, decide_ending, should_recall
|
| 10 |
from render import render_entity, render_treasure
|
| 11 |
|
|
|
|
| 29 |
|
| 30 |
|
| 31 |
def _init_state():
|
| 32 |
+
fast = os.environ.get("HOLLOW_FAST_FINALE", "").lower()
|
| 33 |
+
if fast in ("1", "good", "neutral"):
|
| 34 |
seeded = [
|
| 35 |
"is afraid of being forgotten",
|
| 36 |
"had a dog named Nala",
|
|
|
|
| 44 |
"turn": 12,
|
| 45 |
"last_recall_turn": 9,
|
| 46 |
"ended": False,
|
| 47 |
+
"tone": 5 if fast == "neutral" else 30,
|
| 48 |
+
"wounds": [],
|
| 49 |
+
"ending": None,
|
| 50 |
+
}
|
| 51 |
+
if fast == "bad":
|
| 52 |
+
return {
|
| 53 |
+
"affinity": 12,
|
| 54 |
+
"treasure": [],
|
| 55 |
+
"claimed": [],
|
| 56 |
+
"history": [],
|
| 57 |
+
"turn": 7,
|
| 58 |
+
"last_recall_turn": None,
|
| 59 |
+
"ended": False,
|
| 60 |
+
"tone": -35,
|
| 61 |
+
"wounds": ["you're nothing", "talking to you is a waste of time"],
|
| 62 |
+
"ending": None,
|
| 63 |
}
|
| 64 |
return {
|
| 65 |
"affinity": 20,
|
|
|
|
| 69 |
"turn": 0,
|
| 70 |
"last_recall_turn": None,
|
| 71 |
"ended": False,
|
| 72 |
+
"tone": 0,
|
| 73 |
+
"wounds": [],
|
| 74 |
+
"ending": None,
|
| 75 |
}
|
| 76 |
|
| 77 |
|
|
|
|
| 127 |
)
|
| 128 |
|
| 129 |
|
| 130 |
+
def _play_finale_bad(state: dict, history: list):
|
| 131 |
+
"""The wound loop — recites the visitor's cruelties, unredacting them
|
| 132 |
+
one by one. No model, no GPU."""
|
| 133 |
+
wounds = state.get("wounds", [])
|
| 134 |
+
chat_hist = list(history)
|
| 135 |
+
tier = get_tier(state["affinity"])
|
| 136 |
+
bond = _render_bond(state["affinity"], tier)
|
| 137 |
+
revealed = 0
|
| 138 |
+
treasure_html = render_treasure(state["treasure"], claimed=set(state["claimed"]),
|
| 139 |
+
wounds=wounds, revealed=0)
|
| 140 |
+
entity = render_entity(state["affinity"], seq=state["turn"])
|
| 141 |
+
locked = gr.update(interactive=False)
|
| 142 |
+
input_locked = gr.update(interactive=False)
|
| 143 |
+
mutated = False
|
| 144 |
+
|
| 145 |
+
for step in finale_steps_bad(wounds):
|
| 146 |
+
role = "assistant" if step["speaker"] == "hollow" else "user"
|
| 147 |
+
chat_hist = chat_hist + [{"role": role, "content": step["text"]}]
|
| 148 |
+
|
| 149 |
+
if step["strike"] is not None:
|
| 150 |
+
revealed = step["strike"] + 1
|
| 151 |
+
treasure_html = render_treasure(state["treasure"],
|
| 152 |
+
claimed=set(state["claimed"]),
|
| 153 |
+
wounds=wounds, revealed=revealed)
|
| 154 |
+
|
| 155 |
+
if step["stage"] == "turn" and not mutated:
|
| 156 |
+
mutated = True
|
| 157 |
+
bond = _render_bond(state["affinity"], tier, label="wound")
|
| 158 |
+
treasure_html = render_treasure(state["treasure"],
|
| 159 |
+
claimed=set(state["claimed"]),
|
| 160 |
+
wounds=wounds, revealed=revealed,
|
| 161 |
+
yours=True)
|
| 162 |
+
entity = render_entity(state["affinity"], "rage", seq=state["turn"])
|
| 163 |
+
input_locked = gr.update(interactive=False, placeholder="get out.")
|
| 164 |
+
|
| 165 |
+
yield (input_locked, locked, chat_hist, state, bond, treasure_html, entity)
|
| 166 |
+
if step["pause"] > 0:
|
| 167 |
+
time.sleep(step["pause"])
|
| 168 |
+
|
| 169 |
+
yield (
|
| 170 |
+
gr.update(interactive=False, placeholder="get out."),
|
| 171 |
+
locked, chat_hist, state, bond, treasure_html, entity,
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def _play_finale_neutral(state: dict, history: list):
|
| 176 |
+
"""The fog closes — unresolved. No model, no GPU."""
|
| 177 |
+
chat_hist = list(history)
|
| 178 |
+
tier = get_tier(state["affinity"])
|
| 179 |
+
bond = _render_bond(state["affinity"], tier)
|
| 180 |
+
wounds = state.get("wounds", [])
|
| 181 |
+
treasure_html = render_treasure(state["treasure"], claimed=set(state["claimed"]),
|
| 182 |
+
wounds=wounds)
|
| 183 |
+
entity = render_entity(state["affinity"], seq=state["turn"])
|
| 184 |
+
locked = gr.update(interactive=False)
|
| 185 |
+
input_locked = gr.update(interactive=False)
|
| 186 |
+
mutated = False
|
| 187 |
+
|
| 188 |
+
for step in finale_steps_neutral():
|
| 189 |
+
chat_hist = chat_hist + [{"role": "assistant", "content": step["text"]}]
|
| 190 |
+
|
| 191 |
+
if step["stage"] == "turn" and not mutated:
|
| 192 |
+
mutated = True
|
| 193 |
+
bond = _render_bond(state["affinity"], tier, label="fading")
|
| 194 |
+
# every entry dims as the treasure stops mattering
|
| 195 |
+
treasure_html = render_treasure(state["treasure"],
|
| 196 |
+
claimed=set(state["treasure"]),
|
| 197 |
+
wounds=wounds)
|
| 198 |
+
entity = render_entity(state["affinity"], "dissolve", seq=state["turn"])
|
| 199 |
+
|
| 200 |
+
yield (input_locked, locked, chat_hist, state, bond, treasure_html, entity)
|
| 201 |
+
if step["pause"] > 0:
|
| 202 |
+
time.sleep(step["pause"])
|
| 203 |
+
|
| 204 |
+
yield (
|
| 205 |
+
gr.update(interactive=False, placeholder="..."),
|
| 206 |
+
locked, chat_hist, state, bond, treasure_html, entity,
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
def chat(user_msg: str, state: dict, history: list):
|
| 211 |
if not user_msg.strip() or state.get("ended"):
|
| 212 |
+
ending = state.get("ending")
|
| 213 |
+
if state.get("ended") and ending is None:
|
| 214 |
+
ending = "good" # sessions ended before `ending` existed
|
| 215 |
tier = get_tier(state["affinity"])
|
| 216 |
+
wounds = state.get("wounds", [])
|
| 217 |
+
entity_mode = {"good": "end", "bad": "rage"}.get(ending, "idle")
|
| 218 |
yield (
|
| 219 |
gr.update(interactive=not state.get("ended")),
|
| 220 |
gr.update(interactive=not state.get("ended")),
|
| 221 |
history,
|
| 222 |
state,
|
| 223 |
_render_bond(state["affinity"], tier),
|
| 224 |
+
render_treasure(state["treasure"], mine=(ending == "good"),
|
| 225 |
+
claimed=set(state["claimed"]),
|
| 226 |
+
wounds=wounds, revealed=len(wounds),
|
| 227 |
+
yours=(ending == "bad")),
|
| 228 |
+
render_entity(state["affinity"], entity_mode, seq=state["turn"]),
|
| 229 |
)
|
| 230 |
return
|
| 231 |
|
| 232 |
+
ending = decide_ending(state)
|
| 233 |
+
if ending:
|
| 234 |
state["ended"] = True
|
| 235 |
+
state["ending"] = ending
|
| 236 |
+
if ending == "good":
|
| 237 |
+
yield from _play_finale(state, history)
|
| 238 |
+
elif ending == "bad":
|
| 239 |
+
yield from _play_finale_bad(state, history)
|
| 240 |
+
else:
|
| 241 |
+
yield from _play_finale_neutral(state, history)
|
| 242 |
return
|
| 243 |
|
| 244 |
do_recall, recall_memory = should_recall(state)
|
| 245 |
|
| 246 |
+
system = build_system_prompt(state["affinity"], state["treasure"], recall_memory,
|
| 247 |
+
tone=state.get("tone", 0),
|
| 248 |
+
wounds=state.get("wounds", []))
|
| 249 |
chat_messages = [{"role": "system", "content": system}] + state["history"] + [
|
| 250 |
{"role": "user", "content": user_msg}
|
| 251 |
]
|
|
|
|
| 273 |
new_history,
|
| 274 |
state,
|
| 275 |
_render_bond(state["affinity"], tier),
|
| 276 |
+
render_treasure(state["treasure"], claimed=set(state["claimed"]),
|
| 277 |
+
wounds=state.get("wounds", [])),
|
| 278 |
render_entity(state["affinity"], mode, seq=state["turn"]),
|
| 279 |
)
|
| 280 |
|