diff --git a/documentation/assets/img/agents_demo.gif b/documentation/assets/img/agents_demo.gif
deleted file mode 100644
index 2669033e..00000000
Binary files a/documentation/assets/img/agents_demo.gif and /dev/null differ
diff --git a/documentation/assets/img/notion_integration.gif b/documentation/assets/img/notion_integration.gif
deleted file mode 100644
index e976963f..00000000
Binary files a/documentation/assets/img/notion_integration.gif and /dev/null differ
diff --git a/documentation/docs/clients/desktop.md b/documentation/docs/clients/desktop.md
index 4777dda5..cd42fd52 100644
--- a/documentation/docs/clients/desktop.md
+++ b/documentation/docs/clients/desktop.md
@@ -8,7 +8,7 @@ sidebar_position: 1
Use the Desktop app to chat and search with Khoj.
You can also sync any relevant files with Khoj using the app.
-Khoj will use these files to provide contextual reponses when you search or chat.
+Khoj will use these files to provide contextual responses when you search or chat.
## Features
- **Chat**
diff --git a/documentation/docs/clients/web.md b/documentation/docs/clients/web.md
index dc583d71..0d6def28 100644
--- a/documentation/docs/clients/web.md
+++ b/documentation/docs/clients/web.md
@@ -25,7 +25,7 @@ You can upload documents to Khoj from the web interface, one at a time. This is
1. You can drag and drop the document into the chat window.
2. Or click the paperclip icon in the chat window and select the document from your file system.
-
+
### Install on Phone
You can optionally install Khoj as a [Progressive Web App (PWA)](https://web.dev/learn/pwa/installation). This makes it quick and easy to access Khoj on your phone.
diff --git a/documentation/docs/contributing/development.mdx b/documentation/docs/contributing/development.mdx
index 7045d741..d7ea3ed9 100644
--- a/documentation/docs/contributing/development.mdx
+++ b/documentation/docs/contributing/development.mdx
@@ -14,7 +14,7 @@ If you're looking for a place to get started, check out the list of [Github Issu
## Local Server Installation
### Using Pip
-#### 1. Install
+#### 1. Khoj Installation
```mdx-code-block
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
@@ -43,7 +43,7 @@ git clone https://github.com/khoj-ai/khoj && cd khoj
python3 -m venv .venv && .venv\Scripts\activate
# Install Khoj for Development
-pip install -e .[dev]
+pip install -e '.[dev]'
```
@@ -55,14 +55,59 @@ git clone https://github.com/khoj-ai/khoj && cd khoj
python3 -m venv .venv && source .venv/bin/activate
# Install Khoj for Development
-pip install -e .[dev]
+pip install -e '.[dev]'
+ ```
+
+
+```
+#### 2. Postgres Installation & Setup
+
+Khoj uses the `pgvector` package to store embeddings of your index in a Postgres database. To use this, you need to have Postgres installed.
+
+```mdx-code-block
+
+
+Install [Postgres.app](https://postgresapp.com/). This comes pre-installed with `pgvector` and relevant dependencies.
+
+
+ 1. Use the [recommended installer](https://www.postgresql.org/download/windows/).
+ 2. Follow instructions to [Install PgVector](https://github.com/pgvector/pgvector#windows) in case you need to manually install it. Windows support is experimental for pgvector currently, so we recommend using Docker. Refer to Windows Installation Notes below if there are errors.
+
+
+ From [official instructions](https://wiki.postgresql.org/wiki/Apt)
+
+
+ 1. Follow instructions to [Install Postgres](https://www.postgresql.org/download/)
+ 2. Follow instructions to [Install PgVector](https://github.com/pgvector/pgvector#installation) in case you need to manually install it.
+
+
+```
+
+##### Create the Khoj database
+
+Make sure to update your environment variables to match your Postgres configuration if you're using a different name. The default values should work for most people. When prompted for a password, you can use the default password `postgres`, or configure it to your preference. Make sure to set the environment variable `POSTGRES_PASSWORD` to the same value as the password you set here.
+
+```mdx-code-block
+
+
+ ```shell
+createdb khoj -U postgres --password
+ ```
+
+
+ ```shell
+createdb -U postgres khoj --password
+ ```
+
+
+ ```shell
+sudo -u postgres createdb khoj --password
```
```
-
-#### 2. Run
+#### 3. Run
1. Start Khoj
```bash
khoj -vv
@@ -72,6 +117,37 @@ pip install -e .[dev]
Note: Wait after configuration for khoj to Load ML model, generate embeddings and expose API to query notes, images, documents etc specified in config YAML
+#### Windows Installation Notes
+1. Command `khoj` Not Recognized
+ - Try reactivating the virtual environment and rerunning the `khoj` command.
+ - If it still doesn't work repeat the installation process.
+2. Python Package Missing
+ - Use `pip install xxx` and try running the `khoj` command.
+3. Command `createdb` Not Recognized
+ - make sure path to postgres binaries is included in environment variables. It usually looks something like
+ ```
+ C:\Program Files\PostgreSQL\16\bin
+ ```
+4. Connection Refused on port xxxx
+ - Locate the `pg_hba.conf` file in the location where postgres was installed.
+ - Edit the file to have **trust** as the method for user postgres, local, and host connections.
+ - Below is an example:
+```
+host all postgres 127.0.0.1/32 trust
+# "local" is for Unix domain socket connections only
+local all all trust
+# IPv4 local connections:
+host all all 127.0.0.1/32 trust
+# IPv6 local connections:
+host all all ::1/128 trust
+```
+4. Errors with installing pgvector
+ - Reinstall Visual Studio 2022 Build Tools with:
+ 1. desktop development with c++ selected in workloads
+ 2. MSVC (C++ Build Tools), Windows 10/11 SDK, and C++/CLI support for build tools selected in individual components.
+ - Open the x64 Native Tools Command Prompt as an Administrator
+ - Follow the pgvector windows installation [instructions](https://github.com/pgvector/pgvector?tab=readme-ov-file#windows) in this command prompt.
+
### Using Docker
Make sure you install the latest version of [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/).
diff --git a/documentation/docs/data-sources/notion_integration.md b/documentation/docs/data-sources/notion_integration.md
index 67d0d5bc..23fe9f32 100644
--- a/documentation/docs/data-sources/notion_integration.md
+++ b/documentation/docs/data-sources/notion_integration.md
@@ -4,7 +4,7 @@ The Notion integration allows you to search/chat with your Notion workspaces. [N
Go to https://app.khoj.dev/config to connect your Notion workspace(s) to Khoj.
-
+
## Self-Hosted Setup
diff --git a/documentation/docs/features/agents.md b/documentation/docs/features/agents.md
index 249f5bde..bf74671e 100644
--- a/documentation/docs/features/agents.md
+++ b/documentation/docs/features/agents.md
@@ -6,7 +6,7 @@ sidebar_position: 4
You can use agents to setup custom system prompts with Khoj. The server host can setup their own agents, which are accessible to all users. You can see ours at https://app.khoj.dev/agents.
-
+
## Creating an Agent (Self-Hosted)
diff --git a/documentation/docs/features/share.md b/documentation/docs/features/share.md
new file mode 100644
index 00000000..0a20bb41
--- /dev/null
+++ b/documentation/docs/features/share.md
@@ -0,0 +1,7 @@
+# Shareable Chat
+
+You can share any of your conversations by going to the three dot menu on the conversation and selecting 'Share'. This will create a **public** link that you can share with anyone. The link will open the conversation in the same state it was when you shared it, so your future messages will not be visible to the person you shared it with.
+
+This means you can easily share a conversation with someone to show them how you solved a problem, or to get help with something you're working on.
+
+
diff --git a/documentation/docs/get-started/overview.md b/documentation/docs/get-started/overview.md
index 57f93804..a30193e4 100644
--- a/documentation/docs/get-started/overview.md
+++ b/documentation/docs/get-started/overview.md
@@ -38,7 +38,7 @@ Welcome to the Khoj Docs! This is the best place to get setup and explore Khoj's
- [Read these instructions](/get-started/setup) to self-host a private instance of Khoj
## At a Glance
-
+
#### [Search](/features/search)
- **Natural**: Use natural language queries to quickly find relevant notes and documents.
diff --git a/documentation/docs/get-started/setup.mdx b/documentation/docs/get-started/setup.mdx
index b9f32048..cbf2af7c 100644
--- a/documentation/docs/get-started/setup.mdx
+++ b/documentation/docs/get-started/setup.mdx
@@ -59,6 +59,7 @@ Khoj uses the `pgvector` package to store embeddings of your index in a Postgres
Install [Postgres.app](https://postgresapp.com/). This comes pre-installed with `pgvector` and relevant dependencies.
+ For detailed instructions and troubleshooting, see [this section](/contributing/development#2-postgres-installation--setup).
1. Use the [recommended installer](https://www.postgresql.org/download/windows/).
2. Follow instructions to [Install PgVector](https://github.com/pgvector/pgvector#windows) in case you need to manually install it. Windows support is experimental for pgvector currently, so we recommend using Docker.
@@ -117,13 +118,14 @@ python -m pip install khoj-assistant
```
+ In PowerShell on Windows
```shell
# 1. (Optional) To use NVIDIA (CUDA) GPU
$env:CMAKE_ARGS = "-DLLAMA_OPENBLAS=on"
# 1. (Optional) To use AMD (ROCm) GPU
- CMAKE_ARGS="-DLLAMA_HIPBLAS=on"
+ $env:CMAKE_ARGS = "-DLLAMA_HIPBLAS=on"
# 1. (Optional) To use VULCAN GPU
- CMAKE_ARGS="-DLLAMA_VULKAN=on"
+ $env:CMAKE_ARGS = "-DLLAMA_VULKAN=on"
# 2. Install Khoj
py -m pip install khoj-assistant
@@ -201,6 +203,11 @@ To disable HTTPS, set the `KHOJ_NO_HTTPS` environment variable to `True`. This c
1. Go to http://localhost:42110/server/admin and login with your admin credentials.
#### Configure Chat Model
##### Configure OpenAI or a custom OpenAI-compatible proxy server
+
+:::info[Ollama Integration]
+Using Ollama? See the [Ollama Integration](/miscellaneous/ollama) section for more custom setup instructions.
+:::
+
1. Go to the [OpenAI settings](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/) in the server admin settings to add an OpenAI processor conversation config. This is where you set your API key and server API base URL. The API base URL is optional - it's only relevant if you're using another OpenAI-compatible proxy server.
2. Go over to configure your [chat model options](http://localhost:42110/server/admin/database/chatmodeloptions/). Set the `chat-model` field to a supported chat model[^1] of your choice. For example, you can specify `gpt-4-turbo-preview` if you're using OpenAI.
- Make sure to set the `model-type` field to `OpenAI`.
diff --git a/documentation/docs/miscellaneous/ollama.md b/documentation/docs/miscellaneous/ollama.md
new file mode 100644
index 00000000..dc408b2f
--- /dev/null
+++ b/documentation/docs/miscellaneous/ollama.md
@@ -0,0 +1,33 @@
+# Ollama / Khoj
+
+You can run your own open source models locally with Ollama and use them with Khoj.
+
+:::info[Ollama Integration]
+This is only going to be helpful for self-hosted users. If you're using [Khoj Cloud](https://app.khoj.dev), you're limited to our first-party models.
+:::
+
+Khoj supports any OpenAI-API compatible server, which includes [Ollama](http://ollama.ai/). Ollama allows you to start a local server with [several popular open-source LLMs](https://ollama.com/library) directly on your own computer. Combined with Khoj, you can chat with these LLMs and use them to search your notes and documents.
+
+While Khoj also supports local-hosted LLMs downloaded from Hugging Face, the Ollama integration is particularly useful for its ease of setup and multi-model support, especially if you're already using Ollama.
+
+## Setup
+
+1. Setup Ollama: https://ollama.com/
+2. Start your preferred model with Ollama. For example,
+ ```bash
+ ollama run llama3
+ ```
+3. Go to Khoj settings at [OpenAI Processor Conversation Config](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/)
+4. Create a new config.
+ - Name: `ollama`
+ - Api Key: `any string`
+ - Api Base Url: `http://localhost:11434/v1/` (default for Ollama)
+5. Go to [Chat Model Options](http://localhost:42110/server/admin/database/chatmodeloptions/)
+6. Create a new config.
+ - Name: `llama3` (replace with the name of your local model)
+ - Model Type: `Openai`
+ - Openai Config: ``
+ - Max prompt size: `1000` (replace with the max prompt size of your model)
+7. Go to [your config](http://localhost:42110/config) and select the model you just created in the chat model dropdown.
+
+That's it! You should now be able to chat with your Ollama model from Khoj. If you want to add additional models running on Ollama, repeat step 6 for each model.
diff --git a/documentation/docs/miscellaneous/telemetry.md b/documentation/docs/miscellaneous/telemetry.md
index d484d1fa..2d1bd2af 100644
--- a/documentation/docs/miscellaneous/telemetry.md
+++ b/documentation/docs/miscellaneous/telemetry.md
@@ -16,7 +16,11 @@ We don't send any personal information or any information from/about your conten
If you're self-hosting Khoj, you can opt out of telemetry at any time. To do so,
1. Open `~/.khoj/khoj.yml`
-2. Set `should-log-telemetry` to `false`
+2. Add the following configuration:
+```
+app:
+ should-log-telemetry: false
+```
3. Save the file and restart Khoj
If you have any questions or concerns, please reach out to us on [Discord](https://discord.gg/BDgyabRM6e).
diff --git a/manifest.json b/manifest.json
index d9cbf4bd..21d72dca 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"id": "khoj",
"name": "Khoj",
- "version": "1.12.0",
+ "version": "1.12.1",
"minAppVersion": "0.15.0",
"description": "An AI copilot for your Second Brain",
"author": "Khoj Inc.",
diff --git a/pyproject.toml b/pyproject.toml
index 06b2da55..85664ec6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -59,6 +59,7 @@ dependencies = [
"aiohttp ~= 3.9.0",
"langchain <= 0.2.0",
"langchain-openai >= 0.0.5",
+ "langchain-community == 0.0.27",
"requests >= 2.26.0",
"anyio == 3.7.1",
"pymupdf >= 1.23.5",
@@ -84,6 +85,7 @@ dependencies = [
"pytz ~= 2024.1",
"cron-descriptor == 1.4.3",
"django_apscheduler == 0.6.2",
+ "anthropic == 0.26.1",
]
dynamic = ["version"]
@@ -102,7 +104,7 @@ prod = [
"stripe == 7.3.0",
"twilio == 8.11",
"boto3 >= 1.34.57",
- "resend >= 0.8.0",
+ "resend == 1.0.1",
]
dev = [
"khoj-assistant[prod]",
diff --git a/src/interface/desktop/assets/khoj.css b/src/interface/desktop/assets/khoj.css
index 521936b8..9269f649 100644
--- a/src/interface/desktop/assets/khoj.css
+++ b/src/interface/desktop/assets/khoj.css
@@ -188,12 +188,14 @@ img.khoj-logo {
.khoj-nav-dropdown-content.show {
opacity: 1;
pointer-events: auto;
+ border-radius: 20px;
}
.khoj-nav-dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
+ border-radius: 20px;
}
.khoj-nav-dropdown-content a:hover {
background-color: var(--primary-hover);
diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html
index b1b31b71..5078904b 100644
--- a/src/interface/desktop/chat.html
+++ b/src/interface/desktop/chat.html
@@ -334,6 +334,16 @@
let anchorElements = element.querySelectorAll('a');
anchorElements.forEach((anchorElement) => {
+ // Tag external links to open in separate window
+ if (
+ !anchorElement.href.startsWith("./") &&
+ !anchorElement.href.startsWith("#") &&
+ !anchorElement.href.startsWith("/")
+ ) {
+ anchorElement.setAttribute('target', '_blank');
+ anchorElement.setAttribute('rel', 'noopener noreferrer');
+ }
+
// Add the class "inline-chat-link" to each element
anchorElement.classList.add("inline-chat-link");
});
@@ -1024,11 +1034,12 @@
threeDotMenu.appendChild(conversationMenu);
let deleteButton = document.createElement('button');
+ deleteButton.type = "button";
deleteButton.innerHTML = "Delete";
deleteButton.classList.add("delete-conversation-button");
deleteButton.classList.add("three-dot-menu-button-item");
- deleteButton.addEventListener('click', function() {
- // Ask for confirmation before deleting chat session
+ deleteButton.addEventListener('click', function(event) {
+ event.preventDefault();
let confirmation = confirm('Are you sure you want to delete this chat session?');
if (!confirmation) return;
let deleteURL = `/api/chat/history?client=web&conversation_id=${incomingConversationId}`;
@@ -1643,9 +1654,10 @@
content: "▶";
margin-right: 5px;
display: inline-block;
- transition: transform 0.3s ease-in-out;
+ transition: transform 0.1s ease-in-out;
}
+ button.reference-button.expanded::before,
button.reference-button:active:before,
button.reference-button[aria-expanded="true"]::before {
transform: rotate(90deg);
@@ -1876,6 +1888,7 @@
text-align: left;
display: flex;
position: relative;
+ margin: 0 8px;
}
.three-dot-menu {
diff --git a/src/interface/desktop/package.json b/src/interface/desktop/package.json
index 91c76e28..dfc52cbf 100644
--- a/src/interface/desktop/package.json
+++ b/src/interface/desktop/package.json
@@ -1,6 +1,6 @@
{
"name": "Khoj",
- "version": "1.12.0",
+ "version": "1.12.1",
"description": "An AI copilot for your Second Brain",
"author": "Saba Imran, Debanjum Singh Solanky ",
"license": "GPL-3.0-or-later",
diff --git a/src/interface/desktop/utils.js b/src/interface/desktop/utils.js
index 49872dcf..c880a7cd 100644
--- a/src/interface/desktop/utils.js
+++ b/src/interface/desktop/utils.js
@@ -84,6 +84,7 @@ async function populateHeaderPane() {
`}
+
+
diff --git a/src/khoj/interface/web/agents.html b/src/khoj/interface/web/agents.html
index 21a5cda2..36bb4caf 100644
--- a/src/khoj/interface/web/agents.html
+++ b/src/khoj/interface/web/agents.html
@@ -193,9 +193,55 @@
}
}
+ .loader {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ display: inline-block;
+ border-top: 4px solid var(--primary-color);
+ border-right: 4px solid transparent;
+ box-sizing: border-box;
+ animation: rotation 1s linear infinite;
+ }
+ .loader::after {
+ content: '';
+ box-sizing: border-box;
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ border-left: 4px solid var(--summer-sun);
+ border-bottom: 4px solid transparent;
+ animation: rotation 0.5s linear infinite reverse;
+ }
+ @keyframes rotation {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+
+
-
+
@@ -179,11 +178,30 @@ To get started, just start typing below. You can also type / to see a list of co
return referenceButton;
}
-
- function renderMessage(message, by, dt=null, annotations=null, raw=false, renderType="append") {
+ var khojQuery = "";
+ function renderMessage(message, by, dt=null, annotations=null, raw=false, renderType="append", userQuery=null) {
let message_time = formatDate(dt ?? new Date());
let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You";
- let formattedMessage = formatHTMLMessage(message, raw);
+ let formattedMessage = formatHTMLMessage(message, raw, true, userQuery);
+ //update userQuery or khojQuery to latest query for feedback purposes
+ if(by !== "khoj"){
+ raw = formattedMessage.innerHTML;
+ }
+
+ //find the thumbs up and thumbs down buttons from the message formatter
+ var thumbsUpButtons = formattedMessage.querySelectorAll('.thumbs-up-button');
+ var thumbsDownButtons = formattedMessage.querySelectorAll('.thumbs-down-button');
+
+ //only render the feedback options if the message is a response from khoj
+ if(by !== "khoj"){
+ thumbsUpButtons.forEach(function(element) {
+ element.parentNode.removeChild(element);
+ });
+ thumbsDownButtons.forEach(function(element) {
+ element.parentNode.removeChild(element);
+ });
+ }
+
// Create a new div for the chat message
let chatMessage = document.createElement('div');
@@ -303,7 +321,22 @@ To get started, just start typing below. You can also type / to see a list of co
return imageMarkdown;
}
- function formatHTMLMessage(message, raw=false, willReplace=true) {
+ //handler function for posting feedback data to endpoint
+ function sendFeedback(_uquery="", _kquery="", _sentiment="") {
+ const uquery = _uquery;
+ const kquery = _kquery;
+ const sentiment = _sentiment;
+ fetch('/api/chat/feedback', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({uquery: uquery, kquery: kquery, sentiment: sentiment})
+ })
+ .then(response => response.json())
+ }
+
+ function formatHTMLMessage(message, raw=false, willReplace=true, userQuery) {
var md = window.markdownit();
let newHTML = message;
@@ -347,7 +380,35 @@ To get started, just start typing below. You can also type / to see a list of co
copyIcon.classList.add("copy-icon");
copyButton.appendChild(copyIcon);
copyButton.addEventListener('click', createCopyParentText(message));
- element.append(copyButton);
+
+ //create thumbs-up button
+ let thumbsUpButton = document.createElement('button');
+ thumbsUpButton.className = 'thumbs-up-button';
+ let thumbsUpIcon = document.createElement("img");
+ thumbsUpIcon.src = "/static/assets/icons/thumbs-up-svgrepo-com.svg";
+ thumbsUpIcon.classList.add("thumbs-up-icon");
+ thumbsUpButton.appendChild(thumbsUpIcon);
+ thumbsUpButton.onclick = function() {
+ khojQuery = newHTML;
+ thumbsUpIcon.src = "/static/assets/icons/confirm-icon.svg";
+ sendFeedback(userQuery ,khojQuery, "Good Response");
+ };
+
+ // Create thumbs-down button
+ let thumbsDownButton = document.createElement('button');
+ thumbsDownButton.className = 'thumbs-down-button';
+ let thumbsDownIcon = document.createElement("img");
+ thumbsDownIcon.src = "/static/assets/icons/thumbs-down-svgrepo-com.svg";
+ thumbsDownIcon.classList.add("thumbs-down-icon");
+ thumbsDownButton.appendChild(thumbsDownIcon);
+ thumbsDownButton.onclick = function() {
+ khojQuery = newHTML;
+ thumbsDownIcon.src = "/static/assets/icons/confirm-icon.svg";
+ sendFeedback(userQuery, khojQuery, "Bad Response");
+ };
+
+ // Append buttons to parent element
+ element.append(copyButton, thumbsDownButton, thumbsUpButton);
}
renderMathInElement(element, {
@@ -355,7 +416,6 @@ To get started, just start typing below. You can also type / to see a list of co
// • auto-render specific keys, e.g.:
delimiters: [
{left: '$$', right: '$$', display: true},
- {left: '$', right: '$', display: false},
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
],
@@ -546,7 +606,7 @@ To get started, just start typing below. You can also type / to see a list of co
} else {
// If the chunk is not a JSON object, just display it as is
rawResponse += chunk;
- handleStreamResponse(newResponseText, rawResponse, loadingEllipsis);
+ handleStreamResponse(newResponseText, rawResponse, query, loadingEllipsis);
readStream();
}
});
@@ -582,14 +642,14 @@ To get started, just start typing below. You can also type / to see a list of co
return loadingEllipsis;
}
- function handleStreamResponse(newResponseElement, rawResponse, loadingEllipsis, replace=true) {
+ function handleStreamResponse(newResponseElement, rawResponse, rawQuery, loadingEllipsis, replace=true) {
if (newResponseElement.getElementsByClassName("lds-ellipsis").length > 0 && loadingEllipsis) {
newResponseElement.removeChild(loadingEllipsis);
}
if (replace) {
newResponseElement.innerHTML = "";
}
- newResponseElement.appendChild(formatHTMLMessage(rawResponse, false, replace));
+ newResponseElement.appendChild(formatHTMLMessage(rawResponse, false, replace, rawQuery));
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
}
@@ -889,6 +949,7 @@ To get started, just start typing below. You can also type / to see a list of co
loadingEllipsis: null,
references: {},
rawResponse: "",
+ rawQuery: "",
}
if (chatBody.dataset.conversationId) {
@@ -907,6 +968,7 @@ To get started, just start typing below. You can also type / to see a list of co
// Append any references after all the data has been streamed
finalizeChatBodyResponse(websocketState.references, websocketState.newResponseTextEl);
+ const liveQuery = websocketState.rawQuery;
// Reset variables
websocketState = {
newResponseTextEl: null,
@@ -914,6 +976,7 @@ To get started, just start typing below. You can also type / to see a list of co
loadingEllipsis: null,
references: {},
rawResponse: "",
+ rawQuery: liveQuery,
}
} else {
try {
@@ -935,9 +998,9 @@ To get started, just start typing below. You can also type / to see a list of co
websocketState.rawResponse = rawResponse;
websocketState.references = references;
} else if (chunk.type == "status") {
- handleStreamResponse(websocketState.newResponseTextEl, chunk.message, null, false);
+ handleStreamResponse(websocketState.newResponseTextEl, chunk.message, websocketState.rawQuery, null, false);
} else if (chunk.type == "rate_limit") {
- handleStreamResponse(websocketState.newResponseTextEl, chunk.message, websocketState.loadingEllipsis, true);
+ handleStreamResponse(websocketState.newResponseTextEl, chunk.message, websocketState.rawQuery, websocketState.loadingEllipsis, true);
} else {
rawResponse = chunk.response;
}
@@ -960,7 +1023,7 @@ To get started, just start typing below. You can also type / to see a list of co
// If the chunk is not a JSON object, just display it as is
websocketState.rawResponse += chunk;
if (websocketState.newResponseTextEl) {
- handleStreamResponse(websocketState.newResponseTextEl, websocketState.rawResponse, websocketState.loadingEllipsis);
+ handleStreamResponse(websocketState.newResponseTextEl, websocketState.rawResponse, websocketState.rawQuery, websocketState.loadingEllipsis);
}
}
@@ -1037,6 +1100,7 @@ To get started, just start typing below. You can also type / to see a list of co
loadingEllipsis,
references,
rawResponse,
+ rawQuery: query,
}
}
@@ -1154,7 +1218,8 @@ To get started, just start typing below. You can also type / to see a list of co
new Date(chat_log.created + "Z"),
chat_log.onlineContext,
chat_log.intent?.type,
- chat_log.intent?.["inferred-queries"]);
+ chat_log.intent?.["inferred-queries"],
+ chat_log.intent?.query);
chatBody.appendChild(messageElement);
// When the 4th oldest message is within viewing distance (~60% scroll up)
@@ -1249,7 +1314,8 @@ To get started, just start typing below. You can also type / to see a list of co
new Date(chat_log.created + "Z"),
chat_log.onlineContext,
chat_log.intent?.type,
- chat_log.intent?.["inferred-queries"]
+ chat_log.intent?.["inferred-queries"],
+ chat_log.intent?.query
);
entry.target.replaceWith(messageElement);
@@ -1512,11 +1578,79 @@ To get started, just start typing below. You can also type / to see a list of co
conversationMenu.appendChild(editTitleButton);
threeDotMenu.appendChild(conversationMenu);
+ let shareButton = document.createElement('button');
+ shareButton.innerHTML = "Share";
+ shareButton.type = "button";
+ shareButton.classList.add("share-conversation-button");
+ shareButton.classList.add("three-dot-menu-button-item");
+ shareButton.addEventListener('click', function(event) {
+ event.preventDefault();
+ let confirmation = confirm('Are you sure you want to share this chat session? This will make the conversation public.');
+ if (!confirmation) return;
+ let duplicateURL = `/api/chat/share?client=web&conversation_id=${incomingConversationId}`;
+ fetch(duplicateURL , { method: "POST" })
+ .then(response => response.ok ? response.json() : Promise.reject(response))
+ .then(data => {
+ if (data.status == "ok") {
+ flashStatusInChatInput("✅ Conversation shared successfully");
+ }
+ // Make a pop-up that shows data.url to share the conversation
+ let shareURL = data.url;
+ let shareModal = document.createElement('div');
+ shareModal.classList.add("modal");
+ shareModal.id = "share-conversation-modal";
+ let shareModalContent = document.createElement('div');
+ shareModalContent.classList.add("modal-content");
+ let shareModalHeader = document.createElement('div');
+ shareModalHeader.classList.add("modal-header");
+ let shareModalTitle = document.createElement('h2');
+ shareModalTitle.textContent = "Share Conversation";
+ let shareModalCloseButton = document.createElement('button');
+ shareModalCloseButton.classList.add("modal-close-button");
+ shareModalCloseButton.innerHTML = "×";
+ shareModalCloseButton.addEventListener('click', function() {
+ shareModal.remove();
+ });
+ shareModalHeader.appendChild(shareModalTitle);
+ shareModalHeader.appendChild(shareModalCloseButton);
+ shareModalContent.appendChild(shareModalHeader);
+ let shareModalBody = document.createElement('div');
+ shareModalBody.classList.add("modal-body");
+ let shareModalText = document.createElement('p');
+ shareModalText.textContent = "The link has been copied to your clipboard. Use it to share your conversation with others!";
+ let shareModalLink = document.createElement('input');
+ shareModalLink.setAttribute("value", shareURL);
+ shareModalLink.setAttribute("readonly", "");
+ shareModalLink.classList.add("share-link");
+ let copyButton = document.createElement('button');
+ copyButton.textContent = "Copy";
+ copyButton.addEventListener('click', function() {
+ shareModalLink.select();
+ document.execCommand('copy');
+ });
+ copyButton.id = "copy-share-url-button";
+ shareModalBody.appendChild(shareModalText);
+ shareModalBody.appendChild(shareModalLink);
+ shareModalBody.appendChild(copyButton);
+ shareModalContent.appendChild(shareModalBody);
+ shareModal.appendChild(shareModalContent);
+ document.body.appendChild(shareModal);
+ shareModalLink.select();
+ document.execCommand('copy');
+ })
+ .catch(err => {
+ return;
+ });
+ });
+ conversationMenu.appendChild(shareButton);
+
let deleteButton = document.createElement('button');
+ deleteButton.type = "button";
deleteButton.innerHTML = "Delete";
deleteButton.classList.add("delete-conversation-button");
deleteButton.classList.add("three-dot-menu-button-item");
- deleteButton.addEventListener('click', function() {
+ deleteButton.addEventListener('click', function(event) {
+ event.preventDefault();
// Ask for confirmation before deleting chat session
let confirmation = confirm('Are you sure you want to delete this chat session?');
if (!confirmation) return;
@@ -1864,9 +1998,10 @@ To get started, just start typing below. You can also type / to see a list of co
content: "▶";
margin-right: 5px;
display: inline-block;
- transition: transform 0.3s ease-in-out;
+ transition: transform 0.1s ease-in-out;
}
+ button.reference-button.expanded::before,
button.reference-button:active:before,
button.reference-button[aria-expanded="true"]::before {
transform: rotate(90deg);
@@ -2175,7 +2310,7 @@ To get started, just start typing below. You can also type / to see a list of co
}
.side-panel-button {
- background: var(--background-color);
+ background: none;
border: none;
box-shadow: none;
font-size: 14px;
@@ -2274,6 +2409,32 @@ To get started, just start typing below. You can also type / to see a list of co
float: right;
}
+ button.thumbs-up-button {
+ border-radius: 4px;
+ background-color: var(--background-color);
+ border: 1px solid var(--main-text-color);
+ text-align: center;
+ font-size: 16px;
+ transition: all 0.5s;
+ cursor: pointer;
+ padding: 4px;
+ float: right;
+ margin-right: 4px;
+ }
+
+ button.thumbs-down-button {
+ border-radius: 4px;
+ background-color: var(--background-color);
+ border: 1px solid var(--main-text-color);
+ text-align: center;
+ font-size: 16px;
+ transition: all 0.5s;
+ cursor: pointer;
+ padding: 4px;
+ float: right;
+ margin-right:4px;
+ }
+
button.copy-button span {
cursor: pointer;
display: inline-block;
@@ -2282,8 +2443,18 @@ To get started, just start typing below. You can also type / to see a list of co
}
img.copy-icon {
- width: 16px;
- height: 16px;
+ width: 18px;
+ height: 18px;
+ }
+
+ img.thumbs-up-icon {
+ width: 18px;
+ height: 18px;
+ }
+
+ img.thumbs-down-icon {
+ width: 18px;
+ height: 18px;
}
button.copy-button:hover {
@@ -2291,6 +2462,16 @@ To get started, just start typing below. You can also type / to see a list of co
color: #f5f5f5;
}
+ button.thumbs-up-button:hover {
+ background-color: var(--primary-hover);
+ color: #f5f5f5;
+ }
+
+ button.thumbs-down-button:hover {
+ background-color: var(--primary-hover);
+ color: #f5f5f5;
+ }
+
pre {
text-wrap: unset;
}
@@ -2394,6 +2575,7 @@ To get started, just start typing below. You can also type / to see a list of co
margin-bottom: 8px;
}
+ button#copy-share-url-button,
button#new-conversation-button {
display: inline-flex;
align-items: center;
@@ -2414,14 +2596,12 @@ To get started, just start typing below. You can also type / to see a list of co
text-align: left;
display: flex;
position: relative;
+ margin-right: 8px;
}
.three-dot-menu {
display: block;
- /* background: var(--background-color); */
- /* border: 1px solid var(--main-text-color); */
border-radius: 5px;
- /* position: relative; */
position: absolute;
right: 4px;
top: 4px;
@@ -2603,13 +2783,6 @@ To get started, just start typing below. You can also type / to see a list of co
color: #333;
}
- #agent-instructions {
- font-size: 14px;
- color: #666;
- height: 50px;
- overflow: auto;
- }
-
#agent-owned-by-user {
font-size: 12px;
color: #007BFF;
@@ -2631,7 +2804,7 @@ To get started, just start typing below. You can also type / to see a list of co
margin: 15% auto; /* 15% from the top and centered */
padding: 20px;
border: 1px solid #888;
- width: 250px;
+ width: 300px;
text-align: left;
background: var(--background-color);
border-radius: 5px;
@@ -2705,6 +2878,28 @@ To get started, just start typing below. You can also type / to see a list of co
border: 1px solid var(--main-text-color);
}
+ .share-link {
+ display: block;
+ width: 100%;
+ padding: 10px;
+ margin-top: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background-color: #f9f9f9;
+ font-family: 'Courier New', monospace;
+ color: #333;
+ font-size: 16px;
+ box-sizing: border-box;
+ transition: all 0.3s ease;
+ }
+
+ .share-link:focus {
+ outline: none;
+ border-color: #007BFF;
+ box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
+ }
+
+ button#copy-share-url-button,
button#new-conversation-submit-button {
background: var(--summer-sun);
transition: background 0.2s ease-in-out;
@@ -2715,6 +2910,7 @@ To get started, just start typing below. You can also type / to see a list of co
transition: background 0.2s ease-in-out;
}
+ button#copy-share-url-button:hover,
button#new-conversation-submit-button:hover {
background: var(--primary);
}
diff --git a/src/khoj/interface/web/config.html b/src/khoj/interface/web/config.html
index b808ef33..64841f97 100644
--- a/src/khoj/interface/web/config.html
+++ b/src/khoj/interface/web/config.html
@@ -635,85 +635,6 @@
// List user's API keys on page load
listApiKeys();
- function deleteAutomation(automationId) {
- const AutomationList = document.getElementById("automations-list");
- fetch(`/api/automation?automation_id=${automationId}`, {
- method: 'DELETE',
- })
- .then(response => {
- if (response.status == 200) {
- const AutomationItem = document.getElementById(`automation-item-${automationId}`);
- AutomationList.removeChild(AutomationItem);
- }
- });
- }
-
- function generateAutomationRow(automationObj) {
- let automationId = automationObj.id;
- let automationNextRun = `Next run at ${automationObj.next}`;
- return `
-
-
${automationObj.subject}
-
${automationObj.scheduling_request}
-
${automationObj.query_to_run}
-
${automationObj.schedule}
-
-
-
-
-
- `;
- }
-
- function listAutomations() {
- const AutomationsList = document.getElementById("automations-list");
- fetch('/api/automations')
- .then(response => response.json())
- .then(automations => {
- if (!automations?.length > 0) return;
- AutomationsList.innerHTML = automations.map(generateAutomationRow).join("");
- });
- }
-
- async function createAutomation() {
- const scheduling_request = window.prompt("Describe the automation you want to create");
- if (!scheduling_request) return;
-
- const ip_response = await fetch("https://ipapi.co/json");
- const ip_data = await ip_response.json();
-
- const query_string = `q=${scheduling_request}&city=${ip_data.city}®ion=${ip_data.region}&country=${ip_data.country_name}&timezone=${ip_data.timezone}`;
- const automation_response = await fetch(`/api/automation?${query_string}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- });
- if (!automation_response.ok) {
- throw new Error(`Failed to create automation: ${automation_response.status}`);
- }
-
- listAutomations();
- }
- document.getElementById("create-automation").addEventListener("click", async () => { await createAutomation(); });
-
- function editAutomation(automationId) {
- const query_to_run = window.prompt("What is the query you want to run on this automation's schedule?");
- if (!query_to_run) return;
-
- fetch(`/api/automation?automation_id=${automationId}&query_to_run=${query_to_run}`, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- },
- }).then(response => {
- if (response.ok) {
- const automationQueryToRunColumn = document.getElementById(`automation-query-to-run-${automationId}`);
- automationQueryToRunColumn.innerHTML = `${query_to_run}`;
- }
- });
- }
-
function getIndexedDataSize() {
document.getElementById("indexed-data-size").innerHTML = "Calculating...";
fetch('/api/config/index/size')
@@ -723,9 +644,6 @@
});
}
- // List user's automations on page load
- listAutomations();
-
function removeFile(path) {
fetch('/api/config/data/file?filename=' + path, {
method: 'DELETE',
diff --git a/src/khoj/interface/web/config_automation.html b/src/khoj/interface/web/config_automation.html
index d307a463..d8089053 100644
--- a/src/khoj/interface/web/config_automation.html
+++ b/src/khoj/interface/web/config_automation.html
@@ -6,17 +6,29 @@
Automate (Preview)
- You can automate queries to run on a schedule using Khoj's automations for smart reminders. Results will be sent straight to your inbox. This is an experimental feature, so your results may vary. Report any issues to team@khoj.dev.
+ Automations allow you to schedule smart reminders using Khoj. This is an experimental feature, so your results may vary! Send any feedback to team@khoj.dev.
+