""" 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. """)