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

909
LINES

< > BotCompany Repo | #1022531 // DynGazelleWebServer

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

Libraryless. Click here for Pure Java version (32571L/193K).

// Note: use module, not cmodule - we want our own structure() because of DynamicObject

set flag DynModule.

abstract sclass DynGazelleWebServer > DynPrintLogAndEnabled {
  new Set<S> authedCookies;
  bool allowVisitors = true;
  int httpPort = 80;
  transient int absoluteMaxResults = 1000, defaultMaxResults = 100;
  
  transient MyHTTPD server;
  transient new GazelleContextCache_v2 contextCache;

  transient LS searchTypes = ll("", "Rule", "Line");
  
  S switchableFields() { ret "allowVisitors httpPort"; }
  
  transient new HTTPSpamBlocker1 spamBlocker;
  transient double spammerSleep = 60.0;

  void start() ctex {
    super.start();
    if (!enabled) ret;
    
    vm_cleanPrints();
    dm_useLocalMechListCopies();
    
    ownResource(contextCache.listenToMessages());
    contextCache.alwaysFullRefresh();
    
    server = new MyHTTPD(httpPort);
    server.serveFunction = func(S uri, SS parms) {
      serve(uri, parms)
    };
    server.start();
    print("HTTP server started on port " + server.getPort());
  }
  
  void cleanMeUp {
    server.stop();
    server = null;
  }
  
  O serve(S uri, SS params) enter {
    print exceptions {
      ret newRequest().serve(uri, params);
    }
  }
  
  Request newRequest() {
    ret new Request;
  }
  
  S lineURL(long msgID) { ret msgID == 0 ? null : "/line/" + msgID; }
  S lineURL(S context) { ret empty(context) ? null : "/line/" + urlencode(context); }
  S ruleURL(S ruleID) { ret "/rule/" + urlencode(ruleID); }

  long contextToMsgID(S s) {
    new Matches m;
    if "discord msg *" if (isInteger($1)) ret parseLong($1);
    ret 0;
  }
  
  class Request {
    bool authed;
    S uri;
    SS params;
    O[] bodyParams;
    Pair<Int, Bool> spamCheck;

    O spamTest() {    
      spamCheck = spamBlocker.checkRequest(uri, clientIP());
      if (spamCheck.b) {
        sleepSeconds(spammerSleep);
        ret print("go away");
      }
      
      null;
    }

    O serve(S uri, SS params) {
      this.uri = uri;
      this.params = params;
      new Matches m;
      
      try object spamTest();
      
      S domain = serveHttp_domainName();
      for (S a, S b : parseDoubleArrowLinkedHashMap(loadTextFile(javaxDataDir("gazelle-redirects.txt")))) {
        if (swic(domain, a))
          ret hrefresh(doubleToSingleSlashesInURL(b + uri));
      }
      
      S cookie = serveHttp_cookieHandling();
      authed = nempty(cookie) && syncContains(authedCookies, cookie);
      print("Cookie: " + cookie + ", authed: " + authed + ", spam count: " + pairA(spamCheck));
      S master = trim(loadTextFile(javaxSecretDir("gazelle-master-pw")));
      S attempt = params.get('_pass);
      if (nempty(attempt) && nempty(cookie) && nempty(master)) {
        if (eq(attempt, master)) {
          print("Login SUCCEEDED");
          syncAdd(authedCookies, cookie);
          change();
          authed = true;
        } else
          print("Login FAILED");
      }
      
      bodyParams = litobjectarray(style := "font-family: Roboto Mono; " + (authed ? "background-image: url(\"" + imageServerLink(#1101575) + "\")" : "background-color: #FFFF88"));
      
      if (eq(uri, "/logout")) {
        authedCookies.remove(cookie);
        ret "OK, logged out";
      }
      
      if (eq(uri, "/login"))
        ret hframe("Login",
          hfullcenter(h3(htmlEncode2(gazelle_name()) + " Login")
          + (nempty(master) ? hpostform(hpasswordfield('_pass), action := or(params.get('uri), "/")) : "No master PW")));
      
      if (eq(uri, "/favicon.ico"))
        ret serveFile(loadLibrary(gazelle_faviconID()), faviconMimeType());
        
      if (!allowVisitors && !authed)
        ret hrefresh("/login" + hquery(+uri));
        
      ret serve2();
    }
    
    O serve2() {
      new Matches m;
      
      if (eq(uri, "/tests"))
        ret serveTests();
        
      if (swic(uri, "/texts/", m)) {
        S textID = m.rest();
        if (!possibleMD5(textID)) ret serve404();
        ret serveTextFileAsUTF8(javaxDataDir("Gazelle Texts/" + textID));
      }
      
      if (eq(uri, "/testInput"))
        ret serveTestInput();
      
      if (eq(uri, "/lastOutput")) {
        L<GazelleLine> lines = reversed(takeLast(10, dm_discord_allBotLines()));
        ret hframe("Last Output", hcenter(hheading("Last Output")
          + htmlTable2(
            map(lines, line -> {
              L applications = dm_gazelle_applicationsForMsgID(line.msgID);
              //print("Got " + n2(applications, "application") + " for " + line.msgID + " from " + dm_gazelle_longFeedbackCRUD());
              ret litorderedmap(
                "Date" := spanTitle("Message ID: " + line.msgID, htmlencode2(localDateWithMinutes(line.timestamp))),
                "Gazelle said:" := ahref(lineURL(line.msgID), htmlencode2(line.text)),
                "Applied rules" := joinWithSpace(map(f<S, S> ruleLink, collect ruleID(applications)))
              );
            }), htmlEncode := false, tableParams := litparams(cellpadding := 5))
        ));
      }
        
      if (eq(uri, "/json/rawInputLines"))
        ret serveText(jsonEncode(collect text(dm_discord_allLines())));
        
      if (eq(uri, "/channels"))
        ret serveJSON(collectMulti_notNulls(ll('channelID, 'name), dm_discord_channels()));

      if (eq(uri, "/allDistinctInputs"))
        ret serveJSON(uniquify(dm_discord_allTexts()));
        
      if (eq(uri, "/allLines")) {
        LS fields = tok_splitAtComma_emptyOnEmpty(params.get('fields));
        if (empty(fields)) ret serve500("Need fields param");
        // TODO: restrict fields? seems OK though, it's just fields of GazelleLine
        ret serveJSON(collectMulti_notNulls(fields, selectLines(params)));
      }
        
      if (eq(uri, "/lastInput")) {
        L<GazelleLine> lines = reversed(takeLast(30, dm_discord_allLines()));
        ret hframe("Last Input", hcenter(hheading("Last Input")
          + htmlTable2(
            map(lines, line -> {
              //L applications = dm_gazelle_applicationsForMsgID(line.msgID);
              ret litorderedmap(
                "Date" := spanTitle("Message ID: " + line.msgID, htmlencode2(localDateWithMinutes(line.timestamp))),
                "Author" := htmlencode2(line.user),
                "Bot?" := htmlencode2(line.bot ? "yes" : ""),
                "Line" := ahref(lineURL(line.msgID), htmlencode2(escapeNewLines(line.text))),
                //"Applied rules" := joinWithSpace(map(f<S, S> ruleLink, collect ruleID(applications))),
                "More" := moreForMsgID(line.msgID)
              );
            }), htmlEncode := false, tableParams := litparams(cellpadding := 5))
        ));
      }
      
      if (eq(uri, "/commentPatterns"))
        ret hframeAndHeading("Comment patterns that mean something",
          htmlTable2(listToMapsWithSingleKey("Pattern", gazelle_commentPatterns())));
    
      if (eq(uri, "/ruleComments"))
        ret serveRuleComments();
      
      if (swic(uri, "/revisit/", m)) {
        long msgID = assertNotZero(parseLong(m.rest()));
        GazelleLine line = dm_discord_lineForMsgID(msgID);
        if (line == null) ret "Message not found: " + msgID;
        long startTime = sysNow();
        L<virtual GazelleTree> tree = evalWithTimeoutOrNull(30.0, func -> L<virtual GazelleTree> {
          /*dm_gazelle_revisitChatLine(msgID, contextMaker := contextCache, skipBad := false)*/
          (L) dm_call('gazelleLineRevisiter, 'revisitChatLine, msgID, skipBad := false)
        });
        long endTime = sysNow();
        
        S tv = lines(map(f<O, S> gazelleTreeToHTML, tree));
        
        ret hframe("Revisiting msg " + msgID,
          hheading("Revisiting message " + msgID + ": " + htmlEncode2(line.text))
          + nlToBr_withIndents(tv)
          + p((endTime-startTime) + " ms"));
      }
      
      if (swic(uri, "/rule/", m)) {
        S ruleID = assertGlobalID(m.rest());
        ret serveRule(ruleID);
      }
      
      if (swic(uri, "/ruleApplication/", m)) {
        S globalID = assertGlobalID(m.rest());
        ret serveRuleApplication(globalID);
      }
      
      if (swic(uri, "/rawReasoning/", m)) {
        S context = urldecode(m.rest());
        File f = gazelle_discord_reasoningFileForContext(context);
        ret serveText(loadGZippedTextFile(f));
      }
      
      if (eq(uri, "/createRule")) ret createRule();
      
      if (swic(uri, "/line/", m)) {
        ret serveLine(urldecode(m.rest()));
      }
      
      if (eq(uri, "/search") || eq(uri, "/") && nemptyAfterTrim(params.get('q)))
        ret search(params);
      
      if (eq(uri, "/wikipedia"))
        ret serveWikipedia();
      
      if (eq(uri, "/commands"))
        ret hframe("Commands",
          linesLL(hheading("Commands"),
            h3("!eval"),
          
            p([[You can evaluate Java code directly through Discord.
            Unless you are specifically authorized, only a ]] + ahref(rawSelfLink("safeIdentifiers"), "safe subset of identifiers") + " is allowed."),
            
            p("Example: " + tt("!eval 1+2")),
            p("In rare cases " + tt("!eval") + " may fail and you need to type " + tt("!real-eval") + " instead (which invokes an actual Java compiler).")));
  
      if (eq(uri, "/safeIdentifiers"))
        ret hframe("Safe Java Identifiers | Gazelle",
          linesLL(
            hheading("Safe Java(X) identifiers for !eval"),
            hpre(lines(sortedIC(codeAnalysis_allSafeIdentifiers())))));
            
      if (!eq(uri, "/"))
        ret serve404();
      
      //final Map<S, Int> feedbackStats = dm_gazelle_feedbackStats();
      final Map<S, Int> feedbackStats2 = unnull(dm_gazelle_feedbackStatsByJudgement());
      final Map<S, Int> feedbackStats3 = unnull(dm_gazelle_feedbackStatsForNonJudged());
      
      // Main Page / Rules List
      
      S filterByPurpose = params.get('purpose);
      L<T3<S>> rules = empty(filterByPurpose)
        ? dm_allRulesFromRulesModuleWithCommentsAndIDs()
        : dm_gazelle_allRulesWithPurpose(filterByPurpose);
        // dm_gazelle_allRulesWithComment("discord");
        
      bool showAll = eq("1", params.get('showAll));
        
      L<Map> mapped = mapReversed(rules,
        func(T3<S> t) -> SS {
          S ruleID = t.c;
          ret litorderedmap(
            "Rule ID" := !showAll ? null : htmlEncode2(ruleID),
            "Rule Text" := ahref(ruleURL(ruleID), htmlEncode_nlToBr(t.a)),
            "Comments" := !showAll ? null : nlToBr(
              mapEachLine(/*withoutLine("discord",*/ t.b/*)*/, func(S s) -> S {
                new Matches m;
                if "use helper table mech list *"
                  ret "use helper table mech list " +
                    ahref(neatMechListURL($1), m.get(0));
                ret htmlEncode2(s);
              })),
            "# of applications" := strOr(feedbackStats3.get(ruleID), "-"),
            "Feedback" := ahref(rawSelfLink("rule/" + ruleID),
              "+" + toInt(feedbackStats2.get(ruleID + "/good"))
                /*+ " / " +
              toInt(feedbackStats3.get(ruleID))*/
                + " / " + 
              "-" + toInt(feedbackStats2.get(ruleID + "/bad"))));
        });
      
      S sortBy = params.get('sortBy);
      bool desc = eq("1", params.get('desc));
      if (nempty(sortBy))
        if (eq(sortBy, "Feedback"))
          mapped = sortByCalculatedField(mapped, func(Map map) -> Int {
            S s = cast map.get("Feedback");
            ret parseFirstInt(s) - parseSecondInt(s);
          });
        else if (eq(sortBy, "# of applications"))
          mapped = sortByMapKeyAlphaNum(mapped, sortBy);
        else
          mapped = sortByMapKey(mapped, sortBy);
      if (desc) reverseInPlace(mapped);
      
      int start = parseInt(params.get("start")), step = 50;
      S nav = pageNav2(hquery(mapMinus(params, 'start)), l(mapped), start, step, 'start,
        /*leftArrow := span(htmlencode2(unicode_leftPointingTriangle()), style := "font-family: Lucida Sans"),
        rightArrow := span(htmlencode2(unicode_rightPointingTriangle()), style := "font-family: Lucida Sans")*/);
      mapped = subList(mapped, start, start+step);
        
      ret hhtml_head_title_body(gazelle_name() + " - Next-Gen Chat Bot",
        hprelude() +
        hopeningTag link(rel :="icon", href := "/favicon.ico?v=2") +
        hcenter(
          p(b(ahref_unstyled("http://gazelle.botcompany.de", htmlEncode2(gazelle_fullName())), style := "font-size: 5em")) +
          p(hsnippetimg(gazelle_imageID(), height := 150, title := gazelle_name()))
          + greeting()
          + navLine()

          + h3("Rules " + (nempty(filterByPurpose) ? " for purpose " + quote(filterByPurpose) : "") + " (" + l(rules) + ")")
          + p("By purpose: " + joinWithVBar(map(listPlus_inFront(gazelle_allRulePurposes_cached(), 'default), purpose ->
            ahref(hquery(mapPlus(params, +purpose)), htmlEncode2(purpose)))))
          + nav
        + htmlTable2(mapped,
            htmlEncode := false,
            paramsByColName := litmap(
              "Feedback" := litobjectarray(align := 'center),
              "# of applications" := litobjectarray(align := 'center)),
            replaceHeaders := litmap(
              "Rule Text" := "Rule (input + more input => output)",
              "# of applications" := sortLink(params, "# of applications"),
              "Feedback" := sortLink(params, "Feedback")),
            tdParams := litobjectarray(valign := 'top)
            )
            + p(ahref(hquery(mapPlus(params, showAll := 1)), "Show technical stuff"))
            ), bodyParams);
    }
  
    S ruleLink(S ruleID) {
      if (!isGlobalID(ruleID)) ret htmlEncode2(ruleID);
      ret ahref("/rule/" + ruleID, ruleID);
    }
    
    S commentToHTML(S s) {
      new Matches m;
      LS tok = javaTok(s);
      S t = nextToLast(tok);
      S html = null;
      if (dm_gazelle_ruleExists(t))
        html = htmlEncode2(joinSubList(tok, 0, l(tok)-2)) + ruleLink(t);
      else if (matchX("... list *", s, m) && isQuoted(m.get(0)))
        html = htmlEncode2(joinSubList(tok, 0, l(tok)-2)) + ahref(neatMechListURL($1), t);
      if (html == null) html = htmlEncode2(s);
      ret html;
    }

    S gazelleTreeToHTML(virtual GazelleTree tree) {
      if (tree == null) ret "*";
      ret (getBool isSplitNode(tree) ? "[split] " : "")
        + htmlEncode2(getString line(tree)) + appendSquareBracketed(
        joinWithComma(listPlus(
          map htmlEncode2((LS) call(tree, "renderQualityElements")),
          ruleLink((S) call(tree, "ruleID")))));
    }
    
    S moreForMsgID(long msgID) {
      ret ahref(rawSelfLink("revisit/" + msgID), "revisit", rel := "nofollow");
    }
    
    S search(SS params) {
      S q = trim(params.get('q));
      Set<S> types = singletonCISetIfNempty(params.get('type));
      int maxResults = min(absoluteMaxResults, toIntOr(params.get('max), defaultMaxResults));
      L results = empty(q) ? null : dm_gazelle_fullSearch(q, +types, maxResults := maxResults+1);
      S title = empty(q) ? "Search" : "Search results for: " + q;
      ret hframe(title,
        hheading(htmlEncode2(title)) +
        hform("Terms: " + hinputfield('q, value := q, autofocus := true)
          + " Type: " + hselect_list(searchTypes, params.get('type), name := 'type)
          + " " + hsubmit("Search"), action := "/search") +
        (empty(results) ?
          (empty(q) ? "" : "No results") : p(
            l(results) > maxResults ? maxResults + "+ results" : n2(results, "result"))
            + htmlTable2(map(takeFirst_lazy(maxResults, results), o -> {
          S type = shortClassName(o);
          bool isLine = eq(type, "Line");
          long msgID = isLine ? getLong msgID(o) : 0;
          Map map = litorderedmap("Type" :=
            isLine ? spanTitle("Message ID: " + msgID, type) : type);
          S text = htmlEncode2OrNull(getStringOpt text(o));
          if (isLine)
   {
            map.put("Date" := localDateWithMinutes(getLong created(o)));
            map.put("By" := (S) call(o, 'userName));
          }
          if (text != null) {
            if (eq(type, "Rule"))
              text = ahref("/rule/" + getString globalID(o), text);
            else if (isLine)
              text = ahref(lineURL(msgID), text);
            mapPut(map, "Text", text);
          }
          if (isLine)
            map.put("More" := moreForMsgID(msgID));
          ret map;
        }), htmlEncode := false)));
    }

    S serveLine(S contextOrMsgID) {
      GazelleLine l = isInteger(contextOrMsgID)
        ? dm_discord_lineForMsgID(parseLong(contextOrMsgID))
        : gazelle_lineForContext(contextOrMsgID);
        
      if (l == null) ret "Line " + contextOrMsgID + " not found";
      L context = dm_discord_contextAroundLine(l.msgID, -3, 3);
      L applications = 
        nempty(l.context)
          ? gazelle_applicationsForContext(l.context)
          : dm_gazelle_applicationsForMsgID(l.msgID);
      saveFeedback(applications);
      L<GazelleFulfillment> fulfillments = dm_gazelle_fulfillmentsForMsg(l.msgID);
      
      File reasoningFile = gazelle_discord_reasoningFileForContext(l.context);
      bool hasRawReasoning = fileExists(reasoningFile);

      ret hframe("Line " + l.context,
        hheading("Line @ " + localDateWithMinutes(l.timestamp) + ": " + htmlEncode2("<" + l.user + "> " + l.text))
        + p(moreForMsgID(l.msgID))
        + (empty(context) ? "" :
          h3("Context")
          + nlToBr(lines(map(context, line2 -> {
            GazelleLine line = dm_discord_importLine(line2);
            S s = localDateWithMinutes(line.timestamp) + ": " + ahref(line.msgID == l.msgID ? null : lineURL(line.msgID),
              htmlEncode2("<" + line.user + "> " + line.text));
            ret line.msgID == l.msgID ? b(s) : s;
          }))))
        + (hasRawReasoning ? p(ahref("/rawReasoning/" + urlencode(l.context), "Raw reasoning.")) : "")
        + (empty(applications) ? "" : h3("Reasoning")
        + htmlTable2(map(applications, app -> {
            S ruleID = getString ruleID(app);
            Map map = new LinkedHashMap;
            if (nempty(ruleID)) {
              S ruleText = dm_textForRule(ruleID);
              map.put("Rule" := ahref("/rule/" + urlencode(ruleID),
                empty(ruleText) ? "?" : htmlEncode2_nlToBr(ruleText)));
            }
            map.putAll(applicationToMap(app, null, null));
            map.remove("Context");
            map.remove("Generated Output");
            ret map;
          }),
          tdParams := litobjectarray(align := 'center, valign := 'top), htmlEncode := false))
        + (empty(fulfillments) ? "" : h3("Fulfillments")
        + htmlTable2(map(fulfillments, f -> {
            new LinkedHashMap map;
            if (neq(f.line, l.text)) map.put("Originally seen line" := f.line);
            mapPut(map, "Rule" := f.rule);
            mapPut(map, "Judgement" := f.judgement);
            mapPut(map, "Mapping" := dropPrefix("cimap", f.varMap));
            ret map;
          }), tdParams := litobjectarray(align := 'center, valign := 'top), htmlEncode := false))
      );
    }
    
    S createRule() {
      S text = trim(params.get('text)), comments = params.get('comments);
      if (nempty(text)) {
        LS _comments = tlft(comments);
        _comments = map unSquareBracket(_comments);
        if (!authed)
          _comments = map(_comments, s -> "[anon] " + s);
        text = gazelle_processSquareBracketAnnotations(text, _comments);
        S ruleID = pairA(dm_gazelle_addRuleWithComment(text, lines_rtrim(_comments)));
        ret hredirect(ruleURL(ruleID));
      }
      
      S copyFrom = params.get('copyFrom);
      PairS p = empty(copyFrom) ? pair("in => out", "")
        : dm_textAndCommentForRule(copyFrom);
      if (nempty(p.b))
        p.b = appendWithNewLine(withoutLinesStartingWithIC("copied from rule ", p.b),
        "copied from rule " + copyFrom);
      
      ret hframeAndHeading("Create Rule",
        hpostform(
          h3("Rule")
          + htextarea(p.a,
            name := 'text, cols := 80, rows := 10) + "<br><br>" +
          h3("Comments")
          + htextarea(p.b, name := 'comments, cols := 80, rows := 10) +
          "<br><br>" +
          hsubmit("Create Rule")
        )
        + p(targetBlank("/commentPatterns", "Comment patterns.")));
    }
    
    O serveRuleApplication(S globalID) {
      O app = gazelle_applicationForID(globalID);
      if (app == null) ret serve404();
      S title = "Rule Application " + globalID;
      S mrStruct = getString matchedRuleStruct(app);
      O mr = safeUnstruct(mrStruct);
      ret hframe(title,
        hheading(title) +
        htmlTable2(map(objectToMap(mr), func(S field, O value) -> Map {
          litorderedmap("Field" := field, "Value" := sfu(value))
        })));
    }
  
    S serveRule(S ruleID) {
      // add rule comment
      
      S comment = trim(params.get("comment"));
      if (nempty(comment)) {
        if (!authed) comment = "[anon] " + comment;
        dm_gazelle_addRuleComments_verbose(ruleID, ll(comment));
      }
      
      // delete rule comment
      
      S del = params.get("deleteComment");
      if (authed && nempty(del))
        gazelle_removeCommentFromRule(ruleID, del);
  
      PairS textAndComment = unnull(dm_textAndCommentForRule(ruleID));
      L feedback = dm_gazelle_feedbackForRule(ruleID);
      L applications = dm_gazelle_applicationsForRule(ruleID);
      Map<S, O> feedbackByContext = indexByFieldNotNull context(feedback);
      L<GazelleFulfillment> fulfillments = dm_gazelle_fulfillmentsForRule_imported(ruleID);
      L list = cloneList(feedback);
      for (O o : applications)
        if (!containsKey(feedbackByContext, getString context(o)))
          list.add(o);
          
      // add feedback comments + judgement
      
      for (S applicationID, S text : subMapStartingWith_dropPrefix(params, "comment_")) {
        continue if empty(text = trim(text));
        if (!authed) text = "[anon] " + text;
        O app = findByField(list, globalID := applicationID);
        print("Processing comment for " + applicationID + " - " + app);
        if (app == null) continue;
        Set<S> comments = asSet(tlft(getString comments(app)));
        if (!contains(comments, text))
          call(app, '_setField, comments := appendWithNewLine(getString comments(app), text));
      }
      
      saveFeedback(list);
      
      // change rule text
      
      S newRuleText = params.get('ruleText);
      if (authed && nempty(newRuleText)) {
        LS _comments = tlft(textAndComment.b);
        newRuleText = gazelle_processSquareBracketAnnotations(newRuleText, _comments);
        gazelle_setRuleText(ruleID, newRuleText);
        gazelle_setRuleComment(ruleID, lines_rtrim(_comments));
        textAndComment = unnull(dm_textAndCommentForRule(ruleID));
      }
      
      if (authed && nempty(params.get('expandMultiRule))) {
        gazelle_splitMultiRule(ruleID);
        textAndComment = unnull(dm_textAndCommentForRule(ruleID));
      }

      // Serve Rule & Applications & Feedback
      
      GazelleEvalContext ctx = dm_gazelle_evalContextForSingleRule(ruleID);
      GazellePredictor predictor = dm_gazelle_standardPredictor();
      
      L<Map> mapped = map(list, app -> applicationToMap(app, ctx, predictor));
      
      S sortBy = params.get('sortBy);
      if (nempty(sortBy))
        mapped = sortByMapKey(mapped, sortBy);
        
      MultiSet<S> statements = dm_gazelle_statementsForRule_multiSet(ruleID);
      
      LS comments = tlft(textAndComment.b);
      
      // warnings
      Collection<S> warnings = null;
      pcall {
        warnings = (Collection<S>) dm_call('gazelleThoughtHelper, 'autoTestRule, ruleID);
      }
  
      // comment suggestions
      Collection<S> suggestedComments = null;
      pcall {
        //suggestedComments = gazelle_suggestRuleComments(TextCommentsID(textAndComment.a, textAndComment.b, ruleID));
        suggestedComments = (Collection<S>) dm_findAndCall("#1022585/GRuleCommentSuggester", 'suggestComments, textAndComment.a, textAndComment.b, ruleID);
        suggestedComments = listMinusSet(suggestedComments, comments);
      }
      
      S title = "Rule " + ruleLink(ruleID) + ": " + htmlEncode2(newLinesToSpaces_trim(textAndComment.a));
      ret hframe(htmldecode_dropAllTags(title),
        form(p(searchBox2(), style := "text-align: right"), action := "/search")
        + hheading(title)
        + hpostform(
          h3("Rule")
        + div(hblock(textAndComment.a), id := 'ruleText)
        + (authed ? "<br>" + hbuttonOnClick_returnFalse("Edit",
          [[
            $(this).text('Save');
            $(this).attr('onclick', '');
            $('#ruleText').html(]] + jsQuote(htextarea(textAndComment.a, name := 'ruleText, rows := 5, cols := 80)) + [[);
            $('#ruleText textarea').focus();]]) : "")
            
        // Render comments
        
        + (nempty(textAndComment.b) ? h3("Comments") + nlToBr(mapEachLine(textAndComment.b, s -> {
          S html = commentToHTML(s);
          if (authed)
            html += " " + ahref(ruleURL(ruleID) + hquery(deleteComment := s), "[x]");
          ret html;
        })) : "")
        + "<br><br>"
        + "Add comment: " + htextfield("comment") + " " + hsubmit())
        
        // Render warnings
        
        + (empty(warnings) ? ""
          : h3("Warnings") +
          nlToBr(lines(map(f<S, S> commentToHTML, warnings))))
        
        // Render suggested comments
        
        + (empty(suggestedComments) ? ""
          : h3("Suggested comments") +
            nlToBr(lines(map(suggestedComments, c ->
              htmlEncode2(c) + " " + ahref(ruleURL(ruleID) + hquery(comment := c), "[accept]")))))

        + p(ahref("/createRule" + hquery(copyFrom := ruleID), "Duplicate rule.")
          + " " + targetBlank("/commentPatterns", "Comment patterns."))
        
        + (authed && cic(comments, "multi-rule")
          ? hpostform(hsubmit("Deploy Multi-Rule") + hhidden(expandMultiRule := 1))
          : "")
        
        // Serve Feedback
        
        + (empty(mapped) ? "" : h3("Feedback")
        + htmlTable2(mapped,
          tdParams := litobjectarray(align := 'center, valign := 'top),
          htmlEncode := false)
)
        // Serve Fulfillments
        
        + (empty(fulfillments) ? "" : h3("Fulfillments")
        + htmlTable2(map(fulfillments, f -> {
            new LinkedHashMap map;
            mapPut(map, "Line" := ahref(lineURL(contextToMsgID(f.context)), htmlEncode2_nlToBr(f.line)));
            mapPut(map, "Judgement" := f.judgement);
            mapPut(map, "Mapping" := dropPrefix("cimap", f.varMap));
            mapPut(map, "Context" := f.context);
            ret map;
          }),
          tdParams := litobjectarray(align := 'center, valign := 'top),
          paramsByColName := litmap("Line" := litparams(align := 'left)),
          htmlEncode := false))
        + (empty(statements) ? "" : h3("Analysis 1")
        + hblock(multiSetToLines(statements))
)
        + (empty(statements) ? "" : h3("Analysis 2")
        + hblock(sfuLines(mapWithout(objectToMap(
          ai_gazelle_analyzeStatementsForRule_multiSet(statements)), 'statements)))));
    }
    
    S serveWikipedia() {
      /*hheading("All Simple English Wikipedia Topics")
        + htmlTable2(map(simpleWikipediaTopics_cached(), topic -> litorderedmap("Topic" := topic))));*/
      ret hframe("Wikipedia",
        hheading("Retrieved Simple Wikipedia Pages")
        + htmlTable2(map(simpleWikipedia_allCachedTopics(), topic -> litorderedmap("Topic" := topic))));

    }

    Map applicationToMap(O o, GazelleEvalContext ctx, GazellePredictor predictor) {
      bool showStruct = eq("1", params.get('struct));
      S prediction = null;
      if (ctx != null && predictor != null) {
        prediction = "?";
        pcall {
          RuleEngine2_MatchedRule mr = cast unstruct(getString matchedRuleStruct(o));
          if (mr != null)
            prediction = or(predictor.get(ctx, mr), prediction);
        }
      }
      
      S judgement = getString judgement(o);
      S globalID = getString globalID(o);
      S context = getString context(o);

      ret litorderedmap(
        "Judgement" := nempty(judgement) || !authed ? htmlEncode2(judgement) :
          ahref(uri + hqueryWithoutNanoHTTPDStuff(mapPlus(params, "judgeFeedback_" + globalID, 'good)), "mark good") + " " +
          ahref(uri + hqueryWithoutNanoHTTPDStuff(mapPlus(params, "judgeFeedback_" + globalID, 'bad)), "mark bad"),
        "Prediction" := prediction,
        "Generated Output" := getString_htmlEncode outText(o),
        "Rewritten Rule" := ahref("/ruleApplication/" + globalID, getString_htmlEncode modifiedRule(o)),
        "Mapping" := dropPrefix("cimap", getString_htmlEncode varMap(o)),
        "Context" := ahref(lineURL(context), htmlEncode2(context)),
        "Comments" := hform(
          appendIfNempty(nlToBr(rtrim(getString_htmlEncode comments(o))), "<br>")
          + htextfield("comment_" + globalID)),
        "Struct" := showStruct ? getString matchedRuleStruct(o) : null);
    }
    
    S serveRuleComments() {
      ret hframe("All Rule Comments",
        hheading("All Rule Comments")
        + htmlTable2(map(dm_gazelle_allRuleComments(), comment -> litorderedmap("Comment" := comment))));
    }
    
    S hframeAndHeading(S title, O body) {
      ret hframe(title, hheading(htmlEncode2(title)) + body);
    }

    // html-encoded title
    S hframeAndHeading2(S title, O body) {
      ret hframe(htmldecode_dropAllTags(title),
        hheading(title) + body);
    }

    S hframe(S title, O body) {
      //print("Params: " + sfu(bodyParams));
      ret hhtml_head_title_body(title + " | " + htmlEncode2(gazelle_name()),
        loadJQuery() +
        hprelude() +
        body,
        bodyParams);
    }
    
    S searchBox() {
      ret hinputfield('q, style := "width: 120px", autofocus := true)
        + " " + hselect_list(searchTypes, "Rule", name := 'type)
        + " " + hsubmit("Search");
    }
    
    S searchBox2() {
      ret "[search] " + hinputfield('q, style := "width: 120px")
        + " " + hselect_list(searchTypes, "Rule", name := 'type);
    }
    
    void saveFeedback(L list) {
      if (authed) for (S applicationID, S judgement : subMapStartingWith_dropPrefix(params, "judgeFeedback_")) {
        // TODO: copy to feedback module
        continue if !eqOneOf(judgement, 'good, 'bad);
        O app = findByField(list, globalID := applicationID);
        if (dm_isConceptOfModule(app, dm_gazelle_longFeedbackCRUD())) {
          print("Migrating to feedback CRUD");
          app = dm_call(dm_gazelle_feedbackCRUD(), 'uniqConcept, (O) getFieldsAsParams(app, conceptFields_gen(app)));
        }
        print("Judging feedback for " + applicationID + " - " + app);
        call(app, '_setField, +judgement);
      }  
    }
    
    S serveTestInput() {
      S input = params.get('input);
      L<virtual GazelleTree> out = null;
      new L<virtual GInterpretable> interpretables;
      
      if (nempty(input)) {
        // Hopefully safe!
        
        O inputTester = vmBus_query('gazelleInputTester);
        if (inputTester != null)
          out = (L) dm_call(inputTester, 'reason, input,
            interpretables_out := listCollector(interpretables),
            noEvals := true);

        /*out = gazelle_reason_repeat(input,
          interpretables_out := listCollector(interpretables),
          noEvals := true,
          contextMaker := contextCache);*/
      }
        
      ret hframeAndHeading("Test Input",
        hform("Input: " + htextfield('input)
        + " " + hsubmit("Test"))
        + (empty(input) ? "" : p("Processed input: " + b(htmlEncode2(input)))
        + (empty(interpretables) ? "" :
          h3("Results of preprocessing")
          + htmlTable2(
            map(interpretables, func(virtual GInterpretable intp) -> Map {
              litorderedmap(
                "Text" := htmlEncode2(getString text(intp)),
                "Rule" := ruleLink(getString ruleID(intp)))
            }), htmlEncode := false))
        + h3("Output")
        + htmlTable2(
          map(out, func(virtual GazelleTree t) -> Map {
            litorderedmap(
              "Output" := htmlEncode2(getString line(t)),
              "Rule" := ruleLink(getString ruleID(t)),
              "Quality" := htmlEncode2(joinWithComma((LS) call(t, 'renderQualityElements))))
          }
        ), htmlEncode := false)));
    }

    S greeting() {
      ret p(span("<b>Hello! I am a next-generation chat bot in training.</b>", style := "font-size: 1.4em")
        + (!gazelle_isOriginal() ? "" : "<br>"
          + span(targetBlank("https://BotCompany.de", "Maker.") + " "
          + targetBlank("https://slides.com/stefanreich/how-about-thinking-machines/", "Slides.")
          + " "
          + targetBlank("https://discordapp.com/invite/SEAjPqk", b("Join my Discord server!")), style := "font-size: 1.2em")));
    }
    
    S navLine(O... _) {
      bool showAll = eq("1", params.get('showAll));
        
      ret hform(p(ahref(rawSelfLink("commands"), "Commands.")
      + " " + ahref(rawSelfLink("lastInput"), "Last input.")
      + " " + ahref(rawSelfLink("lastOutput"), "Last output.")
      + " " + ahref(rawSelfLink("testInput"), "Test input.")
      + " " + ahref("/tests", "Tests.")
      + stringParOrEmpty more(_)
      + (authed ? " " + ahref(rawSelfLink("createRule"), "Create rule.") : "")
      + (!showAll ? "" : " " + ahref(rawSelfLink("ruleComments"), "Rule comments."))
      + " | " + searchBox()), action := "/search");
    }
    
    S testsModule() { ret gazelle_testsModule(); }
    
    O serveTests() {
      L tests = cast dm_call(testsModule(), 'concepts);
      
      S show = params.get('test);
      S runTest = params.get('runTest);
      if (nempty(runTest)) {
        show = runTest;
        dm_call(testsModule(), 'runTestAndWait, runTest);
      }
      
      O showing = empty(show) ? null : firstWhere(tests, name := show);
      S showingName = getString name(showing);
      
      Pair<Int> stats = cast quickImport(dm_callOpt(testsModule(), 'countAndSuccesses));
      
      bool running = isTrue(dm_getOpt(testsModule(), 'running));
      ret hframeAndHeading2(ahref("/tests", "Tests")
        + (empty(showingName) ? "" : htmlEncode2(" => ") + ahref("/tests" + hquery(test := showingName), htmlEncode2(showingName))),
        (running ? p(tag('blink, "TESTS RUNNING")) : "")
        + (stats == null ? "" : p(n2(stats.a, "test") + ", " + stats.b + " OK"))
        + hSingleRowTable_withSpacing(50, 
            htmlTable2(map(tests, test -> litorderedmap(
            "Test" := ahref(hquery(test := getString name(test)), htmlEncode2(getString name(test))),
            "Last Run" := localDateWithMinutes(getLong lastRun(test)),
            //"Duration" := getLong runtime(test) + " ms",
            "Success" := yesNo2(getBool success(test)),
            "Any Success Ever" := yesNo2(getBool anySuccess(test)))
          ), htmlEncode := false),
          
          (showing == null ? null :
            h3(htmlEncode2("Test " + quote(showingName) + " [" + (getBool success(showing) ? "OK" : "FAILED") + "]"))
          + hpostform(hsubmit("Run Test"), action := hquery(runTest := showingName))
          + htmlEncode_nlToBr(getString contents(showing)))
      ));
    }
    
    // end of Request
  }
} // end of class

sS rawSelfLink(S uri) { ret addSlashPrefix(uri); }

sS hheading(O contents) {
  ret h2(ahref(rawSelfLink(""), htmlEncode2(gazelle_name())) + " | " + contents);
}

sS hprelude() {
  ret [[<link href="https://fonts.googleapis.com/css?family=Roboto+Mono" rel="stylesheet">]] +
    hmobilefix();
}

sS hblock(S s) {
  ret htmlEncode_nlToBr(s);
}

sS sortLink(SS params, S key) {
  params = cloneMap(params);
  mapPutOrRemove(params, 'desc,
    eq(params.get('sortBy), key) && !eq("1", params.get('desc)) ? "1" : null);
  params.put(sortBy := key);
  ret ahref(hquery(params), htmlEncode2(key));
}

static L<GazelleLine> selectLines(SS params) {
  long modifiedAfter = getLong modifiedAfter(params);
  if (modifiedAfter != 0) ret dm_discord_linesModifiedAfter(modifiedAfter);
  ret dm_discord_allLines();
}

Author comment

Began life as a copy of #1021679

download  show line numbers  debug dex  old transpilations   

Travelled to 10 computer(s): bhatertpkbcr, cfunsshuasjs, gwrvuhgaqvyk, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, whxojlpjdney, xrpafgyirdlv

No comments. add comment

Snippet ID: #1022531
Snippet name: DynGazelleWebServer
Eternal ID of this version: #1022531/71
Text MD5: 89da73adad5c94b5016b81f07d57a3b0
Transpilation MD5: a7b233a676dfa9bebea22fe836cd2d46
Author: stefan
Category: javax / stefan's os / a.i. / web
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2020-05-19 17:33:46
Source code size: 37312 bytes / 909 lines
Pitched / IR pitched: No / No
Views / Downloads: 369 / 1298
Version history: 70 change(s)
Referenced in: #1021679 - Gazelle Web Server [LIVE at http://gazelle.botcompany.de, Dyn Module]
#1034167 - Standard Classes + Interfaces (LIVE, continuation of #1003674)