1 | sO thoughtBot; |
2 | |
3 | static int longPollTick = 100; |
4 | static int longPollMaxWait = 1000*60; |
5 | |
6 | sclass Msg { |
7 | long time; |
8 | bool fromUser; |
9 | S text; |
10 | L<S> buttons; |
11 | |
12 | *() {} |
13 | *(bool *fromUser, S *text) { time = now(); } |
14 | } |
15 | |
16 | concept Conversation { |
17 | S cookie; |
18 | new LL<Msg> oldDialogs; |
19 | new L<Msg> msgs; |
20 | |
21 | void add(Msg m) { |
22 | msgs.add(m); |
23 | change(); |
24 | } |
25 | |
26 | int allCount() { ret lengthLevel2(oldDialogs) + l(msgs); } |
27 | int archiveSize() { ret lengthLevel2(oldDialogs); } |
28 | } |
29 | |
30 | svoid pWebChatBot { |
31 | db(); |
32 | thoughtBot = runDependent(thoughtBotID); |
33 | Class envType = fieldType(thoughtBot, "env"); |
34 | if (envType != null) |
35 | setOpt(thoughtBot, "env", proxy(envType, (O) mc())); |
36 | } |
37 | |
38 | static void sayAsync(S session, S text) { |
39 | lock dbLock(); |
40 | Conversation conv = getConv(session); |
41 | conv.add(new Msg(false, text)); |
42 | print("sayAsync " + session + ", new size=" + conv.allCount()); |
43 | } |
44 | |
45 | html { |
46 | _registerThread(); |
47 | registerVisitor(); |
48 | fS cookie = cookieSent(); |
49 | try { |
50 | Conversation conv; |
51 | { |
52 | lock dbLock(); |
53 | if (eq(uri, "/stats")) |
54 | ret "Threads: " + ul_htmlEncode(getThreadNames(registeredThreads())); |
55 | |
56 | if (eq(uri, "/logs")) |
57 | ret webChatBotLogsHTML(); |
58 | |
59 | S message = trim(params.get("btn")); |
60 | if (empty(message)) message = trim(params.get("message")); |
61 | conv = getConv(cookie); |
62 | |
63 | if (match("clear", message)) { |
64 | conv.oldDialogs.add(conv.msgs); |
65 | cset(conv, msgs := new L); |
66 | conv.change(); |
67 | callOpt(thoughtBot, "clearSession", conv.cookie); |
68 | message = null; |
69 | } |
70 | |
71 | call(thoughtBot, "setSession", cookieSent()); |
72 | |
73 | if (empty(conv.msgs)) |
74 | conv.add(new Msg(false, initialMessage())); |
75 | |
76 | if (nempty(message) && !lastUserMessageWas(conv, message)) |
77 | conv.add(new Msg(true, message)); |
78 | |
79 | if (nempty(conv.msgs) && last(conv.msgs).fromUser) { |
80 | S reply = "I'm sorry, I don't understand"; |
81 | L<S> buttons = null; |
82 | pcall { |
83 | reply = or2(makeReply(last(conv.msgs).text), reply); |
84 | buttons = (L<S>) getThreadLocal(thoughtBot, "buttons"); |
85 | } |
86 | Msg msg = new Msg(false, reply); |
87 | msg.buttons = buttons; |
88 | conv.add(msg); |
89 | } |
90 | } // locked |
91 | |
92 | if (eq(uri, "/msg")) ret "OK"; |
93 | |
94 | if (eq(uri, "/incremental")) { |
95 | int a = parseInt(params.get("a")), origA = a; |
96 | //int b = parseInt(params.get("b")); |
97 | int as = conv.archiveSize(); |
98 | a -= as; |
99 | //b -= as; |
100 | |
101 | long start = sysNow(); |
102 | L msgs; |
103 | bool first = true; |
104 | while (licensed() && sysNow() < start+longPollMaxWait) { |
105 | msgs = subList(conv.msgs, a); |
106 | if (empty(msgs)) { |
107 | if (first) { |
108 | print("Long poll starting on " + cookie + ", " + origA + "/" + a); |
109 | first = false; |
110 | } |
111 | sleep(longPollTick); |
112 | } else { |
113 | if (first) print("Long poll ended."); |
114 | new StringBuilder buf; |
115 | renderMessages(buf, msgs); |
116 | ret "<!-- " + conv.allCount() + "-->\n" + buf; |
117 | } |
118 | } |
119 | ret ""; |
120 | } |
121 | |
122 | { |
123 | lock dbLock(); |
124 | S html = loadSnippet(templateID); // TODO: cache |
125 | new StringBuilder buf; |
126 | |
127 | renderMessages(buf, conv.msgs); |
128 | |
129 | html = html.replace("#N#", str(conv.allCount())); |
130 | html = html.replace("#INCREMENTALURL#", relativeRawBotLink(programID(), "incremental?a="); |
131 | html = html.replace("#MSGURL#", relativeRawBotLink(programID(), "msg?message=")); |
132 | if (nempty(params.get("debug"))) |
133 | html = html.replace("var showActions = false;", "var showActions = true;"); |
134 | |
135 | html = html.replace("$HEADING", heading); |
136 | html = html.replace("<!-- MSGS HERE -->", str(buf)); |
137 | html = hreplaceTitle(html, heading); |
138 | html = hmobilefix(html); |
139 | ret html; |
140 | } |
141 | } finally { |
142 | _unregisterThread(); |
143 | } |
144 | } |
145 | |
146 | svoid renderMessages(StringBuilder buf, L<Msg> msgs) { |
147 | for (Msg m : msgs) |
148 | if (m.fromUser || neq(m.text, "-")) |
149 | appendMsg(buf, m.fromUser ? "Esteemed Customer" : botName, formatTime(m.time), htmlEncode2_nlToBr(trim(m.text)), !m.fromUser); |
150 | |
151 | L<S> buttons = last(msgs).buttons; |
152 | if (nempty(buttons)) |
153 | appendButtons(buf, buttons); |
154 | } |
155 | |
156 | svoid appendMsg(StringBuilder buf, S name, S time, S text, bool bot) { |
157 | S imgLink = snippetImgLink(bot ? #1008323 : #1008359); |
158 | buf.append([[<div class="direct-chat-msg doted-border"> |
159 | <div class="direct-chat-info clearfix"> |
160 | <span class="direct-chat-name pull-left">$NAME</span> |
161 | </div> |
162 | <img alt="message user image" src="$IMG" class="direct-chat-img"> |
163 | <div class="direct-chat-text"> |
164 | $TEXT |
165 | </div> |
166 | <div class="direct-chat-info clearfix"> |
167 | <span class="direct-chat-timestamp pull-right">$TIME</span> |
168 | </div> |
169 | </div> |
170 | ]].replace("$IMG", imgLink) |
171 | .replace("$NAME", name) |
172 | .replace("$TIME", time) |
173 | .replace("$TEXT", text)); |
174 | } |
175 | |
176 | svoid appendButtons(StringBuilder buf, L<S> buttons) { |
177 | S buttonsHtml = lines(map(buttons, func(S text) { |
178 | hsubmit(text, name := "btn") |
179 | })); |
180 | buf.append([[<div class="direct-chat-msg doted-border"> |
181 | <div class="direct-chat-buttons"> |
182 | $BUTTONS |
183 | </div> |
184 | </div> |
185 | ]].replace("$BUTTONS", buttonsHtml); |
186 | } |
187 | |
188 | svoid appendDate(StringBuilder buf, S date) { |
189 | buf.append([[ |
190 | <div class="chat-box-single-line"> |
191 | <abbr class="timestamp">DATE</abbr> |
192 | </div>]].replace("DATE", date)); |
193 | } |
194 | |
195 | sS initialMessage() { |
196 | ret (S) call(thoughtBot, "initialMessage"); |
197 | } |
198 | |
199 | static bool lastUserMessageWas(Conversation conv, S message) { |
200 | Msg m = last(conv.msgs); |
201 | ret m != null && m.fromUser && eq(m.text, message); |
202 | } |
203 | |
204 | sS makeReply(S message) { |
205 | ret callStaticAnswerMethod(thoughtBot, message); |
206 | } |
207 | |
208 | sS formatTime(long time) { |
209 | ret formatGMT_24(time); |
210 | } |
211 | |
212 | sS formatDialog(S id, L<Msg> msgs) { |
213 | new L<S> lc; |
214 | for (Msg m : msgs) |
215 | lc.add(htmlencode((m.fromUser ? "> " : "< ") + m.text)); |
216 | ret id + ul(lc); |
217 | } |
218 | |
219 | static Conversation getConv(fS cookie) { |
220 | ret withDBLock(func -> Conversation { |
221 | uniq(Conversation, +cookie) |
222 | }); |
223 | } |
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: | 771 / 1260 |
Version history: | 29 change(s) |
Referenced in: | [show references] |