mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-04 21:29:12 +00:00
Add multi-user support to Khoj and use Postgres for backend storage (#549)
- Adds support for multiple users to be connected to the same Khoj instance using their Google login credentials - Moves storage solution from in-memory json data to a Postgres db. This stores all relevant information, including accounts, embeddings, chat history, server side chat configuration - Adds the concept of a Khoj server admin for configuring instance-wide settings regarding search model, and chat configuration - Miscellaneous updates and fixes to the UX, including chat references, colors, and an updated config page - Adds billing to allow users to subscribe to the cloud service easily - Adds a separate GitHub action for building the dockerized production (tag `prod`) and dev (tag `dev`) images, separate from the image used for local building. The production image uses `gunicorn` with multiple workers to run the server. - Updates all clients (Obsidian, Emacs, Desktop) to follow the client/server architecture. The server no longer reads from the file system at all; it only accepts data via the indexer API. In line with that, removes the functionality to configure org, markdown, plaintext, or other file-specific settings in the server. Only leaves GitHub and Notion for server-side configuration. - Changes license to GNU AGPLv3 Resolves #467 Resolves #488 Resolves #303 Resolves #345 Resolves #195 Resolves #280 Resolves #461 Closes #259 Resolves #351 Resolves #301 Resolves #296
This commit is contained in:
88
src/interface/desktop/about.html
Normal file
88
src/interface/desktop/about.html
Normal file
@@ -0,0 +1,88 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
||||
<title>Khoj - About</title>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="./assets/icons/favicon-128x128.png">
|
||||
<link rel="manifest" href="/static/khoj_chat.webmanifest">
|
||||
<link rel="stylesheet" href="./assets/khoj.css">
|
||||
</head>
|
||||
<script type="text/javascript" src="./utils.js"></script>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
background: var(--background-color);
|
||||
color: var(--main-text-color);
|
||||
text-align: center;
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-size: small;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
header > *,
|
||||
body > * {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
header > * {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
}
|
||||
#about-page-version {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: block;
|
||||
width: 60%;
|
||||
padding: 10px 16px;
|
||||
margin: 10px auto;
|
||||
background-color: var(--primary);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 10px;
|
||||
color: slategray;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<header>
|
||||
<img id="logo" src="./assets/icons/favicon-128x128.png" alt="Khoj Logo">
|
||||
<p id="about-page-title"><b>Khoj for Desktop</b>
|
||||
<p id="about-page-version"></p>
|
||||
</header>
|
||||
<div class="action">
|
||||
<button class="button" onclick="window.open('https://khoj.dev/terms-of-service', '_blank')">Terms of Service</button>
|
||||
<button class="button" onclick="window.open('https://khoj.dev/privacy-policy', '_blank')">Privacy Policy</button>
|
||||
</div>
|
||||
<footer>
|
||||
© 2023 Khoj Inc. All rights reserved.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
BIN
src/interface/desktop/assets/icons/favicon-20x20.png
Normal file
BIN
src/interface/desktop/assets/icons/favicon-20x20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
4
src/interface/desktop/assets/icons/key.svg
Normal file
4
src/interface/desktop/assets/icons/key.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 8.29344C22 11.7692 19.1708 14.5869 15.6807 14.5869C15.0439 14.5869 13.5939 14.4405 12.8885 13.8551L12.0067 14.7333C11.4883 15.2496 11.6283 15.4016 11.8589 15.652C11.9551 15.7565 12.0672 15.8781 12.1537 16.0505C12.1537 16.0505 12.8885 17.075 12.1537 18.0995C11.7128 18.6849 10.4783 19.5045 9.06754 18.0995L8.77362 18.3922C8.77362 18.3922 9.65538 19.4167 8.92058 20.4412C8.4797 21.0267 7.30403 21.6121 6.27531 20.5876L5.2466 21.6121C4.54119 22.3146 3.67905 21.9048 3.33616 21.6121L2.45441 20.7339C1.63143 19.9143 2.1115 19.0264 2.45441 18.6849L10.0963 11.0743C10.0963 11.0743 9.3615 9.90338 9.3615 8.29344C9.3615 4.81767 12.1907 2 15.6807 2C19.1708 2 22 4.81767 22 8.29344ZM15.681 10.4889C16.8984 10.4889 17.8853 9.50601 17.8853 8.29353C17.8853 7.08105 16.8984 6.09814 15.681 6.09814C14.4635 6.09814 13.4766 7.08105 13.4766 8.29353C13.4766 9.50601 14.4635 10.4889 15.681 10.4889Z" fill="#1C274C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 29 KiB |
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.197 3.35462C16.8703 1.67483 19.4476 1.53865 20.9536 3.05046C22.4596 4.56228 22.3239 7.14956 20.6506 8.82935L18.2268 11.2626M10.0464 14C8.54044 12.4882 8.67609 9.90087 10.3494 8.22108L12.5 6.06212" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M13.9536 10C15.4596 11.5118 15.3239 14.0991 13.6506 15.7789L11.2268 18.2121L8.80299 20.6454C7.12969 22.3252 4.55237 22.4613 3.0464 20.9495C1.54043 19.4377 1.67609 16.8504 3.34939 15.1706L5.77323 12.7373" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M9.16488 17.6505C8.92513 17.8743 8.73958 18.0241 8.54996 18.1336C7.62175 18.6695 6.47816 18.6695 5.54996 18.1336C5.20791 17.9361 4.87912 17.6073 4.22153 16.9498C3.56394 16.2922 3.23514 15.9634 3.03767 15.6213C2.50177 14.6931 2.50177 13.5495 3.03767 12.6213C3.23514 12.2793 3.56394 11.9505 4.22153 11.2929L7.04996 8.46448C7.70755 7.80689 8.03634 7.47809 8.37838 7.28062C9.30659 6.74472 10.4502 6.74472 11.3784 7.28061C11.7204 7.47809 12.0492 7.80689 12.7068 8.46448C13.3644 9.12207 13.6932 9.45086 13.8907 9.7929C14.4266 10.7211 14.4266 11.8647 13.8907 12.7929C13.7812 12.9825 13.6314 13.1681 13.4075 13.4078M10.5919 10.5922C10.368 10.8319 10.2182 11.0175 10.1087 11.2071C9.57284 12.1353 9.57284 13.2789 10.1087 14.2071C10.3062 14.5492 10.635 14.878 11.2926 15.5355C11.9502 16.1931 12.279 16.5219 12.621 16.7194C13.5492 17.2553 14.6928 17.2553 15.621 16.7194C15.9631 16.5219 16.2919 16.1931 16.9495 15.5355L19.7779 12.7071C20.4355 12.0495 20.7643 11.7207 20.9617 11.3787C21.4976 10.4505 21.4976 9.30689 20.9617 8.37869C20.7643 8.03665 20.4355 7.70785 19.7779 7.05026C19.1203 6.39267 18.7915 6.06388 18.4495 5.8664C17.5212 5.3305 16.3777 5.3305 15.4495 5.8664C15.2598 5.97588 15.0743 6.12571 14.8345 6.34955" stroke="#000000" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 777 B After Width: | Height: | Size: 1.4 KiB |
@@ -2,29 +2,44 @@
|
||||
/* Can be forced with data-theme="light" */
|
||||
[data-theme="light"],
|
||||
:root:not([data-theme="dark"]) {
|
||||
--primary: #ffb300;
|
||||
--primary-hover: #ffa000;
|
||||
--primary: #fee285;
|
||||
--primary-hover: #fcc50b;
|
||||
--primary-focus: rgba(255, 179, 0, 0.125);
|
||||
--primary-inverse: rgba(0, 0, 0, 0.75);
|
||||
--background-color: #f5f4f3;
|
||||
--main-text-color: #475569;
|
||||
--water: #44b9da;
|
||||
--leaf: #7b990a;
|
||||
--flower: #d1684e;
|
||||
}
|
||||
|
||||
/* Amber Dark scheme (Auto) */
|
||||
/* Automatically enabled if user has Dark mode enabled */
|
||||
@media only screen and (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme]) {
|
||||
--primary: #ffb300;
|
||||
--primary-hover: #ffc107;
|
||||
--primary: #fee285;
|
||||
--primary-hover: #fcc50b;
|
||||
--primary-focus: rgba(255, 179, 0, 0.25);
|
||||
--primary-inverse: rgba(0, 0, 0, 0.75);
|
||||
--background-color: #f5f4f3;
|
||||
--main-text-color: #475569;
|
||||
--water: #44b9da;
|
||||
--leaf: #7b990a;
|
||||
--flower: #d1684e;
|
||||
}
|
||||
}
|
||||
/* Amber Dark scheme (Forced) */
|
||||
/* Enabled if forced with data-theme="dark" */
|
||||
[data-theme="dark"] {
|
||||
--primary: #ffb300;
|
||||
--primary-hover: #ffc107;
|
||||
--primary: #fee285;
|
||||
--primary-hover: #fcc50b;
|
||||
--primary-focus: rgba(255, 179, 0, 0.25);
|
||||
--primary-inverse: rgba(0, 0, 0, 0.75);
|
||||
--background-color: #f5f4f3;
|
||||
--main-text-color: #475569;
|
||||
--water: #44b9da;
|
||||
--leaf: #7b990a;
|
||||
--flower: #d1684e;
|
||||
}
|
||||
/* Amber (Common styles) */
|
||||
:root {
|
||||
@@ -37,8 +52,10 @@
|
||||
.khoj-configure {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
padding: 0 24px;
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.khoj-header {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
@@ -64,7 +81,7 @@ a.khoj-logo {
|
||||
}
|
||||
|
||||
.khoj-nav a {
|
||||
color: #333;
|
||||
color: var(--main-text-color);
|
||||
text-decoration: none;
|
||||
font-size: small;
|
||||
font-weight: normal;
|
||||
@@ -75,8 +92,9 @@ a.khoj-logo {
|
||||
}
|
||||
.khoj-nav a:hover {
|
||||
background-color: var(--primary-hover);
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
.khoj-nav-selected {
|
||||
a.khoj-nav-selected {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
img.khoj-logo {
|
||||
@@ -85,21 +103,6 @@ img.khoj-logo {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
a.khoj-banner {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p.khoj-banner {
|
||||
font-size: small;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
p#khoj-banner {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
div.khoj-header {
|
||||
display: grid;
|
||||
|
||||
991
src/interface/desktop/assets/three.min.js
vendored
Normal file
991
src/interface/desktop/assets/three.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -8,6 +8,8 @@
|
||||
<link rel="manifest" href="/static/khoj_chat.webmanifest">
|
||||
<link rel="stylesheet" href="./assets/khoj.css">
|
||||
</head>
|
||||
<script src="./utils.js"></script>
|
||||
|
||||
<script>
|
||||
let chatOptions = [];
|
||||
function copyProgrammaticOutput(event) {
|
||||
@@ -32,32 +34,101 @@
|
||||
let escaped_ref = reference.replaceAll('"', '"');
|
||||
|
||||
// Generate HTML for Chat Reference
|
||||
return `<sup><abbr title="${escaped_ref}" tabindex="0">${index}</abbr></sup>`;
|
||||
let short_ref = escaped_ref.slice(0, 100);
|
||||
short_ref = short_ref.length < escaped_ref.length ? short_ref + "..." : short_ref;
|
||||
let referenceButton = document.createElement('button');
|
||||
referenceButton.innerHTML = short_ref;
|
||||
referenceButton.id = `ref-${index}`;
|
||||
referenceButton.classList.add("reference-button");
|
||||
referenceButton.classList.add("collapsed");
|
||||
referenceButton.tabIndex = 0;
|
||||
|
||||
// Add event listener to toggle full reference on click
|
||||
referenceButton.addEventListener('click', function() {
|
||||
console.log(`Toggling ref-${index}`)
|
||||
if (this.classList.contains("collapsed")) {
|
||||
this.classList.remove("collapsed");
|
||||
this.classList.add("expanded");
|
||||
this.innerHTML = escaped_ref;
|
||||
} else {
|
||||
this.classList.add("collapsed");
|
||||
this.classList.remove("expanded");
|
||||
this.innerHTML = short_ref;
|
||||
}
|
||||
});
|
||||
|
||||
return referenceButton;
|
||||
}
|
||||
|
||||
function renderMessage(message, by, dt=null) {
|
||||
function renderMessage(message, by, dt=null, annotations=null) {
|
||||
let message_time = formatDate(dt ?? new Date());
|
||||
let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You";
|
||||
let formattedMessage = formatHTMLMessage(message);
|
||||
// Generate HTML for Chat Message and Append to Chat Body
|
||||
document.getElementById("chat-body").innerHTML += `
|
||||
<div data-meta="${by_name} at ${message_time}" class="chat-message ${by}">
|
||||
<div class="chat-message-text ${by}">${formattedMessage}</div>
|
||||
</div>
|
||||
`;
|
||||
let chatBody = document.getElementById("chat-body");
|
||||
|
||||
// Create a new div for the chat message
|
||||
let chatMessage = document.createElement('div');
|
||||
chatMessage.className = `chat-message ${by}`;
|
||||
chatMessage.dataset.meta = `${by_name} at ${message_time}`;
|
||||
|
||||
// Create a new div for the chat message text and append it to the chat message
|
||||
let chatMessageText = document.createElement('div');
|
||||
chatMessageText.className = `chat-message-text ${by}`;
|
||||
chatMessageText.innerHTML = formattedMessage;
|
||||
chatMessage.appendChild(chatMessageText);
|
||||
|
||||
// Append annotations div to the chat message
|
||||
if (annotations) {
|
||||
chatMessageText.appendChild(annotations);
|
||||
}
|
||||
|
||||
// Append chat message div to chat body
|
||||
chatBody.appendChild(chatMessage);
|
||||
|
||||
// Scroll to bottom of chat-body element
|
||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||
chatBody.scrollTop = chatBody.scrollHeight;
|
||||
}
|
||||
|
||||
function renderMessageWithReference(message, by, context=null, dt=null) {
|
||||
let references = '';
|
||||
if (context) {
|
||||
references = context
|
||||
.map((reference, index) => generateReference(reference, index))
|
||||
.join("<sup>,</sup>");
|
||||
if (context == null || context.length == 0) {
|
||||
renderMessage(message, by, dt);
|
||||
return;
|
||||
}
|
||||
|
||||
renderMessage(message+references, by, dt);
|
||||
let references = document.createElement('div');
|
||||
|
||||
let referenceExpandButton = document.createElement('button');
|
||||
referenceExpandButton.classList.add("reference-expand-button");
|
||||
let expandButtonText = context.length == 1 ? "1 reference" : `${context.length} references`;
|
||||
referenceExpandButton.innerHTML = expandButtonText;
|
||||
|
||||
references.appendChild(referenceExpandButton);
|
||||
|
||||
let referenceSection = document.createElement('div');
|
||||
referenceSection.classList.add("reference-section");
|
||||
referenceSection.classList.add("collapsed");
|
||||
|
||||
referenceExpandButton.addEventListener('click', function() {
|
||||
if (referenceSection.classList.contains("collapsed")) {
|
||||
referenceSection.classList.remove("collapsed");
|
||||
referenceSection.classList.add("expanded");
|
||||
} else {
|
||||
referenceSection.classList.add("collapsed");
|
||||
referenceSection.classList.remove("expanded");
|
||||
}
|
||||
});
|
||||
|
||||
references.classList.add("references");
|
||||
if (context) {
|
||||
for (let index in context) {
|
||||
let reference = context[index];
|
||||
let polishedReference = generateReference(reference, index);
|
||||
referenceSection.appendChild(polishedReference);
|
||||
}
|
||||
}
|
||||
references.appendChild(referenceSection);
|
||||
|
||||
renderMessage(message, by, dt, references);
|
||||
}
|
||||
|
||||
function formatHTMLMessage(htmlMessage) {
|
||||
@@ -66,6 +137,8 @@
|
||||
// Replace any ** with <b> and __ with <u>
|
||||
newHTML = newHTML.replace(/\*\*([\s\S]*?)\*\*/g, '<b>$1</b>');
|
||||
newHTML = newHTML.replace(/__([\s\S]*?)__/g, '<u>$1</u>');
|
||||
// Remove any text between <s>[INST] and </s> tags. These are spurious instructions for the AI chat model.
|
||||
newHTML = newHTML.replace(/<s>\[INST\].+(<\/s>)?/g, '');
|
||||
return newHTML;
|
||||
}
|
||||
|
||||
@@ -89,6 +162,8 @@
|
||||
|
||||
// Generate backend API URL to execute query
|
||||
let url = `${hostURL}/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true`;
|
||||
const khojToken = await window.tokenAPI.getToken();
|
||||
const headers = { 'Authorization': `Bearer ${khojToken}` };
|
||||
|
||||
let chat_body = document.getElementById("chat-body");
|
||||
let new_response = document.createElement("div");
|
||||
@@ -113,10 +188,11 @@
|
||||
chatInput.classList.remove("option-enabled");
|
||||
|
||||
// Call specified Khoj API which returns a streamed response of type text/plain
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
fetch(url, { headers })
|
||||
.then(response => {
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let references = null;
|
||||
|
||||
function readStream() {
|
||||
reader.read().then(({ done, value }) => {
|
||||
@@ -124,7 +200,8 @@
|
||||
// Evaluate the contents of new_response_text.innerHTML after all the data has been streamed
|
||||
const currentHTML = newResponseText.innerHTML;
|
||||
newResponseText.innerHTML = formatHTMLMessage(currentHTML);
|
||||
|
||||
newResponseText.appendChild(references);
|
||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -137,11 +214,36 @@
|
||||
|
||||
const rawReference = chunk.split("### compiled references:")[1];
|
||||
const rawReferenceAsJson = JSON.parse(rawReference);
|
||||
let polishedReference = rawReferenceAsJson.map((reference, index) => generateReference(reference, index))
|
||||
.join("<sup>,</sup>");
|
||||
references = document.createElement('div');
|
||||
references.classList.add("references");
|
||||
|
||||
newResponseText.innerHTML += polishedReference;
|
||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||
|
||||
let referenceExpandButton = document.createElement('button');
|
||||
referenceExpandButton.classList.add("reference-expand-button");
|
||||
let expandButtonText = rawReferenceAsJson.length == 1 ? "1 reference" : `${rawReferenceAsJson.length} references`;
|
||||
referenceExpandButton.innerHTML = expandButtonText;
|
||||
|
||||
references.appendChild(referenceExpandButton);
|
||||
|
||||
let referenceSection = document.createElement('div');
|
||||
referenceSection.classList.add("reference-section");
|
||||
referenceSection.classList.add("collapsed");
|
||||
|
||||
referenceExpandButton.addEventListener('click', function() {
|
||||
if (referenceSection.classList.contains("collapsed")) {
|
||||
referenceSection.classList.remove("collapsed");
|
||||
referenceSection.classList.add("expanded");
|
||||
} else {
|
||||
referenceSection.classList.add("collapsed");
|
||||
referenceSection.classList.remove("expanded");
|
||||
}
|
||||
});
|
||||
|
||||
rawReferenceAsJson.forEach((reference, index) => {
|
||||
let polishedReference = generateReference(reference, index);
|
||||
referenceSection.appendChild(polishedReference);
|
||||
});
|
||||
references.appendChild(referenceSection);
|
||||
readStream();
|
||||
} else {
|
||||
// Display response from Khoj
|
||||
@@ -164,6 +266,7 @@
|
||||
|
||||
function incrementalChat(event) {
|
||||
if (!event.shiftKey && event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
chat();
|
||||
}
|
||||
}
|
||||
@@ -217,12 +320,23 @@
|
||||
|
||||
async function loadChat() {
|
||||
const hostURL = await window.hostURLAPI.getURL();
|
||||
fetch(`${hostURL}/api/chat/history?client=web`)
|
||||
const khojToken = await window.tokenAPI.getToken();
|
||||
const headers = { 'Authorization': `Bearer ${khojToken}` };
|
||||
|
||||
fetch(`${hostURL}/api/chat/history?client=web`, { headers })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.detail) {
|
||||
// If the server returns a 500 error with detail, render a setup hint.
|
||||
renderMessage("Hi 👋🏾, to get started you have two options:<ol><li><b>Use OpenAI</b>: <ol><li>Get your <a class='inline-chat-link' href='https://platform.openai.com/account/api-keys'>OpenAI API key</a></li><li>Save it in the Khoj <a class='inline-chat-link' href='/config/processor/conversation/openai'>chat settings</a></li><li>Click Configure on the Khoj <a class='inline-chat-link' href='/config'>settings page</a></li></ol></li><li><b>Enable offline chat</b>: <ol><li>Go to the Khoj <a class='inline-chat-link' href='/config'>settings page</a> and enable offline chat</li></ol></li></ol>", "khoj");
|
||||
first_run_message = `Hi 👋🏾, to get started:
|
||||
<ol>
|
||||
<li>Generate an API token in the <a class='inline-chat-link' href="#" onclick="window.navigateAPI.navigateToWebSettings()">Khoj Web settings</a></li>
|
||||
<li>Paste it into the API Key field in the <a class='inline-chat-link' href="#" onclick="window.navigateAPI.navigateToSettings()">Khoj Desktop settings</a></li>
|
||||
</ol>`
|
||||
.trim()
|
||||
.replace(/(\r\n|\n|\r)/gm, "");
|
||||
|
||||
renderMessage(first_run_message, "khoj");
|
||||
|
||||
// Disable chat input field and update placeholder text
|
||||
document.getElementById("chat-input").setAttribute("disabled", "disabled");
|
||||
@@ -243,7 +357,7 @@
|
||||
return;
|
||||
});
|
||||
|
||||
fetch(`${hostURL}/api/chat/options`)
|
||||
fetch(`${hostURL}/api/chat/options`, { headers })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Render chat options, if any
|
||||
@@ -264,17 +378,18 @@
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
<div id="khoj-banner-container" class="khoj-banner-container">
|
||||
<div id="khoj-empty-container" class="khoj-empty-container">
|
||||
</div>
|
||||
|
||||
<!--Add Header Logo and Nav Pane-->
|
||||
<div class="khoj-header">
|
||||
<a class="khoj-logo" href="/">
|
||||
<img class="khoj-logo" src="./assets/icons/khoj-logo-sideways-500.png" alt="Khoj"></img>
|
||||
</a>
|
||||
<nav class="khoj-nav">
|
||||
<a class="khoj-nav khoj-nav-selected" href="./chat.html">Chat</a>
|
||||
<a class="khoj-nav" href="./index.html">Search</a>
|
||||
<a class="khoj-nav" href="./config.html">⚙️</a>
|
||||
<a class="khoj-nav khoj-nav-selected" href="./chat.html">💬 Chat</a>
|
||||
<a class="khoj-nav" href="./search.html">🔎 Search</a>
|
||||
<a class="khoj-nav" href="./config.html">⚙️ Settings</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -284,8 +399,7 @@
|
||||
<!-- Chat Footer -->
|
||||
<div id="chat-footer">
|
||||
<div id="chat-tooltip" style="display: none;"></div>
|
||||
<textarea id="chat-input" class="option" oninput="onChatInput()" onkeyup=incrementalChat(event) autofocus="autofocus" placeholder="Type / to see a list of commands, or just type your questions and hit enter.">
|
||||
</textarea>
|
||||
<textarea id="chat-input" class="option" oninput="onChatInput()" onkeydown=incrementalChat(event) autofocus="autofocus" placeholder="Type / to see a list of commands, or just type your questions and hit enter."></textarea>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -298,8 +412,8 @@
|
||||
}
|
||||
body {
|
||||
display: grid;
|
||||
background: #fff;
|
||||
color: #475569;
|
||||
background: var(--background-color);
|
||||
color: var(--main-text-color);
|
||||
text-align: center;
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-size: small;
|
||||
@@ -433,6 +547,83 @@
|
||||
box-shadow: 0 0 12px rgb(119, 156, 46);
|
||||
}
|
||||
|
||||
div.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.expanded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.reference {
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
grid-auto-flow: row;
|
||||
grid-column-gap: 10px;
|
||||
grid-row-gap: 10px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
div.expanded.reference-section {
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
grid-auto-flow: row;
|
||||
grid-column-gap: 10px;
|
||||
grid-row-gap: 10px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
button.reference-button {
|
||||
background: var(--background-color);
|
||||
color: var(--main-text-color);
|
||||
border: 1px solid var(--main-text-color);
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease-in-out;
|
||||
text-align: left;
|
||||
max-height: 75px;
|
||||
transition: max-height 0.3s ease-in-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
button.reference-button.expanded {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
button.reference-button::before {
|
||||
content: "▶";
|
||||
margin-right: 5px;
|
||||
display: inline-block;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
button.reference-button:active:before,
|
||||
button.reference-button[aria-expanded="true"]::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
button.reference-expand-button {
|
||||
background: var(--background-color);
|
||||
color: var(--main-text-color);
|
||||
border: 1px dotted var(--main-text-color);
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
cursor: pointer;
|
||||
transition: background 0.4s ease-in-out;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button.reference-expand-button:hover {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
|
||||
|
||||
.option-enabled:focus {
|
||||
outline: none !important;
|
||||
border:1px solid #475569;
|
||||
@@ -445,6 +636,11 @@
|
||||
border-bottom: 1px dotted #475569;
|
||||
}
|
||||
|
||||
div.khoj-empty-container {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (pointer: coarse), (hover: none) {
|
||||
abbr[title] {
|
||||
position: relative;
|
||||
@@ -481,12 +677,6 @@
|
||||
margin: 4px;
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
a.khoj-banner {
|
||||
display: block;
|
||||
}
|
||||
p.khoj-banner {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 600px) {
|
||||
body {
|
||||
@@ -498,11 +688,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
div.khoj-banner-container {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
div#chat-tooltip {
|
||||
text-align: left;
|
||||
font-size: medium;
|
||||
@@ -524,23 +709,6 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button#khoj-banner-submit,
|
||||
input#khoj-banner-email {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #475569;
|
||||
background: #f9fafc;
|
||||
}
|
||||
|
||||
button#khoj-banner-submit:hover,
|
||||
input#khoj-banner-email:hover {
|
||||
box-shadow: 0 0 11px #aaa;
|
||||
}
|
||||
div.khoj-banner-container-hidden {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
div.programmatic-output {
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
|
||||
@@ -2,89 +2,105 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
||||
<title>Khoj - Search</title>
|
||||
<title>Khoj - Settings</title>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="./assets/icons/favicon-128x128.png">
|
||||
<link rel="manifest" href="./khoj.webmanifest">
|
||||
<link rel="stylesheet" href="./assets/khoj.css">
|
||||
</head>
|
||||
<script type="text/javascript" src="./assets/org.min.js"></script>
|
||||
<script type="text/javascript" src="./assets/markdown-it.min.js"></script>
|
||||
<script src="./utils.js"></script>
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<!--Add Header Logo and Nav Pane-->
|
||||
<div class="khoj-header">
|
||||
<a class="khoj-logo" href="./index.html">
|
||||
<img class="khoj-logo" src="./assets/icons/khoj-logo-sideways-500.png" alt="Khoj"></img>
|
||||
</a>
|
||||
<nav class="khoj-nav">
|
||||
<a class="khoj-nav" href="./chat.html">Chat</a>
|
||||
<a class="khoj-nav" href="./index.html">Search</a>
|
||||
<a class="khoj-nav khoj-nav-selected" href="./config.html">⚙️</a>
|
||||
</nav>
|
||||
</div>
|
||||
<!--Add Header Logo and Nav Pane-->
|
||||
<div class="khoj-header">
|
||||
<a class="khoj-logo" href="./chat.html">
|
||||
<img class="khoj-logo" src="./assets/icons/khoj-logo-sideways-500.png" alt="Khoj"></img>
|
||||
</a>
|
||||
<nav class="khoj-nav">
|
||||
<a class="khoj-nav" href="./chat.html">💬 Chat</a>
|
||||
<a class="khoj-nav" href="./search.html">🔎 Search</a>
|
||||
<a class="khoj-nav khoj-nav-selected" href="./config.html">⚙️ Settings</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="section-cards">
|
||||
<div class="card configuration">
|
||||
<div class="card-title-row">
|
||||
<img class="card-icon" src="./assets/icons/link.svg" alt="File">
|
||||
<h3 class="card-title">
|
||||
Host
|
||||
</h3>
|
||||
<div class="card-description-row">
|
||||
<div class="card configuration">
|
||||
<div class="card-title-row">
|
||||
<img class="card-icon" src="./assets/icons/link.svg" alt="Khoj Server URL">
|
||||
<h3 class="card-title">
|
||||
Server URL
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<input id="khoj-host-url" class="card-input" type="text">
|
||||
</div>
|
||||
<div class="card-title-row">
|
||||
<img class="card-icon" src="./assets/icons/key.svg" alt="Khoj Access Key">
|
||||
<h3 class="card-title">
|
||||
API Key
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<input id="khoj-access-key" class="card-input" type="text" placeholder="Enter API key to access your Khoj">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<input id="khoj-host-url" class="card-input" type="text">
|
||||
</div>
|
||||
<div class="card-title-row">
|
||||
<img class="card-icon" src="./assets/icons/plaintext.svg" alt="File">
|
||||
<h3 class="card-title">
|
||||
Files
|
||||
<button id="toggle-files" class="card-button">
|
||||
<svg id="toggle-files-svg" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12l7 7 7-7"></path></svg>
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<div class="card configuration">
|
||||
<div class="card-title-row">
|
||||
<img class="card-icon" src="./assets/icons/plaintext.svg" alt="File">
|
||||
<h3 class="card-title">
|
||||
Files
|
||||
<button id="toggle-files" class="card-button">
|
||||
<svg id="toggle-files-svg" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12l7 7 7-7"></path></svg>
|
||||
</button>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<div id="current-files"></div>
|
||||
</div>
|
||||
<div class="card-action-row">
|
||||
<button id="update-file" class="card-button">
|
||||
Add
|
||||
<img class="add-files-icon" src="./assets/icons/circular-add.svg" alt="Add">
|
||||
</button>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<div id="current-files"></div>
|
||||
</div>
|
||||
<div class="card-action-row">
|
||||
<button id="update-file" class="card-button">
|
||||
Add
|
||||
<img class="add-files-icon" src="./assets/icons/circular-add.svg" alt="Add">
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-title-row">
|
||||
<img class="card-icon" src="./assets/icons/folder.svg" alt="Folder">
|
||||
<h3 class="card-title">
|
||||
Folders
|
||||
<button id="toggle-folders" class="card-button">
|
||||
<svg id="toggle-folders-svg" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12l7 7 7-7"></path></svg>
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<div class="card configuration">
|
||||
<div class="card-title-row">
|
||||
<img class="card-icon" src="./assets/icons/folder.svg" alt="Folder">
|
||||
<h3 class="card-title">
|
||||
Folders
|
||||
<button id="toggle-folders" class="card-button">
|
||||
<svg id="toggle-folders-svg" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12l7 7 7-7"></path></svg>
|
||||
</button>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<div id="current-folders"></div>
|
||||
</div>
|
||||
<div class="card-action-row">
|
||||
<button id="update-folder" class="card-button">
|
||||
Add
|
||||
<img class="add-files-icon" src="./assets/icons/circular-add.svg" alt="Add">
|
||||
</button>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-action-row">
|
||||
<div class="card-description-row">
|
||||
<button id="sync-force" class="sync-data">💾 Save</button>
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<div id="current-folders"></div>
|
||||
</div>
|
||||
<div class="card-action-row">
|
||||
<button id="update-folder" class="card-button">
|
||||
Add
|
||||
<img class="add-files-icon" src="./assets/icons/circular-add.svg" alt="Add">
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<button id="sync-data">Sync</button>
|
||||
</div>
|
||||
<div class="card-description-row sync-force-toggle">
|
||||
<input id="sync-force" type="checkbox" name="sync-force" value="force">
|
||||
<label for="sync-force">Force Sync</label>
|
||||
</div>
|
||||
<div id="loading-bar" style="display: none;">
|
||||
</div>
|
||||
<div class="card-description-row">
|
||||
<div id="sync-status"></div>
|
||||
<button id="delete-all" class="sync-data">🗑️ Delete All</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="loading-bar" style="display: none;"></div>
|
||||
<div class="card-description-row">
|
||||
<div id="sync-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -93,7 +109,7 @@
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr auto auto auto minmax(80px, 100%);
|
||||
grid-template-rows: 1fr auto;
|
||||
font-size: small!important;
|
||||
}
|
||||
body > * {
|
||||
@@ -104,8 +120,7 @@
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min(70vw, 100%) 1fr;
|
||||
grid-template-rows: 1fr auto auto auto minmax(80px, 100%);
|
||||
padding-top: 60vw;
|
||||
grid-template-rows: 80px auto;
|
||||
}
|
||||
body > * {
|
||||
grid-column: 2;
|
||||
@@ -114,7 +129,7 @@
|
||||
body, input {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
background: #fff;
|
||||
background: var(--background-color);
|
||||
color: #475569;
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-size: small;
|
||||
@@ -126,11 +141,6 @@
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
div.page {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
svg {
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
@@ -167,19 +177,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
#khoj-host-url {
|
||||
.card-input {
|
||||
padding: 4px;
|
||||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.3);
|
||||
border: none;
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: grid;
|
||||
/* grid-template-rows: repeat(3, 1fr); */
|
||||
gap: 8px;
|
||||
padding: 24px 16px;
|
||||
width: 100%;
|
||||
background: white;
|
||||
width: 450px;
|
||||
background: var(--background-color);
|
||||
border: 1px solid rgb(229, 229, 229);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.1),0px 1px 2px -1px rgba(0,0,0,0.1);
|
||||
@@ -188,15 +198,15 @@
|
||||
|
||||
.section-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
gap: 16px;
|
||||
justify-items: start;
|
||||
justify-items: center;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
div.configuration {
|
||||
width: auto;
|
||||
.section-action-row {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 16px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.card-title-row {
|
||||
@@ -247,7 +257,7 @@
|
||||
}
|
||||
.primary-button {
|
||||
border: none;
|
||||
color: white;
|
||||
color: var(--background-color);
|
||||
padding: 15px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
@@ -256,7 +266,7 @@
|
||||
}
|
||||
|
||||
button.card-button.disabled {
|
||||
color: rgb(255, 136, 136);
|
||||
color: var(--flower);
|
||||
background: transparent;
|
||||
font-size: small;
|
||||
cursor: pointer;
|
||||
@@ -268,11 +278,7 @@
|
||||
}
|
||||
|
||||
button.card-button.happy {
|
||||
color: rgb(0, 146, 0);
|
||||
}
|
||||
|
||||
button.card-button.happy {
|
||||
color: rgb(0, 146, 0);
|
||||
color: var(--leaf);
|
||||
}
|
||||
|
||||
img.configured-icon {
|
||||
@@ -296,13 +302,14 @@
|
||||
div.folder-element {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgb(229, 229, 229);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.1),0px 1px 2px -1px rgba(0,0,0,0.8);
|
||||
padding: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
div.content-name {
|
||||
width: 500px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -315,7 +322,7 @@
|
||||
background-color: rgb(253 214 214);
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
color: rgb(207, 67, 59);
|
||||
color: var(--flower);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
@@ -324,14 +331,14 @@
|
||||
background-color: rgb(255 235 235);
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
color: rgb(207, 67, 59);
|
||||
color: var(--flower);
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#sync-data {
|
||||
background-color: #ffb300;
|
||||
button.sync-data {
|
||||
background-color: var(--primary);
|
||||
border: none;
|
||||
color: white;
|
||||
color: var(--main-text-color);
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
@@ -340,43 +347,20 @@
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0px 5px 0px #f9f5de;
|
||||
box-shadow: 0px 5px 0px var(--background-color);
|
||||
}
|
||||
|
||||
#sync-data:hover {
|
||||
background-color: #ffcc00;
|
||||
box-shadow: 0px 3px 0px #f9f5de;
|
||||
button.sync-data:hover {
|
||||
background-color: var(--primary-hover);
|
||||
box-shadow: 0px 3px 0px var(--background-color);
|
||||
}
|
||||
.sync-force-toggle {
|
||||
align-content: center;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var khojBannerSubmit = document.getElementById("khoj-banner-submit");
|
||||
khojBannerSubmit?.addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
var email = document.getElementById("khoj-banner-email").value;
|
||||
fetch("https://app.khoj.dev/beta/users/", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
email: email
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}).then(function(response) {
|
||||
return response.json();
|
||||
}).then(function(data) {
|
||||
console.log(data);
|
||||
if (data.user != null) {
|
||||
document.getElementById("khoj-banner").innerHTML = "Thanks for signing up. We'll be in touch soon! 🚀";
|
||||
document.getElementById("khoj-banner-submit").remove();
|
||||
} else {
|
||||
document.getElementById("khoj-banner").innerHTML = "There was an error signing up. Please contact team@khoj.dev";
|
||||
}
|
||||
}).catch(function(error) {
|
||||
console.log(error);
|
||||
document.getElementById("khoj-banner").innerHTML = "There was an error signing up. Please contact team@khoj.dev";
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script src="./renderer.js"></script>
|
||||
|
||||
</html>
|
||||
|
||||
129
src/interface/desktop/loading-animation.js
Normal file
129
src/interface/desktop/loading-animation.js
Normal file
@@ -0,0 +1,129 @@
|
||||
var $wrap = document.getElementById('loading-animation'),
|
||||
|
||||
canvassize = 380,
|
||||
|
||||
length = 40,
|
||||
radius = 6.8,
|
||||
|
||||
rotatevalue = 0.02,
|
||||
acceleration = 0,
|
||||
animatestep = 0,
|
||||
toend = false,
|
||||
|
||||
pi2 = Math.PI*2,
|
||||
|
||||
group = new THREE.Group(),
|
||||
mesh, ringcover, ring,
|
||||
|
||||
camera, scene, renderer;
|
||||
|
||||
|
||||
camera = new THREE.PerspectiveCamera(65, 1, 1, 10000);
|
||||
camera.position.z = 120;
|
||||
|
||||
scene = new THREE.Scene();
|
||||
// scene.add(new THREE.AxisHelper(30));
|
||||
scene.add(group);
|
||||
|
||||
mesh = new THREE.Mesh(
|
||||
new THREE.TubeGeometry(new (THREE.Curve.create(function() {},
|
||||
function(percent) {
|
||||
|
||||
var x = length*Math.sin(pi2*percent),
|
||||
y = radius*Math.cos(pi2*3*percent),
|
||||
z, t;
|
||||
|
||||
t = percent%0.25/0.25;
|
||||
t = percent%0.25-(2*(1-t)*t* -0.0185 +t*t*0.25);
|
||||
if (Math.floor(percent/0.25) == 0 || Math.floor(percent/0.25) == 2) {
|
||||
t *= -1;
|
||||
}
|
||||
z = radius*Math.sin(pi2*2* (percent-t));
|
||||
|
||||
return new THREE.Vector3(x, y, z);
|
||||
|
||||
}
|
||||
))(), 200, 1.1, 2, true),
|
||||
new THREE.MeshBasicMaterial({
|
||||
color: 0xfcc50b
|
||||
// , wireframe: true
|
||||
})
|
||||
);
|
||||
group.add(mesh);
|
||||
|
||||
ringcover = new THREE.Mesh(new THREE.PlaneGeometry(50, 15, 1), new THREE.MeshBasicMaterial({color: 0xd1684e, opacity: 0, transparent: true}));
|
||||
ringcover.position.x = length+1;
|
||||
ringcover.rotation.y = Math.PI/2;
|
||||
group.add(ringcover);
|
||||
|
||||
ring = new THREE.Mesh(new THREE.RingGeometry(4.3, 5.55, 32), new THREE.MeshBasicMaterial({color: 0xfcc50b, opacity: 0, transparent: true}));
|
||||
ring.position.x = length+1.1;
|
||||
ring.rotation.y = Math.PI/2;
|
||||
group.add(ring);
|
||||
|
||||
// fake shadow
|
||||
(function() {
|
||||
var plain, i;
|
||||
for (i = 0; i < 10; i++) {
|
||||
plain = new THREE.Mesh(new THREE.PlaneGeometry(length*2+1, radius*3, 1), new THREE.MeshBasicMaterial({color: 0xd1684e, transparent: true, opacity: 0.15}));
|
||||
plain.position.z = -2.5+i*0.5;
|
||||
group.add(plain);
|
||||
}
|
||||
})();
|
||||
|
||||
renderer = new THREE.WebGLRenderer({
|
||||
antialias: true
|
||||
});
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(canvassize, canvassize);
|
||||
renderer.setClearColor('#d1684e');
|
||||
|
||||
|
||||
$wrap.appendChild(renderer.domElement);
|
||||
|
||||
function start() {
|
||||
toend = true;
|
||||
}
|
||||
|
||||
function back() {
|
||||
toend = false;
|
||||
}
|
||||
|
||||
function tilt(percent) {
|
||||
group.rotation.y = percent*0.5;
|
||||
}
|
||||
|
||||
function render() {
|
||||
var progress;
|
||||
|
||||
animatestep = Math.max(0, Math.min(240, toend ? animatestep+1 : animatestep-4));
|
||||
acceleration = easing(animatestep, 0, 1, 240);
|
||||
|
||||
if (acceleration > 0.35) {
|
||||
progress = (acceleration-0.35)/0.65;
|
||||
group.rotation.y = -Math.PI/2 *progress;
|
||||
group.position.z = 20*progress;
|
||||
progress = Math.max(0, (acceleration-0.99)/0.01);
|
||||
mesh.material.opacity = 1-progress;
|
||||
ringcover.material.opacity = ring.material.opacity = progress;
|
||||
ring.scale.x = ring.scale.y = 0.9 + 0.1*progress;
|
||||
}
|
||||
|
||||
renderer.render(scene, camera);
|
||||
|
||||
}
|
||||
|
||||
function animate() {
|
||||
mesh.rotation.x += rotatevalue + acceleration*Math.sin(Math.PI*acceleration);
|
||||
render();
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
function easing(t, b, c, d) {
|
||||
if ((t /= d/2) < 1)
|
||||
return c/2*t*t+b;
|
||||
return c/2*((t-=2)*t*t+2)+b;
|
||||
}
|
||||
|
||||
animate();
|
||||
setTimeout(start, 30);
|
||||
@@ -1,5 +1,6 @@
|
||||
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, shell } = require('electron');
|
||||
const todesktop = require("@todesktop/runtime");
|
||||
const khojPackage = require('./package.json');
|
||||
|
||||
todesktop.init();
|
||||
|
||||
@@ -9,7 +10,7 @@ const {dialog} = require('electron');
|
||||
const cron = require('cron').CronJob;
|
||||
const axios = require('axios');
|
||||
|
||||
const KHOJ_URL = 'http://127.0.0.1:42110'
|
||||
const KHOJ_URL = 'https://app.khoj.dev';
|
||||
|
||||
const Store = require('electron-store');
|
||||
|
||||
@@ -42,6 +43,10 @@ const schema = {
|
||||
},
|
||||
default: []
|
||||
},
|
||||
khojToken: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
hostURL: {
|
||||
type: 'string',
|
||||
default: KHOJ_URL
|
||||
@@ -62,8 +67,8 @@ const schema = {
|
||||
}
|
||||
};
|
||||
|
||||
let syncing = false;
|
||||
var state = {}
|
||||
|
||||
const store = new Store({ schema });
|
||||
|
||||
console.log(store);
|
||||
@@ -106,6 +111,15 @@ function filenameToMimeType (filename) {
|
||||
}
|
||||
|
||||
function pushDataToKhoj (regenerate = false) {
|
||||
// Don't sync if token or hostURL is not set or if already syncing
|
||||
if (store.get('khojToken') === '' || store.get('hostURL') === '' || syncing === true) {
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
if (win) win.webContents.send('update-state', state);
|
||||
return;
|
||||
} else {
|
||||
syncing = true;
|
||||
}
|
||||
|
||||
let filesToPush = [];
|
||||
const files = store.get('files') || [];
|
||||
const folders = store.get('folders') || [];
|
||||
@@ -168,7 +182,7 @@ function pushDataToKhoj (regenerate = false) {
|
||||
if (!!formData?.entries()?.next().value) {
|
||||
const hostURL = store.get('hostURL') || KHOJ_URL;
|
||||
const headers = {
|
||||
'x-api-key': 'secret'
|
||||
'Authorization': `Bearer ${store.get("khojToken")}`
|
||||
};
|
||||
axios.post(`${hostURL}/api/v1/index/update?force=${regenerate}&client=desktop`, formData, { headers })
|
||||
.then(response => {
|
||||
@@ -188,11 +202,13 @@ function pushDataToKhoj (regenerate = false) {
|
||||
})
|
||||
.finally(() => {
|
||||
// Syncing complete
|
||||
syncing = false;
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
if (win) win.webContents.send('update-state', state);
|
||||
});
|
||||
} else {
|
||||
// Syncing complete
|
||||
syncing = false;
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
if (win) win.webContents.send('update-state', state);
|
||||
}
|
||||
@@ -246,6 +262,15 @@ async function handleFileOpen (type) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getToken () {
|
||||
return store.get('khojToken');
|
||||
}
|
||||
|
||||
async function setToken (event, token) {
|
||||
store.set('khojToken', token);
|
||||
return store.get('khojToken');
|
||||
}
|
||||
|
||||
async function getFiles () {
|
||||
return store.get('files');
|
||||
}
|
||||
@@ -255,6 +280,12 @@ async function getFolders () {
|
||||
}
|
||||
|
||||
async function setURL (event, url) {
|
||||
// Sanitize the URL. Remove trailing slash if present. Add http:// if not present.
|
||||
url = url.replace(/\/$/, "");
|
||||
if (!url.match(/^[a-zA-Z]+:\/\//)) {
|
||||
url = `http://${url}`;
|
||||
}
|
||||
|
||||
store.set('hostURL', url);
|
||||
return store.get('hostURL');
|
||||
}
|
||||
@@ -287,10 +318,26 @@ async function syncData (regenerate = false) {
|
||||
}
|
||||
}
|
||||
|
||||
const createWindow = () => {
|
||||
const win = new BrowserWindow({
|
||||
async function deleteAllFiles () {
|
||||
try {
|
||||
store.set('files', []);
|
||||
store.set('folders', []);
|
||||
pushDataToKhoj(true);
|
||||
const date = new Date();
|
||||
console.log('Pushing data to Khoj at: ', date);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let firstRun = true;
|
||||
let win = null;
|
||||
const createWindow = (tab = 'chat.html') => {
|
||||
win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 800,
|
||||
show: false,
|
||||
// titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
@@ -311,12 +358,30 @@ const createWindow = () => {
|
||||
|
||||
win.setResizable(true);
|
||||
win.setOpacity(0.95);
|
||||
win.setBackgroundColor('#FFFFFF');
|
||||
win.setBackgroundColor('#f5f4f3');
|
||||
win.setHasShadow(true);
|
||||
|
||||
job.start();
|
||||
|
||||
win.loadFile('index.html')
|
||||
win.loadFile(tab)
|
||||
|
||||
if (firstRun === true) {
|
||||
firstRun = false;
|
||||
|
||||
// Create splash screen
|
||||
var splash = new BrowserWindow({width: 400, height: 400, transparent: true, frame: false, alwaysOnTop: true});
|
||||
splash.setOpacity(1.0);
|
||||
splash.setBackgroundColor('#d16b4e');
|
||||
splash.loadFile('splash.html');
|
||||
|
||||
// Show splash screen on app load
|
||||
win.once('ready-to-show', () => {
|
||||
setTimeout(function(){ splash.close(); win.show(); }, 4500);
|
||||
});
|
||||
} else {
|
||||
// Show main window directly if not first run
|
||||
win.once('ready-to-show', () => { win.show(); });
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
@@ -331,6 +396,14 @@ app.whenReady().then(() => {
|
||||
event.reply('update-state', arg);
|
||||
});
|
||||
|
||||
ipcMain.on('navigate', (event, page) => {
|
||||
win.loadFile(page);
|
||||
});
|
||||
|
||||
ipcMain.on('navigateToWebApp', (event, page) => {
|
||||
shell.openExternal(`${store.get('hostURL')}/${page}`);
|
||||
});
|
||||
|
||||
ipcMain.handle('getFiles', getFiles);
|
||||
ipcMain.handle('getFolders', getFolders);
|
||||
|
||||
@@ -340,19 +413,24 @@ app.whenReady().then(() => {
|
||||
ipcMain.handle('setURL', setURL);
|
||||
ipcMain.handle('getURL', getURL);
|
||||
|
||||
ipcMain.handle('setToken', setToken);
|
||||
ipcMain.handle('getToken', getToken);
|
||||
|
||||
ipcMain.handle('syncData', (event, regenerate) => {
|
||||
syncData(regenerate);
|
||||
});
|
||||
ipcMain.handle('deleteAllFiles', deleteAllFiles);
|
||||
|
||||
createWindow()
|
||||
|
||||
app.setAboutPanelOptions({
|
||||
applicationName: "Khoj",
|
||||
applicationVersion: "0.0.1",
|
||||
version: "0.0.1",
|
||||
authors: "Khoj Team",
|
||||
applicationVersion: khojPackage.version,
|
||||
version: khojPackage.version,
|
||||
authors: "Saba Imran, Debanjum Singh Solanky and contributors",
|
||||
website: "https://khoj.dev",
|
||||
iconPath: path.join(__dirname, 'assets', 'khoj.png')
|
||||
copyright: "GPL v3",
|
||||
iconPath: path.join(__dirname, 'assets', 'icons', 'favicon-128x128.png')
|
||||
});
|
||||
|
||||
app.on('ready', async() => {
|
||||
@@ -375,3 +453,71 @@ app.whenReady().then(() => {
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
|
||||
/*
|
||||
** About Page
|
||||
*/
|
||||
|
||||
let aboutWindow;
|
||||
|
||||
function openAboutWindow() {
|
||||
if (aboutWindow) { aboutWindow.focus(); return; }
|
||||
|
||||
aboutWindow = new BrowserWindow({
|
||||
width: 400,
|
||||
height: 400,
|
||||
titleBarStyle: 'hidden',
|
||||
show: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
nodeIntegration: true,
|
||||
},
|
||||
});
|
||||
|
||||
aboutWindow.loadFile('about.html');
|
||||
|
||||
// Pass OS, Khoj version to About page
|
||||
aboutWindow.webContents.on('did-finish-load', () => {
|
||||
aboutWindow.webContents.send('appInfo', { version: khojPackage.version, platform: process.platform });
|
||||
});
|
||||
|
||||
// Open links in external browser
|
||||
aboutWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
shell.openExternal(url);
|
||||
return { action: 'deny' };
|
||||
});
|
||||
|
||||
aboutWindow.once('ready-to-show', () => { aboutWindow.show(); });
|
||||
aboutWindow.on('closed', () => { aboutWindow = null; });
|
||||
}
|
||||
|
||||
/*
|
||||
** System Tray Icon
|
||||
*/
|
||||
|
||||
let tray
|
||||
|
||||
openWindow = (page) => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow(page);
|
||||
} else {
|
||||
win.loadFile(page); win.show();
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const icon = nativeImage.createFromPath('assets/icons/favicon-20x20.png')
|
||||
tray = new Tray(icon)
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{ label: 'Chat', type: 'normal', click: () => { openWindow('chat.html'); }},
|
||||
{ label: 'Search', type: 'normal', click: () => { openWindow('search.html') }},
|
||||
{ label: 'Configure', type: 'normal', click: () => { openWindow('config.html') }},
|
||||
{ type: 'separator' },
|
||||
{ label: 'About Khoj', type: 'normal', click: () => { openAboutWindow(); } },
|
||||
{ label: 'Quit', type: 'normal', click: () => { app.quit() } }
|
||||
])
|
||||
|
||||
tray.setToolTip('Khoj')
|
||||
tray.setContextMenu(contextMenu)
|
||||
})
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
"main": "main.js",
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
"electron": "25.8.1"
|
||||
"electron": "25.8.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "yarn electron ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@todesktop/runtime": "^1.3.0",
|
||||
"axios": "^1.5.0",
|
||||
"axios": "^1.6.0",
|
||||
"cron": "^2.4.3",
|
||||
"electron-store": "^8.1.0",
|
||||
"fs": "^0.0.1-security"
|
||||
|
||||
@@ -45,5 +45,20 @@ contextBridge.exposeInMainWorld('hostURLAPI', {
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('syncDataAPI', {
|
||||
syncData: (regenerate) => ipcRenderer.invoke('syncData', regenerate)
|
||||
syncData: (regenerate) => ipcRenderer.invoke('syncData', regenerate),
|
||||
deleteAllFiles: () => ipcRenderer.invoke('deleteAllFiles')
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('tokenAPI', {
|
||||
setToken: (token) => ipcRenderer.invoke('setToken', token),
|
||||
getToken: () => ipcRenderer.invoke('getToken')
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('appInfoAPI', {
|
||||
getInfo: (callback) => ipcRenderer.on('appInfo', callback)
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('navigateAPI', {
|
||||
navigateToSettings: () => ipcRenderer.send('navigate', 'config.html'),
|
||||
navigateToWebSettings: () => ipcRenderer.send('navigateToWebApp', 'config'),
|
||||
})
|
||||
|
||||
@@ -61,6 +61,7 @@ toggleFoldersButton.addEventListener('click', () => {
|
||||
function makeFileElement(file) {
|
||||
let fileElement = document.createElement("div");
|
||||
fileElement.classList.add("file-element");
|
||||
|
||||
let fileNameElement = document.createElement("div");
|
||||
fileNameElement.classList.add("content-name");
|
||||
fileNameElement.innerHTML = file.path;
|
||||
@@ -82,6 +83,7 @@ function makeFileElement(file) {
|
||||
function makeFolderElement(folder) {
|
||||
let folderElement = document.createElement("div");
|
||||
folderElement.classList.add("folder-element");
|
||||
|
||||
let folderNameElement = document.createElement("div");
|
||||
folderNameElement.classList.add("content-name");
|
||||
folderNameElement.innerHTML = folder.path;
|
||||
@@ -153,11 +155,14 @@ window.updateStateAPI.onUpdateState((event, state) => {
|
||||
loadingBar.style.display = 'none';
|
||||
let syncStatusElement = document.getElementById("sync-status");
|
||||
const currentTime = new Date();
|
||||
nextSyncTime = new Date();
|
||||
nextSyncTime.setMinutes(Math.ceil((nextSyncTime.getMinutes() + 1) / 10) * 10);
|
||||
if (state.completed == false) {
|
||||
syncStatusElement.innerHTML = `Sync was unsuccessful at ${currentTime.toLocaleTimeString()}. Contact team@khoj.dev to report this issue.`;
|
||||
return;
|
||||
}
|
||||
syncStatusElement.innerHTML = `Last synced at ${currentTime.toLocaleTimeString()}`;
|
||||
const options = { hour: '2-digit', minute: '2-digit' };
|
||||
syncStatusElement.innerHTML = `⏱️ Synced at ${currentTime.toLocaleTimeString(undefined, options)}. Next sync at ${nextSyncTime.toLocaleTimeString(undefined, options)}.`;
|
||||
});
|
||||
|
||||
const urlInput = document.getElementById('khoj-host-url');
|
||||
@@ -174,6 +179,7 @@ urlInput.addEventListener('blur', async () => {
|
||||
new URL(urlInputValue);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
alert('Please enter a valid URL');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,10 +187,25 @@ urlInput.addEventListener('blur', async () => {
|
||||
urlInput.value = url;
|
||||
});
|
||||
|
||||
const syncButton = document.getElementById('sync-data');
|
||||
const syncForceToggle = document.getElementById('sync-force');
|
||||
syncButton.addEventListener('click', async () => {
|
||||
loadingBar.style.display = 'block';
|
||||
const regenerate = syncForceToggle.checked;
|
||||
await window.syncDataAPI.syncData(regenerate);
|
||||
const khojKeyInput = document.getElementById('khoj-access-key');
|
||||
(async function() {
|
||||
const token = await window.tokenAPI.getToken();
|
||||
khojKeyInput.value = token;
|
||||
})();
|
||||
|
||||
khojKeyInput.addEventListener('blur', async () => {
|
||||
const token = await window.tokenAPI.setToken(khojKeyInput.value.trim());
|
||||
khojKeyInput.value = token;
|
||||
});
|
||||
|
||||
const syncForceButton = document.getElementById('sync-force');
|
||||
syncForceButton.addEventListener('click', async () => {
|
||||
loadingBar.style.display = 'block';
|
||||
await window.syncDataAPI.syncData(true);
|
||||
});
|
||||
|
||||
const deleteAllButton = document.getElementById('delete-all');
|
||||
deleteAllButton.addEventListener('click', async () => {
|
||||
loadingBar.style.display = 'block';
|
||||
await window.syncDataAPI.deleteAllFiles();
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
</head>
|
||||
<script type="text/javascript" src="./assets/org.min.js"></script>
|
||||
<script type="text/javascript" src="./assets/markdown-it.min.js"></script>
|
||||
<script src="./utils.js"></script>
|
||||
|
||||
<script>
|
||||
function render_image(item) {
|
||||
@@ -94,6 +95,15 @@
|
||||
}).join("\n");
|
||||
}
|
||||
|
||||
function render_xml(query, data) {
|
||||
return data.map(function (item) {
|
||||
return `<div class="results-xml">` +
|
||||
`<b><a href="${item.additional.file}">${item.additional.heading}</a></b>` +
|
||||
`<xml>${item.entry}</xml>` +
|
||||
`</div>`
|
||||
}).join("\n");
|
||||
}
|
||||
|
||||
function render_multiple(query, data, type) {
|
||||
let html = "";
|
||||
data.forEach(item => {
|
||||
@@ -113,6 +123,8 @@
|
||||
html += `<div class="results-notion">` + `<b><a href="${item.additional.file}">${item.additional.heading}</a></b>` + `<p>${item.entry}</p>` + `</div>`;
|
||||
} else if (item.additional.file.endsWith(".html")) {
|
||||
html += render_html(query, [item]);
|
||||
} else if (item.additional.file.endsWith(".xml")) {
|
||||
html += render_xml(query, [item])
|
||||
} else {
|
||||
html += `<div class="results-plugin">` + `<b><a href="${item.additional.file}">${item.additional.heading}</a></b>` + `<p>${item.entry}</p>` + `</div>`;
|
||||
}
|
||||
@@ -170,10 +182,12 @@
|
||||
|
||||
// Execute Search and Render Results
|
||||
url = await createRequestUrl(query, type, results_count || 5, rerank);
|
||||
fetch(url)
|
||||
const khojToken = await window.tokenAPI.getToken();
|
||||
const headers = { 'Authorization': `Bearer ${khojToken}` };
|
||||
|
||||
fetch(url, { headers })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
document.getElementById("results").innerHTML = render_results(data, query, type);
|
||||
});
|
||||
}
|
||||
@@ -192,9 +206,11 @@
|
||||
|
||||
async function populate_type_dropdown() {
|
||||
const hostURL = await window.hostURLAPI.getURL();
|
||||
const khojToken = await window.tokenAPI.getToken();
|
||||
const headers = { 'Authorization': `Bearer ${khojToken}` };
|
||||
|
||||
// Populate type dropdown field with enabled content types only
|
||||
fetch(`${hostURL}/api/config/types`)
|
||||
fetch(`${hostURL}/api/config/types`, { headers })
|
||||
.then(response => response.json())
|
||||
.then(enabled_types => {
|
||||
// Show warning if no content types are enabled
|
||||
@@ -247,9 +263,9 @@
|
||||
<img class="khoj-logo" src="./assets/icons/khoj-logo-sideways-500.png" alt="Khoj"></img>
|
||||
</a>
|
||||
<nav class="khoj-nav">
|
||||
<a class="khoj-nav" href="./chat.html">Chat</a>
|
||||
<a class="khoj-nav khoj-nav-selected" href="./index.html">Search</a>
|
||||
<a class="khoj-nav" href="./config.html">⚙️</a>
|
||||
<a class="khoj-nav" href="./chat.html">💬 Chat</a>
|
||||
<a class="khoj-nav khoj-nav-selected" href="./search.html">🔎 Search</a>
|
||||
<a class="khoj-nav" href="./config.html">⚙️ Settings</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -286,8 +302,8 @@
|
||||
body {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
background: #fff;
|
||||
color: #475569;
|
||||
background: var(--background-color);
|
||||
color: var(--main-text-color);
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-size: small;
|
||||
font-weight: 300;
|
||||
@@ -419,14 +435,6 @@
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
div.khoj-banner-container {
|
||||
background: linear-gradient(-45deg, #FFC107, #FF9800, #FF5722, #FF9800, #FFC107);
|
||||
background-size: 400% 400%;
|
||||
animation: gradient 15s ease infinite;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
@@ -443,57 +451,5 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button#khoj-banner-submit,
|
||||
input#khoj-banner-email {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #475569;
|
||||
background: #f9fafc;
|
||||
}
|
||||
|
||||
button#khoj-banner-submit:hover,
|
||||
input#khoj-banner-email:hover {
|
||||
box-shadow: 0 0 11px #aaa;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
a.khoj-banner {
|
||||
display: block;
|
||||
}
|
||||
p.khoj-banner {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
var khojBannerSubmit = document.getElementById("khoj-banner-submit");
|
||||
khojBannerSubmit?.addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
var email = document.getElementById("khoj-banner-email").value;
|
||||
fetch("https://app.khoj.dev/beta/users/", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
email: email
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}).then(function(response) {
|
||||
return response.json();
|
||||
}).then(function(data) {
|
||||
console.log(data);
|
||||
if (data.user != null) {
|
||||
document.getElementById("khoj-banner").innerHTML = "Thanks for signing up. We'll be in touch soon! 🚀";
|
||||
document.getElementById("khoj-banner-submit").remove();
|
||||
} else {
|
||||
document.getElementById("khoj-banner").innerHTML = "There was an error signing up. Please contact team@khoj.dev";
|
||||
}
|
||||
}).catch(function(error) {
|
||||
console.log(error);
|
||||
document.getElementById("khoj-banner").innerHTML = "There was an error signing up. Please contact team@khoj.dev";
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</html>
|
||||
15
src/interface/desktop/splash.html
Normal file
15
src/interface/desktop/splash.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
||||
<title>Khoj</title>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="./assets/icons/favicon-128x128.png">
|
||||
<link rel="manifest" href="./khoj.webmanifest">
|
||||
</head>
|
||||
<script type="text/javascript" src="./assets/three.min.js"></script>
|
||||
<body>
|
||||
<div id="loading-animation"></div>
|
||||
</body>
|
||||
<script src="./loading-animation.js"></script>
|
||||
</html>
|
||||
26
src/interface/desktop/utils.js
Normal file
26
src/interface/desktop/utils.js
Normal file
@@ -0,0 +1,26 @@
|
||||
console.log(`%c %s`, "font-family:monospace", `
|
||||
__ __ __ __ ______ __ _____ __
|
||||
/\\ \\/ / /\\ \\_\\ \\ /\\ __ \\ /\\ \\ /\\ __ \\ /\\ \\
|
||||
\\ \\ _"-. \\ \\ __ \\ \\ \\ \\/\\ \\ _\\_\\ \\ \\ \\ __ \\ \\ \\ \\
|
||||
\\ \\_\\ \\_\\ \\ \\_\\ \\_\\ \\ \\_____\\ /\\_____\\ \\ \\_\\ \\_\\ \\ \\_\\
|
||||
\\/_/\\/_/ \\/_/\\/_/ \\/_____/ \\/_____/ \\/_/\\/_/ \\/_/
|
||||
|
||||
Greetings traveller,
|
||||
|
||||
I am ✨Khoj✨, your open-source, personal AI copilot.
|
||||
|
||||
See my source code at https://github.com/khoj-ai/khoj
|
||||
Read my operating manual at https://docs.khoj.dev
|
||||
`);
|
||||
|
||||
|
||||
window.appInfoAPI.getInfo((_, info) => {
|
||||
let khojVersionElement = document.getElementById("about-page-version");
|
||||
if (khojVersionElement) {
|
||||
khojVersionElement.innerHTML = `<code>${info.version}</code>`;
|
||||
}
|
||||
let khojTitleElement = document.getElementById("about-page-title");
|
||||
if (khojTitleElement) {
|
||||
khojTitleElement.innerHTML = '<b>Khoj for ' + (info.platform === 'win32' ? 'Windows' : info.platform === 'darwin' ? 'macOS' : 'Linux') + '</b>';
|
||||
}
|
||||
});
|
||||
@@ -163,10 +163,10 @@ atomically@^1.7.0:
|
||||
resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe"
|
||||
integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==
|
||||
|
||||
axios@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.5.0.tgz#f02e4af823e2e46a9768cfc74691fdd0517ea267"
|
||||
integrity sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==
|
||||
axios@^1.6.0:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2"
|
||||
integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.0"
|
||||
form-data "^4.0.0"
|
||||
@@ -379,10 +379,10 @@ electron-updater@^4.6.1:
|
||||
lodash.isequal "^4.5.0"
|
||||
semver "^7.3.5"
|
||||
|
||||
electron@25.8.1:
|
||||
version "25.8.1"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-25.8.1.tgz#092fab5a833db4d9240d4d6f36218cf7ca954f86"
|
||||
integrity sha512-GtcP1nMrROZfFg0+mhyj1hamrHvukfF6of2B/pcWxmWkd5FVY1NJib0tlhiorFZRzQN5Z+APLPr7aMolt7i2AQ==
|
||||
electron@25.8.4:
|
||||
version "25.8.4"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-25.8.4.tgz#b50877aac7d96323920437baf309ad86382cb455"
|
||||
integrity sha512-hUYS3RGdaa6E1UWnzeGnsdsBYOggwMMg4WGxNGvAoWtmRrr6J1BsjFW/yRq4WsJHJce2HdzQXtz4OGXV6yUCLg==
|
||||
dependencies:
|
||||
"@electron/get" "^2.0.0"
|
||||
"@types/node" "^18.11.18"
|
||||
|
||||
Reference in New Issue
Block a user