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) (push (format "-d%s" data) args)
(nreverse (cons url 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 ;;;###autoload
(defun gptel-curl-get-response (info &optional callback) (defun gptel-curl-get-response (info &optional callback)
"Retrieve response to prompt in INFO. "Retrieve response to prompt in INFO.
@ -83,7 +85,13 @@ the response is inserted into the current buffer after point."
:callback (or callback :callback (or callback
(if gptel-playback (if gptel-playback
#'gptel--insert-response-stream #'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)) info))
(if gptel-playback (if gptel-playback
(progn (set-process-sentinel process #'gptel-curl--cleanup-stream) (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)) (status-str (plist-get response :status))
(gptel-buffer (plist-get info :gptel-buffer)) (gptel-buffer (plist-get info :gptel-buffer))
(start-marker (plist-get info :insert-marker)) (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 (if content-str
(with-current-buffer gptel-buffer (with-current-buffer gptel-buffer
(save-excursion (save-excursion
@ -134,8 +143,9 @@ See `gptel--url-get-response' for details."
(set-marker-insertion-type tracking-marker t) (set-marker-insertion-type tracking-marker t)
(plist-put info :tracking-marker tracking-marker)) (plist-put info :tracking-marker tracking-marker))
(setq content-str (gptel--transform-response (when transformer
content-str gptel-buffer)) (setq content-str (funcall transformer content-str)))
(put-text-property 0 (length content-str) 'gptel 'response content-str) (put-text-property 0 (length content-str) 'gptel 'response content-str)
(goto-char tracking-marker) (goto-char tracking-marker)
(insert content-str))) (insert content-str)))

View file

@ -550,6 +550,58 @@ elements."
(insert "/"))))))) (insert "/")))))))
(buffer-string))) (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) (defun gptel--playback (buf content-str start-pt)
"Playback CONTENT-STR in BUF. "Playback CONTENT-STR in BUF.