emacs: gptel enhancements including gptel-ask and keybindings

This commit is contained in:
Joseph Ferano 2025-06-24 18:56:59 +07:00
parent ec98d3bfab
commit 40be300bf4

View File

@ -2320,71 +2320,90 @@ These help speed eglot up apparently [[https://www.reddit.com/r/emacs/comments/1
#+begin_src emacs-lisp
(setq gptel-default-mode #'org-mode)
(setq
gptel-model 'claude-3-sonnet-20240229
gptel-model 'claude-sonnet-4-20250514
gptel-backend (gptel-make-anthropic "Claude"
:stream t :key (with-temp-buffer
(insert-file-contents (expand-file-name "gptel-key" user-emacs-directory))
(buffer-string))))
(add-hook 'gptel-post-response-functions #'font-lock-ensure)
(insert-file-contents (expand-file-name "gptel-key" user-emacs-directory))
(buffer-string))))
;; (add-hook 'gptel-post-response-functions #'font-lock-ensure)
(setq gptel-prompt-prefix-alist '((markdown-mode . "### ")
(org-mode . "* ")
(text-mode . "### ")))
(evil-define-key 'normal joe/evil-space-mode-map (kbd "SPC a a") #'gptel)
(evil-define-key 'normal joe/evil-space-mode-map (kbd "SPC a RET") #'gptel-ask)
(evil-define-key 'normal joe/evil-space-mode-map (kbd "SPC a M") #'gptel-mode)
(evil-define-key 'visual joe/evil-space-mode-map (kbd "SPC a c") #'gptel-add) ;; Will delete context at point
(evil-define-key 'normal joe/evil-space-mode-map (kbd "SPC a <backspace>") #'joe/gptel-context-remove-all)
(evil-define-key 'visual joe/evil-space-mode-map (kbd "SPC a r") #'gptel-rewrite)
(evil-define-key 'visual joe/evil-space-mode-map (kbd "SPC a RET") #'gptel-ask)
(evil-define-key 'visual joe/evil-space-mode-map (kbd "SPC a c") #'gptel-add)
#+end_src
This function was suggested by Karthink in order to fix an issue where gptel
org-mode was jumping back up to the top anytime the buffer was saved. Keeping it
around just in case.
gptel-context-remove-all without confirmation
#+begin_src emacs-lisp
(defun joe/gptel-context-remove-all (&optional verbose)
"Remove all gptel context. No confirmation."
(interactive (list t))
(if (null gptel-context--alist)
(message "No gptel context sources to remove.")
(cl-loop
for (source . ovs) in gptel-context--alist
if (bufferp source) do ;Buffers and buffer regions
(mapc #'gptel-context-remove ovs)
else do (gptel-context-remove source) ;files or other types
finally do (setq gptel-context--alist nil))))
#+end_src
gptel-ask command so I can ask LLMs about whatever I have in my region. Might be
nice to add some more functionality similar to gptel-quick, like dwim behavior
#+begin_src emacs-lisp
(defvar gptel-ask--history nil)
(defun gptel-ask (prompt)
(interactive
(list (read-string (format "Ask %s: " (gptel-backend-name gptel-backend)) nil 'gptel-ask--history)))
(when (string= prompt "") (user-error "A prompt is required."))
(let ((region-text (buffer-substring-no-properties (region-beginning) (region-end)))
(buffer-existed (get-buffer "*gptel-ask*"))
(buffer (get-buffer-create "*gptel-ask*")))
(with-current-buffer buffer
(unless buffer-existed
(org-mode)
(let ((map (copy-keymap (current-local-map))))
(evil-define-key 'nomal map "q" 'quit-window)
(use-local-map map)))
(erase-buffer)
(insert (format "* %s\n\n" prompt)))
(pop-to-buffer buffer)
(gptel-request
(concat prompt "\n\nRegion text:\n" region-text)
:system "You are an LLM living inside of Emacs. Answer questions concisely, no flattery"
:stream t
:callback
(lambda (response info)
(cond
((not response)
(message "gptel-ask failed with message: %s" (plist-get info :status)))
((stringp response)
(with-current-buffer (get-buffer "*gptel-ask*")
(let ((inhibit-read-only t))
(goto-char (point-max))
(insert response)))))))))
#+end_src
https://github.com/karthink/gptel/issues/199
#+begin_src emacs-lisp :tangle no
(defun gptel--save-state ()
"Write the gptel state to the buffer.
This saves chat metadata when writing the buffer to disk. To
restore a chat session, turn on `gptel-mode' after opening the
file."
(pcase major-mode
('org-mode
(org-with-wide-buffer
(goto-char (point-min))
(when (org-at-heading-p)
(org-open-line 1))
(org-entry-put (point-min) "GPTEL_MODEL" gptel-model)
(org-entry-put (point-min) "GPTEL_BACKEND" (gptel-backend-name gptel-backend))
(unless (equal (default-value 'gptel-temperature) gptel-temperature)
(org-entry-put (point-min) "GPTEL_TEMPERATURE"
(number-to-string gptel-temperature)))
(unless (string= (default-value 'gptel--system-message)
gptel--system-message)
(org-entry-put (point-min) "GPTEL_SYSTEM"
gptel--system-message))
(when gptel-max-tokens
(org-entry-put
(point-min) "GPTEL_MAX_TOKENS" gptel-max-tokens))
;; Save response boundaries
(letrec ((write-bounds
(lambda (attempts)
(let* ((bounds (gptel--get-bounds))
(offset (caar bounds))
(offset-marker (set-marker (make-marker) offset)))
(org-entry-put (point-min) "GPTEL_BOUNDS"
(prin1-to-string (gptel--get-bounds)))
(when (and (not (= (marker-position offset-marker) offset))
(> attempts 0))
(funcall write-bounds (1- attempts)))))))
(funcall write-bounds 6))))
(_ (save-excursion
(save-restriction
(add-file-local-variable 'gptel-model gptel-model)
(add-file-local-variable 'gptel--backend-name
(gptel-backend-name gptel-backend))
(unless (equal (default-value 'gptel-temperature) gptel-temperature)
(add-file-local-variable 'gptel-temperature gptel-temperature))
(unless (string= (default-value 'gptel--system-message)
gptel--system-message)
(add-file-local-variable 'gptel--system-message gptel--system-message))
(when gptel-max-tokens
(add-file-local-variable 'gptel-max-tokens gptel-max-tokens))
(add-file-local-variable 'gptel--bounds (gptel--get-bounds)))))))
;; Quick helper to cat the key
(with-temp-buffer
(insert-file-contents (expand-file-name "gptel-key" user-emacs-directory))
(clipboard-kill-region (point-min) (point-max)))
#+end_src
** Programming Languages
*** COMMENT treesitter