murmur-rag 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +27 -23
  2. package/dist/cdn/anthropic-ynz7717b.js +2 -0
  3. package/dist/cdn/chunk-hpx6rpea.js +1 -0
  4. package/dist/cdn/{embed-59r4jck6.js → embed-0m8xctqy.js} +3 -3
  5. package/dist/cdn/{embed-pnm9593k.js → embed-7pk91zq8.js} +2 -2
  6. package/dist/cdn/embed-dbkjhg16.js +83 -0
  7. package/dist/cdn/{embed-q6y6vgv1.js → embed-krrjjyf5.js} +4 -4
  8. package/dist/cdn/embed-n5nzhrf2.js +9 -0
  9. package/dist/cdn/{embed-hn0r8fmg.js → embed-rp9gb1qq.js} +2 -2
  10. package/dist/cdn/embed-xtkxyrpe.js +44 -0
  11. package/dist/cdn/google-k5kwa54v.js +14 -0
  12. package/dist/cdn/index-db4ne93m.js +1 -0
  13. package/dist/cdn/index-gyd02jvt.js +9 -0
  14. package/dist/cdn/index-qc8kgsdt.js +1 -0
  15. package/dist/cdn/index-t0qn2m4r.js +1 -0
  16. package/dist/cdn/index-y0faj89r.js +1 -0
  17. package/dist/cdn/mistral-p2qcnn42.js +3 -0
  18. package/dist/cdn/murmur-d6t6cjc7.js +5 -0
  19. package/dist/cdn/murmur.js +24 -5
  20. package/dist/cdn/openai-compatible-psnkrpxy.js +11 -0
  21. package/dist/cdn/openai-d3q1d6cz.js +18 -0
  22. package/dist/cdn/retriever-6qvn2maq.js +1 -0
  23. package/dist/cdn/transformers.web-xn16v7pz.js +46 -0
  24. package/dist/cli.js +2 -0
  25. package/dist/lib/chunks/{index-ynjdey8j.js → index-8h9c8ekq.js} +4 -2
  26. package/dist/lib/chunks/{index-6re28wc1.js → index-b77xqap3.js} +13 -1
  27. package/dist/lib/chunks/{index-10g918kt.js → index-p86sy3y4.js} +1 -1
  28. package/dist/lib/chunks/{index-4at214g0.js → index-y3v46yjp.js} +314 -27
  29. package/dist/lib/chunks/{murmur-c1c79eh8.js → murmur-t36hd4vt.js} +2 -2
  30. package/dist/lib/chunks/{retriever-h8nz6jsc.js → retriever-wxshtnm0.js} +1 -1
  31. package/dist/lib/embed.js +4 -2
  32. package/dist/lib/index.js +5 -3
  33. package/dist/lib/indexer/index.js +2 -0
  34. package/dist/lib/local/index.js +163 -0
  35. package/dist/lib/rag/index.js +13 -11
  36. package/dist/lib/widget/index.js +2 -2
  37. package/dist/types/local/index.d.ts +56 -0
  38. package/dist/types/rag/ground.d.ts +7 -0
  39. package/dist/types/rag/index.d.ts +1 -1
  40. package/dist/types/widget/element.d.ts +3 -0
  41. package/dist/types/widget/i18n.d.ts +14 -0
  42. package/dist/types/widget/session.d.ts +32 -1
  43. package/dist/types/widget/styles.d.ts +1 -1
  44. package/package.json +17 -3
  45. package/dist/cdn/anthropic-w7nkjk5e.js +0 -2
  46. package/dist/cdn/chunk-e0j28zmb.js +0 -1
  47. package/dist/cdn/embed-2xm309sb.js +0 -7
  48. package/dist/cdn/embed-n7jskdxd.js +0 -44
  49. package/dist/cdn/embed-sgjh2kg2.js +0 -83
  50. package/dist/cdn/google-qha5xyx4.js +0 -14
  51. package/dist/cdn/index-11c9cspb.js +0 -1
  52. package/dist/cdn/index-e5entetm.js +0 -1
  53. package/dist/cdn/index-hv290nbv.js +0 -9
  54. package/dist/cdn/index-kk9hgxdc.js +0 -1
  55. package/dist/cdn/mistral-16bekg8b.js +0 -3
  56. package/dist/cdn/murmur-bnm335nb.js +0 -5
  57. package/dist/cdn/openai-compatible-za7hc1fv.js +0 -11
  58. package/dist/cdn/openai-dhy4fp4c.js +0 -18
  59. package/dist/cdn/retriever-m8peqj5t.js +0 -1
package/dist/lib/embed.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  __chunks,
3
3
  mountMurmur2
4
- } from "./chunks/index-4at214g0.js";
5
- __chunks(import.meta.url,["3qtg95t7","4at214g0","h8nz6jsc","gp7czjd1","vb9smwta","kaeec41p","c1c79eh8","6re28wc1","10g918kt","1zk822m5","ynjdey8j"],[["./embed.js",1],["./chunks/index-4at214g0.js",7],["./chunks/retriever-h8nz6jsc.js",8],["./indexer/index.js",9],["./llm/index.js"],["./chunks/chunk-kaeec41p.js",9],["./chunks/murmur-c1c79eh8.js",10,8],["./chunks/index-6re28wc1.js"],["./chunks/index-10g918kt.js",7],["./chunks/index-1zk822m5.js"],["./chunks/index-ynjdey8j.js",8,7]],0);
4
+ } from "./chunks/index-y3v46yjp.js";
5
+ __chunks(import.meta.url,["jc4f9310","y3v46yjp","t36hd4vt","wxshtnm0","dj4321kh","vb9smwta","nb4hdy2t","kaeec41p","b77xqap3","8h9c8ekq","p86sy3y4","1zk822m5"],[["./embed.js",1],["./chunks/index-y3v46yjp.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) {
@@ -45,6 +45,8 @@ function optionsFromDataset(data, base) {
45
45
  o.storageKey = data.storageKey;
46
46
  if (data.open === "true")
47
47
  o.open = true;
48
+ if (data.local === "false")
49
+ o.local = false;
48
50
  return o;
49
51
  }
50
52
  function start() {
package/dist/lib/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  passagesOf2,
3
3
  bm25Retriever2
4
- } from "./chunks/index-10g918kt.js";
4
+ } from "./chunks/index-p86sy3y4.js";
5
5
  import {
6
6
  searchQuery2,
7
7
  createMurmur2
8
- } from "./chunks/index-ynjdey8j.js";
8
+ } from "./chunks/index-8h9c8ekq.js";
9
9
  import {
10
10
  MAX_CHUNK2,
11
11
  splitText2,
@@ -21,9 +21,10 @@ import {
21
21
  formatSources2,
22
22
  SOURCES_RULE2,
23
23
  groundingRules2,
24
+ cleanAnswer2,
24
25
  citedSources2,
25
26
  passageHref2
26
- } from "./chunks/index-6re28wc1.js";
27
+ } from "./chunks/index-b77xqap3.js";
27
28
  export {
28
29
  MAX_CHUNK2 as MAX_CHUNK,
29
30
  SOURCES_RULE2 as SOURCES_RULE,
@@ -31,6 +32,7 @@ export {
31
32
  buildIndex2 as buildIndex,
32
33
  chunkSections2 as chunkSections,
33
34
  citedSources2 as citedSources,
35
+ cleanAnswer2 as cleanAnswer,
34
36
  createMurmur2 as createMurmur,
35
37
  estimateTokens2 as estimateTokens,
36
38
  formatSources2 as formatSources,
@@ -44,6 +44,8 @@ function extractSections(doc, { root = DEFAULT_ROOT, exclude = DEFAULT_EXCLUDE }
44
44
  if (tag === "BR")
45
45
  cur.text += `
46
46
  `;
47
+ if ((tag === "TD" || tag === "TH") && !/(^|\n)\s*$/.test(cur.text))
48
+ cur.text += " | ";
47
49
  const block = BLOCK.test(tag);
48
50
  if (block)
49
51
  cur.text += `
@@ -0,0 +1,163 @@
1
+ // src/local/index.ts
2
+ var DEFAULT_LOCAL_MODEL = {
3
+ id: "onnx-community/LFM3-1.7B-ONNX",
4
+ dtype: "q4f16",
5
+ name: "LFM3 1.7B",
6
+ sizeMB: 1090
7
+ };
8
+ async function hasWebGPU() {
9
+ const gpu = globalThis.navigator?.gpu;
10
+ if (!gpu)
11
+ return false;
12
+ try {
13
+ return !!await gpu.requestAdapter();
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+ function chatMessages(prompt) {
19
+ return prompt.flatMap((m) => {
20
+ if (m.role === "system")
21
+ return [{ role: "system", content: m.content }];
22
+ if (m.role === "tool")
23
+ return [];
24
+ const text = m.content.map((part) => part.type === "text" ? part.text : "").join("").trim();
25
+ return text ? [{ role: m.role, content: text }] : [];
26
+ });
27
+ }
28
+ var loaded = new Map;
29
+ var listeners = new Map;
30
+ function loadLocalModel(spec = DEFAULT_LOCAL_MODEL, onProgress) {
31
+ const key = `${spec.id}@${spec.dtype}`;
32
+ const set = listeners.get(key) ?? new Set;
33
+ listeners.set(key, set);
34
+ if (onProgress)
35
+ set.add(onProgress);
36
+ let pending = loaded.get(key);
37
+ if (!pending) {
38
+ pending = (async () => {
39
+ if (!await hasWebGPU())
40
+ throw new LocalModelError("webgpu", "This browser has no WebGPU.");
41
+ const { pipeline } = await import("@huggingface/transformers");
42
+ const generator = await pipeline("text-generation", spec.id, {
43
+ dtype: spec.dtype,
44
+ device: "webgpu",
45
+ progress_callback: (info) => {
46
+ if (info.status !== "progress_total")
47
+ return;
48
+ const p = {
49
+ loaded: info.loaded ?? 0,
50
+ total: info.total ?? 0,
51
+ progress: Math.min(1, (info.progress ?? 0) / 100)
52
+ };
53
+ for (const listener of set)
54
+ listener(p);
55
+ }
56
+ });
57
+ return generator;
58
+ })();
59
+ pending.catch(() => loaded.delete(key));
60
+ loaded.set(key, pending);
61
+ }
62
+ return pending;
63
+ }
64
+
65
+ class LocalModelError extends Error {
66
+ code;
67
+ constructor(code, message) {
68
+ super(message);
69
+ this.code = code;
70
+ this.name = "LocalModelError";
71
+ }
72
+ }
73
+ var usage = (input, output) => ({
74
+ inputTokens: { total: input, noCache: input, cacheRead: undefined, cacheWrite: undefined },
75
+ outputTokens: { total: output, text: output, reasoning: undefined }
76
+ });
77
+ function localModel(options = {}) {
78
+ const spec = options.spec ?? DEFAULT_LOCAL_MODEL;
79
+ async function run(call, onText) {
80
+ const generator = await loadLocalModel(spec, options.onProgress);
81
+ const { TextStreamer, InterruptableStoppingCriteria, StoppingCriteriaList } = await import("@huggingface/transformers");
82
+ const stop = new InterruptableStoppingCriteria;
83
+ const criteria = new StoppingCriteriaList;
84
+ criteria.push(stop);
85
+ const onAbort = () => stop.interrupt();
86
+ call.abortSignal?.addEventListener("abort", onAbort, { once: true });
87
+ let tokens = 0;
88
+ const streamer = new TextStreamer(generator.tokenizer, {
89
+ skip_prompt: true,
90
+ skip_special_tokens: true,
91
+ decode_kwargs: { clean_up_tokenization_spaces: false },
92
+ callback_function: (text) => {
93
+ if (text)
94
+ onText(text);
95
+ },
96
+ token_callback_function: () => {
97
+ tokens++;
98
+ }
99
+ });
100
+ try {
101
+ await generator(chatMessages(call.prompt), {
102
+ max_new_tokens: call.maxOutputTokens ?? options.maxNewTokens ?? 512,
103
+ do_sample: true,
104
+ temperature: call.temperature ?? 0.1,
105
+ top_k: 50,
106
+ repetition_penalty: 1.1,
107
+ streamer,
108
+ stopping_criteria: criteria,
109
+ tokenizer_encode_kwargs: { enable_thinking: false }
110
+ });
111
+ } finally {
112
+ call.abortSignal?.removeEventListener("abort", onAbort);
113
+ }
114
+ return { tokens, interrupted: stop.interrupted };
115
+ }
116
+ return {
117
+ specificationVersion: "v4",
118
+ provider: "murmur-local",
119
+ modelId: spec.id,
120
+ supportedUrls: {},
121
+ async doGenerate(call) {
122
+ let text = "";
123
+ const { tokens, interrupted } = await run(call, (t) => {
124
+ text += t;
125
+ });
126
+ return {
127
+ content: [{ type: "text", text }],
128
+ finishReason: { unified: interrupted ? "other" : "stop", raw: undefined },
129
+ usage: usage(0, tokens),
130
+ warnings: []
131
+ };
132
+ },
133
+ async doStream(call) {
134
+ const stream = new ReadableStream({
135
+ start(controller) {
136
+ controller.enqueue({ type: "stream-start", warnings: [] });
137
+ controller.enqueue({ type: "text-start", id: "0" });
138
+ run(call, (delta) => controller.enqueue({ type: "text-delta", id: "0", delta })).then(({ tokens, interrupted }) => {
139
+ controller.enqueue({ type: "text-end", id: "0" });
140
+ controller.enqueue({
141
+ type: "finish",
142
+ finishReason: { unified: interrupted ? "other" : "stop", raw: undefined },
143
+ usage: usage(0, tokens)
144
+ });
145
+ controller.close();
146
+ }, (error) => {
147
+ controller.enqueue({ type: "error", error });
148
+ controller.close();
149
+ });
150
+ }
151
+ });
152
+ return { stream };
153
+ }
154
+ };
155
+ }
156
+ export {
157
+ DEFAULT_LOCAL_MODEL,
158
+ LocalModelError,
159
+ chatMessages,
160
+ hasWebGPU,
161
+ loadLocalModel,
162
+ localModel
163
+ };
@@ -1,25 +1,26 @@
1
1
  import"../chunks/index-fp1p1byg.js";
2
- import {
3
- MAX_CHUNK2,
4
- splitText2,
5
- slugify2,
6
- chunkSections2,
7
- markdownSections2,
8
- buildIndex2,
9
- isMurmurIndex2
10
- } from "../chunks/index-1zk822m5.js";
11
2
  import {
12
3
  passagesOf2,
13
4
  bm25Retriever2
14
- } from "../chunks/index-10g918kt.js";
5
+ } from "../chunks/index-p86sy3y4.js";
15
6
  import {
16
7
  estimateTokens2,
17
8
  formatSources2,
18
9
  SOURCES_RULE2,
19
10
  groundingRules2,
11
+ cleanAnswer2,
20
12
  citedSources2,
21
13
  passageHref2
22
- } from "../chunks/index-6re28wc1.js";
14
+ } from "../chunks/index-b77xqap3.js";
15
+ import {
16
+ MAX_CHUNK2,
17
+ splitText2,
18
+ slugify2,
19
+ chunkSections2,
20
+ markdownSections2,
21
+ buildIndex2,
22
+ isMurmurIndex2
23
+ } from "../chunks/index-1zk822m5.js";
23
24
  export {
24
25
  MAX_CHUNK2 as MAX_CHUNK,
25
26
  SOURCES_RULE2 as SOURCES_RULE,
@@ -27,6 +28,7 @@ export {
27
28
  buildIndex2 as buildIndex,
28
29
  chunkSections2 as chunkSections,
29
30
  citedSources2 as citedSources,
31
+ cleanAnswer2 as cleanAnswer,
30
32
  estimateTokens2 as estimateTokens,
31
33
  formatSources2 as formatSources,
32
34
  groundingRules2 as groundingRules,
@@ -8,8 +8,8 @@ import {
8
8
  MurmurChat2,
9
9
  defineMurmurChat2,
10
10
  mountMurmur2
11
- } from "../chunks/index-4at214g0.js";
12
- __chunks(import.meta.url,["1ykj7t4p","4at214g0","h8nz6jsc","gp7czjd1","vb9smwta","kaeec41p","c1c79eh8","6re28wc1","10g918kt","1zk822m5","ynjdey8j"],[["./index.js",1],["../chunks/index-4at214g0.js",7],["../chunks/retriever-h8nz6jsc.js",8],["../indexer/index.js",9],["../llm/index.js"],["../chunks/chunk-kaeec41p.js",9],["../chunks/murmur-c1c79eh8.js",10,8],["../chunks/index-6re28wc1.js"],["../chunks/index-10g918kt.js",7],["../chunks/index-1zk822m5.js"],["../chunks/index-ynjdey8j.js",8,7]],0);
11
+ } from "../chunks/index-y3v46yjp.js";
12
+ __chunks(import.meta.url,["jzqx319b","y3v46yjp","t36hd4vt","wxshtnm0","dj4321kh","vb9smwta","nb4hdy2t","kaeec41p","b77xqap3","8h9c8ekq","p86sy3y4","1zk822m5"],[["./index.js",1],["../chunks/index-y3v46yjp.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,
@@ -0,0 +1,56 @@
1
+ import type { LanguageModelV4, LanguageModelV4Prompt } from "@ai-sdk/provider";
2
+ /** A model that runs in the page on WebGPU, through Transformers.js. */
3
+ export interface LocalModelSpec {
4
+ /** Hugging Face repository of the ONNX weights. */
5
+ id: string;
6
+ /** Weights variant: q4f16 is the one meant for WebGPU. */
7
+ dtype: string;
8
+ /** Display name. */
9
+ name: string;
10
+ /** Approximate download, in megabytes, shown before the visitor agrees to it. */
11
+ sizeMB: number;
12
+ }
13
+ /**
14
+ * LFM3-1.7B by Liquid AI (October 2026), 4-bit weights with fp16 activations: the smallest
15
+ * text model released after mid-2026 with weights made for WebGPU. About 1.1 GB, downloaded
16
+ * once and kept in the browser's cache.
17
+ */
18
+ export declare const DEFAULT_LOCAL_MODEL: LocalModelSpec;
19
+ export interface LoadProgress {
20
+ /** Bytes loaded so far, across every file of the model. */
21
+ loaded: number;
22
+ /** Total bytes, once known. */
23
+ total: number;
24
+ /** 0 to 1. */
25
+ progress: number;
26
+ }
27
+ export interface LocalModelOptions {
28
+ /** Which model to run. Default {@link DEFAULT_LOCAL_MODEL}. */
29
+ spec?: LocalModelSpec;
30
+ /** Called while the weights download. */
31
+ onProgress?: (progress: LoadProgress) => void;
32
+ /** Longest answer, in tokens. Default 512. */
33
+ maxNewTokens?: number;
34
+ }
35
+ /** Whether this browser exposes a WebGPU adapter. */
36
+ export declare function hasWebGPU(): Promise<boolean>;
37
+ /** The chat messages a Transformers.js template takes, from an AI SDK prompt (text only). */
38
+ export declare function chatMessages(prompt: LanguageModelV4Prompt): {
39
+ role: string;
40
+ content: string;
41
+ }[];
42
+ type Generator = ((messages: unknown, options: Record<string, unknown>) => Promise<unknown>) & {
43
+ tokenizer: unknown;
44
+ };
45
+ /** Downloads (or reads from cache) and compiles the model; later calls share the same load. */
46
+ export declare function loadLocalModel(spec?: LocalModelSpec, onProgress?: (progress: LoadProgress) => void): Promise<Generator>;
47
+ export declare class LocalModelError extends Error {
48
+ readonly code: "webgpu" | "load";
49
+ constructor(code: "webgpu" | "load", message: string);
50
+ }
51
+ /**
52
+ * An AI SDK language model that runs in the page: pass it to `createMurmur({ model })`, or let
53
+ * the widget create it with `local: true`. Nothing leaves the browser once the weights are cached.
54
+ */
55
+ export declare function localModel(options?: LocalModelOptions): LanguageModelV4;
56
+ export {};
@@ -6,6 +6,13 @@ export declare function formatSources(passages: readonly Passage[]): string;
6
6
  export declare const SOURCES_RULE: string;
7
7
  /** The rules every answer follows; the site owner's prompt comes before them. */
8
8
  export declare function groundingRules(language?: string): string;
9
+ /**
10
+ * An answer as a visitor should read it, whatever the model: reasoning blocks and copied
11
+ * source delimiters removed (also half-written ones while streaming), citation variants
12
+ * (`[1, 2]`, `[Source 1]`, `【1】`) written as `[1][2]`, and a trailing "Sources: 1, 2"
13
+ * line dropped, since the interface lists the sources itself.
14
+ */
15
+ export declare function cleanAnswer(text: string): string;
9
16
  /** The source numbers an answer cites, in order of first use, keeping only those that were given. */
10
17
  export declare function citedSources(answer: string, count: number): number[];
11
18
  /** Link to a passage: its page, at its heading. */
@@ -1,4 +1,4 @@
1
1
  export { buildIndex, chunkSections, isMurmurIndex, MAX_CHUNK, markdownSections, type Section, slugify, splitText, } from "./chunk.js";
2
- export { citedSources, estimateTokens, formatSources, groundingRules, passageHref, SOURCES_RULE, } from "./ground.js";
2
+ export { citedSources, cleanAnswer, estimateTokens, formatSources, groundingRules, passageHref, SOURCES_RULE, } from "./ground.js";
3
3
  export { bm25Retriever, passagesOf, type Retriever, type SearchOptions } from "./retriever.js";
4
4
  export type { Chunk, IndexDoc, MurmurIndex, Passage } from "./types.js";
@@ -16,6 +16,7 @@ export declare class MurmurChat extends Base {
16
16
  private options;
17
17
  private session;
18
18
  private t;
19
+ private uiLang;
19
20
  private readonly root;
20
21
  private launcher;
21
22
  private panel;
@@ -48,6 +49,8 @@ export declare class MurmurChat extends Base {
48
49
  /** Draws an answer: its text with citation rings, then the sources it cites. */
49
50
  private fillAnswer;
50
51
  private submit;
52
+ /** Asks the visitor before downloading the in-browser model, or says why it cannot run. */
53
+ private showConsent;
51
54
  /** The visitor's model settings, drawn from llm-connect's store. */
52
55
  private showSetup;
53
56
  private hideSetup;
@@ -40,6 +40,20 @@ export interface Messages {
40
40
  searchModels: string;
41
41
  recommended: string;
42
42
  otherProviders: string;
43
+ localOption: string;
44
+ localOptionHint: string;
45
+ localActive: string;
46
+ orKey: string;
47
+ useKey: string;
48
+ localConsent: string;
49
+ localDownload: string;
50
+ localLoading: string;
51
+ localLoadingLabel: string;
52
+ localPreparing: string;
53
+ localNoWebGPU: string;
54
+ localLoadFailed: string;
55
+ gigabytes: string;
56
+ megabytes: string;
43
57
  }
44
58
  export declare const MESSAGES: Record<string, Messages>;
45
59
  /** The first supported language among the candidates (`fr-CA` matches `fr`), else English. */
@@ -1,6 +1,7 @@
1
1
  import type { LanguageModel } from "ai";
2
2
  import type { Turn } from "../core/murmur.js";
3
3
  import type { LlmConfig, LlmSettings, LlmSettingsStore } from "../llm/index.js";
4
+ import type { LoadProgress, LocalModelSpec } from "../local/index.js";
4
5
  import type { Retriever } from "../rag/retriever.js";
5
6
  import type { MurmurIndex, Passage } from "../rag/types.js";
6
7
  import type { Messages } from "./i18n.js";
@@ -29,13 +30,20 @@ export interface WidgetOptions {
29
30
  storageKey?: string;
30
31
  /** Open the panel on load. */
31
32
  open?: boolean;
33
+ /**
34
+ * The model that runs in the visitor's browser on WebGPU, the default way to answer when there
35
+ * is no proxy: LFM3 1.7B, about 1.1 GB downloaded once after the visitor agrees. Visitors can
36
+ * still connect their own key in the AI settings. Pass your own spec to run another model, or
37
+ * `false` to offer only the visitor's key.
38
+ */
39
+ local?: boolean | LocalModelSpec;
32
40
  }
33
41
  export interface ChatTurn extends Turn {
34
42
  passages?: Pick<Passage, "url" | "anchor" | "title" | "heading">[];
35
43
  cited?: number[];
36
44
  error?: string;
37
45
  }
38
- type Mode = "custom" | "proxy" | "visitor";
46
+ type Mode = "custom" | "local" | "proxy" | "visitor";
39
47
  /** Everything the widget does except drawing: index, model settings, history. */
40
48
  export declare class ChatSession {
41
49
  readonly options: WidgetOptions;
@@ -43,8 +51,29 @@ export declare class ChatSession {
43
51
  private engine;
44
52
  private storePromise;
45
53
  private readonly historyKey;
54
+ private readonly key;
55
+ /** Called while the in-browser model downloads. */
56
+ onProgress: ((progress: LoadProgress) => void) | null;
46
57
  constructor(options: WidgetOptions);
58
+ /**
59
+ * How this session answers: your own model, then the owner's proxy, then the visitor's own
60
+ * key if they chose one, else the model in the browser.
61
+ */
47
62
  get mode(): Mode;
63
+ /** Whether the in-browser model is offered: not with your own model or a proxy, nor `local: false`. */
64
+ get localAllowed(): boolean;
65
+ /**
66
+ * Before a question: a visitor who saved a key before the in-browser model became the default,
67
+ * and never chose between the two, keeps their key.
68
+ */
69
+ settleChoice(): Promise<void>;
70
+ /** The in-browser model this session would run. */
71
+ localSpec(): Promise<LocalModelSpec>;
72
+ /** The visitor picks the in-browser model, or their own key. */
73
+ useLocal(on: boolean): void;
74
+ /** Whether the visitor must still agree to download the in-browser model. */
75
+ get needsDownloadConsent(): boolean;
76
+ agreeToDownload(): void;
48
77
  persist(): void;
49
78
  clear(): void;
50
79
  /** The visitor's settings form, created on first use. */
@@ -52,6 +81,8 @@ export declare class ChatSession {
52
81
  /** The settings to call, or null when the visitor has to set up a model first. */
53
82
  settings(): Promise<LlmSettings | null>;
54
83
  needsSetup(): Promise<boolean>;
84
+ /** Whether a model can run in this browser at all. */
85
+ canRunLocally(): Promise<boolean>;
55
86
  private loadIndex;
56
87
  private createEngine;
57
88
  /** Asks a question; `onText` receives the answer so far. Resolves to the finished turn. */
@@ -3,4 +3,4 @@
3
3
  * custom properties on the element: --murmur-accent, --murmur-accent-ink, --murmur-font,
4
4
  * --murmur-radius, --murmur-z.
5
5
  */
6
- export declare const STYLES = "\n:host {\n --accent: var(--murmur-accent, #211c17);\n --accent-ink: var(--murmur-accent-ink, #f6efe0);\n --lamp: var(--murmur-lamp, #e0a032);\n --surface: #fbf8f1;\n --raised: #f2ece0;\n --ink: #211c17;\n --muted: #675c4f;\n --line: #e2d8c5;\n --focus: #b4541d;\n --danger: #a1301f;\n --radius: var(--murmur-radius, 16px);\n --shadow: 0 18px 48px -12px rgb(33 28 23 / 0.28), 0 4px 12px -4px rgb(33 28 23 / 0.16);\n --ease: cubic-bezier(0.16, 1, 0.3, 1);\n all: initial;\n position: fixed;\n z-index: var(--murmur-z, 2147483000);\n inset: auto 20px 20px auto;\n font-family: var(--murmur-font, inherit);\n font-size: 15px;\n line-height: 1.5;\n color: var(--ink);\n color-scheme: light;\n -webkit-font-smoothing: antialiased;\n}\n:host([position=\"bottom-left\"]) { inset: auto auto 20px 20px; }\n:host([inline]) { position: relative; inset: auto; z-index: auto; display: block; height: 100%; }\n:host([theme=\"dark\"]) { --surface: #1c1814; --raised: #26211b; --ink: #f2ead9; --muted: #b8ab95; --line: #3b342b; --focus: #f0a35c; --danger: #f08a73; color-scheme: dark; }\n@media (prefers-color-scheme: dark) {\n :host(:not([theme=\"light\"])) { --surface: #1c1814; --raised: #26211b; --ink: #f2ead9; --muted: #b8ab95; --line: #3b342b; --focus: #f0a35c; --danger: #f08a73; color-scheme: dark; }\n :host(:not([theme=\"light\"])) { --murmur-accent-default: #f2ead9; }\n}\n@media print { :host { display: none !important; } }\n* { box-sizing: border-box; }\n[hidden] { display: none !important; }\n::selection { background: color-mix(in oklab, var(--lamp) 45%, transparent); color: inherit; }\nbutton, input, select, textarea { font: inherit; color: inherit; }\nbutton { cursor: pointer; }\n:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }\na { color: inherit; text-underline-offset: 0.2em; text-decoration-thickness: 1px; }\n\n/* Launcher */\n.launcher {\n display: inline-flex; align-items: center; gap: 10px;\n height: 52px; padding: 0 20px 0 16px; border: 0; border-radius: 26px;\n background: var(--accent); color: var(--accent-ink);\n box-shadow: var(--shadow); font-weight: 600; letter-spacing: 0.01em;\n transition: transform 220ms var(--ease), box-shadow 220ms var(--ease);\n}\n.launcher:hover { transform: translateY(-2px); }\n.launcher:active { transform: translateY(0); }\n.launcher[aria-expanded=\"true\"] { display: none; }\n.lamp {\n width: 8px; height: 8px; border-radius: 50%; flex: none;\n background: var(--lamp); box-shadow: 0 0 0 3px color-mix(in oklab, var(--lamp) 28%, transparent);\n}\n.lamp.off { background: transparent; box-shadow: inset 0 0 0 1.5px var(--muted); }\n.lamp.busy { animation: blink 1.1s steps(2, jump-none) infinite; }\n@keyframes blink { 50% { opacity: 0.25; } }\n\n/* Panel */\n.panel {\n position: absolute; right: 0; bottom: 0;\n display: flex; flex-direction: column;\n width: min(400px, calc(100vw - 40px)); height: min(640px, calc(100dvh - 40px));\n background: var(--surface); border-radius: var(--radius); box-shadow: var(--shadow);\n overflow: hidden; transform-origin: bottom right;\n animation: rise 320ms var(--ease);\n}\n:host([position=\"bottom-left\"]) .panel { right: auto; left: 0; transform-origin: bottom left; }\n:host([inline]) .panel { position: static; width: 100%; height: 100%; box-shadow: none; animation: none; border-radius: inherit; }\n@keyframes rise { from { opacity: 0; transform: translateY(12px) scale(0.98); } }\n@media (max-width: 480px) {\n :host(:not([inline])) { inset: auto 12px 12px auto; }\n :host(:not([inline])[position=\"bottom-left\"]) { inset: auto auto 12px 12px; }\n :host(:not([inline])) .panel { position: fixed; inset: 0; width: 100%; height: 100dvh; border-radius: 0; }\n}\n\nheader {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 10px 14px 18px; background: var(--accent); color: var(--accent-ink);\n}\nheader h2 { flex: 1; margin: 0; font-size: 16px; font-weight: 650; letter-spacing: 0.005em; line-height: 1.25; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.icon-btn {\n display: inline-grid; place-items: center; width: 36px; height: 36px; flex: none;\n border: 0; border-radius: 10px; background: transparent; color: inherit;\n transition: background 160ms;\n}\n.icon-btn:hover { background: color-mix(in oklab, currentColor 14%, transparent); }\nheader .icon-btn:focus-visible { outline-color: var(--accent-ink); }\n\n/* Conversation */\n.log {\n flex: 1; overflow-y: auto; overscroll-behavior: contain;\n padding: 18px 18px 8px; display: flex; flex-direction: column; gap: 16px;\n scrollbar-width: thin; scrollbar-color: var(--line) transparent;\n}\n.msg { max-width: 100%; overflow-wrap: anywhere; }\n.msg p { margin: 0 0 0.6em; }\n.msg p:last-child { margin-bottom: 0; }\n.msg ul, .msg ol { margin: 0 0 0.6em; padding-left: 1.3em; }\n.msg li + li { margin-top: 0.2em; }\n.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; }\n.msg pre { margin: 0 0 0.6em; padding: 10px 12px; background: var(--raised); border-radius: 10px; overflow-x: auto; }\n.msg pre code { background: none; padding: 0; }\n.user {\n align-self: flex-end; max-width: 85%;\n padding: 9px 14px; border-radius: 16px 16px 4px 16px;\n background: var(--accent); color: var(--accent-ink); white-space: pre-wrap;\n}\n.bot { align-self: stretch; }\n.greeting { color: var(--muted); }\n\n.cite {\n display: inline-grid; place-items: center; min-width: 1.45em; height: 1.45em; padding: 0 0.3em;\n margin: 0 0.1em; vertical-align: 0.12em;\n border-radius: 1em; box-shadow: inset 0 0 0 1.25px currentColor;\n font-size: 0.72em; font-weight: 700; line-height: 1; text-decoration: none;\n font-variant-numeric: tabular-nums; color: var(--muted);\n transition: background 160ms, color 160ms;\n}\n.cite:hover, .cite:focus-visible { background: var(--lamp); color: #211c17; box-shadow: none; }\n\n.sources { margin: 12px 0 0; padding: 0; list-style: none; display: grid; gap: 2px; }\n.sources-label { margin: 18px 0 2px; font-size: 12px; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }\n.sources a {\n display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: 10px;\n padding: 7px 10px; margin: 0 -10px; border-radius: 10px; text-decoration: none;\n}\n.sources a:hover { background: var(--raised); }\n.sources .cite { margin: 0; }\n.sources .where { min-width: 0; font-size: 14px; line-height: 1.35; }\n.sources .where span { color: var(--muted); }\n\n.status { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 14px; }\n.error { color: var(--danger); }\n.error p { margin: 0 0 8px; }\n.text-btn {\n display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 9px;\n border: 0; background: var(--raised); color: var(--ink); font-size: 14px; font-weight: 600;\n}\n.text-btn:hover { background: var(--line); }\n\n/* Composer */\nform.composer {\n display: flex; align-items: flex-end; gap: 8px; margin: 8px 12px 0; padding: 6px 6px 6px 14px;\n border-radius: 14px; background: var(--raised);\n box-shadow: inset 0 0 0 1px var(--line); transition: box-shadow 160ms;\n}\nform.composer:focus-within { box-shadow: inset 0 0 0 1.5px var(--focus); }\ntextarea {\n flex: 1; min-height: 38px; max-height: 140px; padding: 8px 0; resize: none;\n border: 0; background: transparent; outline: none; line-height: 1.4;\n}\ntextarea:focus-visible { outline: none; }\ntextarea::placeholder, input::placeholder { color: var(--muted); opacity: 1; }\n.send {\n display: inline-grid; place-items: center; width: 38px; height: 38px; flex: none;\n border: 0; border-radius: 10px; background: var(--accent); color: var(--accent-ink);\n transition: opacity 160ms;\n}\n.send:disabled { opacity: 0.35; cursor: default; }\nfooter { padding: 8px 18px 12px; font-size: 12px; color: var(--muted); text-align: center; }\nfooter a { text-decoration: none; }\nfooter a:hover { text-decoration: underline; }\n\n/* Settings */\n.setup { flex: 1; overflow-y: auto; padding: 18px; display: grid; gap: 16px; align-content: start; }\n.setup p.intro { margin: 0; color: var(--muted); font-size: 14px; }\n.field { display: grid; gap: 6px; }\n.field > span { font-size: 13px; font-weight: 650; }\n.field small { color: var(--muted); font-size: 12.5px; }\n.field input:not([type=\"checkbox\"]), .field select {\n width: 100%; height: 40px; padding: 0 12px; border-radius: 10px;\n border: 0; background: var(--raised); box-shadow: inset 0 0 0 1px var(--line);\n}\n.field input:focus-visible, .field select:focus-visible { outline: none; box-shadow: inset 0 0 0 1.5px var(--focus); }\n.check { display: flex; align-items: center; gap: 10px; font-size: 14px; }\n.check input { width: 18px; height: 18px; accent-color: var(--accent); margin: 0; }\n.presets { display: flex; gap: 8px; flex-wrap: wrap; }\n.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n.primary {\n display: inline-flex; align-items: center; justify-content: center; gap: 8px;\n height: 42px; padding: 0 18px; border: 0; border-radius: 11px;\n background: var(--accent); color: var(--accent-ink); font-weight: 650;\n}\n.primary:disabled { opacity: 0.4; cursor: default; }\n.result { font-size: 14px; }\n.result.ok { color: inherit; }\n.result.ok::before { content: \"\"; display: inline-block; width: 8px; height: 8px; margin-right: 8px; border-radius: 50%; background: var(--lamp); }\n.result.fail { color: var(--danger); }\n.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; }\n\n.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n\n@media (prefers-reduced-motion: reduce) {\n *, .panel { animation: none !important; transition: none !important; }\n}\n";
6
+ export declare const STYLES = "\n:host {\n --accent: var(--murmur-accent, #211c17);\n --accent-ink: var(--murmur-accent-ink, #f6efe0);\n --lamp: var(--murmur-lamp, #e0a032);\n --surface: #fbf8f1;\n --raised: #f2ece0;\n --ink: #211c17;\n --muted: #675c4f;\n --line: #e2d8c5;\n --focus: #b4541d;\n --danger: #a1301f;\n --radius: var(--murmur-radius, 16px);\n --shadow: 0 18px 48px -12px rgb(33 28 23 / 0.28), 0 4px 12px -4px rgb(33 28 23 / 0.16);\n --ease: cubic-bezier(0.16, 1, 0.3, 1);\n all: initial;\n position: fixed;\n z-index: var(--murmur-z, 2147483000);\n inset: auto 20px 20px auto;\n font-family: var(--murmur-font, inherit);\n font-size: 15px;\n line-height: 1.5;\n color: var(--ink);\n color-scheme: light;\n -webkit-font-smoothing: antialiased;\n}\n:host([position=\"bottom-left\"]) { inset: auto auto 20px 20px; }\n:host([inline]) { position: relative; inset: auto; z-index: auto; display: block; height: 100%; }\n:host([theme=\"dark\"]) { --surface: #1c1814; --raised: #26211b; --ink: #f2ead9; --muted: #b8ab95; --line: #3b342b; --focus: #f0a35c; --danger: #f08a73; color-scheme: dark; }\n@media (prefers-color-scheme: dark) {\n :host(:not([theme=\"light\"])) { --surface: #1c1814; --raised: #26211b; --ink: #f2ead9; --muted: #b8ab95; --line: #3b342b; --focus: #f0a35c; --danger: #f08a73; color-scheme: dark; }\n :host(:not([theme=\"light\"])) { --murmur-accent-default: #f2ead9; }\n}\n@media print { :host { display: none !important; } }\n* { box-sizing: border-box; }\n[hidden] { display: none !important; }\n::selection { background: color-mix(in oklab, var(--lamp) 45%, transparent); color: inherit; }\nbutton, input, select, textarea { font: inherit; color: inherit; }\nbutton { cursor: pointer; }\n:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }\na { color: inherit; text-underline-offset: 0.2em; text-decoration-thickness: 1px; }\n\n/* Launcher */\n.launcher {\n display: inline-flex; align-items: center; gap: 10px;\n height: 52px; padding: 0 20px 0 16px; border: 0; border-radius: 26px;\n background: var(--accent); color: var(--accent-ink);\n box-shadow: var(--shadow); font-weight: 600; letter-spacing: 0.01em;\n transition: transform 220ms var(--ease), box-shadow 220ms var(--ease);\n}\n.launcher:hover { transform: translateY(-2px); }\n.launcher:active { transform: translateY(0); }\n.launcher[aria-expanded=\"true\"] { display: none; }\n.lamp {\n width: 8px; height: 8px; border-radius: 50%; flex: none;\n background: var(--lamp); box-shadow: 0 0 0 3px color-mix(in oklab, var(--lamp) 28%, transparent);\n}\n.lamp.off { background: transparent; box-shadow: inset 0 0 0 1.5px var(--muted); }\n.lamp.busy { animation: blink 1.1s steps(2, jump-none) infinite; }\n@keyframes blink { 50% { opacity: 0.25; } }\n\n/* Panel */\n.panel {\n position: absolute; right: 0; bottom: 0;\n display: flex; flex-direction: column;\n width: min(400px, calc(100vw - 40px)); height: min(640px, calc(100dvh - 40px));\n background: var(--surface); border-radius: var(--radius); box-shadow: var(--shadow);\n overflow: hidden; transform-origin: bottom right;\n animation: rise 320ms var(--ease);\n}\n:host([position=\"bottom-left\"]) .panel { right: auto; left: 0; transform-origin: bottom left; }\n:host([inline]) .panel { position: static; width: 100%; height: 100%; box-shadow: none; animation: none; border-radius: inherit; }\n@keyframes rise { from { opacity: 0; transform: translateY(12px) scale(0.98); } }\n@media (max-width: 480px) {\n :host(:not([inline])) { inset: auto 12px 12px auto; }\n :host(:not([inline])[position=\"bottom-left\"]) { inset: auto auto 12px 12px; }\n :host(:not([inline])) .panel { position: fixed; inset: 0; width: 100%; height: 100dvh; border-radius: 0; }\n}\n\nheader {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 10px 14px 18px; background: var(--accent); color: var(--accent-ink);\n}\nheader h2 { flex: 1; margin: 0; font-size: 16px; font-weight: 650; letter-spacing: 0.005em; line-height: 1.25; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.icon-btn {\n display: inline-grid; place-items: center; width: 36px; height: 36px; flex: none;\n border: 0; border-radius: 10px; background: transparent; color: inherit;\n transition: background 160ms;\n}\n.icon-btn:hover { background: color-mix(in oklab, currentColor 14%, transparent); }\nheader .icon-btn:focus-visible { outline-color: var(--accent-ink); }\n\n/* Conversation */\n.log {\n flex: 1; overflow-y: auto; overscroll-behavior: contain;\n padding: 18px 18px 8px; display: flex; flex-direction: column; gap: 16px;\n scrollbar-width: thin; scrollbar-color: var(--line) transparent;\n}\n.msg { max-width: 100%; overflow-wrap: anywhere; }\n.msg p { margin: 0 0 0.6em; }\n.msg p:last-child { margin-bottom: 0; }\n.msg ul, .msg ol { margin: 0 0 0.6em; padding-left: 1.3em; }\n.msg li + li { margin-top: 0.2em; }\n.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; }\n.msg pre { margin: 0 0 0.6em; padding: 10px 12px; background: var(--raised); border-radius: 10px; overflow-x: auto; }\n.msg pre code { background: none; padding: 0; }\n.msg .table { margin: 0 0 0.6em; overflow-x: auto; border-radius: 10px; box-shadow: inset 0 0 0 1px var(--line); }\n.msg table { width: 100%; border-collapse: collapse; font-size: 14px; }\n.msg th, .msg td { padding: 6px 10px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--line); }\n.msg tr:last-child td { border-bottom: 0; }\n.msg th { font-weight: 650; background: var(--raised); }\n.msg blockquote { margin: 0 0 0.6em; padding: 2px 0 2px 12px; color: var(--muted); box-shadow: inset 1px 0 0 var(--line); }\n.msg hr { margin: 0.8em 0; border: 0; border-top: 1px solid var(--line); }\n.user {\n align-self: flex-end; max-width: 85%;\n padding: 9px 14px; border-radius: 16px 16px 4px 16px;\n background: var(--accent); color: var(--accent-ink); white-space: pre-wrap;\n}\n.bot { align-self: stretch; }\n.greeting { color: var(--muted); }\n\n.cite {\n display: inline-grid; place-items: center; min-width: 1.45em; height: 1.45em; padding: 0 0.3em;\n margin: 0 0.1em; vertical-align: 0.12em;\n border-radius: 1em; box-shadow: inset 0 0 0 1.25px currentColor;\n font-size: 0.72em; font-weight: 700; line-height: 1; text-decoration: none;\n font-variant-numeric: tabular-nums; color: var(--muted);\n transition: background 160ms, color 160ms;\n}\n.cite:hover, .cite:focus-visible { background: var(--lamp); color: #211c17; box-shadow: none; }\n\n.sources { margin: 12px 0 0; padding: 0; list-style: none; display: grid; gap: 2px; }\n.sources-label { margin: 18px 0 2px; font-size: 12px; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }\n.sources a {\n display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: 10px;\n padding: 7px 10px; margin: 0 -10px; border-radius: 10px; text-decoration: none;\n}\n.sources a:hover { background: var(--raised); }\n.sources .cite { margin: 0; }\n.sources .where { min-width: 0; font-size: 14px; line-height: 1.35; }\n.sources .where span { color: var(--muted); }\n\n.status { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 14px; }\n.error { color: var(--danger); }\n.error p { margin: 0 0 8px; }\n.text-btn {\n display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 9px;\n border: 0; background: var(--raised); color: var(--ink); font-size: 14px; font-weight: 600;\n}\n.text-btn:hover { background: var(--line); }\n\n/* Composer */\nform.composer {\n display: flex; align-items: flex-end; gap: 8px; margin: 8px 12px 0; padding: 6px 6px 6px 14px;\n border-radius: 14px; background: var(--raised);\n box-shadow: inset 0 0 0 1px var(--line); transition: box-shadow 160ms;\n}\nform.composer:focus-within { box-shadow: inset 0 0 0 1.5px var(--focus); }\ntextarea {\n flex: 1; min-height: 38px; max-height: 140px; padding: 8px 0; resize: none;\n border: 0; background: transparent; outline: none; line-height: 1.4;\n}\ntextarea:focus-visible { outline: none; }\ntextarea::placeholder, input::placeholder { color: var(--muted); opacity: 1; }\n.send {\n display: inline-grid; place-items: center; width: 38px; height: 38px; flex: none;\n border: 0; border-radius: 10px; background: var(--accent); color: var(--accent-ink);\n transition: opacity 160ms;\n}\n.send:disabled { opacity: 0.35; cursor: default; }\nfooter { padding: 8px 18px 12px; font-size: 12px; color: var(--muted); text-align: center; }\nfooter a { text-decoration: none; }\nfooter a:hover { text-decoration: underline; }\n\n/* Settings */\n.setup { flex: 1; overflow-y: auto; padding: 18px; display: grid; gap: 16px; align-content: start; }\n.setup p.intro { margin: 0; color: var(--muted); font-size: 14px; }\n.field { display: grid; gap: 6px; }\n.field > span { font-size: 13px; font-weight: 650; }\n.field small { color: var(--muted); font-size: 12.5px; }\n.field input:not([type=\"checkbox\"]), .field select {\n width: 100%; height: 40px; padding: 0 12px; border-radius: 10px;\n border: 0; background: var(--raised); box-shadow: inset 0 0 0 1px var(--line);\n}\n.field input:focus-visible, .field select:focus-visible { outline: none; box-shadow: inset 0 0 0 1.5px var(--focus); }\n.check { display: flex; align-items: center; gap: 10px; font-size: 14px; }\n.check input { width: 18px; height: 18px; accent-color: var(--accent); margin: 0; }\n.presets { display: flex; gap: 8px; flex-wrap: wrap; }\n.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n.primary {\n display: inline-flex; align-items: center; justify-content: center; gap: 8px;\n height: 42px; padding: 0 18px; border: 0; border-radius: 11px;\n background: var(--accent); color: var(--accent-ink); font-weight: 650;\n}\n.primary:disabled { opacity: 0.4; cursor: default; }\n.result { font-size: 14px; }\n.result.ok { color: inherit; }\n.result.ok::before { content: \"\"; display: inline-block; width: 8px; height: 8px; margin-right: 8px; border-radius: 50%; background: var(--lamp); }\n.result.fail { color: var(--danger); }\n.local-choice { display: grid; gap: 6px; padding: 14px; border-radius: 12px; background: var(--raised); }\n.local-choice small { color: var(--muted); font-size: 12.5px; }\n.local-choice .link-btn { margin-top: 2px; }\np.or { margin: 0; color: var(--muted); font-size: 13px; text-align: center; }\n.consent { display: grid; gap: 10px; padding: 14px; border-radius: 12px; background: var(--raised); }\n.consent p { margin: 0; }\n.status .grow { display: grid; gap: 6px; flex: 1; }\nprogress { width: 100%; height: 6px; appearance: none; border: 0; border-radius: 3px; background: var(--line); overflow: hidden; }\nprogress::-webkit-progress-bar { background: var(--line); }\nprogress::-webkit-progress-value { background: var(--lamp); }\nprogress::-moz-progress-bar { background: var(--lamp); }\n.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; }\n\n.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n\n@media (prefers-reduced-motion: reduce) {\n *, .panel { animation: none !important; transition: none !important; }\n}\n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "murmur-rag",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Embeddable AI chat for static sites: one script tag, a mini RAG over your own pages, any LLM through llm-connect (your proxy or the visitor's own key). No backend.",
5
5
  "author": "maxgfr",
6
6
  "license": "MIT",
@@ -45,6 +45,10 @@
45
45
  "types": "./dist/types/llm/index.d.ts",
46
46
  "default": "./dist/lib/llm/index.js"
47
47
  },
48
+ "./local": {
49
+ "types": "./dist/types/local/index.d.ts",
50
+ "default": "./dist/lib/local/index.js"
51
+ },
48
52
  "./widget": {
49
53
  "types": "./dist/types/widget/index.d.ts",
50
54
  "default": "./dist/lib/widget/index.js"
@@ -103,7 +107,8 @@
103
107
  "linkedom": "^0.18.14",
104
108
  "llm-connect": "^2.5.0",
105
109
  "llm-models": "^1.14.0",
106
- "minisearch": "^7.2.0"
110
+ "minisearch": "^7.2.0",
111
+ "@ai-sdk/provider": "^4.0.34"
107
112
  },
108
113
  "devDependencies": {
109
114
  "@axe-core/playwright": "^4.14.0",
@@ -113,6 +118,15 @@
113
118
  "@types/bun": "^1.4.4",
114
119
  "conventional-changelog-conventionalcommits": "^9",
115
120
  "semantic-release": "^25.0.9",
116
- "typescript": "^7.0.2"
121
+ "typescript": "^7.0.2",
122
+ "@huggingface/transformers": "^4.3.2"
123
+ },
124
+ "peerDependencies": {
125
+ "@huggingface/transformers": "^4.3.2"
126
+ },
127
+ "peerDependenciesMeta": {
128
+ "@huggingface/transformers": {
129
+ "optional": true
130
+ }
117
131
  }
118
132
  }