static S generatorsID; static JTable table, chatTable; static JTextArea chat; static JTextField input; static L log = synchroList(); static L recommendations; static L thinkThreads = synchroList(); static JLabel status; static int listDelay = 2000; static int maxLineLength = 1000; static int maxLongStringLength = 100*1000; static Bool thinking; static new Thinker thinker; static S systemPrefix = "[system]"; static S dialog = "new"; svoid randomMain { //substanceLAF("EmeraldDusk"); // Too dark! substanceLAF("ChallengerDeep"); table = tableWithTooltips(); chat = autoScroll(wordWrapTextArea()); chatTable = tableWithTooltips(); input = new JTextField; status = new JLabel(" "); JFrame frame = showFrame(vgrid(centerAndSouth( jtabs(1, "Chat", chat, "Details", chatTable), input), centerAndSouth(table, status))); //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 { chooseSuggestionForEditing(_i-1); }); registerShiftFunctionKey(frame, i, r { chooseSuggestion(_i-1); }); } onUpdate(input, r { updateOnce(); }); loadDialog(); logEvent("Starting"); updateOnce(); input.requestFocus(); if (isAction(last(log)) && confirmYesNo(input, "Run action? " + last(log))) action(last(log)); } static S getInput() { ret joinLines(" # ", input.getText().trim()); } sv post() { postAsUser(getInput(), null); } static void postAsUser(S i, Map infos) { if (inputAllowedByUser(i)) post(i, infos); } svoid chooseSuggestionForEditing(int row) { Map map = getTableLineAsMap(table, row); if (map == null) ret; rankToTop(map); S s = trim(unnull(map.get("Suggestion"))); logEvent("Suggestion chosen for editing", mapPlus(map, "Row", row+1, "Input", getInput(), "Top Suggesters", topSuggesters())); input.setText(s); input.selectAll(); input.requestFocus(); } svoid rankToTop(Map map) { // Table cells have been structure'd by dataToTable thinker.rankToTop(first((L) unstructure(getString(map, "Suggesters")))); } svoid chooseSuggestion(int row) { Map map = getTableLineAsMap(table, row); if (map == null) ret; rankToTop(map); S s = trim(unnull(map.get("Suggestion"))); if (empty(s)) ret; //logEvent("Suggestion chosen", mapPlus(map, "Row", row+1, "Input", getInput(), "Top Suggesters", topSuggesters)); input.setText(s); postAsUser(s, mapPlus(map, "Index", row+1)); } static L topSuggesters() { int n = 20; n = min(n, tableRows(table)); new L topSuggesters; for (int i = 0; i < n; i++) topSuggesters.add(getTableLineAsMap(table, i)); //if (empty(topSuggesters)) topSuggesters = null; ret topSuggesters; } 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 (empty(msg)) ret; awtIfNecessary { post(systemPrefix + " " + msg, litmap("By", "System")); } } static void post(S i, Map infos) { try { i = trim(i); if (empty(i)) ret; //i = escapeNewLines(i); infos = mapPlus(infos, "Top Suggesters", topSuggesters()); bool tooLong = l(i) > maxLongStringLength; if (l(i) > maxLineLength) { S id = saveLongString(i); i = substring(i, 0, maxLineLength) + "... [" + (tooLong ? "too " : "") + "long text " + id + "]"; } } catch e { printStackTrace(e); i = systemPrefix + " " + exceptionToStringShort(e); } S s = i + "\n"; chat.append(escapeNewLines(i) + "\n"); appendToFile(logFile(), "[" + chatTime() + "] " + s); logEvent("Posting", litmap("Text", i, "Infos", infos)); log.add(i); updateChatTable(); input.selectAll(); updateOnce(); try { action(i); } catch e { printStackTrace(e); postSystemMessage(exceptionToStringShort(e)); } } static S dropActionPrefix(S s) { if (s == null) null; s = dropBracketPrefix(s); // e.g. "[bot]" if (!s.startsWith("!")) null; ret s.substring(1); } static bool isAction(S s) { ret dropActionPrefix(s) != null; } static void action(S s) { s = dropActionPrefix(s); if (s == null) ret; final S _s = s; thread "Action" { loading { try { genLog_set(getLog()); // 'case user needs it randomsOwnCmds(_s); actionImpl(_s); // user must provide this } catch e { printStackTrace(e); postSystemMessage("Error - " + exceptionToStringShort(e)); } } } } static volatile bool again; // This logic is bad... static void fillList(bool force) { bool t = force || shouldUpdateList(); if (neq(t, thinking)) { thinking = t; setFrameIcon(table, t ? "#1003603" : "#1003593"); } if (!t) { if (!force) againl8r(); } else { if (nempty(thinkThreads)) { again = true; ret; } fillListImpl(); } } static void fillListImpl() { thread "Fill List" { try { thinkThreads.add(currentThread()); final new L data; thinker.makeListData(cloneList(log), getInput(), data); awt { pcall { dataToTable_uneditable(table, data); tableColumnMaxWidth(table, 0, 30); // "Key" column } againl8r(); } } finally { thinkThreads.remove(currentThread()); if (again) { again = false; fillListImpl(); } } } } static void updateOnce() { fillList(true); } static void againl8r() { swingAfter(table, listDelay, r { fillList(false); }); } static bool shouldUpdateList() { bool result = false; S text = " "; if (getFrame(table).isFocused()) { result = !mouseInComponent(table); text = result ? " Thinking... " : "Not thinking cause you got the mouse in there"; } status.setText(text); ret result; } // also called from outside static L loadLog() { log.clear(); log.addAll(collect(scanEventLogForPosts(dialogDir()), "text")); ret log; } synchronized static L getLastFromLog(int n) { ret cloneList(getLast(log, n)); } synchronized static L getLog() { ret cloneList(log); } 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; loadDialog(); }); } static void loadDialog() { loadLog(); thinker = new Thinker; thinker.startUp(log); chat.setText(joinLines(log)); updateChatTable(); } static void randomsOwnCmds(S s) { new Matches m; if "dialog *" { switchDialog(m.unq(0)); // TODO: show current dialog somewhere else //postSystemMessage("OK, dialog switched to " + quote(dialog)); } if "gen" generators = null; if "delete" updateChatTable(); } static void updateChatTable() { assertAWT(); new L data; L l = scanLog_safeUnstructure(new File(dialogDir(), "event.log")); for (int i = 0; i < l(l); i++) pcall { L a = l.get(i), prev = get(l, i-1); if (firstIs(a, "Posting")) { Map map = cast get(a, 2); S text = getString(map, "Text").trim(); if (eq(text, "!delete")) { removeLast(data); continue; } S idx = ""; Map infos = cast map.get("Infos"); if (infos != null && infos.containsKey("Index")) idx = str(infos.get("Index")); else pcall { //printStruct("prev: ", prev); if (prev != null && firstIs(prev, "Suggestion chosen for editing")) { Map m = getMap(prev, 2); S suggestion = getString(m, "Suggestion"); //print("Suggestion: " + structure(suggestion)); idx = str(get(m, "Row")); if (neq(suggestion, text)) idx += "?"; } } data.add(litorderedmap("Text", escapeNewLines(text), "SI" /* Suggestion Index */, idx)); } } dataToTable_uneditable(chatTable, data); tableColumnMaxWidth(chatTable, 1, 40); // enough for 2 digits and a "?" scrollTableDown(chatTable); } static synchronized S saveLongString(S s) { s = substring(s, 0, maxLongStringLength); S id; File f; do { id = randomID(10); f = getProgramFile("long-strings/" + id); } while (f.exists()); saveTextFile(f, s); ret id; } static O generators; static void makeGenerators(L l, L log) { synchronized(main.class) { if (!isSnippetID(generatorsID)) fail("No generators ID set"); if (generators == null) generators = hotwire(generatorsID); } new L l2; call(generators, "makeGenerators", l2, log); l.addAll((L) quickImport(l2)); }