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

332
LINES

< > BotCompany Repo | #1026247 // Imager Answers DB [LIVE]

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

Download Jar. Libraryless. Click here for Pure Java version (17605L/124K).

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

Author comment

Began life as a copy of #1026223

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: #1026247
Snippet name: Imager Answers DB [LIVE]
Eternal ID of this version: #1026247/6
Text MD5: 4f90b76fe67a26f5b28719c8208f4372
Transpilation MD5: 393eb7c804654c31bcd92ef49ee65022
Author: stefan
Category: javax / html
Type: JavaX module (desktop)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2019-12-07 18:16:34
Source code size: 10042 bytes / 332 lines
Pitched / IR pitched: No / No
Views / Downloads: 132 / 930
Version history: 5 change(s)
Referenced in: [show references]