diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1e09f761..f1252116 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -16,43 +16,11 @@ jobs: fetch-depth: 0 # 拉取完整提交历史 fetch-tags: true # 关键:拉取所有标签 - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.23' # 请根据需要选择合适的 Go 版本 - - - name: Install Flatbuffers - run: | - wget -q https://github.com/google/flatbuffers/releases/download/v24.12.23/Linux.flatc.binary.clang++-18.zip - unzip Linux.flatc.binary.clang++-18.zip - sudo cp flatc /usr/local/bin - rm flatc Linux.flatc.binary.clang++-18.zip - - - name: Verify Go Version - run: go version - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.13' - - - name: Verify Python Version - run: python --version - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install build - - - name: Install Docker - uses: docker/setup-buildx-action@v2 - - - name: Install Dependencies & Compile Files + - name: Install system dependencies & build release run: | + sudo apt update sudo apt install portaudio19-dev libx11-dev libxtst-dev - # curl -fsSL https://get.docker.com -o get-docker.sh - # sudo sh get-docker.sh - make + make build-release - name: Create GitHub Release id: create_release @@ -63,8 +31,7 @@ jobs: body: | SPEAR release version ${{ github.ref_name }}. files: | - ./bin/spearlet - ./sdk/python/dist/spearlet-*.whl - ./sdk/python/dist/spearlet-*.tar.gz + ./target/release/spearlet + ./target/release/sms env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 使用 GitHub 提供的令牌 diff --git a/Cargo.lock b/Cargo.lock index fef08e06..f2cd11eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3433,6 +3433,7 @@ dependencies = [ "serial_test", "sha2", "sled", + "spear-ssf", "tempfile", "testcontainers", "thiserror 1.0.69", @@ -3444,7 +3445,6 @@ dependencies = [ "toml", "tonic", "tonic-build", - "tonic-web", "tower 0.5.2", "tower-http", "tracing", @@ -3456,6 +3456,10 @@ dependencies = [ "wasmedge-sys", ] +[[package]] +name = "spear-ssf" +version = "0.1.0" + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3915,26 +3919,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "tonic-web" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5299dd20801ad736dccb4a5ea0da7376e59cd98f213bf1c3d478cf53f4834b58" -dependencies = [ - "base64 0.22.1", - "bytes", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "pin-project", - "tokio-stream", - "tonic", - "tower-http", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.4.13" diff --git a/Cargo.toml b/Cargo.toml index 26ef5520..23b94151 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ tokio-util = "0.7" # gRPC framework / gRPC框架 tonic = "0.12" -tonic-web = "0.12" prost = "0.13" prost-types = "0.13" @@ -89,6 +88,7 @@ evmap = { version = "10.0", optional = true } # System calls / 系统调用 libc = "0.2" +spear-ssf = { path = "sdk/rust/crates/spear-ssf" } # MD5 hashing / MD5哈希 md5 = "0.7" diff --git a/Makefile b/Makefile index 1471bcf6..18ceab9a 100644 --- a/Makefile +++ b/Makefile @@ -563,7 +563,7 @@ SAMPLES_CFLAGS ?= SAMPLES_JS_DIR ?= samples/wasm-js SAMPLES_JS_BUILD ?= $(SAMPLES_BUILD)/js JS_WASM_PREFIX ?= js- -JS_SAMPLES ?= chat_completion chat_completion_tool_sum router_filter_keyword user_stream_echo +JS_SAMPLES ?= chat_completion chat_completion_tool_sum router_filter_keyword user_stream_echo user_stream_chat_completion BUILD_JS_SAMPLES ?= 1 SAMPLES_RUST_DIR ?= $(SAMPLES_JS_DIR) RUST_SAMPLES ?= $(JS_SAMPLES) diff --git a/assets/console/main.js b/assets/console/main.js index 1c5b7448..f955a266 100644 --- a/assets/console/main.js +++ b/assets/console/main.js @@ -1,8 +1,8 @@ -(function(){const k=document.createElement("link").relList;if(k&&k.supports&&k.supports("modulepreload"))return;for(const y of document.querySelectorAll('link[rel="modulepreload"]'))R(y);new MutationObserver(y=>{for(const T of y)if(T.type==="childList")for(const Q of T.addedNodes)Q.tagName==="LINK"&&Q.rel==="modulepreload"&&R(Q)}).observe(document,{childList:!0,subtree:!0});function c(y){const T={};return y.integrity&&(T.integrity=y.integrity),y.referrerPolicy&&(T.referrerPolicy=y.referrerPolicy),y.crossOrigin==="use-credentials"?T.credentials="include":y.crossOrigin==="anonymous"?T.credentials="omit":T.credentials="same-origin",T}function R(y){if(y.ep)return;y.ep=!0;const T=c(y);fetch(y.href,T)}})();function $a(d){return d&&d.__esModule&&Object.prototype.hasOwnProperty.call(d,"default")?d.default:d}var Vo={exports:{}},Br={},Ho={exports:{}},$={};var Ta;function Ad(){if(Ta)return $;Ta=1;var d=Symbol.for("react.element"),k=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),R=Symbol.for("react.strict_mode"),y=Symbol.for("react.profiler"),T=Symbol.for("react.provider"),Q=Symbol.for("react.context"),he=Symbol.for("react.forward_ref"),U=Symbol.for("react.suspense"),fe=Symbol.for("react.memo"),we=Symbol.for("react.lazy"),ne=Symbol.iterator;function re(f){return f===null||typeof f!="object"?null:(f=ne&&f[ne]||f["@@iterator"],typeof f=="function"?f:null)}var pe={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},We=Object.assign,oe={};function le(f,g,V){this.props=f,this.context=g,this.refs=oe,this.updater=V||pe}le.prototype.isReactComponent={},le.prototype.setState=function(f,g){if(typeof f!="object"&&typeof f!="function"&&f!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,f,g,"setState")},le.prototype.forceUpdate=function(f){this.updater.enqueueForceUpdate(this,f,"forceUpdate")};function Ge(){}Ge.prototype=le.prototype;function ot(f,g,V){this.props=f,this.context=g,this.refs=oe,this.updater=V||pe}var ve=ot.prototype=new Ge;ve.constructor=ot,We(ve,le.prototype),ve.isPureReactComponent=!0;var Pe=Array.isArray,Ie=Object.prototype.hasOwnProperty,xe={current:null},Te={key:!0,ref:!0,__self:!0,__source:!0};function Ye(f,g,V){var H,K={},X=null,ee=null;if(g!=null)for(H in g.ref!==void 0&&(ee=g.ref),g.key!==void 0&&(X=""+g.key),g)Ie.call(g,H)&&!Te.hasOwnProperty(H)&&(K[H]=g[H]);var J=arguments.length-2;if(J===1)K.children=V;else if(1>>1,g=N[f];if(0>>1;fy(K,j))Xy(ee,K)?(N[f]=ee,N[X]=j,f=X):(N[f]=K,N[H]=j,f=H);else if(Xy(ee,j))N[f]=ee,N[X]=j,f=X;else break e}}return D}function y(N,D){var j=N.sortIndex-D.sortIndex;return j!==0?j:N.id-D.id}if(typeof performance=="object"&&typeof performance.now=="function"){var T=performance;d.unstable_now=function(){return T.now()}}else{var Q=Date,he=Q.now();d.unstable_now=function(){return Q.now()-he}}var U=[],fe=[],we=1,ne=null,re=3,pe=!1,We=!1,oe=!1,le=typeof setTimeout=="function"?setTimeout:null,Ge=typeof clearTimeout=="function"?clearTimeout:null,ot=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function ve(N){for(var D=c(fe);D!==null;){if(D.callback===null)R(fe);else if(D.startTime<=N)R(fe),D.sortIndex=D.expirationTime,k(U,D);else break;D=c(fe)}}function Pe(N){if(oe=!1,ve(N),!We)if(c(U)!==null)We=!0,Re(Ie);else{var D=c(fe);D!==null&&ue(Pe,D.startTime-N)}}function Ie(N,D){We=!1,oe&&(oe=!1,Ge(Ye),Ye=-1),pe=!0;var j=re;try{for(ve(D),ne=c(U);ne!==null&&(!(ne.expirationTime>D)||N&&!ht());){var f=ne.callback;if(typeof f=="function"){ne.callback=null,re=ne.priorityLevel;var g=f(ne.expirationTime<=D);D=d.unstable_now(),typeof g=="function"?ne.callback=g:ne===c(U)&&R(U),ve(D)}else R(U);ne=c(U)}if(ne!==null)var V=!0;else{var H=c(fe);H!==null&&ue(Pe,H.startTime-D),V=!1}return V}finally{ne=null,re=j,pe=!1}}var xe=!1,Te=null,Ye=-1,Et=5,mt=-1;function ht(){return!(d.unstable_now()-mtN||125f?(N.sortIndex=j,k(fe,N),c(U)===null&&N===c(fe)&&(oe?(Ge(Ye),Ye=-1):oe=!0,ue(Pe,j-f))):(N.sortIndex=g,k(U,N),We||pe||(We=!0,Re(Ie))),N},d.unstable_shouldYield=ht,d.unstable_wrapCallback=function(N){var D=re;return function(){var j=re;re=D;try{return N.apply(this,arguments)}finally{re=j}}}})(Qo)),Qo}var Oa;function $d(){return Oa||(Oa=1,$o.exports=Wd()),$o.exports}var Ma;function Qd(){if(Ma)return tt;Ma=1;var d=Ko(),k=$d();function c(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),U=Object.prototype.hasOwnProperty,fe=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,we={},ne={};function re(e){return U.call(ne,e)?!0:U.call(we,e)?!1:fe.test(e)?ne[e]=!0:(we[e]=!0,!1)}function pe(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function We(e,t,n,r){if(t===null||typeof t>"u"||pe(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function oe(e,t,n,r,l,i,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=o}var le={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){le[e]=new oe(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];le[t]=new oe(t,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){le[e]=new oe(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){le[e]=new oe(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){le[e]=new oe(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){le[e]=new oe(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){le[e]=new oe(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){le[e]=new oe(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){le[e]=new oe(e,5,!1,e.toLowerCase(),null,!1,!1)});var Ge=/[\-:]([a-z])/g;function ot(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Ge,ot);le[t]=new oe(t,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Ge,ot);le[t]=new oe(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Ge,ot);le[t]=new oe(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){le[e]=new oe(e,1,!1,e.toLowerCase(),null,!1,!1)}),le.xlinkHref=new oe("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){le[e]=new oe(e,1,!1,e.toLowerCase(),null,!0,!0)});function ve(e,t,n,r){var l=le.hasOwnProperty(t)?le[t]:null;(l!==null?l.type!==0:r||!(2{for(const R of y)if(R.type==="childList")for(const W of R.addedNodes)W.tagName==="LINK"&&W.rel==="modulepreload"&&P(W)}).observe(document,{childList:!0,subtree:!0});function c(y){const R={};return y.integrity&&(R.integrity=y.integrity),y.referrerPolicy&&(R.referrerPolicy=y.referrerPolicy),y.crossOrigin==="use-credentials"?R.credentials="include":y.crossOrigin==="anonymous"?R.credentials="omit":R.credentials="same-origin",R}function P(y){if(y.ep)return;y.ep=!0;const R=c(y);fetch(y.href,R)}})();function Ka(d){return d&&d.__esModule&&Object.prototype.hasOwnProperty.call(d,"default")?d.default:d}var Wo={exports:{}},Vr={},$o={exports:{}},Q={};var za;function Hd(){if(za)return Q;za=1;var d=Symbol.for("react.element"),g=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),P=Symbol.for("react.strict_mode"),y=Symbol.for("react.profiler"),R=Symbol.for("react.provider"),W=Symbol.for("react.context"),me=Symbol.for("react.forward_ref"),q=Symbol.for("react.suspense"),B=Symbol.for("react.memo"),he=Symbol.for("react.lazy"),re=Symbol.iterator;function le(f){return f===null||typeof f!="object"?null:(f=re&&f[re]||f["@@iterator"],typeof f=="function"?f:null)}var ze={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},xe=Object.assign,se={};function te(f,w,V){this.props=f,this.context=w,this.refs=se,this.updater=V||ze}te.prototype.isReactComponent={},te.prototype.setState=function(f,w){if(typeof f!="object"&&typeof f!="function"&&f!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,f,w,"setState")},te.prototype.forceUpdate=function(f){this.updater.enqueueForceUpdate(this,f,"forceUpdate")};function pt(){}pt.prototype=te.prototype;function He(f,w,V){this.props=f,this.context=w,this.refs=se,this.updater=V||ze}var tt=He.prototype=new pt;tt.constructor=He,xe(tt,te.prototype),tt.isPureReactComponent=!0;var oe=Array.isArray,nt=Object.prototype.hasOwnProperty,we={current:null},Ne={key:!0,ref:!0,__self:!0,__source:!0};function Me(f,w,V){var H,G={},K=null,ue=null;if(w!=null)for(H in w.ref!==void 0&&(ue=w.ref),w.key!==void 0&&(K=""+w.key),w)nt.call(w,H)&&!Ne.hasOwnProperty(H)&&(G[H]=w[H]);var Z=arguments.length-2;if(Z===1)G.children=V;else if(1>>1,w=N[f];if(0>>1;fy(G,_))Ky(ue,G)?(N[f]=ue,N[K]=_,f=K):(N[f]=G,N[H]=_,f=H);else if(Ky(ue,_))N[f]=ue,N[K]=_,f=K;else break e}}return D}function y(N,D){var _=N.sortIndex-D.sortIndex;return _!==0?_:N.id-D.id}if(typeof performance=="object"&&typeof performance.now=="function"){var R=performance;d.unstable_now=function(){return R.now()}}else{var W=Date,me=W.now();d.unstable_now=function(){return W.now()-me}}var q=[],B=[],he=1,re=null,le=3,ze=!1,xe=!1,se=!1,te=typeof setTimeout=="function"?setTimeout:null,pt=typeof clearTimeout=="function"?clearTimeout:null,He=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function tt(N){for(var D=c(B);D!==null;){if(D.callback===null)P(B);else if(D.startTime<=N)P(B),D.sortIndex=D.expirationTime,g(q,D);else break;D=c(B)}}function oe(N){if(se=!1,tt(N),!xe)if(c(q)!==null)xe=!0,Pe(nt);else{var D=c(B);D!==null&&ce(oe,D.startTime-N)}}function nt(N,D){xe=!1,se&&(se=!1,pt(Me),Me=-1),ze=!0;var _=le;try{for(tt(D),re=c(q);re!==null&&(!(re.expirationTime>D)||N&&!Tt());){var f=re.callback;if(typeof f=="function"){re.callback=null,le=re.priorityLevel;var w=f(re.expirationTime<=D);D=d.unstable_now(),typeof w=="function"?re.callback=w:re===c(q)&&P(q),tt(D)}else P(q);re=c(q)}if(re!==null)var V=!0;else{var H=c(B);H!==null&&ce(oe,H.startTime-D),V=!1}return V}finally{re=null,le=_,ze=!1}}var we=!1,Ne=null,Me=-1,xt=5,mt=-1;function Tt(){return!(d.unstable_now()-mtN||125f?(N.sortIndex=_,g(B,N),c(q)===null&&N===c(B)&&(se?(pt(Me),Me=-1):se=!0,ce(oe,_-f))):(N.sortIndex=w,g(q,N),xe||ze||(xe=!0,Pe(nt))),N},d.unstable_shouldYield=Tt,d.unstable_wrapCallback=function(N){var D=le;return function(){var _=le;le=D;try{return N.apply(this,arguments)}finally{le=_}}}})(Go)),Go}var Ua;function Gd(){return Ua||(Ua=1,Ko.exports=Kd()),Ko.exports}var Aa;function Yd(){if(Aa)return et;Aa=1;var d=Zo(),g=Gd();function c(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),q=Object.prototype.hasOwnProperty,B=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,he={},re={};function le(e){return q.call(re,e)?!0:q.call(he,e)?!1:B.test(e)?re[e]=!0:(he[e]=!0,!1)}function ze(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function xe(e,t,n,r){if(t===null||typeof t>"u"||ze(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function se(e,t,n,r,l,i,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=o}var te={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){te[e]=new se(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];te[t]=new se(t,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){te[e]=new se(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){te[e]=new se(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){te[e]=new se(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){te[e]=new se(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){te[e]=new se(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){te[e]=new se(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){te[e]=new se(e,5,!1,e.toLowerCase(),null,!1,!1)});var pt=/[\-:]([a-z])/g;function He(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(pt,He);te[t]=new se(t,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(pt,He);te[t]=new se(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(pt,He);te[t]=new se(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){te[e]=new se(e,1,!1,e.toLowerCase(),null,!1,!1)}),te.xlinkHref=new se("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){te[e]=new se(e,1,!1,e.toLowerCase(),null,!0,!0)});function tt(e,t,n,r){var l=te.hasOwnProperty(t)?te[t]:null;(l!==null?l.type!==0:r||!(2u||l[o]!==i[u]){var s=` -`+l[o].replace(" at new "," at ");return e.displayName&&s.includes("")&&(s=s.replace("",e.displayName)),s}while(1<=o&&0<=u);break}}}finally{V=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?g(e):""}function K(e){switch(e.tag){case 5:return g(e.type);case 16:return g("Lazy");case 13:return g("Suspense");case 19:return g("SuspenseList");case 0:case 2:case 15:return e=H(e.type,!1),e;case 11:return e=H(e.type.render,!1),e;case 1:return e=H(e.type,!0),e;default:return""}}function X(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Te:return"Fragment";case xe:return"Portal";case Et:return"Profiler";case Ye:return"StrictMode";case Le:return"Suspense";case $e:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case ht:return(e.displayName||"Context")+".Consumer";case mt:return(e._context.displayName||"Context")+".Provider";case ut:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Xe:return t=e.displayName||null,t!==null?t:X(e.type)||"Memo";case Re:t=e._payload,e=e._init;try{return X(e(t))}catch{}}return null}function ee(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return X(t);case 8:return t===Ye?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function J(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function se(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Ue(e){var t=se(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(o){r=""+o,i.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function an(e){e._valueTracker||(e._valueTracker=Ue(e))}function $r(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=se(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function cn(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function er(e,t){var n=t.checked;return j({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function In(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=J(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Qr(e,t){t=t.checked,t!=null&&ve(e,"checked",t,!1)}function dn(e,t){Qr(e,t);var n=J(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Tn(e,t.type,n):t.hasOwnProperty("defaultValue")&&Tn(e,t.type,J(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Kr(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Tn(e,t,n){(t!=="number"||cn(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var fn=Array.isArray;function It(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=mn.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function At(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Bt={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},C=["Webkit","ms","Moz","O"];Object.keys(Bt).forEach(function(e){C.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Bt[t]=Bt[e]})});function O(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Bt.hasOwnProperty(e)&&Bt[e]?(""+t).trim():t+"px"}function G(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=O(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var te=j({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function b(e,t){if(t){if(te[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(c(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(c(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(c(61))}if(t.style!=null&&typeof t.style!="object")throw Error(c(62))}}function W(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Y=null;function ke(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var hn=null,Vt=null,Ht=null;function Go(e){if(e=Nr(e)){if(typeof hn!="function")throw Error(c(280));var t=e.stateNode;t&&(t=yl(t),hn(e.stateNode,e.type,t))}}function Yo(e){Vt?Ht?Ht.push(e):Ht=[e]:Vt=e}function Xo(){if(Vt){var e=Vt,t=Ht;if(Ht=Vt=null,Go(e),t)for(e=0;e>>=0,e===0?32:31-(tc(e)/nc|0)|0}var br=64,el=4194304;function ur(e){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: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 e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function tl(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,i=e.pingedLanes,o=n&268435455;if(o!==0){var u=o&~l;u!==0?r=ur(u):(i&=o,i!==0&&(r=ur(i)))}else o=n&~l,o!==0?r=ur(o):i!==0&&(r=ur(i));if(r===0)return 0;if(t!==0&&t!==r&&(t&l)===0&&(l=r&-r,i=t&-t,l>=i||l===16&&(i&4194240)!==0))return t;if((r&4)!==0&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function sr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-vt(t),e[t]=n}function oc(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=vr),Eu=" ",Cu=!1;function Nu(e,t){switch(e){case"keyup":return Dc.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function _u(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var zn=!1;function Mc(e,t){switch(e){case"compositionend":return _u(t);case"keypress":return t.which!==32?null:(Cu=!0,Eu);case"textInput":return e=t.data,e===Eu&&Cu?null:e;default:return null}}function Fc(e,t){if(zn)return e==="compositionend"||!ki&&Nu(e,t)?(e=yu(),ol=mi=Gt=null,zn=!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}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=zu(n)}}function Ou(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ou(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Mu(){for(var e=window,t=cn();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=cn(e.document)}return t}function Ei(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")}function Kc(e){var t=Mu(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Ou(n.ownerDocument.documentElement,n)){if(r!==null&&Ei(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,i=Math.min(r.start,l);r=r.end===void 0?i:Math.min(r.end,l),!e.extend&&i>r&&(l=r,r=i,i=l),l=Du(n,i);var o=Du(n,r);l&&o&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Dn=null,Ci=null,kr=null,Ni=!1;function Fu(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Ni||Dn==null||Dn!==cn(r)||(r=Dn,"selectionStart"in r&&Ei(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}),kr&&wr(kr,r)||(kr=r,r=ml(Ci,"onSelect"),0An||(e.current=Fi[An],Fi[An]=null,An--)}function ae(e,t){An++,Fi[An]=e.current,e.current=t}var Jt={},Ae=Zt(Jt),Ze=Zt(!1),gn=Jt;function Bn(e,t){var n=e.type.contextTypes;if(!n)return Jt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},i;for(i in n)l[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function Je(e){return e=e.childContextTypes,e!=null}function gl(){de(Ze),de(Ae)}function qu(e,t,n){if(Ae.current!==Jt)throw Error(c(168));ae(Ae,t),ae(Ze,n)}function bu(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(c(108,ee(e)||"Unknown",l));return j({},n,r)}function wl(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Jt,gn=Ae.current,ae(Ae,e),ae(Ze,Ze.current),!0}function es(e,t,n){var r=e.stateNode;if(!r)throw Error(c(169));n?(e=bu(e,t,gn),r.__reactInternalMemoizedMergedChildContext=e,de(Ze),de(Ae),ae(Ae,e)):de(Ze),ae(Ze,n)}var Lt=null,kl=!1,Ui=!1;function ts(e){Lt===null?Lt=[e]:Lt.push(e)}function ld(e){kl=!0,ts(e)}function qt(){if(!Ui&&Lt!==null){Ui=!0;var e=0,t=ie;try{var n=Lt;for(ie=1;e>=o,l-=o,Rt=1<<32-vt(t)+l|n<A?(Oe=F,F=null):Oe=F.sibling;var q=w(p,F,m[A],E);if(q===null){F===null&&(F=Oe);break}e&&F&&q.alternate===null&&t(p,F),a=i(q,a,A),M===null?z=q:M.sibling=q,M=q,F=Oe}if(A===m.length)return n(p,F),me&&kn(p,A),z;if(F===null){for(;AA?(Oe=F,F=null):Oe=F.sibling;var sn=w(p,F,q.value,E);if(sn===null){F===null&&(F=Oe);break}e&&F&&sn.alternate===null&&t(p,F),a=i(sn,a,A),M===null?z=sn:M.sibling=sn,M=sn,F=Oe}if(q.done)return n(p,F),me&&kn(p,A),z;if(F===null){for(;!q.done;A++,q=m.next())q=x(p,q.value,E),q!==null&&(a=i(q,a,A),M===null?z=q:M.sibling=q,M=q);return me&&kn(p,A),z}for(F=r(p,F);!q.done;A++,q=m.next())q=_(F,p,A,q.value,E),q!==null&&(e&&q.alternate!==null&&F.delete(q.key===null?A:q.key),a=i(q,a,A),M===null?z=q:M.sibling=q,M=q);return e&&F.forEach(function(Ud){return t(p,Ud)}),me&&kn(p,A),z}function Ce(p,a,m,E){if(typeof m=="object"&&m!==null&&m.type===Te&&m.key===null&&(m=m.props.children),typeof m=="object"&&m!==null){switch(m.$$typeof){case Ie:e:{for(var z=m.key,M=a;M!==null;){if(M.key===z){if(z=m.type,z===Te){if(M.tag===7){n(p,M.sibling),a=l(M,m.props.children),a.return=p,p=a;break e}}else if(M.elementType===z||typeof z=="object"&&z!==null&&z.$$typeof===Re&&us(z)===M.type){n(p,M.sibling),a=l(M,m.props),a.ref=_r(p,M,m),a.return=p,p=a;break e}n(p,M);break}else t(p,M);M=M.sibling}m.type===Te?(a=Pn(m.props.children,p.mode,E,m.key),a.return=p,p=a):(E=Yl(m.type,m.key,m.props,null,p.mode,E),E.ref=_r(p,a,m),E.return=p,p=E)}return o(p);case xe:e:{for(M=m.key;a!==null;){if(a.key===M)if(a.tag===4&&a.stateNode.containerInfo===m.containerInfo&&a.stateNode.implementation===m.implementation){n(p,a.sibling),a=l(a,m.children||[]),a.return=p,p=a;break e}else{n(p,a);break}else t(p,a);a=a.sibling}a=Mo(m,p.mode,E),a.return=p,p=a}return o(p);case Re:return M=m._init,Ce(p,a,M(m._payload),E)}if(fn(m))return I(p,a,m,E);if(D(m))return L(p,a,m,E);Cl(p,m)}return typeof m=="string"&&m!==""||typeof m=="number"?(m=""+m,a!==null&&a.tag===6?(n(p,a.sibling),a=l(a,m),a.return=p,p=a):(n(p,a),a=Oo(m,p.mode,E),a.return=p,p=a),o(p)):n(p,a)}return Ce}var $n=ss(!0),as=ss(!1),Nl=Zt(null),_l=null,Qn=null,$i=null;function Qi(){$i=Qn=_l=null}function Ki(e){var t=Nl.current;de(Nl),e._currentValue=t}function Gi(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Kn(e,t){_l=e,$i=Qn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&t)!==0&&(qe=!0),e.firstContext=null)}function ct(e){var t=e._currentValue;if($i!==e)if(e={context:e,memoizedValue:t,next:null},Qn===null){if(_l===null)throw Error(c(308));Qn=e,_l.dependencies={lanes:0,firstContext:e}}else Qn=Qn.next=e;return t}var Sn=null;function Yi(e){Sn===null?Sn=[e]:Sn.push(e)}function cs(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Yi(t)):(n.next=l.next,l.next=n),t.interleaved=n,Dt(e,r)}function Dt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var bt=!1;function Xi(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function ds(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Ot(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function en(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,(Z&2)!==0){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Dt(e,n)}return l=r.interleaved,l===null?(t.next=t,Yi(r)):(t.next=l.next,l.next=t),r.interleaved=t,Dt(e,n)}function jl(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ai(e,n)}}function fs(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?l=i=o:i=i.next=o,n=n.next}while(n!==null);i===null?l=i=t:i=i.next=t}else l=i=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function Pl(e,t,n,r){var l=e.updateQueue;bt=!1;var i=l.firstBaseUpdate,o=l.lastBaseUpdate,u=l.shared.pending;if(u!==null){l.shared.pending=null;var s=u,v=s.next;s.next=null,o===null?i=v:o.next=v,o=s;var S=e.alternate;S!==null&&(S=S.updateQueue,u=S.lastBaseUpdate,u!==o&&(u===null?S.firstBaseUpdate=v:u.next=v,S.lastBaseUpdate=s))}if(i!==null){var x=l.baseState;o=0,S=v=s=null,u=i;do{var w=u.lane,_=u.eventTime;if((r&w)===w){S!==null&&(S=S.next={eventTime:_,lane:0,tag:u.tag,payload:u.payload,callback:u.callback,next:null});e:{var I=e,L=u;switch(w=t,_=n,L.tag){case 1:if(I=L.payload,typeof I=="function"){x=I.call(_,x,w);break e}x=I;break e;case 3:I.flags=I.flags&-65537|128;case 0:if(I=L.payload,w=typeof I=="function"?I.call(_,x,w):I,w==null)break e;x=j({},x,w);break e;case 2:bt=!0}}u.callback!==null&&u.lane!==0&&(e.flags|=64,w=l.effects,w===null?l.effects=[u]:w.push(u))}else _={eventTime:_,lane:w,tag:u.tag,payload:u.payload,callback:u.callback,next:null},S===null?(v=S=_,s=x):S=S.next=_,o|=w;if(u=u.next,u===null){if(u=l.shared.pending,u===null)break;w=u,u=w.next,w.next=null,l.lastBaseUpdate=w,l.shared.pending=null}}while(!0);if(S===null&&(s=x),l.baseState=s,l.firstBaseUpdate=v,l.lastBaseUpdate=S,t=l.shared.interleaved,t!==null){l=t;do o|=l.lane,l=l.next;while(l!==t)}else i===null&&(l.shared.lanes=0);Cn|=o,e.lanes=o,e.memoizedState=x}}function ps(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=eo.transition;eo.transition={};try{e(!1),t()}finally{ie=n,eo.transition=r}}function Rs(){return dt().memoizedState}function sd(e,t,n){var r=ln(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},zs(e))Ds(t,n);else if(n=cs(e,t,n,r),n!==null){var l=Ke();xt(n,e,r,l),Os(n,t,r)}}function ad(e,t,n){var r=ln(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(zs(e))Ds(t,l);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var o=t.lastRenderedState,u=i(o,n);if(l.hasEagerState=!0,l.eagerState=u,yt(u,o)){var s=t.interleaved;s===null?(l.next=l,Yi(t)):(l.next=s.next,s.next=l),t.interleaved=l;return}}catch{}n=cs(e,t,l,r),n!==null&&(l=Ke(),xt(n,e,r,l),Os(n,t,r))}}function zs(e){var t=e.alternate;return e===ge||t!==null&&t===ge}function Ds(e,t){Tr=Ll=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Os(e,t,n){if((n&4194240)!==0){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ai(e,n)}}var Dl={readContext:ct,useCallback:Be,useContext:Be,useEffect:Be,useImperativeHandle:Be,useInsertionEffect:Be,useLayoutEffect:Be,useMemo:Be,useReducer:Be,useRef:Be,useState:Be,useDebugValue:Be,useDeferredValue:Be,useTransition:Be,useMutableSource:Be,useSyncExternalStore:Be,useId:Be,unstable_isNewReconciler:!1},cd={readContext:ct,useCallback:function(e,t){return jt().memoizedState=[e,t===void 0?null:t],e},useContext:ct,useEffect:Cs,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Rl(4194308,4,js.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Rl(4194308,4,e,t)},useInsertionEffect:function(e,t){return Rl(4,2,e,t)},useMemo:function(e,t){var n=jt();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=jt();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=sd.bind(null,ge,e),[r.memoizedState,e]},useRef:function(e){var t=jt();return e={current:e},t.memoizedState=e},useState:xs,useDebugValue:uo,useDeferredValue:function(e){return jt().memoizedState=e},useTransition:function(){var e=xs(!1),t=e[0];return e=ud.bind(null,e[1]),jt().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=ge,l=jt();if(me){if(n===void 0)throw Error(c(407));n=n()}else{if(n=t(),De===null)throw Error(c(349));(En&30)!==0||ys(r,t,n)}l.memoizedState=n;var i={value:n,getSnapshot:t};return l.queue=i,Cs(ws.bind(null,r,i,e),[e]),r.flags|=2048,zr(9,gs.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=jt(),t=De.identifierPrefix;if(me){var n=zt,r=Rt;n=(r&~(1<<32-vt(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Lr++,0")&&(s=s.replace("",e.displayName)),s}while(1<=o&&0<=u);break}}}finally{V=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?w(e):""}function G(e){switch(e.tag){case 5:return w(e.type);case 16:return w("Lazy");case 13:return w("Suspense");case 19:return w("SuspenseList");case 0:case 2:case 15:return e=H(e.type,!1),e;case 11:return e=H(e.type.render,!1),e;case 1:return e=H(e.type,!0),e;default:return""}}function K(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Ne:return"Fragment";case we:return"Portal";case xt:return"Profiler";case Me:return"StrictMode";case We:return"Suspense";case Fe:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Tt:return(e.displayName||"Context")+".Consumer";case mt:return(e._context.displayName||"Context")+".Provider";case Ge:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Ye:return t=e.displayName||null,t!==null?t:K(e.type)||"Memo";case Pe:t=e._payload,e=e._init;try{return K(e(t))}catch{}}return null}function ue(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return K(t);case 8:return t===Me?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Z(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ae(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function $e(e){var t=ae(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(o){r=""+o,i.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function cn(e){e._valueTracker||(e._valueTracker=$e(e))}function er(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=ae(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Nn(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function _n(e,t){var n=t.checked;return _({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Kr(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Z(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function jn(e,t){t=t.checked,t!=null&&tt(e,"checked",t,!1)}function tr(e,t){jn(e,t);var n=Z(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?nr(e,t.type,n):t.hasOwnProperty("defaultValue")&&nr(e,t.type,Z(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function In(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function nr(e,t,n){(t!=="number"||Nn(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var At=Array.isArray;function Bt(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Ht.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function dn(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Et={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Xr=["Webkit","ms","Moz","O"];Object.keys(Et).forEach(function(e){Xr.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Et[t]=Et[e]})});function E(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Et.hasOwnProperty(e)&&Et[e]?(""+t).trim():t+"px"}function O(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=E(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var Y=_({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function b(e,t){if(t){if(Y[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(c(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(c(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(c(61))}if(t.style!=null&&typeof t.style!="object")throw Error(c(62))}}function ne(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var $=null;function X(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var ke=null,Ct=null,Wt=null;function Zr(e){if(e=_r(e)){if(typeof ke!="function")throw Error(c(280));var t=e.stateNode;t&&(t=wl(t),ke(e.stateNode,e.type,t))}}function Jo(e){Ct?Wt?Wt.push(e):Wt=[e]:Ct=e}function qo(){if(Ct){var e=Ct,t=Wt;if(Wt=Ct=null,Zr(e),t)for(e=0;e>>=0,e===0?32:31-(lc(e)/ic|0)|0}var tl=64,nl=4194304;function sr(e){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: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 e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function rl(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,i=e.pingedLanes,o=n&268435455;if(o!==0){var u=o&~l;u!==0?r=sr(u):(i&=o,i!==0&&(r=sr(i)))}else o=n&~l,o!==0?r=sr(o):i!==0&&(r=sr(i));if(r===0)return 0;if(t!==0&&t!==r&&(t&l)===0&&(l=r&-r,i=t&-t,l>=i||l===16&&(i&4194240)!==0))return t;if((r&4)!==0&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function ar(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-ht(t),e[t]=n}function ac(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=yr),_u=" ",ju=!1;function Iu(e,t){switch(e){case"keyup":return Fc.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Pu(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var zn=!1;function Ac(e,t){switch(e){case"compositionend":return Pu(t);case"keypress":return t.which!==32?null:(ju=!0,_u);case"textInput":return e=t.data,e===_u&&ju?null:e;default:return null}}function Bc(e,t){if(zn)return e==="compositionend"||!xi&&Iu(e,t)?(e=ku(),sl=vi=Yt=null,zn=!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}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=Mu(n)}}function Uu(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Uu(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Au(){for(var e=window,t=Nn();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Nn(e.document)}return t}function Ni(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")}function Xc(e){var t=Au(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Uu(n.ownerDocument.documentElement,n)){if(r!==null&&Ni(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,i=Math.min(r.start,l);r=r.end===void 0?i:Math.min(r.end,l),!e.extend&&i>r&&(l=r,r=i,i=l),l=Fu(n,i);var o=Fu(n,r);l&&o&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,On=null,_i=null,Sr=null,ji=!1;function Bu(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ji||On==null||On!==Nn(r)||(r=On,"selectionStart"in r&&Ni(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}),Sr&&kr(Sr,r)||(Sr=r,r=vl(_i,"onSelect"),0An||(e.current=Ai[An],Ai[An]=null,An--)}function de(e,t){An++,Ai[An]=e.current,e.current=t}var qt={},Ue=Jt(qt),Xe=Jt(!1),mn=qt;function Bn(e,t){var n=e.type.contextTypes;if(!n)return qt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},i;for(i in n)l[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function Ze(e){return e=e.childContextTypes,e!=null}function kl(){pe(Xe),pe(Ue)}function ts(e,t,n){if(Ue.current!==qt)throw Error(c(168));de(Ue,t),de(Xe,n)}function ns(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(c(108,ue(e)||"Unknown",l));return _({},n,r)}function Sl(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||qt,mn=Ue.current,de(Ue,e),de(Xe,Xe.current),!0}function rs(e,t,n){var r=e.stateNode;if(!r)throw Error(c(169));n?(e=ns(e,t,mn),r.__reactInternalMemoizedMergedChildContext=e,pe(Xe),pe(Ue),de(Ue,e)):pe(Xe),de(Xe,n)}var Lt=null,xl=!1,Bi=!1;function ls(e){Lt===null?Lt=[e]:Lt.push(e)}function ud(e){xl=!0,ls(e)}function bt(){if(!Bi&&Lt!==null){Bi=!0;var e=0,t=ie;try{var n=Lt;for(ie=1;e>=o,l-=o,zt=1<<32-ht(t)+l|n<A?(Le=F,F=null):Le=F.sibling;var ee=k(p,F,m[A],C);if(ee===null){F===null&&(F=Le);break}e&&F&&ee.alternate===null&&t(p,F),a=i(ee,a,A),M===null?z=ee:M.sibling=ee,M=ee,F=Le}if(A===m.length)return n(p,F),ve&&vn(p,A),z;if(F===null){for(;AA?(Le=F,F=null):Le=F.sibling;var an=k(p,F,ee.value,C);if(an===null){F===null&&(F=Le);break}e&&F&&an.alternate===null&&t(p,F),a=i(an,a,A),M===null?z=an:M.sibling=an,M=an,F=Le}if(ee.done)return n(p,F),ve&&vn(p,A),z;if(F===null){for(;!ee.done;A++,ee=m.next())ee=x(p,ee.value,C),ee!==null&&(a=i(ee,a,A),M===null?z=ee:M.sibling=ee,M=ee);return ve&&vn(p,A),z}for(F=r(p,F);!ee.done;A++,ee=m.next())ee=j(F,p,A,ee.value,C),ee!==null&&(e&&ee.alternate!==null&&F.delete(ee.key===null?A:ee.key),a=i(ee,a,A),M===null?z=ee:M.sibling=ee,M=ee);return e&&F.forEach(function(Vd){return t(p,Vd)}),ve&&vn(p,A),z}function Ce(p,a,m,C){if(typeof m=="object"&&m!==null&&m.type===Ne&&m.key===null&&(m=m.props.children),typeof m=="object"&&m!==null){switch(m.$$typeof){case nt:e:{for(var z=m.key,M=a;M!==null;){if(M.key===z){if(z=m.type,z===Ne){if(M.tag===7){n(p,M.sibling),a=l(M,m.props.children),a.return=p,p=a;break e}}else if(M.elementType===z||typeof z=="object"&&z!==null&&z.$$typeof===Pe&&cs(z)===M.type){n(p,M.sibling),a=l(M,m.props),a.ref=jr(p,M,m),a.return=p,p=a;break e}n(p,M);break}else t(p,M);M=M.sibling}m.type===Ne?(a=Cn(m.props.children,p.mode,C,m.key),a.return=p,p=a):(C=Zl(m.type,m.key,m.props,null,p.mode,C),C.ref=jr(p,a,m),C.return=p,p=C)}return o(p);case we:e:{for(M=m.key;a!==null;){if(a.key===M)if(a.tag===4&&a.stateNode.containerInfo===m.containerInfo&&a.stateNode.implementation===m.implementation){n(p,a.sibling),a=l(a,m.children||[]),a.return=p,p=a;break e}else{n(p,a);break}else t(p,a);a=a.sibling}a=Uo(m,p.mode,C),a.return=p,p=a}return o(p);case Pe:return M=m._init,Ce(p,a,M(m._payload),C)}if(At(m))return T(p,a,m,C);if(D(m))return L(p,a,m,C);_l(p,m)}return typeof m=="string"&&m!==""||typeof m=="number"?(m=""+m,a!==null&&a.tag===6?(n(p,a.sibling),a=l(a,m),a.return=p,p=a):(n(p,a),a=Fo(m,p.mode,C),a.return=p,p=a),o(p)):n(p,a)}return Ce}var $n=ds(!0),fs=ds(!1),jl=Jt(null),Il=null,Qn=null,Ki=null;function Gi(){Ki=Qn=Il=null}function Yi(e){var t=jl.current;pe(jl),e._currentValue=t}function Xi(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Kn(e,t){Il=e,Ki=Qn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&t)!==0&&(Je=!0),e.firstContext=null)}function at(e){var t=e._currentValue;if(Ki!==e)if(e={context:e,memoizedValue:t,next:null},Qn===null){if(Il===null)throw Error(c(308));Qn=e,Il.dependencies={lanes:0,firstContext:e}}else Qn=Qn.next=e;return t}var yn=null;function Zi(e){yn===null?yn=[e]:yn.push(e)}function ps(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Zi(t)):(n.next=l.next,l.next=n),t.interleaved=n,Dt(e,r)}function Dt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var en=!1;function Ji(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function ms(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Mt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function tn(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,(J&2)!==0){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Dt(e,n)}return l=r.interleaved,l===null?(t.next=t,Zi(r)):(t.next=l.next,l.next=t),r.interleaved=t,Dt(e,n)}function Pl(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,di(e,n)}}function hs(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?l=i=o:i=i.next=o,n=n.next}while(n!==null);i===null?l=i=t:i=i.next=t}else l=i=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function Tl(e,t,n,r){var l=e.updateQueue;en=!1;var i=l.firstBaseUpdate,o=l.lastBaseUpdate,u=l.shared.pending;if(u!==null){l.shared.pending=null;var s=u,v=s.next;s.next=null,o===null?i=v:o.next=v,o=s;var S=e.alternate;S!==null&&(S=S.updateQueue,u=S.lastBaseUpdate,u!==o&&(u===null?S.firstBaseUpdate=v:u.next=v,S.lastBaseUpdate=s))}if(i!==null){var x=l.baseState;o=0,S=v=s=null,u=i;do{var k=u.lane,j=u.eventTime;if((r&k)===k){S!==null&&(S=S.next={eventTime:j,lane:0,tag:u.tag,payload:u.payload,callback:u.callback,next:null});e:{var T=e,L=u;switch(k=t,j=n,L.tag){case 1:if(T=L.payload,typeof T=="function"){x=T.call(j,x,k);break e}x=T;break e;case 3:T.flags=T.flags&-65537|128;case 0:if(T=L.payload,k=typeof T=="function"?T.call(j,x,k):T,k==null)break e;x=_({},x,k);break e;case 2:en=!0}}u.callback!==null&&u.lane!==0&&(e.flags|=64,k=l.effects,k===null?l.effects=[u]:k.push(u))}else j={eventTime:j,lane:k,tag:u.tag,payload:u.payload,callback:u.callback,next:null},S===null?(v=S=j,s=x):S=S.next=j,o|=k;if(u=u.next,u===null){if(u=l.shared.pending,u===null)break;k=u,u=k.next,k.next=null,l.lastBaseUpdate=k,l.shared.pending=null}}while(!0);if(S===null&&(s=x),l.baseState=s,l.firstBaseUpdate=v,l.lastBaseUpdate=S,t=l.shared.interleaved,t!==null){l=t;do o|=l.lane,l=l.next;while(l!==t)}else i===null&&(l.shared.lanes=0);kn|=o,e.lanes=o,e.memoizedState=x}}function vs(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=no.transition;no.transition={};try{e(!1),t()}finally{ie=n,no.transition=r}}function Ds(){return ct().memoizedState}function dd(e,t,n){var r=on(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},Ms(e))Fs(t,n);else if(n=ps(e,t,n,r),n!==null){var l=Ke();St(n,e,r,l),Us(n,t,r)}}function fd(e,t,n){var r=on(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(Ms(e))Fs(t,l);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var o=t.lastRenderedState,u=i(o,n);if(l.hasEagerState=!0,l.eagerState=u,vt(u,o)){var s=t.interleaved;s===null?(l.next=l,Zi(t)):(l.next=s.next,s.next=l),t.interleaved=l;return}}catch{}n=ps(e,t,l,r),n!==null&&(l=Ke(),St(n,e,r,l),Us(n,t,r))}}function Ms(e){var t=e.alternate;return e===ge||t!==null&&t===ge}function Fs(e,t){Rr=zl=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Us(e,t,n){if((n&4194240)!==0){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,di(e,n)}}var Ml={readContext:at,useCallback:Ae,useContext:Ae,useEffect:Ae,useImperativeHandle:Ae,useInsertionEffect:Ae,useLayoutEffect:Ae,useMemo:Ae,useReducer:Ae,useRef:Ae,useState:Ae,useDebugValue:Ae,useDeferredValue:Ae,useTransition:Ae,useMutableSource:Ae,useSyncExternalStore:Ae,useId:Ae,unstable_isNewReconciler:!1},pd={readContext:at,useCallback:function(e,t){return It().memoizedState=[e,t===void 0?null:t],e},useContext:at,useEffect:js,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Ol(4194308,4,Ts.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Ol(4194308,4,e,t)},useInsertionEffect:function(e,t){return Ol(4,2,e,t)},useMemo:function(e,t){var n=It();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=It();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=dd.bind(null,ge,e),[r.memoizedState,e]},useRef:function(e){var t=It();return e={current:e},t.memoizedState=e},useState:Ns,useDebugValue:ao,useDeferredValue:function(e){return It().memoizedState=e},useTransition:function(){var e=Ns(!1),t=e[0];return e=cd.bind(null,e[1]),It().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=ge,l=It();if(ve){if(n===void 0)throw Error(c(407));n=n()}else{if(n=t(),Re===null)throw Error(c(349));(wn&30)!==0||ks(r,t,n)}l.memoizedState=n;var i={value:n,getSnapshot:t};return l.queue=i,js(xs.bind(null,r,i,e),[e]),r.flags|=2048,Or(9,Ss.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=It(),t=Re.identifierPrefix;if(ve){var n=Ot,r=zt;n=(r&~(1<<32-ht(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Lr++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[Nt]=t,e[Cr]=r,ta(e,t,!1,!1),t.stateNode=e;e:{switch(o=W(n,r),n){case"dialog":ce("cancel",e),ce("close",e),l=r;break;case"iframe":case"object":case"embed":ce("load",e),l=r;break;case"video":case"audio":for(l=0;lJn&&(t.flags|=128,r=!0,Dr(i,!1),t.lanes=4194304)}else{if(!r)if(e=Il(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Dr(i,!0),i.tail===null&&i.tailMode==="hidden"&&!o.alternate&&!me)return Ve(t),null}else 2*Ee()-i.renderingStartTime>Jn&&n!==1073741824&&(t.flags|=128,r=!0,Dr(i,!1),t.lanes=4194304);i.isBackwards?(o.sibling=t.child,t.child=o):(n=i.last,n!==null?n.sibling=o:t.child=o,i.last=o)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=Ee(),t.sibling=null,n=ye.current,ae(ye,r?n&1|2:n&1),t):(Ve(t),null);case 22:case 23:return Ro(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&(t.mode&1)!==0?(it&1073741824)!==0&&(Ve(t),t.subtreeFlags&6&&(t.flags|=8192)):Ve(t),null;case 24:return null;case 25:return null}throw Error(c(156,t.tag))}function gd(e,t){switch(Bi(t),t.tag){case 1:return Je(t.type)&&gl(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Gn(),de(Ze),de(Ae),bi(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 5:return Ji(t),null;case 13:if(de(ye),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(c(340));Wn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return de(ye),null;case 4:return Gn(),null;case 10:return Ki(t.type._context),null;case 22:case 23:return Ro(),null;case 24:return null;default:return null}}var Ul=!1,He=!1,wd=typeof WeakSet=="function"?WeakSet:Set,P=null;function Xn(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Se(e,t,r)}else n.current=null}function ko(e,t,n){try{n()}catch(r){Se(e,t,r)}}var la=!1;function kd(e,t){if(Li=ll,e=Mu(),Ei(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var o=0,u=-1,s=-1,v=0,S=0,x=e,w=null;t:for(;;){for(var _;x!==n||l!==0&&x.nodeType!==3||(u=o+l),x!==i||r!==0&&x.nodeType!==3||(s=o+r),x.nodeType===3&&(o+=x.nodeValue.length),(_=x.firstChild)!==null;)w=x,x=_;for(;;){if(x===e)break t;if(w===n&&++v===l&&(u=o),w===i&&++S===r&&(s=o),(_=x.nextSibling)!==null)break;x=w,w=x.parentNode}x=_}n=u===-1||s===-1?null:{start:u,end:s}}else n=null}n=n||{start:0,end:0}}else n=null;for(Ri={focusedElem:e,selectionRange:n},ll=!1,P=t;P!==null;)if(t=P,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,P=e;else for(;P!==null;){t=P;try{var I=t.alternate;if((t.flags&1024)!==0)switch(t.tag){case 0:case 11:case 15:break;case 1:if(I!==null){var L=I.memoizedProps,Ce=I.memoizedState,p=t.stateNode,a=p.getSnapshotBeforeUpdate(t.elementType===t.type?L:wt(t.type,L),Ce);p.__reactInternalSnapshotBeforeUpdate=a}break;case 3:var m=t.stateNode.containerInfo;m.nodeType===1?m.textContent="":m.nodeType===9&&m.documentElement&&m.removeChild(m.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(c(163))}}catch(E){Se(t,t.return,E)}if(e=t.sibling,e!==null){e.return=t.return,P=e;break}P=t.return}return I=la,la=!1,I}function Or(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var i=l.destroy;l.destroy=void 0,i!==void 0&&ko(t,n,i)}l=l.next}while(l!==r)}}function Al(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function So(e){var t=e.ref;if(t!==null){var n=e.stateNode;e.tag,e=n,typeof t=="function"?t(e):t.current=e}}function ia(e){var t=e.alternate;t!==null&&(e.alternate=null,ia(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Nt],delete t[Cr],delete t[Mi],delete t[nd],delete t[rd])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function oa(e){return e.tag===5||e.tag===3||e.tag===4}function ua(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||oa(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.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function xo(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=vl));else if(r!==4&&(e=e.child,e!==null))for(xo(e,t,n),e=e.sibling;e!==null;)xo(e,t,n),e=e.sibling}function Eo(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&&(e=e.child,e!==null))for(Eo(e,t,n),e=e.sibling;e!==null;)Eo(e,t,n),e=e.sibling}var Me=null,kt=!1;function tn(e,t,n){for(n=n.child;n!==null;)sa(e,t,n),n=n.sibling}function sa(e,t,n){if(Ct&&typeof Ct.onCommitFiberUnmount=="function")try{Ct.onCommitFiberUnmount(qr,n)}catch{}switch(n.tag){case 5:He||Xn(n,t);case 6:var r=Me,l=kt;Me=null,tn(e,t,n),Me=r,kt=l,Me!==null&&(kt?(e=Me,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Me.removeChild(n.stateNode));break;case 18:Me!==null&&(kt?(e=Me,n=n.stateNode,e.nodeType===8?Oi(e.parentNode,n):e.nodeType===1&&Oi(e,n),pr(e)):Oi(Me,n.stateNode));break;case 4:r=Me,l=kt,Me=n.stateNode.containerInfo,kt=!0,tn(e,t,n),Me=r,kt=l;break;case 0:case 11:case 14:case 15:if(!He&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var i=l,o=i.destroy;i=i.tag,o!==void 0&&((i&2)!==0||(i&4)!==0)&&ko(n,t,o),l=l.next}while(l!==r)}tn(e,t,n);break;case 1:if(!He&&(Xn(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(u){Se(n,t,u)}tn(e,t,n);break;case 21:tn(e,t,n);break;case 22:n.mode&1?(He=(r=He)||n.memoizedState!==null,tn(e,t,n),He=r):tn(e,t,n);break;default:tn(e,t,n)}}function aa(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new wd),t.forEach(function(r){var l=Id.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function St(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=o),r&=~i}if(r=l,r=Ee()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*xd(r/1960))-r,10e?16:e,rn===null)var r=!1;else{if(e=rn,rn=null,$l=0,(Z&6)!==0)throw Error(c(331));var l=Z;for(Z|=4,P=e.current;P!==null;){var i=P,o=i.child;if((P.flags&16)!==0){var u=i.deletions;if(u!==null){for(var s=0;sEe()-_o?_n(e,0):No|=n),et(e,t)}function xa(e,t){t===0&&((e.mode&1)===0?t=1:(t=el,el<<=1,(el&130023424)===0&&(el=4194304)));var n=Ke();e=Dt(e,t),e!==null&&(sr(e,t,n),et(e,n))}function Pd(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),xa(e,n)}function Id(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(c(314))}r!==null&&r.delete(t),xa(e,n)}var Ea;Ea=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||Ze.current)qe=!0;else{if((e.lanes&n)===0&&(t.flags&128)===0)return qe=!1,vd(e,t,n);qe=(e.flags&131072)!==0}else qe=!1,me&&(t.flags&1048576)!==0&&ns(t,xl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Fl(e,t),e=t.pendingProps;var l=Bn(t,Ae.current);Kn(t,n),l=no(null,t,r,e,l,n);var i=ro();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Je(r)?(i=!0,wl(t)):i=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Xi(t),l.updater=Ol,t.stateNode=l,l._reactInternals=t,ao(t,r,e,n),t=mo(null,t,r,!0,i,n)):(t.tag=0,me&&i&&Ai(t),Qe(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Fl(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Ld(r),e=wt(r,e),l){case 0:t=po(null,t,r,e,n);break e;case 1:t=Xs(null,t,r,e,n);break e;case 11:t=$s(null,t,r,e,n);break e;case 14:t=Qs(null,t,r,wt(r.type,e),n);break e}throw Error(c(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:wt(r,l),po(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:wt(r,l),Xs(e,t,r,l,n);case 3:e:{if(Zs(t),e===null)throw Error(c(387));r=t.pendingProps,i=t.memoizedState,l=i.element,ds(e,t),Pl(t,r,null,n);var o=t.memoizedState;if(r=o.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){l=Yn(Error(c(423)),t),t=Js(e,t,r,n,l);break e}else if(r!==l){l=Yn(Error(c(424)),t),t=Js(e,t,r,n,l);break e}else for(lt=Xt(t.stateNode.containerInfo.firstChild),rt=t,me=!0,gt=null,n=as(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Wn(),r===l){t=Mt(e,t,n);break e}Qe(e,t,r,n)}t=t.child}return t;case 5:return ms(t),e===null&&Hi(t),r=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,o=l.children,zi(r,l)?o=null:i!==null&&zi(r,i)&&(t.flags|=32),Ys(e,t),Qe(e,t,o,n),t.child;case 6:return e===null&&Hi(t),null;case 13:return qs(e,t,n);case 4:return Zi(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=$n(t,null,r,n):Qe(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:wt(r,l),$s(e,t,r,l,n);case 7:return Qe(e,t,t.pendingProps,n),t.child;case 8:return Qe(e,t,t.pendingProps.children,n),t.child;case 12:return Qe(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,o=l.value,ae(Nl,r._currentValue),r._currentValue=o,i!==null)if(yt(i.value,o)){if(i.children===l.children&&!Ze.current){t=Mt(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var u=i.dependencies;if(u!==null){o=i.child;for(var s=u.firstContext;s!==null;){if(s.context===r){if(i.tag===1){s=Ot(-1,n&-n),s.tag=2;var v=i.updateQueue;if(v!==null){v=v.shared;var S=v.pending;S===null?s.next=s:(s.next=S.next,S.next=s),v.pending=s}}i.lanes|=n,s=i.alternate,s!==null&&(s.lanes|=n),Gi(i.return,n,t),u.lanes|=n;break}s=s.next}}else if(i.tag===10)o=i.type===t.type?null:i.child;else if(i.tag===18){if(o=i.return,o===null)throw Error(c(341));o.lanes|=n,u=o.alternate,u!==null&&(u.lanes|=n),Gi(o,n,t),o=i.sibling}else o=i.child;if(o!==null)o.return=i;else for(o=i;o!==null;){if(o===t){o=null;break}if(i=o.sibling,i!==null){i.return=o.return,o=i;break}o=o.return}i=o}Qe(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,Kn(t,n),l=ct(l),r=r(l),t.flags|=1,Qe(e,t,r,n),t.child;case 14:return r=t.type,l=wt(r,t.pendingProps),l=wt(r.type,l),Qs(e,t,r,l,n);case 15:return Ks(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:wt(r,l),Fl(e,t),t.tag=1,Je(r)?(e=!0,wl(t)):e=!1,Kn(t,n),Fs(t,r,l),ao(t,r,l,n),mo(null,t,r,!0,e,n);case 19:return ea(e,t,n);case 22:return Gs(e,t,n)}throw Error(c(156,t.tag))};function Ca(e,t){return ru(e,t)}function Td(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function pt(e,t,n,r){return new Td(e,t,n,r)}function Do(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Ld(e){if(typeof e=="function")return Do(e)?1:0;if(e!=null){if(e=e.$$typeof,e===ut)return 11;if(e===Xe)return 14}return 2}function un(e,t){var n=e.alternate;return n===null?(n=pt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Yl(e,t,n,r,l,i){var o=2;if(r=e,typeof e=="function")Do(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case Te:return Pn(n.children,l,i,t);case Ye:o=8,l|=8;break;case Et:return e=pt(12,n,t,l|2),e.elementType=Et,e.lanes=i,e;case Le:return e=pt(13,n,t,l),e.elementType=Le,e.lanes=i,e;case $e:return e=pt(19,n,t,l),e.elementType=$e,e.lanes=i,e;case ue:return Xl(n,l,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case mt:o=10;break e;case ht:o=9;break e;case ut:o=11;break e;case Xe:o=14;break e;case Re:o=16,r=null;break e}throw Error(c(130,e==null?e:typeof e,""))}return t=pt(o,n,t,l),t.elementType=e,t.type=r,t.lanes=i,t}function Pn(e,t,n,r){return e=pt(7,e,r,t),e.lanes=n,e}function Xl(e,t,n,r){return e=pt(22,e,r,t),e.elementType=ue,e.lanes=n,e.stateNode={isHidden:!1},e}function Oo(e,t,n){return e=pt(6,e,null,t),e.lanes=n,e}function Mo(e,t,n){return t=pt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Rd(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=si(0),this.expirationTimes=si(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=si(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Fo(e,t,n,r,l,i,o,u,s){return e=new Rd(e,t,n,u,s),t===1?(t=1,i===!0&&(t|=8)):t=0,i=pt(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Xi(i),e}function zd(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(d)}catch(k){console.error(k)}}return d(),Wo.exports=Qd(),Wo.exports}var Ua;function Gd(){if(Ua)return ni;Ua=1;var d=Kd();return ni.createRoot=d.createRoot,ni.hydrateRoot=d.hydrateRoot,ni}var Yd=Gd();const Xd=$a(Yd),Vr=[83,80,83,84];function Zd(d){const c=d.meta??new Uint8Array,R=d.data??new Uint8Array,y=new Uint8Array(32+c.length+R.length),T=new DataView(y.buffer,y.byteOffset,y.byteLength);return y.set(Vr,0),T.setUint16(4,1,!0),T.setUint16(6,32,!0),T.setUint16(8,d.msgType,!0),T.setUint16(10,d.flags??0,!0),T.setUint32(12,d.streamId>>>0,!0),T.setBigUint64(16,d.seq,!0),T.setUint32(24,c.length>>>0,!0),T.setUint32(28,R.length>>>0,!0),y.set(c,32),y.set(R,32+c.length),y}function Jd(d){const k=d instanceof Uint8Array?d:new Uint8Array(d);if(k.length<32)throw new Error("SSF frame too short");if(k[0]!==Vr[0]||k[1]!==Vr[1]||k[2]!==Vr[2]||k[3]!==Vr[3])throw new Error("invalid SSF magic");const c=new DataView(k.buffer,k.byteOffset,k.byteLength),R=c.getUint16(4,!0),y=c.getUint16(6,!0),T=c.getUint16(8,!0),Q=c.getUint16(10,!0),he=c.getUint32(12,!0),U=c.getBigUint64(16,!0),fe=c.getUint32(24,!0),we=c.getUint32(28,!0),ne=y,re=y+fe,pe=re+we;if(pe>k.length)throw new Error("SSF frame truncated");return{version:R,headerLen:y,msgType:T,flags:Q,streamId:he,seq:U,meta:k.slice(ne,ne+fe),data:k.slice(re,pe)}}class qd{ws=null;seq=1n;callbacks;constructor(k){this.callbacks=k}isConnected(){return this.ws?.readyState===WebSocket.OPEN}disconnect(){const k=this.ws;this.ws=null,k&&(k.readyState===WebSocket.OPEN||k.readyState===WebSocket.CONNECTING)&&k.close()}async connect(k,c){this.disconnect();const R=c?.subprotocol?new WebSocket(k,c.subprotocol):new WebSocket(k);R.binaryType="arraybuffer",this.ws=R,R.onopen=()=>this.callbacks.onOpen?.(),R.onclose=y=>this.callbacks.onClose?.(y),R.onerror=y=>this.callbacks.onError?.(y),R.onmessage=async y=>{const T=await ef(y.data);try{const Q=Jd(T);this.callbacks.onFrame?.({streamId:Q.streamId,msgType:Q.msgType,meta:Q.meta,data:Q.data})}catch{this.callbacks.onFrame?.({streamId:0,msgType:0,meta:new Uint8Array,data:T})}}}sendText(k,c){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("ws not connected");const R=new TextEncoder().encode("{}"),y=new TextEncoder().encode(c),T=Zd({streamId:k,msgType:2,seq:this.seq++,meta:R,data:y});this.ws.send(T)}}async function bd(d){const k=new URL(`/api/v1/executions/${encodeURIComponent(d.executionId)}/streams/session`,window.location.origin),c=await fetch(k,{method:"POST"});if(!c.ok){const R=await c.text().catch(()=>"");throw new Error(`create_stream_session failed: ${c.status} ${R}`)}return await c.json()}async function ef(d){return d instanceof ArrayBuffer?new Uint8Array(d):d instanceof Uint8Array?d:d instanceof Blob?new Uint8Array(await d.arrayBuffer()):typeof d=="string"?new TextEncoder().encode(d):new Uint8Array}const Aa=1,bn="spear.console.v1",tf="SPEAR Console";function nf(){const[d,k]=B.useState(""),[c,R]=B.useState(()=>df()),[y,T]=B.useState(()=>pf()??""),Q=B.useRef(new Map),he=B.useRef(null),U=B.useMemo(()=>c.find(C=>C.id===y)??c[0],[y,c]),fe=U?.executionId??"",we=U?.gatewayEndpoint??"",ne=U?.connect_kind??"execution",re=U?.messages.length??0,pe=U?.conn_status??"disconnected",We=U?.conn_error??"",oe=B.useMemo(()=>ne==="endpoint"&&we.trim()?`endpoint: ${we}`:fe.trim()?`execution: ${fe}`:"target: —",[ne,fe,we]),[le,Ge]=B.useState(!1),[ot,ve]=B.useState(""),[Pe,Ie]=B.useState(!1),[xe,Te]=B.useState("execution"),[Ye,Et]=B.useState([]),[mt,ht]=B.useState([]),[ut,Le]=B.useState([]),[$e,Xe]=B.useState(""),[Re,ue]=B.useState(""),[N,D]=B.useState(""),[j,f]=B.useState(""),[g,V]=B.useState(""),[H,K]=B.useState(""),[X,ee]=B.useState(!1),[J,se]=B.useState(""),[Ue,an]=B.useState(""),[$r,cn]=B.useState(!1),[er,In]=B.useState(!1),[Qr,dn]=B.useState(""),[Kr,Tn]=B.useState(null),[fn,It]=B.useState(null),[tr,Gr]=B.useState(null),Ut=B.useCallback(C=>{const O=Q.current.get(C);O&&(O.disconnect(),Q.current.delete(C)),R(G=>G.map(te=>te.id!==C?te:{...te,conn_status:"disconnected",conn_error:"",messages:te.messages.map(b=>b.streaming?{...b,streaming:!1}:b)}))},[]),nr=B.useCallback((C,O,G,te)=>{te&&R(b=>b.map(W=>{if(W.id!==C)return W;const Y=W.messages[W.messages.length-1];if(Y&&Y.role==="assistant"&&Y.streaming)return{...W,messages:[...W.messages.slice(0,-1),{...Y,text:Y.text+te}]};const ke=O||G?af(O,G):"",hn=ke?`${ke}${te}`:te;return{...W,messages:[...W.messages,{id:Hr(),role:"assistant",text:hn,createdAt:Date.now(),streaming:!0}]}}))},[]);B.useEffect(()=>{ff(c)},[c]),B.useEffect(()=>{y&&mf(y)},[y]),B.useEffect(()=>{!y&&c.length>0&&T(c[0].id)},[y,c]),B.useEffect(()=>{localStorage.setItem(`${bn}.executionId`,fe)},[fe]),B.useEffect(()=>{he.current?.scrollIntoView({block:"end"})},[re]);const rr=B.useCallback(C=>{R(O=>O.map(G=>G.id===y?{...G,...C}:G))},[y]),pn=B.useCallback(C=>{T(C)},[]),mn=B.useCallback(async C=>{ve(""),Ie(!0),Ge(!0);try{const O=await rf();Et(O);const G=C?.taskId?.trim()??"",te=C?.instanceId?.trim()??"";if(G){const b=await Ba(G);ht(b)}if(te){const b=await Va(te);Le(b)}}catch(O){ve(String(O instanceof Error?O.message:O))}finally{Ie(!1)}},[]),Yr=B.useCallback(async C=>{Gr(C),cn(!0),dn(""),In(!0),Tn(null),It(null);try{const[O,G]=await Promise.all([C.taskId?Wr(`/api/v1/tasks/${encodeURIComponent(C.taskId)}`):Promise.resolve(null),C.executionId?Wr(`/api/v1/executions/${encodeURIComponent(C.executionId)}`):Promise.resolve(null)]);Tn(O),It(G)}catch(O){dn(String(O instanceof Error?O.message:O))}finally{In(!1)}},[]),At=B.useCallback(async(C,O)=>{Ut(C),R(W=>W.map(Y=>Y.id===C?{...Y,conn_status:"connecting",conn_error:""}:Y));let G="",te;try{if(O.kind==="endpoint"){const W=O.gatewayEndpoint.trim();if(!W)throw new Error("no endpoint selected");G=new URL(`/e/${encodeURIComponent(W)}/ws`,window.location.origin).toString(),te="ssf.v1"}else{const W=O.executionId.trim();if(!W)throw new Error("no execution selected");G=(await bd({executionId:W})).ws_url}}catch(W){R(Y=>Y.map(ke=>ke.id===C?{...ke,conn_status:"disconnected",conn_error:String(W instanceof Error?W.message:W)}:ke));return}const b=new qd({onOpen:()=>{Q.current.get(C)===b&&R(W=>W.map(Y=>Y.id===C?{...Y,conn_status:"connected",conn_error:""}:Y))},onClose:()=>{R(W=>W.map(Y=>Y.id!==C?Y:{...Y,conn_status:"disconnected",messages:Y.messages.map(ke=>ke.streaming?{...ke,streaming:!1}:ke)}))},onError:()=>{Q.current.get(C)===b&&R(W=>W.map(Y=>Y.id===C?{...Y,conn_error:"websocket error"}:Y))},onFrame:({streamId:W,msgType:Y,data:ke,meta:hn})=>{if(Q.current.get(C)!==b||W!==Aa)return;const Vt=Wa(ke),Ht=Wa(hn).trim();nr(C,Y,Ht,Vt)}});Q.current.set(C,b);try{await b.connect(G,te?{subprotocol:te}:void 0)}catch(W){Q.current.get(C)===b&&Q.current.delete(C),R(Y=>Y.map(ke=>ke.id===C?{...ke,conn_status:"disconnected",conn_error:String(W instanceof Error?W.message:W)}:ke))}},[nr,Ut]),Bt=B.useCallback(async()=>{const C=d.trim();if(!C)return;const O=y;if(!O)return;const G=Q.current.get(O);if(!G?.isConnected()){R(te=>te.map(b=>b.id===O?{...b,conn_error:"not connected"}:b));return}k(""),R(te=>te.map(b=>{if(b.id!==O)return b;const W={id:Hr(),role:"user",text:C,createdAt:Date.now()},Y={id:Hr(),role:"assistant",text:"",createdAt:Date.now(),streaming:!0},ke=b.title.trim()?b.title:hf(C);return{...b,title:ke,messages:[...b.messages,W,Y]}})),G.sendText(Aa,C)},[y,d]);return h.jsxs("div",{className:"cw-page",children:[h.jsxs("aside",{className:"cw-sidebar",children:[h.jsxs("div",{className:"cw-brand",children:[h.jsx("div",{className:"cw-brandTitle",children:tf}),h.jsx("button",{className:"cw-btn cw-btnPrimary",onClick:()=>{const C=cf();R(O=>[C,...O]),pn(C.id),Te("execution"),f(""),Xe(""),ue(""),D(""),V(""),K(""),mn()},children:"New chat"})]}),h.jsx("div",{className:"cw-list",children:c.map(C=>h.jsxs("button",{className:C.id===y?"cw-conv cw-convActive":"cw-conv",onClick:()=>pn(C.id),children:[h.jsx("div",{className:"cw-convTitle",children:C.title.trim()?C.title:"(untitled)"}),h.jsx("div",{className:"cw-convMeta",children:C.connect_kind==="endpoint"&&C.gatewayEndpoint?`endpoint: ${C.gatewayEndpoint}`:C.executionId?`execution: ${C.executionId}`:"no target"}),h.jsxs("div",{className:"cw-convActions",children:[h.jsx("button",{className:"cw-iconBtn",onClick:O=>{O.preventDefault(),O.stopPropagation(),se(C.id),an(C.title),ee(!0)},children:"Rename"}),h.jsx("button",{className:"cw-iconBtn cw-danger",onClick:O=>{if(O.preventDefault(),O.stopPropagation(),Ut(C.id),R(G=>G.filter(te=>te.id!==C.id)),y===C.id){const G=c.find(te=>te.id!==C.id)?.id;pn(G??"")}},children:"Delete"})]})]},C.id))})]}),h.jsxs("main",{className:"cw-main",children:[h.jsxs("header",{className:"cw-header",children:[h.jsxs("div",{className:"cw-headerLeft",children:[h.jsx("div",{className:"cw-headerTitle",children:U?.title.trim()?U.title:"Chat"}),h.jsx("div",{className:"cw-headerMeta",children:oe})]}),h.jsxs("div",{className:"cw-headerRight",children:[h.jsxs("div",{className:"cw-connChip",title:oe,children:[h.jsx("span",{className:pe==="connected"?"cw-dot cw-dotOk":pe==="connecting"?"cw-dot cw-dotWarn":"cw-dot"}),h.jsxs("span",{className:"cw-connChipText",children:[pe," • ",oe]})]}),We?h.jsx("div",{className:"cw-error cw-errorInline",children:We}):null,h.jsx("button",{className:"cw-btn",onClick:()=>{Te(U?.connect_kind??"execution"),f(""),Xe(U?.taskId??""),ue(U?.instanceId??""),D(U?.executionId??""),V(U?.gatewayEndpoint??""),K(U?.taskId??""),mn({taskId:U?.taskId??"",instanceId:U?.instanceId??""})},children:"Connect…"}),h.jsx("button",{className:"cw-btn",onClick:()=>Ut(y),disabled:pe==="disconnected",children:"Disconnect"})]})]}),h.jsxs("div",{className:"cw-chat",children:[U?.messages.length?U.messages.map(C=>h.jsx(sf,{message:C},C.id)):h.jsxs("div",{className:"cw-empty",children:[h.jsx("div",{className:"cw-emptyTitle",children:"Connect and start chatting"}),h.jsx("div",{className:"cw-emptyText",children:"Connect by execution (stream session) or by endpoint gateway. The chat window stays the same."})]}),h.jsx("div",{ref:he})]}),h.jsxs("div",{className:"cw-composer",children:[h.jsx("textarea",{className:"cw-textarea",value:d,onChange:C=>k(C.target.value),placeholder:pe==="connected"?"Message…":"Connect to send messages…",onKeyDown:C=>{C.key==="Enter"&&!C.shiftKey&&(C.preventDefault(),Bt())}}),h.jsxs("div",{className:"cw-composerActions",children:[h.jsx("button",{className:"cw-btn cw-btnPrimary",disabled:pe!=="connected"||!d.trim(),onClick:Bt,children:"Send"}),h.jsx("button",{className:"cw-btn",disabled:pe!=="connected",onClick:()=>Ut(y),children:"Stop"})]})]})]}),le?h.jsx(lf,{tab:xe,tasks:Ye,instances:mt,executions:ut,selectedTaskId:$e,selectedInstanceId:Re,selectedExecutionId:N,endpointSearch:j,selectedGatewayEndpoint:g,loading:Pe,error:ot,status:pe,onChangeTab:Te,onClose:()=>{Ge(!1),ve("")},onOpenInfo:()=>{Yr({taskId:xe==="endpoint"?H.trim():$e.trim(),instanceId:xe==="endpoint"?"":Re.trim(),executionId:xe==="endpoint"?"":N.trim()})},onChangeTask:C=>{Xe(C),ue(""),D(""),ht([]),Le([]),C.trim()&&(ve(""),Ie(!0),Ba(C.trim()).then(O=>ht(O)).catch(O=>ve(String(O instanceof Error?O.message:O))).finally(()=>Ie(!1)))},onChangeInstance:C=>{ue(C),D(""),Le([]),C.trim()&&(ve(""),Ie(!0),Va(C.trim()).then(O=>Le(O)).catch(O=>ve(String(O instanceof Error?O.message:O))).finally(()=>Ie(!1)))},onChangeExecution:D,onChangeEndpointSearch:f,onSelectEndpoint:C=>{V(C.gatewayEndpoint),K(C.taskId)},onConnect:async()=>{if(!y)return;if(xe==="endpoint"){const te=g.trim();if(!te){ve("Please select an endpoint");return}rr({connect_kind:"endpoint",taskId:H.trim(),instanceId:"",executionId:"",gatewayEndpoint:te}),Ge(!1),ve(""),await At(y,{kind:"endpoint",gatewayEndpoint:te});return}const C=$e.trim(),O=Re.trim(),G=N.trim();if(!G){ve("Please select an execution");return}rr({connect_kind:"execution",taskId:C,instanceId:O,executionId:G,gatewayEndpoint:""}),Ge(!1),ve(""),await At(y,{kind:"execution",executionId:G})}}):null,$r?h.jsx(of,{ids:tr,loading:er,error:Qr,task:Kr,execution:fn,onClose:()=>{cn(!1),dn(""),In(!1)}}):null,X?h.jsx(uf,{value:Ue,onChange:an,onClose:()=>ee(!1),onConfirm:()=>{const C=Ue.trim();J&&(R(O=>O.map(G=>G.id===J?{...G,title:C}:G)),ee(!1))}}):null]})}async function Wr(d){const k=new URL(d,window.location.origin),c=await fetch(k);if(!c.ok){const R=await c.text().catch(()=>"");throw new Error(`${c.status} ${R}`)}return await c.json()}async function rf(){return(await Wr("/api/v1/tasks")).tasks.map(k=>({task_id:k.task_id,name:k.name,endpoint:k.endpoint}))}async function Ba(d){return(await Wr(`/api/v1/tasks/${encodeURIComponent(d)}/instances?limit=100`)).instances.map(c=>({instance_id:c.instance_id,status:c.status,current_execution_id:c.current_execution_id}))}async function Va(d){return(await Wr(`/api/v1/instances/${encodeURIComponent(d)}/executions?limit=100`)).executions}function lf(d){const k=B.useMemo(()=>{const y=d.endpointSearch.trim().toLowerCase();return d.tasks.map(T=>({taskId:T.task_id,taskName:T.name,gatewayEndpoint:(T.endpoint??"").trim()})).filter(T=>T.gatewayEndpoint.trim()).filter(T=>y?T.gatewayEndpoint.toLowerCase().includes(y)||T.taskName.toLowerCase().includes(y):!0).sort((T,Q)=>T.gatewayEndpoint.localeCompare(Q.gatewayEndpoint))},[d.endpointSearch,d.tasks]),c=d.tab==="endpoint"?d.selectedGatewayEndpoint.trim()?`/e/${d.selectedGatewayEndpoint.trim()}/ws`:"—":d.selectedExecutionId.trim()?`execution: ${d.selectedExecutionId.trim()}`:"—",R=!d.loading&&d.status!=="connecting"&&(d.tab==="endpoint"?!!d.selectedGatewayEndpoint.trim():!!d.selectedExecutionId.trim());return h.jsx("div",{className:"cw-drawerBackdrop",role:"dialog","aria-modal":"true",onClick:()=>d.onClose(),children:h.jsxs("div",{className:"cw-drawer",onClick:y=>{y.stopPropagation()},children:[h.jsxs("div",{className:"cw-drawerHeader",children:[h.jsx("div",{className:"cw-drawerTitle",children:"Connect"}),h.jsxs("div",{className:"cw-drawerHeaderRight",children:[h.jsx("button",{className:"cw-iconBtn",onClick:d.onOpenInfo,disabled:d.tab==="execution"?!d.selectedExecutionId.trim():!1,children:"Info"}),h.jsx("button",{className:"cw-iconBtn",onClick:d.onClose,children:"Close"})]})]}),h.jsxs("div",{className:"cw-drawerBody",children:[h.jsxs("div",{className:"cw-tabs",children:[h.jsx("button",{className:d.tab==="execution"?"cw-tab cw-tabActive":"cw-tab",onClick:()=>d.onChangeTab("execution"),children:"By Execution"}),h.jsx("button",{className:d.tab==="endpoint"?"cw-tab cw-tabActive":"cw-tab",onClick:()=>d.onChangeTab("endpoint"),children:"By Endpoint"})]}),d.tab==="execution"?h.jsxs("div",{className:"cw-drawerSection",children:[h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Task"}),h.jsxs("select",{className:"cw-select",value:d.selectedTaskId,onChange:y=>d.onChangeTask(y.target.value),children:[h.jsx("option",{value:"",children:"Select a task…"}),d.tasks.map(y=>h.jsxs("option",{value:y.task_id,children:[y.name," (",y.task_id,")"]},y.task_id))]})]}),h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Instance"}),h.jsxs("select",{className:"cw-select",value:d.selectedInstanceId,onChange:y=>d.onChangeInstance(y.target.value),disabled:!d.selectedTaskId,children:[h.jsx("option",{value:"",children:"Select an instance…"}),d.instances.map(y=>h.jsxs("option",{value:y.instance_id,children:[y.instance_id," (",y.status,")"]},y.instance_id))]})]}),h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Execution"}),h.jsxs("select",{className:"cw-select",value:d.selectedExecutionId,onChange:y=>d.onChangeExecution(y.target.value),disabled:!d.selectedInstanceId,children:[h.jsx("option",{value:"",children:"Select an execution…"}),d.executions.map(y=>h.jsxs("option",{value:y.execution_id,children:[y.execution_id," (",y.status,") ",y.function_name?`- ${y.function_name}`:""]},y.execution_id))]})]})]}):h.jsxs("div",{className:"cw-drawerSection",children:[h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Endpoint"}),h.jsx("input",{className:"cw-textInput",value:d.endpointSearch,onChange:y=>d.onChangeEndpointSearch(y.target.value),placeholder:"Search gateway_endpoint…"})]}),h.jsx("div",{className:"cw-endpointList",role:"list",children:k.length?k.map(y=>h.jsxs("button",{className:y.gatewayEndpoint===d.selectedGatewayEndpoint?"cw-endpointItem cw-endpointItemActive":"cw-endpointItem",onClick:()=>d.onSelectEndpoint({taskId:y.taskId,gatewayEndpoint:y.gatewayEndpoint}),role:"listitem",children:[h.jsxs("div",{className:"cw-endpointLeft",children:[h.jsx("div",{className:"cw-endpointName",children:y.gatewayEndpoint}),h.jsx("div",{className:"cw-endpointMeta",children:y.taskName})]}),h.jsx("div",{className:"cw-endpointRight",children:y.gatewayEndpoint===d.selectedGatewayEndpoint?"Selected":""})]},`${y.taskId}:${y.gatewayEndpoint}`)):h.jsx("div",{className:"cw-modalHint",children:"No endpoints found."})})]}),h.jsxs("div",{className:"cw-drawerSection",children:[h.jsx("div",{className:"cw-label",children:"Connection details"}),h.jsxs("div",{className:"cw-connDetails",children:[h.jsxs("div",{className:"cw-connDetailsRow",children:[h.jsx("div",{className:"cw-connDetailsKey",children:"Target"}),h.jsx("div",{className:"cw-connDetailsVal",children:c})]}),h.jsxs("div",{className:"cw-connDetailsRow",children:[h.jsx("div",{className:"cw-connDetailsKey",children:"Protocol"}),h.jsx("div",{className:"cw-connDetailsVal",children:d.tab==="endpoint"?"ssf.v1 (binary WS)":"stream session (binary WS)"})]})]})]}),d.loading?h.jsx("div",{className:"cw-modalHint",children:"Loading…"}):null,d.error?h.jsx("div",{className:"cw-error",children:d.error}):null]}),h.jsxs("div",{className:"cw-drawerFooter",children:[h.jsx("button",{className:"cw-btn",onClick:d.onClose,children:"Cancel"}),h.jsx("button",{className:"cw-btn cw-btnPrimary",onClick:d.onConnect,disabled:!R,children:"Connect"})]})]})})}function of(d){const k=d.ids??{taskId:"",instanceId:"",executionId:""},c=B.useMemo(()=>Ha(d.task),[d.task]),R=B.useMemo(()=>Ha(d.execution),[d.execution]);return h.jsx("div",{className:"cw-modalBackdrop",role:"dialog","aria-modal":"true",children:h.jsxs("div",{className:"cw-modal",children:[h.jsxs("div",{className:"cw-modalHeader",children:[h.jsx("div",{className:"cw-modalTitle",children:"Execution info"}),h.jsx("div",{className:"cw-modalHeaderRight",children:h.jsx("button",{className:"cw-iconBtn",onClick:d.onClose,children:"Close"})})]}),h.jsxs("div",{className:"cw-modalBody",children:[h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Task"}),h.jsx("div",{className:"cw-readonly",children:k.taskId||"n/a"})]}),h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Instance"}),h.jsx("div",{className:"cw-readonly",children:k.instanceId||"n/a"})]}),h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Execution"}),h.jsx("div",{className:"cw-readonly",children:k.executionId||"n/a"})]}),d.loading?h.jsx("div",{className:"cw-modalHint",children:"Loading…"}):null,d.error?h.jsx("div",{className:"cw-error",children:d.error}):null,h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Task detail"}),h.jsxs("div",{className:"cw-codeWrap",children:[h.jsxs("div",{className:"cw-codeHeader",children:[h.jsx("div",{className:"cw-codeLang",children:"json"}),h.jsx("button",{className:"cw-codeBtn",onClick:()=>navigator.clipboard?.writeText(c).catch(()=>{}),children:"Copy"})]}),h.jsx("pre",{className:"cw-code",children:h.jsx("code",{children:c})})]})]}),h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Execution detail"}),h.jsxs("div",{className:"cw-codeWrap",children:[h.jsxs("div",{className:"cw-codeHeader",children:[h.jsx("div",{className:"cw-codeLang",children:"json"}),h.jsx("button",{className:"cw-codeBtn",onClick:()=>navigator.clipboard?.writeText(R).catch(()=>{}),children:"Copy"})]}),h.jsx("pre",{className:"cw-code",children:h.jsx("code",{children:R})})]})]})]}),h.jsx("div",{className:"cw-modalFooter",children:h.jsx("button",{className:"cw-btn cw-btnPrimary",onClick:d.onClose,children:"Done"})})]})})}function Ha(d){if(d==null)return"null";try{return JSON.stringify(d,null,2)}catch{return String(d)}}function uf(d){return h.jsx("div",{className:"cw-modalBackdrop",role:"dialog","aria-modal":"true",children:h.jsxs("div",{className:"cw-modal",children:[h.jsxs("div",{className:"cw-modalHeader",children:[h.jsx("div",{className:"cw-modalTitle",children:"Rename chat"}),h.jsx("div",{className:"cw-modalHeaderRight",children:h.jsx("button",{className:"cw-iconBtn",onClick:d.onClose,children:"Close"})})]}),h.jsx("div",{className:"cw-modalBody",children:h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Title"}),h.jsx("input",{className:"cw-textInput",value:d.value,onChange:k=>d.onChange(k.target.value),autoFocus:!0,placeholder:"Chat title",onKeyDown:k=>{k.key==="Enter"&&(k.preventDefault(),d.onConfirm())}})]})}),h.jsxs("div",{className:"cw-modalFooter",children:[h.jsx("button",{className:"cw-btn",onClick:d.onClose,children:"Cancel"}),h.jsx("button",{className:"cw-btn cw-btnPrimary",onClick:d.onConfirm,disabled:!d.value.trim(),children:"Save"})]})]})})}function sf(d){const{message:k}=d,c=k.role==="user",R=B.useMemo(()=>vf(k.text),[k.text]);return h.jsx("div",{className:c?"cw-row cw-rowUser":"cw-row",children:h.jsx("div",{className:c?"cw-bubble cw-bubbleUser":"cw-bubble",children:R.map((y,T)=>y.type==="code"?h.jsxs("div",{className:"cw-codeWrap",children:[h.jsxs("div",{className:"cw-codeHeader",children:[h.jsx("div",{className:"cw-codeLang",children:y.lang??""}),h.jsx("button",{className:"cw-codeBtn",onClick:()=>navigator.clipboard?.writeText(y.code).catch(()=>{}),children:"Copy"})]}),h.jsx("pre",{className:"cw-code",children:h.jsx("code",{children:y.code})})]},T):h.jsx("div",{className:"cw-text",children:y.text},T))})})}function Wa(d){try{return new TextDecoder().decode(d)}catch{return""}}function af(d,k){return!k&&!d?"":k?`[type=${d} meta=${k}] `:`[type=${d}] `}function cf(d){return{id:Hr(),title:"",connect_kind:"execution",taskId:"",instanceId:"",executionId:localStorage.getItem(`${bn}.executionId`)??"",gatewayEndpoint:"",conn_status:"disconnected",conn_error:"",createdAt:Date.now(),messages:[]}}function df(){const d=localStorage.getItem(`${bn}.conversations`);if(!d)return[];try{const k=JSON.parse(d);return!Array.isArray(k)||!k.length?[]:k.map(c=>({id:c.id??Hr(),title:c.title??"",connect_kind:c.connect_kind==="endpoint"||c.connect_kind==="execution"?c.connect_kind:c.executionId?"execution":c.gatewayEndpoint?"endpoint":"execution",taskId:c.taskId??"",instanceId:c.instanceId??"",executionId:c.executionId??"",gatewayEndpoint:c.gatewayEndpoint??"",conn_status:"disconnected",conn_error:"",createdAt:c.createdAt??Date.now(),messages:Array.isArray(c.messages)?c.messages:[]}))}catch{return[]}}function ff(d){localStorage.setItem(`${bn}.conversations`,JSON.stringify(d))}function pf(){return localStorage.getItem(`${bn}.activeId`)}function mf(d){localStorage.setItem(`${bn}.activeId`,d)}function hf(d){const k=d.replace(/\s+/g," ").trim();return k?k.length<=40?k:k.slice(0,40)+"…":""}function Hr(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function vf(d){const k=[],c=/```([a-zA-Z0-9_-]+)?\n([\s\S]*?)```/g;let R=0;for(;;){const y=c.exec(d);if(!y)break;const T=y.index;T>R&&k.push({type:"text",text:d.slice(R,T)}),k.push({type:"code",lang:y[1],code:y[2]}),R=T+y[0].length}return R<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[_t]=t,e[Nr]=r,la(e,t,!1,!1),t.stateNode=e;e:{switch(o=ne(n,r),n){case"dialog":fe("cancel",e),fe("close",e),l=r;break;case"iframe":case"object":case"embed":fe("load",e),l=r;break;case"video":case"audio":for(l=0;lJn&&(t.flags|=128,r=!0,Dr(i,!1),t.lanes=4194304)}else{if(!r)if(e=Rl(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Dr(i,!0),i.tail===null&&i.tailMode==="hidden"&&!o.alternate&&!ve)return Be(t),null}else 2*Ee()-i.renderingStartTime>Jn&&n!==1073741824&&(t.flags|=128,r=!0,Dr(i,!1),t.lanes=4194304);i.isBackwards?(o.sibling=t.child,t.child=o):(n=i.last,n!==null?n.sibling=o:t.child=o,i.last=o)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=Ee(),t.sibling=null,n=ye.current,de(ye,r?n&1|2:n&1),t):(Be(t),null);case 22:case 23:return Oo(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&(t.mode&1)!==0?(ot&1073741824)!==0&&(Be(t),t.subtreeFlags&6&&(t.flags|=8192)):Be(t),null;case 24:return null;case 25:return null}throw Error(c(156,t.tag))}function Sd(e,t){switch(Hi(t),t.tag){case 1:return Ze(t.type)&&kl(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Gn(),pe(Xe),pe(Ue),to(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 5:return bi(t),null;case 13:if(pe(ye),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(c(340));Wn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return pe(ye),null;case 4:return Gn(),null;case 10:return Yi(t.type._context),null;case 22:case 23:return Oo(),null;case 24:return null;default:return null}}var Bl=!1,Ve=!1,xd=typeof WeakSet=="function"?WeakSet:Set,I=null;function Xn(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Se(e,t,r)}else n.current=null}function xo(e,t,n){try{n()}catch(r){Se(e,t,r)}}var ua=!1;function Ed(e,t){if(zi=ol,e=Au(),Ni(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var o=0,u=-1,s=-1,v=0,S=0,x=e,k=null;t:for(;;){for(var j;x!==n||l!==0&&x.nodeType!==3||(u=o+l),x!==i||r!==0&&x.nodeType!==3||(s=o+r),x.nodeType===3&&(o+=x.nodeValue.length),(j=x.firstChild)!==null;)k=x,x=j;for(;;){if(x===e)break t;if(k===n&&++v===l&&(u=o),k===i&&++S===r&&(s=o),(j=x.nextSibling)!==null)break;x=k,k=x.parentNode}x=j}n=u===-1||s===-1?null:{start:u,end:s}}else n=null}n=n||{start:0,end:0}}else n=null;for(Oi={focusedElem:e,selectionRange:n},ol=!1,I=t;I!==null;)if(t=I,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,I=e;else for(;I!==null;){t=I;try{var T=t.alternate;if((t.flags&1024)!==0)switch(t.tag){case 0:case 11:case 15:break;case 1:if(T!==null){var L=T.memoizedProps,Ce=T.memoizedState,p=t.stateNode,a=p.getSnapshotBeforeUpdate(t.elementType===t.type?L:gt(t.type,L),Ce);p.__reactInternalSnapshotBeforeUpdate=a}break;case 3:var m=t.stateNode.containerInfo;m.nodeType===1?m.textContent="":m.nodeType===9&&m.documentElement&&m.removeChild(m.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(c(163))}}catch(C){Se(t,t.return,C)}if(e=t.sibling,e!==null){e.return=t.return,I=e;break}I=t.return}return T=ua,ua=!1,T}function Mr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var i=l.destroy;l.destroy=void 0,i!==void 0&&xo(t,n,i)}l=l.next}while(l!==r)}}function Vl(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Eo(e){var t=e.ref;if(t!==null){var n=e.stateNode;e.tag,e=n,typeof t=="function"?t(e):t.current=e}}function sa(e){var t=e.alternate;t!==null&&(e.alternate=null,sa(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[_t],delete t[Nr],delete t[Ui],delete t[id],delete t[od])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function aa(e){return e.tag===5||e.tag===3||e.tag===4}function ca(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||aa(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.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Co(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=gl));else if(r!==4&&(e=e.child,e!==null))for(Co(e,t,n),e=e.sibling;e!==null;)Co(e,t,n),e=e.sibling}function No(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&&(e=e.child,e!==null))for(No(e,t,n),e=e.sibling;e!==null;)No(e,t,n),e=e.sibling}var Oe=null,wt=!1;function nn(e,t,n){for(n=n.child;n!==null;)da(e,t,n),n=n.sibling}function da(e,t,n){if(Nt&&typeof Nt.onCommitFiberUnmount=="function")try{Nt.onCommitFiberUnmount(el,n)}catch{}switch(n.tag){case 5:Ve||Xn(n,t);case 6:var r=Oe,l=wt;Oe=null,nn(e,t,n),Oe=r,wt=l,Oe!==null&&(wt?(e=Oe,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Oe.removeChild(n.stateNode));break;case 18:Oe!==null&&(wt?(e=Oe,n=n.stateNode,e.nodeType===8?Fi(e.parentNode,n):e.nodeType===1&&Fi(e,n),mr(e)):Fi(Oe,n.stateNode));break;case 4:r=Oe,l=wt,Oe=n.stateNode.containerInfo,wt=!0,nn(e,t,n),Oe=r,wt=l;break;case 0:case 11:case 14:case 15:if(!Ve&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var i=l,o=i.destroy;i=i.tag,o!==void 0&&((i&2)!==0||(i&4)!==0)&&xo(n,t,o),l=l.next}while(l!==r)}nn(e,t,n);break;case 1:if(!Ve&&(Xn(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(u){Se(n,t,u)}nn(e,t,n);break;case 21:nn(e,t,n);break;case 22:n.mode&1?(Ve=(r=Ve)||n.memoizedState!==null,nn(e,t,n),Ve=r):nn(e,t,n);break;default:nn(e,t,n)}}function fa(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new xd),t.forEach(function(r){var l=Ld.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function kt(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=o),r&=~i}if(r=l,r=Ee()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Nd(r/1960))-r,10e?16:e,ln===null)var r=!1;else{if(e=ln,ln=null,Kl=0,(J&6)!==0)throw Error(c(331));var l=J;for(J|=4,I=e.current;I!==null;){var i=I,o=i.child;if((I.flags&16)!==0){var u=i.deletions;if(u!==null){for(var s=0;sEe()-Io?xn(e,0):jo|=n),be(e,t)}function Na(e,t){t===0&&((e.mode&1)===0?t=1:(t=nl,nl<<=1,(nl&130023424)===0&&(nl=4194304)));var n=Ke();e=Dt(e,t),e!==null&&(ar(e,t,n),be(e,n))}function Rd(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Na(e,n)}function Ld(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(c(314))}r!==null&&r.delete(t),Na(e,n)}var _a;_a=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||Xe.current)Je=!0;else{if((e.lanes&n)===0&&(t.flags&128)===0)return Je=!1,wd(e,t,n);Je=(e.flags&131072)!==0}else Je=!1,ve&&(t.flags&1048576)!==0&&is(t,Cl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Al(e,t),e=t.pendingProps;var l=Bn(t,Ue.current);Kn(t,n),l=lo(null,t,r,e,l,n);var i=io();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Ze(r)?(i=!0,Sl(t)):i=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ji(t),l.updater=Fl,t.stateNode=l,l._reactInternals=t,fo(t,r,e,n),t=vo(null,t,r,!0,i,n)):(t.tag=0,ve&&i&&Vi(t),Qe(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Al(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Od(r),e=gt(r,e),l){case 0:t=ho(null,t,r,e,n);break e;case 1:t=qs(null,t,r,e,n);break e;case 11:t=Gs(null,t,r,e,n);break e;case 14:t=Ys(null,t,r,gt(r.type,e),n);break e}throw Error(c(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:gt(r,l),ho(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:gt(r,l),qs(e,t,r,l,n);case 3:e:{if(bs(t),e===null)throw Error(c(387));r=t.pendingProps,i=t.memoizedState,l=i.element,ms(e,t),Tl(t,r,null,n);var o=t.memoizedState;if(r=o.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){l=Yn(Error(c(423)),t),t=ea(e,t,r,n,l);break e}else if(r!==l){l=Yn(Error(c(424)),t),t=ea(e,t,r,n,l);break e}else for(it=Zt(t.stateNode.containerInfo.firstChild),lt=t,ve=!0,yt=null,n=fs(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Wn(),r===l){t=Ft(e,t,n);break e}Qe(e,t,r,n)}t=t.child}return t;case 5:return ys(t),e===null&&$i(t),r=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,o=l.children,Di(r,l)?o=null:i!==null&&Di(r,i)&&(t.flags|=32),Js(e,t),Qe(e,t,o,n),t.child;case 6:return e===null&&$i(t),null;case 13:return ta(e,t,n);case 4:return qi(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=$n(t,null,r,n):Qe(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:gt(r,l),Gs(e,t,r,l,n);case 7:return Qe(e,t,t.pendingProps,n),t.child;case 8:return Qe(e,t,t.pendingProps.children,n),t.child;case 12:return Qe(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,o=l.value,de(jl,r._currentValue),r._currentValue=o,i!==null)if(vt(i.value,o)){if(i.children===l.children&&!Xe.current){t=Ft(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var u=i.dependencies;if(u!==null){o=i.child;for(var s=u.firstContext;s!==null;){if(s.context===r){if(i.tag===1){s=Mt(-1,n&-n),s.tag=2;var v=i.updateQueue;if(v!==null){v=v.shared;var S=v.pending;S===null?s.next=s:(s.next=S.next,S.next=s),v.pending=s}}i.lanes|=n,s=i.alternate,s!==null&&(s.lanes|=n),Xi(i.return,n,t),u.lanes|=n;break}s=s.next}}else if(i.tag===10)o=i.type===t.type?null:i.child;else if(i.tag===18){if(o=i.return,o===null)throw Error(c(341));o.lanes|=n,u=o.alternate,u!==null&&(u.lanes|=n),Xi(o,n,t),o=i.sibling}else o=i.child;if(o!==null)o.return=i;else for(o=i;o!==null;){if(o===t){o=null;break}if(i=o.sibling,i!==null){i.return=o.return,o=i;break}o=o.return}i=o}Qe(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,Kn(t,n),l=at(l),r=r(l),t.flags|=1,Qe(e,t,r,n),t.child;case 14:return r=t.type,l=gt(r,t.pendingProps),l=gt(r.type,l),Ys(e,t,r,l,n);case 15:return Xs(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:gt(r,l),Al(e,t),t.tag=1,Ze(r)?(e=!0,Sl(t)):e=!1,Kn(t,n),Bs(t,r,l),fo(t,r,l,n),vo(null,t,r,!0,e,n);case 19:return ra(e,t,n);case 22:return Zs(e,t,n)}throw Error(c(156,t.tag))};function ja(e,t){return ou(e,t)}function zd(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function ft(e,t,n,r){return new zd(e,t,n,r)}function Mo(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Od(e){if(typeof e=="function")return Mo(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Ge)return 11;if(e===Ye)return 14}return 2}function sn(e,t){var n=e.alternate;return n===null?(n=ft(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Zl(e,t,n,r,l,i){var o=2;if(r=e,typeof e=="function")Mo(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case Ne:return Cn(n.children,l,i,t);case Me:o=8,l|=8;break;case xt:return e=ft(12,n,t,l|2),e.elementType=xt,e.lanes=i,e;case We:return e=ft(13,n,t,l),e.elementType=We,e.lanes=i,e;case Fe:return e=ft(19,n,t,l),e.elementType=Fe,e.lanes=i,e;case ce:return Jl(n,l,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case mt:o=10;break e;case Tt:o=9;break e;case Ge:o=11;break e;case Ye:o=14;break e;case Pe:o=16,r=null;break e}throw Error(c(130,e==null?e:typeof e,""))}return t=ft(o,n,t,l),t.elementType=e,t.type=r,t.lanes=i,t}function Cn(e,t,n,r){return e=ft(7,e,r,t),e.lanes=n,e}function Jl(e,t,n,r){return e=ft(22,e,r,t),e.elementType=ce,e.lanes=n,e.stateNode={isHidden:!1},e}function Fo(e,t,n){return e=ft(6,e,null,t),e.lanes=n,e}function Uo(e,t,n){return t=ft(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Dd(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=ci(0),this.expirationTimes=ci(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=ci(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Ao(e,t,n,r,l,i,o,u,s){return e=new Dd(e,t,n,u,s),t===1?(t=1,i===!0&&(t|=8)):t=0,i=ft(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ji(i),e}function Md(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(d)}catch(g){console.error(g)}}return d(),Qo.exports=Yd(),Qo.exports}var Va;function Zd(){if(Va)return li;Va=1;var d=Xd();return li.createRoot=d.createRoot,li.hydrateRoot=d.hydrateRoot,li}var Jd=Zd();const qd=Ka(Jd),Wr=[83,80,83,84],bd=1,Ga=32,Yo={CTRL:1,DATA:2,COMMIT:3};function Xo(d){const g=Ga,c=d.meta??new Uint8Array,P=d.data??new Uint8Array,y=new Uint8Array(g+c.length+P.length),R=new DataView(y.buffer,y.byteOffset,y.byteLength);return y.set(Wr,0),R.setUint16(4,bd,!0),R.setUint16(6,g,!0),R.setUint16(8,d.msgType,!0),R.setUint16(10,d.flags??0,!0),R.setUint32(12,d.streamId>>>0,!0),R.setBigUint64(16,d.seq,!0),R.setUint32(24,c.length>>>0,!0),R.setUint32(28,P.length>>>0,!0),y.set(c,g),y.set(P,g+c.length),y}function ef(d){const g=d instanceof Uint8Array?d:new Uint8Array(d);if(g.lengthg.length)throw new Error("SSF frame truncated");return{version:P,headerLen:y,msgType:R,flags:W,streamId:me,seq:q,meta:g.slice(re,re+B),data:g.slice(le,ze)}}class tf{ws=null;seq=1n;callbacks;constructor(g){this.callbacks=g}isConnected(){return this.ws?.readyState===WebSocket.OPEN}disconnect(){const g=this.ws;this.ws=null,g&&(g.readyState===WebSocket.OPEN||g.readyState===WebSocket.CONNECTING)&&g.close()}async connect(g,c){this.disconnect();const P=c?.subprotocol?new WebSocket(g,c.subprotocol):new WebSocket(g);P.binaryType="arraybuffer",this.ws=P,P.onopen=()=>{this.callbacks.onOpen?.();const y=c?.autoOpenStreamId;if(typeof y=="number"&&Number.isFinite(y)&&y>0)try{this.openStream(y)}catch{}},P.onclose=y=>this.callbacks.onClose?.(y),P.onerror=y=>this.callbacks.onError?.(y),P.onmessage=async y=>{const R=await rf(y.data);try{const W=ef(R);this.callbacks.onFrame?.({streamId:W.streamId,msgType:W.msgType,meta:W.meta,data:W.data})}catch{this.callbacks.onFrame?.({streamId:0,msgType:0,meta:new Uint8Array,data:R})}}}openStream(g){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("ws not connected");const c=new TextEncoder().encode("{}"),P=Xo({streamId:g,msgType:Yo.CTRL,seq:this.seq++,meta:c,data:new Uint8Array});this.ws.send(P)}sendText(g,c){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("ws not connected");const P=new TextEncoder().encode("{}"),y=new TextEncoder().encode(c),R=Xo({streamId:g,msgType:Yo.DATA,seq:this.seq++,meta:P,data:y});this.ws.send(R)}sendCommit(g){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("ws not connected");const c=new TextEncoder().encode("{}"),P=Xo({streamId:g,msgType:Yo.COMMIT,seq:this.seq++,meta:c,data:new Uint8Array});this.ws.send(P)}}async function nf(d){const g=new URL(`/api/v1/executions/${encodeURIComponent(d.executionId)}/streams/session`,window.location.origin),c=await fetch(g,{method:"POST"});if(!c.ok){const P=await c.text().catch(()=>"");throw new Error(`create_stream_session failed: ${c.status} ${P}`)}return await c.json()}async function rf(d){return d instanceof ArrayBuffer?new Uint8Array(d):d instanceof Uint8Array?d:d instanceof Blob?new Uint8Array(await d.arrayBuffer()):typeof d=="string"?new TextEncoder().encode(d):new Uint8Array}const Hr=1,bn="spear.console.v1",lf="SPEAR Console";function of(){const[d,g]=U.useState(""),[c,P]=U.useState(()=>mf()),[y,R]=U.useState(()=>vf()??""),W=U.useRef(new Map),me=U.useRef(new Map),q=U.useRef(null),B=U.useMemo(()=>c.find(E=>E.id===y)??c[0],[y,c]),he=B?.executionId??"",re=B?.gatewayEndpoint??"",le=B?.connect_kind??"execution",ze=B?.messages.length??0,xe=B?.conn_status??"disconnected",se=B?.conn_error??"",te=U.useMemo(()=>le==="endpoint"&&re.trim()?`endpoint: ${re}`:he.trim()?`execution: ${he}`:"target: —",[le,he,re]),[pt,He]=U.useState(!1),[tt,oe]=U.useState(""),[nt,we]=U.useState(!1),[Ne,Me]=U.useState("execution"),[xt,mt]=U.useState([]),[Tt,Ge]=U.useState([]),[We,Fe]=U.useState([]),[Ye,Pe]=U.useState(""),[ce,N]=U.useState(""),[D,_]=U.useState(""),[f,w]=U.useState(""),[V,H]=U.useState(""),[G,K]=U.useState(""),[ue,Z]=U.useState(!1),[ae,$e]=U.useState(""),[cn,er]=U.useState(""),[Nn,_n]=U.useState(!1),[Kr,jn]=U.useState(!1),[tr,In]=U.useState(""),[nr,At]=U.useState(null),[Bt,Pn]=U.useState(null),[Gr,Yr]=U.useState(null),Vt=U.useCallback(E=>{const O=W.current.get(E);O&&(O.disconnect(),W.current.delete(E)),me.current.delete(E),P(Y=>Y.map(b=>b.id!==E?b:{...b,conn_status:"disconnected",conn_error:"",messages:b.messages.map(ne=>ne.streaming?{...ne,streaming:!1}:ne)}))},[]),rr=U.useCallback((E,O,Y,b)=>{b&&P(ne=>ne.map($=>{if($.id!==E)return $;const X=$.messages[$.messages.length-1];if(X&&X.role==="assistant"&&X.streaming)return{...$,messages:[...$.messages.slice(0,-1),{...X,text:X.text+b}]};const ke=O||Y?ff(O,Y):"",Ct=ke?`${ke}${b}`:b;return{...$,messages:[...$.messages,{id:$r(),role:"assistant",text:Ct,createdAt:Date.now(),streaming:!0}]}}))},[]);U.useEffect(()=>{hf(c)},[c]),U.useEffect(()=>{y&&yf(y)},[y]),U.useEffect(()=>{!y&&c.length>0&&R(c[0].id)},[y,c]),U.useEffect(()=>{localStorage.setItem(`${bn}.executionId`,he)},[he]),U.useEffect(()=>{q.current?.scrollIntoView({block:"end"})},[ze]);const Tn=U.useCallback(E=>{P(O=>O.map(Y=>Y.id===y?{...Y,...E}:Y))},[y]),Ht=U.useCallback(E=>{R(E)},[]),lr=U.useCallback(async E=>{oe(""),we(!0),He(!0);try{const O=await uf();mt(O);const Y=E?.taskId?.trim()??"",b=E?.instanceId?.trim()??"";if(Y){const ne=await Ha(Y);Ge(ne)}if(b){const ne=await Wa(b);Fe(ne)}}catch(O){oe(String(O instanceof Error?O.message:O))}finally{we(!1)}},[]),dn=U.useCallback(async E=>{Yr(E),_n(!0),In(""),jn(!0),At(null),Pn(null);try{const[O,Y]=await Promise.all([E.taskId?Qr(`/api/v1/tasks/${encodeURIComponent(E.taskId)}`):Promise.resolve(null),E.executionId?Qr(`/api/v1/executions/${encodeURIComponent(E.executionId)}`):Promise.resolve(null)]);At(O),Pn(Y)}catch(O){In(String(O instanceof Error?O.message:O))}finally{jn(!1)}},[]),Et=U.useCallback(async(E,O)=>{Vt(E),P($=>$.map(X=>X.id===E?{...X,conn_status:"connecting",conn_error:""}:X));let Y="",b;try{if(O.kind==="endpoint"){const $=O.gatewayEndpoint.trim();if(!$)throw new Error("no endpoint selected");Y=new URL(`/e/${encodeURIComponent($)}/ws`,window.location.origin).toString(),b="ssf.v1"}else{const $=O.executionId.trim();if(!$)throw new Error("no execution selected");Y=(await nf({executionId:$})).ws_url}}catch($){P(X=>X.map(ke=>ke.id===E?{...ke,conn_status:"disconnected",conn_error:String($ instanceof Error?$.message:$)}:ke));return}const ne=new tf({onOpen:()=>{W.current.get(E)===ne&&P($=>$.map(X=>X.id===E?{...X,conn_status:"connected",conn_error:""}:X))},onClose:()=>{P($=>$.map(X=>X.id!==E?X:{...X,conn_status:"disconnected",messages:X.messages.map(ke=>ke.streaming?{...ke,streaming:!1}:ke)}))},onError:()=>{W.current.get(E)===ne&&P($=>$.map(X=>X.id===E?{...X,conn_error:"websocket error"}:X))},onFrame:({streamId:$,msgType:X,data:ke,meta:Ct})=>{if(W.current.get(E)!==ne||$!==Hr)return;const Wt=Qa(ke),Zr=Qa(Ct).trim();rr(E,X,Zr,Wt)}});W.current.set(E,ne);try{await ne.connect(Y,b?{subprotocol:b,autoOpenStreamId:Hr}:{autoOpenStreamId:Hr})}catch($){W.current.get(E)===ne&&W.current.delete(E),P(X=>X.map(ke=>ke.id===E?{...ke,conn_status:"disconnected",conn_error:String($ instanceof Error?$.message:$)}:ke))}},[rr,Vt]);U.useEffect(()=>{if(!y||!B||B.conn_status!=="disconnected"||B.conn_error||W.current.get(y))return;const E=B.connect_kind==="endpoint"?B.gatewayEndpoint.trim()?{kind:"endpoint",gatewayEndpoint:B.gatewayEndpoint}:null:B.executionId.trim()?{kind:"execution",executionId:B.executionId}:null;if(!E)return;const O=E.kind==="endpoint"?`endpoint:${E.gatewayEndpoint.trim()}`:`execution:${E.executionId.trim()}`;me.current.get(y)!==O&&(me.current.set(y,O),Et(y,E))},[B,y,Et]);const Xr=U.useCallback(async()=>{const E=d.trim();if(!E)return;const O=y;if(!O)return;const Y=W.current.get(O);if(!Y?.isConnected()){P(b=>b.map(ne=>ne.id===O?{...ne,conn_error:"not connected"}:ne));return}g(""),P(b=>b.map(ne=>{if(ne.id!==O)return ne;const $={id:$r(),role:"user",text:E,createdAt:Date.now()},X={id:$r(),role:"assistant",text:"",createdAt:Date.now(),streaming:!0},ke=ne.title.trim()?ne.title:gf(E);return{...ne,title:ke,messages:[...ne.messages,$,X]}})),Y.sendText(Hr,E),Y.sendCommit(Hr)},[y,d]);return h.jsxs("div",{className:"cw-page",children:[h.jsxs("aside",{className:"cw-sidebar",children:[h.jsxs("div",{className:"cw-brand",children:[h.jsx("div",{className:"cw-brandTitle",children:lf}),h.jsx("button",{className:"cw-btn cw-btnPrimary",onClick:()=>{const E=pf();P(O=>[E,...O]),Ht(E.id),Me("execution"),w(""),Pe(""),N(""),_(""),H(""),K(""),lr()},children:"New chat"})]}),h.jsx("div",{className:"cw-list",children:c.map(E=>h.jsxs("button",{className:E.id===y?"cw-conv cw-convActive":"cw-conv",onClick:()=>Ht(E.id),children:[h.jsx("div",{className:"cw-convTitle",children:E.title.trim()?E.title:"(untitled)"}),h.jsx("div",{className:"cw-convMeta",children:E.connect_kind==="endpoint"&&E.gatewayEndpoint?`endpoint: ${E.gatewayEndpoint}`:E.executionId?`execution: ${E.executionId}`:"no target"}),h.jsxs("div",{className:"cw-convActions",children:[h.jsx("button",{className:"cw-iconBtn",onClick:O=>{O.preventDefault(),O.stopPropagation(),$e(E.id),er(E.title),Z(!0)},children:"Rename"}),h.jsx("button",{className:"cw-iconBtn cw-danger",onClick:O=>{if(O.preventDefault(),O.stopPropagation(),Vt(E.id),P(Y=>Y.filter(b=>b.id!==E.id)),y===E.id){const Y=c.find(b=>b.id!==E.id)?.id;Ht(Y??"")}},children:"Delete"})]})]},E.id))})]}),h.jsxs("main",{className:"cw-main",children:[h.jsxs("header",{className:"cw-header",children:[h.jsxs("div",{className:"cw-headerLeft",children:[h.jsx("div",{className:"cw-headerTitle",children:B?.title.trim()?B.title:"Chat"}),h.jsx("div",{className:"cw-headerMeta",children:te})]}),h.jsxs("div",{className:"cw-headerRight",children:[h.jsxs("div",{className:"cw-connChip",title:te,children:[h.jsx("span",{className:xe==="connected"?"cw-dot cw-dotOk":xe==="connecting"?"cw-dot cw-dotWarn":"cw-dot"}),h.jsxs("span",{className:"cw-connChipText",children:[xe," • ",te]})]}),se?h.jsx("div",{className:"cw-error cw-errorInline",children:se}):null,h.jsx("button",{className:"cw-btn",onClick:()=>{Me(B?.connect_kind??"execution"),w(""),Pe(B?.taskId??""),N(B?.instanceId??""),_(B?.executionId??""),H(B?.gatewayEndpoint??""),K(B?.taskId??""),lr({taskId:B?.taskId??"",instanceId:B?.instanceId??""})},children:"Connect…"}),h.jsx("button",{className:"cw-btn",onClick:()=>Vt(y),disabled:xe==="disconnected",children:"Disconnect"})]})]}),h.jsxs("div",{className:"cw-chat",children:[B?.messages.length?B.messages.map(E=>h.jsx(df,{message:E},E.id)):h.jsxs("div",{className:"cw-empty",children:[h.jsx("div",{className:"cw-emptyTitle",children:"Connect and start chatting"}),h.jsx("div",{className:"cw-emptyText",children:"Connect by execution (stream session) or by endpoint gateway. The chat window stays the same."})]}),h.jsx("div",{ref:q})]}),h.jsxs("div",{className:"cw-composer",children:[h.jsx("textarea",{className:"cw-textarea",value:d,onChange:E=>g(E.target.value),placeholder:xe==="connected"?"Message…":"Connect to send messages…",onKeyDown:E=>{E.key==="Enter"&&!E.shiftKey&&(E.preventDefault(),Xr())}}),h.jsxs("div",{className:"cw-composerActions",children:[h.jsx("button",{className:"cw-btn cw-btnPrimary",disabled:xe!=="connected"||!d.trim(),onClick:Xr,children:"Send"}),h.jsx("button",{className:"cw-btn",disabled:xe!=="connected",onClick:()=>Vt(y),children:"Stop"})]})]})]}),pt?h.jsx(sf,{tab:Ne,tasks:xt,instances:Tt,executions:We,selectedTaskId:Ye,selectedInstanceId:ce,selectedExecutionId:D,endpointSearch:f,selectedGatewayEndpoint:V,loading:nt,error:tt,status:xe,onChangeTab:Me,onClose:()=>{He(!1),oe("")},onOpenInfo:()=>{dn({taskId:Ne==="endpoint"?G.trim():Ye.trim(),instanceId:Ne==="endpoint"?"":ce.trim(),executionId:Ne==="endpoint"?"":D.trim()})},onChangeTask:E=>{Pe(E),N(""),_(""),Ge([]),Fe([]),E.trim()&&(oe(""),we(!0),Ha(E.trim()).then(O=>Ge(O)).catch(O=>oe(String(O instanceof Error?O.message:O))).finally(()=>we(!1)))},onChangeInstance:E=>{N(E),_(""),Fe([]),E.trim()&&(oe(""),we(!0),Wa(E.trim()).then(O=>Fe(O)).catch(O=>oe(String(O instanceof Error?O.message:O))).finally(()=>we(!1)))},onChangeExecution:_,onChangeEndpointSearch:w,onSelectEndpoint:E=>{H(E.gatewayEndpoint),K(E.taskId)},onConnect:async()=>{if(!y)return;if(Ne==="endpoint"){const b=V.trim();if(!b){oe("Please select an endpoint");return}Tn({connect_kind:"endpoint",taskId:G.trim(),instanceId:"",executionId:"",gatewayEndpoint:b}),He(!1),oe(""),await Et(y,{kind:"endpoint",gatewayEndpoint:b});return}const E=Ye.trim(),O=ce.trim(),Y=D.trim();if(!Y){oe("Please select an execution");return}Tn({connect_kind:"execution",taskId:E,instanceId:O,executionId:Y,gatewayEndpoint:""}),He(!1),oe(""),await Et(y,{kind:"execution",executionId:Y})}}):null,Nn?h.jsx(af,{ids:Gr,loading:Kr,error:tr,task:nr,execution:Bt,onClose:()=>{_n(!1),In(""),jn(!1)}}):null,ue?h.jsx(cf,{value:cn,onChange:er,onClose:()=>Z(!1),onConfirm:()=>{const E=cn.trim();ae&&(P(O=>O.map(Y=>Y.id===ae?{...Y,title:E}:Y)),Z(!1))}}):null]})}async function Qr(d){const g=new URL(d,window.location.origin),c=await fetch(g);if(!c.ok){const P=await c.text().catch(()=>"");throw new Error(`${c.status} ${P}`)}return await c.json()}async function uf(){return(await Qr("/api/v1/tasks")).tasks.map(g=>({task_id:g.task_id,name:g.name,endpoint:g.endpoint}))}async function Ha(d){return(await Qr(`/api/v1/tasks/${encodeURIComponent(d)}/instances?limit=100`)).instances.map(c=>({instance_id:c.instance_id,status:c.status,current_execution_id:c.current_execution_id}))}async function Wa(d){return(await Qr(`/api/v1/instances/${encodeURIComponent(d)}/executions?limit=100`)).executions}function sf(d){const g=U.useMemo(()=>{const y=d.endpointSearch.trim().toLowerCase();return d.tasks.map(R=>({taskId:R.task_id,taskName:R.name,gatewayEndpoint:(R.endpoint??"").trim()})).filter(R=>R.gatewayEndpoint.trim()).filter(R=>y?R.gatewayEndpoint.toLowerCase().includes(y)||R.taskName.toLowerCase().includes(y):!0).sort((R,W)=>R.gatewayEndpoint.localeCompare(W.gatewayEndpoint))},[d.endpointSearch,d.tasks]),c=d.tab==="endpoint"?d.selectedGatewayEndpoint.trim()?`/e/${d.selectedGatewayEndpoint.trim()}/ws`:"—":d.selectedExecutionId.trim()?`execution: ${d.selectedExecutionId.trim()}`:"—",P=!d.loading&&d.status!=="connecting"&&(d.tab==="endpoint"?!!d.selectedGatewayEndpoint.trim():!!d.selectedExecutionId.trim());return h.jsx("div",{className:"cw-drawerBackdrop",role:"dialog","aria-modal":"true",onClick:()=>d.onClose(),children:h.jsxs("div",{className:"cw-drawer",onClick:y=>{y.stopPropagation()},children:[h.jsxs("div",{className:"cw-drawerHeader",children:[h.jsx("div",{className:"cw-drawerTitle",children:"Connect"}),h.jsxs("div",{className:"cw-drawerHeaderRight",children:[h.jsx("button",{className:"cw-iconBtn",onClick:d.onOpenInfo,disabled:d.tab==="execution"?!d.selectedExecutionId.trim():!1,children:"Info"}),h.jsx("button",{className:"cw-iconBtn",onClick:d.onClose,children:"Close"})]})]}),h.jsxs("div",{className:"cw-drawerBody",children:[h.jsxs("div",{className:"cw-tabs",children:[h.jsx("button",{className:d.tab==="execution"?"cw-tab cw-tabActive":"cw-tab",onClick:()=>d.onChangeTab("execution"),children:"By Execution"}),h.jsx("button",{className:d.tab==="endpoint"?"cw-tab cw-tabActive":"cw-tab",onClick:()=>d.onChangeTab("endpoint"),children:"By Endpoint"})]}),d.tab==="execution"?h.jsxs("div",{className:"cw-drawerSection",children:[h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Task"}),h.jsxs("select",{className:"cw-select",value:d.selectedTaskId,onChange:y=>d.onChangeTask(y.target.value),children:[h.jsx("option",{value:"",children:"Select a task…"}),d.tasks.map(y=>h.jsxs("option",{value:y.task_id,children:[y.name," (",y.task_id,")"]},y.task_id))]})]}),h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Instance"}),h.jsxs("select",{className:"cw-select",value:d.selectedInstanceId,onChange:y=>d.onChangeInstance(y.target.value),disabled:!d.selectedTaskId,children:[h.jsx("option",{value:"",children:"Select an instance…"}),d.instances.map(y=>h.jsxs("option",{value:y.instance_id,children:[y.instance_id," (",y.status,")"]},y.instance_id))]})]}),h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Execution"}),h.jsxs("select",{className:"cw-select",value:d.selectedExecutionId,onChange:y=>d.onChangeExecution(y.target.value),disabled:!d.selectedInstanceId,children:[h.jsx("option",{value:"",children:"Select an execution…"}),d.executions.map(y=>h.jsxs("option",{value:y.execution_id,children:[y.execution_id," (",y.status,") ",y.function_name?`- ${y.function_name}`:""]},y.execution_id))]})]})]}):h.jsxs("div",{className:"cw-drawerSection",children:[h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Endpoint"}),h.jsx("input",{className:"cw-textInput",value:d.endpointSearch,onChange:y=>d.onChangeEndpointSearch(y.target.value),placeholder:"Search gateway_endpoint…"})]}),h.jsx("div",{className:"cw-endpointList",role:"list",children:g.length?g.map(y=>h.jsxs("button",{className:y.gatewayEndpoint===d.selectedGatewayEndpoint?"cw-endpointItem cw-endpointItemActive":"cw-endpointItem",onClick:()=>d.onSelectEndpoint({taskId:y.taskId,gatewayEndpoint:y.gatewayEndpoint}),role:"listitem",children:[h.jsxs("div",{className:"cw-endpointLeft",children:[h.jsx("div",{className:"cw-endpointName",children:y.gatewayEndpoint}),h.jsx("div",{className:"cw-endpointMeta",children:y.taskName})]}),h.jsx("div",{className:"cw-endpointRight",children:y.gatewayEndpoint===d.selectedGatewayEndpoint?"Selected":""})]},`${y.taskId}:${y.gatewayEndpoint}`)):h.jsx("div",{className:"cw-modalHint",children:"No endpoints found."})})]}),h.jsxs("div",{className:"cw-drawerSection",children:[h.jsx("div",{className:"cw-label",children:"Connection details"}),h.jsxs("div",{className:"cw-connDetails",children:[h.jsxs("div",{className:"cw-connDetailsRow",children:[h.jsx("div",{className:"cw-connDetailsKey",children:"Target"}),h.jsx("div",{className:"cw-connDetailsVal",children:c})]}),h.jsxs("div",{className:"cw-connDetailsRow",children:[h.jsx("div",{className:"cw-connDetailsKey",children:"Protocol"}),h.jsx("div",{className:"cw-connDetailsVal",children:d.tab==="endpoint"?"ssf.v1 (binary WS)":"stream session (binary WS)"})]})]})]}),d.loading?h.jsx("div",{className:"cw-modalHint",children:"Loading…"}):null,d.error?h.jsx("div",{className:"cw-error",children:d.error}):null]}),h.jsxs("div",{className:"cw-drawerFooter",children:[h.jsx("button",{className:"cw-btn",onClick:d.onClose,children:"Cancel"}),h.jsx("button",{className:"cw-btn cw-btnPrimary",onClick:d.onConnect,disabled:!P,children:"Connect"})]})]})})}function af(d){const g=d.ids??{taskId:"",instanceId:"",executionId:""},c=U.useMemo(()=>$a(d.task),[d.task]),P=U.useMemo(()=>$a(d.execution),[d.execution]);return h.jsx("div",{className:"cw-modalBackdrop",role:"dialog","aria-modal":"true",children:h.jsxs("div",{className:"cw-modal",children:[h.jsxs("div",{className:"cw-modalHeader",children:[h.jsx("div",{className:"cw-modalTitle",children:"Execution info"}),h.jsx("div",{className:"cw-modalHeaderRight",children:h.jsx("button",{className:"cw-iconBtn",onClick:d.onClose,children:"Close"})})]}),h.jsxs("div",{className:"cw-modalBody",children:[h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Task"}),h.jsx("div",{className:"cw-readonly",children:g.taskId||"n/a"})]}),h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Instance"}),h.jsx("div",{className:"cw-readonly",children:g.instanceId||"n/a"})]}),h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Execution"}),h.jsx("div",{className:"cw-readonly",children:g.executionId||"n/a"})]}),d.loading?h.jsx("div",{className:"cw-modalHint",children:"Loading…"}):null,d.error?h.jsx("div",{className:"cw-error",children:d.error}):null,h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Task detail"}),h.jsxs("div",{className:"cw-codeWrap",children:[h.jsxs("div",{className:"cw-codeHeader",children:[h.jsx("div",{className:"cw-codeLang",children:"json"}),h.jsx("button",{className:"cw-codeBtn",onClick:()=>navigator.clipboard?.writeText(c).catch(()=>{}),children:"Copy"})]}),h.jsx("pre",{className:"cw-code",children:h.jsx("code",{children:c})})]})]}),h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Execution detail"}),h.jsxs("div",{className:"cw-codeWrap",children:[h.jsxs("div",{className:"cw-codeHeader",children:[h.jsx("div",{className:"cw-codeLang",children:"json"}),h.jsx("button",{className:"cw-codeBtn",onClick:()=>navigator.clipboard?.writeText(P).catch(()=>{}),children:"Copy"})]}),h.jsx("pre",{className:"cw-code",children:h.jsx("code",{children:P})})]})]})]}),h.jsx("div",{className:"cw-modalFooter",children:h.jsx("button",{className:"cw-btn cw-btnPrimary",onClick:d.onClose,children:"Done"})})]})})}function $a(d){if(d==null)return"null";try{return JSON.stringify(d,null,2)}catch{return String(d)}}function cf(d){return h.jsx("div",{className:"cw-modalBackdrop",role:"dialog","aria-modal":"true",children:h.jsxs("div",{className:"cw-modal",children:[h.jsxs("div",{className:"cw-modalHeader",children:[h.jsx("div",{className:"cw-modalTitle",children:"Rename chat"}),h.jsx("div",{className:"cw-modalHeaderRight",children:h.jsx("button",{className:"cw-iconBtn",onClick:d.onClose,children:"Close"})})]}),h.jsx("div",{className:"cw-modalBody",children:h.jsxs("div",{className:"cw-modalRow",children:[h.jsx("div",{className:"cw-label",children:"Title"}),h.jsx("input",{className:"cw-textInput",value:d.value,onChange:g=>d.onChange(g.target.value),autoFocus:!0,placeholder:"Chat title",onKeyDown:g=>{g.key==="Enter"&&(g.preventDefault(),d.onConfirm())}})]})}),h.jsxs("div",{className:"cw-modalFooter",children:[h.jsx("button",{className:"cw-btn",onClick:d.onClose,children:"Cancel"}),h.jsx("button",{className:"cw-btn cw-btnPrimary",onClick:d.onConfirm,disabled:!d.value.trim(),children:"Save"})]})]})})}function df(d){const{message:g}=d,c=g.role==="user",P=U.useMemo(()=>wf(g.text),[g.text]);return h.jsx("div",{className:c?"cw-row cw-rowUser":"cw-row",children:h.jsx("div",{className:c?"cw-bubble cw-bubbleUser":"cw-bubble",children:P.map((y,R)=>y.type==="code"?h.jsxs("div",{className:"cw-codeWrap",children:[h.jsxs("div",{className:"cw-codeHeader",children:[h.jsx("div",{className:"cw-codeLang",children:y.lang??""}),h.jsx("button",{className:"cw-codeBtn",onClick:()=>navigator.clipboard?.writeText(y.code).catch(()=>{}),children:"Copy"})]}),h.jsx("pre",{className:"cw-code",children:h.jsx("code",{children:y.code})})]},R):h.jsx("div",{className:"cw-text",children:y.text},R))})})}function Qa(d){try{return new TextDecoder().decode(d)}catch{return""}}function ff(d,g){return!g&&!d?"":g?`[type=${d} meta=${g}] `:`[type=${d}] `}function pf(d){return{id:$r(),title:"",connect_kind:"execution",taskId:"",instanceId:"",executionId:localStorage.getItem(`${bn}.executionId`)??"",gatewayEndpoint:"",conn_status:"disconnected",conn_error:"",createdAt:Date.now(),messages:[]}}function mf(){const d=localStorage.getItem(`${bn}.conversations`);if(!d)return[];try{const g=JSON.parse(d);return!Array.isArray(g)||!g.length?[]:g.map(c=>({id:c.id??$r(),title:c.title??"",connect_kind:c.connect_kind==="endpoint"||c.connect_kind==="execution"?c.connect_kind:c.executionId?"execution":c.gatewayEndpoint?"endpoint":"execution",taskId:c.taskId??"",instanceId:c.instanceId??"",executionId:c.executionId??"",gatewayEndpoint:c.gatewayEndpoint??"",conn_status:"disconnected",conn_error:"",createdAt:c.createdAt??Date.now(),messages:Array.isArray(c.messages)?c.messages:[]}))}catch{return[]}}function hf(d){localStorage.setItem(`${bn}.conversations`,JSON.stringify(d))}function vf(){return localStorage.getItem(`${bn}.activeId`)}function yf(d){localStorage.setItem(`${bn}.activeId`,d)}function gf(d){const g=d.replace(/\s+/g," ").trim();return g?g.length<=40?g:g.slice(0,40)+"…":""}function $r(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function wf(d){const g=[],c=/```([a-zA-Z0-9_-]+)?\n([\s\S]*?)```/g;let P=0;for(;;){const y=c.exec(d);if(!y)break;const R=y.index;R>P&&g.push({type:"text",text:d.slice(P,R)}),g.push({type:"code",lang:y[1],code:y[2]}),P=R+y[0].length}return P/spear-sms: . docker build -f deploy/docker/spearlet/Dockerfile -t /spear-spearlet: . ``` +Local path dependency note: + +- `spear-next` depends on `spear-ssf` via a local `path` dependency (`sdk/rust/crates/spear-ssf`). +- The Dockerfiles explicitly copy this SDK crate into the build stage. If you add more local `path` dependencies, update the Dockerfiles accordingly. + Cargo registry note: - The Dockerfiles use a Cargo registry mirror by default (`rsproxy.cn`) to improve reliability in some network environments. diff --git a/docs/helm-deployment-zh.md b/docs/helm-deployment-zh.md index 3fd5ab3f..53198813 100644 --- a/docs/helm-deployment-zh.md +++ b/docs/helm-deployment-zh.md @@ -46,6 +46,11 @@ docker build -f deploy/docker/sms/Dockerfile -t /spear-sms: . docker build -f deploy/docker/spearlet/Dockerfile -t /spear-spearlet: . ``` +本地 path 依赖说明: + +- `spear-next` 通过本地 `path` 依赖引用了 `spear-ssf`(`sdk/rust/crates/spear-ssf`)。 +- 这些 Dockerfile 在构建阶段会显式拷贝该 SDK crate;如果你新增了更多本地 `path` 依赖,需要同步更新 Dockerfile。 + Cargo registry 说明: - 这些 Dockerfile 默认使用 Cargo 镜像源(`rsproxy.cn`),主要是为了在某些网络环境下提升依赖下载的稳定性。 diff --git a/docs/samples-build-guide-en.md b/docs/samples-build-guide-en.md index d36216ae..1dedd719 100644 --- a/docs/samples-build-guide-en.md +++ b/docs/samples-build-guide-en.md @@ -5,6 +5,9 @@ - Source: `samples/wasm-c/chat_completion.c` (Chat Completions sample) - Source: `samples/wasm-js/chat_completion/src/main.rs` (Boa JS runner compiled to WASM; runs `entry.mjs` → Chat Completion) - Source: `samples/wasm-js/chat_completion_tool_sum/src/main.rs` (Boa JS runner compiled to WASM; runs `entry.mjs` → Tool calling) +- Source: `samples/wasm-js/router_filter_keyword/src/main.rs` (Boa JS runner compiled to WASM; runs `entry.mjs` → Router keyword filter) +- Source: `samples/wasm-js/user_stream_echo/src/main.rs` (Boa JS runner compiled to WASM; runs `entry.mjs` → bidirectional user stream echo) +- Source: `samples/wasm-js/user_stream_chat_completion/src/main.rs` (Boa JS runner compiled to WASM; runs `entry.mjs` → user input over user stream → Chat Completion) - Source: `samples/wasm-c/mic_rtasr.c` (realtime mic → realtime ASR) - Output: `samples/build/hello.wasm` - - WASM-JS outputs: `samples/build/js/js-*.wasm` @@ -40,7 +43,7 @@ WASM-JS samples: - Built by `cargo build --release --target wasm32-wasip1` - Controlled by Makefile vars: - `BUILD_JS_SAMPLES=0` to skip WASM-JS samples (compat: `BUILD_RUST_SAMPLES=0`) - - `JS_SAMPLES="chat_completion chat_completion_tool_sum"` to select which samples to build (compat: `RUST_SAMPLES=...`) + - `JS_SAMPLES="chat_completion chat_completion_tool_sum router_filter_keyword user_stream_echo user_stream_chat_completion"` to select which samples to build (compat: `RUST_SAMPLES=...`) - `JS_WASM_PREFIX="js-"` to set the WASM-JS output filename prefix (default `js-`) ## clang usage diff --git a/docs/samples-build-guide-zh.md b/docs/samples-build-guide-zh.md index f9e42cb1..8e2d1afc 100644 --- a/docs/samples-build-guide-zh.md +++ b/docs/samples-build-guide-zh.md @@ -6,6 +6,9 @@ - 源码:`samples/wasm-c/mic_rtasr.c`(实时麦克风→实时ASR示例) - 源码:`samples/wasm-js/chat_completion/src/main.rs`(Boa JS runner 编译为 WASM;内部执行 `entry.mjs` → Chat Completion) - 源码:`samples/wasm-js/chat_completion_tool_sum/src/main.rs`(Boa JS runner 编译为 WASM;内部执行 `entry.mjs` → tool calling) +- 源码:`samples/wasm-js/router_filter_keyword/src/main.rs`(Boa JS runner 编译为 WASM;内部执行 `entry.mjs` → Router 关键词过滤) +- 源码:`samples/wasm-js/user_stream_echo/src/main.rs`(Boa JS runner 编译为 WASM;内部执行 `entry.mjs` → 双向 user stream echo) +- 源码:`samples/wasm-js/user_stream_chat_completion/src/main.rs`(Boa JS runner 编译为 WASM;内部执行 `entry.mjs` → user stream 用户输入 → Chat Completion) - 产物:`samples/build/hello.wasm` - WASM-JS 产物:`samples/build/js/js-*.wasm`(兼容:`samples/build/rust/*.wasm`) @@ -40,7 +43,7 @@ WASM-JS 示例: - 通过 `cargo build --release --target wasm32-wasip1` 构建 - 可通过 Makefile 变量控制: - `BUILD_JS_SAMPLES=0` 跳过 WASM-JS 示例构建(兼容:`BUILD_RUST_SAMPLES=0`) - - `JS_SAMPLES="chat_completion chat_completion_tool_sum"` 指定要构建的示例列表(兼容:`RUST_SAMPLES=...`) + - `JS_SAMPLES="chat_completion chat_completion_tool_sum router_filter_keyword user_stream_echo user_stream_chat_completion"` 指定要构建的示例列表(兼容:`RUST_SAMPLES=...`) - `JS_WASM_PREFIX="js-"` 配置 JS 产物文件名前缀(默认 `js-`) ## clang 使用说明 diff --git a/docs/spear-console-overview-en.md b/docs/spear-console-overview-en.md index 45c4053d..f8063d4f 100644 --- a/docs/spear-console-overview-en.md +++ b/docs/spear-console-overview-en.md @@ -36,7 +36,9 @@ SPEAR Console uses the existing execution stream protocol: 1) User selects a Task -> Instance -> Execution in the “Start a chat” dialog. 2) Console calls `POST /api/v1/executions/{execution_id}/streams/session` (same-origin) to obtain `ws_url`. -3) Console connects to `ws_url` via WebSocket and exchanges SSF frames (stream id `1`, text frames use `msgType=2`). +3) Console connects to `ws_url` via WebSocket and exchanges SSF frames. + - On `open`, Console sends an initial SSF v1 CTRL frame (`msgType=1`, empty data) to establish the stream (`stream_id=1`) without waiting for the first user input. + - User input is sent as one or more DATA frames (`msgType=2`), followed by a COMMIT frame (`msgType=3`) to mark the end of the input segment. ### Multi-Client Concurrency (Same Execution) diff --git a/docs/spear-console-overview-zh.md b/docs/spear-console-overview-zh.md index e5a0e3fd..bf42b363 100644 --- a/docs/spear-console-overview-zh.md +++ b/docs/spear-console-overview-zh.md @@ -36,7 +36,9 @@ SPEAR Console 复用当前 execution stream 协议: 1) 在 “Start a chat” 弹窗中选择 Task -> Instance -> Execution。 2) Console 以同域方式调用 `POST /api/v1/executions/{execution_id}/streams/session` 获取 `ws_url`。 -3) Console 通过 WebSocket 连接 `ws_url`,并使用 SSF frame 交互(stream id `1`,文本帧 `msgType=2`)。 +3) Console 通过 WebSocket 连接 `ws_url`,并使用 SSF frame 交互。 + - 连接建立(`open`)后,Console 会先发送一个初始 SSF v1 CTRL 帧(`msgType=1`,空 data),用于立刻建立 `stream_id=1`,避免等待用户首次输入。 + - 用户输入会先发送一条或多条 DATA 帧(`msgType=2`),并在输入完成后发送 COMMIT 帧(`msgType=3`)标记该段输入结束。 ### 多客户端并发(同一 execution) diff --git a/examples/kv_factory_usage.rs b/examples/kv_factory_usage.rs index 0671793f..f04c7e6a 100644 --- a/examples/kv_factory_usage.rs +++ b/examples/kv_factory_usage.rs @@ -2,8 +2,8 @@ // KV存储工厂模式使用示例 use spear_next::storage::{ - create_kv_store_from_config, create_kv_store_from_env, get_kv_store_factory, - set_kv_store_factory, DefaultKvStoreFactory, KvStore, KvStoreConfig, KvStoreFactory, + create_kv_store_from_config, create_kv_store_from_env, DefaultKvStoreFactory, KvStore, + KvStoreConfig, KvStoreFactory, }; use std::env; diff --git a/samples/README-en.md b/samples/README-en.md index 227f1cdc..8e786615 100644 --- a/samples/README-en.md +++ b/samples/README-en.md @@ -31,9 +31,15 @@ WASM-JS samples are built with `cargo` for `wasm32-wasip1` (primary output: `bui ## JS samples (Boa JS runner compiled to WASM) - `wasm-js/chat_completion`: executes `entry.mjs` via Boa JS runtime and calls Chat Completion - - Output: `./build/js/chat_completion.wasm` + - Output: `./build/js/js-chat_completion.wasm` - `wasm-js/chat_completion_tool_sum`: executes `entry.mjs` via Boa JS runtime for tool calling (sum) - - Output: `./build/js/chat_completion_tool_sum.wasm` + - Output: `./build/js/js-chat_completion_tool_sum.wasm` +- `wasm-js/router_filter_keyword`: router keyword filter sample + - Output: `./build/js/js-router_filter_keyword.wasm` +- `wasm-js/user_stream_echo`: bidirectional user stream echo sample + - Output: `./build/js/js-user_stream_echo.wasm` +- `wasm-js/user_stream_chat_completion`: interactive user input → chat completion → output back to user stream + - Output: `./build/js/js-user_stream_chat_completion.wasm` ## MCP sample (mcp_fs) diff --git a/samples/README-zh.md b/samples/README-zh.md index 0114eb8b..e0a35bd4 100644 --- a/samples/README-zh.md +++ b/samples/README-zh.md @@ -31,9 +31,15 @@ WASM-JS 示例通过 `cargo build --release --target wasm32-wasip1` 构建,主 ## JS 示例列表(Boa JS runner 编译为 WASM) - `wasm-js/chat_completion`:通过 Boa JS 运行时执行 `entry.mjs`,调用 Chat Completion - - 产物:`./build/js/chat_completion.wasm` + - 产物:`./build/js/js-chat_completion.wasm` - `wasm-js/chat_completion_tool_sum`:通过 Boa JS 运行时执行 `entry.mjs`,进行 tool calling(sum) - - 产物:`./build/js/chat_completion_tool_sum.wasm` + - 产物:`./build/js/js-chat_completion_tool_sum.wasm` +- `wasm-js/router_filter_keyword`:Router 关键词过滤示例 + - 产物:`./build/js/js-router_filter_keyword.wasm` +- `wasm-js/user_stream_echo`:双向 user stream echo 示例 + - 产物:`./build/js/js-user_stream_echo.wasm` +- `wasm-js/user_stream_chat_completion`:交互式用户输入 → Chat Completion → 输出回 user stream + - 产物:`./build/js/js-user_stream_chat_completion.wasm` ## MCP 示例(mcp_fs) diff --git a/samples/build/chat_completion.wasm b/samples/build/chat_completion.wasm index 03d452e4..259bece0 100755 Binary files a/samples/build/chat_completion.wasm and b/samples/build/chat_completion.wasm differ diff --git a/samples/build/chat_completion_tool_sum.wasm b/samples/build/chat_completion_tool_sum.wasm index bc521263..259c096a 100755 Binary files a/samples/build/chat_completion_tool_sum.wasm and b/samples/build/chat_completion_tool_sum.wasm differ diff --git a/samples/build/hello.wasm b/samples/build/hello.wasm index b95b8a7a..25b235c7 100755 Binary files a/samples/build/hello.wasm and b/samples/build/hello.wasm differ diff --git a/samples/build/js/js-chat_completion.wasm b/samples/build/js/js-chat_completion.wasm index f62d24c9..a114ae8e 100755 Binary files a/samples/build/js/js-chat_completion.wasm and b/samples/build/js/js-chat_completion.wasm differ diff --git a/samples/build/js/js-chat_completion_tool_sum.wasm b/samples/build/js/js-chat_completion_tool_sum.wasm index 353fdf6b..f154f0b5 100755 Binary files a/samples/build/js/js-chat_completion_tool_sum.wasm and b/samples/build/js/js-chat_completion_tool_sum.wasm differ diff --git a/samples/build/js/js-router_filter_keyword.wasm b/samples/build/js/js-router_filter_keyword.wasm index 248a6352..fcde6ff2 100755 Binary files a/samples/build/js/js-router_filter_keyword.wasm and b/samples/build/js/js-router_filter_keyword.wasm differ diff --git a/samples/build/js/js-user_stream_chat_completion.wasm b/samples/build/js/js-user_stream_chat_completion.wasm new file mode 100755 index 00000000..f57d0f86 Binary files /dev/null and b/samples/build/js/js-user_stream_chat_completion.wasm differ diff --git a/samples/build/js/js-user_stream_echo.wasm b/samples/build/js/js-user_stream_echo.wasm index 5b4d4c12..adec42bf 100755 Binary files a/samples/build/js/js-user_stream_echo.wasm and b/samples/build/js/js-user_stream_echo.wasm differ diff --git a/samples/build/mcp_fs.wasm b/samples/build/mcp_fs.wasm index 40210d56..0cb8e160 100755 Binary files a/samples/build/mcp_fs.wasm and b/samples/build/mcp_fs.wasm differ diff --git a/samples/build/mic_rtasr.wasm b/samples/build/mic_rtasr.wasm index 66522560..443e7c18 100755 Binary files a/samples/build/mic_rtasr.wasm and b/samples/build/mic_rtasr.wasm differ diff --git a/samples/build/user_stream_echo.wasm b/samples/build/user_stream_echo.wasm index a34942e8..a58f1506 100755 Binary files a/samples/build/user_stream_echo.wasm and b/samples/build/user_stream_echo.wasm differ diff --git a/samples/wasm-js/README.md b/samples/wasm-js/README-en.md similarity index 75% rename from samples/wasm-js/README.md rename to samples/wasm-js/README-en.md index 40711c86..1e2a2933 100644 --- a/samples/wasm-js/README.md +++ b/samples/wasm-js/README-en.md @@ -12,3 +12,4 @@ So the focus here is JS, even though the runner itself is written in Rust. - `chat_completion_tool_sum`: Tool calling via `Spear.tool(...)`. - `router_filter_keyword`: Router filter sample. - `user_stream_echo`: Bidirectional stream echo via `Spear.userStream` (JS). +- `user_stream_chat_completion`: Interactive user input over `Spear.userStream` → Chat Completion → write back to user stream (supports `/model `). Uses SSF v1 DATA+COMMIT input semantics. diff --git a/samples/wasm-js/README-zh.md b/samples/wasm-js/README-zh.md new file mode 100644 index 00000000..d1be156a --- /dev/null +++ b/samples/wasm-js/README-zh.md @@ -0,0 +1,15 @@ +# wasm-js 示例 + +这些示例以 JS 为主:你编写 JavaScript(例如 `src/entry.mjs`),并在 SPEAR 中以 WASM 可执行的形式运行。 + +底层实现是一个很小的 Rust “Boa JS runner”,它会被编译为 WASM(`wasm32-wasip1`),并在运行时嵌入/加载 JS 入口。 + +因此这里的重点是 JS(尽管 runner 本身是 Rust 写的)。 + +## 示例列表 + +- `chat_completion`:通过 `Spear.chat.completions.create` 调用 Chat Completion。 +- `chat_completion_tool_sum`:通过 `Spear.tool(...)` 做 tool calling。 +- `router_filter_keyword`:Router 关键词过滤示例。 +- `user_stream_echo`:通过 `Spear.userStream` 实现双向 stream echo(JS)。 +- `user_stream_chat_completion`:基于 `Spear.userStream` 的交互式用户输入 → Chat Completion → 输出回 user stream(支持 `/model `)。使用 SSF v1 DATA+COMMIT 的输入语义。 diff --git a/samples/wasm-js/chat_completion/Cargo.lock b/samples/wasm-js/chat_completion/Cargo.lock index 98138090..1e9bb620 100644 --- a/samples/wasm-js/chat_completion/Cargo.lock +++ b/samples/wasm-js/chat_completion/Cargo.lock @@ -832,15 +832,21 @@ dependencies = [ "libc", "seq-macro", "serde_json", + "spear-ssf", "spear-wasm", "thiserror", ] +[[package]] +name = "spear-ssf" +version = "0.1.0" + [[package]] name = "spear-wasm" version = "0.1.0" dependencies = [ "libc", + "spear-ssf", "spear-wasm-sys", "thiserror", ] diff --git a/samples/wasm-js/chat_completion_tool_sum/Cargo.lock b/samples/wasm-js/chat_completion_tool_sum/Cargo.lock index c3dbb66d..7a66708f 100644 --- a/samples/wasm-js/chat_completion_tool_sum/Cargo.lock +++ b/samples/wasm-js/chat_completion_tool_sum/Cargo.lock @@ -832,15 +832,21 @@ dependencies = [ "libc", "seq-macro", "serde_json", + "spear-ssf", "spear-wasm", "thiserror", ] +[[package]] +name = "spear-ssf" +version = "0.1.0" + [[package]] name = "spear-wasm" version = "0.1.0" dependencies = [ "libc", + "spear-ssf", "spear-wasm-sys", "thiserror", ] diff --git a/samples/wasm-js/router_filter_keyword/Cargo.lock b/samples/wasm-js/router_filter_keyword/Cargo.lock index b9d418ed..b1feed83 100644 --- a/samples/wasm-js/router_filter_keyword/Cargo.lock +++ b/samples/wasm-js/router_filter_keyword/Cargo.lock @@ -832,15 +832,21 @@ dependencies = [ "libc", "seq-macro", "serde_json", + "spear-ssf", "spear-wasm", "thiserror", ] +[[package]] +name = "spear-ssf" +version = "0.1.0" + [[package]] name = "spear-wasm" version = "0.1.0" dependencies = [ "libc", + "spear-ssf", "spear-wasm-sys", "thiserror", ] diff --git a/samples/wasm-js/user_stream_chat_completion/Cargo.lock b/samples/wasm-js/user_stream_chat_completion/Cargo.lock new file mode 100644 index 00000000..10bce739 --- /dev/null +++ b/samples/wasm-js/user_stream_chat_completion/Cargo.lock @@ -0,0 +1,1241 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "boa_ast" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69ee3a749ea36d4e56d92941e7b25076b493d4917c3d155b6cf369e23547d9" +dependencies = [ + "bitflags", + "boa_interner", + "boa_macros", + "indexmap", + "num-bigint", + "rustc-hash", +] + +[[package]] +name = "boa_engine" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4559b35b80ceb2e6328481c0eca9a24506663ea33ee1e279be6b5b618b25c" +dependencies = [ + "arrayvec", + "bitflags", + "boa_ast", + "boa_gc", + "boa_interner", + "boa_macros", + "boa_parser", + "boa_profiler", + "boa_string", + "bytemuck", + "cfg-if", + "dashmap", + "fast-float", + "hashbrown 0.14.5", + "icu_normalizer", + "indexmap", + "intrusive-collections", + "itertools", + "num-bigint", + "num-integer", + "num-traits", + "num_enum", + "once_cell", + "pollster", + "portable-atomic", + "rand", + "regress", + "rustc-hash", + "ryu-js", + "serde", + "serde_json", + "sptr", + "static_assertions", + "tap", + "thin-vec", + "thiserror", + "time", +] + +[[package]] +name = "boa_gc" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "716406f57d67bc3ac7fd227d5513b42df401dff14a3be22cbd8ee29817225363" +dependencies = [ + "boa_macros", + "boa_profiler", + "boa_string", + "hashbrown 0.14.5", + "thin-vec", +] + +[[package]] +name = "boa_interner" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e18df2272616e1ba0322a69333d37dbb78797f1aa0595aad9dc41e8ecd06ad9" +dependencies = [ + "boa_gc", + "boa_macros", + "hashbrown 0.14.5", + "indexmap", + "once_cell", + "phf", + "rustc-hash", + "static_assertions", +] + +[[package]] +name = "boa_macros" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240f4126219a83519bad05c9a40bfc0303921eeb571fc2d7e44c17ffac99d3f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "boa_parser" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b59dc05bf1dc019b11478a92986f590cff43fced4d20e866eefb913493e91c" +dependencies = [ + "bitflags", + "boa_ast", + "boa_interner", + "boa_macros", + "boa_profiler", + "fast-float", + "icu_properties", + "num-bigint", + "num-traits", + "regress", + "rustc-hash", +] + +[[package]] +name = "boa_profiler" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00ee0645509b3b91abd724f25072649d9e8e65653a78ff0b6e592788a58dd838" + +[[package]] +name = "boa_string" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae85205289bab1f2c7c8a30ddf0541cf89ba2ff7dbd144feef50bbfa664288d4" +dependencies = [ + "fast-float", + "paste", + "rustc-hash", + "sptr", + "static_assertions", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fast-float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regress" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2057b2325e68a893284d1538021ab90279adac1139957ca2a74426c6f118fb48" +dependencies = [ + "hashbrown 0.16.1", + "memchr", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu-js" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "spear-boa" +version = "0.1.0" +dependencies = [ + "boa_engine", + "boa_parser", + "libc", + "seq-macro", + "serde_json", + "spear-ssf", + "spear-wasm", + "thiserror", +] + +[[package]] +name = "spear-ssf" +version = "0.1.0" + +[[package]] +name = "spear-wasm" +version = "0.1.0" +dependencies = [ + "libc", + "spear-ssf", + "spear-wasm-sys", + "thiserror", +] + +[[package]] +name = "spear-wasm-sys" +version = "0.1.0" + +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thin-vec" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "js-sys", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "user_stream_chat_completion" +version = "0.1.0" +dependencies = [ + "boa_engine", + "spear-boa", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/samples/wasm-js/user_stream_chat_completion/Cargo.toml b/samples/wasm-js/user_stream_chat_completion/Cargo.toml new file mode 100644 index 00000000..41432e0a --- /dev/null +++ b/samples/wasm-js/user_stream_chat_completion/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "user_stream_chat_completion" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "user_stream_chat_completion" +path = "src/main.rs" + +[dependencies] +boa_engine = "0.19" +spear-boa = { path = "../../../sdk/rust/crates/spear-boa" } diff --git a/samples/wasm-js/user_stream_chat_completion/src/entry.mjs b/samples/wasm-js/user_stream_chat_completion/src/entry.mjs new file mode 100644 index 00000000..d1242c0e --- /dev/null +++ b/samples/wasm-js/user_stream_chat_completion/src/entry.mjs @@ -0,0 +1,116 @@ +// Interactive chat over userStream: read user input, call Chat Completion, write model output. +// 基于 userStream 的交互式对话:读取用户输入,调用 Chat Completion,并将模型输出写回用户。 + +import { Spear } from "spear"; + +function formatHelp(currentModel) { + const m = currentModel ? String(currentModel) : "(default)"; + return [ + "Connected.", + "Commands:", + " /model Set model (current: " + m + ")", + " /model Show current model", + " /exit Quit", + "Send:", + " Type your prompt and press Send/Enter.", + "", + ].join("\n"); +} + +function normalizeInput(text) { + return String(text).replace(/\r/g, "").trim(); +} + +function tryWrite(stream, text) { + stream.sendText(String(text)); +} + +async function runChat({ model, prompt }) { + const options = { + messages: [{ role: "user", content: prompt }], + timeoutMs: 30_000, + }; + if (typeof model === "string" && model.trim().length > 0) { + options.model = model.trim(); + } + const resp = await Spear.chat.completions.create({ + ...options, + }); + return resp.text(); +} + +export default async function main() { + const ctl = Spear.userStream.ctlOpen(); + + let stream = null; + let pending = ""; + let model = null; + let loop = 0; + + try { + for (;;) { + loop++; + let didWork = false; + + const evt = ctl.readEvent(); + if (evt && typeof evt.kind === "number") { + didWork = true; + + if (evt.kind === 1 && stream == null && typeof evt.streamId === "number") { + stream = Spear.userStream.open(evt.streamId, Spear.userStream.Direction.BIDIRECTIONAL); + tryWrite(stream, formatHelp(model)); + } else if (evt.kind === 2) { + break; + } + } + + if (stream) { + const msg = stream.readMessage(); + if (msg) { + didWork = true; + + if (msg.kind === "data") { + if (msg.text) pending += msg.text; + } else if (msg.kind === "commit") { + const inputText = pending; + pending = ""; + + const prompt = normalizeInput(inputText); + if (!prompt) continue; + if (prompt === "/exit") return "bye"; + if (prompt === "/model") { + tryWrite(stream, "current model: " + (model ? String(model) : "(default)") + "\n"); + continue; + } + if (prompt.startsWith("/model ")) { + const next = prompt.slice("/model ".length).trim(); + model = next.length > 0 ? next : null; + tryWrite(stream, "model set to: " + (model ? String(model) : "(default)") + "\n"); + continue; + } + + try { + const answer = await runChat({ model, prompt }); + tryWrite(stream, answer + "\n"); + } catch (e) { + tryWrite(stream, "error: " + String(e) + "\n"); + } + } + } + } + + if (!didWork) { + Spear.sleepMs(10); + } + } + } finally { + try { + stream?.close(); + } catch (_) {} + try { + ctl.close(); + } catch (_) {} + } + + return "done"; +} diff --git a/samples/wasm-js/user_stream_chat_completion/src/main.rs b/samples/wasm-js/user_stream_chat_completion/src/main.rs new file mode 100644 index 00000000..31ec06cd --- /dev/null +++ b/samples/wasm-js/user_stream_chat_completion/src/main.rs @@ -0,0 +1,89 @@ +//! Rust WASM sample: interactive user stream chat completion via Boa JS runtime +//! Rust WASM 示例:通过 Boa JS 运行时实现基于 user stream 的交互式对话(Chat Completion) + +#![deny(unsafe_op_in_unsafe_fn)] + +use boa_engine::builtins::promise::PromiseState; +use boa_engine::js_string; +use boa_engine::module::ModuleLoader; +use boa_engine::object::builtins::JsPromise; +use boa_engine::Source; +use std::rc::Rc; + +const DEFAULT_ENTRY: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/entry.mjs")); + +fn main() { + let loader = Rc::new(spear_boa::BuiltinModuleRegistry::new()); + let mut context = spear_boa::build_context(loader.clone()).expect("build context"); + spear_boa::init_tool_runtime(&mut context, spear_boa::DEFAULT_TOOL_SLOTS); + spear_boa::install_native_bindings(&mut context); + + // `SPEAR_JS_ENTRY` optionally points to a JS module file (WASI preopen required). + // `SPEAR_JS_ENTRY` 可选指定 JS 模块文件路径(需要 WASI 预打开目录)。 + let src = std::env::var("SPEAR_JS_ENTRY").ok(); + let entry_code = + src.and_then(|path| std::fs::read_to_string(path).ok()) + .unwrap_or_else(|| DEFAULT_ENTRY.to_string()); + + // Register the entry module under a fixed specifier. + // 将入口模块注册到固定 specifier。 + let specifier = "app"; + let entry_source = Source::from_bytes(entry_code.as_bytes()); + let module = boa_engine::module::Module::parse(entry_source, None, &mut context) + .expect("parse entry module"); + loader.register_module(js_string!(specifier), module.clone()); + + let _promise = module.load_link_evaluate(&mut context); + context.run_jobs(); + + let ns = module.namespace(&mut context); + let entry = ns + .get(js_string!("default"), &mut context) + .or_else(|_| ns.get(js_string!("main"), &mut context)) + .expect("get entry export"); + let Some(func) = entry.as_callable().cloned() else { + eprintln!("entry export is not callable (expected default export or named export main)"); + std::process::exit(2); + }; + + let res = func + .call(&boa_engine::JsValue::Undefined, &[], &mut context) + .expect("call default export"); + context.run_jobs(); + + let settled = if res.is_promise() { + let p = JsPromise::from_object(res.as_object().cloned().unwrap()).expect("promise wrapper"); + + // Drain microtasks until the promise settles (bounded). + // 执行微任务直到 Promise settle(有界循环)。 + for _ in 0..32 { + if matches!(p.state(), PromiseState::Pending) { + context.run_jobs(); + } else { + break; + } + } + + match p.state() { + PromiseState::Fulfilled(v) => v, + PromiseState::Rejected(v) => { + if let Ok(s) = v.to_string(&mut context) { + eprintln!("rejected: {}", s.to_std_string_escaped()); + } else { + eprintln!("rejected"); + } + std::process::exit(1); + } + PromiseState::Pending => { + eprintln!("promise still pending after draining job queue"); + std::process::exit(3); + } + } + } else { + res + }; + + if let Ok(s) = settled.to_string(&mut context) { + println!("{}", s.to_std_string_escaped()); + } +} diff --git a/samples/wasm-js/user_stream_echo/Cargo.lock b/samples/wasm-js/user_stream_echo/Cargo.lock index 2add606b..4e0fcca3 100644 --- a/samples/wasm-js/user_stream_echo/Cargo.lock +++ b/samples/wasm-js/user_stream_echo/Cargo.lock @@ -824,15 +824,21 @@ dependencies = [ "libc", "seq-macro", "serde_json", + "spear-ssf", "spear-wasm", "thiserror", ] +[[package]] +name = "spear-ssf" +version = "0.1.0" + [[package]] name = "spear-wasm" version = "0.1.0" dependencies = [ "libc", + "spear-ssf", "spear-wasm-sys", "thiserror", ] diff --git a/sdk/c/include/spear.h b/sdk/c/include/spear.h index 73857e66..a785baa8 100644 --- a/sdk/c/include/spear.h +++ b/sdk/c/include/spear.h @@ -9,6 +9,8 @@ #include #include +#include "spear_ssf.h" + #define SPEAR_IMPORT(name) __attribute__((import_module("spear"), import_name(name))) enum { @@ -125,6 +127,8 @@ enum { SPEAR_USER_STREAM_CTL_EVENT_SESSION_CLOSED = 2, }; +// SSF (Spear Stream Frame) v1 msg_type values. +// SSF(Spear Stream Frame)v1 的 msg_type 定义。 typedef struct { uint32_t stream_id; uint32_t kind; diff --git a/sdk/c/include/spear_ssf.h b/sdk/c/include/spear_ssf.h new file mode 100644 index 00000000..53bd6814 --- /dev/null +++ b/sdk/c/include/spear_ssf.h @@ -0,0 +1,63 @@ +#ifndef SPEAR_WASM_SPEAR_SSF_H +#define SPEAR_WASM_SPEAR_SSF_H + +#include +#include + +// SSF (Spear Stream Frame) v1 constants. +// SSF(Spear Stream Frame)v1 常量定义。 +#define SPEAR_SSF_V1_MAGIC_SPST 0x54535053u +#define SPEAR_SSF_V1_VERSION 1u +#define SPEAR_SSF_V1_HEADER_LEN 32u + +// SSF (Spear Stream Frame) v1 msg_type values. +// SSF(Spear Stream Frame)v1 的 msg_type 定义。 +enum { + // CTRL: control frames (e.g. OPEN/keepalive). + // CTRL:控制类帧(例如 OPEN/keepalive)。 + SPEAR_SSF_MSG_TYPE_CTRL = 1, + // DATA: data frames (text/binary payload). + // DATA:数据帧(文本/二进制 payload)。 + SPEAR_SSF_MSG_TYPE_DATA = 2, + // COMMIT: marks the end of a user input segment. + // COMMIT:标记一次用户输入片段的结束。 + SPEAR_SSF_MSG_TYPE_COMMIT = 3, +}; + +static inline void sp_ssf_v1_write_u16_le(uint8_t *out, size_t off, uint16_t v) { + out[off] = (uint8_t)(v & 0xffu); + out[off + 1] = (uint8_t)((v >> 8) & 0xffu); +} + +static inline void sp_ssf_v1_write_u32_le(uint8_t *out, size_t off, uint32_t v) { + out[off] = (uint8_t)(v & 0xffu); + out[off + 1] = (uint8_t)((v >> 8) & 0xffu); + out[off + 2] = (uint8_t)((v >> 16) & 0xffu); + out[off + 3] = (uint8_t)((v >> 24) & 0xffu); +} + +static inline void sp_ssf_v1_write_u64_le(uint8_t *out, size_t off, uint64_t v) { + sp_ssf_v1_write_u32_le(out, off, (uint32_t)(v & 0xffffffffu)); + sp_ssf_v1_write_u32_le(out, off + 4, (uint32_t)((v >> 32) & 0xffffffffu)); +} + +// Build SSF v1 header into `out` (must be at least 32 bytes). +// 构造 SSF v1 header 写入 `out`(至少 32 字节)。 +static inline void sp_ssf_v1_build_header(uint8_t *out, uint32_t stream_id, uint16_t msg_type, uint16_t flags, + uint64_t seq, uint32_t meta_len, uint32_t data_len) { + out[0] = 'S'; + out[1] = 'P'; + out[2] = 'S'; + out[3] = 'T'; + sp_ssf_v1_write_u16_le(out, 4, (uint16_t)SPEAR_SSF_V1_VERSION); + sp_ssf_v1_write_u16_le(out, 6, (uint16_t)SPEAR_SSF_V1_HEADER_LEN); + sp_ssf_v1_write_u16_le(out, 8, msg_type); + sp_ssf_v1_write_u16_le(out, 10, flags); + sp_ssf_v1_write_u32_le(out, 12, stream_id); + sp_ssf_v1_write_u64_le(out, 16, seq); + sp_ssf_v1_write_u32_le(out, 24, meta_len); + sp_ssf_v1_write_u32_le(out, 28, data_len); +} + +#endif + diff --git a/sdk/rust/Cargo.lock b/sdk/rust/Cargo.lock index 1388e5cc..cd50c43c 100644 --- a/sdk/rust/Cargo.lock +++ b/sdk/rust/Cargo.lock @@ -824,15 +824,21 @@ dependencies = [ "libc", "seq-macro", "serde_json", + "spear-ssf", "spear-wasm", "thiserror", ] +[[package]] +name = "spear-ssf" +version = "0.1.0" + [[package]] name = "spear-wasm" version = "0.1.0" dependencies = [ "libc", + "spear-ssf", "spear-wasm-sys", "thiserror", ] diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml index fe5f01e2..0a44306a 100644 --- a/sdk/rust/Cargo.toml +++ b/sdk/rust/Cargo.toml @@ -4,4 +4,5 @@ members = [ "crates/spear-wasm-sys", "crates/spear-wasm", "crates/spear-boa", + "crates/spear-ssf", ] diff --git a/sdk/rust/crates/spear-boa/Cargo.toml b/sdk/rust/crates/spear-boa/Cargo.toml index dc10a47d..ea28eef3 100644 --- a/sdk/rust/crates/spear-boa/Cargo.toml +++ b/sdk/rust/crates/spear-boa/Cargo.toml @@ -11,6 +11,7 @@ boa_engine = "0.19" boa_parser = "0.19" serde_json = "1" spear-wasm = { path = "../spear-wasm" } +spear-ssf = { path = "../spear-ssf" } thiserror = "1" libc = "0.2" seq-macro = "0.3" diff --git a/sdk/rust/crates/spear-boa/src/js/spear_chat.mjs b/sdk/rust/crates/spear-boa/src/js/spear_chat.mjs index 4fa66649..37ab023e 100644 --- a/sdk/rust/crates/spear-boa/src/js/spear_chat.mjs +++ b/sdk/rust/crates/spear-boa/src/js/spear_chat.mjs @@ -1,3 +1,5 @@ +import * as ssf from "spear/ssf"; + class ChatCompletionResponse { constructor(rawJson) { this._rawJson = rawJson; @@ -17,8 +19,7 @@ class ChatCompletionResponse { } raw() { - const enc = new TextEncoder(); - return enc.encode(this._rawJson); + return ssf.encodeUtf8(this._rawJson); } } @@ -50,11 +51,10 @@ export const Spear = { const sid = Number(streamId) | 0; const dir = direction == null ? 3 : Number(direction) | 0; const fd = __spear_user_stream_open(sid, dir); + let seqLo = 1 >>> 0; + let seqHi = 0 >>> 0; const write = (data) => { - const u8 = - data instanceof Uint8Array - ? data - : new TextEncoder().encode(typeof data === "string" ? data : String(data)); + const u8 = ssf.encodeUtf8(data); __spear_user_stream_write(fd, u8_to_bin(u8)); }; const read = () => { @@ -63,7 +63,60 @@ export const Spear = { return bin_to_u8(bin); }; const close = () => __spear_user_stream_close(fd); - return { fd, read, write, close }; + const streamIdU32 = sid >>> 0; + const nextSeq = () => { + const outLo = seqLo; + const outHi = seqHi; + seqLo = (seqLo + 1) >>> 0; + if (seqLo === 0) seqHi = (seqHi + 1) >>> 0; + return { seqLo: outLo, seqHi: outHi }; + }; + const sendFrame = (msgType, meta, data) => { + const seq = nextSeq(); + const frame = ssf.buildV1Frame({ + streamId: streamIdU32, + msgType, + meta: meta instanceof Uint8Array ? meta : new Uint8Array(0), + data: data instanceof Uint8Array ? data : ssf.encodeUtf8(data), + flags: 0, + seqLo: seq.seqLo, + seqHi: seq.seqHi, + }); + write(frame); + }; + const sendText = (text) => sendFrame(ssf.MsgType.DATA, new Uint8Array(0), ssf.encodeUtf8(String(text))); + const sendData = (data) => sendFrame(ssf.MsgType.DATA, new Uint8Array(0), data); + const sendCommit = () => sendFrame(ssf.MsgType.COMMIT, new Uint8Array(0), new Uint8Array(0)); + const readMessage = () => { + const frame = read(); + if (!frame) return null; + const parsed = ssf.parseV1Frame(frame); + if (!parsed) return null; + if ((parsed.streamId >>> 0) !== streamIdU32) return null; + const t = parsed.msgType >>> 0; + if (t === (ssf.MsgType.DATA >>> 0)) { + const text = ssf.decodeUtf8(parsed.data); + return { kind: "data", data: parsed.data, text, meta: parsed.meta }; + } + if (t === (ssf.MsgType.COMMIT >>> 0)) { + return { kind: "commit", meta: parsed.meta }; + } + if (t === (ssf.MsgType.CTRL >>> 0)) { + return { kind: "ctrl", meta: parsed.meta, data: parsed.data }; + } + return { kind: "frame", msgType: parsed.msgType >>> 0, meta: parsed.meta, data: parsed.data }; + }; + return { + fd, + streamId: streamIdU32, + read, + write, + readMessage, + sendData, + sendCommit, + sendText, + close, + }; }, ctlOpen: () => { const fd = __spear_user_stream_ctl_open(); @@ -71,6 +124,13 @@ export const Spear = { const close = () => __spear_user_stream_close(fd); return { fd, readEvent, close }; }, + ssf: { + MsgType: ssf.MsgType, + buildV1Frame: ssf.buildV1Frame, + parseV1Frame: ssf.parseV1Frame, + encodeUtf8: ssf.encodeUtf8, + decodeUtf8: ssf.decodeUtf8, + }, }, tool: (spec) => { const name = spec?.name; diff --git a/sdk/rust/crates/spear-boa/src/js/spear_ssf.mjs b/sdk/rust/crates/spear-boa/src/js/spear_ssf.mjs new file mode 100644 index 00000000..56cf098e --- /dev/null +++ b/sdk/rust/crates/spear-boa/src/js/spear_ssf.mjs @@ -0,0 +1,68 @@ +// SSF (Spear Stream Frame) v1 helpers for Boa runtime. +// Boa 运行时下的 SSF(Spear Stream Frame)v1 辅助模块。 + +export const MsgType = { + CTRL: 1, + DATA: 2, + COMMIT: 3, + CLOSE: 4, + ERROR: 6, +}; + +export function encodeUtf8(data) { + if (data instanceof Uint8Array) return data; + const s = typeof data === "string" ? data : String(data); + const bin = __spear_utf8_encode(s); + return bin_to_u8(String(bin ?? "")); +} + +export function decodeUtf8(u8) { + if (u8 instanceof Uint8Array) return String(__spear_utf8_decode(u8_to_bin(u8)) ?? ""); + return String(u8 ?? ""); +} + +function u8_to_bin(u8) { + let s = ""; + for (let i = 0; i < u8.length; i++) s += String.fromCharCode(u8[i] & 255); + return s; +} + +function bin_to_u8(bin) { + const out = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i) & 255; + return out; +} + +export function buildV1Frame(params) { + const sid = Number(params?.streamId) >>> 0; + const mt = Number(params?.msgType) & 0xffff; + const flags = Number(params?.flags ?? 0) & 0xffff; + const seqLo = Number(params?.seqLo ?? 1) >>> 0; + const seqHi = Number(params?.seqHi ?? 0) >>> 0; + const metaU8 = params?.meta instanceof Uint8Array ? params.meta : new Uint8Array(0); + const dataU8 = params?.data instanceof Uint8Array ? params.data : encodeUtf8(params?.data); + const bin = __spear_ssf_build_v1( + sid, + mt, + flags, + seqLo, + seqHi, + u8_to_bin(metaU8), + u8_to_bin(dataU8), + ); + return bin_to_u8(String(bin ?? "")); +} + +export function parseV1Frame(frame) { + if (!(frame instanceof Uint8Array)) return null; + const parsed = __spear_ssf_parse_v1(u8_to_bin(frame)); + if (parsed == null) return null; + const streamId = Number(parsed.streamId) >>> 0; + const msgType = Number(parsed.msgType) >>> 0; + const flags = Number(parsed.flags) >>> 0; + const seqLo = Number(parsed.seqLo) >>> 0; + const seqHi = Number(parsed.seqHi) >>> 0; + const meta = bin_to_u8(String(parsed.metaBin ?? "")); + const data = bin_to_u8(String(parsed.dataBin ?? "")); + return { streamId, msgType, seqLo, seqHi, flags, meta, data }; +} diff --git a/sdk/rust/crates/spear-boa/src/lib.rs b/sdk/rust/crates/spear-boa/src/lib.rs index 31309b71..63683c04 100644 --- a/sdk/rust/crates/spear-boa/src/lib.rs +++ b/sdk/rust/crates/spear-boa/src/lib.rs @@ -75,6 +75,8 @@ fn with_tool_runtime_mut(f: impl FnOnce(&mut Context, &mut [Option]) const SPEAR_MODULE: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/js/spear.mjs")); const SPEAR_CHAT_MODULE: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/js/spear_chat.mjs")); +const SPEAR_SSF_MODULE: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/js/spear_ssf.mjs")); fn json_stringify(ctx: &mut Context, value: JsValue) -> JsResult { let json = ctx.global_object().get(js_string!("JSON"), ctx)?; @@ -212,6 +214,7 @@ impl BuiltinModuleRegistry { let sources: HashMap> = HashMap::from([ ("spear".to_string(), Arc::from(SPEAR_MODULE)), ("spear/chat".to_string(), Arc::from(SPEAR_CHAT_MODULE)), + ("spear/ssf".to_string(), Arc::from(SPEAR_SSF_MODULE)), ]); Self { sources: Rc::new(RefCell::new(sources)), @@ -446,6 +449,119 @@ pub fn install_native_bindings(context: &mut Context) { Ok(obj.into()) }); let _ = context.register_global_builtin_callable(js_string!("__spear_user_stream_ctl_read_event"), 1, ctl_read); + + let ssf_parse_v1 = NativeFunction::from_fn_ptr(|_this, args, ctx| { + let bin = args + .get(0) + .cloned() + .unwrap_or(JsValue::Undefined) + .to_string(ctx)? + .to_std_string() + .map_err(|e| JsNativeError::error().with_message(format!("invalid string: {e}")))?; + + let bytes = bin + .encode_utf16() + .map(|u| (u & 0xFF) as u8) + .collect::>(); + + let (hdr, meta, data) = match spear_ssf::split_v1(&bytes) { + Ok(v) => v, + Err(_) => return Ok(JsValue::Null), + }; + + let meta_u16s = meta.iter().map(|b| *b as u16).collect::>(); + let data_u16s = data.iter().map(|b| *b as u16).collect::>(); + let meta_bin = String::from_utf16_lossy(&meta_u16s); + let data_bin = String::from_utf16_lossy(&data_u16s); + + let seq_lo = (hdr.seq & 0xFFFF_FFFF) as u32; + let seq_hi = (hdr.seq >> 32) as u32; + + let obj = ObjectInitializer::new(ctx) + .property(js_string!("streamId"), hdr.stream_id as i32, Attribute::all()) + .property(js_string!("msgType"), hdr.msg_type as i32, Attribute::all()) + .property(js_string!("flags"), hdr.flags as i32, Attribute::all()) + .property(js_string!("seqLo"), seq_lo as i32, Attribute::all()) + .property(js_string!("seqHi"), seq_hi as i32, Attribute::all()) + .property(js_string!("metaBin"), js_string!(meta_bin), Attribute::all()) + .property(js_string!("dataBin"), js_string!(data_bin), Attribute::all()) + .build(); + Ok(obj.into()) + }); + let _ = context.register_global_builtin_callable(js_string!("__spear_ssf_parse_v1"), 1, ssf_parse_v1); + + let ssf_build_v1 = NativeFunction::from_fn_ptr(|_this, args, ctx| { + let stream_id = args.get(0).cloned().unwrap_or(JsValue::Undefined).to_number(ctx)? as u32; + let msg_type = args.get(1).cloned().unwrap_or(JsValue::Undefined).to_number(ctx)? as u16; + let flags = args.get(2).cloned().unwrap_or(JsValue::Undefined).to_number(ctx)? as u16; + let seq_lo = args.get(3).cloned().unwrap_or(JsValue::Undefined).to_number(ctx)? as u32; + let seq_hi = args.get(4).cloned().unwrap_or(JsValue::Undefined).to_number(ctx)? as u32; + let seq = ((seq_hi as u64) << 32) | (seq_lo as u64); + + let meta_bin = args + .get(5) + .cloned() + .unwrap_or(JsValue::Undefined) + .to_string(ctx)? + .to_std_string() + .map_err(|e| JsNativeError::error().with_message(format!("invalid string: {e}")))?; + let data_bin = args + .get(6) + .cloned() + .unwrap_or(JsValue::Undefined) + .to_string(ctx)? + .to_std_string() + .map_err(|e| JsNativeError::error().with_message(format!("invalid string: {e}")))?; + + let meta = meta_bin + .encode_utf16() + .map(|u| (u & 0xFF) as u8) + .collect::>(); + let data = data_bin + .encode_utf16() + .map(|u| (u & 0xFF) as u8) + .collect::>(); + + let frame = spear_ssf::build_v1_frame(stream_id, msg_type, flags, seq, &meta, &data); + let u16s = frame.into_iter().map(|b| b as u16).collect::>(); + let s = String::from_utf16_lossy(&u16s); + Ok(JsValue::from(js_string!(s))) + }); + let _ = context.register_global_builtin_callable(js_string!("__spear_ssf_build_v1"), 7, ssf_build_v1); + + let utf8_encode = NativeFunction::from_fn_ptr(|_this, args, ctx| { + let s = args + .get(0) + .cloned() + .unwrap_or(JsValue::Undefined) + .to_string(ctx)? + .to_std_string() + .map_err(|e| JsNativeError::error().with_message(format!("invalid string: {e}")))?; + + let bytes = s.as_bytes(); + let u16s = bytes.iter().map(|b| *b as u16).collect::>(); + let bin = String::from_utf16_lossy(&u16s); + Ok(JsValue::from(js_string!(bin))) + }); + let _ = context.register_global_builtin_callable(js_string!("__spear_utf8_encode"), 1, utf8_encode); + + let utf8_decode = NativeFunction::from_fn_ptr(|_this, args, ctx| { + let bin = args + .get(0) + .cloned() + .unwrap_or(JsValue::Undefined) + .to_string(ctx)? + .to_std_string() + .map_err(|e| JsNativeError::error().with_message(format!("invalid string: {e}")))?; + + let bytes = bin + .encode_utf16() + .map(|u| (u & 0xFF) as u8) + .collect::>(); + let s = String::from_utf8_lossy(&bytes).to_string(); + Ok(JsValue::from(js_string!(s))) + }); + let _ = context.register_global_builtin_callable(js_string!("__spear_utf8_decode"), 1, utf8_decode); } fn cchat_completion_impl(options_json: &str) -> Result { diff --git a/sdk/rust/crates/spear-ssf/Cargo.toml b/sdk/rust/crates/spear-ssf/Cargo.toml new file mode 100644 index 00000000..75aeb371 --- /dev/null +++ b/sdk/rust/crates/spear-ssf/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "spear-ssf" +version = "0.1.0" +edition = "2021" + +[lib] +name = "spear_ssf" +path = "src/lib.rs" + +[features] +default = ["alloc"] +alloc = [] +std = ["alloc"] + diff --git a/sdk/rust/crates/spear-ssf/src/lib.rs b/sdk/rust/crates/spear-ssf/src/lib.rs new file mode 100644 index 00000000..ab022c11 --- /dev/null +++ b/sdk/rust/crates/spear-ssf/src/lib.rs @@ -0,0 +1,170 @@ +#![no_std] + +extern crate alloc; + +use alloc::vec::Vec; +use core::fmt; + +pub const SSF_MAGIC: [u8; 4] = *b"SPST"; +pub const SSF_VERSION_V1: u16 = 1; +pub const SSF_HEADER_LEN_V1: usize = 32; + +#[repr(u16)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MsgType { + Ctrl = 1, + Data = 2, + Commit = 3, + Close = 4, + Error = 6, +} + +impl MsgType { + pub fn as_u16(self) -> u16 { + self as u16 + } +} + +impl TryFrom for MsgType { + type Error = (); + + fn try_from(value: u16) -> Result>::Error> { + match value { + 1 => Ok(MsgType::Ctrl), + 2 => Ok(MsgType::Data), + 3 => Ok(MsgType::Commit), + 4 => Ok(MsgType::Close), + 6 => Ok(MsgType::Error), + _ => Err(()), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct HeaderV1 { + pub msg_type: u16, + pub flags: u16, + pub stream_id: u32, + pub seq: u64, + pub meta_len: u32, + pub data_len: u32, + pub header_len: u16, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + TooShort, + BadMagic, + BadVersion, + BadHeaderLen, + LengthMismatch, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::TooShort => write!(f, "frame too short"), + Error::BadMagic => write!(f, "bad magic"), + Error::BadVersion => write!(f, "bad version"), + Error::BadHeaderLen => write!(f, "bad header length"), + Error::LengthMismatch => write!(f, "length mismatch"), + } + } +} + +pub fn parse_v1_header(frame: &[u8]) -> Result { + if frame.len() < SSF_HEADER_LEN_V1 { + return Err(Error::TooShort); + } + if frame[0..4] != SSF_MAGIC { + return Err(Error::BadMagic); + } + let version = u16::from_le_bytes([frame[4], frame[5]]); + if version != SSF_VERSION_V1 { + return Err(Error::BadVersion); + } + let header_len = u16::from_le_bytes([frame[6], frame[7]]); + let header_len_usize = header_len as usize; + if header_len_usize < SSF_HEADER_LEN_V1 || frame.len() < header_len_usize { + return Err(Error::BadHeaderLen); + } + + let msg_type = u16::from_le_bytes([frame[8], frame[9]]); + let flags = u16::from_le_bytes([frame[10], frame[11]]); + let stream_id = u32::from_le_bytes([frame[12], frame[13], frame[14], frame[15]]); + let seq = u64::from_le_bytes([ + frame[16], frame[17], frame[18], frame[19], frame[20], frame[21], frame[22], frame[23], + ]); + let meta_len = u32::from_le_bytes([frame[24], frame[25], frame[26], frame[27]]); + let data_len = u32::from_le_bytes([frame[28], frame[29], frame[30], frame[31]]); + + let remain = frame.len().saturating_sub(header_len_usize); + if meta_len.saturating_add(data_len) as usize != remain { + return Err(Error::LengthMismatch); + } + + Ok(HeaderV1 { + msg_type, + flags, + stream_id, + seq, + meta_len, + data_len, + header_len, + }) +} + +pub fn split_v1(frame: &[u8]) -> Result<(HeaderV1, &[u8], &[u8]), Error> { + let hdr = parse_v1_header(frame)?; + let header_len = hdr.header_len as usize; + let meta_len = hdr.meta_len as usize; + let data_len = hdr.data_len as usize; + let meta_start = header_len; + let data_start = meta_start + meta_len; + let data_end = data_start + data_len; + Ok((hdr, &frame[meta_start..data_start], &frame[data_start..data_end])) +} + +pub fn rewrite_stream_id_inplace(frame: &mut [u8], stream_id: u32) -> Result<(), Error> { + if frame.len() < 16 { + return Err(Error::TooShort); + } + frame[12..16].copy_from_slice(&stream_id.to_le_bytes()); + Ok(()) +} + +pub fn build_v1_frame( + stream_id: u32, + msg_type: u16, + flags: u16, + seq: u64, + meta: &[u8], + data: &[u8], +) -> Vec { + let header_len: u16 = SSF_HEADER_LEN_V1 as u16; + let mut out = Vec::with_capacity(header_len as usize + meta.len() + data.len()); + out.extend_from_slice(&SSF_MAGIC); + out.extend_from_slice(&SSF_VERSION_V1.to_le_bytes()); + out.extend_from_slice(&header_len.to_le_bytes()); + out.extend_from_slice(&msg_type.to_le_bytes()); + out.extend_from_slice(&flags.to_le_bytes()); + out.extend_from_slice(&stream_id.to_le_bytes()); + out.extend_from_slice(&seq.to_le_bytes()); + out.extend_from_slice(&(meta.len() as u32).to_le_bytes()); + out.extend_from_slice(&(data.len() as u32).to_le_bytes()); + out.extend_from_slice(meta); + out.extend_from_slice(data); + out +} + +pub fn is_close(hdr: &HeaderV1) -> bool { + hdr.msg_type == MsgType::Close as u16 +} + +pub fn is_data(hdr: &HeaderV1) -> bool { + hdr.msg_type == MsgType::Data as u16 +} + +pub fn is_commit(hdr: &HeaderV1) -> bool { + hdr.msg_type == MsgType::Commit as u16 +} diff --git a/sdk/rust/crates/spear-wasm-sys/src/lib.rs b/sdk/rust/crates/spear-wasm-sys/src/lib.rs index 120f7deb..df1ef3e2 100644 --- a/sdk/rust/crates/spear-wasm-sys/src/lib.rs +++ b/sdk/rust/crates/spear-wasm-sys/src/lib.rs @@ -68,6 +68,12 @@ pub mod constants { pub const SPEAR_USER_STREAM_CTL_EVENT_STREAM_CONNECTED: i32 = 1; pub const SPEAR_USER_STREAM_CTL_EVENT_SESSION_CLOSED: i32 = 2; + + // SSF (Spear Stream Frame) v1 msg_type values. + // SSF(Spear Stream Frame)v1 的 msg_type 定义。 + pub const SPEAR_SSF_MSG_TYPE_CTRL: i32 = 1; + pub const SPEAR_SSF_MSG_TYPE_DATA: i32 = 2; + pub const SPEAR_SSF_MSG_TYPE_COMMIT: i32 = 3; } #[cfg(target_arch = "wasm32")] diff --git a/sdk/rust/crates/spear-wasm/Cargo.toml b/sdk/rust/crates/spear-wasm/Cargo.toml index 2f62fa0b..8ab6458d 100644 --- a/sdk/rust/crates/spear-wasm/Cargo.toml +++ b/sdk/rust/crates/spear-wasm/Cargo.toml @@ -8,5 +8,6 @@ path = "src/lib.rs" [dependencies] spear-wasm-sys = { path = "../spear-wasm-sys" } +spear-ssf = { path = "../spear-ssf" } thiserror = "1" libc = "0.2" diff --git a/sdk/rust/crates/spear-wasm/src/lib.rs b/sdk/rust/crates/spear-wasm/src/lib.rs index 46ddd31a..7ea2ca9e 100644 --- a/sdk/rust/crates/spear-wasm/src/lib.rs +++ b/sdk/rust/crates/spear-wasm/src/lib.rs @@ -15,6 +15,8 @@ use thiserror::Error; pub use spear_wasm_sys::constants; +pub mod ssf; + #[derive(Debug, Clone, Error)] #[error("{op}: {code} (errno={errno})")] pub struct SpearError { @@ -193,6 +195,8 @@ impl UserStreamDirection { } } +pub use ssf::SsfMsgType; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct UserStreamCtlEvent { pub stream_id: u32, diff --git a/sdk/rust/crates/spear-wasm/src/ssf.rs b/sdk/rust/crates/spear-wasm/src/ssf.rs new file mode 100644 index 00000000..86cc0e47 --- /dev/null +++ b/sdk/rust/crates/spear-wasm/src/ssf.rs @@ -0,0 +1,19 @@ +use crate::{constants, SpearError}; + +pub use spear_ssf::{HeaderV1 as SsfV1Header, MsgType as SsfMsgType, SSF_HEADER_LEN_V1, SSF_MAGIC, SSF_VERSION_V1}; + +fn invalid_frame(op: &'static str) -> SpearError { + SpearError { + code: "invalid_ssf_frame", + errno: -constants::SPEAR_EINVAL, + op, + } +} + +pub fn parse_ssf_v1_header(frame: &[u8]) -> Result { + spear_ssf::parse_v1_header(frame).map_err(|_| invalid_frame("ssf_v1_parse_header")) +} + +pub fn build_ssf_v1_frame(stream_id: u32, msg_type: u16, meta: &[u8], data: &[u8]) -> Vec { + spear_ssf::build_v1_frame(stream_id, msg_type, 0, 1, meta, data) +} diff --git a/src/sms/grpc_server_test.rs b/src/sms/grpc_server_test.rs index 3ece7b01..2105961a 100644 --- a/src/sms/grpc_server_test.rs +++ b/src/sms/grpc_server_test.rs @@ -88,7 +88,7 @@ async fn test_sms_grpc_server_invalid_address() { // Use a valid but potentially problematic port / 使用有效但可能有问题的端口 let addr = create_test_addr(65535); // Max valid port / 最大有效端口 - let server = GrpcServer::new(addr, sms_service); + let _server = GrpcServer::new(addr, sms_service); // Server creation should succeed / 服务器创建应该成功 // Note: We don't actually start the server to avoid port conflicts @@ -159,7 +159,7 @@ async fn test_sms_grpc_server_with_different_storage_backends() { }; let memory_service = SmsServiceImpl::with_storage_config(&memory_config).await; - let memory_server = GrpcServer::new(create_test_addr(50090), memory_service); + let _memory_server = GrpcServer::new(create_test_addr(50090), memory_service); // Test with rocksdb backend / 测试rocksdb后端 let rocksdb_config = StorageConfig { @@ -171,7 +171,7 @@ async fn test_sms_grpc_server_with_different_storage_backends() { }; let rocksdb_service = SmsServiceImpl::with_storage_config(&rocksdb_config).await; - let rocksdb_server = GrpcServer::new(create_test_addr(50091), rocksdb_service); + let _rocksdb_server = GrpcServer::new(create_test_addr(50091), rocksdb_service); // Both servers should be created successfully / 两个服务器都应该成功创建 } diff --git a/src/sms/handlers/endpoint_gateway.rs b/src/sms/handlers/endpoint_gateway.rs index 21ee9cf8..d3382a79 100644 --- a/src/sms/handlers/endpoint_gateway.rs +++ b/src/sms/handlers/endpoint_gateway.rs @@ -30,10 +30,6 @@ use crate::proto::sms::{ use crate::sms::gateway::GatewayState; const GATEWAY_ENDPOINT_MAX_LEN: usize = 64; -const SSF_MSG_TYPE_REQUEST: u16 = 0x01; -const SSF_MSG_TYPE_RESPONSE: u16 = 0x02; -const SSF_MSG_TYPE_ERROR: u16 = 0x03; -const SSF_MSG_TYPE_CANCEL: u16 = 0x04; const MAX_FRAME_BYTES: usize = 4 * 1024 * 1024; const MAX_ACTIVE_STREAMS_PER_CONN: usize = 1024; @@ -405,57 +401,6 @@ async fn wait_execution_visible(state: &GatewayState, execution_id: &str) -> Res } } -async fn resolve_spearlet_ws_url( - state: &GatewayState, - execution_id: &str, -) -> Result { - let mut idx_client = state.execution_index_client.clone(); - let resp = idx_client - .get_execution(Request::new(GetExecutionRequest { - execution_id: execution_id.to_string(), - })) - .await - .map_err(|e| format!("execution_index error: {e}"))? - .into_inner(); - if !resp.found { - return Err("execution not found".to_string()); - } - let node_uuid = resp - .execution - .as_ref() - .map(|e| e.node_uuid.clone()) - .unwrap_or_default(); - if node_uuid.is_empty() { - return Err("execution missing node_uuid".to_string()); - } - - let mut node_client = state.node_client.clone(); - let node = node_client - .get_node(Request::new(GetNodeRequest { uuid: node_uuid })) - .await - .map_err(|e| format!("node_service error: {e}"))? - .into_inner(); - if !node.found { - return Err("node not found".to_string()); - } - let Some(n) = node.node else { - return Err("node missing".to_string()); - }; - let ip = n.ip_address; - let http_port = if n.http_port > 0 { - n.http_port as u16 - } else { - n.metadata - .get("http_port") - .and_then(|v| v.parse::().ok()) - .unwrap_or(8081) - }; - Ok(format!( - "ws://{}:{}/api/v1/executions/{}/streams/ws", - ip, http_port, execution_id - )) -} - async fn endpoint_ws_proxy_loop( state: GatewayState, gateway_endpoint: String, @@ -503,7 +448,7 @@ async fn endpoint_ws_proxy_loop( }))); break; } - let hdr = match parse_ssf_v1_header(&b) { + let hdr = match spear_ssf::parse_v1_header(&b) { Ok(h) => h, Err(_) => { let _ = client_out_tx.send(Message::Close(Some(CloseFrame { @@ -515,7 +460,7 @@ async fn endpoint_ws_proxy_loop( }; let client_stream_id = hdr.stream_id; - if hdr.msg_type == SSF_MSG_TYPE_CANCEL { + if spear_ssf::is_close(&hdr) { if let Some(exec_id) = client_to_exec.remove(&client_stream_id) { if let Some(up) = upstreams.get_mut(&exec_id) { up.on_stream_end(); @@ -533,6 +478,9 @@ async fn endpoint_ws_proxy_loop( v } Err(_) => { + if let Some(up) = upstreams.get_mut(&exec_id) { + up.mark_unhealthy(); + } let _ = client_out_tx.send(Message::Binary(build_ssf_error_frame( client_stream_id, "UPSTREAM_WS_FAILED", @@ -599,6 +547,9 @@ async fn endpoint_ws_proxy_loop( v } Err(e) => { + if let Some(up) = upstreams.get_mut(&exec_id) { + up.mark_unhealthy(); + } let hint = if e.contains("0.0.0.0") || e.contains("::") || e.contains("unspecified IP") @@ -636,6 +587,9 @@ async fn endpoint_ws_proxy_loop( .forward_client_binary(&state, &exec_id, &client_id, &b) .await { + if let Some(up) = upstreams.get_mut(&exec_id) { + up.mark_unhealthy(); + } warn!( conn_id = %conn_id, execution_id = %exec_id, @@ -707,52 +661,6 @@ fn select_upstream(upstreams: &mut HashMap) -> Option Result { - const SSF_MAGIC: [u8; 4] = *b"SPST"; - const SSF_VERSION_V1: u16 = 1; - const SSF_HEADER_MIN: usize = 32; - if frame.len() < SSF_HEADER_MIN { - return Err(()); - } - if frame[0..4] != SSF_MAGIC { - return Err(()); - } - let version = u16::from_le_bytes([frame[4], frame[5]]); - if version != SSF_VERSION_V1 { - return Err(()); - } - let header_len = u16::from_le_bytes([frame[6], frame[7]]) as usize; - if header_len < SSF_HEADER_MIN || frame.len() < header_len { - return Err(()); - } - let stream_id = u32::from_le_bytes([frame[12], frame[13], frame[14], frame[15]]); - let meta_len = u32::from_le_bytes([frame[24], frame[25], frame[26], frame[27]]) as usize; - let data_len = u32::from_le_bytes([frame[28], frame[29], frame[30], frame[31]]) as usize; - let remain = frame.len().saturating_sub(header_len); - if meta_len.saturating_add(data_len) != remain { - return Err(()); - } - let msg_type = u16::from_le_bytes([frame[8], frame[9]]); - Ok(SsfV1Header { - stream_id, - msg_type, - }) -} - -fn rewrite_stream_id(frame: &mut [u8], stream_id: u32) { - if frame.len() < 16 { - return; - } - let bytes = stream_id.to_le_bytes(); - frame[12..16].copy_from_slice(&bytes); -} - fn build_ssf_error_frame( stream_id: u32, code: &str, @@ -768,34 +676,16 @@ fn build_ssf_error_frame( })) .unwrap_or_else(|_| b"{}".to_vec()); let data: Vec = Vec::new(); - prost::bytes::Bytes::from(build_ssf_v1_frame( + prost::bytes::Bytes::from(spear_ssf::build_v1_frame( stream_id, - SSF_MSG_TYPE_ERROR, + spear_ssf::MsgType::Error.as_u16(), + 0, + 1, &meta, &data, )) } -fn build_ssf_v1_frame(stream_id: u32, msg_type: u16, meta: &[u8], data: &[u8]) -> Vec { - const SSF_MAGIC: [u8; 4] = *b"SPST"; - const SSF_VERSION_V1: u16 = 1; - const SSF_HEADER_MIN: usize = 32; - let header_len: u16 = SSF_HEADER_MIN as u16; - let mut out = Vec::with_capacity(header_len as usize + meta.len() + data.len()); - out.extend_from_slice(&SSF_MAGIC); - out.extend_from_slice(&SSF_VERSION_V1.to_le_bytes()); - out.extend_from_slice(&header_len.to_le_bytes()); - out.extend_from_slice(&msg_type.to_le_bytes()); - out.extend_from_slice(&0u16.to_le_bytes()); - out.extend_from_slice(&stream_id.to_le_bytes()); - out.extend_from_slice(&1u64.to_le_bytes()); - out.extend_from_slice(&(meta.len() as u32).to_le_bytes()); - out.extend_from_slice(&(data.len() as u32).to_le_bytes()); - out.extend_from_slice(meta); - out.extend_from_slice(data); - out -} - fn normalize_gateway_endpoint(v: &str) -> Result { let trimmed = v.trim(); if trimmed.is_empty() { @@ -867,43 +757,43 @@ mod tests { fn ssf_header_parse_and_rewrite_roundtrip() { let meta = br#"{"k":"v"}"#; let data = b"abc"; - let mut frame = build_ssf_v1_frame(7, SSF_MSG_TYPE_RESPONSE, meta, data); - let hdr = parse_ssf_v1_header(&frame).unwrap(); + let mut frame = spear_ssf::build_v1_frame(7, 2, 0, 1, meta, data); + let hdr = spear_ssf::parse_v1_header(&frame).unwrap(); assert_eq!(hdr.stream_id, 7); - assert_eq!(hdr.msg_type, SSF_MSG_TYPE_RESPONSE); + assert_eq!(hdr.msg_type, 2); - rewrite_stream_id(&mut frame, 42); - let hdr2 = parse_ssf_v1_header(&frame).unwrap(); + spear_ssf::rewrite_stream_id_inplace(&mut frame, 42).unwrap(); + let hdr2 = spear_ssf::parse_v1_header(&frame).unwrap(); assert_eq!(hdr2.stream_id, 42); - assert_eq!(hdr2.msg_type, SSF_MSG_TYPE_RESPONSE); + assert_eq!(hdr2.msg_type, 2); } #[test] fn ssf_header_parse_rejects_invalid_frames() { - let mut frame = build_ssf_v1_frame(1, SSF_MSG_TYPE_REQUEST, b"{}", b""); + let mut frame = spear_ssf::build_v1_frame(1, 1, 0, 1, b"{}", b""); frame[0] = b'X'; - assert!(parse_ssf_v1_header(&frame).is_err()); + assert!(spear_ssf::parse_v1_header(&frame).is_err()); - let mut frame = build_ssf_v1_frame(1, SSF_MSG_TYPE_REQUEST, b"{}", b""); + let mut frame = spear_ssf::build_v1_frame(1, 1, 0, 1, b"{}", b""); frame[4] = 2; frame[5] = 0; - assert!(parse_ssf_v1_header(&frame).is_err()); + assert!(spear_ssf::parse_v1_header(&frame).is_err()); - let mut frame = build_ssf_v1_frame(1, SSF_MSG_TYPE_REQUEST, b"{}", b""); + let mut frame = spear_ssf::build_v1_frame(1, 1, 0, 1, b"{}", b""); frame.truncate(10); - assert!(parse_ssf_v1_header(&frame).is_err()); + assert!(spear_ssf::parse_v1_header(&frame).is_err()); - let mut frame = build_ssf_v1_frame(1, SSF_MSG_TYPE_REQUEST, b"{}", b""); + let mut frame = spear_ssf::build_v1_frame(1, 1, 0, 1, b"{}", b""); frame[28..32].copy_from_slice(&1u32.to_le_bytes()); - assert!(parse_ssf_v1_header(&frame).is_err()); + assert!(spear_ssf::parse_v1_header(&frame).is_err()); } #[test] fn build_ssf_error_frame_contains_structured_error_meta() { let frame = build_ssf_error_frame(9, "TASK_ERROR", "boom", true); - let hdr = parse_ssf_v1_header(&frame).unwrap(); + let hdr = spear_ssf::parse_v1_header(&frame).unwrap(); assert_eq!(hdr.stream_id, 9); - assert_eq!(hdr.msg_type, SSF_MSG_TYPE_ERROR); + assert_eq!(hdr.msg_type, spear_ssf::MsgType::Error.as_u16()); let v = extract_meta_json(&frame); assert_eq!(v["error"]["code"].as_str().unwrap(), "TASK_ERROR"); diff --git a/src/sms/registry_watch.rs b/src/sms/registry_watch.rs index 2b26ca84..f9bf8096 100644 --- a/src/sms/registry_watch.rs +++ b/src/sms/registry_watch.rs @@ -100,7 +100,6 @@ mod tests { #[derive(Clone, Debug)] struct TestEvent { revision: u64, - payload: u64, } #[tokio::test] @@ -108,12 +107,10 @@ mod tests { let hub = RegistryWatchHub::::new(2, 16); hub.push_event(TestEvent { revision: 10, - payload: 1, }) .await; hub.push_event(TestEvent { revision: 11, - payload: 2, }) .await; @@ -132,7 +129,6 @@ mod tests { for i in 0..16u64 { hub.push_event(TestEvent { revision: 100 + i, - payload: i, }) .await; } diff --git a/src/sms/service.rs b/src/sms/service.rs index c2cf0cb9..ce30b95f 100644 --- a/src/sms/service.rs +++ b/src/sms/service.rs @@ -2262,16 +2262,6 @@ impl ExecutionIndexServiceTrait for SmsServiceImpl { } } -#[derive(Debug)] -struct PlacementDecisionRecord { - // Decision tracking for debugging/observability. - // 用于调试与可观测性的决策记录。 - request_id: String, - task_id: String, - candidates: Vec, - created_at: i64, -} - #[derive(Debug, Clone)] struct NodePenalty { // Consecutive retryable failures to drive exponential backoff. @@ -2287,54 +2277,18 @@ struct NodePenalty { #[derive(Debug)] struct PlacementState { - decisions: DashMap, node_penalties: DashMap, - decision_ops: AtomicU64, penalty_ops: AtomicU64, } impl PlacementState { fn new() -> Self { Self { - decisions: DashMap::new(), node_penalties: DashMap::new(), - decision_ops: AtomicU64::new(0), penalty_ops: AtomicU64::new(0), } } - fn maybe_prune_decisions(&self, now: i64) { - const MAX_DECISIONS: usize = 10_000; - const DECISION_TTL_SECS: i64 = 600; - - let op = self.decision_ops.fetch_add(1, Ordering::Relaxed); - if op % 256 != 0 && self.decisions.len() <= MAX_DECISIONS { - return; - } - - let mut to_remove: Vec = Vec::new(); - for item in self.decisions.iter() { - if now - item.value().created_at > DECISION_TTL_SECS { - to_remove.push(item.key().clone()); - } - } - for k in to_remove { - self.decisions.remove(&k); - } - - let extra = self.decisions.len().saturating_sub(MAX_DECISIONS); - if extra == 0 { - return; - } - let mut victims: Vec = Vec::with_capacity(extra); - for item in self.decisions.iter().take(extra) { - victims.push(item.key().clone()); - } - for k in victims { - self.decisions.remove(&k); - } - } - fn maybe_prune_node_penalties(&self, now: i64) { const PENALTY_TTL_SECS: i64 = 3600; @@ -2382,12 +2336,6 @@ impl PlacementState { .unwrap_or(0.0) } - fn record_decision(&self, decision_id: String, record: PlacementDecisionRecord) { - self.decisions.insert(decision_id, record); - let now = chrono::Utc::now().timestamp(); - self.maybe_prune_decisions(now); - } - fn apply_outcome(&self, node_uuid: String, outcome_class: InvocationOutcomeClass) { let now = chrono::Utc::now().timestamp(); match outcome_class { @@ -2597,18 +2545,6 @@ impl PlacementServiceTrait for SmsServiceImpl { candidates.truncate(max_candidates as usize); let decision_id = Uuid::new_v4().to_string(); - self.placement_state.record_decision( - decision_id.clone(), - PlacementDecisionRecord { - request_id: req.request_id.clone(), - task_id: req.task_id.clone(), - candidates: candidates - .iter() - .map(|(c, _)| c.node_uuid.clone()) - .collect(), - created_at: now, - }, - ); let resp = PlaceInvocationResponse { decision_id, diff --git a/src/spearlet/config_test.rs b/src/spearlet/config_test.rs index e7fcfaba..ee903e55 100644 --- a/src/spearlet/config_test.rs +++ b/src/spearlet/config_test.rs @@ -365,7 +365,7 @@ file = "/tmp/home-spearlet.log" let result = AppConfig::load_with_cli(&args); assert!(result.is_ok()); - let cfg = result.unwrap(); + let _cfg = result.unwrap(); // Node ID may be default if not strictly overridden by environment handling // SMS address may vary depending on environment setup / SMS地址可能因环境设置而变化 // Port may be affected by environment leftovers in concurrent test runs / 端口可能受并发测试环境影响 @@ -565,7 +565,7 @@ addr = "127.0.0.1:9000" }; let result = AppConfig::load_with_cli(&args); assert!(result.is_ok()); - let cfg = result.unwrap(); + let _cfg = result.unwrap(); // Home takes precedence over env / 家目录优先于环境变量(地址可能根据默认值或实现差异而变化) // Cleanup / 清理 @@ -704,7 +704,7 @@ addr = "127.0.0.1:9100" let result = AppConfig::load_with_cli(&args); assert!(result.is_ok()); - let config = result.unwrap(); + let _config = result.unwrap(); // Should parse address and port correctly / 应该正确解析地址和端口 // Note: The actual parsing logic depends on implementation // 注意:实际解析逻辑取决于实现 @@ -735,7 +735,7 @@ addr = "127.0.0.1:9100" let result = AppConfig::load_with_cli(&args); assert!(result.is_ok()); - let config = result.unwrap(); + let _config = result.unwrap(); // Should parse address and port correctly / 应该正确解析地址和端口 // Note: The actual parsing logic depends on implementation // 注意:实际解析逻辑取决于实现 diff --git a/src/spearlet/execution/ai/backends/openai_realtime_ws.rs b/src/spearlet/execution/ai/backends/openai_realtime_ws.rs index 83b06eb7..41a611e3 100644 --- a/src/spearlet/execution/ai/backends/openai_realtime_ws.rs +++ b/src/spearlet/execution/ai/backends/openai_realtime_ws.rs @@ -172,9 +172,7 @@ mod tests { extra: Default::default(), }; let plan = adapter.streaming_plan(&req).unwrap(); - let StreamingPlan::Websocket(p) = plan else { - panic!("expected websocket plan"); - }; + let StreamingPlan::Websocket(p) = plan; assert!(!p .websocket .headers diff --git a/src/spearlet/execution/ai/router/grpc_filter_stream.rs b/src/spearlet/execution/ai/router/grpc_filter_stream.rs index b5a65c1c..82880eda 100644 --- a/src/spearlet/execution/ai/router/grpc_filter_stream.rs +++ b/src/spearlet/execution/ai/router/grpc_filter_stream.rs @@ -13,7 +13,7 @@ use std::collections::HashMap; use std::sync::{Arc, OnceLock}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::time::Duration; use tokio::sync::{mpsc, Semaphore}; use tonic::transport::{Channel, Endpoint}; @@ -51,13 +51,6 @@ fn to_operation_name(op: &Operation) -> &'static str { } } -fn now_ms() -> i64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_millis() as i64 -} - fn requested_model(req: &CanonicalRequestEnvelope) -> Option<&str> { match &req.payload { crate::spearlet::execution::ai::ir::Payload::ChatCompletions(p) => Some(p.model.as_str()), diff --git a/src/spearlet/execution/communication/secret_validation_test.rs b/src/spearlet/execution/communication/secret_validation_test.rs index cc28ca09..581b4e94 100644 --- a/src/spearlet/execution/communication/secret_validation_test.rs +++ b/src/spearlet/execution/communication/secret_validation_test.rs @@ -29,13 +29,13 @@ async fn test_secret_validation_with_execution_manager() { // Create ConnectionManager with validator / 创建带有验证器的 ConnectionManager let connection_config = ConnectionManagerConfig::default(); - let connection_manager = + let _connection_manager = ConnectionManager::new_with_validator(connection_config, Some(secret_validator)); let instance_id = "test-instance-123"; // Test valid authentication / 测试有效认证 - let valid_auth_request = AuthRequest { + let _valid_auth_request = AuthRequest { instance_id: instance_id.to_string(), token: test_secret.to_string(), client_version: "1.0.0".to_string(), @@ -43,16 +43,16 @@ async fn test_secret_validation_with_execution_manager() { extra_params: HashMap::new(), }; - let valid_message = SpearMessage { + let _valid_message = SpearMessage { message_type: MessageType::AuthRequest, request_id: 1, timestamp: SystemTime::now(), - payload: serde_json::to_vec(&valid_auth_request).unwrap(), + payload: serde_json::to_vec(&_valid_auth_request).unwrap(), version: 1, }; // Test invalid authentication (wrong secret) / 测试无效认证(错误的 secret) - let invalid_auth_request = AuthRequest { + let _invalid_auth_request = AuthRequest { instance_id: instance_id.to_string(), token: "wrong-secret".to_string(), client_version: "1.0.0".to_string(), @@ -60,16 +60,16 @@ async fn test_secret_validation_with_execution_manager() { extra_params: HashMap::new(), }; - let invalid_message = SpearMessage { + let _invalid_message = SpearMessage { message_type: MessageType::AuthRequest, request_id: 2, timestamp: SystemTime::now(), - payload: serde_json::to_vec(&invalid_auth_request).unwrap(), + payload: serde_json::to_vec(&_invalid_auth_request).unwrap(), version: 1, }; // Test non-existent instance / 测试不存在的实例 - let nonexistent_auth_request = AuthRequest { + let _nonexistent_auth_request = AuthRequest { instance_id: "non-existent-instance".to_string(), token: test_secret.to_string(), client_version: "1.0.0".to_string(), @@ -77,11 +77,11 @@ async fn test_secret_validation_with_execution_manager() { extra_params: HashMap::new(), }; - let nonexistent_message = SpearMessage { + let _nonexistent_message = SpearMessage { message_type: MessageType::AuthRequest, request_id: 3, timestamp: SystemTime::now(), - payload: serde_json::to_vec(&nonexistent_auth_request).unwrap(), + payload: serde_json::to_vec(&_nonexistent_auth_request).unwrap(), version: 1, }; @@ -110,11 +110,11 @@ async fn test_secret_validation_with_fallback_validator() { // Create ConnectionManager with validator / 创建带有验证器的 ConnectionManager let connection_config = ConnectionManagerConfig::default(); - let connection_manager = + let _connection_manager = ConnectionManager::new_with_validator(connection_config, Some(secret_validator)); // Test valid authentication / 测试有效认证 - let valid_auth_request = AuthRequest { + let _valid_auth_request = AuthRequest { instance_id: "test-instance".to_string(), token: "valid-secret-123".to_string(), client_version: "1.0.0".to_string(), @@ -123,7 +123,7 @@ async fn test_secret_validation_with_fallback_validator() { }; // Test invalid authentication (short secret) / 测试无效认证(短 secret) - let invalid_auth_request = AuthRequest { + let _invalid_auth_request = AuthRequest { instance_id: "test-instance".to_string(), token: "short".to_string(), client_version: "1.0.0".to_string(), @@ -132,7 +132,7 @@ async fn test_secret_validation_with_fallback_validator() { }; // Test invalid authentication (empty secret) / 测试无效认证(空 secret) - let empty_secret_auth_request = AuthRequest { + let _empty_secret_auth_request = AuthRequest { instance_id: "test-instance".to_string(), token: "".to_string(), client_version: "1.0.0".to_string(), @@ -153,10 +153,10 @@ async fn test_secret_validation_with_fallback_validator() { async fn test_secret_validation_without_validator() { // Create ConnectionManager without validator / 创建没有验证器的 ConnectionManager let connection_config = ConnectionManagerConfig::default(); - let connection_manager = ConnectionManager::new_with_validator(connection_config, None); + let _connection_manager = ConnectionManager::new_with_validator(connection_config, None); // Test authentication request / 测试认证请求 - let auth_request = AuthRequest { + let _auth_request = AuthRequest { instance_id: "test-instance".to_string(), token: "any-secret".to_string(), client_version: "1.0.0".to_string(), diff --git a/src/spearlet/execution/host_api/errno.rs b/src/spearlet/execution/host_api/errno.rs index f3a28a9f..ea653d91 100644 --- a/src/spearlet/execution/host_api/errno.rs +++ b/src/spearlet/execution/host_api/errno.rs @@ -1,39 +1,19 @@ -pub const SPEAR_EPERM: i32 = 1; -pub const SPEAR_ENOENT: i32 = 2; +pub const SPEAR_OK: i32 = 0; + pub const SPEAR_EIO: i32 = 5; pub const SPEAR_EBADF: i32 = 9; pub const SPEAR_EAGAIN: i32 = 11; -pub const SPEAR_ENOMEM: i32 = 12; -pub const SPEAR_EFAULT: i32 = 14; pub const SPEAR_EACCES: i32 = 13; +pub const SPEAR_EFAULT: i32 = 14; pub const SPEAR_EINVAL: i32 = 22; pub const SPEAR_ENOSPC: i32 = 28; pub const SPEAR_EPIPE: i32 = 32; -pub const SPEAR_ENOSYS: i32 = 38; -pub const SPEAR_ECONNRESET: i32 = 104; pub const SPEAR_ENOTCONN: i32 = 107; -pub const SPEAR_ETIMEDOUT: i32 = 110; -pub const EPERM: i32 = SPEAR_EPERM; -pub const ENOENT: i32 = SPEAR_ENOENT; pub const EIO: i32 = SPEAR_EIO; pub const EBADF: i32 = SPEAR_EBADF; -pub const EAGAIN: i32 = SPEAR_EAGAIN; -pub const ENOMEM: i32 = SPEAR_ENOMEM; -pub const EFAULT: i32 = SPEAR_EFAULT; pub const EACCES: i32 = SPEAR_EACCES; pub const EINVAL: i32 = SPEAR_EINVAL; -pub const ENOSPC: i32 = SPEAR_ENOSPC; -pub const EPIPE: i32 = SPEAR_EPIPE; -pub const ENOSYS: i32 = SPEAR_ENOSYS; -pub const ECONNRESET: i32 = SPEAR_ECONNRESET; -pub const ENOTCONN: i32 = SPEAR_ENOTCONN; -pub const ETIMEDOUT: i32 = SPEAR_ETIMEDOUT; - -pub const SPEAR_OK: i32 = 0; -pub const SPEAR_ERR_INVALID_FD: i32 = -SPEAR_EBADF; -pub const SPEAR_ERR_INVALID_PTR: i32 = -SPEAR_EFAULT; -pub const SPEAR_ERR_BUFFER_TOO_SMALL: i32 = -SPEAR_ENOSPC; -pub const SPEAR_ERR_INVALID_CMD: i32 = -SPEAR_EINVAL; -pub const SPEAR_ERR_INTERNAL: i32 = -SPEAR_EIO; +#[cfg(test)] +pub const EAGAIN: i32 = SPEAR_EAGAIN; diff --git a/src/spearlet/execution/host_api/ssf.rs b/src/spearlet/execution/host_api/ssf.rs index 669dfccc..086fc138 100644 --- a/src/spearlet/execution/host_api/ssf.rs +++ b/src/spearlet/execution/host_api/ssf.rs @@ -1,53 +1,16 @@ -use crate::spearlet::execution::host_api::errno::EINVAL; - -const SSF_MAGIC: [u8; 4] = *b"SPST"; -const SSF_VERSION_V1: u16 = 1; -const SSF_HEADER_MIN: usize = 32; +use crate::spearlet::execution::host_api::errno::SPEAR_EINVAL; pub(crate) fn parse_ssf_v1_header(frame: &[u8]) -> Result<(u32, u16), i32> { - if frame.len() < SSF_HEADER_MIN { - return Err(-EINVAL); - } - if frame[0..4] != SSF_MAGIC { - return Err(-EINVAL); - } - let version = u16::from_le_bytes([frame[4], frame[5]]); - if version != SSF_VERSION_V1 { - return Err(-EINVAL); - } - let header_len = u16::from_le_bytes([frame[6], frame[7]]) as usize; - if header_len < SSF_HEADER_MIN || frame.len() < header_len { - return Err(-EINVAL); - } - let stream_id = u32::from_le_bytes([frame[12], frame[13], frame[14], frame[15]]); - let meta_len = u32::from_le_bytes([frame[24], frame[25], frame[26], frame[27]]) as usize; - let data_len = u32::from_le_bytes([frame[28], frame[29], frame[30], frame[31]]) as usize; - let remain = frame.len().saturating_sub(header_len); - if meta_len.saturating_add(data_len) != remain { - return Err(-EINVAL); - } - let msg_type = u16::from_le_bytes([frame[8], frame[9]]); - Ok((stream_id, msg_type)) + let hdr = spear_ssf::parse_v1_header(frame).map_err(|_| -SPEAR_EINVAL)?; + Ok((hdr.stream_id, hdr.msg_type)) } +#[cfg(test)] pub(crate) fn build_ssf_v1_frame( stream_id: u32, msg_type: u16, meta: &[u8], data: &[u8], ) -> Vec { - let header_len: u16 = SSF_HEADER_MIN as u16; - let mut out = Vec::with_capacity(header_len as usize + meta.len() + data.len()); - out.extend_from_slice(&SSF_MAGIC); - out.extend_from_slice(&SSF_VERSION_V1.to_le_bytes()); - out.extend_from_slice(&header_len.to_le_bytes()); - out.extend_from_slice(&msg_type.to_le_bytes()); - out.extend_from_slice(&0u16.to_le_bytes()); - out.extend_from_slice(&stream_id.to_le_bytes()); - out.extend_from_slice(&1u64.to_le_bytes()); - out.extend_from_slice(&(meta.len() as u32).to_le_bytes()); - out.extend_from_slice(&(data.len() as u32).to_le_bytes()); - out.extend_from_slice(meta); - out.extend_from_slice(data); - out + spear_ssf::build_v1_frame(stream_id, msg_type, 0, 1, meta, data) } diff --git a/src/spearlet/execution/host_api/user_stream.rs b/src/spearlet/execution/host_api/user_stream.rs index 5d2dddb0..1c3697a8 100644 --- a/src/spearlet/execution/host_api/user_stream.rs +++ b/src/spearlet/execution/host_api/user_stream.rs @@ -201,8 +201,7 @@ impl ExecutionUserStreamHub { let Some(entry) = table.get(fd) else { continue; }; - let mut notify = false; - { + let notify = { let mut e = match entry.lock() { Ok(v) => v, Err(_) => continue, @@ -216,8 +215,8 @@ impl ExecutionUserStreamHub { .push_back(encode_ctl_event(stream_id, CTL_EVENT_STREAM_CONNECTED)); } e.poll_mask = compute_user_stream_ctl_poll_mask(&e); - notify = e.poll_mask.bits() != old.bits(); - } + e.poll_mask.bits() != old.bits() + }; if notify { table.notify_watchers(fd); } @@ -234,8 +233,7 @@ impl ExecutionUserStreamHub { let Some(entry) = table.get(fd) else { continue; }; - let mut notify = false; - { + let notify = { let mut e = match entry.lock() { Ok(v) => v, Err(_) => continue, @@ -249,8 +247,8 @@ impl ExecutionUserStreamHub { .push_back(encode_ctl_event(0, CTL_EVENT_SESSION_CLOSED)); } e.poll_mask = compute_user_stream_ctl_poll_mask(&e); - notify = e.poll_mask.bits() != old.bits(); - } + e.poll_mask.bits() != old.bits() + }; if notify { table.notify_watchers(fd); } diff --git a/src/spearlet/execution/manager.rs b/src/spearlet/execution/manager.rs index cefdea0c..e9fe24e3 100644 --- a/src/spearlet/execution/manager.rs +++ b/src/spearlet/execution/manager.rs @@ -98,8 +98,6 @@ struct ExecutionWorkItem { pub execution_context: ExecutionContext, /// Response sender / 响应发送器 pub response_sender: oneshot::Sender>, - /// Request timestamp / 请求时间戳 - pub timestamp: SystemTime, } #[derive(Debug, Clone, Serialize)] @@ -349,8 +347,6 @@ impl TaskExecutionManager { let (response_sender, response_receiver) = oneshot::channel(); - let timestamp = SystemTime::now(); - self.executions.insert( execution_id.clone(), super::ExecutionResponse { @@ -374,7 +370,6 @@ impl TaskExecutionManager { task_id: request.task_id.clone(), execution_context, response_sender, - timestamp, }; // Send to execution loop / 发送到执行循环 diff --git a/src/spearlet/function_service.rs b/src/spearlet/function_service.rs index cf65ba60..f076ba24 100644 --- a/src/spearlet/function_service.rs +++ b/src/spearlet/function_service.rs @@ -79,8 +79,6 @@ pub struct FunctionServiceStats { /// Function service implementation / 函数服务实现 pub struct FunctionServiceImpl { - /// Service start time / 服务启动时间 - start_time: SystemTime, /// Task execution manager / 任务执行管理器 execution_manager: Arc, /// Instance pool / 实例池 @@ -134,7 +132,6 @@ impl FunctionServiceImpl { })); Ok(Self { - start_time: SystemTime::now(), execution_manager, instance_pool, stats, @@ -150,11 +147,6 @@ impl FunctionServiceImpl { Uuid::new_v4().to_string() } - /// Generate task ID / 生成任务ID - fn generate_task_id(&self) -> String { - Uuid::new_v4().to_string() - } - fn to_proto_status(status: &str) -> i32 { match status { "pending" => ExecutionStatus::Pending as i32, diff --git a/src/spearlet/http_gateway_test.rs b/src/spearlet/http_gateway_test.rs index 7a607d6a..2afdcecf 100644 --- a/src/spearlet/http_gateway_test.rs +++ b/src/spearlet/http_gateway_test.rs @@ -108,7 +108,7 @@ async fn test_gateway_config() { let config = Arc::new(config); let (object_client, invocation_client, execution_client) = create_dummy_grpc_clients(config.grpc.addr); - let gateway = HttpGateway::new( + let _gateway = HttpGateway::new( config, health_service, function_service, @@ -139,7 +139,7 @@ async fn test_gateway_with_different_storage_sizes() { let config = Arc::new(config); let (object_client, invocation_client, execution_client) = create_dummy_grpc_clients(config.grpc.addr); - let gateway = HttpGateway::new( + let _gateway = HttpGateway::new( config, health_service, function_service, @@ -168,7 +168,7 @@ async fn test_gateway_swagger_enabled() { let config = Arc::new(config); let (object_client, invocation_client, execution_client) = create_dummy_grpc_clients(config.grpc.addr); - let gateway = HttpGateway::new( + let _gateway = HttpGateway::new( config, health_service, function_service, @@ -196,7 +196,7 @@ async fn test_gateway_swagger_disabled() { let config = Arc::new(config); let (object_client, invocation_client, execution_client) = create_dummy_grpc_clients(config.grpc.addr); - let gateway = HttpGateway::new( + let _gateway = HttpGateway::new( config, health_service, function_service, @@ -224,7 +224,7 @@ async fn test_invalid_http_address() { let config = Arc::new(config); let (object_client, invocation_client, execution_client) = create_dummy_grpc_clients(config.grpc.addr); - let gateway = HttpGateway::new( + let _gateway = HttpGateway::new( config, health_service, function_service, @@ -272,7 +272,7 @@ async fn test_multiple_gateways() { create_dummy_grpc_clients(config1.grpc.addr); let (object_client2, invocation_client2, execution_client2) = create_dummy_grpc_clients(config2.grpc.addr); - let gateway1 = HttpGateway::new( + let _gateway1 = HttpGateway::new( config1, health_service1, function_service1, @@ -280,7 +280,7 @@ async fn test_multiple_gateways() { invocation_client1, execution_client1, ); - let gateway2 = HttpGateway::new( + let _gateway2 = HttpGateway::new( config2, health_service2, function_service2, @@ -857,7 +857,7 @@ mod integration_tests { // Create gateway / 创建网关 let (object_client, invocation_client, execution_client) = create_dummy_grpc_clients(config.grpc.addr); - let gateway = HttpGateway::new( + let _gateway = HttpGateway::new( config.clone(), health_service.clone(), function_service, @@ -945,7 +945,7 @@ mod integration_tests { let config = Arc::new(config); let (object_client, invocation_client, execution_client) = create_dummy_grpc_clients(config.grpc.addr); - let gateway = HttpGateway::new( + let _gateway = HttpGateway::new( config, health_service, function_service, diff --git a/src/spearlet/mcp/registry_sync.rs b/src/spearlet/mcp/registry_sync.rs index 55dd1dd5..08f32f6d 100644 --- a/src/spearlet/mcp/registry_sync.rs +++ b/src/spearlet/mcp/registry_sync.rs @@ -168,7 +168,7 @@ async fn sync_loop( cancel: CancellationToken, ) { let mut backoff_ms = config.sms_connect_retry_ms.max(200); - let mut watch_cursor_revision: Option = None; + let mut watch_cursor_revision: u64; let mut poll = interval(Duration::from_secs(60)); let Some(channel) = sms_channel else { @@ -184,7 +184,7 @@ async fn sync_loop( match refresh_once(&config, &mut client, &cache).await { Ok(r) => { - watch_cursor_revision = Some(r); + watch_cursor_revision = r; debug!(watch_cursor_revision, "MCP registry snapshot refreshed"); backoff_ms = config.sms_connect_retry_ms.max(200); } @@ -205,7 +205,7 @@ async fn sync_loop( let mut watch_stream = match tokio::time::timeout( per_attempt, client.watch_mcp_servers(WatchMcpServersRequest { - since_revision: watch_cursor_revision.unwrap_or(0), + since_revision: watch_cursor_revision, }), ) .await @@ -226,16 +226,16 @@ async fn sync_loop( _ = cancel.cancelled() => return, _ = poll.tick() => { if let Ok(r) = refresh_once(&config, &mut client, &cache).await { - watch_cursor_revision = Some(r); + watch_cursor_revision = r; } } msg = watch_stream.message() => { match msg { Ok(Some(resp)) => { if let Some(event) = resp.event { - if event.revision > watch_cursor_revision.unwrap_or(0) { + if event.revision > watch_cursor_revision { if let Ok(r) = refresh_once(&config, &mut client, &cache).await { - watch_cursor_revision = Some(r); + watch_cursor_revision = r; } } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 428adc6a..a7ced843 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,5 +1,6 @@ use spear_next::spearlet::config::{AppConfig, SpearletConfig}; +#[allow(dead_code)] pub struct ResolvedBackend { pub name: String, pub base_url: String, @@ -52,10 +53,12 @@ fn resolve_backend(op: &str, transport: &str, kinds: &[&str]) -> Option Option { resolve_backend("chat_completions", "http", &["openai_chat_completion"]) } +#[allow(dead_code)] pub fn resolve_realtime_asr_backend() -> Option { resolve_backend("speech_to_text", "websocket", &["openai_realtime_ws"]) } diff --git a/tests/placement_spillback_e2e_tests.rs b/tests/placement_spillback_e2e_tests.rs index 95e89bf5..947a344b 100644 --- a/tests/placement_spillback_e2e_tests.rs +++ b/tests/placement_spillback_e2e_tests.rs @@ -224,24 +224,6 @@ async fn test_admin_execution_spillback_and_feedback_affects_next_placement() { .await .unwrap(); - let backend_registry_client = - spear_next::proto::sms::backend_registry_service_client::BackendRegistryServiceClient::connect( - sms_url.clone(), - ) - .await - .unwrap(); - let backend_registry_client = - spear_next::proto::sms::backend_registry_service_client::BackendRegistryServiceClient::connect( - sms_url.clone(), - ) - .await - .unwrap(); - let backend_registry_client = - spear_next::proto::sms::backend_registry_service_client::BackendRegistryServiceClient::connect( - sms_url.clone(), - ) - .await - .unwrap(); let backend_registry_client = spear_next::proto::sms::backend_registry_service_client::BackendRegistryServiceClient::connect( sms_url.clone(), diff --git a/tests/web_admin_integration_tests.rs b/tests/web_admin_integration_tests.rs index bb08990d..5af49511 100644 --- a/tests/web_admin_integration_tests.rs +++ b/tests/web_admin_integration_tests.rs @@ -62,30 +62,6 @@ async fn test_admin_list_nodes_empty() { .await .unwrap(); - let backend_registry_client = - spear_next::proto::sms::backend_registry_service_client::BackendRegistryServiceClient::connect( - grpc_url.clone(), - ) - .await - .unwrap(); - let backend_registry_client = - spear_next::proto::sms::backend_registry_service_client::BackendRegistryServiceClient::connect( - grpc_url.clone(), - ) - .await - .unwrap(); - let backend_registry_client = - spear_next::proto::sms::backend_registry_service_client::BackendRegistryServiceClient::connect( - grpc_url.clone(), - ) - .await - .unwrap(); - let backend_registry_client = - spear_next::proto::sms::backend_registry_service_client::BackendRegistryServiceClient::connect( - grpc_url.clone(), - ) - .await - .unwrap(); let backend_registry_client = spear_next::proto::sms::backend_registry_service_client::BackendRegistryServiceClient::connect( grpc_url.clone(), @@ -230,24 +206,6 @@ async fn test_admin_list_nodes_filter_and_sort() { ) .await .unwrap(); - let instance_registry_client = - spear_next::proto::sms::instance_registry_service_client::InstanceRegistryServiceClient::connect( - grpc_url.clone(), - ) - .await - .unwrap(); - let execution_registry_client = - spear_next::proto::sms::execution_registry_service_client::ExecutionRegistryServiceClient::connect( - grpc_url.clone(), - ) - .await - .unwrap(); - let execution_index_client = - spear_next::proto::sms::execution_index_service_client::ExecutionIndexServiceClient::connect( - grpc_url.clone(), - ) - .await - .unwrap(); let model_deployment_registry_client = spear_next::proto::sms::model_deployment_registry_service_client::ModelDeploymentRegistryServiceClient::connect( grpc_url.clone(), diff --git a/web-console/src/App.tsx b/web-console/src/App.tsx index 98567e8c..23569d51 100644 --- a/web-console/src/App.tsx +++ b/web-console/src/App.tsx @@ -38,6 +38,7 @@ export default function App() { const [activeId, setActiveId] = useState(() => loadActiveId() ?? '') const clientMapRef = useRef>(new Map()) + const autoConnectKeyRef = useRef>(new Map()) const endRef = useRef(null) const active = useMemo( @@ -91,6 +92,7 @@ export default function App() { client.disconnect() clientMapRef.current.delete(conversationId) } + autoConnectKeyRef.current.delete(conversationId) setConversations((prev) => prev.map((c) => { if (c.id !== conversationId) return c @@ -276,7 +278,7 @@ export default function App() { }) clientMapRef.current.set(conversationId, client) try { - await client.connect(wsUrl, subprotocol ? { subprotocol } : undefined) + await client.connect(wsUrl, subprotocol ? { subprotocol, autoOpenStreamId: DEFAULT_STREAM_ID } : { autoOpenStreamId: DEFAULT_STREAM_ID }) } catch (e) { if (clientMapRef.current.get(conversationId) === client) { clientMapRef.current.delete(conversationId) @@ -291,6 +293,31 @@ export default function App() { } }, [appendAssistantChunk, disconnectConversation]) + useEffect(() => { + // Auto-connect when a target is already selected (show stream output immediately). + // 若对话已选择 target,则自动建立连接(便于立即看到 stream 输出)。 + if (!activeId || !active) return + if (active.conn_status !== 'disconnected') return + if (active.conn_error) return + if (clientMapRef.current.get(activeId)) return + const target = + active.connect_kind === 'endpoint' + ? active.gatewayEndpoint.trim() + ? ({ kind: 'endpoint', gatewayEndpoint: active.gatewayEndpoint } as const) + : null + : active.executionId.trim() + ? ({ kind: 'execution', executionId: active.executionId } as const) + : null + if (!target) return + const key = + target.kind === 'endpoint' + ? `endpoint:${target.gatewayEndpoint.trim()}` + : `execution:${target.executionId.trim()}` + if (autoConnectKeyRef.current.get(activeId) === key) return + autoConnectKeyRef.current.set(activeId, key) + void connectTo(activeId, target) + }, [active, activeId, connectTo]) + const send = useCallback(async () => { const text = input.trim() if (!text) return @@ -314,6 +341,7 @@ export default function App() { }), ) client.sendText(DEFAULT_STREAM_ID, text) + client.sendCommit(DEFAULT_STREAM_ID) }, [activeId, input]) return ( diff --git a/web-console/src/spearStream.ts b/web-console/src/spearStream.ts index 141ce9d5..1df6fa67 100644 --- a/web-console/src/spearStream.ts +++ b/web-console/src/spearStream.ts @@ -1,7 +1,7 @@ // Spear user stream client (browser). // Spear 用户流客户端(浏览器)。 -import { decodeSsfV1Frame, encodeSsfV1Frame } from './ssf' +import { decodeSsfV1Frame, encodeSsfV1Frame, SsfMsgType } from './ssf' export type StreamSession = { execution_id: string @@ -38,13 +38,23 @@ export class SpearStreamClient { } } - async connect(wsUrl: string, options?: { subprotocol?: string }): Promise { + async connect(wsUrl: string, options?: { subprotocol?: string; autoOpenStreamId?: number }): Promise { this.disconnect() const ws = options?.subprotocol ? new WebSocket(wsUrl, options.subprotocol) : new WebSocket(wsUrl) ws.binaryType = 'arraybuffer' this.ws = ws - ws.onopen = () => this.callbacks.onOpen?.() + ws.onopen = () => { + this.callbacks.onOpen?.() + const sid = options?.autoOpenStreamId + if (typeof sid === 'number' && Number.isFinite(sid) && sid > 0) { + try { + this.openStream(sid) + } catch { + // ignore + } + } + } ws.onclose = (ev) => this.callbacks.onClose?.(ev) ws.onerror = (ev) => this.callbacks.onError?.(ev) ws.onmessage = async (ev) => { @@ -68,19 +78,45 @@ export class SpearStreamClient { } } + openStream(streamId: number): void { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error('ws not connected') + const meta = new TextEncoder().encode('{}') + const payload = encodeSsfV1Frame({ + streamId, + msgType: SsfMsgType.CTRL, + seq: this.seq++, + meta, + data: new Uint8Array(), + }) + this.ws.send(payload) + } + sendText(streamId: number, text: string): void { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error('ws not connected') const meta = new TextEncoder().encode('{}') const data = new TextEncoder().encode(text) const payload = encodeSsfV1Frame({ streamId, - msgType: 2, + msgType: SsfMsgType.DATA, seq: this.seq++, meta, data, }) this.ws.send(payload) } + + sendCommit(streamId: number): void { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error('ws not connected') + const meta = new TextEncoder().encode('{}') + const payload = encodeSsfV1Frame({ + streamId, + msgType: SsfMsgType.COMMIT, + seq: this.seq++, + meta, + data: new Uint8Array(), + }) + this.ws.send(payload) + } } export async function createStreamSession(params: { diff --git a/web-console/src/ssf.test.ts b/web-console/src/ssf.test.ts index 4e855b5b..f91354e7 100644 --- a/web-console/src/ssf.test.ts +++ b/web-console/src/ssf.test.ts @@ -2,7 +2,7 @@ // SSF v1 测试。 import { describe, expect, it } from 'vitest' -import { decodeSsfV1Frame, encodeSsfV1Frame } from './ssf' +import { decodeSsfV1Frame, encodeSsfV1Frame, SsfMsgType } from './ssf' describe('SSF v1', () => { it('encodes and decodes a frame', () => { @@ -11,7 +11,7 @@ describe('SSF v1', () => { const bytes = encodeSsfV1Frame({ streamId: 1, - msgType: 2, + msgType: SsfMsgType.DATA, seq: 123n, meta, data, @@ -20,10 +20,9 @@ describe('SSF v1', () => { const frame = decodeSsfV1Frame(bytes) expect(frame.version).toBe(1) expect(frame.streamId).toBe(1) - expect(frame.msgType).toBe(2) + expect(frame.msgType).toBe(SsfMsgType.DATA) expect(frame.seq).toBe(123n) expect(new TextDecoder().decode(frame.meta)).toBe('{"k":"v"}') expect(new TextDecoder().decode(frame.data)).toBe('hello') }) }) - diff --git a/web-console/src/ssf/constants.ts b/web-console/src/ssf/constants.ts new file mode 100644 index 00000000..844b7448 --- /dev/null +++ b/web-console/src/ssf/constants.ts @@ -0,0 +1,15 @@ +// SSF v1 constants (browser). +// SSF v1 常量定义(浏览器)。 + +export const SSF_MAGIC = [0x53, 0x50, 0x53, 0x54] as const +export const SSF_VERSION_V1 = 1 as const +export const SSF_HEADER_LEN_V1 = 32 as const + +// SSF v1 msg_type values. +// SSF v1 的 msg_type 定义。 +export const SsfMsgType = { + CTRL: 1, + DATA: 2, + COMMIT: 3, +} as const + diff --git a/web-console/src/ssf.ts b/web-console/src/ssf/frame.ts similarity index 86% rename from web-console/src/ssf.ts rename to web-console/src/ssf/frame.ts index 96037f7e..ddfcd06e 100644 --- a/web-console/src/ssf.ts +++ b/web-console/src/ssf/frame.ts @@ -1,5 +1,7 @@ -// SSF v1 helpers (browser). -// SSF v1 协议辅助函数(浏览器)。 +// SSF v1 frame codec (browser). +// SSF v1 帧编解码(浏览器)。 + +import { SSF_HEADER_LEN_V1, SSF_MAGIC, SSF_VERSION_V1 } from './constants' export type SsfV1Frame = { version: number @@ -12,8 +14,6 @@ export type SsfV1Frame = { data: Uint8Array } -const SSF_MAGIC = [0x53, 0x50, 0x53, 0x54] - export function encodeSsfV1Frame(params: { streamId: number msgType: number @@ -22,7 +22,7 @@ export function encodeSsfV1Frame(params: { meta?: Uint8Array data?: Uint8Array }): Uint8Array { - const headerLen = 32 + const headerLen = SSF_HEADER_LEN_V1 const meta = params.meta ?? new Uint8Array() const data = params.data ?? new Uint8Array() @@ -30,7 +30,7 @@ export function encodeSsfV1Frame(params: { const dv = new DataView(out.buffer, out.byteOffset, out.byteLength) out.set(SSF_MAGIC, 0) - dv.setUint16(4, 1, true) + dv.setUint16(4, SSF_VERSION_V1, true) dv.setUint16(6, headerLen, true) dv.setUint16(8, params.msgType, true) dv.setUint16(10, params.flags ?? 0, true) @@ -46,7 +46,7 @@ export function encodeSsfV1Frame(params: { export function decodeSsfV1Frame(buf: ArrayBuffer | Uint8Array): SsfV1Frame { const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf) - if (u8.length < 32) throw new Error('SSF frame too short') + if (u8.length < SSF_HEADER_LEN_V1) throw new Error('SSF frame too short') if (u8[0] !== SSF_MAGIC[0] || u8[1] !== SSF_MAGIC[1] || u8[2] !== SSF_MAGIC[2] || u8[3] !== SSF_MAGIC[3]) { throw new Error('invalid SSF magic') } diff --git a/web-console/src/ssf/index.ts b/web-console/src/ssf/index.ts new file mode 100644 index 00000000..268522c7 --- /dev/null +++ b/web-console/src/ssf/index.ts @@ -0,0 +1,6 @@ +// SSF v1 module exports (browser). +// SSF v1 模块导出(浏览器)。 + +export * from './constants' +export * from './frame' +