murmur-rag 0.1.2 → 1.1.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 +23 -19
- package/dist/cdn/anthropic-dc7h9prc.js +2 -0
- package/dist/cdn/chunk-hpx6rpea.js +1 -0
- package/dist/cdn/{embed-hn0r8fmg.js → embed-6gef7ym5.js} +2 -2
- package/dist/cdn/{embed-59r4jck6.js → embed-acjpjh6t.js} +3 -3
- package/dist/cdn/embed-fd96s8qk.js +44 -0
- package/dist/cdn/embed-jdraznwg.js +83 -0
- package/dist/cdn/{embed-q6y6vgv1.js → embed-krrjjyf5.js} +4 -4
- package/dist/cdn/{embed-we24qtes.js → embed-ssqsb38w.js} +2 -2
- package/dist/cdn/embed-z32y952f.js +9 -0
- package/dist/cdn/google-yywkqdph.js +14 -0
- package/dist/cdn/index-bq0x71h1.js +1 -0
- package/dist/cdn/index-gyd02jvt.js +9 -0
- package/dist/cdn/index-jt6n0d28.js +1 -0
- package/dist/cdn/index-nesgnf2n.js +1 -0
- package/dist/cdn/index-wgzgvzaf.js +1 -0
- package/dist/cdn/mistral-0mg4kk7s.js +3 -0
- package/dist/cdn/murmur-j1ky5vmr.js +5 -0
- package/dist/cdn/murmur.js +24 -5
- package/dist/cdn/openai-compatible-zhaj1r43.js +11 -0
- package/dist/cdn/openai-g1685z9v.js +18 -0
- package/dist/cdn/retriever-dqxzjaqk.js +1 -0
- package/dist/cdn/transformers.web-3kmbvcbt.js +46 -0
- package/dist/cli.js +10 -5
- package/dist/lib/chunks/{index-fs6gmhz9.js → index-8h9c8ekq.js} +4 -2
- package/dist/lib/chunks/{index-0x7gav29.js → index-92w507gk.js} +304 -27
- package/dist/lib/chunks/{index-6re28wc1.js → index-b77xqap3.js} +13 -1
- package/dist/lib/chunks/{index-z8pn9sdj.js → index-p86sy3y4.js} +17 -4
- package/dist/lib/chunks/{murmur-wq1d15n7.js → murmur-t36hd4vt.js} +2 -2
- package/dist/lib/chunks/{retriever-1rw4z2n6.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 +10 -5
- 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 +20 -1
- package/dist/types/widget/styles.d.ts +1 -1
- package/package.json +18 -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-kk9hgxdc.js +0 -1
- package/dist/cdn/index-zrmby2xc.js +0 -9
- package/dist/cdn/mistral-16bekg8b.js +0 -3
- package/dist/cdn/murmur-5vkc01ns.js +0 -5
- package/dist/cdn/openai-compatible-za7hc1fv.js +0 -11
- package/dist/cdn/openai-dhy4fp4c.js +0 -18
- package/dist/cdn/retriever-k9mmdcz5.js +0 -1
package/dist/cli.js
CHANGED
|
@@ -99,7 +99,7 @@ function extractSections(doc, { root = DEFAULT_ROOT, exclude = DEFAULT_EXCLUDE }
|
|
|
99
99
|
return { title, sections: [] };
|
|
100
100
|
const sections = [];
|
|
101
101
|
let cur = { heading: "", anchor: "", text: "" };
|
|
102
|
-
const
|
|
102
|
+
const slugs = new Map;
|
|
103
103
|
const walk = (node) => {
|
|
104
104
|
if (node.nodeType === 3) {
|
|
105
105
|
cur.text += (node.textContent ?? "").replace(/\s+/g, " ");
|
|
@@ -114,16 +114,21 @@ function extractSections(doc, { root = DEFAULT_ROOT, exclude = DEFAULT_EXCLUDE }
|
|
|
114
114
|
if (/^H[1-3]$/.test(tag)) {
|
|
115
115
|
sections.push(cur);
|
|
116
116
|
const heading = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
117
|
-
let anchor = el.id || el.closest("[id]")?.id ||
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
let anchor = el.id || el.closest("[id]")?.id || "";
|
|
118
|
+
if (!anchor) {
|
|
119
|
+
const base = slugify(heading);
|
|
120
|
+
const n = slugs.get(base) ?? 0;
|
|
121
|
+
slugs.set(base, n + 1);
|
|
122
|
+
anchor = n ? `${base}-${n}` : base;
|
|
123
|
+
}
|
|
121
124
|
cur = { heading, anchor, text: "" };
|
|
122
125
|
return;
|
|
123
126
|
}
|
|
124
127
|
if (tag === "BR")
|
|
125
128
|
cur.text += `
|
|
126
129
|
`;
|
|
130
|
+
if ((tag === "TD" || tag === "TH") && !/(^|\n)\s*$/.test(cur.text))
|
|
131
|
+
cur.text += " | ";
|
|
127
132
|
const block = BLOCK.test(tag);
|
|
128
133
|
if (block)
|
|
129
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);
|
|
@@ -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,8 +394,29 @@ class ChatSession {
|
|
|
296
394
|
get mode() {
|
|
297
395
|
if (this.options.model)
|
|
298
396
|
return "custom";
|
|
397
|
+
if (this.options.local || this.visitorChoseLocal)
|
|
398
|
+
return "local";
|
|
299
399
|
return this.options.llm?.endpoint ? "proxy" : "visitor";
|
|
300
400
|
}
|
|
401
|
+
async localSpec() {
|
|
402
|
+
const { local } = this.options;
|
|
403
|
+
if (local && typeof local === "object")
|
|
404
|
+
return local;
|
|
405
|
+
return (await (__preload("nb4hdy2t"), import("../local/index.js"))).DEFAULT_LOCAL_MODEL;
|
|
406
|
+
}
|
|
407
|
+
get visitorChoseLocal() {
|
|
408
|
+
return readLocal(`${this.key}-local`) === "1";
|
|
409
|
+
}
|
|
410
|
+
useLocal(on) {
|
|
411
|
+
writeLocal(`${this.key}-local`, on ? "1" : null);
|
|
412
|
+
this.engine = null;
|
|
413
|
+
}
|
|
414
|
+
get needsDownloadConsent() {
|
|
415
|
+
return this.mode === "local" && readLocal(`${this.key}-local-consent`) !== "1";
|
|
416
|
+
}
|
|
417
|
+
agreeToDownload() {
|
|
418
|
+
writeLocal(`${this.key}-local-consent`, "1");
|
|
419
|
+
}
|
|
301
420
|
persist() {
|
|
302
421
|
try {
|
|
303
422
|
sessionStorage.setItem(this.historyKey, JSON.stringify(this.turns.slice(-30)));
|
|
@@ -327,6 +446,9 @@ class ChatSession {
|
|
|
327
446
|
async needsSetup() {
|
|
328
447
|
return this.mode === "visitor" && !await this.settings();
|
|
329
448
|
}
|
|
449
|
+
async canRunLocally() {
|
|
450
|
+
return (await (__preload("nb4hdy2t"), import("../local/index.js"))).hasWebGPU();
|
|
451
|
+
}
|
|
330
452
|
async loadIndex() {
|
|
331
453
|
const { index } = this.options;
|
|
332
454
|
if (index && typeof index !== "string")
|
|
@@ -341,23 +463,34 @@ class ChatSession {
|
|
|
341
463
|
throw new Error("murmur: not a murmur index");
|
|
342
464
|
return data;
|
|
343
465
|
}
|
|
344
|
-
const { indexDocument } = await (__preload("
|
|
466
|
+
const { indexDocument } = await (__preload("dj4321kh"), import("../indexer/index.js"));
|
|
345
467
|
return indexDocument(document, location.pathname + location.search);
|
|
346
468
|
}
|
|
347
469
|
createEngine() {
|
|
348
470
|
return (async () => {
|
|
349
471
|
const [{ createMurmur }, retriever] = await Promise.all([
|
|
350
|
-
(__preload("
|
|
351
|
-
this.options.retriever ?? (__preload("
|
|
472
|
+
(__preload("t36hd4vt"), import("./murmur-t36hd4vt.js")),
|
|
473
|
+
this.options.retriever ?? (__preload("wxshtnm0"), import("./retriever-wxshtnm0.js")).then(async (r) => r.bm25Retriever(await this.loadIndex()))
|
|
352
474
|
]);
|
|
353
|
-
const
|
|
475
|
+
const local = this.mode === "local";
|
|
476
|
+
const model = this.options.model ?? (local ? async () => {
|
|
477
|
+
const { localModel } = await (__preload("nb4hdy2t"), import("../local/index.js"));
|
|
478
|
+
return localModel({
|
|
479
|
+
spec: await this.localSpec(),
|
|
480
|
+
onProgress: (p) => this.onProgress?.(p)
|
|
481
|
+
});
|
|
482
|
+
} : async () => {
|
|
354
483
|
const settings = await this.settings();
|
|
355
484
|
if (!settings)
|
|
356
485
|
throw new SetupNeeded2;
|
|
357
486
|
const { loadModel } = await (__preload("vb9smwta"), import("../llm/index.js"));
|
|
358
487
|
return loadModel(settings);
|
|
359
488
|
});
|
|
360
|
-
const opts = {
|
|
489
|
+
const opts = {
|
|
490
|
+
retriever,
|
|
491
|
+
model,
|
|
492
|
+
...local ? LOCAL_SEARCH : {}
|
|
493
|
+
};
|
|
361
494
|
if (this.options.instructions)
|
|
362
495
|
opts.instructions = this.options.instructions;
|
|
363
496
|
if (this.options.lang)
|
|
@@ -388,7 +521,9 @@ class ChatSession {
|
|
|
388
521
|
turn.content += part;
|
|
389
522
|
onText(turn.content, turn);
|
|
390
523
|
}
|
|
391
|
-
|
|
524
|
+
const done = await answer.done;
|
|
525
|
+
turn.content = done.text;
|
|
526
|
+
turn.cited = done.cited;
|
|
392
527
|
} catch (error) {
|
|
393
528
|
if (signal.aborted) {
|
|
394
529
|
turn.cited = [];
|
|
@@ -404,9 +539,12 @@ class ChatSession {
|
|
|
404
539
|
return turn;
|
|
405
540
|
}
|
|
406
541
|
async describe(error) {
|
|
542
|
+
if (error instanceof Error && error.name === "LocalModelError") {
|
|
543
|
+
return `local-${error.code ?? "load"}|${error.message}`;
|
|
544
|
+
}
|
|
407
545
|
try {
|
|
408
546
|
const { describeError } = await (__preload("vb9smwta"), import("../llm/index.js"));
|
|
409
|
-
const settings = this.mode === "custom" ? null : await this.settings();
|
|
547
|
+
const settings = this.mode === "custom" || this.mode === "local" ? null : await this.settings();
|
|
410
548
|
const info = describeError(error, settings?.apiKey);
|
|
411
549
|
return `${info.code}|${info.message}`;
|
|
412
550
|
} catch {
|
|
@@ -414,6 +552,21 @@ class ChatSession {
|
|
|
414
552
|
}
|
|
415
553
|
}
|
|
416
554
|
}
|
|
555
|
+
function readLocal(key) {
|
|
556
|
+
try {
|
|
557
|
+
return localStorage.getItem(key);
|
|
558
|
+
} catch {
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function writeLocal(key, value) {
|
|
563
|
+
try {
|
|
564
|
+
if (value === null)
|
|
565
|
+
localStorage.removeItem(key);
|
|
566
|
+
else
|
|
567
|
+
localStorage.setItem(key, value);
|
|
568
|
+
} catch {}
|
|
569
|
+
}
|
|
417
570
|
|
|
418
571
|
class SetupNeeded2 extends Error {
|
|
419
572
|
constructor() {
|
|
@@ -528,6 +681,13 @@ header .icon-btn:focus-visible { outline-color: var(--accent-ink); }
|
|
|
528
681
|
.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
682
|
.msg pre { margin: 0 0 0.6em; padding: 10px 12px; background: var(--raised); border-radius: 10px; overflow-x: auto; }
|
|
530
683
|
.msg pre code { background: none; padding: 0; }
|
|
684
|
+
.msg .table { margin: 0 0 0.6em; overflow-x: auto; border-radius: 10px; box-shadow: inset 0 0 0 1px var(--line); }
|
|
685
|
+
.msg table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
686
|
+
.msg th, .msg td { padding: 6px 10px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--line); }
|
|
687
|
+
.msg tr:last-child td { border-bottom: 0; }
|
|
688
|
+
.msg th { font-weight: 650; background: var(--raised); }
|
|
689
|
+
.msg blockquote { margin: 0 0 0.6em; padding: 2px 0 2px 12px; color: var(--muted); box-shadow: inset 1px 0 0 var(--line); }
|
|
690
|
+
.msg hr { margin: 0.8em 0; border: 0; border-top: 1px solid var(--line); }
|
|
531
691
|
.user {
|
|
532
692
|
align-self: flex-end; max-width: 85%;
|
|
533
693
|
padding: 9px 14px; border-radius: 16px 16px 4px 16px;
|
|
@@ -614,6 +774,17 @@ footer a:hover { text-decoration: underline; }
|
|
|
614
774
|
.result.ok { color: inherit; }
|
|
615
775
|
.result.ok::before { content: ""; display: inline-block; width: 8px; height: 8px; margin-right: 8px; border-radius: 50%; background: var(--lamp); }
|
|
616
776
|
.result.fail { color: var(--danger); }
|
|
777
|
+
.local-choice { display: grid; gap: 6px; padding: 14px; border-radius: 12px; background: var(--raised); }
|
|
778
|
+
.local-choice small { color: var(--muted); font-size: 12.5px; }
|
|
779
|
+
.local-choice .link-btn { margin-top: 2px; }
|
|
780
|
+
p.or { margin: 0; color: var(--muted); font-size: 13px; text-align: center; }
|
|
781
|
+
.consent { display: grid; gap: 10px; padding: 14px; border-radius: 12px; background: var(--raised); }
|
|
782
|
+
.consent p { margin: 0; }
|
|
783
|
+
.status .grow { display: grid; gap: 6px; flex: 1; }
|
|
784
|
+
progress { width: 100%; height: 6px; appearance: none; border: 0; border-radius: 3px; background: var(--line); overflow: hidden; }
|
|
785
|
+
progress::-webkit-progress-bar { background: var(--line); }
|
|
786
|
+
progress::-webkit-progress-value { background: var(--lamp); }
|
|
787
|
+
progress::-moz-progress-bar { background: var(--lamp); }
|
|
617
788
|
.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
789
|
|
|
619
790
|
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
@@ -628,8 +799,17 @@ var ERROR_KEYS = {
|
|
|
628
799
|
auth: "errorAuth",
|
|
629
800
|
"auth-or-network": "errorNetwork",
|
|
630
801
|
"rate-limit": "errorRate",
|
|
631
|
-
overloaded: "errorRate"
|
|
802
|
+
overloaded: "errorRate",
|
|
803
|
+
"local-webgpu": "localNoWebGPU",
|
|
804
|
+
"local-load": "localLoadFailed"
|
|
632
805
|
};
|
|
806
|
+
function size(megabytes, lang, t) {
|
|
807
|
+
if (megabytes >= 1000) {
|
|
808
|
+
const n = (megabytes / 1000).toLocaleString(lang, { maximumFractionDigits: 1 });
|
|
809
|
+
return `${n} ${t.gigabytes}`;
|
|
810
|
+
}
|
|
811
|
+
return `${Math.round(megabytes).toLocaleString(lang)} ${t.megabytes}`;
|
|
812
|
+
}
|
|
633
813
|
var Base = typeof HTMLElement === "undefined" ? class {
|
|
634
814
|
} : HTMLElement;
|
|
635
815
|
|
|
@@ -637,6 +817,7 @@ class MurmurChat2 extends Base {
|
|
|
637
817
|
options = {};
|
|
638
818
|
session = null;
|
|
639
819
|
t = messagesFor2("en");
|
|
820
|
+
uiLang = "en";
|
|
640
821
|
root;
|
|
641
822
|
launcher;
|
|
642
823
|
panel;
|
|
@@ -698,6 +879,7 @@ class MurmurChat2 extends Base {
|
|
|
698
879
|
build() {
|
|
699
880
|
const o = this.options;
|
|
700
881
|
const lang = pickLanguage2(o.lang, document.documentElement.lang, navigator.language);
|
|
882
|
+
this.uiLang = lang;
|
|
701
883
|
const overrides = { ...o.messages };
|
|
702
884
|
this.t = messagesFor2(lang, overrides);
|
|
703
885
|
if (o.position)
|
|
@@ -736,7 +918,7 @@ class MurmurChat2 extends Base {
|
|
|
736
918
|
this.input.focus();
|
|
737
919
|
});
|
|
738
920
|
this.settingsBtn = iconButton(doc, "settings", this.t.settings);
|
|
739
|
-
this.settingsBtn.hidden = !!o.model || !!o.llm?.endpoint;
|
|
921
|
+
this.settingsBtn.hidden = !!o.model || !!o.llm?.endpoint || !!o.local;
|
|
740
922
|
this.settingsBtn.addEventListener("click", () => void this.showSetup());
|
|
741
923
|
const closeBtn = iconButton(doc, "close", this.t.close);
|
|
742
924
|
closeBtn.hidden = this.hasAttribute("inline");
|
|
@@ -861,7 +1043,7 @@ class MurmurChat2 extends Base {
|
|
|
861
1043
|
}
|
|
862
1044
|
const passages = turn.passages ?? [];
|
|
863
1045
|
const body = el(doc, "div");
|
|
864
|
-
renderMarkdown2(body, turn.content, (n) => {
|
|
1046
|
+
renderMarkdown2(body, cleanAnswer2(turn.content), (n) => {
|
|
865
1047
|
const p = passages[n - 1];
|
|
866
1048
|
if (!p)
|
|
867
1049
|
return null;
|
|
@@ -904,6 +1086,10 @@ class MurmurChat2 extends Base {
|
|
|
904
1086
|
const question = this.input.value.trim();
|
|
905
1087
|
if (!question || this.controller || !this.session)
|
|
906
1088
|
return;
|
|
1089
|
+
if (this.session.needsDownloadConsent) {
|
|
1090
|
+
await this.showConsent(question);
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
907
1093
|
if (await this.session.needsSetup()) {
|
|
908
1094
|
await this.showSetup(question);
|
|
909
1095
|
return;
|
|
@@ -919,6 +1105,15 @@ class MurmurChat2 extends Base {
|
|
|
919
1105
|
this.log.append(node);
|
|
920
1106
|
this.log.scrollTop = this.log.scrollHeight;
|
|
921
1107
|
this.log.setAttribute("aria-busy", "true");
|
|
1108
|
+
this.session.onProgress = (p) => {
|
|
1109
|
+
if (!status.isConnected)
|
|
1110
|
+
return;
|
|
1111
|
+
const bar = el(doc, "progress", { max: "1", value: String(p.progress) });
|
|
1112
|
+
bar.setAttribute("aria-label", this.t.localLoadingLabel);
|
|
1113
|
+
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)}`;
|
|
1114
|
+
status.replaceChildren(el(doc, "span", { class: "lamp busy", "aria-hidden": "true" }), el(doc, "span", { class: "grow" }));
|
|
1115
|
+
status.lastElementChild?.append(doc.createTextNode(label), bar);
|
|
1116
|
+
};
|
|
922
1117
|
const controller = new AbortController;
|
|
923
1118
|
this.controller = controller;
|
|
924
1119
|
this.setSendMode("stop");
|
|
@@ -952,6 +1147,52 @@ class MurmurChat2 extends Base {
|
|
|
952
1147
|
this.log.scrollTop = this.log.scrollHeight;
|
|
953
1148
|
}
|
|
954
1149
|
}
|
|
1150
|
+
async showConsent(question) {
|
|
1151
|
+
if (!this.session)
|
|
1152
|
+
return;
|
|
1153
|
+
const doc = this.ownerDocument;
|
|
1154
|
+
const t = this.t;
|
|
1155
|
+
const session = this.session;
|
|
1156
|
+
const spec = await session.localSpec();
|
|
1157
|
+
const card = el(doc, "div", { class: "msg bot consent" });
|
|
1158
|
+
const row = el(doc, "div", { class: "row" });
|
|
1159
|
+
const ownerLocal = !!this.options.local;
|
|
1160
|
+
const useKey = () => {
|
|
1161
|
+
card.remove();
|
|
1162
|
+
session.useLocal(false);
|
|
1163
|
+
this.showSetup(question);
|
|
1164
|
+
};
|
|
1165
|
+
if (!await session.canRunLocally()) {
|
|
1166
|
+
card.append(text(doc, "p", t.localNoWebGPU));
|
|
1167
|
+
if (!ownerLocal) {
|
|
1168
|
+
const key = el(doc, "button", { class: "text-btn", type: "button" });
|
|
1169
|
+
key.textContent = t.useKey;
|
|
1170
|
+
key.addEventListener("click", useKey);
|
|
1171
|
+
row.append(key);
|
|
1172
|
+
}
|
|
1173
|
+
} else {
|
|
1174
|
+
card.append(text(doc, "p", t.localConsent.replace("{name}", spec.name).replace("{size}", size(spec.sizeMB, this.uiLang, t))));
|
|
1175
|
+
const go = el(doc, "button", { class: "primary", type: "button" });
|
|
1176
|
+
go.textContent = t.localDownload;
|
|
1177
|
+
go.addEventListener("click", () => {
|
|
1178
|
+
session.agreeToDownload();
|
|
1179
|
+
card.remove();
|
|
1180
|
+
this.input.value = question;
|
|
1181
|
+
this.submit();
|
|
1182
|
+
});
|
|
1183
|
+
row.append(go);
|
|
1184
|
+
if (!ownerLocal) {
|
|
1185
|
+
const key = el(doc, "button", { class: "link-btn", type: "button" });
|
|
1186
|
+
key.textContent = t.useKey;
|
|
1187
|
+
key.addEventListener("click", useKey);
|
|
1188
|
+
row.append(key);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
card.append(row);
|
|
1192
|
+
this.log.append(card);
|
|
1193
|
+
this.log.scrollTop = this.log.scrollHeight;
|
|
1194
|
+
row.querySelector("button")?.focus();
|
|
1195
|
+
}
|
|
955
1196
|
async showSetup(pending) {
|
|
956
1197
|
if (!this.session)
|
|
957
1198
|
return;
|
|
@@ -968,6 +1209,40 @@ class MurmurChat2 extends Base {
|
|
|
968
1209
|
const heading = text(doc, "h3", t.setupTitle);
|
|
969
1210
|
heading.style.cssText = "margin:0;font-size:17px;font-weight:650";
|
|
970
1211
|
const intro = text(doc, "p", t.setupIntro, "intro");
|
|
1212
|
+
const session = this.session;
|
|
1213
|
+
const spec = await session.localSpec();
|
|
1214
|
+
const inBrowser = el(doc, "div", { class: "local-choice" });
|
|
1215
|
+
const localBtn = el(doc, "button", { class: "primary", type: "button" });
|
|
1216
|
+
localBtn.textContent = t.localOption;
|
|
1217
|
+
const localHint = text(doc, "small", t.localOptionHint.replace("{name}", spec.name).replace("{size}", size(spec.sizeMB, this.uiLang, t)));
|
|
1218
|
+
inBrowser.append(localBtn, localHint);
|
|
1219
|
+
localBtn.addEventListener("click", () => {
|
|
1220
|
+
session.useLocal(true);
|
|
1221
|
+
this.hideSetup();
|
|
1222
|
+
if (pending) {
|
|
1223
|
+
this.input.value = pending;
|
|
1224
|
+
this.submit();
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
session.canRunLocally().then((ok) => {
|
|
1228
|
+
localBtn.disabled = !ok;
|
|
1229
|
+
if (!ok)
|
|
1230
|
+
localHint.textContent = t.localNoWebGPU;
|
|
1231
|
+
});
|
|
1232
|
+
if (session.mode === "local") {
|
|
1233
|
+
localBtn.textContent = t.localActive;
|
|
1234
|
+
localBtn.disabled = true;
|
|
1235
|
+
const back = el(doc, "button", { class: "link-btn", type: "button" });
|
|
1236
|
+
back.textContent = t.useKey;
|
|
1237
|
+
back.addEventListener("click", () => {
|
|
1238
|
+
session.useLocal(false);
|
|
1239
|
+
localBtn.textContent = t.localOption;
|
|
1240
|
+
localBtn.disabled = false;
|
|
1241
|
+
back.remove();
|
|
1242
|
+
});
|
|
1243
|
+
inBrowser.append(back);
|
|
1244
|
+
}
|
|
1245
|
+
const or = text(doc, "p", t.orKey, "or");
|
|
971
1246
|
const local = checkbox(doc, t.local);
|
|
972
1247
|
const providerField = field(doc, t.provider, el(doc, "select"));
|
|
973
1248
|
const providerSelect = providerField.control;
|
|
@@ -1014,6 +1289,7 @@ class MurmurChat2 extends Base {
|
|
|
1014
1289
|
const saved = await store.save();
|
|
1015
1290
|
if (!saved.ok)
|
|
1016
1291
|
return;
|
|
1292
|
+
session.useLocal(false);
|
|
1017
1293
|
this.hideSetup();
|
|
1018
1294
|
if (pending) {
|
|
1019
1295
|
this.input.value = pending;
|
|
@@ -1023,7 +1299,7 @@ class MurmurChat2 extends Base {
|
|
|
1023
1299
|
forget.addEventListener("click", () => void store.clear());
|
|
1024
1300
|
const testRow = el(doc, "div", { class: "row" });
|
|
1025
1301
|
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);
|
|
1302
|
+
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
1303
|
const draw = (s) => {
|
|
1028
1304
|
const custom = s.draft.custom;
|
|
1029
1305
|
local.input.checked = custom;
|
|
@@ -1055,7 +1331,8 @@ class MurmurChat2 extends Base {
|
|
|
1055
1331
|
};
|
|
1056
1332
|
this.unsubscribe?.();
|
|
1057
1333
|
this.unsubscribe = store.subscribe(draw);
|
|
1058
|
-
|
|
1334
|
+
this.setupView.scrollTop = 0;
|
|
1335
|
+
back.focus();
|
|
1059
1336
|
}
|
|
1060
1337
|
hideSetup() {
|
|
1061
1338
|
this.unsubscribe?.();
|
|
@@ -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 };
|