ParthSadaria commited on
Commit
b833367
·
verified ·
1 Parent(s): 258641e

Update playground.html

Browse files
Files changed (1) hide show
  1. playground.html +465 -425
playground.html CHANGED
@@ -622,486 +622,526 @@
622
  Made with ❤️ by Parth Sadaria
623
  </div>
624
  <script>
625
- document.addEventListener('DOMContentLoaded', function() {
626
- const chatWrapper = document.querySelector('.chat-wrapper');
627
- const initialInput = document.querySelector('.initial-input');
628
- const chatContainer = document.getElementById('chatContainer');
629
- const initialChatInput = document.getElementById('initialChatInput');
630
- const initialSendIcon = document.getElementById('initialSendIcon');
631
- const chatMessages = document.getElementById('chatMessages');
632
- const chatInput = document.getElementById('chatInput');
633
- const sendButtonIcon = document.getElementById('sendButtonIcon');
634
- const sendLoader = document.getElementById('sendLoader');
635
- const clearChatButton = document.getElementById('clearChatButton');
636
- const modelSelectDisplay = document.getElementById('modelSelectDisplay');
637
- const modelOptions = document.getElementById('modelOptions');
638
- const modelLoader = document.getElementById('modelLoader');
639
- const themeToggle = document.getElementById('themeToggle');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
 
641
- let currentStreamingMessage = null;
642
- let isStreamingInProgress = false;
643
- let conversationHistory = [{ role: "system", content: "You are a helpful AI assistant. Assist the user effectively." }];
644
- let selectedModel = '';
645
- let modelsList = [];
646
- let isDarkMode = true;
647
-
648
- // Fetch models from API
649
- async function fetchModels() {
650
- modelLoader.style.display = 'block';
651
- try {
652
- const response = await fetch('https://parthsadaria-lokiai.hf.space/models', {
653
- method: 'GET',
654
- headers: {
655
- 'Authorization': 'playground'
656
- }
657
- });
658
-
659
- if (response.ok) {
660
- const data = await response.json();
661
- modelsList = data;
662
- populateModels(data);
663
- } else {
664
- console.error('Failed to fetch models:', response.status);
665
- // Fallback to some default models
666
- const fallbackModels = [
667
- { id: "gpt-4o", object: "model" },
668
- { id: "gpt-3.5-turbo", object: "model" },
669
- { id: "claude-3-5-sonnet", object: "model" },
670
- { id: "claude-3-haiku", object: "model" },
671
- { id: "llama-3.1-70b", object: "model" }
672
- ];
673
- populateModels(fallbackModels);
674
- }
675
- } catch (error) {
676
- console.error('Error fetching models:', error);
677
- // Use the same fallback
678
- const fallbackModels = [
679
- { id: "gpt-4o", object: "model" },
680
- { id: "gpt-3.5-turbo", object: "model" },
681
- { id: "claude-3-5-sonnet", object: "model" },
682
- { id: "claude-3-haiku", object: "model" },
683
- { id: "llama-3.1-70b", object: "model" }
684
- ];
685
- populateModels(fallbackModels);
686
- } finally {
687
- modelLoader.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
688
  }
689
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
690
 
691
- // Organize and display models
692
- function populateModels(models) {
693
- // Group models by provider
694
- const providers = {};
695
- models.forEach(model => {
696
- const modelName = model.id;
697
- let provider = 'Other';
698
-
699
- if (modelName.includes('gpt')) provider = 'OpenAI';
700
- else if (modelName.includes('claude')) provider = 'Anthropic';
701
- else if (modelName.includes('llama')) provider = 'Meta';
702
- else if (modelName.includes('gemini')) provider = 'Google';
703
- else if (modelName.includes('mistral')) provider = 'Mistral';
704
- else if (modelName.includes('yi')) provider = 'Yi';
705
-
706
- if (!providers[provider]) providers[provider] = [];
707
- providers[provider].push(model);
708
- });
709
-
710
- // Clear existing options
711
- modelOptions.innerHTML = '';
712
-
713
- // Add special option for web search
714
- const webSearchOption = document.createElement('div');
715
- webSearchOption.className = 'custom-option';
716
- webSearchOption.textContent = 'SearchGPT (Web-access)';
717
- webSearchOption.dataset.value = 'searchgpt';
718
- modelOptions.appendChild(webSearchOption);
719
-
720
- // Add a separator
721
- const separator = document.createElement('div');
722
- separator.className = 'model-group';
723
- separator.textContent = 'AI Models';
724
- modelOptions.appendChild(separator);
725
-
726
- // Create and append provider groups and their models
727
- Object.keys(providers).sort().forEach(provider => {
728
- const providerGroup = document.createElement('div');
729
- providerGroup.className = 'model-group';
730
- providerGroup.textContent = provider;
731
- modelOptions.appendChild(providerGroup);
732
-
733
- providers[provider].sort((a, b) => a.id.localeCompare(b.id)).forEach(model => {
734
- const option = document.createElement('div');
735
- option.className = 'custom-option';
736
- option.textContent = model.id;
737
- option.dataset.value = model.id;
738
- modelOptions.appendChild(option);
739
  });
740
- });
741
-
742
- // Set default model if none selected yet
743
- if (!selectedModel && models.length > 0) {
744
- const preferredModels = ['gpt-4o', 'gpt-4', 'claude-3-5-sonnet', 'gpt-3.5-turbo'];
745
-
746
- // Find the first preferred model that exists in our list
747
- for (const preferred of preferredModels) {
748
- const match = models.find(m => m.id.includes(preferred));
749
- if (match) {
750
- selectedModel = match.id;
751
- modelSelectDisplay.textContent = selectedModel;
752
- break;
753
- }
754
- }
755
-
756
- // If no preferred model found, use the first one in the list
757
- if (!selectedModel) {
758
- selectedModel = models[0].id;
759
- modelSelectDisplay.textContent = selectedModel;
760
- }
761
  }
762
-
763
- // Add event listeners to options
764
- document.querySelectorAll('.custom-option').forEach(option => {
765
- option.addEventListener('click', function() {
766
- selectedModel = this.dataset.value;
767
- modelSelectDisplay.textContent = this.textContent;
768
- modelOptions.style.display = 'none';
769
- });
770
- });
771
  }
772
 
773
- function scrollToBottom() {
774
- chatMessages.scrollTop = chatMessages.scrollHeight;
775
- }
 
 
 
776
 
777
- function appendMessage(content, type = 'bot', isStreaming = false) {
778
- if (type === 'bot' && isStreaming) {
779
- if (!currentStreamingMessage) {
780
- currentStreamingMessage = document.createElement('div');
781
- currentStreamingMessage.className = `message ${type}`;
782
- chatMessages.appendChild(currentStreamingMessage);
783
-
784
- // Add to conversation history only at the start of streaming
785
- if (!isStreamingInProgress) {
786
- conversationHistory.push({
787
- role: 'assistant',
788
- content: ''
789
- });
790
- isStreamingInProgress = true;
791
- }
792
 
793
- // Apply markdown-like formatting
794
- const formattedContent = content
795
- .replace(/```([\s\S]*?)```/g, (match, code) => `<pre><code>${code}</code></pre>`)
796
- .replace(/(\d+)\.\s*/g, '<br>$1. ')
797
- .replace(/\n/g, '<br>')
798
- .replace(/\*\*(.*?)\*\*/g, (match, p1) => '<strong>' + p1 + '</strong>');
799
 
800
- currentStreamingMessage.innerHTML += formattedContent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
801
 
802
- // Update the last message in conversation history
803
- if (conversationHistory.length > 0) {
804
- conversationHistory[conversationHistory.length - 1].content += content;
805
  }
 
806
 
807
- scrollToBottom();
808
- } else {
809
- if (currentStreamingMessage) {
810
- isStreamingInProgress = false;
811
- currentStreamingMessage = null;
812
- }
813
-
814
- const messageBox = document.createElement('div');
815
- messageBox.className = `message ${type}`;
816
-
817
- // Apply markdown-like formatting
818
- const formattedContent = content
819
- .replace(/```([\s\S]*?)```/g, (match, code) => `<pre><code>${code}</code></pre>`)
820
- .replace(/(\d+)\.\s*/g, '<br>$1. ')
821
- .replace(/\n/g, '<br>')
822
- .replace(/\*\*(.*?)\*\*/g, (match, p1) => '<strong>' + p1 + '</strong>');
823
-
824
- messageBox.innerHTML = formattedContent;
825
- chatMessages.appendChild(messageBox);
826
-
827
- // Add to conversation history only for new messages
828
- if (type === 'user') {
829
- conversationHistory.push({
830
- role: 'user',
831
- content: content
832
- });
833
- } else if (type === 'bot' && !isStreamingInProgress) {
834
- conversationHistory.push({
835
- role: 'assistant',
836
- content: content
837
- });
838
- }
839
 
 
 
 
 
 
 
 
 
 
840
  scrollToBottom();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
841
  }
842
- }
843
-
844
- function clearChat() {
845
- chatMessages.innerHTML = '';
846
- conversationHistory = [{ role: "system", content: "You are a helpful AI assistant. Assist the user effectively." }];
847
- initialInput.style.display = 'flex';
848
- chatContainer.style.display = 'none';
849
- }
850
-
851
- async function sendInitialMessage() {
852
- const userMessage = initialChatInput.value.trim();
853
- if (!userMessage || !selectedModel) return;
854
-
855
- initialInput.style.display = 'none';
856
- chatContainer.style.display = 'flex';
857
-
858
- appendMessage(userMessage, 'user');
859
- initialChatInput.value = '';
860
- scrollToBottom();
861
-
862
- // Show the loader, hide the send icon
863
- sendLoader.style.display = 'block';
864
- sendButtonIcon.querySelector('svg').style.display = 'none';
865
-
866
- try {
867
- await callApi(userMessage, selectedModel);
868
- } catch (error) {
869
- appendMessage("Oops! Something went wrong. Please try again.", 'bot');
870
- console.error("API Error:", error);
871
- } finally {
872
- // Hide loader, show send icon
873
- sendLoader.style.display = 'none';
874
- sendButtonIcon.querySelector('svg').style.display = 'block';
875
- }
876
- }
877
 
878
- async function sendMessage() {
879
- const userMessage = chatInput.value.trim();
880
- if (!userMessage || !selectedModel) return;
881
-
882
- appendMessage(userMessage, 'user');
883
- chatInput.value = '';
884
-
885
- // Show the loader, hide the send icon
886
- sendLoader.style.display = 'block';
887
- sendButtonIcon.querySelector('svg').style.display = 'none';
888
-
889
- try {
890
- await callApi(userMessage, selectedModel);
891
- } catch (error) {
892
- appendMessage("Oops! Something went wrong. Please try again.", 'bot');
893
- console.error("API Error:", error);
894
- } finally {
895
- // Hide loader, show send icon
896
- sendLoader.style.display = 'none';
897
- sendButtonIcon.querySelector('svg').style.display = 'block';
 
898
  }
899
- }
900
 
901
- async function callApi(userMessage, model) {
902
- if (model === "searchgpt") {
903
- const url = `https://parthsadaria-lokiai.hf.space/searchgpt?q=${encodeURIComponent(userMessage)}&stream=true&systemprompt=You are **SearchGPT**, an AI with internet access. Reply directly and accurately to user requests. Mention Sources at end`;
904
  try {
905
- const response = await fetch(url, {
906
- headers: {
907
- 'Authorization': 'playground'
908
- }
909
- });
 
910
 
911
- if (response.ok) {
912
- const reader = response.body.getReader();
913
- const decoder = new TextDecoder("utf-8");
914
- let done = false;
915
 
916
- // Add typing indicator
917
- const typingIndicator = document.createElement('div');
918
- typingIndicator.className = 'message bot typing-indicator';
919
- typingIndicator.innerHTML = '<span></span><span></span><span></span>';
920
- chatMessages.appendChild(typingIndicator);
921
- scrollToBottom();
922
 
923
- while (!done) {
924
- const { value, done: streamDone } = await reader.read();
925
- done = streamDone;
 
 
 
926
 
927
- if (value) {
928
- // Remove typing indicator when data starts coming
929
- if (typingIndicator) {
930
- typingIndicator.remove();
931
- }
932
-
933
- const chunk = decoder.decode(value);
934
- const cleanedChunk = chunk.trim().replace(/^data:\s*/, '');
935
- const jsonChunks = cleanedChunk.split("data:").filter(Boolean);
936
 
937
- for (const jsonString of jsonChunks) {
938
- try {
939
- const jsonData = JSON.parse(jsonString);
940
- const content = jsonData.choices?.[0]?.message?.content || "";
941
- if (content) {
942
- appendMessage(content, 'bot', true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
943
  }
944
- } catch (err) {
945
- console.warn("Parsing error:", err);
 
946
  }
947
  }
948
  }
 
 
949
  }
950
  } else {
951
- throw new Error(`API responded with status ${response.status}`);
952
- }
953
- } catch (error) {
954
- console.error("API call error:", error);
955
- throw error;
956
- }
957
- } else {
958
- const url = "https://parthsadaria-lokiai.hf.space/chat/completions";
959
- const payload = {
960
- model: model,
961
- messages: [...conversationHistory],
962
- stream: true
963
- };
964
-
965
- const headers = {
966
- "Content-Type": "application/json",
967
- "Authorization": "playground"
968
- };
969
-
970
- try {
971
- const response = await fetch(url, {
972
- method: "POST",
973
- headers: headers,
974
- body: JSON.stringify(payload)
975
- });
976
-
977
- if (response.ok) {
978
- const reader = response.body.getReader();
979
- const decoder = new TextDecoder("utf-8");
980
- let done = false;
981
 
982
- // Add typing indicator
983
- const typingIndicator = document.createElement('div');
984
- typingIndicator.className = 'message bot typing-indicator';
985
- typingIndicator.innerHTML = '<span></span><span></span><span></span>';
986
- chatMessages.appendChild(typingIndicator);
987
- scrollToBottom();
 
 
 
 
988
 
989
- while (!done) {
990
- const { value, done: streamDone } = await reader.read();
991
- done = streamDone;
 
 
 
992
 
993
- if (value) {
994
- // Remove typing indicator when data starts coming
995
- if (typingIndicator) {
996
- typingIndicator.remove();
997
- }
998
-
999
- const chunk = decoder.decode(value);
1000
- const cleanedChunk = chunk.trim().replace(/^data:\s*/, '');
1001
- const jsonChunks = cleanedChunk.split("data:").filter(Boolean);
1002
 
1003
- for (const jsonString of jsonChunks) {
1004
- try {
1005
- const jsonData = JSON.parse(jsonString);
1006
- const delta = jsonData.choices?.[0]?.delta || {};
1007
- const content = delta.content || "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1008
 
1009
  if (content) {
1010
  appendMessage(content, 'bot', true);
1011
  }
1012
- } catch (err) {
1013
- console.warn("Parsing error:", err);
1014
  }
 
 
1015
  }
1016
  }
 
 
1017
  }
1018
- } else {
1019
- throw new Error(`API responded with status ${response.status}`);
1020
  }
 
 
 
 
1021
  } catch (error) {
1022
  console.error("API call error:", error);
 
 
 
 
1023
  throw error;
1024
  }
1025
  }
1026
- }
1027
-
1028
- // Toggle theme
1029
- function toggleTheme() {
1030
- isDarkMode = !isDarkMode;
1031
- document.body.classList.toggle('light-mode');
1032
-
1033
- // Update icon based on current theme
1034
- if (isDarkMode) {
1035
- themeToggle.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1036
- <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
1037
- </svg>`;
1038
- } else {
1039
- themeToggle.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1040
- <circle cx="12" cy="12" r="5"></circle>
1041
- <line x1="12" y1="1" x2="12" y2="3"></line>
1042
- <line x1="12" y1="21" x2="12" y2="23"></line>
1043
- <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
1044
- <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
1045
- <line x1="1" y1="12" x2="3" y2="12"></line>
1046
- <line x1="21" y1="12" x2="23" y2="12"></line>
1047
- <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
1048
- <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
1049
- </svg>`;
1050
- }
1051
- }
1052
-
1053
- // Event Listeners
1054
- initialSendIcon.addEventListener('click', sendInitialMessage);
1055
- initialChatInput.addEventListener('keypress', (event) => {
1056
- if (event.key === 'Enter') sendInitialMessage();
1057
- });
1058
-
1059
- sendButtonIcon.addEventListener('click', sendMessage);
1060
- chatInput.addEventListener('keypress', (event) => {
1061
- if (event.key === 'Enter') sendMessage();
1062
- });
1063
-
1064
- // Clear Chat Button Event Listener
1065
- clearChatButton.addEventListener('click', clearChat);
1066
-
1067
- // Model selector dropdown event listeners
1068
- modelSelectDisplay.addEventListener('click', function() {
1069
- if (modelOptions.style.display === 'block') {
1070
- modelOptions.style.display = 'none';
1071
- } else {
1072
- modelOptions.style.display = 'block';
1073
- }
1074
- });
1075
 
1076
- // Close dropdown when clicking outside
1077
- document.addEventListener('click', function(event) {
1078
- if (!event.target.closest('.custom-select-wrapper') && modelOptions.style.display === 'block') {
1079
- modelOptions.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1080
  }
1081
- });
1082
-
1083
- // Theme toggle event listener
1084
- themeToggle.addEventListener('click', toggleTheme);
1085
-
1086
- // Initialize the app
1087
- fetchModels();
1088
 
1089
- // Add an animation to the chat wrapper
1090
- anime({
1091
- targets: '.chat-wrapper',
1092
- translateY: [50, 0],
1093
- opacity: [0, 1],
1094
- easing: 'easeOutExpo',
1095
- duration: 1000
1096
- });
1097
-
1098
- // Detect escape key to close model selector
1099
- document.addEventListener('keydown', function(event) {
1100
- if (event.key === 'Escape' && modelOptions.style.display === 'block') {
1101
- modelOptions.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1102
  }
1103
- });
1104
 
 
 
 
 
 
 
1105
  });
1106
  </script>
1107
  </body>
 
622
  Made with ❤️ by Parth Sadaria
623
  </div>
624
  <script>
625
+ document.addEventListener('DOMContentLoaded', function() {
626
+ const chatWrapper = document.querySelector('.chat-wrapper');
627
+ const initialInput = document.querySelector('.initial-input');
628
+ const chatContainer = document.getElementById('chatContainer');
629
+ const initialChatInput = document.getElementById('initialChatInput');
630
+ const initialSendIcon = document.getElementById('initialSendIcon');
631
+ const chatMessages = document.getElementById('chatMessages');
632
+ const chatInput = document.getElementById('chatInput');
633
+ const sendButtonIcon = document.getElementById('sendButtonIcon');
634
+ const sendLoader = document.getElementById('sendLoader');
635
+ const clearChatButton = document.getElementById('clearChatButton');
636
+ const modelSelectDisplay = document.getElementById('modelSelectDisplay');
637
+ const modelOptions = document.getElementById('modelOptions');
638
+ const modelLoader = document.getElementById('modelLoader');
639
+ const themeToggle = document.getElementById('themeToggle');
640
+
641
+ let currentStreamingMessage = null;
642
+ let isStreamingInProgress = false;
643
+ let conversationHistory = [{ role: "system", content: "You are a helpful AI assistant. Assist the user effectively." }];
644
+ let selectedModel = '';
645
+ let modelsList = [];
646
+ let isDarkMode = true;
647
+
648
+ // Fetch models from API
649
+ async function fetchModels() {
650
+ modelLoader.style.display = 'block';
651
+ try {
652
+ const response = await fetch('https://parthsadaria-lokiai.hf.space/models', {
653
+ method: 'GET',
654
+ headers: {
655
+ 'Authorization': 'playground'
656
+ }
657
+ });
658
 
659
+ if (response.ok) {
660
+ const data = await response.json();
661
+ modelsList = data;
662
+ populateModels(data);
663
+ } else {
664
+ console.error('Failed to fetch models:', response.status);
665
+ // Fallback to some default models
666
+ const fallbackModels = [
667
+ { id: "gpt-4o", object: "model" },
668
+ { id: "gpt-3.5-turbo", object: "model" },
669
+ { id: "claude-3-5-sonnet", object: "model" },
670
+ { id: "claude-3-haiku", object: "model" },
671
+ { id: "llama-3.1-70b", object: "model" }
672
+ ];
673
+ populateModels(fallbackModels);
674
+ }
675
+ } catch (error) {
676
+ console.error('Error fetching models:', error);
677
+ // Use the same fallback
678
+ const fallbackModels = [
679
+ { id: "gpt-4o", object: "model" },
680
+ { id: "gpt-3.5-turbo", object: "model" },
681
+ { id: "claude-3-5-sonnet", object: "model" },
682
+ { id: "claude-3-haiku", object: "model" },
683
+ { id: "llama-3.1-70b", object: "model" }
684
+ ];
685
+ populateModels(fallbackModels);
686
+ } finally {
687
+ modelLoader.style.display = 'none';
688
+ }
689
+ }
690
+
691
+ // Organize and display models
692
+ function populateModels(models) {
693
+ // Group models by provider
694
+ const providers = {};
695
+ models.forEach(model => {
696
+ const modelName = model.id;
697
+ let provider = 'Other';
698
+
699
+ if (modelName.includes('gpt')) provider = 'OpenAI';
700
+ else if (modelName.includes('claude')) provider = 'Anthropic';
701
+ else if (modelName.includes('llama')) provider = 'Meta';
702
+ else if (modelName.includes('gemini')) provider = 'Google';
703
+ else if (modelName.includes('mistral')) provider = 'Mistral';
704
+ else if (modelName.includes('yi')) provider = 'Yi';
705
+
706
+ if (!providers[provider]) providers[provider] = [];
707
+ providers[provider].push(model);
708
+ });
709
+
710
+ // Clear existing options
711
+ modelOptions.innerHTML = '';
712
+
713
+ // Add special option for web search
714
+ const webSearchOption = document.createElement('div');
715
+ webSearchOption.className = 'custom-option';
716
+ webSearchOption.textContent = 'SearchGPT (Web-access)';
717
+ webSearchOption.dataset.value = 'searchgpt';
718
+ modelOptions.appendChild(webSearchOption);
719
+
720
+ // Add a separator
721
+ const separator = document.createElement('div');
722
+ separator.className = 'model-group';
723
+ separator.textContent = 'AI Models';
724
+ modelOptions.appendChild(separator);
725
+
726
+ // Create and append provider groups and their models
727
+ Object.keys(providers).sort().forEach(provider => {
728
+ const providerGroup = document.createElement('div');
729
+ providerGroup.className = 'model-group';
730
+ providerGroup.textContent = provider;
731
+ modelOptions.appendChild(providerGroup);
732
+
733
+ providers[provider].sort((a, b) => a.id.localeCompare(b.id)).forEach(model => {
734
+ const option = document.createElement('div');
735
+ option.className = 'custom-option';
736
+ option.textContent = model.id;
737
+ option.dataset.value = model.id;
738
+ modelOptions.appendChild(option);
739
+ });
740
+ });
741
+
742
+ // Set default model if none selected yet
743
+ if (!selectedModel && models.length > 0) {
744
+ const preferredModels = ['gpt-4o', 'gpt-4', 'claude-3-5-sonnet', 'gpt-3.5-turbo'];
745
+
746
+ // Find the first preferred model that exists in our list
747
+ for (const preferred of preferredModels) {
748
+ const match = models.find(m => m.id.includes(preferred));
749
+ if (match) {
750
+ selectedModel = match.id;
751
+ modelSelectDisplay.textContent = selectedModel;
752
+ break;
753
  }
754
  }
755
+
756
+ // If no preferred model found, use the first one in the list
757
+ if (!selectedModel) {
758
+ selectedModel = models[0].id;
759
+ modelSelectDisplay.textContent = selectedModel;
760
+ }
761
+ }
762
+
763
+ // Add event listeners to options
764
+ document.querySelectorAll('.custom-option').forEach(option => {
765
+ option.addEventListener('click', function() {
766
+ selectedModel = this.dataset.value;
767
+ modelSelectDisplay.textContent = this.textContent;
768
+ modelOptions.style.display = 'none';
769
+ });
770
+ });
771
+ }
772
 
773
+ function scrollToBottom() {
774
+ chatMessages.scrollTop = chatMessages.scrollHeight;
775
+ }
776
+
777
+ function appendMessage(content, type = 'bot', isStreaming = false) {
778
+ if (type === 'bot' && isStreaming) {
779
+ if (!currentStreamingMessage) {
780
+ currentStreamingMessage = document.createElement('div');
781
+ currentStreamingMessage.className = `message ${type}`;
782
+ chatMessages.appendChild(currentStreamingMessage);
783
+
784
+ // Add to conversation history only at the start of streaming
785
+ if (!isStreamingInProgress) {
786
+ conversationHistory.push({
787
+ role: 'assistant',
788
+ content: ''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
789
  });
790
+ isStreamingInProgress = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
791
  }
 
 
 
 
 
 
 
 
 
792
  }
793
 
794
+ // Apply markdown-like formatting
795
+ const formattedContent = content
796
+ .replace(/```([\s\S]*?)```/g, (match, code) => `<pre><code>${code}</code></pre>`)
797
+ .replace(/(\d+)\.\s*/g, '<br>$1. ')
798
+ .replace(/\n/g, '<br>')
799
+ .replace(/\*\*(.*?)\*\*/g, (match, p1) => '<strong>' + p1 + '</strong>');
800
 
801
+ currentStreamingMessage.innerHTML += formattedContent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
802
 
803
+ // Update the last message in conversation history
804
+ if (conversationHistory.length > 0) {
805
+ conversationHistory[conversationHistory.length - 1].content += content;
806
+ }
 
 
807
 
808
+ scrollToBottom();
809
+ } else {
810
+ if (type === 'bot' && currentStreamingMessage) {
811
+ isStreamingInProgress = false;
812
+ currentStreamingMessage = null;
813
+ } else {
814
+ const messageBox = document.createElement('div');
815
+ messageBox.className = `message ${type}`;
816
+
817
+ // Apply markdown-like formatting
818
+ const formattedContent = content
819
+ .replace(/```([\s\S]*?)```/g, (match, code) => `<pre><code>${code}</code></pre>`)
820
+ .replace(/(\d+)\.\s*/g, '<br>$1. ')
821
+ .replace(/\n/g, '<br>')
822
+ .replace(/\*\*(.*?)\*\*/g, (match, p1) => '<strong>' + p1 + '</strong>');
823
+
824
+ messageBox.innerHTML = formattedContent;
825
+ chatMessages.appendChild(messageBox);
826
+
827
+ // Add to conversation history only for new messages
828
+ if (type === 'user') {
829
+ conversationHistory.push({
830
+ role: 'user',
831
+ content: content
832
+ });
833
+ } else if (type === 'bot' && !isStreamingInProgress) {
834
+ conversationHistory.push({
835
+ role: 'assistant',
836
+ content: content
837
+ });
838
+ }
839
 
840
+ scrollToBottom();
841
+ }
 
842
  }
843
+ }
844
 
845
+ function clearChat() {
846
+ chatMessages.innerHTML = '';
847
+ conversationHistory = [{ role: "system", content: "You are a helpful AI assistant. Assist the user effectively." }];
848
+ initialInput.style.display = 'flex';
849
+ chatContainer.style.display = 'none';
850
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
851
 
852
+ async function sendInitialMessage() {
853
+ const userMessage = initialChatInput.value.trim();
854
+ if (!userMessage || !selectedModel) return;
855
+
856
+ initialInput.style.display = 'none';
857
+ chatContainer.style.display = 'flex';
858
+
859
+ appendMessage(userMessage, 'user');
860
+ initialChatInput.value = '';
861
  scrollToBottom();
862
+
863
+ // Show the loader, hide the send icon
864
+ sendLoader.style.display = 'block';
865
+ sendButtonIcon.querySelector('svg').style.display = 'none';
866
+
867
+ try {
868
+ await callApi(userMessage, selectedModel);
869
+ } catch (error) {
870
+ appendMessage("Oops! Something went wrong. Please try again.", 'bot');
871
+ console.error("API Error:", error);
872
+ } finally {
873
+ // Hide loader, show send icon
874
+ sendLoader.style.display = 'none';
875
+ sendButtonIcon.querySelector('svg').style.display = 'block';
876
+ }
877
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
878
 
879
+ async function sendMessage() {
880
+ const userMessage = chatInput.value.trim();
881
+ if (!userMessage || !selectedModel) return;
882
+
883
+ appendMessage(userMessage, 'user');
884
+ chatInput.value = '';
885
+
886
+ // Show the loader, hide the send icon
887
+ sendLoader.style.display = 'block';
888
+ sendButtonIcon.querySelector('svg').style.display = 'none';
889
+
890
+ try {
891
+ await callApi(userMessage, selectedModel);
892
+ } catch (error) {
893
+ appendMessage("Oops! Something went wrong. Please try again.", 'bot');
894
+ console.error("API Error:", error);
895
+ } finally {
896
+ // Hide loader, show send icon
897
+ sendLoader.style.display = 'none';
898
+ sendButtonIcon.querySelector('svg').style.display = 'block';
899
+ }
900
  }
 
901
 
902
+ async function callApi(userMessage, model) {
903
+ let typingIndicator = null;
904
+
905
  try {
906
+ // Add typing indicator before any API call
907
+ typingIndicator = document.createElement('div');
908
+ typingIndicator.className = 'message bot typing-indicator';
909
+ typingIndicator.innerHTML = '<span></span><span></span><span></span>';
910
+ chatMessages.appendChild(typingIndicator);
911
+ scrollToBottom();
912
 
913
+ if (model === "searchgpt") {
914
+ const url = `https://parthsadaria-lokiai.hf.space/searchgpt?q=${encodeURIComponent(userMessage)}&stream=true&systemprompt=You are **SearchGPT**, an AI with internet access. Reply directly and accurately to user requests. Mention Sources at end`;
 
 
915
 
916
+ const response = await fetch(url, {
917
+ headers: {
918
+ 'Authorization': 'playground'
919
+ }
920
+ });
 
921
 
922
+ if (response.ok) {
923
+ // Remove typing indicator
924
+ if (typingIndicator) {
925
+ typingIndicator.remove();
926
+ typingIndicator = null;
927
+ }
928
 
929
+ const reader = response.body.getReader();
930
+ const decoder = new TextDecoder("utf-8");
931
+ let done = false;
932
+
933
+ while (!done) {
934
+ const { value, done: streamDone } = await reader.read();
935
+ done = streamDone;
 
 
936
 
937
+ if (value) {
938
+ const chunk = decoder.decode(value);
939
+ const cleanedChunk = chunk.trim();
940
+
941
+ // Handle different API response formats
942
+ if (cleanedChunk.startsWith('data:')) {
943
+ const jsonChunks = cleanedChunk.split("data:").filter(Boolean);
944
+
945
+ for (const jsonString of jsonChunks) {
946
+ try {
947
+ const cleanJson = jsonString.trim();
948
+ if (cleanJson === '[DONE]') continue;
949
+
950
+ const jsonData = JSON.parse(cleanJson);
951
+ const content = jsonData.choices?.[0]?.message?.content ||
952
+ jsonData.choices?.[0]?.delta?.content || "";
953
+
954
+ if (content) {
955
+ appendMessage(content, 'bot', true);
956
+ }
957
+ } catch (err) {
958
+ console.warn("Parsing error:", err, "for chunk:", jsonString);
959
+ }
960
  }
961
+ } else {
962
+ // Handle plain text response
963
+ appendMessage(cleanedChunk, 'bot', true);
964
  }
965
  }
966
  }
967
+ } else {
968
+ throw new Error(`API responded with status ${response.status}`);
969
  }
970
  } else {
971
+ const url = "https://parthsadaria-lokiai.hf.space/chat/completions";
972
+ const payload = {
973
+ model: model,
974
+ messages: [...conversationHistory],
975
+ stream: true
976
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
977
 
978
+ const headers = {
979
+ "Content-Type": "application/json",
980
+ "Authorization": "playground"
981
+ };
982
+
983
+ const response = await fetch(url, {
984
+ method: "POST",
985
+ headers: headers,
986
+ body: JSON.stringify(payload)
987
+ });
988
 
989
+ if (response.ok) {
990
+ // Remove typing indicator
991
+ if (typingIndicator) {
992
+ typingIndicator.remove();
993
+ typingIndicator = null;
994
+ }
995
 
996
+ const reader = response.body.getReader();
997
+ const decoder = new TextDecoder("utf-8");
998
+ let done = false;
999
+ let buffer = "";
1000
+
1001
+ while (!done) {
1002
+ const { value, done: streamDone } = await reader.read();
1003
+ done = streamDone;
 
1004
 
1005
+ if (value) {
1006
+ const chunk = decoder.decode(value);
1007
+ buffer += chunk;
1008
+
1009
+ // Process complete data chunks
1010
+ const lines = buffer.split('\n');
1011
+ buffer = lines.pop() || ""; // Keep the last incomplete line in buffer
1012
+
1013
+ for (const line of lines) {
1014
+ if (line.trim() === '') continue;
1015
+
1016
+ const cleanedLine = line.replace(/^data:\s*/, '').trim();
1017
+ if (cleanedLine === '[DONE]') continue;
1018
+
1019
+ try {
1020
+ const jsonData = JSON.parse(cleanedLine);
1021
+ const content = jsonData.choices?.[0]?.delta?.content || "";
1022
+
1023
+ if (content) {
1024
+ appendMessage(content, 'bot', true);
1025
+ }
1026
+ } catch (err) {
1027
+ console.warn("Parsing error:", err, "for line:", cleanedLine);
1028
+ }
1029
+ }
1030
+ }
1031
+ }
1032
+
1033
+ // Handle any remaining data in buffer
1034
+ if (buffer.trim() !== '') {
1035
+ try {
1036
+ const cleanedBuffer = buffer.replace(/^data:\s*/, '').trim();
1037
+ if (cleanedBuffer !== '[DONE]') {
1038
+ const jsonData = JSON.parse(cleanedBuffer);
1039
+ const content = jsonData.choices?.[0]?.delta?.content || "";
1040
 
1041
  if (content) {
1042
  appendMessage(content, 'bot', true);
1043
  }
 
 
1044
  }
1045
+ } catch (err) {
1046
+ console.warn("Parsing error for remaining buffer:", err);
1047
  }
1048
  }
1049
+ } else {
1050
+ throw new Error(`API responded with status ${response.status}`);
1051
  }
 
 
1052
  }
1053
+
1054
+ // End streaming after successful completion
1055
+ appendMessage("", 'bot', false);
1056
+
1057
  } catch (error) {
1058
  console.error("API call error:", error);
1059
+ // Remove typing indicator if still present
1060
+ if (typingIndicator) {
1061
+ typingIndicator.remove();
1062
+ }
1063
  throw error;
1064
  }
1065
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1066
 
1067
+ // Toggle theme
1068
+ function toggleTheme() {
1069
+ isDarkMode = !isDarkMode;
1070
+ document.body.classList.toggle('light-mode');
1071
+
1072
+ // Update icon based on current theme
1073
+ if (isDarkMode) {
1074
+ themeToggle.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1075
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
1076
+ </svg>`;
1077
+ } else {
1078
+ themeToggle.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1079
+ <circle cx="12" cy="12" r="5"></circle>
1080
+ <line x1="12" y1="1" x2="12" y2="3"></line>
1081
+ <line x1="12" y1="21" x2="12" y2="23"></line>
1082
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
1083
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
1084
+ <line x1="1" y1="12" x2="3" y2="12"></line>
1085
+ <line x1="21" y1="12" x2="23" y2="12"></line>
1086
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
1087
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
1088
+ </svg>`;
1089
+ }
1090
  }
 
 
 
 
 
 
 
1091
 
1092
+ // Event Listeners
1093
+ initialSendIcon.addEventListener('click', sendInitialMessage);
1094
+ initialChatInput.addEventListener('keypress', (event) => {
1095
+ if (event.key === 'Enter') sendInitialMessage();
1096
+ });
1097
+
1098
+ sendButtonIcon.addEventListener('click', sendMessage);
1099
+ chatInput.addEventListener('keypress', (event) => {
1100
+ if (event.key === 'Enter') sendMessage();
1101
+ });
1102
+
1103
+ // Clear Chat Button Event Listener
1104
+ clearChatButton.addEventListener('click', clearChat);
1105
+
1106
+ // Model selector dropdown event listeners
1107
+ modelSelectDisplay.addEventListener('click', function() {
1108
+ if (modelOptions.style.display === 'block') {
1109
+ modelOptions.style.display = 'none';
1110
+ } else {
1111
+ modelOptions.style.display = 'block';
1112
+ }
1113
+ });
1114
+
1115
+ // Close dropdown when clicking outside
1116
+ document.addEventListener('click', function(event) {
1117
+ if (!event.target.closest('.custom-select-wrapper') && modelOptions.style.display === 'block') {
1118
+ modelOptions.style.display = 'none';
1119
+ }
1120
+ });
1121
+
1122
+ // Theme toggle event listener
1123
+ themeToggle.addEventListener('click', toggleTheme);
1124
+
1125
+ // Initialize the app
1126
+ fetchModels();
1127
+
1128
+ // Add an animation to the chat wrapper
1129
+ if (typeof anime !== 'undefined') {
1130
+ anime({
1131
+ targets: '.chat-wrapper',
1132
+ translateY: [50, 0],
1133
+ opacity: [0, 1],
1134
+ easing: 'easeOutExpo',
1135
+ duration: 1000
1136
+ });
1137
  }
 
1138
 
1139
+ // Detect escape key to close model selector
1140
+ document.addEventListener('keydown', function(event) {
1141
+ if (event.key === 'Escape' && modelOptions.style.display === 'block') {
1142
+ modelOptions.style.display = 'none';
1143
+ }
1144
+ });
1145
  });
1146
  </script>
1147
  </body>