gptel: org support for streaming WIP

* gptel.el (gptel--convert-playback-markdown->org): New converter
for markdown->org that works on text chunks while maintaining the
parse state until the text stream is finished.

* gptel-curl.el (gptel--insert-response-stream,
gptel-curl-get-response): When using `gptel-playback' and
requesting ChatGPT's responses in org-mode, run the above
converter on the received response. This works by storing the
converter and associated state as a closure in the async info
plist that is supplied along with the response, and running it
repeatedly on each chunk of text in the response stream before it
is inserted into the buffer.

FIXME: Note that `gptel-response-filter-functions' is currently
ignored if using `gptel-stream'.
This commit is contained in:
Karthik Chikmagalur 2023-04-04 22:19:37 -07:00
parent d5ad620555
commit c9795fe9e8
2 changed files with 66 additions and 4 deletions

View file

@ -59,6 +59,8 @@ PROMPTS is the data to send, TOKEN is a unique identifier."
(push (format "-d%s" data) args)
(nreverse (cons url args))))
;;TODO: The :transformer argument here is an alternate implementation of
;;`gptel-response-filter-functions'. The two need to be unified.
;;;###autoload
(defun gptel-curl-get-response (info &optional callback)
"Retrieve response to prompt in INFO.
@ -83,7 +85,13 @@ the response is inserted into the current buffer after point."
:callback (or callback
(if gptel-playback
#'gptel--insert-response-stream
#'gptel--insert-response)))
#'gptel--insert-response))
:transformer (when (or (eq gptel-default-mode 'org-mode)
(eq (buffer-local-value
'major-mode
(plist-get info :gptel-buffer))
'org-mode))
(gptel--convert-playback-markdown->org)))
info))
(if gptel-playback
(progn (set-process-sentinel process #'gptel-curl--cleanup-stream)
@ -122,7 +130,8 @@ See `gptel--url-get-response' for details."
(status-str (plist-get response :status))
(gptel-buffer (plist-get info :gptel-buffer))
(start-marker (plist-get info :insert-marker))
(tracking-marker (plist-get info :tracking-marker)))
(tracking-marker (plist-get info :tracking-marker))
(transformer (plist-get info :transformer)))
(if content-str
(with-current-buffer gptel-buffer
(save-excursion
@ -134,8 +143,9 @@ See `gptel--url-get-response' for details."
(set-marker-insertion-type tracking-marker t)
(plist-put info :tracking-marker tracking-marker))
(setq content-str (gptel--transform-response
content-str gptel-buffer))
(when transformer
(setq content-str (funcall transformer content-str)))
(put-text-property 0 (length content-str) 'gptel 'response content-str)
(goto-char tracking-marker)
(insert content-str)))

View file

@ -550,6 +550,58 @@ elements."
(insert "/")))))))
(buffer-string)))
(defun gptel--convert-playback-markdown->org ()
""
(let ((in-src-block)
(temp-buf (generate-new-buffer-name "*gptel-temp*"))
(start-pt (make-marker)))
(lambda (str)
(let ((noop-p))
(with-current-buffer (get-buffer-create temp-buf)
(save-excursion (goto-char (point-max))
(insert str))
(when (marker-position start-pt) (goto-char start-pt))
(save-excursion
(while (re-search-forward "`\\|\\*\\{1,2\\}\\|_" nil t)
(pcase (match-string 0)
("`"
(cond
((looking-at "``")
(backward-char 1)
(delete-char 3)
(if in-src-block
(progn (insert "#+end_src")
(setq in-src-block nil))
(insert "#+begin_src ")
(setq in-src-block t)))
((looking-at "`\\|$")
(setq noop-p t)
(set-marker start-pt (1- (point)))
(unless (eobp) (forward-char 1)))
((not in-src-block) (replace-match "="))))
((and "**" (guard (not in-src-block)))
(cond
((looking-at "\\*\\(?:[[:word:]]\\|\s\\)")
(delete-char 1))
((looking-back "\\(?:[[:word:]]\\|\s\\)\\*\\{2\\}"
(max (- (point) 3) (point-min)))
(backward-delete-char 1))))
((and (or "_" "*") (guard (not in-src-block)))
(when (save-match-data
(save-excursion
(backward-char 2)
(or
(looking-at
"[^[:space:][:punct:]\n]\\(?:_\\|\\*\\)\\(?:[[:space:][:punct:]]\\|$\\)")
(looking-at
"\\(?:[[:space:][:punct:]]\\)\\(?:_\\|\\*\\)\\([^[:space:][:punct:]]\\|$\\)"))))
(backward-delete-char 1)
(insert "/"))))))
(if noop-p
(buffer-substring (point) start-pt)
(prog1 (buffer-substring (point) (point-max))
(set-marker start-pt (point-max)))))))))
(defun gptel--playback (buf content-str start-pt)
"Playback CONTENT-STR in BUF.