m3n99 commited on
Commit
f7cd3ef
·
0 Parent(s):
Files changed (5) hide show
  1. .gitattributes +35 -0
  2. .gitignore +22 -0
  3. README.md +24 -0
  4. app.py +447 -0
  5. requirements.txt +3 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### AL ###
2
+ #Template for AL projects for Dynamics 365 Business Central
3
+ #launch.json folder
4
+ .vscode/
5
+ #Cache folder
6
+ .alcache/
7
+ #Symbols folder
8
+ .alpackages/
9
+ #Snapshots folder
10
+ .snapshots/
11
+ #Testing Output folder
12
+ .output/
13
+ #Extension App-file
14
+ *.app
15
+ #Rapid Application Development File
16
+ rad.json
17
+ #Translation Base-file
18
+ *.g.xlf
19
+ #License-file
20
+ *.flf
21
+ #Test results file
22
+ TestResults.xml
README.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Prompt2Project
3
+ emoji: 🚀
4
+ colorFrom: indigo
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: 4.44.1
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # 🚀 Prompt2Project
14
+
15
+ An AI-powered project idea generator that creates detailed, structured project plans as downloadable PDFs.
16
+
17
+ **Powered by Google Gemini 1.5 Flash** — enter a topic, pick your skill level, and get a complete project blueprint including a ready-to-use Master Prompt you can feed into any AI assistant.
18
+
19
+ ## Features
20
+
21
+ - 📝 **Structured Project Plans** — title, description, goals, step-by-step instructions, tools, tips, bonus challenges
22
+ - 🧠 **Master Prompt** — a detailed, 1000+ word prompt you can copy into ChatGPT, Claude, or Gemini for hands-on help
23
+ - 📄 **PDF Export** — beautifully formatted, downloadable PDF with cover page and UTF-8 support
24
+ - 🎯 **Customizable** — choose topic, skill level (Beginner/Intermediate/Advanced), and difficulty (1–10)
app.py ADDED
@@ -0,0 +1,447 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+ import re
4
+ import tempfile
5
+ import textwrap
6
+ import urllib.request
7
+ import zipfile
8
+
9
+ import gradio as gr
10
+ import google.generativeai as genai
11
+ from fpdf import FPDF
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Gemini setup
15
+ # ---------------------------------------------------------------------------
16
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Font helper — bundle DejaVuSans for full UTF-8 PDF support
20
+ # ---------------------------------------------------------------------------
21
+ FONT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fonts")
22
+ FONT_PATH = os.path.join(FONT_DIR, "DejaVuSans.ttf")
23
+ FONT_BOLD_PATH = os.path.join(FONT_DIR, "DejaVuSans-Bold.ttf")
24
+
25
+ DEJAVU_ZIP_URL = (
26
+ "https://github.com/dejavu-fonts/dejavu-fonts/releases/download/"
27
+ "version_2_37/dejavu-fonts-ttf-2.37.zip"
28
+ )
29
+
30
+
31
+ def _ensure_fonts():
32
+ """Download DejaVu fonts from the v2.37 release if not already present."""
33
+ if os.path.exists(FONT_PATH) and os.path.exists(FONT_BOLD_PATH):
34
+ return
35
+ os.makedirs(FONT_DIR, exist_ok=True)
36
+ # Download the release zip and extract just the two TTFs we need
37
+ data = urllib.request.urlopen(DEJAVU_ZIP_URL).read()
38
+ with zipfile.ZipFile(io.BytesIO(data)) as zf:
39
+ for name in zf.namelist():
40
+ basename = os.path.basename(name)
41
+ if basename == "DejaVuSans.ttf":
42
+ with open(FONT_PATH, "wb") as f:
43
+ f.write(zf.read(name))
44
+ elif basename == "DejaVuSans-Bold.ttf":
45
+ with open(FONT_BOLD_PATH, "wb") as f:
46
+ f.write(zf.read(name))
47
+
48
+
49
+ _ensure_fonts()
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # Prompt template
53
+ # ---------------------------------------------------------------------------
54
+ SYSTEM_PROMPT = textwrap.dedent("""\
55
+ You are an expert project mentor and curriculum designer. Given a **topic**, a
56
+ **skill level**, and a **project difficulty** (1-10 scale), generate a complete,
57
+ structured project plan in Markdown with the following sections. Use the EXACT
58
+ headers shown below (including the ## markdown prefix). Do NOT add any extra
59
+ top-level headers.
60
+
61
+ ## Project Title
62
+ A creative, descriptive title for the project.
63
+
64
+ ## Project Description
65
+ A clear 3-5 sentence overview of what the project is and why it matters.
66
+
67
+ ## Goals & Learning Outcomes
68
+ A bulleted list of 5-8 concrete things the learner will achieve.
69
+
70
+ ## Step-by-Step Instructions
71
+ Provide **at least 7** numbered steps. Each step should have a brief title in
72
+ bold followed by 2-4 sentences of detailed explanation.
73
+
74
+ ## Tools & Technologies Needed
75
+ A bulleted list of every tool, library, framework, language, and service the
76
+ learner will use, with a one-line note on what each is used for.
77
+
78
+ ## Tips & Common Mistakes
79
+ A bulleted list of at least 5 practical tips and pitfalls to avoid.
80
+
81
+ ## Bonus Challenges
82
+ A bulleted list of 3-5 stretch goals that push the learner further.
83
+
84
+ ## Master Prompt
85
+ Write a **minimum of 1000 words**. This section is a powerful, detailed,
86
+ ready-to-use AI prompt that the reader can copy and paste into any AI tool
87
+ (ChatGPT, Claude, Gemini, etc.) to get full hands-on help building this exact
88
+ project. The prompt MUST include ALL of the following:
89
+ - Full project context and background
90
+ - Expected inputs and outputs with concrete examples
91
+ - Architecture guidance (folder structure, modules, data flow)
92
+ - Coding style preferences (naming conventions, comments, error handling)
93
+ - Edge cases to handle and how to test them
94
+ - How to ask follow-up questions effectively
95
+ - A checklist the AI can use to verify its own output
96
+ Wrap the entire master prompt content inside a fenced code block so the user
97
+ can copy it easily.
98
+ """)
99
+
100
+
101
+ def build_user_prompt(topic: str, level: str, difficulty: int) -> str:
102
+ return (
103
+ f"**Topic:** {topic}\n"
104
+ f"**Skill Level:** {level}\n"
105
+ f"**Project Difficulty:** {difficulty}/10\n\n"
106
+ "Generate the full project plan now."
107
+ )
108
+
109
+
110
+ # ---------------------------------------------------------------------------
111
+ # Gemini API call
112
+ # ---------------------------------------------------------------------------
113
+ def call_gemini(topic: str, level: str, difficulty: int) -> str:
114
+ if not GEMINI_API_KEY:
115
+ raise gr.Error(
116
+ "GEMINI_API_KEY is not set. Add it as a Secret in your "
117
+ "Hugging Face Space settings."
118
+ )
119
+
120
+ genai.configure(api_key=GEMINI_API_KEY)
121
+ model = genai.GenerativeModel(
122
+ "gemini-1.5-flash",
123
+ system_instruction=SYSTEM_PROMPT,
124
+ )
125
+ response = model.generate_content(
126
+ build_user_prompt(topic, level, difficulty),
127
+ generation_config=genai.GenerationConfig(
128
+ temperature=0.9,
129
+ max_output_tokens=8192,
130
+ ),
131
+ )
132
+ return response.text
133
+
134
+
135
+ # ---------------------------------------------------------------------------
136
+ # Markdown → section parser
137
+ # ---------------------------------------------------------------------------
138
+ SECTION_TITLES = [
139
+ "Project Title",
140
+ "Project Description",
141
+ "Goals & Learning Outcomes",
142
+ "Step-by-Step Instructions",
143
+ "Tools & Technologies Needed",
144
+ "Tips & Common Mistakes",
145
+ "Bonus Challenges",
146
+ "Master Prompt",
147
+ ]
148
+
149
+
150
+ def parse_sections(md_text: str) -> dict[str, str]:
151
+ """Split markdown text on ## headers into {title: body} dict."""
152
+ pattern = r"^##\s+" + r"|^##\s+".join(re.escape(t) for t in SECTION_TITLES)
153
+ parts = re.split(r"(?m)^##\s+", md_text)
154
+
155
+ sections: dict[str, str] = {}
156
+ for part in parts:
157
+ if not part.strip():
158
+ continue
159
+ first_line, _, body = part.partition("\n")
160
+ title = first_line.strip().rstrip("#").strip()
161
+ sections[title] = body.strip()
162
+ return sections
163
+
164
+
165
+ # ---------------------------------------------------------------------------
166
+ # PDF generator
167
+ # ---------------------------------------------------------------------------
168
+ class ProjectPDF(FPDF):
169
+ """Custom FPDF with DejaVu font and helper methods."""
170
+
171
+ DARK = (30, 30, 46)
172
+ ACCENT = (114, 137, 218)
173
+ LIGHT = (248, 248, 242)
174
+ SUBTEXT = (166, 172, 205)
175
+
176
+ def __init__(self):
177
+ super().__init__()
178
+ self.add_font("DejaVu", "", FONT_PATH, uni=True)
179
+ self.add_font("DejaVu", "B", FONT_BOLD_PATH, uni=True)
180
+ self.set_auto_page_break(auto=True, margin=20)
181
+
182
+ # -- cover page ----------------------------------------------------------
183
+ def cover_page(self, title: str, topic: str, level: str, difficulty: int):
184
+ self.add_page()
185
+ # Background
186
+ self.set_fill_color(*self.DARK)
187
+ self.rect(0, 0, 210, 297, "F")
188
+
189
+ # Accent bar
190
+ self.set_fill_color(*self.ACCENT)
191
+ self.rect(0, 80, 210, 6, "F")
192
+
193
+ # Title
194
+ self.set_y(100)
195
+ self.set_font("DejaVu", "B", 26)
196
+ self.set_text_color(*self.LIGHT)
197
+ self.multi_cell(0, 14, title, align="C")
198
+
199
+ # Metadata
200
+ self.ln(12)
201
+ self.set_font("DejaVu", "", 13)
202
+ self.set_text_color(*self.SUBTEXT)
203
+ meta = f"Topic: {topic} | Level: {level} | Difficulty: {difficulty}/10"
204
+ self.cell(0, 10, meta, align="C", new_x="LMARGIN", new_y="NEXT")
205
+
206
+ # Tagline
207
+ self.ln(6)
208
+ self.set_font("DejaVu", "", 11)
209
+ self.cell(0, 10, "Generated by Prompt2Project — powered by Google Gemini",
210
+ align="C", new_x="LMARGIN", new_y="NEXT")
211
+
212
+ # -- section header ------------------------------------------------------
213
+ def section_header(self, title: str):
214
+ self.ln(6)
215
+ self.set_fill_color(*self.ACCENT)
216
+ self.set_text_color(255, 255, 255)
217
+ self.set_font("DejaVu", "B", 14)
218
+ self.cell(0, 10, f" {title}", fill=True, new_x="LMARGIN", new_y="NEXT")
219
+ self.ln(4)
220
+
221
+ # -- body text -----------------------------------------------------------
222
+ def body_text(self, text: str):
223
+ self.set_text_color(50, 50, 50)
224
+ self.set_font("DejaVu", "", 10)
225
+ for line in text.split("\n"):
226
+ stripped = line.strip()
227
+ if not stripped:
228
+ self.ln(3)
229
+ continue
230
+
231
+ # Detect bullet points
232
+ if stripped.startswith(("- ", "* ", "• ")):
233
+ bullet_text = stripped.lstrip("-*• ").strip()
234
+ self.cell(6)
235
+ self.set_font("DejaVu", "B", 10)
236
+ self.cell(5, 6, "•")
237
+ self.set_font("DejaVu", "", 10)
238
+ # Use multi_cell for wrapping, indented
239
+ x = self.get_x()
240
+ w = self.w - self.r_margin - x
241
+ self.multi_cell(w, 6, bullet_text)
242
+ # Detect numbered steps
243
+ elif re.match(r"^\d+[\.\)]\s", stripped):
244
+ num, _, rest = stripped.partition(" ")
245
+ self.set_font("DejaVu", "B", 10)
246
+ self.cell(10, 6, num)
247
+ self.set_font("DejaVu", "", 10)
248
+ x = self.get_x()
249
+ w = self.w - self.r_margin - x
250
+ self.multi_cell(w, 6, rest)
251
+ # Bold sub‑headings (lines that start and end with **)
252
+ elif stripped.startswith("**") and "**" in stripped[2:]:
253
+ clean = stripped.replace("**", "")
254
+ self.set_font("DejaVu", "B", 11)
255
+ self.multi_cell(0, 7, clean)
256
+ self.set_font("DejaVu", "", 10)
257
+ else:
258
+ # Remove inline markdown bold markers for cleaner PDF
259
+ clean = stripped.replace("**", "")
260
+ self.multi_cell(0, 6, clean)
261
+
262
+ # -- code block (for master prompt) --------------------------------------
263
+ def code_block(self, text: str):
264
+ self.set_fill_color(240, 240, 240)
265
+ self.set_text_color(40, 40, 40)
266
+ self.set_font("DejaVu", "", 9)
267
+ # Remove fenced code block markers
268
+ text = re.sub(r"^```[a-zA-Z]*\n?", "", text)
269
+ text = re.sub(r"\n?```$", "", text)
270
+ for line in text.split("\n"):
271
+ bg_w = self.w - self.l_margin - self.r_margin
272
+ self.cell(bg_w, 5.5, "", fill=True, new_x="LMARGIN", new_y="NEXT")
273
+ self.set_y(self.get_y() - 5.5)
274
+ self.cell(3)
275
+ x = self.get_x()
276
+ w = self.w - self.r_margin - x - 2
277
+ self.multi_cell(w, 5.5, line)
278
+
279
+
280
+ def generate_pdf(
281
+ sections: dict[str, str],
282
+ topic: str,
283
+ level: str,
284
+ difficulty: int,
285
+ ) -> str:
286
+ pdf = ProjectPDF()
287
+
288
+ title = sections.get("Project Title", "Untitled Project")
289
+
290
+ # Cover
291
+ pdf.cover_page(title, topic, level, difficulty)
292
+
293
+ # Content pages
294
+ pdf.add_page()
295
+ for sec_title in SECTION_TITLES:
296
+ if sec_title == "Project Title":
297
+ continue # already on cover
298
+ body = sections.get(sec_title, "")
299
+ if not body:
300
+ continue
301
+
302
+ # Master Prompt starts on its own page
303
+ if sec_title == "Master Prompt":
304
+ pdf.add_page()
305
+
306
+ pdf.section_header(sec_title)
307
+
308
+ if sec_title == "Master Prompt":
309
+ # Render the code block portion nicely
310
+ pdf.body_text(body)
311
+ else:
312
+ pdf.body_text(body)
313
+
314
+ # Save to temp file
315
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf",
316
+ prefix="prompt2project_")
317
+ pdf.output(tmp.name)
318
+ return tmp.name
319
+
320
+
321
+ # ---------------------------------------------------------------------------
322
+ # Main handler
323
+ # ---------------------------------------------------------------------------
324
+ def generate_project(topic: str, level: str, difficulty: int):
325
+ if not topic or not topic.strip():
326
+ raise gr.Error("Please enter a topic.")
327
+
328
+ md_text = call_gemini(topic.strip(), level, int(difficulty))
329
+ sections = parse_sections(md_text)
330
+
331
+ pdf_path = generate_pdf(sections, topic.strip(), level, int(difficulty))
332
+
333
+ return md_text, pdf_path
334
+
335
+
336
+ # ---------------------------------------------------------------------------
337
+ # Gradio UI
338
+ # ---------------------------------------------------------------------------
339
+ THEME = gr.themes.Soft(
340
+ primary_hue=gr.themes.colors.indigo,
341
+ secondary_hue=gr.themes.colors.purple,
342
+ font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
343
+ )
344
+
345
+ CSS = """
346
+ #header {
347
+ text-align: center;
348
+ padding: 1.5rem 0 0.5rem;
349
+ }
350
+ #header h1 {
351
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
352
+ -webkit-background-clip: text;
353
+ -webkit-text-fill-color: transparent;
354
+ font-size: 2.4rem;
355
+ font-weight: 800;
356
+ margin-bottom: 0.25rem;
357
+ }
358
+ #header p {
359
+ color: #888;
360
+ font-size: 1.05rem;
361
+ }
362
+ #generate-btn {
363
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
364
+ color: white !important;
365
+ font-weight: 700 !important;
366
+ font-size: 1.1rem !important;
367
+ border: none !important;
368
+ padding: 12px 32px !important;
369
+ border-radius: 10px !important;
370
+ transition: transform 0.15s, box-shadow 0.15s !important;
371
+ }
372
+ #generate-btn:hover {
373
+ transform: translateY(-2px) !important;
374
+ box-shadow: 0 6px 20px rgba(102,126,234,0.4) !important;
375
+ }
376
+ .output-markdown {
377
+ border: 1px solid #e2e8f0;
378
+ border-radius: 12px;
379
+ padding: 1.5rem;
380
+ background: #fafbff;
381
+ }
382
+ footer { display: none !important; }
383
+ """
384
+
385
+ with gr.Blocks(theme=THEME, css=CSS, title="Prompt2Project") as demo:
386
+ # Header
387
+ gr.HTML(
388
+ """
389
+ <div id="header">
390
+ <h1>🚀 Prompt2Project</h1>
391
+ <p>Generate a complete project plan &amp; Master Prompt — powered by Gemini</p>
392
+ </div>
393
+ """
394
+ )
395
+
396
+ with gr.Row(equal_height=True):
397
+ with gr.Column(scale=1, min_width=300):
398
+ gr.Markdown("### ⚙️ Configuration")
399
+ topic_input = gr.Textbox(
400
+ label="📌 Topic",
401
+ placeholder="e.g. Machine Learning, Web Development, Game Design…",
402
+ lines=1,
403
+ max_lines=1,
404
+ )
405
+ level_input = gr.Dropdown(
406
+ label="📊 Skill Level",
407
+ choices=["Beginner", "Intermediate", "Advanced"],
408
+ value="Intermediate",
409
+ )
410
+ difficulty_input = gr.Slider(
411
+ label="🎯 Project Difficulty",
412
+ minimum=1,
413
+ maximum=10,
414
+ step=1,
415
+ value=5,
416
+ )
417
+ generate_btn = gr.Button("✨ Generate Project", elem_id="generate-btn")
418
+
419
+ gr.Markdown("---")
420
+ gr.Markdown(
421
+ "**📥 Download your PDF below** after generating.",
422
+ elem_classes=["note"],
423
+ )
424
+ pdf_output = gr.File(label="📄 Project PDF", interactive=False)
425
+
426
+ with gr.Column(scale=2, min_width=500):
427
+ gr.Markdown("### 📋 Generated Project Plan")
428
+ md_output = gr.Markdown(
429
+ value="*Your project plan will appear here…*",
430
+ elem_classes=["output-markdown"],
431
+ )
432
+
433
+ generate_btn.click(
434
+ fn=generate_project,
435
+ inputs=[topic_input, level_input, difficulty_input],
436
+ outputs=[md_output, pdf_output],
437
+ )
438
+
439
+ gr.Markdown(
440
+ "<center style='color:#aaa;font-size:0.85rem;margin-top:1rem;'>"
441
+ "Built with ❤️ using Gradio &amp; Google Gemini &nbsp;|&nbsp; "
442
+ "Deploy your own on <a href='https://huggingface.co/spaces' target='_blank'>HF Spaces</a>"
443
+ "</center>"
444
+ )
445
+
446
+ if __name__ == "__main__":
447
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=6.0.0
2
+ google-generativeai>=0.8.3
3
+ fpdf2>=2.8.1