Ali Abdullah commited on
Commit
79fd5d1
·
verified ·
1 Parent(s): 3d9f649

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +737 -65
app.py CHANGED
@@ -24,7 +24,7 @@ def initialize_groq():
24
 
25
  def initialize_vectorstore():
26
  global vectorstore, retriever
27
-
28
  try:
29
  vectorstore = FAISS.load_local("atomcamp_vector_db", embeddings, allow_dangerous_deserialization=True)
30
  retriever = vectorstore.as_retriever()
@@ -44,29 +44,29 @@ def initialize_vectorstore():
44
  "url": "https://www.atomcamp.com/learning-paths"
45
  }
46
  ]
47
-
48
  docs = [Document(page_content=item["text"], metadata={"url": item["url"]}) for item in sample_data]
49
  splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
50
  chunks = splitter.split_documents(docs)
51
-
52
  vectorstore = FAISS.from_documents(chunks, embeddings)
53
  vectorstore.save_local("atomcamp_vector_db")
54
  retriever = vectorstore.as_retriever()
55
-
56
  return "Sample vectorstore created successfully"
57
 
58
  def call_groq_api(message, context):
59
  global groq_api_key
60
-
61
  if not groq_api_key:
62
  return None
63
-
64
  try:
65
  system_prompt = f"""You are an AI assistant for Atomcamp, a data science education platform.
66
  Use the following context to answer questions about Atomcamp's courses, career services, and data science topics.
67
-
68
  Context: {context}
69
-
70
  Guidelines:
71
  - Be helpful and informative
72
  - Focus on Atomcamp's offerings
@@ -75,12 +75,12 @@ def call_groq_api(message, context):
75
  - Keep responses concise but comprehensive
76
  - Do not use emojis
77
  """
78
-
79
  headers = {
80
  "Authorization": f"Bearer {groq_api_key}",
81
  "Content-Type": "application/json"
82
  }
83
-
84
  data = {
85
  "messages": [
86
  {"role": "system", "content": system_prompt},
@@ -90,101 +90,773 @@ def call_groq_api(message, context):
90
  "temperature": 0.7,
91
  "max_tokens": 1000
92
  }
93
-
94
  response = requests.post(
95
  "https://api.groq.com/openai/v1/chat/completions",
96
  headers=headers,
97
  json=data,
98
  timeout=30
99
  )
100
-
101
  if response.status_code == 200:
102
  result = response.json()
103
  return result["choices"][0]["message"]["content"]
104
  else:
105
  return None
106
-
107
  except Exception as e:
108
  return None
109
 
110
  def generate_response(message, context):
111
  groq_response = call_groq_api(message, context)
112
-
113
  if groq_response:
114
  return groq_response
115
-
 
116
  message_lower = message.lower()
117
-
118
  if any(word in message_lower for word in ['course', 'courses', 'learn', 'study']):
119
- return """<strong>Atomcamp Courses:</strong><br><br>
120
- <ul>
121
- <li><strong>Python for Data Science</strong> - Master Python programming fundamentals</li>
122
- <li><strong>Machine Learning Fundamentals</strong> - Learn ML algorithms and applications</li>
123
- <li><strong>Deep Learning with TensorFlow</strong> - Build neural networks and AI models</li>
124
- <li><strong>Data Visualization</strong> - Create stunning charts with Matplotlib & Seaborn</li>
125
- <li><strong>SQL for Data Analysis</strong> - Database querying and data manipulation</li>
126
- <li><strong>Statistics for Data Science</strong> - Statistical analysis and hypothesis testing</li>
127
- </ul><br>
128
- <strong>Learning Tracks:</strong><br>
129
- <ul>
130
- <li>Beginner Track (3 months) - Perfect for newcomers</li>
131
- <li>Intermediate Track (6 months) - Build real-world projects</li>
132
- <li>Advanced Track (9 months) - Industry-ready with job placement</li>
133
- </ul><br>
134
- Would you like details about any specific course?"""
135
-
136
  elif any(word in message_lower for word in ['career', 'job', 'placement']):
137
- return """<strong>Career Services at Atomcamp:</strong><br><br>
138
- <ul>
139
- <li>Resume building and optimization</li>
140
- <li>Technical interview preparation</li>
141
- <li>Portfolio development guidance</li>
142
- <li>Direct connections with hiring partners</li>
143
- <li>Mock interviews with industry experts</li>
144
- </ul><br>
145
- <strong>Career Growth:</strong><br>
146
- <ul>
147
- <li>Average salary increase: 150-300%</li>
148
- <li>95% job placement rate within 6 months</li>
149
- <li>Access to exclusive job opportunities</li>
150
- <li>Ongoing career mentorship</li>
151
- <li>Industry networking events</li>
152
- </ul><br>
153
- Ready to transform your career in data science?"""
154
-
155
  else:
156
- return f"""Thank you for your question about <strong>{message}</strong>!<br><br>
157
- As an Atomcamp AI assistant, I'm here to help you with:<br>
158
- <ul>
159
- <li>Course Information - Learn about our data science programs</li>
160
- <li>Career Guidance - Job placement and career growth</li>
161
- <li>Technical Topics - Python, ML, AI, and data analysis</li>
162
- <li>Getting Started - How to begin your data science journey</li>
163
- </ul><br>
164
- Would you like me to elaborate on any specific aspect?"""
 
165
 
166
  @app.route('/')
167
  def index():
168
- return render_template_string("""<h2>Atomcamp Chatbot is running. Please deploy with UI.</h2>""")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
  @app.route('/chat', methods=['POST'])
171
  def chat():
172
  try:
173
  data = request.get_json()
174
  message = data.get('message', '')
175
-
176
  if not message:
177
  return jsonify({'error': 'No message provided'}), 400
178
-
179
  if retriever:
180
  docs = retriever.get_relevant_documents(message)
181
  context = "\n\n".join([doc.page_content for doc in docs[:3]])
182
  else:
183
  context = "I'm an AI assistant for Atomcamp, a data science education platform."
184
-
185
  response = generate_response(message, context)
186
  return jsonify({'response': response})
187
-
188
  except Exception as e:
189
  return jsonify({'error': f'Error: {str(e)}'}), 500
190
 
 
24
 
25
  def initialize_vectorstore():
26
  global vectorstore, retriever
27
+
28
  try:
29
  vectorstore = FAISS.load_local("atomcamp_vector_db", embeddings, allow_dangerous_deserialization=True)
30
  retriever = vectorstore.as_retriever()
 
44
  "url": "https://www.atomcamp.com/learning-paths"
45
  }
46
  ]
47
+
48
  docs = [Document(page_content=item["text"], metadata={"url": item["url"]}) for item in sample_data]
49
  splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
50
  chunks = splitter.split_documents(docs)
51
+
52
  vectorstore = FAISS.from_documents(chunks, embeddings)
53
  vectorstore.save_local("atomcamp_vector_db")
54
  retriever = vectorstore.as_retriever()
55
+
56
  return "Sample vectorstore created successfully"
57
 
58
  def call_groq_api(message, context):
59
  global groq_api_key
60
+
61
  if not groq_api_key:
62
  return None
63
+
64
  try:
65
  system_prompt = f"""You are an AI assistant for Atomcamp, a data science education platform.
66
  Use the following context to answer questions about Atomcamp's courses, career services, and data science topics.
67
+
68
  Context: {context}
69
+
70
  Guidelines:
71
  - Be helpful and informative
72
  - Focus on Atomcamp's offerings
 
75
  - Keep responses concise but comprehensive
76
  - Do not use emojis
77
  """
78
+
79
  headers = {
80
  "Authorization": f"Bearer {groq_api_key}",
81
  "Content-Type": "application/json"
82
  }
83
+
84
  data = {
85
  "messages": [
86
  {"role": "system", "content": system_prompt},
 
90
  "temperature": 0.7,
91
  "max_tokens": 1000
92
  }
93
+
94
  response = requests.post(
95
  "https://api.groq.com/openai/v1/chat/completions",
96
  headers=headers,
97
  json=data,
98
  timeout=30
99
  )
100
+
101
  if response.status_code == 200:
102
  result = response.json()
103
  return result["choices"][0]["message"]["content"]
104
  else:
105
  return None
106
+
107
  except Exception as e:
108
  return None
109
 
110
  def generate_response(message, context):
111
  groq_response = call_groq_api(message, context)
112
+
113
  if groq_response:
114
  return groq_response
115
+
116
+ # Fallback responses
117
  message_lower = message.lower()
118
+
119
  if any(word in message_lower for word in ['course', 'courses', 'learn', 'study']):
120
+ return """Atomcamp Courses:
121
+
122
+ Core Programs:
123
+ Python for Data Science - Master Python programming fundamentals
124
+ Machine Learning Fundamentals - Learn ML algorithms and applications
125
+ Deep Learning with TensorFlow - Build neural networks and AI models
126
+ Data Visualization - Create stunning charts with Matplotlib & Seaborn
127
+ SQL for Data Analysis - Database querying and data manipulation
128
+ • Statistics for Data Science - Statistical analysis and hypothesis testing
129
+
130
+ Learning Tracks:
131
+ Beginner Track (3 months) - Perfect for newcomers
132
+ Intermediate Track (6 months) - Build real-world projects
133
+ Advanced Track (9 months) - Industry-ready with job placement
134
+
135
+ Would you like details about any specific course?"""
136
+
137
  elif any(word in message_lower for word in ['career', 'job', 'placement']):
138
+ return """Career Services at Atomcamp:
139
+
140
+ Job Placement Support:
141
+ Resume building and optimization
142
+ Technical interview preparation
143
+ Portfolio development guidance
144
+ Direct connections with hiring partners
145
+ • Mock interviews with industry experts
146
+
147
+ Career Growth:
148
+ Average salary increase: 150-300%
149
+ 95% job placement rate within 6 months
150
+ Access to exclusive job opportunities
151
+ Ongoing career mentorship
152
+ Industry networking events
153
+
154
+ Ready to transform your career in data science?"""
155
+
156
  else:
157
+ return f"""Thank you for your question about "{message}"!
158
+
159
+ As an Atomcamp AI assistant, I'm here to help you with:
160
+
161
+ Course Information - Learn about our data science programs
162
+ Career Guidance - Job placement and career growth
163
+ Technical Topics - Python, ML, AI, and data analysis
164
+ Getting Started - How to begin your data science journey
165
+
166
+ Would you like me to elaborate on any specific aspect?"""
167
 
168
  @app.route('/')
169
  def index():
170
+ html_template = """
171
+ <!DOCTYPE html>
172
+ <html lang="en">
173
+ <head>
174
+ <meta charset="UTF-8">
175
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
176
+ <title>atomcamp AI Chatbot</title>
177
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
178
+ <style>
179
+ * {
180
+ margin: 0;
181
+ padding: 0;
182
+ box-sizing: border-box;
183
+ }
184
+
185
+ body {
186
+ font-family: 'Inter', sans-serif;
187
+ background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%);
188
+ min-height: 100vh;
189
+ display: flex;
190
+ flex-direction: column;
191
+ }
192
+
193
+ .header {
194
+ display: flex;
195
+ flex-direction: column;
196
+ align-items: center;
197
+ padding-top: 2rem;
198
+ padding-bottom: 1.5rem;
199
+ }
200
+
201
+ .logo {
202
+ margin-bottom: 1.5rem;
203
+ }
204
+
205
+ .logo-placeholder {
206
+ width: 160px;
207
+ height: 48px;
208
+ background: #22c55e;
209
+ border-radius: 24px;
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ color: white;
214
+ font-weight: 700;
215
+ font-size: 18px;
216
+ }
217
+
218
+ .title-bubble {
219
+ width: 100%;
220
+ max-width: 100%;
221
+ padding: 0 1rem;
222
+ }
223
+
224
+ .bubble-container {
225
+ background: #f0fdf4;
226
+ border: 1px solid #e5e7eb;
227
+ border-radius: 1rem;
228
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
229
+ padding: 1.5rem;
230
+ max-width: fit-content;
231
+ }
232
+
233
+ .bubble-content {
234
+ text-align: left;
235
+ }
236
+
237
+ .chatbot-title {
238
+ font-size: 1.5rem;
239
+ font-weight: 600;
240
+ color: #166534;
241
+ margin-bottom: 0.25rem;
242
+ letter-spacing: -0.025em;
243
+ }
244
+
245
+ .typing-indicator {
246
+ display: flex;
247
+ align-items: center;
248
+ gap: 0.5rem;
249
+ }
250
+
251
+ .typing-dots {
252
+ display: flex;
253
+ gap: 0.25rem;
254
+ }
255
+
256
+ .typing-dot {
257
+ width: 0.375rem;
258
+ height: 0.375rem;
259
+ background: #166534;
260
+ border-radius: 50%;
261
+ animation: bounce 1s infinite;
262
+ }
263
+
264
+ .typing-dot:nth-child(2) {
265
+ animation-delay: 0.1s;
266
+ }
267
+
268
+ .typing-dot:nth-child(3) {
269
+ animation-delay: 0.2s;
270
+ }
271
+
272
+ .typing-text {
273
+ color: #166534;
274
+ font-size: 1rem;
275
+ font-weight: 500;
276
+ }
277
+
278
+ .status-indicator {
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 0.5rem;
282
+ }
283
+
284
+ .status-dot {
285
+ width: 0.5rem;
286
+ height: 0.5rem;
287
+ border-radius: 50%;
288
+ background: #16a34a;
289
+ }
290
+
291
+ .status-text {
292
+ font-size: 0.875rem;
293
+ font-weight: 500;
294
+ color: #166534;
295
+ }
296
+
297
+ .chat-container {
298
+ width: 100%;
299
+ margin: 0;
300
+ padding: 0 1rem 8rem 1rem;
301
+ flex: 1;
302
+ overflow-y: auto;
303
+ height: calc(100vh - 300px);
304
+
305
+ }
306
+
307
+ .welcome-screen {
308
+ text-align: center;
309
+ padding: 3rem 0;
310
+ }
311
+
312
+ .welcome-card {
313
+ background: white;
314
+ border-radius: 1rem;
315
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
316
+ padding: 2rem;
317
+ max-width: 36rem;
318
+ margin: 0 auto;
319
+ }
320
+
321
+ .welcome-icon {
322
+ width: 3rem;
323
+ height: 3rem;
324
+ background: #22c55e;
325
+ border-radius: 0.75rem;
326
+ display: flex;
327
+ align-items: center;
328
+ justify-content: center;
329
+ margin: 0 auto 1rem;
330
+ color: white;
331
+ font-size: 1.5rem;
332
+ font-weight: bold;
333
+ }
334
+
335
+ .welcome-title {
336
+ font-size: 1.25rem;
337
+ font-weight: 600;
338
+ color: #1f2937;
339
+ margin-bottom: 0.75rem;
340
+ letter-spacing: -0.025em;
341
+ }
342
+
343
+ .welcome-description {
344
+ color: #6b7280;
345
+ font-size: 0.875rem;
346
+ line-height: 1.5;
347
+ margin-bottom: 1.5rem;
348
+ }
349
+
350
+ .feature-grid {
351
+ display: grid;
352
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
353
+ gap: 0.75rem;
354
+ text-align: left;
355
+ }
356
+
357
+ .feature-card {
358
+ background: #f9fafb;
359
+ border-radius: 0.5rem;
360
+ padding: 0.75rem;
361
+ }
362
+
363
+ .feature-title {
364
+ font-weight: 600;
365
+ color: #1f2937;
366
+ margin-bottom: 0.5rem;
367
+ font-size: 0.875rem;
368
+ }
369
+
370
+ .feature-list {
371
+ font-size: 0.75rem;
372
+ color: #6b7280;
373
+ line-height: 1.5;
374
+ list-style: none;
375
+ padding: 0;
376
+ margin: 0;
377
+ }
378
+
379
+ .feature-list li {
380
+ margin-bottom: 0.25rem;
381
+ }
382
+
383
+ .messages-container {
384
+ display: flex;
385
+ flex-direction: column;
386
+ gap: 1rem;
387
+ }
388
+
389
+ .message {
390
+ display: flex;
391
+ animation: fadeIn 0.3s ease-out;
392
+ }
393
+
394
+ .message.user {
395
+ justify-content: flex-end;
396
+ }
397
+
398
+ .message.assistant {
399
+ justify-content: flex-start;
400
+ }
401
+
402
+ .message-content {
403
+ display: flex;
404
+ align-items: flex-end;
405
+ gap: 0.5rem;
406
+ max-width: 48rem;
407
+ }
408
+
409
+ .message.user .message-content {
410
+ max-width: 28rem;
411
+ }
412
+
413
+ .avatar {
414
+ width: 2rem;
415
+ height: 2rem;
416
+ border-radius: 50%;
417
+ display: flex;
418
+ align-items: center;
419
+ justify-content: center;
420
+ flex-shrink: 0;
421
+ }
422
+
423
+ .avatar.user {
424
+ background: #22c55e;
425
+ color: white;
426
+ }
427
+
428
+ .avatar.assistant {
429
+ background: #e5e7eb;
430
+ color: #6b7280;
431
+ }
432
+
433
+ .message-bubble {
434
+ padding: 0.75rem 1rem;
435
+ border-radius: 1rem;
436
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
437
+ }
438
+
439
+ .message.user .message-bubble {
440
+ background: #22c55e;
441
+ color: white;
442
+ border-bottom-right-radius: 0.375rem;
443
+ }
444
+
445
+ .message.assistant .message-bubble {
446
+ background: #e5e7eb;
447
+ color: #1f2937;
448
+ border-bottom-left-radius: 0.375rem;
449
+ }
450
+
451
+ .message-text {
452
+ font-size: 0.875rem;
453
+ font-weight: 500;
454
+ line-height: 1.5;
455
+ }
456
+
457
+ .message-formatted {
458
+ font-size: 0.875rem;
459
+ line-height: 1.5;
460
+ }
461
+
462
+ .input-area {
463
+ position: fixed;
464
+ bottom: 0;
465
+ left: 0;
466
+ right: 0;
467
+ background: rgba(255, 255, 255, 0.95);
468
+ backdrop-filter: blur(8px);
469
+ border-top: 1px solid #e5e7eb;
470
+ padding: 1rem;
471
+ }
472
+
473
+ .input-container {
474
+ max-width: 720px;
475
+ margin: 0 auto;
476
+ width: 100%;
477
+ }
478
+
479
+ .input-form {
480
+ position: relative;
481
+ }
482
+
483
+ .input-wrapper {
484
+ position: relative;
485
+ background: #4b5563;
486
+ border-radius: 1.5rem;
487
+ overflow: hidden;
488
+ padding: 0.25rem 0.75rem;
489
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
490
+
491
+ }
492
+
493
+
494
+ .message-input {
495
+ width: 100%;
496
+ padding: 0.875rem 1.25rem;
497
+ padding-right: 3rem;
498
+ background: transparent;
499
+ border: none;
500
+ outline: none;
501
+ color: white;
502
+ font-size: 0.875rem;
503
+ font-weight: 500;
504
+ letter-spacing: 0.025em;
505
+ }
506
+
507
+ .message-input::placeholder {
508
+ color: #d1d5db;
509
+ }
510
+
511
+ .send-button {
512
+ position: absolute;
513
+ right: 0.5rem;
514
+ top: 50%;
515
+ transform: translateY(-50%);
516
+ background: #22c55e;
517
+ color: white;
518
+ border: none;
519
+ border-radius: 50%;
520
+ width: 2.25rem;
521
+ height: 2.25rem;
522
+ display: flex;
523
+ align-items: center;
524
+ justify-content: center;
525
+ cursor: pointer;
526
+ transition: all 0.2s;
527
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
528
+ }
529
+
530
+ .send-button:hover:not(:disabled) {
531
+ background: #16a34a;
532
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
533
+ }
534
+
535
+ .send-button:disabled {
536
+ opacity: 0.5;
537
+ cursor: not-allowed;
538
+ }
539
+
540
+ .status-bar {
541
+ display: flex;
542
+ justify-content: space-between;
543
+ align-items: center;
544
+ margin-top: 0.5rem;
545
+ padding: 0 0.5rem;
546
+ }
547
+
548
+ .connection-status {
549
+ font-size: 0.75rem;
550
+ font-weight: 500;
551
+ color: #6b7280;
552
+ }
553
+
554
+ .char-count {
555
+ font-size: 0.75rem;
556
+ font-weight: 500;
557
+ color: #9ca3af;
558
+ }
559
+
560
+ .hidden {
561
+ display: none !important;
562
+ }
563
+
564
+ @keyframes bounce {
565
+ 0%, 80%, 100% {
566
+ transform: scale(0);
567
+ }
568
+ 40% {
569
+ transform: scale(1);
570
+ }
571
+ }
572
+
573
+ @keyframes fadeIn {
574
+ from {
575
+ opacity: 0;
576
+ transform: translateY(8px);
577
+ }
578
+ to {
579
+ opacity: 1;
580
+ transform: translateY(0);
581
+ }
582
+ }
583
+
584
+ @media (max-width: 768px) {
585
+ .header {
586
+ padding-top: 1rem;
587
+ padding-bottom: 1rem;
588
+ }
589
+
590
+ .chat-container {
591
+ padding-bottom: 6rem;
592
+ }
593
+
594
+ .message-content {
595
+ max-width: calc(100vw - 2rem);
596
+ }
597
+
598
+ .message.user .message-content {
599
+ max-width: calc(100vw - 2rem);
600
+ }
601
+ }
602
+ </style>
603
+ </head>
604
+ <body>
605
+ <div class="header">
606
+ <div class="logo">
607
+ <img src="/static/atomcamp_logo.png" alt="Atomcamp Logo" style="height: 48px;">
608
+ </div>
609
+
610
+ <div class="title-bubble">
611
+ <div class="bubble-container">
612
+ <div class="bubble-content">
613
+ <h1 class="chatbot-title">Chatbot</h1>
614
+
615
+ <div id="typingIndicator" class="typing-indicator hidden">
616
+ <div class="typing-dots">
617
+ <div class="typing-dot"></div>
618
+ <div class="typing-dot"></div>
619
+ <div class="typing-dot"></div>
620
+ </div>
621
+ <span class="typing-text">Typing...</span>
622
+ </div>
623
+
624
+ <div id="statusIndicator" class="status-indicator">
625
+ <div class="status-dot"></div>
626
+ <span class="status-text">Online</span>
627
+ </div>
628
+ </div>
629
+ </div>
630
+ </div>
631
+ </div>
632
+
633
+ <div class="chat-container">
634
+ <div id="welcomeScreen" class="welcome-screen">
635
+ <div class="welcome-card">
636
+ <div class="welcome-icon">✨</div>
637
+ <h2 class="welcome-title">Welcome to atomcamp AI</h2>
638
+ <p class="welcome-description">Ask me about courses, data science concepts, and learning paths.</p>
639
+
640
+ <div class="feature-grid">
641
+ <div class="feature-card">
642
+ <h3 class="feature-title">Ask me about:</h3>
643
+ <ul class="feature-list">
644
+ <li>• Course information</li>
645
+ <li>• Data science concepts</li>
646
+ <li>• Learning paths</li>
647
+ </ul>
648
+ </div>
649
+ <div class="feature-card">
650
+ <h3 class="feature-title">Try asking:</h3>
651
+ <ul class="feature-list">
652
+ <li>• "What courses do you offer?"</li>
653
+ <li>• "Explain machine learning"</li>
654
+ <li>• "How do I get started?"</li>
655
+ </ul>
656
+ </div>
657
+ </div>
658
+ </div>
659
+ </div>
660
+
661
+ <div id="messagesContainer" class="messages-container hidden"></div>
662
+ </div>
663
+
664
+ <div class="input-area">
665
+ <div class="input-container">
666
+ <form id="chatForm" class="input-form">
667
+ <div class="input-wrapper">
668
+ <input
669
+ type="text"
670
+ id="messageInput"
671
+ class="message-input"
672
+ placeholder="Ask me anything about atomcamp..."
673
+ maxlength="1000"
674
+ autocomplete="off"
675
+ >
676
+ <button type="submit" id="sendButton" class="send-button">
677
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
678
+ <path d="M12 19V5M5 12l7-7 7 7"/>
679
+ </svg>
680
+ </button>
681
+ </div>
682
+
683
+ <div class="status-bar">
684
+ <span id="connectionStatus" class="connection-status">Connected to atomcamp AI</span>
685
+ <span id="charCount" class="char-count">0/1000</span>
686
+ </div>
687
+ </form>
688
+ </div>
689
+ </div>
690
+
691
+ <script>
692
+ let messages = [];
693
+ let isTyping = false;
694
+ let isConnected = true;
695
+
696
+ const welcomeScreen = document.getElementById('welcomeScreen');
697
+ const messagesContainer = document.getElementById('messagesContainer');
698
+ const chatForm = document.getElementById('chatForm');
699
+ const messageInput = document.getElementById('messageInput');
700
+ const sendButton = document.getElementById('sendButton');
701
+ const typingIndicator = document.getElementById('typingIndicator');
702
+ const statusIndicator = document.getElementById('statusIndicator');
703
+ const connectionStatus = document.getElementById('connectionStatus');
704
+ const charCount = document.getElementById('charCount');
705
+
706
+ chatForm.addEventListener('submit', handleSubmit);
707
+ messageInput.addEventListener('input', updateCharCount);
708
+
709
+ function updateCharCount() {
710
+ const count = messageInput.value.length;
711
+ charCount.textContent = ${count}/1000;
712
+ }
713
+
714
+ async function handleSubmit(e) {
715
+ e.preventDefault();
716
+ const message = messageInput.value.trim();
717
+
718
+ if (!message || isTyping) return;
719
+
720
+ addMessage(message, 'user');
721
+ messageInput.value = '';
722
+ updateCharCount();
723
+ setTyping(true);
724
+
725
+ try {
726
+ const response = await fetch('/chat', {
727
+ method: 'POST',
728
+ headers: { 'Content-Type': 'application/json' },
729
+ body: JSON.stringify({ message: message })
730
+ });
731
+
732
+ const data = await response.json();
733
+
734
+ await new Promise(resolve => setTimeout(resolve, 1200));
735
+
736
+ addMessage(data.response || 'Sorry, I could not generate a response.', 'assistant');
737
+ setConnected(true);
738
+
739
+ } catch (error) {
740
+ console.error('Error:', error);
741
+ setConnected(false);
742
+ addMessage('I am having trouble connecting. Please try again.', 'assistant');
743
+ } finally {
744
+ setTyping(false);
745
+ }
746
+ }
747
+
748
+ function addMessage(content, role) {
749
+ if (messages.length === 0) {
750
+ welcomeScreen.classList.add('hidden');
751
+ messagesContainer.classList.remove('hidden');
752
+ }
753
+
754
+ const messageDiv = document.createElement('div');
755
+ messageDiv.className = message ${role};
756
+
757
+ const messageContent = document.createElement('div');
758
+ messageContent.className = 'message-content';
759
+
760
+ const avatar = document.createElement('div');
761
+ avatar.className = avatar ${role};
762
+
763
+ if (role === 'user') {
764
+ avatar.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>';
765
+ } else {
766
+ avatar.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><circle cx="12" cy="5" r="2"></circle><path d="M12 7v4"></path><line x1="8" y1="16" x2="8" y2="16"></line><line x1="16" y1="16" x2="16" y2="16"></line></svg>';
767
+ }
768
+
769
+ const bubble = document.createElement('div');
770
+ bubble.className = 'message-bubble';
771
+
772
+ if (role === 'user') {
773
+ bubble.innerHTML = <div class="message-text">${content}</div>;
774
+ messageContent.appendChild(bubble);
775
+ messageContent.appendChild(avatar);
776
+ } else {
777
+ bubble.innerHTML = <div class="message-formatted">${formatMessageContent(content)}</div>;
778
+ messageContent.appendChild(avatar);
779
+ messageContent.appendChild(bubble);
780
+ }
781
+
782
+ messageDiv.appendChild(messageContent);
783
+ messagesContainer.appendChild(messageDiv);
784
+
785
+ scrollToBottom();
786
+ messages.push({ content, role, timestamp: new Date() });
787
+ }
788
+
789
+ function formatMessageContent(content) {
790
+ return content.split('\\n').map(line => {
791
+ if (line.trim().startsWith('•') || line.trim().startsWith('-')) {
792
+ return <div style="display: flex; align-items: flex-start; margin-bottom: 0.375rem;">
793
+ <span style="color: #16a34a; margin-right: 0.5rem; margin-top: 0.125rem; font-size: 0.875rem; font-weight: 500;">•</span>
794
+ <span style="font-size: 0.875rem; line-height: 1.5;">${line.replace(/^[•-]\\s*/, '')}</span>
795
+ </div>;
796
+ } else if (/^\\d+\\./.test(line.trim())) {
797
+ const match = line.match(/^\\d+\\./);
798
+ return <div style="display: flex; align-items: flex-start; margin-bottom: 0.375rem;">
799
+ <span style="color: #16a34a; margin-right: 0.5rem; font-weight: 600; font-size: 0.875rem;">${match ? match[0] : ''}</span>
800
+ <span style="font-size: 0.875rem; line-height: 1.5;">${line.replace(/^\\d+\\.\\s*/, '')}</span>
801
+ </div>;
802
+ } else if (line.trim() === '') {
803
+ return '<br>';
804
+ } else {
805
+ return <p style="margin-bottom: 0.375rem; font-size: 0.875rem; line-height: 1.5;">${line}</p>;
806
+ }
807
+ }).join('');
808
+ }
809
+
810
+ function setTyping(typing) {
811
+ isTyping = typing;
812
+ sendButton.disabled = typing;
813
+
814
+ if (typing) {
815
+ typingIndicator.classList.remove('hidden');
816
+ statusIndicator.classList.add('hidden');
817
+ } else {
818
+ typingIndicator.classList.add('hidden');
819
+ statusIndicator.classList.remove('hidden');
820
+ }
821
+ }
822
+
823
+ function setConnected(connected) {
824
+ isConnected = connected;
825
+ connectionStatus.textContent = connected ? 'Connected to atomcamp AI' : 'Connection lost';
826
+ }
827
+
828
+ function scrollToBottom() {
829
+ setTimeout(() => {
830
+ window.scrollTo({
831
+ top: document.body.scrollHeight,
832
+ behavior: 'smooth'
833
+ });
834
+ }, 100);
835
+ }
836
+ </script>
837
+ </body>
838
+ </html>
839
+ """
840
+ return render_template_string(html_template)
841
 
842
  @app.route('/chat', methods=['POST'])
843
  def chat():
844
  try:
845
  data = request.get_json()
846
  message = data.get('message', '')
847
+
848
  if not message:
849
  return jsonify({'error': 'No message provided'}), 400
850
+
851
  if retriever:
852
  docs = retriever.get_relevant_documents(message)
853
  context = "\n\n".join([doc.page_content for doc in docs[:3]])
854
  else:
855
  context = "I'm an AI assistant for Atomcamp, a data science education platform."
856
+
857
  response = generate_response(message, context)
858
  return jsonify({'response': response})
859
+
860
  except Exception as e:
861
  return jsonify({'error': f'Error: {str(e)}'}), 500
862