sO thoughtBot; static int longPollTick = 100; static int longPollMaxWait = 1000*60; sclass Msg { long time; bool fromUser; S text; L<S> buttons; *() {} *(bool *fromUser, S *text) { time = now(); } } concept Conversation { S cookie; new LL<Msg> oldDialogs; new L<Msg> msgs; void add(Msg m) { msgs.add(m); change(); } int allCount() { ret lengthLevel2(oldDialogs) + l(msgs); } int archiveSize() { ret lengthLevel2(oldDialogs); } } svoid pWebChatBot { db(); thoughtBot = runDependent(thoughtBotID); Class envType = fieldType(thoughtBot, "env"); if (envType != null) setOpt(thoughtBot, "env", proxy(envType, (O) mc())); } static void sayAsync(S session, S text) { lock dbLock(); Conversation conv = getConv(session); conv.add(new Msg(false, text)); print("sayAsync " + session + ", new size=" + conv.allCount()); } html { _registerThread(); registerVisitor(); fS cookie = cookieSent(); try { Conversation conv; { lock dbLock(); if (eq(uri, "/stats")) ret "Threads: " + ul_htmlEncode(getThreadNames(registeredThreads())); if (eq(uri, "/logs")) ret webChatBotLogsHTML(); S message = trim(params.get("btn")); if (empty(message)) message = trim(params.get("message")); conv = getConv(cookie); if (match("clear", message)) { conv.oldDialogs.add(conv.msgs); cset(conv, msgs := new L); conv.change(); callOpt(thoughtBot, "clearSession", conv.cookie); message = null; } call(thoughtBot, "setSession", cookieSent()); if (empty(conv.msgs)) conv.add(new Msg(false, initialMessage())); if (nempty(message) && !lastUserMessageWas(conv, message)) conv.add(new Msg(true, message)); if (nempty(conv.msgs) && last(conv.msgs).fromUser) { S reply = "I'm sorry, I don't understand"; L<S> buttons = null; pcall { reply = or2(makeReply(last(conv.msgs).text), reply); buttons = (L<S>) getThreadLocal(thoughtBot, "buttons"); } Msg msg = new Msg(false, reply); msg.buttons = buttons; conv.add(msg); } } // locked if (eq(uri, "/msg")) ret "OK"; if (eq(uri, "/incremental")) { int a = parseInt(params.get("a")), origA = a; //int b = parseInt(params.get("b")); int as = conv.archiveSize(); a -= as; //b -= as; long start = sysNow(); L msgs; bool first = true; while (licensed() && sysNow() < start+longPollMaxWait) { msgs = subList(conv.msgs, a); if (empty(msgs)) { if (first) { print("Long poll starting on " + cookie + ", " + origA + "/" + a); first = false; } sleep(longPollTick); } else { if (first) print("Long poll ended."); new StringBuilder buf; renderMessages(buf, msgs); ret "<!-- " + conv.allCount() + "-->\n" + buf; } } ret ""; } { lock dbLock(); S html = loadSnippet(templateID); // TODO: cache new StringBuilder buf; renderMessages(buf, conv.msgs); html = html.replace("#N#", str(conv.allCount())); html = html.replace("#INCREMENTALURL#", relativeRawBotLink(programID(), "incremental?a="); html = html.replace("#MSGURL#", relativeRawBotLink(programID(), "msg?message=")); if (nempty(params.get("debug"))) html = html.replace("var showActions = false;", "var showActions = true;"); html = html.replace("$HEADING", heading); html = html.replace("<!-- MSGS HERE -->", str(buf)); html = hreplaceTitle(html, heading); html = hmobilefix(html); ret html; } } finally { _unregisterThread(); } } svoid renderMessages(StringBuilder buf, L<Msg> msgs) { for (Msg m : msgs) if (m.fromUser || neq(m.text, "-")) appendMsg(buf, m.fromUser ? "Esteemed Customer" : botName, formatTime(m.time), htmlEncode2_nlToBr(trim(m.text)), !m.fromUser); L<S> buttons = last(msgs).buttons; if (nempty(buttons)) appendButtons(buf, buttons); } svoid appendMsg(StringBuilder buf, S name, S time, S text, bool bot) { S imgLink = snippetImgLink(bot ? #1008323 : #1008359); buf.append([[<div class="direct-chat-msg doted-border"> <div class="direct-chat-info clearfix"> <span class="direct-chat-name pull-left">$NAME</span> </div> <img alt="message user image" src="$IMG" class="direct-chat-img"> <div class="direct-chat-text"> $TEXT </div> <div class="direct-chat-info clearfix"> <span class="direct-chat-timestamp pull-right">$TIME</span> </div> </div> ]].replace("$IMG", imgLink) .replace("$NAME", name) .replace("$TIME", time) .replace("$TEXT", text)); } svoid appendButtons(StringBuilder buf, L<S> buttons) { S buttonsHtml = lines(map(buttons, func(S text) { hsubmit(text, name := "btn") })); buf.append([[<div class="direct-chat-msg doted-border"> <div class="direct-chat-buttons"> $BUTTONS </div> </div> ]].replace("$BUTTONS", buttonsHtml); } svoid appendDate(StringBuilder buf, S date) { buf.append([[ <div class="chat-box-single-line"> <abbr class="timestamp">DATE</abbr> </div>]].replace("DATE", date)); } sS initialMessage() { ret (S) call(thoughtBot, "initialMessage"); } static bool lastUserMessageWas(Conversation conv, S message) { Msg m = last(conv.msgs); ret m != null && m.fromUser && eq(m.text, message); } sS makeReply(S message) { ret callStaticAnswerMethod(thoughtBot, message); } sS formatTime(long time) { ret formatGMT_24(time); } sS formatDialog(S id, L<Msg> msgs) { new L<S> lc; for (Msg m : msgs) lc.add(htmlencode((m.fromUser ? "> " : "< ") + m.text)); ret id + ul(lc); } static Conversation getConv(fS cookie) { ret withDBLock(func -> Conversation { uniq(Conversation, +cookie) }); }
Began life as a copy of #1008316
download show line numbers debug dex old transpilations
Travelled to 13 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1008764 |
Snippet name: | Web Chat Bot Include |
Eternal ID of this version: | #1008764/30 |
Text MD5: | 1fa29ebce5c453a757504d821e5811c6 |
Author: | stefan |
Category: | javax / a.i. |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-11-05 12:25:25 |
Source code size: | 6074 bytes / 223 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 770 / 1259 |
Version history: | 29 change(s) |
Referenced in: | [show references] |