Uses 44637K of libraries. Click here for Pure Java version (35156L/196K).
1 | // Gazelle's "left brain hemisphere" (where all the bots run) |
2 | |
3 | set flag DynModule. // for transpilation |
4 | |
5 | //set flag dm_evalJava_withModule_debug. |
6 | //set flag veryQuickJava3_debug. |
7 | |
8 | // store bot data per conversation |
9 | concept Conversation { |
10 | S cookie; |
11 | S dataStruct; |
12 | } |
13 | |
14 | asclass DynGazelleMultiBot > DynGazelleBot { |
15 | switchable int maxEvalResultLength = oneMegabyte_int(); |
16 | switchable double evalTimeout = 30.0; |
17 | switchable long botProcessed; // timestamp of last post processed by bots |
18 | switchable int maxBotAnswerLength = 100000; |
19 | switchable bool runAutoBots = true; |
20 | switchable bool doPython = true; |
21 | switchable int maxRuntimeMinutes = 60; |
22 | transient new L<Bot> bots; |
23 | Map<Long, GazellePost> allPosts = syncTreeMap(); |
24 | transient Map<Long, O> loadedCodePosts = syncTreeMap(); |
25 | transient bool considerMasterPostsSafe; |
26 | |
27 | sclass CouldntLoadCode {} |
28 | |
29 | transient Lock codeLoadLock = lock(); |
30 | |
31 | volatile long requestsServed; |
32 | transient double systemLoad; |
33 | transient long processSize; |
34 | transient Lock statsLock = lock(); |
35 | |
36 | transient ReliableSingleThread rstPersistRarely = dm_rstWithPreDelay(this, 60.0, r change); |
37 | |
38 | set flag NoNanoHTTPD. |
39 | !include #1029545 // API for Eleu |
40 | |
41 | class Bot { |
42 | S name; |
43 | |
44 | *() {} |
45 | *(S *name) {} |
46 | *(S *name, IVF1<GazellePost> *handlePost) {} |
47 | |
48 | swappable void handlePost(GazellePost post) {} |
49 | |
50 | GazelleBotCred cred() { ret GazelleBotCred(_user, _botToken, name).server(gazelleServer); } |
51 | |
52 | void postReply(GazellePost post, S text, S type, S title default null) { |
53 | if (empty(text) && empty(title)) |
54 | text = "<no output>"; |
55 | gazelle_createPost(cred(), text, type, refs := post.id, +title); |
56 | } |
57 | |
58 | // post: post we are replying to |
59 | void createPostFromBotResult(GazellePost post, IF0 f, IF1<S> modifyBotInfo default null) { |
60 | S text, type = "Code Result", title = "", botInfo = ""; |
61 | bool overrideLastPost = false; |
62 | try { |
63 | O result = f!; |
64 | if (result == null) ret; |
65 | print("Result type: " + className(result)); |
66 | if (result cast CreatePost) { |
67 | print("Is CreatePost"); |
68 | O[] params = toObjectArray(result.params); |
69 | text = (S) optPar text(params); |
70 | type = (S) optPar type(params, type); |
71 | title = (S) optPar title(params); |
72 | botInfo = (S) optPar botInfo(params); |
73 | overrideLastPost = boolPar overrideLastPost(params); |
74 | } else |
75 | text = str_shortenSyntheticAndStandardToString(result); |
76 | } catch print e { |
77 | text = getStackTrace(e); |
78 | } |
79 | if (empty(text) && empty(title)) |
80 | text = "<no output>"; |
81 | botInfo = callFOrKeep(modifyBotInfo, botInfo); |
82 | GazelleBotCred cred = cred(); |
83 | if (nempty(botInfo)) |
84 | cred.botInfo = botInfo; |
85 | |
86 | if (overrideLastPost) { |
87 | // find last post |
88 | S _botInfo = botInfo; |
89 | GazellePost lastPost = firstThat(repliesTo(post), p -> eqic(p.botInfo, _botInfo)); |
90 | print("Last post with bot info " + botInfo + ": " + lastPost); |
91 | if (lastPost != null) { |
92 | gazelle_editPost(cred, lastPost.id, text, type, +title, refs := post.id); |
93 | ret; |
94 | } |
95 | } |
96 | |
97 | gazelle_createPost(cred, text, type, +title, refs := post.id); |
98 | } |
99 | } |
100 | |
101 | srecord CreatePost(L params) {} |
102 | |
103 | start { |
104 | dm_require("#1017856/SystemLoad"); |
105 | dm_vmBus_onMessage systemLoad(voidfunc(double load) { |
106 | if (setField_noPersist(systemLoad := load)) |
107 | distributeDivChanges("serverLoadLeftHemi"); |
108 | }); |
109 | dm_vmBus_onMessage processSize(voidfunc(long processSize) { |
110 | if (setField_noPersist(+processSize)) |
111 | distributeDivChanges("memLeftHemi"); |
112 | }); |
113 | |
114 | // test if we can share classes with dynamically loaded code |
115 | hotwire_addSharedClasses(Pair); |
116 | |
117 | dm_useLocalMechListCopies(); |
118 | dbIndexing(Conversation, "cookie"); |
119 | |
120 | grabLoop.handlePosts = posts -> { |
121 | dm_mediumRefreshTranspiler(); |
122 | |
123 | grabLoop.handlePosts_base(posts); |
124 | |
125 | for (GazellePost post : posts) |
126 | setField(botProcessed := max(botProcessed, post.modified)); |
127 | }; |
128 | |
129 | // legacy conversion to sort allPosts |
130 | setField_noCheck(allPosts := asSyncTreeMap(allPosts)); |
131 | |
132 | bots.add(new Bot("Math Bot", post -> { |
133 | if (post.creating) ret; |
134 | gazelle_mathBot1_handlePost_2(_user, _botToken, post); |
135 | })); |
136 | |
137 | bots.add(new Bot("Code Safety Checker") { |
138 | void handlePost(GazellePost post) { |
139 | if (post.creating) ret; |
140 | if (post.isJavaXCode()) |
141 | gazelle_createPost(cred(), codeSafetyCheckResult(post.text), "Code Safety", refs := post.id); |
142 | } |
143 | }); |
144 | |
145 | bots.add(new Bot("Python Code Safety Checker") { |
146 | void handlePost(GazellePost post) { |
147 | if (post.creating) ret; |
148 | if (post.isPythonCode()) |
149 | gazelle_createPost(cred(), pythonCodeSafetyCheckResult(post.text), "Python Code Safety", refs := post.id); |
150 | } |
151 | }); |
152 | |
153 | bots.add(new Bot("Safe Code Runner") { |
154 | void handlePost(GazellePost post) { |
155 | if (post.creating) ret; |
156 | if (post.isJavaXCode()) { |
157 | int runtime = min(maxRuntimeMinutes, parseFirstInt(jextractIC("runtime <int> minutes", post.type))); |
158 | S code = post.text; |
159 | if (isCodePostSafe(post)) { |
160 | S _code = prepareCode(code, post); |
161 | //S out = shorten(maxEvalResultLength, runCode(code)); |
162 | createPostFromBotResult(post, () -> evalCode(runtime*60.0, _code, post)); |
163 | } |
164 | } |
165 | } |
166 | }); |
167 | |
168 | bots.add(new Bot("Python Safe Code Runner") { |
169 | void handlePost(GazellePost post) { |
170 | if (post.creating) ret; |
171 | if (!doPython) ret; |
172 | if (post.isPythonCode()) { |
173 | S code = post.text; |
174 | if (isPythonCodeSafe(code)) { |
175 | IF0 f = () -> { |
176 | PythonInterpreter interpreter = jython(); |
177 | PyCode compiled = interpreter.compile(code); |
178 | TraceFunction traceFunction = new { |
179 | @Override |
180 | public TraceFunction traceCall(PyFrame frame) { |
181 | ret null with ping(); |
182 | } |
183 | |
184 | @Override |
185 | public TraceFunction traceReturn(PyFrame frame, PyObject ret) { |
186 | ret null with ping(); |
187 | } |
188 | |
189 | @Override |
190 | public TraceFunction traceLine(PyFrame frame, int line) { |
191 | ret null with ping(); |
192 | } |
193 | |
194 | @Override |
195 | public TraceFunction traceException(PyFrame frame, PyException exc) { |
196 | ret null with ping(); |
197 | } |
198 | }; |
199 | Py.getThreadState().tracefunc = traceFunction; |
200 | ret interpreter.eval(compiled); |
201 | }; |
202 | |
203 | createPostFromBotResult(post, () -> evalWithTimeoutOrFail(evalTimeout, f)); |
204 | } |
205 | } |
206 | } |
207 | }); |
208 | |
209 | bots.add(new Bot("Run Code On All Posts") { |
210 | void handlePost(GazellePost post) { |
211 | if (post.creating) ret; |
212 | if (eqic(post.type, "Instruction") && match("Please run this code on all posts", post.text)) { |
213 | long ref = gazelle_firstPostRef(post.id); |
214 | if (ref == 0) ret; |
215 | |
216 | S code = gazelle_text(ref); |
217 | if (!isCodeSafe(code)) ret; |
218 | |
219 | S code2 = "ret func(S post) { " + code + " };"; |
220 | O function = evalCode(code2); |
221 | |
222 | new LS lines; |
223 | S out = shorten(maxEvalResultLength, runCode(code)); |
224 | for (GazellePost post2 : cloneValues(allPosts)) { |
225 | lines.add("Post " + post2.id + " (" + quote(shorten(20, post2.text)) + "): " + shorten(80, runFunc(() -> callF(function, post2.text)))); |
226 | } |
227 | |
228 | gazelle_createPost(cred(), lines(lines), "Code Result", refs := post.id); |
229 | } |
230 | } |
231 | }); |
232 | |
233 | bots.add(new Bot("Mark identifiers safe") { |
234 | void handlePost(GazellePost post) { |
235 | if (post.creating) ret; |
236 | if (/*eqic(post.type, "Instruction") |
237 | &&*/ post.creator.isMaster |
238 | && match("Mark safe", post.text)) { |
239 | S text = getPost(first(post.postRefs)).text; |
240 | LS ids = tok_identifiersInOrder(regexpFirstGroupIC("Unknown identifiers: (.+)", text)); |
241 | print("Marking safe: " + ids); |
242 | postReply(post, markSafe(ids), "Marked safe"); |
243 | } |
244 | } |
245 | }); |
246 | |
247 | bots.add(new Bot("Post Deleter") { |
248 | void handlePost(GazellePost post) { |
249 | if (post.creating) ret; |
250 | if (/*eqic(post.type, "Instruction") |
251 | &&*/ post.creator.isMaster |
252 | && match("Delete posts", post.text)) { |
253 | long ref = gazelle_firstPostRef(post.id); |
254 | if (ref == 0) ret; |
255 | S text = gazelle_text(ref); |
256 | L<Long> postIDs = allToLong(regexpAllFirstGroups(gazelle_deletePostRegexp(), text)); |
257 | print("Deleting posts: " + postIDs); |
258 | if (nempty(postIDs)) { |
259 | Map result = gazelle_deletePosts(cred(), postIDs); |
260 | postReply(post, str(result), "Deletion result"); |
261 | } else |
262 | postReply(post, "No mentioned posts found", "Deletion result"); |
263 | } |
264 | } |
265 | }); |
266 | |
267 | bots.add(new Bot("Detector Runner") { |
268 | void handlePost(GazellePost post) { |
269 | if (post.creating) ret; |
270 | ret unless eqic(post.type, "Instruction") |
271 | && match("Please run detector", post.text); |
272 | |
273 | try { |
274 | long detectorID = post.refWithTagOrFail("detector"); |
275 | long posExamplesID = post.refWithTagOrFail("positive examples"); |
276 | long negExamplesID = post.refWithTagOrFail("negative examples"); |
277 | |
278 | S code = getPost(detectorID).text; |
279 | LS posExamples = tlft(getPost(posExamplesID).text); |
280 | LS negExamples = tlft(getPost(negExamplesID).text); |
281 | LPair<S, Bool> examples = trueFalseBPairs(posExamples, negExamples); |
282 | if (!isCodeSafe(code)) fail("Detector code not safe"); |
283 | |
284 | IF1<S, O> detector = proxy IF1(evalCode(code)); |
285 | new LS good; |
286 | new LS bad; |
287 | new Scorer scorer; |
288 | long time = sysNow(); |
289 | evalWithTimeoutOrFail(evalTimeout, r { |
290 | for (Pair<S, Bool> example : examples) { |
291 | S result = runFunc(() -> detector.get(example.a)); |
292 | bool ok = eq(result, str(example.b)); |
293 | scorer.add(ok); |
294 | S line = (ok ? "OK" : example.b ? "False negative" : "False positive") |
295 | + " (" + shorten(10, result) + "): " + example.a; |
296 | (ok ? good : bad).add(line); |
297 | } |
298 | }); |
299 | time = sysNow()-time; |
300 | |
301 | S text = "Detector code:\n\n" + indentx(code) + "\n\n" |
302 | + n2(good, "correct answer") + ", " + n2(bad, "bad answer") + ". Runtime: " + n2(time) + " ms\n\n" |
303 | + or2(trim(lines(concatLists(bad, ll(""), good))), "No errors"); |
304 | S title = "Score for detector " + detectorID + ": " + scorer; |
305 | |
306 | gazelle_createPost(cred(), text, "Detector Score", +title, |
307 | refs := joinWithSpace(ll(post.id, detectorID, posExamplesID, negExamplesID)), |
308 | refTags := linesLL_rtrim("", "detector", "positive examples", "negative examples")); |
309 | } catch e { |
310 | postReply(post, getStackTrace(e), "Error"); |
311 | } |
312 | } |
313 | }); |
314 | |
315 | bots.add(new Bot("Auto Bot Runner") { |
316 | void handlePost(GazellePost post) { |
317 | if (!runAutoBots) ret; |
318 | if (post.creating) ret; |
319 | |
320 | print("Auto Bot Runner: " + post.id); |
321 | for (GazellePost botPost : values(allPosts)) pcall { |
322 | continue unless eqicOneOf(botPost.type, "JavaX Code (Bot run on every post)", "JavaX Code (Live Auto Bot)") |
323 | && botPost.creator.isMaster; |
324 | |
325 | print("Processing auto-bot " + botPost.id + " on " + post.id); |
326 | |
327 | O code = codeForPost(botPost); |
328 | if (shortNameIs CouldntLoadCode(code)) |
329 | continue with print("Couldn't load code for auto-bot " + botPost.id); |
330 | print("Code type: " + className(code)); |
331 | O botInstance = evalWithTimeoutOrFail(evalTimeout, () -> callFOrNewInstance(code)); |
332 | print("Bot instance type: " + className(botInstance)); |
333 | |
334 | setOpt(botInstance, postID := post.id); |
335 | setOpt(botInstance, post := post.text); |
336 | setOpt(botInstance, postType := post.type); |
337 | |
338 | // prevent endless loop of auto-bots replying to themselves or each other |
339 | // (unless they explicitly request this) |
340 | if (post.isAutoBotMade()) { |
341 | // not a bot by master user? don't allow override |
342 | if (!botPost.isMasterMade()) continue; |
343 | |
344 | // check for override request |
345 | if (!isTrue(evalWithTimeoutOrFail(evalTimeout, |
346 | () -> call(botInstance, "runOnAutoBotPost")))) continue; |
347 | } |
348 | |
349 | createPostFromBotResult(post, () -> evalWithTimeoutOrFail(evalTimeout, |
350 | () -> call(botInstance, "calc")), |
351 | botInfo -> joinNemptiesWithColon("Auto-Bot " + botPost.id, botInfo)); |
352 | } |
353 | } |
354 | }); |
355 | |
356 | dm_doEvery(60.0, 3600.0, r removeDeletedPosts); |
357 | } |
358 | |
359 | void handlePost(GazellePost post) { |
360 | allPosts.put(post.id, post); |
361 | change(); |
362 | //if (grabLoop.firstGrab) ret; |
363 | loadedCodePosts.remove(post.id); |
364 | if (post.modified > botProcessed) { |
365 | print("modified post: " + post.id + " - " + post.modified + "/" + botProcessed); |
366 | for (Bot bot : bots) pcall { |
367 | bot.handlePost(post); |
368 | } |
369 | } |
370 | } |
371 | |
372 | // if post != null, store transpilation |
373 | O evalCode(S code, GazellePost post default null) { |
374 | ret evalCode(evalTimeout, code, post); |
375 | } |
376 | |
377 | O evalCode(double timeout, S code, GazellePost post default null) { |
378 | //printWithIndent("CODE> ", code); |
379 | veryQuickJava_transpiled.set(post != null ? "" : null); // request transpilation |
380 | try { |
381 | // TODO: don't makeDependent |
382 | ret dm_javaEvalWithTimeout(timeout, code); |
383 | } finally { |
384 | if (post != null) { |
385 | S java = veryQuickJava_transpiled!; |
386 | print("Transpilation for " + post.id + ": " + shorten(java)); |
387 | saveTextFile(transpilationFile(post.id), nullOnEmpty(java)); |
388 | } |
389 | } |
390 | } |
391 | |
392 | // assumes code is safety-checked |
393 | S runCode(S code) { |
394 | printWithIndent("CODE> ", code); |
395 | ret runFunc(() -> str_shortenSyntheticAndStandardToString(dm_javaEval(code))); |
396 | } |
397 | |
398 | // run IF0 with timeout, exception to string, convert result to string |
399 | S runFunc(IF0 f) { |
400 | ret str_shortenSyntheticAndStandardToString(evalWithTimeoutOrException(evalTimeout, func { |
401 | try { |
402 | ret str_shortenSyntheticAndStandardToString(f!); |
403 | } catch e { |
404 | ret getStackTrace(e); |
405 | } |
406 | })); |
407 | } |
408 | |
409 | L<GazellePost> repliesTo(GazellePost post) { |
410 | ret filter(values(allPosts), p -> contains(p.postRefs, post.id)); |
411 | } |
412 | |
413 | L<GazellePost> repliesWithTag(GazellePost post, S tag) { |
414 | Pair<Long, S> pair = pair(post.id, upper(tag)); |
415 | ret filter(repliesTo(post), p -> contains(mapPairsB toUpper(p.taggedRefs()), pair)); |
416 | } |
417 | |
418 | GazellePost getPost(long id) { |
419 | ret allPosts.get(id); |
420 | } |
421 | |
422 | S getPostText(long id) { |
423 | ret getPost(id).text; |
424 | } |
425 | |
426 | Cl<GazellePost> getAllPosts() { |
427 | ret values(allPosts); |
428 | } |
429 | |
430 | CreatePost createPost(O... _) { |
431 | ret new CreatePost(asList(_)); |
432 | } |
433 | |
434 | CodeSafetyChecker codeSafetyChecker() { |
435 | new CodeSafetyChecker checker; |
436 | checker.init(); |
437 | checker.markSafe("getAllPosts"); |
438 | ret checker; |
439 | } |
440 | |
441 | PythonCodeSafetyChecker pythonCodeSafetyChecker() { |
442 | new PythonCodeSafetyChecker checker; |
443 | checker.init(); |
444 | checker.markSafe("getAllPosts"); |
445 | ret checker; |
446 | } |
447 | |
448 | S codeSafetyCheckResult(S code) { |
449 | CodeSafetyChecker checker = codeSafetyChecker(); |
450 | checker.checkCode(code); |
451 | ret checker.verbalCheckResult(); |
452 | } |
453 | |
454 | S pythonCodeSafetyCheckResult(S code) { |
455 | PythonCodeSafetyChecker checker = pythonCodeSafetyChecker(); |
456 | checker.checkCode(code); |
457 | ret checker.verbalCheckResult(); |
458 | } |
459 | |
460 | bool isCodePostSafe(GazellePost post) { |
461 | ret considerMasterPostsSafe && post.creator.isMaster |
462 | || isCodeSafe(post.text); |
463 | } |
464 | |
465 | bool isCodeSafe(S code) { |
466 | CodeSafetyChecker checker = codeSafetyChecker(); |
467 | checker.checkCode(code); |
468 | ret checker.isSafe(); |
469 | } |
470 | |
471 | bool isPythonCodeSafe(S code) { |
472 | PythonCodeSafetyChecker checker = pythonCodeSafetyChecker(); |
473 | checker.checkCode(code); |
474 | ret checker.isSafe(); |
475 | } |
476 | |
477 | S prepareCode(S code, GazellePost post) { |
478 | if (tok_isStaticLevelCode(code)) ret code; |
479 | |
480 | code = tok_addReturn(code); |
481 | |
482 | CodeInRewriting cir = new(javaTok(code)); |
483 | augmentCode(cir, post); |
484 | ret cir!; |
485 | } |
486 | |
487 | // define implicit vars and functions |
488 | void augmentCode(CodeInRewriting cir, GazellePost post) { |
489 | // post = text of parent post |
490 | if (cir.contains("post")) { |
491 | long ref = gazelle_firstPostRef(post.id); |
492 | if (ref != 0) |
493 | cir.add("S post = " + quote(gazelle_text(ref)) + ";"); |
494 | } |
495 | |
496 | // post = text of grandparent post |
497 | if (cir.contains("post2")) { |
498 | long ref = gazelle_firstPostRef(gazelle_firstPostRef(post.id)); |
499 | if (ref != 0) |
500 | cir.add("S post2 = " + quote(gazelle_text(ref)) + ";"); |
501 | } |
502 | |
503 | // postType = typeof parent post |
504 | if (cir.contains("postType")) { |
505 | GazellePost post2 = getPost(gazelle_firstPostRef(post.id)); |
506 | if (post2 != null) |
507 | cir.add("S postType = " + quote(post2.type) + ";"); |
508 | } |
509 | |
510 | if (cir.contains("getAllPosts")) |
511 | cir.add([[ |
512 | embedded Cl<GazellePost> getAllPosts() { |
513 | ret lazyMap_bitSet quickImport(asList((Cl) dm_call(dm_current_generic(), "getAllPosts"))); |
514 | } |
515 | ]]); |
516 | |
517 | if (cir.contains("createPost")) |
518 | cir.add([[ |
519 | embedded O createPost(O... _) { |
520 | ret dm_call(dm_current_generic(), "createPost", _); |
521 | } |
522 | ]]); |
523 | |
524 | // optimize gazelle_text |
525 | if (cir.contains("gazelle_text")) |
526 | cir.add([[ |
527 | embedded S gazelle_text(long id) { |
528 | ret (S) dm_call(dm_current_generic(), "getPostText", id); |
529 | } |
530 | ]]); |
531 | } |
532 | |
533 | class CodeInRewriting { |
534 | LS tok; |
535 | new LS additions; |
536 | |
537 | *() {} |
538 | *(LS *tok) {} |
539 | |
540 | bool contains(S token) { |
541 | ret containsToken(tok, token); |
542 | } |
543 | |
544 | S get() { |
545 | ret lines(additions) + join(tok); |
546 | } |
547 | |
548 | void add(S code) { |
549 | addIfNempty(additions, code); |
550 | } |
551 | } |
552 | |
553 | O html(IWebRequest req) { |
554 | { |
555 | lock statsLock; |
556 | requestsServed++; |
557 | rstPersistRarely.trigger(); |
558 | requestServed(); |
559 | } |
560 | |
561 | new Matches m; |
562 | |
563 | if (eqic(req.uri(), "/favicon.ico")) |
564 | ret serveFile(loadLibrary(#1400439), faviconMimeType()); |
565 | |
566 | if (startsWith(req.uri(), "/htmlBot/", m) && isInteger(m.rest())) { |
567 | req.noSpam(); |
568 | long postID = parseLong(m.rest()); |
569 | GazellePost post = getPost(postID); |
570 | if (post == null) ret serve404("Post " + postID + " not found"); |
571 | O code = codeForPost(post); |
572 | IF0 calc; |
573 | |
574 | // Case 1: Static page (code just returns a string) |
575 | if (code instanceof S) |
576 | calc = () -> code; |
577 | |
578 | // Case 2: Code is an argumentless function |
579 | else if (implementsInterfaceShortNamed IF0(code)) |
580 | calc = toIF0(code); |
581 | |
582 | else { |
583 | // Case 3: Code is IF1<IWebRequest, ?> |
584 | // Sadly, for a lambda like (IF1<IWebRequest, S>) req -> ..., we can't find the |
585 | // IWebRequest type by reflection. So we find the IWebRequest interface by name. |
586 | Class reqType = getClassInRealm("main$IWebRequest", code); |
587 | //print(+reqType); |
588 | if (reqType == null) fail("IWebRequest not found in bot"); |
589 | O wrappedReq = proxy(reqType, req); |
590 | calc = () -> callF(code, wrappedReq); |
591 | } |
592 | |
593 | O result = evalWithTimeoutOrFail(evalTimeout, () -> calc!); |
594 | ret hcomment("Made by HTML bot " + postID) + "\n" + str(result); |
595 | } |
596 | |
597 | if (startsWith(req.uri(), "/chatBotReply/", m) && isInteger(m.rest())) { |
598 | req.noSpam(); |
599 | long postID = parseLong(m.rest()); |
600 | GazellePost post = getPost(postID); |
601 | if (post == null) ret serve404("Post " + postID + " not found"); |
602 | O code = codeForPost(post); |
603 | if (shortNameIs CouldntLoadCode(code)) ret withHeader(serve500("Couldn't load code for post")); |
604 | S q = req.params().get("q"); // user input |
605 | bool initial = eq(req.params().get("initial"), "1"); |
606 | S cookie = req.params().get("cookie"); |
607 | if (!initial && empty(q)) ret withHeader(serveText("")); |
608 | |
609 | S answer = ""; |
610 | if (implementsInterfaceShortNamed IF1(code)) { // stateless bot |
611 | if (!initial) |
612 | answer = evalWithTimeoutOrFail(evalTimeout, () -> strOrEmpty(callF(code, q))); |
613 | } else { |
614 | print("Stateful bot. cookie: " + cookie); |
615 | if (empty(cookie)) ret withHeader(serve500("Need cookie for stateful bot")); |
616 | Conversation conv = uniq(Conversation, +cookie); |
617 | O instance = code; |
618 | if (instance == null) ret withHeader(serve500("No bot instance")); |
619 | print("Stateful bot instance: " + instance); |
620 | if (nempty(conv.dataStruct)) { |
621 | O data = unstructureInRealm(conv.dataStruct, instance); // hopefully this is safe |
622 | print("Unstructured: " + data); |
623 | copyAllThisDollarFields(instance, data); // probably not needed anymore |
624 | instance = data; |
625 | } |
626 | O _instance = instance; |
627 | answer = evalWithTimeoutOrFail(evalTimeout, () -> strOrEmpty( |
628 | initial ? callOpt(_instance, "initialMessage") : call(_instance, "answer", q))); |
629 | cset(conv, dataStruct := structure(instance)); |
630 | print("Structured: " + conv.dataStruct); |
631 | } |
632 | |
633 | if (initial && empty(answer)) |
634 | answer = "Bot " + post.id + " ready"; |
635 | |
636 | ret withHeader(serveJSON(litorderedmap(answer := shorten(maxBotAnswerLength, answer)))); |
637 | } |
638 | |
639 | if (startsWith(req.uri(), "/transpilation/", m) && isInteger(m.rest())) { |
640 | long postID = parseLong(m.rest()); |
641 | GazellePost post = getPost(postID); |
642 | if (post == null) ret serve404("Post " + postID + " not found"); |
643 | S src = loadTextFile(transpilationFile(post.id)); |
644 | if (empty(src)) |
645 | src = "No transpilation found for post " + postID + "." + |
646 | (!post.isJavaXCode() ? " Code is not a code post." : " Please try \"Touch post\" and wait a few seconds"); |
647 | else pcall { |
648 | src = javaPrettyPrint(src); |
649 | } |
650 | ret serveText(src); |
651 | } |
652 | |
653 | if (eq(req.uri(), "/")) { |
654 | ret "Loaded code posts:" |
655 | + ul(keys(loadedCodePosts)); |
656 | } |
657 | |
658 | ret serve404(); |
659 | } |
660 | |
661 | O codeForPost(GazellePost post) { |
662 | lock codeLoadLock; |
663 | O code = loadedCodePosts.get(post.id); |
664 | if (code == null) { |
665 | try { |
666 | S codeText = post.text; |
667 | if (!isCodePostSafe(post)) fail("Code is not safe: " + codeSafetyCheckResult(codeText)); |
668 | codeText = prepareCode(codeText, null); |
669 | code = evalCode(codeText, post); |
670 | } catch print e { |
671 | code = new CouldntLoadCode; |
672 | } |
673 | loadedCodePosts.put(post.id, code); |
674 | } |
675 | dm_pointSubmoduleToMe(mainClass(code)); |
676 | ret code; |
677 | } |
678 | |
679 | File transpilationFile(long postID) { |
680 | ret programFile("Post-Transpilations/" + postID + ".java"); |
681 | } |
682 | |
683 | O withHeader(O response) { |
684 | call(response, 'addHeader, "Access-Control-Allow-Origin", "*"); |
685 | ret response; |
686 | } |
687 | |
688 | O _getReloadData() { |
689 | ret loadedCodePosts; |
690 | } |
691 | |
692 | void _setReloadData(Map<Long, O> data) { |
693 | if (data != null) |
694 | loadedCodePosts = data; |
695 | } |
696 | |
697 | void forgetLoadedCodePosts { clear(loadedCodePosts); } |
698 | |
699 | void enhanceFrame(Container f) { |
700 | super.enhanceFrame(f); |
701 | internalFramePopupMenuItems(f, |
702 | "Forget loaded code", rEnter forgetLoadedCodePosts, |
703 | "Remove deleted posts", rThreadEnter removeDeletedPosts); |
704 | } |
705 | |
706 | void removeDeletedPosts { |
707 | Cl<Long> posts = asSet(grabLoop.allPostIDs()); |
708 | print("Keeping " + nPosts(posts)); |
709 | if (syncRemoveAllExcept(allPosts, posts)) change(); |
710 | print("New count: " + nPosts(allPosts)); |
711 | removeAllExcept(loadedCodePosts, posts); |
712 | } |
713 | |
714 | // web sockets |
715 | |
716 | class WebSocketInfo { |
717 | S uri; |
718 | SS params; |
719 | |
720 | new Set<Pair<S, O>> liveDivs; // id/content info |
721 | } |
722 | |
723 | transient Map<virtual WebSocket, WebSocketInfo> webSockets = syncWeakHashMap(); |
724 | |
725 | void cleanMeUp_webSockets { |
726 | closeAllKeysAndClear((Map) webSockets); |
727 | } |
728 | |
729 | void handleWebSocket(virtual WebSocket ws) { |
730 | set(ws, onClose := r { webSockets.remove(ws) }); |
731 | set(ws, onOpen := rEnter { |
732 | S uri = cast rcall getUri(ws); |
733 | SS params = cast rcall getParms(ws); |
734 | print("WebSocket opened! uri: " + uri + ", params: " + params); |
735 | new WebSocketInfo info; |
736 | info.uri = uri; |
737 | info.params = params; |
738 | webSockets.put(ws, info); |
739 | }); |
740 | |
741 | setFieldToIVF1Proxy(ws, onMessage := msg -> { temp enter(); pcall { |
742 | WebSocketInfo info = webSockets.get(ws); |
743 | if (info == null) ret; |
744 | S data = rcall_string getTextPayload(msg); |
745 | Map map = jsonDecodeMap(data); |
746 | O div = map.get("liveDiv"); |
747 | if (div cast S) { |
748 | S contentDesc = div; |
749 | syncAdd(info.liveDivs, pair(div, (O) contentDesc)); |
750 | reloadDiv(ws, div, calcDivContent(contentDesc)); |
751 | } |
752 | }}); |
753 | } |
754 | |
755 | transient ReliableSingleThread_Multi<S> rstDistributeDivChanges = new(1000, lambda1 distributeDivChanges_impl); |
756 | |
757 | void distributeDivChanges(S contentDesc) { |
758 | rstDistributeDivChanges.add(contentDesc); |
759 | } |
760 | |
761 | void distributeDivChanges_impl(S contentDesc) enter { |
762 | //print("distributeDivChanges_impl " + contentDesc); |
763 | S content = null; |
764 | for (Pair<virtual WebSocket, WebSocketInfo> p : syncMapToPairs(webSockets)) { |
765 | for (S div : asForPairsWithB(p.b.liveDivs, contentDesc)) { |
766 | if (content == null) content = calcDivContent(contentDesc); |
767 | if (content == null) { print("No content for " + contentDesc); ret; } |
768 | reloadDiv(p.a, div, content); |
769 | } |
770 | } |
771 | } |
772 | |
773 | void reloadDiv(virtual WebSocket ws, S div, S content) { |
774 | print("Reloading div " + div + " through WebSocket"); |
775 | S jsCode = replaceDollarVars( |
776 | [[ $("#" + $div).html($content);]], |
777 | div := jsQuote(div), content := jsQuote(content)); |
778 | dm_call(ws, "send", jsonEncode(litmap(eval := jsCode))); |
779 | } |
780 | |
781 | S calcDivContent(S contentDesc) { |
782 | if (eq(contentDesc, "webRequestsLeftHemi")) |
783 | ret n2(requestsServed); |
784 | |
785 | if (eq(contentDesc, "serverLoadLeftHemi")) |
786 | ret formatDoubleX(systemLoad, 1); |
787 | |
788 | if (eq(contentDesc, "memLeftHemi")) |
789 | ret str_toM(processSize); |
790 | |
791 | null; |
792 | } |
793 | |
794 | void requestServed { |
795 | distributeDivChanges("webRequestsLeftHemi"); |
796 | } |
797 | |
798 | void setFirstGrab { |
799 | grabLoop.firstGrab = true; |
800 | change(); |
801 | } |
802 | } // end of module |
Began life as a copy of #1029997
download show line numbers debug dex old transpilations
Travelled to 4 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt
No comments. add comment
Snippet ID: | #1030627 |
Snippet name: | DynGazelleMultiBot |
Eternal ID of this version: | #1030627/22 |
Text MD5: | 267beffe9a303e1f7704f10549c1b0e5 |
Transpilation MD5: | b88ba97144c14a2cb0c7a0d1a7cdc9cc |
Author: | stefan |
Category: | javax / gazelle.rocks |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-06-28 22:16:01 |
Source code size: | 27408 bytes / 802 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 331 / 652 |
Version history: | 21 change(s) |
Referenced in: | [show references] |