jarondon82 commited on
Commit
0804bea
Β·
verified Β·
1 Parent(s): 38b181c

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +399 -0
app.py ADDED
@@ -0,0 +1,399 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import openai
3
+ import streamlit as st
4
+ import requests
5
+ from PIL import Image
6
+ from io import BytesIO
7
+ import replicate
8
+ from dotenv import load_dotenv
9
+
10
+ # Page configuration must be the first Streamlit command
11
+ st.set_page_config(page_title="CuentAI – AI Story Maker", layout="wide")
12
+
13
+ # Load environment variables
14
+ load_dotenv()
15
+ openai.api_key = os.getenv("OPENAI_API_KEY")
16
+ replicate_token = os.getenv("REPLICATE_API_TOKEN")
17
+
18
+ # Optional TTS setup
19
+ # Uncomment if using Google Cloud TTS
20
+ # from google.cloud import texttospeech
21
+
22
+ # Function to load prompt templates
23
+ def load_prompt(file_path):
24
+ try:
25
+ with open(file_path, 'r', encoding='utf-8') as file:
26
+ return file.read().strip()
27
+ except Exception as e:
28
+ st.error(f"Error loading prompt file: {e}")
29
+ return ""
30
+
31
+ # Story generation function
32
+ def generate_story(name: str, theme: str) -> str:
33
+ """
34
+ Prompt GPT-4 to write a 300–400 word children's story in English,
35
+ with protagonist {name} and theme {theme}. Use playful tone,
36
+ simple dialogue, and a clear beginning, middle, and end.
37
+ """
38
+ # Load prompt template
39
+ prompt_template = load_prompt("prompts/story_prompt.txt")
40
+ if not prompt_template:
41
+ prompt_template = (
42
+ f"You are a children's story author. "
43
+ f"Write a 300-400 word children's story where the protagonist is named {name} "
44
+ f"and the plot is about {theme}. Use a friendly style and simple dialogue."
45
+ )
46
+
47
+ # Format the prompt with user inputs
48
+ prompt = prompt_template.format(name=name, theme=theme)
49
+
50
+ try:
51
+ response = openai.ChatCompletion.create(
52
+ model="gpt-4",
53
+ messages=[{"role": "user", "content": prompt}],
54
+ temperature=0.8,
55
+ max_tokens=600,
56
+ )
57
+ return response.choices[0].message.content.strip()
58
+ except Exception as e:
59
+ st.error(f"Error generating story: {e}")
60
+ # Return a fallback story for demo purposes
61
+ return f"""
62
+ # The Great Discovery of {name}
63
+
64
+ Once upon a time, there was a child named {name} who dreamed about {theme}.
65
+
66
+ On a sunny day, {name} decided to explore the garden of their house. Among the flowers and trees,
67
+ they found a small door that had never been seen before.
68
+
69
+ "What could this be?" {name} wondered curiously.
70
+
71
+ Upon opening the door, they discovered a magical world full of bright colors and fantastic creatures.
72
+
73
+ "Welcome!" said a talking butterfly. "We've been waiting for you."
74
+
75
+ {name} spent the entire day meeting new friends and learning about the importance of caring for nature.
76
+
77
+ When they returned home, they promised to come back soon and share their adventures with all their friends.
78
+
79
+ The End.
80
+ """
81
+
82
+ # Scene segmentation
83
+ def split_into_scenes(text: str, num_scenes: int = 3) -> list[str]:
84
+ """
85
+ Split story into specified number of scenes, trying to preserve paragraph structure.
86
+ """
87
+ # Split by paragraphs if possible, else chunk by word count
88
+ paras = [p for p in text.split("\n") if p.strip()]
89
+
90
+ if len(paras) >= num_scenes:
91
+ # Combine paragraphs to get desired number of scenes
92
+ result = []
93
+ paragraphs_per_scene = len(paras) // num_scenes
94
+ for i in range(num_scenes):
95
+ start_idx = i * paragraphs_per_scene
96
+ end_idx = start_idx + paragraphs_per_scene if i < num_scenes - 1 else len(paras)
97
+ result.append("\n".join(paras[start_idx:end_idx]))
98
+ return result
99
+ else:
100
+ # If not enough paragraphs, split by word count
101
+ words = text.split()
102
+ chunk_size = len(words) // num_scenes
103
+ return [" ".join(words[i*chunk_size : (i+1)*chunk_size]) for i in range(num_scenes)]
104
+
105
+ # Image generation with DALL-E 3
106
+ def generate_image_dalle(prompt: str, protagonist: str) -> str:
107
+ """
108
+ Call OpenAI Image API to create one 512Γ—512 image from the prompt.
109
+ Returns the image URL.
110
+ """
111
+ # Load image prompt template
112
+ img_prompt_template = load_prompt("prompts/image_prompt.txt")
113
+ if not img_prompt_template:
114
+ img_prompt_template = "Crea una ilustraciΓ³n de estilo infantil y colorido para un cuento para niΓ±os. La escena muestra: {scene_description} Con {protagonist_name} como personaje principal."
115
+
116
+ # Format the prompt with user inputs
117
+ full_prompt = img_prompt_template.format(
118
+ scene_description=prompt,
119
+ protagonist_name=protagonist
120
+ )
121
+
122
+ try:
123
+ response = openai.Image.create(
124
+ prompt=full_prompt,
125
+ n=1,
126
+ size="512x512"
127
+ )
128
+ return response["data"][0]["url"]
129
+ except Exception as e:
130
+ st.error(f"Error generating image: {e}")
131
+ # Return a placeholder image URL
132
+ return "https://via.placeholder.com/512x512.png?text=Image+Generation+Failed"
133
+
134
+ # Optional: Image generation with Replicate (Stable Diffusion)
135
+ def generate_image_replicate(prompt: str, protagonist: str) -> str:
136
+ """
137
+ Alternative image generation using Replicate API with Stable Diffusion.
138
+ """
139
+ if not replicate_token:
140
+ st.warning("Replicate API token not set. Using fallback image.")
141
+ return "https://via.placeholder.com/512x512.png?text=Replicate+API+Token+Missing"
142
+
143
+ # Load image prompt template
144
+ img_prompt_template = load_prompt("prompts/image_prompt.txt")
145
+ if not img_prompt_template:
146
+ img_prompt_template = "Crea una ilustraciΓ³n de estilo infantil y colorido para un cuento para niΓ±os. La escena muestra: {scene_description} Con {protagonist_name} como personaje principal."
147
+
148
+ # Format the prompt with user inputs
149
+ full_prompt = img_prompt_template.format(
150
+ scene_description=prompt,
151
+ protagonist_name=protagonist
152
+ )
153
+
154
+ try:
155
+ client = replicate.Client(api_token=replicate_token)
156
+ output = client.run(
157
+ "stability-ai/sdxl:2b017d9b67edd2ee1401238df49d75da53c523f36e363881e057f5dc3ed3c5b2",
158
+ input={"prompt": full_prompt}
159
+ )
160
+ if output and isinstance(output, list) and len(output) > 0:
161
+ return output[0]
162
+ else:
163
+ raise Exception("No output from Replicate API")
164
+ except Exception as e:
165
+ st.error(f"Error generating image with Replicate: {e}")
166
+ return "https://via.placeholder.com/512x512.png?text=Replicate+Image+Failed"
167
+
168
+ # Optional Audio TTS function
169
+ def generate_audio_tts(text: str, filename="narration.mp3") -> str:
170
+ """
171
+ Generate audio narration from text using Google Cloud TTS.
172
+ """
173
+ # Check if Google Cloud TTS is available
174
+ try:
175
+ from google.cloud import texttospeech
176
+
177
+ # Load TTS prompt template
178
+ tts_params = load_prompt("prompts/tts_prompt.txt")
179
+
180
+ client = texttospeech.TextToSpeechClient()
181
+ input_text = texttospeech.SynthesisInput(text=text)
182
+ voice = texttospeech.VoiceSelectionParams(
183
+ language_code="en-US",
184
+ ssml_gender=texttospeech.SsmlVoiceGender.FEMALE
185
+ )
186
+ audio_config = texttospeech.AudioConfig(
187
+ audio_encoding=texttospeech.AudioEncoding.MP3
188
+ )
189
+
190
+ response = client.synthesize_speech(
191
+ input=input_text,
192
+ voice=voice,
193
+ audio_config=audio_config
194
+ )
195
+
196
+ with open(filename, "wb") as out:
197
+ out.write(response.audio_content)
198
+
199
+ return filename
200
+ except ImportError:
201
+ st.warning("Google Cloud Text-to-Speech is not installed. Skipping audio generation.")
202
+ return None
203
+ except Exception as e:
204
+ st.error(f"Error generating audio: {e}")
205
+ return None
206
+
207
+ # Custom CSS for child-friendly interface
208
+ def set_custom_css():
209
+ st.markdown("""
210
+ <style>
211
+ @import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@400;700&display=swap');
212
+
213
+ * {
214
+ font-family: 'Comic Neue', cursive;
215
+ }
216
+
217
+ h1, h2, h3 {
218
+ color: #3366cc;
219
+ }
220
+
221
+ .stApp {
222
+ background-color: #f0f8ff;
223
+ }
224
+
225
+ .stButton>button {
226
+ background-color: #ff9966;
227
+ color: white;
228
+ font-weight: bold;
229
+ border-radius: 20px;
230
+ padding: 10px 20px;
231
+ border: none;
232
+ }
233
+
234
+ .stButton>button:hover {
235
+ background-color: #ff7733;
236
+ }
237
+
238
+ .scene-container {
239
+ background-color: white;
240
+ padding: 20px;
241
+ border-radius: 15px;
242
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
243
+ margin-bottom: 20px;
244
+ }
245
+
246
+ .story-text {
247
+ font-size: 18px;
248
+ line-height: 1.6;
249
+ }
250
+ </style>
251
+ """, unsafe_allow_html=True)
252
+
253
+ # Streamlit UI
254
+ def main():
255
+ # Apply custom CSS
256
+ set_custom_css()
257
+
258
+ # Header
259
+ st.title("πŸ§β€β™‚οΈ CuentAI – AI Story Generator")
260
+ st.markdown("### Create personalized stories with AI-generated images")
261
+
262
+ # Sidebar with explanation
263
+ with st.sidebar:
264
+ st.subheader("About CuentAI")
265
+ st.write("""
266
+ CuentAI is an application that uses artificial intelligence to create personalized children's stories in English,
267
+ with automatically generated illustrations for each scene of the story.
268
+
269
+ **How it works:**
270
+ 1. Enter the protagonist's name
271
+ 2. Choose a theme for the story
272
+ 3. Click on "Generate Story"
273
+ 4. Enjoy your personalized story with images!
274
+ """)
275
+
276
+ st.subheader("Technologies")
277
+ st.write("""
278
+ - OpenAI GPT-4 for generating text
279
+ - DALL-E 3 for creating illustrations
280
+ - Streamlit for the web interface
281
+ """)
282
+
283
+ # Image generation options
284
+ st.subheader("Options")
285
+ image_generator = st.radio(
286
+ "Image generation engine:",
287
+ options=["DALL-E 3", "Stable Diffusion (Replicate)"],
288
+ index=0
289
+ )
290
+ st.session_state.image_generator = image_generator
291
+
292
+ # Input form
293
+ col1, col2 = st.columns(2)
294
+
295
+ with col1:
296
+ st.subheader("Customize your story")
297
+ protagonist = st.text_input("Protagonist Name", "Alice")
298
+ theme = st.text_input("Story Theme", "explores a magical jungle")
299
+ num_scenes = st.slider("Number of scenes", min_value=1, max_value=5, value=3)
300
+
301
+ generate_button = st.button("✨ Generate Story")
302
+
303
+ if generate_button:
304
+ with st.spinner("Writing story with GPT-4..."):
305
+ story_text = generate_story(protagonist, theme)
306
+ st.session_state.story = story_text
307
+ st.session_state.protagonist = protagonist
308
+ st.session_state.scenes = split_into_scenes(story_text, num_scenes=num_scenes)
309
+
310
+ with col2:
311
+ if "story" not in st.session_state:
312
+ st.image("https://img.freepik.com/free-vector/hand-drawn-fairy-tale-castle_23-2149423879.jpg",
313
+ caption="Sample image - Generate your personalized story",
314
+ use_column_width=True)
315
+
316
+ # Display story and images
317
+ if "story" in st.session_state:
318
+ st.markdown("---")
319
+ st.subheader("πŸ“š Your Personalized Story")
320
+
321
+ # Choose one layout: tabs, pagination, or scroll
322
+ tabs = st.tabs([f"Scene {i+1}" for i in range(len(st.session_state.scenes))])
323
+
324
+ for i, (tab, scene) in enumerate(zip(tabs, st.session_state.scenes)):
325
+ with tab:
326
+ col1, col2 = st.columns([1, 1])
327
+
328
+ with col1:
329
+ # Generate image if not already in session state
330
+ if f"image_url_{i}" not in st.session_state:
331
+ with st.spinner("Generating illustration..."):
332
+ # Get first 100 words for the prompt to avoid token limits
333
+ scene_summary = " ".join(scene.split()[:100])
334
+
335
+ # Use selected image generator
336
+ if st.session_state.image_generator == "DALL-E 3":
337
+ img_url = generate_image_dalle(scene_summary, st.session_state.protagonist)
338
+ else:
339
+ img_url = generate_image_replicate(scene_summary, st.session_state.protagonist)
340
+
341
+ st.session_state[f"image_url_{i}"] = img_url
342
+
343
+ # Display image
344
+ st.image(st.session_state[f"image_url_{i}"], use_column_width=True)
345
+ st.caption(f"Illustration generated for Scene {i+1}")
346
+
347
+ with col2:
348
+ st.markdown(f"<div class='scene-container'><div class='story-text'>{scene}</div></div>", unsafe_allow_html=True)
349
+
350
+ # Full story text
351
+ with st.expander("View complete story"):
352
+ st.markdown(f"<div class='story-text'>{st.session_state.story}</div>", unsafe_allow_html=True)
353
+
354
+ # Optional TTS toggle
355
+ st.markdown("---")
356
+ st.subheader("πŸ”Š Narration")
357
+
358
+ if st.checkbox("Include audio narration"):
359
+ # Check if TTS is imported
360
+ try:
361
+ from google.cloud import texttospeech
362
+ with st.spinner("Generando audio..."):
363
+ if "audio_file" not in st.session_state:
364
+ audio_file = generate_audio_tts(st.session_state.story)
365
+ st.session_state.audio_file = audio_file
366
+
367
+ if st.session_state.audio_file:
368
+ st.audio(st.session_state.audio_file)
369
+ else:
370
+ st.warning("Could not generate audio. Please check your Google Cloud configuration.")
371
+ except ImportError:
372
+ st.warning("""
373
+ The narration feature requires Google Cloud Text-to-Speech.
374
+
375
+ To enable this feature:
376
+ 1. Install the library: `pip install google-cloud-texttospeech`
377
+ 2. Configure your Google Cloud credentials
378
+ """)
379
+
380
+ # Download options
381
+ st.markdown("---")
382
+ st.subheader("πŸ’Ύ Save your story")
383
+ col1, col2 = st.columns(2)
384
+
385
+ with col1:
386
+ st.download_button(
387
+ label="Download story text",
388
+ data=st.session_state.story,
389
+ file_name=f"story_{st.session_state.protagonist.lower().replace(' ', '_')}.txt",
390
+ mime="text/plain"
391
+ )
392
+
393
+ # This is just a placeholder - in a real app you'd need to implement image downloading
394
+ with col2:
395
+ st.info("Image downloading will be available in a future version.")
396
+
397
+ # Run the app
398
+ if __name__ == "__main__":
399
+ main()