Spaces:
Running
Running
| import os | |
| import tempfile | |
| import traceback | |
| import gradio as gr | |
| from slide_generator import generate_presentation | |
| from pptx_builder import build_pptx | |
| STYLES = ["Professional", "Creative", "Academic", "Startup"] | |
| CSS = """ | |
| * { box-sizing: border-box; } | |
| body, .gradio-container { | |
| background: #f0f7f4 !important; | |
| font-family: 'Inter', system-ui, sans-serif !important; | |
| } | |
| footer { display: none !important; } | |
| .header-block { | |
| background: linear-gradient(135deg, #1b6ca8 0%, #19a88a 100%); | |
| border-radius: 16px; padding: 32px 36px 28px; margin-bottom: 24px; | |
| } | |
| button.primary { | |
| background: linear-gradient(135deg, #1b6ca8, #19a88a) !important; | |
| color: #fff !important; border: none !important; | |
| border-radius: 12px !important; font-size: 17px !important; | |
| font-weight: 700 !important; padding: 16px 0 !important; | |
| width: 100% !important; cursor: pointer !important; | |
| box-shadow: 0 4px 16px rgba(25,168,138,0.3) !important; | |
| } | |
| button.primary:hover { opacity: .87 !important; } | |
| textarea, input[type="text"] { | |
| background: #f5fbf9 !important; border: 1.5px solid #b2ddd1 !important; | |
| border-radius: 10px !important; color: #1a3a3a !important; font-size: 14px !important; | |
| } | |
| input[type="range"] { accent-color: #19a88a !important; } | |
| .status-ok { | |
| background: #e6f7f2; border: 1px solid #a8dfd0; border-radius: 10px; | |
| padding: 12px 18px; font-size: 14px; color: #1a5a4a; margin-bottom: 8px; | |
| } | |
| .status-wait { | |
| background: #f0f7ff; border: 1px solid #b2cfe8; border-radius: 10px; | |
| padding: 12px 18px; font-size: 14px; color: #1a3a6a; margin-bottom: 8px; | |
| } | |
| .preview-md, .preview-md p, .preview-md li, | |
| .preview-md h1, .preview-md h2, .preview-md h3 { color: #0d1b2a !important; } | |
| .preview-md { | |
| background: #f5fbf9 !important; border: 1px solid #c8e8df !important; | |
| border-radius: 12px !important; padding: 16px 20px !important; | |
| min-height: 220px !important; max-height: 420px !important; | |
| overflow-y: auto !important; font-size: 14px !important; line-height: 1.75 !important; | |
| } | |
| .preview-md h1 { color: #1b6ca8 !important; font-size: 18px !important; } | |
| .preview-md h3 { color: #19a88a !important; font-size: 14px !important; margin: 10px 0 4px !important; } | |
| .preview-md blockquote { border-left: 3px solid #19a88a; padding-left: 10px; } | |
| .preview-md em { color: #5a8a8a !important; font-size: 12px !important; } | |
| """ | |
| def format_preview(data): | |
| lines = [f"# {data.get('title','')}", ""] | |
| if data.get("subtitle"): | |
| lines += [f"*{data['subtitle']}*", ""] | |
| lines.append("---") | |
| for slide in data.get("slides", []): | |
| num = slide.get("slide_number", "") | |
| title = slide.get("title", "") | |
| kw = slide.get("image_keyword", "") | |
| if slide.get("type") == "title": | |
| lines.append(f"\n### 🎯 Slide {num} — {title}") | |
| if slide.get("subtitle"): | |
| lines.append(f"> {slide['subtitle']}") | |
| else: | |
| lines.append(f"\n### 📄 Slide {num} — {title}") | |
| if kw: | |
| lines.append(f"*📸 Image: {kw}*") | |
| for b in slide.get("bullets", []): | |
| lines.append(f"- {b}") | |
| if slide.get("speaker_notes"): | |
| lines.append(f"\n*🗒 {slide['speaker_notes']}*") | |
| return "\n".join(lines) | |
| def generate_and_download(topic, audience, style, num_slides, key_points, | |
| progress=gr.Progress()): | |
| if not topic.strip(): | |
| raise gr.Error("Please enter a topic.") | |
| if not audience.strip(): | |
| raise gr.Error("Please enter the target audience.") | |
| try: | |
| progress(0.1, desc="AI is writing your slides…") | |
| data = generate_presentation( | |
| topic=topic.strip(), style=style, num_slides=int(num_slides), | |
| audience=audience.strip(), key_points=key_points.strip(), | |
| ) | |
| progress(0.65, desc="Fetching images & building PPTX…") | |
| pptx_bytes = build_pptx(data, style) | |
| tmp_dir = tempfile.mkdtemp() | |
| safe = topic[:30].replace(" ", "_").replace("/", "-") | |
| out_path = os.path.join(tmp_dir, f"{safe}.pptx") | |
| with open(out_path, "wb") as f: | |
| f.write(pptx_bytes) | |
| progress(1.0, desc="Done!") | |
| n = len(data.get("slides", [])) | |
| status = f"<div class='status-ok'>✅ <strong>{n} slides</strong> with images — download below!</div>" | |
| return format_preview(data), out_path, status | |
| except gr.Error: | |
| raise | |
| except Exception: | |
| raise gr.Error(traceback.format_exc()) | |
| with gr.Blocks(title="SlideAI", css=CSS) as demo: | |
| gr.HTML(""" | |
| <div class="header-block"> | |
| <h1 style="margin:0;font-size:28px;font-weight:800;color:#fff;">SlideAI</h1> | |
| <p style="margin:6px 0 0;font-size:14px;color:rgba(255,255,255,.88);"> | |
| Turn any topic into a polished, image-rich, download-ready presentation. | |
| </p> | |
| </div>""") | |
| with gr.Row(): | |
| with gr.Column(scale=1, min_width=300): | |
| topic = gr.Textbox(label="Topic *", placeholder="e.g. Climate Change…", lines=2) | |
| audience = gr.Textbox(label="Target Audience *", placeholder="e.g. Students…", lines=1) | |
| with gr.Row(): | |
| style = gr.Dropdown(choices=STYLES, value="Professional", label="Style", scale=1) | |
| num_slides = gr.Slider(minimum=5, maximum=15, step=1, value=8, label="Slides", scale=1) | |
| key_points = gr.Textbox(label="Key Points (optional)", | |
| placeholder="Specific facts or ideas to include…", lines=2) | |
| btn = gr.Button("✨ Generate Presentation", variant="primary", size="lg") | |
| with gr.Column(scale=2, min_width=380): | |
| status = gr.HTML("<div class='status-wait'>Fill in the form and hit <strong>Generate</strong>.</div>") | |
| preview = gr.Markdown(elem_classes=["preview-md"]) | |
| download = gr.File(label="📥 Download your PPTX", interactive=False) | |
| btn.click(fn=generate_and_download, | |
| inputs=[topic, audience, style, num_slides, key_points], | |
| outputs=[preview, download, status]) | |
| if __name__ == "__main__": | |
| demo.launch() | |