branch: externals/minuet
commit b92b1f07d94e514b5ebd6b8113b3a40645660b7c
Author: milanglacier <d...@milanglacier.com>
Commit: GitHub <nore...@github.com>

    feat: add chat input template. (#16)
    
    Add configurable chat input template with dynamic placeholders similar
    to system prompt.
---
 minuet.el | 162 ++++++++++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 115 insertions(+), 47 deletions(-)

diff --git a/minuet.el b/minuet.el
index 8492ff89fa..619618926e 100644
--- a/minuet.el
+++ b/minuet.el
@@ -34,6 +34,7 @@
 
 (require 'plz)
 (require 'dash)
+(require 'cl-lib)
 
 (defgroup minuet nil
     "Minuet group."
@@ -194,6 +195,14 @@ after the cursor, then the code before the cursor.
     "{{{:prompt}}}\n{{{:guidelines}}}\n{{{:n-completions-template}}}"
     "The default template for minuet system template.")
 
+(defvar minuet-default-chat-input-template
+    "{{{:language-and-tab}}}
+<contextAfterCursor>
+{{{:context-after-cursor}}}
+<contextBeforeCursor>
+{{{:context-before-cursor}}}<cursorPosition>"
+    "The default template for minuet chat input.")
+
 (defvar minuet-default-fewshots
     `((:role "user"
        :content "# language: python
@@ -231,6 +240,11 @@ def fibonacci(n):
        :guidelines minuet-default-guidelines
        :n-completions-template minuet-default-n-completion-template)
       :fewshots minuet-default-fewshots
+      :chat-input
+      (:template minuet-default-chat-input-template
+       :language-and-tab minuet--default-chat-input-language-and-tab-function
+       :context-before-cursor minuet--default-chat-input-before-cursor-function
+       :context-after-cursor minuet--default-chat-input-after-cursor-function)
       :optional nil)
     "Config options for Minuet Claude provider.")
 
@@ -243,6 +257,11 @@ def fibonacci(n):
        :guidelines minuet-default-guidelines
        :n-completions-template minuet-default-n-completion-template)
       :fewshots minuet-default-fewshots
+      :chat-input
+      (:template minuet-default-chat-input-template
+       :language-and-tab minuet--default-chat-input-language-and-tab-function
+       :context-before-cursor minuet--default-chat-input-before-cursor-function
+       :context-after-cursor minuet--default-chat-input-after-cursor-function)
       :optional nil)
     "Config options for Minuet OpenAI provider.")
 
@@ -265,6 +284,11 @@ def fibonacci(n):
        :guidelines minuet-default-guidelines
        :n-completions-template minuet-default-n-completion-template)
       :fewshots minuet-default-fewshots
+      :chat-input
+      (:template minuet-default-chat-input-template
+       :language-and-tab minuet--default-chat-input-language-and-tab-function
+       :context-before-cursor minuet--default-chat-input-before-cursor-function
+       :context-after-cursor minuet--default-chat-input-after-cursor-function)
       :optional nil)
     "Config options for Minuet OpenAI compatible provider.")
 
@@ -287,6 +311,11 @@ def fibonacci(n):
        :guidelines minuet-default-guidelines
        :n-completions-template minuet-default-n-completion-template)
       :fewshots minuet-default-fewshots
+      :chat-input
+      (:template minuet-default-chat-input-template
+       :language-and-tab minuet--default-chat-input-language-and-tab-function
+       :context-before-cursor minuet--default-chat-input-before-cursor-function
+       :context-after-cursor minuet--default-chat-input-after-cursor-function)
       :optional nil)
     "Config options for Minuet Gemini provider.")
 
@@ -500,15 +529,36 @@ Also print the MESSAGE when MESSAGE-P is t."
           :after-cursor ,(buffer-substring-no-properties point after-end)
           :additional ,(format "%s\n%s" (minuet--add-language-comment) 
(minuet--add-tab-comment)))))
 
-(defun minuet--make-chat-llm-shot (context)
-    "Build the prompt for chat llm from CONTEXT."
-    (concat
-     (plist-get context :additional)
-     "\n<contextAfterCursor>\n"
-     (plist-get context :after-cursor)
-     "\n<contextBeforeCursor>\n"
-     (plist-get context :before-cursor)
-     "<cursorPosition>"))
+(defun minuet--make-chat-llm-shot (context options)
+    "Build the final chat input for chat llm.
+CONTEXT is read from current buffer content.
+OPTIONS should be the provider options plist."
+    (let* ((chat-input (copy-tree (plist-get options :chat-input)))
+           (template (minuet--eval-value (plist-get chat-input :template)))
+           (parts nil))
+        ;; Remove template from options to avoid infinite recursion
+        (setq chat-input (plist-put chat-input :template nil))
+        ;; Use cl-loop for better control flow
+        (cl-loop with last-pos = 0
+                 for match = (string-match "{{{\\(.+?\\)}}}" template last-pos)
+                 until (not match)
+                 for start-pos = (match-beginning 0)
+                 for end-pos = (match-end 0)
+                 for key = (match-string 1 template)
+                 do
+                 ;; Add text before placeholder
+                 (when (> start-pos last-pos)
+                     (push (substring template last-pos start-pos) parts))
+                 ;; Get and add replacement value
+                 (when-let* ((repl-fn (plist-get chat-input (intern key)))
+                             (value (funcall repl-fn context)))
+                     (push value parts))
+                 (setq last-pos end-pos)
+                 finally
+                 ;; Add remaining text after last match
+                 (push (substring template last-pos) parts))
+        ;; Join parts in reverse order
+        (apply #'concat (nreverse parts))))
 
 (defun minuet--make-context-filter-sequence (context len)
     "Create a filtering string based on CONTEXT with maximum length LEN."
@@ -851,19 +901,22 @@ to be called when completion items arrive."
     (minuet--with-temp-response
      (push
       (plz 'post (plist-get options :end-point)
-          :headers `(("Content-Type" . "application/json")
-                     ("Accept" . "application/json")
-                     ("Authorization" . ,(concat "Bearer " 
(minuet--get-api-key (plist-get options :api-key)))))
+          :headers
+          `(("Content-Type" . "application/json")
+            ("Accept" . "application/json")
+            ("Authorization" . ,(concat "Bearer " (minuet--get-api-key 
(plist-get options :api-key)))))
           :timeout minuet-request-timeout
-          :body (json-serialize `(,@(plist-get options :optional)
-                                  :stream t
-                                  :model ,(plist-get options :model)
-                                  :messages ,(vconcat
-                                              `((:role "system"
-                                                 :content 
,(minuet--make-system-prompt (plist-get options :system)))
-                                                ,@(minuet--eval-value 
(plist-get options :fewshots))
-                                                (:role "user"
-                                                 :content 
,(minuet--make-chat-llm-shot context))))))
+          :body
+          (json-serialize
+           `(,@(plist-get options :optional)
+             :stream t
+             :model ,(plist-get options :model)
+             :messages ,(vconcat
+                         `((:role "system"
+                            :content ,(minuet--make-system-prompt (plist-get 
options :system)))
+                           ,@(minuet--eval-value (plist-get options :fewshots))
+                           (:role "user"
+                            :content ,(minuet--make-chat-llm-shot context 
options))))))
           :as 'string
           :filter (minuet--make-process-stream-filter --response--)
           :then
@@ -914,16 +967,18 @@ to be called when completion items arrive."
                      ("x-api-key" . ,(minuet--get-api-key (plist-get 
minuet-claude-options :api-key)))
                      ("anthropic-version" . "2023-06-01"))
           :timeout minuet-request-timeout
-          :body (json-serialize (let ((options (copy-tree 
minuet-claude-options)))
-                                    `(,@(plist-get options :optional)
-                                      :stream t
-                                      :model ,(plist-get options :model)
-                                      :system ,(minuet--make-system-prompt 
(plist-get options :system))
-                                      :max_tokens ,(plist-get options 
:max_tokens)
-                                      :messages ,(vconcat
-                                                  `(,@(minuet--eval-value 
(plist-get options :fewshots))
-                                                    (:role "user"
-                                                     :content 
,(minuet--make-chat-llm-shot context)))))))
+          :body
+          (json-serialize
+           (let ((options (copy-tree minuet-claude-options)))
+               `(,@(plist-get options :optional)
+                 :stream t
+                 :model ,(plist-get options :model)
+                 :system ,(minuet--make-system-prompt (plist-get options 
:system))
+                 :max_tokens ,(plist-get options :max_tokens)
+                 :messages ,(vconcat
+                             `(,@(minuet--eval-value (plist-get options 
:fewshots))
+                               (:role "user"
+                                :content ,(minuet--make-chat-llm-shot context 
minuet-claude-options)))))))
           :as 'string
           :filter (minuet--make-process-stream-filter --response--)
           :then
@@ -964,22 +1019,23 @@ to be called when completion items arrive."
           :headers `(("Content-Type" . "application/json")
                      ("Accept" . "application/json"))
           :timeout minuet-request-timeout
-          :body (json-serialize
-                 (let* ((options (copy-tree minuet-gemini-options))
-                        (fewshots (minuet--eval-value (plist-get options 
:fewshots)))
-                        (fewshots (mapcar
-                                   (lambda (shot)
-                                       `(:role
-                                         ,(if (equal (plist-get shot :role) 
"user") "user" "model")
-                                         :parts
-                                         [(:text ,(plist-get shot :content))]))
-                                   fewshots)))
-                     `(,@(plist-get options :optional)
-                       :system_instruction (:parts (:text 
,(minuet--make-system-prompt (plist-get options :system))))
-                       :contents ,(vconcat
-                                   `(,@fewshots
-                                     (:role "user"
-                                      :parts [(:text 
,(minuet--make-chat-llm-shot context))]))))))
+          :body
+          (json-serialize
+           (let* ((options (copy-tree minuet-gemini-options))
+                  (fewshots (minuet--eval-value (plist-get options :fewshots)))
+                  (fewshots (mapcar
+                             (lambda (shot)
+                                 `(:role
+                                   ,(if (equal (plist-get shot :role) "user") 
"user" "model")
+                                   :parts
+                                   [(:text ,(plist-get shot :content))]))
+                             fewshots)))
+               `(,@(plist-get options :optional)
+                 :system_instruction (:parts (:text 
,(minuet--make-system-prompt (plist-get options :system))))
+                 :contents ,(vconcat
+                             `(,@fewshots
+                               (:role "user"
+                                :parts [(:text ,(minuet--make-chat-llm-shot 
context minuet-gemini-options))]))))))
           :as 'string
           :filter (minuet--make-process-stream-filter --response--)
           :then
@@ -1043,6 +1099,18 @@ to be called when completion items arrive."
     "Default function to generate suffix for FIM completions from CTX."
     (plist-get ctx :after-cursor))
 
+(defun minuet--default-chat-input-language-and-tab-function (ctx)
+    "Default function to get language and tab style from CTX."
+    (plist-get ctx :additional))
+
+(defun minuet--default-chat-input-before-cursor-function (ctx)
+    "Default function to get before cursor from CTX."
+    (plist-get ctx :before-cursor))
+
+(defun minuet--default-chat-input-after-cursor-function (ctx)
+    "Default function to get after cursor from CTX."
+    (plist-get ctx :after-cursor))
+
 (defun minuet--cleanup-auto-suggestion ()
     "Clean up auto-suggestion timers and hooks."
     (remove-hook 'post-command-hook #'minuet--maybe-show-suggestion t)

Reply via email to