From 8dbcbbb908a8b8da415e320a96ec8b7da0696bfc Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur Date: Sun, 3 Mar 2024 12:00:36 -0800 Subject: [PATCH] gptel-org: Move session save/restore code for Org * gptel.el (gptel--restore-backend, gptel--save-state, gptel--restore-state): Move the Org-specific code for saving and restoring state to gptel-org. * gptel-org.el (gptel-org--entry-properties, gptel-org--save-state, gptel-org--restore-state): Org-specific code for saving and restoring state using Org properties, moved from gptel-org. --- gptel-org.el | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ gptel.el | 77 ++++++++++------------------------------ 2 files changed, 117 insertions(+), 59 deletions(-) diff --git a/gptel-org.el b/gptel-org.el index 63ab4aa..29d9866 100644 --- a/gptel-org.el +++ b/gptel-org.el @@ -161,7 +161,106 @@ value of `gptel-org-branching-context', which see." ;; Create prompt the usual way (gptel--parse-buffer gptel-backend max-entries)))) + +;;; Saving and restoring state +(defun gptel-org--entry-properties (&optional pt) + "Find gptel configuration properties stored in the current heading." + (pcase-let + ((`(,system ,backend ,model ,temperature ,tokens) + (mapcar + (lambda (prop) (org-entry-get (or pt (point)) prop 'selective)) + '("GPTEL_SYSTEM" "GPTEL_BACKEND" "GPTEL_MODEL" + "GPTEL_TEMPERATURE" "GPTEL_MAX_TOKENS")))) + (when system + (setq system (string-replace "\\n" "\n" system))) + (when backend + (setq backend (alist-get backend gptel--known-backends + nil nil #'equal))) + (when temperature + (setq temperature (gptel--numberize temperature))) + (when tokens (setq tokens (gptel--numberize tokens))) + (list system backend model temperature tokens))) + ;; (pcase-let ((`(,gptel--system-message ,gptel-backend + ;; ,gptel-model ,gptel-temperature) + ;; (if (derived-mode-p 'org-mode) + ;; (progn (require 'gptel-org) + ;; (gptel-org--entry-properties)) + ;; `(,gptel--system-message ,gptel-backend + ;; ,gptel-model ,gptel-temperature))))) + +(defun gptel-org--restore-state () + "Restore gptel state for Org buffers when turning on `gptel-mode'." + (save-restriction + (widen) + (condition-case status + (progn + (when-let ((bounds (org-entry-get (point-min) "GPTEL_BOUNDS"))) + (mapc (pcase-lambda (`(,beg . ,end)) + (put-text-property beg end 'gptel 'response)) + (read bounds))) + (pcase-let ((`(,system ,backend ,model ,temperature ,tokens) + (gptel-org--entry-properties (point-min)))) + (when system (setq-local gptel--system-message system)) + (if backend (setq-local gptel-backend backend) + (message + (substitute-command-keys + (concat + "Could not activate gptel backend \"%s\"! " + "Switch backends with \\[universal-argument] \\[gptel-send]" + " before using gptel.")) + backend)) + (when model (setq-local gptel-model model)) + (when temperature (setq-local gptel-temperature temperature)) + (when tokens (setq-local gptel-max-tokens tokens)))) + (:success (message "gptel chat restored.")) + (error (message "Could not restore gptel state, sorry! Error: %s" status))))) + +(defun gptel-org-set-properties (pt &optional msg) + "Store the active gptel configuration under the current heading. + +The active gptel configuration includes the current system +message, language model and provider (backend), and additional +settings when applicable. + +PT is the cursor position by default. If MSG is +non-nil (default), display a message afterwards." + (interactive (list (point) t)) + (org-entry-put pt "GPTEL_MODEL" gptel-model) + (org-entry-put pt "GPTEL_BACKEND" (gptel-backend-name gptel-backend)) + (unless (equal (default-value 'gptel-temperature) gptel-temperature) + (org-entry-put pt "GPTEL_TEMPERATURE" + (number-to-string gptel-temperature))) + (unless (string= (default-value 'gptel--system-message) + gptel--system-message) + (org-entry-put pt "GPTEL_SYSTEM" + (string-replace "\n" "\\n" gptel--system-message))) + (when gptel-max-tokens + (org-entry-put + pt "GPTEL_MAX_TOKENS" (number-to-string gptel-max-tokens))) + (when msg + (message "Added gptel configuration to current headline."))) + +(defun gptel-org--save-state () + "Write the gptel state to the Org buffer as Org properties." + (org-with-wide-buffer + (goto-char (point-min)) + (when (org-at-heading-p) + (org-open-line 1)) + (gptel-org-set-properties (point-min)) + ;; Save response boundaries + (letrec ((write-bounds + (lambda (attempts) + (let* ((bounds (gptel--get-buffer-bounds)) + (offset (caar bounds)) + (offset-marker (set-marker (make-marker) offset))) + (org-entry-put (point-min) "GPTEL_BOUNDS" + (prin1-to-string (gptel--get-buffer-bounds))) + (when (and (not (= (marker-position offset-marker) offset)) + (> attempts 0)) + (funcall write-bounds (1- attempts))))))) + (funcall write-bounds 6)))) + (provide 'gptel-org) ;;; gptel-org.el ends here diff --git a/gptel.el b/gptel.el index 07fb917..65cda06 100644 --- a/gptel.el +++ b/gptel.el @@ -652,48 +652,33 @@ Valid JSON unless NO-JSON is t." ;; Saving and restoring state -(defun gptel--restore-backend (name) - "Activate gptel backend with NAME in current buffer. - -If no backend with this name exists, inform the user. Intended -for when gptel restores chat metadata." - (when name - (if-let ((backend (alist-get name gptel--known-backends - nil nil #'equal))) - (setq-local gptel-backend backend) - (message - (substitute-command-keys - "Could not activate gptel backend \"%s\"! Switch backends with \\[universal-argument] \\[gptel-send] before using gptel.") - name)))) +(declare-function gptel-org--restore-state "gptel-org") +(declare-function gptel-org--save-state "gptel-org") (defun gptel--restore-state () "Restore gptel state when turning on `gptel-mode'." (when (buffer-file-name) (pcase major-mode ('org-mode - (save-restriction - (widen) - (condition-case-unless-debug nil - (progn - (when-let ((bounds (org-entry-get (point-min) "GPTEL_BOUNDS"))) - (mapc (pcase-lambda (`(,beg . ,end)) - (put-text-property beg end 'gptel 'response)) - (read bounds)) - (message "gptel chat restored.")) - (when-let ((model (org-entry-get (point-min) "GPTEL_MODEL"))) - (setq-local gptel-model model)) - (gptel--restore-backend (org-entry-get (point-min) "GPTEL_BACKEND")) - (when-let ((system (org-entry-get (point-min) "GPTEL_SYSTEM"))) - (setq-local gptel--system-message (string-replace "\\n" "\n" system))) - (when-let ((temp (org-entry-get (point-min) "GPTEL_TEMPERATURE"))) - (setq-local gptel-temperature (gptel--numberize temp)))) - (error (message "Could not restore gptel state, sorry!"))))) + (require 'gptel-org) + (gptel-org--restore-state)) (_ (when gptel--bounds (mapc (pcase-lambda (`(,beg . ,end)) (put-text-property beg end 'gptel 'response)) gptel--bounds) (message "gptel chat restored.")) - (gptel--restore-backend gptel--backend-name))))) + (when gptel--backend-name + (if-let ((backend (alist-get + gptel--backend-name gptel--known-backends + nil nil #'equal))) + (setq-local gptel-backend backend) + (message + (substitute-command-keys + (concat + "Could not activate gptel backend \"%s\"! " + "Switch backends with \\[universal-argument] \\[gptel-send]" + " before using gptel.")) + gptel--backend-name))))))) (defun gptel--save-state () "Write the gptel state to the buffer. @@ -703,34 +688,8 @@ 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" - (string-replace "\n" "\\n" 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-buffer-bounds)) - (offset (caar bounds)) - (offset-marker (set-marker (make-marker) offset))) - (org-entry-put (point-min) "GPTEL_BOUNDS" - (prin1-to-string (gptel--get-buffer-bounds))) - (when (and (not (= (marker-position offset-marker) offset)) - (> attempts 0)) - (funcall write-bounds (1- attempts))))))) - (funcall write-bounds 6)))) + (require 'gptel-org) + (gptel-org--save-state)) (_ (let ((print-escape-newlines t)) (save-excursion (save-restriction