@degreesign/chat 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -85,6 +85,7 @@ import {
85
85
  ChatModelId,
86
86
  LlmRole,
87
87
  createChat,
88
+ preloadModel,
88
89
  } from "@degreesign/chat";
89
90
 
90
91
  const messages = [
@@ -92,6 +93,9 @@ const messages = [
92
93
  { role: LlmRole.user, content: "Hello" },
93
94
  ];
94
95
 
96
+ // Optional: download and load the model before the first send.
97
+ await preloadModel({ model: ChatModelId.qwen3_vl_2b });
98
+
95
99
  const cancel = createChat({
96
100
  model: ChatModelId.qwen3_vl_2b,
97
101
  messages,
@@ -106,8 +110,10 @@ const cancel = createChat({
106
110
  cancel();
107
111
  ```
108
112
 
109
- There is no promise and no controller object. Every result arrives through the
110
- three callbacks, and `onStatus` reports `requesting: false` when the reply ends.
113
+ There is no promise and no controller object on `createChat`. Every result
114
+ arrives through the three callbacks, and `onStatus` reports `requesting: false`
115
+ when the reply ends. `preloadModel` is the one promise: it resolves once the
116
+ model is loaded, or `false` when it cannot load.
111
117
 
112
118
  Attach images to a user turn with `images: [dataUrl]`. The package sends them
113
119
  only to a model with vision support, center-crops each image to a square,
@@ -120,6 +126,7 @@ the call and appends the assistant message after the last `onResponse`.
120
126
 
121
127
  ```ts
122
128
  createChat(input: ChatInput): CancelChat
129
+ preloadModel(input: ChatPreloadInput): Promise<boolean>
123
130
  ```
124
131
 
125
132
  Call it once per response. A new call aborts the previous stream. When the
@@ -148,6 +155,30 @@ requested model is already loaded, no download or load happens.
148
155
 
149
156
  The model stays loaded between calls until a cancel frees it.
150
157
 
158
+ ### Preload
159
+
160
+ ```ts
161
+ preloadModel(input: ChatPreloadInput): Promise<boolean>
162
+ ```
163
+
164
+ Call it before the first `createChat` to download and load the model early. It
165
+ resolves `true` when the runtime is ready and `false` when the model is unknown,
166
+ unsupported or failed. A later `createChat` for the same model reuses the loaded
167
+ runtime, and either call joins a load already in flight.
168
+
169
+ `ChatPreloadInput` is the subset of `ChatInput` that a preload needs:
170
+
171
+ | Field | Description |
172
+ | ----- | ----------- |
173
+ | `model` | `ChatModelId` to download and load. |
174
+ | `limits?` | Partial `LlmLimits`; `maxThreads` applies when the model loads. |
175
+ | `logging?` | Whether package and engine console logs are written. Defaults to `false`. |
176
+ | `onStatus?` | Receives a `ChatStatusSnapshot` on every progress and state change. |
177
+
178
+ The final snapshot reports `ready: true`, `phase: LlmPhase.load` and
179
+ `percent: 100` on success, or an `error` with `availability: ChatStatus.offline`
180
+ on failure. A preload owns no cancel function; a new load replaces it.
181
+
151
182
  ## Status
152
183
 
153
184
  `ChatStatusSnapshot` is the only lifecycle signal:
@@ -230,6 +261,26 @@ The CPU engine derives a raw thread count from the device, then caps it at
230
261
  `limits.maxThreads`, default 2. The cap is applied after the raw maximum, so the
231
262
  engine never exceeds the device or the caller budget.
232
263
 
264
+ Threads only take effect when the host page is cross-origin isolated. Serve it
265
+ with both headers:
266
+
267
+ ```
268
+ Cross-Origin-Opener-Policy: same-origin
269
+ Cross-Origin-Embedder-Policy: require-corp
270
+ ```
271
+
272
+ Then assert `crossOriginIsolated === true` in the page. Without isolation the
273
+ engine falls back to one thread and image encoding becomes much slower, since
274
+ the projector runs on the CPU.
275
+
276
+ Under `require-corp` every cross-origin subresource must be CORS enabled or
277
+ carry `Cross-Origin-Resource-Policy`. Hugging Face downloads already qualify;
278
+ third party images, fonts and embeds need `crossorigin="anonymous"` or must be
279
+ self hosted. Chromium also accepts
280
+ `Cross-Origin-Embedder-Policy: credentialless` when those assets cannot be
281
+ changed. A page inside an iframe is isolated only when every ancestor sets the
282
+ same headers.
283
+
233
284
  Only one model stays on device. Loading a new model unloads the previous runtime
234
285
  and deletes other model caches. A quota failure purges every model cache before
235
286
  retrying, so a half written download never blocks the next attempt.
@@ -254,14 +305,15 @@ assembly, budgets, status state and cancellation.
254
305
 
255
306
  The wllama engine needs a browser worker and wasm runtime. In Node,
256
307
  `createChat` reports `LlmStatus.unsupported` and `ChatStatus.offline` through
257
- `onStatus`, and the reply callbacks stay silent.
308
+ `onStatus`, and the reply callbacks stay silent. `preloadModel` resolves `false`
309
+ and reports the same unsupported status.
258
310
 
259
311
  ```js
260
- const { createChat, ChatModelId, LlmRole } = require("@degreesign/chat");
312
+ const { createChat, preloadModel, ChatModelId, LlmRole } = require("@degreesign/chat");
261
313
  ```
262
314
 
263
315
  ```js
264
- import { createChat, ChatModelId, LlmRole } from "@degreesign/chat";
316
+ import { createChat, preloadModel, ChatModelId, LlmRole } from "@degreesign/chat";
265
317
  ```
266
318
 
267
319
  The Node entries are `dist/node/chat.node.min.js` for `require` and
@@ -306,6 +358,7 @@ const useReply = () => {
306
358
  | Type | Description |
307
359
  | ---- | ----------- |
308
360
  | `ChatInput` | One request: model, messages, budgets, reasoning and callbacks. |
361
+ | `ChatPreloadInput` | Model, limits, logging and status, the subset `preloadModel` needs. |
309
362
  | `CancelChat` | The function returned by `createChat`. |
310
363
  | `ChatMessage` | One prompt turn, `role`, `content` and optional `images`. |
311
364
  | `ChatStatusSnapshot` | Availability, engine status, backend, progress and request state. |
@@ -1,2 +1,2 @@
1
1
  /*! For license information please see chat.min.mjs.LICENSE.txt */
2
- var e={};const t={};function n(a){const o=t[a];if(void 0!==o)return o.exports;const r=t[a]={exports:{}};return e[a](r,r.exports,n),r.exports}var a,o,r,s,i,c;n.m=e,n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.f={},n.e=e=>{const t=[];return n.f.j(e,t),Promise.all(t)},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{const e={924:()=>import("./engine-wllama.min.mjs")},t={792:0},a=e=>{let{__webpack_esm_ids__:a,__webpack_esm_modules__:o,__webpack_esm_runtime__:r}=e;var s,i,c=0;for(s in o)n.o(o,s)&&(n.m[s]=o[s]);for(r&&r(n);c<a.length;c++)i=a[c],n.o(t,i)&&t[i]&&t[i][0](),t[i]=0};n.f.j=(o,r)=>{let s=n.o(t,o)?t[o]:void 0;if(0!==s)if(s)r.push(s[1]);else{let n=e[o]().then(a,e=>{throw 0!==t[o]&&(t[o]=void 0),e});n=Promise.race([n,new Promise(e=>s=t[o]=[e])]),r.push(s[1]=n)}}})(),function(e){e.system="system",e.user="user",e.assistant="assistant"}(a||(a={})),function(e){e[e.online=0]="online",e[e.away=1]="away",e[e.offline=2]="offline"}(o||(o={})),function(e){e[e.idle=0]="idle",e[e.downloading=1]="downloading",e[e.ready=2]="ready",e[e.error=3]="error",e[e.unsupported=4]="unsupported"}(r||(r={})),function(e){e.cpu="cpu",e.none="none"}(s||(s={})),function(e){e.download="download",e.load="load"}(i||(i={})),function(e){e.qwen3_vl_2b="qwen3-vl-2b"}(c||(c={}));const l=[{id:c.qwen3_vl_2b,reasoning:!0,cpu:{repo:"Qwen/Qwen3-VL-2B-Thinking-GGUF",file:"Qwen3VL-2B-Thinking-Q4_K_M.gguf",mmproj:{file:"mmproj-Qwen3VL-2B-Thinking-Q8_0.gguf"}}}],d=e=>e?l.find(t=>t.id==e):void 0,g=e=>!!e.cpu.mmproj,u=new URL("./../assets/wllama/wllama.wasm",import.meta.url).href,m=new URL("./../assets/wllama/compat/wllama.wasm",import.meta.url).href,f=new URL("./../assets/wllama/compat/wllama.js",import.meta.url).href,p=288,h=async e=>{if(!e||"function"!=typeof fetch||"function"!=typeof createImageBitmap)return e;try{const t=await fetch(e),n=await createImageBitmap(await t.blob()),a=Math.min(n.width,n.height),o=Math.floor((n.width-a)/2),r=Math.floor((n.height-a)/2),s=((e,t)=>{if("undefined"!=typeof OffscreenCanvas)return new OffscreenCanvas(e,t);if("undefined"==typeof document)return;const n=document.createElement("canvas");return n.width=e,n.height=t,n})(p,p),i=s?s.getContext("2d"):null;if(!s||!i||a<1)return n.close(),e;if(i.drawImage(n,o,r,a,a,0,0,p,p),n.close(),(e=>"undefined"!=typeof OffscreenCanvas&&e instanceof OffscreenCanvas)(s)){const t=await s.convertToBlob({type:"image/jpeg",quality:.85});return await(e=>new Promise(t=>{if("undefined"==typeof FileReader)return void t("");const n=new FileReader;n.onerror=()=>t(""),n.onload=()=>t(`${n.result}`),n.readAsDataURL(e)}))(t)||e}return s.toDataURL("image/jpeg",.85)}catch{return e}},w={maxReplyTokens:200,maxThinkingTokens:64,maxReasoningTokens:150,maxImages:2,maxContentLength:1e3,promptCharBudget:1e4,maxThreads:2},y=(e,t,n)=>"number"==typeof e&&Number.isFinite(e)&&e>=n?Math.floor(e):t,x=["<|channel>thought\n","<|channel>thought","..."],k=["<channel|>","..."],v=(e,t)=>t.length?[...e?[{type:"text",text:e}]:[],...t.map(e=>({type:"image",url:e}))]:e,b=e=>"string"==typeof e?e?[{type:"text",text:e}]:[]:e,_=(e,t)=>{if("string"==typeof e&&"string"==typeof t)return`${e}\n${t}`;const n=[];for(const a of[...b(e),{type:"text",text:"\n"},...b(t)]){const e=n[n.length-1];"text"==a.type&&"text"==e?.type?e.text+=a.text:n.push(a)}return n},T=async({model:e,logging:t,onReason:n,onText:a,start:o,stopped:r})=>{let s="",i="",c="";const l=((e,t)=>{let n="",a=!1;const o=(e,t)=>{let n=-1,a="";for(const o of t){const t=e.indexOf(o);t>=0&&(n<0||t<n)&&(n=t,a=o)}return{at:n,tag:a}},r=(e,t)=>{let n="";for(const a of t)for(let t=Math.min(e.length,a.length-1);t>n.length;t--)if(e.endsWith(a.slice(0,t))){n=e.slice(e.length-t);break}return n};return{feed:s=>{let i=n+s;for(n="";i;){if(a){const{at:t,tag:s}=o(i,k);if(t>=0){t&&e(i.slice(0,t)),i=i.slice(t+s.length),a=!1;continue}const c=r(i,k);e(i.slice(0,i.length-c.length)),n=c;break}{const{at:e,tag:s}=o(i,x);if(e>=0){e&&t(i.slice(0,e)),i=i.slice(e+s.length),i.startsWith("\n")&&(i=i.slice(1)),a=!0;continue}const c=r(i,x);t(i.slice(0,i.length-c.length)),n=c;break}}},flush:()=>{n&&(a?e(n):t(n),n="")}}})(e=>{i+=e,n(e)},e=>{s+=e,a(e)});try{const e=await o();for await(const t of e){if(r())break;const e=t?.choices?.[0],a=e?.delta,o="string"==typeof a?.reasoning_content?a.reasoning_content:"string"==typeof a?.reasoning?a.reasoning:"";e?.finish_reason&&(c=e.finish_reason),o&&(i+=o,n(o)),a?.content&&l.feed(a.content)}l.flush()}catch(e){t&&console.log("Local chat failed",e)}const d=r()?void 0:s||void 0;return t&&(console.log("Local chat reply",e.id,d?"****":""),console.log("Local chat finish",e.id,c||"none",s.length,i.length)),d},M=async({model:e,params:t,start:n,stopped:o})=>{const r=await(async e=>{const t=[];for(const n of e){if("string"==typeof n.content){t.push(n);continue}const e=[];for(const t of n.content)e.push("image"==t.type?{type:"image",url:await h(t.url)}:t);t.push({role:"user",content:e})}return t})(((e,t=w)=>{const n=t.maxContentLength,o=e.filter(e=>"string"==typeof e.content&&!!e.content.trim()||!(e.role!=a.user||!e.images?.length)),r=[],s=[];let i=0,c=t.maxImages;for(let e=o.length-1;e>=0;e--){const l=o[e],d=("string"==typeof l.content?l.content:"").slice(0,n),g=l.role==a.user?(l.images||[]).filter(e=>"string"==typeof e&&!!e).slice(0,Math.max(0,c)):[];if(d||g.length){if(d&&i+d.length>t.promptCharBudget)break;i+=d.length,c-=g.length,l.role==a.system?r.unshift(d):l.role==a.assistant?s.unshift({role:"assistant",content:d}):s.unshift({role:"user",content:v(d,g)})}}for(;s.length&&"user"!=s[0].role;)s.shift();const l=[];for(const e of s){const t=l[l.length-1];"assistant"==t?.role&&"assistant"==e.role?t.content=`${t.content}\n${e.content}`:"user"==t?.role&&"user"==e.role?t.content=_(t.content,e.content):"assistant"==e.role?l.push({role:"assistant",content:e.content}):l.push({role:"user",content:e.content})}const d=r.join("\n\n"),g=[];return d&&g.push({role:"system",content:d}),g.push(...l),g})(t.messages,t.limits));return t.logging&&((e,t)=>{console.log("Local chat call",e.id,t.map(({role:e,content:t})=>({role:e,content:"****",...Array.isArray(t)?{images:t.filter(e=>"image"==e.type).length}:{}})))})(e,r),await T({model:e,logging:t.logging,onReason:t.onReason,onText:t.onText,start:()=>n(r),stopped:o})};let C,L,R="",j=!1;const W=e=>`${e.wasm}|${e.compatWasm}|${e.compatWorker}`,$=e=>{const t={wasm:u,compatWasm:m,compatWorker:f};return{wasm:e?.wasm||t.wasm,compatWasm:e?.compatWasm||t.compatWasm,compatWorker:e?.compatWorker||t.compatWorker}},q=e=>{const t=e=>(...t)=>{j&&("string"==typeof t[0]&&t[0].includes("ggml_webgpu: Failed to get an adapter")||e(...t))};return{...e,log:t(e.log),warn:t(e.warn),error:t(e.error)}},B=async()=>{if(C)return C;const e=await n.e(924).then(()=>n(577)),t=L||$(),a=new e.Wllama({default:t.wasm},{logger:q(e.LoggerWithoutDebug)});return L||(L=t,R=W(t)),a.setCompat({wasm:t.compatWasm,worker:t.compatWorker}),C=a,a},I=async()=>{const e=C;C=void 0;try{await(e?.exit())}catch{}},P=(e,t)=>e.name.includes(t)||!!e.metadata?.originalURL?.includes(t),O=e=>[e.cpu.file,...e.cpu.mmproj?[e.cpu.mmproj.file]:[]],U=(e,t)=>`https://huggingface.co/${e}/resolve/main/${t}`,A=async e=>{if(!e.startsWith("data:"))return await(await fetch(e)).arrayBuffer();const t=e.indexOf(","),n=t>=0?e.slice(0,t):"";if(t<0)throw new Error("Invalid image data");if(!n.includes(";base64"))return await(await fetch(e)).arrayBuffer();const a=atob(e.slice(t+1)),o=new Uint8Array(a.length);for(let e=0;e<a.length;e++)o[e]=a.charCodeAt(e);return o.buffer},F=async e=>{const t=[];for(const n of e){if("string"==typeof n.content){"system"==n.role?t.push({role:"system",content:n.content}):"assistant"==n.role?t.push({role:"assistant",content:n.content}):t.push({role:"user",content:n.content});continue}const e=[];for(const t of n.content)e.push("text"==t.type?{type:"text",text:t.text}:{type:"image",data:await A(t.url)});t.push({role:"user",content:e})}return t},E=async(e,t,n)=>{if(j=!0===n.logging,e.isModelLoaded())return await M({model:t,params:n,stopped:()=>!!n.signal?.aborted,start:async a=>{if((e=>e.some(e=>Array.isArray(e.content)&&e.content.some(e=>"image"==e.type)))(a)&&!e.supportInputModality("image"))throw new Error("Model does not accept images");return await e.createChatCompletion({messages:await F(a),...(o=n.limits,r=n.reasoning,{stream:!0,temperature:.7,max_tokens:r?o.maxReasoningTokens:o.maxReplyTokens}),abortSignal:n.signal,...t.reasoning?{chat_template_kwargs:{enable_thinking:n.reasoning}}:{}});var o,r}})},Q={backend:s.cpu,startLLM:async({model:e,limits:t,onProgress:n,signal:a,logging:o,assets:r})=>{j=!0===o;const c=$(r),l=W(c);C&&l!=R&&await I(),L=c,R=l;const d=(g="undefined"!=typeof navigator&&navigator.hardwareConcurrency||4,u=t.maxThreads,Math.max(1,Math.min(Math.max(2,Math.min(8,(g||4)-1)),u)));var g,u;const m=await B(),f=U(e.cpu.repo,e.cpu.file),p=e.cpu.mmproj?.file,h=p?U(e.cpu.repo,p):"";if(a?.aborted)throw await I(),new Error("Cancelled");if(p){const t=await m.cacheManager.list(),n=t.some(t=>P(t,e.cpu.file)),a=t.some(e=>P(e,p));n&&!a&&await m.cacheManager.deleteMany(t=>P(t,e.cpu.file))}try{await m.loadModelFromUrl(h?{url:f,mmprojUrl:h}:f,{n_ctx:8192,n_threads:d,reasoning_budget_tokens:t.maxThinkingTokens,n_gpu_layers:0,useCache:!0,jinja:!0,signal:a,progressCallback:({loaded:e,total:t})=>{const a=t?e/t:0;n(a,a>=1?i.load:i.download)}})}catch(e){throw await I(),e}if(a?.aborted)throw await I(),new Error("Cancelled");if(!m.getLoadedContextInfo().n_ctx)throw await I(),new Error("Failed to create model context");return{model:e,backend:s.cpu,contextSize:8192,chat:t=>E(m,e,t),unload:async()=>{if(C==m)await I();else try{await m.exit()}catch{}}}},cached:async e=>{try{const t=await B(),n=await t.cacheManager.list();return O(e).every(e=>n.some(t=>P(t,e)))}catch{return!1}},hasCache:async()=>{try{const e=await B();return(await e.cacheManager.list()).length>0}catch{return!1}},remove:async e=>{try{const t=await B();await t.cacheManager.deleteMany(t=>O(e).some(e=>P(t,e)))}catch{}},purge:async()=>{try{const e=await B();await e.cacheManager.clear()}catch{}}},S=((e={})=>{const t=e.engines?.cpu||Q;let n,a,o,c,d,g,u,m=s.none,f=r.idle,p=0,h={percent:0,phase:i.download};const w=()=>e.backend||("undefined"==typeof window&&"undefined"==typeof self&&"undefined"==typeof Worker||"undefined"==typeof WebAssembly?s.none:s.cpu),y=async()=>{await t.purge()},x=(e,t)=>{h={percent:Math.max(0,Math.min(1,e)),phase:t||i.download},g?.(h.percent,h.phase)},k=e=>{f=e},v=()=>f==r.ready&&!!n&&!!a;return{load:async({model:e,limits:u,assets:f,logging:b,onProgress:_})=>{if(v()&&a?.id==e.id)return!0;if(o&&c==e.id)return g=_,_(h.percent,h.phase),await o;o&&(g=void 0,d?.abort(),await o.catch(()=>!1)),g=_;const T=++p,M=(async()=>{await(async()=>{try{if("undefined"==typeof navigator)return;await(navigator.storage?.persist?.())}catch{}})();const o=w();if(T!=p)return!1;if(o==s.none)return k(r.unsupported),!1;if(await(async e=>{if(a&&a.id!=e.id){const e=a,o=n;n=void 0,a=void 0,await(o?.unload()),await t.remove(e)}if(await t.hasCache())for(const n of l)n.id!=e.id&&await t.remove(n)})(e),T!=p)return!1;const c=await t.cached(e);if(T!=p)return!1;if(!c&&await(async()=>{try{if("undefined"==typeof navigator)return!1;if(!navigator.storage?.estimate)return!1;const{quota:e=0,usage:t=0}=await navigator.storage.estimate();return!!e&&t>=e}catch{return!1}})()&&await y(),T!=p)return!1;k(r.downloading),x(0,i.download),c&&x(1,i.load);const g=new AbortController;d=g;try{const s=await t.startLLM({model:e,limits:u,logging:b,signal:g.signal,assets:f,onProgress:(e,t)=>{T==p&&x(e,t)}});return T!=p||g.signal.aborted?(await s.unload(),!1):(n=s,a=e,m=o,k(r.ready),x(1,i.load),!0)}catch(e){return T!=p||(n=void 0,a=void 0,b&&console.log("Local model failed",e),(e=>["quota","no space","disk full","ns_error_dom_quota_reached"].some(t=>`${e?.name||""} ${e?.message||`${e}`}`.toLowerCase().includes(t)))(e)&&await y(),k(r.error)),!1}finally{d==g&&(d=void 0)}})().finally(()=>{o==M&&(o=void 0,c=void 0)});return o=M,c=e.id,await M},chat:async e=>{if(!v()||!n)return;for(;u;)await u.catch(()=>{});if(!v()||!n)return;const t=n.chat(e);u=t;try{return await t}finally{u==t&&(u=void 0)}},unload:async()=>{p++,d?.abort(),d=void 0,c=void 0;const e=n;n=void 0,a=void 0,k(r.idle),await(e?.unload())},cancelLoad:()=>{p++,d?.abort(),d=void 0,c=void 0,f==r.downloading&&k(r.idle)},ready:v,loading:()=>!v()&&f==r.downloading,status:()=>f,backend:()=>m,model:()=>a}})(),D=(e=>{let t,n=0;const s=t=>{const n=e.status(),a=e.backend(),s=d(t.input.model),i={availability:n==r.ready?o.online:n==r.error||n==r.unsupported?o.offline:o.away,status:n,backend:a,supportsImages:!!s&&g(s),ready:e.ready(),loading:e.loading(),requesting:t.requesting,phase:t.phase,percent:t.percent,...t.error?{error:t.error}:{}};t.input.onStatus?.(i)},c=async t=>{const{input:o}=t,r=!0===o.logging,c=d(o.model),l=o.messages.some(e=>e.role!=a.system&&"string"==typeof e.content&&!!e.content.trim()),u=o.messages.some(e=>e.role==a.user&&!!e.images?.some(e=>"string"==typeof e&&!!e)),m=l||u;if(!c)return t.error="Unknown model",void s(t);if(!m)return void s(t);if(u&&!l&&!g(c))return t.error="Model does not accept images",void s(t);const f=((e={})=>{const t=w;return{maxReplyTokens:y(e.maxReplyTokens,t.maxReplyTokens,1),maxThinkingTokens:y(e.maxThinkingTokens,t.maxThinkingTokens,1),maxReasoningTokens:y(e.maxReasoningTokens,t.maxReasoningTokens,1),maxImages:y(e.maxImages,t.maxImages,0),maxContentLength:y(e.maxContentLength,t.maxContentLength,1),promptCharBudget:y(e.promptCharBudget,t.promptCharBudget,1),maxThreads:y(e.maxThreads,t.maxThreads,1)}})(o.limits),p=await e.load({model:c,limits:f,logging:r,onProgress:(e,a)=>{t.token==n&&(t.percent=Math.round(100*e),t.phase=a,s(t))}}).catch(e=>(t.token==n&&(t.error=`${e?.message||`${e}`}`),!1));if(t.token!=n)return;if(!p)return t.cancelled||t.error||(t.error="Model not ready"),void s(t);const h=g(c),x=u&&!h?o.messages.map(({images:e,...t})=>t):o.messages;if(u&&!h&&!l)return t.error="Model does not accept images",void s(t);t.requesting=!0,t.error=void 0,t.percent=100,t.phase=i.load,s(t);let k,v="",b="";try{k=await e.chat({messages:x,limits:f,reasoning:!0===o.reasoning,logging:r,onReason:e=>{t.token!=n||t.cancelled||(v+=e,o.onThinking?.(v))},onText:e=>{t.token!=n||t.cancelled||(b+=e,o.onResponse?.(b))},signal:t.controller.signal})}catch(e){if(t.token!=n)return;return t.requesting=!1,t.error=`${e?.message||`${e}`}`,void s(t)}t.token==n&&(t.requesting=!1,k||t.cancelled||(t.error="No reply"),s(t))};return a=>{t?.controller.abort();const o={token:++n,input:a,controller:new AbortController,percent:e.ready()?100:0,phase:e.ready()?i.load:i.download,requesting:!1,cancelled:!1};return t=o,s(o),c(o),()=>(t=>{t.token==n&&(t.cancelled=!0,t.controller.abort(),e.status()!=r.ready?(e.cancelLoad(),s(t)):e.unload().then(()=>s(t)))})(o)}})(S);export{c as ChatModelId,o as ChatStatus,s as LlmBackend,i as LlmPhase,a as LlmRole,r as LlmStatus,D as createChat};
2
+ var e={};const t={};function n(a){const o=t[a];if(void 0!==o)return o.exports;const r=t[a]={exports:{}};return e[a](r,r.exports,n),r.exports}var a,o,r,s,i,c;n.m=e,n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.f={},n.e=e=>{const t=[];return n.f.j(e,t),Promise.all(t)},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{const e={924:()=>import("./engine-wllama.min.mjs")},t={792:0},a=e=>{let{__webpack_esm_ids__:a,__webpack_esm_modules__:o,__webpack_esm_runtime__:r}=e;var s,i,c=0;for(s in o)n.o(o,s)&&(n.m[s]=o[s]);for(r&&r(n);c<a.length;c++)i=a[c],n.o(t,i)&&t[i]&&t[i][0](),t[i]=0};n.f.j=(o,r)=>{let s=n.o(t,o)?t[o]:void 0;if(0!==s)if(s)r.push(s[1]);else{let n=e[o]().then(a,e=>{throw 0!==t[o]&&(t[o]=void 0),e});n=Promise.race([n,new Promise(e=>s=t[o]=[e])]),r.push(s[1]=n)}}})(),function(e){e.system="system",e.user="user",e.assistant="assistant"}(a||(a={})),function(e){e[e.online=0]="online",e[e.away=1]="away",e[e.offline=2]="offline"}(o||(o={})),function(e){e[e.idle=0]="idle",e[e.downloading=1]="downloading",e[e.ready=2]="ready",e[e.error=3]="error",e[e.unsupported=4]="unsupported"}(r||(r={})),function(e){e.cpu="cpu",e.none="none"}(s||(s={})),function(e){e.download="download",e.load="load"}(i||(i={})),function(e){e.qwen3_vl_2b="qwen3-vl-2b"}(c||(c={}));const l=[{id:c.qwen3_vl_2b,reasoning:!0,cpu:{repo:"Qwen/Qwen3-VL-2B-Thinking-GGUF",file:"Qwen3VL-2B-Thinking-Q4_K_M.gguf",mmproj:{file:"mmproj-Qwen3VL-2B-Thinking-Q8_0.gguf"}}}],d=e=>e?l.find(t=>t.id==e):void 0,g=e=>!!e.cpu.mmproj,u=new URL("./../assets/wllama/wllama.wasm",import.meta.url).href,m=new URL("./../assets/wllama/compat/wllama.wasm",import.meta.url).href,p=new URL("./../assets/wllama/compat/wllama.js",import.meta.url).href,f=288,h=async e=>{if(!e||"function"!=typeof fetch||"function"!=typeof createImageBitmap)return e;try{const t=await fetch(e),n=await createImageBitmap(await t.blob()),a=Math.min(n.width,n.height),o=Math.floor((n.width-a)/2),r=Math.floor((n.height-a)/2),s=((e,t)=>{if("undefined"!=typeof OffscreenCanvas)return new OffscreenCanvas(e,t);if("undefined"==typeof document)return;const n=document.createElement("canvas");return n.width=e,n.height=t,n})(f,f),i=s?s.getContext("2d"):null;if(!s||!i||a<1)return n.close(),e;if(i.drawImage(n,o,r,a,a,0,0,f,f),n.close(),(e=>"undefined"!=typeof OffscreenCanvas&&e instanceof OffscreenCanvas)(s)){const t=await s.convertToBlob({type:"image/jpeg",quality:.85});return await(e=>new Promise(t=>{if("undefined"==typeof FileReader)return void t("");const n=new FileReader;n.onerror=()=>t(""),n.onload=()=>t(`${n.result}`),n.readAsDataURL(e)}))(t)||e}return s.toDataURL("image/jpeg",.85)}catch{return e}},w={maxReplyTokens:200,maxThinkingTokens:64,maxReasoningTokens:150,maxImages:2,maxContentLength:1e3,promptCharBudget:1e4,maxThreads:2},y=(e,t,n)=>"number"==typeof e&&Number.isFinite(e)&&e>=n?Math.floor(e):t,x=(e={})=>{const t=w;return{maxReplyTokens:y(e.maxReplyTokens,t.maxReplyTokens,1),maxThinkingTokens:y(e.maxThinkingTokens,t.maxThinkingTokens,1),maxReasoningTokens:y(e.maxReasoningTokens,t.maxReasoningTokens,1),maxImages:y(e.maxImages,t.maxImages,0),maxContentLength:y(e.maxContentLength,t.maxContentLength,1),promptCharBudget:y(e.promptCharBudget,t.promptCharBudget,1),maxThreads:y(e.maxThreads,t.maxThreads,1)}},k=["<|channel>thought\n","<|channel>thought","..."],v=["<channel|>","..."],b=(e,t)=>t.length?[...e?[{type:"text",text:e}]:[],...t.map(e=>({type:"image",url:e}))]:e,_=e=>"string"==typeof e?e?[{type:"text",text:e}]:[]:e,M=(e,t)=>{if("string"==typeof e&&"string"==typeof t)return`${e}\n${t}`;const n=[];for(const a of[..._(e),{type:"text",text:"\n"},..._(t)]){const e=n[n.length-1];"text"==a.type&&"text"==e?.type?e.text+=a.text:n.push(a)}return n},T=async({model:e,logging:t,onReason:n,onText:a,start:o,stopped:r})=>{let s="",i="",c="";const l=((e,t)=>{let n="",a=!1;const o=(e,t)=>{let n=-1,a="";for(const o of t){const t=e.indexOf(o);t>=0&&(n<0||t<n)&&(n=t,a=o)}return{at:n,tag:a}},r=(e,t)=>{let n="";for(const a of t)for(let t=Math.min(e.length,a.length-1);t>n.length;t--)if(e.endsWith(a.slice(0,t))){n=e.slice(e.length-t);break}return n};return{feed:s=>{let i=n+s;for(n="";i;){if(a){const{at:t,tag:s}=o(i,v);if(t>=0){t&&e(i.slice(0,t)),i=i.slice(t+s.length),a=!1;continue}const c=r(i,v);e(i.slice(0,i.length-c.length)),n=c;break}{const{at:e,tag:s}=o(i,k);if(e>=0){e&&t(i.slice(0,e)),i=i.slice(e+s.length),i.startsWith("\n")&&(i=i.slice(1)),a=!0;continue}const c=r(i,k);t(i.slice(0,i.length-c.length)),n=c;break}}},flush:()=>{n&&(a?e(n):t(n),n="")}}})(e=>{i+=e,n(e)},e=>{s+=e,a(e)});try{const e=await o();for await(const t of e){if(r())break;const e=t?.choices?.[0],a=e?.delta,o="string"==typeof a?.reasoning_content?a.reasoning_content:"string"==typeof a?.reasoning?a.reasoning:"";e?.finish_reason&&(c=e.finish_reason),o&&(i+=o,n(o)),a?.content&&l.feed(a.content)}l.flush()}catch(e){t&&console.log("Local chat failed",e)}const d=r()?void 0:s||void 0;return t&&(console.log("Local chat reply",e.id,d?"****":""),console.log("Local chat finish",e.id,c||"none",s.length,i.length)),d},C=async({model:e,params:t,start:n,stopped:o})=>{const r=await(async e=>{const t=[];for(const n of e){if("string"==typeof n.content){t.push(n);continue}const e=[];for(const t of n.content)e.push("image"==t.type?{type:"image",url:await h(t.url)}:t);t.push({role:"user",content:e})}return t})(((e,t=w)=>{const n=t.maxContentLength,o=e.filter(e=>"string"==typeof e.content&&!!e.content.trim()||!(e.role!=a.user||!e.images?.length)),r=[],s=[];let i=0,c=t.maxImages;for(let e=o.length-1;e>=0;e--){const l=o[e],d=("string"==typeof l.content?l.content:"").slice(0,n),g=l.role==a.user?(l.images||[]).filter(e=>"string"==typeof e&&!!e).slice(0,Math.max(0,c)):[];if(d||g.length){if(d&&i+d.length>t.promptCharBudget)break;i+=d.length,c-=g.length,l.role==a.system?r.unshift(d):l.role==a.assistant?s.unshift({role:"assistant",content:d}):s.unshift({role:"user",content:b(d,g)})}}for(;s.length&&"user"!=s[0].role;)s.shift();const l=[];for(const e of s){const t=l[l.length-1];"assistant"==t?.role&&"assistant"==e.role?t.content=`${t.content}\n${e.content}`:"user"==t?.role&&"user"==e.role?t.content=M(t.content,e.content):"assistant"==e.role?l.push({role:"assistant",content:e.content}):l.push({role:"user",content:e.content})}const d=r.join("\n\n"),g=[];return d&&g.push({role:"system",content:d}),g.push(...l),g})(t.messages,t.limits));return t.logging&&((e,t)=>{console.log("Local chat call",e.id,t.map(({role:e,content:t})=>({role:e,content:"****",...Array.isArray(t)?{images:t.filter(e=>"image"==e.type).length}:{}})))})(e,r),await T({model:e,logging:t.logging,onReason:t.onReason,onText:t.onText,start:()=>n(r),stopped:o})};let L,R,j="",W=!1;const $=e=>`${e.wasm}|${e.compatWasm}|${e.compatWorker}`,q=e=>{const t={wasm:u,compatWasm:m,compatWorker:p};return{wasm:e?.wasm||t.wasm,compatWasm:e?.compatWasm||t.compatWasm,compatWorker:e?.compatWorker||t.compatWorker}},B=e=>{const t=e=>(...t)=>{W&&("string"==typeof t[0]&&t[0].includes("ggml_webgpu: Failed to get an adapter")||e(...t))};return{...e,log:t(e.log),warn:t(e.warn),error:t(e.error)}},P=async()=>{if(L)return L;const e=await n.e(924).then(()=>n(577)),t=R||q(),a=new e.Wllama({default:t.wasm},{logger:B(e.LoggerWithoutDebug)});return R||(R=t,j=$(t)),a.setCompat({wasm:t.compatWasm,worker:t.compatWorker}),L=a,a},I=async()=>{const e=L;L=void 0;try{await(e?.exit())}catch{}},U=(e,t)=>e.name.includes(t)||!!e.metadata?.originalURL?.includes(t),O=e=>[e.cpu.file,...e.cpu.mmproj?[e.cpu.mmproj.file]:[]],A=(e,t)=>`https://huggingface.co/${e}/resolve/main/${t}`,F=async e=>{if(!e.startsWith("data:"))return await(await fetch(e)).arrayBuffer();const t=e.indexOf(","),n=t>=0?e.slice(0,t):"";if(t<0)throw new Error("Invalid image data");if(!n.includes(";base64"))return await(await fetch(e)).arrayBuffer();const a=atob(e.slice(t+1)),o=new Uint8Array(a.length);for(let e=0;e<a.length;e++)o[e]=a.charCodeAt(e);return o.buffer},E=async e=>{const t=[];for(const n of e){if("string"==typeof n.content){"system"==n.role?t.push({role:"system",content:n.content}):"assistant"==n.role?t.push({role:"assistant",content:n.content}):t.push({role:"user",content:n.content});continue}const e=[];for(const t of n.content)e.push("text"==t.type?{type:"text",text:t.text}:{type:"image",data:await F(t.url)});t.push({role:"user",content:e})}return t},Q=async(e,t,n)=>{if(W=!0===n.logging,e.isModelLoaded())return await C({model:t,params:n,stopped:()=>!!n.signal?.aborted,start:async a=>{if((e=>e.some(e=>Array.isArray(e.content)&&e.content.some(e=>"image"==e.type)))(a)&&!e.supportInputModality("image"))throw new Error("Model does not accept images");return await e.createChatCompletion({messages:await E(a),...(o=n.limits,r=n.reasoning,{stream:!0,temperature:.7,max_tokens:r?o.maxReasoningTokens:o.maxReplyTokens}),abortSignal:n.signal,...t.reasoning?{chat_template_kwargs:{enable_thinking:n.reasoning}}:{}});var o,r}})},S={backend:s.cpu,startLLM:async({model:e,limits:t,onProgress:n,signal:a,logging:o,assets:r})=>{W=!0===o;const c=q(r),l=$(c);L&&l!=j&&await I(),R=c,j=l;const d=(g="undefined"!=typeof navigator&&navigator.hardwareConcurrency||4,u=t.maxThreads,Math.max(1,Math.min(Math.max(2,Math.min(8,(g||4)-1)),u)));var g,u;const m=await P(),p=A(e.cpu.repo,e.cpu.file),f=e.cpu.mmproj?.file,h=f?A(e.cpu.repo,f):"";if(a?.aborted)throw await I(),new Error("Cancelled");if(f){const t=await m.cacheManager.list(),n=t.some(t=>U(t,e.cpu.file)),a=t.some(e=>U(e,f));n&&!a&&await m.cacheManager.deleteMany(t=>U(t,e.cpu.file))}try{await m.loadModelFromUrl(h?{url:p,mmprojUrl:h}:p,{n_ctx:8192,n_threads:d,reasoning_budget_tokens:t.maxThinkingTokens,n_gpu_layers:0,useCache:!0,jinja:!0,signal:a,progressCallback:({loaded:e,total:t})=>{const a=t?e/t:0;n(a,a>=1?i.load:i.download)}})}catch(e){throw await I(),e}if(a?.aborted)throw await I(),new Error("Cancelled");if(!m.getLoadedContextInfo().n_ctx)throw await I(),new Error("Failed to create model context");return{model:e,backend:s.cpu,contextSize:8192,chat:t=>Q(m,e,t),unload:async()=>{if(L==m)await I();else try{await m.exit()}catch{}}}},cached:async e=>{try{const t=await P(),n=await t.cacheManager.list();return O(e).every(e=>n.some(t=>U(t,e)))}catch{return!1}},hasCache:async()=>{try{const e=await P();return(await e.cacheManager.list()).length>0}catch{return!1}},remove:async e=>{try{const t=await P();await t.cacheManager.deleteMany(t=>O(e).some(e=>U(t,e)))}catch{}},purge:async()=>{try{const e=await P();await e.cacheManager.clear()}catch{}}},D=((e={})=>{const t=e.engines?.cpu||S;let n,a,o,c,d,g,u,m=s.none,p=r.idle,f=0,h={percent:0,phase:i.download};const w=()=>e.backend||("undefined"==typeof window&&"undefined"==typeof self&&"undefined"==typeof Worker||"undefined"==typeof WebAssembly?s.none:s.cpu),y=async()=>{await t.purge()},x=(e,t)=>{h={percent:Math.max(0,Math.min(1,e)),phase:t||i.download},g?.(h.percent,h.phase)},k=e=>{p=e},v=()=>p==r.ready&&!!n&&!!a;return{load:async({model:e,limits:u,assets:p,logging:b,onProgress:_})=>{if(v()&&a?.id==e.id)return!0;if(o&&c==e.id)return g=_,_(h.percent,h.phase),await o;o&&(g=void 0,d?.abort(),await o.catch(()=>!1)),g=_;const M=++f,T=(async()=>{await(async()=>{try{if("undefined"==typeof navigator)return;await(navigator.storage?.persist?.())}catch{}})();const o=w();if(M!=f)return!1;if(o==s.none)return k(r.unsupported),!1;if(await(async e=>{if(a&&a.id!=e.id){const e=a,o=n;n=void 0,a=void 0,await(o?.unload()),await t.remove(e)}if(await t.hasCache())for(const n of l)n.id!=e.id&&await t.remove(n)})(e),M!=f)return!1;const c=await t.cached(e);if(M!=f)return!1;if(!c&&await(async()=>{try{if("undefined"==typeof navigator)return!1;if(!navigator.storage?.estimate)return!1;const{quota:e=0,usage:t=0}=await navigator.storage.estimate();return!!e&&t>=e}catch{return!1}})()&&await y(),M!=f)return!1;k(r.downloading),x(0,i.download),c&&x(1,i.load);const g=new AbortController;d=g;try{const s=await t.startLLM({model:e,limits:u,logging:b,signal:g.signal,assets:p,onProgress:(e,t)=>{M==f&&x(e,t)}});return M!=f||g.signal.aborted?(await s.unload(),!1):(n=s,a=e,m=o,k(r.ready),x(1,i.load),!0)}catch(e){return M!=f||(n=void 0,a=void 0,b&&console.log("Local model failed",e),(e=>["quota","no space","disk full","ns_error_dom_quota_reached"].some(t=>`${e?.name||""} ${e?.message||`${e}`}`.toLowerCase().includes(t)))(e)&&await y(),k(r.error)),!1}finally{d==g&&(d=void 0)}})().finally(()=>{o==T&&(o=void 0,c=void 0)});return o=T,c=e.id,await T},chat:async e=>{if(!v()||!n)return;for(;u;)await u.catch(()=>{});if(!v()||!n)return;const t=n.chat(e);u=t;try{return await t}finally{u==t&&(u=void 0)}},unload:async()=>{f++,d?.abort(),d=void 0,c=void 0;const e=n;n=void 0,a=void 0,k(r.idle),await(e?.unload())},cancelLoad:()=>{f++,d?.abort(),d=void 0,c=void 0,p==r.downloading&&k(r.idle)},ready:v,loading:()=>!v()&&p==r.downloading,status:()=>p,backend:()=>m,model:()=>a}})(),V=(e,t,n)=>{const a=e.status(),s=d(t);return{availability:a==r.ready?o.online:a==r.error||a==r.unsupported?o.offline:o.away,status:a,backend:e.backend(),supportsImages:!!s&&g(s),ready:e.ready(),loading:e.loading(),requesting:n.requesting,phase:n.phase,percent:n.percent,...n.error?{error:n.error}:{}}},G=(e=>{let t,n=0;const o=t=>t.input.onStatus?.(V(e,t.input.model,t));return s=>{t?.controller.abort();const c={token:++n,input:s,controller:new AbortController,percent:e.ready()?100:0,phase:e.ready()?i.load:i.download,requesting:!1,cancelled:!1};return t=c,o(c),(async t=>{const{input:r}=t,s=!0===r.logging,c=d(r.model),l=r.messages.some(e=>e.role!=a.system&&"string"==typeof e.content&&!!e.content.trim()),u=r.messages.some(e=>e.role==a.user&&!!e.images?.some(e=>"string"==typeof e&&!!e)),m=l||u;if(!c)return t.error="Unknown model",void o(t);if(!m)return void o(t);if(u&&!l&&!g(c))return t.error="Model does not accept images",void o(t);const p=x(r.limits),f=await e.load({model:c,limits:p,logging:s,onProgress:(e,a)=>{t.token==n&&(t.percent=Math.round(100*e),t.phase=a,o(t))}}).catch(e=>(t.token==n&&(t.error=`${e?.message||`${e}`}`),!1));if(t.token!=n)return;if(!f)return t.cancelled||t.error||(t.error="Model not ready"),void o(t);const h=g(c),w=u&&!h?r.messages.map(({images:e,...t})=>t):r.messages;if(u&&!h&&!l)return t.error="Model does not accept images",void o(t);t.requesting=!0,t.error=void 0,t.percent=100,t.phase=i.load,o(t);let y,k="",v="";try{y=await e.chat({messages:w,limits:p,reasoning:!0===r.reasoning,logging:s,onReason:e=>{t.token!=n||t.cancelled||(k+=e,r.onThinking?.(k))},onText:e=>{t.token!=n||t.cancelled||(v+=e,r.onResponse?.(v))},signal:t.controller.signal})}catch(e){if(t.token!=n)return;return t.requesting=!1,t.error=`${e?.message||`${e}`}`,void o(t)}t.token==n&&(t.requesting=!1,y||t.cancelled||(t.error="No reply"),o(t))})(c),()=>(t=>{t.token==n&&(t.cancelled=!0,t.controller.abort(),e.status()!=r.ready?(e.cancelLoad(),o(t)):e.unload().then(()=>o(t)))})(c)}})(D),N=(z=D,async e=>{const t=d(e.model),n=t=>e.onStatus?.(V(z,e.model,t));if(!t)return n({requesting:!1,phase:i.download,percent:0,error:"Unknown model"}),!1;let a,o=0,r=i.download;const s=z.ready()&&z.model()?.id==t.id;return await z.load({model:t,limits:x(e.limits),logging:!0===e.logging,onProgress:(e,t)=>{o=Math.round(100*e),r=t,n({requesting:!1,phase:r,percent:o})}}).catch(e=>(a=`${e?.message||`${e}`}`,!1))?(s&&n({requesting:!1,phase:i.load,percent:100}),!0):(n({requesting:!1,phase:r,percent:o,error:a||"Model not ready"}),!1)});var z;export{c as ChatModelId,o as ChatStatus,s as LlmBackend,i as LlmPhase,a as LlmRole,r as LlmStatus,G as createChat,N as preloadModel};
package/dist/chat.d.ts CHANGED
@@ -1,5 +1,8 @@
1
- import type { CancelChat, ChatInput, Runtime } from './types';
1
+ import type { CancelChat, ChatInput, ChatPreloadInput, Runtime } from './types';
2
2
  /** Build a one-shot chat function over one runtime */
3
3
  declare const createChatOn: (service: Runtime) => ((input: ChatInput) => CancelChat);
4
+ /** Build a model preload over one runtime */
5
+ declare const createPreloadOn: (service: Runtime) => (input: ChatPreloadInput) => Promise<boolean>;
4
6
  declare const createChat: (input: ChatInput) => CancelChat;
5
- export { createChat, createChatOn };
7
+ declare const preloadModel: (input: ChatPreloadInput) => Promise<boolean>;
8
+ export { createChat, createChatOn, createPreloadOn, preloadModel };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { createChat } from './chat';
1
+ export { createChat, preloadModel } from './chat';
2
2
  export { LlmRole, ChatStatus, LlmStatus, LlmBackend, LlmPhase, ChatModelId, } from './enums';
3
- export type { ChatMessage, LlmLimits, ChatStatusSnapshot, ChatInput, CancelChat, WllamaAssets, } from './types';
3
+ export type { ChatMessage, LlmLimits, ChatStatusSnapshot, ChatInput, ChatPreloadInput, CancelChat, WllamaAssets, } from './types';
@@ -1,2 +1,2 @@
1
1
  /*! For license information please see chat.node.min.js.LICENSE.txt */
2
- (()=>{"use strict";var e={};const t={};function n(a){const o=t[a];if(void 0!==o)return o.exports;const r=t[a]={exports:{}};return e[a](r,r.exports,n),r.exports}n.m=e,n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.f={},n.e=e=>{const t=[];return n.f.require(e,t),Promise.all(t)},n.u=e=>"engine-wllama.node.min.js",n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{const e={792:1};n.f.require=(t,a)=>{if(!e[t]){const a=require("./"+n.u(t));e[t]||(t=>{const a=t.modules,o=t.ids,r=t.runtime;for(var s in a)n.o(a,s)&&(n.m[s]=a[s]);r&&r(n);for(var i=0;i<o.length;i++)e[o[i]]=1})(a)}}})();let a={};var o,r,s,i,c,l;n.r(a),n.d(a,{ChatModelId:()=>l,ChatStatus:()=>r,LlmBackend:()=>i,LlmPhase:()=>c,LlmRole:()=>o,LlmStatus:()=>s,createChat:()=>D}),function(e){e.system="system",e.user="user",e.assistant="assistant"}(o||(o={})),function(e){e[e.online=0]="online",e[e.away=1]="away",e[e.offline=2]="offline"}(r||(r={})),function(e){e[e.idle=0]="idle",e[e.downloading=1]="downloading",e[e.ready=2]="ready",e[e.error=3]="error",e[e.unsupported=4]="unsupported"}(s||(s={})),function(e){e.cpu="cpu",e.none="none"}(i||(i={})),function(e){e.download="download",e.load="load"}(c||(c={})),function(e){e.qwen3_vl_2b="qwen3-vl-2b"}(l||(l={}));const d=[{id:l.qwen3_vl_2b,reasoning:!0,cpu:{repo:"Qwen/Qwen3-VL-2B-Thinking-GGUF",file:"Qwen3VL-2B-Thinking-Q4_K_M.gguf",mmproj:{file:"mmproj-Qwen3VL-2B-Thinking-Q8_0.gguf"}}}],g=e=>e?d.find(t=>t.id==e):void 0,u=e=>!!e.cpu.mmproj,m=require("node:path"),f=()=>{return e=(0,m.join)(__dirname,"../assets/wllama"),{wasm:(0,m.join)(e,"wllama.wasm"),compatWasm:(0,m.join)(e,"compat/wllama.wasm"),compatWorker:(0,m.join)(e,"compat/wllama.js")};var e},p=288,h=async e=>{if(!e||"function"!=typeof fetch||"function"!=typeof createImageBitmap)return e;try{const t=await fetch(e),n=await createImageBitmap(await t.blob()),a=Math.min(n.width,n.height),o=Math.floor((n.width-a)/2),r=Math.floor((n.height-a)/2),s=((e,t)=>{if("undefined"!=typeof OffscreenCanvas)return new OffscreenCanvas(e,t);if("undefined"==typeof document)return;const n=document.createElement("canvas");return n.width=e,n.height=t,n})(p,p),i=s?s.getContext("2d"):null;if(!s||!i||a<1)return n.close(),e;if(i.drawImage(n,o,r,a,a,0,0,p,p),n.close(),(e=>"undefined"!=typeof OffscreenCanvas&&e instanceof OffscreenCanvas)(s)){const t=await s.convertToBlob({type:"image/jpeg",quality:.85});return await(e=>new Promise(t=>{if("undefined"==typeof FileReader)return void t("");const n=new FileReader;n.onerror=()=>t(""),n.onload=()=>t(`${n.result}`),n.readAsDataURL(e)}))(t)||e}return s.toDataURL("image/jpeg",.85)}catch{return e}},y={maxReplyTokens:200,maxThinkingTokens:64,maxReasoningTokens:150,maxImages:2,maxContentLength:1e3,promptCharBudget:1e4,maxThreads:2},w=(e,t,n)=>"number"==typeof e&&Number.isFinite(e)&&e>=n?Math.floor(e):t,x=["<|channel>thought\n","<|channel>thought","..."],v=["<channel|>","..."],k=(e,t)=>t.length?[...e?[{type:"text",text:e}]:[],...t.map(e=>({type:"image",url:e}))]:e,b=e=>"string"==typeof e?e?[{type:"text",text:e}]:[]:e,T=(e,t)=>{if("string"==typeof e&&"string"==typeof t)return`${e}\n${t}`;const n=[];for(const a of[...b(e),{type:"text",text:"\n"},...b(t)]){const e=n[n.length-1];"text"==a.type&&"text"==e?.type?e.text+=a.text:n.push(a)}return n},M=async({model:e,logging:t,onReason:n,onText:a,start:o,stopped:r})=>{let s="",i="",c="";const l=((e,t)=>{let n="",a=!1;const o=(e,t)=>{let n=-1,a="";for(const o of t){const t=e.indexOf(o);t>=0&&(n<0||t<n)&&(n=t,a=o)}return{at:n,tag:a}},r=(e,t)=>{let n="";for(const a of t)for(let t=Math.min(e.length,a.length-1);t>n.length;t--)if(e.endsWith(a.slice(0,t))){n=e.slice(e.length-t);break}return n};return{feed:s=>{let i=n+s;for(n="";i;){if(a){const{at:t,tag:s}=o(i,v);if(t>=0){t&&e(i.slice(0,t)),i=i.slice(t+s.length),a=!1;continue}const c=r(i,v);e(i.slice(0,i.length-c.length)),n=c;break}{const{at:e,tag:s}=o(i,x);if(e>=0){e&&t(i.slice(0,e)),i=i.slice(e+s.length),i.startsWith("\n")&&(i=i.slice(1)),a=!0;continue}const c=r(i,x);t(i.slice(0,i.length-c.length)),n=c;break}}},flush:()=>{n&&(a?e(n):t(n),n="")}}})(e=>{i+=e,n(e)},e=>{s+=e,a(e)});try{const e=await o();for await(const t of e){if(r())break;const e=t?.choices?.[0],a=e?.delta,o="string"==typeof a?.reasoning_content?a.reasoning_content:"string"==typeof a?.reasoning?a.reasoning:"";e?.finish_reason&&(c=e.finish_reason),o&&(i+=o,n(o)),a?.content&&l.feed(a.content)}l.flush()}catch(e){t&&console.log("Local chat failed",e)}const d=r()?void 0:s||void 0;return t&&(console.log("Local chat reply",e.id,d?"****":""),console.log("Local chat finish",e.id,c||"none",s.length,i.length)),d},C=async({model:e,params:t,start:n,stopped:a})=>{const r=await(async e=>{const t=[];for(const n of e){if("string"==typeof n.content){t.push(n);continue}const e=[];for(const t of n.content)e.push("image"==t.type?{type:"image",url:await h(t.url)}:t);t.push({role:"user",content:e})}return t})(((e,t=y)=>{const n=t.maxContentLength,a=e.filter(e=>"string"==typeof e.content&&!!e.content.trim()||!(e.role!=o.user||!e.images?.length)),r=[],s=[];let i=0,c=t.maxImages;for(let e=a.length-1;e>=0;e--){const l=a[e],d=("string"==typeof l.content?l.content:"").slice(0,n),g=l.role==o.user?(l.images||[]).filter(e=>"string"==typeof e&&!!e).slice(0,Math.max(0,c)):[];if(d||g.length){if(d&&i+d.length>t.promptCharBudget)break;i+=d.length,c-=g.length,l.role==o.system?r.unshift(d):l.role==o.assistant?s.unshift({role:"assistant",content:d}):s.unshift({role:"user",content:k(d,g)})}}for(;s.length&&"user"!=s[0].role;)s.shift();const l=[];for(const e of s){const t=l[l.length-1];"assistant"==t?.role&&"assistant"==e.role?t.content=`${t.content}\n${e.content}`:"user"==t?.role&&"user"==e.role?t.content=T(t.content,e.content):"assistant"==e.role?l.push({role:"assistant",content:e.content}):l.push({role:"user",content:e.content})}const d=r.join("\n\n"),g=[];return d&&g.push({role:"system",content:d}),g.push(...l),g})(t.messages,t.limits));return t.logging&&((e,t)=>{console.log("Local chat call",e.id,t.map(({role:e,content:t})=>({role:e,content:"****",...Array.isArray(t)?{images:t.filter(e=>"image"==e.type).length}:{}})))})(e,r),await M({model:e,logging:t.logging,onReason:t.onReason,onText:t.onText,start:()=>n(r),stopped:a})};let _,L,j="",R=!1;const W=e=>`${e.wasm}|${e.compatWasm}|${e.compatWorker}`,q=e=>{const t=f();return{wasm:e?.wasm||t.wasm,compatWasm:e?.compatWasm||t.compatWasm,compatWorker:e?.compatWorker||t.compatWorker}},$=e=>{const t=e=>(...t)=>{R&&("string"==typeof t[0]&&t[0].includes("ggml_webgpu: Failed to get an adapter")||e(...t))};return{...e,log:t(e.log),warn:t(e.warn),error:t(e.error)}},B=async()=>{if(_)return _;const e=await n.e(924).then(()=>n(577)),t=L||q(),a=new e.Wllama({default:t.wasm},{logger:$(e.LoggerWithoutDebug)});return L||(L=t,j=W(t)),a.setCompat({wasm:t.compatWasm,worker:t.compatWorker}),_=a,a},I=async()=>{const e=_;_=void 0;try{await(e?.exit())}catch{}},O=(e,t)=>e.name.includes(t)||!!e.metadata?.originalURL?.includes(t),P=e=>[e.cpu.file,...e.cpu.mmproj?[e.cpu.mmproj.file]:[]],A=(e,t)=>`https://huggingface.co/${e}/resolve/main/${t}`,U=async e=>{if(!e.startsWith("data:"))return await(await fetch(e)).arrayBuffer();const t=e.indexOf(","),n=t>=0?e.slice(0,t):"";if(t<0)throw new Error("Invalid image data");if(!n.includes(";base64"))return await(await fetch(e)).arrayBuffer();const a=atob(e.slice(t+1)),o=new Uint8Array(a.length);for(let e=0;e<a.length;e++)o[e]=a.charCodeAt(e);return o.buffer},F=async e=>{const t=[];for(const n of e){if("string"==typeof n.content){"system"==n.role?t.push({role:"system",content:n.content}):"assistant"==n.role?t.push({role:"assistant",content:n.content}):t.push({role:"user",content:n.content});continue}const e=[];for(const t of n.content)e.push("text"==t.type?{type:"text",text:t.text}:{type:"image",data:await U(t.url)});t.push({role:"user",content:e})}return t},S=async(e,t,n)=>{if(R=!0===n.logging,e.isModelLoaded())return await C({model:t,params:n,stopped:()=>!!n.signal?.aborted,start:async a=>{if((e=>e.some(e=>Array.isArray(e.content)&&e.content.some(e=>"image"==e.type)))(a)&&!e.supportInputModality("image"))throw new Error("Model does not accept images");return await e.createChatCompletion({messages:await F(a),...(o=n.limits,r=n.reasoning,{stream:!0,temperature:.7,max_tokens:r?o.maxReasoningTokens:o.maxReplyTokens}),abortSignal:n.signal,...t.reasoning?{chat_template_kwargs:{enable_thinking:n.reasoning}}:{}});var o,r}})},E={backend:i.cpu,startLLM:async({model:e,limits:t,onProgress:n,signal:a,logging:o,assets:r})=>{R=!0===o;const s=q(r),l=W(s);_&&l!=j&&await I(),L=s,j=l;const d=(g="undefined"!=typeof navigator&&navigator.hardwareConcurrency||4,u=t.maxThreads,Math.max(1,Math.min(Math.max(2,Math.min(8,(g||4)-1)),u)));var g,u;const m=await B(),f=A(e.cpu.repo,e.cpu.file),p=e.cpu.mmproj?.file,h=p?A(e.cpu.repo,p):"";if(a?.aborted)throw await I(),new Error("Cancelled");if(p){const t=await m.cacheManager.list(),n=t.some(t=>O(t,e.cpu.file)),a=t.some(e=>O(e,p));n&&!a&&await m.cacheManager.deleteMany(t=>O(t,e.cpu.file))}try{await m.loadModelFromUrl(h?{url:f,mmprojUrl:h}:f,{n_ctx:8192,n_threads:d,reasoning_budget_tokens:t.maxThinkingTokens,n_gpu_layers:0,useCache:!0,jinja:!0,signal:a,progressCallback:({loaded:e,total:t})=>{const a=t?e/t:0;n(a,a>=1?c.load:c.download)}})}catch(e){throw await I(),e}if(a?.aborted)throw await I(),new Error("Cancelled");if(!m.getLoadedContextInfo().n_ctx)throw await I(),new Error("Failed to create model context");return{model:e,backend:i.cpu,contextSize:8192,chat:t=>S(m,e,t),unload:async()=>{if(_==m)await I();else try{await m.exit()}catch{}}}},cached:async e=>{try{const t=await B(),n=await t.cacheManager.list();return P(e).every(e=>n.some(t=>O(t,e)))}catch{return!1}},hasCache:async()=>{try{const e=await B();return(await e.cacheManager.list()).length>0}catch{return!1}},remove:async e=>{try{const t=await B();await t.cacheManager.deleteMany(t=>P(e).some(e=>O(t,e)))}catch{}},purge:async()=>{try{const e=await B();await e.cacheManager.clear()}catch{}}},Q=((e={})=>{const t=e.engines?.cpu||E;let n,a,o,r,l,g,u,m=i.none,f=s.idle,p=0,h={percent:0,phase:c.download};const y=()=>e.backend||("undefined"==typeof window&&"undefined"==typeof self&&"undefined"==typeof Worker||"undefined"==typeof WebAssembly?i.none:i.cpu),w=async()=>{await t.purge()},x=(e,t)=>{h={percent:Math.max(0,Math.min(1,e)),phase:t||c.download},g?.(h.percent,h.phase)},v=e=>{f=e},k=()=>f==s.ready&&!!n&&!!a;return{load:async({model:e,limits:u,assets:f,logging:b,onProgress:T})=>{if(k()&&a?.id==e.id)return!0;if(o&&r==e.id)return g=T,T(h.percent,h.phase),await o;o&&(g=void 0,l?.abort(),await o.catch(()=>!1)),g=T;const M=++p,C=(async()=>{await(async()=>{try{if("undefined"==typeof navigator)return;await(navigator.storage?.persist?.())}catch{}})();const o=y();if(M!=p)return!1;if(o==i.none)return v(s.unsupported),!1;if(await(async e=>{if(a&&a.id!=e.id){const e=a,o=n;n=void 0,a=void 0,await(o?.unload()),await t.remove(e)}if(await t.hasCache())for(const n of d)n.id!=e.id&&await t.remove(n)})(e),M!=p)return!1;const r=await t.cached(e);if(M!=p)return!1;if(!r&&await(async()=>{try{if("undefined"==typeof navigator)return!1;if(!navigator.storage?.estimate)return!1;const{quota:e=0,usage:t=0}=await navigator.storage.estimate();return!!e&&t>=e}catch{return!1}})()&&await w(),M!=p)return!1;v(s.downloading),x(0,c.download),r&&x(1,c.load);const g=new AbortController;l=g;try{const r=await t.startLLM({model:e,limits:u,logging:b,signal:g.signal,assets:f,onProgress:(e,t)=>{M==p&&x(e,t)}});return M!=p||g.signal.aborted?(await r.unload(),!1):(n=r,a=e,m=o,v(s.ready),x(1,c.load),!0)}catch(e){return M!=p||(n=void 0,a=void 0,b&&console.log("Local model failed",e),(e=>["quota","no space","disk full","ns_error_dom_quota_reached"].some(t=>`${e?.name||""} ${e?.message||`${e}`}`.toLowerCase().includes(t)))(e)&&await w(),v(s.error)),!1}finally{l==g&&(l=void 0)}})().finally(()=>{o==C&&(o=void 0,r=void 0)});return o=C,r=e.id,await C},chat:async e=>{if(!k()||!n)return;for(;u;)await u.catch(()=>{});if(!k()||!n)return;const t=n.chat(e);u=t;try{return await t}finally{u==t&&(u=void 0)}},unload:async()=>{p++,l?.abort(),l=void 0,r=void 0;const e=n;n=void 0,a=void 0,v(s.idle),await(e?.unload())},cancelLoad:()=>{p++,l?.abort(),l=void 0,r=void 0,f==s.downloading&&v(s.idle)},ready:k,loading:()=>!k()&&f==s.downloading,status:()=>f,backend:()=>m,model:()=>a}})(),D=(e=>{let t,n=0;const a=t=>{const n=e.status(),a=e.backend(),o=g(t.input.model),i={availability:n==s.ready?r.online:n==s.error||n==s.unsupported?r.offline:r.away,status:n,backend:a,supportsImages:!!o&&u(o),ready:e.ready(),loading:e.loading(),requesting:t.requesting,phase:t.phase,percent:t.percent,...t.error?{error:t.error}:{}};t.input.onStatus?.(i)},i=async t=>{const{input:r}=t,s=!0===r.logging,i=g(r.model),l=r.messages.some(e=>e.role!=o.system&&"string"==typeof e.content&&!!e.content.trim()),d=r.messages.some(e=>e.role==o.user&&!!e.images?.some(e=>"string"==typeof e&&!!e)),m=l||d;if(!i)return t.error="Unknown model",void a(t);if(!m)return void a(t);if(d&&!l&&!u(i))return t.error="Model does not accept images",void a(t);const f=((e={})=>{const t=y;return{maxReplyTokens:w(e.maxReplyTokens,t.maxReplyTokens,1),maxThinkingTokens:w(e.maxThinkingTokens,t.maxThinkingTokens,1),maxReasoningTokens:w(e.maxReasoningTokens,t.maxReasoningTokens,1),maxImages:w(e.maxImages,t.maxImages,0),maxContentLength:w(e.maxContentLength,t.maxContentLength,1),promptCharBudget:w(e.promptCharBudget,t.promptCharBudget,1),maxThreads:w(e.maxThreads,t.maxThreads,1)}})(r.limits),p=await e.load({model:i,limits:f,logging:s,onProgress:(e,o)=>{t.token==n&&(t.percent=Math.round(100*e),t.phase=o,a(t))}}).catch(e=>(t.token==n&&(t.error=`${e?.message||`${e}`}`),!1));if(t.token!=n)return;if(!p)return t.cancelled||t.error||(t.error="Model not ready"),void a(t);const h=u(i),x=d&&!h?r.messages.map(({images:e,...t})=>t):r.messages;if(d&&!h&&!l)return t.error="Model does not accept images",void a(t);t.requesting=!0,t.error=void 0,t.percent=100,t.phase=c.load,a(t);let v,k="",b="";try{v=await e.chat({messages:x,limits:f,reasoning:!0===r.reasoning,logging:s,onReason:e=>{t.token!=n||t.cancelled||(k+=e,r.onThinking?.(k))},onText:e=>{t.token!=n||t.cancelled||(b+=e,r.onResponse?.(b))},signal:t.controller.signal})}catch(e){if(t.token!=n)return;return t.requesting=!1,t.error=`${e?.message||`${e}`}`,void a(t)}t.token==n&&(t.requesting=!1,v||t.cancelled||(t.error="No reply"),a(t))};return o=>{t?.controller.abort();const r={token:++n,input:o,controller:new AbortController,percent:e.ready()?100:0,phase:e.ready()?c.load:c.download,requesting:!1,cancelled:!1};return t=r,a(r),i(r),()=>(t=>{t.token==n&&(t.cancelled=!0,t.controller.abort(),e.status()!=s.ready?(e.cancelLoad(),a(t)):e.unload().then(()=>a(t)))})(r)}})(Q);module.exports=a})();
2
+ (()=>{"use strict";var e={};const t={};function n(a){const o=t[a];if(void 0!==o)return o.exports;const r=t[a]={exports:{}};return e[a](r,r.exports,n),r.exports}n.m=e,n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.f={},n.e=e=>{const t=[];return n.f.require(e,t),Promise.all(t)},n.u=e=>"engine-wllama.node.min.js",n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{const e={792:1};n.f.require=(t,a)=>{if(!e[t]){const a=require("./"+n.u(t));e[t]||(t=>{const a=t.modules,o=t.ids,r=t.runtime;for(var s in a)n.o(a,s)&&(n.m[s]=a[s]);r&&r(n);for(var i=0;i<o.length;i++)e[o[i]]=1})(a)}}})();let a={};var o,r,s,i,c,l;n.r(a),n.d(a,{ChatModelId:()=>l,ChatStatus:()=>r,LlmBackend:()=>i,LlmPhase:()=>c,LlmRole:()=>o,LlmStatus:()=>s,createChat:()=>G,preloadModel:()=>N}),function(e){e.system="system",e.user="user",e.assistant="assistant"}(o||(o={})),function(e){e[e.online=0]="online",e[e.away=1]="away",e[e.offline=2]="offline"}(r||(r={})),function(e){e[e.idle=0]="idle",e[e.downloading=1]="downloading",e[e.ready=2]="ready",e[e.error=3]="error",e[e.unsupported=4]="unsupported"}(s||(s={})),function(e){e.cpu="cpu",e.none="none"}(i||(i={})),function(e){e.download="download",e.load="load"}(c||(c={})),function(e){e.qwen3_vl_2b="qwen3-vl-2b"}(l||(l={}));const d=[{id:l.qwen3_vl_2b,reasoning:!0,cpu:{repo:"Qwen/Qwen3-VL-2B-Thinking-GGUF",file:"Qwen3VL-2B-Thinking-Q4_K_M.gguf",mmproj:{file:"mmproj-Qwen3VL-2B-Thinking-Q8_0.gguf"}}}],g=e=>e?d.find(t=>t.id==e):void 0,u=e=>!!e.cpu.mmproj,m=require("node:path"),p=()=>{return e=(0,m.join)(__dirname,"../assets/wllama"),{wasm:(0,m.join)(e,"wllama.wasm"),compatWasm:(0,m.join)(e,"compat/wllama.wasm"),compatWorker:(0,m.join)(e,"compat/wllama.js")};var e},f=288,h=async e=>{if(!e||"function"!=typeof fetch||"function"!=typeof createImageBitmap)return e;try{const t=await fetch(e),n=await createImageBitmap(await t.blob()),a=Math.min(n.width,n.height),o=Math.floor((n.width-a)/2),r=Math.floor((n.height-a)/2),s=((e,t)=>{if("undefined"!=typeof OffscreenCanvas)return new OffscreenCanvas(e,t);if("undefined"==typeof document)return;const n=document.createElement("canvas");return n.width=e,n.height=t,n})(f,f),i=s?s.getContext("2d"):null;if(!s||!i||a<1)return n.close(),e;if(i.drawImage(n,o,r,a,a,0,0,f,f),n.close(),(e=>"undefined"!=typeof OffscreenCanvas&&e instanceof OffscreenCanvas)(s)){const t=await s.convertToBlob({type:"image/jpeg",quality:.85});return await(e=>new Promise(t=>{if("undefined"==typeof FileReader)return void t("");const n=new FileReader;n.onerror=()=>t(""),n.onload=()=>t(`${n.result}`),n.readAsDataURL(e)}))(t)||e}return s.toDataURL("image/jpeg",.85)}catch{return e}},y={maxReplyTokens:200,maxThinkingTokens:64,maxReasoningTokens:150,maxImages:2,maxContentLength:1e3,promptCharBudget:1e4,maxThreads:2},w=(e,t,n)=>"number"==typeof e&&Number.isFinite(e)&&e>=n?Math.floor(e):t,x=(e={})=>{const t=y;return{maxReplyTokens:w(e.maxReplyTokens,t.maxReplyTokens,1),maxThinkingTokens:w(e.maxThinkingTokens,t.maxThinkingTokens,1),maxReasoningTokens:w(e.maxReasoningTokens,t.maxReasoningTokens,1),maxImages:w(e.maxImages,t.maxImages,0),maxContentLength:w(e.maxContentLength,t.maxContentLength,1),promptCharBudget:w(e.promptCharBudget,t.promptCharBudget,1),maxThreads:w(e.maxThreads,t.maxThreads,1)}},v=["<|channel>thought\n","<|channel>thought","..."],k=["<channel|>","..."],b=(e,t)=>t.length?[...e?[{type:"text",text:e}]:[],...t.map(e=>({type:"image",url:e}))]:e,M=e=>"string"==typeof e?e?[{type:"text",text:e}]:[]:e,T=(e,t)=>{if("string"==typeof e&&"string"==typeof t)return`${e}\n${t}`;const n=[];for(const a of[...M(e),{type:"text",text:"\n"},...M(t)]){const e=n[n.length-1];"text"==a.type&&"text"==e?.type?e.text+=a.text:n.push(a)}return n},C=async({model:e,logging:t,onReason:n,onText:a,start:o,stopped:r})=>{let s="",i="",c="";const l=((e,t)=>{let n="",a=!1;const o=(e,t)=>{let n=-1,a="";for(const o of t){const t=e.indexOf(o);t>=0&&(n<0||t<n)&&(n=t,a=o)}return{at:n,tag:a}},r=(e,t)=>{let n="";for(const a of t)for(let t=Math.min(e.length,a.length-1);t>n.length;t--)if(e.endsWith(a.slice(0,t))){n=e.slice(e.length-t);break}return n};return{feed:s=>{let i=n+s;for(n="";i;){if(a){const{at:t,tag:s}=o(i,k);if(t>=0){t&&e(i.slice(0,t)),i=i.slice(t+s.length),a=!1;continue}const c=r(i,k);e(i.slice(0,i.length-c.length)),n=c;break}{const{at:e,tag:s}=o(i,v);if(e>=0){e&&t(i.slice(0,e)),i=i.slice(e+s.length),i.startsWith("\n")&&(i=i.slice(1)),a=!0;continue}const c=r(i,v);t(i.slice(0,i.length-c.length)),n=c;break}}},flush:()=>{n&&(a?e(n):t(n),n="")}}})(e=>{i+=e,n(e)},e=>{s+=e,a(e)});try{const e=await o();for await(const t of e){if(r())break;const e=t?.choices?.[0],a=e?.delta,o="string"==typeof a?.reasoning_content?a.reasoning_content:"string"==typeof a?.reasoning?a.reasoning:"";e?.finish_reason&&(c=e.finish_reason),o&&(i+=o,n(o)),a?.content&&l.feed(a.content)}l.flush()}catch(e){t&&console.log("Local chat failed",e)}const d=r()?void 0:s||void 0;return t&&(console.log("Local chat reply",e.id,d?"****":""),console.log("Local chat finish",e.id,c||"none",s.length,i.length)),d},_=async({model:e,params:t,start:n,stopped:a})=>{const r=await(async e=>{const t=[];for(const n of e){if("string"==typeof n.content){t.push(n);continue}const e=[];for(const t of n.content)e.push("image"==t.type?{type:"image",url:await h(t.url)}:t);t.push({role:"user",content:e})}return t})(((e,t=y)=>{const n=t.maxContentLength,a=e.filter(e=>"string"==typeof e.content&&!!e.content.trim()||!(e.role!=o.user||!e.images?.length)),r=[],s=[];let i=0,c=t.maxImages;for(let e=a.length-1;e>=0;e--){const l=a[e],d=("string"==typeof l.content?l.content:"").slice(0,n),g=l.role==o.user?(l.images||[]).filter(e=>"string"==typeof e&&!!e).slice(0,Math.max(0,c)):[];if(d||g.length){if(d&&i+d.length>t.promptCharBudget)break;i+=d.length,c-=g.length,l.role==o.system?r.unshift(d):l.role==o.assistant?s.unshift({role:"assistant",content:d}):s.unshift({role:"user",content:b(d,g)})}}for(;s.length&&"user"!=s[0].role;)s.shift();const l=[];for(const e of s){const t=l[l.length-1];"assistant"==t?.role&&"assistant"==e.role?t.content=`${t.content}\n${e.content}`:"user"==t?.role&&"user"==e.role?t.content=T(t.content,e.content):"assistant"==e.role?l.push({role:"assistant",content:e.content}):l.push({role:"user",content:e.content})}const d=r.join("\n\n"),g=[];return d&&g.push({role:"system",content:d}),g.push(...l),g})(t.messages,t.limits));return t.logging&&((e,t)=>{console.log("Local chat call",e.id,t.map(({role:e,content:t})=>({role:e,content:"****",...Array.isArray(t)?{images:t.filter(e=>"image"==e.type).length}:{}})))})(e,r),await C({model:e,logging:t.logging,onReason:t.onReason,onText:t.onText,start:()=>n(r),stopped:a})};let L,j,q="",R=!1;const W=e=>`${e.wasm}|${e.compatWasm}|${e.compatWorker}`,$=e=>{const t=p();return{wasm:e?.wasm||t.wasm,compatWasm:e?.compatWasm||t.compatWasm,compatWorker:e?.compatWorker||t.compatWorker}},B=e=>{const t=e=>(...t)=>{R&&("string"==typeof t[0]&&t[0].includes("ggml_webgpu: Failed to get an adapter")||e(...t))};return{...e,log:t(e.log),warn:t(e.warn),error:t(e.error)}},I=async()=>{if(L)return L;const e=await n.e(924).then(()=>n(577)),t=j||$(),a=new e.Wllama({default:t.wasm},{logger:B(e.LoggerWithoutDebug)});return j||(j=t,q=W(t)),a.setCompat({wasm:t.compatWasm,worker:t.compatWorker}),L=a,a},P=async()=>{const e=L;L=void 0;try{await(e?.exit())}catch{}},O=(e,t)=>e.name.includes(t)||!!e.metadata?.originalURL?.includes(t),A=e=>[e.cpu.file,...e.cpu.mmproj?[e.cpu.mmproj.file]:[]],U=(e,t)=>`https://huggingface.co/${e}/resolve/main/${t}`,S=async e=>{if(!e.startsWith("data:"))return await(await fetch(e)).arrayBuffer();const t=e.indexOf(","),n=t>=0?e.slice(0,t):"";if(t<0)throw new Error("Invalid image data");if(!n.includes(";base64"))return await(await fetch(e)).arrayBuffer();const a=atob(e.slice(t+1)),o=new Uint8Array(a.length);for(let e=0;e<a.length;e++)o[e]=a.charCodeAt(e);return o.buffer},F=async e=>{const t=[];for(const n of e){if("string"==typeof n.content){"system"==n.role?t.push({role:"system",content:n.content}):"assistant"==n.role?t.push({role:"assistant",content:n.content}):t.push({role:"user",content:n.content});continue}const e=[];for(const t of n.content)e.push("text"==t.type?{type:"text",text:t.text}:{type:"image",data:await S(t.url)});t.push({role:"user",content:e})}return t},E=async(e,t,n)=>{if(R=!0===n.logging,e.isModelLoaded())return await _({model:t,params:n,stopped:()=>!!n.signal?.aborted,start:async a=>{if((e=>e.some(e=>Array.isArray(e.content)&&e.content.some(e=>"image"==e.type)))(a)&&!e.supportInputModality("image"))throw new Error("Model does not accept images");return await e.createChatCompletion({messages:await F(a),...(o=n.limits,r=n.reasoning,{stream:!0,temperature:.7,max_tokens:r?o.maxReasoningTokens:o.maxReplyTokens}),abortSignal:n.signal,...t.reasoning?{chat_template_kwargs:{enable_thinking:n.reasoning}}:{}});var o,r}})},Q={backend:i.cpu,startLLM:async({model:e,limits:t,onProgress:n,signal:a,logging:o,assets:r})=>{R=!0===o;const s=$(r),l=W(s);L&&l!=q&&await P(),j=s,q=l;const d=(g="undefined"!=typeof navigator&&navigator.hardwareConcurrency||4,u=t.maxThreads,Math.max(1,Math.min(Math.max(2,Math.min(8,(g||4)-1)),u)));var g,u;const m=await I(),p=U(e.cpu.repo,e.cpu.file),f=e.cpu.mmproj?.file,h=f?U(e.cpu.repo,f):"";if(a?.aborted)throw await P(),new Error("Cancelled");if(f){const t=await m.cacheManager.list(),n=t.some(t=>O(t,e.cpu.file)),a=t.some(e=>O(e,f));n&&!a&&await m.cacheManager.deleteMany(t=>O(t,e.cpu.file))}try{await m.loadModelFromUrl(h?{url:p,mmprojUrl:h}:p,{n_ctx:8192,n_threads:d,reasoning_budget_tokens:t.maxThinkingTokens,n_gpu_layers:0,useCache:!0,jinja:!0,signal:a,progressCallback:({loaded:e,total:t})=>{const a=t?e/t:0;n(a,a>=1?c.load:c.download)}})}catch(e){throw await P(),e}if(a?.aborted)throw await P(),new Error("Cancelled");if(!m.getLoadedContextInfo().n_ctx)throw await P(),new Error("Failed to create model context");return{model:e,backend:i.cpu,contextSize:8192,chat:t=>E(m,e,t),unload:async()=>{if(L==m)await P();else try{await m.exit()}catch{}}}},cached:async e=>{try{const t=await I(),n=await t.cacheManager.list();return A(e).every(e=>n.some(t=>O(t,e)))}catch{return!1}},hasCache:async()=>{try{const e=await I();return(await e.cacheManager.list()).length>0}catch{return!1}},remove:async e=>{try{const t=await I();await t.cacheManager.deleteMany(t=>A(e).some(e=>O(t,e)))}catch{}},purge:async()=>{try{const e=await I();await e.cacheManager.clear()}catch{}}},D=((e={})=>{const t=e.engines?.cpu||Q;let n,a,o,r,l,g,u,m=i.none,p=s.idle,f=0,h={percent:0,phase:c.download};const y=()=>e.backend||("undefined"==typeof window&&"undefined"==typeof self&&"undefined"==typeof Worker||"undefined"==typeof WebAssembly?i.none:i.cpu),w=async()=>{await t.purge()},x=(e,t)=>{h={percent:Math.max(0,Math.min(1,e)),phase:t||c.download},g?.(h.percent,h.phase)},v=e=>{p=e},k=()=>p==s.ready&&!!n&&!!a;return{load:async({model:e,limits:u,assets:p,logging:b,onProgress:M})=>{if(k()&&a?.id==e.id)return!0;if(o&&r==e.id)return g=M,M(h.percent,h.phase),await o;o&&(g=void 0,l?.abort(),await o.catch(()=>!1)),g=M;const T=++f,C=(async()=>{await(async()=>{try{if("undefined"==typeof navigator)return;await(navigator.storage?.persist?.())}catch{}})();const o=y();if(T!=f)return!1;if(o==i.none)return v(s.unsupported),!1;if(await(async e=>{if(a&&a.id!=e.id){const e=a,o=n;n=void 0,a=void 0,await(o?.unload()),await t.remove(e)}if(await t.hasCache())for(const n of d)n.id!=e.id&&await t.remove(n)})(e),T!=f)return!1;const r=await t.cached(e);if(T!=f)return!1;if(!r&&await(async()=>{try{if("undefined"==typeof navigator)return!1;if(!navigator.storage?.estimate)return!1;const{quota:e=0,usage:t=0}=await navigator.storage.estimate();return!!e&&t>=e}catch{return!1}})()&&await w(),T!=f)return!1;v(s.downloading),x(0,c.download),r&&x(1,c.load);const g=new AbortController;l=g;try{const r=await t.startLLM({model:e,limits:u,logging:b,signal:g.signal,assets:p,onProgress:(e,t)=>{T==f&&x(e,t)}});return T!=f||g.signal.aborted?(await r.unload(),!1):(n=r,a=e,m=o,v(s.ready),x(1,c.load),!0)}catch(e){return T!=f||(n=void 0,a=void 0,b&&console.log("Local model failed",e),(e=>["quota","no space","disk full","ns_error_dom_quota_reached"].some(t=>`${e?.name||""} ${e?.message||`${e}`}`.toLowerCase().includes(t)))(e)&&await w(),v(s.error)),!1}finally{l==g&&(l=void 0)}})().finally(()=>{o==C&&(o=void 0,r=void 0)});return o=C,r=e.id,await C},chat:async e=>{if(!k()||!n)return;for(;u;)await u.catch(()=>{});if(!k()||!n)return;const t=n.chat(e);u=t;try{return await t}finally{u==t&&(u=void 0)}},unload:async()=>{f++,l?.abort(),l=void 0,r=void 0;const e=n;n=void 0,a=void 0,v(s.idle),await(e?.unload())},cancelLoad:()=>{f++,l?.abort(),l=void 0,r=void 0,p==s.downloading&&v(s.idle)},ready:k,loading:()=>!k()&&p==s.downloading,status:()=>p,backend:()=>m,model:()=>a}})(),V=(e,t,n)=>{const a=e.status(),o=g(t);return{availability:a==s.ready?r.online:a==s.error||a==s.unsupported?r.offline:r.away,status:a,backend:e.backend(),supportsImages:!!o&&u(o),ready:e.ready(),loading:e.loading(),requesting:n.requesting,phase:n.phase,percent:n.percent,...n.error?{error:n.error}:{}}},G=(e=>{let t,n=0;const a=t=>t.input.onStatus?.(V(e,t.input.model,t));return r=>{t?.controller.abort();const i={token:++n,input:r,controller:new AbortController,percent:e.ready()?100:0,phase:e.ready()?c.load:c.download,requesting:!1,cancelled:!1};return t=i,a(i),(async t=>{const{input:r}=t,s=!0===r.logging,i=g(r.model),l=r.messages.some(e=>e.role!=o.system&&"string"==typeof e.content&&!!e.content.trim()),d=r.messages.some(e=>e.role==o.user&&!!e.images?.some(e=>"string"==typeof e&&!!e)),m=l||d;if(!i)return t.error="Unknown model",void a(t);if(!m)return void a(t);if(d&&!l&&!u(i))return t.error="Model does not accept images",void a(t);const p=x(r.limits),f=await e.load({model:i,limits:p,logging:s,onProgress:(e,o)=>{t.token==n&&(t.percent=Math.round(100*e),t.phase=o,a(t))}}).catch(e=>(t.token==n&&(t.error=`${e?.message||`${e}`}`),!1));if(t.token!=n)return;if(!f)return t.cancelled||t.error||(t.error="Model not ready"),void a(t);const h=u(i),y=d&&!h?r.messages.map(({images:e,...t})=>t):r.messages;if(d&&!h&&!l)return t.error="Model does not accept images",void a(t);t.requesting=!0,t.error=void 0,t.percent=100,t.phase=c.load,a(t);let w,v="",k="";try{w=await e.chat({messages:y,limits:p,reasoning:!0===r.reasoning,logging:s,onReason:e=>{t.token!=n||t.cancelled||(v+=e,r.onThinking?.(v))},onText:e=>{t.token!=n||t.cancelled||(k+=e,r.onResponse?.(k))},signal:t.controller.signal})}catch(e){if(t.token!=n)return;return t.requesting=!1,t.error=`${e?.message||`${e}`}`,void a(t)}t.token==n&&(t.requesting=!1,w||t.cancelled||(t.error="No reply"),a(t))})(i),()=>(t=>{t.token==n&&(t.cancelled=!0,t.controller.abort(),e.status()!=s.ready?(e.cancelLoad(),a(t)):e.unload().then(()=>a(t)))})(i)}})(D),N=(z=D,async e=>{const t=g(e.model),n=t=>e.onStatus?.(V(z,e.model,t));if(!t)return n({requesting:!1,phase:c.download,percent:0,error:"Unknown model"}),!1;let a,o=0,r=c.download;const s=z.ready()&&z.model()?.id==t.id;return await z.load({model:t,limits:x(e.limits),logging:!0===e.logging,onProgress:(e,t)=>{o=Math.round(100*e),r=t,n({requesting:!1,phase:r,percent:o})}}).catch(e=>(a=`${e?.message||`${e}`}`,!1))?(s&&n({requesting:!1,phase:c.load,percent:100}),!0):(n({requesting:!1,phase:r,percent:o,error:a||"Model not ready"}),!1)});var z;module.exports=a})();
@@ -1,2 +1,2 @@
1
1
  /*! For license information please see chat.node.min.mjs.LICENSE.txt */
2
- import{dirname as e,join as t}from"node:path";import{fileURLToPath as n}from"node:url";var a={};const o={};function r(e){const t=o[e];if(void 0!==t)return t.exports;const n=o[e]={exports:{}};return a[e](n,n.exports,r),n.exports}var s,i,c,l,d,g;r.m=a,r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.f={},r.e=e=>{const t=[];return r.f.j(e,t),Promise.all(t)},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{const e={924:()=>import("./engine-wllama.node.min.mjs")},t={792:0},n=e=>{let{__webpack_esm_ids__:n,__webpack_esm_modules__:a,__webpack_esm_runtime__:o}=e;var s,i,c=0;for(s in a)r.o(a,s)&&(r.m[s]=a[s]);for(o&&o(r);c<n.length;c++)i=n[c],r.o(t,i)&&t[i]&&t[i][0](),t[i]=0};r.f.j=(a,o)=>{let s=r.o(t,a)?t[a]:void 0;if(0!==s)if(s)o.push(s[1]);else{let r=e[a]().then(n,e=>{throw 0!==t[a]&&(t[a]=void 0),e});r=Promise.race([r,new Promise(e=>s=t[a]=[e])]),o.push(s[1]=r)}}})(),function(e){e.system="system",e.user="user",e.assistant="assistant"}(s||(s={})),function(e){e[e.online=0]="online",e[e.away=1]="away",e[e.offline=2]="offline"}(i||(i={})),function(e){e[e.idle=0]="idle",e[e.downloading=1]="downloading",e[e.ready=2]="ready",e[e.error=3]="error",e[e.unsupported=4]="unsupported"}(c||(c={})),function(e){e.cpu="cpu",e.none="none"}(l||(l={})),function(e){e.download="download",e.load="load"}(d||(d={})),function(e){e.qwen3_vl_2b="qwen3-vl-2b"}(g||(g={}));const u=[{id:g.qwen3_vl_2b,reasoning:!0,cpu:{repo:"Qwen/Qwen3-VL-2B-Thinking-GGUF",file:"Qwen3VL-2B-Thinking-Q4_K_M.gguf",mmproj:{file:"mmproj-Qwen3VL-2B-Thinking-Q8_0.gguf"}}}],m=e=>e?u.find(t=>t.id==e):void 0,f=e=>!!e.cpu.mmproj,p=()=>{return a=t(e(n("file:///media/x/store/code/DegreeSign/ds_chat/src/assets/nodeEsm.js")),"../assets/wllama"),{wasm:t(a,"wllama.wasm"),compatWasm:t(a,"compat/wllama.wasm"),compatWorker:t(a,"compat/wllama.js")};var a},h=288,w=async e=>{if(!e||"function"!=typeof fetch||"function"!=typeof createImageBitmap)return e;try{const t=await fetch(e),n=await createImageBitmap(await t.blob()),a=Math.min(n.width,n.height),o=Math.floor((n.width-a)/2),r=Math.floor((n.height-a)/2),s=((e,t)=>{if("undefined"!=typeof OffscreenCanvas)return new OffscreenCanvas(e,t);if("undefined"==typeof document)return;const n=document.createElement("canvas");return n.width=e,n.height=t,n})(h,h),i=s?s.getContext("2d"):null;if(!s||!i||a<1)return n.close(),e;if(i.drawImage(n,o,r,a,a,0,0,h,h),n.close(),(e=>"undefined"!=typeof OffscreenCanvas&&e instanceof OffscreenCanvas)(s)){const t=await s.convertToBlob({type:"image/jpeg",quality:.85});return await(e=>new Promise(t=>{if("undefined"==typeof FileReader)return void t("");const n=new FileReader;n.onerror=()=>t(""),n.onload=()=>t(`${n.result}`),n.readAsDataURL(e)}))(t)||e}return s.toDataURL("image/jpeg",.85)}catch{return e}},y={maxReplyTokens:200,maxThinkingTokens:64,maxReasoningTokens:150,maxImages:2,maxContentLength:1e3,promptCharBudget:1e4,maxThreads:2},x=(e,t,n)=>"number"==typeof e&&Number.isFinite(e)&&e>=n?Math.floor(e):t,k=["<|channel>thought\n","<|channel>thought","..."],v=["<channel|>","..."],b=(e,t)=>t.length?[...e?[{type:"text",text:e}]:[],...t.map(e=>({type:"image",url:e}))]:e,_=e=>"string"==typeof e?e?[{type:"text",text:e}]:[]:e,T=(e,t)=>{if("string"==typeof e&&"string"==typeof t)return`${e}\n${t}`;const n=[];for(const a of[..._(e),{type:"text",text:"\n"},..._(t)]){const e=n[n.length-1];"text"==a.type&&"text"==e?.type?e.text+=a.text:n.push(a)}return n},M=async({model:e,logging:t,onReason:n,onText:a,start:o,stopped:r})=>{let s="",i="",c="";const l=((e,t)=>{let n="",a=!1;const o=(e,t)=>{let n=-1,a="";for(const o of t){const t=e.indexOf(o);t>=0&&(n<0||t<n)&&(n=t,a=o)}return{at:n,tag:a}},r=(e,t)=>{let n="";for(const a of t)for(let t=Math.min(e.length,a.length-1);t>n.length;t--)if(e.endsWith(a.slice(0,t))){n=e.slice(e.length-t);break}return n};return{feed:s=>{let i=n+s;for(n="";i;){if(a){const{at:t,tag:s}=o(i,v);if(t>=0){t&&e(i.slice(0,t)),i=i.slice(t+s.length),a=!1;continue}const c=r(i,v);e(i.slice(0,i.length-c.length)),n=c;break}{const{at:e,tag:s}=o(i,k);if(e>=0){e&&t(i.slice(0,e)),i=i.slice(e+s.length),i.startsWith("\n")&&(i=i.slice(1)),a=!0;continue}const c=r(i,k);t(i.slice(0,i.length-c.length)),n=c;break}}},flush:()=>{n&&(a?e(n):t(n),n="")}}})(e=>{i+=e,n(e)},e=>{s+=e,a(e)});try{const e=await o();for await(const t of e){if(r())break;const e=t?.choices?.[0],a=e?.delta,o="string"==typeof a?.reasoning_content?a.reasoning_content:"string"==typeof a?.reasoning?a.reasoning:"";e?.finish_reason&&(c=e.finish_reason),o&&(i+=o,n(o)),a?.content&&l.feed(a.content)}l.flush()}catch(e){t&&console.log("Local chat failed",e)}const d=r()?void 0:s||void 0;return t&&(console.log("Local chat reply",e.id,d?"****":""),console.log("Local chat finish",e.id,c||"none",s.length,i.length)),d},C=async({model:e,params:t,start:n,stopped:a})=>{const o=await(async e=>{const t=[];for(const n of e){if("string"==typeof n.content){t.push(n);continue}const e=[];for(const t of n.content)e.push("image"==t.type?{type:"image",url:await w(t.url)}:t);t.push({role:"user",content:e})}return t})(((e,t=y)=>{const n=t.maxContentLength,a=e.filter(e=>"string"==typeof e.content&&!!e.content.trim()||!(e.role!=s.user||!e.images?.length)),o=[],r=[];let i=0,c=t.maxImages;for(let e=a.length-1;e>=0;e--){const l=a[e],d=("string"==typeof l.content?l.content:"").slice(0,n),g=l.role==s.user?(l.images||[]).filter(e=>"string"==typeof e&&!!e).slice(0,Math.max(0,c)):[];if(d||g.length){if(d&&i+d.length>t.promptCharBudget)break;i+=d.length,c-=g.length,l.role==s.system?o.unshift(d):l.role==s.assistant?r.unshift({role:"assistant",content:d}):r.unshift({role:"user",content:b(d,g)})}}for(;r.length&&"user"!=r[0].role;)r.shift();const l=[];for(const e of r){const t=l[l.length-1];"assistant"==t?.role&&"assistant"==e.role?t.content=`${t.content}\n${e.content}`:"user"==t?.role&&"user"==e.role?t.content=T(t.content,e.content):"assistant"==e.role?l.push({role:"assistant",content:e.content}):l.push({role:"user",content:e.content})}const d=o.join("\n\n"),g=[];return d&&g.push({role:"system",content:d}),g.push(...l),g})(t.messages,t.limits));return t.logging&&((e,t)=>{console.log("Local chat call",e.id,t.map(({role:e,content:t})=>({role:e,content:"****",...Array.isArray(t)?{images:t.filter(e=>"image"==e.type).length}:{}})))})(e,o),await M({model:e,logging:t.logging,onReason:t.onReason,onText:t.onText,start:()=>n(o),stopped:a})};let L,j,R="",W=!1;const $=e=>`${e.wasm}|${e.compatWasm}|${e.compatWorker}`,q=e=>{const t=p();return{wasm:e?.wasm||t.wasm,compatWasm:e?.compatWasm||t.compatWasm,compatWorker:e?.compatWorker||t.compatWorker}},B=e=>{const t=e=>(...t)=>{W&&("string"==typeof t[0]&&t[0].includes("ggml_webgpu: Failed to get an adapter")||e(...t))};return{...e,log:t(e.log),warn:t(e.warn),error:t(e.error)}},P=async()=>{if(L)return L;const e=await r.e(924).then(()=>r(577)),t=j||q(),n=new e.Wllama({default:t.wasm},{logger:B(e.LoggerWithoutDebug)});return j||(j=t,R=$(t)),n.setCompat({wasm:t.compatWasm,worker:t.compatWorker}),L=n,n},I=async()=>{const e=L;L=void 0;try{await(e?.exit())}catch{}},O=(e,t)=>e.name.includes(t)||!!e.metadata?.originalURL?.includes(t),A=e=>[e.cpu.file,...e.cpu.mmproj?[e.cpu.mmproj.file]:[]],U=(e,t)=>`https://huggingface.co/${e}/resolve/main/${t}`,E=async e=>{if(!e.startsWith("data:"))return await(await fetch(e)).arrayBuffer();const t=e.indexOf(","),n=t>=0?e.slice(0,t):"";if(t<0)throw new Error("Invalid image data");if(!n.includes(";base64"))return await(await fetch(e)).arrayBuffer();const a=atob(e.slice(t+1)),o=new Uint8Array(a.length);for(let e=0;e<a.length;e++)o[e]=a.charCodeAt(e);return o.buffer},F=async e=>{const t=[];for(const n of e){if("string"==typeof n.content){"system"==n.role?t.push({role:"system",content:n.content}):"assistant"==n.role?t.push({role:"assistant",content:n.content}):t.push({role:"user",content:n.content});continue}const e=[];for(const t of n.content)e.push("text"==t.type?{type:"text",text:t.text}:{type:"image",data:await E(t.url)});t.push({role:"user",content:e})}return t},Q=async(e,t,n)=>{if(W=!0===n.logging,e.isModelLoaded())return await C({model:t,params:n,stopped:()=>!!n.signal?.aborted,start:async a=>{if((e=>e.some(e=>Array.isArray(e.content)&&e.content.some(e=>"image"==e.type)))(a)&&!e.supportInputModality("image"))throw new Error("Model does not accept images");return await e.createChatCompletion({messages:await F(a),...(o=n.limits,r=n.reasoning,{stream:!0,temperature:.7,max_tokens:r?o.maxReasoningTokens:o.maxReplyTokens}),abortSignal:n.signal,...t.reasoning?{chat_template_kwargs:{enable_thinking:n.reasoning}}:{}});var o,r}})},S={backend:l.cpu,startLLM:async({model:e,limits:t,onProgress:n,signal:a,logging:o,assets:r})=>{W=!0===o;const s=q(r),i=$(s);L&&i!=R&&await I(),j=s,R=i;const c=(g="undefined"!=typeof navigator&&navigator.hardwareConcurrency||4,u=t.maxThreads,Math.max(1,Math.min(Math.max(2,Math.min(8,(g||4)-1)),u)));var g,u;const m=await P(),f=U(e.cpu.repo,e.cpu.file),p=e.cpu.mmproj?.file,h=p?U(e.cpu.repo,p):"";if(a?.aborted)throw await I(),new Error("Cancelled");if(p){const t=await m.cacheManager.list(),n=t.some(t=>O(t,e.cpu.file)),a=t.some(e=>O(e,p));n&&!a&&await m.cacheManager.deleteMany(t=>O(t,e.cpu.file))}try{await m.loadModelFromUrl(h?{url:f,mmprojUrl:h}:f,{n_ctx:8192,n_threads:c,reasoning_budget_tokens:t.maxThinkingTokens,n_gpu_layers:0,useCache:!0,jinja:!0,signal:a,progressCallback:({loaded:e,total:t})=>{const a=t?e/t:0;n(a,a>=1?d.load:d.download)}})}catch(e){throw await I(),e}if(a?.aborted)throw await I(),new Error("Cancelled");if(!m.getLoadedContextInfo().n_ctx)throw await I(),new Error("Failed to create model context");return{model:e,backend:l.cpu,contextSize:8192,chat:t=>Q(m,e,t),unload:async()=>{if(L==m)await I();else try{await m.exit()}catch{}}}},cached:async e=>{try{const t=await P(),n=await t.cacheManager.list();return A(e).every(e=>n.some(t=>O(t,e)))}catch{return!1}},hasCache:async()=>{try{const e=await P();return(await e.cacheManager.list()).length>0}catch{return!1}},remove:async e=>{try{const t=await P();await t.cacheManager.deleteMany(t=>A(e).some(e=>O(t,e)))}catch{}},purge:async()=>{try{const e=await P();await e.cacheManager.clear()}catch{}}},D=((e={})=>{const t=e.engines?.cpu||S;let n,a,o,r,s,i,g,m=l.none,f=c.idle,p=0,h={percent:0,phase:d.download};const w=()=>e.backend||("undefined"==typeof window&&"undefined"==typeof self&&"undefined"==typeof Worker||"undefined"==typeof WebAssembly?l.none:l.cpu),y=async()=>{await t.purge()},x=(e,t)=>{h={percent:Math.max(0,Math.min(1,e)),phase:t||d.download},i?.(h.percent,h.phase)},k=e=>{f=e},v=()=>f==c.ready&&!!n&&!!a;return{load:async({model:e,limits:g,assets:f,logging:b,onProgress:_})=>{if(v()&&a?.id==e.id)return!0;if(o&&r==e.id)return i=_,_(h.percent,h.phase),await o;o&&(i=void 0,s?.abort(),await o.catch(()=>!1)),i=_;const T=++p,M=(async()=>{await(async()=>{try{if("undefined"==typeof navigator)return;await(navigator.storage?.persist?.())}catch{}})();const o=w();if(T!=p)return!1;if(o==l.none)return k(c.unsupported),!1;if(await(async e=>{if(a&&a.id!=e.id){const e=a,o=n;n=void 0,a=void 0,await(o?.unload()),await t.remove(e)}if(await t.hasCache())for(const n of u)n.id!=e.id&&await t.remove(n)})(e),T!=p)return!1;const r=await t.cached(e);if(T!=p)return!1;if(!r&&await(async()=>{try{if("undefined"==typeof navigator)return!1;if(!navigator.storage?.estimate)return!1;const{quota:e=0,usage:t=0}=await navigator.storage.estimate();return!!e&&t>=e}catch{return!1}})()&&await y(),T!=p)return!1;k(c.downloading),x(0,d.download),r&&x(1,d.load);const i=new AbortController;s=i;try{const r=await t.startLLM({model:e,limits:g,logging:b,signal:i.signal,assets:f,onProgress:(e,t)=>{T==p&&x(e,t)}});return T!=p||i.signal.aborted?(await r.unload(),!1):(n=r,a=e,m=o,k(c.ready),x(1,d.load),!0)}catch(e){return T!=p||(n=void 0,a=void 0,b&&console.log("Local model failed",e),(e=>["quota","no space","disk full","ns_error_dom_quota_reached"].some(t=>`${e?.name||""} ${e?.message||`${e}`}`.toLowerCase().includes(t)))(e)&&await y(),k(c.error)),!1}finally{s==i&&(s=void 0)}})().finally(()=>{o==M&&(o=void 0,r=void 0)});return o=M,r=e.id,await M},chat:async e=>{if(!v()||!n)return;for(;g;)await g.catch(()=>{});if(!v()||!n)return;const t=n.chat(e);g=t;try{return await t}finally{g==t&&(g=void 0)}},unload:async()=>{p++,s?.abort(),s=void 0,r=void 0;const e=n;n=void 0,a=void 0,k(c.idle),await(e?.unload())},cancelLoad:()=>{p++,s?.abort(),s=void 0,r=void 0,f==c.downloading&&k(c.idle)},ready:v,loading:()=>!v()&&f==c.downloading,status:()=>f,backend:()=>m,model:()=>a}})(),V=(e=>{let t,n=0;const a=t=>{const n=e.status(),a=e.backend(),o=m(t.input.model),r={availability:n==c.ready?i.online:n==c.error||n==c.unsupported?i.offline:i.away,status:n,backend:a,supportsImages:!!o&&f(o),ready:e.ready(),loading:e.loading(),requesting:t.requesting,phase:t.phase,percent:t.percent,...t.error?{error:t.error}:{}};t.input.onStatus?.(r)},o=async t=>{const{input:o}=t,r=!0===o.logging,i=m(o.model),c=o.messages.some(e=>e.role!=s.system&&"string"==typeof e.content&&!!e.content.trim()),l=o.messages.some(e=>e.role==s.user&&!!e.images?.some(e=>"string"==typeof e&&!!e)),g=c||l;if(!i)return t.error="Unknown model",void a(t);if(!g)return void a(t);if(l&&!c&&!f(i))return t.error="Model does not accept images",void a(t);const u=((e={})=>{const t=y;return{maxReplyTokens:x(e.maxReplyTokens,t.maxReplyTokens,1),maxThinkingTokens:x(e.maxThinkingTokens,t.maxThinkingTokens,1),maxReasoningTokens:x(e.maxReasoningTokens,t.maxReasoningTokens,1),maxImages:x(e.maxImages,t.maxImages,0),maxContentLength:x(e.maxContentLength,t.maxContentLength,1),promptCharBudget:x(e.promptCharBudget,t.promptCharBudget,1),maxThreads:x(e.maxThreads,t.maxThreads,1)}})(o.limits),p=await e.load({model:i,limits:u,logging:r,onProgress:(e,o)=>{t.token==n&&(t.percent=Math.round(100*e),t.phase=o,a(t))}}).catch(e=>(t.token==n&&(t.error=`${e?.message||`${e}`}`),!1));if(t.token!=n)return;if(!p)return t.cancelled||t.error||(t.error="Model not ready"),void a(t);const h=f(i),w=l&&!h?o.messages.map(({images:e,...t})=>t):o.messages;if(l&&!h&&!c)return t.error="Model does not accept images",void a(t);t.requesting=!0,t.error=void 0,t.percent=100,t.phase=d.load,a(t);let k,v="",b="";try{k=await e.chat({messages:w,limits:u,reasoning:!0===o.reasoning,logging:r,onReason:e=>{t.token!=n||t.cancelled||(v+=e,o.onThinking?.(v))},onText:e=>{t.token!=n||t.cancelled||(b+=e,o.onResponse?.(b))},signal:t.controller.signal})}catch(e){if(t.token!=n)return;return t.requesting=!1,t.error=`${e?.message||`${e}`}`,void a(t)}t.token==n&&(t.requesting=!1,k||t.cancelled||(t.error="No reply"),a(t))};return r=>{t?.controller.abort();const s={token:++n,input:r,controller:new AbortController,percent:e.ready()?100:0,phase:e.ready()?d.load:d.download,requesting:!1,cancelled:!1};return t=s,a(s),o(s),()=>(t=>{t.token==n&&(t.cancelled=!0,t.controller.abort(),e.status()!=c.ready?(e.cancelLoad(),a(t)):e.unload().then(()=>a(t)))})(s)}})(D);export{g as ChatModelId,i as ChatStatus,l as LlmBackend,d as LlmPhase,s as LlmRole,c as LlmStatus,V as createChat};
2
+ import{dirname as e,join as t}from"node:path";import{fileURLToPath as n}from"node:url";var a={};const o={};function r(e){const t=o[e];if(void 0!==t)return t.exports;const n=o[e]={exports:{}};return a[e](n,n.exports,r),n.exports}var s,i,c,l,d,g;r.m=a,r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.f={},r.e=e=>{const t=[];return r.f.j(e,t),Promise.all(t)},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{const e={924:()=>import("./engine-wllama.node.min.mjs")},t={792:0},n=e=>{let{__webpack_esm_ids__:n,__webpack_esm_modules__:a,__webpack_esm_runtime__:o}=e;var s,i,c=0;for(s in a)r.o(a,s)&&(r.m[s]=a[s]);for(o&&o(r);c<n.length;c++)i=n[c],r.o(t,i)&&t[i]&&t[i][0](),t[i]=0};r.f.j=(a,o)=>{let s=r.o(t,a)?t[a]:void 0;if(0!==s)if(s)o.push(s[1]);else{let r=e[a]().then(n,e=>{throw 0!==t[a]&&(t[a]=void 0),e});r=Promise.race([r,new Promise(e=>s=t[a]=[e])]),o.push(s[1]=r)}}})(),function(e){e.system="system",e.user="user",e.assistant="assistant"}(s||(s={})),function(e){e[e.online=0]="online",e[e.away=1]="away",e[e.offline=2]="offline"}(i||(i={})),function(e){e[e.idle=0]="idle",e[e.downloading=1]="downloading",e[e.ready=2]="ready",e[e.error=3]="error",e[e.unsupported=4]="unsupported"}(c||(c={})),function(e){e.cpu="cpu",e.none="none"}(l||(l={})),function(e){e.download="download",e.load="load"}(d||(d={})),function(e){e.qwen3_vl_2b="qwen3-vl-2b"}(g||(g={}));const u=[{id:g.qwen3_vl_2b,reasoning:!0,cpu:{repo:"Qwen/Qwen3-VL-2B-Thinking-GGUF",file:"Qwen3VL-2B-Thinking-Q4_K_M.gguf",mmproj:{file:"mmproj-Qwen3VL-2B-Thinking-Q8_0.gguf"}}}],m=e=>e?u.find(t=>t.id==e):void 0,p=e=>!!e.cpu.mmproj,f=()=>{return a=t(e(n("file:///media/x/store/code/DegreeSign/ds_chat/src/assets/nodeEsm.js")),"../assets/wllama"),{wasm:t(a,"wllama.wasm"),compatWasm:t(a,"compat/wllama.wasm"),compatWorker:t(a,"compat/wllama.js")};var a},h=288,w=async e=>{if(!e||"function"!=typeof fetch||"function"!=typeof createImageBitmap)return e;try{const t=await fetch(e),n=await createImageBitmap(await t.blob()),a=Math.min(n.width,n.height),o=Math.floor((n.width-a)/2),r=Math.floor((n.height-a)/2),s=((e,t)=>{if("undefined"!=typeof OffscreenCanvas)return new OffscreenCanvas(e,t);if("undefined"==typeof document)return;const n=document.createElement("canvas");return n.width=e,n.height=t,n})(h,h),i=s?s.getContext("2d"):null;if(!s||!i||a<1)return n.close(),e;if(i.drawImage(n,o,r,a,a,0,0,h,h),n.close(),(e=>"undefined"!=typeof OffscreenCanvas&&e instanceof OffscreenCanvas)(s)){const t=await s.convertToBlob({type:"image/jpeg",quality:.85});return await(e=>new Promise(t=>{if("undefined"==typeof FileReader)return void t("");const n=new FileReader;n.onerror=()=>t(""),n.onload=()=>t(`${n.result}`),n.readAsDataURL(e)}))(t)||e}return s.toDataURL("image/jpeg",.85)}catch{return e}},y={maxReplyTokens:200,maxThinkingTokens:64,maxReasoningTokens:150,maxImages:2,maxContentLength:1e3,promptCharBudget:1e4,maxThreads:2},x=(e,t,n)=>"number"==typeof e&&Number.isFinite(e)&&e>=n?Math.floor(e):t,k=(e={})=>{const t=y;return{maxReplyTokens:x(e.maxReplyTokens,t.maxReplyTokens,1),maxThinkingTokens:x(e.maxThinkingTokens,t.maxThinkingTokens,1),maxReasoningTokens:x(e.maxReasoningTokens,t.maxReasoningTokens,1),maxImages:x(e.maxImages,t.maxImages,0),maxContentLength:x(e.maxContentLength,t.maxContentLength,1),promptCharBudget:x(e.promptCharBudget,t.promptCharBudget,1),maxThreads:x(e.maxThreads,t.maxThreads,1)}},v=["<|channel>thought\n","<|channel>thought","..."],b=["<channel|>","..."],_=(e,t)=>t.length?[...e?[{type:"text",text:e}]:[],...t.map(e=>({type:"image",url:e}))]:e,T=e=>"string"==typeof e?e?[{type:"text",text:e}]:[]:e,M=(e,t)=>{if("string"==typeof e&&"string"==typeof t)return`${e}\n${t}`;const n=[];for(const a of[...T(e),{type:"text",text:"\n"},...T(t)]){const e=n[n.length-1];"text"==a.type&&"text"==e?.type?e.text+=a.text:n.push(a)}return n},C=async({model:e,logging:t,onReason:n,onText:a,start:o,stopped:r})=>{let s="",i="",c="";const l=((e,t)=>{let n="",a=!1;const o=(e,t)=>{let n=-1,a="";for(const o of t){const t=e.indexOf(o);t>=0&&(n<0||t<n)&&(n=t,a=o)}return{at:n,tag:a}},r=(e,t)=>{let n="";for(const a of t)for(let t=Math.min(e.length,a.length-1);t>n.length;t--)if(e.endsWith(a.slice(0,t))){n=e.slice(e.length-t);break}return n};return{feed:s=>{let i=n+s;for(n="";i;){if(a){const{at:t,tag:s}=o(i,b);if(t>=0){t&&e(i.slice(0,t)),i=i.slice(t+s.length),a=!1;continue}const c=r(i,b);e(i.slice(0,i.length-c.length)),n=c;break}{const{at:e,tag:s}=o(i,v);if(e>=0){e&&t(i.slice(0,e)),i=i.slice(e+s.length),i.startsWith("\n")&&(i=i.slice(1)),a=!0;continue}const c=r(i,v);t(i.slice(0,i.length-c.length)),n=c;break}}},flush:()=>{n&&(a?e(n):t(n),n="")}}})(e=>{i+=e,n(e)},e=>{s+=e,a(e)});try{const e=await o();for await(const t of e){if(r())break;const e=t?.choices?.[0],a=e?.delta,o="string"==typeof a?.reasoning_content?a.reasoning_content:"string"==typeof a?.reasoning?a.reasoning:"";e?.finish_reason&&(c=e.finish_reason),o&&(i+=o,n(o)),a?.content&&l.feed(a.content)}l.flush()}catch(e){t&&console.log("Local chat failed",e)}const d=r()?void 0:s||void 0;return t&&(console.log("Local chat reply",e.id,d?"****":""),console.log("Local chat finish",e.id,c||"none",s.length,i.length)),d},L=async({model:e,params:t,start:n,stopped:a})=>{const o=await(async e=>{const t=[];for(const n of e){if("string"==typeof n.content){t.push(n);continue}const e=[];for(const t of n.content)e.push("image"==t.type?{type:"image",url:await w(t.url)}:t);t.push({role:"user",content:e})}return t})(((e,t=y)=>{const n=t.maxContentLength,a=e.filter(e=>"string"==typeof e.content&&!!e.content.trim()||!(e.role!=s.user||!e.images?.length)),o=[],r=[];let i=0,c=t.maxImages;for(let e=a.length-1;e>=0;e--){const l=a[e],d=("string"==typeof l.content?l.content:"").slice(0,n),g=l.role==s.user?(l.images||[]).filter(e=>"string"==typeof e&&!!e).slice(0,Math.max(0,c)):[];if(d||g.length){if(d&&i+d.length>t.promptCharBudget)break;i+=d.length,c-=g.length,l.role==s.system?o.unshift(d):l.role==s.assistant?r.unshift({role:"assistant",content:d}):r.unshift({role:"user",content:_(d,g)})}}for(;r.length&&"user"!=r[0].role;)r.shift();const l=[];for(const e of r){const t=l[l.length-1];"assistant"==t?.role&&"assistant"==e.role?t.content=`${t.content}\n${e.content}`:"user"==t?.role&&"user"==e.role?t.content=M(t.content,e.content):"assistant"==e.role?l.push({role:"assistant",content:e.content}):l.push({role:"user",content:e.content})}const d=o.join("\n\n"),g=[];return d&&g.push({role:"system",content:d}),g.push(...l),g})(t.messages,t.limits));return t.logging&&((e,t)=>{console.log("Local chat call",e.id,t.map(({role:e,content:t})=>({role:e,content:"****",...Array.isArray(t)?{images:t.filter(e=>"image"==e.type).length}:{}})))})(e,o),await C({model:e,logging:t.logging,onReason:t.onReason,onText:t.onText,start:()=>n(o),stopped:a})};let j,R,W="",$=!1;const q=e=>`${e.wasm}|${e.compatWasm}|${e.compatWorker}`,P=e=>{const t=f();return{wasm:e?.wasm||t.wasm,compatWasm:e?.compatWasm||t.compatWasm,compatWorker:e?.compatWorker||t.compatWorker}},B=e=>{const t=e=>(...t)=>{$&&("string"==typeof t[0]&&t[0].includes("ggml_webgpu: Failed to get an adapter")||e(...t))};return{...e,log:t(e.log),warn:t(e.warn),error:t(e.error)}},I=async()=>{if(j)return j;const e=await r.e(924).then(()=>r(577)),t=R||P(),n=new e.Wllama({default:t.wasm},{logger:B(e.LoggerWithoutDebug)});return R||(R=t,W=q(t)),n.setCompat({wasm:t.compatWasm,worker:t.compatWorker}),j=n,n},O=async()=>{const e=j;j=void 0;try{await(e?.exit())}catch{}},A=(e,t)=>e.name.includes(t)||!!e.metadata?.originalURL?.includes(t),U=e=>[e.cpu.file,...e.cpu.mmproj?[e.cpu.mmproj.file]:[]],E=(e,t)=>`https://huggingface.co/${e}/resolve/main/${t}`,F=async e=>{if(!e.startsWith("data:"))return await(await fetch(e)).arrayBuffer();const t=e.indexOf(","),n=t>=0?e.slice(0,t):"";if(t<0)throw new Error("Invalid image data");if(!n.includes(";base64"))return await(await fetch(e)).arrayBuffer();const a=atob(e.slice(t+1)),o=new Uint8Array(a.length);for(let e=0;e<a.length;e++)o[e]=a.charCodeAt(e);return o.buffer},S=async e=>{const t=[];for(const n of e){if("string"==typeof n.content){"system"==n.role?t.push({role:"system",content:n.content}):"assistant"==n.role?t.push({role:"assistant",content:n.content}):t.push({role:"user",content:n.content});continue}const e=[];for(const t of n.content)e.push("text"==t.type?{type:"text",text:t.text}:{type:"image",data:await F(t.url)});t.push({role:"user",content:e})}return t},Q=async(e,t,n)=>{if($=!0===n.logging,e.isModelLoaded())return await L({model:t,params:n,stopped:()=>!!n.signal?.aborted,start:async a=>{if((e=>e.some(e=>Array.isArray(e.content)&&e.content.some(e=>"image"==e.type)))(a)&&!e.supportInputModality("image"))throw new Error("Model does not accept images");return await e.createChatCompletion({messages:await S(a),...(o=n.limits,r=n.reasoning,{stream:!0,temperature:.7,max_tokens:r?o.maxReasoningTokens:o.maxReplyTokens}),abortSignal:n.signal,...t.reasoning?{chat_template_kwargs:{enable_thinking:n.reasoning}}:{}});var o,r}})},D={backend:l.cpu,startLLM:async({model:e,limits:t,onProgress:n,signal:a,logging:o,assets:r})=>{$=!0===o;const s=P(r),i=q(s);j&&i!=W&&await O(),R=s,W=i;const c=(g="undefined"!=typeof navigator&&navigator.hardwareConcurrency||4,u=t.maxThreads,Math.max(1,Math.min(Math.max(2,Math.min(8,(g||4)-1)),u)));var g,u;const m=await I(),p=E(e.cpu.repo,e.cpu.file),f=e.cpu.mmproj?.file,h=f?E(e.cpu.repo,f):"";if(a?.aborted)throw await O(),new Error("Cancelled");if(f){const t=await m.cacheManager.list(),n=t.some(t=>A(t,e.cpu.file)),a=t.some(e=>A(e,f));n&&!a&&await m.cacheManager.deleteMany(t=>A(t,e.cpu.file))}try{await m.loadModelFromUrl(h?{url:p,mmprojUrl:h}:p,{n_ctx:8192,n_threads:c,reasoning_budget_tokens:t.maxThinkingTokens,n_gpu_layers:0,useCache:!0,jinja:!0,signal:a,progressCallback:({loaded:e,total:t})=>{const a=t?e/t:0;n(a,a>=1?d.load:d.download)}})}catch(e){throw await O(),e}if(a?.aborted)throw await O(),new Error("Cancelled");if(!m.getLoadedContextInfo().n_ctx)throw await O(),new Error("Failed to create model context");return{model:e,backend:l.cpu,contextSize:8192,chat:t=>Q(m,e,t),unload:async()=>{if(j==m)await O();else try{await m.exit()}catch{}}}},cached:async e=>{try{const t=await I(),n=await t.cacheManager.list();return U(e).every(e=>n.some(t=>A(t,e)))}catch{return!1}},hasCache:async()=>{try{const e=await I();return(await e.cacheManager.list()).length>0}catch{return!1}},remove:async e=>{try{const t=await I();await t.cacheManager.deleteMany(t=>U(e).some(e=>A(t,e)))}catch{}},purge:async()=>{try{const e=await I();await e.cacheManager.clear()}catch{}}},V=((e={})=>{const t=e.engines?.cpu||D;let n,a,o,r,s,i,g,m=l.none,p=c.idle,f=0,h={percent:0,phase:d.download};const w=()=>e.backend||("undefined"==typeof window&&"undefined"==typeof self&&"undefined"==typeof Worker||"undefined"==typeof WebAssembly?l.none:l.cpu),y=async()=>{await t.purge()},x=(e,t)=>{h={percent:Math.max(0,Math.min(1,e)),phase:t||d.download},i?.(h.percent,h.phase)},k=e=>{p=e},v=()=>p==c.ready&&!!n&&!!a;return{load:async({model:e,limits:g,assets:p,logging:b,onProgress:_})=>{if(v()&&a?.id==e.id)return!0;if(o&&r==e.id)return i=_,_(h.percent,h.phase),await o;o&&(i=void 0,s?.abort(),await o.catch(()=>!1)),i=_;const T=++f,M=(async()=>{await(async()=>{try{if("undefined"==typeof navigator)return;await(navigator.storage?.persist?.())}catch{}})();const o=w();if(T!=f)return!1;if(o==l.none)return k(c.unsupported),!1;if(await(async e=>{if(a&&a.id!=e.id){const e=a,o=n;n=void 0,a=void 0,await(o?.unload()),await t.remove(e)}if(await t.hasCache())for(const n of u)n.id!=e.id&&await t.remove(n)})(e),T!=f)return!1;const r=await t.cached(e);if(T!=f)return!1;if(!r&&await(async()=>{try{if("undefined"==typeof navigator)return!1;if(!navigator.storage?.estimate)return!1;const{quota:e=0,usage:t=0}=await navigator.storage.estimate();return!!e&&t>=e}catch{return!1}})()&&await y(),T!=f)return!1;k(c.downloading),x(0,d.download),r&&x(1,d.load);const i=new AbortController;s=i;try{const r=await t.startLLM({model:e,limits:g,logging:b,signal:i.signal,assets:p,onProgress:(e,t)=>{T==f&&x(e,t)}});return T!=f||i.signal.aborted?(await r.unload(),!1):(n=r,a=e,m=o,k(c.ready),x(1,d.load),!0)}catch(e){return T!=f||(n=void 0,a=void 0,b&&console.log("Local model failed",e),(e=>["quota","no space","disk full","ns_error_dom_quota_reached"].some(t=>`${e?.name||""} ${e?.message||`${e}`}`.toLowerCase().includes(t)))(e)&&await y(),k(c.error)),!1}finally{s==i&&(s=void 0)}})().finally(()=>{o==M&&(o=void 0,r=void 0)});return o=M,r=e.id,await M},chat:async e=>{if(!v()||!n)return;for(;g;)await g.catch(()=>{});if(!v()||!n)return;const t=n.chat(e);g=t;try{return await t}finally{g==t&&(g=void 0)}},unload:async()=>{f++,s?.abort(),s=void 0,r=void 0;const e=n;n=void 0,a=void 0,k(c.idle),await(e?.unload())},cancelLoad:()=>{f++,s?.abort(),s=void 0,r=void 0,p==c.downloading&&k(c.idle)},ready:v,loading:()=>!v()&&p==c.downloading,status:()=>p,backend:()=>m,model:()=>a}})(),G=(e,t,n)=>{const a=e.status(),o=m(t);return{availability:a==c.ready?i.online:a==c.error||a==c.unsupported?i.offline:i.away,status:a,backend:e.backend(),supportsImages:!!o&&p(o),ready:e.ready(),loading:e.loading(),requesting:n.requesting,phase:n.phase,percent:n.percent,...n.error?{error:n.error}:{}}},N=(e=>{let t,n=0;const a=t=>t.input.onStatus?.(G(e,t.input.model,t));return o=>{t?.controller.abort();const r={token:++n,input:o,controller:new AbortController,percent:e.ready()?100:0,phase:e.ready()?d.load:d.download,requesting:!1,cancelled:!1};return t=r,a(r),(async t=>{const{input:o}=t,r=!0===o.logging,i=m(o.model),c=o.messages.some(e=>e.role!=s.system&&"string"==typeof e.content&&!!e.content.trim()),l=o.messages.some(e=>e.role==s.user&&!!e.images?.some(e=>"string"==typeof e&&!!e)),g=c||l;if(!i)return t.error="Unknown model",void a(t);if(!g)return void a(t);if(l&&!c&&!p(i))return t.error="Model does not accept images",void a(t);const u=k(o.limits),f=await e.load({model:i,limits:u,logging:r,onProgress:(e,o)=>{t.token==n&&(t.percent=Math.round(100*e),t.phase=o,a(t))}}).catch(e=>(t.token==n&&(t.error=`${e?.message||`${e}`}`),!1));if(t.token!=n)return;if(!f)return t.cancelled||t.error||(t.error="Model not ready"),void a(t);const h=p(i),w=l&&!h?o.messages.map(({images:e,...t})=>t):o.messages;if(l&&!h&&!c)return t.error="Model does not accept images",void a(t);t.requesting=!0,t.error=void 0,t.percent=100,t.phase=d.load,a(t);let y,x="",v="";try{y=await e.chat({messages:w,limits:u,reasoning:!0===o.reasoning,logging:r,onReason:e=>{t.token!=n||t.cancelled||(x+=e,o.onThinking?.(x))},onText:e=>{t.token!=n||t.cancelled||(v+=e,o.onResponse?.(v))},signal:t.controller.signal})}catch(e){if(t.token!=n)return;return t.requesting=!1,t.error=`${e?.message||`${e}`}`,void a(t)}t.token==n&&(t.requesting=!1,y||t.cancelled||(t.error="No reply"),a(t))})(r),()=>(t=>{t.token==n&&(t.cancelled=!0,t.controller.abort(),e.status()!=c.ready?(e.cancelLoad(),a(t)):e.unload().then(()=>a(t)))})(r)}})(V),z=(K=V,async e=>{const t=m(e.model),n=t=>e.onStatus?.(G(K,e.model,t));if(!t)return n({requesting:!1,phase:d.download,percent:0,error:"Unknown model"}),!1;let a,o=0,r=d.download;const s=K.ready()&&K.model()?.id==t.id;return await K.load({model:t,limits:k(e.limits),logging:!0===e.logging,onProgress:(e,t)=>{o=Math.round(100*e),r=t,n({requesting:!1,phase:r,percent:o})}}).catch(e=>(a=`${e?.message||`${e}`}`,!1))?(s&&n({requesting:!1,phase:d.load,percent:100}),!0):(n({requesting:!1,phase:r,percent:o,error:a||"Model not ready"}),!1)});var K;export{g as ChatModelId,i as ChatStatus,l as LlmBackend,d as LlmPhase,s as LlmRole,c as LlmStatus,N as createChat,z as preloadModel};
package/dist/types.d.ts CHANGED
@@ -76,6 +76,8 @@ interface ChatInput {
76
76
  /** Model and request state, reported on every change */
77
77
  onStatus?: (snapshot: ChatStatusSnapshot) => void;
78
78
  }
79
+ /** Model preload request, a subset of ChatInput */
80
+ type ChatPreloadInput = Pick<ChatInput, `model` | `limits` | `logging` | `onStatus`>;
79
81
  /** Stop the current request, its model load, or free the loaded model */
80
82
  type CancelChat = () => void;
81
83
  /** wllama wasm assets used by the CPU engine */
@@ -87,16 +89,22 @@ interface WllamaAssets {
87
89
  /** URL of the wllama compat worker script */
88
90
  compatWorker: string;
89
91
  }
92
+ /** Request state that builds one status snapshot */
93
+ interface ChatState {
94
+ /** Whether a reply is streaming right now */
95
+ requesting: boolean;
96
+ phase: LlmPhase;
97
+ /** Download or load progress, 0..100 */
98
+ percent: number;
99
+ /** Last request error, cleared when a reply starts */
100
+ error?: string;
101
+ }
90
102
  /** One live chat request */
91
- interface ActiveCall {
103
+ interface ActiveCall extends ChatState {
92
104
  token: number;
93
105
  input: ChatInput;
94
106
  controller: AbortController;
95
- percent: number;
96
- phase: LlmPhase;
97
- requesting: boolean;
98
107
  cancelled: boolean;
99
- error?: string;
100
108
  }
101
109
  /** Engine and backend overrides used by tests */
102
110
  interface RuntimeDeps {
@@ -267,4 +275,4 @@ interface EngineRunParams {
267
275
  /** Whether the caller stopped the generation */
268
276
  stopped: () => boolean;
269
277
  }
270
- export type { ChatMessage, ChatModel, LlmLimits, ChatStatusSnapshot, ChatInput, CancelChat, WllamaAssets, ActiveCall, RuntimeDeps, RuntimeLoadParams, RuntimeChatParams, Runtime, EngineChatParams, StartLlmParams, LlmRuntime, LlmEngine, EngineMessage, EngineContentPart, WllamaContentPart, WllamaMessage, EngineDelta, EngineChunk, StreamReplyParams, EngineRunParams, };
278
+ export type { ChatMessage, ChatModel, LlmLimits, ChatStatusSnapshot, ChatInput, ChatPreloadInput, ChatState, CancelChat, WllamaAssets, ActiveCall, RuntimeDeps, RuntimeLoadParams, RuntimeChatParams, Runtime, EngineChatParams, StartLlmParams, LlmRuntime, LlmEngine, EngineMessage, EngineContentPart, WllamaContentPart, WllamaMessage, EngineDelta, EngineChunk, StreamReplyParams, EngineRunParams, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@degreesign/chat",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Headless on-device AI chat for browsers, workers and Node with a wllama CPU engine",
5
5
  "main": "dist/node/chat.node.min.js",
6
6
  "module": "dist/browser/chat.min.mjs",