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