!7 cmodule GazelleWebServer > DynPrintLogAndEnabled { transient MyHTTPD server; new Set authedCookies; transient new GazelleContextCache contextCache; transient new ThreadLocal bodyParams; transient new ThreadLocal authed; int absoluteMaxResults = 1000, defaultMaxResults = 100; void start() ctex { super.start(); if (!enabled) ret; dm_useLocalMechListCopies(); server = new MyHTTPD(80); 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 { new Matches m; S cookie = serveHttp_cookieHandling(); bool authed = nempty(cookie) && syncContains(authedCookies, cookie); temp tempSetTL(this.authed, authed); print("Cookie: " + cookie + ", authed: " + authed); 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"); } if (eq(uri, "/favicon.ico")) ret serveFile(loadLibrary(#1400189), faviconMimeType()); O[] bodyParams = litobjectarray(style := "font-family: Roboto Mono; " + (authed ? "background-color: #FFFF88" : "")); temp tempSetTL(this.bodyParams, bodyParams); if (eq(uri, "/login")) ret hhtml_head_title_body("Login" + " | Gazelle", hprelude() + (nempty(master) ? hpostform(hpasswordfield('_pass)) : "No master PW"), bodyParams); if (swic(uri, "/texts/", m)) { S textID = m.rest(); if (!possibleMD5(textID)) ret serve404(); ret serveTextFileAsUTF8(javaxDataDir("Gazelle Texts/" + textID)); } 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:" := htmlencode2(line.text), "Applied rules" := joinWithSpace(map(f ruleLink, collect ruleID(applications))) ); }), htmlEncode := false, tableParams := litparams(cellpadding := 5)) )); } 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" := htmlencode(line.user), "Bot?" := htmlencode(line.bot ? "yes" : ""), "Line" := htmlencode2(line.text), //"Applied rules" := joinWithSpace(map(f ruleLink, collect ruleID(applications))), "More" := moreForMsgID(line.msgID) ); }), htmlEncode := false, tableParams := litparams(cellpadding := 5)) )); } 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 = dm_gazelle_revisitChatLine(msgID, ctx := contextCache!); long endTime = sysNow(); //print("Tree to string."); S tv = treeViewToString_withoutHijackPrint(gazelle_treeView(tree), stringifier := func(TreeView t) -> S { gazelleTreeToHTML(t.node()) }); //print("children=" + l(tree) + ", tv = " + quote(tv)); ret hframe("Revisiting msg " + msgID, hheading("Revisiting message " + msgID + ": " + htmlEncode2(line.text)) + nlToBr_withIndents(tv) + p((endTime-startTime) + " ms")); } Request request = new(authed, params); if (swic(uri, "/rule/", m)) { S ruleID = assertGlobalID(m.rest()); ret request.serveRule(ruleID, params); } if (eq(uri, "/search")) ret search(params); if (eq(uri, "/wikipedia")) ret request.serveWikipedia(); if (eq(uri, "/commands")) ret hhtml_head_title_body("Commands | Gazelle", hprelude() + 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).")), bodyParams); if (eq(uri, "/safeIdentifiers")) ret hhtml_head_title_body("Safe Java Identifiers | Gazelle", hprelude() + linesLL( hheading("Safe Java(X) identifiers for !eval"), hpre(lines(sortedIC(codeAnalysis_allSafeIdentifiers())))), bodyParams); if (!eq(uri, "/")) ret serve404(); //final Map feedbackStats = dm_gazelle_feedbackStats(); final Map feedbackStats2 = dm_gazelle_feedbackStatsByJudgement(); final Map feedbackStats3 = dm_gazelle_feedbackStatsForNonJudged(); // Home Page / Main List L> rules = dm_allRulesFromRulesModuleWithCommentsAndIDs(); // 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" := 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 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); ret hhtml_head_title_body("Gazelle - Next-Gen Chat Bot", hprelude() + hopeningTag link(rel :="icon", href := "/favicon.ico?v=2") + hcenter( p(b("GAZELLE 2019", style := "font-size: 5em")) + p(hsnippetimg(#1101500, height := 150, title := "Gazelle")) + p(span("Hello! I am a next-generation chat bot in training.", style := "font-size: 1.4em") + "
" + 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")) + hform(p(ahref(rawSelfLink("commands"), "Commands.") + " " + ahref(rawSelfLink("lastOutput"), "Last output.") //+ " " + ahref(rawSelfLink("search"), "Search.") + " | " + hinputfield('q, style := "width: 120px") + " " + hsubmit("Search")), action := "/search") + h3("Rules (" + l(rules) + ")") + 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(hqueryWithoutNanoHTTPDStuff(mapPlus(params, showAll := 1)), "Show technical stuff")) ), bodyParams); } S hframe(S title, O body) { print("Params: " + sfu(bodyParams!)); ret hhtml_head_title_body(title + " | Gazelle", hprelude() + body, bodyParams!); } S ruleLink(S ruleID) { if (!isGlobalID(ruleID)) ret htmlEncode2(ruleID); ret ahref("/rule/" + ruleID, ruleID); } S gazelleTreeToHTML(GazelleTree tree) { if (tree == null) ret "*"; ret (tree.isSplitNode ? "[split] " : "") + htmlEncode2(tree.line) + appendSquareBracketed( joinWithComma(listPlus( map htmlEncode2(tree.renderQualityElements()), ruleLink(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(ll("", "Rules", "Lines"), 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); mapPut(map, "Text", text); } if (isLine) map.put("More" := moreForMsgID(msgID)); ret map; }), htmlEncode := false))); } class Request { bool authed; SS params; *(bool *authed, SS *params) {} S serveRule(S ruleID, SS params) { // add rule comment S comment = trim(params.get("comment")); if (nempty(comment)) { if (!authed) comment = "[anon] " + comment; dm_gazelle_addRuleComments_verbose(ruleID, ll(comment)); } PairS textAndComment = unnull(dm_textAndCommentForRule(ruleID)); L feedback = dm_gazelle_feedbackForRule(ruleID); L applications = dm_gazelle_applicationsForRule(ruleID); Map feedbackByContext = indexByFieldNotNull context(feedback); S title = "Rule " + ruleID; bool showStruct = eq("1", params.get('struct)); L list = cloneList(feedback); for (O o : applications) if (!containsKey(feedbackByContext, getString context(o))) list.add(o); // add feedback comments 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)); } // Serve Rule & Applications & Feedback L mapped = map(list, func(O o) -> Map { litorderedmap( "Judgement" := getString judgement(o), "Rewritten Rule" := getString_htmlEncode modifiedRule(o), "Generated Output" := getString_htmlEncode outText(o), "Mapping" := dropPrefix("cimap", getString_htmlEncode varMap(o)), "Context" := getString_htmlEncode context(o), "Comments" := hform( appendIfNempty(nlToBr(rtrim(getString_htmlEncode comments(o))), "
") + htextfield("comment_" + getString globalID(o))), "Struct" := showStruct ? getString matchedRuleStruct(o) : null) }); S sortBy = params.get('sortBy); if (nempty(sortBy)) mapped = sortByMapKey(mapped, sortBy); Collection statements = dm_gazelle_statementsForRule(ruleID); ret hhtml_head_title_body(title + " | Gazelle", hprelude() + hheading(htmlEncode2(title)) + h3("Rule") + hblock(textAndComment.a) + (nempty(textAndComment.b) ? h3("Comments") + hblock(textAndComment.b) : "") + "

" + hform( "Add comment: " + htextfield("comment")) + h3("Feedback") + htmlTable2(mapped, tdParams := litobjectarray(align := 'center, valign := 'top), htmlEncode := false) + h3("Analysis 1") + hblock(lines(statements)) + h3("Analysis 2") + hblock(sfuLines(objectToMap( ai_gazelle_analyzeStatementsForRule(statements)))) , bodyParams!); } 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)))); } } // end of Request } // end of class sS rawSelfLink(S uri) { ret addSlashPrefix(uri); } sS hheading(O contents) { ret h2(ahref(rawSelfLink(""), "Gazelle") + " | " + 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(hqueryWithoutNanoHTTPDStuff(params), htmlEncode2(key)); }