Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
|
45aae4f721 | ||
|
040ef0a35d | ||
|
28ac88cada | ||
|
a4af87f908 |
5 changed files with 275 additions and 275 deletions
|
@ -36,6 +36,8 @@
|
||||||
(declare-function json-read "json" ())
|
(declare-function json-read "json" ())
|
||||||
(defvar json-object-type)
|
(defvar json-object-type)
|
||||||
|
|
||||||
|
(declare-function gptel--stream-convert-markdown->org "gptel-org")
|
||||||
|
|
||||||
(defconst gptel-curl--common-args
|
(defconst gptel-curl--common-args
|
||||||
(if (memq system-type '(windows-nt ms-dos))
|
(if (memq system-type '(windows-nt ms-dos))
|
||||||
'("--disable" "--location" "--silent" "-XPOST"
|
'("--disable" "--location" "--silent" "-XPOST"
|
||||||
|
|
204
gptel-org.el
204
gptel-org.el
|
@ -3,7 +3,7 @@
|
||||||
;; Copyright (C) 2024 Karthik Chikmagalur
|
;; Copyright (C) 2024 Karthik Chikmagalur
|
||||||
|
|
||||||
;; Author: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
|
;; Author: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
|
||||||
;; Keywords:
|
;; Keywords:
|
||||||
|
|
||||||
;; This program is free software; you can redistribute it and/or modify
|
;; 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
|
;; it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,13 +20,26 @@
|
||||||
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
|
|
||||||
;;
|
;;
|
||||||
|
|
||||||
;;; Code:
|
;;; Code:
|
||||||
(eval-when-compile (require 'cl-lib))
|
(eval-when-compile (require 'cl-lib))
|
||||||
(require 'org-element)
|
(require 'org-element)
|
||||||
(require 'outline)
|
(require 'outline)
|
||||||
|
|
||||||
|
(declare-function org-element-begin "org-element")
|
||||||
|
(declare-function org-element-lineage-map "org-element-ast")
|
||||||
|
|
||||||
|
;; Functions used for saving/restoring gptel state in Org buffers
|
||||||
|
(defvar org-entry-property-inherited-from)
|
||||||
|
(declare-function org-entry-get "org")
|
||||||
|
(declare-function org-entry-put "org")
|
||||||
|
(declare-function org-with-wide-buffer "org-macs")
|
||||||
|
(declare-function org-set-property "org")
|
||||||
|
(declare-function org-property-values "org")
|
||||||
|
(declare-function org-open-line "org")
|
||||||
|
(declare-function org-at-heading-p "org")
|
||||||
|
(declare-function org-get-heading "org")
|
||||||
(declare-function org-at-heading-p "org")
|
(declare-function org-at-heading-p "org")
|
||||||
|
|
||||||
|
|
||||||
|
@ -118,7 +131,7 @@ value of `gptel-org-branching-context', which see."
|
||||||
(unless prompt-end (setq prompt-end (point)))
|
(unless prompt-end (setq prompt-end (point)))
|
||||||
(let ((max-entries (and gptel--num-messages-to-send
|
(let ((max-entries (and gptel--num-messages-to-send
|
||||||
(* 2 gptel--num-messages-to-send)))
|
(* 2 gptel--num-messages-to-send)))
|
||||||
(topic-start (gptel--get-topic-start)))
|
(topic-start (gptel-org--get-topic-start)))
|
||||||
(when topic-start
|
(when topic-start
|
||||||
;; narrow to GPTEL_TOPIC property scope
|
;; narrow to GPTEL_TOPIC property scope
|
||||||
(narrow-to-region topic-start prompt-end))
|
(narrow-to-region topic-start prompt-end))
|
||||||
|
@ -161,6 +174,31 @@ value of `gptel-org-branching-context', which see."
|
||||||
;; Create prompt the usual way
|
;; Create prompt the usual way
|
||||||
(gptel--parse-buffer gptel-backend max-entries))))
|
(gptel--parse-buffer gptel-backend max-entries))))
|
||||||
|
|
||||||
|
(defun gptel-org--send-with-props (send-fun &rest args)
|
||||||
|
"Conditionally modify SEND-FUN's calling environment.
|
||||||
|
|
||||||
|
If in an Org buffer under a heading containing a stored gptel
|
||||||
|
configuration, use that for requests instead. This includes the
|
||||||
|
system message, model and provider (backend), among other
|
||||||
|
parameters."
|
||||||
|
(if (derived-mode-p 'org-mode)
|
||||||
|
(pcase-let ((`(,gptel--system-message ,gptel-backend ,gptel-model
|
||||||
|
,gptel-temperature ,gptel-max-tokens)
|
||||||
|
(seq-mapn (lambda (a b) (or a b))
|
||||||
|
(gptel-org--entry-properties)
|
||||||
|
(list gptel--system-message gptel-backend gptel-model
|
||||||
|
gptel-temperature gptel-max-tokens))))
|
||||||
|
(apply send-fun args))
|
||||||
|
(apply send-fun args)))
|
||||||
|
|
||||||
|
(advice-add 'gptel-send :around #'gptel-org--send-with-props)
|
||||||
|
(advice-add 'gptel--suffix-send :around #'gptel-org--send-with-props)
|
||||||
|
|
||||||
|
;; ;; NOTE: Basic uses in org-mode are covered by advising gptel-send and
|
||||||
|
;; ;; gptel--suffix-send. For custom commands it might be necessary to advise
|
||||||
|
;; ;; gptel-request instead.
|
||||||
|
;; (advice-add 'gptel-request :around #'gptel-org--send-with-props)
|
||||||
|
|
||||||
|
|
||||||
;;; Saving and restoring state
|
;;; Saving and restoring state
|
||||||
(defun gptel-org--entry-properties (&optional pt)
|
(defun gptel-org--entry-properties (&optional pt)
|
||||||
|
@ -181,14 +219,6 @@ value of `gptel-org-branching-context', which see."
|
||||||
(when tokens (setq tokens (gptel--numberize tokens)))
|
(when tokens (setq tokens (gptel--numberize tokens)))
|
||||||
(list system backend model temperature tokens)))
|
(list system backend model temperature tokens)))
|
||||||
|
|
||||||
;; (pcase-let ((`(,gptel--system-message ,gptel-backend
|
|
||||||
;; ,gptel-model ,gptel-temperature)
|
|
||||||
;; (if (derived-mode-p 'org-mode)
|
|
||||||
;; (progn (require 'gptel-org)
|
|
||||||
;; (gptel-org--entry-properties))
|
|
||||||
;; `(,gptel--system-message ,gptel-backend
|
|
||||||
;; ,gptel-model ,gptel-temperature)))))
|
|
||||||
|
|
||||||
(defun gptel-org--restore-state ()
|
(defun gptel-org--restore-state ()
|
||||||
"Restore gptel state for Org buffers when turning on `gptel-mode'."
|
"Restore gptel state for Org buffers when turning on `gptel-mode'."
|
||||||
(save-restriction
|
(save-restriction
|
||||||
|
@ -260,7 +290,157 @@ non-nil (default), display a message afterwards."
|
||||||
(> attempts 0))
|
(> attempts 0))
|
||||||
(funcall write-bounds (1- attempts)))))))
|
(funcall write-bounds (1- attempts)))))))
|
||||||
(funcall write-bounds 6))))
|
(funcall write-bounds 6))))
|
||||||
|
|
||||||
|
|
||||||
|
;;; Transforming responses
|
||||||
|
(defun gptel--convert-markdown->org (str)
|
||||||
|
"Convert string STR from markdown to org markup.
|
||||||
|
|
||||||
|
This is a very basic converter that handles only a few markup
|
||||||
|
elements."
|
||||||
|
(interactive)
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert str)
|
||||||
|
(goto-char (point-min))
|
||||||
|
(while (re-search-forward "`\\|\\*\\{1,2\\}\\|_" nil t)
|
||||||
|
(pcase (match-string 0)
|
||||||
|
("`" (if (save-excursion
|
||||||
|
(beginning-of-line)
|
||||||
|
(skip-chars-forward " \t")
|
||||||
|
(looking-at "```"))
|
||||||
|
(progn (backward-char)
|
||||||
|
(delete-char 3)
|
||||||
|
(insert "#+begin_src ")
|
||||||
|
(when (re-search-forward "^```" nil t)
|
||||||
|
(replace-match "#+end_src")))
|
||||||
|
(replace-match "=")))
|
||||||
|
("**" (cond
|
||||||
|
((looking-at "\\*\\(?:[[:word:]]\\|\s\\)")
|
||||||
|
(delete-char 1))
|
||||||
|
((looking-back "\\(?:[[:word:]]\\|\s\\)\\*\\{2\\}"
|
||||||
|
(max (- (point) 3) (point-min)))
|
||||||
|
(delete-char -1))))
|
||||||
|
("*"
|
||||||
|
(cond
|
||||||
|
((save-match-data
|
||||||
|
(and (looking-back "\\(?:[[:space:]]\\|\s\\)\\(?:_\\|\\*\\)"
|
||||||
|
(max (- (point) 2) (point-min)))
|
||||||
|
(not (looking-at "[[:space:]]\\|\s"))))
|
||||||
|
;; Possible beginning of emphasis
|
||||||
|
(and
|
||||||
|
(save-excursion
|
||||||
|
(when (and (re-search-forward (regexp-quote (match-string 0))
|
||||||
|
(line-end-position) t)
|
||||||
|
(looking-at "[[:space]]\\|\s")
|
||||||
|
(not (looking-back "\\(?:[[:space]]\\|\s\\)\\(?:_\\|\\*\\)"
|
||||||
|
(max (- (point) 2) (point-min)))))
|
||||||
|
(delete-char -1) (insert "/") t))
|
||||||
|
(progn (delete-char -1) (insert "/"))))
|
||||||
|
((save-excursion
|
||||||
|
(ignore-errors (backward-char 2))
|
||||||
|
(looking-at "\\(?:$\\|\\`\\)\n\\*[[:space:]]"))
|
||||||
|
;; Bullet point, replace with hyphen
|
||||||
|
(delete-char -1) (insert "-"))))))
|
||||||
|
(buffer-string)))
|
||||||
|
|
||||||
|
(defun gptel--replace-source-marker (num-ticks &optional end)
|
||||||
|
"Replace markdown style backticks with Org equivalents.
|
||||||
|
|
||||||
|
NUM-TICKS is the number of backticks being replaced. If END is
|
||||||
|
true these are \"ending\" backticks.
|
||||||
|
|
||||||
|
This is intended for use in the markdown to org stream converter."
|
||||||
|
(let ((from (match-beginning 0)))
|
||||||
|
(delete-region from (point))
|
||||||
|
(if (and (= num-ticks 3)
|
||||||
|
(save-excursion (beginning-of-line)
|
||||||
|
(skip-chars-forward " \t")
|
||||||
|
(eq (point) from)))
|
||||||
|
(insert (if end "#+end_src" "#+begin_src "))
|
||||||
|
(insert "="))))
|
||||||
|
|
||||||
|
(defun gptel--stream-convert-markdown->org ()
|
||||||
|
"Return a Markdown to Org converter.
|
||||||
|
|
||||||
|
This function parses a stream of Markdown text to Org
|
||||||
|
continuously when it is called with successive chunks of the
|
||||||
|
text stream."
|
||||||
|
(letrec ((in-src-block nil) ;explicit nil to address BUG #183
|
||||||
|
(temp-buf (generate-new-buffer-name "*gptel-temp*"))
|
||||||
|
(start-pt (make-marker))
|
||||||
|
(ticks-total 0)
|
||||||
|
(cleanup-fn
|
||||||
|
(lambda (&rest _)
|
||||||
|
(when (buffer-live-p (get-buffer temp-buf))
|
||||||
|
(set-marker start-pt nil)
|
||||||
|
(kill-buffer temp-buf))
|
||||||
|
(remove-hook 'gptel-post-response-functions cleanup-fn))))
|
||||||
|
(add-hook 'gptel-post-response-functions cleanup-fn)
|
||||||
|
(lambda (str)
|
||||||
|
(let ((noop-p) (ticks 0))
|
||||||
|
(with-current-buffer (get-buffer-create temp-buf)
|
||||||
|
(save-excursion (goto-char (point-max)) (insert str))
|
||||||
|
(when (marker-position start-pt) (goto-char start-pt))
|
||||||
|
(when in-src-block (setq ticks ticks-total))
|
||||||
|
(save-excursion
|
||||||
|
(while (re-search-forward "`\\|\\*\\{1,2\\}\\|_" nil t)
|
||||||
|
(pcase (match-string 0)
|
||||||
|
("`"
|
||||||
|
;; Count number of consecutive backticks
|
||||||
|
(backward-char)
|
||||||
|
(while (and (char-after) (eq (char-after) ?`))
|
||||||
|
(forward-char)
|
||||||
|
(if in-src-block (cl-decf ticks) (cl-incf ticks)))
|
||||||
|
;; Set the verbatim state of the parser
|
||||||
|
(if (and (eobp)
|
||||||
|
;; Special case heuristic: If the response ends with
|
||||||
|
;; ^``` we don't wait for more input.
|
||||||
|
;; FIXME: This can have false positives.
|
||||||
|
(not (save-excursion (beginning-of-line)
|
||||||
|
(looking-at "^```$"))))
|
||||||
|
;; End of input => there could be more backticks coming,
|
||||||
|
;; so we wait for more input
|
||||||
|
(progn (setq noop-p t) (set-marker start-pt (match-beginning 0)))
|
||||||
|
;; We reached a character other than a backtick
|
||||||
|
(cond
|
||||||
|
;; Ticks balanced, end src block
|
||||||
|
((= ticks 0)
|
||||||
|
(progn (setq in-src-block nil)
|
||||||
|
(gptel--replace-source-marker ticks-total 'end)))
|
||||||
|
;; Positive number of ticks, start an src block
|
||||||
|
((and (> ticks 0) (not in-src-block))
|
||||||
|
(setq ticks-total ticks
|
||||||
|
in-src-block t)
|
||||||
|
(gptel--replace-source-marker ticks-total))
|
||||||
|
;; Negative number of ticks or in a src block already,
|
||||||
|
;; reset ticks
|
||||||
|
(t (setq ticks ticks-total)))))
|
||||||
|
;; Handle other chars: emphasis, bold and bullet items
|
||||||
|
((and "**" (guard (not in-src-block)))
|
||||||
|
(cond
|
||||||
|
((looking-at "\\*\\(?:[[:word:]]\\|\s\\)")
|
||||||
|
(delete-char 1))
|
||||||
|
((looking-back "\\(?:[[:word:]]\\|\s\\)\\*\\{2\\}"
|
||||||
|
(max (- (point) 3) (point-min)))
|
||||||
|
(delete-char -1))))
|
||||||
|
((and "*" (guard (not in-src-block)))
|
||||||
|
(save-match-data
|
||||||
|
(save-excursion
|
||||||
|
(ignore-errors (backward-char 2))
|
||||||
|
(cond
|
||||||
|
((or (looking-at
|
||||||
|
"[^[:space:][:punct:]\n]\\(?:_\\|\\*\\)\\(?:[[:space:][:punct:]]\\|$\\)")
|
||||||
|
(looking-at
|
||||||
|
"\\(?:[[:space:][:punct:]]\\)\\(?:_\\|\\*\\)\\([^[:space:][:punct:]]\\|$\\)"))
|
||||||
|
;; Emphasis, replace with slashes
|
||||||
|
(forward-char 2) (delete-char -1) (insert "/"))
|
||||||
|
((looking-at "\\(?:$\\|\\`\\)\n\\*[[:space:]]")
|
||||||
|
;; Bullet point, replace with hyphen
|
||||||
|
(forward-char 2) (delete-char -1) (insert "-")))))))))
|
||||||
|
(if noop-p
|
||||||
|
(buffer-substring (point) start-pt)
|
||||||
|
(prog1 (buffer-substring (point) (point-max))
|
||||||
|
(set-marker start-pt (point-max)))))))))
|
||||||
|
|
||||||
(provide 'gptel-org)
|
(provide 'gptel-org)
|
||||||
;;; gptel-org.el ends here
|
;;; gptel-org.el ends here
|
||||||
|
|
|
@ -336,24 +336,19 @@ Also format its value in the Transient menu."
|
||||||
(lambda ()
|
(lambda ()
|
||||||
"Inspect the query that will be sent as a lisp object."
|
"Inspect the query that will be sent as a lisp object."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let* ((extra (gptel--get-directive
|
(gptel--sanitize-model)
|
||||||
(transient-args
|
(gptel--inspect-query
|
||||||
transient-current-command)))
|
(gptel--suffix-send
|
||||||
(gptel--system-message
|
(cons "I" (transient-args transient-current-command))))))
|
||||||
(concat gptel--system-message extra)))
|
|
||||||
(gptel--sanitize-model)
|
|
||||||
(gptel--inspect-query))))
|
|
||||||
("J" "Inspect query (JSON)"
|
("J" "Inspect query (JSON)"
|
||||||
(lambda ()
|
(lambda ()
|
||||||
"Inspect the query that will be sent as a JSON object."
|
"Inspect the query that will be sent as a JSON object."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let* ((extra (gptel--get-directive
|
(gptel--sanitize-model)
|
||||||
(transient-args
|
(gptel--inspect-query
|
||||||
transient-current-command)))
|
(gptel--suffix-send
|
||||||
(gptel--system-message
|
(cons "I" (transient-args transient-current-command)))
|
||||||
(concat gptel--system-message extra)))
|
'json)))]]
|
||||||
(gptel--sanitize-model)
|
|
||||||
(gptel--inspect-query 'json))))]]
|
|
||||||
(interactive)
|
(interactive)
|
||||||
(gptel--sanitize-model)
|
(gptel--sanitize-model)
|
||||||
(transient-setup 'gptel-menu))
|
(transient-setup 'gptel-menu))
|
||||||
|
@ -594,6 +589,7 @@ Or in an extended conversation:
|
||||||
(buffer) (position)
|
(buffer) (position)
|
||||||
(callback) (gptel-buffer-name)
|
(callback) (gptel-buffer-name)
|
||||||
(system-extra (gptel--get-directive args))
|
(system-extra (gptel--get-directive args))
|
||||||
|
(dry-run (and (member "I" args) t))
|
||||||
;; Input redirection: grab prompt from elsewhere?
|
;; Input redirection: grab prompt from elsewhere?
|
||||||
(prompt
|
(prompt
|
||||||
(cond
|
(cond
|
||||||
|
@ -689,44 +685,47 @@ Or in an extended conversation:
|
||||||
(setq buffer (get-buffer-create gptel-buffer-name))
|
(setq buffer (get-buffer-create gptel-buffer-name))
|
||||||
(with-current-buffer buffer (setq position (point)))))
|
(with-current-buffer buffer (setq position (point)))))
|
||||||
|
|
||||||
(gptel-request
|
(prog1 (gptel-request prompt
|
||||||
prompt
|
:buffer (or buffer (current-buffer))
|
||||||
:buffer (or buffer (current-buffer))
|
:position position
|
||||||
:position position
|
:in-place (and in-place (not output-to-other-buffer-p))
|
||||||
:in-place (and in-place (not output-to-other-buffer-p))
|
:stream stream
|
||||||
:stream stream
|
:system (concat gptel--system-message system-extra)
|
||||||
:system (concat gptel--system-message system-extra)
|
:callback callback
|
||||||
:callback callback)
|
:dry-run dry-run)
|
||||||
|
|
||||||
;; NOTE: Possible future race condition here if Emacs ever drops the GIL.
|
;; NOTE: Possible future race condition here if Emacs ever drops the GIL.
|
||||||
;; The HTTP request callback might modify the buffer before the in-place
|
;; The HTTP request callback might modify the buffer before the in-place
|
||||||
;; text is killed below.
|
;; text is killed below.
|
||||||
(when in-place
|
(when in-place
|
||||||
;; Kill the latest prompt
|
;; Kill the latest prompt
|
||||||
(let ((beg
|
(let ((beg
|
||||||
(if (use-region-p)
|
(if (use-region-p)
|
||||||
(region-beginning)
|
(region-beginning)
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(text-property-search-backward
|
(text-property-search-backward
|
||||||
'gptel 'response
|
'gptel 'response
|
||||||
(when (get-char-property (max (point-min) (1- (point)))
|
(when (get-char-property (max (point-min) (1- (point)))
|
||||||
'gptel)
|
'gptel)
|
||||||
t))
|
t))
|
||||||
(point))))
|
(point))))
|
||||||
(end (if (use-region-p) (region-end) (point))))
|
(end (if (use-region-p) (region-end) (point))))
|
||||||
(unless output-to-other-buffer-p
|
(unless output-to-other-buffer-p
|
||||||
;; store the killed text in gptel-history
|
;; store the killed text in gptel-history
|
||||||
(gptel--attach-response-history
|
(gptel--attach-response-history
|
||||||
(list (buffer-substring-no-properties beg end))))
|
(list (buffer-substring-no-properties beg end))))
|
||||||
(kill-region beg end)))
|
(kill-region beg end)))
|
||||||
|
|
||||||
(when output-to-other-buffer-p
|
(when output-to-other-buffer-p
|
||||||
(message (concat "Prompt sent to buffer: "
|
(message (concat "Prompt sent to buffer: "
|
||||||
(propertize gptel-buffer-name 'face 'help-key-binding)))
|
(propertize gptel-buffer-name 'face 'help-key-binding)))
|
||||||
(display-buffer
|
(display-buffer
|
||||||
buffer '((display-buffer-reuse-window
|
buffer '((display-buffer-reuse-window
|
||||||
display-buffer-pop-up-window)
|
display-buffer-pop-up-window)
|
||||||
(reusable-frames . visible))))))
|
(reusable-frames . visible)))))))
|
||||||
|
|
||||||
|
;; Allow calling from elisp
|
||||||
|
(put 'gptel--suffix-send 'interactive-only nil)
|
||||||
|
|
||||||
;; ** Suffix to regenerate response
|
;; ** Suffix to regenerate response
|
||||||
|
|
||||||
|
|
242
gptel.el
242
gptel.el
|
@ -3,7 +3,7 @@
|
||||||
;; Copyright (C) 2023 Karthik Chikmagalur
|
;; Copyright (C) 2023 Karthik Chikmagalur
|
||||||
|
|
||||||
;; Author: Karthik Chikmagalur
|
;; Author: Karthik Chikmagalur
|
||||||
;; Version: 0.7.0
|
;; Version: 0.8.5
|
||||||
;; Package-Requires: ((emacs "27.1") (transient "0.4.0") (compat "29.1.4.1"))
|
;; Package-Requires: ((emacs "27.1") (transient "0.4.0") (compat "29.1.4.1"))
|
||||||
;; Keywords: convenience
|
;; Keywords: convenience
|
||||||
;; URL: https://github.com/karthink/gptel
|
;; URL: https://github.com/karthink/gptel
|
||||||
|
@ -111,19 +111,18 @@
|
||||||
(declare-function gptel-system-prompt "gptel-transient")
|
(declare-function gptel-system-prompt "gptel-transient")
|
||||||
(declare-function pulse-momentary-highlight-region "pulse")
|
(declare-function pulse-momentary-highlight-region "pulse")
|
||||||
|
|
||||||
;; Functions used for saving/restoring gptel state in Org buffers
|
|
||||||
(defvar org-entry-property-inherited-from)
|
|
||||||
(declare-function org-entry-get "org")
|
|
||||||
(declare-function org-entry-put "org")
|
|
||||||
(declare-function org-with-wide-buffer "org-macs")
|
|
||||||
(declare-function org-set-property "org")
|
|
||||||
(declare-function org-property-values "org")
|
|
||||||
(declare-function org-open-line "org")
|
|
||||||
(declare-function org-at-heading-p "org")
|
|
||||||
(declare-function org-get-heading "org")
|
|
||||||
(declare-function ediff-make-cloned-buffer "ediff-util")
|
(declare-function ediff-make-cloned-buffer "ediff-util")
|
||||||
(declare-function ediff-regions-internal "ediff")
|
(declare-function ediff-regions-internal "ediff")
|
||||||
|
|
||||||
|
(declare-function gptel-org--create-prompt "gptel-org")
|
||||||
|
(declare-function gptel-org-set-topic "gptel-org")
|
||||||
|
(declare-function gptel-org--save-state "gptel-org")
|
||||||
|
(declare-function gptel-org--restore-state "gptel-org")
|
||||||
|
(declare-function gptel--stream-convert-markdown->org "gptel-org")
|
||||||
|
(declare-function gptel--convert-markdown->org "gptel-org")
|
||||||
|
(define-obsolete-function-alias
|
||||||
|
'gptel-set-topic 'gptel-org-set-topic "0.7.5")
|
||||||
|
|
||||||
(eval-when-compile
|
(eval-when-compile
|
||||||
(require 'subr-x)
|
(require 'subr-x)
|
||||||
(require 'cl-lib))
|
(require 'cl-lib))
|
||||||
|
@ -134,6 +133,9 @@
|
||||||
(require 'cl-generic)
|
(require 'cl-generic)
|
||||||
(require 'gptel-openai)
|
(require 'gptel-openai)
|
||||||
|
|
||||||
|
(with-eval-after-load 'org
|
||||||
|
(require 'gptel-org))
|
||||||
|
|
||||||
|
|
||||||
;; User options
|
;; User options
|
||||||
|
|
||||||
|
@ -652,9 +654,6 @@ Valid JSON unless NO-JSON is t."
|
||||||
|
|
||||||
;; Saving and restoring state
|
;; Saving and restoring state
|
||||||
|
|
||||||
(declare-function gptel-org--restore-state "gptel-org")
|
|
||||||
(declare-function gptel-org--save-state "gptel-org")
|
|
||||||
|
|
||||||
(defun gptel--restore-state ()
|
(defun gptel--restore-state ()
|
||||||
"Restore gptel state when turning on `gptel-mode'."
|
"Restore gptel state when turning on `gptel-mode'."
|
||||||
(when (buffer-file-name)
|
(when (buffer-file-name)
|
||||||
|
@ -932,29 +931,27 @@ waiting for the response."
|
||||||
(gptel--update-status " Waiting..." 'warning)))
|
(gptel--update-status " Waiting..." 'warning)))
|
||||||
|
|
||||||
(declare-function json-pretty-print-buffer "json")
|
(declare-function json-pretty-print-buffer "json")
|
||||||
(defun gptel--inspect-query (&optional arg)
|
(defun gptel--inspect-query (request-data &optional arg)
|
||||||
"Show the full LLM query to be sent in a new buffer.
|
"Show REQUEST-DATA, the full LLM query to be sent, in a buffer.
|
||||||
|
|
||||||
This functions as a dry run of `gptel-send'. If prefix ARG is
|
This functions as a dry run of `gptel-send'. If ARG is
|
||||||
the symbol json, show the encoded JSON query instead of the lisp
|
the symbol json, show the encoded JSON query instead of the lisp
|
||||||
structure gptel uses."
|
structure gptel uses."
|
||||||
(let* ((request-data
|
(with-current-buffer (get-buffer-create "*gptel-query*")
|
||||||
(gptel-request nil :stream gptel-stream :dry-run t)))
|
(let ((standard-output (current-buffer))
|
||||||
(with-current-buffer (get-buffer-create "*gptel-query*")
|
(inhibit-read-only t))
|
||||||
(let ((standard-output (current-buffer))
|
(buffer-disable-undo)
|
||||||
(inhibit-read-only t))
|
(erase-buffer)
|
||||||
(buffer-disable-undo)
|
(if (eq arg 'json)
|
||||||
(erase-buffer)
|
(progn (fundamental-mode)
|
||||||
(if (eq arg 'json)
|
(insert (gptel--json-encode request-data))
|
||||||
(progn (fundamental-mode)
|
(json-pretty-print-buffer))
|
||||||
(insert (gptel--json-encode request-data))
|
(lisp-data-mode)
|
||||||
(json-pretty-print-buffer))
|
(prin1 request-data)
|
||||||
(lisp-data-mode)
|
(pp-buffer))
|
||||||
(prin1 request-data)
|
(goto-char (point-min))
|
||||||
(pp-buffer))
|
(view-mode 1)
|
||||||
(goto-char (point-min))
|
(display-buffer (current-buffer) gptel-display-buffer-action))))
|
||||||
(view-mode 1)
|
|
||||||
(display-buffer (current-buffer) gptel-display-buffer-action)))))
|
|
||||||
|
|
||||||
(defun gptel--insert-response (response info)
|
(defun gptel--insert-response (response info)
|
||||||
"Insert the LLM RESPONSE into the gptel buffer.
|
"Insert the LLM RESPONSE into the gptel buffer.
|
||||||
|
@ -1012,37 +1009,6 @@ See `gptel--url-get-response' for details."
|
||||||
(with-current-buffer gptel-buffer
|
(with-current-buffer gptel-buffer
|
||||||
(run-hook-with-args 'gptel-post-response-functions response-beg response-end)))))
|
(run-hook-with-args 'gptel-post-response-functions response-beg response-end)))))
|
||||||
|
|
||||||
(defun gptel-set-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)
|
|
||||||
(pcase major-mode
|
|
||||||
('org-mode
|
|
||||||
(org-set-property
|
|
||||||
"GPTEL_TOPIC"
|
|
||||||
(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)))))
|
|
||||||
('markdown-mode
|
|
||||||
(message
|
|
||||||
"Support for multiple topics per buffer is not implemented for `markdown-mode'."))))
|
|
||||||
|
|
||||||
(defun gptel--get-topic-start ()
|
|
||||||
"If a conversation topic is set, return it."
|
|
||||||
(pcase major-mode
|
|
||||||
('org-mode
|
|
||||||
(when (org-entry-get (point) "GPTEL_TOPIC" 'inherit)
|
|
||||||
(marker-position org-entry-property-inherited-from)))
|
|
||||||
('markdown-mode nil)))
|
|
||||||
|
|
||||||
(defun gptel--create-prompt (&optional prompt-end)
|
(defun gptel--create-prompt (&optional prompt-end)
|
||||||
"Return a full conversation prompt from the contents of this buffer.
|
"Return a full conversation prompt from the contents of this buffer.
|
||||||
|
|
||||||
|
@ -1281,152 +1247,6 @@ INTERACTIVEP is t when gptel is called interactively."
|
||||||
(substitute-command-keys "\\[gptel-send]")))
|
(substitute-command-keys "\\[gptel-send]")))
|
||||||
(current-buffer)))
|
(current-buffer)))
|
||||||
|
|
||||||
(defun gptel--convert-markdown->org (str)
|
|
||||||
"Convert string STR from markdown to org markup.
|
|
||||||
|
|
||||||
This is a very basic converter that handles only a few markup
|
|
||||||
elements."
|
|
||||||
(interactive)
|
|
||||||
(with-temp-buffer
|
|
||||||
(insert str)
|
|
||||||
(goto-char (point-min))
|
|
||||||
(while (re-search-forward "`\\|\\*\\{1,2\\}\\|_" nil t)
|
|
||||||
(pcase (match-string 0)
|
|
||||||
("`" (if (looking-at "``")
|
|
||||||
(progn (backward-char)
|
|
||||||
(delete-char 3)
|
|
||||||
(insert "#+begin_src ")
|
|
||||||
(when (re-search-forward "^```" nil t)
|
|
||||||
(replace-match "#+end_src")))
|
|
||||||
(replace-match "=")))
|
|
||||||
("**" (cond
|
|
||||||
((looking-at "\\*\\(?:[[:word:]]\\|\s\\)")
|
|
||||||
(delete-char 1))
|
|
||||||
((looking-back "\\(?:[[:word:]]\\|\s\\)\\*\\{2\\}"
|
|
||||||
(max (- (point) 3) (point-min)))
|
|
||||||
(delete-char -1))))
|
|
||||||
("*"
|
|
||||||
(cond
|
|
||||||
((save-match-data
|
|
||||||
(and (looking-back "\\(?:[[:space:]]\\|\s\\)\\(?:_\\|\\*\\)"
|
|
||||||
(max (- (point) 2) (point-min)))
|
|
||||||
(not (looking-at "[[:space:]]\\|\s"))))
|
|
||||||
;; Possible beginning of emphasis
|
|
||||||
(and
|
|
||||||
(save-excursion
|
|
||||||
(when (and (re-search-forward (regexp-quote (match-string 0))
|
|
||||||
(line-end-position) t)
|
|
||||||
(looking-at "[[:space]]\\|\s")
|
|
||||||
(not (looking-back "\\(?:[[:space]]\\|\s\\)\\(?:_\\|\\*\\)"
|
|
||||||
(max (- (point) 2) (point-min)))))
|
|
||||||
(delete-char -1) (insert "/") t))
|
|
||||||
(progn (delete-char -1) (insert "/"))))
|
|
||||||
((save-excursion
|
|
||||||
(ignore-errors (backward-char 2))
|
|
||||||
(looking-at "\\(?:$\\|\\`\\)\n\\*[[:space:]]"))
|
|
||||||
;; Bullet point, replace with hyphen
|
|
||||||
(delete-char -1) (insert "-"))))))
|
|
||||||
(buffer-string)))
|
|
||||||
|
|
||||||
(defun gptel--replace-source-marker (num-ticks &optional end)
|
|
||||||
"Replace markdown style backticks with Org equivalents.
|
|
||||||
|
|
||||||
NUM-TICKS is the number of backticks being replaced. If END is
|
|
||||||
true these are \"ending\" backticks.
|
|
||||||
|
|
||||||
This is intended for use in the markdown to org stream converter."
|
|
||||||
(let ((from (match-beginning 0)))
|
|
||||||
(delete-region from (point))
|
|
||||||
(if (and (= num-ticks 3)
|
|
||||||
(save-excursion (beginning-of-line)
|
|
||||||
(skip-chars-forward " \t")
|
|
||||||
(eq (point) from)))
|
|
||||||
(insert (if end "#+end_src" "#+begin_src "))
|
|
||||||
(insert "="))))
|
|
||||||
|
|
||||||
(defun gptel--stream-convert-markdown->org ()
|
|
||||||
"Return a Markdown to Org converter.
|
|
||||||
|
|
||||||
This function parses a stream of Markdown text to Org
|
|
||||||
continuously when it is called with successive chunks of the
|
|
||||||
text stream."
|
|
||||||
(letrec ((in-src-block nil) ;explicit nil to address BUG #183
|
|
||||||
(temp-buf (generate-new-buffer-name "*gptel-temp*"))
|
|
||||||
(start-pt (make-marker))
|
|
||||||
(ticks-total 0)
|
|
||||||
(cleanup-fn
|
|
||||||
(lambda (&rest _)
|
|
||||||
(when (buffer-live-p (get-buffer temp-buf))
|
|
||||||
(set-marker start-pt nil)
|
|
||||||
(kill-buffer temp-buf))
|
|
||||||
(remove-hook 'gptel-post-response-functions cleanup-fn))))
|
|
||||||
(add-hook 'gptel-post-response-functions cleanup-fn)
|
|
||||||
(lambda (str)
|
|
||||||
(let ((noop-p) (ticks 0))
|
|
||||||
(with-current-buffer (get-buffer-create temp-buf)
|
|
||||||
(save-excursion (goto-char (point-max)) (insert str))
|
|
||||||
(when (marker-position start-pt) (goto-char start-pt))
|
|
||||||
(when in-src-block (setq ticks ticks-total))
|
|
||||||
(save-excursion
|
|
||||||
(while (re-search-forward "`\\|\\*\\{1,2\\}\\|_" nil t)
|
|
||||||
(pcase (match-string 0)
|
|
||||||
("`"
|
|
||||||
;; Count number of consecutive backticks
|
|
||||||
(backward-char)
|
|
||||||
(while (and (char-after) (eq (char-after) ?`))
|
|
||||||
(forward-char)
|
|
||||||
(if in-src-block (cl-decf ticks) (cl-incf ticks)))
|
|
||||||
;; Set the verbatim state of the parser
|
|
||||||
(if (and (eobp)
|
|
||||||
;; Special case heuristic: If the response ends with
|
|
||||||
;; ^``` we don't wait for more input.
|
|
||||||
;; FIXME: This can have false positives.
|
|
||||||
(not (save-excursion (beginning-of-line)
|
|
||||||
(looking-at "^```$"))))
|
|
||||||
;; End of input => there could be more backticks coming,
|
|
||||||
;; so we wait for more input
|
|
||||||
(progn (setq noop-p t) (set-marker start-pt (match-beginning 0)))
|
|
||||||
;; We reached a character other than a backtick
|
|
||||||
(cond
|
|
||||||
;; Ticks balanced, end src block
|
|
||||||
((= ticks 0)
|
|
||||||
(progn (setq in-src-block nil)
|
|
||||||
(gptel--replace-source-marker ticks-total 'end)))
|
|
||||||
;; Positive number of ticks, start an src block
|
|
||||||
((and (> ticks 0) (not in-src-block))
|
|
||||||
(setq ticks-total ticks
|
|
||||||
in-src-block t)
|
|
||||||
(gptel--replace-source-marker ticks-total))
|
|
||||||
;; Negative number of ticks or in a src block already,
|
|
||||||
;; reset ticks
|
|
||||||
(t (setq ticks ticks-total)))))
|
|
||||||
;; Handle other chars: emphasis, bold and bullet items
|
|
||||||
((and "**" (guard (not in-src-block)))
|
|
||||||
(cond
|
|
||||||
((looking-at "\\*\\(?:[[:word:]]\\|\s\\)")
|
|
||||||
(delete-char 1))
|
|
||||||
((looking-back "\\(?:[[:word:]]\\|\s\\)\\*\\{2\\}"
|
|
||||||
(max (- (point) 3) (point-min)))
|
|
||||||
(delete-char -1))))
|
|
||||||
((and "*" (guard (not in-src-block)))
|
|
||||||
(save-match-data
|
|
||||||
(save-excursion
|
|
||||||
(ignore-errors (backward-char 2))
|
|
||||||
(cond
|
|
||||||
((or (looking-at
|
|
||||||
"[^[:space:][:punct:]\n]\\(?:_\\|\\*\\)\\(?:[[:space:][:punct:]]\\|$\\)")
|
|
||||||
(looking-at
|
|
||||||
"\\(?:[[:space:][:punct:]]\\)\\(?:_\\|\\*\\)\\([^[:space:][:punct:]]\\|$\\)"))
|
|
||||||
;; Emphasis, replace with slashes
|
|
||||||
(forward-char 2) (delete-char -1) (insert "/"))
|
|
||||||
((looking-at "\\(?:$\\|\\`\\)\n\\*[[:space:]]")
|
|
||||||
;; Bullet point, replace with hyphen
|
|
||||||
(forward-char 2) (delete-char -1) (insert "-")))))))))
|
|
||||||
(if noop-p
|
|
||||||
(buffer-substring (point) start-pt)
|
|
||||||
(prog1 (buffer-substring (point) (point-max))
|
|
||||||
(set-marker start-pt (point-max)))))))))
|
|
||||||
|
|
||||||
|
|
||||||
;; Response tweaking commands
|
;; Response tweaking commands
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,7 @@
|
||||||
;; (forward-char)
|
;; (forward-char)
|
||||||
(condition-case-unless-debug err
|
(condition-case-unless-debug err
|
||||||
(thread-first
|
(thread-first
|
||||||
(gptel--json-read :object-type 'plist)
|
(gptel--json-read)
|
||||||
;; (json-read)
|
|
||||||
(map-nested-elt '(:choices 0 :delta :content))
|
(map-nested-elt '(:choices 0 :delta :content))
|
||||||
(push strs))
|
(push strs))
|
||||||
(error strs)
|
(error strs)
|
||||||
|
|
Loading…
Add table
Reference in a new issue