branch: externals/ellama
commit 6e5372b015c63abaf18df9a9b53c9efe38b77bda
Author: Sergey Kostyaev <[email protected]>
Commit: Sergey Kostyaev <[email protected]>
Add ephemeral context functionality
Implemented ephemeral context elements that are cleared after a single LLM
request. Added corresponding functions and transient suffixes to manage
ephemeral context elements. Updated existing functions to handle both
global and
ephemeral contexts.
Fix #309
---
ellama-context.el | 169 ++++++++++++++++++++++++++++++++++++----------------
ellama-transient.el | 52 ++++++++++++++--
2 files changed, 163 insertions(+), 58 deletions(-)
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 c2431297ac..36016fec50 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."
@@ -302,6 +303,45 @@ 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."
@@ -312,11 +352,13 @@ 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)]
+ ["Options"
+ ("-e" "Use Ephemeral Context" "--ephemeral")]
["Manage"
("m" "Manage context" ellama-context-manage)
("D" "Delete element" ellama-context-element-remove-by-name)