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

381
LINES

< > BotCompany Repo | #1028036 // Tomii Boi Answers DB Module [for OS, use with #1026494]

JavaX source code (Dynamic Module) [tags: use-pretranspiled] - run with: Stefan's OS

Uses 911K of libraries. Click here for Pure Java version (22739L/129K).

!7

cmodule TomiiBoiAnswers > DynPrintLogAndEnabled {
  switchable int port = 8080;
  switchable S frontendName = "tomiiBoiDiscordBot";
  
  transient CRUD<Category> categoriesCRUD;
  transient CRUD<Server> serversCRUD;
  transient CRUD<Channel> channelsCRUD;
  transient CRUD<ServerToCategory> serverToCategoryCRUD;

  start {
    init();
    categoriesCRUD = new CRUD(Category);
    serversCRUD = new CRUD(Server);
    serverToCategoryCRUD = new CRUD(ServerToCategory);
    channelsCRUD = new CRUD(Channel);

    thread {
      dm_serveHttpFromFunction(port, lambda2 html);
      print("Admin live at: http://localhost:" + port);
      dm_registerAs('tomiiBoiQA);
    }
  }
  
  visualize {
    JComponent c = super.visualize();
    addComponents(buttons,
      jbutton("Open admin in browser", rThread { openURLInBrowser("http://localhost:" + port) }),
      jPopDownButton_noText(
        "Import data...", rThreadEnter importData));
    c = jtabs(
      "Main", c,
      "Categories", categoriesCRUD.visualize(),
      "Servers", serversCRUD.visualize(),
      "Server-to-category", serverToCategoryCRUD.visualize(),
      "Channels", channelsCRUD.visualize());
    channelsCRUD.addButton("Update list", rThreadEnter grabChannels);
    ret c;
  }
  
  void importData {
    selectFile("Tomii Brain File", voidfunc(File f) enter {
      replaceConceptsWithTextFileOnNextStart(f);
      dm_reloadModule();
    });
  }

  void grabChannels {
    dm_call(frontendName, 'grabChannels);
  }

  // API

  Server addServer(S serverID, S name) {
    ret csetAndReturn(uniq Server(+serverID), +name);
  }

  Channel addChannel(Server server, S channelID, S name) {
    ret csetAndReturn(uniq Channel(+server, +channelID), +name);
  }

  Channel channelForID(S channelID) {
    ret conceptWhere Channel(+channelID);
  }
}

//sS mainBotID = #1026411;
static int maxTypos = 1;

sS adminTitle = "Tomii Boi Chat Bot Admin";
sS shortLink; // catchy link to admin if available

sS embedderLink; // where chat bot will go
sS goDaddyEmbedCode;

static Cache<Scorer<ConsistencyError>> consistencyCheckResult = new(lambda0 consistencyCheck);

concept Server {
  S serverID, name;

  toString { ret name; }
}

concept ServerToCategory {
  Server server;
  Category category;
  bool enabled;

  sS _fieldOrder = "server category enabled";
}

concept Channel {
  Server server;
  S channelID;
  S name;
  bool botEnabled = true;

  toString { ret name; }
  sS _fieldOrder = "botEnabled name server";
}

concept Category {
  int index;
  S name;
  bool onByDefault = true;

  toString { ret name; }
}

concept Language {
  int index;
  S code;
}

concept QA {
  int index;
  S questions; // line by line
  S patterns; // to be determined
  S answers; // one answer, or "random { answers separated by empty lines }"
  S category; // "business", "smalltalk" etc.

  transient MMOPattern parsedPattern;

  sS _fieldOrder = "category index questions patterns answers";
  
  void change {
    parsedPattern = null;
    super.change();
  }
  
  MMOPattern parsedPattern() {
    if (parsedPattern == null)
      parsedPattern = mmo_parsePattern(patterns);
    ret parsedPattern;
  }
}

// keep these because otherwise deserialization breaks
concept Settings {}
concept Defaults {}

svoid init {
  processConceptsOverwriteFile();
  //print("fieldOrder", getFieldOrder(QA));
  dbIndexing(Category, 'index, Channel, 'channelID);
  print("QA count: " + countConcepts(QA));
  
  // categories, default category
  int i = 0;
  for (S category : ll("business", "smalltalk", "btd"))
    cset(uniq(Category, name := category), index := i++);
  cset(conceptsWhere QA(category := null), category := "business");
  
  onConceptsChange(r { consistencyCheckResult.clear(); });
  reindexQAs();
}

html { try {
  print(+uri);
    
  // force auth
  /*try answer callHtmlMethod(getBot(mainBotID), "/auth-only", mapPlus(
params, uri := rawLink(uri)));*/
    
  if (eq(uri, "/download"))
    ret serveText(conceptsStructure());
    
  HCRUD_Concepts<QA> data = new HCRUD_Concepts<QA>(QA) {
    S itemName() { ret "question"; }
    
    S fieldHelp(S field) {
      if (eq(field, "index"))
        ret "lower index = higher matching precedence";
      if (eq(field, "patterns"))
        ret [[Phrases to match. Use "+" as "and" operator, commas as "or" operator. Round brackets for grouping. Quotes for special characters. Put exclamation mark after phrase to disable typo correction.]];
      if (eq(field, "answers"))
        ret "Text of answer. Use random { ... } for multiple answers (separated from each other by an empty line)";
      null;
    }
    
    Renderer getRenderer(S field) {
      if (eqOneOf(field, 'questions, 'answers))
        ret new TextArea(80, 10);
      if (eq(field, 'patterns))
        ret new TextField(80);
      if (eq(field, 'category))
        ret new ComboBox(collect name(conceptsSortedByField(Category, 'index)));
      ret super.getRenderer(field);
    }
    
    // sort for display
    L<QA> defaultSort(L<QA> l) {
      ret sortedByCalculatedField(l, q -> pair(lower(q.category), q.index));
    }
  };
  data.onCreateOrUpdate.add(qa -> reindexQAs());
  
  HCRUD crud = new(rawLink(), data) {
    S frame(S title, S contents) {
      title = ahref(or2(shortLink, rawLink("")), adminTitle) + " | " + title;
      ret hhtml(hhead_title_decode(title)
        + hbody(h2(title)
        + p(joinWithVBar(
          targetBlank(rawLink("download"), "export brain"),
          ))
        + contents));
    }
    
    S renderTable(bool withCmds) {
      Scorer<ConsistencyError> scorer = consistencyCheckResult!;
      ret (empty(scorer.errors)
        ? p("Pattern analysis: No problems found. " + nEntries(countConcepts(QA)) + ".")
        : joinMap(scorer.errors, lambda1 renderErrorAsParagraph))
        + super.renderTable(withCmds);
    }
    
    S renderErrorAsParagraph(ConsistencyError error) {
      S fix = error.renderFix();
      ret p("Error: " + htmlEncode2(error.msg) + " " +
        joinMap(error.items, qa -> ahref(editLink(qa.id), "[item]"))
        + (empty(fix) ? "" : " " + fix));
    }
    
    S renderValue(S field, O value) {
      S html = super.renderValue(field, value);
      if (eq(field, "questions")) html = b(html);
      ret html;
    }
  };
  
  // actions

  if (eqGet(params, action := 'reindex)) {
    long id = parseLong(params.get('id));
    int newIndex = parseInt(params.get('newIndex));
    QA qa = getConcept(QA, id);
    if (qa == null) ret crud.refreshWithMsgs("Item not found");
    cset(qa, index := newIndex);
    reindexQAs();
    ret crud.refreshWithMsgs("Index of item " + qa.id + " changed");
  }

  crud.tableClass = "responstable";
  ret hsansserif() + hcss_responstable() + crud.renderPage(params);
 } catch print e {
  ret "ERROR.";
 }
}

// main API function for other bots
sS answer(S s, O... _) {
  optPar Server server;
  QA qa = findMatchingQA(s, qasForServer(server));
  if (qa == null) null;
  S raw = qa.answers;
  S contents = extractKeywordPlusBracketed_keepComments("random", raw);
  if (contents != null)
    ret random(splitAtEmptyLines(contents));
  ret raw;
}

static L<QA> qasForServer(Server server) {
  Set<S> categories = asSet(categoriesForServer(server));
  ret sortQAs(filter(list(QA), qa -> contains(categories, qa.category)));
}

static QA findQAWithTypos(S s, Cl<QA> qas) {
  Lowest<QA> qa = findClosestQA(s, qas);
  if (!qa.has()) null;
  if (qa.score() > maxTypos) {
    print("Rejecting " + nTypos((int) qa.score()) + ": " + s + " / " + qa->patterns);
    null;
  }
  print("Accepting " + nTypos((int) qa.score()) + ": " + s + " / " + qa->patterns);
  ret qa!;
}

static QA findMatchingQA(S s, Cl<QA> qas, bool allowTypos) {
  try object QA qa = findMatchingQA(s, qas);
  if (allowTypos)
    try object QA qa = findQAWithTypos(s, qas);
  null;
}

static Lowest<QA> findClosestQA(S s, Cl<QA> qas) {
  time "findClosestQA" {
    new Lowest<QA> qa;
    findClosestQA(s, qa, qas);
  }
  ret qa;
}

static L<QA> sortQAs(Cl<QA> qas) {
  Map<S, Int> categoryToIndex = fieldToFieldIndex('name, 'index, list(Category));
  ret sortedByCalculatedField(qas, q -> pair(categoryToIndex.get(q.category), q.index));
}

svoid reindexQAs() {
  int index = 1;
  for (QA qa : sortQAs(list(QA)))
    cset(qa, index := index++);
}

static QA findMatchingQA(S s, Cl<QA> qas) {
  for (QA qa : sortQAs(qas))
    if (mmo_match_parsedPattern(qa.parsedPattern(), s))
      ret qa;
  null;
}

svoid findClosestQA(S s, Lowest<QA> best, Cl<QA> qas) {
  for (QA qa : sortQAs(qas)) {
    Int score = mmo_levenWithSwapsScore_parsedPattern(qa.parsedPattern(), s);
    if (score != null)
      best.put(qa, score);
  }
}

sclass ConsistencyError {
  S msg;
  L<QA> items;
  
  *(S *msg, QA... items) { this.items = asList(items); }
  
  S renderFix() { null; }
}

svoid checkLocalConsistency(QA qa, Scorer<ConsistencyError> scorer) {
  LS questions = tlft(qa.questions);
  for (S q : questions)
    if (mmo_match_parsedPattern(qa.parsedPattern(), q))
      scorer.ok();
    else
      scorer.error(ConsistencyError("Question " + quote(q) + " not matched by patterns " + quote(qa.patterns), qa));
}

svoid checkGlobalConsistency(QA qa, Cl<QA> qas, Scorer<ConsistencyError> scorer) {
  for (S q : tlft(qa.questions)) {
    QA found = findMatchingQA(q, qas);
    if (found != null && found != qa)
      scorer.error(new ConsistencyError("Question " + quote(q) + " (item " + qa.id + ") shadowed by patterns " + quote(found.patterns) + " (item " + found.id + ")", qa, found) {
        S renderFix() {
          ret ahref(rawLink("?action=reindex&id=" + qa.id + "&newIndex=" + (found.index-1)), "[fix by changing index]");
        }
      });
    else
      scorer.ok();
  }
}

static Scorer<ConsistencyError> consistencyCheck() {
  new Scorer<ConsistencyError> scorer;
  scorer.collectErrors();
  for (QA qa) checkLocalConsistency(qa, scorer);
  for (Server server) {
    L<QA> qas = qasForServer(server);
    for (QA qa : qas)
      checkGlobalConsistency(qa, qas, scorer);
  }
  ret scorer;
}

// server can be null
static Cl<S> categoriesForServer(Server server) {
  Set<Category> set = asSet(conceptsWhere Category(onByDefault := true));
  if (server != null) for (ServerToCategory link : conceptsWhere(ServerToCategory, +server))
    addOrRemove(set, link.category, link.enabled);
  ret collectAsSet name(set);
}


// API

svoid importQA(virtual QA qa_external) {
  QA qa = shallowCloneToUnlistedConcept QA(qa_external);
  uniq QA(allConceptFieldsAsParams(qa));
}

sS rawLink() { ret "/"; }
sS rawLink(S uri) { ret addSlashPrefix(uri) ; }

Author comment

Began life as a copy of #1026409

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: #1028036
Snippet name: Tomii Boi Answers DB Module [for OS, use with #1026494]
Eternal ID of this version: #1028036/50
Text MD5: 0aa86cc87a2029f24c313e7c3f090ccc
Transpilation MD5: 4e8b27f9083c487ebf58d8a2ce9b14d8
Author: stefan
Category: javax / html
Type: JavaX source code (Dynamic Module)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2020-06-21 00:41:03
Source code size: 10959 bytes / 381 lines
Pitched / IR pitched: No / No
Views / Downloads: 308 / 4259
Version history: 49 change(s)
Referenced in: #1026494 - Tomii Boi Discord Bot [in-OS db version, use with #1028036]
#1028043 - Tomii Boi Answers DB Module backup 1
#1029471 - Tomii Boi Discord Bot Extension [in-OS db version, use with #1028036]