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

359
LINES

< > BotCompany Repo | #1026409 - Tomii Boi Answers DB [LIVE]

JavaX module (desktop) [tags: butter use-pretranspiled] - homepage

Libraryless. Click here for Pure Java version (20009L/144K).

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

Author comment

Began life as a copy of #1026223

download  show line numbers  debug dex   

Travelled to 3 computer(s): mqqgnosmbjvj, tvejysmllsmz, xrpafgyirdlv

No comments. add comment

Snippet ID: #1026409
Snippet name: Tomii Boi Answers DB [LIVE]
Eternal ID of this version: #1026409/25
Text MD5: 10400f79d3a2fcecdd3258c488bd34d3
Transpilation MD5: 0907ab0259e67548c16201702a655471
Author: stefan
Category: javax / html
Type: JavaX module (desktop)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2020-01-12 16:29:46
Source code size: 11160 bytes / 359 lines
Pitched / IR pitched: No / No
Views / Downloads: 100 / 447
Version history: 24 change(s)
Referenced in: [show references]

Formerly at http://tinybrain.de/1026409 & http://1026409.tinybrain.de