mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-09 21:29:11 +00:00
Find similar notes to current note at cursor automatically in background
- Call find similar on current element if point has moved to new element - Delete the first result from find-similar search results as that'll be the current note (which is trivially most similar to itself) - Determine find-similar based text formating at the rendering layer rather than at the top level find-similar func
This commit is contained in:
@@ -93,6 +93,11 @@
|
|||||||
:group 'khoj
|
:group 'khoj
|
||||||
:type 'number)
|
:type 'number)
|
||||||
|
|
||||||
|
(defcustom khoj-auto-find-similar t
|
||||||
|
"Should try find similar notes automatically."
|
||||||
|
:group 'khoj
|
||||||
|
:type 'boolean)
|
||||||
|
|
||||||
(defcustom khoj-api-key nil
|
(defcustom khoj-api-key nil
|
||||||
"API Key to your Khoj. Default at https://app.khoj.dev/config#clients."
|
"API Key to your Khoj. Default at https://app.khoj.dev/config#clients."
|
||||||
:group 'khoj
|
:group 'khoj
|
||||||
@@ -186,6 +191,9 @@ Use `which-key` if available, else display simple message in echo area"
|
|||||||
nil t t))
|
nil t t))
|
||||||
(message "%s" (khoj--keybindings-info-message))))
|
(message "%s" (khoj--keybindings-info-message))))
|
||||||
|
|
||||||
|
(defvar khoj--last-heading-pos nil
|
||||||
|
"The last heading position point was in.")
|
||||||
|
|
||||||
|
|
||||||
;; ----------------
|
;; ----------------
|
||||||
;; Khoj Setup
|
;; Khoj Setup
|
||||||
@@ -495,11 +503,17 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
|
|||||||
;; -------------------------------------------
|
;; -------------------------------------------
|
||||||
;; Render Response from Khoj server for Emacs
|
;; Render Response from Khoj server for Emacs
|
||||||
;; -------------------------------------------
|
;; -------------------------------------------
|
||||||
|
(defun construct-find-similar-title (query)
|
||||||
|
"Construct title for find-similar query."
|
||||||
|
(format "Similar to: %s"
|
||||||
|
(replace-regexp-in-string "^[#\\*]* " "" (car (split-string query "\n")))))
|
||||||
|
|
||||||
(defun khoj--extract-entries-as-markdown (json-response query)
|
(defun khoj--extract-entries-as-markdown (json-response query is-find-similar)
|
||||||
"Convert JSON-RESPONSE, QUERY from API to markdown entries."
|
"Convert JSON-RESPONSE, QUERY from API to markdown entries."
|
||||||
(thread-last
|
(thread-last
|
||||||
json-response
|
json-response
|
||||||
|
;; filter our first result if is find similar as it'll be the current entry at point
|
||||||
|
((lambda (response) (if is-find-similar (seq-drop response 1) response)))
|
||||||
;; Extract and render each markdown entry from response
|
;; Extract and render each markdown entry from response
|
||||||
(mapcar (lambda (json-response-item)
|
(mapcar (lambda (json-response-item)
|
||||||
(thread-last
|
(thread-last
|
||||||
@@ -510,14 +524,17 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
|
|||||||
;; Standardize results to 2nd level heading for consistent rendering
|
;; Standardize results to 2nd level heading for consistent rendering
|
||||||
(replace-regexp-in-string "^\#+" "##"))))
|
(replace-regexp-in-string "^\#+" "##"))))
|
||||||
;; Render entries into markdown formatted string with query set as as top level heading
|
;; Render entries into markdown formatted string with query set as as top level heading
|
||||||
(format "# %s\n%s" query)
|
(format "# %s\n%s" (if is-find-similar (construct-find-similar-title query) query))
|
||||||
;; remove leading (, ) or SPC from extracted entries string
|
;; remove leading (, ) or SPC from extracted entries string
|
||||||
(replace-regexp-in-string "^[\(\) ]" "")))
|
(replace-regexp-in-string "^[\(\) ]" "")))
|
||||||
|
|
||||||
(defun khoj--extract-entries-as-org (json-response query)
|
(defun khoj--extract-entries-as-org (json-response query is-find-similar)
|
||||||
"Convert JSON-RESPONSE, QUERY from API to `org-mode' entries."
|
"Convert JSON-RESPONSE, QUERY from API to `org-mode' entries.
|
||||||
|
Use IS-FIND-SIMILAR to determine if results should be ."
|
||||||
(thread-last
|
(thread-last
|
||||||
json-response
|
json-response
|
||||||
|
;; filter our first result if is find similar as it'll be the current entry at point
|
||||||
|
((lambda (response) (if is-find-similar (seq-drop response 1) response)))
|
||||||
;; Extract and render each org-mode entry from response
|
;; Extract and render each org-mode entry from response
|
||||||
(mapcar (lambda (json-response-item)
|
(mapcar (lambda (json-response-item)
|
||||||
(thread-last
|
(thread-last
|
||||||
@@ -528,14 +545,16 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
|
|||||||
;; Standardize results to 2nd level heading for consistent rendering
|
;; Standardize results to 2nd level heading for consistent rendering
|
||||||
(replace-regexp-in-string "^\*+" "**"))))
|
(replace-regexp-in-string "^\*+" "**"))))
|
||||||
;; Render entries into org formatted string with query set as as top level heading
|
;; Render entries into org formatted string with query set as as top level heading
|
||||||
(format "* %s\n%s\n" query)
|
(format "* %s\n%s\n" (if is-find-similar (construct-find-similar-title query) query))
|
||||||
;; remove leading (, ) or SPC from extracted entries string
|
;; remove leading (, ) or SPC from extracted entries string
|
||||||
(replace-regexp-in-string "^[\(\) ]" "")))
|
(replace-regexp-in-string "^[\(\) ]" "")))
|
||||||
|
|
||||||
(defun khoj--extract-entries-as-pdf (json-response query)
|
(defun khoj--extract-entries-as-pdf (json-response query is-find-similar)
|
||||||
"Convert QUERY, JSON-RESPONSE from API with PDF results to `org-mode' entries."
|
"Convert QUERY, JSON-RESPONSE from API with PDF results to `org-mode' entries."
|
||||||
(thread-last
|
(thread-last
|
||||||
json-response
|
json-response
|
||||||
|
;; filter our first result if is find similar as it'll be the current entry at point
|
||||||
|
((lambda (response) (if is-find-similar (seq-drop response 1) response)))
|
||||||
;; Extract and render each pdf entry from response
|
;; Extract and render each pdf entry from response
|
||||||
(mapcar (lambda (json-response-item)
|
(mapcar (lambda (json-response-item)
|
||||||
(thread-last
|
(thread-last
|
||||||
@@ -544,7 +563,7 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
|
|||||||
;; Format pdf entry as a org entry string
|
;; Format pdf entry as a org entry string
|
||||||
(format "** %s\n\n"))))
|
(format "** %s\n\n"))))
|
||||||
;; Render entries into org formatted string with query set as as top level heading
|
;; Render entries into org formatted string with query set as as top level heading
|
||||||
(format "* %s\n%s\n" query)
|
(format "* %s\n%s\n" (if is-find-similar (construct-find-similar-title query) query))
|
||||||
;; remove leading (, ) or SPC from extracted entries string
|
;; remove leading (, ) or SPC from extracted entries string
|
||||||
(replace-regexp-in-string "^[\(\) ]" "")))
|
(replace-regexp-in-string "^[\(\) ]" "")))
|
||||||
|
|
||||||
@@ -576,9 +595,11 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
|
|||||||
;; remove trailing (, ) or SPC from extracted entries string
|
;; remove trailing (, ) or SPC from extracted entries string
|
||||||
(replace-regexp-in-string "[\(\) ]$" ""))))
|
(replace-regexp-in-string "[\(\) ]$" ""))))
|
||||||
|
|
||||||
(defun khoj--extract-entries (json-response query)
|
(defun khoj--extract-entries (json-response query is-find-similar)
|
||||||
"Convert JSON-RESPONSE, QUERY from API to text entries."
|
"Convert JSON-RESPONSE, QUERY from API to text entries."
|
||||||
(thread-last json-response
|
(thread-last json-response
|
||||||
|
;; filter our first result if is find similar as it'll be the current entry at point
|
||||||
|
((lambda (response) (if is-find-similar (seq-drop response 1) response)))
|
||||||
;; extract and render entries from API response
|
;; extract and render entries from API response
|
||||||
(mapcar (lambda (json-response-item)
|
(mapcar (lambda (json-response-item)
|
||||||
(thread-last
|
(thread-last
|
||||||
@@ -592,7 +613,7 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
|
|||||||
;; Format entries as org entry string
|
;; Format entries as org entry string
|
||||||
(format "** %s"))))
|
(format "** %s"))))
|
||||||
;; Set query as heading in rendered results buffer
|
;; Set query as heading in rendered results buffer
|
||||||
(format "* %s\n%s\n" query)
|
(format "* %s\n%s\n" (if is-find-similar (construct-find-similar-title query) query))
|
||||||
;; remove leading (, ) or SPC from extracted entries string
|
;; remove leading (, ) or SPC from extracted entries string
|
||||||
(replace-regexp-in-string "^[\(\) ]" "")
|
(replace-regexp-in-string "^[\(\) ]" "")
|
||||||
;; remove trailing (, ) or SPC from extracted entries string
|
;; remove trailing (, ) or SPC from extracted entries string
|
||||||
@@ -656,30 +677,30 @@ Return json parsed response as alist."
|
|||||||
"Get content types enabled for search from API."
|
"Get content types enabled for search from API."
|
||||||
(khoj--call-api "/api/config/types" "GET" nil `(lambda (item) (mapcar #'intern item))))
|
(khoj--call-api "/api/config/types" "GET" nil `(lambda (item) (mapcar #'intern item))))
|
||||||
|
|
||||||
(defun khoj--query-search-api-and-render-results (query content-type buffer-name &optional rerank title)
|
(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 (optional) RERANK as query params
|
"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."
|
Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional) TITLE."
|
||||||
(let ((title (or title query))
|
(let ((rerank (or rerank "false"))
|
||||||
(rerank (or rerank "false"))
|
|
||||||
(params `((q ,query) (t ,content-type) (r ,rerank) (n ,khoj-results-count)))
|
(params `((q ,query) (t ,content-type) (r ,rerank) (n ,khoj-results-count)))
|
||||||
(path "/api/search"))
|
(path "/api/search"))
|
||||||
(khoj--call-api-async path
|
(khoj--call-api-async path
|
||||||
"GET"
|
"GET"
|
||||||
params
|
params
|
||||||
'khoj--render-search-results
|
'khoj--render-search-results
|
||||||
content-type title buffer-name)))
|
content-type query buffer-name is-find-similar)))
|
||||||
|
|
||||||
(defun khoj--render-search-results (json-response content-type query buffer-name)
|
(defun khoj--render-search-results (json-response content-type query buffer-name &optional is-find-similar)
|
||||||
;; render json response into formatted entries
|
;; render json response into formatted entries
|
||||||
(with-current-buffer buffer-name
|
(with-current-buffer buffer-name
|
||||||
(let ((inhibit-read-only t))
|
(let ((is-find-similar (or is-find-similar nil))
|
||||||
|
(inhibit-read-only t))
|
||||||
(erase-buffer)
|
(erase-buffer)
|
||||||
(insert
|
(insert
|
||||||
(cond ((equal content-type "org") (khoj--extract-entries-as-org json-response query))
|
(cond ((equal content-type "org") (khoj--extract-entries-as-org json-response query is-find-similar))
|
||||||
((equal content-type "markdown") (khoj--extract-entries-as-markdown json-response query))
|
((equal content-type "markdown") (khoj--extract-entries-as-markdown json-response query is-find-similar))
|
||||||
((equal content-type "pdf") (khoj--extract-entries-as-pdf json-response query))
|
((equal content-type "pdf") (khoj--extract-entries-as-pdf json-response query is-find-similar))
|
||||||
((equal content-type "image") (khoj--extract-entries-as-images json-response query))
|
((equal content-type "image") (khoj--extract-entries-as-images json-response))
|
||||||
(t (khoj--extract-entries json-response query))))
|
(t (khoj--extract-entries json-response query is-find-similar))))
|
||||||
(cond ((or (equal content-type "all")
|
(cond ((or (equal content-type "all")
|
||||||
(equal content-type "pdf")
|
(equal content-type "pdf")
|
||||||
(equal content-type "org"))
|
(equal content-type "org"))
|
||||||
@@ -1081,6 +1102,16 @@ RECEIVE-DATE is the message receive date."
|
|||||||
;; Similar Search
|
;; Similar Search
|
||||||
;; --------------
|
;; --------------
|
||||||
|
|
||||||
|
(defun khoj--get-current-outline-entry-pos ()
|
||||||
|
"Get heading position of current outline section."
|
||||||
|
;; get heading position of current outline entry
|
||||||
|
(cond
|
||||||
|
;; when at heading of entry
|
||||||
|
((looking-at outline-regexp)
|
||||||
|
(point))
|
||||||
|
;; when within entry
|
||||||
|
(t (save-excursion (outline-previous-heading) (point)))))
|
||||||
|
|
||||||
(defun khoj--get-current-outline-entry-text ()
|
(defun khoj--get-current-outline-entry-text ()
|
||||||
"Get text under current outline section."
|
"Get text under current outline section."
|
||||||
(string-trim
|
(string-trim
|
||||||
@@ -1124,10 +1155,6 @@ Paragraph only starts at first text after blank line."
|
|||||||
;; get paragraph, if in text mode
|
;; get paragraph, if in text mode
|
||||||
(t
|
(t
|
||||||
(khoj--get-current-paragraph-text))))
|
(khoj--get-current-paragraph-text))))
|
||||||
;; extract heading to show in result buffer from query
|
|
||||||
(query-title
|
|
||||||
(format "Similar to: %s"
|
|
||||||
(replace-regexp-in-string "^[#\\*]* " "" (car (split-string query "\n")))))
|
|
||||||
(buffer-name (get-buffer-create khoj--search-buffer-name)))
|
(buffer-name (get-buffer-create khoj--search-buffer-name)))
|
||||||
(progn
|
(progn
|
||||||
(khoj--query-search-api-and-render-results
|
(khoj--query-search-api-and-render-results
|
||||||
@@ -1135,10 +1162,39 @@ Paragraph only starts at first text after blank line."
|
|||||||
content-type
|
content-type
|
||||||
buffer-name
|
buffer-name
|
||||||
rerank
|
rerank
|
||||||
query-title)
|
t)
|
||||||
(khoj--open-side-pane buffer-name)
|
(khoj--open-side-pane buffer-name)
|
||||||
(goto-char (point-min)))))
|
(goto-char (point-min)))))
|
||||||
|
|
||||||
|
(defun khoj--auto-find-similar ()
|
||||||
|
"Call find similar on current element, if point has moved to a new element."
|
||||||
|
;; Call find similar
|
||||||
|
(when (and (derived-mode-p 'org-mode)
|
||||||
|
(not (null (org-element-at-point)))
|
||||||
|
(not (string= (buffer-name (current-buffer)) khoj--search-buffer-name))
|
||||||
|
(not (null (get-buffer-window khoj--search-buffer-name))))
|
||||||
|
(let ((current-heading-pos (khoj--get-current-outline-entry-pos)))
|
||||||
|
(unless (eq current-heading-pos khoj--last-heading-pos)
|
||||||
|
(progn
|
||||||
|
(setq khoj--last-heading-pos current-heading-pos)
|
||||||
|
(save-excursion
|
||||||
|
(khoj--find-similar)))))))
|
||||||
|
|
||||||
|
(defun khoj--setup-auto-find-similar ()
|
||||||
|
"Setup automatic call to find similar to current element."
|
||||||
|
(if khoj-auto-find-similar
|
||||||
|
(add-hook 'post-command-hook 'khoj--auto-find-similar)
|
||||||
|
(remove-hook 'post-command-hook 'khoj--auto-find-similar)))
|
||||||
|
|
||||||
|
(defun khoj-toggle-auto-find-similar ()
|
||||||
|
"Toggle automatic call to find similar to current element."
|
||||||
|
(interactive)
|
||||||
|
(setq khoj-auto-find-similar (not khoj-auto-find-similar))
|
||||||
|
(khoj--setup-auto-find-similar)
|
||||||
|
(if khoj-auto-find-similar
|
||||||
|
(message "Auto find similar enabled")
|
||||||
|
(message "Auto find similar disabled")))
|
||||||
|
|
||||||
|
|
||||||
;; ---------
|
;; ---------
|
||||||
;; Khoj Menu
|
;; Khoj Menu
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
")))
|
")))
|
||||||
(should
|
(should
|
||||||
(equal
|
(equal
|
||||||
(khoj--extract-entries-as-markdown json-response-from-khoj-backend user-query)
|
(khoj--extract-entries-as-markdown json-response-from-khoj-backend user-query nil)
|
||||||
"\
|
"\
|
||||||
# Become God\n\
|
# Become God\n\
|
||||||
## Upgrade\n\
|
## Upgrade\n\
|
||||||
@@ -100,7 +100,7 @@ Rule everything\n\n"))))
|
|||||||
")))
|
")))
|
||||||
(should
|
(should
|
||||||
(equal
|
(equal
|
||||||
(khoj--extract-entries-as-org json-response-from-khoj-backend user-query)
|
(khoj--extract-entries-as-org json-response-from-khoj-backend user-query nil)
|
||||||
"\
|
"\
|
||||||
* Become God\n\
|
* Become God\n\
|
||||||
** Upgrade\n\
|
** Upgrade\n\
|
||||||
|
|||||||
Reference in New Issue
Block a user