branch: externals/ellama
commit d8e56596803e3080c6dca12e6bd800beee00e51e
Merge: 7a714c9114 6ecde6aa8b
Author: Sergey Kostyaev <s-kosty...@users.noreply.github.com>
Commit: GitHub <nore...@github.com>

    Merge pull request #184 from s-kostyaev/send-message-from-session-buffer
    
    Add ability to send message from session buffer
---
 README.org | 111 ++++++++++++++++++++++++++++++++++---------------------------
 ellama.el  | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 156 insertions(+), 61 deletions(-)

diff --git a/README.org b/README.org
index 48c40e50ff..8665b93851 100644
--- a/README.org
+++ b/README.org
@@ -33,58 +33,65 @@ In that case you should customize ellama configuration like 
this:
 
 #+BEGIN_SRC  emacs-lisp
   (use-package ellama
-    :bind ("C-c e" . ellama-transient-main-menu)
-    :init
-    ;; setup key bindings
-    ;; (setopt ellama-keymap-prefix "C-c e")
-    ;; language you want ellama to translate to
-    (setopt ellama-language "German")
-    ;; could be llm-openai for example
-    (require 'llm-ollama)
-    (setopt ellama-provider
-           (make-llm-ollama
-            ;; this model should be pulled to use it
-            ;; value should be the same as you print in terminal during pull
-            :chat-model "llama3:8b-instruct-q8_0"
-            :embedding-model "nomic-embed-text"
-            :default-chat-non-standard-params '(("num_ctx" . 8192))))
-    (setopt ellama-summarization-provider
+      :bind ("C-c e" . ellama-transient-main-menu)
+      :init
+      ;; setup key bindings
+      ;; (setopt ellama-keymap-prefix "C-c e")
+      ;; language you want ellama to translate to
+      (setopt ellama-language "German")
+      ;; could be llm-openai for example
+      (require 'llm-ollama)
+      (setopt ellama-provider
+         (make-llm-ollama
+              ;; this model should be pulled to use it
+              ;; value should be the same as you print in terminal during pull
+              :chat-model "llama3:8b-instruct-q8_0"
+              :embedding-model "nomic-embed-text"
+              :default-chat-non-standard-params '(("num_ctx" . 8192))))
+      (setopt ellama-summarization-provider
+             (make-llm-ollama
+              :chat-model "qwen2.5:3b"
+              :embedding-model "nomic-embed-text"
+              :default-chat-non-standard-params '(("num_ctx" . 32768))))
+      (setopt ellama-coding-provider
+             (make-llm-ollama
+              :chat-model "qwen2.5-coder:3b"
+              :embedding-model "nomic-embed-text"
+              :default-chat-non-standard-params '(("num_ctx" . 32768))))
+      ;; Predefined llm providers for interactive switching.
+      ;; You shouldn't add ollama providers here - it can be selected 
interactively
+      ;; without it. It is just example.
+      (setopt ellama-providers
+             '(("zephyr" . (make-llm-ollama
+                            :chat-model "zephyr:7b-beta-q6_K"
+                            :embedding-model "zephyr:7b-beta-q6_K"))
+               ("mistral" . (make-llm-ollama
+                             :chat-model "mistral:7b-instruct-v0.2-q6_K"
+                             :embedding-model "mistral:7b-instruct-v0.2-q6_K"))
+               ("mixtral" . (make-llm-ollama
+                             :chat-model "mixtral:8x7b-instruct-v0.1-q3_K_M-4k"
+                             :embedding-model 
"mixtral:8x7b-instruct-v0.1-q3_K_M-4k"))))
+      ;; Naming new sessions with llm
+      (setopt ellama-naming-provider
+             (make-llm-ollama
+              :chat-model "llama3:8b-instruct-q8_0"
+              :embedding-model "nomic-embed-text"
+              :default-chat-non-standard-params '(("stop" . ("\n")))))
+      (setopt ellama-naming-scheme 'ellama-generate-name-by-llm)
+      ;; Translation llm provider
+      (setopt ellama-translation-provider
            (make-llm-ollama
             :chat-model "qwen2.5:3b"
             :embedding-model "nomic-embed-text"
-            :default-chat-non-standard-params '(("num_ctx" . 32768))))
-    (setopt ellama-coding-provider
-           (make-llm-ollama
-            :chat-model "qwen2.5-coder:3b"
-            :embedding-model "nomic-embed-text"
-            :default-chat-non-standard-params '(("num_ctx" . 32768))))
-    ;; Predefined llm providers for interactive switching.
-    ;; You shouldn't add ollama providers here - it can be selected 
interactively
-    ;; without it. It is just example.
-    (setopt ellama-providers
-           '(("zephyr" . (make-llm-ollama
-                          :chat-model "zephyr:7b-beta-q6_K"
-                          :embedding-model "zephyr:7b-beta-q6_K"))
-             ("mistral" . (make-llm-ollama
-                           :chat-model "mistral:7b-instruct-v0.2-q6_K"
-                           :embedding-model "mistral:7b-instruct-v0.2-q6_K"))
-             ("mixtral" . (make-llm-ollama
-                           :chat-model "mixtral:8x7b-instruct-v0.1-q3_K_M-4k"
-                           :embedding-model 
"mixtral:8x7b-instruct-v0.1-q3_K_M-4k"))))
-    ;; Naming new sessions with llm
-    (setopt ellama-naming-provider
-           (make-llm-ollama
-            :chat-model "llama3:8b-instruct-q8_0"
-            :embedding-model "nomic-embed-text"
-            :default-chat-non-standard-params '(("stop" . ("\n")))))
-    (setopt ellama-naming-scheme 'ellama-generate-name-by-llm)
-    ;; Translation llm provider
-    (setopt ellama-translation-provider
-         (make-llm-ollama
-          :chat-model "qwen2.5:3b"
-          :embedding-model "nomic-embed-text"
-          :default-chat-non-standard-params
-          '(("num_ctx" . 32768)))))
+            :default-chat-non-standard-params
+            '(("num_ctx" . 32768))))
+      ;; customize display buffer behaviour
+      ;; see ~(info "(elisp) Buffer Display Action Functions")~
+      (setopt ellama-chat-display-action-function #'display-buffer-full-frame)
+      (setopt ellama-instant-display-action-function 
#'display-buffer-at-bottom)
+      :config
+      ;; send last message in chat buffer with C-c C-c
+      (add-hook 'org-ctrl-c-ctrl-c-hook #'ellama-chat-send-last-message))
 #+END_SRC
 
 ** Commands
@@ -96,6 +103,10 @@ buffer and continue conversation. If called with universal 
argument
 (~C-u~) will start new session with llm model interactive selection.
 [[imgs/ellama-ask.gif]]
 
+*** ellama-chat-send-last-message
+
+Send last user message extracted from current ellama chat buffer.
+
 *** ellama-ask-about
 
 Ask Ellama about a selected region or the current buffer.
@@ -350,6 +361,8 @@ argument generated text string.
   ~ellama-provider~ will be used if not set.
 - ~ellama-show-quotes~: Show quotes content in chat buffer. Disabled
   by default.
+- ~ellama-chat-display-action-function~: Display action function for 
~ellama-chat~.
+- ~ellama-instant-display-action-function~: Display action function for 
~ellama-instant~.
 
 ** Acknowledgments
 
diff --git a/ellama.el b/ellama.el
index 5bd5d8ecd2..c493c274b9 100644
--- a/ellama.el
+++ b/ellama.el
@@ -386,6 +386,16 @@ Too low value can break generated code by splitting long 
comment lines."
   :group 'ellama
   :type 'boolean)
 
+(defcustom ellama-chat-display-action-function nil
+  "Display action function for `ellama-chat'."
+  :group 'ellama
+  :type 'function)
+
+(defcustom ellama-instant-display-action-function nil
+  "Display action function for `ellama-instant'."
+  :group 'ellama
+  :type 'function)
+
 (define-minor-mode ellama-session-mode
   "Minor mode for ellama session buffers."
   :interactive nil
@@ -779,6 +789,16 @@ If EPHEMERAL non nil new session will not be associated 
with any file."
        (save-buffer)
        (goto-char (point-min))))
     (with-current-buffer buffer
+      ;; support sessions without user nick at the end of buffer
+      (when (not (save-excursion
+                  (save-match-data
+                    (goto-char (point-max))
+                    (and (search-backward (concat 
(ellama-get-nick-prefix-for-mode) " " ellama-user-nick ":\n") nil t)
+                         (search-forward (concat 
(ellama-get-nick-prefix-for-mode) " " ellama-user-nick ":\n") nil t)
+                         (equal (point) (point-max))))))
+       (goto-char (point-max))
+       (insert (ellama-get-nick-prefix-for-mode) " " ellama-user-nick ":\n")
+       (save-buffer))
       (let ((session (read session-buffer)))
        (setq ellama--current-session
              (make-ellama-session
@@ -793,7 +813,8 @@ If EPHEMERAL non nil new session will not be associated 
with any file."
               buffer ellama--active-sessions)
       (ellama-session-mode +1))
     (kill-buffer session-buffer)
-    (display-buffer buffer)))
+    (display-buffer buffer (when ellama-chat-display-action-function
+                            `((ignore . 
(,ellama-chat-display-action-function)))))))
 
 ;;;###autoload
 (defun ellama-session-remove ()
@@ -832,7 +853,8 @@ If EPHEMERAL non nil new session will not be associated 
with any file."
              (hash-table-keys ellama--active-sessions)))
         (buffer (ellama-get-session-buffer id)))
     (ellama-activate-session id)
-    (display-buffer buffer)))
+    (display-buffer buffer (when ellama-chat-display-action-function
+                            `((ignore . 
(,ellama-chat-display-action-function)))))))
 
 ;;;###autoload
 (defun ellama-session-rename ()
@@ -1468,7 +1490,10 @@ last step only.
                                      (ellama-generate-name provider 
real-this-command prompt)))
                (ellama-get-session-buffer ellama--current-session-id))))
     (when show
-      (display-buffer buf))
+      (display-buffer buf (if chat (when ellama-chat-display-action-function
+                                    `((ignore . 
(,ellama-chat-display-action-function))))
+                           (when ellama-instant-display-action-function
+                             `((ignore . 
(,ellama-instant-display-action-function)))))))
     (with-current-buffer buf
       (funcall ellama-major-mode))
     (if chat
@@ -1545,12 +1570,40 @@ Extract profession from this message. Be short and 
concise."
            :transform (lambda (_ _)
                         "Provide short final answer based on final 
solution.")))))
 
+(declare-function org-export-to-buffer "ox")
+(defvar org-export-show-temporary-export-buffer)
+
+(defun ellama-convert-org-to-md (text)
+  "Translate TEXT from org syntax to markdown syntax."
+  (require 'ox)
+  (require 'ox-md)
+  (let ((buf (make-temp-name "ellama-"))
+       (org-export-show-temporary-export-buffer nil))
+    (with-temp-buffer
+      (insert "#+OPTIONS: toc:nil\n" text)
+      (org-export-to-buffer 'md buf
+       nil nil t t nil (lambda () (text-mode))))
+    (with-current-buffer buf
+      (prog1
+         (string-trim (buffer-substring-no-properties (point-min) (point-max)))
+       (kill-buffer)))))
+
+(defun ellama-get-last-user-message ()
+  "Return last not sent user message in current session buffer."
+  (when ellama--current-session
+    (save-excursion
+      (save-match-data
+       (goto-char (point-max))
+       (and (search-backward (concat (ellama-get-nick-prefix-for-mode) " " 
ellama-user-nick ":\n") nil t)
+            (search-forward (concat (ellama-get-nick-prefix-for-mode) " " 
ellama-user-nick ":\n") nil t)
+            (buffer-substring-no-properties (point) (point-max)))))))
+
 (defun ellama-chat-done (text &optional on-done)
   "Chat done.
 Will call `ellama-chat-done-callback' and ON-DONE on TEXT."
   (save-excursion
     (goto-char (point-max))
-    (insert "\n\n")
+    (insert "\n\n" (ellama-get-nick-prefix-for-mode) " " ellama-user-nick 
":\n")
     (when ellama-session-auto-save
       (save-buffer)))
   (when ellama-chat-done-callback
@@ -1562,7 +1615,8 @@ Will call `ellama-chat-done-callback' and ON-DONE on 
TEXT."
   "Translate generated text into TRANSLATION-BUFFER."
   (lambda (generated)
     (ellama-chat-done generated)
-    (display-buffer translation-buffer)
+    (display-buffer translation-buffer (when 
ellama-chat-display-action-function
+                                        `((ignore . 
(,ellama-chat-display-action-function)))))
     (with-current-buffer translation-buffer
       (save-excursion
        (goto-char (point-max))
@@ -1584,7 +1638,8 @@ Will call `ellama-chat-done-callback' and ON-DONE on 
TEXT."
       (goto-char (point-max))
       (delete-char -2)
       (delete-char (- (length result))))
-    (display-buffer buffer)
+    (display-buffer buffer (when ellama-chat-display-action-function
+                            `((ignore . 
(,ellama-chat-display-action-function)))))
     (with-current-buffer buffer
       (save-excursion
        (goto-char (point-max))
@@ -1599,7 +1654,8 @@ Will call `ellama-chat-done-callback' and ON-DONE on 
TEXT."
 
 (defun ellama--translate-interaction (prompt translation-buffer buffer session)
   "Translate chat PROMPT in TRANSLATION-BUFFER for BUFFER with SESSION."
-  (display-buffer translation-buffer)
+  (display-buffer translation-buffer (when ellama-chat-display-action-function
+                                      `((ignore . 
(,ellama-chat-display-action-function)))))
   (with-current-buffer translation-buffer
     (save-excursion
       (goto-char (point-max))
@@ -1679,13 +1735,17 @@ the full response text when the request completes (with 
BUFFER current)."
                                 (get-buffer-create (ellama-session-id 
session))))))
     (if ellama-chat-translation-enabled
        (ellama--translate-interaction prompt translation-buffer buffer session)
-      (display-buffer buffer)
+      (display-buffer buffer (when ellama-chat-display-action-function
+                              `((ignore . 
(,ellama-chat-display-action-function)))))
       (with-current-buffer buffer
        (save-excursion
          (goto-char (point-max))
-         (insert (ellama-get-nick-prefix-for-mode) " " ellama-user-nick ":\n"
-                 (ellama--format-context session) (ellama--fill-long-lines 
prompt) "\n\n"
-                 (ellama-get-nick-prefix-for-mode) " " ellama-assistant-nick 
":\n")
+         (if (equal (point-min) (point-max)) ;; empty buffer
+             (insert (ellama-get-nick-prefix-for-mode) " " ellama-user-nick 
":\n"
+                     (ellama--format-context session) (ellama--fill-long-lines 
prompt) "\n\n"
+                     (ellama-get-nick-prefix-for-mode) " " 
ellama-assistant-nick ":\n")
+           (insert (ellama--format-context session) (ellama--fill-long-lines 
prompt) "\n\n"
+                   (ellama-get-nick-prefix-for-mode) " " ellama-assistant-nick 
":\n"))
          (ellama-stream prompt
                         :session session
                         :on-done (if donecb (list 'ellama-chat-done donecb)
@@ -1693,6 +1753,27 @@ the full response text when the request completes (with 
BUFFER current)."
                         :filter (when (derived-mode-p 'org-mode)
                                   
#'ellama--translate-markdown-to-org-filter)))))))
 
+;;;###autoload
+(defun ellama-chat-send-last-message ()
+  "Send last user message extracted from current ellama chat buffer."
+  (interactive)
+  (when-let* ((session ellama--current-session)
+             (message (ellama-get-last-user-message))
+             ((length> message 0))
+             (text (if (derived-mode-p 'org-mode)
+                       (ellama-convert-org-to-md message)
+                     message)))
+    (goto-char (point-max))
+    (insert "\n\n")
+    (when (ellama-session-context session)
+      (insert (ellama--format-context session)))
+    (insert (ellama-get-nick-prefix-for-mode) " " ellama-assistant-nick ":\n")
+    (ellama-stream text
+                  :session session
+                  :on-done #'ellama-chat-done
+                  :filter (when (derived-mode-p 'org-mode)
+                            #'ellama--translate-markdown-to-org-filter))))
+
 ;;;###autoload
 (defun ellama-ask-about ()
   "Ask ellama about selected region or current buffer."
@@ -1815,7 +1896,8 @@ ARGS contains keys for fine control.
       (funcall ellama-major-mode)
       (when (derived-mode-p 'org-mode)
        (setq filter 'ellama--translate-markdown-to-org-filter)))
-    (display-buffer buffer)
+    (display-buffer buffer (when ellama-instant-display-action-function
+                            `((ignore . 
(,ellama-instant-display-action-function)))))
     (ellama-stream prompt
                   :buffer buffer
                   :filter filter

Reply via email to