diff --git a/manifest.json b/manifest.json index e4fab58e..979c67c7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "khoj", "name": "Khoj", - "version": "1.26.0", + "version": "1.26.4", "minAppVersion": "0.15.0", "description": "Your Second Brain", "author": "Khoj Inc.", diff --git a/pyproject.toml b/pyproject.toml index 960d8d72..93df0b42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ dependencies = [ "requests >= 2.26.0", "tenacity == 8.3.0", "anyio == 3.7.1", - "pymupdf >= 1.23.5", + "pymupdf == 1.24.11", "django == 5.0.9", "authlib == 1.2.1", "llama-cpp-python == 0.2.88", diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html index a6ae9a15..4c2258cc 100644 --- a/src/interface/desktop/chat.html +++ b/src/interface/desktop/chat.html @@ -326,7 +326,7 @@ entries.forEach(entry => { // If the element is in the viewport, fetch the remaining message and unobserve the element if (entry.isIntersecting) { - fetchRemainingChatMessages(chatHistoryUrl, headers); + fetchRemainingChatMessages(chatHistoryUrl, headers, chatBody.dataset.conversation_id, hostURL); observer.unobserve(entry.target); } }); @@ -342,7 +342,11 @@ new Date(chat_log.created), chat_log.onlineContext, chat_log.intent?.type, - chat_log.intent?.["inferred-queries"]); + chat_log.intent?.["inferred-queries"], + chatBody.dataset.conversationId ?? "", + hostURL, + ); + chatBody.appendChild(messageElement); // When the 4th oldest message is within viewing distance (~60% scrolled up) @@ -421,7 +425,7 @@ } } - function fetchRemainingChatMessages(chatHistoryUrl, headers) { + function fetchRemainingChatMessages(chatHistoryUrl, headers, conversationId, hostURL) { // Create a new IntersectionObserver let observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { @@ -435,7 +439,9 @@ new Date(chat_log.created), chat_log.onlineContext, chat_log.intent?.type, - chat_log.intent?.["inferred-queries"] + chat_log.intent?.["inferred-queries"], + chatBody.dataset.conversationId ?? "", + hostURL, ); entry.target.replaceWith(messageElement); diff --git a/src/interface/desktop/chatutils.js b/src/interface/desktop/chatutils.js index 5213979f..48fb72c3 100644 --- a/src/interface/desktop/chatutils.js +++ b/src/interface/desktop/chatutils.js @@ -189,11 +189,19 @@ function processOnlineReferences(referenceSection, onlineContext) { //same return numOnlineReferences; } -function renderMessageWithReference(message, by, context=null, dt=null, onlineContext=null, intentType=null, inferredQueries=null) { //same +function renderMessageWithReference(message, by, context=null, dt=null, onlineContext=null, intentType=null, inferredQueries=null, conversationId=null, hostURL=null) { let chatEl; if (intentType?.includes("text-to-image")) { let imageMarkdown = generateImageMarkdown(message, intentType, inferredQueries); chatEl = renderMessage(imageMarkdown, by, dt, null, false, "return"); + } else if (intentType === "excalidraw") { + let domain = hostURL ?? "https://app.khoj.dev/"; + + if (!domain.endsWith("/")) domain += "/"; + + let excalidrawMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in the web app at ${domain}chat?conversationId=${conversationId}`; + + chatEl = renderMessage(excalidrawMessage, by, dt, null, false, "return"); } else { chatEl = renderMessage(message, by, dt, null, false, "return"); } @@ -312,7 +320,6 @@ function formatHTMLMessage(message, raw=false, willReplace=true) { //same } function createReferenceSection(references, createLinkerSection=false) { - console.log("linker data: ", createLinkerSection); let referenceSection = document.createElement('div'); referenceSection.classList.add("reference-section"); referenceSection.classList.add("collapsed"); @@ -417,7 +424,11 @@ function handleImageResponse(imageJson, rawResponse) { rawResponse += `![generated_image](${imageJson.image})`; } else if (imageJson.intentType === "text-to-image-v3") { rawResponse = `![](data:image/webp;base64,${imageJson.image})`; + } else if (imageJson.intentType === "excalidraw") { + const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in the web app`; + rawResponse += redirectMessage; } + if (inferredQuery) { rawResponse += `\n\n**Inferred Query**:\n\n${inferredQuery}`; } diff --git a/src/interface/desktop/package.json b/src/interface/desktop/package.json index 87904130..bbae8d2b 100644 --- a/src/interface/desktop/package.json +++ b/src/interface/desktop/package.json @@ -1,6 +1,6 @@ { "name": "Khoj", - "version": "1.26.0", + "version": "1.26.4", "description": "Your Second Brain", "author": "Khoj Inc. ", "license": "GPL-3.0-or-later", diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 02d1dd6f..cad27196 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -6,7 +6,7 @@ ;; Saba Imran ;; Description: Your Second Brain ;; Keywords: search, chat, ai, org-mode, outlines, markdown, pdf, image -;; Version: 1.26.0 +;; Version: 1.26.4 ;; Package-Requires: ((emacs "27.1") (transient "0.3.0") (dash "2.19.1")) ;; URL: https://github.com/khoj-ai/khoj/tree/master/src/interface/emacs @@ -127,6 +127,11 @@ (const "image") (const "pdf"))) +(defcustom khoj-default-agent "khoj" + "The default agent to chat with. See https://app.khoj.dev/agents for available options." + :group 'khoj + :type 'string) + ;; -------------------------- ;; Khoj Dynamic Configuration @@ -144,6 +149,9 @@ (defconst khoj--chat-buffer-name "*🏮 Khoj Chat*" "Name of chat buffer for Khoj.") +(defvar khoj--selected-agent khoj-default-agent + "Currently selected Khoj agent.") + (defvar khoj--content-type "org" "The type of content to perform search on.") @@ -656,13 +664,15 @@ Simplified fork of `org-cycle-content' from Emacs 29.1 to work with >=27.1." ;; -------------- ;; Query Khoj API ;; -------------- -(defun khoj--call-api (path &optional method params callback &rest cbargs) - "Sync call API at PATH with METHOD and query PARAMS as kv assoc list. +(defun khoj--call-api (path &optional method params body callback &rest cbargs) + "Sync call API at PATH with METHOD, query PARAMS and BODY as kv assoc list. Optionally apply CALLBACK with JSON parsed response and CBARGS." (let* ((url-request-method (or method "GET")) (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)))) - (param-string (if params (url-build-query-string params) "")) - (query-url (format "%s%s?%s&client=emacs" khoj-server-url path param-string)) + (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)) ("Content-Type" . "application/json"))) + (url-request-data (if body (json-encode body) nil)) + (param-string (url-build-query-string (append params '((client "emacs"))))) + (query-url (format "%s%s?%s" khoj-server-url path param-string)) (cbargs (if (and (listp cbargs) (listp (car cbargs))) (car cbargs) cbargs))) ; normalize cbargs to (a b) from ((a b)) if required (with-temp-buffer (condition-case ex @@ -682,8 +692,8 @@ Optionally apply CALLBACK with JSON parsed response and CBARGS." (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)) ("Content-Type" . "application/json"))) (url-request-data (if body (json-encode body) nil)) (param-string (url-build-query-string (append params '((client "emacs"))))) - (cbargs (if (and (listp cbargs) (listp (car cbargs))) (car cbargs) cbargs)) ; normalize cbargs to (a b) from ((a b)) if required - (query-url (format "%s%s?%s" khoj-server-url path param-string))) + (query-url (format "%s%s?%s" khoj-server-url path param-string)) + (cbargs (if (and (listp cbargs) (listp (car cbargs))) (car cbargs) cbargs))) ; normalize cbargs to (a b) from ((a b)) if required (url-retrieve query-url (lambda (status) (if (plist-get status :error) @@ -699,7 +709,7 @@ Optionally apply CALLBACK with JSON parsed response and CBARGS." (defun khoj--get-enabled-content-types () "Get content types enabled for search from API." - (khoj--call-api "/api/content/types" "GET" nil `(lambda (item) (mapcar #'intern item)))) + (khoj--call-api "/api/content/types" "GET" nil nil `(lambda (item) (mapcar #'intern item)))) (defun khoj--query-search-api-and-render-results (query content-type buffer-name &optional rerank is-find-similar) "Query Khoj Search API with QUERY, CONTENT-TYPE and RERANK as query params. @@ -913,14 +923,16 @@ Call CALLBACK func with response and CBARGS." (let ((selected-session-id (khoj--select-conversation-session "Open"))) (khoj--load-chat-session khoj--chat-buffer-name selected-session-id))) -(defun khoj--create-chat-session () - "Create new chat session." - (khoj--call-api "/api/chat/sessions" "POST")) +(defun khoj--create-chat-session (&optional agent) + "Create new chat session with AGENT." + (khoj--call-api "/api/chat/sessions" + "POST" + (when agent `(("agent_slug" ,agent))))) -(defun khoj--new-conversation-session () - "Create new Khoj conversation session." +(defun khoj--new-conversation-session (&optional agent) + "Create new Khoj conversation session with AGENT." (thread-last - (khoj--create-chat-session) + (khoj--create-chat-session agent) (assoc 'conversation_id) (cdr) (khoj--chat))) @@ -935,6 +947,15 @@ Call CALLBACK func with response and CBARGS." (khoj--select-conversation-session "Delete") (khoj--delete-chat-session))) +(defun khoj--get-agents () + "Get list of available Khoj agents." + (let* ((response (khoj--call-api "/api/agents" "GET")) + (agents (mapcar (lambda (agent) + (cons (cdr (assoc 'name agent)) + (cdr (assoc 'slug agent)))) + response))) + agents)) + (defun khoj--render-chat-message (message sender &optional receive-date) "Render chat messages as `org-mode' list item. MESSAGE is the text of the chat message. @@ -1246,6 +1267,20 @@ Paragraph only starts at first text after blank line." ;; dynamically set choices to content types enabled on khoj backend :choices (or (ignore-errors (mapcar #'symbol-name (khoj--get-enabled-content-types))) '("all" "org" "markdown" "pdf" "image"))) + (transient-define-argument khoj--agent-switch () + :class 'transient-switches + :argument-format "--agent=%s" + :argument-regexp ".+" + :init-value (lambda (obj) + (oset obj value (format "--agent=%s" khoj--selected-agent))) + :choices (or (ignore-errors (mapcar #'cdr (khoj--get-agents))) '("khoj")) + :reader (lambda (prompt initial-input history) + (let* ((agents (khoj--get-agents)) + (selected (completing-read prompt agents nil t initial-input history)) + (slug (cdr (assoc selected agents)))) + (setq khoj--selected-agent slug) + slug))) + (transient-define-suffix khoj--search-command (&optional args) (interactive (list (transient-args transient-current-command))) (progn @@ -1287,10 +1322,11 @@ Paragraph only starts at first text after blank line." (interactive (list (transient-args transient-current-command))) (khoj--open-conversation-session)) - (transient-define-suffix khoj--new-conversation-session-command (&optional _) + (transient-define-suffix khoj--new-conversation-session-command (&optional args) "Command to select Khoj conversation sessions to open." (interactive (list (transient-args transient-current-command))) - (khoj--new-conversation-session)) + (let ((agent-slug (transient-arg-value "--agent=" args))) + (khoj--new-conversation-session agent-slug))) (transient-define-suffix khoj--delete-conversation-session-command (&optional _) "Command to select Khoj conversation sessions to delete." @@ -1298,14 +1334,15 @@ Paragraph only starts at first text after blank line." (khoj--delete-conversation-session)) (transient-define-prefix khoj--chat-menu () - "Open the Khoj chat menu." - ["Act" - ("c" "Chat" khoj--chat-command) - ("o" "Open Conversation" khoj--open-conversation-session-command) - ("n" "New Conversation" khoj--new-conversation-session-command) - ("d" "Delete Conversation" khoj--delete-conversation-session-command) - ("q" "Quit" transient-quit-one) - ]) + "Create the Khoj Chat Menu and Execute Commands." + [["Configure" + ("a" "Select Agent" khoj--agent-switch)]] + [["Act" + ("c" "Chat" khoj--chat-command) + ("o" "Open Conversation" khoj--open-conversation-session-command) + ("n" "New Conversation" khoj--new-conversation-session-command) + ("d" "Delete Conversation" khoj--delete-conversation-session-command) + ("q" "Quit" transient-quit-one)]]) (transient-define-prefix khoj--menu () "Create Khoj Menu to Configure and Execute Commands." diff --git a/src/interface/obsidian/manifest.json b/src/interface/obsidian/manifest.json index e4fab58e..979c67c7 100644 --- a/src/interface/obsidian/manifest.json +++ b/src/interface/obsidian/manifest.json @@ -1,7 +1,7 @@ { "id": "khoj", "name": "Khoj", - "version": "1.26.0", + "version": "1.26.4", "minAppVersion": "0.15.0", "description": "Your Second Brain", "author": "Khoj Inc.", diff --git a/src/interface/obsidian/package.json b/src/interface/obsidian/package.json index 42ee8c26..46b74922 100644 --- a/src/interface/obsidian/package.json +++ b/src/interface/obsidian/package.json @@ -1,6 +1,6 @@ { "name": "Khoj", - "version": "1.26.0", + "version": "1.26.4", "description": "Your Second Brain", "author": "Debanjum Singh Solanky, Saba Imran ", "license": "GPL-3.0-or-later", diff --git a/src/interface/obsidian/src/chat_view.ts b/src/interface/obsidian/src/chat_view.ts index ed23bff0..408ce3a1 100644 --- a/src/interface/obsidian/src/chat_view.ts +++ b/src/interface/obsidian/src/chat_view.ts @@ -484,12 +484,13 @@ export class KhojChatView extends KhojPaneView { dt?: Date, intentType?: string, inferredQueries?: string[], + conversationId?: string, ) { if (!message) return; let chatMessageEl; - if (intentType?.includes("text-to-image")) { - let imageMarkdown = this.generateImageMarkdown(message, intentType, inferredQueries); + if (intentType?.includes("text-to-image") || intentType === "excalidraw") { + let imageMarkdown = this.generateImageMarkdown(message, intentType, inferredQueries, conversationId); chatMessageEl = this.renderMessage(chatEl, imageMarkdown, sender, dt); } else { chatMessageEl = this.renderMessage(chatEl, message, sender, dt); @@ -509,7 +510,7 @@ export class KhojChatView extends KhojPaneView { chatMessageBodyEl.appendChild(this.createReferenceSection(references)); } - generateImageMarkdown(message: string, intentType: string, inferredQueries?: string[]) { + generateImageMarkdown(message: string, intentType: string, inferredQueries?: string[], conversationId?: string): string { let imageMarkdown = ""; if (intentType === "text-to-image") { imageMarkdown = `![](data:image/png;base64,${message})`; @@ -517,6 +518,10 @@ export class KhojChatView extends KhojPaneView { imageMarkdown = `![](${message})`; } else if (intentType === "text-to-image-v3") { imageMarkdown = `![](data:image/webp;base64,${message})`; + } else if (intentType === "excalidraw") { + const domain = this.setting.khojUrl.endsWith("/") ? this.setting.khojUrl : `${this.setting.khojUrl}/`; + const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in ${domain}chat?conversationId=${conversationId}`; + imageMarkdown = redirectMessage; } if (inferredQueries) { imageMarkdown += "\n\n**Inferred Query**:"; @@ -884,6 +889,7 @@ export class KhojChatView extends KhojPaneView { new Date(chatLog.created), chatLog.intent?.type, chatLog.intent?.["inferred-queries"], + chatBodyEl.dataset.conversationId ?? "", ); // push the user messages to the chat history if(chatLog.by === "you"){ @@ -1354,6 +1360,10 @@ export class KhojChatView extends KhojPaneView { rawResponse += `![generated_image](${imageJson.image})`; } else if (imageJson.intentType === "text-to-image-v3") { rawResponse = `![](data:image/webp;base64,${imageJson.image})`; + } else if (imageJson.intentType === "excalidraw") { + const domain = this.setting.khojUrl.endsWith("/") ? this.setting.khojUrl : `${this.setting.khojUrl}/`; + const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in ${domain}`; + rawResponse += redirectMessage; } if (inferredQuery) { rawResponse += `\n\n**Inferred Query**:\n\n${inferredQuery}`; diff --git a/src/interface/obsidian/versions.json b/src/interface/obsidian/versions.json index 8c9fd580..4ed50e51 100644 --- a/src/interface/obsidian/versions.json +++ b/src/interface/obsidian/versions.json @@ -78,5 +78,9 @@ "1.24.0": "0.15.0", "1.24.1": "0.15.0", "1.25.0": "0.15.0", - "1.26.0": "0.15.0" + "1.26.0": "0.15.0", + "1.26.1": "0.15.0", + "1.26.2": "0.15.0", + "1.26.3": "0.15.0", + "1.26.4": "0.15.0" } diff --git a/src/interface/web/app/chat/chat.module.css b/src/interface/web/app/chat/chat.module.css index 942087e6..69be25b4 100644 --- a/src/interface/web/app/chat/chat.module.css +++ b/src/interface/web/app/chat/chat.module.css @@ -79,7 +79,7 @@ div.titleBar { div.chatBoxBody { display: grid; height: 100%; - width: 70%; + width: 95%; margin: auto; } diff --git a/src/interface/web/app/chat/layout.tsx b/src/interface/web/app/chat/layout.tsx index 6688cfc8..09c3afb7 100644 --- a/src/interface/web/app/chat/layout.tsx +++ b/src/interface/web/app/chat/layout.tsx @@ -47,7 +47,14 @@ export default function RootLayout({ child-src 'none'; object-src 'none';" > - {children} + + {children} +