GameConfigIdea / ui_tabs /narrative_engine_tab.py
kwabs22
Port changes from duplicate space to original
9328e91
"""
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.
""")