Uses 3098K of libraries. Click here for Pure Java version (21318L/122K).
1 | !7 |
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 | cmodule2 GazelleMultiBot > 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 int maxRuntimeMinutes = 60; |
21 | transient new L<Bot> bots; |
22 | Map<Long, GazellePost> allPosts = syncTreeMap(); |
23 | transient Map<Long, O> loadedCodePosts = syncTreeMap(); |
24 | transient new O couldntLoadCode; |
25 | |
26 | transient Lock codeLoadLock = lock(); |
27 | |
28 | set flag NoNanoHTTPD. |
29 | !include #1029545 // API for Eleu |
30 | |
31 | class Bot {
|
32 | S name; |
33 | |
34 | *() {}
|
35 | *(S *name) {}
|
36 | *(S *name, IVF1<GazellePost> *handlePost) {}
|
37 | |
38 | swappable void handlePost(GazellePost post) {}
|
39 | |
40 | GazelleBotCred cred() { ret GazelleBotCred(_user, _botToken, name); }
|
41 | |
42 | void postReply(GazellePost post, S text, S type, S title default null) {
|
43 | if (empty(text) && empty(title)) |
44 | text = "<no output>"; |
45 | gazelle_createPost(cred(), text, type, refs := post.id, +title); |
46 | } |
47 | |
48 | void createPostFromBotResult(GazellePost post, IF0 f, IF1<S> modifyBotInfo default null) {
|
49 | S text, type = "Code Result", title = "", botInfo = ""; |
50 | try {
|
51 | O result = f!; |
52 | if (result cast CreatePost) {
|
53 | O[] params = toObjectArray(result.params); |
54 | text = (S) optPar text(params); |
55 | type = (S) optPar type(params, type); |
56 | title = (S) optPar title(params); |
57 | botInfo = (S) optPar botInfo(params); |
58 | } else |
59 | text = str_shortenSyntheticAndStandardToString(result); |
60 | } catch print e {
|
61 | text = getStackTrace(e); |
62 | } |
63 | if (empty(text) && empty(title)) |
64 | text = "<no output>"; |
65 | botInfo = callFOrKeep(modifyBotInfo, botInfo); |
66 | GazelleBotCred cred = cred(); |
67 | if (nempty(botInfo)) |
68 | cred.botInfo = botInfo; |
69 | gazelle_createPost(cred, text, type, +title, refs := post.id); |
70 | } |
71 | } |
72 | |
73 | srecord CreatePost(L params) {}
|
74 | |
75 | start {
|
76 | dm_useLocalMechListCopies(); |
77 | dbIndexing(Conversation, "cookie"); |
78 | |
79 | grabLoop.handlePosts = posts -> {
|
80 | dm_mediumRefreshTranspiler(); |
81 | |
82 | grabLoop.handlePosts_base(posts); |
83 | |
84 | for (GazellePost post : posts) |
85 | setField(botProcessed := max(botProcessed, post.modified)); |
86 | }; |
87 | |
88 | // legacy conversion to sort allPosts |
89 | setField_noCheck(allPosts := asSyncTreeMap(allPosts)); |
90 | |
91 | bots.add(new Bot("Math Bot", post -> {
|
92 | if (post.creating) ret; |
93 | gazelle_mathBot1_handlePost_2(_user, _botToken, post); |
94 | })); |
95 | |
96 | bots.add(new Bot("Code Safety Checker") {
|
97 | void handlePost(GazellePost post) {
|
98 | if (post.creating) ret; |
99 | if (post.isJavaXCode()) |
100 | gazelle_createPost(cred(), codeSafetyCheckResult(post.text), "Code Safety", refs := post.id); |
101 | } |
102 | }); |
103 | |
104 | bots.add(new Bot("Safe Code Runner") {
|
105 | void handlePost(GazellePost post) {
|
106 | if (post.creating) ret; |
107 | if (post.isJavaXCode()) {
|
108 | int runtime = min(maxRuntimeMinutes, parseFirstInt(jextractIC("runtime <int> minutes", post.type)));
|
109 | S code = post.text; |
110 | if (isCodeSafe(code)) {
|
111 | S _code = prepareCode(code, post); |
112 | //S out = shorten(maxEvalResultLength, runCode(code)); |
113 | createPostFromBotResult(post, () -> evalCode(runtime*60.0, _code, post)); |
114 | } |
115 | } |
116 | } |
117 | }); |
118 | |
119 | bots.add(new Bot("Run Code On All Posts") {
|
120 | void handlePost(GazellePost post) {
|
121 | if (post.creating) ret; |
122 | if (eqic(post.type, "Instruction") && match("Please run this code on all posts", post.text)) {
|
123 | long ref = gazelle_firstPostRef(post.id); |
124 | if (ref == 0) ret; |
125 | |
126 | S code = gazelle_text(ref); |
127 | if (!isCodeSafe(code)) ret; |
128 | |
129 | S code2 = "ret func(S post) { " + code + " };";
|
130 | O function = evalCode(code2); |
131 | |
132 | new LS lines; |
133 | S out = shorten(maxEvalResultLength, runCode(code)); |
134 | for (GazellePost post2 : cloneValues(allPosts)) {
|
135 | lines.add("Post " + post2.id + " (" + quote(shorten(20, post2.text)) + "): " + shorten(80, runFunc(() -> callF(function, post2.text))));
|
136 | } |
137 | |
138 | gazelle_createPost(cred(), lines(lines), "Code Result", refs := post.id); |
139 | } |
140 | } |
141 | }); |
142 | |
143 | bots.add(new Bot("Mark identifiers safe") {
|
144 | void handlePost(GazellePost post) {
|
145 | if (post.creating) ret; |
146 | if (/*eqic(post.type, "Instruction") |
147 | &&*/ post.creator.isMaster |
148 | && match("Mark safe", post.text)) {
|
149 | S text = getPost(first(post.postRefs)).text; |
150 | LS ids = tok_identifiersInOrder(regexpFirstGroupIC("Unknown identifiers: (.+)", text));
|
151 | print("Marking safe: " + ids);
|
152 | postReply(post, markSafe(ids), "Marked safe"); |
153 | } |
154 | } |
155 | }); |
156 | |
157 | bots.add(new Bot("Post Deleter") {
|
158 | void handlePost(GazellePost post) {
|
159 | if (post.creating) ret; |
160 | if (/*eqic(post.type, "Instruction") |
161 | &&*/ post.creator.isMaster |
162 | && match("Delete posts", post.text)) {
|
163 | long ref = gazelle_firstPostRef(post.id); |
164 | if (ref == 0) ret; |
165 | S text = gazelle_text(ref); |
166 | L<Long> postIDs = allToLong(regexpAllFirstGroups(gazelle_deletePostRegexp(), text)); |
167 | print("Deleting posts: " + postIDs);
|
168 | if (nempty(postIDs)) {
|
169 | Map result = gazelle_deletePosts(cred(), postIDs); |
170 | postReply(post, str(result), "Deletion result"); |
171 | } else |
172 | postReply(post, "No mentioned posts found", "Deletion result"); |
173 | } |
174 | } |
175 | }); |
176 | |
177 | bots.add(new Bot("Detector Runner") {
|
178 | void handlePost(GazellePost post) {
|
179 | if (post.creating) ret; |
180 | ret unless eqic(post.type, "Instruction") |
181 | && match("Please run detector", post.text);
|
182 | |
183 | try {
|
184 | long detectorID = post.refWithTagOrFail("detector");
|
185 | long posExamplesID = post.refWithTagOrFail("positive examples");
|
186 | long negExamplesID = post.refWithTagOrFail("negative examples");
|
187 | |
188 | S code = getPost(detectorID).text; |
189 | LS posExamples = tlft(getPost(posExamplesID).text); |
190 | LS negExamples = tlft(getPost(negExamplesID).text); |
191 | LPair<S, Bool> examples = trueFalseBPairs(posExamples, negExamples); |
192 | if (!isCodeSafe(code)) fail("Detector code not safe");
|
193 | |
194 | IF1<S, O> detector = proxy IF1(evalCode(code)); |
195 | new LS good; |
196 | new LS bad; |
197 | new Scorer scorer; |
198 | long time = sysNow(); |
199 | evalWithTimeoutOrFail(evalTimeout, r {
|
200 | for (Pair<S, Bool> example : examples) {
|
201 | S result = runFunc(() -> detector.get(example.a)); |
202 | bool ok = eq(result, str(example.b)); |
203 | scorer.add(ok); |
204 | S line = (ok ? "OK" : example.b ? "False negative" : "False positive") |
205 | + " (" + shorten(10, result) + "): " + example.a;
|
206 | (ok ? good : bad).add(line); |
207 | } |
208 | }); |
209 | time = sysNow()-time; |
210 | |
211 | S text = "Detector code:\n\n" + indentx(code) + "\n\n" |
212 | + n2(good, "correct answer") + ", " + n2(bad, "bad answer") + ". Runtime: " + n2(time) + " ms\n\n" |
213 | + or2(trim(lines(concatLists(bad, ll(""), good))), "No errors");
|
214 | S title = "Score for detector " + detectorID + ": " + scorer; |
215 | |
216 | gazelle_createPost(cred(), text, "Detector Score", +title, |
217 | refs := joinWithSpace(ll(post.id, detectorID, posExamplesID, negExamplesID)), |
218 | refTags := linesLL_rtrim("", "detector", "positive examples", "negative examples"));
|
219 | } catch e {
|
220 | postReply(post, getStackTrace(e), "Error"); |
221 | } |
222 | } |
223 | }); |
224 | |
225 | bots.add(new Bot("Auto Bot Runner") {
|
226 | void handlePost(GazellePost post) {
|
227 | if (!runAutoBots) ret; |
228 | if (post.creating) ret; |
229 | if (post.isAutoBotMade()) ret; |
230 | |
231 | print("Auto Bot Runner: " + post.id);
|
232 | for (GazellePost botPost : values(allPosts)) {
|
233 | continue unless eqic(botPost.type, "JavaX Code (Bot run on every post)") |
234 | && botPost.creator.isMaster; |
235 | |
236 | print("Processing auto-bot " + botPost.id + " on " + post.id);
|
237 | |
238 | O code = codeForPost(botPost); |
239 | if (code == couldntLoadCode) |
240 | continue with print("Couldn't load code for auto-bot " + botPost.id);
|
241 | print("Code type: " + className(code));
|
242 | O botInstance = evalWithTimeoutOrFail(evalTimeout, () -> callFOrNewInstance(code)); |
243 | print("Bot instance type: " + className(botInstance));
|
244 | |
245 | setOpt(botInstance, post := post.text); |
246 | |
247 | createPostFromBotResult(post, () -> call(botInstance, "calc"), |
248 | botInfo -> joinNemptiesWithColon("Auto-Bot " + botPost.id, botInfo));
|
249 | } |
250 | } |
251 | }); |
252 | |
253 | dm_doEvery(60.0, 3600.0, r removeDeletedPosts); |
254 | } |
255 | |
256 | void handlePost(GazellePost post) {
|
257 | allPosts.put(post.id, post); |
258 | change(); |
259 | loadedCodePosts.remove(post.id); |
260 | if (post.modified > botProcessed) {
|
261 | print("modified: " + post.modified + "/" + botProcessed);
|
262 | for (Bot bot : bots) pcall {
|
263 | bot.handlePost(post); |
264 | } |
265 | } |
266 | } |
267 | |
268 | // if post != null, store transpilation |
269 | O evalCode(S code, GazellePost post default null) {
|
270 | ret evalCode(evalTimeout, code, post); |
271 | } |
272 | |
273 | O evalCode(double timeout, S code, GazellePost post default null) {
|
274 | printWithIndent("CODE> ", code);
|
275 | veryQuickJava_transpiled.set(post != null ? "" : null); // request transpilation |
276 | try {
|
277 | ret dm_javaEvalWithTimeout(timeout, code); |
278 | } finally {
|
279 | if (post != null) {
|
280 | S java = veryQuickJava_transpiled!; |
281 | print("Transpilation for " + post.id + ": " + shorten(java));
|
282 | saveTextFile(transpilationFile(post.id), nullOnEmpty(java)); |
283 | } |
284 | } |
285 | } |
286 | |
287 | // assumes code is safety-checked |
288 | S runCode(S code) {
|
289 | printWithIndent("CODE> ", code);
|
290 | ret runFunc(() -> str_shortenSyntheticAndStandardToString(dm_javaEval(code))); |
291 | } |
292 | |
293 | // run IF0 with timeout, exception to string, convert result to string |
294 | S runFunc(IF0 f) {
|
295 | ret str_shortenSyntheticAndStandardToString(evalWithTimeoutOrException(evalTimeout, func {
|
296 | try {
|
297 | ret str_shortenSyntheticAndStandardToString(f!); |
298 | } catch e {
|
299 | ret getStackTrace(e); |
300 | } |
301 | })); |
302 | } |
303 | |
304 | L<GazellePost> repliesTo(GazellePost post) {
|
305 | ret filter(values(allPosts), p -> contains(p.postRefs, post.id)); |
306 | } |
307 | |
308 | L<GazellePost> repliesWithTag(GazellePost post, S tag) {
|
309 | Pair<Long, S> pair = pair(post.id, upper(tag)); |
310 | ret filter(repliesTo(post), p -> contains(mapPairsB toUpper(p.taggedRefs()), pair)); |
311 | } |
312 | |
313 | GazellePost getPost(long id) {
|
314 | ret allPosts.get(id); |
315 | } |
316 | |
317 | S getPostText(long id) {
|
318 | ret getPost(id).text; |
319 | } |
320 | |
321 | Cl<GazellePost> getAllPosts() {
|
322 | ret values(allPosts); |
323 | } |
324 | |
325 | CreatePost createPost(O... _) {
|
326 | ret new CreatePost(asList(_)); |
327 | } |
328 | |
329 | CodeSafetyChecker codeSafetyChecker() {
|
330 | new CodeSafetyChecker checker; |
331 | checker.init(); |
332 | checker.markSafe("getAllPosts");
|
333 | ret checker; |
334 | } |
335 | |
336 | S codeSafetyCheckResult(S code) {
|
337 | CodeSafetyChecker checker = codeSafetyChecker(); |
338 | checker.checkCode(code); |
339 | ret checker.verbalCheckResult(); |
340 | } |
341 | |
342 | bool isCodeSafe(S code) {
|
343 | CodeSafetyChecker checker = codeSafetyChecker(); |
344 | checker.checkCode(code); |
345 | ret checker.isSafe(); |
346 | } |
347 | |
348 | S prepareCode(S code, GazellePost post) {
|
349 | if (tok_isStaticLevelCode(code)) ret code; |
350 | |
351 | code = tok_addReturn(code); |
352 | |
353 | // define implicit vars and functions |
354 | |
355 | // post = text of parent post |
356 | if (containsJavaToken(code, "post")) {
|
357 | long ref = gazelle_firstPostRef(post.id); |
358 | if (ref != 0) |
359 | code = "S post = " + quote(gazelle_text(ref)) + ";\n" + code; |
360 | } |
361 | |
362 | // post = text of grandparent post |
363 | if (containsJavaToken(code, "post2")) {
|
364 | long ref = gazelle_firstPostRef(gazelle_firstPostRef(post.id)); |
365 | if (ref != 0) |
366 | code = "S post2 = " + quote(gazelle_text(ref)) + ";\n" + code; |
367 | } |
368 | |
369 | if (containsJavaToken(code, "getAllPosts")) |
370 | code = [[ |
371 | embedded Cl<GazellePost> getAllPosts() {
|
372 | ret lazyMap_bitSet quickImport(asList((Cl) dm_call(dm_current_generic(), "getAllPosts"))); |
373 | } |
374 | ]] + code; |
375 | |
376 | if (containsJavaToken(code, "createPost")) |
377 | code = [[ |
378 | embedded O createPost(O... _) {
|
379 | ret dm_call(dm_current_generic(), "createPost", _); |
380 | } |
381 | ]] + code; |
382 | |
383 | // optimize gazelle_text |
384 | if (containsJavaToken(code, "gazelle_text")) {
|
385 | code = [[ |
386 | embedded S gazelle_text(long id) {
|
387 | ret (S) dm_call(dm_current_generic(), "getPostText", id); |
388 | } |
389 | ]] + code; |
390 | } |
391 | |
392 | ret code; |
393 | } |
394 | |
395 | O html(IWebRequest req) {
|
396 | new Matches m; |
397 | |
398 | if (eqic(req.uri(), "/favicon.ico")) |
399 | ret serveFile(loadLibrary(#1400439), faviconMimeType()); |
400 | |
401 | if (startsWith(req.uri(), "/htmlBot/", m) && isInteger(m.rest())) {
|
402 | req.noSpam(); |
403 | long postID = parseLong(m.rest()); |
404 | GazellePost post = getPost(postID); |
405 | if (post == null) ret serve404("Post " + postID + " not found");
|
406 | O code = codeForPost(post); |
407 | IF0 calc; |
408 | |
409 | // Case 1: Static page (code just returns a string) |
410 | if (code instanceof S) |
411 | calc = () -> code; |
412 | |
413 | // Case 2: Code is an argumentless function |
414 | else if (implementsInterfaceShortNamed IF0(code)) |
415 | calc = toIF0(code); |
416 | |
417 | else {
|
418 | // Case 3: Code is IF1<IWebRequest, S> |
419 | // Sadly, for a lambda like (IF1<IWebRequest, S>) req -> ..., we can't find the |
420 | // IWebRequest type by reflection. So we find the IWebRequest interface by name. |
421 | Class reqType = getClassInRealm("main$IWebRequest", code);
|
422 | print(+reqType); |
423 | if (reqType == null) fail("IWebRequest not found in bot");
|
424 | O wrappedReq = proxy(reqType, req); |
425 | calc = () -> callF(code, wrappedReq); |
426 | } |
427 | |
428 | O result = evalWithTimeoutOrFail(evalTimeout, () -> calc!); |
429 | ret (S) result; |
430 | } |
431 | |
432 | if (startsWith(req.uri(), "/chatBotReply/", m) && isInteger(m.rest())) {
|
433 | req.noSpam(); |
434 | long postID = parseLong(m.rest()); |
435 | GazellePost post = getPost(postID); |
436 | if (post == null) ret serve404("Post " + postID + " not found");
|
437 | O code = codeForPost(post); |
438 | if (code == couldntLoadCode) ret withHeader(serve500("Couldn't load code for post"));
|
439 | S q = req.params().get("q"); // user input
|
440 | bool initial = eq(req.params().get("initial"), "1");
|
441 | S cookie = req.params().get("cookie");
|
442 | if (!initial && empty(q)) ret withHeader(serveText(""));
|
443 | |
444 | S answer = ""; |
445 | if (implementsInterfaceShortNamed IF1(code)) { // stateless bot
|
446 | if (!initial) |
447 | answer = evalWithTimeoutOrFail(evalTimeout, () -> strOrEmpty(callF(code, q))); |
448 | } else {
|
449 | print("Stateful bot. cookie: " + cookie);
|
450 | if (empty(cookie)) ret withHeader(serve500("Need cookie for stateful bot"));
|
451 | Conversation conv = uniq(Conversation, +cookie); |
452 | O instance = code; |
453 | if (instance == null) ret withHeader(serve500("No bot instance"));
|
454 | print("Stateful bot instance: " + instance);
|
455 | if (nempty(conv.dataStruct)) {
|
456 | O data = unstructureInRealm(conv.dataStruct, instance); // hopefully this is safe |
457 | print("Unstructured: " + data);
|
458 | copyAllThisDollarFields(instance, data); // probably not needed anymore |
459 | instance = data; |
460 | } |
461 | O _instance = instance; |
462 | answer = evalWithTimeoutOrFail(evalTimeout, () -> strOrEmpty( |
463 | initial ? callOpt(_instance, "initialMessage") : call(_instance, "answer", q))); |
464 | cset(conv, dataStruct := structure(instance)); |
465 | print("Structured: " + conv.dataStruct);
|
466 | } |
467 | |
468 | if (initial && empty(answer)) |
469 | answer = "Bot " + post.id + " ready"; |
470 | |
471 | ret withHeader(serveJSON(litorderedmap(answer := shorten(maxBotAnswerLength, answer)))); |
472 | } |
473 | |
474 | if (startsWith(req.uri(), "/transpilation/", m) && isInteger(m.rest())) {
|
475 | long postID = parseLong(m.rest()); |
476 | GazellePost post = getPost(postID); |
477 | if (post == null) ret serve404("Post " + postID + " not found");
|
478 | S src = loadTextFile(transpilationFile(post.id)); |
479 | if (empty(src)) |
480 | src = "No transpilation found for post " + postID + "." + |
481 | (!post.isJavaXCode() ? " Code is not a code post." : " Please try \"Touch post\" and wait a few seconds"); |
482 | else pcall {
|
483 | src = javaPrettyPrint(src); |
484 | } |
485 | ret serveText(src); |
486 | } |
487 | |
488 | if (eq(req.uri(), "/")) {
|
489 | ret "Loaded code posts:" |
490 | + ul(keys(loadedCodePosts)); |
491 | } |
492 | |
493 | ret serve404(); |
494 | } |
495 | |
496 | O codeForPost(GazellePost post) {
|
497 | lock codeLoadLock; |
498 | O code = loadedCodePosts.get(post.id); |
499 | if (code == null) {
|
500 | try {
|
501 | S codeText = post.text; |
502 | if (!isCodeSafe(codeText)) fail("Code is not safe: " + codeSafetyCheckResult(codeText));
|
503 | codeText = prepareCode(codeText, null); |
504 | code = evalCode(codeText, post); |
505 | } catch print e {
|
506 | code = couldntLoadCode; |
507 | } |
508 | loadedCodePosts.put(post.id, code); |
509 | } |
510 | ret code; |
511 | } |
512 | |
513 | File transpilationFile(long postID) {
|
514 | ret programFile("Post-Transpilations/" + postID + ".java");
|
515 | } |
516 | |
517 | O withHeader(O response) {
|
518 | call(response, 'addHeader, "Access-Control-Allow-Origin", "*"); |
519 | ret response; |
520 | } |
521 | |
522 | O _getReloadData() {
|
523 | ret loadedCodePosts; |
524 | } |
525 | |
526 | void _setReloadData(Map<Long, O> data) {
|
527 | if (data != null) |
528 | loadedCodePosts = data; |
529 | } |
530 | |
531 | void forgetLoadedCodePosts { clear(loadedCodePosts); }
|
532 | |
533 | void enhanceFrame(Container f) {
|
534 | super.enhanceFrame(f); |
535 | internalFramePopupMenuItems(f, |
536 | "Forget loaded code", rEnter forgetLoadedCodePosts, |
537 | "Remove deleted posts", rThreadEnter removeDeletedPosts); |
538 | } |
539 | |
540 | void removeDeletedPosts {
|
541 | Cl<Long> posts = asSet(grabLoop.allPostIDs()); |
542 | print("Keeping " + nPosts(posts));
|
543 | if (syncRemoveAllExcept(allPosts, posts)) change(); |
544 | print("New count: " + nPosts(allPosts));
|
545 | removeAllExcept(loadedCodePosts, posts); |
546 | } |
547 | } |
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: | #1030313 |
| Snippet name: | Gazelle.rocks Multi-Bot [backup before adding Jython] |
| Eternal ID of this version: | #1030313/1 |
| Text MD5: | 1f843b79b4666eda2b9e5739d32f24a3 |
| Transpilation MD5: | 3061bfc6bace1d6f2fb64f3e7f279510 |
| Author: | stefan |
| Category: | javax / gazelle.rocks |
| Type: | JavaX source code (Dynamic Module) |
| Public (visible to everyone): | Yes |
| Archived (hidden from active list): | No |
| Created/modified: | 2020-11-30 15:09:38 |
| Source code size: | 19116 bytes / 547 lines |
| Pitched / IR pitched: | No / No |
| Views / Downloads: | 410 / 537 |
| Referenced in: | [show references] |