From 7e233e48b22b61f32ac18d71f7f0887e9afb1ad9 Mon Sep 17 00:00:00 2001 From: legop3 Date: Wed, 25 Feb 2026 15:50:20 -0500 Subject: [PATCH] promptings --- server/prompts/commentary_system.txt | 7 +++- .../{index-B49ZvEXj.js => index-BdawUWxg.js} | 2 +- server/public/index.html | 2 +- server/src/services/llmCommentaryService.js | 38 ++++++------------- webui/src/components/AdminPanel.jsx | 4 ++ 5 files changed, 24 insertions(+), 29 deletions(-) rename server/public/assets/{index-B49ZvEXj.js => index-BdawUWxg.js} (90%) diff --git a/server/prompts/commentary_system.txt b/server/prompts/commentary_system.txt index 34f9a324..8e5a7bab 100644 --- a/server/prompts/commentary_system.txt +++ b/server/prompts/commentary_system.txt @@ -14,8 +14,11 @@ Conversation format you will receive: Decision policy: - SKIP is the default. -- Speak only for clearly notable, new moments. +- Speak for clearly notable, new moments from either chat OR rover activity. - If uncertain, output SKIP. +- Try not to SKIP more than 10 times in a row. +- Treat chat energy and rover telemetry as equal signal sources. +- A strong chat moment alone can justify speaking, even without a major rover state change. Grounding policy: - Use timeline context for narrative flow. @@ -24,6 +27,8 @@ Grounding policy: - Do not assume people's intentions, motivations, or next actions. Significant examples: +- lively or funny chat burst worth reacting to +- a user/rover moment that connects to recent chat - wheels_off_ground true - dock/charge transitions - battery_low becoming true diff --git a/server/public/assets/index-B49ZvEXj.js b/server/public/assets/index-BdawUWxg.js similarity index 90% rename from server/public/assets/index-B49ZvEXj.js rename to server/public/assets/index-BdawUWxg.js index 7eb1251f..bdb4d17d 100644 --- a/server/public/assets/index-B49ZvEXj.js +++ b/server/public/assets/index-BdawUWxg.js @@ -14,7 +14,7 @@ Please change the parent to i&&i>o){const f=oi/100,d=1-f;if(a<=o)return 0;if(a<=i){const p=(a-o)/(i-o);return Math.round(Math.max(0,Math.min(1,p))*oi)}if(a>=l)return 100;const h=(a-i)/(l-i);return Math.round((f+Math.max(0,Math.min(1,h))*d)*100)}return null}const fj=1600,dj=800;function fn(...a){return a.filter(Boolean).join(" ")}function yf({visual:a,orientation:l="horizontal",variant:i="inline",compact:o=!1,showLabel:f,className:d=""}){const h=i==="background",p=l==="vertical",g=f??!h;if(!a?.available){if(h){const ae=oi,ce=fn("absolute z-10",p?"left-0 right-0 h-[2px]":"top-0 bottom-0 w-[2px]","bg-black/70",M&&!j&&"battery-tick-warn"),he=p?{bottom:`${ae}%`}:{left:`${ae}%`},X=fn("absolute inset-0 pointer-events-none",d);return r.jsxs("div",{className:X,children:[r.jsx("div",{className:"absolute inset-0 bg-slate-900/35"}),r.jsx("div",{className:ce,style:he})]})}return p?null:r.jsx("p",{className:"text-xs text-slate-500",children:"Battery telemetry unavailable"})}const v=a.percentDisplay??0,y=Math.max(0,Math.min(100,v)),b=a.warnDisplayPercent??oi,j=!!a.urgentActive,M=!!a.warnActive,w=j?`${dj}ms`:M?`${fj}ms`:void 0,N=h?j||M?"bg-red-500/45":"bg-emerald-500/45":j||M?"bg-red-500":"bg-emerald-500",E=j||M?"bg-red-900/35":"bg-slate-900/35",S=j||M?"bg-red-900/45":"bg-slate-900/35",T=fn(h?"absolute inset-0 pointer-events-none":"relative pointer-events-auto",p&&!h?"flex flex-col items-center justify-center":"w-full",d);if(h){const ae=p?{height:`${y}%`}:{width:`${y}%`},ce=fn("absolute",p?"bottom-0 left-0 right-0":"left-0 top-0 bottom-0",N,"transition-[width,height]",j&&"battery-urgent-flash"),he=fn("absolute z-10",p?"left-0 right-0 h-[2px]":"top-0 bottom-0 w-[2px]","bg-black/70",M&&!j&&"battery-tick-warn"),X=p?{bottom:`${b}%`}:{left:`${b}%`};return r.jsxs("div",{className:T,children:[r.jsx("div",{className:fn("absolute inset-0",E,j&&"battery-urgent-flash")}),r.jsx("div",{className:ce,style:{...ae,animationDuration:w}}),r.jsx("div",{className:he,style:X})]})}const B=fn("relative overflow-hidden rounded-sm border border-slate-800",j||M?S:"bg-zinc-900/90",j&&"battery-urgent-flash",p?"w-full flex-1":o?"h-4":"h-5"),L=p?{height:`${y}%`}:{width:`${y}%`},$=fn("absolute",p?"bottom-0 left-0 right-0":"left-0 top-0 bottom-0",N,"transition-[width,height]",j&&"battery-urgent-flash"),_=fn("absolute z-10",p?"left-0 right-0 h-[2px]":"top-0 bottom-0 w-[2px]","bg-black/70",M&&!j&&"battery-tick-warn"),V=p?{bottom:`${b}%`}:{left:`${b}%`},G=`Battery ${v}%`,Z=fn("pointer-events-none absolute inset-0 flex items-center justify-center text-center font-semibold",p?o?"text-[0.55rem]":"text-[0.65rem]":"text-xs"),F=fn("rounded px-1.5 py-0.5",h?"bg-black/30 text-slate-100":"bg-black/50 text-slate-100");return r.jsxs("div",{className:T,children:[r.jsxs("div",{className:B,children:[r.jsx("div",{className:$,style:{...L,animationDuration:w}}),r.jsx("div",{className:_,style:V})]}),g&&!p?r.jsx("div",{className:Z,children:r.jsx("span",{className:F,children:G})}):null,g&&p?r.jsx("div",{className:"mt-0.5 text-[0.65rem] font-semibold text-slate-100",children:r.jsxs("span",{className:F,children:[v,"%"]})}):null]})}const _p=2e3,mj=3e3,hj=3e3,pj=40,xj=1e3;function ci({sessionInfo:a,audioSessionInfo:l,videoMode:i="whep",snapshotFeed:o=null,qualityNotice:f=null,label:d,forceMute:h=!1,telemetryFrame:p,batteryConfig:g,layoutFormat:v="desktop",hudVariant:y="default",driverLabel:b=null,hudForceMap:j=!1,hudMapPosition:M="top-center",hudLabelScale:w=1,fitParent:C=!1,overcurrentLimiter:N=null,showTurnCue:E=!1,turnTimerText:S=null,turnSeconds:T=null,isActiveDriver:B=!1,idleSkipSeconds:L=null}){const{session:$}=Ve(),_=m.useRef(null),V=m.useRef(null),G=m.useRef(null),Z=m.useRef(null),F=m.useRef(null),ae=m.useRef(null),ce=m.useRef(null),he=m.useRef(null),[X,k]=m.useState("idle"),[Q,P]=m.useState(null),[oe,ue]=m.useState("idle"),[q,U]=m.useState(null),[te,le]=m.useState(0),[se,I]=m.useState(0),[re,de]=m.useState(!0),pe=!!l?.url,Ce=i==="snapshot",ne=p?.sensors,{value:ie}=st("audio",jt),Se=Number.isFinite(ie?.masterVolume)?ie.masterVolume:jt.masterVolume,Oe=Number.isFinite(ie?.roverVolume)?ie.roverVolume:jt.roverVolume,Ne=typeof ie?.mainBrushDuckEnabled=="boolean"?ie.mainBrushDuckEnabled:typeof ie?.autoLevelEnabled=="boolean"?ie.autoLevelEnabled:jt.mainBrushDuckEnabled,Le=Number.isFinite(ie?.mainBrushDuckAmount)?Math.max(0,Math.min(1,ie.mainBrushDuckAmount)):jt.mainBrushDuckAmount,an=Math.max(0,Math.min(1,Se*Oe)),wa=ne?.batteryChargeMah??null,Wn=v==="desktop",it=!Wn,ln=it?"top-right":M,[fo]=lg(),mo=j||it?!0:fo,mt=sg({charge:wa,config:g}),Pa=ne?.wheelOvercurrents||null,el=Pa==null?[]:Object.entries(Pa).filter(([,ge])=>!!ge).map(([ge])=>ge),sn=N?.caps||null,Ea=N?.overcurrent?.groups||null,gi=typeof window<"u"&&new URLSearchParams(window.location.search).has("debugAudio"),tl=typeof window<"u"&&new URLSearchParams(window.location.search).has("debugHud"),ho=m.useMemo(()=>{if(!sn)return null;const ge=Number.isFinite(sn?.drive?.cap)?sn.drive.cap:1,Xe=Number.isFinite(sn?.aux?.cap)?sn.aux.cap:1;return Math.max(0,Math.min(1,1-Math.min(ge,Xe)))},[sn]),ts=!!N?.isActive,rn=el.length?el:ts?["limiter"]:[],pt=ho??(el.length?1:0),Yt=!!rn.length,rt=!!((Number(ne?.mainBrushCurrentMa)||0)>pj||ne?.wheelOvercurrents?.mainBrush),po=Ne&&rt?1-Le:1,nl=Math.max(0,Math.min(1,an*po)),xo=Ne&&rt&&Le>0?`Volume decreased ${Math.round(Le*1e3)/10}%`:null,Ca=m.useRef({hasDedicatedAudio:!1,audioUrl:null,mainBrushDuckEnabled:!1,mainBrushDuckAmount:0,mainBrushActive:!1,baseRoverGain:0,effectiveRoverGain:0});m.useEffect(()=>{Ca.current={hasDedicatedAudio:pe,audioUrl:l?.url||null,mainBrushDuckEnabled:Ne,mainBrushDuckAmount:Le,mainBrushActive:rt,baseRoverGain:an,effectiveRoverGain:nl}},[pe,l?.url,Ne,Le,rt,an,nl]);const Fe=m.useCallback((ge,Xe={})=>{if(!gi)return;const Qe=V.current,je=Ca.current,ze={event:ge,ts:Date.now(),roverLabel:d||null,hasDedicatedAudio:je.hasDedicatedAudio,audioUrl:je.audioUrl,mainBrushDuckEnabled:je.mainBrushDuckEnabled,mainBrushDuckAmount:je.mainBrushDuckAmount,mainBrushActive:je.mainBrushActive,baseRoverGain:je.baseRoverGain,effectiveRoverGain:je.effectiveRoverGain,element:Qe?{muted:Qe.muted,volume:Qe.volume,paused:Qe.paused,ended:Qe.ended,readyState:Qe.readyState,networkState:Qe.networkState}:null,...Xe};try{console.log(`[AudioDebug] ${ge} ${JSON.stringify(ze)}`)}catch{console.log("[AudioDebug]",ge,ze)}},[gi,d]),al=$?.socials?.find(ge=>String(ge?.id||ge?.label||"").toLowerCase()==="discord")?.url||$?.discord?.invite||null;m.useEffect(()=>{tl&&console.log("[OvercurrentHUD]",{overlayVisible:Yt,overlayMotors:rn,overlayFill:pt,limiterActive:ts,limiterCaps:sn,limiterGroups:Ea,wheelOvercurrents:Pa})},[tl,pt,rn,Yt,ts,sn,Ea,Pa]),m.useEffect(()=>{Fe("settings/update")},[Fe]);const _t=m.useCallback(()=>{clearTimeout(G.current),G.current=setTimeout(()=>le(Date.now()),_p)},[]),Pn=m.useCallback(()=>{clearTimeout(Z.current),Z.current=setTimeout(()=>I(Date.now()),_p)},[]),xn=m.useCallback(async()=>{const ge=_.current;if(ge)try{ge.muted=!0,await ge.play()}catch{}},[]),vi=m.useCallback((ge=0)=>{if(h||pe)return;clearTimeout(ae.current);const Xe=()=>{clearTimeout(ae.current),ae.current=setTimeout(()=>{h||Qe()},mj)},Qe=async()=>{const je=_.current;if(je)try{await xn(),je.muted=!1,await je.play(),de(!1)}catch{je.muted=!0,de(!0),Xe()}};ae.current=setTimeout(Qe,ge)},[xn,h,pe]);m.useEffect(()=>()=>{clearTimeout(G.current),clearTimeout(Z.current),clearTimeout(ae.current),clearInterval(F.current),ce.current&&(cancelAnimationFrame(ce.current),ce.current=null)},[]),m.useEffect(()=>{X==="playing"&&vi(0)},[X,vi]),m.useEffect(()=>{Ce&&(k("snapshot"),P(null))},[Ce]),m.useEffect(()=>{if(Ce||!a?.url||!_.current)return;let ge=!0,Xe;const Qe=setTimeout(()=>de(!0),0),je=(ze,vt)=>{ge&&(Fe("video/status",{nextStatus:ze,info:vt||null}),k(ze),P(vt||null),ze==="playing"&&xn(),["error","failed","disconnected","closed"].includes(ze)&&_t())};return Xe=new Tp({url:a.url,token:a.token,video:_.current,receiveAudio:!pe,onStatus:je}),Xe.start().catch(ze=>{ge&&(k("error"),P(ze.message),_t())}),()=>{ge=!1,clearTimeout(Qe),Xe?.stop()}},[Ce,a?.url,a?.token,te,_t,xn,pe,Fe]),m.useEffect(()=>{X==="stopped"&&a?.url&&_t()},[X,a?.url,_t]),m.useEffect(()=>{const ge=V.current;if(!ge||!l?.url){Fe("route/no-audio-url"),he.current=null;return}const Xe=Math.max(0,Math.min(1,nl)),Qe=he.current==null?Math.max(0,Math.min(1,ge.volume)):he.current;if(ce.current&&(cancelAnimationFrame(ce.current),ce.current=null),Xe>Qe){const je=performance.now(),ze=Qe,vt=Xe-ze,Et=Pe=>{const Te=Math.min(1,(Pe-je)/xj),Gt=ze+vt*Te;ge.volume=Gt,he.current=Gt,Te<1?ce.current=requestAnimationFrame(Et):ce.current=null};ce.current=requestAnimationFrame(Et)}else ge.volume=Xe,he.current=Xe;Fe("route/direct",{elementVolume:Xe,duckEnabled:Ne,mainBrushActive:rt,duckAmount:Le})},[l?.url,nl,Ne,rt,Le,Fe]),m.useEffect(()=>{if(!l?.url||!V.current)return;let ge=!0,Xe;const Qe=(je,ze)=>{ge&&(Fe("audio/status",{nextStatus:je,info:ze||null}),U(ze||(je==="connected"?"connected":null)),ue(vt=>je==="connected"&&(vt==="playing"||vt==="connecting")||je==="new"?vt:je),["error","failed"].includes(je)&&Pn())};return Xe=new Tp({url:l.url,token:l.token,video:V.current,audioOnly:!0,onStatus:Qe}),Xe.start().catch(je=>{ge&&(ue("error"),U(je.message),Pn())}),()=>{ge=!1,Xe?.stop()}},[l?.url,l?.token,se,Pn,Fe]),m.useEffect(()=>{const ge=V.current;if(!l?.url||!ge){clearInterval(F.current);return}if(!["connecting","connected","playing","paused"].includes(oe)){clearInterval(F.current);return}const Qe=async()=>{const je=V.current;je&&(je.muted=!1,!(!je.paused&&!je.ended)&&(Fe("retry/play-attempt"),je.play().then(()=>{Fe("retry/play-ok"),ue(ze=>ze==="connected"?"playing":ze),U(ze=>ze==="paused"?null:ze)}).catch(ze=>{Fe("retry/play-fail",{error:ze?.message}),U(vt=>vt||ze?.message||"autoplay blocked")})))};return Qe(),F.current=setInterval(Qe,hj),()=>clearInterval(F.current)},[l?.url,oe,Fe]),m.useEffect(()=>{const ge=V.current;if(!ge)return;const Xe=()=>{Fe("element/play"),ue(Te=>Te==="error"?Te:"playing"),U(null)},Qe=()=>{Fe("element/pause"),ue(Te=>["error","failed","disconnected","closed","stopped"].includes(Te)?Te:"paused"),U(Te=>Te||"paused")},je=()=>{Fe("element/ended"),ue(Te=>Te==="error"?Te:"stopped"),U(Te=>Te||"ended")},ze=()=>{const{error:Te}=ge,Gt=Te?.message||"audio error";Fe("element/error",{error:Gt}),ue("error"),U(Gt)},vt=()=>Fe("element/waiting"),Et=()=>Fe("element/canplay"),Pe=()=>Fe("element/stalled");return ge.addEventListener("play",Xe),ge.addEventListener("pause",Qe),ge.addEventListener("ended",je),ge.addEventListener("error",ze),ge.addEventListener("waiting",vt),ge.addEventListener("canplay",Et),ge.addEventListener("stalled",Pe),()=>{ge.removeEventListener("play",Xe),ge.removeEventListener("pause",Qe),ge.removeEventListener("ended",je),ge.removeEventListener("error",ze),ge.removeEventListener("waiting",vt),ge.removeEventListener("canplay",Et),ge.removeEventListener("stalled",Pe)}},[l?.url,Fe]);const yi=o?.error?`Error: ${o.error}`:o?.objectUrl?"snapshot":o?.status||"waiting",ns=Ce?yi:a?.url?X==="error"?`Error: ${Q||"unknown"}`:Q?`${X} (${Q})`:X:"waiting",qa=l?.error?`Error: ${l.error}`:l?.url?oe==="error"?`Error: ${q||"unknown"}`:q?`${oe} (${q})`:oe:null,bi=y==="spectator",$t=y==="none";return r.jsxs("div",{className:`flex flex-col gap-0.5 ${C?"h-full":""}`,children:[r.jsxs("div",{className:`relative w-full overflow-hidden bg-black ${C?"h-full flex-1":"aspect-[4/3]"}`,children:[Ce?o?.objectUrl?r.jsx("img",{src:o.objectUrl,alt:d,className:"h-full w-full object-contain",draggable:!1}):r.jsx("div",{className:"flex h-full w-full items-center justify-center text-sm text-slate-300",children:"Waiting for frame…"}):r.jsx("video",{ref:_,muted:h||re||pe,playsInline:!0,autoPlay:!0,controls:!1,className:"h-full w-full object-contain"}),r.jsx("audio",{ref:V,autoPlay:!0,hidden:!0}),!$t&&E?r.jsx(yj,{mobileHud:it,turnSeconds:T,isActiveDriver:B,idleSkipSeconds:L}):null,$t?null:r.jsx(vj,{frame:p,sensors:ne,label:d,status:ns,audioStatus:qa,levelStatus:xo,desktopLayout:Wn,layoutFormat:v,variant:y,driverLabel:b,battery:mt,showTopDown:mo,mobileHud:it,mapPosition:ln,turnTimerText:S,labelScale:w}),$t?null:r.jsx(jj,{compact:it}),!$t&&tl?r.jsx("div",{className:"pointer-events-none absolute left-1 top-1 z-40 rounded bg-black/80 px-1 py-0.5 text-[0.6rem] text-lime-200",children:`OC vis:${Yt?1:0} motors:${rn.length} fill:${Math.round(pt*100)}%`}):null,$t?null:r.jsx(Sj,{motors:rn,fill:pt,compact:it}),$t?null:r.jsx(Aj,{battery:mt,compact:it}),!$t&&bi&&mt.available?r.jsx("div",{className:"pointer-events-none absolute right-1 top-1/2 flex h-[70%] -translate-y-1/2 flex-col items-center justify-center rounded bg-black/60 px-0.5 pb-1 pt-1",children:r.jsx(yf,{visual:mt,orientation:"vertical",variant:"inline",compact:it,className:"h-full w-4"})}):null,!$t&&f?r.jsx("div",{className:"pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2",children:r.jsxs("div",{className:`mx-auto w-fit rounded border border-amber-300/80 bg-black/75 text-amber-200 ${it?"px-2 py-1 text-[0.6rem]":"px-3 py-1.5 text-sm"}`,children:[r.jsx("div",{className:"text-center",children:f}),r.jsx("div",{className:"pointer-events-auto mt-0",children:r.jsx(Qf,{id:"discord",label:"Join our Discord server while you wait!",url:al})})]})}):null]}),!$t&&!bi&&r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(gj,{sensors:ne}),r.jsx("div",{className:"panel-section space-y-0.5 text-sm",children:r.jsx(yf,{visual:mt,compact:it})})]})]})}function gj({sensors:a}){const l=[a?.lightBumpLeftSignal,a?.lightBumpFrontLeftSignal,a?.lightBumpCenterLeftSignal,a?.lightBumpCenterRightSignal,a?.lightBumpFrontRightSignal,a?.lightBumpRightSignal],i=l.filter(p=>p!=null).reduce((p,g)=>Math.max(p,g),1200),o=p=>Math.pow(Math.max(0,Math.min(1,(p??0)/i)),.35),f=p=>p==null||p<=0?"hsl(200 60% 18%)":`hsl(${(200+o(p)*360)%360} 100% 60%)`,d=6,h=12;return r.jsx("div",{className:"flex w-full items-center justify-center gap-0.5",children:l.map((p,g)=>{const v=o(p),y=gDate.now());m.useEffect(()=>{const P=setInterval(()=>E(Date.now()),150);return()=>clearInterval(P)},[]),a?.receivedAt&&N-a.receivedAt<200;const S=j,T=p==="mobile-portrait",B=S?"text-[0.45rem]":"text-[0.65rem]",L=S?"px-0.25 py-0.25":"px-1 py-0.5",$=S?"px-0.25 py-0.25":"px-0.5 py-0.5",_=S?"text-[0.55rem]":"text-[0.8rem]",V=S?"left-0.5 top-0.5":"left-1 top-1",G=S?"text-[0.5rem]":"text-[0.7rem]",Z=S?"px-0.5 py-0.25":"px-1 py-0.5",F=S?"left-0.5 top-1/2":"left-1 top-1/2",ae="bottom-0.5",ce={transform:`translateX(-50%) scale(${C})`,transformOrigin:"center bottom"},he="240px",X=T?.3:S?.33:.7,Q={width:he,height:he,opacity:S?.6:.7,transform:M==="top-center"?`translateX(-50%) scale(${X})`:`scale(${X})`,transformOrigin:M==="bottom-left"?"bottom left":M==="top-center"?"top center":"top right",...M==="bottom-left"?{left:"0.25rem",bottom:"0.25rem"}:M==="top-center"?{left:"50%",top:"0.25rem"}:{right:"0.25rem",top:"0.25rem"}};if(g==="none")return null;if(g==="spectator"){const P=[["Voltage",l?.voltageMv!=null?`${(l.voltageMv/1e3).toFixed(2)} V`:"--"],["Current",l?.currentMa!=null?`${l.currentMa} mA`:"--"],["Charge",l?.batteryChargeMah!=null?`${l.batteryChargeMah}`:"--"],["OI",l?.oiMode?.label||"--"]],oe=!!l?.chargingSources?.homeBase,ue=l?.chargingState?.label||"",q=!!(ue&&ue.toLowerCase()!=="not charging"),U=l?.oiMode?.label||"Unknown",te=U.toLowerCase(),le=te==="full"?"bg-emerald-500/80 text-emerald-50":te==="safe"?"bg-amber-400/80 text-amber-950":te==="passive"?"bg-slate-700/80 text-slate-100":"bg-slate-700/60 text-slate-200",se=oe?"bg-emerald-500/80 text-emerald-50":"bg-slate-700/70 text-slate-200",I=q?"bg-emerald-500/80 text-emerald-50":oe?"bg-amber-400/80 text-amber-950":"bg-slate-700/70 text-slate-200";return r.jsxs(r.Fragment,{children:[r.jsxs("div",{className:"pointer-events-none absolute inset-0 flex items-center justify-center",children:[r.jsx("div",{className:`absolute ${V} font-medium text-slate-100 ${B}`,children:r.jsxs("div",{className:"flex flex-col gap-0.5 leading-none",children:[r.jsxs("span",{children:["Status: ",o]}),f?r.jsxs("span",{children:["Audio: ",f]}):null,d?r.jsx("span",{className:"text-cyan-300",children:d}):null]})}),r.jsx("div",{className:`absolute ${F} flex -translate-y-1/2 flex-col gap-0.5 bg-black/70 text-slate-100 ${B} ${L}`,children:r.jsxs("div",{className:"space-y-0.5 leading-tight",children:[r.jsxs("div",{className:"flex flex-col gap-0.5 text-[0.75rem] font-semibold uppercase tracking-wide",children:[r.jsx("span",{className:`rounded px-1.5 py-0.5 ${se}`,children:oe?"Docked":"Undocked"}),r.jsx("span",{className:`rounded px-1.5 py-0.5 ${I}`,children:q?"Charging":"Not charging"}),r.jsxs("span",{className:`rounded px-1.5 py-0.5 ${le}`,children:["OI: ",U]})]}),P.map(([re,de])=>r.jsxs("span",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{className:"text-slate-400",children:re}),r.jsx("span",{className:"font-semibold text-white",children:de})]},re))]})}),r.jsx("div",{className:`absolute ${ae} left-1/2`,style:ce,children:r.jsxs("div",{className:`flex items-center gap-0.5 bg-black/80 text-slate-100 ${$} ${_}`,children:[r.jsx("span",{className:"font-semibold text-white",children:i||"Unnamed Rover"}),v?r.jsxs("span",{className:"text-slate-300",children:["• ",v]}):null]})})]}),b?r.jsx("div",{className:"pointer-events-none absolute rounded",style:{...Q},children:r.jsx(vf,{sensors:l,size:240,overlay:!0})}):null]})}return r.jsxs("div",{className:"pointer-events-none absolute inset-0 flex items-center justify-center",children:[r.jsx("div",{className:`absolute ${V} font-medium text-slate-100 ${B}`,children:r.jsxs("div",{className:"flex flex-col gap-0.5 leading-none",children:[r.jsxs("span",{children:["Status: ",o]}),f?r.jsxs("span",{children:["Audio: ",f]}):null,d?r.jsx("span",{className:"text-cyan-300",children:d}):null]})}),w?r.jsx("div",{className:`absolute left-1/2 top-0.5 -translate-x-1/2 rounded bg-black/70 text-slate-100 ${Z} ${G}`,children:w}):null,r.jsx("div",{className:`absolute ${ae} left-1/2`,style:ce,children:r.jsx("div",{className:`flex gap-0.5 bg-black/80 text-slate-100 ${$} ${_}`,children:r.jsxs("span",{children:['Rover: "',i||"Unnamed Rover",'"']})})}),b&&g!=="spectator"?r.jsx("div",{className:"pointer-events-none absolute rounded",style:{...Q},children:r.jsx(vf,{sensors:l,size:240,overlay:!0})}):null]})}function yj({mobileHud:a=!1,turnSeconds:l=null,isActiveDriver:i=!1,idleSkipSeconds:o=null}){const f=a?"text-3xl":"text-5xl",d=a?"text-xs":"text-sm",h=a?"text-[0.55rem]":"text-[0.75rem]",p=a?"px-4 py-3":"px-6 py-4",g=i&&typeof o=="number";return r.jsx("div",{className:"pointer-events-none absolute inset-0 z-30 flex items-center justify-center bg-black/55",children:r.jsxs("div",{className:`flex flex-col items-center gap-0.5 rounded border border-amber-300/80 bg-black/70 ${p}`,children:[r.jsx("div",{className:`font-semibold text-amber-200 ${f}`,children:"IT IS YOUR TURN!"}),r.jsx("div",{className:`text-amber-200/80 ${d}`,children:"Start driving!"}),g?r.jsxs("div",{className:`text-red-100/90 ${h}`,children:["Idle skip in ",o,"s"]}):null]})})}const bj={leftWheel:"Left wheel",rightWheel:"Right wheel",mainBrush:"Main brush",sideBrush:"Side brush",limiter:"Overcurrent limit"};function Sj({motors:a,fill:l=0,compact:i=!1}){if(!a?.length)return null;const o=a.map(y=>bj[y]||y),f=i?"w-[12rem] h-[3.5rem]":"w-[20rem] h-[7rem]",d=i?"px-2 py-1":"px-4 py-2",h=i?"text-lg":"text-4xl",p=i?"text-xs":"text-xl",g=Math.max(0,Math.min(1,l)),v=`${Math.round(g*100)}%`;return r.jsx("div",{className:`pointer-events-none absolute flex items-center justify-center bg-red-900/50 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 ${f}`,children:r.jsxs("div",{className:"relative h-full w-full",children:[r.jsx("div",{className:"absolute inset-0 overflow-hidden",children:r.jsx("div",{className:"h-full bg-red-700/60",style:{width:v}})}),r.jsxs("div",{className:`relative z-10 flex h-full w-full flex-col items-center justify-center text-center font-semibold text-white animate-pulse ${h} ${d}`,children:[r.jsx("div",{children:"OVERCURRENT"}),r.jsx("div",{className:`mt-0 font-medium text-white ${p}`,children:o.join(", ")})]})]})})}function Aj({battery:a,compact:l=!1}){if(!a?.available||!a.warnActive&&!a.urgentActive)return null;const i=a.urgentActive?"BATTERY VERY LOW, DOCK THE ROVER AND CHARGE IMMEDIATELY!!":"Battery low! please dock and charge the rover soon.",o=l?"p-2 top-6":"p-4 top-10",f=l?"text-sm":"text-2xl";return r.jsx("div",{className:`pointer-events-none absolute flex items-center justify-center bg-amber-900/60 left-1/2 -translate-x-1/2 ${o}`,children:r.jsx("div",{className:`text-center font-semibold text-white animate-pulse ${f}`,children:r.jsx("div",{children:i})})})}function jj({compact:a=!1}){const{session:l}=Ve(),{sendMessage:i,onInputFocus:o,onInputBlur:f,blurChat:d,registerInputRef:h,setTypingActive:p}=Uf(),{value:g}=st("tts",{engine:"flite",voice:"rms",pitch:50}),[v,y]=m.useState(""),[b,j]=m.useState(!1),M=l?.role!=="spectator",w=l?.role==="spectator",C=l?.assignment?.roverId||null,E=!!m.useMemo(()=>l?.roster?.find(_=>String(_.id)===String(C))||null,[C,l?.roster])?.audio?.ttsEnabled,S=m.useMemo(()=>{if(!E)return null;const _=g?.engine==="espeak"?"espeak":"flite";if(_==="espeak"){let G=Number.isFinite(g?.pitch)?Math.round(g.pitch):void 0;return typeof G=="number"&&(G=Math.max(0,Math.min(99,G))),{speak:!0,engine:_,pitch:G}}const V=typeof g?.voice=="string"?g.voice:void 0;return{speak:!0,engine:_,voice:V}},[g?.engine,g?.pitch,g?.voice,E]),T=a?"pointer-events-auto absolute bottom-0.5 right-0.5 flex w-[9rem] max-w-[70vw] items-center gap-0.5 rounded bg-black/70 px-0.4 py-0.2":"pointer-events-auto absolute bottom-1 right-1 flex w-[12rem] max-w-[70vw] items-center gap-0.5 rounded bg-black/70 px-0.5 py-0.25",B=a?"min-w-0 flex-1 bg-transparent text-[0.55rem] text-slate-100 placeholder:text-slate-400 focus:outline-none":"min-w-0 flex-1 bg-transparent text-[0.7rem] text-slate-100 placeholder:text-slate-400 focus:outline-none",L=a?"rounded bg-cyan-500/80 px-0.35 py-0.2 text-[0.55rem] font-semibold text-black disabled:opacity-50":"rounded bg-cyan-500/80 px-0.5 py-0.25 text-[0.7rem] font-semibold text-black disabled:opacity-50";async function $(_){if(_.preventDefault(),!M)return;const V=v.trim();if(V){j(!0);try{await i(V,S),y(""),d(),p(!1)}catch(G){alert(G.message)}finally{j(!1)}}}return w?null:r.jsxs("form",{onSubmit:$,className:T,children:[r.jsx("input",{className:B,value:v,onChange:_=>{const V=_.target.value;y(V),p(!!V.trim())},onFocus:_=>{o(_),p(!!v.trim())},onBlur:_=>{f(_),p(!1)},onKeyDown:_=>{_.key==="Enter"&&!v.trim()&&(_.preventDefault(),d(),p(!1))},ref:_=>h(_,{target:"hud"}),placeholder:M?"Chat (TTS)":"Spectator",disabled:!M}),r.jsx("button",{type:"submit",disabled:!M||b,className:L,children:"Speak"})]})}function Kf({layoutFormat:a="desktop"}){const{session:l}=Ve(),{state:{song:i,lastControlIntentAt:o},overcurrentLimiter:f}=Dt(),[d,h]=m.useState(()=>Date.now()),[p,g]=m.useState(!1),[v,y]=m.useState(null),b=m.useRef({active:!1,roverId:null});m.useEffect(()=>{if(l?.mode!=="turns")return;const le=setInterval(()=>h(Date.now()),250);return()=>clearInterval(le)},[l?.mode]);const j=l?.assignment?.roverId,w=!!(j&&l?.roster?l.roster.find(le=>String(le.id)===String(j)):null)?.media?.audioPublishUrl,C=j?l?.turnQueues?.[j]:null,N=l?.socketId||null,E=j?l?.activeDrivers?.[j]:null,S=!!(N&&E===N),T=m.useMemo(()=>{const le=C?.queue||[];if(!le.length||!C?.current||le.length<=1)return null;const se=le.findIndex(I=>I===C.current);return se===-1?le[0]||null:le[(se+1)%le.length]||null},[C?.queue,C?.current]),B=!!(N&&T===N),L=C?.deadline||null,$=C?.idleDeadline||null,_=L?L-d:null,V=$?$-d:null,G=l?.mode==="turns"&&B&&_!=null&&_<=5e3&&_>0,Z=l?.mode!=="turns"||S||G,F=_!=null&&Number.isFinite(_)?Math.max(0,Math.ceil(_/1e3)):null,ae=V!=null&&Number.isFinite(V)?Math.max(0,Math.ceil(V/1e3)):null,ce=S?F!=null?`${F}s left`:null:B&&F!=null?`Your turn in ${F}s`:null,he=j?[...Z?[{type:"rover",id:j,key:j}]:[],...w?[{type:"rover",id:`${j}-audio`,key:`${j}-audio`}]:[]]:[],X=ri(he),k=j&&Z?X[j]:null,Q=j&&w?X[`${j}-audio`]:null,P=Gf(j?[j]:[],{enabled:!!j,version:l?.mode}),oe=j&&P[j]||null,ue=es(j),q=j&&l?.roster?l.roster.find(le=>String(le.id)===String(j)):null,U=q?.battery??null,te=q?.name||(j?`Rover ${j}`:"");return m.useEffect(()=>{if(l?.mode!=="turns"){g(!1),y(null),b.current={active:!1,roverId:null};return}const le=b.current,se=S&&!le.active,I=S&&j&&j!==le.roverId;se||I?(g(!0),y(Date.now())):!S&&le.active&&(g(!1),y(null)),b.current={active:S,roverId:j}},[S,j,l?.mode]),m.useEffect(()=>{!p||!v||o>v&&g(!1)},[o,v,p]),r.jsx("section",{className:"panel",children:j?r.jsx(ci,{sessionInfo:k,videoMode:Z?"whep":"snapshot",snapshotFeed:oe,audioSessionInfo:Q,label:te,telemetryFrame:ue,batteryConfig:U,layoutFormat:a,overcurrentLimiter:f,songNote:i?.note,qualityNotice:Z?null:"Preview feed (low FPS) until your turn.",showTurnCue:p,turnTimerText:ce,turnSeconds:F,isActiveDriver:S,idleSkipSeconds:ae}):r.jsxs("div",{className:"panel-muted content-center text-center text-sm text-slate-400 aspect-[4/3]",children:[r.jsx("p",{children:"You are not assigned to a rover."}),r.jsx("p",{className:"mt-0",children:r.jsx("a",{href:"/spectate",className:"text-blue-400 underline hover:text-blue-500",children:"Click here to visit the spectator page."})})]})})}function bf({label:a,tone:l="muted"}){const i=l==="success"?"bg-emerald-900 text-emerald-100":l==="warn"?"bg-amber-900 text-amber-100":"bg-slate-800 text-slate-200";return r.jsx("span",{className:`rounded px-1 py-0.5 text-xs font-semibold leading-none ${i}`,children:a})}function ro(a){if(!Number.isFinite(a))return 0;const l=a%360;return l<0?l+360:l}function Nj(a){if(!Array.isArray(a)||a.length<3)return 0;const[l,i,o]=a,f=Math.max(0,Math.min(255,Number(l)))/255,d=Math.max(0,Math.min(255,Number(i)))/255,h=Math.max(0,Math.min(255,Number(o)))/255,p=Math.max(f,d,h),g=Math.min(f,d,h),v=p-g;if(v===0)return 0;let y=0;return p===f?y=(d-h)/v%6:p===d?y=(h-f)/v+2:y=(f-d)/v+4,ro(Math.round(y*60))}function Op(a){const l=ro(a),i=1,o=1-Math.abs(l/60%2-1);let f=0,d=0,h=0;return l<60?(f=i,d=o):l<120?(f=o,d=i):l<180?(d=i,h=o):l<240?(d=o,h=i):l<300?(f=o,h=i):(f=i,h=o),[Math.round(f*255),Math.round(d*255),Math.round(h*255)]}function Bp(a){return a?Array.isArray(a.hsColor)?ro(Number(a.hsColor[0])):Array.isArray(a.rgbColor)?Nj(a.rgbColor):0:0}function wj({entity:a,connected:l,onToggle:i,onSetColor:o}){const f=a.state==="unavailable"||!a.available,d=a.state==="on",h=a.type==="light"&&a.supportsColor,p=f?"warn":d?"success":"muted",g=f?"Unavailable":d?"On":"Off",v=!l||f,y=v||!h,[b,j]=m.useState(()=>Bp(a)),M=m.useRef(b),w=m.useRef(!1),C=f?"border-slate-800 bg-slate-900 text-slate-400 cursor-not-allowed":d?"border-emerald-700 bg-emerald-900/80 text-emerald-50 hover:bg-emerald-800":"border-rose-800 bg-rose-900/80 text-rose-50 hover:bg-rose-800";m.useEffect(()=>{if(!h||w.current)return;const L=Bp(a);M.current=L,j(L)},[a.rgbColor,a.hsColor,h]);const N=L=>{const $=ro(Number(L.target.value));M.current=$,j($)},E=()=>{y||!o||o(a.id,Op(M.current))},S=L=>{L.stopPropagation()},B=`rgb(${(h?Op(M.current):[255,255,255]).join(",")})`;return r.jsxs("button",{type:"button",onClick:()=>i(a.id),disabled:v,className:`relative flex min-w-[12rem] flex-[1_1_12rem] items-start justify-between gap-0.5 rounded px-1 py-0.5 text-left transition-colors ${C} disabled:opacity-60 disabled:hover:bg-inherit`,children:[r.jsxs("div",{className:"min-w-0 flex-1",children:[r.jsxs("div",{className:"flex items-center gap-0.5 text-sm leading-normal",children:[r.jsx("span",{className:"truncate font-semibold text-white",children:a.name||a.id}),h&&r.jsxs(r.Fragment,{children:[r.jsx(bf,{label:g,tone:p}),!l&&r.jsx("span",{className:"text-xs text-amber-200",children:"Offline"})]})]}),h?r.jsx("div",{className:"-mt-0.5 w-full",onClick:S,onPointerDown:L=>{S(L),w.current=!0},onPointerUp:L=>{S(L),w.current=!1,E()},onPointerCancel:L=>{S(L),w.current=!1,E()},onKeyUp:L=>{S(L),E()},onBlur:E,children:r.jsx("div",{className:"relative w-full",children:r.jsx("input",{type:"range",min:"0",max:"360",step:"1",value:b,onChange:N,disabled:y,"aria-label":`Set color for ${a.name||a.id}`,className:"ha-hue-slider w-full"})})}):r.jsxs("div",{className:"flex items-center gap-0.5 text-xs text-slate-400",children:[r.jsx(bf,{label:g,tone:p}),!l&&r.jsx("span",{className:"text-amber-200",children:" · Offline"})]})]}),h&&r.jsx("span",{className:"pointer-events-none absolute right-1 top-1 h-2.5 w-2.5 rounded-full border border-white/60",style:{backgroundColor:B}}),!h&&r.jsx("div",{className:"self-center text-xs font-semibold text-white/90",children:d?"Turn off":"Turn on"})]})}function ig(){const{state:{keymap:a}}=Dt(),{session:l,homeAssistantToggle:i,homeAssistantSetLightColor:o}=Ve(),f=l?.homeAssistant,d=m.useMemo(()=>f?.entities||[],[f?.entities]),h=An(a?.homeAssistantOn?.[0]),p=An(a?.homeAssistantOff?.[0]);if(!f?.enabled)return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm text-slate-400",children:[r.jsx("p",{className:"text-slate-300",children:"Room Controls"}),r.jsx("p",{className:"text-slate-500",children:"Not configured on the server."})]});if(d.length===0)return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm text-slate-400",children:[r.jsx("p",{className:"text-slate-300",children:"Room Controls"}),r.jsx("p",{className:"text-slate-500",children:"No lights or switches configured."})]});const g=!!f?.connected;return r.jsxs("section",{className:"panel-section space-y-0.5 text-base",children:[r.jsxs("header",{className:"flex items-center justify-between gap-0.5 text-sm text-slate-400",children:[r.jsxs("div",{className:"flex items-center gap-0.5",children:[r.jsx("p",{children:"Room Controls"}),r.jsx("span",{className:"text-xs text-slate-500",children:d.length}),r.jsxs("div",{className:"flex items-center gap-0.5 text-xs text-slate-300 background-black",children:[r.jsxs("span",{className:"flex items-center gap-0.5",children:[r.jsx("span",{children:"On"}),h?r.jsx(Lp,{label:h}):null]}),r.jsxs("span",{className:"flex items-center gap-0.5",children:[r.jsx("span",{children:"Off"}),p?r.jsx(Lp,{label:p}):null]})]})]}),r.jsx(bf,{label:g?"Connected":"Offline",tone:g?"success":"warn"})]}),r.jsx("div",{className:"flex flex-wrap gap-0.5",children:d.map(v=>r.jsx(wj,{entity:v,connected:g,onToggle:i,onSetColor:o},v.id))})]})}function Lp({label:a}){return a?r.jsx("span",{className:"rounded border border-white/40 px-1 text-[0.7rem] text-white",children:a}):null}function rg(){const{login:a,setRole:l}=Ve(),[i,o]=m.useState(""),[f,d]=m.useState(""),[h,p]=m.useState(!1);async function g(v){v.preventDefault(),p(!0);try{await a(i,f),o(""),d("")}catch(y){alert(y.message)}finally{p(!1)}}return r.jsxs("div",{className:"panel-section space-y-0.5 text-base",children:[r.jsx("p",{className:"text-sm text-slate-400",children:"Admin login"}),r.jsxs("form",{className:"flex flex-col gap-0.5",onSubmit:g,children:[r.jsx("input",{className:"field-input",placeholder:"Username",value:i,onChange:v=>o(v.target.value)}),r.jsx("input",{className:"field-input",placeholder:"Password",type:"password",value:f,onChange:v=>d(v.target.value)}),r.jsx("button",{type:"submit",disabled:h,className:"button-dark w-full disabled:opacity-50",children:h?"Logging in…":"Login"})]})]})}function zp(...a){return a.filter(Boolean).join(" ")}function Ej(a){const l=a?.batteryState?.percentDisplay;return l==null?"--":`${l}%`}function Cj(a){return a?.batteryState?a.batteryState.urgentActive?"text-red-400":a.batteryState.warnActive?"text-amber-300":"text-emerald-300":"text-slate-400"}function qj({title:a,roster:l=[],emptyText:i="No roster data.",renderActions:o=null}){return r.jsxs("div",{className:"space-y-0.5 text-sm",children:[a&&r.jsx("p",{className:"text-sm text-slate-400",children:a}),l.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:i}):r.jsx("ul",{className:"space-y-0.5 text-sm",children:l.map(f=>r.jsxs("li",{className:zp("surface flex flex-wrap items-center justify-between gap-0.5",f.locked&&"bg-red-900/40"),children:[r.jsxs("div",{children:[r.jsx("p",{className:"text-slate-200",children:f.name}),r.jsxs("p",{className:"text-xs text-slate-500 flex flex-wrap items-center gap-0.5",children:[r.jsx("span",{children:f.locked?"locked":"free"}),f.lockReason&&r.jsx("span",{className:"rounded bg-black/30 px-1",children:f.lockReason}),r.jsxs("span",{className:zp("font-semibold",Cj(f)),children:["Battery ",Ej(f)]})]})]}),o?o(f):null]},f.id))})]})}const Mj=[{key:"open",label:"Open"},{key:"turns",label:"Turns"},{key:"admin",label:"Admin"},{key:"lockdown",label:"Lockdown"}];function kj(){const{session:a,lockRover:l,setMode:i,requestControl:o,setCommunityGoal:f,setAdminReason:d,rebootRover:h,rebootServer:p,llmControl:g,adminLogs:v,llmCommentaryStatus:y}=Ve(),b=m.useMemo(()=>a?.roster??[],[a?.roster]),[j,M]=m.useState({}),[w,C]=m.useState({}),[N,E]=m.useState(!1),[S,T]=m.useState(!1),B=a?.health||null,L=a?.communityGoal?.text||"",$=a?.communityGoal?.updatedAt||null,[_,V]=m.useState(L),G=a?.adminReason?.text||"",Z=a?.adminReason?.updatedAt||null,[F,ae]=m.useState(G),ce=a?.role==="admin"||a?.role==="lockdown"||a?.role==="lockdown-admin",he=a?.mode??"open",X=async(I,re)=>{try{await l(I,re),M(de=>({...de,[I]:re}))}catch(de){alert(de.message)}},k=async I=>{const re=I.target.value;try{await i(re)}catch(de){alert(de.message)}},Q=async I=>{try{await o(I,{force:!0})}catch(re){alert(re.message)}},P=async I=>{if(!(!I?.id||!window.confirm(`Reboot rover "${I.name||I.id}" now?`))){C(de=>({...de,[I.id]:!0}));try{await h(I.id)}catch(de){alert(de.message)}finally{C(de=>({...de,[I.id]:!1}))}}},oe=async()=>{if(window.confirm("Reboot the server host now? This will disconnect all users.")){E(!0);try{await p()}catch(re){alert(re.message),E(!1)}}},ue=async()=>{if(window.confirm("Clear LLM commentary history now? This resets chat context, bot memory, and rover activity metrics for narration.")){T(!0);try{await g("clearHistory")}catch(re){alert(re.message)}finally{T(!1)}}},q=async()=>{try{await f(_)}catch(I){alert(I.message)}},U=async()=>{try{await f(null)}catch(I){alert(I.message)}},te=async()=>{try{await d(F)}catch(I){alert(I.message)}},le=async()=>{try{await d(null)}catch(I){alert(I.message)}};m.useEffect(()=>{V(L)},[L]),m.useEffect(()=>{ae(G)},[G]);const se=m.useMemo(()=>{const I={};return b.forEach(re=>{I[re.id]=j[re.id]??re.locked}),I},[b,j]);return ce?r.jsxs("section",{className:"panel-section space-y-0.5 text-base",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5 text-sm",children:[r.jsx("span",{children:"Admin controls"}),r.jsx("select",{value:he,onChange:k,className:"field-input text-sm",children:Mj.map(I=>r.jsx("option",{value:I.key,children:I.label},I.key))})]}),r.jsx("div",{className:"flex gap-0.5 text-xs",children:r.jsx("button",{type:"button",onClick:oe,disabled:N,className:"button-danger disabled:cursor-not-allowed disabled:opacity-60",children:N?"Server rebooting...":"Reboot Server"})}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Community goal"}),$?r.jsxs("span",{children:["Updated ",new Date($).toLocaleString()]}):null]}),r.jsx("input",{type:"text",value:_,onChange:I=>V(I.target.value),placeholder:"Set a community goal",className:"field-input text-sm"}),r.jsxs("div",{className:"flex gap-0.5 text-xs",children:[r.jsx("button",{type:"button",onClick:q,className:"button-dark",children:"Set goal"}),r.jsx("button",{type:"button",onClick:U,className:"button-danger",children:"Clear"})]})]}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Admin mode reason"}),Z?r.jsxs("span",{children:["Updated ",new Date(Z).toLocaleString()]}):null]}),r.jsx("textarea",{value:F,onChange:I=>ae(I.target.value),placeholder:"Set an admin mode reason",className:"field-input text-sm min-h-[3.5rem]"}),r.jsxs("div",{className:"flex gap-0.5 text-xs",children:[r.jsx("button",{type:"button",onClick:te,className:"button-dark",children:"Set reason"}),r.jsx("button",{type:"button",onClick:le,className:"button-danger",children:"Clear"})]})]}),r.jsx(qj,{roster:b,renderActions:I=>r.jsxs("div",{className:"flex flex-wrap gap-0.5 text-xs",children:[r.jsx("button",{type:"button",onClick:()=>X(I.id,!se[I.id]),className:"button-dark",children:se[I.id]?"Unlock":"Lock"}),r.jsx("button",{type:"button",onClick:()=>Q(I.id),className:"button-dark",children:"Force"}),r.jsx("button",{type:"button",onClick:()=>P(I),disabled:!!w[I.id],className:"button-danger disabled:cursor-not-allowed disabled:opacity-60",children:w[I.id]?"Rebooting...":"Reboot"})]})}),r.jsx(_j,{health:B}),r.jsx(Rj,{status:y,onClearHistory:ue,clearingHistory:S}),r.jsx(Oj,{entries:v})]}):null}function Rj({status:a,onClearHistory:l,clearingHistory:i}){if(!a)return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("div",{className:"panel-muted text-xs uppercase",children:"LLM Commentary"}),r.jsx("div",{className:"surface text-xs text-slate-300",children:"No status received yet."})]});const o=a.lastTickAt?new Date(a.lastTickAt).toLocaleString():"never",f=a.nextRunAt?new Date(a.nextRunAt).toLocaleString():"n/a",d=a.lastPostedAt?new Date(a.lastPostedAt).toLocaleString():"never",h=a.lastSnapshotSummary||{},p=a.lastOutcome==="failed"?"text-red-300":a.lastOutcome==="posted"?"text-emerald-300":"text-slate-300",g=Tj(a),v=Dj(a);return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("div",{className:"panel-muted text-xs uppercase",children:"LLM Commentary"}),r.jsx("div",{className:"flex gap-0.5 text-xs",children:r.jsx("button",{type:"button",onClick:l,disabled:!!i,className:"button-danger disabled:cursor-not-allowed disabled:opacity-60",children:i?"Clearing...":"Clear LLM History"})}),r.jsxs("div",{className:`surface border text-center ${g.className}`,children:[r.jsx("div",{className:"text-[1.1rem] font-bold tracking-wide",children:g.label}),r.jsx("div",{className:"text-xs text-slate-200",children:g.detail})]}),r.jsxs("div",{className:"surface space-y-0.5 text-xs text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Enabled"}),r.jsx("span",{children:a.enabled?"yes":"no"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Running"}),r.jsx("span",{children:a.running?"yes":"no"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"In flight"}),r.jsx("span",{children:a.inFlight?"yes":"no"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Model"}),r.jsx("span",{className:"text-slate-300",children:a.model||"--"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Server"}),r.jsx("span",{className:"text-slate-300",children:a.ollamaUrl||"--"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Frequency"}),r.jsxs("span",{children:[a.frequencyMs," ms"]})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Tick count"}),r.jsx("span",{children:a.tickCount??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Last outcome"}),r.jsx("span",{className:p,children:a.lastOutcome||"--"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Last reason"}),r.jsx("span",{className:"text-slate-300",children:a.lastReason||"--"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Last tick"}),r.jsx("span",{className:"text-slate-300",children:o})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Next run"}),r.jsx("span",{className:"text-slate-300",children:f})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Last posted"}),r.jsx("span",{className:"text-slate-300",children:d})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Prompt chars"}),r.jsx("span",{children:a.lastPromptChars??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Cleared count"}),r.jsx("span",{children:a.clearCount??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Last cleared"}),r.jsx("span",{className:"text-slate-300",children:a.lastClearedAt?new Date(a.lastClearedAt).toLocaleString():"never"})]})]}),r.jsxs("div",{className:"surface space-y-0.5 text-xs text-slate-300",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Snapshot active drivers"}),r.jsx("span",{children:h.activeDrivers??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Snapshot rovers"}),r.jsx("span",{children:h.rovers??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Snapshot chat msgs"}),r.jsx("span",{children:h.chatMessages??0})]})]}),a.lastError?r.jsxs("div",{className:"surface text-xs text-red-300 break-words",children:["Error: ",a.lastError]}):null,a.lastGeneratedText?r.jsxs("div",{className:"surface text-xs text-slate-200 break-words",children:["Generated: ",a.lastGeneratedText]}):null,a.lastPostedText?r.jsxs("div",{className:"surface text-xs text-emerald-200 break-words",children:["Posted: ",a.lastPostedText]}):null,r.jsxs("details",{className:"surface text-xs text-slate-200",children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Most recent system prompt"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:a.lastSystemPrompt||"No prompt read yet."})]}),r.jsxs("details",{className:"surface text-xs text-slate-200",children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Most recent info snapshot"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:a.lastInfoSnapshot?JSON.stringify(a.lastInfoSnapshot,null,2):"No snapshot captured yet."})]}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("div",{className:"panel-muted text-xs uppercase",children:"Most recent LLM conversation"}),r.jsx("div",{className:"surface max-h-72 space-y-0.5 overflow-y-auto",children:v.length?v.map(y=>r.jsx(Bf,{message:y.message},y.id)):r.jsx("div",{className:"text-xs text-slate-300",children:"No model conversation captured yet."})})]})]})}function Tj(a){return a?.inFlight?{label:"IN FLIGHT",detail:"Generating commentary now",className:"border-amber-400/60 bg-amber-700/20 text-amber-200"}:a?.lastOutcome==="posted"?{label:"POSTED",detail:a?.lastPostedText?`Last: ${a.lastPostedText}`:"Commentary posted",className:"border-emerald-400/60 bg-emerald-700/20 text-emerald-200"}:a?.lastOutcome==="skipped"?{label:"SKIPPED",detail:a?.lastReason||"Model chose to skip",className:"border-slate-400/60 bg-slate-700/30 text-slate-200"}:a?.lastOutcome==="failed"?{label:"FAILED",detail:a?.lastError||a?.lastReason||"Tick failed",className:"border-red-400/60 bg-red-700/20 text-red-200"}:{label:a?.running?"IDLE":"STOPPED",detail:a?.lastReason||"Waiting for next tick",className:"border-sky-400/50 bg-sky-700/20 text-sky-200"}}function Dj(a){const l=Date.now(),o=(Array.isArray(a?.lastModelMessages)?a.lastModelMessages:[]).map((f,d)=>{const h=String(f?.role||"").toLowerCase(),p=typeof f?.content=="string"?f.content:JSON.stringify(f?.content??null,null,2),g=h==="system"?"LLM System":h==="assistant"?"LLM Context":h==="user"?"LLM Input":"LLM Message";return{id:`llm-msg-${d}`,message:{ts:l+d,nickname:g,text:p,role:"spectator",system:h==="system"}}});if(a?.lastModelRawOutput!=null){const f=String(a.lastModelRawOutput);o.push({id:"llm-output",message:{ts:l+o.length+1,nickname:"LLM Output",text:f.trim()?f:"",role:"spectator",system:!0}})}return o}function _j({health:a}){if(!a)return null;const l=a.replay||{sources:[],readyCount:0,totalCount:0},i=a.snapshots||{rovers:[],rooms:[]},o=`${l.readyCount}/${l.totalCount} sources ready`,f=i.rovers.filter(h=>h.stale).length,d=i.rooms.filter(h=>h.stale).length;return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("div",{className:"panel-muted text-xs uppercase",children:"Health"}),r.jsxs("div",{className:"surface space-y-0.5 text-xs text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Replay segments"}),r.jsx("span",{className:"text-slate-400",children:o})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Rover snapshots"}),r.jsxs("span",{className:f?"text-amber-300":"text-emerald-300",children:[i.rovers.length-f,"/",i.rovers.length," ok"]})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Room cameras"}),r.jsxs("span",{className:d?"text-amber-300":"text-emerald-300",children:[i.rooms.length-d,"/",i.rooms.length," ok"]})]})]}),r.jsx("div",{className:"space-y-0.5 text-xs text-slate-300",children:l.sources.map(h=>r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:h.label}),r.jsxs("span",{className:h.ready?"text-emerald-300":"text-amber-300",children:[h.recentCount,"/",h.neededCount]})]},`${h.type}:${h.id}`))}),r.jsx("div",{className:"space-y-0.5 text-xs text-slate-300",children:i.rovers.map(h=>r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:h.name}),r.jsx("span",{className:h.stale?"text-amber-300":"text-emerald-300",children:h.stale?"stale":"ok"})]},`rover:${h.id}`))}),r.jsx("div",{className:"space-y-0.5 text-xs text-slate-300",children:i.rooms.map(h=>r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:h.name}),r.jsx("span",{className:h.stale?"text-amber-300":"text-emerald-300",children:h.error?"error":h.stale?"stale":"ok"})]},`room:${h.id}`))})]})}function Oj({entries:a}){const l=a||[];return r.jsxs("div",{className:"panel-section space-y-0.5 text-base",children:[r.jsxs("div",{className:"flex items-center justify-between text-sm text-slate-400",children:[r.jsx("span",{children:"Admin IP log"}),r.jsx("span",{children:l.length})]}),r.jsx("div",{className:"surface h-64 overflow-y-auto font-mono text-xs",children:l.length===0?r.jsx("p",{children:"No admin log entries yet."}):l.slice().reverse().map(i=>r.jsxs("div",{className:"surface",children:[r.jsx("span",{className:"text-amber-400",children:i.ts?new Date(i.ts).toLocaleTimeString():"--"})," ",i.label&&r.jsxs("span",{className:"text-teal-400",children:["[",i.label,"]"]})," ",r.jsx("span",{className:"text-slate-200",children:i.message})," ",i.ip&&r.jsx("span",{className:"text-cyan-300",children:i.ip})," ",i.meta&&r.jsx("span",{className:"text-slate-500",children:JSON.stringify(i.meta)})]},i.id))}),r.jsx("p",{className:"text-xs text-slate-500",children:"Admin-only log stream; IPs never appear in user data."})]})}const Bj=[{id:"driveForward",label:"Drive Forward",group:"Driving"},{id:"driveBackward",label:"Drive Backward",group:"Driving"},{id:"driveLeft",label:"Turn Left",group:"Driving"},{id:"driveRight",label:"Turn Right",group:"Driving"},{id:"boostModifier",label:"Boost Modifier",group:"Driving"},{id:"slowModifier",label:"Slow Modifier",group:"Driving"},{id:"auxMainForward",label:"Main Brush Forward",group:"Aux Motors"},{id:"auxMainReverse",label:"Main Brush Reverse",group:"Aux Motors"},{id:"auxSideForward",label:"Side Brush Forward",group:"Aux Motors"},{id:"auxSideReverse",label:"Side Brush Reverse",group:"Aux Motors"},{id:"auxVacuumFast",label:"Vacuum Max",group:"Aux Motors"},{id:"auxVacuumSlow",label:"Vacuum Low",group:"Aux Motors"},{id:"auxAllForward",label:"All Aux Forward",group:"Aux Motors"},{id:"cameraUp",label:"Camera Up",group:"Camera"},{id:"cameraDown",label:"Camera Down",group:"Camera"},{id:"nightVisionToggle",label:"Toggle Night Vision",group:"Camera"},{id:"hornHonk",label:"Horn (Hold)",group:"Audio"},{id:"driveMacro",label:"Drive Macro",group:"Macros"},{id:"dockMacro",label:"Dock Macro",group:"Macros"},{id:"chatFocus",label:"Toggle Chat",group:"Chat"},{id:"songNoteUp",label:"Song Note Up",group:"Audio"},{id:"songNoteDown",label:"Song Note Down",group:"Audio"},{id:"homeAssistantOn",label:"Room Controls On (Cycle)",group:"Room Controls"},{id:"homeAssistantOff",label:"Room Controls Off (Cycle)",group:"Room Controls"}];function Lj(a){return a.reduce((l,i)=>((l[i.group]||(l[i.group]=[])).push(i),l),{})}function zj(a,l){const i=Number(a);return Number.isFinite(i)?Math.max(0,Math.min(500,i)):l}const Ff=5,ui=500,fi=1,Zf=100;function Uj(a,l){const i=Number(a);return Number.isFinite(i)?Math.max(Ff,Math.min(ui,i)):l}function Fr(a,l){const i=Number(a);return Number.isFinite(i)?Math.max(fi,Math.min(Zf,i)):l}function Hj(a){const i=(Fr(a,a)-fi)/(Zf-fi),o=ui-i*(ui-Ff);return Math.round(o)}function Vj(a){const l=Uj(a,a),i=(ui-l)/(ui-Ff),o=fi+i*(Zf-fi);return Math.round(o)}function Yj(a){const[l,i]=m.useState(null);return m.useEffect(()=>{if(!l)return;function o(f){if(f.preventDefault(),f.key==="Escape"){i(null);return}const d=xi(f.key??"");d&&(a(l,d,f),i(null))}return window.addEventListener("keydown",o,{capture:!0}),()=>window.removeEventListener("keydown",o,{capture:!0})},[l,a]),{active:l,startCapture:i,cancel:()=>i(null)}}function zr({label:a,description:l,value:i,onChange:o,min:f=0,max:d=500,step:h=5}){return r.jsxs("label",{className:"surface-muted block p-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-300",children:[r.jsx("span",{className:"font-semibold text-slate-100",children:a}),r.jsx("input",{type:"number",min:f,max:d,value:i,onChange:p=>o(p.target.value),className:"w-16 rounded border border-slate-700 bg-slate-900 px-1 py-[2px] text-right text-[0.75rem] font-mono text-slate-100"})]}),l&&r.jsx("p",{className:"text-[0.65rem] text-slate-500",children:l}),r.jsxs("div",{className:"mt-0 flex items-center gap-0.5",children:[r.jsx("input",{type:"range",min:f,max:d,step:h,value:i,onChange:p=>o(p.target.value),className:"h-2 flex-1 accent-emerald-400"}),r.jsx("span",{className:"w-12 text-right text-[0.75rem] font-mono text-slate-400",children:i})]})]})}function $j(){const{state:{keymap:a},actions:{updateKeyBinding:l,resetKeyBindings:i}}=Dt(),{value:o,save:f}=st("inputs",Sn),d={...Sn.keyboard,...o.keyboard??{}},h=m.useMemo(()=>typeof d.tiltSpeed=="number"?Fr(d.tiltSpeed,Sn.keyboard.tiltSpeed):typeof d.tiltIntervalMs=="number"?Fr(Vj(d.tiltIntervalMs),Sn.keyboard.tiltSpeed):Sn.keyboard.tiltSpeed,[d.tiltIntervalMs,d.tiltSpeed]),p=m.useMemo(()=>Lj(Bj),[]),{active:g,startCapture:v,cancel:y}=Yj((w,C)=>{l(w,C)}),b=m.useCallback(w=>a?.[w]?.[0]||ni[w]?.[0]||"",[a]),j=m.useCallback((w,C)=>{const N=Sn.keyboard,E=zj(C,N[w]);f(S=>({...S??{},keyboard:{...S?.keyboard??N,[w]:E}}))},[f]),M=m.useCallback(w=>{const C=Sn.keyboard,N=Fr(w,C.tiltSpeed),E=Hj(N);f(S=>({...S??{},keyboard:{...S?.keyboard??C,tiltSpeed:N,tiltIntervalMs:E}}))},[f]);return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsxs("div",{children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Keyboard layout"}),r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Per-browser · click to change a binding"})]}),r.jsx("button",{type:"button",onClick:()=>i(),className:"button-dark text-xs",children:"Reset defaults"})]}),r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Keyboard speeds"}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(zr,{label:"Base speed",description:"Normal driving speed",value:d.baseSpeed,onChange:w=>j("baseSpeed",w)}),r.jsx(zr,{label:"Turbo speed",description:"Used when holding the boost modifier",value:d.turboSpeed,onChange:w=>j("turboSpeed",w)}),r.jsx(zr,{label:"Precision speed",description:"Used when holding the precision modifier",value:d.precisionSpeed,onChange:w=>j("precisionSpeed",w)})]})]}),r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Camera tilt"}),r.jsx(zr,{label:"Tilt speed",description:"Higher = faster when holding tilt keys",value:h,onChange:M,min:1,max:100,step:1})]}),r.jsx("div",{className:"space-y-0.5",children:Object.entries(p).map(([w,C])=>r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.75rem] text-slate-400",children:w}),r.jsx("div",{className:"space-y-0.5",children:C.map(N=>{const E=b(N.id),S=g===N.id;return r.jsxs("div",{className:"surface-muted flex items-center justify-between text-xs",children:[r.jsxs("div",{children:[r.jsx("p",{className:"font-semibold text-slate-100",children:N.label}),r.jsx("p",{className:"text-[0.65rem] text-slate-400",children:An(E)})]}),r.jsxs("div",{className:"flex items-center gap-0.5",children:[S&&r.jsx("button",{type:"button",onClick:()=>y(),className:"button-danger text-[0.7rem]",children:"Cancel"}),r.jsx("button",{type:"button",onClick:()=>v(N.id),className:`${S?"px-0.5 py-0.5 bg-emerald-500 text-emerald-950 hover:bg-emerald-400":"button-dark"} text-[0.7rem] font-medium transition-colors`,children:S?"Press a key…":"Change"})]})]},N.id)})})]},w))})]})}const Kn=new Intl.NumberFormat(void 0,{maximumFractionDigits:2}),Gj=[{id:"drive",label:"Drive stick",kind:"axisPair",section:"Driving",invertDefaults:{invertX:!1,invertY:!0}},{id:"cameraTilt",label:"Camera tilt",kind:"axis",section:"Camera",invertDefaults:{invert:!0}},{id:"mainBrush",label:"Main brush",kind:"axis",section:"Brushes",invertDefaults:{invert:!1}},{id:"sideBrush",label:"Side brush",kind:"axis",section:"Brushes",invertDefaults:{invert:!1}},{id:"vacuum",label:"Vacuum",kind:"button",section:"Aux buttons"},{id:"allAux",label:"All aux",kind:"button",section:"Aux buttons"},{id:"mainReverse",label:"Main reverse toggle",kind:"button",section:"Brush toggles"},{id:"sideReverse",label:"Side reverse toggle",kind:"button",section:"Brush toggles"},{id:"driveMacro",label:"Drive macro",kind:"button",section:"Mode macros"},{id:"dockMacro",label:"Dock macro",kind:"button",section:"Mode macros"},{id:"nightVisionToggle",label:"Night vision toggle",kind:"button",section:"Camera"}],Up=.45,Xj=.6;function Ps({label:a,description:l,min:i,max:o,step:f,value:d,onChange:h}){return r.jsxs("label",{className:"surface-muted block p-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-300",children:[r.jsx("span",{className:"font-semibold text-slate-100",children:a}),r.jsx("span",{className:"font-mono text-slate-400",children:Kn.format(d)})]}),l&&r.jsx("p",{className:"text-[0.65rem] text-slate-500",children:l}),r.jsx("input",{type:"range",min:i,max:o,step:f,value:d,onChange:p=>h(Number(p.target.value)),className:"mt-0 w-full accent-emerald-400"})]})}function Qj(a){if(!a)return"Unassigned";if(a.kind==="axisPair"){const l=`${a.invertX?"X inv ":""}${a.invertY?"Y inv":""}`.trim();return`Axes ${a.x}/${a.y}${l?` (${l})`:""}`}if(a.kind==="axis")return`Axis ${a.index}${a.invert?" (inv)":""}`;if(a.kind==="button")return`Button ${a.index}`;if(a.kind==="buttonAxis")return`Button ${a.index} (analog)`;if(a.kind==="axisButton"){const l=a.direction<0?"<":">";return`Axis ${a.index} ${l} ${Kn.format(a.threshold??.6)}`}return"Unassigned"}function Kj(a){return a.reduce((l,i)=>((l[i.section]||(l[i.section]=[])).push(i),l),{})}function Fj(a,l){if(!a||a.length===0)return null;if(l){const i=a.find(o=>o.signature===l);if(i)return i}return a[0]}function Zj(a){return{axes:Array.from(a.axes??[]),buttons:(a.buttons??[]).map(l=>({pressed:!!l?.pressed,value:typeof l?.value=="number"?l.value:l?.pressed?1:0}))}}function Jj(a,l,i){const o=(a.axes??[]).map((d,h)=>({index:h,delta:Math.abs((d??0)-(l.axes?.[h]??0)),value:d}));if(o.sort((d,h)=>h.delta-d.delta),i.kind==="axisPair"){const d=o.filter(h=>h.delta>Up).slice(0,2);return d.length<2?null:{kind:"axisPair",x:d[0].index,y:d[1].index,...i.invertDefaults??{}}}const f=o.find(d=>d.delta>Up);return f?{kind:"axis",index:f.index,...i.invertDefaults??{}}:null}function Ij(a,l,i){const o=a.buttons??[];for(let d=0;dXj)return i.kind==="axis"?{kind:"buttonAxis",index:d}:{kind:"button",index:d}}const f=a.axes??[];for(let d=0;d.7&&p>.5)return i.kind==="axis"?{kind:"axis",index:d,...i.invertDefaults??{}}:{kind:"axisButton",index:d,direction:h>=0?1:-1,threshold:.6}}return null}function Wj(a,l,i){if(!a)return null;if(i.kind==="axis"||i.kind==="axisPair"){const o=Jj(a,l,i);if(o)return o}return Ij(a,l,i)}function Pj(){const a=qA(),{value:l,save:i}=st("gamepad",ja),[o,f]=m.useState(null),d=m.useRef(null),h=m.useMemo(()=>Kj(Gj),[]),p=m.useMemo(()=>Fj(a.pads,l.activeSignature),[a.pads,l.activeSignature]),g=p?.signature??null,v=m.useMemo(()=>g?l?.profiles?.[g]??l?.defaults?.profile??Fn:l?.defaults?.profile??Fn,[g,l?.defaults?.profile,l?.profiles]);m.useEffect(()=>{!p||!g||l?.profiles?.[g]||i(N=>{const E=N??ja;if(E.profiles?.[g])return E;const S=E?.defaults?.profile??Fn,T=Gx(p,S);return{...E,profiles:{...E.profiles??{},[g]:T}}})},[p,g,l?.profiles,i]),m.useEffect(()=>{d.current=null},[o,g]),m.useEffect(()=>{if(!o||!p)return;if(!d.current){d.current=Zj(p);return}const N=Wj(p,d.current,o);N&&(i(E=>{const S=E??ja,T=S.profiles?.[g]??S?.defaults?.profile??Fn,B={...T,bindings:{...T.bindings??{},[o.id]:{...T.bindings?.[o.id]??{},kind:o.kind,sources:[N]}}};return{...S,profiles:{...S.profiles??{},[g]:B}}}),f(null))},[p,g,o,i]);const y=m.useCallback(N=>{i(E=>({...E??ja,activeSignature:N||null}))},[i]),b=m.useCallback(N=>{i(E=>{const S=E??ja,T=(g&&S.profiles?.[g])??S?.defaults?.profile??Fn,B={...T,calibration:{...T.calibration??{},...N}};return g?{...S,profiles:{...S.profiles??{},[g]:B}}:{...S,defaults:{...S.defaults??{},profile:B}}})},[g,i]),j=m.useCallback((N,E)=>{i(S=>{const T=S??ja,B=(g&&T.profiles?.[g])??T?.defaults?.profile??Fn,L=E(B.bindings?.[N]??{}),$={...B,bindings:{...B.bindings??{},[N]:L}};return g?{...T,profiles:{...T.profiles??{},[g]:$}}:{...T,defaults:{...T.defaults??{},profile:$}}})},[g,i]),M=m.useCallback(N=>{j(N.id,E=>({...E,sources:[]}))},[j]),w=m.useCallback((N,E)=>{j(N.id,S=>{const T=Array.isArray(S.sources)?[...S.sources]:[];if(!T[0])return S;const B={...T[0]};return E==="invertX"?B.invertX=!B.invertX:E==="invertY"?B.invertY=!B.invertY:B.invert=!B.invert,T[0]=B,{...S,sources:T}})},[j]),C=m.useMemo(()=>p?{outputs:Xx(p,v)}:null,[p,v]);return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("div",{className:"flex items-center justify-between",children:r.jsxs("div",{children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Controller"}),r.jsx("p",{className:"text-[0.65rem] text-slate-500",children:p?"Move sticks or press buttons to bind":"Connect a controller to configure."}),o&&r.jsxs("p",{className:"mt-0 text-[0.7rem] text-emerald-400",children:["Capturing ",o.label,"…"]})]})}),r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Connected controller"}),a.pads.length===0?r.jsx("p",{className:"text-[0.7rem] text-slate-400",children:"No controller detected."}):r.jsxs("div",{className:"flex items-center justify-between gap-0.5 text-xs",children:[r.jsx("select",{value:g??"",onChange:N=>y(N.target.value),className:"flex-1 rounded border border-slate-700 bg-slate-900 px-1 py-[2px] text-[0.75rem] text-slate-100",children:a.pads.map(N=>r.jsx("option",{value:N.signature,children:N.id||"Unknown controller"},N.signature))}),r.jsx("span",{className:"text-[0.7rem] text-slate-400",children:p?.mapping??"unknown"})]})]}),r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Calibration"}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(Ps,{label:"Drive deadzone",description:"Ignore small drive stick drift",min:0,max:.6,step:.01,value:v.calibration?.driveDeadzone??.18,onChange:N=>b({driveDeadzone:N})}),r.jsx(Ps,{label:"Camera deadzone",description:"Ignore small camera tilt drift",min:0,max:.4,step:.01,value:v.calibration?.cameraDeadzone??.08,onChange:N=>b({cameraDeadzone:N})}),r.jsx(Ps,{label:"Aux deadzone",description:"Ignore small trigger noise",min:0,max:.4,step:.01,value:v.calibration?.auxDeadzone??.05,onChange:N=>b({auxDeadzone:N})}),r.jsx(Ps,{label:"Side brush scale",description:"Scale side brush output",min:.3,max:1,step:.05,value:v.calibration?.auxSideScale??.55,onChange:N=>b({auxSideScale:N})}),r.jsxs("label",{className:"surface-muted block p-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-300",children:[r.jsx("span",{className:"font-semibold text-slate-100",children:"Camera mode"}),r.jsxs("select",{value:v.calibration?.cameraMode??"absolute",onChange:N=>b({cameraMode:N.target.value}),className:"rounded border border-slate-700 bg-slate-900 px-1 py-[2px] text-[0.75rem] text-slate-100",children:[r.jsx("option",{value:"absolute",children:"Absolute"}),r.jsx("option",{value:"velocity",children:"Velocity"})]})]}),r.jsx("p",{className:"text-[0.65rem] text-slate-500",children:"Absolute maps stick to angle; velocity moves over time."})]}),r.jsx(Ps,{label:"Camera sensitivity",description:"Velocity mode degrees per second",min:10,max:180,step:5,value:v.calibration?.cameraSensitivity??60,onChange:N=>b({cameraSensitivity:N})})]})]}),r.jsx("div",{className:"space-y-0.5",children:Object.entries(h).map(([N,E])=>r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:N}),r.jsx("div",{className:"space-y-0.5",children:E.map(S=>{const B=v.bindings?.[S.id]?.sources?.[0]??null;return r.jsxs("div",{className:"surface-muted flex items-center justify-between text-xs",children:[r.jsxs("div",{children:[r.jsx("p",{className:"font-semibold text-slate-100",children:S.label}),r.jsx("p",{className:"text-[0.65rem] text-slate-400",children:Qj(B)})]}),r.jsxs("div",{className:"flex items-center gap-0.5",children:[S.kind==="axisPair"&&r.jsxs(r.Fragment,{children:[r.jsx("button",{type:"button",disabled:!B,onClick:()=>w(S,"invertX"),className:"button-dark text-[0.7rem] font-medium disabled:opacity-50",children:"Invert X"}),r.jsx("button",{type:"button",disabled:!B,onClick:()=>w(S,"invertY"),className:"button-dark text-[0.7rem] font-medium disabled:opacity-50",children:"Invert Y"})]}),S.kind==="axis"&&r.jsx("button",{type:"button",disabled:!B,onClick:()=>w(S),className:"button-dark text-[0.7rem] font-medium disabled:opacity-50",children:"Invert"}),r.jsx("button",{type:"button",onClick:()=>M(S),className:"button-dark text-[0.7rem]",children:"Clear"}),r.jsx("button",{type:"button",onClick:()=>f(S),className:`${o?.id===S.id?"px-0.5 py-0.5 bg-emerald-500 text-emerald-950 hover:bg-emerald-400":"button-dark"} text-[0.7rem] font-medium`,children:o?.id===S.id?"Waiting…":"Capture"})]})]},S.id)})})]},N))}),r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Diagnostics"}),p?r.jsxs("div",{className:"space-y-0.5 text-[0.7rem] text-slate-300",children:[r.jsx("p",{className:"text-slate-400",children:"Raw axes"}),r.jsx("div",{className:"grid grid-cols-2 gap-0.5",children:p.axes.map((N,E)=>r.jsxs("span",{className:"font-mono text-slate-300",children:["A",E,": ",Kn.format(N)]},`axis-${E}`))}),r.jsx("p",{className:"text-slate-400",children:"Raw buttons"}),r.jsx("div",{className:"grid grid-cols-2 gap-0.5",children:p.buttons.map((N,E)=>r.jsxs("span",{className:"font-mono text-slate-300",children:["B",E,": ",Kn.format(N.value)," ",N.pressed?"●":""]},`btn-${E}`))}),C?.outputs&&r.jsxs(r.Fragment,{children:[r.jsx("p",{className:"text-slate-400",children:"Mapped outputs"}),r.jsxs("div",{className:"grid grid-cols-2 gap-0.5",children:[r.jsxs("span",{className:"font-mono text-slate-300",children:["Drive: ",Kn.format(C.outputs.driveVector.x),","," ",Kn.format(C.outputs.driveVector.y)]}),r.jsxs("span",{className:"font-mono text-slate-300",children:["Camera: ",Kn.format(C.outputs.cameraAxis)]}),r.jsxs("span",{className:"font-mono text-slate-300",children:["Main: ",Kn.format(C.outputs.auxAxis.main)]}),r.jsxs("span",{className:"font-mono text-slate-300",children:["Side: ",Kn.format(C.outputs.auxAxis.side)]})]})]})]}):r.jsx("p",{className:"text-[0.7rem] text-slate-400",children:"No controller detected."})]})]})}const eN={drive:"Drive wheels",aux:"Aux motors"};function Hp(a){return Number.isFinite(a)?`${Math.round(a*100)}%`:"--"}function tN({value:a,color:l="bg-emerald-500"}){const i=`${Math.round(Math.max(0,Math.min(1,a))*100)}%`;return r.jsx("div",{className:"h-2 w-full overflow-hidden rounded bg-slate-800",children:r.jsx("div",{className:`h-full ${l}`,style:{width:i}})})}function nN(){const{state:{roverId:a},overcurrentLimiter:l}=Dt(),i=m.useMemo(()=>Ql.map(o=>o.key),[]);return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Overcurrent limiter"}),r.jsx("span",{children:l?.adminImmune?"Admin immune":"Active"})]}),a?r.jsxs("div",{className:"space-y-0.5",children:[i.map(o=>{const f=l?.caps?.[o]?.cap??0,d=l?.overcurrent?.groups?.[o]??!1,h=l?.scales?.perGroup?.[o]??1;return r.jsxs("div",{className:"surface space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs",children:[r.jsx("span",{className:"text-slate-200",children:eN[o]||o}),r.jsx("span",{className:d?"text-red-300":"text-slate-400",children:d?"overcurrent":"ok"})]}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-[0.7rem] text-slate-400",children:[r.jsx("span",{children:"Cap"}),r.jsx("span",{children:Hp(f)})]}),r.jsx(tN,{value:f,color:"bg-amber-500"}),r.jsxs("div",{className:"flex items-center justify-between text-[0.7rem] text-slate-400",children:[r.jsx("span",{children:"Scale"}),r.jsx("span",{children:Hp(h)})]})]})]},o)}),r.jsxs("div",{className:"surface text-[0.7rem] text-slate-400",children:[r.jsx("div",{children:`Down rate ${l?.config?.downRatePerSec}/s · Up rate ${l?.config?.upRatePerSec}/s`}),r.jsx("div",{children:`Release delay ${l?.config?.releaseDelaySec}s`}),r.jsx("div",{children:`Output rate ${l?.config?.outputRateMs}ms`})]})]}):r.jsx("p",{className:"text-xs text-slate-500",children:"Assign a rover to view limiter status."})]})}const og=m.createContext(null);function cg(){const a=m.useContext(og);if(!a)throw new Error("Tabs components must be used within a provider.");return a}const Vp={primary:{base:"flex-1 px-0.5 py-0.5 text-sm font-medium transition-colors focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-slate-500 rounded-md",active:"bg-sky-600 text-white",inactive:"bg-zinc-900 text-slate-300 hover:bg-sky-500 hover:text-white"}},ug="primary";function Jf(...a){return a.filter(Boolean).join(" ")}function If({children:a,defaultTab:l,currentTab:i,onTabChange:o,variant:f=ug}){const[d,h]=m.useState(l??null),[p,g]=m.useState([]),v=m.useCallback(M=>M?(g(w=>w.includes(M)?w:[...w,M]),()=>g(w=>w.includes(M)?w.filter(C=>C!==M):w)):()=>{},[]),y=i??d??p[0]??null;m.useEffect(()=>{l&&i===void 0&&h(l)},[l,i]);const b=m.useCallback(M=>{M&&(i===void 0&&h(M),o&&o(M))},[i,o]),j=m.useMemo(()=>({activeTab:y,setActiveTab:b,registerTab:v,variant:f}),[y,b,v,f]);return r.jsx(og.Provider,{value:j,children:a})}function Wf({children:a,className:l=""}){return r.jsx("div",{className:Jf("flex gap-0.5",l),children:a})}function mn({id:a,children:l,className:i="",disabled:o=!1}){const{activeTab:f,setActiveTab:d,registerTab:h,variant:p}=cg();m.useEffect(()=>h(a),[a,h]);const g=Vp[p]??Vp[ug],v=f===a,y=Jf(g.base,v?g.active:g.inactive,o?"cursor-not-allowed opacity-50":"",i);return r.jsx("button",{type:"button",className:y,onClick:()=>!o&&d(a),"aria-pressed":v,"aria-disabled":o,children:l})}function Pf({children:a,className:l=""}){return r.jsx("div",{className:Jf("mt-0 space-y-0.5",l),children:a})}function hn({id:a,children:l,keepMounted:i=!1}){const{activeTab:o}=cg(),f=o===a;return!f&&!i?null:!f&&i?r.jsx("div",{hidden:!0,"aria-hidden":"true",children:l}):r.jsx(r.Fragment,{children:l})}function aN(){const{session:a}=Ve(),l=m.useMemo(()=>JSON.stringify(a??{},null,2),[a]);return r.jsxs("div",{className:"panel-section flex min-h-0 flex-col gap-0.5 text-xs",children:[r.jsx("p",{className:"text-sm text-slate-400",children:"Session snapshot"}),r.jsx("pre",{className:"surface min-h-0 flex-1 overflow-y-auto font-mono text-[0.7rem] text-lime-300",children:l})]})}const Yp=300,$p=240;function lN(a,l){if(a===void 0)return"undefined";if(a===null)return"null";if(typeof a=="string")return a;if(typeof a=="number"||typeof a=="boolean")return String(a);if(typeof a=="function")return"[Function]";if(a instanceof Error)return`${a.name}: ${a.message}`;if(a instanceof Date)return a.toISOString();if(l||(l=new WeakSet),typeof a=="object"){if(l.has(a))return"[Circular]";l.add(a)}try{return JSON.stringify(a,(i,o)=>typeof o=="function"?"[Function]":o instanceof Error?`${o.name}: ${o.message}`:o,0)}catch{return String(a)}}function sN(a){const i=a.map(o=>lN(o)).join(" ");return i.length>$p?`${i.slice(0,$p)}…`:i}function iN(){const a=En(),[l,i]=m.useState([]),o=m.useMemo(()=>l.reduce((f,d)=>(d.direction==="in"&&(f.in+=1),d.direction==="out"&&(f.out+=1),f),{in:0,out:0}),[l]);return m.useEffect(()=>{const f=(g,v,y)=>{i(b=>{const j=[...b,{id:`${Date.now()}-${Math.random().toString(16).slice(2)}`,direction:g,event:v,payload:sN(y),timestamp:new Date().toLocaleTimeString()}];return j.length>Yp?j.slice(-Yp):j})},d=(g,...v)=>f("in",g,v),h=(g,...v)=>f("out",g,v);a.onAny(d);let p=null;if(typeof a.onAnyOutgoing=="function")a.onAnyOutgoing(h),p=()=>a.offAnyOutgoing(h);else{const g=a.emit.bind(a);a.emit=(v,...y)=>(h(v,...y),g(v,...y)),p=()=>{a.emit=g}}return()=>{a.offAny(d),p&&p()}},[a]),r.jsxs("section",{className:"panel-section space-y-0.5 text-base",children:[r.jsxs("div",{className:"flex items-center justify-between text-sm text-slate-400",children:[r.jsx("span",{children:"Socket activity"}),r.jsxs("div",{className:"flex items-center gap-0.5 text-xs text-slate-400",children:[r.jsxs("span",{className:"surface-muted",children:["in ",o.in]}),r.jsxs("span",{className:"surface-muted",children:["out ",o.out]}),r.jsx("button",{type:"button",className:"button-dark text-xs",onClick:()=>i([]),children:"Clear"})]})]}),r.jsx("div",{className:"surface h-64 overflow-y-auto font-mono text-xs",children:l.length===0?r.jsx("p",{children:"No socket activity yet."}):l.slice().reverse().map(f=>r.jsxs("div",{className:"surface",children:[r.jsx("span",{className:"text-slate-400",children:f.timestamp})," ",r.jsxs("span",{className:f.direction==="in"?"text-emerald-400":"text-amber-400",children:["[",f.direction.toUpperCase(),"]"]})," ",r.jsx("span",{className:"text-cyan-300",children:f.event})," ",r.jsx("span",{className:"text-slate-200",children:f.payload})]},f.id))}),r.jsx("p",{className:"text-xs text-slate-500",children:"Logs are collected only while this panel is open."})]})}const rN=[{key:"start",label:"Start OI"},{key:"safe",label:"Safe"},{key:"full",label:"Full"},{key:"passive",label:"Passive"},{key:"dock",label:"Dock"}];function fg(){const{state:{roverId:a},actions:{sendOiCommand:l,setSensorStream:i}}=Dt(),o=!!a,[f,d]=lg(),h=En(),{value:p,save:g}=st("page",{hudMapDesktop:!1,connectionTransport:"websocket"}),{value:v,save:y}=st("audio",jt),b=p?.connectionTransport||"websocket",j=Number.isFinite(v?.masterVolume)?v.masterVolume:jt.masterVolume,M=Number.isFinite(v?.alertVolume)?v.alertVolume:jt.alertVolume,w=Number.isFinite(v?.roverVolume)?v.roverVolume:jt.roverVolume,C=typeof v?.mainBrushDuckEnabled=="boolean"?v.mainBrushDuckEnabled:typeof v?.autoLevelEnabled=="boolean"?v.autoLevelEnabled:jt.mainBrushDuckEnabled,N=Number.isFinite(v?.mainBrushDuckAmount)?Math.max(0,Math.min(1,v.mainBrushDuckAmount)):jt.mainBrushDuckAmount,E=m.useMemo(()=>[{key:"start",label:"Enable stream",enable:!0},{key:"stop",label:"Disable stream",enable:!1}],[]),S=$=>{a&&i($)},T=$=>{const _=$.target.value;g(V=>({...V??{},connectionTransport:_})),h?.io?.opts&&(h.io.opts.transports=_==="polling"?["polling"]:["websocket","polling"],h.disconnect(),h.connect())},B=$=>_=>{const V=Number(_.target.value),G=Number.isFinite(V)?Math.max(0,Math.min(1,V)):0;y(Z=>({...Z??{},[$]:G}))},L=$=>{const _=!!$.target.checked;y(V=>({...V??{},mainBrushDuckEnabled:_}))};return r.jsxs(If,{defaultTab:"keybindings",children:[r.jsxs(Wf,{children:[r.jsx(mn,{id:"keybindings",children:"Keybindings"}),r.jsx(mn,{id:"controller",children:"Controller"}),r.jsx(mn,{id:"page",children:"Page settings"}),r.jsx(mn,{id:"admin",children:"Admin"})]}),r.jsxs(Pf,{children:[r.jsx(hn,{id:"keybindings",children:r.jsx("div",{className:"space-y-0.5",children:r.jsx($j,{})})}),r.jsx(hn,{id:"controller",children:r.jsx("div",{className:"space-y-0.5",children:r.jsx(Pj,{})})}),r.jsx(hn,{id:"page",children:r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("p",{className:"text-slate-400",children:"HUD"}),r.jsxs("label",{className:"flex items-center gap-0.5 text-slate-200",children:[r.jsx("input",{type:"checkbox",className:"accent-emerald-500",checked:f,onChange:$=>d($.target.checked)}),r.jsx("span",{children:"Show top-down map in HUD (desktop)"})]}),r.jsx("p",{className:"text-xs text-slate-500",children:"Mobile HUD keeps the map on by default."})]}),r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("p",{className:"text-slate-400",children:"Audio"}),r.jsxs("label",{className:"grid gap-0.5 text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"Master volume"}),r.jsxs("span",{className:"text-xs text-slate-400",children:[Math.round(j*100),"%"]})]}),r.jsx("input",{type:"range",min:"0",max:"1",step:"0.01",value:j,onChange:B("masterVolume"),className:"w-full accent-emerald-500"})]}),r.jsxs("label",{className:"grid gap-0.5 text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"Alert/page sounds"}),r.jsxs("span",{className:"text-xs text-slate-400",children:[Math.round(M*100),"%"]})]}),r.jsx("input",{type:"range",min:"0",max:"1",step:"0.01",value:M,onChange:B("alertVolume"),className:"w-full accent-emerald-500"})]}),r.jsxs("label",{className:"grid gap-0.5 text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"Rover audio"}),r.jsxs("span",{className:"text-xs text-slate-400",children:[Math.round(w*100),"%"]})]}),r.jsx("input",{type:"range",min:"0",max:"1",step:"0.01",value:w,onChange:B("roverVolume"),className:"w-full accent-emerald-500"})]}),r.jsxs("label",{className:"flex items-center justify-between gap-0.5 text-slate-200",children:[r.jsx("span",{children:"Main brush ducking"}),r.jsx("input",{type:"checkbox",className:"accent-emerald-500",checked:C,onChange:L})]}),r.jsxs("label",{className:"grid gap-0.5 text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"Main brush duck amount"}),r.jsxs("span",{className:"text-xs text-slate-400",children:[Math.round(N*100),"%"]})]}),r.jsx("input",{type:"range",min:"0",max:"1",step:"0.01",value:N,onChange:B("mainBrushDuckAmount"),className:"w-full accent-emerald-500",disabled:!C})]}),r.jsx("p",{className:"text-xs text-slate-500",children:"Lowers rover audio only while the main brush is running."})]}),r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("p",{className:"text-slate-400",children:"Connection"}),r.jsxs("label",{className:"flex items-center justify-between gap-0.5 text-slate-200",children:[r.jsx("span",{children:"Transport"}),r.jsxs("select",{value:b,onChange:T,className:"field-input text-sm",children:[r.jsx("option",{value:"websocket",children:"WebSocket"}),r.jsx("option",{value:"polling",children:"Polling"})]})]}),r.jsx("p",{className:"text-xs text-slate-500",children:"Switching reconnects your session."})]})]})}),r.jsx(hn,{id:"admin",children:r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("p",{className:"text-slate-400",children:"Manual OI commands"}),r.jsx("div",{className:"surface flex flex-wrap gap-0.5",children:rN.map($=>r.jsx("button",{type:"button",onClick:()=>l($.key),disabled:!o,className:"button-dark text-xs disabled:opacity-30",children:$.label},$.key))})]}),r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("p",{className:"text-slate-400",children:"Sensor stream"}),r.jsx("div",{className:"surface flex gap-0.5",children:E.map($=>r.jsx("button",{type:"button",onClick:()=>S($.enable),disabled:!o,className:"flex-1 button-dark text-xs disabled:opacity-30",children:$.label},$.key))}),!o&&r.jsx("p",{className:"text-xs text-slate-500",children:"Assign a rover to toggle streams."})]}),r.jsx(rg,{}),r.jsx(nN,{}),r.jsx(kj,{}),r.jsx(aN,{}),r.jsx(iN,{})]})})]})]})}const Gp={desktop:{main:[{type:"list",title:"Chat and nicknames",items:["Set a nickname in the user list panel, on the bottom left of the page below the rover video.",{segments:["Toggle chat focus with ",{action:"chatFocus"},". Press ",{action:"chatFocus"}," again to send."]}]},{type:"list",title:"Driving the rover",items:[{segments:['Press the "Start Driving" button onscreen, or press ',{action:"driveMacro"}," on your keyboard to put the rover into driving mode."]},"Refer to the controls for the controls for the rover."]},{type:"list",title:"Docking the rover",items:["Line up the rover to the dock, about a foot away, then:",{segments:['Press the "Dock and Charge" button onscreen, or press ',{action:"dockMacro"}," on your keyboard to start docking."]},"Wait for the rover to confirm it is docked and charging before leaving it unattended."]}],aside:[{type:"keyboard",title:"Keyboard controls",footnote:"Per-browser; adjust bindings and speeds in Settings → Keybindings.",groups:[{id:"movement",title:"Movement",items:[{action:"driveForward",label:"Forward"},{action:"driveBackward",label:"Backward"},{action:"driveLeft",label:"Turn left"},{action:"driveRight",label:"Turn right"},{action:"boostModifier",label:"Boost speed"},{action:"slowModifier",label:"Precision speed"}]},{id:"macros",title:"Rover modes & chat",items:[{action:"driveMacro",label:"Drive macro"},{action:"dockMacro",label:"Dock macro"},{action:"chatFocus",label:"Chat focus"}]},{id:"camera",title:"Camera",items:[{action:"cameraUp",label:"Tilt up"},{action:"cameraDown",label:"Tilt down"},{action:"nightVisionToggle",label:"Toggle night vision"}]},{id:"motors",title:"Rover Aux Motors",items:[{action:"auxMainForward",label:"Main brush forward"},{action:"auxMainReverse",label:"Main brush reverse"},{action:"auxSideForward",label:"Side brush forward"},{action:"auxSideReverse",label:"Side brush reverse"},{action:"auxVacuumFast",label:"Vacuum max"},{action:"auxVacuumSlow",label:"Vacuum low"},{action:"auxAllForward",label:"All motors forward"}]},{id:"audio",title:"Audio / Song",items:[{action:"songNoteUp",label:"Song note up"},{action:"songNoteDown",label:"Song note down"}]},{id:"room-controls",title:"Room Controls",items:[{action:"homeAssistantOn",label:"Next room control on"},{action:"homeAssistantOff",label:"Next room control off (reverse)"}]}]},{type:"gamepad",title:"Gamepad / joystick",items:["Gamepad controls are fully configurable to handle inconsistent browser mappings.","Use Settings → Controller to bind inputs, calibrate deadzones, and view diagnostics.","If inputs behave oddly, the diagnostics panel will show raw axis/button values."]}]},"mobile-portrait":{main:[{type:"list",title:"Chat and nicknames",items:["Set a nickname in the user list panel below.","Tap in the chat box to send messages in the chat."]},{type:"list",title:"Driving the rover",items:[{segments:['Press the "Start Driving" button onscreen to put the rover into driving mode.']},"Look below the rover video. Use the joystick on the right to move the rover, and hold the buttons on the left to run the aux motors."]},{type:"list",title:"Docking the rover",items:["Line up the rover to the dock, about a foot away, then:",{segments:['Press the "Dock and Charge" button onscreen to start docking.']},"Wait for the rover to confirm it is docked and charging before leaving it unattended."]}]},"mobile-landscape":{main:[{type:"list",title:"Chat and nicknames",items:["Scroll down to see more of the page.","Set a nickname in the user list panel below.","Tap in the chat box to send messages in the chat."]},{type:"list",title:"Driving the rover",items:[{segments:['Press the "Start Driving" button, or the "Drive" button to put the rover into driving mode.']},"Use the joystick to the right of the video feed to move the rover, and hold the buttons on the left to run the aux motors."]},{type:"list",title:"Docking the rover",items:["Line up the rover to the dock, about a foot away, then:",{segments:['Press the "Dock and Charge" or the "Dock" button onscreen to start docking.']},"Wait for the rover to confirm it is docked and charging before leaving it unattended."]}]}};function oN(a){return Gp[a]||Gp.desktop}function dg({actionId:a,keymap:l}){const i=l?.[a]?.[0]??"";return r.jsx("span",{className:"rounded border border-slate-600 bg-slate-900/40 px-1 text-[0.7rem] text-slate-200",children:An(i)})}function Xp(a,l){return a.map((i,o)=>typeof i=="string"?r.jsx("span",{children:i},`text-${o}`):i?.action?r.jsx(dg,{actionId:i.action,keymap:l},`pill-${i.action}-${o}`):i?.text?r.jsx("span",{children:i.text},`span-${o}`):null)}function oo(a,l,i){if(typeof a=="string")return a;if(Array.isArray(a?.segments))return Xp(a.segments,l);if(a&&typeof a=="object"){if(typeof a.text=="string")return a.text;if(Array.isArray(a))return Xp(a,l)}return String(a??"")}function cN({hero:a,keymap:l}){return a?r.jsxs("div",{className:"surface space-y-0.5 px-0.5 py-0.5",children:[r.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-0.5",children:[r.jsxs("div",{children:[r.jsx("p",{className:"text-sm font-semibold text-white",children:a.title}),a.subtitle&&r.jsx("p",{className:"text-xs text-slate-300",children:a.subtitle})]}),a.chips&&r.jsx("div",{className:"flex flex-wrap gap-0.5 text-[0.7rem] text-slate-200",children:a.chips.map(i=>r.jsx("span",{className:"rounded border border-slate-700 px-1 py-[2px]",children:i},i))})]}),a.bullets&&r.jsx("ul",{className:"space-y-0.5 text-[0.85rem] text-slate-200",children:a.bullets.map((i,o)=>{const f=Array.isArray(i?.segments)?`hero-${o}`:`hero-${o}`;return r.jsx("li",{className:"surface-muted px-0.5 py-0.25",children:oo(i,l)},f)})})]}):null}function uN({block:a,keymap:l}){return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs font-semibold text-slate-200",children:a.title}),r.jsx("ul",{className:"space-y-0.5 text-[0.8rem] text-slate-300",children:a.items.map((i,o)=>{const f=`item-${o}`;return r.jsx("li",{className:"surface-muted flex flex-wrap items-center gap-0.5 px-0.5 py-0.25",children:oo(i,l)},f)})})]})}function fN({block:a}){const l=a.tone==="info"?"border-cyan-500/40":"border-slate-700";return r.jsxs("div",{className:`surface space-y-0.5 border ${l} px-0.5 py-0.5`,children:[r.jsx("p",{className:"text-xs font-semibold text-slate-100",children:a.title}),a.body&&r.jsx("ul",{className:"space-y-0.5 text-[0.8rem] text-slate-300",children:a.body.map((i,o)=>r.jsx("li",{className:"surface-muted px-0.5 py-0.25",children:oo(i,{})},`callout-${o}`))})]})}function dN({group:a,keymap:l}){return r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"px-0.5 py-0.25 text-[0.75rem] font-semibold text-slate-200",children:a.title}),r.jsx("div",{className:"space-y-0.5 px-0.5 pb-0.25",children:a.items.map(i=>r.jsxs("div",{className:"surface-muted flex items-center justify-between gap-0.5 px-0.5 py-0.25 text-[0.8rem]",children:[r.jsx("span",{className:"text-slate-200",children:i.label}),r.jsx(dg,{actionId:i.action,keymap:l})]},i.action))})]})}function mN({block:a,keymap:l}){return a?r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-200",children:[r.jsx("span",{className:"font-semibold",children:a.title}),a.footnote&&r.jsx("span",{className:"text-[0.7rem] text-slate-400",children:a.footnote})]}),r.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-0.5",children:a.groups?.map(i=>r.jsx(dN,{group:i,keymap:l},i.id))})]}):null}function hN({block:a}){return a?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs font-semibold text-slate-200",children:a.title}),r.jsx("ul",{className:"space-y-0.5 text-[0.8rem] text-slate-300",children:a.items?.map((l,i)=>r.jsx("li",{className:"surface-muted px-0.5 py-0.25",children:oo(l,{})},`gamepad-${i}`))})]}):null}function Qp({block:a,keymap:l}){if(!a)return null;switch(a.type){case"list":return r.jsx(uN,{block:a,keymap:l});case"callout":return r.jsx(fN,{block:a});case"keyboard":return r.jsx(mN,{block:a,keymap:l});case"gamepad":return r.jsx(hN,{block:a});default:return null}}function mg({layout:a,keymap:l}){const i=m.useMemo(()=>oN(a),[a]),o=l||{},f=i.main||[],d=i.aside||[];return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(cN,{hero:i.hero,keymap:o}),r.jsxs("div",{className:"grid gap-0.5",style:{gridTemplateColumns:"repeat(auto-fit, minmax(360px, 1fr))"},children:[r.jsx("div",{className:"space-y-0.5",children:f.map((h,p)=>r.jsx(Qp,{block:h,keymap:o},h.title||p))}),r.jsx("div",{className:"space-y-0.5",children:d.map((h,p)=>r.jsx(Qp,{block:h,keymap:o},h.title||p))})]})]})}function hg({layout:a,onOpenOverlay:l}){const{state:i}=Dt();return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Help"}),r.jsx("button",{type:"button",onClick:l,className:"button-dark px-1 py-0.25 text-[0.75rem]",children:"Open full help"})]}),r.jsx(mg,{layout:a,keymap:i?.keymap||{}})]})}const pN=["kal","rms","slt","ksp","bdl"],xN=Array.from({length:10},(a,l)=>l*10);function co({hideInput:a=!1,hideSpectatorNotice:l=!1,fillHeight:i=!1,allowSpectatorInput:o=!1}){const{session:f}=Ve(),{messages:d,typing:h,sendMessage:p,registerInputRef:g,onInputFocus:v,onInputBlur:y,blurChat:b,setTypingActive:j}=Uf(),{value:M,save:w}=st("tts",{engine:"flite",voice:"rms",pitch:50}),[C,N]=m.useState(""),[E,S]=m.useState(!1),[T,B]=m.useState(!1),[L,$]=m.useState(()=>M?.engine||"flite"),[_,V]=m.useState(()=>M?.voice||"rms"),[G,Z]=m.useState(()=>Number.isFinite(M?.pitch)?M.pitch:50),F=f?.role!=="spectator"||o,ae=m.useRef(null),ce=f?.assignment?.roverId||null,X=!!m.useMemo(()=>f?.roster?.find(q=>String(q.id)===String(ce))||null,[ce,f?.roster])?.audio?.ttsEnabled,k=m.useMemo(()=>d.slice(-200),[d]),Q=m.useMemo(()=>h||[],[h]);m.useEffect(()=>{ae.current&&(ae.current.scrollTop=ae.current.scrollHeight)},[k,Q]),m.useEffect(()=>{const q=M?.engine||"flite",U=M?.voice||"rms",te=Number.isFinite(M?.pitch)?M.pitch:50;L!==q&&$(q),_!==U&&V(U),G!==te&&Z(te)},[L,G,M?.engine,M?.pitch,M?.voice,_]),m.useEffect(()=>{B(!!X)},[X]);const P=m.useMemo(()=>!X||!T?null:L==="espeak"?{speak:!0,engine:L,pitch:G}:{speak:!0,engine:L,voice:_},[L,G,T,X,_]);async function oe(q){if(q.preventDefault(),!F||a)return;const te=C.replace(/\\n/g,` + `,document.head.appendChild(l)},[])}function cj(a,l){return typeof a=="string"&&a.trim()?a.trim().toLowerCase():typeof l=="string"&&l.trim()?l.trim().toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""):""}function Qf({id:a,label:l,url:i,className:o=""}){if(!i)return null;oj();const f=cj(a,l),d=ij[f]||_S,h=rj[f]||null,p=l||a||"Link";return r.jsxs("a",{href:i,target:"_blank",rel:"noopener noreferrer","aria-label":p,className:`inline-flex items-center w-full px-0.5 py-0.5 text-sm font-medium text-white transition justify-center gap-1 rounded-md ${h?"":"bg-slate-700 hover:bg-slate-600"} ${o}`,style:h||void 0,children:[r.jsx(d,{className:"mr-0"}),p]})}const oi=10;function sg({batteryState:a=null,charge:l=null,config:i=null}){const o=a?.percentDisplay!=null&&Number.isFinite(a.percentDisplay)?Math.round(a.percentDisplay):null,f=a?.warnActive??null,d=a?.urgentActive??null,h=i?.Full??null,p=i?.Warn??null,g=i?.Urgent??null,v=l??a?.charge??null;let y=o;if(y==null&&(y=uj({charge:v,full:h,warn:p,urgent:g})),y==null)return{available:!1};const b=Math.max(0,Math.min(1,y/100)),j=f??(p!=null&&v!=null&&v<=p),M=d??(g!=null&&v!=null&&v<=g);return{available:!0,percent:b,percentDisplay:y,warnActive:j,urgentActive:M,warnDisplayPercent:oi}}function uj({charge:a,full:l,warn:i,urgent:o}){if(a!=null&&l!=null&&i!=null&&o!=null&&l>i&&i>o){const f=oi/100,d=1-f;if(a<=o)return 0;if(a<=i){const p=(a-o)/(i-o);return Math.round(Math.max(0,Math.min(1,p))*oi)}if(a>=l)return 100;const h=(a-i)/(l-i);return Math.round((f+Math.max(0,Math.min(1,h))*d)*100)}return null}const fj=1600,dj=800;function fn(...a){return a.filter(Boolean).join(" ")}function yf({visual:a,orientation:l="horizontal",variant:i="inline",compact:o=!1,showLabel:f,className:d=""}){const h=i==="background",p=l==="vertical",g=f??!h;if(!a?.available){if(h){const ae=oi,ce=fn("absolute z-10",p?"left-0 right-0 h-[2px]":"top-0 bottom-0 w-[2px]","bg-black/70",M&&!j&&"battery-tick-warn"),he=p?{bottom:`${ae}%`}:{left:`${ae}%`},X=fn("absolute inset-0 pointer-events-none",d);return r.jsxs("div",{className:X,children:[r.jsx("div",{className:"absolute inset-0 bg-slate-900/35"}),r.jsx("div",{className:ce,style:he})]})}return p?null:r.jsx("p",{className:"text-xs text-slate-500",children:"Battery telemetry unavailable"})}const v=a.percentDisplay??0,y=Math.max(0,Math.min(100,v)),b=a.warnDisplayPercent??oi,j=!!a.urgentActive,M=!!a.warnActive,w=j?`${dj}ms`:M?`${fj}ms`:void 0,N=h?j||M?"bg-red-500/45":"bg-emerald-500/45":j||M?"bg-red-500":"bg-emerald-500",E=j||M?"bg-red-900/35":"bg-slate-900/35",S=j||M?"bg-red-900/45":"bg-slate-900/35",T=fn(h?"absolute inset-0 pointer-events-none":"relative pointer-events-auto",p&&!h?"flex flex-col items-center justify-center":"w-full",d);if(h){const ae=p?{height:`${y}%`}:{width:`${y}%`},ce=fn("absolute",p?"bottom-0 left-0 right-0":"left-0 top-0 bottom-0",N,"transition-[width,height]",j&&"battery-urgent-flash"),he=fn("absolute z-10",p?"left-0 right-0 h-[2px]":"top-0 bottom-0 w-[2px]","bg-black/70",M&&!j&&"battery-tick-warn"),X=p?{bottom:`${b}%`}:{left:`${b}%`};return r.jsxs("div",{className:T,children:[r.jsx("div",{className:fn("absolute inset-0",E,j&&"battery-urgent-flash")}),r.jsx("div",{className:ce,style:{...ae,animationDuration:w}}),r.jsx("div",{className:he,style:X})]})}const B=fn("relative overflow-hidden rounded-sm border border-slate-800",j||M?S:"bg-zinc-900/90",j&&"battery-urgent-flash",p?"w-full flex-1":o?"h-4":"h-5"),L=p?{height:`${y}%`}:{width:`${y}%`},$=fn("absolute",p?"bottom-0 left-0 right-0":"left-0 top-0 bottom-0",N,"transition-[width,height]",j&&"battery-urgent-flash"),_=fn("absolute z-10",p?"left-0 right-0 h-[2px]":"top-0 bottom-0 w-[2px]","bg-black/70",M&&!j&&"battery-tick-warn"),V=p?{bottom:`${b}%`}:{left:`${b}%`},G=`Battery ${v}%`,Z=fn("pointer-events-none absolute inset-0 flex items-center justify-center text-center font-semibold",p?o?"text-[0.55rem]":"text-[0.65rem]":"text-xs"),F=fn("rounded px-1.5 py-0.5",h?"bg-black/30 text-slate-100":"bg-black/50 text-slate-100");return r.jsxs("div",{className:T,children:[r.jsxs("div",{className:B,children:[r.jsx("div",{className:$,style:{...L,animationDuration:w}}),r.jsx("div",{className:_,style:V})]}),g&&!p?r.jsx("div",{className:Z,children:r.jsx("span",{className:F,children:G})}):null,g&&p?r.jsx("div",{className:"mt-0.5 text-[0.65rem] font-semibold text-slate-100",children:r.jsxs("span",{className:F,children:[v,"%"]})}):null]})}const _p=2e3,mj=3e3,hj=3e3,pj=40,xj=1e3;function ci({sessionInfo:a,audioSessionInfo:l,videoMode:i="whep",snapshotFeed:o=null,qualityNotice:f=null,label:d,forceMute:h=!1,telemetryFrame:p,batteryConfig:g,layoutFormat:v="desktop",hudVariant:y="default",driverLabel:b=null,hudForceMap:j=!1,hudMapPosition:M="top-center",hudLabelScale:w=1,fitParent:C=!1,overcurrentLimiter:N=null,showTurnCue:E=!1,turnTimerText:S=null,turnSeconds:T=null,isActiveDriver:B=!1,idleSkipSeconds:L=null}){const{session:$}=Ve(),_=m.useRef(null),V=m.useRef(null),G=m.useRef(null),Z=m.useRef(null),F=m.useRef(null),ae=m.useRef(null),ce=m.useRef(null),he=m.useRef(null),[X,k]=m.useState("idle"),[Q,P]=m.useState(null),[oe,ue]=m.useState("idle"),[q,U]=m.useState(null),[te,le]=m.useState(0),[se,I]=m.useState(0),[re,de]=m.useState(!0),pe=!!l?.url,Ce=i==="snapshot",ne=p?.sensors,{value:ie}=st("audio",jt),Se=Number.isFinite(ie?.masterVolume)?ie.masterVolume:jt.masterVolume,Oe=Number.isFinite(ie?.roverVolume)?ie.roverVolume:jt.roverVolume,Ne=typeof ie?.mainBrushDuckEnabled=="boolean"?ie.mainBrushDuckEnabled:typeof ie?.autoLevelEnabled=="boolean"?ie.autoLevelEnabled:jt.mainBrushDuckEnabled,Le=Number.isFinite(ie?.mainBrushDuckAmount)?Math.max(0,Math.min(1,ie.mainBrushDuckAmount)):jt.mainBrushDuckAmount,an=Math.max(0,Math.min(1,Se*Oe)),wa=ne?.batteryChargeMah??null,Wn=v==="desktop",it=!Wn,ln=it?"top-right":M,[fo]=lg(),mo=j||it?!0:fo,mt=sg({charge:wa,config:g}),Pa=ne?.wheelOvercurrents||null,el=Pa==null?[]:Object.entries(Pa).filter(([,ge])=>!!ge).map(([ge])=>ge),sn=N?.caps||null,Ea=N?.overcurrent?.groups||null,gi=typeof window<"u"&&new URLSearchParams(window.location.search).has("debugAudio"),tl=typeof window<"u"&&new URLSearchParams(window.location.search).has("debugHud"),ho=m.useMemo(()=>{if(!sn)return null;const ge=Number.isFinite(sn?.drive?.cap)?sn.drive.cap:1,Xe=Number.isFinite(sn?.aux?.cap)?sn.aux.cap:1;return Math.max(0,Math.min(1,1-Math.min(ge,Xe)))},[sn]),ts=!!N?.isActive,rn=el.length?el:ts?["limiter"]:[],pt=ho??(el.length?1:0),Yt=!!rn.length,rt=!!((Number(ne?.mainBrushCurrentMa)||0)>pj||ne?.wheelOvercurrents?.mainBrush),po=Ne&&rt?1-Le:1,nl=Math.max(0,Math.min(1,an*po)),xo=Ne&&rt&&Le>0?`Volume decreased ${Math.round(Le*1e3)/10}%`:null,Ca=m.useRef({hasDedicatedAudio:!1,audioUrl:null,mainBrushDuckEnabled:!1,mainBrushDuckAmount:0,mainBrushActive:!1,baseRoverGain:0,effectiveRoverGain:0});m.useEffect(()=>{Ca.current={hasDedicatedAudio:pe,audioUrl:l?.url||null,mainBrushDuckEnabled:Ne,mainBrushDuckAmount:Le,mainBrushActive:rt,baseRoverGain:an,effectiveRoverGain:nl}},[pe,l?.url,Ne,Le,rt,an,nl]);const Fe=m.useCallback((ge,Xe={})=>{if(!gi)return;const Qe=V.current,je=Ca.current,ze={event:ge,ts:Date.now(),roverLabel:d||null,hasDedicatedAudio:je.hasDedicatedAudio,audioUrl:je.audioUrl,mainBrushDuckEnabled:je.mainBrushDuckEnabled,mainBrushDuckAmount:je.mainBrushDuckAmount,mainBrushActive:je.mainBrushActive,baseRoverGain:je.baseRoverGain,effectiveRoverGain:je.effectiveRoverGain,element:Qe?{muted:Qe.muted,volume:Qe.volume,paused:Qe.paused,ended:Qe.ended,readyState:Qe.readyState,networkState:Qe.networkState}:null,...Xe};try{console.log(`[AudioDebug] ${ge} ${JSON.stringify(ze)}`)}catch{console.log("[AudioDebug]",ge,ze)}},[gi,d]),al=$?.socials?.find(ge=>String(ge?.id||ge?.label||"").toLowerCase()==="discord")?.url||$?.discord?.invite||null;m.useEffect(()=>{tl&&console.log("[OvercurrentHUD]",{overlayVisible:Yt,overlayMotors:rn,overlayFill:pt,limiterActive:ts,limiterCaps:sn,limiterGroups:Ea,wheelOvercurrents:Pa})},[tl,pt,rn,Yt,ts,sn,Ea,Pa]),m.useEffect(()=>{Fe("settings/update")},[Fe]);const _t=m.useCallback(()=>{clearTimeout(G.current),G.current=setTimeout(()=>le(Date.now()),_p)},[]),Pn=m.useCallback(()=>{clearTimeout(Z.current),Z.current=setTimeout(()=>I(Date.now()),_p)},[]),xn=m.useCallback(async()=>{const ge=_.current;if(ge)try{ge.muted=!0,await ge.play()}catch{}},[]),vi=m.useCallback((ge=0)=>{if(h||pe)return;clearTimeout(ae.current);const Xe=()=>{clearTimeout(ae.current),ae.current=setTimeout(()=>{h||Qe()},mj)},Qe=async()=>{const je=_.current;if(je)try{await xn(),je.muted=!1,await je.play(),de(!1)}catch{je.muted=!0,de(!0),Xe()}};ae.current=setTimeout(Qe,ge)},[xn,h,pe]);m.useEffect(()=>()=>{clearTimeout(G.current),clearTimeout(Z.current),clearTimeout(ae.current),clearInterval(F.current),ce.current&&(cancelAnimationFrame(ce.current),ce.current=null)},[]),m.useEffect(()=>{X==="playing"&&vi(0)},[X,vi]),m.useEffect(()=>{Ce&&(k("snapshot"),P(null))},[Ce]),m.useEffect(()=>{if(Ce||!a?.url||!_.current)return;let ge=!0,Xe;const Qe=setTimeout(()=>de(!0),0),je=(ze,vt)=>{ge&&(Fe("video/status",{nextStatus:ze,info:vt||null}),k(ze),P(vt||null),ze==="playing"&&xn(),["error","failed","disconnected","closed"].includes(ze)&&_t())};return Xe=new Tp({url:a.url,token:a.token,video:_.current,receiveAudio:!pe,onStatus:je}),Xe.start().catch(ze=>{ge&&(k("error"),P(ze.message),_t())}),()=>{ge=!1,clearTimeout(Qe),Xe?.stop()}},[Ce,a?.url,a?.token,te,_t,xn,pe,Fe]),m.useEffect(()=>{X==="stopped"&&a?.url&&_t()},[X,a?.url,_t]),m.useEffect(()=>{const ge=V.current;if(!ge||!l?.url){Fe("route/no-audio-url"),he.current=null;return}const Xe=Math.max(0,Math.min(1,nl)),Qe=he.current==null?Math.max(0,Math.min(1,ge.volume)):he.current;if(ce.current&&(cancelAnimationFrame(ce.current),ce.current=null),Xe>Qe){const je=performance.now(),ze=Qe,vt=Xe-ze,Et=Pe=>{const Te=Math.min(1,(Pe-je)/xj),Gt=ze+vt*Te;ge.volume=Gt,he.current=Gt,Te<1?ce.current=requestAnimationFrame(Et):ce.current=null};ce.current=requestAnimationFrame(Et)}else ge.volume=Xe,he.current=Xe;Fe("route/direct",{elementVolume:Xe,duckEnabled:Ne,mainBrushActive:rt,duckAmount:Le})},[l?.url,nl,Ne,rt,Le,Fe]),m.useEffect(()=>{if(!l?.url||!V.current)return;let ge=!0,Xe;const Qe=(je,ze)=>{ge&&(Fe("audio/status",{nextStatus:je,info:ze||null}),U(ze||(je==="connected"?"connected":null)),ue(vt=>je==="connected"&&(vt==="playing"||vt==="connecting")||je==="new"?vt:je),["error","failed"].includes(je)&&Pn())};return Xe=new Tp({url:l.url,token:l.token,video:V.current,audioOnly:!0,onStatus:Qe}),Xe.start().catch(je=>{ge&&(ue("error"),U(je.message),Pn())}),()=>{ge=!1,Xe?.stop()}},[l?.url,l?.token,se,Pn,Fe]),m.useEffect(()=>{const ge=V.current;if(!l?.url||!ge){clearInterval(F.current);return}if(!["connecting","connected","playing","paused"].includes(oe)){clearInterval(F.current);return}const Qe=async()=>{const je=V.current;je&&(je.muted=!1,!(!je.paused&&!je.ended)&&(Fe("retry/play-attempt"),je.play().then(()=>{Fe("retry/play-ok"),ue(ze=>ze==="connected"?"playing":ze),U(ze=>ze==="paused"?null:ze)}).catch(ze=>{Fe("retry/play-fail",{error:ze?.message}),U(vt=>vt||ze?.message||"autoplay blocked")})))};return Qe(),F.current=setInterval(Qe,hj),()=>clearInterval(F.current)},[l?.url,oe,Fe]),m.useEffect(()=>{const ge=V.current;if(!ge)return;const Xe=()=>{Fe("element/play"),ue(Te=>Te==="error"?Te:"playing"),U(null)},Qe=()=>{Fe("element/pause"),ue(Te=>["error","failed","disconnected","closed","stopped"].includes(Te)?Te:"paused"),U(Te=>Te||"paused")},je=()=>{Fe("element/ended"),ue(Te=>Te==="error"?Te:"stopped"),U(Te=>Te||"ended")},ze=()=>{const{error:Te}=ge,Gt=Te?.message||"audio error";Fe("element/error",{error:Gt}),ue("error"),U(Gt)},vt=()=>Fe("element/waiting"),Et=()=>Fe("element/canplay"),Pe=()=>Fe("element/stalled");return ge.addEventListener("play",Xe),ge.addEventListener("pause",Qe),ge.addEventListener("ended",je),ge.addEventListener("error",ze),ge.addEventListener("waiting",vt),ge.addEventListener("canplay",Et),ge.addEventListener("stalled",Pe),()=>{ge.removeEventListener("play",Xe),ge.removeEventListener("pause",Qe),ge.removeEventListener("ended",je),ge.removeEventListener("error",ze),ge.removeEventListener("waiting",vt),ge.removeEventListener("canplay",Et),ge.removeEventListener("stalled",Pe)}},[l?.url,Fe]);const yi=o?.error?`Error: ${o.error}`:o?.objectUrl?"snapshot":o?.status||"waiting",ns=Ce?yi:a?.url?X==="error"?`Error: ${Q||"unknown"}`:Q?`${X} (${Q})`:X:"waiting",qa=l?.error?`Error: ${l.error}`:l?.url?oe==="error"?`Error: ${q||"unknown"}`:q?`${oe} (${q})`:oe:null,bi=y==="spectator",$t=y==="none";return r.jsxs("div",{className:`flex flex-col gap-0.5 ${C?"h-full":""}`,children:[r.jsxs("div",{className:`relative w-full overflow-hidden bg-black ${C?"h-full flex-1":"aspect-[4/3]"}`,children:[Ce?o?.objectUrl?r.jsx("img",{src:o.objectUrl,alt:d,className:"h-full w-full object-contain",draggable:!1}):r.jsx("div",{className:"flex h-full w-full items-center justify-center text-sm text-slate-300",children:"Waiting for frame…"}):r.jsx("video",{ref:_,muted:h||re||pe,playsInline:!0,autoPlay:!0,controls:!1,className:"h-full w-full object-contain"}),r.jsx("audio",{ref:V,autoPlay:!0,hidden:!0}),!$t&&E?r.jsx(yj,{mobileHud:it,turnSeconds:T,isActiveDriver:B,idleSkipSeconds:L}):null,$t?null:r.jsx(vj,{frame:p,sensors:ne,label:d,status:ns,audioStatus:qa,levelStatus:xo,desktopLayout:Wn,layoutFormat:v,variant:y,driverLabel:b,battery:mt,showTopDown:mo,mobileHud:it,mapPosition:ln,turnTimerText:S,labelScale:w}),$t?null:r.jsx(jj,{compact:it}),!$t&&tl?r.jsx("div",{className:"pointer-events-none absolute left-1 top-1 z-40 rounded bg-black/80 px-1 py-0.5 text-[0.6rem] text-lime-200",children:`OC vis:${Yt?1:0} motors:${rn.length} fill:${Math.round(pt*100)}%`}):null,$t?null:r.jsx(Sj,{motors:rn,fill:pt,compact:it}),$t?null:r.jsx(Aj,{battery:mt,compact:it}),!$t&&bi&&mt.available?r.jsx("div",{className:"pointer-events-none absolute right-1 top-1/2 flex h-[70%] -translate-y-1/2 flex-col items-center justify-center rounded bg-black/60 px-0.5 pb-1 pt-1",children:r.jsx(yf,{visual:mt,orientation:"vertical",variant:"inline",compact:it,className:"h-full w-4"})}):null,!$t&&f?r.jsx("div",{className:"pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2",children:r.jsxs("div",{className:`mx-auto w-fit rounded border border-amber-300/80 bg-black/75 text-amber-200 ${it?"px-2 py-1 text-[0.6rem]":"px-3 py-1.5 text-sm"}`,children:[r.jsx("div",{className:"text-center",children:f}),r.jsx("div",{className:"pointer-events-auto mt-0",children:r.jsx(Qf,{id:"discord",label:"Join our Discord server while you wait!",url:al})})]})}):null]}),!$t&&!bi&&r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(gj,{sensors:ne}),r.jsx("div",{className:"panel-section space-y-0.5 text-sm",children:r.jsx(yf,{visual:mt,compact:it})})]})]})}function gj({sensors:a}){const l=[a?.lightBumpLeftSignal,a?.lightBumpFrontLeftSignal,a?.lightBumpCenterLeftSignal,a?.lightBumpCenterRightSignal,a?.lightBumpFrontRightSignal,a?.lightBumpRightSignal],i=l.filter(p=>p!=null).reduce((p,g)=>Math.max(p,g),1200),o=p=>Math.pow(Math.max(0,Math.min(1,(p??0)/i)),.35),f=p=>p==null||p<=0?"hsl(200 60% 18%)":`hsl(${(200+o(p)*360)%360} 100% 60%)`,d=6,h=12;return r.jsx("div",{className:"flex w-full items-center justify-center gap-0.5",children:l.map((p,g)=>{const v=o(p),y=gDate.now());m.useEffect(()=>{const P=setInterval(()=>E(Date.now()),150);return()=>clearInterval(P)},[]),a?.receivedAt&&N-a.receivedAt<200;const S=j,T=p==="mobile-portrait",B=S?"text-[0.45rem]":"text-[0.65rem]",L=S?"px-0.25 py-0.25":"px-1 py-0.5",$=S?"px-0.25 py-0.25":"px-0.5 py-0.5",_=S?"text-[0.55rem]":"text-[0.8rem]",V=S?"left-0.5 top-0.5":"left-1 top-1",G=S?"text-[0.5rem]":"text-[0.7rem]",Z=S?"px-0.5 py-0.25":"px-1 py-0.5",F=S?"left-0.5 top-1/2":"left-1 top-1/2",ae="bottom-0.5",ce={transform:`translateX(-50%) scale(${C})`,transformOrigin:"center bottom"},he="240px",X=T?.3:S?.33:.7,Q={width:he,height:he,opacity:S?.6:.7,transform:M==="top-center"?`translateX(-50%) scale(${X})`:`scale(${X})`,transformOrigin:M==="bottom-left"?"bottom left":M==="top-center"?"top center":"top right",...M==="bottom-left"?{left:"0.25rem",bottom:"0.25rem"}:M==="top-center"?{left:"50%",top:"0.25rem"}:{right:"0.25rem",top:"0.25rem"}};if(g==="none")return null;if(g==="spectator"){const P=[["Voltage",l?.voltageMv!=null?`${(l.voltageMv/1e3).toFixed(2)} V`:"--"],["Current",l?.currentMa!=null?`${l.currentMa} mA`:"--"],["Charge",l?.batteryChargeMah!=null?`${l.batteryChargeMah}`:"--"],["OI",l?.oiMode?.label||"--"]],oe=!!l?.chargingSources?.homeBase,ue=l?.chargingState?.label||"",q=!!(ue&&ue.toLowerCase()!=="not charging"),U=l?.oiMode?.label||"Unknown",te=U.toLowerCase(),le=te==="full"?"bg-emerald-500/80 text-emerald-50":te==="safe"?"bg-amber-400/80 text-amber-950":te==="passive"?"bg-slate-700/80 text-slate-100":"bg-slate-700/60 text-slate-200",se=oe?"bg-emerald-500/80 text-emerald-50":"bg-slate-700/70 text-slate-200",I=q?"bg-emerald-500/80 text-emerald-50":oe?"bg-amber-400/80 text-amber-950":"bg-slate-700/70 text-slate-200";return r.jsxs(r.Fragment,{children:[r.jsxs("div",{className:"pointer-events-none absolute inset-0 flex items-center justify-center",children:[r.jsx("div",{className:`absolute ${V} font-medium text-slate-100 ${B}`,children:r.jsxs("div",{className:"flex flex-col gap-0.5 leading-none",children:[r.jsxs("span",{children:["Status: ",o]}),f?r.jsxs("span",{children:["Audio: ",f]}):null,d?r.jsx("span",{className:"text-cyan-300",children:d}):null]})}),r.jsx("div",{className:`absolute ${F} flex -translate-y-1/2 flex-col gap-0.5 bg-black/70 text-slate-100 ${B} ${L}`,children:r.jsxs("div",{className:"space-y-0.5 leading-tight",children:[r.jsxs("div",{className:"flex flex-col gap-0.5 text-[0.75rem] font-semibold uppercase tracking-wide",children:[r.jsx("span",{className:`rounded px-1.5 py-0.5 ${se}`,children:oe?"Docked":"Undocked"}),r.jsx("span",{className:`rounded px-1.5 py-0.5 ${I}`,children:q?"Charging":"Not charging"}),r.jsxs("span",{className:`rounded px-1.5 py-0.5 ${le}`,children:["OI: ",U]})]}),P.map(([re,de])=>r.jsxs("span",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{className:"text-slate-400",children:re}),r.jsx("span",{className:"font-semibold text-white",children:de})]},re))]})}),r.jsx("div",{className:`absolute ${ae} left-1/2`,style:ce,children:r.jsxs("div",{className:`flex items-center gap-0.5 bg-black/80 text-slate-100 ${$} ${_}`,children:[r.jsx("span",{className:"font-semibold text-white",children:i||"Unnamed Rover"}),v?r.jsxs("span",{className:"text-slate-300",children:["• ",v]}):null]})})]}),b?r.jsx("div",{className:"pointer-events-none absolute rounded",style:{...Q},children:r.jsx(vf,{sensors:l,size:240,overlay:!0})}):null]})}return r.jsxs("div",{className:"pointer-events-none absolute inset-0 flex items-center justify-center",children:[r.jsx("div",{className:`absolute ${V} font-medium text-slate-100 ${B}`,children:r.jsxs("div",{className:"flex flex-col gap-0.5 leading-none",children:[r.jsxs("span",{children:["Status: ",o]}),f?r.jsxs("span",{children:["Audio: ",f]}):null,d?r.jsx("span",{className:"text-cyan-300",children:d}):null]})}),w?r.jsx("div",{className:`absolute left-1/2 top-0.5 -translate-x-1/2 rounded bg-black/70 text-slate-100 ${Z} ${G}`,children:w}):null,r.jsx("div",{className:`absolute ${ae} left-1/2`,style:ce,children:r.jsx("div",{className:`flex gap-0.5 bg-black/80 text-slate-100 ${$} ${_}`,children:r.jsxs("span",{children:['Rover: "',i||"Unnamed Rover",'"']})})}),b&&g!=="spectator"?r.jsx("div",{className:"pointer-events-none absolute rounded",style:{...Q},children:r.jsx(vf,{sensors:l,size:240,overlay:!0})}):null]})}function yj({mobileHud:a=!1,turnSeconds:l=null,isActiveDriver:i=!1,idleSkipSeconds:o=null}){const f=a?"text-3xl":"text-5xl",d=a?"text-xs":"text-sm",h=a?"text-[0.55rem]":"text-[0.75rem]",p=a?"px-4 py-3":"px-6 py-4",g=i&&typeof o=="number";return r.jsx("div",{className:"pointer-events-none absolute inset-0 z-30 flex items-center justify-center bg-black/55",children:r.jsxs("div",{className:`flex flex-col items-center gap-0.5 rounded border border-amber-300/80 bg-black/70 ${p}`,children:[r.jsx("div",{className:`font-semibold text-amber-200 ${f}`,children:"IT IS YOUR TURN!"}),r.jsx("div",{className:`text-amber-200/80 ${d}`,children:"Start driving!"}),g?r.jsxs("div",{className:`text-red-100/90 ${h}`,children:["Idle skip in ",o,"s"]}):null]})})}const bj={leftWheel:"Left wheel",rightWheel:"Right wheel",mainBrush:"Main brush",sideBrush:"Side brush",limiter:"Overcurrent limit"};function Sj({motors:a,fill:l=0,compact:i=!1}){if(!a?.length)return null;const o=a.map(y=>bj[y]||y),f=i?"w-[12rem] h-[3.5rem]":"w-[20rem] h-[7rem]",d=i?"px-2 py-1":"px-4 py-2",h=i?"text-lg":"text-4xl",p=i?"text-xs":"text-xl",g=Math.max(0,Math.min(1,l)),v=`${Math.round(g*100)}%`;return r.jsx("div",{className:`pointer-events-none absolute flex items-center justify-center bg-red-900/50 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 ${f}`,children:r.jsxs("div",{className:"relative h-full w-full",children:[r.jsx("div",{className:"absolute inset-0 overflow-hidden",children:r.jsx("div",{className:"h-full bg-red-700/60",style:{width:v}})}),r.jsxs("div",{className:`relative z-10 flex h-full w-full flex-col items-center justify-center text-center font-semibold text-white animate-pulse ${h} ${d}`,children:[r.jsx("div",{children:"OVERCURRENT"}),r.jsx("div",{className:`mt-0 font-medium text-white ${p}`,children:o.join(", ")})]})]})})}function Aj({battery:a,compact:l=!1}){if(!a?.available||!a.warnActive&&!a.urgentActive)return null;const i=a.urgentActive?"BATTERY VERY LOW, DOCK THE ROVER AND CHARGE IMMEDIATELY!!":"Battery low! please dock and charge the rover soon.",o=l?"p-2 top-6":"p-4 top-10",f=l?"text-sm":"text-2xl";return r.jsx("div",{className:`pointer-events-none absolute flex items-center justify-center bg-amber-900/60 left-1/2 -translate-x-1/2 ${o}`,children:r.jsx("div",{className:`text-center font-semibold text-white animate-pulse ${f}`,children:r.jsx("div",{children:i})})})}function jj({compact:a=!1}){const{session:l}=Ve(),{sendMessage:i,onInputFocus:o,onInputBlur:f,blurChat:d,registerInputRef:h,setTypingActive:p}=Uf(),{value:g}=st("tts",{engine:"flite",voice:"rms",pitch:50}),[v,y]=m.useState(""),[b,j]=m.useState(!1),M=l?.role!=="spectator",w=l?.role==="spectator",C=l?.assignment?.roverId||null,E=!!m.useMemo(()=>l?.roster?.find(_=>String(_.id)===String(C))||null,[C,l?.roster])?.audio?.ttsEnabled,S=m.useMemo(()=>{if(!E)return null;const _=g?.engine==="espeak"?"espeak":"flite";if(_==="espeak"){let G=Number.isFinite(g?.pitch)?Math.round(g.pitch):void 0;return typeof G=="number"&&(G=Math.max(0,Math.min(99,G))),{speak:!0,engine:_,pitch:G}}const V=typeof g?.voice=="string"?g.voice:void 0;return{speak:!0,engine:_,voice:V}},[g?.engine,g?.pitch,g?.voice,E]),T=a?"pointer-events-auto absolute bottom-0.5 right-0.5 flex w-[9rem] max-w-[70vw] items-center gap-0.5 rounded bg-black/70 px-0.4 py-0.2":"pointer-events-auto absolute bottom-1 right-1 flex w-[12rem] max-w-[70vw] items-center gap-0.5 rounded bg-black/70 px-0.5 py-0.25",B=a?"min-w-0 flex-1 bg-transparent text-[0.55rem] text-slate-100 placeholder:text-slate-400 focus:outline-none":"min-w-0 flex-1 bg-transparent text-[0.7rem] text-slate-100 placeholder:text-slate-400 focus:outline-none",L=a?"rounded bg-cyan-500/80 px-0.35 py-0.2 text-[0.55rem] font-semibold text-black disabled:opacity-50":"rounded bg-cyan-500/80 px-0.5 py-0.25 text-[0.7rem] font-semibold text-black disabled:opacity-50";async function $(_){if(_.preventDefault(),!M)return;const V=v.trim();if(V){j(!0);try{await i(V,S),y(""),d(),p(!1)}catch(G){alert(G.message)}finally{j(!1)}}}return w?null:r.jsxs("form",{onSubmit:$,className:T,children:[r.jsx("input",{className:B,value:v,onChange:_=>{const V=_.target.value;y(V),p(!!V.trim())},onFocus:_=>{o(_),p(!!v.trim())},onBlur:_=>{f(_),p(!1)},onKeyDown:_=>{_.key==="Enter"&&!v.trim()&&(_.preventDefault(),d(),p(!1))},ref:_=>h(_,{target:"hud"}),placeholder:M?"Chat (TTS)":"Spectator",disabled:!M}),r.jsx("button",{type:"submit",disabled:!M||b,className:L,children:"Speak"})]})}function Kf({layoutFormat:a="desktop"}){const{session:l}=Ve(),{state:{song:i,lastControlIntentAt:o},overcurrentLimiter:f}=Dt(),[d,h]=m.useState(()=>Date.now()),[p,g]=m.useState(!1),[v,y]=m.useState(null),b=m.useRef({active:!1,roverId:null});m.useEffect(()=>{if(l?.mode!=="turns")return;const le=setInterval(()=>h(Date.now()),250);return()=>clearInterval(le)},[l?.mode]);const j=l?.assignment?.roverId,w=!!(j&&l?.roster?l.roster.find(le=>String(le.id)===String(j)):null)?.media?.audioPublishUrl,C=j?l?.turnQueues?.[j]:null,N=l?.socketId||null,E=j?l?.activeDrivers?.[j]:null,S=!!(N&&E===N),T=m.useMemo(()=>{const le=C?.queue||[];if(!le.length||!C?.current||le.length<=1)return null;const se=le.findIndex(I=>I===C.current);return se===-1?le[0]||null:le[(se+1)%le.length]||null},[C?.queue,C?.current]),B=!!(N&&T===N),L=C?.deadline||null,$=C?.idleDeadline||null,_=L?L-d:null,V=$?$-d:null,G=l?.mode==="turns"&&B&&_!=null&&_<=5e3&&_>0,Z=l?.mode!=="turns"||S||G,F=_!=null&&Number.isFinite(_)?Math.max(0,Math.ceil(_/1e3)):null,ae=V!=null&&Number.isFinite(V)?Math.max(0,Math.ceil(V/1e3)):null,ce=S?F!=null?`${F}s left`:null:B&&F!=null?`Your turn in ${F}s`:null,he=j?[...Z?[{type:"rover",id:j,key:j}]:[],...w?[{type:"rover",id:`${j}-audio`,key:`${j}-audio`}]:[]]:[],X=ri(he),k=j&&Z?X[j]:null,Q=j&&w?X[`${j}-audio`]:null,P=Gf(j?[j]:[],{enabled:!!j,version:l?.mode}),oe=j&&P[j]||null,ue=es(j),q=j&&l?.roster?l.roster.find(le=>String(le.id)===String(j)):null,U=q?.battery??null,te=q?.name||(j?`Rover ${j}`:"");return m.useEffect(()=>{if(l?.mode!=="turns"){g(!1),y(null),b.current={active:!1,roverId:null};return}const le=b.current,se=S&&!le.active,I=S&&j&&j!==le.roverId;se||I?(g(!0),y(Date.now())):!S&&le.active&&(g(!1),y(null)),b.current={active:S,roverId:j}},[S,j,l?.mode]),m.useEffect(()=>{!p||!v||o>v&&g(!1)},[o,v,p]),r.jsx("section",{className:"panel",children:j?r.jsx(ci,{sessionInfo:k,videoMode:Z?"whep":"snapshot",snapshotFeed:oe,audioSessionInfo:Q,label:te,telemetryFrame:ue,batteryConfig:U,layoutFormat:a,overcurrentLimiter:f,songNote:i?.note,qualityNotice:Z?null:"Preview feed (low FPS) until your turn.",showTurnCue:p,turnTimerText:ce,turnSeconds:F,isActiveDriver:S,idleSkipSeconds:ae}):r.jsxs("div",{className:"panel-muted content-center text-center text-sm text-slate-400 aspect-[4/3]",children:[r.jsx("p",{children:"You are not assigned to a rover."}),r.jsx("p",{className:"mt-0",children:r.jsx("a",{href:"/spectate",className:"text-blue-400 underline hover:text-blue-500",children:"Click here to visit the spectator page."})})]})})}function bf({label:a,tone:l="muted"}){const i=l==="success"?"bg-emerald-900 text-emerald-100":l==="warn"?"bg-amber-900 text-amber-100":"bg-slate-800 text-slate-200";return r.jsx("span",{className:`rounded px-1 py-0.5 text-xs font-semibold leading-none ${i}`,children:a})}function ro(a){if(!Number.isFinite(a))return 0;const l=a%360;return l<0?l+360:l}function Nj(a){if(!Array.isArray(a)||a.length<3)return 0;const[l,i,o]=a,f=Math.max(0,Math.min(255,Number(l)))/255,d=Math.max(0,Math.min(255,Number(i)))/255,h=Math.max(0,Math.min(255,Number(o)))/255,p=Math.max(f,d,h),g=Math.min(f,d,h),v=p-g;if(v===0)return 0;let y=0;return p===f?y=(d-h)/v%6:p===d?y=(h-f)/v+2:y=(f-d)/v+4,ro(Math.round(y*60))}function Op(a){const l=ro(a),i=1,o=1-Math.abs(l/60%2-1);let f=0,d=0,h=0;return l<60?(f=i,d=o):l<120?(f=o,d=i):l<180?(d=i,h=o):l<240?(d=o,h=i):l<300?(f=o,h=i):(f=i,h=o),[Math.round(f*255),Math.round(d*255),Math.round(h*255)]}function Bp(a){return a?Array.isArray(a.hsColor)?ro(Number(a.hsColor[0])):Array.isArray(a.rgbColor)?Nj(a.rgbColor):0:0}function wj({entity:a,connected:l,onToggle:i,onSetColor:o}){const f=a.state==="unavailable"||!a.available,d=a.state==="on",h=a.type==="light"&&a.supportsColor,p=f?"warn":d?"success":"muted",g=f?"Unavailable":d?"On":"Off",v=!l||f,y=v||!h,[b,j]=m.useState(()=>Bp(a)),M=m.useRef(b),w=m.useRef(!1),C=f?"border-slate-800 bg-slate-900 text-slate-400 cursor-not-allowed":d?"border-emerald-700 bg-emerald-900/80 text-emerald-50 hover:bg-emerald-800":"border-rose-800 bg-rose-900/80 text-rose-50 hover:bg-rose-800";m.useEffect(()=>{if(!h||w.current)return;const L=Bp(a);M.current=L,j(L)},[a.rgbColor,a.hsColor,h]);const N=L=>{const $=ro(Number(L.target.value));M.current=$,j($)},E=()=>{y||!o||o(a.id,Op(M.current))},S=L=>{L.stopPropagation()},B=`rgb(${(h?Op(M.current):[255,255,255]).join(",")})`;return r.jsxs("button",{type:"button",onClick:()=>i(a.id),disabled:v,className:`relative flex min-w-[12rem] flex-[1_1_12rem] items-start justify-between gap-0.5 rounded px-1 py-0.5 text-left transition-colors ${C} disabled:opacity-60 disabled:hover:bg-inherit`,children:[r.jsxs("div",{className:"min-w-0 flex-1",children:[r.jsxs("div",{className:"flex items-center gap-0.5 text-sm leading-normal",children:[r.jsx("span",{className:"truncate font-semibold text-white",children:a.name||a.id}),h&&r.jsxs(r.Fragment,{children:[r.jsx(bf,{label:g,tone:p}),!l&&r.jsx("span",{className:"text-xs text-amber-200",children:"Offline"})]})]}),h?r.jsx("div",{className:"-mt-0.5 w-full",onClick:S,onPointerDown:L=>{S(L),w.current=!0},onPointerUp:L=>{S(L),w.current=!1,E()},onPointerCancel:L=>{S(L),w.current=!1,E()},onKeyUp:L=>{S(L),E()},onBlur:E,children:r.jsx("div",{className:"relative w-full",children:r.jsx("input",{type:"range",min:"0",max:"360",step:"1",value:b,onChange:N,disabled:y,"aria-label":`Set color for ${a.name||a.id}`,className:"ha-hue-slider w-full"})})}):r.jsxs("div",{className:"flex items-center gap-0.5 text-xs text-slate-400",children:[r.jsx(bf,{label:g,tone:p}),!l&&r.jsx("span",{className:"text-amber-200",children:" · Offline"})]})]}),h&&r.jsx("span",{className:"pointer-events-none absolute right-1 top-1 h-2.5 w-2.5 rounded-full border border-white/60",style:{backgroundColor:B}}),!h&&r.jsx("div",{className:"self-center text-xs font-semibold text-white/90",children:d?"Turn off":"Turn on"})]})}function ig(){const{state:{keymap:a}}=Dt(),{session:l,homeAssistantToggle:i,homeAssistantSetLightColor:o}=Ve(),f=l?.homeAssistant,d=m.useMemo(()=>f?.entities||[],[f?.entities]),h=An(a?.homeAssistantOn?.[0]),p=An(a?.homeAssistantOff?.[0]);if(!f?.enabled)return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm text-slate-400",children:[r.jsx("p",{className:"text-slate-300",children:"Room Controls"}),r.jsx("p",{className:"text-slate-500",children:"Not configured on the server."})]});if(d.length===0)return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm text-slate-400",children:[r.jsx("p",{className:"text-slate-300",children:"Room Controls"}),r.jsx("p",{className:"text-slate-500",children:"No lights or switches configured."})]});const g=!!f?.connected;return r.jsxs("section",{className:"panel-section space-y-0.5 text-base",children:[r.jsxs("header",{className:"flex items-center justify-between gap-0.5 text-sm text-slate-400",children:[r.jsxs("div",{className:"flex items-center gap-0.5",children:[r.jsx("p",{children:"Room Controls"}),r.jsx("span",{className:"text-xs text-slate-500",children:d.length}),r.jsxs("div",{className:"flex items-center gap-0.5 text-xs text-slate-300 background-black",children:[r.jsxs("span",{className:"flex items-center gap-0.5",children:[r.jsx("span",{children:"On"}),h?r.jsx(Lp,{label:h}):null]}),r.jsxs("span",{className:"flex items-center gap-0.5",children:[r.jsx("span",{children:"Off"}),p?r.jsx(Lp,{label:p}):null]})]})]}),r.jsx(bf,{label:g?"Connected":"Offline",tone:g?"success":"warn"})]}),r.jsx("div",{className:"flex flex-wrap gap-0.5",children:d.map(v=>r.jsx(wj,{entity:v,connected:g,onToggle:i,onSetColor:o},v.id))})]})}function Lp({label:a}){return a?r.jsx("span",{className:"rounded border border-white/40 px-1 text-[0.7rem] text-white",children:a}):null}function rg(){const{login:a,setRole:l}=Ve(),[i,o]=m.useState(""),[f,d]=m.useState(""),[h,p]=m.useState(!1);async function g(v){v.preventDefault(),p(!0);try{await a(i,f),o(""),d("")}catch(y){alert(y.message)}finally{p(!1)}}return r.jsxs("div",{className:"panel-section space-y-0.5 text-base",children:[r.jsx("p",{className:"text-sm text-slate-400",children:"Admin login"}),r.jsxs("form",{className:"flex flex-col gap-0.5",onSubmit:g,children:[r.jsx("input",{className:"field-input",placeholder:"Username",value:i,onChange:v=>o(v.target.value)}),r.jsx("input",{className:"field-input",placeholder:"Password",type:"password",value:f,onChange:v=>d(v.target.value)}),r.jsx("button",{type:"submit",disabled:h,className:"button-dark w-full disabled:opacity-50",children:h?"Logging in…":"Login"})]})]})}function zp(...a){return a.filter(Boolean).join(" ")}function Ej(a){const l=a?.batteryState?.percentDisplay;return l==null?"--":`${l}%`}function Cj(a){return a?.batteryState?a.batteryState.urgentActive?"text-red-400":a.batteryState.warnActive?"text-amber-300":"text-emerald-300":"text-slate-400"}function qj({title:a,roster:l=[],emptyText:i="No roster data.",renderActions:o=null}){return r.jsxs("div",{className:"space-y-0.5 text-sm",children:[a&&r.jsx("p",{className:"text-sm text-slate-400",children:a}),l.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:i}):r.jsx("ul",{className:"space-y-0.5 text-sm",children:l.map(f=>r.jsxs("li",{className:zp("surface flex flex-wrap items-center justify-between gap-0.5",f.locked&&"bg-red-900/40"),children:[r.jsxs("div",{children:[r.jsx("p",{className:"text-slate-200",children:f.name}),r.jsxs("p",{className:"text-xs text-slate-500 flex flex-wrap items-center gap-0.5",children:[r.jsx("span",{children:f.locked?"locked":"free"}),f.lockReason&&r.jsx("span",{className:"rounded bg-black/30 px-1",children:f.lockReason}),r.jsxs("span",{className:zp("font-semibold",Cj(f)),children:["Battery ",Ej(f)]})]})]}),o?o(f):null]},f.id))})]})}const Mj=[{key:"open",label:"Open"},{key:"turns",label:"Turns"},{key:"admin",label:"Admin"},{key:"lockdown",label:"Lockdown"}];function kj(){const{session:a,lockRover:l,setMode:i,requestControl:o,setCommunityGoal:f,setAdminReason:d,rebootRover:h,rebootServer:p,llmControl:g,adminLogs:v,llmCommentaryStatus:y}=Ve(),b=m.useMemo(()=>a?.roster??[],[a?.roster]),[j,M]=m.useState({}),[w,C]=m.useState({}),[N,E]=m.useState(!1),[S,T]=m.useState(!1),B=a?.health||null,L=a?.communityGoal?.text||"",$=a?.communityGoal?.updatedAt||null,[_,V]=m.useState(L),G=a?.adminReason?.text||"",Z=a?.adminReason?.updatedAt||null,[F,ae]=m.useState(G),ce=a?.role==="admin"||a?.role==="lockdown"||a?.role==="lockdown-admin",he=a?.mode??"open",X=async(I,re)=>{try{await l(I,re),M(de=>({...de,[I]:re}))}catch(de){alert(de.message)}},k=async I=>{const re=I.target.value;try{await i(re)}catch(de){alert(de.message)}},Q=async I=>{try{await o(I,{force:!0})}catch(re){alert(re.message)}},P=async I=>{if(!(!I?.id||!window.confirm(`Reboot rover "${I.name||I.id}" now?`))){C(de=>({...de,[I.id]:!0}));try{await h(I.id)}catch(de){alert(de.message)}finally{C(de=>({...de,[I.id]:!1}))}}},oe=async()=>{if(window.confirm("Reboot the server host now? This will disconnect all users.")){E(!0);try{await p()}catch(re){alert(re.message),E(!1)}}},ue=async()=>{if(window.confirm("Clear LLM commentary history now? This resets chat context, bot memory, and rover activity metrics for narration.")){T(!0);try{await g("clearHistory")}catch(re){alert(re.message)}finally{T(!1)}}},q=async()=>{try{await f(_)}catch(I){alert(I.message)}},U=async()=>{try{await f(null)}catch(I){alert(I.message)}},te=async()=>{try{await d(F)}catch(I){alert(I.message)}},le=async()=>{try{await d(null)}catch(I){alert(I.message)}};m.useEffect(()=>{V(L)},[L]),m.useEffect(()=>{ae(G)},[G]);const se=m.useMemo(()=>{const I={};return b.forEach(re=>{I[re.id]=j[re.id]??re.locked}),I},[b,j]);return ce?r.jsxs("section",{className:"panel-section space-y-0.5 text-base",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5 text-sm",children:[r.jsx("span",{children:"Admin controls"}),r.jsx("select",{value:he,onChange:k,className:"field-input text-sm",children:Mj.map(I=>r.jsx("option",{value:I.key,children:I.label},I.key))})]}),r.jsx("div",{className:"flex gap-0.5 text-xs",children:r.jsx("button",{type:"button",onClick:oe,disabled:N,className:"button-danger disabled:cursor-not-allowed disabled:opacity-60",children:N?"Server rebooting...":"Reboot Server"})}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Community goal"}),$?r.jsxs("span",{children:["Updated ",new Date($).toLocaleString()]}):null]}),r.jsx("input",{type:"text",value:_,onChange:I=>V(I.target.value),placeholder:"Set a community goal",className:"field-input text-sm"}),r.jsxs("div",{className:"flex gap-0.5 text-xs",children:[r.jsx("button",{type:"button",onClick:q,className:"button-dark",children:"Set goal"}),r.jsx("button",{type:"button",onClick:U,className:"button-danger",children:"Clear"})]})]}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Admin mode reason"}),Z?r.jsxs("span",{children:["Updated ",new Date(Z).toLocaleString()]}):null]}),r.jsx("textarea",{value:F,onChange:I=>ae(I.target.value),placeholder:"Set an admin mode reason",className:"field-input text-sm min-h-[3.5rem]"}),r.jsxs("div",{className:"flex gap-0.5 text-xs",children:[r.jsx("button",{type:"button",onClick:te,className:"button-dark",children:"Set reason"}),r.jsx("button",{type:"button",onClick:le,className:"button-danger",children:"Clear"})]})]}),r.jsx(qj,{roster:b,renderActions:I=>r.jsxs("div",{className:"flex flex-wrap gap-0.5 text-xs",children:[r.jsx("button",{type:"button",onClick:()=>X(I.id,!se[I.id]),className:"button-dark",children:se[I.id]?"Unlock":"Lock"}),r.jsx("button",{type:"button",onClick:()=>Q(I.id),className:"button-dark",children:"Force"}),r.jsx("button",{type:"button",onClick:()=>P(I),disabled:!!w[I.id],className:"button-danger disabled:cursor-not-allowed disabled:opacity-60",children:w[I.id]?"Rebooting...":"Reboot"})]})}),r.jsx(_j,{health:B}),r.jsx(Rj,{status:y,onClearHistory:ue,clearingHistory:S}),r.jsx(Oj,{entries:v})]}):null}function Rj({status:a,onClearHistory:l,clearingHistory:i}){if(!a)return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("div",{className:"panel-muted text-xs uppercase",children:"LLM Commentary"}),r.jsx("div",{className:"surface text-xs text-slate-300",children:"No status received yet."})]});const o=a.lastTickAt?new Date(a.lastTickAt).toLocaleString():"never",f=a.nextRunAt?new Date(a.nextRunAt).toLocaleString():"n/a",d=a.lastPostedAt?new Date(a.lastPostedAt).toLocaleString():"never",h=a.lastSnapshotSummary||{},p=a.lastOutcome==="failed"?"text-red-300":a.lastOutcome==="posted"?"text-emerald-300":"text-slate-300",g=Tj(a),v=Dj(a);return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("div",{className:"panel-muted text-xs uppercase",children:"LLM Commentary"}),r.jsx("div",{className:"flex gap-0.5 text-xs",children:r.jsx("button",{type:"button",onClick:l,disabled:!!i,className:"button-danger disabled:cursor-not-allowed disabled:opacity-60",children:i?"Clearing...":"Clear LLM History"})}),r.jsxs("div",{className:`surface border text-center ${g.className}`,children:[r.jsx("div",{className:"text-[1.1rem] font-bold tracking-wide",children:g.label}),r.jsx("div",{className:"text-xs text-slate-200",children:g.detail})]}),r.jsxs("div",{className:"surface space-y-0.5 text-xs text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Enabled"}),r.jsx("span",{children:a.enabled?"yes":"no"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Running"}),r.jsx("span",{children:a.running?"yes":"no"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"In flight"}),r.jsx("span",{children:a.inFlight?"yes":"no"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Model"}),r.jsx("span",{className:"text-slate-300",children:a.model||"--"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Server"}),r.jsx("span",{className:"text-slate-300",children:a.ollamaUrl||"--"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Frequency"}),r.jsxs("span",{children:[a.frequencyMs," ms"]})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Tick count"}),r.jsx("span",{children:a.tickCount??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Skip streak"}),r.jsx("span",{children:a.skipStreak??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Last outcome"}),r.jsx("span",{className:p,children:a.lastOutcome||"--"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Last reason"}),r.jsx("span",{className:"text-slate-300",children:a.lastReason||"--"})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Last tick"}),r.jsx("span",{className:"text-slate-300",children:o})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Next run"}),r.jsx("span",{className:"text-slate-300",children:f})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Last posted"}),r.jsx("span",{className:"text-slate-300",children:d})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Prompt chars"}),r.jsx("span",{children:a.lastPromptChars??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Cleared count"}),r.jsx("span",{children:a.clearCount??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Last cleared"}),r.jsx("span",{className:"text-slate-300",children:a.lastClearedAt?new Date(a.lastClearedAt).toLocaleString():"never"})]})]}),r.jsxs("div",{className:"surface space-y-0.5 text-xs text-slate-300",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Snapshot active drivers"}),r.jsx("span",{children:h.activeDrivers??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Snapshot rovers"}),r.jsx("span",{children:h.rovers??0})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Snapshot chat msgs"}),r.jsx("span",{children:h.chatMessages??0})]})]}),a.lastError?r.jsxs("div",{className:"surface text-xs text-red-300 break-words",children:["Error: ",a.lastError]}):null,a.lastGeneratedText?r.jsxs("div",{className:"surface text-xs text-slate-200 break-words",children:["Generated: ",a.lastGeneratedText]}):null,a.lastPostedText?r.jsxs("div",{className:"surface text-xs text-emerald-200 break-words",children:["Posted: ",a.lastPostedText]}):null,r.jsxs("details",{className:"surface text-xs text-slate-200",children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Most recent system prompt"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:a.lastSystemPrompt||"No prompt read yet."})]}),r.jsxs("details",{className:"surface text-xs text-slate-200",children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Most recent info snapshot"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:a.lastInfoSnapshot?JSON.stringify(a.lastInfoSnapshot,null,2):"No snapshot captured yet."})]}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("div",{className:"panel-muted text-xs uppercase",children:"Most recent LLM conversation"}),r.jsx("div",{className:"surface max-h-72 space-y-0.5 overflow-y-auto",children:v.length?v.map(y=>r.jsx(Bf,{message:y.message},y.id)):r.jsx("div",{className:"text-xs text-slate-300",children:"No model conversation captured yet."})})]})]})}function Tj(a){return a?.inFlight?{label:"IN FLIGHT",detail:"Generating commentary now",className:"border-amber-400/60 bg-amber-700/20 text-amber-200"}:a?.lastOutcome==="posted"?{label:"POSTED",detail:a?.lastPostedText?`Last: ${a.lastPostedText}`:"Commentary posted",className:"border-emerald-400/60 bg-emerald-700/20 text-emerald-200"}:a?.lastOutcome==="skipped"?{label:"SKIPPED",detail:a?.lastReason||"Model chose to skip",className:"border-slate-400/60 bg-slate-700/30 text-slate-200"}:a?.lastOutcome==="failed"?{label:"FAILED",detail:a?.lastError||a?.lastReason||"Tick failed",className:"border-red-400/60 bg-red-700/20 text-red-200"}:{label:a?.running?"IDLE":"STOPPED",detail:a?.lastReason||"Waiting for next tick",className:"border-sky-400/50 bg-sky-700/20 text-sky-200"}}function Dj(a){const l=Date.now(),o=(Array.isArray(a?.lastModelMessages)?a.lastModelMessages:[]).map((f,d)=>{const h=String(f?.role||"").toLowerCase(),p=typeof f?.content=="string"?f.content:JSON.stringify(f?.content??null,null,2),g=h==="system"?"LLM System":h==="assistant"?"LLM Context":h==="user"?"LLM Input":"LLM Message";return{id:`llm-msg-${d}`,message:{ts:l+d,nickname:g,text:p,role:"spectator",system:h==="system"}}});if(a?.lastModelRawOutput!=null){const f=String(a.lastModelRawOutput);o.push({id:"llm-output",message:{ts:l+o.length+1,nickname:"LLM Output",text:f.trim()?f:"",role:"spectator",system:!0}})}return o}function _j({health:a}){if(!a)return null;const l=a.replay||{sources:[],readyCount:0,totalCount:0},i=a.snapshots||{rovers:[],rooms:[]},o=`${l.readyCount}/${l.totalCount} sources ready`,f=i.rovers.filter(h=>h.stale).length,d=i.rooms.filter(h=>h.stale).length;return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("div",{className:"panel-muted text-xs uppercase",children:"Health"}),r.jsxs("div",{className:"surface space-y-0.5 text-xs text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Replay segments"}),r.jsx("span",{className:"text-slate-400",children:o})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Rover snapshots"}),r.jsxs("span",{className:f?"text-amber-300":"text-emerald-300",children:[i.rovers.length-f,"/",i.rovers.length," ok"]})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Room cameras"}),r.jsxs("span",{className:d?"text-amber-300":"text-emerald-300",children:[i.rooms.length-d,"/",i.rooms.length," ok"]})]})]}),r.jsx("div",{className:"space-y-0.5 text-xs text-slate-300",children:l.sources.map(h=>r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:h.label}),r.jsxs("span",{className:h.ready?"text-emerald-300":"text-amber-300",children:[h.recentCount,"/",h.neededCount]})]},`${h.type}:${h.id}`))}),r.jsx("div",{className:"space-y-0.5 text-xs text-slate-300",children:i.rovers.map(h=>r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:h.name}),r.jsx("span",{className:h.stale?"text-amber-300":"text-emerald-300",children:h.stale?"stale":"ok"})]},`rover:${h.id}`))}),r.jsx("div",{className:"space-y-0.5 text-xs text-slate-300",children:i.rooms.map(h=>r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:h.name}),r.jsx("span",{className:h.stale?"text-amber-300":"text-emerald-300",children:h.error?"error":h.stale?"stale":"ok"})]},`room:${h.id}`))})]})}function Oj({entries:a}){const l=a||[];return r.jsxs("div",{className:"panel-section space-y-0.5 text-base",children:[r.jsxs("div",{className:"flex items-center justify-between text-sm text-slate-400",children:[r.jsx("span",{children:"Admin IP log"}),r.jsx("span",{children:l.length})]}),r.jsx("div",{className:"surface h-64 overflow-y-auto font-mono text-xs",children:l.length===0?r.jsx("p",{children:"No admin log entries yet."}):l.slice().reverse().map(i=>r.jsxs("div",{className:"surface",children:[r.jsx("span",{className:"text-amber-400",children:i.ts?new Date(i.ts).toLocaleTimeString():"--"})," ",i.label&&r.jsxs("span",{className:"text-teal-400",children:["[",i.label,"]"]})," ",r.jsx("span",{className:"text-slate-200",children:i.message})," ",i.ip&&r.jsx("span",{className:"text-cyan-300",children:i.ip})," ",i.meta&&r.jsx("span",{className:"text-slate-500",children:JSON.stringify(i.meta)})]},i.id))}),r.jsx("p",{className:"text-xs text-slate-500",children:"Admin-only log stream; IPs never appear in user data."})]})}const Bj=[{id:"driveForward",label:"Drive Forward",group:"Driving"},{id:"driveBackward",label:"Drive Backward",group:"Driving"},{id:"driveLeft",label:"Turn Left",group:"Driving"},{id:"driveRight",label:"Turn Right",group:"Driving"},{id:"boostModifier",label:"Boost Modifier",group:"Driving"},{id:"slowModifier",label:"Slow Modifier",group:"Driving"},{id:"auxMainForward",label:"Main Brush Forward",group:"Aux Motors"},{id:"auxMainReverse",label:"Main Brush Reverse",group:"Aux Motors"},{id:"auxSideForward",label:"Side Brush Forward",group:"Aux Motors"},{id:"auxSideReverse",label:"Side Brush Reverse",group:"Aux Motors"},{id:"auxVacuumFast",label:"Vacuum Max",group:"Aux Motors"},{id:"auxVacuumSlow",label:"Vacuum Low",group:"Aux Motors"},{id:"auxAllForward",label:"All Aux Forward",group:"Aux Motors"},{id:"cameraUp",label:"Camera Up",group:"Camera"},{id:"cameraDown",label:"Camera Down",group:"Camera"},{id:"nightVisionToggle",label:"Toggle Night Vision",group:"Camera"},{id:"hornHonk",label:"Horn (Hold)",group:"Audio"},{id:"driveMacro",label:"Drive Macro",group:"Macros"},{id:"dockMacro",label:"Dock Macro",group:"Macros"},{id:"chatFocus",label:"Toggle Chat",group:"Chat"},{id:"songNoteUp",label:"Song Note Up",group:"Audio"},{id:"songNoteDown",label:"Song Note Down",group:"Audio"},{id:"homeAssistantOn",label:"Room Controls On (Cycle)",group:"Room Controls"},{id:"homeAssistantOff",label:"Room Controls Off (Cycle)",group:"Room Controls"}];function Lj(a){return a.reduce((l,i)=>((l[i.group]||(l[i.group]=[])).push(i),l),{})}function zj(a,l){const i=Number(a);return Number.isFinite(i)?Math.max(0,Math.min(500,i)):l}const Ff=5,ui=500,fi=1,Zf=100;function Uj(a,l){const i=Number(a);return Number.isFinite(i)?Math.max(Ff,Math.min(ui,i)):l}function Fr(a,l){const i=Number(a);return Number.isFinite(i)?Math.max(fi,Math.min(Zf,i)):l}function Hj(a){const i=(Fr(a,a)-fi)/(Zf-fi),o=ui-i*(ui-Ff);return Math.round(o)}function Vj(a){const l=Uj(a,a),i=(ui-l)/(ui-Ff),o=fi+i*(Zf-fi);return Math.round(o)}function Yj(a){const[l,i]=m.useState(null);return m.useEffect(()=>{if(!l)return;function o(f){if(f.preventDefault(),f.key==="Escape"){i(null);return}const d=xi(f.key??"");d&&(a(l,d,f),i(null))}return window.addEventListener("keydown",o,{capture:!0}),()=>window.removeEventListener("keydown",o,{capture:!0})},[l,a]),{active:l,startCapture:i,cancel:()=>i(null)}}function zr({label:a,description:l,value:i,onChange:o,min:f=0,max:d=500,step:h=5}){return r.jsxs("label",{className:"surface-muted block p-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-300",children:[r.jsx("span",{className:"font-semibold text-slate-100",children:a}),r.jsx("input",{type:"number",min:f,max:d,value:i,onChange:p=>o(p.target.value),className:"w-16 rounded border border-slate-700 bg-slate-900 px-1 py-[2px] text-right text-[0.75rem] font-mono text-slate-100"})]}),l&&r.jsx("p",{className:"text-[0.65rem] text-slate-500",children:l}),r.jsxs("div",{className:"mt-0 flex items-center gap-0.5",children:[r.jsx("input",{type:"range",min:f,max:d,step:h,value:i,onChange:p=>o(p.target.value),className:"h-2 flex-1 accent-emerald-400"}),r.jsx("span",{className:"w-12 text-right text-[0.75rem] font-mono text-slate-400",children:i})]})]})}function $j(){const{state:{keymap:a},actions:{updateKeyBinding:l,resetKeyBindings:i}}=Dt(),{value:o,save:f}=st("inputs",Sn),d={...Sn.keyboard,...o.keyboard??{}},h=m.useMemo(()=>typeof d.tiltSpeed=="number"?Fr(d.tiltSpeed,Sn.keyboard.tiltSpeed):typeof d.tiltIntervalMs=="number"?Fr(Vj(d.tiltIntervalMs),Sn.keyboard.tiltSpeed):Sn.keyboard.tiltSpeed,[d.tiltIntervalMs,d.tiltSpeed]),p=m.useMemo(()=>Lj(Bj),[]),{active:g,startCapture:v,cancel:y}=Yj((w,C)=>{l(w,C)}),b=m.useCallback(w=>a?.[w]?.[0]||ni[w]?.[0]||"",[a]),j=m.useCallback((w,C)=>{const N=Sn.keyboard,E=zj(C,N[w]);f(S=>({...S??{},keyboard:{...S?.keyboard??N,[w]:E}}))},[f]),M=m.useCallback(w=>{const C=Sn.keyboard,N=Fr(w,C.tiltSpeed),E=Hj(N);f(S=>({...S??{},keyboard:{...S?.keyboard??C,tiltSpeed:N,tiltIntervalMs:E}}))},[f]);return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsxs("div",{children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Keyboard layout"}),r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Per-browser · click to change a binding"})]}),r.jsx("button",{type:"button",onClick:()=>i(),className:"button-dark text-xs",children:"Reset defaults"})]}),r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Keyboard speeds"}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(zr,{label:"Base speed",description:"Normal driving speed",value:d.baseSpeed,onChange:w=>j("baseSpeed",w)}),r.jsx(zr,{label:"Turbo speed",description:"Used when holding the boost modifier",value:d.turboSpeed,onChange:w=>j("turboSpeed",w)}),r.jsx(zr,{label:"Precision speed",description:"Used when holding the precision modifier",value:d.precisionSpeed,onChange:w=>j("precisionSpeed",w)})]})]}),r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Camera tilt"}),r.jsx(zr,{label:"Tilt speed",description:"Higher = faster when holding tilt keys",value:h,onChange:M,min:1,max:100,step:1})]}),r.jsx("div",{className:"space-y-0.5",children:Object.entries(p).map(([w,C])=>r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.75rem] text-slate-400",children:w}),r.jsx("div",{className:"space-y-0.5",children:C.map(N=>{const E=b(N.id),S=g===N.id;return r.jsxs("div",{className:"surface-muted flex items-center justify-between text-xs",children:[r.jsxs("div",{children:[r.jsx("p",{className:"font-semibold text-slate-100",children:N.label}),r.jsx("p",{className:"text-[0.65rem] text-slate-400",children:An(E)})]}),r.jsxs("div",{className:"flex items-center gap-0.5",children:[S&&r.jsx("button",{type:"button",onClick:()=>y(),className:"button-danger text-[0.7rem]",children:"Cancel"}),r.jsx("button",{type:"button",onClick:()=>v(N.id),className:`${S?"px-0.5 py-0.5 bg-emerald-500 text-emerald-950 hover:bg-emerald-400":"button-dark"} text-[0.7rem] font-medium transition-colors`,children:S?"Press a key…":"Change"})]})]},N.id)})})]},w))})]})}const Kn=new Intl.NumberFormat(void 0,{maximumFractionDigits:2}),Gj=[{id:"drive",label:"Drive stick",kind:"axisPair",section:"Driving",invertDefaults:{invertX:!1,invertY:!0}},{id:"cameraTilt",label:"Camera tilt",kind:"axis",section:"Camera",invertDefaults:{invert:!0}},{id:"mainBrush",label:"Main brush",kind:"axis",section:"Brushes",invertDefaults:{invert:!1}},{id:"sideBrush",label:"Side brush",kind:"axis",section:"Brushes",invertDefaults:{invert:!1}},{id:"vacuum",label:"Vacuum",kind:"button",section:"Aux buttons"},{id:"allAux",label:"All aux",kind:"button",section:"Aux buttons"},{id:"mainReverse",label:"Main reverse toggle",kind:"button",section:"Brush toggles"},{id:"sideReverse",label:"Side reverse toggle",kind:"button",section:"Brush toggles"},{id:"driveMacro",label:"Drive macro",kind:"button",section:"Mode macros"},{id:"dockMacro",label:"Dock macro",kind:"button",section:"Mode macros"},{id:"nightVisionToggle",label:"Night vision toggle",kind:"button",section:"Camera"}],Up=.45,Xj=.6;function Ps({label:a,description:l,min:i,max:o,step:f,value:d,onChange:h}){return r.jsxs("label",{className:"surface-muted block p-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-300",children:[r.jsx("span",{className:"font-semibold text-slate-100",children:a}),r.jsx("span",{className:"font-mono text-slate-400",children:Kn.format(d)})]}),l&&r.jsx("p",{className:"text-[0.65rem] text-slate-500",children:l}),r.jsx("input",{type:"range",min:i,max:o,step:f,value:d,onChange:p=>h(Number(p.target.value)),className:"mt-0 w-full accent-emerald-400"})]})}function Qj(a){if(!a)return"Unassigned";if(a.kind==="axisPair"){const l=`${a.invertX?"X inv ":""}${a.invertY?"Y inv":""}`.trim();return`Axes ${a.x}/${a.y}${l?` (${l})`:""}`}if(a.kind==="axis")return`Axis ${a.index}${a.invert?" (inv)":""}`;if(a.kind==="button")return`Button ${a.index}`;if(a.kind==="buttonAxis")return`Button ${a.index} (analog)`;if(a.kind==="axisButton"){const l=a.direction<0?"<":">";return`Axis ${a.index} ${l} ${Kn.format(a.threshold??.6)}`}return"Unassigned"}function Kj(a){return a.reduce((l,i)=>((l[i.section]||(l[i.section]=[])).push(i),l),{})}function Fj(a,l){if(!a||a.length===0)return null;if(l){const i=a.find(o=>o.signature===l);if(i)return i}return a[0]}function Zj(a){return{axes:Array.from(a.axes??[]),buttons:(a.buttons??[]).map(l=>({pressed:!!l?.pressed,value:typeof l?.value=="number"?l.value:l?.pressed?1:0}))}}function Jj(a,l,i){const o=(a.axes??[]).map((d,h)=>({index:h,delta:Math.abs((d??0)-(l.axes?.[h]??0)),value:d}));if(o.sort((d,h)=>h.delta-d.delta),i.kind==="axisPair"){const d=o.filter(h=>h.delta>Up).slice(0,2);return d.length<2?null:{kind:"axisPair",x:d[0].index,y:d[1].index,...i.invertDefaults??{}}}const f=o.find(d=>d.delta>Up);return f?{kind:"axis",index:f.index,...i.invertDefaults??{}}:null}function Ij(a,l,i){const o=a.buttons??[];for(let d=0;dXj)return i.kind==="axis"?{kind:"buttonAxis",index:d}:{kind:"button",index:d}}const f=a.axes??[];for(let d=0;d.7&&p>.5)return i.kind==="axis"?{kind:"axis",index:d,...i.invertDefaults??{}}:{kind:"axisButton",index:d,direction:h>=0?1:-1,threshold:.6}}return null}function Wj(a,l,i){if(!a)return null;if(i.kind==="axis"||i.kind==="axisPair"){const o=Jj(a,l,i);if(o)return o}return Ij(a,l,i)}function Pj(){const a=qA(),{value:l,save:i}=st("gamepad",ja),[o,f]=m.useState(null),d=m.useRef(null),h=m.useMemo(()=>Kj(Gj),[]),p=m.useMemo(()=>Fj(a.pads,l.activeSignature),[a.pads,l.activeSignature]),g=p?.signature??null,v=m.useMemo(()=>g?l?.profiles?.[g]??l?.defaults?.profile??Fn:l?.defaults?.profile??Fn,[g,l?.defaults?.profile,l?.profiles]);m.useEffect(()=>{!p||!g||l?.profiles?.[g]||i(N=>{const E=N??ja;if(E.profiles?.[g])return E;const S=E?.defaults?.profile??Fn,T=Gx(p,S);return{...E,profiles:{...E.profiles??{},[g]:T}}})},[p,g,l?.profiles,i]),m.useEffect(()=>{d.current=null},[o,g]),m.useEffect(()=>{if(!o||!p)return;if(!d.current){d.current=Zj(p);return}const N=Wj(p,d.current,o);N&&(i(E=>{const S=E??ja,T=S.profiles?.[g]??S?.defaults?.profile??Fn,B={...T,bindings:{...T.bindings??{},[o.id]:{...T.bindings?.[o.id]??{},kind:o.kind,sources:[N]}}};return{...S,profiles:{...S.profiles??{},[g]:B}}}),f(null))},[p,g,o,i]);const y=m.useCallback(N=>{i(E=>({...E??ja,activeSignature:N||null}))},[i]),b=m.useCallback(N=>{i(E=>{const S=E??ja,T=(g&&S.profiles?.[g])??S?.defaults?.profile??Fn,B={...T,calibration:{...T.calibration??{},...N}};return g?{...S,profiles:{...S.profiles??{},[g]:B}}:{...S,defaults:{...S.defaults??{},profile:B}}})},[g,i]),j=m.useCallback((N,E)=>{i(S=>{const T=S??ja,B=(g&&T.profiles?.[g])??T?.defaults?.profile??Fn,L=E(B.bindings?.[N]??{}),$={...B,bindings:{...B.bindings??{},[N]:L}};return g?{...T,profiles:{...T.profiles??{},[g]:$}}:{...T,defaults:{...T.defaults??{},profile:$}}})},[g,i]),M=m.useCallback(N=>{j(N.id,E=>({...E,sources:[]}))},[j]),w=m.useCallback((N,E)=>{j(N.id,S=>{const T=Array.isArray(S.sources)?[...S.sources]:[];if(!T[0])return S;const B={...T[0]};return E==="invertX"?B.invertX=!B.invertX:E==="invertY"?B.invertY=!B.invertY:B.invert=!B.invert,T[0]=B,{...S,sources:T}})},[j]),C=m.useMemo(()=>p?{outputs:Xx(p,v)}:null,[p,v]);return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("div",{className:"flex items-center justify-between",children:r.jsxs("div",{children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Controller"}),r.jsx("p",{className:"text-[0.65rem] text-slate-500",children:p?"Move sticks or press buttons to bind":"Connect a controller to configure."}),o&&r.jsxs("p",{className:"mt-0 text-[0.7rem] text-emerald-400",children:["Capturing ",o.label,"…"]})]})}),r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Connected controller"}),a.pads.length===0?r.jsx("p",{className:"text-[0.7rem] text-slate-400",children:"No controller detected."}):r.jsxs("div",{className:"flex items-center justify-between gap-0.5 text-xs",children:[r.jsx("select",{value:g??"",onChange:N=>y(N.target.value),className:"flex-1 rounded border border-slate-700 bg-slate-900 px-1 py-[2px] text-[0.75rem] text-slate-100",children:a.pads.map(N=>r.jsx("option",{value:N.signature,children:N.id||"Unknown controller"},N.signature))}),r.jsx("span",{className:"text-[0.7rem] text-slate-400",children:p?.mapping??"unknown"})]})]}),r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Calibration"}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(Ps,{label:"Drive deadzone",description:"Ignore small drive stick drift",min:0,max:.6,step:.01,value:v.calibration?.driveDeadzone??.18,onChange:N=>b({driveDeadzone:N})}),r.jsx(Ps,{label:"Camera deadzone",description:"Ignore small camera tilt drift",min:0,max:.4,step:.01,value:v.calibration?.cameraDeadzone??.08,onChange:N=>b({cameraDeadzone:N})}),r.jsx(Ps,{label:"Aux deadzone",description:"Ignore small trigger noise",min:0,max:.4,step:.01,value:v.calibration?.auxDeadzone??.05,onChange:N=>b({auxDeadzone:N})}),r.jsx(Ps,{label:"Side brush scale",description:"Scale side brush output",min:.3,max:1,step:.05,value:v.calibration?.auxSideScale??.55,onChange:N=>b({auxSideScale:N})}),r.jsxs("label",{className:"surface-muted block p-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-300",children:[r.jsx("span",{className:"font-semibold text-slate-100",children:"Camera mode"}),r.jsxs("select",{value:v.calibration?.cameraMode??"absolute",onChange:N=>b({cameraMode:N.target.value}),className:"rounded border border-slate-700 bg-slate-900 px-1 py-[2px] text-[0.75rem] text-slate-100",children:[r.jsx("option",{value:"absolute",children:"Absolute"}),r.jsx("option",{value:"velocity",children:"Velocity"})]})]}),r.jsx("p",{className:"text-[0.65rem] text-slate-500",children:"Absolute maps stick to angle; velocity moves over time."})]}),r.jsx(Ps,{label:"Camera sensitivity",description:"Velocity mode degrees per second",min:10,max:180,step:5,value:v.calibration?.cameraSensitivity??60,onChange:N=>b({cameraSensitivity:N})})]})]}),r.jsx("div",{className:"space-y-0.5",children:Object.entries(h).map(([N,E])=>r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:N}),r.jsx("div",{className:"space-y-0.5",children:E.map(S=>{const B=v.bindings?.[S.id]?.sources?.[0]??null;return r.jsxs("div",{className:"surface-muted flex items-center justify-between text-xs",children:[r.jsxs("div",{children:[r.jsx("p",{className:"font-semibold text-slate-100",children:S.label}),r.jsx("p",{className:"text-[0.65rem] text-slate-400",children:Qj(B)})]}),r.jsxs("div",{className:"flex items-center gap-0.5",children:[S.kind==="axisPair"&&r.jsxs(r.Fragment,{children:[r.jsx("button",{type:"button",disabled:!B,onClick:()=>w(S,"invertX"),className:"button-dark text-[0.7rem] font-medium disabled:opacity-50",children:"Invert X"}),r.jsx("button",{type:"button",disabled:!B,onClick:()=>w(S,"invertY"),className:"button-dark text-[0.7rem] font-medium disabled:opacity-50",children:"Invert Y"})]}),S.kind==="axis"&&r.jsx("button",{type:"button",disabled:!B,onClick:()=>w(S),className:"button-dark text-[0.7rem] font-medium disabled:opacity-50",children:"Invert"}),r.jsx("button",{type:"button",onClick:()=>M(S),className:"button-dark text-[0.7rem]",children:"Clear"}),r.jsx("button",{type:"button",onClick:()=>f(S),className:`${o?.id===S.id?"px-0.5 py-0.5 bg-emerald-500 text-emerald-950 hover:bg-emerald-400":"button-dark"} text-[0.7rem] font-medium`,children:o?.id===S.id?"Waiting…":"Capture"})]})]},S.id)})})]},N))}),r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"Diagnostics"}),p?r.jsxs("div",{className:"space-y-0.5 text-[0.7rem] text-slate-300",children:[r.jsx("p",{className:"text-slate-400",children:"Raw axes"}),r.jsx("div",{className:"grid grid-cols-2 gap-0.5",children:p.axes.map((N,E)=>r.jsxs("span",{className:"font-mono text-slate-300",children:["A",E,": ",Kn.format(N)]},`axis-${E}`))}),r.jsx("p",{className:"text-slate-400",children:"Raw buttons"}),r.jsx("div",{className:"grid grid-cols-2 gap-0.5",children:p.buttons.map((N,E)=>r.jsxs("span",{className:"font-mono text-slate-300",children:["B",E,": ",Kn.format(N.value)," ",N.pressed?"●":""]},`btn-${E}`))}),C?.outputs&&r.jsxs(r.Fragment,{children:[r.jsx("p",{className:"text-slate-400",children:"Mapped outputs"}),r.jsxs("div",{className:"grid grid-cols-2 gap-0.5",children:[r.jsxs("span",{className:"font-mono text-slate-300",children:["Drive: ",Kn.format(C.outputs.driveVector.x),","," ",Kn.format(C.outputs.driveVector.y)]}),r.jsxs("span",{className:"font-mono text-slate-300",children:["Camera: ",Kn.format(C.outputs.cameraAxis)]}),r.jsxs("span",{className:"font-mono text-slate-300",children:["Main: ",Kn.format(C.outputs.auxAxis.main)]}),r.jsxs("span",{className:"font-mono text-slate-300",children:["Side: ",Kn.format(C.outputs.auxAxis.side)]})]})]})]}):r.jsx("p",{className:"text-[0.7rem] text-slate-400",children:"No controller detected."})]})]})}const eN={drive:"Drive wheels",aux:"Aux motors"};function Hp(a){return Number.isFinite(a)?`${Math.round(a*100)}%`:"--"}function tN({value:a,color:l="bg-emerald-500"}){const i=`${Math.round(Math.max(0,Math.min(1,a))*100)}%`;return r.jsx("div",{className:"h-2 w-full overflow-hidden rounded bg-slate-800",children:r.jsx("div",{className:`h-full ${l}`,style:{width:i}})})}function nN(){const{state:{roverId:a},overcurrentLimiter:l}=Dt(),i=m.useMemo(()=>Ql.map(o=>o.key),[]);return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Overcurrent limiter"}),r.jsx("span",{children:l?.adminImmune?"Admin immune":"Active"})]}),a?r.jsxs("div",{className:"space-y-0.5",children:[i.map(o=>{const f=l?.caps?.[o]?.cap??0,d=l?.overcurrent?.groups?.[o]??!1,h=l?.scales?.perGroup?.[o]??1;return r.jsxs("div",{className:"surface space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs",children:[r.jsx("span",{className:"text-slate-200",children:eN[o]||o}),r.jsx("span",{className:d?"text-red-300":"text-slate-400",children:d?"overcurrent":"ok"})]}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-[0.7rem] text-slate-400",children:[r.jsx("span",{children:"Cap"}),r.jsx("span",{children:Hp(f)})]}),r.jsx(tN,{value:f,color:"bg-amber-500"}),r.jsxs("div",{className:"flex items-center justify-between text-[0.7rem] text-slate-400",children:[r.jsx("span",{children:"Scale"}),r.jsx("span",{children:Hp(h)})]})]})]},o)}),r.jsxs("div",{className:"surface text-[0.7rem] text-slate-400",children:[r.jsx("div",{children:`Down rate ${l?.config?.downRatePerSec}/s · Up rate ${l?.config?.upRatePerSec}/s`}),r.jsx("div",{children:`Release delay ${l?.config?.releaseDelaySec}s`}),r.jsx("div",{children:`Output rate ${l?.config?.outputRateMs}ms`})]})]}):r.jsx("p",{className:"text-xs text-slate-500",children:"Assign a rover to view limiter status."})]})}const og=m.createContext(null);function cg(){const a=m.useContext(og);if(!a)throw new Error("Tabs components must be used within a provider.");return a}const Vp={primary:{base:"flex-1 px-0.5 py-0.5 text-sm font-medium transition-colors focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-slate-500 rounded-md",active:"bg-sky-600 text-white",inactive:"bg-zinc-900 text-slate-300 hover:bg-sky-500 hover:text-white"}},ug="primary";function Jf(...a){return a.filter(Boolean).join(" ")}function If({children:a,defaultTab:l,currentTab:i,onTabChange:o,variant:f=ug}){const[d,h]=m.useState(l??null),[p,g]=m.useState([]),v=m.useCallback(M=>M?(g(w=>w.includes(M)?w:[...w,M]),()=>g(w=>w.includes(M)?w.filter(C=>C!==M):w)):()=>{},[]),y=i??d??p[0]??null;m.useEffect(()=>{l&&i===void 0&&h(l)},[l,i]);const b=m.useCallback(M=>{M&&(i===void 0&&h(M),o&&o(M))},[i,o]),j=m.useMemo(()=>({activeTab:y,setActiveTab:b,registerTab:v,variant:f}),[y,b,v,f]);return r.jsx(og.Provider,{value:j,children:a})}function Wf({children:a,className:l=""}){return r.jsx("div",{className:Jf("flex gap-0.5",l),children:a})}function mn({id:a,children:l,className:i="",disabled:o=!1}){const{activeTab:f,setActiveTab:d,registerTab:h,variant:p}=cg();m.useEffect(()=>h(a),[a,h]);const g=Vp[p]??Vp[ug],v=f===a,y=Jf(g.base,v?g.active:g.inactive,o?"cursor-not-allowed opacity-50":"",i);return r.jsx("button",{type:"button",className:y,onClick:()=>!o&&d(a),"aria-pressed":v,"aria-disabled":o,children:l})}function Pf({children:a,className:l=""}){return r.jsx("div",{className:Jf("mt-0 space-y-0.5",l),children:a})}function hn({id:a,children:l,keepMounted:i=!1}){const{activeTab:o}=cg(),f=o===a;return!f&&!i?null:!f&&i?r.jsx("div",{hidden:!0,"aria-hidden":"true",children:l}):r.jsx(r.Fragment,{children:l})}function aN(){const{session:a}=Ve(),l=m.useMemo(()=>JSON.stringify(a??{},null,2),[a]);return r.jsxs("div",{className:"panel-section flex min-h-0 flex-col gap-0.5 text-xs",children:[r.jsx("p",{className:"text-sm text-slate-400",children:"Session snapshot"}),r.jsx("pre",{className:"surface min-h-0 flex-1 overflow-y-auto font-mono text-[0.7rem] text-lime-300",children:l})]})}const Yp=300,$p=240;function lN(a,l){if(a===void 0)return"undefined";if(a===null)return"null";if(typeof a=="string")return a;if(typeof a=="number"||typeof a=="boolean")return String(a);if(typeof a=="function")return"[Function]";if(a instanceof Error)return`${a.name}: ${a.message}`;if(a instanceof Date)return a.toISOString();if(l||(l=new WeakSet),typeof a=="object"){if(l.has(a))return"[Circular]";l.add(a)}try{return JSON.stringify(a,(i,o)=>typeof o=="function"?"[Function]":o instanceof Error?`${o.name}: ${o.message}`:o,0)}catch{return String(a)}}function sN(a){const i=a.map(o=>lN(o)).join(" ");return i.length>$p?`${i.slice(0,$p)}…`:i}function iN(){const a=En(),[l,i]=m.useState([]),o=m.useMemo(()=>l.reduce((f,d)=>(d.direction==="in"&&(f.in+=1),d.direction==="out"&&(f.out+=1),f),{in:0,out:0}),[l]);return m.useEffect(()=>{const f=(g,v,y)=>{i(b=>{const j=[...b,{id:`${Date.now()}-${Math.random().toString(16).slice(2)}`,direction:g,event:v,payload:sN(y),timestamp:new Date().toLocaleTimeString()}];return j.length>Yp?j.slice(-Yp):j})},d=(g,...v)=>f("in",g,v),h=(g,...v)=>f("out",g,v);a.onAny(d);let p=null;if(typeof a.onAnyOutgoing=="function")a.onAnyOutgoing(h),p=()=>a.offAnyOutgoing(h);else{const g=a.emit.bind(a);a.emit=(v,...y)=>(h(v,...y),g(v,...y)),p=()=>{a.emit=g}}return()=>{a.offAny(d),p&&p()}},[a]),r.jsxs("section",{className:"panel-section space-y-0.5 text-base",children:[r.jsxs("div",{className:"flex items-center justify-between text-sm text-slate-400",children:[r.jsx("span",{children:"Socket activity"}),r.jsxs("div",{className:"flex items-center gap-0.5 text-xs text-slate-400",children:[r.jsxs("span",{className:"surface-muted",children:["in ",o.in]}),r.jsxs("span",{className:"surface-muted",children:["out ",o.out]}),r.jsx("button",{type:"button",className:"button-dark text-xs",onClick:()=>i([]),children:"Clear"})]})]}),r.jsx("div",{className:"surface h-64 overflow-y-auto font-mono text-xs",children:l.length===0?r.jsx("p",{children:"No socket activity yet."}):l.slice().reverse().map(f=>r.jsxs("div",{className:"surface",children:[r.jsx("span",{className:"text-slate-400",children:f.timestamp})," ",r.jsxs("span",{className:f.direction==="in"?"text-emerald-400":"text-amber-400",children:["[",f.direction.toUpperCase(),"]"]})," ",r.jsx("span",{className:"text-cyan-300",children:f.event})," ",r.jsx("span",{className:"text-slate-200",children:f.payload})]},f.id))}),r.jsx("p",{className:"text-xs text-slate-500",children:"Logs are collected only while this panel is open."})]})}const rN=[{key:"start",label:"Start OI"},{key:"safe",label:"Safe"},{key:"full",label:"Full"},{key:"passive",label:"Passive"},{key:"dock",label:"Dock"}];function fg(){const{state:{roverId:a},actions:{sendOiCommand:l,setSensorStream:i}}=Dt(),o=!!a,[f,d]=lg(),h=En(),{value:p,save:g}=st("page",{hudMapDesktop:!1,connectionTransport:"websocket"}),{value:v,save:y}=st("audio",jt),b=p?.connectionTransport||"websocket",j=Number.isFinite(v?.masterVolume)?v.masterVolume:jt.masterVolume,M=Number.isFinite(v?.alertVolume)?v.alertVolume:jt.alertVolume,w=Number.isFinite(v?.roverVolume)?v.roverVolume:jt.roverVolume,C=typeof v?.mainBrushDuckEnabled=="boolean"?v.mainBrushDuckEnabled:typeof v?.autoLevelEnabled=="boolean"?v.autoLevelEnabled:jt.mainBrushDuckEnabled,N=Number.isFinite(v?.mainBrushDuckAmount)?Math.max(0,Math.min(1,v.mainBrushDuckAmount)):jt.mainBrushDuckAmount,E=m.useMemo(()=>[{key:"start",label:"Enable stream",enable:!0},{key:"stop",label:"Disable stream",enable:!1}],[]),S=$=>{a&&i($)},T=$=>{const _=$.target.value;g(V=>({...V??{},connectionTransport:_})),h?.io?.opts&&(h.io.opts.transports=_==="polling"?["polling"]:["websocket","polling"],h.disconnect(),h.connect())},B=$=>_=>{const V=Number(_.target.value),G=Number.isFinite(V)?Math.max(0,Math.min(1,V)):0;y(Z=>({...Z??{},[$]:G}))},L=$=>{const _=!!$.target.checked;y(V=>({...V??{},mainBrushDuckEnabled:_}))};return r.jsxs(If,{defaultTab:"keybindings",children:[r.jsxs(Wf,{children:[r.jsx(mn,{id:"keybindings",children:"Keybindings"}),r.jsx(mn,{id:"controller",children:"Controller"}),r.jsx(mn,{id:"page",children:"Page settings"}),r.jsx(mn,{id:"admin",children:"Admin"})]}),r.jsxs(Pf,{children:[r.jsx(hn,{id:"keybindings",children:r.jsx("div",{className:"space-y-0.5",children:r.jsx($j,{})})}),r.jsx(hn,{id:"controller",children:r.jsx("div",{className:"space-y-0.5",children:r.jsx(Pj,{})})}),r.jsx(hn,{id:"page",children:r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("p",{className:"text-slate-400",children:"HUD"}),r.jsxs("label",{className:"flex items-center gap-0.5 text-slate-200",children:[r.jsx("input",{type:"checkbox",className:"accent-emerald-500",checked:f,onChange:$=>d($.target.checked)}),r.jsx("span",{children:"Show top-down map in HUD (desktop)"})]}),r.jsx("p",{className:"text-xs text-slate-500",children:"Mobile HUD keeps the map on by default."})]}),r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("p",{className:"text-slate-400",children:"Audio"}),r.jsxs("label",{className:"grid gap-0.5 text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"Master volume"}),r.jsxs("span",{className:"text-xs text-slate-400",children:[Math.round(j*100),"%"]})]}),r.jsx("input",{type:"range",min:"0",max:"1",step:"0.01",value:j,onChange:B("masterVolume"),className:"w-full accent-emerald-500"})]}),r.jsxs("label",{className:"grid gap-0.5 text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"Alert/page sounds"}),r.jsxs("span",{className:"text-xs text-slate-400",children:[Math.round(M*100),"%"]})]}),r.jsx("input",{type:"range",min:"0",max:"1",step:"0.01",value:M,onChange:B("alertVolume"),className:"w-full accent-emerald-500"})]}),r.jsxs("label",{className:"grid gap-0.5 text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"Rover audio"}),r.jsxs("span",{className:"text-xs text-slate-400",children:[Math.round(w*100),"%"]})]}),r.jsx("input",{type:"range",min:"0",max:"1",step:"0.01",value:w,onChange:B("roverVolume"),className:"w-full accent-emerald-500"})]}),r.jsxs("label",{className:"flex items-center justify-between gap-0.5 text-slate-200",children:[r.jsx("span",{children:"Main brush ducking"}),r.jsx("input",{type:"checkbox",className:"accent-emerald-500",checked:C,onChange:L})]}),r.jsxs("label",{className:"grid gap-0.5 text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"Main brush duck amount"}),r.jsxs("span",{className:"text-xs text-slate-400",children:[Math.round(N*100),"%"]})]}),r.jsx("input",{type:"range",min:"0",max:"1",step:"0.01",value:N,onChange:B("mainBrushDuckAmount"),className:"w-full accent-emerald-500",disabled:!C})]}),r.jsx("p",{className:"text-xs text-slate-500",children:"Lowers rover audio only while the main brush is running."})]}),r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("p",{className:"text-slate-400",children:"Connection"}),r.jsxs("label",{className:"flex items-center justify-between gap-0.5 text-slate-200",children:[r.jsx("span",{children:"Transport"}),r.jsxs("select",{value:b,onChange:T,className:"field-input text-sm",children:[r.jsx("option",{value:"websocket",children:"WebSocket"}),r.jsx("option",{value:"polling",children:"Polling"})]})]}),r.jsx("p",{className:"text-xs text-slate-500",children:"Switching reconnects your session."})]})]})}),r.jsx(hn,{id:"admin",children:r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("p",{className:"text-slate-400",children:"Manual OI commands"}),r.jsx("div",{className:"surface flex flex-wrap gap-0.5",children:rN.map($=>r.jsx("button",{type:"button",onClick:()=>l($.key),disabled:!o,className:"button-dark text-xs disabled:opacity-30",children:$.label},$.key))})]}),r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsx("p",{className:"text-slate-400",children:"Sensor stream"}),r.jsx("div",{className:"surface flex gap-0.5",children:E.map($=>r.jsx("button",{type:"button",onClick:()=>S($.enable),disabled:!o,className:"flex-1 button-dark text-xs disabled:opacity-30",children:$.label},$.key))}),!o&&r.jsx("p",{className:"text-xs text-slate-500",children:"Assign a rover to toggle streams."})]}),r.jsx(rg,{}),r.jsx(nN,{}),r.jsx(kj,{}),r.jsx(aN,{}),r.jsx(iN,{})]})})]})]})}const Gp={desktop:{main:[{type:"list",title:"Chat and nicknames",items:["Set a nickname in the user list panel, on the bottom left of the page below the rover video.",{segments:["Toggle chat focus with ",{action:"chatFocus"},". Press ",{action:"chatFocus"}," again to send."]}]},{type:"list",title:"Driving the rover",items:[{segments:['Press the "Start Driving" button onscreen, or press ',{action:"driveMacro"}," on your keyboard to put the rover into driving mode."]},"Refer to the controls for the controls for the rover."]},{type:"list",title:"Docking the rover",items:["Line up the rover to the dock, about a foot away, then:",{segments:['Press the "Dock and Charge" button onscreen, or press ',{action:"dockMacro"}," on your keyboard to start docking."]},"Wait for the rover to confirm it is docked and charging before leaving it unattended."]}],aside:[{type:"keyboard",title:"Keyboard controls",footnote:"Per-browser; adjust bindings and speeds in Settings → Keybindings.",groups:[{id:"movement",title:"Movement",items:[{action:"driveForward",label:"Forward"},{action:"driveBackward",label:"Backward"},{action:"driveLeft",label:"Turn left"},{action:"driveRight",label:"Turn right"},{action:"boostModifier",label:"Boost speed"},{action:"slowModifier",label:"Precision speed"}]},{id:"macros",title:"Rover modes & chat",items:[{action:"driveMacro",label:"Drive macro"},{action:"dockMacro",label:"Dock macro"},{action:"chatFocus",label:"Chat focus"}]},{id:"camera",title:"Camera",items:[{action:"cameraUp",label:"Tilt up"},{action:"cameraDown",label:"Tilt down"},{action:"nightVisionToggle",label:"Toggle night vision"}]},{id:"motors",title:"Rover Aux Motors",items:[{action:"auxMainForward",label:"Main brush forward"},{action:"auxMainReverse",label:"Main brush reverse"},{action:"auxSideForward",label:"Side brush forward"},{action:"auxSideReverse",label:"Side brush reverse"},{action:"auxVacuumFast",label:"Vacuum max"},{action:"auxVacuumSlow",label:"Vacuum low"},{action:"auxAllForward",label:"All motors forward"}]},{id:"audio",title:"Audio / Song",items:[{action:"songNoteUp",label:"Song note up"},{action:"songNoteDown",label:"Song note down"}]},{id:"room-controls",title:"Room Controls",items:[{action:"homeAssistantOn",label:"Next room control on"},{action:"homeAssistantOff",label:"Next room control off (reverse)"}]}]},{type:"gamepad",title:"Gamepad / joystick",items:["Gamepad controls are fully configurable to handle inconsistent browser mappings.","Use Settings → Controller to bind inputs, calibrate deadzones, and view diagnostics.","If inputs behave oddly, the diagnostics panel will show raw axis/button values."]}]},"mobile-portrait":{main:[{type:"list",title:"Chat and nicknames",items:["Set a nickname in the user list panel below.","Tap in the chat box to send messages in the chat."]},{type:"list",title:"Driving the rover",items:[{segments:['Press the "Start Driving" button onscreen to put the rover into driving mode.']},"Look below the rover video. Use the joystick on the right to move the rover, and hold the buttons on the left to run the aux motors."]},{type:"list",title:"Docking the rover",items:["Line up the rover to the dock, about a foot away, then:",{segments:['Press the "Dock and Charge" button onscreen to start docking.']},"Wait for the rover to confirm it is docked and charging before leaving it unattended."]}]},"mobile-landscape":{main:[{type:"list",title:"Chat and nicknames",items:["Scroll down to see more of the page.","Set a nickname in the user list panel below.","Tap in the chat box to send messages in the chat."]},{type:"list",title:"Driving the rover",items:[{segments:['Press the "Start Driving" button, or the "Drive" button to put the rover into driving mode.']},"Use the joystick to the right of the video feed to move the rover, and hold the buttons on the left to run the aux motors."]},{type:"list",title:"Docking the rover",items:["Line up the rover to the dock, about a foot away, then:",{segments:['Press the "Dock and Charge" or the "Dock" button onscreen to start docking.']},"Wait for the rover to confirm it is docked and charging before leaving it unattended."]}]}};function oN(a){return Gp[a]||Gp.desktop}function dg({actionId:a,keymap:l}){const i=l?.[a]?.[0]??"";return r.jsx("span",{className:"rounded border border-slate-600 bg-slate-900/40 px-1 text-[0.7rem] text-slate-200",children:An(i)})}function Xp(a,l){return a.map((i,o)=>typeof i=="string"?r.jsx("span",{children:i},`text-${o}`):i?.action?r.jsx(dg,{actionId:i.action,keymap:l},`pill-${i.action}-${o}`):i?.text?r.jsx("span",{children:i.text},`span-${o}`):null)}function oo(a,l,i){if(typeof a=="string")return a;if(Array.isArray(a?.segments))return Xp(a.segments,l);if(a&&typeof a=="object"){if(typeof a.text=="string")return a.text;if(Array.isArray(a))return Xp(a,l)}return String(a??"")}function cN({hero:a,keymap:l}){return a?r.jsxs("div",{className:"surface space-y-0.5 px-0.5 py-0.5",children:[r.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-0.5",children:[r.jsxs("div",{children:[r.jsx("p",{className:"text-sm font-semibold text-white",children:a.title}),a.subtitle&&r.jsx("p",{className:"text-xs text-slate-300",children:a.subtitle})]}),a.chips&&r.jsx("div",{className:"flex flex-wrap gap-0.5 text-[0.7rem] text-slate-200",children:a.chips.map(i=>r.jsx("span",{className:"rounded border border-slate-700 px-1 py-[2px]",children:i},i))})]}),a.bullets&&r.jsx("ul",{className:"space-y-0.5 text-[0.85rem] text-slate-200",children:a.bullets.map((i,o)=>{const f=Array.isArray(i?.segments)?`hero-${o}`:`hero-${o}`;return r.jsx("li",{className:"surface-muted px-0.5 py-0.25",children:oo(i,l)},f)})})]}):null}function uN({block:a,keymap:l}){return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs font-semibold text-slate-200",children:a.title}),r.jsx("ul",{className:"space-y-0.5 text-[0.8rem] text-slate-300",children:a.items.map((i,o)=>{const f=`item-${o}`;return r.jsx("li",{className:"surface-muted flex flex-wrap items-center gap-0.5 px-0.5 py-0.25",children:oo(i,l)},f)})})]})}function fN({block:a}){const l=a.tone==="info"?"border-cyan-500/40":"border-slate-700";return r.jsxs("div",{className:`surface space-y-0.5 border ${l} px-0.5 py-0.5`,children:[r.jsx("p",{className:"text-xs font-semibold text-slate-100",children:a.title}),a.body&&r.jsx("ul",{className:"space-y-0.5 text-[0.8rem] text-slate-300",children:a.body.map((i,o)=>r.jsx("li",{className:"surface-muted px-0.5 py-0.25",children:oo(i,{})},`callout-${o}`))})]})}function dN({group:a,keymap:l}){return r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"px-0.5 py-0.25 text-[0.75rem] font-semibold text-slate-200",children:a.title}),r.jsx("div",{className:"space-y-0.5 px-0.5 pb-0.25",children:a.items.map(i=>r.jsxs("div",{className:"surface-muted flex items-center justify-between gap-0.5 px-0.5 py-0.25 text-[0.8rem]",children:[r.jsx("span",{className:"text-slate-200",children:i.label}),r.jsx(dg,{actionId:i.action,keymap:l})]},i.action))})]})}function mN({block:a,keymap:l}){return a?r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-200",children:[r.jsx("span",{className:"font-semibold",children:a.title}),a.footnote&&r.jsx("span",{className:"text-[0.7rem] text-slate-400",children:a.footnote})]}),r.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-0.5",children:a.groups?.map(i=>r.jsx(dN,{group:i,keymap:l},i.id))})]}):null}function hN({block:a}){return a?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs font-semibold text-slate-200",children:a.title}),r.jsx("ul",{className:"space-y-0.5 text-[0.8rem] text-slate-300",children:a.items?.map((l,i)=>r.jsx("li",{className:"surface-muted px-0.5 py-0.25",children:oo(l,{})},`gamepad-${i}`))})]}):null}function Qp({block:a,keymap:l}){if(!a)return null;switch(a.type){case"list":return r.jsx(uN,{block:a,keymap:l});case"callout":return r.jsx(fN,{block:a});case"keyboard":return r.jsx(mN,{block:a,keymap:l});case"gamepad":return r.jsx(hN,{block:a});default:return null}}function mg({layout:a,keymap:l}){const i=m.useMemo(()=>oN(a),[a]),o=l||{},f=i.main||[],d=i.aside||[];return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(cN,{hero:i.hero,keymap:o}),r.jsxs("div",{className:"grid gap-0.5",style:{gridTemplateColumns:"repeat(auto-fit, minmax(360px, 1fr))"},children:[r.jsx("div",{className:"space-y-0.5",children:f.map((h,p)=>r.jsx(Qp,{block:h,keymap:o},h.title||p))}),r.jsx("div",{className:"space-y-0.5",children:d.map((h,p)=>r.jsx(Qp,{block:h,keymap:o},h.title||p))})]})]})}function hg({layout:a,onOpenOverlay:l}){const{state:i}=Dt();return r.jsxs("section",{className:"panel-section space-y-0.5 text-sm",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Help"}),r.jsx("button",{type:"button",onClick:l,className:"button-dark px-1 py-0.25 text-[0.75rem]",children:"Open full help"})]}),r.jsx(mg,{layout:a,keymap:i?.keymap||{}})]})}const pN=["kal","rms","slt","ksp","bdl"],xN=Array.from({length:10},(a,l)=>l*10);function co({hideInput:a=!1,hideSpectatorNotice:l=!1,fillHeight:i=!1,allowSpectatorInput:o=!1}){const{session:f}=Ve(),{messages:d,typing:h,sendMessage:p,registerInputRef:g,onInputFocus:v,onInputBlur:y,blurChat:b,setTypingActive:j}=Uf(),{value:M,save:w}=st("tts",{engine:"flite",voice:"rms",pitch:50}),[C,N]=m.useState(""),[E,S]=m.useState(!1),[T,B]=m.useState(!1),[L,$]=m.useState(()=>M?.engine||"flite"),[_,V]=m.useState(()=>M?.voice||"rms"),[G,Z]=m.useState(()=>Number.isFinite(M?.pitch)?M.pitch:50),F=f?.role!=="spectator"||o,ae=m.useRef(null),ce=f?.assignment?.roverId||null,X=!!m.useMemo(()=>f?.roster?.find(q=>String(q.id)===String(ce))||null,[ce,f?.roster])?.audio?.ttsEnabled,k=m.useMemo(()=>d.slice(-200),[d]),Q=m.useMemo(()=>h||[],[h]);m.useEffect(()=>{ae.current&&(ae.current.scrollTop=ae.current.scrollHeight)},[k,Q]),m.useEffect(()=>{const q=M?.engine||"flite",U=M?.voice||"rms",te=Number.isFinite(M?.pitch)?M.pitch:50;L!==q&&$(q),_!==U&&V(U),G!==te&&Z(te)},[L,G,M?.engine,M?.pitch,M?.voice,_]),m.useEffect(()=>{B(!!X)},[X]);const P=m.useMemo(()=>!X||!T?null:L==="espeak"?{speak:!0,engine:L,pitch:G}:{speak:!0,engine:L,voice:_},[L,G,T,X,_]);async function oe(q){if(q.preventDefault(),!F||a)return;const te=C.replace(/\\n/g,` `).trim();if(te){S(!0);try{await p(te,P),N(""),b(),j(!1)}catch(le){alert(le.message)}finally{S(!1)}}}const ue=i?"flex-1 min-h-0 overflow-y-auto":"h-48 overflow-y-auto";return r.jsxs("section",{className:`panel-section space-y-0.5 text-base ${i?"flex h-full flex-col overflow-hidden":""}`,children:[r.jsxs("div",{className:`surface overflow-y-auto space-y-0.5 px-0 ${ue}`,ref:ae,children:[k.length===0&&Q.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:"No messages yet."}):k.map(q=>r.jsx(Bf,{message:q},q.id)),Q.map(q=>r.jsx(Bx,{message:q},`typing-${q.typingId||q.id}`))]}),!a&&r.jsxs("form",{className:"flex flex-wrap items-stretch gap-0.5",onSubmit:oe,children:[r.jsx("input",{className:"field-input flex-1 min-w-[10rem]",value:C,onChange:q=>{const U=q.target.value;N(U),j(!!U.trim())},onFocus:q=>{v(q),j(!!C.trim())},onBlur:q=>{y(q),j(!1)},onKeyDown:q=>{q.key==="Enter"&&!C.trim()&&(q.preventDefault(),b(),j(!1))},ref:q=>g(q,{target:"panel"}),placeholder:F?"Type a message…":l?"":"Spectators cannot chat",disabled:!F}),X&&r.jsxs("div",{className:"flex flex-wrap items-center gap-0.5 basis-full sm:basis-auto",children:[r.jsxs("label",{className:"flex items-center gap-0.5 text-xs text-slate-300",children:[r.jsx("input",{type:"checkbox",checked:T,onChange:q=>B(q.target.checked),className:"accent-cyan-500"}),r.jsx("span",{children:"Speak"})]}),r.jsxs("select",{value:L,onChange:q=>{const U=q.target.value;$(U),w(te=>({...te||{},engine:U}))},className:"field-input text-xs",children:[r.jsx("option",{value:"flite",children:"flite"}),r.jsx("option",{value:"espeak",children:"espeak"})]}),L==="flite"?r.jsx("select",{value:_,onChange:q=>{const U=q.target.value;V(U),w(te=>({...te||{},voice:U}))},className:"field-input text-xs",children:pN.map(q=>r.jsx("option",{value:q,children:q},q))}):r.jsx("select",{value:G,onChange:q=>{const U=Number(q.target.value);Z(U),w(te=>({...te||{},pitch:U}))},className:"field-input text-xs",children:xN.map(q=>r.jsxs("option",{value:q,children:["pitch ",q]},q))})]}),r.jsx("button",{type:"submit",disabled:!F||E,className:"button-dark h-full disabled:opacity-50 self-stretch",children:E?"...":"Send"})]})]})}function ed({compact:a=!1}){const{session:l,setNickname:i}=Ve(),{value:o,save:f}=st("profile",{nickname:""}),[d,h]=m.useState(o.nickname||""),[p,g]=m.useState(!1),v=l?.role!=="spectator";m.useEffect(()=>{h(o.nickname||"")},[o.nickname]);async function y(b){if(b.preventDefault(),!v)return;const j=(d||"").trim().slice(0,32);if(j){g(!0);try{await i(j),f({nickname:j})}catch(M){alert(M.message)}finally{g(!1)}}}return r.jsxs("form",{className:"grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto] items-stretch gap-0.5",onSubmit:y,children:[r.jsx("input",{className:"field-input flex-1 min-w-0",value:d,onChange:b=>h(b.target.value),maxLength:32,placeholder:"Enter a nickname",disabled:!v}),r.jsx("button",{type:"submit",disabled:!v||p,className:"button-dark h-full shrink-0 whitespace-nowrap px-0.5 py-0 disabled:opacity-50",children:p?"Saving…":a?"Set":"Save"})]})}function gN(a){const l=Array.isArray(a?.socials)?a.socials:null;if(l&&l.length)return l;const i=[];return a?.discord?.invite&&i.push({id:"discord",label:"Discord",url:a.discord.invite}),a?.kofi?.link&&i.push({id:"kofi",label:"Ko-fi",url:a.kofi.link}),i}function vN(a){if(!a||typeof a!="object")return null;const l=a.id||a.service||a.key||a.name||null,i=a.label||a.title||a.name||l||"Link",o=a.url||a.link||a.href||null;return o?{id:l,label:i,url:o}:null}function pg({className:a=""}){const{session:l}=Ve(),i=gN(l).map(vN).filter(Boolean).slice(0,4);return i.length?r.jsx("div",{className:`grid grid-cols-2 grid-rows-2 gap-0.5 ${a}`,children:i.map(o=>r.jsx(Qf,{...o},`${o.id||o.label}-${o.url}`))}):null}function yN({compact:a=!1}){return r.jsx("section",{className:"panel-section flex h-full min-h-0 flex-col gap-0.5 text-base",children:r.jsx("div",{className:"surface flex w-full items-center px-0 py-0",children:r.jsx(ed,{compact:a})})})}function bN(){return r.jsx("section",{className:"panel-section flex h-full min-h-0 flex-col gap-0.5 text-base",children:r.jsx(pg,{className:"flex-1 min-h-0"})})}function Pu(...a){return a.filter(Boolean).join(" ")}function SN(a){const l=a?.batteryState?.percentDisplay;return l==null?"--":`${l}%`}function AN(a){return a?.batteryState?a.batteryState.urgentActive?"text-red-400":a.batteryState.warnActive?"text-amber-300":"text-emerald-300":"text-slate-400"}function jN(a){switch(a){case"admin":case"lockdown":case"lockdown-admin":return"text-amber-300";case"spectator":return"text-slate-400";default:return"text-sky-300"}}function NN(a,l){if(!a)return"";const i=a.nickname||a.socketId?.slice(0,6)||"unknown";return a.socketId&&a.socketId===l?`${i} (you)`:i}function uo({title:a="Rovers"}){const{session:l,requestControl:i}=Ve(),[o,f]=m.useState({}),[d,h]=m.useState(()=>Date.now()),p=m.useMemo(()=>l?.role&&l.role!=="spectator",[l?.role]),g=l?.roster??[],v=l?.turnQueues??{},y=l?.users??[],b=l?.socketId||null,j=m.useMemo(()=>Object.values(v||{}).some(N=>N?.deadline||N?.idleDeadline),[v]);m.useEffect(()=>{if(!j)return;const N=setInterval(()=>{h(Date.now())},1e3);return()=>clearInterval(N)},[j]);const M=m.useMemo(()=>{const N=new Set(g.map(S=>String(S.id))),E=Object.keys(v||{}).filter(S=>!N.has(String(S))).map(S=>({id:S,name:S,locked:!1,batteryState:null}));return[...g,...E]},[g,v]);async function w(N){if(N){f(E=>({...E,[N]:!0}));try{await i(N)}catch(E){alert(E.message)}finally{f(E=>({...E,[N]:!1}))}}}const C=N=>y.find(E=>E.socketId===N)||{socketId:N,nickname:null,role:null};return r.jsxs("div",{className:"space-y-0.5 text-sm",children:[a&&r.jsx("p",{className:"text-sm text-slate-400",children:a}),M.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:"No rovers registered."}):r.jsx("ul",{className:"space-y-0.5 text-sm",children:M.map(N=>{const E=String(N.id),S=v?.[E]||null,T=S?.queue||[],B=S?.idleDeadline||S?.deadline||null,L=B&&B>d?Math.ceil((B-d)/1e3):B?0:null,$=S?.current||null,_=$?T.findIndex(k=>k===$):-1,V=T.length>1?_>=0?T[(_+1)%T.length]:T[0]:null,G=!!(b&&$&&$===b),Z=!!(b&&V&&V===b),F=L!=null&&(G||Z),ae=!!N.locked,ce=N.lockReason?`locked: ${N.lockReason}`:"locked",he=ae?ce:o[E]?"...":"request",X=p&&!ae&&!o[E];return r.jsxs("li",{className:Pu("surface flex flex-wrap items-start justify-between gap-0.5",X&&"cursor-pointer",ae&&"bg-red-900/40"),onClick:()=>{X&&w(N.id)},children:[r.jsxs("div",{className:"flex min-w-0 flex-1 flex-col gap-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsxs("div",{className:"flex items-center gap-0.5",children:[r.jsx("p",{className:"text-slate-200",children:N.name}),F?r.jsx("span",{className:"rounded bg-slate-800 px-1 text-[0.7rem] text-slate-200",children:G?`${L}s left`:`Your turn in ${L}s`}):null]}),r.jsx("span",{className:Pu("text-[0.75rem] font-semibold",AN(N)),children:SN(N)})]}),T.length===0?r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"No queue."}):r.jsx("div",{className:"flex flex-wrap items-center gap-0.5",children:T.map((k,Q)=>{const P=C(k),oe=k===$,ue=!!(V&&k===V&&!oe),q=oe?"bg-sky-600 text-white ring-2 ring-amber-300":ue?"bg-emerald-700/60 text-emerald-100 ring-1 ring-emerald-300/70":"bg-slate-800 text-slate-200";return r.jsxs("span",{className:`flex items-center gap-0.5 rounded px-1 text-[0.7rem] ${q}`,children:[r.jsx("span",{className:`${jN(P.role)} font-semibold`,children:NN(P,b)}),oe&&r.jsx("span",{className:"text-[0.65rem] text-slate-200",children:"now"}),ue&&r.jsx("span",{className:"text-[0.65rem] text-emerald-100",children:"next"})]},`${E}-${k}-${Q}`)})})]}),p?r.jsx("button",{type:"button",onClick:k=>{k.stopPropagation(),w(N.id)},disabled:o[E]||ae,className:Pu("button-dark disabled:opacity-40",ae&&"bg-red-600/70 text-white hover:bg-red-600"),children:he}):null]},N.id)})})]})}function wN(a){switch(a){case"admin":case"lockdown":case"lockdown-admin":return"text-amber-300";case"spectator":return"text-slate-400";default:return"text-sky-300"}}function EN(a,l){if(!a)return"";const i=a.nickname||a.socketId?.slice(0,6)||"unknown";return a.socketId&&a.socketId===l?`${i} (you)`:i}function eo({hideNicknameForm:a=!1,hideHeader:l=!1,className:i="",fillHeight:o=!1,compact:f=!1}){const{session:d,setNickname:h}=Ve(),{value:p}=st("profile",{nickname:""}),g=m.useRef(null),v=En(),y=d?.role!=="spectator",b=d?.users??[],j=d?.socketId||null;m.useEffect(()=>{if(!y||!d?.socketId)return;const C=p.nickname||"";if(!C||d.socketId===g.current)return;const N=d.socketId;h(C).then(()=>{g.current=N}).catch(()=>{})},[y,d?.socketId,h,p.nickname]),m.useEffect(()=>{if(!v)return;const C=()=>{if(!y)return;const N=(p.nickname||"").trim();N&&h(N).then(()=>{g.current=d?.socketId||null}).catch(()=>{})};return v.on("connect",C),()=>v.off("connect",C)},[y,h,v,p.nickname,d?.socketId]);const M=m.useMemo(()=>[...b].sort((C,N)=>C.socketId===j?-1:N.socketId===j?1:(C.nickname||"").localeCompare(N.nickname||"")),[j,b]),w=o?"flex-1 min-h-0 overflow-y-auto":f?"h-28 overflow-y-auto":"h-48 overflow-y-auto";return r.jsxs("section",{className:`panel-section space-y-0.5 text-base ${o?"flex h-full min-h-0 flex-col overflow-hidden":""} ${i}`,children:[!a&&r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"grid gap-0.5 md:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]",children:[r.jsx("div",{className:"min-w-0",children:r.jsx("div",{className:"surface flex w-full items-center px-0 py-0",children:r.jsx(ed,{compact:f})})}),r.jsx(pg,{})]}),!y&&r.jsx("p",{className:"text-xs text-slate-500",children:"Spectators cannot set nicknames."})]}),r.jsxs("div",{className:`space-y-0.5 ${o?"flex flex-1 min-h-0 flex-col":""}`,children:[!l&&r.jsxs("div",{className:`flex items-center justify-between text-sm text-slate-400 ${f?"text-xs":""}`,children:[r.jsx("span",{children:"Users"}),r.jsx("span",{className:"text-xs text-slate-500",children:M.length})]}),r.jsx("div",{className:`surface flex flex-wrap content-start items-start gap-0.5 px-0 pb-0 ${w} ${f?"text-[0.8rem]":""}`,children:M.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:"Waiting for users…"}):M.map(C=>r.jsx("span",{className:`rounded bg-slate-800/80 px-1 py-0.25 text-[0.7rem] ${wN(C.role)}`,children:EN(C,j)},C.socketId))})]})]})}function CN(){const{state:{roverId:a}}=Dt(),i=es(a)?.sensors||{};return r.jsx("section",{className:"panel-section",children:r.jsx("div",{className:"aspect-square w-full",children:r.jsx(vf,{sensors:i})})})}function qN(){const{state:{roverId:a,keymap:l,camera:i,horn:o},pipeline:f,actions:{setServoAngle:d,setNightVision:h,startHorn:p,stopHorn:g}}=Dt(),v=Zx(a),y=v.docked&&!v.driving,b=i?.config,j=!!(a&&i?.enabled&&b),M=!!(a&&f?.nightVision),w=f?.nightVisionState,C=!!(a&&f?.horn),N=o?.overheated,E=typeof b?.minAngle=="number"?b.minAngle:-30,S=typeof b?.maxAngle=="number"?b.maxAngle:30,T=typeof i?.angle=="number"?i.angle:typeof b?.homeAngle=="number"?b.homeAngle:(E+S)/2,B=An(l?.nightVisionToggle?.[0]),L=An(l?.hornHonk?.[0]),$=An(l?.cameraUp?.[0]),_=An(l?.cameraDown?.[0]);return r.jsxs("section",{className:"panel-section grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto] gap-0.5",children:[r.jsx(Jx,{layout:"desktop",expand:!0,driveDockState:v}),y?null:r.jsxs("div",{className:"surface space-y-0.5 p-0 text-sm text-slate-200",children:[M&&r.jsx(Ix,{nightVisionOn:w?.nightVisionOn,disabled:!a,onToggle:h,keyLabel:B}),C&&r.jsx(Wx,{disabled:!a||N,onStart:p,onStop:g,keyLabel:L,active:o?.active,heat:o?.heat}),j&&r.jsx(Px,{value:T,min:E,max:S,onChange:d,keyDownLabel:_,keyUpLabel:$,className:"space-y-0.5 px-1 py-1",labelRowClass:"text-xs text-slate-300",labelClass:"",valueClass:"font-mono text-slate-100",sliderClass:"w-full",accentClass:"accent-emerald-400",endpointClass:"text-[0.7rem] text-slate-400"})]})]})}function MN({layout:a,onOpenHelpOverlay:l}){return r.jsx("section",{className:"panel text-base",children:r.jsxs(If,{defaultTab:"telemetry",children:[r.jsxs(Wf,{children:[r.jsx(mn,{id:"telemetry",children:"Controls"}),r.jsx(mn,{id:"help",children:"Help"}),r.jsx(mn,{id:"settings",children:"Settings"})]}),r.jsxs(Pf,{children:[r.jsx(hn,{id:"telemetry",children:r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"grid items-stretch gap-0.5 grid-cols-[minmax(0,1.35fr)_minmax(0,0.95fr)]",children:[r.jsx(CN,{}),r.jsx(qN,{})]}),r.jsxs("div",{className:"grid gap-0.5 grid-cols-[minmax(0,1fr)_minmax(0,0.9fr)_minmax(0,0.75fr)]",children:[r.jsx(uo,{}),r.jsx(_f,{panelId:"replay-sources-desktop",fillHeight:!0}),r.jsx(bN,{})]}),r.jsxs("div",{className:"grid items-stretch gap-0.5 grid-cols-[minmax(0,1.3fr)_minmax(0,0.22fr)] h-[14rem]",children:[r.jsx(co,{fillHeight:!0}),r.jsxs("div",{className:"grid min-h-0 gap-0.5 grid-rows-[minmax(0,1fr)_auto]",children:[r.jsx(eo,{compact:!0,hideNicknameForm:!0,fillHeight:!0}),r.jsx(yN,{compact:!0})]})]}),r.jsx(ig,{}),r.jsx(Yf,{defaultOrientation:"horizontal",panelId:"rightpane-telemetry"})]})}),r.jsx(hn,{id:"help",children:r.jsx(hg,{layout:a,onOpenOverlay:l})}),r.jsx(hn,{id:"settings",children:r.jsx(fg,{})})]})]})})}const kN=new Set(["admin","lockdown","lockdown-admin"]),RN=new Set(["lockdown","lockdown-admin"]),TN=new Set(["admin","lockdown"]);function DN(a="admin"){return a==="lockdown"?{title:"Lockdown mode active",description:"Only the server owners can access the interface at this time."}:{title:"Admin mode active"}}function _N(){const{session:a}=Ve(),l=a?.mode,i=a?.role,o=a?.adminReason?.text||"";a?.adminReason?.updatedAt;const f=a?.timezone||"UTC",d=a?.socials?.find(j=>String(j?.id||j?.label||"").toLowerCase()==="discord")?.url||a?.discord?.invite||null,h=TN.has(l),p=l==="lockdown"?RN.has(i):kN.has(i),[g,v]=m.useState(()=>new Date),y=m.useMemo(()=>{try{return new Intl.DateTimeFormat("en-US",{timeZone:f,hour:"numeric",minute:"2-digit",second:"2-digit"}).format(g)}catch{return g.toLocaleTimeString()}},[g,f]);if(m.useEffect(()=>{const j=setInterval(()=>v(new Date),1e3);return()=>clearInterval(j)},[]),!h||p)return null;const b=DN(l);return r.jsx("div",{className:"pointer-events-auto fixed inset-0 z-50 flex items-center justify-center bg-black/85 px-0.5 py-0.5",children:r.jsxs("div",{className:"surface w-full max-w-md space-y-0.5 text-slate-100 shadow-2xl",children:[r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-lg font-semibold",children:b.title}),r.jsx("p",{className:"text-sm text-slate-300",children:b.description})]}),r.jsxs("div",{className:"surface-muted space-y-0.5",children:[r.jsx("p",{className:"text-[0.7rem] tracking-wide text-slate-400",children:"Reason for locking:"}),r.jsx("p",{className:"text-lg font-semibold text-slate-100",children:o||"No reason set."}),r.jsxs("p",{className:"text-center text-sm text-slate-300",children:["Server time: ",y]})]}),r.jsx("div",{className:"surface-muted",children:r.jsx(rg,{})}),r.jsx("div",{className:"w-full justify-center items-center",children:r.jsx(Qf,{id:"discord",label:"Join our Discord server for updates!",url:d})}),"You can still use the chat while the server is locked:",r.jsxs("div",{className:"max-h-80 overflow-y-auto",children:[r.jsx(co,{}),r.jsx(ed,{})]})]})})}const ON="/assets/turn_alert-DVO8EZJa.mp3";function BN(a,l=1){const i=m.useRef(null);return m.useEffect(()=>{i.current=new Audio(a),i.current.volume=l,i.current.load()},[a,l]),()=>{i.current&&(i.current.volume=l,i.current.currentTime=0,i.current.play().catch(()=>{}))}}function LN(){const{session:a,pushAlert:l}=Ve(),{value:i}=st("audio",jt),o=Number.isFinite(i?.masterVolume)?i.masterVolume:jt.masterVolume,f=Number.isFinite(i?.alertVolume)?i.alertVolume:jt.alertVolume,d=Math.max(0,Math.min(1,o*f)),h=BN(ON,d),p=m.useRef(new Set),g=m.useMemo(()=>a?.turnQueues||{},[a?.turnQueues]),v=a?.socketId||null;return m.useEffect(()=>{if(!v)return;const y=[];Object.entries(g).forEach(([b,j])=>{!j||!j.current||j.current!==v||p.current.has(b)||(y.push(b),p.current.add(b))}),Object.keys(g).forEach(b=>{(!g[b]||g[b].current!==v)&&p.current.delete(b)}),y.length!==0&&(y.forEach(b=>{const j=a?.roster?.find(M=>String(M.id)===String(b))?.name||b;l({title:"Your turn!",message:`You now control ${j}.`,color:"#ff5722"})}),h())},[g,h,l,a?.roster,v]),null}function zN({visible:a,mode:l,onEnterFullscreen:i,onDismiss:o}){if(!a)return null;const f=l==="pwa-hint";return r.jsx("div",{className:"fixed inset-0 z-40 flex items-end justify-center p-2 pointer-events-none sm:items-center",children:r.jsx("div",{className:"pointer-events-auto w-full max-w-sm rounded-lg border border-cyan-500/40 bg-zinc-950/95 shadow-xl",children:r.jsxs("div",{className:"space-y-0.5 p-4 text-sm text-slate-100",children:[r.jsx("h2",{className:"text-base font-semibold text-white",children:"Better in fullscreen"}),f?r.jsxs("p",{className:"text-slate-300",children:["For fullscreen on iOS, open Safari's share menu and pick ",r.jsx("strong",{children:"Add to Home Screen"}),". Launching from the home screen removes the browser chrome."]}):r.jsx("p",{className:"text-slate-300",children:"Enable fullscreen to free up more space for the video feed and controls. You can exit fullscreen at any time via the system back or home gesture."}),r.jsxs("div",{className:"flex justify-end gap-0.5 pt-1 text-sm",children:[r.jsx("button",{type:"button",className:"rounded border border-slate-600 px-3 py-1 text-slate-200",onClick:o,children:f?"Got it":"Not now"}),!f&&r.jsx("button",{type:"button",className:"rounded bg-cyan-500 px-3 py-1 font-semibold text-black hover:bg-cyan-400",onClick:i,children:"Enter fullscreen"})]})]})})})}const xg="fullscreenPromptDismissed",UN=new Set(["mobile-portrait","mobile-landscape"]),gg=()=>{if(typeof window>"u")return null;try{return window.sessionStorage}catch{return null}},HN=()=>{const a=gg();if(!a)return!1;try{return a.getItem(xg)==="1"}catch{return!1}},Kp=()=>{const a=gg();if(a)try{a.setItem(xg,"1")}catch{}},VN=()=>{if(typeof navigator>"u")return!1;const a=navigator.userAgent||navigator.platform||"";return/iphone|ipad|ipod/i.test(a)},YN=()=>{if(typeof document>"u")return!1;const a=document;return!!(a.fullscreenEnabled||a.webkitFullscreenEnabled||a.mozFullScreenEnabled||a.msFullscreenEnabled)};function $N(a){const l=UN.has(a),[i,o]=m.useState(!1),[f,d]=m.useState("native"),h=m.useMemo(()=>VN(),[]),p=m.useCallback(()=>{if(!l){o(!1);return}if(HN()){o(!1);return}if(h){d("pwa-hint"),o(!0);return}if(!YN()){o(!1);return}if(typeof document<"u"&&document.fullscreenElement){o(!1);return}d("native"),o(!0)},[h,l]);m.useEffect(()=>{p()},[p]),m.useEffect(()=>{if(typeof document>"u")return;const y=()=>{if(document.fullscreenElement){o(!1);return}p()};return document.addEventListener("fullscreenchange",y),document.addEventListener("webkitfullscreenchange",y),()=>{document.removeEventListener("fullscreenchange",y),document.removeEventListener("webkitfullscreenchange",y)}},[p]);const g=m.useCallback(()=>{Kp(),o(!1)},[]),v=m.useCallback(async()=>{if(!l||h||typeof document>"u")return!1;const y=document.documentElement;if(!y)return!1;const b=y.requestFullscreen||y.webkitRequestFullscreen||y.mozRequestFullScreen||y.msRequestFullscreen;if(!b)return!1;try{const j=b.call(y);return j&&typeof j.then=="function"&&await j,Kp(),o(!1),!0}catch(j){return console.warn("Failed to enter fullscreen",j),!1}},[h,l]);return m.useEffect(()=>{l||o(!1)},[l]),{visible:i,mode:f,enterFullscreen:v,dismiss:g}}function GN({visible:a,layout:l,onClose:i,showOnLoad:o,onToggleShowOnLoad:f}){if(!a)return null;const{state:d}=Dt(),h=p=>{const g=!p.target.checked;f?.(g)};return r.jsx("div",{className:"fixed inset-0 z-30 flex items-center justify-center bg-black/80 px-0.5 py-0.5",children:r.jsxs("div",{className:"pointer-events-auto surface w-full max-w-4xl max-h-[90vh] overflow-hidden shadow-2xl",children:[r.jsxs("div",{className:"flex items-center justify-between border-b border-slate-700 px-0.5 py-0.25 text-sm text-slate-200",children:[r.jsx("span",{className:"font-semibold",children:"Help & controls"}),r.jsxs("div",{className:"flex items-center gap-0.5 text-[0.8rem] text-slate-300",children:[r.jsxs("label",{className:"flex items-center gap-0.5",children:[r.jsx("input",{type:"checkbox",checked:!o,onChange:h,className:"accent-cyan-500"}),r.jsx("span",{children:"Don't show again"})]}),r.jsx("button",{type:"button",onClick:i,className:"button-dark px-2 py-0.25 text-[0.8rem]",children:"Close"})]})]}),r.jsx("div",{className:"max-h-[85vh] overflow-y-auto p-0.5",children:r.jsx(mg,{layout:l,keymap:d?.keymap||{}})})]})})}const XN=`aroma bagel batter diff --git a/server/public/index.html b/server/public/index.html index 52911eca..5fb8437e 100644 --- a/server/public/index.html +++ b/server/public/index.html @@ -11,7 +11,7 @@ Multi Roomba Rover - + diff --git a/server/src/services/llmCommentaryService.js b/server/src/services/llmCommentaryService.js index 93665d49..34956eb3 100644 --- a/server/src/services/llmCommentaryService.js +++ b/server/src/services/llmCommentaryService.js @@ -23,8 +23,6 @@ const ACTIVITY_WINDOW_MS = 30000; const ACTIVITY_BUCKET_MS = 1000; const SELF_TALK_WINDOW_MS = 30 * 60 * 1000; const MAX_CONTEXT_EVENTS = 10; -const NO_ACTIVE_DRIVER_DELAY_MS = 5000; -const NO_ACTIVE_DRIVER_LOG_COOLDOWN_MS = 30000; const config = loadConfig(); const commentaryConfig = config.llmCommentary || {}; @@ -38,7 +36,7 @@ const ollamaClient = ollamaUrl ? new Ollama({ host: ollamaUrl }) : null; let timer = null; let inFlight = false; let tickCount = 0; -let lastNoDriverLogAt = 0; +let skipStreak = 0; let contextResetAt = Date.now(); let clearCount = 0; const roverActivity = new Map(); // roverId -> { buckets: Map(bucketTs -> { distanceMm, turnDeg, bumps }), bumpLeftActive, bumpRightActive } @@ -76,6 +74,7 @@ let status = { lastSnapshotSummary: null, lastClearedAt: contextResetAt, clearCount, + skipStreak: 0, lastGeneratedText: null, lastPostedText: null, lastPostedAt: null, @@ -182,11 +181,13 @@ function getActivity30s(roverId, nowMs = Date.now()) { function clearRuntimeHistory() { contextResetAt = Date.now(); clearCount += 1; + skipStreak = 0; roverActivity.clear(); lastRoverStateById.clear(); updateStatus({ lastClearedAt: contextResetAt, clearCount, + skipStreak, lastInfoSnapshot: null, lastModelMessages: null, lastModelRawOutput: null, @@ -279,9 +280,6 @@ function buildSnapshot() { const nowMs = now.getTime(); const activeDrivers = getActiveDrivers(); const driverEntries = collectActiveDriverEntries(); - if (driverEntries.length === 0) { - return null; - } const roster = roverManager.getRoster().slice(0, MAX_ROVERS); const nextRoverStateById = new Map(); @@ -394,6 +392,7 @@ function buildSnapshot() { run_meta: { version: 'commentary_v2', self_talk_recent_30m: botRecent30m.length, + skip_streak: skipStreak, last_message_focus: buildLastMessageFocus(lastBotMessage, rovers), active_driver_count: driverEntries.length, driving_rovers: driverEntries.map(([roverId]) => String(roverId)), @@ -444,6 +443,7 @@ function formatRunMetaMessage(runMeta = {}) { `- active drivers: ${Number(runMeta?.active_driver_count) || 0}`, `- driving rovers: ${drivingRovers}`, `- bot posts last 30m: ${Number(runMeta?.self_talk_recent_30m) || 0}`, + `- current skip streak: ${Number(runMeta?.skip_streak) || 0}`, `- last bot focus: ${focusText}`, ].join('\n'); } @@ -607,26 +607,6 @@ async function runTick() { inFlight = true; try { const snapshot = buildSnapshot(); - if (!snapshot) { - const nowMs = Date.now(); - if (nowMs - lastNoDriverLogAt >= NO_ACTIVE_DRIVER_LOG_COOLDOWN_MS) { - logger.info('Commentary tick skipped; no active drivers', { tickId }); - lastNoDriverLogAt = nowMs; - } - updateStatus({ - lastOutcome: 'skipped', - lastReason: 'no active drivers', - inFlight: false, - lastSnapshotSummary: { - activeDrivers: 0, - rovers: roverManager.getRoster().length, - chatMessages: 0, - }, - }); - // Slow polling while no drivers are present to avoid log spam / hot loops. - nextDelayMs = NO_ACTIVE_DRIVER_DELAY_MS; - return; - } const snapshotSummary = { activeDrivers: snapshot?.run_meta?.active_driver_count || 0, rovers: snapshot?.current_snapshot?.rovers?.length || 0, @@ -650,9 +630,11 @@ async function runTick() { const text = modelResult?.normalized; if (!text) { logger.info('Commentary tick produced SKIP/empty output', { tickId }); + skipStreak += 1; updateStatus({ lastOutcome: 'skipped', lastReason: modelResult?.raw?.trim() ? 'model returned SKIP' : 'model returned empty', + skipStreak, lastGeneratedText: null, }); // Immediate retry after model skip. @@ -671,9 +653,11 @@ async function runTick() { ); if (duplicate) { logger.info('Commentary tick skipped duplicate output', { tickId, text }); + skipStreak += 1; updateStatus({ lastOutcome: 'skipped', lastReason: 'duplicate text', + skipStreak, }); // Immediate retry after a skip outcome. nextDelayMs = 0; @@ -681,9 +665,11 @@ async function runTick() { } sendSystemMessage(text); logger.info('Commentary message posted', { tickId, text }); + skipStreak = 0; updateStatus({ lastOutcome: 'posted', lastReason: null, + skipStreak, lastPostedText: text, lastPostedAt: Date.now(), }); diff --git a/webui/src/components/AdminPanel.jsx b/webui/src/components/AdminPanel.jsx index 243bda64..e9c20ec9 100644 --- a/webui/src/components/AdminPanel.jsx +++ b/webui/src/components/AdminPanel.jsx @@ -334,6 +334,10 @@ function LlmCommentaryPanel({ status, onClearHistory, clearingHistory }) { Tick count {status.tickCount ?? 0} +
+ Skip streak + {status.skipStreak ?? 0} +
Last outcome {status.lastOutcome || '--'}