-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathaide.el
329 lines (277 loc) · 12.1 KB
/
aide.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
;;; aide.el --- An Emacs front end for GPT APIs like OpenAI -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Junji Zhi
;; Author: Junji Zhi
;; Keywords: gpt-3 openai
;; 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:
;; Simple wrapper to call GPT APIs
;;
;; For details, please see http://github.com/junjizhi/aide.el
;;; Code:
(require 'request) ;; M-x package-install RET request RET
(defgroup aide nil
"aide.el custom settings"
:group 'external
:prefix "aide-")
(defcustom aide-ai-model "text-davinci-003"
"The model paramater that aide.el sends to all OpenAI API endpoints (except Chat API)."
:type 'string
:group 'aide)
(defcustom aide-chat-model "gpt-3.5-turbo"
"The model paramater that aide.el sends to OpenAI Chat API."
:type 'string
:group 'aide)
(defcustom aide-max-input-tokens 3800
"The maximum number of tokens that aide.el sends to OpenAI API"
:type 'integer
:group 'aide)
(defcustom aide-max-output-tokens 100
"The max-tokens paramater that aide.el sends to OpenAI API."
:type 'integer
:group 'aide)
(defcustom aide-temperature 0
"The temperature paramater that aide.el sends to OpenAI API."
:type 'float
:group 'aide)
(defcustom aide-top-p 0.1
"The top-p paramater that aide.el sends to OpenAI API."
:type 'float
:group 'aide)
(defcustom aide-frequency-penalty 0
"The frequency_penalty paramater that aide.el sends to OpenAI API."
:type 'float
:group 'aide)
(defcustom aide-presence-penalty 0
"The presence_penalty paramater that aide.el sends to OpenAI API."
:type 'float
:group 'aide)
(defcustom aide-completions-model "davinci"
"Name of the model used for completions. aide sends requests to
the OpenAI API endpoint of this model."
:type 'string
:group 'aide
:options '("davinci", "text-davinci-002", "text-curie-001", "text-babbage-001", "text-ada-001"))
(defcustom aide-openai-api-key-getter (lambda () openai-api-key)
"Function that retrieves the valid OpenAI API key"
:type 'function
:group 'aide)
(defun aide-openai-complete (api-key prompt)
"Return the prompt answer from OpenAI API.
API-KEY is the OpenAI API key.
PROMPT is the prompt string we send to the API."
(let ((result nil)
(auth-value (format "Bearer %s" api-key)))
(request
"https://api.openai.com/v1/completions"
:type "POST"
:data (json-encode `(("prompt" . ,prompt)
("model" . ,aide-ai-model)
("max_tokens" . ,aide-max-output-tokens)
("temperature" . ,aide-temperature)
("frequency_penalty" . ,aide-frequency-penalty)
("presence_penalty" . ,aide-presence-penalty)
("top_p" . ,aide-top-p)))
:headers `(("Authorization" . ,auth-value) ("Content-Type" . "application/json"))
:sync t
:parser 'json-read
:success (cl-function
(lambda (&key data &allow-other-keys)
(setq result (alist-get 'text (elt (alist-get 'choices data) 0)))))
:error (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
(message "Got error: %S" error-thrown))))
result))
(defun aide-openai-chat (api-key prompt callback)
"Return the prompt answer from OpenAI API.
API-KEY is the OpenAI API key.
PROMPT is the prompt string we send to the API."
(let* ((result nil)
(auth-value (format "Bearer %s" api-key))
(payload (json-encode `(("model" . ,aide-chat-model)
("messages" . [(("role" . "user") ("content" . ,prompt))])))))
(message "Waiting for OpenAI...")
(request
"https://api.openai.com/v1/chat/completions"
:type "POST"
:data payload
:headers `(("Authorization" . ,auth-value) ("Content-Type" . "application/json"))
:sync nil
:parser 'json-read
:success (cl-function
(lambda (&key data &allow-other-keys)
(progn
(setq result (alist-get 'content (alist-get 'message (elt (alist-get 'choices data) 0))))
(funcall callback result)
(message "Done."))))
:error (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
(message "Got error: %S, payload: %S" error-thrown payload))))
result))
(defun aide-openai-complete-region (start end)
"Send the region to OpenAI autocomplete engine and get the result.
START and END are selected region boundaries."
(interactive "r")
(let* ((region (buffer-substring-no-properties start end))
(result (aide--openai-complete-string region)))
(message "%s" result)))
(defun aide-openai-complete-region-insert (start end)
"Send the region to OpenAI and insert the result to the end of buffer.
START and END are selected region boundaries."
(interactive "r")
(let* ((region (buffer-substring-no-properties start end))
(result (aide--openai-complete-string region))
original-point)
(goto-char (point-max))
(setq original-point (point))
(if result
(progn
(insert "\n" result)
(fill-paragraph)
(let ((x (make-overlay original-point (point-max))))
(overlay-put x 'face '(:foreground "orange red")))
result)
(message "Empty result"))))
(defun aide-openai-chat-region-insert (start end)
"Send the region to OpenAI Chat API and insert the result to the end of buffer.
The function is smart to check if current buffer in org mode, and present result accordingly.
START and END are selected region boundaries.
"
(interactive "r")
(let* ((region (buffer-substring-no-properties start end))
(is-in-org-mode (string-equal major-mode "org-mode"))
(extra-conditions "\"\n\nIn your response, limit the characters to 80 characters
per line for text explanations and add line breaks if needed. Do not apply the character limit to code blocks.")
(final-prompt (concat "Please help me with the following question:\n\n \"" region extra-conditions))
original-point
tmp-text-end-point)
(goto-char (point-max))
(setq original-point (point))
(insert "\n\n>>> GPT: Generating response... (This is placeholder text. It will disppear. DO NOT edit.)")
(setq tmp-text-end-point (point))
(let ((x (make-overlay original-point tmp-text-end-point)))
(overlay-put x 'face '(:foreground "lime green"))
(deactivate-mark))
(aide--openai-chat-string final-prompt (lambda (result)
(delete-region original-point tmp-text-end-point)
(if result
(progn
(if is-in-org-mode
(insert "\n\n>>> GPT:\n#+BEGIN_SRC markdown\n" result "\n#+END_SRC")
(insert "\n\n>>> GPT: " result))
(if is-in-org-mode
nil
(let ((x (make-overlay original-point (point-max))))
(overlay-put x 'face '(:foreground "orange red"))
(deactivate-mark)))
result)
(message "Empty result"))))))
(defun aide-openai-chat-paragraph-insert ()
"Send the current paragraph to OpenAI Chat API and append the result to the end of the buffer
"
(interactive)
(let (region-start
region-end)
(save-excursion
(backward-paragraph)
(setq region-start (point))
(forward-paragraph)
(setq region-end (point))
)
(aide-openai-chat-region-insert region-start region-end)))
(defun aide-openai-complete-buffer-insert ()
"Send the ENTIRE buffer, up to max tokens, to OpenAI and insert the result to the end of buffer."
(interactive)
(let (region
result
original-point)
(setq region (buffer-substring-no-properties (get-min-point) (point-max)))
(setq result (aide--openai-complete-string region))
(goto-char (point-max))
(setq original-point (point))
(if result
(progn
(insert "\n" result)
(fill-paragraph)
(let ((x (make-overlay original-point (point-max))))
(overlay-put x 'face '(:foreground "orange red")))
result)
(message "Empty result"))))
(defun aide-openai-tldr-region (start end)
"Send the region to OpenAI autocomplete engine and get the TLDR result.
START and END are selected region boundaries."
(interactive "r")
(let* ((region (buffer-substring-no-properties start end))
(result (aide--openai-complete-string (concat region "\n\n tl;dr:"))))
(message "%s" result)))
(defun aide-openai-edits (api-key instruction input)
"Return the edits answer from OpenAI API.
API-KEY is the OpenAI API key.
INSTRUCTION and INPUT are the two params we send to the API."
(let ((result nil)
(auth-value (format "Bearer %s" api-key)))
(request
"https://api.openai.com/v1/engines/text-davinci-edit-001/edits"
:type "POST"
:data (json-encode `(("input" . ,input)
("instruction" . ,instruction)
("temperature" . 0.9)))
:headers `(("Authorization" . ,auth-value)
("Content-Type" . "application/json"))
:sync t
:parser 'json-read
:success (cl-function
(lambda (&key data &allow-other-keys)
(setq result (alist-get 'text (elt (alist-get 'choices data) 0))))))
result))
(defun aide-openai-edits-region-insert (start end)
"Send the region to OpenAI edits and insert the result to the end of region.
START and END are selected region boundaries."
(interactive "r")
(let* ((region (buffer-substring-no-properties start end))
(result (aide-openai-edits (funcall aide-openai-api-key-getter) "Rephrase the text" region)))
(goto-char end)
(if result
(progn
(insert "\n" result)
(fill-paragraph)
(let ((x (make-overlay end (point))))
(overlay-put x 'face '(:foreground "orange red")))
result)
(message "Empty result"))))
(defun aide-openai-edits-region-replace (start end)
"Send the region to OpenAI edits and replace the region.
START and END are selected region boundaries.
The original content will be stored in the kill ring."
(interactive "r")
(let* ((region (buffer-substring-no-properties start end))
(result (aide-openai-edits (funcall aide-openai-api-key-getter) "Rephrase the text" region)))
(goto-char end)
(if result
(progn
(kill-region start end)
(insert "\n" result)
(fill-paragraph)
(let ((x (make-overlay end (point))))
(overlay-put x 'face '(:foreground "orange red")))
result)
(message "Empty result"))))
;; private
(defun aide--openai-complete-string (string)
(aide-openai-complete (funcall aide-openai-api-key-getter) string))
(defun aide--openai-chat-string (string callback)
(aide-openai-chat (funcall aide-openai-api-key-getter) string callback))
(defun get-min-point ()
"OpenAI API limits requests of > ~4000 tokens (model-specific; davinci
maxes out at request of 4000 tokens; ~15200 char"
(if (> (buffer-size) (* 4 (or aide-max-input-tokens 3800))) ;; 1 tokens = ~4 char
(- (point-max) (* 4 (or aide-max-input-tokens 3800)))
(point-min)))
(provide 'aide)
;;; aide.el ends here