2023-03-09 16:56:26 -08:00
|
|
|
;;; gptel-transient.el --- Transient menu for GPTel -*- lexical-binding: t; -*-
|
|
|
|
|
|
|
|
;; Copyright (C) 2023 Karthik Chikmagalur
|
|
|
|
|
|
|
|
;; Author: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
|
|
|
|
;; Keywords: convenience
|
|
|
|
|
|
|
|
;; SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
;;
|
|
|
|
|
|
|
|
;;; Code:
|
2023-03-09 22:58:54 -08:00
|
|
|
(eval-when-compile (require 'cl-lib))
|
2023-03-09 16:56:26 -08:00
|
|
|
(require 'gptel)
|
|
|
|
(require 'transient)
|
|
|
|
|
2023-04-09 03:34:16 -07:00
|
|
|
(declare-function ediff-regions-internal "ediff")
|
|
|
|
(declare-function ediff-make-cloned-buffer "ediff-utils")
|
|
|
|
|
|
|
|
;; * Helper functions
|
|
|
|
(defun gptel--refactor-or-rewrite ()
|
|
|
|
"Rewrite should be refactored into refactor.
|
|
|
|
|
|
|
|
Or is it the other way around?"
|
|
|
|
(if (derived-mode-p 'prog-mode)
|
|
|
|
"Refactor" "Rewrite"))
|
|
|
|
|
|
|
|
(defvar-local gptel--rewrite-message nil)
|
|
|
|
(defun gptel--rewrite-message ()
|
|
|
|
"Set a generic refactor/rewrite message for the buffer."
|
|
|
|
(if (derived-mode-p 'prog-mode)
|
|
|
|
(format "You are a %s programmer. Refactor the following code. Generate only code, no explanation."
|
|
|
|
(substring (symbol-name major-mode) nil -5))
|
|
|
|
(format "You are a prose editor. Rewrite the following text to be more professional.")))
|
|
|
|
|
|
|
|
;; * Transient Prefixes
|
|
|
|
|
|
|
|
(define-obsolete-function-alias 'gptel-send-menu 'gptel-menu "0.3.2")
|
|
|
|
|
2023-04-09 04:19:02 -07:00
|
|
|
;; BUG: The `:incompatible' spec doesn't work if there's a `:description' below it.
|
2023-04-09 03:34:16 -07:00
|
|
|
;;;###autoload (autoload 'gptel-menu "gptel-transient" nil t)
|
|
|
|
(transient-define-prefix gptel-menu ()
|
2023-04-09 04:19:02 -07:00
|
|
|
"Change parameters of prompt to send ChatGPT."
|
|
|
|
;; :incompatible '(("-m" "-n" "-k" "-e"))
|
|
|
|
[:description
|
|
|
|
(lambda () (format "Directive: %s"
|
|
|
|
(truncate-string-to-width
|
|
|
|
gptel--system-message (max (- (window-width) 14) 20) nil nil t)))
|
|
|
|
("h" "Set directives for chat" gptel-system-prompt)]
|
|
|
|
[["Session Parameters"
|
|
|
|
(gptel--infix-max-tokens)
|
|
|
|
(gptel--infix-num-messages-to-send)
|
|
|
|
(gptel--infix-temperature)
|
|
|
|
(gptel--infix-model)]
|
|
|
|
["Prompt:"
|
|
|
|
("-r" "From minibuffer instead" "-r")
|
|
|
|
("-i" "Overwrite/Delete prompt" "-i")
|
|
|
|
"Response to:"
|
|
|
|
("-m" "Minibuffer instead" "-m")
|
|
|
|
("-n" "New session" "-n"
|
|
|
|
:class transient-option
|
|
|
|
:prompt "Name for new session: "
|
|
|
|
:reader
|
|
|
|
(lambda (prompt _ history)
|
|
|
|
(read-string
|
|
|
|
prompt (generate-new-buffer-name "*ChatGPT*") history)))
|
|
|
|
("-e" "Existing session" "-e"
|
|
|
|
:class transient-option
|
|
|
|
:prompt "Existing session: "
|
|
|
|
:reader
|
|
|
|
(lambda (prompt _ history)
|
|
|
|
(completing-read
|
|
|
|
prompt (mapcar #'buffer-name (buffer-list))
|
|
|
|
(lambda (buf) (and (buffer-local-value 'gptel-mode (get-buffer buf))
|
|
|
|
(not (equal (current-buffer) buf))))
|
|
|
|
t nil history)))
|
|
|
|
("-k" "Kill-ring" "-k")]
|
2023-04-09 04:12:13 -07:00
|
|
|
[:description gptel--refactor-or-rewrite
|
|
|
|
:if use-region-p
|
|
|
|
("r"
|
|
|
|
;;FIXME: Transient complains if I use `gptel--refactor-or-rewrite' here. It
|
|
|
|
;;reads this function as a suffix instead of a function that returns the
|
|
|
|
;;description.
|
|
|
|
(lambda () (if (derived-mode-p 'prog-mode)
|
|
|
|
"Refactor" "Rewrite"))
|
|
|
|
gptel-rewrite-menu)]
|
|
|
|
["Send" (gptel--suffix-send)]])
|
|
|
|
|
2023-03-09 16:56:26 -08:00
|
|
|
|
2023-04-09 04:16:09 -07:00
|
|
|
;; ** Prefix for setting the system prompt.
|
|
|
|
|
2023-03-09 16:56:26 -08:00
|
|
|
(transient-define-prefix gptel-system-prompt ()
|
|
|
|
"Change the system prompt to send ChatGPT.
|
|
|
|
|
|
|
|
The \"system\" prompt establishes directives for the chat
|
|
|
|
session. Some examples of system prompts are:
|
|
|
|
|
|
|
|
You are a helpful assistant. Answer as concisely as possible.
|
|
|
|
Reply only with shell commands and no prose.
|
|
|
|
You are a poet. Reply only in verse.
|
|
|
|
|
2023-03-24 17:30:02 -07:00
|
|
|
Customize `gptel-directives' for task-specific prompts."
|
2023-03-09 16:56:26 -08:00
|
|
|
[:description
|
|
|
|
(lambda () (format "Directive: %s"
|
2023-04-09 04:22:11 -07:00
|
|
|
(truncate-string-to-width
|
|
|
|
gptel--system-message
|
|
|
|
(max (- (window-width) 14) 20) nil nil t)))
|
2023-03-09 16:56:26 -08:00
|
|
|
:class transient-column
|
|
|
|
:pad-keys t
|
|
|
|
(gptel--suffix-system-message)
|
|
|
|
("p" "Programming"
|
|
|
|
(lambda () (interactive)
|
|
|
|
(setq gptel--system-message
|
2023-03-24 17:30:02 -07:00
|
|
|
(alist-get 'programming gptel-directives)))
|
2023-03-09 16:56:26 -08:00
|
|
|
:transient t)
|
|
|
|
("d" "Default"
|
|
|
|
(lambda () (interactive)
|
|
|
|
(setq gptel--system-message
|
2023-03-24 17:30:02 -07:00
|
|
|
(alist-get 'default gptel-directives)))
|
2023-03-09 16:56:26 -08:00
|
|
|
:transient t)
|
|
|
|
("w" "Writing"
|
|
|
|
(lambda () (interactive)
|
|
|
|
(setq gptel--system-message
|
2023-03-24 17:30:02 -07:00
|
|
|
(alist-get 'writing gptel-directives)))
|
2023-03-09 16:56:26 -08:00
|
|
|
:transient t)
|
|
|
|
("c" "Chat"
|
|
|
|
(lambda () (interactive)
|
|
|
|
(setq gptel--system-message
|
2023-03-24 17:30:02 -07:00
|
|
|
(alist-get 'chat gptel-directives)))
|
2023-03-09 16:56:26 -08:00
|
|
|
:transient t)])
|
|
|
|
|
2023-04-09 04:16:09 -07:00
|
|
|
;; ** Prefix for rewriting/refactoring
|
|
|
|
|
2023-04-09 04:12:13 -07:00
|
|
|
(transient-define-prefix gptel-rewrite-menu ()
|
|
|
|
"Rewrite or refactor text region using ChatGPT."
|
|
|
|
[:description
|
|
|
|
(lambda ()
|
|
|
|
(format "Directive: %s"
|
|
|
|
(truncate-string-to-width
|
|
|
|
(or gptel--rewrite-message (gptel--rewrite-message))
|
|
|
|
(max (- (window-width) 14) 20) nil nil t)))
|
|
|
|
(gptel--infix-rewrite-prompt)]
|
|
|
|
[[:description "Diff Options"
|
|
|
|
("-w" "Wordwise diff" "-w")]
|
|
|
|
[:description
|
|
|
|
(lambda () (if (derived-mode-p 'prog-mode)
|
|
|
|
"Refactor" "Rewrite"))
|
|
|
|
(gptel--suffix-rewrite)
|
|
|
|
(gptel--suffix-rewrite-and-replace)
|
|
|
|
(gptel--suffix-rewrite-and-ediff)]]
|
|
|
|
(interactive)
|
|
|
|
(unless gptel--rewrite-message
|
|
|
|
(setq gptel--rewrite-message (gptel--rewrite-message)))
|
|
|
|
(transient-setup 'gptel-rewrite-menu))
|
2023-04-09 04:16:09 -07:00
|
|
|
|
2023-03-09 16:56:26 -08:00
|
|
|
;; TODO: Switch to dynamic Transient menus (below) once there's a new Transient release
|
|
|
|
;; (transient-define-prefix gptel-system-prompt ()
|
|
|
|
;; "Change the system prompt to send ChatGPT."
|
|
|
|
;; [:description (lambda () (format "Current directive: %s"
|
|
|
|
;; (truncate-string-to-width gptel--system-message 100 nil nil t)))
|
|
|
|
;; :class transient-column
|
|
|
|
;; :setup-children gptel-system-prompt--setup
|
|
|
|
;; :pad-keys t])
|
|
|
|
|
|
|
|
;; (defun gptel-system-prompt--setup (_)
|
|
|
|
;; "Set up suffixes for system prompt."
|
|
|
|
;; (transient-parse-suffixes
|
|
|
|
;; 'gptel-system-prompt
|
2023-03-24 17:30:02 -07:00
|
|
|
;; (cl-loop for (type . prompt) in gptel-directives
|
2023-03-09 16:56:26 -08:00
|
|
|
;; for name = (symbol-name type)
|
|
|
|
;; for key = (substring name 0 1)
|
|
|
|
;; collect (list (key-description key) (capitalize name)
|
|
|
|
;; `(lambda () (interactive)
|
|
|
|
;; (message "Directive: %s" ,prompt)
|
|
|
|
;; (setq gptel--system-message ,prompt))
|
|
|
|
;; :transient t)
|
|
|
|
;; into prompt-suffixes
|
|
|
|
;; finally return (cons (list 'gptel--suffix-system-message)
|
|
|
|
;; prompt-suffixes))))
|
|
|
|
|
2023-04-09 04:16:09 -07:00
|
|
|
;; * Transient Infixes
|
|
|
|
|
|
|
|
;; ** Infixes for model parameters
|
|
|
|
|
2023-03-09 16:56:26 -08:00
|
|
|
(transient-define-infix gptel--infix-num-messages-to-send ()
|
|
|
|
"Number of recent messages to send with each exchange.
|
|
|
|
|
|
|
|
By default, the full conversation history is sent with every new
|
|
|
|
prompt. This retains the full context of the conversation, but
|
|
|
|
can be expensive in token size. Set how many recent messages to
|
|
|
|
include."
|
|
|
|
:description "Number of past messages to send"
|
|
|
|
:class 'transient-lisp-variable
|
|
|
|
:variable 'gptel--num-messages-to-send
|
|
|
|
:key "n"
|
2023-03-14 02:06:01 -07:00
|
|
|
:prompt "Number of past messages to include for context (leave empty for all): "
|
|
|
|
:reader 'transient-read-number-N0)
|
2023-03-09 16:56:26 -08:00
|
|
|
|
|
|
|
(transient-define-infix gptel--infix-max-tokens ()
|
|
|
|
"Max tokens per response.
|
|
|
|
|
|
|
|
This is roughly the number of words in the response. 100-300 is a
|
|
|
|
reasonable range for short answers, 400 or more for longer
|
|
|
|
responses.
|
|
|
|
|
|
|
|
If left unset, ChatGPT will target about 40% of the total token
|
|
|
|
count of the conversation so far in each message, so messages
|
|
|
|
will get progressively longer!"
|
|
|
|
:description "Response length (tokens)"
|
|
|
|
:class 'transient-lisp-variable
|
2023-03-24 17:30:02 -07:00
|
|
|
:variable 'gptel-max-tokens
|
2023-03-09 16:56:26 -08:00
|
|
|
:key "<"
|
|
|
|
:prompt "Response length in tokens (leave empty: default, 80-200: short, 200-500: long): "
|
|
|
|
:reader 'transient-read-number-N+)
|
|
|
|
|
|
|
|
(transient-define-infix gptel--infix-model ()
|
|
|
|
"AI Model for Chat."
|
|
|
|
:description "GPT Model: "
|
|
|
|
:class 'transient-lisp-variable
|
2023-03-24 17:30:02 -07:00
|
|
|
:variable 'gptel-model
|
2023-03-11 18:40:06 -08:00
|
|
|
:key "m"
|
2023-03-23 17:17:00 +01:00
|
|
|
:choices '("gpt-3.5-turbo-0301" "gpt-3.5-turbo" "gpt-4")
|
2023-03-09 16:56:26 -08:00
|
|
|
:reader (lambda (prompt &rest _)
|
|
|
|
(completing-read
|
|
|
|
prompt
|
2023-03-23 17:17:00 +01:00
|
|
|
'("gpt-3.5-turbo-0301" "gpt-3.5-turbo" "gpt-4"))))
|
2023-03-09 16:56:26 -08:00
|
|
|
|
|
|
|
(transient-define-infix gptel--infix-temperature ()
|
|
|
|
"Temperature of request."
|
|
|
|
:description "Randomness (0 - 2.0)"
|
|
|
|
:class 'transient-lisp-variable
|
2023-03-24 17:30:02 -07:00
|
|
|
:variable 'gptel-temperature
|
2023-03-09 16:56:26 -08:00
|
|
|
:key "t"
|
2023-03-17 23:44:17 -07:00
|
|
|
:reader (lambda (&rest _)
|
2023-03-12 14:51:07 -07:00
|
|
|
(read-from-minibuffer "Set temperature (0.0-2.0, leave empty for default): "
|
2023-03-24 17:30:02 -07:00
|
|
|
(number-to-string gptel-temperature))))
|
2023-03-09 16:56:26 -08:00
|
|
|
|
2023-04-09 04:16:09 -07:00
|
|
|
;; ** Infix for the refactor/rewrite system message
|
|
|
|
|
2023-04-09 04:12:13 -07:00
|
|
|
(transient-define-infix gptel--infix-rewrite-prompt ()
|
|
|
|
"Chat directive (system message) to use for rewriting or refactoring."
|
|
|
|
:description (lambda () (if (derived-mode-p 'prog-mode)
|
|
|
|
"Set directives for refactor"
|
|
|
|
"Set directives for rewrite"))
|
|
|
|
:format "%k %d"
|
|
|
|
:class 'transient-lisp-variable
|
|
|
|
:variable 'gptel--rewrite-message
|
|
|
|
:key "h"
|
|
|
|
:prompt "Set directive for rewrite: "
|
|
|
|
:reader (lambda (prompt _ history)
|
|
|
|
(read-string
|
|
|
|
prompt (gptel--rewrite-message) history)))
|
2023-03-09 22:58:54 -08:00
|
|
|
|
2023-04-09 04:16:09 -07:00
|
|
|
;; * Transient Suffixes
|
|
|
|
|
|
|
|
;; ** Suffix to send prompt
|
2023-04-09 04:19:02 -07:00
|
|
|
|
|
|
|
(transient-define-suffix gptel--suffix-send (args)
|
|
|
|
"Send ARGS."
|
|
|
|
:key "RET"
|
|
|
|
:description "Send prompt"
|
|
|
|
(interactive (list (transient-args transient-current-command)))
|
|
|
|
(let ((stream gptel-stream)
|
|
|
|
(in-place (and (member "-i" args) t))
|
|
|
|
(output-to-other-buffer-p)
|
|
|
|
(buffer) (position)
|
|
|
|
(callback) (buffer-name)
|
|
|
|
(prompt
|
|
|
|
(and (member "-r" args)
|
|
|
|
(read-string
|
|
|
|
"Ask ChatGPT: "
|
|
|
|
(apply #'buffer-substring-no-properties
|
|
|
|
(if (use-region-p)
|
|
|
|
(list (region-beginning) (region-end))
|
|
|
|
(list (line-beginning-position) (line-end-position))))))))
|
|
|
|
(cond
|
|
|
|
((member "-m" args)
|
|
|
|
(setq stream nil)
|
|
|
|
(setq callback
|
|
|
|
(lambda (resp info)
|
|
|
|
(if resp
|
|
|
|
(message "ChatGPT response: %s" resp)
|
|
|
|
(message "ChatGPT response error: %s" (plist-get info :status))))))
|
|
|
|
((member "-k" args)
|
|
|
|
(setq stream nil)
|
|
|
|
(setq callback
|
|
|
|
(lambda (resp info)
|
|
|
|
(if (not resp)
|
|
|
|
(message "ChatGPT response error: %s" (plist-get info :status))
|
|
|
|
(kill-new resp)
|
|
|
|
(message "ChatGPT response: copied to kill-ring.")))))
|
|
|
|
((setq buffer-name
|
|
|
|
(cl-some (lambda (s) (and (string-prefix-p "-n" s)
|
|
|
|
(substring s 2)))
|
|
|
|
args))
|
|
|
|
(setq buffer
|
|
|
|
(gptel buffer-name
|
|
|
|
(condition-case nil
|
|
|
|
(gptel--api-key)
|
|
|
|
((error user-error)
|
|
|
|
(setq gptel-api-key
|
|
|
|
(read-passwd "OpenAI API key: "))))
|
|
|
|
(or prompt
|
|
|
|
(if (use-region-p)
|
|
|
|
(buffer-substring-no-properties (region-beginning)
|
|
|
|
(region-end))
|
|
|
|
(buffer-substring-no-properties
|
|
|
|
(save-excursion
|
|
|
|
(text-property-search-backward
|
|
|
|
'gptel 'response
|
|
|
|
(when (get-char-property (max (point-min) (1- (point)))
|
|
|
|
'gptel)
|
|
|
|
t))
|
|
|
|
(point))
|
|
|
|
(point))))))
|
|
|
|
(setq position (with-current-buffer buffer (point)))
|
|
|
|
(setq output-to-other-buffer-p t))
|
|
|
|
((setq buffer-name
|
|
|
|
(cl-some (lambda (s) (and (string-prefix-p "-e" s)
|
|
|
|
(substring s 2)))
|
|
|
|
args))
|
|
|
|
(setq buffer (get-buffer buffer-name))
|
|
|
|
(setq output-to-other-buffer-p t)
|
|
|
|
(let ((reduced-prompt
|
|
|
|
(if (use-region-p)
|
|
|
|
(buffer-substring-no-properties (region-beginning)
|
|
|
|
(region-end))
|
|
|
|
(buffer-substring-no-properties
|
|
|
|
(save-excursion
|
|
|
|
(text-property-search-backward
|
|
|
|
'gptel 'response
|
|
|
|
(when (get-char-property (max (point-min) (1- (point)))
|
|
|
|
'gptel)
|
|
|
|
t))
|
|
|
|
(point))
|
|
|
|
(point)))))
|
|
|
|
(with-current-buffer buffer
|
|
|
|
(goto-char (point-max))
|
|
|
|
(insert reduced-prompt)
|
|
|
|
(setq position (point))))))
|
|
|
|
|
|
|
|
(when in-place
|
|
|
|
(setq prompt (gptel--create-prompt (point)))
|
|
|
|
(let ((beg
|
|
|
|
(if (use-region-p)
|
|
|
|
(region-beginning)
|
|
|
|
(save-excursion
|
|
|
|
(text-property-search-backward
|
|
|
|
'gptel 'response
|
|
|
|
(when (get-char-property (max (point-min) (1- (point)))
|
|
|
|
'gptel)
|
|
|
|
t))
|
|
|
|
(point))))
|
|
|
|
(end (if (use-region-p) (region-end) (point))))
|
|
|
|
(kill-region beg end)))
|
|
|
|
|
|
|
|
(gptel-request
|
|
|
|
prompt
|
|
|
|
:buffer (or buffer (current-buffer))
|
|
|
|
:position position
|
|
|
|
:in-place (and in-place (not output-to-other-buffer-p))
|
|
|
|
:stream stream
|
|
|
|
:callback callback)))
|
|
|
|
|
2023-04-09 04:16:09 -07:00
|
|
|
;; ** Set system message
|
2023-03-09 16:56:26 -08:00
|
|
|
(transient-define-suffix gptel--suffix-system-message ()
|
2023-03-09 22:58:54 -08:00
|
|
|
"Set directives sent to ChatGPT."
|
2023-03-09 16:56:26 -08:00
|
|
|
:transient nil
|
|
|
|
:description "Set custom directives"
|
|
|
|
:key "h"
|
|
|
|
(interactive)
|
|
|
|
(let ((orig-buf (current-buffer))
|
|
|
|
(msg-start (make-marker)))
|
|
|
|
(with-current-buffer (get-buffer-create "*gptel-system*")
|
|
|
|
(erase-buffer)
|
|
|
|
(text-mode)
|
|
|
|
(insert
|
|
|
|
"# Insert your system message below and press "
|
|
|
|
(propertize "C-c C-c" 'face 'help-key-binding)
|
|
|
|
" when ready.\n"
|
|
|
|
"# Example: You are a helpful assistant. Answer as concisely as possible.\n"
|
|
|
|
"# Example: Reply only with shell commands and no prose.\n"
|
|
|
|
"# Example: You are a poet. Reply only in verse.\n\n")
|
|
|
|
(set-marker msg-start (point))
|
|
|
|
(insert (buffer-local-value 'gptel--system-message orig-buf))
|
2023-03-12 14:51:07 -07:00
|
|
|
(beginning-of-line)
|
2023-03-09 16:56:26 -08:00
|
|
|
(push-mark)
|
2023-03-12 14:51:07 -07:00
|
|
|
(end-of-line)
|
2023-03-09 16:56:26 -08:00
|
|
|
(activate-mark)
|
|
|
|
(display-buffer (current-buffer)
|
|
|
|
`((display-buffer-below-selected)
|
|
|
|
(body-function . ,#'select-window)
|
|
|
|
(window-height . ,#'fit-window-to-buffer)))
|
|
|
|
(local-set-key (kbd "C-c C-c")
|
|
|
|
(lambda ()
|
|
|
|
(interactive)
|
|
|
|
(setf (buffer-local-value 'gptel--system-message orig-buf)
|
|
|
|
(buffer-substring msg-start (point-max)))
|
|
|
|
(quit-window)
|
|
|
|
(display-buffer
|
|
|
|
orig-buf
|
|
|
|
`((display-buffer-reuse-window
|
|
|
|
display-buffer-use-some-window)
|
|
|
|
(body-function . ,#'select-window)))
|
2023-04-09 03:34:16 -07:00
|
|
|
(call-interactively #'gptel-menu))))))
|
2023-03-09 16:56:26 -08:00
|
|
|
|
2023-04-09 04:16:09 -07:00
|
|
|
;; ** Suffixes for rewriting/refactoring
|
|
|
|
|
2023-04-09 04:12:13 -07:00
|
|
|
(transient-define-suffix gptel--suffix-rewrite ()
|
|
|
|
"Rewrite or refactor region contents."
|
|
|
|
:key "r"
|
|
|
|
:description #'gptel--refactor-or-rewrite
|
|
|
|
(interactive)
|
|
|
|
(let* ((prompt (buffer-substring-no-properties
|
|
|
|
(region-beginning) (region-end)))
|
|
|
|
(stream gptel-stream)
|
|
|
|
(gptel--system-message gptel--rewrite-message))
|
|
|
|
(gptel-request prompt :stream stream)))
|
|
|
|
|
|
|
|
(transient-define-suffix gptel--suffix-rewrite-and-replace ()
|
|
|
|
"Refactor region contents and replace it."
|
|
|
|
:key "R"
|
|
|
|
:description (lambda () (concat (gptel--refactor-or-rewrite) " in place"))
|
|
|
|
(interactive)
|
|
|
|
(let* ((prompt (buffer-substring-no-properties
|
|
|
|
(region-beginning) (region-end)))
|
|
|
|
(stream gptel-stream)
|
|
|
|
(gptel--system-message gptel--rewrite-message))
|
|
|
|
(kill-region (region-beginning) (region-end))
|
|
|
|
(message "Original text saved to kill-ring.")
|
|
|
|
(gptel-request prompt :stream stream :in-place t)))
|
|
|
|
|
|
|
|
(transient-define-suffix gptel--suffix-rewrite-and-ediff (args)
|
|
|
|
"Refactoring or rewrite region contents and run Ediff."
|
|
|
|
:key "E"
|
|
|
|
:description (lambda () (concat (gptel--refactor-or-rewrite) " and Ediff"))
|
|
|
|
(interactive (list (transient-args transient-current-command)))
|
|
|
|
(let* ((prompt (buffer-substring-no-properties
|
|
|
|
(region-beginning) (region-end)))
|
|
|
|
(gptel--system-message gptel--rewrite-message))
|
|
|
|
(message "Waiting for response... ")
|
|
|
|
(gptel-request
|
|
|
|
prompt
|
|
|
|
:context (cons (region-beginning) (region-end))
|
|
|
|
:callback
|
|
|
|
(lambda (response info)
|
|
|
|
(if (not response)
|
|
|
|
(message "ChatGPT response error: %s" (plist-get info :status))
|
|
|
|
(let* ((gptel-buffer (plist-get info :buffer))
|
|
|
|
(gptel-bounds (plist-get info :context))
|
|
|
|
(buffer-mode
|
|
|
|
(buffer-local-value 'major-mode gptel-buffer)))
|
|
|
|
(pcase-let ((`(,new-buf ,new-beg ,new-end)
|
|
|
|
(with-current-buffer (get-buffer-create "*gptel-rewrite-Region.B-*")
|
|
|
|
(erase-buffer)
|
|
|
|
(funcall buffer-mode)
|
|
|
|
(insert response)
|
|
|
|
(goto-char (point-min))
|
|
|
|
(list (current-buffer) (point-min) (point-max)))))
|
|
|
|
(require 'ediff)
|
|
|
|
(apply
|
|
|
|
#'ediff-regions-internal
|
|
|
|
(get-buffer (ediff-make-cloned-buffer gptel-buffer "-Region.A-"))
|
|
|
|
(car gptel-bounds) (cdr gptel-bounds)
|
|
|
|
new-buf new-beg new-end
|
|
|
|
nil
|
|
|
|
(if (transient-arg-value "-w" args)
|
|
|
|
(list 'ediff-regions-wordwise 'word-wise nil)
|
|
|
|
(list 'ediff-regions-linewise nil nil))))))))))
|
|
|
|
|
2023-03-09 16:56:26 -08:00
|
|
|
(provide 'gptel-transient)
|
|
|
|
;;; gptel-transient.el ends here
|
2023-04-09 04:16:09 -07:00
|
|
|
|
|
|
|
;; Local Variables:
|
|
|
|
;; outline-regexp: "^;; \\*+"
|
|
|
|
;; eval: (outline-minor-mode 1)
|
|
|
|
;; End:
|