| import gradio as gr |
| import requests |
| import json |
| import os |
| import io |
| from reportlab.lib.pagesizes import letter |
| from reportlab.pdfgen import canvas |
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle |
| from reportlab.lib.units import inch |
| import speech_recognition as sr |
|
|
| |
| API_KEY = os.getenv("XAI_API_KEY", "your-xai-api-key-here") |
| if API_KEY == "your-xai-api-key-here": |
| print("π API Key: Using placeholder - replace with your actual xAI API key") |
| |
| API_URL = "https://api.x.ai/v1/chat/completions" |
|
|
| |
| STARS = [ |
| "Tom Hanks", "Meryl Streep", "Leonardo DiCaprio", "Emma Watson", |
| "Chris Hemsworth", "Scarlett Johansson", "Denzel Washington", "Zendaya", |
| "Ryan Reynolds", "Margot Robbie", "Brad Pitt", "Viola Davis", |
| "Keanu Reeves", "Florence Pugh", "Pedro Pascal", "Anya Taylor-Joy" |
| ] |
|
|
| def speech_to_text(audio): |
| """Convert audio to text using Google STT.""" |
| if audio is None: |
| return "" |
| r = sr.Recognizer() |
| try: |
| if isinstance(audio, tuple): |
| audio_path = audio[1] |
| else: |
| audio_path = audio |
| |
| with sr.AudioFile(audio_path) as source: |
| audio_data = r.record(source) |
| text = r.recognize_google(audio_data) |
| return text |
| except sr.UnknownValueError: |
| return "π Couldn't understand audio. Please try speaking clearly." |
| except sr.RequestError as e: |
| return f"π€ Speech service error: {str(e)}" |
| except Exception as e: |
| return f"β οΈ Audio processing error: {str(e)}" |
|
|
| def generate_sequel(original_plot, continuation_point, selected_stars, custom_star, word_count): |
| """Generate story with Grok-4.""" |
| if not API_KEY or API_KEY == "your-xai-api-key-here": |
| return "π API key not configured. Please set XAI_API_KEY environment variable with your actual xAI API key.", None, None |
| |
| if not original_plot.strip(): |
| return "π Please provide an original plot to continue from!", None, None |
| |
| |
| cast_parts = [] |
| if selected_stars: |
| cast_parts.append(f"Starring: {', '.join(selected_stars)}") |
| if custom_star.strip(): |
| cast_parts.append(f"Introducing: {custom_star}") |
| |
| stars_section = " ".join(cast_parts) if cast_parts else "Featuring an all-star cast." |
| |
| |
| max_tokens = max(100, min(3000, int(word_count * 1.3))) |
| |
| prompt = f"""You are an award-winning screenwriter crafting the perfect sequel. |
| |
| ORIGINAL STORY: "{original_plot}" |
| CONTINUATION POINT: "{continuation_point}" |
| CAST: {stars_section} |
| |
| Create an engaging sequel that honors the original while bringing fresh excitement. |
| Target length: approximately {word_count} words. |
| |
| STRUCTURE: |
| - TITLE: [Catchy sequel title] |
| - LOGLINE: [One-sentence premise] |
| - ACT 1: [Setup and character reintroduction] |
| - ACT 2: [Rising action with meaningful twists] |
| - ACT 3: [Satisfying climax and resolution] |
| - POST-CREDITS TEASER: [Hint at future adventures] |
| |
| Include memorable dialogue moments and character development. Balance nostalgia with innovation. |
| """ |
| |
| headers = { |
| "Authorization": f"Bearer {API_KEY}", |
| "Content-Type": "application/json" |
| } |
| data = { |
| "model": "grok-4", |
| "messages": [{"role": "user", "content": prompt}], |
| "max_tokens": max_tokens, |
| "temperature": 0.85, |
| "top_p": 0.9 |
| } |
| |
| try: |
| response = requests.post(API_URL, headers=headers, json=data, timeout=60) |
| response.raise_for_status() |
| story = response.json()["choices"][0]["message"]["content"] |
| return story, gr.update(visible=True), gr.update(visible=False) |
| except requests.exceptions.Timeout: |
| return "β° API timeout - try again with a shorter word count.", None, None |
| except Exception as e: |
| return f"π API error: {str(e)}", None, None |
|
|
| def create_pdf(story_text): |
| """Generate a well-formatted PDF from story text.""" |
| if not story_text or "API" in story_text or "Please provide" in story_text: |
| return None, None |
| |
| buffer = io.BytesIO() |
| doc = SimpleDocTemplate(buffer, pagesize=letter, |
| topMargin=0.5*inch, bottomMargin=0.5*inch, |
| leftMargin=0.5*inch, rightMargin=0.5*inch) |
| |
| styles = getSampleStyleSheet() |
| title_style = ParagraphStyle( |
| 'CustomTitle', |
| parent=styles['Heading1'], |
| fontSize=16, |
| spaceAfter=12, |
| textColor='#2E86AB' |
| ) |
| body_style = ParagraphStyle( |
| 'CustomBody', |
| parent=styles['BodyText'], |
| fontSize=11, |
| spaceAfter=6, |
| leading=14 |
| ) |
| |
| story_parts = [] |
| |
| |
| story_parts.append(Paragraph("π¬ AI-Generated Sequel Story", title_style)) |
| story_parts.append(Spacer(1, 0.2*inch)) |
| |
| |
| lines = story_text.split('\n') |
| for line in lines: |
| line = line.strip() |
| if not line: |
| continue |
| if any(marker in line for marker in ['TITLE:', 'LOGLINE:', 'ACT 1:', 'ACT 2:', 'ACT 3:', 'TEASER:']): |
| story_parts.append(Paragraph(f"<b>{line}</b>", body_style)) |
| else: |
| story_parts.append(Paragraph(line, body_style)) |
| story_parts.append(Spacer(1, 0.1*inch)) |
| |
| try: |
| doc.build(story_parts) |
| buffer.seek(0) |
| return buffer.getvalue(), "sequel_story.pdf" |
| except Exception as e: |
| print(f"PDF generation error: {e}") |
| return None, None |
|
|
| |
| with gr.Blocks( |
| title="IdeaForge Studio - Voice your vision. Forge the future.", |
| theme=gr.themes.Soft(), |
| css=""" |
| .gradio-container { |
| max-width: 1200px !important; |
| } |
| """ |
| ) as demo: |
| gr.Markdown(""" |
| # π¬ IdeaForge Studio |
| *Voice your vision. Forge the future.* |
| """) |
| |
| gr.Markdown(""" |
| **How it works:** |
| 1. π Provide the original story (text or voice) |
| 2. π― Set where the sequel begins |
| 3. π Cast your stars |
| 4. π Choose story depth |
| 5. π Generate & export! |
| """) |
| |
| with gr.Tabs(): |
| with gr.TabItem("π€ Voice Input"): |
| with gr.Row(): |
| with gr.Column(scale=2): |
| audio_input = gr.Audio( |
| sources=["microphone"], |
| type="filepath", |
| label="Record Original Plot" |
| ) |
| with gr.Column(scale=3): |
| plot_text = gr.Textbox( |
| label="Transcribed Plot", |
| interactive=True, |
| lines=3, |
| placeholder="Your spoken plot will appear here..." |
| ) |
| voice_btn = gr.Button("π€ Transcribe Voice", variant="secondary") |
| voice_btn.click(speech_to_text, inputs=audio_input, outputs=plot_text) |
| |
| with gr.TabItem("π Text Input"): |
| plot_text = gr.Textbox( |
| label="Original Plot/Theme", |
| placeholder="E.g., 'A young wizard discovers his magical heritage and must defeat the dark lord who killed his parents'", |
| lines=4 |
| ) |
| |
| with gr.Row(): |
| continuation = gr.Textbox( |
| label="π― Sequel Starting Point", |
| placeholder="E.g., 'Five years after the final battle, a new dark force emerges from an unexpected source...'", |
| lines=2, |
| scale=3 |
| ) |
| |
| with gr.Row(): |
| with gr.Column(): |
| stars_multiselect = gr.CheckboxGroup( |
| choices=STARS, |
| label="π Cast Existing Stars", |
| info="Select from popular actors" |
| ) |
| with gr.Column(): |
| custom_star = gr.Textbox( |
| label="β¨ Add Custom Character", |
| placeholder="E.g., 'Zara Voss, time-traveling detective'", |
| info="Create original characters" |
| ) |
| |
| word_slider = gr.Slider( |
| minimum=100, |
| maximum=2000, |
| value=600, |
| step=100, |
| label="π Story Length (Words)", |
| info="Short summary (100) β Detailed treatment (2000)" |
| ) |
| |
| with gr.Row(): |
| generate_btn = gr.Button("π Generate Sequel!", variant="primary", scale=2) |
| pdf_btn = gr.Button("π Export PDF", visible=False, variant="secondary", scale=1) |
| |
| story_output = gr.Markdown( |
| label="π Your Generated Sequel", |
| placeholder="Your AI-generated sequel will appear here..." |
| ) |
| |
| pdf_file = gr.File( |
| label="π₯ Download PDF", |
| visible=False, |
| file_types=[".pdf"] |
| ) |
| |
| def full_generate(audio, plot, cont, stars, custom, words): |
| if audio is not None: |
| transcribed = speech_to_text(audio) |
| if transcribed and not transcribed.startswith("π") and not transcribed.startswith("π€"): |
| plot = transcribed |
| |
| story, btn_visible, file_visible = generate_sequel(plot, cont, stars, custom, words) |
| return story, btn_visible, file_visible |
| |
| def export_pdf(story): |
| pdf_data, filename = create_pdf(story) |
| if pdf_data: |
| return gr.update(value=pdf_data, visible=True, label=f"π₯ Download {filename}") |
| else: |
| return gr.update(visible=False) |
| |
| generate_btn.click( |
| full_generate, |
| inputs=[audio_input, plot_text, continuation, stars_multiselect, custom_star, word_slider], |
| outputs=[story_output, pdf_btn, pdf_file] |
| ) |
| |
| pdf_btn.click(export_pdf, inputs=story_output, outputs=pdf_file) |
| |
| |
| gr.Markdown("### πͺ Quick Start Examples") |
| gr.Examples( |
| examples=[ |
| [ |
| None, |
| "A hobbit's quest to destroy a powerful ring in the fires of Mount Doom", |
| "Years after the ring's destruction, a new shadow grows in the South", |
| ["Elijah Wood", "Ian McKellen", "Orlando Bloom"], |
| "Elara, ranger of the Northern wastes", |
| 800 |
| ], |
| [ |
| None, |
| "80s cop thriller about an undercover detective infiltrating a drug ring", |
| "After the diner shootout, the detective's past comes back to haunt him", |
| ["Al Pacino", "Michelle Pfeiffer"], |
| "Echo Kane, rogue AI informant", |
| 1200 |
| ] |
| ], |
| inputs=[audio_input, plot_text, continuation, stars_multiselect, custom_star, word_slider], |
| outputs=[story_output], |
| fn=full_generate, |
| cache_examples=False |
| ) |
| |
| gr.Markdown("---") |
| gr.Markdown(""" |
| <div style='text-align: center; color: #666;'> |
| <i>Powered by Grok-4 Γ Gradio Γ PiForge</i><br> |
| <small>Voice processing requires microphone access. PDF export works best on desktop.</small> |
| </div> |
| """) |
|
|
| if __name__ == "__main__": |
| demo.launch(share=False, show_error=True) |