slidegent / app.py
jomasego's picture
Upload app.py with huggingface_hub
e40f37d verified
Raw
History Blame Contribute Delete
11.3 kB
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 &nbsp;Β·&nbsp;
<a href="https://www.loom.com/share/d7e25c7a114b4deda6b8de63abcba6a7" target="_blank">β–Ά Demo video</a> &nbsp;Β·&nbsp;
<a href="https://www.linkedin.com/posts/jmsegui_buildsmallhackathon-huggingface-opensource-share-7472435145162080257-qxf5/" target="_blank">LinkedIn post</a> &nbsp;Β·&nbsp;
<a href="https://huggingface.co/build-small-hackathon" target="_blank">Build Small Hackathon</a>
</div>
""")
if __name__ == "__main__":
demo.launch()