murmur-rag 1.1.0 → 1.3.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 +28 -19
- package/dist/cdn/{anthropic-dc7h9prc.js → anthropic-tw416spz.js} +2 -2
- package/dist/cdn/chunk-xyp6nm65.js +1 -0
- package/dist/cdn/{embed-fd96s8qk.js → embed-3am76qfd.js} +9 -9
- package/dist/cdn/embed-50pfy67v.js +83 -0
- package/dist/cdn/{embed-z32y952f.js → embed-cx1r66ch.js} +6 -6
- package/dist/cdn/{embed-ssqsb38w.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-acjpjh6t.js → embed-yz75savs.js} +3 -3
- package/dist/cdn/{google-yywkqdph.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-25w1nsx5.js +3 -0
- package/dist/cdn/{murmur-j1ky5vmr.js → murmur-1a55et82.js} +3 -3
- package/dist/cdn/murmur.js +7 -7
- package/dist/cdn/openai-bjrbem49.js +18 -0
- package/dist/cdn/{openai-compatible-zhaj1r43.js → openai-compatible-sg25g0vj.js} +6 -6
- package/dist/cdn/retriever-06ybegpd.js +1 -0
- package/dist/cdn/{transformers.web-3kmbvcbt.js → transformers.web-49m0ht4a.js} +9 -9
- package/dist/lib/chunks/{index-92w507gk.js → index-4ftk20yq.js} +164 -50
- package/dist/lib/embed.js +10 -4
- 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 +56 -8
- package/dist/types/widget/styles.d.ts +1 -1
- package/package.json +1 -1
- package/dist/cdn/chunk-hpx6rpea.js +0 -1
- package/dist/cdn/embed-6gef7ym5.js +0 -10
- package/dist/cdn/embed-jdraznwg.js +0 -83
- package/dist/cdn/index-bq0x71h1.js +0 -1
- package/dist/cdn/index-jt6n0d28.js +0 -1
- package/dist/cdn/index-nesgnf2n.js +0 -1
- package/dist/cdn/index-wgzgvzaf.js +0 -1
- package/dist/cdn/mistral-0mg4kk7s.js +0 -3
- package/dist/cdn/openai-g1685z9v.js +0 -18
- package/dist/cdn/retriever-dqxzjaqk.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,28 +394,93 @@ 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 (
|
|
404
|
+
if (m === "proxy" || m === "auto" && this.options.llm?.endpoint)
|
|
405
|
+
return "proxy";
|
|
406
|
+
if ((m === "auto" || m === "key") && this.options.llm?.apiKey)
|
|
407
|
+
return "owner";
|
|
408
|
+
if (m === "local")
|
|
398
409
|
return "local";
|
|
399
|
-
|
|
410
|
+
if (m === "key" || !this.localAllowed)
|
|
411
|
+
return "visitor";
|
|
412
|
+
return readLocal(`${this.key}-local`) === "0" ? "visitor" : "local";
|
|
413
|
+
}
|
|
414
|
+
get localAllowed() {
|
|
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";
|
|
463
|
+
}
|
|
464
|
+
async settleChoice() {
|
|
465
|
+
if (!this.localAllowed || !this.keyAllowed || readLocal(`${this.key}-local`) !== null)
|
|
466
|
+
return;
|
|
467
|
+
const [{ isConfigured }, store] = await Promise.all([(__preload("xreqcp1t"), import("../llm/index.js")), this.store()]);
|
|
468
|
+
const saved = await store.load();
|
|
469
|
+
if (saved && isConfigured(saved))
|
|
470
|
+
writeLocal(`${this.key}-local`, "0");
|
|
400
471
|
}
|
|
401
472
|
async localSpec() {
|
|
402
473
|
const { local } = this.options;
|
|
403
474
|
if (local && typeof local === "object")
|
|
404
475
|
return local;
|
|
405
|
-
return (await (__preload("
|
|
406
|
-
}
|
|
407
|
-
get visitorChoseLocal() {
|
|
408
|
-
return readLocal(`${this.key}-local`) === "1";
|
|
476
|
+
return (await (__preload("qfx1tgz2"), import("../local/index.js"))).DEFAULT_LOCAL_MODEL;
|
|
409
477
|
}
|
|
410
478
|
useLocal(on) {
|
|
411
|
-
writeLocal(`${this.key}-local`, on ? "1" :
|
|
479
|
+
writeLocal(`${this.key}-local`, on ? "1" : "0");
|
|
412
480
|
this.engine = null;
|
|
413
481
|
}
|
|
414
482
|
get needsDownloadConsent() {
|
|
415
|
-
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";
|
|
416
484
|
}
|
|
417
485
|
agreeToDownload() {
|
|
418
486
|
writeLocal(`${this.key}-local-consent`, "1");
|
|
@@ -427,7 +495,7 @@ class ChatSession {
|
|
|
427
495
|
this.persist();
|
|
428
496
|
}
|
|
429
497
|
store() {
|
|
430
|
-
this.storePromise ??= (__preload("
|
|
498
|
+
this.storePromise ??= (__preload("xreqcp1t"), import("../llm/index.js")).then((llm) => {
|
|
431
499
|
const opts = {};
|
|
432
500
|
if (this.options.catalogUrl)
|
|
433
501
|
opts.catalogUrl = this.options.catalogUrl;
|
|
@@ -438,16 +506,22 @@ class ChatSession {
|
|
|
438
506
|
return this.storePromise;
|
|
439
507
|
}
|
|
440
508
|
async settings() {
|
|
441
|
-
|
|
509
|
+
const mode = this.mode;
|
|
510
|
+
if (mode === "custom" || mode === "local")
|
|
442
511
|
return null;
|
|
443
|
-
const llm = await (__preload("
|
|
444
|
-
|
|
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());
|
|
445
519
|
}
|
|
446
520
|
async needsSetup() {
|
|
447
521
|
return this.mode === "visitor" && !await this.settings();
|
|
448
522
|
}
|
|
449
523
|
async canRunLocally() {
|
|
450
|
-
return (await (__preload("
|
|
524
|
+
return (await (__preload("qfx1tgz2"), import("../local/index.js"))).hasWebGPU();
|
|
451
525
|
}
|
|
452
526
|
async loadIndex() {
|
|
453
527
|
const { index } = this.options;
|
|
@@ -474,16 +548,16 @@ class ChatSession {
|
|
|
474
548
|
]);
|
|
475
549
|
const local = this.mode === "local";
|
|
476
550
|
const model = this.options.model ?? (local ? async () => {
|
|
477
|
-
const { localModel } = await (__preload("
|
|
551
|
+
const { localModel } = await (__preload("qfx1tgz2"), import("../local/index.js"));
|
|
478
552
|
return localModel({
|
|
479
553
|
spec: await this.localSpec(),
|
|
480
|
-
onProgress: (
|
|
554
|
+
onProgress: (progress) => this.setLocal({ status: "loading", progress })
|
|
481
555
|
});
|
|
482
556
|
} : async () => {
|
|
483
557
|
const settings = await this.settings();
|
|
484
558
|
if (!settings)
|
|
485
559
|
throw new SetupNeeded2;
|
|
486
|
-
const { loadModel } = await (__preload("
|
|
560
|
+
const { loadModel } = await (__preload("xreqcp1t"), import("../llm/index.js"));
|
|
487
561
|
return loadModel(settings);
|
|
488
562
|
});
|
|
489
563
|
const opts = {
|
|
@@ -505,6 +579,8 @@ class ChatSession {
|
|
|
505
579
|
this.turns.push(turn);
|
|
506
580
|
this.persist();
|
|
507
581
|
try {
|
|
582
|
+
if (this.mode === "local")
|
|
583
|
+
this.warmUp();
|
|
508
584
|
this.engine ??= this.createEngine();
|
|
509
585
|
const engine = await this.engine.catch((error) => {
|
|
510
586
|
this.engine = null;
|
|
@@ -522,6 +598,8 @@ class ChatSession {
|
|
|
522
598
|
onText(turn.content, turn);
|
|
523
599
|
}
|
|
524
600
|
const done = await answer.done;
|
|
601
|
+
if (this.mode === "local")
|
|
602
|
+
this.setLocal({ status: "ready" });
|
|
525
603
|
turn.content = done.text;
|
|
526
604
|
turn.cited = done.cited;
|
|
527
605
|
} catch (error) {
|
|
@@ -543,7 +621,7 @@ class ChatSession {
|
|
|
543
621
|
return `local-${error.code ?? "load"}|${error.message}`;
|
|
544
622
|
}
|
|
545
623
|
try {
|
|
546
|
-
const { describeError } = await (__preload("
|
|
624
|
+
const { describeError } = await (__preload("xreqcp1t"), import("../llm/index.js"));
|
|
547
625
|
const settings = this.mode === "custom" || this.mode === "local" ? null : await this.settings();
|
|
548
626
|
const info = describeError(error, settings?.apiKey);
|
|
549
627
|
return `${info.code}|${info.message}`;
|
|
@@ -780,7 +858,7 @@ footer a:hover { text-decoration: underline; }
|
|
|
780
858
|
p.or { margin: 0; color: var(--muted); font-size: 13px; text-align: center; }
|
|
781
859
|
.consent { display: grid; gap: 10px; padding: 14px; border-radius: 12px; background: var(--raised); }
|
|
782
860
|
.consent p { margin: 0; }
|
|
783
|
-
.
|
|
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; }
|
|
784
862
|
progress { width: 100%; height: 6px; appearance: none; border: 0; border-radius: 3px; background: var(--line); overflow: hidden; }
|
|
785
863
|
progress::-webkit-progress-bar { background: var(--line); }
|
|
786
864
|
progress::-webkit-progress-value { background: var(--lamp); }
|
|
@@ -830,6 +908,8 @@ class MurmurChat2 extends Base {
|
|
|
830
908
|
controller = null;
|
|
831
909
|
built = false;
|
|
832
910
|
unsubscribe = null;
|
|
911
|
+
unsubscribeLocal = null;
|
|
912
|
+
loadbar;
|
|
833
913
|
constructor() {
|
|
834
914
|
super();
|
|
835
915
|
this.root = this.attachShadow({ mode: "open" });
|
|
@@ -849,12 +929,16 @@ class MurmurChat2 extends Base {
|
|
|
849
929
|
disconnectedCallback() {
|
|
850
930
|
this.controller?.abort();
|
|
851
931
|
this.unsubscribe?.();
|
|
932
|
+
this.unsubscribeLocal?.();
|
|
933
|
+
this.unsubscribeLocal?.();
|
|
852
934
|
}
|
|
853
935
|
async open() {
|
|
854
936
|
if (!this.built)
|
|
855
937
|
this.build();
|
|
856
938
|
this.launcher.setAttribute("aria-expanded", "true");
|
|
857
939
|
this.panel.hidden = false;
|
|
940
|
+
if (this.session?.preload === "open")
|
|
941
|
+
this.session.warmUp();
|
|
858
942
|
this.session?.persist();
|
|
859
943
|
tabStorage("set", this.openKey);
|
|
860
944
|
this.input.focus();
|
|
@@ -918,7 +1002,7 @@ class MurmurChat2 extends Base {
|
|
|
918
1002
|
this.input.focus();
|
|
919
1003
|
});
|
|
920
1004
|
this.settingsBtn = iconButton(doc, "settings", this.t.settings);
|
|
921
|
-
this.settingsBtn.hidden = !!o.model || !!o.llm?.endpoint
|
|
1005
|
+
this.settingsBtn.hidden = !!o.model || !!o.llm?.endpoint;
|
|
922
1006
|
this.settingsBtn.addEventListener("click", () => void this.showSetup());
|
|
923
1007
|
const closeBtn = iconButton(doc, "close", this.t.close);
|
|
924
1008
|
closeBtn.hidden = this.hasAttribute("inline");
|
|
@@ -955,6 +1039,8 @@ class MurmurChat2 extends Base {
|
|
|
955
1039
|
this.submit();
|
|
956
1040
|
});
|
|
957
1041
|
form.append(this.input, this.sendBtn);
|
|
1042
|
+
this.loadbar = el(doc, "div", { class: "loadbar", role: "status" });
|
|
1043
|
+
this.loadbar.hidden = true;
|
|
958
1044
|
const footer = el(doc, "footer");
|
|
959
1045
|
const by = el(doc, "a", {
|
|
960
1046
|
href: "https://maxgfr.github.io/murmur/",
|
|
@@ -963,7 +1049,7 @@ class MurmurChat2 extends Base {
|
|
|
963
1049
|
});
|
|
964
1050
|
by.textContent = this.t.poweredBy;
|
|
965
1051
|
footer.append(by);
|
|
966
|
-
this.chatView.append(this.log, form, footer);
|
|
1052
|
+
this.chatView.append(this.log, this.loadbar, form, footer);
|
|
967
1053
|
this.setupView = el(doc, "div", { class: "setup" });
|
|
968
1054
|
this.setupView.hidden = true;
|
|
969
1055
|
this.panel.append(header, this.chatView, this.setupView);
|
|
@@ -971,11 +1057,37 @@ class MurmurChat2 extends Base {
|
|
|
971
1057
|
if (this.hasAttribute("inline"))
|
|
972
1058
|
this.launcher.hidden = true;
|
|
973
1059
|
this.built = true;
|
|
974
|
-
|
|
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
|
+
});
|
|
975
1070
|
this.renderChat();
|
|
976
1071
|
if (o.open || !this.hasAttribute("inline") && tabStorage("get", this.openKey))
|
|
977
1072
|
this.open();
|
|
978
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
|
+
}
|
|
979
1091
|
autosize() {
|
|
980
1092
|
this.input.style.height = "auto";
|
|
981
1093
|
this.input.style.height = `${Math.min(this.input.scrollHeight, 140)}px`;
|
|
@@ -1086,7 +1198,8 @@ class MurmurChat2 extends Base {
|
|
|
1086
1198
|
const question = this.input.value.trim();
|
|
1087
1199
|
if (!question || this.controller || !this.session)
|
|
1088
1200
|
return;
|
|
1089
|
-
|
|
1201
|
+
await this.session.settleChoice();
|
|
1202
|
+
if (this.session.needsDownloadConsent || this.session.mode === "local" && !await this.session.canRunLocally()) {
|
|
1090
1203
|
await this.showConsent(question);
|
|
1091
1204
|
return;
|
|
1092
1205
|
}
|
|
@@ -1105,15 +1218,6 @@ class MurmurChat2 extends Base {
|
|
|
1105
1218
|
this.log.append(node);
|
|
1106
1219
|
this.log.scrollTop = this.log.scrollHeight;
|
|
1107
1220
|
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
|
-
};
|
|
1117
1221
|
const controller = new AbortController;
|
|
1118
1222
|
this.controller = controller;
|
|
1119
1223
|
this.setSendMode("stop");
|
|
@@ -1156,16 +1260,15 @@ class MurmurChat2 extends Base {
|
|
|
1156
1260
|
const spec = await session.localSpec();
|
|
1157
1261
|
const card = el(doc, "div", { class: "msg bot consent" });
|
|
1158
1262
|
const row = el(doc, "div", { class: "row" });
|
|
1159
|
-
const ownerLocal = !!this.options.local;
|
|
1160
1263
|
const useKey = () => {
|
|
1161
1264
|
card.remove();
|
|
1162
1265
|
session.useLocal(false);
|
|
1163
1266
|
this.showSetup(question);
|
|
1164
1267
|
};
|
|
1165
1268
|
if (!await session.canRunLocally()) {
|
|
1166
|
-
card.append(text(doc, "p", t.localNoWebGPU));
|
|
1167
|
-
if (
|
|
1168
|
-
const key = el(doc, "button", { class: "
|
|
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" });
|
|
1169
1272
|
key.textContent = t.useKey;
|
|
1170
1273
|
key.addEventListener("click", useKey);
|
|
1171
1274
|
row.append(key);
|
|
@@ -1181,7 +1284,7 @@ class MurmurChat2 extends Base {
|
|
|
1181
1284
|
this.submit();
|
|
1182
1285
|
});
|
|
1183
1286
|
row.append(go);
|
|
1184
|
-
if (
|
|
1287
|
+
if (session.keyAllowed) {
|
|
1185
1288
|
const key = el(doc, "button", { class: "link-btn", type: "button" });
|
|
1186
1289
|
key.textContent = t.useKey;
|
|
1187
1290
|
key.addEventListener("click", useKey);
|
|
@@ -1224,25 +1327,29 @@ class MurmurChat2 extends Base {
|
|
|
1224
1327
|
this.submit();
|
|
1225
1328
|
}
|
|
1226
1329
|
});
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1330
|
+
const switchToKey = el(doc, "button", { class: "link-btn", type: "button" });
|
|
1331
|
+
switchToKey.textContent = t.useKey;
|
|
1332
|
+
switchToKey.addEventListener("click", () => {
|
|
1333
|
+
session.useLocal(false);
|
|
1334
|
+
localBtn.textContent = t.localOption;
|
|
1335
|
+
localBtn.disabled = false;
|
|
1336
|
+
switchToKey.remove();
|
|
1231
1337
|
});
|
|
1232
1338
|
if (session.mode === "local") {
|
|
1233
1339
|
localBtn.textContent = t.localActive;
|
|
1234
1340
|
localBtn.disabled = true;
|
|
1235
|
-
|
|
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);
|
|
1341
|
+
inBrowser.append(switchToKey);
|
|
1244
1342
|
}
|
|
1343
|
+
session.canRunLocally().then((ok) => {
|
|
1344
|
+
if (ok)
|
|
1345
|
+
return;
|
|
1346
|
+
localBtn.textContent = t.localOption;
|
|
1347
|
+
localBtn.disabled = true;
|
|
1348
|
+
localHint.textContent = t.localNoWebGPU;
|
|
1349
|
+
switchToKey.remove();
|
|
1350
|
+
});
|
|
1245
1351
|
const or = text(doc, "p", t.orKey, "or");
|
|
1352
|
+
inBrowser.hidden = or.hidden = !session.localAllowed;
|
|
1246
1353
|
const local = checkbox(doc, t.local);
|
|
1247
1354
|
const providerField = field(doc, t.provider, el(doc, "select"));
|
|
1248
1355
|
const providerSelect = providerField.control;
|
|
@@ -1379,6 +1486,13 @@ function tabStorage(op, key) {
|
|
|
1379
1486
|
} catch {}
|
|
1380
1487
|
return false;
|
|
1381
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
|
+
}
|
|
1382
1496
|
function el(doc, tag, attrs = {}) {
|
|
1383
1497
|
const node = doc.createElement(tag);
|
|
1384
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)
|
|
@@ -45,8 +47,12 @@ function optionsFromDataset(data, base) {
|
|
|
45
47
|
o.storageKey = data.storageKey;
|
|
46
48
|
if (data.open === "true")
|
|
47
49
|
o.open = true;
|
|
48
|
-
if (data.local === "
|
|
49
|
-
o.local =
|
|
50
|
+
if (data.local === "false")
|
|
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;
|