!7 concept Screen { S screenID; long lastSeen = now(); S query; transient S status; // the ongoing search (if any) transient LC_ParallelSearch search; transient Lock lock = lock(); toString { ret "Screen " + screenID; } } cmodule2 BEALiveInput > DynPrintLogAndEnabled { switchable int statusUpdateInterval = 100; transient Map webSockets = syncWeakHashMap(); transient Set liveSearches = weakHashSet(); start { dbIndexing(Screen, 'screenID); dm_doEvery(statusUpdateInterval, r updateStati); } set flag NoNanoHTTPD. O html(IWebRequest request) { try { //try object redirectToHTTPS(request); S uri = request.uri(); SS params = request.params(); print(+params); S cookie = request.cookie(); print("cookie", cookie); new Matches m; if (eqic(uri, "/about")) ret serveText(unindent_mls([[ What is the BEA Live Input? Bla bla bla. Also, bla. ]])); if (eqic(uri, "/websockettest")) { ret htitle("Web Socket Test") + hreconnectingWebSockets() + hloadjquery() + hdiv("bla
", id := "dynamic", style := "font-size: 40px") + hjavascript([[ var ws = new ReconnectingWebSocket("wss://wikify.live/test"); ws.onopen = function(event) { ws.send("here i am"); $("#dynamic").append("Streaming...
"); }; ws.onmessage = function(event) { $("#dynamic").html(event.data); document.title = event.data; }; ]]); } // home page S query = ""; if (swic(uri, "/search/", m)) query = takeFirst(1000, urldecode(m.rest())); else // now we actually accept wikify.live/{any search term} query = urldecode(dropSlashPrefix(uri)); ret hhtml(hmobilefix() + hhead( htitle("BEA Live Input [dev.]") + hsansserif() + hstyle([[ .queryContainer { height: 100%; width: 100%; display: flex; flex-flow: column; } input.query { font-size: 40px; padding: 20px; width: 400px; height: 2em; position: relative; left: 50%; transform: translate(-50%, 0); flex: .01 1 auto; } .filler { flex: .5 1 auto; } .filler.ontop { flex: .01 1 auto; } input.query.ontop { transform: translate(-50%, 0); margin-top: 20px; font-size: 20px; } .searching { display: none; } .searching.show { display: block; position: relative; left: 50%; margin-left: -45px; } .results { font-family: monospace; white-space: pre; /* no word-wrapping */ overflow: auto; /* scrollbars */ margin-top: 20px; border: 2px solid black; padding: 0px; display: block; flex: .01 1 auto; opacity: 0%; width: 1px; margin-left: auto; margin-right: auto; } .results.ontop { padding: 20px; flex: 1 0 0; opacity: 100%; width: calc(100% - 50px); left: 0; transform: none; } .about { margin-bottom: 10px; font-size: 12px; text-align: center; margin-left: auto; margin-right: auto; } .status { display: none; margin-top: 10px; font-size: 12px; } .status.ontop { display: block; margin-left: auto; margin-right: auto; } .query, .results, .filler { transition: all 1s; } ]]) + hLoadJQuery2() + hreconnectingWebSockets()) + hbody(hdiv( hdiv("", class := "filler") + hdiv("AI bla bla
" + "By " + ahref("https://BotCompany.de", "BotCompany") + "." + targetBlank("/about", "More info."), class := "about") + hinput("", value := query, autofocus := true, class := "query", placeholder := "TYPE ANYTHING") + hdiv("", class := "status") + hdiv( hdiv("", class := "actualResults") + hdiv(himgsnippet(#1102949, title := "Searching..."), class := "searching"), class := "results") + hdiv("", class := "filler"), class := "queryContainer") + hjs_urlencode() + hjs([[ function queryUpdate() { var sel = $("input.query, .queryContainer, .results, .filler, .status"); var q = $("input.query").val(); if (q == "") { $(".actualResults").html(""); sel.removeClass("ontop"); } else { sel.addClass("ontop"); } if (history.replaceState) history.replaceState(null, "Wikify.LIVE", q == "" ? "/" : "/search/" + urlencode(q)); sendQuery(); } $("input.query").on('change keydown paste input', queryUpdate); var screenID = Math.random().toString(36).substr(2, 9); var wsURL = "wss://wikify.live/search?screen=" + urlencode(screenID); console.log("WS: " + wsURL); var ws = new ReconnectingWebSocket(wsURL); var wsReady = false; var querySent = ""; var maxQueryLength = 1000; function wsSend(text) { if (wsReady) { console.log("wsSend: " + text); ws.send(text); } else console.log("wsNotReady: " + text); } function sendQuery() { var q = $("input.query").val().substring(0, maxQueryLength); if (querySent != q) { querySent = q; wsSend(JSON.stringify({ q: q })); } } ws.onopen = function(event) { wsReady = true; ws.onmessage = function(event) { var text = event.data; console.log("Got text: " + text); var data = JSON.parse(text); if ('clearResults' in data) $(".actualResults").html(""); if ('searching' in data) $(".searching").addClass("show"); if ('result' in data) $(".actualResults").append(data.result + "
"); if ('searchCancelled' in data) $(".searching").removeClass("show"); if ('searchDone' in data) { $(".actualResults").append("
[SEARCH DONE]
"); $(".searching").removeClass("show"); } if ('status' in data) $(".status").html(data.status); }; queryUpdate(); }; ]]) )); } catch e { printStackTrace(e); throw rethrow(e); } } class Request { S cookie; } void cleanMeUp_webSockets { for (virtual WebSocket ws : cloneAndClearKeys(webSockets)) { print("Closing WebSocket"); close((AutoCloseable) ws); } } void cleanMeUp_searches { for (virtual WebSocket ws : cloneAndClearList(liveSearches)) cancelSearch_noLock(webSockets.get(ws)); } // API for Eleu void setEleu(O eleu) { setOptMC(mainBot := eleu); } void handleWebSocket(virtual WebSocket ws) { print("Got new websocket!"); set(ws, onClose := r { webSockets.remove(ws) }); set(ws, onOpen := rEnter { S uri = cast rcall getUri(ws); print("WebSocket opened! uri: " + uri); SS params = cast rcall getParms(ws); S screenID = takeFirst(params.get("screen"), 100); Screen screen = uniq Screen(+screenID); screen.status = null; webSockets.put(ws, screen); cset(screen, lastSeen := now()); print("Screen ID: " + screenID); setFieldToIVF1Proxy(ws, onMessage := msg -> { temp enter(); pcall { S text = cast rcall getTextPayload(msg); print("Got msg: " + text); Map json = jsonDecodeMap(text); S q = upper((S) json.get("q")); if (q != null && neq(screen.query, q)) { temp tempLock("start search", screen.lock); cancelSearch_noLock(screen); cset(screen, query := q); if (nempty(q)) startSearch_noLock(ws, screen, q); } } }); if (eq(uri, "/test")) thread { repeat 100 { call(ws, 'send, "It is " + localTimeWithMilliseconds()); sleep(100); } } if (eq(uri, "/search")) { } }); } void cancelSearch_noLock(Screen screen) { if (screen == null) ret; close(screen.search); screen.search = null; } void startSearch_noLock(virtual WebSocket ws, Screen screen, S query) { cset(screen, +query); call(ws, "send", jsonEncode(litorderedmap("clearResults", true))); if (empty(query)) ret; call(ws, "send", jsonEncode(litorderedmap("searching", true))); print("Starting search for " + quote(query) + " for " + screen); /*LC_ParallelSearch search = new(searchers, query); screen.search = search; liveSearches.add(ws); search.enter = lambda0 enter; search.maxResults = defaultResults; search.onResult = (iSearcher, iFile, position) -> { temp tempLock("onResult", screen.lock); if (screen.search != search) ret; try { //print("Sending result"); S status = search.status_html(); call(ws, "send", jsonEncode(litorderedmap( result := renderResult(iSearcher, iFile, position, query, screen.search.startTime), +status))); //print("Sent result"); } catch print e { cancelSearch_noLock(screen); } }; search.onSearchDone = r { liveSearches.remove(ws); print("Search done. " + screen.search + " / " + search); temp tempLock("onSearchDone", screen.lock); if (screen.search != search) ret; updateStatus(ws, screen); print("Cancelling search"); cancelSearch_noLock(screen); print("Sending searchDone"); call(ws, "send", jsonEncode(litorderedmap( search.completed() ? "searchDone" : "searchCancelled", true))); print("Sent searchDone"); }; search.start();*/ } void updateStati enter { for (virtual WebSocket ws : cloneList(liveSearches)) pcall { Screen screen = webSockets.get(ws); updateStatus(ws, screen); } } void updateStatus(virtual WebSocket ws, Screen screen) { if (screen == null) ret; /*LC_ParallelSearch search = screen.search; if (search == null) ret; S status = search.status_html(); if (neq(status, screen.status)) { screen.status = status; call(ws, "send", jsonEncode(litorderedmap(status := screen.status))); }*/ } }