Compare commits
46 commits
192a4ba9de
...
3f31258e48
Author | SHA1 | Date | |
---|---|---|---|
|
3f31258e48 | ||
|
8ccdc31b12 | ||
|
69fb2f09f3 | ||
|
533724042e | ||
|
f663f3a9db | ||
|
cdb07d0d2b | ||
|
97ab6cbd1e | ||
|
c319966997 | ||
|
4273f067e8 | ||
|
b2985392f4 | ||
|
306fe3bd8c | ||
|
a2b16c43b1 | ||
|
44feb1637f | ||
|
66a63e6c82 | ||
|
9b094b8b1e | ||
|
2b938114cf | ||
|
70889ad95c | ||
|
e994a443d3 | ||
|
b4088e3f7b | ||
|
7b6e3c5900 | ||
|
4d4b61af94 | ||
|
567af4d2ee | ||
|
f2fd2b13b0 | ||
|
8dbcbbb908 | ||
|
2982ede17d | ||
|
5d74ec4de0 | ||
|
53a905dafc | ||
|
53ee34653e | ||
|
f24ec164cd | ||
|
81bb467104 | ||
|
26326c302e | ||
|
9a5a4a60d5 | ||
|
12e00cbd09 | ||
|
34a52aa047 | ||
|
9eea4be5ed | ||
|
9bc54bed9c | ||
|
5d069cfca8 | ||
|
22f7043c32 | ||
|
5dcbf40066 | ||
|
e3b3591d73 | ||
|
94b13e78ec | ||
|
b31c9be5e0 | ||
|
73a0cc25ba | ||
|
6d3e4a99f5 | ||
|
161c77ad7f | ||
|
376fb4b423 |
11 changed files with 1355 additions and 661 deletions
1
.elpaignore
Normal file
1
.elpaignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
test
|
218
README.org
218
README.org
|
@ -1,8 +1,8 @@
|
||||||
#+title: GPTel: A simple LLM client for Emacs
|
#+title: gptel: A simple LLM client for Emacs
|
||||||
|
|
||||||
[[https://melpa.org/#/gptel][file:https://melpa.org/packages/gptel-badge.svg]]
|
[[https://elpa.nongnu.org/nongnu/gptel.svg][file:https://elpa.nongnu.org/nongnu/gptel.svg]] [[https://stable.melpa.org/packages/gptel-badge.svg][file:https://stable.melpa.org/packages/gptel-badge.svg]] [[https://melpa.org/#/gptel][file:https://melpa.org/packages/gptel-badge.svg]]
|
||||||
|
|
||||||
GPTel is a simple Large Language Model chat client for Emacs, with support for multiple models and backends.
|
gptel is a simple Large Language Model chat client for Emacs, with support for multiple models and backends.
|
||||||
|
|
||||||
| LLM Backend | Supports | Requires |
|
| LLM Backend | Supports | Requires |
|
||||||
|--------------------+----------+---------------------------|
|
|--------------------+----------+---------------------------|
|
||||||
|
@ -19,6 +19,8 @@ GPTel is a simple Large Language Model chat client for Emacs, with support for m
|
||||||
| Anyscale | ✓ | [[https://docs.endpoints.anyscale.com/][API key]] |
|
| Anyscale | ✓ | [[https://docs.endpoints.anyscale.com/][API key]] |
|
||||||
| Perplexity | ✓ | [[https://docs.perplexity.ai/docs/getting-started][API key]] |
|
| Perplexity | ✓ | [[https://docs.perplexity.ai/docs/getting-started][API key]] |
|
||||||
| Anthropic (Claude) | ✓ | [[https://www.anthropic.com/api][API key]] |
|
| Anthropic (Claude) | ✓ | [[https://www.anthropic.com/api][API key]] |
|
||||||
|
| Groq | ✓ | [[https://console.groq.com/keys][API key]] |
|
||||||
|
| OpenRouter | ✓ | [[https://openrouter.ai/keys][API key]] |
|
||||||
|
|
||||||
*General usage*: ([[https://www.youtube.com/watch?v=bsRnh_brggM][YouTube Demo]])
|
*General usage*: ([[https://www.youtube.com/watch?v=bsRnh_brggM][YouTube Demo]])
|
||||||
|
|
||||||
|
@ -38,7 +40,7 @@ https://github-production-user-asset-6210df.s3.amazonaws.com/8607532/278854024-a
|
||||||
- You can go back and edit your previous prompts or LLM responses when continuing a conversation. These will be fed back to the model.
|
- You can go back and edit your previous prompts or LLM responses when continuing a conversation. These will be fed back to the model.
|
||||||
- Don't like gptel's workflow? Use it to create your own for any supported model/backend with a [[https://github.com/karthink/gptel/wiki#defining-custom-gptel-commands][simple API]].
|
- Don't like gptel's workflow? Use it to create your own for any supported model/backend with a [[https://github.com/karthink/gptel/wiki#defining-custom-gptel-commands][simple API]].
|
||||||
|
|
||||||
GPTel uses Curl if available, but falls back to url-retrieve to work without external dependencies.
|
gptel uses Curl if available, but falls back to url-retrieve to work without external dependencies.
|
||||||
|
|
||||||
** Contents :toc:
|
** Contents :toc:
|
||||||
- [[#installation][Installation]]
|
- [[#installation][Installation]]
|
||||||
|
@ -59,10 +61,13 @@ GPTel uses Curl if available, but falls back to url-retrieve to work without ext
|
||||||
- [[#anyscale][Anyscale]]
|
- [[#anyscale][Anyscale]]
|
||||||
- [[#perplexity][Perplexity]]
|
- [[#perplexity][Perplexity]]
|
||||||
- [[#anthropic-claude][Anthropic (Claude)]]
|
- [[#anthropic-claude][Anthropic (Claude)]]
|
||||||
|
- [[#groq][Groq]]
|
||||||
|
- [[#openrouter][OpenRouter]]
|
||||||
- [[#usage][Usage]]
|
- [[#usage][Usage]]
|
||||||
- [[#in-any-buffer][In any buffer:]]
|
- [[#in-any-buffer][In any buffer:]]
|
||||||
- [[#in-a-dedicated-chat-buffer][In a dedicated chat buffer:]]
|
- [[#in-a-dedicated-chat-buffer][In a dedicated chat buffer:]]
|
||||||
- [[#save-and-restore-your-chat-sessions][Save and restore your chat sessions]]
|
- [[#save-and-restore-your-chat-sessions][Save and restore your chat sessions]]
|
||||||
|
- [[#extra-org-mode-conveniences][Extra Org mode conveniences]]
|
||||||
- [[#faq][FAQ]]
|
- [[#faq][FAQ]]
|
||||||
- [[#i-want-the-window-to-scroll-automatically-as-the-response-is-inserted][I want the window to scroll automatically as the response is inserted]]
|
- [[#i-want-the-window-to-scroll-automatically-as-the-response-is-inserted][I want the window to scroll automatically as the response is inserted]]
|
||||||
- [[#i-want-the-cursor-to-move-to-the-next-prompt-after-the-response-is-inserted][I want the cursor to move to the next prompt after the response is inserted]]
|
- [[#i-want-the-cursor-to-move-to-the-next-prompt-after-the-response-is-inserted][I want the cursor to move to the next prompt after the response is inserted]]
|
||||||
|
@ -74,13 +79,13 @@ GPTel uses Curl if available, but falls back to url-retrieve to work without ext
|
||||||
- [[#why-another-llm-client][Why another LLM client?]]
|
- [[#why-another-llm-client][Why another LLM client?]]
|
||||||
- [[#additional-configuration][Additional Configuration]]
|
- [[#additional-configuration][Additional Configuration]]
|
||||||
- [[#alternatives][Alternatives]]
|
- [[#alternatives][Alternatives]]
|
||||||
- [[#extensions-using-gptel][Extensions using GPTel]]
|
- [[#extensions-using-gptel][Extensions using gptel]]
|
||||||
- [[#breaking-changes][Breaking Changes]]
|
- [[#breaking-changes][Breaking Changes]]
|
||||||
- [[#acknowledgments][Acknowledgments]]
|
- [[#acknowledgments][Acknowledgments]]
|
||||||
|
|
||||||
** Installation
|
** Installation
|
||||||
|
|
||||||
GPTel is on MELPA. Ensure that MELPA is in your list of sources, then install gptel with =M-x package-install⏎= =gptel=.
|
gptel is on MELPA. Ensure that MELPA is in your list of sources, then install it with =M-x package-install⏎= =gptel=.
|
||||||
|
|
||||||
(Optional: Install =markdown-mode=.)
|
(Optional: Install =markdown-mode=.)
|
||||||
|
|
||||||
|
@ -158,10 +163,10 @@ You can pick this backend from the menu when using gptel. (see [[#usage][Usage]]
|
||||||
|
|
||||||
***** (Optional) Set as the default gptel backend
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the default value of =gptel-backend=. Use this instead of the above.
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; OPTIONAL configuration
|
;; OPTIONAL configuration
|
||||||
(setq-default
|
(setq
|
||||||
gptel-model "gpt-3.5-turbo"
|
gptel-model "gpt-3.5-turbo"
|
||||||
gptel-backend (gptel-make-azure "Azure-1"
|
gptel-backend (gptel-make-azure "Azure-1"
|
||||||
:protocol "https"
|
:protocol "https"
|
||||||
|
@ -190,10 +195,10 @@ You can pick this backend from the menu when using gptel (see [[#usage][Usage]])
|
||||||
|
|
||||||
***** (Optional) Set as the default gptel backend
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the default value of =gptel-backend=. Use this instead of the above. Additionally you may want to increase the response token size since GPT4All uses very short (often truncated) responses by default.
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above. Additionally you may want to increase the response token size since GPT4All uses very short (often truncated) responses by default.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; OPTIONAL configuration
|
;; OPTIONAL configuration
|
||||||
(setq-default
|
(setq
|
||||||
gptel-max-tokens 500
|
gptel-max-tokens 500
|
||||||
gptel-model "mistral-7b-openorca.Q4_0.gguf"
|
gptel-model "mistral-7b-openorca.Q4_0.gguf"
|
||||||
gptel-backend (gptel-make-gpt4all "GPT4All"
|
gptel-backend (gptel-make-gpt4all "GPT4All"
|
||||||
|
@ -221,10 +226,10 @@ You can pick this backend from the menu when using gptel (see [[#usage][Usage]])
|
||||||
|
|
||||||
***** (Optional) Set as the default gptel backend
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the default value of =gptel-backend=. Use this instead of the above.
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; OPTIONAL configuration
|
;; OPTIONAL configuration
|
||||||
(setq-default
|
(setq
|
||||||
gptel-model "mistral:latest"
|
gptel-model "mistral:latest"
|
||||||
gptel-backend (gptel-make-ollama "Ollama"
|
gptel-backend (gptel-make-ollama "Ollama"
|
||||||
:host "localhost:11434"
|
:host "localhost:11434"
|
||||||
|
@ -249,10 +254,10 @@ You can pick this backend from the menu when using gptel (see [[#usage][Usage]])
|
||||||
|
|
||||||
***** (Optional) Set as the default gptel backend
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the default value of =gptel-backend=. Use this instead of the above.
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; OPTIONAL configuration
|
;; OPTIONAL configuration
|
||||||
(setq-default
|
(setq
|
||||||
gptel-model "gemini-pro"
|
gptel-model "gemini-pro"
|
||||||
gptel-backend (gptel-make-gemini "Gemini"
|
gptel-backend (gptel-make-gemini "Gemini"
|
||||||
:key "YOUR_GEMINI_API_KEY"
|
:key "YOUR_GEMINI_API_KEY"
|
||||||
|
@ -283,10 +288,10 @@ You can pick this backend from the menu when using gptel (see [[#usage][Usage]])
|
||||||
|
|
||||||
***** (Optional) Set as the default gptel backend
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the default value of =gptel-backend=. Use this instead of the above.
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; OPTIONAL configuration
|
;; OPTIONAL configuration
|
||||||
(setq-default
|
(setq
|
||||||
gptel-model "test"
|
gptel-model "test"
|
||||||
gptel-backend (gptel-make-openai "llama-cpp"
|
gptel-backend (gptel-make-openai "llama-cpp"
|
||||||
:stream t
|
:stream t
|
||||||
|
@ -317,10 +322,10 @@ You can pick this backend and the model (fastgpt/summarizer) from the transient
|
||||||
|
|
||||||
***** (Optional) Set as the default gptel backend
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the default value of =gptel-backend=. Use this instead of the above.
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; OPTIONAL configuration
|
;; OPTIONAL configuration
|
||||||
(setq-default
|
(setq
|
||||||
gptel-model "fastgpt"
|
gptel-model "fastgpt"
|
||||||
gptel-backend (gptel-make-kagi "Kagi"
|
gptel-backend (gptel-make-kagi "Kagi"
|
||||||
:key "YOUR_KAGI_API_KEY"))
|
:key "YOUR_KAGI_API_KEY"))
|
||||||
|
@ -350,10 +355,10 @@ You can pick this backend from the menu when using gptel (see [[#usage][Usage]])
|
||||||
|
|
||||||
***** (Optional) Set as the default gptel backend
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the default value of =gptel-backend=. Use this instead of the above.
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; OPTIONAL configuration
|
;; OPTIONAL configuration
|
||||||
(setq-default
|
(setq
|
||||||
gptel-model "mistralai/Mixtral-8x7B-Instruct-v0.1"
|
gptel-model "mistralai/Mixtral-8x7B-Instruct-v0.1"
|
||||||
gptel-backend
|
gptel-backend
|
||||||
(gptel-make-openai "TogetherAI"
|
(gptel-make-openai "TogetherAI"
|
||||||
|
@ -385,10 +390,10 @@ You can pick this backend from the menu when using gptel (see [[#usage][Usage]])
|
||||||
|
|
||||||
***** (Optional) Set as the default gptel backend
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the default value of =gptel-backend=. Use this instead of the above.
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; OPTIONAL configuration
|
;; OPTIONAL configuration
|
||||||
(setq-default
|
(setq
|
||||||
gptel-model "mistralai/Mixtral-8x7B-Instruct-v0.1"
|
gptel-model "mistralai/Mixtral-8x7B-Instruct-v0.1"
|
||||||
gptel-backend
|
gptel-backend
|
||||||
(gptel-make-openai "Anyscale"
|
(gptel-make-openai "Anyscale"
|
||||||
|
@ -422,10 +427,10 @@ You can pick this backend from the menu when using gptel (see [[#usage][Usage]])
|
||||||
|
|
||||||
***** (Optional) Set as the default gptel backend
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the default value of =gptel-backend=. Use this instead of the above.
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; OPTIONAL configuration
|
;; OPTIONAL configuration
|
||||||
(setq-default
|
(setq
|
||||||
gptel-model "pplx-7b-chat"
|
gptel-model "pplx-7b-chat"
|
||||||
gptel-backend
|
gptel-backend
|
||||||
(gptel-make-openai "Perplexity"
|
(gptel-make-openai "Perplexity"
|
||||||
|
@ -456,31 +461,119 @@ You can pick this backend from the menu when using gptel (see [[#usage][Usage]])
|
||||||
|
|
||||||
***** (Optional) Set as the default gptel backend
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the default value of =gptel-backend=. Use this instead of the above.
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; OPTIONAL configuration
|
;; OPTIONAL configuration
|
||||||
(setq-default
|
(setq
|
||||||
gptel-model "claude-3-sonnet-20240229" ; "claude-3-opus-20240229" also available
|
gptel-model "claude-3-sonnet-20240229" ; "claude-3-opus-20240229" also available
|
||||||
gptel-backend (gptel-make-anthropic "Claude"
|
gptel-backend (gptel-make-anthropic "Claude"
|
||||||
:stream t :key "your-api-key"))
|
:stream t :key "your-api-key"))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
#+html: </details>
|
#+html: </details>
|
||||||
|
#+html: <details><summary>
|
||||||
|
**** Groq
|
||||||
|
#+html: </summary>
|
||||||
|
|
||||||
|
Register a backend with
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
;; Groq offers an OpenAI compatible API
|
||||||
|
(gptel-make-openai "Groq" ;Any name you want
|
||||||
|
:host "api.groq.com"
|
||||||
|
:endpoint "/openai/v1/chat/completions"
|
||||||
|
:stream t
|
||||||
|
:key "your-api-key" ;can be a function that returns the key
|
||||||
|
:models '("mixtral-8x7b-32768"
|
||||||
|
"gemma-7b-it"
|
||||||
|
"llama2-70b-4096"))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
You can pick this backend from the menu when using gptel (see [[#usage][Usage]]). Note that Groq is fast enough that you could easily set =:stream nil= and still get near-instant responses.
|
||||||
|
|
||||||
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
;; OPTIONAL configuration
|
||||||
|
(setq gptel-model "mixtral-8x7b-32768"
|
||||||
|
gptel-backend
|
||||||
|
(gptel-make-openai "Groq"
|
||||||
|
:host "api.groq.com"
|
||||||
|
:endpoint "/openai/v1/chat/completions"
|
||||||
|
:stream t
|
||||||
|
:key "your-api-key"
|
||||||
|
:models '("mixtral-8x7b-32768"
|
||||||
|
"gemma-7b-it"
|
||||||
|
"llama2-70b-4096")))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+html: </details>
|
||||||
|
|
||||||
|
#+html: <details><summary>
|
||||||
|
**** OpenRouter
|
||||||
|
#+html: </summary>
|
||||||
|
|
||||||
|
Register a backend with
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
;; OpenRouter offers an OpenAI compatible API
|
||||||
|
(gptel-make-openai "OpenRouter" ;Any name you want
|
||||||
|
:host "openrouter.ai"
|
||||||
|
:endpoint "/api/v1/chat/completions"
|
||||||
|
:stream t
|
||||||
|
:key "your-api-key" ;can be a function that returns the key
|
||||||
|
:models '("openai/gpt-3.5-turbo"
|
||||||
|
"mistralai/mixtral-8x7b-instruct"
|
||||||
|
"meta-llama/codellama-34b-instruct"
|
||||||
|
"codellama/codellama-70b-instruct"
|
||||||
|
"google/palm-2-codechat-bison-32k"
|
||||||
|
"google/gemini-pro"))
|
||||||
|
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
You can pick this backend from the menu when using gptel (see [[#usage][Usage]]).
|
||||||
|
|
||||||
|
***** (Optional) Set as the default gptel backend
|
||||||
|
|
||||||
|
The above code makes the backend available to select. If you want it to be the default backend for gptel, you can set this as the value of =gptel-backend=. Use this instead of the above.
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
;; OPTIONAL configuration
|
||||||
|
(setq gptel-model "mixtral-8x7b-32768"
|
||||||
|
gptel-backend
|
||||||
|
(gptel-make-openai "OpenRouter" ;Any name you want
|
||||||
|
:host "openrouter.ai"
|
||||||
|
:endpoint "/api/v1/chat/completions"
|
||||||
|
:stream t
|
||||||
|
:key "your-api-key" ;can be a function that returns the key
|
||||||
|
:models '("openai/gpt-3.5-turbo"
|
||||||
|
"mistralai/mixtral-8x7b-instruct"
|
||||||
|
"meta-llama/codellama-34b-instruct"
|
||||||
|
"codellama/codellama-70b-instruct"
|
||||||
|
"google/palm-2-codechat-bison-32k"
|
||||||
|
"google/gemini-pro")))
|
||||||
|
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+html: </details>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
** Usage
|
** Usage
|
||||||
|
|
||||||
(This is also a [[https://www.youtube.com/watch?v=bsRnh_brggM][video demo]] showing various uses of gptel.)
|
(This is also a [[https://www.youtube.com/watch?v=bsRnh_brggM][video demo]] showing various uses of gptel.)
|
||||||
|
|
||||||
|-------------------+-------------------------------------------------------------------------|
|
|-----------------------------+------------------------------------------------------------------------------------------------|
|
||||||
| *Command* | Description |
|
| *Command* | Description |
|
||||||
|-------------------+-------------------------------------------------------------------------|
|
|-----------------------------+------------------------------------------------------------------------------------------------|
|
||||||
| =gptel-send= | Send conversation up to =(point)=, or selection if region is active. Works anywhere in Emacs. |
|
| =gptel-send= | Send conversation up to =(point)=, or selection if region is active. Works anywhere in Emacs. |
|
||||||
| =gptel= | Create a new dedicated chat buffer. Not required to use gptel. |
|
| =gptel= | Create a new dedicated chat buffer. Not required to use gptel. |
|
||||||
| =C-u= =gptel-send= | Transient menu for preferences, input/output redirection etc. |
|
| =C-u= =gptel-send= | Transient menu for preferences, input/output redirection etc. |
|
||||||
| =gptel-menu= | /(Same)/ |
|
| =gptel-menu= | /(Same)/ |
|
||||||
|-------------------+-------------------------------------------------------------------------|
|
|-----------------------------+------------------------------------------------------------------------------------------------|
|
||||||
| =gptel-set-topic= | /(Org-mode only)/ Limit conversation context to an Org heading |
|
| *Command* /(Org mode only)/ | |
|
||||||
|-------------------+-------------------------------------------------------------------------|
|
|-----------------------------+------------------------------------------------------------------------------------------------|
|
||||||
|
| =gptel-org-set-topic= | Limit conversation context to an Org heading |
|
||||||
|
| =gptel-org-set-properties= | Write gptel configuration as Org properties (for self-contained chat logs) |
|
||||||
|
|-----------------------------+------------------------------------------------------------------------------------------------|
|
||||||
|
|
||||||
*** In any buffer:
|
*** In any buffer:
|
||||||
|
|
||||||
|
@ -523,12 +616,51 @@ The default mode is =markdown-mode= if available, else =text-mode=. You can set
|
||||||
|
|
||||||
Saving the file will save the state of the conversation as well. To resume the chat, open the file and turn on =gptel-mode= before editing the buffer.
|
Saving the file will save the state of the conversation as well. To resume the chat, open the file and turn on =gptel-mode= before editing the buffer.
|
||||||
|
|
||||||
|
*** Extra Org mode conveniences
|
||||||
|
|
||||||
|
gptel offers a few extra conveniences in Org mode.
|
||||||
|
|
||||||
|
- You can limit the conversation context to an Org heading with the command =gptel-org-set-topic=.
|
||||||
|
|
||||||
|
- You can have branching conversations in Org mode, where each hierarchical outline path through the document is a separate conversation branch. This is also useful for limiting the context size of each query. See the variable =gptel-org-branching-context=.
|
||||||
|
|
||||||
|
- You can declare the gptel model, backend, temperature, system message and other parameters as Org properties with the command =gptel-org-set-properties=. gptel queries under the corresponding heading will always use these settings, allowing you to create mostly reproducible LLM chat notebooks, and to have simultaneous chats with different models, model settings and directives under different Org headings.
|
||||||
|
|
||||||
|
*** Optional: Add more context to your query
|
||||||
|
|
||||||
|
You can add contextual information from any Emacs buffer by utilizing
|
||||||
|
=gptel-add-context=. Each call will add another snippet including
|
||||||
|
metadata like buffer-name, LOC and major mode.
|
||||||
|
|
||||||
|
1. Select the text you want to add as context. If no text is selected,
|
||||||
|
the entire content of the current buffer will be used.
|
||||||
|
|
||||||
|
2. =gptel-add-context= adds the selected text or the whole buffer
|
||||||
|
content to the "*gptel-context*" buffer.
|
||||||
|
|
||||||
|
3. Proceed with LLM interactions using =gptel= as usual. The added
|
||||||
|
context will influence the LLM's responses, making them more
|
||||||
|
relevant and contextualized.
|
||||||
|
|
||||||
|
4. At any point, you can manually edit the "*gptel-context*" buffer to
|
||||||
|
remove stale information.
|
||||||
|
|
||||||
|
**** Practical Applications
|
||||||
|
|
||||||
|
- Enhancing code development sessions with relevant documentation or
|
||||||
|
code snippets as a reference.
|
||||||
|
- Accumulating research notes or sources while writing papers or
|
||||||
|
articles to ensure consistency in the narrative or arguments.
|
||||||
|
- Providing detailed error logs or system information during debugging
|
||||||
|
sessions to assist in generating more accurate solutions or
|
||||||
|
suggestions from the LLM.
|
||||||
|
|
||||||
** FAQ
|
** FAQ
|
||||||
#+html: <details><summary>
|
#+html: <details><summary>
|
||||||
**** I want the window to scroll automatically as the response is inserted
|
**** I want the window to scroll automatically as the response is inserted
|
||||||
#+html: </summary>
|
#+html: </summary>
|
||||||
|
|
||||||
To be minimally annoying, GPTel does not move the cursor by default. Add the following to your configuration to enable auto-scrolling.
|
To be minimally annoying, gptel does not move the cursor by default. Add the following to your configuration to enable auto-scrolling.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(add-hook 'gptel-post-stream-hook 'gptel-auto-scroll)
|
(add-hook 'gptel-post-stream-hook 'gptel-auto-scroll)
|
||||||
|
@ -539,7 +671,7 @@ To be minimally annoying, GPTel does not move the cursor by default. Add the fo
|
||||||
**** I want the cursor to move to the next prompt after the response is inserted
|
**** I want the cursor to move to the next prompt after the response is inserted
|
||||||
#+html: </summary>
|
#+html: </summary>
|
||||||
|
|
||||||
To be minimally annoying, GPTel does not move the cursor by default. Add the following to your configuration to move the cursor:
|
To be minimally annoying, gptel does not move the cursor by default. Add the following to your configuration to move the cursor:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(add-hook 'gptel-post-response-functions 'gptel-end-of-response)
|
(add-hook 'gptel-post-response-functions 'gptel-end-of-response)
|
||||||
|
@ -564,29 +696,29 @@ Anywhere in Emacs: Use =gptel-pre-response-hook= and =gptel-post-response-functi
|
||||||
|
|
||||||
Any model options you set are saved for the current buffer. But the redirection options in the menu are set for the next query only:
|
Any model options you set are saved for the current buffer. But the redirection options in the menu are set for the next query only:
|
||||||
|
|
||||||
https://github.com/karthink/gptel/assets/8607532/2ecc6be9-aa52-4287-a739-ba06e1369ec2
|
#+html: <img src="https://github.com/karthink/gptel/assets/8607532/2ecc6be9-aa52-4287-a739-ba06e1369ec2" alt="https://github.com/karthink/gptel/assets/8607532/2ecc6be9-aa52-4287-a739-ba06e1369ec2">
|
||||||
|
|
||||||
You can make them persistent across this Emacs session by pressing ~C-x C-s~:
|
You can make them persistent across this Emacs session by pressing ~C-x C-s~:
|
||||||
|
|
||||||
https://github.com/karthink/gptel/assets/8607532/b8bcb6ad-c974-41e1-9336-fdba0098a2fe
|
#+html: <img src="https://github.com/karthink/gptel/assets/8607532/b8bcb6ad-c974-41e1-9336-fdba0098a2fe" alt="https://github.com/karthink/gptel/assets/8607532/b8bcb6ad-c974-41e1-9336-fdba0098a2fe">
|
||||||
|
|
||||||
(You can also cycle through presets you've saved with ~C-x p~ and ~C-x n~.)
|
(You can also cycle through presets you've saved with ~C-x p~ and ~C-x n~.)
|
||||||
|
|
||||||
Now these will be enabled whenever you send a query from the transient menu. If you want to use these options without invoking the transient menu, you can use a keyboard macro:
|
Now these will be enabled whenever you send a query from the transient menu. If you want to use these saved options without invoking the transient menu, you can use a keyboard macro:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
;; Replace with your key to invoke the transient menu:
|
;; Replace with your key to invoke the transient menu:
|
||||||
(keymap-global-set "<f6>" "C-u C-c <return> <return>")
|
(keymap-global-set "<f6>" "C-u C-c <return> <return>")
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
See [[https://github.com/karthink/gptel/issues/94#issuecomment-1657093458][this comment by Tianshu Wang]] for an Elisp solution.
|
Or see this [[https://github.com/karthink/gptel/wiki#save-transient-flags][wiki entry]].
|
||||||
|
|
||||||
#+html: </details>
|
#+html: </details>
|
||||||
#+html: <details><summary>
|
#+html: <details><summary>
|
||||||
**** I want to use gptel in a way that's not supported by =gptel-send= or the options menu
|
**** I want to use gptel in a way that's not supported by =gptel-send= or the options menu
|
||||||
#+html: </summary>
|
#+html: </summary>
|
||||||
|
|
||||||
GPTel's default usage pattern is simple, and will stay this way: Read input in any buffer and insert the response below it. Some custom behavior is possible with the transient menu (=C-u M-x gptel-send=).
|
gptel's default usage pattern is simple, and will stay this way: Read input in any buffer and insert the response below it. Some custom behavior is possible with the transient menu (=C-u M-x gptel-send=).
|
||||||
|
|
||||||
For more programmable usage, gptel provides a general =gptel-request= function that accepts a custom prompt and a callback to act on the response. You can use this to build custom workflows not supported by =gptel-send=. See the documentation of =gptel-request=, and the [[https://github.com/karthink/gptel/wiki][wiki]] for examples.
|
For more programmable usage, gptel provides a general =gptel-request= function that accepts a custom prompt and a callback to act on the response. You can use this to build custom workflows not supported by =gptel-send=. See the documentation of =gptel-request=, and the [[https://github.com/karthink/gptel/wiki][wiki]] for examples.
|
||||||
|
|
||||||
|
@ -693,17 +825,21 @@ Features being considered or in the pipeline:
|
||||||
|
|
||||||
Other Emacs clients for LLMs include
|
Other Emacs clients for LLMs include
|
||||||
|
|
||||||
|
- [[https://github.com/ahyatt/llm][llm]]: llm provides a uniform API across language model providers for building LLM clients in Emacs, and is intended as a library for use by package authors. For similar scripting purposes, gptel provides the command =gptel-request=, which see.
|
||||||
|
- [[https://github.com/s-kostyaev/ellama][Ellama]]: A full-fledged LLM client built on llm, that supports many LLM providers (Ollama, Open AI, Vertex, GPT4All and more). Its usage differs from gptel in that it provides separate commands for dozens of common tasks, like general chat, summarizing code/text, refactoring code, improving grammar, translation and so on.
|
||||||
- [[https://github.com/xenodium/chatgpt-shell][chatgpt-shell]]: comint-shell based interaction with ChatGPT. Also supports DALL-E, executable code blocks in the responses, and more.
|
- [[https://github.com/xenodium/chatgpt-shell][chatgpt-shell]]: comint-shell based interaction with ChatGPT. Also supports DALL-E, executable code blocks in the responses, and more.
|
||||||
- [[https://github.com/rksm/org-ai][org-ai]]: Interaction through special =#+begin_ai ... #+end_ai= Org-mode blocks. Also supports DALL-E, querying ChatGPT with the contents of project files, and more.
|
- [[https://github.com/rksm/org-ai][org-ai]]: Interaction through special =#+begin_ai ... #+end_ai= Org-mode blocks. Also supports DALL-E, querying ChatGPT with the contents of project files, and more.
|
||||||
|
|
||||||
There are several more: [[https://github.com/CarlQLange/chatgpt-arcana.el][chatgpt-arcana]], [[https://github.com/MichaelBurge/leafy-mode][leafy-mode]], [[https://github.com/iwahbe/chat.el][chat.el]]
|
There are several more: [[https://github.com/CarlQLange/chatgpt-arcana.el][chatgpt-arcana]], [[https://github.com/MichaelBurge/leafy-mode][leafy-mode]], [[https://github.com/iwahbe/chat.el][chat.el]]
|
||||||
|
|
||||||
*** Extensions using GPTel
|
*** Extensions using gptel
|
||||||
|
|
||||||
These are packages that depend on GPTel to provide additional functionality
|
These are packages that use gptel to provide additional functionality
|
||||||
|
|
||||||
- [[https://github.com/kamushadenes/gptel-extensions.el][gptel-extensions]]: Extra utility functions for GPTel.
|
- [[https://github.com/kamushadenes/gptel-extensions.el][gptel-extensions]]: Extra utility functions for gptel.
|
||||||
- [[https://github.com/kamushadenes/ai-blog.el][ai-blog.el]]: Streamline generation of blog posts in Hugo.
|
- [[https://github.com/kamushadenes/ai-blog.el][ai-blog.el]]: Streamline generation of blog posts in Hugo.
|
||||||
|
- [[https://github.com/douo/magit-gptcommit][magit-gptcommit]]: Generate Commit Messages within magit-status Buffer using gptel.
|
||||||
|
- [[https://github.com/armindarvish/consult-web][consult-web]]: Provides gptel as a source when querying multiple local and online sources.
|
||||||
|
|
||||||
** Breaking Changes
|
** Breaking Changes
|
||||||
|
|
||||||
|
|
|
@ -45,20 +45,18 @@
|
||||||
(condition-case nil
|
(condition-case nil
|
||||||
(while (re-search-forward "^event: " nil t)
|
(while (re-search-forward "^event: " nil t)
|
||||||
(setq pt (match-beginning 0))
|
(setq pt (match-beginning 0))
|
||||||
(cond
|
(if (equal (line-end-position) (point-max))
|
||||||
((looking-at "content_block_\\(?:start\\|delta\\|stop\\)")
|
(error "Data block incomplete"))
|
||||||
(save-match-data
|
(when (looking-at "content_block_\\(?:start\\|delta\\|stop\\)")
|
||||||
(forward-line 1) (forward-char 5)
|
(forward-line 1) (forward-char 5)
|
||||||
(when-let* ((response (gptel--json-read))
|
(when-let* ((response (gptel--json-read))
|
||||||
(content (map-nested-elt
|
(content (map-nested-elt
|
||||||
response '(:delta :text))))
|
response '(:delta :text))))
|
||||||
(push content content-strs))))))
|
(push content content-strs))))
|
||||||
(error (goto-char pt)))
|
(error (goto-char pt)))
|
||||||
(apply #'concat (nreverse content-strs))))
|
(apply #'concat (nreverse content-strs))))
|
||||||
|
|
||||||
(cl-defmethod gptel--parse-response ((_backend gptel-anthropic) response _info)
|
(cl-defmethod gptel--parse-response ((_backend gptel-anthropic) response _info)
|
||||||
(with-current-buffer (get-buffer "*gptel-log*")
|
|
||||||
(princ response))
|
|
||||||
(map-nested-elt response '(:content 0 :text)))
|
(map-nested-elt response '(:content 0 :text)))
|
||||||
|
|
||||||
(cl-defmethod gptel--request-data ((_backend gptel-anthropic) prompts)
|
(cl-defmethod gptel--request-data ((_backend gptel-anthropic) prompts)
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -238,10 +240,16 @@ PROCESS and _STATUS are process parameters."
|
||||||
(when gptel-mode
|
(when gptel-mode
|
||||||
(gptel--update-status
|
(gptel--update-status
|
||||||
(format " Response Error: %s" http-msg) 'error))))
|
(format " Response Error: %s" http-msg) 'error))))
|
||||||
|
;; Run hook in visible window to set window-point, BUG #269
|
||||||
|
(if-let ((gptel-window (get-buffer-window gptel-buffer 'visible)))
|
||||||
|
(with-selected-window gptel-window
|
||||||
|
(run-hook-with-args 'gptel-post-response-functions
|
||||||
|
(marker-position start-marker)
|
||||||
|
(marker-position (or tracking-marker start-marker))))
|
||||||
(with-current-buffer gptel-buffer
|
(with-current-buffer gptel-buffer
|
||||||
(run-hook-with-args 'gptel-post-response-functions
|
(run-hook-with-args 'gptel-post-response-functions
|
||||||
(marker-position start-marker)
|
(marker-position start-marker)
|
||||||
(marker-position (or tracking-marker start-marker)))))
|
(marker-position (or tracking-marker start-marker))))))
|
||||||
(setf (alist-get process gptel-curl--process-alist nil 'remove) nil)
|
(setf (alist-get process gptel-curl--process-alist nil 'remove) nil)
|
||||||
(kill-buffer proc-buf)))
|
(kill-buffer proc-buf)))
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,8 @@
|
||||||
(name &key curl-args header key (stream nil)
|
(name &key curl-args header key (stream nil)
|
||||||
(host "generativelanguage.googleapis.com")
|
(host "generativelanguage.googleapis.com")
|
||||||
(protocol "https")
|
(protocol "https")
|
||||||
(models '("gemini-pro"))
|
(models '("gemini-pro"
|
||||||
|
"gemini-1.5-pro-latest"))
|
||||||
(endpoint "/v1beta/models"))
|
(endpoint "/v1beta/models"))
|
||||||
|
|
||||||
"Register a Gemini backend for gptel with NAME.
|
"Register a Gemini backend for gptel with NAME.
|
||||||
|
|
|
@ -34,71 +34,86 @@
|
||||||
(:copier nil)
|
(:copier nil)
|
||||||
(:include gptel-backend)))
|
(:include gptel-backend)))
|
||||||
|
|
||||||
(defvar-local gptel--ollama-context nil
|
(defvar-local gptel--ollama-token-count 0
|
||||||
"Context for ollama conversations.
|
"Token count for ollama conversations.
|
||||||
|
|
||||||
This variable holds the context array for conversations with
|
This variable holds the total token count for conversations with
|
||||||
Ollama models.")
|
Ollama models.
|
||||||
|
|
||||||
|
Intended for internal use only.")
|
||||||
|
|
||||||
(cl-defmethod gptel-curl--parse-stream ((_backend gptel-ollama) info)
|
(cl-defmethod gptel-curl--parse-stream ((_backend gptel-ollama) info)
|
||||||
";TODO: "
|
"Parse response stream for the Ollama API."
|
||||||
(when (bobp)
|
(when (and (bobp) (re-search-forward "^{" nil t))
|
||||||
(re-search-forward "^{")
|
|
||||||
(forward-line 0))
|
(forward-line 0))
|
||||||
(let* ((content-strs)
|
(let* ((content-strs) (content) (pt (point)))
|
||||||
(content))
|
|
||||||
(condition-case nil
|
(condition-case nil
|
||||||
(while (setq content (gptel--json-read))
|
(while (setq content (gptel--json-read))
|
||||||
|
(setq pt (point))
|
||||||
(let ((done (map-elt content :done))
|
(let ((done (map-elt content :done))
|
||||||
(response (map-elt content :response)))
|
(response (map-nested-elt content '(:message :content))))
|
||||||
(push response content-strs)
|
(push response content-strs)
|
||||||
(unless (eq done :json-false)
|
(unless (eq done :json-false)
|
||||||
(with-current-buffer (plist-get info :buffer)
|
(with-current-buffer (plist-get info :buffer)
|
||||||
(setq gptel--ollama-context (map-elt content :context)))
|
(cl-incf gptel--ollama-token-count
|
||||||
|
(+ (or (map-elt content :prompt_eval_count) 0)
|
||||||
|
(or (map-elt content :eval_count) 0))))
|
||||||
(goto-char (point-max)))))
|
(goto-char (point-max)))))
|
||||||
(error (forward-line 0)))
|
(error (goto-char pt)))
|
||||||
(apply #'concat (nreverse content-strs))))
|
(apply #'concat (nreverse content-strs))))
|
||||||
|
|
||||||
(cl-defmethod gptel--parse-response ((_backend gptel-ollama) response info)
|
(cl-defmethod gptel--parse-response ((_backend gptel-ollama) response info)
|
||||||
(when-let ((context (map-elt response :context)))
|
"Parse a one-shot RESPONSE from the Ollama API."
|
||||||
|
(when-let ((context
|
||||||
|
(+ (or (map-elt response :prompt_eval_count) 0)
|
||||||
|
(or (map-elt response :eval_count) 0))))
|
||||||
(with-current-buffer (plist-get info :buffer)
|
(with-current-buffer (plist-get info :buffer)
|
||||||
(setq gptel--ollama-context context)))
|
(cl-incf gptel--ollama-token-count context)))
|
||||||
(map-elt response :response))
|
(map-nested-elt response '(:message :content)))
|
||||||
|
|
||||||
(cl-defmethod gptel--request-data ((_backend gptel-ollama) prompts)
|
(cl-defmethod gptel--request-data ((_backend gptel-ollama) prompts)
|
||||||
"JSON encode PROMPTS for Ollama."
|
"JSON encode PROMPTS for sending to ChatGPT."
|
||||||
(let ((prompts-plist
|
(let ((prompts-plist
|
||||||
`(:model ,gptel-model
|
`(:model ,gptel-model
|
||||||
,@prompts
|
:messages [,@prompts]
|
||||||
:stream ,(or (and gptel-stream gptel-use-curl
|
:stream ,(or (and gptel-stream gptel-use-curl
|
||||||
(gptel-backend-stream gptel-backend))
|
(gptel-backend-stream gptel-backend))
|
||||||
:json-false))))
|
:json-false)))
|
||||||
(when gptel--ollama-context
|
options-plist)
|
||||||
(plist-put prompts-plist :context gptel--ollama-context))
|
(when gptel-temperature
|
||||||
|
(setq options-plist
|
||||||
|
(plist-put options-plist :temperature
|
||||||
|
gptel-temperature)))
|
||||||
|
(when gptel-max-tokens
|
||||||
|
(setq options-plist
|
||||||
|
(plist-put options-plist :num_predict
|
||||||
|
gptel-max-tokens)))
|
||||||
|
(when options-plist
|
||||||
|
(plist-put prompts-plist :options options-plist))
|
||||||
prompts-plist))
|
prompts-plist))
|
||||||
|
|
||||||
(cl-defmethod gptel--parse-buffer ((_backend gptel-ollama) &optional _max-entries)
|
(cl-defmethod gptel--parse-buffer ((_backend gptel-ollama) &optional max-entries)
|
||||||
(let ((prompts)
|
(let ((prompts) (prop))
|
||||||
(prop (text-property-search-backward
|
(while (and
|
||||||
|
(or (not max-entries) (>= max-entries 0))
|
||||||
|
(setq prop (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))))
|
||||||
(if (and (prop-match-p prop)
|
(push (list :role (if (prop-match-value prop) "assistant" "user")
|
||||||
(prop-match-value prop))
|
:content
|
||||||
(user-error "No user prompt found!")
|
|
||||||
(setq prompts (list
|
|
||||||
:system gptel--system-message
|
|
||||||
:prompt
|
|
||||||
(if (prop-match-p prop)
|
|
||||||
(string-trim
|
(string-trim
|
||||||
(buffer-substring-no-properties (prop-match-beginning prop)
|
(buffer-substring-no-properties (prop-match-beginning prop)
|
||||||
(prop-match-end prop))
|
(prop-match-end prop))
|
||||||
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
|
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
|
||||||
(regexp-quote (gptel-prompt-prefix-string)))
|
(regexp-quote (gptel-prompt-prefix-string)))
|
||||||
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
|
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
|
||||||
(regexp-quote (gptel-response-prefix-string))))
|
(regexp-quote (gptel-response-prefix-string)))))
|
||||||
"")))
|
prompts)
|
||||||
|
(and max-entries (cl-decf max-entries)))
|
||||||
|
(cons (list :role "system"
|
||||||
|
:content gptel--system-message)
|
||||||
prompts)))
|
prompts)))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
|
@ -106,7 +121,7 @@ Ollama models.")
|
||||||
(name &key curl-args header key models stream
|
(name &key curl-args header key models stream
|
||||||
(host "localhost:11434")
|
(host "localhost:11434")
|
||||||
(protocol "http")
|
(protocol "http")
|
||||||
(endpoint "/api/generate"))
|
(endpoint "/api/chat"))
|
||||||
"Register an Ollama backend for gptel with NAME.
|
"Register an Ollama backend for gptel with NAME.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
(defvar json-object-type)
|
(defvar json-object-type)
|
||||||
(declare-function json-read "json" ())
|
(declare-function json-read "json" ())
|
||||||
`(let ((json-object-type 'plist))
|
`(let ((json-object-type 'plist))
|
||||||
gptel--json-read)))
|
(json-read))))
|
||||||
|
|
||||||
(defmacro gptel--json-encode (object)
|
(defmacro gptel--json-encode (object)
|
||||||
(if (fboundp 'json-serialize)
|
(if (fboundp 'json-serialize)
|
||||||
|
|
484
gptel-org.el
Normal file
484
gptel-org.el
Normal file
|
@ -0,0 +1,484 @@
|
||||||
|
;;; 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-element-begin "org-element")
|
||||||
|
|
||||||
|
;; 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")
|
||||||
|
|
||||||
|
;; Bundle `org-element-lineage-map' if it's not available (for Org 9.67 or older)
|
||||||
|
(eval-when-compile
|
||||||
|
(if (fboundp 'org-element-lineage-map)
|
||||||
|
(progn (declare-function org-element-lineage-map "org-element-ast")
|
||||||
|
(defalias 'gptel-org--element-lineage-map 'org-element-lineage-map))
|
||||||
|
(defun gptel-org--element-lineage-map (datum fun &optional types with-self first-match)
|
||||||
|
"Map FUN across ancestors of DATUM, from closest to furthest.
|
||||||
|
|
||||||
|
DATUM is an object or element. For TYPES, WITH-SELF and
|
||||||
|
FIRST-MATCH see `org-element-lineage-map'.
|
||||||
|
|
||||||
|
This function is provided for compatibility with older versions
|
||||||
|
of Org."
|
||||||
|
(declare (indent 2))
|
||||||
|
(setq fun (if (functionp fun) fun `(lambda (node) ,fun)))
|
||||||
|
(let ((up (if with-self datum (org-element-parent datum)))
|
||||||
|
acc rtn)
|
||||||
|
(catch :--first-match
|
||||||
|
(while up
|
||||||
|
(when (or (not types) (org-element-type-p up types))
|
||||||
|
(setq rtn (funcall fun up))
|
||||||
|
(if (and first-match rtn)
|
||||||
|
(throw :--first-match rtn)
|
||||||
|
(when rtn (push rtn acc))))
|
||||||
|
(setq up (org-element-parent up)))
|
||||||
|
(nreverse acc))))))
|
||||||
|
|
||||||
|
|
||||||
|
;;; 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-org--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 (gptel-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))))
|
||||||
|
|
||||||
|
(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
|
||||||
|
(defun gptel-org--entry-properties (&optional pt)
|
||||||
|
"Find gptel configuration properties stored in the current heading."
|
||||||
|
(pcase-let
|
||||||
|
((`(,system ,backend ,model ,temperature ,tokens)
|
||||||
|
(mapcar
|
||||||
|
(lambda (prop) (org-entry-get (or pt (point)) prop 'selective))
|
||||||
|
'("GPTEL_SYSTEM" "GPTEL_BACKEND" "GPTEL_MODEL"
|
||||||
|
"GPTEL_TEMPERATURE" "GPTEL_MAX_TOKENS"))))
|
||||||
|
(when system
|
||||||
|
(setq system (string-replace "\\n" "\n" system)))
|
||||||
|
(when backend
|
||||||
|
(setq backend (alist-get backend gptel--known-backends
|
||||||
|
nil nil #'equal)))
|
||||||
|
(when temperature
|
||||||
|
(setq temperature (gptel--numberize temperature)))
|
||||||
|
(when tokens (setq tokens (gptel--numberize tokens)))
|
||||||
|
(list system backend model temperature tokens)))
|
||||||
|
|
||||||
|
(defun gptel-org--restore-state ()
|
||||||
|
"Restore gptel state for Org buffers when turning on `gptel-mode'."
|
||||||
|
(save-restriction
|
||||||
|
(widen)
|
||||||
|
(condition-case status
|
||||||
|
(progn
|
||||||
|
(when-let ((bounds (org-entry-get (point-min) "GPTEL_BOUNDS")))
|
||||||
|
(mapc (pcase-lambda (`(,beg . ,end))
|
||||||
|
(put-text-property beg end 'gptel 'response))
|
||||||
|
(read bounds)))
|
||||||
|
(pcase-let ((`(,system ,backend ,model ,temperature ,tokens)
|
||||||
|
(gptel-org--entry-properties (point-min))))
|
||||||
|
(when system (setq-local gptel--system-message system))
|
||||||
|
(if backend (setq-local gptel-backend backend)
|
||||||
|
(message
|
||||||
|
(substitute-command-keys
|
||||||
|
(concat
|
||||||
|
"Could not activate gptel backend \"%s\"! "
|
||||||
|
"Switch backends with \\[universal-argument] \\[gptel-send]"
|
||||||
|
" before using gptel."))
|
||||||
|
backend))
|
||||||
|
(when model (setq-local gptel-model model))
|
||||||
|
(when temperature (setq-local gptel-temperature temperature))
|
||||||
|
(when tokens (setq-local gptel-max-tokens tokens))))
|
||||||
|
(:success (message "gptel chat restored."))
|
||||||
|
(error (message "Could not restore gptel state, sorry! Error: %s" status)))))
|
||||||
|
|
||||||
|
(defun gptel-org-set-properties (pt &optional msg)
|
||||||
|
"Store the active gptel configuration under the current heading.
|
||||||
|
|
||||||
|
The active gptel configuration includes the current system
|
||||||
|
message, language model and provider (backend), and additional
|
||||||
|
settings when applicable.
|
||||||
|
|
||||||
|
PT is the cursor position by default. If MSG is
|
||||||
|
non-nil (default), display a message afterwards."
|
||||||
|
(interactive (list (point) t))
|
||||||
|
(org-entry-put pt "GPTEL_MODEL" gptel-model)
|
||||||
|
(org-entry-put pt "GPTEL_BACKEND" (gptel-backend-name gptel-backend))
|
||||||
|
(unless (equal (default-value 'gptel-temperature) gptel-temperature)
|
||||||
|
(org-entry-put pt "GPTEL_TEMPERATURE"
|
||||||
|
(number-to-string gptel-temperature)))
|
||||||
|
(org-entry-put pt "GPTEL_SYSTEM"
|
||||||
|
(string-replace "\n" "\\n" gptel--system-message))
|
||||||
|
(when gptel-max-tokens
|
||||||
|
(org-entry-put
|
||||||
|
pt "GPTEL_MAX_TOKENS" (number-to-string gptel-max-tokens)))
|
||||||
|
(when msg
|
||||||
|
(message "Added gptel configuration to current headline.")))
|
||||||
|
|
||||||
|
(defun gptel-org--save-state ()
|
||||||
|
"Write the gptel state to the Org buffer as Org properties."
|
||||||
|
(org-with-wide-buffer
|
||||||
|
(goto-char (point-min))
|
||||||
|
(when (org-at-heading-p)
|
||||||
|
(org-open-line 1))
|
||||||
|
(gptel-org-set-properties (point-min))
|
||||||
|
;; Save response boundaries
|
||||||
|
(letrec ((write-bounds
|
||||||
|
(lambda (attempts)
|
||||||
|
(let* ((bounds (gptel--get-buffer-bounds))
|
||||||
|
(offset (caar bounds))
|
||||||
|
(offset-marker (set-marker (make-marker) offset)))
|
||||||
|
(org-entry-put (point-min) "GPTEL_BOUNDS"
|
||||||
|
(prin1-to-string (gptel--get-buffer-bounds)))
|
||||||
|
(when (and (not (= (marker-position offset-marker) offset))
|
||||||
|
(> attempts 0))
|
||||||
|
(funcall write-bounds (1- attempts)))))))
|
||||||
|
(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: heading, emphasis, bold and bullet items
|
||||||
|
((and (guard (and (not in-src-block) (eq (char-before) ?#))) heading)
|
||||||
|
(if (eobp)
|
||||||
|
;; Not enough information about the heading yet
|
||||||
|
(progn (setq noop-p t) (set-marker start-pt (match-beginning 0)))
|
||||||
|
;; Convert markdown heading to Org heading
|
||||||
|
(when (looking-at "[[:space:]]")
|
||||||
|
(delete-region (line-beginning-position) (point))
|
||||||
|
(insert (make-string (length heading) ?*)))))
|
||||||
|
((and "**" (guard (not in-src-block)))
|
||||||
|
(cond
|
||||||
|
;; TODO Not sure why this branch was needed
|
||||||
|
;; ((looking-at "\\*\\(?:[[:word:]]\\|\s\\)") (delete-char 1))
|
||||||
|
|
||||||
|
;; Looking back at "w**" or " **"
|
||||||
|
((looking-back "\\(?:[[:word:][:punct:]\n]\\|\s\\)\\*\\{2\\}"
|
||||||
|
(max (- (point) 3) (point-min)))
|
||||||
|
(delete-char -1))))
|
||||||
|
((and "*" (guard (not in-src-block)))
|
||||||
|
(if (eobp)
|
||||||
|
;; Not enough information about the "*" yet
|
||||||
|
(progn (setq noop-p t) (set-marker start-pt (match-beginning 0)))
|
||||||
|
;; "*" is either emphasis or a bullet point
|
||||||
|
(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)
|
||||||
|
;;; gptel-org.el ends here
|
|
@ -33,7 +33,61 @@
|
||||||
(declare-function ediff-make-cloned-buffer "ediff-utils")
|
(declare-function ediff-make-cloned-buffer "ediff-utils")
|
||||||
|
|
||||||
|
|
||||||
;; * Helper functions
|
;; * Helper functions and vars
|
||||||
|
|
||||||
|
(defvar gptel--set-buffer-locally nil
|
||||||
|
"Set model parameters from `gptel-menu' buffer-locally.
|
||||||
|
|
||||||
|
Affects the system message too.")
|
||||||
|
|
||||||
|
(defun gptel--set-with-scope (sym value &optional scope)
|
||||||
|
"Set SYMBOL's symbol-value to VALUE with SCOPE.
|
||||||
|
|
||||||
|
If SCOPE is non-nil, set it buffer-locally, else clear any
|
||||||
|
buffer-local value and set its default global value."
|
||||||
|
(if scope
|
||||||
|
(set (make-local-variable sym) value)
|
||||||
|
(kill-local-variable sym)
|
||||||
|
(set sym value)))
|
||||||
|
|
||||||
|
(defun gptel--get-directive (args)
|
||||||
|
"Find the additional directive in the transient ARGS.
|
||||||
|
|
||||||
|
Meant to be called when `gptel-menu' is active."
|
||||||
|
(cl-some (lambda (s) (and (stringp s) (string-prefix-p ":" s)
|
||||||
|
(concat "\n\n" (substring s 1))))
|
||||||
|
args))
|
||||||
|
|
||||||
|
(defun gptel--instructions-make-overlay (text &optional ov)
|
||||||
|
"TODO"
|
||||||
|
(save-excursion
|
||||||
|
(cond
|
||||||
|
((use-region-p) (goto-char (region-beginning)))
|
||||||
|
((gptel--in-response-p) (gptel-beginning-of-response))
|
||||||
|
(t (text-property-search-backward 'gptel 'response)))
|
||||||
|
(skip-chars-forward "\n \t")
|
||||||
|
(if (and ov (overlayp ov))
|
||||||
|
(move-overlay ov (point) (point) (current-buffer))
|
||||||
|
(setq ov (make-overlay (point) (point) nil t)))
|
||||||
|
(overlay-put ov 'before-string nil)
|
||||||
|
;; (unless (or (bobp) (eq (char-before) "\n"))
|
||||||
|
;; (overlay-put ov 'before-string (propertize "\n" 'font-lock-face 'shadow)))
|
||||||
|
(overlay-put ov 'category 'gptel)
|
||||||
|
(overlay-put
|
||||||
|
ov 'after-string
|
||||||
|
(concat
|
||||||
|
(propertize (concat "GPTEL: " text)
|
||||||
|
'font-lock-face '(:inherit shadow :box t))
|
||||||
|
"\n"))
|
||||||
|
ov))
|
||||||
|
|
||||||
|
(defun gptel--transient-read-variable (prompt initial-input history)
|
||||||
|
"Read value from minibuffer and interpret the result as a Lisp object.
|
||||||
|
|
||||||
|
PROMPT, INITIAL-INPUT and HISTORY are as in the Transient reader
|
||||||
|
documention."
|
||||||
|
(ignore-errors
|
||||||
|
(read-from-minibuffer prompt initial-input read-expression-map t history)))
|
||||||
|
|
||||||
(defun gptel--refactor-or-rewrite ()
|
(defun gptel--refactor-or-rewrite ()
|
||||||
"Rewrite should be refactored into refactor.
|
"Rewrite should be refactored into refactor.
|
||||||
|
@ -47,7 +101,9 @@ Or is it the other way around?"
|
||||||
"Set a generic refactor/rewrite message for the buffer."
|
"Set a generic refactor/rewrite message for the buffer."
|
||||||
(if (derived-mode-p 'prog-mode)
|
(if (derived-mode-p 'prog-mode)
|
||||||
(format "You are a %s programmer. Refactor the following code. Generate only code, no explanation."
|
(format "You are a %s programmer. Refactor the following code. Generate only code, no explanation."
|
||||||
(substring (symbol-name major-mode) nil -5))
|
(thread-last (symbol-name major-mode)
|
||||||
|
(string-remove-suffix "-mode")
|
||||||
|
(string-remove-suffix "-ts")))
|
||||||
(format "You are a prose editor. Rewrite the following text to be more professional.")))
|
(format "You are a prose editor. Rewrite the following text to be more professional.")))
|
||||||
|
|
||||||
(defvar gptel--crowdsourced-prompts-url
|
(defvar gptel--crowdsourced-prompts-url
|
||||||
|
@ -105,6 +161,100 @@ which see."
|
||||||
(forward-line 1)))))
|
(forward-line 1)))))
|
||||||
gptel--crowdsourced-prompts))
|
gptel--crowdsourced-prompts))
|
||||||
|
|
||||||
|
|
||||||
|
;; * Transient classes and methods for gptel
|
||||||
|
|
||||||
|
(defclass gptel--switches (transient-lisp-variable)
|
||||||
|
((display-if-true :initarg :display-if-true :initform "for this buffer")
|
||||||
|
(display-if-false :initarg :display-if-false :initform "globally"))
|
||||||
|
"Boolean lisp variable class for gptel-transient.")
|
||||||
|
|
||||||
|
(cl-defmethod transient-infix-read ((obj gptel--switches))
|
||||||
|
"Cycle through the mutually exclusive switches."
|
||||||
|
(not (oref obj value)))
|
||||||
|
|
||||||
|
(cl-defmethod transient-format-value ((obj gptel--switches))
|
||||||
|
(with-slots (value display-if-true display-if-false) obj
|
||||||
|
(format
|
||||||
|
(propertize "(%s)" 'face 'transient-delimiter)
|
||||||
|
(concat
|
||||||
|
(propertize display-if-false
|
||||||
|
'face (if value 'transient-inactive-value 'transient-value))
|
||||||
|
(propertize "|" 'face 'transient-delimiter)
|
||||||
|
(propertize display-if-true
|
||||||
|
'face (if value 'transient-value 'transient-inactive-value))))))
|
||||||
|
|
||||||
|
(defclass gptel-lisp-variable (transient-lisp-variable)
|
||||||
|
((display-nil :initarg :display-nil))
|
||||||
|
"Lisp variables that show :display-nil instead of nil.")
|
||||||
|
|
||||||
|
(cl-defmethod transient-format-value
|
||||||
|
((obj gptel-lisp-variable))
|
||||||
|
(propertize (prin1-to-string (or (oref obj value)
|
||||||
|
(oref obj display-nil)))
|
||||||
|
'face 'transient-value))
|
||||||
|
|
||||||
|
(cl-defmethod transient-infix-set ((obj gptel-lisp-variable) value)
|
||||||
|
(funcall (oref obj set-value)
|
||||||
|
(oref obj variable)
|
||||||
|
(oset obj value value)
|
||||||
|
gptel--set-buffer-locally))
|
||||||
|
|
||||||
|
(defclass gptel-provider-variable (transient-lisp-variable)
|
||||||
|
((model :initarg :model)
|
||||||
|
(model-value :initarg :model-value)
|
||||||
|
(always-read :initform t)
|
||||||
|
(set-value :initarg :set-value :initform #'set))
|
||||||
|
"Class used for gptel-backends.")
|
||||||
|
|
||||||
|
(cl-defmethod transient-format-value ((obj gptel-provider-variable))
|
||||||
|
(propertize (concat (gptel-backend-name (oref obj value)) ":"
|
||||||
|
(buffer-local-value (oref obj model) transient--original-buffer))
|
||||||
|
'face 'transient-value))
|
||||||
|
|
||||||
|
(cl-defmethod transient-infix-set ((obj gptel-provider-variable) value)
|
||||||
|
(pcase-let ((`(,backend-value ,model-value) value))
|
||||||
|
(funcall (oref obj set-value)
|
||||||
|
(oref obj variable)
|
||||||
|
(oset obj value backend-value)
|
||||||
|
gptel--set-buffer-locally)
|
||||||
|
(funcall (oref obj set-value)
|
||||||
|
(oref obj model)
|
||||||
|
(oset obj model-value model-value)
|
||||||
|
gptel--set-buffer-locally)))
|
||||||
|
|
||||||
|
(defclass gptel-option-overlaid (transient-option)
|
||||||
|
((display-nil :initarg :display-nil)
|
||||||
|
(overlay :initarg :overlay))
|
||||||
|
"Transient options for overlays displayed in the working buffer.")
|
||||||
|
|
||||||
|
(cl-defmethod transient-format-value ((obj gptel-option-overlaid))
|
||||||
|
"set up the in-buffer overlay for additional directive, a string.
|
||||||
|
|
||||||
|
Also format its value in the Transient menu."
|
||||||
|
(let ((value (oref obj value))
|
||||||
|
(ov (oref obj overlay))
|
||||||
|
(argument (oref obj argument)))
|
||||||
|
;; Making an overlay
|
||||||
|
(if (or (not value) (string-empty-p value))
|
||||||
|
(when ov (delete-overlay ov))
|
||||||
|
(with-current-buffer transient--original-buffer
|
||||||
|
(oset obj overlay (gptel--instructions-make-overlay value ov)))
|
||||||
|
(letrec ((ov-clear-hook
|
||||||
|
(lambda () (when-let* ((ov (oref obj overlay))
|
||||||
|
((overlayp ov)))
|
||||||
|
(remove-hook 'transient-exit-hook
|
||||||
|
ov-clear-hook)
|
||||||
|
(delete-overlay ov)))))
|
||||||
|
(add-hook 'transient-exit-hook ov-clear-hook)))
|
||||||
|
;; Updating transient menu display
|
||||||
|
(if value
|
||||||
|
(propertize (concat argument (truncate-string-to-width value 25 nil nil "..."))
|
||||||
|
'face 'transient-value)
|
||||||
|
(propertize
|
||||||
|
(concat "(" (symbol-name (oref obj display-nil)) ")")
|
||||||
|
'face 'transient-inactive-value))))
|
||||||
|
|
||||||
|
|
||||||
;; * Transient Prefixes
|
;; * Transient Prefixes
|
||||||
|
|
||||||
|
@ -116,23 +266,29 @@ which see."
|
||||||
"Change parameters of prompt to send to the LLM."
|
"Change parameters of prompt to send to the LLM."
|
||||||
;; :incompatible '(("-m" "-n" "-k" "-e"))
|
;; :incompatible '(("-m" "-n" "-k" "-e"))
|
||||||
[:description
|
[:description
|
||||||
(lambda () (format "Directive: %s"
|
(lambda ()
|
||||||
|
(string-replace
|
||||||
|
"\n" "⮐ "
|
||||||
(truncate-string-to-width
|
(truncate-string-to-width
|
||||||
gptel--system-message (max (- (window-width) 14) 20) nil nil t)))
|
gptel--system-message (max (- (window-width) 12) 14) nil nil t)))
|
||||||
("h" "Set directives for chat" gptel-system-prompt :transient t)]
|
[""
|
||||||
[["Session Parameters"
|
"Instructions"
|
||||||
|
("s" "Set system message" gptel-system-prompt :transient t)
|
||||||
|
(gptel--infix-add-directive)]]
|
||||||
|
[["Model Parameters"
|
||||||
|
:pad-keys t
|
||||||
|
(gptel--infix-variable-scope)
|
||||||
(gptel--infix-provider)
|
(gptel--infix-provider)
|
||||||
;; (gptel--infix-model)
|
|
||||||
(gptel--infix-max-tokens)
|
(gptel--infix-max-tokens)
|
||||||
(gptel--infix-num-messages-to-send)
|
(gptel--infix-num-messages-to-send)
|
||||||
(gptel--infix-temperature)]
|
(gptel--infix-temperature :if (lambda () gptel-expert-commands))]
|
||||||
["Prompt from"
|
["Prompt from"
|
||||||
("p" "Minibuffer instead" "p")
|
("m" "Minibuffer instead" "m")
|
||||||
("y" "Kill-ring instead" "y")
|
("y" "Kill-ring instead" "y")
|
||||||
""
|
""
|
||||||
("i" "Replace/Delete prompt" "i")]
|
("i" "Respond in place" "i")]
|
||||||
["Response to"
|
["Response to"
|
||||||
("m" "Minibuffer instead" "m")
|
("e" "Echo area instead" "e")
|
||||||
("g" "gptel session" "g"
|
("g" "gptel session" "g"
|
||||||
:class transient-option
|
:class transient-option
|
||||||
:prompt "Existing or new gptel session: "
|
:prompt "Existing or new gptel session: "
|
||||||
|
@ -175,19 +331,24 @@ which see."
|
||||||
:transient t)
|
:transient t)
|
||||||
("E" "Ediff previous" gptel--ediff
|
("E" "Ediff previous" gptel--ediff
|
||||||
:if gptel--at-response-history-p)]
|
:if gptel--at-response-history-p)]
|
||||||
["Inspect"
|
["Dry Run" :if (lambda () (or gptel-log-level gptel-expert-commands))
|
||||||
("I" "Query as Lisp"
|
("I" "Inspect query (Lisp)"
|
||||||
(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)
|
||||||
(gptel--sanitize-model)
|
(gptel--sanitize-model)
|
||||||
(gptel--inspect-query)))
|
(gptel--inspect-query
|
||||||
("J" "Query as JSON"
|
(gptel--suffix-send
|
||||||
|
(cons "I" (transient-args transient-current-command))))))
|
||||||
|
("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)
|
||||||
(gptel--sanitize-model)
|
(gptel--sanitize-model)
|
||||||
(gptel--inspect-query 'json)))]]
|
(gptel--inspect-query
|
||||||
|
(gptel--suffix-send
|
||||||
|
(cons "I" (transient-args transient-current-command)))
|
||||||
|
'json)))]]
|
||||||
(interactive)
|
(interactive)
|
||||||
(gptel--sanitize-model)
|
(gptel--sanitize-model)
|
||||||
(transient-setup 'gptel-menu))
|
(transient-setup 'gptel-menu))
|
||||||
|
@ -199,7 +360,7 @@ which see."
|
||||||
'gptel-system-prompt
|
'gptel-system-prompt
|
||||||
(cl-loop for (type . prompt) in gptel-directives
|
(cl-loop for (type . prompt) in gptel-directives
|
||||||
;; Avoid clashes with the custom directive key
|
;; Avoid clashes with the custom directive key
|
||||||
with unused-keys = (delete ?h (number-sequence ?a ?z))
|
with unused-keys = (delete ?s (number-sequence ?a ?z))
|
||||||
with width = (window-width)
|
with width = (window-width)
|
||||||
for name = (symbol-name type)
|
for name = (symbol-name type)
|
||||||
for key = (seq-find (lambda (k) (member k unused-keys)) name (seq-first unused-keys))
|
for key = (seq-find (lambda (k) (member k unused-keys)) name (seq-first unused-keys))
|
||||||
|
@ -224,12 +385,12 @@ which see."
|
||||||
(message "Directive: %s"
|
(message "Directive: %s"
|
||||||
,(string-replace "\n" "⮐ "
|
,(string-replace "\n" "⮐ "
|
||||||
(truncate-string-to-width prompt 100 nil nil t)))
|
(truncate-string-to-width prompt 100 nil nil t)))
|
||||||
(setq gptel--system-message ,prompt))
|
(gptel--set-with-scope 'gptel--system-message ,prompt
|
||||||
|
gptel--set-buffer-locally))
|
||||||
:transient 'transient--do-return)
|
:transient 'transient--do-return)
|
||||||
into prompt-suffixes
|
into prompt-suffixes
|
||||||
finally return
|
finally return
|
||||||
(nconc
|
(nconc
|
||||||
(list (list 'gptel--suffix-system-message))
|
|
||||||
prompt-suffixes
|
prompt-suffixes
|
||||||
(list (list "SPC" "Pick crowdsourced prompt"
|
(list (list "SPC" "Pick crowdsourced prompt"
|
||||||
'gptel--read-crowdsourced-prompt
|
'gptel--read-crowdsourced-prompt
|
||||||
|
@ -238,22 +399,29 @@ which see."
|
||||||
;; instead of returning to the system prompt menu.
|
;; instead of returning to the system prompt menu.
|
||||||
:transient 'transient--do-exit))))))
|
:transient 'transient--do-exit))))))
|
||||||
|
|
||||||
|
;;;###autoload (autoload 'gptel-system-prompt "gptel-transient" nil t)
|
||||||
(transient-define-prefix gptel-system-prompt ()
|
(transient-define-prefix gptel-system-prompt ()
|
||||||
"Change the LLM system prompt.
|
"Set the LLM system message for LLM interactions in this buffer.
|
||||||
|
|
||||||
The \"system\" prompt establishes directives for the chat
|
The \"system message\" establishes directives for the chat
|
||||||
session. Some examples of system prompts are:
|
session and modifies the behavior of the LLM. Some examples of
|
||||||
|
system prompts are:
|
||||||
|
|
||||||
You are a helpful assistant. Answer as concisely as possible.
|
You are a helpful assistant. Answer as concisely as possible.
|
||||||
Reply only with shell commands and no prose.
|
Reply only with shell commands and no prose.
|
||||||
You are a poet. Reply only in verse.
|
You are a poet. Reply only in verse.
|
||||||
|
|
||||||
|
More extensive system messages can be useful for specific tasks.
|
||||||
|
|
||||||
Customize `gptel-directives' for task-specific prompts."
|
Customize `gptel-directives' for task-specific prompts."
|
||||||
[:description
|
[:description
|
||||||
(lambda () (format "Current directive: %s"
|
(lambda () (string-replace
|
||||||
|
"\n" "⮐ "
|
||||||
(truncate-string-to-width
|
(truncate-string-to-width
|
||||||
gptel--system-message 100 nil nil t)))
|
gptel--system-message (max (- (window-width) 12) 14) nil nil t)))
|
||||||
:class transient-column
|
[(gptel--suffix-system-message)]
|
||||||
|
[(gptel--infix-variable-scope)]]
|
||||||
|
[:class transient-column
|
||||||
:setup-children gptel-system-prompt--setup
|
:setup-children gptel-system-prompt--setup
|
||||||
:pad-keys t])
|
:pad-keys t])
|
||||||
|
|
||||||
|
@ -286,23 +454,14 @@ Customize `gptel-directives' for task-specific prompts."
|
||||||
|
|
||||||
;; ** Infixes for model parameters
|
;; ** Infixes for model parameters
|
||||||
|
|
||||||
(defun gptel--transient-read-variable (prompt initial-input history)
|
(transient-define-infix gptel--infix-variable-scope ()
|
||||||
"Read value from minibuffer and interpret the result as a Lisp object.
|
"Set gptel's model parameters and system message in this buffer or globally."
|
||||||
|
:argument "scope"
|
||||||
PROMPT, INITIAL-INPUT and HISTORY are as in the Transient reader
|
:variable 'gptel--set-buffer-locally
|
||||||
documention."
|
:class 'gptel--switches
|
||||||
(ignore-errors
|
:format " %k %d %v"
|
||||||
(read-from-minibuffer prompt initial-input read-expression-map t history)))
|
:key "="
|
||||||
|
:description (propertize "Set" 'face 'transient-inactive-argument))
|
||||||
(defclass gptel-lisp-variable (transient-lisp-variable)
|
|
||||||
((display-nil :initarg :display-nil))
|
|
||||||
"Lisp variables that show :display-nil instead of nil.")
|
|
||||||
|
|
||||||
(cl-defmethod transient-format-value
|
|
||||||
((obj gptel-lisp-variable))
|
|
||||||
(propertize (prin1-to-string (or (oref obj value)
|
|
||||||
(oref obj display-nil)))
|
|
||||||
'face 'transient-value))
|
|
||||||
|
|
||||||
(transient-define-infix gptel--infix-num-messages-to-send ()
|
(transient-define-infix gptel--infix-num-messages-to-send ()
|
||||||
"Number of recent messages to send with each exchange.
|
"Number of recent messages to send with each exchange.
|
||||||
|
@ -314,6 +473,7 @@ include."
|
||||||
:description "previous responses"
|
:description "previous responses"
|
||||||
:class 'gptel-lisp-variable
|
:class 'gptel-lisp-variable
|
||||||
:variable 'gptel--num-messages-to-send
|
:variable 'gptel--num-messages-to-send
|
||||||
|
:set-value #'gptel--set-with-scope
|
||||||
:display-nil 'all
|
:display-nil 'all
|
||||||
:format " %k %v %d"
|
:format " %k %v %d"
|
||||||
:key "-n"
|
:key "-n"
|
||||||
|
@ -329,79 +489,72 @@ responses."
|
||||||
:description "Response length (tokens)"
|
:description "Response length (tokens)"
|
||||||
:class 'gptel-lisp-variable
|
:class 'gptel-lisp-variable
|
||||||
:variable 'gptel-max-tokens
|
:variable 'gptel-max-tokens
|
||||||
|
:set-value #'gptel--set-with-scope
|
||||||
:display-nil 'auto
|
:display-nil 'auto
|
||||||
:key "-c"
|
:key "-c"
|
||||||
:prompt "Response length in tokens (leave empty: default, 80-200: short, 200-500: long): "
|
:prompt "Response length in tokens (leave empty: default, 80-200: short, 200-500: long): "
|
||||||
:reader 'gptel--transient-read-variable)
|
:reader 'gptel--transient-read-variable)
|
||||||
|
|
||||||
(defclass gptel-provider-variable (transient-lisp-variable)
|
|
||||||
((model :initarg :model)
|
|
||||||
(model-value :initarg :model-value)
|
|
||||||
(always-read :initform t)
|
|
||||||
(set-value :initarg :set-value :initform #'set))
|
|
||||||
"Class used for gptel-backends.")
|
|
||||||
|
|
||||||
(cl-defmethod transient-format-value ((obj gptel-provider-variable))
|
|
||||||
(propertize (concat (gptel-backend-name (oref obj value)) ":"
|
|
||||||
(buffer-local-value (oref obj model) transient--original-buffer))
|
|
||||||
'face 'transient-value))
|
|
||||||
|
|
||||||
(cl-defmethod transient-infix-set ((obj gptel-provider-variable) value)
|
|
||||||
(pcase-let ((`(,backend-value ,model-value) value))
|
|
||||||
(funcall (oref obj set-value)
|
|
||||||
(oref obj variable)
|
|
||||||
(oset obj value backend-value))
|
|
||||||
(funcall (oref obj set-value)
|
|
||||||
(oref obj model)
|
|
||||||
(oset obj model-value model-value))))
|
|
||||||
|
|
||||||
(transient-define-infix gptel--infix-provider ()
|
(transient-define-infix gptel--infix-provider ()
|
||||||
"AI Provider for Chat."
|
"AI Provider for Chat."
|
||||||
:description "GPT Model"
|
:description "GPT Model"
|
||||||
:class 'gptel-provider-variable
|
:class 'gptel-provider-variable
|
||||||
:prompt "Model provider: "
|
:prompt "Model: "
|
||||||
:variable 'gptel-backend
|
:variable 'gptel-backend
|
||||||
|
:set-value #'gptel--set-with-scope
|
||||||
:model 'gptel-model
|
:model 'gptel-model
|
||||||
:key "-m"
|
:key "-m"
|
||||||
:reader (lambda (prompt &rest _)
|
:reader (lambda (prompt &rest _)
|
||||||
(let* ((backend-name
|
(cl-loop
|
||||||
(if (<= (length gptel--known-backends) 1)
|
for (name . backend) in gptel--known-backends
|
||||||
(caar gptel--known-backends)
|
nconc (cl-loop for model in (gptel-backend-models backend)
|
||||||
(completing-read
|
collect (list (concat name ":" model) backend model))
|
||||||
prompt
|
into models-alist finally return
|
||||||
(mapcar #'car gptel--known-backends))))
|
(cdr (assoc (completing-read prompt models-alist nil t)
|
||||||
(backend (alist-get backend-name gptel--known-backends
|
models-alist)))))
|
||||||
nil nil #'equal))
|
|
||||||
(backend-models (gptel-backend-models backend))
|
|
||||||
(model-name (if (= (length backend-models) 1)
|
|
||||||
(car backend-models)
|
|
||||||
(completing-read
|
|
||||||
"Model: " backend-models))))
|
|
||||||
(list backend model-name))))
|
|
||||||
|
|
||||||
(transient-define-infix gptel--infix-model ()
|
|
||||||
"AI Model for Chat."
|
|
||||||
:description "GPT Model: "
|
|
||||||
:class 'transient-lisp-variable
|
|
||||||
:variable 'gptel-model
|
|
||||||
:key "-m"
|
|
||||||
:choices '("gpt-3.5-turbo" "gpt-3.5-turbo-16k" "gpt-4" "gpt-4-1106-preview")
|
|
||||||
:reader (lambda (prompt &rest _)
|
|
||||||
(completing-read
|
|
||||||
prompt
|
|
||||||
'("gpt-3.5-turbo" "gpt-3.5-turbo-16k" "gpt-4" "gpt-4-1106-preview"))))
|
|
||||||
|
|
||||||
(transient-define-infix gptel--infix-temperature ()
|
(transient-define-infix gptel--infix-temperature ()
|
||||||
"Temperature of request."
|
"Temperature of request."
|
||||||
:description "Randomness (0 - 2.0)"
|
:description "Temperature (0 - 2.0)"
|
||||||
:class 'transient-lisp-variable
|
:class 'transient-lisp-variable
|
||||||
:variable 'gptel-temperature
|
:variable 'gptel-temperature
|
||||||
|
:set-value #'gptel--set-with-scope
|
||||||
:key "-t"
|
:key "-t"
|
||||||
:prompt "Set temperature (0.0-2.0, leave empty for default): "
|
:prompt "Temperature controls the response randomness (0.0-2.0, leave empty for default): "
|
||||||
:reader 'gptel--transient-read-variable)
|
:reader 'gptel--transient-read-variable)
|
||||||
|
|
||||||
;; ** Infix for the refactor/rewrite system message
|
;; ** Infix for the refactor/rewrite system message
|
||||||
|
|
||||||
|
(transient-define-infix gptel--infix-add-directive ()
|
||||||
|
"Additional directive intended for the next query only.
|
||||||
|
|
||||||
|
This is useful to define a quick task on top of a more extensive
|
||||||
|
or detailed system message.
|
||||||
|
|
||||||
|
For example, with code/text selected:
|
||||||
|
|
||||||
|
- Rewrite this function to do X while avoiding Y.
|
||||||
|
- Change the tone of the following paragraph to be more direct.
|
||||||
|
|
||||||
|
Or in an extended conversation:
|
||||||
|
|
||||||
|
- Phrase you next response in ten words or less.
|
||||||
|
- Pretend for now that you're an anthropologist."
|
||||||
|
:class 'gptel-option-overlaid
|
||||||
|
;; :variable 'gptel--instructions
|
||||||
|
:display-nil 'none
|
||||||
|
:overlay nil
|
||||||
|
:argument ":"
|
||||||
|
:prompt "Instructions for next response only: "
|
||||||
|
:reader (lambda (prompt initial history)
|
||||||
|
(let* ((extra (read-string prompt initial history)))
|
||||||
|
(unless (string-empty-p extra) extra)))
|
||||||
|
:format " %k %d %v"
|
||||||
|
:key "d"
|
||||||
|
:argument ":"
|
||||||
|
:description "Add directive"
|
||||||
|
:transient t)
|
||||||
|
|
||||||
(transient-define-infix gptel--infix-rewrite-prompt ()
|
(transient-define-infix gptel--infix-rewrite-prompt ()
|
||||||
"Chat directive (system message) to use for rewriting or refactoring."
|
"Chat directive (system message) to use for rewriting or refactoring."
|
||||||
:description (lambda () (if (derived-mode-p 'prog-mode)
|
:description (lambda () (if (derived-mode-p 'prog-mode)
|
||||||
|
@ -435,16 +588,17 @@ responses."
|
||||||
(backend-name (gptel-backend-name gptel-backend))
|
(backend-name (gptel-backend-name gptel-backend))
|
||||||
(buffer) (position)
|
(buffer) (position)
|
||||||
(callback) (gptel-buffer-name)
|
(callback) (gptel-buffer-name)
|
||||||
|
(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
|
||||||
((member "p" args)
|
((member "m" args)
|
||||||
(read-string
|
(read-string
|
||||||
(format "Ask %s: " (gptel-backend-name gptel-backend))
|
(format "Ask %s: " (gptel-backend-name gptel-backend))
|
||||||
(apply #'buffer-substring-no-properties
|
(and (use-region-p)
|
||||||
(if (use-region-p)
|
(buffer-substring-no-properties
|
||||||
(list (region-beginning) (region-end))
|
(region-beginning) (region-end)))))
|
||||||
(list (line-beginning-position) (line-end-position))))))
|
|
||||||
((member "y" args)
|
((member "y" args)
|
||||||
(unless (car-safe kill-ring)
|
(unless (car-safe kill-ring)
|
||||||
(user-error "`kill-ring' is empty! Nothing to send"))
|
(user-error "`kill-ring' is empty! Nothing to send"))
|
||||||
|
@ -454,7 +608,7 @@ responses."
|
||||||
|
|
||||||
;; Output redirection: Send response elsewhere?
|
;; Output redirection: Send response elsewhere?
|
||||||
(cond
|
(cond
|
||||||
((member "m" args)
|
((member "e" args)
|
||||||
(setq stream nil)
|
(setq stream nil)
|
||||||
(setq callback
|
(setq callback
|
||||||
(lambda (resp info)
|
(lambda (resp info)
|
||||||
|
@ -472,7 +626,7 @@ responses."
|
||||||
backend-name
|
backend-name
|
||||||
(truncate-string-to-width resp 30))))))
|
(truncate-string-to-width resp 30))))))
|
||||||
((setq gptel-buffer-name
|
((setq gptel-buffer-name
|
||||||
(cl-some (lambda (s) (and (string-prefix-p "g" s)
|
(cl-some (lambda (s) (and (stringp s) (string-prefix-p "g" s)
|
||||||
(substring s 1)))
|
(substring s 1)))
|
||||||
args))
|
args))
|
||||||
(setq output-to-other-buffer-p t)
|
(setq output-to-other-buffer-p t)
|
||||||
|
@ -524,17 +678,27 @@ responses."
|
||||||
(gptel--update-status " Waiting..." 'warning)
|
(gptel--update-status " Waiting..." 'warning)
|
||||||
(setq position (point)))))))
|
(setq position (point)))))))
|
||||||
((setq gptel-buffer-name
|
((setq gptel-buffer-name
|
||||||
(cl-some (lambda (s) (and (string-prefix-p "b" s)
|
(cl-some (lambda (s) (and (stringp s) (string-prefix-p "b" s)
|
||||||
(substring s 1)))
|
(substring s 1)))
|
||||||
args))
|
args))
|
||||||
(setq output-to-other-buffer-p t)
|
(setq output-to-other-buffer-p t)
|
||||||
(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)))))
|
||||||
|
|
||||||
;; Create prompt, unless doing input-redirection above
|
(prog1 (gptel-request prompt
|
||||||
(unless prompt
|
:buffer (or buffer (current-buffer))
|
||||||
(setq prompt (gptel--create-prompt (gptel--at-word-end (point)))))
|
:position position
|
||||||
|
:in-place (and in-place (not output-to-other-buffer-p))
|
||||||
|
:stream stream
|
||||||
|
:system (concat gptel--system-message system-extra)
|
||||||
|
:callback callback
|
||||||
|
:dry-run dry-run)
|
||||||
|
|
||||||
|
(gptel--update-status " Waiting..." 'warning)
|
||||||
|
|
||||||
|
;; NOTE: Possible future race condition here if Emacs ever drops the GIL.
|
||||||
|
;; The HTTP request callback might modify the buffer before the in-place
|
||||||
|
;; text is killed below.
|
||||||
(when in-place
|
(when in-place
|
||||||
;; Kill the latest prompt
|
;; Kill the latest prompt
|
||||||
(let ((beg
|
(let ((beg
|
||||||
|
@ -554,20 +718,16 @@ responses."
|
||||||
(list (buffer-substring-no-properties beg end))))
|
(list (buffer-substring-no-properties beg end))))
|
||||||
(kill-region beg end)))
|
(kill-region beg end)))
|
||||||
|
|
||||||
(gptel-request
|
|
||||||
prompt
|
|
||||||
:buffer (or buffer (current-buffer))
|
|
||||||
:position position
|
|
||||||
:in-place (and in-place (not output-to-other-buffer-p))
|
|
||||||
:stream stream
|
|
||||||
:callback callback)
|
|
||||||
(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
|
||||||
|
|
||||||
|
@ -624,10 +784,13 @@ This uses the prompts in the variable
|
||||||
(message "No prompts available.")))
|
(message "No prompts available.")))
|
||||||
|
|
||||||
(transient-define-suffix gptel--suffix-system-message ()
|
(transient-define-suffix gptel--suffix-system-message ()
|
||||||
"Edit LLM directives."
|
"Edit LLM system message.
|
||||||
|
|
||||||
|
When LOCAL is non-nil, set the system message only in the current buffer."
|
||||||
:transient 'transient--do-exit
|
:transient 'transient--do-exit
|
||||||
:description "Set custom directives"
|
:description "Set or edit system message"
|
||||||
:key "h"
|
:format " %k %d"
|
||||||
|
:key "s"
|
||||||
(interactive)
|
(interactive)
|
||||||
(let ((orig-buf (current-buffer))
|
(let ((orig-buf (current-buffer))
|
||||||
(msg-start (make-marker)))
|
(msg-start (make-marker)))
|
||||||
|
@ -678,7 +841,8 @@ This uses the prompts in the variable
|
||||||
(let ((system-message
|
(let ((system-message
|
||||||
(buffer-substring msg-start (point-max))))
|
(buffer-substring msg-start (point-max))))
|
||||||
(with-current-buffer orig-buf
|
(with-current-buffer orig-buf
|
||||||
(setq gptel--system-message system-message)))
|
(gptel--set-with-scope 'gptel--system-message system-message
|
||||||
|
gptel--set-buffer-locally)))
|
||||||
(funcall quit-to-menu)))
|
(funcall quit-to-menu)))
|
||||||
(local-set-key (kbd "C-c C-k") quit-to-menu)))))
|
(local-set-key (kbd "C-c C-k") quit-to-menu)))))
|
||||||
|
|
||||||
|
|
550
gptel.el
550
gptel.el
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
;; Copyright (C) 2023 Karthik Chikmagalur
|
;; Copyright (C) 2023 Karthik Chikmagalur
|
||||||
|
|
||||||
;; Author: Karthik Chikmagalur
|
;; Author: Karthik Chikmagalur <karthik.chikmagalur@gmail.com>
|
||||||
;; Version: 0.7.0
|
;; Version: 0.8.6
|
||||||
;; 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
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
;; gptel supports
|
;; gptel supports
|
||||||
;;
|
;;
|
||||||
;; - The services ChatGPT, Azure, Gemini, Anthropic AI, Anyscale, Together.ai,
|
;; - The services ChatGPT, Azure, Gemini, Anthropic AI, Anyscale, Together.ai,
|
||||||
;; Perplexity, and Kagi (FastGPT & Summarizer)
|
;; Perplexity, Anyscale, OpenRouter, Groq and Kagi (FastGPT & Summarizer)
|
||||||
;; - Local models via Ollama, Llama.cpp, Llamafiles or GPT4All
|
;; - Local models via Ollama, Llama.cpp, Llamafiles or GPT4All
|
||||||
;;
|
;;
|
||||||
;; Additionally, any LLM service (local or remote) that provides an
|
;; Additionally, any LLM service (local or remote) that provides an
|
||||||
|
@ -54,10 +54,14 @@
|
||||||
;; key or to a function of no arguments that returns the key. (It tries to
|
;; key or to a function of no arguments that returns the key. (It tries to
|
||||||
;; use `auth-source' by default)
|
;; use `auth-source' by default)
|
||||||
;;
|
;;
|
||||||
|
;; ChatGPT is configured out of the box. For the other sources:
|
||||||
|
;;
|
||||||
;; - For Azure: define a gptel-backend with `gptel-make-azure', which see.
|
;; - For Azure: define a gptel-backend with `gptel-make-azure', which see.
|
||||||
;; - For Gemini: define a gptel-backend with `gptel-make-gemini', which see.
|
;; - For Gemini: define a gptel-backend with `gptel-make-gemini', which see.
|
||||||
;; - For Anthropic (Claude): define a gptel-backend with `gptel-make-anthropic',
|
;; - For Anthropic (Claude): define a gptel-backend with `gptel-make-anthropic',
|
||||||
;; which see
|
;; which see
|
||||||
|
;; - For Together.ai, Anyscale, Perplexity, Groq and OpenRouter: define a
|
||||||
|
;; gptel-backend with `gptel-make-openai', which see.
|
||||||
;; - For Kagi: define a gptel-backend with `gptel-make-kagi', which see.
|
;; - For Kagi: define a gptel-backend with `gptel-make-kagi', which see.
|
||||||
;;
|
;;
|
||||||
;; For local models using Ollama, Llama.cpp or GPT4All:
|
;; For local models using Ollama, Llama.cpp or GPT4All:
|
||||||
|
@ -65,6 +69,7 @@
|
||||||
;; - The model has to be running on an accessible address (or localhost)
|
;; - The model has to be running on an accessible address (or localhost)
|
||||||
;; - Define a gptel-backend with `gptel-make-ollama' or `gptel-make-gpt4all',
|
;; - Define a gptel-backend with `gptel-make-ollama' or `gptel-make-gpt4all',
|
||||||
;; which see.
|
;; which see.
|
||||||
|
;; - Llama.cpp or Llamafiles: Define a gptel-backend with `gptel-make-openai',
|
||||||
;;
|
;;
|
||||||
;; Consult the package README for examples and more help with configuring
|
;; Consult the package README for examples and more help with configuring
|
||||||
;; backends.
|
;; backends.
|
||||||
|
@ -81,14 +86,14 @@
|
||||||
;; - Call `gptel-send' to send the text up to the cursor. Select a region to
|
;; - Call `gptel-send' to send the text up to the cursor. Select a region to
|
||||||
;; send only the region.
|
;; send only the region.
|
||||||
;;
|
;;
|
||||||
;; - You can select previous prompts and responses to
|
;; - You can select previous prompts and responses to continue the conversation.
|
||||||
;; continue the conversation.
|
|
||||||
;;
|
;;
|
||||||
;; - Call `gptel-send' with a prefix argument to access a menu where you can set
|
;; - Call `gptel-send' with a prefix argument to access a menu where you can set
|
||||||
;; your backend, model and other parameters, or to redirect the
|
;; your backend, model and other parameters, or to redirect the
|
||||||
;; prompt/response.
|
;; prompt/response.
|
||||||
;;
|
;;
|
||||||
;; To use this in a dedicated buffer:
|
;; To use this in a dedicated buffer:
|
||||||
|
;;
|
||||||
;; - M-x gptel: Start a chat session
|
;; - M-x gptel: Start a chat session
|
||||||
;; - C-u M-x gptel: Start another session or multiple independent chat sessions
|
;; - C-u M-x gptel: Start another session or multiple independent chat sessions
|
||||||
;;
|
;;
|
||||||
|
@ -98,31 +103,48 @@
|
||||||
;; model, or choose to redirect the input or output elsewhere (such as to the
|
;; model, or choose to redirect the input or output elsewhere (such as to the
|
||||||
;; kill ring).
|
;; kill ring).
|
||||||
;;
|
;;
|
||||||
;; - You can save this buffer to a file. When opening this file, turning on
|
;; - You can save this buffer to a file. When opening this file, turn on
|
||||||
;; `gptel-mode' will allow resuming the conversation.
|
;; `gptel-mode' before editing it to restore the conversation state and
|
||||||
|
;; continue chatting.
|
||||||
|
;;
|
||||||
|
;; gptel in Org mode:
|
||||||
|
;;
|
||||||
|
;; gptel offers a few extra conveniences in Org mode.
|
||||||
|
;; - You can limit the conversation context to an Org heading with
|
||||||
|
;; `gptel-org-set-topic'.
|
||||||
|
;;
|
||||||
|
;; - You can have branching conversations in Org mode, where each hierarchical
|
||||||
|
;; outline path through the document is a separate conversation branch.
|
||||||
|
;; See the variable `gptel-org-branching-context'.
|
||||||
|
;;
|
||||||
|
;; - You can declare the gptel model, backend, temperature, system message and
|
||||||
|
;; other parameters as Org properties with the command
|
||||||
|
;; `gptel-org-set-properties'. gptel queries under the corresponding heading
|
||||||
|
;; will always use these settings, allowing you to create mostly reproducible
|
||||||
|
;; LLM chat notebooks.
|
||||||
;;
|
;;
|
||||||
;; Finally, gptel offers a general purpose API for writing LLM ineractions
|
;; Finally, gptel offers a general purpose API for writing LLM ineractions
|
||||||
;; that suit how you work, see `gptel-request'.
|
;; that suit your workflow, see `gptel-request'.
|
||||||
|
|
||||||
;;; Code:
|
;;; Code:
|
||||||
(declare-function markdown-mode "markdown-mode")
|
(declare-function markdown-mode "markdown-mode")
|
||||||
(declare-function gptel-curl-get-response "gptel-curl")
|
(declare-function gptel-curl-get-response "gptel-curl")
|
||||||
(declare-function gptel-menu "gptel-transient")
|
(declare-function gptel-menu "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))
|
||||||
|
@ -133,6 +155,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
|
||||||
|
|
||||||
|
@ -149,11 +174,19 @@
|
||||||
"Use `gptel-make-openai' instead."
|
"Use `gptel-make-openai' instead."
|
||||||
"0.5.0")
|
"0.5.0")
|
||||||
|
|
||||||
|
(defcustom gptel-context-prompt-preamble
|
||||||
|
"Here, you find more context for the following user prompts. Key aspects are:
|
||||||
|
- User inputs are encapsulated within Emacs Org mode src blocks.
|
||||||
|
- Naming Convention: Each src block is identified using a structured name format '{{name-of-original-buffer}}:{{beginning-line-number}}-{{ending-line-number}}'. This scheme offers insight into the origin and scope of the code or text snippet.
|
||||||
|
- Mode Indication: The mode of the original file is included within each src block. This detail informs you about the programming language or markup format of the snippet, aiding in accurate interpretation and response."
|
||||||
|
"Instruct the llm about how to treat the additional context from *gptel-context*."
|
||||||
|
:group 'gptel
|
||||||
|
:type 'string)
|
||||||
|
|
||||||
(defcustom gptel-proxy ""
|
(defcustom gptel-proxy ""
|
||||||
"Path to a proxy to use for gptel interactions.
|
"Path to a proxy to use for gptel interactions.
|
||||||
Passed to curl via --proxy arg, for example \"proxy.yourorg.com:80\"
|
Passed to curl via --proxy arg, for example \"proxy.yourorg.com:80\"
|
||||||
Leave it empty if you don't use a proxy."
|
Leave it empty if you don't use a proxy."
|
||||||
:group 'gptel
|
|
||||||
:type 'string)
|
:type 'string)
|
||||||
|
|
||||||
(defcustom gptel-api-key #'gptel-api-key-from-auth-source
|
(defcustom gptel-api-key #'gptel-api-key-from-auth-source
|
||||||
|
@ -163,7 +196,6 @@ OpenAI by default.
|
||||||
|
|
||||||
Can also be a function of no arguments that returns an API
|
Can also be a function of no arguments that returns an API
|
||||||
key (more secure) for the active backend."
|
key (more secure) for the active backend."
|
||||||
:group 'gptel
|
|
||||||
:type '(choice
|
:type '(choice
|
||||||
(string :tag "API key")
|
(string :tag "API key")
|
||||||
(function :tag "Function that returns the API key")))
|
(function :tag "Function that returns the API key")))
|
||||||
|
@ -179,13 +211,11 @@ When set to nil, Emacs waits for the full response and inserts it
|
||||||
all at once. This wait is asynchronous.
|
all at once. This wait is asynchronous.
|
||||||
|
|
||||||
\='tis a bit silly."
|
\='tis a bit silly."
|
||||||
:group 'gptel
|
|
||||||
:type 'boolean)
|
:type 'boolean)
|
||||||
(make-obsolete-variable 'gptel-playback 'gptel-stream "0.3.0")
|
(make-obsolete-variable 'gptel-playback 'gptel-stream "0.3.0")
|
||||||
|
|
||||||
(defcustom gptel-use-curl (and (executable-find "curl") t)
|
(defcustom gptel-use-curl (and (executable-find "curl") t)
|
||||||
"Whether gptel should prefer Curl when available."
|
"Whether gptel should prefer Curl when available."
|
||||||
:group 'gptel
|
|
||||||
:type 'boolean)
|
:type 'boolean)
|
||||||
|
|
||||||
(defcustom gptel-curl-file-size-threshold 130000
|
(defcustom gptel-curl-file-size-threshold 130000
|
||||||
|
@ -204,11 +234,10 @@ and the typical size of the data being sent in GPTel queries.
|
||||||
A larger value may improve performance by avoiding the overhead of creating
|
A larger value may improve performance by avoiding the overhead of creating
|
||||||
temporary files for small data payloads, while a smaller value may be needed
|
temporary files for small data payloads, while a smaller value may be needed
|
||||||
if the command-line argument size is limited by the operating system."
|
if the command-line argument size is limited by the operating system."
|
||||||
:group 'gptel
|
:type 'natnum)
|
||||||
:type 'integer)
|
|
||||||
|
|
||||||
(defcustom gptel-response-filter-functions
|
(defcustom gptel-response-filter-functions
|
||||||
'(gptel--convert-org)
|
(list #'gptel--convert-org)
|
||||||
"Abnormal hook for transforming the response from an LLM.
|
"Abnormal hook for transforming the response from an LLM.
|
||||||
|
|
||||||
This is used to format the response in some way, such as filling
|
This is used to format the response in some way, such as filling
|
||||||
|
@ -222,7 +251,6 @@ should return the transformed string.
|
||||||
NOTE: This is only used for non-streaming responses. To
|
NOTE: This is only used for non-streaming responses. To
|
||||||
transform streaming responses, use `gptel-post-stream-hook' and
|
transform streaming responses, use `gptel-post-stream-hook' and
|
||||||
`gptel-post-response-functions'."
|
`gptel-post-response-functions'."
|
||||||
:group 'gptel
|
|
||||||
:type 'hook)
|
:type 'hook)
|
||||||
|
|
||||||
(defcustom gptel-pre-response-hook nil
|
(defcustom gptel-pre-response-hook nil
|
||||||
|
@ -232,7 +260,6 @@ This hook is called in the buffer where the LLM response will be
|
||||||
inserted.
|
inserted.
|
||||||
|
|
||||||
Note: this hook only runs if the request succeeds."
|
Note: this hook only runs if the request succeeds."
|
||||||
:group 'gptel
|
|
||||||
:type 'hook)
|
:type 'hook)
|
||||||
|
|
||||||
(define-obsolete-variable-alias
|
(define-obsolete-variable-alias
|
||||||
|
@ -244,15 +271,14 @@ start and end buffer positions of the response.")
|
||||||
(defcustom gptel-post-response-functions nil
|
(defcustom gptel-post-response-functions nil
|
||||||
"Abnormal hook run after inserting the LLM response into the current buffer.
|
"Abnormal hook run after inserting the LLM response into the current buffer.
|
||||||
|
|
||||||
This hook is called in the buffer from which the prompt was sent
|
This hook is called in the buffer to which the LLM response is
|
||||||
to the LLM, and after the full response has been inserted. Each
|
sent, and after the full response has been inserted. Each
|
||||||
function is called with two arguments: the response beginning and
|
function is called with two arguments: the response beginning and
|
||||||
end positions.
|
end positions.
|
||||||
|
|
||||||
Note: this hook runs even if the request fails. In this case the
|
Note: this hook runs even if the request fails. In this case the
|
||||||
response beginning and end positions are both the cursor position
|
response beginning and end positions are both the cursor position
|
||||||
at the time of the request."
|
at the time of the request."
|
||||||
:group 'gptel
|
|
||||||
:type 'hook)
|
:type 'hook)
|
||||||
|
|
||||||
;; (defcustom gptel-pre-stream-insert-hook nil
|
;; (defcustom gptel-pre-stream-insert-hook nil
|
||||||
|
@ -268,7 +294,6 @@ at the time of the request."
|
||||||
|
|
||||||
This hook is called in the buffer from which the prompt was sent
|
This hook is called in the buffer from which the prompt was sent
|
||||||
to the LLM, and after a text insertion."
|
to the LLM, and after a text insertion."
|
||||||
:group 'gptel
|
|
||||||
:type 'hook)
|
:type 'hook)
|
||||||
|
|
||||||
(defcustom gptel-default-mode (if (fboundp 'markdown-mode)
|
(defcustom gptel-default-mode (if (fboundp 'markdown-mode)
|
||||||
|
@ -278,8 +303,7 @@ to the LLM, and after a text insertion."
|
||||||
|
|
||||||
If `markdown-mode' is available, it is used. Otherwise gptel
|
If `markdown-mode' is available, it is used. Otherwise gptel
|
||||||
defaults to `text-mode'."
|
defaults to `text-mode'."
|
||||||
:group 'gptel
|
:type 'function)
|
||||||
:type 'symbol)
|
|
||||||
|
|
||||||
;; TODO: Handle `prog-mode' using the `comment-start' variable
|
;; TODO: Handle `prog-mode' using the `comment-start' variable
|
||||||
(defcustom gptel-prompt-prefix-alist
|
(defcustom gptel-prompt-prefix-alist
|
||||||
|
@ -293,7 +317,6 @@ responses, and is removed from the query before it is sent.
|
||||||
|
|
||||||
This is an alist mapping major modes to the prefix strings. This
|
This is an alist mapping major modes to the prefix strings. This
|
||||||
is only inserted in dedicated gptel buffers."
|
is only inserted in dedicated gptel buffers."
|
||||||
:group 'gptel
|
|
||||||
:type '(alist :key-type symbol :value-type string))
|
:type '(alist :key-type symbol :value-type string))
|
||||||
|
|
||||||
(defcustom gptel-response-prefix-alist
|
(defcustom gptel-response-prefix-alist
|
||||||
|
@ -307,7 +330,6 @@ responses.
|
||||||
|
|
||||||
This is an alist mapping major modes to the reply prefix strings. This
|
This is an alist mapping major modes to the reply prefix strings. This
|
||||||
is only inserted in dedicated gptel buffers before the AI's response."
|
is only inserted in dedicated gptel buffers before the AI's response."
|
||||||
:group 'gptel
|
|
||||||
:type '(alist :key-type symbol :value-type string))
|
:type '(alist :key-type symbol :value-type string))
|
||||||
|
|
||||||
(defcustom gptel-use-header-line t
|
(defcustom gptel-use-header-line t
|
||||||
|
@ -315,8 +337,7 @@ is only inserted in dedicated gptel buffers before the AI's response."
|
||||||
|
|
||||||
When set to nil, use the mode line for (minimal) status
|
When set to nil, use the mode line for (minimal) status
|
||||||
information and the echo area for messages."
|
information and the echo area for messages."
|
||||||
:type 'boolean
|
:type 'boolean)
|
||||||
:group 'gptel)
|
|
||||||
|
|
||||||
(defcustom gptel-display-buffer-action '(pop-to-buffer)
|
(defcustom gptel-display-buffer-action '(pop-to-buffer)
|
||||||
"The action used to display gptel chat buffers.
|
"The action used to display gptel chat buffers.
|
||||||
|
@ -330,17 +351,12 @@ where FUNCTION is a function or a list of functions. Each such
|
||||||
function should accept two arguments: a buffer to display and an
|
function should accept two arguments: a buffer to display and an
|
||||||
alist of the same form as ALIST. See info node `(elisp)Choosing
|
alist of the same form as ALIST. See info node `(elisp)Choosing
|
||||||
Window' for details."
|
Window' for details."
|
||||||
:group 'gptel
|
:type display-buffer--action-custom-type)
|
||||||
:type '(choice
|
|
||||||
(const :tag "Use display-buffer defaults" nil)
|
|
||||||
(const :tag "Display in selected window" (pop-to-buffer-same-window))
|
|
||||||
(cons :tag "Specify display-buffer action"
|
|
||||||
(choice function (repeat :tag "Functions" function))
|
|
||||||
alist)))
|
|
||||||
|
|
||||||
(defcustom gptel-crowdsourced-prompts-file
|
(defcustom gptel-crowdsourced-prompts-file
|
||||||
(let ((cache-dir (or (getenv "XDG_CACHE_HOME")
|
(let ((cache-dir (or (eval-when-compile
|
||||||
(getenv "XDG_DATA_HOME")
|
(require 'xdg)
|
||||||
|
(xdg-cache-home))
|
||||||
user-emacs-directory)))
|
user-emacs-directory)))
|
||||||
(expand-file-name "gptel-crowdsourced-prompts.csv" cache-dir))
|
(expand-file-name "gptel-crowdsourced-prompts.csv" cache-dir))
|
||||||
"File used to store crowdsourced system prompts.
|
"File used to store crowdsourced system prompts.
|
||||||
|
@ -348,7 +364,6 @@ Window' for details."
|
||||||
These are prompts cached from an online source (see
|
These are prompts cached from an online source (see
|
||||||
`gptel--crowdsourced-prompts-url'), and can be set from the
|
`gptel--crowdsourced-prompts-url'), and can be set from the
|
||||||
transient menu interface provided by `gptel-menu'."
|
transient menu interface provided by `gptel-menu'."
|
||||||
:group 'gptel
|
|
||||||
:type 'file)
|
:type 'file)
|
||||||
|
|
||||||
;; Model and interaction parameters
|
;; Model and interaction parameters
|
||||||
|
@ -365,11 +380,11 @@ request to the LLM.
|
||||||
Each entry in this alist maps a symbol naming the directive to
|
Each entry in this alist maps a symbol naming the directive to
|
||||||
the string that is sent. To set the directive for a chat session
|
the string that is sent. To set the directive for a chat session
|
||||||
interactively call `gptel-send' with a prefix argument."
|
interactively call `gptel-send' with a prefix argument."
|
||||||
:group 'gptel
|
|
||||||
:safe #'always
|
:safe #'always
|
||||||
:type '(alist :key-type symbol :value-type string))
|
:type '(alist :key-type symbol :value-type string))
|
||||||
|
|
||||||
(defvar-local gptel--system-message (alist-get 'default gptel-directives))
|
(defvar gptel--system-message (alist-get 'default gptel-directives)
|
||||||
|
"The system message used by gptel.")
|
||||||
(put 'gptel--system-message 'safe-local-variable #'always)
|
(put 'gptel--system-message 'safe-local-variable #'always)
|
||||||
|
|
||||||
(defcustom gptel-max-tokens nil
|
(defcustom gptel-max-tokens nil
|
||||||
|
@ -381,10 +396,8 @@ responses.
|
||||||
|
|
||||||
To set the target token count for a chat session interactively
|
To set the target token count for a chat session interactively
|
||||||
call `gptel-send' with a prefix argument."
|
call `gptel-send' with a prefix argument."
|
||||||
:local t
|
|
||||||
:safe #'always
|
:safe #'always
|
||||||
:group 'gptel
|
:type '(choice (natnum :tag "Specify Token count")
|
||||||
:type '(choice (integer :tag "Specify Token count")
|
|
||||||
(const :tag "Default" nil)))
|
(const :tag "Default" nil)))
|
||||||
|
|
||||||
(defcustom gptel-model "gpt-3.5-turbo"
|
(defcustom gptel-model "gpt-3.5-turbo"
|
||||||
|
@ -396,19 +409,21 @@ by the LLM provider's API.
|
||||||
The current options for ChatGPT are
|
The current options for ChatGPT are
|
||||||
- \"gpt-3.5-turbo\"
|
- \"gpt-3.5-turbo\"
|
||||||
- \"gpt-3.5-turbo-16k\"
|
- \"gpt-3.5-turbo-16k\"
|
||||||
- \"gpt-4\" (experimental)
|
- \"gpt-4\"
|
||||||
- \"gpt-4-1106-preview\" (experimental)
|
- \"gpt-4-turbo\"
|
||||||
|
- \"gpt-4-turbo-preview\"
|
||||||
|
- \"gpt-4-32k\"
|
||||||
|
- \"gpt-4-1106-preview\"
|
||||||
|
|
||||||
To set the model for a chat session interactively call
|
To set the model for a chat session interactively call
|
||||||
`gptel-send' with a prefix argument."
|
`gptel-send' with a prefix argument."
|
||||||
:local t
|
|
||||||
:safe #'always
|
:safe #'always
|
||||||
:group 'gptel
|
|
||||||
:type '(choice
|
:type '(choice
|
||||||
(string :tag "Specify model name")
|
(string :tag "Specify model name")
|
||||||
(const :tag "GPT 3.5 turbo" "gpt-3.5-turbo")
|
(const :tag "GPT 3.5 turbo" "gpt-3.5-turbo")
|
||||||
(const :tag "GPT 3.5 turbo 16k" "gpt-3.5-turbo-16k")
|
(const :tag "GPT 3.5 turbo 16k" "gpt-3.5-turbo-16k")
|
||||||
(const :tag "GPT 4" "gpt-4")
|
(const :tag "GPT 4" "gpt-4")
|
||||||
|
(const :tag "GPT 4 turbo" "gpt-4-turbo")
|
||||||
(const :tag "GPT 4 turbo (preview)" "gpt-4-turbo-preview")
|
(const :tag "GPT 4 turbo (preview)" "gpt-4-turbo-preview")
|
||||||
(const :tag "GPT 4 32k" "gpt-4-32k")
|
(const :tag "GPT 4 32k" "gpt-4-32k")
|
||||||
(const :tag "GPT 4 1106 (preview)" "gpt-4-1106-preview")))
|
(const :tag "GPT 4 1106 (preview)" "gpt-4-1106-preview")))
|
||||||
|
@ -421,9 +436,7 @@ of the response, with 2.0 being the most random.
|
||||||
|
|
||||||
To set the temperature for a chat session interactively call
|
To set the temperature for a chat session interactively call
|
||||||
`gptel-send' with a prefix argument."
|
`gptel-send' with a prefix argument."
|
||||||
:local t
|
|
||||||
:safe #'always
|
:safe #'always
|
||||||
:group 'gptel
|
|
||||||
:type 'number)
|
:type 'number)
|
||||||
|
|
||||||
(defvar gptel--known-backends nil
|
(defvar gptel--known-backends nil
|
||||||
|
@ -440,7 +453,7 @@ with differing settings.")
|
||||||
"ChatGPT"
|
"ChatGPT"
|
||||||
:key 'gptel-api-key
|
:key 'gptel-api-key
|
||||||
:stream t
|
:stream t
|
||||||
:models '("gpt-3.5-turbo" "gpt-3.5-turbo-16k" "gpt-4"
|
:models '("gpt-3.5-turbo" "gpt-3.5-turbo-16k" "gpt-4" "gpt-4-turbo"
|
||||||
"gpt-4-turbo-preview" "gpt-4-32k" "gpt-4-1106-preview"
|
"gpt-4-turbo-preview" "gpt-4-32k" "gpt-4-1106-preview"
|
||||||
"gpt-4-0125-preview")))
|
"gpt-4-0125-preview")))
|
||||||
|
|
||||||
|
@ -461,18 +474,21 @@ one of the available backend creation functions:
|
||||||
- `gptel-make-gemini'
|
- `gptel-make-gemini'
|
||||||
See their documentation for more information and the package
|
See their documentation for more information and the package
|
||||||
README for examples."
|
README for examples."
|
||||||
:local t
|
|
||||||
:safe #'always
|
:safe #'always
|
||||||
:group 'gptel
|
|
||||||
:type `(choice
|
:type `(choice
|
||||||
(const :tag "ChatGPT" ,gptel--openai)
|
(const :tag "ChatGPT" ,gptel--openai)
|
||||||
(restricted-sexp :match-alternatives (gptel-backend-p 'nil)
|
(restricted-sexp :match-alternatives (gptel-backend-p 'nil)
|
||||||
:tag "Other backend")))
|
:tag "Other backend")))
|
||||||
|
|
||||||
|
(defvar gptel-expert-commands nil
|
||||||
|
"Whether experimental gptel options should be enabled.
|
||||||
|
|
||||||
|
This opens up advanced options in `gptel-menu'.")
|
||||||
|
|
||||||
(defvar-local gptel--bounds nil)
|
(defvar-local gptel--bounds nil)
|
||||||
(put 'gptel--bounds 'safe-local-variable #'always)
|
(put 'gptel--bounds 'safe-local-variable #'always)
|
||||||
|
|
||||||
(defvar-local gptel--num-messages-to-send nil)
|
(defvar gptel--num-messages-to-send nil)
|
||||||
(put 'gptel--num-messages-to-send 'safe-local-variable #'always)
|
(put 'gptel--num-messages-to-send 'safe-local-variable #'always)
|
||||||
|
|
||||||
(defcustom gptel-log-level nil
|
(defcustom gptel-log-level nil
|
||||||
|
@ -487,7 +503,6 @@ debug: Log request/response bodies, headers and all other
|
||||||
|
|
||||||
When non-nil, information is logged to `gptel--log-buffer-name',
|
When non-nil, information is logged to `gptel--log-buffer-name',
|
||||||
which see."
|
which see."
|
||||||
:group 'gptel
|
|
||||||
:type '(choice
|
:type '(choice
|
||||||
(const :tag "No logging" nil)
|
(const :tag "No logging" nil)
|
||||||
(const :tag "Limited" info)
|
(const :tag "Limited" info)
|
||||||
|
@ -519,7 +534,7 @@ and \"apikey\" as USER."
|
||||||
;; FIXME Should we utf-8 encode the api-key here?
|
;; FIXME Should we utf-8 encode the api-key here?
|
||||||
(defun gptel--get-api-key (&optional key)
|
(defun gptel--get-api-key (&optional key)
|
||||||
"Get api key from KEY, or from `gptel-api-key'."
|
"Get api key from KEY, or from `gptel-api-key'."
|
||||||
(when-let* ((key-sym (or key (gptel-backend-key gptel-backend))))
|
(when-let ((key-sym (or key (gptel-backend-key gptel-backend))))
|
||||||
(cl-typecase key-sym
|
(cl-typecase key-sym
|
||||||
(function (funcall key-sym))
|
(function (funcall key-sym))
|
||||||
(string key-sym)
|
(string key-sym)
|
||||||
|
@ -531,13 +546,16 @@ and \"apikey\" as USER."
|
||||||
|
|
||||||
(defsubst gptel--numberize (val)
|
(defsubst gptel--numberize (val)
|
||||||
"Ensure VAL is a number."
|
"Ensure VAL is a number."
|
||||||
(if (stringp val) (string-to-number val) val))
|
(cond
|
||||||
|
((numberp val) val)
|
||||||
|
((stringp val) (string-to-number val))
|
||||||
|
((error "%S cannot be converted to a number" val))))
|
||||||
|
|
||||||
(defun gptel-auto-scroll ()
|
(defun gptel-auto-scroll ()
|
||||||
"Scroll window if LLM response continues below viewport.
|
"Scroll window if LLM response continues below viewport.
|
||||||
|
|
||||||
Note: This will move the cursor."
|
Note: This will move the cursor."
|
||||||
(when-let* ((win (get-buffer-window (current-buffer) 'visible))
|
(when-let ((win (get-buffer-window (current-buffer) 'visible))
|
||||||
((not (pos-visible-in-window-p (point) win)))
|
((not (pos-visible-in-window-p (point) win)))
|
||||||
(scroll-error-top-bottom t))
|
(scroll-error-top-bottom t))
|
||||||
(condition-case nil
|
(condition-case nil
|
||||||
|
@ -545,22 +563,39 @@ Note: This will move the cursor."
|
||||||
(scroll-up-command))
|
(scroll-up-command))
|
||||||
(error nil))))
|
(error nil))))
|
||||||
|
|
||||||
(defun gptel-end-of-response (_ _ &optional arg)
|
(defun gptel-beginning-of-response (&optional _ _ arg)
|
||||||
|
"Move point to the beginning of the LLM response ARG times."
|
||||||
|
(interactive "p")
|
||||||
|
;; FIXME: Only works for arg == 1
|
||||||
|
(gptel-end-of-response nil nil (- (or arg 1))))
|
||||||
|
|
||||||
|
(defun gptel-end-of-response (&optional _ _ arg)
|
||||||
"Move point to the end of the LLM response ARG times."
|
"Move point to the end of the LLM response ARG times."
|
||||||
(interactive (list nil nil current-prefix-arg))
|
(interactive (list nil nil
|
||||||
(dotimes (_ (if arg (abs arg) 1))
|
(prefix-numeric-value current-prefix-arg)))
|
||||||
(text-property-search-forward 'gptel 'response t)
|
(unless arg (setq arg 1))
|
||||||
|
(let ((search (if (> arg 0)
|
||||||
|
#'text-property-search-forward
|
||||||
|
#'text-property-search-backward)))
|
||||||
|
(dotimes (_ (abs arg))
|
||||||
|
(funcall search 'gptel 'response t)
|
||||||
|
(if (> arg 0)
|
||||||
(when (looking-at (concat "\n\\{1,2\\}"
|
(when (looking-at (concat "\n\\{1,2\\}"
|
||||||
(regexp-quote
|
(regexp-quote
|
||||||
(gptel-prompt-prefix-string))
|
(gptel-prompt-prefix-string))
|
||||||
"?"))
|
"?"))
|
||||||
(goto-char (match-end 0)))))
|
(goto-char (match-end 0)))
|
||||||
|
(when (looking-back (concat (regexp-quote
|
||||||
|
(gptel-response-prefix-string))
|
||||||
|
"?")
|
||||||
|
(point-min))
|
||||||
|
(goto-char (match-beginning 0)))))))
|
||||||
|
|
||||||
(defmacro gptel--at-word-end (&rest body)
|
(defmacro gptel--at-word-end (&rest body)
|
||||||
"Execute BODY at end of the current word or punctuation."
|
"Execute BODY at end of the current word or punctuation."
|
||||||
`(save-excursion
|
`(save-excursion
|
||||||
(skip-syntax-forward "w.")
|
(skip-syntax-forward "w.")
|
||||||
,@body))
|
,(macroexp-progn body)))
|
||||||
|
|
||||||
(defun gptel-prompt-prefix-string ()
|
(defun gptel-prompt-prefix-string ()
|
||||||
(or (alist-get major-mode gptel-prompt-prefix-alist) ""))
|
(or (alist-get major-mode gptel-prompt-prefix-alist) ""))
|
||||||
|
@ -632,48 +667,30 @@ Valid JSON unless NO-JSON is t."
|
||||||
|
|
||||||
;; Saving and restoring state
|
;; Saving and restoring state
|
||||||
|
|
||||||
(defun gptel--restore-backend (name)
|
|
||||||
"Activate gptel backend with NAME in current buffer.
|
|
||||||
|
|
||||||
If no backend with this name exists, inform the user. Intended
|
|
||||||
for when gptel restores chat metadata."
|
|
||||||
(when name
|
|
||||||
(if-let ((backend (alist-get name gptel--known-backends
|
|
||||||
nil nil #'equal)))
|
|
||||||
(setq-local gptel-backend backend)
|
|
||||||
(message
|
|
||||||
(substitute-command-keys
|
|
||||||
"Could not activate gptel backend \"%s\"! Switch backends with \\[universal-argument] \\[gptel-send] before using gptel.")
|
|
||||||
name))))
|
|
||||||
|
|
||||||
(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)
|
||||||
(pcase major-mode
|
(pcase major-mode
|
||||||
('org-mode
|
('org-mode
|
||||||
(save-restriction
|
(require 'gptel-org)
|
||||||
(widen)
|
(gptel-org--restore-state))
|
||||||
(condition-case-unless-debug nil
|
|
||||||
(progn
|
|
||||||
(when-let ((bounds (org-entry-get (point-min) "GPTEL_BOUNDS")))
|
|
||||||
(mapc (pcase-lambda (`(,beg . ,end))
|
|
||||||
(put-text-property beg end 'gptel 'response))
|
|
||||||
(read bounds))
|
|
||||||
(message "gptel chat restored."))
|
|
||||||
(when-let ((model (org-entry-get (point-min) "GPTEL_MODEL")))
|
|
||||||
(setq-local gptel-model model))
|
|
||||||
(gptel--restore-backend (org-entry-get (point-min) "GPTEL_BACKEND"))
|
|
||||||
(when-let ((system (org-entry-get (point-min) "GPTEL_SYSTEM")))
|
|
||||||
(setq-local gptel--system-message (string-replace "\\n" "\n" system)))
|
|
||||||
(when-let ((temp (org-entry-get (point-min) "GPTEL_TEMPERATURE")))
|
|
||||||
(setq-local gptel-temperature (gptel--numberize temp))))
|
|
||||||
(error (message "Could not restore gptel state, sorry!")))))
|
|
||||||
(_ (when gptel--bounds
|
(_ (when gptel--bounds
|
||||||
(mapc (pcase-lambda (`(,beg . ,end))
|
(mapc (pcase-lambda (`(,beg . ,end))
|
||||||
(put-text-property beg end 'gptel 'response))
|
(put-text-property beg end 'gptel 'response))
|
||||||
gptel--bounds)
|
gptel--bounds)
|
||||||
(message "gptel chat restored."))
|
(message "gptel chat restored."))
|
||||||
(gptel--restore-backend gptel--backend-name)))))
|
(when gptel--backend-name
|
||||||
|
(if-let ((backend (alist-get
|
||||||
|
gptel--backend-name gptel--known-backends
|
||||||
|
nil nil #'equal)))
|
||||||
|
(setq-local gptel-backend backend)
|
||||||
|
(message
|
||||||
|
(substitute-command-keys
|
||||||
|
(concat
|
||||||
|
"Could not activate gptel backend \"%s\"! "
|
||||||
|
"Switch backends with \\[universal-argument] \\[gptel-send]"
|
||||||
|
" before using gptel."))
|
||||||
|
gptel--backend-name)))))))
|
||||||
|
|
||||||
(defun gptel--save-state ()
|
(defun gptel--save-state ()
|
||||||
"Write the gptel state to the buffer.
|
"Write the gptel state to the buffer.
|
||||||
|
@ -683,34 +700,8 @@ restore a chat session, turn on `gptel-mode' after opening the
|
||||||
file."
|
file."
|
||||||
(pcase major-mode
|
(pcase major-mode
|
||||||
('org-mode
|
('org-mode
|
||||||
(org-with-wide-buffer
|
(require 'gptel-org)
|
||||||
(goto-char (point-min))
|
(gptel-org--save-state))
|
||||||
(when (org-at-heading-p)
|
|
||||||
(org-open-line 1))
|
|
||||||
(org-entry-put (point-min) "GPTEL_MODEL" gptel-model)
|
|
||||||
(org-entry-put (point-min) "GPTEL_BACKEND" (gptel-backend-name gptel-backend))
|
|
||||||
(unless (equal (default-value 'gptel-temperature) gptel-temperature)
|
|
||||||
(org-entry-put (point-min) "GPTEL_TEMPERATURE"
|
|
||||||
(number-to-string gptel-temperature)))
|
|
||||||
(unless (string= (default-value 'gptel--system-message)
|
|
||||||
gptel--system-message)
|
|
||||||
(org-entry-put (point-min) "GPTEL_SYSTEM"
|
|
||||||
(string-replace "\n" "\\n" gptel--system-message)))
|
|
||||||
(when gptel-max-tokens
|
|
||||||
(org-entry-put
|
|
||||||
(point-min) "GPTEL_MAX_TOKENS" gptel-max-tokens))
|
|
||||||
;; Save response boundaries
|
|
||||||
(letrec ((write-bounds
|
|
||||||
(lambda (attempts)
|
|
||||||
(let* ((bounds (gptel--get-buffer-bounds))
|
|
||||||
(offset (caar bounds))
|
|
||||||
(offset-marker (set-marker (make-marker) offset)))
|
|
||||||
(org-entry-put (point-min) "GPTEL_BOUNDS"
|
|
||||||
(prin1-to-string (gptel--get-buffer-bounds)))
|
|
||||||
(when (and (not (= (marker-position offset-marker) offset))
|
|
||||||
(> attempts 0))
|
|
||||||
(funcall write-bounds (1- attempts)))))))
|
|
||||||
(funcall write-bounds 6))))
|
|
||||||
(_ (let ((print-escape-newlines t))
|
(_ (let ((print-escape-newlines t))
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(save-restriction
|
(save-restriction
|
||||||
|
@ -754,21 +745,20 @@ file."
|
||||||
(format "%s" (gptel-backend-name gptel-backend))))
|
(format "%s" (gptel-backend-name gptel-backend))))
|
||||||
(propertize " Ready" 'face 'success)
|
(propertize " Ready" 'face 'success)
|
||||||
'(:eval
|
'(:eval
|
||||||
(let* ((l1 (length gptel-model))
|
(let ((system
|
||||||
(num-exchanges
|
(format "[Prompt: %s]"
|
||||||
(if gptel--num-messages-to-send
|
(or (car-safe (rassoc gptel--system-message gptel-directives))
|
||||||
(format "[Send: %s exchanges]" gptel--num-messages-to-send)
|
(truncate-string-to-width gptel--system-message 15 nil nil t)))))
|
||||||
"[Send: buffer]"))
|
|
||||||
(l2 (length num-exchanges)))
|
|
||||||
(concat
|
(concat
|
||||||
(propertize
|
(propertize
|
||||||
" " 'display `(space :align-to ,(max 1 (- (window-width) (+ 2 l1 l2)))))
|
" " 'display
|
||||||
|
`(space :align-to (- right ,(+ 2 (length gptel-model) (length system)))))
|
||||||
(propertize
|
(propertize
|
||||||
(buttonize num-exchanges
|
(buttonize system
|
||||||
(lambda (&rest _) (gptel-menu)))
|
(lambda (&rest _) (gptel-system-prompt)))
|
||||||
'mouse-face 'highlight
|
'mouse-face 'highlight
|
||||||
'help-echo
|
'help-echo
|
||||||
"Number of past exchanges to include with each request")
|
"System message for buffer")
|
||||||
" "
|
" "
|
||||||
(propertize
|
(propertize
|
||||||
(buttonize (concat "[" gptel-model "]")
|
(buttonize (concat "[" gptel-model "]")
|
||||||
|
@ -895,7 +885,8 @@ query data as usual, but do not send the request.
|
||||||
|
|
||||||
Model parameters can be let-bound around calls to this function."
|
Model parameters can be let-bound around calls to this function."
|
||||||
(declare (indent 1))
|
(declare (indent 1))
|
||||||
(let* ((gptel-stream stream)
|
(let* ((gptel--system-message system)
|
||||||
|
(gptel-stream stream)
|
||||||
(start-marker
|
(start-marker
|
||||||
(cond
|
(cond
|
||||||
((null position)
|
((null position)
|
||||||
|
@ -905,20 +896,28 @@ Model parameters can be let-bound around calls to this function."
|
||||||
((markerp position) position)
|
((markerp position) position)
|
||||||
((integerp position)
|
((integerp position)
|
||||||
(set-marker (make-marker) position buffer))))
|
(set-marker (make-marker) position buffer))))
|
||||||
(full-prompt
|
(full-prompt-draft
|
||||||
(cond
|
(cond
|
||||||
((null prompt)
|
((null prompt) (gptel--create-prompt start-marker))
|
||||||
(let ((gptel--system-message system))
|
|
||||||
(gptel--create-prompt start-marker)))
|
|
||||||
((stringp prompt)
|
((stringp prompt)
|
||||||
;; FIXME Dear reader, welcome to Jank City:
|
;; FIXME Dear reader, welcome to Jank City:
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(let ((gptel--system-message system)
|
(let ((gptel-model (buffer-local-value 'gptel-model buffer))
|
||||||
(gptel-model (buffer-local-value 'gptel-model buffer))
|
|
||||||
(gptel-backend (buffer-local-value 'gptel-backend buffer)))
|
(gptel-backend (buffer-local-value 'gptel-backend buffer)))
|
||||||
(insert prompt)
|
(insert prompt)
|
||||||
(gptel--create-prompt))))
|
(gptel--create-prompt))))
|
||||||
((consp prompt) prompt)))
|
((consp prompt) prompt)))
|
||||||
|
(context-prompt
|
||||||
|
(when (get-buffer "*gptel-context*")
|
||||||
|
(list :role "user"
|
||||||
|
:content (format "%s\n\n%s"
|
||||||
|
gptel-context-prompt-preamble
|
||||||
|
(with-current-buffer "*gptel-context*"
|
||||||
|
(save-excursion
|
||||||
|
(buffer-substring-no-properties (point-min) (point-max))))))))
|
||||||
|
(full-prompt (if context-prompt
|
||||||
|
(append (list context-prompt) full-prompt-draft)
|
||||||
|
full-prompt-draft))
|
||||||
(request-data (gptel--request-data gptel-backend full-prompt))
|
(request-data (gptel--request-data gptel-backend full-prompt))
|
||||||
(info (list :data request-data
|
(info (list :data request-data
|
||||||
:buffer buffer
|
:buffer buffer
|
||||||
|
@ -956,14 +955,12 @@ 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
|
|
||||||
(gptel-request nil :stream gptel-stream :dry-run t)))
|
|
||||||
(with-current-buffer (get-buffer-create "*gptel-query*")
|
(with-current-buffer (get-buffer-create "*gptel-query*")
|
||||||
(let ((standard-output (current-buffer))
|
(let ((standard-output (current-buffer))
|
||||||
(inhibit-read-only t))
|
(inhibit-read-only t))
|
||||||
|
@ -978,7 +975,7 @@ structure gptel uses."
|
||||||
(pp-buffer))
|
(pp-buffer))
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(view-mode 1)
|
(view-mode 1)
|
||||||
(display-buffer (current-buffer) gptel-display-buffer-action)))))
|
(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.
|
||||||
|
@ -1028,39 +1025,13 @@ See `gptel--url-get-response' for details."
|
||||||
(gptel--update-status
|
(gptel--update-status
|
||||||
(format " Response Error: %s" status-str) 'error)
|
(format " Response Error: %s" status-str) 'error)
|
||||||
(message "gptel response error: (%s) %s"
|
(message "gptel response error: (%s) %s"
|
||||||
status-str (plist-get info :error)))
|
status-str (plist-get info :error))))
|
||||||
(run-hook-with-args 'gptel-post-response-functions response-beg response-end))))
|
;; Run hook in visible window to set window-point, BUG #269
|
||||||
|
(if-let ((gptel-window (get-buffer-window gptel-buffer 'visible)))
|
||||||
(defun gptel-set-topic ()
|
(with-selected-window gptel-window
|
||||||
"Set a topic and limit this conversation to the current heading.
|
(run-hook-with-args 'gptel-post-response-functions response-beg response-end))
|
||||||
|
(with-current-buffer gptel-buffer
|
||||||
This limits the context sent to the LLM to the text between the
|
(run-hook-with-args 'gptel-post-response-functions response-beg response-end)))))
|
||||||
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.
|
||||||
|
@ -1075,19 +1046,19 @@ If PROMPT-END (a marker) is provided, end the prompt contents
|
||||||
there."
|
there."
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(save-restriction
|
(save-restriction
|
||||||
|
(let ((max-entries (and gptel--num-messages-to-send
|
||||||
|
(* 2 gptel--num-messages-to-send))))
|
||||||
(cond
|
(cond
|
||||||
((use-region-p)
|
((use-region-p)
|
||||||
;; Narrow to region
|
;; Narrow to region
|
||||||
(narrow-to-region (region-beginning) (region-end))
|
(narrow-to-region (region-beginning) (region-end))
|
||||||
(goto-char (point-max)))
|
(goto-char (point-max))
|
||||||
((when-let ((topic-start (gptel--get-topic-start)))
|
(gptel--parse-buffer gptel-backend max-entries))
|
||||||
;; Narrow to topic
|
((derived-mode-p 'org-mode)
|
||||||
(narrow-to-region topic-start (or prompt-end (point-max)))
|
(require 'gptel-org)
|
||||||
(goto-char (point-max))))
|
(gptel-org--create-prompt (or prompt-end (point-max))))
|
||||||
(t (goto-char (or prompt-end (point-max)))))
|
(t (goto-char (or prompt-end (point-max)))
|
||||||
(let ((max-entries (and gptel--num-messages-to-send
|
(gptel--parse-buffer gptel-backend max-entries)))))))
|
||||||
(* 2 gptel--num-messages-to-send))))
|
|
||||||
(gptel--parse-buffer gptel-backend max-entries)))))
|
|
||||||
|
|
||||||
(cl-defgeneric gptel--parse-buffer (backend max-entries)
|
(cl-defgeneric gptel--parse-buffer (backend max-entries)
|
||||||
"Parse current buffer backwards from point and return a list of prompts.
|
"Parse current buffer backwards from point and return a list of prompts.
|
||||||
|
@ -1155,6 +1126,7 @@ the response is inserted into the current buffer after point."
|
||||||
(encode-coding-string
|
(encode-coding-string
|
||||||
(gptel--json-encode (plist-get info :data))
|
(gptel--json-encode (plist-get info :data))
|
||||||
'utf-8)))
|
'utf-8)))
|
||||||
|
;; why do these checks not occur inside of `gptel--log'?
|
||||||
(when gptel-log-level ;logging
|
(when gptel-log-level ;logging
|
||||||
(when (eq gptel-log-level 'debug)
|
(when (eq gptel-log-level 'debug)
|
||||||
(gptel--log (gptel--json-encode
|
(gptel--log (gptel--json-encode
|
||||||
|
@ -1210,7 +1182,7 @@ See `gptel-curl--get-response' for its contents.")
|
||||||
((or (memq url-http-response-status '(200 100))
|
((or (memq url-http-response-status '(200 100))
|
||||||
(string-match-p "\\(?:1\\|2\\)00 OK" http-msg))
|
(string-match-p "\\(?:1\\|2\\)00 OK" http-msg))
|
||||||
(list (string-trim (gptel--parse-response backend response
|
(list (string-trim (gptel--parse-response backend response
|
||||||
'(:buffer response-buffer)))
|
`(:buffer ,response-buffer)))
|
||||||
http-msg))
|
http-msg))
|
||||||
((plist-get response :error)
|
((plist-get response :error)
|
||||||
(let* ((error-data (plist-get response :error))
|
(let* ((error-data (plist-get response :error))
|
||||||
|
@ -1218,11 +1190,13 @@ See `gptel-curl--get-response' for its contents.")
|
||||||
(error-type (plist-get error-data :type))
|
(error-type (plist-get error-data :type))
|
||||||
(backend-name (gptel-backend-name backend)))
|
(backend-name (gptel-backend-name backend)))
|
||||||
(if (stringp error-data)
|
(if (stringp error-data)
|
||||||
(progn (message "%s error: (%s) %s" backend-name http-msg error-data)
|
(progn
|
||||||
|
(message "%s error: (%s) %s" backend-name http-msg error-data)
|
||||||
(setq error-msg (string-trim error-data)))
|
(setq error-msg (string-trim error-data)))
|
||||||
(when (stringp error-msg)
|
(when (stringp error-msg)
|
||||||
(message "%s error: (%s) %s" backend-name http-msg (string-trim error-msg)))
|
(message "%s error: (%s) %s" backend-name http-msg (string-trim error-msg)))
|
||||||
(when error-type (setq http-msg (concat "(" http-msg ") " (string-trim error-type)))))
|
(when error-type
|
||||||
|
(setq http-msg (concat "(" http-msg ") " (string-trim error-type)))))
|
||||||
(list nil (concat "(" http-msg ") " (or error-msg "")))))
|
(list nil (concat "(" http-msg ") " (or error-msg "")))))
|
||||||
((eq response 'json-read-error)
|
((eq response 'json-read-error)
|
||||||
(list nil (concat "(" http-msg ") Malformed JSON in response.") "json-read-error"))
|
(list nil (concat "(" http-msg ") Malformed JSON in response.") "json-read-error"))
|
||||||
|
@ -1237,7 +1211,7 @@ See `gptel-curl--get-response' for its contents.")
|
||||||
"Check if MODEL is available in BACKEND, adjust accordingly.
|
"Check if MODEL is available in BACKEND, adjust accordingly.
|
||||||
|
|
||||||
If SHOOSH is true, don't issue a warning."
|
If SHOOSH is true, don't issue a warning."
|
||||||
(let* ((available (gptel-backend-models backend)))
|
(let ((available (gptel-backend-models backend)))
|
||||||
(unless (member model available)
|
(unless (member model available)
|
||||||
(let ((fallback (car available)))
|
(let ((fallback (car available)))
|
||||||
(unless shoosh
|
(unless shoosh
|
||||||
|
@ -1300,152 +1274,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
|
||||||
|
|
||||||
|
@ -1524,7 +1352,7 @@ context for the ediff session."
|
||||||
"Mark gptel response at point, if any."
|
"Mark gptel response at point, if any."
|
||||||
(interactive)
|
(interactive)
|
||||||
(unless (gptel--in-response-p) (user-error "No gptel response at point"))
|
(unless (gptel--in-response-p) (user-error "No gptel response at point"))
|
||||||
(pcase-let* ((`(,beg . ,end) (gptel--get-bounds)))
|
(pcase-let ((`(,beg . ,end) (gptel--get-bounds)))
|
||||||
(goto-char beg) (push-mark) (goto-char end) (activate-mark)))
|
(goto-char beg) (push-mark) (goto-char end) (activate-mark)))
|
||||||
|
|
||||||
(defun gptel--previous-variant (&optional arg)
|
(defun gptel--previous-variant (&optional arg)
|
||||||
|
@ -1553,6 +1381,28 @@ context for the ediff session."
|
||||||
(goto-char (+ beg offset))
|
(goto-char (+ beg offset))
|
||||||
(pulse-momentary-highlight-region beg (+ beg (length alt-response)))))
|
(pulse-momentary-highlight-region beg (+ beg (length alt-response)))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun gptel-add-context ()
|
||||||
|
"Add selected region (or whole buffer) to *gptel-context*."
|
||||||
|
(interactive)
|
||||||
|
(let* ((context (if (use-region-p)
|
||||||
|
(buffer-substring-no-properties (region-beginning) (region-end))
|
||||||
|
(buffer-substring-no-properties (point-min) (point-max))))
|
||||||
|
(src-name (buffer-name))
|
||||||
|
(start (line-number-at-pos (region-beginning)))
|
||||||
|
(end (line-number-at-pos (region-end)))
|
||||||
|
(region-major-mode (symbol-name major-mode))
|
||||||
|
(loc (format "%s:%d-%d" src-name start end)))
|
||||||
|
(with-current-buffer (get-buffer-create "*gptel-context*")
|
||||||
|
(org-mode)
|
||||||
|
(goto-char (point-max))
|
||||||
|
(unless (bolp) (insert "\n"))
|
||||||
|
(insert (format "#+NAME: %s\n" loc))
|
||||||
|
(insert (format "#+BEGIN_SRC %s\n" region-major-mode))
|
||||||
|
(insert (format "%s\n" context))
|
||||||
|
(insert "#+END_SRC\n\n"))
|
||||||
|
(message "Context has been added to *gptel-context*.")))
|
||||||
|
|
||||||
(defun gptel--next-variant (&optional arg)
|
(defun gptel--next-variant (&optional arg)
|
||||||
"Switch to next gptel-response at this point, if it exists."
|
"Switch to next gptel-response at this point, if it exists."
|
||||||
(interactive "p")
|
(interactive "p")
|
||||||
|
@ -1560,3 +1410,7 @@ context for the ediff session."
|
||||||
|
|
||||||
(provide 'gptel)
|
(provide 'gptel)
|
||||||
;;; gptel.el ends here
|
;;; gptel.el ends here
|
||||||
|
|
||||||
|
;; Local Variables:
|
||||||
|
;; bug-reference-url-format: "https://github.com/karthink/gptel/issues/%s"
|
||||||
|
;; End:
|
||||||
|
|
|
@ -19,17 +19,40 @@
|
||||||
;; (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)
|
||||||
(:success strs)))
|
(:success strs)))
|
||||||
(setq strs (delq nil (nreverse strs))))))
|
(setq strs (delq nil (nreverse strs))))))
|
||||||
|
|
||||||
;;; Basic tests for markdown to org conversion
|
(defun gptel-org--test-compare-org (md-list)
|
||||||
(let ((string-sequences
|
"Given a list of markdown-formatted strings MD-LIST, covert it to
|
||||||
'(("" "```" "cpp" "
|
Org markup incrementally and display both in buffers."
|
||||||
|
(let ((func (gptel--stream-convert-markdown->org)))
|
||||||
|
(with-current-buffer (get-buffer-create "gptel-org-test-md-input.md")
|
||||||
|
(erase-buffer)
|
||||||
|
(apply #'insert md-list)
|
||||||
|
(display-buffer (current-buffer)))
|
||||||
|
(with-current-buffer (get-buffer-create "gptel-org-test-org-output.org")
|
||||||
|
(erase-buffer)
|
||||||
|
(apply #'insert (mapcar func md-list))
|
||||||
|
(display-buffer (current-buffer)))))
|
||||||
|
|
||||||
|
(defmacro gptel-org--test-stream-conversion (md-list org-str)
|
||||||
|
`(ert-deftest
|
||||||
|
,(intern (concat "gptel-org--test-stream-conversion-"
|
||||||
|
(substring (sha1 (prin1-to-string md-list)) 0 10)))
|
||||||
|
()
|
||||||
|
(let ((func (gptel--stream-convert-markdown->org)))
|
||||||
|
(prog1
|
||||||
|
(should
|
||||||
|
(string= (apply #'concat (mapcar func ,md-list))
|
||||||
|
,org-str))
|
||||||
|
(setq gptel-post-response-functions nil)))))
|
||||||
|
|
||||||
|
(gptel-org--test-stream-conversion
|
||||||
|
'("" "```" "cpp" "
|
||||||
" "#include" " <" "cstdio" ">
|
" "#include" " <" "cstdio" ">
|
||||||
|
|
||||||
" "int" " main" "()" " {
|
" "int" " main" "()" " {
|
||||||
|
@ -37,7 +60,18 @@
|
||||||
" " " " return" " " "0" ";
|
" " " " return" " " "0" ";
|
||||||
" "}
|
" "}
|
||||||
" "```")
|
" "```")
|
||||||
("" "Here" " is" " a" " simple" " C" "++" " program" " that" " uses" " the" " `" "printf" "`" " function" " to" " print" " the" " string" " containing" " " "5" " back" "ticks" ":
|
"#+begin_src cpp
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf(\"`````\n\");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#+end_src")
|
||||||
|
|
||||||
|
(gptel-org--test-stream-conversion
|
||||||
|
;; markdown
|
||||||
|
'("" "Here" " is" " a" " simple" " C" "++" " program" " that" " uses" " the" " `" "printf" "`" " function" " to" " print" " the" " string" " containing" " " "5" " back" "ticks" ":
|
||||||
|
|
||||||
" "```" "cpp" "
|
" "```" "cpp" "
|
||||||
" "#include" " <" "iostream" ">
|
" "#include" " <" "iostream" ">
|
||||||
|
@ -51,16 +85,7 @@
|
||||||
" "``" "`
|
" "``" "`
|
||||||
|
|
||||||
" "In" " this" " code" " snippet" "," " `" "printf" "(\"" "``" "``" "`" "\n" "\");" "`" " is" " used" " to" " print" " the" " string" " \"" "``" "```" "\"" " followed" " by" " a" " newline" " character" ".")
|
" "In" " this" " code" " snippet" "," " `" "printf" "(\"" "``" "``" "`" "\n" "\");" "`" " is" " used" " to" " print" " the" " string" " \"" "``" "```" "\"" " followed" " by" " a" " newline" " character" ".")
|
||||||
("" "In" " the" " definition" " of" " the" " `" "struct" " json" "_parser" "`," " the" " line" " `" "L" "isp" "_Object" " *" "object" "_workspace" ";" "`" " declares" " a" " pointer" " named" " `" "object" "_workspace" "`" " of" " type" " `" "L" "isp" "_Object" "`.\n\n" "The" " aster" "isk" " (*)" " in" " `" "L" "isp" "_Object" " *" "object" "_workspace" ";" "`" " is" " the" " pointer" " ind" "irection" " operator" " in" " C" "." " It" " indicates" " that" " `" "object" "_workspace" "`" " is" " a" " pointer" " to" " an" " object" " of" " type" " `" "L" "isp" "_Object" "`." " This" " means" " that" " `" "object" "_workspace" "`" " will" " store" " the" " memory" " address" " (" "location" ")" " of" " a" " `" "L" "isp" "_Object" "`" " variable" " rather" " than" " storing" " the" " actual" " `" "L" "isp" "_Object" "`" " value" ".\n\n" "Therefore" "," " `" "object" "_workspace" "`" " will" " be" " used" " to" " point" " to" " or" " reference" " locations" " in" " memory" " where" " `" "L" "isp" "_Object" "`" " instances" " are" " stored" "." " This" " allows" " the" " `" "struct" " json" "_parser" "`" " to" " store" " and" " work" " with" " `" "L" "isp" "_Object" "`" " instances" " indirectly" " through" " pointers" ".")))
|
;; Org
|
||||||
(converted-sequences
|
|
||||||
'("#+begin_src cpp
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
printf(\"`````\n\");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#+end_src"
|
|
||||||
"Here is a simple C++ program that uses the =printf= function to print the string containing 5 backticks:
|
"Here is a simple C++ program that uses the =printf= function to print the string containing 5 backticks:
|
||||||
|
|
||||||
#+begin_src cpp
|
#+begin_src cpp
|
||||||
|
@ -74,27 +99,35 @@ int main() {
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
In this code snippet, =printf(\"`````\n\");= is used to print the string \"=\" followed by a newline character."
|
In this code snippet, =printf(\"`````\n\");= is used to print the string \"=\" followed by a newline character.")
|
||||||
|
|
||||||
|
|
||||||
|
(gptel-org--test-stream-conversion
|
||||||
|
;; markdown
|
||||||
|
'("" "In" " the" " definition" " of" " the" " `" "struct" " json" "_parser" "`," " the" " line" " `" "L" "isp" "_Object" " *" "object" "_workspace" ";" "`" " declares" " a" " pointer" " named" " `" "object" "_workspace" "`" " of" " type" " `" "L" "isp" "_Object" "`.\n\n" "The" " aster" "isk" " (*)" " in" " `" "L" "isp" "_Object" " *" "object" "_workspace" ";" "`" " is" " the" " pointer" " ind" "irection" " operator" " in" " C" "." " It" " indicates" " that" " `" "object" "_workspace" "`" " is" " a" " pointer" " to" " an" " object" " of" " type" " `" "L" "isp" "_Object" "`." " This" " means" " that" " `" "object" "_workspace" "`" " will" " store" " the" " memory" " address" " (" "location" ")" " of" " a" " `" "L" "isp" "_Object" "`" " variable" " rather" " than" " storing" " the" " actual" " `" "L" "isp" "_Object" "`" " value" ".\n\n" "Therefore" "," " `" "object" "_workspace" "`" " will" " be" " used" " to" " point" " to" " or" " reference" " locations" " in" " memory" " where" " `" "L" "isp" "_Object" "`" " instances" " are" " stored" "." " This" " allows" " the" " `" "struct" " json" "_parser" "`" " to" " store" " and" " work" " with" " `" "L" "isp" "_Object" "`" " instances" " indirectly" " through" " pointers" ".")
|
||||||
|
;; org
|
||||||
"In the definition of the =struct json_parser=, the line =Lisp_Object *object_workspace;= declares a pointer named =object_workspace= of type =Lisp_Object=.
|
"In the definition of the =struct json_parser=, the line =Lisp_Object *object_workspace;= declares a pointer named =object_workspace= of type =Lisp_Object=.
|
||||||
|
|
||||||
The asterisk (*) in =Lisp_Object *object_workspace;= is the pointer indirection operator in C. It indicates that =object_workspace= is a pointer to an object of type =Lisp_Object=. This means that =object_workspace= will store the memory address (location) of a =Lisp_Object= variable rather than storing the actual =Lisp_Object= value.
|
The asterisk (*) in =Lisp_Object *object_workspace;= is the pointer indirection operator in C. It indicates that =object_workspace= is a pointer to an object of type =Lisp_Object=. This means that =object_workspace= will store the memory address (location) of a =Lisp_Object= variable rather than storing the actual =Lisp_Object= value.
|
||||||
|
|
||||||
Therefore, =object_workspace= will be used to point to or reference locations in memory where =Lisp_Object= instances are stored. This allows the =struct json_parser= to store and work with =Lisp_Object= instances indirectly through pointers.")))
|
Therefore, =object_workspace= will be used to point to or reference locations in memory where =Lisp_Object= instances are stored. This allows the =struct json_parser= to store and work with =Lisp_Object= instances indirectly through pointers.")
|
||||||
(ert-deftest test--gptel--convert-markdown->org ()
|
|
||||||
(cl-loop
|
|
||||||
for input in string-sequences
|
|
||||||
for output in converted-sequences
|
|
||||||
do
|
|
||||||
(should (string= (gptel--convert-markdown->org (apply #'concat input))
|
|
||||||
output))))
|
|
||||||
|
|
||||||
(ert-deftest test--gptel--stream-convert-markdown->org ()
|
|
||||||
(cl-loop
|
|
||||||
for input in string-sequences
|
|
||||||
for output in converted-sequences
|
|
||||||
for func = (gptel--stream-convert-markdown->org)
|
|
||||||
do
|
|
||||||
(should
|
|
||||||
(string= (apply #'concat (mapcar func input))
|
|
||||||
output)))))
|
|
||||||
|
|
||||||
|
(gptel-org--test-stream-conversion
|
||||||
|
;; markdown
|
||||||
|
'("#" "# Advantages of"
|
||||||
|
" Org-Mode: A Detailed Overview\n\nOrg-mode is a powerful and versatile Emacs extension that offers a wide range of features"
|
||||||
|
" for organizing information, planning projects, and writing documents. Here's a detailed overview of its advantages:\n\n**Note Taking and Idea Management:**\n\n"
|
||||||
|
"* **Unified platform:** Org-mode provides a single platform for capturing notes, ideas, and tasks, eliminating the need for multiple tools and ensuring everything is in one place.\n* **Flexibility:** Notes can be structured hierarchically"
|
||||||
|
" using headlines and subheadings, allowing for easy organization and navigation.\n* **Linking and tags:** Link notes together for easy reference and connect them with tags for categorized browsing.\n* **Metadata:** Capture additional information like author, deadline, and"
|
||||||
|
" priority for better organization and search.\n\n**Project Management and Planning:**\n\n* **Task management:** Create to-do lists with deadlines, priority levels, and tags for efficient task management.\n* **Gantt charts and time estimates:** Visualize project timelines and estimate time commitments for better planning and organization.\n* **Calendar integration:** Link tasks to your calendar for better integration and time management.\n* **"
|
||||||
|
"Progress tracking:** Track the progress of tasks and projects with checkboxes and progress bars.\n\n**Writing and Document Creation:**\n\n* **Rich text formatting:** Org-mode supports a variety of text formatting options, including bold, italics, headings, and lists, for creating professional-looking documents.\n* **Exporting to various formats:** Easily export your notes and documents to various formats like PDF, HTML, LaTeX, and"
|
||||||
|
" plain text.\n* **Beamer presentations:** Create Beamer presentations directly within Org-mode for academic or professional presentations.\n* **Source code blocks:** Include and highlight code blocks for easy reference and documentation.\n\n**Additional Features:**\n\n* **Org-Capture:** Quickly capture ideas, notes, and tasks from anywhere using keyboard shortcuts.\n* **Org-Agenda:** View and manage your tasks and appointments in a calendar-like format.\n* **Emacs Lisp scripting:** Add custom"
|
||||||
|
" functionality and automate tasks with Emacs Lisp scripting.\n* *"
|
||||||
|
"*Active development and community support:** Benefit from the active development and supportive community of Org-mode users.\n\n**Overall Advantages:**\n\n* **Increased productivity:** Organize your thoughts, tasks, and projects efficiently, leading to increased productivity.\n*"
|
||||||
|
" **Improved focus:** Eliminate distractions and stay focused on the task at hand.\n* **Enhanced creativity:** Capture ideas quickly and easily, fostering creativity and innovation.\n* **Knowledge management:** Build a comprehensive knowledge base"
|
||||||
|
" of notes, ideas, and projects for future reference.\n*"
|
||||||
|
" **Personalized workflow:** Tailor Org-mode to your specific needs and preferences for a truly personalized workflow.\n\n**Limitations:**\n\n* **Learning curve:** While powerful, Org-mode has a steeper learning curve compared to simpler note-taking apps.\n* **Emacs dependency:** Org-mode requires Emacs, which may not be suitable for all users.\n* **Limited mobile support:** Mobile support for Org-mode is available but not as feature-rich as"
|
||||||
|
" the desktop version.\n\n**Who should use Org-mode?**\n\n"
|
||||||
|
"* Students\n* Researchers\n* Writers\n* Project managers\n* Anyone who wants to improve their personal organization and productivity\n\n**Conclusion:**\n\nOrg-mode is a powerful and versatile tool that can significantly increase your productivity and organization. Its flexibility, rich features, and active community make it an excellent choice for managing notes, projects, and documents. If you're looking for a way to streamline your workflow and unleash your creativity, Org-mode is definitely worth exploring.")
|
||||||
|
;; org
|
||||||
|
"** Advantages of Org-Mode: A Detailed Overview\n\nOrg-mode is a powerful and versatile Emacs extension that offers a wide range of features for organizing information, planning projects, and writing documents. Here's a detailed overview of its advantages:\n\n*Note Taking and Idea Management:*\n\n- *Unified platform:* Org-mode provides a single platform for capturing notes, ideas, and tasks, eliminating the need for multiple tools and ensuring everything is in one place.\n- *Flexibility:* Notes can be structured hierarchically using headlines and subheadings, allowing for easy organization and navigation.\n- *Linking and tags:* Link notes together for easy reference and connect them with tags for categorized browsing.\n- *Metadata:* Capture additional information like author, deadline, and priority for better organization and search.\n\n*Project Management and Planning:*\n\n- *Task management:* Create to-do lists with deadlines, priority levels, and tags for efficient task management.\n- *Gantt charts and time estimates:* Visualize project timelines and estimate time commitments for better planning and organization.\n- *Calendar integration:* Link tasks to your calendar for better integration and time management.\n- *Progress tracking:* Track the progress of tasks and projects with checkboxes and progress bars.\n\n*Writing and Document Creation:*\n\n- *Rich text formatting:* Org-mode supports a variety of text formatting options, including bold, italics, headings, and lists, for creating professional-looking documents.\n- *Exporting to various formats:* Easily export your notes and documents to various formats like PDF, HTML, LaTeX, and plain text.\n- *Beamer presentations:* Create Beamer presentations directly within Org-mode for academic or professional presentations.\n- *Source code blocks:* Include and highlight code blocks for easy reference and documentation.\n\n*Additional Features:*\n\n- *Org-Capture:* Quickly capture ideas, notes, and tasks from anywhere using keyboard shortcuts.\n- *Org-Agenda:* View and manage your tasks and appointments in a calendar-like format.\n- *Emacs Lisp scripting:* Add custom functionality and automate tasks with Emacs Lisp scripting.\n- *Active development and community support:* Benefit from the active development and supportive community of Org-mode users.\n\n*Overall Advantages:*\n\n- *Increased productivity:* Organize your thoughts, tasks, and projects efficiently, leading to increased productivity.\n- *Improved focus:* Eliminate distractions and stay focused on the task at hand.\n- *Enhanced creativity:* Capture ideas quickly and easily, fostering creativity and innovation.\n- *Knowledge management:* Build a comprehensive knowledge base of notes, ideas, and projects for future reference.\n- *Personalized workflow:* Tailor Org-mode to your specific needs and preferences for a truly personalized workflow.\n\n*Limitations:*\n\n- *Learning curve:* While powerful, Org-mode has a steeper learning curve compared to simpler note-taking apps.\n- *Emacs dependency:* Org-mode requires Emacs, which may not be suitable for all users.\n- *Limited mobile support:* Mobile support for Org-mode is available but not as feature-rich as the desktop version.\n\n*Who should use Org-mode?*\n\n- Students\n- Researchers\n- Writers\n- Project managers\n- Anyone who wants to improve their personal organization and productivity\n\n*Conclusion:*\n\nOrg-mode is a powerful and versatile tool that can significantly increase your productivity and organization. Its flexibility, rich features, and active community make it an excellent choice for managing notes, projects, and documents. If you're looking for a way to streamline your workflow and unleash your creativity, Org-mode is definitely worth exploring.")
|
||||||
|
|
Loading…
Add table
Reference in a new issue