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

375
LINES

< > BotCompany Repo | #1028655 // Web Bot Answers DB Include [backup before test scripts]

JavaX fragment (include)

1  
sS mainBotID;
2  
static int maxTypos = 1;
3  
4  
sS adminTitle = "Chat Bot Admin";
5  
sS shortLink; // catchy link to admin if available
6  
sbool enableWorkerChat;
7  
8  
sS embedderLink; // where chat bot will go
9  
sS goDaddyEmbedCode;
10  
11  
static Cache<Scorer<ConsistencyError>> consistencyCheckResult = new(lambda0 consistencyCheck);
12  
13  
concept Category {
14  
  int index;
15  
  S name;
16  
}
17  
18  
concept Language {
19  
  int index;
20  
  S code;
21  
}
22  
23  
concept QA {
24  
  int index;
25  
  S language;
26  
  S questions; // line by line
27  
  S patterns; // to be determined
28  
  S answers; // one answer, or "random { answers separated by empty lines }"
29  
  S category; // "business", "smalltalk"
30  
31  
  transient MMOPattern parsedPattern;
32  
33  
  sS _fieldOrder = "language category index questions patterns answers";
34  
  
35  
  void change {
36  
    parsedPattern = null;
37  
    super.change();
38  
  }
39  
  
40  
  MMOPattern parsedPattern() {
41  
    if (parsedPattern == null)
42  
      parsedPattern = mmo2_parsePattern(patterns);
43  
    ret parsedPattern;
44  
  }
45  
}
46  
47  
concept Defaults {
48  
  S lastLanguage;
49  
}
50  
51  
concept Settings {
52  
  S contactFormViberID;
53  
  bool botOn, botAutoOpen;
54  
}
55  
56  
svoid pQADB {
57  
  print("fieldOrder", getFieldOrder(QA));
58  
  dbIndexing(Language, 'index, Category, 'index);
59  
  indexSingletonConcept(Defaults);
60  
  indexSingletonConcept(Settings);
61  
  
62  
  // languages, default language
63  
  cset(conceptsWhere QA(language := null), language := 'en);
64  
  uniq(Language, code := 'en);
65  
  removeConceptsWhere(Language, code := 'de);
66  
  
67  
  // categories, default category
68  
  int i = 0;
69  
  for (S category : ll("business", "smalltalk"))
70  
    cset(uniq(Category, name := category), index := i++);
71  
  cset(conceptsWhere QA(category := null), category := "business");
72  
  
73  
  onConceptsChange(r { consistencyCheckResult.clear(); });
74  
  reindexQAs();
75  
}
76  
77  
html { try {
78  
  if (swic(addSlash(uri), "/answer/")) {
79  
    temp tempSetTL(opt_noDefault, valueIs1 noDefault(params));
80  
    ret serveText(unnull(answer(params.get("q"), "en")));
81  
  }
82  
    
83  
  // force auth
84  
  try answer callHtmlMethod(getBot(mainBotID), "/auth-only", mapPlus(
85  
params, uri := rawLink(uri)));
86  
    
87  
  if (eq(uri, "/demo"))
88  
    ret hrefresh(appendQueryToURL(rawBotLink(mainBotID), _botDemo := 1, bot := 1));
89  
    
90  
  if (eq(uri, "/download"))
91  
    ret subBot_serveText(conceptsStructure());
92  
    
93  
  if (eq(uri, "/embedCode"))
94  
    ret hsansserif() + htitle_h2("Chat bot embed code")
95  
      + (empty(goDaddyEmbedCode) ? "" :
96  
        h3("GoDaddy Site Builder")
97  
      
98  
        + p("Add an HTML box with this code:")
99  
      
100  
        + pre(htmlEncode2(goDaddyEmbedCode))
101  
        
102  
        + h3("Other"))
103  
        
104  
      + p("Add this code in front of your " + tt(htmlEncode2("</body>")) + " tag:")
105  
      
106  
      + pre(htmlEncode2(hjavascript_src_withType(rawBotLink(mainBotID))));
107  
    
108  
  HCRUD_Concepts<QA> data = new HCRUD_Concepts<QA>(QA) {
109  
    S itemName() { ret "question"; }
110  
    
111  
    S fieldHelp(S field) {
112  
      if (eq(field, "category"))
113  
        ret "smalltalk has a lower precedence than business";
114  
      if (eq(field, "index"))
115  
        ret "lower index = higher matching precedence";
116  
      if (eq(field, "patterns"))
117  
        ret [[Phrases to match. Use "+" as "and" operator, commas as "or" operator. Round brackets for grouping. Quotes for special characters. Put exclamation mark after phrase to disable typo correction.]];
118  
      if (eq(field, "answers"))
119  
        ret "Text of answer. Use random { ... } for multiple answers (separated from each other by an empty line)";
120  
      null;
121  
    }
122  
    
123  
    Renderer getRenderer(S field) {
124  
      if (eqOneOf(field, 'questions, 'answers))
125  
        ret new TextArea(80, 10);
126  
      if (eq(field, 'patterns))
127  
        ret new TextField(80);
128  
      if (eq(field, 'language))
129  
        ret new ComboBox(collect code(conceptsSortedByField(Language, 'index)));
130  
      if (eq(field, 'category))
131  
        ret new ComboBox(collect name(conceptsSortedByField(Category, 'index)));
132  
      ret super.getRenderer(field);
133  
    }
134  
    
135  
    // sort by language first, then by priority (category + index)
136  
    L<QA> defaultSort(L<QA> l) {
137  
      ret sortByFieldDesc language(sortQAs(l));
138  
    }
139  
    
140  
    Map<S, O> emptyObject() {
141  
      ret mapPlus(super.emptyObject(), language := uniq(Defaults).lastLanguage);
142  
    }
143  
    
144  
    O createObject(SS map) {
145  
      S language = cast map.get('language);
146  
      if (language != null) cset(uniq(Defaults), lastLanguage := language);
147  
      ret super.createObject(map);
148  
    }
149  
  };
150  
  data.onCreateOrUpdate.add(qa -> reindexQAs());
151  
  
152  
  HCRUD crud = new(rawLink(), data) {
153  
    S frame(S title, S contents) {
154  
      title = ahref(or2(shortLink, rawLink("")), adminTitle) + " | " + title;
155  
      ret hhtml(hhead_title_decode(title)
156  
        + hbody(h2(title)
157  
        + p(makeNav())
158  
        + contents));
159  
    }
160  
    
161  
    S renderTable(bool withCmds) {
162  
      Scorer<ConsistencyError> scorer = consistencyCheckResult!;
163  
      ret (empty(scorer.errors)
164  
        ? p("Pattern analysis: No problems found. " + nEntries(countConcepts(QA)) + ".")
165  
        : joinMap(scorer.errors, lambda1 renderErrorAsParagraph))
166  
        + super.renderTable(withCmds);
167  
    }
168  
    
169  
    S renderErrorAsParagraph(ConsistencyError error) {
170  
      S fix = error.renderFix();
171  
      ret p("Error: " + htmlEncode2(error.msg) + " " +
172  
        joinMap(error.items, qa -> ahref(editLink(qa.id), "[item]"))
173  
        + (empty(fix) ? "" : " " + fix));
174  
    }
175  
  };
176  
  
177  
  // actions
178  
179  
  if (eqGet(params, action := 'reindex)) {
180  
    long id = parseLong(params.get('id));
181  
    int newIndex = parseInt(params.get('newIndex));
182  
    QA qa = getConcept(QA, id);
183  
    if (qa == null) ret crud.refreshWithMsgs("Item not found");
184  
    cset(qa, index := newIndex);
185  
    reindexQAs();
186  
    ret crud.refreshWithMsgs("Index of item " + qa.id + " changed");
187  
  }
188  
189  
  if (eqGet(params, botOn := "1")) {
190  
    cset(uniq(Settings), botOn := true);
191  
    ret crud.refreshWithMsgs("Bot turned on!");
192  
  }
193  
  
194  
  if (eqGet(params, botOff := "1")) {
195  
    cset(uniq(Settings), botOn := false);
196  
    ret crud.refreshWithMsgs("Bot turned off!");
197  
  }
198  
  
199  
  if (nemptyGet botAutoOpen(params)) {
200  
    cset(uniq(Settings), botAutoOpen := eq("1", params.get("botAutoOpen")));
201  
    ret crud.refreshWithMsgs("Bot auto-open turned " + onOff(botAutoOpen()) + "!");
202  
  }
203  
  
204  
  crud.tableClass = "responstable";
205  
  ret hsansserif() + hcss_responstable() + crud.renderPage(params);
206  
 } catch print e {
207  
  ret "ERROR.";
208  
 }
209  
}
210  
211  
static new ThreadLocal<S> language_out;
212  
static new ThreadLocal<Bool> opt_noDefault; // true = return null instead of #default text
213  
214  
// main API function for other bots
215  
sS answer(S s, S language) {
216  
  language_out.set(null);
217  
  ret trim(answer_main(s, language));
218  
}
219  
220  
sS answer_main(S s, S language) {
221  
  S raw = findRawAnswer(s, language, true);
222  
  S contents = extractKeywordPlusBracketed_keepComments("random", raw);
223  
  if (contents != null)
224  
    ret random(splitAtEmptyLines(contents));
225  
  ret raw;
226  
}
227  
228  
sS findRawAnswer(S s, S language, bool allowTypos) {
229  
  ret selectQA(findMatchingQA(s, language, allowTypos));
230  
}
231  
232  
static QA findQAWithTypos(S s, S language) {
233  
  Lowest<QA> qa = findClosestQA(s, language);
234  
  if (!qa.has()) null;
235  
  if (qa.score() > maxTypos) {
236  
    print("Rejecting " + nTypos((int) qa.score()) + ": " + s + " / " + qa->patterns);
237  
    null;
238  
  }
239  
  print("Accepting " + nTypos((int) qa.score()) + ": " + s + " / " + qa->patterns);
240  
  ret qa!;
241  
}
242  
243  
static QA findMatchingQA(S s, S language, bool allowTypos) {
244  
  try object QA qa = findMatchingQA(s, conceptsWhere(QA, +language));
245  
  try object QA qa = findMatchingQA(s, filter(list(QA), q -> neq(q.language, language)));
246  
  if (allowTypos)
247  
    try object QA qa = findQAWithTypos(s, language);
248  
  if (!eq(s, "#default") && !isTrue(opt_noDefault!)) ret findMatchingQA("#default", language, false);
249  
  null;
250  
}
251  
252  
static Lowest<QA> findClosestQA(S s, S language) {
253  
  time "findClosestQA" {
254  
    new Lowest<QA> qa;
255  
    findClosestQA(s, qa, conceptsWhere(QA, +language));
256  
    findClosestQA(s, qa, filter(list(QA), q -> neq(q.language, language)));
257  
  }
258  
  ret qa;
259  
}
260  
261  
static L<QA> sortQAs(Cl<QA> qas) {
262  
  Map<S, Int> categoryToIndex = fieldToFieldIndex('name, 'index, list(Category));
263  
  ret sortedByCalculatedField(qas, q -> pair(categoryToIndex.get(q.category), q.index));
264  
}
265  
266  
svoid reindexQAs() {
267  
  for (Language lang) {
268  
    int index = 1;
269  
    for (QA qa : sortQAs(conceptsWhere(QA, language := lang.code)))
270  
      cset(qa, index := index++);
271  
  }
272  
}
273  
274  
static QA findMatchingQA(S s, Cl<QA> qas) {
275  
  for (QA qa : sortQAs(qas))
276  
    if (mmo2_match(qa.parsedPattern(), s))
277  
      ret qa;
278  
  null;
279  
}
280  
281  
svoid findClosestQA(S s, Lowest<QA> best, Cl<QA> qas) {
282  
  for (QA qa : sortQAs(qas)) {
283  
    Int score = mmo2_levenWithSwapsScore(qa.parsedPattern(), s);
284  
    if (score != null)
285  
      best.put(qa, score);
286  
  }
287  
}
288  
289  
// returns answers
290  
sS selectQA(QA qa) {
291  
  if (qa == null) null;
292  
  language_out.set(qa.language);
293  
  ret qa.answers;
294  
}
295  
296  
sclass ConsistencyError {
297  
  S msg;
298  
  L<QA> items;
299  
  
300  
  *(S *msg, QA... items) { this.items = asList(items); }
301  
  
302  
  S renderFix() { null; }
303  
}
304  
305  
svoid checkLocalConsistency(QA qa, Scorer<ConsistencyError> scorer) {
306  
  LS questions = tlft(qa.questions);
307  
  for (S q : questions)
308  
    if (mmo2_match(qa.parsedPattern(), q))
309  
      scorer.ok();
310  
    else
311  
      scorer.error(ConsistencyError("Question " + quote(q) + " not matched by patterns " + quote(qa.patterns), qa));
312  
}
313  
314  
svoid checkGlobalConsistency(QA qa, Scorer<ConsistencyError> scorer) {
315  
  for (S q : tlft(qa.questions)) {
316  
    QA found = findMatchingQA(q, qa.language, false);
317  
    if (found != null && found != qa)
318  
      scorer.error(new ConsistencyError("Question " + quote(q) + " (item " + qa.id + ") shadowed by patterns " + quote(found.patterns) + " (item " + found.id + ")", qa, found) {
319  
        S renderFix() {
320  
          ret ahref(rawLink("?action=reindex&id=" + qa.id + "&newIndex=" + (found.index-1)), "[fix by changing index]");
321  
        }
322  
      });
323  
    else
324  
      scorer.ok();
325  
  }
326  
}
327  
328  
static Scorer<ConsistencyError> consistencyCheck() {
329  
  new Scorer<ConsistencyError> scorer;
330  
  scorer.collectErrors();
331  
  for (QA qa) {
332  
    checkLocalConsistency(qa, scorer);
333  
    checkGlobalConsistency(qa, scorer);
334  
  }
335  
  ret scorer;
336  
}
337  
338  
sS contactFormViberID() {
339  
  ret uniq(Settings).contactFormViberID;
340  
}
341  
342  
// API
343  
344  
sbool botOn() {
345  
  ret uniq(Settings).botOn;
346  
}
347  
348  
sbool botAutoOpen() {
349  
  ret uniq(Settings).botAutoOpen;
350  
}
351  
352  
svoid importQA(virtual QA qa_external) {
353  
  QA qa = shallowCloneToUnlistedConcept QA(qa_external);
354  
  uniq QA(allConceptFieldsAsParams(qa));
355  
}
356  
357  
static swappable S makeNav() {
358  
  S s = joinWithVBar(
359  
    ahref("?logout=1", "log out"),
360  
    targetBlank(relativeRawBotLink(mainBotID, "/logs"), "chat logs"),
361  
    targetBlank(rawLink("demo?bot=1"), "demo"),
362  
    targetBlank(rawLink("download"), "export brain"),
363  
    ahref(rawLink("embedCode"), "show embed code"),
364  
    botOn()
365  
      ? "Bot is ON (appears on home page) " + ahrefWithConfirm("Switch bot off?", "?botOff=1", "[switch bot off]")
366  
      : "Bot is OFF (appears only with " + targetBlank(appendQueryToURL(embedderLink, bot := 1), "?bot=1") + ") " + ahrefWithConfirm("Switch bot on?", "?botOn=1", "[switch bot on]")
367  
    );
368  
  
369  
  if (enableWorkerChat)
370  
    s +=
371  
      " | " + ahref(rawBotLink(mainBotID, "workers-admin"), "workers admin")
372  
    + " | " + targetBlank(rawBotLink(mainBotID, "worker"), "worker chat");
373  
    
374  
  ret s;
375  
}

Author comment

Began life as a copy of #1028280

download  show line numbers  debug dex  old transpilations   

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

No comments. add comment

Snippet ID: #1028655
Snippet name: Web Bot Answers DB Include [backup before test scripts]
Eternal ID of this version: #1028655/1
Text MD5: 01679977a9fe315c5e443542c918e1d6
Author: stefan
Category: javax / web chat bots
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2020-07-02 12:12:26
Source code size: 11534 bytes / 375 lines
Pitched / IR pitched: No / No
Views / Downloads: 84 / 104
Referenced in: [show references]