diff --git a/.gitignore b/.gitignore index 05baefb71d75b192e9cf879e4b1096bd5a56af14..fc8bed828d6bd3bc17940079f476af895db6fb6c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ node_modules/ -dist/ playwright-report/ test-results/ *.log diff --git a/README.md b/README.md index 35ab60ef5f2eedd3e6af1125fb6682fb47dc41a4..72c990dc23c12cf36ebb0672f6ddbd16d90cbb33 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,12 @@ shard from byte zero; the UI does not claim resumable Range downloads. `maxStorageBufferBindingSize`, `shader-f16`, browser quota, or all-WebGPU graph requirement does not pass. Chrome on Apple Silicon is the validated release path. Safari 26.5 on Apple Silicon has also passed a real foreground cold 27B -load through compat/Asyncify plus explicit OPFS eviction. Firefox gating was +load through compat/Asyncify plus explicit OPFS eviction. The normal compat +path caps prefill at `n_batch=32` and `n_ubatch=16` so Safari submits shorter +Metal command buffers; explicit benchmark tuning can still override the cap. +On Safari 26.5.2, the final 64-token gate completed at 1.2 prefill tok/s and +3.5 decode tok/s with one graph, zero CPU ops, and 65/65 GPU layers. Chrome's +JSPI path does not use this cap. Firefox gating was validated on macOS: it selected compat, offered CPU-WASM for dense tiers, and refused 27B without WebGPU; Firefox model generation was not exercised. Windows Chrome/Edge on a discrete GPU and a physical low-limit iGPU laptop @@ -85,7 +90,7 @@ validates the pinned native CPU prompt/reference fixture, then teacher-forces that fixed 1,024-token sequence through raw token-ID prefixes. Schema v3 keeps the forced reference logprob, natural top five, mean NLL, and perplexity separate from the natural browser generation. This scoring method is not -available to normal chat and does not activate DFlash. +available to normal chat. On the pinned Apple `metal-3` release path, the final JSPI runtime completed the natural 1,024-token 27B run at 11.3 tok/s with one graph, zero CPU ops, and @@ -128,18 +133,6 @@ first-download claim: | 8B | WebGPU | 9,870 ms | 998 ms | 33.8 tok/s | | 27B | WebGPU | 29,488 ms | 3,348 ms | 11.9 tok/s | -## DFlash research lane - -DFlash is visible only as an inspection-only experiment. Its BF16 source and -Q8_0 native GGUF reference are pinned in the manifest. The strict D1 audit -passes all 39 shape, tokenizer, target-tap, conversion, revision, and evidence -checks, including a 32-token temperature-0 native Bonsai-27B parity smoke. The -custom WASM contains the DFlash llama.cpp architecture, but the browser load -API does not yet plumb `spec_type=draft-dflash`; the Q8 -`fc.weight` also exceeds the 128 MiB baseline WebGPU binding limit. It remains -disabled and unavailable until a lower-bit candidate, browser parity, -acceptance, and positive speed gates all pass. - ## Local tools and safety boundary Tools are off by default. Memory writes/deletes require operator confirmation. @@ -155,10 +148,10 @@ stay in the browser; only model/static asset downloads contact Hugging Face. - Dawn native WebGPU validation: `18eb229ef5f707c1464cc581252e7603c73a3ef0` - custom wllama source: `912c18b75d4358c1405a64646b8dbe43a205943b` - custom nested llama.cpp: `00fa7cb284cbf133fc426733bd64238a3588a33e` -- custom patch set: `27b96fa48c6fd66b44fe70e83cc775902e29ec5877261c7eb04fecabc529d2f9` -- custom ESM: `10c3811776e34092a225632929b97046af4215a4c91156169105c886be5cba5d` -- JSPI WASM: `b292bf670a25e1ecf02c71dd92b49196f3336842135f1f85efd5a3fd5f36ea8a` -- compat WASM: `ea0623d2ca1758287a569169b4ad5a69c22e41bce3e5d022c7d3e4fb5c37918c` +- custom patch set: `e94aef3f672211e72bab039385b3bcbeda46f0e3218db7a86a9b5468186ab491` +- custom ESM: `b7e883ebeed6aad1b4587d89c20cc4ee8e69e7a0e94e1d307dcad756d657a41a` +- JSPI WASM: `5a8686c10f7639653e7e1854d34f39c31ddba404de876a5ae15d36c42263ddc4` +- compat WASM: `c823684229149dd2386308978288e366f2ff209b231b78485507db8cb735bbdd` - compat worker: `0b667db536815fb9e5dc9ffbfbd6ba29affdda38a4ba892d5372ddb41ce9a8c8` The vendored runtime exposes the validated token-embedding WebGPU placement @@ -178,8 +171,7 @@ remains off and F16/F16 remains the release default. ## Licenses and attribution The Bonsai GGUF tiers are Apache-2.0 model artifacts; their upstream notices -are published in `WaveCut/Bonsai-web-GGUF@v1.1.3/notices/`. The DFlash Q8_0 -native reference is MIT-licensed by Z Lab and has a separate notice in that -same release. The shipped wllama/llama.cpp engines, React runtime, and +are published in `WaveCut/Bonsai-web-GGUF@v1.1.3/notices/`. The shipped +wllama/llama.cpp engines, React runtime, and `@noble/hashes` are MIT-licensed. Exact runtime license texts are included in `public/licenses/`; the consolidated inventory is `THIRD_PARTY_NOTICES.md`. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index aeb432c3dea4375e7c718d689d79aba751715000..99152b3a40a2ca23245347aebf091cfa0949ef49 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -4,8 +4,6 @@ The distributed Space bundle includes: - Bonsai 1.7B/4B/8B/27B GGUF model downloads, Apache-2.0, with per-tier notices in `WaveCut/Bonsai-web-GGUF@v1.1.3/notices/`; -- the optional, runtime-disabled DFlash Q8_0 native reference, MIT, - Copyright (c) 2026 Z Lab, with `NOTICE-DFLASH.txt` in that model release; - wllama at `912c18b75d4358c1405a64646b8dbe43a205943b`, MIT; - llama.cpp/ggml at `00fa7cb284cbf133fc426733bd64238a3588a33e`, MIT; - React and React DOM 19.2.4, MIT; diff --git a/dist/assets/index-BY7HNxCd.js b/dist/assets/index-BY7HNxCd.js new file mode 100644 index 0000000000000000000000000000000000000000..c9bcd7751cbbb15cec60cb8f5315e25421fb9806 --- /dev/null +++ b/dist/assets/index-BY7HNxCd.js @@ -0,0 +1,69 @@ +var e=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var t=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function D(e,t){return E(e.type,t,e.props)}function O(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ee(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var k=/\/+/g;function A(e,t){return typeof e==`object`&&e&&e.key!=null?ee(``+e.key):t.toString(36)}function te(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function ne(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,ne(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+A(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(k,`$&/`)+`/`),ne(o,r,i,``,function(e){return e})):o!=null&&(O(o)&&(o=D(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(k,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{n.exports=t()})),r=e((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,O());else{var t=n(l);t!==null&&A(x,t.startTime-e)}}var S=!1,C=-1,w=5,T=-1;function E(){return g?!0:!(e.unstable_now()-Tt&&E());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&A(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?O():S=!1}}}var O;if(typeof y==`function`)O=function(){y(D)};else if(typeof MessageChannel<`u`){var ee=new MessageChannel,k=ee.port2;ee.port1.onmessage=D,O=function(){k.postMessage(null)}}else O=function(){_(D,0)};function A(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,A(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,O()))),r},e.unstable_shouldYield=E,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),i=e(((e,t)=>{t.exports=r()})),a=e((e=>{var t=n();function r(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=a()})),s=e((e=>{var t=i(),r=n(),a=o();function s(e){var t=`https://react.dev/errors/`+e;if(1ae||(e.current=P[ae],P[ae]=null,ae--)}function L(e,t){ae++,P[ae]=e.current,e.current=t}var oe=F(null),se=F(null),ce=F(null),le=F(null);function ue(e,t){switch(L(ce,t),L(se,e),L(oe,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}I(oe),L(oe,e)}function de(){I(oe),I(se),I(ce)}function fe(e){e.memoizedState!==null&&L(le,e);var t=oe.current,n=Hd(t,e.type);t!==n&&(L(se,e),L(oe,n))}function pe(e){se.current===e&&(I(oe),I(se)),le.current===e&&(I(le),Qf._currentValue=N)}var me,he;function ge(e){if(me===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);me=t&&t[1]||``,he=-1)`:-1i||c[r]!==l[i]){var u=` +`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{_e=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?ge(n):``}function ye(e,t){switch(e.tag){case 26:case 27:case 5:return ge(e.type);case 16:return ge(`Lazy`);case 13:return e.child!==t&&t!==null?ge(`Suspense Fallback`):ge(`Suspense`);case 19:return ge(`SuspenseList`);case 0:case 15:return ve(e.type,!1);case 11:return ve(e.type.render,!1);case 1:return ve(e.type,!0);case 31:return ge(`Activity`);default:return``}}function be(e){try{var t=``,n=null;do t+=ye(e,n),n=e,e=e.return;while(e);return t}catch(e){return` +Error generating stack: `+e.message+` +`+e.stack}}var xe=Object.prototype.hasOwnProperty,Se=t.unstable_scheduleCallback,Ce=t.unstable_cancelCallback,we=t.unstable_shouldYield,Te=t.unstable_requestPaint,Ee=t.unstable_now,De=t.unstable_getCurrentPriorityLevel,Oe=t.unstable_ImmediatePriority,ke=t.unstable_UserBlockingPriority,Ae=t.unstable_NormalPriority,je=t.unstable_LowPriority,Me=t.unstable_IdlePriority,Ne=t.log,Pe=t.unstable_setDisableYieldValue,Fe=null,Ie=null;function Le(e){if(typeof Ne==`function`&&Pe(e),Ie&&typeof Ie.setStrictMode==`function`)try{Ie.setStrictMode(Fe,e)}catch{}}var Re=Math.clz32?Math.clz32:Ve,ze=Math.log,Be=Math.LN2;function Ve(e){return e>>>=0,e===0?32:31-(ze(e)/Be|0)|0}var He=256,Ue=262144,We=4194304;function Ge(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Ke(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Ge(n))):i=Ge(o):i=Ge(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Ge(n))):i=Ge(o)):i=Ge(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function qe(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Je(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Ye(){var e=We;return We<<=1,!(We&62914560)&&(We=4194304),e}function Xe(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function R(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Ze(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),un=!1;if(ln)try{var dn={};Object.defineProperty(dn,"passive",{get:function(){un=!0}}),window.addEventListener(`test`,dn,dn),window.removeEventListener(`test`,dn,dn)}catch{un=!1}var fn=null,pn=null,mn=null;function hn(){if(mn)return mn;var e,t=pn,n=t.length,r,i=`value`in fn?fn.value:fn.textContent,a=i.length;for(e=0;e=Kn),Yn=` `,Xn=!1;function Zn(e,t){switch(e){case`keyup`:return Wn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function Qn(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var $n=!1;function er(e,t){switch(e){case`compositionend`:return Qn(t);case`keypress`:return t.which===32?(Xn=!0,Yn):null;case`textInput`:return e=t.data,e===Yn&&Xn?null:e;default:return null}}function tr(e,t){if($n)return e===`compositionend`||!Gn&&Zn(e,t)?(e=hn(),mn=pn=fn=null,$n=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Cr(n)}}function Tr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Tr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Er(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=It(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=It(e.document)}return t}function Dr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Or=ln&&`documentMode`in document&&11>=document.documentMode,kr=null,Ar=null,jr=null,Mr=!1;function Nr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Mr||kr==null||kr!==It(r)||(r=kr,`selectionStart`in r&&Dr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),jr&&Sr(jr,r)||(jr=r,r=Ed(Ar,`onSelect`),0>=o,i-=o,Ti=1<<32-Re(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),o=a(_,o,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),V&&Di(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(i,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(i,h),o=a(y,o,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(i,h),V&&Di(i,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(i,v.value,l),v!==null&&(o=a(v,o,g),d===null?u=v:d.sibling=v,d=v);return V&&Di(i,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,i,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),o=a(v,o,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(i,e)}),V&&Di(i,g),u}function b(e,r,a,c){if(typeof a==`object`&&a&&a.type===y&&a.key===null&&(a=a.props.children),typeof a==`object`&&a){switch(a.$$typeof){case _:a:{for(var l=a.key;r!==null;){if(r.key===l){if(l=a.type,l===y){if(r.tag===7){n(e,r.sibling),c=i(r,a.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===O&&Ea(l)===r.type){n(e,r.sibling),c=i(r,a.props),Na(c,a),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}a.type===y?(c=fi(a.props.children,e.mode,c,a.key),c.return=e,e=c):(c=di(a.type,a.key,a.props,null,e.mode,c),Na(c,a),c.return=e,e=c)}return o(e);case v:a:{for(l=a.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===a.containerInfo&&r.stateNode.implementation===a.implementation){n(e,r.sibling),c=i(r,a.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=hi(a,e.mode,c),c.return=e,e=c}return o(e);case O:return a=Ea(a),b(e,r,a,c)}if(ie(a))return h(e,r,a,c);if(te(a)){if(l=te(a),typeof l!=`function`)throw Error(s(150));return a=l.call(a),g(e,r,a,c)}if(typeof a.then==`function`)return b(e,r,Ma(a),c);if(a.$$typeof===C)return b(e,r,ea(e,a),c);Pa(e,a)}return typeof a==`string`&&a!==``||typeof a==`number`||typeof a==`bigint`?(a=``+a,r!==null&&r.tag===6?(n(e,r.sibling),c=i(r,a),c.return=e,e=c):(n(e,r),c=pi(a,e.mode,c),c.return=e,e=c),o(e)):n(e,r)}return function(e,t,n,r){try{ja=0;var i=b(e,t,n,r);return Aa=null,i}catch(t){if(t===ba||t===Sa)throw t;var a=si(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Ia=Fa(!0),La=Fa(!1),Ra=!1;function za(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ba(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Va(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ha(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,K&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ii(e),ri(e,null,n),t}return ei(e,r,t,n),ii(e)}function Ua(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,$e(e,n)}}function Wa(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ga=!1;function Ka(){if(Ga){var e=da;if(e!==null)throw e}}function qa(e,t,n,r){Ga=!1;var i=e.updateQueue;Ra=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(Y&f)===f:(r&f)===f){f!==0&&f===ua&&(Ga=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:Ra=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Gl|=o,e.lanes=o,e.memoizedState=d}}function Ja(e,t){if(typeof e!=`function`)throw Error(s(191,e));e.call(t)}function Ya(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=j.T,s={};j.T=s,Ps(e,!1,t,n);try{var c=i(),l=j.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ns(e,t,ma(c,r),pu(e)):Ns(e,t,r,pu(e))}catch(n){Ns(e,t,{then:function(){},status:`rejected`,reason:n},pu())}finally{M.p=a,o!==null&&s.types!==null&&(o.types=s.types),j.T=o}}function Cs(){}function ws(e,t,n,r){if(e.tag!==5)throw Error(s(476));var i=Ts(e).queue;Ss(e,i,t,N,n===null?Cs:function(){return Es(e),n(r)})}function Ts(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:N,baseState:N,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Fo,lastRenderedState:N},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Fo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Es(e){var t=Ts(e);t.next===null&&(t=e.alternate.memoizedState),Ns(e,t.next.queue,{},pu())}function Ds(){return $i(Qf)}function Os(){return Ao().memoizedState}function ks(){return Ao().memoizedState}function As(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=pu();e=Va(n);var r=Ha(t,e,n);r!==null&&(hu(r,t,n),Ua(r,t,n)),t={cache:oa()},e.payload=t;return}t=t.return}}function js(e,t,n){var r=pu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Fs(e)?Is(t,n):(n=ti(e,t,n,r),n!==null&&(hu(n,e,r),Ls(n,t,r)))}function Ms(e,t,n){Ns(e,t,n,pu())}function Ns(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Fs(e))Is(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,xr(s,o))return ei(e,t,i,0),q===null&&$r(),!1}catch{}if(n=ti(e,t,i,r),n!==null)return hu(n,e,r),Ls(n,t,r),!0}return!1}function Ps(e,t,n,r){if(r={lane:2,revertLane:dd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Fs(e)){if(t)throw Error(s(479))}else t=ti(e,n,r,2),t!==null&&hu(t,e,2)}function Fs(e){var t=e.alternate;return e===H||t!==null&&t===H}function Is(e,t){mo=po=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ls(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,$e(e,n)}}var Rs={readContext:$i,use:No,useCallback:bo,useContext:bo,useEffect:bo,useImperativeHandle:bo,useLayoutEffect:bo,useInsertionEffect:bo,useMemo:bo,useReducer:bo,useRef:bo,useState:bo,useDebugValue:bo,useDeferredValue:bo,useTransition:bo,useSyncExternalStore:bo,useId:bo,useHostTransitionStatus:bo,useFormState:bo,useActionState:bo,useOptimistic:bo,useMemoCache:bo,useCacheRefresh:bo};Rs.useEffectEvent=bo;var zs={readContext:$i,use:No,useCallback:function(e,t){return ko().memoizedState=[e,t===void 0?null:t],e},useContext:$i,useEffect:ls,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ss(4194308,4,hs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ss(4194308,4,e,t)},useInsertionEffect:function(e,t){ss(4,2,e,t)},useMemo:function(e,t){var n=ko();t=t===void 0?null:t;var r=e();if(ho){Le(!0);try{e()}finally{Le(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=ko();if(n!==void 0){var i=n(t);if(ho){Le(!0);try{n(t)}finally{Le(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=js.bind(null,H,e),[r.memoizedState,e]},useRef:function(e){var t=ko();return e={current:e},t.memoizedState=e},useState:function(e){e=Go(e);var t=e.queue,n=Ms.bind(null,H,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:_s,useDeferredValue:function(e,t){return bs(ko(),e,t)},useTransition:function(){var e=Go(!1);return e=Ss.bind(null,H,e.queue,!0,!1),ko().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=H,i=ko();if(V){if(n===void 0)throw Error(s(407));n=n()}else{if(n=t(),q===null)throw Error(s(349));Y&127||Bo(r,t,n)}i.memoizedState=n;var a={value:n,getSnapshot:t};return i.queue=a,ls(Ho.bind(null,r,a,e),[e]),r.flags|=2048,as(9,{destroy:void 0},Vo.bind(null,r,a,n,t),null),n},useId:function(){var e=ko(),t=q.identifierPrefix;if(V){var n=Ei,r=Ti;n=(r&~(1<<32-Re(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=go++,0<\/script>`,a=a.removeChild(a.firstChild);break;case`select`:a=typeof r.is==`string`?o.createElement(`select`,{is:r.is}):o.createElement(`select`),r.multiple?a.multiple=!0:r.size&&(a.size=r.size);break;default:a=typeof r.is==`string`?o.createElement(i,{is:r.is}):o.createElement(i)}}a[ot]=t,a[st]=r;a:for(o=t.child;o!==null;){if(o.tag===5||o.tag===6)a.appendChild(o.stateNode);else if(o.tag!==4&&o.tag!==27&&o.child!==null){o.child.return=o,o=o.child;continue}if(o===t)break a;for(;o.sibling===null;){if(o.return===null||o.return===t)break a;o=o.return}o.sibling.return=o.return,o=o.sibling}t.stateNode=a;a:switch(Pd(a,i,r),i){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Nc(t)}}return W(t),Pc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Nc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(s(166));if(e=ce.current,zi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,i=Mi,i!==null)switch(i.tag){case 27:case 5:r=i.memoizedProps}e[ot]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Md(e.nodeValue,n)),e||Ii(t,!0)}else e=Bd(e).createTextNode(r),e[ot]=t,t.stateNode=e}return W(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=zi(t),n!==null){if(e===null){if(!r)throw Error(s(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(s(557));e[ot]=t}else Bi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;W(t),e=!1}else n=Vi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(so(t),t):(so(t),null);if(t.flags&128)throw Error(s(558))}return W(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(i=zi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!i)throw Error(s(318));if(i=t.memoizedState,i=i===null?null:i.dehydrated,!i)throw Error(s(317));i[ot]=t}else Bi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;W(t),i=!1}else i=Vi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=i),i=!0;if(!i)return t.flags&256?(so(t),t):(so(t),null)}return so(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,i=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(i=r.alternate.memoizedState.cachePool.pool),a=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(a=r.memoizedState.cachePool.pool),a!==i&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Ic(t,t.updateQueue),W(t),null);case 4:return de(),e===null&&Sd(t.stateNode.containerInfo),W(t),null;case 10:return qi(t.type),W(t),null;case 19:if(I(co),r=t.memoizedState,r===null)return W(t),null;if(i=(t.flags&128)!=0,a=r.rendering,a===null)if(i)Lc(r,!1);else{if(Wl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(a=lo(e),a!==null){for(t.flags|=128,Lc(r,!1),e=a.updateQueue,t.updateQueue=e,Ic(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)ui(n,e),n=n.sibling;return L(co,co.current&1|2),V&&Di(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Ee()>tu&&(t.flags|=128,i=!0,Lc(r,!1),t.lanes=4194304)}else{if(!i)if(e=lo(a),e!==null){if(t.flags|=128,i=!0,e=e.updateQueue,t.updateQueue=e,Ic(t,e),Lc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!a.alternate&&!V)return W(t),null}else 2*Ee()-r.renderingStartTime>tu&&n!==536870912&&(t.flags|=128,i=!0,Lc(r,!1),t.lanes=4194304);r.isBackwards?(a.sibling=t.child,t.child=a):(e=r.last,e===null?t.child=a:e.sibling=a,r.last=a)}return r.tail===null?(W(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Ee(),e.sibling=null,n=co.current,L(co,i?n&1|2:n&1),V&&Di(t,r.treeForkCount),e);case 22:case 23:return so(t),eo(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(W(t),t.subtreeFlags&6&&(t.flags|=8192)):W(t),n=t.updateQueue,n!==null&&Ic(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&I(ga),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),qi(aa),W(t),null;case 25:return null;case 30:return null}throw Error(s(156,t.tag))}function zc(e,t){switch(Ai(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return qi(aa),de(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return pe(t),null;case 31:if(t.memoizedState!==null){if(so(t),t.alternate===null)throw Error(s(340));Bi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(so(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(s(340));Bi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return I(co),null;case 4:return de(),null;case 10:return qi(t.type),null;case 22:case 23:return so(t),eo(),e!==null&&I(ga),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return qi(aa),null;case 25:return null;default:return null}}function Bc(e,t){switch(Ai(t),t.tag){case 3:qi(aa),de();break;case 26:case 27:case 5:pe(t);break;case 4:de();break;case 31:t.memoizedState!==null&&so(t);break;case 13:so(t);break;case 19:I(co);break;case 10:qi(t.type);break;case 22:case 23:so(t),eo(),e!==null&&I(ga);break;case 24:qi(aa)}}function Vc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Z(t,t.return,e)}}function Hc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Z(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Z(t,t.return,e)}}function Uc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Ya(t,n)}catch(t){Z(e,e.return,t)}}}function Wc(e,t,n){n.props=Ks(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Z(e,t,n)}}function Gc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Z(e,t,n)}}function Kc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Z(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Z(e,t,n)}else n.current=null}function qc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Z(e,e.return,t)}}function Jc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[st]=t}catch(t){Z(e,e.return,t)}}function Yc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function Xc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Yc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Zc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=$t));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Zc(e,t,n),e=e.sibling;e!==null;)Zc(e,t,n),e=e.sibling}function Qc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(Qc(e,t,n),e=e.sibling;e!==null;)Qc(e,t,n),e=e.sibling}function $c(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[ot]=e,t[st]=n}catch(t){Z(e,e.return,t)}}var el=!1,tl=!1,nl=!1,rl=typeof WeakSet==`function`?WeakSet:Set,il=null;function al(e,t){if(e=e.containerInfo,Rd=sp,e=Er(e),Dr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,a=r.focusNode;r=r.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break a}var o=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||i!==0&&f.nodeType!==3||(c=o+i),f!==a||r!==0&&f.nodeType!==3||(l=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===i&&(c=o),p===a&&++d===r&&(l=o),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,il=t;il!==null;)if(t=il,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,il=e;else for(;il!==null;){switch(t=il,a=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(a,r,n),a[ot]=e,yt(a),r=a;break a;case`link`:var o=Vf(`link`,`href`,i).get(r+(n.href||``));if(o){for(var c=0;cg&&(o=g,g=h,h=o);var _=wr(s,h),v=wr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,j.T=null,n=lu,lu=null;var a=au,o=su;if(iu=0,ou=au=null,su=0,K&6)throw Error(s(331));var c=K;if(K|=4,Pl(a.current),El(a,a.current,o,n),K=c,id(0,!1),Ie&&typeof Ie.onPostCommitFiberRoot==`function`)try{Ie.onPostCommitFiberRoot(Fe,a)}catch{}return!0}finally{M.p=i,j.T=r,Vu(e,t)}}function Wu(e,t,n){t=_i(n,t),t=Qs(e.stateNode,t,2),e=Ha(e,t,2),e!==null&&(R(e,2),rd(e))}function Z(e,t,n){if(e.tag===3)Wu(e,e,n);else for(;t!==null;){if(t.tag===3){Wu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(ru===null||!ru.has(r))){e=_i(n,e),n=$s(2),r=Ha(t,n,2),r!==null&&(ec(n,r,t,e),R(r,2),rd(r));break}}t=t.return}}function Gu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Rl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Hl=!0,i.add(n),e=Ku.bind(null,e,t,n),t.then(e,e))}function Ku(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,q===e&&(Y&n)===n&&(Wl===4||Wl===3&&(Y&62914560)===Y&&300>Ee()-$l?!(K&2)&&Su(e,0):ql|=n,Yl===Y&&(Yl=0)),rd(e)}function qu(e,t){t===0&&(t=Ye()),e=ni(e,t),e!==null&&(R(e,t),rd(e))}function Ju(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function Yu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(s(314))}r!==null&&r.delete(t),qu(e,n)}function Xu(e,t){return Se(e,t)}var Zu=null,Qu=null,$u=!1,ed=!1,td=!1,nd=0;function rd(e){e!==Qu&&e.next===null&&(Qu===null?Zu=Qu=e:Qu=Qu.next=e),ed=!0,$u||($u=!0,ud())}function id(e,t){if(!td&&ed){td=!0;do for(var n=!1,r=Zu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Re(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ld(r,a))}else a=Y,a=Ke(r,r===q?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||qe(r,a)||(n=!0,ld(r,a));r=r.next}while(n);td=!1}}function ad(){od()}function od(){ed=$u=!1;var e=0;nd!==0&&Gd()&&(e=nd);for(var t=Ee(),n=null,r=Zu;r!==null;){var i=r.next,a=sd(r,t);a===0?(r.next=null,n===null?Zu=i:n.next=i,i===null&&(Qu=n)):(n=r,(e!==0||a&3)&&(ed=!0)),r=i}iu!==0&&iu!==5||id(e,!1),nd!==0&&(nd=0)}function sd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Rt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),yt(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Rt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Rt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Rt(n.imageSizes)+`"]`)):i+=`[href="`+Rt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),yt(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Rt(r)+`"][href="`+Rt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),yt(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=vt(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);yt(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=vt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),yt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=vt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),yt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var i=(i=ce.current)?gf(i):null;if(!i)throw Error(s(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=vt(i).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var a=vt(i).hoistableStyles,o=a.get(e);if(o||(i=i.ownerDocument||i,o={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},a.set(e,o),(a=i.querySelector(jf(e)))&&!a._p&&(o.instance=a,o.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),a||Nf(i,e,n,o.state))),t&&r===null)throw Error(s(528,``));return o}if(t&&r!==null)throw Error(s(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=vt(i).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(s(444,e))}}function Af(e){return`href="`+Rt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),yt(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Rt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Rt(n.href)+`"]`);if(r)return t.instance=r,yt(r),r;var i=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),yt(r),Pd(r,`style`,i),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:i=Af(n.href);var a=e.querySelector(jf(i));if(a)return t.state.loading|=4,t.instance=a,yt(a),a;r=Mf(n),(i=mf.get(i))&&Rf(r,i),a=(e.ownerDocument||e).createElement(`link`),yt(a);var o=a;return o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),t.state.loading|=4,Lf(a,n.precedence,e),t.instance=a;case`script`:return a=Pf(n.src),(i=e.querySelector(Ff(a)))?(t.instance=i,yt(i),i):(r=n,(i=mf.get(a))&&(r=h({},n),zf(r,i)),e=e.ownerDocument||e,i=e.createElement(`script`),yt(i),Pd(i,`link`,r),e.head.appendChild(i),t.instance=i);case`void`:return null;default:throw Error(s(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,yt(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),yt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=s()})),l=3e4,u=5e3,d=class extends Error{code;details;constructor(e){super(e.message),this.name=`EngineClientError`,this.code=e.code,this.details=e.details}};function f(){return typeof crypto.randomUUID==`function`?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}var p=class{worker;workerFactory;pending=new Map;closed=!1;constructor(e){if(this.workerFactory=e?null:()=>new Worker(new URL(`/assets/worker-Bn87aL54.js`,``+import.meta.url),{type:`module`,name:`bonsai-browser-engine`}),this.worker=e??this.workerFactory?.()??null,!this.worker)throw Error(`Browser engine worker could not be created.`);this.attachWorker(this.worker)}attachWorker(e){e.onmessage=t=>{this.worker===e&&this.handleMessage(t.data)},e.onerror=t=>{this.worker===e&&this.failWorker(Error(t.message||`Browser engine worker failed.`))},e.onmessageerror=()=>{this.worker===e&&this.failWorker(Error(`Browser engine worker sent an invalid message.`))}}ensureWorker(){if(this.closed)throw Error(`Browser engine client is closed.`);if(!this.worker){if(!this.workerFactory)throw Error(`Browser engine worker failed and cannot be recreated.`);this.worker=this.workerFactory(),this.attachWorker(this.worker)}return this.worker}request(e,t,n={}){if(n.signal?.aborted)return Promise.reject(new DOMException(`The engine operation was aborted.`,`AbortError`));let r;try{r=this.ensureWorker()}catch(e){return Promise.reject(e)}let i=n.requestId??f();if(this.pending.has(i))return Promise.reject(Error(`Duplicate engine request id: ${i}`));let a={type:`request`,requestId:i,method:e,params:t};return new Promise((t,o)=>{let s={method:e,resolve:t,reject:o,options:n};n.signal&&(s.abortListener=()=>{if(e===`loadModel`&&s.loadPhase===`load`)this.restart(new DOMException(`Model loading was aborted.`,`AbortError`));else{if(e===`loadModel`)s.abortFallbackTimer=setTimeout(()=>{this.pending.has(i)&&this.restart(new DOMException(`Model download cleanup timed out after abort.`,`AbortError`))},l);else if(e===`generate`||e===`scoreSequence`){let t=e===`generate`?`Generation`:`Sequence scoring`,n=e===`generate`?`generation-abort-timeout`:`score-sequence-abort-timeout`;s.abortFallbackTimer=setTimeout(()=>{this.pending.has(i)&&this.restart(new d({code:`ENGINE_WORKER_FAILED`,message:`${t} did not stop within 5 seconds. The browser engine worker was restarted and the loaded model was invalidated.`,details:{recoverable:!0,nextAction:`reload-model`,cause:n,graceMs:u}}))},u)}this.abort(i).catch(()=>void 0)}},n.signal.addEventListener(`abort`,s.abortListener,{once:!0})),this.pending.set(i,s);try{r.postMessage(a)}catch(e){this.failWorker(e)}})}capabilities(){return this.request(`capabilities`,{})}loadModel(e,t){return this.request(`loadModel`,e,t)}generate(e,t){return this.request(`generate`,e,t)}scoreSequence(e,t){return this.request(`scoreSequence`,e,t)}abort(e){return this.request(`abort`,{targetRequestId:e})}unload(){return this.request(`unload`,{})}backendReport(){return this.request(`backendReport`,{})}storageEstimate(){return this.request(`storageEstimate`,{})}storagePersist(){return this.request(`storagePersist`,{})}storageClear(){return this.request(`storageClear`,{})}close(){this.closed=!0,this.worker?.terminate(),this.worker=null,this.rejectAll(Error(`Browser engine client was closed.`))}restart(e=Error(`Browser engine worker was restarted.`)){this.closed||(this.worker?.terminate(),this.worker=null,this.rejectAll(e),this.workerFactory&&(this.worker=this.workerFactory(),this.attachWorker(this.worker)))}handleMessage(e){let t=this.pending.get(e.requestId);if(t){if(e.type===`event`){if(e.event===`progress`){if(t.loadPhase=e.phase,t.method===`loadModel`&&t.options.signal?.aborted&&e.phase===`load`){this.restart(new DOMException(`Model loading was aborted.`,`AbortError`));return}t.options.onProgress?.(e)}else e.event===`token`?t.options.onToken?.(e.text,e.reasoningDelta):t.options.onGenerationProgress?.(e);return}this.finishPending(e,t)}}finishPending(e,t){if(this.pending.delete(e.requestId),t.abortListener&&t.options.signal&&t.options.signal.removeEventListener(`abort`,t.abortListener),t.abortFallbackTimer!==void 0&&clearTimeout(t.abortFallbackTimer),e.method!==t.method)t.reject(Error(`Engine response method mismatch: expected ${t.method}, received ${e.method}.`));else if(e.ok)if(t.options.signal?.aborted){let e=new DOMException(`The engine operation was aborted.`,`AbortError`);t.reject(e),t.method===`loadModel`&&this.restart(e)}else t.resolve(e.result);else{let n=new d(e.error);t.reject(n),n.code===`WEBGPU_DEVICE_LOST`&&this.restart(n)}}rejectAll(e){for(let[t,n]of this.pending)this.pending.delete(t),n.abortListener&&n.options.signal&&n.options.signal.removeEventListener(`abort`,n.abortListener),n.abortFallbackTimer!==void 0&&clearTimeout(n.abortFallbackTimer),n.reject(e)}failWorker(e){let t=e instanceof d?e:new d({code:`ENGINE_WORKER_FAILED`,message:`Browser engine worker failed: ${e instanceof Error?e.message:String(e)}`,details:{recoverable:!0,nextAction:`reload-model`}});this.worker?.terminate(),this.worker=null,this.rejectAll(t)}},m=2048,h=8448;function g(e,t){return e.quotaBytes===null||e.usageBytes===null?null:Math.max(0,e.quotaBytes-e.usageBytes+t)}function _(e,t,n){let r=e.id===`27b`&&n.tuningScope===`benchmark`&&n.requestedBackend===`webgpu`&&n.flashMode===`auto`&&n.kvCacheType===`q4_0`,i=e.id===`27b`?Math.min(e.contextLength,r?h:m):e.contextLength;return{allowed:Number.isSafeInteger(t)&&t>0&&t<=i,limit:i}}function v(e,t){if(!t.available)return[`WebGPU adapter is unavailable.`];let n=[];e.runtimePolicy.tokenEmbeddingOnWebGPU&&!t.features.includes(`shader-f16`)&&n.push(`WebGPU adapter lacks shader-f16, which the token-embedding single-graph path requires.`);for(let[r,i]of Object.entries(e.requiredLimits)){let a=t.limits[r];a===void 0?n.push(`WebGPU adapter does not report required limit ${r}.`):a0?a.push(...c):l=`webgpu`:c.length===0?l=`webgpu`:e.cpuFallback?(l=`wasm`,o.push(...c.map(e=>`${e} Falling back to CPU-WASM.`))):(a.push(...c),a.push(`${e.displayName} forbids CPU-WASM fallback; choose a smaller tier.`)),a.length>0&&(l=null),{allowed:a.length===0,requestedBackend:t,selectedBackend:l,reasons:a,warnings:o,requiredBytes:e.downloadBytes,availableStorageBytes:s}}var b=new Set([`1_7b`,`4b`,`8b`,`27b`]),x=/^[0-9a-f]{64}$/,S=/^[0-9a-f]{40}$/,C=/-(\d{5})-of-(\d{5})\.gguf$/i;function w(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function T(e,t){if(!e)throw Error(`Invalid model manifest v2: ${t}`)}function E(e,t){return T(typeof e==`string`&&e.length>0,`${t} must be a non-empty string`),e}function D(e,t){return T(Number.isSafeInteger(e)&&Number(e)>0,`${t} must be a positive integer`),Number(e)}function O(e,t){return T(Number.isSafeInteger(e)&&Number(e)>=0,`${t} must be a non-negative integer`),Number(e)}function ee(e,t){let n=E(e,t);return T(x.test(n),`${t} must be lowercase SHA-256 hex`),n}function k(e,t){let n=E(e,t);return T(S.test(n),`${t} must be a full 40-character commit`),n}function A(e,t){let n=E(e,t);return T(/^[^/\s]+\/[^/\s]+$/.test(n),`${t} must be a Hugging Face owner/repository id`),n}function te(e,t){let n=E(e,t);return T(!n.startsWith(`/`),`${t} must be repository-relative`),T(!n.split(`/`).some(e=>e===``||e===`.`||e===`..`),`${t} contains an unsafe segment`),T(n.toLowerCase().endsWith(`.gguf`),`${t} must point to a GGUF file`),n}function ne(e,t){return T(w(e),`${t} must be an object`),{repo:A(e.repo,`${t}.repo`),revision:k(e.revision,`${t}.revision`),file:te(e.file,`${t}.file`),bytes:D(e.bytes,`${t}.bytes`),sha256:ee(e.sha256,`${t}.sha256`)}}function re(e,t){T(w(e),`${t} must be an object`);let n={path:te(e.path,`${t}.path`),bytes:D(e.bytes,`${t}.bytes`),sha256:ee(e.sha256,`${t}.sha256`)};if(e.url!==void 0){let r=E(e.url,`${t}.url`);T(new URL(r).protocol===`https:`,`${t}.url must use HTTPS`),n.url=r}return n}function ie(e,t){T(w(e),`${t} must be an object`);let n={};for(let[r,i]of Object.entries(e))T(r.length>0,`${t} contains an empty key`),n[r]=D(i,`${t}.${r}`);return n}function j(e,t){T(w(e),`${t} must be an object`),T(w(e.markers),`${t}.markers must be an object`);let n=e.markers;return T(typeof n.think==`boolean`,`${t}.markers.think must be boolean`),T(typeof n.toolCall==`boolean`,`${t}.markers.toolCall must be boolean`),T(typeof n.toolResponse==`boolean`,`${t}.markers.toolResponse must be boolean`),{bytes:D(e.bytes,`${t}.bytes`),sha256:ee(e.sha256,`${t}.sha256`),markers:{think:n.think,toolCall:n.toolCall,toolResponse:n.toolResponse}}}function M(e,t){let n=e.map(e=>e.path.match(C)),r=n.filter(Boolean).length;if(r===0){T(e.length===1,`${t} must use GGUF split names when it contains multiple files`);return}T(r===e.length,`${t} mixes split and non-split GGUF names`);for(let r=0;r0,`${n}.files must be a non-empty array`);let i=e.files.map((e,t)=>re(e,`${n}.files[${t}]`));M(i,`${n}.files`),T(new Set(i.map(e=>e.path)).size===i.length,`${n}.files contains duplicate paths`);let a=D(e.downloadBytes,`${n}.downloadBytes`);T(i.reduce((e,t)=>e+t.bytes,0)===a,`${n}.downloadBytes does not equal the shard byte sum`);let o=D(e.contextLength,`${n}.contextLength`),s=D(e.defaultContext,`${n}.defaultContext`);T(s<=o,`${n}.defaultContext exceeds contextLength`),T(typeof e.cpuFallback==`boolean`,`${n}.cpuFallback must be boolean`),T(w(e.runtimePolicy),`${n}.runtimePolicy must be an object`),T(e.runtimePolicy.flashAttention===!1,`${n}.runtimePolicy.flashAttention must be false`),T(typeof e.runtimePolicy.tokenEmbeddingOnWebGPU==`boolean`,`${n}.runtimePolicy.tokenEmbeddingOnWebGPU must be boolean`),T(typeof e.runtimePolicy.requireSingleWebGPUGraph==`boolean`,`${n}.runtimePolicy.requireSingleWebGPUGraph must be boolean`);let c={id:r,displayName:E(e.displayName,`${n}.displayName`),architecture:E(e.architecture,`${n}.architecture`),source:ne(e.source,`${n}.source`),files:i,downloadBytes:a,contextLength:o,defaultContext:s,cpuFallback:e.cpuFallback,largestTensorBytes:D(e.largestTensorBytes,`${n}.largestTensorBytes`),requiredLimits:ie(e.requiredLimits,`${n}.requiredLimits`),chatTemplate:j(e.chatTemplate,`${n}.chatTemplate`),hybridDimensions:ie(e.hybridDimensions,`${n}.hybridDimensions`),nextNTensorCount:O(e.nextNTensorCount,`${n}.nextNTensorCount`),runtimePolicy:{flashAttention:!1,tokenEmbeddingOnWebGPU:e.runtimePolicy.tokenEmbeddingOnWebGPU,requireSingleWebGPUGraph:e.runtimePolicy.requireSingleWebGPUGraph}};return T(c.requiredLimits.maxStorageBufferBindingSize===c.largestTensorBytes,`${n}.requiredLimits.maxStorageBufferBindingSize must equal largestTensorBytes`),c.id===`27b`&&(T(c.cpuFallback===!1,`${n} must disable CPU fallback for 27B`),T(c.runtimePolicy.requireSingleWebGPUGraph,`${n} must require a single WebGPU graph for 27B`)),c}function P(e){T(w(e),`root must be an object`),T(e.schemaVersion===2,`schemaVersion must equal 2`),T(w(e.repository),`repository must be an object`);let t=A(e.repository.id,`repository.id`),n=e.repository.revision===null?null:k(e.repository.revision,`repository.revision`);T(Array.isArray(e.models),`models must be an array`);let r=e.models.map(N);return T(r.length===b.size,`models must contain exactly the four Bonsai tiers`),T(new Set(r.map(e=>e.id)).size===r.length,`models contains duplicate ids`),T(r.every(e=>b.has(e.id)),`models contains an unsupported id`),{schemaVersion:2,repository:{id:t,revision:n},models:r}}async function ae(e,t){let n=await fetch(e,{...t?{signal:t}:{},headers:{Accept:`application/json`}});if(!n.ok)throw Error(`Failed to load model manifest: HTTP ${n.status}`);return P(await n.json())}function F(e){if(typeof e!=`object`||!e)return!1;let t=e;return t.kind===`shard-download-failure`&&(t.failure===`network`||t.failure===`abort`||t.failure===`verification`)&&typeof t.causeCode==`string`&&Number.isSafeInteger(t.shardIndex)&&(t.shardIndex??-1)>=0&&Number.isSafeInteger(t.shardCount)&&(t.shardCount??0)>0&&(t.shardIndex??0)<(t.shardCount??0)&&typeof t.shardPath==`string`&&t.shardPath.length>0&&t.retryFromByteZero===!0&&typeof t.partialDeleted==`boolean`}var I=n(),L=c(),oe=`bonsai-webgpu-chat`,se=1,ce=`memory`,le=`conversations`;function ue(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error??Error(`IndexedDB request failed`))})}function de(e){return new Promise((t,n)=>{e.oncomplete=()=>t(),e.onerror=()=>n(e.error??Error(`IndexedDB transaction failed`)),e.onabort=()=>n(e.error??Error(`IndexedDB transaction aborted`))})}async function fe(){if(!(`indexedDB`in globalThis))throw Error(`IndexedDB is unavailable in this browser`);let e=indexedDB.open(oe,se);return e.onupgradeneeded=()=>{let t=e.result;t.objectStoreNames.contains(ce)||t.createObjectStore(ce,{keyPath:`key`}),t.objectStoreNames.contains(le)||t.createObjectStore(le,{keyPath:`id`}).createIndex(`updatedAt`,`updatedAt`)},ue(e)}function pe(e,t){let n=e.trim();if(!n||n.length>256)throw Error(`${t} must contain 1–256 characters`);return n}async function me(e,t){let n={key:pe(e,`memory key`),value:t.slice(0,64*1024),updatedAt:Date.now()},r=await fe(),i=r.transaction(ce,`readwrite`);return i.objectStore(ce).put(n),await de(i),r.close(),n}async function he(e){let t=await fe(),n=t.transaction(ce,`readonly`),r=await ue(n.objectStore(ce).get(pe(e,`memory key`)));return await de(n),t.close(),r}async function ge(){let e=await fe(),t=e.transaction(ce,`readonly`),n=await ue(t.objectStore(ce).getAll());return await de(t),e.close(),n.sort((e,t)=>t.updatedAt-e.updatedAt)}async function _e(e){let t=await fe(),n=t.transaction(ce,`readwrite`);n.objectStore(ce).delete(pe(e,`memory key`)),await de(n),t.close()}async function ve(e){let t=pe(e.id,`conversation id`),n=await fe(),r=n.transaction(le,`readwrite`);r.objectStore(le).put({...e,id:t,title:e.title.slice(0,256),updatedAt:Date.now()}),await de(r),n.close()}async function ye(){let e=await fe(),t=e.transaction(le,`readonly`),n=await ue(t.objectStore(le).getAll());return await de(t),e.close(),n.sort((e,t)=>t.updatedAt-e.updatedAt)}var be=64*1024,xe=16*1024,Se=` +(() => { + const send = self.postMessage.bind(self); + const render = (value) => { + if (typeof value === 'string') return value; + try { return JSON.stringify(value); } catch { return String(value); } + }; + const blocked = () => Promise.reject(new Error('Network access is disabled in js_eval')); + const hide = (target, key, value) => { + try { Object.defineProperty(target, key, { value, writable: false, configurable: false }); } catch {} + }; + hide(self, 'fetch', blocked); + hide(self, 'XMLHttpRequest', undefined); + hide(self, 'WebSocket', undefined); + hide(self, 'EventSource', undefined); + hide(self, 'SharedWorker', undefined); + hide(self, 'Worker', undefined); + hide(self, 'BroadcastChannel', undefined); + hide(self, 'importScripts', undefined); + hide(self, 'indexedDB', undefined); + hide(self, 'caches', undefined); + hide(self.navigator, 'storage', undefined); + hide(self, 'postMessage', undefined); + hide(self, 'close', undefined); + self.onmessage = async ({ data }) => { + const logs = []; + const capture = (...values) => logs.push(values.map(render).join(' ')); + const console = { log: capture, info: capture, warn: capture, error: capture }; + try { + const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; + const execute = new AsyncFunction('console', '"use strict";\\n' + data.source); + const value = await execute(console); + send({ ok: true, value: render(value), logs }); + } catch (error) { + send({ ok: false, error: error?.message ?? String(error), logs }); + } + }; +})(); +`;function Ce(e){return` - + +
diff --git a/dist/manifest/models.json b/dist/manifest/models.json index 228e7cc25d71d18b0665be31739c95b7674c06a5..392fb07230e6b2e759ad22442786408c596a3948 100644 --- a/dist/manifest/models.json +++ b/dist/manifest/models.json @@ -248,94 +248,5 @@ "requireSingleWebGPUGraph": true } } - ], - "dflash": { - "status": "conditional", - "enabledByDefault": false, - "runtimeAvailable": false, - "source": { - "repo": "z-lab/Qwen3.6-27B-DFlash", - "revision": "0919688658996800f86b895034249700e9481106", - "file": "model.safetensors", - "bytes": 3460432504, - "sha256": "e0c050b34798d32728a164d2c3f1681746ff85c11945701b0205b654e2f1fdbe", - "format": "safetensors", - "dtype": "BF16", - "parameters": 1730213120, - "sourceOnly": true, - "license": "MIT" - }, - "nativeReference": { - "path": "dflash/Qwen3.6-27B-DFlash-Q8_0.gguf", - "bytes": 1849481440, - "sha256": "80f04984d70c0989f4bc2c3b8ef34bc274226fce2723dc8a68e930ae46225fe9", - "format": "GGUF", - "quantization": "Q8_0", - "largestTensor": { - "name": "fc.weight", - "bytes": 139264000 - }, - "role": "native correctness reference only" - }, - "browser": { - "enabled": false, - "architectureCompiled": true, - "specTypePlumbed": false, - "candidateQuantizationStatus": "q8-rejected-binding-limit" - }, - "validation": { - "shape": "pass", - "tokenizer": "exact-match", - "targetTaps": [ - 2, - 17, - 32, - 47, - 62 - ], - "nativeGreedyParity": { - "status": "pass", - "tokens": 32, - "temperature": 0 - }, - "acceptanceBenchmark": "pending", - "browserSpeedGate": "pending" - }, - "pairings": { - "1_7b": { - "support": "unsupported", - "reason": "No compatible DFlash drafter is locked and validated for the Bonsai 1.7B target." - }, - "4b": { - "support": "unsupported", - "reason": "The pinned drafter is trained for Qwen3.6-27B (hidden size 5120, mask token 248070, and 64 target layers) and is not compatible with Bonsai 4B." - }, - "8b": { - "support": "unsupported", - "reason": "The pinned drafter is trained for Qwen3.6-27B (hidden size 5120, mask token 248070, and 64 target layers) and is not compatible with Bonsai 8B." - }, - "27b": { - "support": "conditional", - "compatibilityStatus": "native-greedy-parity-pass", - "runtimeAvailable": false, - "blockSize": 16, - "maskTokenId": 248070, - "targetLayerIdsZeroBased": [ - 1, - 16, - 31, - 46, - 61 - ], - "targetTapsOneBased": [ - 2, - 17, - 32, - 47, - 62 - ], - "reason": "The target/drafter contract and 32-token native greedy parity pass. Browser runtime stays disabled because spec_type=draft-dflash is not plumbed, the Q8 fc.weight exceeds the 128 MiB baseline binding limit, and acceptance/speed are unmeasured." - } - } - } + ] } diff --git a/dist/wasm/wllama-compat.wasm b/dist/wasm/wllama-compat.wasm index 0d620231f4af12ebb6ea56d44f3474d8f0598063..7db97eac90fe2ad123d868df46e2a94f11261314 100644 --- a/dist/wasm/wllama-compat.wasm +++ b/dist/wasm/wllama-compat.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea0623d2ca1758287a569169b4ad5a69c22e41bce3e5d022c7d3e4fb5c37918c -size 14865338 +oid sha256:c823684229149dd2386308978288e366f2ff209b231b78485507db8cb735bbdd +size 14871898 diff --git a/dist/wasm/wllama.wasm b/dist/wasm/wllama.wasm index 2fd5070dbbcec42f7616a579b84268d0cfb7bb57..c3ba94ae2b858a3fd77ada6df34b7cb3c2af8f90 100644 --- a/dist/wasm/wllama.wasm +++ b/dist/wasm/wllama.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b292bf670a25e1ecf02c71dd92b49196f3336842135f1f85efd5a3fd5f36ea8a -size 8017784 +oid sha256:5a8686c10f7639653e7e1854d34f39c31ddba404de876a5ae15d36c42263ddc4 +size 8020809 diff --git a/public/manifest/models.json b/public/manifest/models.json index 228e7cc25d71d18b0665be31739c95b7674c06a5..392fb07230e6b2e759ad22442786408c596a3948 100644 --- a/public/manifest/models.json +++ b/public/manifest/models.json @@ -248,94 +248,5 @@ "requireSingleWebGPUGraph": true } } - ], - "dflash": { - "status": "conditional", - "enabledByDefault": false, - "runtimeAvailable": false, - "source": { - "repo": "z-lab/Qwen3.6-27B-DFlash", - "revision": "0919688658996800f86b895034249700e9481106", - "file": "model.safetensors", - "bytes": 3460432504, - "sha256": "e0c050b34798d32728a164d2c3f1681746ff85c11945701b0205b654e2f1fdbe", - "format": "safetensors", - "dtype": "BF16", - "parameters": 1730213120, - "sourceOnly": true, - "license": "MIT" - }, - "nativeReference": { - "path": "dflash/Qwen3.6-27B-DFlash-Q8_0.gguf", - "bytes": 1849481440, - "sha256": "80f04984d70c0989f4bc2c3b8ef34bc274226fce2723dc8a68e930ae46225fe9", - "format": "GGUF", - "quantization": "Q8_0", - "largestTensor": { - "name": "fc.weight", - "bytes": 139264000 - }, - "role": "native correctness reference only" - }, - "browser": { - "enabled": false, - "architectureCompiled": true, - "specTypePlumbed": false, - "candidateQuantizationStatus": "q8-rejected-binding-limit" - }, - "validation": { - "shape": "pass", - "tokenizer": "exact-match", - "targetTaps": [ - 2, - 17, - 32, - 47, - 62 - ], - "nativeGreedyParity": { - "status": "pass", - "tokens": 32, - "temperature": 0 - }, - "acceptanceBenchmark": "pending", - "browserSpeedGate": "pending" - }, - "pairings": { - "1_7b": { - "support": "unsupported", - "reason": "No compatible DFlash drafter is locked and validated for the Bonsai 1.7B target." - }, - "4b": { - "support": "unsupported", - "reason": "The pinned drafter is trained for Qwen3.6-27B (hidden size 5120, mask token 248070, and 64 target layers) and is not compatible with Bonsai 4B." - }, - "8b": { - "support": "unsupported", - "reason": "The pinned drafter is trained for Qwen3.6-27B (hidden size 5120, mask token 248070, and 64 target layers) and is not compatible with Bonsai 8B." - }, - "27b": { - "support": "conditional", - "compatibilityStatus": "native-greedy-parity-pass", - "runtimeAvailable": false, - "blockSize": 16, - "maskTokenId": 248070, - "targetLayerIdsZeroBased": [ - 1, - 16, - 31, - 46, - 61 - ], - "targetTapsOneBased": [ - 2, - 17, - 32, - 47, - 62 - ], - "reason": "The target/drafter contract and 32-token native greedy parity pass. Browser runtime stays disabled because spec_type=draft-dflash is not plumbed, the Q8 fc.weight exceeds the 128 MiB baseline binding limit, and acceptance/speed are unmeasured." - } - } - } + ] } diff --git a/public/wasm/wllama-compat.wasm b/public/wasm/wllama-compat.wasm index 0d620231f4af12ebb6ea56d44f3474d8f0598063..7db97eac90fe2ad123d868df46e2a94f11261314 100644 --- a/public/wasm/wllama-compat.wasm +++ b/public/wasm/wllama-compat.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea0623d2ca1758287a569169b4ad5a69c22e41bce3e5d022c7d3e4fb5c37918c -size 14865338 +oid sha256:c823684229149dd2386308978288e366f2ff209b231b78485507db8cb735bbdd +size 14871898 diff --git a/public/wasm/wllama.wasm b/public/wasm/wllama.wasm index 2fd5070dbbcec42f7616a579b84268d0cfb7bb57..c3ba94ae2b858a3fd77ada6df34b7cb3c2af8f90 100644 --- a/public/wasm/wllama.wasm +++ b/public/wasm/wllama.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b292bf670a25e1ecf02c71dd92b49196f3336842135f1f85efd5a3fd5f36ea8a -size 8017784 +oid sha256:5a8686c10f7639653e7e1854d34f39c31ddba404de876a5ae15d36c42263ddc4 +size 8020809 diff --git a/scripts/verify-wllama-assets.mjs b/scripts/verify-wllama-assets.mjs index 95a2c03737baee88c6b70247436a28c01dc34df0..427b0c9a1f06cf0c79a6adbd6fb43d485350f2c0 100644 --- a/scripts/verify-wllama-assets.mjs +++ b/scripts/verify-wllama-assets.mjs @@ -48,6 +48,15 @@ const oaiDeclarations = await readFile(resolve(root, 'vendor/wllama-bonsai/esm/t if (!oaiDeclarations.includes('export interface ChatCompletionLogprob {\n id: number;')) { throw new Error('Vendored Bonsai wllama declarations lack sampled token ids'); } +if (!oaiDeclarations.includes('return_progress?: boolean;')) { + throw new Error('Vendored Bonsai wllama declarations lack prompt-progress opt-in'); +} +if (!oaiDeclarations.includes('export interface PromptProgress')) { + throw new Error('Vendored Bonsai wllama declarations lack prompt-progress payloads'); +} +if (!oaiDeclarations.includes('prompt_progress?: PromptProgress;')) { + throw new Error('Vendored Bonsai wllama declarations do not expose streamed prompt progress'); +} const moduleSource = await readFile(resolve(root, 'vendor/wllama-bonsai/esm/index.js'), 'utf8'); if (!moduleSource.includes('n_ubatch: params.n_ubatch')) { throw new Error('Vendored Bonsai wllama module does not forward n_ubatch'); diff --git a/src/app/App.test.ts b/src/app/App.test.ts index e16308d3147238dc2bed7a79b51881a85059749f..bdbe4bab552fdb84295191cd6f7731c68eef69e1 100644 --- a/src/app/App.test.ts +++ b/src/app/App.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'; import { EngineClientError } from '../engine'; import { DEFAULT_MODEL_ID, + publishBackendReport, reconcileCompletionTokens, reconcileTotalTokens, reportsModelWeightProgress, @@ -58,3 +59,24 @@ describe('App streamed completion telemetry', () => { expect(reconcileTotalTokens(80, 5, 64)).toBe(80); }); }); + +describe('App backend diagnostics', () => { + it('publishes the complete backend report for the browser gate without rendering it', () => { + const report = { + backends: ['WebGPU'], + nGraphSplits: 1, + opsOnCpu: 0, + layersGpu: { offloaded: 65, total: 65 }, + flashAttention: false, + cacheTypeK: 'f16', + cacheTypeV: 'f16', + webgpuKvBufferBytes: 1024, + webgpuTrace: ['@@WEBGPU_TRACE@@completion_end id=1 steps=65 error=0'], + }; + + publishBackendReport(report); + + expect(globalThis.__bonsaiBackendReport).toEqual(report); + delete globalThis.__bonsaiBackendReport; + }); +}); diff --git a/src/app/App.tsx b/src/app/App.tsx index b994f8fc17f798abc6a5b1f1a1385ad52d1bbeb3..5f6911adb321fa37dfed7c23d3674fa5af4617da 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -6,6 +6,7 @@ import { isShardDownloadFailureDetails, loadModelManifestV2, type EngineCapabilities, + type BackendReport, type EngineChatMessage, type EngineChatToolCall, type EngineEvent, @@ -18,7 +19,7 @@ import { AGENT_TOOLS, executeAgentTool, type ArtifactDocument } from '../lib/age import type { ChatMessage, ComposerSubmission, - DFlashState, + ModelLoadProgress, ModelId, ModelOption, RuntimeSettings, @@ -100,6 +101,12 @@ const INITIAL_TELEMETRY: TelemetrySnapshot = { contextUsed: 0, contextLimit: 4096, tokensPerSecond: 0, + prefillTokensPerSecond: 0, + decodeTokensPerSecond: 0, + inferencePhase: 'idle', + promptProcessed: 0, + promptTotal: 0, + completionTokens: 0, timeToFirstTokenMs: 0, gpuLayers: '—', graphSplits: null, @@ -112,6 +119,18 @@ const INITIAL_STORAGE: StorageStatus = { downloads: [], }; +const INITIAL_MODEL_LOAD_PROGRESS: ModelLoadProgress = { + modelId: null, + modelLabel: '', + stage: 'idle', + stageProgress: 0, + downloadedBytes: 0, + totalBytes: 0, + residentBytes: 0, + nativeStage: null, + shards: [], +}; + let sharedClient: BrowserEngineClient | null = null; function getEngineClient(): BrowserEngineClient { @@ -189,6 +208,10 @@ export function reconcileTotalTokens( return Math.max(totalCount, promptCount + completionTokens); } +export function publishBackendReport(report: BackendReport): void { + globalThis.__bonsaiBackendReport = report; +} + function appendSegment(current: string, next: string): string { const trimmed = next.trim(); if (!trimmed) return current; @@ -244,24 +267,6 @@ function fallbackToolCalls(text: string): EngineChatToolCall[] { })); } -function dflashState(manifest: ModelManifestV2 | null, tier: ModelTierId): DFlashState { - if (!manifest) { - return { - enabled: false, - available: false, - status: 'unsupported', - reason: 'No DFlash audit is present in the pinned manifest.', - }; - } - const pairing = manifest.dflash.pairings[tier]; - return { - enabled: false, - available: false, - status: pairing.support, - reason: pairing.reason, - }; -} - export function App() { const [client] = useState(getEngineClient); const [manifest, setManifest] = useState(null); @@ -271,6 +276,7 @@ export function App() { const [loadingModelId, setLoadingModelId] = useState(null); const [messages, setMessages] = useState([]); const [streamState, setStreamState] = useState('idle'); + const [modelLoadProgress, setModelLoadProgress] = useState(INITIAL_MODEL_LOAD_PROGRESS); const [toolsEnabled, setToolsEnabled] = useState(false); const [settings, setSettings] = useState(INITIAL_SETTINGS); const [settingsOpen, setSettingsOpen] = useState(false); @@ -284,6 +290,61 @@ export function App() { const operationRef = useRef(null); const loadedConfigurationRef = useRef<{ id: ModelId; backend: RequestedBackend; contextSize: number } | null>(null); const historyReadyRef = useRef(false); + const loadProgressFrameRef = useRef(null); + const pendingLoadProgressRef = useRef(null); + const modelLoadProgressRef = useRef(INITIAL_MODEL_LOAD_PROGRESS); + const inferenceFrameRef = useRef(null); + const pendingInferenceRef = useRef | null>(null); + + const publishModelLoadProgress = (next: ModelLoadProgress): void => { + modelLoadProgressRef.current = next; + pendingLoadProgressRef.current = next; + if (loadProgressFrameRef.current !== null) return; + loadProgressFrameRef.current = window.requestAnimationFrame(() => { + loadProgressFrameRef.current = null; + const pending = pendingLoadProgressRef.current; + pendingLoadProgressRef.current = null; + if (pending) setModelLoadProgress(pending); + }); + }; + + const publishInferenceProgress = ( + next: Extract, + ): void => { + pendingInferenceRef.current = next; + if (inferenceFrameRef.current !== null) return; + inferenceFrameRef.current = window.requestAnimationFrame(() => { + inferenceFrameRef.current = null; + const pending = pendingInferenceRef.current; + pendingInferenceRef.current = null; + if (!pending) return; + const liveRate = pending.phase === 'prefill' + ? pending.promptTokensPerSecond + : pending.decodeTokensPerSecond; + setTelemetry((current) => ({ + ...current, + inferencePhase: pending.phase, + tokensPerSecond: liveRate, + prefillTokensPerSecond: pending.promptTokensPerSecond || current.prefillTokensPerSecond, + decodeTokensPerSecond: pending.decodeTokensPerSecond || current.decodeTokensPerSecond, + promptProcessed: pending.promptProcessed, + promptTotal: pending.promptTotal, + completionTokens: pending.completionTokens, + contextUsed: Math.min( + current.contextLimit, + Math.max(current.contextUsed, pending.promptProcessed + pending.completionTokens), + ), + })); + }); + }; + + const clearPendingInferenceProgress = (): void => { + if (inferenceFrameRef.current !== null) { + window.cancelAnimationFrame(inferenceFrameRef.current); + inferenceFrameRef.current = null; + } + pendingInferenceRef.current = null; + }; const handleModelInvalidation = (runtimeError: unknown): boolean => { if (!requiresModelReload(runtimeError)) return false; @@ -295,6 +356,13 @@ export function App() { modelMemoryBytes: 0, kvMemoryBytes: 0, contextUsed: 0, + tokensPerSecond: 0, + prefillTokensPerSecond: 0, + decodeTokensPerSecond: 0, + inferencePhase: 'idle', + promptProcessed: 0, + promptTotal: 0, + completionTokens: 0, gpuLayers: '—', graphSplits: null, })); @@ -306,6 +374,15 @@ export function App() { }; const persistenceAttemptedRef = useRef(false); + useEffect(() => () => { + if (loadProgressFrameRef.current !== null) { + window.cancelAnimationFrame(loadProgressFrameRef.current); + } + if (inferenceFrameRef.current !== null) { + window.cancelAnimationFrame(inferenceFrameRef.current); + } + }, []); + useEffect(() => { let cancelled = false; const bootstrap = async () => { @@ -413,14 +490,12 @@ export function App() { capability: decor.capability, contextLimit: model.contextLength, defaultContext: model.defaultContext, - dflash: manifest.dflash.pairings[model.id].support, ...(limitReason ? { limitReason } : {}), }; }); }, [capabilities, loadedModelId, loadingModelId, manifest, settings.backend, settings.contextSize, storage.downloads]); const activeTier = UI_TO_TIER[activeModelId]; - const dflash = useMemo(() => dflashState(manifest, activeTier), [activeTier, manifest]); const refreshStorage = async (): Promise => { const estimate = await client.storageEstimate(); @@ -473,6 +548,15 @@ export function App() { setError(null); setShardRetry(null); setBootStatus(`Preparing ${model.displayName}…`); + const initialProgress: ModelLoadProgress = { + ...INITIAL_MODEL_LOAD_PROGRESS, + modelId, + modelLabel: model.displayName, + stage: 'manifest', + totalBytes: model.downloadBytes, + }; + modelLoadProgressRef.current = initialProgress; + setModelLoadProgress(initialProgress); try { const result = await client.loadModel({ manifestUrl: manifestUrl(), @@ -482,26 +566,51 @@ export function App() { }, { signal: controller.signal, onProgress: (progress) => { + const received = Math.min(progress.loadedBytes, model.downloadBytes); + const shard = progress.shardIndex === null ? '' : ` · shard ${progress.shardIndex + 1}/${progress.shardCount}`; + const stageProgress = progress.stageProgress + ?? (progress.totalBytes > 0 ? progress.loadedBytes / progress.totalBytes : 0); + publishModelLoadProgress({ + modelId, + modelLabel: model.displayName, + stage: progress.phase, + stageProgress: Math.min(1, Math.max(0, stageProgress)), + downloadedBytes: received, + totalBytes: model.downloadBytes, + residentBytes: progress.residentBytes ?? 0, + nativeStage: progress.nativeStage, + shards: progress.shards.map((item) => ({ ...item })), + }); + if (progress.residentBytes !== null) { + setTelemetry((current) => ({ ...current, modelMemoryBytes: progress.residentBytes ?? 0 })); + } if (!reportsModelWeightProgress(progress.phase)) { setBootStatus('Reading pinned model manifest…'); return; } - const received = Math.min(progress.loadedBytes, model.downloadBytes); - const shard = progress.shardIndex === null ? '' : ` · shard ${progress.shardIndex + 1}/${progress.shardCount}`; - setBootStatus(`${progress.phase}${shard} · ${formatBytes(received)} / ${formatBytes(model.downloadBytes)}`); - setStorage((current) => ({ - ...current, - downloads: [ - ...current.downloads.filter((download) => download.modelId !== modelId), - { - modelId, - label: `${model.displayName}${shard}`, - receivedBytes: received, - totalBytes: model.downloadBytes, - state: progress.loadedBytes >= model.downloadBytes ? 'complete' : 'downloading', - }, - ], - })); + const stageLabel = progress.phase === 'cache' + ? 'Inspecting cache' + : progress.phase === 'verify' + ? 'Verifying shards' + : progress.phase === 'load' + ? 'Allocating model' + : 'Downloading model'; + setBootStatus(`${stageLabel}${shard} · ${Math.round(stageProgress * 100)}%`); + if (progress.phase !== 'load') { + setStorage((current) => ({ + ...current, + downloads: [ + ...current.downloads.filter((download) => download.modelId !== modelId), + { + modelId, + label: model.displayName, + receivedBytes: received, + totalBytes: model.downloadBytes, + state: received >= model.downloadBytes ? 'complete' : 'downloading', + }, + ], + })); + } }, }); setLoadedModelId(modelId); @@ -513,7 +622,8 @@ export function App() { device: result.backend === 'webgpu' ? capabilities?.webgpu.name || 'WebGPU adapter' : `CPU · ${capabilities?.hardwareConcurrency ?? navigator.hardwareConcurrency ?? 1} logical cores`, - modelMemoryBytes: model.downloadBytes, + modelMemoryBytes: result.backendReport.modelBufferBytes ?? model.downloadBytes, + kvMemoryBytes: result.backendReport.webgpuKvBufferBytes ?? 0, contextUsed: 0, contextLimit: result.context.size, gpuLayers: result.backendReport.layersGpu @@ -521,6 +631,17 @@ export function App() { : result.backend === 'wasm' ? 'CPU' : 'unreported', graphSplits: result.backendReport.nGraphSplits, })); + publishModelLoadProgress({ + ...modelLoadProgressRef.current, + modelId, + modelLabel: model.displayName, + stage: 'ready', + stageProgress: 1, + downloadedBytes: model.downloadBytes, + totalBytes: model.downloadBytes, + residentBytes: result.backendReport.allocatedBufferBytes ?? result.backendReport.modelBufferBytes ?? model.downloadBytes, + nativeStage: null, + }); setBootStatus(`${model.displayName} loaded · ${result.backend.toUpperCase()} · ${result.context.size.toLocaleString()} ctx`); await refreshStorage(); } catch (loadError) { @@ -539,6 +660,11 @@ export function App() { error: errorMessage(loadError), }); } + publishModelLoadProgress({ + ...modelLoadProgressRef.current, + stage: operatorAborted ? 'idle' : 'error', + nativeStage: null, + }); setStorage((current) => ({ ...current, downloads: operatorAborted @@ -621,6 +747,22 @@ export function App() { baseMessages: ChatMessage[], ) => { if (operationRef.current || !manifest) return; + const tier = UI_TO_TIER[activeModelId]; + const selectedModel = manifest.models.find((candidate) => candidate.id === tier); + if (!selectedModel) { + setError(`Model ${tier} is absent from the pinned manifest.`); + return; + } + const requestedContext = targetContext(selectedModel); + const configured = loadedConfigurationRef.current; + const needsLoad = loadedModelId !== activeModelId + || configured?.backend !== settings.backend + || configured.contextSize !== requestedContext; + if (needsLoad) { + setError(`Load ${selectedModel.displayName} before running this prompt.`); + setBootStatus('Explicit model load required'); + return; + } const controller = new AbortController(); operationRef.current = controller; const nonce = crypto.randomUUID(); @@ -645,37 +787,41 @@ export function App() { setError(null); const run = async () => { - const tier = UI_TO_TIER[activeModelId]; - const model = manifest.models.find((candidate) => candidate.id === tier); - if (!model) throw new Error(`Model ${tier} is absent from the pinned manifest.`); - const requestedContext = targetContext(model); - const configured = loadedConfigurationRef.current; - const needsLoad = loadedModelId !== activeModelId - || configured?.backend !== settings.backend - || configured.contextSize !== requestedContext; - if (needsLoad) { - await requestPersistentStorage(); - if (controller.signal.aborted) throw new DOMException('Model loading was aborted.', 'AbortError'); - await loadModel(activeModelId, controller, requestedContext); - } setStreamState('streaming'); - setBootStatus(`Generating with ${model.displayName}…`); + setBootStatus(`Generating with ${selectedModel.displayName}…`); const engineMessages = buildEngineHistory(historySnapshot, settings.systemPrompt); let visibleText = ''; let reasoningText = ''; let totalCompletionTokens = 0; + let totalPromptTokens = 0; let lastTotalTokens = 0; let tokensPerSecond = 0; + let prefillTokensPerSecond = 0; + let totalPromptSeconds = 0; + let totalDecodeSeconds = 0; let firstTokenAt = 0; const generationStartedAt = performance.now(); let toolRounds = 0; let totalToolCalls = 0; + clearPendingInferenceProgress(); + setTelemetry((current) => ({ + ...current, + inferencePhase: 'prefill', + tokensPerSecond: 0, + prefillTokensPerSecond: 0, + decodeTokensPerSecond: 0, + promptProcessed: 0, + promptTotal: 0, + completionTokens: 0, + timeToFirstTokenMs: 0, + })); while (true) { let streamedText = ''; let streamedReasoning = ''; let streamedTokenEvents = 0; + const completionOffset = totalCompletionTokens; const result = await client.generate({ messages: engineMessages, maxTokens: settings.maxTokens, @@ -686,6 +832,12 @@ export function App() { cachePrompt: true, }, { signal: controller.signal, + onGenerationProgress: (progress) => { + publishInferenceProgress({ + ...progress, + completionTokens: completionOffset + progress.completionTokens, + }); + }, onToken: (delta, reasoningDelta) => { if (!firstTokenAt && (delta || reasoningDelta)) firstTokenAt = performance.now(); if (delta || reasoningDelta) streamedTokenEvents += 1; @@ -708,6 +860,8 @@ export function App() { result.usage?.completionTokens, streamedTokenEvents, ); + const roundPromptTokens = result.usage?.promptTokens ?? 0; + totalPromptTokens += roundPromptTokens; totalCompletionTokens += roundCompletionTokens; lastTotalTokens = Math.max( lastTotalTokens, @@ -717,7 +871,14 @@ export function App() { roundCompletionTokens, ), ); - tokensPerSecond = result.timings?.predictedTokensPerSecond ?? tokensPerSecond; + const roundPrefillRate = result.timings?.promptTokensPerSecond ?? 0; + const roundDecodeRate = result.timings?.predictedTokensPerSecond ?? 0; + if (roundPromptTokens > 0 && roundPrefillRate > 0) { + totalPromptSeconds += roundPromptTokens / roundPrefillRate; + } + if (roundCompletionTokens > 0 && roundDecodeRate > 0) { + totalDecodeSeconds += roundCompletionTokens / roundDecodeRate; + } const thought = parseThoughtStream(result.text); const textual = parseToolCalls(thought.content); if (textual.incomplete || textual.errors.length > 0) { @@ -791,11 +952,14 @@ export function App() { } const generationElapsedSeconds = Math.max(0.001, (performance.now() - generationStartedAt) / 1000); - if (!Number.isFinite(tokensPerSecond) || tokensPerSecond <= 0 || tokensPerSecond > 10_000 || totalCompletionTokens < 2) { - tokensPerSecond = totalCompletionTokens / generationElapsedSeconds; - } + tokensPerSecond = totalDecodeSeconds > 0 + ? totalCompletionTokens / totalDecodeSeconds + : totalCompletionTokens / generationElapsedSeconds; + prefillTokensPerSecond = totalPromptSeconds > 0 ? totalPromptTokens / totalPromptSeconds : 0; const ttft = firstTokenAt ? Math.round(firstTokenAt - generationStartedAt) : 0; const report = await client.backendReport(); + publishBackendReport(report); + clearPendingInferenceProgress(); replaceMessage(setMessages, assistantId, (message) => ({ ...message, state: 'complete', @@ -803,6 +967,7 @@ export function App() { metrics: { tokens: totalCompletionTokens, tokensPerSecond, + promptTokensPerSecond: prefillTokensPerSecond, timeToFirstTokenMs: ttft, }, })); @@ -810,6 +975,12 @@ export function App() { ...current, contextUsed: Math.min(current.contextLimit, Math.max(lastTotalTokens, totalCompletionTokens)), tokensPerSecond, + prefillTokensPerSecond, + decodeTokensPerSecond: tokensPerSecond, + inferencePhase: 'complete', + promptProcessed: totalPromptTokens, + promptTotal: totalPromptTokens, + completionTokens: totalCompletionTokens, timeToFirstTokenMs: ttft, graphSplits: report.nGraphSplits, gpuLayers: report.layersGpu @@ -817,12 +988,13 @@ export function App() { : current.gpuLayers, })); setStreamState('complete'); - setBootStatus(`${model.displayName} complete · ${tokensPerSecond.toFixed(1)} tok/s`); + setBootStatus(`${selectedModel.displayName} complete · ${tokensPerSecond.toFixed(1)} tok/s`); await refreshStorage(); }; void run() .catch((runError) => { + clearPendingInferenceProgress(); if (handleModelInvalidation(runError)) { const message = errorMessage(runError); replaceMessage(setMessages, assistantId, (current) => ({ @@ -837,6 +1009,7 @@ export function App() { if (isAborted(runError, controller.signal)) { replaceMessage(setMessages, assistantId, (message) => ({ ...message, state: 'complete', isThinking: false })); setStreamState('complete'); + setTelemetry((current) => ({ ...current, inferencePhase: 'idle' })); setBootStatus('Generation stopped by operator'); return; } @@ -849,6 +1022,7 @@ export function App() { })); setError(message); setStreamState('error'); + setTelemetry((current) => ({ ...current, inferencePhase: 'idle' })); setBootStatus('Runtime stopped safely'); }) .finally(() => { @@ -944,10 +1118,10 @@ export function App() { loadedModelId={effectiveLoadedModelId} messages={messages} streamState={streamState} + modelLoadProgress={modelLoadProgress} telemetry={telemetry} toolEvents={toolEvents} storage={storage} - dflash={dflash} artifact={artifact} toolsEnabled={toolsEnabled} settings={settings} @@ -961,7 +1135,6 @@ export function App() { onRegenerate={handleRegenerate} onCancel={handleCancel} onToolsEnabledChange={setToolsEnabled} - onDFlashChange={() => undefined} onOpenSettings={() => { if (!operationRef.current) setSettingsOpen(true); }} onCloseSettings={() => setSettingsOpen(false)} onSettingsChange={(nextSettings) => { if (!operationRef.current) setSettings(nextSettings); }} @@ -971,7 +1144,6 @@ export function App() { onDismissError={() => setError(null)} onRetryShard={handleLoadModel} onCloseArtifact={() => setArtifact(null)} - onRequestDownload={handleLoadModel} /> ); } diff --git a/src/app/components/ArtifactPane.tsx b/src/app/components/ArtifactPane.tsx index 2fd3076ed97fcdea83091b8b6e4edc39ba7eb469..4adf655d37e7f2d69c37335a6baaa37ad914a4e6 100644 --- a/src/app/components/ArtifactPane.tsx +++ b/src/app/components/ArtifactPane.tsx @@ -25,7 +25,7 @@ export function ArtifactPane({ artifact, onClose }: ArtifactPaneProps) { return (
- 05 + 04

Isolated terrarium

Artifact

diff --git a/src/app/components/BonsaiShell.tsx b/src/app/components/BonsaiShell.tsx index 38dccc97480c040c4cbf06edd0641151d605ac73..f8ef2cc8749e1e7022d0f49e310862a4de68f456 100644 --- a/src/app/components/BonsaiShell.tsx +++ b/src/app/components/BonsaiShell.tsx @@ -20,17 +20,16 @@ export function BonsaiShell(props: BonsaiShellProps) { diff --git a/src/app/components/ChatSurface.tsx b/src/app/components/ChatSurface.tsx index 5e38b75e5e86d0160f1d0201df32aa93cb05f204..439dd9387195ecf4082f7928795bb324dc39d417 100644 --- a/src/app/components/ChatSurface.tsx +++ b/src/app/components/ChatSurface.tsx @@ -2,12 +2,15 @@ import { useMemo, useState, type FormEvent, type KeyboardEvent } from 'react'; import type { ChatMessage, ComposerSubmission, + ModelLoadProgress, ModelId, ModelOption, ShardRetryNotice, StreamState, + TelemetrySnapshot, ToolRun, } from '../../lib/contracts'; +import { ComposerActivity } from './ComposerActivity'; import { MarkdownContent } from './MarkdownContent'; interface ChatSurfaceProps { @@ -15,6 +18,8 @@ interface ChatSurfaceProps { loadedModelId: ModelId | null; messages: ChatMessage[]; streamState: StreamState; + modelLoadProgress: ModelLoadProgress; + telemetry: TelemetrySnapshot; toolsEnabled: boolean; runtimeStatus: string; error: string | null; @@ -33,6 +38,8 @@ export function ChatSurface({ loadedModelId, messages, streamState, + modelLoadProgress, + telemetry, toolsEnabled, runtimeStatus, error, @@ -46,7 +53,9 @@ export function ChatSurface({ onToolsEnabledChange, }: ChatSurfaceProps) { const [prompt, setPrompt] = useState(''); - const isBusy = streamState === 'streaming' || streamState === 'loading'; + const isLoading = streamState === 'loading'; + const isStreaming = streamState === 'streaming'; + const isBusy = isStreaming || isLoading; const isLoaded = loadedModelId === activeModel.id; const logDate = useMemo(() => new Intl.DateTimeFormat('en-GB', { day: '2-digit', month: 'long', year: 'numeric', @@ -54,7 +63,7 @@ export function ChatSurface({ const submit = () => { const trimmed = prompt.trim(); - if (!trimmed || isBusy) return; + if (!trimmed || isBusy || !isLoaded) return; onSend({ prompt: trimmed, toolsEnabled }); setPrompt(''); }; @@ -83,11 +92,6 @@ export function ChatSurface({ {activeModel.capability} canopy {Math.round(activeModel.contextLimit / 1024)}K context {isLoaded ? 'local graph' : 'not loaded'} - {!isLoaded && ( - - )}
@@ -129,6 +133,13 @@ export function ChatSurface({
+
{messages.some((message) => message.role === 'user') && ( - )} @@ -152,17 +163,21 @@ export function ChatSurface({ value={prompt} onChange={(event) => setPrompt(event.target.value)} onKeyDown={handleKeyDown} - placeholder={isLoaded ? 'Describe an observation or assign a tool…' : 'The first run will download this model…'} + placeholder={isLoaded ? 'Describe an observation or assign a tool…' : 'Write while the model loads, then run locally…'} rows={2} - disabled={isBusy} + disabled={isStreaming} /> {isBusy ? ( + ) : !isLoaded ? ( + ) : ( )}
@@ -196,7 +211,10 @@ function Message({ message, ordinal }: { message: ChatMessage; ordinal: number } {message.metrics && (
{message.metrics.tokens} tok - {message.metrics.tokensPerSecond.toFixed(1)} tok/s + {message.metrics.promptTokensPerSecond !== undefined && ( + Prefill {message.metrics.promptTokensPerSecond.toFixed(1)} tok/s + )} + Decode {message.metrics.tokensPerSecond.toFixed(1)} tok/s TTFT {message.metrics.timeToFirstTokenMs} ms
)} @@ -235,3 +253,11 @@ function ArrowIcon() { ); } + +function DownloadIcon() { + return ( + + ); +} diff --git a/src/app/components/ComposerActivity.tsx b/src/app/components/ComposerActivity.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7e87d3527c235573a5f8341a14b059be716ee79a --- /dev/null +++ b/src/app/components/ComposerActivity.tsx @@ -0,0 +1,205 @@ +import type { + ModelId, + ModelLoadProgress, + StreamState, + TelemetrySnapshot, +} from '../../lib/contracts'; +import { formatBytes, formatPercent } from '../../lib/format'; + +interface ComposerActivityProps { + modelId: ModelId; + isLoaded: boolean; + streamState: StreamState; + progress: ModelLoadProgress; + telemetry: TelemetrySnapshot; +} + +const LOAD_STAGES = [ + ['manifest', 'Manifest'], + ['cache', 'Cache'], + ['download', 'Download'], + ['verify', 'Verify'], + ['load', 'Allocate'], +] as const; + +type LoadStage = typeof LOAD_STAGES[number][0]; + +function ratio(value: number, total: number): number { + return total > 0 ? Math.min(1, Math.max(0, value / total)) : 0; +} + +function humanizeStage(value: string | null): string { + if (!value) return ''; + return value.replaceAll('_', ' ').replace(/\b\w/g, (letter) => letter.toUpperCase()); +} + +export function ComposerActivity({ + modelId, + isLoaded, + streamState, + progress, + telemetry, +}: ComposerActivityProps) { + const tracksModel = progress.modelId === modelId; + const isLoading = streamState === 'loading' && tracksModel; + const isInferring = streamState === 'streaming' + || telemetry.inferencePhase === 'prefill' + || telemetry.inferencePhase === 'decode'; + const activeStage = tracksModel && LOAD_STAGES.some(([stage]) => stage === progress.stage) + ? progress.stage as LoadStage + : null; + const downloadRatio = tracksModel ? ratio(progress.downloadedBytes, progress.totalBytes) : 0; + const verifiedBytes = displayShardsTotal(progress.shards, 'verifiedBytes'); + const verifiedRatio = tracksModel ? ratio(verifiedBytes, progress.totalBytes) : 0; + const prefillRatio = ratio(telemetry.promptProcessed, telemetry.promptTotal); + const displayShards = tracksModel ? progress.shards : []; + const pipelineProgress = !tracksModel + ? 0 + : progress.stage === 'ready' + ? 1 + : progress.stage === 'load' + ? 0.85 + Math.min(1, Math.max(0, progress.stageProgress)) * 0.15 + : progress.stage === 'manifest' + ? Math.min(1, Math.max(0, progress.stageProgress)) * 0.05 + : Math.min(0.85, 0.05 + downloadRatio * 0.55 + verifiedRatio * 0.25); + + const status = isLoading + ? progress.nativeStage + ? humanizeStage(progress.nativeStage) + : LOAD_STAGES.find(([stage]) => stage === activeStage)?.[1] ?? 'Preparing model' + : isInferring + ? telemetry.inferencePhase === 'prefill' ? 'Prefilling prompt' : 'Generating response' + : progress.stage === 'error' && tracksModel + ? 'Model load stopped' + : isLoaded ? 'Model ready in this tab' : 'Model is not loaded'; + + return ( +
+ + +
+
+
+ {isLoading && ( + {formatPercent(pipelineProgress)} · {formatBytes(progress.downloadedBytes)} / {formatBytes(progress.totalBytes)} + )} + {isInferring && telemetry.inferencePhase === 'prefill' && ( + {telemetry.promptProcessed.toLocaleString()} / {telemetry.promptTotal.toLocaleString()} tokens + )} + {isInferring && telemetry.inferencePhase === 'decode' && ( + {telemetry.completionTokens.toLocaleString()} generated tokens + )} + {!isLoading && !isInferring && isLoaded && progress.residentBytes > 0 && ( + {formatBytes(progress.residentBytes)} allocated + )} +
+ + {isInferring ? ( +
+ 0 ? formatPercent(prefillRatio) : 'waiting'} + progress={prefillRatio} + active={telemetry.inferencePhase === 'prefill'} + /> + +
+ ) : ( +
+
+
Model transfer{formatPercent(downloadRatio)}
+ +
+
+
Load pipeline{formatPercent(pipelineProgress)}
+ +
+
+ Resident + {formatBytes(tracksModel ? progress.residentBytes : 0)} +
+
+ )} + +
+ {displayShards.length === 0 + ? Shard detail appears during cache inspection and transfer. + : displayShards.map((shard) => { + const shardRatio = shard.state === 'complete' || shard.state === 'cached' + ? 1 + : shard.state === 'verifying' + ? ratio(shard.verifiedBytes, shard.totalBytes) + : ratio(shard.loadedBytes, shard.totalBytes); + return ( +
+ S{shard.index + 1}{formatPercent(shardRatio)} + +
+ ); + })} +
+
+ ); +} + +function displayShardsTotal( + shards: ModelLoadProgress['shards'], + field: 'loadedBytes' | 'verifiedBytes', +): number { + return shards.reduce((total, shard) => total + shard[field], 0); +} + +function LiveMetric({ + label, + value, + detail, + progress, + active, +}: { + label: string; + value: string; + detail: string; + progress: number; + active: boolean; +}) { + return ( +
+
{label}{value}{detail}
+ +
+ ); +} + +function ProgressTrack({ label, value }: { label: string; value: number }) { + const percentage = Math.round(Math.min(1, Math.max(0, value)) * 100); + return ( +
+ +
+ ); +} diff --git a/src/app/components/InspectorPanel.tsx b/src/app/components/InspectorPanel.tsx index 912b6f9c810cca694975f3105a36f7c343e4406c..20ca97bcdf9fa843a1ba81c132326c2150701757 100644 --- a/src/app/components/InspectorPanel.tsx +++ b/src/app/components/InspectorPanel.tsx @@ -1,20 +1,26 @@ import type { ArtifactDocument } from '../../lib/agent-tools'; -import type { StorageStatus, TelemetrySnapshot, ToolEvent } from '../../lib/contracts'; -import { formatBytes, formatPercent } from '../../lib/format'; +import type { TelemetrySnapshot, ToolEvent } from '../../lib/contracts'; +import { formatBytes } from '../../lib/format'; import { ArtifactPane } from './ArtifactPane'; interface InspectorPanelProps { telemetry: TelemetrySnapshot; toolEvents: ToolEvent[]; - storage: StorageStatus; artifact: ArtifactDocument | null; onCloseArtifact: () => void; } -export function InspectorPanel({ telemetry, toolEvents, storage, artifact, onCloseArtifact }: InspectorPanelProps) { +export function InspectorPanel({ telemetry, toolEvents, artifact, onCloseArtifact }: InspectorPanelProps) { const contextRatio = telemetry.contextLimit > 0 ? telemetry.contextUsed / telemetry.contextLimit : 0; - const storageRatio = storage.quotaBytes > 0 ? storage.usedBytes / storage.quotaBytes : 0; - const speedRatio = Math.min(1, Math.max(0, telemetry.tokensPerSecond / 50)); + const activeRate = telemetry.inferencePhase === 'prefill' + ? telemetry.prefillTokensPerSecond + : telemetry.decodeTokensPerSecond || telemetry.tokensPerSecond; + const speedRatio = Math.min(1, Math.max(0, activeRate / 50)); + const gaugeLabel = telemetry.inferencePhase === 'prefill' + ? 'prefill tok / sec' + : telemetry.inferencePhase === 'decode' + ? 'decode tok / sec' + : 'average tok / sec'; return ( ); diff --git a/src/app/components/ModelRail.tsx b/src/app/components/ModelRail.tsx index 03a6da8766d4e65ce19614b1d57ce4e13f65194a..01644051a494a06eb4c0329ad724e5395d19f487 100644 --- a/src/app/components/ModelRail.tsx +++ b/src/app/components/ModelRail.tsx @@ -1,13 +1,10 @@ -import type { DFlashState, ModelId, ModelOption } from '../../lib/contracts'; +import type { ModelId, ModelOption } from '../../lib/contracts'; interface ModelRailProps { models: ModelOption[]; activeModelId: ModelId; - dflash: DFlashState; busy: boolean; onModelSelect: (modelId: ModelId) => void; - onDFlashChange: (enabled: boolean) => void; - onRequestDownload: (modelId: ModelId) => void; } const availabilityLabels: Record = { @@ -21,11 +18,8 @@ const availabilityLabels: Record = { export function ModelRail({ models, activeModelId, - dflash, busy, onModelSelect, - onDFlashChange, - onRequestDownload, }: ModelRailProps) { return (