khoj 1.16.1.dev15__py3-none-any.whl → 1.17.1.dev229__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.
- khoj/configure.py +6 -6
- khoj/database/adapters/__init__.py +56 -12
- khoj/database/migrations/0053_agent_style_color_agent_style_icon.py +61 -0
- khoj/database/migrations/0054_alter_agent_style_color.py +38 -0
- khoj/database/models/__init__.py +35 -0
- khoj/interface/web/assets/icons/favicon-128x128.png +0 -0
- khoj/interface/web/assets/icons/favicon-256x256.png +0 -0
- khoj/interface/web/assets/icons/khoj-logo-sideways-200.png +0 -0
- khoj/interface/web/assets/icons/khoj-logo-sideways-500.png +0 -0
- khoj/interface/web/assets/icons/khoj-logo-sideways.svg +31 -5384
- khoj/interface/web/assets/icons/khoj.svg +26 -0
- khoj/interface/web/chat.html +191 -301
- khoj/interface/web/content_source_computer_input.html +3 -3
- khoj/interface/web/content_source_github_input.html +1 -1
- khoj/interface/web/content_source_notion_input.html +1 -1
- khoj/interface/web/public_conversation.html +1 -1
- khoj/interface/web/search.html +2 -2
- khoj/interface/web/{config.html → settings.html} +30 -30
- khoj/interface/web/utils.html +1 -1
- khoj/processor/content/docx/docx_to_entries.py +4 -9
- khoj/processor/content/github/github_to_entries.py +1 -3
- khoj/processor/content/images/image_to_entries.py +4 -9
- khoj/processor/content/markdown/markdown_to_entries.py +4 -9
- khoj/processor/content/notion/notion_to_entries.py +1 -3
- khoj/processor/content/org_mode/org_to_entries.py +4 -9
- khoj/processor/content/pdf/pdf_to_entries.py +4 -9
- khoj/processor/content/plaintext/plaintext_to_entries.py +4 -9
- khoj/processor/content/text_to_entries.py +1 -3
- khoj/processor/conversation/anthropic/anthropic_chat.py +10 -4
- khoj/processor/conversation/offline/chat_model.py +19 -7
- khoj/processor/conversation/offline/utils.py +2 -0
- khoj/processor/conversation/openai/gpt.py +9 -3
- khoj/processor/conversation/prompts.py +56 -25
- khoj/processor/conversation/utils.py +5 -6
- khoj/processor/tools/online_search.py +13 -7
- khoj/routers/api.py +60 -10
- khoj/routers/api_agents.py +3 -1
- khoj/routers/api_chat.py +335 -562
- khoj/routers/api_content.py +538 -0
- khoj/routers/api_model.py +156 -0
- khoj/routers/helpers.py +339 -26
- khoj/routers/notion.py +2 -8
- khoj/routers/web_client.py +43 -256
- khoj/search_type/text_search.py +5 -4
- khoj/utils/fs_syncer.py +4 -2
- khoj/utils/rawconfig.py +6 -1
- {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev229.dist-info}/METADATA +3 -3
- {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev229.dist-info}/RECORD +51 -49
- khoj/interface/web/assets/icons/favicon.icns +0 -0
- khoj/routers/api_config.py +0 -434
- khoj/routers/indexer.py +0 -349
- {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev229.dist-info}/WHEEL +0 -0
- {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev229.dist-info}/entry_points.txt +0 -0
- {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev229.dist-info}/licenses/LICENSE +0 -0
khoj/interface/web/chat.html
CHANGED
|
@@ -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](/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
603
|
-
|
|
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(
|
|
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
|
-
|
|
636
|
-
refreshChatSessionsPanel();
|
|
628
|
+
chatBody.dataset.conversationId = conversationID;
|
|
629
|
+
await refreshChatSessionsPanel();
|
|
637
630
|
}
|
|
638
631
|
|
|
639
|
-
let
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
-
|
|
701
|
-
|
|
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
|
+
? `®ion=${region}&city=${city}&country=${countryName}&timezone=${timezone}`
|
|
668
|
+
: '';
|
|
702
669
|
|
|
703
|
-
|
|
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
|
-
|
|
715
|
-
|
|
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
|
|
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
|
-
|
|
757
|
-
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
|
|
805
|
-
|
|
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
|
-
|
|
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")
|
|
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/
|
|
1002
|
-
method: "
|
|
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
|
|
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
|
-
|
|
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 ? `®ion=${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
|
-
|
|
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
|
-
|
|
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](/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
125
|
+
fetch('/api/content/computer', {
|
|
126
126
|
method: 'DELETE',
|
|
127
127
|
headers: {
|
|
128
128
|
'Content-Type': 'application/json',
|