-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathrobby-transients.el
285 lines (238 loc) · 11.3 KB
/
robby-transients.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
;;; robby-transients.el --- Robby transient menu definitions -*- lexical-binding:t -*-
;;; Commentary:
;; Robby transient menu definitions
;;; Code:
(require 'cl-lib)
(require 'cus-edit)
(require 'seq)
(require 'transient)
(require 'robby-actions)
(require 'robby-commands)
(require 'robby-customization)
(require 'robby-models)
(require 'robby-prompts)
(require 'robby-run-command)
(require 'robby-utils)
(require 'robby-validation)
;;; Util Functions
(defun robby--custom-type (symbol)
"Get the custom type of the custom variable indicated by SYMBOL."
(let ((type (get symbol 'custom-type)))
(if (sequencep type)
(let ((nested-type (nth 1 type)))
(if (sequencep nested-type)
(car nested-type)
nested-type))
type)))
(defun robby--transient-args-to-options (args)
"Convert ARGS to a plist of OpenAI API options."
(seq-reduce
(lambda (plist arg)
(let* ((parts (split-string arg "="))
(key-str (car parts)) (key-sym (intern (format ":%s" key-str)))
(raw-value (cadr parts))
(custom-var (intern (format "robby-chat-%s" key-str)))
(custom-type (robby--custom-type custom-var))
(value (if (or (eq custom-type 'integer) (eq custom-type 'number)) (string-to-number raw-value) raw-value)))
(plist-put plist key-sym value)))
args
'()))
(defun robby--run-transient-command (action &optional arg)
"Run ACTION with ARG and transient args."
(let* ((args (transient-args transient-current-command))
(simple-prompt (transient-arg-value "prompt=" args))
(prompt-prefix (transient-arg-value "prompt-prefix=" args))
(prompt-suffix (transient-arg-value "prompt-suffix=" args))
(prompt-buffer (transient-arg-value "prompt-buffer=" args))
(prompt (or simple-prompt #'robby-get-prompt-from-region))
(prompt-args (if simple-prompt
'()
`(:prompt-prefix ,prompt-prefix :prompt-suffix ,prompt-suffix :buffer ,prompt-buffer :never-ask-p t)))
(response-buffer (transient-arg-value "response-buffer=" args))
(action-args `(:response-buffer ,response-buffer)))
(robby-run-command
:arg arg
:prompt prompt
:prompt-args prompt-args
:action action
:action-args action-args)))
;;; Readers
(defun robby--read-buffer (_prompt _initial-input _history)
"Select a buffer."
(interactive)
(read-buffer "Select buffer: "))
(defun robby--read-decimal (prompt _initial-input _history)
"Read a decimal number from the minibuffer with PROMPT."
(interactive)
(format "%s" (read-number prompt)))
(defun robby--read-option-with-validation (option prompt initial-input history)
"Read an OpenAI API option with validation.
OPTION is the option to validate against.
PROMPT is the prompt to display in the minibuffer when reading the option value.
INITIAL-INPUT is the initial value to display in the minibuffer.
HISTORY is the history list to use for the minibuffer."
(save-match-data
(cl-block nil
(while t
(let* ((str (read-from-minibuffer prompt initial-input nil nil history))
(err-msg (robby--validate option str)))
(if err-msg
(progn
(message err-msg)
(sit-for 1))
(cl-return str)))))))
(defun robby--read-temperature (prompt initial-input history)
"Read temperature API option from the minibuffer.
PROMPT is the prompt to display in the minibuffer when reading the option value.
INITIAL-INPUT is the initial value to display in the minibuffer.
HISTORY is the history list to use for the minibuffer."
(interactive)
(robby--read-option-with-validation 'chat-temperature prompt initial-input history))
(defun robby--read-top-p (prompt initial-input history)
"Read top-p API Option from the minibuffer.
PROMPT is the prompt to display in the minibuffer when reading the option value.
INITIAL-INPUT is the initial value to display in the minibuffer.
HISTORY is the history list to use for the minibuffer."
(interactive)
(robby--read-option-with-validation 'chat-top-p prompt initial-input history))
(defun robby--read-presence-penalty (prompt initial-input history)
"Read presence penalty API option from the minibuffer.
PROMPT is the prompt to display in the minibuffer when reading the option value.
INITIAL-INPUT is the initial value to display in the minibuffer.
HISTORY is the history list to use for the minibuffer."
(interactive)
(robby--read-option-with-validation 'chat-presence-penalty prompt initial-input history))
(defun robby--read-frequency-penalty (prompt initial-input history)
"Read frequency penalty API option from the minibuffer.
PROMPT is the prompt to display in the minibuffer when reading the option value.
INITIAL-INPUT is the initial value to display in the minibuffer.
HISTORY is the history list to use for the minibuffer."
(interactive)
(robby--read-option-with-validation 'chat-frequency-penalty prompt initial-input history))
;;; Action Suffixes
(transient-define-suffix
robby--respond-with-robby-chat-suffix ()
(interactive)
(robby--run-transient-command #'robby-respond-with-robby-chat))
(transient-define-suffix
robby--prefix-region-with-response-suffix ()
(interactive)
(robby--run-transient-command #'robby-prepend-response-to-region))
(transient-define-suffix
robby--append-response-to-region-suffix ()
(interactive)
(robby--run-transient-command #'robby-append-response-to-region))
(transient-define-suffix
robby--replace-region-with-response-suffix ()
(interactive)
(let* ((args (transient-args transient-current-command))
(diff-preview (transient-arg-value "diff-preview" args)))
(robby--run-transient-command #'robby-replace-region-with-response diff-preview)))
;;; Misc Suffixes
(transient-define-suffix
robby--clear-history-suffix ()
(interactive)
(robby-clear-history))
;;; API Options Prefixes
(defvar robby--api-options
'(robby-chat-max-tokens robby-chat-temperature robby-chat-top-p robby-chat-n robby-chat-stop robby-chat-presence-penalty robby-chat-frequency-penalty robby-chat-user)
"API options available for customization in transient.
Only includes options that cannot be nil.")
(transient-define-suffix robby--apply-api-options ()
(interactive)
(seq-do (lambda (custom-var) (set custom-var nil)) robby--api-options)
(let ((args (transient-args transient-current-command)))
(seq-do
(lambda (arg)
(let* ((parts (split-string arg "="))
(key-str (car parts))
(raw-value (cadr parts))
(custom-var (intern (format "robby-chat-%s" key-str)))
(custom-type (robby--custom-type custom-var))
(value (if (or (eq custom-type 'integer) (eq custom-type 'number)) (string-to-number raw-value) raw-value)))
(set custom-var value)))
args)))
(defun robby--init-api-options (obj)
"Initialize API options for transient object OBJ."
(oset obj value `(,@(robby--options-transient-value))))
;;; robby-api-options
(transient-define-prefix robby-api-options ()
"Chat API option transient."
:init-value 'robby--init-api-options
["Chat API Options"
("m" "model" "model=" :always-read t :choices ,(robby--get-models))
("t" "max tokens" "max-tokens=" :reader transient-read-number-N+ :always-read t)
("e" "temperature" "temperature=" :reader robby--read-temperature :always-read t)
("p" "top p" "top-p=" :reader robby--read-top-p :always-read t)
("n" "n" "n=" :reader transient-read-number-N+ :always-read t)
("o" "stop" "stop=" :always-read t)
("r" "presence penalty" "presence-penalty=" :reader robby--read-presence-penalty :always-read t)
("f" "frequency penalty" "frequency-penalty=" :reader robby--read-frequency-penalty :always-read t)
("u" "user" "user=" :always-read t)]
[[("a" "apply options" robby--apply-api-options :transient transient--do-return)]
[("x" "exit without applying options" ignore :transient transient--do-return :if (lambda () transient-current-command))]])
;;; robby-builder
;;;###autoload (autoload 'robby-builder "robby" "Build a robby AI command." t)
(transient-define-prefix robby-builder ()
"Build a robby AI command."
:incompatible '(("prompt=" "prompt-prefix=")
("prompt=" "prompt-suffix=")
("prompt=" "prompt-buffer="))
["Prompt"
("i" "simple prompt" "prompt=" :always-read t :level 3)]
["Prompt from Region or Buffer Options"
("p" "prompt prefix" "prompt-prefix=" :always-read t :level 3)
("s" "prompt suffix" "prompt-suffix=" :always-read t :level 3)
("b" "prompt buffer" "prompt-buffer=" :reader robby--read-buffer :level 6)]
[["Region Actions"
("x" "prefix region with response" robby--prefix-region-with-response-suffix :level 3)
("a" "append response to region" robby--append-response-to-region-suffix :level 3)
("g" "replace region with response" robby--replace-region-with-response-suffix :level 3)]
["Misc Actions"
("v" "respond in robby view buffer" robby--respond-with-robby-chat-suffix :level 3)]]
["Region Action Options"
("f" "response buffer" "response-buffer=" :reader robby--read-buffer :level 6)
("d" "show diff preview before replacing region" "diff-preview" :reader robby--read-buffer :level 5)]
["Commands"
("u" "re-run last command" robby-run-last-command :level 4)
("c" "insert last command" robby-insert-last-command :level 4)]
["History" :description (lambda () (concat (propertize "History " 'face 'transient-heading) (propertize (format "(%d)" (length robby--history)) 'face 'transient-inactive-value)))
("h" "use history" "historyp" :level 5)
("l" "clear history" robby--clear-history-suffix :transient t :level 5)]
["API"
("A" "API options" robby-api-options :transient transient--do-recurse :level 5)])
;;; robby-commands
(transient-define-suffix
robby--fix-code ()
(interactive)
(let* ((args (transient-args transient-current-command))
(diff-preview (transient-arg-value "diff-preview" args)))
(robby-fix-code diff-preview)))
(transient-define-suffix
robby--proof-read ()
(interactive)
(let* ((args (transient-args transient-current-command))
(diff-preview (transient-arg-value "diff-preview" args)))
(robby-proof-read diff-preview)))
;;;###autoload (autoload 'robby-commands "robby" "Display menu for executing robby commands." t)
(transient-define-prefix robby-commands ()
"Display menu for executing built-in robby commands."
["Core Commands"
("c" "chat with AI" robby-chat)
("C" "chat with AI with initial prompt from selected region" robby-chat-from-region)
("m" "ask AI and respond with message" robby-message)
("p" "prompt from region and prepend response to region" robby-prepend-region)
("a" "prompt from region and append response to region" robby-append-region)
("r" "prompt from region and replace region with response" robby-replace-region)]
["Commands for Specific Tasks"
("d" "describe code" robby-describe-code)
("f" "fix code" robby--fix-code)
("g" "generate commit message from staged changes" robby-git-commit-message)
("o" "add comments" robby-add-comment)
("t" "write tests" robby-write-tests)
("s" "summarize text" robby-summarize)
("x" "proof read text" robby--proof-read)]
["Options"
("d" "show diff preview before replacing region" "diff-preview" :reader robby--read-buffer :level 5)])
(provide 'robby-transients)
;;; robby-transients.el ends here