!7 concept Cmd { S patterns; // for mmo_parsePattern S cmd; // a pattern recognized by #1027443 int index; transient MMOPattern parsedPattern; void change { parsedPattern = null; super.change(); } MMOPattern parsedPattern() { if (parsedPattern == null) parsedPattern = mmo_parsePattern(patterns); ret parsedPattern; } sS _fieldOrder = "patterns cmd index"; } cmodule ScenariosFrontend > DynCRUD { S backendModuleLibID = "#1027443/Scenarios"; S handleCommand(Cmd cmd) { if (cmd == null) null; print("handleCommand " + cmd.cmd); if (hasAsteriskTokens(cmd.cmd)) ret "Need arguments"; else ret (S) dm_call(getBackend(), 'answer, cmd.cmd); } S getBackend() { ret dm_require(backendModuleLibID); } afterVisualize { addComponent(crud.buttons, jbutton("Talk to me", rThread talkToMe)); } void talkToMe enter { dm_showConversationPopupForModule(); } // API void copyCommandsFromBackend() { for (S cmd : allIfQuotedPatterns(loadSnippet(#1027443))) uniqConcept(+cmd); } S answer(S s) { ret handleCommand(mmo_matchMultiWithTypos(1, conceptsSortedByField Cmd('index), c -> c.parsedPattern(), s)); } }