diff --git a/src/backend/main.py b/src/backend/main.py index c72b0a6e..bfd7cb5a 100644 --- a/src/backend/main.py +++ b/src/backend/main.py @@ -95,14 +95,6 @@ async def stream_cluster_status(): ) -@app.post("/v1/completions") -async def openai_v1_completions(raw_request: Request): - request_data = await raw_request.json() - request_id = uuid.uuid4() - received_ts = time.time() - return await request_handler.v1_completions(request_data, request_id, received_ts) - - @app.post("/v1/chat/completions") async def openai_v1_chat_completions(raw_request: Request): request_data = await raw_request.json() @@ -145,6 +137,7 @@ async def serve_index(): f"/ip4/0.0.0.0/udp/{args.udp_port}/quic-v1", ], announce_maddrs=args.announce_maddrs, + http_port=args.port, ) request_handler.set_scheduler_manage(scheduler_manage) diff --git a/src/backend/server/constants.py b/src/backend/server/constants.py index fd4c5f22..74cded7a 100644 --- a/src/backend/server/constants.py +++ b/src/backend/server/constants.py @@ -2,6 +2,7 @@ CLUSTER_STATUS_WAITING = "waiting" CLUSTER_STATUS_AVAILABLE = "available" CLUSTER_STATUS_REBALANCING = "rebalancing" +CLUSTER_STATUS_FAILED = "failed" # Node status constants NODE_STATUS_WAITING = "waiting" diff --git a/src/backend/server/rpc_connection_handler.py b/src/backend/server/rpc_connection_handler.py index 50185d86..0fee9922 100644 --- a/src/backend/server/rpc_connection_handler.py +++ b/src/backend/server/rpc_connection_handler.py @@ -1,6 +1,6 @@ import time -from lattica import ConnectionHandler, Lattica, rpc_method, rpc_stream +from lattica import ConnectionHandler, Lattica, rpc_method, rpc_stream, rpc_stream_iter from parallax_utils.logging_config import get_logger from scheduling.node import Node, NodeHardwareInfo @@ -8,6 +8,10 @@ logger = get_logger(__name__) +import json + +import httpx + class RPCConnectionHandler(ConnectionHandler): """ @@ -19,10 +23,12 @@ def __init__( self, lattica: Lattica, scheduler: Scheduler, + http_port: int, ): # Initialize the base class super().__init__(lattica) self.scheduler = scheduler + self.http_port = http_port @rpc_stream def node_join(self, message): @@ -79,6 +85,47 @@ def node_update(self, message): logger.exception(f"node_update error: {e}") return {} + @rpc_stream_iter + def chat_completion( + self, + request, + ): + """Handle chat completion request""" + logger.debug(f"Chat completion request: {request}, type: {type(request)}") + try: + with httpx.Client(timeout=10 * 60, proxy=None, trust_env=False) as client: + if request.get("stream", False): + with client.stream( + "POST", + f"http://localhost:{self.http_port}/v1/chat/completions", + json=request, + ) as response: + for chunk in response.iter_bytes(): + if chunk: + yield chunk + else: + response = client.post( + f"http://localhost:{self.http_port}/v1/chat/completions", json=request + ).json() + yield json.dumps(response).encode() + except Exception as e: + logger.exception(f"Error in chat completion: {e}") + yield b"internal server error" + + @rpc_stream_iter + def cluster_status(self): + try: + with httpx.Client(timeout=10 * 60, proxy=None, trust_env=False) as client: + with client.stream( + "GET", f"http://localhost:{self.http_port}/cluster/status" + ) as response: + for chunk in response.iter_bytes(): + if chunk: + yield chunk + except Exception as e: + logger.exception(f"Error in cluster status: {e}") + yield json.dumps({"error": "internal server error"}).encode() + def wait_layer_allocation(self, current_node_id, wait_seconds): start_time = time.time() while True: diff --git a/src/backend/server/scheduler_manage.py b/src/backend/server/scheduler_manage.py index 582a9dc4..bdb78982 100644 --- a/src/backend/server/scheduler_manage.py +++ b/src/backend/server/scheduler_manage.py @@ -31,6 +31,7 @@ def __init__( dht_prefix: str = "gradient", host_maddrs: List[str] = [], announce_maddrs: List[str] = [], + http_port: int = 3001, ): """Initialize the manager with networking bootstrap parameters.""" self.initial_peers = initial_peers @@ -38,7 +39,7 @@ def __init__( self.dht_prefix = dht_prefix self.host_maddrs = host_maddrs self.announce_maddrs = announce_maddrs - + self.http_port = http_port self.model_name = None self.init_nodes_num = None self.scheduler = None @@ -190,6 +191,7 @@ def _start_lattica(self): self.connection_handler = RPCConnectionHandler( lattica=self.lattica, scheduler=self.scheduler, + http_port=self.http_port, ) logger.debug("RPCConnectionHandler initialized") diff --git a/src/frontend/chat.html b/src/frontend/chat.html new file mode 100644 index 00000000..0ad191f8 --- /dev/null +++ b/src/frontend/chat.html @@ -0,0 +1,13 @@ + + + + + + + CHAT Parallax by Gradient + + +
+ + + diff --git a/src/frontend/dist/assets/App-BqDXU43s.js b/src/frontend/dist/assets/App-BqDXU43s.js new file mode 100644 index 00000000..7b3a8cbf --- /dev/null +++ b/src/frontend/dist/assets/App-BqDXU43s.js @@ -0,0 +1,320 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/setup-M4Nab0me.js","assets/main-layout-p0BmTwP8.js","assets/main-layout-DVneG3Rq.css","assets/join-DNfE0X8-.js","assets/chat-C9ZhywtF.js"])))=>i.map(i=>d[i]); +function N2(n,r){for(var l=0;lo[u]})}}}return Object.freeze(Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}))}(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const u of document.querySelectorAll('link[rel="modulepreload"]'))o(u);new MutationObserver(u=>{for(const c of u)if(c.type==="childList")for(const d of c.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&o(d)}).observe(document,{childList:!0,subtree:!0});function l(u){const c={};return u.integrity&&(c.integrity=u.integrity),u.referrerPolicy&&(c.referrerPolicy=u.referrerPolicy),u.crossOrigin==="use-credentials"?c.credentials="include":u.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function o(u){if(u.ep)return;u.ep=!0;const c=l(u);fetch(u.href,c)}})();function Ba(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var Of={exports:{}},vl={};/** + * @license React + * react-jsx-runtime.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var ng;function L2(){if(ng)return vl;ng=1;var n=Symbol.for("react.transitional.element"),r=Symbol.for("react.fragment");function l(o,u,c){var d=null;if(c!==void 0&&(d=""+c),u.key!==void 0&&(d=""+u.key),"key"in u){c={};for(var h in u)h!=="key"&&(c[h]=u[h])}else c=u;return u=c.ref,{$$typeof:n,type:o,key:d,ref:u!==void 0?u:null,props:c}}return vl.Fragment=r,vl.jsx=l,vl.jsxs=l,vl}var ag;function B2(){return ag||(ag=1,Of.exports=L2()),Of.exports}var W=B2(),Rf={exports:{}},bl={},Df={exports:{}},_f={};/** + * @license React + * scheduler.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var rg;function j2(){return rg||(rg=1,(function(n){function r(A,U){var X=A.length;A.push(U);e:for(;0>>1,w=A[le];if(0>>1;leu(ie,X))oeu(fe,ie)?(A[le]=fe,A[oe]=X,le=oe):(A[le]=ie,A[re]=X,le=re);else if(oeu(fe,X))A[le]=fe,A[oe]=X,le=oe;else break e}}return U}function u(A,U){var X=A.sortIndex-U.sortIndex;return X!==0?X:A.id-U.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var c=performance;n.unstable_now=function(){return c.now()}}else{var d=Date,h=d.now();n.unstable_now=function(){return d.now()-h}}var p=[],m=[],y=1,b=null,E=3,O=!1,C=!1,T=!1,M=!1,D=typeof setTimeout=="function"?setTimeout:null,k=typeof clearTimeout=="function"?clearTimeout:null,$=typeof setImmediate<"u"?setImmediate:null;function B(A){for(var U=l(m);U!==null;){if(U.callback===null)o(m);else if(U.startTime<=A)o(m),U.sortIndex=U.expirationTime,r(p,U);else break;U=l(m)}}function _(A){if(T=!1,B(A),!C)if(l(p)!==null)C=!0,N||(N=!0,S());else{var U=l(m);U!==null&&Z(_,U.startTime-A)}}var N=!1,V=-1,F=5,Q=-1;function I(){return M?!0:!(n.unstable_now()-QA&&I());){var le=b.callback;if(typeof le=="function"){b.callback=null,E=b.priorityLevel;var w=le(b.expirationTime<=A);if(A=n.unstable_now(),typeof w=="function"){b.callback=w,B(A),U=!0;break t}b===l(p)&&o(p),B(A)}else o(p);b=l(p)}if(b!==null)U=!0;else{var Y=l(m);Y!==null&&Z(_,Y.startTime-A),U=!1}}break e}finally{b=null,E=X,O=!1}U=void 0}}finally{U?S():N=!1}}}var S;if(typeof $=="function")S=function(){$(K)};else if(typeof MessageChannel<"u"){var J=new MessageChannel,G=J.port2;J.port1.onmessage=K,S=function(){G.postMessage(null)}}else S=function(){D(K,0)};function Z(A,U){V=D(function(){A(n.unstable_now())},U)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(A){A.callback=null},n.unstable_forceFrameRate=function(A){0>A||125le?(A.sortIndex=X,r(m,A),l(p)===null&&A===l(m)&&(T?(k(V),V=-1):T=!0,Z(_,X-le))):(A.sortIndex=w,r(p,A),C||O||(C=!0,N||(N=!0,S()))),A},n.unstable_shouldYield=I,n.unstable_wrapCallback=function(A){var U=E;return function(){var X=E;E=U;try{return A.apply(this,arguments)}finally{E=X}}}})(_f)),_f}var ig;function U2(){return ig||(ig=1,Df.exports=j2()),Df.exports}var kf={exports:{}},ye={};/** + * @license React + * react.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var lg;function H2(){if(lg)return ye;lg=1;var n=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),l=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),u=Symbol.for("react.profiler"),c=Symbol.for("react.consumer"),d=Symbol.for("react.context"),h=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),m=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),b=Symbol.iterator;function E(w){return w===null||typeof w!="object"?null:(w=b&&w[b]||w["@@iterator"],typeof w=="function"?w:null)}var O={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},C=Object.assign,T={};function M(w,Y,re){this.props=w,this.context=Y,this.refs=T,this.updater=re||O}M.prototype.isReactComponent={},M.prototype.setState=function(w,Y){if(typeof w!="object"&&typeof w!="function"&&w!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,w,Y,"setState")},M.prototype.forceUpdate=function(w){this.updater.enqueueForceUpdate(this,w,"forceUpdate")};function D(){}D.prototype=M.prototype;function k(w,Y,re){this.props=w,this.context=Y,this.refs=T,this.updater=re||O}var $=k.prototype=new D;$.constructor=k,C($,M.prototype),$.isPureReactComponent=!0;var B=Array.isArray,_={H:null,A:null,T:null,S:null,V:null},N=Object.prototype.hasOwnProperty;function V(w,Y,re,ie,oe,fe){return re=fe.ref,{$$typeof:n,type:w,key:Y,ref:re!==void 0?re:null,props:fe}}function F(w,Y){return V(w.type,Y,void 0,void 0,void 0,w.props)}function Q(w){return typeof w=="object"&&w!==null&&w.$$typeof===n}function I(w){var Y={"=":"=0",":":"=2"};return"$"+w.replace(/[=:]/g,function(re){return Y[re]})}var K=/\/+/g;function S(w,Y){return typeof w=="object"&&w!==null&&w.key!=null?I(""+w.key):Y.toString(36)}function J(){}function G(w){switch(w.status){case"fulfilled":return w.value;case"rejected":throw w.reason;default:switch(typeof w.status=="string"?w.then(J,J):(w.status="pending",w.then(function(Y){w.status==="pending"&&(w.status="fulfilled",w.value=Y)},function(Y){w.status==="pending"&&(w.status="rejected",w.reason=Y)})),w.status){case"fulfilled":return w.value;case"rejected":throw w.reason}}throw w}function Z(w,Y,re,ie,oe){var fe=typeof w;(fe==="undefined"||fe==="boolean")&&(w=null);var ue=!1;if(w===null)ue=!0;else switch(fe){case"bigint":case"string":case"number":ue=!0;break;case"object":switch(w.$$typeof){case n:case r:ue=!0;break;case y:return ue=w._init,Z(ue(w._payload),Y,re,ie,oe)}}if(ue)return oe=oe(w),ue=ie===""?"."+S(w,0):ie,B(oe)?(re="",ue!=null&&(re=ue.replace(K,"$&/")+"/"),Z(oe,Y,re,"",function(Ie){return Ie})):oe!=null&&(Q(oe)&&(oe=F(oe,re+(oe.key==null||w&&w.key===oe.key?"":(""+oe.key).replace(K,"$&/")+"/")+ue)),Y.push(oe)),1;ue=0;var Oe=ie===""?".":ie+":";if(B(w))for(var Se=0;Se"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(r){console.error(r)}}return n(),zf.exports=Y2(),zf.exports}/** + * @license React + * react-dom-client.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var cg;function q2(){if(cg)return bl;cg=1;var n=U2(),r=_d(),l=Hy();function o(e){var t="https://react.dev/errors/"+e;if(1w||(e.current=le[w],le[w]=null,w--)}function ie(e,t){w++,le[w]=e.current,e.current=t}var oe=Y(null),fe=Y(null),ue=Y(null),Oe=Y(null);function Se(e,t){switch(ie(ue,t),ie(fe,e),ie(oe,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?D0(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=D0(t),e=_0(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}re(oe),ie(oe,e)}function Ie(){re(oe),re(fe),re(ue)}function Xe(e){e.memoizedState!==null&&ie(Oe,e);var t=oe.current,a=_0(t,e.type);t!==a&&(ie(fe,e),ie(oe,a))}function lt(e){fe.current===e&&(re(oe),re(fe)),Oe.current===e&&(re(Oe),hl._currentValue=X)}var bt=Object.prototype.hasOwnProperty,wt=n.unstable_scheduleCallback,Nt=n.unstable_cancelCallback,yn=n.unstable_shouldYield,Un=n.unstable_requestPaint,Je=n.unstable_now,Lt=n.unstable_getCurrentPriorityLevel,ft=n.unstable_ImmediatePriority,vn=n.unstable_UserBlockingPriority,wn=n.unstable_NormalPriority,pe=n.unstable_LowPriority,Kl=n.unstable_IdlePriority,Wl=n.log,Fl=n.unstable_setDisableYieldValue,St=null,Ee=null;function ot(e){if(typeof Wl=="function"&&Fl(e),Ee&&typeof Ee.setStrictMode=="function")try{Ee.setStrictMode(St,e)}catch{}}var Qe=Math.clz32?Math.clz32:x1,xi=Math.log,pu=Math.LN2;function x1(e){return e>>>=0,e===0?32:31-(xi(e)/pu|0)|0}var Jl=256,eo=4194304;function Ua(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function to(e,t,a){var i=e.pendingLanes;if(i===0)return 0;var s=0,f=e.suspendedLanes,g=e.pingedLanes;e=e.warmLanes;var v=i&134217727;return v!==0?(i=v&~f,i!==0?s=Ua(i):(g&=v,g!==0?s=Ua(g):a||(a=v&~e,a!==0&&(s=Ua(a))))):(v=i&~f,v!==0?s=Ua(v):g!==0?s=Ua(g):a||(a=i&~e,a!==0&&(s=Ua(a)))),s===0?0:t!==0&&t!==s&&(t&f)===0&&(f=s&-s,a=t&-t,f>=a||f===32&&(a&4194048)!==0)?t:s}function Ci(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function C1(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function hh(){var e=Jl;return Jl<<=1,(Jl&4194048)===0&&(Jl=256),e}function mh(){var e=eo;return eo<<=1,(eo&62914560)===0&&(eo=4194304),e}function gu(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function Ei(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function E1(e,t,a,i,s,f){var g=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var v=e.entanglements,R=e.expirationTimes,H=e.hiddenUpdates;for(a=g&~a;0)":-1s||R[i]!==H[s]){var te=` +`+R[i].replace(" at new "," at ");return e.displayName&&te.includes("")&&(te=te.replace("",e.displayName)),te}while(1<=i&&0<=s);break}}}finally{Cu=!1,Error.prepareStackTrace=a}return(a=e?e.displayName||e.name:"")?Cr(a):""}function R1(e){switch(e.tag){case 26:case 27:case 5:return Cr(e.type);case 16:return Cr("Lazy");case 13:return Cr("Suspense");case 19:return Cr("SuspenseList");case 0:case 15:return Eu(e.type,!1);case 11:return Eu(e.type.render,!1);case 1:return Eu(e.type,!0);case 31:return Cr("Activity");default:return""}}function Th(e){try{var t="";do t+=R1(e),e=e.return;while(e);return t}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}function an(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Mh(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function D1(e){var t=Mh(e)?"checked":"value",a=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),i=""+e[t];if(!e.hasOwnProperty(t)&&typeof a<"u"&&typeof a.get=="function"&&typeof a.set=="function"){var s=a.get,f=a.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return s.call(this)},set:function(g){i=""+g,f.call(this,g)}}),Object.defineProperty(e,t,{enumerable:a.enumerable}),{getValue:function(){return i},setValue:function(g){i=""+g},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function ro(e){e._valueTracker||(e._valueTracker=D1(e))}function wh(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var a=t.getValue(),i="";return e&&(i=Mh(e)?e.checked?"true":"false":e.value),e=i,e!==a?(t.setValue(e),!0):!1}function io(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}}var _1=/[\n"\\]/g;function rn(e){return e.replace(_1,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Tu(e,t,a,i,s,f,g,v){e.name="",g!=null&&typeof g!="function"&&typeof g!="symbol"&&typeof g!="boolean"?e.type=g:e.removeAttribute("type"),t!=null?g==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+an(t)):e.value!==""+an(t)&&(e.value=""+an(t)):g!=="submit"&&g!=="reset"||e.removeAttribute("value"),t!=null?Mu(e,g,an(t)):a!=null?Mu(e,g,an(a)):i!=null&&e.removeAttribute("value"),s==null&&f!=null&&(e.defaultChecked=!!f),s!=null&&(e.checked=s&&typeof s!="function"&&typeof s!="symbol"),v!=null&&typeof v!="function"&&typeof v!="symbol"&&typeof v!="boolean"?e.name=""+an(v):e.removeAttribute("name")}function Ah(e,t,a,i,s,f,g,v){if(f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(e.type=f),t!=null||a!=null){if(!(f!=="submit"&&f!=="reset"||t!=null))return;a=a!=null?""+an(a):"",t=t!=null?""+an(t):a,v||t===e.value||(e.value=t),e.defaultValue=t}i=i??s,i=typeof i!="function"&&typeof i!="symbol"&&!!i,e.checked=v?e.checked:!!i,e.defaultChecked=!!i,g!=null&&typeof g!="function"&&typeof g!="symbol"&&typeof g!="boolean"&&(e.name=g)}function Mu(e,t,a){t==="number"&&io(e.ownerDocument)===e||e.defaultValue===""+a||(e.defaultValue=""+a)}function Er(e,t,a,i){if(e=e.options,t){t={};for(var s=0;s"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Du=!1;if(Yn)try{var Ai={};Object.defineProperty(Ai,"passive",{get:function(){Du=!0}}),window.addEventListener("test",Ai,Ai),window.removeEventListener("test",Ai,Ai)}catch{Du=!1}var fa=null,_u=null,oo=null;function $h(){if(oo)return oo;var e,t=_u,a=t.length,i,s="value"in fa?fa.value:fa.textContent,f=s.length;for(e=0;e=Di),Hh=" ",Yh=!1;function qh(e,t){switch(e){case"keyup":return ib.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Gh(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Ar=!1;function ob(e,t){switch(e){case"compositionend":return Gh(t);case"keypress":return t.which!==32?null:(Yh=!0,Hh);case"textInput":return e=t.data,e===Hh&&Yh?null:e;default:return null}}function sb(e,t){if(Ar)return e==="compositionend"||!Lu&&qh(e,t)?(e=$h(),oo=_u=fa=null,Ar=!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:a,offset:t-e};e=i}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=Wh(a)}}function Jh(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Jh(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function em(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=io(e.document);t instanceof e.HTMLIFrameElement;){try{var a=typeof t.contentWindow.location.href=="string"}catch{a=!1}if(a)e=t.contentWindow;else break;t=io(e.document)}return t}function Uu(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var gb=Yn&&"documentMode"in document&&11>=document.documentMode,Or=null,Hu=null,$i=null,Yu=!1;function tm(e,t,a){var i=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;Yu||Or==null||Or!==io(i)||(i=Or,"selectionStart"in i&&Uu(i)?i={start:i.selectionStart,end:i.selectionEnd}:(i=(i.ownerDocument&&i.ownerDocument.defaultView||window).getSelection(),i={anchorNode:i.anchorNode,anchorOffset:i.anchorOffset,focusNode:i.focusNode,focusOffset:i.focusOffset}),$i&&zi($i,i)||($i=i,i=Fo(Hu,"onSelect"),0>=g,s-=g,Gn=1<<32-Qe(t)+s|a<f?f:8;var g=A.T,v={};A.T=v,wc(e,!1,t,a);try{var R=s(),H=A.S;if(H!==null&&H(v,R),R!==null&&typeof R=="object"&&typeof R.then=="function"){var te=Mb(R,i);Qi(e,t,te,Wt(e))}else Qi(e,t,i,Wt(e))}catch(ae){Qi(e,t,{then:function(){},status:"rejected",reason:ae},Wt())}finally{U.p=f,A.T=g}}function Db(){}function Tc(e,t,a,i){if(e.tag!==5)throw Error(o(476));var s=np(e).queue;tp(e,s,t,X,a===null?Db:function(){return ap(e),a(i)})}function np(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:X,baseState:X,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Zn,lastRenderedState:X},next:null};var a={};return t.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Zn,lastRenderedState:a},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function ap(e){var t=np(e).next.queue;Qi(e,t,{},Wt())}function Mc(){return kt(hl)}function rp(){return ht().memoizedState}function ip(){return ht().memoizedState}function _b(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var a=Wt();e=ma(a);var i=pa(t,e,a);i!==null&&(Ft(i,t,a),Gi(i,t,a)),t={cache:tc()},e.payload=t;return}t=t.return}}function kb(e,t,a){var i=Wt();a={lane:i,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null},ko(e)?op(t,a):(a=Pu(e,t,a,i),a!==null&&(Ft(a,e,i),sp(a,t,i)))}function lp(e,t,a){var i=Wt();Qi(e,t,a,i)}function Qi(e,t,a,i){var s={lane:i,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null};if(ko(e))op(t,s);else{var f=e.alternate;if(e.lanes===0&&(f===null||f.lanes===0)&&(f=t.lastRenderedReducer,f!==null))try{var g=t.lastRenderedState,v=f(g,a);if(s.hasEagerState=!0,s.eagerState=v,Xt(v,g))return po(e,t,s,0),Pe===null&&mo(),!1}catch{}finally{}if(a=Pu(e,t,s,i),a!==null)return Ft(a,e,i),sp(a,t,i),!0}return!1}function wc(e,t,a,i){if(i={lane:2,revertLane:rf(),action:i,hasEagerState:!1,eagerState:null,next:null},ko(e)){if(t)throw Error(o(479))}else t=Pu(e,a,i,2),t!==null&&Ft(t,e,2)}function ko(e){var t=e.alternate;return e===ve||t!==null&&t===ve}function op(e,t){jr=wo=!0;var a=e.pending;a===null?t.next=t:(t.next=a.next,a.next=t),e.pending=t}function sp(e,t,a){if((a&4194048)!==0){var i=t.lanes;i&=e.pendingLanes,a|=i,t.lanes=a,gh(e,a)}}var zo={readContext:kt,use:Oo,useCallback:st,useContext:st,useEffect:st,useImperativeHandle:st,useLayoutEffect:st,useInsertionEffect:st,useMemo:st,useReducer:st,useRef:st,useState:st,useDebugValue:st,useDeferredValue:st,useTransition:st,useSyncExternalStore:st,useId:st,useHostTransitionStatus:st,useFormState:st,useActionState:st,useOptimistic:st,useMemoCache:st,useCacheRefresh:st},up={readContext:kt,use:Oo,useCallback:function(e,t){return Yt().memoizedState=[e,t===void 0?null:t],e},useContext:kt,useEffect:Xm,useImperativeHandle:function(e,t,a){a=a!=null?a.concat([e]):null,_o(4194308,4,Km.bind(null,t,e),a)},useLayoutEffect:function(e,t){return _o(4194308,4,e,t)},useInsertionEffect:function(e,t){_o(4,2,e,t)},useMemo:function(e,t){var a=Yt();t=t===void 0?null:t;var i=e();if(Fa){ot(!0);try{e()}finally{ot(!1)}}return a.memoizedState=[i,t],i},useReducer:function(e,t,a){var i=Yt();if(a!==void 0){var s=a(t);if(Fa){ot(!0);try{a(t)}finally{ot(!1)}}}else s=t;return i.memoizedState=i.baseState=s,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:s},i.queue=e,e=e.dispatch=kb.bind(null,ve,e),[i.memoizedState,e]},useRef:function(e){var t=Yt();return e={current:e},t.memoizedState=e},useState:function(e){e=Sc(e);var t=e.queue,a=lp.bind(null,ve,t);return t.dispatch=a,[e.memoizedState,a]},useDebugValue:Cc,useDeferredValue:function(e,t){var a=Yt();return Ec(a,e,t)},useTransition:function(){var e=Sc(!1);return e=tp.bind(null,ve,e.queue,!0,!1),Yt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,a){var i=ve,s=Yt();if(De){if(a===void 0)throw Error(o(407));a=a()}else{if(a=t(),Pe===null)throw Error(o(349));(Te&124)!==0||Dm(i,t,a)}s.memoizedState=a;var f={value:a,getSnapshot:t};return s.queue=f,Xm(km.bind(null,i,f,e),[e]),i.flags|=2048,Hr(9,Do(),_m.bind(null,i,f,a,t),null),a},useId:function(){var e=Yt(),t=Pe.identifierPrefix;if(De){var a=Vn,i=Gn;a=(i&~(1<<32-Qe(i)-1)).toString(32)+a,t="«"+t+"R"+a,a=Ao++,0he?(Tt=ce,ce=null):Tt=ce.sibling;var Re=q(L,ce,j[he],ne);if(Re===null){ce===null&&(ce=Tt);break}e&&ce&&Re.alternate===null&&t(L,ce),z=f(Re,z,he),be===null?se=Re:be.sibling=Re,be=Re,ce=Tt}if(he===j.length)return a(L,ce),De&&Xa(L,he),se;if(ce===null){for(;hehe?(Tt=ce,ce=null):Tt=ce.sibling;var ka=q(L,ce,Re.value,ne);if(ka===null){ce===null&&(ce=Tt);break}e&&ce&&ka.alternate===null&&t(L,ce),z=f(ka,z,he),be===null?se=ka:be.sibling=ka,be=ka,ce=Tt}if(Re.done)return a(L,ce),De&&Xa(L,he),se;if(ce===null){for(;!Re.done;he++,Re=j.next())Re=ae(L,Re.value,ne),Re!==null&&(z=f(Re,z,he),be===null?se=Re:be.sibling=Re,be=Re);return De&&Xa(L,he),se}for(ce=i(ce);!Re.done;he++,Re=j.next())Re=P(ce,L,he,Re.value,ne),Re!==null&&(e&&Re.alternate!==null&&ce.delete(Re.key===null?he:Re.key),z=f(Re,z,he),be===null?se=Re:be.sibling=Re,be=Re);return e&&ce.forEach(function($2){return t(L,$2)}),De&&Xa(L,he),se}function Ye(L,z,j,ne){if(typeof j=="object"&&j!==null&&j.type===C&&j.key===null&&(j=j.props.children),typeof j=="object"&&j!==null){switch(j.$$typeof){case E:e:{for(var se=j.key;z!==null;){if(z.key===se){if(se=j.type,se===C){if(z.tag===7){a(L,z.sibling),ne=s(z,j.props.children),ne.return=L,L=ne;break e}}else if(z.elementType===se||typeof se=="object"&&se!==null&&se.$$typeof===F&&fp(se)===z.type){a(L,z.sibling),ne=s(z,j.props),Wi(ne,j),ne.return=L,L=ne;break e}a(L,z);break}else t(L,z);z=z.sibling}j.type===C?(ne=Va(j.props.children,L.mode,ne,j.key),ne.return=L,L=ne):(ne=yo(j.type,j.key,j.props,null,L.mode,ne),Wi(ne,j),ne.return=L,L=ne)}return g(L);case O:e:{for(se=j.key;z!==null;){if(z.key===se)if(z.tag===4&&z.stateNode.containerInfo===j.containerInfo&&z.stateNode.implementation===j.implementation){a(L,z.sibling),ne=s(z,j.children||[]),ne.return=L,L=ne;break e}else{a(L,z);break}else t(L,z);z=z.sibling}ne=Iu(j,L.mode,ne),ne.return=L,L=ne}return g(L);case F:return se=j._init,j=se(j._payload),Ye(L,z,j,ne)}if(Z(j))return me(L,z,j,ne);if(S(j)){if(se=S(j),typeof se!="function")throw Error(o(150));return j=se.call(j),de(L,z,j,ne)}if(typeof j.then=="function")return Ye(L,z,$o(j),ne);if(j.$$typeof===$)return Ye(L,z,xo(L,j),ne);No(L,j)}return typeof j=="string"&&j!==""||typeof j=="number"||typeof j=="bigint"?(j=""+j,z!==null&&z.tag===6?(a(L,z.sibling),ne=s(z,j),ne.return=L,L=ne):(a(L,z),ne=Zu(j,L.mode,ne),ne.return=L,L=ne),g(L)):a(L,z)}return function(L,z,j,ne){try{Ki=0;var se=Ye(L,z,j,ne);return Yr=null,se}catch(ce){if(ce===Yi||ce===Eo)throw ce;var be=Zt(29,ce,null,L.mode);return be.lanes=ne,be.return=L,be}finally{}}}var qr=dp(!0),hp=dp(!1),cn=Y(null),On=null;function ya(e){var t=e.alternate;ie(gt,gt.current&1),ie(cn,e),On===null&&(t===null||Br.current!==null||t.memoizedState!==null)&&(On=e)}function mp(e){if(e.tag===22){if(ie(gt,gt.current),ie(cn,e),On===null){var t=e.alternate;t!==null&&t.memoizedState!==null&&(On=e)}}else va()}function va(){ie(gt,gt.current),ie(cn,cn.current)}function In(e){re(cn),On===e&&(On=null),re(gt)}var gt=Y(0);function Lo(e){for(var t=e;t!==null;){if(t.tag===13){var a=t.memoizedState;if(a!==null&&(a=a.dehydrated,a===null||a.data==="$?"||yf(a)))return t}else if(t.tag===19&&t.memoizedProps.revealOrder!==void 0){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function Ac(e,t,a,i){t=e.memoizedState,a=a(i,t),a=a==null?t:y({},t,a),e.memoizedState=a,e.lanes===0&&(e.updateQueue.baseState=a)}var Oc={enqueueSetState:function(e,t,a){e=e._reactInternals;var i=Wt(),s=ma(i);s.payload=t,a!=null&&(s.callback=a),t=pa(e,s,i),t!==null&&(Ft(t,e,i),Gi(t,e,i))},enqueueReplaceState:function(e,t,a){e=e._reactInternals;var i=Wt(),s=ma(i);s.tag=1,s.payload=t,a!=null&&(s.callback=a),t=pa(e,s,i),t!==null&&(Ft(t,e,i),Gi(t,e,i))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var a=Wt(),i=ma(a);i.tag=2,t!=null&&(i.callback=t),t=pa(e,i,a),t!==null&&(Ft(t,e,a),Gi(t,e,a))}};function pp(e,t,a,i,s,f,g){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(i,f,g):t.prototype&&t.prototype.isPureReactComponent?!zi(a,i)||!zi(s,f):!0}function gp(e,t,a,i){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(a,i),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(a,i),t.state!==e&&Oc.enqueueReplaceState(t,t.state,null)}function Ja(e,t){var a=t;if("ref"in t){a={};for(var i in t)i!=="ref"&&(a[i]=t[i])}if(e=e.defaultProps){a===t&&(a=y({},a));for(var s in e)a[s]===void 0&&(a[s]=e[s])}return a}var Bo=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)};function yp(e){Bo(e)}function vp(e){console.error(e)}function bp(e){Bo(e)}function jo(e,t){try{var a=e.onUncaughtError;a(t.value,{componentStack:t.stack})}catch(i){setTimeout(function(){throw i})}}function Sp(e,t,a){try{var i=e.onCaughtError;i(a.value,{componentStack:a.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(s){setTimeout(function(){throw s})}}function Rc(e,t,a){return a=ma(a),a.tag=3,a.payload={element:null},a.callback=function(){jo(e,t)},a}function xp(e){return e=ma(e),e.tag=3,e}function Cp(e,t,a,i){var s=a.type.getDerivedStateFromError;if(typeof s=="function"){var f=i.value;e.payload=function(){return s(f)},e.callback=function(){Sp(t,a,i)}}var g=a.stateNode;g!==null&&typeof g.componentDidCatch=="function"&&(e.callback=function(){Sp(t,a,i),typeof s!="function"&&(Ta===null?Ta=new Set([this]):Ta.add(this));var v=i.stack;this.componentDidCatch(i.value,{componentStack:v!==null?v:""})})}function $b(e,t,a,i,s){if(a.flags|=32768,i!==null&&typeof i=="object"&&typeof i.then=="function"){if(t=a.alternate,t!==null&&ji(t,a,s,!0),a=cn.current,a!==null){switch(a.tag){case 13:return On===null?Jc():a.alternate===null&&tt===0&&(tt=3),a.flags&=-257,a.flags|=65536,a.lanes=s,i===rc?a.flags|=16384:(t=a.updateQueue,t===null?a.updateQueue=new Set([i]):t.add(i),tf(e,i,s)),!1;case 22:return a.flags|=65536,i===rc?a.flags|=16384:(t=a.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([i])},a.updateQueue=t):(a=t.retryQueue,a===null?t.retryQueue=new Set([i]):a.add(i)),tf(e,i,s)),!1}throw Error(o(435,a.tag))}return tf(e,i,s),Jc(),!1}if(De)return t=cn.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=s,i!==Wu&&(e=Error(o(422),{cause:i}),Bi(ln(e,a)))):(i!==Wu&&(t=Error(o(423),{cause:i}),Bi(ln(t,a))),e=e.current.alternate,e.flags|=65536,s&=-s,e.lanes|=s,i=ln(i,a),s=Rc(e.stateNode,i,s),oc(e,s),tt!==4&&(tt=2)),!1;var f=Error(o(520),{cause:i});if(f=ln(f,a),rl===null?rl=[f]:rl.push(f),tt!==4&&(tt=2),t===null)return!0;i=ln(i,a),a=t;do{switch(a.tag){case 3:return a.flags|=65536,e=s&-s,a.lanes|=e,e=Rc(a.stateNode,i,e),oc(a,e),!1;case 1:if(t=a.type,f=a.stateNode,(a.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||f!==null&&typeof f.componentDidCatch=="function"&&(Ta===null||!Ta.has(f))))return a.flags|=65536,s&=-s,a.lanes|=s,s=xp(s),Cp(s,e,a,i),oc(a,s),!1}a=a.return}while(a!==null);return!1}var Ep=Error(o(461)),Ct=!1;function At(e,t,a,i){t.child=e===null?hp(t,null,a,i):qr(t,e.child,a,i)}function Tp(e,t,a,i,s){a=a.render;var f=t.ref;if("ref"in i){var g={};for(var v in i)v!=="ref"&&(g[v]=i[v])}else g=i;return Ka(t),i=dc(e,t,a,g,f,s),v=hc(),e!==null&&!Ct?(mc(e,t,s),Qn(e,t,s)):(De&&v&&Qu(t),t.flags|=1,At(e,t,i,s),t.child)}function Mp(e,t,a,i,s){if(e===null){var f=a.type;return typeof f=="function"&&!Xu(f)&&f.defaultProps===void 0&&a.compare===null?(t.tag=15,t.type=f,wp(e,t,f,i,s)):(e=yo(a.type,null,i,t,t.mode,s),e.ref=t.ref,e.return=t,t.child=e)}if(f=e.child,!Bc(e,s)){var g=f.memoizedProps;if(a=a.compare,a=a!==null?a:zi,a(g,i)&&e.ref===t.ref)return Qn(e,t,s)}return t.flags|=1,e=qn(f,i),e.ref=t.ref,e.return=t,t.child=e}function wp(e,t,a,i,s){if(e!==null){var f=e.memoizedProps;if(zi(f,i)&&e.ref===t.ref)if(Ct=!1,t.pendingProps=i=f,Bc(e,s))(e.flags&131072)!==0&&(Ct=!0);else return t.lanes=e.lanes,Qn(e,t,s)}return Dc(e,t,a,i,s)}function Ap(e,t,a){var i=t.pendingProps,s=i.children,f=e!==null?e.memoizedState:null;if(i.mode==="hidden"){if((t.flags&128)!==0){if(i=f!==null?f.baseLanes|a:a,e!==null){for(s=t.child=e.child,f=0;s!==null;)f=f|s.lanes|s.childLanes,s=s.sibling;t.childLanes=f&~i}else t.childLanes=0,t.child=null;return Op(e,t,i,a)}if((a&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&Co(t,f!==null?f.cachePool:null),f!==null?wm(t,f):uc(),mp(t);else return t.lanes=t.childLanes=536870912,Op(e,t,f!==null?f.baseLanes|a:a,a)}else f!==null?(Co(t,f.cachePool),wm(t,f),va(),t.memoizedState=null):(e!==null&&Co(t,null),uc(),va());return At(e,t,s,a),t.child}function Op(e,t,a,i){var s=ac();return s=s===null?null:{parent:pt._currentValue,pool:s},t.memoizedState={baseLanes:a,cachePool:s},e!==null&&Co(t,null),uc(),mp(t),e!==null&&ji(e,t,i,!0),null}function Uo(e,t){var a=t.ref;if(a===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof a!="function"&&typeof a!="object")throw Error(o(284));(e===null||e.ref!==a)&&(t.flags|=4194816)}}function Dc(e,t,a,i,s){return Ka(t),a=dc(e,t,a,i,void 0,s),i=hc(),e!==null&&!Ct?(mc(e,t,s),Qn(e,t,s)):(De&&i&&Qu(t),t.flags|=1,At(e,t,a,s),t.child)}function Rp(e,t,a,i,s,f){return Ka(t),t.updateQueue=null,a=Om(t,i,a,s),Am(e),i=hc(),e!==null&&!Ct?(mc(e,t,f),Qn(e,t,f)):(De&&i&&Qu(t),t.flags|=1,At(e,t,a,f),t.child)}function Dp(e,t,a,i,s){if(Ka(t),t.stateNode===null){var f=kr,g=a.contextType;typeof g=="object"&&g!==null&&(f=kt(g)),f=new a(i,f),t.memoizedState=f.state!==null&&f.state!==void 0?f.state:null,f.updater=Oc,t.stateNode=f,f._reactInternals=t,f=t.stateNode,f.props=i,f.state=t.memoizedState,f.refs={},ic(t),g=a.contextType,f.context=typeof g=="object"&&g!==null?kt(g):kr,f.state=t.memoizedState,g=a.getDerivedStateFromProps,typeof g=="function"&&(Ac(t,a,g,i),f.state=t.memoizedState),typeof a.getDerivedStateFromProps=="function"||typeof f.getSnapshotBeforeUpdate=="function"||typeof f.UNSAFE_componentWillMount!="function"&&typeof f.componentWillMount!="function"||(g=f.state,typeof f.componentWillMount=="function"&&f.componentWillMount(),typeof f.UNSAFE_componentWillMount=="function"&&f.UNSAFE_componentWillMount(),g!==f.state&&Oc.enqueueReplaceState(f,f.state,null),Pi(t,i,f,s),Vi(),f.state=t.memoizedState),typeof f.componentDidMount=="function"&&(t.flags|=4194308),i=!0}else if(e===null){f=t.stateNode;var v=t.memoizedProps,R=Ja(a,v);f.props=R;var H=f.context,te=a.contextType;g=kr,typeof te=="object"&&te!==null&&(g=kt(te));var ae=a.getDerivedStateFromProps;te=typeof ae=="function"||typeof f.getSnapshotBeforeUpdate=="function",v=t.pendingProps!==v,te||typeof f.UNSAFE_componentWillReceiveProps!="function"&&typeof f.componentWillReceiveProps!="function"||(v||H!==g)&&gp(t,f,i,g),ha=!1;var q=t.memoizedState;f.state=q,Pi(t,i,f,s),Vi(),H=t.memoizedState,v||q!==H||ha?(typeof ae=="function"&&(Ac(t,a,ae,i),H=t.memoizedState),(R=ha||pp(t,a,R,i,q,H,g))?(te||typeof f.UNSAFE_componentWillMount!="function"&&typeof f.componentWillMount!="function"||(typeof f.componentWillMount=="function"&&f.componentWillMount(),typeof f.UNSAFE_componentWillMount=="function"&&f.UNSAFE_componentWillMount()),typeof f.componentDidMount=="function"&&(t.flags|=4194308)):(typeof f.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=i,t.memoizedState=H),f.props=i,f.state=H,f.context=g,i=R):(typeof f.componentDidMount=="function"&&(t.flags|=4194308),i=!1)}else{f=t.stateNode,lc(e,t),g=t.memoizedProps,te=Ja(a,g),f.props=te,ae=t.pendingProps,q=f.context,H=a.contextType,R=kr,typeof H=="object"&&H!==null&&(R=kt(H)),v=a.getDerivedStateFromProps,(H=typeof v=="function"||typeof f.getSnapshotBeforeUpdate=="function")||typeof f.UNSAFE_componentWillReceiveProps!="function"&&typeof f.componentWillReceiveProps!="function"||(g!==ae||q!==R)&&gp(t,f,i,R),ha=!1,q=t.memoizedState,f.state=q,Pi(t,i,f,s),Vi();var P=t.memoizedState;g!==ae||q!==P||ha||e!==null&&e.dependencies!==null&&So(e.dependencies)?(typeof v=="function"&&(Ac(t,a,v,i),P=t.memoizedState),(te=ha||pp(t,a,te,i,q,P,R)||e!==null&&e.dependencies!==null&&So(e.dependencies))?(H||typeof f.UNSAFE_componentWillUpdate!="function"&&typeof f.componentWillUpdate!="function"||(typeof f.componentWillUpdate=="function"&&f.componentWillUpdate(i,P,R),typeof f.UNSAFE_componentWillUpdate=="function"&&f.UNSAFE_componentWillUpdate(i,P,R)),typeof f.componentDidUpdate=="function"&&(t.flags|=4),typeof f.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof f.componentDidUpdate!="function"||g===e.memoizedProps&&q===e.memoizedState||(t.flags|=4),typeof f.getSnapshotBeforeUpdate!="function"||g===e.memoizedProps&&q===e.memoizedState||(t.flags|=1024),t.memoizedProps=i,t.memoizedState=P),f.props=i,f.state=P,f.context=R,i=te):(typeof f.componentDidUpdate!="function"||g===e.memoizedProps&&q===e.memoizedState||(t.flags|=4),typeof f.getSnapshotBeforeUpdate!="function"||g===e.memoizedProps&&q===e.memoizedState||(t.flags|=1024),i=!1)}return f=i,Uo(e,t),i=(t.flags&128)!==0,f||i?(f=t.stateNode,a=i&&typeof a.getDerivedStateFromError!="function"?null:f.render(),t.flags|=1,e!==null&&i?(t.child=qr(t,e.child,null,s),t.child=qr(t,null,a,s)):At(e,t,a,s),t.memoizedState=f.state,e=t.child):e=Qn(e,t,s),e}function _p(e,t,a,i){return Li(),t.flags|=256,At(e,t,a,i),t.child}var _c={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function kc(e){return{baseLanes:e,cachePool:vm()}}function zc(e,t,a){return e=e!==null?e.childLanes&~a:0,t&&(e|=fn),e}function kp(e,t,a){var i=t.pendingProps,s=!1,f=(t.flags&128)!==0,g;if((g=f)||(g=e!==null&&e.memoizedState===null?!1:(gt.current&2)!==0),g&&(s=!0,t.flags&=-129),g=(t.flags&32)!==0,t.flags&=-33,e===null){if(De){if(s?ya(t):va(),De){var v=et,R;if(R=v){e:{for(R=v,v=An;R.nodeType!==8;){if(!v){v=null;break e}if(R=xn(R.nextSibling),R===null){v=null;break e}}v=R}v!==null?(t.memoizedState={dehydrated:v,treeContext:Pa!==null?{id:Gn,overflow:Vn}:null,retryLane:536870912,hydrationErrors:null},R=Zt(18,null,null,0),R.stateNode=v,R.return=t,t.child=R,Bt=t,et=null,R=!0):R=!1}R||Ia(t)}if(v=t.memoizedState,v!==null&&(v=v.dehydrated,v!==null))return yf(v)?t.lanes=32:t.lanes=536870912,null;In(t)}return v=i.children,i=i.fallback,s?(va(),s=t.mode,v=Ho({mode:"hidden",children:v},s),i=Va(i,s,a,null),v.return=t,i.return=t,v.sibling=i,t.child=v,s=t.child,s.memoizedState=kc(a),s.childLanes=zc(e,g,a),t.memoizedState=_c,i):(ya(t),$c(t,v))}if(R=e.memoizedState,R!==null&&(v=R.dehydrated,v!==null)){if(f)t.flags&256?(ya(t),t.flags&=-257,t=Nc(e,t,a)):t.memoizedState!==null?(va(),t.child=e.child,t.flags|=128,t=null):(va(),s=i.fallback,v=t.mode,i=Ho({mode:"visible",children:i.children},v),s=Va(s,v,a,null),s.flags|=2,i.return=t,s.return=t,i.sibling=s,t.child=i,qr(t,e.child,null,a),i=t.child,i.memoizedState=kc(a),i.childLanes=zc(e,g,a),t.memoizedState=_c,t=s);else if(ya(t),yf(v)){if(g=v.nextSibling&&v.nextSibling.dataset,g)var H=g.dgst;g=H,i=Error(o(419)),i.stack="",i.digest=g,Bi({value:i,source:null,stack:null}),t=Nc(e,t,a)}else if(Ct||ji(e,t,a,!1),g=(a&e.childLanes)!==0,Ct||g){if(g=Pe,g!==null&&(i=a&-a,i=(i&42)!==0?1:yu(i),i=(i&(g.suspendedLanes|a))!==0?0:i,i!==0&&i!==R.retryLane))throw R.retryLane=i,_r(e,i),Ft(g,e,i),Ep;v.data==="$?"||Jc(),t=Nc(e,t,a)}else v.data==="$?"?(t.flags|=192,t.child=e.child,t=null):(e=R.treeContext,et=xn(v.nextSibling),Bt=t,De=!0,Za=null,An=!1,e!==null&&(sn[un++]=Gn,sn[un++]=Vn,sn[un++]=Pa,Gn=e.id,Vn=e.overflow,Pa=t),t=$c(t,i.children),t.flags|=4096);return t}return s?(va(),s=i.fallback,v=t.mode,R=e.child,H=R.sibling,i=qn(R,{mode:"hidden",children:i.children}),i.subtreeFlags=R.subtreeFlags&65011712,H!==null?s=qn(H,s):(s=Va(s,v,a,null),s.flags|=2),s.return=t,i.return=t,i.sibling=s,t.child=i,i=s,s=t.child,v=e.child.memoizedState,v===null?v=kc(a):(R=v.cachePool,R!==null?(H=pt._currentValue,R=R.parent!==H?{parent:H,pool:H}:R):R=vm(),v={baseLanes:v.baseLanes|a,cachePool:R}),s.memoizedState=v,s.childLanes=zc(e,g,a),t.memoizedState=_c,i):(ya(t),a=e.child,e=a.sibling,a=qn(a,{mode:"visible",children:i.children}),a.return=t,a.sibling=null,e!==null&&(g=t.deletions,g===null?(t.deletions=[e],t.flags|=16):g.push(e)),t.child=a,t.memoizedState=null,a)}function $c(e,t){return t=Ho({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function Ho(e,t){return e=Zt(22,e,null,t),e.lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function Nc(e,t,a){return qr(t,e.child,null,a),e=$c(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function zp(e,t,a){e.lanes|=t;var i=e.alternate;i!==null&&(i.lanes|=t),Ju(e.return,t,a)}function Lc(e,t,a,i,s){var f=e.memoizedState;f===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:i,tail:a,tailMode:s}:(f.isBackwards=t,f.rendering=null,f.renderingStartTime=0,f.last=i,f.tail=a,f.tailMode=s)}function $p(e,t,a){var i=t.pendingProps,s=i.revealOrder,f=i.tail;if(At(e,t,i.children,a),i=gt.current,(i&2)!==0)i=i&1|2,t.flags|=128;else{if(e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&zp(e,a,t);else if(e.tag===19)zp(e,a,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}i&=1}switch(ie(gt,i),s){case"forwards":for(a=t.child,s=null;a!==null;)e=a.alternate,e!==null&&Lo(e)===null&&(s=a),a=a.sibling;a=s,a===null?(s=t.child,t.child=null):(s=a.sibling,a.sibling=null),Lc(t,!1,s,a,f);break;case"backwards":for(a=null,s=t.child,t.child=null;s!==null;){if(e=s.alternate,e!==null&&Lo(e)===null){t.child=s;break}e=s.sibling,s.sibling=a,a=s,s=e}Lc(t,!0,a,null,f);break;case"together":Lc(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function Qn(e,t,a){if(e!==null&&(t.dependencies=e.dependencies),Ea|=t.lanes,(a&t.childLanes)===0)if(e!==null){if(ji(e,t,a,!1),(a&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(o(153));if(t.child!==null){for(e=t.child,a=qn(e,e.pendingProps),t.child=a,a.return=t;e.sibling!==null;)e=e.sibling,a=a.sibling=qn(e,e.pendingProps),a.return=t;a.sibling=null}return t.child}function Bc(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&So(e)))}function Nb(e,t,a){switch(t.tag){case 3:Se(t,t.stateNode.containerInfo),da(t,pt,e.memoizedState.cache),Li();break;case 27:case 5:Xe(t);break;case 4:Se(t,t.stateNode.containerInfo);break;case 10:da(t,t.type,t.memoizedProps.value);break;case 13:var i=t.memoizedState;if(i!==null)return i.dehydrated!==null?(ya(t),t.flags|=128,null):(a&t.child.childLanes)!==0?kp(e,t,a):(ya(t),e=Qn(e,t,a),e!==null?e.sibling:null);ya(t);break;case 19:var s=(e.flags&128)!==0;if(i=(a&t.childLanes)!==0,i||(ji(e,t,a,!1),i=(a&t.childLanes)!==0),s){if(i)return $p(e,t,a);t.flags|=128}if(s=t.memoizedState,s!==null&&(s.rendering=null,s.tail=null,s.lastEffect=null),ie(gt,gt.current),i)break;return null;case 22:case 23:return t.lanes=0,Ap(e,t,a);case 24:da(t,pt,e.memoizedState.cache)}return Qn(e,t,a)}function Np(e,t,a){if(e!==null)if(e.memoizedProps!==t.pendingProps)Ct=!0;else{if(!Bc(e,a)&&(t.flags&128)===0)return Ct=!1,Nb(e,t,a);Ct=(e.flags&131072)!==0}else Ct=!1,De&&(t.flags&1048576)!==0&&fm(t,bo,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var i=t.elementType,s=i._init;if(i=s(i._payload),t.type=i,typeof i=="function")Xu(i)?(e=Ja(i,e),t.tag=1,t=Dp(null,t,i,e,a)):(t.tag=0,t=Dc(null,t,i,e,a));else{if(i!=null){if(s=i.$$typeof,s===B){t.tag=11,t=Tp(null,t,i,e,a);break e}else if(s===V){t.tag=14,t=Mp(null,t,i,e,a);break e}}throw t=G(i)||i,Error(o(306,t,""))}}return t;case 0:return Dc(e,t,t.type,t.pendingProps,a);case 1:return i=t.type,s=Ja(i,t.pendingProps),Dp(e,t,i,s,a);case 3:e:{if(Se(t,t.stateNode.containerInfo),e===null)throw Error(o(387));i=t.pendingProps;var f=t.memoizedState;s=f.element,lc(e,t),Pi(t,i,null,a);var g=t.memoizedState;if(i=g.cache,da(t,pt,i),i!==f.cache&&ec(t,[pt],a,!0),Vi(),i=g.element,f.isDehydrated)if(f={element:i,isDehydrated:!1,cache:g.cache},t.updateQueue.baseState=f,t.memoizedState=f,t.flags&256){t=_p(e,t,i,a);break e}else if(i!==s){s=ln(Error(o(424)),t),Bi(s),t=_p(e,t,i,a);break e}else{switch(e=t.stateNode.containerInfo,e.nodeType){case 9:e=e.body;break;default:e=e.nodeName==="HTML"?e.ownerDocument.body:e}for(et=xn(e.firstChild),Bt=t,De=!0,Za=null,An=!0,a=hp(t,null,i,a),t.child=a;a;)a.flags=a.flags&-3|4096,a=a.sibling}else{if(Li(),i===s){t=Qn(e,t,a);break e}At(e,t,i,a)}t=t.child}return t;case 26:return Uo(e,t),e===null?(a=U0(t.type,null,t.pendingProps,null))?t.memoizedState=a:De||(a=t.type,e=t.pendingProps,i=es(ue.current).createElement(a),i[_t]=t,i[Ut]=e,Rt(i,a,e),xt(i),t.stateNode=i):t.memoizedState=U0(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return Xe(t),e===null&&De&&(i=t.stateNode=L0(t.type,t.pendingProps,ue.current),Bt=t,An=!0,s=et,Aa(t.type)?(vf=s,et=xn(i.firstChild)):et=s),At(e,t,t.pendingProps.children,a),Uo(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&De&&((s=i=et)&&(i=u2(i,t.type,t.pendingProps,An),i!==null?(t.stateNode=i,Bt=t,et=xn(i.firstChild),An=!1,s=!0):s=!1),s||Ia(t)),Xe(t),s=t.type,f=t.pendingProps,g=e!==null?e.memoizedProps:null,i=f.children,mf(s,f)?i=null:g!==null&&mf(s,g)&&(t.flags|=32),t.memoizedState!==null&&(s=dc(e,t,Ab,null,null,a),hl._currentValue=s),Uo(e,t),At(e,t,i,a),t.child;case 6:return e===null&&De&&((e=a=et)&&(a=c2(a,t.pendingProps,An),a!==null?(t.stateNode=a,Bt=t,et=null,e=!0):e=!1),e||Ia(t)),null;case 13:return kp(e,t,a);case 4:return Se(t,t.stateNode.containerInfo),i=t.pendingProps,e===null?t.child=qr(t,null,i,a):At(e,t,i,a),t.child;case 11:return Tp(e,t,t.type,t.pendingProps,a);case 7:return At(e,t,t.pendingProps,a),t.child;case 8:return At(e,t,t.pendingProps.children,a),t.child;case 12:return At(e,t,t.pendingProps.children,a),t.child;case 10:return i=t.pendingProps,da(t,t.type,i.value),At(e,t,i.children,a),t.child;case 9:return s=t.type._context,i=t.pendingProps.children,Ka(t),s=kt(s),i=i(s),t.flags|=1,At(e,t,i,a),t.child;case 14:return Mp(e,t,t.type,t.pendingProps,a);case 15:return wp(e,t,t.type,t.pendingProps,a);case 19:return $p(e,t,a);case 31:return i=t.pendingProps,a=t.mode,i={mode:i.mode,children:i.children},e===null?(a=Ho(i,a),a.ref=t.ref,t.child=a,a.return=t,t=a):(a=qn(e.child,i),a.ref=t.ref,t.child=a,a.return=t,t=a),t;case 22:return Ap(e,t,a);case 24:return Ka(t),i=kt(pt),e===null?(s=ac(),s===null&&(s=Pe,f=tc(),s.pooledCache=f,f.refCount++,f!==null&&(s.pooledCacheLanes|=a),s=f),t.memoizedState={parent:i,cache:s},ic(t),da(t,pt,s)):((e.lanes&a)!==0&&(lc(e,t),Pi(t,null,null,a),Vi()),s=e.memoizedState,f=t.memoizedState,s.parent!==i?(s={parent:i,cache:i},t.memoizedState=s,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=s),da(t,pt,i)):(i=f.cache,da(t,pt,i),i!==s.cache&&ec(t,[pt],a,!0))),At(e,t,t.pendingProps.children,a),t.child;case 29:throw t.pendingProps}throw Error(o(156,t.tag))}function Kn(e){e.flags|=4}function Lp(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!V0(t)){if(t=cn.current,t!==null&&((Te&4194048)===Te?On!==null:(Te&62914560)!==Te&&(Te&536870912)===0||t!==On))throw qi=rc,bm;e.flags|=8192}}function Yo(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?mh():536870912,e.lanes|=t,Xr|=t)}function Fi(e,t){if(!De)switch(e.tailMode){case"hidden":t=e.tail;for(var a=null;t!==null;)t.alternate!==null&&(a=t),t=t.sibling;a===null?e.tail=null:a.sibling=null;break;case"collapsed":a=e.tail;for(var i=null;a!==null;)a.alternate!==null&&(i=a),a=a.sibling;i===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:i.sibling=null}}function Fe(e){var t=e.alternate!==null&&e.alternate.child===e.child,a=0,i=0;if(t)for(var s=e.child;s!==null;)a|=s.lanes|s.childLanes,i|=s.subtreeFlags&65011712,i|=s.flags&65011712,s.return=e,s=s.sibling;else for(s=e.child;s!==null;)a|=s.lanes|s.childLanes,i|=s.subtreeFlags,i|=s.flags,s.return=e,s=s.sibling;return e.subtreeFlags|=i,e.childLanes=a,t}function Lb(e,t,a){var i=t.pendingProps;switch(Ku(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Fe(t),null;case 1:return Fe(t),null;case 3:return a=t.stateNode,i=null,e!==null&&(i=e.memoizedState.cache),t.memoizedState.cache!==i&&(t.flags|=2048),Xn(pt),Ie(),a.pendingContext&&(a.context=a.pendingContext,a.pendingContext=null),(e===null||e.child===null)&&(Ni(t)?Kn(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,mm())),Fe(t),null;case 26:return a=t.memoizedState,e===null?(Kn(t),a!==null?(Fe(t),Lp(t,a)):(Fe(t),t.flags&=-16777217)):a?a!==e.memoizedState?(Kn(t),Fe(t),Lp(t,a)):(Fe(t),t.flags&=-16777217):(e.memoizedProps!==i&&Kn(t),Fe(t),t.flags&=-16777217),null;case 27:lt(t),a=ue.current;var s=t.type;if(e!==null&&t.stateNode!=null)e.memoizedProps!==i&&Kn(t);else{if(!i){if(t.stateNode===null)throw Error(o(166));return Fe(t),null}e=oe.current,Ni(t)?dm(t):(e=L0(s,i,a),t.stateNode=e,Kn(t))}return Fe(t),null;case 5:if(lt(t),a=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==i&&Kn(t);else{if(!i){if(t.stateNode===null)throw Error(o(166));return Fe(t),null}if(e=oe.current,Ni(t))dm(t);else{switch(s=es(ue.current),e){case 1:e=s.createElementNS("http://www.w3.org/2000/svg",a);break;case 2:e=s.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;default:switch(a){case"svg":e=s.createElementNS("http://www.w3.org/2000/svg",a);break;case"math":e=s.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;case"script":e=s.createElement("div"),e.innerHTML=" + + + + +
+ + diff --git a/src/frontend/dist/index.html b/src/frontend/dist/index.html index 1f8bc88b..6e3d321c 100644 --- a/src/frontend/dist/index.html +++ b/src/frontend/dist/index.html @@ -5,8 +5,9 @@ Parallax by Gradient - - + + +
diff --git a/src/frontend/package.json b/src/frontend/package.json index c761c97b..b2b8606e 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -43,6 +43,7 @@ "devDependencies": { "@eslint/js": "^9.33.0", "@gradient-network/prettier-config": "^0.0.1", + "@types/node": "^22.18.12", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", "@vitejs/plugin-react-swc": "^4.0.0", diff --git a/src/frontend/pnpm-lock.yaml b/src/frontend/pnpm-lock.yaml index b4a0a18a..f30f59fd 100644 --- a/src/frontend/pnpm-lock.yaml +++ b/src/frontend/pnpm-lock.yaml @@ -102,6 +102,9 @@ importers: '@gradient-network/prettier-config': specifier: ^0.0.1 version: 0.0.1(prettier@3.6.2) + '@types/node': + specifier: ^22.18.12 + version: 22.18.12 '@types/react': specifier: ^19.1.10 version: 19.1.13 @@ -110,7 +113,7 @@ importers: version: 19.1.9(@types/react@19.1.13) '@vitejs/plugin-react-swc': specifier: ^4.0.0 - version: 4.1.0(vite@7.1.5) + version: 4.1.0(vite@7.1.5(@types/node@22.18.12)) eslint: specifier: ^9.33.0 version: 9.35.0 @@ -134,7 +137,7 @@ importers: version: 8.44.0(eslint@9.35.0)(typescript@5.8.3) vite: specifier: ^7.1.2 - version: 7.1.5 + version: 7.1.5(@types/node@22.18.12) packages: @@ -964,6 +967,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@22.18.12': + resolution: {integrity: sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -2106,6 +2112,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -2955,6 +2964,10 @@ snapshots: '@types/ms@2.1.0': {} + '@types/node@22.18.12': + dependencies: + undici-types: 6.21.0 + '@types/parse-json@4.0.2': {} '@types/prop-types@15.7.15': {} @@ -3070,11 +3083,11 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react-swc@4.1.0(vite@7.1.5)': + '@vitejs/plugin-react-swc@4.1.0(vite@7.1.5(@types/node@22.18.12))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.35 '@swc/core': 1.13.5 - vite: 7.1.5 + vite: 7.1.5(@types/node@22.18.12) transitivePeerDependencies: - '@swc/helpers' @@ -4438,6 +4451,8 @@ snapshots: typescript@5.8.3: {} + undici-types@6.21.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -4504,7 +4519,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@7.1.5: + vite@7.1.5(@types/node@22.18.12): dependencies: esbuild: 0.25.9 fdir: 6.5.0(picomatch@4.0.3) @@ -4513,6 +4528,7 @@ snapshots: rollup: 4.50.2 tinyglobby: 0.2.15 optionalDependencies: + '@types/node': 22.18.12 fsevents: 2.3.3 web-namespaces@2.0.1: {} diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 454bdb71..3aad205a 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -1,6 +1,12 @@ -import { styled } from '@mui/material'; -import { Router } from './router'; -import { ChatProvider, ClusterProvider } from './services'; +import './global.css'; + +import type { FC, PropsWithChildren } from 'react'; +import { StrictMode } from 'react'; +import { HashRouter } from 'react-router-dom'; +import { CssBaseline, styled } from '@mui/material'; +import { ThemeProvider } from './themes'; +import { MainRouter, ChatRouter } from './router'; +import { ChatProvider, ClusterProvider, HostProvider, type HostProps } from './services'; const AppRoot = styled('div')(({ theme }) => { const { palette, typography } = theme; @@ -19,14 +25,40 @@ const AppRoot = styled('div')(({ theme }) => { }; }); -export const App = () => { +const Providers: FC = ({ + children, + hostProps, +}) => { + return ( + + + + + + + + {children} + + + + + + + ); +}; + +export const Main = () => { + return ( + + + + ); +}; + +export const Chat = () => { return ( - - - - - - - + + + ); }; diff --git a/src/frontend/src/chat.tsx b/src/frontend/src/chat.tsx new file mode 100644 index 00000000..a30ad728 --- /dev/null +++ b/src/frontend/src/chat.tsx @@ -0,0 +1,4 @@ +import { createRoot } from 'react-dom/client'; +import { Chat } from './App'; + +createRoot(document.getElementById('root')!).render(); diff --git a/src/frontend/src/components/common/drawer-layout.tsx b/src/frontend/src/components/common/drawer-layout.tsx index 862c5a98..6222df6d 100644 --- a/src/frontend/src/components/common/drawer-layout.tsx +++ b/src/frontend/src/components/common/drawer-layout.tsx @@ -9,7 +9,7 @@ import { Tooltip, Typography, } from '@mui/material'; -import { useCluster } from '../../services'; +import { useCluster, useHost } from '../../services'; import { useAlertDialog } from '../mui'; import { IconBrandGradient } from '../brand'; import { @@ -85,6 +85,8 @@ const DrawerLayoutContent = styled(Stack)(({ theme }) => { }); export const DrawerLayout: FC = ({ children }) => { + const [{ type: hostType }] = useHost(); + const [ { modelName, @@ -115,7 +117,7 @@ export const DrawerLayout: FC = ({ children }) => { confirmLabel: 'Finish', }); useEffect(() => { - if (clusterStatus === 'waiting') { + if (hostType === 'cluster' && clusterStatus === 'waiting') { openWaiting(); } }, [clusterStatus, openWaiting]); @@ -142,6 +144,33 @@ export const DrawerLayout: FC = ({ children }) => { } }, [clusterStatus, openRebalancing]); + const [dialogFailed, { open: openFailed }] = useAlertDialog({ + color: 'primary', + title: '', + content: ( + <> + Scheduler restart + + We have noticed that your scheduler has been disconnected (this would be the computer that + ran the parallax run command). You would need to restart the scheduler, + reconfigure the cluster, and your chat will be back up again! + + + ), + confirmLabel: 'Finish', + }); + useEffect(() => { + if (clusterStatus === 'failed') { + openFailed(); + return; + } + if (clusterStatus === 'idle') { + // Delay trigger, due to the cluster init status is 'idle' before connecting to the scheduler. + const timeoutId = setTimeout(() => openFailed(), 1000); + return () => clearTimeout(timeoutId); + } + }, [clusterStatus, openFailed]); + const [sidebarExpanded, setMenuOpen] = useState(true); const [dialogJoinCommand, { open: openJoinCommand }] = useAlertDialog({ @@ -304,6 +333,7 @@ export const DrawerLayout: FC = ({ children }) => { {dialogJoinCommand} {dialogWaiting} {dialogRebalancing} + {dialogFailed} ); }; diff --git a/src/frontend/src/components/inputs/model-select.tsx b/src/frontend/src/components/inputs/model-select.tsx index 01c7e7ca..d20ecdb0 100644 --- a/src/frontend/src/components/inputs/model-select.tsx +++ b/src/frontend/src/components/inputs/model-select.tsx @@ -9,13 +9,15 @@ import { styled, Typography, } from '@mui/material'; -import { useCluster, type ModelInfo } from '../../services'; +import { useCluster, useHost, type ModelInfo } from '../../services'; import { useRefCallback } from '../../hooks'; import { useAlertDialog } from '../mui'; import { IconRestore } from '@tabler/icons-react'; - -const ModelSelectRoot = styled(Select)<{ ownerState: ModelSelectProps }>(({ theme, ownerState }) => { +const ModelSelectRoot = styled(Select)<{ ownerState: ModelSelectProps }>(({ + theme, + ownerState, +}) => { const { spacing, typography, palette } = theme; const { variant = 'outlined' } = ownerState; @@ -99,6 +101,7 @@ export interface ModelSelectProps { } export const ModelSelect: FC = ({ variant = 'outlined' }) => { + const [{ type: hostType }] = useHost(); const [ { modelName, @@ -133,25 +136,25 @@ export const ModelSelect: FC = ({ variant = 'outlined' }) => { <> : } value={modelName} onChange={onChange} - renderValue={(value) => { + renderValue={(value: unknown) => { const model = modelInfoList.find((m) => m.name === value); - if (!model) return undefined; - - return variant === 'outlined' ? ( - - - - {model.displayName} - {model.name} - - - ) : ( - model.name - ); + if (!model) return value as string; + + return variant === 'outlined' ? + + + + {model.displayName} + {model.name} + + + : model.name; }} + IconComponent={hostType === 'node' ? () => null : undefined} > {modelInfoList.map((model) => renderOption(model))} diff --git a/src/frontend/src/main.tsx b/src/frontend/src/main.tsx index 33dafbf2..8d64d005 100644 --- a/src/frontend/src/main.tsx +++ b/src/frontend/src/main.tsx @@ -1,20 +1,4 @@ -import './global.css'; - -import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import { HashRouter } from 'react-router-dom'; - -import { ThemeProvider } from './themes'; -import { App } from './App'; -import { CssBaseline } from '@mui/material'; +import { Main } from './App'; -createRoot(document.getElementById('root')!).render( - - - - - - - - , -); +createRoot(document.getElementById('root')!).render(
); diff --git a/src/frontend/src/router/chat.tsx b/src/frontend/src/router/chat.tsx new file mode 100644 index 00000000..9498d1d0 --- /dev/null +++ b/src/frontend/src/router/chat.tsx @@ -0,0 +1,46 @@ +// src/router/index.tsx +import { lazy, Suspense, useEffect } from 'react'; +import { useLocation, useRoutes, useNavigate } from 'react-router-dom'; + +const PATH_CHAT = '/chat'; + +const PageChat = lazy(() => import('../pages/chat')); + +const debugLog = (...args: any[]) => { + console.log('%c router.tsx ', 'color: white; background: purple;', ...args); +}; + +export const ChatRouter = () => { + const navigate = useNavigate(); + const { pathname } = useLocation(); + + useEffect(() => { + const lazyNavigate = (path: string) => { + const timer = setTimeout(() => { + debugLog('navigate to', path); + navigate(path); + }, 300); + return () => clearTimeout(timer); + }; + + if (!pathname.startsWith(PATH_CHAT)) { + return lazyNavigate(PATH_CHAT); + } + }, [navigate, pathname]); + + const routes = useRoutes([ + { + path: PATH_CHAT, + element: ( + Loading...}> + + + ), + }, + { + path: '*', + element:
404 - Page Not Found
, + }, + ]); + return routes; +}; diff --git a/src/frontend/src/router/index.tsx b/src/frontend/src/router/index.tsx index 7c5ae970..7a004f11 100644 --- a/src/frontend/src/router/index.tsx +++ b/src/frontend/src/router/index.tsx @@ -1,81 +1,2 @@ -// src/router/index.tsx -import { lazy, Suspense, useEffect } from 'react'; -import { useLocation, useRoutes, Navigate, useNavigate } from 'react-router-dom'; -import { useCluster } from '../services'; -import { useConstCallback, useRefCallback } from '../hooks'; - -const PATH_SETUP = '/setup'; -const PATH_JOIN = '/join'; -const PATH_CHAT = '/chat'; - -const PageSetup = lazy(() => import('../pages/setup')); -const PageJoin = lazy(() => import('../pages/join')); -const PageChat = lazy(() => import('../pages/chat')); - -const debugLog = (...args: any[]) => { - console.log('%c router.tsx ', 'color: white; background: purple;', ...args); -}; - -export const Router = () => { - const navigate = useNavigate(); - const { pathname } = useLocation(); - - const [ - { - clusterInfo: { status }, - }, - ] = useCluster(); - - useEffect(() => { - const lazyNavigate = (path: string) => { - const timer = setTimeout(() => { - debugLog('navigate to', path); - navigate(path); - }, 300); - return () => clearTimeout(timer); - }; - - if (pathname === '/') { - return lazyNavigate(PATH_SETUP); - } - debugLog('pathname', pathname, 'cluster status', status); - if (status === 'idle' && pathname.startsWith(PATH_CHAT)) { - return lazyNavigate(PATH_SETUP); - } - if (status === 'available' && !pathname.startsWith(PATH_CHAT)) { - return lazyNavigate(PATH_CHAT); - } - }, [navigate, pathname, status]); - - const routes = useRoutes([ - { - path: PATH_SETUP, - element: ( - Loading...}> - - - ), - }, - { - path: PATH_JOIN, - element: ( - Loading...}> - - - ), - }, - { - path: PATH_CHAT, - element: ( - Loading...}> - - - ), - }, - { - path: '*', - element:
404 - Page Not Found
, - }, - ]); - return routes; -}; +export * from './main'; +export * from './chat'; diff --git a/src/frontend/src/router/main.tsx b/src/frontend/src/router/main.tsx new file mode 100644 index 00000000..7f5eae74 --- /dev/null +++ b/src/frontend/src/router/main.tsx @@ -0,0 +1,81 @@ +// src/router/index.tsx +import { lazy, Suspense, useEffect } from 'react'; +import { useLocation, useRoutes, Navigate, useNavigate } from 'react-router-dom'; +import { useCluster } from '../services'; +import { useConstCallback, useRefCallback } from '../hooks'; + +const PATH_SETUP = '/setup'; +const PATH_JOIN = '/join'; +const PATH_CHAT = '/chat'; + +const PageSetup = lazy(() => import('../pages/setup')); +const PageJoin = lazy(() => import('../pages/join')); +const PageChat = lazy(() => import('../pages/chat')); + +const debugLog = (...args: any[]) => { + console.log('%c router.tsx ', 'color: white; background: purple;', ...args); +}; + +export const MainRouter = () => { + const navigate = useNavigate(); + const { pathname } = useLocation(); + + const [ + { + clusterInfo: { status }, + }, + ] = useCluster(); + + useEffect(() => { + const lazyNavigate = (path: string) => { + const timer = setTimeout(() => { + debugLog('navigate to', path); + navigate(path); + }, 300); + return () => clearTimeout(timer); + }; + + if (pathname === '/') { + return lazyNavigate(PATH_SETUP); + } + debugLog('pathname', pathname, 'cluster status', status); + if (status === 'idle' && pathname.startsWith(PATH_CHAT)) { + return lazyNavigate(PATH_SETUP); + } + if (status === 'available' && !pathname.startsWith(PATH_CHAT)) { + return lazyNavigate(PATH_CHAT); + } + }, [navigate, pathname, status]); + + const routes = useRoutes([ + { + path: PATH_SETUP, + element: ( + Loading...}> + + + ), + }, + { + path: PATH_JOIN, + element: ( + Loading...}> + + + ), + }, + { + path: PATH_CHAT, + element: ( + Loading...}> + + + ), + }, + { + path: '*', + element:
404 - Page Not Found
, + }, + ]); + return routes; +}; diff --git a/src/frontend/src/services/cluster.tsx b/src/frontend/src/services/cluster.tsx index 54205a4d..65f46fa2 100644 --- a/src/frontend/src/services/cluster.tsx +++ b/src/frontend/src/services/cluster.tsx @@ -6,6 +6,7 @@ import { createStreamClusterStatus, getModelList, initScheduler } from './api'; import logoUrlQwen from '../assets/models/Qwen3.png'; import logoUrlGpt from '../assets/models/OpenAI-black-monoblossom.svg'; +import { useHost } from './host'; const getLogoUrl = (name: string) => { name = name.toLowerCase(); @@ -29,7 +30,7 @@ export interface ModelInfo { readonly logoUrl: string; } -export type ClusterStatus = 'idle' | 'waiting' | 'available' | 'rebalancing'; +export type ClusterStatus = 'idle' | 'waiting' | 'available' | 'rebalancing' | 'failed'; export interface ClusterInfo { readonly id: string; @@ -85,6 +86,8 @@ const context = createContext = ({ children }) => { + const [{ type: hostType }] = useHost(); + // Init Parameters const [networkType, setNetworkType] = useState('local'); const [initNodesNumber, setInitNodesNumber] = useState(1); @@ -94,6 +97,9 @@ export const ClusterProvider: FC = ({ children }) => { const [modelInfoList, setModelInfoList] = useState([]); const updateModelList = useRefCallback(async () => { + if (hostType === 'node') { + return; + } let succeed = false; while (!succeed) { try { diff --git a/src/frontend/src/services/host.tsx b/src/frontend/src/services/host.tsx new file mode 100644 index 00000000..f3483a21 --- /dev/null +++ b/src/frontend/src/services/host.tsx @@ -0,0 +1,44 @@ +/* eslint-disable react-refresh/only-export-components */ +import type { FC, PropsWithChildren } from 'react'; +import { createContext, useContext, useMemo } from 'react'; +import { useConst } from '../hooks'; + +export type HostType = 'cluster' | 'node'; + +export interface HostProps { + readonly type: HostType; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface HostStates extends HostProps {} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface HostActions {} + +const context = createContext(undefined); + +const { Provider } = context; + +export const HostProvider: FC> = ({ children, type }) => { + const actions: HostActions = useConst(() => ({})); + + const value = useMemo( + () => [ + { + type, + }, + actions, + ], + [type, actions], + ); + + return {children}; +}; + +export const useHost = (): readonly [HostStates, HostActions] => { + const value = useContext(context); + if (!value) { + throw new Error('useHost must be used within a HostProvider'); + } + return value; +}; diff --git a/src/frontend/src/services/index.ts b/src/frontend/src/services/index.ts index 8d47839f..6218097c 100644 --- a/src/frontend/src/services/index.ts +++ b/src/frontend/src/services/index.ts @@ -1,2 +1,3 @@ export * from './chat'; export * from './cluster'; +export * from './host'; diff --git a/src/frontend/vite.config.ts b/src/frontend/vite.config.ts index 587adb6d..87aee74d 100644 --- a/src/frontend/vite.config.ts +++ b/src/frontend/vite.config.ts @@ -1,8 +1,21 @@ +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react-swc'; +const __dirname = dirname(fileURLToPath(import.meta.url)); + // https://vite.dev/config/ export default defineConfig({ + build: { + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html'), + chat: resolve(__dirname, 'chat.html'), + }, + }, + }, plugins: [react()], server: { proxy: { diff --git a/src/parallax/launch.py b/src/parallax/launch.py index e0d9c6d8..50badf74 100644 --- a/src/parallax/launch.py +++ b/src/parallax/launch.py @@ -21,6 +21,7 @@ from parallax.p2p.server import ServerState, launch_p2p_server from parallax.server.executor import Executor from parallax.server.http_server import launch_http_server +from parallax.server.node_chat_http_server import launch_node_chat_http_server from parallax.server.server_args import parse_args from parallax.utils.utils import get_current_device from parallax_utils.ascii_anime import display_parallax_join @@ -47,6 +48,7 @@ gradient_server = None http_server_process = None executor = None + node_chat_http_server_process = None try: args = parse_args() set_log_level(args.log_level) @@ -75,6 +77,7 @@ if args.start_layer == 0: http_server_process = launch_http_server(args) executor = Executor.create_from_args(args) + node_chat_http_server_process = launch_node_chat_http_server(args) launch_p2p_server( initial_peers=args.initial_peers, scheduler_addr=args.scheduler_addr, @@ -135,6 +138,7 @@ if args.start_layer == 0: http_server_process = launch_http_server(args) executor = Executor.create_from_args(args) + node_chat_http_server_process = launch_node_chat_http_server(args) if gradient_server is not None: gradient_server.status = ServerState.READY @@ -160,6 +164,21 @@ def terminate_http_server_process(process): target=terminate_http_server_process, args=(http_server_process,) ) t.start() + if node_chat_http_server_process is not None: + + def terminate_node_chat_http_server_process(process): + logger.debug("Terminating node chat HTTP server process...") + try: + process.kill() + process.join() + except Exception as e: + logger.error(f"Failed to terminate node chat HTTP server process: {e}") + + t = threading.Thread( + target=terminate_node_chat_http_server_process, + args=(node_chat_http_server_process,), + ) + t.start() if gradient_server is not None: gradient_server.shutdown() if executor is not None: diff --git a/src/parallax/p2p/server.py b/src/parallax/p2p/server.py index cc2c0f6f..0370163b 100644 --- a/src/parallax/p2p/server.py +++ b/src/parallax/p2p/server.py @@ -300,7 +300,7 @@ def run(self): if self.scheduler_addr is not None: # central scheduler mode try: - self.scheduler_stub = RPCConnectionHandler(self.lattica, None).get_stub( + self.scheduler_stub = RPCConnectionHandler(self.lattica, None, None).get_stub( self.scheduler_peer_id ) node_info = self.get_node_info() diff --git a/src/parallax/server/node_chat_http_server.py b/src/parallax/server/node_chat_http_server.py new file mode 100644 index 00000000..e1762325 --- /dev/null +++ b/src/parallax/server/node_chat_http_server.py @@ -0,0 +1,318 @@ +import asyncio +import json +import multiprocessing as mp +import time +from typing import Dict + +import fastapi +import uvicorn +from fastapi import Request +from fastapi.responses import FileResponse, JSONResponse, StreamingResponse +from fastapi.staticfiles import StaticFiles +from lattica import Lattica +from starlette.concurrency import iterate_in_threadpool +from starlette.datastructures import State + +from backend.server.rpc_connection_handler import RPCConnectionHandler +from common.file_util import get_project_root +from parallax_utils.logging_config import get_logger + +logger = get_logger(__name__) + +import uuid + +# Fast API +app = fastapi.FastAPI( + openapi_url="/openapi.json", +) + + +async def init_app_states(state: State, node_chat_http_server): + """Init FastAPI app states, including http handler, etc.""" + state.http_server = node_chat_http_server + + +async def v1_chat_completions(request_data: Dict, request_id: str, received_ts: int): + return app.state.http_server.chat_completion(request_data, request_id, received_ts) + + +async def get_cluster_status(): + return app.state.http_server.get_cluster_status() + + +@app.post("/v1/chat/completions") +async def openai_v1_chat_completions(raw_request: Request): + """OpenAI v1/chat/complete post function""" + request_data = await raw_request.json() + request_id = uuid.uuid4() + received_ts = time.time() + return await v1_chat_completions(request_data, request_id, received_ts) + + +@app.get("/cluster/status") +async def cluster_status(): + return await get_cluster_status() + + +# Disable caching for index.html +@app.get("/") +async def serve_index(): + response = FileResponse(str(get_project_root()) + "/src/frontend/dist/chat.html") + # Disable cache + response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" + response.headers["Pragma"] = "no-cache" + response.headers["Expires"] = "0" + return response + + +# mount the frontend +app.mount( + "/", + StaticFiles(directory=str(get_project_root() / "src" / "frontend" / "dist"), html=True), + name="static", +) + + +class NodeChatHttpServer: + + def __init__(self, args): + self.host = args.host + self.port = args.node_chat_port + self.tcp_port = args.tcp_port + self.udp_port = args.udp_port + self.scheduler_addr = args.scheduler_addr + self.relay_servers = args.relay_servers + self.announce_maddrs = args.announce_maddrs + self.initial_peers = args.initial_peers + self.host_maddrs = ( + [f"/ip4/0.0.0.0/tcp/{self.tcp_port}", f"/ip4/0.0.0.0/udp/{self.udp_port}/quic-v1"], + ) + self.scheduler_peer_id = None + self.scheduler_stub = None + self.lattica = None + + def build_lattica(self): + self.lattica = Lattica.builder().with_listen_addrs(self.host_maddrs) + + if self.scheduler_addr is not None and self.scheduler_addr != "auto": + if self.scheduler_addr.startswith("/"): + logger.info(f"Using scheduler addr: {self.scheduler_addr}") + self.lattica.with_bootstraps([self.scheduler_addr]) + self.scheduler_peer_id = self.scheduler_addr.split("/")[-1] + + if len(self.relay_servers) > 0: + logger.info(f"Using relay servers: {self.relay_servers}") + self.lattica.with_relay_servers(self.relay_servers).with_dcutr(True) + if self.scheduler_peer_id is not None: + logger.info(f"Using protocol: /{self.scheduler_peer_id}") + self.lattica.with_protocol("/" + self.scheduler_peer_id) + + if len(self.announce_maddrs) > 0: + logger.info(f"Using announce maddrs: {self.announce_maddrs}") + self.lattica.with_external_addrs(self.announce_maddrs) + + if len(self.initial_peers) > 0: + logger.info(f"Using initial peers: {self.initial_peers}") + self.lattica.with_bootstraps(self.initial_peers) + + self.lattica.build() + + if self.scheduler_addr == "auto": + self.scheduler_peer_id = None + for _ in range(20): + try: + time.sleep(3) + self.scheduler_peer_id = self.lattica.get("scheduler_peer_id") + if self.scheduler_peer_id is not None: + self.scheduler_peer_id = self.scheduler_peer_id.value + logger.info(f"Found scheduler peer id: {self.scheduler_peer_id}") + break + logger.info( + f"Discovering scheduler peer id, {_ + 1} times, you can specify scheduler peer id by -s" + ) + except Exception as e: + logger.warning(f"Failed to get scheduler addr: {e}, waiting for 3 seconds.") + if self.scheduler_peer_id is None: + logger.error("Failed to get scheduler peer id") + return False + + # ensure connect to scheduler + for _ in range(10): + if self.scheduler_peer_id in self.lattica.get_all_peers(): + return True + logger.warning("Scheduler peer id not found, waiting for 1 second.") + time.sleep(1) + + return False + + def chat_completion(self, request_data, request_id: str, received_ts: int): + if self.scheduler_addr is not None: # central scheduler mode + try: + if self.scheduler_stub is None: + self.scheduler_stub = RPCConnectionHandler(self.lattica, None, None).get_stub( + self.scheduler_peer_id + ) + stub = self.scheduler_stub + is_stream = request_data.get("stream", False) + try: + if is_stream: + + async def stream_generator(): + response = stub.chat_completion(request_data) + + try: + iterator = iterate_in_threadpool(response) + async for chunk in iterator: + yield chunk + finally: + response.cancel() + + resp = StreamingResponse( + stream_generator(), + media_type="text/event-stream", + headers={ + "X-Content-Type-Options": "nosniff", + "Cache-Control": "no-cache", + }, + ) + logger.debug(f"Streaming response initiated for {request_id}") + return resp + else: + response = stub.chat_completion(request_data) + response = next(response).decode() + logger.debug(f"Non-stream response completed for {request_id}") + # response is a JSON string; parse to Python object before returning + return JSONResponse(content=json.loads(response)) + except Exception as e: + logger.exception(f"Error in _forward_request: {e}") + return JSONResponse( + content={"error": "Internal server error"}, + status_code=500, + ) + + except Exception as e: + logger.exception(f"Error in chat completion: {e}") + return JSONResponse( + content={"error": "Internal server error"}, + status_code=500, + ) + else: + logger.error("No scheduler address specified") + return JSONResponse( + content={"error": "No scheduler address specified"}, + status_code=500, + ) + + def get_cluster_status(self): + if self.scheduler_addr is not None: # central scheduler mode + try: + if self.scheduler_stub is None: + self.scheduler_stub = RPCConnectionHandler(self.lattica, None, None).get_stub( + self.scheduler_peer_id + ) + stub = self.scheduler_stub + try: + + async def stream_status(): + response = stub.cluster_status() + + async def non_blocking_next_streamiter(stream_iter, timeout=0.01): + try: + chunk = await asyncio.wait_for( + asyncio.get_event_loop().run_in_executor( + None, next, stream_iter + ), + timeout=timeout, + ) + return chunk + except asyncio.TimeoutError: + return None + except StopIteration: + return None + + try: + as_iterator = iter(response) + for _ in range(60): + await asyncio.sleep(1) + chunk = await non_blocking_next_streamiter(as_iterator, timeout=0.1) + if chunk is None: + continue + yield (chunk) + finally: + response.cancel() + + resp = StreamingResponse( + stream_status(), + media_type="application/x-ndjson", + headers={ + "X-Content-Type-Options": "nosniff", + "Cache-Control": "no-cache", + }, + ) + return resp + except Exception as e: + logger.exception(f"Error in get cluster status: {e}") + return JSONResponse( + content={"error": "Internal server error"}, + status_code=500, + ) + + except Exception as e: + logger.exception(f"Error in get cluster status: {e}") + return JSONResponse( + content={"error": "Internal server error"}, + status_code=500, + ) + else: + logger.error("No scheduler address specified") + return JSONResponse( + content={"error": "No scheduler address specified"}, + status_code=500, + ) + + async def run_uvicorn(self): + """ + Since uvicorn.run() uses asyncio.run, we need another wrapper + to create a uvicorn asyncio task to run multiple tasks. + """ + config = uvicorn.Config( + app, + host=self.host, + port=self.port, + timeout_keep_alive=5, + loop="uvloop", + ) + server = uvicorn.Server(config) + await server.serve() + + def run(self): + """ + Launch A FastAPI server that routes requests to the executor. + + Note: + 1. The HTTP server and executor both run in the main process. + 2. Inter-process communication is done through IPC (each process uses a different port) via the ZMQ library. + """ + while not self.build_lattica(): + logger.error("Failed to build lattica, waiting for 10 seconds") + time.sleep(10) + logger.info("Lattica built successfully") + + asyncio.run( + init_app_states( + app.state, + self, + ) + ) + asyncio.run(self.run_uvicorn()) + + +def launch_node_chat_http_server(args): + """ + Launch function of node chat http server. + It creates a sub-process for the http server. + """ + node_chat_http_server = NodeChatHttpServer(args) + process = mp.Process(target=node_chat_http_server.run) + process.start() + return process diff --git a/src/parallax/server/server_args.py b/src/parallax/server/server_args.py index 34c38412..5b1429e0 100644 --- a/src/parallax/server/server_args.py +++ b/src/parallax/server/server_args.py @@ -28,6 +28,9 @@ def parse_args() -> argparse.Namespace: # HTTP server configuration parser.add_argument("--host", type=str, default="localhost", help="Host of the HTTP server.") parser.add_argument("--port", type=int, default=3000, help="Port of the HTTP server") + parser.add_argument( + "--node-chat-port", type=int, default=3002, help="Port of the node chat HTTP server" + ) # Lattica configuration parser.add_argument("--initial-peers", nargs="+", default=[], help="List of initial DHT peers")