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 | } |
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] |