Download Jar. Libraryless. Click here for Pure Java version (17632L/124K).
1 | !7 |
2 | |
3 | sS mainBotID = #1026322; |
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 | } |
Began life as a copy of #1026247
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: | #1026323 |
Snippet name: | ABots.space Answers DB [LIVE] |
Eternal ID of this version: | #1026323/1 |
Text MD5: | b9f117d4999382432626d87742343269 |
Transpilation MD5: | baf2df1da7c6eb18233db5ff4eee24f4 |
Author: | stefan |
Category: | javax / html |
Type: | JavaX module (desktop) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-12-19 11:14:12 |
Source code size: | 10042 bytes / 332 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 305 / 1473 |
Referenced in: | [show references] |