From 39376aa3f42436711c717fba647793754dfe7e6b Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur Date: Thu, 9 Mar 2023 16:56:26 -0800 Subject: [PATCH] gptel-transient: Add transient menus for setting parameters gptel-transient (gptel-send-menu): Add new transient-prefix `gptel-send-menu' for setting API options for the buffer and (optionally) sending queries. Various infix options are supported. 2023-03-09: Some transient menus are generated dynamically, which requires a more recent version of transient than the latest tagged release. As a result, the dynamic generation code is commented out for now. --- gptel-transient.el | 208 +++++++++++++++++++++++++++++++++++++++++++++ gptel.el | 4 +- 2 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 gptel-transient.el diff --git a/gptel-transient.el b/gptel-transient.el new file mode 100644 index 0000000..f841820 --- /dev/null +++ b/gptel-transient.el @@ -0,0 +1,208 @@ +;;; gptel-transient.el --- Transient menu for GPTel -*- lexical-binding: t; -*- + +;; Copyright (C) 2023 Karthik Chikmagalur + +;; Author: Karthik Chikmagalur +;; 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 . + +;;; Commentary: + +;; + +;;; Code: +(require 'gptel) +(require 'transient) + +;;;###autoload +(transient-define-prefix gptel-send-menu () + "Change parameters of prompt to send ChatGPT." + [: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)] + [["Parameters" + (gptel--infix-max-tokens) + (gptel--infix-num-messages-to-send) + (gptel--infix-temperature) + (gptel--infix-model)] + ["Send" + (gptel--suffix-send-existing) + (gptel--suffix-send-new) + ("RET" "Send prompt" gptel-send)]]) + +(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. + +Customize `gptel--system-message-alist' for task-specific +prompts." + [:description + (lambda () (format "Directive: %s" + (truncate-string-to-width gptel--system-message (max (- (window-width) 14) 20) nil nil t))) + :class transient-column + :pad-keys t + (gptel--suffix-system-message) + ("p" "Programming" + (lambda () (interactive) + (setq gptel--system-message + (alist-get 'programming gptel--system-message-alist))) + :transient t) + ("d" "Default" + (lambda () (interactive) + (setq gptel--system-message + (alist-get 'default gptel--system-message-alist))) + :transient t) + ("w" "Writing" + (lambda () (interactive) + (setq gptel--system-message + (alist-get 'writing gptel--system-message-alist))) + :transient t) + ("c" "Chat" + (lambda () (interactive) + (setq gptel--system-message + (alist-get 'chat gptel--system-message-alist))) + :transient t)]) + +;; 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 +;; (cl-loop for (type . prompt) in gptel--system-message-alist +;; 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)))) + +(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" + :prompt "Number of past messages to include (leave empty for all): " + :reader 'transient-read-number-N+) + +(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 + :variable 'gptel--max-tokens + :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 + :variable 'gptel--model + :key "M" + :choices '("gpt-3.5-turbo-0301" "gpt-3.5-turbo") + :reader (lambda (prompt &rest _) + (completing-read + prompt + '("gpt-3.5-turbo-0301" "gpt-3.5-turbo")))) + +(transient-define-infix gptel--infix-temperature () + "Temperature of request." + :description "Randomness (0 - 2.0)" + :class 'transient-lisp-variable + :variable 'gptel--temperature + :key "t" + :reader '(lambda (_ initial history) + (read-from-minibuffer "Set temperature (0.0-2.0, leave empty for default): " + (number-to-string gptel--temperature)))) + +(transient-define-suffix gptel--suffix-system-message () + "Set Establishing message sent to ChatGPT." + :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)) + (goto-char (line-beginning-position)) + (push-mark) + (goto-char (line-end-position)) + (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))) + (call-interactively #'gptel-send-menu)))))) + +(provide 'gptel-transient) +;;; gptel-transient.el ends here diff --git a/gptel.el b/gptel.el index b5d7310..59ea3e9 100644 --- a/gptel.el +++ b/gptel.el @@ -4,7 +4,7 @@ ;; Author: Karthik Chikmagalur ;; Version: 0.10 -;; Package-Requires: ((emacs "27.1") (aio "1.0")) +;; Package-Requires: ((emacs "27.1") (aio "1.0") (transient "0.3.7")) ;; Keywords: convenience ;; URL: https://github.com/karthink/gptel @@ -126,7 +126,7 @@ return the transformed string." (aio-defun gptel-send (&optional arg) "Submit this prompt to ChatGPT." (interactive "P") - (if (and arg (featurep 'gptel-transient)) + (if (and arg (require 'gptel-transient nil t)) (call-interactively #'gptel-send-menu) (message "Querying ChatGPT...") (and header-line-format