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).

!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();
  }
}

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: 250 / 737
Version history: 21 change(s)
Referenced in: [show references]