Compare commits
No commits in common. "bf02ee68a4b7955ba9178e818b61697db06572fb" and "e994a443d35a34082ad5d6d45dd92163b10635cd" have entirely different histories.
bf02ee68a4
...
e994a443d3
7 changed files with 148 additions and 278 deletions
49
README.org
49
README.org
|
@ -560,16 +560,16 @@ The above code makes the backend available to select. If you want it to be the
|
|||
|
||||
(This is also a [[https://www.youtube.com/watch?v=bsRnh_brggM][video demo]] showing various uses of gptel.)
|
||||
|
||||
|--------------------+----------------------------------------------------------------------------------------|
|
||||
| *Command* | Description |
|
||||
|--------------------+----------------------------------------------------------------------------------------|
|
||||
| =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. |
|
||||
| =C-u= =gptel-send= | Transient menu for preferences, input/output redirection etc. |
|
||||
| =gptel-menu= | /(Same)/ |
|
||||
|--------------------+----------------------------------------------------------------------------------------|
|
||||
| =gptel-set-topic= | /(Org-mode only)/ Limit conversation context to an Org heading |
|
||||
|--------------------+----------------------------------------------------------------------------------------|
|
||||
|-------------------+-------------------------------------------------------------------------|
|
||||
| *Command* | Description |
|
||||
|-------------------+-------------------------------------------------------------------------|
|
||||
| =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. |
|
||||
| =C-u= =gptel-send= | Transient menu for preferences, input/output redirection etc. |
|
||||
| =gptel-menu= | /(Same)/ |
|
||||
|-------------------+-------------------------------------------------------------------------|
|
||||
| =gptel-set-topic= | /(Org-mode only)/ Limit conversation context to an Org heading |
|
||||
|-------------------+-------------------------------------------------------------------------|
|
||||
|
||||
*** In any buffer:
|
||||
|
||||
|
@ -612,35 +612,6 @@ 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.
|
||||
|
||||
*** 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
|
||||
#+html: <details><summary>
|
||||
**** I want the window to scroll automatically as the response is inserted
|
||||
|
|
|
@ -114,8 +114,7 @@
|
|||
(name &key curl-args header key (stream nil)
|
||||
(host "generativelanguage.googleapis.com")
|
||||
(protocol "https")
|
||||
(models '("gemini-pro"
|
||||
"gemini-1.5-pro-latest"))
|
||||
(models '("gemini-pro"))
|
||||
(endpoint "/v1beta/models"))
|
||||
|
||||
"Register a Gemini backend for gptel with NAME.
|
||||
|
|
|
@ -34,94 +34,79 @@
|
|||
(:copier nil)
|
||||
(:include gptel-backend)))
|
||||
|
||||
(defvar-local gptel--ollama-token-count 0
|
||||
"Token count for ollama conversations.
|
||||
(defvar-local gptel--ollama-context nil
|
||||
"Context for ollama conversations.
|
||||
|
||||
This variable holds the total token count for conversations with
|
||||
Ollama models.
|
||||
|
||||
Intended for internal use only.")
|
||||
This variable holds the context array for conversations with
|
||||
Ollama models.")
|
||||
|
||||
(cl-defmethod gptel-curl--parse-stream ((_backend gptel-ollama) info)
|
||||
"Parse response stream for the Ollama API."
|
||||
(when (and (bobp) (re-search-forward "^{" nil t))
|
||||
";TODO: "
|
||||
(when (bobp)
|
||||
(re-search-forward "^{")
|
||||
(forward-line 0))
|
||||
(let* ((content-strs) (content) (pt (point)))
|
||||
(condition-case nil
|
||||
(while (setq content (gptel--json-read))
|
||||
(setq pt (point))
|
||||
(let ((done (map-elt content :done))
|
||||
(response (map-nested-elt content '(:message :content))))
|
||||
(response (map-elt content :response)))
|
||||
(push response content-strs)
|
||||
(unless (eq done :json-false)
|
||||
(with-current-buffer (plist-get info :buffer)
|
||||
(cl-incf gptel--ollama-token-count
|
||||
(+ (or (map-elt content :prompt_eval_count) 0)
|
||||
(or (map-elt content :eval_count) 0))))
|
||||
(setq gptel--ollama-context (map-elt content :context)))
|
||||
(goto-char (point-max)))))
|
||||
(error (goto-char pt)))
|
||||
(apply #'concat (nreverse content-strs))))
|
||||
|
||||
(cl-defmethod gptel--parse-response ((_backend gptel-ollama) response info)
|
||||
"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))))
|
||||
(when-let ((context (map-elt response :context)))
|
||||
(with-current-buffer (plist-get info :buffer)
|
||||
(cl-incf gptel--ollama-token-count context)))
|
||||
(map-nested-elt response '(:message :content)))
|
||||
(setq gptel--ollama-context context)))
|
||||
(map-elt response :response))
|
||||
|
||||
(cl-defmethod gptel--request-data ((_backend gptel-ollama) prompts)
|
||||
"JSON encode PROMPTS for sending to ChatGPT."
|
||||
"JSON encode PROMPTS for Ollama."
|
||||
(let ((prompts-plist
|
||||
`(:model ,gptel-model
|
||||
:messages [,@prompts]
|
||||
,@prompts
|
||||
:stream ,(or (and gptel-stream gptel-use-curl
|
||||
(gptel-backend-stream gptel-backend))
|
||||
:json-false)))
|
||||
options-plist)
|
||||
(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))
|
||||
(gptel-backend-stream gptel-backend))
|
||||
:json-false))))
|
||||
(when gptel--ollama-context
|
||||
(plist-put prompts-plist :context gptel--ollama-context))
|
||||
prompts-plist))
|
||||
|
||||
(cl-defmethod gptel--parse-buffer ((_backend gptel-ollama) &optional max-entries)
|
||||
(let ((prompts) (prop))
|
||||
(while (and
|
||||
(or (not max-entries) (>= max-entries 0))
|
||||
(setq prop (text-property-search-backward
|
||||
'gptel 'response
|
||||
(when (get-char-property (max (point-min) (1- (point)))
|
||||
'gptel)
|
||||
t))))
|
||||
(push (list :role (if (prop-match-value prop) "assistant" "user")
|
||||
:content
|
||||
(string-trim
|
||||
(buffer-substring-no-properties (prop-match-beginning prop)
|
||||
(prop-match-end prop))
|
||||
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
|
||||
(regexp-quote (gptel-prompt-prefix-string)))
|
||||
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
|
||||
(regexp-quote (gptel-response-prefix-string)))))
|
||||
prompts)
|
||||
(and max-entries (cl-decf max-entries)))
|
||||
(cons (list :role "system"
|
||||
:content gptel--system-message)
|
||||
prompts)))
|
||||
(cl-defmethod gptel--parse-buffer ((_backend gptel-ollama) &optional _max-entries)
|
||||
(let ((prompts)
|
||||
(prop (text-property-search-backward
|
||||
'gptel 'response
|
||||
(when (get-char-property (max (point-min) (1- (point)))
|
||||
'gptel)
|
||||
t))))
|
||||
(if (and (prop-match-p prop)
|
||||
(prop-match-value prop))
|
||||
(user-error "No user prompt found!")
|
||||
(setq prompts (list
|
||||
:system gptel--system-message
|
||||
:prompt
|
||||
(if (prop-match-p prop)
|
||||
(string-trim
|
||||
(buffer-substring-no-properties (prop-match-beginning prop)
|
||||
(prop-match-end prop))
|
||||
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
|
||||
(regexp-quote (gptel-prompt-prefix-string)))
|
||||
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
|
||||
(regexp-quote (gptel-response-prefix-string))))
|
||||
"")))
|
||||
prompts)))
|
||||
|
||||
;;;###autoload
|
||||
(cl-defun gptel-make-ollama
|
||||
(name &key curl-args header key models stream
|
||||
(host "localhost:11434")
|
||||
(protocol "http")
|
||||
(endpoint "/api/chat"))
|
||||
(endpoint "/api/generate"))
|
||||
"Register an Ollama backend for gptel with NAME.
|
||||
|
||||
Keyword arguments:
|
||||
|
|
50
gptel-org.el
50
gptel-org.el
|
@ -28,6 +28,7 @@
|
|||
(require 'outline)
|
||||
|
||||
(declare-function org-element-begin "org-element")
|
||||
(declare-function org-element-lineage-map "org-element-ast")
|
||||
|
||||
;; Functions used for saving/restoring gptel state in Org buffers
|
||||
(defvar org-entry-property-inherited-from)
|
||||
|
@ -41,33 +42,6 @@
|
|||
(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
|
||||
|
@ -165,7 +139,7 @@ value of `gptel-org-branching-context', which see."
|
|||
;; Create prompt from direct ancestors of point
|
||||
(save-excursion
|
||||
(let* ((org-buf (current-buffer))
|
||||
(start-bounds (gptel-org--element-lineage-map
|
||||
(start-bounds (org-element-lineage-map
|
||||
(org-element-at-point) #'org-element-begin
|
||||
'(headline org-data) 'with-self))
|
||||
(end-bounds
|
||||
|
@ -407,7 +381,7 @@ text stream."
|
|||
(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)
|
||||
(while (re-search-forward "`\\|\\*\\{1,2\\}\\|_" nil t)
|
||||
(pcase (match-string 0)
|
||||
("`"
|
||||
;; Count number of consecutive backticks
|
||||
|
@ -439,22 +413,12 @@ text stream."
|
|||
;; 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) ?*)))))
|
||||
;; Handle other chars: emphasis, bold and bullet items
|
||||
((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\\}"
|
||||
((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)))
|
||||
|
|
|
@ -694,8 +694,6 @@ Or in an extended conversation:
|
|||
: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.
|
||||
|
|
136
gptel.el
136
gptel.el
|
@ -152,19 +152,11 @@
|
|||
"Use `gptel-make-openai' instead."
|
||||
"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 ""
|
||||
"Path to a proxy to use for gptel interactions.
|
||||
Passed to curl via --proxy arg, for example \"proxy.yourorg.com:80\"
|
||||
Leave it empty if you don't use a proxy."
|
||||
:group 'gptel
|
||||
:type 'string)
|
||||
|
||||
(defcustom gptel-api-key #'gptel-api-key-from-auth-source
|
||||
|
@ -174,6 +166,7 @@ OpenAI by default.
|
|||
|
||||
Can also be a function of no arguments that returns an API
|
||||
key (more secure) for the active backend."
|
||||
:group 'gptel
|
||||
:type '(choice
|
||||
(string :tag "API key")
|
||||
(function :tag "Function that returns the API key")))
|
||||
|
@ -189,11 +182,13 @@ When set to nil, Emacs waits for the full response and inserts it
|
|||
all at once. This wait is asynchronous.
|
||||
|
||||
\='tis a bit silly."
|
||||
:group 'gptel
|
||||
:type 'boolean)
|
||||
(make-obsolete-variable 'gptel-playback 'gptel-stream "0.3.0")
|
||||
|
||||
(defcustom gptel-use-curl (and (executable-find "curl") t)
|
||||
"Whether gptel should prefer Curl when available."
|
||||
:group 'gptel
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom gptel-curl-file-size-threshold 130000
|
||||
|
@ -212,10 +207,11 @@ and the typical size of the data being sent in GPTel queries.
|
|||
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
|
||||
if the command-line argument size is limited by the operating system."
|
||||
:type 'natnum)
|
||||
:group 'gptel
|
||||
:type 'integer)
|
||||
|
||||
(defcustom gptel-response-filter-functions
|
||||
(list #'gptel--convert-org)
|
||||
'(gptel--convert-org)
|
||||
"Abnormal hook for transforming the response from an LLM.
|
||||
|
||||
This is used to format the response in some way, such as filling
|
||||
|
@ -229,6 +225,7 @@ should return the transformed string.
|
|||
NOTE: This is only used for non-streaming responses. To
|
||||
transform streaming responses, use `gptel-post-stream-hook' and
|
||||
`gptel-post-response-functions'."
|
||||
:group 'gptel
|
||||
:type 'hook)
|
||||
|
||||
(defcustom gptel-pre-response-hook nil
|
||||
|
@ -238,6 +235,7 @@ This hook is called in the buffer where the LLM response will be
|
|||
inserted.
|
||||
|
||||
Note: this hook only runs if the request succeeds."
|
||||
:group 'gptel
|
||||
:type 'hook)
|
||||
|
||||
(define-obsolete-variable-alias
|
||||
|
@ -257,6 +255,7 @@ end positions.
|
|||
Note: this hook runs even if the request fails. In this case the
|
||||
response beginning and end positions are both the cursor position
|
||||
at the time of the request."
|
||||
:group 'gptel
|
||||
:type 'hook)
|
||||
|
||||
;; (defcustom gptel-pre-stream-insert-hook nil
|
||||
|
@ -272,16 +271,18 @@ at the time of the request."
|
|||
|
||||
This hook is called in the buffer from which the prompt was sent
|
||||
to the LLM, and after a text insertion."
|
||||
:group 'gptel
|
||||
:type 'hook)
|
||||
|
||||
(defcustom gptel-default-mode (if (fboundp 'markdown-mode)
|
||||
'markdown-mode
|
||||
'text-mode)
|
||||
'markdown-mode
|
||||
'text-mode)
|
||||
"The default major mode for dedicated chat buffers.
|
||||
|
||||
If `markdown-mode' is available, it is used. Otherwise gptel
|
||||
defaults to `text-mode'."
|
||||
:type 'function)
|
||||
:group 'gptel
|
||||
:type 'symbol)
|
||||
|
||||
;; TODO: Handle `prog-mode' using the `comment-start' variable
|
||||
(defcustom gptel-prompt-prefix-alist
|
||||
|
@ -295,6 +296,7 @@ responses, and is removed from the query before it is sent.
|
|||
|
||||
This is an alist mapping major modes to the prefix strings. This
|
||||
is only inserted in dedicated gptel buffers."
|
||||
:group 'gptel
|
||||
:type '(alist :key-type symbol :value-type string))
|
||||
|
||||
(defcustom gptel-response-prefix-alist
|
||||
|
@ -308,6 +310,7 @@ responses.
|
|||
|
||||
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."
|
||||
:group 'gptel
|
||||
:type '(alist :key-type symbol :value-type string))
|
||||
|
||||
(defcustom gptel-use-header-line t
|
||||
|
@ -315,7 +318,8 @@ is only inserted in dedicated gptel buffers before the AI's response."
|
|||
|
||||
When set to nil, use the mode line for (minimal) status
|
||||
information and the echo area for messages."
|
||||
:type 'boolean)
|
||||
:type 'boolean
|
||||
:group 'gptel)
|
||||
|
||||
(defcustom gptel-display-buffer-action '(pop-to-buffer)
|
||||
"The action used to display gptel chat buffers.
|
||||
|
@ -329,12 +333,17 @@ where FUNCTION is a function or a list of functions. Each such
|
|||
function should accept two arguments: a buffer to display and an
|
||||
alist of the same form as ALIST. See info node `(elisp)Choosing
|
||||
Window' for details."
|
||||
:type display-buffer--action-custom-type)
|
||||
:group 'gptel
|
||||
: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
|
||||
(let ((cache-dir (or (eval-when-compile
|
||||
(require 'xdg)
|
||||
(xdg-cache-home))
|
||||
(let ((cache-dir (or (getenv "XDG_CACHE_HOME")
|
||||
(getenv "XDG_DATA_HOME")
|
||||
user-emacs-directory)))
|
||||
(expand-file-name "gptel-crowdsourced-prompts.csv" cache-dir))
|
||||
"File used to store crowdsourced system prompts.
|
||||
|
@ -342,6 +351,7 @@ Window' for details."
|
|||
These are prompts cached from an online source (see
|
||||
`gptel--crowdsourced-prompts-url'), and can be set from the
|
||||
transient menu interface provided by `gptel-menu'."
|
||||
:group 'gptel
|
||||
:type 'file)
|
||||
|
||||
;; Model and interaction parameters
|
||||
|
@ -358,6 +368,7 @@ request to the LLM.
|
|||
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
|
||||
interactively call `gptel-send' with a prefix argument."
|
||||
:group 'gptel
|
||||
:safe #'always
|
||||
:type '(alist :key-type symbol :value-type string))
|
||||
|
||||
|
@ -375,7 +386,8 @@ responses.
|
|||
To set the target token count for a chat session interactively
|
||||
call `gptel-send' with a prefix argument."
|
||||
:safe #'always
|
||||
:type '(choice (natnum :tag "Specify Token count")
|
||||
:group 'gptel
|
||||
:type '(choice (integer :tag "Specify Token count")
|
||||
(const :tag "Default" nil)))
|
||||
|
||||
(defcustom gptel-model "gpt-3.5-turbo"
|
||||
|
@ -387,21 +399,18 @@ by the LLM provider's API.
|
|||
The current options for ChatGPT are
|
||||
- \"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\" (experimental)
|
||||
- \"gpt-4-1106-preview\" (experimental)
|
||||
|
||||
To set the model for a chat session interactively call
|
||||
`gptel-send' with a prefix argument."
|
||||
:safe #'always
|
||||
:group 'gptel
|
||||
:type '(choice
|
||||
(string :tag "Specify model name")
|
||||
(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 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 32k" "gpt-4-32k")
|
||||
(const :tag "GPT 4 1106 (preview)" "gpt-4-1106-preview")))
|
||||
|
@ -415,6 +424,7 @@ of the response, with 2.0 being the most random.
|
|||
To set the temperature for a chat session interactively call
|
||||
`gptel-send' with a prefix argument."
|
||||
:safe #'always
|
||||
:group 'gptel
|
||||
:type 'number)
|
||||
|
||||
(defvar gptel--known-backends nil
|
||||
|
@ -431,7 +441,7 @@ with differing settings.")
|
|||
"ChatGPT"
|
||||
:key 'gptel-api-key
|
||||
:stream t
|
||||
:models '("gpt-3.5-turbo" "gpt-3.5-turbo-16k" "gpt-4" "gpt-4-turbo"
|
||||
:models '("gpt-3.5-turbo" "gpt-3.5-turbo-16k" "gpt-4"
|
||||
"gpt-4-turbo-preview" "gpt-4-32k" "gpt-4-1106-preview"
|
||||
"gpt-4-0125-preview")))
|
||||
|
||||
|
@ -453,6 +463,7 @@ one of the available backend creation functions:
|
|||
See their documentation for more information and the package
|
||||
README for examples."
|
||||
:safe #'always
|
||||
:group 'gptel
|
||||
:type `(choice
|
||||
(const :tag "ChatGPT" ,gptel--openai)
|
||||
(restricted-sexp :match-alternatives (gptel-backend-p 'nil)
|
||||
|
@ -481,6 +492,7 @@ debug: Log request/response bodies, headers and all other
|
|||
|
||||
When non-nil, information is logged to `gptel--log-buffer-name',
|
||||
which see."
|
||||
:group 'gptel
|
||||
:type '(choice
|
||||
(const :tag "No logging" nil)
|
||||
(const :tag "Limited" info)
|
||||
|
@ -512,7 +524,7 @@ and \"apikey\" as USER."
|
|||
;; FIXME Should we utf-8 encode the api-key here?
|
||||
(defun gptel--get-api-key (&optional 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
|
||||
(function (funcall key-sym))
|
||||
(string key-sym)
|
||||
|
@ -524,18 +536,15 @@ and \"apikey\" as USER."
|
|||
|
||||
(defsubst gptel--numberize (val)
|
||||
"Ensure VAL is a number."
|
||||
(cond
|
||||
((numberp val) val)
|
||||
((stringp val) (string-to-number val))
|
||||
((error "%S cannot be converted to a number" val))))
|
||||
(if (stringp val) (string-to-number val) val))
|
||||
|
||||
(defun gptel-auto-scroll ()
|
||||
"Scroll window if LLM response continues below viewport.
|
||||
|
||||
Note: This will move the cursor."
|
||||
(when-let ((win (get-buffer-window (current-buffer) 'visible))
|
||||
((not (pos-visible-in-window-p (point) win)))
|
||||
(scroll-error-top-bottom t))
|
||||
(when-let* ((win (get-buffer-window (current-buffer) 'visible))
|
||||
((not (pos-visible-in-window-p (point) win)))
|
||||
(scroll-error-top-bottom t))
|
||||
(condition-case nil
|
||||
(with-selected-window win
|
||||
(scroll-up-command))
|
||||
|
@ -573,7 +582,7 @@ Note: This will move the cursor."
|
|||
"Execute BODY at end of the current word or punctuation."
|
||||
`(save-excursion
|
||||
(skip-syntax-forward "w.")
|
||||
,(macroexp-progn body)))
|
||||
,@body))
|
||||
|
||||
(defun gptel-prompt-prefix-string ()
|
||||
(or (alist-get major-mode gptel-prompt-prefix-alist) ""))
|
||||
|
@ -874,7 +883,7 @@ Model parameters can be let-bound around calls to this function."
|
|||
((markerp position) position)
|
||||
((integerp position)
|
||||
(set-marker (make-marker) position buffer))))
|
||||
(full-prompt-draft
|
||||
(full-prompt
|
||||
(cond
|
||||
((null prompt) (gptel--create-prompt start-marker))
|
||||
((stringp prompt)
|
||||
|
@ -885,17 +894,6 @@ Model parameters can be let-bound around calls to this function."
|
|||
(insert prompt)
|
||||
(gptel--create-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))
|
||||
(info (list :data request-data
|
||||
:buffer buffer
|
||||
|
@ -1159,7 +1157,7 @@ See `gptel-curl--get-response' for its contents.")
|
|||
((or (memq url-http-response-status '(200 100))
|
||||
(string-match-p "\\(?:1\\|2\\)00 OK" http-msg))
|
||||
(list (string-trim (gptel--parse-response backend response
|
||||
`(:buffer ,response-buffer)))
|
||||
'(:buffer response-buffer)))
|
||||
http-msg))
|
||||
((plist-get response :error)
|
||||
(let* ((error-data (plist-get response :error))
|
||||
|
@ -1167,13 +1165,11 @@ See `gptel-curl--get-response' for its contents.")
|
|||
(error-type (plist-get error-data :type))
|
||||
(backend-name (gptel-backend-name backend)))
|
||||
(if (stringp error-data)
|
||||
(progn
|
||||
(message "%s error: (%s) %s" backend-name http-msg error-data)
|
||||
(setq error-msg (string-trim error-data)))
|
||||
(progn (message "%s error: (%s) %s" backend-name http-msg error-data)
|
||||
(setq error-msg (string-trim error-data)))
|
||||
(when (stringp 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 "")))))
|
||||
((eq response 'json-read-error)
|
||||
(list nil (concat "(" http-msg ") Malformed JSON in response.") "json-read-error"))
|
||||
|
@ -1188,7 +1184,7 @@ See `gptel-curl--get-response' for its contents.")
|
|||
"Check if MODEL is available in BACKEND, adjust accordingly.
|
||||
|
||||
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)
|
||||
(let ((fallback (car available)))
|
||||
(unless shoosh
|
||||
|
@ -1329,7 +1325,7 @@ context for the ediff session."
|
|||
"Mark gptel response at point, if any."
|
||||
(interactive)
|
||||
(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)))
|
||||
|
||||
(defun gptel--previous-variant (&optional arg)
|
||||
|
@ -1358,28 +1354,6 @@ context for the ediff session."
|
|||
(goto-char (+ beg offset))
|
||||
(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)
|
||||
"Switch to next gptel-response at this point, if it exists."
|
||||
(interactive "p")
|
||||
|
@ -1387,7 +1361,3 @@ context for the ediff session."
|
|||
|
||||
(provide 'gptel)
|
||||
;;; gptel.el ends here
|
||||
|
||||
;; Local Variables:
|
||||
;; bug-reference-url-format: "https://github.com/karthink/gptel/issues/%s"
|
||||
;; End:
|
||||
|
|
|
@ -26,20 +26,9 @@
|
|||
(:success strs)))
|
||||
(setq strs (delq nil (nreverse strs))))))
|
||||
|
||||
(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" "
|
||||
;;; Basic tests for markdown to org conversion
|
||||
(let ((string-sequences
|
||||
'(("" "```" "cpp" "
|
||||
" "#include" " <" "cstdio" ">
|
||||
|
||||
" "int" " main" "()" " {
|
||||
|
@ -47,18 +36,7 @@
|
|||
" " " " return" " " "0" ";
|
||||
" "}
|
||||
" "```")
|
||||
"#+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" ":
|
||||
("" "Here" " is" " a" " simple" " C" "++" " program" " that" " uses" " the" " `" "printf" "`" " function" " to" " print" " the" " string" " containing" " " "5" " back" "ticks" ":
|
||||
|
||||
" "```" "cpp" "
|
||||
" "#include" " <" "iostream" ">
|
||||
|
@ -72,8 +50,17 @@ int main() {
|
|||
" "``" "`
|
||||
|
||||
" "In" " this" " code" " snippet" "," " `" "printf" "(\"" "``" "``" "`" "\n" "\");" "`" " is" " used" " to" " print" " the" " string" " \"" "``" "```" "\"" " followed" " by" " a" " newline" " character" ".")
|
||||
;; Org
|
||||
"Here is a simple C++ program that uses the =printf= function to print the string containing 5 backticks:
|
||||
("" "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" ".")))
|
||||
(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:
|
||||
|
||||
#+begin_src cpp
|
||||
#include <iostream>
|
||||
|
@ -86,31 +73,27 @@ int main() {
|
|||
}
|
||||
#+end_src
|
||||
|
||||
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 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 =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.
|
||||
|
||||
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