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

381
LINES

< > BotCompany Repo | #1028036 // Tomii Boi Answers DB Module [for OS, use with #1026494]

JavaX source code (Dynamic Module) [tags: use-pretranspiled] - run with: Stefan's OS

Uses 911K of libraries. Click here for Pure Java version (22739L/129K).

1  
!7
2  
3  
cmodule TomiiBoiAnswers > DynPrintLogAndEnabled {
4  
  switchable int port = 8080;
5  
  switchable S frontendName = "tomiiBoiDiscordBot";
6  
  
7  
  transient CRUD<Category> categoriesCRUD;
8  
  transient CRUD<Server> serversCRUD;
9  
  transient CRUD<Channel> channelsCRUD;
10  
  transient CRUD<ServerToCategory> serverToCategoryCRUD;
11  
12  
  start {
13  
    init();
14  
    categoriesCRUD = new CRUD(Category);
15  
    serversCRUD = new CRUD(Server);
16  
    serverToCategoryCRUD = new CRUD(ServerToCategory);
17  
    channelsCRUD = new CRUD(Channel);
18  
19  
    thread {
20  
      dm_serveHttpFromFunction(port, lambda2 html);
21  
      print("Admin live at: http://localhost:" + port);
22  
      dm_registerAs('tomiiBoiQA);
23  
    }
24  
  }
25  
  
26  
  visualize {
27  
    JComponent c = super.visualize();
28  
    addComponents(buttons,
29  
      jbutton("Open admin in browser", rThread { openURLInBrowser("http://localhost:" + port) }),
30  
      jPopDownButton_noText(
31  
        "Import data...", rThreadEnter importData));
32  
    c = jtabs(
33  
      "Main", c,
34  
      "Categories", categoriesCRUD.visualize(),
35  
      "Servers", serversCRUD.visualize(),
36  
      "Server-to-category", serverToCategoryCRUD.visualize(),
37  
      "Channels", channelsCRUD.visualize());
38  
    channelsCRUD.addButton("Update list", rThreadEnter grabChannels);
39  
    ret c;
40  
  }
41  
  
42  
  void importData {
43  
    selectFile("Tomii Brain File", voidfunc(File f) enter {
44  
      replaceConceptsWithTextFileOnNextStart(f);
45  
      dm_reloadModule();
46  
    });
47  
  }
48  
49  
  void grabChannels {
50  
    dm_call(frontendName, 'grabChannels);
51  
  }
52  
53  
  // API
54  
55  
  Server addServer(S serverID, S name) {
56  
    ret csetAndReturn(uniq Server(+serverID), +name);
57  
  }
58  
59  
  Channel addChannel(Server server, S channelID, S name) {
60  
    ret csetAndReturn(uniq Channel(+server, +channelID), +name);
61  
  }
62  
63  
  Channel channelForID(S channelID) {
64  
    ret conceptWhere Channel(+channelID);
65  
  }
66  
}
67  
68  
//sS mainBotID = #1026411;
69  
static int maxTypos = 1;
70  
71  
sS adminTitle = "Tomii Boi Chat Bot Admin";
72  
sS shortLink; // catchy link to admin if available
73  
74  
sS embedderLink; // where chat bot will go
75  
sS goDaddyEmbedCode;
76  
77  
static Cache<Scorer<ConsistencyError>> consistencyCheckResult = new(lambda0 consistencyCheck);
78  
79  
concept Server {
80  
  S serverID, name;
81  
82  
  toString { ret name; }
83  
}
84  
85  
concept ServerToCategory {
86  
  Server server;
87  
  Category category;
88  
  bool enabled;
89  
90  
  sS _fieldOrder = "server category enabled";
91  
}
92  
93  
concept Channel {
94  
  Server server;
95  
  S channelID;
96  
  S name;
97  
  bool botEnabled = true;
98  
99  
  toString { ret name; }
100  
  sS _fieldOrder = "botEnabled name server";
101  
}
102  
103  
concept Category {
104  
  int index;
105  
  S name;
106  
  bool onByDefault = true;
107  
108  
  toString { ret name; }
109  
}
110  
111  
concept Language {
112  
  int index;
113  
  S code;
114  
}
115  
116  
concept QA {
117  
  int index;
118  
  S questions; // line by line
119  
  S patterns; // to be determined
120  
  S answers; // one answer, or "random { answers separated by empty lines }"
121  
  S category; // "business", "smalltalk" etc.
122  
123  
  transient MMOPattern parsedPattern;
124  
125  
  sS _fieldOrder = "category index questions patterns answers";
126  
  
127  
  void change {
128  
    parsedPattern = null;
129  
    super.change();
130  
  }
131  
  
132  
  MMOPattern parsedPattern() {
133  
    if (parsedPattern == null)
134  
      parsedPattern = mmo_parsePattern(patterns);
135  
    ret parsedPattern;
136  
  }
137  
}
138  
139  
// keep these because otherwise deserialization breaks
140  
concept Settings {}
141  
concept Defaults {}
142  
143  
svoid init {
144  
  processConceptsOverwriteFile();
145  
  //print("fieldOrder", getFieldOrder(QA));
146  
  dbIndexing(Category, 'index, Channel, 'channelID);
147  
  print("QA count: " + countConcepts(QA));
148  
  
149  
  // categories, default category
150  
  int i = 0;
151  
  for (S category : ll("business", "smalltalk", "btd"))
152  
    cset(uniq(Category, name := category), index := i++);
153  
  cset(conceptsWhere QA(category := null), category := "business");
154  
  
155  
  onConceptsChange(r { consistencyCheckResult.clear(); });
156  
  reindexQAs();
157  
}
158  
159  
html { try {
160  
  print(+uri);
161  
    
162  
  // force auth
163  
  /*try answer callHtmlMethod(getBot(mainBotID), "/auth-only", mapPlus(
164  
params, uri := rawLink(uri)));*/
165  
    
166  
  if (eq(uri, "/download"))
167  
    ret serveText(conceptsStructure());
168  
    
169  
  HCRUD_Concepts<QA> data = new HCRUD_Concepts<QA>(QA) {
170  
    S itemName() { ret "question"; }
171  
    
172  
    S fieldHelp(S field) {
173  
      if (eq(field, "index"))
174  
        ret "lower index = higher matching precedence";
175  
      if (eq(field, "patterns"))
176  
        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.]];
177  
      if (eq(field, "answers"))
178  
        ret "Text of answer. Use random { ... } for multiple answers (separated from each other by an empty line)";
179  
      null;
180  
    }
181  
    
182  
    Renderer getRenderer(S field) {
183  
      if (eqOneOf(field, 'questions, 'answers))
184  
        ret new TextArea(80, 10);
185  
      if (eq(field, 'patterns))
186  
        ret new TextField(80);
187  
      if (eq(field, 'category))
188  
        ret new ComboBox(collect name(conceptsSortedByField(Category, 'index)));
189  
      ret super.getRenderer(field);
190  
    }
191  
    
192  
    // sort for display
193  
    L<QA> defaultSort(L<QA> l) {
194  
      ret sortedByCalculatedField(l, q -> pair(lower(q.category), q.index));
195  
    }
196  
  };
197  
  data.onCreateOrUpdate.add(qa -> reindexQAs());
198  
  
199  
  HCRUD crud = new(rawLink(), data) {
200  
    S frame(S title, S contents) {
201  
      title = ahref(or2(shortLink, rawLink("")), adminTitle) + " | " + title;
202  
      ret hhtml(hhead_title_decode(title)
203  
        + hbody(h2(title)
204  
        + p(joinWithVBar(
205  
          targetBlank(rawLink("download"), "export brain"),
206  
          ))
207  
        + contents));
208  
    }
209  
    
210  
    S renderTable(bool withCmds) {
211  
      Scorer<ConsistencyError> scorer = consistencyCheckResult!;
212  
      ret (empty(scorer.errors)
213  
        ? p("Pattern analysis: No problems found. " + nEntries(countConcepts(QA)) + ".")
214  
        : joinMap(scorer.errors, lambda1 renderErrorAsParagraph))
215  
        + super.renderTable(withCmds);
216  
    }
217  
    
218  
    S renderErrorAsParagraph(ConsistencyError error) {
219  
      S fix = error.renderFix();
220  
      ret p("Error: " + htmlEncode2(error.msg) + " " +
221  
        joinMap(error.items, qa -> ahref(editLink(qa.id), "[item]"))
222  
        + (empty(fix) ? "" : " " + fix));
223  
    }
224  
    
225  
    S renderValue(S field, O value) {
226  
      S html = super.renderValue(field, value);
227  
      if (eq(field, "questions")) html = b(html);
228  
      ret html;
229  
    }
230  
  };
231  
  
232  
  // actions
233  
234  
  if (eqGet(params, action := 'reindex)) {
235  
    long id = parseLong(params.get('id));
236  
    int newIndex = parseInt(params.get('newIndex));
237  
    QA qa = getConcept(QA, id);
238  
    if (qa == null) ret crud.refreshWithMsgs("Item not found");
239  
    cset(qa, index := newIndex);
240  
    reindexQAs();
241  
    ret crud.refreshWithMsgs("Index of item " + qa.id + " changed");
242  
  }
243  
244  
  crud.tableClass = "responstable";
245  
  ret hsansserif() + hcss_responstable() + crud.renderPage(params);
246  
 } catch print e {
247  
  ret "ERROR.";
248  
 }
249  
}
250  
251  
// main API function for other bots
252  
sS answer(S s, O... _) {
253  
  optPar Server server;
254  
  QA qa = findMatchingQA(s, qasForServer(server));
255  
  if (qa == null) null;
256  
  S raw = qa.answers;
257  
  S contents = extractKeywordPlusBracketed_keepComments("random", raw);
258  
  if (contents != null)
259  
    ret random(splitAtEmptyLines(contents));
260  
  ret raw;
261  
}
262  
263  
static L<QA> qasForServer(Server server) {
264  
  Set<S> categories = asSet(categoriesForServer(server));
265  
  ret sortQAs(filter(list(QA), qa -> contains(categories, qa.category)));
266  
}
267  
268  
static QA findQAWithTypos(S s, Cl<QA> qas) {
269  
  Lowest<QA> qa = findClosestQA(s, qas);
270  
  if (!qa.has()) null;
271  
  if (qa.score() > maxTypos) {
272  
    print("Rejecting " + nTypos((int) qa.score()) + ": " + s + " / " + qa->patterns);
273  
    null;
274  
  }
275  
  print("Accepting " + nTypos((int) qa.score()) + ": " + s + " / " + qa->patterns);
276  
  ret qa!;
277  
}
278  
279  
static QA findMatchingQA(S s, Cl<QA> qas, bool allowTypos) {
280  
  try object QA qa = findMatchingQA(s, qas);
281  
  if (allowTypos)
282  
    try object QA qa = findQAWithTypos(s, qas);
283  
  null;
284  
}
285  
286  
static Lowest<QA> findClosestQA(S s, Cl<QA> qas) {
287  
  time "findClosestQA" {
288  
    new Lowest<QA> qa;
289  
    findClosestQA(s, qa, qas);
290  
  }
291  
  ret qa;
292  
}
293  
294  
static L<QA> sortQAs(Cl<QA> qas) {
295  
  Map<S, Int> categoryToIndex = fieldToFieldIndex('name, 'index, list(Category));
296  
  ret sortedByCalculatedField(qas, q -> pair(categoryToIndex.get(q.category), q.index));
297  
}
298  
299  
svoid reindexQAs() {
300  
  int index = 1;
301  
  for (QA qa : sortQAs(list(QA)))
302  
    cset(qa, index := index++);
303  
}
304  
305  
static QA findMatchingQA(S s, Cl<QA> qas) {
306  
  for (QA qa : sortQAs(qas))
307  
    if (mmo_match_parsedPattern(qa.parsedPattern(), s))
308  
      ret qa;
309  
  null;
310  
}
311  
312  
svoid findClosestQA(S s, Lowest<QA> best, Cl<QA> qas) {
313  
  for (QA qa : sortQAs(qas)) {
314  
    Int score = mmo_levenWithSwapsScore_parsedPattern(qa.parsedPattern(), s);
315  
    if (score != null)
316  
      best.put(qa, score);
317  
  }
318  
}
319  
320  
sclass ConsistencyError {
321  
  S msg;
322  
  L<QA> items;
323  
  
324  
  *(S *msg, QA... items) { this.items = asList(items); }
325  
  
326  
  S renderFix() { null; }
327  
}
328  
329  
svoid checkLocalConsistency(QA qa, Scorer<ConsistencyError> scorer) {
330  
  LS questions = tlft(qa.questions);
331  
  for (S q : questions)
332  
    if (mmo_match_parsedPattern(qa.parsedPattern(), q))
333  
      scorer.ok();
334  
    else
335  
      scorer.error(ConsistencyError("Question " + quote(q) + " not matched by patterns " + quote(qa.patterns), qa));
336  
}
337  
338  
svoid checkGlobalConsistency(QA qa, Cl<QA> qas, Scorer<ConsistencyError> scorer) {
339  
  for (S q : tlft(qa.questions)) {
340  
    QA found = findMatchingQA(q, qas);
341  
    if (found != null && found != qa)
342  
      scorer.error(new ConsistencyError("Question " + quote(q) + " (item " + qa.id + ") shadowed by patterns " + quote(found.patterns) + " (item " + found.id + ")", qa, found) {
343  
        S renderFix() {
344  
          ret ahref(rawLink("?action=reindex&id=" + qa.id + "&newIndex=" + (found.index-1)), "[fix by changing index]");
345  
        }
346  
      });
347  
    else
348  
      scorer.ok();
349  
  }
350  
}
351  
352  
static Scorer<ConsistencyError> consistencyCheck() {
353  
  new Scorer<ConsistencyError> scorer;
354  
  scorer.collectErrors();
355  
  for (QA qa) checkLocalConsistency(qa, scorer);
356  
  for (Server server) {
357  
    L<QA> qas = qasForServer(server);
358  
    for (QA qa : qas)
359  
      checkGlobalConsistency(qa, qas, scorer);
360  
  }
361  
  ret scorer;
362  
}
363  
364  
// server can be null
365  
static Cl<S> categoriesForServer(Server server) {
366  
  Set<Category> set = asSet(conceptsWhere Category(onByDefault := true));
367  
  if (server != null) for (ServerToCategory link : conceptsWhere(ServerToCategory, +server))
368  
    addOrRemove(set, link.category, link.enabled);
369  
  ret collectAsSet name(set);
370  
}
371  
372  
373  
// API
374  
375  
svoid importQA(virtual QA qa_external) {
376  
  QA qa = shallowCloneToUnlistedConcept QA(qa_external);
377  
  uniq QA(allConceptFieldsAsParams(qa));
378  
}
379  
380  
sS rawLink() { ret "/"; }
381  
sS rawLink(S uri) { ret addSlashPrefix(uri) ; }

Author comment

Began life as a copy of #1026409

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: #1028036
Snippet name: Tomii Boi Answers DB Module [for OS, use with #1026494]
Eternal ID of this version: #1028036/50
Text MD5: 0aa86cc87a2029f24c313e7c3f090ccc
Transpilation MD5: 4e8b27f9083c487ebf58d8a2ce9b14d8
Author: stefan
Category: javax / html
Type: JavaX source code (Dynamic Module)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2020-06-21 00:41:03
Source code size: 10959 bytes / 381 lines
Pitched / IR pitched: No / No
Views / Downloads: 226 / 4148
Version history: 49 change(s)
Referenced in: [show references]