programmersd commited on
Commit
246b32e
·
verified ·
1 Parent(s): 084efc7

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +159 -0
app.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PDF → Manim Animation Pipeline
3
+ Hugging Face Spaces — Gradio 6.x
4
+ """
5
+
6
+ import asyncio
7
+ import atexit
8
+ import queue
9
+ import threading
10
+ import uuid
11
+
12
+ import gradio as gr
13
+
14
+ from queue_manager import JobQueue, State
15
+ from pipeline import run_pipeline
16
+
17
+ def _cleanup_event_loop():
18
+ try:
19
+ loop = asyncio.get_event_loop()
20
+ if not loop.is_closed():
21
+ loop.close()
22
+ except Exception:
23
+ pass
24
+
25
+ atexit.register(_cleanup_event_loop)
26
+
27
+ job_queue = JobQueue(max_workers=8, max_jobs=100)
28
+
29
+
30
+ def submit_and_stream(pdf_file, api_key: str):
31
+ """
32
+ Generator yielding exactly 4 values per tick:
33
+ status_md (str)
34
+ code_box (gr.update)
35
+ video_player (gr.update)
36
+ zip_download (gr.update)
37
+ """
38
+
39
+ def _err(msg):
40
+ return (
41
+ msg,
42
+ gr.update(visible=False),
43
+ gr.update(visible=False),
44
+ gr.update(visible=False),
45
+ )
46
+
47
+ if pdf_file is None:
48
+ yield _err("❌ Please upload a PDF file.")
49
+ return
50
+ if not api_key or len(api_key) < 10:
51
+ yield _err("❌ Please enter a valid Gemini API key.")
52
+ return
53
+ if job_queue.is_full():
54
+ yield _err("⚠️ Queue is full (max 100 jobs). Please try again shortly.")
55
+ return
56
+
57
+ job_id = uuid.uuid4().hex
58
+ pdf_path = pdf_file.name
59
+ job_queue.register(job_id)
60
+
61
+ update_q: queue.Queue = queue.Queue()
62
+
63
+ def status_cb(state: State, message: str = "", code: str | None = None):
64
+ update_q.put((state, message, code))
65
+
66
+ result_holder: dict = {}
67
+
68
+ def _run():
69
+ try:
70
+ result = run_pipeline(
71
+ job_id=job_id,
72
+ pdf_path=pdf_path,
73
+ gemini_api_key=api_key,
74
+ status_cb=status_cb,
75
+ )
76
+ result_holder.update(result)
77
+ update_q.put((State.DONE, "✅ Render complete!", result.get("code")))
78
+ except Exception as exc:
79
+ update_q.put((State.FAILED, f"❌ {exc}", None))
80
+ finally:
81
+ update_q.put(None)
82
+
83
+ threading.Thread(target=_run, daemon=True).start()
84
+
85
+ icons = {State.QUEUED: "⏳", State.RUNNING: "⚙️", State.DONE: "✅", State.FAILED: "❌"}
86
+
87
+ code_so_far = ""
88
+
89
+ # Initial tick
90
+ yield (
91
+ f"⏳ **Queued** — Starting…\n\n*Job `{job_id}`*",
92
+ gr.update(visible=False),
93
+ gr.update(visible=False),
94
+ gr.update(visible=False),
95
+ )
96
+
97
+ while True:
98
+ item = update_q.get()
99
+ if item is None:
100
+ break
101
+
102
+ state, message, code = item
103
+ if code:
104
+ code_so_far = code
105
+
106
+ status_text = f"{icons.get(state,'❓')} **{state.value.title()}** — {message}\n\n*Job `{job_id}`*"
107
+ is_done = state == State.DONE
108
+
109
+ yield (
110
+ status_text,
111
+ gr.update(value=code_so_far, visible=bool(code_so_far)),
112
+ gr.update(value=result_holder.get("video_path") if is_done else None, visible=is_done),
113
+ gr.update(value=result_holder.get("zip_path") if is_done else None, visible=is_done),
114
+ )
115
+
116
+
117
+ # ── UI ────────────────────────────────────────────────────────────────────────
118
+ with gr.Blocks(title="PDF → Manim Video") as demo:
119
+
120
+ gr.Markdown("# 🎬 PDF → Manim Animation Pipeline\nUpload a PDF and get a downloadable Manim animation.")
121
+
122
+ saved_api_key = gr.BrowserState("")
123
+
124
+ with gr.Row():
125
+ with gr.Column(scale=1):
126
+ pdf_input = gr.File(label="📄 Upload PDF", file_types=[".pdf"])
127
+ api_key_input = gr.Textbox(
128
+ label="🔑 Gemini API Key",
129
+ placeholder="AIza…",
130
+ type="password",
131
+ info="Saved in your browser — enter once.",
132
+ )
133
+ submit_btn = gr.Button("🚀 Generate Video", variant="primary")
134
+
135
+ with gr.Column(scale=1):
136
+ status_md = gr.Markdown("*Submit a job to see live status here.*")
137
+ code_box = gr.Code(label="📝 Generated Manim Code", language="python", visible=False, interactive=False)
138
+ video_player = gr.Video(label="🎬 Rendered Animation", visible=False, interactive=False)
139
+ zip_download = gr.File(label="⬇️ Download Artifacts (.py + .mp4)", visible=False, interactive=False)
140
+
141
+ gr.Markdown("---\n**Notes:** Processing takes 2–5 minutes. The artifacts ZIP contains the `.py` source and rendered `.mp4`. Your API key is never stored server-side.")
142
+
143
+ demo.load(fn=lambda k: k, inputs=[saved_api_key], outputs=[api_key_input])
144
+ api_key_input.change(fn=lambda v: v, inputs=[api_key_input], outputs=[saved_api_key])
145
+
146
+ submit_btn.click(
147
+ fn=submit_and_stream,
148
+ inputs=[pdf_input, api_key_input],
149
+ outputs=[status_md, code_box, video_player, zip_download],
150
+ )
151
+
152
+
153
+ if __name__ == "__main__":
154
+ demo.launch(
155
+ theme=gr.themes.Soft(),
156
+ ssr_mode=False,
157
+ server_name="0.0.0.0",
158
+ server_port=7860,
159
+ )