// Note: use module, not cmodule - we want our own structure() because of DynamicObject set flag DynModule. abstract sclass DynGazelleWebServer > DynPrintLogAndEnabled { new Set 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 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 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 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 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 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 tree = evalWithTimeoutOrNull(30.0, func -> L { /*dm_gazelle_revisitChatLine(msgID, contextMaker := contextCache, skipBad := false)*/ (L) dm_call('gazelleLineRevisiter, 'revisitChatLine, msgID, skipBad := false) }); long endTime = sysNow(); S tv = lines(map(f 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 feedbackStats = dm_gazelle_feedbackStats(); final Map feedbackStats2 = unnull(dm_gazelle_feedbackStatsByJudgement()); final Map feedbackStats3 = unnull(dm_gazelle_feedbackStatsForNonJudged()); // Main Page / Rules List S filterByPurpose = params.get('purpose); L> rules = empty(filterByPurpose) ? dm_allRulesFromRulesModuleWithCommentsAndIDs() : dm_gazelle_allRulesWithPurpose(filterByPurpose); // dm_gazelle_allRulesWithComment("discord"); bool showAll = eq("1", params.get('showAll)); L mapped = mapReversed(rules, func(T3 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 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 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) + "

" + h3("Comments") + htextarea(p.b, name := 'comments, cols := 80, rows := 10) + "

" + 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 feedbackByContext = indexByFieldNotNull context(feedback); L 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 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 mapped = map(list, app -> applicationToMap(app, ctx, predictor)); S sortBy = params.get('sortBy); if (nempty(sortBy)) mapped = sortByMapKey(mapped, sortBy); MultiSet statements = dm_gazelle_statementsForRule_multiSet(ruleID); LS comments = tlft(textAndComment.b); // warnings Collection warnings = null; pcall { warnings = (Collection) dm_call('gazelleThoughtHelper, 'autoTestRule, ruleID); } // comment suggestions Collection suggestedComments = null; pcall { //suggestedComments = gazelle_suggestRuleComments(TextCommentsID(textAndComment.a, textAndComment.b, ruleID)); suggestedComments = (Collection) 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 ? "
" + 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; })) : "") + "

" + "Add comment: " + htextfield("comment") + " " + hsubmit()) // Render warnings + (empty(warnings) ? "" : h3("Warnings") + nlToBr(lines(map(f 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))), "
") + 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 out = null; new L 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("Hello! I am a next-generation chat bot in training.", style := "font-size: 1.4em") + (!gazelle_isOriginal() ? "" : "
" + 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 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 [[]] + 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 selectLines(SS params) { long modifiedAfter = getLong modifiedAfter(params); if (modifiedAfter != 0) ret dm_discord_linesModifiedAfter(modifiedAfter); ret dm_discord_allLines(); }