Spaces:
Running on Zero
Running on Zero
| """ | |
| Narrative Engine Tab - Layered storytelling with mystery iceberg. | |
| Uses the Narrative Engine for generating narratives with: | |
| - 5-layer mystery iceberg (surface → abyss) | |
| - Character generation with psychological depth | |
| - Demo stories across genres | |
| - Custom narrative creation with mystery layers | |
| """ | |
| import gradio as gr | |
| import json | |
| from typing import Dict, Any, List | |
| # Import narrative engine components | |
| from narrativeengine_hfport import ( | |
| NarrativeEngine, NarrativeMode, NodeType, DepthLevel, | |
| get_demo, list_demos, DEMO_STORIES, | |
| CharacterGenerator, GeneratedCharacter | |
| ) | |
| def format_story_setup(engine: NarrativeEngine) -> str: | |
| """Format story setup as markdown.""" | |
| if not engine.story_setup: | |
| return "No story setup available." | |
| setup = engine.story_setup | |
| return f"""## {setup.title} | |
| ### Setting | |
| **{setup.place['name']}**: {setup.place['description']} | |
| **Key Locations**: {', '.join(setup.place.get('key_locations', []))} | |
| ### Time | |
| {setup.time['period']} - {setup.time['specific_moment']} | |
| *{setup.time['tension']}* | |
| ### Protagonist | |
| **{setup.protagonist['name']}**: {setup.protagonist['description']} | |
| - **Motivation**: {setup.protagonist['motivation']} | |
| - **Flaw**: {setup.protagonist['flaw']} | |
| ### Hook | |
| > {setup.hook} | |
| ### Stakes | |
| {setup.stakes} | |
| """ | |
| def format_characters(engine: NarrativeEngine) -> str: | |
| """Format characters as markdown.""" | |
| if not engine.characters: | |
| return "No characters registered." | |
| lines = ["## Characters\n"] | |
| for char_id, char in engine.characters.items(): | |
| lines.append(f"### {char.name}") | |
| lines.append(f"**Role**: {char.role}") | |
| lines.append(f"**Background**: {char.background}") | |
| if char.wants: | |
| lines.append("\n**Wants**:") | |
| for want_id, want in char.wants.items(): | |
| lines.append(f"- {want['description']}") | |
| lines.append("") | |
| return "\n".join(lines) | |
| def format_clues(engine: NarrativeEngine) -> str: | |
| """Format story clues as markdown.""" | |
| if not engine.story_clues: | |
| return "No clues planted." | |
| lines = ["## Story Clues\n"] | |
| for clue in sorted(engine.story_clues, key=lambda c: c.planted_at): | |
| visibility = "Obvious" if clue.noticeability > 0.6 else "Subtle" if clue.noticeability > 0.3 else "Hidden" | |
| lines.append(f"- **[{clue.clue_type.upper()}]** ({visibility}) {clue.content}") | |
| return "\n".join(lines) | |
| def format_iceberg(iceberg: Dict[str, Any]) -> str: | |
| """Format iceberg layers as markdown.""" | |
| if not iceberg: | |
| return "No iceberg data available." | |
| lines = ["## Narrative Iceberg\n"] | |
| emoji_map = { | |
| "surface": "Surface", | |
| "shallow": "Shallow", | |
| "mid": "Mid-depth", | |
| "deep": "Deep", | |
| "abyss": "Abyss" | |
| } | |
| for level, data in iceberg.items(): | |
| label = emoji_map.get(level, level.title()) | |
| lines.append(f"### {label}: {data['name']}") | |
| for item in data['content'][:5]: | |
| lines.append(f"- {item}") | |
| if len(data['content']) > 5: | |
| lines.append(f"- *... and {len(data['content']) - 5} more*") | |
| lines.append("") | |
| return "\n".join(lines) | |
| def format_generated_character(char: GeneratedCharacter) -> Dict[str, Any]: | |
| """Format generated character as dict.""" | |
| return { | |
| "id": char.id, | |
| "name": char.name, | |
| "role": char.role, | |
| "background": char.background, | |
| "skill": char.skill, | |
| "trait": char.trait, | |
| "equipment": char.equipment, | |
| "context": char.context, | |
| "psychological_profile": char.psychological_profile, | |
| "personality_traits": char.personality_traits | |
| } | |
| def create_narrative_engine_tab(): | |
| """Create the Narrative Engine tab.""" | |
| with gr.Tab("Narrative Engine"): | |
| gr.Markdown("""## Narrative Engine - Layered Mystery & Character Systems | |
| A comprehensive narrative generation system with 7 storytelling paradigms: | |
| - **Struggle Propagation** - Character wants with butterfly effects | |
| - **Mystery Iceberg** - 5-layer depth (Surface to Abyss) | |
| - **Reveal Ripple Observer** - Information timing control | |
| - **Emotional Mechanics** - Setup, Trigger, Sustain, Kill patterns | |
| """) | |
| with gr.Tabs(): | |
| # === TAB 1: DEMO STORIES === | |
| with gr.TabItem("Demo Stories"): | |
| gr.Markdown("### Pre-built Narrative Demonstrations") | |
| gr.Markdown("Explore 6 complete story structures with mystery layers, character dynamics, and planted clues.") | |
| with gr.Row(): | |
| demo_dropdown = gr.Dropdown( | |
| choices=[ | |
| ("The Hartfield Affair (Political Thriller)", "hartfield"), | |
| ("The Vanishing Code (Sci-Fi)", "scifi"), | |
| ("The Last Performance (Romance/Drama)", "romance"), | |
| ("The Gilded Cage (Heist)", "heist"), | |
| ("The Hollow Season (Folk Horror)", "horror"), | |
| ("The Redemption (Classic Arc)", "redemption") | |
| ], | |
| label="Select Demo Story", | |
| value="hartfield" | |
| ) | |
| demo_btn = gr.Button("Generate Story", variant="primary") | |
| with gr.Row(): | |
| with gr.Column(): | |
| demo_narrative = gr.Markdown(label="Story Narrative") | |
| with gr.Column(): | |
| demo_iceberg = gr.Markdown(label="Narrative Iceberg") | |
| demo_json = gr.JSON(label="Full Story Data") | |
| def run_demo_story(story_id: str) -> tuple: | |
| """Run a demo story.""" | |
| try: | |
| engine = get_demo(story_id) | |
| # Execute sample actions | |
| action_map = { | |
| "hartfield": [("detective", "acquire", "letter"), ("mayor", "pressure", "testimony")], | |
| "scifi": [("engineer", "reveal", "manifest"), ("ai", "compromise", "escape_pods")], | |
| "romance": [("isabella", "reveal", "letters"), ("lucia", "acquire", "role")], | |
| "heist": [("saint", "acquire", "security"), ("viktor", "reveal", "painting")], | |
| "horror": [("rowan", "reveal", "ritual"), ("agnes", "pressure", "mark")], | |
| "redemption": [("hero", "reveal", "soul"), ("emperor", "pressure", "darkside")] | |
| } | |
| for char, action, target in action_map.get(story_id, []): | |
| engine.execute_action(char, action, target) | |
| iceberg = engine.generate_full_iceberg() | |
| setup_md = format_story_setup(engine) | |
| chars_md = format_characters(engine) | |
| clues_md = format_clues(engine) | |
| iceberg_md = format_iceberg(iceberg) | |
| narrative_md = f"{setup_md}\n\n---\n\n{chars_md}\n\n---\n\n{clues_md}" | |
| full_data = engine.to_dict() | |
| return narrative_md, iceberg_md, full_data | |
| except Exception as e: | |
| error_msg = f"Error: {str(e)}" | |
| return error_msg, error_msg, {"error": str(e)} | |
| demo_btn.click( | |
| fn=run_demo_story, | |
| inputs=[demo_dropdown], | |
| outputs=[demo_narrative, demo_iceberg, demo_json] | |
| ) | |
| # === TAB 2: CHARACTER GENERATOR === | |
| with gr.TabItem("Character Generator"): | |
| gr.Markdown("### Generate Characters with Psychological Depth") | |
| with gr.Row(): | |
| char_context = gr.Dropdown( | |
| choices=[ | |
| ("Detective/Mystery", "detective"), | |
| ("Action/Adventure", "action"), | |
| ("Horror", "horror"), | |
| ("Fantasy", "fantasy"), | |
| ("Sci-Fi", "sci_fi"), | |
| ("Romance", "romance"), | |
| ("Spy/Espionage", "spy"), | |
| ("Crime/Heist", "crime") | |
| ], | |
| label="Character Context", | |
| value="detective" | |
| ) | |
| char_count = gr.Slider( | |
| minimum=1, maximum=10, value=3, step=1, | |
| label="Number of Characters" | |
| ) | |
| char_btn = gr.Button("Generate Characters", variant="primary") | |
| char_output = gr.JSON(label="Generated Characters") | |
| def generate_characters(context: str, count: int) -> Dict[str, Any]: | |
| """Generate characters.""" | |
| try: | |
| generator = CharacterGenerator(context) | |
| characters = generator.generate_multiple(int(count)) | |
| return { | |
| "context": context, | |
| "count": len(characters), | |
| "characters": [format_generated_character(c) for c in characters] | |
| } | |
| except Exception as e: | |
| return {"error": str(e)} | |
| char_btn.click( | |
| fn=generate_characters, | |
| inputs=[char_context, char_count], | |
| outputs=[char_output] | |
| ) | |
| # === TAB 3: CUSTOM NARRATIVE === | |
| with gr.TabItem("Custom Narrative"): | |
| gr.Markdown("### Create Your Own Narrative Structure") | |
| with gr.Row(): | |
| with gr.Column(): | |
| custom_title = gr.Textbox(label="Story Title", value="Untitled Story") | |
| custom_setting = gr.Textbox(label="Setting", value="A city on the edge of change") | |
| custom_mode = gr.Dropdown( | |
| choices=[ | |
| "Struggle (Plot-driven)", | |
| "Compressed (Emotional density)", | |
| "Meditative (Presence-based)", | |
| "Hybrid (Combined)" | |
| ], | |
| label="Narrative Mode", | |
| value="Struggle (Plot-driven)" | |
| ) | |
| with gr.Column(): | |
| gr.Markdown("**Protagonist**") | |
| protag_name = gr.Textbox(label="Name", value="Alex") | |
| protag_want = gr.Textbox(label="What they want", value="To find the truth") | |
| protag_struggle = gr.Textbox(label="Their struggle", value="Haunted by the past") | |
| with gr.Column(): | |
| gr.Markdown("**Antagonist**") | |
| antag_name = gr.Textbox(label="Name", value="Morgan") | |
| antag_want = gr.Textbox(label="What they want", value="To keep secrets buried") | |
| custom_btn = gr.Button("Generate Narrative", variant="primary") | |
| with gr.Row(): | |
| custom_narrative = gr.Markdown(label="Generated Narrative") | |
| custom_iceberg = gr.Markdown(label="Iceberg Layers") | |
| custom_json = gr.JSON(label="Full Data") | |
| def create_custom_narrative(title, protag_name_val, protag_want_val, protag_struggle_val, | |
| antag_name_val, antag_want_val, setting, mode) -> tuple: | |
| """Create custom narrative.""" | |
| try: | |
| engine = NarrativeEngine() | |
| mode_map = { | |
| "Struggle (Plot-driven)": NarrativeMode.STRUGGLE, | |
| "Compressed (Emotional density)": NarrativeMode.COMPRESSED, | |
| "Meditative (Presence-based)": NarrativeMode.MEDITATIVE, | |
| "Hybrid (Combined)": NarrativeMode.HYBRID | |
| } | |
| engine.set_mode(mode_map.get(mode, NarrativeMode.STRUGGLE)) | |
| engine.register_character("protagonist", protag_name_val, "protagonist") | |
| engine.register_character("antagonist", antag_name_val, "antagonist") | |
| engine.add_want("protagonist", "main_goal", protag_want_val) | |
| engine.add_want("antagonist", "main_goal", antag_want_val) | |
| engine.create_node("objective", NodeType.RESOURCE, "The Objective", | |
| description=f"What both {protag_name_val} and {antag_name_val} seek") | |
| engine.create_node("setting", NodeType.LOCATION, setting) | |
| engine.create_node("deadline", NodeType.TIME_WINDOW, "The Deadline", | |
| metadata={"remaining_time": 24, "unit": "hours"}) | |
| engine.connect_nodes("objective", "setting") | |
| engine.connect_nodes("deadline", "objective") | |
| engine.execute_action("protagonist", "reveal", "objective", | |
| narrative_context=protag_struggle_val) | |
| engine.execute_action("antagonist", "pressure", "objective", | |
| narrative_context="opposing the protagonist") | |
| iceberg = engine.generate_full_iceberg() | |
| narrative_lines = [f"# {title}\n"] | |
| narrative_lines.append(f"**Setting**: {setting}\n") | |
| narrative_lines.append(f"**Mode**: {mode}\n") | |
| narrative_lines.append("\n## Characters\n") | |
| narrative_lines.append(f"**{protag_name_val}** (Protagonist)") | |
| narrative_lines.append(f"- Want: {protag_want_val}") | |
| narrative_lines.append(f"- Struggle: {protag_struggle_val}\n") | |
| narrative_lines.append(f"**{antag_name_val}** (Antagonist)") | |
| narrative_lines.append(f"- Want: {antag_want_val}\n") | |
| narrative_lines.append("\n## Narrative Events\n") | |
| for entry in engine.narrative_log: | |
| narrative_lines.append(f"- {entry['surface_event']}") | |
| narrative_md = "\n".join(narrative_lines) | |
| iceberg_md = format_iceberg(iceberg) | |
| return narrative_md, iceberg_md, engine.to_dict() | |
| except Exception as e: | |
| error_msg = f"Error: {str(e)}" | |
| return error_msg, error_msg, {"error": str(e)} | |
| custom_btn.click( | |
| fn=create_custom_narrative, | |
| inputs=[custom_title, protag_name, protag_want, protag_struggle, | |
| antag_name, antag_want, custom_setting, custom_mode], | |
| outputs=[custom_narrative, custom_iceberg, custom_json] | |
| ) | |
| # === TAB 4: MYSTERY LAYERS === | |
| with gr.TabItem("Mystery Layers"): | |
| gr.Markdown("### Add Mystery Depth to Any Event") | |
| gr.Markdown("Transform a simple surface event into layered narrative with multiple interpretations, hidden connections, and unresolved threads.") | |
| with gr.Row(): | |
| with gr.Column(): | |
| mystery_event = gr.Textbox( | |
| label="Surface Event", | |
| value="The mayor suddenly resigns from office", | |
| lines=2 | |
| ) | |
| with gr.Row(): | |
| mystery_char1 = gr.Textbox(label="Character 1", value="The Mayor") | |
| mystery_char2 = gr.Textbox(label="Character 2", value="The Reporter") | |
| mystery_interps = gr.Slider( | |
| minimum=2, maximum=5, value=3, step=1, | |
| label="Number of Interpretations" | |
| ) | |
| mystery_btn = gr.Button("Add Mystery Layers", variant="primary") | |
| mystery_output = gr.JSON(label="Layered Event") | |
| def add_mystery_layers(surface_event, char1, char2, num_interps) -> Dict[str, Any]: | |
| """Add mystery layers to event.""" | |
| try: | |
| engine = NarrativeEngine() | |
| engine.register_character("char1", char1) | |
| engine.register_character("char2", char2) | |
| layered = engine.mystery.add_mystery_layers( | |
| surface_event, | |
| character_ids=["char1", "char2"], | |
| num_interpretations=int(num_interps), | |
| include_hidden_connection=True, | |
| include_unresolved_thread=True | |
| ) | |
| iceberg = engine.mystery.generate_iceberg_summary(layered.id) | |
| return { | |
| "surface_event": surface_event, | |
| "characters": [char1, char2], | |
| "interpretations": [ | |
| { | |
| "reading": i.reading, | |
| "description": i.description, | |
| "plausibility": round(i.plausibility, 2), | |
| "darkness_level": round(i.darkness_level, 2) | |
| } for i in layered.interpretations | |
| ], | |
| "evidence": [ | |
| { | |
| "type": e.evidence_type, | |
| "content": e.content, | |
| "is_red_herring": e.is_red_herring | |
| } for e in layered.evidence | |
| ], | |
| "hidden_connections": [ | |
| { | |
| "type": c.connection_type, | |
| "description": c.description | |
| } for c in layered.hidden_connections | |
| ], | |
| "unresolved_threads": [ | |
| { | |
| "element": t.element, | |
| "speculation_hooks": t.speculation_hooks | |
| } for t in layered.unresolved_threads | |
| ], | |
| "iceberg": iceberg | |
| } | |
| except Exception as e: | |
| return {"error": str(e)} | |
| mystery_btn.click( | |
| fn=add_mystery_layers, | |
| inputs=[mystery_event, mystery_char1, mystery_char2, mystery_interps], | |
| outputs=[mystery_output] | |
| ) | |
| gr.Markdown(""" | |
| --- | |
| **Narrative Engine** integrates 7 storytelling paradigms for rich, layered narratives. | |
| All generation is deterministic - no external APIs required. | |
| """) | |