awacke1 commited on
Commit
a0ebdf1
ยท
verified ยท
1 Parent(s): 42bab4b

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +250 -0
app.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import random
3
+ from datetime import datetime
4
+ import tempfile
5
+ import os
6
+ import edge_tts
7
+ import asyncio
8
+ import warnings
9
+ import pytz
10
+ import re
11
+ import json
12
+ import pandas as pd
13
+ from pathlib import Path
14
+ from gradio_client import Client
15
+ import hashlib
16
+
17
+ warnings.filterwarnings('ignore')
18
+
19
+ # Initialize story starters
20
+ STORY_STARTERS = [
21
+ ['Adventure', 'In a hidden temple deep in the Amazon...'],
22
+ ['Mystery', 'The detective found an unusual note...'],
23
+ ['Romance', 'Two strangers meet on a rainy evening...'],
24
+ ['Sci-Fi', 'The space station received an unexpected signal...'],
25
+ ['Fantasy', 'A magical portal appeared in the garden...']
26
+ ]
27
+
28
+ # Initialize client outside of interface definition
29
+ arxiv_client = None
30
+
31
+ def sanitize_filename(text, max_length=50):
32
+ """Create a safe filename from text"""
33
+ # Get first line or first few words
34
+ first_line = text.split('\n')[0].strip()
35
+ # Remove special characters and spaces
36
+ safe_name = re.sub(r'[^\w\s-]', '', first_line)
37
+ safe_name = re.sub(r'[-\s]+', '-', safe_name).strip('-')
38
+ # Truncate to max length while keeping words intact
39
+ if len(safe_name) > max_length:
40
+ safe_name = safe_name[:max_length].rsplit('-', 1)[0]
41
+ return safe_name.lower()
42
+
43
+ def generate_unique_filename(base_name, timestamp, extension):
44
+ """Generate a unique filename with timestamp and hash"""
45
+ # Create a hash of the base name to ensure uniqueness
46
+ name_hash = hashlib.md5(base_name.encode()).hexdigest()[:6]
47
+ return f"{timestamp}_{base_name}_{name_hash}{extension}"
48
+
49
+ def save_story(story, audio_path):
50
+ """Save story and audio to gallery with improved naming"""
51
+ try:
52
+ # Create gallery directory if it doesn't exist
53
+ gallery_dir = Path("gallery")
54
+ gallery_dir.mkdir(exist_ok=True)
55
+
56
+ # Generate timestamp and safe filename base
57
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
58
+ safe_name = sanitize_filename(story)
59
+
60
+ # Generate unique filenames
61
+ story_filename = generate_unique_filename(safe_name, timestamp, ".md")
62
+ audio_filename = generate_unique_filename(safe_name, timestamp, ".mp3")
63
+
64
+ # Save story text as markdown
65
+ story_path = gallery_dir / story_filename
66
+ with open(story_path, "w") as f:
67
+ f.write(f"# {safe_name.replace('-', ' ').title()}\n\n{story}")
68
+
69
+ # Copy audio file to gallery with new name
70
+ new_audio_path = None
71
+ if audio_path:
72
+ new_audio_path = gallery_dir / audio_filename
73
+ os.system(f"cp {audio_path} {str(new_audio_path)}")
74
+
75
+ return str(story_path), str(new_audio_path) if new_audio_path else None
76
+ except Exception as e:
77
+ print(f"Error saving to gallery: {str(e)}")
78
+ return None, None
79
+
80
+ def format_gallery_entry(timestamp, preview, story_path, audio_path):
81
+ """Format gallery entry as markdown with audio controls"""
82
+ story_link = f"[{preview}]({story_path})"
83
+ if audio_path:
84
+ audio_html = f'<audio controls><source src="{audio_path}" type="audio/mp3">Your browser does not support the audio element.</audio>'
85
+ return f"{timestamp}: {story_link}\n{audio_html}"
86
+ return f"{timestamp}: {story_link}"
87
+
88
+ def load_gallery():
89
+ """Load all stories and audio from gallery with markdown formatting"""
90
+ try:
91
+ gallery_dir = Path("gallery")
92
+ if not gallery_dir.exists():
93
+ return []
94
+
95
+ files = []
96
+ for story_file in sorted(gallery_dir.glob("*.md"), reverse=True):
97
+ # Extract timestamp from filename
98
+ timestamp = story_file.stem.split('_')[0]
99
+
100
+ # Read story content
101
+ with open(story_file) as f:
102
+ story_text = f.read()
103
+ # Extract preview from content (skip markdown header)
104
+ preview = story_text.split('\n\n', 1)[1][:100] + "..."
105
+
106
+ # Find matching audio file
107
+ audio_file = gallery_dir / f"{story_file.stem}.mp3"
108
+
109
+ # Format as markdown with audio controls
110
+ formatted_entry = format_gallery_entry(
111
+ timestamp,
112
+ preview,
113
+ str(story_file),
114
+ str(audio_file) if audio_file.exists() else None
115
+ )
116
+
117
+ files.append([
118
+ timestamp,
119
+ formatted_entry,
120
+ str(story_file),
121
+ str(audio_file) if audio_file.exists() else None
122
+ ])
123
+
124
+ return files
125
+ except Exception as e:
126
+ print(f"Error loading gallery: {str(e)}")
127
+ return []
128
+
129
+ # Rest of your existing functions remain the same
130
+ def generate_story(prompt, model_choice):
131
+ """Generate story using specified model"""
132
+ try:
133
+ client = init_client()
134
+ if client is None:
135
+ return "Error: Story generation service is not available."
136
+
137
+ result = client.predict(
138
+ prompt=prompt,
139
+ llm_model_picked=model_choice,
140
+ stream_outputs=True,
141
+ api_name="/ask_llm"
142
+ )
143
+ return result
144
+ except Exception as e:
145
+ return f"Error generating story: {str(e)}"
146
+
147
+ async def generate_speech(text, voice="en-US-AriaNeural"):
148
+ """Generate speech from text"""
149
+ try:
150
+ communicate = edge_tts.Communicate(text, voice)
151
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
152
+ tmp_path = tmp_file.name
153
+ await communicate.save(tmp_path)
154
+ return tmp_path
155
+ except Exception as e:
156
+ print(f"Error in text2speech: {str(e)}")
157
+ return None
158
+
159
+ def process_story_and_audio(prompt, model_choice):
160
+ """Process story, generate audio, and save to gallery"""
161
+ try:
162
+ # Generate story
163
+ story = generate_story(prompt, model_choice)
164
+ if isinstance(story, str) and story.startswith("Error"):
165
+ return story, None, None
166
+
167
+ # Generate audio
168
+ audio_path = asyncio.run(generate_speech(story))
169
+
170
+ # Save to gallery
171
+ story_path, saved_audio_path = save_story(story, audio_path)
172
+
173
+ return story, audio_path, load_gallery()
174
+ except Exception as e:
175
+ return f"Error: {str(e)}", None, None
176
+
177
+ # Create the Gradio interface
178
+ with gr.Blocks(title="AI Story Generator") as demo:
179
+ gr.Markdown("""
180
+ # ๐ŸŽญ AI Story Generator & Narrator
181
+ Generate creative stories, listen to them, and build your gallery!
182
+ """)
183
+
184
+ with gr.Row():
185
+ with gr.Column(scale=3):
186
+ with gr.Row():
187
+ prompt_input = gr.Textbox(
188
+ label="Story Concept",
189
+ placeholder="Enter your story idea...",
190
+ lines=3
191
+ )
192
+
193
+ with gr.Row():
194
+ model_choice = gr.Dropdown(
195
+ label="Model",
196
+ choices=[
197
+ "mistralai/Mixtral-8x7B-Instruct-v0.1",
198
+ "mistralai/Mistral-7B-Instruct-v0.2"
199
+ ],
200
+ value="mistralai/Mixtral-8x7B-Instruct-v0.1"
201
+ )
202
+ generate_btn = gr.Button("Generate Story")
203
+
204
+ with gr.Row():
205
+ story_output = gr.Textbox(
206
+ label="Generated Story",
207
+ lines=10,
208
+ interactive=False
209
+ )
210
+
211
+ with gr.Row():
212
+ audio_output = gr.Audio(
213
+ label="Story Narration",
214
+ type="filepath"
215
+ )
216
+
217
+ # Sidebar with Story Starters and Gallery
218
+ with gr.Column(scale=1):
219
+ gr.Markdown("### ๐Ÿ“š Story Starters")
220
+ story_starters = gr.Dataframe(
221
+ value=STORY_STARTERS,
222
+ headers=["Category", "Starter"],
223
+ interactive=False
224
+ )
225
+
226
+ gr.Markdown("### ๐ŸŽฌ Story Gallery")
227
+ gallery = gr.HTML(value="")
228
+
229
+ def update_gallery():
230
+ gallery_entries = load_gallery()
231
+ if not gallery_entries:
232
+ return "<p>No stories in gallery yet.</p>"
233
+ return "<br>".join(entry[1] for entry in gallery_entries)
234
+
235
+ demo.load(update_gallery, outputs=[gallery])
236
+
237
+ # Event handlers
238
+ def update_prompt(evt: gr.SelectData):
239
+ return STORY_STARTERS[evt.index[0]][1]
240
+
241
+ story_starters.select(update_prompt, None, prompt_input)
242
+
243
+ generate_btn.click(
244
+ fn=process_story_and_audio,
245
+ inputs=[prompt_input, model_choice],
246
+ outputs=[story_output, audio_output, gallery]
247
+ )
248
+
249
+ if __name__ == "__main__":
250
+ demo.launch()