pi-mega-compact 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +7 -0
- package/dist/extensions/dashboard-server/api-contracts/model-thresholds.js +1 -0
- package/dist/extensions/dashboard-server/routes-model-thresholds.js +167 -0
- package/dist/extensions/dashboard-server/routes.js +3 -2
- package/dist/extensions/dashboard-server/server.js +6 -2
- package/dist/extensions/mega-events/context-handler.js +37 -11
- package/dist/src/store/sqlite/model-snapshots.js +21 -0
- package/dist/src/store/sqlite/model-thresholds.js +93 -0
- package/dist/src/store/sqlite/schema/core.js +10 -0
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/vector-search.js +1 -1
- package/extensions/dashboard-client/dist/assets/{AreaChart-CrmTcE7w.js → AreaChart-Y_X0Bbjd.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-CrmTcE7w.js.map → AreaChart-Y_X0Bbjd.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-Cf8DTaXS.js → BarChart-B9XDb-Ob.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-Cf8DTaXS.js.map → BarChart-B9XDb-Ob.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-Ce0MES6p.js → CacheTab-BUGVb1G1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-Ce0MES6p.js.map → CacheTab-BUGVb1G1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-DbaUuA58.js → EventsTab-dOqLYTsy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-DbaUuA58.js.map → EventsTab-dOqLYTsy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-BmEXkcfL.js → HealthTab-c2vezKg1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-BmEXkcfL.js.map → HealthTab-c2vezKg1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-n2JQrMy7.js → MaintenanceTab-bQYHIcdx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-n2JQrMy7.js.map → MaintenanceTab-bQYHIcdx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-2L8pH6y2.js +2 -0
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-Dvu7vkOm.js.map → MemoryMapTab-2L8pH6y2.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-Q0g32AmP.js → MetricsTab-CzUx1jpo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-Q0g32AmP.js.map → MetricsTab-CzUx1jpo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-iKiEcOLL.js → OverviewTab-eCNoaclv.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-iKiEcOLL.js.map → OverviewTab-eCNoaclv.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DWpnv77y.js → ReposTab-tb0IW3NF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DWpnv77y.js.map → ReposTab-tb0IW3NF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-G0YxR1vG.js → SessionsTab-C-IoLw5h.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-G0YxR1vG.js.map → SessionsTab-C-IoLw5h.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-BvmFi8gf.js +2 -0
- package/extensions/dashboard-client/dist/assets/SetupTab-BvmFi8gf.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-1_LCFiJp.js → TimeSavedCard-DBH-cOlA.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-1_LCFiJp.js.map → TimeSavedCard-DBH-cOlA.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-pqOkjE4L.js → TurnsTab-DBMgmvnX.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{TurnsTab-pqOkjE4L.js.map → TurnsTab-DBMgmvnX.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/WikiTab-1LcQrXk3.js +2 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-99sKE4nB.js.map → WikiTab-1LcQrXk3.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/button-DtEsZTUE.js +2 -0
- package/extensions/dashboard-client/dist/assets/{button-YcV2VQEP.js.map → button-DtEsZTUE.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-Bwxzo50p.js → card-DcfTmw-4.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-Bwxzo50p.js.map → card-DcfTmw-4.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-C6hlfMYG.js → generateCategoricalChart-BsIxUDyw.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-C6hlfMYG.js.map → generateCategoricalChart-BsIxUDyw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-DRa2d4TF.js → index-B93cewiX.js} +14 -14
- package/extensions/dashboard-client/dist/assets/index-B93cewiX.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/index-C_tCbOI0.css +1 -0
- package/extensions/dashboard-client/dist/assets/{switch-rU-lor0v.js → switch-Bowd7jCD.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-rU-lor0v.js.map → switch-Bowd7jCD.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-h-9B4w9X.js → toggle-Bo7kJZ4u.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-h-9B4w9X.js.map → toggle-Bo7kJZ4u.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-BxJORWx8.js → useSSE-zNhb0c3u.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-BxJORWx8.js.map → useSSE-zNhb0c3u.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/api/client.ts +38 -13
- package/extensions/dashboard-client/src/tabs/SetupTab/ThresholdsPanel.tsx +334 -0
- package/extensions/dashboard-client/src/tabs/SetupTab.tsx +9 -1
- package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +50 -17
- package/extensions/dashboard-server/api-contracts/index.ts +28 -3
- package/extensions/dashboard-server/api-contracts/model-thresholds.ts +76 -0
- package/extensions/dashboard-server/routes-model-thresholds.ts +222 -0
- package/extensions/dashboard-server/routes.ts +11 -2
- package/extensions/dashboard-server/server.ts +5 -1
- package/extensions/mega-events/context-handler.ts +106 -69
- package/package.json +1 -1
- package/src/store/sqlite/model-snapshots.ts +99 -62
- package/src/store/sqlite/model-thresholds.ts +168 -0
- package/src/store/sqlite/schema/core.ts +10 -0
- package/src/store/sqlite.ts +1 -0
- package/src/vector-search.ts +1 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-Dvu7vkOm.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-C-y6H-F-.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-C-y6H-F-.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/WikiTab-99sKE4nB.js +0 -2
- package/extensions/dashboard-client/dist/assets/button-YcV2VQEP.js +0 -2
- package/extensions/dashboard-client/dist/assets/index-DRa2d4TF.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-za_gmEmQ.css +0 -1
package/extensions/dashboard-client/dist/assets/{ReposTab-DWpnv77y.js → ReposTab-tb0IW3NF.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{j as e,r as d,u as y,e as F,g as W}from"./index-DRa2d4TF.js";import{C as m,c as x}from"./card-Bwxzo50p.js";import{b as C}from"./format-COjDIVGj.js";import{c as A}from"./pricing-DkYp8-vi.js";function I(t){return t>=1048576?`${(t/1048576).toFixed(1)} MiB`:t>=1024?`${(t/1024).toFixed(1)} KiB`:`${t} B`}function D(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:String(t)}function R({totalRepos:t,totalCheckpoints:n,totalTokensSaved:s,compressedOriginalBytes:r}){return e.jsxs("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4",children:[e.jsx(m,{className:"electric-hover",children:e.jsxs(x,{children:[e.jsx("span",{className:"tile-label",children:"Repositories"}),e.jsx("div",{className:"text-xl font-semibold",children:t.toLocaleString()})]})}),e.jsx(m,{className:"electric-hover",children:e.jsxs(x,{children:[e.jsx("span",{className:"tile-label",children:"Total Checkpoints"}),e.jsx("div",{className:"text-xl font-semibold",children:n.toLocaleString()})]})}),e.jsx(m,{className:"electric-hover",children:e.jsxs(x,{children:[e.jsx("span",{className:"tile-label",children:"Total Tokens Saved"}),e.jsx("div",{className:"text-xl font-semibold",children:D(s)})]})}),e.jsx(m,{className:"electric-hover",children:e.jsxs(x,{children:[e.jsx("span",{className:"tile-label",children:"Compressed-Original"}),e.jsx("div",{className:"text-xl font-semibold",children:I(r)})]})})]})}const w=[{key:"displayName",label:"Repo"},{key:"modelName",label:"Model"},{key:"checkpointCount",label:"Checkpoints"},{key:"tokensSaved",label:"Tokens Saved"},{key:"compressedOriginalBytes",label:"Retained"},{key:"lastCompactedAt",label:"Last Compacted"}];function E(t){return t>=1048576?`${(t/1048576).toFixed(1)} MiB`:t>=1024?`${(t/1024).toFixed(1)} KiB`:`${t} B`}function H(t){if(t==null)return"—";try{return new Date(t).toLocaleString()}catch{return String(t)}}function K({repos:t,onSelect:n}){const[s,r]=d.useState(""),[a,c]=d.useState("lastCompactedAt"),[l,j]=d.useState("desc"),b=d.useMemo(()=>{const o=s.trim().toLowerCase(),f=o?t.filter(g=>g.displayName.toLowerCase().includes(o)):t.slice();return f.sort((g,B)=>{const h=g[a],u=B[a],v=h==null,k=u==null;if(v&&k)return 0;if(v)return l==="desc"?1:-1;if(k)return l==="desc"?-1:1;const S=typeof h=="number"&&typeof u=="number"?h-u:String(h).localeCompare(String(u));return l==="desc"?-S:S}),f},[t,s,a,l]),O=o=>{a===o?j(f=>f==="asc"?"desc":"asc"):(c(o),j(o==="lastCompactedAt"||o==="tokensSaved"||o==="compressedOriginalBytes"?"desc":"asc"))};return e.jsx(m,{children:e.jsxs(x,{children:[e.jsx("input",{type:"search",className:"mb-3 w-full rounded-md border border-border bg-bg-elevated/50 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/60",placeholder:"Search repos…",value:s,onChange:o=>r(o.target.value),"aria-label":"Search repos"}),e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full border-collapse text-sm",children:[e.jsx("thead",{children:e.jsx("tr",{children:w.map(o=>e.jsx("th",{className:"border-b border-border px-3 py-2 text-left",children:e.jsxs("button",{type:"button",className:`font-medium text-muted-foreground transition-colors hover:text-foreground ${a===o.key?"text-neon":""}`,onClick:()=>O(o.key),children:[o.label,a===o.key&&e.jsx("span",{children:l==="asc"?" ▲":" ▼"})]})},o.key))})}),e.jsxs("tbody",{children:[b.length===0&&e.jsx("tr",{children:e.jsx("td",{colSpan:w.length,className:"px-3 py-4 text-muted-foreground",children:t.length===0?"No repositories registered yet.":"No repos match your search."})}),b.map(o=>e.jsxs("tr",{onClick:()=>n(o),className:"cursor-pointer border-b border-border/50 transition-colors hover:bg-bg-elevated/40",children:[e.jsx("td",{className:"px-3 py-2 font-medium",title:o.repoRoot,children:o.displayName}),e.jsx("td",{className:"px-3 py-2 text-muted-foreground",children:o.modelName??"—"}),e.jsx("td",{className:"px-3 py-2 text-right",children:o.checkpointCount.toLocaleString()}),e.jsx("td",{className:"px-3 py-2 text-right",children:o.tokensSaved.toLocaleString()}),e.jsx("td",{className:"px-3 py-2 text-right",children:E(o.compressedOriginalBytes)}),e.jsx("td",{className:"px-3 py-2 text-right",children:H(o.lastCompactedAt)})]},o.stateDir))]})]})})]})})}function p(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:String(t)}function P(t){return t>=1048576?`${(t/1048576).toFixed(1)} MiB`:t>=1024?`${(t/1024).toFixed(1)} KiB`:`${t} B`}function $(t){if(t==null)return"never";try{return new Date(t).toLocaleString()}catch{return String(t)}}function U({repo:t,onClose:n}){return d.useEffect(()=>{const s=r=>{r.key==="Escape"&&n()};return window.addEventListener("keydown",s),()=>window.removeEventListener("keydown",s)},[n]),e.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4",onClick:n,role:"dialog","aria-modal":"true",children:e.jsxs("div",{className:"max-h-[85vh] w-full max-w-xl overflow-y-auto rounded-xl border border-border bg-bg-card p-5 shadow-panel",onClick:s=>s.stopPropagation(),children:[e.jsxs("header",{className:"mb-2 flex items-start justify-between gap-4",children:[e.jsx("h2",{className:"font-heading text-lg font-semibold",children:t.displayName}),e.jsx("button",{type:"button",className:"text-2xl leading-none text-muted-foreground hover:text-foreground",onClick:n,"aria-label":"Close",children:"×"})]}),e.jsx("p",{className:"mb-4 break-all font-mono text-xs text-muted-foreground",children:t.repoRoot}),e.jsxs("div",{className:"mb-4 grid grid-cols-2 gap-3 sm:grid-cols-3",children:[e.jsxs("div",{className:"rounded-md border border-border/50 bg-bg-elevated/40 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Checkpoints"}),e.jsx("span",{className:"text-xl font-semibold",children:t.checkpointCount.toLocaleString()})]}),e.jsxs("div",{className:"rounded-md border border-border/50 bg-bg-elevated/40 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Sessions"}),e.jsx("span",{className:"text-xl font-semibold",children:t.sessions.toLocaleString()})]}),e.jsxs("div",{className:"rounded-md border border-border/50 bg-primary/10 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Tokens saved"}),e.jsx("span",{className:"text-xl font-semibold",children:p(t.tokensSaved)})]}),e.jsxs("div",{className:"rounded-md border border-border/50 bg-bg-elevated/40 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Tokens kept"}),e.jsx("span",{className:"text-xl font-semibold",children:p(t.tokensKept)})]}),e.jsxs("div",{className:"rounded-md border border-border/50 bg-bg-elevated/40 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Tokens dropped"}),e.jsx("span",{className:"text-xl font-semibold",children:p(t.tokensDropped)})]}),e.jsxs("div",{className:"rounded-md border border-border/50 bg-bg-elevated/40 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Dedup rate"}),e.jsx("span",{className:"text-xl font-semibold",children:"—"})]})]}),e.jsx("h3",{className:"mb-2 font-heading text-sm font-semibold",children:"Model"}),e.jsxs("dl",{className:"mb-4 grid grid-cols-1 gap-1 text-sm sm:grid-cols-2",children:[e.jsx("dt",{className:"text-muted-foreground",children:"Provider"}),e.jsx("dd",{className:"text-right",children:t.providerName??"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Model"}),e.jsx("dd",{className:"text-right",children:t.modelName??"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Input rate"}),e.jsx("dd",{className:"text-right",children:t.inputRate!==null?`${t.inputRate} tok/s`:"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Output rate"}),e.jsx("dd",{className:"text-right",children:t.outputRate!==null?`${t.outputRate} tok/s`:"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Context window"}),e.jsx("dd",{className:"text-right",children:t.contextWindow!==null?p(t.contextWindow):"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Max tokens"}),e.jsx("dd",{className:"text-right",children:t.maxTokens!==null?p(t.maxTokens):"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Reasoning"}),e.jsx("dd",{className:"text-right",children:t.reasoning===null?"—":t.reasoning?"on":"off"})]}),e.jsx("h3",{className:"mb-2 font-heading text-sm font-semibold",children:"Activity"}),e.jsxs("dl",{className:"grid grid-cols-1 gap-1 text-sm sm:grid-cols-2",children:[e.jsx("dt",{className:"text-muted-foreground",children:"Last compacted"}),e.jsx("dd",{className:"text-right",children:$(t.lastCompactedAt)}),e.jsx("dt",{className:"text-muted-foreground",children:"Last seen"}),e.jsx("dd",{className:"text-right",children:$(t.lastSeen)}),e.jsx("dt",{className:"text-muted-foreground",children:"Compressed-Original"}),e.jsx("dd",{className:"text-right",children:P(t.compressedOriginalBytes)})]})]})})}function N(t){const n=t??0;return n>=3600?`${(n/3600).toFixed(1)}h`:n>=60?`${Math.round(n/60)}m`:n>=1?`${n.toFixed(1)}s`:`${Math.round(n*1e3)}ms`}const T=["Repo","Model","Tier","Context %","State","Compactions (s/t)","Cache Hits (s/t)","Cache Hit %","Est. Saved","Cache Read","Cache Write","Compact s/t (s)","CacheHit s/t (s)"];function q({servers:t}){return e.jsx(m,{children:e.jsxs(x,{children:[e.jsx("p",{className:"mb-3 text-xs text-muted-foreground",children:"Repos seen within the last 30 minutes, with their per-repo cache-hit, compaction, and time-saved (est.) totals pulled live from each repo's dashboard.json."}),e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full border-collapse text-sm",children:[e.jsx("thead",{children:e.jsx("tr",{children:T.map((n,s)=>e.jsx("th",{className:`border-b border-border px-3 py-2 text-left font-medium text-muted-foreground${s>=3&&s!==4?" text-right":""}`,children:n},n))})}),e.jsxs("tbody",{children:[t.length===0&&e.jsx("tr",{children:e.jsx("td",{colSpan:T.length,className:"px-3 py-4 text-muted-foreground",children:"No active repositories."})}),t.map(n=>{const s=n.cacheHits??{session:0,total:0},r=n.compacts??{session:0,total:0},a=n.timeSaved??{compact:{sessionSec:0,totalSec:0},cacheHit:{sessionSec:0,totalSec:0}};return e.jsxs("tr",{children:[e.jsx("td",{title:n.repoRoot,children:n.displayName||n.repoRoot}),e.jsx("td",{children:n.model??"—"}),e.jsx("td",{children:n.tier??"—"}),e.jsx("td",{className:"num",children:n.contextPct!=null?`${Math.round(n.contextPct*100)}%`:"—"}),e.jsx("td",{children:n.state??"—"}),e.jsxs("td",{className:"num",children:[r.session," / ",r.total]}),e.jsxs("td",{className:"num",children:[s.session," / ",s.total]}),e.jsx("td",{className:"num",children:n.providerCache!=null?`${n.providerCache.avgHitPct.toFixed(1)}%`:"—"}),e.jsx("td",{className:"num",children:n.providerCache?.estimatedSaved!=null?`$${n.providerCache.estimatedSaved.toFixed(2)}`:"—"}),e.jsx("td",{className:"num",children:n.providerCache?.cacheRead!=null?C(n.providerCache.cacheRead):"—"}),e.jsx("td",{className:"num",children:n.providerCache?.cacheWrite!=null?C(n.providerCache.cacheWrite):"—"}),e.jsxs("td",{className:"num",children:[N(a.compact.sessionSec)," /"," ",N(a.compact.totalSec)]}),e.jsxs("td",{className:"num",children:[N(a.cacheHit.sessionSec)," /"," ",N(a.cacheHit.totalSec)]})]},n.repoRoot)})]})]})})]})})}function L(t){if(!t.length)return"—";const n=Math.min(...t),s=Math.max(...t);return n===s?n.toLocaleString():`${n.toLocaleString()}–${s.toLocaleString()}`}function M(t){if(!t.length)return"—";const n=Math.min(...t),s=Math.max(...t),r=a=>`$${a.toFixed(6)}`;return n===s?r(n):`${r(n)}–${r(s)}`}function z(t){const n={};for(const s of t){const r=s.modelName&&s.modelName.trim()||"(unknown)";n[r]||(n[r]={model:r,provider:s.providerName??s.provider??"—",checkpoints:0,tokensSaved:0,tokensIn:0,tokensOut:0,sessions:0,usd:0,inRates:[],outRates:[],ctxWindows:[],maxTokens:[],reasoning:null,lastAt:0,cacheRead:0,cacheWrite:0});const a=n[r];a.checkpoints+=s.checkpointCount||0,a.tokensSaved+=s.tokensSaved||0,a.tokensIn+=s.tokensDropped||0,a.tokensOut+=s.tokensKept||0,a.sessions+=s.sessions||0,s.inputRate&&(a.usd+=(s.tokensSaved||0)*s.inputRate,a.inRates.push(s.inputRate)),s.outputRate&&a.outRates.push(s.outputRate),s.contextWindow&&a.ctxWindows.push(s.contextWindow),s.maxTokens&&a.maxTokens.push(s.maxTokens),s.reasoning!=null&&(a.reasoning=s.reasoning),s.providerCacheRead&&(a.cacheRead+=s.providerCacheRead),s.providerCacheWrite&&(a.cacheWrite+=s.providerCacheWrite),s.lastCompactedAt&&s.lastCompactedAt>a.lastAt&&(a.lastAt=s.lastCompactedAt)}return Object.values(n).sort((s,r)=>r.tokensSaved-s.tokensSaved)}const i={tokensIn:"Tokens dropped from context by compaction (the input reclaimed)",tokensOut:"Tokens kept as compacted summaries still in context (the output retained)",ctxWindow:"Model context window (max input tokens the model accepts)",maxOut:"Model max output tokens per turn",reas:"Reasoning-capable model",sessions:"Distinct sessions with at least one checkpoint",inRate:"USD per input token",outRate:"USD per output token"};function _({repos:t}){const n=d.useMemo(()=>z(t),[t]);return e.jsxs("div",{className:"savings-by-model",children:[e.jsx("p",{className:"legend-note",children:"How much context & cost mega-compact has reclaimed, grouped by the model you were running. Compression ratio reflects workload/content, not model quality."}),e.jsx("div",{className:"table-scroll",children:e.jsxs("table",{className:"savings-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Model"}),e.jsx("th",{children:"Provider"}),e.jsx("th",{className:"num",title:i.tokensIn,children:"Tokens In"}),e.jsx("th",{className:"num",title:i.tokensOut,children:"Tokens Out"}),e.jsx("th",{className:"num",children:"Freed"}),e.jsx("th",{className:"num",title:i.ctxWindow,children:"Ctx Window"}),e.jsx("th",{className:"num",title:i.maxOut,children:"Max Out"}),e.jsx("th",{className:"num",title:i.reas,children:"Reas."}),e.jsx("th",{className:"num",title:i.sessions,children:"Sessions"}),e.jsx("th",{className:"num",children:"Checkpoints"}),e.jsx("th",{className:"num",title:i.inRate,children:"In $/tok"}),e.jsx("th",{className:"num",title:i.outRate,children:"Out $/tok"}),e.jsx("th",{className:"num",children:"$ Saved"}),e.jsx("th",{className:"num",title:"Provider prompt-cache hit rate (reads / reads+writes+tokens_in)",children:"Cache Hit %"}),e.jsx("th",{className:"num",title:"Estimated provider prompt-cache savings (read tokens * 0.9 * inputRate − write tokens * 0.25 * inputRate)",children:"Cache $ Saved"}),e.jsx("th",{className:"num",children:"Last Used"})]})}),e.jsxs("tbody",{children:[n.length===0&&e.jsx("tr",{children:e.jsx("td",{colSpan:16,className:"repo-empty",children:"No repositories registered yet."})}),n.map(s=>{const r=(s.tokensIn||0)-(s.tokensOut||0),a=s.usd>0?`$${s.usd.toFixed(4)}`:"—",c=s.lastAt?new Date(s.lastAt).toLocaleString():"—",l=s.reasoning==null?"—":s.reasoning?"yes":"no";return e.jsxs("tr",{children:[e.jsx("td",{className:"repo-model",children:s.model}),e.jsx("td",{children:s.provider}),e.jsx("td",{className:"num",children:s.tokensIn.toLocaleString()}),e.jsx("td",{className:"num",children:s.tokensOut.toLocaleString()}),e.jsx("td",{className:"num",children:r.toLocaleString()}),e.jsx("td",{className:"num",children:L(s.ctxWindows)}),e.jsx("td",{className:"num",children:L(s.maxTokens)}),e.jsx("td",{className:"num",children:l}),e.jsx("td",{className:"num",children:s.sessions.toLocaleString()}),e.jsx("td",{className:"num",children:s.checkpoints.toLocaleString()}),e.jsx("td",{className:"num",children:M(s.inRates)}),e.jsx("td",{className:"num",children:M(s.outRates)}),e.jsx("td",{className:"num",children:a}),e.jsx("td",{className:"num",children:s.cacheRead>0?`${(s.cacheRead*100/(s.cacheRead+s.cacheWrite+s.tokensIn||1)).toFixed(1)}%`:"—"}),e.jsx("td",{className:"num",children:s.cacheRead>0?`$${A(s.cacheRead,s.cacheWrite,s.inRates[0]||0).netSaved.toFixed(4)}`:"—"}),e.jsx("td",{className:"num",children:c})]},s.model)})]})]})}),e.jsx("p",{className:"legend-note",children:"Tokens In = Σ original region tokens dropped by compaction. Tokens Out = Σ compacted summary tokens still retained in context. Freed = Tokens In − Tokens Out (net context reclaimed). Ctx Window / Max Out / Reas. come from the latest captured model snapshot for each repo."})]})}async function G(){return await W()}function Y(){const[t,n]=d.useState(null),{data:s,error:r}=y(d.useCallback(()=>G(),[]),{pollInterval:1e4}),{data:a}=y(d.useCallback(()=>F(),[]),{pollInterval:1e4});if(r&&!s)return e.jsxs("div",{className:"tab-stub",children:["Error loading repos: ",r.message]});if(!s)return e.jsx("div",{className:"tab-stub",children:"Loading repos…"});const c=s.summary,l=s.repos,j=a?.servers??[];return l.length===0?e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(R,{totalRepos:0,totalCheckpoints:0,totalTokensSaved:0,compressedOriginalBytes:0}),e.jsxs("div",{className:"tab-stub",children:[e.jsx("h3",{children:"Repositories / Memory"}),e.jsx("p",{children:"Repo memory appears after the first compaction. Compaction fires when context crosses the tier threshold (~50-70% of the window). Run a longer session or lower the compaction tier to see it sooner."})]})]}):e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(R,{totalRepos:c?.totalRepos??0,totalCheckpoints:c?.totalCheckpoints??0,totalTokensSaved:c?.totalTokensSaved??0,compressedOriginalBytes:c?.totalCompressedOriginalBytes??0}),e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"All Repositories"}),e.jsx(K,{repos:l,onSelect:n}),e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Active Repos — Live Cache Hits & Compactions"}),e.jsx(q,{servers:j}),e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Savings by Model"}),e.jsx(_,{repos:l}),t&&e.jsx(U,{repo:t,onClose:()=>n(null)})]})}export{Y as default};
|
|
2
|
-
//# sourceMappingURL=ReposTab-
|
|
1
|
+
import{j as e,r as d,u as y,e as F,g as W}from"./index-B93cewiX.js";import{C as m,c as x}from"./card-DcfTmw-4.js";import{b as C}from"./format-COjDIVGj.js";import{c as A}from"./pricing-DkYp8-vi.js";function I(t){return t>=1048576?`${(t/1048576).toFixed(1)} MiB`:t>=1024?`${(t/1024).toFixed(1)} KiB`:`${t} B`}function D(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:String(t)}function R({totalRepos:t,totalCheckpoints:n,totalTokensSaved:s,compressedOriginalBytes:r}){return e.jsxs("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4",children:[e.jsx(m,{className:"electric-hover",children:e.jsxs(x,{children:[e.jsx("span",{className:"tile-label",children:"Repositories"}),e.jsx("div",{className:"text-xl font-semibold",children:t.toLocaleString()})]})}),e.jsx(m,{className:"electric-hover",children:e.jsxs(x,{children:[e.jsx("span",{className:"tile-label",children:"Total Checkpoints"}),e.jsx("div",{className:"text-xl font-semibold",children:n.toLocaleString()})]})}),e.jsx(m,{className:"electric-hover",children:e.jsxs(x,{children:[e.jsx("span",{className:"tile-label",children:"Total Tokens Saved"}),e.jsx("div",{className:"text-xl font-semibold",children:D(s)})]})}),e.jsx(m,{className:"electric-hover",children:e.jsxs(x,{children:[e.jsx("span",{className:"tile-label",children:"Compressed-Original"}),e.jsx("div",{className:"text-xl font-semibold",children:I(r)})]})})]})}const w=[{key:"displayName",label:"Repo"},{key:"modelName",label:"Model"},{key:"checkpointCount",label:"Checkpoints"},{key:"tokensSaved",label:"Tokens Saved"},{key:"compressedOriginalBytes",label:"Retained"},{key:"lastCompactedAt",label:"Last Compacted"}];function E(t){return t>=1048576?`${(t/1048576).toFixed(1)} MiB`:t>=1024?`${(t/1024).toFixed(1)} KiB`:`${t} B`}function H(t){if(t==null)return"—";try{return new Date(t).toLocaleString()}catch{return String(t)}}function K({repos:t,onSelect:n}){const[s,r]=d.useState(""),[a,c]=d.useState("lastCompactedAt"),[l,j]=d.useState("desc"),b=d.useMemo(()=>{const o=s.trim().toLowerCase(),f=o?t.filter(g=>g.displayName.toLowerCase().includes(o)):t.slice();return f.sort((g,B)=>{const h=g[a],u=B[a],v=h==null,k=u==null;if(v&&k)return 0;if(v)return l==="desc"?1:-1;if(k)return l==="desc"?-1:1;const S=typeof h=="number"&&typeof u=="number"?h-u:String(h).localeCompare(String(u));return l==="desc"?-S:S}),f},[t,s,a,l]),O=o=>{a===o?j(f=>f==="asc"?"desc":"asc"):(c(o),j(o==="lastCompactedAt"||o==="tokensSaved"||o==="compressedOriginalBytes"?"desc":"asc"))};return e.jsx(m,{children:e.jsxs(x,{children:[e.jsx("input",{type:"search",className:"mb-3 w-full rounded-md border border-border bg-bg-elevated/50 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/60",placeholder:"Search repos…",value:s,onChange:o=>r(o.target.value),"aria-label":"Search repos"}),e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full border-collapse text-sm",children:[e.jsx("thead",{children:e.jsx("tr",{children:w.map(o=>e.jsx("th",{className:"border-b border-border px-3 py-2 text-left",children:e.jsxs("button",{type:"button",className:`font-medium text-muted-foreground transition-colors hover:text-foreground ${a===o.key?"text-neon":""}`,onClick:()=>O(o.key),children:[o.label,a===o.key&&e.jsx("span",{children:l==="asc"?" ▲":" ▼"})]})},o.key))})}),e.jsxs("tbody",{children:[b.length===0&&e.jsx("tr",{children:e.jsx("td",{colSpan:w.length,className:"px-3 py-4 text-muted-foreground",children:t.length===0?"No repositories registered yet.":"No repos match your search."})}),b.map(o=>e.jsxs("tr",{onClick:()=>n(o),className:"cursor-pointer border-b border-border/50 transition-colors hover:bg-bg-elevated/40",children:[e.jsx("td",{className:"px-3 py-2 font-medium",title:o.repoRoot,children:o.displayName}),e.jsx("td",{className:"px-3 py-2 text-muted-foreground",children:o.modelName??"—"}),e.jsx("td",{className:"px-3 py-2 text-right",children:o.checkpointCount.toLocaleString()}),e.jsx("td",{className:"px-3 py-2 text-right",children:o.tokensSaved.toLocaleString()}),e.jsx("td",{className:"px-3 py-2 text-right",children:E(o.compressedOriginalBytes)}),e.jsx("td",{className:"px-3 py-2 text-right",children:H(o.lastCompactedAt)})]},o.stateDir))]})]})})]})})}function p(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:String(t)}function P(t){return t>=1048576?`${(t/1048576).toFixed(1)} MiB`:t>=1024?`${(t/1024).toFixed(1)} KiB`:`${t} B`}function $(t){if(t==null)return"never";try{return new Date(t).toLocaleString()}catch{return String(t)}}function U({repo:t,onClose:n}){return d.useEffect(()=>{const s=r=>{r.key==="Escape"&&n()};return window.addEventListener("keydown",s),()=>window.removeEventListener("keydown",s)},[n]),e.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4",onClick:n,role:"dialog","aria-modal":"true",children:e.jsxs("div",{className:"max-h-[85vh] w-full max-w-xl overflow-y-auto rounded-xl border border-border bg-bg-card p-5 shadow-panel",onClick:s=>s.stopPropagation(),children:[e.jsxs("header",{className:"mb-2 flex items-start justify-between gap-4",children:[e.jsx("h2",{className:"font-heading text-lg font-semibold",children:t.displayName}),e.jsx("button",{type:"button",className:"text-2xl leading-none text-muted-foreground hover:text-foreground",onClick:n,"aria-label":"Close",children:"×"})]}),e.jsx("p",{className:"mb-4 break-all font-mono text-xs text-muted-foreground",children:t.repoRoot}),e.jsxs("div",{className:"mb-4 grid grid-cols-2 gap-3 sm:grid-cols-3",children:[e.jsxs("div",{className:"rounded-md border border-border/50 bg-bg-elevated/40 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Checkpoints"}),e.jsx("span",{className:"text-xl font-semibold",children:t.checkpointCount.toLocaleString()})]}),e.jsxs("div",{className:"rounded-md border border-border/50 bg-bg-elevated/40 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Sessions"}),e.jsx("span",{className:"text-xl font-semibold",children:t.sessions.toLocaleString()})]}),e.jsxs("div",{className:"rounded-md border border-border/50 bg-primary/10 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Tokens saved"}),e.jsx("span",{className:"text-xl font-semibold",children:p(t.tokensSaved)})]}),e.jsxs("div",{className:"rounded-md border border-border/50 bg-bg-elevated/40 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Tokens kept"}),e.jsx("span",{className:"text-xl font-semibold",children:p(t.tokensKept)})]}),e.jsxs("div",{className:"rounded-md border border-border/50 bg-bg-elevated/40 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Tokens dropped"}),e.jsx("span",{className:"text-xl font-semibold",children:p(t.tokensDropped)})]}),e.jsxs("div",{className:"rounded-md border border-border/50 bg-bg-elevated/40 p-3",children:[e.jsx("span",{className:"block text-xs text-muted-foreground",children:"Dedup rate"}),e.jsx("span",{className:"text-xl font-semibold",children:"—"})]})]}),e.jsx("h3",{className:"mb-2 font-heading text-sm font-semibold",children:"Model"}),e.jsxs("dl",{className:"mb-4 grid grid-cols-1 gap-1 text-sm sm:grid-cols-2",children:[e.jsx("dt",{className:"text-muted-foreground",children:"Provider"}),e.jsx("dd",{className:"text-right",children:t.providerName??"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Model"}),e.jsx("dd",{className:"text-right",children:t.modelName??"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Input rate"}),e.jsx("dd",{className:"text-right",children:t.inputRate!==null?`${t.inputRate} tok/s`:"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Output rate"}),e.jsx("dd",{className:"text-right",children:t.outputRate!==null?`${t.outputRate} tok/s`:"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Context window"}),e.jsx("dd",{className:"text-right",children:t.contextWindow!==null?p(t.contextWindow):"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Max tokens"}),e.jsx("dd",{className:"text-right",children:t.maxTokens!==null?p(t.maxTokens):"—"}),e.jsx("dt",{className:"text-muted-foreground",children:"Reasoning"}),e.jsx("dd",{className:"text-right",children:t.reasoning===null?"—":t.reasoning?"on":"off"})]}),e.jsx("h3",{className:"mb-2 font-heading text-sm font-semibold",children:"Activity"}),e.jsxs("dl",{className:"grid grid-cols-1 gap-1 text-sm sm:grid-cols-2",children:[e.jsx("dt",{className:"text-muted-foreground",children:"Last compacted"}),e.jsx("dd",{className:"text-right",children:$(t.lastCompactedAt)}),e.jsx("dt",{className:"text-muted-foreground",children:"Last seen"}),e.jsx("dd",{className:"text-right",children:$(t.lastSeen)}),e.jsx("dt",{className:"text-muted-foreground",children:"Compressed-Original"}),e.jsx("dd",{className:"text-right",children:P(t.compressedOriginalBytes)})]})]})})}function N(t){const n=t??0;return n>=3600?`${(n/3600).toFixed(1)}h`:n>=60?`${Math.round(n/60)}m`:n>=1?`${n.toFixed(1)}s`:`${Math.round(n*1e3)}ms`}const T=["Repo","Model","Tier","Context %","State","Compactions (s/t)","Cache Hits (s/t)","Cache Hit %","Est. Saved","Cache Read","Cache Write","Compact s/t (s)","CacheHit s/t (s)"];function q({servers:t}){return e.jsx(m,{children:e.jsxs(x,{children:[e.jsx("p",{className:"mb-3 text-xs text-muted-foreground",children:"Repos seen within the last 30 minutes, with their per-repo cache-hit, compaction, and time-saved (est.) totals pulled live from each repo's dashboard.json."}),e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full border-collapse text-sm",children:[e.jsx("thead",{children:e.jsx("tr",{children:T.map((n,s)=>e.jsx("th",{className:`border-b border-border px-3 py-2 text-left font-medium text-muted-foreground${s>=3&&s!==4?" text-right":""}`,children:n},n))})}),e.jsxs("tbody",{children:[t.length===0&&e.jsx("tr",{children:e.jsx("td",{colSpan:T.length,className:"px-3 py-4 text-muted-foreground",children:"No active repositories."})}),t.map(n=>{const s=n.cacheHits??{session:0,total:0},r=n.compacts??{session:0,total:0},a=n.timeSaved??{compact:{sessionSec:0,totalSec:0},cacheHit:{sessionSec:0,totalSec:0}};return e.jsxs("tr",{children:[e.jsx("td",{title:n.repoRoot,children:n.displayName||n.repoRoot}),e.jsx("td",{children:n.model??"—"}),e.jsx("td",{children:n.tier??"—"}),e.jsx("td",{className:"num",children:n.contextPct!=null?`${Math.round(n.contextPct*100)}%`:"—"}),e.jsx("td",{children:n.state??"—"}),e.jsxs("td",{className:"num",children:[r.session," / ",r.total]}),e.jsxs("td",{className:"num",children:[s.session," / ",s.total]}),e.jsx("td",{className:"num",children:n.providerCache!=null?`${n.providerCache.avgHitPct.toFixed(1)}%`:"—"}),e.jsx("td",{className:"num",children:n.providerCache?.estimatedSaved!=null?`$${n.providerCache.estimatedSaved.toFixed(2)}`:"—"}),e.jsx("td",{className:"num",children:n.providerCache?.cacheRead!=null?C(n.providerCache.cacheRead):"—"}),e.jsx("td",{className:"num",children:n.providerCache?.cacheWrite!=null?C(n.providerCache.cacheWrite):"—"}),e.jsxs("td",{className:"num",children:[N(a.compact.sessionSec)," /"," ",N(a.compact.totalSec)]}),e.jsxs("td",{className:"num",children:[N(a.cacheHit.sessionSec)," /"," ",N(a.cacheHit.totalSec)]})]},n.repoRoot)})]})]})})]})})}function L(t){if(!t.length)return"—";const n=Math.min(...t),s=Math.max(...t);return n===s?n.toLocaleString():`${n.toLocaleString()}–${s.toLocaleString()}`}function M(t){if(!t.length)return"—";const n=Math.min(...t),s=Math.max(...t),r=a=>`$${a.toFixed(6)}`;return n===s?r(n):`${r(n)}–${r(s)}`}function z(t){const n={};for(const s of t){const r=s.modelName&&s.modelName.trim()||"(unknown)";n[r]||(n[r]={model:r,provider:s.providerName??s.provider??"—",checkpoints:0,tokensSaved:0,tokensIn:0,tokensOut:0,sessions:0,usd:0,inRates:[],outRates:[],ctxWindows:[],maxTokens:[],reasoning:null,lastAt:0,cacheRead:0,cacheWrite:0});const a=n[r];a.checkpoints+=s.checkpointCount||0,a.tokensSaved+=s.tokensSaved||0,a.tokensIn+=s.tokensDropped||0,a.tokensOut+=s.tokensKept||0,a.sessions+=s.sessions||0,s.inputRate&&(a.usd+=(s.tokensSaved||0)*s.inputRate,a.inRates.push(s.inputRate)),s.outputRate&&a.outRates.push(s.outputRate),s.contextWindow&&a.ctxWindows.push(s.contextWindow),s.maxTokens&&a.maxTokens.push(s.maxTokens),s.reasoning!=null&&(a.reasoning=s.reasoning),s.providerCacheRead&&(a.cacheRead+=s.providerCacheRead),s.providerCacheWrite&&(a.cacheWrite+=s.providerCacheWrite),s.lastCompactedAt&&s.lastCompactedAt>a.lastAt&&(a.lastAt=s.lastCompactedAt)}return Object.values(n).sort((s,r)=>r.tokensSaved-s.tokensSaved)}const i={tokensIn:"Tokens dropped from context by compaction (the input reclaimed)",tokensOut:"Tokens kept as compacted summaries still in context (the output retained)",ctxWindow:"Model context window (max input tokens the model accepts)",maxOut:"Model max output tokens per turn",reas:"Reasoning-capable model",sessions:"Distinct sessions with at least one checkpoint",inRate:"USD per input token",outRate:"USD per output token"};function _({repos:t}){const n=d.useMemo(()=>z(t),[t]);return e.jsxs("div",{className:"savings-by-model",children:[e.jsx("p",{className:"legend-note",children:"How much context & cost mega-compact has reclaimed, grouped by the model you were running. Compression ratio reflects workload/content, not model quality."}),e.jsx("div",{className:"table-scroll",children:e.jsxs("table",{className:"savings-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Model"}),e.jsx("th",{children:"Provider"}),e.jsx("th",{className:"num",title:i.tokensIn,children:"Tokens In"}),e.jsx("th",{className:"num",title:i.tokensOut,children:"Tokens Out"}),e.jsx("th",{className:"num",children:"Freed"}),e.jsx("th",{className:"num",title:i.ctxWindow,children:"Ctx Window"}),e.jsx("th",{className:"num",title:i.maxOut,children:"Max Out"}),e.jsx("th",{className:"num",title:i.reas,children:"Reas."}),e.jsx("th",{className:"num",title:i.sessions,children:"Sessions"}),e.jsx("th",{className:"num",children:"Checkpoints"}),e.jsx("th",{className:"num",title:i.inRate,children:"In $/tok"}),e.jsx("th",{className:"num",title:i.outRate,children:"Out $/tok"}),e.jsx("th",{className:"num",children:"$ Saved"}),e.jsx("th",{className:"num",title:"Provider prompt-cache hit rate (reads / reads+writes+tokens_in)",children:"Cache Hit %"}),e.jsx("th",{className:"num",title:"Estimated provider prompt-cache savings (read tokens * 0.9 * inputRate − write tokens * 0.25 * inputRate)",children:"Cache $ Saved"}),e.jsx("th",{className:"num",children:"Last Used"})]})}),e.jsxs("tbody",{children:[n.length===0&&e.jsx("tr",{children:e.jsx("td",{colSpan:16,className:"repo-empty",children:"No repositories registered yet."})}),n.map(s=>{const r=(s.tokensIn||0)-(s.tokensOut||0),a=s.usd>0?`$${s.usd.toFixed(4)}`:"—",c=s.lastAt?new Date(s.lastAt).toLocaleString():"—",l=s.reasoning==null?"—":s.reasoning?"yes":"no";return e.jsxs("tr",{children:[e.jsx("td",{className:"repo-model",children:s.model}),e.jsx("td",{children:s.provider}),e.jsx("td",{className:"num",children:s.tokensIn.toLocaleString()}),e.jsx("td",{className:"num",children:s.tokensOut.toLocaleString()}),e.jsx("td",{className:"num",children:r.toLocaleString()}),e.jsx("td",{className:"num",children:L(s.ctxWindows)}),e.jsx("td",{className:"num",children:L(s.maxTokens)}),e.jsx("td",{className:"num",children:l}),e.jsx("td",{className:"num",children:s.sessions.toLocaleString()}),e.jsx("td",{className:"num",children:s.checkpoints.toLocaleString()}),e.jsx("td",{className:"num",children:M(s.inRates)}),e.jsx("td",{className:"num",children:M(s.outRates)}),e.jsx("td",{className:"num",children:a}),e.jsx("td",{className:"num",children:s.cacheRead>0?`${(s.cacheRead*100/(s.cacheRead+s.cacheWrite+s.tokensIn||1)).toFixed(1)}%`:"—"}),e.jsx("td",{className:"num",children:s.cacheRead>0?`$${A(s.cacheRead,s.cacheWrite,s.inRates[0]||0).netSaved.toFixed(4)}`:"—"}),e.jsx("td",{className:"num",children:c})]},s.model)})]})]})}),e.jsx("p",{className:"legend-note",children:"Tokens In = Σ original region tokens dropped by compaction. Tokens Out = Σ compacted summary tokens still retained in context. Freed = Tokens In − Tokens Out (net context reclaimed). Ctx Window / Max Out / Reas. come from the latest captured model snapshot for each repo."})]})}async function G(){return await W()}function Y(){const[t,n]=d.useState(null),{data:s,error:r}=y(d.useCallback(()=>G(),[]),{pollInterval:1e4}),{data:a}=y(d.useCallback(()=>F(),[]),{pollInterval:1e4});if(r&&!s)return e.jsxs("div",{className:"tab-stub",children:["Error loading repos: ",r.message]});if(!s)return e.jsx("div",{className:"tab-stub",children:"Loading repos…"});const c=s.summary,l=s.repos,j=a?.servers??[];return l.length===0?e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(R,{totalRepos:0,totalCheckpoints:0,totalTokensSaved:0,compressedOriginalBytes:0}),e.jsxs("div",{className:"tab-stub",children:[e.jsx("h3",{children:"Repositories / Memory"}),e.jsx("p",{children:"Repo memory appears after the first compaction. Compaction fires when context crosses the tier threshold (~50-70% of the window). Run a longer session or lower the compaction tier to see it sooner."})]})]}):e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(R,{totalRepos:c?.totalRepos??0,totalCheckpoints:c?.totalCheckpoints??0,totalTokensSaved:c?.totalTokensSaved??0,compressedOriginalBytes:c?.totalCompressedOriginalBytes??0}),e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"All Repositories"}),e.jsx(K,{repos:l,onSelect:n}),e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Active Repos — Live Cache Hits & Compactions"}),e.jsx(q,{servers:j}),e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Savings by Model"}),e.jsx(_,{repos:l}),t&&e.jsx(U,{repo:t,onClose:()=>n(null)})]})}export{Y as default};
|
|
2
|
+
//# sourceMappingURL=ReposTab-tb0IW3NF.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReposTab-DWpnv77y.js","sources":["../../src/components/SummaryTiles.tsx","../../src/components/RepoTable.tsx","../../src/components/RepoDetailModal.tsx","../../src/components/ActiveReposTable.tsx","../../src/components/SavingsByModelTable.tsx","../../src/tabs/ReposTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/SummaryTiles.tsx — aggregate header tiles.\n *\n * 4 tiles: Repositories, Total Checkpoints, Total Tokens Saved,\n * Compressed-Original. Matches old dashboard #panel-summary tiles.\n */\n\nimport type React from \"react\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\nexport interface SummaryTilesProps {\n\t/** Total repos in the registry. */\n\ttotalRepos: number;\n\t/** Total checkpoints across all repos. */\n\ttotalCheckpoints: number;\n\t/** Total tokens saved across all repos. */\n\ttotalTokensSaved: number;\n\t/** Total compressed-original bytes across all repos. */\n\tcompressedOriginalBytes: number;\n}\n\n/** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */\nfunction fmtBytesTop(b: number): string {\n\tif (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;\n\tif (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;\n\treturn `${b} B`;\n}\n\n/** Format large numbers with K/M suffix. */\nfunction fmt(n: number): string {\n\tif (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n\tif (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n\treturn String(n);\n}\n\nexport function SummaryTiles({\n\ttotalRepos,\n\ttotalCheckpoints,\n\ttotalTokensSaved,\n\tcompressedOriginalBytes,\n}: SummaryTilesProps): React.ReactElement {\n\treturn (\n\t\t<div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4\">\n\t\t\t<Card className=\"electric-hover\">\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"tile-label\">Repositories</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{totalRepos.toLocaleString()}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card className=\"electric-hover\">\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"tile-label\">Total Checkpoints</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{totalCheckpoints.toLocaleString()}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card className=\"electric-hover\">\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"tile-label\">Total Tokens Saved</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{fmt(totalTokensSaved)}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card className=\"electric-hover\">\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"tile-label\">Compressed-Original</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{fmtBytesTop(compressedOriginalBytes)}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/RepoTable.tsx — sortable, searchable repo table.\n *\n * 6 columns: Repo, Model, Checkpoints, Tokens Saved, Retained, Last Compacted.\n * Client-side sort (click header to toggle asc/desc). Search by displayName\n * (case-insensitive substring). Default sort: Last Compacted DESC.\n * Row click opens RepoDetailModal.\n */\n\nimport type React from \"react\";\nimport { useState, useMemo } from \"react\";\nimport type { IndexesIndexRow } from \"@contracts\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\nexport interface RepoTableProps {\n\trepos: IndexesIndexRow[];\n\t/** Called when a row is clicked — opens the detail modal. */\n\tonSelect: (repo: IndexesIndexRow) => void;\n}\n\ntype SortKey =\n\t| \"displayName\"\n\t| \"modelName\"\n\t| \"checkpointCount\"\n\t| \"tokensSaved\"\n\t| \"compressedOriginalBytes\"\n\t| \"lastCompactedAt\";\n\ntype SortDir = \"asc\" | \"desc\";\n\nconst COLUMNS: Array<{ key: SortKey; label: string }> = [\n\t{ key: \"displayName\", label: \"Repo\" },\n\t{ key: \"modelName\", label: \"Model\" },\n\t{ key: \"checkpointCount\", label: \"Checkpoints\" },\n\t{ key: \"tokensSaved\", label: \"Tokens Saved\" },\n\t{ key: \"compressedOriginalBytes\", label: \"Retained\" },\n\t{ key: \"lastCompactedAt\", label: \"Last Compacted\" },\n];\n\n/** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */\nfunction fmtBytesTop(b: number): string {\n\tif (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;\n\tif (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;\n\treturn `${b} B`;\n}\n\nfunction formatTs(ts: number | null): string {\n\tif (ts === null || ts === undefined) return \"\\u2014\";\n\ttry {\n\t\treturn new Date(ts).toLocaleString();\n\t} catch {\n\t\treturn String(ts);\n\t}\n}\n\nexport function RepoTable({\n\trepos,\n\tonSelect,\n}: RepoTableProps): React.ReactElement {\n\tconst [search, setSearch] = useState(\"\");\n\tconst [sortKey, setSortKey] = useState<SortKey>(\"lastCompactedAt\");\n\tconst [sortDir, setSortDir] = useState<SortDir>(\"desc\");\n\n\tconst filtered = useMemo(() => {\n\t\tconst q = search.trim().toLowerCase();\n\t\tconst list = q\n\t\t\t? repos.filter((r) => r.displayName.toLowerCase().includes(q))\n\t\t\t: repos.slice();\n\t\tlist.sort((a, b) => {\n\t\t\tconst av = a[sortKey];\n\t\t\tconst bv = b[sortKey];\n\t\t\t// nulls last on desc, first on asc (consistent UX)\n\t\t\tconst aNull = av === null || av === undefined;\n\t\t\tconst bNull = bv === null || bv === undefined;\n\t\t\tif (aNull && bNull) return 0;\n\t\t\tif (aNull) return sortDir === \"desc\" ? 1 : -1;\n\t\t\tif (bNull) return sortDir === \"desc\" ? -1 : 1;\n\t\t\tconst cmp =\n\t\t\t\ttypeof av === \"number\" && typeof bv === \"number\"\n\t\t\t\t\t? av - bv\n\t\t\t\t\t: String(av).localeCompare(String(bv));\n\t\t\treturn sortDir === \"desc\" ? -cmp : cmp;\n\t\t});\n\t\treturn list;\n\t}, [repos, search, sortKey, sortDir]);\n\n\tconst handleSort = (key: SortKey): void => {\n\t\tif (sortKey === key) {\n\t\t\tsetSortDir((d) => (d === \"asc\" ? \"desc\" : \"asc\"));\n\t\t} else {\n\t\t\tsetSortKey(key);\n\t\t\tsetSortDir(\n\t\t\t\tkey === \"lastCompactedAt\" ||\n\t\t\t\tkey === \"tokensSaved\" ||\n\t\t\t\tkey === \"compressedOriginalBytes\"\n\t\t\t\t\t? \"desc\"\n\t\t\t\t\t: \"asc\",\n\t\t\t);\n\t\t}\n\t};\n\n\treturn (\n\t\t<Card>\n\t\t\t<CardContent>\n\t\t\t<input\n\t\t\t\ttype=\"search\"\n\t\t\t\tclassName=\"mb-3 w-full rounded-md border border-border bg-bg-elevated/50 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/60\"\n\t\t\t\tplaceholder=\"Search repos…\"\n\t\t\t\tvalue={search}\n\t\t\t\tonChange={(e) => setSearch(e.target.value)}\n\t\t\t\taria-label=\"Search repos\"\n\t\t\t/>\n\t\t\t<div className=\"overflow-x-auto\">\n\t\t\t<table className=\"w-full border-collapse text-sm\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t{COLUMNS.map((col) => (\n\t\t\t\t\t\t\t<th key={col.key} className=\"border-b border-border px-3 py-2 text-left\">\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tclassName={`font-medium text-muted-foreground transition-colors hover:text-foreground ${\n\t\t\t\t\t\t\t\t\t\tsortKey === col.key ? \"text-neon\" : \"\"\n\t\t\t\t\t\t\t\t\t}`}\n\t\t\t\t\t\t\t\t\tonClick={() => handleSort(col.key)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{col.label}\n\t\t\t\t\t\t\t\t\t{sortKey === col.key && (\n\t\t\t\t\t\t\t\t\t\t<span>{sortDir === \"asc\" ? \" ▲\" : \" ▼\"}</span>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t{filtered.length === 0 && (\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<td colSpan={COLUMNS.length} className=\"px-3 py-4 text-muted-foreground\">\n\t\t\t\t\t\t\t\t{repos.length === 0\n\t\t\t\t\t\t\t\t\t? \"No repositories registered yet.\"\n\t\t\t\t\t\t\t\t\t: \"No repos match your search.\"}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t)}\n\t\t\t\t\t{filtered.map((repo) => (\n\t\t\t\t\t\t<tr\n\t\t\t\t\t\t\tkey={repo.stateDir}\n\t\t\t\t\t\t\tonClick={() => onSelect(repo)}\n\t\t\t\t\t\t\tclassName=\"cursor-pointer border-b border-border/50 transition-colors hover:bg-bg-elevated/40\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 font-medium\" title={repo.repoRoot}>\n\t\t\t\t\t\t\t\t{repo.displayName}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-muted-foreground\">{repo.modelName ?? \"\\u2014\"}</td>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">{repo.checkpointCount.toLocaleString()}</td>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">{repo.tokensSaved.toLocaleString()}</td>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">{fmtBytesTop(repo.compressedOriginalBytes)}</td>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">{formatTs(repo.lastCompactedAt)}</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t))}\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n","/**\n * dashboard-client/src/components/RepoDetailModal.tsx — repo drill-down.\n *\n * Full detail from an /api/index row: token breakdown (kept/dropped), model\n * info, context window, dedup rate, reasoning flag. Click backdrop to close.\n */\n\nimport type React from \"react\";\nimport { useEffect } from \"react\";\nimport type { IndexesIndexRow } from \"@contracts\";\n\nexport interface RepoDetailModalProps {\n\trepo: IndexesIndexRow;\n\tonClose: () => void;\n}\n\nfunction fmt(n: number): string {\n\tif (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n\tif (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n\treturn String(n);\n}\n\n/** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */\nfunction fmtBytesTop(b: number): string {\n\tif (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;\n\tif (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;\n\treturn `${b} B`;\n}\n\nfunction formatTs(ts: number | null): string {\n\tif (ts === null || ts === undefined) return \"never\";\n\ttry {\n\t\treturn new Date(ts).toLocaleString();\n\t} catch {\n\t\treturn String(ts);\n\t}\n}\n\nexport function RepoDetailModal({\n\trepo,\n\tonClose,\n}: RepoDetailModalProps): React.ReactElement {\n\t// Close on Escape key.\n\tuseEffect(() => {\n\t\tconst handler = (e: KeyboardEvent): void => {\n\t\t\tif (e.key === \"Escape\") onClose();\n\t\t};\n\t\twindow.addEventListener(\"keydown\", handler);\n\t\treturn () => window.removeEventListener(\"keydown\", handler);\n\t}, [onClose]);\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4\"\n\t\t\tonClick={onClose}\n\t\t\trole=\"dialog\"\n\t\t\taria-modal=\"true\"\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName=\"max-h-[85vh] w-full max-w-xl overflow-y-auto rounded-xl border border-border bg-bg-card p-5 shadow-panel\"\n\t\t\t\tonClick={(e) => e.stopPropagation()}\n\t\t\t>\n\t\t\t\t<header className=\"mb-2 flex items-start justify-between gap-4\">\n\t\t\t\t\t<h2 className=\"font-heading text-lg font-semibold\">{repo.displayName}</h2>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"text-2xl leading-none text-muted-foreground hover:text-foreground\"\n\t\t\t\t\t\tonClick={onClose}\n\t\t\t\t\t\taria-label=\"Close\"\n\t\t\t\t\t>\n\t\t\t\t\t\t×\n\t\t\t\t\t</button>\n\t\t\t\t</header>\n\t\t\t\t<p className=\"mb-4 break-all font-mono text-xs text-muted-foreground\">{repo.repoRoot}</p>\n\t\t\t\t<div className=\"mb-4 grid grid-cols-2 gap-3 sm:grid-cols-3\">\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-bg-elevated/40 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Checkpoints</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">\n\t\t\t\t\t\t\t{repo.checkpointCount.toLocaleString()}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-bg-elevated/40 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Sessions</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">\n\t\t\t\t\t\t\t{repo.sessions.toLocaleString()}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-primary/10 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Tokens saved</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">{fmt(repo.tokensSaved)}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-bg-elevated/40 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Tokens kept</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">{fmt(repo.tokensKept)}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-bg-elevated/40 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Tokens dropped</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">{fmt(repo.tokensDropped)}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-bg-elevated/40 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Dedup rate</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">—</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<h3 className=\"mb-2 font-heading text-sm font-semibold\">Model</h3>\n\t\t\t\t<dl className=\"mb-4 grid grid-cols-1 gap-1 text-sm sm:grid-cols-2\">\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Provider</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.providerName ?? \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Model</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.modelName ?? \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Input rate</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.inputRate !== null ? `${repo.inputRate} tok/s` : \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Output rate</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.outputRate !== null ? `${repo.outputRate} tok/s` : \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Context window</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.contextWindow !== null ? fmt(repo.contextWindow) : \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Max tokens</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.maxTokens !== null ? fmt(repo.maxTokens) : \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Reasoning</dt>\n\t\t\t\t\t<dd className=\"text-right\">\n\t\t\t\t\t\t{repo.reasoning === null ? \"—\" : repo.reasoning ? \"on\" : \"off\"}\n\t\t\t\t\t</dd>\n\t\t\t\t</dl>\n\t\t\t\t<h3 className=\"mb-2 font-heading text-sm font-semibold\">Activity</h3>\n\t\t\t\t<dl className=\"grid grid-cols-1 gap-1 text-sm sm:grid-cols-2\">\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Last compacted</dt>\n\t\t\t\t\t<dd className=\"text-right\">{formatTs(repo.lastCompactedAt)}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Last seen</dt>\n\t\t\t\t\t<dd className=\"text-right\">{formatTs(repo.lastSeen)}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Compressed-Original</dt>\n\t\t\t\t\t<dd className=\"text-right\">{fmtBytesTop(repo.compressedOriginalBytes)}</dd>\n\t\t\t\t</dl>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/ActiveReposTable.tsx — Active repos table.\n *\n * Columns: Repo, Model, Tier, Context %, State, Compactions s/t,\n * Cache Hits s/t, Cache Hit %, Cache Read, Cache Write, Est. Saved,\n * Compact s/t (s), CacheHit s/t (s).\n * Data from fetchServers() via useApi with 10s polling.\n */\n\nimport type React from \"react\";\nimport type { ServerEntry } from \"@contracts\";\nimport { fmtTokens } from \"@/utils/format.ts\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\nexport interface ActiveReposTableProps {\n\tservers: ServerEntry[];\n}\n\n/** Format seconds → human-readable duration (matches html.ts fmtSec). */\nfunction fmtSec(s: number | undefined): string {\n\tconst v = s ?? 0;\n\tif (v >= 3600) return `${(v / 3600).toFixed(1)}h`;\n\tif (v >= 60) return `${Math.round(v / 60)}m`;\n\tif (v >= 1) return `${v.toFixed(1)}s`;\n\treturn `${Math.round(v * 1000)}ms`;\n}\n\nconst HEADERS = [\n\t\"Repo\",\n\t\"Model\",\n\t\"Tier\",\n\t\"Context %\",\n\t\"State\",\n\t\"Compactions (s/t)\",\n\t\"Cache Hits (s/t)\",\n\t\"Cache Hit %\",\n\t\"Est. Saved\",\n\t\"Cache Read\",\n\t\"Cache Write\",\n\t\"Compact s/t (s)\",\n\t\"CacheHit s/t (s)\",\n] as const;\n\nexport function ActiveReposTable({\n\tservers,\n}: ActiveReposTableProps): React.ReactElement {\n\treturn (\n\t\t<Card>\n\t\t\t<CardContent>\n\t\t\t<p className=\"mb-3 text-xs text-muted-foreground\">\n\t\t\t\tRepos seen within the last 30 minutes, with their per-repo cache-hit,\n\t\t\t\tcompaction, and time-saved (est.) totals pulled live from each\n\t\t\t\trepo's dashboard.json.\n\t\t\t</p>\n\t\t\t<div className=\"overflow-x-auto\">\n\t\t\t\t<table className=\"w-full border-collapse text-sm\">\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t{HEADERS.map((h, i) => (\n\t\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\t\tkey={h}\n\t\t\t\t\t\t\t\t\tclassName={`border-b border-border px-3 py-2 text-left font-medium text-muted-foreground${\n\t\t\t\t\t\t\t\t\t\ti >= 3 && i !== 4 ? \" text-right\" : \"\"\n\t\t\t\t\t\t\t\t\t}`}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{h}\n\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t{servers.length === 0 && (\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td colSpan={HEADERS.length} className=\"px-3 py-4 text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tNo active repositories.\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{servers.map((r) => {\n\t\t\t\t\t\t\tconst ch = r.cacheHits ?? {\n\t\t\t\t\t\t\t\tsession: 0,\n\t\t\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\t\t\tsessionTokensSaved: 0,\n\t\t\t\t\t\t\t\ttotalTokensSaved: 0,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tconst cp = r.compacts ?? { session: 0, total: 0 };\n\t\t\t\t\t\t\tconst ts = r.timeSaved ?? {\n\t\t\t\t\t\t\t\tcompact: { sessionSec: 0, totalSec: 0 },\n\t\t\t\t\t\t\t\tcacheHit: { sessionSec: 0, totalSec: 0 },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<tr key={r.repoRoot}>\n\t\t\t\t\t\t\t\t\t<td title={r.repoRoot}>{r.displayName || r.repoRoot}</td>\n\t\t\t\t\t\t\t\t\t<td>{r.model ?? \"\\u2014\"}</td>\n\t\t\t\t\t\t\t\t\t<td>{r.tier ?? \"\\u2014\"}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{r.contextPct != null\n\t\t\t\t\t\t\t\t\t\t\t? `${Math.round(r.contextPct * 100)}%`\n\t\t\t\t\t\t\t\t\t\t\t: \"\\u2014\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td>{r.state ?? \"\\u2014\"}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{cp.session} / {cp.total}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{ch.session} / {ch.total}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{r.providerCache != null\n\t\t\t\t\t\t\t\t\t\t\t? `${r.providerCache.avgHitPct.toFixed(1)}%`\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{r.providerCache?.estimatedSaved != null\n\t\t\t\t\t\t\t\t\t\t\t? `$${r.providerCache.estimatedSaved.toFixed(2)}`\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{r.providerCache?.cacheRead != null\n\t\t\t\t\t\t\t\t\t\t\t? fmtTokens(r.providerCache.cacheRead)\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{r.providerCache?.cacheWrite != null\n\t\t\t\t\t\t\t\t\t\t\t? fmtTokens(r.providerCache.cacheWrite)\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{fmtSec(ts.compact.sessionSec)} /{\" \"}\n\t\t\t\t\t\t\t\t\t\t{fmtSec(ts.compact.totalSec)}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{fmtSec(ts.cacheHit.sessionSec)} /{\" \"}\n\t\t\t\t\t\t\t\t\t\t{fmtSec(ts.cacheHit.totalSec)}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n","/**\n * dashboard-client/src/components/SavingsByModelTable.tsx — Savings by Model.\n *\n * 14 columns aggregated client-side from /api/index repos by model.\n * Replicates html.ts renderByModel logic: group by modelName, accumulate\n * tokens/cost/sessions, collapse numeric ranges when repos disagree.\n */\n\nimport type React from \"react\";\nimport { useMemo } from \"react\";\nimport type { IndexesIndexRow } from \"@contracts\";\nimport { computeCacheSavings } from \"@pricing\";\n\nexport interface SavingsByModelTableProps {\n\trepos: IndexesIndexRow[];\n}\n\ninterface ModelGroup {\n\tmodel: string;\n\tprovider: string;\n\tcheckpoints: number;\n\ttokensSaved: number;\n\ttokensIn: number;\n\ttokensOut: number;\n\tsessions: number;\n\tusd: number;\n\tinRates: number[];\n\toutRates: number[];\n\tctxWindows: number[];\n\tmaxTokens: number[];\n\treasoning: boolean | null;\n\tlastAt: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n}\n\n/** Collapse numeric samples: \"—\" | single | \"lo–hi\" (matches html.ts). */\nfunction collapseNum(samples: number[]): string {\n\tif (!samples.length) return \"\\u2014\";\n\tconst lo = Math.min(...samples);\n\tconst hi = Math.max(...samples);\n\treturn lo === hi\n\t\t? lo.toLocaleString()\n\t\t: `${lo.toLocaleString()}\\u2013${hi.toLocaleString()}`;\n}\n\n/** Collapse rate samples: \"—\" | \"$rate\" | \"$lo–$hi\" (matches html.ts). */\nfunction collapseRate(samples: number[]): string {\n\tif (!samples.length) return \"\\u2014\";\n\tconst lo = Math.min(...samples);\n\tconst hi = Math.max(...samples);\n\tconst fmt = (v: number): string => `$${v.toFixed(6)}`;\n\treturn lo === hi ? fmt(lo) : `${fmt(lo)}\\u2013${fmt(hi)}`;\n}\n\n/** Group repos by model, accumulate totals, sort by tokensSaved desc. */\nfunction aggregate(repos: IndexesIndexRow[]): ModelGroup[] {\n\tconst map: Record<string, ModelGroup> = {};\n\tfor (const r of repos) {\n\t\tconst key = (r.modelName && r.modelName.trim()) || \"(unknown)\";\n\t\tif (!map[key]) {\n\t\t\tmap[key] = {\n\t\t\t\tmodel: key,\n\t\t\t\tprovider: r.providerName ?? r.provider ?? \"\\u2014\",\n\t\t\t\tcheckpoints: 0,\n\t\t\t\ttokensSaved: 0,\n\t\t\t\ttokensIn: 0,\n\t\t\t\ttokensOut: 0,\n\t\t\t\tsessions: 0,\n\t\t\t\tusd: 0,\n\t\t\t\tinRates: [],\n\t\t\t\toutRates: [],\n\t\t\t\tctxWindows: [],\n\t\t\t\tmaxTokens: [],\n\t\t\t\treasoning: null,\n\t\t\t\tlastAt: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t};\n\t\t}\n\t\tconst g = map[key];\n\t\tg.checkpoints += r.checkpointCount || 0;\n\t\tg.tokensSaved += r.tokensSaved || 0;\n\t\tg.tokensIn += r.tokensDropped || 0;\n\t\tg.tokensOut += r.tokensKept || 0;\n\t\tg.sessions += r.sessions || 0;\n\t\tif (r.inputRate) {\n\t\t\tg.usd += (r.tokensSaved || 0) * r.inputRate;\n\t\t\tg.inRates.push(r.inputRate);\n\t\t}\n\t\tif (r.outputRate) g.outRates.push(r.outputRate);\n\t\tif (r.contextWindow) g.ctxWindows.push(r.contextWindow);\n\t\tif (r.maxTokens) g.maxTokens.push(r.maxTokens);\n\t\tif (r.reasoning != null) g.reasoning = r.reasoning;\n\t\tif (r.providerCacheRead) g.cacheRead += r.providerCacheRead;\n\t\tif (r.providerCacheWrite) g.cacheWrite += r.providerCacheWrite;\n\t\tif (r.lastCompactedAt && r.lastCompactedAt > g.lastAt)\n\t\t\tg.lastAt = r.lastCompactedAt;\n\t}\n\treturn Object.values(map).sort((a, b) => b.tokensSaved - a.tokensSaved);\n}\n\n/** Tooltip text copied verbatim from html.ts title attributes. */\nconst TOOLTIPS = {\n\ttokensIn: \"Tokens dropped from context by compaction (the input reclaimed)\",\n\ttokensOut:\n\t\t\"Tokens kept as compacted summaries still in context (the output retained)\",\n\tctxWindow: \"Model context window (max input tokens the model accepts)\",\n\tmaxOut: \"Model max output tokens per turn\",\n\treas: \"Reasoning-capable model\",\n\tsessions: \"Distinct sessions with at least one checkpoint\",\n\tinRate: \"USD per input token\",\n\toutRate: \"USD per output token\",\n} as const;\n\nexport function SavingsByModelTable({\n\trepos,\n}: SavingsByModelTableProps): React.ReactElement {\n\tconst groups = useMemo(() => aggregate(repos), [repos]);\n\n\treturn (\n\t\t<div className=\"savings-by-model\">\n\t\t\t<p className=\"legend-note\">\n\t\t\t\tHow much context & cost mega-compact has reclaimed, grouped by the\n\t\t\t\tmodel you were running. Compression ratio reflects workload/content, not\n\t\t\t\tmodel quality.\n\t\t\t</p>\n\t\t\t<div className=\"table-scroll\">\n\t\t\t\t<table className=\"savings-table\">\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<th>Model</th>\n\t\t\t\t\t\t\t<th>Provider</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.tokensIn}>\n\t\t\t\t\t\t\t\tTokens In\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.tokensOut}>\n\t\t\t\t\t\t\t\tTokens Out\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\">Freed</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.ctxWindow}>\n\t\t\t\t\t\t\t\tCtx Window\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.maxOut}>\n\t\t\t\t\t\t\t\tMax Out\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.reas}>\n\t\t\t\t\t\t\t\tReas.\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.sessions}>\n\t\t\t\t\t\t\t\tSessions\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\">Checkpoints</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.inRate}>\n\t\t\t\t\t\t\t\tIn $/tok\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.outRate}>\n\t\t\t\t\t\t\t\tOut $/tok\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\">$ Saved</th>\n\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\tclassName=\"num\"\n\t\t\t\t\t\t\t\ttitle=\"Provider prompt-cache hit rate (reads / reads+writes+tokens_in)\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tCache Hit %\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\tclassName=\"num\"\n\t\t\t\t\t\t\t\ttitle=\"Estimated provider prompt-cache savings (read tokens * 0.9 * inputRate − write tokens * 0.25 * inputRate)\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tCache $ Saved\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\">Last Used</th>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t{groups.length === 0 && (\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td colSpan={16} className=\"repo-empty\">\n\t\t\t\t\t\t\t\t\tNo repositories registered yet.\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{groups.map((g) => {\n\t\t\t\t\t\t\tconst freed = (g.tokensIn || 0) - (g.tokensOut || 0);\n\t\t\t\t\t\t\tconst usd = g.usd > 0 ? `$${g.usd.toFixed(4)}` : \"\\u2014\";\n\t\t\t\t\t\t\tconst when = g.lastAt\n\t\t\t\t\t\t\t\t? new Date(g.lastAt).toLocaleString()\n\t\t\t\t\t\t\t\t: \"\\u2014\";\n\t\t\t\t\t\t\tconst reas =\n\t\t\t\t\t\t\t\tg.reasoning == null ? \"\\u2014\" : g.reasoning ? \"yes\" : \"no\";\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<tr key={g.model}>\n\t\t\t\t\t\t\t\t\t<td className=\"repo-model\">{g.model}</td>\n\t\t\t\t\t\t\t\t\t<td>{g.provider}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{g.tokensIn.toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{g.tokensOut.toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{freed.toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{collapseNum(g.ctxWindows)}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{collapseNum(g.maxTokens)}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{reas}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{g.sessions.toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{g.checkpoints.toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{collapseRate(g.inRates)}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{collapseRate(g.outRates)}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{usd}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{g.cacheRead > 0\n\t\t\t\t\t\t\t\t\t\t\t? `${(\n\t\t\t\t\t\t\t\t\t\t\t\t\t(g.cacheRead * 100) /\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t(g.cacheRead + g.cacheWrite + g.tokensIn || 1)\n\t\t\t\t\t\t\t\t\t\t\t\t).toFixed(1)}%`\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{g.cacheRead > 0\n\t\t\t\t\t\t\t\t\t\t\t? `$${computeCacheSavings(g.cacheRead, g.cacheWrite, g.inRates[0] || 0).netSaved.toFixed(4)}`\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{when}</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t</div>\n\t\t\t<p className=\"legend-note\">\n\t\t\t\tTokens In = Σ original region tokens dropped by compaction. Tokens Out =\n\t\t\t\tΣ compacted summary tokens still retained in context. Freed = Tokens In\n\t\t\t\t− Tokens Out (net context reclaimed). Ctx Window / Max Out / Reas. come\n\t\t\t\tfrom the latest captured model snapshot for each repo.\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/ReposTab.tsx — Repos tab (C2 fleshed out).\n *\n * Renders: SummaryTiles (4 tiles), All Repositories table (RepoTable),\n * Active Repos table (ActiveReposTable), Savings by Model table\n * (SavingsByModelTable), and per-repo detail modal (RepoDetailModal).\n *\n * Data sources: fetchIndex() for summary + all repos + savings-by-model;\n * fetchServers() for active repos. Both polled every 10s.\n */\n\nimport type React from \"react\";\nimport { useCallback, useState } from \"react\";\nimport { useApi } from \"../hooks/useApi\";\nimport { fetchIndex, fetchServers } from \"../api/client\";\nimport type { IndexesIndexRow, ServersResponse } from \"@contracts\";\nimport { SummaryTiles } from \"../components/SummaryTiles\";\nimport { RepoTable } from \"../components/RepoTable\";\nimport { RepoDetailModal } from \"../components/RepoDetailModal\";\nimport { ActiveReposTable } from \"../components/ActiveReposTable\";\nimport { SavingsByModelTable } from \"../components/SavingsByModelTable\";\n\n/**\n * Actual runtime shape of /api/index. The contract's IndexesSummaryResponse\n * puts summary fields at the top level, but the real server nests them under\n * `summary` (matching IndexFallbackResponse shape with non-null summary).\n */\ninterface DashboardIndexSummary {\n\ttotalRepos: number;\n\ttotalCheckpoints: number;\n\ttotalTokensSaved: number;\n\ttotalCompressedOriginalBytes: number;\n}\n\ninterface DashboardIndexResponse {\n\tupdatedAt: string | null;\n\tsummary: DashboardIndexSummary | null;\n\trepos: IndexesIndexRow[];\n}\n\n/** Fetch /api/index and cast to the actual runtime shape. */\nasync function fetchIndexTyped(): Promise<DashboardIndexResponse> {\n\treturn (await fetchIndex()) as unknown as DashboardIndexResponse;\n}\n\nexport default function ReposTab(): React.ReactElement {\n\tconst [selected, setSelected] = useState<IndexesIndexRow | null>(null);\n\n\tconst { data: indexData, error: indexErr } =\n\t\tuseApi<DashboardIndexResponse>(\n\t\t\tuseCallback(() => fetchIndexTyped(), []),\n\t\t\t{ pollInterval: 10_000 },\n\t\t);\n\n\tconst { data: serversData } = useApi<ServersResponse>(\n\t\tuseCallback(() => fetchServers(), []),\n\t\t{ pollInterval: 10_000 },\n\t);\n\n\tif (indexErr && !indexData) {\n\t\treturn (\n\t\t\t<div className=\"tab-stub\">Error loading repos: {indexErr.message}</div>\n\t\t);\n\t}\n\tif (!indexData) {\n\t\treturn <div className=\"tab-stub\">Loading repos…</div>;\n\t}\n\n\tconst summary = indexData.summary;\n\tconst repos = indexData.repos;\n\tconst servers = serversData?.servers ?? [];\n\n\tif (repos.length === 0) {\n\t\treturn (\n\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t<SummaryTiles\n\t\t\t\t\ttotalRepos={0}\n\t\t\t\t\ttotalCheckpoints={0}\n\t\t\t\t\ttotalTokensSaved={0}\n\t\t\t\t\tcompressedOriginalBytes={0}\n\t\t\t\t/>\n\t\t\t\t<div className=\"tab-stub\">\n\t\t\t\t\t<h3>Repositories / Memory</h3>\n\t\t\t\t\t<p>\n\t\t\t\t\t\tRepo memory appears after the first compaction. Compaction fires\n\t\t\t\t\t\twhen context crosses the tier threshold (~50-70% of the window).\n\t\t\t\t\t\tRun a longer session or lower the compaction tier to see it sooner.\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<SummaryTiles\n\t\t\t\ttotalRepos={summary?.totalRepos ?? 0}\n\t\t\t\ttotalCheckpoints={summary?.totalCheckpoints ?? 0}\n\t\t\t\ttotalTokensSaved={summary?.totalTokensSaved ?? 0}\n\t\t\t\tcompressedOriginalBytes={summary?.totalCompressedOriginalBytes ?? 0}\n\t\t\t/>\n\t\t\t<h2 className=\"font-heading text-lg font-semibold\">All Repositories</h2>\n\t\t\t<RepoTable repos={repos} onSelect={setSelected} />\n\t\t\t<h2 className=\"font-heading text-lg font-semibold\">\n\t\t\t\tActive Repos — Live Cache Hits & Compactions\n\t\t\t</h2>\n\t\t\t<ActiveReposTable servers={servers} />\n\t\t\t<h2 className=\"font-heading text-lg font-semibold\">Savings by Model</h2>\n\t\t\t<SavingsByModelTable repos={repos} />\n\t\t\t{selected && (\n\t\t\t\t<RepoDetailModal repo={selected} onClose={() => setSelected(null)} />\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["fmtBytesTop","b","fmt","n","SummaryTiles","totalRepos","totalCheckpoints","totalTokensSaved","compressedOriginalBytes","jsxs","jsx","Card","CardContent","COLUMNS","formatTs","ts","RepoTable","repos","onSelect","search","setSearch","useState","sortKey","setSortKey","sortDir","setSortDir","filtered","useMemo","q","list","r","a","av","bv","aNull","bNull","cmp","handleSort","key","d","e","col","repo","RepoDetailModal","onClose","useEffect","handler","fmtSec","s","v","HEADERS","ActiveReposTable","servers","h","i","ch","cp","fmtTokens","collapseNum","samples","lo","hi","collapseRate","aggregate","map","g","TOOLTIPS","SavingsByModelTable","groups","freed","usd","when","reas","computeCacheSavings","fetchIndexTyped","fetchIndex","ReposTab","selected","setSelected","indexData","indexErr","useApi","useCallback","serversData","fetchServers","summary"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ReposTab-tb0IW3NF.js","sources":["../../src/components/SummaryTiles.tsx","../../src/components/RepoTable.tsx","../../src/components/RepoDetailModal.tsx","../../src/components/ActiveReposTable.tsx","../../src/components/SavingsByModelTable.tsx","../../src/tabs/ReposTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/SummaryTiles.tsx — aggregate header tiles.\n *\n * 4 tiles: Repositories, Total Checkpoints, Total Tokens Saved,\n * Compressed-Original. Matches old dashboard #panel-summary tiles.\n */\n\nimport type React from \"react\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\nexport interface SummaryTilesProps {\n\t/** Total repos in the registry. */\n\ttotalRepos: number;\n\t/** Total checkpoints across all repos. */\n\ttotalCheckpoints: number;\n\t/** Total tokens saved across all repos. */\n\ttotalTokensSaved: number;\n\t/** Total compressed-original bytes across all repos. */\n\tcompressedOriginalBytes: number;\n}\n\n/** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */\nfunction fmtBytesTop(b: number): string {\n\tif (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;\n\tif (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;\n\treturn `${b} B`;\n}\n\n/** Format large numbers with K/M suffix. */\nfunction fmt(n: number): string {\n\tif (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n\tif (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n\treturn String(n);\n}\n\nexport function SummaryTiles({\n\ttotalRepos,\n\ttotalCheckpoints,\n\ttotalTokensSaved,\n\tcompressedOriginalBytes,\n}: SummaryTilesProps): React.ReactElement {\n\treturn (\n\t\t<div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4\">\n\t\t\t<Card className=\"electric-hover\">\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"tile-label\">Repositories</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{totalRepos.toLocaleString()}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card className=\"electric-hover\">\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"tile-label\">Total Checkpoints</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{totalCheckpoints.toLocaleString()}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card className=\"electric-hover\">\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"tile-label\">Total Tokens Saved</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{fmt(totalTokensSaved)}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card className=\"electric-hover\">\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"tile-label\">Compressed-Original</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{fmtBytesTop(compressedOriginalBytes)}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/RepoTable.tsx — sortable, searchable repo table.\n *\n * 6 columns: Repo, Model, Checkpoints, Tokens Saved, Retained, Last Compacted.\n * Client-side sort (click header to toggle asc/desc). Search by displayName\n * (case-insensitive substring). Default sort: Last Compacted DESC.\n * Row click opens RepoDetailModal.\n */\n\nimport type React from \"react\";\nimport { useState, useMemo } from \"react\";\nimport type { IndexesIndexRow } from \"@contracts\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\nexport interface RepoTableProps {\n\trepos: IndexesIndexRow[];\n\t/** Called when a row is clicked — opens the detail modal. */\n\tonSelect: (repo: IndexesIndexRow) => void;\n}\n\ntype SortKey =\n\t| \"displayName\"\n\t| \"modelName\"\n\t| \"checkpointCount\"\n\t| \"tokensSaved\"\n\t| \"compressedOriginalBytes\"\n\t| \"lastCompactedAt\";\n\ntype SortDir = \"asc\" | \"desc\";\n\nconst COLUMNS: Array<{ key: SortKey; label: string }> = [\n\t{ key: \"displayName\", label: \"Repo\" },\n\t{ key: \"modelName\", label: \"Model\" },\n\t{ key: \"checkpointCount\", label: \"Checkpoints\" },\n\t{ key: \"tokensSaved\", label: \"Tokens Saved\" },\n\t{ key: \"compressedOriginalBytes\", label: \"Retained\" },\n\t{ key: \"lastCompactedAt\", label: \"Last Compacted\" },\n];\n\n/** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */\nfunction fmtBytesTop(b: number): string {\n\tif (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;\n\tif (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;\n\treturn `${b} B`;\n}\n\nfunction formatTs(ts: number | null): string {\n\tif (ts === null || ts === undefined) return \"\\u2014\";\n\ttry {\n\t\treturn new Date(ts).toLocaleString();\n\t} catch {\n\t\treturn String(ts);\n\t}\n}\n\nexport function RepoTable({\n\trepos,\n\tonSelect,\n}: RepoTableProps): React.ReactElement {\n\tconst [search, setSearch] = useState(\"\");\n\tconst [sortKey, setSortKey] = useState<SortKey>(\"lastCompactedAt\");\n\tconst [sortDir, setSortDir] = useState<SortDir>(\"desc\");\n\n\tconst filtered = useMemo(() => {\n\t\tconst q = search.trim().toLowerCase();\n\t\tconst list = q\n\t\t\t? repos.filter((r) => r.displayName.toLowerCase().includes(q))\n\t\t\t: repos.slice();\n\t\tlist.sort((a, b) => {\n\t\t\tconst av = a[sortKey];\n\t\t\tconst bv = b[sortKey];\n\t\t\t// nulls last on desc, first on asc (consistent UX)\n\t\t\tconst aNull = av === null || av === undefined;\n\t\t\tconst bNull = bv === null || bv === undefined;\n\t\t\tif (aNull && bNull) return 0;\n\t\t\tif (aNull) return sortDir === \"desc\" ? 1 : -1;\n\t\t\tif (bNull) return sortDir === \"desc\" ? -1 : 1;\n\t\t\tconst cmp =\n\t\t\t\ttypeof av === \"number\" && typeof bv === \"number\"\n\t\t\t\t\t? av - bv\n\t\t\t\t\t: String(av).localeCompare(String(bv));\n\t\t\treturn sortDir === \"desc\" ? -cmp : cmp;\n\t\t});\n\t\treturn list;\n\t}, [repos, search, sortKey, sortDir]);\n\n\tconst handleSort = (key: SortKey): void => {\n\t\tif (sortKey === key) {\n\t\t\tsetSortDir((d) => (d === \"asc\" ? \"desc\" : \"asc\"));\n\t\t} else {\n\t\t\tsetSortKey(key);\n\t\t\tsetSortDir(\n\t\t\t\tkey === \"lastCompactedAt\" ||\n\t\t\t\tkey === \"tokensSaved\" ||\n\t\t\t\tkey === \"compressedOriginalBytes\"\n\t\t\t\t\t? \"desc\"\n\t\t\t\t\t: \"asc\",\n\t\t\t);\n\t\t}\n\t};\n\n\treturn (\n\t\t<Card>\n\t\t\t<CardContent>\n\t\t\t<input\n\t\t\t\ttype=\"search\"\n\t\t\t\tclassName=\"mb-3 w-full rounded-md border border-border bg-bg-elevated/50 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/60\"\n\t\t\t\tplaceholder=\"Search repos…\"\n\t\t\t\tvalue={search}\n\t\t\t\tonChange={(e) => setSearch(e.target.value)}\n\t\t\t\taria-label=\"Search repos\"\n\t\t\t/>\n\t\t\t<div className=\"overflow-x-auto\">\n\t\t\t<table className=\"w-full border-collapse text-sm\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t{COLUMNS.map((col) => (\n\t\t\t\t\t\t\t<th key={col.key} className=\"border-b border-border px-3 py-2 text-left\">\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tclassName={`font-medium text-muted-foreground transition-colors hover:text-foreground ${\n\t\t\t\t\t\t\t\t\t\tsortKey === col.key ? \"text-neon\" : \"\"\n\t\t\t\t\t\t\t\t\t}`}\n\t\t\t\t\t\t\t\t\tonClick={() => handleSort(col.key)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{col.label}\n\t\t\t\t\t\t\t\t\t{sortKey === col.key && (\n\t\t\t\t\t\t\t\t\t\t<span>{sortDir === \"asc\" ? \" ▲\" : \" ▼\"}</span>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t{filtered.length === 0 && (\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<td colSpan={COLUMNS.length} className=\"px-3 py-4 text-muted-foreground\">\n\t\t\t\t\t\t\t\t{repos.length === 0\n\t\t\t\t\t\t\t\t\t? \"No repositories registered yet.\"\n\t\t\t\t\t\t\t\t\t: \"No repos match your search.\"}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t)}\n\t\t\t\t\t{filtered.map((repo) => (\n\t\t\t\t\t\t<tr\n\t\t\t\t\t\t\tkey={repo.stateDir}\n\t\t\t\t\t\t\tonClick={() => onSelect(repo)}\n\t\t\t\t\t\t\tclassName=\"cursor-pointer border-b border-border/50 transition-colors hover:bg-bg-elevated/40\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 font-medium\" title={repo.repoRoot}>\n\t\t\t\t\t\t\t\t{repo.displayName}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-muted-foreground\">{repo.modelName ?? \"\\u2014\"}</td>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">{repo.checkpointCount.toLocaleString()}</td>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">{repo.tokensSaved.toLocaleString()}</td>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">{fmtBytesTop(repo.compressedOriginalBytes)}</td>\n\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">{formatTs(repo.lastCompactedAt)}</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t))}\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n","/**\n * dashboard-client/src/components/RepoDetailModal.tsx — repo drill-down.\n *\n * Full detail from an /api/index row: token breakdown (kept/dropped), model\n * info, context window, dedup rate, reasoning flag. Click backdrop to close.\n */\n\nimport type React from \"react\";\nimport { useEffect } from \"react\";\nimport type { IndexesIndexRow } from \"@contracts\";\n\nexport interface RepoDetailModalProps {\n\trepo: IndexesIndexRow;\n\tonClose: () => void;\n}\n\nfunction fmt(n: number): string {\n\tif (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n\tif (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n\treturn String(n);\n}\n\n/** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */\nfunction fmtBytesTop(b: number): string {\n\tif (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;\n\tif (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;\n\treturn `${b} B`;\n}\n\nfunction formatTs(ts: number | null): string {\n\tif (ts === null || ts === undefined) return \"never\";\n\ttry {\n\t\treturn new Date(ts).toLocaleString();\n\t} catch {\n\t\treturn String(ts);\n\t}\n}\n\nexport function RepoDetailModal({\n\trepo,\n\tonClose,\n}: RepoDetailModalProps): React.ReactElement {\n\t// Close on Escape key.\n\tuseEffect(() => {\n\t\tconst handler = (e: KeyboardEvent): void => {\n\t\t\tif (e.key === \"Escape\") onClose();\n\t\t};\n\t\twindow.addEventListener(\"keydown\", handler);\n\t\treturn () => window.removeEventListener(\"keydown\", handler);\n\t}, [onClose]);\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4\"\n\t\t\tonClick={onClose}\n\t\t\trole=\"dialog\"\n\t\t\taria-modal=\"true\"\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName=\"max-h-[85vh] w-full max-w-xl overflow-y-auto rounded-xl border border-border bg-bg-card p-5 shadow-panel\"\n\t\t\t\tonClick={(e) => e.stopPropagation()}\n\t\t\t>\n\t\t\t\t<header className=\"mb-2 flex items-start justify-between gap-4\">\n\t\t\t\t\t<h2 className=\"font-heading text-lg font-semibold\">{repo.displayName}</h2>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"text-2xl leading-none text-muted-foreground hover:text-foreground\"\n\t\t\t\t\t\tonClick={onClose}\n\t\t\t\t\t\taria-label=\"Close\"\n\t\t\t\t\t>\n\t\t\t\t\t\t×\n\t\t\t\t\t</button>\n\t\t\t\t</header>\n\t\t\t\t<p className=\"mb-4 break-all font-mono text-xs text-muted-foreground\">{repo.repoRoot}</p>\n\t\t\t\t<div className=\"mb-4 grid grid-cols-2 gap-3 sm:grid-cols-3\">\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-bg-elevated/40 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Checkpoints</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">\n\t\t\t\t\t\t\t{repo.checkpointCount.toLocaleString()}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-bg-elevated/40 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Sessions</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">\n\t\t\t\t\t\t\t{repo.sessions.toLocaleString()}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-primary/10 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Tokens saved</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">{fmt(repo.tokensSaved)}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-bg-elevated/40 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Tokens kept</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">{fmt(repo.tokensKept)}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-bg-elevated/40 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Tokens dropped</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">{fmt(repo.tokensDropped)}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"rounded-md border border-border/50 bg-bg-elevated/40 p-3\">\n\t\t\t\t\t\t<span className=\"block text-xs text-muted-foreground\">Dedup rate</span>\n\t\t\t\t\t\t<span className=\"text-xl font-semibold\">—</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<h3 className=\"mb-2 font-heading text-sm font-semibold\">Model</h3>\n\t\t\t\t<dl className=\"mb-4 grid grid-cols-1 gap-1 text-sm sm:grid-cols-2\">\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Provider</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.providerName ?? \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Model</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.modelName ?? \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Input rate</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.inputRate !== null ? `${repo.inputRate} tok/s` : \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Output rate</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.outputRate !== null ? `${repo.outputRate} tok/s` : \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Context window</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.contextWindow !== null ? fmt(repo.contextWindow) : \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Max tokens</dt>\n\t\t\t\t\t<dd className=\"text-right\">{repo.maxTokens !== null ? fmt(repo.maxTokens) : \"—\"}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Reasoning</dt>\n\t\t\t\t\t<dd className=\"text-right\">\n\t\t\t\t\t\t{repo.reasoning === null ? \"—\" : repo.reasoning ? \"on\" : \"off\"}\n\t\t\t\t\t</dd>\n\t\t\t\t</dl>\n\t\t\t\t<h3 className=\"mb-2 font-heading text-sm font-semibold\">Activity</h3>\n\t\t\t\t<dl className=\"grid grid-cols-1 gap-1 text-sm sm:grid-cols-2\">\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Last compacted</dt>\n\t\t\t\t\t<dd className=\"text-right\">{formatTs(repo.lastCompactedAt)}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Last seen</dt>\n\t\t\t\t\t<dd className=\"text-right\">{formatTs(repo.lastSeen)}</dd>\n\t\t\t\t\t<dt className=\"text-muted-foreground\">Compressed-Original</dt>\n\t\t\t\t\t<dd className=\"text-right\">{fmtBytesTop(repo.compressedOriginalBytes)}</dd>\n\t\t\t\t</dl>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/ActiveReposTable.tsx — Active repos table.\n *\n * Columns: Repo, Model, Tier, Context %, State, Compactions s/t,\n * Cache Hits s/t, Cache Hit %, Cache Read, Cache Write, Est. Saved,\n * Compact s/t (s), CacheHit s/t (s).\n * Data from fetchServers() via useApi with 10s polling.\n */\n\nimport type React from \"react\";\nimport type { ServerEntry } from \"@contracts\";\nimport { fmtTokens } from \"@/utils/format.ts\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\nexport interface ActiveReposTableProps {\n\tservers: ServerEntry[];\n}\n\n/** Format seconds → human-readable duration (matches html.ts fmtSec). */\nfunction fmtSec(s: number | undefined): string {\n\tconst v = s ?? 0;\n\tif (v >= 3600) return `${(v / 3600).toFixed(1)}h`;\n\tif (v >= 60) return `${Math.round(v / 60)}m`;\n\tif (v >= 1) return `${v.toFixed(1)}s`;\n\treturn `${Math.round(v * 1000)}ms`;\n}\n\nconst HEADERS = [\n\t\"Repo\",\n\t\"Model\",\n\t\"Tier\",\n\t\"Context %\",\n\t\"State\",\n\t\"Compactions (s/t)\",\n\t\"Cache Hits (s/t)\",\n\t\"Cache Hit %\",\n\t\"Est. Saved\",\n\t\"Cache Read\",\n\t\"Cache Write\",\n\t\"Compact s/t (s)\",\n\t\"CacheHit s/t (s)\",\n] as const;\n\nexport function ActiveReposTable({\n\tservers,\n}: ActiveReposTableProps): React.ReactElement {\n\treturn (\n\t\t<Card>\n\t\t\t<CardContent>\n\t\t\t<p className=\"mb-3 text-xs text-muted-foreground\">\n\t\t\t\tRepos seen within the last 30 minutes, with their per-repo cache-hit,\n\t\t\t\tcompaction, and time-saved (est.) totals pulled live from each\n\t\t\t\trepo's dashboard.json.\n\t\t\t</p>\n\t\t\t<div className=\"overflow-x-auto\">\n\t\t\t\t<table className=\"w-full border-collapse text-sm\">\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t{HEADERS.map((h, i) => (\n\t\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\t\tkey={h}\n\t\t\t\t\t\t\t\t\tclassName={`border-b border-border px-3 py-2 text-left font-medium text-muted-foreground${\n\t\t\t\t\t\t\t\t\t\ti >= 3 && i !== 4 ? \" text-right\" : \"\"\n\t\t\t\t\t\t\t\t\t}`}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{h}\n\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t{servers.length === 0 && (\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td colSpan={HEADERS.length} className=\"px-3 py-4 text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tNo active repositories.\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{servers.map((r) => {\n\t\t\t\t\t\t\tconst ch = r.cacheHits ?? {\n\t\t\t\t\t\t\t\tsession: 0,\n\t\t\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\t\t\tsessionTokensSaved: 0,\n\t\t\t\t\t\t\t\ttotalTokensSaved: 0,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tconst cp = r.compacts ?? { session: 0, total: 0 };\n\t\t\t\t\t\t\tconst ts = r.timeSaved ?? {\n\t\t\t\t\t\t\t\tcompact: { sessionSec: 0, totalSec: 0 },\n\t\t\t\t\t\t\t\tcacheHit: { sessionSec: 0, totalSec: 0 },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<tr key={r.repoRoot}>\n\t\t\t\t\t\t\t\t\t<td title={r.repoRoot}>{r.displayName || r.repoRoot}</td>\n\t\t\t\t\t\t\t\t\t<td>{r.model ?? \"\\u2014\"}</td>\n\t\t\t\t\t\t\t\t\t<td>{r.tier ?? \"\\u2014\"}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{r.contextPct != null\n\t\t\t\t\t\t\t\t\t\t\t? `${Math.round(r.contextPct * 100)}%`\n\t\t\t\t\t\t\t\t\t\t\t: \"\\u2014\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td>{r.state ?? \"\\u2014\"}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{cp.session} / {cp.total}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{ch.session} / {ch.total}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{r.providerCache != null\n\t\t\t\t\t\t\t\t\t\t\t? `${r.providerCache.avgHitPct.toFixed(1)}%`\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{r.providerCache?.estimatedSaved != null\n\t\t\t\t\t\t\t\t\t\t\t? `$${r.providerCache.estimatedSaved.toFixed(2)}`\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{r.providerCache?.cacheRead != null\n\t\t\t\t\t\t\t\t\t\t\t? fmtTokens(r.providerCache.cacheRead)\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{r.providerCache?.cacheWrite != null\n\t\t\t\t\t\t\t\t\t\t\t? fmtTokens(r.providerCache.cacheWrite)\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{fmtSec(ts.compact.sessionSec)} /{\" \"}\n\t\t\t\t\t\t\t\t\t\t{fmtSec(ts.compact.totalSec)}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{fmtSec(ts.cacheHit.sessionSec)} /{\" \"}\n\t\t\t\t\t\t\t\t\t\t{fmtSec(ts.cacheHit.totalSec)}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n","/**\n * dashboard-client/src/components/SavingsByModelTable.tsx — Savings by Model.\n *\n * 14 columns aggregated client-side from /api/index repos by model.\n * Replicates html.ts renderByModel logic: group by modelName, accumulate\n * tokens/cost/sessions, collapse numeric ranges when repos disagree.\n */\n\nimport type React from \"react\";\nimport { useMemo } from \"react\";\nimport type { IndexesIndexRow } from \"@contracts\";\nimport { computeCacheSavings } from \"@pricing\";\n\nexport interface SavingsByModelTableProps {\n\trepos: IndexesIndexRow[];\n}\n\ninterface ModelGroup {\n\tmodel: string;\n\tprovider: string;\n\tcheckpoints: number;\n\ttokensSaved: number;\n\ttokensIn: number;\n\ttokensOut: number;\n\tsessions: number;\n\tusd: number;\n\tinRates: number[];\n\toutRates: number[];\n\tctxWindows: number[];\n\tmaxTokens: number[];\n\treasoning: boolean | null;\n\tlastAt: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n}\n\n/** Collapse numeric samples: \"—\" | single | \"lo–hi\" (matches html.ts). */\nfunction collapseNum(samples: number[]): string {\n\tif (!samples.length) return \"\\u2014\";\n\tconst lo = Math.min(...samples);\n\tconst hi = Math.max(...samples);\n\treturn lo === hi\n\t\t? lo.toLocaleString()\n\t\t: `${lo.toLocaleString()}\\u2013${hi.toLocaleString()}`;\n}\n\n/** Collapse rate samples: \"—\" | \"$rate\" | \"$lo–$hi\" (matches html.ts). */\nfunction collapseRate(samples: number[]): string {\n\tif (!samples.length) return \"\\u2014\";\n\tconst lo = Math.min(...samples);\n\tconst hi = Math.max(...samples);\n\tconst fmt = (v: number): string => `$${v.toFixed(6)}`;\n\treturn lo === hi ? fmt(lo) : `${fmt(lo)}\\u2013${fmt(hi)}`;\n}\n\n/** Group repos by model, accumulate totals, sort by tokensSaved desc. */\nfunction aggregate(repos: IndexesIndexRow[]): ModelGroup[] {\n\tconst map: Record<string, ModelGroup> = {};\n\tfor (const r of repos) {\n\t\tconst key = (r.modelName && r.modelName.trim()) || \"(unknown)\";\n\t\tif (!map[key]) {\n\t\t\tmap[key] = {\n\t\t\t\tmodel: key,\n\t\t\t\tprovider: r.providerName ?? r.provider ?? \"\\u2014\",\n\t\t\t\tcheckpoints: 0,\n\t\t\t\ttokensSaved: 0,\n\t\t\t\ttokensIn: 0,\n\t\t\t\ttokensOut: 0,\n\t\t\t\tsessions: 0,\n\t\t\t\tusd: 0,\n\t\t\t\tinRates: [],\n\t\t\t\toutRates: [],\n\t\t\t\tctxWindows: [],\n\t\t\t\tmaxTokens: [],\n\t\t\t\treasoning: null,\n\t\t\t\tlastAt: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t};\n\t\t}\n\t\tconst g = map[key];\n\t\tg.checkpoints += r.checkpointCount || 0;\n\t\tg.tokensSaved += r.tokensSaved || 0;\n\t\tg.tokensIn += r.tokensDropped || 0;\n\t\tg.tokensOut += r.tokensKept || 0;\n\t\tg.sessions += r.sessions || 0;\n\t\tif (r.inputRate) {\n\t\t\tg.usd += (r.tokensSaved || 0) * r.inputRate;\n\t\t\tg.inRates.push(r.inputRate);\n\t\t}\n\t\tif (r.outputRate) g.outRates.push(r.outputRate);\n\t\tif (r.contextWindow) g.ctxWindows.push(r.contextWindow);\n\t\tif (r.maxTokens) g.maxTokens.push(r.maxTokens);\n\t\tif (r.reasoning != null) g.reasoning = r.reasoning;\n\t\tif (r.providerCacheRead) g.cacheRead += r.providerCacheRead;\n\t\tif (r.providerCacheWrite) g.cacheWrite += r.providerCacheWrite;\n\t\tif (r.lastCompactedAt && r.lastCompactedAt > g.lastAt)\n\t\t\tg.lastAt = r.lastCompactedAt;\n\t}\n\treturn Object.values(map).sort((a, b) => b.tokensSaved - a.tokensSaved);\n}\n\n/** Tooltip text copied verbatim from html.ts title attributes. */\nconst TOOLTIPS = {\n\ttokensIn: \"Tokens dropped from context by compaction (the input reclaimed)\",\n\ttokensOut:\n\t\t\"Tokens kept as compacted summaries still in context (the output retained)\",\n\tctxWindow: \"Model context window (max input tokens the model accepts)\",\n\tmaxOut: \"Model max output tokens per turn\",\n\treas: \"Reasoning-capable model\",\n\tsessions: \"Distinct sessions with at least one checkpoint\",\n\tinRate: \"USD per input token\",\n\toutRate: \"USD per output token\",\n} as const;\n\nexport function SavingsByModelTable({\n\trepos,\n}: SavingsByModelTableProps): React.ReactElement {\n\tconst groups = useMemo(() => aggregate(repos), [repos]);\n\n\treturn (\n\t\t<div className=\"savings-by-model\">\n\t\t\t<p className=\"legend-note\">\n\t\t\t\tHow much context & cost mega-compact has reclaimed, grouped by the\n\t\t\t\tmodel you were running. Compression ratio reflects workload/content, not\n\t\t\t\tmodel quality.\n\t\t\t</p>\n\t\t\t<div className=\"table-scroll\">\n\t\t\t\t<table className=\"savings-table\">\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<th>Model</th>\n\t\t\t\t\t\t\t<th>Provider</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.tokensIn}>\n\t\t\t\t\t\t\t\tTokens In\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.tokensOut}>\n\t\t\t\t\t\t\t\tTokens Out\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\">Freed</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.ctxWindow}>\n\t\t\t\t\t\t\t\tCtx Window\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.maxOut}>\n\t\t\t\t\t\t\t\tMax Out\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.reas}>\n\t\t\t\t\t\t\t\tReas.\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.sessions}>\n\t\t\t\t\t\t\t\tSessions\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\">Checkpoints</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.inRate}>\n\t\t\t\t\t\t\t\tIn $/tok\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\" title={TOOLTIPS.outRate}>\n\t\t\t\t\t\t\t\tOut $/tok\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\">$ Saved</th>\n\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\tclassName=\"num\"\n\t\t\t\t\t\t\t\ttitle=\"Provider prompt-cache hit rate (reads / reads+writes+tokens_in)\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tCache Hit %\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\tclassName=\"num\"\n\t\t\t\t\t\t\t\ttitle=\"Estimated provider prompt-cache savings (read tokens * 0.9 * inputRate − write tokens * 0.25 * inputRate)\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tCache $ Saved\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th className=\"num\">Last Used</th>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t{groups.length === 0 && (\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td colSpan={16} className=\"repo-empty\">\n\t\t\t\t\t\t\t\t\tNo repositories registered yet.\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{groups.map((g) => {\n\t\t\t\t\t\t\tconst freed = (g.tokensIn || 0) - (g.tokensOut || 0);\n\t\t\t\t\t\t\tconst usd = g.usd > 0 ? `$${g.usd.toFixed(4)}` : \"\\u2014\";\n\t\t\t\t\t\t\tconst when = g.lastAt\n\t\t\t\t\t\t\t\t? new Date(g.lastAt).toLocaleString()\n\t\t\t\t\t\t\t\t: \"\\u2014\";\n\t\t\t\t\t\t\tconst reas =\n\t\t\t\t\t\t\t\tg.reasoning == null ? \"\\u2014\" : g.reasoning ? \"yes\" : \"no\";\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<tr key={g.model}>\n\t\t\t\t\t\t\t\t\t<td className=\"repo-model\">{g.model}</td>\n\t\t\t\t\t\t\t\t\t<td>{g.provider}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{g.tokensIn.toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{g.tokensOut.toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{freed.toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{collapseNum(g.ctxWindows)}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{collapseNum(g.maxTokens)}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{reas}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{g.sessions.toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{g.checkpoints.toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{collapseRate(g.inRates)}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{collapseRate(g.outRates)}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{usd}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{g.cacheRead > 0\n\t\t\t\t\t\t\t\t\t\t\t? `${(\n\t\t\t\t\t\t\t\t\t\t\t\t\t(g.cacheRead * 100) /\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t(g.cacheRead + g.cacheWrite + g.tokensIn || 1)\n\t\t\t\t\t\t\t\t\t\t\t\t).toFixed(1)}%`\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">\n\t\t\t\t\t\t\t\t\t\t{g.cacheRead > 0\n\t\t\t\t\t\t\t\t\t\t\t? `$${computeCacheSavings(g.cacheRead, g.cacheWrite, g.inRates[0] || 0).netSaved.toFixed(4)}`\n\t\t\t\t\t\t\t\t\t\t\t: \"—\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"num\">{when}</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t</div>\n\t\t\t<p className=\"legend-note\">\n\t\t\t\tTokens In = Σ original region tokens dropped by compaction. Tokens Out =\n\t\t\t\tΣ compacted summary tokens still retained in context. Freed = Tokens In\n\t\t\t\t− Tokens Out (net context reclaimed). Ctx Window / Max Out / Reas. come\n\t\t\t\tfrom the latest captured model snapshot for each repo.\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/ReposTab.tsx — Repos tab (C2 fleshed out).\n *\n * Renders: SummaryTiles (4 tiles), All Repositories table (RepoTable),\n * Active Repos table (ActiveReposTable), Savings by Model table\n * (SavingsByModelTable), and per-repo detail modal (RepoDetailModal).\n *\n * Data sources: fetchIndex() for summary + all repos + savings-by-model;\n * fetchServers() for active repos. Both polled every 10s.\n */\n\nimport type React from \"react\";\nimport { useCallback, useState } from \"react\";\nimport { useApi } from \"../hooks/useApi\";\nimport { fetchIndex, fetchServers } from \"../api/client\";\nimport type { IndexesIndexRow, ServersResponse } from \"@contracts\";\nimport { SummaryTiles } from \"../components/SummaryTiles\";\nimport { RepoTable } from \"../components/RepoTable\";\nimport { RepoDetailModal } from \"../components/RepoDetailModal\";\nimport { ActiveReposTable } from \"../components/ActiveReposTable\";\nimport { SavingsByModelTable } from \"../components/SavingsByModelTable\";\n\n/**\n * Actual runtime shape of /api/index. The contract's IndexesSummaryResponse\n * puts summary fields at the top level, but the real server nests them under\n * `summary` (matching IndexFallbackResponse shape with non-null summary).\n */\ninterface DashboardIndexSummary {\n\ttotalRepos: number;\n\ttotalCheckpoints: number;\n\ttotalTokensSaved: number;\n\ttotalCompressedOriginalBytes: number;\n}\n\ninterface DashboardIndexResponse {\n\tupdatedAt: string | null;\n\tsummary: DashboardIndexSummary | null;\n\trepos: IndexesIndexRow[];\n}\n\n/** Fetch /api/index and cast to the actual runtime shape. */\nasync function fetchIndexTyped(): Promise<DashboardIndexResponse> {\n\treturn (await fetchIndex()) as unknown as DashboardIndexResponse;\n}\n\nexport default function ReposTab(): React.ReactElement {\n\tconst [selected, setSelected] = useState<IndexesIndexRow | null>(null);\n\n\tconst { data: indexData, error: indexErr } =\n\t\tuseApi<DashboardIndexResponse>(\n\t\t\tuseCallback(() => fetchIndexTyped(), []),\n\t\t\t{ pollInterval: 10_000 },\n\t\t);\n\n\tconst { data: serversData } = useApi<ServersResponse>(\n\t\tuseCallback(() => fetchServers(), []),\n\t\t{ pollInterval: 10_000 },\n\t);\n\n\tif (indexErr && !indexData) {\n\t\treturn (\n\t\t\t<div className=\"tab-stub\">Error loading repos: {indexErr.message}</div>\n\t\t);\n\t}\n\tif (!indexData) {\n\t\treturn <div className=\"tab-stub\">Loading repos…</div>;\n\t}\n\n\tconst summary = indexData.summary;\n\tconst repos = indexData.repos;\n\tconst servers = serversData?.servers ?? [];\n\n\tif (repos.length === 0) {\n\t\treturn (\n\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t<SummaryTiles\n\t\t\t\t\ttotalRepos={0}\n\t\t\t\t\ttotalCheckpoints={0}\n\t\t\t\t\ttotalTokensSaved={0}\n\t\t\t\t\tcompressedOriginalBytes={0}\n\t\t\t\t/>\n\t\t\t\t<div className=\"tab-stub\">\n\t\t\t\t\t<h3>Repositories / Memory</h3>\n\t\t\t\t\t<p>\n\t\t\t\t\t\tRepo memory appears after the first compaction. Compaction fires\n\t\t\t\t\t\twhen context crosses the tier threshold (~50-70% of the window).\n\t\t\t\t\t\tRun a longer session or lower the compaction tier to see it sooner.\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<SummaryTiles\n\t\t\t\ttotalRepos={summary?.totalRepos ?? 0}\n\t\t\t\ttotalCheckpoints={summary?.totalCheckpoints ?? 0}\n\t\t\t\ttotalTokensSaved={summary?.totalTokensSaved ?? 0}\n\t\t\t\tcompressedOriginalBytes={summary?.totalCompressedOriginalBytes ?? 0}\n\t\t\t/>\n\t\t\t<h2 className=\"font-heading text-lg font-semibold\">All Repositories</h2>\n\t\t\t<RepoTable repos={repos} onSelect={setSelected} />\n\t\t\t<h2 className=\"font-heading text-lg font-semibold\">\n\t\t\t\tActive Repos — Live Cache Hits & Compactions\n\t\t\t</h2>\n\t\t\t<ActiveReposTable servers={servers} />\n\t\t\t<h2 className=\"font-heading text-lg font-semibold\">Savings by Model</h2>\n\t\t\t<SavingsByModelTable repos={repos} />\n\t\t\t{selected && (\n\t\t\t\t<RepoDetailModal repo={selected} onClose={() => setSelected(null)} />\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["fmtBytesTop","b","fmt","n","SummaryTiles","totalRepos","totalCheckpoints","totalTokensSaved","compressedOriginalBytes","jsxs","jsx","Card","CardContent","COLUMNS","formatTs","ts","RepoTable","repos","onSelect","search","setSearch","useState","sortKey","setSortKey","sortDir","setSortDir","filtered","useMemo","q","list","r","a","av","bv","aNull","bNull","cmp","handleSort","key","d","e","col","repo","RepoDetailModal","onClose","useEffect","handler","fmtSec","s","v","HEADERS","ActiveReposTable","servers","h","i","ch","cp","fmtTokens","collapseNum","samples","lo","hi","collapseRate","aggregate","map","g","TOOLTIPS","SavingsByModelTable","groups","freed","usd","when","reas","computeCacheSavings","fetchIndexTyped","fetchIndex","ReposTab","selected","setSelected","indexData","indexErr","useApi","useCallback","serversData","fetchServers","summary"],"mappings":"qMAsBA,SAASA,EAAYC,EAAmB,CACvC,OAAIA,GAAK,QAAkB,IAAIA,EAAI,SAAW,QAAQ,CAAC,CAAC,OACpDA,GAAK,KAAa,IAAIA,EAAI,MAAM,QAAQ,CAAC,CAAC,OACvC,GAAGA,CAAC,IACZ,CAGA,SAASC,EAAIC,EAAmB,CAC/B,OAAIA,GAAK,IAAkB,IAAIA,EAAI,KAAW,QAAQ,CAAC,CAAC,IACpDA,GAAK,IAAc,IAAIA,EAAI,KAAO,QAAQ,CAAC,CAAC,IACzC,OAAOA,CAAC,CAChB,CAEO,SAASC,EAAa,CAC5B,WAAAC,EACA,iBAAAC,EACA,iBAAAC,EACA,wBAAAC,CACD,EAA0C,CACzC,OACCC,EAAAA,KAAC,MAAA,CAAI,UAAU,uDACd,SAAA,CAAAC,MAACC,EAAA,CAAK,UAAU,iBACf,SAAAF,EAAAA,KAACG,EAAA,CACA,SAAA,CAAAF,EAAAA,IAAC,OAAA,CAAK,UAAU,aAAa,SAAA,eAAY,QACxC,MAAA,CAAI,UAAU,wBAAyB,SAAAL,EAAW,gBAAe,CAAE,CAAA,CAAA,CACrE,CAAA,CACD,EACAK,MAACC,EAAA,CAAK,UAAU,iBACf,gBAACC,EAAA,CACA,SAAA,CAAAF,EAAAA,IAAC,OAAA,CAAK,UAAU,aAAa,SAAA,oBAAiB,QAC7C,MAAA,CAAI,UAAU,wBAAyB,SAAAJ,EAAiB,gBAAe,CAAE,CAAA,CAAA,CAC3E,CAAA,CACD,EACAI,MAACC,EAAA,CAAK,UAAU,iBACf,gBAACC,EAAA,CACA,SAAA,CAAAF,EAAAA,IAAC,OAAA,CAAK,UAAU,aAAa,SAAA,qBAAkB,QAC9C,MAAA,CAAI,UAAU,wBAAyB,SAAAR,EAAIK,CAAgB,CAAA,CAAE,CAAA,CAAA,CAC/D,CAAA,CACD,EACAG,MAACC,EAAA,CAAK,UAAU,iBACf,gBAACC,EAAA,CACA,SAAA,CAAAF,EAAAA,IAAC,OAAA,CAAK,UAAU,aAAa,SAAA,sBAAmB,QAC/C,MAAA,CAAI,UAAU,wBAAyB,SAAAV,EAAYQ,CAAuB,CAAA,CAAE,CAAA,CAAA,CAC9E,CAAA,CACD,CAAA,EACD,CAEF,CCvCA,MAAMK,EAAkD,CACvD,CAAE,IAAK,cAAe,MAAO,MAAA,EAC7B,CAAE,IAAK,YAAa,MAAO,OAAA,EAC3B,CAAE,IAAK,kBAAmB,MAAO,aAAA,EACjC,CAAE,IAAK,cAAe,MAAO,cAAA,EAC7B,CAAE,IAAK,0BAA2B,MAAO,UAAA,EACzC,CAAE,IAAK,kBAAmB,MAAO,gBAAA,CAClC,EAGA,SAASb,EAAYC,EAAmB,CACvC,OAAIA,GAAK,QAAkB,IAAIA,EAAI,SAAW,QAAQ,CAAC,CAAC,OACpDA,GAAK,KAAa,IAAIA,EAAI,MAAM,QAAQ,CAAC,CAAC,OACvC,GAAGA,CAAC,IACZ,CAEA,SAASa,EAASC,EAA2B,CAC5C,GAAIA,GAAO,KAA0B,MAAO,IAC5C,GAAI,CACH,OAAO,IAAI,KAAKA,CAAE,EAAE,eAAA,CACrB,MAAQ,CACP,OAAO,OAAOA,CAAE,CACjB,CACD,CAEO,SAASC,EAAU,CACzB,MAAAC,EACA,SAAAC,CACD,EAAuC,CACtC,KAAM,CAACC,EAAQC,CAAS,EAAIC,EAAAA,SAAS,EAAE,EACjC,CAACC,EAASC,CAAU,EAAIF,EAAAA,SAAkB,iBAAiB,EAC3D,CAACG,EAASC,CAAU,EAAIJ,EAAAA,SAAkB,MAAM,EAEhDK,EAAWC,EAAAA,QAAQ,IAAM,CAC9B,MAAMC,EAAIT,EAAO,KAAA,EAAO,YAAA,EAClBU,EAAOD,EACVX,EAAM,OAAQa,GAAMA,EAAE,YAAY,YAAA,EAAc,SAASF,CAAC,CAAC,EAC3DX,EAAM,MAAA,EACT,OAAAY,EAAK,KAAK,CAACE,EAAG9B,IAAM,CACnB,MAAM+B,EAAKD,EAAET,CAAO,EACdW,EAAKhC,EAAEqB,CAAO,EAEdY,EAAQF,GAAO,KACfG,EAAQF,GAAO,KACrB,GAAIC,GAASC,EAAO,MAAO,GAC3B,GAAID,EAAO,OAAOV,IAAY,OAAS,EAAI,GAC3C,GAAIW,EAAO,OAAOX,IAAY,OAAS,GAAK,EAC5C,MAAMY,EACL,OAAOJ,GAAO,UAAY,OAAOC,GAAO,SACrCD,EAAKC,EACL,OAAOD,CAAE,EAAE,cAAc,OAAOC,CAAE,CAAC,EACvC,OAAOT,IAAY,OAAS,CAACY,EAAMA,CACpC,CAAC,EACMP,CACR,EAAG,CAACZ,EAAOE,EAAQG,EAASE,CAAO,CAAC,EAE9Ba,EAAcC,GAAuB,CACtChB,IAAYgB,EACfb,EAAYc,GAAOA,IAAM,MAAQ,OAAS,KAAM,GAEhDhB,EAAWe,CAAG,EACdb,EACCa,IAAQ,mBACRA,IAAQ,eACRA,IAAQ,0BACL,OACA,KAAA,EAGN,EAEA,OACC5B,EAAAA,IAACC,EAAA,CACA,SAAAF,EAAAA,KAACG,EAAA,CACD,SAAA,CAAAF,EAAAA,IAAC,QAAA,CACA,KAAK,SACL,UAAU,0LACV,YAAY,gBACZ,MAAOS,EACP,SAAWqB,GAAMpB,EAAUoB,EAAE,OAAO,KAAK,EACzC,aAAW,cAAA,CAAA,QAEX,MAAA,CAAI,UAAU,kBACf,SAAA/B,EAAAA,KAAC,QAAA,CAAM,UAAU,iCAChB,SAAA,CAAAC,EAAAA,IAAC,QAAA,CACA,SAAAA,EAAAA,IAAC,KAAA,CACC,SAAAG,EAAQ,IAAK4B,GACb/B,EAAAA,IAAC,KAAA,CAAiB,UAAU,6CAC3B,SAAAD,EAAAA,KAAC,SAAA,CACA,KAAK,SACL,UAAW,6EACVa,IAAYmB,EAAI,IAAM,YAAc,EACrC,GACA,QAAS,IAAMJ,EAAWI,EAAI,GAAG,EAEhC,SAAA,CAAAA,EAAI,MACJnB,IAAYmB,EAAI,KAChB/B,EAAAA,IAAC,QAAM,SAAAc,IAAY,MAAQ,KAAO,IAAA,CAAK,CAAA,CAAA,CAAA,CAEzC,EAZQiB,EAAI,GAab,CACA,EACF,CAAA,CACD,SACC,QAAA,CACC,SAAA,CAAAf,EAAS,SAAW,GACpBhB,MAAC,KAAA,CACA,SAAAA,EAAAA,IAAC,MAAG,QAASG,EAAQ,OAAQ,UAAU,kCACrC,SAAAI,EAAM,SAAW,EACf,kCACA,8BACJ,EACD,EAEAS,EAAS,IAAKgB,GACdjC,EAAAA,KAAC,KAAA,CAEA,QAAS,IAAMS,EAASwB,CAAI,EAC5B,UAAU,qFAEV,SAAA,CAAAhC,EAAAA,IAAC,MAAG,UAAU,wBAAwB,MAAOgC,EAAK,SAChD,WAAK,WAAA,CACP,QACC,KAAA,CAAG,UAAU,kCAAmC,SAAAA,EAAK,WAAa,IAAS,QAC3E,KAAA,CAAG,UAAU,uBAAwB,SAAAA,EAAK,gBAAgB,iBAAiB,QAC3E,KAAA,CAAG,UAAU,uBAAwB,SAAAA,EAAK,YAAY,iBAAiB,QACvE,KAAA,CAAG,UAAU,uBAAwB,SAAA1C,EAAY0C,EAAK,uBAAuB,EAAE,QAC/E,KAAA,CAAG,UAAU,uBAAwB,SAAA5B,EAAS4B,EAAK,eAAe,CAAA,CAAE,CAAA,CAAA,EAXhEA,EAAK,QAAA,CAaX,CAAA,CAAA,CACF,CAAA,CAAA,CACD,CAAA,CACA,CAAA,CAAA,CACA,CAAA,CACD,CAEF,CCtJA,SAASxC,EAAIC,EAAmB,CAC/B,OAAIA,GAAK,IAAkB,IAAIA,EAAI,KAAW,QAAQ,CAAC,CAAC,IACpDA,GAAK,IAAc,IAAIA,EAAI,KAAO,QAAQ,CAAC,CAAC,IACzC,OAAOA,CAAC,CAChB,CAGA,SAASH,EAAYC,EAAmB,CACvC,OAAIA,GAAK,QAAkB,IAAIA,EAAI,SAAW,QAAQ,CAAC,CAAC,OACpDA,GAAK,KAAa,IAAIA,EAAI,MAAM,QAAQ,CAAC,CAAC,OACvC,GAAGA,CAAC,IACZ,CAEA,SAASa,EAASC,EAA2B,CAC5C,GAAIA,GAAO,KAA0B,MAAO,QAC5C,GAAI,CACH,OAAO,IAAI,KAAKA,CAAE,EAAE,eAAA,CACrB,MAAQ,CACP,OAAO,OAAOA,CAAE,CACjB,CACD,CAEO,SAAS4B,EAAgB,CAC/B,KAAAD,EACA,QAAAE,CACD,EAA6C,CAE5CC,OAAAA,EAAAA,UAAU,IAAM,CACf,MAAMC,EAAWN,GAA2B,CACvCA,EAAE,MAAQ,UAAUI,EAAA,CACzB,EACA,cAAO,iBAAiB,UAAWE,CAAO,EACnC,IAAM,OAAO,oBAAoB,UAAWA,CAAO,CAC3D,EAAG,CAACF,CAAO,CAAC,EAGXlC,EAAAA,IAAC,MAAA,CACA,UAAU,sEACV,QAASkC,EACT,KAAK,SACL,aAAW,OAEX,SAAAnC,EAAAA,KAAC,MAAA,CACA,UAAU,2GACV,QAAU+B,GAAMA,EAAE,gBAAA,EAElB,SAAA,CAAA/B,EAAAA,KAAC,SAAA,CAAO,UAAU,8CACjB,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAG,UAAU,qCAAsC,SAAAgC,EAAK,YAAY,EACrEhC,EAAAA,IAAC,SAAA,CACA,KAAK,SACL,UAAU,oEACV,QAASkC,EACT,aAAW,QACX,SAAA,GAAA,CAAA,CAED,EACD,EACAlC,EAAAA,IAAC,IAAA,CAAE,UAAU,yDAA0D,WAAK,SAAS,EACrFD,EAAAA,KAAC,MAAA,CAAI,UAAU,6CACd,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAU,2DACd,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,sCAAsC,SAAA,cAAW,QAChE,OAAA,CAAK,UAAU,wBACd,SAAAgC,EAAK,gBAAgB,gBAAe,CACtC,CAAA,EACD,EACAjC,EAAAA,KAAC,MAAA,CAAI,UAAU,2DACd,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,sCAAsC,SAAA,WAAQ,QAC7D,OAAA,CAAK,UAAU,wBACd,SAAAgC,EAAK,SAAS,gBAAe,CAC/B,CAAA,EACD,EACAjC,EAAAA,KAAC,MAAA,CAAI,UAAU,uDACd,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,sCAAsC,SAAA,eAAY,QACjE,OAAA,CAAK,UAAU,wBAAyB,SAAAR,EAAIwC,EAAK,WAAW,CAAA,CAAE,CAAA,EAChE,EACAjC,EAAAA,KAAC,MAAA,CAAI,UAAU,2DACd,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,sCAAsC,SAAA,cAAW,QAChE,OAAA,CAAK,UAAU,wBAAyB,SAAAR,EAAIwC,EAAK,UAAU,CAAA,CAAE,CAAA,EAC/D,EACAjC,EAAAA,KAAC,MAAA,CAAI,UAAU,2DACd,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,sCAAsC,SAAA,iBAAc,QACnE,OAAA,CAAK,UAAU,wBAAyB,SAAAR,EAAIwC,EAAK,aAAa,CAAA,CAAE,CAAA,EAClE,EACAjC,EAAAA,KAAC,MAAA,CAAI,UAAU,2DACd,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,sCAAsC,SAAA,aAAU,EAChEA,EAAAA,IAAC,OAAA,CAAK,UAAU,wBAAwB,SAAA,GAAA,CAAC,CAAA,CAAA,CAC1C,CAAA,EACD,EACAA,EAAAA,IAAC,KAAA,CAAG,UAAU,0CAA0C,SAAA,QAAK,EAC7DD,EAAAA,KAAC,KAAA,CAAG,UAAU,qDACb,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAG,UAAU,wBAAwB,SAAA,WAAQ,QAC7C,KAAA,CAAG,UAAU,aAAc,SAAAgC,EAAK,cAAgB,IAAI,EACrDhC,EAAAA,IAAC,KAAA,CAAG,UAAU,wBAAwB,SAAA,QAAK,QAC1C,KAAA,CAAG,UAAU,aAAc,SAAAgC,EAAK,WAAa,IAAI,EAClDhC,EAAAA,IAAC,KAAA,CAAG,UAAU,wBAAwB,SAAA,aAAU,EAChDA,EAAAA,IAAC,KAAA,CAAG,UAAU,aAAc,SAAAgC,EAAK,YAAc,KAAO,GAAGA,EAAK,SAAS,SAAW,GAAA,CAAI,EACtFhC,EAAAA,IAAC,KAAA,CAAG,UAAU,wBAAwB,SAAA,cAAW,EACjDA,EAAAA,IAAC,KAAA,CAAG,UAAU,aAAc,SAAAgC,EAAK,aAAe,KAAO,GAAGA,EAAK,UAAU,SAAW,GAAA,CAAI,EACxFhC,EAAAA,IAAC,KAAA,CAAG,UAAU,wBAAwB,SAAA,iBAAc,EACpDA,EAAAA,IAAC,KAAA,CAAG,UAAU,aAAc,SAAAgC,EAAK,gBAAkB,KAAOxC,EAAIwC,EAAK,aAAa,EAAI,GAAA,CAAI,EACxFhC,EAAAA,IAAC,KAAA,CAAG,UAAU,wBAAwB,SAAA,aAAU,EAChDA,EAAAA,IAAC,KAAA,CAAG,UAAU,aAAc,SAAAgC,EAAK,YAAc,KAAOxC,EAAIwC,EAAK,SAAS,EAAI,GAAA,CAAI,EAChFhC,EAAAA,IAAC,KAAA,CAAG,UAAU,wBAAwB,SAAA,YAAS,EAC/CA,EAAAA,IAAC,KAAA,CAAG,UAAU,aACZ,SAAAgC,EAAK,YAAc,KAAO,IAAMA,EAAK,UAAY,KAAO,KAAA,CAC1D,CAAA,EACD,EACAhC,EAAAA,IAAC,KAAA,CAAG,UAAU,0CAA0C,SAAA,WAAQ,EAChED,EAAAA,KAAC,KAAA,CAAG,UAAU,gDACb,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAG,UAAU,wBAAwB,SAAA,iBAAc,QACnD,KAAA,CAAG,UAAU,aAAc,SAAAI,EAAS4B,EAAK,eAAe,EAAE,EAC3DhC,EAAAA,IAAC,KAAA,CAAG,UAAU,wBAAwB,SAAA,YAAS,QAC9C,KAAA,CAAG,UAAU,aAAc,SAAAI,EAAS4B,EAAK,QAAQ,EAAE,EACpDhC,EAAAA,IAAC,KAAA,CAAG,UAAU,wBAAwB,SAAA,sBAAmB,QACxD,KAAA,CAAG,UAAU,aAAc,SAAAV,EAAY0C,EAAK,uBAAuB,CAAA,CAAE,CAAA,CAAA,CACvE,CAAA,CAAA,CAAA,CACD,CAAA,CAGH,CCpHA,SAASK,EAAOC,EAA+B,CAC9C,MAAMC,EAAID,GAAK,EACf,OAAIC,GAAK,KAAa,IAAIA,EAAI,MAAM,QAAQ,CAAC,CAAC,IAC1CA,GAAK,GAAW,GAAG,KAAK,MAAMA,EAAI,EAAE,CAAC,IACrCA,GAAK,EAAU,GAAGA,EAAE,QAAQ,CAAC,CAAC,IAC3B,GAAG,KAAK,MAAMA,EAAI,GAAI,CAAC,IAC/B,CAEA,MAAMC,EAAU,CACf,OACA,QACA,OACA,YACA,QACA,oBACA,mBACA,cACA,aACA,aACA,cACA,kBACA,kBACD,EAEO,SAASC,EAAiB,CAChC,QAAAC,CACD,EAA8C,CAC7C,OACC1C,EAAAA,IAACC,EAAA,CACA,SAAAF,EAAAA,KAACG,EAAA,CACD,SAAA,CAAAF,EAAAA,IAAC,IAAA,CAAE,UAAU,qCAAqC,SAAA,8JAIlD,QACC,MAAA,CAAI,UAAU,kBACd,SAAAD,EAAAA,KAAC,QAAA,CAAM,UAAU,iCAChB,SAAA,CAAAC,EAAAA,IAAC,SACA,SAAAA,EAAAA,IAAC,KAAA,CACC,WAAQ,IAAI,CAAC2C,EAAGC,IAChB5C,EAAAA,IAAC,KAAA,CAEA,UAAW,+EACV4C,GAAK,GAAKA,IAAM,EAAI,cAAgB,EACrC,GAEC,SAAAD,CAAA,EALIA,CAAA,CAON,EACF,CAAA,CACD,SACC,QAAA,CACC,SAAA,CAAAD,EAAQ,SAAW,GACnB1C,EAAAA,IAAC,KAAA,CACA,SAAAA,EAAAA,IAAC,KAAA,CAAG,QAASwC,EAAQ,OAAQ,UAAU,kCAAkC,SAAA,yBAAA,CAEzE,EACD,EAEAE,EAAQ,IAAKtB,GAAM,CACnB,MAAMyB,EAAKzB,EAAE,WAAa,CACzB,QAAS,EACT,MAAO,CAGR,EACM0B,EAAK1B,EAAE,UAAY,CAAE,QAAS,EAAG,MAAO,CAAA,EACxCf,EAAKe,EAAE,WAAa,CACzB,QAAS,CAAE,WAAY,EAAG,SAAU,CAAA,EACpC,SAAU,CAAE,WAAY,EAAG,SAAU,CAAA,CAAE,EAExC,cACE,KAAA,CACA,SAAA,CAAApB,EAAAA,IAAC,MAAG,MAAOoB,EAAE,SAAW,SAAAA,EAAE,aAAeA,EAAE,QAAA,CAAS,EACpDpB,EAAAA,IAAC,KAAA,CAAI,SAAAoB,EAAE,OAAS,IAAS,EACzBpB,EAAAA,IAAC,KAAA,CAAI,SAAAoB,EAAE,MAAQ,IAAS,EACxBpB,EAAAA,IAAC,KAAA,CAAG,UAAU,MACZ,WAAE,YAAc,KACd,GAAG,KAAK,MAAMoB,EAAE,WAAa,GAAG,CAAC,IACjC,IACJ,EACApB,EAAAA,IAAC,KAAA,CAAI,SAAAoB,EAAE,OAAS,IAAS,EACzBrB,EAAAA,KAAC,KAAA,CAAG,UAAU,MACZ,SAAA,CAAA+C,EAAG,QAAQ,MAAIA,EAAG,KAAA,EACpB,EACA/C,EAAAA,KAAC,KAAA,CAAG,UAAU,MACZ,SAAA,CAAA8C,EAAG,QAAQ,MAAIA,EAAG,KAAA,EACpB,EACA7C,EAAAA,IAAC,KAAA,CAAG,UAAU,MACZ,WAAE,eAAiB,KACjB,GAAGoB,EAAE,cAAc,UAAU,QAAQ,CAAC,CAAC,IACvC,IACJ,QACC,KAAA,CAAG,UAAU,MACZ,SAAAA,EAAE,eAAe,gBAAkB,KACjC,IAAIA,EAAE,cAAc,eAAe,QAAQ,CAAC,CAAC,GAC7C,IACJ,EACApB,EAAAA,IAAC,KAAA,CAAG,UAAU,MACZ,SAAAoB,EAAE,eAAe,WAAa,KAC5B2B,EAAU3B,EAAE,cAAc,SAAS,EACnC,IACJ,EACApB,EAAAA,IAAC,KAAA,CAAG,UAAU,MACZ,SAAAoB,EAAE,eAAe,YAAc,KAC7B2B,EAAU3B,EAAE,cAAc,UAAU,EACpC,IACJ,EACArB,EAAAA,KAAC,KAAA,CAAG,UAAU,MACZ,SAAA,CAAAsC,EAAOhC,EAAG,QAAQ,UAAU,EAAE,KAAG,IACjCgC,EAAOhC,EAAG,QAAQ,QAAQ,CAAA,EAC5B,EACAN,EAAAA,KAAC,KAAA,CAAG,UAAU,MACZ,SAAA,CAAAsC,EAAOhC,EAAG,SAAS,UAAU,EAAE,KAAG,IAClCgC,EAAOhC,EAAG,SAAS,QAAQ,CAAA,CAAA,CAC7B,CAAA,CAAA,EA3CQe,EAAE,QA4CX,CAEF,CAAC,CAAA,CAAA,CACF,CAAA,CAAA,CACD,CAAA,CACD,CAAA,CAAA,CACA,CAAA,CACD,CAEF,CC3GA,SAAS4B,EAAYC,EAA2B,CAC/C,GAAI,CAACA,EAAQ,OAAQ,MAAO,IAC5B,MAAMC,EAAK,KAAK,IAAI,GAAGD,CAAO,EACxBE,EAAK,KAAK,IAAI,GAAGF,CAAO,EAC9B,OAAOC,IAAOC,EACXD,EAAG,eAAA,EACH,GAAGA,EAAG,eAAA,CAAgB,IAASC,EAAG,eAAA,CAAgB,EACtD,CAGA,SAASC,EAAaH,EAA2B,CAChD,GAAI,CAACA,EAAQ,OAAQ,MAAO,IAC5B,MAAMC,EAAK,KAAK,IAAI,GAAGD,CAAO,EACxBE,EAAK,KAAK,IAAI,GAAGF,CAAO,EACxBzD,EAAO+C,GAAsB,IAAIA,EAAE,QAAQ,CAAC,CAAC,GACnD,OAAOW,IAAOC,EAAK3D,EAAI0D,CAAE,EAAI,GAAG1D,EAAI0D,CAAE,CAAC,IAAS1D,EAAI2D,CAAE,CAAC,EACxD,CAGA,SAASE,EAAU9C,EAAwC,CAC1D,MAAM+C,EAAkC,CAAA,EACxC,UAAWlC,KAAKb,EAAO,CACtB,MAAMqB,EAAOR,EAAE,WAAaA,EAAE,UAAU,QAAW,YAC9CkC,EAAI1B,CAAG,IACX0B,EAAI1B,CAAG,EAAI,CACV,MAAOA,EACP,SAAUR,EAAE,cAAgBA,EAAE,UAAY,IAC1C,YAAa,EACb,YAAa,EACb,SAAU,EACV,UAAW,EACX,SAAU,EACV,IAAK,EACL,QAAS,CAAA,EACT,SAAU,CAAA,EACV,WAAY,CAAA,EACZ,UAAW,CAAA,EACX,UAAW,KACX,OAAQ,EACR,UAAW,EACX,WAAY,CAAA,GAGd,MAAMmC,EAAID,EAAI1B,CAAG,EACjB2B,EAAE,aAAenC,EAAE,iBAAmB,EACtCmC,EAAE,aAAenC,EAAE,aAAe,EAClCmC,EAAE,UAAYnC,EAAE,eAAiB,EACjCmC,EAAE,WAAanC,EAAE,YAAc,EAC/BmC,EAAE,UAAYnC,EAAE,UAAY,EACxBA,EAAE,YACLmC,EAAE,MAAQnC,EAAE,aAAe,GAAKA,EAAE,UAClCmC,EAAE,QAAQ,KAAKnC,EAAE,SAAS,GAEvBA,EAAE,YAAYmC,EAAE,SAAS,KAAKnC,EAAE,UAAU,EAC1CA,EAAE,eAAemC,EAAE,WAAW,KAAKnC,EAAE,aAAa,EAClDA,EAAE,WAAWmC,EAAE,UAAU,KAAKnC,EAAE,SAAS,EACzCA,EAAE,WAAa,OAAMmC,EAAE,UAAYnC,EAAE,WACrCA,EAAE,oBAAmBmC,EAAE,WAAanC,EAAE,mBACtCA,EAAE,qBAAoBmC,EAAE,YAAcnC,EAAE,oBACxCA,EAAE,iBAAmBA,EAAE,gBAAkBmC,EAAE,SAC9CA,EAAE,OAASnC,EAAE,gBACf,CACA,OAAO,OAAO,OAAOkC,CAAG,EAAE,KAAK,CAACjC,EAAG9B,IAAMA,EAAE,YAAc8B,EAAE,WAAW,CACvE,CAGA,MAAMmC,EAAW,CAChB,SAAU,kEACV,UACC,4EACD,UAAW,4DACX,OAAQ,mCACR,KAAM,0BACN,SAAU,iDACV,OAAQ,sBACR,QAAS,sBACV,EAEO,SAASC,EAAoB,CACnC,MAAAlD,CACD,EAAiD,CAChD,MAAMmD,EAASzC,EAAAA,QAAQ,IAAMoC,EAAU9C,CAAK,EAAG,CAACA,CAAK,CAAC,EAEtD,OACCR,EAAAA,KAAC,MAAA,CAAI,UAAU,mBACd,SAAA,CAAAC,EAAAA,IAAC,IAAA,CAAE,UAAU,cAAc,SAAA,6JAI3B,QACC,MAAA,CAAI,UAAU,eACd,SAAAD,EAAAA,KAAC,QAAA,CAAM,UAAU,gBAChB,SAAA,CAAAC,EAAAA,IAAC,QAAA,CACA,gBAAC,KAAA,CACA,SAAA,CAAAA,EAAAA,IAAC,MAAG,SAAA,OAAA,CAAK,EACTA,EAAAA,IAAC,MAAG,SAAA,UAAA,CAAQ,QACX,KAAA,CAAG,UAAU,MAAM,MAAOwD,EAAS,SAAU,SAAA,YAE9C,QACC,KAAA,CAAG,UAAU,MAAM,MAAOA,EAAS,UAAW,SAAA,aAE/C,EACAxD,EAAAA,IAAC,KAAA,CAAG,UAAU,MAAM,SAAA,QAAK,QACxB,KAAA,CAAG,UAAU,MAAM,MAAOwD,EAAS,UAAW,SAAA,aAE/C,QACC,KAAA,CAAG,UAAU,MAAM,MAAOA,EAAS,OAAQ,SAAA,UAE5C,QACC,KAAA,CAAG,UAAU,MAAM,MAAOA,EAAS,KAAM,SAAA,QAE1C,QACC,KAAA,CAAG,UAAU,MAAM,MAAOA,EAAS,SAAU,SAAA,WAE9C,EACAxD,EAAAA,IAAC,KAAA,CAAG,UAAU,MAAM,SAAA,cAAW,QAC9B,KAAA,CAAG,UAAU,MAAM,MAAOwD,EAAS,OAAQ,SAAA,WAE5C,QACC,KAAA,CAAG,UAAU,MAAM,MAAOA,EAAS,QAAS,SAAA,YAE7C,EACAxD,EAAAA,IAAC,KAAA,CAAG,UAAU,MAAM,SAAA,UAAO,EAC3BA,EAAAA,IAAC,KAAA,CACA,UAAU,MACV,MAAM,kEACN,SAAA,aAAA,CAAA,EAGDA,EAAAA,IAAC,KAAA,CACA,UAAU,MACV,MAAM,4GACN,SAAA,eAAA,CAAA,EAGDA,EAAAA,IAAC,KAAA,CAAG,UAAU,MAAM,SAAA,WAAA,CAAS,CAAA,CAAA,CAC9B,CAAA,CACD,SACC,QAAA,CACC,SAAA,CAAA0D,EAAO,SAAW,GAClB1D,EAAAA,IAAC,KAAA,CACA,SAAAA,MAAC,KAAA,CAAG,QAAS,GAAI,UAAU,aAAa,SAAA,iCAAA,CAExC,EACD,EAEA0D,EAAO,IAAKH,GAAM,CAClB,MAAMI,GAASJ,EAAE,UAAY,IAAMA,EAAE,WAAa,GAC5CK,EAAML,EAAE,IAAM,EAAI,IAAIA,EAAE,IAAI,QAAQ,CAAC,CAAC,GAAK,IAC3CM,EAAON,EAAE,OACZ,IAAI,KAAKA,EAAE,MAAM,EAAE,eAAA,EACnB,IACGO,EACLP,EAAE,WAAa,KAAO,IAAWA,EAAE,UAAY,MAAQ,KACxD,cACE,KAAA,CACA,SAAA,CAAAvD,EAAAA,IAAC,KAAA,CAAG,UAAU,aAAc,SAAAuD,EAAE,MAAM,EACpCvD,EAAAA,IAAC,KAAA,CAAI,SAAAuD,EAAE,QAAA,CAAS,QACf,KAAA,CAAG,UAAU,MAAO,SAAAA,EAAE,SAAS,iBAAiB,QAChD,KAAA,CAAG,UAAU,MAAO,SAAAA,EAAE,UAAU,iBAAiB,QACjD,KAAA,CAAG,UAAU,MAAO,SAAAI,EAAM,iBAAiB,QAC3C,KAAA,CAAG,UAAU,MAAO,SAAAX,EAAYO,EAAE,UAAU,EAAE,QAC9C,KAAA,CAAG,UAAU,MAAO,SAAAP,EAAYO,EAAE,SAAS,EAAE,EAC9CvD,EAAAA,IAAC,KAAA,CAAG,UAAU,MAAO,SAAA8D,EAAK,QACzB,KAAA,CAAG,UAAU,MAAO,SAAAP,EAAE,SAAS,iBAAiB,QAChD,KAAA,CAAG,UAAU,MAAO,SAAAA,EAAE,YAAY,iBAAiB,QACnD,KAAA,CAAG,UAAU,MAAO,SAAAH,EAAaG,EAAE,OAAO,EAAE,QAC5C,KAAA,CAAG,UAAU,MAAO,SAAAH,EAAaG,EAAE,QAAQ,EAAE,EAC9CvD,EAAAA,IAAC,KAAA,CAAG,UAAU,MAAO,SAAA4D,EAAI,EACzB5D,EAAAA,IAAC,MAAG,UAAU,MACZ,WAAE,UAAY,EACZ,IACCuD,EAAE,UAAY,KACbA,EAAE,UAAYA,EAAE,WAAaA,EAAE,UAAY,IAC5C,QAAQ,CAAC,CAAC,IACX,GAAA,CACJ,EACAvD,EAAAA,IAAC,KAAA,CAAG,UAAU,MACZ,SAAAuD,EAAE,UAAY,EACZ,IAAIQ,EAAoBR,EAAE,UAAWA,EAAE,WAAYA,EAAE,QAAQ,CAAC,GAAK,CAAC,EAAE,SAAS,QAAQ,CAAC,CAAC,GACzF,GAAA,CACJ,EACAvD,EAAAA,IAAC,KAAA,CAAG,UAAU,MAAO,SAAA6D,CAAA,CAAK,CAAA,CAAA,EA3BlBN,EAAE,KA4BX,CAEF,CAAC,CAAA,CAAA,CACF,CAAA,CAAA,CACD,CAAA,CACD,EACAvD,EAAAA,IAAC,IAAA,CAAE,UAAU,cAAc,SAAA,iRAAA,CAK3B,CAAA,EACD,CAEF,CCjMA,eAAegE,GAAmD,CACjE,OAAQ,MAAMC,EAAA,CACf,CAEA,SAAwBC,GAA+B,CACtD,KAAM,CAACC,EAAUC,CAAW,EAAIzD,EAAAA,SAAiC,IAAI,EAE/D,CAAE,KAAM0D,EAAW,MAAOC,GAC/BC,EACCC,cAAY,IAAMR,EAAA,EAAmB,EAAE,EACvC,CAAE,aAAc,GAAA,CAAO,EAGnB,CAAE,KAAMS,CAAA,EAAgBF,EAC7BC,cAAY,IAAME,EAAA,EAAgB,EAAE,EACpC,CAAE,aAAc,GAAA,CAAO,EAGxB,GAAIJ,GAAY,CAACD,EAChB,OACCtE,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,wBAAsBuE,EAAS,OAAA,EAAQ,EAGnE,GAAI,CAACD,EACJ,OAAOrE,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,iBAAc,EAGhD,MAAM2E,EAAUN,EAAU,QACpB9D,EAAQ8D,EAAU,MAClB3B,EAAU+B,GAAa,SAAW,CAAA,EAExC,OAAIlE,EAAM,SAAW,EAEnBR,EAAAA,KAAC,MAAA,CAAI,UAAU,sBACd,SAAA,CAAAC,EAAAA,IAACN,EAAA,CACA,WAAY,EACZ,iBAAkB,EAClB,iBAAkB,EAClB,wBAAyB,CAAA,CAAA,EAE1BK,EAAAA,KAAC,MAAA,CAAI,UAAU,WACd,SAAA,CAAAC,EAAAA,IAAC,MAAG,SAAA,uBAAA,CAAqB,EACzBA,EAAAA,IAAC,KAAE,SAAA,uMAAA,CAIH,CAAA,CAAA,CACD,CAAA,EACD,EAKDD,EAAAA,KAAC,MAAA,CAAI,UAAU,sBACd,SAAA,CAAAC,EAAAA,IAACN,EAAA,CACA,WAAYiF,GAAS,YAAc,EACnC,iBAAkBA,GAAS,kBAAoB,EAC/C,iBAAkBA,GAAS,kBAAoB,EAC/C,wBAAyBA,GAAS,8BAAgC,CAAA,CAAA,EAEnE3E,EAAAA,IAAC,KAAA,CAAG,UAAU,qCAAqC,SAAA,mBAAgB,EACnEA,EAAAA,IAACM,EAAA,CAAU,MAAAC,EAAc,SAAU6D,CAAA,CAAa,EAChDpE,EAAAA,IAAC,KAAA,CAAG,UAAU,qCAAqC,SAAA,+CAEnD,EACAA,MAACyC,GAAiB,QAAAC,EAAkB,EACpC1C,EAAAA,IAAC,KAAA,CAAG,UAAU,qCAAqC,SAAA,mBAAgB,EACnEA,MAACyD,GAAoB,MAAAlD,EAAc,EAClC4D,SACClC,EAAA,CAAgB,KAAMkC,EAAU,QAAS,IAAMC,EAAY,IAAI,CAAA,CAAG,CAAA,EAErE,CAEF"}
|
package/extensions/dashboard-client/dist/assets/{SessionsTab-G0YxR1vG.js → SessionsTab-C-IoLw5h.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as d,j as e,u as b,b as M,
|
|
2
|
-
//# sourceMappingURL=SessionsTab-
|
|
1
|
+
import{r as d,j as e,u as b,b as M,E as A}from"./index-B93cewiX.js";import{u as C}from"./useSSE-zNhb0c3u.js";import{C as c,c as m}from"./card-DcfTmw-4.js";import{R as I,C as T,X as D,Y as L,T as E,a as F,L as W}from"./generateCategoricalChart-BsIxUDyw.js";import{A as R,a as z}from"./AreaChart-Y_X0Bbjd.js";import{T as P}from"./toggle-Bo7kJZ4u.js";function O(s,i){const n=new Map,o=s.map(t=>t.sessionId);for(const t of s)for(const r of t.data){let a=n.get(r.ts);if(!a){a={ts:r.ts,total:0};for(const l of o)a[l]=0;n.set(r.ts,a)}a[t.sessionId]=r.tokens}for(const t of i){let r=n.get(t.ts);if(!r){r={ts:t.ts,total:0};for(const a of o)r[a]=0;n.set(t.ts,r)}r.total=t.tokens}return[...n.values()].sort((t,r)=>t.ts-r.ts)}function v(s){return new Date(s).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"})}function k(s){const i=Number(s);return Number.isFinite(i)?i.toLocaleString():String(s)}function N(s){return`grad-${s.replace(/[^a-zA-Z0-9_-]/g,"_")}`}function G({series:s,totals:i}){const n=d.useMemo(()=>O(s,i),[s,i]);return s.length===0?e.jsx(c,{children:e.jsx(m,{children:"No token samples in this window yet."})}):e.jsx(c,{children:e.jsx(m,{children:e.jsx(I,{width:"100%",height:360,children:e.jsxs(R,{data:n,margin:{top:8,right:16,left:0,bottom:0},children:[e.jsx("defs",{children:s.map(o=>e.jsxs("linearGradient",{id:N(o.sessionId),x1:"0",y1:"0",x2:"0",y2:"1",children:[e.jsx("stop",{offset:"5%",stopColor:o.color,stopOpacity:.7}),e.jsx("stop",{offset:"95%",stopColor:o.color,stopOpacity:.25})]},`${o.sessionId}-grad`))}),e.jsx(T,{strokeDasharray:"3 3",stroke:"#30363d"}),e.jsx(D,{dataKey:"ts",type:"number",domain:["dataMin","dataMax"],tickFormatter:o=>v(o),stroke:"#8b949e",tick:{fontSize:11},scale:"time"}),e.jsx(L,{stroke:"#8b949e",tick:{fontSize:11},width:56,tickFormatter:o=>k(o)}),e.jsx(E,{labelFormatter:o=>v(o),formatter:o=>[k(o),"tokens"],contentStyle:{background:"#161b22",border:"1px solid #30363d",borderRadius:"6px",color:"#e6edf3"},labelStyle:{color:"#8b949e",fontSize:12}}),e.jsx(F,{wrapperStyle:{fontSize:12}}),s.map(o=>e.jsx(z,{type:"monotone",dataKey:o.sessionId,name:o.label,stackId:"1",stroke:o.color,fill:`url(#${N(o.sessionId)})`,fillOpacity:1,isAnimationActive:!1,dot:!1},o.sessionId)),e.jsx(W,{type:"monotone",dataKey:"total",name:"Total",stroke:"#e6edf3",strokeWidth:1.5,strokeDasharray:"4 2",dot:!1,isAnimationActive:!1})]})})})})}const y=["PID","Repo","Model","Context %","Tokens / Window","Last Heartbeat","State Dir","Trend"];function K(s){const i=Math.max(0,Math.round(s/1e3));if(i<60)return`${i}s ago`;const n=Math.floor(i/60);return n<60?`${n}m ${i%60}s ago`:`${Math.floor(n/60)}h ${n%60}m ago`}function S(s){return s==null?"—":s>=1e5?`${(s/1e3).toFixed(0)}k`:s>=1e3?`${(s/1e3).toFixed(1)}k`:s.toLocaleString()}function X(s){return s==null?"sessions-pct-ok":s>=85?"sessions-pct-err":s>=60?"sessions-pct-warn":"sessions-pct-ok"}function _(s){return s==null?"—":`${Math.round(s)}%`}function B(s,i){return s==null?"—":`${S(s)} / ${S(i)}`}function H({points:s,color:i}){if(s.length<2)return e.jsx("span",{className:"sessions-sparkline-empty",children:"—"});const n=Math.min(...s),t=Math.max(...s)-n||1,r=64,a=18,l=r/(s.length-1),x=s.map((u,h)=>{const j=h*l,p=a-(u-n)/t*a;return`${h===0?"M":"L"} ${j.toFixed(1)} ${p.toFixed(1)}`});return e.jsx("svg",{className:"sessions-sparkline",width:r,height:a,viewBox:`0 0 ${r} ${a}`,role:"img","aria-label":"token trend",children:e.jsx("path",{d:x.join(" "),fill:"none",stroke:i,strokeWidth:"1.5",strokeLinejoin:"round",strokeLinecap:"round"})})}function Y(s){const i=new Map;for(const n of s)i.set(n.sessionId,n);return i}function Z({sessions:s,series:i}){const n=Y(i),o=Date.now();return e.jsx(c,{children:e.jsx(m,{children:e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full border-collapse text-sm",children:[e.jsx("thead",{children:e.jsx("tr",{children:y.map((t,r)=>e.jsx("th",{className:`border-b border-border px-3 py-2 text-left font-medium text-muted-foreground${r>=3&&r!==6?" text-right":""}`,children:t},t))})}),e.jsxs("tbody",{children:[s.length===0&&e.jsx("tr",{children:e.jsx("td",{colSpan:y.length,className:"px-3 py-4 text-muted-foreground",children:"No active sessions."})}),s.map(t=>{const r=n.get(t.sessionId),a=r?.data.map(x=>x.tokens)??[],l=r?.color??"#58a6ff";return e.jsxs("tr",{className:"border-b border-border/50 hover:bg-bg-elevated/40",children:[e.jsx("td",{className:"px-3 py-2 text-right font-mono",children:t.pid}),e.jsx("td",{className:"px-3 py-2",title:t.repoRoot??void 0,children:t.displayName||t.repoRoot||"—"}),e.jsx("td",{className:"px-3 py-2",children:t.model??"—"}),e.jsx("td",{className:"px-3 py-2 text-right",children:e.jsx("span",{className:`sessions-pct-pill ${X(t.percent)}`,children:_(t.percent)})}),e.jsx("td",{className:"px-3 py-2 text-right",children:B(t.tokens,t.ctxWindow)}),e.jsx("td",{className:"px-3 py-2 text-right",children:K(o-t.lastSeen)}),e.jsx("td",{title:t.stateDir??void 0,className:"px-3 py-2 font-mono text-muted-foreground",children:t.stateDir?t.stateDir.split("/").pop()||t.stateDir:"—"}),e.jsx("td",{className:"px-3 py-2",children:e.jsx(H,{points:a,color:l})})]},`${t.pid}:${t.sessionId}`)})]})]})})})})}function q(s){return s.type==="session_sample"}const w=[{value:5,label:"5m"},{value:15,label:"15m"},{value:30,label:"30m"},{value:60,label:"60m"}];function J({sessions:s}){const i=s.sessions.length,n=s.sessions.reduce((r,a)=>r+(a.tokens??0),0),o=s.sessions.reduce((r,a)=>Math.max(r,a.ctxWindow),0),t=o>0?n/o*100:0;return e.jsxs("div",{className:"grid grid-cols-2 gap-4 md:grid-cols-4",children:[e.jsx(c,{children:e.jsxs(m,{children:[e.jsx("span",{className:"text-xs text-muted-foreground",children:"Active sessions"}),e.jsx("div",{className:"text-xl font-semibold",children:i})]})}),e.jsx(c,{children:e.jsxs(m,{children:[e.jsx("span",{className:"text-xs text-muted-foreground",children:"Combined tokens"}),e.jsx("div",{className:"text-xl font-semibold",children:n>=1e5?`${(n/1e3).toFixed(0)}k`:n.toLocaleString()})]})}),e.jsx(c,{children:e.jsxs(m,{children:[e.jsx("span",{className:"text-xs text-muted-foreground",children:"Combined % of max window"}),e.jsxs("div",{className:"text-xl font-semibold",children:[Math.round(t),"%"]})]})}),e.jsx(c,{children:e.jsxs(m,{children:[e.jsx("span",{className:"text-xs text-muted-foreground",children:"Pruned (stale)"}),e.jsx("div",{className:"text-xl font-semibold",children:s.pruned})]})})]})}function ne(){const[s,i]=d.useState(30),{data:n,error:o,refetch:t}=b(d.useCallback(()=>M(),[]),{pollInterval:2e3}),{data:r,error:a,refetch:l}=b(d.useCallback(()=>A(s),[s]),{pollInterval:2e3}),{events:x}=C(),u=d.useMemo(()=>x.filter(q),[x]),h=d.useMemo(()=>u.length===0?null:u[u.length-1].ts,[u]);if(d.useEffect(()=>{h!=null&&(t(),l())},[h,t,l]),o&&!n)return e.jsxs("div",{className:"tab-stub",children:["Error loading sessions: ",o.message]});if(a&&!r)return e.jsxs("div",{className:"tab-stub",children:["Error loading timeseries: ",a.message]});if(!n)return e.jsx("div",{className:"tab-stub",children:"Loading sessions…"});const j=n.sessions,p=r?.series??[],g=r?.totals??[],$=w.find(f=>f.value===s)?.label??`${s}m`;return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Session Memory Graph"}),e.jsx("div",{className:"flex gap-2",children:w.map(f=>e.jsx(P,{pressed:s===f.value,onClick:()=>i(f.value),children:f.label},f.value))})]}),j.length===0?e.jsx("div",{className:"text-sm text-muted-foreground",children:"No active sessions."}):e.jsxs(e.Fragment,{children:[e.jsx(J,{sessions:n}),e.jsx(G,{series:p,totals:g}),e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Active Sessions"}),e.jsx(Z,{sessions:j,series:p}),e.jsxs("div",{className:"text-xs text-muted-foreground",children:[p.length," series · ",g.length," timestamps ·"," ",$," window · updated"," ",r?.updatedAt??n.updatedAt]})]})]})}export{ne as default};
|
|
2
|
+
//# sourceMappingURL=SessionsTab-C-IoLw5h.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionsTab-G0YxR1vG.js","sources":["../../src/components/SessionsMemoryChart.tsx","../../src/components/ActiveSessionsTable.tsx","../../src/tabs/SessionsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/SessionsMemoryChart.tsx — Stacked memory graph (S39).\n *\n * Recharts stacked `<Area>` per session + total `<Line>` + `<Tooltip>` +\n * `<Legend>` + `<ResponsiveContainer>`. Data comes from `/api/sessions/timeseries`\n * which returns `SessionSeries[]` (per-session data points + stable hex color)\n * and a `totals[]` array (sum across all sessions per timestamp).\n *\n * PREVENT-PI-004: recharts is bundled into this localhost-served static\n * dashboard bundle; it makes no runtime network calls. Chart data arrives via\n * same-origin fetch to the dashboard server (loopback, ports 9320–9329).\n */\n\nimport type React from \"react\";\nimport { useMemo } from \"react\";\nimport {\n\tResponsiveContainer,\n\tAreaChart,\n\tArea,\n\tLine,\n\tXAxis,\n\tYAxis,\n\tCartesianGrid,\n\tTooltip,\n\tLegend,\n} from \"recharts\";\nimport type { SessionSeries } from \"@contracts\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\nexport interface SessionsMemoryChartProps {\n\t/** Per-session series with stable colors (from /api/sessions/timeseries). */\n\tseries: SessionSeries[];\n\t/** Totals (sum across all sessions) per timestamp. */\n\ttotals: ReadonlyArray<{ ts: number; tokens: number }>;\n}\n\n/** A row in the pivoted data table passed to recharts `<AreaChart>`. */\ninterface PivotedRow {\n\t/** Unix timestamp (ms). */\n\tts: number;\n\t/** Total tokens across all sessions at this timestamp. */\n\ttotal: number;\n\t/** Per-session token count keyed by sessionId. */\n\t[sessionId: string]: number;\n}\n\n/**\n * Pivot the per-session series + totals into recharts' expected flat shape:\n * one row per unique timestamp, with each session's token count as a column\n * keyed by sessionId. Missing values for a session at a given timestamp are\n * filled with zero so the stack renders continuously.\n */\nfunction pivot(\n\tseries: SessionSeries[],\n\ttotals: ReadonlyArray<{ ts: number; tokens: number }>,\n): PivotedRow[] {\n\tconst map = new Map<number, PivotedRow>();\n\tconst sessionIds = series.map((s) => s.sessionId);\n\n\tfor (const s of series) {\n\t\tfor (const pt of s.data) {\n\t\t\tlet row = map.get(pt.ts);\n\t\t\tif (!row) {\n\t\t\t\trow = { ts: pt.ts, total: 0 } as PivotedRow;\n\t\t\t\tfor (const sid of sessionIds) row[sid] = 0;\n\t\t\t\tmap.set(pt.ts, row);\n\t\t\t}\n\t\t\trow[s.sessionId] = pt.tokens;\n\t\t}\n\t}\n\tfor (const t of totals) {\n\t\tlet row = map.get(t.ts);\n\t\tif (!row) {\n\t\t\trow = { ts: t.ts, total: 0 } as PivotedRow;\n\t\t\tfor (const sid of sessionIds) row[sid] = 0;\n\t\t\tmap.set(t.ts, row);\n\t\t}\n\t\trow.total = t.tokens;\n\t}\n\n\treturn [...map.values()].sort((a, b) => a.ts - b.ts);\n}\n\nfunction fmtTs(ts: number): string {\n\treturn new Date(ts).toLocaleTimeString([], {\n\t\thour: \"2-digit\",\n\t\tminute: \"2-digit\",\n\t\tsecond: \"2-digit\",\n\t});\n}\n\nfunction fmtTokens(value: number | string): string {\n\tconst n = Number(value);\n\treturn Number.isFinite(n) ? n.toLocaleString() : String(value);\n}\n\n/** Sanitize an arbitrary sessionId for use in an SVG gradient id. */\nfunction gradId(sessionId: string): string {\n\tconst safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n\treturn `grad-${safe}`;\n}\n\nexport function SessionsMemoryChart({\n\tseries,\n\ttotals,\n}: SessionsMemoryChartProps): React.ReactElement {\n\tconst data = useMemo(\n\t\t() => pivot(series, totals),\n\t\t[series, totals],\n\t);\n\n\tif (series.length === 0) {\n\t\treturn (\n\t\t\t<Card>\n\t\t\t\t<CardContent>No token samples in this window yet.</CardContent>\n\t\t\t</Card>\n\t\t);\n\t}\n\n\treturn (\n\t\t<Card>\n\t\t\t<CardContent>\n\t\t\t<ResponsiveContainer width=\"100%\" height={360}>\n\t\t\t\t<AreaChart\n\t\t\t\t\tdata={data}\n\t\t\t\t\tmargin={{ top: 8, right: 16, left: 0, bottom: 0 }}\n\t\t\t\t>\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t{series.map((s) => (\n\t\t\t\t\t\t\t<linearGradient\n\t\t\t\t\t\t\t\tkey={`${s.sessionId}-grad`}\n\t\t\t\t\t\t\t\tid={gradId(s.sessionId)}\n\t\t\t\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t\t\t\tx2=\"0\"\n\t\t\t\t\t\t\t\ty2=\"1\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<stop offset=\"5%\" stopColor={s.color} stopOpacity={0.7} />\n\t\t\t\t\t\t\t\t<stop offset=\"95%\" stopColor={s.color} stopOpacity={0.25} />\n\t\t\t\t\t\t\t</linearGradient>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</defs>\n\t\t\t\t\t<CartesianGrid strokeDasharray=\"3 3\" stroke=\"#30363d\" />\n\t\t\t\t\t<XAxis\n\t\t\t\t\t\tdataKey=\"ts\"\n\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\tdomain={[\"dataMin\", \"dataMax\"]}\n\t\t\t\t\t\ttickFormatter={(ts: number) => fmtTs(ts)}\n\t\t\t\t\t\tstroke=\"#8b949e\"\n\t\t\t\t\t\ttick={{ fontSize: 11 }}\n\t\t\t\t\t\tscale=\"time\"\n\t\t\t\t\t/>\n\t\t\t\t\t<YAxis\n\t\t\t\t\t\tstroke=\"#8b949e\"\n\t\t\t\t\t\ttick={{ fontSize: 11 }}\n\t\t\t\t\t\twidth={56}\n\t\t\t\t\t\ttickFormatter={(v: number) => fmtTokens(v)}\n\t\t\t\t\t/>\n\t\t\t\t\t<Tooltip\n\t\t\t\t\t\tlabelFormatter={(ts: number) => fmtTs(ts)}\n\t\t\t\t\t\tformatter={(value) =>\n\t\t\t\t\t\t\t[fmtTokens(value as number), \"tokens\"] as [string, string]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontentStyle={{\n\t\t\t\t\t\t\tbackground: \"#161b22\",\n\t\t\t\t\t\t\tborder: \"1px solid #30363d\",\n\t\t\t\t\t\t\tborderRadius: \"6px\",\n\t\t\t\t\t\t\tcolor: \"#e6edf3\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tlabelStyle={{ color: \"#8b949e\", fontSize: 12 }}\n\t\t\t\t\t/>\n\t\t\t\t\t<Legend wrapperStyle={{ fontSize: 12 }} />\n\t\t\t\t\t{series.map((s) => (\n\t\t\t\t\t\t<Area\n\t\t\t\t\t\t\tkey={s.sessionId}\n\t\t\t\t\t\t\ttype=\"monotone\"\n\t\t\t\t\t\t\tdataKey={s.sessionId}\n\t\t\t\t\t\t\tname={s.label}\n\t\t\t\t\t\t\tstackId=\"1\"\n\t\t\t\t\t\t\tstroke={s.color}\n\t\t\t\t\t\t\tfill={`url(#${gradId(s.sessionId)})`}\n\t\t\t\t\t\t\tfillOpacity={1}\n\t\t\t\t\t\t\tisAnimationActive={false}\n\t\t\t\t\t\t\tdot={false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t\t<Line\n\t\t\t\t\t\ttype=\"monotone\"\n\t\t\t\t\t\tdataKey=\"total\"\n\t\t\t\t\t\tname=\"Total\"\n\t\t\t\t\t\tstroke=\"#e6edf3\"\n\t\t\t\t\t\tstrokeWidth={1.5}\n\t\t\t\t\t\tstrokeDasharray=\"4 2\"\n\t\t\t\t\t\tdot={false}\n\t\t\t\t\t\tisAnimationActive={false}\n\t\t\t\t\t/>\n\t\t\t\t</AreaChart>\n\t\t\t</ResponsiveContainer>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n","/**\n * dashboard-client/src/components/ActiveSessionsTable.tsx — Active sessions table (S39).\n *\n * Per-session rows: PID, Repo, Model, Context %, Tokens/Window,\n * Heartbeat age, State dir, Sparkline.\n *\n * Data from /api/sessions (active sessions list) and /api/sessions/timeseries\n * (per-session data points used to render inline sparklines).\n */\n\nimport type React from \"react\";\nimport type { ActiveSession, SessionSeries } from \"@contracts\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\nexport interface ActiveSessionsTableProps {\n\t/** Active sessions, sorted by lastSeen descending (from /api/sessions). */\n\tsessions: ActiveSession[];\n\t/** Per-session timeseries used to render the inline sparkline column. */\n\tseries: SessionSeries[];\n}\n\nconst HEADERS = [\n\t\"PID\",\n\t\"Repo\",\n\t\"Model\",\n\t\"Context %\",\n\t\"Tokens / Window\",\n\t\"Last Heartbeat\",\n\t\"State Dir\",\n\t\"Trend\",\n] as const;\n\n/** Format a duration in milliseconds → human-readable age string. */\nfunction fmtAge(ms: number): string {\n\tconst s = Math.max(0, Math.round(ms / 1000));\n\tif (s < 60) return `${s}s ago`;\n\tconst m = Math.floor(s / 60);\n\tif (m < 60) return `${m}m ${s % 60}s ago`;\n\tconst h = Math.floor(m / 60);\n\treturn `${h}h ${m % 60}m ago`;\n}\n\nfunction fmtTokens(n: number | null): string {\n\tif (n == null) return \"\\u2014\";\n\tif (n >= 100_000) return `${(n / 1000).toFixed(0)}k`;\n\tif (n >= 1000) return `${(n / 1000).toFixed(1)}k`;\n\treturn n.toLocaleString();\n}\n\nfunction pctClass(pct: number | null): string {\n\tif (pct == null) return \"sessions-pct-ok\";\n\tif (pct >= 85) return \"sessions-pct-err\";\n\tif (pct >= 60) return \"sessions-pct-warn\";\n\treturn \"sessions-pct-ok\";\n}\n\nfunction fmtPct(pct: number | null): string {\n\tif (pct == null) return \"\\u2014\";\n\treturn `${Math.round(pct)}%`;\n}\n\nfunction fmtWindow(tokens: number | null, window: number): string {\n\tif (tokens == null) return \"\\u2014\";\n\treturn `${fmtTokens(tokens)} / ${fmtTokens(window)}`;\n}\n\ninterface SparklineProps {\n\t/** Token counts over time (oldest → newest). */\n\tpoints: number[];\n\t/** Stable hex color for the stroke. */\n\tcolor: string;\n}\n\nfunction Sparkline({ points, color }: SparklineProps): React.ReactElement {\n\tif (points.length < 2) {\n\t\treturn <span className=\"sessions-sparkline-empty\">{\"\\u2014\"}</span>;\n\t}\n\tconst min = Math.min(...points);\n\tconst max = Math.max(...points);\n\tconst range = max - min || 1;\n\tconst w = 64;\n\tconst h = 18;\n\tconst stepX = w / (points.length - 1);\n\tconst segments = points.map((p, i) => {\n\t\tconst x = i * stepX;\n\t\tconst y = h - ((p - min) / range) * h;\n\t\treturn `${i === 0 ? \"M\" : \"L\"} ${x.toFixed(1)} ${y.toFixed(1)}`;\n\t});\n\treturn (\n\t\t<svg\n\t\t\tclassName=\"sessions-sparkline\"\n\t\t\twidth={w}\n\t\t\theight={h}\n\t\t\tviewBox={`0 0 ${w} ${h}`}\n\t\t\trole=\"img\"\n\t\t\taria-label=\"token trend\"\n\t\t>\n\t\t\t<path\n\t\t\t\td={segments.join(\" \")}\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke={color}\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t/>\n\t\t</svg>\n\t);\n}\n\n/** Map sessionId → series for the sparkline column lookup. */\nfunction indexSeries(\n\tseries: SessionSeries[],\n): Map<string, SessionSeries> {\n\tconst m = new Map<string, SessionSeries>();\n\tfor (const s of series) m.set(s.sessionId, s);\n\treturn m;\n}\n\nexport function ActiveSessionsTable({\n\tsessions,\n\tseries,\n}: ActiveSessionsTableProps): React.ReactElement {\n\tconst seriesById = indexSeries(series);\n\tconst now = Date.now();\n\n\treturn (\n\t\t<Card>\n\t\t\t<CardContent>\n\t\t\t<div className=\"overflow-x-auto\">\n\t\t\t\t<table className=\"w-full border-collapse text-sm\">\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t{HEADERS.map((h, i) => (\n\t\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\t\tkey={h}\n\t\t\t\t\t\t\t\t\tclassName={`border-b border-border px-3 py-2 text-left font-medium text-muted-foreground${\n\t\t\t\t\t\t\t\t\t\ti >= 3 && i !== 6 ? \" text-right\" : \"\"\n\t\t\t\t\t\t\t\t\t}`}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{h}\n\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t{sessions.length === 0 && (\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td colSpan={HEADERS.length} className=\"px-3 py-4 text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tNo active sessions.\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{sessions.map((s) => {\n\t\t\t\t\t\t\tconst srs = seriesById.get(s.sessionId);\n\t\t\t\t\t\t\tconst sparkPts =\n\t\t\t\t\t\t\t\tsrs?.data.map((d) => d.tokens) ?? [];\n\t\t\t\t\t\t\tconst color = srs?.color ?? \"#58a6ff\";\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<tr\n\t\t\t\t\t\t\t\t\tkey={`${s.pid}:${s.sessionId}`}\n\t\t\t\t\t\t\t\t\tclassName=\"border-b border-border/50 hover:bg-bg-elevated/40\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right font-mono\">{s.pid}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2\" title={s.repoRoot ?? undefined}>\n\t\t\t\t\t\t\t\t\t\t{s.displayName || s.repoRoot || \"\\u2014\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2\">{s.model ?? \"\\u2014\"}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">\n\t\t\t\t\t\t\t\t\t\t<span className={`sessions-pct-pill ${pctClass(s.percent)}`}>\n\t\t\t\t\t\t\t\t\t\t\t{fmtPct(s.percent)}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">\n\t\t\t\t\t\t\t\t\t\t{fmtWindow(s.tokens, s.ctxWindow)}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">{fmtAge(now - s.lastSeen)}</td>\n\t\t\t\t\t\t\t\t\t<td\n\t\t\t\t\t\t\t\t\t\ttitle={s.stateDir ?? undefined}\n\t\t\t\t\t\t\t\t\t\tclassName=\"px-3 py-2 font-mono text-muted-foreground\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{s.stateDir\n\t\t\t\t\t\t\t\t\t\t\t? s.stateDir.split(\"/\").pop() || s.stateDir\n\t\t\t\t\t\t\t\t\t\t\t: \"\\u2014\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2\">\n\t\t\t\t\t\t\t\t\t\t<Sparkline points={sparkPts} color={color} />\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/SessionsTab.tsx — Sessions tab (S39).\n *\n * Renders: window selector (5/15/30/60 min), sessions summary tiles,\n * the stacked-memory graph (SessionsMemoryChart), and the active sessions\n * table (ActiveSessionsTable).\n *\n * Data sources: fetchSessions() + fetchSessionTimeseries(minutes). Both poll\n * every 2s for near-real-time updates. useSSE() listens for session_sample\n * events emitted by appendTokenSample (Step 5) so the chart refetches\n * instantly between polls when a new token sample is appended to events.log.\n *\n * Empty state: shows \"No active sessions.\" when the /api/sessions response\n * returns an empty array (e.g. no pi processes are running yet).\n */\n\nimport type React from \"react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useApi } from \"../hooks/useApi\";\nimport { useSSE } from \"../hooks/useSSE\";\nimport { fetchSessions, fetchSessionTimeseries } from \"../api/client\";\nimport type { SseEvent, SseSessionSample, SessionsResponse, SessionTimeseriesResponse } from \"@contracts\";\nimport { SessionsMemoryChart } from \"../components/SessionsMemoryChart\";\nimport { ActiveSessionsTable } from \"../components/ActiveSessionsTable\";\nimport { Toggle } from \"../components/ui/toggle\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\n/**\n * Shape of the {@link SseSessionSample} SSE event appended to events.log by\n * `appendTokenSample` (src/store/sqlite/global-index.ts). The contract now\n * defines this variant as part of the {@link SseEvent} union (Step 5); the\n * local alias keeps the surface terser in this file.\n */\ntype SessionSampleEvent = SseSessionSample;\n\n/** Type guard: narrows an SSE event to a session_sample event. */\nfunction isSessionSample(\n\te: SseEvent,\n): e is SessionSampleEvent {\n\treturn e.type === \"session_sample\";\n}\n\nconst WINDOWS: ReadonlyArray<{ value: number; label: string }> = [\n\t{ value: 5, label: \"5m\" },\n\t{ value: 15, label: \"15m\" },\n\t{ value: 30, label: \"30m\" },\n\t{ value: 60, label: \"60m\" },\n] as const;\n\ninterface SummaryTilesProps {\n\tsessions: SessionsResponse;\n}\n\nfunction SummaryTiles({ sessions }: SummaryTilesProps): React.ReactElement {\n\tconst active = sessions.sessions.length;\n\tconst combinedTokens = sessions.sessions.reduce(\n\t\t(sum, s) => sum + (s.tokens ?? 0),\n\t\t0,\n\t);\n\tconst maxWindow = sessions.sessions.reduce(\n\t\t(max, s) => Math.max(max, s.ctxWindow),\n\t\t0,\n\t);\n\tconst combinedPct = maxWindow > 0 ? (combinedTokens / maxWindow) * 100 : 0;\n\treturn (\n\t\t<div className=\"grid grid-cols-2 gap-4 md:grid-cols-4\">\n\t\t\t<Card>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">Active sessions</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{active}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">Combined tokens</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">\n\t\t\t\t\t\t{combinedTokens >= 100_000\n\t\t\t\t\t\t\t? `${(combinedTokens / 1000).toFixed(0)}k`\n\t\t\t\t\t\t\t: combinedTokens.toLocaleString()}\n\t\t\t\t\t</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\tCombined % of max window\n\t\t\t\t\t</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{Math.round(combinedPct)}%</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">Pruned (stale)</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{sessions.pruned}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t</div>\n\t);\n}\n\nexport default function SessionsTab(): React.ReactElement {\n\tconst [minutes, setMinutes] = useState(30);\n\n\tconst {\n\t\tdata: sessionsData,\n\t\terror: sessionsErr,\n\t\trefetch: refetchSessions,\n\t} = useApi<SessionsResponse>(\n\t\tuseCallback(() => fetchSessions(), []),\n\t\t{ pollInterval: 2_000 },\n\t);\n\n\tconst {\n\t\tdata: timeseriesData,\n\t\terror: timeseriesErr,\n\t\trefetch: refetchTimeseries,\n\t} = useApi<SessionTimeseriesResponse>(\n\t\tuseCallback(() => fetchSessionTimeseries(minutes), [minutes]),\n\t\t{ pollInterval: 2_000 },\n\t);\n\n\tconst { events } = useSSE();\n\n\t// Filter SSE events for session_sample pushes (Step 5) and trigger an\n\t// immediate refetch when a new sample arrives, so the chart updates\n\t// between the 2s polls. The events array grows as new samples arrive; the\n\t// length-based dependency means the effect re-runs when a new sample event\n\t// lands (not on every poll). We debounce via a microtask guard.\n\tconst sampleEvents = useMemo(\n\t\t() => events.filter(isSessionSample),\n\t\t[events],\n\t);\n\n\tconst lastSampleTs = useMemo(() => {\n\t\tif (sampleEvents.length === 0) return null;\n\t\treturn sampleEvents[sampleEvents.length - 1].ts;\n\t}, [sampleEvents]);\n\n\tuseEffect(() => {\n\t\tif (lastSampleTs == null) return;\n\t\trefetchSessions();\n\t\trefetchTimeseries();\n\t}, [lastSampleTs, refetchSessions, refetchTimeseries]);\n\n\tif (sessionsErr && !sessionsData) {\n\t\treturn (\n\t\t\t<div className=\"tab-stub\">\n\t\t\t\tError loading sessions: {sessionsErr.message}\n\t\t\t</div>\n\t\t);\n\t}\n\tif (timeseriesErr && !timeseriesData) {\n\t\treturn (\n\t\t\t<div className=\"tab-stub\">\n\t\t\t\tError loading timeseries: {timeseriesErr.message}\n\t\t\t</div>\n\t\t);\n\t}\n\tif (!sessionsData) {\n\t\treturn <div className=\"tab-stub\">Loading sessions…</div>;\n\t}\n\n\tconst sessions = sessionsData.sessions;\n\tconst series = timeseriesData?.series ?? [];\n\tconst totals = timeseriesData?.totals ?? [];\n\tconst windowLabel =\n\t\tWINDOWS.find((w) => w.value === minutes)?.label ?? `${minutes}m`;\n\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t<h2 className=\"font-heading text-lg font-semibold\">\n\t\t\t\t\tSession Memory Graph\n\t\t\t\t</h2>\n\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t{WINDOWS.map((w) => (\n\t\t\t\t\t\t<Toggle\n\t\t\t\t\t\t\tkey={w.value}\n\t\t\t\t\t\t\tpressed={minutes === w.value}\n\t\t\t\t\t\t\tonClick={() => setMinutes(w.value)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{w.label}\n\t\t\t\t\t\t</Toggle>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{sessions.length === 0 ? (\n\t\t\t\t<div className=\"text-sm text-muted-foreground\">No active sessions.</div>\n\t\t\t) : (\n\t\t\t\t<>\n\t\t\t\t\t<SummaryTiles sessions={sessionsData} />\n\t\t\t\t\t<SessionsMemoryChart series={series} totals={totals} />\n\t\t\t\t\t<h2 className=\"font-heading text-lg font-semibold\">\n\t\t\t\t\t\tActive Sessions\n\t\t\t\t\t</h2>\n\t\t\t\t\t<ActiveSessionsTable sessions={sessions} series={series} />\n\t\t\t\t\t<div className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t{series.length} series · {totals.length} timestamps ·{\" \"}\n\t\t\t\t\t\t{windowLabel} window · updated{\" \"}\n\t\t\t\t\t\t{timeseriesData?.updatedAt ?? sessionsData.updatedAt}\n\t\t\t\t\t</div>\n\t\t\t\t</>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["pivot","series","totals","map","sessionIds","s","pt","row","sid","a","b","fmtTs","ts","fmtTokens","value","n","gradId","sessionId","SessionsMemoryChart","data","useMemo","jsx","Card","CardContent","ResponsiveContainer","jsxs","AreaChart","CartesianGrid","XAxis","YAxis","v","Tooltip","Legend","Area","Line","HEADERS","fmtAge","ms","m","pctClass","pct","fmtPct","fmtWindow","tokens","window","Sparkline","points","color","min","range","w","h","stepX","segments","p","i","x","y","indexSeries","ActiveSessionsTable","sessions","seriesById","now","srs","sparkPts","d","isSessionSample","e","WINDOWS","SummaryTiles","active","combinedTokens","sum","maxWindow","max","combinedPct","SessionsTab","minutes","setMinutes","useState","sessionsData","sessionsErr","refetchSessions","useApi","useCallback","fetchSessions","timeseriesData","timeseriesErr","refetchTimeseries","fetchSessionTimeseries","events","useSSE","sampleEvents","lastSampleTs","useEffect","windowLabel","Toggle","Fragment"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionsTab-C-IoLw5h.js","sources":["../../src/components/SessionsMemoryChart.tsx","../../src/components/ActiveSessionsTable.tsx","../../src/tabs/SessionsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/SessionsMemoryChart.tsx — Stacked memory graph (S39).\n *\n * Recharts stacked `<Area>` per session + total `<Line>` + `<Tooltip>` +\n * `<Legend>` + `<ResponsiveContainer>`. Data comes from `/api/sessions/timeseries`\n * which returns `SessionSeries[]` (per-session data points + stable hex color)\n * and a `totals[]` array (sum across all sessions per timestamp).\n *\n * PREVENT-PI-004: recharts is bundled into this localhost-served static\n * dashboard bundle; it makes no runtime network calls. Chart data arrives via\n * same-origin fetch to the dashboard server (loopback, ports 9320–9329).\n */\n\nimport type React from \"react\";\nimport { useMemo } from \"react\";\nimport {\n\tResponsiveContainer,\n\tAreaChart,\n\tArea,\n\tLine,\n\tXAxis,\n\tYAxis,\n\tCartesianGrid,\n\tTooltip,\n\tLegend,\n} from \"recharts\";\nimport type { SessionSeries } from \"@contracts\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\nexport interface SessionsMemoryChartProps {\n\t/** Per-session series with stable colors (from /api/sessions/timeseries). */\n\tseries: SessionSeries[];\n\t/** Totals (sum across all sessions) per timestamp. */\n\ttotals: ReadonlyArray<{ ts: number; tokens: number }>;\n}\n\n/** A row in the pivoted data table passed to recharts `<AreaChart>`. */\ninterface PivotedRow {\n\t/** Unix timestamp (ms). */\n\tts: number;\n\t/** Total tokens across all sessions at this timestamp. */\n\ttotal: number;\n\t/** Per-session token count keyed by sessionId. */\n\t[sessionId: string]: number;\n}\n\n/**\n * Pivot the per-session series + totals into recharts' expected flat shape:\n * one row per unique timestamp, with each session's token count as a column\n * keyed by sessionId. Missing values for a session at a given timestamp are\n * filled with zero so the stack renders continuously.\n */\nfunction pivot(\n\tseries: SessionSeries[],\n\ttotals: ReadonlyArray<{ ts: number; tokens: number }>,\n): PivotedRow[] {\n\tconst map = new Map<number, PivotedRow>();\n\tconst sessionIds = series.map((s) => s.sessionId);\n\n\tfor (const s of series) {\n\t\tfor (const pt of s.data) {\n\t\t\tlet row = map.get(pt.ts);\n\t\t\tif (!row) {\n\t\t\t\trow = { ts: pt.ts, total: 0 } as PivotedRow;\n\t\t\t\tfor (const sid of sessionIds) row[sid] = 0;\n\t\t\t\tmap.set(pt.ts, row);\n\t\t\t}\n\t\t\trow[s.sessionId] = pt.tokens;\n\t\t}\n\t}\n\tfor (const t of totals) {\n\t\tlet row = map.get(t.ts);\n\t\tif (!row) {\n\t\t\trow = { ts: t.ts, total: 0 } as PivotedRow;\n\t\t\tfor (const sid of sessionIds) row[sid] = 0;\n\t\t\tmap.set(t.ts, row);\n\t\t}\n\t\trow.total = t.tokens;\n\t}\n\n\treturn [...map.values()].sort((a, b) => a.ts - b.ts);\n}\n\nfunction fmtTs(ts: number): string {\n\treturn new Date(ts).toLocaleTimeString([], {\n\t\thour: \"2-digit\",\n\t\tminute: \"2-digit\",\n\t\tsecond: \"2-digit\",\n\t});\n}\n\nfunction fmtTokens(value: number | string): string {\n\tconst n = Number(value);\n\treturn Number.isFinite(n) ? n.toLocaleString() : String(value);\n}\n\n/** Sanitize an arbitrary sessionId for use in an SVG gradient id. */\nfunction gradId(sessionId: string): string {\n\tconst safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n\treturn `grad-${safe}`;\n}\n\nexport function SessionsMemoryChart({\n\tseries,\n\ttotals,\n}: SessionsMemoryChartProps): React.ReactElement {\n\tconst data = useMemo(\n\t\t() => pivot(series, totals),\n\t\t[series, totals],\n\t);\n\n\tif (series.length === 0) {\n\t\treturn (\n\t\t\t<Card>\n\t\t\t\t<CardContent>No token samples in this window yet.</CardContent>\n\t\t\t</Card>\n\t\t);\n\t}\n\n\treturn (\n\t\t<Card>\n\t\t\t<CardContent>\n\t\t\t<ResponsiveContainer width=\"100%\" height={360}>\n\t\t\t\t<AreaChart\n\t\t\t\t\tdata={data}\n\t\t\t\t\tmargin={{ top: 8, right: 16, left: 0, bottom: 0 }}\n\t\t\t\t>\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t{series.map((s) => (\n\t\t\t\t\t\t\t<linearGradient\n\t\t\t\t\t\t\t\tkey={`${s.sessionId}-grad`}\n\t\t\t\t\t\t\t\tid={gradId(s.sessionId)}\n\t\t\t\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t\t\t\tx2=\"0\"\n\t\t\t\t\t\t\t\ty2=\"1\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<stop offset=\"5%\" stopColor={s.color} stopOpacity={0.7} />\n\t\t\t\t\t\t\t\t<stop offset=\"95%\" stopColor={s.color} stopOpacity={0.25} />\n\t\t\t\t\t\t\t</linearGradient>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</defs>\n\t\t\t\t\t<CartesianGrid strokeDasharray=\"3 3\" stroke=\"#30363d\" />\n\t\t\t\t\t<XAxis\n\t\t\t\t\t\tdataKey=\"ts\"\n\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\tdomain={[\"dataMin\", \"dataMax\"]}\n\t\t\t\t\t\ttickFormatter={(ts: number) => fmtTs(ts)}\n\t\t\t\t\t\tstroke=\"#8b949e\"\n\t\t\t\t\t\ttick={{ fontSize: 11 }}\n\t\t\t\t\t\tscale=\"time\"\n\t\t\t\t\t/>\n\t\t\t\t\t<YAxis\n\t\t\t\t\t\tstroke=\"#8b949e\"\n\t\t\t\t\t\ttick={{ fontSize: 11 }}\n\t\t\t\t\t\twidth={56}\n\t\t\t\t\t\ttickFormatter={(v: number) => fmtTokens(v)}\n\t\t\t\t\t/>\n\t\t\t\t\t<Tooltip\n\t\t\t\t\t\tlabelFormatter={(ts: number) => fmtTs(ts)}\n\t\t\t\t\t\tformatter={(value) =>\n\t\t\t\t\t\t\t[fmtTokens(value as number), \"tokens\"] as [string, string]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontentStyle={{\n\t\t\t\t\t\t\tbackground: \"#161b22\",\n\t\t\t\t\t\t\tborder: \"1px solid #30363d\",\n\t\t\t\t\t\t\tborderRadius: \"6px\",\n\t\t\t\t\t\t\tcolor: \"#e6edf3\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tlabelStyle={{ color: \"#8b949e\", fontSize: 12 }}\n\t\t\t\t\t/>\n\t\t\t\t\t<Legend wrapperStyle={{ fontSize: 12 }} />\n\t\t\t\t\t{series.map((s) => (\n\t\t\t\t\t\t<Area\n\t\t\t\t\t\t\tkey={s.sessionId}\n\t\t\t\t\t\t\ttype=\"monotone\"\n\t\t\t\t\t\t\tdataKey={s.sessionId}\n\t\t\t\t\t\t\tname={s.label}\n\t\t\t\t\t\t\tstackId=\"1\"\n\t\t\t\t\t\t\tstroke={s.color}\n\t\t\t\t\t\t\tfill={`url(#${gradId(s.sessionId)})`}\n\t\t\t\t\t\t\tfillOpacity={1}\n\t\t\t\t\t\t\tisAnimationActive={false}\n\t\t\t\t\t\t\tdot={false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t\t<Line\n\t\t\t\t\t\ttype=\"monotone\"\n\t\t\t\t\t\tdataKey=\"total\"\n\t\t\t\t\t\tname=\"Total\"\n\t\t\t\t\t\tstroke=\"#e6edf3\"\n\t\t\t\t\t\tstrokeWidth={1.5}\n\t\t\t\t\t\tstrokeDasharray=\"4 2\"\n\t\t\t\t\t\tdot={false}\n\t\t\t\t\t\tisAnimationActive={false}\n\t\t\t\t\t/>\n\t\t\t\t</AreaChart>\n\t\t\t</ResponsiveContainer>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n","/**\n * dashboard-client/src/components/ActiveSessionsTable.tsx — Active sessions table (S39).\n *\n * Per-session rows: PID, Repo, Model, Context %, Tokens/Window,\n * Heartbeat age, State dir, Sparkline.\n *\n * Data from /api/sessions (active sessions list) and /api/sessions/timeseries\n * (per-session data points used to render inline sparklines).\n */\n\nimport type React from \"react\";\nimport type { ActiveSession, SessionSeries } from \"@contracts\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\nexport interface ActiveSessionsTableProps {\n\t/** Active sessions, sorted by lastSeen descending (from /api/sessions). */\n\tsessions: ActiveSession[];\n\t/** Per-session timeseries used to render the inline sparkline column. */\n\tseries: SessionSeries[];\n}\n\nconst HEADERS = [\n\t\"PID\",\n\t\"Repo\",\n\t\"Model\",\n\t\"Context %\",\n\t\"Tokens / Window\",\n\t\"Last Heartbeat\",\n\t\"State Dir\",\n\t\"Trend\",\n] as const;\n\n/** Format a duration in milliseconds → human-readable age string. */\nfunction fmtAge(ms: number): string {\n\tconst s = Math.max(0, Math.round(ms / 1000));\n\tif (s < 60) return `${s}s ago`;\n\tconst m = Math.floor(s / 60);\n\tif (m < 60) return `${m}m ${s % 60}s ago`;\n\tconst h = Math.floor(m / 60);\n\treturn `${h}h ${m % 60}m ago`;\n}\n\nfunction fmtTokens(n: number | null): string {\n\tif (n == null) return \"\\u2014\";\n\tif (n >= 100_000) return `${(n / 1000).toFixed(0)}k`;\n\tif (n >= 1000) return `${(n / 1000).toFixed(1)}k`;\n\treturn n.toLocaleString();\n}\n\nfunction pctClass(pct: number | null): string {\n\tif (pct == null) return \"sessions-pct-ok\";\n\tif (pct >= 85) return \"sessions-pct-err\";\n\tif (pct >= 60) return \"sessions-pct-warn\";\n\treturn \"sessions-pct-ok\";\n}\n\nfunction fmtPct(pct: number | null): string {\n\tif (pct == null) return \"\\u2014\";\n\treturn `${Math.round(pct)}%`;\n}\n\nfunction fmtWindow(tokens: number | null, window: number): string {\n\tif (tokens == null) return \"\\u2014\";\n\treturn `${fmtTokens(tokens)} / ${fmtTokens(window)}`;\n}\n\ninterface SparklineProps {\n\t/** Token counts over time (oldest → newest). */\n\tpoints: number[];\n\t/** Stable hex color for the stroke. */\n\tcolor: string;\n}\n\nfunction Sparkline({ points, color }: SparklineProps): React.ReactElement {\n\tif (points.length < 2) {\n\t\treturn <span className=\"sessions-sparkline-empty\">{\"\\u2014\"}</span>;\n\t}\n\tconst min = Math.min(...points);\n\tconst max = Math.max(...points);\n\tconst range = max - min || 1;\n\tconst w = 64;\n\tconst h = 18;\n\tconst stepX = w / (points.length - 1);\n\tconst segments = points.map((p, i) => {\n\t\tconst x = i * stepX;\n\t\tconst y = h - ((p - min) / range) * h;\n\t\treturn `${i === 0 ? \"M\" : \"L\"} ${x.toFixed(1)} ${y.toFixed(1)}`;\n\t});\n\treturn (\n\t\t<svg\n\t\t\tclassName=\"sessions-sparkline\"\n\t\t\twidth={w}\n\t\t\theight={h}\n\t\t\tviewBox={`0 0 ${w} ${h}`}\n\t\t\trole=\"img\"\n\t\t\taria-label=\"token trend\"\n\t\t>\n\t\t\t<path\n\t\t\t\td={segments.join(\" \")}\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke={color}\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t/>\n\t\t</svg>\n\t);\n}\n\n/** Map sessionId → series for the sparkline column lookup. */\nfunction indexSeries(\n\tseries: SessionSeries[],\n): Map<string, SessionSeries> {\n\tconst m = new Map<string, SessionSeries>();\n\tfor (const s of series) m.set(s.sessionId, s);\n\treturn m;\n}\n\nexport function ActiveSessionsTable({\n\tsessions,\n\tseries,\n}: ActiveSessionsTableProps): React.ReactElement {\n\tconst seriesById = indexSeries(series);\n\tconst now = Date.now();\n\n\treturn (\n\t\t<Card>\n\t\t\t<CardContent>\n\t\t\t<div className=\"overflow-x-auto\">\n\t\t\t\t<table className=\"w-full border-collapse text-sm\">\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t{HEADERS.map((h, i) => (\n\t\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\t\tkey={h}\n\t\t\t\t\t\t\t\t\tclassName={`border-b border-border px-3 py-2 text-left font-medium text-muted-foreground${\n\t\t\t\t\t\t\t\t\t\ti >= 3 && i !== 6 ? \" text-right\" : \"\"\n\t\t\t\t\t\t\t\t\t}`}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{h}\n\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t{sessions.length === 0 && (\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td colSpan={HEADERS.length} className=\"px-3 py-4 text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tNo active sessions.\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{sessions.map((s) => {\n\t\t\t\t\t\t\tconst srs = seriesById.get(s.sessionId);\n\t\t\t\t\t\t\tconst sparkPts =\n\t\t\t\t\t\t\t\tsrs?.data.map((d) => d.tokens) ?? [];\n\t\t\t\t\t\t\tconst color = srs?.color ?? \"#58a6ff\";\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<tr\n\t\t\t\t\t\t\t\t\tkey={`${s.pid}:${s.sessionId}`}\n\t\t\t\t\t\t\t\t\tclassName=\"border-b border-border/50 hover:bg-bg-elevated/40\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right font-mono\">{s.pid}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2\" title={s.repoRoot ?? undefined}>\n\t\t\t\t\t\t\t\t\t\t{s.displayName || s.repoRoot || \"\\u2014\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2\">{s.model ?? \"\\u2014\"}</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">\n\t\t\t\t\t\t\t\t\t\t<span className={`sessions-pct-pill ${pctClass(s.percent)}`}>\n\t\t\t\t\t\t\t\t\t\t\t{fmtPct(s.percent)}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">\n\t\t\t\t\t\t\t\t\t\t{fmtWindow(s.tokens, s.ctxWindow)}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2 text-right\">{fmtAge(now - s.lastSeen)}</td>\n\t\t\t\t\t\t\t\t\t<td\n\t\t\t\t\t\t\t\t\t\ttitle={s.stateDir ?? undefined}\n\t\t\t\t\t\t\t\t\t\tclassName=\"px-3 py-2 font-mono text-muted-foreground\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{s.stateDir\n\t\t\t\t\t\t\t\t\t\t\t? s.stateDir.split(\"/\").pop() || s.stateDir\n\t\t\t\t\t\t\t\t\t\t\t: \"\\u2014\"}\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2\">\n\t\t\t\t\t\t\t\t\t\t<Sparkline points={sparkPts} color={color} />\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/SessionsTab.tsx — Sessions tab (S39).\n *\n * Renders: window selector (5/15/30/60 min), sessions summary tiles,\n * the stacked-memory graph (SessionsMemoryChart), and the active sessions\n * table (ActiveSessionsTable).\n *\n * Data sources: fetchSessions() + fetchSessionTimeseries(minutes). Both poll\n * every 2s for near-real-time updates. useSSE() listens for session_sample\n * events emitted by appendTokenSample (Step 5) so the chart refetches\n * instantly between polls when a new token sample is appended to events.log.\n *\n * Empty state: shows \"No active sessions.\" when the /api/sessions response\n * returns an empty array (e.g. no pi processes are running yet).\n */\n\nimport type React from \"react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useApi } from \"../hooks/useApi\";\nimport { useSSE } from \"../hooks/useSSE\";\nimport { fetchSessions, fetchSessionTimeseries } from \"../api/client\";\nimport type { SseEvent, SseSessionSample, SessionsResponse, SessionTimeseriesResponse } from \"@contracts\";\nimport { SessionsMemoryChart } from \"../components/SessionsMemoryChart\";\nimport { ActiveSessionsTable } from \"../components/ActiveSessionsTable\";\nimport { Toggle } from \"../components/ui/toggle\";\nimport { Card, CardContent } from \"../components/ui/card\";\n\n/**\n * Shape of the {@link SseSessionSample} SSE event appended to events.log by\n * `appendTokenSample` (src/store/sqlite/global-index.ts). The contract now\n * defines this variant as part of the {@link SseEvent} union (Step 5); the\n * local alias keeps the surface terser in this file.\n */\ntype SessionSampleEvent = SseSessionSample;\n\n/** Type guard: narrows an SSE event to a session_sample event. */\nfunction isSessionSample(\n\te: SseEvent,\n): e is SessionSampleEvent {\n\treturn e.type === \"session_sample\";\n}\n\nconst WINDOWS: ReadonlyArray<{ value: number; label: string }> = [\n\t{ value: 5, label: \"5m\" },\n\t{ value: 15, label: \"15m\" },\n\t{ value: 30, label: \"30m\" },\n\t{ value: 60, label: \"60m\" },\n] as const;\n\ninterface SummaryTilesProps {\n\tsessions: SessionsResponse;\n}\n\nfunction SummaryTiles({ sessions }: SummaryTilesProps): React.ReactElement {\n\tconst active = sessions.sessions.length;\n\tconst combinedTokens = sessions.sessions.reduce(\n\t\t(sum, s) => sum + (s.tokens ?? 0),\n\t\t0,\n\t);\n\tconst maxWindow = sessions.sessions.reduce(\n\t\t(max, s) => Math.max(max, s.ctxWindow),\n\t\t0,\n\t);\n\tconst combinedPct = maxWindow > 0 ? (combinedTokens / maxWindow) * 100 : 0;\n\treturn (\n\t\t<div className=\"grid grid-cols-2 gap-4 md:grid-cols-4\">\n\t\t\t<Card>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">Active sessions</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{active}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">Combined tokens</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">\n\t\t\t\t\t\t{combinedTokens >= 100_000\n\t\t\t\t\t\t\t? `${(combinedTokens / 1000).toFixed(0)}k`\n\t\t\t\t\t\t\t: combinedTokens.toLocaleString()}\n\t\t\t\t\t</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\tCombined % of max window\n\t\t\t\t\t</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{Math.round(combinedPct)}%</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t\t<Card>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">Pruned (stale)</span>\n\t\t\t\t\t<div className=\"text-xl font-semibold\">{sessions.pruned}</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t</div>\n\t);\n}\n\nexport default function SessionsTab(): React.ReactElement {\n\tconst [minutes, setMinutes] = useState(30);\n\n\tconst {\n\t\tdata: sessionsData,\n\t\terror: sessionsErr,\n\t\trefetch: refetchSessions,\n\t} = useApi<SessionsResponse>(\n\t\tuseCallback(() => fetchSessions(), []),\n\t\t{ pollInterval: 2_000 },\n\t);\n\n\tconst {\n\t\tdata: timeseriesData,\n\t\terror: timeseriesErr,\n\t\trefetch: refetchTimeseries,\n\t} = useApi<SessionTimeseriesResponse>(\n\t\tuseCallback(() => fetchSessionTimeseries(minutes), [minutes]),\n\t\t{ pollInterval: 2_000 },\n\t);\n\n\tconst { events } = useSSE();\n\n\t// Filter SSE events for session_sample pushes (Step 5) and trigger an\n\t// immediate refetch when a new sample arrives, so the chart updates\n\t// between the 2s polls. The events array grows as new samples arrive; the\n\t// length-based dependency means the effect re-runs when a new sample event\n\t// lands (not on every poll). We debounce via a microtask guard.\n\tconst sampleEvents = useMemo(\n\t\t() => events.filter(isSessionSample),\n\t\t[events],\n\t);\n\n\tconst lastSampleTs = useMemo(() => {\n\t\tif (sampleEvents.length === 0) return null;\n\t\treturn sampleEvents[sampleEvents.length - 1].ts;\n\t}, [sampleEvents]);\n\n\tuseEffect(() => {\n\t\tif (lastSampleTs == null) return;\n\t\trefetchSessions();\n\t\trefetchTimeseries();\n\t}, [lastSampleTs, refetchSessions, refetchTimeseries]);\n\n\tif (sessionsErr && !sessionsData) {\n\t\treturn (\n\t\t\t<div className=\"tab-stub\">\n\t\t\t\tError loading sessions: {sessionsErr.message}\n\t\t\t</div>\n\t\t);\n\t}\n\tif (timeseriesErr && !timeseriesData) {\n\t\treturn (\n\t\t\t<div className=\"tab-stub\">\n\t\t\t\tError loading timeseries: {timeseriesErr.message}\n\t\t\t</div>\n\t\t);\n\t}\n\tif (!sessionsData) {\n\t\treturn <div className=\"tab-stub\">Loading sessions…</div>;\n\t}\n\n\tconst sessions = sessionsData.sessions;\n\tconst series = timeseriesData?.series ?? [];\n\tconst totals = timeseriesData?.totals ?? [];\n\tconst windowLabel =\n\t\tWINDOWS.find((w) => w.value === minutes)?.label ?? `${minutes}m`;\n\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t<h2 className=\"font-heading text-lg font-semibold\">\n\t\t\t\t\tSession Memory Graph\n\t\t\t\t</h2>\n\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t{WINDOWS.map((w) => (\n\t\t\t\t\t\t<Toggle\n\t\t\t\t\t\t\tkey={w.value}\n\t\t\t\t\t\t\tpressed={minutes === w.value}\n\t\t\t\t\t\t\tonClick={() => setMinutes(w.value)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{w.label}\n\t\t\t\t\t\t</Toggle>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{sessions.length === 0 ? (\n\t\t\t\t<div className=\"text-sm text-muted-foreground\">No active sessions.</div>\n\t\t\t) : (\n\t\t\t\t<>\n\t\t\t\t\t<SummaryTiles sessions={sessionsData} />\n\t\t\t\t\t<SessionsMemoryChart series={series} totals={totals} />\n\t\t\t\t\t<h2 className=\"font-heading text-lg font-semibold\">\n\t\t\t\t\t\tActive Sessions\n\t\t\t\t\t</h2>\n\t\t\t\t\t<ActiveSessionsTable sessions={sessions} series={series} />\n\t\t\t\t\t<div className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t{series.length} series · {totals.length} timestamps ·{\" \"}\n\t\t\t\t\t\t{windowLabel} window · updated{\" \"}\n\t\t\t\t\t\t{timeseriesData?.updatedAt ?? sessionsData.updatedAt}\n\t\t\t\t\t</div>\n\t\t\t\t</>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["pivot","series","totals","map","sessionIds","s","pt","row","sid","a","b","fmtTs","ts","fmtTokens","value","n","gradId","sessionId","SessionsMemoryChart","data","useMemo","jsx","Card","CardContent","ResponsiveContainer","jsxs","AreaChart","CartesianGrid","XAxis","YAxis","v","Tooltip","Legend","Area","Line","HEADERS","fmtAge","ms","m","pctClass","pct","fmtPct","fmtWindow","tokens","window","Sparkline","points","color","min","range","w","h","stepX","segments","p","i","x","y","indexSeries","ActiveSessionsTable","sessions","seriesById","now","srs","sparkPts","d","isSessionSample","e","WINDOWS","SummaryTiles","active","combinedTokens","sum","maxWindow","max","combinedPct","SessionsTab","minutes","setMinutes","useState","sessionsData","sessionsErr","refetchSessions","useApi","useCallback","fetchSessions","timeseriesData","timeseriesErr","refetchTimeseries","fetchSessionTimeseries","events","useSSE","sampleEvents","lastSampleTs","useEffect","windowLabel","Toggle","Fragment"],"mappings":"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"}
|