gptel: Add response regeneration, history and ediff
* gptel.el (gptel--attach-response-history, gptel--ediff, gptel--next-variant, gptel--previous-variant, gptel--mark-response): Add `gptel--attach-response-history` -- this can be used to add text properties to the next gptel response in the buffer. This is (currently) useful for tracking changes when the response overwrites existing text. The next three commands -- `gptel--ediff`, `gptel--previous-variant`, `gptel--next-variant` -- provide facilities for manipulating a gptel response at point when there is history. `gptel--mark-response` marks the response at point. These are considered internal functions for now and can be accessed from the transient menu, where they work together with `gptel--regenerate`. The input arguments to these commands are expected to change to support copilot-style functionality in the near future. * gptel-transient.el (gptel-menu, gptel--suffix-send, gptel--regenerate): Change the transient menu layout to be more compact (with a newly added column.) When overwriting the prompt with a response, save the prompt to the gptel response's history. Add `gptel--regenerate` to regenerate a response. This is accessible from the transient menu when the point is inside response text.
This commit is contained in:
parent
49cfc78378
commit
bf994c0765
2 changed files with 159 additions and 7 deletions
|
@ -126,11 +126,12 @@ which see."
|
||||||
(gptel--infix-max-tokens)
|
(gptel--infix-max-tokens)
|
||||||
(gptel--infix-num-messages-to-send)
|
(gptel--infix-num-messages-to-send)
|
||||||
(gptel--infix-temperature)]
|
(gptel--infix-temperature)]
|
||||||
["Prompt:"
|
["Prompt from"
|
||||||
("p" "From minibuffer instead" "p")
|
("p" "Minibuffer instead" "p")
|
||||||
("y" "From kill-ring instead" "y")
|
("y" "Kill-ring instead" "y")
|
||||||
("i" "Replace/Delete prompt" "i")
|
""
|
||||||
"Response to:"
|
("i" "Replace/Delete prompt" "i")]
|
||||||
|
["Response to"
|
||||||
("m" "Minibuffer instead" "m")
|
("m" "Minibuffer instead" "m")
|
||||||
("g" "gptel session" "g"
|
("g" "gptel session" "g"
|
||||||
:class transient-option
|
:class transient-option
|
||||||
|
@ -151,7 +152,10 @@ which see."
|
||||||
:reader
|
:reader
|
||||||
(lambda (prompt _ _history)
|
(lambda (prompt _ _history)
|
||||||
(read-buffer prompt (buffer-name (other-buffer)) nil)))
|
(read-buffer prompt (buffer-name (other-buffer)) nil)))
|
||||||
("k" "Kill-ring" "k")]
|
("k" "Kill-ring" "k")]]
|
||||||
|
[["Send"
|
||||||
|
(gptel--suffix-send)
|
||||||
|
("M-RET" "Regenerate" gptel--regenerate :if gptel--in-response-p)]
|
||||||
[:description gptel--refactor-or-rewrite
|
[:description gptel--refactor-or-rewrite
|
||||||
:if use-region-p
|
:if use-region-p
|
||||||
("r"
|
("r"
|
||||||
|
@ -161,7 +165,16 @@ which see."
|
||||||
(lambda () (if (derived-mode-p 'prog-mode)
|
(lambda () (if (derived-mode-p 'prog-mode)
|
||||||
"Refactor" "Rewrite"))
|
"Refactor" "Rewrite"))
|
||||||
gptel-rewrite-menu)]
|
gptel-rewrite-menu)]
|
||||||
["Send" (gptel--suffix-send)]]
|
["Tweak Response" :if gptel--in-response-p :pad-keys t
|
||||||
|
("SPC" "Mark" gptel--mark-response)
|
||||||
|
("P" "Previous variant" gptel--previous-variant
|
||||||
|
:if gptel--at-response-history-p
|
||||||
|
:transient t)
|
||||||
|
("N" "Next variant" gptel--previous-variant
|
||||||
|
:if gptel--at-response-history-p
|
||||||
|
:transient t)
|
||||||
|
("E" "Ediff previous" gptel--ediff
|
||||||
|
:if gptel--at-response-history-p)]]
|
||||||
(interactive)
|
(interactive)
|
||||||
(gptel--sanitize-model)
|
(gptel--sanitize-model)
|
||||||
(transient-setup 'gptel-menu))
|
(transient-setup 'gptel-menu))
|
||||||
|
@ -510,6 +523,10 @@ responses."
|
||||||
t))
|
t))
|
||||||
(point))))
|
(point))))
|
||||||
(end (if (use-region-p) (region-end) (point))))
|
(end (if (use-region-p) (region-end) (point))))
|
||||||
|
(unless output-to-other-buffer-p
|
||||||
|
;; store the killed text in gptel-history
|
||||||
|
(gptel--attach-response-history
|
||||||
|
(list (buffer-substring-no-properties beg end))))
|
||||||
(kill-region beg end)))
|
(kill-region beg end)))
|
||||||
|
|
||||||
(gptel-request
|
(gptel-request
|
||||||
|
@ -527,6 +544,30 @@ responses."
|
||||||
display-buffer-pop-up-window)
|
display-buffer-pop-up-window)
|
||||||
(reusable-frames . visible))))))
|
(reusable-frames . visible))))))
|
||||||
|
|
||||||
|
;; ** Suffix to regenerate response
|
||||||
|
|
||||||
|
(defun gptel--regenerate ()
|
||||||
|
"Regenerate gptel response at point."
|
||||||
|
(interactive)
|
||||||
|
(when (gptel--in-response-p)
|
||||||
|
(pcase-let* ((`(,beg . ,end) (gptel--get-bounds))
|
||||||
|
(history (get-char-property (point) 'gptel-history))
|
||||||
|
(prev-responses (cons (buffer-substring-no-properties beg end)
|
||||||
|
history)))
|
||||||
|
(when gptel-mode ;Remove prefix/suffix
|
||||||
|
(save-excursion
|
||||||
|
(goto-char beg)
|
||||||
|
(when (looking-back (concat "\n+" (regexp-quote (gptel-response-prefix-string)))
|
||||||
|
(point-min) 'greedy)
|
||||||
|
(setq beg (match-beginning 0)))
|
||||||
|
(goto-char end)
|
||||||
|
(when (looking-at
|
||||||
|
(concat "\n+" (regexp-quote (gptel-prompt-prefix-string))))
|
||||||
|
(setq end (match-end 0)))))
|
||||||
|
(delete-region beg end)
|
||||||
|
(gptel--attach-response-history prev-responses)
|
||||||
|
(call-interactively #'gptel--suffix-send))))
|
||||||
|
|
||||||
;; ** Set system message
|
;; ** Set system message
|
||||||
(defun gptel--read-crowdsourced-prompt ()
|
(defun gptel--read-crowdsourced-prompt ()
|
||||||
"Pick a crowdsourced system prompt for gptel.
|
"Pick a crowdsourced system prompt for gptel.
|
||||||
|
|
111
gptel.el
111
gptel.el
|
@ -1352,5 +1352,116 @@ text stream."
|
||||||
(prog1 (buffer-substring (point) (point-max))
|
(prog1 (buffer-substring (point) (point-max))
|
||||||
(set-marker start-pt (point-max)))))))))
|
(set-marker start-pt (point-max)))))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Response tweaking commands
|
||||||
|
|
||||||
|
(defun gptel--attach-response-history (history &optional buf)
|
||||||
|
"Attach HISTORY to the next gptel response in buffer BUF.
|
||||||
|
|
||||||
|
HISTORY is a list of strings typically containing text replaced
|
||||||
|
by gptel. BUF is the current buffer if not specified.
|
||||||
|
|
||||||
|
This is used to maintain variants of prompts or responses to diff
|
||||||
|
against if required."
|
||||||
|
(with-current-buffer (or buf (current-buffer))
|
||||||
|
(letrec ((gptel--attach-after
|
||||||
|
(lambda (b e)
|
||||||
|
(put-text-property b e 'gptel-history
|
||||||
|
(append (ensure-list history)
|
||||||
|
(get-char-property (1- e) 'gptel-history)))
|
||||||
|
(remove-hook 'gptel-post-response-functions
|
||||||
|
gptel--attach-after 'local))))
|
||||||
|
(add-hook 'gptel-post-response-functions gptel--attach-after
|
||||||
|
nil 'local))))
|
||||||
|
|
||||||
|
(defun gptel--ediff (&optional arg bounds-func)
|
||||||
|
"Ediff response at point against previous gptel responses.
|
||||||
|
|
||||||
|
If prefix ARG is non-nil, select the previous response to ediff
|
||||||
|
against interactively.
|
||||||
|
|
||||||
|
If specified, use BOUNDS-FUNC to compute the bounds of the
|
||||||
|
response at point. This can be used to include additional
|
||||||
|
context for the ediff session."
|
||||||
|
(interactive "P")
|
||||||
|
(when (gptel--at-response-history-p)
|
||||||
|
(pcase-let* ((`(,beg . ,end) (funcall (or bounds-func #'gptel--get-bounds)))
|
||||||
|
(prev-response
|
||||||
|
(if arg
|
||||||
|
(completing-read "Choose response variant to diff against: "
|
||||||
|
(get-char-property (point) 'gptel-history)
|
||||||
|
nil t)
|
||||||
|
(car-safe (get-char-property (point) 'gptel-history))))
|
||||||
|
(buffer-mode major-mode)
|
||||||
|
(bufname (buffer-name))
|
||||||
|
(`(,new-buf ,new-beg ,new-end)
|
||||||
|
(with-current-buffer
|
||||||
|
(get-buffer-create (concat bufname "-PREVIOUS-*"))
|
||||||
|
(let ((inhibit-read-only t))
|
||||||
|
(erase-buffer)
|
||||||
|
(delay-mode-hooks (funcall buffer-mode))
|
||||||
|
(insert prev-response)
|
||||||
|
(goto-char (point-min))
|
||||||
|
(list (current-buffer) (point-min) (point-max))))))
|
||||||
|
(unless prev-response (user-error "gptel response is additive: no changes to ediff"))
|
||||||
|
(require 'ediff)
|
||||||
|
(letrec ((cwc (current-window-configuration))
|
||||||
|
(gptel--ediff-restore
|
||||||
|
(lambda ()
|
||||||
|
(when (window-configuration-p cwc)
|
||||||
|
(set-window-configuration cwc))
|
||||||
|
(kill-buffer (get-buffer (concat bufname "-PREVIOUS-*")))
|
||||||
|
(kill-buffer (get-buffer (concat bufname "-CURRENT-*")))
|
||||||
|
(remove-hook 'ediff-quit-hook gptel--ediff-restore))))
|
||||||
|
(add-hook 'ediff-quit-hook gptel--ediff-restore)
|
||||||
|
(apply
|
||||||
|
#'ediff-regions-internal
|
||||||
|
(get-buffer (ediff-make-cloned-buffer (current-buffer) "-CURRENT-*"))
|
||||||
|
beg end new-buf new-beg new-end
|
||||||
|
nil
|
||||||
|
(list 'ediff-regions-wordwise 'word-wise nil)
|
||||||
|
;; (if (transient-arg-value "-w" args)
|
||||||
|
;; (list 'ediff-regions-wordwise 'word-wise nil)
|
||||||
|
;; (list 'ediff-regions-linewise nil nil))
|
||||||
|
)))))
|
||||||
|
|
||||||
|
(defun gptel--mark-response ()
|
||||||
|
"Mark gptel response at point, if any."
|
||||||
|
(interactive)
|
||||||
|
(unless (gptel--in-response-p) (user-error "No gptel response at point"))
|
||||||
|
(pcase-let* ((`(,beg . ,end) (gptel--get-bounds)))
|
||||||
|
(goto-char beg) (push-mark) (goto-char end) (activate-mark)))
|
||||||
|
|
||||||
|
(defun gptel--previous-variant (&optional arg)
|
||||||
|
"Switch to previous gptel-response at this point, if it exists."
|
||||||
|
(interactive "p")
|
||||||
|
(pcase-let* ((`(,beg . ,end) (gptel--get-bounds))
|
||||||
|
(history (get-char-property (point) 'gptel-history))
|
||||||
|
(alt-response (car-safe history))
|
||||||
|
(offset))
|
||||||
|
(unless (and history alt-response)
|
||||||
|
(user-error "No variant responses available"))
|
||||||
|
(if (> arg 0)
|
||||||
|
(setq history (append (cdr history)
|
||||||
|
(list (buffer-substring-no-properties beg end))))
|
||||||
|
(setq
|
||||||
|
alt-response (car (last history))
|
||||||
|
history (cons (buffer-substring-no-properties beg end)
|
||||||
|
(nbutlast history))))
|
||||||
|
(add-text-properties
|
||||||
|
0 (length alt-response)
|
||||||
|
`(gptel response rear-nonsticky t gptel-history ,history)
|
||||||
|
alt-response)
|
||||||
|
(setq offset (min (- (point) beg) (1- (length alt-response))))
|
||||||
|
(delete-region beg end)
|
||||||
|
(insert alt-response)
|
||||||
|
(goto-char (+ beg offset))
|
||||||
|
(pulse-momentary-highlight-region beg (+ beg (length alt-response)))))
|
||||||
|
|
||||||
|
(defun gptel--next-variant (&optional arg)
|
||||||
|
"Switch to next gptel-response at this point, if it exists."
|
||||||
|
(interactive "p")
|
||||||
|
(gptel--previous-variant (- arg)))
|
||||||
|
|
||||||
(provide 'gptel)
|
(provide 'gptel)
|
||||||
;;; gptel.el ends here
|
;;; gptel.el ends here
|
||||||
|
|
Loading…
Add table
Reference in a new issue