branch: externals/ellama
commit f6cc81e9992d981523823d793def4bede5efe8ba
Author: Sergey Kostyaev <sskosty...@gmail.com>
Commit: Sergey Kostyaev <sskosty...@gmail.com>

    Refactor text insertion and handling in `ellama.el`
    
    Refactored the text insertion and handling logic in `ellama.el`. Introduced 
new
    functions `ellama--insert` and `ellama--handle-partial` to manage text 
streaming
    and partial responses more effectively. Updated the `ellama-stream` 
function to
    utilize these new helpers, improving modularity and maintainability.
    Additionally, enhanced reasoning handling by showing it in separate buffer.
---
 ellama.el            | 193 ++++++++++++++++++++++++++++++---------------------
 tests/test-ellama.el |   6 +-
 2 files changed, 115 insertions(+), 84 deletions(-)

diff --git a/ellama.el b/ellama.el
index 653b76cd4a..e8d4d0e0fd 100644
--- a/ellama.el
+++ b/ellama.el
@@ -5,7 +5,7 @@
 ;; Author: Sergey Kostyaev <sskosty...@gmail.com>
 ;; URL: http://github.com/s-kostyaev/ellama
 ;; Keywords: help local tools
-;; Package-Requires: ((emacs "28.1") (llm "0.22.0") (plz "0.8") (transient 
"0.7") (compat "29.1"))
+;; Package-Requires: ((emacs "28.1") (llm "0.24.0") (plz "0.8") (transient 
"0.7") (compat "29.1"))
 ;; Version: 1.5.6
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;; Created: 8th Oct 2023
@@ -1159,6 +1159,74 @@ Otherwire return current active session."
 
 (defvar ellama-global-system nil)
 
+(defvar-local ellama--stop-scroll nil)
+
+(defun ellama--insert (buffer point filter)
+  "Insert text during streaming.
+
+Works inside BUFFER starting at POINT.
+If POINT is nil, current point will be used.
+FILTER is a function for text transformation."
+  (with-current-buffer buffer
+    (let ((start (make-marker))
+         (end (make-marker))
+         (distance-to-end (- (point-max) (point))))
+      (ellama-set-markers start end (or point (point)))
+      (lambda (text)
+       ;; Erase and insert the new text between the marker cons.
+       (with-current-buffer buffer
+         ;; Manually save/restore point as save-excursion doesn't
+         ;; restore the point into the middle of replaced text.
+         (let* ((pt (point))
+                (new-distance-to-end (- (point-max) (point)))
+                (new-pt))
+           (save-excursion
+             (if (and (eq (window-buffer (selected-window))
+                          buffer)
+                      (not (equal distance-to-end new-distance-to-end)))
+                 (setq ellama--stop-scroll t)
+               (setq ellama--stop-scroll nil))
+             (goto-char start)
+             (delete-region start end)
+             (insert (funcall filter text))
+             (when (and ellama-fill-paragraphs
+                        (pcase ellama-fill-paragraphs
+                          ((cl-type function) (funcall ellama-fill-paragraphs))
+                          ((cl-type boolean) ellama-fill-paragraphs)
+                          ((cl-type list) (and (apply #'derived-mode-p
+                                                      ellama-fill-paragraphs)
+                                               (not (equal major-mode 
'org-mode))))))
+               (fill-region start (point)))
+             (setq new-pt (point)))
+           (if (and ellama-auto-scroll (not ellama--stop-scroll))
+               (ellama--scroll buffer new-pt)
+             (goto-char pt)))
+         (undo-amalgamate-change-group ellama--change-group))))))
+
+(defun ellama--handle-partial (insert-text insert-reasoning reasoning-buffer)
+  "Handle partial llm callback.
+INSERT-TEXT is a function for text insertion.
+INSERT-REASONING is a function for reasoning insertion.
+REASONING-BUFFER is a buffer for reasoning."
+  (lambda (response)
+    (let ((text (plist-get response :text))
+         (reasoning (plist-get response :reasoning)))
+      (funcall
+       insert-text
+       (concat
+       (when reasoning
+         (if
+             (or (not ellama-output-remove-reasoning)
+                 ellama--current-session)
+             (format "<think>%s</think>" reasoning)
+           (progn
+             (with-current-buffer reasoning-buffer
+               (funcall insert-reasoning reasoning)
+               (display-buffer reasoning-buffer))
+             nil)))
+       (when text
+         (string-trim text)))))))
+
 (defun ellama-stream (prompt &rest args)
   "Query ellama for PROMPT.
 ARGS contains keys for fine control.
@@ -1204,6 +1272,8 @@ failure (with BUFFER current).
                     (when (ellama-session-p session)
                       (ellama-get-session-buffer (ellama-session-id session)))
                     (current-buffer)))
+        (reasoning-buffer (get-buffer-create
+                           (concat (make-temp-name "*ellama-reasoning-") "*")))
         (point (or (plist-get args :point)
                    (with-current-buffer buffer (point))))
         (filter (or (plist-get args :filter) #'identity))
@@ -1227,93 +1297,54 @@ failure (with BUFFER current).
                               (ellama-session-prompt session))
                           (setf (ellama-session-prompt session)
                                 (llm-make-chat-prompt prompt-with-ctx :context 
system)))
-                      (llm-make-chat-prompt prompt-with-ctx :context system)))
-        (stop-scroll))
+                      (llm-make-chat-prompt prompt-with-ctx :context system))))
+    (with-current-buffer reasoning-buffer
+      (org-mode))
     (with-current-buffer buffer
       (ellama-request-mode +1)
-      (let* ((start (make-marker))
-            (end (make-marker))
-            (distance-to-end (- (point-max) (point)))
-            (new-pt)
-            (insert-text
-             (lambda (text)
-               ;; Erase and insert the new text between the marker cons.
-               (with-current-buffer buffer
-                 ;; Manually save/restore point as save-excursion doesn't
-                 ;; restore the point into the middle of replaced text.
-                 (let* ((pt (point))
-                        (new-distance-to-end (- (point-max) (point))))
-                   (save-excursion
-                     (if (and (eq (window-buffer (selected-window))
-                                  buffer)
-                              (not (equal distance-to-end 
new-distance-to-end)))
-                         (setq stop-scroll t)
-                       (setq stop-scroll nil))
-                     (goto-char start)
-                     (delete-region start end)
-                     (insert (funcall filter text))
-                      (when (and ellama-fill-paragraphs
-                                (pcase ellama-fill-paragraphs
-                                  ((cl-type function) (funcall 
ellama-fill-paragraphs))
-                                  ((cl-type boolean) ellama-fill-paragraphs)
-                                  ((cl-type list) (and (apply #'derived-mode-p
-                                                              
ellama-fill-paragraphs)
-                                                       (not (equal major-mode 
'org-mode))))))
-                       (fill-region start (point)))
-                     (setq new-pt (point)))
-                   (if (and ellama-auto-scroll (not stop-scroll))
-                       (ellama--scroll buffer new-pt)
-                     (goto-char pt)))
-                 (undo-amalgamate-change-group ellama--change-group)))))
+      (let* ((insert-text
+             (ellama--insert buffer point filter))
+            (insert-reasoning
+             (ellama--insert reasoning-buffer nil 
#'ellama--translate-markdown-to-org-filter)))
        (setq ellama--change-group (prepare-change-group))
        (activate-change-group ellama--change-group)
-       (ellama-set-markers start end point)
        (when ellama-spinner-enabled
          (require 'spinner)
          (spinner-start ellama-spinner-type))
-       (let ((request (llm-chat-streaming
-                       provider
-                       llm-prompt
-                       insert-text
-                       (lambda (text)
-                         (funcall insert-text
-                                  (string-trim
-                                   (if (and ellama-output-remove-reasoning
-                                            (not session))
-                                       (ellama-remove-reasoning text)
-                                     text)))
-                         (with-current-buffer buffer
-                           (accept-change-group ellama--change-group)
-                           (when ellama-spinner-enabled
-                             (spinner-stop))
-                           (if (and (listp donecb)
-                                    (functionp (car donecb)))
-                               (mapc (lambda (fn) (funcall fn text))
-                                     donecb)
-                             (funcall donecb text))
-                           (when ellama-session-hide-org-quotes
-                             (ellama-collapse-org-quotes))
-                           (when (and ellama--current-session
-                                      ellama-session-remove-reasoning)
-                             (mapc (lambda (interaction)
-                                     (setf (llm-chat-prompt-interaction-content
-                                            interaction)
-                                           (ellama-remove-reasoning
-                                            
(llm-chat-prompt-interaction-content
-                                             interaction))))
-                                   (llm-chat-prompt-interactions
-                                    (ellama-session-prompt
-                                     ellama--current-session))))
-                           (setq ellama--current-request nil)
-                           (ellama-request-mode -1)))
-                       (lambda (_ msg)
-                         (with-current-buffer buffer
-                           (cancel-change-group ellama--change-group)
-                           (when ellama-spinner-enabled
-                             (spinner-stop))
-                           (funcall errcb msg)
-                           (setq ellama--current-request nil)
-                           (ellama-request-mode -1))))))
+       (let* ((handler (ellama--handle-partial insert-text insert-reasoning 
reasoning-buffer))
+              (request (llm-chat-streaming
+                        provider
+                        llm-prompt
+                        handler
+                        (lambda (response)
+                          (let ((text (plist-get response :text))
+                                (reasoning (plist-get response :reasoning)))
+                            (funcall handler response)
+                            (when (or ellama--current-session
+                                      (not reasoning))
+                              (kill-buffer reasoning-buffer))
+                            (with-current-buffer buffer
+                              (accept-change-group ellama--change-group)
+                              (when ellama-spinner-enabled
+                                (spinner-stop))
+                              (if (and (listp donecb)
+                                       (functionp (car donecb)))
+                                  (mapc (lambda (fn) (funcall fn text))
+                                        donecb)
+                                (funcall donecb text))
+                              (when ellama-session-hide-org-quotes
+                                (ellama-collapse-org-quotes))
+                              (setq ellama--current-request nil)
+                              (ellama-request-mode -1))))
+                        (lambda (_ msg)
+                          (with-current-buffer buffer
+                            (cancel-change-group ellama--change-group)
+                            (when ellama-spinner-enabled
+                              (spinner-stop))
+                            (funcall errcb msg)
+                            (setq ellama--current-request nil)
+                            (ellama-request-mode -1)))
+                        t)))
          (with-current-buffer buffer
            (setq ellama--current-request request)))))))
 
diff --git a/tests/test-ellama.el b/tests/test-ellama.el
index 022c0605b0..6ed47a72ed 100644
--- a/tests/test-ellama.el
+++ b/tests/test-ellama.el
@@ -41,11 +41,11 @@
     (with-temp-buffer
       (insert original)
       (cl-letf (((symbol-function 'llm-chat-streaming)
-                 (lambda (_provider prompt partial-callback response-callback 
_error-callback)
+                 (lambda (_provider prompt partial-callback response-callback 
_error-callback _multi-output)
                    (should (string-match original (llm-chat-prompt-to-text 
prompt)))
                    (cl-loop for i from 0 to (- (length improved) 1)
-                            do (funcall partial-callback (substring improved 0 
i)))
-                   (funcall response-callback improved))))
+                            do (funcall partial-callback `(:text ,(substring 
improved 0 i))))
+                   (funcall response-callback `(:text ,improved)))))
         (ellama-code-improve)
         (should (equal original (buffer-string)))))))
 

Reply via email to