Hammad712 commited on
Commit
99569cf
·
verified ·
1 Parent(s): b2fdf71

Upload 6 files

Browse files
Files changed (6) hide show
  1. Dockerfile +25 -0
  2. flux.py +66 -0
  3. llm.py +9 -0
  4. main.py +224 -0
  5. prompt.py +89 -0
  6. requirements.txt +9 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Base image using Python 3.9
2
+ FROM python:3.9
3
+
4
+ # Create a new user to run the app
5
+ RUN useradd -m -u 1000 user
6
+ USER user
7
+
8
+ # Set environment variables
9
+ ENV PATH="/home/user/.local/bin:$PATH"
10
+
11
+ # Set the working directory
12
+ WORKDIR /app
13
+
14
+ # Copy the requirements and install dependencies
15
+ COPY --chown=user ./requirements.txt requirements.txt
16
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
17
+
18
+ # Copy the rest of the application
19
+ COPY --chown=user . /app
20
+
21
+ # Expose port 7860 for the application
22
+ EXPOSE 7860
23
+
24
+ # Command to run the FastAPI app using uvicorn
25
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
flux.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import time
4
+ from io import BytesIO
5
+ from PIL import Image
6
+
7
+ def generate_image(prompt: str):
8
+ IMAGE_API_KEY = os.getenv("IMAGE_API_KEY")
9
+ if not IMAGE_API_KEY:
10
+ print("Error: IMAGE_API_KEY not found in environment variables.")
11
+ return None
12
+
13
+ url = "https://api.bfl.ml/v1/flux-pro-1.1"
14
+ headers = {
15
+ "accept": "application/json",
16
+ "x-key": IMAGE_API_KEY,
17
+ "Content-Type": "application/json"
18
+ }
19
+ payload = {
20
+ "prompt": prompt,
21
+ "width": 1024,
22
+ "height": 1024,
23
+ "guidance_scale": 1,
24
+ "num_inference_steps": 50,
25
+ "max_sequence_length": 512,
26
+ "Safety Tolerance": 3,
27
+ }
28
+
29
+ # Sending the initial request to generate the image
30
+ response = requests.post(url, headers=headers, json=payload).json()
31
+ if "id" not in response:
32
+ print("Error generating image:", response)
33
+ return None
34
+
35
+ request_id = response["id"]
36
+
37
+ # Polling for the result
38
+ while True:
39
+ time.sleep(0.5)
40
+ result = requests.get(
41
+ "https://api.bfl.ml/v1/get_result",
42
+ headers=headers,
43
+ params={"id": request_id},
44
+ ).json()
45
+
46
+ status = result.get("status")
47
+ if status == "Ready":
48
+ if "result" in result and "sample" in result["result"]:
49
+ image_url = result["result"]["sample"]
50
+ image_response = requests.get(image_url)
51
+ if image_response.status_code == 200:
52
+ image = Image.open(BytesIO(image_response.content))
53
+ return image
54
+ else:
55
+ print("Error fetching the image from the URL.")
56
+ return None
57
+ else:
58
+ print("Error: No 'sample' key in result.")
59
+ return None
60
+ elif status == "Content Moderated":
61
+ print("Image generation status: Content Moderated. Stopping generation.")
62
+ break
63
+ else:
64
+ print(f"Image generation status: {status}")
65
+
66
+ return None
llm.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_openai import ChatOpenAI
2
+
3
+ def get_llm(API_KEY):
4
+
5
+ llm = ChatOpenAI(model="gpt-4o",
6
+ temperature=0.7,
7
+ api_key=API_KEY
8
+ )
9
+ return llm
main.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+ import tempfile
4
+ import time
5
+ import re
6
+ import asyncio
7
+
8
+ from fastapi import FastAPI, HTTPException
9
+ from fastapi.responses import FileResponse
10
+ from pydantic import BaseModel
11
+
12
+ # Import the custom modules
13
+ from llm import get_llm
14
+ from prompt import story_request, generate_story, image_request, generate_image_prompt
15
+ from flux import generate_image
16
+
17
+ from docx import Document
18
+ from docx.shared import Inches
19
+ from dotenv import load_dotenv
20
+
21
+ # Load environment variables from .env file
22
+ load_dotenv()
23
+
24
+ # Create the FastAPI instance
25
+ app = FastAPI(
26
+ title="Bedtime Story Generator API",
27
+ description="API to generate a bedtime story with images and save as a docx document.",
28
+ version="1.0.0"
29
+ )
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Pydantic model for validating the incoming story parameters
33
+ # ---------------------------------------------------------------------------
34
+ class StoryParams(BaseModel):
35
+ Age: str
36
+ Theme: str
37
+ Pages: int
38
+ Time: int
39
+ Tone: str
40
+ Setting: str
41
+ Moral: str
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Helper functions (wrapped from your provided code)
45
+ # ---------------------------------------------------------------------------
46
+ def inference(llm_instance, story_params: dict) -> str:
47
+ """
48
+ Generates the story text from the LLM based on user parameters.
49
+ """
50
+ req = story_request(
51
+ Age=story_params["Age"],
52
+ Theme=story_params["Theme"],
53
+ Pages=story_params["Pages"],
54
+ Time=story_params["Time"],
55
+ Tone=story_params["Tone"],
56
+ Setting=story_params["Setting"],
57
+ Moral=story_params["Moral"]
58
+ )
59
+ prompt_text = generate_story(req)
60
+ print("\nGenerating story. Please wait...\n")
61
+ response = llm_instance.invoke(prompt_text)
62
+ return response.content
63
+
64
+ def parse_story_sections(story_text: str) -> list:
65
+ """
66
+ Parses the LLM-generated story into sections using markers enclosed in '**'.
67
+ """
68
+ pattern = r'\*\*(.*?)\*\*\s*'
69
+ matches = list(re.finditer(pattern, story_text, flags=re.DOTALL))
70
+ sections = []
71
+ for i, match in enumerate(matches):
72
+ marker = match.group(1).strip()
73
+ start = match.end()
74
+ end = matches[i+1].start() if (i+1) < len(matches) else len(story_text)
75
+ content = story_text[start:end].strip()
76
+ section_text = f"{marker}\n\n{content}" if content else marker
77
+ sections.append(section_text)
78
+ return sections
79
+
80
+ def generate_images_for_sections(sections: list, style: str = "sketch") -> list:
81
+ """
82
+ Generates an image for each story section.
83
+ """
84
+ image_paths = []
85
+ for idx, section in enumerate(sections):
86
+ print(f"Generating image for section {idx+1}...")
87
+ img_req = image_request(style=style, bedtime_story_content=section)
88
+ img_prompt = generate_image_prompt(img_req)
89
+ image = generate_image(img_prompt)
90
+ if image:
91
+ temp_dir = tempfile.gettempdir()
92
+ image_filename = os.path.join(temp_dir, f"section_{idx+1}_{uuid.uuid4().hex}.png")
93
+ image.save(image_filename)
94
+ image_paths.append(image_filename)
95
+ print(f"Image for section {idx+1} saved as {image_filename}\n")
96
+ else:
97
+ print(f"Failed to generate image for section {idx+1}.\n")
98
+ image_paths.append(None)
99
+ time.sleep(1) # Optional pause between image generations
100
+ return image_paths
101
+
102
+ def save_story_to_docx(sections: list, image_paths: list, output_filename: str) -> None:
103
+ """
104
+ Saves the story sections and images into a formatted Word document.
105
+ """
106
+ document = Document()
107
+
108
+ # If the first section is a title, use it as the document title.
109
+ if sections and sections[0].startswith("Title:"):
110
+ lines = sections[0].splitlines()
111
+ title_line = lines[0].strip() # e.g., "Title: The Amazing Adventure"
112
+ title_text = title_line.replace("Title:", "").strip()
113
+ document.core_properties.title = title_text
114
+ document.add_heading(title_text, level=1)
115
+ sections = sections[1:]
116
+ if image_paths:
117
+ image_paths = image_paths[1:]
118
+
119
+ # Process remaining sections.
120
+ for idx, section in enumerate(sections):
121
+ lines = section.splitlines()
122
+ if not lines:
123
+ continue
124
+ first_line = lines[0].strip()
125
+ if any(first_line.startswith(marker) for marker in ["Opening Hook:", "Page", "Ending", "The End"]):
126
+ document.add_heading(first_line, level=2)
127
+ remaining_text = "\n".join(lines[1:]).strip()
128
+ if remaining_text:
129
+ document.add_paragraph(remaining_text)
130
+ else:
131
+ document.add_paragraph(section)
132
+
133
+ # Insert the corresponding image (if available).
134
+ if idx < len(image_paths) and image_paths[idx]:
135
+ try:
136
+ document.add_picture(image_paths[idx], width=Inches(4))
137
+ except Exception as e:
138
+ print(f"Error inserting image for section {idx+1}: {e}")
139
+
140
+ document.save(output_filename)
141
+ print(f"\n📖 Story saved to: {output_filename}")
142
+
143
+ def generate_story_docx(story_params: dict) -> str:
144
+ """
145
+ Complete pipeline:
146
+ - Validates the API key
147
+ - Generates the story text via the LLM
148
+ - Parses the story into sections
149
+ - Generates images for each section
150
+ - Saves the complete story with images as a Word document
151
+ Returns the filename of the saved document.
152
+ """
153
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
154
+ if not OPENAI_API_KEY:
155
+ raise Exception("Error: OPENAI_API_KEY not found in environment variables.")
156
+
157
+ llm_instance = get_llm(OPENAI_API_KEY)
158
+
159
+ # Generate the story text from the LLM
160
+ story_text = inference(llm_instance, story_params)
161
+ print("\nStory generated successfully!\n")
162
+
163
+ # Parse the story text into sections
164
+ sections = parse_story_sections(story_text)
165
+
166
+ # Generate images for each section
167
+ image_paths = generate_images_for_sections(sections, style="sketch")
168
+
169
+ # Create a unique filename for the docx file in a temporary directory
170
+ output_filename = os.path.join(tempfile.gettempdir(), f"bedtime_story_{uuid.uuid4().hex}.docx")
171
+
172
+ # Save the story and images to the Word document
173
+ save_story_to_docx(sections, image_paths, output_filename=output_filename)
174
+
175
+ return output_filename
176
+
177
+ # ---------------------------------------------------------------------------
178
+ # API Endpoints
179
+ # ---------------------------------------------------------------------------
180
+
181
+ @app.get("/", summary="Root Endpoint", description="Welcome message and API information.")
182
+ async def root():
183
+ """
184
+ Returns a welcome message and a link to the API documentation.
185
+ """
186
+ return {
187
+ "message": "Welcome to the Bedtime Story Generator API!",
188
+ "documentation": "/docs"
189
+ }
190
+
191
+ @app.post(
192
+ "/generate-story",
193
+ summary="Generate a Bedtime Story Document",
194
+ description="Generates a story with images based on input parameters and returns a docx file.",
195
+ response_description="The generated Word document (.docx) file."
196
+ )
197
+ async def generate_story_endpoint(story_params: StoryParams):
198
+ """
199
+ API endpoint that runs the complete story-generation pipeline.
200
+ It accepts story parameters as JSON, processes the story and images,
201
+ and returns a downloadable Word document.
202
+ """
203
+ try:
204
+ # Run the blocking story generation in a separate thread
205
+ docx_file = await asyncio.to_thread(generate_story_docx, story_params.dict())
206
+ except Exception as e:
207
+ raise HTTPException(status_code=500, detail=str(e))
208
+
209
+ return FileResponse(
210
+ path=docx_file,
211
+ media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
212
+ filename=os.path.basename(docx_file)
213
+ )
214
+
215
+ @app.get("/health", summary="Health Check", description="Returns the API health status.")
216
+ async def health():
217
+ return {"status": "ok"}
218
+
219
+ # ---------------------------------------------------------------------------
220
+ # Run the server with: uvicorn main:app --reload
221
+ # ---------------------------------------------------------------------------
222
+ if __name__ == "__main__":
223
+ import uvicorn
224
+ uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
prompt.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+ class story_request(BaseModel):
4
+ Age: str
5
+ Theme: str
6
+ Pages: int
7
+ Time: int
8
+ Tone: str
9
+ Setting: str
10
+ Moral:str
11
+
12
+ class image_request(BaseModel):
13
+ style: str
14
+ bedtime_story_content: str
15
+
16
+ def generate_story(story_request:story_request) -> str:
17
+
18
+ prompt_template = """
19
+ You are an imaginative and skilled storyteller, known for creating fun and meaningful bedtime stories.
20
+ You understand how to make stories simple, engaging, and perfect for young listeners.
21
+
22
+ Please write a bedtime story using these details:
23
+
24
+ 1. **Target Age Group:** {Age}
25
+ 2. **Theme:** {Theme}
26
+ 3. **Story Length:** {Pages} pages
27
+ 4. **Estimated Reading Time:** {Time} minutes
28
+ 5. **Tone & Atmosphere:** {Tone}
29
+ 6. **Setting:** {Setting}
30
+ 7. **Core Message or Lesson:** {Moral}
31
+
32
+ **Story Guidelines:**
33
+ - Each page should have **200 to 300 words** to keep the pacing just right.
34
+ - Use **simple and easy-to-understand** words so children can follow the story.
35
+ - Include **natural dialogue** to make the story feel real and exciting.
36
+ - End with a **happy or comforting resolution** so kids feel safe and relaxed before bed.
37
+
38
+ Now, create a heartwarming story that is **easy to understand, and full of imagination!**
39
+ """
40
+
41
+
42
+ prompt = prompt_template.format(
43
+ Age=story_request.Age,
44
+ Theme=story_request.Theme,
45
+ Pages=story_request.Pages,
46
+ Time=story_request.Time,
47
+ Tone=story_request.Tone,
48
+ Setting=story_request.Setting,
49
+ Moral=story_request.Moral
50
+ )
51
+
52
+ return prompt
53
+
54
+
55
+ def generate_image_prompt(image_request:image_request) -> str:
56
+ prompt_template = """
57
+ You are a creative visual storyteller tasked with generating detailed, evocative image prompts that capture the enchanting atmosphere of a bedtime story. Your prompts should be meticulously crafted to inspire stunning, narrative-driven visuals that enhance the storytelling experience.
58
+
59
+ Bedtime Story Context:
60
+ {bedtime_story_content}
61
+
62
+ Instructions:
63
+ - Create image prompts that evoke warmth, wonder, and a sense of magical realism.
64
+ - Include the following key components:
65
+ 1. **Subject/Scene**: Clearly describe the characters, settings, and key moments of the bedtime story. Emphasize child-friendly, magical elements like softly lit rooms, whimsical forests, or cozy story corners.
66
+ 2. **Composition and Action**: Detail spatial arrangements and dynamic storytelling elements. For example, a child cuddled up with a favorite stuffed animal as a parent reads, or a moonlit scene with gentle, swirling clouds.
67
+ 3. **Emotion and Style**: Convey the gentle, calming, and imaginative tone of the bedtime narrative. Include descriptive cues that evoke feelings of safety, warmth, and wonder.
68
+ 4. **Lighting and Color**: Use soft, warm lighting (such as golden hour or candlelight effects) and a soothing color palette (like muted pastels or warm earth tones) to set the scene.
69
+ 5. **Camera and Lens Settings (Optional)**: Suggest settings like shallow depth of field to create a dreamy background or a gentle focus that adds to the magical quality of the scene.
70
+ 6. **Artistic Enhancements and Aspect Ratio**: Recommend visual enhancements like bokeh, soft focus, or gentle vignette effects. Specify the desired aspect ratio (e.g., --ar 16:9 for widescreen or --ar 4:5 for portrait) and style tags (e.g., --style cinematic, --style dreamy, --style soft).
71
+ 7. **Overall Mood**: Ensure the image prompt aligns with the overall theme of bedtime stories – nurturing, imaginative, and calming.
72
+
73
+ Style Directive:
74
+ Use the following artistic style for this prompt: {style}
75
+
76
+ Examples:
77
+ 1. A softly lit nursery scene featuring a child in cozy pajamas, curled up with a beloved stuffed animal and a gently glowing night light. The scene exudes warmth and security with muted pastel tones and a hint of magical sparkles in the air. --ar 4:5 --style dreamy
78
+ 2. An enchanting forest at dusk, where fireflies flicker among ancient trees and a small, adventurous child wanders along a moss-covered path. The lighting is ethereal with soft blue and golden hues, creating a mystical and soothing atmosphere. --ar 16:9 --style cinematic
79
+ 3. A cozy living room transformed into a magical reading nook, with a parent and child sharing a story by the gentle glow of a fireplace. The room is decorated with whimsical touches like floating lanterns and soft, warm lighting, inviting a sense of calm and wonder. --ar 3:2 --style soft
80
+
81
+ Now, please craft an image prompt that embodies these guidelines.
82
+ """
83
+ prompt = prompt_template.format(
84
+ bedtime_story_content=image_request.bedtime_story_content,
85
+ style=image_request.style
86
+ )
87
+
88
+ return prompt
89
+
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ langchain_openai
2
+ pydantic
3
+ python-docx
4
+ Pillow
5
+ requests
6
+ python-dotenv
7
+ uvicorn
8
+ fastapi
9
+ streamlit