Construct config page elements on Web via DOM scripting.

Minimize isage of innerHTML to prevent DOM clobbering and unintended
escape by user Input
This commit is contained in:
Debanjum Singh Solanky
2024-06-23 22:57:56 +05:30
parent 69c9e8cc08
commit 2f034f807a
7 changed files with 229 additions and 126 deletions

View File

@@ -242,18 +242,25 @@
<script> <script>
async function openChat(agentSlug) { async function openChat(agentSlug) {
// Create a loading animation // Create a loading animation
let loading = document.createElement("div"); let loadingTextEl = document.createElement("div");
loading.innerHTML = '<div>Booting your agent...</div><span class="loader"></span>'; loadingTextEl.textContent = 'Booting your agent...';
loading.style.position = "fixed";
loading.style.top = "0"; let loadingAnimationEl = document.createElement("span");
loading.style.right = "0"; loadingAnimationEl.className = "loader";
loading.style.bottom = "0";
loading.style.left = "0"; let loadingEl = document.createElement("div");
loading.style.display = "flex"; loadingEl.style.position = "fixed";
loading.style.justifyContent = "center"; loadingEl.style.top = "0";
loading.style.alignItems = "center"; loadingEl.style.right = "0";
loading.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; // Semi-transparent black loadingEl.style.bottom = "0";
document.body.appendChild(loading); loadingEl.style.left = "0";
loadingEl.style.display = "flex";
loadingEl.style.justifyContent = "center";
loadingEl.style.alignItems = "center";
loadingEl.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; // Semi-transparent black
loadingEl.append(loadingTextEl, loadingAnimationEl);
document.body.appendChild(loadingEl);
let response = await fetch(`/api/chat/sessions?agent_slug=${agentSlug}`, { method: "POST" }); let response = await fetch(`/api/chat/sessions?agent_slug=${agentSlug}`, { method: "POST" });
let data = await response.json(); let data = await response.json();

View File

@@ -149,7 +149,6 @@ To get started, just start typing below. You can also type / to see a list of co
} }
function generateOnlineReference(reference, index) { function generateOnlineReference(reference, index) {
// Generate HTML for Chat Reference // Generate HTML for Chat Reference
let title = reference.title || reference.link; let title = reference.title || reference.link;
let link = reference.link; let link = reference.link;
@@ -170,7 +169,7 @@ To get started, just start typing below. You can also type / to see a list of co
linkElement.textContent = title; linkElement.textContent = title;
let referenceButton = document.createElement('button'); let referenceButton = document.createElement('button');
referenceButton.innerHTML = linkElement.outerHTML; referenceButton.appendChild(linkElement);
referenceButton.id = `ref-${index}`; referenceButton.id = `ref-${index}`;
referenceButton.classList.add("reference-button"); referenceButton.classList.add("reference-button");
referenceButton.classList.add("collapsed"); referenceButton.classList.add("collapsed");
@@ -181,11 +180,12 @@ To get started, just start typing below. You can also type / to see a list of co
if (this.classList.contains("collapsed")) { if (this.classList.contains("collapsed")) {
this.classList.remove("collapsed"); this.classList.remove("collapsed");
this.classList.add("expanded"); this.classList.add("expanded");
this.innerHTML = linkElement.outerHTML + `<br><br>${question + snippet}`; this.innerHTML = `${linkElement.outerHTML}<br><br>${question}${snippet}`;
} else { } else {
this.classList.add("collapsed"); this.classList.add("collapsed");
this.classList.remove("expanded"); this.classList.remove("expanded");
this.innerHTML = linkElement.outerHTML; this.innerHTML = "";
this.appendChild(linkElement);
} }
}); });
@@ -578,7 +578,7 @@ To get started, just start typing below. You can also type / to see a list of co
let referenceExpandButton = document.createElement('button'); let referenceExpandButton = document.createElement('button');
referenceExpandButton.classList.add("reference-expand-button"); referenceExpandButton.classList.add("reference-expand-button");
referenceExpandButton.innerHTML = numReferences == 1 ? "1 reference" : `${numReferences} references`; referenceExpandButton.textContent = numReferences == 1 ? "1 reference" : `${numReferences} references`;
referenceExpandButton.addEventListener('click', function() { referenceExpandButton.addEventListener('click', function() {
if (referenceSection.classList.contains("collapsed")) { if (referenceSection.classList.contains("collapsed")) {
@@ -888,7 +888,7 @@ To get started, just start typing below. You can also type / to see a list of co
if (overlayText == null) { if (overlayText == null) {
dropzone.classList.add('dragover'); dropzone.classList.add('dragover');
var overlayText = document.createElement("div"); var overlayText = document.createElement("div");
overlayText.innerHTML = "Select file(s) or drag + drop it here to share it with Khoj"; overlayText.textContent = "Select file(s) or drag + drop it here to share it with Khoj";
overlayText.className = "dropzone-overlay"; overlayText.className = "dropzone-overlay";
overlayText.id = "dropzone-overlay"; overlayText.id = "dropzone-overlay";
dropzone.appendChild(overlayText); dropzone.appendChild(overlayText);
@@ -949,7 +949,7 @@ To get started, just start typing below. You can also type / to see a list of co
if (overlayText != null) { if (overlayText != null) {
// Display loading spinner // Display loading spinner
var loadingSpinner = document.createElement("div"); var loadingSpinner = document.createElement("div");
overlayText.innerHTML = "Uploading file(s) for indexing"; overlayText.textContent = "Uploading file(s) for indexing";
loadingSpinner.className = "spinner"; loadingSpinner.className = "spinner";
overlayText.appendChild(loadingSpinner); overlayText.appendChild(loadingSpinner);
} }
@@ -1042,7 +1042,7 @@ To get started, just start typing below. You can also type / to see a list of co
if (overlayText == null) { if (overlayText == null) {
var overlayText = document.createElement("div"); var overlayText = document.createElement("div");
overlayText.innerHTML = "Drop file to share it with Khoj"; overlayText.textContent = "Drop file to share it with Khoj";
overlayText.className = "dropzone-overlay"; overlayText.className = "dropzone-overlay";
overlayText.id = "dropzone-overlay"; overlayText.id = "dropzone-overlay";
this.appendChild(overlayText); this.appendChild(overlayText);
@@ -1179,11 +1179,15 @@ To get started, just start typing below. You can also type / to see a list of co
websocket.onclose = function(event) { websocket.onclose = function(event) {
websocket = null; websocket = null;
console.log("WebSocket is closed now."); console.log("WebSocket is closed now.");
let setupWebSocketButton = document.createElement("button");
setupWebSocketButton.textContent = "Reconnect to Server";
setupWebSocketButton.onclick = setupWebSocket;
let statusDotIcon = document.getElementById("connection-status-icon"); let statusDotIcon = document.getElementById("connection-status-icon");
statusDotIcon.style.backgroundColor = "red"; statusDotIcon.style.backgroundColor = "red";
let statusDotText = document.getElementById("connection-status-text"); let statusDotText = document.getElementById("connection-status-text");
statusDotText.innerHTML = "";
statusDotText.style.marginTop = "5px"; statusDotText.style.marginTop = "5px";
statusDotText.innerHTML = '<button onclick="setupWebSocket()">Reconnect to Server</button>'; statusDotText.appendChild(setupWebSocketButton);
} }
websocket.onerror = function(event) { websocket.onerror = function(event) {
console.log("WebSocket error observed:", event); console.log("WebSocket error observed:", event);
@@ -1434,7 +1438,7 @@ To get started, just start typing below. You can also type / to see a list of co
questionStarterSuggestions.innerHTML = ""; questionStarterSuggestions.innerHTML = "";
data.forEach((questionStarter) => { data.forEach((questionStarter) => {
let questionStarterButton = document.createElement('button'); let questionStarterButton = document.createElement('button');
questionStarterButton.innerHTML = questionStarter; questionStarterButton.textContent = questionStarter;
questionStarterButton.classList.add("question-starter"); questionStarterButton.classList.add("question-starter");
questionStarterButton.addEventListener('click', function() { questionStarterButton.addEventListener('click', function() {
questionStarterSuggestions.style.display = "none"; questionStarterSuggestions.style.display = "none";
@@ -1606,7 +1610,7 @@ To get started, just start typing below. You can also type / to see a list of co
let closeButton = document.createElement('button'); let closeButton = document.createElement('button');
closeButton.id = "close-button"; closeButton.id = "close-button";
closeButton.innerHTML = "Close"; closeButton.textContent = "Close";
closeButton.classList.add("close-button"); closeButton.classList.add("close-button");
closeButton.addEventListener('click', function() { closeButton.addEventListener('click', function() {
modal.remove(); modal.remove();
@@ -1660,7 +1664,7 @@ To get started, just start typing below. You can also type / to see a list of co
let threeDotMenu = document.createElement('div'); let threeDotMenu = document.createElement('div');
threeDotMenu.classList.add("three-dot-menu"); threeDotMenu.classList.add("three-dot-menu");
let threeDotMenuButton = document.createElement('button'); let threeDotMenuButton = document.createElement('button');
threeDotMenuButton.innerHTML = "⋮"; threeDotMenuButton.textContent = "⋮";
threeDotMenuButton.classList.add("three-dot-menu-button"); threeDotMenuButton.classList.add("three-dot-menu-button");
threeDotMenuButton.addEventListener('click', function(event) { threeDotMenuButton.addEventListener('click', function(event) {
event.stopPropagation(); event.stopPropagation();
@@ -1679,7 +1683,7 @@ To get started, just start typing below. You can also type / to see a list of co
conversationMenu.classList.add("conversation-menu"); conversationMenu.classList.add("conversation-menu");
let editTitleButton = document.createElement('button'); let editTitleButton = document.createElement('button');
editTitleButton.innerHTML = "Rename"; editTitleButton.textContent = "Rename";
editTitleButton.classList.add("edit-title-button"); editTitleButton.classList.add("edit-title-button");
editTitleButton.classList.add("three-dot-menu-button-item"); editTitleButton.classList.add("three-dot-menu-button-item");
editTitleButton.addEventListener('click', function(event) { editTitleButton.addEventListener('click', function(event) {
@@ -1713,7 +1717,7 @@ To get started, just start typing below. You can also type / to see a list of co
conversationTitleInputBox.appendChild(conversationTitleInput); conversationTitleInputBox.appendChild(conversationTitleInput);
let conversationTitleInputButton = document.createElement('button'); let conversationTitleInputButton = document.createElement('button');
conversationTitleInputButton.innerHTML = "Save"; conversationTitleInputButton.textContent = "Save";
conversationTitleInputButton.classList.add("three-dot-menu-button-item"); conversationTitleInputButton.classList.add("three-dot-menu-button-item");
conversationTitleInputButton.addEventListener('click', function(event) { conversationTitleInputButton.addEventListener('click', function(event) {
event.stopPropagation(); event.stopPropagation();
@@ -1737,7 +1741,7 @@ To get started, just start typing below. You can also type / to see a list of co
threeDotMenu.appendChild(conversationMenu); threeDotMenu.appendChild(conversationMenu);
let shareButton = document.createElement('button'); let shareButton = document.createElement('button');
shareButton.innerHTML = "Share"; shareButton.textContent = "Share";
shareButton.type = "button"; shareButton.type = "button";
shareButton.classList.add("share-conversation-button"); shareButton.classList.add("share-conversation-button");
shareButton.classList.add("three-dot-menu-button-item"); shareButton.classList.add("three-dot-menu-button-item");
@@ -1804,7 +1808,7 @@ To get started, just start typing below. You can also type / to see a list of co
let deleteButton = document.createElement('button'); let deleteButton = document.createElement('button');
deleteButton.type = "button"; deleteButton.type = "button";
deleteButton.innerHTML = "Delete"; deleteButton.textContent = "Delete";
deleteButton.classList.add("delete-conversation-button"); deleteButton.classList.add("delete-conversation-button");
deleteButton.classList.add("three-dot-menu-button-item"); deleteButton.classList.add("three-dot-menu-button-item");
deleteButton.addEventListener('click', function(event) { deleteButton.addEventListener('click', function(event) {
@@ -1968,12 +1972,16 @@ To get started, just start typing below. You can also type / to see a list of co
} }
allFiles = data; allFiles = data;
var nofilesmessage = document.getElementsByClassName("no-files-message")[0]; var nofilesmessage = document.getElementsByClassName("no-files-message")[0];
nofilesmessage.innerHTML = "";
if(allFiles.length === 0){ if(allFiles.length === 0){
nofilesmessage.innerHTML = `<a class="inline-chat-link" href="https://docs.khoj.dev/category/clients/">How to upload files</a>`; let inlineChatLinkEl = document.createElement('a');
inlineChatLinkEl.className = "inline-chat-link";
inlineChatLinkEl.href = "https://docs.khoj.dev/category/clients/";
inlineChatLinkEl.textContent = "How to upload files";
nofilesmessage.appendChild(inlineChatLinkEl);
document.getElementsByClassName("file-toggle-button")[0].style.display = "none"; document.getElementsByClassName("file-toggle-button")[0].style.display = "none";
} }
else{ else{
nofilesmessage.innerHTML = "";
document.getElementsByClassName("file-toggle-button")[0].style.display = "block"; document.getElementsByClassName("file-toggle-button")[0].style.display = "block";
} }
}) })

View File

@@ -163,10 +163,6 @@
<div class="section-cards"> <div class="section-cards">
<div class="finalize-buttons"> <div class="finalize-buttons">
<button id="sync" type="submit" title="Regenerate index from scratch for Notion, GitHub configuration" style="display: flex; justify-content: center;"> <button id="sync" type="submit" title="Regenerate index from scratch for Notion, GitHub configuration" style="display: flex; justify-content: center;">
<img class="card-icon" src="/static/assets/icons/sync.svg" alt="Sync">
<h3 class="card-title">
Sync
</h3>
</button> </button>
</div> </div>
</div> </div>
@@ -408,7 +404,8 @@
.then(data => { .then(data => {
if (data.status == "ok") { if (data.status == "ok") {
let notificationBanner = document.getElementById("notification-banner"); let notificationBanner = document.getElementById("notification-banner");
notificationBanner.innerHTML = "Profile name has been updated!"; notificationBanner.innerHTML = "";
notificationBanner.textContent = "Profile name has been updated!";
notificationBanner.style.display = "block"; notificationBanner.style.display = "block";
setTimeout(function() { setTimeout(function() {
notificationBanner.style.display = "none"; notificationBanner.style.display = "none";
@@ -420,8 +417,9 @@
function updateVoiceModel() { function updateVoiceModel() {
const voiceModel = document.getElementById("voice-models").value; const voiceModel = document.getElementById("voice-models").value;
const saveVoiceModelButton = document.getElementById("save-voice-model"); const saveVoiceModelButton = document.getElementById("save-voice-model");
saveVoiceModelButton.innerHTML = "";
saveVoiceModelButton.disabled = true; saveVoiceModelButton.disabled = true;
saveVoiceModelButton.innerHTML = "Saving..."; saveVoiceModelButton.textContent = "Saving...";
fetch('/api/config/data/voice/model?id=' + voiceModel, { fetch('/api/config/data/voice/model?id=' + voiceModel, {
method: 'POST', method: 'POST',
@@ -432,18 +430,19 @@
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.status == "ok") { if (data.status == "ok") {
saveVoiceModelButton.innerHTML = "Save"; saveVoiceModelButton.textContent = "Save";
saveVoiceModelButton.disabled = false; saveVoiceModelButton.disabled = false;
let notificationBanner = document.getElementById("notification-banner"); let notificationBanner = document.getElementById("notification-banner");
notificationBanner.innerHTML = "Voice model has been updated!"; notificationBanner.innerHTML = "";
notificationBanner.textContent = "Voice model has been updated!";
notificationBanner.style.display = "block"; notificationBanner.style.display = "block";
setTimeout(function() { setTimeout(function() {
notificationBanner.style.display = "none"; notificationBanner.style.display = "none";
}, 5000); }, 5000);
} else { } else {
saveVoiceModelButton.innerHTML = "Error"; saveVoiceModelButton.textContent = "Error";
saveVoiceModelButton.disabled = false; saveVoiceModelButton.disabled = false;
} }
}) })
@@ -453,7 +452,8 @@
const chatModel = document.getElementById("chat-models").value; const chatModel = document.getElementById("chat-models").value;
const saveModelButton = document.getElementById("save-chat-model"); const saveModelButton = document.getElementById("save-chat-model");
saveModelButton.disabled = true; saveModelButton.disabled = true;
saveModelButton.innerHTML = "Saving..."; saveModelButton.innerHTML = "";
saveModelButton.textContent = "Saving...";
fetch('/api/config/data/conversation/model?id=' + chatModel, { fetch('/api/config/data/conversation/model?id=' + chatModel, {
method: 'POST', method: 'POST',
@@ -464,18 +464,19 @@
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.status == "ok") { if (data.status == "ok") {
saveModelButton.innerHTML = "Save"; saveModelButton.textContent = "Save";
saveModelButton.disabled = false; saveModelButton.disabled = false;
let notificationBanner = document.getElementById("notification-banner"); let notificationBanner = document.getElementById("notification-banner");
notificationBanner.innerHTML = "Conversation model has been updated!"; notificationBanner.innerHTML = "";
notificationBanner.textContent = "Conversation model has been updated!";
notificationBanner.style.display = "block"; notificationBanner.style.display = "block";
setTimeout(function() { setTimeout(function() {
notificationBanner.style.display = "none"; notificationBanner.style.display = "none";
}, 5000); }, 5000);
} else { } else {
saveModelButton.innerHTML = "Error"; saveModelButton.textContent = "Error";
saveModelButton.disabled = false; saveModelButton.disabled = false;
} }
}) })
@@ -489,8 +490,9 @@
const searchModel = document.getElementById("search-models").value; const searchModel = document.getElementById("search-models").value;
const saveSearchModelButton = document.getElementById("save-search-model"); const saveSearchModelButton = document.getElementById("save-search-model");
saveSearchModelButton.innerHTML = "";
saveSearchModelButton.disabled = true; saveSearchModelButton.disabled = true;
saveSearchModelButton.innerHTML = "Saving..."; saveSearchModelButton.textContent = "Saving...";
fetch('/api/config/data/search/model?id=' + searchModel, { fetch('/api/config/data/search/model?id=' + searchModel, {
method: 'POST', method: 'POST',
@@ -501,15 +503,16 @@
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.status == "ok") { if (data.status == "ok") {
saveSearchModelButton.innerHTML = "Save"; saveSearchModelButton.textContent = "Save";
saveSearchModelButton.disabled = false; saveSearchModelButton.disabled = false;
} else { } else {
saveSearchModelButton.innerHTML = "Error"; saveSearchModelButton.textContent = "Error";
saveSearchModelButton.disabled = false; saveSearchModelButton.disabled = false;
} }
let notificationBanner = document.getElementById("notification-banner"); let notificationBanner = document.getElementById("notification-banner");
notificationBanner.innerHTML = "Khoj can now better understand the language of your content! Manually sync your data from one of the Khoj clients to update your knowledge base."; notificationBanner.innerHTML = "";
notificationBanner.textContent = "Khoj can now better understand the language of your content! Manually sync your data from one of the Khoj clients to update your knowledge base.";
notificationBanner.style.display = "block"; notificationBanner.style.display = "block";
setTimeout(function() { setTimeout(function() {
notificationBanner.style.display = "none"; notificationBanner.style.display = "none";
@@ -607,23 +610,38 @@
}) })
} }
var sync = document.getElementById("sync"); function populateSyncButton() {
sync.addEventListener("click", function(event) { let syncIconEl = document.createElement("img");
syncIconEl.className = "card-icon";
syncIconEl.src = "/static/assets/icons/sync.svg";
syncIconEl.alt = "Sync";
let syncButtonTitleEl = document.createElement("h3");
syncButtonTitleEl.className = "card-title";
syncButtonTitleEl.textContent = "Sync";
return [syncButtonTitleEl, syncIconEl];
}
var syncButtonEl = document.getElementById("sync");
syncButtonEl.innerHTML = "";
syncButtonEl.append(...populateSyncButton());
syncButtonEl.addEventListener("click", function(event) {
event.preventDefault(); event.preventDefault();
updateIndex( updateIndex(
force=true, force=true,
successText="Synced!", successText="Synced!",
errorText="Unable to sync. Raise issue on Khoj <a href='https://github.com/khoj-ai/khoj/issues'>Github</a> or <a href='https://discord.gg/BDgyabRM6e'>Discord</a>.", errorText="Unable to sync. Raise issue on Khoj <a href='https://github.com/khoj-ai/khoj/issues'>Github</a> or <a href='https://discord.gg/BDgyabRM6e'>Discord</a>.",
button=sync, button=syncButtonEl,
loadingText="Syncing...", loadingText="Syncing...",
emoji=""); emoji="");
}); });
function updateIndex(force, successText, errorText, button, loadingText, emoji) { function updateIndex(force, successText, errorText, button, loadingText, emoji) {
const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken'))?.split('=')[1]; const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken'))?.split('=')[1];
const original_html = button.innerHTML;
button.disabled = true; button.disabled = true;
button.innerHTML = emoji + " " + loadingText; button.innerHTML = ""
button.textContent = emoji + " " + loadingText;
fetch('/api/update?&client=web&force=' + force, { fetch('/api/update?&client=web&force=' + force, {
method: 'GET', method: 'GET',
headers: { headers: {
@@ -640,19 +658,19 @@
document.getElementById("status").style.display = "none"; document.getElementById("status").style.display = "none";
button.disabled = false; button.disabled = false;
button.innerHTML = `${successText}`; button.textContent = `${successText}`;
setTimeout(function() { setTimeout(function() {
button.innerHTML = original_html; button.append(...populateSyncButton());
}, 2000); }, 2000);
}) })
.catch((error) => { .catch((error) => {
console.error('Error:', error); console.error('Error:', error);
document.getElementById("status").innerHTML = emoji + " " + errorText document.getElementById("status").textContent = emoji + " " + errorText
document.getElementById("status").style.display = "block"; document.getElementById("status").style.display = "block";
button.disabled = false; button.disabled = false;
button.innerHTML = '⚠️ Unsuccessful'; button.textContent = '⚠️ Unsuccessful';
setTimeout(function() { setTimeout(function() {
button.innerHTML = original_html; button.append(...populateSyncButton());
}, 2000); }, 2000);
}); });
@@ -687,7 +705,7 @@
}) })
.then(response => response.json()) .then(response => response.json())
.then(tokenObj => { .then(tokenObj => {
apiKeyList.innerHTML += generateTokenRow(tokenObj); apiKeyList.appendChild(generateTokenRow(tokenObj));
}); });
} }
@@ -696,16 +714,16 @@
navigator.clipboard.writeText(token); navigator.clipboard.writeText(token);
// Flash the API key copied icon // Flash the API key copied icon
const apiKeyColumn = document.getElementById(`api-key-${token}`); const apiKeyColumn = document.getElementById(`api-key-${token}`);
const original_html = apiKeyColumn.innerHTML; const original_text = apiKeyColumn.textContent;
const copyApiKeyButton = document.getElementById(`api-key-copy-${token}`); const copyApiKeyButton = document.getElementById(`api-key-copy-${token}`);
setTimeout(function() { setTimeout(function() {
copyApiKeyButton.src = "/static/assets/icons/copy-button-success.svg"; copyApiKeyButton.src = "/static/assets/icons/copy-button-success.svg";
setTimeout(() => { setTimeout(() => {
copyApiKeyButton.src = "/static/assets/icons/copy-button.svg"; copyApiKeyButton.src = "/static/assets/icons/copy-button.svg";
}, 1000); }, 1000);
apiKeyColumn.innerHTML = "✅ Copied!"; apiKeyColumn.textContent = "✅ Copied!";
setTimeout(function() { setTimeout(function() {
apiKeyColumn.innerHTML = original_html; apiKeyColumn.textContent = original_text;
}, 1000); }, 1000);
}, 100); }, 100);
} }
@@ -728,16 +746,50 @@
let tokenName = tokenObj.name; let tokenName = tokenObj.name;
let truncatedToken = token.slice(0, 4) + "..." + token.slice(-4); let truncatedToken = token.slice(0, 4) + "..." + token.slice(-4);
let tokenId = `${tokenName}-${truncatedToken}`; let tokenId = `${tokenName}-${truncatedToken}`;
return `
<tr id="api-key-item-${token}"> // Create API Key Row
<td><b>${tokenName}</b></td> let apiKeyItemEl = document.createElement("tr");
<td id="api-key-${token}">${truncatedToken}</td> apiKeyItemEl.id = `api-key-item-${token}`;
<td>
<img id="api-key-copy-${token}" onclick="copyAPIKey('${token}')" class="configured-icon api-key-action enabled" src="/static/assets/icons/copy-button.svg" alt="Copy API Key" title="Copy API Key"> // API Key Name Row
<img id="api-key-delete-${token}" onclick="deleteAPIKey('${token}')" class="configured-icon api-key-action enabled" src="/static/assets/icons/delete.svg" alt="Delete API Key" title="Delete API Key"> let apiKeyNameEl = document.createElement("td");
</td> let apiKeyNameTextEl = document.createElement("b");
</tr> apiKeyNameTextEl.textContent = tokenName;
`;
// API Key Token Row
let apiKeyTokenEl = document.createElement("td");
apiKeyTokenEl.id = `api-key-${token}`;
apiKeyTokenEl.textContent = truncatedToken;
// API Key Actions Row
let apiKeyActionsEl = document.createElement("td");
// Copy API Key Button
let copyApiKeyButtonEl = document.createElement("img");
copyApiKeyButtonEl.id = `api-key-copy-${token}`;
copyApiKeyButtonEl.className = "configured-icon api-key-action enabled";
copyApiKeyButtonEl.src = "/static/assets/icons/copy-button.svg";
copyApiKeyButtonEl.alt = "Copy API Key";
copyApiKeyButtonEl.title = "Copy API Key";
copyApiKeyButtonEl.onclick = function() {
copyAPIKey(token);
};
// Delete API Key Button
let deleteApiKeyButtonEl = document.createElement("img");
deleteApiKeyButtonEl.id = `api-key-delete-${token}`;
deleteApiKeyButtonEl.className = "configured-icon api-key-action enabled";
deleteApiKeyButtonEl.src = "/static/assets/icons/delete.svg";
deleteApiKeyButtonEl.alt = "Delete API Key";
deleteApiKeyButtonEl.title = "Delete API Key";
deleteApiKeyButtonEl.onclick = function() {
deleteAPIKey(token);
};
// Construct the API Key Row
apiKeyNameEl.append(apiKeyNameTextEl);
apiKeyActionsEl.append(copyApiKeyButtonEl, deleteApiKeyButtonEl);
apiKeyItemEl.append(apiKeyNameEl, apiKeyTokenEl, apiKeyActionsEl);
return apiKeyItemEl;
} }
function listApiKeys() { function listApiKeys() {
@@ -746,7 +798,7 @@
.then(response => response.json()) .then(response => response.json())
.then(tokens => { .then(tokens => {
if (!tokens?.length > 0) return; if (!tokens?.length > 0) return;
apiKeyList.innerHTML = tokens?.map(generateTokenRow).join(""); apiKeyList.append(...tokens?.map(generateTokenRow));
}); });
} }
@@ -754,11 +806,11 @@
listApiKeys(); listApiKeys();
function getIndexedDataSize() { function getIndexedDataSize() {
document.getElementById("indexed-data-size").innerHTML = "Calculating..."; document.getElementById("indexed-data-size").textContent = "Calculating...";
fetch('/api/config/index/size') fetch('/api/config/index/size')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
document.getElementById("indexed-data-size").innerHTML = data.indexed_data_size_in_mb + " MB used"; document.getElementById("indexed-data-size").textContent = data.indexed_data_size_in_mb + " MB used";
}); });
} }
@@ -787,7 +839,7 @@
.catch(() => callback("us")) .catch(() => callback("us"))
}, },
separateDialCode: true, separateDialCode: true,
utilsScript: "https://cdn.jsdelivr.net/npm/intl-tel-input@18.2.1/build/js/utils.js", utilsScript: "https://assets.khoj.dev/intl-tel-input/utils.js",
}); });
const errorMap = ["Invalid number", "Invalid country code", "Too short", "Too long", "Invalid number"]; const errorMap = ["Invalid number", "Invalid country code", "Too short", "Too long", "Invalid number"];
@@ -858,7 +910,7 @@
phonenumberVerifyButton.addEventListener("click", () => { phonenumberVerifyButton.addEventListener("click", () => {
console.log(iti.getValidationError()); console.log(iti.getValidationError());
if (iti.isValidNumber() == false) { if (iti.isValidNumber() == false) {
phoneNumberUpdateCallback.innerHTML = "Invalid phone number: " + errorMap[iti.getValidationError()]; phoneNumberUpdateCallback.textContent = "Invalid phone number: " + errorMap[iti.getValidationError()];
phoneNumberUpdateCallback.style.display = "block"; phoneNumberUpdateCallback.style.display = "block";
setTimeout(function() { setTimeout(function() {
phoneNumberUpdateCallback.style.display = "none"; phoneNumberUpdateCallback.style.display = "none";
@@ -875,12 +927,12 @@
.then(data => { .then(data => {
if (data.status == "ok") { if (data.status == "ok") {
if (isTwilioEnabled == "True" || isTwilioEnabled == "true") { if (isTwilioEnabled == "True" || isTwilioEnabled == "true") {
phoneNumberUpdateCallback.innerHTML = "OTP sent to your phone number"; phoneNumberUpdateCallback.textContent = "OTP sent to your phone number";
phonenumberVerifyOTPButton.style.display = "block"; phonenumberVerifyOTPButton.style.display = "block";
phonenumberOTPInput.style.display = "block"; phonenumberOTPInput.style.display = "block";
} else { } else {
phonenumberVerifiedText.style.display = "block"; phonenumberVerifiedText.style.display = "block";
phoneNumberUpdateCallback.innerHTML = "Phone number updated"; phoneNumberUpdateCallback.textContent = "Phone number updated";
phonenumberUnverifiedText.style.display = "none"; phonenumberUnverifiedText.style.display = "none";
} }
phonenumberVerifyButton.style.display = "none"; phonenumberVerifyButton.style.display = "none";
@@ -889,7 +941,7 @@
phoneNumberUpdateCallback.style.display = "none"; phoneNumberUpdateCallback.style.display = "none";
}, 5000); }, 5000);
} else { } else {
phoneNumberUpdateCallback.innerHTML = "Error updating phone number"; phoneNumberUpdateCallback.textContent = "Error updating phone number";
phoneNumberUpdateCallback.style.display = "block"; phoneNumberUpdateCallback.style.display = "block";
setTimeout(function() { setTimeout(function() {
phoneNumberUpdateCallback.style.display = "none"; phoneNumberUpdateCallback.style.display = "none";
@@ -898,7 +950,7 @@
}) })
.catch((error) => { .catch((error) => {
console.error('Error:', error); console.error('Error:', error);
phoneNumberUpdateCallback.innerHTML = "Error updating phone number"; phoneNumberUpdateCallback.textContent = "Error updating phone number";
phoneNumberUpdateCallback.style.display = "block"; phoneNumberUpdateCallback.style.display = "block";
setTimeout(function() { setTimeout(function() {
phoneNumberUpdateCallback.style.display = "none"; phoneNumberUpdateCallback.style.display = "none";
@@ -910,7 +962,7 @@
phonenumberVerifyOTPButton.addEventListener("click", () => { phonenumberVerifyOTPButton.addEventListener("click", () => {
const otp = phonenumberOTPInput.value; const otp = phonenumberOTPInput.value;
if (otp.length != 6) { if (otp.length != 6) {
phoneNumberUpdateCallback.innerHTML = "Your OTP should be exactly 6 digits"; phoneNumberUpdateCallback.textContent = "Your OTP should be exactly 6 digits";
phoneNumberUpdateCallback.style.display = "block"; phoneNumberUpdateCallback.style.display = "block";
setTimeout(function() { setTimeout(function() {
phoneNumberUpdateCallback.style.display = "none"; phoneNumberUpdateCallback.style.display = "none";
@@ -927,7 +979,7 @@
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.status == "ok") { if (data.status == "ok") {
phoneNumberUpdateCallback.innerHTML = "Phone number updated"; phoneNumberUpdateCallback.textContent = "Phone number updated";
phonenumberVerifiedText.style.display = "block"; phonenumberVerifiedText.style.display = "block";
phonenumberUnverifiedText.style.display = "none"; phonenumberUnverifiedText.style.display = "none";
phoneNumberUpdateCallback.style.display = "block"; phoneNumberUpdateCallback.style.display = "block";
@@ -939,7 +991,7 @@
phoneNumberUpdateCallback.style.display = "none"; phoneNumberUpdateCallback.style.display = "none";
}, 5000); }, 5000);
} else { } else {
phoneNumberUpdateCallback.innerHTML = "Error updating phone number"; phoneNumberUpdateCallback.textContent = "Error updating phone number";
phoneNumberUpdateCallback.style.display = "block"; phoneNumberUpdateCallback.style.display = "block";
setTimeout(function() { setTimeout(function() {
phoneNumberUpdateCallback.style.display = "none"; phoneNumberUpdateCallback.style.display = "none";
@@ -948,7 +1000,7 @@
}) })
.catch((error) => { .catch((error) => {
console.error('Error:', error); console.error('Error:', error);
phoneNumberUpdateCallback.innerHTML = "Error updating phone number"; phoneNumberUpdateCallback.textContent = "Error updating phone number";
phoneNumberUpdateCallback.style.display = "block"; phoneNumberUpdateCallback.style.display = "block";
setTimeout(function() { setTimeout(function() {
phoneNumberUpdateCallback.style.display = "none"; phoneNumberUpdateCallback.style.display = "none";

View File

@@ -56,7 +56,10 @@
if (data.length == 0) { if (data.length == 0) {
document.getElementById("delete-all-files").style.display = "none"; document.getElementById("delete-all-files").style.display = "none";
indexedFiles.innerHTML = "<div class='card-description'>No documents synced with Khoj</div>"; let noFilesElement = document.createElement("div");
noFilesElement.classList.add("card-description");
noFilesElement.textContent = "No documents synced with Khoj";
indexedFiles.appendChild(noFilesElement);
} else { } else {
document.getElementById("get-desktop-client").style.display = "none"; document.getElementById("get-desktop-client").style.display = "none";
document.getElementById("delete-all-files").style.display = "block"; document.getElementById("delete-all-files").style.display = "block";
@@ -86,14 +89,14 @@
let fileNameElement = document.createElement("div"); let fileNameElement = document.createElement("div");
fileNameElement.classList.add("content-name"); fileNameElement.classList.add("content-name");
fileNameElement.innerHTML = filename; fileNameElement.textContent = filename;
fileElement.appendChild(fileNameElement); fileElement.appendChild(fileNameElement);
let buttonContainer = document.createElement("div"); let buttonContainer = document.createElement("div");
buttonContainer.classList.add("remove-button-container"); buttonContainer.classList.add("remove-button-container");
let removeFileButton = document.createElement("button"); let removeFileButton = document.createElement("button");
removeFileButton.classList.add("remove-file-button"); removeFileButton.classList.add("remove-file-button");
removeFileButton.innerHTML = "🗑️"; removeFileButton.textContent = "🗑️";
removeFileButton.addEventListener("click", ((filename) => { removeFileButton.addEventListener("click", ((filename) => {
return () => { return () => {
removeFile(filename); removeFile(filename);

View File

@@ -70,18 +70,50 @@
repo.classList.add("repo"); repo.classList.add("repo");
const id = Date.now(); const id = Date.now();
repo.id = "repo-card-" + id; repo.id = "repo-card-" + id;
repo.innerHTML = `
<label for="repo-owner">Repository Owner</label> // Create repo owner, name, branch elements
<input type="text" id="repo-owner" name="repo_owner"> let repoOwnerLabel = document.createElement("label");
<label for="repo-name">Repository Name</label> repoOwnerLabel.textContent = "Repository Owner";
<input type="text" id="repo-name" name="repo_name"> repoOwnerLabel.for = "repo-owner";
<label for="repo-branch">Repository Branch</label>
<input type="text" id="repo-branch" name="repo_branch"> let repoOwner = document.createElement("input");
<button type="button" repoOwner.type = "text";
class="remove-repo-button" repoOwner.id = "repo-owner-" + id;
onclick="remove_repo(${id})" repoOwner.name = "repo_owner";
id="remove-repo-button-${id}">Remove Repository</button>
`; let repoNameLabel = document.createElement("label");
repoNameLabel.textContent = "Repository Name";
repoNameLabel.for = "repo-name";
let repoName = document.createElement("input");
repoName.type = "text";
repoName.id = "repo-name-" + id;
repoName.name = "repo_name";
let repoBranchLabel = document.createElement("label");
repoBranchLabel.textContent = "Repository Branch";
repoBranchLabel.for = "repo-branch";
let repoBranch = document.createElement("input");
repoBranch.type = "text";
repoBranch.id = "repo-branch-" + id;
repoBranch.name = "repo_branch";
let removeRepoButton = document.createElement("button");
removeRepoButton.type = "button";
removeRepoButton.classList.add("remove-repo-button");
removeRepoButton.onclick = function() { remove_repo(id); };
removeRepoButton.id = "remove-repo-button-" + id;
removeRepoButton.textContent = "Remove Repository";
// Append elements to repo card
repo.append(
repoOwnerLabel, repoOwner,
repoNameLabel, repoName,
repoBranchLabel, repoBranch,
removeRepoButton
);
document.getElementById("repositories").appendChild(repo); document.getElementById("repositories").appendChild(repo);
}) })
@@ -95,7 +127,7 @@
const pat_token = document.getElementById("pat-token").value; const pat_token = document.getElementById("pat-token").value;
if (pat_token == "") { if (pat_token == "") {
document.getElementById("success").innerHTML = "❌ Please enter a Personal Access Token."; document.getElementById("success").textContent = "❌ Please enter a Personal Access Token.";
document.getElementById("success").style.display = "block"; document.getElementById("success").style.display = "block";
return; return;
} }
@@ -122,14 +154,14 @@
} }
if (repos.length == 0) { if (repos.length == 0) {
document.getElementById("success").innerHTML = "❌ Please add at least one repository."; document.getElementById("success").textContent = "❌ Please add at least one repository.";
document.getElementById("success").style.display = "block"; document.getElementById("success").style.display = "block";
return; return;
} }
const submitButton = document.getElementById("submit"); const submitButton = document.getElementById("submit");
submitButton.disabled = true; submitButton.disabled = true;
submitButton.innerHTML = "Saving..."; submitButton.textContent = "Saving...";
// Save Github config on server // Save Github config on server
const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken'))?.split('=')[1]; const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken'))?.split('=')[1];
@@ -147,11 +179,11 @@
.then(response => response.json()) .then(response => response.json())
.then(data => { data["status"] === "ok" ? data : Promise.reject(data) }) .then(data => { data["status"] === "ok" ? data : Promise.reject(data) })
.catch(error => { .catch(error => {
document.getElementById("success").innerHTML = "⚠️ Failed to save Github settings."; document.getElementById("success").textContent = "⚠️ Failed to save Github settings.";
document.getElementById("success").style.display = "block"; document.getElementById("success").style.display = "block";
submitButton.innerHTML = "⚠️ Failed to save settings"; submitButton.textContent = "⚠️ Failed to save settings";
setTimeout(function() { setTimeout(function() {
submitButton.innerHTML = "Save"; submitButton.textContent = "Save";
submitButton.disabled = false; submitButton.disabled = false;
}, 2000); }, 2000);
return; return;
@@ -163,18 +195,18 @@
.then(data => { data["status"] == "ok" ? data : Promise.reject(data) }) .then(data => { data["status"] == "ok" ? data : Promise.reject(data) })
.then(data => { .then(data => {
document.getElementById("success").style.display = "none"; document.getElementById("success").style.display = "none";
submitButton.innerHTML = "✅ Successfully updated"; submitButton.textContent = "✅ Successfully updated";
setTimeout(function() { setTimeout(function() {
submitButton.innerHTML = "Save"; submitButton.textContent = "Save";
submitButton.disabled = false; submitButton.disabled = false;
}, 2000); }, 2000);
}) })
.catch(error => { .catch(error => {
document.getElementById("success").innerHTML = "⚠️ Failed to save Github content."; document.getElementById("success").textContent = "⚠️ Failed to save Github content.";
document.getElementById("success").style.display = "block"; document.getElementById("success").style.display = "block";
submitButton.innerHTML = "⚠️ Failed to save content"; submitButton.textContent = "⚠️ Failed to save content";
setTimeout(function() { setTimeout(function() {
submitButton.innerHTML = "Save"; submitButton.textContent = "Save";
submitButton.disabled = false; submitButton.disabled = false;
}, 2000); }, 2000);
}); });

View File

@@ -34,14 +34,14 @@
const token = document.getElementById("token").value; const token = document.getElementById("token").value;
if (token == "") { if (token == "") {
document.getElementById("success").innerHTML = "❌ Please enter a Notion Token."; document.getElementById("success").textContent = "❌ Please enter a Notion Token.";
document.getElementById("success").style.display = "block"; document.getElementById("success").style.display = "block";
return; return;
} }
const submitButton = document.getElementById("submit"); const submitButton = document.getElementById("submit");
submitButton.disabled = true; submitButton.disabled = true;
submitButton.innerHTML = "Syncing..."; submitButton.textContent = "Syncing...";
// Save Notion config on server // Save Notion config on server
const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken'))?.split('=')[1]; const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken'))?.split('=')[1];
@@ -58,11 +58,11 @@
.then(response => response.json()) .then(response => response.json())
.then(data => { data["status"] === "ok" ? data : Promise.reject(data) }) .then(data => { data["status"] === "ok" ? data : Promise.reject(data) })
.catch(error => { .catch(error => {
document.getElementById("success").innerHTML = "⚠️ Failed to save Notion settings."; document.getElementById("success").textContent = "⚠️ Failed to save Notion settings.";
document.getElementById("success").style.display = "block"; document.getElementById("success").style.display = "block";
submitButton.innerHTML = "⚠️ Failed to save settings"; submitButton.textContent = "⚠️ Failed to save settings";
setTimeout(function() { setTimeout(function() {
submitButton.innerHTML = "Save"; submitButton.textContent = "Save";
submitButton.disabled = false; submitButton.disabled = false;
}, 2000); }, 2000);
return; return;
@@ -74,18 +74,18 @@
.then(data => { data["status"] == "ok" ? data : Promise.reject(data) }) .then(data => { data["status"] == "ok" ? data : Promise.reject(data) })
.then(data => { .then(data => {
document.getElementById("success").style.display = "none"; document.getElementById("success").style.display = "none";
submitButton.innerHTML = "✅ Successfully updated"; submitButton.textContent = "✅ Successfully updated";
setTimeout(function() { setTimeout(function() {
submitButton.innerHTML = "Save"; submitButton.textContent = "Save";
submitButton.disabled = false; submitButton.disabled = false;
}, 2000); }, 2000);
}) })
.catch(error => { .catch(error => {
document.getElementById("success").innerHTML = "⚠️ Failed to save Notion content."; document.getElementById("success").textContent = "⚠️ Failed to save Notion content.";
document.getElementById("success").style.display = "block"; document.getElementById("success").style.display = "block";
submitButton.innerHTML = "⚠️ Failed to save content"; submitButton.textContent = "⚠️ Failed to save content";
setTimeout(function() { setTimeout(function() {
submitButton.innerHTML = "Save"; submitButton.textContent = "Save";
submitButton.disabled = false; submitButton.disabled = false;
}, 2000); }, 2000);
}); });

View File

@@ -127,7 +127,7 @@ To get started, just start typing below. You can also type / to see a list of co
linkElement.textContent = title; linkElement.textContent = title;
let referenceButton = document.createElement('button'); let referenceButton = document.createElement('button');
referenceButton.innerHTML = linkElement.outerHTML; referenceButton.appendChild(linkElement);
referenceButton.id = `ref-${index}`; referenceButton.id = `ref-${index}`;
referenceButton.classList.add("reference-button"); referenceButton.classList.add("reference-button");
referenceButton.classList.add("collapsed"); referenceButton.classList.add("collapsed");
@@ -138,11 +138,12 @@ To get started, just start typing below. You can also type / to see a list of co
if (this.classList.contains("collapsed")) { if (this.classList.contains("collapsed")) {
this.classList.remove("collapsed"); this.classList.remove("collapsed");
this.classList.add("expanded"); this.classList.add("expanded");
this.innerHTML = linkElement.outerHTML + `<br><br>${question + snippet}`; this.innerHTML = `${linkElement.outerHTML}<br><br>${question + snippet}`;
} else { } else {
this.classList.add("collapsed"); this.classList.add("collapsed");
this.classList.remove("expanded"); this.classList.remove("expanded");
this.innerHTML = linkElement.outerHTML; this.innerHTML = "";
this.appendChild(linkElement);
} }
}); });
@@ -296,7 +297,7 @@ To get started, just start typing below. You can also type / to see a list of co
} }
let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`; let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`;
referenceExpandButton.innerHTML = expandButtonText; referenceExpandButton.textContent = expandButtonText;
references.appendChild(referenceSection); references.appendChild(referenceSection);
@@ -447,7 +448,7 @@ To get started, just start typing below. You can also type / to see a list of co
let referenceExpandButton = document.createElement('button'); let referenceExpandButton = document.createElement('button');
referenceExpandButton.classList.add("reference-expand-button"); referenceExpandButton.classList.add("reference-expand-button");
referenceExpandButton.innerHTML = numReferences == 1 ? "1 reference" : `${numReferences} references`; referenceExpandButton.textContent = numReferences == 1 ? "1 reference" : `${numReferences} references`;
referenceExpandButton.addEventListener('click', function() { referenceExpandButton.addEventListener('click', function() {
if (referenceSection.classList.contains("collapsed")) { if (referenceSection.classList.contains("collapsed")) {
@@ -815,7 +816,7 @@ Learn more [here](https://khoj.dev).
let closeButton = document.createElement('button'); let closeButton = document.createElement('button');
closeButton.id = "close-button"; closeButton.id = "close-button";
closeButton.innerHTML = "Close"; closeButton.textContent = "Close";
closeButton.classList.add("close-button"); closeButton.classList.add("close-button");
closeButton.addEventListener('click', function() { closeButton.addEventListener('click', function() {
modal.remove(); modal.remove();