ShubhamSetia commited on
Commit
6bc4afd
·
1 Parent(s): 4b1a6e9

feat: add puppet theater session models

Browse files
.python-version CHANGED
@@ -0,0 +1 @@
 
 
1
+ 3.11
app.py CHANGED
@@ -2,6 +2,8 @@ from html import escape
2
 
3
  import gradio as gr
4
 
 
 
5
 
6
  EMPTY_STAGE = """
7
  <div class="stage-placeholder">
@@ -29,50 +31,128 @@ CUSTOM_CSS = """
29
  }
30
  .stage-live {
31
  background: linear-gradient(180deg, #1f2937 0%, #7c2d12 100%);
 
 
32
  }
33
  .stage-lights {
34
  font-size: 1.8rem;
35
  font-weight: 700;
 
36
  }
37
  .stage-floor {
38
  max-width: 48rem;
39
  font-size: 1rem;
40
  line-height: 1.5;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
  """
43
 
44
 
45
- def create_show(premise: str, session: dict | None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  premise = premise.strip()
47
  if not premise:
48
- session = {"premise": "", "created": False}
49
  return (
50
- session,
51
  EMPTY_STAGE,
52
  "No premise yet. Add a premise to raise the curtain.",
53
  )
54
 
55
- session = {"premise": premise, "created": True}
56
- safe_premise = escape(premise)
57
- stage = f"""
58
- <div class="stage-placeholder stage-live">
59
- <div class="stage-lights">Curtain Up</div>
60
- <div class="stage-floor">Premise: {safe_premise}</div>
61
- </div>
62
- """
63
- transcript = (
64
- "Director: The puppet cast is waiting in the wings.\n\n"
65
- "Stage Manager: This shell is ready for the deterministic skit engine."
66
- )
67
- return session, stage, transcript
68
 
69
 
70
  def reset_show():
71
- return {"premise": "", "created": False}, "", EMPTY_STAGE, EMPTY_TRANSCRIPT
72
 
73
 
74
- with gr.Blocks(title="AI Puppet Theater") as demo:
75
- session_state = gr.State({"premise": "", "created": False})
76
 
77
  gr.Markdown(
78
  """
@@ -115,4 +195,4 @@ with gr.Blocks(title="AI Puppet Theater") as demo:
115
 
116
 
117
  if __name__ == "__main__":
118
- demo.launch(css=CUSTOM_CSS)
 
2
 
3
  import gradio as gr
4
 
5
+ from puppet_theater import TheaterSession, create_show_from_premise
6
+
7
 
8
  EMPTY_STAGE = """
9
  <div class="stage-placeholder">
 
31
  }
32
  .stage-live {
33
  background: linear-gradient(180deg, #1f2937 0%, #7c2d12 100%);
34
+ align-items: stretch;
35
+ text-align: left;
36
  }
37
  .stage-lights {
38
  font-size: 1.8rem;
39
  font-weight: 700;
40
+ text-align: center;
41
  }
42
  .stage-floor {
43
  max-width: 48rem;
44
  font-size: 1rem;
45
  line-height: 1.5;
46
+ margin: 0 auto;
47
+ text-align: center;
48
+ }
49
+ .actor-row {
50
+ display: grid;
51
+ grid-template-columns: repeat(3, minmax(0, 1fr));
52
+ gap: 0.75rem;
53
+ margin-top: 1rem;
54
+ }
55
+ .actor-card {
56
+ border: 1px solid rgba(255, 255, 255, 0.28);
57
+ border-radius: 8px;
58
+ background: rgba(255, 255, 255, 0.1);
59
+ padding: 0.85rem;
60
+ }
61
+ .actor-avatar {
62
+ font-size: 1.8rem;
63
+ }
64
+ .actor-name {
65
+ font-weight: 700;
66
+ margin-top: 0.25rem;
67
+ }
68
+ .actor-detail {
69
+ font-size: 0.9rem;
70
+ line-height: 1.35;
71
+ opacity: 0.92;
72
+ margin-top: 0.35rem;
73
+ }
74
+ .beat-counter {
75
+ margin-top: 1rem;
76
+ text-align: center;
77
+ font-weight: 700;
78
  }
79
  """
80
 
81
 
82
+ def render_stage(session: TheaterSession | None) -> str:
83
+ if session is None:
84
+ return EMPTY_STAGE
85
+
86
+ actor_cards = []
87
+ for actor in session.actors:
88
+ tools = ", ".join(actor.tools) if actor.tools else "none"
89
+ actor_cards.append(
90
+ f"""
91
+ <div class="actor-card">
92
+ <div class="actor-avatar">{escape(actor.avatar)}</div>
93
+ <div class="actor-name">{escape(actor.name)}</div>
94
+ <div class="actor-detail"><strong>Goal:</strong> {escape(actor.goal)}</div>
95
+ <div class="actor-detail"><strong>Style:</strong> {escape(actor.speaking_style)}</div>
96
+ <div class="actor-detail"><strong>Tools:</strong> {escape(tools)}</div>
97
+ </div>
98
+ """
99
+ )
100
+
101
+ return f"""
102
+ <div class="stage-placeholder stage-live">
103
+ <div class="stage-lights">{escape(session.show_title)}</div>
104
+ <div class="stage-floor">
105
+ <strong>Setting:</strong> {escape(session.setting)}<br />
106
+ <strong>Premise:</strong> {escape(session.premise)}
107
+ </div>
108
+ <div class="actor-row">
109
+ {''.join(actor_cards)}
110
+ </div>
111
+ <div class="beat-counter">Beat {session.beat_index} of {session.max_beats}</div>
112
+ </div>
113
+ """
114
+
115
+
116
+ def render_notes(session: TheaterSession | None) -> str:
117
+ if session is None:
118
+ return EMPTY_TRANSCRIPT
119
+
120
+ transcript_lines = [
121
+ "Transcript:",
122
+ "No puppet lines yet. The first beat will be added in the next milestone.",
123
+ ]
124
+ if session.transcript:
125
+ transcript_lines = ["Transcript:"]
126
+ for beat in session.transcript:
127
+ transcript_lines.append(f"{beat.speaker}: {beat.line}")
128
+
129
+ director_lines = ["", "Director Log:"]
130
+ director_lines.extend(f"- {entry}" for entry in session.director_log)
131
+ director_lines.extend(["", "Trace Events:"])
132
+ director_lines.extend(f"- {entry}" for entry in session.trace_events)
133
+
134
+ return "\n".join(transcript_lines + director_lines)
135
+
136
+
137
+ def create_show(premise: str, session: TheaterSession | None):
138
  premise = premise.strip()
139
  if not premise:
 
140
  return (
141
+ None,
142
  EMPTY_STAGE,
143
  "No premise yet. Add a premise to raise the curtain.",
144
  )
145
 
146
+ session = create_show_from_premise(premise)
147
+ return session, render_stage(session), render_notes(session)
 
 
 
 
 
 
 
 
 
 
 
148
 
149
 
150
  def reset_show():
151
+ return None, "", EMPTY_STAGE, EMPTY_TRANSCRIPT
152
 
153
 
154
+ with gr.Blocks(title="AI Puppet Theater") as app:
155
+ session_state = gr.State(None)
156
 
157
  gr.Markdown(
158
  """
 
195
 
196
 
197
  if __name__ == "__main__":
198
+ app.launch(css=CUSTOM_CSS)
puppet_theater/__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from puppet_theater.models import Actor, Beat, TheaterSession
2
+ from puppet_theater.session import create_show_from_premise
3
+
4
+ __all__ = [
5
+ "Actor",
6
+ "Beat",
7
+ "TheaterSession",
8
+ "create_show_from_premise",
9
+ ]
puppet_theater/models.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass, field
2
+
3
+
4
+ @dataclass
5
+ class Actor:
6
+ name: str
7
+ avatar: str
8
+ goal: str
9
+ secret: str
10
+ speaking_style: str
11
+ tools: list[str] = field(default_factory=list)
12
+
13
+
14
+ @dataclass
15
+ class Beat:
16
+ speaker: str
17
+ line: str
18
+ emotion: str
19
+ gesture: str
20
+ stage_effect: str
21
+ tool_request: str | None = None
22
+
23
+
24
+ @dataclass
25
+ class TheaterSession:
26
+ show_title: str
27
+ premise: str
28
+ setting: str
29
+ actors: list[Actor]
30
+ beat_index: int = 0
31
+ max_beats: int = 6
32
+ transcript: list[Beat] = field(default_factory=list)
33
+ props: list[str] = field(default_factory=list)
34
+ director_log: list[str] = field(default_factory=list)
35
+ trace_events: list[str] = field(default_factory=list)
36
+ finale_requested: bool = False
puppet_theater/session.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from puppet_theater.models import Actor, TheaterSession
2
+
3
+
4
+ def _clean_premise(premise: str) -> str:
5
+ cleaned = " ".join(premise.strip().split())
6
+ return cleaned or "A mysterious puppet show with no premise"
7
+
8
+
9
+ def _title_from_premise(premise: str) -> str:
10
+ words = [word.strip(".,!?;:()[]{}\"'") for word in premise.split()]
11
+ keywords = [word.title() for word in words if len(word.strip(".,!?;:()[]{}\"'")) > 3]
12
+ if not keywords:
13
+ return "The Tiny Improv"
14
+ return f"The {' '.join(keywords[:4])}"
15
+
16
+
17
+ def _setting_from_premise(premise: str) -> str:
18
+ lowered = premise.lower()
19
+ if "moon" in lowered or "space" in lowered or "star" in lowered:
20
+ return "a cardboard moon base with glittery stars and a squeaky hatch"
21
+ if "castle" in lowered or "dragon" in lowered or "wizard" in lowered:
22
+ return "a shoebox castle with velvet curtains and a suspicious tower"
23
+ if "detective" in lowered or "mystery" in lowered:
24
+ return "a rainy cardboard alley lit by one dramatic desk lamp"
25
+ if "kitchen" in lowered or "toaster" in lowered or "chef" in lowered:
26
+ return "a tiny kitchen counter where every appliance has stage fright"
27
+ return "a pocket-sized improv stage with painted flats and a wobbly spotlight"
28
+
29
+
30
+ def create_show_from_premise(premise: str) -> TheaterSession:
31
+ cleaned_premise = _clean_premise(premise)
32
+ show_title = _title_from_premise(cleaned_premise)
33
+ setting = _setting_from_premise(cleaned_premise)
34
+
35
+ actors = [
36
+ Actor(
37
+ name="Pip the Director",
38
+ avatar="🎬",
39
+ goal="Keep the scene moving toward a crisp finale.",
40
+ secret="Has already misplaced the final cue card.",
41
+ speaking_style="brisk, theatrical, and slightly overconfident",
42
+ tools=["spotlight", "cue_cards"],
43
+ ),
44
+ Actor(
45
+ name="Mina Moonbutton",
46
+ avatar="🌙",
47
+ goal="Find the emotional truth hiding inside the premise.",
48
+ secret="Believes every prop is personally judging her.",
49
+ speaking_style="earnest, poetic, and prone to dramatic pauses",
50
+ tools=["monologue", "soft_lights"],
51
+ ),
52
+ Actor(
53
+ name="Bolt McJiggle",
54
+ avatar="🧰",
55
+ goal="Turn every problem into a practical stage gag.",
56
+ secret="Is secretly building a confetti finale backstage.",
57
+ speaking_style="punchy, practical, and full of suspicious confidence",
58
+ tools=["props", "sound_effects"],
59
+ ),
60
+ ]
61
+
62
+ director_log = [
63
+ "Director created a deterministic six-beat show plan.",
64
+ f"Setting selected: {setting}.",
65
+ "Three puppet actors are waiting for the first beat.",
66
+ ]
67
+ trace_events = [
68
+ "show_created",
69
+ "actors_created:3",
70
+ "director_plan_created",
71
+ ]
72
+
73
+ return TheaterSession(
74
+ show_title=show_title,
75
+ premise=cleaned_premise,
76
+ setting=setting,
77
+ actors=actors,
78
+ beat_index=0,
79
+ max_beats=6,
80
+ transcript=[],
81
+ props=[],
82
+ director_log=director_log,
83
+ trace_events=trace_events,
84
+ finale_requested=False,
85
+ )