murmur-rag 1.0.0 → 1.2.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/README.md +27 -23
- package/dist/cdn/anthropic-ynz7717b.js +2 -0
- package/dist/cdn/chunk-hpx6rpea.js +1 -0
- package/dist/cdn/{embed-59r4jck6.js → embed-0m8xctqy.js} +3 -3
- package/dist/cdn/{embed-pnm9593k.js → embed-7pk91zq8.js} +2 -2
- package/dist/cdn/embed-dbkjhg16.js +83 -0
- package/dist/cdn/{embed-q6y6vgv1.js → embed-krrjjyf5.js} +4 -4
- package/dist/cdn/embed-n5nzhrf2.js +9 -0
- package/dist/cdn/{embed-hn0r8fmg.js → embed-rp9gb1qq.js} +2 -2
- package/dist/cdn/embed-xtkxyrpe.js +44 -0
- package/dist/cdn/google-k5kwa54v.js +14 -0
- package/dist/cdn/index-db4ne93m.js +1 -0
- package/dist/cdn/index-gyd02jvt.js +9 -0
- package/dist/cdn/index-qc8kgsdt.js +1 -0
- package/dist/cdn/index-t0qn2m4r.js +1 -0
- package/dist/cdn/index-y0faj89r.js +1 -0
- package/dist/cdn/mistral-p2qcnn42.js +3 -0
- package/dist/cdn/murmur-d6t6cjc7.js +5 -0
- package/dist/cdn/murmur.js +24 -5
- package/dist/cdn/openai-compatible-psnkrpxy.js +11 -0
- package/dist/cdn/openai-d3q1d6cz.js +18 -0
- package/dist/cdn/retriever-6qvn2maq.js +1 -0
- package/dist/cdn/transformers.web-xn16v7pz.js +46 -0
- package/dist/cli.js +2 -0
- package/dist/lib/chunks/{index-ynjdey8j.js → index-8h9c8ekq.js} +4 -2
- package/dist/lib/chunks/{index-6re28wc1.js → index-b77xqap3.js} +13 -1
- package/dist/lib/chunks/{index-10g918kt.js → index-p86sy3y4.js} +1 -1
- package/dist/lib/chunks/{index-4at214g0.js → index-y3v46yjp.js} +314 -27
- package/dist/lib/chunks/{murmur-c1c79eh8.js → murmur-t36hd4vt.js} +2 -2
- package/dist/lib/chunks/{retriever-h8nz6jsc.js → retriever-wxshtnm0.js} +1 -1
- package/dist/lib/embed.js +4 -2
- package/dist/lib/index.js +5 -3
- package/dist/lib/indexer/index.js +2 -0
- package/dist/lib/local/index.js +163 -0
- package/dist/lib/rag/index.js +13 -11
- package/dist/lib/widget/index.js +2 -2
- package/dist/types/local/index.d.ts +56 -0
- package/dist/types/rag/ground.d.ts +7 -0
- package/dist/types/rag/index.d.ts +1 -1
- package/dist/types/widget/element.d.ts +3 -0
- package/dist/types/widget/i18n.d.ts +14 -0
- package/dist/types/widget/session.d.ts +32 -1
- package/dist/types/widget/styles.d.ts +1 -1
- package/package.json +17 -3
- package/dist/cdn/anthropic-w7nkjk5e.js +0 -2
- package/dist/cdn/chunk-e0j28zmb.js +0 -1
- package/dist/cdn/embed-2xm309sb.js +0 -7
- package/dist/cdn/embed-n7jskdxd.js +0 -44
- package/dist/cdn/embed-sgjh2kg2.js +0 -83
- package/dist/cdn/google-qha5xyx4.js +0 -14
- package/dist/cdn/index-11c9cspb.js +0 -1
- package/dist/cdn/index-e5entetm.js +0 -1
- package/dist/cdn/index-hv290nbv.js +0 -9
- package/dist/cdn/index-kk9hgxdc.js +0 -1
- package/dist/cdn/mistral-16bekg8b.js +0 -3
- package/dist/cdn/murmur-bnm335nb.js +0 -5
- package/dist/cdn/openai-compatible-za7hc1fv.js +0 -11
- package/dist/cdn/openai-dhy4fp4c.js +0 -18
- package/dist/cdn/retriever-m8peqj5t.js +0 -1
package/dist/cli.js
CHANGED
|
@@ -127,6 +127,8 @@ function extractSections(doc, { root = DEFAULT_ROOT, exclude = DEFAULT_EXCLUDE }
|
|
|
127
127
|
if (tag === "BR")
|
|
128
128
|
cur.text += `
|
|
129
129
|
`;
|
|
130
|
+
if ((tag === "TD" || tag === "TH") && !/(^|\n)\s*$/.test(cur.text))
|
|
131
|
+
cur.text += " | ";
|
|
130
132
|
const block = BLOCK.test(tag);
|
|
131
133
|
if (block)
|
|
132
134
|
cur.text += `
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
bm25Retriever2
|
|
3
|
-
} from "./index-
|
|
3
|
+
} from "./index-p86sy3y4.js";
|
|
4
4
|
import {
|
|
5
5
|
estimateTokens2,
|
|
6
6
|
formatSources2,
|
|
7
7
|
groundingRules2,
|
|
8
|
+
cleanAnswer2,
|
|
8
9
|
citedSources2
|
|
9
|
-
} from "./index-
|
|
10
|
+
} from "./index-b77xqap3.js";
|
|
10
11
|
|
|
11
12
|
// src/core/murmur.ts
|
|
12
13
|
import { streamText } from "ai";
|
|
@@ -67,6 +68,7 @@ ${formatSources2(passages)}` : "Sources: none matched this question. Say you cou
|
|
|
67
68
|
text += part.text;
|
|
68
69
|
yield part.text;
|
|
69
70
|
}
|
|
71
|
+
text = cleanAnswer2(text);
|
|
70
72
|
settle({ text, cited: citedSources2(text, passages.length) });
|
|
71
73
|
} catch (error) {
|
|
72
74
|
fail(error);
|
|
@@ -22,11 +22,23 @@ function groundingRules2(language) {
|
|
|
22
22
|
"Cite the sources you use with their number in square brackets, like [1] or [2][3], right after the sentence they support.",
|
|
23
23
|
"If the sources do not contain the answer, say so plainly and suggest where on the site to look; never invent facts, links or prices.",
|
|
24
24
|
"Be concise: a few short paragraphs or a short list. Use Markdown for emphasis, lists and code.",
|
|
25
|
+
"Do not end with a list of sources: the interface shows them under your answer.",
|
|
25
26
|
language ? `Write in the language whose code is "${language}" unless the visitor writes in another language.` : "Write in the visitor's language.",
|
|
26
27
|
SOURCES_RULE2
|
|
27
28
|
].join(`
|
|
28
29
|
`);
|
|
29
30
|
}
|
|
31
|
+
function cleanAnswer2(text) {
|
|
32
|
+
return text.replace(/<think>[\s\S]*?(<\/think>|$)/gi, "").replace(/[ \t]*<<<\s*(?:END\s+)?SOURCE\b[^>\n]*>{0,3}\.?/gi, "").replace(/<{1,3}(?:\s*E(?:N(?:D)?)?)?\s*(?:S(?:O(?:U(?:R(?:C(?:E)?)?)?)?)?)?$/i, (m) => m.startsWith("<<") || /[A-Z]/i.test(m) ? "" : m).replace(/【\s*(\d{1,2})\s*】/g, "[$1]").replace(/\[\s*(?:sources?|src|réf\.?|ref\.?)\s*(\d{1,2})\s*\]/gi, "[$1]").replace(/\[\s*(\d{1,2})\s*[-–]\s*(\d{1,2})\s*\]/g, (all, a, b) => {
|
|
33
|
+
const from = Number(a);
|
|
34
|
+
const to = Number(b);
|
|
35
|
+
if (to <= from || to - from > 8)
|
|
36
|
+
return all;
|
|
37
|
+
return Array.from({ length: to - from + 1 }, (_, i) => `[${from + i}]`).join("");
|
|
38
|
+
}).replace(/\[\s*(\d{1,2}(?:\s*[,;]\s*\d{1,2})+)\s*\]/g, (_, list) => list.split(/[,;]/).map((n) => `[${n.trim()}]`).join("")).replace(/\n+\s*\**\s*(?:sources?|références?|references?)\s*:?\s*\**\s*:?\s*(?:\[?\d{1,2}\]?[\s,;.]*)+$/i, "").replace(/\n{3,}/g, `
|
|
39
|
+
|
|
40
|
+
`).trimEnd();
|
|
41
|
+
}
|
|
30
42
|
function citedSources2(answer, count) {
|
|
31
43
|
const out = [];
|
|
32
44
|
for (const m of answer.matchAll(/\[(\d{1,2})\]/g)) {
|
|
@@ -43,4 +55,4 @@ function passageHref2(p) {
|
|
|
43
55
|
return `${base}#${encodeURIComponent(p.anchor)}`;
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
export { estimateTokens2, formatSources2, SOURCES_RULE2, groundingRules2, citedSources2, passageHref2 };
|
|
58
|
+
export { estimateTokens2, formatSources2, SOURCES_RULE2, groundingRules2, cleanAnswer2, citedSources2, passageHref2 };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
cleanAnswer2,
|
|
2
3
|
passageHref2
|
|
3
|
-
} from "./index-
|
|
4
|
+
} from "./index-b77xqap3.js";
|
|
4
5
|
var __chunkGraphs;
|
|
5
6
|
var __chunkSeen;
|
|
6
7
|
var __chunkNonce;
|
|
@@ -54,7 +55,7 @@ var en = {
|
|
|
54
55
|
errorOther: "The answer failed:",
|
|
55
56
|
retry: "Try again",
|
|
56
57
|
setupTitle: "Connect an AI model",
|
|
57
|
-
setupIntro: "
|
|
58
|
+
setupIntro: "Pick how this site answers you: a model that runs in your browser, or your own AI key, kept encrypted in your browser and sent only to the provider you pick.",
|
|
58
59
|
provider: "Provider",
|
|
59
60
|
model: "Model",
|
|
60
61
|
apiKey: "API key",
|
|
@@ -73,7 +74,21 @@ var en = {
|
|
|
73
74
|
catalogError: "Could not load the model list.",
|
|
74
75
|
searchModels: "Search models",
|
|
75
76
|
recommended: "Recommended",
|
|
76
|
-
otherProviders: "Other providers"
|
|
77
|
+
otherProviders: "Other providers",
|
|
78
|
+
localOption: "Run a model in this browser",
|
|
79
|
+
localOptionHint: "{name}, about {size} downloaded once, then kept in this browser. Needs WebGPU; nothing you type leaves your device.",
|
|
80
|
+
localActive: "Running in this browser",
|
|
81
|
+
orKey: "or connect a provider with your key",
|
|
82
|
+
useKey: "Use an API key instead",
|
|
83
|
+
localConsent: "This assistant can run entirely in your browser with {name}, an AI model of about {size}. It downloads once, then stays in this browser; your questions never leave your device.",
|
|
84
|
+
localDownload: "Download and answer",
|
|
85
|
+
localLoading: "Downloading the model…",
|
|
86
|
+
localLoadingLabel: "Model download",
|
|
87
|
+
localPreparing: "Preparing the model on your GPU…",
|
|
88
|
+
localNoWebGPU: "This browser cannot run the model locally (no WebGPU). Try a recent Chrome, Edge or Safari, or use an API key.",
|
|
89
|
+
localLoadFailed: "The model could not be loaded in this browser.",
|
|
90
|
+
gigabytes: "GB",
|
|
91
|
+
megabytes: "MB"
|
|
77
92
|
};
|
|
78
93
|
var fr = {
|
|
79
94
|
open: "Poser une question",
|
|
@@ -96,7 +111,7 @@ var fr = {
|
|
|
96
111
|
errorOther: "La réponse a échoué :",
|
|
97
112
|
retry: "Réessayer",
|
|
98
113
|
setupTitle: "Connecter un modèle d'IA",
|
|
99
|
-
setupIntro: "
|
|
114
|
+
setupIntro: "Choisissez comment ce site vous répond : un modèle qui tourne dans votre navigateur, ou votre propre clé d'IA, gardée chiffrée dans votre navigateur et envoyée seulement au fournisseur choisi.",
|
|
100
115
|
provider: "Fournisseur",
|
|
101
116
|
model: "Modèle",
|
|
102
117
|
apiKey: "Clé API",
|
|
@@ -115,7 +130,21 @@ var fr = {
|
|
|
115
130
|
catalogError: "Impossible de charger la liste des modèles.",
|
|
116
131
|
searchModels: "Chercher un modèle",
|
|
117
132
|
recommended: "Recommandés",
|
|
118
|
-
otherProviders: "Autres fournisseurs"
|
|
133
|
+
otherProviders: "Autres fournisseurs",
|
|
134
|
+
localOption: "Faire tourner un modèle dans ce navigateur",
|
|
135
|
+
localOptionHint: "{name}, environ {size} téléchargés une fois puis gardés dans ce navigateur. Nécessite WebGPU ; rien de ce que vous tapez ne quitte votre appareil.",
|
|
136
|
+
localActive: "Modèle actif dans ce navigateur",
|
|
137
|
+
orKey: "ou connectez un fournisseur avec votre clé",
|
|
138
|
+
useKey: "Utiliser une clé API à la place",
|
|
139
|
+
localConsent: "Cet assistant peut fonctionner entièrement dans votre navigateur avec {name}, un modèle d'IA d'environ {size}. Il se télécharge une fois puis reste dans ce navigateur ; vos questions ne quittent jamais votre appareil.",
|
|
140
|
+
localDownload: "Télécharger et répondre",
|
|
141
|
+
localLoading: "Téléchargement du modèle…",
|
|
142
|
+
localLoadingLabel: "Téléchargement du modèle",
|
|
143
|
+
localPreparing: "Préparation du modèle sur votre GPU…",
|
|
144
|
+
localNoWebGPU: "Ce navigateur ne peut pas faire tourner le modèle en local (pas de WebGPU). Essayez un Chrome, Edge ou Safari récent, ou utilisez une clé API.",
|
|
145
|
+
localLoadFailed: "Le modèle n'a pas pu être chargé dans ce navigateur.",
|
|
146
|
+
gigabytes: "Go",
|
|
147
|
+
megabytes: "Mo"
|
|
119
148
|
};
|
|
120
149
|
var MESSAGES2 = { en, fr };
|
|
121
150
|
function pickLanguage2(...candidates) {
|
|
@@ -194,6 +223,13 @@ function inline(doc, text, cite, out) {
|
|
|
194
223
|
if (last < text.length)
|
|
195
224
|
out.appendChild(doc.createTextNode(text.slice(last)));
|
|
196
225
|
}
|
|
226
|
+
var TABLE_RULE = /^\s*\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)*\|?\s*$/;
|
|
227
|
+
function lineBreak(doc, previous) {
|
|
228
|
+
return / {2,}$|\\$/.test(previous) ? doc.createElement("br") : doc.createTextNode(" ");
|
|
229
|
+
}
|
|
230
|
+
function cells(row) {
|
|
231
|
+
return row.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
|
|
232
|
+
}
|
|
197
233
|
function renderMarkdown2(target, markdown, cite) {
|
|
198
234
|
const doc = target.ownerDocument;
|
|
199
235
|
const frag = doc.createDocumentFragment();
|
|
@@ -206,7 +242,11 @@ function renderMarkdown2(target, markdown, cite) {
|
|
|
206
242
|
if (!para.length)
|
|
207
243
|
return;
|
|
208
244
|
const p = doc.createElement("p");
|
|
209
|
-
|
|
245
|
+
para.forEach((raw, n) => {
|
|
246
|
+
if (n)
|
|
247
|
+
p.appendChild(lineBreak(doc, para[n - 1] ?? ""));
|
|
248
|
+
inline(doc, raw.trim(), cite, p);
|
|
249
|
+
});
|
|
210
250
|
frag.appendChild(p);
|
|
211
251
|
para = [];
|
|
212
252
|
};
|
|
@@ -215,20 +255,68 @@ function renderMarkdown2(target, markdown, cite) {
|
|
|
215
255
|
};
|
|
216
256
|
for (let i = 0;i < lines.length; i++) {
|
|
217
257
|
const line = lines[i] ?? "";
|
|
218
|
-
const fence =
|
|
258
|
+
const fence = /^(\s*)```/.exec(line);
|
|
219
259
|
if (fence) {
|
|
220
260
|
flushPara();
|
|
221
|
-
|
|
261
|
+
const indent = fence[1]?.length ?? 0;
|
|
262
|
+
const owner = indent >= 2 && list ? list.lastElementChild : null;
|
|
263
|
+
if (!owner)
|
|
264
|
+
closeList();
|
|
222
265
|
const body = [];
|
|
223
|
-
for (i++;i < lines.length && !/^\s*```/.test(lines[i] ?? ""); i++)
|
|
224
|
-
|
|
266
|
+
for (i++;i < lines.length && !/^\s*```/.test(lines[i] ?? ""); i++) {
|
|
267
|
+
const raw = lines[i] ?? "";
|
|
268
|
+
body.push(raw.startsWith(" ".repeat(indent)) ? raw.slice(indent) : raw.trimStart());
|
|
269
|
+
}
|
|
225
270
|
const pre = doc.createElement("pre");
|
|
226
271
|
pre.tabIndex = 0;
|
|
227
272
|
const code = doc.createElement("code");
|
|
228
273
|
code.textContent = body.join(`
|
|
229
274
|
`);
|
|
230
275
|
pre.appendChild(code);
|
|
231
|
-
frag.appendChild(pre);
|
|
276
|
+
(owner ?? frag).appendChild(pre);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (/^\s*([-*_])(?:\s*\1){2,}\s*$/.test(line)) {
|
|
280
|
+
flushPara();
|
|
281
|
+
closeList();
|
|
282
|
+
frag.appendChild(doc.createElement("hr"));
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (line.includes("|") && TABLE_RULE.test(lines[i + 1] ?? "")) {
|
|
286
|
+
flushPara();
|
|
287
|
+
closeList();
|
|
288
|
+
const rows = [cells(line)];
|
|
289
|
+
for (i += 2;i < lines.length && (lines[i] ?? "").includes("|"); i++)
|
|
290
|
+
rows.push(cells(lines[i] ?? ""));
|
|
291
|
+
i--;
|
|
292
|
+
const wrap = doc.createElement("div");
|
|
293
|
+
wrap.className = "table";
|
|
294
|
+
wrap.tabIndex = 0;
|
|
295
|
+
const table = doc.createElement("table");
|
|
296
|
+
rows.forEach((row, r) => {
|
|
297
|
+
const tr = doc.createElement("tr");
|
|
298
|
+
for (const cell of row) {
|
|
299
|
+
const td = doc.createElement(r === 0 ? "th" : "td");
|
|
300
|
+
inline(doc, cell, cite, td);
|
|
301
|
+
tr.appendChild(td);
|
|
302
|
+
}
|
|
303
|
+
table.appendChild(tr);
|
|
304
|
+
});
|
|
305
|
+
wrap.appendChild(table);
|
|
306
|
+
frag.appendChild(wrap);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const quote = /^\s*>\s?(.*)$/.exec(line);
|
|
310
|
+
if (quote) {
|
|
311
|
+
flushPara();
|
|
312
|
+
closeList();
|
|
313
|
+
const body = [quote[1] ?? ""];
|
|
314
|
+
for (;/^\s*>/.test(lines[i + 1] ?? ""); i++)
|
|
315
|
+
body.push((lines[i + 1] ?? "").replace(/^\s*>\s?/, ""));
|
|
316
|
+
const blockquote = doc.createElement("blockquote");
|
|
317
|
+
renderMarkdown2(blockquote, body.join(`
|
|
318
|
+
`), cite);
|
|
319
|
+
frag.appendChild(blockquote);
|
|
232
320
|
continue;
|
|
233
321
|
}
|
|
234
322
|
const item = /^\s*(?:([-*+])|(\d+)[.)])\s+(.*)$/.exec(line);
|
|
@@ -237,10 +325,12 @@ function renderMarkdown2(target, markdown, cite) {
|
|
|
237
325
|
const tag = item[1] ? "ul" : "ol";
|
|
238
326
|
if (!list || list.tagName.toLowerCase() !== tag) {
|
|
239
327
|
list = doc.createElement(tag);
|
|
328
|
+
if (tag === "ol" && item[2] && item[2] !== "1")
|
|
329
|
+
list.setAttribute("start", item[2]);
|
|
240
330
|
frag.appendChild(list);
|
|
241
331
|
}
|
|
242
332
|
const li = doc.createElement("li");
|
|
243
|
-
inline(doc, item[3] ?? "", cite, li);
|
|
333
|
+
inline(doc, (item[3] ?? "").trimEnd(), cite, li);
|
|
244
334
|
list.appendChild(li);
|
|
245
335
|
continue;
|
|
246
336
|
}
|
|
@@ -257,33 +347,41 @@ function renderMarkdown2(target, markdown, cite) {
|
|
|
257
347
|
}
|
|
258
348
|
if (!line.trim()) {
|
|
259
349
|
flushPara();
|
|
260
|
-
|
|
350
|
+
const next = lines.slice(i + 1).find((l) => l.trim()) ?? "";
|
|
351
|
+
const same = list?.tagName.toLowerCase() === "ol" ? /^\s*\d+[.)]\s+/ : /^\s*[-*+]\s+/;
|
|
352
|
+
if (!list || !same.test(next))
|
|
353
|
+
closeList();
|
|
261
354
|
continue;
|
|
262
355
|
}
|
|
263
356
|
if (list && /^\s{2,}\S/.test(line)) {
|
|
264
357
|
const li = list.lastElementChild;
|
|
265
358
|
if (li) {
|
|
266
|
-
li.appendChild(doc
|
|
359
|
+
li.appendChild(lineBreak(doc, lines[i - 1] ?? ""));
|
|
267
360
|
inline(doc, line.trim(), cite, li);
|
|
268
361
|
continue;
|
|
269
362
|
}
|
|
270
363
|
}
|
|
271
364
|
closeList();
|
|
272
|
-
para.push(line
|
|
365
|
+
para.push(line);
|
|
273
366
|
}
|
|
274
367
|
flushPara();
|
|
275
368
|
target.replaceChildren(frag);
|
|
276
369
|
}
|
|
277
370
|
|
|
278
371
|
// src/widget/session.ts
|
|
372
|
+
var LOCAL_SEARCH = { limit: 4, budgetTokens: 1500 };
|
|
373
|
+
|
|
279
374
|
class ChatSession {
|
|
280
375
|
options;
|
|
281
376
|
turns = [];
|
|
282
377
|
engine = null;
|
|
283
378
|
storePromise = null;
|
|
284
379
|
historyKey;
|
|
380
|
+
key;
|
|
381
|
+
onProgress = null;
|
|
285
382
|
constructor(options) {
|
|
286
383
|
this.options = options;
|
|
384
|
+
this.key = options.storageKey ?? "murmur";
|
|
287
385
|
this.historyKey = `${options.storageKey ?? "murmur"}-chat:${location.pathname.split("/")[1] ?? ""}`;
|
|
288
386
|
try {
|
|
289
387
|
const saved = sessionStorage.getItem(this.historyKey);
|
|
@@ -296,7 +394,38 @@ class ChatSession {
|
|
|
296
394
|
get mode() {
|
|
297
395
|
if (this.options.model)
|
|
298
396
|
return "custom";
|
|
299
|
-
|
|
397
|
+
if (this.options.llm?.endpoint)
|
|
398
|
+
return "proxy";
|
|
399
|
+
if (!this.localAllowed)
|
|
400
|
+
return "visitor";
|
|
401
|
+
return readLocal(`${this.key}-local`) === "0" ? "visitor" : "local";
|
|
402
|
+
}
|
|
403
|
+
get localAllowed() {
|
|
404
|
+
return !this.options.model && !this.options.llm?.endpoint && this.options.local !== false;
|
|
405
|
+
}
|
|
406
|
+
async settleChoice() {
|
|
407
|
+
if (!this.localAllowed || readLocal(`${this.key}-local`) !== null)
|
|
408
|
+
return;
|
|
409
|
+
const [{ isConfigured }, store] = await Promise.all([(__preload("vb9smwta"), import("../llm/index.js")), this.store()]);
|
|
410
|
+
const saved = await store.load();
|
|
411
|
+
if (saved && isConfigured(saved))
|
|
412
|
+
writeLocal(`${this.key}-local`, "0");
|
|
413
|
+
}
|
|
414
|
+
async localSpec() {
|
|
415
|
+
const { local } = this.options;
|
|
416
|
+
if (local && typeof local === "object")
|
|
417
|
+
return local;
|
|
418
|
+
return (await (__preload("nb4hdy2t"), import("../local/index.js"))).DEFAULT_LOCAL_MODEL;
|
|
419
|
+
}
|
|
420
|
+
useLocal(on) {
|
|
421
|
+
writeLocal(`${this.key}-local`, on ? "1" : "0");
|
|
422
|
+
this.engine = null;
|
|
423
|
+
}
|
|
424
|
+
get needsDownloadConsent() {
|
|
425
|
+
return this.mode === "local" && readLocal(`${this.key}-local-consent`) !== "1";
|
|
426
|
+
}
|
|
427
|
+
agreeToDownload() {
|
|
428
|
+
writeLocal(`${this.key}-local-consent`, "1");
|
|
300
429
|
}
|
|
301
430
|
persist() {
|
|
302
431
|
try {
|
|
@@ -327,6 +456,9 @@ class ChatSession {
|
|
|
327
456
|
async needsSetup() {
|
|
328
457
|
return this.mode === "visitor" && !await this.settings();
|
|
329
458
|
}
|
|
459
|
+
async canRunLocally() {
|
|
460
|
+
return (await (__preload("nb4hdy2t"), import("../local/index.js"))).hasWebGPU();
|
|
461
|
+
}
|
|
330
462
|
async loadIndex() {
|
|
331
463
|
const { index } = this.options;
|
|
332
464
|
if (index && typeof index !== "string")
|
|
@@ -341,23 +473,34 @@ class ChatSession {
|
|
|
341
473
|
throw new Error("murmur: not a murmur index");
|
|
342
474
|
return data;
|
|
343
475
|
}
|
|
344
|
-
const { indexDocument } = await (__preload("
|
|
476
|
+
const { indexDocument } = await (__preload("dj4321kh"), import("../indexer/index.js"));
|
|
345
477
|
return indexDocument(document, location.pathname + location.search);
|
|
346
478
|
}
|
|
347
479
|
createEngine() {
|
|
348
480
|
return (async () => {
|
|
349
481
|
const [{ createMurmur }, retriever] = await Promise.all([
|
|
350
|
-
(__preload("
|
|
351
|
-
this.options.retriever ?? (__preload("
|
|
482
|
+
(__preload("t36hd4vt"), import("./murmur-t36hd4vt.js")),
|
|
483
|
+
this.options.retriever ?? (__preload("wxshtnm0"), import("./retriever-wxshtnm0.js")).then(async (r) => r.bm25Retriever(await this.loadIndex()))
|
|
352
484
|
]);
|
|
353
|
-
const
|
|
485
|
+
const local = this.mode === "local";
|
|
486
|
+
const model = this.options.model ?? (local ? async () => {
|
|
487
|
+
const { localModel } = await (__preload("nb4hdy2t"), import("../local/index.js"));
|
|
488
|
+
return localModel({
|
|
489
|
+
spec: await this.localSpec(),
|
|
490
|
+
onProgress: (p) => this.onProgress?.(p)
|
|
491
|
+
});
|
|
492
|
+
} : async () => {
|
|
354
493
|
const settings = await this.settings();
|
|
355
494
|
if (!settings)
|
|
356
495
|
throw new SetupNeeded2;
|
|
357
496
|
const { loadModel } = await (__preload("vb9smwta"), import("../llm/index.js"));
|
|
358
497
|
return loadModel(settings);
|
|
359
498
|
});
|
|
360
|
-
const opts = {
|
|
499
|
+
const opts = {
|
|
500
|
+
retriever,
|
|
501
|
+
model,
|
|
502
|
+
...local ? LOCAL_SEARCH : {}
|
|
503
|
+
};
|
|
361
504
|
if (this.options.instructions)
|
|
362
505
|
opts.instructions = this.options.instructions;
|
|
363
506
|
if (this.options.lang)
|
|
@@ -388,7 +531,9 @@ class ChatSession {
|
|
|
388
531
|
turn.content += part;
|
|
389
532
|
onText(turn.content, turn);
|
|
390
533
|
}
|
|
391
|
-
|
|
534
|
+
const done = await answer.done;
|
|
535
|
+
turn.content = done.text;
|
|
536
|
+
turn.cited = done.cited;
|
|
392
537
|
} catch (error) {
|
|
393
538
|
if (signal.aborted) {
|
|
394
539
|
turn.cited = [];
|
|
@@ -404,9 +549,12 @@ class ChatSession {
|
|
|
404
549
|
return turn;
|
|
405
550
|
}
|
|
406
551
|
async describe(error) {
|
|
552
|
+
if (error instanceof Error && error.name === "LocalModelError") {
|
|
553
|
+
return `local-${error.code ?? "load"}|${error.message}`;
|
|
554
|
+
}
|
|
407
555
|
try {
|
|
408
556
|
const { describeError } = await (__preload("vb9smwta"), import("../llm/index.js"));
|
|
409
|
-
const settings = this.mode === "custom" ? null : await this.settings();
|
|
557
|
+
const settings = this.mode === "custom" || this.mode === "local" ? null : await this.settings();
|
|
410
558
|
const info = describeError(error, settings?.apiKey);
|
|
411
559
|
return `${info.code}|${info.message}`;
|
|
412
560
|
} catch {
|
|
@@ -414,6 +562,21 @@ class ChatSession {
|
|
|
414
562
|
}
|
|
415
563
|
}
|
|
416
564
|
}
|
|
565
|
+
function readLocal(key) {
|
|
566
|
+
try {
|
|
567
|
+
return localStorage.getItem(key);
|
|
568
|
+
} catch {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function writeLocal(key, value) {
|
|
573
|
+
try {
|
|
574
|
+
if (value === null)
|
|
575
|
+
localStorage.removeItem(key);
|
|
576
|
+
else
|
|
577
|
+
localStorage.setItem(key, value);
|
|
578
|
+
} catch {}
|
|
579
|
+
}
|
|
417
580
|
|
|
418
581
|
class SetupNeeded2 extends Error {
|
|
419
582
|
constructor() {
|
|
@@ -528,6 +691,13 @@ header .icon-btn:focus-visible { outline-color: var(--accent-ink); }
|
|
|
528
691
|
.msg code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.88em; background: var(--raised); padding: 0.1em 0.35em; border-radius: 5px; }
|
|
529
692
|
.msg pre { margin: 0 0 0.6em; padding: 10px 12px; background: var(--raised); border-radius: 10px; overflow-x: auto; }
|
|
530
693
|
.msg pre code { background: none; padding: 0; }
|
|
694
|
+
.msg .table { margin: 0 0 0.6em; overflow-x: auto; border-radius: 10px; box-shadow: inset 0 0 0 1px var(--line); }
|
|
695
|
+
.msg table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
696
|
+
.msg th, .msg td { padding: 6px 10px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--line); }
|
|
697
|
+
.msg tr:last-child td { border-bottom: 0; }
|
|
698
|
+
.msg th { font-weight: 650; background: var(--raised); }
|
|
699
|
+
.msg blockquote { margin: 0 0 0.6em; padding: 2px 0 2px 12px; color: var(--muted); box-shadow: inset 1px 0 0 var(--line); }
|
|
700
|
+
.msg hr { margin: 0.8em 0; border: 0; border-top: 1px solid var(--line); }
|
|
531
701
|
.user {
|
|
532
702
|
align-self: flex-end; max-width: 85%;
|
|
533
703
|
padding: 9px 14px; border-radius: 16px 16px 4px 16px;
|
|
@@ -614,6 +784,17 @@ footer a:hover { text-decoration: underline; }
|
|
|
614
784
|
.result.ok { color: inherit; }
|
|
615
785
|
.result.ok::before { content: ""; display: inline-block; width: 8px; height: 8px; margin-right: 8px; border-radius: 50%; background: var(--lamp); }
|
|
616
786
|
.result.fail { color: var(--danger); }
|
|
787
|
+
.local-choice { display: grid; gap: 6px; padding: 14px; border-radius: 12px; background: var(--raised); }
|
|
788
|
+
.local-choice small { color: var(--muted); font-size: 12.5px; }
|
|
789
|
+
.local-choice .link-btn { margin-top: 2px; }
|
|
790
|
+
p.or { margin: 0; color: var(--muted); font-size: 13px; text-align: center; }
|
|
791
|
+
.consent { display: grid; gap: 10px; padding: 14px; border-radius: 12px; background: var(--raised); }
|
|
792
|
+
.consent p { margin: 0; }
|
|
793
|
+
.status .grow { display: grid; gap: 6px; flex: 1; }
|
|
794
|
+
progress { width: 100%; height: 6px; appearance: none; border: 0; border-radius: 3px; background: var(--line); overflow: hidden; }
|
|
795
|
+
progress::-webkit-progress-bar { background: var(--line); }
|
|
796
|
+
progress::-webkit-progress-value { background: var(--lamp); }
|
|
797
|
+
progress::-moz-progress-bar { background: var(--lamp); }
|
|
617
798
|
.link-btn { padding: 0; border: 0; background: none; color: var(--muted); font-size: 13px; text-decoration: underline; text-underline-offset: 0.2em; justify-self: start; }
|
|
618
799
|
|
|
619
800
|
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
@@ -628,8 +809,17 @@ var ERROR_KEYS = {
|
|
|
628
809
|
auth: "errorAuth",
|
|
629
810
|
"auth-or-network": "errorNetwork",
|
|
630
811
|
"rate-limit": "errorRate",
|
|
631
|
-
overloaded: "errorRate"
|
|
812
|
+
overloaded: "errorRate",
|
|
813
|
+
"local-webgpu": "localNoWebGPU",
|
|
814
|
+
"local-load": "localLoadFailed"
|
|
632
815
|
};
|
|
816
|
+
function size(megabytes, lang, t) {
|
|
817
|
+
if (megabytes >= 1000) {
|
|
818
|
+
const n = (megabytes / 1000).toLocaleString(lang, { maximumFractionDigits: 1 });
|
|
819
|
+
return `${n} ${t.gigabytes}`;
|
|
820
|
+
}
|
|
821
|
+
return `${Math.round(megabytes).toLocaleString(lang)} ${t.megabytes}`;
|
|
822
|
+
}
|
|
633
823
|
var Base = typeof HTMLElement === "undefined" ? class {
|
|
634
824
|
} : HTMLElement;
|
|
635
825
|
|
|
@@ -637,6 +827,7 @@ class MurmurChat2 extends Base {
|
|
|
637
827
|
options = {};
|
|
638
828
|
session = null;
|
|
639
829
|
t = messagesFor2("en");
|
|
830
|
+
uiLang = "en";
|
|
640
831
|
root;
|
|
641
832
|
launcher;
|
|
642
833
|
panel;
|
|
@@ -698,6 +889,7 @@ class MurmurChat2 extends Base {
|
|
|
698
889
|
build() {
|
|
699
890
|
const o = this.options;
|
|
700
891
|
const lang = pickLanguage2(o.lang, document.documentElement.lang, navigator.language);
|
|
892
|
+
this.uiLang = lang;
|
|
701
893
|
const overrides = { ...o.messages };
|
|
702
894
|
this.t = messagesFor2(lang, overrides);
|
|
703
895
|
if (o.position)
|
|
@@ -861,7 +1053,7 @@ class MurmurChat2 extends Base {
|
|
|
861
1053
|
}
|
|
862
1054
|
const passages = turn.passages ?? [];
|
|
863
1055
|
const body = el(doc, "div");
|
|
864
|
-
renderMarkdown2(body, turn.content, (n) => {
|
|
1056
|
+
renderMarkdown2(body, cleanAnswer2(turn.content), (n) => {
|
|
865
1057
|
const p = passages[n - 1];
|
|
866
1058
|
if (!p)
|
|
867
1059
|
return null;
|
|
@@ -904,6 +1096,11 @@ class MurmurChat2 extends Base {
|
|
|
904
1096
|
const question = this.input.value.trim();
|
|
905
1097
|
if (!question || this.controller || !this.session)
|
|
906
1098
|
return;
|
|
1099
|
+
await this.session.settleChoice();
|
|
1100
|
+
if (this.session.needsDownloadConsent) {
|
|
1101
|
+
await this.showConsent(question);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
907
1104
|
if (await this.session.needsSetup()) {
|
|
908
1105
|
await this.showSetup(question);
|
|
909
1106
|
return;
|
|
@@ -919,6 +1116,15 @@ class MurmurChat2 extends Base {
|
|
|
919
1116
|
this.log.append(node);
|
|
920
1117
|
this.log.scrollTop = this.log.scrollHeight;
|
|
921
1118
|
this.log.setAttribute("aria-busy", "true");
|
|
1119
|
+
this.session.onProgress = (p) => {
|
|
1120
|
+
if (!status.isConnected)
|
|
1121
|
+
return;
|
|
1122
|
+
const bar = el(doc, "progress", { max: "1", value: String(p.progress) });
|
|
1123
|
+
bar.setAttribute("aria-label", this.t.localLoadingLabel);
|
|
1124
|
+
const label = p.progress >= 1 ? this.t.localPreparing : `${this.t.localLoading} ${Math.round(p.progress * 100)} % · ${Math.round(p.loaded / 1e6).toLocaleString(this.uiLang)} / ${size(p.total / 1e6, this.uiLang, this.t)}`;
|
|
1125
|
+
status.replaceChildren(el(doc, "span", { class: "lamp busy", "aria-hidden": "true" }), el(doc, "span", { class: "grow" }));
|
|
1126
|
+
status.lastElementChild?.append(doc.createTextNode(label), bar);
|
|
1127
|
+
};
|
|
922
1128
|
const controller = new AbortController;
|
|
923
1129
|
this.controller = controller;
|
|
924
1130
|
this.setSendMode("stop");
|
|
@@ -952,6 +1158,47 @@ class MurmurChat2 extends Base {
|
|
|
952
1158
|
this.log.scrollTop = this.log.scrollHeight;
|
|
953
1159
|
}
|
|
954
1160
|
}
|
|
1161
|
+
async showConsent(question) {
|
|
1162
|
+
if (!this.session)
|
|
1163
|
+
return;
|
|
1164
|
+
const doc = this.ownerDocument;
|
|
1165
|
+
const t = this.t;
|
|
1166
|
+
const session = this.session;
|
|
1167
|
+
const spec = await session.localSpec();
|
|
1168
|
+
const card = el(doc, "div", { class: "msg bot consent" });
|
|
1169
|
+
const row = el(doc, "div", { class: "row" });
|
|
1170
|
+
const useKey = () => {
|
|
1171
|
+
card.remove();
|
|
1172
|
+
session.useLocal(false);
|
|
1173
|
+
this.showSetup(question);
|
|
1174
|
+
};
|
|
1175
|
+
if (!await session.canRunLocally()) {
|
|
1176
|
+
card.append(text(doc, "p", t.localNoWebGPU));
|
|
1177
|
+
const key = el(doc, "button", { class: "primary", type: "button" });
|
|
1178
|
+
key.textContent = t.useKey;
|
|
1179
|
+
key.addEventListener("click", useKey);
|
|
1180
|
+
row.append(key);
|
|
1181
|
+
} else {
|
|
1182
|
+
card.append(text(doc, "p", t.localConsent.replace("{name}", spec.name).replace("{size}", size(spec.sizeMB, this.uiLang, t))));
|
|
1183
|
+
const go = el(doc, "button", { class: "primary", type: "button" });
|
|
1184
|
+
go.textContent = t.localDownload;
|
|
1185
|
+
go.addEventListener("click", () => {
|
|
1186
|
+
session.agreeToDownload();
|
|
1187
|
+
card.remove();
|
|
1188
|
+
this.input.value = question;
|
|
1189
|
+
this.submit();
|
|
1190
|
+
});
|
|
1191
|
+
row.append(go);
|
|
1192
|
+
const key = el(doc, "button", { class: "link-btn", type: "button" });
|
|
1193
|
+
key.textContent = t.useKey;
|
|
1194
|
+
key.addEventListener("click", useKey);
|
|
1195
|
+
row.append(key);
|
|
1196
|
+
}
|
|
1197
|
+
card.append(row);
|
|
1198
|
+
this.log.append(card);
|
|
1199
|
+
this.log.scrollTop = this.log.scrollHeight;
|
|
1200
|
+
row.querySelector("button")?.focus();
|
|
1201
|
+
}
|
|
955
1202
|
async showSetup(pending) {
|
|
956
1203
|
if (!this.session)
|
|
957
1204
|
return;
|
|
@@ -968,6 +1215,44 @@ class MurmurChat2 extends Base {
|
|
|
968
1215
|
const heading = text(doc, "h3", t.setupTitle);
|
|
969
1216
|
heading.style.cssText = "margin:0;font-size:17px;font-weight:650";
|
|
970
1217
|
const intro = text(doc, "p", t.setupIntro, "intro");
|
|
1218
|
+
const session = this.session;
|
|
1219
|
+
const spec = await session.localSpec();
|
|
1220
|
+
const inBrowser = el(doc, "div", { class: "local-choice" });
|
|
1221
|
+
const localBtn = el(doc, "button", { class: "primary", type: "button" });
|
|
1222
|
+
localBtn.textContent = t.localOption;
|
|
1223
|
+
const localHint = text(doc, "small", t.localOptionHint.replace("{name}", spec.name).replace("{size}", size(spec.sizeMB, this.uiLang, t)));
|
|
1224
|
+
inBrowser.append(localBtn, localHint);
|
|
1225
|
+
localBtn.addEventListener("click", () => {
|
|
1226
|
+
session.useLocal(true);
|
|
1227
|
+
this.hideSetup();
|
|
1228
|
+
if (pending) {
|
|
1229
|
+
this.input.value = pending;
|
|
1230
|
+
this.submit();
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
const switchToKey = el(doc, "button", { class: "link-btn", type: "button" });
|
|
1234
|
+
switchToKey.textContent = t.useKey;
|
|
1235
|
+
switchToKey.addEventListener("click", () => {
|
|
1236
|
+
session.useLocal(false);
|
|
1237
|
+
localBtn.textContent = t.localOption;
|
|
1238
|
+
localBtn.disabled = false;
|
|
1239
|
+
switchToKey.remove();
|
|
1240
|
+
});
|
|
1241
|
+
if (session.mode === "local") {
|
|
1242
|
+
localBtn.textContent = t.localActive;
|
|
1243
|
+
localBtn.disabled = true;
|
|
1244
|
+
inBrowser.append(switchToKey);
|
|
1245
|
+
}
|
|
1246
|
+
session.canRunLocally().then((ok) => {
|
|
1247
|
+
if (ok)
|
|
1248
|
+
return;
|
|
1249
|
+
localBtn.textContent = t.localOption;
|
|
1250
|
+
localBtn.disabled = true;
|
|
1251
|
+
localHint.textContent = t.localNoWebGPU;
|
|
1252
|
+
switchToKey.remove();
|
|
1253
|
+
});
|
|
1254
|
+
const or = text(doc, "p", t.orKey, "or");
|
|
1255
|
+
inBrowser.hidden = or.hidden = !session.localAllowed;
|
|
971
1256
|
const local = checkbox(doc, t.local);
|
|
972
1257
|
const providerField = field(doc, t.provider, el(doc, "select"));
|
|
973
1258
|
const providerSelect = providerField.control;
|
|
@@ -1014,6 +1299,7 @@ class MurmurChat2 extends Base {
|
|
|
1014
1299
|
const saved = await store.save();
|
|
1015
1300
|
if (!saved.ok)
|
|
1016
1301
|
return;
|
|
1302
|
+
session.useLocal(false);
|
|
1017
1303
|
this.hideSetup();
|
|
1018
1304
|
if (pending) {
|
|
1019
1305
|
this.input.value = pending;
|
|
@@ -1023,7 +1309,7 @@ class MurmurChat2 extends Base {
|
|
|
1023
1309
|
forget.addEventListener("click", () => void store.clear());
|
|
1024
1310
|
const testRow = el(doc, "div", { class: "row" });
|
|
1025
1311
|
testRow.append(testBtn, result);
|
|
1026
|
-
this.setupView.replaceChildren(back, heading, intro, local.label, providerField.label, modelField.label, presets, endpointField.label, customModelField.label, keyField.label, remember.label, testRow, saveBtn, forget);
|
|
1312
|
+
this.setupView.replaceChildren(back, heading, intro, inBrowser, or, local.label, providerField.label, modelField.label, presets, endpointField.label, customModelField.label, keyField.label, remember.label, testRow, saveBtn, forget);
|
|
1027
1313
|
const draw = (s) => {
|
|
1028
1314
|
const custom = s.draft.custom;
|
|
1029
1315
|
local.input.checked = custom;
|
|
@@ -1055,7 +1341,8 @@ class MurmurChat2 extends Base {
|
|
|
1055
1341
|
};
|
|
1056
1342
|
this.unsubscribe?.();
|
|
1057
1343
|
this.unsubscribe = store.subscribe(draw);
|
|
1058
|
-
|
|
1344
|
+
this.setupView.scrollTop = 0;
|
|
1345
|
+
back.focus();
|
|
1059
1346
|
}
|
|
1060
1347
|
hideSetup() {
|
|
1061
1348
|
this.unsubscribe?.();
|