From cd4baa3fa5d0b933ef9d72056e2e2e1ce5c83383 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sun, 9 Jun 2024 16:49:19 +0530 Subject: [PATCH 01/14] Fix loading chat history, references in khoj.el chat buffer --- src/interface/emacs/khoj.el | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 78485925..a001ccdd 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -702,7 +702,8 @@ Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." (defun khoj--load-chat-history (buffer-name) "Load Khoj Chat conversation history into BUFFER-NAME." - (let ((json-response (cdr (assoc 'response (khoj--get-chat-history-api))))) + (setq khoj--reference-count 0) + (let ((json-response (cdr (elt (cdr (assoc 'response (khoj--get-chat-history-api))) 0)))) (with-current-buffer (get-buffer-create buffer-name) (erase-buffer) (insert "* Khoj Chat\n") @@ -829,16 +830,17 @@ RECEIVE-DATE is the message receive date." (defun khoj--generate-reference (reference) "Create `org-mode' footnotes with REFERENCE." (setq khoj--reference-count (1+ khoj--reference-count)) + (let ((compiled-reference (cdr (assoc 'compiled reference)))) (cons - (propertize (format "^{ [fn:%x]}" khoj--reference-count) 'help-echo reference) + (propertize (format "^{ [fn:%x]}" khoj--reference-count) 'help-echo compiled-reference) (thread-last - reference + compiled-reference ;; remove filename top heading line from reference ;; prevents actual reference heading in next line jumping out of references footnote section (replace-regexp-in-string "^\* .*\n" "") ;; remove multiple, consecutive empty lines from reference (replace-regexp-in-string "\n\n" "\n") - (format "\n[fn:%x] %s" khoj--reference-count)))) + (format "\n[fn:%x] %s" khoj--reference-count))))) (defun khoj--render-chat-response (json-response) "Render chat message using JSON-RESPONSE from Khoj Chat API." From 906ebee075e41624314f5227da3766b55b0f2865 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sun, 9 Jun 2024 18:34:03 +0530 Subject: [PATCH 02/14] Open Khoj chat, search in right pane to allow for ambient engagement See the currently active window in context while doing chat, search or find similar operations in a side pane. This is similar to how we've moved Khoj on Obsidian into the side pane as well --- src/interface/emacs/khoj.el | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index a001ccdd..7e77e151 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -683,6 +683,10 @@ Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." ((equal content-type "image") (progn (shr-render-region (point-min) (point-max)) (goto-char (point-min)))) (t (fundamental-mode)))) + ;; keep cursor at top of khoj buffer by default + (goto-char (point-min)) + ;; enable minor modes for khoj chat + (visual-line-mode) (read-only-mode t))) @@ -695,11 +699,29 @@ Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." (interactive) (when (not (get-buffer khoj--chat-buffer-name)) (khoj--load-chat-history khoj--chat-buffer-name)) - (switch-to-buffer khoj--chat-buffer-name) + (khoj--open-side-pane khoj--chat-buffer-name) (let ((query (read-string "Query: "))) (when (not (string-empty-p query)) (khoj--query-chat-api-and-render-messages query khoj--chat-buffer-name)))) +(defun khoj--open-side-pane (buffer-name) + "Open Khoj BUFFER-NAME in right side pane." + (if (get-buffer-window-list buffer-name) + ; if window is already open, switch to it + (progn + (select-window (get-buffer-window buffer-name)) + (switch-to-buffer buffer-name)) + ;; else if window is not open, open it as a right-side window pane + (progn + ;; Select the right-most window + (select-window (some-window (lambda (window) (window-at-side-p window 'right)))) + ;; Split the window to the right and resize it to take up 1/3 of the frame width + (let ((new-window (split-window-right))) + (set-window-buffer new-window buffer-name) + (window-resize new-window + (- (truncate (* 0.33 (frame-width))) (window-width)) + t))))) + (defun khoj--load-chat-history (buffer-name) "Load Khoj Chat conversation history into BUFFER-NAME." (setq khoj--reference-count 0) @@ -925,8 +947,8 @@ RECEIVE-DATE is the message receive date." "Natural, Incremental Search for your personal notes and documents." (interactive) (let* ((khoj-buffer-name (get-buffer-create khoj--search-buffer-name))) - ;; switch to khoj results buffer - (switch-to-buffer khoj-buffer-name) + ;; switch to khoj search buffer + (khoj--open-side-pane khoj-buffer-name) ;; open and setup minibuffer for incremental search (minibuffer-with-setup-hook (lambda () @@ -1003,7 +1025,7 @@ Paragraph only starts at first text after blank line." content-type query-title buffer-name) - (switch-to-buffer buffer-name) + (khoj--open-side-pane buffer-name) (goto-char (point-min))))) From 385057f09eb74b59d78dc342d4d43e943252a2ed Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Mon, 10 Jun 2024 08:02:33 +0530 Subject: [PATCH 03/14] Make khoj.el chat API call async to not block user interactions --- src/interface/emacs/khoj.el | 85 +++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 7e77e151..db5fd147 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -732,7 +732,7 @@ Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." (thread-last json-response ;; generate chat messages from Khoj Chat API response - (mapcar #'khoj--render-chat-response) + (mapcar #'khoj--format-chat-response) ;; insert chat messages into Khoj Chat Buffer (mapc #'insert)) (progn @@ -786,33 +786,29 @@ Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." ;; render json response into formatted chat messages (with-current-buffer (get-buffer buffer-name) (let ((inhibit-read-only t) - (new-content-start-pos (point-max)) - (query-time (format-time-string "%F %T")) - (json-response (khoj--query-chat-api query))) - (goto-char new-content-start-pos) + (query-time (format-time-string "%F %T"))) + (goto-char (point-max)) (insert - (khoj--render-chat-message query "you" query-time) - (khoj--render-chat-response json-response)) - (khoj--add-hover-text-to-footnote-refs new-content-start-pos)) - (progn - (org-set-startup-visibility) - (visual-line-mode) - (re-search-backward "^\*+ 🏮" nil t)))) + (khoj--render-chat-message query "you" query-time)) + (khoj--query-chat-api query + #'khoj--format-chat-response + #'khoj--render-chat-response buffer-name)))) -(defun khoj--query-chat-api (query) - "Send QUERY to Khoj Chat API." +(defun khoj--query-chat-api (query callback &rest cbargs) + "Send QUERY to Khoj Chat API and call CALLBACK with the response. +CBARGS are optional additional arguments to pass to CALLBACK." (let* ((url-request-method "GET") (encoded-query (url-hexify-string query)) (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)))) (query-url (format "%s/api/chat?q=%s&n=%s&client=emacs" khoj-server-url encoded-query khoj-results-count))) - (with-temp-buffer - (condition-case ex - (progn - (url-insert-file-contents query-url) - (json-parse-buffer :object-type 'alist)) - ('file-error (cond ((string-match "Internal server error" (nth 2 ex)) - (message "Chat processor not configured. Configure OpenAI API key and restart it. Exception: [%s]" ex)) - (t (message "Chat exception: [%s]" ex)))))))) + (url-retrieve query-url + (lambda (status) + (if (plist-get status :error) + (message "Chat exception: [%s]" (plist-get status :error)) + (goto-char (point-min)) + (re-search-forward "^$") + (delete-region (point) (point-min)) + (apply callback (json-parse-buffer :object-type 'alist) cbargs)))))) (defun khoj--get-chat-history-api () @@ -864,7 +860,20 @@ RECEIVE-DATE is the message receive date." (replace-regexp-in-string "\n\n" "\n") (format "\n[fn:%x] %s" khoj--reference-count))))) -(defun khoj--render-chat-response (json-response) +(defun khoj--render-chat-response (response buffer-name) + (with-current-buffer (get-buffer buffer-name) + (let ((start-pos (point)) + (inhibit-read-only t)) + (goto-char (point-max)) + (insert + response + (or (khoj--add-hover-text-to-footnote-refs start-pos) "")) + (progn + (org-set-startup-visibility) + (visual-line-mode) + (re-search-backward "^\*+ 🏮" nil t))))) + +(defun khoj--format-chat-response (json-response &optional callback &rest cbargs) "Render chat message using JSON-RESPONSE from Khoj Chat API." (let* ((message (cdr (or (assoc 'response json-response) (assoc 'message json-response)))) (sender (cdr (assoc 'by json-response))) @@ -872,19 +881,23 @@ RECEIVE-DATE is the message receive date." (references (or (cdr (assoc 'context json-response)) '())) (footnotes (mapcar #'khoj--generate-reference references)) (footnote-links (mapcar #'car footnotes)) - (footnote-defs (mapcar #'cdr footnotes))) - (thread-first - ;; concatenate khoj message and references from API - (concat - message - ;; append reference links to khoj message - (string-join footnote-links "") - ;; append reference sub-section to khoj message and fold it - (if footnote-defs "\n**** References\n:PROPERTIES:\n:VISIBILITY: folded\n:END:" "") - ;; append reference definitions to references subsection - (string-join footnote-defs " ")) - ;; Render chat message using data obtained from API - (khoj--render-chat-message sender receive-date)))) + (footnote-defs (mapcar #'cdr footnotes)) + (formatted-response + (thread-first + ;; concatenate khoj message and references from API + (concat + message + ;; append reference links to khoj message + (string-join footnote-links "") + ;; append reference sub-section to khoj message and fold it + (if footnote-defs "\n**** References\n:PROPERTIES:\n:VISIBILITY: folded\n:END:" "") + ;; append reference definitions to references subsection + (string-join footnote-defs " ")) + ;; Render chat message using data obtained from API + (khoj--render-chat-message sender receive-date)))) + (if callback + (apply callback formatted-response cbargs) + formatted-response))) ;; ------------------ From df9c5ff2639578c64cdc3e9cba10b534c3749531 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Tue, 11 Jun 2024 11:11:07 +0530 Subject: [PATCH 04/14] Show online references used for chat response as footnotes in Emacs Previously online references used weren't being shown --- src/interface/emacs/khoj.el | 71 ++++++++++++++++++----- src/interface/emacs/tests/khoj-tests.el | 77 ++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 16 deletions(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index db5fd147..639aade4 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -849,16 +849,55 @@ RECEIVE-DATE is the message receive date." "Create `org-mode' footnotes with REFERENCE." (setq khoj--reference-count (1+ khoj--reference-count)) (let ((compiled-reference (cdr (assoc 'compiled reference)))) - (cons - (propertize (format "^{ [fn:%x]}" khoj--reference-count) 'help-echo compiled-reference) - (thread-last - compiled-reference - ;; remove filename top heading line from reference - ;; prevents actual reference heading in next line jumping out of references footnote section - (replace-regexp-in-string "^\* .*\n" "") - ;; remove multiple, consecutive empty lines from reference - (replace-regexp-in-string "\n\n" "\n") - (format "\n[fn:%x] %s" khoj--reference-count))))) + (cons + (propertize (format "^{ [fn:%x]}" khoj--reference-count) 'help-echo compiled-reference) + (thread-last + compiled-reference + ;; remove filename top heading line from reference + ;; prevents actual reference heading in next line jumping out of references footnote section + (replace-regexp-in-string "^\* .*\n" "") + ;; remove multiple, consecutive empty lines from reference + (replace-regexp-in-string "\n\n" "\n") + (format "\n[fn:%x] %s" khoj--reference-count))))) + +(defun khoj--generate-online-reference (reference) + (setq khoj--reference-count (1+ khoj--reference-count)) + (let ((link (cdr (assoc 'link reference))) + (title (cdr (assoc 'title reference))) + (description (cdr (assoc 'description reference)))) + (cons + (propertize (format "^{ [fn:%x]}" khoj--reference-count) 'help-echo (format "%s\n%s" link description)) + (thread-last + description + ;; remove multiple, consecutive empty lines from reference + (replace-regexp-in-string "\n\n" "\n") + (format "\n[fn:%x] [[%s][%s]]\n%s\n" khoj--reference-count link title))))) + +(defun khoj--extract-online-references (result-types searches) + "Extract link, title, and description of specified RESULT-TYPES from SEARCHES." + (let ((result '())) + (-map + (lambda (search) + (let ((search-q (car search)) + (search-results (cdr search))) + (-map-when + ;; filter search results by specified result types + (lambda (search-result) (member (car search-result) result-types)) + ;; extract link, title, and description from search results + (lambda (search-result) + (-map + (lambda (entry) + (let ((link (cdr (or (assoc 'link entry) (assoc 'descriptionLink entry)))) + (title (cdr (or (assoc 'title entry) '(title . ,link)))) + (description (cdr (or (assoc 'snippet entry) (assoc 'description entry))))) + (setq result (append result `(((title . ,title) (link . ,link) (description . ,description) (search . ,search-q))))))) + ;; wrap search results in a list if it is not already a list + (if (or (equal 'knowledgeGraph (car search-result)) (equal 'webpages (car search-result))) + (list (cdr search-result)) + (cdr search-result)))) + search-results))) + searches) + result)) (defun khoj--render-chat-response (response buffer-name) (with-current-buffer (get-buffer buffer-name) @@ -878,10 +917,14 @@ RECEIVE-DATE is the message receive date." (let* ((message (cdr (or (assoc 'response json-response) (assoc 'message json-response)))) (sender (cdr (assoc 'by json-response))) (receive-date (cdr (assoc 'created json-response))) - (references (or (cdr (assoc 'context json-response)) '())) - (footnotes (mapcar #'khoj--generate-reference references)) - (footnote-links (mapcar #'car footnotes)) - (footnote-defs (mapcar #'cdr footnotes)) + (online-references (or (cdr (assoc 'onlineContext json-response)) '())) + (online-footnotes (-map #'khoj--generate-online-reference + (khoj--extract-online-references '(organic knowledgeGraph peopleAlsoAsk webpages) + online-references))) + (doc-references (or (cdr (assoc 'context json-response)) '())) + (doc-footnotes (mapcar #'khoj--generate-reference doc-references)) + (footnote-links (mapcar #'car (append doc-footnotes online-footnotes))) + (footnote-defs (mapcar #'cdr (append doc-footnotes online-footnotes))) (formatted-response (thread-first ;; concatenate khoj message and references from API diff --git a/src/interface/emacs/tests/khoj-tests.el b/src/interface/emacs/tests/khoj-tests.el index cd2a1f02..c1dfe90e 100644 --- a/src/interface/emacs/tests/khoj-tests.el +++ b/src/interface/emacs/tests/khoj-tests.el @@ -221,7 +221,7 @@ Rule everything\n") (equal (khoj--render-update-files-as-request-body (list upgrade-file act-file) "khoj") (format - "\n--khoj\r\n\ + "\n--khoj\r\n\ Content-Disposition: form-data; name=\"files\"; filename=\"%s\"\r\n\ Content-Type: text/org\r\n\r\n\ # Become God\n\ @@ -246,7 +246,7 @@ Rule everything\n\n\r\n\ (equal (khoj--render-delete-files-as-request-body (list upgrade-file act-file "/tmp/deleted-file.org") "khoj") (format - "\n--khoj\r\n\ + "\n--khoj\r\n\ Content-Disposition: form-data; name=\"files\"; filename=\"%s\"\r\n\ Content-Type: text/org\r\n\r\n\ \r @@ -262,6 +262,79 @@ Content-Type: text/org\r\n\r\n\ (delete-file upgrade-file) (delete-file act-file)))) +(ert-deftest khoj-tests--extract-online-references () + (let* (;; Arrange + (onlineContext '((Albert\ Einstein\ wife + (organic . [((link . "https://en.wikipedia.org/wiki/Mileva_Mari%C4%87") + (title . "Mileva Marić - Wikipedia") + (snippet . "Marić married Einstein in 1903 and they had three ...") + (position . 1) + (sitelinks . [((link . "https://en.wikipedia.org/wiki/Mileva_Mari%C4%87#Bi...") + (title . "Biography")) + ((link . "https://en.wikipedia.org/wiki/Mileva_Mari%C4%87#De...") + (title . "Debate over collaboration with..."))])) + ((link . "https://en.wikipedia.org/wiki/Elsa_Einstein") + (title . "Elsa Einstein - Wikipedia") + (snippet . "Elsa Einstein (18 January 1876 – 20 December 1936)...") + (position . 2)) + ((link . "https://www.amazon.com/Einsteins-Wife-Story-Mileva...") + (price . 25.86) + (title . "Einstein's Wife: The Real Story of Mileva Einstein...") + (rating . 3.8) + (snippet . "Albert Einstein's first wife, Mileva Einstein-Mari...") + (currency . "$") + (position . 3) + (ratingCount . 80))]) + (peopleAlsoAsk . [((link . "https://en.wikipedia.org/wiki/Mileva_Mari%C4%87") + (title . "Mileva Marić - Wikipedia") + (snippet . "Death. Mileva Marić suffered a severe stroke and d...") + (question . "What happened to Einstein's first wife?")) + ((link . "https://www.redalyc.org/journal/5117/511767145014/...") + (title . "The story of Mileva Marić: Did Einstein's first wi...") + (snippet . "The couple were married in 1903. Some claim that M...") + (question . "Did Albert Einstein's wife do all his work?")) + ((link . "https://en.wikipedia.org/wiki/Hans_Albert_Einstein") + (title . "Hans Albert Einstein - Wikipedia") + (snippet . "Klaus Martin Einstein (1932–1939), died of diphthe...") + (question . "What happened to Einstein's children?"))]) + (knowledgeGraph (type . "Theoretical physicist") + (title . "Albert Einstein") + (attributes (Born . "March 14, 1879, Ulm, Germany") + (Died . "April 18, 1955 (age 76 years), Princeton, NJ") + (Spouse . "Elsa Einstein (m. 1919–1936) and Mileva Marić (m. ...") + (Children . "Eduard Einstein, Hans Albert Einstein, and Lieserl...") + (Education . "University of Zurich (1905), ETH Zürich (1897–1900...") + (Nationality . "American, German, Hungarian, and more") + (Grandchildren . "Evelyn Einstein, Bernhard Caesar Einstein, Klaus M...")) + (description . "Albert Einstein was a German-born theoretical phys...") + (descriptionLink . "https://en.wikipedia.org/wiki/Albert_Einstein") + (descriptionSource . "Wikipedia"))) + (Prince\ Albert\ spouse + (webpages (link . "https://en.wikipedia.org/wiki/Prince_Albert_of_Sax...") (snippet . "Prince Albert of Saxe-Coburg and Gotha was the hus...")) + (organic . [((date . "Feb 4, 2024") + (link . "https://en.wikipedia.org/wiki/Prince_Albert_of_Sax...") + (title . "Prince Albert of Saxe-Coburg and Gotha - Wikipedia") + (snippet . "Prince Albert of Saxe-Coburg and Gotha was the hus...") + (position . 1) + (sitelinks . [((link . "https://en.wikipedia.org/wiki/Prince_consort") + (title . "Prince consort")) + ((link . "https://en.wikipedia.org/wiki/Nellie_Clifden") + (title . "Nellie Clifden"))]))]) + (answerBox (title . "Prince Albert of Saxe-Coburg and Gotha / Spouse") + (answer . "Queen Victoria")) + (peopleAlsoAsk . [((link . "https://en.wikipedia.org/wiki/Albert_II,_Prince_of...") + (title . "Albert II, Prince of Monaco - Wikipedia") + (snippet . "In July 2011, Prince Albert married South African ...") + (question . "How many children does Prince Albert of Monaco hav..."))])))) + ;; Act + (result (khoj--extract-online-references + '(organic knowledgeGraph peopleAlsoAsk webpages) + onlineContext))) + ;; Assert + (progn + (should (equal (length result) 10)) + result))) + (provide 'khoj-tests) ;;; khoj-tests.el ends here From 7bcb49b6e7b65b454b09f27faf87d2a3971f4d33 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Tue, 11 Jun 2024 19:57:43 +0530 Subject: [PATCH 05/14] Support conversation sessions in the Khoj Emacs client Add option in khoj main transient menu option to open menu to - switch between existing conversations --- src/interface/emacs/khoj.el | 55 +++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 639aade4..9b7e3ef4 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -698,7 +698,7 @@ Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." "Chat with Khoj." (interactive) (when (not (get-buffer khoj--chat-buffer-name)) - (khoj--load-chat-history khoj--chat-buffer-name)) + (khoj--load-chat-session khoj--chat-buffer-name)) (khoj--open-side-pane khoj--chat-buffer-name) (let ((query (read-string "Query: "))) (when (not (string-empty-p query)) @@ -722,10 +722,11 @@ Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." (- (truncate (* 0.33 (frame-width))) (window-width)) t))))) -(defun khoj--load-chat-history (buffer-name) +(defun khoj--load-chat-session (buffer-name &optional session-id) "Load Khoj Chat conversation history into BUFFER-NAME." (setq khoj--reference-count 0) - (let ((json-response (cdr (elt (cdr (assoc 'response (khoj--get-chat-history-api))) 0)))) + (let ((inhibit-read-only t) + (json-response (cdr (elt (cdr (assoc 'response (khoj--get-chat-session session-id))) 0)))) (with-current-buffer (get-buffer-create buffer-name) (erase-buffer) (insert "* Khoj Chat\n") @@ -794,6 +795,19 @@ Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." #'khoj--format-chat-response #'khoj--render-chat-response buffer-name)))) +(defun khoj--get-chat-sessions () + "Get all chat sessions from Khoj server." + (let* ((url-request-method "GET") + (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)))) + (query-url (format "%s/api/chat/sessions?client=emacs" khoj-server-url))) + (with-temp-buffer + (condition-case ex + (progn + (url-insert-file-contents query-url) + (json-parse-buffer :object-type 'alist)) + ('file-error (message "Chat exception: [%s]" ex)))))) + + (defun khoj--query-chat-api (query callback &rest cbargs) "Send QUERY to Khoj Chat API and call CALLBACK with the response. CBARGS are optional additional arguments to pass to CALLBACK." @@ -811,11 +825,12 @@ CBARGS are optional additional arguments to pass to CALLBACK." (apply callback (json-parse-buffer :object-type 'alist) cbargs)))))) -(defun khoj--get-chat-history-api () - "Send QUERY to Khoj Chat History API." +(defun khoj--get-chat-session (&optional session-id) + "Get chat messages from default or SESSION-ID chat session." (let* ((url-request-method "GET") (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)))) - (query-url (format "%s/api/chat/history?client=emacs" khoj-server-url))) + (session-id-query-param (if session-id (format "&conversation_id=%s" session-id) "")) + (query-url (format "%s/api/chat/history?client=emacs%s" khoj-server-url session-id-query-param))) (with-temp-buffer (condition-case ex (progn @@ -825,6 +840,17 @@ CBARGS are optional additional arguments to pass to CALLBACK." (message "Chat processor not configured. Configure OpenAI API key and restart it. Exception: [%s]" ex)) (t (message "Chat exception: [%s]" ex)))))))) +(defun khoj--open-conversation-session () + "Menu to select Khoj conversation session to open." + (let* ((sessions (khoj--get-chat-sessions)) + (session-alist (-map (lambda (session) + (cons (cdr (assoc 'slug session)) + (cdr (assoc 'conversation_id session)))) + sessions)) + (selected-session-slug (completing-read "Open Conversation: " session-alist nil t)) + (selected-session-id (cdr (assoc selected-session-slug session-alist)))) + (khoj--load-chat-session khoj--chat-buffer-name selected-session-id) + (khoj--open-side-pane khoj--chat-buffer-name))) (defun khoj--render-chat-message (message sender &optional receive-date) "Render chat messages as `org-mode' list item. @@ -1090,7 +1116,7 @@ Paragraph only starts at first text after blank line." ;; --------- (defun khoj--setup-and-show-menu () - "Create Transient menu for khoj and show it." + "Create main Transient menu for Khoj and show it." ;; Create the Khoj Transient menu (transient-define-argument khoj--content-type-switch () :class 'transient-switches @@ -1137,15 +1163,26 @@ Paragraph only starts at first text after blank line." (interactive (list (transient-args transient-current-command))) (khoj--chat)) + (transient-define-suffix khoj--open-conversation-session-command (&optional _) + "Command to select Khoj conversation sessions to open." + (interactive (list (transient-args transient-current-command))) + (khoj--open-conversation-session)) + + (transient-define-prefix khoj--chat-menu () + "Open the Khoj chat menu." + ["Act" + ("c" "Chat" khoj--chat-command) + ("o" "Open Session" khoj--open-conversation-session-command)]) + (transient-define-prefix khoj--menu () "Create Khoj Menu to Configure and Execute Commands." [["Configure Search" - ("n" "Results Count" "--results-count=" :init-value (lambda (obj) (oset obj value (format "%s" khoj-results-count)))) + ("-n" "Results Count" "--results-count=" :init-value (lambda (obj) (oset obj value (format "%s" khoj-results-count)))) ("t" "Content Type" khoj--content-type-switch)] ["Configure Update" ("-f" "Force Update" "--force-update")]] [["Act" - ("c" "Chat" khoj--chat-command) + ("c" "Chat" khoj--chat-menu) ("s" "Search" khoj--search-command) ("f" "Find Similar" khoj--find-similar-command) ("u" "Update" khoj--update-command) From e21c0648aefda8a5efd15daf6d56c268b49be36b Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 12 Jun 2024 21:01:39 +0530 Subject: [PATCH 06/14] Create, use reusable function to call Khoj API from elisp --- src/interface/emacs/khoj.el | 157 ++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 9b7e3ef4..a64608d7 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -629,38 +629,67 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request. ;; -------------- ;; 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. +Return json parsed response as alist." + (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)) + (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 + (progn + (url-insert-file-contents query-url) + (if (and callback cbargs) + (apply callback (json-parse-buffer :object-type 'alist) cbargs) + (if callback + (funcall callback (json-parse-buffer :object-type 'alist)) + (json-parse-buffer :object-type 'alist)))) + ('file-error (message "Chat exception: [%s]" ex)))))) + +(defun khoj--call-api-async (path &optional method params callback &rest cbargs) + "Async call to API at PATH with METHOD and query PARAMS as kv assoc list. +Return json parsed response as alist." + (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) "")) + (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&client=emacs" khoj-server-url path param-string))) + (url-retrieve query-url + (lambda (status) + (if (plist-get status :error) + (message "Chat exception: [%s]" (plist-get status :error)) + (goto-char (point-min)) + (re-search-forward "^$") + (delete-region (point) (point-min)) + (if (and callback cbargs) + (apply callback (json-parse-buffer :object-type 'alist) cbargs) + (if callback + (funcall callback (json-parse-buffer :object-type 'alist)) + (json-parse-buffer :object-type 'alist)))))))) + (defun khoj--get-enabled-content-types () "Get content types enabled for search from API." - (let ((config-url (format "%s/api/config/types" khoj-server-url)) - (url-request-method "GET") - (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key))))) - (with-temp-buffer - (url-insert-file-contents config-url) - (thread-last - (json-parse-buffer :object-type 'alist) - (mapcar #'intern))))) + (khoj--call-api "/api/config/types" "GET" nil `(lambda (item) (mapcar #'intern item)))) -(defun khoj--construct-search-api-query (query content-type &optional rerank) - "Construct Search API Query. -Use QUERY, CONTENT-TYPE and (optional) RERANK as query params" - (let ((rerank (or rerank "false")) - (encoded-query (url-hexify-string query))) - (format "%s/api/search?q=%s&t=%s&r=%s&n=%s&client=emacs" khoj-server-url encoded-query content-type rerank khoj-results-count))) +(defun khoj--query-search-api-and-render-results (query content-type buffer-name &optional rerank title) + "Query Khoj Search API with QUERY, CONTENT-TYPE and (optional) RERANK as query params +Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional) TITLE." + (let ((title (or title query)) + (rerank (or rerank "false")) + (params `((q ,query) (t ,content-type) (r ,rerank) (n ,khoj-results-count))) + (path "/api/search")) + (khoj--call-api path + "GET" + params + 'khoj--render-search-results + content-type title buffer-name))) -(defun khoj--query-search-api-and-render-results (query-url content-type query buffer-name) - "Query Khoj Search with QUERY-URL. -Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." - ;; get json response from api - (with-current-buffer buffer-name - (let ((inhibit-read-only t) - (url-request-method "GET") - (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key))))) - (erase-buffer) - (url-insert-file-contents query-url))) +(defun khoj--render-search-results (json-response content-type query buffer-name) ;; render json response into formatted entries (with-current-buffer buffer-name - (let ((inhibit-read-only t) - (json-response (json-parse-buffer :object-type 'alist))) + (let ((inhibit-read-only t)) (erase-buffer) (insert (cond ((equal content-type "org") (khoj--extract-entries-as-org json-response query)) @@ -673,15 +702,15 @@ Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." (equal content-type "org")) (progn (visual-line-mode) (org-mode) - (setq-local - org-startup-folded "showall" - org-hide-leading-stars t - org-startup-with-inline-images t) - (org-set-startup-visibility))) + (setq-local + org-startup-folded "showall" + org-hide-leading-stars t + org-startup-with-inline-images t) + (org-set-startup-visibility))) ((equal content-type "markdown") (progn (markdown-mode) (visual-line-mode))) ((equal content-type "image") (progn (shr-render-region (point-min) (point-max)) - (goto-char (point-min)))) + (goto-char (point-min)))) (t (fundamental-mode)))) ;; keep cursor at top of khoj buffer by default (goto-char (point-min)) @@ -795,50 +824,23 @@ Render results in BUFFER-NAME using QUERY, CONTENT-TYPE." #'khoj--format-chat-response #'khoj--render-chat-response buffer-name)))) -(defun khoj--get-chat-sessions () - "Get all chat sessions from Khoj server." - (let* ((url-request-method "GET") - (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)))) - (query-url (format "%s/api/chat/sessions?client=emacs" khoj-server-url))) - (with-temp-buffer - (condition-case ex - (progn - (url-insert-file-contents query-url) - (json-parse-buffer :object-type 'alist)) - ('file-error (message "Chat exception: [%s]" ex)))))) - - (defun khoj--query-chat-api (query callback &rest cbargs) "Send QUERY to Khoj Chat API and call CALLBACK with the response. CBARGS are optional additional arguments to pass to CALLBACK." - (let* ((url-request-method "GET") - (encoded-query (url-hexify-string query)) - (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)))) - (query-url (format "%s/api/chat?q=%s&n=%s&client=emacs" khoj-server-url encoded-query khoj-results-count))) - (url-retrieve query-url - (lambda (status) - (if (plist-get status :error) - (message "Chat exception: [%s]" (plist-get status :error)) - (goto-char (point-min)) - (re-search-forward "^$") - (delete-region (point) (point-min)) - (apply callback (json-parse-buffer :object-type 'alist) cbargs)))))) + (khoj--call-api-async "/api/chat" + "GET" + `(("q" ,query) ("n" ,khoj-results-count)) + callback cbargs)) +(defun khoj--get-chat-sessions () + "Get all chat sessions from Khoj server." + (khoj--call-api "/api/chat/sessions" "GET")) (defun khoj--get-chat-session (&optional session-id) "Get chat messages from default or SESSION-ID chat session." - (let* ((url-request-method "GET") - (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)))) - (session-id-query-param (if session-id (format "&conversation_id=%s" session-id) "")) - (query-url (format "%s/api/chat/history?client=emacs%s" khoj-server-url session-id-query-param))) - (with-temp-buffer - (condition-case ex - (progn - (url-insert-file-contents query-url) - (json-parse-buffer :object-type 'alist)) - ('file-error (cond ((string-match "Internal server error" (nth 2 ex)) - (message "Chat processor not configured. Configure OpenAI API key and restart it. Exception: [%s]" ex)) - (t (message "Chat exception: [%s]" ex)))))))) + (khoj--call-api "/api/chat/history" + "GET" + (when session-id `(("conversation_id" ,session-id))))) (defun khoj--open-conversation-session () "Menu to select Khoj conversation session to open." @@ -977,8 +979,7 @@ RECEIVE-DATE is the message receive date." "Perform Incremental Search on Khoj. Allow optional RERANK of results." (let* ((rerank-str (cond (rerank "true") (t "false"))) (khoj-buffer-name (get-buffer-create khoj--search-buffer-name)) - (query (minibuffer-contents-no-properties)) - (query-url (khoj--construct-search-api-query query khoj--content-type rerank-str))) + (query (minibuffer-contents-no-properties))) ;; Query khoj API only when user in khoj minibuffer and non-empty query ;; Prevents querying if ;; 1. user hasn't started typing query @@ -998,10 +999,10 @@ RECEIVE-DATE is the message receive date." (setq khoj--rerank t) (message "khoj.el: Rerank Results")) (khoj--query-search-api-and-render-results - query-url - khoj--content-type query - khoj-buffer-name)))))) + khoj--content-type + khoj-buffer-name + rerank-str)))))) (defun khoj--delete-open-network-connections-to-server () "Delete all network connections to khoj server." @@ -1095,7 +1096,6 @@ Paragraph only starts at first text after blank line." ;; get paragraph, if in text mode (t (khoj--get-current-paragraph-text)))) - (query-url (khoj--construct-search-api-query query content-type rerank)) ;; extract heading to show in result buffer from query (query-title (format "Similar to: %s" @@ -1103,10 +1103,11 @@ Paragraph only starts at first text after blank line." (buffer-name (get-buffer-create khoj--search-buffer-name))) (progn (khoj--query-search-api-and-render-results - query-url + query content-type - query-title - buffer-name) + buffer-name + rerank + query-title) (khoj--open-side-pane buffer-name) (goto-char (point-min))))) From c33954cd93053c37f19b903d1b572a400db7f5f2 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 12 Jun 2024 22:49:47 +0530 Subject: [PATCH 07/14] Fix loading an empty chat session in Emacs --- src/interface/emacs/khoj.el | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index a64608d7..d702963f 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -755,16 +755,17 @@ Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional) "Load Khoj Chat conversation history into BUFFER-NAME." (setq khoj--reference-count 0) (let ((inhibit-read-only t) - (json-response (cdr (elt (cdr (assoc 'response (khoj--get-chat-session session-id))) 0)))) + (json-response (cdr (assoc 'chat (cdr (assoc 'response (khoj--get-chat-session session-id))))))) (with-current-buffer (get-buffer-create buffer-name) (erase-buffer) (insert "* Khoj Chat\n") - (thread-last - json-response - ;; generate chat messages from Khoj Chat API response - (mapcar #'khoj--format-chat-response) - ;; insert chat messages into Khoj Chat Buffer - (mapc #'insert)) + (when json-response + (thread-last + json-response + ;; generate chat messages from Khoj Chat API response + (mapcar #'khoj--format-chat-response) + ;; insert chat messages into Khoj Chat Buffer + (mapc #'insert))) (progn (org-mode) (khoj--add-hover-text-to-footnote-refs (point-min)) @@ -846,7 +847,9 @@ CBARGS are optional additional arguments to pass to CALLBACK." "Menu to select Khoj conversation session to open." (let* ((sessions (khoj--get-chat-sessions)) (session-alist (-map (lambda (session) - (cons (cdr (assoc 'slug session)) + (cons (if (not (equal :null (cdr (assoc 'slug session)))) + (cdr (assoc 'slug session)) + (format "New Conversation (%s)" (cdr (assoc 'conversation_id session)))) (cdr (assoc 'conversation_id session)))) sessions)) (selected-session-slug (completing-read "Open Conversation: " session-alist nil t)) From 055e5e8d261abd401ab849e0c1ddc339d0a36356 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 12 Jun 2024 23:02:04 +0530 Subject: [PATCH 08/14] Create new conversation from the chat menu in Khoj Emacs --- src/interface/emacs/khoj.el | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index d702963f..f18680ef 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -857,6 +857,17 @@ CBARGS are optional additional arguments to pass to CALLBACK." (khoj--load-chat-session khoj--chat-buffer-name selected-session-id) (khoj--open-side-pane khoj--chat-buffer-name))) +(defun khoj--create-chat-session () + "Create new chat session." + (khoj--call-api "/api/chat/sessions" "POST")) + +(defun khoj--new-conversation-session () + "Create new Khoj conversation session." + (let* ((session (khoj--create-chat-session)) + (new-session-id (cdr (assoc 'conversation_id session)))) + (khoj--load-chat-session khoj--chat-buffer-name new-session-id) + (khoj--open-side-pane khoj--chat-buffer-name))) + (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. @@ -1172,11 +1183,18 @@ 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 _) + "Command to select Khoj conversation sessions to open." + (interactive (list (transient-args transient-current-command))) + (khoj--new-conversation-session)) + (transient-define-prefix khoj--chat-menu () "Open the Khoj chat menu." ["Act" ("c" "Chat" khoj--chat-command) - ("o" "Open Session" khoj--open-conversation-session-command)]) + ("o" "Open Conversation" khoj--open-conversation-session-command) + ("n" "New Conversation" khoj--new-conversation-session-command) + ]) (transient-define-prefix khoj--menu () "Create Khoj Menu to Configure and Execute Commands." From e15dc23bbe3a4cd815e90a7df10fbaa7f35d34a3 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 13 Jun 2024 08:02:33 +0530 Subject: [PATCH 09/14] Improve logic to create vs reuse window for khoj side pane logic Khoj side pane occupies a vertically split bottom right side pane. If the bottom right window is not a vertical split, create a new vertical split pane for khoj, otherwise reuse the existing window --- src/interface/emacs/khoj.el | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index f18680ef..7a68707a 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -736,20 +736,27 @@ Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional) (defun khoj--open-side-pane (buffer-name) "Open Khoj BUFFER-NAME in right side pane." (if (get-buffer-window-list buffer-name) - ; if window is already open, switch to it + ;; if window is already open, switch to it (progn (select-window (get-buffer-window buffer-name)) (switch-to-buffer buffer-name)) ;; else if window is not open, open it as a right-side window pane - (progn - ;; Select the right-most window - (select-window (some-window (lambda (window) (window-at-side-p window 'right)))) - ;; Split the window to the right and resize it to take up 1/3 of the frame width - (let ((new-window (split-window-right))) - (set-window-buffer new-window buffer-name) - (window-resize new-window - (- (truncate (* 0.33 (frame-width))) (window-width)) - t))))) + (let ((bottomright-window (some-window (lambda (window) (and (window-at-side-p window 'right) (window-at-side-p window 'bottom)))))) + (progn + ;; Select the right-most window + (select-window bottomright-window) + ;; if bottom-right window is not a vertical pane, split it vertically, else use the existing bottom-right vertical window + (let ((khoj-window (if (window-at-side-p bottomright-window 'left) + (split-window-right) + bottomright-window))) + ;; Set the buffer in the khoj window + (set-window-buffer khoj-window buffer-name) + ;; Switch to the khoj window + (select-window khoj-window) + ;; Resize the window to 1/3 of the frame width + (window-resize khoj-window + (- (truncate (* 0.33 (frame-width))) (window-width)) + t)))))) (defun khoj--load-chat-session (buffer-name &optional session-id) "Load Khoj Chat conversation history into BUFFER-NAME." @@ -781,6 +788,7 @@ Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional) ;; create khoj chat shortcut keybindings (use-local-map (copy-keymap org-mode-map)) + (local-set-key (kbd "q") #'khoj--close) (local-set-key (kbd "m") #'khoj--chat) (local-set-key (kbd "C-x m") #'khoj--chat) @@ -788,6 +796,13 @@ Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional) (visual-line-mode) (read-only-mode t))))) +(defun khoj--close () + "Kill Khoj buffer and window" + (interactive) + (progn + (kill-buffer (current-buffer)) + (delete-window))) + (defun khoj--add-hover-text-to-footnote-refs (start-pos) "Show footnote defs on mouse hover on footnote refs from START-POS." (org-with-wide-buffer From e3d995a74f7fb9fe9c2f8ef01b0c98a67b201733 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 13 Jun 2024 08:24:53 +0530 Subject: [PATCH 10/14] Extract select conversation session logic into func for reusability --- src/interface/emacs/khoj.el | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 7a68707a..587cce56 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -858,17 +858,22 @@ CBARGS are optional additional arguments to pass to CALLBACK." "GET" (when session-id `(("conversation_id" ,session-id))))) -(defun khoj--open-conversation-session () - "Menu to select Khoj conversation session to open." - (let* ((sessions (khoj--get-chat-sessions)) +(defun khoj--select-conversation-session (&optional completion-action) + "Select Khoj conversation session to perform COMPLETION-ACTION on." + (let* ((completion-text (format "%s Conversation:" (or completion-action "Open"))) + (sessions (khoj--get-chat-sessions)) (session-alist (-map (lambda (session) (cons (if (not (equal :null (cdr (assoc 'slug session)))) (cdr (assoc 'slug session)) (format "New Conversation (%s)" (cdr (assoc 'conversation_id session)))) (cdr (assoc 'conversation_id session)))) sessions)) - (selected-session-slug (completing-read "Open Conversation: " session-alist nil t)) - (selected-session-id (cdr (assoc selected-session-slug session-alist)))) + (selected-session-slug (completing-read completion-text session-alist nil t))) + (cdr (assoc selected-session-slug session-alist)))) + +(defun khoj--open-conversation-session () + "Menu to select Khoj conversation session to open." + (let ((selected-session-id (khoj--select-conversation-session "Open"))) (khoj--load-chat-session khoj--chat-buffer-name selected-session-id) (khoj--open-side-pane khoj--chat-buffer-name))) From db056c896dcb0b59a43e0ec43adc83105682894d Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 13 Jun 2024 08:29:51 +0530 Subject: [PATCH 11/14] Delete old conversation sessions from the chat menu in Khoj Emacs --- src/interface/emacs/khoj.el | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 587cce56..5d9c9229 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -888,6 +888,17 @@ CBARGS are optional additional arguments to pass to CALLBACK." (khoj--load-chat-session khoj--chat-buffer-name new-session-id) (khoj--open-side-pane khoj--chat-buffer-name))) +(defun khoj--delete-chat-session (session-id) + "Delete new chat session." + (khoj--call-api "/api/chat/history" "DELETE" `(("conversation_id" ,session-id)))) + +(defun khoj--delete-conversation-session () + "Delete new Khoj conversation session." + (let* ((selected-session-id (khoj--select-conversation-session "Delete")) + (session (khoj--delete-chat-session selected-session-id))) + (khoj--load-chat-session khoj--chat-buffer-name) + (khoj--open-side-pane khoj--chat-buffer-name))) + (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. @@ -1208,12 +1219,19 @@ Paragraph only starts at first text after blank line." (interactive (list (transient-args transient-current-command))) (khoj--new-conversation-session)) + (transient-define-suffix khoj--delete-conversation-session-command (&optional _) + "Command to select Khoj conversation sessions to delete." + (interactive (list (transient-args transient-current-command))) + (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) ]) (transient-define-prefix khoj--menu () From c6b95f877650c95b43ca0ebe6f6438e852219ca5 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sat, 15 Jun 2024 15:32:28 +0530 Subject: [PATCH 12/14] Handle rendering messages using the old reference schema in khoj.el Previously references were a list instead of a map --- src/interface/emacs/khoj.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 5d9c9229..599250e0 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -921,7 +921,7 @@ RECEIVE-DATE is the message receive date." (defun khoj--generate-reference (reference) "Create `org-mode' footnotes with REFERENCE." (setq khoj--reference-count (1+ khoj--reference-count)) - (let ((compiled-reference (cdr (assoc 'compiled reference)))) + (let ((compiled-reference (if (stringp reference) reference (cdr (assoc 'compiled reference))))) (cons (propertize (format "^{ [fn:%x]}" khoj--reference-count) 'help-echo compiled-reference) (thread-last From 2a84524d19a8a1dc48899464c0b8cee4547651bf Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sat, 15 Jun 2024 17:28:16 +0530 Subject: [PATCH 13/14] Make khoj.el search, similar API calls async to not block user interactions --- src/interface/emacs/khoj.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 599250e0..1e670c70 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -680,7 +680,7 @@ Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional) (rerank (or rerank "false")) (params `((q ,query) (t ,content-type) (r ,rerank) (n ,khoj-results-count))) (path "/api/search")) - (khoj--call-api path + (khoj--call-api-async path "GET" params 'khoj--render-search-results From fe36adb7b9305cacefca52be854415c815142779 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sat, 15 Jun 2024 17:31:19 +0530 Subject: [PATCH 14/14] Remove short keys to switch content type during search to avoid conflict - C-x o to switch to search org content conflicts with switch buffer shortkey This is more apparent in the async search scenario as it prevents perform other actions while async search is in progress - Also switching content type wouldn't scale to all the content types Khoj will support without causing more conflicting keybinding --- src/interface/emacs/khoj.el | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 1e670c70..a3d71248 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -113,7 +113,7 @@ :group 'khoj :type 'number) -(defcustom khoj-default-content-type "org" +(defcustom khoj-default-content-type "all" "The default content type to perform search on." :group 'khoj :type '(choice (const "org") @@ -163,6 +163,7 @@ NO-PAGING FILTER)) " Set Content Type -------------------------\n" + ("C-c RET | improve sort \n") (when (member 'markdown enabled-content-types) "C-x m | markdown\n") (when (member 'org enabled-content-types) @@ -174,24 +175,12 @@ NO-PAGING FILTER)) (defvar khoj--rerank nil "Track when re-rank of results triggered.") (defvar khoj--reference-count 0 "Track number of references currently in chat bufffer.") -(defun khoj--search-markdown () "Set content-type to `markdown'." (interactive) (setq khoj--content-type "markdown")) -(defun khoj--search-org () "Set content-type to `org-mode'." (interactive) (setq khoj--content-type "org")) -(defun khoj--search-images () "Set content-type to image." (interactive) (setq khoj--content-type "image")) -(defun khoj--search-pdf () "Set content-type to pdf." (interactive) (setq khoj--content-type "pdf")) -(defun khoj--improve-rank () "Use cross-encoder to rerank search results." (interactive) (khoj--incremental-search t)) +(defun khoj--improve-sort () "Use cross-encoder to improve sorting of search results." (interactive) (khoj--incremental-search t)) (defun khoj--make-search-keymap (&optional existing-keymap) "Setup keymap to configure Khoj search. Build of EXISTING-KEYMAP when passed." (let ((enabled-content-types (khoj--get-enabled-content-types)) (kmap (or existing-keymap (make-sparse-keymap)))) - (define-key kmap (kbd "C-c RET") #'khoj--improve-rank) - (when (member 'markdown enabled-content-types) - (define-key kmap (kbd "C-x m") #'khoj--search-markdown)) - (when (member 'org enabled-content-types) - (define-key kmap (kbd "C-x o") #'khoj--search-org)) - (when (member 'image enabled-content-types) - (define-key kmap (kbd "C-x i") #'khoj--search-images)) - (when (member 'pdf enabled-content-types) - (define-key kmap (kbd "C-x p") #'khoj--search-pdf)) + (define-key kmap (kbd "C-c RET") #'khoj--improve-sort) kmap)) (defvar khoj--keymap nil "Track Khoj keymap in this variable.")