diff --git a/documentation/docs/get-started/setup.mdx b/documentation/docs/get-started/setup.mdx index 9cbb5d8d..29e5083e 100644 --- a/documentation/docs/get-started/setup.mdx +++ b/documentation/docs/get-started/setup.mdx @@ -15,7 +15,7 @@ import TabItem from '@theme/TabItem'; ``` ## Setup -These are the general setup instructions for Khoj. +These are the general setup instructions for self-hosted Khoj. - Make sure [python](https://realpython.com/installing-python/) and [pip](https://pip.pypa.io/en/stable/installation/) are installed on your machine - Check the [Khoj Emacs docs](/clients/emacs#setup) to setup Khoj with Emacs
@@ -23,7 +23,7 @@ These are the general setup instructions for Khoj. - Check the [Khoj Obsidian docs](/clients/obsidian#setup) to setup Khoj with Obsidian
Its simpler as it can skip the *configure* step below. -For Installation, you can either use Docker or install Khoj locally. +For Installation, you can either use Docker or install the Khoj server locally. ### Installation Option 1 (Docker) @@ -267,6 +267,18 @@ You can head to http://localhost:42110 to use the web interface. You can also us ## Troubleshoot +#### Dependency conflict when trying to install Khoj python package with pip +- **Reason**: When conflicting dependency versions are required by Khoj vs other python packages installed on your system +- **Fix**: Install Khoj in a python virtual environment using [venv](https://docs.python.org/3/library/venv.html) or [pipx](https://pypa.github.io/pipx) to avoid this dependency conflicts +- **Process**: + 1. Install [pipx](https://pypa.github.io/pipx/#install-pipx) + 2. Use `pipx` to install Khoj to avoid dependency conflicts with other python packages. + ```shell + pipx install khoj-assistant + ``` + 3. Now start `khoj` using the standard steps described earlier + + #### Install fails while building Tokenizer dependency - **Details**: `pip install khoj-assistant` fails while building the `tokenizers` dependency. Complains about Rust. - **Fix**: Install Rust to build the tokenizers package. For example on Mac run: diff --git a/src/interface/desktop/about.html b/src/interface/desktop/about.html index 83ef1573..ec729df0 100644 --- a/src/interface/desktop/about.html +++ b/src/interface/desktop/about.html @@ -5,7 +5,7 @@ Khoj - About - + diff --git a/src/interface/desktop/assets/khoj.css b/src/interface/desktop/assets/khoj.css index b8157bcc..c2a7d367 100644 --- a/src/interface/desktop/assets/khoj.css +++ b/src/interface/desktop/assets/khoj.css @@ -1,6 +1,6 @@ /* Amber Light scheme (Default) */ /* Can be forced with data-theme="light" */ -@import url('https://fonts.googleapis.com/css2?family=Tajawal&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;500;700&display=swap'); [data-theme="light"], :root:not([data-theme="dark"]) { diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html index 6d5ced15..78df9d4c 100644 --- a/src/interface/desktop/chat.html +++ b/src/interface/desktop/chat.html @@ -5,7 +5,7 @@ Khoj - Chat - + @@ -104,7 +104,7 @@ linkElement.classList.add("inline-chat-link"); linkElement.classList.add("reference-link"); linkElement.setAttribute('title', title); - linkElement.innerHTML = title; + linkElement.textContent = title; let referenceButton = document.createElement('button'); referenceButton.innerHTML = linkElement.outerHTML; @@ -133,7 +133,6 @@ let message_time = formatDate(dt ?? new Date()); let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You"; let formattedMessage = formatHTMLMessage(message, raw); - let chatBody = document.getElementById("chat-body"); // Create a new div for the chat message let chatMessage = document.createElement('div'); @@ -152,6 +151,7 @@ } // Append chat message div to chat body + let chatBody = document.getElementById("chat-body"); chatBody.appendChild(chatMessage); // Scroll to bottom of chat-body element @@ -285,9 +285,12 @@ // Render markdown newHTML = raw ? newHTML : md.render(newHTML); - // Get any elements with a class that starts with "language" + // Set rendered markdown to HTML DOM element let element = document.createElement('div'); element.innerHTML = newHTML; + element.className = "chat-message-text-response"; + + // Get any elements with a class that starts with "language" let codeBlockElements = element.querySelectorAll('[class^="language-"]'); // For each element, add a parent div with the class "programmatic-output" codeBlockElements.forEach((codeElement) => { @@ -341,22 +344,20 @@ let chat_body = document.getElementById("chat-body"); let conversationID = chat_body.dataset.conversationId; - let hostURL = await window.hostURLAPI.getURL(); + const khojToken = await window.tokenAPI.getToken(); + const headers = { 'Authorization': `Bearer ${khojToken}` }; if (!conversationID) { - let response = await fetch(`${hostURL}/api/chat/sessions`, { method: "POST" }); + let response = await fetch(`${hostURL}/api/chat/sessions`, { method: "POST", headers }); let data = await response.json(); conversationID = data.conversation_id; chat_body.dataset.conversationId = conversationID; await refreshChatSessionsPanel(); } - // Generate backend API URL to execute query - let url = `${hostURL}/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true&conversation_id=${conversationID}®ion=${region}&city=${city}&country=${countryName}`; - const khojToken = await window.tokenAPI.getToken(); - const headers = { 'Authorization': `Bearer ${khojToken}` }; + let chatApi = `${hostURL}/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true&conversation_id=${conversationID}®ion=${region}&city=${city}&country=${countryName}`; let new_response = document.createElement("div"); new_response.classList.add("chat-message", "khoj"); @@ -379,8 +380,8 @@ let chatInput = document.getElementById("chat-input"); chatInput.classList.remove("option-enabled"); - // Call specified Khoj API - let response = await fetch(url, { headers }); + // Call Khoj chat API + let response = await fetch(chatApi, { headers }); let rawResponse = ""; const contentType = response.headers.get("content-type"); @@ -540,6 +541,7 @@ chatInput.value = chatInput.value.trimStart(); let questionStarterSuggestions = document.getElementById("question-starters"); + questionStarterSuggestions.innerHTML = ""; questionStarterSuggestions.style.display = "none"; if (chatInput.value.startsWith("/") && chatInput.value.split(" ").length === 1) { @@ -591,6 +593,7 @@ const headers = { 'Authorization': `Bearer ${khojToken}` }; let chatBody = document.getElementById("chat-body"); + chatBody.innerHTML = ""; let conversationId = chatBody.dataset.conversationId; let chatHistoryUrl = `/api/chat/history?client=desktop`; if (conversationId) { @@ -671,11 +674,11 @@ fetch(`${hostURL}/api/chat/starters?client=desktop`, { headers }) .then(response => response.json()) .then(data => { - // Render chat options, if any + // Render conversation starters, if any if (data.length > 0) { let questionStarterSuggestions = document.getElementById("question-starters"); - for (let index in data) { - let questionStarter = data[index]; + questionStarterSuggestions.innerHTML = ""; + data.forEach((questionStarter) => { let questionStarterButton = document.createElement('button'); questionStarterButton.innerHTML = questionStarter; questionStarterButton.classList.add("question-starter"); @@ -685,7 +688,7 @@ chat(); }); questionStarterSuggestions.appendChild(questionStarterButton); - } + }); questionStarterSuggestions.style.display = "grid"; } }) @@ -785,7 +788,7 @@ let conversationButton = document.createElement('div'); let incomingConversationId = conversation["conversation_id"]; const conversationTitle = conversation["slug"] || `New conversation 🌱`; - conversationButton.innerHTML = conversationTitle; + conversationButton.textContent = conversationTitle; conversationButton.classList.add("conversation-button"); if (incomingConversationId == conversationId) { conversationButton.classList.add("selected-conversation"); @@ -883,7 +886,7 @@ fetch(`${hostURL}${editURL}` , { method: "PATCH" }) .then(response => response.ok ? response.json() : Promise.reject(response)) .then(data => { - conversationButton.innerHTML = newTitle; + conversationButton.textContent = newTitle; }) .catch(err => { return; @@ -1014,6 +1017,7 @@ document.getElementById('side-panel').classList.toggle('collapsed'); document.getElementById('new-conversation').classList.toggle('collapsed'); document.getElementById('existing-conversations').classList.toggle('collapsed'); + document.getElementById('side-panel-collapse').style.transform = document.getElementById('side-panel').classList.contains('collapsed') ? 'rotate(0deg)' : 'rotate(180deg)'; document.getElementById('chat-section-wrapper').classList.toggle('mobile-friendly'); } @@ -1058,7 +1062,7 @@ id="collapse-side-panel-button" onclick="handleCollapseSidePanel()" > - + @@ -1161,6 +1165,7 @@ } #side-panel { + width: 250px; padding: 10px; background: var(--background-color); border-radius: 5px; @@ -1168,11 +1173,11 @@ overflow-y: scroll; text-align: left; transition: width 0.3s ease-in-out; - width: 250px; } div#side-panel.collapsed { - width: 1px; + width: 0; + padding: 0; display: block; overflow: hidden; } @@ -1227,6 +1232,7 @@ display: inline-block; max-width: 80%; text-align: left; + white-space: pre-line; } /* color chat bubble by khoj blue */ .chat-message-text.khoj { @@ -1234,6 +1240,15 @@ background: var(--primary); margin-left: auto; } + .chat-message-text ol, + .chat-message-text ul { + white-space: normal; + margin: 0; + } + .chat-message-text-response { + margin-bottom: -16px; + } + /* Spinner symbol when the chat message is loading */ .spinner { border: 4px solid #f3f3f3; @@ -1350,7 +1365,7 @@ font-size: large; } - svg.side-panel-collapse { + svg#side-panel-collapse { width: 30px; height: 30px; } @@ -1604,7 +1619,7 @@ padding: 0; } - svg.side-panel-collapse { + svg#side-panel-collapse { width: 24px; height: 24px; } diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index cb8cef89..250e1b65 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -219,7 +219,7 @@ def subscription_to_state(subscription: Subscription) -> str: return SubscriptionState.INVALID.value elif subscription.type == Subscription.Type.TRIAL: # Trial subscription is valid for 7 days - if datetime.now(tz=timezone.utc) - subscription.created_at > timedelta(days=7): + if datetime.now(tz=timezone.utc) - subscription.created_at > timedelta(days=14): return SubscriptionState.EXPIRED.value return SubscriptionState.TRIAL.value @@ -573,7 +573,7 @@ class ConversationAdapters: return await SpeechToTextModelOptions.objects.filter().afirst() @staticmethod - async def aget_conversation_starters(user: KhojUser): + async def aget_conversation_starters(user: KhojUser, max_results=3): all_questions = [] if await ReflectiveQuestion.objects.filter(user=user).aexists(): all_questions = await sync_to_async(ReflectiveQuestion.objects.filter(user=user).values_list)( @@ -584,7 +584,6 @@ class ConversationAdapters: "question", flat=True ) - max_results = 3 all_questions = await sync_to_async(list)(all_questions) # type: ignore if len(all_questions) < max_results: return all_questions diff --git a/src/khoj/interface/web/assets/icons/favicon-256x256.png b/src/khoj/interface/web/assets/icons/favicon-256x256.png new file mode 100644 index 00000000..a164e15e Binary files /dev/null and b/src/khoj/interface/web/assets/icons/favicon-256x256.png differ diff --git a/src/khoj/interface/web/assets/khoj.css b/src/khoj/interface/web/assets/khoj.css index 0aa11d78..7ba93c6a 100644 --- a/src/khoj/interface/web/assets/khoj.css +++ b/src/khoj/interface/web/assets/khoj.css @@ -1,6 +1,6 @@ /* Amber Light scheme (Default) */ /* Can be forced with data-theme="light" */ -@import url('https://fonts.googleapis.com/css2?family=Tajawal&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;500;700&display=swap'); [data-theme="light"], :root:not([data-theme="dark"]) { @@ -9,6 +9,7 @@ --primary-focus: rgba(255, 179, 0, 0.125); --primary-inverse: rgba(0, 0, 0, 0.75); --background-color: #f5f4f3; + --frosted-background-color: rgba(245, 244, 243, 0.75); --main-text-color: #475569; --summer-sun: #fcc50b; --water: #44b9da; @@ -26,6 +27,7 @@ --primary-focus: rgba(255, 179, 0, 0.25); --primary-inverse: rgba(0, 0, 0, 0.75); --background-color: #f5f4f3; + --frosted-background-color: rgba(245, 244, 243, 0.75); --main-text-color: #475569; --summer-sun: #fcc50b; --water: #44b9da; @@ -42,6 +44,7 @@ --primary-focus: rgba(255, 179, 0, 0.25); --primary-inverse: rgba(0, 0, 0, 0.75); --background-color: #f5f4f3; + --frosted-background-color: rgba(245, 244, 243, 0.75); --main-text-color: #475569; --summer-sun: #fcc50b; --water: #44b9da; diff --git a/src/khoj/interface/web/assets/samples/desktop-browse-draw-sample.png b/src/khoj/interface/web/assets/samples/desktop-browse-draw-sample.png new file mode 100644 index 00000000..d38bf7e3 Binary files /dev/null and b/src/khoj/interface/web/assets/samples/desktop-browse-draw-sample.png differ diff --git a/src/khoj/interface/web/assets/samples/desktop-plain-chat-sample.png b/src/khoj/interface/web/assets/samples/desktop-plain-chat-sample.png new file mode 100644 index 00000000..2520014f Binary files /dev/null and b/src/khoj/interface/web/assets/samples/desktop-plain-chat-sample.png differ diff --git a/src/khoj/interface/web/assets/samples/desktop-remember-plan-sample.png b/src/khoj/interface/web/assets/samples/desktop-remember-plan-sample.png new file mode 100644 index 00000000..acb5904c Binary files /dev/null and b/src/khoj/interface/web/assets/samples/desktop-remember-plan-sample.png differ diff --git a/src/khoj/interface/web/assets/samples/phone-browse-draw-sample.png b/src/khoj/interface/web/assets/samples/phone-browse-draw-sample.png new file mode 100644 index 00000000..a11369f9 Binary files /dev/null and b/src/khoj/interface/web/assets/samples/phone-browse-draw-sample.png differ diff --git a/src/khoj/interface/web/assets/samples/phone-plain-chat-sample.png b/src/khoj/interface/web/assets/samples/phone-plain-chat-sample.png new file mode 100644 index 00000000..84b1b9d1 Binary files /dev/null and b/src/khoj/interface/web/assets/samples/phone-plain-chat-sample.png differ diff --git a/src/khoj/interface/web/assets/samples/phone-remember-plan-sample.png b/src/khoj/interface/web/assets/samples/phone-remember-plan-sample.png new file mode 100644 index 00000000..357c854a Binary files /dev/null and b/src/khoj/interface/web/assets/samples/phone-remember-plan-sample.png differ diff --git a/src/khoj/interface/web/chat.html b/src/khoj/interface/web/chat.html index 91751002..8b0635a5 100644 --- a/src/khoj/interface/web/chat.html +++ b/src/khoj/interface/web/chat.html @@ -5,7 +5,7 @@ Khoj - Chat