Spaces:
Running on Zero
Running on Zero
File size: 5,432 Bytes
3edeefe 3cf33dd 3edeefe 3cf33dd 3edeefe 823ce16 3edeefe 823ce16 3edeefe 823ce16 3cf33dd b828530 3edeefe 823ce16 3cf33dd 3edeefe 3cf33dd 3edeefe 3cf33dd 3edeefe 3cf33dd 3edeefe 3cf33dd 3edeefe 3cf33dd 823ce16 3cf33dd 823ce16 3cf33dd 823ce16 3cf33dd 823ce16 3cf33dd 3826b11 3cf33dd 3826b11 3cf33dd 3826b11 3cf33dd 3826b11 3edeefe 3826b11 3edeefe | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | """
Hook β DramaBox scene drafting via an LLM.
The frontend's "Adapt to my niche" step POSTs to /api/tools/scene; this turns a
viral-hook structure plus the creator's niche into a directed DramaBox scene.
Provider chain: AWS Bedrock (primary) β Pollinations (fallback). If both fail,
the endpoint errors and the frontend falls back to its offline template engine.
"""
from __future__ import annotations
import json
import os
import urllib.request
from steps.lang._shared import bedrock_converse, log_llm_call
# Bedrock model for scene drafting. Defaults to the BEDROCK_MODEL env the rest
# of the backend already uses β the dub pipeline's translation fallback runs on
# it, so it's a known-good id on this Space. (A hardcoded default here shadowed
# that env and hit Bedrock "Operation not allowed".) Override: SCENE_BEDROCK_MODEL.
SCENE_MODEL = os.getenv("SCENE_BEDROCK_MODEL") or os.getenv("BEDROCK_MODEL")
# Pollinations fallback uses the keyless anonymous text endpoint. The keyed
# gen.pollinations.ai route (build_client) blocks the Space's requests; the
# anonymous text route stays open. Override the model via env POLLEN_TEXT_MODEL.
POLLEN_TEXT_URL = "https://text.pollinations.ai/openai"
POLLEN_TEXT_MODEL = os.getenv("POLLEN_TEXT_MODEL", "openai")
_TEMPERATURE = 0.8
_SYSTEM_PROMPT = """You are a scriptwriter for DramaBox, a text-to-speech engine that performs short, emotionally directed monologues.
Write ONE scene from the given viral hook and the creator's niche.
FORMAT β follow exactly:
- Structure: <speaker description>, "<dialogue>" <stage action> "<more dialogue>"
- Text inside double quotes is SPOKEN aloud, verbatim.
- Text outside quotes is a STAGE DIRECTION, never spoken (e.g. She sighs deeply. A long pause.).
- Phonetic sounds may go inside quotes: "Hahaha", "Mmmmm", "Ugh".
- NEVER put Ahem, Pfft, Sigh, Gasp or Cough inside quotes β use a stage direction.
CONTENT:
- Follow the hook's structure, rewritten specifically for the creator's niche β do not just slot the topic in.
- About 3 short sentences β one performable take, roughly 10-20 seconds spoken.
- Output ONLY the scene text. No preamble, no markdown, no surrounding quotes."""
def _build_user_prompt(
hook_pattern: str,
hook_title: str,
register: str,
register_label: str,
niche: str,
) -> str:
return (
f'Viral hook: "{hook_title}"\n'
f"Hook structure: {hook_pattern}\n"
f"Emotional register: {register_label} ({register})\n"
f"Creator's niche / topic: {niche}\n\n"
"Write the DramaBox scene now."
)
def _clean(scene: str) -> str:
"""Strip whitespace and a markdown fence if the model wrapped its output."""
s = (scene or "").strip()
if s.startswith("```"):
s = s.split("\n", 1)[-1].rsplit("```", 1)[0].strip()
return s
def _bedrock_scene(user_prompt: str) -> str:
"""Primary provider β AWS Bedrock."""
raw = bedrock_converse(
_SYSTEM_PROMPT,
user_prompt,
temperature=_TEMPERATURE,
step="dramabox_scene",
model_id=SCENE_MODEL,
)
return _clean(raw)
def _pollinations_scene(user_prompt: str) -> str:
"""Fallback provider β Pollinations' keyless anonymous text endpoint."""
payload = json.dumps({
"model": POLLEN_TEXT_MODEL,
"temperature": _TEMPERATURE,
"messages": [
{"role": "system", "content": _SYSTEM_PROMPT},
{"role": "user", "content": user_prompt},
],
}).encode("utf-8")
req = urllib.request.Request(
POLLEN_TEXT_URL,
data=payload,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=60) as resp:
data = json.loads(resp.read().decode("utf-8"))
raw = (data["choices"][0]["message"]["content"] or "").strip()
log_llm_call(
step="dramabox_scene",
provider="pollinations-text",
model=POLLEN_TEXT_MODEL,
system_prompt=_SYSTEM_PROMPT,
user_prompt=user_prompt,
response=raw,
temperature=_TEMPERATURE,
)
return _clean(raw)
def write_scene(
*,
hook_pattern: str,
hook_title: str,
register: str,
register_label: str,
niche: str,
) -> str:
"""Draft a directed DramaBox scene β Bedrock primary, Pollinations fallback.
Raises if both providers fail; the caller surfaces a 500 and the frontend
falls back to its offline template engine.
"""
user_prompt = _build_user_prompt(
hook_pattern, hook_title, register, register_label, niche
)
# Primary β Bedrock.
bedrock_err = "unknown error"
try:
scene = _bedrock_scene(user_prompt)
if scene:
return scene
bedrock_err = "returned an empty scene"
except Exception as e: # noqa: BLE001
bedrock_err = str(e)
print(f"[scene] Bedrock unavailable ({bedrock_err}) β trying Pollinations.")
# Fallback β Pollinations.
try:
scene = _pollinations_scene(user_prompt)
except Exception as e: # noqa: BLE001
print(f"[scene] Pollinations also failed ({e}).")
raise RuntimeError(
f"Bedrock failed [{bedrock_err}]; Pollinations failed [{e}]"
)
if not scene:
raise RuntimeError(
f"Bedrock failed [{bedrock_err}]; Pollinations returned an empty scene"
)
return scene
|