Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import random | |
| from datetime import datetime | |
| import tempfile | |
| import os | |
| import edge_tts | |
| import asyncio | |
| import warnings | |
| import pytz | |
| import re | |
| import json | |
| import pandas as pd | |
| from pathlib import Path | |
| from gradio_client import Client | |
| import hashlib | |
| warnings.filterwarnings('ignore') | |
| # Initialize story starters | |
| STORY_STARTERS = [ | |
| ['Adventure', 'In a hidden temple deep in the Amazon...'], | |
| ['Mystery', 'The detective found an unusual note...'], | |
| ['Romance', 'Two strangers meet on a rainy evening...'], | |
| ['Sci-Fi', 'The space station received an unexpected signal...'], | |
| ['Fantasy', 'A magical portal appeared in the garden...'] | |
| ] | |
| # Initialize client outside of interface definition | |
| arxiv_client = None | |
| def sanitize_filename(text, max_length=50): | |
| """Create a safe filename from text""" | |
| # Get first line or first few words | |
| first_line = text.split('\n')[0].strip() | |
| # Remove special characters and spaces | |
| safe_name = re.sub(r'[^\w\s-]', '', first_line) | |
| safe_name = re.sub(r'[-\s]+', '-', safe_name).strip('-') | |
| # Truncate to max length while keeping words intact | |
| if len(safe_name) > max_length: | |
| safe_name = safe_name[:max_length].rsplit('-', 1)[0] | |
| return safe_name.lower() | |
| def generate_unique_filename(base_name, timestamp, extension): | |
| """Generate a unique filename with timestamp and hash""" | |
| # Create a hash of the base name to ensure uniqueness | |
| name_hash = hashlib.md5(base_name.encode()).hexdigest()[:6] | |
| return f"{timestamp}_{base_name}_{name_hash}{extension}" | |
| def save_story(story, audio_path): | |
| """Save story and audio to gallery with improved naming""" | |
| try: | |
| # Create gallery directory if it doesn't exist | |
| gallery_dir = Path("gallery") | |
| gallery_dir.mkdir(exist_ok=True) | |
| # Generate timestamp and safe filename base | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| safe_name = sanitize_filename(story) | |
| # Generate unique filenames | |
| story_filename = generate_unique_filename(safe_name, timestamp, ".md") | |
| audio_filename = generate_unique_filename(safe_name, timestamp, ".mp3") | |
| # Save story text as markdown | |
| story_path = gallery_dir / story_filename | |
| with open(story_path, "w") as f: | |
| f.write(f"# {safe_name.replace('-', ' ').title()}\n\n{story}") | |
| # Copy audio file to gallery with new name | |
| new_audio_path = None | |
| if audio_path: | |
| new_audio_path = gallery_dir / audio_filename | |
| os.system(f"cp {audio_path} {str(new_audio_path)}") | |
| return str(story_path), str(new_audio_path) if new_audio_path else None | |
| except Exception as e: | |
| print(f"Error saving to gallery: {str(e)}") | |
| return None, None | |
| def format_gallery_entry(timestamp, preview, story_path, audio_path): | |
| """Format gallery entry as markdown with audio controls""" | |
| story_link = f"[{preview}]({story_path})" | |
| if audio_path: | |
| audio_html = f'<audio controls><source src="{audio_path}" type="audio/mp3">Your browser does not support the audio element.</audio>' | |
| return f"{timestamp}: {story_link}\n{audio_html}" | |
| return f"{timestamp}: {story_link}" | |
| def load_gallery(): | |
| """Load all stories and audio from gallery with markdown formatting""" | |
| try: | |
| gallery_dir = Path("gallery") | |
| if not gallery_dir.exists(): | |
| return [] | |
| files = [] | |
| for story_file in sorted(gallery_dir.glob("*.md"), reverse=True): | |
| # Extract timestamp from filename | |
| timestamp = story_file.stem.split('_')[0] | |
| # Read story content | |
| with open(story_file) as f: | |
| story_text = f.read() | |
| # Extract preview from content (skip markdown header) | |
| preview = story_text.split('\n\n', 1)[1][:100] + "..." | |
| # Find matching audio file | |
| audio_file = gallery_dir / f"{story_file.stem}.mp3" | |
| # Format as markdown with audio controls | |
| formatted_entry = format_gallery_entry( | |
| timestamp, | |
| preview, | |
| str(story_file), | |
| str(audio_file) if audio_file.exists() else None | |
| ) | |
| files.append([ | |
| timestamp, | |
| formatted_entry, | |
| str(story_file), | |
| str(audio_file) if audio_file.exists() else None | |
| ]) | |
| return files | |
| except Exception as e: | |
| print(f"Error loading gallery: {str(e)}") | |
| return [] | |
| # Rest of your existing functions remain the same | |
| def generate_story(prompt, model_choice): | |
| """Generate story using specified model""" | |
| try: | |
| client = init_client() | |
| if client is None: | |
| return "Error: Story generation service is not available." | |
| result = client.predict( | |
| prompt=prompt, | |
| llm_model_picked=model_choice, | |
| stream_outputs=True, | |
| api_name="/ask_llm" | |
| ) | |
| return result | |
| except Exception as e: | |
| return f"Error generating story: {str(e)}" | |
| async def generate_speech(text, voice="en-US-AriaNeural"): | |
| """Generate speech from text""" | |
| try: | |
| communicate = edge_tts.Communicate(text, voice) | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file: | |
| tmp_path = tmp_file.name | |
| await communicate.save(tmp_path) | |
| return tmp_path | |
| except Exception as e: | |
| print(f"Error in text2speech: {str(e)}") | |
| return None | |
| def process_story_and_audio(prompt, model_choice): | |
| """Process story, generate audio, and save to gallery""" | |
| try: | |
| # Generate story | |
| story = generate_story(prompt, model_choice) | |
| if isinstance(story, str) and story.startswith("Error"): | |
| return story, None, None | |
| # Generate audio | |
| audio_path = asyncio.run(generate_speech(story)) | |
| # Save to gallery | |
| story_path, saved_audio_path = save_story(story, audio_path) | |
| return story, audio_path, load_gallery() | |
| except Exception as e: | |
| return f"Error: {str(e)}", None, None | |
| # Create the Gradio interface | |
| with gr.Blocks(title="AI Story Generator") as demo: | |
| gr.Markdown(""" | |
| # π AI Story Generator & Narrator | |
| Generate creative stories, listen to them, and build your gallery! | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| with gr.Row(): | |
| prompt_input = gr.Textbox( | |
| label="Story Concept", | |
| placeholder="Enter your story idea...", | |
| lines=3 | |
| ) | |
| with gr.Row(): | |
| model_choice = gr.Dropdown( | |
| label="Model", | |
| choices=[ | |
| "mistralai/Mixtral-8x7B-Instruct-v0.1", | |
| "mistralai/Mistral-7B-Instruct-v0.2" | |
| ], | |
| value="mistralai/Mixtral-8x7B-Instruct-v0.1" | |
| ) | |
| generate_btn = gr.Button("Generate Story") | |
| with gr.Row(): | |
| story_output = gr.Textbox( | |
| label="Generated Story", | |
| lines=10, | |
| interactive=False | |
| ) | |
| with gr.Row(): | |
| audio_output = gr.Audio( | |
| label="Story Narration", | |
| type="filepath" | |
| ) | |
| # Sidebar with Story Starters and Gallery | |
| with gr.Column(scale=1): | |
| gr.Markdown("### π Story Starters") | |
| story_starters = gr.Dataframe( | |
| value=STORY_STARTERS, | |
| headers=["Category", "Starter"], | |
| interactive=False | |
| ) | |
| gr.Markdown("### π¬ Story Gallery") | |
| gallery = gr.HTML(value="") | |
| def update_gallery(): | |
| gallery_entries = load_gallery() | |
| if not gallery_entries: | |
| return "<p>No stories in gallery yet.</p>" | |
| return "<br>".join(entry[1] for entry in gallery_entries) | |
| demo.load(update_gallery, outputs=[gallery]) | |
| # Event handlers | |
| def update_prompt(evt: gr.SelectData): | |
| return STORY_STARTERS[evt.index[0]][1] | |
| story_starters.select(update_prompt, None, prompt_input) | |
| generate_btn.click( | |
| fn=process_story_and_audio, | |
| inputs=[prompt_input, model_choice], | |
| outputs=[story_output, audio_output, gallery] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |