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: | 551 / 8761 |
| Version history: | 33 change(s) |
| Referenced in: | [show references] |