Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

362
LINES

< > BotCompany Repo | #1025984 // Web Chat Bot Include v2 [used by MMO]

JavaX fragment (include)

1  
sO thoughtBot;
2  
3  
static int longPollTick = 100;
4  
static int longPollMaxWait = 1000*60;
5  
6  
sS botImageID = #1102802;
7  
sS userImageID = #1102803;
8  
sS chatHeaderImageID = #1102802;
9  
sS baseLink;
10  
sbool botOnRight = true;
11  
sS timeZone = germanTimeZone_string();
12  
13  
static Set<S> specialButtons = litciset("Cancel", "Back", "Abbrechen", "Zurück");
14  
15  
sclass Out extends DynamicObject {
16  
  S buttonsIntro;
17  
  LS buttons;
18  
  S placeholder;
19  
  S defaultInput;
20  
}
21  
22  
sclass Msg extends DynamicObject {
23  
  long time;
24  
  bool fromUser;
25  
  S text;
26  
  Out out;
27  
  
28  
  *() {}
29  
  *(bool *fromUser, S *text) { time = now(); }
30  
}
31  
32  
concept Conversation {
33  
  bool authed;
34  
  S cookie;
35  
  new LL<Msg> oldDialogs;
36  
  new L<Msg> msgs;
37  
38  
  void add(Msg m) {
39  
    syncAdd(msgs, m);
40  
    change();
41  
  }
42  
  
43  
  int allCount() { ret lengthLevel2(oldDialogs) + l(msgs); }
44  
  int archiveSize() { ret lengthLevel2(oldDialogs); }
45  
}
46  
47  
svoid pWebChatBot {
48  
  db();
49  
  thoughtBot = runDependent(thoughtBotID);
50  
  Class envType = fieldType(thoughtBot, "env");
51  
  if (envType != null)
52  
    setOpt(thoughtBot, "env", proxy(envType, (O) mc()));
53  
}
54  
55  
static void sayAsync(S session, S text) {
56  
  lock dbLock();
57  
  Conversation conv = getConv(session);
58  
  conv.add(new Msg(false, text));
59  
  print("sayAsync " + session + ", new size=" + conv.allCount());
60  
}
61  
62  
html {
63  
  temp tempRegisterThread();
64  
  S cookie = params.get('cookie);
65  
  if (empty(cookie)) {
66  
    registerVisitor();
67  
    cookie = cookieSent();
68  
  }
69  
  Conversation conv = nempty(cookie) ? getConv(cookie) : null;
70  
  print("URI: " + uri + ", cookie: " + cookie + ", msgs: " + l(conv.msgs));
71  
  
72  
  S pw = trim(params.get('pw));
73  
  if (nempty(pw)) {
74  
    S realPW = trim(loadSecretTextFile("password.txt"));
75  
    if (empty(realPW)) ret errorMsg("Administrator has not set a password");
76  
    if (neq(pw, realPW))
77  
      ret errorMsg("Bad password, please try again");
78  
    cset(conv, authed := true);
79  
    if (nempty(params.get('redirect)))
80  
      ret hrefresh(params.get('redirect));
81  
  }
82  
  
83  
  if (eq(uri, "/stats")) {
84  
    if (!conv.authed) ret serveAuthForm(rawLink(uri));
85  
    ret "Threads: " + ul_htmlEncode(getThreadNames(registeredThreads()));
86  
  }
87  
      
88  
  if (eq(uri, "/logs")) {
89  
    if (!conv.authed) ret serveAuthForm(rawLink(uri));
90  
    ret webChatBotLogsHTML2(rawLink(uri), params);
91  
  }
92  
93  
  if (eq(uri, "/auth-only")) {
94  
    if (eq(params.get('logout), "1"))
95  
      cset(conv, authed := false);
96  
    if (!conv.authed) ret serveAuthForm(params.get('uri));
97  
    ret "";
98  
  }
99  
100  
  {
101  
    lock dbLock();
102  
    
103  
    S message = trim(params.get("btn"));
104  
    if (empty(message)) message = trim(params.get("message"));
105  
    
106  
    if (match("new dialog", message)) {
107  
      conv.oldDialogs.add(conv.msgs);
108  
      cset(conv, msgs := new L);
109  
      conv.change();
110  
      callOpt(thoughtBot, "clearSession", conv.cookie);
111  
      message = null;
112  
    }
113  
    
114  
    call(thoughtBot, "setSession", cookie, params);
115  
    
116  
    if (empty(conv.msgs))
117  
      addReplyToConvo(conv, lambda0 initialMessage);
118  
      
119  
    if (nempty(message) && !lastUserMessageWas(conv, message)) {
120  
      print("Adding message: " + message);
121  
      conv.add(new Msg(true, message));
122  
    }
123  
  
124  
    if (nempty(conv.msgs) && last(conv.msgs).fromUser)
125  
      addReplyToConvo(conv, () -> makeReply(last(conv.msgs).text));
126  
  } // locked
127  
  
128  
  if (eq(uri, "/msg")) ret withHeader("OK");
129  
  
130  
  if (eq(uri, "/incremental")) {
131  
    int a = parseInt(params.get("a"));
132  
133  
    long start = sysNow();
134  
    L msgs;
135  
    bool first = true;
136  
    while (licensed() && sysNow() < start+longPollMaxWait) {
137  
      int as = conv.archiveSize();
138  
      msgs = cloneSubList(conv.msgs, a-as);
139  
      bool newDialog = a <= as;
140  
      if (empty(msgs)) {
141  
        if (first) {
142  
          print("Long poll starting on " + cookie + ", " + a + "/" + a);
143  
          first = false;
144  
        }
145  
        sleep(longPollTick);
146  
      } else {
147  
        if (first) print("Long poll ended.");
148  
        new StringBuilder buf;
149  
        renderMessages(buf, msgs);
150  
        ret withHeader("<!-- " + conv.allCount() + " " + (newDialog ? "NEW DIALOG " : "") + "-->\n"  + buf);
151  
      }
152  
    }
153  
    ret withHeader("");
154  
  }
155  
  
156  
  {
157  
    lock dbLock();
158  
    processParams(params);
159  
    S html = loadSnippet(templateID); // TODO: cache
160  
    new StringBuilder buf;
161  
162  
    // incremental only    
163  
    //renderMessages(buf, conv.msgs);
164  
  
165  
    S langlinks = "<!-- langlinks here -->";
166  
    if (html.contains(langlinks))
167  
      html = html.replace(langlinks,
168  
        ahref(rawLink("eng"), "English") + " | " + ahref(rawLink("deu"), "German"));
169  
    
170  
    html = html.replace("#BOTIMG#", imageSnippetURLOrEmptyGIF(chatHeaderImageID));
171  
    html = html.replace("#N#", "0" /*str(conv.allCount())*/);
172  
    html = html.replace("#INCREMENTALURL#", baseLink + "/incremental?a=");
173  
    html = html.replace("#MSGURL#", baseLink + "/msg?message=");
174  
    if (nempty(params.get("debug")))
175  
      html = html.replace("var showActions = false;", "var showActions = true;");
176  
  
177  
    html = html.replace("#AUTOOPEN#", jsBool(botAutoOpen());
178  
    html = html.replace("#BOT_ON#", jsBool(botOn());
179  
    html = html.replace("$HEADING", heading);
180  
    html = html.replace("<!-- MSGS HERE -->", str(buf));
181  
    html = hreplaceTitle(html, heading);
182  
    
183  
    if (eqGet(params, "_botDemo", "1"))
184  
      ret hhtml(hhead(
185  
        htitle("Bot Demo")
186  
        + loadJQuery()
187  
      ) + hbody(hjavascript(html)));
188  
    else
189  
      ret withHeader(subBot_serveJavaScript(html));
190  
  }
191  
}
192  
193  
svoid addReplyToConvo(Conversation conv, IF0<S> think) {
194  
  S reply = "";
195  
  Out out = null;
196  
  pcall {
197  
    reply = think!;
198  
    out = (Out) quickImport(getThreadLocal(thoughtBot, "out"));
199  
  }
200  
  Msg msg = new Msg(false, reply);
201  
  msg.out = out;
202  
  conv.add(msg);
203  
}
204  
205  
sO withHeader(S html) {
206  
  ret withHeader(subBot_serveHTML(html));
207  
}
208  
209  
sO withHeader(O response) {
210  
  call(response, 'addHeader, "Access-Control-Allow-Origin", "*");
211  
  ret response;
212  
}
213  
214  
svoid renderMessages(StringBuilder buf, L<Msg> msgs) {
215  
  new Set<S> buttonsToSkip;
216  
  new LS buttonsHtml;
217  
  for (Msg m : msgs) {
218  
    if (!m.fromUser && eq(m.text, "-")) continue;
219  
    S html = htmlEncode2_nlToBr(trim(m.text));
220  
    // pull back & cancel buttons to beginning of msg
221  
    if (m == last(msgs) && m.out != null) {
222  
      fOr (S btn : m.out.buttons)
223  
        if (specialButtons.contains(btn)) {
224  
          buttonsToSkip.add(btn);
225  
          buttonsHtml.add(renderButtons(ll(btn)));
226  
        }
227  
    }
228  
    if (nempty(buttonsHtml) && l(m.out.buttons) == l(buttonsToSkip))
229  
      html += " " + hspan("&nbsp;&nbsp;", class := "chat-button-span") + lines(buttonsHtml);
230  
    else
231  
      buttonsToSkip.clear();
232  
    appendMsg(buf, m.fromUser ? defaultUserName() : botName, formatTime(m.time), html, !m.fromUser);
233  
  }
234  
      
235  
  appendButtons(buf, last(msgs).out, buttonsToSkip);
236  
}
237  
238  
svoid appendMsg(StringBuilder buf, S name, S time, S text, bool bot) {
239  
  // img now before text because of position: absolute
240  
  // removed from last div: <span class="direct-chat-timestamp pull-$LR">$TIME</span>
241  
  buf.append(([[<div class="direct-chat-msg doted-border">]]);
242  
  
243  
  S imgID = bot ? botImageID : userImageID;
244  
  if (nempty(imgID))
245  
    buf.append([[<img alt="message user image" src="$IMG" class="direct-chat-img">]]
246  
      .replace("$IMG", snippetImgLink(imgID)));
247  
  
248  
  buf.append([[
249  
    <div class="direct-chat-info clearfix">
250  
    <div style="float: right" class="direct-chat-timestamp">$TIME</div>
251  
    <span class="direct-chat-name pull-left">$NAME</span>
252  
    </div>
253  
    <div class="direct-chat-text pull-$LR clearfix">
254  
      $TEXT
255  
    </div>
256  
    <div class="direct-chat-info clearfix"></div>
257  
  </div>
258  
   ]].replace("$LR", bot != botOnRight ? "left" : "right")
259  
     .replace("$NAME", name)
260  
     .replace("$TIME", time)
261  
     .replace("$TEXT", text));
262  
}
263  
264  
sS replaceButtonText(S s) {
265  
  if (eqicOneOf(s, "back", "zurück")) ret unicode_undoArrow();
266  
  if (eqicOneOf(s, "cancel", "Abbrechen")) ret unicode_crossProduct();
267  
  ret s;
268  
}
269  
270  
sS renderButtons(LS buttons) {
271  
  new LS out;
272  
  for i over buttons: {
273  
    S code = buttons.get(i);
274  
    S text = replaceButtonText(code);
275  
    out.add(hbuttonOnClick_returnFalse(text, "submitAMsg(" + jsQuote(text) + ")", class := "chatbot-choice-button", title := eq(code, text) ? null : code));
276  
    if (!specialButtons.contains(code)
277  
      && i+1 < l(buttons) && specialButtons.contains(buttons.get(i+1)))
278  
      out.add("&nbsp;&nbsp;");
279  
  }
280  
  ret lines(out);
281  
}
282  
283  
svoid appendButtons(StringBuilder buf, Out out, Set<S> buttonsToSkip) {
284  
  S placeholder = out == null ? "" : unnull(out.placeholder);
285  
  S defaultInput = out == null ? "" : unnull(out.defaultInput);
286  
  buf.append(hscript("chatBot_setInput(" + jsQuote(defaultInput) + ", " + jsQuote(placeholder) + ");"));
287  
  if (out == null) ret;
288  
  LS buttons = listMinusSet(out.buttons, buttonsToSkip);
289  
  if (empty(buttons)) ret;
290  
  S buttonsHtml = renderButtons(buttons);
291  
  buf.append([[<div class="direct-chat-msg doted-border">
292  
    <div class="direct-chat-buttons">
293  
      $BUTTONS
294  
    </div>
295  
  </div>
296  
    ]].replace("$BUTTONS", htmlEncode2_nlToBr(appendNewLineIfNempty(trim(out.buttonsIntro))) + buttonsHtml));
297  
}
298  
299  
svoid appendDate(StringBuilder buf, S date) {
300  
  buf.append([[
301  
  <div class="chat-box-single-line">
302  
    <abbr class="timestamp">DATE</abbr>
303  
  </div>]].replace("DATE", date));
304  
}
305  
306  
sS initialMessage() {
307  
  ret (S) call(thoughtBot, "initialMessage"); 
308  
}
309  
310  
static bool lastUserMessageWas(Conversation conv, S message) {
311  
  Msg m = last(conv.msgs);
312  
  ret m != null && m.fromUser && eq(m.text, message);
313  
}
314  
315  
sS makeReply(S message) {
316  
  ret callStaticAnswerMethod(thoughtBot, message);
317  
}
318  
319  
sS formatTime(long time) {
320  
  ret timeInTimeZoneWithOptionalDate_24(timeZone, time);
321  
}
322  
323  
sS formatDialog(S id, L<Msg> msgs) {
324  
  new L<S> lc;
325  
  for (Msg m : msgs)
326  
    lc.add(htmlencode((m.fromUser ? "> " : "< ") + m.text));
327  
  ret id + ul(lc);
328  
}
329  
330  
static Conversation getConv(fS cookie) {
331  
  ret withDBLock(func -> Conversation {
332  
    uniq(Conversation, +cookie)
333  
  });
334  
}
335  
336  
sS serveAuthForm(S redirect) {
337  
  ret hhtml(hhead(htitle("Authorization required")) + hbody(hfullcenter(
338  
    h3_htmlEncode(heading + " Admin")
339  
    + hpostform(
340  
      hhidden(+redirect)
341  
    + "Password: " + hpassword('pw) + "<br><br>" + hsubmit()))));
342  
}
343  
344  
sS errorMsg(S msg) {
345  
  ret hhtml(hhead_title("Error") + hbody(hfullcenter(msg + "<br><br>" + ahref(jsBackLink(), "Back"))));
346  
}
347  
348  
sS defaultUserName() {
349  
  ret de() ? "Sie" : "You";
350  
}
351  
352  
sbool de() {
353  
  ret isTrue(callOpt(thoughtBot, "de"));
354  
}
355  
356  
sbool botOn() {
357  
  ret isTrue(callOpt(thoughtBot, "botOn"));
358  
}
359  
360  
sbool botAutoOpen() {
361  
  ret !isFalse(callOpt(thoughtBot, "botAutoOpen"));
362  
}

Author comment

Began life as a copy of #1008764

download  show line numbers  debug dex  old transpilations   

Travelled to 6 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt

No comments. add comment

Snippet ID: #1025984
Snippet name: Web Chat Bot Include v2 [used by MMO]
Eternal ID of this version: #1025984/100
Text MD5: 70b726b84e05d5918e8ad9d68774bb09
Author: stefan
Category: javax / a.i.
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2019-12-07 20:49:36
Source code size: 10781 bytes / 362 lines
Pitched / IR pitched: No / No
Views / Downloads: 251 / 591
Version history: 99 change(s)
Referenced in: [show references]