Uses 911K of libraries. Click here for Pure Java version (19870L/119K).
1 | !7 |
2 | |
3 | concept StandardScript { |
4 | S scriptID; |
5 | } |
6 | |
7 | cmodule Cruddie > DynPrintLogAndEnabled { |
8 | !include #1027628 // HTTP+HTTPS servers |
9 | |
10 | transient S salt; |
11 | transient WebChatBot chatBot; |
12 | transient CRUD<StandardScript> standardScriptsCRUD; |
13 | transient CRUD<Conversation> conversationsCRUD; |
14 | |
15 | switchable int vadUpdateInterval = 50; |
16 | switchable double listenTime = 10.0; // listen for 10 seconds after voice activity |
17 | switchable double transcriptTitleShowTime = 5.0; // how long to show recognized text in window title |
18 | |
19 | S myLink() { ret "https://cruddie.site/"; } |
20 | S botLink() { ret "bot"; /*ret appendWithSlash(myLink(), "bot");*/ } |
21 | |
22 | switchable S frontendModuleLibID = "#1027675/ChatBotFrontend"; |
23 | switchable S backendModuleLibID = "#1027591/DynamicClassesMultiCRUD"; |
24 | transient S cmdsSnippetID = #1027616; |
25 | |
26 | start { |
27 | standardScriptsCRUD = new CRUD(StandardScript); |
28 | conversationsCRUD = new CRUD(Conversation); |
29 | thread enter { pcall { |
30 | File saltFile = secretProgramFile("salt.txt"); |
31 | S salt = trimLoadTextFile(saltFile); |
32 | if (empty(salt)) { |
33 | saveTextFile(saltFile, salt = randomID()); |
34 | print("Made salt"); |
35 | } |
36 | dm_restartOnFieldChange enabled(); |
37 | if (!enabled) ret; |
38 | chatBot = new WebChatBot; |
39 | chatBot.preprocess = s -> { |
40 | S s2 = googleDecensor(s); |
41 | print("Preprocessing: " + s + " => " + s2); |
42 | ret s2; |
43 | }; |
44 | chatBot.templateID = #1027690; |
45 | chatBot.baseLink = botLink(); |
46 | chatBot.thoughtBot = new ThoughtBot; |
47 | |
48 | chatBot.jsOnMsgHTML = "window.processNewStuff(src);"; |
49 | |
50 | chatBot.onBotShown = [[ { |
51 | var input = $("#status_message")[0]; |
52 | console.log("input: " + input); |
53 | if (input) |
54 | new Awesomplete(input, { |
55 | minChars: 1, |
56 | list: [ |
57 | "I call you Fido", |
58 | "What is your name?", |
59 | 'add script "#1027704/SomeCruddieScripts/RepeatAfterMe"', |
60 | 'add script "#1027704/SomeCruddieScripts/GoPublic"', |
61 | 'clear scripts' |
62 | ] |
63 | }); |
64 | } ]]; |
65 | |
66 | chatBot.afterHeading = "` + ('webkitSpeechRecognition' in window ? ` " + tag("button", "...", onclick := lineBreaksToSpaces([[ |
67 | startOrStop(); |
68 | if (bigOn) { lastHadVoice = Date.now(); startVAD(); startUpdater(); } |
69 | else stopVAD(); |
70 | ]]), type := 'button, class := 'speechOnBtn, disabled := 'disabled, display := 'inline) |
71 | /*+ hjs([[console.log("Updating"); window.srUpdate();]])*/ + "` : ``) + `" |
72 | + hdiv(hsnippetimg(#1102908, width := 24, height := 24, title := "Someone is speaking (either me or you)"), style := "display: inline; visibility: hidden; margin-left: 10px", class := "vadStatus") |
73 | + hdiv(hsnippetimg(#1102909, width := 24, height := 24, title := "Listening"), style := "display: inline; visibility: hidden; margin-left: 10px", class := "listenStatus") |
74 | + hdiv(small("Note: All conversations are public rn " + targetBlank("https://www.youtube.com/watch?v=StxQerL0D-o", "(why)"))); |
75 | |
76 | chatBot.moreStuff = "window.srUpdate();"; |
77 | |
78 | chatBot.start(); |
79 | |
80 | set redirectHttpToHttps; |
81 | start_webServers(serverSocketFactory_botCompanyEtc()); |
82 | }} |
83 | } |
84 | |
85 | O webServe(S uri, SS params) { |
86 | Pair<Int, Bool> spamCheck = spamBlocker.checkRequest(uri, serveHttp_clientIP()); |
87 | if (spamCheck.b) { |
88 | sleepSeconds(60.0); |
89 | ret print("go away"); |
90 | } |
91 | printVars("webServe", +uri); |
92 | //S cookie = serveHttp_cookieHandling(); |
93 | |
94 | if (startsWith(uri, "/.well-known/")) |
95 | ret loadTextFile(userDir("validation.txt")); |
96 | |
97 | // new-style cookie isn't really used yet |
98 | S newStyleCookie = nu ServeHttp_CookieHandler(verbose := true).handle(); |
99 | |
100 | new Matches m; |
101 | S uri2 = appendSlash(uri); |
102 | if (startsWith(uri2, "/bot/", m)) |
103 | ret chatBot.html("/" + m.rest(), params); |
104 | if (eq(uri, "/awesomplete.css")) ret serveWithContentType(loadSnippet(#2000595), "text/css"); |
105 | if (eq(uri, "/awesomplete.js")) ret serveText(loadSnippet(#2000594)); |
106 | if (endsWith(uri, ".map")) ret ""; |
107 | |
108 | if (eq(uri, "/frames")) |
109 | ret serveFrameSet(params); |
110 | |
111 | S jsOnSpeech = [[ |
112 | if (transcript == 'stop listening') |
113 | stopVAD(); |
114 | else |
115 | window.submitAMsg(transcript); |
116 | lastHeard = transcript; |
117 | lastHeardWhen = Date.now(); |
118 | ]]; |
119 | |
120 | S sayBotMsgsScript = [[ |
121 | window.processNewStuff = function(src) { |
122 | ]] + (eq(params.get('quiet), "1") ? "" : [[ |
123 | if ($("#speechResults") == null) return; // no speech |
124 | // we assume that webkit speech synthesis is present |
125 | // when there is webkit speech recognition |
126 | if (!bigOn) return; // not enabled |
127 | console.log("Got speech"); |
128 | var match = src.match(/\d+/); |
129 | if (match == null) return; |
130 | if (src.match(/NEW DIALOG -->/)) return; |
131 | console.log("Got incremental"); |
132 | var re = /bot-utterance">(.*?)</g; |
133 | var match = re.exec(src); |
134 | var lastUtterance = null; |
135 | while (match != null) { |
136 | lastUtterance = match[1]; |
137 | match = re.exec(src); |
138 | } |
139 | // TODO: properly drop HTML tags/HTML-decode |
140 | if (lastUtterance) |
141 | say(lastUtterance); |
142 | ]]) + [[ |
143 | }; |
144 | ]]; |
145 | |
146 | S speechUI = ""; |
147 | /*hdiv( |
148 | "< Talk to me >", |
149 | id := 'speechResults, |
150 | style := "margin: 10px");*/ |
151 | |
152 | ret hhtml(hmobilefix() + hhead( |
153 | htitle("CRUDDIE - I manage your anything") |
154 | + hLoadJQuery2() |
155 | + hJsMakeCookie() |
156 | + [[<link rel="stylesheet" href="awesomplete.css" /><script src="awesomplete.js"></script>]] // took out async |
157 | ) |
158 | + hbody(hOnBottom( |
159 | p(hsnippetimage(#1102905)) |
160 | + p("Chat bot is doing basic stuff. Documentation coming up.") |
161 | + stats() |
162 | + hSpeechRecognition(jsOnSpeech, true, "en-US", false, |
163 | noWebKit := p("Use Chrome if you want speech recognition"), |
164 | +speechUI) |
165 | + hjavascript([[ |
166 | function say(text) { |
167 | console.log("Saying: " + text); |
168 | var u = new SpeechSynthesisUtterance(text); |
169 | u.lang = 'en-US'; |
170 | u.onstart = function() { console.log("speech start"); srPause = true; srUpdate(); }; |
171 | u.onend = function() { srPause = false; srUpdate(); }; |
172 | window.speechSynthesis.speak(u); |
173 | } |
174 | ]] + sayBotMsgsScript) |
175 | + hjs((S) chatBot.html("/", litmap(), returnJS := true)) |
176 | + hVAD( |
177 | [[console.log("voice start"); $(".vadStatus").css("visibility", "visible");]], |
178 | [[console.log("voice stop"); $(".vadStatus").css("visibility", "hidden");]], |
179 | false) |
180 | + hjs_setTitleStatus() |
181 | + hjs(replaceDollarVars([[ |
182 | var updater; |
183 | var lastHadVoice = 0; |
184 | var lastHeard, lastHeardWhen = 0; |
185 | |
186 | //audioMeterDebug = true; |
187 | |
188 | function startUpdater() { |
189 | if (updater) return; |
190 | console.log("Starting updater"); |
191 | updater = setInterval(vadMagicUpdate, $interval); |
192 | srPause = true; |
193 | } |
194 | |
195 | function stopUpdater() { |
196 | if (!updater) return; |
197 | console.log("Stopping updater"); |
198 | clearInterval(updater); |
199 | updater = null; |
200 | window.resetTitle(); |
201 | } |
202 | |
203 | function vadMagicUpdate() { |
204 | if (!bigOn) { stopUpdater(); return; } |
205 | var now = Date.now(); |
206 | var hasVoice = vadHasVoice(); |
207 | var clipping = vadHasClipping(); |
208 | if (hasVoice) lastHadVoice = now; |
209 | var shouldListen = lastHadVoice >= now-$listenTime; |
210 | var titleStatus = ""; |
211 | if (lastHeardWhen >= now-$transcriptTitleShowTime) |
212 | titleStatus = lastHeard + " |"; |
213 | else |
214 | titleStatus = shouldListen ? $listeningSymbol : ""; |
215 | if (clipping) |
216 | titleStatus = "! " + titleStatus; |
217 | window.setTitleStatus(titleStatus); |
218 | if (srPause != !shouldListen) { |
219 | console.log(shouldListen ? "Listening" : "Not listening"); |
220 | srPause = !shouldListen; |
221 | srUpdate(); |
222 | } |
223 | } |
224 | |
225 | // debug mic level |
226 | /*setInterval(function() { |
227 | if (audioMeter) |
228 | console.log("Mic level: " + audioMeter.absLevel); |
229 | }, 1000);*/ |
230 | ]], |
231 | interval := vadUpdateInterval, |
232 | listenTime := toMS(listenTime), |
233 | transcriptTitleShowTime := toMS(transcriptTitleShowTime), |
234 | listeningSymbol := jsQuote(/*"[LISTENING]"*/unicode_cloud()))) |
235 | )/*, onLoad := "startAwesomplete()"*/)); |
236 | } |
237 | |
238 | S cookieToCaseID(S cookie) { |
239 | ret md5(cookie + salt); |
240 | } |
241 | |
242 | class Request { |
243 | S cookie, caseID; |
244 | S frontend, backend; // module IDs |
245 | |
246 | *(S *cookie) { |
247 | caseID = cookieToCaseID(cookie); |
248 | frontend = dm_makeModuleWithParams_systemQ(frontendModuleLibID, +caseID); |
249 | backend = dm_makeModuleWithParams_systemQ(backendModuleLibID, +caseID); |
250 | dm_call(frontend, 'connectToBackend, backend); |
251 | dm_call(frontend, 'importCmdsFromSnippetIfEmpty, cmdsSnippetID); |
252 | dm_call(frontend, 'addScripts, collect scriptID(list StandardScript())); |
253 | Conversation conv = uniq Conversation(+cookie); |
254 | forwardSwappableFunctionToObject(dm_mod(frontend), |
255 | 'chatLog_userMessagesOnly, func -> LS { |
256 | map(m -> m.text, filter(conv.allMsgs(), m -> m.fromUser)) |
257 | }, 'get); |
258 | printVars(+caseID, +backend); |
259 | } |
260 | } |
261 | |
262 | class ThoughtBot { |
263 | new ThreadLocal<Request> request; |
264 | |
265 | void setSession(S cookie, SS params) { |
266 | //session.set(uniq_sync(Session, +cookie)); |
267 | request.set(new Request(cookie)); |
268 | } |
269 | |
270 | S initialMessage() { |
271 | //ret "Hello from module " + request->backend; |
272 | ret (S) dm_call(request->backend, 'answer, "stats"); |
273 | } |
274 | |
275 | S answer(S s) { |
276 | ret (S) dm_call(request->frontend, 'answer, s); |
277 | } |
278 | } |
279 | |
280 | S stats() { |
281 | ret p(joinWithBR( |
282 | "Server temperature is " + dm_cpuTemperature(), |
283 | n2(numberOfCruddies(), "cruddie") + ", " + n2(vmBus_countResponses chatBotFrontend()) + " loaded", |
284 | )); |
285 | } |
286 | |
287 | int numberOfCruddies() { |
288 | ret countDirsInDir(getProgramDir(beforeSlash(frontendModuleLibID))); |
289 | } |
290 | |
291 | visual |
292 | jtabs("Main", super, |
293 | "Standard Scripts", standardScriptsCRUD.visualize(), |
294 | "Conversations", conversationsCRUD.visualize()); |
295 | |
296 | S serveFrameSet(SS params) { |
297 | ret hhtml(hhead_title("CRUDDIE with frames") + |
298 | tag frameset( |
299 | tag frame("", name := "leftmonitor") + |
300 | tag frame("", src := appendParamsToURL(myLink(), params)) + |
301 | tag frame("", name := "rightmonitor"), cols := "*,550,*")); |
302 | } |
303 | } |
Began life as a copy of #1027610
download show line numbers debug dex old transpilations
Travelled to 7 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
Snippet ID: | #1028679 |
Snippet name: | Cruddie Spike [with VAT magic for Chrome] |
Eternal ID of this version: | #1028679/34 |
Text MD5: | cb8a154981fa170c61f92d47c303fd31 |
Transpilation MD5: | 496e02387fc9fb96cea7efe313751ed7 |
Author: | stefan |
Category: | javax |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2020-07-05 15:14:31 |
Source code size: | 10979 bytes / 303 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 285 / 8464 |
Version history: | 33 change(s) |
Referenced in: | [show references] |