Libraryless. Click here for Pure Java version (25382L/190K).
!include once #1027578 // Named // Note: db_mainConcepts() doesn't work (because of cases), always use cc! concept BotName > Named {} // e.g. for finding the module in Cruddie concept Cmd > ConceptWithGlobalID { S patterns; // for mmo_parsePattern S exampleInputs; // line-separated S cmd; // a star pattern recognized by the backend S questionsForArguments; // line-separated S conditions; int index; bool autoGenerated; LS translatableFields() { ret splitAtSpace("patterns exampleInputs cmd questionsForArguments"); } sS importableFields() { ret "cmd conditions exampleInputs globalID index patterns questionsForArguments"; } transient MMOPattern parsedPattern; void change { parsedPattern = null; super.change(); } MMOPattern parsedPattern() { if (parsedPattern == null) parsedPattern = mmo_parsePattern(patterns); ret parsedPattern; } sS _fieldOrder = "autoGenerated exampleInputs patterns cmd questionsForArguments conditions"; } concept Replacement { S in, out; // word to replace and which word to replace it with S except; // IDs of Cmd concepts to skip sS _fieldOrder = "in out except"; } concept Mishearing { S in, out; } // main class asclass DynChatBotFrontend extends DynCRUD<Cmd> { switchable S backendModuleID; switchable S baseThingName = "$thing"; O currentAttractor; transient CRUD<Replacement> replacementsCRUD; transient CRUD<Mishearing> mishearingsCRUD; transient JTabbedPane tabs; transient long lastAccessed = sysNow(); transient Set currentActivities = syncSet(); transient bool deleting; class HandleArgument implements IF1<S> { S cmd; LS argQuestions; new LS arguments; *() {} *(S *cmd, LS *argQuestions) {} // process an argument public S get(S arg) { arguments.add(arg); if (l(arguments) >= countAsteriskTokens(cmd)) ret sendToBackend(format_quoteAll(cmd, asObjectArray(arguments))); else ret handleQuestionWithArguments(this); } S currentQuestion() { ret or2(_get(argQuestions, l(arguments)), "Need argument"); } } runnable class Cancel { setField(currentAttractor := null); } class UndoHandler implements IF1<Bool, S> { public S get(Bool yes) { if (!yes) ret "OK"; // user said no ret (S) sendToBackend("undo"); } } void start { cc = dm_handleCaseIDField(); super.start(); dm_watchFieldAndNow backendModuleID(r updateModuleName); crud.multiLineFields = litset("exampleInputs", "questionsForArguments"); crud.sorter = func(Cl<Cmd> l) -> L<Cmd> { sortByField index(l) }; crud.formFixer = 48; replacementsCRUD = new CRUD(cc, Replacement); mishearingsCRUD = new CRUD(cc, Mishearing); dm_vmBus_onMessage_q objectTypesChanged((module, names) -> { if (dm_isSame(module, backend())) importReplacements(); }); } void importReplacements() { LS names = cast dm_call(backend(), 'objectTypes); print("Got names: " + names); if (names != null) { deleteConcepts(cc, Replacement); for (S name : names) if (neqic(name, baseThingName)) cnew(cc, Replacement, in := baseThingName, out := name); } applyReplacements(); } S handleCommand(Cmd cmd) { if (cmd == null) null; print("handleCommand " + cmd.cmd); if (match("undo after confirm", cmd.cmd)) { O undo = dm_call(backend(), 'lastUndo); if (undo == null) ret "Nothing to undo"; new WaitForAnswer_YesNo attractor; attractor.question = "Undo " + undo + "?"; setField(currentAttractor := attractor); print("Set attractor to: " + attractor); attractor.processAnswer = new UndoHandler; attractor.cancelSilently = new Cancel; ret attractor.question; } if (hasAsteriskTokens(cmd.cmd)) ret handleQuestionWithArguments( new HandleArgument(cmd.cmd, tlft(cmd.questionsForArguments))); else ret sendToBackend(cmd.cmd); } S handleQuestionWithArguments(HandleArgument handler) { new WaitForName attractor; attractor.question = handler.currentQuestion(); setField(currentAttractor := attractor); print("Set attractor to: " + attractor); attractor.processName = handler; attractor.cancelSilently = new Cancel; ret attractor.question; } S sendToBackend(S cmd) { ret (S) dm_call(backend(), 'answer, cmd); } S backend() { ret backendModuleID; } visual { JComponent mainCRUD = super.visualize(); addComponents(crud.buttons, jbutton("Talk to me", rThread talkToMe), jPopDownButton_noText(popDownButtonParams())); ret tabs = jtabs( "Commands" := mainCRUD, "Replacements" := replacementsCRUD.visualize(), "Mishearings" := mishearingsCRUD.visualize()); } O[] popDownButtonParams() { ret litobjectarray( "Apply replacements", rThread applyReplacements, "Import replacements", rThread importReplacements, "Consistency check", rThread consistencyCheck, "Import commands from snippet...", rThread importCmdsFromSnippet); } void talkToMe enter { dm_showConversationPopupForModule(); } void consistencyCheck enter { reindex(); L<Cmd> cmds = list(Cmd); L<MMOConsistencyError> errors = mmo_consistencyCheck( map(cmds, cmd -> pair(cmd.exampleInputs, cmd.patterns))); if (empty(errors)) infoBox("No consistency problems"); else dm_showText(n2(errors, "consistency problem"), lines(errors)); } void reindex { int index = 1; for (Cmd cmd : conceptsSortedByFields(cc, Cmd, 'autoGenerated, 'index)) cset(cmd, index := index++); } void applyReplacements { deleteConceptsWhere(cc, Cmd, autoGenerated := true); for (Cmd cmd) for (Replacement r) { if (jcontains(r.except, str(cmd.id))) continue; SS map = litcimap(r.in, r.out, plural(r.in), plural(r.out)); Cmd cmd2 = shallowCloneUnlisted(cmd); cset(cmd2, autoGenerated := true, globalID := aGlobalIDObject()); for (S field : cmd.translatableFields()) cset(cmd2, field, fixAOrAn(replacePhrases(map, getString(cmd, field)))); if (anyFieldsDifferent(cmd, cmd2, cmd.translatableFields())) registerConcept(cc, cmd2); } consistencyCheck(); } bool unloadBackendTooWhenUnloaded() { true; } // API // for initial fill of translation table. probably doesn't catch many patterns usually void copyCommandsFromBackend() { for (S cmd : allIfQuotedPatterns(loadSnippet(beforeSlash(dm_moduleLibID(backend()))))) uniqConcept(+cmd); } S preprocess(S s) { S s1 = s; s = replacePhrases(fieldToFieldIndexCI('in, 'out, list(Mishearing)), s); if (neq(s, s1)) print("Corrected to: " + s); ret s; } S answer(S s) { if (deleting) fail("Deleting module"); touch(); afterwards { touch(); } temp tempAddToCollection(currentActivities, new Var("Answering: " + s)); // abusing Var to allow duplicates s = preprocess(s); if (currentAttractor != null) { print("Sending to attractor " + currentAttractor + ": " + s); S a = strOrNull(call(currentAttractor, 'answer, s)); if (a != null) { print("Attractor said: " + a); ret a; } } try S a = answer_other(s); try S a = answer_cmds(s); try S a = sendToBackend(s); ret answer_other_lowPrio(s); } // overridable S answer_other(S s) { null; } // overridable S answer_other_lowPrio(S s) { null; } // returns null iff not handled S answer_cmds(S s) null { L<Cmd> cmds = filter(conceptsSortedByField(cc, Cmd, 'index), c -> nempty(c.patterns) && checkCondition(c)); Cmd cmd = mmo_matchMultiWithTypos(1, cmds, c -> c.parsedPattern(), s); if (cmd != null) ret unnull(handleCommand(cmd)); } bool checkCondition(Cmd cmd) { true; } // e.g. from snippet #1027616 void importCmds(S src) { L l = dynShortNamed Cmd(safeUnstructList(src)); int imported = 0; for (O o : l) { S id = getString globalID(o); if (empty(id)) continue; ++imported; GlobalID globalID = GlobalID(id); Cmd cmd = uniq_sync(cc, Cmd, +globalID); for (S field : splitAtSpace(Cmd.importableFields())) cSmartSet(cmd, field, getOpt(o, field)); } topLeftInfoBox("Imported/updated " + nEntries(imported)); if (imported != 0) importReplacements(); } void importCmdsFromSnippet(S snippetID) { print("Importing cmds from " + snippetID); importCmds(loadSnippet(snippetID)); } // import if we have no cmds yet void importCmdsFromSnippetIfEmpty(S snippetID) { if (conceptCount() == 0) importCmdsFromSnippet(snippetID); } void importCmdsFromSnippet enter { selectSnippetID("Commands to import", vf<S> importCmdsFromSnippet); } void connectToBackend(S backendModuleID) { setField(+backendModuleID); } void setBotName(S name) { cset(uniq(cc, BotName), +name); updateModuleName(); } // may return null or empty string S botName() { ret getString name(conceptWhere(cc, BotName)); } S getBotName() { ret botName(); } void updateModuleName enter { S name = botName(); if (empty(name)) name = dm_moduleName(backend()); dm_setModuleName("Frontend for " + name); } void touch { setField(lastAccessed := sysNow()); } void deleteYourself { setField(deleting := true); if (unloadBackendTooWhenUnloaded()) { print("Deleting backend " + backendModuleID); dm_deleteModule(backendModuleID); } dm_deleteModule(); } }
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: | 250 / 737 |
Version history: | 21 change(s) |
Referenced in: | [show references] |