Spaces:
Sleeping
Sleeping
| import inspect | |
| from pathlib import Path | |
| from PIL import Image | |
| import app | |
| from sovereign_bench.models import AgentTurn, EvidenceItem, JurorVote, TrialEvent | |
| OLD_CARD_CLASSES = [ | |
| "paper-panel", | |
| "juror-panel", | |
| "mind-panel", | |
| "empty-state", | |
| "trial-downloads", | |
| ] | |
| def _event_with_lower_tab_data() -> TrialEvent: | |
| evidence = EvidenceItem( | |
| id="E1", | |
| title="Ledger entry", | |
| source="Archive", | |
| excerpt="A short exhibit excerpt.", | |
| supports="claimant", | |
| reliability=0.82, | |
| note="Useful but incomplete.", | |
| ) | |
| vote = JurorVote( | |
| juror="Karl Marx", | |
| persona=app.JUROR_PERSONAS["Karl Marx"], | |
| vote="liable", | |
| reason="The exhibit supports the claim.", | |
| evidence_ids=["E1"], | |
| ) | |
| return TrialEvent( | |
| phase="deliberation", | |
| title="Jury weighs the record", | |
| body="The jury reviews the record.", | |
| turns=[ | |
| AgentTurn( | |
| agent="Nemotron Jury", | |
| role="juror panel", | |
| content="The jurors compare E1 and state their votes.", | |
| model="nvidia/Nemotron-Orchestrator-8B", | |
| confidence=0.84, | |
| input="SYSTEM:\nYou are the jury.\n\nUSER:\nWeigh E1 and explain the vote.", | |
| ) | |
| ], | |
| evidence=[evidence], | |
| votes=[vote], | |
| trace={"mode": "test"}, | |
| ) | |
| def _speaker_event(agent: str, phase: str = "questions") -> TrialEvent: | |
| return TrialEvent( | |
| phase=phase, | |
| title=f"{agent} speaks", | |
| body="A single speaker takes the floor.", | |
| turns=[ | |
| AgentTurn( | |
| agent=agent, | |
| role="test speaker", | |
| content=f"{agent} has the visible floor.", | |
| model="test-model", | |
| confidence=0.9, | |
| input="SYSTEM:\nTest prompt.", | |
| ) | |
| ], | |
| ) | |
| def test_lower_tab_renderers_emit_plain_text_classes(): | |
| event = _event_with_lower_tab_data() | |
| html = "\n".join( | |
| [ | |
| app.render_evidence([]), | |
| app.render_evidence([event]), | |
| app.render_jurors([]), | |
| app.render_jurors([event]), | |
| app.render_mind([], True), | |
| app.render_mind([event], True), | |
| app.render_mind([event], False), | |
| ] | |
| ) | |
| for class_name in OLD_CARD_CLASSES: | |
| assert class_name not in html | |
| assert "drawer-text-block" in html | |
| assert "drawer-empty" in html | |
| assert "mind-text" in html | |
| def test_download_controls_are_not_wired_into_app(): | |
| source = inspect.getsource(app.build_app) | |
| assert "DownloadButton" not in source | |
| assert "Download decree" not in source | |
| assert "Download agent trace" not in source | |
| def test_courtroom_splits_six_jurors_between_side_benches(): | |
| html = app.render_court([_event_with_lower_tab_data()], started=True) | |
| assert "jury-benches left" in html | |
| assert "jury-benches right" in html | |
| assert html.count("<a class='juror") == 6 | |
| assert html.find("jury-benches left") < html.find("jury-benches right") | |
| assert ".jury-benches.left {\n left: 1%;" in app.CSS | |
| assert ".jury-benches.right {\n right: 1%;" in app.CSS | |
| assert ".jury-benches.left {\n left: .5%;" in app.CSS | |
| assert ".jury-benches.right {\n right: .5%;" in app.CSS | |
| def test_courtroom_threads_show_model_input_output_on_hover_and_click(): | |
| html = app.render_court([_event_with_lower_tab_data()], started=True) | |
| assert "tooltip-io-label'>Input" in html | |
| assert "tooltip-io-label'>Output" in html | |
| assert "Click to open full thread" in html | |
| assert "class='ai-thread-modal'" in html | |
| assert "thread-block'>SYSTEM:" in html | |
| assert "The jurors compare E1 and state their votes." in html | |
| assert "href='#ai-thread-karl-marx'" in html | |
| def test_courtroom_renders_historical_judge_and_juror_assets(): | |
| html = app.render_court([_event_with_lower_tab_data()], started=True) | |
| assert "Marcus Aurelius" in html | |
| assert "assets/characters/marcus-aurelius.png" in html | |
| for name, image in app.JUROR_IMAGES.items(): | |
| assert name in html | |
| assert image in html | |
| assert html.count("class='juror-portrait'") == 6 | |
| def test_courtroom_renders_foreground_fences_and_judge_table_above_characters(): | |
| html = app.render_court([_event_with_lower_tab_data()], started=True) | |
| assert html.count("assets/foreground/foregroundFence.png") == 2 | |
| assert "assets/foreground/JudgeTable.png" in html | |
| assert html.find("class='puppet judge") < html.find("class='foreground-props'") | |
| assert ".foreground-props {\n position: absolute;\n inset: 0;\n z-index: 13;" in app.CSS | |
| assert ".puppet {\n --skin: #c99257;" in app.CSS | |
| assert "z-index: 8;" in app.CSS | |
| def test_foreground_prop_assets_have_real_transparency(): | |
| for path in [ | |
| Path("assets/foreground/foregroundFence.png"), | |
| Path("assets/foreground/JudgeTable.png"), | |
| ]: | |
| alpha = Image.open(path).convert("RGBA").getchannel("A") | |
| histogram = alpha.histogram() | |
| assert histogram[0] > 0, f"{path} has no fully transparent pixels" | |
| assert histogram[255] > 0, f"{path} has no fully opaque prop pixels" | |
| def test_latest_speaker_sets_stage_class_and_speech_bubble(): | |
| html = app.render_court([_speaker_event("Advocate Auric", phase="claims")], started=True) | |
| assert "speaker-auric" in html | |
| assert "class='speech-bubble'" in html | |
| assert "Advocate Auric has the visible floor." in html | |
| assert "puppet auric active walking" in html | |
| assert "puppet sable active" not in html | |
| def test_individual_juror_can_be_active_speaker(): | |
| event = TrialEvent( | |
| phase="deliberation", | |
| title="Juror Karl Marx Votes", | |
| body=app.JUROR_PERSONAS["Karl Marx"], | |
| turns=[ | |
| AgentTurn( | |
| agent="Karl Marx", | |
| role="juror", | |
| content="Liable. E1 carries the record.", | |
| model="nvidia/Nemotron-Orchestrator-8B", | |
| confidence=0.86, | |
| input="SYSTEM:\nJury JSON prompt.", | |
| ) | |
| ], | |
| votes=[ | |
| JurorVote( | |
| juror="Karl Marx", | |
| persona=app.JUROR_PERSONAS["Karl Marx"], | |
| vote="liable", | |
| reason="E1 carries the record.", | |
| evidence_ids=["E1"], | |
| ) | |
| ], | |
| ) | |
| html = app.render_court([event], started=True) | |
| assert "speaker-karl-marx" in html | |
| assert "<a class='juror active'" in html | |
| assert "Liable. E1 carries the record." in html | |
| def test_lawyer_movement_css_is_speaker_specific_not_phase_wide(): | |
| assert ".speaker-auric .puppet.auric" in app.CSS | |
| assert ".speaker-sable .puppet.sable" in app.CSS | |
| assert ".phase-claims .puppet.auric" not in app.CSS | |
| assert ".phase-opening .puppet.sable" not in app.CSS | |
| def test_closed_book_is_smaller_and_key_characters_are_lowered(): | |
| assert ".episode-book.closed {\n top: 61%;\n width: min(163px, 20vw);" in app.CSS | |
| assert ".puppet.judge {\n left: 50%;\n top: 56%;" in app.CSS | |
| assert ".puppet.auric {\n left: 24%;\n top: 87%;" in app.CSS | |
| assert ".speaker-auric .puppet.auric {\n left: 43%;\n top: 91%;" in app.CSS | |
| assert ".puppet.auditor {\n left: 71%;\n top: 80%;" in app.CSS | |
| assert ".episode-book.closed {\n top: 750px;\n width: 140px;" in app.CSS | |
| assert ".puppet.judge {\n top: 717px;" in app.CSS | |
| assert ".puppet.auric {\n left: 20%;\n top: 970px;" in app.CSS | |
| assert ".puppet.auditor {\n left: 78%;\n top: 860px;" in app.CSS | |
| def test_run_ui_yields_five_outputs_without_download_status(monkeypatch): | |
| event = _event_with_lower_tab_data() | |
| monkeypatch.setattr(app, "get_events", lambda request: iter([event])) | |
| outputs = list(app.run_ui("Trial of Socrates", "", "", "swift", True)) | |
| assert outputs | |
| assert all(len(output) == 5 for output in outputs) | |
| assert outputs[1][-1] == "Step 1: Jury weighs the record" | |
| assert outputs[-1][-1] == "Verdict sealed." | |
| assert "download" not in outputs[-1][-1].lower() | |
| def test_run_ui_stops_with_model_unavailable_error(monkeypatch): | |
| def broken_events(request): | |
| raise RuntimeError("Marcus Aurelius unavailable: offline") | |
| yield | |
| monkeypatch.setattr(app, "get_events", broken_events) | |
| outputs = list(app.run_ui("Trial of Socrates", "", "", "swift", True)) | |
| assert outputs[-1][-1] == "Model response required. Trial stopped: Marcus Aurelius unavailable: offline" | |
| assert "Claimant score" not in outputs[-1][0] | |
| def test_court_renders_sound_toggle(): | |
| html = app.render_court([]) | |
| assert "sound-toggle" in html | |
| assert "aria-label='Toggle sound'" in html | |
| assert "aria-pressed='false'" in html | |
| def test_audio_controller_has_score_breathing_and_mute_toggle(): | |
| assert "SCORE_BREATH_INTERVAL_MS = 20000" in app.APP_JS | |
| assert "SCORE_BREATH_DURATION_MS = 5000" in app.APP_JS | |
| assert "toggleMuted()" in app.APP_JS | |
| assert "this.fadeScore(SCORE_QUIET_VOLUME, halfDuration" in app.APP_JS | |
| def test_courtroom_background_has_no_overlay_or_character_shadow(): | |
| assert "background: #141413 !important;" in app.CSS | |
| assert "background-color: #141413 !important;" in app.CSS | |
| assert "cover fixed no-repeat" not in app.CSS | |
| assert ".court-episode-stage::before {\n content: \"\";\n display: none;" in app.CSS | |
| assert ".court-episode-stage::after {\n content: \"\";\n display: none;" in app.CSS | |
| assert "url('/gradio_api/file=assets/background/CourtRoom.png') center center / 100% 100% no-repeat" in app.CSS | |
| assert "filter: drop-shadow(0 12px 14px" not in app.CSS | |
| assert "filter: drop-shadow(0 8px 10px" not in app.CSS | |
| def test_synthetic_stage_props_do_not_tint_background(): | |
| assert ".bench-front {\n display: none;" in app.CSS | |
| assert ".trial-floor-mark {\n display: none;" in app.CSS | |
| assert ".gallery-benches {\n display: none;" in app.CSS | |
| assert ".prop-label {\n display: none;" in app.CSS | |
| assert ".counsel-table" in app.CSS | |
| assert "background: transparent;\n box-shadow: none;" in app.CSS | |
| assert ".witness-area" in app.CSS | |