karthink / gptel

A simple LLM client for Emacs
GNU General Public License v3.0
1.03k stars 111 forks source link

Enhance `gptel` to allow selecting from list of existing gptel buffers, when called interactively #310

Closed algal closed 1 month ago

algal commented 1 month ago

This is a feature suggestion.

I use gptel all the time so I often have a half dozen gptel buffers open at once. When I call gptel, and more than one gptel buffer exists, it switches to one based only on backend-name. I think it would be preferable if in that case it gave me a list of the existing gptel buffers so I could choose the one I want.

I've hacked this up below, and it seems to work. However, I should admit that I don't have very expert knowledge of emacs so I'm not sure if this is the best way to do it.

In particular, I'm aware that when it comes to implementing selectable lists, some ways are better than others because they fit into intended extension points. I have no idea if I've used the right way. I probably haven't, so I'm sharing this code just to give an idea of the functionality I am proposing, rather than raising a PR.

(defun gptel-selecting (name &optional _ initial interactivep)
  "Switch to or start a chat session with NAME.

With a prefix arg, query for a (new) session name.

Ask for API-KEY if `gptel-api-key' is unset.

If region is active, use it as the INITIAL prompt.  Returns the
buffer created or switched to.

INTERACTIVEP is t when gptel is called interactively."
(interactive
 (let* ((backend (default-value 'gptel-backend))
         (backend-name (format "*%s*" (gptel-backend-name backend)))
         (gptel-bufs 
          (seq-filter 
            (lambda (buf) (buffer-local-value 'gptel-mode buf)) 
             (buffer-list))))
    (list (if current-prefix-arg
              (read-string "Session name: "
                            (generate-new-buffer-name
                             backend-name))
            (if (> (length gptel-bufs) 1)
                (completing-read "Choose Gptel buffer: " (mapcar #'buffer-name gptel-bufs))
              backend-name))
          (condition-case nil
              (gptel--get-api-key
               (gptel-backend-key backend))
            ((error user-error)
             (setq gptel-api-key
                   (read-passwd
                    (format "%s API key: " backend-name)))))
          (and (use-region-p)
               (buffer-substring (region-beginning)
                                 (region-end)))
          t)))

  (with-current-buffer (get-buffer-create name)
    (cond ;Set major mode
     ((eq major-mode gptel-default-mode))
     ((eq gptel-default-mode 'text-mode)
      (text-mode)
      (visual-line-mode 1))
     (t (funcall gptel-default-mode)))
    (gptel--sanitize-model :backend (default-value 'gptel-backend)
                           :model (default-value 'gptel-model)
                           :shoosh nil)
    (unless gptel-mode (gptel-mode 1))
    (goto-char (point-max))
    (skip-chars-backward "\t\r\n")
    (if (bobp) (insert (or initial (gptel-prompt-prefix-string))))
    (when interactivep
      (display-buffer (current-buffer) gptel-display-buffer-action)
      (message "Send your query with %s!"
               (substitute-command-keys "\\[gptel-send]")))
    (current-buffer)))
algal commented 1 month ago

I updated the comment and raised a PR to make it easier to consider.

https://github.com/karthink/gptel/pull/311