bedtime-stories / app.py
gauravgmi's picture
Create app.py
8909bb1 verified
"""
bedtime_stories_gradio.py Β· interactive 3-scene bedtime stories with emojis πŸŽ‰
"""
import os, re
import openai
import gradio as gr
from enrich_idea import generate_enriched_idea # prompt-enricher
# ---------- CONFIG ---------------------------------------------------------
openai.api_key = os.getenv("OPENAI_API_KEY")
MODEL = "gpt-4o-mini"
SCENE_TEMPLATE = """
You are a children’s storyteller. Write **SCENE {scene_no}/3** of an
age-5-to-10 bedtime story (β‰ˆ 150 words).
The child’s idea is: **β€œ{idea}”**.
πŸ‘‰ *Make sure this idea is central to the scene and appears within the first two sentences.*
### Style rules
1. Use vivid, friendly language and **plenty of relevant emojis** (πŸ˜€πŸ‰πŸͺπŸŒŸπŸš€ etc.).
2. Keep sentences short and clear.
3. Leave a blank line between paragraphs.
4. **Scenes 1 & 2:** end with *exactly two* numbered choices (β€œ1.” and β€œ2.”).
5. **Scene 3:** wrap up the tale (no choices). Do **not** write β€œThe end.” in scenes 1 or 2.
Story so far:
\"\"\"{story_so_far}\"\"\"
`last_choice` = "{last_choice}"
If `last_choice` == "N/A", this is the opening scene; otherwise, nod to the child’s last choice in one friendly sentence before continuing.
"""
# ---------- LLM HELPERS ----------------------------------------------------
def call_llm(prompt: str) -> str:
resp = openai.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=0.4,
max_tokens=350,
)
return resp.choices[0].message.content.strip()
def generate_scene(scene_no: int,
story_so_far: str,
last_choice: str,
idea: str) -> str:
prompt = SCENE_TEMPLATE.format(
scene_no=scene_no,
story_so_far=story_so_far,
last_choice=last_choice,
idea=idea,
)
return call_llm(prompt)
# ---------- UTILS ----------------------------------------------------------
def _strip_early_ending(text: str, scene_no: int) -> str:
if scene_no < 3:
text = re.sub(r"^\s*(The\s+end\.?)\s*$", "", text, flags=re.I | re.M)
return text
def _extract_options(scene_text: str):
lines = [ln.strip() for ln in scene_text.splitlines()]
opts = [ln for ln in lines if ln.startswith(("1.", "2."))]
return opts if len(opts) == 2 else ["1. Continue bravely 🌟", "2. Take a quiet turn πŸ’€"]
def _maybe_enrich(raw: str) -> str:
if len(raw.split()) < 4:
try:
return generate_enriched_idea(raw)
except Exception as e:
print("Enrich failed:", e)
return raw
return raw
# ---------- GRADIO CALLBACKS ----------------------------------------------
def start_story(child_prompt, state):
raw = child_prompt.strip()
if not raw:
return (gr.update(value="🌜 *Please type a story idea first!*"),
*(gr.update(visible=False),)*3, state)
idea = _maybe_enrich(raw)
scene1 = generate_scene(1, "", "N/A", idea)
scene1 = _strip_early_ending(scene1, 1)
state = {"scene_no": 1, "story": scene1, "idea": idea}
opt1, opt2 = _extract_options(scene1)
return (gr.update(value=scene1),
gr.update(value=opt1, visible=True),
gr.update(value=opt2, visible=True),
gr.update(visible=False), state)
def choose(option_text, state):
scene_no = state["scene_no"] + 1
combined_story = (state["story"] +
f"\n\n🎲 **You chose:** {option_text}\n\n")
new_scene = generate_scene(scene_no, state["story"], option_text, state["idea"])
new_scene = _strip_early_ending(new_scene, scene_no)
combined_story += new_scene
state.update({"scene_no": scene_no, "story": combined_story})
if scene_no < 3:
opt1, opt2 = _extract_options(new_scene)
return (gr.update(value=combined_story),
gr.update(value=opt1, visible=True),
gr.update(value=opt2, visible=True),
gr.update(visible=False), state)
else:
ending = combined_story + "\n\n🌟 **The End!** 🌟"
return (gr.update(value=ending),
*(gr.update(visible=False),)*2,
gr.update(visible=True), state)
def new_story(state):
state = {"scene_no": 0, "story": "", "idea": ""}
return (gr.update(value=""),
*(gr.update(visible=False),)*3, state)
# ---------- UI -------------------------------------------------------------
with gr.Blocks(title="πŸŒ™ Interactive Bedtime Stories") as demo:
gr.Markdown("## πŸŒ™ **Interactive Bedtime Stories**\n"
"Describe an adventure, then guide the tale with your choices!")
story_state = gr.State({"scene_no": 0, "story": "", "idea": ""})
with gr.Row():
prompt_box = gr.Textbox(
label="✨ Your story idea",
placeholder="e.g. A friendly dragon who loves cookies...",
lines=1,
)
start_btn = gr.Button("πŸš€ Start Story", variant="primary")
story_display = gr.Markdown()
with gr.Row():
choice_btn1 = gr.Button(visible=False)
choice_btn2 = gr.Button(visible=False)
restart_btn = gr.Button("πŸ”„ Tell me another story!", visible=False)
start_btn.click(start_story,
inputs=[prompt_box, story_state],
outputs=[story_display, choice_btn1, choice_btn2, restart_btn, story_state])
choice_btn1.click(choose,
inputs=[choice_btn1, story_state],
outputs=[story_display, choice_btn1, choice_btn2, restart_btn, story_state])
choice_btn2.click(choose,
inputs=[choice_btn2, story_state],
outputs=[story_display, choice_btn1, choice_btn2, restart_btn, story_state])
restart_btn.click(new_story,
inputs=[story_state],
outputs=[story_display, choice_btn1, choice_btn2, restart_btn, story_state])
# --------------------------------------------------------------------------
if __name__ == "__main__":
demo.launch()