Ali Abdullah commited on
Commit
386945f
·
verified ·
1 Parent(s): fc96ff2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +282 -585
app.py CHANGED
@@ -22,7 +22,9 @@ def initialize_groq():
22
  global groq_api_key
23
  groq_api_key = os.getenv("GROQ_API_KEY")
24
  if groq_api_key:
 
25
  return "Groq API key found"
 
26
  return "Groq API key not found"
27
 
28
  def initialize_vectorstore():
@@ -31,6 +33,7 @@ def initialize_vectorstore():
31
  try:
32
  vectorstore = FAISS.load_local("atomcamp_vector_db", embeddings, allow_dangerous_deserialization=True)
33
  retriever = vectorstore.as_retriever()
 
34
  return "Vectorstore loaded successfully"
35
  except:
36
  sample_data = [
@@ -67,6 +70,7 @@ def initialize_vectorstore():
67
  vectorstore.save_local("atomcamp_vector_db")
68
  retriever = vectorstore.as_retriever()
69
 
 
70
  return "Sample vectorstore created successfully"
71
 
72
  def call_groq_api(message, context):
@@ -253,606 +257,299 @@ Getting Started - How to begin your data science journey
253
 
254
  Would you like me to elaborate on any specific aspect?"""
255
 
256
- initialize_groq()
257
- init_message = initialize_vectorstore()
258
-
259
- def create_chat_interface():
260
- return """
261
- <!DOCTYPE html>
262
- <html lang="en">
263
- <head>
264
- <meta charset="UTF-8">
265
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
266
- <title>atomcamp AI Chatbot</title>
267
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
268
- <style>
269
- * {
270
- margin: 0;
271
- padding: 0;
272
- box-sizing: border-box;
273
- }
274
-
275
- body {
276
- font-family: 'Inter', sans-serif;
277
- background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
278
- min-height: 100vh;
279
- display: flex;
280
- flex-direction: column;
281
- }
282
-
283
- .header {
284
- text-align: center;
285
- padding: 2rem 1rem;
286
- }
287
-
288
- .logo {
289
- width: 160px;
290
- height: 48px;
291
- margin: 0 auto 1.5rem;
292
- background: #22c55e;
293
- border-radius: 24px;
294
- display: flex;
295
- align-items: center;
296
- justify-content: center;
297
- color: white;
298
- font-weight: 700;
299
- font-size: 18px;
300
- }
301
-
302
- .title-bubble {
303
- background: #f0fdf4;
304
- border: 1px solid #bbf7d0;
305
- border-radius: 1rem;
306
- padding: 1.5rem;
307
- display: inline-block;
308
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
309
- max-width: fit-content;
310
- }
311
-
312
- .title {
313
- font-size: 1.5rem;
314
- font-weight: 600;
315
- color: #166534;
316
- margin-bottom: 0.5rem;
317
- text-align: left;
318
- }
319
-
320
- .status {
321
- display: flex;
322
- align-items: center;
323
- gap: 0.5rem;
324
- font-size: 0.875rem;
325
- font-weight: 500;
326
- text-align: left;
327
- }
328
-
329
- .status-dot {
330
- width: 8px;
331
- height: 8px;
332
- border-radius: 50%;
333
- background: #22c55e;
334
- }
335
-
336
- .status.typing .status-dot {
337
- animation: pulse 1s infinite;
338
- }
339
-
340
- .typing-dots {
341
- display: flex;
342
- gap: 4px;
343
- margin-right: 8px;
344
- }
345
-
346
- .typing-dot {
347
- width: 6px;
348
- height: 6px;
349
- background: #16a34a;
350
- border-radius: 50%;
351
- animation: bounce 1.4s infinite ease-in-out;
352
- }
353
-
354
- .typing-dot:nth-child(1) { animation-delay: -0.32s; }
355
- .typing-dot:nth-child(2) { animation-delay: -0.16s; }
356
-
357
- .chat-container {
358
- flex: 1;
359
- max-width: 4rem;
360
- margin: 0 auto;
361
- padding: 0 1rem;
362
- overflow-y: auto;
363
- max-height: calc(100vh - 300px);
364
- }
365
-
366
- .welcome-screen {
367
- text-align: center;
368
- padding: 3rem 1rem;
369
- }
370
-
371
- .welcome-card {
372
- background: white;
373
- border-radius: 1rem;
374
- padding: 2rem;
375
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
376
- max-width: 600px;
377
- margin: 0 auto;
378
- }
379
-
380
- .welcome-icon {
381
- width: 48px;
382
- height: 48px;
383
- background: #22c55e;
384
- border-radius: 12px;
385
- display: flex;
386
- align-items: center;
387
- justify-content: center;
388
- margin: 0 auto 1rem;
389
- color: white;
390
- font-size: 24px;
391
- }
392
-
393
- .message {
394
- margin-bottom: 1rem;
395
- display: flex;
396
- align-items: flex-end;
397
- gap: 0.5rem;
398
- animation: fadeIn 0.3s ease-out;
399
- }
400
-
401
- .message.user {
402
- justify-content: flex-end;
403
- }
404
-
405
- .message-bubble {
406
- max-width: 70%;
407
- padding: 0.75rem 1rem;
408
- border-radius: 1rem;
409
- font-size: 0.875rem;
410
- line-height: 1.5;
411
- }
412
-
413
- .message.user .message-bubble {
414
- background: #22c55e;
415
- color: white;
416
- border-bottom-right-radius: 0.25rem;
417
- }
418
-
419
- .message.bot .message-bubble {
420
- background: #f3f4f6;
421
- color: #374151;
422
- border-bottom-left-radius: 0.25rem;
423
- }
424
-
425
- .avatar {
426
- width: 32px;
427
- height: 32px;
428
- border-radius: 50%;
429
- display: flex;
430
- align-items: center;
431
- justify-content: center;
432
- font-size: 14px;
433
- flex-shrink: 0;
434
- }
435
-
436
- .avatar.user {
437
- background: #22c55e;
438
- color: white;
439
- }
440
-
441
- .avatar.bot {
442
- background: #e5e7eb;
443
- color: #6b7280;
444
- }
445
-
446
- .typing-indicator {
447
- display: flex;
448
- align-items: center;
449
- gap: 0.5rem;
450
- padding: 0.75rem 1rem;
451
- background: #f3f4f6;
452
- border-radius: 1rem;
453
- border-bottom-left-radius: 0.25rem;
454
- max-width: 70%;
455
- }
456
-
457
- .input-area {
458
- position: fixed;
459
- bottom: 0;
460
- left: 0;
461
- right: 0;
462
- background: rgba(255, 255, 255, 0.95);
463
- backdrop-filter: blur(10px);
464
- border-top: 1px solid #e5e7eb;
465
- padding: 1rem;
466
- }
467
-
468
- .input-container {
469
- max-width: 4rem;
470
- margin: 0 auto;
471
- position: relative;
472
- }
473
-
474
- .input-wrapper {
475
- background: #4b5563;
476
- border-radius: 2rem;
477
- display: flex;
478
- align-items: center;
479
- padding: 0.5rem;
480
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
481
- }
482
-
483
- .message-input {
484
- flex: 1;
485
- background: transparent;
486
- border: none;
487
- outline: none;
488
- color: white;
489
- padding: 0.75rem 1rem;
490
- font-size: 0.875rem;
491
- font-weight: 500;
492
- }
493
-
494
- .message-input::placeholder {
495
- color: #d1d5db;
496
- }
497
-
498
- .send-button {
499
- background: #22c55e;
500
- border: none;
501
- border-radius: 50%;
502
- width: 40px;
503
- height: 40px;
504
- display: flex;
505
- align-items: center;
506
- justify-content: center;
507
- color: white;
508
- cursor: pointer;
509
- transition: all 0.2s;
510
- }
511
-
512
- .send-button:hover:not(:disabled) {
513
- background: #16a34a;
514
- transform: scale(1.05);
515
- }
516
-
517
- .send-button:disabled {
518
- opacity: 0.5;
519
- cursor: not-allowed;
520
- }
521
-
522
- .status-bar {
523
- display: flex;
524
- justify-content: space-between;
525
- align-items: center;
526
- margin-top: 0.5rem;
527
- padding: 0 1rem;
528
- font-size: 0.75rem;
529
- color: #6b7280;
530
- font-weight: 500;
531
- }
532
-
533
- @keyframes pulse {
534
- 0%, 100% { opacity: 1; }
535
- 50% { opacity: 0.5; }
536
- }
537
-
538
- @keyframes bounce {
539
- 0%, 80%, 100% { transform: scale(0); }
540
- 40% { transform: scale(1); }
541
- }
542
-
543
- @keyframes fadeIn {
544
- from { opacity: 0; transform: translateY(8px); }
545
- to { opacity: 1; transform: translateY(0); }
546
- }
547
-
548
- @media (max-width: 768px) {
549
- .chat-container {
550
- max-width: 100%;
551
- }
552
- .input-container {
553
- max-width: 100%;
554
- }
555
- }
556
- </style>
557
- </head>
558
- <body>
559
- <div class="header">
560
- <div class="logo">atomcamp</div>
561
- <div class="title-bubble">
562
- <div class="title">Chatbot</div>
563
- <div class="status" id="status">
564
- <div class="status-dot"></div>
565
- <span id="status-text">Online</span>
566
- </div>
567
- </div>
568
- </div>
569
-
570
- <div class="chat-container" id="chatContainer">
571
- <div class="welcome-screen" id="welcomeScreen">
572
- <div class="welcome-card">
573
- <div class="welcome-icon">AI</div>
574
- <h2 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 1rem; color: #374151;">Welcome to atomcamp AI</h2>
575
- <p style="color: #6b7280; margin-bottom: 2rem; font-size: 0.875rem; line-height: 1.5;">Ask me about courses, data science concepts, and learning paths.</p>
576
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; text-align: left;">
577
- <div style="background: #f9fafb; padding: 1rem; border-radius: 0.5rem;">
578
- <h3 style="font-weight: 600; margin-bottom: 0.5rem; font-size: 0.875rem;">Ask me about:</h3>
579
- <ul style="font-size: 0.75rem; color: #6b7280; line-height: 1.5;">
580
- <li>• Course information</li>
581
- <li>• Data science concepts</li>
582
- <li>• Learning paths</li>
583
- </ul>
584
- </div>
585
- <div style="background: #f9fafb; padding: 1rem; border-radius: 0.5rem;">
586
- <h3 style="font-weight: 600; margin-bottom: 0.5rem; font-size: 0.875rem;">Try asking:</h3>
587
- <ul style="font-size: 0.75rem; color: #6b7280; line-height: 1.5;">
588
- <li>• "What courses do you offer?"</li>
589
- <li>• "Explain machine learning"</li>
590
- <li>• "How do I get started?"</li>
591
- </ul>
592
  </div>
593
  </div>
594
  </div>
595
- </div>
596
- </div>
597
-
598
- <div class="input-area">
599
- <div class="input-container">
600
- <form id="chatForm">
601
- <div class="input-wrapper">
602
- <input
603
- type="text"
604
- class="message-input"
605
- id="messageInput"
606
- placeholder="Ask me anything about atomcamp..."
607
- maxlength="1000"
608
- autocomplete="off"
609
- >
610
- <button type="submit" class="send-button" id="sendButton">
611
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
612
- <path d="M12 19V5M5 12l7-7 7 7"/>
613
- </svg>
614
- </button>
 
 
 
 
 
 
 
615
  </div>
616
- </form>
617
- <div class="status-bar">
618
- <span id="connectionStatus">Connected to atomcamp AI</span>
619
- <span id="charCount">0/1000</span>
620
  </div>
621
  </div>
622
- </div>
623
-
624
- <script>
625
- let messages = [];
626
- let isTyping = false;
627
- let isConnected = true;
628
-
629
- const chatContainer = document.getElementById('chatContainer');
630
- const welcomeScreen = document.getElementById('welcomeScreen');
631
- const chatForm = document.getElementById('chatForm');
632
- const messageInput = document.getElementById('messageInput');
633
- const sendButton = document.getElementById('sendButton');
634
- const status = document.getElementById('status');
635
- const statusText = document.getElementById('status-text');
636
- const connectionStatus = document.getElementById('connectionStatus');
637
- const charCount = document.getElementById('charCount');
638
-
639
- chatForm.addEventListener('submit', handleSubmit);
640
- messageInput.addEventListener('input', updateCharCount);
641
-
642
- function updateCharCount(e) {
643
- const count = e.target.value.length;
644
- charCount.textContent = `${count}/1000`;
645
- }
646
-
647
- async function handleSubmit(e) {
648
- e.preventDefault();
649
- const message = messageInput.value.trim();
650
-
651
- if (!message || isTyping) return;
652
-
653
- addMessage(message, 'user');
654
- messageInput.value = '';
655
- updateCharCount({ target: { value: '' } });
656
- setTyping(true);
657
-
658
- try {
659
- const response = await fetch('/predict', {
660
- method: 'POST',
661
- headers: { 'Content-Type': 'application/json' },
662
- body: JSON.stringify({ data: [message] })
663
- });
664
-
665
- const data = await response.json();
666
-
667
- await new Promise(resolve => setTimeout(resolve, 1200));
668
-
669
- addMessage(data.data[0] || 'Sorry, I could not generate a response.', 'bot');
670
- setConnected(true);
671
 
672
- } catch (error) {
673
- console.error('Error:', error);
674
- setConnected(false);
675
- addMessage('I am having trouble connecting. Please try again.', 'bot');
676
- } finally {
677
- setTyping(false);
678
- }
679
- }
680
-
681
- function addMessage(content, role) {
682
- if (messages.length === 0) {
683
- welcomeScreen.style.display = 'none';
684
- }
685
-
686
- const messageDiv = document.createElement('div');
687
- messageDiv.className = `message ${role}`;
688
-
689
- const avatar = document.createElement('div');
690
- avatar.className = `avatar ${role}`;
691
- avatar.textContent = role === 'user' ? 'U' : 'AI';
692
-
693
- const bubble = document.createElement('div');
694
- bubble.className = 'message-bubble';
695
-
696
- if (role === 'bot') {
697
- bubble.innerHTML = formatMessageContent(content);
698
- } else {
699
- bubble.textContent = content;
700
- }
701
-
702
- if (role === 'user') {
703
- messageDiv.appendChild(bubble);
704
- messageDiv.appendChild(avatar);
705
- } else {
706
- messageDiv.appendChild(avatar);
707
- messageDiv.appendChild(bubble);
708
- }
709
-
710
- chatContainer.appendChild(messageDiv);
711
- scrollToBottom();
712
-
713
- messages.push({ content, role, timestamp: new Date() });
714
- }
715
-
716
- function formatMessageContent(content) {
717
- return content.split('\\n').map(line => {
718
- if (line.trim().startsWith('•') || line.trim().startsWith('-')) {
719
- return `<div style="display: flex; align-items: flex-start; margin-bottom: 6px;">
720
- <span style="color: #16a34a; margin-right: 8px; margin-top: 2px; font-size: 0.875rem; font-weight: 500;">•</span>
721
- <span style="font-size: 0.875rem; line-height: 1.5;">${line.replace(/^[•-]\\s*/, '')}</span>
722
- </div>`;
723
- } else if (/^\\d+\\./.test(line.trim())) {
724
- const match = line.match(/^\\d+\\./);
725
- return `<div style="display: flex; align-items: flex-start; margin-bottom: 6px;">
726
- <span style="color: #16a34a; margin-right: 8px; font-weight: 600; font-size: 0.875rem;">${match ? match[0] : ''}</span>
727
- <span style="font-size: 0.875rem; line-height: 1.5;">${line.replace(/^\\d+\\.\\s*/, '')}</span>
728
- </div>`;
729
- } else if (line.trim() === '') {
730
- return '<br>';
731
- } else {
732
- return `<p style="margin-bottom: 6px; font-size: 0.875rem; line-height: 1.5;">${line}</p>`;
733
- }
734
- }).join('');
735
- }
736
-
737
- function setTyping(typing) {
738
- isTyping = typing;
739
- sendButton.disabled = typing;
740
-
741
- if (typing) {
742
- status.classList.add('typing');
743
- statusText.innerHTML = `
744
- <div class="typing-dots">
745
- <div class="typing-dot"></div>
746
- <div class="typing-dot"></div>
747
- <div class="typing-dot"></div>
748
- </div>
749
- Typing...
750
- `;
751
- showTypingIndicator();
752
- } else {
753
- status.classList.remove('typing');
754
- statusText.innerHTML = `<div class="status-dot"></div> ${isConnected ? 'Online' : 'Offline'}`;
755
- hideTypingIndicator();
756
- }
757
- }
758
-
759
- function showTypingIndicator() {
760
- const typingDiv = document.createElement('div');
761
- typingDiv.className = 'message bot';
762
- typingDiv.id = 'typingIndicator';
763
 
764
- const avatar = document.createElement('div');
765
- avatar.className = 'avatar bot';
766
- avatar.textContent = 'AI';
767
 
768
- const indicator = document.createElement('div');
769
- indicator.className = 'typing-indicator';
770
- indicator.innerHTML = `
771
- <div style="display: flex; gap: 4px;">
772
- <div style="width: 8px; height: 8px; background: #9ca3af; border-radius: 50%; animation: bounce 1.4s infinite ease-in-out;"></div>
773
- <div style="width: 8px; height: 8px; background: #9ca3af; border-radius: 50%; animation: bounce 1.4s infinite ease-in-out; animation-delay: -0.32s;"></div>
774
- <div style="width: 8px; height: 8px; background: #9ca3af; border-radius: 50%; animation: bounce 1.4s infinite ease-in-out; animation-delay: -0.16s;"></div>
775
- </div>
776
- <span style="font-size: 0.875rem; color: #6b7280; font-weight: 500;">typing...</span>
777
- `;
778
 
779
- typingDiv.appendChild(avatar);
780
- typingDiv.appendChild(indicator);
781
- chatContainer.appendChild(typingDiv);
782
- scrollToBottom();
783
- }
784
-
785
- function hideTypingIndicator() {
786
- const indicator = document.getElementById('typingIndicator');
787
- if (indicator) indicator.remove();
788
- }
789
-
790
- function setConnected(connected) {
791
- isConnected = connected;
792
- connectionStatus.textContent = connected ? 'Connected to atomcamp AI' : 'Connection lost';
793
 
794
- if (!isTyping) {
795
- statusText.innerHTML = `<div class="status-dot"></div> ${connected ? 'Online' : 'Offline'}`;
796
- }
797
- }
798
-
799
- function scrollToBottom() {
800
- setTimeout(() => {
801
- chatContainer.scrollTop = chatContainer.scrollHeight;
802
- }, 100);
803
- }
804
- </script>
805
- </body>
806
- </html>
807
- """
808
-
809
- def chat_response(message):
810
- try:
811
- if retriever:
812
- docs = retriever.get_relevant_documents(message)
813
- context = "\n\n".join([doc.page_content for doc in docs[:3]])
814
- else:
815
- context = "I'm an AI assistant for Atomcamp, a data science education platform."
816
 
817
- response = generate_response_with_groq(message, context)
818
- return response
 
819
 
820
- except Exception as e:
821
- return f"Sorry, I encountered an error: {str(e)}. Please try again."
822
-
823
- # Create the Gradio interface
824
- with gr.Blocks(
825
- title="atomcamp AI Chatbot",
826
- css="""
827
- .gradio-container { display: none !important; }
828
- body { margin: 0 !important; padding: 0 !important; }
829
- """,
830
- head="""
831
- <style>
832
- .gradio-container { display: none !important; }
833
- #root { display: none !important; }
834
- </style>
835
- """
836
- ) as demo:
837
-
838
- # Hidden Gradio components for API functionality
839
- with gr.Row(visible=False):
840
- input_text = gr.Textbox()
841
- output_text = gr.Textbox()
842
- submit_btn = gr.Button()
843
-
844
- # Custom HTML interface
845
- gr.HTML(create_chat_interface())
846
-
847
- # Set up the API endpoint
848
- submit_btn.click(
849
- fn=chat_response,
850
- inputs=input_text,
851
- outputs=output_text,
852
- api_name="predict"
853
- )
854
 
855
  if __name__ == "__main__":
 
 
856
  demo.launch(
857
  server_name="0.0.0.0",
858
  server_port=7860,
 
22
  global groq_api_key
23
  groq_api_key = os.getenv("GROQ_API_KEY")
24
  if groq_api_key:
25
+ print("Groq API key found")
26
  return "Groq API key found"
27
+ print("Groq API key not found")
28
  return "Groq API key not found"
29
 
30
  def initialize_vectorstore():
 
33
  try:
34
  vectorstore = FAISS.load_local("atomcamp_vector_db", embeddings, allow_dangerous_deserialization=True)
35
  retriever = vectorstore.as_retriever()
36
+ print("Vectorstore loaded successfully")
37
  return "Vectorstore loaded successfully"
38
  except:
39
  sample_data = [
 
70
  vectorstore.save_local("atomcamp_vector_db")
71
  retriever = vectorstore.as_retriever()
72
 
73
+ print("Sample vectorstore created successfully")
74
  return "Sample vectorstore created successfully"
75
 
76
  def call_groq_api(message, context):
 
257
 
258
  Would you like me to elaborate on any specific aspect?"""
259
 
260
+ def chat_response(message):
261
+ try:
262
+ print(f"Received message: {message}")
263
+
264
+ if retriever:
265
+ docs = retriever.get_relevant_documents(message)
266
+ context = "\n\n".join([doc.page_content for doc in docs[:3]])
267
+ else:
268
+ context = "I'm an AI assistant for Atomcamp, a data science education platform."
269
+
270
+ response = generate_response_with_groq(message, context)
271
+ print(f"Generated response: {response[:100]}...")
272
+ return response
273
+
274
+ except Exception as e:
275
+ error_msg = f"Sorry, I encountered an error: {str(e)}. Please try again."
276
+ print(f"Error in chat_response: {error_msg}")
277
+ return error_msg
278
+
279
+ # Initialize systems
280
+ print("Initializing systems...")
281
+ groq_status = initialize_groq()
282
+ vectorstore_status = initialize_vectorstore()
283
+ print(f"Groq: {groq_status}")
284
+ print(f"Vectorstore: {vectorstore_status}")
285
+
286
+ # Custom CSS and HTML
287
+ custom_css = """
288
+ body {
289
+ font-family: 'Inter', sans-serif;
290
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
291
+ margin: 0;
292
+ padding: 0;
293
+ }
294
+
295
+ .gradio-container {
296
+ max-width: none !important;
297
+ margin: 0 !important;
298
+ padding: 0 !important;
299
+ }
300
+
301
+ .main {
302
+ max-width: none !important;
303
+ margin: 0 !important;
304
+ padding: 0 !important;
305
+ }
306
+
307
+ #chatbot-interface {
308
+ min-height: 100vh;
309
+ display: flex;
310
+ flex-direction: column;
311
+ }
312
+
313
+ .header {
314
+ text-align: center;
315
+ padding: 2rem 1rem;
316
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
317
+ }
318
+
319
+ .logo {
320
+ width: 160px;
321
+ height: 48px;
322
+ margin: 0 auto 1.5rem;
323
+ background: #22c55e;
324
+ border-radius: 24px;
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ color: white;
329
+ font-weight: 700;
330
+ font-size: 18px;
331
+ }
332
+
333
+ .title-bubble {
334
+ background: #f0fdf4;
335
+ border: 1px solid #bbf7d0;
336
+ border-radius: 1rem;
337
+ padding: 1.5rem;
338
+ display: inline-block;
339
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
340
+ max-width: fit-content;
341
+ }
342
+
343
+ .title {
344
+ font-size: 1.5rem;
345
+ font-weight: 600;
346
+ color: #166534;
347
+ margin-bottom: 0.5rem;
348
+ text-align: left;
349
+ }
350
+
351
+ .status {
352
+ display: flex;
353
+ align-items: center;
354
+ gap: 0.5rem;
355
+ font-size: 0.875rem;
356
+ font-weight: 500;
357
+ text-align: left;
358
+ color: #166534;
359
+ }
360
+
361
+ .status-dot {
362
+ width: 8px;
363
+ height: 8px;
364
+ border-radius: 50%;
365
+ background: #22c55e;
366
+ animation: pulse 2s infinite;
367
+ }
368
+
369
+ @keyframes pulse {
370
+ 0%, 100% { opacity: 1; }
371
+ 50% { opacity: 0.5; }
372
+ }
373
+
374
+ .welcome-section {
375
+ flex: 1;
376
+ display: flex;
377
+ align-items: center;
378
+ justify-content: center;
379
+ padding: 2rem;
380
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
381
+ }
382
+
383
+ .welcome-card {
384
+ background: white;
385
+ border-radius: 1rem;
386
+ padding: 2rem;
387
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
388
+ max-width: 600px;
389
+ text-align: center;
390
+ }
391
+
392
+ .welcome-icon {
393
+ width: 48px;
394
+ height: 48px;
395
+ background: #22c55e;
396
+ border-radius: 12px;
397
+ display: flex;
398
+ align-items: center;
399
+ justify-content: center;
400
+ margin: 0 auto 1rem;
401
+ color: white;
402
+ font-size: 24px;
403
+ font-weight: bold;
404
+ }
405
+
406
+ .welcome-title {
407
+ font-size: 1.25rem;
408
+ font-weight: 600;
409
+ margin-bottom: 1rem;
410
+ color: #374151;
411
+ }
412
+
413
+ .welcome-description {
414
+ color: #6b7280;
415
+ margin-bottom: 2rem;
416
+ font-size: 0.875rem;
417
+ line-height: 1.5;
418
+ }
419
+
420
+ .feature-grid {
421
+ display: grid;
422
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
423
+ gap: 1rem;
424
+ text-align: left;
425
+ }
426
+
427
+ .feature-card {
428
+ background: #f9fafb;
429
+ padding: 1rem;
430
+ border-radius: 0.5rem;
431
+ }
432
+
433
+ .feature-title {
434
+ font-weight: 600;
435
+ margin-bottom: 0.5rem;
436
+ font-size: 0.875rem;
437
+ color: #374151;
438
+ }
439
+
440
+ .feature-list {
441
+ font-size: 0.75rem;
442
+ color: #6b7280;
443
+ line-height: 1.5;
444
+ list-style: none;
445
+ padding: 0;
446
+ margin: 0;
447
+ }
448
+
449
+ .feature-list li {
450
+ margin-bottom: 0.25rem;
451
+ }
452
+ """
453
+
454
+ def create_interface():
455
+ with gr.Blocks(
456
+ css=custom_css,
457
+ title="atomcamp AI Chatbot",
458
+ theme=gr.themes.Soft()
459
+ ) as demo:
460
+
461
+ gr.HTML("""
462
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
463
+ <div id="chatbot-interface">
464
+ <div class="header">
465
+ <div class="logo">atomcamp</div>
466
+ <div class="title-bubble">
467
+ <div class="title">Chatbot</div>
468
+ <div class="status">
469
+ <div class="status-dot"></div>
470
+ <span>Online & Ready</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  </div>
472
  </div>
473
  </div>
474
+
475
+ <div class="welcome-section">
476
+ <div class="welcome-card">
477
+ <div class="welcome-icon">AI</div>
478
+ <h2 class="welcome-title">Welcome to atomcamp AI</h2>
479
+ <p class="welcome-description">Ask me about courses, data science concepts, and learning paths.</p>
480
+
481
+ <div class="feature-grid">
482
+ <div class="feature-card">
483
+ <h3 class="feature-title">Ask me about:</h3>
484
+ <ul class="feature-list">
485
+ <li>• Course information</li>
486
+ <li>• Data science concepts</li>
487
+ <li>• Learning paths</li>
488
+ <li>• Career guidance</li>
489
+ </ul>
490
+ </div>
491
+ <div class="feature-card">
492
+ <h3 class="feature-title">Try asking:</h3>
493
+ <ul class="feature-list">
494
+ <li>• "What courses do you offer?"</li>
495
+ <li>• "Explain machine learning"</li>
496
+ <li>• "How do I get started?"</li>
497
+ <li>• "Career opportunities?"</li>
498
+ </ul>
499
+ </div>
500
+ </div>
501
  </div>
 
 
 
 
502
  </div>
503
  </div>
504
+ """)
505
+
506
+ with gr.Row():
507
+ with gr.Column(scale=1):
508
+ chatbot = gr.Chatbot(
509
+ label="Chat with atomcamp AI",
510
+ height=400,
511
+ show_label=True,
512
+ container=True,
513
+ type="messages"
514
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
 
516
+ with gr.Row():
517
+ msg = gr.Textbox(
518
+ placeholder="Ask me anything about atomcamp...",
519
+ container=False,
520
+ scale=4,
521
+ max_lines=3
522
+ )
523
+ submit = gr.Button(
524
+ "Send",
525
+ variant="primary",
526
+ scale=1
527
+ )
528
+
529
+ def respond(message, history):
530
+ if not message.strip():
531
+ return history, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
 
533
+ # Add user message to history
534
+ history.append({"role": "user", "content": message})
 
535
 
536
+ # Get bot response
537
+ bot_response = chat_response(message)
 
 
 
 
 
 
 
 
538
 
539
+ # Add bot response to history
540
+ history.append({"role": "assistant", "content": bot_response})
 
 
 
 
 
 
 
 
 
 
 
 
541
 
542
+ return history, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
 
544
+ # Set up event handlers
545
+ submit.click(respond, [msg, chatbot], [chatbot, msg])
546
+ msg.submit(respond, [msg, chatbot], [chatbot, msg])
547
 
548
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
 
550
  if __name__ == "__main__":
551
+ print("Starting atomcamp AI Chatbot...")
552
+ demo = create_interface()
553
  demo.launch(
554
  server_name="0.0.0.0",
555
  server_port=7860,