emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/gptel 2982ede17d 255/273: gptel-org: Add gptel-org


From: ELPA Syncer
Subject: [nongnu] elpa/gptel 2982ede17d 255/273: gptel-org: Add gptel-org
Date: Wed, 1 May 2024 10:02:45 -0400 (EDT)

branch: elpa/gptel
commit 2982ede17d449c3d06cb7b99aa6ff9ebc5d8ce4f
Author: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
Commit: Karthik Chikmagalur <karthikchikmagalur@gmail.com>

    gptel-org: Add gptel-org
    
    * gptel.el (gptel--create-prompt): Split the Org mode logic in
    `gptel--create-prompt` into gptel-org.el.
    
    * gptel-org.el (gptel-org-branching-context,
    gptel-org--create-prompt): Handle prompt creation for Org buffers
    in `gptel-org--create-prompt`.  The option
    `gptel-org-branching-context` limits the context in Org buffers to
    the direct ancestors of the active heading.  This is useful for
    conversations with branches.  gptel-org.el is intended to
    eventually contain all the Org mode specific gptel code, and will
    not be loaded unless gptel is called in an Org buffer.
---
 gptel-org.el | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gptel.el     |  22 ++++----
 2 files changed, 178 insertions(+), 11 deletions(-)

diff --git a/gptel-org.el b/gptel-org.el
new file mode 100644
index 0000000000..63ab4aaa7f
--- /dev/null
+++ b/gptel-org.el
@@ -0,0 +1,167 @@
+;;; gptel-org.el --- Org functions for gptel         -*- lexical-binding: t; 
-*-
+
+;; Copyright (C) 2024  Karthik Chikmagalur
+
+;; Author: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
+;; Keywords: 
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; 
+
+;;; Code:
+(eval-when-compile (require 'cl-lib))
+(require 'org-element)
+(require 'outline)
+
+(declare-function org-at-heading-p "org")
+
+
+;;; User options
+(defcustom gptel-org-branching-context nil
+  "Use the lineage of the current heading as the context for gptel in Org 
buffers.
+
+This makes each same level heading a separate conversation
+branch.
+
+By default, gptel uses a linear context: all the text up to the
+cursor is sent to the LLM.  Enabling this option makes the
+context the hierarchical lineage of the current Org heading.  In
+this example:
+
+-----
+Top level text
+
+* Heading 1
+heading 1 text
+
+* Heading 2
+heading 2 text
+
+** Heading 2.1
+heading 2.1 text
+** Heading 2.2
+heading 2.2 text
+-----
+
+With the cursor at the end of the buffer, the text sent to the
+LLM will be limited to
+
+-----
+Top level text
+
+* Heading 2
+heading 2 text
+
+** Heading 2.2
+heading 2.2 text
+-----
+
+This makes it feasible to have multiple conversation branches."
+  :local t
+  :type 'boolean
+  :group 'gptel)
+
+
+;;; Setting context and creating queries
+(defun gptel-org--get-topic-start ()
+  "If a conversation topic is set, return it."
+  (when (org-entry-get (point) "GPTEL_TOPIC" 'inherit)
+    (marker-position org-entry-property-inherited-from)))
+
+(defun gptel-org-set-topic (topic)
+  "Set a topic and limit this conversation to the current heading.
+
+This limits the context sent to the LLM to the text between the
+current heading and the cursor position."
+  (interactive
+   (list
+    (progn
+      (or (derived-mode-p 'org-mode)
+          (user-error "Support for multiple topics per buffer is only 
implemented for `org-mode'."))
+      (completing-read "Set topic as: "
+                       (org-property-values "GPTEL_TOPIC")
+                       nil nil (downcase
+                                (truncate-string-to-width
+                                 (substring-no-properties
+                                  (replace-regexp-in-string
+                                   "\\s-+" "-"
+                                   (org-get-heading)))
+                                 50))))))
+  (when (stringp topic) (org-set-property "GPTEL_TOPIC" topic)))
+
+;; NOTE: This can be converted to a cl-defmethod for `gptel--parse-buffer'
+;; (conceptually cleaner), but will cause load-order issues in gptel.el and
+;; might be harder to debug.
+(defun gptel-org--create-prompt (&optional prompt-end)
+  "Return a full conversation prompt from the contents of this Org buffer.
+
+If `gptel--num-messages-to-send' is set, limit to that many
+recent exchanges.
+
+The prompt is constructed from the contents of the buffer up to
+point, or PROMPT-END if provided.  Its contents depend on the
+value of `gptel-org-branching-context', which see."
+  (unless prompt-end (setq prompt-end (point)))
+  (let ((max-entries (and gptel--num-messages-to-send
+                          (* 2 gptel--num-messages-to-send)))
+        (topic-start (gptel--get-topic-start)))
+    (when topic-start
+      ;; narrow to GPTEL_TOPIC property scope
+      (narrow-to-region topic-start prompt-end))
+    (if gptel-org-branching-context
+        ;; Create prompt from direct ancestors of point
+        (save-excursion
+          (let* ((org-buf (current-buffer))
+                 (start-bounds (org-element-lineage-map
+                                   (org-element-at-point) #'org-element-begin
+                                 '(headline org-data) 'with-self))
+                 (end-bounds
+                  (cl-loop
+                   for pos in (cdr start-bounds)
+                   while
+                   (and (>= pos (point-min)) ;respect narrowing
+                        (goto-char pos)
+                        ;; org-element-lineage always returns an extra
+                        ;; (org-data) element at point 1.  If there is also a
+                        ;; heading here, it is either a false positive or we
+                        ;; would be double counting it.  So we reject this node
+                        ;; when also at a heading.
+                        (not (and (eq pos 1) (org-at-heading-p))))
+                   do (outline-next-heading)
+                   collect (point) into ends
+                   finally return (cons prompt-end ends))))
+            (with-temp-buffer
+              (setq-local gptel-backend
+                          (buffer-local-value 'gptel-backend org-buf)
+                          gptel--system-message
+                          (buffer-local-value 'gptel--system-message org-buf)
+                          gptel-model
+                          (buffer-local-value 'gptel-model org-buf))
+              (cl-loop for start in start-bounds
+                       for end   in end-bounds
+                       do (insert-buffer-substring org-buf start end)
+                       (goto-char (point-min)))
+              (goto-char (point-max))
+              (let ((major-mode 'org-mode))
+                (gptel--parse-buffer gptel-backend max-entries)))))
+      ;; Create prompt the usual way
+      (gptel--parse-buffer gptel-backend max-entries))))
+
+
+
+(provide 'gptel-org)
+;;; gptel-org.el ends here
diff --git a/gptel.el b/gptel.el
index 8a014a4bb4..07fb917137 100644
--- a/gptel.el
+++ b/gptel.el
@@ -1097,19 +1097,19 @@ If PROMPT-END (a marker) is provided, end the prompt 
contents
 there."
   (save-excursion
     (save-restriction
-      (cond
-       ((use-region-p)
-        ;; Narrow to region
-        (narrow-to-region (region-beginning) (region-end))
-        (goto-char (point-max)))
-       ((when-let ((topic-start (gptel--get-topic-start)))
-          ;; Narrow to topic
-          (narrow-to-region topic-start (or prompt-end (point-max)))
-          (goto-char (point-max))))
-       (t (goto-char (or prompt-end (point-max)))))
       (let ((max-entries (and gptel--num-messages-to-send
                               (* 2 gptel--num-messages-to-send))))
-        (gptel--parse-buffer gptel-backend max-entries)))))
+        (cond
+         ((use-region-p)
+          ;; Narrow to region
+          (narrow-to-region (region-beginning) (region-end))
+          (goto-char (point-max))
+          (gptel--parse-buffer gptel-backend max-entries))
+         ((derived-mode-p 'org-mode)
+          (require 'gptel-org)
+          (gptel-org--create-prompt (or prompt-end (point-max))))
+         (t (goto-char (or prompt-end (point-max)))
+            (gptel--parse-buffer gptel-backend max-entries)))))))
 
 (cl-defgeneric gptel--parse-buffer (backend max-entries)
   "Parse current buffer backwards from point and return a list of prompts.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]