gptel: Add org-mode support and update README
gptel.el (gptel-response-filter-functions, gptel-send, gptel--create-prompt, gptel--transform-response, gptel--convert-org, gptel--convert-markdown->org): Add support for org-mode by transforming the response manually. (Note: Asking ChatGPT to format its results in org-mode markup produces inconsistent results.) Additionally, the abnormal hook `gptel-resposne-filter-functions' is added for arbitrary transformations of the response. Its implementation seems needlessly complex, and in the future we should change it to use `run-hook-wrapped' with a local accumulator.
This commit is contained in:
parent
b212c24c4a
commit
8fca5bc762
2 changed files with 94 additions and 12 deletions
15
README.org
15
README.org
|
@ -6,7 +6,7 @@ GPTel is a simple, no-frills ChatGPT client for Emacs.
|
||||||
|
|
||||||
- Requires an [[https://platform.openai.com/account/api-keys][OpenAI API key]].
|
- Requires an [[https://platform.openai.com/account/api-keys][OpenAI API key]].
|
||||||
- No external dependencies, only Emacs. Also, it's async.
|
- No external dependencies, only Emacs. Also, it's async.
|
||||||
- Interaction is in a Markdown (or text) buffer.
|
- Interaction is in a Markdown, Org or text buffer.
|
||||||
- Supports conversations (not just one-off queries) and multiple independent sessions.
|
- Supports conversations (not just one-off queries) and multiple independent sessions.
|
||||||
- You can go back and edit your previous prompts, or even ChatGPT's previous responses when continuing a conversation. These will be fed back to ChatGPT.
|
- You can go back and edit your previous prompts, or even ChatGPT's previous responses when continuing a conversation. These will be fed back to ChatGPT.
|
||||||
|
|
||||||
|
@ -39,6 +39,8 @@ Procure an [[https://platform.openai.com/account/api-keys][OpenAI API key]].
|
||||||
|
|
||||||
Optional: Set =gptel-api-key= to the key or to a function that returns the key (more secure).
|
Optional: Set =gptel-api-key= to the key or to a function that returns the key (more secure).
|
||||||
|
|
||||||
|
*** In a dedicated buffer:
|
||||||
|
|
||||||
Run =M-x gptel= to start or switch to the ChatGPT buffer. It will ask you for the key if you skipped the previous step.
|
Run =M-x gptel= to start or switch to the ChatGPT buffer. It will ask you for the key if you skipped the previous step.
|
||||||
|
|
||||||
Run it with a prefix-arg (=C-u M-x gptel=) to start a new session.
|
Run it with a prefix-arg (=C-u M-x gptel=) to start a new session.
|
||||||
|
@ -47,6 +49,14 @@ In the gptel buffer, send your prompt with =M-x gptel-send=, bound to =C-c RET=.
|
||||||
|
|
||||||
That's it. You can go back and edit previous prompts and responses if you want.
|
That's it. You can go back and edit previous prompts and responses if you want.
|
||||||
|
|
||||||
|
The default mode is =markdown-mode= if available, else =text-mode=. You can set =gptel-default-mode= to =org-mode= if desired.
|
||||||
|
|
||||||
|
*** In any buffer:
|
||||||
|
|
||||||
|
Select a region of text, call =M-x gptel-send=.
|
||||||
|
|
||||||
|
The response will be inserted below your region. You can select both the original prompt and the resposne and call =M-x gptel-send= again to continue the conversation.
|
||||||
|
|
||||||
** Why another ChatGPT client?
|
** Why another ChatGPT client?
|
||||||
|
|
||||||
Existing Emacs clients don't /reliably/ let me use it the simple way I can in the browser. They will get better, but I wanted something for now.
|
Existing Emacs clients don't /reliably/ let me use it the simple way I can in the browser. They will get better, but I wanted something for now.
|
||||||
|
@ -65,6 +75,3 @@ Maybe all of these, I don't know yet. As a start, I wanted to replicate the web
|
||||||
** Will you add feature X?
|
** Will you add feature X?
|
||||||
|
|
||||||
Maybe, I'd like to experiment a bit first.
|
Maybe, I'd like to experiment a bit first.
|
||||||
|
|
||||||
- Support for Org Mode instead of Markdown, including source blocks etc, is planned.
|
|
||||||
- I'm experimenting with using it in code buffers.
|
|
||||||
|
|
91
gptel.el
91
gptel.el
|
@ -85,6 +85,20 @@ When set to nil, it is inserted all at once.
|
||||||
:group 'gptel
|
:group 'gptel
|
||||||
:type 'boolean)
|
:type 'boolean)
|
||||||
|
|
||||||
|
(defcustom gptel-response-filter-functions
|
||||||
|
'(gptel--convert-org)
|
||||||
|
"Abnormal hook for transforming the response from ChatGPT.
|
||||||
|
|
||||||
|
This is useful if you want to format the response in some way,
|
||||||
|
such as filling paragraphs, adding annotations or recording
|
||||||
|
information in the response like links.
|
||||||
|
|
||||||
|
Each function in this hook receives two arguments, the response
|
||||||
|
string to transform and the ChatGPT interaction buffer. It should
|
||||||
|
return the transformed string."
|
||||||
|
:group 'gptel
|
||||||
|
:type 'hook)
|
||||||
|
|
||||||
(defvar gptel-default-session "*ChatGPT*")
|
(defvar gptel-default-session "*ChatGPT*")
|
||||||
(defvar gptel-default-mode (if (featurep 'markdown-mode)
|
(defvar gptel-default-mode (if (featurep 'markdown-mode)
|
||||||
'markdown-mode
|
'markdown-mode
|
||||||
|
@ -130,6 +144,8 @@ When set to nil, it is inserted all at once.
|
||||||
(status-str (plist-get response :status)))
|
(status-str (plist-get response :status)))
|
||||||
(if content-str
|
(if content-str
|
||||||
(with-current-buffer gptel-buffer
|
(with-current-buffer gptel-buffer
|
||||||
|
(setq content-str (gptel--transform-response
|
||||||
|
content-str gptel-buffer))
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(put-text-property 0 (length content-str) 'gptel 'response content-str)
|
(put-text-property 0 (length content-str) 'gptel 'response content-str)
|
||||||
(message "Querying ChatGPT... done.")
|
(message "Querying ChatGPT... done.")
|
||||||
|
@ -185,13 +201,7 @@ instead."
|
||||||
prompts)
|
prompts)
|
||||||
(and max-entries (cl-decf max-entries)))
|
(and max-entries (cl-decf max-entries)))
|
||||||
(cons (list :role "system"
|
(cons (list :role "system"
|
||||||
:content
|
:content gptel--system-message)
|
||||||
(concat
|
|
||||||
(when (eq major-mode 'org-mode)
|
|
||||||
(concat
|
|
||||||
"In this conversation, format your responses as in an org-mode buffer in Emacs."
|
|
||||||
" Do NOT use Markdown. I repeat, use org-mode markup and not markdown.\n"))
|
|
||||||
gptel--system-message))
|
|
||||||
prompts)))))
|
prompts)))))
|
||||||
|
|
||||||
(defun gptel--request-data (prompts)
|
(defun gptel--request-data (prompts)
|
||||||
|
@ -205,7 +215,30 @@ instead."
|
||||||
(plist-put prompts-plist :max_tokens (gptel--numberize gptel--max-tokens)))
|
(plist-put prompts-plist :max_tokens (gptel--numberize gptel--max-tokens)))
|
||||||
prompts-plist))
|
prompts-plist))
|
||||||
|
|
||||||
(aio-defun gptel--get-response (prompts)
|
;; TODO: Use `run-hook-wrapped' with an accumulator instead to handle
|
||||||
|
;; buffer-local hooks, etc.
|
||||||
|
(defun gptel--transform-response (content-str buffer)
|
||||||
|
(let ((filtered-str content-str))
|
||||||
|
(dolist (filter-func gptel-response-filter-functions filtered-str)
|
||||||
|
(condition-case nil
|
||||||
|
(when (functionp filter-func)
|
||||||
|
(setq filtered-str
|
||||||
|
(funcall filter-func filtered-str buffer)))
|
||||||
|
(error
|
||||||
|
(display-warning '(gptel filter-functions)
|
||||||
|
(format "Function %S returned an error"
|
||||||
|
filter-func)))))))
|
||||||
|
|
||||||
|
(defun gptel--convert-org (content buffer)
|
||||||
|
"Transform CONTENT according to required major-mode.
|
||||||
|
|
||||||
|
Currently only org-mode is handled.
|
||||||
|
|
||||||
|
BUFFER is the interaction buffer for ChatGPT."
|
||||||
|
(pcase (buffer-local-value 'major-mode buffer)
|
||||||
|
('org-mode (gptel--convert-markdown->org content))
|
||||||
|
(_ content)))
|
||||||
|
|
||||||
(aio-defun gptel--url-get-response (prompts)
|
(aio-defun gptel--url-get-response (prompts)
|
||||||
"Fetch response for PROMPTS from ChatGPT.
|
"Fetch response for PROMPTS from ChatGPT.
|
||||||
|
|
||||||
|
@ -287,6 +320,48 @@ Ask for API-KEY if `gptel-api-key' is unset."
|
||||||
(message "Send your query with %s!"
|
(message "Send your query with %s!"
|
||||||
(substitute-command-keys "\\[gptel-send]"))))
|
(substitute-command-keys "\\[gptel-send]"))))
|
||||||
|
|
||||||
|
(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)))
|
||||||
|
(backward-delete-char 1))))
|
||||||
|
((or "_" "*")
|
||||||
|
(if (save-match-data
|
||||||
|
(and (looking-back "\\(?:[[:space:]]\\|\s\\)\\(?:_\\|\\*\\)"
|
||||||
|
(max (- (point) 2) (point-min)))
|
||||||
|
(not (looking-at "[[:space:]]\\|\s"))))
|
||||||
|
;; Possible beginning of italics
|
||||||
|
(and
|
||||||
|
(save-excursion
|
||||||
|
(when (and (re-search-forward (regexp-quote (match-string 0)) nil t)
|
||||||
|
(looking-at "[[:space]]\\|\s")
|
||||||
|
(not (looking-back "\\(?:[[:space]]\\|\s\\)\\(?:_\\|\\*\\)"
|
||||||
|
(max (- (point) 2) (point-min)))))
|
||||||
|
(backward-delete-char 1)
|
||||||
|
(insert "/") t))
|
||||||
|
(progn (backward-delete-char 1)
|
||||||
|
(insert "/")))))))
|
||||||
|
(buffer-string)))
|
||||||
|
|
||||||
(defun gptel--playback (buf content-str start-pt)
|
(defun gptel--playback (buf content-str start-pt)
|
||||||
"Playback CONTENT-STR in BUF.
|
"Playback CONTENT-STR in BUF.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue