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