branch: externals/ellama
commit bb0bdc2f8ffb82e18d22f4a4bcab954af3a5e91f
Merge: f00f7188cc 50f0f02a39
Author: Sergey Kostyaev <[email protected]>
Commit: GitHub <[email protected]>

    Merge pull request #308 from s-kostyaev/enhance-session-management
    
    Add `create-session` parameter to chat commands
---
 NEWS.org                    |  16 +++++
 README.org                  |  18 +++--
 ellama-community-prompts.el |   1 +
 ellama-context.el           | 169 ++++++++++++++++++++++++++++++--------------
 ellama-transient.el         | 122 +++++++++++++++++++++++++++++---
 ellama.el                   | 118 ++++++++++++++++++++-----------
 ellama.info                 |  67 ++++++++++--------
 7 files changed, 368 insertions(+), 143 deletions(-)

diff --git a/NEWS.org b/NEWS.org
index f21abf4945..668491c5b8 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -1,3 +1,19 @@
+* Version 1.8.0
+- Added ~create-session~ optional parameter to ~ellama-ask-about~,
+  ~ellama-ask-selection~, ~ellama-ask-line~, and ~ellama-code-review~ commands,
+  and added ~--new-session~ option to transient commands. These changes allow
+  creating a new session even if one is already active.
+- Added the ability to create an ephemeral session by adding a new ~:ephemeral~
+  argument to various functions and added the ~--ephemeral~ option to several
+  transient commands in ~ellama-transient.el~.
+- Implemented ephemeral context elements that are cleared after a single LLM
+  request. Added corresponding functions and transient suffixes to manage
+  ephemeral context elements, updating existing functions to handle both global
+  and ephemeral contexts.
+- Set up the transient menu to ensure the Ollama model is filled if it's empty.
+- Added ~use-hard-newlines~ variable and updated text insertion to use hard
+  newlines. Ensured that text is inserted only if there is a delta, and 
improved
+  the conditional logic for filling paragraphs.
 * Version 1.7.2
 - Added detailed context management documentation.
 * Version 1.7.1
diff --git a/README.org b/README.org
index c1d7ab2918..eed1ffa995 100644
--- a/README.org
+++ b/README.org
@@ -365,7 +365,9 @@ which may not always be appropriate.
 
 A “global context” is maintained, which is a collection of text blocks
 accessible to the LLM when responding to prompts. This global context is
-prepened to your prompt before transmission to the LLM.
+prepended to your prompt before transmission to the LLM. Additionally, Ellama
+supports an "ephemeral context," which is temporary and only available for a
+single request.
 
 ** Transient Menus for Context Management
 
@@ -378,12 +380,14 @@ The Context Commands transient menu is structured as 
follows:
 
 Context Commands:
 
-- Add: Provides options for adding content to the global context.
-    - “b” "Add Buffer" ~ellama-context-add-buffer~
-    - “d” "Add Directory" ~ellama-context-add-directory~
-    - “f” "Add File" ~ellama-context-add-file~
-    - “s” "Add Selection" ~ellama-context-add-selection~
-    - “i” "Add Info Node" ~ellama-context-add-info-node~
+- Options: Provides options for managing ephemeral context.
+    - “-e” "Use Ephemeral Context" ~--ephemeral~
+- Add: Provides options for adding content to the global or ephemeral context.
+    - “b” "Add Buffer" ~ellama-transient-add-buffer~
+    - “d” "Add Directory" ~ellama-transient-add-directory~
+    - “f” "Add File" ~ellama-transient-add-file~
+    - “s” "Add Selection" ~ellama-transient-add-selection~
+    - “i” "Add Info Node" ~ellama-transient-add-info-node~
 - Manage: Provides options for managing the global context.
     - “m” "Manage context" ~ellama-context-manage~ - Opens the context
       management buffer.
diff --git a/ellama-community-prompts.el b/ellama-community-prompts.el
index 9da7af4c38..9c6bd7be2e 100644
--- a/ellama-community-prompts.el
+++ b/ellama-community-prompts.el
@@ -113,6 +113,7 @@ PARSED-LINE is expected to be a list with three elements: 
:act,
 (defvar ellama-community-prompts-collection nil
   "Community prompts collection.")
 
+;;;###autoload
 (defun ellama-community-prompts-ensure ()
   "Ensure that the community prompt collection are loaded and available.
 This function ensures that the file specified by 
`ellama-community-prompts-file'
diff --git a/ellama-context.el b/ellama-context.el
index 76dc55f318..ff0e82ff24 100644
--- a/ellama-context.el
+++ b/ellama-context.el
@@ -62,6 +62,9 @@
 (defvar ellama-context-global nil
   "Global context.")
 
+(defvar ellama-context-ephemeral nil
+  "Ephemeral context elements for a single LLM request.")
+
 (defvar ellama--context-buffer " *ellama-context*")
 
 (defvar ellama-context-buffer "*ellama-context*")
@@ -71,6 +74,7 @@
   "Clear global context."
   (interactive)
   (setq ellama-context-global nil)
+  (setq ellama-context-ephemeral nil)
   (with-current-buffer ellama--context-buffer
     (erase-buffer))
   (ellama-context-update-show))
@@ -80,7 +84,11 @@
   (setq ellama-context-global
        (cl-remove-if (lambda (el)
                        (string= name (ellama-context-element-display el)))
-                     ellama-context-global)))
+                     ellama-context-global))
+  (setq ellama-context-ephemeral
+       (cl-remove-if (lambda (el)
+                       (string= name (ellama-context-element-display el)))
+                     ellama-context-ephemeral)))
 
 ;;;###autoload
 (defun ellama-context-element-remove-by-name ()
@@ -93,7 +101,8 @@ the context."
   (ellama-context--element-remove-by-name
    (completing-read
     "Remove context element: "
-    (seq-uniq (mapcar #'ellama-context-element-display 
ellama-context-global))))
+    (seq-uniq (mapcar #'ellama-context-element-display (append 
ellama-context-global
+                                                              
ellama-context-ephemeral)))))
   (ellama-context-update-show))
 
 (defun ellama-context-update-show ()
@@ -102,19 +111,22 @@ the context."
   (declare-function posframe-hide "ext:posframe")
   (with-current-buffer (get-buffer-create ellama--context-buffer)
     (erase-buffer)
-    (if ellama-context-global
+    (if (or ellama-context-global
+           ellama-context-ephemeral)
        (insert (format
                 " ellama ctx: %s"
                 (string-join
                  (mapcar
                   (lambda (el)
                     (ellama-context-element-display el))
-                  ellama-context-global)
+                  (append ellama-context-global
+                          ellama-context-ephemeral))
                  "  ")))
       (insert " ellama ctx")))
   (when ellama-context-posframe-enabled
     (require 'posframe)
-    (if ellama-context-global
+    (if (or ellama-context-global
+           ellama-context-ephemeral)
        (posframe-show
         ellama--context-buffer
         :poshandler ellama-context-poshandler
@@ -159,7 +171,8 @@ the context."
   (when (listp header-line-format)
     (if (and ellama-context-header-line-mode
             (or ellama-context-line-always-visible
-                ellama-context-global))
+                ellama-context-global
+                ellama-context-ephemeral))
        (add-to-list 'header-line-format '(:eval (ellama-context-line)) t)
       (setq header-line-format (delete '(:eval (ellama-context-line)) 
header-line-format)))))
 
@@ -183,7 +196,8 @@ the context."
   "Update and display context information in the mode line."
   (if (and ellama-context-mode-line-mode
           (or ellama-context-line-always-visible
-              ellama-context-global))
+              ellama-context-global
+              ellama-context-ephemeral))
       (add-to-list 'mode-line-format '(:eval (ellama-context-line)) t)
     (setq mode-line-format (delete '(:eval (ellama-context-line)) 
mode-line-format))))
 
@@ -219,7 +233,8 @@ the context."
       (read-only-mode +1)
       (ellama-context-mode)
       (erase-buffer)
-      (dolist (el ellama-context-global)
+      (dolist (el (append ellama-context-global
+                         ellama-context-ephemeral))
        (insert (ellama-context-element-display el))
        (put-text-property (pos-bol) (pos-eol) 'context-element el)
        (insert "\n"))
@@ -277,7 +292,9 @@ the context."
 (defun ellama-context-remove-element (element)
   "Remove context ELEMENT from global context."
   (setf ellama-context-global
-       (cl-remove element ellama-context-global :test 
#'equal-including-properties)))
+       (cl-remove element ellama-context-global :test 
#'equal-including-properties))
+  (setf ellama-context-ephemeral
+       (cl-remove element ellama-context-ephemeral :test 
#'equal-including-properties)))
 
 ;;;###autoload
 (defun ellama-context-preview-element-at-point ()
@@ -303,6 +320,9 @@ the context."
 (cl-defgeneric ellama-context-element-add (element)
   "Add the ELEMENT to the Ellama context.")
 
+(cl-defgeneric ellama-context-ephemeral-element-add (element)
+  "Add the ephemeral ELEMENT to the Ellama context.")
+
 (cl-defgeneric ellama-context-element-extract (element)
   "Extract the content of the context ELEMENT.")
 
@@ -321,6 +341,15 @@ the context."
   (get-buffer-create ellama--context-buffer t)
   (ellama-context-update-show))
 
+(cl-defmethod ellama-context-ephemeral-element-add ((element 
ellama-context-element))
+  "Add the ephemeral ELEMENT to the Ellama context."
+  (setf ellama-context-ephemeral (nreverse ellama-context-ephemeral))
+  (cl-pushnew element ellama-context-ephemeral
+             :test #'equal-including-properties)
+  (setf ellama-context-ephemeral (nreverse ellama-context-ephemeral))
+  (get-buffer-create ellama--context-buffer t)
+  (ellama-context-update-show))
+
 ;; Buffer context element
 
 (defclass ellama-context-element-buffer (ellama-context-element)
@@ -683,22 +712,29 @@ the context."
 
 
 ;;;###autoload
-(defun ellama-context-add-file ()
-  "Add file to context."
+(defun ellama-context-add-file (&optional ephemeral)
+  "Add file to context.
+For one request only if EPHEMERAL."
   (interactive)
   (let* ((file-name (read-file-name "Select file: " nil nil t))
         (element (ellama-context-element-file :name file-name)))
-    (ellama-context-element-add element)))
+    (if ephemeral
+       (ellama-context-ephemeral-element-add element)
+      (ellama-context-element-add element))))
 
-(defun ellama-context-add-file-quote-noninteractive (path content)
-  "Add file with PATH quote CONTENT to context."
+(defun ellama-context-add-file-quote-noninteractive (path content &optional 
ephemeral)
+  "Add file with PATH quote CONTENT to context.
+For one request only if EPHEMERAL."
   (let ((element (ellama-context-element-file-quote
                  :path path :content content)))
-    (ellama-context-element-add element)))
+    (if ephemeral
+       (ellama-context-ephemeral-element-add element)
+      (ellama-context-element-add element))))
 
 ;;;###autoload
-(defun ellama-context-add-file-quote ()
-  "Add file quote to context interactively."
+(defun ellama-context-add-file-quote (&optional ephemeral)
+  "Add file quote to context interactively.
+For one request only if EPHEMERAL."
   (interactive)
   (let ((path (buffer-file-name (current-buffer)))
        (content (if (region-active-p)
@@ -710,30 +746,37 @@ the context."
                    (point-max)))))
     (if (not path)
        (warn "should be called from buffer associated with file")
-      (ellama-context-add-file-quote-noninteractive path content))))
+      (ellama-context-add-file-quote-noninteractive path content ephemeral))))
 
 ;;;###autoload
-(defun ellama-context-add-buffer (buf)
-  "Add BUF to context."
+(defun ellama-context-add-buffer (buf &optional ephemeral)
+  "Add BUF to context.
+For one request only if EPHEMERAL."
   (interactive "bSelect buffer: ")
   (let* ((buffer-name (if (stringp buf)
                          buf
                        (buffer-name buf)))
         (element (ellama-context-element-buffer :name buffer-name)))
-    (ellama-context-element-add element)))
+    (if ephemeral
+       (ellama-context-ephemeral-element-add element)
+      (ellama-context-element-add element))))
 
 ;;;###autoload
-(defun ellama-context-add-directory (dir)
-  "Add all files in DIR to the context."
+(defun ellama-context-add-directory (dir &optional ephemeral)
+  "Add all files in DIR to the context.
+For one request only if EPHEMERAL."
   (interactive "DSelect directory: ")
   (dolist (file-name (directory-files dir t "^[^\.].*"))
     (unless (file-directory-p file-name)
       (let ((element (ellama-context-element-file :name file-name)))
-       (ellama-context-element-add element)))))
+       (if ephemeral
+           (ellama-context-ephemeral-element-add element)
+         (ellama-context-element-add element))))))
 
 ;;;###autoload
-(defun ellama-context-add-selection ()
-  "Add active region to context."
+(defun ellama-context-add-selection (&optional ephemeral)
+  "Add active region to context.
+For one request only if EPHEMERAL."
   (interactive)
   (if (region-active-p)
       (let* ((data (buffer-substring-no-properties (region-beginning) 
(region-end)))
@@ -746,32 +789,44 @@ the context."
                          (ellama-context-element-file-quote :path file-name
                                                             :content content)
                        (ellama-context-element-buffer-quote :name buffer-name 
:content content))))
-       (ellama-context-element-add element))
+       (if ephemeral
+           (ellama-context-ephemeral-element-add element)
+         (ellama-context-element-add element)))
     (warn "No active region")))
 
-(defun ellama-context-add-text (text)
-  "Add TEXT to context."
+(defun ellama-context-add-text (text &optional ephemeral)
+  "Add TEXT to context.
+For one request only if EPHEMERAL."
   (let ((element (ellama-context-element-text :content text)))
-    (ellama-context-element-add element)))
+    (if ephemeral
+       (ellama-context-ephemeral-element-add element)
+      (ellama-context-element-add element))))
 
 (declare-function Info-copy-current-node-name "info")
 
 ;;;###autoload
-(defun ellama-context-add-info-node (node)
-  "Add info NODE to context."
+(defun ellama-context-add-info-node (node &optional ephemeral)
+  "Add info NODE to context.
+For one request only if EPHEMERAL."
   (interactive (list (Info-copy-current-node-name)))
   (let ((element (ellama-context-element-info-node :name node)))
-    (ellama-context-element-add element)))
+    (if ephemeral
+       (ellama-context-ephemeral-element-add element)
+      (ellama-context-element-add element))))
 
-(defun ellama-context-add-info-node-quote-noninteractive (name content)
-  "Add info node with NAME quote CONTENT to context."
+(defun ellama-context-add-info-node-quote-noninteractive (name content 
&optional ephemeral)
+  "Add info node with NAME quote CONTENT to context.
+For one request only if EPHEMERAL."
   (let ((element (ellama-context-element-info-node-quote
                  :name name :content content)))
-    (ellama-context-element-add element)))
+    (if ephemeral
+       (ellama-context-ephemeral-element-add element)
+      (ellama-context-element-add element))))
 
 ;;;###autoload
-(defun ellama-context-add-info-node-quote ()
-  "Add info node quote to context interactively."
+(defun ellama-context-add-info-node-quote (&optional ephemeral)
+  "Add info node quote to context interactively.
+For one request only if EPHEMERAL."
   (interactive)
   (let ((name (Info-copy-current-node-name))
        (content (if (region-active-p)
@@ -783,17 +838,21 @@ the context."
                    (point-max)))))
     (if (not name)
        (warn "should be called from `info' buffer")
-      (ellama-context-add-info-node-quote-noninteractive name content))))
+      (ellama-context-add-info-node-quote-noninteractive name content 
ephemeral))))
 
-(defun ellama-context-add-webpage-quote-noninteractive (name url content)
-  "Add webpage with NAME and URL quote CONTENT to context."
+(defun ellama-context-add-webpage-quote-noninteractive (name url content 
&optional ephemeral)
+  "Add webpage with NAME and URL quote CONTENT to context.
+For one request only if EPHEMERAL."
   (let ((element (ellama-context-element-webpage-quote
                  :name name :url url :content content)))
-    (ellama-context-element-add element)))
+    (if ephemeral
+       (ellama-context-ephemeral-element-add element)
+      (ellama-context-element-add element))))
 
 ;;;###autoload
-(defun ellama-context-add-webpage-quote-eww ()
-  "Add webpage quote to context interactively from `eww'."
+(defun ellama-context-add-webpage-quote-eww (&optional ephemeral)
+  "Add webpage quote to context interactively from `eww'.
+For one request only if EPHEMERAL."
   (interactive)
   (defvar eww-data)
   (declare-function eww-current-url "eww")
@@ -807,14 +866,15 @@ the context."
                        (buffer-substring-no-properties
                         (point-min)
                         (point-max)))))
-       (ellama-context-add-webpage-quote-noninteractive name url content))
+       (ellama-context-add-webpage-quote-noninteractive name url content 
ephemeral))
     (warn "Should be called from `eww'.")))
 
 ;;;###autoload
 (defun ellama-context-format (_)
   "Format context for chat buffer."
   (let ((mode (if (derived-mode-p 'org-mode) 'org-mode 'markdown-mode)))
-    (if-let* ((context ellama-context-global))
+    (if-let* ((context (append ellama-context-global
+                              ellama-context-ephemeral)))
        (concat (string-join
                 (cons "Context:"
                       (mapcar (lambda (elt)
@@ -827,14 +887,17 @@ the context."
 ;;;###autoload
 (defun ellama-context-prompt-with-context (prompt)
   "Add context to PROMPT for sending to llm."
-  (let* ((context ellama-context-global))
+  (let* ((context (append ellama-context-global
+                         ellama-context-ephemeral)))
     (if context
-       (concat (string-join
-                (cons "Context:"
-                      (mapcar #'ellama-context-element-extract context))
-                "\n")
-               "\n\n"
-               prompt)
+       (prog1
+           (concat (string-join
+                    (cons "Context:"
+                          (mapcar #'ellama-context-element-extract context))
+                    "\n")
+                   "\n\n"
+                   prompt)
+         (setq ellama-context-ephemeral nil))
       prompt)))
 
 (provide 'ellama-context)
diff --git a/ellama-transient.el b/ellama-transient.el
index 5f28cc51a9..458110be48 100644
--- a/ellama-transient.el
+++ b/ellama-transient.el
@@ -31,6 +31,7 @@
 ;;; Code:
 (require 'ellama)
 (require 'transient)
+(require 'ellama-context)
 
 (defcustom ellama-transient-system-show-limit 45
   "Maximum length of system message to show."
@@ -194,15 +195,28 @@ Otherwise, prompt the user to enter a system message."
    :default-chat-non-standard-params
    `[("num_ctx" . ,ellama-transient-context-length)]))
 
+(transient-define-suffix ellama-transient-code-review (&optional args)
+  "Review the code.  ARGS used for transient arguments."
+  (interactive (list (transient-args transient-current-command)))
+  (ellama-code-review
+   (transient-arg-value "--new-session" args)
+   :ephemeral (transient-arg-value "--ephemeral" args)))
+
 ;;;###autoload (autoload 'ellama-transient-code-menu "ellama-transient" nil t)
 (transient-define-prefix ellama-transient-code-menu ()
   "Code Commands."
+  ["Session Options"
+   :description (lambda () (ellama-session-line))
+   ("-n" "Create New Session" "--new-session")]
+  ["Ephemeral sessions"
+   :if (lambda () ellama-session-auto-save)
+   ("-e" "Create Ephemeral Session" "--ephemeral")]
   [["Code Commands"
     ("c" "Complete" ellama-code-complete)
     ("a" "Add" ellama-code-add)
     ("e" "Edit" ellama-code-edit)
     ("i" "Improve" ellama-code-improve)
-    ("r" "Review" ellama-code-review)
+    ("r" "Review" ellama-transient-code-review)
     ("m" "Generate Commit Message" ellama-generate-commit-message)]
    ["Quit" ("q" "Quit" transient-quit-one)]])
 
@@ -244,13 +258,40 @@ Otherwise, prompt the user to enter a system message."
     ("f" "Make Format" ellama-make-format)]
    ["Quit" ("q" "Quit" transient-quit-one)]])
 
+(transient-define-suffix ellama-transient-ask-line (&optional args)
+  "Ask line.  ARGS used for transient arguments."
+  (interactive (list (transient-args transient-current-command)))
+  (ellama-ask-line
+   (transient-arg-value "--new-session" args)
+   :ephemeral (transient-arg-value "--ephemeral" args)))
+
+(transient-define-suffix ellama-transient-ask-selection (&optional args)
+  "Ask selection.  ARGS used for transient arguments."
+  (interactive (list (transient-args transient-current-command)))
+  (ellama-ask-selection
+   (transient-arg-value "--new-session" args)
+   :ephemeral (transient-arg-value "--ephemeral" args)))
+
+(transient-define-suffix ellama-transient-ask-about (&optional args)
+  "Ask about current buffer or region.  ARGS used for transient arguments."
+  (interactive (list (transient-args transient-current-command)))
+  (ellama-ask-about
+   (transient-arg-value "--new-session" args)
+   :ephemeral (transient-arg-value "--ephemeral" args)))
+
 ;;;###autoload (autoload 'ellama-transient-ask-menu "ellama-transient" nil t)
 (transient-define-prefix ellama-transient-ask-menu ()
   "Ask Commands."
+  ["Session Options"
+   :description (lambda () (ellama-session-line))
+   ("-n" "Create New Session" "--new-session")]
+  ["Ephemeral sessions"
+   :if (lambda () ellama-session-auto-save)
+   ("-e" "Create Ephemeral Session" "--ephemeral")]
   [["Ask Commands"
-    ("l" "Ask Line" ellama-ask-line)
-    ("s" "Ask Selection" ellama-ask-selection)
-    ("a" "Ask About" ellama-ask-about)]
+    ("l" "Ask Line" ellama-transient-ask-line)
+    ("s" "Ask Selection" ellama-transient-ask-selection)
+    ("a" "Ask About" ellama-transient-ask-about)]
    ["Quit" ("q" "Quit" transient-quit-one)]])
 
 ;;;###autoload (autoload 'ellama-transient-translate-menu "ellama-transient" 
nil t)
@@ -266,9 +307,50 @@ Otherwise, prompt the user to enter a system message."
 (declare-function ellama-context-update-buffer "ellama-context")
 (defvar ellama-context-buffer)
 
+(transient-define-suffix ellama-transient-add-buffer (&optional args)
+  "Add current buffer to context.
+ARGS used for transient arguments."
+  (interactive (list (transient-args transient-current-command)))
+  (ellama-context-add-buffer
+   (read-buffer "Buffer: ")
+   (transient-arg-value "--ephemeral" args)))
+
+(transient-define-suffix ellama-transient-add-directory (&optional args)
+  "Add directory to context.
+ARGS used for transient arguments."
+  (interactive (list (transient-args transient-current-command)))
+  (let ((directory (read-directory-name "Directory: ")))
+    (ellama-context-add-directory
+     directory
+     (transient-arg-value "--ephemeral" args))))
+
+(transient-define-suffix ellama-transient-add-file (&optional args)
+  "Add file to context.
+ARGS used for transient arguments."
+  (interactive (list (transient-args transient-current-command)))
+  (ellama-context-add-file (transient-arg-value "--ephemeral" args)))
+
+(transient-define-suffix ellama-transient-add-selection (&optional args)
+  "Add current selection to context.
+ARGS used for transient arguments."
+  (interactive (list (transient-args transient-current-command)))
+  (when (region-active-p)
+    (ellama-context-add-selection (transient-arg-value "--ephemeral" args))))
+
+(transient-define-suffix ellama-transient-add-info-node (&optional args)
+  "Add Info Node to context.
+ARGS used for transient arguments."
+  (interactive (list (transient-args transient-current-command)))
+  (let ((info-node (Info-copy-current-node-name)))
+    (ellama-context-add-info-node
+     info-node
+     (transient-arg-value "--ephemeral" args))))
+
 ;;;###autoload (autoload 'ellama-transient-context-menu "ellama-transient" nil 
t)
 (transient-define-prefix ellama-transient-context-menu ()
   "Context Commands."
+  ["Options"
+   ("-e" "Use Ephemeral Context" "--ephemeral")]
   ["Context Commands"
    :description (lambda ()
                  (ellama-context-update-buffer)
@@ -276,11 +358,11 @@ Otherwise, prompt the user to enter a system message."
 %s" (with-current-buffer ellama-context-buffer
       (buffer-substring (point-min) (point-max)))))
    ["Add"
-    ("b" "Add Buffer" ellama-context-add-buffer)
-    ("d" "Add Directory" ellama-context-add-directory)
-    ("f" "Add File" ellama-context-add-file)
-    ("s" "Add Selection" ellama-context-add-selection)
-    ("i" "Add Info Node" ellama-context-add-info-node)]
+    ("b" "Add Buffer" ellama-transient-add-buffer)
+    ("d" "Add Directory" ellama-transient-add-directory)
+    ("f" "Add File" ellama-transient-add-file)
+    ("s" "Add Selection" ellama-transient-add-selection)
+    ("i" "Add Info Node" ellama-transient-add-info-node)]
    ["Manage"
     ("m" "Manage context" ellama-context-manage)
     ("D" "Delete element" ellama-context-element-remove-by-name)
@@ -316,11 +398,25 @@ Otherwise, prompt the user to enter a system message."
     ("k" "Kill" ellama-kill-current-buffer)
     ("q" "Quit" transient-quit-one)]])
 
+(transient-define-suffix ellama-transient-chat (&optional args)
+  "Chat with Ellama.  ARGS used for transient arguments."
+  (interactive (list (transient-args transient-current-command)))
+  (ellama-chat
+   (read-string "Ask Ellama: ")
+   (transient-arg-value "--new-session" args)
+   :ephemeral (transient-arg-value "--ephemeral" args)))
+
 ;;;###autoload (autoload 'ellama-transient-main-menu "ellama-transient" nil t)
 (transient-define-prefix ellama-transient-main-menu ()
   "Main Menu."
+  ["Session Options"
+   :description (lambda () (ellama-session-line))
+   ("-n" "Create New Session" "--new-session")]
+  ["Ephemeral sessions"
+   :if (lambda () ellama-session-auto-save)
+   ("-e" "Create Ephemeral Session" "--ephemeral")]
   ["Main"
-   [("c" "Chat" ellama-chat)
+   [("c" "Chat" ellama-transient-chat)
     ("b" "Chat with blueprint" ellama-blueprint-select)
     ("B" "Blueprint Commands" ellama-transient-blueprint-menu)]
    [("a" "Ask Commands" ellama-transient-ask-menu)
@@ -347,7 +443,11 @@ Otherwise, prompt the user to enter a system message."
   [["Problem solving"
     ("R" "Solve reasoning problem" ellama-solve-reasoning-problem)
     ("D" "Solve domain specific problem" 
ellama-solve-domain-specific-problem)]]
-  [["Quit" ("q" "Quit" transient-quit-one)]])
+  [["Quit" ("q" "Quit" transient-quit-one)]]
+  (interactive)
+  (transient-setup 'ellama-transient-main-menu)
+  (when (string-empty-p ellama-transient-ollama-model-name)
+    (ellama-fill-transient-ollama-model ellama-provider)))
 
 ;;;###autoload (autoload 'ellama "ellama-transient" nil t)
 (defalias 'ellama 'ellama-transient-main-menu)
diff --git a/ellama.el b/ellama.el
index a085afacad..2c732d26cf 100644
--- a/ellama.el
+++ b/ellama.el
@@ -6,7 +6,7 @@
 ;; URL: http://github.com/s-kostyaev/ellama
 ;; Keywords: help local tools
 ;; Package-Requires: ((emacs "28.1") (llm "0.24.0") (plz "0.8") (transient 
"0.7") (compat "29.1"))
-;; Version: 1.7.2
+;; Version: 1.8.0
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;; Created: 8th Oct 2023
 
@@ -1235,6 +1235,7 @@ FILTER is a function for text transformation."
            (goto-char end-marker)
            (let* ((filtered-text
                    (funcall filter text))
+                  (use-hard-newlines t)
                   (common-prefix (concat
                                   safe-common-prefix
                                   (ellama-max-common-prefix
@@ -1248,26 +1249,26 @@ FILTER is a function for text transformation."
                                       (length common-prefix)))
                   (delta (string-remove-prefix common-prefix filtered-text)))
              (delete-char (- wrong-chars-cnt))
-             (insert delta)
-             (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)))))
-               (if (not (eq major-mode 'org-mode))
-                   (fill-paragraph)
-                 (when (not (save-excursion
-                              (re-search-backward
-                               "#\\+BEGIN_SRC"
-                               beg-marker t)))
-                   (org-fill-paragraph))))
-             (set-marker end-marker (point))
-             (when (and ellama-auto-scroll (not ellama--stop-scroll))
-               (ellama--scroll buffer end-marker))
-             (setq safe-common-prefix (ellama--string-without-last-line 
common-prefix))
-             (setq previous-filtered-text filtered-text))))))))
+             (when delta (insert (propertize delta 'hard t))
+                   (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)))))
+                     (if (not (eq major-mode 'org-mode))
+                         (fill-paragraph)
+                       (when (not (save-excursion
+                                    (re-search-backward
+                                     "#\\+BEGIN_SRC"
+                                     beg-marker t)))
+                         (org-fill-paragraph))))
+                   (set-marker end-marker (point))
+                   (when (and ellama-auto-scroll (not ellama--stop-scroll))
+                     (ellama--scroll buffer end-marker))
+                   (setq safe-common-prefix (ellama--string-without-last-line 
common-prefix))
+                   (setq previous-filtered-text filtered-text)))))))))
 
 (defun ellama--handle-partial (insert-text insert-reasoning reasoning-buffer)
   "Handle partial llm callback.
@@ -1586,7 +1587,8 @@ Will call `ellama-chat-done-callback' and ON-DONE on 
TEXT."
   (save-excursion
     (goto-char (point-max))
     (insert "\n\n" (ellama-get-nick-prefix-for-mode) " " ellama-user-nick 
":\n")
-    (when ellama-session-auto-save
+    (when (and ellama-session-auto-save
+              buffer-file-name)
       (save-buffer)))
   (ellama--scroll)
   (when ellama-chat-done-callback
@@ -1674,6 +1676,8 @@ ARGS contains keys for fine control.
 
 :system STR -- send STR to model as system message.
 
+:ephemeral BOOL -- create an ephemeral session if set.
+
 :on-done ON-DONE -- ON-DONE a function that's called with
 the full response text when the request completes (with BUFFER current)."
   (interactive "sAsk ellama: ")
@@ -1694,6 +1698,7 @@ the full response text when the request completes (with 
BUFFER current)."
                     (or (plist-get args :provider)
                         ellama-provider
                         (ellama-get-first-ollama-chat-model))))
+        (ephemeral (plist-get args :ephemeral))
         (session (or (plist-get args :session)
                      (if (or create-session
                              current-prefix-arg
@@ -1708,7 +1713,7 @@ the full response text when the request completes (with 
BUFFER current)."
                                           (ellama-session-provider 
ellama--current-session)))))
                              (and (not ellama--current-session)
                                   (not ellama--current-session-id)))
-                         (ellama-new-session provider prompt)
+                         (ellama-new-session provider prompt ephemeral)
                        (or ellama--current-session
                            (with-current-buffer (ellama-get-session-buffer
                                                  (or (plist-get args 
:session-id)
@@ -1782,25 +1787,39 @@ the full response text when the request completes (with 
BUFFER current)."
                             #'ellama--translate-markdown-to-org-filter))))
 
 ;;;###autoload
-(defun ellama-ask-about ()
-  "Ask ellama about selected region or current buffer."
+(defun ellama-ask-about (&optional create-session &rest args)
+  "Ask ellama about selected region or current buffer.
+
+If CREATE-SESSION set, creates new session even if there is an active session.
+
+ARGS contains keys for fine control.
+
+:ephemeral BOOL -- create an ephemeral session if set."
   (interactive)
   (declare-function ellama-context-add-selection "ellama-context")
   (declare-function ellama-context-add-buffer "ellama-context")
-  (let ((input (read-string "Ask ellama about this text: ")))
+  (let ((input (read-string "Ask ellama about this text: "))
+       (ephemeral (plist-get args :ephemeral)))
     (if (region-active-p)
        (ellama-context-add-selection)
       (ellama-context-add-buffer (buffer-name (current-buffer))))
-    (ellama-chat input)))
+    (ellama-chat input create-session :ephemeral ephemeral)))
 
 ;;;###autoload
-(defun ellama-ask-selection ()
-  "Send selected region or current buffer to ellama chat."
+(defun ellama-ask-selection (&optional create-session &rest args)
+  "Send selected region or current buffer to ellama chat.
+
+If CREATE-SESSION set, creates new session even if there is an active session.
+
+ARGS contains keys for fine control.
+
+:ephemeral BOOL -- create an ephemeral session if set."
   (interactive)
   (let ((text (if (region-active-p)
                  (buffer-substring-no-properties (region-beginning) 
(region-end))
-               (buffer-substring-no-properties (point-min) (point-max)))))
-    (ellama-chat text)))
+               (buffer-substring-no-properties (point-min) (point-max))))
+       (ephemeral (plist-get args :ephemeral)))
+    (ellama-chat text create-session :ephemeral ephemeral)))
 
 (defcustom ellama-complete-prompt-template "You're providing text completion. 
Complete the text. Do not aknowledge, reply with completion only."
   "System prompt template for `ellama-complete'."
@@ -1898,11 +1917,18 @@ the full response text when the request completes (with 
BUFFER current)."
        :provider ellama-coding-provider))))
 
 ;;;###autoload
-(defun ellama-ask-line ()
-  "Send current line to ellama chat."
+(defun ellama-ask-line (&optional create-session &rest args)
+  "Send current line to ellama chat.
+
+If CREATE-SESSION set, creates new session even if there is an active session.
+
+ARGS contains keys for fine control.
+
+:ephemeral BOOL -- create an ephemeral session if set."
   (interactive)
-  (let ((text (thing-at-point 'line)))
-    (ellama-chat text)))
+  (let* ((text (thing-at-point 'line))
+        (ephemeral (plist-get args :ephemeral)))
+    (ellama-chat text create-session :ephemeral ephemeral)))
 
 (defun ellama-instant (prompt &rest args)
   "Prompt ellama for PROMPT to reply instantly.
@@ -2006,13 +2032,23 @@ ARGS contains keys for fine control.
                                    (ellama-get-first-ollama-chat-model))))))
 
 ;;;###autoload
-(defun ellama-code-review ()
-  "Review code in selected region or current buffer."
+(defun ellama-code-review (&optional create-session &rest args)
+  "Review code in selected region or current buffer.
+
+If CREATE-SESSION set, creates new session even if there is an active session.
+ARGS contains keys for fine control.
+
+:ephemeral BOOL -- create an ephemeral session if set."
   (interactive)
-  (if (region-active-p)
-      (ellama-context-add-selection)
-    (ellama-context-add-buffer (current-buffer)))
-  (ellama-chat ellama-code-review-prompt-template nil :provider 
ellama-coding-provider))
+  (let ((ephemeral (plist-get args :ephemeral)))
+    (if (region-active-p)
+        (ellama-context-add-selection)
+      (ellama-context-add-buffer (current-buffer)))
+    (ellama-chat
+     ellama-code-review-prompt-template
+     create-session
+     :provider ellama-coding-provider
+     :ephemeral ephemeral)))
 
 ;;;###autoload
 (defun ellama-write (instruction)
diff --git a/ellama.info b/ellama.info
index 229a9e8913..9a305ef574 100644
--- a/ellama.info
+++ b/ellama.info
@@ -489,7 +489,9 @@ its pre-existing knowledge, which may not always be 
appropriate.
 
 A “global context” is maintained, which is a collection of text blocks
 accessible to the LLM when responding to prompts.  This global context
-is prepened to your prompt before transmission to the LLM.
+is prepended to your prompt before transmission to the LLM.
+Additionally, Ellama supports an "ephemeral context," which is temporary
+and only available for a single request.
 
 * Menu:
 
@@ -512,12 +514,15 @@ The Context Commands transient menu is structured as 
follows:
 
 Context Commands:
 
-   • Add: Provides options for adding content to the global context.
-        • “b” "Add Buffer" ‘ellama-context-add-buffer’
-        • “d” "Add Directory" ‘ellama-context-add-directory’
-        • “f” "Add File" ‘ellama-context-add-file’
-        • “s” "Add Selection" ‘ellama-context-add-selection’
-        • “i” "Add Info Node" ‘ellama-context-add-info-node’
+   • Options: Provides options for managing ephemeral context.
+        • “-e” "Use Ephemeral Context" ‘--ephemeral’
+   • Add: Provides options for adding content to the global or ephemeral
+     context.
+        • “b” "Add Buffer" ‘ellama-transient-add-buffer’
+        • “d” "Add Directory" ‘ellama-transient-add-directory’
+        • “f” "Add File" ‘ellama-transient-add-file’
+        • “s” "Add Selection" ‘ellama-transient-add-selection’
+        • “i” "Add Info Node" ‘ellama-transient-add-info-node’
    • Manage: Provides options for managing the global context.
         • “m” "Manage context" ‘ellama-context-manage’ - Opens the
           context management buffer.
@@ -1412,30 +1417,30 @@ Node: Commands8621
 Node: Keymap14965
 Node: Configuration17798
 Node: Context Management23139
-Node: Transient Menus for Context Management23932
-Node: Managing the Context25390
-Node: Considerations26165
-Node: Minor modes26758
-Node: ellama-context-header-line-mode28746
-Node: ellama-context-header-line-global-mode29571
-Node: ellama-context-mode-line-mode30291
-Node: ellama-context-mode-line-global-mode31139
-Node: Ellama Session Header Line Mode31843
-Node: Enabling and Disabling32412
-Node: Customization32859
-Node: Ellama Session Mode Line Mode33147
-Node: Enabling and Disabling (1)33732
-Node: Customization (1)34179
-Node: Using Blueprints34473
-Node: Key Components of Ellama Blueprints35092
-Node: Creating and Managing Blueprints35699
-Node: Variable Management36680
-Node: Keymap and Mode37149
-Node: Transient Menus38085
-Node: Running Blueprints programmatically38631
-Node: Acknowledgments39218
-Node: Contributions39931
-Node: GNU Free Documentation License40315
+Node: Transient Menus for Context Management24047
+Node: Managing the Context25661
+Node: Considerations26436
+Node: Minor modes27029
+Node: ellama-context-header-line-mode29017
+Node: ellama-context-header-line-global-mode29842
+Node: ellama-context-mode-line-mode30562
+Node: ellama-context-mode-line-global-mode31410
+Node: Ellama Session Header Line Mode32114
+Node: Enabling and Disabling32683
+Node: Customization33130
+Node: Ellama Session Mode Line Mode33418
+Node: Enabling and Disabling (1)34003
+Node: Customization (1)34450
+Node: Using Blueprints34744
+Node: Key Components of Ellama Blueprints35363
+Node: Creating and Managing Blueprints35970
+Node: Variable Management36951
+Node: Keymap and Mode37420
+Node: Transient Menus38356
+Node: Running Blueprints programmatically38902
+Node: Acknowledgments39489
+Node: Contributions40202
+Node: GNU Free Documentation License40586
 
 End Tag Table
 


Reply via email to