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

374
LINES

< > BotCompany Repo | #1028039 // Tomii Boi Answers DB Module v2 [dev., with category CRUD]

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

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

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 by priority (category + index)
193  
    L<QA> defaultSort(L<QA> l) {
194  
      ret sortQAs(l);
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  
  
226  
  // actions
227  
228  
  if (eqGet(params, action := 'reindex)) {
229  
    long id = parseLong(params.get('id));
230  
    int newIndex = parseInt(params.get('newIndex));
231  
    QA qa = getConcept(QA, id);
232  
    if (qa == null) ret crud.refreshWithMsgs("Item not found");
233  
    cset(qa, index := newIndex);
234  
    reindexQAs();
235  
    ret crud.refreshWithMsgs("Index of item " + qa.id + " changed");
236  
  }
237  
238  
  ret crud.renderPage(params);
239  
 } catch print e {
240  
  ret "ERROR.";
241  
 }
242  
}
243  
244  
// main API function for other bots
245  
sS answer(S s, O... _) {
246  
  optPar Server server;
247  
  QA qa = findMatchingQA(s, qasForServer(server));
248  
  if (qa == null) null;
249  
  S raw = qa.answers;
250  
  S contents = extractKeywordPlusBracketed_keepComments("random", raw);
251  
  if (contents != null)
252  
    ret random(splitAtEmptyLines(contents));
253  
  ret raw;
254  
}
255  
256  
static L<QA> qasForServer(Server server) {
257  
  Set<S> categories = asSet(categoriesForServer(server));
258  
  ret sortQAs(filter(list(QA), qa -> contains(categories, qa.category)));
259  
}
260  
261  
static QA findQAWithTypos(S s, Cl<QA> qas) {
262  
  Lowest<QA> qa = findClosestQA(s, qas);
263  
  if (!qa.has()) null;
264  
  if (qa.score() > maxTypos) {
265  
    print("Rejecting " + nTypos((int) qa.score()) + ": " + s + " / " + qa->patterns);
266  
    null;
267  
  }
268  
  print("Accepting " + nTypos((int) qa.score()) + ": " + s + " / " + qa->patterns);
269  
  ret qa!;
270  
}
271  
272  
static QA findMatchingQA(S s, Cl<QA> qas, bool allowTypos) {
273  
  try object QA qa = findMatchingQA(s, qas);
274  
  if (allowTypos)
275  
    try object QA qa = findQAWithTypos(s, qas);
276  
  null;
277  
}
278  
279  
static Lowest<QA> findClosestQA(S s, Cl<QA> qas) {
280  
  time "findClosestQA" {
281  
    new Lowest<QA> qa;
282  
    findClosestQA(s, qa, qas);
283  
  }
284  
  ret qa;
285  
}
286  
287  
static L<QA> sortQAs(Cl<QA> qas) {
288  
  Map<S, Int> categoryToIndex = fieldToFieldIndex('name, 'index, list(Category));
289  
  ret sortedByCalculatedField(qas, q -> pair(categoryToIndex.get(q.category), q.index));
290  
}
291  
292  
svoid reindexQAs() {
293  
  int index = 1;
294  
  for (QA qa : sortQAs(list(QA)))
295  
    cset(qa, index := index++);
296  
}
297  
298  
static QA findMatchingQA(S s, Cl<QA> qas) {
299  
  for (QA qa : sortQAs(qas))
300  
    if (mmo_match_parsedPattern(qa.parsedPattern(), s))
301  
      ret qa;
302  
  null;
303  
}
304  
305  
svoid findClosestQA(S s, Lowest<QA> best, Cl<QA> qas) {
306  
  for (QA qa : sortQAs(qas)) {
307  
    Int score = mmo_levenWithSwapsScore_parsedPattern(qa.parsedPattern(), s);
308  
    if (score != null)
309  
      best.put(qa, score);
310  
  }
311  
}
312  
313  
sclass ConsistencyError {
314  
  S msg;
315  
  L<QA> items;
316  
  
317  
  *(S *msg, QA... items) { this.items = asList(items); }
318  
  
319  
  S renderFix() { null; }
320  
}
321  
322  
svoid checkLocalConsistency(QA qa, Scorer<ConsistencyError> scorer) {
323  
  LS questions = tlft(qa.questions);
324  
  for (S q : questions)
325  
    if (mmo_match_parsedPattern(qa.parsedPattern(), q))
326  
      scorer.ok();
327  
    else
328  
      scorer.error(ConsistencyError("Question " + quote(q) + " not matched by patterns " + quote(qa.patterns), qa));
329  
}
330  
331  
svoid checkGlobalConsistency(QA qa, Cl<QA> qas, Scorer<ConsistencyError> scorer) {
332  
  for (S q : tlft(qa.questions)) {
333  
    QA found = findMatchingQA(q, qas);
334  
    if (found != null && found != qa)
335  
      scorer.error(new ConsistencyError("Question " + quote(q) + " (item " + qa.id + ") shadowed by patterns " + quote(found.patterns) + " (item " + found.id + ")", qa, found) {
336  
        S renderFix() {
337  
          ret ahref(rawLink("?action=reindex&id=" + qa.id + "&newIndex=" + (found.index-1)), "[fix by changing index]");
338  
        }
339  
      });
340  
    else
341  
      scorer.ok();
342  
  }
343  
}
344  
345  
static Scorer<ConsistencyError> consistencyCheck() {
346  
  new Scorer<ConsistencyError> scorer;
347  
  scorer.collectErrors();
348  
  for (QA qa) checkLocalConsistency(qa, scorer);
349  
  for (Server server) {
350  
    L<QA> qas = qasForServer(server);
351  
    for (QA qa : qas)
352  
      checkGlobalConsistency(qa, qas, scorer);
353  
  }
354  
  ret scorer;
355  
}
356  
357  
// server can be null
358  
static Cl<S> categoriesForServer(Server server) {
359  
  Set<Category> set = asSet(conceptsWhere Category(onByDefault := true));
360  
  if (server != null) for (ServerToCategory link : conceptsWhere(ServerToCategory, +server))
361  
    addOrRemove(set, link.category, link.enabled);
362  
  ret collectAsSet name(set);
363  
}
364  
365  
366  
// API
367  
368  
svoid importQA(virtual QA qa_external) {
369  
  QA qa = shallowCloneToUnlistedConcept QA(qa_external);
370  
  uniq QA(allConceptFieldsAsParams(qa));
371  
}
372  
373  
sS rawLink() { ret "/"; }
374  
sS rawLink(S uri) { ret addSlashPrefix(uri) ; }

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: #1028039
Snippet name: Tomii Boi Answers DB Module v2 [dev., with category CRUD]
Eternal ID of this version: #1028039/26
Text MD5: 3baf40478096f158b2eda8375ba9cae3
Transpilation MD5: 6e4050b2e0101d3b8a7e2f3811fd201e
Author: stefan
Category:
Type: JavaX source code (Dynamic Module)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2020-05-07 19:46:59
Source code size: 10306 bytes / 374 lines
Pitched / IR pitched: No / No
Views / Downloads: 174 / 473
Version history: 25 change(s)
Referenced in: [show references]