static JTable table; static JTextArea chat; static JTextField input; static new L log; static L recommendations; static int listDelay = 2000; static Bool thinking; static new Thinker thinker; static S systemPrefix = "[system]"; static S dialog = "new"; svoid randomMain { //substanceLAF("EmeraldDusk"); // Too dark! substanceLAF("ChallengerDeep"); loadLog(); logEvent("Starting"); thinker.startUp(log); table = tableWithTooltips(); chat = autoScroll(wordWrapTextArea()); chat.setText(joinLines(log)); input = new JTextField; JFrame frame = showFrame(vgrid(centerAndSouth(chat, input), table)); //setFrameIconLater(frame, "#1003593"); onEnter(input, r { post(); }); onDoubleClick(table, voidfunc(int row) { chooseSuggestion(row); }); for (int i = 1; i <= 12; i++) { final int _i = i; registerFunctionKey(frame, i, r { chooseSuggestion(_i-1); }); } onUpdate(input, r { updateOnce(); }); updateOnce(); input.requestFocus(); if (isAction(last(log)) && confirmYesNo(input, "Run action? " + last(log))) action(last(log)); } static S getInput() { ret joinLines(" # ", input.getText().trim()); } static void post() { S i = getInput(); if (inputAllowedByUser(i)) post(i); } svoid chooseSuggestion(int row) { L line = getTableLine(table, row); if (line == null) ret; Map map = getTableLineAsMap(table, row); S s = line.get(0); if (empty(s)) ret; new L topSuggesters; for (int i = 0; i < row; i++) topSuggesters.add(getTableLineAsMap(table, i)); if (empty(topSuggesters)) topSuggesters = null; logEvent("Suggestion chosen", mapPlus(map, "Row", row+1, "Input", getInput(), "Top Suggesters", topSuggesters)); input.setText(s); post(); } svoid logEvent(S type) { logEvent(type, litmap()); } svoid logEvent(S type, Map map) { logStructure(new File(dialogDir(), "event.log"), ll(type, chatTime(), map)); } static bool inputAllowedByUser(S i) { ret !swic(i, systemPrefix); } // may be called from other thread static void postSystemMessage(final S msg) { if (nempty(msg)) awtIfNecessary { post(systemPrefix + " " + msg); } } static void post(S i) { S s = i + "\n"; chat.append(s); appendToFile(logFile(), "[" + chatTime() + "] " + s); logEvent("Posting", litmap("Text", s)); synchronized(mc()) { log.add(i); } input.selectAll(); updateOnce(); try { action(i); } catch e { printStackTrace(e); postSystemMessage(exceptionToStringShort(e)); } } static bool isAction(S s) { if (s == null) false; s = dropBracketPrefix(s); // e.g. "[bot]" ret s.startsWith("!"); } static void action(S s) { s = dropBracketPrefix(s); // e.g. "[bot]" if (!s.startsWith("!")) ret; s = dropPrefix("!", s); final S _s = s; thread "Action" { loading { try { randomCmds(_s); actionImpl(_s); // user must provide this } catch e { printStackTrace(e); postSystemMessage("Error - " + exceptionToStringShort(e)); } } } } static void fillList(bool force) { bool t = force || shouldUpdateList(); if (neq(t, thinking)) { thinking = t; setFrameIcon(table, t ? "#1003603" : "#1003593"); } if (!t) againl8r(); else thread "Fill List" { final new L data; thinker.makeListData(cloneList(log), getInput(), data); dataToTable_uneditable(table, data); againl8r(); } } static void updateOnce() { fillList(true); } static void againl8r() { swingAfter(table, listDelay, r { fillList(false); }); } static bool shouldUpdateList() { ret getFrame(table).isFocused() && !mouseInComponent(table); } // also called from outside static L loadLog() { log.clear(); for (S s : toLines(loadTextFile(logFile()))) pcall { log.add(substring(s, s.indexOf(']')+1).trim()); } ret log; } synchronized static L getLastFromLog(int n) { ret cloneList(getLast(log, n)); } static File dialogDir() { ret prepareProgramFile(dialog); } static File logFile() { ret new File(dialogDir(), "log.txt"); } static void switchDialog(final S name) { swingAndWait(r { dialog = name; loadLog(); thinker = new Thinker; thinker.startUp(log); chat.setText(joinLines(log)); }); } static void randomCmds(S s) { new Matches m; if "dialog *" { switchDialog(m.unq(0)); postSystemMessage("OK, dialog switched to " + quote(dialog)); } }