branch: elpa/gptel commit edf834a448f1ee68bbd3117145668a3401817bc3 Author: Karthik Chikmagalur <karthikchikmaga...@gmail.com> Commit: Karthik Chikmagalur <karthikchikmaga...@gmail.com>
gptel-transient: Provide description of send action (#627) * gptel-transient.el (gptel--describe-infix-context, gptel--describe-suffix-send, gptel-menu, gptel--suffix-send, gptel--suffix-context-buffer): In `gptel-menu', provide a verbose description of what the send command will do. This is intended to provide clarity on the effect of the various prompt/response redirection options gptel provides, as well as to explicitly indicate the default prompting behavior (which is to send the selected region or buffer up to point). Additional changes to `gptel-menu': - The active context is now indicated in the relevant column header. - Incompatible combinations of redirection options are now disallowed. This restriction was previously disabled due to a bug in older versions of Transient. - Use different faces to indicate active context and selected tools. - Fix alignment of the "Scope" menu item under Request Parameters. --- gptel-transient.el | 162 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 110 insertions(+), 52 deletions(-) diff --git a/gptel-transient.el b/gptel-transient.el index b3b9dffe68..ef2fcaeb44 100644 --- a/gptel-transient.el +++ b/gptel-transient.el @@ -274,6 +274,93 @@ which see." (forward-line 1))))) gptel--crowdsourced-prompts)) +(defun gptel--describe-infix-context () + (if (null gptel-context--alist) "Context" + (pcase-let* + ((contexts (gptel-context--collect)) + (buffer-count (length contexts)) + (`(,file-count ,ov-count) + (if (> buffer-count 0) + (cl-loop for (buf-file . ovs) in contexts + if (bufferp buf-file) + sum (length ovs) into ov-count + else count (stringp buf-file) into file-count + finally return (list file-count ov-count)) + (list 0 0)))) + (concat "Context (" + (propertize + (concat + (and (> ov-count 0) + (format "%d region%s in %d buffer%s" + ov-count (if (> ov-count 1) "s" "") + (- buffer-count file-count) + (if (> ( - buffer-count file-count) 1) "s" ""))) + (and (> file-count 0) + (format "%s%d file%s" + (if (> ov-count 0) ", " "") file-count + (if (> file-count 1) "s" "")))) + 'face 'warning) + ")")))) + +(defun gptel--describe-suffix-send () + "Describe the action of `gptel--suffix-send'." + (cl-flet ((ptv (s) (propertize s 'face 'warning)) + (pth (s) (propertize s 'face 'transient-heading))) + (let* ((args (or (and transient-current-command + (transient-args transient-current-command)) + ;; Not yet exported, simulate. HACK: We are accessing + ;; Transient's internal variables here for live updates. + (let* ((transient-current-command (oref transient--prefix command)) + (transient-current-suffixes transient--suffixes)) + (transient-args transient-current-command)))) + (lbeg (line-number-at-pos (if (use-region-p) (region-beginning) + (point-min)))) + (lend (line-number-at-pos (if (use-region-p) (region-end) + (point)))) + (ltext (ptv (if (> lend lbeg) + (format " (lines %d-%d)" lbeg lend) + (format " (line %d)" lbeg)))) + (dest) (context)) + (setq dest (cond + ((member "e" args) (ptv "echo area")) + ((member "k" args) (ptv "kill-ring")) + ((cl-some (lambda (s) + (and (stringp s) (memq (aref s 0) '(?g ?b)) + (not (equal (substring s 1) (buffer-name))) + (concat (pth "buffer ") (ptv (substring s 1))))) + args)))) + (setq context + (and gptel-context--alist + (let ((lc (length gptel-context--alist))) + (concat (pth " along with ") (ptv (format "%d" lc)) + (pth (concat " context source" (and (/= lc 1) "s"))))))) + (cond ((member "m" args) + (concat (pth "Read prompt from ") (ptv "minibuffer") + context + (if dest (concat (pth ", response to ") dest) + (concat (pth ", insert response at point"))))) + ((member "y" args) + (concat (pth "Send prompt from ") (ptv "kill-ring") + context + (if dest (concat (pth ", response to ") dest) + (concat (pth ", insert response at point"))))) + ((member "i" args) + (let* ((reg (use-region-p)) + (src (ptv (if reg "selection" (buffer-name))))) + (if dest (concat (pth "Send ") src ltext context (pth ", with response to ") + (ptv dest) (pth "; kill") ltext + (and (not reg) (concat (pth " in ") src))) + (concat (pth "Replace ") src ltext (pth " with response") + (and context + (concat (pth " ( with") (substring context 11) " )")))))) + ((use-region-p) + (concat (pth "Send ") (ptv "selection") ltext + context (if dest (concat (pth ", with response to ") dest) + (concat (pth ", insert response at region end"))))) + (t (concat (pth "Send ") (ptv (buffer-name)) ltext + context (if dest (concat (pth ", with response to ") dest) + (concat (pth ", insert response at point"))))))))) + ;; * Transient classes and methods for gptel @@ -450,20 +537,20 @@ Also format its value in the Transient menu." (define-obsolete-function-alias 'gptel-send-menu 'gptel-menu "0.3.2") -;; BUG: The `:incompatible' spec doesn't work if there's a `:description' below it. ;;;###autoload (autoload 'gptel-menu "gptel-transient" nil t) (transient-define-prefix gptel-menu () "Change parameters of prompt to send to the LLM." - ;; :incompatible '(("-m" "-n" "-k" "-e")) + :incompatible '(("m" "y" "i") ("e" "g" "b" "k")) + ;; :value (list (concat "b" (buffer-name))) [:description gptel-system-prompt--format ["" :if (lambda () (not (gptel--model-capable-p 'nosystem))) "Instructions" ("s" "Set system message" gptel-system-prompt :transient t) (gptel--infix-add-directive)] - [:pad-keys t - "" - "Context" + [:pad-keys t "" + (:info #'gptel--describe-infix-context + :face 'transient-heading :format "%d") (gptel--infix-context-add-region) (gptel--infix-context-add-buffer) (gptel--infix-context-add-file) @@ -472,9 +559,13 @@ Also format its value in the Transient menu." [:pad-keys t :if (lambda () (and gptel-use-tools gptel--known-tools)) "" (:info - (lambda () (concat "Tools" (and gptel-tools - (format " (%d selected)" - (length gptel-tools))))) + (lambda () + (concat + "Tools" (and gptel-tools + (concat " (" (propertize (format "%d selected" + (length gptel-tools)) + 'face 'warning) + ")")))) :format "%d" :face transient-heading) ("t" "Select tools" gptel-tools :transient t) ("T" "Continue tool calls" @@ -482,7 +573,6 @@ Also format its value in the Transient menu." :if (lambda () (and gptel--fsm-last (eq (gptel-fsm-state gptel--fsm-last) 'TOOL))))]] [["Request Parameters" - :pad-keys t (gptel--infix-variable-scope) (gptel--infix-provider) (gptel--infix-max-tokens) @@ -501,8 +591,13 @@ Also format its value in the Transient menu." ("y" "Kill-ring instead" "y") "" ("i" "Respond in place" "i")] - [" >Response to" + [" >Response to" ("e" "Echo area" "e") + ("b" "Other buffer" "b" + :class transient-option + :prompt "Output to buffer: " + :reader (lambda (prompt _ _history) + (read-buffer prompt (buffer-name (other-buffer)) nil))) ("g" "gptel session" "g" :class transient-option :prompt "Existing or new gptel session: " @@ -516,17 +611,8 @@ Also format its value in the Transient menu." (let ((buf (get-buffer buf-name))) (and (buffer-local-value 'gptel-mode buf) (not (eq (current-buffer) buf)))))))) - ("b" "Any buffer" "b" - :class transient-option - :prompt "Output to buffer: " - :reader - (lambda (prompt _ _history) - (read-buffer prompt (buffer-name (other-buffer)) nil))) ("k" "Kill-ring" "k")]] - [["Send" - (gptel--suffix-send) - ("M-RET" "Regenerate" gptel--regenerate :if gptel--in-response-p)] - [:description (lambda () (concat (and gptel--rewrite-overlays "Continue ") + [[:description (lambda () (concat (and gptel--rewrite-overlays "Continue ") "Rewrite")) :if (lambda () (or (use-region-p) (and gptel--rewrite-overlays @@ -537,6 +623,7 @@ Also format its value in the Transient menu." gptel-rewrite)] ["Tweak Response" :if gptel--in-response-p :pad-keys t ("SPC" "Mark" gptel--mark-response) + ("M-RET" "Regenerate" gptel--regenerate :if gptel--in-response-p) ("P" "Previous variant" gptel--previous-variant :if gptel--at-response-history-p :transient t) @@ -563,6 +650,7 @@ Also format its value in the Transient menu." (gptel--suffix-send (cons "I" (transient-args transient-current-command))) 'json)))]] + [(gptel--suffix-send)] (interactive) (gptel--sanitize-model) (transient-setup 'gptel-menu)) @@ -1083,7 +1171,7 @@ This sets the variable `gptel-include-tool-results', which see." (transient-define-suffix gptel--suffix-send (args) "Send ARGS." :key "RET" - :description "Send prompt" + :description #'gptel--describe-suffix-send (interactive (list (transient-args (or transient-current-command 'gptel-menu)))) (let ((stream gptel-stream) @@ -1425,37 +1513,7 @@ setting up the buffer." :transient 'transient--do-exit :key " C" :if (lambda () gptel-context--alist) - :description - (lambda () - (pcase-let* - ((contexts (and gptel-context--alist (gptel-context--collect))) - (buffer-count (length contexts)) - (`(,file-count ,ov-count) - (if (> buffer-count 0) - (cl-loop for (buf-file . ovs) in contexts - if (bufferp buf-file) - sum (length ovs) into ov-count - else count (stringp buf-file) into file-count - finally return (list file-count ov-count)) - (list 0 0)))) - (concat "Inspect " - (format - (propertize "(%s)" 'face 'transient-delimiter) - (propertize - (concat - (and (> ov-count 0) - (format "%d region%s in %d buffer%s" - ov-count (if (> ov-count 1) "s" "") - (- buffer-count file-count) - (if (> ( - buffer-count file-count) 1) "s" ""))) - (and (> file-count 0) - (propertize - (format "%s%d file%s" - (if (> ov-count 0) ", " "") file-count - (if (> file-count 1) "s" ""))))) - 'face (if (zerop (length contexts)) - 'transient-inactive-value - 'transient-value)))))) + :description "Inspect context" (interactive) (gptel-context--buffer-setup))