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

332
LINES

< > BotCompany Repo | #1027660 // DynChatBotFrontend

JavaX fragment (include) [tags: use-pretranspiled]

Libraryless. Click here for Pure Java version (25382L/190K).

1  
!include once #1027578 // Named
2  
3  
// Note: db_mainConcepts() doesn't work (because of cases), always use cc!
4  
5  
concept BotName > Named {} // e.g. for finding the module in Cruddie
6  
7  
concept Cmd > ConceptWithGlobalID {
8  
  S patterns; // for mmo_parsePattern
9  
  S exampleInputs; // line-separated
10  
  S cmd; // a star pattern recognized by the backend
11  
  S questionsForArguments; // line-separated
12  
  S conditions;
13  
  int index;
14  
  bool autoGenerated;
15  
16  
  LS translatableFields() { ret splitAtSpace("patterns exampleInputs cmd questionsForArguments"); }
17  
  
18  
  sS importableFields() { ret "cmd conditions exampleInputs globalID index patterns questionsForArguments"; }
19  
  
20  
  transient MMOPattern parsedPattern;
21  
  void change { parsedPattern = null; super.change(); }
22  
  
23  
  MMOPattern parsedPattern() {
24  
    if (parsedPattern == null) parsedPattern = mmo_parsePattern(patterns);
25  
    ret parsedPattern;
26  
  }
27  
  
28  
  sS _fieldOrder = "autoGenerated exampleInputs patterns cmd questionsForArguments conditions";
29  
}
30  
31  
concept Replacement {
32  
  S in, out; // word to replace and which word to replace it with
33  
  S except; // IDs of Cmd concepts to skip
34  
  
35  
  sS _fieldOrder = "in out except";
36  
}
37  
38  
concept Mishearing {
39  
  S in, out;
40  
}
41  
42  
// main class
43  
asclass DynChatBotFrontend extends DynCRUD<Cmd> {
44  
  switchable S backendModuleID;
45  
  switchable S baseThingName = "$thing";
46  
  O currentAttractor;
47  
  transient CRUD<Replacement> replacementsCRUD;
48  
  transient CRUD<Mishearing> mishearingsCRUD;
49  
  transient JTabbedPane tabs;
50  
  transient long lastAccessed = sysNow();
51  
  transient Set currentActivities = syncSet();
52  
  transient bool deleting;
53  
54  
  class HandleArgument implements IF1<S> {
55  
    S cmd;
56  
    LS argQuestions;
57  
    new LS arguments;
58  
59  
    *() {}
60  
    *(S *cmd, LS *argQuestions) {}
61  
62  
    // process an argument
63  
    public S get(S arg) {
64  
      arguments.add(arg);
65  
      if (l(arguments) >= countAsteriskTokens(cmd))
66  
        ret sendToBackend(format_quoteAll(cmd, asObjectArray(arguments)));
67  
      else
68  
        ret handleQuestionWithArguments(this);
69  
    }
70  
71  
    S currentQuestion() {
72  
      ret or2(_get(argQuestions, l(arguments)), "Need argument");
73  
    }
74  
  }
75  
76  
  runnable class Cancel { setField(currentAttractor := null); }
77  
78  
  class UndoHandler implements IF1<Bool, S> {
79  
    public S get(Bool yes) {
80  
      if (!yes) ret "OK"; // user said no
81  
      ret (S) sendToBackend("undo");
82  
    }
83  
  }
84  
85  
  void start {
86  
    cc = dm_handleCaseIDField();
87  
    super.start();
88  
    dm_watchFieldAndNow backendModuleID(r updateModuleName);
89  
    
90  
    crud.multiLineFields = litset("exampleInputs", "questionsForArguments");
91  
    crud.sorter = func(Cl<Cmd> l) -> L<Cmd> { sortByField index(l) };
92  
    crud.formFixer = 48;
93  
    
94  
    replacementsCRUD = new CRUD(cc, Replacement);
95  
    mishearingsCRUD = new CRUD(cc, Mishearing);
96  
    
97  
    dm_vmBus_onMessage_q objectTypesChanged((module, names) -> {
98  
      if (dm_isSame(module, backend()))
99  
        importReplacements();
100  
    });
101  
  }
102  
  
103  
  void importReplacements() {
104  
    LS names = cast dm_call(backend(), 'objectTypes);
105  
    print("Got names: " + names);
106  
    if (names != null) {
107  
      deleteConcepts(cc, Replacement);
108  
      for (S name : names)
109  
        if (neqic(name, baseThingName))
110  
          cnew(cc, Replacement, in := baseThingName, out := name);
111  
    }
112  
    applyReplacements();
113  
  }
114  
115  
  S handleCommand(Cmd cmd) {
116  
    if (cmd == null) null;
117  
 
118  
    print("handleCommand " + cmd.cmd);
119  
    
120  
    if (match("undo after confirm", cmd.cmd)) {
121  
      O undo = dm_call(backend(), 'lastUndo);
122  
      if (undo == null) ret "Nothing to undo";
123  
      new WaitForAnswer_YesNo attractor;
124  
      attractor.question = "Undo " + undo + "?";
125  
      setField(currentAttractor := attractor);
126  
      print("Set attractor to: " + attractor);
127  
      attractor.processAnswer = new UndoHandler;
128  
      attractor.cancelSilently = new Cancel;
129  
      ret attractor.question;
130  
    }
131  
    
132  
    if (hasAsteriskTokens(cmd.cmd))
133  
      ret handleQuestionWithArguments(
134  
        new HandleArgument(cmd.cmd, tlft(cmd.questionsForArguments)));
135  
    else
136  
      ret sendToBackend(cmd.cmd);
137  
  }
138  
139  
  S handleQuestionWithArguments(HandleArgument handler) {
140  
    new WaitForName attractor;
141  
    attractor.question = handler.currentQuestion();
142  
    setField(currentAttractor := attractor);
143  
    print("Set attractor to: " + attractor);
144  
    attractor.processName = handler;
145  
    attractor.cancelSilently = new Cancel;
146  
    ret attractor.question;
147  
  }
148  
149  
  S sendToBackend(S cmd) {
150  
    ret (S) dm_call(backend(), 'answer, cmd);
151  
  }
152  
  
153  
  S backend() {
154  
    ret backendModuleID;
155  
  }
156  
  
157  
  visual {
158  
    JComponent mainCRUD = super.visualize();
159  
160  
    addComponents(crud.buttons,
161  
      jbutton("Talk to me", rThread talkToMe),
162  
      jPopDownButton_noText(popDownButtonParams()));
163  
164  
    ret tabs = jtabs(
165  
      "Commands" := mainCRUD,
166  
      "Replacements" := replacementsCRUD.visualize(),
167  
      "Mishearings" := mishearingsCRUD.visualize());
168  
  }
169  
  
170  
  O[] popDownButtonParams() {
171  
    ret litobjectarray(
172  
      "Apply replacements", rThread applyReplacements,
173  
      "Import replacements", rThread importReplacements,
174  
      "Consistency check", rThread consistencyCheck,
175  
      "Import commands from snippet...", rThread importCmdsFromSnippet);
176  
  }
177  
  
178  
  void talkToMe enter { dm_showConversationPopupForModule(); }
179  
180  
  void consistencyCheck enter {
181  
    reindex();
182  
    L<Cmd> cmds = list(Cmd);
183  
    L<MMOConsistencyError> errors = mmo_consistencyCheck(
184  
      map(cmds, cmd -> pair(cmd.exampleInputs, cmd.patterns)));
185  
    if (empty(errors)) infoBox("No consistency problems");
186  
    else
187  
      dm_showText(n2(errors, "consistency problem"), lines(errors));
188  
  }
189  
190  
  void reindex {
191  
    int index = 1;
192  
    for (Cmd cmd : conceptsSortedByFields(cc, Cmd, 'autoGenerated, 'index))
193  
      cset(cmd, index := index++);
194  
  }
195  
  
196  
  void applyReplacements {
197  
    deleteConceptsWhere(cc, Cmd, autoGenerated := true);
198  
    for (Cmd cmd)
199  
      for (Replacement r) {
200  
        if (jcontains(r.except, str(cmd.id))) continue;
201  
        SS map = litcimap(r.in, r.out, plural(r.in), plural(r.out));
202  
        Cmd cmd2 = shallowCloneUnlisted(cmd);
203  
        cset(cmd2, autoGenerated := true, globalID := aGlobalIDObject());
204  
        for (S field : cmd.translatableFields())
205  
          cset(cmd2, field, fixAOrAn(replacePhrases(map, getString(cmd, field))));
206  
        if (anyFieldsDifferent(cmd, cmd2, cmd.translatableFields()))
207  
          registerConcept(cc, cmd2);
208  
      }
209  
    consistencyCheck();
210  
  }
211  
  
212  
  bool unloadBackendTooWhenUnloaded() { true; }
213  
  
214  
  // API
215  
  
216  
  // for initial fill of translation table. probably doesn't catch many patterns usually
217  
  void copyCommandsFromBackend() {
218  
    for (S cmd : allIfQuotedPatterns(loadSnippet(beforeSlash(dm_moduleLibID(backend())))))
219  
      uniqConcept(+cmd);
220  
  }
221  
  
222  
  S preprocess(S s) {
223  
    S s1 = s;
224  
    s = replacePhrases(fieldToFieldIndexCI('in, 'out, list(Mishearing)), s);
225  
    if (neq(s, s1)) print("Corrected to: " + s);
226  
    ret s;
227  
  }
228  
  
229  
  S answer(S s) {
230  
    if (deleting) fail("Deleting module");
231  
    touch();
232  
    afterwards { touch(); }
233  
    temp tempAddToCollection(currentActivities, new Var("Answering: " + s)); // abusing Var to allow duplicates
234  
    s = preprocess(s);
235  
    if (currentAttractor != null) {
236  
      print("Sending to attractor " + currentAttractor + ": " + s);
237  
      S a = strOrNull(call(currentAttractor, 'answer, s));
238  
      if (a != null) {
239  
        print("Attractor said: " + a);
240  
        ret a;
241  
      }
242  
    }
243  
    try S a = answer_other(s);
244  
    try S a = answer_cmds(s);
245  
    try S a = sendToBackend(s);
246  
    ret answer_other_lowPrio(s);
247  
  }
248  
  
249  
  // overridable
250  
  S answer_other(S s) { null; }
251  
  
252  
  // overridable
253  
  S answer_other_lowPrio(S s) { null; }
254  
255  
  // returns null iff not handled
256  
  S answer_cmds(S s) null {  
257  
    L<Cmd> cmds = filter(conceptsSortedByField(cc, Cmd, 'index),
258  
      c -> nempty(c.patterns) && checkCondition(c));
259  
    Cmd cmd = mmo_matchMultiWithTypos(1, cmds, c -> c.parsedPattern(), s);
260  
    if (cmd != null) ret unnull(handleCommand(cmd));
261  
  }
262  
  
263  
  bool checkCondition(Cmd cmd) {
264  
    true;
265  
  }
266  
  
267  
  // e.g. from snippet #1027616
268  
  void importCmds(S src) {
269  
    L l = dynShortNamed Cmd(safeUnstructList(src));
270  
    int imported = 0;
271  
    for (O o : l) {
272  
      S id = getString globalID(o);
273  
      if (empty(id)) continue;
274  
      ++imported;
275  
      GlobalID globalID = GlobalID(id);
276  
      Cmd cmd = uniq_sync(cc, Cmd, +globalID);
277  
      for (S field : splitAtSpace(Cmd.importableFields()))
278  
        cSmartSet(cmd, field, getOpt(o, field));
279  
    }
280  
    topLeftInfoBox("Imported/updated " + nEntries(imported));
281  
    if (imported != 0)
282  
      importReplacements();
283  
  }
284  
  
285  
  void importCmdsFromSnippet(S snippetID) {
286  
    print("Importing cmds from " + snippetID);
287  
    importCmds(loadSnippet(snippetID));
288  
  }
289  
  
290  
  // import if we have no cmds yet
291  
  void importCmdsFromSnippetIfEmpty(S snippetID) {
292  
    if (conceptCount() == 0)
293  
      importCmdsFromSnippet(snippetID);
294  
  }
295  
  
296  
  void importCmdsFromSnippet enter {
297  
    selectSnippetID("Commands to import", vf<S> importCmdsFromSnippet);
298  
  }
299  
  
300  
  void connectToBackend(S backendModuleID) {
301  
    setField(+backendModuleID);
302  
  }
303  
  
304  
  void setBotName(S name) {
305  
    cset(uniq(cc, BotName), +name);
306  
    updateModuleName();
307  
  }
308  
  
309  
  // may return null or empty string
310  
  S botName() {
311  
    ret getString name(conceptWhere(cc, BotName));
312  
  }
313  
  
314  
  S getBotName() { ret botName(); }
315  
  
316  
  void updateModuleName enter {
317  
    S name = botName();
318  
    if (empty(name)) name = dm_moduleName(backend());
319  
    dm_setModuleName("Frontend for " + name);
320  
  }
321  
  
322  
  void touch { setField(lastAccessed := sysNow()); }
323  
  
324  
  void deleteYourself {
325  
    setField(deleting := true);
326  
    if (unloadBackendTooWhenUnloaded()) {
327  
      print("Deleting backend " + backendModuleID);
328  
      dm_deleteModule(backendModuleID);
329  
    }
330  
    dm_deleteModule();
331  
  }
332  
}

Author comment

Began life as a copy of #1027602

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: #1027660
Snippet name: DynChatBotFrontend
Eternal ID of this version: #1027660/22
Text MD5: b4bf15117f950888d63b0fd2bc355d5f
Transpilation MD5: a14e0d32510a77107abc65a772c6c435
Author: stefan
Category: javax
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2020-07-05 18:39:13
Source code size: 10024 bytes / 332 lines
Pitched / IR pitched: No / No
Views / Downloads: 252 / 741
Version history: 21 change(s)
Referenced in: [show references]