khoj 1.16.1.dev15__py3-none-any.whl → 1.17.1.dev220__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. khoj/configure.py +6 -6
  2. khoj/database/adapters/__init__.py +56 -12
  3. khoj/database/migrations/0053_agent_style_color_agent_style_icon.py +61 -0
  4. khoj/database/migrations/0054_alter_agent_style_color.py +38 -0
  5. khoj/database/models/__init__.py +35 -0
  6. khoj/interface/web/assets/icons/favicon-128x128.png +0 -0
  7. khoj/interface/web/assets/icons/favicon-256x256.png +0 -0
  8. khoj/interface/web/assets/icons/khoj-logo-sideways-200.png +0 -0
  9. khoj/interface/web/assets/icons/khoj-logo-sideways-500.png +0 -0
  10. khoj/interface/web/assets/icons/khoj-logo-sideways.svg +31 -5384
  11. khoj/interface/web/assets/icons/khoj.svg +26 -0
  12. khoj/interface/web/chat.html +191 -301
  13. khoj/interface/web/content_source_computer_input.html +3 -3
  14. khoj/interface/web/content_source_github_input.html +1 -1
  15. khoj/interface/web/content_source_notion_input.html +1 -1
  16. khoj/interface/web/public_conversation.html +1 -1
  17. khoj/interface/web/search.html +2 -2
  18. khoj/interface/web/{config.html → settings.html} +30 -30
  19. khoj/interface/web/utils.html +1 -1
  20. khoj/processor/content/docx/docx_to_entries.py +4 -9
  21. khoj/processor/content/github/github_to_entries.py +1 -3
  22. khoj/processor/content/images/image_to_entries.py +4 -9
  23. khoj/processor/content/markdown/markdown_to_entries.py +4 -9
  24. khoj/processor/content/notion/notion_to_entries.py +1 -3
  25. khoj/processor/content/org_mode/org_to_entries.py +4 -9
  26. khoj/processor/content/pdf/pdf_to_entries.py +4 -9
  27. khoj/processor/content/plaintext/plaintext_to_entries.py +4 -9
  28. khoj/processor/content/text_to_entries.py +1 -3
  29. khoj/processor/conversation/anthropic/anthropic_chat.py +10 -4
  30. khoj/processor/conversation/offline/chat_model.py +19 -7
  31. khoj/processor/conversation/offline/utils.py +2 -0
  32. khoj/processor/conversation/openai/gpt.py +9 -3
  33. khoj/processor/conversation/prompts.py +56 -25
  34. khoj/processor/conversation/utils.py +5 -6
  35. khoj/processor/tools/online_search.py +13 -7
  36. khoj/routers/api.py +60 -10
  37. khoj/routers/api_agents.py +3 -1
  38. khoj/routers/api_chat.py +335 -562
  39. khoj/routers/api_content.py +538 -0
  40. khoj/routers/api_model.py +156 -0
  41. khoj/routers/helpers.py +339 -26
  42. khoj/routers/notion.py +2 -8
  43. khoj/routers/web_client.py +43 -256
  44. khoj/search_type/text_search.py +5 -4
  45. khoj/utils/fs_syncer.py +4 -2
  46. khoj/utils/rawconfig.py +6 -1
  47. {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev220.dist-info}/METADATA +3 -3
  48. {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev220.dist-info}/RECORD +51 -48
  49. khoj/routers/api_config.py +0 -434
  50. khoj/routers/indexer.py +0 -349
  51. {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev220.dist-info}/WHEEL +0 -0
  52. {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev220.dist-info}/entry_points.txt +0 -0
  53. {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev220.dist-info}/licenses/LICENSE +0 -0
@@ -44,7 +44,7 @@ Hi, I am Khoj, your open, personal AI 👋🏽. I can:
44
44
  - 📚 Understand files you drag & drop here
45
45
  - 👩🏾‍🚀 Be tuned to your conversation needs via [agents](./agents)
46
46
 
47
- Get the Khoj [Desktop](https://khoj.dev/downloads), [Obsidian](https://docs.khoj.dev/clients/obsidian#setup), [Emacs](https://docs.khoj.dev/clients/emacs#setup) apps to search, chat with your 🖥️ computer docs. You can manage all the files you've shared with me at any time by going to [your settings](/config/content-source/computer/).
47
+ Get the Khoj [Desktop](https://khoj.dev/downloads), [Obsidian](https://docs.khoj.dev/clients/obsidian#setup), [Emacs](https://docs.khoj.dev/clients/emacs#setup) apps to search, chat with your 🖥️ computer docs. You can manage all the files you've shared with me at any time by going to [your settings](/settings/content/computer/).
48
48
 
49
49
  To get started, just start typing below. You can also type / to see a list of commands.
50
50
  `.trim()
@@ -74,14 +74,13 @@ To get started, just start typing below. You can also type / to see a list of co
74
74
  }, 1000);
75
75
  });
76
76
  }
77
- var websocket = null;
77
+
78
78
  let region = null;
79
79
  let city = null;
80
80
  let countryName = null;
81
81
  let timezone = null;
82
82
  let waitingForLocation = true;
83
-
84
- let websocketState = {
83
+ let chatMessageState = {
85
84
  newResponseTextEl: null,
86
85
  newResponseEl: null,
87
86
  loadingEllipsis: null,
@@ -105,7 +104,7 @@ To get started, just start typing below. You can also type / to see a list of co
105
104
  .finally(() => {
106
105
  console.debug("Region:", region, "City:", city, "Country:", countryName, "Timezone:", timezone);
107
106
  waitingForLocation = false;
108
- setupWebSocket();
107
+ initMessageState();
109
108
  });
110
109
 
111
110
  function formatDate(date) {
@@ -599,13 +598,8 @@ To get started, just start typing below. You can also type / to see a list of co
599
598
  }
600
599
 
601
600
  async function chat(isVoice=false) {
602
- if (websocket) {
603
- sendMessageViaWebSocket(isVoice);
604
- return;
605
- }
606
-
607
- let query = document.getElementById("chat-input").value.trim();
608
- let resultsCount = localStorage.getItem("khojResultsCount") || 5;
601
+ // Extract chat message from chat input form
602
+ var query = document.getElementById("chat-input").value.trim();
609
603
  console.log(`Query: ${query}`);
610
604
 
611
605
  // Short circuit on empty query
@@ -624,31 +618,30 @@ To get started, just start typing below. You can also type / to see a list of co
624
618
  document.getElementById("chat-input").value = "";
625
619
  autoResize();
626
620
  document.getElementById("chat-input").setAttribute("disabled", "disabled");
627
- let chat_body = document.getElementById("chat-body");
628
-
629
- let conversationID = chat_body.dataset.conversationId;
630
621
 
622
+ let chatBody = document.getElementById("chat-body");
623
+ let conversationID = chatBody.dataset.conversationId;
631
624
  if (!conversationID) {
632
- let response = await fetch('/api/chat/sessions', { method: "POST" });
625
+ let response = await fetch(`${hostURL}/api/chat/sessions`, { method: "POST" });
633
626
  let data = await response.json();
634
627
  conversationID = data.conversation_id;
635
- chat_body.dataset.conversationId = conversationID;
636
- refreshChatSessionsPanel();
628
+ chatBody.dataset.conversationId = conversationID;
629
+ await refreshChatSessionsPanel();
637
630
  }
638
631
 
639
- let new_response = document.createElement("div");
640
- new_response.classList.add("chat-message", "khoj");
641
- new_response.attributes["data-meta"] = "🏮 Khoj at " + formatDate(new Date());
642
- chat_body.appendChild(new_response);
632
+ let newResponseEl = document.createElement("div");
633
+ newResponseEl.classList.add("chat-message", "khoj");
634
+ newResponseEl.attributes["data-meta"] = "🏮 Khoj at " + formatDate(new Date());
635
+ chatBody.appendChild(newResponseEl);
643
636
 
644
- let newResponseText = document.createElement("div");
645
- newResponseText.classList.add("chat-message-text", "khoj");
646
- new_response.appendChild(newResponseText);
637
+ let newResponseTextEl = document.createElement("div");
638
+ newResponseTextEl.classList.add("chat-message-text", "khoj");
639
+ newResponseEl.appendChild(newResponseTextEl);
647
640
 
648
641
  // Temporary status message to indicate that Khoj is thinking
649
642
  let loadingEllipsis = createLoadingEllipse();
650
643
 
651
- newResponseText.appendChild(loadingEllipsis);
644
+ newResponseTextEl.appendChild(loadingEllipsis);
652
645
  document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
653
646
 
654
647
  let chatTooltip = document.getElementById("chat-tooltip");
@@ -657,65 +650,38 @@ To get started, just start typing below. You can also type / to see a list of co
657
650
  let chatInput = document.getElementById("chat-input");
658
651
  chatInput.classList.remove("option-enabled");
659
652
 
660
- // Generate backend API URL to execute query
661
- let url = `/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true&conversation_id=${conversationID}&region=${region}&city=${city}&country=${countryName}&timezone=${timezone}`;
662
-
663
- // Call specified Khoj API
664
- let response = await fetch(url);
665
- let rawResponse = "";
666
- let references = null;
667
- const contentType = response.headers.get("content-type");
668
-
669
- if (contentType === "application/json") {
670
- // Handle JSON response
671
- try {
672
- const responseAsJson = await response.json();
673
- if (responseAsJson.image || responseAsJson.detail) {
674
- ({rawResponse, references } = handleImageResponse(responseAsJson, rawResponse));
675
- } else {
676
- rawResponse = responseAsJson.response;
677
- }
678
- } catch (error) {
679
- // If the chunk is not a JSON object, just display it as is
680
- rawResponse += chunk;
681
- } finally {
682
- addMessageToChatBody(rawResponse, newResponseText, references);
683
- }
684
- } else {
685
- // Handle streamed response of type text/event-stream or text/plain
686
- const reader = response.body.getReader();
687
- const decoder = new TextDecoder();
688
- let references = {};
689
-
690
- readStream();
691
-
692
- function readStream() {
693
- reader.read().then(({ done, value }) => {
694
- if (done) {
695
- // Append any references after all the data has been streamed
696
- finalizeChatBodyResponse(references, newResponseText);
697
- return;
698
- }
653
+ // Setup chat message state
654
+ chatMessageState = {
655
+ newResponseTextEl,
656
+ newResponseEl,
657
+ loadingEllipsis,
658
+ references: {},
659
+ rawResponse: "",
660
+ rawQuery: query,
661
+ isVoice: isVoice,
662
+ }
699
663
 
700
- // Decode message chunk from stream
701
- const chunk = decoder.decode(value, { stream: true });
664
+ // Call Khoj chat API
665
+ let chatApi = `/api/chat?q=${encodeURIComponent(query)}&conversation_id=${conversationID}&stream=true&client=web`;
666
+ chatApi += (!!region && !!city && !!countryName && !!timezone)
667
+ ? `&region=${region}&city=${city}&country=${countryName}&timezone=${timezone}`
668
+ : '';
702
669
 
703
- if (chunk.includes("### compiled references:")) {
704
- ({ rawResponse, references } = handleCompiledReferences(newResponseText, chunk, references, rawResponse));
705
- readStream();
706
- } else {
707
- // If the chunk is not a JSON object, just display it as is
708
- rawResponse += chunk;
709
- handleStreamResponse(newResponseText, rawResponse, query, loadingEllipsis);
710
- readStream();
711
- }
712
- });
670
+ const response = await fetch(chatApi);
713
671
 
714
- // Scroll to bottom of chat window as chat response is streamed
715
- document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
716
- };
672
+ try {
673
+ if (!response.ok) throw new Error(response.statusText);
674
+ if (!response.body) throw new Error("Response body is empty");
675
+ // Stream and render chat response
676
+ await readChatStream(response);
677
+ } catch (err) {
678
+ console.error(`Khoj chat response failed with\n${err}`);
679
+ if (chatMessageState.newResponseEl.getElementsByClassName("lds-ellipsis").length > 0 && chatMessageState.loadingEllipsis)
680
+ chatMessageState.newResponseTextEl.removeChild(chatMessageState.loadingEllipsis);
681
+ let errorMsg = "Sorry, unable to get response from Khoj backend ❤️‍🩹. Retry or contact developers for help at <a href=mailto:'team@khoj.dev'>team@khoj.dev</a> or <a href='https://discord.gg/BDgyabRM6e'>on Discord</a>";
682
+ newResponseTextEl.innerHTML = errorMsg;
717
683
  }
718
- };
684
+ }
719
685
 
720
686
  function createLoadingEllipse() {
721
687
  // Temporary status message to indicate that Khoj is thinking
@@ -743,30 +709,20 @@ To get started, just start typing below. You can also type / to see a list of co
743
709
  }
744
710
 
745
711
  function handleStreamResponse(newResponseElement, rawResponse, rawQuery, loadingEllipsis, replace=true) {
746
- if (newResponseElement.getElementsByClassName("lds-ellipsis").length > 0 && loadingEllipsis) {
712
+ if (!newResponseElement) return;
713
+ // Remove loading ellipsis if it exists
714
+ if (newResponseElement.getElementsByClassName("lds-ellipsis").length > 0 && loadingEllipsis)
747
715
  newResponseElement.removeChild(loadingEllipsis);
748
- }
749
- if (replace) {
750
- newResponseElement.innerHTML = "";
751
- }
752
- newResponseElement.appendChild(formatHTMLMessage(rawResponse, false, replace, rawQuery));
753
- document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
754
- }
716
+ // Clear the response element if replace is true
717
+ if (replace) newResponseElement.innerHTML = "";
755
718
 
756
- function handleCompiledReferences(rawResponseElement, chunk, references, rawResponse) {
757
- const additionalResponse = chunk.split("### compiled references:")[0];
758
- rawResponse += additionalResponse;
759
- rawResponseElement.innerHTML = "";
760
- rawResponseElement.appendChild(formatHTMLMessage(rawResponse));
719
+ // Append response to the response element
720
+ newResponseElement.appendChild(formatHTMLMessage(rawResponse, false, replace, rawQuery));
761
721
 
762
- const rawReference = chunk.split("### compiled references:")[1];
763
- const rawReferenceAsJson = JSON.parse(rawReference);
764
- if (rawReferenceAsJson instanceof Array) {
765
- references["notes"] = rawReferenceAsJson;
766
- } else if (typeof rawReferenceAsJson === "object" && rawReferenceAsJson !== null) {
767
- references["online"] = rawReferenceAsJson;
768
- }
769
- return { rawResponse, references };
722
+ // Append loading ellipsis if it exists
723
+ if (!replace && loadingEllipsis) newResponseElement.appendChild(loadingEllipsis);
724
+ // Scroll to bottom of chat view
725
+ document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
770
726
  }
771
727
 
772
728
  function handleImageResponse(imageJson, rawResponse) {
@@ -785,35 +741,139 @@ To get started, just start typing below. You can also type / to see a list of co
785
741
  rawResponse += `\n\n**Inferred Query**:\n\n${inferredQuery}`;
786
742
  }
787
743
  }
788
- let references = {};
789
- if (imageJson.context && imageJson.context.length > 0) {
790
- const rawReferenceAsJson = imageJson.context;
791
- if (rawReferenceAsJson instanceof Array) {
792
- references["notes"] = rawReferenceAsJson;
793
- } else if (typeof rawReferenceAsJson === "object" && rawReferenceAsJson !== null) {
794
- references["online"] = rawReferenceAsJson;
795
- }
796
- }
797
- if (imageJson.detail) {
798
- // If response has detail field, response is an error message.
799
- rawResponse += imageJson.detail;
800
- }
801
- return { rawResponse, references };
802
- }
803
744
 
804
- function addMessageToChatBody(rawResponse, newResponseElement, references) {
805
- newResponseElement.innerHTML = "";
806
- newResponseElement.appendChild(formatHTMLMessage(rawResponse));
745
+ // If response has detail field, response is an error message.
746
+ if (imageJson.detail) rawResponse += imageJson.detail;
807
747
 
808
- finalizeChatBodyResponse(references, newResponseElement);
748
+ return rawResponse;
809
749
  }
810
750
 
811
751
  function finalizeChatBodyResponse(references, newResponseElement) {
812
- if (references != null && Object.keys(references).length > 0) {
752
+ if (!!newResponseElement && references != null && Object.keys(references).length > 0) {
813
753
  newResponseElement.appendChild(createReferenceSection(references));
814
754
  }
815
755
  document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
816
- document.getElementById("chat-input").removeAttribute("disabled");
756
+ document.getElementById("chat-input")?.removeAttribute("disabled");
757
+ }
758
+
759
+ function convertMessageChunkToJson(rawChunk) {
760
+ // Split the chunk into lines
761
+ console.debug("Raw Event:", rawChunk);
762
+ if (rawChunk?.startsWith("{") && rawChunk?.endsWith("}")) {
763
+ try {
764
+ let jsonChunk = JSON.parse(rawChunk);
765
+ if (!jsonChunk.type)
766
+ jsonChunk = {type: 'message', data: jsonChunk};
767
+ return jsonChunk;
768
+ } catch (e) {
769
+ return {type: 'message', data: rawChunk};
770
+ }
771
+ } else if (rawChunk.length > 0) {
772
+ return {type: 'message', data: rawChunk};
773
+ }
774
+ }
775
+
776
+ function processMessageChunk(rawChunk) {
777
+ const chunk = convertMessageChunkToJson(rawChunk);
778
+ console.debug("Json Event:", chunk);
779
+ if (!chunk || !chunk.type) return;
780
+ if (chunk.type ==='status') {
781
+ console.log(`status: ${chunk.data}`);
782
+ const statusMessage = chunk.data;
783
+ handleStreamResponse(chatMessageState.newResponseTextEl, statusMessage, chatMessageState.rawQuery, chatMessageState.loadingEllipsis, false);
784
+ } else if (chunk.type === 'start_llm_response') {
785
+ console.log("Started streaming", new Date());
786
+ } else if (chunk.type === 'end_llm_response') {
787
+ console.log("Stopped streaming", new Date());
788
+
789
+ // Automatically respond with voice if the subscribed user has sent voice message
790
+ if (chatMessageState.isVoice && "{{ is_active }}" == "True")
791
+ textToSpeech(chatMessageState.rawResponse);
792
+
793
+ // Append any references after all the data has been streamed
794
+ finalizeChatBodyResponse(chatMessageState.references, chatMessageState.newResponseTextEl);
795
+
796
+ const liveQuery = chatMessageState.rawQuery;
797
+ // Reset variables
798
+ chatMessageState = {
799
+ newResponseTextEl: null,
800
+ newResponseEl: null,
801
+ loadingEllipsis: null,
802
+ references: {},
803
+ rawResponse: "",
804
+ rawQuery: liveQuery,
805
+ isVoice: false,
806
+ }
807
+ } else if (chunk.type === "references") {
808
+ chatMessageState.references = {"notes": chunk.data.context, "online": chunk.data.onlineContext};
809
+ } else if (chunk.type === 'message') {
810
+ const chunkData = chunk.data;
811
+ if (typeof chunkData === 'object' && chunkData !== null) {
812
+ // If chunkData is already a JSON object
813
+ handleJsonResponse(chunkData);
814
+ } else if (typeof chunkData === 'string' && chunkData.trim()?.startsWith("{") && chunkData.trim()?.endsWith("}")) {
815
+ // Try process chunk data as if it is a JSON object
816
+ try {
817
+ const jsonData = JSON.parse(chunkData.trim());
818
+ handleJsonResponse(jsonData);
819
+ } catch (e) {
820
+ chatMessageState.rawResponse += chunkData;
821
+ handleStreamResponse(chatMessageState.newResponseTextEl, chatMessageState.rawResponse, chatMessageState.rawQuery, chatMessageState.loadingEllipsis);
822
+ }
823
+ } else {
824
+ chatMessageState.rawResponse += chunkData;
825
+ handleStreamResponse(chatMessageState.newResponseTextEl, chatMessageState.rawResponse, chatMessageState.rawQuery, chatMessageState.loadingEllipsis);
826
+ }
827
+ }
828
+ }
829
+
830
+ function handleJsonResponse(jsonData) {
831
+ if (jsonData.image || jsonData.detail) {
832
+ chatMessageState.rawResponse = handleImageResponse(jsonData, chatMessageState.rawResponse);
833
+ } else if (jsonData.response) {
834
+ chatMessageState.rawResponse = jsonData.response;
835
+ }
836
+
837
+ if (chatMessageState.newResponseTextEl) {
838
+ chatMessageState.newResponseTextEl.innerHTML = "";
839
+ chatMessageState.newResponseTextEl.appendChild(formatHTMLMessage(chatMessageState.rawResponse));
840
+ }
841
+ }
842
+
843
+ async function readChatStream(response) {
844
+ if (!response.body) return;
845
+ const reader = response.body.getReader();
846
+ const decoder = new TextDecoder();
847
+ const eventDelimiter = '␃🔚␗';
848
+ let buffer = '';
849
+
850
+ while (true) {
851
+ const { value, done } = await reader.read();
852
+ // If the stream is done
853
+ if (done) {
854
+ // Process the last chunk
855
+ processMessageChunk(buffer);
856
+ buffer = '';
857
+ break;
858
+ }
859
+
860
+ // Read chunk from stream and append it to the buffer
861
+ const chunk = decoder.decode(value, { stream: true });
862
+ console.debug("Raw Chunk:", chunk)
863
+ // Start buffering chunks until complete event is received
864
+ buffer += chunk;
865
+
866
+ // Once the buffer contains a complete event
867
+ let newEventIndex;
868
+ while ((newEventIndex = buffer.indexOf(eventDelimiter)) !== -1) {
869
+ // Extract the event from the buffer
870
+ const event = buffer.slice(0, newEventIndex);
871
+ buffer = buffer.slice(newEventIndex + eventDelimiter.length);
872
+
873
+ // Process the event
874
+ if (event) processMessageChunk(event);
875
+ }
876
+ }
817
877
  }
818
878
 
819
879
  function incrementalChat(event) {
@@ -998,8 +1058,8 @@ To get started, just start typing below. You can also type / to see a list of co
998
1058
  // Wait for all files to be read before making the fetch request
999
1059
  Promise.all(fileReadPromises)
1000
1060
  .then(() => {
1001
- return fetch("/api/v1/index/update?force=false&client=web", {
1002
- method: "POST",
1061
+ return fetch("/api/content?client=web", {
1062
+ method: "PATCH",
1003
1063
  body: formData,
1004
1064
  });
1005
1065
  })
@@ -1069,17 +1129,13 @@ To get started, just start typing below. You can also type / to see a list of co
1069
1129
 
1070
1130
  window.onload = loadChat;
1071
1131
 
1072
- function setupWebSocket(isVoice=false) {
1073
- let chatBody = document.getElementById("chat-body");
1074
- let wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
1075
- let webSocketUrl = `${wsProtocol}//${window.location.host}/api/chat/ws`;
1076
-
1132
+ function initMessageState(isVoice=false) {
1077
1133
  if (waitingForLocation) {
1078
1134
  console.debug("Waiting for location data to be fetched. Will setup WebSocket once location data is available.");
1079
1135
  return;
1080
1136
  }
1081
1137
 
1082
- websocketState = {
1138
+ chatMessageState = {
1083
1139
  newResponseTextEl: null,
1084
1140
  newResponseEl: null,
1085
1141
  loadingEllipsis: null,
@@ -1088,174 +1144,8 @@ To get started, just start typing below. You can also type / to see a list of co
1088
1144
  rawQuery: "",
1089
1145
  isVoice: isVoice,
1090
1146
  }
1091
-
1092
- if (chatBody.dataset.conversationId) {
1093
- webSocketUrl += `?conversation_id=${chatBody.dataset.conversationId}`;
1094
- webSocketUrl += (!!region && !!city && !!countryName) && !!timezone ? `&region=${region}&city=${city}&country=${countryName}&timezone=${timezone}` : '';
1095
-
1096
- websocket = new WebSocket(webSocketUrl);
1097
- websocket.onmessage = function(event) {
1098
-
1099
- // Get the last element in the chat-body
1100
- let chunk = event.data;
1101
- if (chunk == "start_llm_response") {
1102
- console.log("Started streaming", new Date());
1103
- } else if (chunk == "end_llm_response") {
1104
- console.log("Stopped streaming", new Date());
1105
-
1106
- // Automatically respond with voice if the subscribed user has sent voice message
1107
- if (websocketState.isVoice && "{{ is_active }}" == "True")
1108
- textToSpeech(websocketState.rawResponse);
1109
-
1110
- // Append any references after all the data has been streamed
1111
- finalizeChatBodyResponse(websocketState.references, websocketState.newResponseTextEl);
1112
-
1113
- const liveQuery = websocketState.rawQuery;
1114
- // Reset variables
1115
- websocketState = {
1116
- newResponseTextEl: null,
1117
- newResponseEl: null,
1118
- loadingEllipsis: null,
1119
- references: {},
1120
- rawResponse: "",
1121
- rawQuery: liveQuery,
1122
- isVoice: false,
1123
- }
1124
- } else {
1125
- try {
1126
- if (chunk.includes("application/json"))
1127
- {
1128
- chunk = JSON.parse(chunk);
1129
- }
1130
- } catch (error) {
1131
- // If the chunk is not a JSON object, continue.
1132
- }
1133
-
1134
- const contentType = chunk["content-type"]
1135
-
1136
- if (contentType === "application/json") {
1137
- // Handle JSON response
1138
- try {
1139
- if (chunk.image || chunk.detail) {
1140
- ({rawResponse, references } = handleImageResponse(chunk, websocketState.rawResponse));
1141
- websocketState.rawResponse = rawResponse;
1142
- websocketState.references = references;
1143
- } else if (chunk.type == "status") {
1144
- handleStreamResponse(websocketState.newResponseTextEl, chunk.message, websocketState.rawQuery, null, false);
1145
- } else if (chunk.type == "rate_limit") {
1146
- handleStreamResponse(websocketState.newResponseTextEl, chunk.message, websocketState.rawQuery, websocketState.loadingEllipsis, true);
1147
- } else {
1148
- rawResponse = chunk.response;
1149
- }
1150
- } catch (error) {
1151
- // If the chunk is not a JSON object, just display it as is
1152
- websocketState.rawResponse += chunk;
1153
- } finally {
1154
- if (chunk.type != "status" && chunk.type != "rate_limit") {
1155
- addMessageToChatBody(websocketState.rawResponse, websocketState.newResponseTextEl, websocketState.references);
1156
- }
1157
- }
1158
- } else {
1159
-
1160
- // Handle streamed response of type text/event-stream or text/plain
1161
- if (chunk && chunk.includes("### compiled references:")) {
1162
- ({ rawResponse, references } = handleCompiledReferences(websocketState.newResponseTextEl, chunk, websocketState.references, websocketState.rawResponse));
1163
- websocketState.rawResponse = rawResponse;
1164
- websocketState.references = references;
1165
- } else {
1166
- // If the chunk is not a JSON object, just display it as is
1167
- websocketState.rawResponse += chunk;
1168
- if (websocketState.newResponseTextEl) {
1169
- handleStreamResponse(websocketState.newResponseTextEl, websocketState.rawResponse, websocketState.rawQuery, websocketState.loadingEllipsis);
1170
- }
1171
- }
1172
-
1173
- // Scroll to bottom of chat window as chat response is streamed
1174
- document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
1175
- };
1176
- }
1177
- }
1178
- };
1179
- websocket.onclose = function(event) {
1180
- websocket = null;
1181
- console.log("WebSocket is closed now.");
1182
- let setupWebSocketButton = document.createElement("button");
1183
- setupWebSocketButton.textContent = "Reconnect to Server";
1184
- setupWebSocketButton.onclick = setupWebSocket;
1185
- let statusDotIcon = document.getElementById("connection-status-icon");
1186
- statusDotIcon.style.backgroundColor = "red";
1187
- let statusDotText = document.getElementById("connection-status-text");
1188
- statusDotText.innerHTML = "";
1189
- statusDotText.style.marginTop = "5px";
1190
- statusDotText.appendChild(setupWebSocketButton);
1191
- }
1192
- websocket.onerror = function(event) {
1193
- console.log("WebSocket error observed:", event);
1194
- }
1195
-
1196
- websocket.onopen = function(event) {
1197
- console.log("WebSocket is open now.")
1198
- let statusDotIcon = document.getElementById("connection-status-icon");
1199
- statusDotIcon.style.backgroundColor = "green";
1200
- let statusDotText = document.getElementById("connection-status-text");
1201
- statusDotText.textContent = "Connected to Server";
1202
- }
1203
1147
  }
1204
1148
 
1205
- function sendMessageViaWebSocket(isVoice=false) {
1206
- let chatBody = document.getElementById("chat-body");
1207
-
1208
- var query = document.getElementById("chat-input").value.trim();
1209
- console.log(`Query: ${query}`);
1210
-
1211
- if (userMessages.length >= 10) {
1212
- userMessages.shift();
1213
- }
1214
- userMessages.push(query);
1215
- resetUserMessageIndex();
1216
-
1217
- // Add message by user to chat body
1218
- renderMessage(query, "you");
1219
- document.getElementById("chat-input").value = "";
1220
- autoResize();
1221
- document.getElementById("chat-input").setAttribute("disabled", "disabled");
1222
-
1223
- let newResponseEl = document.createElement("div");
1224
- newResponseEl.classList.add("chat-message", "khoj");
1225
- newResponseEl.attributes["data-meta"] = "🏮 Khoj at " + formatDate(new Date());
1226
- chatBody.appendChild(newResponseEl);
1227
-
1228
- let newResponseTextEl = document.createElement("div");
1229
- newResponseTextEl.classList.add("chat-message-text", "khoj");
1230
- newResponseEl.appendChild(newResponseTextEl);
1231
-
1232
- // Temporary status message to indicate that Khoj is thinking
1233
- let loadingEllipsis = createLoadingEllipse();
1234
-
1235
- newResponseTextEl.appendChild(loadingEllipsis);
1236
- document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
1237
-
1238
- let chatTooltip = document.getElementById("chat-tooltip");
1239
- chatTooltip.style.display = "none";
1240
-
1241
- let chatInput = document.getElementById("chat-input");
1242
- chatInput.classList.remove("option-enabled");
1243
-
1244
- // Call specified Khoj API
1245
- websocket.send(query);
1246
- let rawResponse = "";
1247
- let references = {};
1248
-
1249
- websocketState = {
1250
- newResponseTextEl,
1251
- newResponseEl,
1252
- loadingEllipsis,
1253
- references,
1254
- rawResponse,
1255
- rawQuery: query,
1256
- isVoice: isVoice,
1257
- }
1258
- }
1259
1149
  var userMessages = [];
1260
1150
  var userMessageIndex = -1;
1261
1151
  function loadChat() {
@@ -1265,7 +1155,7 @@ To get started, just start typing below. You can also type / to see a list of co
1265
1155
  let chatHistoryUrl = `/api/chat/history?client=web`;
1266
1156
  if (chatBody.dataset.conversationId) {
1267
1157
  chatHistoryUrl += `&conversation_id=${chatBody.dataset.conversationId}`;
1268
- setupWebSocket();
1158
+ initMessageState();
1269
1159
  loadFileFiltersFromConversation();
1270
1160
  }
1271
1161
 
@@ -1305,7 +1195,7 @@ To get started, just start typing below. You can also type / to see a list of co
1305
1195
  let chatBody = document.getElementById("chat-body");
1306
1196
  chatBody.dataset.conversationId = response.conversation_id;
1307
1197
  loadFileFiltersFromConversation();
1308
- setupWebSocket();
1198
+ initMessageState();
1309
1199
  chatBody.dataset.conversationTitle = response.slug || `New conversation 🌱`;
1310
1200
 
1311
1201
  let agentMetadata = response.agent;
@@ -1333,7 +1223,7 @@ To get started, just start typing below. You can also type / to see a list of co
1333
1223
  - 📚 Understand files you drag & drop here
1334
1224
  - 👩🏾‍🚀 Be tuned to your conversation needs via [agents](./agents)
1335
1225
 
1336
- Get the Khoj [Desktop](https://khoj.dev/downloads), [Obsidian](https://docs.khoj.dev/clients/obsidian#setup), or [Emacs](https://docs.khoj.dev/clients/emacs#setup) app to keep your files in sync. You can manage all the files you've shared with me at any time by going to [your settings](/config/content-source/computer/).
1226
+ Get the Khoj [Desktop](https://khoj.dev/downloads), [Obsidian](https://docs.khoj.dev/clients/obsidian#setup), or [Emacs](https://docs.khoj.dev/clients/emacs#setup) app to keep your files in sync. You can manage all the files you've shared with me at any time by going to [your settings](/settings/content/computer/).
1337
1227
 
1338
1228
  To get started, just start typing below. You can also type / to see a list of commands.
1339
1229
 
@@ -1954,7 +1844,7 @@ To get started, just start typing below. You can also type / to see a list of co
1954
1844
  }
1955
1845
  var allFiles;
1956
1846
  function renderAllFiles() {
1957
- fetch('/api/config/data/computer')
1847
+ fetch('/api/content/computer')
1958
1848
  .then(response => response.json())
1959
1849
  .then(data => {
1960
1850
  var indexedFiles = document.getElementsByClassName("indexed-files")[0];
@@ -32,7 +32,7 @@
32
32
  </style>
33
33
  <script>
34
34
  function removeFile(path) {
35
- fetch('/api/config/data/file?filename=' + path, {
35
+ fetch('/api/content/file?filename=' + path, {
36
36
  method: 'DELETE',
37
37
  headers: {
38
38
  'Content-Type': 'application/json',
@@ -48,7 +48,7 @@
48
48
 
49
49
  // Get all currently indexed files
50
50
  function getAllComputerFilenames() {
51
- fetch('/api/config/data/computer')
51
+ fetch('/api/content/computer')
52
52
  .then(response => response.json())
53
53
  .then(data => {
54
54
  var indexedFiles = document.getElementsByClassName("indexed-files")[0];
@@ -122,7 +122,7 @@
122
122
  deleteAllComputerFilesButton.textContent = "🗑️ Deleting...";
123
123
  deleteAllComputerFilesButton.disabled = true;
124
124
 
125
- fetch('/api/config/data/content-source/computer', {
125
+ fetch('/api/content/computer', {
126
126
  method: 'DELETE',
127
127
  headers: {
128
128
  'Content-Type': 'application/json',