AndrewRqy commited on
Commit
f9490c0
·
1 Parent(s): fd741cf

Clean-Space pass: drop self-test, legacy unused handler, BISECT_MINIMAL branch, duplicate banner PNG, stale comments

Browse files
Files changed (4) hide show
  1. .env.example +14 -5
  2. app.py +2 -241
  3. assets/sprites/banner_near.png +0 -3
  4. run.sh +0 -1
.env.example CHANGED
@@ -1,16 +1,25 @@
1
  # Copy to .env.local and fill in. Without these, the app runs in mock mode.
2
 
3
- # Modal-hosted OpenAI-compatible endpoint (vLLM serving Qwen2.5-32B or similar).
4
  # Leave empty to force mock mode for the LLM calls.
5
  MODAL_URL=
6
 
7
- # Modal proxy auth (optional, only if the endpoint requires headers).
8
  MODAL_KEY=
9
  MODAL_SECRET=
10
 
11
  # Force mock mode for the LLM even if MODAL_URL is set.
12
  ORACLES_FORCE_MOCK=0
13
 
14
- # Klein-4B image generation endpoint (separate from the LLM URL).
15
- # Leave empty to disable live image gen; mock SVG cards are used.
16
- KLEIN_URL=
 
 
 
 
 
 
 
 
 
 
1
  # Copy to .env.local and fill in. Without these, the app runs in mock mode.
2
 
3
+ # Modal-hosted OpenAI-compatible endpoint (vLLM serving Qwen2.5-14B + LoRA).
4
  # Leave empty to force mock mode for the LLM calls.
5
  MODAL_URL=
6
 
7
+ # Modal proxy auth (Modal-Key and Modal-Secret headers).
8
  MODAL_KEY=
9
  MODAL_SECRET=
10
 
11
  # Force mock mode for the LLM even if MODAL_URL is set.
12
  ORACLES_FORCE_MOCK=0
13
 
14
+ # Override the served-model alias. Defaults to "oracle-wizard-lora" which
15
+ # the deployed vLLM serves as Qwen2.5-14B + the custom humor LoRA. Set
16
+ # to "llm" to request the bare base model.
17
+ # ORACLES_LLM_MODEL=oracle-wizard-lora
18
+
19
+ # Disable LLM-call trace recording. Tracing is default-on; remove or set
20
+ # to 0 to keep it on. Set to 1 to opt out.
21
+ # ORACLES_TRACE_DISABLE=0
22
+
23
+ # Visual mode: "lean" (default; minimal PNGs for fast load) or "full"
24
+ # (parallax banner, scene landscapes, all decorative assets).
25
+ # ORACLES_VISUAL_MODE=lean
app.py CHANGED
@@ -8,8 +8,7 @@ State machine:
8
  inscribe → send_off → trial (1..5) → epilogue → done
9
 
10
  Run:
11
- python app.py # launches Gradio
12
- python app.py --self-test # scripted walkthrough; exits 0 on pass
13
  """
14
 
15
  from __future__ import annotations
@@ -4098,52 +4097,6 @@ def handle_grimoire_lang(state: GameState, language_label: str,
4098
  return _ui_bundle(state)
4099
 
4100
 
4101
- def _legacy_handle_grimoire_lang_unused(state, language_label, theme_label="", narration_label=""):
4102
- """Kept only to satisfy any stale call sites; identical to the refactor.
4103
- DO NOT USE — call _apply_spread0_choices via handle_grimoire_lang instead."""
4104
- if state.mode != "inscribe" or state.inscribe_step != 0:
4105
- return _ui_bundle(state)
4106
- state.lang = _label_to_lang_code(language_label)
4107
-
4108
- # Resolve theme label back to its key; default to fantasy on miss.
4109
- from oracles.themes import THEMES, DEFAULT_THEME
4110
- chosen_key = DEFAULT_THEME
4111
- for key, theme in THEMES.items():
4112
- if theme.display_name == theme_label:
4113
- chosen_key = key
4114
- break
4115
- state.theme = chosen_key
4116
-
4117
- # Resolve narration-length label back to its key; default to medium.
4118
- # Label is now a `{"en": ..., "zh": ...}` dict — match against either
4119
- # language so the lookup is robust to whichever tongue the dropdown
4120
- # was rendered in when the click happened.
4121
- from oracles.state import NARRATION_LENGTHS as _NL
4122
- chosen_nl = "medium"
4123
- for key, (label, *_rest) in _NL.items():
4124
- if isinstance(label, dict):
4125
- if narration_label in label.values():
4126
- chosen_nl = key
4127
- break
4128
- elif label == narration_label:
4129
- chosen_nl = key
4130
- break
4131
- state.narration_length = chosen_nl
4132
-
4133
- # Refresh hero/village defaults to the theme's archetypes, if the user
4134
- # hasn't customized them yet (so the next spread's prompt shows themed
4135
- # defaults like "Wren" / "Bunker 7" instead of "Tobin" / "the Hollow").
4136
- theme_obj = THEMES[chosen_key]
4137
- if state.hero_name in ("", "Tobin"):
4138
- state.hero_name = theme_obj.hero_default
4139
- if state.village_name in ("", "the Hollow"):
4140
- state.village_name = theme_obj.village_default
4141
-
4142
- state.grimoire_error = ""
4143
- state.inscribe_step = 1
4144
- return _ui_bundle(state)
4145
-
4146
-
4147
  def handle_grimoire_name(state: GameState, hero_name: str):
4148
  """Step 1 → 2: inscribe the hero's name."""
4149
  if state.mode != "inscribe" or state.inscribe_step != 1:
@@ -4234,43 +4187,6 @@ def handle_grimoire_begin(state: GameState):
4234
  yield _ui_bundle(state, trial_substate=TRIAL_STATE_AWAIT)
4235
 
4236
 
4237
- # Back-compat shim for the e2e test and self-test that still call the
4238
- # original "seal everything in one shot" function.
4239
- def handle_seal_oracles(
4240
- state: GameState,
4241
- hero_name: str,
4242
- village_name: str,
4243
- language_label: str,
4244
- o1: str,
4245
- o2: str,
4246
- o3: str,
4247
- o4: str,
4248
- o5: str,
4249
- ):
4250
- if state.mode != "inscribe":
4251
- return _ui_bundle(state)
4252
- texts = [o1 or "", o2 or "", o3 or "", o4 or "", o5 or ""]
4253
- missing = [i + 1 for i, t in enumerate(texts) if not t.strip()]
4254
- if missing:
4255
- roman = ", ".join(_ROMAN.get(i, str(i)) for i in missing)
4256
- err = (
4257
- f"The parchment is blank — Oracle{'s' if len(missing) > 1 else ''} "
4258
- f"{roman} must hold at least one word."
4259
- )
4260
- # Surface error in the legacy slot AND on the grimoire so tests can find it.
4261
- state.grimoire_error = err
4262
- return _ui_bundle(state, inscribe_error=err)
4263
- state.hero_name = (hero_name or "Tobin").strip() or "Tobin"
4264
- state.village_name = (village_name or "the Hollow").strip() or "the Hollow"
4265
- state.lang = _label_to_lang_code(language_label)
4266
- for o, text in zip(state.oracles, texts):
4267
- o.text = text
4268
- state.inscribe_step = 8 # consider all spreads "passed"
4269
- state.mode = "send_off"
4270
- state.grimoire_error = ""
4271
- return _ui_bundle(state, inscribe_error="")
4272
-
4273
-
4274
  def handle_send_off(state: GameState):
4275
  """send_off → trial 1. Generates the 5 obstacles AND kicks off the
4276
  background precompute so trial reveals are instant in live mode."""
@@ -5032,140 +4948,6 @@ def build_app() -> gr.Blocks:
5032
  return demo
5033
 
5034
 
5035
- # ---------------------------------------------------------------------------
5036
- # Self-test
5037
- # ---------------------------------------------------------------------------
5038
-
5039
-
5040
- def _drain(gen_or_value):
5041
- """Drain a generator handler to its final yielded bundle.
5042
-
5043
- Several click handlers (handle_open_oracle, handle_continue,
5044
- handle_grimoire_begin, handle_send_off) are generators that yield
5045
- intermediate "walking-hero" UI states then a terminal bundle. The
5046
- self-test only cares about the terminal bundle. Pass-through any
5047
- non-generator return value untouched so the helper works for
5048
- regular handlers too.
5049
- """
5050
- import inspect as _inspect
5051
- if _inspect.isgenerator(gen_or_value):
5052
- last = None
5053
- for b in gen_or_value:
5054
- last = b
5055
- return last
5056
- return gen_or_value
5057
-
5058
-
5059
- def run_self_test() -> int:
5060
- """Scripted walkthrough that doesn't launch the server.
5061
-
5062
- Returns 0 on success, non-zero on failure.
5063
- """
5064
- # Force mock mode then rebuild the module-level CLIENT.
5065
- os.environ["ORACLES_FORCE_MOCK"] = "1"
5066
- global CLIENT
5067
- CLIENT = LLMClient()
5068
- assert CLIENT.using_mock, "self-test requires mock mode"
5069
-
5070
- # Fresh state — and seed the inputs as scripted.
5071
- state = fresh_state()
5072
-
5073
- oracle_texts = [
5074
- "be patient",
5075
- "pizza is square",
5076
- "🍕",
5077
- "wooden spoon",
5078
- "42",
5079
- ]
5080
-
5081
- # --- inscribe → send_off ---
5082
- bundle = handle_seal_oracles(
5083
- state,
5084
- "Tobin",
5085
- "the Hollow",
5086
- LANG_LABELS["en"],
5087
- *oracle_texts,
5088
- )
5089
- state = bundle[0]
5090
- assert state.mode == "send_off", f"expected send_off, got {state.mode!r}"
5091
- assert all(state.oracles[i].text == oracle_texts[i] for i in range(NUM_ORACLES)), \
5092
- "oracle texts not persisted"
5093
-
5094
- # --- send_off → trial 1 (also generates obstacles) ---
5095
- bundle = _drain(handle_send_off(state))
5096
- state = bundle[0]
5097
- assert state.mode == "trial", f"expected trial, got {state.mode!r}"
5098
- assert state.current_trial == 1, f"expected current_trial=1, got {state.current_trial}"
5099
- assert len(state.obstacles) == NUM_TRIALS, (
5100
- f"expected {NUM_TRIALS} obstacles, got {len(state.obstacles)}"
5101
- )
5102
- assert state.obstacles[-1].is_dragon, "trial 5 should be dragon"
5103
-
5104
- # --- trials 1..5 ---
5105
- for trial_n in range(1, NUM_TRIALS + 1):
5106
- assert state.mode == "trial", f"trial {trial_n}: bad mode {state.mode!r}"
5107
- assert state.current_trial == trial_n, (
5108
- f"trial {trial_n}: bad current_trial {state.current_trial}"
5109
- )
5110
-
5111
- # Open oracle (await -> reveal).
5112
- bundle = _drain(handle_open_oracle(state, TRIAL_STATE_AWAIT))
5113
- state = bundle[0]
5114
- substate = bundle[1]
5115
- assert substate == TRIAL_STATE_REVEAL, (
5116
- f"trial {trial_n}: expected reveal substate, got {substate!r}"
5117
- )
5118
-
5119
- res = state.resolutions[-1]
5120
- assert res.trial_index == trial_n, (
5121
- f"trial {trial_n}: resolution.trial_index mismatch"
5122
- )
5123
- assert res.narration and len(res.narration.strip()) > 0, (
5124
- f"trial {trial_n}: empty narration"
5125
- )
5126
- assert res.tactic and len(res.tactic.strip()) > 0, (
5127
- f"trial {trial_n}: empty tactic"
5128
- )
5129
- # Image file should exist on disk and have non-zero size (mock SVG/PNG).
5130
- assert res.image_path, f"trial {trial_n}: empty image_path"
5131
- assert os.path.exists(res.image_path), (
5132
- f"trial {trial_n}: image_path {res.image_path!r} not on disk"
5133
- )
5134
- assert os.path.getsize(res.image_path) > 0, (
5135
- f"trial {trial_n}: image_path {res.image_path!r} is empty"
5136
- )
5137
-
5138
- # Continue.
5139
- bundle = _drain(handle_continue(state, TRIAL_STATE_REVEAL))
5140
- state = bundle[0]
5141
-
5142
- # After trial 5 continue: epilogue.
5143
- assert state.mode == "epilogue", (
5144
- f"expected epilogue after trial {NUM_TRIALS}, got {state.mode!r}"
5145
- )
5146
- assert state.epilogue and state.epilogue.strip(), "epilogue empty"
5147
-
5148
- # Restart should land us back in inscribe with a fresh state, names preserved.
5149
- bundle = handle_restart(state)
5150
- new_state = bundle[0]
5151
- assert new_state.mode == "inscribe", (
5152
- f"after restart expected inscribe, got {new_state.mode!r}"
5153
- )
5154
- assert new_state.hero_name == "Tobin", "hero name should be preserved"
5155
- assert new_state.village_name == "the Hollow", "village name should be preserved"
5156
- assert all(o.text == "" for o in new_state.oracles), (
5157
- "oracles should be blank after restart"
5158
- )
5159
- assert new_state.resolutions == [], "resolutions should be cleared"
5160
-
5161
- # Also exercise build_app() once.
5162
- demo = build_app()
5163
- assert demo is not None, "build_app returned None"
5164
-
5165
- print("SELF TEST PASSED")
5166
- return 0
5167
-
5168
-
5169
  # ---------------------------------------------------------------------------
5170
  # Entry point
5171
  # ---------------------------------------------------------------------------
@@ -5175,10 +4957,6 @@ def run_self_test() -> int:
5175
  # to its own ``.launch()`` with DEFAULT settings — which means everything we
5176
  # pass to ``demo.launch(...)`` below would be ignored (no theme, no css, no
5177
  # ssr_mode=False, no allowed_paths). So we build the demo at module scope.
5178
- #
5179
- # To keep --self-test fast (it doesn't need a Blocks instance, and building
5180
- # one base64-encodes ~30 sprites), we skip the eager build when the flag is
5181
- # present.
5182
 
5183
  # Register the assets directory with Gradio's static file router so
5184
  # /gradio_api/file=<absolute-path> URLs served at runtime resolve to actual
@@ -5192,27 +4970,10 @@ except Exception:
5192
  pass
5193
 
5194
 
5195
- if "--self-test" in sys.argv:
5196
- demo = None
5197
- elif os.environ.get("BISECT_MINIMAL") == "1":
5198
- # Diagnostic bisect: if the App tab shows this minimal page, then the
5199
- # full build_app() Blocks tree is what breaks iframe rendering. If it
5200
- # ALSO shows nothing, the problem is outside build_app (Docker, port
5201
- # routing, allowed_paths, etc).
5202
- with gr.Blocks(title="The Apprentice — bisect probe") as demo:
5203
- gr.Markdown("# BISECT PROBE")
5204
- gr.Markdown(
5205
- "If you can read this, the Docker + Gradio + iframe layers work. "
5206
- "The full build_app() Blocks tree is what's breaking rendering."
5207
- )
5208
- gr.Button("Click me").click(lambda: "ok", outputs=gr.Textbox())
5209
- else:
5210
- demo = build_app()
5211
 
5212
 
5213
  def main() -> int:
5214
- if "--self-test" in sys.argv:
5215
- return run_self_test()
5216
  # Local entry point — uses the module-level ``demo`` so the local run
5217
  # matches the HF Space behavior exactly.
5218
  # ssr_mode is Gradio 5+ only. The local dev env may still be on
 
8
  inscribe → send_off → trial (1..5) → epilogue → done
9
 
10
  Run:
11
+ python app.py # launches Gradio (uses ./run.sh for full setup)
 
12
  """
13
 
14
  from __future__ import annotations
 
4097
  return _ui_bundle(state)
4098
 
4099
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4100
  def handle_grimoire_name(state: GameState, hero_name: str):
4101
  """Step 1 → 2: inscribe the hero's name."""
4102
  if state.mode != "inscribe" or state.inscribe_step != 1:
 
4187
  yield _ui_bundle(state, trial_substate=TRIAL_STATE_AWAIT)
4188
 
4189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4190
  def handle_send_off(state: GameState):
4191
  """send_off → trial 1. Generates the 5 obstacles AND kicks off the
4192
  background precompute so trial reveals are instant in live mode."""
 
4948
  return demo
4949
 
4950
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4951
  # ---------------------------------------------------------------------------
4952
  # Entry point
4953
  # ---------------------------------------------------------------------------
 
4957
  # to its own ``.launch()`` with DEFAULT settings — which means everything we
4958
  # pass to ``demo.launch(...)`` below would be ignored (no theme, no css, no
4959
  # ssr_mode=False, no allowed_paths). So we build the demo at module scope.
 
 
 
 
4960
 
4961
  # Register the assets directory with Gradio's static file router so
4962
  # /gradio_api/file=<absolute-path> URLs served at runtime resolve to actual
 
4970
  pass
4971
 
4972
 
4973
+ demo = build_app()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4974
 
4975
 
4976
  def main() -> int:
 
 
4977
  # Local entry point — uses the module-level ``demo`` so the local run
4978
  # matches the HF Space behavior exactly.
4979
  # ssr_mode is Gradio 5+ only. The local dev env may still be on
assets/sprites/banner_near.png DELETED

Git LFS Details

  • SHA256: 5cafce547a9e222b8216396bdebc2b496cc3dc6526dd9e0aaf4840c62d0456c2
  • Pointer size: 132 Bytes
  • Size of remote file: 1.22 MB
run.sh CHANGED
@@ -25,7 +25,6 @@
25
  # Sharing-is-Caring badge.
26
  # --no-trace Disable trace writing for this run (sets
27
  # ORACLES_TRACE_DISABLE=1).
28
- # --self-test Pass-through to app.py for the scripted walkthrough.
29
  # --full Enable ALL decorative PNGs (parallax banner,
30
  # parchment, phase backdrops, scene landscapes,
31
  # wizard-desk, open-book, demo-card backdrop).
 
25
  # Sharing-is-Caring badge.
26
  # --no-trace Disable trace writing for this run (sets
27
  # ORACLES_TRACE_DISABLE=1).
 
28
  # --full Enable ALL decorative PNGs (parallax banner,
29
  # parchment, phase backdrops, scene landscapes,
30
  # wizard-desk, open-book, demo-card backdrop).