murmur-rag 1.2.0 → 1.3.1
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 -16
- package/dist/cdn/{anthropic-ynz7717b.js → anthropic-tw416spz.js} +2 -2
- package/dist/cdn/chunk-xyp6nm65.js +1 -0
- package/dist/cdn/{embed-xtkxyrpe.js → embed-3am76qfd.js} +6 -6
- package/dist/cdn/embed-50pfy67v.js +83 -0
- package/dist/cdn/{embed-n5nzhrf2.js → embed-cx1r66ch.js} +6 -6
- package/dist/cdn/{embed-7pk91zq8.js → embed-g0p6yc29.js} +2 -2
- package/dist/cdn/{embed-krrjjyf5.js → embed-q81wdr1z.js} +4 -4
- package/dist/cdn/embed-rjpq8psf.js +10 -0
- package/dist/cdn/{embed-0m8xctqy.js → embed-yz75savs.js} +3 -3
- package/dist/cdn/{google-k5kwa54v.js → google-556tp13p.js} +8 -8
- package/dist/cdn/index-8r44b2wt.js +1 -0
- package/dist/cdn/index-a5j4aat5.js +1 -0
- package/dist/cdn/index-ft8r539a.js +1 -0
- package/dist/cdn/index-pjxy71n5.js +1 -0
- package/dist/cdn/{index-gyd02jvt.js → index-qpy1k923.js} +2 -2
- package/dist/cdn/{mistral-p2qcnn42.js → mistral-25w1nsx5.js} +2 -2
- package/dist/cdn/{murmur-d6t6cjc7.js → murmur-1a55et82.js} +3 -3
- package/dist/cdn/murmur.js +7 -7
- package/dist/cdn/{openai-d3q1d6cz.js → openai-bjrbem49.js} +12 -12
- package/dist/cdn/{openai-compatible-psnkrpxy.js → openai-compatible-sg25g0vj.js} +6 -6
- package/dist/cdn/retriever-06ybegpd.js +1 -0
- package/dist/cdn/{transformers.web-xn16v7pz.js → transformers.web-49m0ht4a.js} +7 -7
- package/dist/lib/chunks/{index-y3v46yjp.js → index-4ftk20yq.js} +143 -39
- package/dist/lib/embed.js +8 -2
- package/dist/lib/llm/index.js +23 -0
- package/dist/lib/local/index.js +38 -2
- package/dist/lib/widget/index.js +2 -2
- package/dist/types/llm/index.d.ts +8 -0
- package/dist/types/local/index.d.ts +12 -0
- package/dist/types/widget/element.d.ts +4 -0
- package/dist/types/widget/i18n.d.ts +1 -0
- package/dist/types/widget/session.d.ts +43 -7
- package/dist/types/widget/styles.d.ts +1 -1
- package/package.json +4 -2
- package/dist/cdn/chunk-hpx6rpea.js +0 -1
- package/dist/cdn/embed-dbkjhg16.js +0 -83
- package/dist/cdn/embed-rp9gb1qq.js +0 -10
- package/dist/cdn/index-db4ne93m.js +0 -1
- package/dist/cdn/index-qc8kgsdt.js +0 -1
- package/dist/cdn/index-t0qn2m4r.js +0 -1
- package/dist/cdn/index-y0faj89r.js +0 -1
- package/dist/cdn/retriever-6qvn2maq.js +0 -1
|
@@ -87,6 +87,7 @@ var en = {
|
|
|
87
87
|
localPreparing: "Preparing the model on your GPU…",
|
|
88
88
|
localNoWebGPU: "This browser cannot run the model locally (no WebGPU). Try a recent Chrome, Edge or Safari, or use an API key.",
|
|
89
89
|
localLoadFailed: "The model could not be loaded in this browser.",
|
|
90
|
+
localNoWebGPUOnly: "This site answers with an AI model that runs in your browser, and this browser cannot run it (no WebGPU). Try a recent Chrome, Edge or Safari.",
|
|
90
91
|
gigabytes: "GB",
|
|
91
92
|
megabytes: "MB"
|
|
92
93
|
};
|
|
@@ -143,6 +144,7 @@ var fr = {
|
|
|
143
144
|
localPreparing: "Préparation du modèle sur votre GPU…",
|
|
144
145
|
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
146
|
localLoadFailed: "Le modèle n'a pas pu être chargé dans ce navigateur.",
|
|
147
|
+
localNoWebGPUOnly: "Ce site répond avec un modèle d'IA qui tourne dans votre navigateur, et ce navigateur ne peut pas le faire tourner (pas de WebGPU). Essayez un Chrome, Edge ou Safari récent.",
|
|
146
148
|
gigabytes: "Go",
|
|
147
149
|
megabytes: "Mo"
|
|
148
150
|
};
|
|
@@ -378,7 +380,8 @@ class ChatSession {
|
|
|
378
380
|
storePromise = null;
|
|
379
381
|
historyKey;
|
|
380
382
|
key;
|
|
381
|
-
|
|
383
|
+
local = { status: "idle" };
|
|
384
|
+
localListeners = new Set;
|
|
382
385
|
constructor(options) {
|
|
383
386
|
this.options = options;
|
|
384
387
|
this.key = options.storageKey ?? "murmur";
|
|
@@ -391,22 +394,77 @@ class ChatSession {
|
|
|
391
394
|
this.turns = [];
|
|
392
395
|
}
|
|
393
396
|
}
|
|
397
|
+
get configured() {
|
|
398
|
+
return this.options.mode ?? "auto";
|
|
399
|
+
}
|
|
394
400
|
get mode() {
|
|
401
|
+
const m = this.configured;
|
|
395
402
|
if (this.options.model)
|
|
396
403
|
return "custom";
|
|
397
|
-
if (this.options.llm?.endpoint)
|
|
404
|
+
if (m === "proxy" || m === "auto" && this.options.llm?.endpoint)
|
|
398
405
|
return "proxy";
|
|
399
|
-
if (
|
|
406
|
+
if ((m === "auto" || m === "key") && this.options.llm?.apiKey)
|
|
407
|
+
return "owner";
|
|
408
|
+
if (m === "local")
|
|
409
|
+
return "local";
|
|
410
|
+
if (m === "key" || !this.localAllowed)
|
|
400
411
|
return "visitor";
|
|
401
412
|
return readLocal(`${this.key}-local`) === "0" ? "visitor" : "local";
|
|
402
413
|
}
|
|
403
414
|
get localAllowed() {
|
|
404
|
-
|
|
415
|
+
const m = this.configured;
|
|
416
|
+
if (this.options.model || m === "proxy" || m === "key")
|
|
417
|
+
return false;
|
|
418
|
+
if (m === "local")
|
|
419
|
+
return true;
|
|
420
|
+
return !this.options.llm?.endpoint && !this.options.llm?.apiKey && this.options.local !== false;
|
|
421
|
+
}
|
|
422
|
+
get keyAllowed() {
|
|
423
|
+
const m = this.configured;
|
|
424
|
+
if (this.options.model || m === "proxy" || m === "local")
|
|
425
|
+
return false;
|
|
426
|
+
return !this.options.llm?.endpoint && !this.options.llm?.apiKey;
|
|
427
|
+
}
|
|
428
|
+
get localState() {
|
|
429
|
+
return this.local;
|
|
430
|
+
}
|
|
431
|
+
onLocalState(listener) {
|
|
432
|
+
this.localListeners.add(listener);
|
|
433
|
+
return () => this.localListeners.delete(listener);
|
|
434
|
+
}
|
|
435
|
+
setLocal(state) {
|
|
436
|
+
this.local = state;
|
|
437
|
+
for (const listener of this.localListeners)
|
|
438
|
+
listener(state);
|
|
439
|
+
}
|
|
440
|
+
async warmUp() {
|
|
441
|
+
if (this.mode !== "local" || this.local.status !== "idle")
|
|
442
|
+
return;
|
|
443
|
+
if (!await this.canRunLocally())
|
|
444
|
+
return;
|
|
445
|
+
const { loadLocalModel } = await (__preload("qfx1tgz2"), import("../local/index.js"));
|
|
446
|
+
this.setLocal({ status: "loading" });
|
|
447
|
+
try {
|
|
448
|
+
await loadLocalModel(await this.localSpec(), (progress) => this.setLocal({ status: "loading", progress }));
|
|
449
|
+
this.setLocal({ status: "ready" });
|
|
450
|
+
} catch {
|
|
451
|
+
this.setLocal({ status: "error" });
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async warmUpIfCached() {
|
|
455
|
+
if (this.mode !== "local" || this.preload === "consent")
|
|
456
|
+
return;
|
|
457
|
+
const { isLocalModelCached } = await (__preload("qfx1tgz2"), import("../local/index.js"));
|
|
458
|
+
if (await isLocalModelCached(await this.localSpec()))
|
|
459
|
+
await this.warmUp();
|
|
460
|
+
}
|
|
461
|
+
get preload() {
|
|
462
|
+
return this.options.preload ?? "question";
|
|
405
463
|
}
|
|
406
464
|
async settleChoice() {
|
|
407
|
-
if (!this.localAllowed || readLocal(`${this.key}-local`) !== null)
|
|
465
|
+
if (!this.localAllowed || !this.keyAllowed || readLocal(`${this.key}-local`) !== null)
|
|
408
466
|
return;
|
|
409
|
-
const [{ isConfigured }, store] = await Promise.all([(__preload("
|
|
467
|
+
const [{ isConfigured }, store] = await Promise.all([(__preload("xreqcp1t"), import("../llm/index.js")), this.store()]);
|
|
410
468
|
const saved = await store.load();
|
|
411
469
|
if (saved && isConfigured(saved))
|
|
412
470
|
writeLocal(`${this.key}-local`, "0");
|
|
@@ -415,14 +473,14 @@ class ChatSession {
|
|
|
415
473
|
const { local } = this.options;
|
|
416
474
|
if (local && typeof local === "object")
|
|
417
475
|
return local;
|
|
418
|
-
return (await (__preload("
|
|
476
|
+
return (await (__preload("qfx1tgz2"), import("../local/index.js"))).DEFAULT_LOCAL_MODEL;
|
|
419
477
|
}
|
|
420
478
|
useLocal(on) {
|
|
421
479
|
writeLocal(`${this.key}-local`, on ? "1" : "0");
|
|
422
480
|
this.engine = null;
|
|
423
481
|
}
|
|
424
482
|
get needsDownloadConsent() {
|
|
425
|
-
return this.mode === "local" && readLocal(`${this.key}-local-consent`) !== "1";
|
|
483
|
+
return this.mode === "local" && this.preload === "consent" && this.local.status !== "ready" && readLocal(`${this.key}-local-consent`) !== "1";
|
|
426
484
|
}
|
|
427
485
|
agreeToDownload() {
|
|
428
486
|
writeLocal(`${this.key}-local-consent`, "1");
|
|
@@ -437,7 +495,7 @@ class ChatSession {
|
|
|
437
495
|
this.persist();
|
|
438
496
|
}
|
|
439
497
|
store() {
|
|
440
|
-
this.storePromise ??= (__preload("
|
|
498
|
+
this.storePromise ??= (__preload("xreqcp1t"), import("../llm/index.js")).then((llm) => {
|
|
441
499
|
const opts = {};
|
|
442
500
|
if (this.options.catalogUrl)
|
|
443
501
|
opts.catalogUrl = this.options.catalogUrl;
|
|
@@ -448,16 +506,22 @@ class ChatSession {
|
|
|
448
506
|
return this.storePromise;
|
|
449
507
|
}
|
|
450
508
|
async settings() {
|
|
451
|
-
|
|
509
|
+
const mode = this.mode;
|
|
510
|
+
if (mode === "custom" || mode === "local")
|
|
452
511
|
return null;
|
|
453
|
-
const llm = await (__preload("
|
|
454
|
-
|
|
512
|
+
const llm = await (__preload("xreqcp1t"), import("../llm/index.js"));
|
|
513
|
+
const config = this.options.llm ?? {};
|
|
514
|
+
if (mode === "proxy")
|
|
515
|
+
return llm.proxySettings(config);
|
|
516
|
+
if (mode === "owner")
|
|
517
|
+
return llm.ownerKeySettings(config, this.options.catalogUrl);
|
|
518
|
+
return llm.resolveSettings({ ...config, endpoint: "" }, await this.store());
|
|
455
519
|
}
|
|
456
520
|
async needsSetup() {
|
|
457
521
|
return this.mode === "visitor" && !await this.settings();
|
|
458
522
|
}
|
|
459
523
|
async canRunLocally() {
|
|
460
|
-
return (await (__preload("
|
|
524
|
+
return (await (__preload("qfx1tgz2"), import("../local/index.js"))).hasWebGPU();
|
|
461
525
|
}
|
|
462
526
|
async loadIndex() {
|
|
463
527
|
const { index } = this.options;
|
|
@@ -484,16 +548,16 @@ class ChatSession {
|
|
|
484
548
|
]);
|
|
485
549
|
const local = this.mode === "local";
|
|
486
550
|
const model = this.options.model ?? (local ? async () => {
|
|
487
|
-
const { localModel } = await (__preload("
|
|
551
|
+
const { localModel } = await (__preload("qfx1tgz2"), import("../local/index.js"));
|
|
488
552
|
return localModel({
|
|
489
553
|
spec: await this.localSpec(),
|
|
490
|
-
onProgress: (
|
|
554
|
+
onProgress: (progress) => this.setLocal({ status: "loading", progress })
|
|
491
555
|
});
|
|
492
556
|
} : async () => {
|
|
493
557
|
const settings = await this.settings();
|
|
494
558
|
if (!settings)
|
|
495
559
|
throw new SetupNeeded2;
|
|
496
|
-
const { loadModel } = await (__preload("
|
|
560
|
+
const { loadModel } = await (__preload("xreqcp1t"), import("../llm/index.js"));
|
|
497
561
|
return loadModel(settings);
|
|
498
562
|
});
|
|
499
563
|
const opts = {
|
|
@@ -515,6 +579,8 @@ class ChatSession {
|
|
|
515
579
|
this.turns.push(turn);
|
|
516
580
|
this.persist();
|
|
517
581
|
try {
|
|
582
|
+
if (this.mode === "local")
|
|
583
|
+
this.warmUp();
|
|
518
584
|
this.engine ??= this.createEngine();
|
|
519
585
|
const engine = await this.engine.catch((error) => {
|
|
520
586
|
this.engine = null;
|
|
@@ -532,6 +598,8 @@ class ChatSession {
|
|
|
532
598
|
onText(turn.content, turn);
|
|
533
599
|
}
|
|
534
600
|
const done = await answer.done;
|
|
601
|
+
if (this.mode === "local")
|
|
602
|
+
this.setLocal({ status: "ready" });
|
|
535
603
|
turn.content = done.text;
|
|
536
604
|
turn.cited = done.cited;
|
|
537
605
|
} catch (error) {
|
|
@@ -553,7 +621,7 @@ class ChatSession {
|
|
|
553
621
|
return `local-${error.code ?? "load"}|${error.message}`;
|
|
554
622
|
}
|
|
555
623
|
try {
|
|
556
|
-
const { describeError } = await (__preload("
|
|
624
|
+
const { describeError } = await (__preload("xreqcp1t"), import("../llm/index.js"));
|
|
557
625
|
const settings = this.mode === "custom" || this.mode === "local" ? null : await this.settings();
|
|
558
626
|
const info = describeError(error, settings?.apiKey);
|
|
559
627
|
return `${info.code}|${info.message}`;
|
|
@@ -790,7 +858,7 @@ footer a:hover { text-decoration: underline; }
|
|
|
790
858
|
p.or { margin: 0; color: var(--muted); font-size: 13px; text-align: center; }
|
|
791
859
|
.consent { display: grid; gap: 10px; padding: 14px; border-radius: 12px; background: var(--raised); }
|
|
792
860
|
.consent p { margin: 0; }
|
|
793
|
-
.
|
|
861
|
+
.loadbar { display: grid; gap: 6px; margin: 4px 12px 0; padding: 8px 12px; border-radius: 10px; background: var(--raised); color: var(--muted); font-size: 13px; }
|
|
794
862
|
progress { width: 100%; height: 6px; appearance: none; border: 0; border-radius: 3px; background: var(--line); overflow: hidden; }
|
|
795
863
|
progress::-webkit-progress-bar { background: var(--line); }
|
|
796
864
|
progress::-webkit-progress-value { background: var(--lamp); }
|
|
@@ -840,6 +908,8 @@ class MurmurChat2 extends Base {
|
|
|
840
908
|
controller = null;
|
|
841
909
|
built = false;
|
|
842
910
|
unsubscribe = null;
|
|
911
|
+
unsubscribeLocal = null;
|
|
912
|
+
loadbar;
|
|
843
913
|
constructor() {
|
|
844
914
|
super();
|
|
845
915
|
this.root = this.attachShadow({ mode: "open" });
|
|
@@ -859,12 +929,16 @@ class MurmurChat2 extends Base {
|
|
|
859
929
|
disconnectedCallback() {
|
|
860
930
|
this.controller?.abort();
|
|
861
931
|
this.unsubscribe?.();
|
|
932
|
+
this.unsubscribeLocal?.();
|
|
933
|
+
this.unsubscribeLocal?.();
|
|
862
934
|
}
|
|
863
935
|
async open() {
|
|
864
936
|
if (!this.built)
|
|
865
937
|
this.build();
|
|
866
938
|
this.launcher.setAttribute("aria-expanded", "true");
|
|
867
939
|
this.panel.hidden = false;
|
|
940
|
+
if (this.session?.preload === "open")
|
|
941
|
+
this.session.warmUp();
|
|
868
942
|
this.session?.persist();
|
|
869
943
|
tabStorage("set", this.openKey);
|
|
870
944
|
this.input.focus();
|
|
@@ -965,6 +1039,8 @@ class MurmurChat2 extends Base {
|
|
|
965
1039
|
this.submit();
|
|
966
1040
|
});
|
|
967
1041
|
form.append(this.input, this.sendBtn);
|
|
1042
|
+
this.loadbar = el(doc, "div", { class: "loadbar", role: "status" });
|
|
1043
|
+
this.loadbar.hidden = true;
|
|
968
1044
|
const footer = el(doc, "footer");
|
|
969
1045
|
const by = el(doc, "a", {
|
|
970
1046
|
href: "https://maxgfr.github.io/murmur/",
|
|
@@ -973,7 +1049,7 @@ class MurmurChat2 extends Base {
|
|
|
973
1049
|
});
|
|
974
1050
|
by.textContent = this.t.poweredBy;
|
|
975
1051
|
footer.append(by);
|
|
976
|
-
this.chatView.append(this.log, form, footer);
|
|
1052
|
+
this.chatView.append(this.log, this.loadbar, form, footer);
|
|
977
1053
|
this.setupView = el(doc, "div", { class: "setup" });
|
|
978
1054
|
this.setupView.hidden = true;
|
|
979
1055
|
this.panel.append(header, this.chatView, this.setupView);
|
|
@@ -981,11 +1057,37 @@ class MurmurChat2 extends Base {
|
|
|
981
1057
|
if (this.hasAttribute("inline"))
|
|
982
1058
|
this.launcher.hidden = true;
|
|
983
1059
|
this.built = true;
|
|
984
|
-
|
|
1060
|
+
const session = new ChatSession({ ...o, lang });
|
|
1061
|
+
this.session = session;
|
|
1062
|
+
this.settingsBtn.hidden = !session.keyAllowed;
|
|
1063
|
+
this.unsubscribeLocal = session.onLocalState((state) => this.drawLocal(state));
|
|
1064
|
+
whenIdle(() => {
|
|
1065
|
+
if (session.preload === "idle")
|
|
1066
|
+
session.warmUp();
|
|
1067
|
+
else
|
|
1068
|
+
session.warmUpIfCached();
|
|
1069
|
+
});
|
|
985
1070
|
this.renderChat();
|
|
986
1071
|
if (o.open || !this.hasAttribute("inline") && tabStorage("get", this.openKey))
|
|
987
1072
|
this.open();
|
|
988
1073
|
}
|
|
1074
|
+
drawLocal(state) {
|
|
1075
|
+
const doc = this.ownerDocument;
|
|
1076
|
+
const lamp = this.launcher.querySelector(".lamp");
|
|
1077
|
+
lamp?.classList.toggle("busy", state.status === "loading");
|
|
1078
|
+
if (state.status !== "loading") {
|
|
1079
|
+
this.loadbar.hidden = true;
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
const p = state.progress;
|
|
1083
|
+
const label = !p ? this.t.localLoading : 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)}`;
|
|
1084
|
+
const bar = el(doc, "progress", { max: "1" });
|
|
1085
|
+
if (p && p.progress < 1)
|
|
1086
|
+
bar.setAttribute("value", String(p.progress));
|
|
1087
|
+
bar.setAttribute("aria-label", this.t.localLoadingLabel);
|
|
1088
|
+
this.loadbar.replaceChildren(text(doc, "span", label), bar);
|
|
1089
|
+
this.loadbar.hidden = false;
|
|
1090
|
+
}
|
|
989
1091
|
autosize() {
|
|
990
1092
|
this.input.style.height = "auto";
|
|
991
1093
|
this.input.style.height = `${Math.min(this.input.scrollHeight, 140)}px`;
|
|
@@ -1097,7 +1199,7 @@ class MurmurChat2 extends Base {
|
|
|
1097
1199
|
if (!question || this.controller || !this.session)
|
|
1098
1200
|
return;
|
|
1099
1201
|
await this.session.settleChoice();
|
|
1100
|
-
if (this.session.needsDownloadConsent) {
|
|
1202
|
+
if (this.session.needsDownloadConsent || this.session.mode === "local" && !await this.session.canRunLocally()) {
|
|
1101
1203
|
await this.showConsent(question);
|
|
1102
1204
|
return;
|
|
1103
1205
|
}
|
|
@@ -1116,15 +1218,6 @@ class MurmurChat2 extends Base {
|
|
|
1116
1218
|
this.log.append(node);
|
|
1117
1219
|
this.log.scrollTop = this.log.scrollHeight;
|
|
1118
1220
|
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
|
-
};
|
|
1128
1221
|
const controller = new AbortController;
|
|
1129
1222
|
this.controller = controller;
|
|
1130
1223
|
this.setSendMode("stop");
|
|
@@ -1173,11 +1266,13 @@ class MurmurChat2 extends Base {
|
|
|
1173
1266
|
this.showSetup(question);
|
|
1174
1267
|
};
|
|
1175
1268
|
if (!await session.canRunLocally()) {
|
|
1176
|
-
card.append(text(doc, "p", t.localNoWebGPU));
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1269
|
+
card.append(text(doc, "p", session.keyAllowed ? t.localNoWebGPU : t.localNoWebGPUOnly));
|
|
1270
|
+
if (session.keyAllowed) {
|
|
1271
|
+
const key = el(doc, "button", { class: "primary", type: "button" });
|
|
1272
|
+
key.textContent = t.useKey;
|
|
1273
|
+
key.addEventListener("click", useKey);
|
|
1274
|
+
row.append(key);
|
|
1275
|
+
}
|
|
1181
1276
|
} else {
|
|
1182
1277
|
card.append(text(doc, "p", t.localConsent.replace("{name}", spec.name).replace("{size}", size(spec.sizeMB, this.uiLang, t))));
|
|
1183
1278
|
const go = el(doc, "button", { class: "primary", type: "button" });
|
|
@@ -1189,10 +1284,12 @@ class MurmurChat2 extends Base {
|
|
|
1189
1284
|
this.submit();
|
|
1190
1285
|
});
|
|
1191
1286
|
row.append(go);
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1287
|
+
if (session.keyAllowed) {
|
|
1288
|
+
const key = el(doc, "button", { class: "link-btn", type: "button" });
|
|
1289
|
+
key.textContent = t.useKey;
|
|
1290
|
+
key.addEventListener("click", useKey);
|
|
1291
|
+
row.append(key);
|
|
1292
|
+
}
|
|
1196
1293
|
}
|
|
1197
1294
|
card.append(row);
|
|
1198
1295
|
this.log.append(card);
|
|
@@ -1389,6 +1486,13 @@ function tabStorage(op, key) {
|
|
|
1389
1486
|
} catch {}
|
|
1390
1487
|
return false;
|
|
1391
1488
|
}
|
|
1489
|
+
function whenIdle(work) {
|
|
1490
|
+
const idle = globalThis.requestIdleCallback;
|
|
1491
|
+
if (idle)
|
|
1492
|
+
idle(work, { timeout: 4000 });
|
|
1493
|
+
else
|
|
1494
|
+
setTimeout(work, 1500);
|
|
1495
|
+
}
|
|
1392
1496
|
function el(doc, tag, attrs = {}) {
|
|
1393
1497
|
const node = doc.createElement(tag);
|
|
1394
1498
|
for (const [k, v] of Object.entries(attrs))
|
package/dist/lib/embed.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__chunks,
|
|
3
3
|
mountMurmur2
|
|
4
|
-
} from "./chunks/index-
|
|
5
|
-
__chunks(import.meta.url,["
|
|
4
|
+
} from "./chunks/index-4ftk20yq.js";
|
|
5
|
+
__chunks(import.meta.url,["n035jphv","4ftk20yq","t36hd4vt","wxshtnm0","dj4321kh","xreqcp1t","qfx1tgz2","kaeec41p","b77xqap3","8h9c8ekq","p86sy3y4","1zk822m5"],[["./embed.js",1],["./chunks/index-4ftk20yq.js",8],["./chunks/murmur-t36hd4vt.js",9,10],["./chunks/retriever-wxshtnm0.js",10],["./indexer/index.js",11],["./llm/index.js"],["./local/index.js"],["./chunks/chunk-kaeec41p.js",11],["./chunks/index-b77xqap3.js"],["./chunks/index-8h9c8ekq.js",10,8],["./chunks/index-p86sy3y4.js",8],["./chunks/index-1zk822m5.js"]],0);
|
|
6
6
|
|
|
7
7
|
// src/embed.ts
|
|
8
8
|
function optionsFromDataset(data, base) {
|
|
@@ -16,6 +16,8 @@ function optionsFromDataset(data, base) {
|
|
|
16
16
|
llm.model = data.model;
|
|
17
17
|
if (data.endpoint)
|
|
18
18
|
llm.endpoint = data.endpoint;
|
|
19
|
+
if (data.apiKey)
|
|
20
|
+
llm.apiKey = data.apiKey;
|
|
19
21
|
if (Object.keys(llm).length)
|
|
20
22
|
o.llm = llm;
|
|
21
23
|
if (data.instructions)
|
|
@@ -47,6 +49,10 @@ function optionsFromDataset(data, base) {
|
|
|
47
49
|
o.open = true;
|
|
48
50
|
if (data.local === "false")
|
|
49
51
|
o.local = false;
|
|
52
|
+
if (data.mode === "auto" || data.mode === "proxy" || data.mode === "local" || data.mode === "key")
|
|
53
|
+
o.mode = data.mode;
|
|
54
|
+
if (data.preload === "question" || data.preload === "open" || data.preload === "idle" || data.preload === "consent")
|
|
55
|
+
o.preload = data.preload;
|
|
50
56
|
return o;
|
|
51
57
|
}
|
|
52
58
|
function start() {
|
package/dist/lib/llm/index.js
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createLlmSettingsStore,
|
|
4
4
|
DEFAULT_RECOMMENDED,
|
|
5
|
+
defaultModelOf,
|
|
5
6
|
isConfigured,
|
|
6
7
|
liveCatalog,
|
|
8
|
+
loadCatalog,
|
|
9
|
+
providerMeta,
|
|
7
10
|
secureStorage,
|
|
8
11
|
validateEndpoint
|
|
9
12
|
} from "llm-connect";
|
|
@@ -23,6 +26,25 @@ function proxySettings(config) {
|
|
|
23
26
|
settings.headers = config.headers;
|
|
24
27
|
return settings;
|
|
25
28
|
}
|
|
29
|
+
var warned = false;
|
|
30
|
+
async function ownerKeySettings(config, catalogUrl = DEFAULT_CATALOG_URL) {
|
|
31
|
+
if (!config.apiKey)
|
|
32
|
+
return null;
|
|
33
|
+
if (!warned && typeof console !== "undefined") {
|
|
34
|
+
warned = true;
|
|
35
|
+
console.warn("murmur: the API key in this page can be read by anyone. Use a key with a spending limit, or a proxy (data-endpoint).");
|
|
36
|
+
}
|
|
37
|
+
const providerId = config.provider || "openai";
|
|
38
|
+
const catalog = await loadCatalog(catalogUrl);
|
|
39
|
+
const meta = providerMeta(catalog, providerId);
|
|
40
|
+
if (!meta)
|
|
41
|
+
throw new Error(`murmur: unknown provider "${providerId}"`);
|
|
42
|
+
const model = config.model || defaultModelOf(catalog[providerId]) || "";
|
|
43
|
+
const settings = { apiKey: config.apiKey, model, ...meta };
|
|
44
|
+
if (config.headers)
|
|
45
|
+
settings.headers = config.headers;
|
|
46
|
+
return settings;
|
|
47
|
+
}
|
|
26
48
|
function visitorStore(config, options = {}) {
|
|
27
49
|
const key = options.storageKey ?? "murmur";
|
|
28
50
|
const defaults = {};
|
|
@@ -49,6 +71,7 @@ export {
|
|
|
49
71
|
describeError,
|
|
50
72
|
isConfigured2 as isConfigured,
|
|
51
73
|
loadModel,
|
|
74
|
+
ownerKeySettings,
|
|
52
75
|
proxySettings,
|
|
53
76
|
resolveSettings,
|
|
54
77
|
visitorStore
|
package/dist/lib/local/index.js
CHANGED
|
@@ -26,9 +26,37 @@ function chatMessages(prompt) {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
var loaded = new Map;
|
|
29
|
+
var ready = new Set;
|
|
29
30
|
var listeners = new Map;
|
|
31
|
+
var keyOf = (spec) => `${spec.id}@${spec.dtype}`;
|
|
32
|
+
var CACHE_NAME = "transformers-cache";
|
|
33
|
+
async function isLocalModelCached(spec = DEFAULT_LOCAL_MODEL) {
|
|
34
|
+
try {
|
|
35
|
+
if (typeof caches === "undefined" || !await caches.has(CACHE_NAME))
|
|
36
|
+
return false;
|
|
37
|
+
const cache = await caches.open(CACHE_NAME);
|
|
38
|
+
const weights = `/${spec.id}/resolve/`;
|
|
39
|
+
const file = `model_${spec.dtype}.onnx`;
|
|
40
|
+
return (await cache.keys()).some((r) => r.url.includes(weights) && r.url.includes(file));
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function isLocalModelReady(spec = DEFAULT_LOCAL_MODEL) {
|
|
46
|
+
return ready.has(keyOf(spec));
|
|
47
|
+
}
|
|
48
|
+
async function persistStorage() {
|
|
49
|
+
try {
|
|
50
|
+
const storage = globalThis.navigator?.storage;
|
|
51
|
+
if (!storage?.persist)
|
|
52
|
+
return false;
|
|
53
|
+
return await storage.persisted?.() || await storage.persist();
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
30
58
|
function loadLocalModel(spec = DEFAULT_LOCAL_MODEL, onProgress) {
|
|
31
|
-
const key =
|
|
59
|
+
const key = keyOf(spec);
|
|
32
60
|
const set = listeners.get(key) ?? new Set;
|
|
33
61
|
listeners.set(key, set);
|
|
34
62
|
if (onProgress)
|
|
@@ -38,6 +66,8 @@ function loadLocalModel(spec = DEFAULT_LOCAL_MODEL, onProgress) {
|
|
|
38
66
|
pending = (async () => {
|
|
39
67
|
if (!await hasWebGPU())
|
|
40
68
|
throw new LocalModelError("webgpu", "This browser has no WebGPU.");
|
|
69
|
+
if (!await isLocalModelCached(spec))
|
|
70
|
+
persistStorage();
|
|
41
71
|
const { pipeline } = await import("@huggingface/transformers");
|
|
42
72
|
const generator = await pipeline("text-generation", spec.id, {
|
|
43
73
|
dtype: spec.dtype,
|
|
@@ -53,7 +83,10 @@ function loadLocalModel(spec = DEFAULT_LOCAL_MODEL, onProgress) {
|
|
|
53
83
|
for (const listener of set)
|
|
54
84
|
listener(p);
|
|
55
85
|
}
|
|
86
|
+
}).catch((error) => {
|
|
87
|
+
throw new LocalModelError("load", error instanceof Error ? error.message : String(error));
|
|
56
88
|
});
|
|
89
|
+
ready.add(key);
|
|
57
90
|
return generator;
|
|
58
91
|
})();
|
|
59
92
|
pending.catch(() => loaded.delete(key));
|
|
@@ -158,6 +191,9 @@ export {
|
|
|
158
191
|
LocalModelError,
|
|
159
192
|
chatMessages,
|
|
160
193
|
hasWebGPU,
|
|
194
|
+
isLocalModelCached,
|
|
195
|
+
isLocalModelReady,
|
|
161
196
|
loadLocalModel,
|
|
162
|
-
localModel
|
|
197
|
+
localModel,
|
|
198
|
+
persistStorage
|
|
163
199
|
};
|
package/dist/lib/widget/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
MurmurChat2,
|
|
9
9
|
defineMurmurChat2,
|
|
10
10
|
mountMurmur2
|
|
11
|
-
} from "../chunks/index-
|
|
12
|
-
__chunks(import.meta.url,["
|
|
11
|
+
} from "../chunks/index-4ftk20yq.js";
|
|
12
|
+
__chunks(import.meta.url,["kaf6n93p","4ftk20yq","t36hd4vt","wxshtnm0","dj4321kh","xreqcp1t","qfx1tgz2","kaeec41p","b77xqap3","8h9c8ekq","p86sy3y4","1zk822m5"],[["./index.js",1],["../chunks/index-4ftk20yq.js",8],["../chunks/murmur-t36hd4vt.js",9,10],["../chunks/retriever-wxshtnm0.js",10],["../indexer/index.js",11],["../llm/index.js"],["../local/index.js"],["../chunks/chunk-kaeec41p.js",11],["../chunks/index-b77xqap3.js"],["../chunks/index-8h9c8ekq.js",10,8],["../chunks/index-p86sy3y4.js",8],["../chunks/index-1zk822m5.js"]],0);
|
|
13
13
|
export {
|
|
14
14
|
MESSAGES2 as MESSAGES,
|
|
15
15
|
MurmurChat2 as MurmurChat,
|
|
@@ -13,11 +13,19 @@ export interface LlmConfig {
|
|
|
13
13
|
endpoint?: string;
|
|
14
14
|
/** Extra headers sent to the proxy. */
|
|
15
15
|
headers?: Record<string, string>;
|
|
16
|
+
/**
|
|
17
|
+
* The owner's own key, used for every visitor with `provider` and `model`. It is in the page,
|
|
18
|
+
* so anyone can read it: only a key with a spending limit (OpenRouter, a restricted project
|
|
19
|
+
* key), or prefer a proxy that keeps it secret.
|
|
20
|
+
*/
|
|
21
|
+
apiKey?: string;
|
|
16
22
|
}
|
|
17
23
|
/** Where the catalog falls back to when models.dev or OpenRouter cannot be reached. */
|
|
18
24
|
export declare const DEFAULT_CATALOG_URL = "https://maxgfr.github.io/murmur/cdn/models.json";
|
|
19
25
|
/** Settings calling the owner's proxy, or null when there is no (valid) proxy. */
|
|
20
26
|
export declare function proxySettings(config: LlmConfig): LlmSettings | null;
|
|
27
|
+
/** Settings that call the provider with the owner's key, or null when there is none. */
|
|
28
|
+
export declare function ownerKeySettings(config: LlmConfig, catalogUrl?: string): Promise<LlmSettings | null>;
|
|
21
29
|
export interface VisitorStoreOptions {
|
|
22
30
|
/** Fallback catalog URL. Default {@link DEFAULT_CATALOG_URL}. */
|
|
23
31
|
catalogUrl?: string;
|
|
@@ -42,6 +42,18 @@ export declare function chatMessages(prompt: LanguageModelV4Prompt): {
|
|
|
42
42
|
type Generator = ((messages: unknown, options: Record<string, unknown>) => Promise<unknown>) & {
|
|
43
43
|
tokenizer: unknown;
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Whether the model's weights are already in this browser's cache, so loading it needs no
|
|
47
|
+
* network: the weights file for this dtype is looked up among the cached URLs.
|
|
48
|
+
*/
|
|
49
|
+
export declare function isLocalModelCached(spec?: LocalModelSpec): Promise<boolean>;
|
|
50
|
+
/** Whether the model is loaded and compiled in this page. */
|
|
51
|
+
export declare function isLocalModelReady(spec?: LocalModelSpec): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Asks the browser to keep this site's storage under pressure, so 1 GB of weights is not
|
|
54
|
+
* evicted and downloaded again. Browsers decide on their own (engagement, installed app…).
|
|
55
|
+
*/
|
|
56
|
+
export declare function persistStorage(): Promise<boolean>;
|
|
45
57
|
/** Downloads (or reads from cache) and compiles the model; later calls share the same load. */
|
|
46
58
|
export declare function loadLocalModel(spec?: LocalModelSpec, onProgress?: (progress: LoadProgress) => void): Promise<Generator>;
|
|
47
59
|
export declare class LocalModelError extends Error {
|
|
@@ -29,6 +29,8 @@ export declare class MurmurChat extends Base {
|
|
|
29
29
|
private controller;
|
|
30
30
|
private built;
|
|
31
31
|
private unsubscribe;
|
|
32
|
+
private unsubscribeLocal;
|
|
33
|
+
private loadbar;
|
|
32
34
|
constructor();
|
|
33
35
|
configure(options: WidgetOptions): this;
|
|
34
36
|
get isOpen(): boolean;
|
|
@@ -41,6 +43,8 @@ export declare class MurmurChat extends Base {
|
|
|
41
43
|
ask(question: string): Promise<void>;
|
|
42
44
|
private get openKey();
|
|
43
45
|
private build;
|
|
46
|
+
/** The download bar and the bubble's lamp follow the in-browser model. */
|
|
47
|
+
private drawLocal;
|
|
44
48
|
private autosize;
|
|
45
49
|
private setSendMode;
|
|
46
50
|
private emit;
|