Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

802
LINES

< > BotCompany Repo | #1030627 // DynGazelleMultiBot

JavaX fragment (include) [tags: use-pretranspiled]

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

Author comment

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: 224 / 486
Version history: 21 change(s)
Referenced in: [show references]