branch: elpa/gptel
commit 07715ecf099092be76f797493f515e51dbea203e
Author: Karthik Chikmagalur <[email protected]>
Commit: Karthik Chikmagalur <[email protected]>
gptel: Record latest applied preset
* gptel.el (gptel--preset): Use this (pre-existing) variable to
record when a preset is applied. Previously it was only used for
preset indication in the transient menu. It is now central to
determining which preset was last applied in any buffer.
(gptel--apply-preset): Set gptel--preset to the preset being
applied, if it is provided as a symbol.
(gptel--preset-mismatch-value): New function to check if a given
value differs from a preset's value for key. This will be used to
simplify what metadata needs to be recorded when saving chat
buffers.
(gptel-with-preset): Add `gptel--preset' to the list of dynamic
bindings, because we don't want to change its persistent value
when using `gptel-with-preset' outside the scope of this macro.
* gptel-transient.el (gptel--preset): Move to gptel.el
(gptel--preset-mismatch-p): Mention new (similar) function
`gptel--preset-mismatch-value'.
---
gptel-transient.el | 11 ++++------
gptel.el | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 66 insertions(+), 9 deletions(-)
diff --git a/gptel-transient.el b/gptel-transient.el
index 881d3478d57..476f80a3aa0 100644
--- a/gptel-transient.el
+++ b/gptel-transient.el
@@ -73,13 +73,11 @@ global value."
(_ (kill-local-variable sym)
(set sym value))))
-(defvar gptel--preset nil
- "Name of last applied gptel preset.
-
-For internal use only.")
-
(defun gptel--preset-mismatch-p (name)
- "Check if gptel preset with NAME is in effect."
+ "Check if gptel preset with NAME is in effect.
+
+This is intended to be fast but imperfect. See
+`gptel--preset-mismatch-value' for more granular checking."
(let ((elm (or (gptel-get-preset name)
(gptel-get-preset (intern-soft name))))
key val)
@@ -540,7 +538,6 @@ which see."
('t "buffer-locally")
(_ "globally")))
gptel--known-presets nil t)))))
- (gptel--set-with-scope 'gptel--preset name gptel--set-buffer-locally)
(gptel--apply-preset
name (lambda (sym val)
(gptel--set-with-scope sym val gptel--set-buffer-locally)))
diff --git a/gptel.el b/gptel.el
index 3d0ae9aec21..1bd4b1b2003 100644
--- a/gptel.el
+++ b/gptel.el
@@ -330,6 +330,12 @@ configuration that might require a UI update.")
(defvar-local gptel--bounds nil)
(put 'gptel--bounds 'safe-local-variable #'always)
+(defvar gptel--preset nil
+ "Name of last applied gptel preset.
+
+For internal use only.")
+(put 'gptel--preset 'safe-local-variable #'symbolp)
+
(defvar-local gptel--tool-names nil
"Store to persist tool names to file across Emacs sessions.
@@ -2128,12 +2134,13 @@ SETTER is the function used to set the gptel options.
It must accept
two arguments, the symbol being set and the value to set it to. It
defaults to `set', and can be set to a different function to (for
example) apply the preset buffer-locally."
+ (unless setter (setq setter #'set))
(when (memq (type-of preset) '(string symbol))
(let ((spec (or (gptel-get-preset preset)
(user-error "gptel preset \"%s\": Cannot find preset"
preset))))
+ (funcall setter 'gptel--preset preset)
(setq preset spec)))
- (unless setter (setq setter #'set))
(when-let* ((func (plist-get preset :pre))) (funcall func))
(when-let* ((parents (plist-get preset :parents)))
(mapc (lambda (parent) (gptel--apply-preset parent setter)) (ensure-list
parents)))
@@ -2239,12 +2246,65 @@ NAME is the name of a preset, or a spec (plist) of the
form
(let ((syms (make-symbol "syms"))
(binds (make-symbol "binds"))
(bodyfun (make-symbol "body")))
- `(let* ((,syms (gptel--preset-syms ,name))
+ ;; Let-bind symbols that we want to modify with the presets. Also include
+ ;; `gptel--preset' in this list as we don't want to change its value
outside
+ ;; of this macro's scope.
+ `(let* ((,syms (cons 'gptel--preset (gptel--preset-syms ,name)))
(,bodyfun (lambda () (gptel--apply-preset ,name) ,@body))
(,binds nil))
(while ,syms (push (list (car ,syms) (pop ,syms)) ,binds))
(eval (list 'let (nreverse ,binds) (list 'funcall (list 'quote
,bodyfun)))))))
+(defun gptel--preset-mismatch-value (preset-spec key val)
+ "Determine if the value of KEY in PRESET-SPEC matches VAL.
+
+This is an imperfect check for whether the value corresponding to KEY (a
+keyword) in PRESET-SPEC (a plist) matches VAL. This is required
+primarily to identify which gptel variable values have changed since
+PRESET-SPEC was applied, which is relevant when writing gptel metadata
+to a chat file.
+
+See also `gptel--preset-mismatch-p'."
+ ;; In all cases, assume a mismatch if the preset's value for KEY is a
+ ;; modify-list spec, such as (:append ...)
+ ;; Mismatches may not even be well-defined/determinable in these cases.
+ (or (not preset-spec)
+ (pcase key
+ ;; special cases
+ ((or :system :system-message)
+ (let ((system (plist-get preset-spec :system)))
+ (or (and (stringp system) (not (equal system val)))
+ (functionp system)
+ (and (consp system) (keywordp (car system)))
+ (and (consp system)
+ (not (equal (car-safe (gptel--parse-directive system))
+ val))))))
+ (:backend
+ (let ((backend (plist-get preset-spec :backend)))
+ (or (and (consp backend) (keywordp (car-safe backend)))
+ (not (equal (or (and (gptel-backend-p val) (gptel-backend-name
val))
+ val)
+ (or (and (gptel-backend-p backend)
(gptel-backend-name backend))
+ backend))))))
+ ;; FIXME: We're assuming that val is a list of tool names, not tools
+ (:tools
+ (and-let* ((preset-tools (plist-get preset-spec :tools)))
+ (or (keywordp (car-safe preset-tools))
+ (cl-loop
+ for tool in preset-tools
+ for tool-name =
+ (or (and (stringp tool) tool)
+ (ignore-errors (gptel-tool-name tool)))
+ if (not (member tool-name uniq-tool-names))
+ collect tool-name into uniq-tool-names
+ finally return
+ (not (equal (sort uniq-tool-names #'string-lessp)
+ (sort (copy-sequence (ensure-list val))
#'string-lessp)))))))
+ ;; Generic case
+ (_ (let ((field-val (plist-get preset-spec key)))
+ (or (and (consp field-val) (keywordp (car field-val)))
+ (not (equal field-val val))))))))
+
;;;; Presets in-buffer UI
(defun gptel--transform-apply-preset (_fsm)
"Apply a gptel preset to the buffer depending on the prompt.