Spaces:
Sleeping
Sleeping
File size: 8,354 Bytes
99569cf |
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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
import os
import uuid
import tempfile
import time
import re
import asyncio
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from pydantic import BaseModel
# Import the custom modules
from llm import get_llm
from prompt import story_request, generate_story, image_request, generate_image_prompt
from flux import generate_image
from docx import Document
from docx.shared import Inches
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Create the FastAPI instance
app = FastAPI(
title="Bedtime Story Generator API",
description="API to generate a bedtime story with images and save as a docx document.",
version="1.0.0"
)
# ---------------------------------------------------------------------------
# Pydantic model for validating the incoming story parameters
# ---------------------------------------------------------------------------
class StoryParams(BaseModel):
Age: str
Theme: str
Pages: int
Time: int
Tone: str
Setting: str
Moral: str
# ---------------------------------------------------------------------------
# Helper functions (wrapped from your provided code)
# ---------------------------------------------------------------------------
def inference(llm_instance, story_params: dict) -> str:
"""
Generates the story text from the LLM based on user parameters.
"""
req = story_request(
Age=story_params["Age"],
Theme=story_params["Theme"],
Pages=story_params["Pages"],
Time=story_params["Time"],
Tone=story_params["Tone"],
Setting=story_params["Setting"],
Moral=story_params["Moral"]
)
prompt_text = generate_story(req)
print("\nGenerating story. Please wait...\n")
response = llm_instance.invoke(prompt_text)
return response.content
def parse_story_sections(story_text: str) -> list:
"""
Parses the LLM-generated story into sections using markers enclosed in '**'.
"""
pattern = r'\*\*(.*?)\*\*\s*'
matches = list(re.finditer(pattern, story_text, flags=re.DOTALL))
sections = []
for i, match in enumerate(matches):
marker = match.group(1).strip()
start = match.end()
end = matches[i+1].start() if (i+1) < len(matches) else len(story_text)
content = story_text[start:end].strip()
section_text = f"{marker}\n\n{content}" if content else marker
sections.append(section_text)
return sections
def generate_images_for_sections(sections: list, style: str = "sketch") -> list:
"""
Generates an image for each story section.
"""
image_paths = []
for idx, section in enumerate(sections):
print(f"Generating image for section {idx+1}...")
img_req = image_request(style=style, bedtime_story_content=section)
img_prompt = generate_image_prompt(img_req)
image = generate_image(img_prompt)
if image:
temp_dir = tempfile.gettempdir()
image_filename = os.path.join(temp_dir, f"section_{idx+1}_{uuid.uuid4().hex}.png")
image.save(image_filename)
image_paths.append(image_filename)
print(f"Image for section {idx+1} saved as {image_filename}\n")
else:
print(f"Failed to generate image for section {idx+1}.\n")
image_paths.append(None)
time.sleep(1) # Optional pause between image generations
return image_paths
def save_story_to_docx(sections: list, image_paths: list, output_filename: str) -> None:
"""
Saves the story sections and images into a formatted Word document.
"""
document = Document()
# If the first section is a title, use it as the document title.
if sections and sections[0].startswith("Title:"):
lines = sections[0].splitlines()
title_line = lines[0].strip() # e.g., "Title: The Amazing Adventure"
title_text = title_line.replace("Title:", "").strip()
document.core_properties.title = title_text
document.add_heading(title_text, level=1)
sections = sections[1:]
if image_paths:
image_paths = image_paths[1:]
# Process remaining sections.
for idx, section in enumerate(sections):
lines = section.splitlines()
if not lines:
continue
first_line = lines[0].strip()
if any(first_line.startswith(marker) for marker in ["Opening Hook:", "Page", "Ending", "The End"]):
document.add_heading(first_line, level=2)
remaining_text = "\n".join(lines[1:]).strip()
if remaining_text:
document.add_paragraph(remaining_text)
else:
document.add_paragraph(section)
# Insert the corresponding image (if available).
if idx < len(image_paths) and image_paths[idx]:
try:
document.add_picture(image_paths[idx], width=Inches(4))
except Exception as e:
print(f"Error inserting image for section {idx+1}: {e}")
document.save(output_filename)
print(f"\n๐ Story saved to: {output_filename}")
def generate_story_docx(story_params: dict) -> str:
"""
Complete pipeline:
- Validates the API key
- Generates the story text via the LLM
- Parses the story into sections
- Generates images for each section
- Saves the complete story with images as a Word document
Returns the filename of the saved document.
"""
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if not OPENAI_API_KEY:
raise Exception("Error: OPENAI_API_KEY not found in environment variables.")
llm_instance = get_llm(OPENAI_API_KEY)
# Generate the story text from the LLM
story_text = inference(llm_instance, story_params)
print("\nStory generated successfully!\n")
# Parse the story text into sections
sections = parse_story_sections(story_text)
# Generate images for each section
image_paths = generate_images_for_sections(sections, style="sketch")
# Create a unique filename for the docx file in a temporary directory
output_filename = os.path.join(tempfile.gettempdir(), f"bedtime_story_{uuid.uuid4().hex}.docx")
# Save the story and images to the Word document
save_story_to_docx(sections, image_paths, output_filename=output_filename)
return output_filename
# ---------------------------------------------------------------------------
# API Endpoints
# ---------------------------------------------------------------------------
@app.get("/", summary="Root Endpoint", description="Welcome message and API information.")
async def root():
"""
Returns a welcome message and a link to the API documentation.
"""
return {
"message": "Welcome to the Bedtime Story Generator API!",
"documentation": "/docs"
}
@app.post(
"/generate-story",
summary="Generate a Bedtime Story Document",
description="Generates a story with images based on input parameters and returns a docx file.",
response_description="The generated Word document (.docx) file."
)
async def generate_story_endpoint(story_params: StoryParams):
"""
API endpoint that runs the complete story-generation pipeline.
It accepts story parameters as JSON, processes the story and images,
and returns a downloadable Word document.
"""
try:
# Run the blocking story generation in a separate thread
docx_file = await asyncio.to_thread(generate_story_docx, story_params.dict())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
return FileResponse(
path=docx_file,
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
filename=os.path.basename(docx_file)
)
@app.get("/health", summary="Health Check", description="Returns the API health status.")
async def health():
return {"status": "ok"}
# ---------------------------------------------------------------------------
# Run the server with: uvicorn main:app --reload
# ---------------------------------------------------------------------------
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|