khoj 1.17.1.dev229__py3-none-any.whl → 1.17.1.dev233__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/routers/web_client.py +29 -130
- {khoj-1.17.1.dev229.dist-info → khoj-1.17.1.dev233.dist-info}/METADATA +1 -1
- {khoj-1.17.1.dev229.dist-info → khoj-1.17.1.dev233.dist-info}/RECORD +6 -51
- khoj/interface/web/404.html +0 -56
- khoj/interface/web/agent.html +0 -312
- khoj/interface/web/agents.html +0 -276
- khoj/interface/web/assets/icons/cancel.svg +0 -3
- khoj/interface/web/assets/icons/collapse.svg +0 -17
- khoj/interface/web/assets/icons/computer.png +0 -0
- khoj/interface/web/assets/icons/confirm-icon.svg +0 -1
- khoj/interface/web/assets/icons/copy-button-success.svg +0 -6
- khoj/interface/web/assets/icons/copy-button.svg +0 -5
- khoj/interface/web/assets/icons/credit-card.png +0 -0
- khoj/interface/web/assets/icons/delete.svg +0 -26
- khoj/interface/web/assets/icons/docx.svg +0 -7
- khoj/interface/web/assets/icons/edit.svg +0 -4
- khoj/interface/web/assets/icons/key.svg +0 -4
- khoj/interface/web/assets/icons/markdown.svg +0 -1
- khoj/interface/web/assets/icons/new.svg +0 -23
- khoj/interface/web/assets/icons/notion.svg +0 -4
- khoj/interface/web/assets/icons/openai-logomark.svg +0 -1
- khoj/interface/web/assets/icons/org.svg +0 -1
- khoj/interface/web/assets/icons/pdf.svg +0 -23
- khoj/interface/web/assets/icons/pencil-edit.svg +0 -5
- khoj/interface/web/assets/icons/plaintext.svg +0 -1
- khoj/interface/web/assets/icons/question-mark-icon.svg +0 -1
- khoj/interface/web/assets/icons/send.svg +0 -1
- khoj/interface/web/assets/icons/share.svg +0 -8
- khoj/interface/web/assets/icons/speaker.svg +0 -4
- khoj/interface/web/assets/icons/stop-solid.svg +0 -37
- khoj/interface/web/assets/icons/thumbs-down-svgrepo-com.svg +0 -6
- khoj/interface/web/assets/icons/thumbs-up-svgrepo-com.svg +0 -6
- khoj/interface/web/assets/icons/user-silhouette.svg +0 -4
- khoj/interface/web/assets/icons/voice.svg +0 -8
- khoj/interface/web/assets/icons/web.svg +0 -2
- khoj/interface/web/assets/icons/whatsapp.svg +0 -17
- khoj/interface/web/assets/markdown-it.min.js +0 -8476
- khoj/interface/web/assets/natural-cron.min.js +0 -1
- khoj/interface/web/assets/org.min.js +0 -1823
- khoj/interface/web/assets/pico.min.css +0 -5
- khoj/interface/web/assets/purify.min.js +0 -3
- khoj/interface/web/chat.html +0 -3436
- khoj/interface/web/config_automation.html +0 -1103
- khoj/interface/web/content_source_computer_input.html +0 -139
- khoj/interface/web/content_source_notion_input.html +0 -94
- khoj/interface/web/public_conversation.html +0 -2006
- khoj/interface/web/search.html +0 -470
- khoj/interface/web/settings.html +0 -1011
- {khoj-1.17.1.dev229.dist-info → khoj-1.17.1.dev233.dist-info}/WHEEL +0 -0
- {khoj-1.17.1.dev229.dist-info → khoj-1.17.1.dev233.dist-info}/entry_points.txt +0 -0
- {khoj-1.17.1.dev229.dist-info → khoj-1.17.1.dev233.dist-info}/licenses/LICENSE +0 -0
khoj/interface/web/chat.html
DELETED
|
@@ -1,3436 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
|
6
|
-
<meta property="og:image" content="https://assets.khoj.dev/khoj_hero.png">
|
|
7
|
-
<meta http-equiv="Content-Security-Policy"
|
|
8
|
-
content="default-src 'self' https://assets.khoj.dev;
|
|
9
|
-
script-src 'self' https://assets.khoj.dev 'unsafe-inline';
|
|
10
|
-
connect-src 'self' https://ipapi.co/json;
|
|
11
|
-
style-src 'self' https://assets.khoj.dev 'unsafe-inline' https://fonts.googleapis.com;
|
|
12
|
-
img-src 'self' data: https://*.khoj.dev https://*.googleusercontent.com;
|
|
13
|
-
font-src https://assets.khoj.dev https://fonts.gstatic.com;
|
|
14
|
-
child-src 'none';
|
|
15
|
-
object-src 'none';">
|
|
16
|
-
<title>Khoj - Chat</title>
|
|
17
|
-
|
|
18
|
-
<link rel="stylesheet" href="/static/assets/khoj.css?v={{ khoj_version }}">
|
|
19
|
-
<link rel="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png?v={{ khoj_version }}">
|
|
20
|
-
<link rel="apple-touch-icon" href="/static/assets/icons/favicon-128x128.png?v={{ khoj_version }}">
|
|
21
|
-
<link rel="manifest" href="/static/khoj.webmanifest?v={{ khoj_version }}">
|
|
22
|
-
<link rel="stylesheet" href="https://assets.khoj.dev/katex/katex.min.css">
|
|
23
|
-
|
|
24
|
-
<!-- The loading of KaTeX is deferred to speed up page rendering -->
|
|
25
|
-
<script defer src="https://assets.khoj.dev/katex/katex.min.js"></script>
|
|
26
|
-
|
|
27
|
-
<!-- To automatically render math in text elements, include the auto-render extension: -->
|
|
28
|
-
<script defer src="https://assets.khoj.dev/katex/auto-render.min.js" onload="renderMathInElement(document.body);"></script>
|
|
29
|
-
</head>
|
|
30
|
-
<script type="text/javascript" src="/static/assets/purify.min.js?v={{ khoj_version }}"></script>
|
|
31
|
-
<script type="text/javascript" src="/static/assets/utils.js?v={{ khoj_version }}"></script>
|
|
32
|
-
<script type="text/javascript" src="/static/assets/markdown-it.min.js?v={{ khoj_version }}"></script>
|
|
33
|
-
<link rel="stylesheet" href="https://assets.khoj.dev/higlightjs/solarized-light.css">
|
|
34
|
-
<script src="https://assets.khoj.dev/higlightjs/highlight.min.js"></script>
|
|
35
|
-
<script>
|
|
36
|
-
let welcome_message = `
|
|
37
|
-
Hi, I am Khoj, your open, personal AI 👋🏽. I can:
|
|
38
|
-
- 🧠 Answer general knowledge questions
|
|
39
|
-
- 💡 Be a sounding board for your ideas
|
|
40
|
-
- 📜 Chat with your notes & documents
|
|
41
|
-
- 🌄 Generate images based on your messages
|
|
42
|
-
- 🔎 Search the web for answers to your questions
|
|
43
|
-
- 🎙️ Listen to your audio messages (use the mic by the input box to speak your message)
|
|
44
|
-
- 📚 Understand files you drag & drop here
|
|
45
|
-
- 👩🏾🚀 Be tuned to your conversation needs via [agents](./agents)
|
|
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](/settings/content/computer/).
|
|
48
|
-
|
|
49
|
-
To get started, just start typing below. You can also type / to see a list of commands.
|
|
50
|
-
`.trim()
|
|
51
|
-
const allowedExtensions = ['text/org', 'text/markdown', 'text/plain', 'text/html', 'application/pdf', 'image/jpeg', 'image/png', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
|
|
52
|
-
const allowedFileEndings = ['org', 'md', 'txt', 'html', 'pdf', 'jpg', 'jpeg', 'png', 'docx'];
|
|
53
|
-
let chatOptions = [];
|
|
54
|
-
function createCopyParentText(message) {
|
|
55
|
-
return function(event) {
|
|
56
|
-
copyParentText(event, message);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function copyParentText(event, message=null) {
|
|
60
|
-
const button = event.currentTarget;
|
|
61
|
-
const textContent = message ?? button.parentNode.textContent.trim();
|
|
62
|
-
navigator.clipboard.writeText(textContent).then(() => {
|
|
63
|
-
button.firstChild.src = "/static/assets/icons/copy-button-success.svg";
|
|
64
|
-
setTimeout(() => {
|
|
65
|
-
button.firstChild.src = "/static/assets/icons/copy-button.svg";
|
|
66
|
-
}, 1000);
|
|
67
|
-
}).catch((error) => {
|
|
68
|
-
console.error("Error copying programmatic output to clipboard:", error);
|
|
69
|
-
const originalButtonText = button.innerHTML;
|
|
70
|
-
button.innerHTML = "⛔️";
|
|
71
|
-
setTimeout(() => {
|
|
72
|
-
button.innerHTML = originalButtonText;
|
|
73
|
-
button.firstChild.src = "/static/assets/icons/copy-button.svg";
|
|
74
|
-
}, 1000);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
let region = null;
|
|
79
|
-
let city = null;
|
|
80
|
-
let countryName = null;
|
|
81
|
-
let timezone = null;
|
|
82
|
-
let waitingForLocation = true;
|
|
83
|
-
let chatMessageState = {
|
|
84
|
-
newResponseTextEl: null,
|
|
85
|
-
newResponseEl: null,
|
|
86
|
-
loadingEllipsis: null,
|
|
87
|
-
references: {},
|
|
88
|
-
rawResponse: "",
|
|
89
|
-
isVoice: false,
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
fetch("https://ipapi.co/json")
|
|
93
|
-
.then(response => response.json())
|
|
94
|
-
.then(data => {
|
|
95
|
-
region = data.region;
|
|
96
|
-
city = data.city;
|
|
97
|
-
countryName = data.country_name;
|
|
98
|
-
timezone = data.timezone;
|
|
99
|
-
})
|
|
100
|
-
.catch(err => {
|
|
101
|
-
console.log(err);
|
|
102
|
-
return;
|
|
103
|
-
})
|
|
104
|
-
.finally(() => {
|
|
105
|
-
console.debug("Region:", region, "City:", city, "Country:", countryName, "Timezone:", timezone);
|
|
106
|
-
waitingForLocation = false;
|
|
107
|
-
initMessageState();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
function formatDate(date) {
|
|
111
|
-
// Format date in HH:MM, DD MMM YYYY format
|
|
112
|
-
let time_string = date.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
113
|
-
let date_string = date.toLocaleString('en-IN', { year: 'numeric', month: 'short', day: '2-digit'}).replaceAll('-', ' ');
|
|
114
|
-
return `${time_string}, ${date_string}`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function generateReference(referenceJson, index) {
|
|
118
|
-
let reference = referenceJson.hasOwnProperty("compiled") ? referenceJson.compiled : referenceJson;
|
|
119
|
-
let referenceFile = referenceJson.hasOwnProperty("file") ? referenceJson.file : null;
|
|
120
|
-
|
|
121
|
-
// Escape reference for HTML rendering
|
|
122
|
-
let escaped_ref = reference.replaceAll('"', '"');
|
|
123
|
-
|
|
124
|
-
// Generate HTML for Chat Reference
|
|
125
|
-
let short_ref = escaped_ref.slice(0, 140);
|
|
126
|
-
short_ref = short_ref.length < escaped_ref.length ? short_ref + "..." : short_ref;
|
|
127
|
-
let referenceButton = document.createElement('button');
|
|
128
|
-
referenceButton.textContent = short_ref;
|
|
129
|
-
referenceButton.id = `ref-${index}`;
|
|
130
|
-
referenceButton.classList.add("reference-button");
|
|
131
|
-
referenceButton.classList.add("collapsed");
|
|
132
|
-
referenceButton.tabIndex = 0;
|
|
133
|
-
|
|
134
|
-
// Add event listener to toggle full reference on click
|
|
135
|
-
referenceButton.addEventListener('click', function() {
|
|
136
|
-
if (this.classList.contains("collapsed")) {
|
|
137
|
-
this.classList.remove("collapsed");
|
|
138
|
-
this.classList.add("expanded");
|
|
139
|
-
this.textContent = escaped_ref;
|
|
140
|
-
} else {
|
|
141
|
-
this.classList.add("collapsed");
|
|
142
|
-
this.classList.remove("expanded");
|
|
143
|
-
this.textContent = short_ref;
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
return referenceButton;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function generateOnlineReference(reference, index) {
|
|
151
|
-
// Generate HTML for Chat Reference
|
|
152
|
-
let title = reference.title || reference.link;
|
|
153
|
-
let link = reference.link;
|
|
154
|
-
let snippet = reference.snippet;
|
|
155
|
-
let question = reference.question;
|
|
156
|
-
if (question) {
|
|
157
|
-
question = `<b>Question:</b> ${question}<br><br>`;
|
|
158
|
-
} else {
|
|
159
|
-
question = "";
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let linkElement = document.createElement('a');
|
|
163
|
-
linkElement.setAttribute('href', link);
|
|
164
|
-
linkElement.setAttribute('target', '_blank');
|
|
165
|
-
linkElement.setAttribute('rel', 'noopener noreferrer');
|
|
166
|
-
linkElement.classList.add("reference-link");
|
|
167
|
-
linkElement.setAttribute('title', title);
|
|
168
|
-
linkElement.textContent = title;
|
|
169
|
-
|
|
170
|
-
let referenceButton = document.createElement('button');
|
|
171
|
-
referenceButton.appendChild(linkElement);
|
|
172
|
-
referenceButton.id = `ref-${index}`;
|
|
173
|
-
referenceButton.classList.add("reference-button");
|
|
174
|
-
referenceButton.classList.add("collapsed");
|
|
175
|
-
referenceButton.tabIndex = 0;
|
|
176
|
-
|
|
177
|
-
// Add event listener to toggle full reference on click
|
|
178
|
-
referenceButton.addEventListener('click', function() {
|
|
179
|
-
if (this.classList.contains("collapsed")) {
|
|
180
|
-
this.classList.remove("collapsed");
|
|
181
|
-
this.classList.add("expanded");
|
|
182
|
-
this.innerHTML = `${linkElement.outerHTML}<br><br>${question}${snippet}`;
|
|
183
|
-
} else {
|
|
184
|
-
this.classList.add("collapsed");
|
|
185
|
-
this.classList.remove("expanded");
|
|
186
|
-
this.innerHTML = "";
|
|
187
|
-
this.appendChild(linkElement);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
return referenceButton;
|
|
192
|
-
}
|
|
193
|
-
var khojQuery = "";
|
|
194
|
-
function renderMessage(message, by, dt=null, annotations=null, raw=false, renderType="append", userQuery=null) {
|
|
195
|
-
let message_time = formatDate(dt ?? new Date());
|
|
196
|
-
let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You";
|
|
197
|
-
let formattedMessage = formatHTMLMessage(message, raw, true, userQuery);
|
|
198
|
-
//update userQuery or khojQuery to latest query for feedback purposes
|
|
199
|
-
if(by !== "khoj"){
|
|
200
|
-
raw = formattedMessage.innerHTML;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
//find the thumbs up and thumbs down buttons from the message formatter
|
|
204
|
-
var thumbsUpButtons = formattedMessage.querySelectorAll('.thumbs-up-button');
|
|
205
|
-
var thumbsDownButtons = formattedMessage.querySelectorAll('.thumbs-down-button');
|
|
206
|
-
|
|
207
|
-
//only render the feedback options if the message is a response from khoj
|
|
208
|
-
if(by !== "khoj"){
|
|
209
|
-
thumbsUpButtons.forEach(function(element) {
|
|
210
|
-
element.parentNode.removeChild(element);
|
|
211
|
-
});
|
|
212
|
-
thumbsDownButtons.forEach(function(element) {
|
|
213
|
-
element.parentNode.removeChild(element);
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// Create a new div for the chat message
|
|
219
|
-
let chatMessage = document.createElement('div');
|
|
220
|
-
chatMessage.className = `chat-message ${by}`;
|
|
221
|
-
chatMessage.dataset.meta = `${by_name} at ${message_time}`;
|
|
222
|
-
|
|
223
|
-
// Create a new div for the chat message text and append it to the chat message
|
|
224
|
-
let chatMessageText = document.createElement('div');
|
|
225
|
-
chatMessageText.className = `chat-message-text ${by}`;
|
|
226
|
-
chatMessageText.appendChild(formattedMessage);
|
|
227
|
-
chatMessage.appendChild(chatMessageText);
|
|
228
|
-
|
|
229
|
-
// Append annotations div to the chat message
|
|
230
|
-
if (annotations) {
|
|
231
|
-
chatMessageText.appendChild(annotations);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Append chat message div to chat body
|
|
235
|
-
let chatBody = document.getElementById("chat-body");
|
|
236
|
-
if (renderType === "append") {
|
|
237
|
-
chatBody.appendChild(chatMessage);
|
|
238
|
-
// Scroll to bottom of chat-body element
|
|
239
|
-
chatBody.scrollTop = chatBody.scrollHeight;
|
|
240
|
-
} else if (renderType === "prepend"){
|
|
241
|
-
let chatBody = document.getElementById("chat-body");
|
|
242
|
-
chatBody.insertBefore(chatMessage, chatBody.firstChild);
|
|
243
|
-
} else if (renderType === "return") {
|
|
244
|
-
return chatMessage;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
let chatBodyWrapper = document.getElementById("chat-body-wrapper");
|
|
248
|
-
chatBodyWrapperHeight = chatBodyWrapper.clientHeight;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function processOnlineReferences(referenceSection, onlineContext) {
|
|
252
|
-
let numOnlineReferences = 0;
|
|
253
|
-
for (let subquery in onlineContext) {
|
|
254
|
-
let onlineReference = onlineContext[subquery];
|
|
255
|
-
if (onlineReference.organic && onlineReference.organic.length > 0) {
|
|
256
|
-
numOnlineReferences += onlineReference.organic.length;
|
|
257
|
-
for (let index in onlineReference.organic) {
|
|
258
|
-
let reference = onlineReference.organic[index];
|
|
259
|
-
let polishedReference = generateOnlineReference(reference, index);
|
|
260
|
-
referenceSection.appendChild(polishedReference);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (onlineReference.knowledgeGraph && onlineReference.knowledgeGraph.length > 0) {
|
|
265
|
-
numOnlineReferences += onlineReference.knowledgeGraph.length;
|
|
266
|
-
for (let index in onlineReference.knowledgeGraph) {
|
|
267
|
-
let reference = onlineReference.knowledgeGraph[index];
|
|
268
|
-
let polishedReference = generateOnlineReference(reference, index);
|
|
269
|
-
referenceSection.appendChild(polishedReference);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (onlineReference.peopleAlsoAsk && onlineReference.peopleAlsoAsk.length > 0) {
|
|
274
|
-
numOnlineReferences += onlineReference.peopleAlsoAsk.length;
|
|
275
|
-
for (let index in onlineReference.peopleAlsoAsk) {
|
|
276
|
-
let reference = onlineReference.peopleAlsoAsk[index];
|
|
277
|
-
let polishedReference = generateOnlineReference(reference, index);
|
|
278
|
-
referenceSection.appendChild(polishedReference);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (onlineReference.webpages && onlineReference.webpages.length > 0) {
|
|
283
|
-
numOnlineReferences += onlineReference.webpages.length;
|
|
284
|
-
for (let index in onlineReference.webpages) {
|
|
285
|
-
let reference = onlineReference.webpages[index];
|
|
286
|
-
let polishedReference = generateOnlineReference(reference, index);
|
|
287
|
-
referenceSection.appendChild(polishedReference);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return numOnlineReferences;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function renderMessageWithReference(message, by, context=null, dt=null, onlineContext=null, intentType=null, inferredQueries=null) {
|
|
296
|
-
let chatEl;
|
|
297
|
-
if (intentType?.includes("text-to-image")) {
|
|
298
|
-
let imageMarkdown = generateImageMarkdown(message, intentType, inferredQueries);
|
|
299
|
-
chatEl = renderMessage(imageMarkdown, by, dt, null, false, "return");
|
|
300
|
-
} else {
|
|
301
|
-
chatEl = renderMessage(message, by, dt, null, false, "return");
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// If no document or online context is provided, render the message as is
|
|
305
|
-
if ((context == null || context?.length == 0)
|
|
306
|
-
&& (onlineContext == null || (onlineContext && Object.keys(onlineContext).length == 0))) {
|
|
307
|
-
return chatEl;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// If document or online context is provided, render the message with its references
|
|
311
|
-
let references = {};
|
|
312
|
-
if (!!context) references["notes"] = context;
|
|
313
|
-
if (!!onlineContext) references["online"] = onlineContext;
|
|
314
|
-
let chatMessageEl = chatEl.getElementsByClassName("chat-message-text")[0];
|
|
315
|
-
chatMessageEl.appendChild(createReferenceSection(references));
|
|
316
|
-
|
|
317
|
-
return chatEl;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function generateImageMarkdown(message, intentType, inferredQueries=null) {
|
|
321
|
-
let imageMarkdown;
|
|
322
|
-
if (intentType === "text-to-image") {
|
|
323
|
-
imageMarkdown = ``;
|
|
324
|
-
} else if (intentType === "text-to-image2") {
|
|
325
|
-
imageMarkdown = ``;
|
|
326
|
-
} else if (intentType === "text-to-image-v3") {
|
|
327
|
-
imageMarkdown = ``;
|
|
328
|
-
}
|
|
329
|
-
const inferredQuery = inferredQueries?.[0];
|
|
330
|
-
if (inferredQuery) {
|
|
331
|
-
imageMarkdown += `\n\n**Inferred Query**:\n\n${inferredQuery}`;
|
|
332
|
-
}
|
|
333
|
-
return imageMarkdown;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
//handler function for posting feedback data to endpoint
|
|
337
|
-
function sendFeedback(_uquery="", _kquery="", _sentiment="") {
|
|
338
|
-
const uquery = _uquery;
|
|
339
|
-
const kquery = _kquery;
|
|
340
|
-
const sentiment = _sentiment;
|
|
341
|
-
fetch('/api/chat/feedback', {
|
|
342
|
-
method: 'POST',
|
|
343
|
-
headers: {
|
|
344
|
-
'Content-Type': 'application/json'
|
|
345
|
-
},
|
|
346
|
-
body: JSON.stringify({uquery: uquery, kquery: kquery, sentiment: sentiment})
|
|
347
|
-
})
|
|
348
|
-
.then(response => response.json())
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function textToSpeech(message, event=null) {
|
|
352
|
-
// Replace the speaker with a loading icon.
|
|
353
|
-
let loader = document.createElement("span");
|
|
354
|
-
loader.classList.add("loader");
|
|
355
|
-
|
|
356
|
-
let speechButton;
|
|
357
|
-
let speechIcon;
|
|
358
|
-
if (event === null) {
|
|
359
|
-
// Pick the last speech button if none is provided
|
|
360
|
-
let speechButtons = document.getElementsByClassName("speech-button");
|
|
361
|
-
speechButton = speechButtons[speechButtons.length - 1];
|
|
362
|
-
|
|
363
|
-
let speechIcons = document.getElementsByClassName("speech-icon");
|
|
364
|
-
speechIcon = speechIcons[speechIcons.length - 1];
|
|
365
|
-
} else {
|
|
366
|
-
speechButton = event.currentTarget;
|
|
367
|
-
speechIcon = event.target;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
speechButton.innerHTML = "";
|
|
371
|
-
speechButton.appendChild(loader);
|
|
372
|
-
speechButton.disabled = true;
|
|
373
|
-
|
|
374
|
-
const context = new (window.AudioContext || window.webkitAudioContext)();
|
|
375
|
-
fetch(`/api/chat/speech?text=${encodeURIComponent(message)}`, {
|
|
376
|
-
method: 'POST',
|
|
377
|
-
headers: {
|
|
378
|
-
'Content-Type': 'application/json'
|
|
379
|
-
},
|
|
380
|
-
})
|
|
381
|
-
.then(response => response.arrayBuffer())
|
|
382
|
-
.then(arrayBuffer => context.decodeAudioData(arrayBuffer))
|
|
383
|
-
.then(audioBuffer => {
|
|
384
|
-
const source = context.createBufferSource();
|
|
385
|
-
source.buffer = audioBuffer;
|
|
386
|
-
source.connect(context.destination);
|
|
387
|
-
source.start(0);
|
|
388
|
-
source.onended = function() {
|
|
389
|
-
speechButton.innerHTML = "";
|
|
390
|
-
speechButton.appendChild(speechIcon);
|
|
391
|
-
speechButton.disabled = false;
|
|
392
|
-
};
|
|
393
|
-
})
|
|
394
|
-
.catch(err => {
|
|
395
|
-
console.error("Error playing speech:", err);
|
|
396
|
-
speechButton.innerHTML = "";
|
|
397
|
-
speechButton.appendChild(speechIcon);
|
|
398
|
-
speechButton.disabled = true;
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function formatHTMLMessage(message, raw=false, willReplace=true, userQuery) {
|
|
403
|
-
var md = window.markdownit();
|
|
404
|
-
let newHTML = message;
|
|
405
|
-
|
|
406
|
-
// Replace LaTeX delimiters with placeholders
|
|
407
|
-
newHTML = newHTML.replace(/\\\(/g, 'LEFTPAREN').replace(/\\\)/g, 'RIGHTPAREN')
|
|
408
|
-
.replace(/\\\[/g, 'LEFTBRACKET').replace(/\\\]/g, 'RIGHTBRACKET');
|
|
409
|
-
|
|
410
|
-
// Remove any text between <s>[INST] and </s> tags. These are spurious instructions for the AI chat model.
|
|
411
|
-
newHTML = newHTML.replace(/<s>\[INST\].+(<\/s>)?/g, '');
|
|
412
|
-
|
|
413
|
-
// Customize the rendering of images
|
|
414
|
-
md.renderer.rules.image = function(tokens, idx, options, env, self) {
|
|
415
|
-
let token = tokens[idx];
|
|
416
|
-
|
|
417
|
-
// Add class="text-to-image" to images
|
|
418
|
-
token.attrPush(['class', 'text-to-image']);
|
|
419
|
-
|
|
420
|
-
// Use the default renderer to render image markdown format
|
|
421
|
-
return self.renderToken(tokens, idx, options);
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
// Render markdown
|
|
425
|
-
newHTML = raw ? newHTML : md.render(newHTML);
|
|
426
|
-
|
|
427
|
-
// Replace placeholders with LaTeX delimiters
|
|
428
|
-
newHTML = newHTML.replace(/LEFTPAREN/g, '\\(').replace(/RIGHTPAREN/g, '\\)')
|
|
429
|
-
.replace(/LEFTBRACKET/g, '\\[').replace(/RIGHTBRACKET/g, '\\]');
|
|
430
|
-
|
|
431
|
-
// Sanitize the rendered markdown
|
|
432
|
-
newHTML = DOMPurify.sanitize(newHTML);
|
|
433
|
-
|
|
434
|
-
// Set rendered markdown to HTML DOM element
|
|
435
|
-
let element = document.createElement('div');
|
|
436
|
-
element.innerHTML = newHTML;
|
|
437
|
-
element.className = "chat-message-text-response";
|
|
438
|
-
|
|
439
|
-
// Add a copy button to each chat message, if it doesn't already exist
|
|
440
|
-
if (willReplace === true) {
|
|
441
|
-
let copyButton = document.createElement('button');
|
|
442
|
-
copyButton.classList.add("copy-button");
|
|
443
|
-
copyButton.title = "Copy Message";
|
|
444
|
-
let copyIcon = document.createElement("img");
|
|
445
|
-
copyIcon.src = "/static/assets/icons/copy-button.svg";
|
|
446
|
-
copyIcon.classList.add("copy-icon");
|
|
447
|
-
copyButton.appendChild(copyIcon);
|
|
448
|
-
copyButton.addEventListener('click', createCopyParentText(message));
|
|
449
|
-
|
|
450
|
-
//create thumbs-up button
|
|
451
|
-
let thumbsUpButton = document.createElement('button');
|
|
452
|
-
thumbsUpButton.className = 'thumbs-up-button';
|
|
453
|
-
let thumbsUpIcon = document.createElement("img");
|
|
454
|
-
thumbsUpIcon.src = "/static/assets/icons/thumbs-up-svgrepo-com.svg";
|
|
455
|
-
thumbsUpIcon.classList.add("thumbs-up-icon");
|
|
456
|
-
thumbsUpButton.appendChild(thumbsUpIcon);
|
|
457
|
-
thumbsUpButton.onclick = function() {
|
|
458
|
-
khojQuery = newHTML;
|
|
459
|
-
thumbsUpIcon.src = "/static/assets/icons/confirm-icon.svg";
|
|
460
|
-
sendFeedback(userQuery ,khojQuery, "Good Response");
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
// Create thumbs-down button
|
|
464
|
-
let thumbsDownButton = document.createElement('button');
|
|
465
|
-
thumbsDownButton.className = 'thumbs-down-button';
|
|
466
|
-
let thumbsDownIcon = document.createElement("img");
|
|
467
|
-
thumbsDownIcon.src = "/static/assets/icons/thumbs-down-svgrepo-com.svg";
|
|
468
|
-
thumbsDownIcon.classList.add("thumbs-down-icon");
|
|
469
|
-
thumbsDownButton.appendChild(thumbsDownIcon);
|
|
470
|
-
thumbsDownButton.onclick = function() {
|
|
471
|
-
khojQuery = newHTML;
|
|
472
|
-
thumbsDownIcon.src = "/static/assets/icons/confirm-icon.svg";
|
|
473
|
-
sendFeedback(userQuery, khojQuery, "Bad Response");
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
// Only enable the speech feature if the user is subscribed
|
|
477
|
-
let speechButton = null;
|
|
478
|
-
|
|
479
|
-
if ("{{ is_active }}" == "True") {
|
|
480
|
-
// Create a speech button icon to play the message out loud
|
|
481
|
-
speechButton = document.createElement('button');
|
|
482
|
-
speechButton.classList.add("speech-button");
|
|
483
|
-
speechButton.title = "Listen to Message";
|
|
484
|
-
let speechIcon = document.createElement("img");
|
|
485
|
-
speechIcon.src = "/static/assets/icons/speaker.svg";
|
|
486
|
-
speechIcon.classList.add("speech-icon");
|
|
487
|
-
speechButton.appendChild(speechIcon);
|
|
488
|
-
speechButton.addEventListener('click', (event) => textToSpeech(message, event));
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Append buttons to parent element
|
|
492
|
-
element.append(copyButton, thumbsDownButton, thumbsUpButton);
|
|
493
|
-
|
|
494
|
-
if (speechButton) {
|
|
495
|
-
element.append(speechButton);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
renderMathInElement(element, {
|
|
500
|
-
// customised options
|
|
501
|
-
// • auto-render specific keys, e.g.:
|
|
502
|
-
delimiters: [
|
|
503
|
-
{left: '$$', right: '$$', display: true},
|
|
504
|
-
{left: '\\(', right: '\\)', display: false},
|
|
505
|
-
{left: '\\[', right: '\\]', display: true}
|
|
506
|
-
],
|
|
507
|
-
// • rendering keys, e.g.:
|
|
508
|
-
throwOnError : false
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
// Get any elements with a class that starts with "language"
|
|
512
|
-
let codeBlockElements = element.querySelectorAll('[class^="language-"]');
|
|
513
|
-
// For each element, add a parent div with the class "programmatic-output"
|
|
514
|
-
codeBlockElements.forEach((codeElement) => {
|
|
515
|
-
// Create the parent div
|
|
516
|
-
let parentDiv = document.createElement('div');
|
|
517
|
-
parentDiv.classList.add("programmatic-output");
|
|
518
|
-
// Add the parent div before the code element
|
|
519
|
-
codeElement.parentNode.insertBefore(parentDiv, codeElement);
|
|
520
|
-
// Move the code element into the parent div
|
|
521
|
-
parentDiv.appendChild(codeElement);
|
|
522
|
-
|
|
523
|
-
// Check if hijs has been loaded
|
|
524
|
-
if (typeof hljs !== 'undefined') {
|
|
525
|
-
// Highlight the code block
|
|
526
|
-
hljs.highlightBlock(codeElement);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// Add a copy button to each code block, if it doesn't already exist
|
|
530
|
-
if (willReplace === true) {
|
|
531
|
-
let copyButton = document.createElement('button');
|
|
532
|
-
copyButton.classList.add("copy-button");
|
|
533
|
-
copyButton.title = "Copy Code";
|
|
534
|
-
let copyIcon = document.createElement("img");
|
|
535
|
-
copyIcon.src = "/static/assets/icons/copy-button.svg";
|
|
536
|
-
copyIcon.classList.add("copy-icon");
|
|
537
|
-
copyButton.appendChild(copyIcon);
|
|
538
|
-
copyButton.addEventListener('click', copyParentText);
|
|
539
|
-
codeElement.prepend(copyButton);
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
// Get all code elements that have no class.
|
|
544
|
-
let codeElements = element.querySelectorAll('code:not([class])');
|
|
545
|
-
codeElements.forEach((codeElement) => {
|
|
546
|
-
// Add the class "chat-response" to each element
|
|
547
|
-
codeElement.classList.add("chat-response");
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
let anchorElements = element.querySelectorAll('a');
|
|
551
|
-
anchorElements.forEach((anchorElement) => {
|
|
552
|
-
// Add the class "inline-chat-link" to each element
|
|
553
|
-
anchorElement.classList.add("inline-chat-link");
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
return element
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function createReferenceSection(references) {
|
|
560
|
-
let referenceSection = document.createElement('div');
|
|
561
|
-
referenceSection.classList.add("reference-section");
|
|
562
|
-
referenceSection.classList.add("collapsed");
|
|
563
|
-
|
|
564
|
-
let numReferences = 0;
|
|
565
|
-
|
|
566
|
-
if (references.hasOwnProperty("notes")) {
|
|
567
|
-
numReferences += references["notes"].length;
|
|
568
|
-
|
|
569
|
-
references["notes"].forEach((reference, index) => {
|
|
570
|
-
let polishedReference = generateReference(reference, index);
|
|
571
|
-
referenceSection.appendChild(polishedReference);
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
if (references.hasOwnProperty("online")) {
|
|
575
|
-
numReferences += processOnlineReferences(referenceSection, references["online"]);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
let referenceExpandButton = document.createElement('button');
|
|
579
|
-
referenceExpandButton.classList.add("reference-expand-button");
|
|
580
|
-
referenceExpandButton.textContent = numReferences == 1 ? "1 reference" : `${numReferences} references`;
|
|
581
|
-
|
|
582
|
-
referenceExpandButton.addEventListener('click', function() {
|
|
583
|
-
if (referenceSection.classList.contains("collapsed")) {
|
|
584
|
-
referenceSection.classList.remove("collapsed");
|
|
585
|
-
referenceSection.classList.add("expanded");
|
|
586
|
-
} else {
|
|
587
|
-
referenceSection.classList.add("collapsed");
|
|
588
|
-
referenceSection.classList.remove("expanded");
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
let referencesDiv = document.createElement('div');
|
|
593
|
-
referencesDiv.classList.add("references");
|
|
594
|
-
referencesDiv.appendChild(referenceExpandButton);
|
|
595
|
-
referencesDiv.appendChild(referenceSection);
|
|
596
|
-
|
|
597
|
-
return referencesDiv;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
async function chat(isVoice=false) {
|
|
601
|
-
// Extract chat message from chat input form
|
|
602
|
-
var query = document.getElementById("chat-input").value.trim();
|
|
603
|
-
console.log(`Query: ${query}`);
|
|
604
|
-
|
|
605
|
-
// Short circuit on empty query
|
|
606
|
-
if (query.length === 0)
|
|
607
|
-
return;
|
|
608
|
-
|
|
609
|
-
// if the query is not empty then update userMessages array. keep the size of the array to 10
|
|
610
|
-
if (userMessages.length >= 10) {
|
|
611
|
-
userMessages.shift();
|
|
612
|
-
}
|
|
613
|
-
userMessages.push(query);
|
|
614
|
-
resetUserMessageIndex();
|
|
615
|
-
|
|
616
|
-
// Add message by user to chat body
|
|
617
|
-
renderMessage(query, "you");
|
|
618
|
-
document.getElementById("chat-input").value = "";
|
|
619
|
-
autoResize();
|
|
620
|
-
document.getElementById("chat-input").setAttribute("disabled", "disabled");
|
|
621
|
-
|
|
622
|
-
let chatBody = document.getElementById("chat-body");
|
|
623
|
-
let conversationID = chatBody.dataset.conversationId;
|
|
624
|
-
if (!conversationID) {
|
|
625
|
-
let response = await fetch(`${hostURL}/api/chat/sessions`, { method: "POST" });
|
|
626
|
-
let data = await response.json();
|
|
627
|
-
conversationID = data.conversation_id;
|
|
628
|
-
chatBody.dataset.conversationId = conversationID;
|
|
629
|
-
await refreshChatSessionsPanel();
|
|
630
|
-
}
|
|
631
|
-
|
|
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);
|
|
636
|
-
|
|
637
|
-
let newResponseTextEl = document.createElement("div");
|
|
638
|
-
newResponseTextEl.classList.add("chat-message-text", "khoj");
|
|
639
|
-
newResponseEl.appendChild(newResponseTextEl);
|
|
640
|
-
|
|
641
|
-
// Temporary status message to indicate that Khoj is thinking
|
|
642
|
-
let loadingEllipsis = createLoadingEllipse();
|
|
643
|
-
|
|
644
|
-
newResponseTextEl.appendChild(loadingEllipsis);
|
|
645
|
-
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
|
646
|
-
|
|
647
|
-
let chatTooltip = document.getElementById("chat-tooltip");
|
|
648
|
-
chatTooltip.style.display = "none";
|
|
649
|
-
|
|
650
|
-
let chatInput = document.getElementById("chat-input");
|
|
651
|
-
chatInput.classList.remove("option-enabled");
|
|
652
|
-
|
|
653
|
-
// Setup chat message state
|
|
654
|
-
chatMessageState = {
|
|
655
|
-
newResponseTextEl,
|
|
656
|
-
newResponseEl,
|
|
657
|
-
loadingEllipsis,
|
|
658
|
-
references: {},
|
|
659
|
-
rawResponse: "",
|
|
660
|
-
rawQuery: query,
|
|
661
|
-
isVoice: isVoice,
|
|
662
|
-
}
|
|
663
|
-
|
|
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
|
-
: '';
|
|
669
|
-
|
|
670
|
-
const response = await fetch(chatApi);
|
|
671
|
-
|
|
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;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
function createLoadingEllipse() {
|
|
687
|
-
// Temporary status message to indicate that Khoj is thinking
|
|
688
|
-
let loadingEllipsis = document.createElement("div");
|
|
689
|
-
loadingEllipsis.classList.add("lds-ellipsis");
|
|
690
|
-
|
|
691
|
-
let firstEllipsis = document.createElement("div");
|
|
692
|
-
firstEllipsis.classList.add("lds-ellipsis-item");
|
|
693
|
-
|
|
694
|
-
let secondEllipsis = document.createElement("div");
|
|
695
|
-
secondEllipsis.classList.add("lds-ellipsis-item");
|
|
696
|
-
|
|
697
|
-
let thirdEllipsis = document.createElement("div");
|
|
698
|
-
thirdEllipsis.classList.add("lds-ellipsis-item");
|
|
699
|
-
|
|
700
|
-
let fourthEllipsis = document.createElement("div");
|
|
701
|
-
fourthEllipsis.classList.add("lds-ellipsis-item");
|
|
702
|
-
|
|
703
|
-
loadingEllipsis.appendChild(firstEllipsis);
|
|
704
|
-
loadingEllipsis.appendChild(secondEllipsis);
|
|
705
|
-
loadingEllipsis.appendChild(thirdEllipsis);
|
|
706
|
-
loadingEllipsis.appendChild(fourthEllipsis);
|
|
707
|
-
|
|
708
|
-
return loadingEllipsis;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
function handleStreamResponse(newResponseElement, rawResponse, rawQuery, loadingEllipsis, replace=true) {
|
|
712
|
-
if (!newResponseElement) return;
|
|
713
|
-
// Remove loading ellipsis if it exists
|
|
714
|
-
if (newResponseElement.getElementsByClassName("lds-ellipsis").length > 0 && loadingEllipsis)
|
|
715
|
-
newResponseElement.removeChild(loadingEllipsis);
|
|
716
|
-
// Clear the response element if replace is true
|
|
717
|
-
if (replace) newResponseElement.innerHTML = "";
|
|
718
|
-
|
|
719
|
-
// Append response to the response element
|
|
720
|
-
newResponseElement.appendChild(formatHTMLMessage(rawResponse, false, replace, rawQuery));
|
|
721
|
-
|
|
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;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
function handleImageResponse(imageJson, rawResponse) {
|
|
729
|
-
if (imageJson.image) {
|
|
730
|
-
const inferredQuery = imageJson.inferredQueries?.[0] ?? "generated image";
|
|
731
|
-
|
|
732
|
-
// If response has image field, response is a generated image.
|
|
733
|
-
if (imageJson.intentType === "text-to-image") {
|
|
734
|
-
rawResponse += ``;
|
|
735
|
-
} else if (imageJson.intentType === "text-to-image2") {
|
|
736
|
-
rawResponse += ``;
|
|
737
|
-
} else if (imageJson.intentType === "text-to-image-v3") {
|
|
738
|
-
rawResponse = ``;
|
|
739
|
-
}
|
|
740
|
-
if (inferredQuery) {
|
|
741
|
-
rawResponse += `\n\n**Inferred Query**:\n\n${inferredQuery}`;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// If response has detail field, response is an error message.
|
|
746
|
-
if (imageJson.detail) rawResponse += imageJson.detail;
|
|
747
|
-
|
|
748
|
-
return rawResponse;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
function finalizeChatBodyResponse(references, newResponseElement) {
|
|
752
|
-
if (!!newResponseElement && references != null && Object.keys(references).length > 0) {
|
|
753
|
-
newResponseElement.appendChild(createReferenceSection(references));
|
|
754
|
-
}
|
|
755
|
-
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
|
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
|
-
}
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
function incrementalChat(event) {
|
|
880
|
-
if (!event.shiftKey && event.key === 'Enter') {
|
|
881
|
-
event.preventDefault();
|
|
882
|
-
chat();
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
function fillCommandInPrompt(command) {
|
|
887
|
-
let chatTooltip = document.getElementById("chat-tooltip");
|
|
888
|
-
chatTooltip.style.display = "none";
|
|
889
|
-
|
|
890
|
-
let chatInput = document.getElementById("chat-input");
|
|
891
|
-
chatInput.value = "/" + command + " ";
|
|
892
|
-
chatInput.classList.add("option-enabled");
|
|
893
|
-
chatInput.focus();
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
function onChatInput() {
|
|
897
|
-
let chatInput = document.getElementById("chat-input");
|
|
898
|
-
chatInput.value = chatInput.value.trimStart();
|
|
899
|
-
|
|
900
|
-
let questionStarterSuggestions = document.getElementById("question-starters");
|
|
901
|
-
questionStarterSuggestions.innerHTML = "";
|
|
902
|
-
questionStarterSuggestions.style.display = "none";
|
|
903
|
-
|
|
904
|
-
if (chatInput.value.startsWith("/") && chatInput.value.split(" ").length === 1) {
|
|
905
|
-
let chatTooltip = document.getElementById("chat-tooltip");
|
|
906
|
-
chatTooltip.style.display = "block";
|
|
907
|
-
let helpText = "<div>";
|
|
908
|
-
const command = chatInput.value.split(" ")[0].substring(1);
|
|
909
|
-
for (let key in chatOptions) {
|
|
910
|
-
if (!!!command || key.startsWith(command)) {
|
|
911
|
-
helpText += `<div class="helpoption" onclick="fillCommandInPrompt('${key}')"><b>/${key}</b>: ${chatOptions[key]}</div>`;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
chatTooltip.innerHTML = helpText;
|
|
915
|
-
} else if (chatInput.value.startsWith("/")) {
|
|
916
|
-
const firstWord = chatInput.value.split(" ")[0];
|
|
917
|
-
if (firstWord.substring(1) in chatOptions) {
|
|
918
|
-
chatInput.classList.add("option-enabled");
|
|
919
|
-
} else {
|
|
920
|
-
chatInput.classList.remove("option-enabled");
|
|
921
|
-
}
|
|
922
|
-
let chatTooltip = document.getElementById("chat-tooltip");
|
|
923
|
-
chatTooltip.style.display = "none";
|
|
924
|
-
} else {
|
|
925
|
-
let chatTooltip = document.getElementById("chat-tooltip");
|
|
926
|
-
chatTooltip.style.display = "none";
|
|
927
|
-
chatInput.classList.remove("option-enabled");
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
autoResize();
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
function autoResize() {
|
|
934
|
-
const textarea = document.getElementById('chat-input');
|
|
935
|
-
const scrollTop = textarea.scrollTop;
|
|
936
|
-
textarea.style.height = '0';
|
|
937
|
-
const scrollHeight = textarea.scrollHeight + 16; // +8 accounts for padding
|
|
938
|
-
textarea.style.height = Math.min(scrollHeight, 200) + 'px';
|
|
939
|
-
textarea.scrollTop = scrollTop;
|
|
940
|
-
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
function openFileBrowser() {
|
|
944
|
-
event.preventDefault();
|
|
945
|
-
var overlayText = document.getElementById("dropzone-overlay");
|
|
946
|
-
var dropzone = document.getElementById('chat-body');
|
|
947
|
-
|
|
948
|
-
if (overlayText == null) {
|
|
949
|
-
dropzone.classList.add('dragover');
|
|
950
|
-
var overlayText = document.createElement("div");
|
|
951
|
-
overlayText.textContent = "Select file(s) or drag + drop it here to share it with Khoj";
|
|
952
|
-
overlayText.className = "dropzone-overlay";
|
|
953
|
-
overlayText.id = "dropzone-overlay";
|
|
954
|
-
dropzone.appendChild(overlayText);
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
const fileInput = document.createElement('input');
|
|
958
|
-
fileInput.type = 'file';
|
|
959
|
-
fileInput.multiple = true;
|
|
960
|
-
fileInput.addEventListener('change', function() {
|
|
961
|
-
const selectedFiles = fileInput.files;
|
|
962
|
-
uploadDataForIndexing(selectedFiles);
|
|
963
|
-
});
|
|
964
|
-
|
|
965
|
-
// Remove overlay text after file input is closed
|
|
966
|
-
fileInput.addEventListener('blur', function() {
|
|
967
|
-
dropzone.classList.remove('dragover');
|
|
968
|
-
var overlayText = document.getElementById("dropzone-overlay");
|
|
969
|
-
if (overlayText != null) {
|
|
970
|
-
overlayText.remove();
|
|
971
|
-
}
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
// Remove overlay text if file input is cancelled
|
|
975
|
-
fileInput.addEventListener('cancel', function() {
|
|
976
|
-
dropzone.classList.remove('dragover');
|
|
977
|
-
var overlayText = document.getElementById("dropzone-overlay");
|
|
978
|
-
if (overlayText != null) {
|
|
979
|
-
overlayText.remove();
|
|
980
|
-
}
|
|
981
|
-
});
|
|
982
|
-
|
|
983
|
-
fileInput.click();
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
function uploadDataForIndexing(files) {
|
|
987
|
-
var dropzone = document.getElementById('chat-body');
|
|
988
|
-
var badfiles = [];
|
|
989
|
-
var goodfiles = [];
|
|
990
|
-
var overlayText = document.getElementById("dropzone-overlay");
|
|
991
|
-
|
|
992
|
-
for (let file of files) {
|
|
993
|
-
if (!file || (!allowedExtensions.includes(file.type) && !allowedFileEndings.includes(file.name.split('.').pop()))) {
|
|
994
|
-
if (file) {
|
|
995
|
-
badfiles.push(file.name);
|
|
996
|
-
}
|
|
997
|
-
} else {
|
|
998
|
-
goodfiles.push(file);
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
if (badfiles.length > 0) {
|
|
1003
|
-
alert("The following files are not supported yet:\n" + badfiles.join('\n'));
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
const formData = new FormData();
|
|
1008
|
-
var overlayText = document.getElementById("dropzone-overlay");
|
|
1009
|
-
if (overlayText != null) {
|
|
1010
|
-
// Display loading spinner
|
|
1011
|
-
var loadingSpinner = document.createElement("div");
|
|
1012
|
-
overlayText.textContent = "Uploading file(s) for indexing";
|
|
1013
|
-
loadingSpinner.className = "spinner";
|
|
1014
|
-
overlayText.appendChild(loadingSpinner);
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// Create an array of Promises for file reading
|
|
1018
|
-
const fileReadPromises = Array.from(goodfiles).map(file => {
|
|
1019
|
-
return new Promise((resolve, reject) => {
|
|
1020
|
-
let reader = new FileReader();
|
|
1021
|
-
reader.onload = function (event) {
|
|
1022
|
-
let fileContents = event.target.result;
|
|
1023
|
-
let fileType = file.type;
|
|
1024
|
-
let fileName = file.name;
|
|
1025
|
-
if (fileType === "") {
|
|
1026
|
-
let fileExtension = fileName.split('.').pop();
|
|
1027
|
-
if (fileExtension === "org") {
|
|
1028
|
-
fileType = "text/org";
|
|
1029
|
-
} else if (fileExtension === "md") {
|
|
1030
|
-
fileType = "text/markdown";
|
|
1031
|
-
} else if (fileExtension === "txt") {
|
|
1032
|
-
fileType = "text/plain";
|
|
1033
|
-
} else if (fileExtension === "html") {
|
|
1034
|
-
fileType = "text/html";
|
|
1035
|
-
} else if (fileExtension === "pdf") {
|
|
1036
|
-
fileType = "application/pdf";
|
|
1037
|
-
} else if (fileExtension === "jpg" || fileExtension === "jpeg"){
|
|
1038
|
-
fileType = "image/jpeg";
|
|
1039
|
-
} else if (fileExtension === "png") {
|
|
1040
|
-
fileType = "image/png";
|
|
1041
|
-
}
|
|
1042
|
-
else {
|
|
1043
|
-
// Skip this file if its type is not supported
|
|
1044
|
-
resolve();
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
let fileObj = new Blob([fileContents], { type: fileType });
|
|
1050
|
-
formData.append("files", fileObj, file.name);
|
|
1051
|
-
resolve();
|
|
1052
|
-
};
|
|
1053
|
-
reader.onerror = reject;
|
|
1054
|
-
reader.readAsArrayBuffer(file);
|
|
1055
|
-
});
|
|
1056
|
-
});
|
|
1057
|
-
|
|
1058
|
-
// Wait for all files to be read before making the fetch request
|
|
1059
|
-
Promise.all(fileReadPromises)
|
|
1060
|
-
.then(() => {
|
|
1061
|
-
return fetch("/api/content?client=web", {
|
|
1062
|
-
method: "PATCH",
|
|
1063
|
-
body: formData,
|
|
1064
|
-
});
|
|
1065
|
-
})
|
|
1066
|
-
.then((data) => {
|
|
1067
|
-
console.log(data);
|
|
1068
|
-
dropzone.classList.remove('dragover');
|
|
1069
|
-
var overlayText = document.getElementById("dropzone-overlay");
|
|
1070
|
-
if (overlayText != null) {
|
|
1071
|
-
overlayText.remove();
|
|
1072
|
-
}
|
|
1073
|
-
// Display indexing success message
|
|
1074
|
-
flashStatusInChatInput("✅ File indexed successfully");
|
|
1075
|
-
renderAllFiles();
|
|
1076
|
-
for (let file of goodfiles) {
|
|
1077
|
-
addFileFilterToConversation(file.name);
|
|
1078
|
-
loadFileFiltersFromConversation();
|
|
1079
|
-
}
|
|
1080
|
-
})
|
|
1081
|
-
.catch((error) => {
|
|
1082
|
-
console.log(error);
|
|
1083
|
-
dropzone.classList.remove('dragover');
|
|
1084
|
-
var overlayText = document.getElementById("dropzone-overlay");
|
|
1085
|
-
if (overlayText != null) {
|
|
1086
|
-
overlayText.remove();
|
|
1087
|
-
}
|
|
1088
|
-
// Display indexing failure message
|
|
1089
|
-
flashStatusInChatInput("⛔️ Failed to upload file for indexing");
|
|
1090
|
-
});
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
function setupDropZone() {
|
|
1095
|
-
var dropzone = document.getElementById('chat-body');
|
|
1096
|
-
|
|
1097
|
-
dropzone.ondragover = function(event) {
|
|
1098
|
-
event.preventDefault();
|
|
1099
|
-
this.classList.add('dragover');
|
|
1100
|
-
var overlayText = document.getElementById("dropzone-overlay");
|
|
1101
|
-
console.log("ondragover triggered");
|
|
1102
|
-
|
|
1103
|
-
if (overlayText == null) {
|
|
1104
|
-
var overlayText = document.createElement("div");
|
|
1105
|
-
overlayText.textContent = "Drop file to share it with Khoj";
|
|
1106
|
-
overlayText.className = "dropzone-overlay";
|
|
1107
|
-
overlayText.id = "dropzone-overlay";
|
|
1108
|
-
this.appendChild(overlayText);
|
|
1109
|
-
}
|
|
1110
|
-
};
|
|
1111
|
-
|
|
1112
|
-
dropzone.ondragleave = function(event) {
|
|
1113
|
-
event.preventDefault();
|
|
1114
|
-
this.classList.remove('dragover');
|
|
1115
|
-
console.log("ondragleave triggered");
|
|
1116
|
-
var overlayText = document.getElementById("dropzone-overlay");
|
|
1117
|
-
if (overlayText != null) {
|
|
1118
|
-
overlayText.remove();
|
|
1119
|
-
}
|
|
1120
|
-
};
|
|
1121
|
-
|
|
1122
|
-
dropzone.ondrop = function(event) {
|
|
1123
|
-
event.preventDefault();
|
|
1124
|
-
|
|
1125
|
-
var file = event.dataTransfer.files[0];
|
|
1126
|
-
uploadDataForIndexing([file]);
|
|
1127
|
-
};
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
window.onload = loadChat;
|
|
1131
|
-
|
|
1132
|
-
function initMessageState(isVoice=false) {
|
|
1133
|
-
if (waitingForLocation) {
|
|
1134
|
-
console.debug("Waiting for location data to be fetched. Will setup WebSocket once location data is available.");
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
chatMessageState = {
|
|
1139
|
-
newResponseTextEl: null,
|
|
1140
|
-
newResponseEl: null,
|
|
1141
|
-
loadingEllipsis: null,
|
|
1142
|
-
references: {},
|
|
1143
|
-
rawResponse: "",
|
|
1144
|
-
rawQuery: "",
|
|
1145
|
-
isVoice: isVoice,
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
var userMessages = [];
|
|
1150
|
-
var userMessageIndex = -1;
|
|
1151
|
-
function loadChat() {
|
|
1152
|
-
let chatBody = document.getElementById("chat-body");
|
|
1153
|
-
chatBody.innerHTML = "";
|
|
1154
|
-
chatBody.classList.add("relative-position");
|
|
1155
|
-
let chatHistoryUrl = `/api/chat/history?client=web`;
|
|
1156
|
-
if (chatBody.dataset.conversationId) {
|
|
1157
|
-
chatHistoryUrl += `&conversation_id=${chatBody.dataset.conversationId}`;
|
|
1158
|
-
initMessageState();
|
|
1159
|
-
loadFileFiltersFromConversation();
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
if (window.screen.width < 700) {
|
|
1163
|
-
handleCollapseSidePanel();
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
// Create loading screen and add it to chat-body
|
|
1167
|
-
let loadingScreen = document.createElement('div');
|
|
1168
|
-
loadingScreen.classList.add("loading-spinner");
|
|
1169
|
-
let yellowOrb = document.createElement('div');
|
|
1170
|
-
loadingScreen.appendChild(yellowOrb);
|
|
1171
|
-
chatBody.appendChild(loadingScreen);
|
|
1172
|
-
|
|
1173
|
-
// Get the most recent 10 chat messages from conversation history
|
|
1174
|
-
fetch(`${chatHistoryUrl}&n=10`, { method: "GET" })
|
|
1175
|
-
.then(response => response.json())
|
|
1176
|
-
.then(data => {
|
|
1177
|
-
if (data.detail) {
|
|
1178
|
-
// If the server returns a 500 error with detail, render a setup hint.
|
|
1179
|
-
let setupMsg = "Hi 👋🏾, to start chatting add available chat models options via <a class='inline-chat-link' href='/server/admin'>the Django Admin panel</a> on the Server";
|
|
1180
|
-
renderMessage(setupMsg, "khoj", null, null, true);
|
|
1181
|
-
|
|
1182
|
-
// Disable chat input field and update placeholder text
|
|
1183
|
-
document.getElementById("chat-input").setAttribute("disabled", "disabled");
|
|
1184
|
-
document.getElementById("chat-input").setAttribute("placeholder", "Configure Khoj to enable chat");
|
|
1185
|
-
} else if (data.status != "ok") {
|
|
1186
|
-
throw new Error(data.message);
|
|
1187
|
-
} else {
|
|
1188
|
-
// Set welcome message on load
|
|
1189
|
-
renderMessage(welcome_message, "khoj");
|
|
1190
|
-
}
|
|
1191
|
-
return data.response;
|
|
1192
|
-
})
|
|
1193
|
-
.then(response => {
|
|
1194
|
-
// Render conversation history, if any
|
|
1195
|
-
let chatBody = document.getElementById("chat-body");
|
|
1196
|
-
chatBody.dataset.conversationId = response.conversation_id;
|
|
1197
|
-
loadFileFiltersFromConversation();
|
|
1198
|
-
initMessageState();
|
|
1199
|
-
chatBody.dataset.conversationTitle = response.slug || `New conversation 🌱`;
|
|
1200
|
-
|
|
1201
|
-
let agentMetadata = response.agent;
|
|
1202
|
-
if (agentMetadata) {
|
|
1203
|
-
chatBody.innerHTML = "";
|
|
1204
|
-
let agentName = agentMetadata.name;
|
|
1205
|
-
let agentAvatar = agentMetadata.avatar;
|
|
1206
|
-
let agentOwnedByUser = agentMetadata.isCreator;
|
|
1207
|
-
|
|
1208
|
-
let agentAvatarElement = document.getElementById("agent-avatar");
|
|
1209
|
-
let agentNameElement = document.getElementById("agent-name");
|
|
1210
|
-
|
|
1211
|
-
let agentLinkElement = document.getElementById("agent-link");
|
|
1212
|
-
|
|
1213
|
-
agentAvatarElement.src = agentAvatar;
|
|
1214
|
-
agentNameElement.textContent = agentName;
|
|
1215
|
-
agentLinkElement.setAttribute("href", `/agent/${agentMetadata.slug}`);
|
|
1216
|
-
renderMessage(`Hello! I'm [${agentName}](/agent/${agentMetadata.slug}). I can:
|
|
1217
|
-
- 🧠 Answer general knowledge questions
|
|
1218
|
-
- 🔎 Get real-time answers from the internet
|
|
1219
|
-
- 📜 Find relevant info in your notes & documents
|
|
1220
|
-
- 💡 Be a sounding board for your ideas
|
|
1221
|
-
- 🌄 Generate images based on your context
|
|
1222
|
-
- 🎙️ Hear you talk (use the mic by the input box to say your message out loud)
|
|
1223
|
-
- 📚 Understand files you drag & drop here
|
|
1224
|
-
- 👩🏾🚀 Be tuned to your conversation needs via [agents](./agents)
|
|
1225
|
-
|
|
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/).
|
|
1227
|
-
|
|
1228
|
-
To get started, just start typing below. You can also type / to see a list of commands.
|
|
1229
|
-
|
|
1230
|
-
**What's on your mind today?**
|
|
1231
|
-
`, "khoj")
|
|
1232
|
-
|
|
1233
|
-
if (agentOwnedByUser) {
|
|
1234
|
-
let agentOwnedByUserElement = document.getElementById("agent-owned-by-user");
|
|
1235
|
-
agentOwnedByUserElement.style.display = "block";
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
let agentMetadataElement = document.getElementById("agent-metadata");
|
|
1239
|
-
agentMetadataElement.style.display = "block";
|
|
1240
|
-
} else {
|
|
1241
|
-
let agentMetadataElement = document.getElementById("agent-metadata");
|
|
1242
|
-
agentMetadataElement.style.display = "none";
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
// Create a new IntersectionObserver
|
|
1246
|
-
let fetchRemainingMessagesObserver = new IntersectionObserver((entries, observer) => {
|
|
1247
|
-
entries.forEach(entry => {
|
|
1248
|
-
// If the element is in the viewport, fetch the remaining message and unobserve the element
|
|
1249
|
-
if (entry.isIntersecting) {
|
|
1250
|
-
fetchRemainingChatMessages(chatHistoryUrl);
|
|
1251
|
-
observer.unobserve(entry.target);
|
|
1252
|
-
}
|
|
1253
|
-
});
|
|
1254
|
-
}, {rootMargin: '0px 0px 0px 0px'});
|
|
1255
|
-
|
|
1256
|
-
const fullChatLog = response.chat || [];
|
|
1257
|
-
userMessages = [];
|
|
1258
|
-
userMessageIndex = 0;
|
|
1259
|
-
fullChatLog.forEach((chat_log, index) => {
|
|
1260
|
-
// Render the last 10 messages immediately
|
|
1261
|
-
// also cache user messages into array for shortcut access
|
|
1262
|
-
if (chat_log.message != null) {
|
|
1263
|
-
if(chat_log.by !== "khoj") {
|
|
1264
|
-
userMessages.push(chat_log.message);
|
|
1265
|
-
}
|
|
1266
|
-
let messageElement = renderMessageWithReference(
|
|
1267
|
-
chat_log.message,
|
|
1268
|
-
chat_log.by,
|
|
1269
|
-
chat_log.context,
|
|
1270
|
-
new Date(chat_log.created + "Z"),
|
|
1271
|
-
chat_log.onlineContext,
|
|
1272
|
-
chat_log.intent?.type,
|
|
1273
|
-
chat_log.intent?.["inferred-queries"],
|
|
1274
|
-
chat_log.intent?.query);
|
|
1275
|
-
chatBody.appendChild(messageElement);
|
|
1276
|
-
|
|
1277
|
-
// When the 4th oldest message is within viewing distance (~60% scroll up)
|
|
1278
|
-
// Fetch the remaining chat messages
|
|
1279
|
-
if (index === 4) {
|
|
1280
|
-
fetchRemainingMessagesObserver.observe(messageElement);
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
loadingScreen.style.height = chatBody.scrollHeight + 'px';
|
|
1284
|
-
});
|
|
1285
|
-
userMessageIndex = userMessages.length;
|
|
1286
|
-
|
|
1287
|
-
// Scroll to bottom of chat-body element
|
|
1288
|
-
chatBody.scrollTop = chatBody.scrollHeight;
|
|
1289
|
-
|
|
1290
|
-
// Set height of chat-body element to the height of the chat-body-wrapper
|
|
1291
|
-
let chatBodyWrapper = document.getElementById("chat-body-wrapper");
|
|
1292
|
-
let chatBodyWrapperHeight = chatBodyWrapper.clientHeight;
|
|
1293
|
-
chatBody.style.height = chatBodyWrapperHeight;
|
|
1294
|
-
|
|
1295
|
-
// Add fade out animation to loading screen and remove it after the animation ends
|
|
1296
|
-
setTimeout(() => {
|
|
1297
|
-
loadingScreen.remove();
|
|
1298
|
-
chatBody.classList.remove("relative-position");
|
|
1299
|
-
setupDropZone();
|
|
1300
|
-
}, 500);
|
|
1301
|
-
|
|
1302
|
-
})
|
|
1303
|
-
.catch(err => {
|
|
1304
|
-
console.log(err);
|
|
1305
|
-
return;
|
|
1306
|
-
});
|
|
1307
|
-
|
|
1308
|
-
refreshChatSessionsPanel();
|
|
1309
|
-
|
|
1310
|
-
fetch('/api/chat/options')
|
|
1311
|
-
.then(response => response.json())
|
|
1312
|
-
.then(data => {
|
|
1313
|
-
// Render chat options, if any
|
|
1314
|
-
if (data) {
|
|
1315
|
-
chatOptions = data;
|
|
1316
|
-
}
|
|
1317
|
-
})
|
|
1318
|
-
.catch(err => {
|
|
1319
|
-
return;
|
|
1320
|
-
});
|
|
1321
|
-
|
|
1322
|
-
fetch('/api/chat/starters')
|
|
1323
|
-
.then(response => response.json())
|
|
1324
|
-
.then(data => {
|
|
1325
|
-
// Render chat options, if any
|
|
1326
|
-
if (data.length > 0) {
|
|
1327
|
-
let questionStarterSuggestions = document.getElementById("question-starters");
|
|
1328
|
-
questionStarterSuggestions.innerHTML = "";
|
|
1329
|
-
data.forEach((questionStarter) => {
|
|
1330
|
-
let questionStarterButton = document.createElement('button');
|
|
1331
|
-
questionStarterButton.textContent = questionStarter;
|
|
1332
|
-
questionStarterButton.classList.add("question-starter");
|
|
1333
|
-
questionStarterButton.addEventListener('click', function() {
|
|
1334
|
-
questionStarterSuggestions.style.display = "none";
|
|
1335
|
-
document.getElementById("chat-input").value = questionStarter;
|
|
1336
|
-
chat();
|
|
1337
|
-
});
|
|
1338
|
-
questionStarterSuggestions.appendChild(questionStarterButton);
|
|
1339
|
-
});
|
|
1340
|
-
questionStarterSuggestions.style.display = "grid";
|
|
1341
|
-
}
|
|
1342
|
-
})
|
|
1343
|
-
.catch(err => {
|
|
1344
|
-
return;
|
|
1345
|
-
});
|
|
1346
|
-
|
|
1347
|
-
// Fill query field with value passed in URL query parameters, if any.
|
|
1348
|
-
var query_via_url = new URLSearchParams(window.location.search).get("q");
|
|
1349
|
-
if (query_via_url) {
|
|
1350
|
-
document.getElementById("chat-input").value = query_via_url;
|
|
1351
|
-
chat();
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
function fetchRemainingChatMessages(chatHistoryUrl) {
|
|
1356
|
-
// Create a new IntersectionObserver
|
|
1357
|
-
let observer = new IntersectionObserver((entries, observer) => {
|
|
1358
|
-
entries.forEach(entry => {
|
|
1359
|
-
// If the element is in the viewport, render the message and unobserve the element
|
|
1360
|
-
if (entry.isIntersecting) {
|
|
1361
|
-
let chat_log = entry.target.chat_log;
|
|
1362
|
-
let messageElement = renderMessageWithReference(
|
|
1363
|
-
chat_log.message,
|
|
1364
|
-
chat_log.by,
|
|
1365
|
-
chat_log.context,
|
|
1366
|
-
new Date(chat_log.created + "Z"),
|
|
1367
|
-
chat_log.onlineContext,
|
|
1368
|
-
chat_log.intent?.type,
|
|
1369
|
-
chat_log.intent?.["inferred-queries"],
|
|
1370
|
-
chat_log.intent?.query
|
|
1371
|
-
);
|
|
1372
|
-
entry.target.replaceWith(messageElement);
|
|
1373
|
-
|
|
1374
|
-
// Remove the observer after the element has been rendered
|
|
1375
|
-
observer.unobserve(entry.target);
|
|
1376
|
-
}
|
|
1377
|
-
});
|
|
1378
|
-
}, {rootMargin: '0px 0px 200px 0px'}); // Trigger when the element is within 200px of the viewport
|
|
1379
|
-
|
|
1380
|
-
// Fetch remaining chat messages from conversation history
|
|
1381
|
-
fetch(`${chatHistoryUrl}&n=-10`, { method: "GET" })
|
|
1382
|
-
.then(response => response.json())
|
|
1383
|
-
.then(data => {
|
|
1384
|
-
if (data.status != "ok") {
|
|
1385
|
-
throw new Error(data.message);
|
|
1386
|
-
}
|
|
1387
|
-
return data.response;
|
|
1388
|
-
})
|
|
1389
|
-
.then(response => {
|
|
1390
|
-
const fullChatLog = response.chat || [];
|
|
1391
|
-
let chatBody = document.getElementById("chat-body");
|
|
1392
|
-
fullChatLog
|
|
1393
|
-
.reverse()
|
|
1394
|
-
.forEach(chat_log => {
|
|
1395
|
-
if (chat_log.message != null) {
|
|
1396
|
-
// Create a new element for each chat log
|
|
1397
|
-
let placeholder = document.createElement('div');
|
|
1398
|
-
placeholder.chat_log = chat_log;
|
|
1399
|
-
|
|
1400
|
-
// Insert the message placeholder as the first child of chat body after the welcome message
|
|
1401
|
-
chatBody.insertBefore(placeholder, chatBody.firstChild.nextSibling);
|
|
1402
|
-
|
|
1403
|
-
// Observe the element
|
|
1404
|
-
placeholder.style.height = "20px";
|
|
1405
|
-
observer.observe(placeholder);
|
|
1406
|
-
}
|
|
1407
|
-
});
|
|
1408
|
-
})
|
|
1409
|
-
.catch(err => {
|
|
1410
|
-
console.log(err);
|
|
1411
|
-
return;
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
function flashStatusInChatInput(message) {
|
|
1416
|
-
// Get chat input element and original placeholder
|
|
1417
|
-
let chatInput = document.getElementById("chat-input");
|
|
1418
|
-
let originalPlaceholder = chatInput.placeholder;
|
|
1419
|
-
// Set placeholder to message
|
|
1420
|
-
chatInput.placeholder = message;
|
|
1421
|
-
// Reset placeholder after 2 seconds
|
|
1422
|
-
setTimeout(() => {
|
|
1423
|
-
chatInput.placeholder = originalPlaceholder;
|
|
1424
|
-
}, 2000);
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
function createNewConversation() {
|
|
1428
|
-
// Create a modal that appears in the middle of the entire screen. It should have a form to create a new conversation.
|
|
1429
|
-
let modal = document.createElement('div');
|
|
1430
|
-
modal.classList.add("modal");
|
|
1431
|
-
modal.id = "new-conversation-modal";
|
|
1432
|
-
let modalContent = document.createElement('div');
|
|
1433
|
-
modalContent.classList.add("modal-content");
|
|
1434
|
-
let modalHeader = document.createElement('div');
|
|
1435
|
-
modalHeader.classList.add("modal-header");
|
|
1436
|
-
let modalTitle = document.createElement('h2');
|
|
1437
|
-
modalTitle.textContent = "New Conversation";
|
|
1438
|
-
let modalCloseButton = document.createElement('button');
|
|
1439
|
-
modalCloseButton.classList.add("modal-close-button");
|
|
1440
|
-
modalCloseButton.innerHTML = "×";
|
|
1441
|
-
modalCloseButton.addEventListener('click', function() {
|
|
1442
|
-
modal.remove();
|
|
1443
|
-
});
|
|
1444
|
-
modalHeader.appendChild(modalTitle);
|
|
1445
|
-
modalHeader.appendChild(modalCloseButton);
|
|
1446
|
-
modalContent.appendChild(modalHeader);
|
|
1447
|
-
let modalBody = document.createElement('div');
|
|
1448
|
-
modalBody.classList.add("modal-body");
|
|
1449
|
-
|
|
1450
|
-
let agentDropDownPicker = document.createElement('select');
|
|
1451
|
-
agentDropDownPicker.setAttribute("id", "agent-dropdown-picker");
|
|
1452
|
-
agentDropDownPicker.setAttribute("name", "agent-dropdown-picker");
|
|
1453
|
-
|
|
1454
|
-
let agentDropDownLabel = document.createElement('label');
|
|
1455
|
-
agentDropDownLabel.setAttribute("for", "agent-dropdown-picker");
|
|
1456
|
-
agentDropDownLabel.textContent = "Who do you want to talk to?";
|
|
1457
|
-
|
|
1458
|
-
fetch('/api/agents')
|
|
1459
|
-
.then(response => response.json())
|
|
1460
|
-
.then(data => {
|
|
1461
|
-
if (data.length > 0) {
|
|
1462
|
-
data.forEach((agent) => {
|
|
1463
|
-
let agentOption = document.createElement('option');
|
|
1464
|
-
agentOption.setAttribute("value", agent.slug);
|
|
1465
|
-
agentOption.textContent = agent.name;
|
|
1466
|
-
agentDropDownPicker.appendChild(agentOption);
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
})
|
|
1470
|
-
.catch(err => {
|
|
1471
|
-
return;
|
|
1472
|
-
});
|
|
1473
|
-
|
|
1474
|
-
let seeAllAgentsLink = document.createElement('a');
|
|
1475
|
-
seeAllAgentsLink.setAttribute("href", "/agents");
|
|
1476
|
-
seeAllAgentsLink.setAttribute("target", "_blank");
|
|
1477
|
-
seeAllAgentsLink.textContent = "See all agents";
|
|
1478
|
-
|
|
1479
|
-
let newConversationSubmitButton = document.createElement('button');
|
|
1480
|
-
newConversationSubmitButton.setAttribute("type", "submit");
|
|
1481
|
-
newConversationSubmitButton.textContent = "Go";
|
|
1482
|
-
newConversationSubmitButton.id = "new-conversation-submit-button";
|
|
1483
|
-
|
|
1484
|
-
newConversationSubmitButton.addEventListener('click', function(event) {
|
|
1485
|
-
event.preventDefault();
|
|
1486
|
-
let agentSlug = agentDropDownPicker.value;
|
|
1487
|
-
let createURL = `/api/chat/sessions?client=web&agent_slug=${agentSlug}`;
|
|
1488
|
-
let chatBody = document.getElementById("chat-body");
|
|
1489
|
-
fetch(createURL, { method: "POST" })
|
|
1490
|
-
.then(response => response.json())
|
|
1491
|
-
.then(data => {
|
|
1492
|
-
chatBody.dataset.conversationId = data.conversation_id;
|
|
1493
|
-
modal.remove();
|
|
1494
|
-
loadChat();
|
|
1495
|
-
})
|
|
1496
|
-
.catch(err => {
|
|
1497
|
-
return;
|
|
1498
|
-
});
|
|
1499
|
-
});
|
|
1500
|
-
|
|
1501
|
-
let closeButton = document.createElement('button');
|
|
1502
|
-
closeButton.id = "close-button";
|
|
1503
|
-
closeButton.textContent = "Close";
|
|
1504
|
-
closeButton.classList.add("close-button");
|
|
1505
|
-
closeButton.addEventListener('click', function() {
|
|
1506
|
-
modal.remove();
|
|
1507
|
-
});
|
|
1508
|
-
|
|
1509
|
-
modalBody.appendChild(agentDropDownLabel);
|
|
1510
|
-
modalBody.appendChild(agentDropDownPicker);
|
|
1511
|
-
modalBody.appendChild(seeAllAgentsLink);
|
|
1512
|
-
|
|
1513
|
-
let modalFooter = document.createElement('div');
|
|
1514
|
-
modalFooter.classList.add("modal-footer");
|
|
1515
|
-
modalFooter.appendChild(closeButton);
|
|
1516
|
-
modalFooter.appendChild(newConversationSubmitButton);
|
|
1517
|
-
modalBody.appendChild(modalFooter);
|
|
1518
|
-
|
|
1519
|
-
modalContent.appendChild(modalBody);
|
|
1520
|
-
modal.appendChild(modalContent);
|
|
1521
|
-
document.body.appendChild(modal);
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
function refreshChatSessionsPanel() {
|
|
1525
|
-
fetch('/api/chat/sessions', { method: "GET" })
|
|
1526
|
-
.then(response => response.json())
|
|
1527
|
-
.then(data => {
|
|
1528
|
-
let conversationListBody = document.getElementById("conversation-list-body");
|
|
1529
|
-
conversationListBody.innerHTML = "";
|
|
1530
|
-
let conversationListBodyHeader = document.getElementById("conversation-list-header");
|
|
1531
|
-
|
|
1532
|
-
let chatBody = document.getElementById("chat-body");
|
|
1533
|
-
conversationId = chatBody.dataset.conversationId;
|
|
1534
|
-
|
|
1535
|
-
if (data.length > 0) {
|
|
1536
|
-
conversationListBodyHeader.style.display = "inline-flex";
|
|
1537
|
-
for (let index in data) {
|
|
1538
|
-
let conversation = data[index];
|
|
1539
|
-
let conversationButton = document.createElement('div');
|
|
1540
|
-
let incomingConversationId = conversation["conversation_id"];
|
|
1541
|
-
const conversationTitle = conversation["slug"] || `New conversation 🌱`;
|
|
1542
|
-
conversationButton.textContent = conversationTitle;
|
|
1543
|
-
conversationButton.classList.add("conversation-button");
|
|
1544
|
-
if (incomingConversationId == conversationId) {
|
|
1545
|
-
conversationButton.classList.add("selected-conversation");
|
|
1546
|
-
}
|
|
1547
|
-
conversationButton.addEventListener('click', function() {
|
|
1548
|
-
let chatBody = document.getElementById("chat-body");
|
|
1549
|
-
chatBody.innerHTML = "";
|
|
1550
|
-
chatBody.dataset.conversationId = incomingConversationId;
|
|
1551
|
-
chatBody.dataset.conversationTitle = conversationTitle;
|
|
1552
|
-
loadChat();
|
|
1553
|
-
});
|
|
1554
|
-
let threeDotMenu = document.createElement('div');
|
|
1555
|
-
threeDotMenu.classList.add("three-dot-menu");
|
|
1556
|
-
let threeDotMenuButton = document.createElement('button');
|
|
1557
|
-
threeDotMenuButton.textContent = "⋮";
|
|
1558
|
-
threeDotMenuButton.classList.add("three-dot-menu-button");
|
|
1559
|
-
threeDotMenuButton.addEventListener('click', function(event) {
|
|
1560
|
-
event.stopPropagation();
|
|
1561
|
-
|
|
1562
|
-
let existingChildren = threeDotMenu.children;
|
|
1563
|
-
|
|
1564
|
-
if (existingChildren.length > 1) {
|
|
1565
|
-
// Skip deleting the first, since that's the menu button.
|
|
1566
|
-
for (let i = 1; i < existingChildren.length; i++) {
|
|
1567
|
-
existingChildren[i].remove();
|
|
1568
|
-
}
|
|
1569
|
-
return;
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
let conversationMenu = document.createElement('div');
|
|
1573
|
-
conversationMenu.classList.add("conversation-menu");
|
|
1574
|
-
|
|
1575
|
-
let editTitleButton = document.createElement('button');
|
|
1576
|
-
editTitleButton.textContent = "Rename";
|
|
1577
|
-
editTitleButton.classList.add("edit-title-button");
|
|
1578
|
-
editTitleButton.classList.add("three-dot-menu-button-item");
|
|
1579
|
-
editTitleButton.addEventListener('click', function(event) {
|
|
1580
|
-
event.stopPropagation();
|
|
1581
|
-
|
|
1582
|
-
let conversationMenuChildren = conversationMenu.children;
|
|
1583
|
-
|
|
1584
|
-
let totalItems = conversationMenuChildren.length;
|
|
1585
|
-
|
|
1586
|
-
for (let i = totalItems - 1; i >= 0; i--) {
|
|
1587
|
-
conversationMenuChildren[i].remove();
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
// Create a dialog box to get new title for conversation
|
|
1591
|
-
let conversationTitleInputBox = document.createElement('div');
|
|
1592
|
-
conversationTitleInputBox.classList.add("conversation-title-input-box");
|
|
1593
|
-
let conversationTitleInput = document.createElement('input');
|
|
1594
|
-
conversationTitleInput.classList.add("conversation-title-input");
|
|
1595
|
-
|
|
1596
|
-
conversationTitleInput.value = conversationTitle;
|
|
1597
|
-
|
|
1598
|
-
conversationTitleInput.addEventListener('click', function(event) {
|
|
1599
|
-
event.stopPropagation();
|
|
1600
|
-
});
|
|
1601
|
-
conversationTitleInput.addEventListener('keydown', function(event) {
|
|
1602
|
-
if (event.key === "Enter") {
|
|
1603
|
-
event.preventDefault();
|
|
1604
|
-
conversationTitleInputButton.click();
|
|
1605
|
-
}
|
|
1606
|
-
});
|
|
1607
|
-
|
|
1608
|
-
conversationTitleInputBox.appendChild(conversationTitleInput);
|
|
1609
|
-
let conversationTitleInputButton = document.createElement('button');
|
|
1610
|
-
conversationTitleInputButton.textContent = "Save";
|
|
1611
|
-
conversationTitleInputButton.classList.add("three-dot-menu-button-item");
|
|
1612
|
-
conversationTitleInputButton.addEventListener('click', function(event) {
|
|
1613
|
-
event.stopPropagation();
|
|
1614
|
-
let newTitle = conversationTitleInput.value;
|
|
1615
|
-
if (newTitle != null) {
|
|
1616
|
-
let editURL = `/api/chat/title?client=web&conversation_id=${incomingConversationId}&title=${newTitle}`;
|
|
1617
|
-
fetch(editURL , { method: "PATCH" })
|
|
1618
|
-
.then(response => response.ok ? response.json() : Promise.reject(response))
|
|
1619
|
-
.then(data => {
|
|
1620
|
-
conversationButton.textContent = newTitle;
|
|
1621
|
-
})
|
|
1622
|
-
.catch(err => {
|
|
1623
|
-
return;
|
|
1624
|
-
});
|
|
1625
|
-
conversationTitleInputBox.remove();
|
|
1626
|
-
}});
|
|
1627
|
-
conversationTitleInputBox.appendChild(conversationTitleInputButton);
|
|
1628
|
-
conversationMenu.appendChild(conversationTitleInputBox);
|
|
1629
|
-
});
|
|
1630
|
-
conversationMenu.appendChild(editTitleButton);
|
|
1631
|
-
threeDotMenu.appendChild(conversationMenu);
|
|
1632
|
-
|
|
1633
|
-
let shareButton = document.createElement('button');
|
|
1634
|
-
shareButton.textContent = "Share";
|
|
1635
|
-
shareButton.type = "button";
|
|
1636
|
-
shareButton.classList.add("share-conversation-button");
|
|
1637
|
-
shareButton.classList.add("three-dot-menu-button-item");
|
|
1638
|
-
shareButton.addEventListener('click', function(event) {
|
|
1639
|
-
event.preventDefault();
|
|
1640
|
-
let confirmation = confirm('Are you sure you want to share this chat session? This will make the conversation public.');
|
|
1641
|
-
if (!confirmation) return;
|
|
1642
|
-
let duplicateURL = `/api/chat/share?client=web&conversation_id=${incomingConversationId}`;
|
|
1643
|
-
fetch(duplicateURL , { method: "POST" })
|
|
1644
|
-
.then(response => response.ok ? response.json() : Promise.reject(response))
|
|
1645
|
-
.then(data => {
|
|
1646
|
-
if (data.status == "ok") {
|
|
1647
|
-
flashStatusInChatInput("✅ Conversation shared successfully");
|
|
1648
|
-
}
|
|
1649
|
-
// Make a pop-up that shows data.url to share the conversation
|
|
1650
|
-
let shareURL = data.url;
|
|
1651
|
-
let shareModal = document.createElement('div');
|
|
1652
|
-
shareModal.classList.add("modal");
|
|
1653
|
-
shareModal.id = "share-conversation-modal";
|
|
1654
|
-
let shareModalContent = document.createElement('div');
|
|
1655
|
-
shareModalContent.classList.add("modal-content");
|
|
1656
|
-
let shareModalHeader = document.createElement('div');
|
|
1657
|
-
shareModalHeader.classList.add("modal-header");
|
|
1658
|
-
let shareModalTitle = document.createElement('h2');
|
|
1659
|
-
shareModalTitle.textContent = "Share Conversation";
|
|
1660
|
-
let shareModalCloseButton = document.createElement('button');
|
|
1661
|
-
shareModalCloseButton.classList.add("modal-close-button");
|
|
1662
|
-
shareModalCloseButton.innerHTML = "×";
|
|
1663
|
-
shareModalCloseButton.addEventListener('click', function() {
|
|
1664
|
-
shareModal.remove();
|
|
1665
|
-
});
|
|
1666
|
-
shareModalHeader.appendChild(shareModalTitle);
|
|
1667
|
-
shareModalHeader.appendChild(shareModalCloseButton);
|
|
1668
|
-
shareModalContent.appendChild(shareModalHeader);
|
|
1669
|
-
let shareModalBody = document.createElement('div');
|
|
1670
|
-
shareModalBody.classList.add("modal-body");
|
|
1671
|
-
let shareModalText = document.createElement('p');
|
|
1672
|
-
shareModalText.textContent = "The link has been copied to your clipboard. Use it to share your conversation with others!";
|
|
1673
|
-
let shareModalLink = document.createElement('input');
|
|
1674
|
-
shareModalLink.setAttribute("value", shareURL);
|
|
1675
|
-
shareModalLink.setAttribute("readonly", "");
|
|
1676
|
-
shareModalLink.classList.add("share-link");
|
|
1677
|
-
let copyButton = document.createElement('button');
|
|
1678
|
-
copyButton.textContent = "Copy";
|
|
1679
|
-
copyButton.addEventListener('click', function() {
|
|
1680
|
-
shareModalLink.select();
|
|
1681
|
-
document.execCommand('copy');
|
|
1682
|
-
});
|
|
1683
|
-
copyButton.id = "copy-share-url-button";
|
|
1684
|
-
shareModalBody.appendChild(shareModalText);
|
|
1685
|
-
shareModalBody.appendChild(shareModalLink);
|
|
1686
|
-
shareModalBody.appendChild(copyButton);
|
|
1687
|
-
shareModalContent.appendChild(shareModalBody);
|
|
1688
|
-
shareModal.appendChild(shareModalContent);
|
|
1689
|
-
document.body.appendChild(shareModal);
|
|
1690
|
-
shareModalLink.select();
|
|
1691
|
-
document.execCommand('copy');
|
|
1692
|
-
})
|
|
1693
|
-
.catch(err => {
|
|
1694
|
-
return;
|
|
1695
|
-
});
|
|
1696
|
-
});
|
|
1697
|
-
conversationMenu.appendChild(shareButton);
|
|
1698
|
-
|
|
1699
|
-
let deleteButton = document.createElement('button');
|
|
1700
|
-
deleteButton.type = "button";
|
|
1701
|
-
deleteButton.textContent = "Delete";
|
|
1702
|
-
deleteButton.classList.add("delete-conversation-button");
|
|
1703
|
-
deleteButton.classList.add("three-dot-menu-button-item");
|
|
1704
|
-
deleteButton.addEventListener('click', function(event) {
|
|
1705
|
-
event.preventDefault();
|
|
1706
|
-
// Ask for confirmation before deleting chat session
|
|
1707
|
-
let confirmation = confirm('Are you sure you want to delete this chat session?');
|
|
1708
|
-
if (!confirmation) return;
|
|
1709
|
-
let deleteURL = `/api/chat/history?client=web&conversation_id=${incomingConversationId}`;
|
|
1710
|
-
fetch(deleteURL , { method: "DELETE" })
|
|
1711
|
-
.then(response => response.ok ? response.json() : Promise.reject(response))
|
|
1712
|
-
.then(data => {
|
|
1713
|
-
let chatBody = document.getElementById("chat-body");
|
|
1714
|
-
chatBody.innerHTML = "";
|
|
1715
|
-
chatBody.dataset.conversationId = "";
|
|
1716
|
-
chatBody.dataset.conversationTitle = "";
|
|
1717
|
-
loadChat();
|
|
1718
|
-
})
|
|
1719
|
-
.catch(err => {
|
|
1720
|
-
flashStatusInChatInput("⛔️ Failed to clear conversation history");
|
|
1721
|
-
});
|
|
1722
|
-
});
|
|
1723
|
-
conversationMenu.appendChild(deleteButton);
|
|
1724
|
-
threeDotMenu.appendChild(conversationMenu);
|
|
1725
|
-
});
|
|
1726
|
-
threeDotMenu.appendChild(threeDotMenuButton);
|
|
1727
|
-
conversationButton.appendChild(threeDotMenu);
|
|
1728
|
-
conversationListBody.appendChild(conversationButton);
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
})
|
|
1732
|
-
.catch(err => {
|
|
1733
|
-
console.log(err);
|
|
1734
|
-
return;
|
|
1735
|
-
});
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
let sendMessageTimeout;
|
|
1739
|
-
let mediaRecorder;
|
|
1740
|
-
function speechToText(event) {
|
|
1741
|
-
event.preventDefault();
|
|
1742
|
-
const speakButtonImg = document.getElementById('speak-button-img');
|
|
1743
|
-
const stopRecordButtonImg = document.getElementById('stop-record-button-img');
|
|
1744
|
-
const sendButtonImg = document.getElementById('send-button-img');
|
|
1745
|
-
const stopSendButtonImg = document.getElementById('stop-send-button-img');
|
|
1746
|
-
const chatInput = document.getElementById('chat-input');
|
|
1747
|
-
|
|
1748
|
-
const sendToServer = (audioBlob) => {
|
|
1749
|
-
const formData = new FormData();
|
|
1750
|
-
formData.append('file', audioBlob);
|
|
1751
|
-
|
|
1752
|
-
fetch('/api/transcribe?client=web', { method: 'POST', body: formData })
|
|
1753
|
-
.then(response => response.ok ? response.json() : Promise.reject(response))
|
|
1754
|
-
.then(data => { chatInput.value += data.text.trimStart(); autoResize(); })
|
|
1755
|
-
.then(() => {
|
|
1756
|
-
// Don't auto-send empty messages
|
|
1757
|
-
if (chatInput.value.length === 0) return;
|
|
1758
|
-
|
|
1759
|
-
// Send message after 3 seconds, unless stop send button is clicked
|
|
1760
|
-
sendButtonImg.style.display = 'none';
|
|
1761
|
-
stopSendButtonImg.style.display = 'initial';
|
|
1762
|
-
|
|
1763
|
-
// Start the countdown timer UI
|
|
1764
|
-
document.getElementById('countdown-circle').style.animation = "countdown 3s linear 1 forwards";
|
|
1765
|
-
|
|
1766
|
-
sendMessageTimeout = setTimeout(() => {
|
|
1767
|
-
// Revert to showing send-button and hide the stop-send-button
|
|
1768
|
-
sendButtonImg.style.display = 'initial';
|
|
1769
|
-
stopSendButtonImg.style.display = 'none';
|
|
1770
|
-
|
|
1771
|
-
// Stop the countdown timer UI
|
|
1772
|
-
document.getElementById('countdown-circle').style.animation = "none";
|
|
1773
|
-
|
|
1774
|
-
// Send message
|
|
1775
|
-
chat(true);
|
|
1776
|
-
}, 3000);
|
|
1777
|
-
})
|
|
1778
|
-
.catch(err => {
|
|
1779
|
-
if (err.status === 501) {
|
|
1780
|
-
flashStatusInChatInput("⛔️ Configure speech-to-text model on server.")
|
|
1781
|
-
} else if (err.status === 422) {
|
|
1782
|
-
flashStatusInChatInput("⛔️ Audio file to large to process.")
|
|
1783
|
-
} else if (err.status === 429) {
|
|
1784
|
-
flashStatusInChatInput("⛔️ " + err.statusText);
|
|
1785
|
-
} else {
|
|
1786
|
-
flashStatusInChatInput("⛔️ Failed to transcribe audio.")
|
|
1787
|
-
}
|
|
1788
|
-
});
|
|
1789
|
-
};
|
|
1790
|
-
|
|
1791
|
-
const handleRecording = (stream) => {
|
|
1792
|
-
const audioChunks = [];
|
|
1793
|
-
const recordingConfig = { mimeType: 'audio/webm' };
|
|
1794
|
-
mediaRecorder = new MediaRecorder(stream, recordingConfig);
|
|
1795
|
-
|
|
1796
|
-
mediaRecorder.addEventListener("dataavailable", function(event) {
|
|
1797
|
-
if (event.data.size > 0) audioChunks.push(event.data);
|
|
1798
|
-
});
|
|
1799
|
-
|
|
1800
|
-
mediaRecorder.addEventListener("stop", function() {
|
|
1801
|
-
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
|
1802
|
-
sendToServer(audioBlob);
|
|
1803
|
-
});
|
|
1804
|
-
|
|
1805
|
-
mediaRecorder.start();
|
|
1806
|
-
speakButtonImg.style.display = 'none';
|
|
1807
|
-
stopRecordButtonImg.style.display = 'initial';
|
|
1808
|
-
};
|
|
1809
|
-
|
|
1810
|
-
// Toggle recording
|
|
1811
|
-
if (!mediaRecorder || mediaRecorder.state === 'inactive' || event.type === 'touchstart') {
|
|
1812
|
-
navigator.mediaDevices
|
|
1813
|
-
?.getUserMedia({ audio: true })
|
|
1814
|
-
.then(handleRecording)
|
|
1815
|
-
.catch((e) => {
|
|
1816
|
-
flashStatusInChatInput("⛔️ Failed to access microphone");
|
|
1817
|
-
});
|
|
1818
|
-
} else if (mediaRecorder.state === 'recording' || event.type === 'touchend' || event.type === 'touchcancel') {
|
|
1819
|
-
mediaRecorder.stop();
|
|
1820
|
-
mediaRecorder.stream.getTracks().forEach(track => track.stop());
|
|
1821
|
-
mediaRecorder = null;
|
|
1822
|
-
speakButtonImg.style.display = 'initial';
|
|
1823
|
-
stopRecordButtonImg.style.display = 'none';
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
function cancelSendMessage() {
|
|
1828
|
-
// Cancel the chat() call if the stop-send-button is clicked
|
|
1829
|
-
clearTimeout(sendMessageTimeout);
|
|
1830
|
-
|
|
1831
|
-
// Revert to showing send-button and hide the stop-send-button
|
|
1832
|
-
document.getElementById('stop-send-button-img').style.display = 'none';
|
|
1833
|
-
document.getElementById('send-button-img').style.display = 'initial';
|
|
1834
|
-
|
|
1835
|
-
// Stop the countdown timer UI
|
|
1836
|
-
document.getElementById('countdown-circle').style.animation = "none";
|
|
1837
|
-
};
|
|
1838
|
-
|
|
1839
|
-
function handleCollapseSidePanel() {
|
|
1840
|
-
document.getElementById('side-panel').classList.toggle('collapsed');
|
|
1841
|
-
document.getElementById('new-conversation').classList.toggle('collapsed');
|
|
1842
|
-
document.getElementById('existing-conversations').classList.toggle('collapsed');
|
|
1843
|
-
document.getElementById('side-panel-collapse').style.transform = document.getElementById('side-panel').classList.contains('collapsed') ? 'rotate(0deg)' : 'rotate(180deg)';
|
|
1844
|
-
}
|
|
1845
|
-
var allFiles;
|
|
1846
|
-
function renderAllFiles() {
|
|
1847
|
-
fetch('/api/content/computer')
|
|
1848
|
-
.then(response => response.json())
|
|
1849
|
-
.then(data => {
|
|
1850
|
-
var indexedFiles = document.getElementsByClassName("indexed-files")[0];
|
|
1851
|
-
indexedFiles.innerHTML = "";
|
|
1852
|
-
|
|
1853
|
-
for (var filename of data) {
|
|
1854
|
-
var listItem = document.createElement("li");
|
|
1855
|
-
listItem.className = "fileName";
|
|
1856
|
-
listItem.id = filename;
|
|
1857
|
-
listItem.textContent = filename;
|
|
1858
|
-
listItem.addEventListener('click', function() {
|
|
1859
|
-
handleFileClick(this.id);
|
|
1860
|
-
});
|
|
1861
|
-
indexedFiles.appendChild(listItem);
|
|
1862
|
-
}
|
|
1863
|
-
allFiles = data;
|
|
1864
|
-
var nofilesmessage = document.getElementsByClassName("no-files-message")[0];
|
|
1865
|
-
nofilesmessage.innerHTML = "";
|
|
1866
|
-
if(allFiles.length === 0){
|
|
1867
|
-
let inlineChatLinkEl = document.createElement('a');
|
|
1868
|
-
inlineChatLinkEl.className = "inline-chat-link";
|
|
1869
|
-
inlineChatLinkEl.href = "https://docs.khoj.dev/category/clients/";
|
|
1870
|
-
inlineChatLinkEl.textContent = "How to upload files";
|
|
1871
|
-
nofilesmessage.appendChild(inlineChatLinkEl);
|
|
1872
|
-
document.getElementsByClassName("file-toggle-button")[0].style.display = "none";
|
|
1873
|
-
}
|
|
1874
|
-
else{
|
|
1875
|
-
document.getElementsByClassName("file-toggle-button")[0].style.display = "block";
|
|
1876
|
-
}
|
|
1877
|
-
})
|
|
1878
|
-
.catch((error) => {
|
|
1879
|
-
console.error('Error:', error);
|
|
1880
|
-
});
|
|
1881
|
-
}
|
|
1882
|
-
function renderFilteredFiles(){
|
|
1883
|
-
var indexedFiles = document.getElementsByClassName("indexed-files")[0];
|
|
1884
|
-
indexedFiles.innerHTML = "";
|
|
1885
|
-
var input = document.getElementsByClassName("file-input")[0];
|
|
1886
|
-
var filter = input.value.toUpperCase();
|
|
1887
|
-
|
|
1888
|
-
for (var filename of allFiles) {
|
|
1889
|
-
if (filename.toUpperCase().indexOf(filter) > -1) {
|
|
1890
|
-
var listItem = document.createElement("li");
|
|
1891
|
-
listItem.className = "fileName";
|
|
1892
|
-
listItem.id = filename;
|
|
1893
|
-
listItem.textContent = filename;
|
|
1894
|
-
|
|
1895
|
-
// Add an event listener for the click event
|
|
1896
|
-
listItem.addEventListener('click', function() {
|
|
1897
|
-
handleFileClick(this.id);
|
|
1898
|
-
});
|
|
1899
|
-
|
|
1900
|
-
// Append the list item to the indexed files container
|
|
1901
|
-
indexedFiles.appendChild(listItem);
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
function handleFileClick(elementId) {
|
|
1906
|
-
var element = document.getElementById(elementId);
|
|
1907
|
-
if (element) {
|
|
1908
|
-
var selectedFiles = document.getElementsByClassName("selected-files")[0];
|
|
1909
|
-
var selectedFile = document.getElementById(elementId);
|
|
1910
|
-
|
|
1911
|
-
// Check if the element has a background color indicating selection
|
|
1912
|
-
if (element.style.backgroundColor === "var(--primary-hover)") {
|
|
1913
|
-
// Remove the file filter from the conversation
|
|
1914
|
-
removeFileFilterFromConversation(elementId);
|
|
1915
|
-
// Remove the selected file from the list of selected files
|
|
1916
|
-
if (selectedFile) {
|
|
1917
|
-
selectedFiles.removeChild(selectedFile);
|
|
1918
|
-
}
|
|
1919
|
-
var selectedFile = document.getElementById(elementId);
|
|
1920
|
-
selectedFile.style.backgroundColor = "var(--primary)";
|
|
1921
|
-
selectedFile.style.border = "1px solid var(--primary-hover)";
|
|
1922
|
-
} else {
|
|
1923
|
-
// If the element is not selected, select it
|
|
1924
|
-
element.style.backgroundColor = "var(--primary-hover)"; // Set background color
|
|
1925
|
-
element.style.border = "3px solid orange"; // Set border
|
|
1926
|
-
// Add the file filter to the conversation
|
|
1927
|
-
addFileFilterToConversation(elementId);
|
|
1928
|
-
// Add the selected file to the list of selected files
|
|
1929
|
-
var li = document.createElement("li");
|
|
1930
|
-
li.className = "fileName";
|
|
1931
|
-
li.id = elementId;
|
|
1932
|
-
li.style.backgroundColor = "var(--primary-hover)"; // match the style
|
|
1933
|
-
li.style.border = "3px solid orange"; // match the style
|
|
1934
|
-
li.innerText = elementId;
|
|
1935
|
-
selectedFiles.appendChild(li);
|
|
1936
|
-
}
|
|
1937
|
-
} else {
|
|
1938
|
-
console.error('Element with id', elementId, 'not found.');
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
function addFileFilterToConversation(filename) {
|
|
1943
|
-
var conversation_id = document.getElementById("chat-body").dataset.conversationId;
|
|
1944
|
-
if (!conversation_id) {
|
|
1945
|
-
console.error("Conversation ID not found on chat-body element.");
|
|
1946
|
-
return;
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
return fetch(`/api/chat/conversation/file-filters`, {
|
|
1950
|
-
method: 'POST',
|
|
1951
|
-
headers: {
|
|
1952
|
-
'Content-Type': 'application/json'
|
|
1953
|
-
},
|
|
1954
|
-
body: JSON.stringify({ filename, conversation_id }) // Pass the filename directly
|
|
1955
|
-
})
|
|
1956
|
-
.then(response => {
|
|
1957
|
-
if (!response.ok) {
|
|
1958
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1959
|
-
}
|
|
1960
|
-
return response.json();
|
|
1961
|
-
})
|
|
1962
|
-
.then(data => {
|
|
1963
|
-
console.log("Response from server:", data);
|
|
1964
|
-
return data;
|
|
1965
|
-
})
|
|
1966
|
-
.catch(error => {
|
|
1967
|
-
console.error("Error:", error);
|
|
1968
|
-
throw error;
|
|
1969
|
-
});
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
-
function removeFileFilterFromConversation(filename) {
|
|
1973
|
-
var conversation_id = document.getElementById("chat-body").dataset.conversationId;
|
|
1974
|
-
if (!conversation_id) {
|
|
1975
|
-
console.error("Conversation ID not found on chat-body element.");
|
|
1976
|
-
return;
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
return fetch(`/api/chat/conversation/file-filters`, {
|
|
1980
|
-
method: 'DELETE',
|
|
1981
|
-
headers: {
|
|
1982
|
-
'Content-Type': 'application/json'
|
|
1983
|
-
},
|
|
1984
|
-
body: JSON.stringify({ filename, conversation_id }) // Pass the filename directly
|
|
1985
|
-
})
|
|
1986
|
-
.then(response => {
|
|
1987
|
-
if (!response.ok) {
|
|
1988
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1989
|
-
}
|
|
1990
|
-
return response.json();
|
|
1991
|
-
})
|
|
1992
|
-
.then(data => {
|
|
1993
|
-
console.log("Response from server:", data);
|
|
1994
|
-
return data;
|
|
1995
|
-
})
|
|
1996
|
-
.catch(error => {
|
|
1997
|
-
console.error("Error:", error);
|
|
1998
|
-
throw error;
|
|
1999
|
-
});
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
function getFileFiltersFromConversation() {
|
|
2003
|
-
// Get the conversation_id from the data attribute
|
|
2004
|
-
var conversation_id = document.getElementById("chat-body").dataset.conversationId;
|
|
2005
|
-
|
|
2006
|
-
// Make sure conversation_id is not undefined or null
|
|
2007
|
-
if (!conversation_id) {
|
|
2008
|
-
console.error("No conversation ID found on chat-body element.");
|
|
2009
|
-
return Promise.reject("No conversation ID found on chat-body element.");
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
// Perform the fetch request
|
|
2013
|
-
return fetch(`/api/chat/conversation/file-filters/${conversation_id}`, {
|
|
2014
|
-
method: 'GET',
|
|
2015
|
-
headers: {
|
|
2016
|
-
'Content-Type': 'application/json'
|
|
2017
|
-
}
|
|
2018
|
-
})
|
|
2019
|
-
.then(function(response) {
|
|
2020
|
-
console.log("Response status:", response.status); // Log the response status
|
|
2021
|
-
|
|
2022
|
-
if (!response.ok) {
|
|
2023
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
return response.json();
|
|
2027
|
-
})
|
|
2028
|
-
.then(function(data) {
|
|
2029
|
-
console.log("Response from server:", data);
|
|
2030
|
-
return data;
|
|
2031
|
-
})
|
|
2032
|
-
.catch(function(error) {
|
|
2033
|
-
console.error("Error:", error);
|
|
2034
|
-
throw error; // Rethrow the error to be handled elsewhere if needed
|
|
2035
|
-
});
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
function loadFileFiltersFromConversation(){
|
|
2040
|
-
getFileFiltersFromConversation()
|
|
2041
|
-
.then(filters => {
|
|
2042
|
-
var selectedFiles = document.getElementsByClassName("selected-files")[0];
|
|
2043
|
-
selectedFiles.innerHTML = "";
|
|
2044
|
-
for (var filter of filters) {
|
|
2045
|
-
var li = document.createElement("li");
|
|
2046
|
-
li.className = "fileName";
|
|
2047
|
-
li.id = filter;
|
|
2048
|
-
li.style.backgroundColor = "var(--primary-hover)"; // set background to orange
|
|
2049
|
-
li.style.border = "2px solid orange"; // set border to orange
|
|
2050
|
-
li.innerText = filter;
|
|
2051
|
-
selectedFiles.appendChild(li);
|
|
2052
|
-
}
|
|
2053
|
-
//update indexed files to have checkmark if they are in the filters
|
|
2054
|
-
var indexedFiles = document.getElementsByClassName("indexed-files")[0];
|
|
2055
|
-
indexedFiles.innerHTML = "";
|
|
2056
|
-
for (var filename of allFiles) {
|
|
2057
|
-
var li = document.createElement("li");
|
|
2058
|
-
li.className = "fileName";
|
|
2059
|
-
li.id = filename;
|
|
2060
|
-
li.innerText = filename;
|
|
2061
|
-
if (filters.includes(filename)) {
|
|
2062
|
-
li.style.backgroundColor = "var(--primary-hover)"; // set background to orange
|
|
2063
|
-
li.style.border = "2px solid orange"; // set border to orange
|
|
2064
|
-
}
|
|
2065
|
-
li.setAttribute("onclick", "handleFileClick('" + filename + "')");
|
|
2066
|
-
indexedFiles.appendChild(li);
|
|
2067
|
-
}
|
|
2068
|
-
})
|
|
2069
|
-
.catch(error => {
|
|
2070
|
-
// Handle any errors that occur during the fetch operation
|
|
2071
|
-
console.error("Error loading file filters:", error);
|
|
2072
|
-
});
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
function inputAutoFiller(key){
|
|
2076
|
-
var chatInput = document.getElementById("chat-input");
|
|
2077
|
-
console.log(key, userMessageIndex)
|
|
2078
|
-
if (key === "up") {
|
|
2079
|
-
if (userMessageIndex > 0) {
|
|
2080
|
-
userMessageIndex -= 1;
|
|
2081
|
-
chatInput.value = userMessages[userMessageIndex];
|
|
2082
|
-
} else {
|
|
2083
|
-
userMessageIndex = -1;
|
|
2084
|
-
chatInput.value = "";
|
|
2085
|
-
}
|
|
2086
|
-
} else if (key === "down") {
|
|
2087
|
-
if (userMessageIndex < userMessages.length - 1) {
|
|
2088
|
-
userMessageIndex += 1;
|
|
2089
|
-
chatInput.value = userMessages[userMessageIndex];
|
|
2090
|
-
} else if (userMessageIndex === userMessages.length - 1) {
|
|
2091
|
-
userMessageIndex += 1;
|
|
2092
|
-
chatInput.value = "";
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
function resetUserMessageIndex(){
|
|
2097
|
-
userMessageIndex = userMessages.length;
|
|
2098
|
-
}
|
|
2099
|
-
</script>
|
|
2100
|
-
<body>
|
|
2101
|
-
<div id="khoj-empty-container" class="khoj-empty-container">
|
|
2102
|
-
</div>
|
|
2103
|
-
<!--Add Header Logo and Nav Pane-->
|
|
2104
|
-
{% import 'utils.html' as utils %}
|
|
2105
|
-
{{ utils.heading_pane(user_photo, username, is_active, has_documents) }}
|
|
2106
|
-
<div id="chat-section-wrapper">
|
|
2107
|
-
<div id="side-panel-wrapper">
|
|
2108
|
-
<div id="side-panel">
|
|
2109
|
-
<div id="new-conversation">
|
|
2110
|
-
<div id="conversation-list-header" style="display: none;">Conversations</div>
|
|
2111
|
-
<button class="side-panel-button" id="new-conversation-button" onclick="createNewConversation()">
|
|
2112
|
-
New
|
|
2113
|
-
<svg class="new-convo-button" viewBox="0 0 40 40" fill="#000000" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
|
2114
|
-
<path d="M16 0c-8.836 0-16 7.163-16 16s7.163 16 16 16c8.837 0 16-7.163 16-16s-7.163-16-16-16zM16 30.032c-7.72 0-14-6.312-14-14.032s6.28-14 14-14 14 6.28 14 14-6.28 14.032-14 14.032zM23 15h-6v-6c0-0.552-0.448-1-1-1s-1 0.448-1 1v6h-6c-0.552 0-1 0.448-1 1s0.448 1 1 1h6v6c0 0.552 0.448 1 1 1s1-0.448 1-1v-6h6c0.552 0 1-0.448 1-1s-0.448-1-1-1z"></path>
|
|
2115
|
-
</svg>
|
|
2116
|
-
</button>
|
|
2117
|
-
</div>
|
|
2118
|
-
<div id="existing-conversations">
|
|
2119
|
-
<div id="conversation-list">
|
|
2120
|
-
<div id="conversation-list-body"></div>
|
|
2121
|
-
</div>
|
|
2122
|
-
</div>
|
|
2123
|
-
<div id="connection-status" class="inline-chat-link">
|
|
2124
|
-
<div id="connection-status-icon"></div>
|
|
2125
|
-
<div id="connection-status-text"></div>
|
|
2126
|
-
</div>
|
|
2127
|
-
<div style="border-top: 1px solid black; ">
|
|
2128
|
-
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 5px; margin-top: 5px;">
|
|
2129
|
-
<p style="margin: 0;">Files</p>
|
|
2130
|
-
<svg class="file-toggle-button" style="width:20px; height:20px; position: relative; top: 2px" viewBox="0 0 40 40" fill="#000000" xmlns="http://www.w3.org/2000/svg">
|
|
2131
|
-
<path d="M16 0c-8.836 0-16 7.163-16 16s7.163 16 16 16c8.837 0 16-7.163 16-16s-7.163-16-16-16zM16 30.032c-7.72 0-14-6.312-14-14.032s6.28-14 14-14 14 6.28 14 14-6.28 14.032-14 14.032zM23 15h-6v-6c0-0.552-0.448-1-1-1s-1 0.448-1 1v6h-6c-0.552 0-1 0.448-1 1s0.448 1 1 1h6v6c0 0.552 0.448 1 1 1s1-0.448 1-1v-6h6c0.552 0 1-0.448 1-1s-0.448-1-1-1z"></path>
|
|
2132
|
-
</svg>
|
|
2133
|
-
</div>
|
|
2134
|
-
<div class="no-files-message"></div>
|
|
2135
|
-
<ul class="selected-files" style="margin: 0; padding: 0; margin-bottom: 10px"></ul>
|
|
2136
|
-
<input class="file-input" style="width:240px; margin-bottom: 5px; color: black; display: none; border-radius: 4px; border: 1px solid black; padding: 4px;" type="text" onkeyup="renderFilteredFiles()" placeholder="Filter">
|
|
2137
|
-
<ul class="indexed-files" style="margin: 0; padding: 0; height:100px; overflow:hidden; overflow-y:scroll; margin-bottom:5px; display:none;"></ul>
|
|
2138
|
-
<script>
|
|
2139
|
-
renderAllFiles();
|
|
2140
|
-
var fileInputs = document.getElementsByClassName('file-input');
|
|
2141
|
-
var fileLists = document.getElementsByClassName('indexed-files');
|
|
2142
|
-
var selectedFileLists = document.getElementsByClassName('selected-files');
|
|
2143
|
-
var fileToggleButtons = document.getElementsByClassName('file-toggle-button');
|
|
2144
|
-
|
|
2145
|
-
var fileInput = fileInputs[0];
|
|
2146
|
-
var fileList = fileLists[0];
|
|
2147
|
-
var selectedFileList = selectedFileLists[0];
|
|
2148
|
-
var fileToggleButton = fileToggleButtons[0];
|
|
2149
|
-
|
|
2150
|
-
function toggleFileInput() {
|
|
2151
|
-
if (fileInput.style.display === 'none' || fileInput.style.display === '') {
|
|
2152
|
-
fileInput.style.display = 'block';
|
|
2153
|
-
fileList.style.display = 'block';
|
|
2154
|
-
selectedFileList.style.display = 'none';
|
|
2155
|
-
} else {
|
|
2156
|
-
fileInput.style.display = 'none';
|
|
2157
|
-
fileList.style.display = 'none';
|
|
2158
|
-
selectedFileList.style.display = 'block';
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
fileToggleButton.addEventListener('click', function(event) {
|
|
2163
|
-
toggleFileInput();
|
|
2164
|
-
event.stopPropagation();
|
|
2165
|
-
});
|
|
2166
|
-
|
|
2167
|
-
document.addEventListener('click', function(event) {
|
|
2168
|
-
if (!fileInput.contains(event.target) && !fileToggleButton.contains(event.target)) {
|
|
2169
|
-
fileInput.style.display = 'none';
|
|
2170
|
-
fileList.style.display = 'none';
|
|
2171
|
-
selectedFileList.style.display = 'block';
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
});
|
|
2175
|
-
|
|
2176
|
-
fileInput.addEventListener('click', function(event) {
|
|
2177
|
-
event.stopPropagation(); // Prevent the document click handler from immediately hiding the input
|
|
2178
|
-
});
|
|
2179
|
-
|
|
2180
|
-
fileList.addEventListener('click', function(event) {
|
|
2181
|
-
event.stopPropagation(); // Prevent the document click handler from hiding the file list
|
|
2182
|
-
});
|
|
2183
|
-
|
|
2184
|
-
</script>
|
|
2185
|
-
</div>
|
|
2186
|
-
<a id="agent-link" class="inline-chat-link" href="">
|
|
2187
|
-
<div id="agent-metadata" style="display: none;">
|
|
2188
|
-
Current Agent
|
|
2189
|
-
<div id="agent-metadata-content">
|
|
2190
|
-
<div id="agent-avatar-wrapper">
|
|
2191
|
-
<img id="agent-avatar" src="" alt="Agent Avatar" />
|
|
2192
|
-
</div>
|
|
2193
|
-
<div id="agent-name-wrapper">
|
|
2194
|
-
<div id="agent-name"></div>
|
|
2195
|
-
<div id="agent-owned-by-user" style="display: none;">Edit</div>
|
|
2196
|
-
</div>
|
|
2197
|
-
</div>
|
|
2198
|
-
</div>
|
|
2199
|
-
</a>
|
|
2200
|
-
</div>
|
|
2201
|
-
<div id="collapse-side-panel">
|
|
2202
|
-
<button
|
|
2203
|
-
class="side-panel-button"
|
|
2204
|
-
id="collapse-side-panel-button"
|
|
2205
|
-
onclick="handleCollapseSidePanel()"
|
|
2206
|
-
>
|
|
2207
|
-
<svg id="side-panel-collapse" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2208
|
-
<path d="M7.82054 20.7313C8.21107 21.1218 8.84423 21.1218 9.23476 20.7313L15.8792 14.0868C17.0505 12.9155 17.0508 11.0167 15.88 9.84497L9.3097 3.26958C8.91918 2.87905 8.28601 2.87905 7.89549 3.26958C7.50497 3.6601 7.50497 4.29327 7.89549 4.68379L14.4675 11.2558C14.8581 11.6464 14.8581 12.2795 14.4675 12.67L7.82054 19.317C7.43002 19.7076 7.43002 20.3407 7.82054 20.7313Z" fill="#0F0F0F"/>
|
|
2209
|
-
</svg>
|
|
2210
|
-
</button>
|
|
2211
|
-
</div>
|
|
2212
|
-
</div>
|
|
2213
|
-
<div id="chat-body-wrapper">
|
|
2214
|
-
<!-- Chat Body -->
|
|
2215
|
-
<div id="chat-body"></div>
|
|
2216
|
-
|
|
2217
|
-
<!-- Chat Suggestions -->
|
|
2218
|
-
<div id="question-starters" style="display: none;"></div>
|
|
2219
|
-
|
|
2220
|
-
<!-- Chat Footer -->
|
|
2221
|
-
<div id="chat-footer">
|
|
2222
|
-
<div id="chat-tooltip" style="display: none;"></div>
|
|
2223
|
-
<div id="input-row">
|
|
2224
|
-
<button id="upload-file-button" class="input-row-button" onclick="openFileBrowser()">
|
|
2225
|
-
<svg id="upload-file-button-img" class="input-row-button-img" alt="Upload File" width="183px" height="183px" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#000000" stroke-width="0.9600000000000002" transform="matrix(1, 0, 0, 1, 0, 0)rotate(-45)">
|
|
2226
|
-
<g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g id="attachment"> <g id="attachment_2"> <path id="Combined Shape" fill-rule="evenodd" clip-rule="evenodd" d="M26.4252 29.1104L39.5729 15.9627C42.3094 13.2262 42.3094 8.78901 39.5729 6.05248C36.8364 3.31601 32.4015 3.31601 29.663 6.05218L16.4487 19.2665L16.4251 19.2909L8.92989 26.7861C5.02337 30.6926 5.02337 37.0238 8.92989 40.9303C12.8344 44.8348 19.1656 44.8348 23.0701 40.9303L41.7835 22.2169C42.174 21.8264 42.174 21.1933 41.7835 20.8027C41.3929 20.4122 40.7598 20.4122 40.3693 20.8027L21.6559 39.5161C18.5324 42.6396 13.4676 42.6396 10.3441 39.5161C7.21863 36.3906 7.21863 31.3258 10.3441 28.2003L30.1421 8.4023L30.1657 8.37788L31.0769 7.4667C33.0341 5.51117 36.2032 5.51117 38.1587 7.4667C40.1142 9.42217 40.1142 12.593 38.1587 14.5485L28.282 24.4252C28.2748 24.4319 28.2678 24.4388 28.2608 24.4458L25.0064 27.7008L24.9447 27.7625C24.9437 27.7635 24.9427 27.7644 24.9418 27.7654L17.3988 35.3097C16.6139 36.0934 15.3401 36.0934 14.5545 35.3091C13.7714 34.5247 13.7714 33.2509 14.5557 32.4653L24.479 22.544C24.8696 22.1535 24.8697 21.5203 24.4792 21.1298C24.0887 20.7392 23.4555 20.7391 23.065 21.1296L13.141 31.0516C11.5766 32.6187 11.5766 35.1569 13.1403 36.7233C14.7079 38.2882 17.2461 38.2882 18.8125 36.7245L26.3589 29.1767L26.4252 29.1104Z" fill="#000000"></path></g> </g> </g>
|
|
2227
|
-
</svg>
|
|
2228
|
-
</button>
|
|
2229
|
-
<textarea id="chat-input" class="option" oninput="onChatInput()" onkeydown=incrementalChat(event) autofocus="autofocus" placeholder="Type / to see a list of commands"></textarea>
|
|
2230
|
-
<!-- Shortcut Handler for Accessing Old Messages -->
|
|
2231
|
-
<script>
|
|
2232
|
-
let chatInput = document.getElementById('chat-input');
|
|
2233
|
-
chatInput.addEventListener('keydown', function(event) {
|
|
2234
|
-
if (event.key === 'ArrowUp') {
|
|
2235
|
-
inputAutoFiller('up');
|
|
2236
|
-
} else if (event.key === 'ArrowDown') {
|
|
2237
|
-
inputAutoFiller('down');
|
|
2238
|
-
}
|
|
2239
|
-
});
|
|
2240
|
-
document.addEventListener('click', function(event) {
|
|
2241
|
-
if (document.activeElement !== document.getElementById('chat-input')) {
|
|
2242
|
-
resetUserMessageIndex();
|
|
2243
|
-
}
|
|
2244
|
-
});
|
|
2245
|
-
</script>
|
|
2246
|
-
<button id="speak-button" class="input-row-button"
|
|
2247
|
-
ontouchstart="speechToText(event)" ontouchend="speechToText(event)" ontouchcancel="speechToText(event)" onmousedown="speechToText(event)">
|
|
2248
|
-
<svg id="speak-button-img" class="input-row-button-img" alt="Transcribe" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
2249
|
-
<path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5z"/>
|
|
2250
|
-
<path d="M10 8a2 2 0 1 1-4 0V3a2 2 0 1 1 4 0v5zM8 0a3 3 0 0 0-3 3v5a3 3 0 0 0 6 0V3a3 3 0 0 0-3-3z"/>
|
|
2251
|
-
</svg>
|
|
2252
|
-
<svg id="stop-record-button-img" style="display: none" class="input-row-button-img" alt="Stop Transcribing" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
2253
|
-
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
|
2254
|
-
<path d="M5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3z"/>
|
|
2255
|
-
</svg>
|
|
2256
|
-
</button>
|
|
2257
|
-
<button id="send-button" class="input-row-button" alt="Send message">
|
|
2258
|
-
<svg id="send-button-img" onclick="chat()" class="input-row-button-img" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
2259
|
-
<path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-7.5 3.5a.5.5 0 0 1-1 0V5.707L5.354 7.854a.5.5 0 1 1-.708-.708l3-3a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 5.707V11.5z"/>
|
|
2260
|
-
</svg>
|
|
2261
|
-
<svg id="stop-send-button-img" onclick="cancelSendMessage()" style="display: none" class="input-row-button-img" alt="Stop Message Send" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
2262
|
-
<circle id="countdown-circle" class="countdown-circle" cx="8" cy="8" r="7" />
|
|
2263
|
-
<path d="M5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3z"/>
|
|
2264
|
-
</svg>
|
|
2265
|
-
</button>
|
|
2266
|
-
</div>
|
|
2267
|
-
</div>
|
|
2268
|
-
</div>
|
|
2269
|
-
</div>
|
|
2270
|
-
</body>
|
|
2271
|
-
<script>
|
|
2272
|
-
// Set the active nav pane
|
|
2273
|
-
let chatNav = document.getElementById("chat-nav");
|
|
2274
|
-
if (chatNav) {
|
|
2275
|
-
chatNav.classList.add("khoj-nav-selected");
|
|
2276
|
-
}
|
|
2277
|
-
</script>
|
|
2278
|
-
<style>
|
|
2279
|
-
html, body {
|
|
2280
|
-
height: 100%;
|
|
2281
|
-
width: 100%;
|
|
2282
|
-
padding: 0px;
|
|
2283
|
-
margin: 0px;
|
|
2284
|
-
}
|
|
2285
|
-
body {
|
|
2286
|
-
display: grid;
|
|
2287
|
-
background: var(--background-color);
|
|
2288
|
-
color: var(--main-text-color);
|
|
2289
|
-
text-align: center;
|
|
2290
|
-
font-family: var(--font-family);
|
|
2291
|
-
font-size: small;
|
|
2292
|
-
font-weight: 300;
|
|
2293
|
-
line-height: 2em;
|
|
2294
|
-
height: 100vh;
|
|
2295
|
-
margin: 0;
|
|
2296
|
-
}
|
|
2297
|
-
body > * {
|
|
2298
|
-
padding: 10px;
|
|
2299
|
-
margin: 10px;
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
|
-
div.collapsed {
|
|
2303
|
-
display: none;
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
div.expanded {
|
|
2307
|
-
display: block;
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
|
-
div.references {
|
|
2311
|
-
padding-top: 8px;
|
|
2312
|
-
}
|
|
2313
|
-
div.reference {
|
|
2314
|
-
display: grid;
|
|
2315
|
-
grid-template-rows: auto;
|
|
2316
|
-
grid-auto-flow: row;
|
|
2317
|
-
grid-column-gap: 10px;
|
|
2318
|
-
grid-row-gap: 10px;
|
|
2319
|
-
margin: 10px;
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
li.fileName {
|
|
2323
|
-
white-space: nowrap;
|
|
2324
|
-
overflow: hidden;
|
|
2325
|
-
text-overflow: ellipsis;
|
|
2326
|
-
max-width: 250px;
|
|
2327
|
-
background-color: var(--primary);
|
|
2328
|
-
border-radius: 10px;
|
|
2329
|
-
border: 1px solid var(--primary-hover);
|
|
2330
|
-
margin-bottom: 4px;
|
|
2331
|
-
margin-top: 4px;
|
|
2332
|
-
padding: 4px;
|
|
2333
|
-
}
|
|
2334
|
-
|
|
2335
|
-
li.fileName:hover {
|
|
2336
|
-
background-color: var(--primary-hover);
|
|
2337
|
-
cursor: pointer;
|
|
2338
|
-
}
|
|
2339
|
-
|
|
2340
|
-
div.expanded.reference-section {
|
|
2341
|
-
display: grid;
|
|
2342
|
-
grid-template-rows: auto;
|
|
2343
|
-
grid-auto-flow: row;
|
|
2344
|
-
grid-column-gap: 10px;
|
|
2345
|
-
grid-row-gap: 10px;
|
|
2346
|
-
margin: 10px;
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2349
|
-
div#question-starters {
|
|
2350
|
-
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
|
2351
|
-
grid-column-gap: 8px;
|
|
2352
|
-
}
|
|
2353
|
-
|
|
2354
|
-
button.question-starter {
|
|
2355
|
-
background: var(--background-color);
|
|
2356
|
-
color: var(--main-text-color);
|
|
2357
|
-
border: 1px solid var(--main-text-color);
|
|
2358
|
-
border-radius: 5px;
|
|
2359
|
-
padding: 5px;
|
|
2360
|
-
font-size: 14px;
|
|
2361
|
-
font-weight: 300;
|
|
2362
|
-
line-height: 1.5em;
|
|
2363
|
-
cursor: pointer;
|
|
2364
|
-
transition: background 0.2s ease-in-out;
|
|
2365
|
-
text-align: left;
|
|
2366
|
-
max-height: 75px;
|
|
2367
|
-
transition: max-height 0.3s ease-in-out;
|
|
2368
|
-
overflow: hidden;
|
|
2369
|
-
}
|
|
2370
|
-
button.question-starter:hover {
|
|
2371
|
-
background: var(--primary-hover);
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
button.reference-button {
|
|
2375
|
-
background: var(--background-color);
|
|
2376
|
-
color: var(--main-text-color);
|
|
2377
|
-
border: 1px solid var(--main-text-color);
|
|
2378
|
-
border-radius: 5px;
|
|
2379
|
-
padding: 5px;
|
|
2380
|
-
font-size: 14px;
|
|
2381
|
-
font-weight: 300;
|
|
2382
|
-
line-height: 1.5em;
|
|
2383
|
-
cursor: pointer;
|
|
2384
|
-
transition: background 0.2s ease-in-out;
|
|
2385
|
-
text-align: left;
|
|
2386
|
-
max-height: 75px;
|
|
2387
|
-
transition: max-height 0.3s ease-in-out;
|
|
2388
|
-
overflow: hidden;
|
|
2389
|
-
}
|
|
2390
|
-
button.reference-button.expanded {
|
|
2391
|
-
max-height: none;
|
|
2392
|
-
white-space: pre-wrap;
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
button.reference-button::before {
|
|
2396
|
-
content: "▶";
|
|
2397
|
-
margin-right: 5px;
|
|
2398
|
-
display: inline-block;
|
|
2399
|
-
transition: transform 0.1s ease-in-out;
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
button.reference-button.expanded::before,
|
|
2403
|
-
button.reference-button:active:before,
|
|
2404
|
-
button.reference-button[aria-expanded="true"]::before {
|
|
2405
|
-
transform: rotate(90deg);
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
button.reference-expand-button {
|
|
2409
|
-
background: var(--background-color);
|
|
2410
|
-
color: var(--main-text-color);
|
|
2411
|
-
border: 1px dotted var(--main-text-color);
|
|
2412
|
-
border-radius: 5px;
|
|
2413
|
-
padding: 5px;
|
|
2414
|
-
font-size: 14px;
|
|
2415
|
-
font-weight: 300;
|
|
2416
|
-
line-height: 1.5em;
|
|
2417
|
-
cursor: pointer;
|
|
2418
|
-
transition: background 0.4s ease-in-out;
|
|
2419
|
-
text-align: left;
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
|
-
button.reference-expand-button:hover {
|
|
2423
|
-
background: var(--primary-hover);
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
code.chat-response {
|
|
2427
|
-
background: var(--primary-hover);
|
|
2428
|
-
color: var(--primary-inverse);
|
|
2429
|
-
border-radius: 5px;
|
|
2430
|
-
padding: 5px;
|
|
2431
|
-
font-size: 14px;
|
|
2432
|
-
font-weight: 300;
|
|
2433
|
-
line-height: 1.5em;
|
|
2434
|
-
}
|
|
2435
|
-
|
|
2436
|
-
input.conversation-title-input {
|
|
2437
|
-
font-family: var(--font-family);
|
|
2438
|
-
font-size: 14px;
|
|
2439
|
-
font-weight: 300;
|
|
2440
|
-
line-height: 1.5em;
|
|
2441
|
-
padding: 5px;
|
|
2442
|
-
border: 1px solid var(--main-text-color);
|
|
2443
|
-
border-radius: 5px;
|
|
2444
|
-
margin: 4px;
|
|
2445
|
-
}
|
|
2446
|
-
|
|
2447
|
-
input.conversation-title-input:focus {
|
|
2448
|
-
outline: none;
|
|
2449
|
-
}
|
|
2450
|
-
|
|
2451
|
-
#chat-section-wrapper {
|
|
2452
|
-
display: grid;
|
|
2453
|
-
grid-template-columns: auto 1fr;
|
|
2454
|
-
grid-column-gap: 10px;
|
|
2455
|
-
grid-row-gap: 10px;
|
|
2456
|
-
padding: 10px;
|
|
2457
|
-
margin: 10px;
|
|
2458
|
-
overflow-y: scroll;
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
|
-
#chat-body-wrapper {
|
|
2462
|
-
display: flex;
|
|
2463
|
-
flex-direction: column;
|
|
2464
|
-
overflow: hidden;
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
#side-panel {
|
|
2468
|
-
width: 250px;
|
|
2469
|
-
padding: 10px;
|
|
2470
|
-
background: var(--background-color);
|
|
2471
|
-
border-radius: 5px;
|
|
2472
|
-
box-shadow: 0 0 11px #aaa;
|
|
2473
|
-
text-align: left;
|
|
2474
|
-
transition: width 0.3s ease-in-out;
|
|
2475
|
-
max-height: 100%;
|
|
2476
|
-
display: grid;
|
|
2477
|
-
grid-template-rows: auto 1fr auto;
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
div#existing-conversations {
|
|
2481
|
-
max-height: 95%;
|
|
2482
|
-
overflow-y: auto;
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
div#side-panel.collapsed {
|
|
2486
|
-
width: 0;
|
|
2487
|
-
padding: 0;
|
|
2488
|
-
display: block;
|
|
2489
|
-
overflow: hidden;
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
div#collapse-side-panel {
|
|
2493
|
-
align-self: center;
|
|
2494
|
-
padding: 8px;
|
|
2495
|
-
}
|
|
2496
|
-
|
|
2497
|
-
div#conversation-list-body {
|
|
2498
|
-
display: grid;
|
|
2499
|
-
grid-template-columns: 1fr;
|
|
2500
|
-
grid-gap: 8px;
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2503
|
-
div#conversation-list {
|
|
2504
|
-
height: 1px;
|
|
2505
|
-
}
|
|
2506
|
-
|
|
2507
|
-
div#side-panel-wrapper {
|
|
2508
|
-
display: flex;
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
#chat-body {
|
|
2512
|
-
height: 100%;
|
|
2513
|
-
font-size: 1.1em;
|
|
2514
|
-
margin: 0px;
|
|
2515
|
-
line-height: 20px;
|
|
2516
|
-
overflow-y: scroll;
|
|
2517
|
-
overflow-x: hidden;
|
|
2518
|
-
transition: background-color 0.2s;
|
|
2519
|
-
transition: opacity 0.2s;
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
#chat-body.dragover {
|
|
2523
|
-
background-color: var(--primary-active);
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2526
|
-
.relative-position {
|
|
2527
|
-
position: relative;
|
|
2528
|
-
}
|
|
2529
|
-
|
|
2530
|
-
#chat-body.dragover {
|
|
2531
|
-
opacity: 50%;
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
div.dropzone-overlay {
|
|
2535
|
-
position: absolute;
|
|
2536
|
-
top: 0;
|
|
2537
|
-
left: 0;
|
|
2538
|
-
width: 100%;
|
|
2539
|
-
height: 100%;
|
|
2540
|
-
display: flex;
|
|
2541
|
-
align-items: center;
|
|
2542
|
-
justify-content: center;
|
|
2543
|
-
font-size: 2rem;
|
|
2544
|
-
color: #333;
|
|
2545
|
-
z-index: 9999; /* This is the important part */
|
|
2546
|
-
pointer-events: none;
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
div.loading-screen {
|
|
2550
|
-
position: absolute;
|
|
2551
|
-
width: 100%;
|
|
2552
|
-
height: 100%;
|
|
2553
|
-
display: flex;
|
|
2554
|
-
align-items: center;
|
|
2555
|
-
justify-content: center;
|
|
2556
|
-
font-size: 2rem;
|
|
2557
|
-
color: #333;
|
|
2558
|
-
z-index: 9999; /* This is the important part */
|
|
2559
|
-
}
|
|
2560
|
-
|
|
2561
|
-
/* add chat metatdata to bottom of bubble */
|
|
2562
|
-
.chat-message::after {
|
|
2563
|
-
content: attr(data-meta);
|
|
2564
|
-
display: block;
|
|
2565
|
-
font-size: x-small;
|
|
2566
|
-
color: var(--main-text-color);
|
|
2567
|
-
margin: -8px 4px 0px 0px;
|
|
2568
|
-
}
|
|
2569
|
-
/* move message by khoj to left */
|
|
2570
|
-
.chat-message.khoj {
|
|
2571
|
-
margin-left: auto;
|
|
2572
|
-
text-align: left;
|
|
2573
|
-
height: fit-content;
|
|
2574
|
-
}
|
|
2575
|
-
/* move message by you to right */
|
|
2576
|
-
.chat-message.you {
|
|
2577
|
-
margin-right: auto;
|
|
2578
|
-
text-align: right;
|
|
2579
|
-
height: fit-content;
|
|
2580
|
-
}
|
|
2581
|
-
/* basic style chat message text */
|
|
2582
|
-
.chat-message-text {
|
|
2583
|
-
margin: 10px;
|
|
2584
|
-
border-radius: 10px;
|
|
2585
|
-
padding: 10px;
|
|
2586
|
-
position: relative;
|
|
2587
|
-
display: inline-block;
|
|
2588
|
-
max-width: 80%;
|
|
2589
|
-
text-align: left;
|
|
2590
|
-
white-space: pre-line;
|
|
2591
|
-
}
|
|
2592
|
-
/* color chat bubble by khoj blue */
|
|
2593
|
-
.chat-message-text.khoj {
|
|
2594
|
-
color: var(--primary-inverse);
|
|
2595
|
-
background: var(--primary);
|
|
2596
|
-
margin-left: auto;
|
|
2597
|
-
}
|
|
2598
|
-
.chat-message-text ol,
|
|
2599
|
-
.chat-message-text ul {
|
|
2600
|
-
white-space: normal;
|
|
2601
|
-
margin: 0;
|
|
2602
|
-
}
|
|
2603
|
-
.chat-message-text-response {
|
|
2604
|
-
margin-bottom: 0px;
|
|
2605
|
-
}
|
|
2606
|
-
|
|
2607
|
-
/* Spinner symbol when the chat message is loading */
|
|
2608
|
-
.spinner {
|
|
2609
|
-
border: 4px solid #f3f3f3;
|
|
2610
|
-
border-top: 4px solid var(--primary-inverse);
|
|
2611
|
-
border-radius: 50%;
|
|
2612
|
-
width: 12px;
|
|
2613
|
-
height: 12px;
|
|
2614
|
-
animation: spin 2s linear infinite;
|
|
2615
|
-
margin: 0px 0px 0px 10px;
|
|
2616
|
-
display: inline-block;
|
|
2617
|
-
}
|
|
2618
|
-
@keyframes spin {
|
|
2619
|
-
0% { transform: rotate(0deg); }
|
|
2620
|
-
100% { transform: rotate(360deg); }
|
|
2621
|
-
}
|
|
2622
|
-
/* add left protrusion to khoj chat bubble */
|
|
2623
|
-
.chat-message-text.khoj:after {
|
|
2624
|
-
content: '';
|
|
2625
|
-
position: absolute;
|
|
2626
|
-
bottom: -2px;
|
|
2627
|
-
left: -7px;
|
|
2628
|
-
border: 10px solid transparent;
|
|
2629
|
-
border-top-color: var(--primary);
|
|
2630
|
-
border-bottom: 0;
|
|
2631
|
-
transform: rotate(-60deg);
|
|
2632
|
-
}
|
|
2633
|
-
/* color chat bubble by you dark grey */
|
|
2634
|
-
.chat-message-text.you {
|
|
2635
|
-
color: #f8fafc;
|
|
2636
|
-
background: #475569;
|
|
2637
|
-
margin-right: auto;
|
|
2638
|
-
}
|
|
2639
|
-
/* add right protrusion to you chat bubble */
|
|
2640
|
-
.chat-message-text.you:after {
|
|
2641
|
-
content: '';
|
|
2642
|
-
position: absolute;
|
|
2643
|
-
top: 91%;
|
|
2644
|
-
right: -2px;
|
|
2645
|
-
border: 10px solid transparent;
|
|
2646
|
-
border-left-color: var(--main-text-color);
|
|
2647
|
-
border-right: 0;
|
|
2648
|
-
margin-top: -10px;
|
|
2649
|
-
transform: rotate(-60deg)
|
|
2650
|
-
}
|
|
2651
|
-
img.text-to-image {
|
|
2652
|
-
max-width: 60%;
|
|
2653
|
-
}
|
|
2654
|
-
h3 > img.text-to-image {
|
|
2655
|
-
height: 24px;
|
|
2656
|
-
vertical-align: sub;
|
|
2657
|
-
}
|
|
2658
|
-
|
|
2659
|
-
#chat-footer {
|
|
2660
|
-
padding: 0;
|
|
2661
|
-
margin: 8px;
|
|
2662
|
-
display: grid;
|
|
2663
|
-
grid-template-columns: minmax(70px, 100%);
|
|
2664
|
-
grid-column-gap: 10px;
|
|
2665
|
-
grid-row-gap: 10px;
|
|
2666
|
-
}
|
|
2667
|
-
#input-row {
|
|
2668
|
-
display: grid;
|
|
2669
|
-
grid-template-columns: 32px auto 40px 32px;
|
|
2670
|
-
grid-column-gap: 10px;
|
|
2671
|
-
grid-row-gap: 10px;
|
|
2672
|
-
background: var(--background-color);
|
|
2673
|
-
align-items: center;
|
|
2674
|
-
}
|
|
2675
|
-
.option:hover {
|
|
2676
|
-
box-shadow: 0 0 11px #aaa;
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2679
|
-
.helpoption:hover {
|
|
2680
|
-
background-color: #d9d9d9;
|
|
2681
|
-
}
|
|
2682
|
-
|
|
2683
|
-
#chat-input {
|
|
2684
|
-
font-family: var(--font-family);
|
|
2685
|
-
font-size: medium;
|
|
2686
|
-
height: 48px;
|
|
2687
|
-
border-radius: 16px;
|
|
2688
|
-
resize: none;
|
|
2689
|
-
overflow-y: hidden;
|
|
2690
|
-
max-height: 200px;
|
|
2691
|
-
box-sizing: border-box;
|
|
2692
|
-
padding: 8px 0 0 12px;
|
|
2693
|
-
line-height: 1.5em;
|
|
2694
|
-
margin: 0;
|
|
2695
|
-
}
|
|
2696
|
-
#chat-input:focus {
|
|
2697
|
-
outline: none !important;
|
|
2698
|
-
}
|
|
2699
|
-
.input-row-button {
|
|
2700
|
-
background: var(--background-color);
|
|
2701
|
-
border: none;
|
|
2702
|
-
box-shadow: none;
|
|
2703
|
-
border-radius: 50%;
|
|
2704
|
-
font-size: small;
|
|
2705
|
-
font-weight: 300;
|
|
2706
|
-
line-height: 1.5em;
|
|
2707
|
-
cursor: pointer;
|
|
2708
|
-
transition: background 0.3s ease-in-out;
|
|
2709
|
-
width: 40px;
|
|
2710
|
-
height: 40px;
|
|
2711
|
-
margin-top: -2px;
|
|
2712
|
-
margin-left: -5px;
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
.side-panel-button {
|
|
2716
|
-
background: none;
|
|
2717
|
-
border: none;
|
|
2718
|
-
box-shadow: none;
|
|
2719
|
-
font-size: small;
|
|
2720
|
-
font-weight: 300;
|
|
2721
|
-
line-height: 1.5em;
|
|
2722
|
-
cursor: pointer;
|
|
2723
|
-
transition: background 0.3s ease-in-out;
|
|
2724
|
-
border-radius: 5%;;
|
|
2725
|
-
font-family: var(--font-family);
|
|
2726
|
-
}
|
|
2727
|
-
|
|
2728
|
-
svg#side-panel-collapse {
|
|
2729
|
-
width: 30px;
|
|
2730
|
-
height: 30px;
|
|
2731
|
-
}
|
|
2732
|
-
|
|
2733
|
-
.side-panel-button:hover,
|
|
2734
|
-
.input-row-button:hover {
|
|
2735
|
-
background: var(--primary-hover);
|
|
2736
|
-
}
|
|
2737
|
-
.side-panel-button:active,
|
|
2738
|
-
.input-row-button:active {
|
|
2739
|
-
background: var(--primary-active);
|
|
2740
|
-
}
|
|
2741
|
-
|
|
2742
|
-
.input-row-button-img {
|
|
2743
|
-
width: 24px;
|
|
2744
|
-
height: 24px;
|
|
2745
|
-
}
|
|
2746
|
-
#send-button {
|
|
2747
|
-
padding: 0;
|
|
2748
|
-
position: relative;
|
|
2749
|
-
}
|
|
2750
|
-
#send-button-img {
|
|
2751
|
-
width: 28px;
|
|
2752
|
-
height: 28px;
|
|
2753
|
-
background: var(--primary-hover);
|
|
2754
|
-
border-radius: 50%;
|
|
2755
|
-
}
|
|
2756
|
-
|
|
2757
|
-
#stop-send-button-img {
|
|
2758
|
-
position: absolute;
|
|
2759
|
-
top: 6px;
|
|
2760
|
-
right: 6px;
|
|
2761
|
-
width: 28px;
|
|
2762
|
-
height: 28px;
|
|
2763
|
-
transform: rotateY(-180deg) rotateZ(-90deg);
|
|
2764
|
-
}
|
|
2765
|
-
#countdown-circle {
|
|
2766
|
-
stroke-dasharray: 44px; /* The circumference of the circle with 7px radius */
|
|
2767
|
-
stroke-dashoffset: 0px;
|
|
2768
|
-
stroke-linecap: round;
|
|
2769
|
-
stroke-width: 1px;
|
|
2770
|
-
stroke: var(--main-text-color);
|
|
2771
|
-
fill: none;
|
|
2772
|
-
}
|
|
2773
|
-
@keyframes countdown {
|
|
2774
|
-
from {
|
|
2775
|
-
stroke-dashoffset: 0px;
|
|
2776
|
-
}
|
|
2777
|
-
to {
|
|
2778
|
-
stroke-dashoffset: -44px; /* The circumference of the circle with 7px radius */
|
|
2779
|
-
}
|
|
2780
|
-
}
|
|
2781
|
-
|
|
2782
|
-
.option-enabled {
|
|
2783
|
-
box-shadow: 0 0 12px rgb(119, 156, 46);
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
|
-
.option-enabled:focus {
|
|
2787
|
-
outline: none !important;
|
|
2788
|
-
border:1px solid #475569;
|
|
2789
|
-
box-shadow: 0 0 16px var(--primary);
|
|
2790
|
-
}
|
|
2791
|
-
|
|
2792
|
-
a.inline-chat-link {
|
|
2793
|
-
color: var(--main-text-color);
|
|
2794
|
-
text-decoration: none;
|
|
2795
|
-
border-bottom: 1px dotted var(--main-text-color);
|
|
2796
|
-
}
|
|
2797
|
-
|
|
2798
|
-
a.reference-link {
|
|
2799
|
-
color: var(--main-text-color);
|
|
2800
|
-
border-bottom: 1px dotted var(--main-text-color);
|
|
2801
|
-
}
|
|
2802
|
-
|
|
2803
|
-
button.copy-button {
|
|
2804
|
-
border-radius: 4px;
|
|
2805
|
-
background-color: var(--background-color);
|
|
2806
|
-
border: 1px solid var(--main-text-color);
|
|
2807
|
-
text-align: center;
|
|
2808
|
-
font-size: medium;
|
|
2809
|
-
transition: all 0.5s;
|
|
2810
|
-
cursor: pointer;
|
|
2811
|
-
padding: 4px;
|
|
2812
|
-
float: right;
|
|
2813
|
-
}
|
|
2814
|
-
|
|
2815
|
-
img.speech-icon {
|
|
2816
|
-
width: 18px;
|
|
2817
|
-
}
|
|
2818
|
-
|
|
2819
|
-
button.thumbs-up-button,
|
|
2820
|
-
button.thumbs-down-button,
|
|
2821
|
-
button.speech-button {
|
|
2822
|
-
border-radius: 4px;
|
|
2823
|
-
background-color: var(--background-color);
|
|
2824
|
-
border: 1px solid var(--main-text-color);
|
|
2825
|
-
text-align: center;
|
|
2826
|
-
font-size: medium;
|
|
2827
|
-
transition: all 0.5s;
|
|
2828
|
-
cursor: pointer;
|
|
2829
|
-
padding: 4px;
|
|
2830
|
-
float: right;
|
|
2831
|
-
margin-right: 4px;
|
|
2832
|
-
}
|
|
2833
|
-
|
|
2834
|
-
button.copy-button span {
|
|
2835
|
-
cursor: pointer;
|
|
2836
|
-
display: inline-block;
|
|
2837
|
-
position: relative;
|
|
2838
|
-
transition: 0.5s;
|
|
2839
|
-
}
|
|
2840
|
-
|
|
2841
|
-
img.copy-icon {
|
|
2842
|
-
width: 18px;
|
|
2843
|
-
height: 18px;
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
img.thumbs-up-icon {
|
|
2847
|
-
width: 18px;
|
|
2848
|
-
height: 18px;
|
|
2849
|
-
}
|
|
2850
|
-
|
|
2851
|
-
img.thumbs-down-icon {
|
|
2852
|
-
width: 18px;
|
|
2853
|
-
height: 18px;
|
|
2854
|
-
}
|
|
2855
|
-
|
|
2856
|
-
button.copy-button:hover,
|
|
2857
|
-
button.thumbs-up-button:hover,
|
|
2858
|
-
button.thumbs-down-button:hover,
|
|
2859
|
-
button.speech-button:hover {
|
|
2860
|
-
background-color: var(--primary-hover);
|
|
2861
|
-
color: #f5f5f5;
|
|
2862
|
-
}
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
pre {
|
|
2866
|
-
text-wrap: unset;
|
|
2867
|
-
}
|
|
2868
|
-
|
|
2869
|
-
@media (pointer: coarse), (hover: none) {
|
|
2870
|
-
abbr[title] {
|
|
2871
|
-
position: relative;
|
|
2872
|
-
padding-left: 4px; /* space references out to ease tapping */
|
|
2873
|
-
}
|
|
2874
|
-
abbr[title]:focus:after {
|
|
2875
|
-
content: attr(title);
|
|
2876
|
-
|
|
2877
|
-
/* position tooltip */
|
|
2878
|
-
position: absolute;
|
|
2879
|
-
left: 16px; /* open tooltip to right of ref link, instead of on top of it */
|
|
2880
|
-
width: auto;
|
|
2881
|
-
z-index: 1; /* show tooltip above chat messages */
|
|
2882
|
-
|
|
2883
|
-
/* style tooltip */
|
|
2884
|
-
background-color: #aaa;
|
|
2885
|
-
color: #f8fafc;
|
|
2886
|
-
border-radius: 2px;
|
|
2887
|
-
box-shadow: 1px 1px 4px 0 rgba(0, 0, 0, 0.4);
|
|
2888
|
-
font-size: small;
|
|
2889
|
-
padding: 2px 4px;
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
@media only screen and (max-width: 700px) {
|
|
2893
|
-
body {
|
|
2894
|
-
grid-template-columns: 1fr;
|
|
2895
|
-
grid-template-rows: auto auto minmax(80px, 100%) auto;
|
|
2896
|
-
}
|
|
2897
|
-
body > * {
|
|
2898
|
-
grid-column: 1;
|
|
2899
|
-
}
|
|
2900
|
-
#chat-footer {
|
|
2901
|
-
padding: 0;
|
|
2902
|
-
margin: 4px;
|
|
2903
|
-
grid-template-columns: auto;
|
|
2904
|
-
}
|
|
2905
|
-
img.text-to-image {
|
|
2906
|
-
max-width: 100%;
|
|
2907
|
-
}
|
|
2908
|
-
#clear-chat-button {
|
|
2909
|
-
margin-left: 0;
|
|
2910
|
-
}
|
|
2911
|
-
|
|
2912
|
-
div#side-panel.collapsed {
|
|
2913
|
-
width: 0px;
|
|
2914
|
-
display: block;
|
|
2915
|
-
overflow: hidden;
|
|
2916
|
-
padding: 0;
|
|
2917
|
-
}
|
|
2918
|
-
|
|
2919
|
-
svg#side-panel-collapse {
|
|
2920
|
-
width: 24px;
|
|
2921
|
-
height: 24px;
|
|
2922
|
-
}
|
|
2923
|
-
|
|
2924
|
-
#chat-body-wrapper {
|
|
2925
|
-
min-width: 0;
|
|
2926
|
-
}
|
|
2927
|
-
|
|
2928
|
-
div#chat-section-wrapper {
|
|
2929
|
-
padding: 4px;
|
|
2930
|
-
margin: 4px;
|
|
2931
|
-
grid-column-gap: 4px;
|
|
2932
|
-
}
|
|
2933
|
-
div#collapse-side-panel {
|
|
2934
|
-
align-self: center;
|
|
2935
|
-
padding: 0px;
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
@media only screen and (min-width: 700px) {
|
|
2939
|
-
body {
|
|
2940
|
-
grid-template-columns: auto min(90vw, 100%) auto;
|
|
2941
|
-
grid-template-rows: auto auto minmax(80px, 100%) auto;
|
|
2942
|
-
}
|
|
2943
|
-
body > * {
|
|
2944
|
-
grid-column: 2;
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
|
|
2948
|
-
div#chat-tooltip {
|
|
2949
|
-
text-align: left;
|
|
2950
|
-
font-size: medium;
|
|
2951
|
-
}
|
|
2952
|
-
div#chat-tooltip:hover {
|
|
2953
|
-
cursor: pointer;
|
|
2954
|
-
}
|
|
2955
|
-
|
|
2956
|
-
svg.new-convo-button {
|
|
2957
|
-
width: 20px;
|
|
2958
|
-
margin-left: 5px;
|
|
2959
|
-
margin-top: 2px;
|
|
2960
|
-
}
|
|
2961
|
-
|
|
2962
|
-
svg.file-toggle-button:hover {
|
|
2963
|
-
background: var(--primary-hover);
|
|
2964
|
-
cursor: pointer;
|
|
2965
|
-
}
|
|
2966
|
-
|
|
2967
|
-
div#new-conversation {
|
|
2968
|
-
display: grid;
|
|
2969
|
-
grid-auto-flow: column;
|
|
2970
|
-
grid-template-columns: 1fr auto;
|
|
2971
|
-
font-size: medium;
|
|
2972
|
-
text-align: left;
|
|
2973
|
-
border-bottom: 1px solid var(--main-text-color);
|
|
2974
|
-
margin-top: 8px;
|
|
2975
|
-
margin-bottom: 8px;
|
|
2976
|
-
}
|
|
2977
|
-
|
|
2978
|
-
button#copy-share-url-button,
|
|
2979
|
-
button#new-conversation-button {
|
|
2980
|
-
display: grid;
|
|
2981
|
-
grid-auto-flow: column;
|
|
2982
|
-
margin-top: 2px;
|
|
2983
|
-
}
|
|
2984
|
-
|
|
2985
|
-
div.conversation-button {
|
|
2986
|
-
background: var(--background-color);
|
|
2987
|
-
color: var(--main-text-color);
|
|
2988
|
-
border: 1px solid var(--main-text-color);
|
|
2989
|
-
border-radius: 5px;
|
|
2990
|
-
padding: 5px;
|
|
2991
|
-
font-size: small;
|
|
2992
|
-
font-weight: 300;
|
|
2993
|
-
line-height: 2em;
|
|
2994
|
-
cursor: pointer;
|
|
2995
|
-
transition: background 0.2s ease-in-out;
|
|
2996
|
-
text-align: left;
|
|
2997
|
-
display: flex;
|
|
2998
|
-
position: relative;
|
|
2999
|
-
margin-right: 8px;
|
|
3000
|
-
}
|
|
3001
|
-
|
|
3002
|
-
.three-dot-menu {
|
|
3003
|
-
display: block;
|
|
3004
|
-
border-radius: 5px;
|
|
3005
|
-
position: absolute;
|
|
3006
|
-
right: 4px;
|
|
3007
|
-
top: 4px;
|
|
3008
|
-
}
|
|
3009
|
-
|
|
3010
|
-
button.three-dot-menu-button-item {
|
|
3011
|
-
background: var(--background-color);
|
|
3012
|
-
color: var(--main-text-color);
|
|
3013
|
-
border: none;
|
|
3014
|
-
box-shadow: none;
|
|
3015
|
-
font-size: 14px;
|
|
3016
|
-
font-weight: 300;
|
|
3017
|
-
line-height: 1.5em;
|
|
3018
|
-
cursor: pointer;
|
|
3019
|
-
transition: background 0.3s ease-in-out;
|
|
3020
|
-
font-family: var(--font-family);
|
|
3021
|
-
border-radius: 4px;
|
|
3022
|
-
right: 0;
|
|
3023
|
-
}
|
|
3024
|
-
|
|
3025
|
-
button.three-dot-menu-button-item:hover {
|
|
3026
|
-
background: var(--primary-hover);
|
|
3027
|
-
color: var(--primary-inverse);
|
|
3028
|
-
}
|
|
3029
|
-
|
|
3030
|
-
.three-dot-menu-button {
|
|
3031
|
-
background: var(--background-color);
|
|
3032
|
-
border: none;
|
|
3033
|
-
box-shadow: none;
|
|
3034
|
-
font-size: 14px;
|
|
3035
|
-
font-weight: 300;
|
|
3036
|
-
line-height: 1.5em;
|
|
3037
|
-
cursor: pointer;
|
|
3038
|
-
transition: background 0.3s ease-in-out;
|
|
3039
|
-
font-family: var(--font-family);
|
|
3040
|
-
border-radius: 4px;
|
|
3041
|
-
right: 0;
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
|
-
.conversation-button:hover .three-dot-menu {
|
|
3045
|
-
display: block;
|
|
3046
|
-
}
|
|
3047
|
-
|
|
3048
|
-
div.conversation-menu {
|
|
3049
|
-
position: absolute;
|
|
3050
|
-
z-index: 1;
|
|
3051
|
-
top: 100%;
|
|
3052
|
-
right: 0;
|
|
3053
|
-
text-align: right;
|
|
3054
|
-
background-color: var(--background-color);
|
|
3055
|
-
border: 1px solid var(--main-text-color);
|
|
3056
|
-
border-radius: 5px;
|
|
3057
|
-
padding: 5px;
|
|
3058
|
-
box-shadow: 0 0 11px #aaa;
|
|
3059
|
-
}
|
|
3060
|
-
|
|
3061
|
-
div.conversation-button:hover {
|
|
3062
|
-
background: var(--primary-hover);
|
|
3063
|
-
color: var(--primary-inverse);
|
|
3064
|
-
}
|
|
3065
|
-
|
|
3066
|
-
div.selected-conversation {
|
|
3067
|
-
background: var(--primary-hover) !important;
|
|
3068
|
-
color: var(--primary-inverse) !important;
|
|
3069
|
-
}
|
|
3070
|
-
|
|
3071
|
-
@keyframes gradient {
|
|
3072
|
-
0% {
|
|
3073
|
-
background-position: 0% 50%;
|
|
3074
|
-
}
|
|
3075
|
-
50% {
|
|
3076
|
-
background-position: 100% 50%;
|
|
3077
|
-
}
|
|
3078
|
-
100% {
|
|
3079
|
-
background-position: 0% 50%;
|
|
3080
|
-
}
|
|
3081
|
-
}
|
|
3082
|
-
|
|
3083
|
-
#connection-status {
|
|
3084
|
-
display: grid;
|
|
3085
|
-
grid-auto-flow: column;
|
|
3086
|
-
grid-template-columns: auto 1fr;
|
|
3087
|
-
align-items: center;
|
|
3088
|
-
border-top: 1px solid black;
|
|
3089
|
-
}
|
|
3090
|
-
#connection-status-icon {
|
|
3091
|
-
width: 10px;
|
|
3092
|
-
height: 10px;
|
|
3093
|
-
border-radius: 50%;
|
|
3094
|
-
margin-right: 5px;
|
|
3095
|
-
}
|
|
3096
|
-
#connection-status-text {
|
|
3097
|
-
margin: 5px;
|
|
3098
|
-
}
|
|
3099
|
-
|
|
3100
|
-
a.khoj-logo {
|
|
3101
|
-
text-align: center;
|
|
3102
|
-
}
|
|
3103
|
-
|
|
3104
|
-
div.khoj-empty-container {
|
|
3105
|
-
margin: 0px;
|
|
3106
|
-
padding: 0px;
|
|
3107
|
-
}
|
|
3108
|
-
|
|
3109
|
-
p {
|
|
3110
|
-
margin: 0;
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
div.programmatic-output {
|
|
3114
|
-
background-color: #f5f5f5;
|
|
3115
|
-
border: 1px solid #ddd;
|
|
3116
|
-
border-radius: 3px;
|
|
3117
|
-
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
|
|
3118
|
-
color: #333;
|
|
3119
|
-
font-family: monospace;
|
|
3120
|
-
font-size: 14px;
|
|
3121
|
-
line-height: 1.5;
|
|
3122
|
-
margin: 10px 0;
|
|
3123
|
-
overflow-x: auto;
|
|
3124
|
-
padding: 10px;
|
|
3125
|
-
white-space: pre-wrap;
|
|
3126
|
-
}
|
|
3127
|
-
|
|
3128
|
-
.loader {
|
|
3129
|
-
width: 18px;
|
|
3130
|
-
height: 18px;
|
|
3131
|
-
border: 3px solid #FFF;
|
|
3132
|
-
border-radius: 50%;
|
|
3133
|
-
display: inline-block;
|
|
3134
|
-
position: relative;
|
|
3135
|
-
box-sizing: border-box;
|
|
3136
|
-
animation: rotation 1s linear infinite;
|
|
3137
|
-
}
|
|
3138
|
-
.loader::after {
|
|
3139
|
-
content: '';
|
|
3140
|
-
box-sizing: border-box;
|
|
3141
|
-
position: absolute;
|
|
3142
|
-
left: 50%;
|
|
3143
|
-
top: 50%;
|
|
3144
|
-
transform: translate(-50%, -50%);
|
|
3145
|
-
width: 18px;
|
|
3146
|
-
height: 18px;
|
|
3147
|
-
border-radius: 50%;
|
|
3148
|
-
border: 3px solid transparent;
|
|
3149
|
-
border-bottom-color: var(--flower);
|
|
3150
|
-
}
|
|
3151
|
-
|
|
3152
|
-
@keyframes rotation {
|
|
3153
|
-
0% {
|
|
3154
|
-
transform: rotate(0deg);
|
|
3155
|
-
}
|
|
3156
|
-
100% {
|
|
3157
|
-
transform: rotate(360deg);
|
|
3158
|
-
}
|
|
3159
|
-
}
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
.loading-spinner {
|
|
3163
|
-
display: inline-block;
|
|
3164
|
-
position: relative;
|
|
3165
|
-
width: 80px;
|
|
3166
|
-
height: 80px;
|
|
3167
|
-
}
|
|
3168
|
-
.loading-spinner div {
|
|
3169
|
-
position: absolute;
|
|
3170
|
-
border: 4px solid var(--primary-hover);
|
|
3171
|
-
opacity: 1;
|
|
3172
|
-
border-radius: 50%;
|
|
3173
|
-
animation: lds-ripple 0.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
|
3174
|
-
}
|
|
3175
|
-
.loading-spinner div:nth-child(2) {
|
|
3176
|
-
animation-delay: -0.5s;
|
|
3177
|
-
}
|
|
3178
|
-
@keyframes lds-ripple {
|
|
3179
|
-
0% {
|
|
3180
|
-
top: 36px;
|
|
3181
|
-
left: 36px;
|
|
3182
|
-
width: 0;
|
|
3183
|
-
height: 0;
|
|
3184
|
-
opacity: 1;
|
|
3185
|
-
border-color: var(--primary-hover);
|
|
3186
|
-
}
|
|
3187
|
-
50% {
|
|
3188
|
-
border-color: var(--flower);
|
|
3189
|
-
}
|
|
3190
|
-
100% {
|
|
3191
|
-
top: 0px;
|
|
3192
|
-
left: 0px;
|
|
3193
|
-
width: 72px;
|
|
3194
|
-
height: 72px;
|
|
3195
|
-
opacity: 0;
|
|
3196
|
-
border-color: var(--water);
|
|
3197
|
-
}
|
|
3198
|
-
}
|
|
3199
|
-
|
|
3200
|
-
#agent-metadata-content {
|
|
3201
|
-
display: grid;
|
|
3202
|
-
grid-template-columns: auto 1fr;
|
|
3203
|
-
padding: 10px;
|
|
3204
|
-
background-color: var(--primary);
|
|
3205
|
-
border-radius: 5px;
|
|
3206
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
3207
|
-
margin-bottom: 20px;
|
|
3208
|
-
}
|
|
3209
|
-
|
|
3210
|
-
#agent-metadata {
|
|
3211
|
-
border-top: 1px solid black;
|
|
3212
|
-
padding-top: 10px;
|
|
3213
|
-
}
|
|
3214
|
-
|
|
3215
|
-
#agent-avatar-wrapper {
|
|
3216
|
-
margin-right: 10px;
|
|
3217
|
-
}
|
|
3218
|
-
|
|
3219
|
-
#agent-avatar {
|
|
3220
|
-
width: 50px;
|
|
3221
|
-
height: 50px;
|
|
3222
|
-
border-radius: 50%;
|
|
3223
|
-
object-fit: cover;
|
|
3224
|
-
}
|
|
3225
|
-
|
|
3226
|
-
#agent-name-wrapper {
|
|
3227
|
-
display: grid;
|
|
3228
|
-
align-items: center;
|
|
3229
|
-
}
|
|
3230
|
-
|
|
3231
|
-
#agent-name {
|
|
3232
|
-
font-size: 18px;
|
|
3233
|
-
font-weight: bold;
|
|
3234
|
-
color: #333;
|
|
3235
|
-
}
|
|
3236
|
-
|
|
3237
|
-
#agent-owned-by-user {
|
|
3238
|
-
font-size: 12px;
|
|
3239
|
-
color: #007BFF;
|
|
3240
|
-
margin-top: 5px;
|
|
3241
|
-
}
|
|
3242
|
-
|
|
3243
|
-
.modal {
|
|
3244
|
-
position: fixed; /* Stay in place */
|
|
3245
|
-
z-index: 1; /* Sit on top */
|
|
3246
|
-
left: 0;
|
|
3247
|
-
top: 0;
|
|
3248
|
-
width: 100%; /* Full width */
|
|
3249
|
-
height: 100%; /* Full height */
|
|
3250
|
-
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
|
3251
|
-
margin: 0px;
|
|
3252
|
-
}
|
|
3253
|
-
|
|
3254
|
-
.modal-content {
|
|
3255
|
-
margin: 15% auto; /* 15% from the top and centered */
|
|
3256
|
-
padding: 20px;
|
|
3257
|
-
border: 1px solid #888;
|
|
3258
|
-
width: 300px;
|
|
3259
|
-
text-align: left;
|
|
3260
|
-
background: var(--background-color);
|
|
3261
|
-
border-radius: 5px;
|
|
3262
|
-
box-shadow: 0 0 11px #aaa;
|
|
3263
|
-
text-align: left;
|
|
3264
|
-
}
|
|
3265
|
-
|
|
3266
|
-
.modal-header {
|
|
3267
|
-
display: grid;
|
|
3268
|
-
grid-template-columns: 1fr auto;
|
|
3269
|
-
color: var(--main-text-color);
|
|
3270
|
-
align-items: baseline;
|
|
3271
|
-
}
|
|
3272
|
-
|
|
3273
|
-
.modal-header h2 {
|
|
3274
|
-
margin: 0;
|
|
3275
|
-
text-align: left;
|
|
3276
|
-
}
|
|
3277
|
-
|
|
3278
|
-
.modal-body {
|
|
3279
|
-
display: grid;
|
|
3280
|
-
grid-auto-flow: row;
|
|
3281
|
-
gap: 8px;
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
|
-
.modal-body a {
|
|
3285
|
-
/* text-decoration: none; */
|
|
3286
|
-
color: var(--summer-sun);
|
|
3287
|
-
}
|
|
3288
|
-
|
|
3289
|
-
.modal-close-button {
|
|
3290
|
-
margin: 0;
|
|
3291
|
-
font-size: 20px;
|
|
3292
|
-
background: none;
|
|
3293
|
-
border: none;
|
|
3294
|
-
color: var(--summer-sun);
|
|
3295
|
-
}
|
|
3296
|
-
|
|
3297
|
-
.modal-close-button:hover,
|
|
3298
|
-
.modal-close-button:focus {
|
|
3299
|
-
color: #000;
|
|
3300
|
-
text-decoration: none;
|
|
3301
|
-
cursor: pointer;
|
|
3302
|
-
}
|
|
3303
|
-
|
|
3304
|
-
#new-conversation-form {
|
|
3305
|
-
display: flex;
|
|
3306
|
-
flex-direction: column;
|
|
3307
|
-
}
|
|
3308
|
-
|
|
3309
|
-
#new-conversation-form label,
|
|
3310
|
-
#new-conversation-form input,
|
|
3311
|
-
#new-conversation-form button {
|
|
3312
|
-
margin-bottom: 10px;
|
|
3313
|
-
}
|
|
3314
|
-
|
|
3315
|
-
#new-conversation-form button {
|
|
3316
|
-
cursor: pointer;
|
|
3317
|
-
}
|
|
3318
|
-
|
|
3319
|
-
.modal-footer {
|
|
3320
|
-
display: grid;
|
|
3321
|
-
grid-template-columns: 1fr 1fr;
|
|
3322
|
-
grid-gap: 12px;
|
|
3323
|
-
}
|
|
3324
|
-
|
|
3325
|
-
.modal-body button {
|
|
3326
|
-
cursor: pointer;
|
|
3327
|
-
border-radius: 12px;
|
|
3328
|
-
padding: 8px;
|
|
3329
|
-
border: 1px solid var(--main-text-color);
|
|
3330
|
-
}
|
|
3331
|
-
|
|
3332
|
-
.share-link {
|
|
3333
|
-
display: block;
|
|
3334
|
-
width: 100%;
|
|
3335
|
-
padding: 10px;
|
|
3336
|
-
margin-top: 10px;
|
|
3337
|
-
border: 1px solid #ccc;
|
|
3338
|
-
border-radius: 4px;
|
|
3339
|
-
background-color: #f9f9f9;
|
|
3340
|
-
font-family: 'Courier New', monospace;
|
|
3341
|
-
color: #333;
|
|
3342
|
-
font-size: 16px;
|
|
3343
|
-
box-sizing: border-box;
|
|
3344
|
-
transition: all 0.3s ease;
|
|
3345
|
-
}
|
|
3346
|
-
|
|
3347
|
-
.share-link:focus {
|
|
3348
|
-
outline: none;
|
|
3349
|
-
border-color: #007BFF;
|
|
3350
|
-
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
|
|
3351
|
-
}
|
|
3352
|
-
|
|
3353
|
-
button#copy-share-url-button,
|
|
3354
|
-
button#new-conversation-submit-button {
|
|
3355
|
-
background: var(--summer-sun);
|
|
3356
|
-
transition: background 0.2s ease-in-out;
|
|
3357
|
-
}
|
|
3358
|
-
|
|
3359
|
-
button#close-button {
|
|
3360
|
-
background: var(--background-color);
|
|
3361
|
-
transition: background 0.2s ease-in-out;
|
|
3362
|
-
}
|
|
3363
|
-
|
|
3364
|
-
button#copy-share-url-button:hover,
|
|
3365
|
-
button#new-conversation-submit-button:hover {
|
|
3366
|
-
background: var(--primary);
|
|
3367
|
-
}
|
|
3368
|
-
|
|
3369
|
-
button#close-button:hover {
|
|
3370
|
-
background: var(--primary-hover);
|
|
3371
|
-
}
|
|
3372
|
-
|
|
3373
|
-
.modal-body select {
|
|
3374
|
-
padding: 8px;
|
|
3375
|
-
border-radius: 12px;
|
|
3376
|
-
border: 1px solid var(--main-text-color);
|
|
3377
|
-
}
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
.lds-ellipsis {
|
|
3381
|
-
display: inline-block;
|
|
3382
|
-
position: relative;
|
|
3383
|
-
width: 60px;
|
|
3384
|
-
height: 32px;
|
|
3385
|
-
}
|
|
3386
|
-
.lds-ellipsis div {
|
|
3387
|
-
position: absolute;
|
|
3388
|
-
top: 12px;
|
|
3389
|
-
width: 8px;
|
|
3390
|
-
height: 8px;
|
|
3391
|
-
border-radius: 50%;
|
|
3392
|
-
background: var(--main-text-color);
|
|
3393
|
-
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
|
3394
|
-
}
|
|
3395
|
-
.lds-ellipsis div:nth-child(1) {
|
|
3396
|
-
left: 8px;
|
|
3397
|
-
animation: lds-ellipsis1 0.6s infinite;
|
|
3398
|
-
}
|
|
3399
|
-
.lds-ellipsis div:nth-child(2) {
|
|
3400
|
-
left: 8px;
|
|
3401
|
-
animation: lds-ellipsis2 0.6s infinite;
|
|
3402
|
-
}
|
|
3403
|
-
.lds-ellipsis div:nth-child(3) {
|
|
3404
|
-
left: 32px;
|
|
3405
|
-
animation: lds-ellipsis2 0.6s infinite;
|
|
3406
|
-
}
|
|
3407
|
-
.lds-ellipsis div:nth-child(4) {
|
|
3408
|
-
left: 56px;
|
|
3409
|
-
animation: lds-ellipsis3 0.6s infinite;
|
|
3410
|
-
}
|
|
3411
|
-
@keyframes lds-ellipsis1 {
|
|
3412
|
-
0% {
|
|
3413
|
-
transform: scale(0);
|
|
3414
|
-
}
|
|
3415
|
-
100% {
|
|
3416
|
-
transform: scale(1);
|
|
3417
|
-
}
|
|
3418
|
-
}
|
|
3419
|
-
@keyframes lds-ellipsis3 {
|
|
3420
|
-
0% {
|
|
3421
|
-
transform: scale(1);
|
|
3422
|
-
}
|
|
3423
|
-
100% {
|
|
3424
|
-
transform: scale(0);
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
@keyframes lds-ellipsis2 {
|
|
3428
|
-
0% {
|
|
3429
|
-
transform: translate(0, 0);
|
|
3430
|
-
}
|
|
3431
|
-
100% {
|
|
3432
|
-
transform: translate(24px, 0);
|
|
3433
|
-
}
|
|
3434
|
-
}
|
|
3435
|
-
</style>
|
|
3436
|
-
</html>
|