@buzzie-ai/jannal 0.4.0 → 0.6.0
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/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buzzie-ai/jannal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Intercept, visualize, and optimize LLM context windows. Proxy that sits between your AI tools and the Anthropic API.",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./server.js"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"jannal": "
|
|
10
|
+
"jannal": "bin/jannal.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"bin/",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
|
-
"url": "https://github.com/Buzzie-AI/jannal"
|
|
42
|
+
"url": "git+https://github.com/Buzzie-AI/jannal.git"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=18.0.0"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e={connected:!1,reqs:[],selectedReq:null,profiles:{},activeProfile:`All Tools`,premium:!1,routerMode:`off`,toolsUsed:new Set,groups:{},groupView:!0,expandedGroups:{},sessions:{},activeSessionTab:null,showSettings:!1,strip:{mode:`off`,keepN:3,threshold:2e3}},t={segment:null,segIndex:null,view:`formatted`,fullContent:``,parsedTools:null,loading:!1},n={system:`#60A5FA`,tools:`#FB923C`,message:`#22D3EE`,assistant:`#34D399`,tool_result:`#FBBF24`,tool_use:`#A78BFA`},r={system:`--seg-system`,tools:`--seg-tools`,message:`--seg-message`,assistant:`--seg-assistant`,tool_result:`--seg-tool-result`,tool_use:`--seg-tool-use`};function i(e){let t=r[e];if(t){let e=getComputedStyle(document.documentElement).getPropertyValue(t).trim();if(e)return e}return n[e]||`#64748B`}var a=3.8;function o(e){if(!e)return 0;let t=typeof e==`string`?e:JSON.stringify(e);return Math.ceil(t.length/a)}function s(e){return e.type===`message`&&e.role===`assistant`?i(`assistant`):e.type===`message`?i(`message`):i(e.type)}function c(e){return e.type===`message`?e.role===`user`?`User Message`:`Assistant Message`:e.type===`tool_result`?`Tool Result`:e.type===`tool_use`?`Tool Use`:e.type===`system`?`System Prompt`:e.type===`tools`?`Tool Definitions`:e.type}function l(e){return e>=1e6?(e/1e6).toFixed(1)+`M`:e>=1e3?(e/1e3).toFixed(1)+`k`:e.toString()}function u(e){return e>=1?`$`+e.toFixed(2):e>=.01?`$`+e.toFixed(3):`$`+e.toFixed(4)}function d(e){return e?e.includes(`opus-4-6`)||e.includes(`opus-4.6`)||e.includes(`opus-4-5`)||e.includes(`opus-4.5`)?5:e.includes(`opus`)?15:e.includes(`haiku-4`)?1:e.includes(`haiku`)?.8:3:3}function f(e){let t=document.createElement(`div`);return t.textContent=e,t.innerHTML}function p(e,t,n){return n||!t||!t.tools||t.tools.length===0?!0:t.mode===`blocklist`?!t.tools.includes(e):t.tools.includes(e)}function m(e){let t=e?.name||``;if(t.startsWith(`mcp__`)){let e=t.split(`__`);if(e.length>=3){let t=e[1].split(`_`);return t[t.length-1].toLowerCase()}}let n=t.match(/^([a-zA-Z0-9]+)[_\/]/);return n?n[1].toLowerCase():`other`}function h(e){let t=new Map;for(let n of e){let e=m(n);t.has(e)||t.set(e,[]),t.get(e).push(n)}let n=new Map,r=[...t.keys()].sort((e,t)=>e===`other`?1:t===`other`?-1:e.localeCompare(t));for(let e of r)n.set(e,t.get(e));return n}var g=`jannal_session`,_=`jannal_daily_costs`,v=`jannal_daily_savings`,ee=500,y=null;function b(e){y&&clearTimeout(y),y=setTimeout(()=>{try{let t={reqs:e.reqs,selectedReq:e.selectedReq,groupView:e.groupView,sessions:e.sessions,activeSessionTab:e.activeSessionTab,savedAt:Date.now()};localStorage.setItem(g,JSON.stringify(t))}catch(e){console.warn(`Failed to persist session:`,e.message)}y=null},ee)}function te(e){try{let t=localStorage.getItem(g);if(!t)return!1;let n=JSON.parse(t),r=n.reqs||n.turns,i=n.selectedReq??n.selectedTurn;if(r&&Array.isArray(r)&&r.length>0){e.reqs=r,e.selectedReq=i!=null&&i<e.reqs.length?i:e.reqs.length-1,n.groupView!=null&&(e.groupView=n.groupView),n.sessions&&(e.sessions=n.sessions),n.activeSessionTab!==void 0&&(e.activeSessionTab=n.activeSessionTab);for(let t of e.reqs)t.sessionId&&!t.tabKey&&(t.tabKey=t.sessionPath||t.sessionId);if(e.toolsUsed){e.toolsUsed.clear();for(let t of e.reqs)t.toolsUsed?.length&&t.toolsUsed.forEach(t=>e.toolsUsed.add(t))}return!0}}catch(e){console.warn(`Failed to restore session:`,e.message)}return!1}function ne(e){let t=0,n=e.reqs.map(e=>{let n=e.actualCost?.totalCost??e.estimatedCost?.totalCost??0;return t+=n,{request:e.turn,model:e.model,timestamp:e.timestamp,inputTokens:e.actualUsage?.input_tokens??e.totalEstimatedTokens,outputTokens:e.actualUsage?.output_tokens??0,cost:n,segments:e.segments?.map(e=>({name:e.name,type:e.type,tokens:e.tokens}))??[]}}),r={exportedAt:new Date().toISOString(),requestCount:n.length,totalCost:t,requests:n};return JSON.stringify(r,null,2)}function re(e){let t=[`Request`,`Model`,`Timestamp`,`Input Tokens`,`Output Tokens`,`Cost ($)`],n=e.reqs.map(e=>[e.turn,e.model,new Date(e.timestamp).toISOString(),e.actualUsage?.input_tokens??e.totalEstimatedTokens??``,e.actualUsage?.output_tokens??0,(e.actualCost?.totalCost??e.estimatedCost?.totalCost??0).toFixed(4)]);return[t.join(`,`),...n.map(e=>e.map((e,t)=>t===5?e:`"${String(e)}"`).join(`,`))].join(`
|
|
2
|
+
`)}function ie(e){if(!(!e||e<=0))try{let t=new Date().toISOString().slice(0,10),n=JSON.parse(localStorage.getItem(_)||`{}`);n[t]=(n[t]||0)+e,localStorage.setItem(_,JSON.stringify(n))}catch{}}function ae(){try{let e=new Date().toISOString().slice(0,10);return JSON.parse(localStorage.getItem(_)||`{}`)[e]||0}catch{return 0}}function oe(e,t){if(!((!e||e<=0)&&(!t||t<=0)))try{let n=new Date().toISOString().slice(0,10),r=JSON.parse(localStorage.getItem(v)||`{}`);r[n]||(r[n]={cost:0,tokens:0}),typeof r[n]==`number`&&(r[n]={cost:r[n],tokens:0}),r[n].cost+=e||0,r[n].tokens+=t||0,localStorage.setItem(v,JSON.stringify(r))}catch{}}function se(){try{let e=new Date().toISOString().slice(0,10),t=JSON.parse(localStorage.getItem(v)||`{}`)[e];return t?typeof t==`number`?{cost:t,tokens:0}:{cost:t.cost||0,tokens:t.tokens||0}:{cost:0,tokens:0}}catch{return{cost:0,tokens:0}}}function x(e,t){let n=new Blob([e],{type:`application/octet-stream`}),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=t,i.click(),URL.revokeObjectURL(r)}function S(e={}){w(),T(),de(),ue(),D(),e.skipDetail||A(),le(),ce()}function ce(){let t=document.getElementById(`stripBadge`);t&&(t.style.display=e.strip.mode===`off`?`none`:`flex`)}function le(){let t=document.getElementById(`exportBtn`);t&&(t.disabled=e.reqs.length===0,t.title=e.reqs.length===0?`No data to export`:`Export session as JSON or CSV`)}function C(){let t=[];for(let n=0;n<e.reqs.length;n++){let r=e.reqs[n];(e.activeSessionTab===null||r.tabKey===e.activeSessionTab)&&t.push({originalIndex:n,req:r})}return t}function ue(){let t=document.getElementById(`sessionTabs`);if(!t)return;let n=Object.keys(e.sessions);if(n.length===0){t.classList.remove(`visible`);return}t.classList.add(`visible`);let r=`<div class="session-tab${e.activeSessionTab===null?` active`:``}" data-tab="">All</div>`;for(let t of n){let n=e.sessions[t],i=e.activeSessionTab===t;r+=`<div class="session-tab${i?` active`:``}" data-tab="${f(t)}">`,r+=`<span class="session-tab-label">${f(n.label)}</span>`,n.path&&(r+=`<span class="session-tab-path">${f(n.path)}</span>`),r+=`<span class="session-tab-close" data-tab-close="${f(t)}">×</span>`,r+=`</div>`}t.innerHTML=r,t.onclick=e=>{let t=e.target.closest(`[data-tab-close]`);if(t){e.stopPropagation(),window.dismissSessionTab(t.dataset.tabClose);return}let n=e.target.closest(`[data-tab]`);n&&window.selectSessionTab(n.dataset.tab||null)}}function w(){document.getElementById(`statusDot`).className=`status-dot ${e.connected?`connected`:`disconnected`}`;let t=document.getElementById(`statusText`);t.textContent=e.connected?`Connected`:`Disconnected`,t.style.color=e.connected?`var(--green)`:`var(--red)`;let n=document.getElementById(`reqBadge`);n&&(n.textContent=`Req ${e.reqs.length}`);let r=ae(),i=document.getElementById(`dailyCost`);i&&(i.textContent=`Cost: ${u(r)}`);let a=document.getElementById(`dailySaved`);if(a)if(!e.premium)a.textContent=`Saved: Pro`,a.className=`daily-saved premium-locked`,a.title=`Savings intelligence requires Pro`;else{let{cost:e,tokens:t}=se(),n=t>0?` (${l(t)})`:``;a.textContent=`Saved: ${u(e)}${n}`,a.className=`daily-saved`,a.classList.toggle(`has-savings`,e>0),a.title=`Estimated daily savings from router intelligence`}let o=document.getElementById(`routerBadge`);if(o)if(e.premium){let t=e.routerMode||`off`;o.textContent={off:`Router Off`,shadow:`Router Shadow`,auto:`Router Auto`}[t]||`Router`,o.className=`router-badge router-badge--${t}`;let n=document.getElementById(`routerPopover`);if(n)for(let e of n.querySelectorAll(`.router-popover-opt`))e.classList.toggle(`active`,e.dataset.mode===t),e.classList.remove(`premium-locked`)}else{o.textContent=`Router Pro`,o.className=`router-badge premium-locked`;let e=document.getElementById(`routerPopover`);if(e)for(let t of e.querySelectorAll(`.router-popover-opt`))t.classList.add(`premium-locked`),t.classList.remove(`active`)}}function T(){let t=e.selectedReq===null?null:e.reqs[e.selectedReq],n=document.getElementById(`barInner`),r=document.getElementById(`barOuter`);if(!t){n.innerHTML=`<div class="bar-empty"><span>No data yet</span></div>`,r.className=`bar-outer`,document.getElementById(`barLegend`).innerHTML=``,document.getElementById(`barTotal`).textContent=`0 / 0`,document.getElementById(`barPct`).textContent=`0%`;return}let i=t.budget,a=t.actualUsage?t.actualUsage.input_tokens:t.totalEstimatedTokens,o=a/i*100;r.className=`bar-outer`+(o>95?` pressure-critical`:o>80?` pressure-high`:``);let u=1,d=0;if(o>0&&o<30){let e=(1-o/30)**2;u=(o+(65-o)*e)/o,d=e}let f=u>1,p=[];for(let e=0;e<t.segments.length;e++){let n=t.segments[e],r=s(n),a=n.tokens/i*100*u,o=p[p.length-1];o&&o.color===r&&a<.3?(o.tokens+=n.tokens,o.count++,o.endIndex=e):p.push({color:r,tokens:n.tokens,name:n.name,count:1,startIndex:e,endIndex:e})}let m=``;for(let e of p){let t=e.tokens/i*100*u;if(t<.1)continue;let n=e.count>1?`${e.name} (×${e.count})`:e.name;m+=`<div class="bar-segment" style="width:${t}%;background:linear-gradient(180deg,${e.color}cc,${e.color}88);border-right:1.5px solid var(--bg3)" title="${n}: ${l(e.tokens)} tokens" onclick="openModal(${e.startIndex})">`,t>5&&(m+=`<span>${t>15?n:l(e.tokens)}</span>`),m+=`</div>`}f&&(m+=`<div class="bar-break" style="opacity:${d}"></div>`),o<100&&(m+=`<div class="bar-empty"><span>${l(i-a)} free</span></div>`),n.innerHTML=m,r.querySelectorAll(`.bar-marker`).forEach(e=>{e.style.display=f?`none`:``});let h=new Map;for(let e of t.segments){let t=c(e),n=s(e);h.has(t)||h.set(t,n)}document.getElementById(`barLegend`).innerHTML=Array.from(h.entries()).map(([e,t])=>`<div class="legend-item"><div class="legend-dot" style="background:${t}"></div>${e}</div>`).join(``);let g=t.actualUsage?l(t.actualUsage.input_tokens):t.tokenCountSource===`count_tokens`?l(t.totalEstimatedTokens):`~${l(t.totalEstimatedTokens)}`,_=document.getElementById(`barTotal`),v=document.getElementById(`barPct`);_.textContent=`${g} / ${l(i)}`,_.style.color=o>95?`var(--red)`:o>80?`var(--orange)`:`var(--text)`,v.textContent=`${o.toFixed(1)}%`,v.style.color=o>95?`var(--red)`:o>80?`var(--orange)`:`var(--text3)`}function de(){let e=document.getElementById(`tokenChartContainer`),t=document.getElementById(`tokenChart`);if(!e||!t)return;let n=C();if(n.length<2){e.style.display=`none`;return}e.style.display=`block`;let r=n.map(e=>e.req.actualUsage?.input_tokens??e.req.totalEstimatedTokens??0),i=Math.max(...r),a=Math.min(...r),o=i-a||1;t.innerHTML=`
|
|
3
|
+
<svg viewBox="0 0 200 36" preserveAspectRatio="none" class="token-chart-svg">
|
|
4
|
+
<polyline
|
|
5
|
+
fill="none"
|
|
6
|
+
stroke="var(--cyan)"
|
|
7
|
+
stroke-width="2"
|
|
8
|
+
stroke-linecap="round"
|
|
9
|
+
stroke-linejoin="round"
|
|
10
|
+
points="${r.map((e,t)=>`${t/(r.length-1)*200},${36-(e-a)/o*32-2}`).join(` `)}"
|
|
11
|
+
/>
|
|
12
|
+
</svg>
|
|
13
|
+
<div class="token-chart-hint">${r.length} reqs · ${l(a)} → ${l(i)} tokens</div>
|
|
14
|
+
`}function E(){return`ANTHROPIC_BASE_URL=http://localhost:${location.port===`5173`?`4455`:location.port||`4455`} claude`}function fe(){navigator.clipboard.writeText(E()).then(()=>{let e=document.getElementById(`copyCommandBtn`);if(e){let t=e.textContent;e.textContent=`Copied!`,e.style.color=`var(--green)`,setTimeout(()=>{e.textContent=t,e.style.color=``},1500)}})}function D(){let t=document.getElementById(`reqList`),n=C();if(n.length===0){e.reqs.length===0?t.innerHTML=`<div class="empty"><div class="empty-icon waiting">🔍</div><h2>Waiting for requests...</h2><p>Start Claude Code with:<br><code style="color:var(--cyan);font-size:11px">${E()}</code> <button id="copyCommandBtn" class="copy-command-btn" onclick="copyClaudeCommand()" title="Copy to clipboard">Copy</button></p></div>`:t.innerHTML=`<div class="empty"><div class="empty-icon">🔍</div><h2>No requests in this session</h2></div>`;return}let r=document.getElementById(`viewToggleBtn`);r&&(r.textContent=e.groupView?`Grouped`:`Flat`,r.classList.toggle(`active`,e.groupView));let i=t.scrollTop;e.groupView&&Object.keys(e.groups).length>0?me(t,n):pe(t,n),t.scrollTop=i}function pe(e,t){let n=``;for(let e=t.length-1;e>=0;e--)n+=O(t[e].originalIndex,e+1);e.innerHTML=n}function O(t,n){let r=e.reqs[t],i=r.actualUsage?r.actualUsage.input_tokens:r.totalEstimatedTokens,a=Math.min(i/r.budget*100,100),o=a>95?`var(--red)`:a>80?`var(--orange)`:`var(--green)`,s=r.model.replace(`claude-`,``).replace(/-\d{8,}$/,``),c=r.actualUsage?l(r.actualUsage.input_tokens):r.tokenCountSource===`count_tokens`?l(r.totalEstimatedTokens):`~`+l(r.totalEstimatedTokens),d=``;r.actualUsage&&(d=`${l(r.actualUsage.input_tokens)} in / ${l(r.actualUsage.output_tokens)} out`);let f=r.actualCost?u(r.actualCost.totalCost):r.estimatedCost?`~`+u(r.estimatedCost.totalCost):``,p=`<div class="req-card${t===e.selectedReq?` selected`:``}" onclick="selectReq(${t})">`,m=n??r.turn;return p+=`<div class="req-card-head"><span class="req-label">Req ${m}</span><span class="req-tokens" style="color:${o}">${c}</span></div>`,p+=`<div class="req-mini-bar"><div class="req-mini-fill" style="width:${a}%;background:${o}"></div></div>`,p+=`<div class="req-meta"><span>${s}</span>`,d&&(p+=`<span class="req-io">${d}</span>`),f&&(p+=`<span class="req-cost-inline">${f}</span>`),p+=`</div>`,p+=`</div>`,p}function me(t,n){let r=new Set(n.map(e=>e.originalIndex)),i=new Map,a=0;for(let e of n)i.set(e.originalIndex,++a);let o=Object.keys(e.groups).map(Number).sort((e,t)=>t-e),s=``;for(let t of o){let n=e.groups[t],a=n.reqIndices.filter(e=>r.has(e));if(a.length===0)continue;let o=e.expandedGroups[t]!==!1,c=0,d=0;for(let t of a){let n=e.reqs[t];n&&(n.actualCost?c+=n.actualCost.totalCost:n.estimatedCost&&(c+=n.estimatedCost.totalCost),d+=n.actualUsage?.input_tokens??n.totalEstimatedTokens??0)}let f=a.length,p=t+1;s+=`<div class="group-card">`,s+=`<div class="group-header" onclick="toggleGroup(${t})">`,s+=`<span class="group-chevron ${o?`expanded`:``}">▶</span>`,s+=`<span class="group-title">Turn ${p}</span>`,s+=`<div class="group-summary">`,s+=`<span class="group-tokens">${l(d)}</span>`,s+=`<span class="group-cost">${u(c)}</span>`,s+=`<span class="group-req-count">${f} req${f===1?``:`s`}</span>`,s+=`</div></div>`,s+=`<div class="group-children ${o?``:`collapsed`}">`;let m={};for(let t of a){let n=e.reqs[t],r=n?.sessionHash||`unknown`;m[r]||(m[r]={reqIndices:[],model:n?.model||`unknown`}),m[r].reqIndices.push(t)}let h=Object.keys(m);if(h.length>1){let e=h.sort((e,t)=>m[t].reqIndices.length-m[e].reqIndices.length),t=0;for(let n of e){let e=m[n],r=e.model||`unknown`,a=t===0,o=a?`Main`:`Subagent`,c=a?`main`:`subagent`;s+=`<div class="group-session-label">`,s+=`<span class="session-pill ${c}">${o}</span>`,s+=`<span>${r} · ${e.reqIndices.length} req${e.reqIndices.length===1?``:`s`}</span>`,s+=`</div>`;for(let t=e.reqIndices.length-1;t>=0;t--)s+=O(e.reqIndices[t],i.get(e.reqIndices[t]));t++}}else for(let e=a.length-1;e>=0;e--)s+=O(a[e],i.get(a[e]));let g=new Date(n.startTime).toLocaleTimeString(),_=new Date(n.endTime).toLocaleTimeString(),v=g===_?g:`${g} – ${_}`;s+=`<div class="group-time">${v}</div>`,s+=`</div></div>`}t.innerHTML=s}function k(){let t=document.getElementById(`settingsBody`),n=document.getElementById(`settingsOverlay`),r=e.strip,i=e=>r.mode===e?`checked`:``;t.innerHTML=`<div class="settings-view">
|
|
15
|
+
<div class="settings-section">
|
|
16
|
+
<div class="settings-section-title">Smart Strip</div>
|
|
17
|
+
<div class="settings-section-desc">Reduce token usage by stripping tool call/result messages from past conversation turns. Only the user message and final assistant response are kept.</div>
|
|
18
|
+
<div class="settings-radio">
|
|
19
|
+
<label><input type="radio" name="stripMode" value="off" ${i(`off`)}> Off <span style="color:var(--text3);font-size:10px">— no modification</span></label>
|
|
20
|
+
<label><input type="radio" name="stripMode" value="keep_n" ${i(`keep_n`)}> Keep last <input type="number" class="settings-inline-input" id="stripKeepN" value="${r.keepN}" min="1" max="20"> turns intact</label>
|
|
21
|
+
<label><input type="radio" name="stripMode" value="strip_all" ${i(`strip_all`)}> Strip all past turns <span style="color:var(--text3);font-size:10px">— most aggressive</span></label>
|
|
22
|
+
<label><input type="radio" name="stripMode" value="smart_size" ${i(`smart_size`)}> Strip turns over <input type="number" class="settings-inline-input" id="stripThreshold" value="${r.threshold}" min="100" step="500"> tokens</label>
|
|
23
|
+
</div>
|
|
24
|
+
${r.mode===`off`?``:`<div class="settings-savings">Smart Strip is active. Savings will appear per request in the context bar.</div>`}
|
|
25
|
+
</div>
|
|
26
|
+
</div>`,e.showSettings?n.classList.add(`open`):n.classList.remove(`open`)}function A(){let t=document.getElementById(`detailBody`),n=document.getElementById(`detailTitle`),r=document.getElementById(`detailMeta`);if(e.selectedReq===null||!e.reqs[e.selectedReq]){n.textContent=`Segment Breakdown`,r.textContent=``,t.innerHTML=`<div class="empty"><div class="empty-icon">📊</div><h2>No request selected</h2><p>Click a request on the left to see its context breakdown.</p></div>`;return}let i=e.reqs[e.selectedReq];n.textContent=`Req ${i.turn} — Segment Breakdown`,r.textContent=`${i.model} | ${i.segments.length} segments | ${i.messageCount} messages`;let a=``;a+=`<div class="stats-grid">`;let o=i.segments?.find(e=>e.type===`system`);if(o&&i.budget){let e=o.tokens/i.budget*100;e>15&&(a+=`<div class="warning-box">`,a+=`<div class="warning-box-title">System prompt is large</div>`,a+=`<div class="usage-row"><span class="usage-label">System prompt</span><span class="usage-value" style="color:var(--orange)">${l(o.tokens)} tokens (${e.toFixed(1)}% of context)</span></div>`,a+=`<div style="margin-top:6px;font-size:10px;color:var(--text3)">Consider trimming to free context for conversation.</div>`,a+=`</div>`)}if(i.filteringActive&&i.removedTools&&i.removedTools.length>0&&(a+=`<div class="filter-box">`,a+=`<div class="filter-box-title">Filtering Active</div>`,a+=`<div class="usage-row"><span class="usage-label">Original tools</span><span class="usage-value" style="color:var(--text2)">${i.originalToolCount}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">After filtering</span><span class="usage-value" style="color:var(--green)">${i.filteredToolCount}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Removed</span><span class="usage-value" style="color:var(--orange)">${i.removedTools.length} tools</span></div>`,i.tokensSaved&&(a+=`<div class="usage-row"><span class="usage-label">Tokens saved</span><span class="usage-value" style="color:var(--green)">~${l(i.tokensSaved)}</span></div>`),a+=`</div>`),i.actualUsage){let e=i.actualUsage,t=e.cache_read_input_tokens||0,n=e.cache_creation_input_tokens||0,r=t>0||n>0,o=e.input_tokens-i.totalEstimatedTokens,s=e.input_tokens?(o/e.input_tokens*100).toFixed(1):`0.0`;if(a+=`<div class="usage-box">`,a+=`<div class="usage-row"><span class="usage-label">Estimated input</span><span class="usage-value estimated">~${i.totalEstimatedTokens.toLocaleString()}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Actual input</span><span class="usage-value actual">${e.input_tokens.toLocaleString()}</span></div>`,r){a+=`<div class="usage-row"><span class="usage-label" style="padding-left:12px">Cache read</span><span class="usage-value" style="color:var(--green)">${t.toLocaleString()}</span></div>`,a+=`<div class="usage-row"><span class="usage-label" style="padding-left:12px">Cache write</span><span class="usage-value" style="color:var(--cyan,var(--blue))">${n.toLocaleString()}</span></div>`;let r=Math.max(0,e.input_tokens-t-n);a+=`<div class="usage-row"><span class="usage-label" style="padding-left:12px">Uncached</span><span class="usage-value">${r.toLocaleString()}</span></div>`}a+=`<div class="usage-row"><span class="usage-label">Estimation error</span><span class="usage-value" style="color:${Math.abs(parseFloat(s))<15?`var(--green)`:`var(--orange)`}">${o>0?`+`:``}${o.toLocaleString()} (${s}%)</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Output tokens</span><span class="usage-value">${e.output_tokens.toLocaleString()}</span></div>`,i.actualCost&&(a+=`<div style="border-top:1px solid var(--border);margin-top:6px;padding-top:6px">`,a+=`<div class="usage-row"><span class="usage-label">Input cost</span><span class="usage-value" style="color:var(--amber)">${u(i.actualCost.inputCost)}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Output cost</span><span class="usage-value" style="color:var(--amber)">${u(i.actualCost.outputCost)}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Total cost</span><span class="usage-value" style="color:var(--amber);font-size:13px">${u(i.actualCost.totalCost)}</span></div>`,a+=`</div>`),a+=`</div>`}else if(i.estimatedCost){let e=i.tokenCountSource===`count_tokens`;a+=`<div class="usage-box">`,a+=`<div class="usage-row"><span class="usage-label">Input tokens ${e?`(exact)`:`(est.)`}</span><span class="usage-value" style="color:${e?`var(--green)`:`var(--text2)`}">${e?``:`~`}${i.totalEstimatedTokens.toLocaleString()}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Input cost ${e?``:`(est.)`}</span><span class="usage-value" style="color:${e?`var(--amber)`:`var(--text3)`}">${e?``:`~`}${u(i.estimatedCost.totalCost)}</span></div>`,e&&(a+=`<div style="margin-top:4px;font-size:9px;color:var(--text3)">via count_tokens API</div>`),a+=`</div>`}a+=`</div>`;for(let e=0;e<i.segments.length;e++){let t=i.segments[e],n=s(t),r=(t.tokens/i.totalEstimatedTokens*100).toFixed(1),o=Math.min(r,100),c=t.preview?t.preview.slice(0,80).replace(/\n/g,` `):``,u=t.index===void 0?`#${e}`:`msg #${t.index}`;a+=`<div class="segment-row" onclick="openModal(${e})">`,a+=`<div class="seg-color" style="background:${n}"></div>`,a+=`<div class="seg-info">`,a+=`<div class="seg-name" style="color:${n}">${t.name} <span style="color:var(--text3);font-size:10px;font-weight:400">${u}</span></div>`,a+=`<div class="seg-sub">${f(c)}${t.charLength>80?`...`:``}</div>`,a+=`</div>`,a+=`<div class="seg-bar"><div class="seg-bar-fill" style="width:${o}%;background:${n}"></div></div>`,a+=`<div class="seg-pct">${r}%</div>`,a+=`<div class="seg-tokens">${l(t.tokens)}</div>`,a+=`<span class="seg-expand-hint">View</span>`,a+=`</div>`}t.innerHTML=a}async function he(e,t){return(await fetch(`/api/content/${e}/${t}`)).json()}async function ge(e,t,n){return(await fetch(`/api/profiles`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({name:e,mode:t,tools:n})})).json()}function j(e){_e({type:`set_active_profile`,profile:e})}async function M(t,n,r){try{let i=await ge(t,n,r);return i.success&&(e.profiles[t]=i.profile,N(),j(t)),i}catch(e){return console.error(`Failed to save profile:`,e),{error:e.message}}}function N(){let t=document.getElementById(`profileSelect`),n=document.getElementById(`filterBadge`),r=e.activeProfile;t.innerHTML=``;for(let n of Object.keys(e.profiles)){let e=document.createElement(`option`);e.value=n,e.textContent=n,n===r&&(e.selected=!0),t.appendChild(e)}let i=r!==`All Tools`;t.className=`profile-select`+(i?` filtering`:``),n.style.display=i?`flex`:`none`}function P(t,n){let r=n.groupId;if(r==null)return;if(!e.groups[r]){e.groups[r]={id:r,reqIndices:[],sessions:{},startTime:n.timestamp,endTime:n.timestamp};for(let t of Object.keys(e.expandedGroups))e.expandedGroups[t]=!1;e.expandedGroups[r]=!0}let i=e.groups[r];i.reqIndices.push(t),i.endTime=Math.max(i.endTime,n.timestamp);let a=n.sessionHash||`unknown`;i.sessions[a]||(i.sessions[a]={reqIndices:[],model:n.model}),i.sessions[a].reqIndices.push(t)}function F(){e.groups={},e.expandedGroups={};for(let t=0;t<e.reqs.length;t++)P(t,e.reqs[t]);let t=Object.keys(e.groups).map(Number);if(t.length>0){let n=Math.max(...t);for(let r of t)e.expandedGroups[r]=r===n}}var I;function _e(e){I&&I.readyState===1&&I.send(JSON.stringify(e))}function L(){let t=location.protocol===`https:`?`wss:`:`ws:`,n=location.port===`5173`?`localhost:4455`:location.host;I=new WebSocket(`${t}//${n}`),I.onopen=()=>{e.connected=!0,w()},I.onclose=()=>{e.connected=!1,w(),setTimeout(L,2e3)},I.onmessage=t=>{let n=JSON.parse(t.data);if(n.type===`connected`){e.premium=!!n.premium,n.profiles&&(e.profiles=n.profiles),n.activeProfile&&(e.activeProfile=n.activeProfile),n.routerMode!=null&&(e.routerMode=n.routerMode),n.strip&&Object.assign(e.strip,n.strip),N(),S();return}if(n.type===`request`){if(n.toolsUsed&&n.toolsUsed.length&&n.toolsUsed.forEach(t=>e.toolsUsed.add(t)),e.reqs.push(n),n.sessionId&&n.sessionPath){let t=n.sessionPath;e.sessions[t]?e.sessions[t].sessionIds.includes(n.sessionId)||e.sessions[t].sessionIds.push(n.sessionId):e.sessions[t]={id:t,label:n.sessionLabel||n.sessionId,path:n.sessionPath||null,sessionIds:[n.sessionId],firstSeen:n.timestamp},n.tabKey=t}e.reqs.length>50?(e.reqs.splice(0,e.reqs.length-50),F()):P(e.reqs.length-1,n);let t=e.selectedReq!==null;t||(e.selectedReq=e.reqs.length-1),S({skipDetail:t}),b(e)}if(n.type===`token_count_update`){let t=e.reqs.find(e=>e.turn===n.turn);t&&(t.exactInputTokens=n.exactInputTokens,t.segments=n.segments,t.totalEstimatedTokens=n.exactInputTokens,t.estimatedCost=n.estimatedCost,t.tokenCountSource=`count_tokens`,S({skipDetail:!(e.selectedReq!==null&&e.reqs[e.selectedReq]?.turn===n.turn)}),b(e))}if(n.type===`response_complete`){let t=n.turn==null?e.reqs[e.reqs.length-1]:e.reqs.find(e=>e.turn===n.turn);t&&(t.actualUsage=n.usage,t.stopReason=n.stopReason,n.cost&&(t.actualCost=n.cost,ie(n.cost.totalCost)),n.toolsUsed&&(t.toolsUsed=n.toolsUsed,n.toolsUsed.forEach(t=>e.toolsUsed.add(t))),S({skipDetail:!(e.selectedReq!==null&&e.reqs[e.selectedReq]?.turn===n.turn)}),b(e))}if(n.type===`router_decision`){let t=e.reqs.find(e=>e.turn===n.turn);if(t){let r=!t.router;if(t.router={mode:n.mode,eligible:n.eligible,skip_reason:n.skip_reason,matched_by:n.matched_by,confidence:n.confidence,selected_groups:n.selected_groups,stripped_groups:n.stripped_groups,estimated_tokens_saved:n.estimated_tokens_saved,sticky_reused:n.sticky_reused},r&&n.estimated_tokens_saved>0){let e=d(t.model);oe(n.estimated_tokens_saved/1e6*e*.1,n.estimated_tokens_saved)}e.selectedReq!==null&&e.reqs[e.selectedReq]?.turn===n.turn&&A(),b(e)}}n.type===`router_mode_changed`&&(e.routerMode=n.mode,w()),n.type===`profiles_updated`&&(e.profiles=n.profiles||{},e.activeProfile=n.active||`All Tools`,N()),n.type===`active_profile_changed`&&(e.activeProfile=n.active||`All Tools`,N()),n.type===`settings_updated`&&(n.strip&&Object.assign(e.strip,n.strip),S())}}async function R(n){let r=e.reqs[e.selectedReq];if(!r)return;let i=r.segments[n];if(!i)return;t.segment=i,t.segIndex=n,t.view=i.type===`tools`?`tools`:`formatted`,t.fullContent=``,t.loading=!0,t.parsedTools=null;let a=s(i);document.getElementById(`modalColorBar`).style.background=a,document.getElementById(`modalTitle`).textContent=i.name;let o=r.groupId==null?``:`Turn ${r.groupId+1} · `;document.getElementById(`modalMeta`).textContent=`${o}${c(i)}${i.role?` (`+i.role+`)`:``}${i.count?` — `+i.count+` tools`:``}`;let u=`
|
|
27
|
+
<div class="modal-stat"><div class="modal-stat-value" style="color:${a}">${l(i.tokens)}</div><div class="modal-stat-label">Tokens</div></div>
|
|
28
|
+
<div class="modal-stat"><div class="modal-stat-value">${(i.charLength||0).toLocaleString()}</div><div class="modal-stat-label">Chars</div></div>
|
|
29
|
+
<div class="modal-stat"><div class="modal-stat-value">${(i.tokens/r.totalEstimatedTokens*100).toFixed(1)}%</div><div class="modal-stat-label">Of total</div></div>
|
|
30
|
+
`;document.getElementById(`modalStats`).innerHTML=u;let d=document.getElementById(`toolsViewBtn`);d.style.display=i.type===`tools`?`inline`:`none`,document.querySelectorAll(`.modal-toolbar button`).forEach(e=>e.classList.remove(`active`)),i.type===`tools`?d.classList.add(`active`):document.getElementById(`formattedBtn`).classList.add(`active`),document.getElementById(`modalSearch`).value=``,document.getElementById(`modalBody`).innerHTML=`<div style="display:flex;align-items:center;justify-content:center;height:200px;color:var(--text3);font-size:13px"><div style="text-align:center"><div style="font-size:24px;margin-bottom:8px;animation:pulse 1s ease-in-out infinite">⏳</div>Loading full content...</div></div>`,document.getElementById(`modalOverlay`).classList.add(`open`);try{let e=await he(r.turn,n);if(e.content){t.fullContent=e.content;let n=document.querySelector(`.modal-stat:nth-child(2) .modal-stat-value`);if(n&&(n.textContent=e.content.length.toLocaleString()),i.type===`tools`)try{t.parsedTools=JSON.parse(e.content)}catch{t.parsedTools=null}}else t.fullContent=i.preview||`(content no longer available — request may have been evicted)`}catch(e){console.error(`Failed to fetch segment content:`,e),t.fullContent=i.preview||`(failed to load content)`}t.loading=!1,V()}function z(){document.getElementById(`modalOverlay`).classList.remove(`open`),t.segment=null,t.parsedTools=null}function B(e,n){t.view=e,document.querySelectorAll(`.modal-toolbar button`).forEach(e=>e.classList.remove(`active`)),n&&n.classList.add(`active`),V()}function V(){let e=document.getElementById(`modalBody`),n=t.fullContent;if(t.loading)return;if(t.view===`tools`&&t.parsedTools){ve(e);return}if(t.view===`raw`){e.innerHTML=`<div class="modal-content" style="white-space:pre-wrap;word-break:break-all">${f(n)}</div>`;return}let r=n;try{let e=JSON.parse(n);r=JSON.stringify(e,null,2)}catch{}let i=r.split(`
|
|
31
|
+
`),a=`<div class="modal-content">`;for(let e=0;e<i.length;e++)a+=`<span class="line"><span class="line-num">${e+1}</span>${f(i[e])}</span>`;a+=`</div>`,e.innerHTML=a}function ve(n){let r=t.parsedTools;if(!r||!r.length){n.innerHTML=`<div style="padding:20px;color:var(--text3)">No tools found</div>`;return}let i=e.profiles[e.activeProfile],a=e.activeProfile===`All Tools`,s=h(r),c=`<div class="modal-tools">`,u=r.filter(e=>p(e.name,i,a)).length;c+=`<div class="modal-tools-header">`,c+=`<div class="modal-tools-header-left"><strong>${u}</strong> of <strong>${r.length}</strong> tools enabled</div>`,c+=`<div style="display:flex;gap:6px">`,c+=`<button class="btn-secondary" onclick="toggleAllTools(true)" style="padding:3px 8px;border-radius:4px;font-size:10px">All</button>`,c+=`<button class="btn-secondary" onclick="toggleAllTools(false)" style="padding:3px 8px;border-radius:4px;font-size:10px">None</button>`,c+=`</div></div>`;let d=[],m=[];for(let[e,t]of s)e===`other`?m.push([e,t]):d.push([e,t]);function g(t){for(let[n,r]of t){let t=[...r].sort((e,t)=>o(t)-o(e)),s=n===`other`?`Other`:n.charAt(0).toUpperCase()+n.slice(1),u=t.reduce((e,t)=>e+o(t),0),d=t.filter(e=>p(e.name,i,a)).length,m=d===t.length;c+=`<div class="tool-group" data-server="${f(n)}">`,c+=`<div class="tool-group-header">`,c+=`<input type="checkbox" class="tool-group-checkbox" data-server="${f(n)}" ${m?`checked`:``} onclick="event.stopPropagation(); toggleGroupCheckbox('${f(n)}', this.checked)">`,c+=`<span class="tool-group-chevron" onclick="toggleGroupAccordion('${f(n)}')">▶</span>`,c+=`<div class="tool-group-title" onclick="toggleGroupAccordion('${f(n)}')">`,c+=`<span class="tool-group-name">${f(s)}</span>`,c+=`<span class="tool-group-meta">${d}/${t.length} tools · ~${l(u)} tok</span>`,c+=`</div>`,c+=`<div class="tool-group-actions">`,c+=`<button class="btn-secondary" onclick="event.stopPropagation(); toggleGroupTools('${f(n)}', true)" style="padding:2px 6px;font-size:9px">All</button>`,c+=`<button class="btn-secondary" onclick="event.stopPropagation(); toggleGroupTools('${f(n)}', false)" style="padding:2px 6px;font-size:9px">None</button>`,c+=`</div></div>`,c+=`<div class="tool-group-body collapsed">`;for(let r of t){let t=p(r.name,i,a),s=o(r),u=(r.description||``).slice(0,120),d=!e.toolsUsed?.has(r.name);c+=`<div class="tool-card" data-tool-name="${f(r.name)}">`,c+=`<input type="checkbox" data-tool="${f(r.name)}" data-server="${f(n)}" ${t?`checked`:``} onchange="onToolToggle()">`,c+=`<div class="tool-card-info">`,c+=`<div class="tool-card-name">${f(r.name)}${d?` <span class="never-used-tag" title="Not used this session">never used</span>`:``}</div>`,u&&(c+=`<div class="tool-card-desc">${f(u)}</div>`),c+=`</div>`,c+=`<div class="tool-card-tokens">~${l(s)} tok</div>`,c+=`</div>`}c+=`</div></div>`}}d.length>0&&(c+=`<div class="tool-section-header">MCP Servers</div>`,g(d)),m.length>0&&(c+=`<div class="tool-section-header">Other Tools</div>`,g(m)),c+=`<div id="toolsSavings"></div>`;let _=e.reqs[e.selectedReq]?.toolsUsed||[];c+=`<div class="save-profile-bar">`,c+=`<input type="text" id="profileNameInput" placeholder="Profile name..." value="">`,c+=`<button class="btn-primary" onclick="saveCurrentAsProfile()">Save as Profile</button>`,_.length>0&&(c+=`<button class="btn-secondary" onclick="createProfileFromThisTurn()" title="Create profile from tools used in this request">From this turn</button>`),c+=`</div>`,c+=`</div>`,n.innerHTML=c,H()}function ye(e){let t=document.querySelector(`.tool-group[data-server="${e}"]`);if(!t)return;let n=t.querySelector(`.tool-group-body`),r=t.querySelector(`.tool-group-chevron`);n&&n.classList.toggle(`collapsed`),r&&r.classList.toggle(`expanded`)}function be(e,t){document.querySelectorAll(`.modal-tools input[type="checkbox"][data-server="${e}"]:not(.tool-group-checkbox)`).forEach(e=>{e.checked=t}),H()}function xe(){document.querySelectorAll(`.tool-group-checkbox`).forEach(e=>{let t=e.dataset.server,n=document.querySelectorAll(`.modal-tools input[type="checkbox"][data-server="${t}"]:not(.tool-group-checkbox)`),r=n.length,i=0;n.forEach(e=>{e.checked&&i++}),e.checked=i===r,e.indeterminate=i>0&&i<r;let a=e.closest(`.tool-group`)?.querySelector(`.tool-group-meta`);if(a){let e=a.textContent.match(/·.*$/);a.textContent=`${i}/${r} tools ${e?e[0]:``}`}})}function Se(e,t){document.querySelectorAll(`.modal-tools input[data-server="${e}"]:not(.tool-group-checkbox)`).forEach(e=>{e.checked=t}),H()}function Ce(e){document.querySelectorAll(`.modal-tools input[type="checkbox"]:not(.tool-group-checkbox)`).forEach(t=>{t.checked=e}),H()}function H(){let e=document.querySelectorAll(`.modal-tools input[type="checkbox"]:not(.tool-group-checkbox)`),n=t.parsedTools;if(!n||!e.length)return;let r=0,i=0,a=0;e.forEach(e=>{let t=n.find(t=>t.name===e.dataset.tool);if(!t)return;let s=o(t);e.checked?(r+=s,a++):i+=s}),xe();let s=document.getElementById(`toolsSavings`);s&&i>0?s.innerHTML=`<div class="savings-banner">Disabling ${n.length-a} tools saves ~${l(i)} tokens per request</div>`:s&&(s.innerHTML=``);let c=document.querySelector(`.modal-tools-header-left`);c&&(c.innerHTML=`<strong>${a}</strong> of <strong>${n.length}</strong> tools enabled`)}async function we(){let t=e.reqs[e.selectedReq],n=t?.toolsUsed||[];if(n.length!==0&&(await M(`Req ${t?.turn??0} tools`,`allowlist`,n)).success){let e=document.querySelector(`.save-profile-bar`);if(e){let t=e.style.background;e.style.background=`rgba(52,211,153,0.1)`,e.style.borderColor=`rgba(52,211,153,0.3)`,setTimeout(()=>{e.style.background=t,e.style.borderColor=``},1500)}}}async function Te(){let e=document.getElementById(`profileNameInput`),t=(e?.value||``).trim();if(!t){e.style.borderColor=`var(--red)`,e.focus(),setTimeout(()=>e.style.borderColor=``,1500);return}let n=document.querySelectorAll(`.modal-tools input[type="checkbox"]:not(.tool-group-checkbox)`),r=[];if(n.forEach(e=>{e.checked&&r.push(e.dataset.tool)}),(await M(t,`allowlist`,r)).success){e.value=``;let t=document.querySelector(`.save-profile-bar`);if(t){let e=t.style.background;t.style.background=`rgba(16,185,129,0.1)`,t.style.borderColor=`rgba(16,185,129,0.3)`,setTimeout(()=>{t.style.background=e,t.style.borderColor=``},1500)}}}function U(){let e=document.getElementById(`modalSearch`).value.trim().toLowerCase(),n=document.getElementById(`modalBody`);if(!e){V();return}if(t.view===`tools`){document.querySelectorAll(`.tool-group`).forEach(t=>{let n=0;t.querySelectorAll(`.tool-card`).forEach(t=>{let r=t.querySelector(`.tool-card-name`)?.textContent?.toLowerCase()||``,i=t.querySelector(`.tool-card-desc`)?.textContent?.toLowerCase()||``,a=r.includes(e)||i.includes(e);t.style.display=a?`flex`:`none`,a&&n++}),t.style.display=n>0?`block`:`none`;let r=t.querySelector(`.tool-group-body`),i=t.querySelector(`.tool-group-chevron`);n>0?(r?.classList.remove(`collapsed`),i?.classList.add(`expanded`)):(r?.classList.add(`collapsed`),i?.classList.remove(`expanded`))});return}let r=t.fullContent;try{r=JSON.stringify(JSON.parse(r),null,2)}catch{}let i=r.split(`
|
|
32
|
+
`),a=`<div class="modal-content">`;for(let t=0;t<i.length;t++){let n=i[t],r=n.toLowerCase().indexOf(e);if(r!==-1){let i=f(n.slice(0,r)),o=f(n.slice(r,r+e.length)),s=f(n.slice(r+e.length));a+=`<span class="line"><span class="line-num">${t+1}</span>${i}<span class="highlight">${o}</span>${s}</span>`}else a+=`<span class="line" style="opacity:0.25"><span class="line-num">${t+1}</span>${f(n)}</span>`}a+=`</div>`,n.innerHTML=a}function W(){navigator.clipboard.writeText(t.fullContent).then(()=>{let e=document.getElementById(`copyBtn`),t=e.textContent;e.textContent=`Copied!`,e.style.color=`var(--green)`,setTimeout(()=>{e.textContent=t,e.style.color=``},1500)})}var G=`jannal_theme`;function K(){return localStorage.getItem(G)||`dark`}function q(e){e===`light`?document.documentElement.setAttribute(`data-theme`,`light`):document.documentElement.removeAttribute(`data-theme`);let t=document.getElementById(`themeIconMoon`),n=document.getElementById(`themeIconSun`);t&&n&&(t.style.display=e===`dark`?`block`:`none`,n.style.display=e===`light`?`block`:`none`)}function J(){let e=K()===`dark`?`light`:`dark`;return localStorage.setItem(G,e),document.documentElement.classList.add(`theme-transitioning`),q(e),setTimeout(()=>document.documentElement.classList.remove(`theme-transitioning`),350),e}function Ee(){q(K())}function Y(){let e=document.activeElement;if(!e)return!1;let t=e.tagName;return t===`INPUT`||t===`TEXTAREA`||t===`SELECT`||e.isContentEditable}function X(){return document.getElementById(`modalOverlay`)?.classList.contains(`open`)}function De(){return document.getElementById(`shortcutsOverlay`)?.classList.contains(`open`)}function Z(t){e.selectedReq=t,T(),D(),A(),b(e)}function Oe(){e.reqs.length!==0&&(e.selectedReq===null?Z(e.reqs.length-1):e.selectedReq>0&&Z(e.selectedReq-1))}function ke(){e.reqs.length!==0&&(e.selectedReq===null?Z(e.reqs.length-1):e.selectedReq<e.reqs.length-1&&Z(e.selectedReq+1))}function Ae(){e.groupView=!e.groupView,D(),b(e)}function je(){let n=e.reqs[e.selectedReq];if(!n)return;let r=(t.segIndex??-1)+1;r<n.segments.length&&R(r)}function Me(){if(!e.reqs[e.selectedReq])return;let n=(t.segIndex??1)-1;n>=0&&R(n)}function Ne(){document.getElementById(`shortcutsOverlay`)?.classList.toggle(`open`)}function Pe(){document.getElementById(`shortcutsOverlay`)?.classList.remove(`open`)}function Fe(){document.addEventListener(`keydown`,t=>{let n=t.key;if((t.metaKey||t.ctrlKey)&&n===`k`){t.preventDefault();let e=document.getElementById(`globalSearch`);e&&(e.focus(),e.select());return}if(n===`Escape`){if(De()){Pe();return}if(document.getElementById(`globalSearchResults`)?.classList.remove(`open`),e.showSettings){e.showSettings=!1,k();return}if(X()){z();return}if(Y()){document.activeElement.blur();return}return}if(!Y()){if(n===`?`||t.shiftKey&&n===`/`){t.preventDefault(),Ne();return}if(X())switch(n){case`f`:t.preventDefault(),B(`formatted`,document.getElementById(`formattedBtn`));return;case`r`:t.preventDefault(),B(`raw`,document.getElementById(`rawBtn`));return;case`t`:t.preventDefault(),B(`tools`,document.getElementById(`toolsViewBtn`));return;case`c`:t.preventDefault(),W();return;case`/`:t.preventDefault(),document.getElementById(`modalSearch`)?.focus();return;case`]`:t.preventDefault(),je();return;case`[`:t.preventDefault(),Me();return}switch(n){case`j`:case`ArrowDown`:t.preventDefault(),Oe();return;case`k`:case`ArrowUp`:t.preventDefault(),ke();return;case`v`:t.preventDefault(),Ae();return;case`d`:t.preventDefault(),J(),S();return}}})}function Q(t){e.selectedReq=t,T(),D(),A(),b(e)}function Ie(){e.reqs=[],e.selectedReq=null,e.groups={},e.expandedGroups={},e.sessions={},e.activeSessionTab=null,S(),b(e)}function Le(t){e.activeSessionTab=t;let n=null;for(let r=e.reqs.length-1;r>=0;r--)if(t===null||e.reqs[r].tabKey===t){n=r;break}e.selectedReq=n,S(),b(e)}function Re(t){delete e.sessions[t],e.activeSessionTab===t&&(e.activeSessionTab=null,e.selectedReq=e.reqs.length>0?e.reqs.length-1:null),S(),b(e)}function ze(){e.groupView=!e.groupView,D(),b(e)}function Be(t){e.expandedGroups[t]=!e.expandedGroups[t],D()}function Ve(){e.reqs.length!==0&&document.getElementById(`exportMenu`)?.classList.toggle(`open`)}function He(){e.reqs.length!==0&&(x(ne(e),`jannal-session-${new Date().toISOString().slice(0,19).replace(/[:-]/g,``)}.json`),document.getElementById(`exportMenu`)?.classList.remove(`open`))}function Ue(){e.reqs.length!==0&&(x(re(e),`jannal-session-${new Date().toISOString().slice(0,19).replace(/[:-]/g,``)}.csv`),document.getElementById(`exportMenu`)?.classList.remove(`open`))}window.openModal=R,window.closeModal=z,window.setModalView=B,window.selectReq=Q,window.clearReqs=Ie,window.toggleGroup=Be,window.onProfileChange=j,window.toggleAllTools=Ce,window.toggleGroupTools=Se,window.toggleGroupAccordion=ye,window.toggleGroupCheckbox=be,window.onToolToggle=H,window.saveCurrentAsProfile=Te,window.createProfileFromThisTurn=we,window.copyClaudeCommand=fe,window.selectSessionTab=Le,window.dismissSessionTab=Re,window.filterModalContent=U,window.copyModalContent=W;var We=null;function Ge(t){let n=document.getElementById(`globalSearchResults`);if(!t||t.length<2){n.classList.remove(`open`),n.innerHTML=``;return}clearTimeout(We),We=setTimeout(async()=>{try{let r=await(await fetch(`/api/search?q=${encodeURIComponent(t)}`)).json();if(r.results.length===0){n.innerHTML=`<div class="search-no-results">No matches found</div>`,n.classList.add(`open`);return}let i=t.toLowerCase(),a=C();n.innerHTML=r.results.map(t=>{let n=e.reqs.findIndex(e=>e.turn===t.turnId),r=t.turnId;if(n>=0){let e=a.findIndex(e=>e.originalIndex===n);r=e>=0?e+1:n+1}let o=n>=0&&e.reqs[n].segments[t.segIndex]?e.reqs[n].segments[t.segIndex].name:`Segment ${t.segIndex}`,s=n>=0&&e.reqs[n].groupId!=null?`Turn ${e.reqs[n].groupId+1} · `:``,c=t.snippet.replace(/</g,`<`).replace(/>/g,`>`).replace(RegExp(`(${i.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)})`,`gi`),`<mark>$1</mark>`);return`<div class="search-result-item" data-turn="${n}" data-seg="${t.segIndex}">
|
|
33
|
+
<div class="search-result-turn">${s}Req ${r} · ${o}</div>
|
|
34
|
+
<div class="search-result-snippet">${c}</div>
|
|
35
|
+
</div>`}).join(``),n.classList.add(`open`)}catch(e){console.error(`Search error:`,e)}},250)}document.getElementById(`settingsToggle`).addEventListener(`click`,()=>{e.showSettings=!e.showSettings,k()}),document.getElementById(`settingsCloseBtn`).addEventListener(`click`,()=>{e.showSettings=!1,k()}),document.getElementById(`settingsOverlay`).addEventListener(`click`,t=>{t.target===document.getElementById(`settingsOverlay`)&&(e.showSettings=!1,k())}),document.getElementById(`settingsBody`).addEventListener(`change`,async t=>{t.target.name===`stripMode`&&(e.strip.mode=t.target.value,await $(),S(),k())}),document.getElementById(`settingsBody`).addEventListener(`input`,t=>{t.target.id===`stripKeepN`?(e.strip.keepN=parseInt(t.target.value)||3,qe()):t.target.id===`stripThreshold`&&(e.strip.threshold=parseInt(t.target.value)||2e3,qe())});var Ke=null;function qe(){clearTimeout(Ke),Ke=setTimeout(()=>$(),500)}async function $(){try{await fetch(`/api/settings`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({strip:e.strip})})}catch(e){console.error(`Failed to save strip settings:`,e)}}document.getElementById(`themeToggle`).addEventListener(`click`,()=>{J(),S()}),document.getElementById(`profileSelect`).addEventListener(`change`,e=>{j(e.target.value)}),document.getElementById(`clearBtn`).addEventListener(`click`,Ie),document.getElementById(`viewToggleBtn`).addEventListener(`click`,ze),document.getElementById(`exportBtn`).addEventListener(`click`,Ve),document.getElementById(`exportMenu`)?.addEventListener(`click`,e=>{let t=e.target.closest(`.export-option`);t&&(t.dataset.format===`json`?He():t.dataset.format===`csv`&&Ue())}),document.addEventListener(`click`,e=>{let t=document.getElementById(`exportMenu`),n=document.querySelector(`.export-dropdown`);t?.classList.contains(`open`)&&n&&!n.contains(e.target)&&t.classList.remove(`open`);let r=document.querySelector(`.router-badge-wrapper`),i=document.getElementById(`routerPopover`);i?.classList.contains(`open`)&&r&&!r.contains(e.target)&&i.classList.remove(`open`)}),document.getElementById(`routerBadge`)?.addEventListener(`click`,e=>{e.stopPropagation(),document.getElementById(`routerPopover`)?.classList.toggle(`open`)}),document.getElementById(`routerPopover`)?.addEventListener(`click`,async t=>{let n=t.target.closest(`.router-popover-opt`);if(!n||!e.premium)return;let r=n.dataset.mode;if(r===e.routerMode){document.getElementById(`routerPopover`)?.classList.remove(`open`);return}try{(await fetch(`/api/router/mode`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({mode:r})})).ok&&(e.routerMode=r,w())}catch(e){console.error(`Failed to set router mode:`,e)}document.getElementById(`routerPopover`)?.classList.remove(`open`)}),document.getElementById(`modalOverlay`).addEventListener(`click`,e=>{e.target===e.currentTarget&&z()}),document.getElementById(`modalCloseBtn`).addEventListener(`click`,z),document.getElementById(`formattedBtn`).addEventListener(`click`,e=>{B(`formatted`,e.target)}),document.getElementById(`rawBtn`).addEventListener(`click`,e=>{B(`raw`,e.target)}),document.getElementById(`toolsViewBtn`).addEventListener(`click`,e=>{B(`tools`,e.target)}),document.getElementById(`modalSearch`).addEventListener(`input`,U),document.getElementById(`copyBtn`).addEventListener(`click`,W),document.getElementById(`globalSearch`).addEventListener(`input`,e=>{Ge(e.target.value.trim())}),document.getElementById(`globalSearchResults`).addEventListener(`click`,e=>{let t=e.target.closest(`.search-result-item`);if(!t)return;let n=parseInt(t.dataset.turn),r=parseInt(t.dataset.seg);n>=0&&(Q(n),R(r)),document.getElementById(`globalSearchResults`).classList.remove(`open`),document.getElementById(`globalSearch`).value=``}),document.addEventListener(`click`,e=>{let t=document.querySelector(`.global-search-wrapper`);t&&!t.contains(e.target)&&document.getElementById(`globalSearchResults`).classList.remove(`open`)}),Ee(),Fe(),te(e),F(),L(),S();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{box-sizing:border-box;margin:0;padding:0}:root{--bg:#09090b;--bg2:#111113;--bg3:#18181b;--bg4:#27272a;--border:#ffffff0f;--text:#fafafa;--text2:#a1a1aa;--text3:#52525b;--blue:#60a5fa;--purple:#a78bfa;--green:#34d399;--orange:#fb923c;--cyan:#22d3ee;--yellow:#fbbf24;--red:#f87171;--amber:#f59e0b;--seg-system:#60a5fa;--seg-tools:#fb923c;--seg-message:#22d3ee;--seg-assistant:#34d399;--seg-tool-result:#fbbf24;--seg-tool-use:#a78bfa;--overlay-1:#ffffff03;--overlay-2:#ffffff05;--overlay-3:#ffffff08;--overlay-4:#ffffff0a;--overlay-5:#ffffff0d;--overlay-6:#ffffff0f;--overlay-8:#ffffff14;--overlay-10:#ffffff1a;--overlay-12:#ffffff1f;--overlay-15:#ffffff26;--scrollbar-thumb:#ffffff0f;--scrollbar-thumb-hover:#ffffff1f;--modal-backdrop:#000000b3;--noise-opacity:.015;--text-hover:white;--bar-seg-text:white;--bar-seg-shadow:0 1px 2px #00000080;--line-num-color:#ffffff1f;--shadow-inset:inset 0 1px 3px #0000004d;--font-ui:"Instrument Sans", -apple-system, system-ui, sans-serif;--font-mono:"JetBrains Mono", "SF Mono", "Fira Code", monospace;--radius-sm:6px;--radius-md:10px;--radius-lg:14px;--radius-xl:20px;--shadow-sm:0 1px 2px #0000004d;--shadow-md:0 4px 12px #0006;--shadow-lg:0 12px 40px #00000080;--shadow-xl:0 24px 80px #0009;--ease-out:cubic-bezier(.16, 1, .3, 1);--ease-spring:cubic-bezier(.34, 1.56, .64, 1);--duration-fast:.15s;--duration-normal:.25s;--duration-slow:.4s}html[data-theme=light]{--bg:#fafafa;--bg2:#f4f4f5;--bg3:#e4e4e7;--bg4:#d4d4d8;--border:#00000014;--text:#18181b;--text2:#52525b;--text3:#a1a1aa;--blue:#2563eb;--purple:#7c3aed;--green:#059669;--orange:#ea580c;--cyan:#0891b2;--yellow:#d97706;--red:#dc2626;--amber:#b45309;--seg-system:#2563eb;--seg-tools:#ea580c;--seg-message:#0891b2;--seg-assistant:#059669;--seg-tool-result:#d97706;--seg-tool-use:#7c3aed;--overlay-1:#00000003;--overlay-2:#00000005;--overlay-3:#00000008;--overlay-4:#0000000a;--overlay-5:#0000000d;--overlay-6:#0000000f;--overlay-8:#0000000f;--overlay-10:#00000014;--overlay-12:#0000001a;--overlay-15:#0000001f;--scrollbar-thumb:#0000001a;--scrollbar-thumb-hover:#0003;--modal-backdrop:#0000004d;--noise-opacity:0;--text-hover:var(--text);--bar-seg-text:white;--bar-seg-shadow:0 1px 2px #0000004d;--line-num-color:#0003;--shadow-inset:inset 0 1px 3px #00000014;--shadow-sm:0 1px 2px #0000000f;--shadow-md:0 4px 12px #00000014;--shadow-lg:0 12px 40px #0000001a;--shadow-xl:0 24px 80px #0000001f}html.theme-transitioning,html.theme-transitioning *,html.theme-transitioning :before,html.theme-transitioning :after{transition:background-color .3s,color .3s,border-color .3s,box-shadow .3s!important}body{background:var(--bg);color:var(--text);font-family:var(--font-ui);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;flex-direction:column;height:100vh;display:flex;overflow:hidden}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);transition:background var(--duration-fast);border-radius:99px}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}.noise-overlay{z-index:9999;pointer-events:none;opacity:var(--noise-opacity);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");position:fixed;inset:0}.header{z-index:100;background:var(--bg2);justify-content:space-between;align-items:center;padding:10px 20px;display:flex;position:relative}.header:after{content:"";background:var(--border);height:1px;position:absolute;bottom:0;left:0;right:0}.header-left{align-items:center;gap:10px;display:flex}.header-brand{flex-direction:column;gap:1px;display:flex}.logo{object-fit:cover;border-radius:7px;width:28px;height:28px}.header h1{letter-spacing:-.02em;font-size:15px;font-weight:800;line-height:1}.header-brand .status{font-size:10px}.header-right{align-items:center;gap:8px;display:flex}.hdr-sep{background:var(--border);flex-shrink:0;width:1px;height:18px}.hdr-metrics{align-items:center;gap:6px;display:flex}.theme-toggle{border-radius:var(--radius-sm);border:1px solid var(--overlay-8);background:var(--overlay-3);width:26px;height:26px;color:var(--text3);cursor:pointer;transition:all var(--duration-fast);flex-shrink:0;justify-content:center;align-items:center;display:flex}.theme-toggle:hover{background:var(--overlay-8);color:var(--text);border-color:var(--overlay-12)}.theme-toggle:active{transform:scale(.95)}.status{align-items:center;gap:5px;font-size:11px;font-weight:600;display:flex}.status-dot{border-radius:50%;flex-shrink:0;width:6px;height:6px}.status-dot.connected{background:var(--green);animation:2s ease-in-out infinite statusPulse;box-shadow:0 0 6px #34d39980}.status-dot.disconnected{background:var(--red)}@keyframes statusPulse{0%,to{box-shadow:0 0 6px #34d39980}50%{box-shadow:0 0 10px #34d399b3}}.req-badge{border-radius:var(--radius-sm);background:var(--overlay-3);height:26px;color:var(--text3);font-size:11px;font-weight:600;font-family:var(--font-mono);font-variant-numeric:tabular-nums;align-items:center;padding:4px 8px;display:flex}.daily-saved{border-radius:var(--radius-sm);height:26px;color:var(--text3);font-size:11px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums;background:#22c55e0f;border:1px solid #22c55e14;align-items:center;padding:4px 8px;display:flex}.daily-saved.has-savings{color:var(--green);background:#22c55e1a;border-color:#22c55e26}.daily-cost{border-radius:var(--radius-sm);height:26px;color:var(--amber);font-size:11px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums;background:#f59e0b14;border:1px solid #f59e0b1f;align-items:center;padding:4px 8px;display:flex}.profile-select{border-radius:var(--radius-sm);border:1px solid var(--overlay-8);background:var(--overlay-3);height:26px;color:var(--text);font-size:11px;font-weight:600;font-family:var(--font-ui);cursor:pointer;max-width:140px;transition:all var(--duration-fast);outline:none;padding:0 8px}.profile-select:hover{background:var(--overlay-6);border-color:var(--overlay-12)}.profile-select.filtering{color:var(--orange);background:#fb923c0f;border-color:#fb923c4d}.filter-badge{border-radius:var(--radius-sm);height:26px;color:var(--orange);letter-spacing:.06em;background:#fb923c1f;border:1px solid #fb923c33;align-items:center;padding:0 6px;font-size:9px;font-weight:800;line-height:1;display:flex}.strip-badge{border-radius:var(--radius-sm);height:26px;color:var(--purple);letter-spacing:.06em;background:#a78bfa1f;border:1px solid #a78bfa33;align-items:center;padding:0 6px;font-size:9px;font-weight:800;line-height:1;display:flex}.router-badge-wrapper{position:relative}.router-badge{border-radius:var(--radius-sm);letter-spacing:.03em;cursor:pointer;-webkit-user-select:none;user-select:none;height:26px;transition:all var(--duration-fast);align-items:center;padding:0 8px;font-size:9px;font-weight:700;display:flex}.router-badge:hover{filter:brightness(1.2)}.router-badge--off{background:var(--overlay-3);color:var(--text3);border:1px solid var(--overlay-8)}.router-badge--shadow{color:var(--purple);background:#a78bfa1a;border:1px solid #a78bfa33}.router-badge--auto{color:var(--cyan);background:#22d3ee1a;border:1px solid #22d3ee33}.router-popover{background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius-md);z-index:300;min-width:210px;padding:6px 0;display:none;position:absolute;top:calc(100% + 8px);right:0;box-shadow:0 8px 30px #00000059}.router-popover.open{display:block}.router-popover-title{color:var(--text3);text-transform:uppercase;letter-spacing:.1em;border-bottom:1px solid var(--border);margin-bottom:2px;padding:6px 12px 8px;font-size:8px;font-weight:700}.router-popover-opt{width:100%;color:var(--text);font-size:11px;font-weight:500;font-family:var(--font-ui);cursor:pointer;text-align:left;transition:background var(--duration-fast);background:0 0;border:none;align-items:center;gap:8px;padding:7px 12px;display:flex}.router-popover-opt:hover{background:var(--overlay-6)}.router-popover-opt.active{background:var(--overlay-4);font-weight:700}.router-opt-dot{border-radius:50%;flex-shrink:0;width:7px;height:7px}.router-opt-dot--off{background:var(--text3)}.router-opt-dot--shadow{background:var(--purple)}.router-opt-dot--auto{background:var(--cyan)}.router-opt-desc{color:var(--text3);margin-left:auto;font-size:9px;font-weight:400}.global-search-wrapper{position:relative}.global-search{border-radius:var(--radius-sm);border:1px solid var(--overlay-8);background:var(--overlay-3);height:26px;color:var(--text);font-size:10px;font-family:var(--font-ui);width:170px;transition:all var(--duration-fast);outline:none;padding:0 10px}.global-search::placeholder{color:var(--text3)}.global-search:focus{border-color:#60a5fa4d;width:220px;box-shadow:0 0 0 2px #60a5fa14}.global-search-results{background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius-md);z-index:200;max-height:320px;box-shadow:var(--shadow-lg);min-width:360px;margin-top:4px;display:none;position:absolute;top:100%;left:0;right:0;overflow-y:auto}.global-search-results.open{display:block}.search-result-item{border-bottom:1px solid var(--overlay-4);cursor:pointer;transition:background var(--duration-fast);padding:8px 12px}.search-result-item:hover{background:var(--overlay-6)}.search-result-item:last-child{border-bottom:none}.search-result-turn{color:var(--blue);margin-bottom:2px;font-size:10px;font-weight:700}.search-result-snippet{color:var(--text2);font-size:11px;font-family:var(--font-mono);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.search-result-snippet mark{color:var(--text);background:#fbbf244d;border-radius:2px}.search-no-results{color:var(--text3);text-align:center;padding:12px;font-size:11px}.bar-container{padding:14px 24px 10px}.bar-outer{background:var(--bg3);border:1px solid var(--border);height:52px;box-shadow:var(--shadow-inset);transition:box-shadow var(--duration-slow), border-color var(--duration-slow);border-radius:12px;position:relative;overflow:hidden}.bar-outer:before{content:"";z-index:0;pointer-events:none;background:radial-gradient(ellipse at 50% 100%, var(--overlay-2) 0%, transparent 70%);position:absolute;inset:0}.bar-outer.pressure-high{box-shadow:var(--shadow-inset), 0 0 24px #fb923c26;border-color:#fb923c40}.bar-outer.pressure-critical{box-shadow:var(--shadow-inset), 0 0 30px #f8717133;border-color:#f871714d}.bar-inner{z-index:1;height:100%;transition:all var(--duration-slow) var(--ease-out);display:flex;position:relative}.bar-segment{cursor:pointer;justify-content:center;align-items:center;height:100%;transition:all .35s;display:flex;overflow:hidden}.bar-segment:hover{filter:brightness(1.2)}.bar-segment span{color:var(--bar-seg-text);text-shadow:var(--bar-seg-shadow);white-space:nowrap;padding:0 3px;font-size:10px;font-weight:600}.bar-empty{flex-grow:1;justify-content:center;align-items:center;display:flex}.bar-empty span{color:var(--text3);font-size:11px}.bar-break{pointer-events:none;background:repeating-linear-gradient(-60deg, transparent, transparent 3px, var(--overlay-10) 3px, var(--overlay-10) 5px);width:12px;min-width:12px;height:100%;transition:opacity .35s;position:relative}.bar-break:before,.bar-break:after{content:"";background:var(--overlay-10);width:1px;position:absolute;top:0;bottom:0}.bar-break:before{left:0}.bar-break:after{right:0}.bar-marker{border-left:1px dashed var(--overlay-10);pointer-events:none;z-index:2;position:absolute;top:0;bottom:0}.bar-marker span{color:var(--text3);font-size:7px;position:absolute;top:1px;left:3px}.bar-stats{justify-content:space-between;align-items:center;margin-top:6px;padding:0 2px;display:flex}.bar-legend{flex-wrap:wrap;gap:16px;display:flex}.legend-item{color:var(--text3);cursor:pointer;align-items:center;gap:4px;font-size:11px;display:flex}.legend-dot{border-radius:2px;width:7px;height:7px}.bar-total{font-size:13px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums}.bar-pct{background:var(--overlay-4);font-size:10px;font-weight:600;font-family:var(--font-mono);border-radius:99px;padding:1px 6px}.token-chart-container{border-radius:var(--radius-sm);background:var(--overlay-2);border:1px solid var(--border);margin-top:8px;padding:8px 12px}.token-chart-label{color:var(--text3);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px;font-size:9px;font-weight:700}.token-chart{align-items:center;gap:8px;display:flex}.token-chart-svg{width:100%;max-width:240px;height:36px}.token-chart-hint{color:var(--text3);white-space:nowrap;font-size:10px;font-family:var(--font-mono)}.main{flex:1;display:flex;overflow:hidden}.panel{flex-direction:column;display:flex;overflow:hidden}.panel-header{border-bottom:1px solid var(--border);color:var(--text2);text-transform:uppercase;letter-spacing:.06em;flex-shrink:0;justify-content:space-between;align-items:center;padding:10px 16px;font-size:12px;font-weight:800;display:flex}.panel-actions{align-items:center;gap:6px;display:flex}.panel-btn{background:var(--overlay-4);border:1px solid var(--overlay-8);color:var(--text2);cursor:pointer;font-size:11px;font-family:var(--font-ui);border-radius:var(--radius-sm);transition:all var(--duration-fast);padding:4px 10px;font-weight:600}.panel-btn:hover:not(:disabled){color:var(--text);background:var(--overlay-6)}.panel-btn:active:not(:disabled){transform:scale(.97)}.panel-btn:disabled{opacity:.5;cursor:not-allowed}.export-dropdown{position:relative}.export-menu{background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius-sm);z-index:100;min-width:90px;box-shadow:var(--shadow-md);margin-top:4px;padding:4px;display:none;position:absolute;top:100%;right:0}.export-menu.open{flex-direction:column;gap:2px;display:flex}.export-option{color:var(--text);cursor:pointer;font-size:11px;font-family:var(--font-ui);text-align:left;transition:background var(--duration-fast);background:0 0;border:none;border-radius:4px;padding:6px 10px}.export-option:hover{background:var(--overlay-8)}.panel-body{flex:1;overflow-y:auto}.reqs-panel{border-right:1px solid var(--border);background:var(--bg2);width:320px}.req-card{border-bottom:1px solid var(--border);cursor:pointer;transition:background var(--duration-fast), transform var(--duration-fast);padding:6px 12px}.req-card:hover{background:var(--overlay-2)}.req-card:active{transform:scale(.99)}.req-card.selected{border-left:3px solid var(--amber);background:#f59e0b0d;padding-left:9px}.req-card-head{justify-content:space-between;align-items:center;display:flex}.req-label{color:var(--text);font-size:12px;font-weight:500}.req-tokens{font-size:11px;font-weight:600;font-family:var(--font-mono);font-variant-numeric:tabular-nums}.req-mini-bar{background:var(--bg);border-radius:99px;height:3px;margin-top:2px;overflow:hidden}.req-mini-fill{height:100%;transition:width .3s var(--ease-out);box-shadow:0 0 4px var(--overlay-5);border-radius:99px}.req-meta{color:var(--text3);font-size:10px;font-family:var(--font-mono);gap:6px;margin-top:2px;display:flex}.req-io{color:var(--green);font-weight:600}.req-cost-inline{color:var(--amber);font-weight:600}.view-toggle{transition:all var(--duration-fast);font-weight:600!important}.view-toggle.active{background:#f59e0b1a;color:var(--amber)!important}.group-card{margin-bottom:2px}.group-header{cursor:pointer;transition:background var(--duration-fast);border-bottom:1px solid var(--border);background:linear-gradient(180deg, var(--overlay-2) 0%, transparent 100%);align-items:center;gap:8px;padding:10px 14px;display:flex}.group-header:hover{background:var(--overlay-3)}.group-chevron{color:var(--text3);transition:transform var(--duration-normal) var(--ease-spring);text-align:center;flex-shrink:0;width:14px;font-size:10px}.group-chevron.expanded{transform:rotate(90deg)}.group-title{color:var(--text);flex:1;min-width:0;font-size:13px;font-weight:600}.group-summary{font-size:11px;font-family:var(--font-mono);font-variant-numeric:tabular-nums;align-items:center;gap:10px;display:flex}.group-req-count{background:var(--overlay-6);color:var(--text2);border-radius:4px;padding:2px 8px;font-weight:600}.group-cost{color:var(--amber);font-weight:600}.group-tokens{color:var(--text3)}.group-children{overflow:hidden}.group-children.collapsed{display:none}.group-session-label{color:var(--text3);text-transform:uppercase;letter-spacing:.05em;align-items:center;gap:6px;margin-left:14px;padding:4px 14px 2px 26px;font-size:9px;font-weight:700;display:flex}.session-pill{letter-spacing:.03em;text-transform:capitalize;border-radius:4px;padding:2px 8px;font-size:9px;font-weight:700;display:inline-block}.session-pill.main{color:var(--purple);background:#a78bfa26}.session-pill.subagent{color:var(--cyan);background:#22d3ee26}.group-children .req-card{border-left:2px solid var(--overlay-6);margin-left:14px;padding-left:26px}.group-children .req-card.selected{border-left:2px solid var(--amber);padding-left:26px}.group-time{color:var(--text3);font-size:9px;font-family:var(--font-mono);padding:2px 14px 8px}.detail-panel{background:var(--bg);flex:1}.segment-row{border-bottom:1px solid var(--overlay-3);transition:background var(--duration-fast);cursor:pointer;align-items:center;gap:10px;padding:12px 18px;display:flex}.segment-row:hover{background:var(--overlay-3)}.seg-color{border-radius:3px;flex-shrink:0;width:5px;height:36px}.seg-info{flex:1;min-width:0}.seg-name{font-size:13px;font-weight:600}.seg-sub{color:var(--text3);white-space:nowrap;text-overflow:ellipsis;max-width:350px;margin-top:1px;font-size:11px;overflow:hidden}.seg-tokens{font-size:13px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums;text-align:right;min-width:60px}.seg-pct{color:var(--text3);text-align:right;min-width:40px;font-size:11px;font-family:var(--font-mono)}.seg-bar{background:var(--bg3);border-radius:99px;flex-shrink:0;width:80px;height:4px;overflow:hidden}.seg-bar-fill{border-radius:99px;height:100%}.seg-expand-hint{color:var(--text3);background:var(--overlay-3);white-space:nowrap;border-radius:4px;padding:2px 6px;font-size:9px}.empty{height:100%;color:var(--text3);flex-direction:column;justify-content:center;align-items:center;display:flex}.empty-icon{opacity:.3;margin-bottom:12px;font-size:40px}.empty h2{color:var(--text2);margin-bottom:4px;font-size:15px;font-weight:700}.empty p{text-align:center;max-width:300px;font-size:13px;line-height:1.5}.copy-command-btn{border:1px solid var(--overlay-12);background:var(--overlay-4);color:var(--cyan);cursor:pointer;font-size:10px;font-family:var(--font-ui);border-radius:4px;margin-left:6px;padding:2px 8px}.copy-command-btn:hover{background:#22d3ee1f;border-color:#22d3ee40}.stats-grid{grid-template-columns:repeat(2,1fr);gap:8px;padding:12px 16px;display:grid}.stats-grid:empty{display:none}.usage-box{border-radius:var(--radius-md);background:var(--bg2);border:1px solid var(--border);padding:10px 12px}.usage-row{justify-content:space-between;align-items:center;padding:2px 0;font-size:11px;display:flex}.usage-label{color:var(--text2)}.usage-value{font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums}.usage-value.actual{color:var(--green)}.usage-value.estimated{color:var(--text2)}.filter-box{border-radius:var(--radius-md);background:#fb923c0f;border:1px solid #fb923c26;padding:10px 12px}.filter-box-title{color:var(--orange);margin-bottom:6px;font-size:11px;font-weight:700}.warning-box{border-radius:var(--radius-md);background:#fbbf240f;border:1px solid #fbbf2426;padding:10px 12px}.warning-box-title{color:var(--yellow);margin-bottom:6px;font-size:11px;font-weight:700}.router-box{border-radius:var(--radius-md);background:#a78bfa0d;border:1px solid #a78bfa26;padding:10px 12px}.router-box-title{color:var(--purple);margin-bottom:6px;font-size:11px;font-weight:700}.router-mode-shadow{color:var(--purple)}.router-mode-auto{color:var(--cyan)}.router-mode-off{color:var(--text3)}.router-shadow-note{color:var(--text3);border-top:1px solid #a78bfa1f;margin-top:6px;padding-top:6px;font-size:9px;font-style:italic}.premium-locked{opacity:.5;cursor:not-allowed}.premium-locked-msg{color:var(--text3);text-align:center;padding:8px 0;font-size:10px;line-height:1.5}.router-box.premium-locked{border-style:dashed}.router-popover-opt.premium-locked{pointer-events:none}.daily-saved.premium-locked{opacity:.5;font-size:10px}.modal-overlay{z-index:1000;background:var(--modal-backdrop);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);justify-content:center;align-items:center;padding:32px;display:none;position:fixed;inset:0}.modal-overlay.open{display:flex}.modal{background:var(--bg2);border:1px solid var(--overlay-8);border-radius:var(--radius-xl);width:100%;max-width:900px;height:85vh;box-shadow:var(--shadow-xl);animation:modal-in var(--duration-normal) var(--ease-spring);flex-direction:column;display:flex;overflow:hidden}.modal-header{border-bottom:1px solid var(--border);background:var(--overlay-2);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);flex-shrink:0;align-items:center;gap:12px;padding:16px 20px;display:flex}.modal-color-bar{border-radius:2px;width:4px;height:28px}.modal-title{flex:1}.modal-title h2{font-size:16px;font-weight:700}.modal-title .modal-meta{color:var(--text3);margin-top:2px;font-size:12px}.modal-stats{align-items:center;gap:12px;display:flex}.modal-stat{text-align:center}.modal-stat-value{font-size:15px;font-weight:800;font-family:var(--font-mono);font-variant-numeric:tabular-nums}.modal-stat-label{color:var(--text3);text-transform:uppercase;letter-spacing:.05em;font-size:10px}.modal-close{border-radius:var(--radius-sm);cursor:pointer;background:var(--overlay-6);width:32px;height:32px;color:var(--text2);transition:all var(--duration-fast);border:none;justify-content:center;align-items:center;font-size:18px;display:flex}.modal-close:hover{background:var(--overlay-12);color:var(--text-hover)}.modal-close:active{transform:scale(.97)}.modal-toolbar{border-bottom:1px solid var(--border);flex-shrink:0;align-items:center;gap:6px;padding:10px 20px;display:flex}.modal-toolbar button{cursor:pointer;font-size:11px;font-weight:600;font-family:var(--font-ui);color:var(--text2);transition:all var(--duration-fast);background:0 0;border:none;border-radius:99px;padding:5px 14px}.modal-toolbar button:hover{background:var(--overlay-6);color:var(--text-hover)}.modal-toolbar button:active{transform:scale(.97)}.modal-toolbar button.active{color:var(--blue);background:#60a5fa1f;box-shadow:inset 0 0 0 1px #60a5fa33}.modal-toolbar .spacer{flex:1}.modal-toolbar .search-box{border:1px solid var(--overlay-8);background:var(--overlay-3);color:var(--text);width:180px;font-size:11px;font-family:var(--font-ui);transition:all var(--duration-fast);border-radius:99px;outline:none;padding:5px 12px}.modal-toolbar .search-box:focus{border-color:#60a5fa66;box-shadow:0 0 0 3px #60a5fa1a}.modal-body{flex:1 1 0;height:0;min-height:0;padding:0;overflow:auto}.modal-content{font-family:var(--font-mono);color:var(--text);white-space:pre-wrap;word-break:break-word;tab-size:2;padding:12px 20px;font-size:12px;line-height:1.3}.modal-content .line{border-left:1px solid var(--overlay-4);padding:1px 0 1px 48px;display:block;position:relative}.modal-content .line:nth-child(2n){background:var(--overlay-1)}.modal-content .line:hover{background:var(--overlay-3)}.modal-content .line-num{text-align:right;width:40px;color:var(--line-num-color);-webkit-user-select:none;user-select:none;padding-right:8px;font-size:10px;position:absolute;left:0}.modal-content .highlight{background:#fbbf2433;border-radius:2px}.modal-tools{padding:16px 20px}.modal-tools-header{border-radius:var(--radius-md);background:var(--overlay-2);justify-content:space-between;align-items:center;margin-bottom:12px;padding:8px 12px;display:flex}.modal-tools-header-left{color:var(--text2);font-size:11px}.modal-tools-header-left strong{color:var(--text)}.tool-section-header{color:var(--text3);text-transform:uppercase;letter-spacing:.06em;margin-top:4px;padding:12px 4px 6px;font-size:10px;font-weight:800}.tool-group{border:1px solid var(--overlay-6);border-radius:var(--radius-md);margin-bottom:10px;overflow:hidden}.tool-group-header{background:var(--overlay-3);cursor:pointer;transition:background var(--duration-fast);align-items:center;gap:8px;padding:8px 12px;display:flex}.tool-group-header:hover{background:var(--overlay-5)}.tool-group-checkbox,.tool-card input[type=checkbox]{appearance:none;border:1.5px solid var(--text3);cursor:pointer;width:16px;height:16px;transition:all var(--duration-fast);background:0 0;border-radius:4px;flex-shrink:0;position:relative}.tool-group-checkbox:checked,.tool-card input[type=checkbox]:checked{background:var(--blue);border-color:var(--blue)}.tool-group-checkbox:checked:after,.tool-card input[type=checkbox]:checked:after{content:"";border:2px solid #fff;border-width:0 2px 2px 0;width:5px;height:9px;position:absolute;top:1px;left:4px;transform:rotate(45deg)}.tool-group-chevron{color:var(--text3);transition:transform var(--duration-normal) var(--ease-spring);-webkit-user-select:none;user-select:none;flex-shrink:0;font-size:9px}.tool-group-chevron.expanded{transform:rotate(90deg)}.tool-group-title{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.tool-group-name{color:var(--purple);font-size:13px;font-weight:700}.tool-group-meta{color:var(--text3);font-size:11px}.tool-group-actions{flex-shrink:0;gap:4px;display:flex}.tool-group-body{padding:8px}.tool-group-body.collapsed{display:none}.tool-card{border-radius:var(--radius-sm);background:var(--overlay-2);border:1px solid var(--overlay-4);transition:all var(--duration-fast);align-items:center;gap:10px;margin-bottom:4px;padding:8px 12px;display:flex}.tool-card:hover{background:var(--overlay-5);box-shadow:var(--shadow-sm)}.tool-card-info{flex:1;min-width:0}.tool-card-name{color:var(--orange);font-size:13px;font-weight:700}.tool-card-desc{color:var(--text3);white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:11px;overflow:hidden}.tool-card-tokens{color:var(--text3);font-size:11px;font-weight:600;font-family:var(--font-mono);font-variant-numeric:tabular-nums;white-space:nowrap}.never-used-tag{color:var(--text3);background:var(--overlay-4);vertical-align:middle;border-radius:3px;margin-left:6px;padding:1px 5px;font-size:8px;font-weight:600}.save-profile-bar{border-radius:var(--radius-md);background:var(--overlay-2);border:1px dashed var(--overlay-10);align-items:center;gap:8px;margin-top:12px;padding:10px 12px;display:flex}.save-profile-bar input{border-radius:var(--radius-sm);border:1px solid var(--overlay-10);background:var(--overlay-4);color:var(--text);font-size:11px;font-family:var(--font-ui);transition:border-color var(--duration-fast);outline:none;flex:1;padding:6px 10px}.save-profile-bar input:focus{border-color:#60a5fa66;box-shadow:0 0 0 3px #60a5fa1a}.save-profile-bar button{border-radius:var(--radius-sm);cursor:pointer;font-size:11px;font-weight:700;font-family:var(--font-ui);transition:all var(--duration-fast);border:none;padding:6px 14px}.save-profile-bar button:active{transform:scale(.97)}.btn-primary{color:var(--blue);background:#60a5fa33}.btn-primary:hover{background:#60a5fa4d}.btn-secondary{background:var(--overlay-6);color:var(--text2)}.btn-secondary:hover{background:var(--overlay-10);color:var(--text-hover)}.savings-banner{border-radius:var(--radius-sm);color:var(--green);background:#34d39914;border:1px solid #34d39926;margin-top:8px;padding:8px 12px;font-size:11px;font-weight:600}.settings-overlay{z-index:900;background:var(--modal-backdrop);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);justify-content:center;align-items:center;padding:32px;display:none;position:fixed;inset:0}.settings-overlay.open{display:flex}.settings-panel{background:var(--bg2);border:1px solid var(--overlay-8);border-radius:var(--radius-xl);width:100%;max-width:600px;max-height:85vh;box-shadow:var(--shadow-xl);animation:modal-in var(--duration-normal) var(--ease-spring);flex-direction:column;display:flex;overflow:hidden}.settings-header{border-bottom:1px solid var(--border);background:var(--overlay-2);justify-content:space-between;align-items:center;padding:16px 24px;display:flex}.settings-header h2{font-size:16px;font-weight:800}.settings-body{flex:1;overflow-y:auto}.settings-view{padding:20px 24px}.settings-section{border-radius:var(--radius-md);background:var(--overlay-2);border:1px solid var(--border);margin-bottom:24px;padding:16px}.settings-section-title{color:var(--text2);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px;font-size:11px;font-weight:800}.settings-section-desc{color:var(--text3);margin-bottom:12px;font-size:11px;line-height:1.5}.settings-radio{flex-direction:column;gap:8px;display:flex}.settings-radio label{color:var(--text);cursor:pointer;border-radius:var(--radius-sm);transition:background var(--duration-fast);align-items:center;gap:8px;padding:8px 10px;font-size:12px;display:flex}.settings-radio label:hover{background:var(--overlay-3)}.settings-radio input[type=radio]{appearance:none;border:2px solid var(--text3);cursor:pointer;width:16px;height:16px;transition:border-color var(--duration-fast);border-radius:50%;flex-shrink:0;position:relative}.settings-radio input[type=radio]:checked{border-color:var(--purple)}.settings-radio input[type=radio]:checked:after{content:"";background:var(--purple);border-radius:50%;width:6px;height:6px;position:absolute;top:3px;left:3px}.settings-inline-input{border:1px solid var(--overlay-10);background:var(--overlay-4);width:60px;color:var(--text);font-size:11px;font-family:var(--font-mono);text-align:center;border-radius:4px;outline:none;margin-left:6px;padding:3px 6px}.settings-inline-input:focus{border-color:#a78bfa66;box-shadow:0 0 0 2px #a78bfa1a}.settings-savings{border-radius:var(--radius-sm);color:var(--purple);background:#a78bfa0f;border:1px solid #a78bfa26;margin-top:12px;padding:10px 12px;font-size:11px;font-weight:600}.session-tabs{border-bottom:1px solid var(--border);background:var(--bg2);scrollbar-width:none;animation:fadeInUp var(--duration-normal) var(--ease-out) 30ms both;gap:0;padding:0 24px;display:none;overflow-x:auto}.session-tabs::-webkit-scrollbar{display:none}.session-tabs.visible{display:flex}.session-tab{color:var(--text3);cursor:pointer;white-space:nowrap;transition:color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);border-bottom:2px solid #0000;flex-shrink:0;padding:8px 18px 8px 14px;font-size:12px;font-weight:600;position:relative}.session-tab:hover{color:var(--text2)}.session-tab.active{color:var(--cyan);border-bottom-color:var(--cyan);font-weight:700}.session-tab-close{color:var(--text3);cursor:pointer;font-size:12px;line-height:1;display:none;position:absolute;top:4px;right:2px}.session-tab:hover .session-tab-close{display:block}.session-tab-close:hover{color:var(--red)}.session-tab-label{display:block}.session-tab-path{color:var(--text3);font-size:9px;font-weight:400;font-family:var(--font-mono);text-overflow:ellipsis;white-space:nowrap;text-align:left;direction:rtl;max-width:160px;display:block;overflow:hidden}@keyframes pulse{0%,to{opacity:1}50%{opacity:.4}}.waiting{animation:2s ease-in-out infinite pulse}@keyframes modal-in{0%{opacity:0;transform:scale(.96)translateY(8px)}to{opacity:1;transform:scale(1)translateY(0)}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.header{animation:fadeInUp var(--duration-normal) var(--ease-out) both}.bar-container{animation:fadeInUp var(--duration-normal) var(--ease-out) 80ms both}.reqs-panel{animation:fadeInUp var(--duration-normal) var(--ease-out) .14s both}.detail-panel{animation:fadeInUp var(--duration-normal) var(--ease-out) .2s both}button:active{transform:scale(.97)}.shortcuts-overlay{z-index:2000;background:var(--modal-backdrop);opacity:0;pointer-events:none;transition:opacity var(--duration-normal,.2s) ease;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.shortcuts-overlay.open{opacity:1;pointer-events:auto}.shortcuts-panel{background:var(--bg2);border:1px solid var(--border);border-radius:12px;width:420px;max-height:80vh;overflow-y:auto;box-shadow:0 20px 60px #00000080}.shortcuts-header{border-bottom:1px solid var(--border);justify-content:space-between;align-items:center;padding:16px 20px 12px;display:flex}.shortcuts-header h2{color:var(--text);font-size:14px;font-weight:700}.shortcuts-close{color:var(--text3);cursor:pointer;background:0 0;border:none;padding:0;font-size:20px;line-height:1}.shortcuts-close:hover{color:var(--text)}.shortcuts-body{padding:16px 20px}.shortcuts-section{margin-bottom:16px}.shortcuts-section:last-child{margin-bottom:0}.shortcuts-section h3{text-transform:uppercase;letter-spacing:.05em;color:var(--text3);margin-bottom:8px;font-size:10px;font-weight:700}.shortcut-row{color:var(--text2);align-items:center;gap:6px;padding:4px 0;font-size:12px;display:flex}.shortcut-row span{margin-left:auto}.shortcut-row kbd{background:var(--bg4);border:1px solid var(--border);color:var(--text);text-align:center;border-radius:4px;min-width:22px;padding:2px 6px;font-family:JetBrains Mono,monospace;font-size:11px;line-height:1.4;display:inline-block}
|
package/public/index.html
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
11
11
|
<script>(function(){var t=localStorage.getItem('jannal_theme');if(t==='light')document.documentElement.setAttribute('data-theme','light')})()</script>
|
|
12
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
12
|
+
<script type="module" crossorigin src="/assets/index-BrchVx8x.js"></script>
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-RRIebi7v.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div class="noise-overlay" aria-hidden="true"></div>
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
<div class="hdr-sep"></div>
|
|
50
50
|
<div class="daily-saved" id="dailySaved" title="Estimated daily savings from router intelligence">Saved: $0.00</div>
|
|
51
51
|
<div class="daily-cost" id="dailyCost" title="Total cost today">Cost: $0.00</div>
|
|
52
|
+
<span class="strip-badge" id="stripBadge" style="display:none">STRIPPING</span>
|
|
53
|
+
<button class="theme-toggle" id="settingsToggle" title="Settings" aria-label="Settings">
|
|
54
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
55
|
+
</button>
|
|
52
56
|
<button class="theme-toggle" id="themeToggle" title="Toggle dark/light mode" aria-label="Toggle theme">
|
|
53
57
|
<svg id="themeIconMoon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
54
58
|
<svg id="themeIconSun" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
@@ -143,5 +147,51 @@
|
|
|
143
147
|
</div>
|
|
144
148
|
</div>
|
|
145
149
|
|
|
150
|
+
<!-- Settings Overlay -->
|
|
151
|
+
<div class="settings-overlay" id="settingsOverlay">
|
|
152
|
+
<div class="settings-panel">
|
|
153
|
+
<div class="settings-header">
|
|
154
|
+
<h2>Settings</h2>
|
|
155
|
+
<button class="modal-close" id="settingsCloseBtn">×</button>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="settings-body" id="settingsBody"></div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<!-- Shortcuts Help -->
|
|
162
|
+
<div class="shortcuts-overlay" id="shortcutsOverlay">
|
|
163
|
+
<div class="shortcuts-panel">
|
|
164
|
+
<div class="shortcuts-header">
|
|
165
|
+
<h2>Keyboard Shortcuts</h2>
|
|
166
|
+
<button class="shortcuts-close" onclick="document.getElementById('shortcutsOverlay').classList.remove('open')">×</button>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="shortcuts-body">
|
|
169
|
+
<div class="shortcuts-section">
|
|
170
|
+
<h3>Navigation</h3>
|
|
171
|
+
<div class="shortcut-row"><kbd>j</kbd> <kbd>↓</kbd><span>Next request</span></div>
|
|
172
|
+
<div class="shortcut-row"><kbd>k</kbd> <kbd>↑</kbd><span>Previous request</span></div>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="shortcuts-section">
|
|
175
|
+
<h3>Global</h3>
|
|
176
|
+
<div class="shortcut-row"><kbd>⌘/Ctrl</kbd> + <kbd>K</kbd><span>Search</span></div>
|
|
177
|
+
<div class="shortcut-row"><kbd>v</kbd><span>Toggle grouped/flat view</span></div>
|
|
178
|
+
<div class="shortcut-row"><kbd>d</kbd><span>Toggle dark/light theme</span></div>
|
|
179
|
+
<div class="shortcut-row"><kbd>Esc</kbd><span>Close modal / search</span></div>
|
|
180
|
+
<div class="shortcut-row"><kbd>?</kbd><span>Show this help</span></div>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="shortcuts-section">
|
|
183
|
+
<h3>Modal (when open)</h3>
|
|
184
|
+
<div class="shortcut-row"><kbd>f</kbd><span>Formatted view</span></div>
|
|
185
|
+
<div class="shortcut-row"><kbd>r</kbd><span>Raw view</span></div>
|
|
186
|
+
<div class="shortcut-row"><kbd>t</kbd><span>Tools view</span></div>
|
|
187
|
+
<div class="shortcut-row"><kbd>c</kbd><span>Copy content</span></div>
|
|
188
|
+
<div class="shortcut-row"><kbd>/</kbd><span>Focus search</span></div>
|
|
189
|
+
<div class="shortcut-row"><kbd>]</kbd><span>Next segment</span></div>
|
|
190
|
+
<div class="shortcut-row"><kbd>[</kbd><span>Previous segment</span></div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
146
196
|
</body>
|
|
147
197
|
</html>
|
package/server.js
CHANGED
|
@@ -295,6 +295,120 @@ function createServer(opts = {}) {
|
|
|
295
295
|
|
|
296
296
|
loadProfiles();
|
|
297
297
|
|
|
298
|
+
// ─── Smart Strip settings ─────────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
const SETTINGS_FILE = path.join(__dirname, "settings.json");
|
|
301
|
+
|
|
302
|
+
let stripSettings = { mode: "off", keepN: 3, threshold: 2000 };
|
|
303
|
+
|
|
304
|
+
function loadSettings() {
|
|
305
|
+
try {
|
|
306
|
+
if (fs.existsSync(SETTINGS_FILE)) {
|
|
307
|
+
const data = JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf-8"));
|
|
308
|
+
if (data.strip) Object.assign(stripSettings, data.strip);
|
|
309
|
+
}
|
|
310
|
+
} catch (e) {
|
|
311
|
+
console.warn("Failed to load settings:", e.message);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function saveSettings() {
|
|
316
|
+
try {
|
|
317
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify({ strip: stripSettings }, null, 2));
|
|
318
|
+
} catch (e) {
|
|
319
|
+
console.warn("Failed to save settings:", e.message);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Identify user-initiated turns in the message history.
|
|
325
|
+
* A turn starts with a user message containing text (no tool_result blocks).
|
|
326
|
+
* Returns array of { startIdx, endIdx } covering each turn's message range.
|
|
327
|
+
*/
|
|
328
|
+
function identifyTurns(messages) {
|
|
329
|
+
const turns = [];
|
|
330
|
+
let current = null;
|
|
331
|
+
for (let i = 0; i < messages.length; i++) {
|
|
332
|
+
const msg = messages[i];
|
|
333
|
+
const hasToolResult = Array.isArray(msg.content) &&
|
|
334
|
+
msg.content.some(c => c.type === "tool_result");
|
|
335
|
+
if (msg.role === "user" && !hasToolResult) {
|
|
336
|
+
current = { startIdx: i, endIdx: i };
|
|
337
|
+
turns.push(current);
|
|
338
|
+
} else if (current) {
|
|
339
|
+
current.endIdx = i;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return turns;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Apply Smart Strip to conversation history.
|
|
347
|
+
* Strips intermediate tool_use/tool_result messages from past turns,
|
|
348
|
+
* keeping only the first (user text) and last (assistant text) messages.
|
|
349
|
+
* Returns { messages, stripped, estimatedTokensSaved }.
|
|
350
|
+
*/
|
|
351
|
+
function applySmartStrip(messages) {
|
|
352
|
+
if (stripSettings.mode === "off" || !messages || messages.length < 3) {
|
|
353
|
+
return { messages, stripped: 0, estimatedTokensSaved: 0 };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const turns = identifyTurns(messages);
|
|
357
|
+
if (turns.length < 2) return { messages, stripped: 0, estimatedTokensSaved: 0 };
|
|
358
|
+
|
|
359
|
+
// Determine which turns to strip based on mode
|
|
360
|
+
let turnsToKeepIntact;
|
|
361
|
+
if (stripSettings.mode === "keep_n") {
|
|
362
|
+
turnsToKeepIntact = stripSettings.keepN || 3;
|
|
363
|
+
} else if (stripSettings.mode === "strip_all") {
|
|
364
|
+
turnsToKeepIntact = 1; // keep only the current (last) turn
|
|
365
|
+
} else {
|
|
366
|
+
turnsToKeepIntact = 1; // smart_size also processes all past turns
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Always keep the last N turns intact (current turn = last one)
|
|
370
|
+
const turnsToStrip = turns.slice(0, Math.max(0, turns.length - turnsToKeepIntact));
|
|
371
|
+
|
|
372
|
+
// Collect indices to remove
|
|
373
|
+
const indicesToRemove = new Set();
|
|
374
|
+
let tokensSaved = 0;
|
|
375
|
+
|
|
376
|
+
for (const turn of turnsToStrip) {
|
|
377
|
+
// A turn needs at least 3 messages to have intermediate ones to strip
|
|
378
|
+
if (turn.endIdx - turn.startIdx < 2) continue;
|
|
379
|
+
|
|
380
|
+
// In smart_size mode, check if this turn's intermediate messages are large enough
|
|
381
|
+
if (stripSettings.mode === "smart_size") {
|
|
382
|
+
let intermediateTokens = 0;
|
|
383
|
+
for (let i = turn.startIdx + 1; i < turn.endIdx; i++) {
|
|
384
|
+
const content = typeof messages[i].content === "string"
|
|
385
|
+
? messages[i].content
|
|
386
|
+
: JSON.stringify(messages[i].content);
|
|
387
|
+
intermediateTokens += Math.ceil(content.length / 3.8);
|
|
388
|
+
}
|
|
389
|
+
if (intermediateTokens < (stripSettings.threshold || 2000)) continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Strip everything between first and last message of this turn
|
|
393
|
+
for (let i = turn.startIdx + 1; i < turn.endIdx; i++) {
|
|
394
|
+
const content = typeof messages[i].content === "string"
|
|
395
|
+
? messages[i].content
|
|
396
|
+
: JSON.stringify(messages[i].content);
|
|
397
|
+
tokensSaved += Math.ceil(content.length / 3.8);
|
|
398
|
+
indicesToRemove.add(i);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (indicesToRemove.size === 0) {
|
|
403
|
+
return { messages, stripped: 0, estimatedTokensSaved: 0 };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const result = messages.filter((_, i) => !indicesToRemove.has(i));
|
|
407
|
+
return { messages: result, stripped: indicesToRemove.size, estimatedTokensSaved: tokensSaved };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
loadSettings();
|
|
411
|
+
|
|
298
412
|
// ─── Group tracking ────────────────────────────────────────────────────────
|
|
299
413
|
|
|
300
414
|
let groupCounter = 0;
|
|
@@ -778,6 +892,31 @@ function createServer(opts = {}) {
|
|
|
778
892
|
return;
|
|
779
893
|
}
|
|
780
894
|
|
|
895
|
+
// ── API: Settings (Smart Strip) ──
|
|
896
|
+
if (req.url === "/api/settings" && req.method === "GET") {
|
|
897
|
+
jsonResponse(res, 200, { strip: stripSettings });
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (req.url === "/api/settings" && req.method === "POST") {
|
|
901
|
+
readBody(req, (err, body) => {
|
|
902
|
+
if (err) { jsonResponse(res, 400, { error: err.message }); return; }
|
|
903
|
+
try {
|
|
904
|
+
const data = JSON.parse(body);
|
|
905
|
+
if (data.strip) {
|
|
906
|
+
if (data.strip.mode) stripSettings.mode = data.strip.mode;
|
|
907
|
+
if (data.strip.keepN != null) stripSettings.keepN = Math.max(1, parseInt(data.strip.keepN) || 3);
|
|
908
|
+
if (data.strip.threshold != null) stripSettings.threshold = Math.max(100, parseInt(data.strip.threshold) || 2000);
|
|
909
|
+
}
|
|
910
|
+
saveSettings();
|
|
911
|
+
broadcast({ type: "settings_updated", strip: stripSettings });
|
|
912
|
+
jsonResponse(res, 200, { success: true, strip: stripSettings });
|
|
913
|
+
} catch (e) {
|
|
914
|
+
jsonResponse(res, 400, { error: e.message });
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
781
920
|
// ── API: List profiles ──
|
|
782
921
|
if (req.url === "/api/profiles") {
|
|
783
922
|
jsonResponse(res, 200, { profiles, active: activeProfile });
|
|
@@ -902,10 +1041,11 @@ function createServer(opts = {}) {
|
|
|
902
1041
|
const originalToolCount = (parsed.tools || []).length;
|
|
903
1042
|
const { filtered, removed } = applyToolFilter(parsed.tools, activeProfile);
|
|
904
1043
|
|
|
1044
|
+
let needsReserialize = false;
|
|
1045
|
+
|
|
905
1046
|
if (removed.length > 0) {
|
|
906
1047
|
parsed.tools = filtered;
|
|
907
|
-
|
|
908
|
-
forwardBuffer = Buffer.from(modifiedStr, "utf-8");
|
|
1048
|
+
needsReserialize = true;
|
|
909
1049
|
filteringInfo = {
|
|
910
1050
|
originalToolCount,
|
|
911
1051
|
filteredToolCount: filtered.length,
|
|
@@ -916,7 +1056,23 @@ function createServer(opts = {}) {
|
|
|
916
1056
|
};
|
|
917
1057
|
}
|
|
918
1058
|
|
|
919
|
-
//
|
|
1059
|
+
// Apply Smart Strip to conversation history
|
|
1060
|
+
let stripInfo = null;
|
|
1061
|
+
const stripResult = applySmartStrip(parsed.messages);
|
|
1062
|
+
if (stripResult.stripped > 0) {
|
|
1063
|
+
parsed.messages = stripResult.messages;
|
|
1064
|
+
needsReserialize = true;
|
|
1065
|
+
stripInfo = {
|
|
1066
|
+
messagesStripped: stripResult.stripped,
|
|
1067
|
+
estimatedTokensSaved: stripResult.estimatedTokensSaved,
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (needsReserialize) {
|
|
1072
|
+
forwardBuffer = Buffer.from(JSON.stringify(parsed), "utf-8");
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Analyze the FILTERED + STRIPPED request (what actually gets sent)
|
|
920
1076
|
const analysis = analyzeRequest(parsed);
|
|
921
1077
|
|
|
922
1078
|
// Attach filtering info
|
|
@@ -927,12 +1083,17 @@ function createServer(opts = {}) {
|
|
|
927
1083
|
analysis.removedTools = filteringInfo.removedTools;
|
|
928
1084
|
analysis.tokensSaved = filteringInfo.tokensSaved;
|
|
929
1085
|
}
|
|
1086
|
+
if (stripInfo) {
|
|
1087
|
+
analysis.stripActive = true;
|
|
1088
|
+
analysis.messagesStripped = stripInfo.messagesStripped;
|
|
1089
|
+
analysis.stripTokensSaved = stripInfo.estimatedTokensSaved;
|
|
1090
|
+
}
|
|
930
1091
|
|
|
931
1092
|
requestTurn = analysis.turn;
|
|
932
1093
|
|
|
933
1094
|
broadcast({ type: "request", ...analysis });
|
|
934
1095
|
console.log(
|
|
935
|
-
`[R${analysis.turn}] ${analysis.model} | ${analysis.segments.length} segs | ~${analysis.totalEstimatedTokens} tokens | $${analysis.estimatedCost.totalCost.toFixed(4)}${filteringInfo ? ` | FILTERED: ${filteringInfo.originalToolCount}→${filteringInfo.filteredToolCount} tools (-${filteringInfo.removedTools.length})` : ""}`
|
|
1096
|
+
`[R${analysis.turn}] ${analysis.model} | ${analysis.segments.length} segs | ~${analysis.totalEstimatedTokens} tokens | $${analysis.estimatedCost.totalCost.toFixed(4)}${filteringInfo ? ` | FILTERED: ${filteringInfo.originalToolCount}→${filteringInfo.filteredToolCount} tools (-${filteringInfo.removedTools.length})` : ""}${stripInfo ? ` | STRIPPED: ${stripInfo.messagesStripped} msgs (-~${stripInfo.estimatedTokensSaved} tokens)` : ""}`
|
|
936
1097
|
);
|
|
937
1098
|
|
|
938
1099
|
// Let plugins analyze the request (router prediction, etc.)
|
|
@@ -1108,6 +1269,7 @@ function createServer(opts = {}) {
|
|
|
1108
1269
|
activeProfile,
|
|
1109
1270
|
premium: false,
|
|
1110
1271
|
routerMode: "off",
|
|
1272
|
+
strip: stripSettings,
|
|
1111
1273
|
...pluginHost.getConnectPayload(),
|
|
1112
1274
|
}));
|
|
1113
1275
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*,:before,:after{box-sizing:border-box;margin:0;padding:0}:root{--bg:#09090b;--bg2:#111113;--bg3:#18181b;--bg4:#27272a;--border:#ffffff0f;--text:#fafafa;--text2:#a1a1aa;--text3:#52525b;--blue:#60a5fa;--purple:#a78bfa;--green:#34d399;--orange:#fb923c;--cyan:#22d3ee;--yellow:#fbbf24;--red:#f87171;--amber:#f59e0b;--seg-system:#60a5fa;--seg-tools:#fb923c;--seg-message:#22d3ee;--seg-assistant:#34d399;--seg-tool-result:#fbbf24;--seg-tool-use:#a78bfa;--overlay-1:#ffffff03;--overlay-2:#ffffff05;--overlay-3:#ffffff08;--overlay-4:#ffffff0a;--overlay-5:#ffffff0d;--overlay-6:#ffffff0f;--overlay-8:#ffffff14;--overlay-10:#ffffff1a;--overlay-12:#ffffff1f;--overlay-15:#ffffff26;--scrollbar-thumb:#ffffff0f;--scrollbar-thumb-hover:#ffffff1f;--modal-backdrop:#000000b3;--noise-opacity:.015;--text-hover:white;--bar-seg-text:white;--bar-seg-shadow:0 1px 2px #00000080;--line-num-color:#ffffff1f;--shadow-inset:inset 0 1px 3px #0000004d;--font-ui:"Instrument Sans", -apple-system, system-ui, sans-serif;--font-mono:"JetBrains Mono", "SF Mono", "Fira Code", monospace;--radius-sm:6px;--radius-md:10px;--radius-lg:14px;--radius-xl:20px;--shadow-sm:0 1px 2px #0000004d;--shadow-md:0 4px 12px #0006;--shadow-lg:0 12px 40px #00000080;--shadow-xl:0 24px 80px #0009;--ease-out:cubic-bezier(.16, 1, .3, 1);--ease-spring:cubic-bezier(.34, 1.56, .64, 1);--duration-fast:.15s;--duration-normal:.25s;--duration-slow:.4s}html[data-theme=light]{--bg:#fafafa;--bg2:#f4f4f5;--bg3:#e4e4e7;--bg4:#d4d4d8;--border:#00000014;--text:#18181b;--text2:#52525b;--text3:#a1a1aa;--blue:#2563eb;--purple:#7c3aed;--green:#059669;--orange:#ea580c;--cyan:#0891b2;--yellow:#d97706;--red:#dc2626;--amber:#b45309;--seg-system:#2563eb;--seg-tools:#ea580c;--seg-message:#0891b2;--seg-assistant:#059669;--seg-tool-result:#d97706;--seg-tool-use:#7c3aed;--overlay-1:#00000003;--overlay-2:#00000005;--overlay-3:#00000008;--overlay-4:#0000000a;--overlay-5:#0000000d;--overlay-6:#0000000f;--overlay-8:#0000000f;--overlay-10:#00000014;--overlay-12:#0000001a;--overlay-15:#0000001f;--scrollbar-thumb:#0000001a;--scrollbar-thumb-hover:#0003;--modal-backdrop:#0000004d;--noise-opacity:0;--text-hover:var(--text);--bar-seg-text:white;--bar-seg-shadow:0 1px 2px #0000004d;--line-num-color:#0003;--shadow-inset:inset 0 1px 3px #00000014;--shadow-sm:0 1px 2px #0000000f;--shadow-md:0 4px 12px #00000014;--shadow-lg:0 12px 40px #0000001a;--shadow-xl:0 24px 80px #0000001f}html.theme-transitioning,html.theme-transitioning *,html.theme-transitioning :before,html.theme-transitioning :after{transition:background-color .3s,color .3s,border-color .3s,box-shadow .3s!important}body{background:var(--bg);color:var(--text);font-family:var(--font-ui);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;flex-direction:column;height:100vh;display:flex;overflow:hidden}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);transition:background var(--duration-fast);border-radius:99px}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}.noise-overlay{z-index:9999;pointer-events:none;opacity:var(--noise-opacity);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");position:fixed;inset:0}.header{z-index:100;background:var(--bg2);justify-content:space-between;align-items:center;padding:10px 20px;display:flex;position:relative}.header:after{content:"";background:var(--border);height:1px;position:absolute;bottom:0;left:0;right:0}.header-left{align-items:center;gap:10px;display:flex}.header-brand{flex-direction:column;gap:1px;display:flex}.logo{object-fit:cover;border-radius:7px;width:28px;height:28px}.header h1{letter-spacing:-.02em;font-size:15px;font-weight:800;line-height:1}.header-brand .status{font-size:10px}.header-right{align-items:center;gap:8px;display:flex}.hdr-sep{background:var(--border);flex-shrink:0;width:1px;height:18px}.hdr-metrics{align-items:center;gap:6px;display:flex}.theme-toggle{border-radius:var(--radius-sm);border:1px solid var(--overlay-8);background:var(--overlay-3);width:26px;height:26px;color:var(--text3);cursor:pointer;transition:all var(--duration-fast);flex-shrink:0;justify-content:center;align-items:center;display:flex}.theme-toggle:hover{background:var(--overlay-8);color:var(--text);border-color:var(--overlay-12)}.theme-toggle:active{transform:scale(.95)}.status{align-items:center;gap:5px;font-size:11px;font-weight:600;display:flex}.status-dot{border-radius:50%;flex-shrink:0;width:6px;height:6px}.status-dot.connected{background:var(--green);animation:2s ease-in-out infinite statusPulse;box-shadow:0 0 6px #34d39980}.status-dot.disconnected{background:var(--red)}@keyframes statusPulse{0%,to{box-shadow:0 0 6px #34d39980}50%{box-shadow:0 0 10px #34d399b3}}.req-badge{border-radius:var(--radius-sm);background:var(--overlay-3);height:26px;color:var(--text3);font-size:11px;font-weight:600;font-family:var(--font-mono);font-variant-numeric:tabular-nums;align-items:center;padding:4px 8px;display:flex}.daily-saved{border-radius:var(--radius-sm);height:26px;color:var(--text3);font-size:11px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums;background:#22c55e0f;border:1px solid #22c55e14;align-items:center;padding:4px 8px;display:flex}.daily-saved.has-savings{color:var(--green);background:#22c55e1a;border-color:#22c55e26}.daily-cost{border-radius:var(--radius-sm);height:26px;color:var(--amber);font-size:11px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums;background:#f59e0b14;border:1px solid #f59e0b1f;align-items:center;padding:4px 8px;display:flex}.profile-select{border-radius:var(--radius-sm);border:1px solid var(--overlay-8);background:var(--overlay-3);height:26px;color:var(--text);font-size:11px;font-weight:600;font-family:var(--font-ui);cursor:pointer;max-width:140px;transition:all var(--duration-fast);outline:none;padding:0 8px}.profile-select:hover{background:var(--overlay-6);border-color:var(--overlay-12)}.profile-select.filtering{color:var(--orange);background:#fb923c0f;border-color:#fb923c4d}.filter-badge{border-radius:var(--radius-sm);height:26px;color:var(--orange);letter-spacing:.06em;background:#fb923c1f;border:1px solid #fb923c33;align-items:center;padding:0 6px;font-size:9px;font-weight:800;line-height:1;display:flex}.router-badge-wrapper{position:relative}.router-badge{border-radius:var(--radius-sm);letter-spacing:.03em;cursor:pointer;-webkit-user-select:none;user-select:none;height:26px;transition:all var(--duration-fast);align-items:center;padding:0 8px;font-size:9px;font-weight:700;display:flex}.router-badge:hover{filter:brightness(1.2)}.router-badge--off{background:var(--overlay-3);color:var(--text3);border:1px solid var(--overlay-8)}.router-badge--shadow{color:var(--purple);background:#a78bfa1a;border:1px solid #a78bfa33}.router-badge--auto{color:var(--cyan);background:#22d3ee1a;border:1px solid #22d3ee33}.router-popover{background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius-md);z-index:300;min-width:210px;padding:6px 0;display:none;position:absolute;top:calc(100% + 8px);right:0;box-shadow:0 8px 30px #00000059}.router-popover.open{display:block}.router-popover-title{color:var(--text3);text-transform:uppercase;letter-spacing:.1em;border-bottom:1px solid var(--border);margin-bottom:2px;padding:6px 12px 8px;font-size:8px;font-weight:700}.router-popover-opt{width:100%;color:var(--text);font-size:11px;font-weight:500;font-family:var(--font-ui);cursor:pointer;text-align:left;transition:background var(--duration-fast);background:0 0;border:none;align-items:center;gap:8px;padding:7px 12px;display:flex}.router-popover-opt:hover{background:var(--overlay-6)}.router-popover-opt.active{background:var(--overlay-4);font-weight:700}.router-opt-dot{border-radius:50%;flex-shrink:0;width:7px;height:7px}.router-opt-dot--off{background:var(--text3)}.router-opt-dot--shadow{background:var(--purple)}.router-opt-dot--auto{background:var(--cyan)}.router-opt-desc{color:var(--text3);margin-left:auto;font-size:9px;font-weight:400}.global-search-wrapper{position:relative}.global-search{border-radius:var(--radius-sm);border:1px solid var(--overlay-8);background:var(--overlay-3);height:26px;color:var(--text);font-size:10px;font-family:var(--font-ui);width:170px;transition:all var(--duration-fast);outline:none;padding:0 10px}.global-search::placeholder{color:var(--text3)}.global-search:focus{border-color:#60a5fa4d;width:220px;box-shadow:0 0 0 2px #60a5fa14}.global-search-results{background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius-md);z-index:200;max-height:320px;box-shadow:var(--shadow-lg);min-width:360px;margin-top:4px;display:none;position:absolute;top:100%;left:0;right:0;overflow-y:auto}.global-search-results.open{display:block}.search-result-item{border-bottom:1px solid var(--overlay-4);cursor:pointer;transition:background var(--duration-fast);padding:8px 12px}.search-result-item:hover{background:var(--overlay-6)}.search-result-item:last-child{border-bottom:none}.search-result-turn{color:var(--blue);margin-bottom:2px;font-size:10px;font-weight:700}.search-result-snippet{color:var(--text2);font-size:11px;font-family:var(--font-mono);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.search-result-snippet mark{color:var(--text);background:#fbbf244d;border-radius:2px}.search-no-results{color:var(--text3);text-align:center;padding:12px;font-size:11px}.bar-container{padding:14px 24px 10px}.bar-outer{background:var(--bg3);border:1px solid var(--border);height:52px;box-shadow:var(--shadow-inset);transition:box-shadow var(--duration-slow), border-color var(--duration-slow);border-radius:12px;position:relative;overflow:hidden}.bar-outer:before{content:"";z-index:0;pointer-events:none;background:radial-gradient(ellipse at 50% 100%, var(--overlay-2) 0%, transparent 70%);position:absolute;inset:0}.bar-outer.pressure-high{box-shadow:var(--shadow-inset), 0 0 24px #fb923c26;border-color:#fb923c40}.bar-outer.pressure-critical{box-shadow:var(--shadow-inset), 0 0 30px #f8717133;border-color:#f871714d}.bar-inner{z-index:1;height:100%;transition:all var(--duration-slow) var(--ease-out);display:flex;position:relative}.bar-segment{cursor:pointer;justify-content:center;align-items:center;height:100%;transition:all .35s;display:flex;overflow:hidden}.bar-segment:hover{filter:brightness(1.2)}.bar-segment span{color:var(--bar-seg-text);text-shadow:var(--bar-seg-shadow);white-space:nowrap;padding:0 3px;font-size:10px;font-weight:600}.bar-empty{flex-grow:1;justify-content:center;align-items:center;display:flex}.bar-empty span{color:var(--text3);font-size:11px}.bar-break{pointer-events:none;background:repeating-linear-gradient(-60deg, transparent, transparent 3px, var(--overlay-10) 3px, var(--overlay-10) 5px);width:12px;min-width:12px;height:100%;transition:opacity .35s;position:relative}.bar-break:before,.bar-break:after{content:"";background:var(--overlay-10);width:1px;position:absolute;top:0;bottom:0}.bar-break:before{left:0}.bar-break:after{right:0}.bar-marker{border-left:1px dashed var(--overlay-10);pointer-events:none;z-index:2;position:absolute;top:0;bottom:0}.bar-marker span{color:var(--text3);font-size:7px;position:absolute;top:1px;left:3px}.bar-stats{justify-content:space-between;align-items:center;margin-top:6px;padding:0 2px;display:flex}.bar-legend{flex-wrap:wrap;gap:16px;display:flex}.legend-item{color:var(--text3);cursor:pointer;align-items:center;gap:4px;font-size:11px;display:flex}.legend-dot{border-radius:2px;width:7px;height:7px}.bar-total{font-size:13px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums}.bar-pct{background:var(--overlay-4);font-size:10px;font-weight:600;font-family:var(--font-mono);border-radius:99px;padding:1px 6px}.token-chart-container{border-radius:var(--radius-sm);background:var(--overlay-2);border:1px solid var(--border);margin-top:8px;padding:8px 12px}.token-chart-label{color:var(--text3);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px;font-size:9px;font-weight:700}.token-chart{align-items:center;gap:8px;display:flex}.token-chart-svg{width:100%;max-width:240px;height:36px}.token-chart-hint{color:var(--text3);white-space:nowrap;font-size:10px;font-family:var(--font-mono)}.main{flex:1;display:flex;overflow:hidden}.panel{flex-direction:column;display:flex;overflow:hidden}.panel-header{border-bottom:1px solid var(--border);color:var(--text2);text-transform:uppercase;letter-spacing:.06em;flex-shrink:0;justify-content:space-between;align-items:center;padding:10px 16px;font-size:12px;font-weight:800;display:flex}.panel-actions{align-items:center;gap:6px;display:flex}.panel-btn{background:var(--overlay-4);border:1px solid var(--overlay-8);color:var(--text2);cursor:pointer;font-size:11px;font-family:var(--font-ui);border-radius:var(--radius-sm);transition:all var(--duration-fast);padding:4px 10px;font-weight:600}.panel-btn:hover:not(:disabled){color:var(--text);background:var(--overlay-6)}.panel-btn:active:not(:disabled){transform:scale(.97)}.panel-btn:disabled{opacity:.5;cursor:not-allowed}.export-dropdown{position:relative}.export-menu{background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius-sm);z-index:100;min-width:90px;box-shadow:var(--shadow-md);margin-top:4px;padding:4px;display:none;position:absolute;top:100%;right:0}.export-menu.open{flex-direction:column;gap:2px;display:flex}.export-option{color:var(--text);cursor:pointer;font-size:11px;font-family:var(--font-ui);text-align:left;transition:background var(--duration-fast);background:0 0;border:none;border-radius:4px;padding:6px 10px}.export-option:hover{background:var(--overlay-8)}.panel-body{flex:1;overflow-y:auto}.reqs-panel{border-right:1px solid var(--border);background:var(--bg2);width:320px}.req-card{border-bottom:1px solid var(--border);cursor:pointer;transition:background var(--duration-fast), transform var(--duration-fast);padding:6px 12px}.req-card:hover{background:var(--overlay-2)}.req-card:active{transform:scale(.99)}.req-card.selected{border-left:3px solid var(--amber);background:#f59e0b0d;padding-left:9px}.req-card-head{justify-content:space-between;align-items:center;display:flex}.req-label{color:var(--text);font-size:12px;font-weight:500}.req-tokens{font-size:11px;font-weight:600;font-family:var(--font-mono);font-variant-numeric:tabular-nums}.req-mini-bar{background:var(--bg);border-radius:99px;height:3px;margin-top:2px;overflow:hidden}.req-mini-fill{height:100%;transition:width .3s var(--ease-out);box-shadow:0 0 4px var(--overlay-5);border-radius:99px}.req-meta{color:var(--text3);font-size:10px;font-family:var(--font-mono);gap:6px;margin-top:2px;display:flex}.req-io{color:var(--green);font-weight:600}.req-cost-inline{color:var(--amber);font-weight:600}.view-toggle{transition:all var(--duration-fast);font-weight:600!important}.view-toggle.active{background:#f59e0b1a;color:var(--amber)!important}.group-card{margin-bottom:2px}.group-header{cursor:pointer;transition:background var(--duration-fast);border-bottom:1px solid var(--border);background:linear-gradient(180deg, var(--overlay-2) 0%, transparent 100%);align-items:center;gap:8px;padding:10px 14px;display:flex}.group-header:hover{background:var(--overlay-3)}.group-chevron{color:var(--text3);transition:transform var(--duration-normal) var(--ease-spring);text-align:center;flex-shrink:0;width:14px;font-size:10px}.group-chevron.expanded{transform:rotate(90deg)}.group-title{color:var(--text);flex:1;min-width:0;font-size:13px;font-weight:600}.group-summary{font-size:11px;font-family:var(--font-mono);font-variant-numeric:tabular-nums;align-items:center;gap:10px;display:flex}.group-req-count{background:var(--overlay-6);color:var(--text2);border-radius:4px;padding:2px 8px;font-weight:600}.group-cost{color:var(--amber);font-weight:600}.group-tokens{color:var(--text3)}.group-children{overflow:hidden}.group-children.collapsed{display:none}.group-session-label{color:var(--text3);text-transform:uppercase;letter-spacing:.05em;align-items:center;gap:6px;margin-left:14px;padding:4px 14px 2px 26px;font-size:9px;font-weight:700;display:flex}.session-pill{letter-spacing:.03em;text-transform:capitalize;border-radius:4px;padding:2px 8px;font-size:9px;font-weight:700;display:inline-block}.session-pill.main{color:var(--purple);background:#a78bfa26}.session-pill.subagent{color:var(--cyan);background:#22d3ee26}.group-children .req-card{border-left:2px solid var(--overlay-6);margin-left:14px;padding-left:26px}.group-children .req-card.selected{border-left:2px solid var(--amber);padding-left:26px}.group-time{color:var(--text3);font-size:9px;font-family:var(--font-mono);padding:2px 14px 8px}.detail-panel{background:var(--bg);flex:1}.segment-row{border-bottom:1px solid var(--overlay-3);transition:background var(--duration-fast);cursor:pointer;align-items:center;gap:10px;padding:12px 18px;display:flex}.segment-row:hover{background:var(--overlay-3)}.seg-color{border-radius:3px;flex-shrink:0;width:5px;height:36px}.seg-info{flex:1;min-width:0}.seg-name{font-size:13px;font-weight:600}.seg-sub{color:var(--text3);white-space:nowrap;text-overflow:ellipsis;max-width:350px;margin-top:1px;font-size:11px;overflow:hidden}.seg-tokens{font-size:13px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums;text-align:right;min-width:60px}.seg-pct{color:var(--text3);text-align:right;min-width:40px;font-size:11px;font-family:var(--font-mono)}.seg-bar{background:var(--bg3);border-radius:99px;flex-shrink:0;width:80px;height:4px;overflow:hidden}.seg-bar-fill{border-radius:99px;height:100%}.seg-expand-hint{color:var(--text3);background:var(--overlay-3);white-space:nowrap;border-radius:4px;padding:2px 6px;font-size:9px}.empty{height:100%;color:var(--text3);flex-direction:column;justify-content:center;align-items:center;display:flex}.empty-icon{opacity:.3;margin-bottom:12px;font-size:40px}.empty h2{color:var(--text2);margin-bottom:4px;font-size:15px;font-weight:700}.empty p{text-align:center;max-width:300px;font-size:13px;line-height:1.5}.copy-command-btn{border:1px solid var(--overlay-12);background:var(--overlay-4);color:var(--cyan);cursor:pointer;font-size:10px;font-family:var(--font-ui);border-radius:4px;margin-left:6px;padding:2px 8px}.copy-command-btn:hover{background:#22d3ee1f;border-color:#22d3ee40}.stats-grid{grid-template-columns:repeat(3,1fr);gap:8px;padding:12px 16px;display:grid}.stats-grid:empty{display:none}.usage-box{border-radius:var(--radius-md);background:var(--bg2);border:1px solid var(--border);padding:10px 12px}.usage-row{justify-content:space-between;align-items:center;padding:2px 0;font-size:11px;display:flex}.usage-label{color:var(--text2)}.usage-value{font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums}.usage-value.actual{color:var(--green)}.usage-value.estimated{color:var(--text2)}.filter-box{border-radius:var(--radius-md);background:#fb923c0f;border:1px solid #fb923c26;padding:10px 12px}.filter-box-title{color:var(--orange);margin-bottom:6px;font-size:11px;font-weight:700}.warning-box{border-radius:var(--radius-md);background:#fbbf240f;border:1px solid #fbbf2426;padding:10px 12px}.warning-box-title{color:var(--yellow);margin-bottom:6px;font-size:11px;font-weight:700}.router-box{border-radius:var(--radius-md);background:#a78bfa0d;border:1px solid #a78bfa26;padding:10px 12px}.router-box-title{color:var(--purple);margin-bottom:6px;font-size:11px;font-weight:700}.router-mode-shadow{color:var(--purple)}.router-mode-auto{color:var(--cyan)}.router-mode-off{color:var(--text3)}.router-shadow-note{color:var(--text3);border-top:1px solid #a78bfa1f;margin-top:6px;padding-top:6px;font-size:9px;font-style:italic}.premium-locked{opacity:.5;cursor:not-allowed}.premium-locked-msg{color:var(--text3);text-align:center;padding:8px 0;font-size:10px;line-height:1.5}.router-box.premium-locked{border-style:dashed}.router-popover-opt.premium-locked{pointer-events:none}.daily-saved.premium-locked{opacity:.5;font-size:10px}.modal-overlay{z-index:1000;background:var(--modal-backdrop);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);justify-content:center;align-items:center;padding:32px;display:none;position:fixed;inset:0}.modal-overlay.open{display:flex}.modal{background:var(--bg2);border:1px solid var(--overlay-8);border-radius:var(--radius-xl);width:100%;max-width:900px;height:85vh;box-shadow:var(--shadow-xl);animation:modal-in var(--duration-normal) var(--ease-spring);flex-direction:column;display:flex;overflow:hidden}.modal-header{border-bottom:1px solid var(--border);background:var(--overlay-2);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);flex-shrink:0;align-items:center;gap:12px;padding:16px 20px;display:flex}.modal-color-bar{border-radius:2px;width:4px;height:28px}.modal-title{flex:1}.modal-title h2{font-size:16px;font-weight:700}.modal-title .modal-meta{color:var(--text3);margin-top:2px;font-size:12px}.modal-stats{align-items:center;gap:12px;display:flex}.modal-stat{text-align:center}.modal-stat-value{font-size:15px;font-weight:800;font-family:var(--font-mono);font-variant-numeric:tabular-nums}.modal-stat-label{color:var(--text3);text-transform:uppercase;letter-spacing:.05em;font-size:10px}.modal-close{border-radius:var(--radius-sm);cursor:pointer;background:var(--overlay-6);width:32px;height:32px;color:var(--text2);transition:all var(--duration-fast);border:none;justify-content:center;align-items:center;font-size:18px;display:flex}.modal-close:hover{background:var(--overlay-12);color:var(--text-hover)}.modal-close:active{transform:scale(.97)}.modal-toolbar{border-bottom:1px solid var(--border);flex-shrink:0;align-items:center;gap:6px;padding:10px 20px;display:flex}.modal-toolbar button{cursor:pointer;font-size:11px;font-weight:600;font-family:var(--font-ui);color:var(--text2);transition:all var(--duration-fast);background:0 0;border:none;border-radius:99px;padding:5px 14px}.modal-toolbar button:hover{background:var(--overlay-6);color:var(--text-hover)}.modal-toolbar button:active{transform:scale(.97)}.modal-toolbar button.active{color:var(--blue);background:#60a5fa1f;box-shadow:inset 0 0 0 1px #60a5fa33}.modal-toolbar .spacer{flex:1}.modal-toolbar .search-box{border:1px solid var(--overlay-8);background:var(--overlay-3);color:var(--text);width:180px;font-size:11px;font-family:var(--font-ui);transition:all var(--duration-fast);border-radius:99px;outline:none;padding:5px 12px}.modal-toolbar .search-box:focus{border-color:#60a5fa66;box-shadow:0 0 0 3px #60a5fa1a}.modal-body{flex:1;min-height:0;padding:0;overflow:auto}.modal-content{font-family:var(--font-mono);color:var(--text);white-space:pre-wrap;word-break:break-word;tab-size:2;padding:12px 20px;font-size:12px;line-height:1.3}.modal-content .line{border-left:1px solid var(--overlay-4);padding:1px 0 1px 48px;display:block;position:relative}.modal-content .line:nth-child(2n){background:var(--overlay-1)}.modal-content .line:hover{background:var(--overlay-3)}.modal-content .line-num{text-align:right;width:40px;color:var(--line-num-color);-webkit-user-select:none;user-select:none;padding-right:8px;font-size:10px;position:absolute;left:0}.modal-content .highlight{background:#fbbf2433;border-radius:2px}.modal-tools{padding:16px 20px}.modal-tools-header{border-radius:var(--radius-md);background:var(--overlay-2);justify-content:space-between;align-items:center;margin-bottom:12px;padding:8px 12px;display:flex}.modal-tools-header-left{color:var(--text2);font-size:11px}.modal-tools-header-left strong{color:var(--text)}.tool-section-header{color:var(--text3);text-transform:uppercase;letter-spacing:.06em;margin-top:4px;padding:12px 4px 6px;font-size:10px;font-weight:800}.tool-group{border:1px solid var(--overlay-6);border-radius:var(--radius-md);margin-bottom:10px;overflow:hidden}.tool-group-header{background:var(--overlay-3);cursor:pointer;transition:background var(--duration-fast);align-items:center;gap:8px;padding:8px 12px;display:flex}.tool-group-header:hover{background:var(--overlay-5)}.tool-group-checkbox,.tool-card input[type=checkbox]{appearance:none;border:1.5px solid var(--text3);cursor:pointer;width:16px;height:16px;transition:all var(--duration-fast);background:0 0;border-radius:4px;flex-shrink:0;position:relative}.tool-group-checkbox:checked,.tool-card input[type=checkbox]:checked{background:var(--blue);border-color:var(--blue)}.tool-group-checkbox:checked:after,.tool-card input[type=checkbox]:checked:after{content:"";border:2px solid #fff;border-width:0 2px 2px 0;width:5px;height:9px;position:absolute;top:1px;left:4px;transform:rotate(45deg)}.tool-group-chevron{color:var(--text3);transition:transform var(--duration-normal) var(--ease-spring);-webkit-user-select:none;user-select:none;flex-shrink:0;font-size:9px}.tool-group-chevron.expanded{transform:rotate(90deg)}.tool-group-title{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.tool-group-name{color:var(--purple);font-size:13px;font-weight:700}.tool-group-meta{color:var(--text3);font-size:11px}.tool-group-actions{flex-shrink:0;gap:4px;display:flex}.tool-group-body{padding:8px}.tool-group-body.collapsed{display:none}.tool-card{border-radius:var(--radius-sm);background:var(--overlay-2);border:1px solid var(--overlay-4);transition:all var(--duration-fast);align-items:center;gap:10px;margin-bottom:4px;padding:8px 12px;display:flex}.tool-card:hover{background:var(--overlay-5);box-shadow:var(--shadow-sm)}.tool-card-info{flex:1;min-width:0}.tool-card-name{color:var(--orange);font-size:13px;font-weight:700}.tool-card-desc{color:var(--text3);white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:11px;overflow:hidden}.tool-card-tokens{color:var(--text3);font-size:11px;font-weight:600;font-family:var(--font-mono);font-variant-numeric:tabular-nums;white-space:nowrap}.never-used-tag{color:var(--text3);background:var(--overlay-4);vertical-align:middle;border-radius:3px;margin-left:6px;padding:1px 5px;font-size:8px;font-weight:600}.save-profile-bar{border-radius:var(--radius-md);background:var(--overlay-2);border:1px dashed var(--overlay-10);align-items:center;gap:8px;margin-top:12px;padding:10px 12px;display:flex}.save-profile-bar input{border-radius:var(--radius-sm);border:1px solid var(--overlay-10);background:var(--overlay-4);color:var(--text);font-size:11px;font-family:var(--font-ui);transition:border-color var(--duration-fast);outline:none;flex:1;padding:6px 10px}.save-profile-bar input:focus{border-color:#60a5fa66;box-shadow:0 0 0 3px #60a5fa1a}.save-profile-bar button{border-radius:var(--radius-sm);cursor:pointer;font-size:11px;font-weight:700;font-family:var(--font-ui);transition:all var(--duration-fast);border:none;padding:6px 14px}.save-profile-bar button:active{transform:scale(.97)}.btn-primary{color:var(--blue);background:#60a5fa33}.btn-primary:hover{background:#60a5fa4d}.btn-secondary{background:var(--overlay-6);color:var(--text2)}.btn-secondary:hover{background:var(--overlay-10);color:var(--text-hover)}.savings-banner{border-radius:var(--radius-sm);color:var(--green);background:#34d39914;border:1px solid #34d39926;margin-top:8px;padding:8px 12px;font-size:11px;font-weight:600}.session-tabs{border-bottom:1px solid var(--border);background:var(--bg2);scrollbar-width:none;animation:fadeInUp var(--duration-normal) var(--ease-out) 30ms both;gap:0;padding:0 24px;display:none;overflow-x:auto}.session-tabs::-webkit-scrollbar{display:none}.session-tabs.visible{display:flex}.session-tab{color:var(--text3);cursor:pointer;white-space:nowrap;transition:color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);border-bottom:2px solid #0000;flex-shrink:0;padding:8px 18px 8px 14px;font-size:12px;font-weight:600;position:relative}.session-tab:hover{color:var(--text2)}.session-tab.active{color:var(--cyan);border-bottom-color:var(--cyan);font-weight:700}.session-tab-close{color:var(--text3);cursor:pointer;font-size:12px;line-height:1;display:none;position:absolute;top:4px;right:2px}.session-tab:hover .session-tab-close{display:block}.session-tab-close:hover{color:var(--red)}.session-tab-label{display:block}.session-tab-path{color:var(--text3);font-size:9px;font-weight:400;font-family:var(--font-mono);text-overflow:ellipsis;white-space:nowrap;text-align:left;direction:rtl;max-width:160px;display:block;overflow:hidden}@keyframes pulse{0%,to{opacity:1}50%{opacity:.4}}.waiting{animation:2s ease-in-out infinite pulse}@keyframes modal-in{0%{opacity:0;transform:scale(.96)translateY(8px)}to{opacity:1;transform:scale(1)translateY(0)}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.header{animation:fadeInUp var(--duration-normal) var(--ease-out) both}.bar-container{animation:fadeInUp var(--duration-normal) var(--ease-out) 80ms both}.reqs-panel{animation:fadeInUp var(--duration-normal) var(--ease-out) .14s both}.detail-panel{animation:fadeInUp var(--duration-normal) var(--ease-out) .2s both}button:active{transform:scale(.97)}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e={connected:!1,reqs:[],selectedReq:null,profiles:{},activeProfile:`All Tools`,premium:!1,routerMode:`off`,toolsUsed:new Set,groups:{},groupView:!0,expandedGroups:{},sessions:{},activeSessionTab:null},t={segment:null,segIndex:null,view:`formatted`,fullContent:``,parsedTools:null,loading:!1},n={system:`#60A5FA`,tools:`#FB923C`,message:`#22D3EE`,assistant:`#34D399`,tool_result:`#FBBF24`,tool_use:`#A78BFA`},r={system:`--seg-system`,tools:`--seg-tools`,message:`--seg-message`,assistant:`--seg-assistant`,tool_result:`--seg-tool-result`,tool_use:`--seg-tool-use`};function i(e){let t=r[e];if(t){let e=getComputedStyle(document.documentElement).getPropertyValue(t).trim();if(e)return e}return n[e]||`#64748B`}var a=3.8;function o(e){if(!e)return 0;let t=typeof e==`string`?e:JSON.stringify(e);return Math.ceil(t.length/a)}function s(e){return e.type===`message`&&e.role===`assistant`?i(`assistant`):e.type===`message`?i(`message`):i(e.type)}function c(e){return e.type===`message`?e.role===`user`?`User Message`:`Assistant Message`:e.type===`tool_result`?`Tool Result`:e.type===`tool_use`?`Tool Use`:e.type===`system`?`System Prompt`:e.type===`tools`?`Tool Definitions`:e.type}function l(e){return e>=1e6?(e/1e6).toFixed(1)+`M`:e>=1e3?(e/1e3).toFixed(1)+`k`:e.toString()}function u(e){return e>=1?`$`+e.toFixed(2):e>=.01?`$`+e.toFixed(3):`$`+e.toFixed(4)}function d(e){return e?e.includes(`opus-4-6`)||e.includes(`opus-4.6`)||e.includes(`opus-4-5`)||e.includes(`opus-4.5`)?5:e.includes(`opus`)?15:e.includes(`haiku-4`)?1:e.includes(`haiku`)?.8:3:3}function f(e){let t=document.createElement(`div`);return t.textContent=e,t.innerHTML}function p(e,t,n){return n||!t||!t.tools||t.tools.length===0?!0:t.mode===`blocklist`?!t.tools.includes(e):t.tools.includes(e)}function m(e){let t=e?.name||``;if(t.startsWith(`mcp__`)){let e=t.split(`__`);if(e.length>=3){let t=e[1].split(`_`);return t[t.length-1].toLowerCase()}}let n=t.match(/^([a-zA-Z0-9]+)[_\/]/);return n?n[1].toLowerCase():`other`}function h(e){let t=new Map;for(let n of e){let e=m(n);t.has(e)||t.set(e,[]),t.get(e).push(n)}let n=new Map,r=[...t.keys()].sort((e,t)=>e===`other`?1:t===`other`?-1:e.localeCompare(t));for(let e of r)n.set(e,t.get(e));return n}var g=`jannal_session`,_=`jannal_daily_costs`,v=`jannal_daily_savings`,ee=500,y=null;function b(e){y&&clearTimeout(y),y=setTimeout(()=>{try{let t={reqs:e.reqs,selectedReq:e.selectedReq,groupView:e.groupView,sessions:e.sessions,activeSessionTab:e.activeSessionTab,savedAt:Date.now()};localStorage.setItem(g,JSON.stringify(t))}catch(e){console.warn(`Failed to persist session:`,e.message)}y=null},ee)}function te(e){try{let t=localStorage.getItem(g);if(!t)return!1;let n=JSON.parse(t),r=n.reqs||n.turns,i=n.selectedReq??n.selectedTurn;if(r&&Array.isArray(r)&&r.length>0){e.reqs=r,e.selectedReq=i!=null&&i<e.reqs.length?i:e.reqs.length-1,n.groupView!=null&&(e.groupView=n.groupView),n.sessions&&(e.sessions=n.sessions),n.activeSessionTab!==void 0&&(e.activeSessionTab=n.activeSessionTab);for(let t of e.reqs)t.sessionId&&!t.tabKey&&(t.tabKey=t.sessionPath||t.sessionId);if(e.toolsUsed){e.toolsUsed.clear();for(let t of e.reqs)t.toolsUsed?.length&&t.toolsUsed.forEach(t=>e.toolsUsed.add(t))}return!0}}catch(e){console.warn(`Failed to restore session:`,e.message)}return!1}function ne(e){let t=0,n=e.reqs.map(e=>{let n=e.actualCost?.totalCost??e.estimatedCost?.totalCost??0;return t+=n,{request:e.turn,model:e.model,timestamp:e.timestamp,inputTokens:e.actualUsage?.input_tokens??e.totalEstimatedTokens,outputTokens:e.actualUsage?.output_tokens??0,cost:n,segments:e.segments?.map(e=>({name:e.name,type:e.type,tokens:e.tokens}))??[]}}),r={exportedAt:new Date().toISOString(),requestCount:n.length,totalCost:t,requests:n};return JSON.stringify(r,null,2)}function x(e){let t=[`Request`,`Model`,`Timestamp`,`Input Tokens`,`Output Tokens`,`Cost ($)`],n=e.reqs.map(e=>[e.turn,e.model,new Date(e.timestamp).toISOString(),e.actualUsage?.input_tokens??e.totalEstimatedTokens??``,e.actualUsage?.output_tokens??0,(e.actualCost?.totalCost??e.estimatedCost?.totalCost??0).toFixed(4)]);return[t.join(`,`),...n.map(e=>e.map((e,t)=>t===5?e:`"${String(e)}"`).join(`,`))].join(`
|
|
2
|
-
`)}function re(e){if(!(!e||e<=0))try{let t=new Date().toISOString().slice(0,10),n=JSON.parse(localStorage.getItem(_)||`{}`);n[t]=(n[t]||0)+e,localStorage.setItem(_,JSON.stringify(n))}catch{}}function ie(){try{let e=new Date().toISOString().slice(0,10);return JSON.parse(localStorage.getItem(_)||`{}`)[e]||0}catch{return 0}}function ae(e,t){if(!((!e||e<=0)&&(!t||t<=0)))try{let n=new Date().toISOString().slice(0,10),r=JSON.parse(localStorage.getItem(v)||`{}`);r[n]||(r[n]={cost:0,tokens:0}),typeof r[n]==`number`&&(r[n]={cost:r[n],tokens:0}),r[n].cost+=e||0,r[n].tokens+=t||0,localStorage.setItem(v,JSON.stringify(r))}catch{}}function oe(){try{let e=new Date().toISOString().slice(0,10),t=JSON.parse(localStorage.getItem(v)||`{}`)[e];return t?typeof t==`number`?{cost:t,tokens:0}:{cost:t.cost||0,tokens:t.tokens||0}:{cost:0,tokens:0}}catch{return{cost:0,tokens:0}}}function S(e,t){let n=new Blob([e],{type:`application/octet-stream`}),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=t,i.click(),URL.revokeObjectURL(r)}function C(e={}){T(),E(),le(),ce(),O(),e.skipDetail||j(),se()}function se(){let t=document.getElementById(`exportBtn`);t&&(t.disabled=e.reqs.length===0,t.title=e.reqs.length===0?`No data to export`:`Export session as JSON or CSV`)}function w(){let t=[];for(let n=0;n<e.reqs.length;n++){let r=e.reqs[n];(e.activeSessionTab===null||r.tabKey===e.activeSessionTab)&&t.push({originalIndex:n,req:r})}return t}function ce(){let t=document.getElementById(`sessionTabs`);if(!t)return;let n=Object.keys(e.sessions);if(n.length===0){t.classList.remove(`visible`);return}t.classList.add(`visible`);let r=`<div class="session-tab${e.activeSessionTab===null?` active`:``}" data-tab="">All</div>`;for(let t of n){let n=e.sessions[t],i=e.activeSessionTab===t;r+=`<div class="session-tab${i?` active`:``}" data-tab="${f(t)}">`,r+=`<span class="session-tab-label">${f(n.label)}</span>`,n.path&&(r+=`<span class="session-tab-path">${f(n.path)}</span>`),r+=`<span class="session-tab-close" data-tab-close="${f(t)}">×</span>`,r+=`</div>`}t.innerHTML=r,t.onclick=e=>{let t=e.target.closest(`[data-tab-close]`);if(t){e.stopPropagation(),window.dismissSessionTab(t.dataset.tabClose);return}let n=e.target.closest(`[data-tab]`);n&&window.selectSessionTab(n.dataset.tab||null)}}function T(){document.getElementById(`statusDot`).className=`status-dot ${e.connected?`connected`:`disconnected`}`;let t=document.getElementById(`statusText`);t.textContent=e.connected?`Connected`:`Disconnected`,t.style.color=e.connected?`var(--green)`:`var(--red)`;let n=document.getElementById(`reqBadge`);n&&(n.textContent=`Req ${e.reqs.length}`);let r=ie(),i=document.getElementById(`dailyCost`);i&&(i.textContent=`Cost: ${u(r)}`);let a=document.getElementById(`dailySaved`);if(a)if(!e.premium)a.textContent=`Saved: Pro`,a.className=`daily-saved premium-locked`,a.title=`Savings intelligence requires Pro`;else{let{cost:e,tokens:t}=oe(),n=t>0?` (${l(t)})`:``;a.textContent=`Saved: ${u(e)}${n}`,a.className=`daily-saved`,a.classList.toggle(`has-savings`,e>0),a.title=`Estimated daily savings from router intelligence`}let o=document.getElementById(`routerBadge`);if(o)if(e.premium){let t=e.routerMode||`off`;o.textContent={off:`Router Off`,shadow:`Router Shadow`,auto:`Router Auto`}[t]||`Router`,o.className=`router-badge router-badge--${t}`;let n=document.getElementById(`routerPopover`);if(n)for(let e of n.querySelectorAll(`.router-popover-opt`))e.classList.toggle(`active`,e.dataset.mode===t),e.classList.remove(`premium-locked`)}else{o.textContent=`Router Pro`,o.className=`router-badge premium-locked`;let e=document.getElementById(`routerPopover`);if(e)for(let t of e.querySelectorAll(`.router-popover-opt`))t.classList.add(`premium-locked`),t.classList.remove(`active`)}}function E(){let t=e.selectedReq===null?null:e.reqs[e.selectedReq],n=document.getElementById(`barInner`),r=document.getElementById(`barOuter`);if(!t){n.innerHTML=`<div class="bar-empty"><span>No data yet</span></div>`,r.className=`bar-outer`,document.getElementById(`barLegend`).innerHTML=``,document.getElementById(`barTotal`).textContent=`0 / 0`,document.getElementById(`barPct`).textContent=`0%`;return}let i=t.budget,a=t.actualUsage?t.actualUsage.input_tokens:t.totalEstimatedTokens,o=a/i*100;r.className=`bar-outer`+(o>95?` pressure-critical`:o>80?` pressure-high`:``);let u=1,d=0;if(o>0&&o<30){let e=(1-o/30)**2;u=(o+(65-o)*e)/o,d=e}let f=u>1,p=[];for(let e=0;e<t.segments.length;e++){let n=t.segments[e],r=s(n),a=n.tokens/i*100*u,o=p[p.length-1];o&&o.color===r&&a<.3?(o.tokens+=n.tokens,o.count++,o.endIndex=e):p.push({color:r,tokens:n.tokens,name:n.name,count:1,startIndex:e,endIndex:e})}let m=``;for(let e of p){let t=e.tokens/i*100*u;if(t<.1)continue;let n=e.count>1?`${e.name} (×${e.count})`:e.name;m+=`<div class="bar-segment" style="width:${t}%;background:linear-gradient(180deg,${e.color}cc,${e.color}88);border-right:1.5px solid var(--bg3)" title="${n}: ${l(e.tokens)} tokens" onclick="openModal(${e.startIndex})">`,t>5&&(m+=`<span>${t>15?n:l(e.tokens)}</span>`),m+=`</div>`}f&&(m+=`<div class="bar-break" style="opacity:${d}"></div>`),o<100&&(m+=`<div class="bar-empty"><span>${l(i-a)} free</span></div>`),n.innerHTML=m,r.querySelectorAll(`.bar-marker`).forEach(e=>{e.style.display=f?`none`:``});let h=new Map;for(let e of t.segments){let t=c(e),n=s(e);h.has(t)||h.set(t,n)}document.getElementById(`barLegend`).innerHTML=Array.from(h.entries()).map(([e,t])=>`<div class="legend-item"><div class="legend-dot" style="background:${t}"></div>${e}</div>`).join(``);let g=t.actualUsage?l(t.actualUsage.input_tokens):t.tokenCountSource===`count_tokens`?l(t.totalEstimatedTokens):`~${l(t.totalEstimatedTokens)}`,_=document.getElementById(`barTotal`),v=document.getElementById(`barPct`);_.textContent=`${g} / ${l(i)}`,_.style.color=o>95?`var(--red)`:o>80?`var(--orange)`:`var(--text)`,v.textContent=`${o.toFixed(1)}%`,v.style.color=o>95?`var(--red)`:o>80?`var(--orange)`:`var(--text3)`}function le(){let e=document.getElementById(`tokenChartContainer`),t=document.getElementById(`tokenChart`);if(!e||!t)return;let n=w();if(n.length<2){e.style.display=`none`;return}e.style.display=`block`;let r=n.map(e=>e.req.actualUsage?.input_tokens??e.req.totalEstimatedTokens??0),i=Math.max(...r),a=Math.min(...r),o=i-a||1;t.innerHTML=`
|
|
3
|
-
<svg viewBox="0 0 200 36" preserveAspectRatio="none" class="token-chart-svg">
|
|
4
|
-
<polyline
|
|
5
|
-
fill="none"
|
|
6
|
-
stroke="var(--cyan)"
|
|
7
|
-
stroke-width="2"
|
|
8
|
-
stroke-linecap="round"
|
|
9
|
-
stroke-linejoin="round"
|
|
10
|
-
points="${r.map((e,t)=>`${t/(r.length-1)*200},${36-(e-a)/o*32-2}`).join(` `)}"
|
|
11
|
-
/>
|
|
12
|
-
</svg>
|
|
13
|
-
<div class="token-chart-hint">${r.length} reqs · ${l(a)} → ${l(i)} tokens</div>
|
|
14
|
-
`}function D(){return`ANTHROPIC_BASE_URL=http://localhost:${location.port===`5173`?`4455`:location.port||`4455`} claude`}function ue(){navigator.clipboard.writeText(D()).then(()=>{let e=document.getElementById(`copyCommandBtn`);if(e){let t=e.textContent;e.textContent=`Copied!`,e.style.color=`var(--green)`,setTimeout(()=>{e.textContent=t,e.style.color=``},1500)}})}function O(){let t=document.getElementById(`reqList`),n=w();if(n.length===0){e.reqs.length===0?t.innerHTML=`<div class="empty"><div class="empty-icon waiting">🔍</div><h2>Waiting for requests...</h2><p>Start Claude Code with:<br><code style="color:var(--cyan);font-size:11px">${D()}</code> <button id="copyCommandBtn" class="copy-command-btn" onclick="copyClaudeCommand()" title="Copy to clipboard">Copy</button></p></div>`:t.innerHTML=`<div class="empty"><div class="empty-icon">🔍</div><h2>No requests in this session</h2></div>`;return}let r=document.getElementById(`viewToggleBtn`);r&&(r.textContent=e.groupView?`Grouped`:`Flat`,r.classList.toggle(`active`,e.groupView));let i=t.scrollTop;e.groupView&&Object.keys(e.groups).length>0?de(t,n):k(t,n),t.scrollTop=i}function k(e,t){let n=``;for(let e=t.length-1;e>=0;e--)n+=A(t[e].originalIndex,e+1);e.innerHTML=n}function A(t,n){let r=e.reqs[t],i=r.actualUsage?r.actualUsage.input_tokens:r.totalEstimatedTokens,a=Math.min(i/r.budget*100,100),o=a>95?`var(--red)`:a>80?`var(--orange)`:`var(--green)`,s=r.model.replace(`claude-`,``).replace(/-\d{8,}$/,``),c=r.actualUsage?l(r.actualUsage.input_tokens):r.tokenCountSource===`count_tokens`?l(r.totalEstimatedTokens):`~`+l(r.totalEstimatedTokens),d=``;r.actualUsage&&(d=`${l(r.actualUsage.input_tokens)} in / ${l(r.actualUsage.output_tokens)} out`);let f=r.actualCost?u(r.actualCost.totalCost):r.estimatedCost?`~`+u(r.estimatedCost.totalCost):``,p=`<div class="req-card${t===e.selectedReq?` selected`:``}" onclick="selectReq(${t})">`,m=n??r.turn;return p+=`<div class="req-card-head"><span class="req-label">Req ${m}</span><span class="req-tokens" style="color:${o}">${c}</span></div>`,p+=`<div class="req-mini-bar"><div class="req-mini-fill" style="width:${a}%;background:${o}"></div></div>`,p+=`<div class="req-meta"><span>${s}</span>`,d&&(p+=`<span class="req-io">${d}</span>`),f&&(p+=`<span class="req-cost-inline">${f}</span>`),p+=`</div>`,p+=`</div>`,p}function de(t,n){let r=new Set(n.map(e=>e.originalIndex)),i=new Map,a=0;for(let e of n)i.set(e.originalIndex,++a);let o=Object.keys(e.groups).map(Number).sort((e,t)=>t-e),s=``;for(let t of o){let n=e.groups[t],a=n.reqIndices.filter(e=>r.has(e));if(a.length===0)continue;let o=e.expandedGroups[t]!==!1,c=0,d=0;for(let t of a){let n=e.reqs[t];n&&(n.actualCost?c+=n.actualCost.totalCost:n.estimatedCost&&(c+=n.estimatedCost.totalCost),d+=n.actualUsage?.input_tokens??n.totalEstimatedTokens??0)}let f=a.length,p=t+1;s+=`<div class="group-card">`,s+=`<div class="group-header" onclick="toggleGroup(${t})">`,s+=`<span class="group-chevron ${o?`expanded`:``}">▶</span>`,s+=`<span class="group-title">Turn ${p}</span>`,s+=`<div class="group-summary">`,s+=`<span class="group-tokens">${l(d)}</span>`,s+=`<span class="group-cost">${u(c)}</span>`,s+=`<span class="group-req-count">${f} req${f===1?``:`s`}</span>`,s+=`</div></div>`,s+=`<div class="group-children ${o?``:`collapsed`}">`;let m={};for(let t of a){let n=e.reqs[t],r=n?.sessionHash||`unknown`;m[r]||(m[r]={reqIndices:[],model:n?.model||`unknown`}),m[r].reqIndices.push(t)}let h=Object.keys(m);if(h.length>1){let e=h.sort((e,t)=>m[t].reqIndices.length-m[e].reqIndices.length),t=0;for(let n of e){let e=m[n],r=e.model||`unknown`,a=t===0,o=a?`Main`:`Subagent`,c=a?`main`:`subagent`;s+=`<div class="group-session-label">`,s+=`<span class="session-pill ${c}">${o}</span>`,s+=`<span>${r} · ${e.reqIndices.length} req${e.reqIndices.length===1?``:`s`}</span>`,s+=`</div>`;for(let t=e.reqIndices.length-1;t>=0;t--)s+=A(e.reqIndices[t],i.get(e.reqIndices[t]));t++}}else for(let e=a.length-1;e>=0;e--)s+=A(a[e],i.get(a[e]));let g=new Date(n.startTime).toLocaleTimeString(),_=new Date(n.endTime).toLocaleTimeString(),v=g===_?g:`${g} – ${_}`;s+=`<div class="group-time">${v}</div>`,s+=`</div></div>`}t.innerHTML=s}function j(){let t=document.getElementById(`detailBody`),n=document.getElementById(`detailTitle`),r=document.getElementById(`detailMeta`);if(e.selectedReq===null||!e.reqs[e.selectedReq]){n.textContent=`Segment Breakdown`,r.textContent=``,t.innerHTML=`<div class="empty"><div class="empty-icon">📊</div><h2>No request selected</h2><p>Click a request on the left to see its context breakdown.</p></div>`;return}let i=e.reqs[e.selectedReq];n.textContent=`Req ${i.turn} — Segment Breakdown`,r.textContent=`${i.model} | ${i.segments.length} segments | ${i.messageCount} messages`;let a=``;a+=`<div class="stats-grid">`;let o=i.segments?.find(e=>e.type===`system`);if(o&&i.budget){let e=o.tokens/i.budget*100;e>15&&(a+=`<div class="warning-box">`,a+=`<div class="warning-box-title">System prompt is large</div>`,a+=`<div class="usage-row"><span class="usage-label">System prompt</span><span class="usage-value" style="color:var(--orange)">${l(o.tokens)} tokens (${e.toFixed(1)}% of context)</span></div>`,a+=`<div style="margin-top:6px;font-size:10px;color:var(--text3)">Consider trimming to free context for conversation.</div>`,a+=`</div>`)}if(i.filteringActive&&i.removedTools&&i.removedTools.length>0&&(a+=`<div class="filter-box">`,a+=`<div class="filter-box-title">Filtering Active</div>`,a+=`<div class="usage-row"><span class="usage-label">Original tools</span><span class="usage-value" style="color:var(--text2)">${i.originalToolCount}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">After filtering</span><span class="usage-value" style="color:var(--green)">${i.filteredToolCount}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Removed</span><span class="usage-value" style="color:var(--orange)">${i.removedTools.length} tools</span></div>`,i.tokensSaved&&(a+=`<div class="usage-row"><span class="usage-label">Tokens saved</span><span class="usage-value" style="color:var(--green)">~${l(i.tokensSaved)}</span></div>`),a+=`</div>`),!e.premium)a+=`<div class="router-box premium-locked">`,a+=`<div class="router-box-title">Router Intelligence</div>`,a+=`<div class="premium-locked-msg">Intelligent routing, savings analysis, and auto-filtering.<br>Available in Pro.</div>`,a+=`</div>`;else if(i.router){let e=i.router,t=e.mode===`shadow`?`Shadow (observe only)`:e.mode===`auto`?`Auto`:e.mode||`off`;if(a+=`<div class="router-box">`,a+=`<div class="router-box-title">Router Decision</div>`,a+=`<div class="usage-row"><span class="usage-label">Mode</span><span class="usage-value router-mode-${e.mode}">${t}</span></div>`,e.eligible){let t=e.mode===`shadow`;if(a+=`<div class="usage-row"><span class="usage-label">Matched by</span><span class="usage-value" style="color:var(--cyan)">${f(e.matched_by||`—`)}</span></div>`,e.confidence!=null){let t=e.confidence>=.9?`var(--green)`:e.confidence>=.7?`var(--amber)`:`var(--orange)`;a+=`<div class="usage-row"><span class="usage-label">Confidence</span><span class="usage-value" style="color:${t}">${(e.confidence*100).toFixed(0)}%</span></div>`}if(e.selected_groups&&e.selected_groups.length>0){let n=e.selected_groups.filter(e=>e!==`core`).join(`, `)||`—`;a+=`<div class="usage-row"><span class="usage-label">${t?`Would keep`:`Selected groups`}</span><span class="usage-value" style="color:var(--text2);font-size:10px">${f(n)}</span></div>`}if(e.stripped_groups&&e.stripped_groups.length>0&&(a+=`<div class="usage-row"><span class="usage-label">${t?`Would strip`:`Stripped groups`}</span><span class="usage-value" style="color:var(--text3);font-size:10px">${f(e.stripped_groups.join(`, `))}</span></div>`),e.estimated_tokens_saved>0){let n=i.totalEstimatedTokens>0?(e.estimated_tokens_saved/i.totalEstimatedTokens*100).toFixed(1):`?`;a+=`<div class="usage-row"><span class="usage-label">${t?`Potential savings`:`Est. savings`}</span><span class="usage-value" style="color:var(--green)">~${l(e.estimated_tokens_saved)} tokens (${n}%)</span></div>`}e.sticky_reused&&(a+=`<div style="margin-top:4px;font-size:9px;color:var(--purple)">Sticky route reused</div>`)}else{let t=e.skip_reason===`router_off`?`Router is off`:e.skip_reason===`below_threshold`?`Below threshold`:e.skip_reason===`no_request_data`?`No request data`:e.skip_reason||`Skipped`;a+=`<div class="usage-row"><span class="usage-label">Status</span><span class="usage-value" style="color:var(--text3)">${f(t)}</span></div>`}e.mode===`shadow`&&(a+=`<div class="router-shadow-note">All tools forwarded — shadow mode</div>`),a+=`</div>`}if(i.actualUsage){let e=i.actualUsage,t=e.cache_read_input_tokens||0,n=e.cache_creation_input_tokens||0,r=t>0||n>0,o=e.input_tokens-i.totalEstimatedTokens,s=e.input_tokens?(o/e.input_tokens*100).toFixed(1):`0.0`;if(a+=`<div class="usage-box">`,a+=`<div class="usage-row"><span class="usage-label">Estimated input</span><span class="usage-value estimated">~${i.totalEstimatedTokens.toLocaleString()}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Actual input</span><span class="usage-value actual">${e.input_tokens.toLocaleString()}</span></div>`,r){a+=`<div class="usage-row"><span class="usage-label" style="padding-left:12px">Cache read</span><span class="usage-value" style="color:var(--green)">${t.toLocaleString()}</span></div>`,a+=`<div class="usage-row"><span class="usage-label" style="padding-left:12px">Cache write</span><span class="usage-value" style="color:var(--cyan,var(--blue))">${n.toLocaleString()}</span></div>`;let r=Math.max(0,e.input_tokens-t-n);a+=`<div class="usage-row"><span class="usage-label" style="padding-left:12px">Uncached</span><span class="usage-value">${r.toLocaleString()}</span></div>`}a+=`<div class="usage-row"><span class="usage-label">Estimation error</span><span class="usage-value" style="color:${Math.abs(parseFloat(s))<15?`var(--green)`:`var(--orange)`}">${o>0?`+`:``}${o.toLocaleString()} (${s}%)</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Output tokens</span><span class="usage-value">${e.output_tokens.toLocaleString()}</span></div>`,i.actualCost&&(a+=`<div style="border-top:1px solid var(--border);margin-top:6px;padding-top:6px">`,a+=`<div class="usage-row"><span class="usage-label">Input cost</span><span class="usage-value" style="color:var(--amber)">${u(i.actualCost.inputCost)}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Output cost</span><span class="usage-value" style="color:var(--amber)">${u(i.actualCost.outputCost)}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Total cost</span><span class="usage-value" style="color:var(--amber);font-size:13px">${u(i.actualCost.totalCost)}</span></div>`,a+=`</div>`),a+=`</div>`}else if(i.estimatedCost){let e=i.tokenCountSource===`count_tokens`;a+=`<div class="usage-box">`,a+=`<div class="usage-row"><span class="usage-label">Input tokens ${e?`(exact)`:`(est.)`}</span><span class="usage-value" style="color:${e?`var(--green)`:`var(--text2)`}">${e?``:`~`}${i.totalEstimatedTokens.toLocaleString()}</span></div>`,a+=`<div class="usage-row"><span class="usage-label">Input cost ${e?``:`(est.)`}</span><span class="usage-value" style="color:${e?`var(--amber)`:`var(--text3)`}">${e?``:`~`}${u(i.estimatedCost.totalCost)}</span></div>`,e&&(a+=`<div style="margin-top:4px;font-size:9px;color:var(--text3)">via count_tokens API</div>`),a+=`</div>`}if(i.toolsUsed&&i.toolsUsed.length>0){a+=`<div class="usage-box">`,a+=`<div style="font-size:10px;font-weight:700;color:var(--cyan);margin-bottom:4px">Tools Used (${i.toolsUsed.length})</div>`;for(let e of i.toolsUsed)a+=`<div style="font-size:10px;color:var(--text2);padding:1px 0">${f(e)}</div>`;a+=`</div>`}a+=`</div>`;for(let e=0;e<i.segments.length;e++){let t=i.segments[e],n=s(t),r=(t.tokens/i.totalEstimatedTokens*100).toFixed(1),o=Math.min(r,100),c=t.preview?t.preview.slice(0,80).replace(/\n/g,` `):``,u=t.index===void 0?`#${e}`:`msg #${t.index}`;a+=`<div class="segment-row" onclick="openModal(${e})">`,a+=`<div class="seg-color" style="background:${n}"></div>`,a+=`<div class="seg-info">`,a+=`<div class="seg-name" style="color:${n}">${t.name} <span style="color:var(--text3);font-size:10px;font-weight:400">${u}</span></div>`,a+=`<div class="seg-sub">${f(c)}${t.charLength>80?`...`:``}</div>`,a+=`</div>`,a+=`<div class="seg-bar"><div class="seg-bar-fill" style="width:${o}%;background:${n}"></div></div>`,a+=`<div class="seg-pct">${r}%</div>`,a+=`<div class="seg-tokens">${l(t.tokens)}</div>`,a+=`<span class="seg-expand-hint">View</span>`,a+=`</div>`}t.innerHTML=a}async function fe(e,t){return(await fetch(`/api/content/${e}/${t}`)).json()}async function pe(e,t,n){return(await fetch(`/api/profiles`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({name:e,mode:t,tools:n})})).json()}function M(e){R({type:`set_active_profile`,profile:e})}async function N(t,n,r){try{let i=await pe(t,n,r);return i.success&&(e.profiles[t]=i.profile,P(),M(t)),i}catch(e){return console.error(`Failed to save profile:`,e),{error:e.message}}}function P(){let t=document.getElementById(`profileSelect`),n=document.getElementById(`filterBadge`),r=e.activeProfile;t.innerHTML=``;for(let n of Object.keys(e.profiles)){let e=document.createElement(`option`);e.value=n,e.textContent=n,n===r&&(e.selected=!0),t.appendChild(e)}let i=r!==`All Tools`;t.className=`profile-select`+(i?` filtering`:``),n.style.display=i?`flex`:`none`}function F(t,n){let r=n.groupId;if(r==null)return;if(!e.groups[r]){e.groups[r]={id:r,reqIndices:[],sessions:{},startTime:n.timestamp,endTime:n.timestamp};for(let t of Object.keys(e.expandedGroups))e.expandedGroups[t]=!1;e.expandedGroups[r]=!0}let i=e.groups[r];i.reqIndices.push(t),i.endTime=Math.max(i.endTime,n.timestamp);let a=n.sessionHash||`unknown`;i.sessions[a]||(i.sessions[a]={reqIndices:[],model:n.model}),i.sessions[a].reqIndices.push(t)}function I(){e.groups={},e.expandedGroups={};for(let t=0;t<e.reqs.length;t++)F(t,e.reqs[t]);let t=Object.keys(e.groups).map(Number);if(t.length>0){let n=Math.max(...t);for(let r of t)e.expandedGroups[r]=r===n}}var L;function R(e){L&&L.readyState===1&&L.send(JSON.stringify(e))}function z(){let t=location.protocol===`https:`?`wss:`:`ws:`,n=location.port===`5173`?`localhost:4455`:location.host;L=new WebSocket(`${t}//${n}`),L.onopen=()=>{e.connected=!0,T()},L.onclose=()=>{e.connected=!1,T(),setTimeout(z,2e3)},L.onmessage=t=>{let n=JSON.parse(t.data);if(n.type===`connected`){e.premium=!!n.premium,n.profiles&&(e.profiles=n.profiles),n.activeProfile&&(e.activeProfile=n.activeProfile),n.routerMode!=null&&(e.routerMode=n.routerMode),P(),C();return}if(n.type===`request`){if(n.toolsUsed&&n.toolsUsed.length&&n.toolsUsed.forEach(t=>e.toolsUsed.add(t)),e.reqs.push(n),n.sessionId&&n.sessionPath){let t=n.sessionPath;e.sessions[t]?e.sessions[t].sessionIds.includes(n.sessionId)||e.sessions[t].sessionIds.push(n.sessionId):e.sessions[t]={id:t,label:n.sessionLabel||n.sessionId,path:n.sessionPath||null,sessionIds:[n.sessionId],firstSeen:n.timestamp},n.tabKey=t}e.reqs.length>50?(e.reqs.splice(0,e.reqs.length-50),I()):F(e.reqs.length-1,n);let t=e.selectedReq!==null;t||(e.selectedReq=e.reqs.length-1),C({skipDetail:t}),b(e)}if(n.type===`token_count_update`){let t=e.reqs.find(e=>e.turn===n.turn);t&&(t.exactInputTokens=n.exactInputTokens,t.segments=n.segments,t.totalEstimatedTokens=n.exactInputTokens,t.estimatedCost=n.estimatedCost,t.tokenCountSource=`count_tokens`,C({skipDetail:!(e.selectedReq!==null&&e.reqs[e.selectedReq]?.turn===n.turn)}),b(e))}if(n.type===`response_complete`){let t=n.turn==null?e.reqs[e.reqs.length-1]:e.reqs.find(e=>e.turn===n.turn);t&&(t.actualUsage=n.usage,t.stopReason=n.stopReason,n.cost&&(t.actualCost=n.cost,re(n.cost.totalCost)),n.toolsUsed&&(t.toolsUsed=n.toolsUsed,n.toolsUsed.forEach(t=>e.toolsUsed.add(t))),C({skipDetail:!(e.selectedReq!==null&&e.reqs[e.selectedReq]?.turn===n.turn)}),b(e))}if(n.type===`router_decision`){let t=e.reqs.find(e=>e.turn===n.turn);if(t){let r=!t.router;if(t.router={mode:n.mode,eligible:n.eligible,skip_reason:n.skip_reason,matched_by:n.matched_by,confidence:n.confidence,selected_groups:n.selected_groups,stripped_groups:n.stripped_groups,estimated_tokens_saved:n.estimated_tokens_saved,sticky_reused:n.sticky_reused},r&&n.estimated_tokens_saved>0){let e=d(t.model);ae(n.estimated_tokens_saved/1e6*e*.1,n.estimated_tokens_saved)}e.selectedReq!==null&&e.reqs[e.selectedReq]?.turn===n.turn&&j(),b(e)}}n.type===`router_mode_changed`&&(e.routerMode=n.mode,T()),n.type===`profiles_updated`&&(e.profiles=n.profiles||{},e.activeProfile=n.active||`All Tools`,P()),n.type===`active_profile_changed`&&(e.activeProfile=n.active||`All Tools`,P())}}async function B(n){let r=e.reqs[e.selectedReq];if(!r)return;let i=r.segments[n];if(!i)return;t.segment=i,t.segIndex=n,t.view=i.type===`tools`?`tools`:`formatted`,t.fullContent=``,t.loading=!0,t.parsedTools=null;let a=s(i);document.getElementById(`modalColorBar`).style.background=a,document.getElementById(`modalTitle`).textContent=i.name,document.getElementById(`modalMeta`).textContent=`${c(i)}${i.role?` (`+i.role+`)`:``}${i.count?` — `+i.count+` tools`:``}`;let o=`
|
|
15
|
-
<div class="modal-stat"><div class="modal-stat-value" style="color:${a}">${l(i.tokens)}</div><div class="modal-stat-label">Tokens</div></div>
|
|
16
|
-
<div class="modal-stat"><div class="modal-stat-value">${(i.charLength||0).toLocaleString()}</div><div class="modal-stat-label">Chars</div></div>
|
|
17
|
-
<div class="modal-stat"><div class="modal-stat-value">${(i.tokens/r.totalEstimatedTokens*100).toFixed(1)}%</div><div class="modal-stat-label">Of total</div></div>
|
|
18
|
-
`;document.getElementById(`modalStats`).innerHTML=o;let u=document.getElementById(`toolsViewBtn`);u.style.display=i.type===`tools`?`inline`:`none`,document.querySelectorAll(`.modal-toolbar button`).forEach(e=>e.classList.remove(`active`)),i.type===`tools`?u.classList.add(`active`):document.getElementById(`formattedBtn`).classList.add(`active`),document.getElementById(`modalSearch`).value=``,document.getElementById(`modalBody`).innerHTML=`<div style="display:flex;align-items:center;justify-content:center;height:200px;color:var(--text3);font-size:13px"><div style="text-align:center"><div style="font-size:24px;margin-bottom:8px;animation:pulse 1s ease-in-out infinite">⏳</div>Loading full content...</div></div>`,document.getElementById(`modalOverlay`).classList.add(`open`);try{let e=await fe(r.turn,n);if(e.content){t.fullContent=e.content;let n=document.querySelector(`.modal-stat:nth-child(2) .modal-stat-value`);if(n&&(n.textContent=e.content.length.toLocaleString()),i.type===`tools`)try{t.parsedTools=JSON.parse(e.content)}catch{t.parsedTools=null}}else t.fullContent=i.preview||`(content no longer available — request may have been evicted)`}catch(e){console.error(`Failed to fetch segment content:`,e),t.fullContent=i.preview||`(failed to load content)`}t.loading=!1,U()}function V(){document.getElementById(`modalOverlay`).classList.remove(`open`),t.segment=null,t.parsedTools=null}function H(e,n){t.view=e,document.querySelectorAll(`.modal-toolbar button`).forEach(e=>e.classList.remove(`active`)),n&&n.classList.add(`active`),U()}function U(){let e=document.getElementById(`modalBody`),n=t.fullContent;if(t.loading)return;if(t.view===`tools`&&t.parsedTools){W(e);return}if(t.view===`raw`){e.innerHTML=`<div class="modal-content" style="white-space:pre-wrap;word-break:break-all">${f(n)}</div>`;return}let r=n;try{let e=JSON.parse(n);r=JSON.stringify(e,null,2)}catch{}let i=r.split(`
|
|
19
|
-
`),a=`<div class="modal-content">`;for(let e=0;e<i.length;e++)a+=`<span class="line"><span class="line-num">${e+1}</span>${f(i[e])}</span>`;a+=`</div>`,e.innerHTML=a}function W(n){let r=t.parsedTools;if(!r||!r.length){n.innerHTML=`<div style="padding:20px;color:var(--text3)">No tools found</div>`;return}let i=e.profiles[e.activeProfile],a=e.activeProfile===`All Tools`,s=h(r),c=`<div class="modal-tools">`,u=r.filter(e=>p(e.name,i,a)).length;c+=`<div class="modal-tools-header">`,c+=`<div class="modal-tools-header-left"><strong>${u}</strong> of <strong>${r.length}</strong> tools enabled</div>`,c+=`<div style="display:flex;gap:6px">`,c+=`<button class="btn-secondary" onclick="toggleAllTools(true)" style="padding:3px 8px;border-radius:4px;font-size:10px">All</button>`,c+=`<button class="btn-secondary" onclick="toggleAllTools(false)" style="padding:3px 8px;border-radius:4px;font-size:10px">None</button>`,c+=`</div></div>`;let d=[],m=[];for(let[e,t]of s)e===`other`?m.push([e,t]):d.push([e,t]);function g(t){for(let[n,r]of t){let t=[...r].sort((e,t)=>o(t)-o(e)),s=n===`other`?`Other`:n.charAt(0).toUpperCase()+n.slice(1),u=t.reduce((e,t)=>e+o(t),0),d=t.filter(e=>p(e.name,i,a)).length,m=d===t.length;c+=`<div class="tool-group" data-server="${f(n)}">`,c+=`<div class="tool-group-header">`,c+=`<input type="checkbox" class="tool-group-checkbox" data-server="${f(n)}" ${m?`checked`:``} onclick="event.stopPropagation(); toggleGroupCheckbox('${f(n)}', this.checked)">`,c+=`<span class="tool-group-chevron" onclick="toggleGroupAccordion('${f(n)}')">▶</span>`,c+=`<div class="tool-group-title" onclick="toggleGroupAccordion('${f(n)}')">`,c+=`<span class="tool-group-name">${f(s)}</span>`,c+=`<span class="tool-group-meta">${d}/${t.length} tools · ~${l(u)} tok</span>`,c+=`</div>`,c+=`<div class="tool-group-actions">`,c+=`<button class="btn-secondary" onclick="event.stopPropagation(); toggleGroupTools('${f(n)}', true)" style="padding:2px 6px;font-size:9px">All</button>`,c+=`<button class="btn-secondary" onclick="event.stopPropagation(); toggleGroupTools('${f(n)}', false)" style="padding:2px 6px;font-size:9px">None</button>`,c+=`</div></div>`,c+=`<div class="tool-group-body collapsed">`;for(let r of t){let t=p(r.name,i,a),s=o(r),u=(r.description||``).slice(0,120),d=!e.toolsUsed?.has(r.name);c+=`<div class="tool-card" data-tool-name="${f(r.name)}">`,c+=`<input type="checkbox" data-tool="${f(r.name)}" data-server="${f(n)}" ${t?`checked`:``} onchange="onToolToggle()">`,c+=`<div class="tool-card-info">`,c+=`<div class="tool-card-name">${f(r.name)}${d?` <span class="never-used-tag" title="Not used this session">never used</span>`:``}</div>`,u&&(c+=`<div class="tool-card-desc">${f(u)}</div>`),c+=`</div>`,c+=`<div class="tool-card-tokens">~${l(s)} tok</div>`,c+=`</div>`}c+=`</div></div>`}}d.length>0&&(c+=`<div class="tool-section-header">MCP Servers</div>`,g(d)),m.length>0&&(c+=`<div class="tool-section-header">Other Tools</div>`,g(m)),c+=`<div id="toolsSavings"></div>`;let _=e.reqs[e.selectedReq]?.toolsUsed||[];c+=`<div class="save-profile-bar">`,c+=`<input type="text" id="profileNameInput" placeholder="Profile name..." value="">`,c+=`<button class="btn-primary" onclick="saveCurrentAsProfile()">Save as Profile</button>`,_.length>0&&(c+=`<button class="btn-secondary" onclick="createProfileFromThisTurn()" title="Create profile from tools used in this request">From this turn</button>`),c+=`</div>`,c+=`</div>`,n.innerHTML=c,G()}function me(e){let t=document.querySelector(`.tool-group[data-server="${e}"]`);if(!t)return;let n=t.querySelector(`.tool-group-body`),r=t.querySelector(`.tool-group-chevron`);n&&n.classList.toggle(`collapsed`),r&&r.classList.toggle(`expanded`)}function he(e,t){document.querySelectorAll(`.modal-tools input[type="checkbox"][data-server="${e}"]:not(.tool-group-checkbox)`).forEach(e=>{e.checked=t}),G()}function ge(){document.querySelectorAll(`.tool-group-checkbox`).forEach(e=>{let t=e.dataset.server,n=document.querySelectorAll(`.modal-tools input[type="checkbox"][data-server="${t}"]:not(.tool-group-checkbox)`),r=n.length,i=0;n.forEach(e=>{e.checked&&i++}),e.checked=i===r,e.indeterminate=i>0&&i<r;let a=e.closest(`.tool-group`)?.querySelector(`.tool-group-meta`);if(a){let e=a.textContent.match(/·.*$/);a.textContent=`${i}/${r} tools ${e?e[0]:``}`}})}function _e(e,t){document.querySelectorAll(`.modal-tools input[data-server="${e}"]:not(.tool-group-checkbox)`).forEach(e=>{e.checked=t}),G()}function ve(e){document.querySelectorAll(`.modal-tools input[type="checkbox"]:not(.tool-group-checkbox)`).forEach(t=>{t.checked=e}),G()}function G(){let e=document.querySelectorAll(`.modal-tools input[type="checkbox"]:not(.tool-group-checkbox)`),n=t.parsedTools;if(!n||!e.length)return;let r=0,i=0,a=0;e.forEach(e=>{let t=n.find(t=>t.name===e.dataset.tool);if(!t)return;let s=o(t);e.checked?(r+=s,a++):i+=s}),ge();let s=document.getElementById(`toolsSavings`);s&&i>0?s.innerHTML=`<div class="savings-banner">Disabling ${n.length-a} tools saves ~${l(i)} tokens per request</div>`:s&&(s.innerHTML=``);let c=document.querySelector(`.modal-tools-header-left`);c&&(c.innerHTML=`<strong>${a}</strong> of <strong>${n.length}</strong> tools enabled`)}async function ye(){let t=e.reqs[e.selectedReq],n=t?.toolsUsed||[];if(n.length!==0&&(await N(`Req ${t?.turn??0} tools`,`allowlist`,n)).success){let e=document.querySelector(`.save-profile-bar`);if(e){let t=e.style.background;e.style.background=`rgba(52,211,153,0.1)`,e.style.borderColor=`rgba(52,211,153,0.3)`,setTimeout(()=>{e.style.background=t,e.style.borderColor=``},1500)}}}async function be(){let e=document.getElementById(`profileNameInput`),t=(e?.value||``).trim();if(!t){e.style.borderColor=`var(--red)`,e.focus(),setTimeout(()=>e.style.borderColor=``,1500);return}let n=document.querySelectorAll(`.modal-tools input[type="checkbox"]:not(.tool-group-checkbox)`),r=[];if(n.forEach(e=>{e.checked&&r.push(e.dataset.tool)}),(await N(t,`allowlist`,r)).success){e.value=``;let t=document.querySelector(`.save-profile-bar`);if(t){let e=t.style.background;t.style.background=`rgba(16,185,129,0.1)`,t.style.borderColor=`rgba(16,185,129,0.3)`,setTimeout(()=>{t.style.background=e,t.style.borderColor=``},1500)}}}function K(){let e=document.getElementById(`modalSearch`).value.trim().toLowerCase(),n=document.getElementById(`modalBody`);if(!e){U();return}if(t.view===`tools`){document.querySelectorAll(`.tool-group`).forEach(t=>{let n=0;t.querySelectorAll(`.tool-card`).forEach(t=>{let r=t.querySelector(`.tool-card-name`)?.textContent?.toLowerCase()||``,i=t.querySelector(`.tool-card-desc`)?.textContent?.toLowerCase()||``,a=r.includes(e)||i.includes(e);t.style.display=a?`flex`:`none`,a&&n++}),t.style.display=n>0?`block`:`none`;let r=t.querySelector(`.tool-group-body`),i=t.querySelector(`.tool-group-chevron`);n>0?(r?.classList.remove(`collapsed`),i?.classList.add(`expanded`)):(r?.classList.add(`collapsed`),i?.classList.remove(`expanded`))});return}let r=t.fullContent;try{r=JSON.stringify(JSON.parse(r),null,2)}catch{}let i=r.split(`
|
|
20
|
-
`),a=`<div class="modal-content">`;for(let t=0;t<i.length;t++){let n=i[t],r=n.toLowerCase().indexOf(e);if(r!==-1){let i=f(n.slice(0,r)),o=f(n.slice(r,r+e.length)),s=f(n.slice(r+e.length));a+=`<span class="line"><span class="line-num">${t+1}</span>${i}<span class="highlight">${o}</span>${s}</span>`}else a+=`<span class="line" style="opacity:0.25"><span class="line-num">${t+1}</span>${f(n)}</span>`}a+=`</div>`,n.innerHTML=a}function q(){navigator.clipboard.writeText(t.fullContent).then(()=>{let e=document.getElementById(`copyBtn`),t=e.textContent;e.textContent=`Copied!`,e.style.color=`var(--green)`,setTimeout(()=>{e.textContent=t,e.style.color=``},1500)})}var J=`jannal_theme`;function Y(){return localStorage.getItem(J)||`dark`}function X(e){e===`light`?document.documentElement.setAttribute(`data-theme`,`light`):document.documentElement.removeAttribute(`data-theme`);let t=document.getElementById(`themeIconMoon`),n=document.getElementById(`themeIconSun`);t&&n&&(t.style.display=e===`dark`?`block`:`none`,n.style.display=e===`light`?`block`:`none`)}function xe(){let e=Y()===`dark`?`light`:`dark`;return localStorage.setItem(J,e),document.documentElement.classList.add(`theme-transitioning`),X(e),setTimeout(()=>document.documentElement.classList.remove(`theme-transitioning`),350),e}function Se(){X(Y())}function Z(t){e.selectedReq=t,E(),O(),j(),b(e)}function Q(){e.reqs=[],e.selectedReq=null,e.groups={},e.expandedGroups={},e.sessions={},e.activeSessionTab=null,C(),b(e)}function Ce(t){e.activeSessionTab=t;let n=null;for(let r=e.reqs.length-1;r>=0;r--)if(t===null||e.reqs[r].tabKey===t){n=r;break}e.selectedReq=n,C(),b(e)}function we(t){delete e.sessions[t],e.activeSessionTab===t&&(e.activeSessionTab=null,e.selectedReq=e.reqs.length>0?e.reqs.length-1:null),C(),b(e)}function Te(){e.groupView=!e.groupView,O(),b(e)}function Ee(t){e.expandedGroups[t]=!e.expandedGroups[t],O()}function De(){e.reqs.length!==0&&document.getElementById(`exportMenu`)?.classList.toggle(`open`)}function Oe(){e.reqs.length!==0&&(S(ne(e),`jannal-session-${new Date().toISOString().slice(0,19).replace(/[:-]/g,``)}.json`),document.getElementById(`exportMenu`)?.classList.remove(`open`))}function ke(){e.reqs.length!==0&&(S(x(e),`jannal-session-${new Date().toISOString().slice(0,19).replace(/[:-]/g,``)}.csv`),document.getElementById(`exportMenu`)?.classList.remove(`open`))}window.openModal=B,window.closeModal=V,window.setModalView=H,window.selectReq=Z,window.clearReqs=Q,window.toggleGroup=Ee,window.onProfileChange=M,window.toggleAllTools=ve,window.toggleGroupTools=_e,window.toggleGroupAccordion=me,window.toggleGroupCheckbox=he,window.onToolToggle=G,window.saveCurrentAsProfile=be,window.createProfileFromThisTurn=ye,window.copyClaudeCommand=ue,window.selectSessionTab=Ce,window.dismissSessionTab=we,window.filterModalContent=K,window.copyModalContent=q;var $=null;function Ae(t){let n=document.getElementById(`globalSearchResults`);if(!t||t.length<2){n.classList.remove(`open`),n.innerHTML=``;return}clearTimeout($),$=setTimeout(async()=>{try{let r=await(await fetch(`/api/search?q=${encodeURIComponent(t)}`)).json();if(r.results.length===0){n.innerHTML=`<div class="search-no-results">No matches found</div>`,n.classList.add(`open`);return}let i=t.toLowerCase();n.innerHTML=r.results.map(t=>{let n=e.reqs.findIndex(e=>e.turn===t.turnId),r=n>=0?`Req ${e.reqs[n].turn}`:`Req ${t.turnId}`,a=t.snippet.replace(/</g,`<`).replace(/>/g,`>`).replace(RegExp(`(${i.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)})`,`gi`),`<mark>$1</mark>`);return`<div class="search-result-item" data-turn="${n}" data-seg="${t.segIndex}">
|
|
21
|
-
<div class="search-result-turn">${r} · segment ${t.segIndex}</div>
|
|
22
|
-
<div class="search-result-snippet">${a}</div>
|
|
23
|
-
</div>`}).join(``),n.classList.add(`open`)}catch(e){console.error(`Search error:`,e)}},250)}document.getElementById(`themeToggle`).addEventListener(`click`,()=>{xe(),C()}),document.getElementById(`profileSelect`).addEventListener(`change`,e=>{M(e.target.value)}),document.getElementById(`clearBtn`).addEventListener(`click`,Q),document.getElementById(`viewToggleBtn`).addEventListener(`click`,Te),document.getElementById(`exportBtn`).addEventListener(`click`,De),document.getElementById(`exportMenu`)?.addEventListener(`click`,e=>{let t=e.target.closest(`.export-option`);t&&(t.dataset.format===`json`?Oe():t.dataset.format===`csv`&&ke())}),document.addEventListener(`click`,e=>{let t=document.getElementById(`exportMenu`),n=document.querySelector(`.export-dropdown`);t?.classList.contains(`open`)&&n&&!n.contains(e.target)&&t.classList.remove(`open`);let r=document.querySelector(`.router-badge-wrapper`),i=document.getElementById(`routerPopover`);i?.classList.contains(`open`)&&r&&!r.contains(e.target)&&i.classList.remove(`open`)}),document.getElementById(`routerBadge`)?.addEventListener(`click`,e=>{e.stopPropagation(),document.getElementById(`routerPopover`)?.classList.toggle(`open`)}),document.getElementById(`routerPopover`)?.addEventListener(`click`,async t=>{let n=t.target.closest(`.router-popover-opt`);if(!n||!e.premium)return;let r=n.dataset.mode;if(r===e.routerMode){document.getElementById(`routerPopover`)?.classList.remove(`open`);return}try{(await fetch(`/api/router/mode`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({mode:r})})).ok&&(e.routerMode=r,T())}catch(e){console.error(`Failed to set router mode:`,e)}document.getElementById(`routerPopover`)?.classList.remove(`open`)}),document.getElementById(`modalOverlay`).addEventListener(`click`,e=>{e.target===e.currentTarget&&V()}),document.getElementById(`modalCloseBtn`).addEventListener(`click`,V),document.getElementById(`formattedBtn`).addEventListener(`click`,e=>{H(`formatted`,e.target)}),document.getElementById(`rawBtn`).addEventListener(`click`,e=>{H(`raw`,e.target)}),document.getElementById(`toolsViewBtn`).addEventListener(`click`,e=>{H(`tools`,e.target)}),document.getElementById(`modalSearch`).addEventListener(`input`,K),document.getElementById(`copyBtn`).addEventListener(`click`,q),document.getElementById(`globalSearch`).addEventListener(`input`,e=>{Ae(e.target.value.trim())}),document.getElementById(`globalSearchResults`).addEventListener(`click`,e=>{let t=e.target.closest(`.search-result-item`);if(!t)return;let n=parseInt(t.dataset.turn),r=parseInt(t.dataset.seg);n>=0&&(Z(n),B(r)),document.getElementById(`globalSearchResults`).classList.remove(`open`),document.getElementById(`globalSearch`).value=``}),document.addEventListener(`click`,e=>{let t=document.querySelector(`.global-search-wrapper`);t&&!t.contains(e.target)&&document.getElementById(`globalSearchResults`).classList.remove(`open`)}),document.addEventListener(`keydown`,e=>{e.key===`Escape`&&(document.getElementById(`globalSearchResults`).classList.remove(`open`),V())}),Se(),te(e),I(),z(),C();
|