Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import os | |
| import threading | |
| import gradio as gr | |
| from src.llm.gemma_client import should_startup_warmup, warmup_model_cache | |
| from src.services.customization_service import customize_presentation | |
| _WARMUP_STATE = {"status": "idle", "message": "Warmup not started."} | |
| def _run_startup_warmup() -> None: | |
| try: | |
| backend = os.getenv("MODEL_BACKEND", "transformers") | |
| _WARMUP_STATE["status"] = "running" | |
| _WARMUP_STATE["message"] = f"Startup warmup running for backend: {backend}" | |
| result = warmup_model_cache(backend=backend) | |
| _WARMUP_STATE["status"] = "done" | |
| _WARMUP_STATE["message"] = f"Startup warmup complete. {result}" | |
| except Exception as exc: # pragma: no cover - best-effort startup hook | |
| _WARMUP_STATE["status"] = "error" | |
| _WARMUP_STATE["message"] = f"Startup warmup failed: {exc}" | |
| def _start_warmup_if_needed() -> None: | |
| if not should_startup_warmup(): | |
| _WARMUP_STATE["status"] = "disabled" | |
| _WARMUP_STATE["message"] = "Startup warmup disabled by environment policy." | |
| return | |
| thread = threading.Thread(target=_run_startup_warmup, daemon=True, name="startup-warmup") | |
| thread.start() | |
| _start_warmup_if_needed() | |
| EXAMPLE_PROMPTS = [ | |
| "5-slide investor update: company vision, product roadmap, Q3 milestones, team traction, and next-quarter asks. Professional tone.", | |
| "3-slide executive summary on AI workflow automation for operations leaders. Concise bullets, business-friendly language.", | |
| "6-slide product launch deck: problem statement, solution overview, key features, competitive landscape, go-to-market strategy, and call to action.", | |
| "4-slide team onboarding overview: company culture, tools and processes, 30-60-90 day plan, and key contacts.", | |
| ] | |
| CSS = """ | |
| /* ββ page background ββ */ | |
| .gradio-container { | |
| background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 50%, #16213e 100%) !important; | |
| min-height: 100vh; | |
| font-family: 'Inter', system-ui, sans-serif; | |
| } | |
| /* ββ hero header ββ */ | |
| .slidegent-hero { | |
| text-align: center; | |
| padding: 2.5rem 1rem 1.5rem; | |
| border-bottom: 1px solid rgba(99, 102, 241, 0.2); | |
| margin-bottom: 1.5rem; | |
| } | |
| .slidegent-hero h1 { | |
| font-size: 2.8rem; | |
| font-weight: 800; | |
| background: linear-gradient(90deg, #818cf8, #c084fc, #fb7185); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin: 0 0 0.4rem; | |
| letter-spacing: -1px; | |
| } | |
| .slidegent-hero p { | |
| color: #94a3b8; | |
| font-size: 1.05rem; | |
| margin: 0; | |
| } | |
| .slidegent-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.4rem; | |
| background: rgba(99,102,241,0.15); | |
| border: 1px solid rgba(99,102,241,0.35); | |
| border-radius: 9999px; | |
| padding: 0.25rem 0.85rem; | |
| font-size: 0.78rem; | |
| color: #a5b4fc; | |
| margin-top: 0.75rem; | |
| text-decoration: none; | |
| } | |
| /* ββ panels ββ */ | |
| .panel-card { | |
| background: rgba(255,255,255,0.04) !important; | |
| border: 1px solid rgba(255,255,255,0.08) !important; | |
| border-radius: 16px !important; | |
| padding: 1.5rem !important; | |
| backdrop-filter: blur(8px); | |
| } | |
| /* ββ labels ββ */ | |
| label span, .gr-form label { | |
| color: #cbd5e1 !important; | |
| font-weight: 500 !important; | |
| font-size: 0.88rem !important; | |
| letter-spacing: 0.02em !important; | |
| } | |
| /* ββ file upload & textbox borders ββ */ | |
| .gr-file, .gr-textbox textarea, input[type=text], textarea { | |
| background: rgba(15,15,30,0.6) !important; | |
| border: 1px solid rgba(99,102,241,0.3) !important; | |
| border-radius: 10px !important; | |
| color: #e2e8f0 !important; | |
| } | |
| .gr-file:hover, textarea:focus { | |
| border-color: rgba(99,102,241,0.7) !important; | |
| outline: none !important; | |
| box-shadow: 0 0 0 3px rgba(99,102,241,0.15) !important; | |
| } | |
| /* ββ generate button ββ */ | |
| #generate-btn { | |
| background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%) !important; | |
| border: none !important; | |
| border-radius: 12px !important; | |
| color: white !important; | |
| font-size: 1rem !important; | |
| font-weight: 700 !important; | |
| letter-spacing: 0.03em !important; | |
| padding: 0.85rem 2rem !important; | |
| transition: all 0.2s ease !important; | |
| box-shadow: 0 4px 20px rgba(99,102,241,0.4) !important; | |
| width: 100% !important; | |
| } | |
| #generate-btn:hover { | |
| transform: translateY(-1px) !important; | |
| box-shadow: 0 6px 28px rgba(99,102,241,0.55) !important; | |
| } | |
| #generate-btn:active { | |
| transform: translateY(0) !important; | |
| } | |
| /* ββ example buttons ββ */ | |
| .gr-examples button { | |
| background: rgba(99,102,241,0.1) !important; | |
| border: 1px solid rgba(99,102,241,0.25) !important; | |
| border-radius: 8px !important; | |
| color: #a5b4fc !important; | |
| font-size: 0.8rem !important; | |
| transition: all 0.15s !important; | |
| } | |
| .gr-examples button:hover { | |
| background: rgba(99,102,241,0.22) !important; | |
| border-color: rgba(99,102,241,0.5) !important; | |
| } | |
| /* ββ output download area ββ */ | |
| .output-area { | |
| border: 1px dashed rgba(99,102,241,0.35) !important; | |
| border-radius: 14px !important; | |
| background: rgba(99,102,241,0.05) !important; | |
| min-height: 80px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| /* ββ how-it-works row ββ */ | |
| .how-step { | |
| text-align: center; | |
| padding: 1rem; | |
| } | |
| .how-step .icon { font-size: 2rem; margin-bottom: 0.4rem; } | |
| .how-step .title { color: #e2e8f0; font-weight: 600; font-size: 0.9rem; } | |
| .how-step .desc { color: #64748b; font-size: 0.78rem; margin-top: 0.2rem; } | |
| /* ββ footer ββ */ | |
| .slidegent-footer { | |
| text-align: center; | |
| padding: 1.5rem 0 0.5rem; | |
| color: #475569; | |
| font-size: 0.8rem; | |
| border-top: 1px solid rgba(255,255,255,0.05); | |
| margin-top: 2rem; | |
| } | |
| .slidegent-footer a { color: #818cf8; text-decoration: none; } | |
| .slidegent-footer a:hover { text-decoration: underline; } | |
| """ | |
| def run_pipeline(template_file, prompt_text): | |
| return customize_presentation(template_file, prompt_text) | |
| with gr.Blocks( | |
| title="Slidegent β AI Presentation Generator", | |
| css=CSS, | |
| theme=gr.themes.Base( | |
| primary_hue=gr.themes.colors.indigo, | |
| secondary_hue=gr.themes.colors.purple, | |
| neutral_hue=gr.themes.colors.slate, | |
| font=gr.themes.GoogleFont("Inter"), | |
| ).set( | |
| body_background_fill="#0f0f1a", | |
| block_background_fill="rgba(255,255,255,0.03)", | |
| block_border_color="rgba(255,255,255,0.08)", | |
| block_label_text_color="#94a3b8", | |
| input_background_fill="rgba(15,15,30,0.6)", | |
| input_border_color="rgba(99,102,241,0.3)", | |
| button_primary_background_fill="linear-gradient(135deg, #6366f1, #8b5cf6)", | |
| button_primary_text_color="white", | |
| ), | |
| ) as demo: | |
| # ββ Hero ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| gr.HTML(""" | |
| <div class="slidegent-hero"> | |
| <h1>β¦ Slidegent</h1> | |
| <p>Upload your branded template Β· describe what you need Β· download a polished deck</p> | |
| <a class="slidegent-badge" href="https://huggingface.co/build-small-hackathon" target="_blank"> | |
| ποΈ Build Small Hackathon 2026 | |
| </a> | |
| </div> | |
| """) | |
| # ββ How it works ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| gr.HTML(""" | |
| <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:0.5rem;margin-bottom:1.5rem;"> | |
| <div class="how-step"> | |
| <div class="icon">π</div> | |
| <div class="title">1 Β· Upload template</div> | |
| <div class="desc">Any .pptx β your brand colors, fonts and layout</div> | |
| </div> | |
| <div class="how-step"> | |
| <div class="icon">βοΈ</div> | |
| <div class="title">2 Β· Describe your deck</div> | |
| <div class="desc">Topic, tone, number of slides β plain English</div> | |
| </div> | |
| <div class="how-step"> | |
| <div class="icon">β‘</div> | |
| <div class="title">3 Β· Download</div> | |
| <div class="desc">Gemma 4 12B generates slides styled to your template</div> | |
| </div> | |
| </div> | |
| """) | |
| # ββ Main inputs βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Row(equal_height=True): | |
| with gr.Column(scale=1, elem_classes="panel-card"): | |
| gr.Markdown("### π Your Template") | |
| template_input = gr.File( | |
| label="Upload .pptx template", | |
| file_types=[".pptx"], | |
| height=140, | |
| ) | |
| gr.Markdown( | |
| "<span style='color:#64748b;font-size:0.78rem;'>The app reads your template's fonts, colors and layout β and mirrors them in the output.</span>" | |
| ) | |
| with gr.Column(scale=2, elem_classes="panel-card"): | |
| gr.Markdown("### π¬ What do you need?") | |
| prompt_input = gr.Textbox( | |
| label="Describe your presentation", | |
| lines=5, | |
| placeholder="e.g. 5-slide investor update: company vision, product roadmap, Q3 milestones, team traction, and next-quarter asks. Professional tone.", | |
| ) | |
| gr.Examples( | |
| examples=[[p] for p in EXAMPLE_PROMPTS], | |
| inputs=[prompt_input], | |
| label="β¨ Quick examples β click to fill", | |
| examples_per_page=4, | |
| ) | |
| # ββ Action & output βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Row(): | |
| with gr.Column(): | |
| generate_btn = gr.Button( | |
| "β‘ Generate Presentation", | |
| variant="primary", | |
| elem_id="generate-btn", | |
| ) | |
| with gr.Row(): | |
| with gr.Column(elem_classes="output-area"): | |
| output_file = gr.File( | |
| label="β¬οΈ Your generated presentation", | |
| interactive=False, | |
| ) | |
| generate_btn.click( | |
| fn=run_pipeline, | |
| inputs=[template_input, prompt_input], | |
| outputs=[output_file], | |
| ) | |
| # ββ Footer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| gr.HTML(""" | |
| <div class="slidegent-footer"> | |
| Powered by <strong style="color:#a5b4fc">google/gemma-4-12b-it</strong> on ZeroGPU Β· | |
| <a href="https://www.loom.com/share/d7e25c7a114b4deda6b8de63abcba6a7" target="_blank">βΆ Demo video</a> Β· | |
| <a href="https://www.linkedin.com/posts/jmsegui_buildsmallhackathon-huggingface-opensource-share-7472435145162080257-qxf5/" target="_blank">LinkedIn post</a> Β· | |
| <a href="https://huggingface.co/build-small-hackathon" target="_blank">Build Small Hackathon</a> | |
| </div> | |
| """) | |
| if __name__ == "__main__": | |
| demo.launch() |