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