aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/assistant/Config.js
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/modules/assistant/Config.js')
-rw-r--r--desktop/modules/assistant/Config.js87
1 files changed, 86 insertions, 1 deletions
diff --git a/desktop/modules/assistant/Config.js b/desktop/modules/assistant/Config.js
index c30ea8b..4c71b14 100644
--- a/desktop/modules/assistant/Config.js
+++ b/desktop/modules/assistant/Config.js
@@ -17,7 +17,8 @@
// The file is edited line by line so its comments survive: the example it
// is copied from documents every key in place. Only the shapes this page
// writes are understood: top-level `key = value` before the first table,
-// and `KEY = "value"` lines in [env]. A top-level key that is unset sits
+// and `KEY = "value"` lines in [env]. [[cloud]] tables are read when they
+// hold only what setClouds writes, and rewritten whole. A top-level key that is unset sits
// commented out (`# key = value`), which is how the example ships, so
// setting one activates its line and unsetting comments it back out.
// ponytail: no multi-line strings or arrays; the page never writes one,
@@ -140,3 +141,87 @@ function setEnv(text, key, val) {
lines.splice(last + 1, 0, key + " = " + quote(val));
return lines.join("\n");
}
+
+// [[cloud]] tables, the daemon's providers: name, url and key_cmd strings
+// and a one-line models array, as setClouds writes them.
+const CLOUD = /^\s*\[\[cloud\]\]\s*(#.*)?$/;
+
+// A one-line array of strings, ["a", "b"]; null for anything else.
+function strings(rest) {
+ let r = rest.trim();
+ if (!r.startsWith("[")) return null;
+ const out = [];
+ r = r.slice(1).trim();
+ while (!r.startsWith("]")) {
+ if (!r.startsWith('"')) return null;
+ const v = value(r);
+ out.push(v.v);
+ r = r.slice(v.end).trim();
+ if (r.startsWith(",")) r = r.slice(1).trim();
+ else if (!r.startsWith("]")) return null;
+ }
+ const tail = r.slice(1).trim();
+ return tail === "" || tail.startsWith("#") ? out : null;
+}
+
+// [{start, end}] of each [[cloud]] table, its header to the next header.
+function cloudBlocks(lines) {
+ const out = [];
+ lines.forEach((l, i) => {
+ if (!CLOUD.test(l)) return;
+ const e = lines.findIndex((x, j) => j > i && HEADER.test(x));
+ out.push({ start: i, end: e < 0 ? lines.length : e });
+ });
+ return out;
+}
+
+// [{name, url, key_cmd, models}] in file order, or null when a table holds
+// a line this cannot read (a multi-line array, another key): the page then
+// leaves them to be edited by hand rather than rewrite what it did not read.
+function clouds(text) {
+ const lines = text.split("\n");
+ const out = [];
+ for (const b of cloudBlocks(lines)) {
+ const c = { name: "", url: "", key_cmd: "", models: [] };
+ for (let i = b.start + 1; i < b.end; i++) {
+ if (/^\s*(#.*)?$/.test(lines[i])) continue;
+ const m = KEY.exec(lines[i]);
+ if (!m) return null;
+ if (m[3] === "models") {
+ const a = strings(m[4]);
+ if (a === null) return null;
+ c.models = a;
+ } else if (["name", "url", "key_cmd"].includes(m[3]) && m[4].startsWith('"')) {
+ c[m[3]] = value(m[4]).v;
+ } else return null;
+ }
+ out.push(c);
+ }
+ return out;
+}
+
+// Replaces every [[cloud]] table with list's, at the end of the file. The
+// lines inside the old tables go with them; comments above them stay.
+function setClouds(text, list) {
+ const lines = text.split("\n");
+ for (const b of cloudBlocks(lines).reverse()) lines.splice(b.start, b.end - b.start);
+ while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
+ for (const c of list)
+ lines.push("", "[[cloud]]", "name = " + quote(c.name), "url = " + quote(c.url),
+ "key_cmd = " + quote(c.key_cmd),
+ "models = [" + c.models.map(quote).join(", ") + "]");
+ return lines.join("\n") + "\n";
+}
+
+// Why list cannot be saved, "" when it can: the daemon refuses to start on
+// any of these (assistant.py, check_cloud).
+function cloudError(list) {
+ for (const c of list) {
+ if (!c.name.trim() || !c.url.trim() || !c.key_cmd.trim())
+ return "A cloud provider needs a name, a URL and a key command.";
+ if (c.name.includes(":")) return `Cloud name ${c.name} has a colon.`;
+ }
+ const names = list.map(c => c.name);
+ const dup = names.find((n, i) => names.indexOf(n) !== i);
+ return dup !== undefined ? `Cloud name ${dup} is used twice.` : "";
+}