File size: 6,135 Bytes
46af8e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6b4926b
46af8e9
 
 
6b4926b
 
 
 
46af8e9
 
 
 
6b4926b
 
46af8e9
 
 
6b4926b
 
46af8e9
 
6b4926b
 
46af8e9
6b4926b
 
 
 
 
 
 
 
 
 
 
 
46af8e9
 
 
6b4926b
 
46af8e9
 
 
 
 
 
6b4926b
46af8e9
 
 
 
 
 
6b4926b
 
46af8e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6b4926b
 
46af8e9
6b4926b
46af8e9
 
 
 
 
 
 
 
6b4926b
46af8e9
 
 
 
 
 
 
 
 
 
 
 
6b4926b
46af8e9
 
 
 
 
6b4926b
 
46af8e9
6b4926b
 
46af8e9
 
 
 
 
6b4926b
 
46af8e9
 
6b4926b
 
 
46af8e9
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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()