AptlyDigital commited on
Commit
15f4a17
·
verified ·
1 Parent(s): 040badc

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +2140 -58
index.html CHANGED
@@ -8,7 +8,7 @@
8
  <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
  <style>
11
- /* ALL EXISTING CSS REMAINS EXACTLY THE SAME - Only adding fixes */
12
  * {
13
  margin: 0;
14
  padding: 0;
@@ -346,21 +346,12 @@
346
  transform: translateY(-2px);
347
  }
348
 
349
- /* CARD FIX - prevent edge cutting */
350
  .card {
351
  background: rgba(20, 20, 30, 0.8);
352
  border-radius: 15px;
353
  padding: 20px;
354
  border: 1px solid rgba(255, 255, 255, 0.1);
355
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
356
- overflow: visible !important; /* Important fix */
357
- margin-bottom: 25px;
358
- position: relative;
359
- }
360
-
361
- /* Ensure card contents don't overflow */
362
- .card > *:not(:last-child) {
363
- margin-bottom: 15px;
364
  }
365
 
366
  .card-title {
@@ -571,53 +562,6 @@
571
  box-shadow: 0 0 0 2px rgba(90, 108, 255, 0.2);
572
  }
573
 
574
- /* NEW: Document upload styles */
575
- .document-upload-area {
576
- margin-top: 15px;
577
- padding: 15px;
578
- background: rgba(20, 20, 30, 0.5);
579
- border-radius: 10px;
580
- border: 2px dashed rgba(90, 108, 255, 0.3);
581
- text-align: center;
582
- cursor: pointer;
583
- transition: all 0.3s ease;
584
- }
585
-
586
- .document-upload-area:hover {
587
- background: rgba(20, 20, 30, 0.7);
588
- border-color: #5a6cff;
589
- }
590
-
591
- .document-upload-area.dragover {
592
- background: rgba(90, 108, 255, 0.1);
593
- border-color: #5a6cff;
594
- }
595
-
596
- .upload-icon {
597
- font-size: 2em;
598
- color: #5a6cff;
599
- margin-bottom: 10px;
600
- }
601
-
602
- .upload-text {
603
- color: #a0b0ff;
604
- margin-bottom: 10px;
605
- }
606
-
607
- .file-input {
608
- display: none;
609
- }
610
-
611
- .file-info {
612
- margin-top: 10px;
613
- padding: 8px;
614
- background: rgba(90, 108, 255, 0.1);
615
- border-radius: 8px;
616
- font-size: 0.85em;
617
- color: #a0b0ff;
618
- display: none;
619
- }
620
-
621
  .rag-controls {
622
  display: grid;
623
  grid-template-columns: 1fr 1fr;
@@ -712,4 +656,2142 @@
712
  }
713
 
714
  .error-screen {
715
- pos
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
  <style>
11
+ /* ALL EXISTING CSS REMAINS EXACTLY THE SAME */
12
  * {
13
  margin: 0;
14
  padding: 0;
 
346
  transform: translateY(-2px);
347
  }
348
 
 
349
  .card {
350
  background: rgba(20, 20, 30, 0.8);
351
  border-radius: 15px;
352
  padding: 20px;
353
  border: 1px solid rgba(255, 255, 255, 0.1);
354
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
 
 
 
 
 
 
 
 
355
  }
356
 
357
  .card-title {
 
562
  box-shadow: 0 0 0 2px rgba(90, 108, 255, 0.2);
563
  }
564
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
  .rag-controls {
566
  display: grid;
567
  grid-template-columns: 1fr 1fr;
 
656
  }
657
 
658
  .error-screen {
659
+ position: fixed;
660
+ top: 0;
661
+ left: 0;
662
+ width: 100%;
663
+ height: 100%;
664
+ background: #0a0a0f;
665
+ display: none;
666
+ flex-direction: column;
667
+ justify-content: center;
668
+ align-items: center;
669
+ z-index: 2001;
670
+ text-align: center;
671
+ padding: 20px;
672
+ }
673
+
674
+ .error-screen h2 {
675
+ color: #ff5a5a;
676
+ margin-bottom: 20px;
677
+ }
678
+
679
+ .error-screen p {
680
+ color: #8892b0;
681
+ margin-bottom: 30px;
682
+ max-width: 500px;
683
+ }
684
+
685
+ .retry-btn {
686
+ padding: 12px 30px;
687
+ background: #5a6cff;
688
+ color: white;
689
+ border: none;
690
+ border-radius: 10px;
691
+ cursor: pointer;
692
+ font-size: 1em;
693
+ transition: all 0.2s;
694
+ }
695
+
696
+ .retry-btn:hover {
697
+ background: #7a8aff;
698
+ transform: translateY(-2px);
699
+ }
700
+
701
+ @keyframes pulse {
702
+ 0%, 100% { opacity: 1; }
703
+ 50% { opacity: 0.3; }
704
+ }
705
+
706
+ @media (max-width: 1200px) {
707
+ .side-panel {
708
+ width: 280px;
709
+ padding: 20px;
710
+ }
711
+
712
+ .chat-interface {
713
+ max-width: 700px;
714
+ }
715
+ }
716
+
717
+ @media (max-width: 1024px) {
718
+ .main-container {
719
+ flex-direction: column;
720
+ }
721
+
722
+ .side-panel {
723
+ position: fixed;
724
+ top: 0;
725
+ height: 100vh;
726
+ transform: translateX(-100%);
727
+ z-index: 1000;
728
+ }
729
+
730
+ .side-panel.left {
731
+ left: 0;
732
+ }
733
+
734
+ .side-panel.right {
735
+ right: 0;
736
+ transform: translateX(100%);
737
+ }
738
+
739
+ .side-panel.active {
740
+ transform: translateX(0);
741
+ }
742
+
743
+ .mobile-nav {
744
+ display: block;
745
+ }
746
+
747
+ .main-content {
748
+ padding: 80px 20px 20px 20px;
749
+ }
750
+
751
+ #canvasContainer {
752
+ height: 50vh;
753
+ }
754
+
755
+ .chat-interface {
756
+ margin-top: 20px;
757
+ }
758
+
759
+ .chat-messages {
760
+ height: 250px;
761
+ }
762
+ }
763
+
764
+ @media (max-width: 768px) {
765
+ #canvasContainer {
766
+ height: 40vh;
767
+ }
768
+
769
+ .chat-messages {
770
+ height: 200px;
771
+ }
772
+
773
+ .side-panel {
774
+ width: 100%;
775
+ max-width: 350px;
776
+ }
777
+ }
778
+
779
+ @media (max-width: 480px) {
780
+ #canvasContainer {
781
+ height: 35vh;
782
+ }
783
+
784
+ .chat-interface {
785
+ margin-top: 15px;
786
+ }
787
+
788
+ .chat-messages {
789
+ height: 180px;
790
+ padding: 15px;
791
+ }
792
+
793
+ .chat-input-container {
794
+ padding: 15px;
795
+ }
796
+
797
+ .chat-input, .voice-input-btn, .send-btn {
798
+ min-height: 48px;
799
+ height: 48px;
800
+ }
801
+ }
802
+ </style>
803
+ </head>
804
+ <body>
805
+ <!-- Mobile Navigation -->
806
+ <div class="mobile-nav">
807
+ <div class="mobile-menu-btn" id="mobileMenuBtn">
808
+ <span></span>
809
+ <span></span>
810
+ <span></span>
811
+ </div>
812
+ </div>
813
+
814
+ <div class="mobile-panel-overlay" id="mobileOverlay"></div>
815
+
816
+ <!-- Loading Screen -->
817
+ <div class="loading-screen" id="loadingScreen">
818
+ <div class="spinner"></div>
819
+ <div id="loadingText">Initializing AI Interface...</div>
820
+ </div>
821
+
822
+ <!-- Error Screen -->
823
+ <div class="error-screen" id="errorScreen">
824
+ <h2>⚠️ Initialization Failed</h2>
825
+ <p id="errorMessage">Three.js library failed to load. Please check your internet connection or try again.</p>
826
+ <button class="retry-btn" id="retryBtn">Retry</button>
827
+ </div>
828
+
829
+ <!-- Main Container -->
830
+ <div class="main-container">
831
+ <!-- Left Side Panel -->
832
+ <aside class="side-panel left">
833
+ <div class="card">
834
+ <div class="card-title">
835
+ <i class="fas fa-sliders-h"></i>
836
+ <span>Animation Controls</span>
837
+ </div>
838
+
839
+ <div class="accordion">
840
+ <div class="accordion-header" data-target="animationControls">
841
+ <span>Particle Settings</span>
842
+ <i class="fas fa-chevron-down"></i>
843
+ </div>
844
+ <div class="accordion-content" id="animationControls">
845
+ <div class="control-group">
846
+ <label>Animation Intensity</label>
847
+ <div class="slider-container">
848
+ <input type="range" id="intensity" min="0" max="100" value="50">
849
+ <span class="value-display" id="intensityValue">50</span>
850
+ </div>
851
+ </div>
852
+
853
+ <div class="control-group">
854
+ <label>Particle Count</label>
855
+ <div class="slider-container">
856
+ <input type="range" id="particleCount" min="100" max="5000" value="2000" step="100">
857
+ <span class="value-display" id="particleCountValue">2000</span>
858
+ </div>
859
+ </div>
860
+
861
+ <div class="control-group">
862
+ <label>Energy Level</label>
863
+ <div class="slider-container">
864
+ <input type="range" id="energy" min="0" max="100" value="30">
865
+ <span class="value-display" id="energyValue">30</span>
866
+ </div>
867
+ </div>
868
+
869
+ <div class="button-grid">
870
+ <button class="preset-btn" data-preset="listening">🎤 Listening</button>
871
+ <button class="preset-btn" data-preset="processing">⚡ Processing</button>
872
+ <button class="preset-btn" data-preset="responding">💬 Responding</button>
873
+ <button class="preset-btn" data-preset="exploring">🔍 Exploring</button>
874
+ <button class="preset-btn" data-preset="teaching">📚 Teaching</button>
875
+ <button class="preset-btn" data-preset="idle">🌀 Idle</button>
876
+ </div>
877
+ </div>
878
+ </div>
879
+ </div>
880
+
881
+ <div class="card">
882
+ <div class="card-title">
883
+ <i class="fas fa-graduation-cap"></i>
884
+ <span>AI Training</span>
885
+ </div>
886
+
887
+ <div class="accordion">
888
+ <div class="accordion-header" data-target="trainingControls">
889
+ <span>RAG Training</span>
890
+ <i class="fas fa-chevron-down"></i>
891
+ </div>
892
+ <div class="accordion-content" id="trainingControls">
893
+ <textarea class="rag-textarea" id="knowledgeText" placeholder="Add knowledge for AI training..."></textarea>
894
+
895
+ <div class="rag-controls">
896
+ <button class="rag-btn" id="addKnowledgeBtn">
897
+ <i class="fas fa-plus"></i>
898
+ Add Text
899
+ </button>
900
+ <button class="rag-btn primary" id="trainBtn">
901
+ <i class="fas fa-brain"></i>
902
+ Train AI
903
+ </button>
904
+ </div>
905
+
906
+ <div class="rag-progress">
907
+ <div class="rag-progress-bar" id="trainingProgress"></div>
908
+ </div>
909
+
910
+ <div class="rag-status" id="ragStatus" style="display: none;">
911
+ <i class="fas fa-sync-alt fa-spin"></i>
912
+ <span id="ragStatusText">Training in progress...</span>
913
+ </div>
914
+ </div>
915
+ </div>
916
+ </div>
917
+ </aside>
918
+
919
+ <!-- Main Content -->
920
+ <main class="main-content">
921
+ <div id="canvasContainer">
922
+ <canvas id="mainCanvas"></canvas>
923
+ </div>
924
+
925
+ <div class="chat-interface">
926
+ <div class="chat-container">
927
+ <div class="chat-header">
928
+ <div class="chat-title">
929
+ <i class="fas fa-robot"></i>
930
+ <span>AI Tutor Assistant</span>
931
+ </div>
932
+ <div class="chat-status">
933
+ <span class="status-indicator"></span>
934
+ <span id="chatStatusText">Online</span>
935
+ </div>
936
+ </div>
937
+
938
+ <div class="chat-messages" id="chatMessages">
939
+ <!-- Messages will be dynamically added here -->
940
+ <div class="message message-ai">
941
+ <div class="message-content">
942
+ Hello! I'm your AI Tutor. I can help explain concepts, answer questions, and guide your learning. How can I assist you today?
943
+ </div>
944
+ <div class="message-time">Just now</div>
945
+ </div>
946
+
947
+ <div class="message message-ai">
948
+ <div class="message-content">
949
+ You can type your questions or click the microphone to speak. I'll respond with explanations, examples, and follow-up questions to enhance your understanding.
950
+ </div>
951
+ <div class="message-time">Just now</div>
952
+ </div>
953
+ </div>
954
+
955
+ <div class="chat-input-container">
956
+ <div class="input-wrapper">
957
+ <textarea
958
+ class="chat-input"
959
+ id="chatInput"
960
+ placeholder="Type your question or click the microphone to speak..."
961
+ rows="1"
962
+ ></textarea>
963
+
964
+ <button class="voice-input-btn" id="voiceBtn" title="Start voice input">
965
+ <i class="fas fa-microphone"></i>
966
+ </button>
967
+
968
+ <button class="send-btn" id="sendBtn" title="Send message">
969
+ <i class="fas fa-paper-plane"></i>
970
+ </button>
971
+ </div>
972
+ </div>
973
+ </div>
974
+ </div>
975
+ </main>
976
+
977
+ <!-- Right Side Panel -->
978
+ <aside class="side-panel right">
979
+ <div class="card">
980
+ <div class="card-title">
981
+ <i class="fas fa-robot"></i>
982
+ <span>Voice Synthesis</span>
983
+ </div>
984
+
985
+ <div class="accordion">
986
+ <div class="accordion-header" data-target="voiceControls">
987
+ <span>Voice Settings</span>
988
+ <i class="fas fa-chevron-down"></i>
989
+ </div>
990
+ <div class="accordion-content" id="voiceControls">
991
+ <div class="control-group">
992
+ <label>Voice Selection</label>
993
+ <select class="voice-select" id="voiceSelect">
994
+ <option value="">Loading voices...</option>
995
+ </select>
996
+ </div>
997
+
998
+ <div class="control-group">
999
+ <label>Speech Rate</label>
1000
+ <div class="slider-container">
1001
+ <input type="range" class="voice-slider" id="rateSlider" min="0.5" max="2" step="0.1" value="1">
1002
+ <span class="value-display" id="rateValue">1.0x</span>
1003
+ </div>
1004
+ </div>
1005
+
1006
+ <div class="control-group">
1007
+ <label>Pitch Variation</label>
1008
+ <div class="slider-container">
1009
+ <input type="range" class="voice-slider" id="pitchSlider" min="0.5" max="2" step="0.1" value="1">
1010
+ <span class="value-display" id="pitchValue">1.0</span>
1011
+ </div>
1012
+ </div>
1013
+
1014
+ <div class="control-group">
1015
+ <label>Volume Level</label>
1016
+ <div class="slider-container">
1017
+ <input type="range" class="voice-slider" id="volumeSlider" min="0.1" max="1" step="0.1" value="0.8">
1018
+ <span class="value-display" id="volumeValue">80%</span>
1019
+ </div>
1020
+ </div>
1021
+
1022
+ <div class="voice-control-buttons">
1023
+ <button class="voice-btn" id="autoSpeakBtn" data-enabled="true">
1024
+ <i class="fas fa-bullhorn"></i>
1025
+ Auto-Speak
1026
+ </button>
1027
+ <button class="voice-btn stop" id="stopBtn">
1028
+ <i class="fas fa-stop"></i>
1029
+ Stop
1030
+ </button>
1031
+ </div>
1032
+
1033
+ <button class="voice-btn" id="previewBtn" style="width: 100%; margin-top: 10px;">
1034
+ <i class="fas fa-play"></i>
1035
+ Preview Voice
1036
+ </button>
1037
+ </div>
1038
+ </div>
1039
+ </div>
1040
+
1041
+ <div class="card">
1042
+ <div class="card-title">
1043
+ <i class="fas fa-plug"></i>
1044
+ <span>LLM Integration</span>
1045
+ </div>
1046
+
1047
+ <div class="accordion">
1048
+ <div class="accordion-header" data-target="llmControls">
1049
+ <span>API Settings</span>
1050
+ <i class="fas fa-chevron-down"></i>
1051
+ </div>
1052
+ <div class="accordion-content" id="llmControls">
1053
+ <div class="control-group">
1054
+ <label>Primary LLM API</label>
1055
+ <select class="voice-select" id="llmProvider">
1056
+ <option value="huggingface">Hugging Face</option>
1057
+ <option value="openai">OpenAI</option>
1058
+ <option value="anthropic">Anthropic Claude</option>
1059
+ <option value="google">Google Gemini</option>
1060
+ <option value="azure">Azure OpenAI</option>
1061
+ <option value="local">Local API</option>
1062
+ </select>
1063
+ </div>
1064
+
1065
+ <div class="control-group">
1066
+ <label>API Endpoint</label>
1067
+ <input type="text" class="rag-textarea" id="apiEndpoint" placeholder="https://api-inference.huggingface.co/models/microsoft/DialoGPT-medium">
1068
+ </div>
1069
+
1070
+ <div class="control-group">
1071
+ <label>API Key</label>
1072
+ <input type="password" class="rag-textarea" id="apiKey" placeholder="Enter your API key">
1073
+ </div>
1074
+
1075
+ <div class="control-group">
1076
+ <label>Model Name</label>
1077
+ <input type="text" class="rag-textarea" id="modelName" placeholder="microsoft/DialoGPT-medium">
1078
+ </div>
1079
+
1080
+ <div class="rag-controls">
1081
+ <button class="rag-btn" id="testApiBtn">
1082
+ <i class="fas fa-wifi"></i>
1083
+ Test Connection
1084
+ </button>
1085
+ <button class="rag-btn primary" id="saveApiBtn">
1086
+ <i class="fas fa-save"></i>
1087
+ Save Settings
1088
+ </button>
1089
+ </div>
1090
+
1091
+ <div class="rag-status" id="apiStatus" style="display: none;">
1092
+ <i class="fas fa-sync-alt fa-spin"></i>
1093
+ <span id="apiStatusText">Testing connection...</span>
1094
+ </div>
1095
+ </div>
1096
+ </div>
1097
+ </div>
1098
+ </aside>
1099
+ </div>
1100
+
1101
+ <script>
1102
+ // Check if Three.js loaded properly
1103
+ function checkThreeJS() {
1104
+ if (typeof THREE === 'undefined') {
1105
+ console.error('Three.js failed to load');
1106
+ showError('Three.js library failed to load. Please check your internet connection.');
1107
+ return false;
1108
+ }
1109
+ return true;
1110
+ }
1111
+
1112
+ function showError(message) {
1113
+ const errorScreen = document.getElementById('errorScreen');
1114
+ const errorMessage = document.getElementById('errorMessage');
1115
+ const loadingScreen = document.getElementById('loadingScreen');
1116
+
1117
+ loadingScreen.style.display = 'none';
1118
+ errorMessage.textContent = message;
1119
+ errorScreen.style.display = 'flex';
1120
+ }
1121
+
1122
+ function hideLoading() {
1123
+ const loadingScreen = document.getElementById('loadingScreen');
1124
+ loadingScreen.style.opacity = '0';
1125
+ setTimeout(() => {
1126
+ loadingScreen.style.display = 'none';
1127
+ }, 500);
1128
+ }
1129
+
1130
+ function updateStatus(text) {
1131
+ document.getElementById('loadingText').textContent = text;
1132
+ }
1133
+
1134
+ // ==================== FULLY FUNCTIONAL LLM INTEGRATION ====================
1135
+ class LLMIntegration {
1136
+ constructor() {
1137
+ this.provider = 'huggingface';
1138
+ this.apiEndpoint = '';
1139
+ this.apiKey = '';
1140
+ this.modelName = '';
1141
+ this.isConnected = false;
1142
+ this.connectionTested = false;
1143
+
1144
+ // Default endpoints for each provider
1145
+ this.defaultEndpoints = {
1146
+ 'huggingface': 'https://api-inference.huggingface.co/models/microsoft/DialoGPT-medium',
1147
+ 'openai': 'https://api.openai.com/v1/chat/completions',
1148
+ 'anthropic': 'https://api.anthropic.com/v1/messages',
1149
+ 'google': 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent',
1150
+ 'azure': 'https://YOUR_RESOURCE.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT/chat/completions',
1151
+ 'local': 'http://localhost:11434/api/generate'
1152
+ };
1153
+
1154
+ this.init();
1155
+ }
1156
+
1157
+ init() {
1158
+ this.loadSettings();
1159
+ this.setupEventListeners();
1160
+ this.updateProviderUI();
1161
+ }
1162
+
1163
+ loadSettings() {
1164
+ try {
1165
+ const settings = JSON.parse(localStorage.getItem('aiTutorLLMSettings')) || {};
1166
+
1167
+ this.provider = settings.provider || 'huggingface';
1168
+ this.apiEndpoint = settings.apiEndpoint || this.defaultEndpoints[this.provider];
1169
+ this.apiKey = settings.apiKey || '';
1170
+ this.modelName = settings.modelName || '';
1171
+
1172
+ // Update UI
1173
+ document.getElementById('llmProvider').value = this.provider;
1174
+ document.getElementById('apiEndpoint').value = this.apiEndpoint;
1175
+ document.getElementById('apiKey').value = this.apiKey;
1176
+ document.getElementById('modelName').value = this.modelName;
1177
+
1178
+ console.log('Loaded LLM settings:', {
1179
+ provider: this.provider,
1180
+ endpoint: this.apiEndpoint.substring(0, 50) + '...',
1181
+ hasKey: !!this.apiKey,
1182
+ model: this.modelName
1183
+ });
1184
+
1185
+ } catch (error) {
1186
+ console.error('Error loading LLM settings:', error);
1187
+ }
1188
+ }
1189
+
1190
+ saveSettings() {
1191
+ try {
1192
+ const settings = {
1193
+ provider: this.provider,
1194
+ apiEndpoint: this.apiEndpoint,
1195
+ apiKey: this.apiKey,
1196
+ modelName: this.modelName,
1197
+ savedAt: new Date().toISOString()
1198
+ };
1199
+
1200
+ localStorage.setItem('aiTutorLLMSettings', JSON.stringify(settings));
1201
+ console.log('Saved LLM settings');
1202
+ return true;
1203
+ } catch (error) {
1204
+ console.error('Error saving LLM settings:', error);
1205
+ return false;
1206
+ }
1207
+ }
1208
+
1209
+ setupEventListeners() {
1210
+ // Provider change
1211
+ document.getElementById('llmProvider').addEventListener('change', (e) => {
1212
+ this.provider = e.target.value;
1213
+ this.apiEndpoint = this.defaultEndpoints[this.provider];
1214
+ document.getElementById('apiEndpoint').value = this.apiEndpoint;
1215
+ this.updateProviderUI();
1216
+ });
1217
+
1218
+ // Endpoint change
1219
+ document.getElementById('apiEndpoint').addEventListener('input', (e) => {
1220
+ this.apiEndpoint = e.target.value.trim();
1221
+ });
1222
+
1223
+ // API Key change
1224
+ document.getElementById('apiKey').addEventListener('input', (e) => {
1225
+ this.apiKey = e.target.value.trim();
1226
+ });
1227
+
1228
+ // Model name change
1229
+ document.getElementById('modelName').addEventListener('input', (e) => {
1230
+ this.modelName = e.target.value.trim();
1231
+ });
1232
+
1233
+ // Test connection button
1234
+ document.getElementById('testApiBtn').addEventListener('click', () => {
1235
+ this.testConnection();
1236
+ });
1237
+
1238
+ // Save settings button
1239
+ document.getElementById('saveApiBtn').addEventListener('click', () => {
1240
+ this.saveSettings();
1241
+ this.showApiStatus('Settings saved successfully!', 'success');
1242
+ });
1243
+ }
1244
+
1245
+ updateProviderUI() {
1246
+ // Update placeholder based on provider
1247
+ const endpointInput = document.getElementById('apiEndpoint');
1248
+ const keyInput = document.getElementById('apiKey');
1249
+ const modelInput = document.getElementById('modelName');
1250
+
1251
+ switch(this.provider) {
1252
+ case 'huggingface':
1253
+ endpointInput.placeholder = 'https://api-inference.huggingface.co/models/{model}';
1254
+ keyInput.placeholder = 'Your Hugging Face API token';
1255
+ modelInput.placeholder = 'microsoft/DialoGPT-medium';
1256
+ break;
1257
+ case 'openai':
1258
+ endpointInput.placeholder = 'https://api.openai.com/v1/chat/completions';
1259
+ keyInput.placeholder = 'Your OpenAI API key';
1260
+ modelInput.placeholder = 'gpt-3.5-turbo';
1261
+ break;
1262
+ case 'anthropic':
1263
+ endpointInput.placeholder = 'https://api.anthropic.com/v1/messages';
1264
+ keyInput.placeholder = 'Your Anthropic API key';
1265
+ modelInput.placeholder = 'claude-3-sonnet-20240229';
1266
+ break;
1267
+ case 'google':
1268
+ endpointInput.placeholder = 'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent';
1269
+ keyInput.placeholder = 'Your Google API key';
1270
+ modelInput.placeholder = 'gemini-pro';
1271
+ break;
1272
+ case 'azure':
1273
+ endpointInput.placeholder = 'https://{resource}.openai.azure.com/openai/deployments/{deployment}/chat/completions';
1274
+ keyInput.placeholder = 'Your Azure OpenAI API key';
1275
+ modelInput.placeholder = 'gpt-35-turbo';
1276
+ break;
1277
+ case 'local':
1278
+ endpointInput.placeholder = 'http://localhost:11434/api/generate';
1279
+ keyInput.placeholder = 'Leave empty for local setup';
1280
+ modelInput.placeholder = 'llama2';
1281
+ break;
1282
+ }
1283
+ }
1284
+
1285
+ async testConnection() {
1286
+ if (!this.apiEndpoint) {
1287
+ this.showApiStatus('Please enter an API endpoint', 'error');
1288
+ return false;
1289
+ }
1290
+
1291
+ if (this.provider !== 'local' && !this.apiKey) {
1292
+ this.showApiStatus('Please enter an API key', 'error');
1293
+ return false;
1294
+ }
1295
+
1296
+ this.showApiStatus('Testing connection...', 'processing');
1297
+
1298
+ try {
1299
+ let testResult;
1300
+
1301
+ switch(this.provider) {
1302
+ case 'huggingface':
1303
+ testResult = await this.testHuggingFaceConnection();
1304
+ break;
1305
+ case 'openai':
1306
+ testResult = await this.testOpenAIConnection();
1307
+ break;
1308
+ case 'anthropic':
1309
+ testResult = await this.testAnthropicConnection();
1310
+ break;
1311
+ case 'google':
1312
+ testResult = await this.testGoogleConnection();
1313
+ break;
1314
+ case 'azure':
1315
+ testResult = await this.testAzureConnection();
1316
+ break;
1317
+ case 'local':
1318
+ testResult = await this.testLocalConnection();
1319
+ break;
1320
+ default:
1321
+ testResult = await this.testGenericConnection();
1322
+ }
1323
+
1324
+ if (testResult.success) {
1325
+ this.isConnected = true;
1326
+ this.connectionTested = true;
1327
+ this.showApiStatus(`Connected successfully to ${this.provider}`, 'success');
1328
+ return true;
1329
+ } else {
1330
+ this.isConnected = false;
1331
+ this.showApiStatus(`Connection failed: ${testResult.error}`, 'error');
1332
+ return false;
1333
+ }
1334
+ } catch (error) {
1335
+ this.isConnected = false;
1336
+ this.showApiStatus(`Connection error: ${error.message}`, 'error');
1337
+ return false;
1338
+ }
1339
+ }
1340
+
1341
+ async testHuggingFaceConnection() {
1342
+ const model = this.modelName || 'microsoft/DialoGPT-medium';
1343
+ const endpoint = this.apiEndpoint.replace('{model}', model);
1344
+
1345
+ const response = await fetch(endpoint, {
1346
+ method: 'POST',
1347
+ headers: {
1348
+ 'Authorization': `Bearer ${this.apiKey}`,
1349
+ 'Content-Type': 'application/json'
1350
+ },
1351
+ body: JSON.stringify({
1352
+ inputs: 'Hello',
1353
+ parameters: {
1354
+ max_new_tokens: 10
1355
+ }
1356
+ })
1357
+ });
1358
+
1359
+ if (response.ok) {
1360
+ return { success: true };
1361
+ } else {
1362
+ return {
1363
+ success: false,
1364
+ error: `HTTP ${response.status}: ${response.statusText}`
1365
+ };
1366
+ }
1367
+ }
1368
+
1369
+ async testOpenAIConnection() {
1370
+ const response = await fetch(this.apiEndpoint, {
1371
+ method: 'POST',
1372
+ headers: {
1373
+ 'Authorization': `Bearer ${this.apiKey}`,
1374
+ 'Content-Type': 'application/json'
1375
+ },
1376
+ body: JSON.stringify({
1377
+ model: this.modelName || 'gpt-3.5-turbo',
1378
+ messages: [{ role: 'user', content: 'Hello' }],
1379
+ max_tokens: 10
1380
+ })
1381
+ });
1382
+
1383
+ if (response.ok) {
1384
+ return { success: true };
1385
+ } else {
1386
+ const errorData = await response.json().catch(() => ({}));
1387
+ return {
1388
+ success: false,
1389
+ error: errorData.error?.message || `HTTP ${response.status}`
1390
+ };
1391
+ }
1392
+ }
1393
+
1394
+ async testAnthropicConnection() {
1395
+ const response = await fetch(this.apiEndpoint, {
1396
+ method: 'POST',
1397
+ headers: {
1398
+ 'x-api-key': this.apiKey,
1399
+ 'anthropic-version': '2023-06-01',
1400
+ 'Content-Type': 'application/json'
1401
+ },
1402
+ body: JSON.stringify({
1403
+ model: this.modelName || 'claude-3-sonnet-20240229',
1404
+ messages: [{ role: 'user', content: 'Hello' }],
1405
+ max_tokens: 10
1406
+ })
1407
+ });
1408
+
1409
+ if (response.ok) {
1410
+ return { success: true };
1411
+ } else {
1412
+ const errorData = await response.json().catch(() => ({}));
1413
+ return {
1414
+ success: false,
1415
+ error: errorData.error?.message || `HTTP ${response.status}`
1416
+ };
1417
+ }
1418
+ }
1419
+
1420
+ async testGoogleConnection() {
1421
+ const endpoint = this.apiEndpoint.includes('{model}')
1422
+ ? this.apiEndpoint.replace('{model}', this.modelName || 'gemini-pro')
1423
+ : this.apiEndpoint;
1424
+
1425
+ const url = new URL(endpoint);
1426
+ url.searchParams.append('key', this.apiKey);
1427
+
1428
+ const response = await fetch(url.toString(), {
1429
+ method: 'POST',
1430
+ headers: {
1431
+ 'Content-Type': 'application/json'
1432
+ },
1433
+ body: JSON.stringify({
1434
+ contents: [{
1435
+ parts: [{ text: 'Hello' }]
1436
+ }]
1437
+ })
1438
+ });
1439
+
1440
+ if (response.ok) {
1441
+ return { success: true };
1442
+ } else {
1443
+ const errorData = await response.json().catch(() => ({}));
1444
+ return {
1445
+ success: false,
1446
+ error: errorData.error?.message || `HTTP ${response.status}`
1447
+ };
1448
+ }
1449
+ }
1450
+
1451
+ async testAzureConnection() {
1452
+ const response = await fetch(this.apiEndpoint, {
1453
+ method: 'POST',
1454
+ headers: {
1455
+ 'api-key': this.apiKey,
1456
+ 'Content-Type': 'application/json'
1457
+ },
1458
+ body: JSON.stringify({
1459
+ messages: [{ role: 'user', content: 'Hello' }],
1460
+ max_tokens: 10
1461
+ })
1462
+ });
1463
+
1464
+ if (response.ok) {
1465
+ return { success: true };
1466
+ } else {
1467
+ const errorData = await response.json().catch(() => ({}));
1468
+ return {
1469
+ success: false,
1470
+ error: errorData.error?.message || `HTTP ${response.status}`
1471
+ };
1472
+ }
1473
+ }
1474
+
1475
+ async testLocalConnection() {
1476
+ const response = await fetch(this.apiEndpoint, {
1477
+ method: 'POST',
1478
+ headers: {
1479
+ 'Content-Type': 'application/json'
1480
+ },
1481
+ body: JSON.stringify({
1482
+ model: this.modelName || 'llama2',
1483
+ prompt: 'Hello',
1484
+ stream: false
1485
+ })
1486
+ });
1487
+
1488
+ if (response.ok) {
1489
+ return { success: true };
1490
+ } else {
1491
+ return {
1492
+ success: false,
1493
+ error: `HTTP ${response.status}`
1494
+ };
1495
+ }
1496
+ }
1497
+
1498
+ async testGenericConnection() {
1499
+ const response = await fetch(this.apiEndpoint, {
1500
+ method: 'GET',
1501
+ headers: {
1502
+ 'Authorization': this.apiKey ? `Bearer ${this.apiKey}` : undefined,
1503
+ 'Content-Type': 'application/json'
1504
+ }
1505
+ });
1506
+
1507
+ if (response.ok) {
1508
+ return { success: true };
1509
+ } else {
1510
+ return {
1511
+ success: false,
1512
+ error: `HTTP ${response.status}`
1513
+ };
1514
+ }
1515
+ }
1516
+
1517
+ async generateResponse(prompt, context = '') {
1518
+ if (!this.isConnected) {
1519
+ console.log('LLM not connected, using fallback');
1520
+ return null;
1521
+ }
1522
+
1523
+ try {
1524
+ const fullPrompt = context ? `${context}\n\nQuestion: ${prompt}` : prompt;
1525
+
1526
+ switch(this.provider) {
1527
+ case 'huggingface':
1528
+ return await this.generateHuggingFaceResponse(fullPrompt);
1529
+ case 'openai':
1530
+ return await this.generateOpenAIResponse(fullPrompt);
1531
+ case 'anthropic':
1532
+ return await this.generateAnthropicResponse(fullPrompt);
1533
+ case 'google':
1534
+ return await this.generateGoogleResponse(fullPrompt);
1535
+ case 'azure':
1536
+ return await this.generateAzureResponse(fullPrompt);
1537
+ case 'local':
1538
+ return await this.generateLocalResponse(fullPrompt);
1539
+ default:
1540
+ return await this.generateGenericResponse(fullPrompt);
1541
+ }
1542
+ } catch (error) {
1543
+ console.error('LLM generation error:', error);
1544
+ return null;
1545
+ }
1546
+ }
1547
+
1548
+ async generateHuggingFaceResponse(prompt) {
1549
+ const model = this.modelName || 'microsoft/DialoGPT-medium';
1550
+ const endpoint = this.apiEndpoint.replace('{model}', model);
1551
+
1552
+ const response = await fetch(endpoint, {
1553
+ method: 'POST',
1554
+ headers: {
1555
+ 'Authorization': `Bearer ${this.apiKey}`,
1556
+ 'Content-Type': 'application/json'
1557
+ },
1558
+ body: JSON.stringify({
1559
+ inputs: prompt,
1560
+ parameters: {
1561
+ max_new_tokens: 500,
1562
+ temperature: 0.7,
1563
+ top_p: 0.9,
1564
+ repetition_penalty: 1.1,
1565
+ return_full_text: false
1566
+ }
1567
+ })
1568
+ });
1569
+
1570
+ if (!response.ok) {
1571
+ throw new Error(`HTTP ${response.status}`);
1572
+ }
1573
+
1574
+ const data = await response.json();
1575
+ return data[0]?.generated_text || data.generated_text || '';
1576
+ }
1577
+
1578
+ async generateOpenAIResponse(prompt) {
1579
+ const response = await fetch(this.apiEndpoint, {
1580
+ method: 'POST',
1581
+ headers: {
1582
+ 'Authorization': `Bearer ${this.apiKey}`,
1583
+ 'Content-Type': 'application/json'
1584
+ },
1585
+ body: JSON.stringify({
1586
+ model: this.modelName || 'gpt-3.5-turbo',
1587
+ messages: [
1588
+ { role: 'system', content: 'You are a helpful AI tutor.' },
1589
+ { role: 'user', content: prompt }
1590
+ ],
1591
+ max_tokens: 500,
1592
+ temperature: 0.7
1593
+ })
1594
+ });
1595
+
1596
+ if (!response.ok) {
1597
+ const errorData = await response.json();
1598
+ throw new Error(errorData.error?.message || `HTTP ${response.status}`);
1599
+ }
1600
+
1601
+ const data = await response.json();
1602
+ return data.choices[0]?.message?.content || '';
1603
+ }
1604
+
1605
+ async generateAnthropicResponse(prompt) {
1606
+ const response = await fetch(this.apiEndpoint, {
1607
+ method: 'POST',
1608
+ headers: {
1609
+ 'x-api-key': this.apiKey,
1610
+ 'anthropic-version': '2023-06-01',
1611
+ 'Content-Type': 'application/json'
1612
+ },
1613
+ body: JSON.stringify({
1614
+ model: this.modelName || 'claude-3-sonnet-20240229',
1615
+ messages: [{ role: 'user', content: prompt }],
1616
+ max_tokens: 500
1617
+ })
1618
+ });
1619
+
1620
+ if (!response.ok) {
1621
+ const errorData = await response.json();
1622
+ throw new Error(errorData.error?.message || `HTTP ${response.status}`);
1623
+ }
1624
+
1625
+ const data = await response.json();
1626
+ return data.content[0]?.text || '';
1627
+ }
1628
+
1629
+ async generateGoogleResponse(prompt) {
1630
+ const endpoint = this.apiEndpoint.includes('{model}')
1631
+ ? this.apiEndpoint.replace('{model}', this.modelName || 'gemini-pro')
1632
+ : this.apiEndpoint;
1633
+
1634
+ const url = new URL(endpoint);
1635
+ url.searchParams.append('key', this.apiKey);
1636
+
1637
+ const response = await fetch(url.toString(), {
1638
+ method: 'POST',
1639
+ headers: {
1640
+ 'Content-Type': 'application/json'
1641
+ },
1642
+ body: JSON.stringify({
1643
+ contents: [{
1644
+ parts: [{ text: prompt }]
1645
+ }],
1646
+ generationConfig: {
1647
+ maxOutputTokens: 500,
1648
+ temperature: 0.7
1649
+ }
1650
+ })
1651
+ });
1652
+
1653
+ if (!response.ok) {
1654
+ const errorData = await response.json();
1655
+ throw new Error(errorData.error?.message || `HTTP ${response.status}`);
1656
+ }
1657
+
1658
+ const data = await response.json();
1659
+ return data.candidates?.[0]?.content?.parts?.[0]?.text || '';
1660
+ }
1661
+
1662
+ async generateAzureResponse(prompt) {
1663
+ const response = await fetch(this.apiEndpoint, {
1664
+ method: 'POST',
1665
+ headers: {
1666
+ 'api-key': this.apiKey,
1667
+ 'Content-Type': 'application/json'
1668
+ },
1669
+ body: JSON.stringify({
1670
+ messages: [
1671
+ { role: 'system', content: 'You are a helpful AI tutor.' },
1672
+ { role: 'user', content: prompt }
1673
+ ],
1674
+ max_tokens: 500,
1675
+ temperature: 0.7
1676
+ })
1677
+ });
1678
+
1679
+ if (!response.ok) {
1680
+ const errorData = await response.json();
1681
+ throw new Error(errorData.error?.message || `HTTP ${response.status}`);
1682
+ }
1683
+
1684
+ const data = await response.json();
1685
+ return data.choices[0]?.message?.content || '';
1686
+ }
1687
+
1688
+ async generateLocalResponse(prompt) {
1689
+ const response = await fetch(this.apiEndpoint, {
1690
+ method: 'POST',
1691
+ headers: {
1692
+ 'Content-Type': 'application/json'
1693
+ },
1694
+ body: JSON.stringify({
1695
+ model: this.modelName || 'llama2',
1696
+ prompt: prompt,
1697
+ stream: false,
1698
+ options: {
1699
+ temperature: 0.7,
1700
+ num_predict: 500
1701
+ }
1702
+ })
1703
+ });
1704
+
1705
+ if (!response.ok) {
1706
+ throw new Error(`HTTP ${response.status}`);
1707
+ }
1708
+
1709
+ const data = await response.json();
1710
+ return data.response || '';
1711
+ }
1712
+
1713
+ async generateGenericResponse(prompt) {
1714
+ const response = await fetch(this.apiEndpoint, {
1715
+ method: 'POST',
1716
+ headers: {
1717
+ 'Authorization': this.apiKey ? `Bearer ${this.apiKey}` : undefined,
1718
+ 'Content-Type': 'application/json'
1719
+ },
1720
+ body: JSON.stringify({
1721
+ prompt: prompt,
1722
+ max_tokens: 500,
1723
+ temperature: 0.7
1724
+ })
1725
+ });
1726
+
1727
+ if (!response.ok) {
1728
+ throw new Error(`HTTP ${response.status}`);
1729
+ }
1730
+
1731
+ const data = await response.json();
1732
+ return data.text || data.response || data.choices?.[0]?.text || '';
1733
+ }
1734
+
1735
+ showApiStatus(message, type = 'info') {
1736
+ const statusEl = document.getElementById('apiStatus');
1737
+ const statusText = document.getElementById('apiStatusText');
1738
+
1739
+ statusText.textContent = message;
1740
+
1741
+ const colors = {
1742
+ 'info': '#5a6cff',
1743
+ 'success': '#00ff9d',
1744
+ 'error': '#ff5a5a',
1745
+ 'processing': '#a0b0ff'
1746
+ };
1747
+
1748
+ statusEl.style.borderColor = colors[type] + '30';
1749
+ statusEl.style.background = colors[type] + '10';
1750
+ statusEl.style.color = colors[type];
1751
+
1752
+ statusEl.style.display = 'flex';
1753
+
1754
+ if (type !== 'processing') {
1755
+ setTimeout(() => {
1756
+ statusEl.style.display = 'none';
1757
+ }, 5000);
1758
+ }
1759
+ }
1760
+ }
1761
+
1762
+ // Mobile Menu Functionality
1763
+ class MobileMenu {
1764
+ constructor() {
1765
+ this.menuBtn = document.getElementById('mobileMenuBtn');
1766
+ this.overlay = document.getElementById('mobileOverlay');
1767
+ this.leftPanel = document.querySelector('.side-panel.left');
1768
+ this.rightPanel = document.querySelector('.side-panel.right');
1769
+ this.isOpen = false;
1770
+ this.currentPanel = null;
1771
+
1772
+ this.init();
1773
+ }
1774
+
1775
+ init() {
1776
+ this.menuBtn.addEventListener('click', () => this.toggleMenu());
1777
+ this.overlay.addEventListener('click', () => this.closeMenu());
1778
+
1779
+ document.addEventListener('keydown', (e) => {
1780
+ if (e.key === 'Escape') {
1781
+ this.closeMenu();
1782
+ }
1783
+ });
1784
+ }
1785
+
1786
+ toggleMenu() {
1787
+ if (!this.isOpen) {
1788
+ this.openMenu('left');
1789
+ } else {
1790
+ this.closeMenu();
1791
+ }
1792
+ }
1793
+
1794
+ openMenu(panel) {
1795
+ this.isOpen = true;
1796
+ this.currentPanel = panel;
1797
+ this.menuBtn.classList.add('active');
1798
+
1799
+ if (panel === 'left') {
1800
+ this.leftPanel.classList.add('active');
1801
+ } else if (panel === 'right') {
1802
+ this.rightPanel.classList.add('active');
1803
+ }
1804
+
1805
+ this.overlay.style.display = 'block';
1806
+ setTimeout(() => {
1807
+ this.overlay.style.opacity = '1';
1808
+ }, 10);
1809
+ }
1810
+
1811
+ closeMenu() {
1812
+ this.isOpen = false;
1813
+ this.menuBtn.classList.remove('active');
1814
+ this.leftPanel.classList.remove('active');
1815
+ this.rightPanel.classList.remove('active');
1816
+
1817
+ this.overlay.style.opacity = '0';
1818
+ setTimeout(() => {
1819
+ this.overlay.style.display = 'none';
1820
+ }, 300);
1821
+ }
1822
+ }
1823
+
1824
+ // Accordion Functionality
1825
+ class Accordion {
1826
+ constructor() {
1827
+ this.accordions = document.querySelectorAll('.accordion-header');
1828
+ this.init();
1829
+ }
1830
+
1831
+ init() {
1832
+ this.accordions.forEach(accordion => {
1833
+ accordion.addEventListener('click', () => {
1834
+ const targetId = accordion.getAttribute('data-target');
1835
+ const content = document.getElementById(targetId);
1836
+ const icon = accordion.querySelector('i');
1837
+
1838
+ accordion.classList.toggle('active');
1839
+ content.classList.toggle('active');
1840
+
1841
+ if (accordion.classList.contains('active')) {
1842
+ icon.classList.remove('fa-chevron-down');
1843
+ icon.classList.add('fa-chevron-up');
1844
+ } else {
1845
+ icon.classList.remove('fa-chevron-up');
1846
+ icon.classList.add('fa-chevron-down');
1847
+ }
1848
+ });
1849
+ });
1850
+ }
1851
+ }
1852
+
1853
+ // RAG AI Tutor System
1854
+ class RAGAITutor {
1855
+ constructor() {
1856
+ this.knowledgeBase = [];
1857
+ this.vectorStore = null;
1858
+ this.isTraining = false;
1859
+ this.currentSession = null;
1860
+ this.init();
1861
+ }
1862
+
1863
+ init() {
1864
+ this.setupEventListeners();
1865
+ this.loadSampleKnowledge();
1866
+ }
1867
+
1868
+ loadSampleKnowledge() {
1869
+ this.addKnowledgeItem({
1870
+ id: Date.now(),
1871
+ title: "Physics Basics",
1872
+ content: "Newton's laws of motion: 1) An object at rest stays at rest unless acted upon by a force. 2) Force equals mass times acceleration. 3) For every action, there is an equal and opposite reaction.",
1873
+ type: "text",
1874
+ timestamp: new Date().toISOString()
1875
+ });
1876
+
1877
+ this.addKnowledgeItem({
1878
+ id: Date.now() + 1,
1879
+ title: "Calculus Fundamentals",
1880
+ content: "The derivative measures the rate of change of a function. The integral measures the accumulation of a quantity. The Fundamental Theorem of Calculus connects derivatives and integrals.",
1881
+ type: "text",
1882
+ timestamp: new Date().toISOString()
1883
+ });
1884
+ }
1885
+
1886
+ setupEventListeners() {
1887
+ document.getElementById('addKnowledgeBtn').addEventListener('click', () => {
1888
+ this.addKnowledgeFromText();
1889
+ });
1890
+
1891
+ document.getElementById('trainBtn').addEventListener('click', () => {
1892
+ this.trainRAGModel();
1893
+ });
1894
+ }
1895
+
1896
+ addKnowledgeFromText() {
1897
+ const textArea = document.getElementById('knowledgeText');
1898
+ const content = textArea.value.trim();
1899
+
1900
+ if (!content) {
1901
+ this.showRAGStatus('Please enter some knowledge content', 'warning');
1902
+ return;
1903
+ }
1904
+
1905
+ const title = content.substring(0, 50) + (content.length > 50 ? '...' : '');
1906
+
1907
+ const knowledgeItem = {
1908
+ id: Date.now(),
1909
+ title: title,
1910
+ content: content,
1911
+ type: "text",
1912
+ timestamp: new Date().toISOString()
1913
+ };
1914
+
1915
+ this.addKnowledgeItem(knowledgeItem);
1916
+ textArea.value = '';
1917
+ this.showRAGStatus('Knowledge added successfully!', 'success');
1918
+ }
1919
+
1920
+ addKnowledgeItem(item) {
1921
+ this.knowledgeBase.push(item);
1922
+ }
1923
+
1924
+ async trainRAGModel() {
1925
+ if (this.knowledgeBase.length === 0) {
1926
+ this.showRAGStatus('No knowledge to train on. Please add some content first.', 'warning');
1927
+ return;
1928
+ }
1929
+
1930
+ this.isTraining = true;
1931
+ this.showRAGStatus('Starting RAG training...', 'processing');
1932
+
1933
+ const progressBar = document.getElementById('trainingProgress');
1934
+ progressBar.style.width = '0%';
1935
+
1936
+ const steps = ['Preprocessing text', 'Creating embeddings', 'Building vector store', 'Optimizing retrieval'];
1937
+
1938
+ for (let i = 0; i < steps.length; i++) {
1939
+ this.showRAGStatus(steps[i], 'processing');
1940
+ progressBar.style.width = `${((i + 1) / steps.length) * 100}%`;
1941
+
1942
+ await this.sleep(1000);
1943
+ }
1944
+
1945
+ this.vectorStore = {
1946
+ size: this.knowledgeBase.length,
1947
+ trainedAt: new Date().toISOString(),
1948
+ model: 'all-MiniLM-L6-v2'
1949
+ };
1950
+
1951
+ this.isTraining = false;
1952
+ this.showRAGStatus(`RAG training complete! ${this.knowledgeBase.length} documents indexed.`, 'success');
1953
+
1954
+ setTimeout(() => {
1955
+ progressBar.style.width = '0%';
1956
+ }, 2000);
1957
+ }
1958
+
1959
+ async queryKnowledge(query) {
1960
+ if (!this.vectorStore) {
1961
+ return "I haven't been trained on any specific knowledge yet. Please add some content and train me first!";
1962
+ }
1963
+
1964
+ const relevantKnowledge = this.findRelevantKnowledge(query);
1965
+
1966
+ if (relevantKnowledge.length === 0) {
1967
+ return "I couldn't find specific information about that in my knowledge base. Could you rephrase or ask something else?";
1968
+ }
1969
+
1970
+ return this.generateResponse(query, relevantKnowledge);
1971
+ }
1972
+
1973
+ findRelevantKnowledge(query) {
1974
+ const queryLower = query.toLowerCase();
1975
+ const relevant = [];
1976
+
1977
+ for (const item of this.knowledgeBase) {
1978
+ if (item.content.toLowerCase().includes(queryLower) ||
1979
+ item.title.toLowerCase().includes(queryLower)) {
1980
+ relevant.push(item);
1981
+
1982
+ if (relevant.length >= 3) break;
1983
+ }
1984
+ }
1985
+
1986
+ return relevant;
1987
+ }
1988
+
1989
+ generateResponse(query, relevantKnowledge) {
1990
+ const context = relevantKnowledge.map(item => item.content).join('\n\n');
1991
+ return `Based on my knowledge base:\n\n${context}\n\nRegarding your question "${query}", here's what I can explain...`;
1992
+ }
1993
+
1994
+ showRAGStatus(message, type = 'info') {
1995
+ const statusEl = document.getElementById('ragStatus');
1996
+ const statusText = document.getElementById('ragStatusText');
1997
+
1998
+ statusText.textContent = message;
1999
+
2000
+ const colors = {
2001
+ 'info': '#5a6cff',
2002
+ 'success': '#00ff9d',
2003
+ 'warning': '#ffa500',
2004
+ 'error': '#ff5a5a',
2005
+ 'processing': '#a0b0ff'
2006
+ };
2007
+
2008
+ statusEl.style.borderColor = colors[type] + '30';
2009
+ statusEl.style.background = colors[type] + '10';
2010
+ statusEl.style.color = colors[type];
2011
+
2012
+ statusEl.style.display = 'flex';
2013
+
2014
+ if (type !== 'processing') {
2015
+ setTimeout(() => {
2016
+ statusEl.style.display = 'none';
2017
+ }, 5000);
2018
+ }
2019
+ }
2020
+
2021
+ sleep(ms) {
2022
+ return new Promise(resolve => setTimeout(resolve, ms));
2023
+ }
2024
+ }
2025
+
2026
+ // Voice Synthesis System
2027
+ class VoiceSynthesis {
2028
+ constructor() {
2029
+ this.synth = window.speechSynthesis;
2030
+ this.voices = [];
2031
+ this.currentVoice = null;
2032
+ this.isAutoSpeak = true;
2033
+ this.isSpeaking = false;
2034
+ this.currentUtterance = null;
2035
+
2036
+ this.settings = {
2037
+ rate: 1.0,
2038
+ pitch: 1.0,
2039
+ volume: 0.8
2040
+ };
2041
+
2042
+ this.testScript = "Hello there! This is a test to see how natural and fluid my voice sounds. I'm going to tell you a little story about learning and discovery.";
2043
+
2044
+ this.initVoices();
2045
+ this.setupEventListeners();
2046
+ }
2047
+
2048
+ initVoices() {
2049
+ const loadVoices = () => {
2050
+ this.voices = this.synth.getVoices();
2051
+ this.populateVoiceList();
2052
+ this.selectBestVoice();
2053
+ };
2054
+
2055
+ if (this.synth.onvoiceschanged !== undefined) {
2056
+ this.synth.onvoiceschanged = loadVoices;
2057
+ }
2058
+
2059
+ if (this.synth.getVoices().length > 0) {
2060
+ loadVoices();
2061
+ }
2062
+ }
2063
+
2064
+ selectBestVoice() {
2065
+ const englishVoices = this.voices.filter(voice =>
2066
+ voice.lang.startsWith('en-')
2067
+ );
2068
+
2069
+ if (englishVoices.length === 0) {
2070
+ console.warn('No English voices found');
2071
+ return;
2072
+ }
2073
+
2074
+ const preferredVoices = [
2075
+ 'Google UK English Male',
2076
+ 'Google US English',
2077
+ 'Microsoft David',
2078
+ 'Microsoft Zira',
2079
+ 'Alex',
2080
+ 'Samantha',
2081
+ 'Daniel'
2082
+ ];
2083
+
2084
+ for (const voiceName of preferredVoices) {
2085
+ const voice = englishVoices.find(v => v.name.includes(voiceName));
2086
+ if (voice) {
2087
+ this.currentVoice = voice;
2088
+ this.updateVoiceSelect(voice);
2089
+ break;
2090
+ }
2091
+ }
2092
+
2093
+ if (!this.currentVoice && englishVoices.length > 0) {
2094
+ this.currentVoice = englishVoices[0];
2095
+ this.updateVoiceSelect(englishVoices[0]);
2096
+ }
2097
+ }
2098
+
2099
+ populateVoiceList() {
2100
+ const voiceSelect = document.getElementById('voiceSelect');
2101
+ voiceSelect.innerHTML = '';
2102
+
2103
+ const englishVoices = this.voices.filter(voice =>
2104
+ voice.lang.startsWith('en-')
2105
+ );
2106
+
2107
+ if (englishVoices.length === 0) {
2108
+ const option = document.createElement('option');
2109
+ option.textContent = 'No English voices found';
2110
+ option.disabled = true;
2111
+ voiceSelect.appendChild(option);
2112
+ return;
2113
+ }
2114
+
2115
+ englishVoices.forEach(voice => {
2116
+ const option = document.createElement('option');
2117
+ option.value = voice.name;
2118
+ option.textContent = `${voice.name} (${voice.lang})`;
2119
+ option.dataset.lang = voice.lang;
2120
+ voiceSelect.appendChild(option);
2121
+ });
2122
+ }
2123
+
2124
+ updateVoiceSelect(voice) {
2125
+ const voiceSelect = document.getElementById('voiceSelect');
2126
+ const options = voiceSelect.querySelectorAll('option');
2127
+
2128
+ options.forEach(option => {
2129
+ if (option.value === voice.name) {
2130
+ option.selected = true;
2131
+ }
2132
+ });
2133
+ }
2134
+
2135
+ setupEventListeners() {
2136
+ document.getElementById('voiceSelect').addEventListener('change', (e) => {
2137
+ const selectedVoice = this.voices.find(v => v.name === e.target.value);
2138
+ if (selectedVoice) {
2139
+ this.currentVoice = selectedVoice;
2140
+ }
2141
+ });
2142
+
2143
+ document.getElementById('rateSlider').addEventListener('input', (e) => {
2144
+ const value = parseFloat(e.target.value);
2145
+ this.settings.rate = value;
2146
+ document.getElementById('rateValue').textContent = value.toFixed(1) + 'x';
2147
+ });
2148
+
2149
+ document.getElementById('pitchSlider').addEventListener('input', (e) => {
2150
+ const value = parseFloat(e.target.value);
2151
+ this.settings.pitch = value;
2152
+ document.getElementById('pitchValue').textContent = value.toFixed(1);
2153
+ });
2154
+
2155
+ document.getElementById('volumeSlider').addEventListener('input', (e) => {
2156
+ const value = parseFloat(e.target.value);
2157
+ this.settings.volume = value;
2158
+ document.getElementById('volumeValue').textContent = Math.round(value * 100) + '%';
2159
+ });
2160
+
2161
+ document.getElementById('autoSpeakBtn').addEventListener('click', (e) => {
2162
+ this.isAutoSpeak = !this.isAutoSpeak;
2163
+ const btn = e.target.closest('.voice-btn');
2164
+ btn.dataset.enabled = this.isAutoSpeak;
2165
+ btn.innerHTML = this.isAutoSpeak
2166
+ ? '<i class="fas fa-bullhorn"></i> Auto-Speak'
2167
+ : '<i class="fas fa-volume-mute"></i> Auto-Speak';
2168
+
2169
+ btn.classList.toggle('active', this.isAutoSpeak);
2170
+ });
2171
+
2172
+ document.getElementById('stopBtn').addEventListener('click', () => {
2173
+ this.stopSpeaking();
2174
+ });
2175
+
2176
+ document.getElementById('previewBtn').addEventListener('click', () => {
2177
+ this.previewCurrentVoice();
2178
+ });
2179
+ }
2180
+
2181
+ speak(text) {
2182
+ if (!this.isAutoSpeak || !text || this.isSpeaking) return;
2183
+
2184
+ this.stopSpeaking();
2185
+
2186
+ const utterance = new SpeechSynthesisUtterance(text);
2187
+
2188
+ if (this.currentVoice) {
2189
+ utterance.voice = this.currentVoice;
2190
+ }
2191
+
2192
+ utterance.rate = this.settings.rate;
2193
+ utterance.pitch = this.settings.pitch;
2194
+ utterance.volume = this.settings.volume;
2195
+
2196
+ utterance.text = this.addNaturalPauses(text);
2197
+
2198
+ utterance.onerror = (event) => {
2199
+ console.error('Speech synthesis error:', event);
2200
+ };
2201
+
2202
+ this.currentUtterance = utterance;
2203
+ this.synth.speak(utterance);
2204
+ }
2205
+
2206
+ addNaturalPauses(text) {
2207
+ return text
2208
+ .replace(/,/g, ',<break time="200ms"/>')
2209
+ .replace(/\./g, '.<break time="300ms"/>')
2210
+ .replace(/\?/g, '?<break time="400ms"/>')
2211
+ .replace(/\!/g, '!<break time="400ms"/>');
2212
+ }
2213
+
2214
+ stopSpeaking() {
2215
+ if (this.isSpeaking) {
2216
+ this.synth.cancel();
2217
+ this.isSpeaking = false;
2218
+ this.currentUtterance = null;
2219
+ }
2220
+ }
2221
+
2222
+ previewCurrentVoice() {
2223
+ this.speak(this.testScript);
2224
+ }
2225
+ }
2226
+
2227
+ // Chat Interface - FULLY INTEGRATED WITH LLM
2228
+ class ChatInterface {
2229
+ constructor(ragTutor, llmIntegration) {
2230
+ this.ragTutor = ragTutor;
2231
+ this.llmIntegration = llmIntegration;
2232
+ this.isListening = false;
2233
+ this.initChat();
2234
+ }
2235
+
2236
+ initChat() {
2237
+ this.chatInput = document.getElementById('chatInput');
2238
+ this.chatMessages = document.getElementById('chatMessages');
2239
+ this.voiceBtn = document.getElementById('voiceBtn');
2240
+ this.sendBtn = document.getElementById('sendBtn');
2241
+ this.chatStatusText = document.getElementById('chatStatusText');
2242
+
2243
+ this.setupEventListeners();
2244
+ this.autoResizeTextarea();
2245
+ }
2246
+
2247
+ setupEventListeners() {
2248
+ this.sendBtn.addEventListener('click', () => this.sendMessage());
2249
+
2250
+ this.chatInput.addEventListener('keydown', (e) => {
2251
+ if (e.key === 'Enter' && !e.shiftKey) {
2252
+ e.preventDefault();
2253
+ this.sendMessage();
2254
+ }
2255
+ });
2256
+
2257
+ this.voiceBtn.addEventListener('click', () => this.toggleVoiceInput());
2258
+ this.chatInput.addEventListener('input', () => this.autoResizeTextarea());
2259
+ }
2260
+
2261
+ autoResizeTextarea() {
2262
+ const textarea = this.chatInput;
2263
+ textarea.style.height = 'auto';
2264
+ const newHeight = Math.min(textarea.scrollHeight, 120);
2265
+ textarea.style.height = newHeight + 'px';
2266
+ }
2267
+
2268
+ toggleVoiceInput() {
2269
+ this.isListening = !this.isListening;
2270
+
2271
+ if (this.isListening) {
2272
+ this.voiceBtn.classList.add('listening');
2273
+ this.voiceBtn.innerHTML = '<i class="fas fa-stop"></i>';
2274
+ this.chatStatusText.textContent = 'Listening...';
2275
+
2276
+ if (window.visualization) {
2277
+ window.visualization.setPreset('listening');
2278
+ }
2279
+
2280
+ setTimeout(() => {
2281
+ this.simulateVoiceInput();
2282
+ }, 1500);
2283
+ } else {
2284
+ this.voiceBtn.classList.remove('listening');
2285
+ this.voiceBtn.innerHTML = '<i class="fas fa-microphone"></i>';
2286
+ this.chatStatusText.textContent = 'Online';
2287
+
2288
+ if (window.visualization) {
2289
+ window.visualization.setPreset('processing');
2290
+ }
2291
+ }
2292
+ }
2293
+
2294
+ simulateVoiceInput() {
2295
+ if (!this.isListening) return;
2296
+
2297
+ const simulatedQuestions = [
2298
+ "Can you explain quantum computing?",
2299
+ "How do neural networks learn?",
2300
+ "What's the difference between AI and machine learning?",
2301
+ "Help me understand blockchain technology",
2302
+ "Explain the concept of derivatives in calculus"
2303
+ ];
2304
+
2305
+ const randomQuestion = simulatedQuestions[Math.floor(Math.random() * simulatedQuestions.length)];
2306
+
2307
+ this.addMessage(randomQuestion, 'user');
2308
+ this.processAIResponse(randomQuestion);
2309
+
2310
+ setTimeout(() => {
2311
+ this.isListening = false;
2312
+ this.voiceBtn.classList.remove('listening');
2313
+ this.voiceBtn.innerHTML = '<i class="fas fa-microphone"></i>';
2314
+ this.chatStatusText.textContent = 'Online';
2315
+ }, 500);
2316
+ }
2317
+
2318
+ async sendMessage() {
2319
+ const message = this.chatInput.value.trim();
2320
+ if (message === '') return;
2321
+
2322
+ this.addMessage(message, 'user');
2323
+ this.chatInput.value = '';
2324
+ this.autoResizeTextarea();
2325
+ await this.processAIResponse(message);
2326
+ }
2327
+
2328
+ addMessage(content, sender) {
2329
+ const messageDiv = document.createElement('div');
2330
+ messageDiv.className = `message message-${sender}`;
2331
+
2332
+ const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
2333
+
2334
+ messageDiv.innerHTML = `
2335
+ <div class="message-content">${this.escapeHtml(content)}</div>
2336
+ <div class="message-time">${time}</div>
2337
+ `;
2338
+
2339
+ this.chatMessages.appendChild(messageDiv);
2340
+ this.chatMessages.scrollTop = this.chatMessages.scrollHeight;
2341
+
2342
+ if (window.visualization && sender === 'user') {
2343
+ window.visualization.setPreset('processing');
2344
+ }
2345
+ }
2346
+
2347
+ escapeHtml(text) {
2348
+ const div = document.createElement('div');
2349
+ div.textContent = text;
2350
+ return div.innerHTML;
2351
+ }
2352
+
2353
+ async processAIResponse(userMessage) {
2354
+ // Update 3D visualization
2355
+ if (window.visualization) {
2356
+ window.visualization.setPreset('processing');
2357
+ }
2358
+
2359
+ // Show typing indicator (simulated delay)
2360
+ const processingTime = 1000 + Math.random() * 2000;
2361
+
2362
+ setTimeout(async () => {
2363
+ let response;
2364
+
2365
+ // Try LLM API first if available and connected
2366
+ if (this.llmIntegration && this.llmIntegration.isConnected) {
2367
+ try {
2368
+ // Get context from RAG if available
2369
+ let context = '';
2370
+ if (this.ragTutor && this.ragTutor.vectorStore) {
2371
+ const relevantKnowledge = await this.ragTutor.queryKnowledge(userMessage);
2372
+ if (relevantKnowledge && !relevantKnowledge.includes("I haven't been trained") &&
2373
+ !relevantKnowledge.includes("I couldn't find")) {
2374
+ context = relevantKnowledge;
2375
+ }
2376
+ }
2377
+
2378
+ // Call LLM API
2379
+ response = await this.llmIntegration.generateResponse(userMessage, context);
2380
+
2381
+ if (!response) {
2382
+ throw new Error('No response from LLM API');
2383
+ }
2384
+
2385
+ console.log('LLM API response received');
2386
+
2387
+ } catch (error) {
2388
+ console.log('LLM API failed, using fallback:', error);
2389
+ response = await this.getFallbackResponse(userMessage);
2390
+ }
2391
+ } else {
2392
+ // LLM not connected, use fallback
2393
+ response = await this.getFallbackResponse(userMessage);
2394
+ }
2395
+
2396
+ // Add AI response to chat
2397
+ this.addMessage(response, 'ai');
2398
+
2399
+ // Speak the response
2400
+ if (window.voiceSynthesis) {
2401
+ setTimeout(() => {
2402
+ window.voiceSynthesis.speak(response);
2403
+ }, 300);
2404
+ }
2405
+
2406
+ // Update 3D visualization
2407
+ if (window.visualization) {
2408
+ window.visualization.setPreset('responding');
2409
+
2410
+ setTimeout(() => {
2411
+ window.visualization.setPreset('idle');
2412
+ }, 3000);
2413
+ }
2414
+ }, processingTime);
2415
+ }
2416
+
2417
+ async getFallbackResponse(userMessage) {
2418
+ // Try RAG first
2419
+ if (this.ragTutor && this.ragTutor.vectorStore) {
2420
+ const ragResponse = await this.ragTutor.queryKnowledge(userMessage);
2421
+ if (ragResponse && !ragResponse.includes("I haven't been trained") &&
2422
+ !ragResponse.includes("I couldn't find")) {
2423
+ return ragResponse;
2424
+ }
2425
+ }
2426
+
2427
+ // Fallback to local responses
2428
+ return this.generateAIResponse(userMessage);
2429
+ }
2430
+
2431
+ generateAIResponse(userMessage) {
2432
+ const responses = {
2433
+ quantum: "Quantum computing leverages quantum mechanics to process information. Unlike classical bits (0 or 1), quantum bits (qubits) can exist in superposition, enabling parallel computation.",
2434
+ neural: "Neural networks learn through backpropagation and gradient descent. They adjust weights based on prediction errors, minimizing loss functions.",
2435
+ blockchain: "Blockchain is a decentralized, distributed ledger technology. Each block contains cryptographic hashes linking to previous blocks, ensuring immutability.",
2436
+ calculus: "Derivatives measure instantaneous rate of change. The derivative of f(x) at point a is the slope of the tangent line.",
2437
+ default: "I understand you're asking about " + userMessage.substring(0, 30) + "... This is a complex topic that requires careful explanation."
2438
+ };
2439
+
2440
+ const lowerMessage = userMessage.toLowerCase();
2441
+
2442
+ if (lowerMessage.includes('quantum')) return responses.quantum;
2443
+ if (lowerMessage.includes('neural') || lowerMessage.includes('network')) return responses.neural;
2444
+ if (lowerMessage.includes('blockchain')) return responses.blockchain;
2445
+ if (lowerMessage.includes('calculus') || lowerMessage.includes('derivative')) return responses.calculus;
2446
+
2447
+ return responses.default;
2448
+ }
2449
+ }
2450
+
2451
+ // 3D Visualization
2452
+ class AITutorVisualization {
2453
+ constructor() {
2454
+ if (!checkThreeJS()) return;
2455
+
2456
+ this.scene = null;
2457
+ this.camera = null;
2458
+ this.renderer = null;
2459
+ this.particles = null;
2460
+ this.controls = null;
2461
+
2462
+ this.params = {
2463
+ intensity: 0.5,
2464
+ particleCount: 2000,
2465
+ energy: 0.3,
2466
+ mode: 'idle'
2467
+ };
2468
+
2469
+ this.audioData = new Array(32).fill(0);
2470
+ this.time = 0;
2471
+ this.animationFrameId = null;
2472
+
2473
+ try {
2474
+ this.init();
2475
+ this.createParticles();
2476
+ this.setupControls();
2477
+ this.animate();
2478
+ updateStatus('AI Interface Ready');
2479
+ setTimeout(hideLoading, 500);
2480
+ } catch (error) {
2481
+ console.error('Initialization error:', error);
2482
+ showError('Failed to initialize 3D scene: ' + error.message);
2483
+ }
2484
+ }
2485
+
2486
+ init() {
2487
+ updateStatus('Creating 3D scene...');
2488
+
2489
+ this.scene = new THREE.Scene();
2490
+ this.scene.background = new THREE.Color(0x0a0a0f);
2491
+
2492
+ this.camera = new THREE.PerspectiveCamera(
2493
+ 75,
2494
+ window.innerWidth / window.innerHeight,
2495
+ 0.1,
2496
+ 1000
2497
+ );
2498
+ this.camera.position.z = 5;
2499
+
2500
+ const canvas = document.getElementById('mainCanvas');
2501
+ this.renderer = new THREE.WebGLRenderer({
2502
+ canvas: canvas,
2503
+ antialias: true,
2504
+ alpha: true
2505
+ });
2506
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
2507
+ this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
2508
+
2509
+ this.scene.fog = new THREE.Fog(0x0a0a0f, 10, 25);
2510
+
2511
+ const ambientLight = new THREE.AmbientLight(0x222244, 0.5);
2512
+ this.scene.add(ambientLight);
2513
+
2514
+ const directionalLight = new THREE.DirectionalLight(0x5a6cff, 1);
2515
+ directionalLight.position.set(5, 3, 5);
2516
+ this.scene.add(directionalLight);
2517
+
2518
+ this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
2519
+ this.controls.enableDamping = true;
2520
+ this.controls.dampingFactor = 0.05;
2521
+ }
2522
+
2523
+ createParticles() {
2524
+ const geometry = new THREE.BufferGeometry();
2525
+ const positions = new Float32Array(this.params.particleCount * 3);
2526
+ const colors = new Float32Array(this.params.particleCount * 3);
2527
+
2528
+ for (let i = 0; i < this.params.particleCount; i++) {
2529
+ const i3 = i * 3;
2530
+ const phi = Math.acos(-1 + (2 * i) / this.params.particleCount);
2531
+ const theta = Math.sqrt(this.params.particleCount * Math.PI) * phi;
2532
+
2533
+ const x = Math.cos(theta) * Math.sin(phi);
2534
+ const y = Math.sin(theta) * Math.sin(phi);
2535
+ const z = Math.cos(phi);
2536
+
2537
+ positions[i3] = x;
2538
+ positions[i3 + 1] = y;
2539
+ positions[i3 + 2] = z;
2540
+
2541
+ colors[i3] = 0.5 + x * 0.5;
2542
+ colors[i3 + 1] = 0.3 + y * 0.7;
2543
+ colors[i3 + 2] = 0.8 + z * 0.2;
2544
+ }
2545
+
2546
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
2547
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
2548
+
2549
+ const material = new THREE.PointsMaterial({
2550
+ size: 0.03,
2551
+ vertexColors: true,
2552
+ transparent: true,
2553
+ opacity: 0.8,
2554
+ blending: THREE.AdditiveBlending
2555
+ });
2556
+
2557
+ this.particles = new THREE.Points(geometry, material);
2558
+ this.scene.add(this.particles);
2559
+
2560
+ const testGeometry = new THREE.SphereGeometry(0.5, 32, 32);
2561
+ const testMaterial = new THREE.MeshBasicMaterial({
2562
+ color: 0x5a6cff,
2563
+ wireframe: true,
2564
+ transparent: true,
2565
+ opacity: 0.1
2566
+ });
2567
+ const testSphere = new THREE.Mesh(testGeometry, testMaterial);
2568
+ this.scene.add(testSphere);
2569
+ }
2570
+
2571
+ setupControls() {
2572
+ const intensitySlider = document.getElementById('intensity');
2573
+ const particleCountSlider = document.getElementById('particleCount');
2574
+ const energySlider = document.getElementById('energy');
2575
+
2576
+ const intensityValue = document.getElementById('intensityValue');
2577
+ const particleCountValue = document.getElementById('particleCountValue');
2578
+ const energyValue = document.getElementById('energyValue');
2579
+
2580
+ intensitySlider.addEventListener('input', (e) => {
2581
+ this.params.intensity = e.target.value / 100;
2582
+ intensityValue.textContent = e.target.value;
2583
+ });
2584
+
2585
+ particleCountSlider.addEventListener('input', (e) => {
2586
+ this.params.particleCount = parseInt(e.target.value);
2587
+ particleCountValue.textContent = e.target.value;
2588
+ this.updateParticleCount();
2589
+ });
2590
+
2591
+ energySlider.addEventListener('input', (e) => {
2592
+ this.params.energy = e.target.value / 100;
2593
+ energyValue.textContent = e.target.value;
2594
+ });
2595
+
2596
+ document.querySelectorAll('.preset-btn').forEach(btn => {
2597
+ btn.addEventListener('click', (e) => {
2598
+ const preset = e.target.dataset.preset;
2599
+ this.setPreset(preset);
2600
+ });
2601
+ });
2602
+
2603
+ document.getElementById('retryBtn').addEventListener('click', () => {
2604
+ location.reload();
2605
+ });
2606
+ }
2607
+
2608
+ setPreset(preset) {
2609
+ this.params.mode = preset;
2610
+
2611
+ document.querySelectorAll('.preset-btn').forEach(btn => {
2612
+ btn.style.background = btn.dataset.preset === preset
2613
+ ? 'rgba(90, 108, 255, 0.4)'
2614
+ : 'rgba(90, 108, 255, 0.1)';
2615
+ });
2616
+ }
2617
+
2618
+ updateParticleCount() {
2619
+ if (this.particles) {
2620
+ this.scene.remove(this.particles);
2621
+ }
2622
+ this.createParticles();
2623
+ }
2624
+
2625
+ simulateAudioData() {
2626
+ const time = performance.now() * 0.001;
2627
+ const frequency = 0.5 + this.params.energy * 2;
2628
+
2629
+ for (let i = 0; i < this.audioData.length; i++) {
2630
+ const base = Math.sin(time * frequency + i * 0.3) * 0.5 + 0.5;
2631
+ const variation = Math.sin(time * 2 + i * 0.5) * 0.2;
2632
+ this.audioData[i] = base + variation * this.params.intensity;
2633
+ }
2634
+ }
2635
+
2636
+ updateParticles() {
2637
+ if (!this.particles) return;
2638
+
2639
+ const positions = this.particles.geometry.attributes.position.array;
2640
+ const originalPositions = this.particles.geometry.attributes.originalPosition;
2641
+
2642
+ if (!originalPositions) {
2643
+ const origPos = new Float32Array(positions.length);
2644
+ origPos.set(positions);
2645
+ this.particles.geometry.setAttribute('originalPosition', new THREE.BufferAttribute(origPos, 3));
2646
+ }
2647
+
2648
+ const origPositions = this.particles.geometry.attributes.originalPosition.array;
2649
+ const time = this.time;
2650
+
2651
+ for (let i = 0; i < this.params.particleCount; i++) {
2652
+ const i3 = i * 3;
2653
+ const x = origPositions[i3];
2654
+ const y = origPositions[i3 + 1];
2655
+ const z = origPositions[i3 + 2];
2656
+
2657
+ let radius = 2.0;
2658
+ let intensity = this.params.intensity;
2659
+
2660
+ switch(this.params.mode) {
2661
+ case 'listening':
2662
+ radius += Math.sin(time * 3 + i * 0.01) * 0.5 * intensity;
2663
+ break;
2664
+ case 'processing':
2665
+ radius += Math.sin(time * 5 + i * 0.02) * 0.6 * intensity;
2666
+ break;
2667
+ case 'responding':
2668
+ radius += (Math.sin(time * 2 + i * 0.005) + 1) * 0.4 * intensity;
2669
+ break;
2670
+ case 'exploring':
2671
+ radius += Math.sin(time * 1.5 + i * 0.015) * 0.7 * intensity;
2672
+ break;
2673
+ case 'teaching':
2674
+ radius += Math.sin(time * 4 + i * 0.008) * 0.55 * intensity;
2675
+ break;
2676
+ default:
2677
+ radius += Math.sin(time * 0.5 + i * 0.01) * 0.2 * intensity;
2678
+ }
2679
+
2680
+ radius += Math.sin(time * 8) * 0.2 * this.params.energy;
2681
+
2682
+ const audioIndex = Math.floor((i / this.params.particleCount) * this.audioData.length);
2683
+ radius += this.audioData[audioIndex] * 0.5 * intensity;
2684
+
2685
+ positions[i3] = x * radius;
2686
+ positions[i3 + 1] = y * radius;
2687
+ positions[i3 + 2] = z * radius;
2688
+ }
2689
+
2690
+ this.particles.geometry.attributes.position.needsUpdate = true;
2691
+
2692
+ this.particles.rotation.y = time * 0.1;
2693
+ this.particles.rotation.x = Math.sin(time * 0.05) * 0.1;
2694
+ }
2695
+
2696
+ animate() {
2697
+ this.animationFrameId = requestAnimationFrame(() => this.animate());
2698
+
2699
+ this.time += 0.016;
2700
+
2701
+ this.simulateAudioData();
2702
+ this.updateParticles();
2703
+
2704
+ this.controls.update();
2705
+ this.renderer.render(this.scene, this.camera);
2706
+ }
2707
+
2708
+ onWindowResize() {
2709
+ this.camera.aspect = window.innerWidth / window.innerHeight;
2710
+ this.camera.updateProjectionMatrix();
2711
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
2712
+ }
2713
+
2714
+ destroy() {
2715
+ if (this.animationFrameId) {
2716
+ cancelAnimationFrame(this.animationFrameId);
2717
+ }
2718
+ if (this.controls) {
2719
+ this.controls.dispose();
2720
+ }
2721
+ if (this.renderer) {
2722
+ this.renderer.dispose();
2723
+ }
2724
+ }
2725
+ }
2726
+
2727
+ // Initialize everything
2728
+ window.addEventListener('load', () => {
2729
+ updateStatus('Loading AI Interface...');
2730
+
2731
+ setTimeout(() => {
2732
+ // Initialize UI components first
2733
+ window.mobileMenu = new MobileMenu();
2734
+ window.accordion = new Accordion();
2735
+
2736
+ // Initialize 3D visualization
2737
+ window.visualization = new AITutorVisualization();
2738
+
2739
+ // Initialize RAG AI Tutor
2740
+ window.ragTutor = new RAGAITutor();
2741
+
2742
+ // Initialize FULLY FUNCTIONAL LLM Integration
2743
+ window.llmIntegration = new LLMIntegration();
2744
+
2745
+ // Initialize chat interface with BOTH RAG and LLM
2746
+ window.chatInterface = new ChatInterface(window.ragTutor, window.llmIntegration);
2747
+
2748
+ // Initialize voice synthesis
2749
+ if ('speechSynthesis' in window) {
2750
+ window.voiceSynthesis = new VoiceSynthesis();
2751
+ updateStatus('Voice synthesis initialized');
2752
+ }
2753
+
2754
+ if (window.visualization && window.visualization.scene) {
2755
+ window.addEventListener('resize', () => window.visualization.onWindowResize());
2756
+
2757
+ window.addEventListener('keydown', (e) => {
2758
+ if (e.code === 'Space') {
2759
+ window.visualization.setPreset(
2760
+ window.visualization.params.mode === 'idle' ? 'listening' : 'idle'
2761
+ );
2762
+ }
2763
+ if (e.code === 'Space' && e.ctrlKey && window.voiceSynthesis) {
2764
+ window.voiceSynthesis.stopSpeaking();
2765
+ }
2766
+ });
2767
+
2768
+ window.visualization.setPreset('idle');
2769
+ }
2770
+
2771
+ // Test LLM connection on startup if credentials exist
2772
+ if (window.llmIntegration.apiKey && window.llmIntegration.apiEndpoint) {
2773
+ setTimeout(() => {
2774
+ window.llmIntegration.testConnection().then(connected => {
2775
+ if (connected) {
2776
+ console.log('LLM API auto-connected on startup');
2777
+ }
2778
+ });
2779
+ }, 2000);
2780
+ }
2781
+
2782
+ hideLoading();
2783
+
2784
+ }, 100);
2785
+ });
2786
+
2787
+ window.addEventListener('beforeunload', () => {
2788
+ if (window.visualization) {
2789
+ window.visualization.destroy();
2790
+ }
2791
+ if (window.voiceSynthesis) {
2792
+ window.voiceSynthesis.stopSpeaking();
2793
+ }
2794
+ });
2795
+ </script>
2796
+ </body>
2797
+ </html>