From 40be300bf48309bafd3eb339ebb3d51de4575703 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Tue, 24 Jun 2025 18:56:59 +0700 Subject: [PATCH] emacs: gptel enhancements including gptel-ask and keybindings --- .config/emacs/init.org | 135 +++++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 58 deletions(-) diff --git a/.config/emacs/init.org b/.config/emacs/init.org index 9f5cea2..9aa3d1a 100644 --- a/.config/emacs/init.org +++ b/.config/emacs/init.org @@ -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 ") #'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