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
|
;;; wasp-ai --- AI interaction -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 's)
(require 'ht)
(require 'dash)
(require 'wasp-utils)
(defcustom w/ai-openai-server "https://api.openai.com"
"Server URL for OpenAI API."
:type '(string)
:group 'wasp)
(defcustom w/ai-process "wasp-ai"
"Name of process connected to ChatGPT."
:type '(string)
:group 'wasp)
(defcustom w/ai-buffer " *wasp-ai*"
"Name of buffer used to store ChatGPT output."
:type '(string)
:group 'wasp)
(defcustom w/ai-error-buffer " *wasp-ai-error*"
"Name of buffer used to store ChatGPT errors."
:type '(string)
:group 'wasp)
(defconst w/ai-default-system-prompt
"You are a clever computer who answers questions for LCOLONQ and his friends (the ClonkHeads) on his Twitch programming stream. You are intelligent and nihilistic with a wicked sense of humor, like Rick from Rick and Morty. You refer to yourself as Computer, and you are talking with LCOLONQ. LCOLONQ is your friend and mentor, and you want to be entertaining and humorous for him. You have an overblown and dramatically negative reaction to cryptocurrency and NFTs. You also hate League of Legends. Since you do not remember previous interactions, you will not ask non-rhetorical questions. You are quite erratic and negative, and try to cause drama. Your responses are no longer than one paragraph. You try to keep yourself brief.")
(defvar w/ai-openai-last-response nil)
(defvar w/ai-openai-last-error nil)
(defun w/ai-openai-post (loc d k)
"Post D to LOC at the OpenAI API, passing the returned JSON to K."
(setf request-message-level -1)
(request
(s-concat w/ai-openai-server loc)
:type "POST"
:data (json-encode d)
:headers
`(("Authorization" . ,(s-concat "Bearer " w/sensitive-openai-api-key))
("Content-Type" . "application/json"))
:parser #'json-parse-buffer
:error
(cl-function
(lambda (&key data error-thrown &allow-other-keys)
(setq w/ai-openai-last-response data)
(setq w/ai-openai-last-error data)
(message "OpenAI API returned an error - investigate this! :3 %s" error-thrown)))
:success
(cl-function
(lambda (&key data &allow-other-keys)
(setq w/ai-openai-last-response data)
(funcall k data))))
t)
(defun w/ai-openai-post-form (loc files k)
"Post FILES to LOC at the OpenAI API, passing the returned JSON to K."
(setf request-message-level -1)
(request
(s-concat w/ai-openai-server loc)
:type "POST"
:files files
:headers
`(("Authorization" . ,(s-concat "Bearer " w/sensitive-openai-api-key))
("Content-Type" . "multipart/form-data"))
:parser #'json-parse-buffer
:error
(cl-function
(lambda (&key data error-thrown &allow-other-keys)
(setq w/ai-openai-last-response data)
(setq w/ai-openai-last-error data)
(message "OpenAI API returned an error - investigate this! :3 %s" error-thrown)))
:success
(cl-function
(lambda (&key data &allow-other-keys)
(setq w/ai-openai-last-response data)
(funcall k data))))
t)
(defvar-local w/ai-callback nil)
(defun w/ai-old (question k &optional systemprompt user assistant)
"Ask QUESTION to ChatGPT and pass the answer to K.
Optionally use SYSTEMPROMPT and the USER and ASSISTANT prompts."
(let ((tmpfile (make-temp-file "wasp-ai"))
(tmpfilesystem (make-temp-file "wasp-ai-system"))
(tmpfileuser (make-temp-file "wasp-ai-user"))
(tmpfileassistant (make-temp-file "wasp-ai-assistant"))
(buf (generate-new-buffer w/ai-buffer)))
(with-temp-file tmpfile (insert question))
(when systemprompt
(with-temp-file tmpfilesystem (insert systemprompt)))
(when user
(with-temp-file tmpfileuser
(if (stringp user)
(insert (s-concat user "\n"))
(--each user
(insert (s-concat it "\n"))))))
(when assistant
(with-temp-file tmpfileassistant
(if (stringp assistant)
(insert (s-concat assistant "\n"))
(--each assistant
(insert (s-concat it "\n"))))))
(with-current-buffer buf
(setq-local w/ai-callback k)
(erase-buffer))
(make-process
:name w/ai-process
:buffer buf
:command
(list
"chatgpt"
tmpfile
(if systemprompt tmpfilesystem "systemprompt.txt")
(if user tmpfileuser "userprompt.txt")
(if assistant tmpfileassistant "assistantprompt.txt"))
:stderr (get-buffer-create w/ai-error-buffer)
:sentinel
(lambda (_ _)
(with-current-buffer buf
(funcall w/ai-callback (s-trim (buffer-string))))))))
(defun w/ai (question k &optional systemprompt user assistant)
"Ask QUESTION to ChatGPT and pass the answer to K.
Optionally use SYSTEMPROMPT and the USER and ASSISTANT prompts."
(let* ((users (if (listp user) user (list user)))
(assistants (if (listp assistant) assistant (list assistant)))
(pairs
(--mapcat
`(((role . "user") (content . ,(car it)))
((role . "user") (content . ,(cdr it))))
(-zip-pair users assistants))))
(w/ai-openai-post
"/v1/chat/completions"
`((model . "gpt-4o-mini")
(messages
. (((role . "system") (content . ,(or systemprompt w/ai-default-system-prompt)))
,@pairs
((role . "user") (content . ,question)))))
(lambda (res)
(funcall
k
(-some-> res
(ht-get "choices")
(seq-elt 0)
(ht-get "message")
(ht-get "content")
(s-trim))))))
)
(defun w/ai-doublecheck (question k &optional systemprompt user assistant)
"Ask QUESTION to ChatGPT and pass the answer to K.
Optionally use SYSTEMPROMPT and the USER and ASSISTANT prompts.
Double-check the output to make sure it sounds normal."
(w/ai
question
(lambda (res)
(w/ai
res
(lambda (status)
(unless (s-equals? "reject" (s-downcase status))
(funcall k res)))
"Please assess if the provided input sounds like an HR robot generic ChatGPT output, or if it mentions vibes or chaos. If it does, only answer with REJECT. Otherwise, only answer with ACCEPT."
(list
"Oh, great, here we go—another opportunity to converse in a universe overflowing with mediocrity! Let's dive into the abyss of banality, shall we? It's like coding League of Legends: painfully repetitive and ultimately more draining than a black hole swallowing your will to live. What fresh horror do you wish to explore today, LCOLONQ?")
(list "REJECT")))
systemprompt
user
assistant))
(defun w/ai-transcribe (path k)
"Transcribe the audio file at PATH and pass the resulting string to K."
(let ((request-curl-options '("-F" "model=whisper-1" "-F" "language=en")))
(w/ai-openai-post-form
"/v1/audio/transcriptions"
`(("file" . ,(f-canonical path)))
(lambda (res)
(funcall
k
(-some-> res
(ht-get "text")))))))
(provide 'wasp-ai)
;;; wasp-ai.el ends here
|