gptel: Add page boundaries, restructure files

* gptel.el: Add page boundaries and reorder file.

* gptel-transient.el: Add page boundaries.
This commit is contained in:
Karthik Chikmagalur 2024-02-04 21:06:14 -08:00
parent d8c604b53b
commit 49cfc78378
2 changed files with 75 additions and 33 deletions

View file

@ -32,7 +32,9 @@
(declare-function ediff-regions-internal "ediff") (declare-function ediff-regions-internal "ediff")
(declare-function ediff-make-cloned-buffer "ediff-utils") (declare-function ediff-make-cloned-buffer "ediff-utils")
;; * Helper functions ;; * Helper functions
(defun gptel--refactor-or-rewrite () (defun gptel--refactor-or-rewrite ()
"Rewrite should be refactored into refactor. "Rewrite should be refactored into refactor.
@ -103,6 +105,7 @@ which see."
(forward-line 1))))) (forward-line 1)))))
gptel--crowdsourced-prompts)) gptel--crowdsourced-prompts))
;; * Transient Prefixes ;; * Transient Prefixes
(define-obsolete-function-alias 'gptel-send-menu 'gptel-menu "0.3.2") (define-obsolete-function-alias 'gptel-send-menu 'gptel-menu "0.3.2")
@ -254,6 +257,7 @@ Customize `gptel-directives' for task-specific prompts."
(setq gptel--rewrite-message (gptel--rewrite-message))) (setq gptel--rewrite-message (gptel--rewrite-message)))
(transient-setup 'gptel-rewrite-menu)) (transient-setup 'gptel-rewrite-menu))
;; * Transient Infixes ;; * Transient Infixes
;; ** Infixes for model parameters ;; ** Infixes for model parameters
@ -375,6 +379,7 @@ responses."
(read-string (read-string
prompt (gptel--rewrite-message) history))) prompt (gptel--rewrite-message) history)))
;; * Transient Suffixes ;; * Transient Suffixes
;; ** Suffix to send prompt ;; ** Suffix to send prompt

103
gptel.el
View file

@ -128,6 +128,9 @@
(require 'cl-generic) (require 'cl-generic)
(require 'gptel-openai) (require 'gptel-openai)
;; User options
(defgroup gptel nil (defgroup gptel nil
"Interact with LLMs from anywhere in Emacs." "Interact with LLMs from anywhere in Emacs."
:group 'hypermedia) :group 'hypermedia)
@ -464,6 +467,11 @@ which see."
(make-obsolete-variable (make-obsolete-variable
'gptel--debug 'gptel-log-level "0.6.5") 'gptel--debug 'gptel-log-level "0.6.5")
(defvar-local gptel--old-header-line nil)
;; Utility functions
(defun gptel-api-key-from-auth-source (&optional host user) (defun gptel-api-key-from-auth-source (&optional host user)
"Lookup api key in the auth source. "Lookup api key in the auth source.
By default, the LLM host for the active backend is used as HOST, By default, the LLM host for the active backend is used as HOST,
@ -539,6 +547,61 @@ Note: Changing this variable does not affect gptel\\='s behavior
in any way.") in any way.")
(put 'gptel--backend-name 'safe-local-variable #'always) (put 'gptel--backend-name 'safe-local-variable #'always)
(defun gptel--get-buffer-bounds ()
"Return the gptel response boundaries in the buffer as an alist."
(save-excursion
(save-restriction
(widen)
(goto-char (point-max))
(let ((prop) (bounds))
(while (setq prop (text-property-search-backward
'gptel 'response t))
(push (cons (prop-match-beginning prop)
(prop-match-end prop))
bounds))
bounds))))
(defun gptel--get-bounds ()
"Return the gptel response boundaries around point."
(let (prop)
(save-excursion
(when (text-property-search-backward
'gptel 'response t)
(when (setq prop (text-property-search-forward
'gptel 'response t))
(cons (prop-match-beginning prop)
(prop-match-end prop)))))))
(defun gptel--in-response-p (&optional pt)
"Check if position PT is inside a gptel response."
(get-char-property (or pt (point)) 'gptel))
(defun gptel--at-response-history-p (&optional pt)
"Check if gptel response at position PT has variants."
(get-char-property (or pt (point)) 'gptel-history))
;; Logging
(defconst gptel--log-buffer-name "*gptel-log*"
"Log buffer for gptel.")
(defun gptel--log (data &optional type no-json)
"Log DATA to `gptel--log-buffer-name'.
TYPE is a label for data being logged. DATA is assumed to be
Valid JSON unless NO-JSON is t."
(with-current-buffer (get-buffer-create gptel--log-buffer-name)
(let ((p (goto-char (point-max))))
(unless (bobp) (insert "\n"))
(insert (format "{\"gptel\": \"%s\", " (or type "none"))
(format-time-string "\"timestamp\": \"%Y-%m-%d %H:%M:%S\"}\n")
data)
(unless no-json (ignore-errors (json-pretty-print p (point)))))))
;; Saving and restoring state
(defun gptel--restore-backend (name) (defun gptel--restore-backend (name)
"Activate gptel backend with NAME in current buffer. "Activate gptel backend with NAME in current buffer.
@ -610,11 +673,11 @@ file."
;; Save response boundaries ;; Save response boundaries
(letrec ((write-bounds (letrec ((write-bounds
(lambda (attempts) (lambda (attempts)
(let* ((bounds (gptel--get-bounds)) (let* ((bounds (gptel--get-buffer-bounds))
(offset (caar bounds)) (offset (caar bounds))
(offset-marker (set-marker (make-marker) offset))) (offset-marker (set-marker (make-marker) offset)))
(org-entry-put (point-min) "GPTEL_BOUNDS" (org-entry-put (point-min) "GPTEL_BOUNDS"
(prin1-to-string (gptel--get-bounds))) (prin1-to-string (gptel--get-buffer-bounds)))
(when (and (not (= (marker-position offset-marker) offset)) (when (and (not (= (marker-position offset-marker) offset))
(> attempts 0)) (> attempts 0))
(funcall write-bounds (1- attempts))))))) (funcall write-bounds (1- attempts)))))))
@ -632,23 +695,11 @@ file."
(add-file-local-variable 'gptel--system-message gptel--system-message)) (add-file-local-variable 'gptel--system-message gptel--system-message))
(when gptel-max-tokens (when gptel-max-tokens
(add-file-local-variable 'gptel-max-tokens gptel-max-tokens)) (add-file-local-variable 'gptel-max-tokens gptel-max-tokens))
(add-file-local-variable 'gptel--bounds (gptel--get-bounds)))))))) (add-file-local-variable 'gptel--bounds (gptel--get-buffer-bounds))))))))
(defun gptel--get-bounds ()
"Return the gptel response boundaries as an alist." ;; Minor mode and UI
(save-excursion
(save-restriction
(widen)
(goto-char (point-max))
(let ((prop) (bounds))
(while (setq prop (text-property-search-backward
'gptel 'response t))
(push (cons (prop-match-beginning prop)
(prop-match-end prop))
bounds))
bounds))))
(defvar-local gptel--old-header-line nil)
;;;###autoload ;;;###autoload
(define-minor-mode gptel-mode (define-minor-mode gptel-mode
"Minor mode for interacting with LLMs." "Minor mode for interacting with LLMs."
@ -717,6 +768,8 @@ file."
(message (propertize msg 'face face)))) (message (propertize msg 'face face))))
(force-mode-line-update))) (force-mode-line-update)))
;; Send queries, handle responses
(cl-defun gptel-request (cl-defun gptel-request
(&optional prompt &key callback (&optional prompt &key callback
(buffer (current-buffer)) (buffer (current-buffer))
@ -997,22 +1050,6 @@ BACKEND is the LLM backend in use.
PROMPTS is the plist of previous user queries and LLM responses.") PROMPTS is the plist of previous user queries and LLM responses.")
(defconst gptel--log-buffer-name "*gptel-log*"
"Log buffer for gptel.")
(defun gptel--log (data &optional type no-json)
"Log DATA to `gptel--log-buffer-name'.
TYPE is a label for data being logged. DATA is assumed to be
Valid JSON unless NO-JSON is t."
(with-current-buffer (get-buffer-create gptel--log-buffer-name)
(let ((p (goto-char (point-max))))
(unless (bobp) (insert "\n"))
(insert (format "{\"gptel\": \"%s\", " (or type "none"))
(format-time-string "\"timestamp\": \"%Y-%m-%d %H:%M:%S\"}\n")
data)
(unless no-json (ignore-errors (json-pretty-print p (point)))))))
;; TODO: Use `run-hook-wrapped' with an accumulator instead to handle ;; TODO: Use `run-hook-wrapped' with an accumulator instead to handle
;; buffer-local hooks, etc. ;; buffer-local hooks, etc.
(defun gptel--transform-response (content-str buffer) (defun gptel--transform-response (content-str buffer)