@mikedown/core 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -1
- package/README.md +1 -1
- package/dist/ai/engines-page.d.ts +11 -4
- package/dist/ai/engines-page.js +5 -4
- package/dist/ai/index.d.ts +1201 -10
- package/dist/ai/index.js +297 -8
- package/dist/{ai-ui-C7ENTWWU.js → ai-ui-QFYFN73D.js} +3165 -138
- package/dist/ai-ui-QFYFN73D.js.map +1 -0
- package/dist/{chunk-QDVY7WW6.js → chunk-APPBNQEY.js} +53 -2
- package/dist/chunk-APPBNQEY.js.map +1 -0
- package/dist/{chunk-7A5HEPLK.js → chunk-DWXGWVA3.js} +850 -12
- package/dist/chunk-DWXGWVA3.js.map +1 -0
- package/dist/{chunk-JZWKEBCI.js → chunk-IWCXWUWW.js} +3260 -201
- package/dist/chunk-IWCXWUWW.js.map +1 -0
- package/dist/{chunk-LXZG7IKN.js → chunk-N4N54LK2.js} +2 -2
- package/dist/chunk-N4N54LK2.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +399 -38
- package/dist/index.js.map +1 -1
- package/dist/schema-DVfssxQX.d.ts +100 -0
- package/dist/styles.css +943 -4
- package/dist/{types-CsVNVSNY.d.ts → types-2TZErbJr.d.ts} +25 -3
- package/dist/{types-CgEeIUhi.d.ts → types-DO8UBybZ.d.ts} +316 -7
- package/package.json +1 -1
- package/dist/ai-ui-C7ENTWWU.js.map +0 -1
- package/dist/chunk-7A5HEPLK.js.map +0 -1
- package/dist/chunk-JZWKEBCI.js.map +0 -1
- package/dist/chunk-LXZG7IKN.js.map +0 -1
- package/dist/chunk-QDVY7WW6.js.map +0 -1
- package/dist/schema-DYzxkuSb.d.ts +0 -46
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
aiHostTier
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-N4N54LK2.js";
|
|
4
4
|
import {
|
|
5
5
|
AI_CLOUD_PRIVACY,
|
|
6
|
-
AI_ENGINE_KINDS,
|
|
7
6
|
AI_ENGINE_PRESETS,
|
|
8
|
-
AI_FALLBACK_MODES,
|
|
9
7
|
AI_HOST_PRESET_FAMILY,
|
|
10
8
|
AI_HOST_PRIVACY,
|
|
11
9
|
AI_LOCAL_DETECT_TIMEOUT_MS,
|
|
12
10
|
AI_PRESET_FAMILIES,
|
|
13
11
|
AI_TASKS,
|
|
14
|
-
|
|
12
|
+
DOCS_NO_HOME_TEXT,
|
|
15
13
|
aiConsentHost,
|
|
16
14
|
aiConsentStepNote,
|
|
17
15
|
aiErrorCopy,
|
|
18
16
|
aiProviderPolicy,
|
|
17
|
+
checkDocRoot,
|
|
18
|
+
clampDocsHops,
|
|
19
|
+
clampDocsSourceShare,
|
|
19
20
|
detectCapabilities,
|
|
21
|
+
docRootKey,
|
|
20
22
|
formatModelSize,
|
|
21
23
|
generateAiId,
|
|
22
24
|
getAiTask,
|
|
@@ -28,6 +30,8 @@ import {
|
|
|
28
30
|
missingCapabilities,
|
|
29
31
|
modelMoeFastHint,
|
|
30
32
|
modelSizeHint,
|
|
33
|
+
normalizeDocHomeFile,
|
|
34
|
+
parseAiSettings,
|
|
31
35
|
presetForEngine,
|
|
32
36
|
recommendSetupPath,
|
|
33
37
|
resolveRoute,
|
|
@@ -38,14 +42,22 @@ import {
|
|
|
38
42
|
vscodeLmAvailability,
|
|
39
43
|
whatIsSent,
|
|
40
44
|
withThinkingLevel
|
|
41
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-IWCXWUWW.js";
|
|
42
46
|
import {
|
|
47
|
+
AI_DOCS_MAX_HOPS_MAX,
|
|
48
|
+
AI_DOCS_MAX_HOPS_MIN,
|
|
49
|
+
AI_DOCS_NAVIGATE_BY,
|
|
50
|
+
AI_DOCS_SOURCE_SHARE_MAX,
|
|
51
|
+
AI_DOCS_SOURCE_SHARE_MIN,
|
|
52
|
+
AI_ENGINE_KINDS,
|
|
53
|
+
AI_FALLBACK_MODES,
|
|
54
|
+
DEFAULT_CONTEXT_TOKENS,
|
|
43
55
|
DEFAULT_LOCAL_NETWORKS,
|
|
44
56
|
detectLocality,
|
|
45
57
|
engineLocality,
|
|
46
58
|
hostOfUrl,
|
|
47
59
|
localityMismatch
|
|
48
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-APPBNQEY.js";
|
|
49
61
|
|
|
50
62
|
// src/ai-settings-model.ts
|
|
51
63
|
var AI_ENGINE_KIND_LABELS = {
|
|
@@ -1149,7 +1161,8 @@ var ICONS = {
|
|
|
1149
1161
|
refresh: svg('<path d="M13 8a5 5 0 1 1-1.5-3.55M13 2.5v3h-3"/>', 12),
|
|
1150
1162
|
search: svg('<circle cx="7" cy="7" r="4.25"/><path d="M10.2 10.2l3.3 3.3"/>', 12),
|
|
1151
1163
|
back: svg('<path d="M10 3.5L5.5 8l4.5 4.5"/>', 14),
|
|
1152
|
-
routing: svg('<path d="M3 4h4.5a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2H13M3 12h3M11 2.5L13 4l-2 1.5M11 10.5l2 1.5-2 1.5"/>', 16)
|
|
1164
|
+
routing: svg('<path d="M3 4h4.5a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2H13M3 12h3M11 2.5L13 4l-2 1.5M11 10.5l2 1.5-2 1.5"/>', 16),
|
|
1165
|
+
book: svg('<path d="M3 13V4a1.5 1.5 0 0 1 1.5-1.5H13v8.5H4.5A1.5 1.5 0 0 0 3 12.5 1.5 1.5 0 0 0 4.5 14H13M6 5.5h4"/>', 16)
|
|
1153
1166
|
};
|
|
1154
1167
|
var KIND_MONOGRAMS = {
|
|
1155
1168
|
lmstudio: "LM",
|
|
@@ -3087,6 +3100,771 @@ function createRoutingSection(ctx, deps) {
|
|
|
3087
3100
|
return { element: body, render };
|
|
3088
3101
|
}
|
|
3089
3102
|
|
|
3103
|
+
// src/ai-docs-page.ts
|
|
3104
|
+
var DOCS_CLOUD_LABEL = "May be sent to cloud engines";
|
|
3105
|
+
var DOCS_CLOUD_LINE = "Pages from this base may go to the cloud engines you allow.";
|
|
3106
|
+
var DOCS_NOT_CHECKED_TEXT = "Not checked yet";
|
|
3107
|
+
var DOCS_ROOT_MISSING_TEXT = "Root not found";
|
|
3108
|
+
var NAVIGATE_LABELS = { section: "Section", page: "Page" };
|
|
3109
|
+
var plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
3110
|
+
function docsRootChips(root) {
|
|
3111
|
+
if (!root.found) {
|
|
3112
|
+
return [{ text: DOCS_ROOT_MISSING_TEXT, tone: "warn" }];
|
|
3113
|
+
}
|
|
3114
|
+
const chips = [];
|
|
3115
|
+
const hasHome = !!root.home && root.home !== "map" && !!root.homePath;
|
|
3116
|
+
chips.push({ text: hasHome ? `Home: ${root.homePath}` : DOCS_NO_HOME_TEXT, tone: "quiet" });
|
|
3117
|
+
chips.push({ text: plural(root.fileCount, "file"), tone: "quiet" });
|
|
3118
|
+
if (root.oversizeSkipped > 0) {
|
|
3119
|
+
chips.push({ text: `${root.oversizeSkipped} too large, skipped`, tone: "quiet" });
|
|
3120
|
+
}
|
|
3121
|
+
if (root.capReached) {
|
|
3122
|
+
chips.push({ text: "File limit reached", tone: "warn" });
|
|
3123
|
+
}
|
|
3124
|
+
if (root.notTextSkipped) {
|
|
3125
|
+
chips.push({ text: `${root.notTextSkipped} not UTF-8 text, skipped`, tone: "quiet" });
|
|
3126
|
+
}
|
|
3127
|
+
if (root.dirCapReached) {
|
|
3128
|
+
chips.push({ text: "Folder limit reached", tone: "warn" });
|
|
3129
|
+
}
|
|
3130
|
+
return chips;
|
|
3131
|
+
}
|
|
3132
|
+
function docsBaseChips(status) {
|
|
3133
|
+
const chips = [];
|
|
3134
|
+
if (status.brokenLinks) {
|
|
3135
|
+
chips.push({ text: plural(status.brokenLinks, "broken link"), tone: "warn" });
|
|
3136
|
+
}
|
|
3137
|
+
if (status.timedOut) {
|
|
3138
|
+
chips.push({ text: "Stopped early: this folder took too long to read", tone: "warn" });
|
|
3139
|
+
}
|
|
3140
|
+
return chips;
|
|
3141
|
+
}
|
|
3142
|
+
function withDocsOptions(settings, patch) {
|
|
3143
|
+
const next = { ...settings.docs, ...patch };
|
|
3144
|
+
return {
|
|
3145
|
+
...settings,
|
|
3146
|
+
docs: {
|
|
3147
|
+
...next,
|
|
3148
|
+
maxHops: clampDocsHops(next.maxHops),
|
|
3149
|
+
sourceShare: clampDocsSourceShare(next.sourceShare),
|
|
3150
|
+
navigateBy: AI_DOCS_NAVIGATE_BY.includes(next.navigateBy) ? next.navigateBy : "section"
|
|
3151
|
+
}
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
3154
|
+
function checkBaseRoot(raw, inThisBase, others) {
|
|
3155
|
+
const checked = checkDocRoot(raw);
|
|
3156
|
+
if (!checked.ok) {
|
|
3157
|
+
return { ok: false, message: `This path ${checked.reason}.` };
|
|
3158
|
+
}
|
|
3159
|
+
const key = docRootKey(checked.path);
|
|
3160
|
+
if (inThisBase.some((r) => docRootKey(r) === key)) {
|
|
3161
|
+
return { ok: false, message: "This base already has that folder." };
|
|
3162
|
+
}
|
|
3163
|
+
const holder = others.find((b) => b.roots.some((r) => docRootKey(r) === key));
|
|
3164
|
+
if (holder) {
|
|
3165
|
+
return { ok: false, message: `That folder is already in \u201C${holder.name}\u201D.` };
|
|
3166
|
+
}
|
|
3167
|
+
return { ok: true, path: checked.path };
|
|
3168
|
+
}
|
|
3169
|
+
function checkDocBaseForm(values, others, existing) {
|
|
3170
|
+
const errors = {};
|
|
3171
|
+
if (values.roots.length === 0) {
|
|
3172
|
+
errors.roots = "Add at least one folder.";
|
|
3173
|
+
}
|
|
3174
|
+
for (const [i, root] of values.roots.entries()) {
|
|
3175
|
+
const checked = checkBaseRoot(root, values.roots.slice(0, i), others);
|
|
3176
|
+
if (!checked.ok) {
|
|
3177
|
+
errors.roots = `${root}: ${checked.message}`;
|
|
3178
|
+
break;
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
const homeText = values.homeFile.trim();
|
|
3182
|
+
const home = homeText ? normalizeDocHomeFile(homeText) : null;
|
|
3183
|
+
if (homeText && !home) {
|
|
3184
|
+
errors.homeFile = "The home file must be a markdown file name or a path inside the folder, such as HOME.md.";
|
|
3185
|
+
}
|
|
3186
|
+
if (errors.roots || errors.homeFile) {
|
|
3187
|
+
return { ok: false, errors };
|
|
3188
|
+
}
|
|
3189
|
+
const base = {
|
|
3190
|
+
...existing ?? { enabled: true },
|
|
3191
|
+
id: existing?.id ?? "",
|
|
3192
|
+
name: values.name.trim(),
|
|
3193
|
+
roots: values.roots,
|
|
3194
|
+
allowCloud: values.allowCloud
|
|
3195
|
+
};
|
|
3196
|
+
if (home) {
|
|
3197
|
+
base.homeFile = home;
|
|
3198
|
+
} else {
|
|
3199
|
+
delete base.homeFile;
|
|
3200
|
+
}
|
|
3201
|
+
return { ok: true, base };
|
|
3202
|
+
}
|
|
3203
|
+
function revalidateDocBases(bases) {
|
|
3204
|
+
const warnings = [];
|
|
3205
|
+
const taken = bases.map((b) => b.id).filter(Boolean);
|
|
3206
|
+
const parsed = parseAiSettings({ docBases: bases }, {
|
|
3207
|
+
onWarning: (w) => warnings.push(w),
|
|
3208
|
+
generateId: (name) => {
|
|
3209
|
+
const id = uniqueEngineId(name, taken);
|
|
3210
|
+
taken.push(id);
|
|
3211
|
+
return id;
|
|
3212
|
+
}
|
|
3213
|
+
}).docBases;
|
|
3214
|
+
if (warnings.length > 0) {
|
|
3215
|
+
return { ok: false, reason: `Not saved: ${warnings[0].replace(/^docBases\[\d+\](\.\w+(\[\d+\])?)?\s*/, "")}` };
|
|
3216
|
+
}
|
|
3217
|
+
return { ok: true, bases: parsed };
|
|
3218
|
+
}
|
|
3219
|
+
function lastPathSegment(path) {
|
|
3220
|
+
const parts = path.split(/[\\/]+/).filter(Boolean);
|
|
3221
|
+
return parts[parts.length - 1] ?? "";
|
|
3222
|
+
}
|
|
3223
|
+
function sameRootsAndHome(a, b) {
|
|
3224
|
+
return JSON.stringify(a.roots) === JSON.stringify(b.roots) && (a.homeFile ?? "") === (b.homeFile ?? "");
|
|
3225
|
+
}
|
|
3226
|
+
function createDocsBasesSection(ctx, deps) {
|
|
3227
|
+
const { docs } = deps;
|
|
3228
|
+
const element = el("div", "ai-docs");
|
|
3229
|
+
const listView = el("div", "ai-docs-list-view");
|
|
3230
|
+
const formView = el("div", "ai-docs-form-view");
|
|
3231
|
+
formView.hidden = true;
|
|
3232
|
+
let entries = [];
|
|
3233
|
+
let loaded = false;
|
|
3234
|
+
let checking = false;
|
|
3235
|
+
let openedOnce = false;
|
|
3236
|
+
let disposed = false;
|
|
3237
|
+
let problem = null;
|
|
3238
|
+
let confirmRemoveId = null;
|
|
3239
|
+
let form = null;
|
|
3240
|
+
let queue = Promise.resolve();
|
|
3241
|
+
function enqueue(job) {
|
|
3242
|
+
const run = queue.then(job);
|
|
3243
|
+
queue = run.catch(() => {
|
|
3244
|
+
});
|
|
3245
|
+
return run;
|
|
3246
|
+
}
|
|
3247
|
+
function focusFk(key) {
|
|
3248
|
+
Array.from(element.querySelectorAll("[data-fk]")).find((n) => n.dataset.fk === key)?.focus({ preventScroll: true });
|
|
3249
|
+
}
|
|
3250
|
+
function load() {
|
|
3251
|
+
if (checking || disposed) {
|
|
3252
|
+
return;
|
|
3253
|
+
}
|
|
3254
|
+
checking = true;
|
|
3255
|
+
problem = null;
|
|
3256
|
+
renderList();
|
|
3257
|
+
deps.announce("Checking bases\u2026");
|
|
3258
|
+
void enqueue(async () => {
|
|
3259
|
+
if (disposed) {
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
try {
|
|
3263
|
+
const list = await docs.listBases({ refresh: true });
|
|
3264
|
+
if (disposed) {
|
|
3265
|
+
return;
|
|
3266
|
+
}
|
|
3267
|
+
entries = list.map((status) => ({ base: status.base, health: status }));
|
|
3268
|
+
loaded = true;
|
|
3269
|
+
deps.announce(`Checked ${plural(entries.length, "base")}.`);
|
|
3270
|
+
} catch (err) {
|
|
3271
|
+
if (disposed) {
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
problem = `Could not read your bases: ${toAiError(err).message}`;
|
|
3275
|
+
deps.announce(problem);
|
|
3276
|
+
}
|
|
3277
|
+
checking = false;
|
|
3278
|
+
if (confirmRemoveId && !entries.some((e) => e.base.id === confirmRemoveId)) {
|
|
3279
|
+
confirmRemoveId = null;
|
|
3280
|
+
}
|
|
3281
|
+
renderList();
|
|
3282
|
+
deps.onCountChanged();
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
function mutate(change) {
|
|
3286
|
+
return enqueue(async () => {
|
|
3287
|
+
if (disposed) {
|
|
3288
|
+
return null;
|
|
3289
|
+
}
|
|
3290
|
+
if (!loaded) {
|
|
3291
|
+
return "Your bases could not be read, so nothing was saved. Choose Refresh and try again.";
|
|
3292
|
+
}
|
|
3293
|
+
const checked = revalidateDocBases(change(entries.map((e) => e.base)));
|
|
3294
|
+
if (!checked.ok) {
|
|
3295
|
+
return checked.reason;
|
|
3296
|
+
}
|
|
3297
|
+
try {
|
|
3298
|
+
await docs.saveBases(checked.bases);
|
|
3299
|
+
} catch (err) {
|
|
3300
|
+
return `Not saved: ${toAiError(err).message}`;
|
|
3301
|
+
}
|
|
3302
|
+
if (disposed) {
|
|
3303
|
+
return null;
|
|
3304
|
+
}
|
|
3305
|
+
const before = new Map(entries.map((e) => [e.base.id, e]));
|
|
3306
|
+
entries = checked.bases.map((base) => {
|
|
3307
|
+
const prev = before.get(base.id);
|
|
3308
|
+
if (!prev?.health || !sameRootsAndHome(prev.base, base)) {
|
|
3309
|
+
return { base, health: null };
|
|
3310
|
+
}
|
|
3311
|
+
return { base, health: { ...prev.health, base, active: base.enabled && prev.health.rootsMissing < base.roots.length } };
|
|
3312
|
+
});
|
|
3313
|
+
problem = null;
|
|
3314
|
+
deps.onCountChanged();
|
|
3315
|
+
return null;
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
function chipNode(chip) {
|
|
3319
|
+
const node = el("span", `ai-docs-chip${chip.tone === "warn" ? " ai-docs-chip--warn" : ""}`);
|
|
3320
|
+
if (chip.tone === "warn") {
|
|
3321
|
+
node.innerHTML = ICONS.warn;
|
|
3322
|
+
}
|
|
3323
|
+
node.append(el("span", "", chip.text));
|
|
3324
|
+
return node;
|
|
3325
|
+
}
|
|
3326
|
+
function chipLine(chips) {
|
|
3327
|
+
const line = el("span", "ai-docs-chips");
|
|
3328
|
+
line.append(...chips.map(chipNode));
|
|
3329
|
+
return line;
|
|
3330
|
+
}
|
|
3331
|
+
function renderList() {
|
|
3332
|
+
keepFocus(listView, () => {
|
|
3333
|
+
listView.replaceChildren();
|
|
3334
|
+
listView.append(buildBar());
|
|
3335
|
+
if (problem) {
|
|
3336
|
+
listView.append(note("error", problem, "ai-result"));
|
|
3337
|
+
}
|
|
3338
|
+
if (loaded && entries.length === 0) {
|
|
3339
|
+
const empty = el("div", "ai-summary-empty");
|
|
3340
|
+
empty.append(
|
|
3341
|
+
el("div", "ai-empty-title", "No bases yet"),
|
|
3342
|
+
el("div", "ai-empty-text", "A base is a folder of markdown the AI can read to answer your questions."),
|
|
3343
|
+
el("div", "ai-empty-text", "Add your docs folder, and chat can cite its pages.")
|
|
3344
|
+
);
|
|
3345
|
+
listView.append(empty);
|
|
3346
|
+
} else if (entries.length > 0) {
|
|
3347
|
+
const group = ctx.makeGroup(...entries.map(buildRow));
|
|
3348
|
+
group.setAttribute("role", "list");
|
|
3349
|
+
group.setAttribute("aria-label", "Documentation bases");
|
|
3350
|
+
listView.append(group);
|
|
3351
|
+
}
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
function buildBar() {
|
|
3355
|
+
const bar = el("div", "ai-docs-bar");
|
|
3356
|
+
bar.append(el("span", "ai-docs-status", checking ? "Checking bases\u2026" : ""));
|
|
3357
|
+
if (entries.length > 0 || !loaded) {
|
|
3358
|
+
const refresh = button("settings-btn", "Refresh", { html: ICONS.refresh, fk: "docs:refresh", title: "Check the folders again" });
|
|
3359
|
+
if (checking) {
|
|
3360
|
+
refresh.setAttribute("aria-disabled", "true");
|
|
3361
|
+
}
|
|
3362
|
+
refresh.addEventListener("click", () => load());
|
|
3363
|
+
bar.append(refresh);
|
|
3364
|
+
}
|
|
3365
|
+
const add = button("settings-btn settings-btn--secondary", "Add base", { html: ICONS.plus, fk: "docs:add" });
|
|
3366
|
+
if (!loaded) {
|
|
3367
|
+
add.setAttribute("aria-disabled", "true");
|
|
3368
|
+
}
|
|
3369
|
+
add.addEventListener("click", () => {
|
|
3370
|
+
if (loaded) {
|
|
3371
|
+
openForm();
|
|
3372
|
+
}
|
|
3373
|
+
});
|
|
3374
|
+
bar.append(add);
|
|
3375
|
+
return bar;
|
|
3376
|
+
}
|
|
3377
|
+
function buildRow({ base, health }) {
|
|
3378
|
+
const row = el("div", "settings-row ai-docs-base");
|
|
3379
|
+
row.setAttribute("role", "listitem");
|
|
3380
|
+
row.dataset.baseId = base.id;
|
|
3381
|
+
row.classList.toggle("is-disabled", !base.enabled);
|
|
3382
|
+
const head = el("div", "ai-docs-base-head");
|
|
3383
|
+
const title = el("div", "ai-docs-base-title");
|
|
3384
|
+
title.append(el("span", "ai-docs-base-name", base.name));
|
|
3385
|
+
if (base.allowCloud) {
|
|
3386
|
+
title.append(chipNode({ text: "May go to cloud", tone: "quiet" }));
|
|
3387
|
+
}
|
|
3388
|
+
if (!health) {
|
|
3389
|
+
title.append(chipNode({ text: DOCS_NOT_CHECKED_TEXT, tone: "quiet" }));
|
|
3390
|
+
}
|
|
3391
|
+
const actions = el("div", "ai-docs-base-actions");
|
|
3392
|
+
const edit = button("ai-link-btn", "Edit", { ariaLabel: `Edit ${base.name}`, fk: `base:${base.id}:edit` });
|
|
3393
|
+
edit.addEventListener("click", () => openForm(base));
|
|
3394
|
+
const remove = button("ai-icon-btn ai-delete-btn", "", { html: ICONS.trash, ariaLabel: `Remove ${base.name}`, title: "Remove from this list", fk: `base:${base.id}:remove` });
|
|
3395
|
+
remove.addEventListener("click", () => {
|
|
3396
|
+
confirmRemoveId = base.id;
|
|
3397
|
+
renderList();
|
|
3398
|
+
focusFk(`base:${base.id}:confirm-cancel`);
|
|
3399
|
+
});
|
|
3400
|
+
const enabled = switchInput(base.enabled, `Use ${base.name}`, `base:${base.id}:enabled`);
|
|
3401
|
+
enabled.addEventListener("change", () => setEnabled(base, enabled.checked));
|
|
3402
|
+
actions.append(edit, remove, enabled);
|
|
3403
|
+
head.append(title, actions);
|
|
3404
|
+
const roots = el("ul", "ai-docs-roots");
|
|
3405
|
+
for (const path of base.roots) {
|
|
3406
|
+
const item = el("li", "ai-docs-root");
|
|
3407
|
+
const pathNode = el("span", "ai-docs-path ai-mono", path);
|
|
3408
|
+
pathNode.title = path;
|
|
3409
|
+
item.append(pathNode);
|
|
3410
|
+
const status = health?.roots.find((r) => docRootKey(r.root) === docRootKey(path));
|
|
3411
|
+
if (status) {
|
|
3412
|
+
item.append(chipLine(docsRootChips(status)));
|
|
3413
|
+
}
|
|
3414
|
+
roots.append(item);
|
|
3415
|
+
}
|
|
3416
|
+
row.append(head, roots);
|
|
3417
|
+
const baseChips = health ? docsBaseChips(health) : [];
|
|
3418
|
+
if (baseChips.length > 0) {
|
|
3419
|
+
row.append(chipLine(baseChips));
|
|
3420
|
+
}
|
|
3421
|
+
if (confirmRemoveId === base.id) {
|
|
3422
|
+
row.append(buildRemoveConfirm(base));
|
|
3423
|
+
}
|
|
3424
|
+
return row;
|
|
3425
|
+
}
|
|
3426
|
+
function setEnabled(base, on) {
|
|
3427
|
+
void mutate((bases) => bases.map((b) => b.id === base.id ? { ...b, enabled: on } : b)).then((error) => {
|
|
3428
|
+
if (disposed) {
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
problem = error;
|
|
3432
|
+
renderList();
|
|
3433
|
+
deps.announce(error ?? `${base.name} turned ${on ? "on" : "off"}.`);
|
|
3434
|
+
});
|
|
3435
|
+
}
|
|
3436
|
+
function buildRemoveConfirm(base) {
|
|
3437
|
+
const box = el("div", "ai-confirm");
|
|
3438
|
+
box.setAttribute("role", "group");
|
|
3439
|
+
box.setAttribute("aria-label", `Remove ${base.name}?`);
|
|
3440
|
+
const yes = button("settings-btn ai-btn--danger", "Remove", { fk: `base:${base.id}:confirm-remove` });
|
|
3441
|
+
const no = button("settings-btn", "Cancel", { fk: `base:${base.id}:confirm-cancel` });
|
|
3442
|
+
yes.addEventListener("click", () => removeBase(base));
|
|
3443
|
+
no.addEventListener("click", () => closeConfirm());
|
|
3444
|
+
box.append(el("span", "ai-confirm-text", `Remove ${base.name} from this list? The folders and files on disk are not touched.`), yes, no);
|
|
3445
|
+
return box;
|
|
3446
|
+
}
|
|
3447
|
+
function closeConfirm() {
|
|
3448
|
+
const id = confirmRemoveId;
|
|
3449
|
+
confirmRemoveId = null;
|
|
3450
|
+
renderList();
|
|
3451
|
+
if (id) {
|
|
3452
|
+
focusFk(`base:${id}:remove`);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
function removeBase(base) {
|
|
3456
|
+
const index = entries.findIndex((e) => e.base.id === base.id);
|
|
3457
|
+
void mutate((bases) => bases.filter((b) => b.id !== base.id)).then((error) => {
|
|
3458
|
+
if (disposed) {
|
|
3459
|
+
return;
|
|
3460
|
+
}
|
|
3461
|
+
problem = error;
|
|
3462
|
+
confirmRemoveId = null;
|
|
3463
|
+
renderList();
|
|
3464
|
+
if (error) {
|
|
3465
|
+
deps.announce(error);
|
|
3466
|
+
focusFk(`base:${base.id}:remove`);
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3469
|
+
deps.announce(`Removed ${base.name} from this list. No files were touched.`);
|
|
3470
|
+
const next = entries[Math.min(index, entries.length - 1)];
|
|
3471
|
+
focusFk(next ? `base:${next.base.id}:edit` : "docs:add");
|
|
3472
|
+
});
|
|
3473
|
+
}
|
|
3474
|
+
const advancedPanelId = nextId("ai-docs-advanced");
|
|
3475
|
+
const advancedToggle = button("ai-disclosure ai-docs-advanced-toggle", "Advanced", { html: ICONS.chevron, fk: "docs:advanced" });
|
|
3476
|
+
advancedToggle.setAttribute("aria-expanded", "false");
|
|
3477
|
+
advancedToggle.setAttribute("aria-controls", advancedPanelId);
|
|
3478
|
+
const advancedPanel = el("div", "ai-advanced ai-docs-advanced");
|
|
3479
|
+
advancedPanel.id = advancedPanelId;
|
|
3480
|
+
advancedPanel.hidden = true;
|
|
3481
|
+
advancedToggle.addEventListener("click", () => {
|
|
3482
|
+
advancedPanel.hidden = !advancedPanel.hidden;
|
|
3483
|
+
advancedToggle.setAttribute("aria-expanded", String(!advancedPanel.hidden));
|
|
3484
|
+
});
|
|
3485
|
+
function numberRow(label, description, read, write, min, max, fk) {
|
|
3486
|
+
const row = ctx.makeRow(label, description, "inline");
|
|
3487
|
+
const input = el("input", "settings-input settings-input--small ai-docs-number");
|
|
3488
|
+
input.type = "number";
|
|
3489
|
+
input.min = String(min);
|
|
3490
|
+
input.max = String(max);
|
|
3491
|
+
input.step = "1";
|
|
3492
|
+
input.value = String(read());
|
|
3493
|
+
input.dataset.fk = fk;
|
|
3494
|
+
input.setAttribute("aria-label", label);
|
|
3495
|
+
input.addEventListener("change", () => {
|
|
3496
|
+
const value = input.value.trim() === "" ? NaN : Number(input.value);
|
|
3497
|
+
if (Number.isFinite(value)) {
|
|
3498
|
+
write(value);
|
|
3499
|
+
}
|
|
3500
|
+
input.value = String(read());
|
|
3501
|
+
});
|
|
3502
|
+
row.append(input);
|
|
3503
|
+
return row;
|
|
3504
|
+
}
|
|
3505
|
+
function buildAdvanced() {
|
|
3506
|
+
const draft = () => deps.getDraft();
|
|
3507
|
+
const hops = numberRow(
|
|
3508
|
+
"Max hops",
|
|
3509
|
+
`Rounds of picking pages before the answer, ${AI_DOCS_MAX_HOPS_MIN} to ${AI_DOCS_MAX_HOPS_MAX}.`,
|
|
3510
|
+
() => draft().docs.maxHops,
|
|
3511
|
+
(n) => deps.setDraft(withDocsOptions(draft(), { maxHops: n })),
|
|
3512
|
+
AI_DOCS_MAX_HOPS_MIN,
|
|
3513
|
+
AI_DOCS_MAX_HOPS_MAX,
|
|
3514
|
+
"docs:hops"
|
|
3515
|
+
);
|
|
3516
|
+
const share = numberRow(
|
|
3517
|
+
"Source share",
|
|
3518
|
+
`How much of the model\u2019s context the pages may take, ${AI_DOCS_SOURCE_SHARE_MIN} to ${AI_DOCS_SOURCE_SHARE_MAX}%.`,
|
|
3519
|
+
() => draft().docs.sourceShare,
|
|
3520
|
+
(n) => deps.setDraft(withDocsOptions(draft(), { sourceShare: n })),
|
|
3521
|
+
AI_DOCS_SOURCE_SHARE_MIN,
|
|
3522
|
+
AI_DOCS_SOURCE_SHARE_MAX,
|
|
3523
|
+
"docs:share"
|
|
3524
|
+
);
|
|
3525
|
+
const by = ctx.makeSelectRow(
|
|
3526
|
+
"Navigate by",
|
|
3527
|
+
"What the AI asks to read: one section, or a whole page.",
|
|
3528
|
+
draft().docs.navigateBy,
|
|
3529
|
+
AI_DOCS_NAVIGATE_BY.map((value) => ({ value, label: NAVIGATE_LABELS[value] }))
|
|
3530
|
+
);
|
|
3531
|
+
by.select.classList.add("settings-input--small", "ai-docs-select");
|
|
3532
|
+
by.select.dataset.fk = "docs:navigate-by";
|
|
3533
|
+
by.select.setAttribute("aria-label", "Navigate by");
|
|
3534
|
+
by.select.addEventListener("change", () => deps.setDraft(withDocsOptions(draft(), { navigateBy: by.select.value })));
|
|
3535
|
+
advancedPanel.append(ctx.makeGroup(hops, share, by.row), el("div", "settings-desc", "These save when you choose Done."));
|
|
3536
|
+
}
|
|
3537
|
+
const otherBases = (existing) => entries.map((e) => e.base).filter((b) => b.id !== existing?.id);
|
|
3538
|
+
function openForm(existing) {
|
|
3539
|
+
confirmRemoveId = null;
|
|
3540
|
+
const values = {
|
|
3541
|
+
name: existing?.name ?? "",
|
|
3542
|
+
roots: [...existing?.roots ?? []],
|
|
3543
|
+
homeFile: existing?.homeFile ?? "",
|
|
3544
|
+
allowCloud: existing?.allowCloud ?? false
|
|
3545
|
+
};
|
|
3546
|
+
const state = {
|
|
3547
|
+
existing,
|
|
3548
|
+
initial: { ...values, roots: [...values.roots] },
|
|
3549
|
+
values,
|
|
3550
|
+
saving: false,
|
|
3551
|
+
showError: () => {
|
|
3552
|
+
},
|
|
3553
|
+
focusFirst: () => {
|
|
3554
|
+
}
|
|
3555
|
+
};
|
|
3556
|
+
form = state;
|
|
3557
|
+
formView.replaceChildren(buildForm(state));
|
|
3558
|
+
showView();
|
|
3559
|
+
state.focusFirst();
|
|
3560
|
+
}
|
|
3561
|
+
function closeForm(focusKey) {
|
|
3562
|
+
form = null;
|
|
3563
|
+
formView.replaceChildren();
|
|
3564
|
+
showView();
|
|
3565
|
+
renderList();
|
|
3566
|
+
if (focusKey) {
|
|
3567
|
+
focusFk(focusKey);
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
function showView() {
|
|
3571
|
+
const editing = !!form;
|
|
3572
|
+
formView.hidden = !editing;
|
|
3573
|
+
listView.hidden = editing;
|
|
3574
|
+
advancedToggle.hidden = editing;
|
|
3575
|
+
if (editing) {
|
|
3576
|
+
advancedPanel.hidden = true;
|
|
3577
|
+
advancedToggle.setAttribute("aria-expanded", "false");
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
function labelRow(label, description, layout, control) {
|
|
3581
|
+
const row = ctx.makeRow(label, description, layout);
|
|
3582
|
+
if (control) {
|
|
3583
|
+
control.id || (control.id = nextId("ai-docs-field"));
|
|
3584
|
+
const labelNode = row.querySelector("label");
|
|
3585
|
+
if (labelNode) {
|
|
3586
|
+
labelNode.htmlFor = control.id;
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
return row;
|
|
3590
|
+
}
|
|
3591
|
+
function buildForm(state) {
|
|
3592
|
+
const { existing, values } = state;
|
|
3593
|
+
const box = el("div", "ai-docs-form");
|
|
3594
|
+
box.setAttribute("role", "group");
|
|
3595
|
+
const heading = el("div", "ai-subhead", existing ? `Edit ${existing.name}` : "Add a base");
|
|
3596
|
+
heading.id = nextId("ai-docs-form-title");
|
|
3597
|
+
box.setAttribute("aria-labelledby", heading.id);
|
|
3598
|
+
const nameInput = el("input", "settings-input");
|
|
3599
|
+
nameInput.type = "text";
|
|
3600
|
+
nameInput.value = values.name;
|
|
3601
|
+
nameInput.dataset.fk = "form:name";
|
|
3602
|
+
nameInput.spellcheck = false;
|
|
3603
|
+
nameInput.placeholder = "Team docs";
|
|
3604
|
+
nameInput.addEventListener("input", () => {
|
|
3605
|
+
values.name = nameInput.value;
|
|
3606
|
+
});
|
|
3607
|
+
const nameRow = labelRow("Name", "Shown in the chat and when asking before a cloud request.", "inline", nameInput);
|
|
3608
|
+
nameRow.append(nameInput);
|
|
3609
|
+
const rootsError = el("div", "ai-field-error");
|
|
3610
|
+
rootsError.id = nextId("ai-docs-roots-error");
|
|
3611
|
+
rootsError.setAttribute("aria-live", "polite");
|
|
3612
|
+
const rootsList = el("ul", "ai-docs-form-roots");
|
|
3613
|
+
rootsList.setAttribute("aria-label", "Folders in this base");
|
|
3614
|
+
function setRootsError(text) {
|
|
3615
|
+
rootsError.textContent = text;
|
|
3616
|
+
}
|
|
3617
|
+
function renderRoots() {
|
|
3618
|
+
keepFocus(rootsList, () => {
|
|
3619
|
+
rootsList.replaceChildren();
|
|
3620
|
+
for (const root of values.roots) {
|
|
3621
|
+
const item = el("li", "ai-docs-form-root");
|
|
3622
|
+
const pathNode = el("span", "ai-docs-path ai-mono", root);
|
|
3623
|
+
pathNode.title = root;
|
|
3624
|
+
const remove = button("ai-icon-btn", "", { html: ICONS.close, ariaLabel: `Take ${root} out of this base`, title: "Take out of this base", fk: `form:root-remove:${root}` });
|
|
3625
|
+
remove.addEventListener("click", () => {
|
|
3626
|
+
const at = values.roots.indexOf(root);
|
|
3627
|
+
values.roots = values.roots.filter((r) => r !== root);
|
|
3628
|
+
renderRoots();
|
|
3629
|
+
const next = values.roots[Math.min(at, values.roots.length - 1)];
|
|
3630
|
+
focusFk(next ? `form:root-remove:${next}` : firstAddKey);
|
|
3631
|
+
});
|
|
3632
|
+
item.append(pathNode, remove);
|
|
3633
|
+
rootsList.append(item);
|
|
3634
|
+
}
|
|
3635
|
+
});
|
|
3636
|
+
}
|
|
3637
|
+
function addRoot(raw) {
|
|
3638
|
+
const checked = checkBaseRoot(raw, values.roots, otherBases(existing));
|
|
3639
|
+
if (!checked.ok) {
|
|
3640
|
+
setRootsError(checked.message);
|
|
3641
|
+
return false;
|
|
3642
|
+
}
|
|
3643
|
+
setRootsError("");
|
|
3644
|
+
values.roots = [...values.roots, checked.path];
|
|
3645
|
+
if (!values.name.trim()) {
|
|
3646
|
+
values.name = lastPathSegment(checked.path);
|
|
3647
|
+
nameInput.value = values.name;
|
|
3648
|
+
}
|
|
3649
|
+
renderRoots();
|
|
3650
|
+
deps.announce(`Added ${checked.path}.`);
|
|
3651
|
+
return true;
|
|
3652
|
+
}
|
|
3653
|
+
function pickerButton(label, fk, pick) {
|
|
3654
|
+
const b = button("settings-btn settings-btn--secondary", label, { fk });
|
|
3655
|
+
b.addEventListener("click", () => {
|
|
3656
|
+
void pick().then(
|
|
3657
|
+
(path) => {
|
|
3658
|
+
if (form === state && path) {
|
|
3659
|
+
addRoot(path);
|
|
3660
|
+
}
|
|
3661
|
+
},
|
|
3662
|
+
(err) => {
|
|
3663
|
+
if (form === state) {
|
|
3664
|
+
setRootsError(toAiError(err).message);
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
);
|
|
3668
|
+
});
|
|
3669
|
+
return b;
|
|
3670
|
+
}
|
|
3671
|
+
const adders = el("div", "ai-docs-form-adders");
|
|
3672
|
+
if (docs.addWorkspaceFolder) {
|
|
3673
|
+
adders.append(pickerButton("Add this workspace folder", "form:workspace", () => docs.addWorkspaceFolder()));
|
|
3674
|
+
}
|
|
3675
|
+
if (docs.pickFolder) {
|
|
3676
|
+
adders.append(pickerButton("Choose folder\u2026", "form:pick", () => docs.pickFolder()));
|
|
3677
|
+
}
|
|
3678
|
+
let pathLine = null;
|
|
3679
|
+
if (!docs.pickFolder) {
|
|
3680
|
+
pathLine = el("div", "ai-docs-form-path");
|
|
3681
|
+
const pathInput = el("input", "settings-input ai-mono");
|
|
3682
|
+
pathInput.type = "text";
|
|
3683
|
+
pathInput.dataset.fk = "form:path";
|
|
3684
|
+
pathInput.spellcheck = false;
|
|
3685
|
+
pathInput.placeholder = "/full/path/to/docs";
|
|
3686
|
+
pathInput.setAttribute("aria-label", "Folder path");
|
|
3687
|
+
pathInput.setAttribute("aria-describedby", rootsError.id);
|
|
3688
|
+
const addPath = button("settings-btn settings-btn--secondary", "Add", { ariaLabel: "Add this path", fk: "form:add-path" });
|
|
3689
|
+
const submitPath = () => {
|
|
3690
|
+
if (addRoot(pathInput.value)) {
|
|
3691
|
+
pathInput.value = "";
|
|
3692
|
+
pathInput.removeAttribute("aria-invalid");
|
|
3693
|
+
} else {
|
|
3694
|
+
pathInput.setAttribute("aria-invalid", "true");
|
|
3695
|
+
}
|
|
3696
|
+
};
|
|
3697
|
+
addPath.addEventListener("click", submitPath);
|
|
3698
|
+
pathInput.addEventListener("input", () => {
|
|
3699
|
+
pathInput.removeAttribute("aria-invalid");
|
|
3700
|
+
setRootsError("");
|
|
3701
|
+
});
|
|
3702
|
+
pathInput.addEventListener("keydown", (e) => {
|
|
3703
|
+
if (e.key !== "Enter") {
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
e.preventDefault();
|
|
3707
|
+
submitPath();
|
|
3708
|
+
});
|
|
3709
|
+
pathLine.append(pathInput, addPath);
|
|
3710
|
+
}
|
|
3711
|
+
const firstAddKey = docs.addWorkspaceFolder ? "form:workspace" : docs.pickFolder ? "form:pick" : "form:path";
|
|
3712
|
+
const rootsRow = ctx.makeRow("Folders", "Every markdown file under these folders can be read. Symlinks are skipped.");
|
|
3713
|
+
rootsRow.append(rootsList);
|
|
3714
|
+
if (adders.childElementCount > 0) {
|
|
3715
|
+
rootsRow.append(adders);
|
|
3716
|
+
}
|
|
3717
|
+
if (pathLine) {
|
|
3718
|
+
rootsRow.append(pathLine);
|
|
3719
|
+
}
|
|
3720
|
+
rootsRow.append(rootsError);
|
|
3721
|
+
renderRoots();
|
|
3722
|
+
const homeInput = el("input", "settings-input ai-mono");
|
|
3723
|
+
homeInput.type = "text";
|
|
3724
|
+
homeInput.value = values.homeFile;
|
|
3725
|
+
homeInput.dataset.fk = "form:home";
|
|
3726
|
+
homeInput.spellcheck = false;
|
|
3727
|
+
homeInput.placeholder = "HOME.md";
|
|
3728
|
+
const homeError = el("div", "ai-field-error");
|
|
3729
|
+
homeError.id = nextId("ai-docs-home-error");
|
|
3730
|
+
homeError.setAttribute("aria-live", "polite");
|
|
3731
|
+
homeInput.setAttribute("aria-describedby", homeError.id);
|
|
3732
|
+
homeInput.addEventListener("input", () => {
|
|
3733
|
+
values.homeFile = homeInput.value;
|
|
3734
|
+
homeError.textContent = "";
|
|
3735
|
+
homeInput.removeAttribute("aria-invalid");
|
|
3736
|
+
});
|
|
3737
|
+
const homeRow = labelRow("Home file", "Optional. The page to start from in each folder. Otherwise INDEX.md, then README.md.", "inline", homeInput);
|
|
3738
|
+
homeRow.append(homeInput, homeError);
|
|
3739
|
+
const cloud = switchInput(values.allowCloud, DOCS_CLOUD_LABEL, "form:cloud");
|
|
3740
|
+
cloud.addEventListener("change", () => {
|
|
3741
|
+
values.allowCloud = cloud.checked;
|
|
3742
|
+
});
|
|
3743
|
+
const cloudRow = labelRow(DOCS_CLOUD_LABEL, DOCS_CLOUD_LINE, "inline", cloud);
|
|
3744
|
+
cloudRow.append(cloud);
|
|
3745
|
+
const formError = el("div", "ai-docs-form-error");
|
|
3746
|
+
formError.setAttribute("aria-live", "polite");
|
|
3747
|
+
state.showError = (text) => formError.replaceChildren(note("error", text, "ai-result"));
|
|
3748
|
+
const save = button("settings-btn settings-btn--primary", existing ? "Save" : "Add base", { fk: "form:save" });
|
|
3749
|
+
const cancel = button("settings-btn", "Cancel", { fk: "form:cancel" });
|
|
3750
|
+
save.addEventListener("click", () => submit());
|
|
3751
|
+
cancel.addEventListener("click", () => back());
|
|
3752
|
+
const actions = el("div", "ai-docs-form-actions");
|
|
3753
|
+
actions.append(save, cancel);
|
|
3754
|
+
function submit() {
|
|
3755
|
+
if (state.saving) {
|
|
3756
|
+
return;
|
|
3757
|
+
}
|
|
3758
|
+
formError.replaceChildren();
|
|
3759
|
+
const checked = checkDocBaseForm(values, otherBases(existing), existing);
|
|
3760
|
+
setRootsError("");
|
|
3761
|
+
homeError.textContent = "";
|
|
3762
|
+
homeInput.removeAttribute("aria-invalid");
|
|
3763
|
+
if (!checked.ok) {
|
|
3764
|
+
setRootsError(checked.errors.roots ?? "");
|
|
3765
|
+
homeError.textContent = checked.errors.homeFile ?? "";
|
|
3766
|
+
if (checked.errors.homeFile) {
|
|
3767
|
+
homeInput.setAttribute("aria-invalid", "true");
|
|
3768
|
+
}
|
|
3769
|
+
deps.announce(checked.errors.roots ?? checked.errors.homeFile ?? "");
|
|
3770
|
+
focusFk(checked.errors.roots ? firstAddKey : "form:home");
|
|
3771
|
+
return;
|
|
3772
|
+
}
|
|
3773
|
+
state.saving = true;
|
|
3774
|
+
save.setAttribute("aria-disabled", "true");
|
|
3775
|
+
const base = checked.base;
|
|
3776
|
+
void mutate((bases) => existing ? bases.map((b) => b.id === existing.id ? base : b) : [...bases, base]).then((error) => {
|
|
3777
|
+
if (disposed) {
|
|
3778
|
+
return;
|
|
3779
|
+
}
|
|
3780
|
+
if (form !== state) {
|
|
3781
|
+
problem = error;
|
|
3782
|
+
renderList();
|
|
3783
|
+
deps.announce(error ?? `Saved ${base.name || lastPathSegment(base.roots[0])}.`);
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
3786
|
+
state.saving = false;
|
|
3787
|
+
save.removeAttribute("aria-disabled");
|
|
3788
|
+
if (error) {
|
|
3789
|
+
state.showError(error);
|
|
3790
|
+
deps.announce(error);
|
|
3791
|
+
save.focus();
|
|
3792
|
+
return;
|
|
3793
|
+
}
|
|
3794
|
+
const name = base.name || lastPathSegment(base.roots[0]);
|
|
3795
|
+
deps.announce(existing ? `Saved ${name}.` : `Added ${name}. Choose Refresh to check it.`);
|
|
3796
|
+
const saved = existing?.id ?? entries[entries.length - 1]?.base.id;
|
|
3797
|
+
closeForm(saved ? `base:${saved}:edit` : "docs:add");
|
|
3798
|
+
});
|
|
3799
|
+
}
|
|
3800
|
+
for (const input of [nameInput, homeInput]) {
|
|
3801
|
+
input.addEventListener("keydown", (e) => {
|
|
3802
|
+
if (e.key !== "Enter") {
|
|
3803
|
+
return;
|
|
3804
|
+
}
|
|
3805
|
+
e.preventDefault();
|
|
3806
|
+
submit();
|
|
3807
|
+
});
|
|
3808
|
+
}
|
|
3809
|
+
state.focusFirst = () => {
|
|
3810
|
+
if (existing || values.roots.length > 0) {
|
|
3811
|
+
nameInput.focus({ preventScroll: true });
|
|
3812
|
+
} else {
|
|
3813
|
+
focusFk(firstAddKey);
|
|
3814
|
+
}
|
|
3815
|
+
};
|
|
3816
|
+
box.append(heading, ctx.makeGroup(nameRow, rootsRow, homeRow, cloudRow), formError, actions);
|
|
3817
|
+
return box;
|
|
3818
|
+
}
|
|
3819
|
+
function formDirty() {
|
|
3820
|
+
return !!form && JSON.stringify(form.values) !== JSON.stringify(form.initial);
|
|
3821
|
+
}
|
|
3822
|
+
function back() {
|
|
3823
|
+
if (form) {
|
|
3824
|
+
const id = form.existing?.id;
|
|
3825
|
+
closeForm(id ? `base:${id}:edit` : "docs:add");
|
|
3826
|
+
return true;
|
|
3827
|
+
}
|
|
3828
|
+
if (confirmRemoveId) {
|
|
3829
|
+
closeConfirm();
|
|
3830
|
+
return true;
|
|
3831
|
+
}
|
|
3832
|
+
return false;
|
|
3833
|
+
}
|
|
3834
|
+
buildAdvanced();
|
|
3835
|
+
element.append(listView, formView, advancedToggle, advancedPanel);
|
|
3836
|
+
renderList();
|
|
3837
|
+
return {
|
|
3838
|
+
element,
|
|
3839
|
+
opened() {
|
|
3840
|
+
if (openedOnce) {
|
|
3841
|
+
return;
|
|
3842
|
+
}
|
|
3843
|
+
openedOnce = true;
|
|
3844
|
+
load();
|
|
3845
|
+
},
|
|
3846
|
+
back,
|
|
3847
|
+
hasUnsavedForm: formDirty,
|
|
3848
|
+
flagUnsavedForm() {
|
|
3849
|
+
form?.showError("Save this base or cancel it before you choose Done.");
|
|
3850
|
+
deps.announce("Save this base or cancel it before you choose Done.");
|
|
3851
|
+
focusFk("form:save");
|
|
3852
|
+
},
|
|
3853
|
+
focus() {
|
|
3854
|
+
if (form) {
|
|
3855
|
+
form.focusFirst();
|
|
3856
|
+
} else {
|
|
3857
|
+
focusFk("docs:add");
|
|
3858
|
+
}
|
|
3859
|
+
},
|
|
3860
|
+
count: () => loaded ? entries.length : null,
|
|
3861
|
+
dispose() {
|
|
3862
|
+
disposed = true;
|
|
3863
|
+
form = null;
|
|
3864
|
+
}
|
|
3865
|
+
};
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3090
3868
|
// src/ai-setup-wizard.ts
|
|
3091
3869
|
var AI_WIZARD_STEPS = 3;
|
|
3092
3870
|
function wizardStepNumber(step) {
|
|
@@ -3787,7 +4565,7 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
3787
4565
|
let hardware = knownHardware(ai);
|
|
3788
4566
|
let selection = initialSelection();
|
|
3789
4567
|
let narrow = false;
|
|
3790
|
-
let view = opts.initial?.engineId || opts.initial?.section === "routing" ? "detail" : "list";
|
|
4568
|
+
let view = opts.initial?.engineId || opts.initial?.section === "routing" || opts.initial?.section === "bases" && opts.docs ? "detail" : "list";
|
|
3791
4569
|
let confirmDeleteId = null;
|
|
3792
4570
|
let confirmDiscard = false;
|
|
3793
4571
|
let finished = false;
|
|
@@ -3797,6 +4575,9 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
3797
4575
|
if (opts.initial?.section === "routing") {
|
|
3798
4576
|
return { kind: "routing" };
|
|
3799
4577
|
}
|
|
4578
|
+
if (opts.initial?.section === "bases" && opts.docs) {
|
|
4579
|
+
return { kind: "bases" };
|
|
4580
|
+
}
|
|
3800
4581
|
const wanted = opts.initial?.engineId && engines.find((e) => e.id === opts.initial?.engineId);
|
|
3801
4582
|
if (wanted) {
|
|
3802
4583
|
return { kind: "engine", id: wanted.id };
|
|
@@ -3880,6 +4661,15 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
3880
4661
|
},
|
|
3881
4662
|
statusList: () => session.statusList()
|
|
3882
4663
|
});
|
|
4664
|
+
const docsSection = opts.docs ? createDocsBasesSection(ctx, {
|
|
4665
|
+
docs: opts.docs,
|
|
4666
|
+
getDraft: () => session.draft,
|
|
4667
|
+
setDraft: (next) => {
|
|
4668
|
+
session.draft = next;
|
|
4669
|
+
},
|
|
4670
|
+
announce,
|
|
4671
|
+
onCountChanged: () => refreshBasesEntry()
|
|
4672
|
+
}) : null;
|
|
3883
4673
|
async function setKey(id, value) {
|
|
3884
4674
|
const engine = getEngine(id);
|
|
3885
4675
|
const error = await session.setKey(id, value);
|
|
@@ -3972,7 +4762,7 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
3972
4762
|
}
|
|
3973
4763
|
navEngines.append(navList);
|
|
3974
4764
|
}
|
|
3975
|
-
routingEntry.replaceChildren(buildRoutingEntry(), buildAddEntry());
|
|
4765
|
+
routingEntry.replaceChildren(buildRoutingEntry(), ...docsSection ? [buildBasesEntry()] : [], buildAddEntry());
|
|
3976
4766
|
});
|
|
3977
4767
|
}
|
|
3978
4768
|
function buildNavItem(engine) {
|
|
@@ -4046,6 +4836,27 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
4046
4836
|
sub.textContent = routingSubline();
|
|
4047
4837
|
}
|
|
4048
4838
|
}
|
|
4839
|
+
function basesSubline() {
|
|
4840
|
+
const count = docsSection?.count() ?? session.draft.docBases.length;
|
|
4841
|
+
return count === 0 ? "None yet" : `${count} ${count === 1 ? "base" : "bases"}`;
|
|
4842
|
+
}
|
|
4843
|
+
function buildBasesEntry() {
|
|
4844
|
+
const btn = navButton("nav:bases", selection.kind === "bases");
|
|
4845
|
+
btn.classList.add("ai-nav-btn--bases");
|
|
4846
|
+
const icon = el("span", "ai-nav-icon");
|
|
4847
|
+
icon.innerHTML = ICONS.book;
|
|
4848
|
+
const text = el("span", "ai-nav-text");
|
|
4849
|
+
text.append(el("span", "ai-nav-name", "Documentation bases"), el("span", "ai-nav-sub", basesSubline()));
|
|
4850
|
+
btn.append(icon, text);
|
|
4851
|
+
btn.addEventListener("click", () => select({ kind: "bases" }));
|
|
4852
|
+
return btn;
|
|
4853
|
+
}
|
|
4854
|
+
function refreshBasesEntry() {
|
|
4855
|
+
const sub = routingEntry.querySelector(".ai-nav-btn--bases .ai-nav-sub");
|
|
4856
|
+
if (sub) {
|
|
4857
|
+
sub.textContent = basesSubline();
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4049
4860
|
function buildAddEntry() {
|
|
4050
4861
|
const btn = navButton("nav:add", selection.kind === "add");
|
|
4051
4862
|
btn.classList.add("ai-nav-btn--add");
|
|
@@ -4119,6 +4930,11 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
4119
4930
|
} else if (selection.kind === "routing") {
|
|
4120
4931
|
routing.render();
|
|
4121
4932
|
detailBody.append(routing.element);
|
|
4933
|
+
} else if (selection.kind === "bases") {
|
|
4934
|
+
if (docsSection) {
|
|
4935
|
+
detailBody.append(docsSection.element);
|
|
4936
|
+
docsSection.opened();
|
|
4937
|
+
}
|
|
4122
4938
|
} else {
|
|
4123
4939
|
detailBody.append(buildPresetCards());
|
|
4124
4940
|
if (canDetect && !detectedOnce) {
|
|
@@ -4150,6 +4966,14 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
4150
4966
|
detailHead.append(icon, main);
|
|
4151
4967
|
return;
|
|
4152
4968
|
}
|
|
4969
|
+
if (selection.kind === "bases") {
|
|
4970
|
+
const icon = el("span", "ai-detail-icon");
|
|
4971
|
+
icon.innerHTML = ICONS.book;
|
|
4972
|
+
const main = el("div", "ai-detail-main");
|
|
4973
|
+
main.append(detailTitle("Documentation bases"), el("div", "ai-detail-sub", "Folders of markdown the AI can answer from. A change to a base saves right away; nothing on disk is ever changed."));
|
|
4974
|
+
detailHead.append(icon, main);
|
|
4975
|
+
return;
|
|
4976
|
+
}
|
|
4153
4977
|
if (selection.kind === "add") {
|
|
4154
4978
|
const icon = el("span", "ai-detail-icon");
|
|
4155
4979
|
icon.innerHTML = ICONS.plus;
|
|
@@ -4578,6 +5402,9 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
4578
5402
|
wizard.back();
|
|
4579
5403
|
return;
|
|
4580
5404
|
}
|
|
5405
|
+
if (selection.kind === "bases" && docsSection?.back()) {
|
|
5406
|
+
return;
|
|
5407
|
+
}
|
|
4581
5408
|
if (narrow && view === "detail") {
|
|
4582
5409
|
view = "list";
|
|
4583
5410
|
applyLayout();
|
|
@@ -4614,6 +5441,13 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
4614
5441
|
requirePick(unpicked.id);
|
|
4615
5442
|
return;
|
|
4616
5443
|
}
|
|
5444
|
+
if (docsSection?.hasUnsavedForm()) {
|
|
5445
|
+
if (selection.kind !== "bases") {
|
|
5446
|
+
select({ kind: "bases" });
|
|
5447
|
+
}
|
|
5448
|
+
docsSection.flagUnsavedForm();
|
|
5449
|
+
return;
|
|
5450
|
+
}
|
|
4617
5451
|
finished = true;
|
|
4618
5452
|
const patch = session.collect();
|
|
4619
5453
|
const saved = session.commit();
|
|
@@ -4655,7 +5489,7 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
4655
5489
|
box.setAttribute("aria-describedby", textId);
|
|
4656
5490
|
const head = el("div", "ai-page-confirm-title", "Discard changes?");
|
|
4657
5491
|
head.id = headId;
|
|
4658
|
-
const text = el("div", "ai-page-confirm-text", "Your changes to engines and tasks have not been saved. Engines added here and their keys are removed.");
|
|
5492
|
+
const text = el("div", "ai-page-confirm-text", docsSection ? "Your changes to engines, tasks and base options have not been saved. Engines added here and their keys are removed. Bases you saved stay saved." : "Your changes to engines and tasks have not been saved. Engines added here and their keys are removed.");
|
|
4659
5493
|
text.id = textId;
|
|
4660
5494
|
const keep = button("settings-btn", "Keep editing", { fk: "page:keep" });
|
|
4661
5495
|
keep.addEventListener("click", () => hideDiscard());
|
|
@@ -4705,7 +5539,7 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
4705
5539
|
applyLayout();
|
|
4706
5540
|
}
|
|
4707
5541
|
function focusSelectedEntry() {
|
|
4708
|
-
const fk = selection.kind === "engine" ? `nav:eng:${selection.id}` : selection.kind === "routing" ? "nav:routing" : "nav:add";
|
|
5542
|
+
const fk = selection.kind === "engine" ? `nav:eng:${selection.id}` : selection.kind === "routing" ? "nav:routing" : selection.kind === "bases" ? "nav:bases" : "nav:add";
|
|
4709
5543
|
focusKey(fk);
|
|
4710
5544
|
}
|
|
4711
5545
|
let resizeObserver = null;
|
|
@@ -4855,6 +5689,7 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
4855
5689
|
unsubscribe?.();
|
|
4856
5690
|
resizeObserver?.disconnect();
|
|
4857
5691
|
form.dispose();
|
|
5692
|
+
docsSection?.dispose();
|
|
4858
5693
|
container.replaceChildren();
|
|
4859
5694
|
},
|
|
4860
5695
|
focus() {
|
|
@@ -4866,6 +5701,8 @@ function mountAiEnginesPage(container, ctx, opts) {
|
|
|
4866
5701
|
focusDetailTitle();
|
|
4867
5702
|
} else if (!narrow && selection.kind === "routing") {
|
|
4868
5703
|
focusKey("route:default");
|
|
5704
|
+
} else if (!narrow && selection.kind === "bases") {
|
|
5705
|
+
docsSection?.focus();
|
|
4869
5706
|
} else if (!narrow && selection.kind === "add" && session.draft.engines.length === 0) {
|
|
4870
5707
|
detailBody.querySelector(".ai-card")?.focus({ preventScroll: true });
|
|
4871
5708
|
} else {
|
|
@@ -4905,6 +5742,7 @@ export {
|
|
|
4905
5742
|
bestLocalServer,
|
|
4906
5743
|
setUpLocalEngine,
|
|
4907
5744
|
scanForSetup,
|
|
5745
|
+
createDocsBasesSection,
|
|
4908
5746
|
mountAiEnginesPage
|
|
4909
5747
|
};
|
|
4910
|
-
//# sourceMappingURL=chunk-
|
|
5748
|
+
//# sourceMappingURL=chunk-DWXGWVA3.js.map
|