sreejang commited on
Commit
d0e7d1b
·
verified ·
1 Parent(s): de743c6

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +359 -0
app.py ADDED
@@ -0,0 +1,359 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from groq import Groq
3
+ from fpdf import FPDF
4
+ import json
5
+ import os
6
+ import re
7
+ from datetime import datetime
8
+
9
+ # Initialize Groq client from environment variable
10
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
11
+ if not GROQ_API_KEY:
12
+ raise ValueError("GROQ_API_KEY not found in environment variables. Please set it in Space Secrets.")
13
+
14
+ client = Groq(api_key=GROQ_API_KEY)
15
+
16
+ def clean_json_string(content):
17
+ """Remove invalid control characters from JSON string"""
18
+ content = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]', '', content)
19
+ return content
20
+
21
+ def generate_roadmap(domain, level, time_available, time_unit):
22
+ """Generate roadmap using Groq API"""
23
+ if time_unit == "Weeks":
24
+ total_weeks = int(time_available)
25
+ elif time_unit == "Months":
26
+ total_weeks = int(time_available) * 4
27
+ elif time_unit == "Years":
28
+ total_weeks = int(time_available) * 52
29
+
30
+ system_prompt = """You are an expert educational curriculum designer. Return only valid JSON without any markdown formatting or explanatory text."""
31
+
32
+ user_prompt = f"""Create a detailed learning roadmap for {domain} at {level} level.
33
+ Duration: {time_available} {time_unit.lower()} ({total_weeks} weeks).
34
+
35
+ STRICT REQUIREMENTS:
36
+ 1. Return ONLY valid JSON, no markdown, no backticks, no explanation
37
+ 2. Use this exact structure:
38
+ {{
39
+ "domain": "{domain}",
40
+ "level": "{level}",
41
+ "duration": "{time_available} {time_unit}",
42
+ "overview": "Brief overview text here",
43
+ "phases": [
44
+ {{
45
+ "phase_name": "Phase Name",
46
+ "duration_weeks": number,
47
+ "description": "Description here",
48
+ "topics": [
49
+ {{
50
+ "name": "Topic name",
51
+ "resources": ["resource 1", "resource 2"],
52
+ "practice_project": "Project description"
53
+ }}
54
+ ],
55
+ "milestones": ["milestone 1", "milestone 2"]
56
+ }}
57
+ ],
58
+ "essential_resources": {{
59
+ "courses": ["course 1"],
60
+ "books": ["book 1"],
61
+ "communities": ["community 1"],
62
+ "tools": ["tool 1"]
63
+ }},
64
+ "tips_for_success": ["tip 1", "tip 2"],
65
+ "next_steps": "What to do next"
66
+ }}
67
+
68
+ 3. Ensure all strings are properly escaped (no raw newlines or tabs in strings)
69
+ 4. Use \\n for newlines within strings if needed"""
70
+
71
+ try:
72
+ response = client.chat.completions.create(
73
+ model="llama-3.3-70b-versatile",
74
+ messages=[
75
+ {"role": "system", "content": system_prompt},
76
+ {"role": "user", "content": user_prompt}
77
+ ],
78
+ temperature=0.7,
79
+ max_tokens=4096
80
+ )
81
+
82
+ content = response.choices[0].message.content.strip()
83
+
84
+ # Clean up markdown code blocks
85
+ if content.startswith("```json"):
86
+ content = content[7:]
87
+ elif content.startswith("```"):
88
+ content = content[3:]
89
+ if content.endswith("```"):
90
+ content = content[:-3]
91
+
92
+ content = content.strip()
93
+ content = clean_json_string(content)
94
+
95
+ # Try to parse JSON
96
+ try:
97
+ data = json.loads(content)
98
+ return data
99
+ except json.JSONDecodeError as e:
100
+ # Try to fix common issues
101
+ content = re.sub(r'(".*?)\n(.*?")', r'\1\\n\2', content, flags=re.DOTALL)
102
+ content = re.sub(r'(".*?)\t(.*?")', r'\1\\t\2', content, flags=re.DOTALL)
103
+ data = json.loads(content)
104
+ return data
105
+
106
+ except Exception as e:
107
+ return {"error": f"Failed to generate roadmap: {str(e)}"}
108
+
109
+ def format_roadmark_for_display(roadmap_data):
110
+ """Format roadmap as HTML"""
111
+ if "error" in roadmap_data:
112
+ return f"<p style='color:red; padding:20px;'>{roadmap_data['error']}</p>", None
113
+
114
+ try:
115
+ html_output = f"""
116
+ <div style="font-family: 'Segoe UI', sans-serif; max-width: 900px; margin: 0 auto;">
117
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 15px; margin-bottom: 20px;">
118
+ <h1 style="margin: 0;">🎯 {roadmap_data.get('domain', 'Unknown')} Roadmap</h1>
119
+ <p>Level: {roadmap_data.get('level', 'N/A')} | Duration: {roadmap_data.get('duration', 'N/A')}</p>
120
+ </div>
121
+ <div style="background: #f8f9fa; padding: 20px; border-radius: 10px; margin-bottom: 20px;">
122
+ <h3>Overview</h3>
123
+ <p>{roadmap_data.get('overview', 'No overview provided').replace(chr(10), '<br>')}</p>
124
+ </div>
125
+ """
126
+
127
+ for i, phase in enumerate(roadmap_data.get('phases', []), 1):
128
+ html_output += f"""
129
+ <div style="border: 2px solid #e9ecef; border-radius: 15px; margin-bottom: 20px; overflow: hidden;">
130
+ <div style="background: #667eea; color: white; padding: 15px;">
131
+ <h3 style="margin:0;">Phase {i}: {phase.get('phase_name', 'Unnamed')}</h3>
132
+ <p style="margin:5px 0 0 0;">Duration: {phase.get('duration_weeks', 'N/A')} weeks</p>
133
+ </div>
134
+ <div style="padding: 20px;">
135
+ <p>{phase.get('description', '').replace(chr(10), '<br>')}</p>
136
+ <h4>Topics:</h4>
137
+ """
138
+
139
+ for topic in phase.get('topics', []):
140
+ html_output += f"<div style='background:#f8f9fa; padding:10px; margin:10px 0; border-radius:5px;'>"
141
+ html_output += f"<p style='margin:0;'><b>{topic.get('name', 'Unnamed')}</b></p>"
142
+ html_output += f"<ul style='margin:5px 0;'>"
143
+ for res in topic.get('resources', []):
144
+ html_output += f"<li>{res}</li>"
145
+ html_output += f"</ul>"
146
+ html_output += f"<p style='background:#fff3cd; padding:5px; margin:5px 0; border-radius:3px;'><b>Project:</b> {topic.get('practice_project', 'N/A')}</p>"
147
+ html_output += f"</div>"
148
+
149
+ if phase.get('milestones'):
150
+ html_output += f"<h4>Milestones:</h4><ul>"
151
+ for ms in phase['milestones']:
152
+ html_output += f"<li>☐ {ms}</li>"
153
+ html_output += f"</ul>"
154
+
155
+ html_output += "</div></div>"
156
+
157
+ html_output += "</div>"
158
+ return html_output, json.dumps(roadmap_data)
159
+
160
+ except Exception as e:
161
+ return f"<p style='color:red; padding:20px;'>Error formatting display: {str(e)}</p>", None
162
+
163
+ def generate_pdf(roadmap_data, domain):
164
+ """Generate PDF file"""
165
+ try:
166
+ if isinstance(roadmap_data, str):
167
+ roadmap_data = json.loads(roadmap_data)
168
+
169
+ if not roadmap_data or "error" in roadmap_data:
170
+ return None
171
+
172
+ pdf = FPDF()
173
+ pdf.set_auto_page_break(auto=True, margin=15)
174
+
175
+ def clean(text):
176
+ if text is None:
177
+ return ""
178
+ text = str(text)
179
+ return text.encode('latin-1', 'replace').decode('latin-1')
180
+
181
+ # Title Page
182
+ pdf.add_page()
183
+ pdf.set_font("Arial", 'B', 24)
184
+ pdf.set_text_color(102, 126, 234)
185
+ pdf.cell(0, 20, clean(f"Learning Roadmap: {roadmap_data.get('domain', domain)}"), ln=True, align='C')
186
+
187
+ pdf.set_font("Arial", '', 12)
188
+ pdf.set_text_color(100, 100, 100)
189
+ pdf.cell(0, 10, clean(f"Level: {roadmap_data.get('level', 'N/A')} | Duration: {roadmap_data.get('duration', 'N/A')}"), ln=True, align='C')
190
+ pdf.ln(10)
191
+
192
+ # Overview
193
+ pdf.set_font("Arial", 'B', 16)
194
+ pdf.set_text_color(0, 0, 0)
195
+ pdf.cell(0, 10, "Overview", ln=True)
196
+ pdf.set_font("Arial", '', 11)
197
+ pdf.multi_cell(0, 6, clean(roadmap_data.get('overview', 'No overview')))
198
+ pdf.ln(5)
199
+
200
+ # Phases
201
+ for i, phase in enumerate(roadmap_data.get('phases', []), 1):
202
+ pdf.add_page()
203
+ pdf.set_font("Arial", 'B', 18)
204
+ pdf.set_fill_color(102, 126, 234)
205
+ pdf.set_text_color(255, 255, 255)
206
+ pdf.cell(0, 12, clean(f"Phase {i}: {phase.get('phase_name', f'Phase {i}')}"), ln=True, fill=True)
207
+
208
+ pdf.set_text_color(100, 100, 100)
209
+ pdf.set_font("Arial", 'I', 11)
210
+ pdf.cell(0, 8, f"Duration: {phase.get('duration_weeks', 'N/A')} weeks", ln=True)
211
+ pdf.ln(2)
212
+
213
+ pdf.set_text_color(0, 0, 0)
214
+ pdf.set_font("Arial", '', 11)
215
+ pdf.multi_cell(0, 6, clean(phase.get('description', '')))
216
+ pdf.ln(5)
217
+
218
+ pdf.set_font("Arial", 'B', 13)
219
+ pdf.set_text_color(102, 126, 234)
220
+ pdf.cell(0, 8, "Topics & Resources:", ln=True)
221
+
222
+ for topic in phase.get('topics', []):
223
+ pdf.set_font("Arial", 'B', 11)
224
+ pdf.set_text_color(0, 0, 0)
225
+ pdf.cell(0, 6, clean(f"- {topic.get('name', 'Topic')}"), ln=True)
226
+
227
+ pdf.set_font("Arial", '', 10)
228
+ pdf.set_text_color(80, 80, 80)
229
+ for resource in topic.get('resources', []):
230
+ pdf.cell(10)
231
+ pdf.cell(0, 5, clean(f" * {resource}"), ln=True)
232
+
233
+ pdf.set_text_color(200, 100, 0)
234
+ pdf.set_font("Arial", 'B', 10)
235
+ pdf.cell(0, 5, clean(f"Project: {topic.get('practice_project', 'N/A')}"), ln=True)
236
+ pdf.ln(3)
237
+
238
+ if phase.get('milestones'):
239
+ pdf.set_font("Arial", 'B', 12)
240
+ pdf.set_text_color(40, 167, 69)
241
+ pdf.cell(0, 8, "Milestones:", ln=True)
242
+ pdf.set_font("Arial", '', 10)
243
+ pdf.set_text_color(0, 0, 0)
244
+ for milestone in phase.get('milestones', []):
245
+ pdf.cell(10)
246
+ pdf.cell(0, 5, clean(f"[ ] {milestone}"), ln=True)
247
+ pdf.ln(5)
248
+
249
+ # Resources
250
+ if roadmap_data.get('essential_resources'):
251
+ pdf.add_page()
252
+ pdf.set_font("Arial", 'B', 18)
253
+ pdf.set_fill_color(102, 126, 234)
254
+ pdf.set_text_color(255, 255, 255)
255
+ pdf.cell(0, 12, "Essential Resources", ln=True, fill=True)
256
+ pdf.ln(5)
257
+
258
+ resources = roadmap_data['essential_resources']
259
+ sections = [
260
+ ("Recommended Courses", resources.get('courses', [])),
261
+ ("Books", resources.get('books', [])),
262
+ ("Communities", resources.get('communities', [])),
263
+ ("Tools", resources.get('tools', []))
264
+ ]
265
+
266
+ for title, items in sections:
267
+ if items:
268
+ pdf.set_font("Arial", 'B', 13)
269
+ pdf.set_text_color(102, 126, 234)
270
+ pdf.cell(0, 8, clean(title), ln=True)
271
+ pdf.set_font("Arial", '', 10)
272
+ pdf.set_text_color(0, 0, 0)
273
+ for item in items:
274
+ pdf.cell(5)
275
+ pdf.cell(0, 5, clean(f"- {item}"), ln=True)
276
+ pdf.ln(3)
277
+
278
+ # Save to /tmp for Hugging Face
279
+ safe_domain = "".join(c for c in domain if c.isalnum() or c in (' ', '-', '_')).rstrip().replace(' ', '_') or "roadmap"
280
+ filename = f"roadmap_{safe_domain}_{datetime.now().strftime('%Y%m%d')}.pdf"
281
+ filepath = os.path.join("/tmp", filename)
282
+ pdf.output(filepath)
283
+ return filepath
284
+
285
+ except Exception as e:
286
+ print(f"PDF Error: {e}")
287
+ return None
288
+
289
+ # Build Gradio Interface
290
+ def create_app():
291
+ with gr.Blocks(title="AI Learning Roadmap Generator") as app:
292
+ roadmap_json = gr.State("")
293
+ domain_name = gr.State("")
294
+
295
+ gr.Markdown("# 🎯 AI Learning Roadmap Generator")
296
+ gr.Markdown("Generate personalized learning paths for any skill domain")
297
+
298
+ with gr.Row():
299
+ with gr.Column(scale=1):
300
+ domain_input = gr.Textbox(label="Learning Domain", placeholder="e.g., Machine Learning, Web Development...")
301
+ level_input = gr.Dropdown(["Beginner", "Intermediate", "Advanced"], label="Level", value="Beginner")
302
+
303
+ with gr.Row():
304
+ time_input = gr.Number(value=3, minimum=1, label="Time")
305
+ time_unit = gr.Dropdown(["Weeks", "Months", "Years"], value="Months", label="Unit")
306
+
307
+ generate_btn = gr.Button("🚀 Generate Roadmap", variant="primary")
308
+
309
+ gr.Examples(
310
+ [["Machine Learning", "Beginner", 6, "Months"],
311
+ ["Web Development", "Intermediate", 4, "Months"],
312
+ ["Python Programming", "Beginner", 8, "Weeks"]],
313
+ inputs=[domain_input, level_input, time_input, time_unit]
314
+ )
315
+
316
+ with gr.Column(scale=2):
317
+ html_output = gr.HTML(label="Your Roadmap")
318
+
319
+ with gr.Row():
320
+ download_btn = gr.Button("📄 Download PDF", variant="primary", visible=False)
321
+
322
+ pdf_output = gr.File(label="Your PDF", interactive=False, visible=False)
323
+
324
+ def generate(domain, level, time_val, unit):
325
+ if not domain or not domain.strip():
326
+ return "Please enter a domain", gr.update(visible=False), gr.update(visible=False), "", ""
327
+
328
+ result = generate_roadmap(domain, level, time_val, unit)
329
+
330
+ if "error" in result:
331
+ return f"<p style='color:red; padding:20px;'>❌ {result['error']}</p>", gr.update(visible=False), gr.update(visible=False), "", ""
332
+
333
+ html, json_str = format_roadmark_for_display(result)
334
+ if json_str is None:
335
+ return html, gr.update(visible=False), gr.update(visible=False), "", ""
336
+
337
+ return html, gr.update(visible=True), gr.update(visible=True), json_str, domain
338
+
339
+ def download(json_data, dom):
340
+ if not json_data:
341
+ return None
342
+ return generate_pdf(json_data, dom)
343
+
344
+ generate_btn.click(
345
+ fn=generate,
346
+ inputs=[domain_input, level_input, time_input, time_unit],
347
+ outputs=[html_output, download_btn, pdf_output, roadmap_json, domain_name]
348
+ )
349
+
350
+ download_btn.click(
351
+ fn=download,
352
+ inputs=[roadmap_json, domain_name],
353
+ outputs=pdf_output
354
+ )
355
+
356
+ return app
357
+
358
+ app = create_app()
359
+ app.launch()