Warning: session_start(): open(/var/lib/php/sessions/sess_qtd23dmstb9i8e68m0u77gjiso, O_RDWR) failed: No space left on device (28) in /var/www/tb-usercake/models/config.php on line 51
Warning: session_start(): Failed to read session data: files (path: /var/lib/php/sessions) in /var/www/tb-usercake/models/config.php on line 51
!7
// See #1023660 for the older 99 lines version
!include once #1024472 // agi.blue core db
// global constants
static SS searchTypeToText = litmap(
leven := "Leven",
literal := "Literal",
scored := "Scored");
// global vars
static volatile bool started, broken;
static int sourceCodeLines;
static TimedCache sizeOnDisk = new(60.0, f guessClusteredSizeOfProgramDirWithoutBackups);
static RealAGIBlue agiBlue;
static Renderer renderer = new Renderer1;
set flag AllPublic.
sS classesToShare = [[
DynamicObject Concept Concepts Page Entry Slice LoadedSlice
AbstractEntry Signer Session User GoogleUser MinimalUser CookieUser
SliceInfo CentralIndexEntry AGIBlue
GlobalID Renderer
]];
// end of global data
p {
agiBlue = new RealAGIBlue(false);
}
svoid pClone {
agiBlue = new RealAGIBlue(true);
}
svoid cleanMeUp {
cleanUp(agiBlue);
agiBlue = null;
}
sinterface Renderer {
O html(S uri, SS params);
}
set flag NoNanoHTTPD. html {
ret renderer.html(uri, params);
}
sclass RealAGIBlue > AGIBlue {
*(bool *isClone) {
if (isClone) ret;
try {
if (bootstrapDataFrom(#1023558)) deleteMyBackups();
thinMyBackups();
fan = slicesLoadedOnDemand(loadedSlices);
mainConcepts = fan.get("", null);
// index main DB
indexConceptFields(Slice, 'globalID, Slice, 'caseID);
indexConceptField(CookieUser, 'cookie);
idx_slicesByName = new ConceptFieldIndexCI(Slice, 'name);
idx_slicesByModification = new ConceptFieldIndexDesc(Slice, '_modified);
idx_usersByName = new ConceptFieldIndexCI(GoogleUser, 'googleLastName);
idx_usersBySeen = new ConceptFieldIndexDesc(User, 'lastSeen);
cset(uniq_returnIfNew Slice(caseID := ""), name := "main slice");
loadedSlices.get("").initialSetup(toGlobalIDObj(mainSliceGlobalID()));
loadedSlices.get("").sliceConcept = sliceConceptForCaseID("");
backgroundFan = slicesLoadedOnDemand(backgroundSlices);
backgroundFan.lock = fan.lock;
// legacy conversions!
//deleteConcepts(Slice, globalID := GlobalID('wftlawbagrwprywn));
// Approve this machine's key
PKIKeyPair machineKey = agiBot_trustedKeyForMachine();
if (machineKey != null) {
print("Approving this machine's key: " + machineKey.publicKey);
cset(uniq_sync(Signer, publicKey := machineKey.publicKey), trusted := true, approvedBy := "local");
}
sourceCodeLines = countLines(mySource());
rst_index.trigger();
} catch print e {
set broken;
} finally {
set started;
}
// do stuff after we are officially started
// calculate DB size regularly iff there were changes
sizeOnDisk.keepValueWhileCalculating = true;
doEveryAndNow(60.0, rWatcher(() -> db_mainConceptsChangeCount(), r { sizeOnDisk! }));
} // end of main program
L entriesOnPage(Page p) {
ret p == null ? null : sortedByField count(findBackRefs(p, Entry));
}
L pagesSortedByLength(L l) {
ret sortedByCalculatedField(l, p -> l(p.q));
}
sbool agiBlue_isOriginal() {
ret amProgram(#1023558);
}
S agiBlueURL(S subUri) { ret agiBlueURL() + prependSlash(subUri); }
S agiBlueURL() {
ret agiBlue_isOriginal() ? "https://agi.blue" : "/" + psI(programID()) + "/raw";
}
S agiBlueName() {
ret agiBlue_isOriginal() ? "agi.blue" : "agi.blue clone " + programID();
}
void cleanMeUp {
cleanUp(fan);
}
LoadedSlice loadSlice(Slice slice) {
ret loadSlice(slice.caseID, str(slice.globalID));
}
LoadedSlice loadSlice(S caseID, S globalID) {
caseID = unnull(caseID);
lock fan.lock;
// move slice from background to foreground
Concepts cc = backgroundFan.getIfLoaded(caseID);
if (cc != null) {
LoadedSlice slice = assertNotNull(backgroundSlices.get(caseID));
backgroundFan.loaded.remove(caseID);
fan.loaded.put(caseID, cc);
backgroundSlices.remove(caseID);
loadedSlices.put(caseID, slice);
ret slice;
}
fan.get(caseID, globalID);
ret loadedSlices.get(caseID);
}
// load background slice
LoadedSlice loadBackgroundSlice(Slice slice) {
ret loadBackgroundSlice(slice.caseID, str(slice.globalID));
}
LoadedSlice loadBackgroundSlice(S caseID, S globalID) {
caseID = unnull(caseID);
lock fan.lock;
// check if in foreground
if (fan.getIfLoaded(caseID) != null)
ret loadedSlices.get(caseID);
backgroundFan.get(caseID, globalID);
LoadedSlice slice = assertNotNull(backgroundSlices.get(caseID));
slice.lastAccess = sysNow();
while (l(backgroundSlices) > maxBackgroundSlices)
unloadLeastRecentlyAccessedBackgroundSlice();
assertNotNull("slice.sliceConcept", slice.sliceConcept);
ret slice;
}
// TODO: sync unloading slice with accessors
void unloadLeastRecentlyAccessedBackgroundSlice() {
lock fan.lock;
S caseID = lowestByField lastAccess(keys(backgroundSlices));
if (caseID != null)
backgroundFan.unloadCase(caseID);
}
Slice sliceConceptForGlobalID(S globalID) {
ret sliceConceptForGlobalID(toGlobalIDObj(globalID));
}
Slice sliceConceptForGlobalID(GlobalID globalID) {
ret conceptWhere Slice(+globalID);
}
Slice sliceConceptForCaseID(S caseID) {
ret conceptWhere Slice(+caseID);
}
Slice sliceForName(S name) {
ret conceptWhereCI Slice(+name);
}
class Query extends WorksOnSlice {
SS vars = ciMap();
*(LoadedSlice slice) { super(slice); }
O process(S query) {
try object processLines(agiBlue_parseQueryScript(query));
ret serveJSON("No return statement");
}
O processLines(L lines) {
for (ALQLLine line : lines) {
if (line cast ALQLSlice) {
Slice slice = sliceForName(line.slice);
if (slice == null) ret serveJSON("Slice not found: " + line.slice);
LoadedSlice oldSlice = Query.this.slice;
try {
setSlice(loadSlice(slice));
try object processLines(line.contents);
} finally {
setSlice(oldSlice);
}
} else if (line cast ALQLReturn)
ret serveJSON(ll(getOrKeep(vars, line.var)));
else if (line cast ALQLTriple) {
T3S t = line.triple;
t = tripleMap(t, s -> getOrKeep(vars, s));
Cl pages;
S var;
if (isDollarVar(t.c)) {
var = t.c;
if (isDollarVar(t.a)) {
if (isDollarVar(t.b)) todo(t);
Entry e = random(conceptsWhereCI(cc, Entry, key := t.b));
if (e == null) ret serveJSON("No results for " + var);
pages = pagesForKeyAndValue(t.b, t.c);
vars.put(t.a, e.page->q);
vars.put(t.c, e.value);
continue;
} else if (isDollarVar(t.b)) {
Page page = findPageFromQ(t.a);
Entry e = random(findBackRefs(page, Entry));
if (e == null) ret serveJSON("No results for " + var);
vars.put(t.b, e.key);
vars.put(t.c, e.value);
continue;
} else {
S val = getValue(t.a, t.b);
if (val == null) ret serveJSON("No results for " + var);
pages = ll(pageFromQ(val));
}
} else if (isDollarVar(t.b)) {
var = t.b;
if (isDollarVar(t.c)) todo(t);
if (isDollarVar(t.a)) {
L entries = conceptsWhereCI(cc, Entry, value := t.c);
if (empty(entries)) ret serveJSON("No results for " + var);
Entry e = random(entries);
vars.put(t.a, e.page->q);
vars.put(t.b, e.key);
continue;
} else {
Cl keys = keysForPageAndValue(t.a, t.c);
if (empty(keys)) ret serveJSON("No results for " + var);
pages = map(f pageFromQ, keys);
}
} else {
var = t.a;
if (!isDollarVar(t.a)) todo(t);
if (isDollarVar(t.b)) todo(t);
if (isDollarVar(t.c)) todo(t);
pages = pagesForKeyAndValue(t.b, t.c);
}
if (empty(pages)) ret serveJSON("No results for " + var);
vars.put(var, random(pages).q);
} else if (line cast ALQLPage) {
if (eq(line.matchMethod, 'eqic))
findPageFromQ(line.page);
else if (eq(line.matchMethod, 'flexMatchDollarVarsIC_first)) {
new L l;
for (Page p : list(cc, Page))
addIfNotNull(l, flexMatchDollarVarsIC_first(line.page, p.q));
if (empty(l)) ret serveJSON("No results for " + curly(line.page));
vars.putAll(random(l));
} else
fail("Unknown match method: " + line.matchMethod);
} else
fail("Can't interpret: " + line);
}
null;
}
}
/*S globalIDFromCaseID(S caseID) {
ret assertGlobalID(takeLast(globalIDLength(), caseID));
}*/
S agiBlue_pageURLWithSlice(Page p) {
ret p == null ? null : agiBlueURL() + hquery(slice := _get(sliceForPage(p), 'globalID), q := p.q);
}
Slice sliceForPage(Page p) {
if (p == null) null;
SliceInfo info = conceptWhere(p._concepts, SliceInfo);
ret info == null ? null : sliceConceptForGlobalID(info.globalID);
}
GlobalID mainSliceGlobalID() {
ret conceptWhere Slice(caseID := "").globalID;
}
T3S entryToTriple(Entry e) {
ret e == null ? null : t3(e.page->q, e.key, e.value);
}
ConceptsLoadedOnDemand slicesLoadedOnDemand(Map loadedSlices) {
new ConceptsLoadedOnDemand fan;
fan.onCaseLoaded(voidfunc(S caseID, O globalID, Concepts concepts) {
concepts.miscMap = mapPutOrCreate(concepts.miscMap, agiBlue := RealAGIBlue.this);
LoadedSlice ls = new(caseID, concepts);
ls.sliceConcept = sliceConceptForCaseID(caseID);
if (nempty(globalID))
ls.initialSetup(toGlobalIDObj((S) globalID));
loadedSlices.put(caseID, ls);
});
fan.onUnloadingCase(voidfunc(S caseID, Concepts concepts) {
loadedSlices.remove(caseID);
});
ret fan;
}
// check if slices are loaded both in foreground and background (BAD)
S checkDoubleLoads() {
LS l = sharedKeys(loadedSlices, backgroundSlices);
ret empty(l) ? null : "ERROR: Slice loaded in background and foreground: " + first(l);
}
// just use size of concepts.structure.gz
long estimatedSliceDataSize(Slice slice) {
ret estimatedSliceDataSize(slice.caseID);
}
long estimatedSliceDataSize(S caseID) {
ret l(conceptsFile(fan.dbID(caseID)));
}
CentralIndexEntry centralIndexGet(S s) { ret getOrCreate CentralIndexEntry(centralIndex, s); }
CentralIndexEntry newCentralIndexGet(S s) { ret getOrCreate CentralIndexEntry(newCentralIndex, s); }
Cl centralIndexGetSlices(S s) {
CentralIndexEntry e = centralIndexGet(s);
ret e == null ? null : e.slices;
}
// restart magic!
void softRestart { // TODO
print("Cloning...");
cloningSince = sysNow();
Class c = hotwire(programID());
call(c, 'pClone);
AGIBlue clone = cast get(c, 'agiBlue);
copyFields(mc(), c, 'mainConcepts);
copyAllFields(RealAGIBlue.this, clone);
setOpt(clone, isClone := true);
setOpt(clone, clonedSince := cloningSince);
agiBlue = clone;
print("Cloned.");
// TODO: any clean up of this instance?
}
}
sclass WorksOnSlice {
Concepts cc;
LoadedSlice slice;
*() {}
*(LoadedSlice *slice) { cc = slice.cc; }
void setSlice(LoadedSlice slice) {
this.slice = slice;
cc = slice.cc;
}
Page findPageFromParams(Map map) {
S q = getString q(map);
ret empty(q) ? null : findPageFromQ(q);
}
Page findOrMakePageFromParams(Map map) {
ret pageFromQ(getString q(map));
}
Page findPageFromQ(S q) {
ret conceptWhereCI(cc, Page, +q);
}
Page pageFromQ(S q) {
ret slice.pageFromQ(q);
}
Set pagesForKeyAndValue(S key, S value) {
L entries = conceptsWhereIC(cc, Entry, +value, +key);
ret asSet(ccollect page(entries));
}
Cl keysForPageAndValue(S q, S value) {
Page page = findPageFromQ(q);
if (page == null) null;
ret collect key(conceptsWhereIC(cc, Entry, +page, +value));
}
// DB functions
bool hasPage(S q) { ret hasConceptWhereIC(Page, +q); }
S getValue(Page page, S key) {
ret page == null || empty(key) ? null : getString value(highestByField count(objectsWhereIC(findBackRefs(page, Entry), +key)));
}
S getValue(S page, S key) {
ret getValue(findPageFromQ(page), key);
}
S pageDisplayName(Page page) {
/*S name = getValue(page, "read as");
bool unnaturalName = nempty(name) && !eq(makeAGIDomain(name), page.url);
ret unnaturalName ? name + " " + squareBracketed(page.url) : or2(name, unpackAGIDomainOpt(page.url));*/
ret page.q;
}
S renderThing(S s, bool forceURLDisplay) {
ret forceURLDisplay || isURL(s) || isAGIDomain(s)
? ahref(fixAGILink(absoluteURL(s)), htmlencode2(shorten(displayLength, s)))
: ahref(agiBlue_linkForPhrase(s,
slice := slice.isMainSlice() ? null : slice.sliceConcept.globalID),
htmlencode2(shorten(displayLength, s)));
}
GlobalID sliceID() { ret slice.globalID(); }
} // end of WorksOnSlice
sclass Renderer1 implements Renderer {
// Serve page
O html(S uri, SS params) {
sleepWhile(() -> !started);
if (broken) ret subBot_serve500("Internal error");
ret new Request().serve(uri, params);
}
// forward things to AGIBlue object
delegate loadSlice to agiBlue.
delegate agiBlueName to agiBlue.
delegate idx_slicesByModification to agiBlue.
delegate idx_slicesByName to agiBlue.
delegate idx_usersBySeen to agiBlue.
delegate idx_usersByName to agiBlue.
delegate agiBlue_isOriginal to agiBlue.
delegate searchResultsToShow to agiBlue.
class Request extends WorksOnSlice {
S cookie;
Session session;
S uri;
SS params;
long started = sysNow();
S q, domain;
Set get;
// when serving a concept page, info that may be grabbed
// by thought bots
L usesAsKey;
// should also work for standalone
bool isHomeDomain() {
S domain = domain();
ret eqic(domain, "www.agi.blue") || !ewic(domain, ".agi.blue");
}
O serve(S uri, SS params) {
this.uri = dropMultipleSlashes(uri);
this.params = params;
new Matches m;
print(uri + " ? " + unnull(subBot_query()));
// get cookie & session
cookie = cookieFromUser();
if (cookie != null)
{
session = uniq_sync(Session, +cookie);
if (session.user! == null)
cset(session, user := uniq_sync CookieUser(+cookie));
} else
session = unlistedWithValues(Session);
// check if user wants to change slices
S selectSlice = params.get('slice);
if (isGlobalID(selectSlice))
cset(session, selectedSlice := selectSlice);
if (session.selectedSlice == null)
cset(session, selectedSlice := str(mainSliceGlobalID()));
// get slice's case ID
S caseID = "";
Slice sliceConcept = sliceConceptForGlobalID(session.selectedSlice);
print("Selected slice: " + session.selectedSlice + ", obj? " + (sliceConcept != null));
if (sliceConcept != null) caseID = sliceConcept.caseID;
print("caseID: " + caseID);
// load slice
slice = assertNotNull(loadSlice(caseID, session.selectedSlice));
cc = slice.cc;
// Check for special URIs
if (swic(uri, "/bot/")) ret serveBot();
if (swic(uri, "/user/", m)) {
User user = conceptWhere User(globalID := toGlobalIDObj(assertGlobalID(m.rest())));
if (user == null) ret subBot_serve404("User not found");
S title = "User " + user;
ret hhtml_miniPage(title, h3(agiBlueNameHTML() + " | " + title)
+ "User type: " + classShortName(user));
}
// eleu appends a slash to the URI if it's a single identifier, so we drop it again
S uri2 = dropTrailingSlash(uri);
if (eqic(uri2, "/google-verify")) {
print("Google-verify started.");
Payload payload = printStruct(googleVerifyUserToken2(googleSignInID(), params.get("token")));
if (payload == null) ret print("google-verify", "No");
S email = payload.getEmail();
if (empty(email)) ret print("google-verify", "No");
GoogleUser user = uniqCI_sync GoogleUser(googleEmail := email);
cset(user,
googleEmailVerified := payload.getEmailVerified(),
googleFirstName := strOrNull(payload.get("given_name")),
googleLastName := strOrNull(payload.get("family_name")));
cset(session,
+user);
ret print("google-verify", payload.getEmail() + " " + (payload.getEmailVerified() ? "(verified)" : "(not verified)"));
}
cset(session.user!, lastSeen := now());
if (eqic(uri2, "/users")) ret serveUsersList();
if (eqic(uri2, "/slices")) ret serveSlicesList();
if (eqic(uri2, "/search")) ret serveScoredSearch();
if (eqic(uri2, "/literalSearch")) ret serveLiteralSearch();
if (eqic(uri2, "/levenSearch")) ret serveLevenSearch();
if (eqic(uri2, "/query")) ret serveQueryPage();
if (eqic(uri2, "/createSlice")) ret serveCreateSlicePage();
if (eqic(uri2, "/deletePage")) ret serveDeletePage();
if (eqic(uri2, "/checkboxes")) ret serveCheckboxes();
if (eqic(uri2, "/multiAdd")) ret serveMultiAdd();
if (eqic(uri2, "/restart") && authed()) {
softRestart();
ret "OK";
}
q = params.get('q);
get = asCISet(nempties(subBot_paramsAsMultiMap().get('get)));
domain = or2(params.get('domain), domain());
S raw = firstKeyWithValue("", params); // agi.blue?something
if (nempty(raw) && empty(q)) q = raw;
/*if (nempty(q)) {
domain = makeAGIDomain(q);
if (l(domain) > maximumDomainPartLength()) // escape with "domain="
ret hrefresh(agiBlueURL() + hquery(+domain, key := "read as", value := q));
ret hrefresh("http://" + domain + (eq(q, domain) ? "" : "/" + hquery(key := "read as", value := q)));
//uri = "/"; replaceMapWithParams(params, key := "read as", value := q);
}*/
S url = domain + dropTrailingSlash(uri);
// domain to query
//if (empty(q)) q = url;
if (empty(q)) {
S qq = agiBlue_urlToQuery(url);
if (neqic(qq, "agi.blue")) q = qq;
}
Page page, bool newPage = unpair uniqCI2_sync(cc, Page, +q);
if (newPage) dbLog("New page", +q);
//printStructs(+params, +raw, +q);
if (empty(params.get('q)) && empty(raw) && isHomeDomain()) {
//L pages = sortedByFieldDesc _modified(list(cc, Page)); // TODO: use index
L pages = cloneList(slice.idx_latestChangedPages.objectIterator());
int start = parseInt(params.get("start")), step = 100;
S nav = pageNav2("/", l(pages), start, step, "start");
S content = hform(b("GIVE ME INPUT: ") + htextinput('q, autofocus := true) + " " + hsubmit("Ask"))
+ h1(sliceAsHTML() + " has " + nPages(countConcepts(cc, Page)) + " and " + nConnections(countConcepts(cc, Entry)))
+ p(nav)
+ p_nemptyLines(map(pageToHTMLLink(), subList(pages, start, start+step)));
ret hhtml_agiBlue(hhead_title("Slice " + sliceConcept().name + " | agi.blue") // SERVE SLICE HOME PAGE
+ hbody(hfullcenterAndTopLeft(top() + content
+ footer(),
sliceSelector()
)));
}
S key = trim(params.get('key)), value = trim(params.get('value));
L entries = agiBlue.new GetEntriesAndPost(cc).go(page, params).entries;
//S get = params.get('get);
if (nempty(get))
ret serveJSON(collect value(llNotNulls(firstThat(entries, e -> get.contains(e.key)))));
S key2 = key, value2 = value; if (nempty(key) && nempty(value)) key2 = value2 = ""; // input reset
bool withHidden = eq(params.get('withHidden), "1");
new Set hide;
if (!withHidden) for (Entry e : entries) if (eqic(e.key, 'hide) && isSquareBracketedInt(e.value)) addAll(hide, e.count, parseInt(unSquareBracket(e.value)));
new MultiMap mmMeta;
for (Entry e : entries) if (isSquareBracketedInt(e.key)) mmMeta.put(parseInt(unSquareBracket(e.key)), e.value);
new MultiMap mm;
for (Entry e : entries) mm.put(e.key, e.value);
//S name = or2(/* ouch */ last(mm.get("read as")), /* end ouch */ unpackAGIDomain(page.url), page.url);
S name = page.q;
// Find references
L refs = concatLists(conceptsWhereIC(cc, Entry, value := name),
conceptsWhereIC(cc, Entry, key := name));
Set refPages = asSet(ccollect page(refs));
refPages.remove(page); // don't list current page as reference
// Search in page names (depending on default search type)
L searchResults;
int totalResults = 0, searchResultsToShow = 50;
if (eq(sideSearchType, 'leven))
searchResults = levenSearch(page.q, max := searchResultsToShow);
else if (eq(sideSearchType, 'literal))
{
searchResults = literalSearch(page.q, max := maxInt());
print("totalResults", totalResults = l(searchResults));
searchResults = takeFirst(searchResultsToShow, searchResults);
} else
searchResults = (L) dm_call('agiBlueSearch, 'search, page.q, maxResult := clipIntPlus(searchResultsToShow, 1), agiBlueBotID := programID(), +cc);
searchResults.remove(page);
// Find uses as key
usesAsKey = conceptsWhereIC(cc, Entry, key := page.q);
// Find page in other slices
CentralIndexEntry cie = centralIndex.get(page.q);
L pageInOtherSlices = cie == null ? null : listMinus(cie.slices, slice.sliceConcept);
S pageName_html = htmlEncode2(shorten(displayLength, name));
S mainContents =
top() + h1(ahref_unstyled(agiBlue_pageURLWithSlice(page), pageName_html) + (newPage ? " [huh????]" : ""))
+ p_nemptyLines(map(entries, func(Entry e) -> S {
!withHidden && (hide.contains(e.count) || eqic(e.key, "read as") && eqic(e.value, name)) ? ""
: "[" + e.count + "] " +
renderThing(e.key, false) + ": " +
b(renderThing(e.value, cic(mmMeta.get(e.count), "is a URL")))
}))
+ hpostform(h3("Add an entry")
+ "Key: " + hinputfield(key := key2) + " Value: " + hinputfield(value := value2) + "
" + hsubmit("Add")
)
+ p(ahref(agiBlueURL() + "/literalSearch" + hquery(q := page.q), "[literal search]", title := "Search pages with a name containing this page's name literally")
+ " " +
ahref(agiBlueURL() + "/levenSearch" + hquery(q := page.q), "[leven search 1]", title := "Search pages with a Levenshtein similarity of 1 containing this page's name literally")
+ " " +
ahref(agiBlueURL() + "/search" + hquery(q := page.q), "[scored search]", title := "Search pages with ScoredSearch")
+ " " +
ahref(agiBlueURL() + "/deletePage" + hquery(slice := sliceID(), q := page.q), "[delete page]")
)
// optional "uses as key" section
+ (empty(usesAsKey) ? "" : h3(quote(pageName_html) + " as key")
+ p_nemptyLines_showFirst(50, map(usesAsKey, e ->
pageToHTMLLink().get(e.page!) + unicode_spacedRightPointingTriangle() +
pageName_html + unicode_spacedRightPointingTriangle() +
pageToHTMLLink().get(pageFromQ(e.value))
)))
// optional "in other slices" section
+ (empty(pageInOtherSlices) ? "" : h3(quote(pageName_html) + " in other slices")
+ p_nemptyLines_showFirst(50, map(slice ->
ahref(agiBlueURL() + hquery(slice := slice.globalID, q := page.q), htmlEncode2(slice.name)), pageInOtherSlices)))
;
// end of mainContents
S sideContents =
hform(b("GIVE ME INPUT:") + " "
+ htextinput('q) + " "
+ hsubmit("Ask", onclick := "document.getElementById('newInputForm').target = '';") + " "
+ hsubmit("+Tab", title := "Ask and show result in a new tab", onclick := "document.getElementById('newInputForm').target = '_blank';"),
id := 'newInputForm)
+ h3("References (" + l(refPages) + ")")
+ p_nemptyLines_showFirst(10, map(pageToHTMLLink(displayLength := sideDisplayLength), refPages))
+ h3(searchTypeToText.get(sideSearchType) + " search results ("
+ (l(searchResults) >= searchResultsToShow ? searchResultsToShow + "+" : str(l(searchResults)))
+ (totalResults > l(searchResults) ? " of " + n2(totalResults) : "") + ")")
+ p_nemptyLines_showFirst(searchResultsToShow, map(pageToHTMLLink(displayLength := sideDisplayLength), searchResults))
+ hdiv("", id := 'extraStuff);
// TODO: sync search delivery with WebSocket creation
if (asyncSearch)
doLater(6.0, r { dm_call('agiBlueSearch, 'searchAndPost, page.q, agiBlueBotID := programID(), +cc) });
// notify local interested parties
vmBus_send('agiBlue_servingConceptPage, this, page);
S iframe = params.get('render_iframe);
// serve a concept page
ret hhtml_agiBlue(hhead_title(pageDisplayName(page)) + hbody(
tag('table,
tr(
td(sliceSelector(), valign := 'top)
+ td(sideContents, align := 'right, valign := 'top, rowspan := 2))
+ tr(td(mainContents +
(empty(iframe) ? "" : iframe(iframe, width := 600, height := 400)) +
footer(), align := 'center, valign := 'top)),
width := "100%", height := "100%")));
}
O servePagesToBot(Iterable pages) {
ret serveListToBot(map(pageToMap(wrapMapAsParams(params)), pages));
}
O serveListToBot(Collection l) {
if (nempty(params.get('max)))
l = takeFirst(parseInt(params.get('max)), l);
ret serveJSON(l);
}
// uri starts with "/bot/"
O serveBot() {
S q = params.get('q);
if (eqic(uri, "/bot/hello")) ret serveJSON("hello");
if (eqic(uri, "/bot/hasPage")) ret serveJSON(hasPage(q));
if (eqic(uri, "/bot/randomPageContaining")) {
assertNempty(q);
ret servePageToBot(random(filter(list(cc, Page), p -> cic(p.q, q))), params);
}
if (eqic(uri, "/bot/allPages"))
ret servePagesToBot(list(cc, Page));
if (eqic(uri, "/bot/allPagesStartingWith")) {
assertNempty(q);
ret servePagesToBot(filter(list(cc, Page), p -> swic(p.q, q)));
}
if (eqic(uri, "/bot/allPagesEndingWith")) {
assertNempty(q);
ret servePagesToBot(filter(list(cc, Page), p -> ewic(p.q, q)));
}
if (eqic(uri, "/bot/allPagesContaining")) {
assertNempty(q);
ret servePagesToBot(filter(list(cc, Page), p -> cic(p.q, q)));
}
if (eqic(uri, "/bot/allPagesContainingRegexp")) {
assertNempty(q);
Pattern pat = regexpIC(q);
ret servePagesToBot(filter(list(cc, Page), p -> regexpFindIC(pat, p.q)));
}
if (eqicOneOf(uri, "/bot/postSigned", /*"/bot/makePhysicalSlice",*/ "/bot/approveTrustRequest")) {
S text = rtrim(params.get('text));
S key = getSignerKey(text);
if (empty(key)) ret subBot_serve500("Please include your public key");
if (!isSignedWithKey(text, key)) ret subBot_serve500("Signature didn't verify");
text = dropLastTwoLines(text); // drop signer + sig line
Signer signer = uniq_sync Signer(publicKey := key);
/*if (eqic(uri, "/bot/makePhysicalSlice")) {
if (!signer.trusted) ret subBot_serve500("Untrusted signer");
Page page = findPageFromParams(jsonDecodeMap(text));
if (page == null) ret subBot_serve500("Page not found");
ret serveJSON(uniq2_sync(PhysicalSlice, slicePage := page).b ? "Slice made" : "Slice exists");
}*/
if (eqic(uri, "/bot/postSigned")) {
new L out;
for (S line : tlft(text)) {
SS map = jsonDecodeMap(line);
GetEntriesAndPost x = new(db_mainConcepts());
x.signer = signer;
Page page = findOrMakePageFromParams(map);
if (page == null) continue with out.add("Invalid page reference");
x.go(page, map);
out.add(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value");
}
ret serveJSON(out);
}
if (eqic(uri, "/bot/approveTrustRequest")) {
if (!signer.trusted) ret subBot_serve500("Untrusted signer");
Signer toApprove = conceptWhere Signer(publicKey := trim(text));
if (toApprove == null) ret subBot_serve500("Signer to approve not found");
cset(toApprove, trusted := true, approvedBy := signer.globalID);
ret serveJSON("Approved: " + trim(text));
}
ret subBot_serve500("CONFUSION");
}
if (eqic(uri, "/bot/post")) {
GetEntriesAndPost x = new(cc);
x.go(pageFromQ(q), params);
ret serveJSON(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value");
}
if (eqic(uri, "/bot/entriesOnPage"))
ret serveJSON(map(entriesOnPage(findPageFromParams(params)), entryToMap(false)));
if (eqic(uri, "/bot/entriesForKey"))
ret serveJSON(map(conceptsWhereIC(cc, Entry, key := params.get('key)), entryToMap(true)));
// look up value from page & key in current slice
if (eqic(uri, "/bot/lookup")) {
S key = params.get('key);
if (empty(key)) ret serveJSON("Need key");
S value = getValue(findPageFromParams(params), key);
ret serveJSON(empty(value) ? "" : litmap(+value));
}
if (eqic(uri, "/bot/multiLookup")) {
S key = params.get('key);
if (empty(key)) ret serveJSON("Need key");
ret serveJSON(collect value(objectsWhereIC(findBackRefs(findPageFromParams(params), Entry), +key)));
}
// look up value from page & key in all slices
if (eqic(uri, "/bot/multiLookupInAllSlices")) {
S key = params.get('key);
if (empty(key)) ret serveJSON("Need key");
Cl slices = centralIndexGetSlices(key);
new L