@buzzie-ai/jannal 0.3.0 → 0.5.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/README.md +13 -11
- package/package.json +4 -3
- package/profiles.json +72 -0
- package/public/assets/index-B4-LdibW.css +1 -0
- package/public/assets/index-BhRfrW6t.js +35 -0
- package/public/index.html +55 -2
- package/server.js +208 -4
- package/public/assets/index-B8dfyj9-.css +0 -1
- package/public/assets/index-CzXZ1AkJ.js +0 -23
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Jannal
|
|
2
2
|
|
|
3
|
+
**/jun-nul/** — Tamil for "window".
|
|
4
|
+
|
|
3
5
|
**See what's eating your context window. Then fix it.**
|
|
4
6
|
|
|
5
7
|
Jannal sits between your AI tools and the Anthropic API. It intercepts every request, visualizes how your context window is being used, and lets you filter out tools you don't need — saving tokens and money.
|
|
@@ -8,16 +10,6 @@ Works with Claude Code and any tool that speaks the Anthropic Messages API. [Cur
|
|
|
8
10
|
|
|
9
11
|

|
|
10
12
|
|
|
11
|
-

|
|
12
|
-
|
|
13
|
-
## What it does
|
|
14
|
-
|
|
15
|
-
**Inspect** — Watch every API request in real time. See exactly how many tokens go to the system prompt, tool definitions, conversation history, and tool results. The context bar shows you at a glance where your tokens are going.
|
|
16
|
-
|
|
17
|
-
**Cost tracking** — See the cost of every turn, with per-model pricing (Opus, Sonnet, Haiku). Session cost accumulates in the header so you always know what you're spending. Uses the official `count_tokens` API for accurate counts before the response even finishes.
|
|
18
|
-
|
|
19
|
-
**Filter tools** — The killer feature. If you're running Claude Code with 40+ MCP tools defined, half of them are probably irrelevant to what you're doing right now. Jannal strips them from the request before it hits the API. Create named profiles ("Coding Only", "Browser Automation") and switch between them from the UI.
|
|
20
|
-
|
|
21
13
|
## Quick start
|
|
22
14
|
|
|
23
15
|
```bash
|
|
@@ -46,6 +38,16 @@ ANTHROPIC_BASE_URL=http://localhost:4455 your-tool
|
|
|
46
38
|
|
|
47
39
|
Open `http://localhost:4455` in your browser to see the Inspector.
|
|
48
40
|
|
|
41
|
+
[](https://youtu.be/AI1q4yKAxV8)
|
|
42
|
+
|
|
43
|
+
## What it does
|
|
44
|
+
|
|
45
|
+
**Inspect** — Watch every API request in real time. See exactly how many tokens go to the system prompt, tool definitions, conversation history, and tool results. The context bar shows you at a glance where your tokens are going.
|
|
46
|
+
|
|
47
|
+
**Cost tracking** — See the cost of every turn, with per-model pricing (Opus, Sonnet, Haiku). Session cost accumulates in the header so you always know what you're spending. Uses the official `count_tokens` API for accurate counts before the response even finishes.
|
|
48
|
+
|
|
49
|
+
**Filter tools** — The killer feature. If you're running Claude Code with 40+ MCP tools defined, half of them are probably irrelevant to what you're doing right now. Jannal strips them from the request before it hits the API. Create named profiles ("Coding Only", "Browser Automation") and switch between them from the UI.
|
|
50
|
+
|
|
49
51
|
## How it works
|
|
50
52
|
|
|
51
53
|
```
|
|
@@ -151,7 +153,7 @@ Open `http://localhost:5173` for the dev UI (auto-proxies API calls to the serve
|
|
|
151
153
|
```
|
|
152
154
|
jannal/
|
|
153
155
|
├── server.js # Proxy server, createServer() factory, plugin hooks
|
|
154
|
-
├── bin/jannal.js # CLI entry point (npx jannal)
|
|
156
|
+
├── bin/jannal.js # CLI entry point (npx @buzzie-ai/jannal)
|
|
155
157
|
├── lib/
|
|
156
158
|
│ ├── plugins.js # Plugin host (lifecycle hooks, route handling)
|
|
157
159
|
│ └── tokens.js # Token estimation, budget inference
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buzzie-ai/jannal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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/",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"profiles.json"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
+
"dev": "vite",
|
|
20
21
|
"dev:server": "node server.js",
|
|
21
22
|
"dev:ui": "vite",
|
|
22
23
|
"build": "vite build",
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"license": "MIT",
|
|
39
40
|
"repository": {
|
|
40
41
|
"type": "git",
|
|
41
|
-
"url": "https://github.com/Buzzie-AI/jannal"
|
|
42
|
+
"url": "git+https://github.com/Buzzie-AI/jannal.git"
|
|
42
43
|
},
|
|
43
44
|
"engines": {
|
|
44
45
|
"node": ">=18.0.0"
|
package/profiles.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"profiles": {
|
|
3
|
+
"All Tools": {
|
|
4
|
+
"name": "All Tools",
|
|
5
|
+
"mode": "allowlist",
|
|
6
|
+
"tools": []
|
|
7
|
+
},
|
|
8
|
+
"Simple": {
|
|
9
|
+
"name": "Simple",
|
|
10
|
+
"mode": "allowlist",
|
|
11
|
+
"tools": [
|
|
12
|
+
"Bash",
|
|
13
|
+
"Agent",
|
|
14
|
+
"AskUserQuestion",
|
|
15
|
+
"EnterPlanMode",
|
|
16
|
+
"TaskUpdate",
|
|
17
|
+
"Grep",
|
|
18
|
+
"CronCreate",
|
|
19
|
+
"TaskCreate",
|
|
20
|
+
"ExitPlanMode",
|
|
21
|
+
"Read",
|
|
22
|
+
"ExitWorktree",
|
|
23
|
+
"WebSearch",
|
|
24
|
+
"WebFetch",
|
|
25
|
+
"Edit",
|
|
26
|
+
"Skill",
|
|
27
|
+
"EnterWorktree",
|
|
28
|
+
"NotebookEdit",
|
|
29
|
+
"TaskList",
|
|
30
|
+
"Glob",
|
|
31
|
+
"Write",
|
|
32
|
+
"TaskGet",
|
|
33
|
+
"TaskOutput",
|
|
34
|
+
"TaskStop",
|
|
35
|
+
"CronDelete",
|
|
36
|
+
"CronList"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"No tools": {
|
|
40
|
+
"name": "No tools",
|
|
41
|
+
"mode": "allowlist",
|
|
42
|
+
"tools": [
|
|
43
|
+
"Bash",
|
|
44
|
+
"Agent",
|
|
45
|
+
"AskUserQuestion",
|
|
46
|
+
"EnterPlanMode",
|
|
47
|
+
"TaskUpdate",
|
|
48
|
+
"Grep",
|
|
49
|
+
"CronCreate",
|
|
50
|
+
"TaskCreate",
|
|
51
|
+
"ExitPlanMode",
|
|
52
|
+
"Read",
|
|
53
|
+
"ExitWorktree",
|
|
54
|
+
"WebSearch",
|
|
55
|
+
"WebFetch",
|
|
56
|
+
"Edit",
|
|
57
|
+
"Skill",
|
|
58
|
+
"EnterWorktree",
|
|
59
|
+
"NotebookEdit",
|
|
60
|
+
"TaskList",
|
|
61
|
+
"Glob",
|
|
62
|
+
"Write",
|
|
63
|
+
"TaskGet",
|
|
64
|
+
"TaskOutput",
|
|
65
|
+
"TaskStop",
|
|
66
|
+
"CronDelete",
|
|
67
|
+
"CronList"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"activeProfile": "No tools"
|
|
72
|
+
}
|
|
@@ -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(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}.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}
|
|
@@ -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>`),!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 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();
|
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-BhRfrW6t.js"></script>
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B4-LdibW.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>
|
|
@@ -56,6 +60,9 @@
|
|
|
56
60
|
</div>
|
|
57
61
|
</div>
|
|
58
62
|
|
|
63
|
+
<!-- Session Tabs -->
|
|
64
|
+
<div class="session-tabs" id="sessionTabs"></div>
|
|
65
|
+
|
|
59
66
|
<!-- Context Bar -->
|
|
60
67
|
<div class="bar-container">
|
|
61
68
|
<div class="bar-outer" id="barOuter">
|
|
@@ -140,5 +147,51 @@
|
|
|
140
147
|
</div>
|
|
141
148
|
</div>
|
|
142
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
|
+
|
|
143
196
|
</body>
|
|
144
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;
|
|
@@ -328,6 +442,41 @@ function createServer(opts = {}) {
|
|
|
328
442
|
return simpleHash(model + "|" + text.slice(0, 5000));
|
|
329
443
|
}
|
|
330
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Extract Claude Code session identity from the request body.
|
|
447
|
+
* - sessionId: the per-session hash from the billing header (cch=...)
|
|
448
|
+
* - sessionLabel: last path component of the working directory
|
|
449
|
+
* - sessionPath: full working directory path
|
|
450
|
+
*/
|
|
451
|
+
function extractSessionInfo(body) {
|
|
452
|
+
if (!body.system) return { sessionId: null, sessionLabel: null, sessionPath: null }
|
|
453
|
+
let text
|
|
454
|
+
if (typeof body.system === 'string') {
|
|
455
|
+
text = body.system
|
|
456
|
+
} else if (Array.isArray(body.system)) {
|
|
457
|
+
text = body.system
|
|
458
|
+
.filter(b => b.type === 'text' && b.text)
|
|
459
|
+
.map(b => b.text)
|
|
460
|
+
.join('\n')
|
|
461
|
+
} else {
|
|
462
|
+
return { sessionId: null, sessionLabel: null, sessionPath: null }
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const cchMatch = text.match(/cch=([a-zA-Z0-9]+)/)
|
|
466
|
+
const sessionId = cchMatch ? cchMatch[1] : null
|
|
467
|
+
|
|
468
|
+
const dirMatch = text.match(/Primary working directory:\s*(.+)/)
|
|
469
|
+
let sessionLabel = null
|
|
470
|
+
let sessionPath = null
|
|
471
|
+
if (dirMatch) {
|
|
472
|
+
sessionPath = dirMatch[1].trim()
|
|
473
|
+
const parts = sessionPath.split('/')
|
|
474
|
+
sessionLabel = parts[parts.length - 1] || parts[parts.length - 2] || sessionPath
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return { sessionId, sessionLabel, sessionPath }
|
|
478
|
+
}
|
|
479
|
+
|
|
331
480
|
/**
|
|
332
481
|
* Walk body.messages backwards to find the most recent human-authored text.
|
|
333
482
|
*/
|
|
@@ -580,6 +729,7 @@ function createServer(opts = {}) {
|
|
|
580
729
|
const model = body.model || "unknown";
|
|
581
730
|
const sessionHash = getSessionHash(body);
|
|
582
731
|
const groupId = assignGroup(body);
|
|
732
|
+
const { sessionId, sessionLabel, sessionPath } = extractSessionInfo(body);
|
|
583
733
|
|
|
584
734
|
const toolsSeg = segments.find((s) => s.type === "tools");
|
|
585
735
|
const userMessages = (body.messages || [])
|
|
@@ -611,6 +761,9 @@ function createServer(opts = {}) {
|
|
|
611
761
|
toolCount: toolsSeg?.count || 0,
|
|
612
762
|
estimatedToolTokens: toolsSeg?.tokens || 0,
|
|
613
763
|
userMessages,
|
|
764
|
+
sessionId,
|
|
765
|
+
sessionLabel,
|
|
766
|
+
sessionPath,
|
|
614
767
|
});
|
|
615
768
|
|
|
616
769
|
// Evict old requests if over limit
|
|
@@ -637,6 +790,9 @@ function createServer(opts = {}) {
|
|
|
637
790
|
toolsUsed: [...toolsUsed],
|
|
638
791
|
groupId,
|
|
639
792
|
sessionHash,
|
|
793
|
+
sessionId,
|
|
794
|
+
sessionLabel,
|
|
795
|
+
sessionPath,
|
|
640
796
|
};
|
|
641
797
|
}
|
|
642
798
|
|
|
@@ -736,6 +892,31 @@ function createServer(opts = {}) {
|
|
|
736
892
|
return;
|
|
737
893
|
}
|
|
738
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
|
+
|
|
739
920
|
// ── API: List profiles ──
|
|
740
921
|
if (req.url === "/api/profiles") {
|
|
741
922
|
jsonResponse(res, 200, { profiles, active: activeProfile });
|
|
@@ -860,10 +1041,11 @@ function createServer(opts = {}) {
|
|
|
860
1041
|
const originalToolCount = (parsed.tools || []).length;
|
|
861
1042
|
const { filtered, removed } = applyToolFilter(parsed.tools, activeProfile);
|
|
862
1043
|
|
|
1044
|
+
let needsReserialize = false;
|
|
1045
|
+
|
|
863
1046
|
if (removed.length > 0) {
|
|
864
1047
|
parsed.tools = filtered;
|
|
865
|
-
|
|
866
|
-
forwardBuffer = Buffer.from(modifiedStr, "utf-8");
|
|
1048
|
+
needsReserialize = true;
|
|
867
1049
|
filteringInfo = {
|
|
868
1050
|
originalToolCount,
|
|
869
1051
|
filteredToolCount: filtered.length,
|
|
@@ -874,7 +1056,23 @@ function createServer(opts = {}) {
|
|
|
874
1056
|
};
|
|
875
1057
|
}
|
|
876
1058
|
|
|
877
|
-
//
|
|
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)
|
|
878
1076
|
const analysis = analyzeRequest(parsed);
|
|
879
1077
|
|
|
880
1078
|
// Attach filtering info
|
|
@@ -885,12 +1083,17 @@ function createServer(opts = {}) {
|
|
|
885
1083
|
analysis.removedTools = filteringInfo.removedTools;
|
|
886
1084
|
analysis.tokensSaved = filteringInfo.tokensSaved;
|
|
887
1085
|
}
|
|
1086
|
+
if (stripInfo) {
|
|
1087
|
+
analysis.stripActive = true;
|
|
1088
|
+
analysis.messagesStripped = stripInfo.messagesStripped;
|
|
1089
|
+
analysis.stripTokensSaved = stripInfo.estimatedTokensSaved;
|
|
1090
|
+
}
|
|
888
1091
|
|
|
889
1092
|
requestTurn = analysis.turn;
|
|
890
1093
|
|
|
891
1094
|
broadcast({ type: "request", ...analysis });
|
|
892
1095
|
console.log(
|
|
893
|
-
`[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)` : ""}`
|
|
894
1097
|
);
|
|
895
1098
|
|
|
896
1099
|
// Let plugins analyze the request (router prediction, etc.)
|
|
@@ -1066,6 +1269,7 @@ function createServer(opts = {}) {
|
|
|
1066
1269
|
activeProfile,
|
|
1067
1270
|
premium: false,
|
|
1068
1271
|
routerMode: "off",
|
|
1272
|
+
strip: stripSettings,
|
|
1069
1273
|
...pluginHost.getConnectPayload(),
|
|
1070
1274
|
}));
|
|
1071
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:14px;font-weight:800;line-height:1}.header-brand .status{font-size:9px}.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:10px;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:10px;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:10px;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:10px;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:10px;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:3px 6px;font-size:8px;font-weight:800;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:9px;font-weight:600}.bar-empty{flex-grow:1;justify-content:center;align-items:center;display:flex}.bar-empty span{color:var(--text3);font-size:10px}.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:10px;display:flex}.legend-dot{border-radius:2px;width:7px;height:7px}.bar-total{font-size:12px;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:11px;font-weight:800;display:flex}.panel-actions{align-items:center;gap:6px;display:flex}.panel-btn{color:var(--text3);cursor:pointer;font-size:10px;font-family:var(--font-ui);border-radius:var(--radius-sm);transition:all var(--duration-fast);background:0 0;border:none;padding:3px 8px}.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:11px;font-weight:700}.req-tokens{font-size:10px;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:9px;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:12px;font-weight:700}.group-summary{font-size:10px;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:12px;font-weight:600}.seg-sub{color:var(--text3);white-space:nowrap;text-overflow:ellipsis;max-width:350px;margin-top:1px;font-size:10px;overflow:hidden}.seg-tokens{font-size:12px;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:10px;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:14px;font-weight:700}.empty p{text-align:center;max-width:300px;font-size:12px;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:15px;font-weight:700}.modal-title .modal-meta{color:var(--text3);margin-top:2px;font-size:11px}.modal-stats{align-items:center;gap:12px;display:flex}.modal-stat{text-align:center}.modal-stat-value{font-size:14px;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:9px}.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:11px;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:12px;font-weight:700}.tool-group-meta{color:var(--text3);font-size:10px}.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:12px;font-weight:700}.tool-card-desc{color:var(--text3);white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:10px;overflow:hidden}.tool-card-tokens{color:var(--text3);font-size:10px;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}@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) 60ms both}.reqs-panel{animation:fadeInUp var(--duration-normal) var(--ease-out) .12s both}.detail-panel{animation:fadeInUp var(--duration-normal) var(--ease-out) .18s both}button:active{transform:scale(.97)}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
var e=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);(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 t={connected:!1,reqs:[],selectedReq:null,profiles:{},activeProfile:`All Tools`,premium:!1,routerMode:`off`,toolsUsed:new Set,groups:{},groupView:!0,expandedGroups:{}},n={segment:null,segIndex:null,view:`formatted`,fullContent:``,parsedTools:null,loading:!1},r={system:`#60A5FA`,tools:`#FB923C`,message:`#22D3EE`,assistant:`#34D399`,tool_result:`#FBBF24`,tool_use:`#A78BFA`},i={system:`--seg-system`,tools:`--seg-tools`,message:`--seg-message`,assistant:`--seg-assistant`,tool_result:`--seg-tool-result`,tool_use:`--seg-tool-use`};function a(e){let t=i[e];if(t){let e=getComputedStyle(document.documentElement).getPropertyValue(t).trim();if(e)return e}return r[e]||`#64748B`}var o=e(((e,t)=>{var n=3.8;function r(e){if(!e)return 0;let t=typeof e==`string`?e:JSON.stringify(e);return Math.ceil(t.length/n)}function i(e){return r(e)}var a={"gpt-4o":128e3,"gpt-4-turbo":128e3,"gpt-3.5":16385,gemini:1e6},o=[128e3,2e5,1e6];function s(e){if(!e)return 2e5;let t=e.toLowerCase();if(t.includes(`1m`)||t.includes(`opus-4-5`)||t.includes(`opus-4-6`)||t.includes(`opus-4.5`)||t.includes(`opus-4.6`))return 1e6;if(t.includes(`claude`))return 2e5;for(let[e,n]of Object.entries(a))if(t.includes(e))return n;return 2e5}function c(e,t){let n=s(e);if(t<=n)return n;for(let e of o)if(t<=e)return e;return Math.max(n,t)}function l(e){let t=[],n=e.model||`unknown`;if(e.system){let n=typeof e.system==`string`?e.system:JSON.stringify(e.system,null,2);t.push({type:`system`,name:`System Prompt`,tokens:r(n),charLength:n.length})}if(e.tools&&e.tools.length>0){let n=JSON.stringify(e.tools);t.push({type:`tools`,name:`Tools (${e.tools.length})`,tokens:r(n),count:e.tools.length})}if(e.messages)for(let n=0;n<e.messages.length;n++){let i=e.messages[n],a=typeof i.content==`string`?i.content:JSON.stringify(i.content),o=Array.isArray(i.content)&&i.content.some(e=>e.type===`tool_result`),s=Array.isArray(i.content)&&i.content.some(e=>e.type===`tool_use`),c=`message`,l=`${i.role} message`;o?(c=`tool_result`,l=`Tool Result`):s&&(c=`tool_use`,l=`Tool Use (assistant)`),t.push({type:c,role:i.role,name:l,tokens:r(a),charLength:a.length,index:n})}let i=t.reduce((e,t)=>e+t.tokens,0);return{segments:t,totalEstimatedTokens:i,budget:c(n,i)}}t.exports={CHARS_PER_TOKEN:n,estimateTokens:r,estimateToolTokens:i,getBudget:s,inferBudget:c,MODEL_BUDGETS:a,CONTEXT_TIERS:o,analyzeSegments:l}}))();function s(e){return e.type===`message`&&e.role===`assistant`?a(`assistant`):e.type===`message`?a(`message`):a(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`,y=500,b=null;function x(e){b&&clearTimeout(b),b=setTimeout(()=>{try{let t={reqs:e.reqs,selectedReq:e.selectedReq,groupView:e.groupView,savedAt:Date.now()};localStorage.setItem(g,JSON.stringify(t))}catch(e){console.warn(`Failed to persist session:`,e.message)}b=null},y)}function ee(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){if(e.reqs=r,e.selectedReq=i!=null&&i<e.reqs.length?i:e.reqs.length-1,n.groupView!=null&&(e.groupView=n.groupView),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 te(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 S(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 ne(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 re(){try{let e=new Date().toISOString().slice(0,10);return JSON.parse(localStorage.getItem(_)||`{}`)[e]||0}catch{return 0}}function ie(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 ae(){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 C(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 w(e={}){E(),D(),oe(),k(),e.skipDetail||M(),T()}function T(){let e=document.getElementById(`exportBtn`);e&&(e.disabled=t.reqs.length===0,e.title=t.reqs.length===0?`No data to export`:`Export session as JSON or CSV`)}function E(){document.getElementById(`statusDot`).className=`status-dot ${t.connected?`connected`:`disconnected`}`;let e=document.getElementById(`statusText`);e.textContent=t.connected?`Connected`:`Disconnected`,e.style.color=t.connected?`var(--green)`:`var(--red)`;let n=document.getElementById(`reqBadge`);n&&(n.textContent=`Req ${t.reqs.length}`);let r=re(),i=document.getElementById(`dailyCost`);i&&(i.textContent=`Cost: ${u(r)}`);let a=document.getElementById(`dailySaved`);if(a)if(!t.premium)a.textContent=`Saved: Pro`,a.className=`daily-saved premium-locked`,a.title=`Savings intelligence requires Pro`;else{let{cost:e,tokens:t}=ae(),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(t.premium){let e=t.routerMode||`off`;o.textContent={off:`Router Off`,shadow:`Router Shadow`,auto:`Router Auto`}[e]||`Router`,o.className=`router-badge router-badge--${e}`;let n=document.getElementById(`routerPopover`);if(n)for(let t of n.querySelectorAll(`.router-popover-opt`))t.classList.toggle(`active`,t.dataset.mode===e),t.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 D(){let e=t.selectedReq===null?null:t.reqs[t.selectedReq],n=document.getElementById(`barInner`),r=document.getElementById(`barOuter`);if(!e){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=e.budget,a=e.actualUsage?e.actualUsage.input_tokens:e.totalEstimatedTokens,o=a/i*100;r.className=`bar-outer`+(o>95?` pressure-critical`:o>80?` pressure-high`:``);let u=[];for(let t=0;t<e.segments.length;t++){let n=e.segments[t],r=s(n),i=u[u.length-1];i&&i.color===r?(i.tokens+=n.tokens,i.count++,i.endIndex=t):u.push({color:r,tokens:n.tokens,name:n.name,count:1,startIndex:t,endIndex:t})}let d=``;for(let e of u){let t=e.tokens/i*100;if(t<.1)continue;let n=e.count>1?`${e.name} (×${e.count})`:e.name;d+=`<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&&(d+=`<span>${t>15?n:l(e.tokens)}</span>`),d+=`</div>`}o<100&&(d+=`<div class="bar-empty"><span>${l(i-a)} free</span></div>`),n.innerHTML=d;let f=new Map;for(let t of e.segments){let e=c(t),n=s(t);f.has(e)||f.set(e,n)}document.getElementById(`barLegend`).innerHTML=Array.from(f.entries()).map(([e,t])=>`<div class="legend-item"><div class="legend-dot" style="background:${t}"></div>${e}</div>`).join(``);let p=e.actualUsage?l(e.actualUsage.input_tokens):e.tokenCountSource===`count_tokens`?l(e.totalEstimatedTokens):`~${l(e.totalEstimatedTokens)}`,m=document.getElementById(`barTotal`),h=document.getElementById(`barPct`);m.textContent=`${p} / ${l(i)}`,m.style.color=o>95?`var(--red)`:o>80?`var(--orange)`:`var(--text)`,h.textContent=`${o.toFixed(1)}%`,h.style.color=o>95?`var(--red)`:o>80?`var(--orange)`:`var(--text3)`}function oe(){let e=document.getElementById(`tokenChartContainer`),n=document.getElementById(`tokenChart`);if(!e||!n)return;if(t.reqs.length<2){e.style.display=`none`;return}e.style.display=`block`;let r=t.reqs.map(e=>e.actualUsage?.input_tokens??e.totalEstimatedTokens??0),i=Math.max(...r),a=Math.min(...r),o=i-a||1;n.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 O(){return`ANTHROPIC_BASE_URL=http://localhost:${location.port===`5173`?`4455`:location.port||`4455`} claude`}function se(){navigator.clipboard.writeText(O()).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 k(){let e=document.getElementById(`reqList`);if(t.reqs.length===0){e.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">${O()}</code> <button id="copyCommandBtn" class="copy-command-btn" onclick="copyClaudeCommand()" title="Copy to clipboard">Copy</button></p></div>`;return}let n=document.getElementById(`viewToggleBtn`);n&&(n.textContent=t.groupView?`Grouped`:`Flat`,n.classList.toggle(`active`,t.groupView));let r=e.scrollTop;t.groupView&&Object.keys(t.groups).length>0?j(e):ce(e),e.scrollTop=r}function ce(e){let n=``;for(let e=t.reqs.length-1;e>=0;e--)n+=A(e);e.innerHTML=n}function A(e){let n=t.reqs[e],r=n.actualUsage?n.actualUsage.input_tokens:n.totalEstimatedTokens,i=Math.min(r/n.budget*100,100),a=i>95?`var(--red)`:i>80?`var(--orange)`:`var(--green)`,o=n.model.replace(`claude-`,``).replace(/-\d{8,}$/,``),s=n.actualUsage?l(n.actualUsage.input_tokens):n.tokenCountSource===`count_tokens`?l(n.totalEstimatedTokens):`~`+l(n.totalEstimatedTokens),c=``;n.actualUsage&&(c=`${l(n.actualUsage.input_tokens)} in / ${l(n.actualUsage.output_tokens)} out`);let d=n.actualCost?u(n.actualCost.totalCost):n.estimatedCost?`~`+u(n.estimatedCost.totalCost):``,f=`<div class="req-card${e===t.selectedReq?` selected`:``}" onclick="selectReq(${e})">`;return f+=`<div class="req-card-head"><span class="req-label">Req ${n.turn}</span><span class="req-tokens" style="color:${a}">${s}</span></div>`,f+=`<div class="req-mini-bar"><div class="req-mini-fill" style="width:${i}%;background:${a}"></div></div>`,f+=`<div class="req-meta"><span>${o}</span>`,c&&(f+=`<span class="req-io">${c}</span>`),d&&(f+=`<span class="req-cost-inline">${d}</span>`),f+=`</div>`,f+=`</div>`,f}function j(e){let n=Object.keys(t.groups).map(Number).sort((e,t)=>t-e),r=``;for(let e of n){let n=t.groups[e],i=t.expandedGroups[e]!==!1,a=0,o=0;for(let e of n.reqIndices){let n=t.reqs[e];n&&(n.actualCost?a+=n.actualCost.totalCost:n.estimatedCost&&(a+=n.estimatedCost.totalCost),o+=n.actualUsage?.input_tokens??n.totalEstimatedTokens??0)}let s=n.reqIndices.length,c=Object.keys(n.sessions),d=c.length>1,f=e+1;if(r+=`<div class="group-card">`,r+=`<div class="group-header" onclick="toggleGroup(${e})">`,r+=`<span class="group-chevron ${i?`expanded`:``}">▶</span>`,r+=`<span class="group-title">Turn ${f}</span>`,r+=`<div class="group-summary">`,r+=`<span class="group-tokens">${l(o)}</span>`,r+=`<span class="group-cost">${u(a)}</span>`,r+=`<span class="group-req-count">${s} req${s===1?``:`s`}</span>`,r+=`</div></div>`,r+=`<div class="group-children ${i?``:`collapsed`}">`,d){let e=c.sort((e,t)=>{let r=n.sessions[e].reqIndices.length;return n.sessions[t].reqIndices.length-r}),t=0;for(let i of e){let e=n.sessions[i],a=e.model||`unknown`,o=t===0,s=o?`Main`:`Subagent`,c=o?`main`:`subagent`;r+=`<div class="group-session-label">`,r+=`<span class="session-pill ${c}">${s}</span>`,r+=`<span>${a} · ${e.reqIndices.length} req${e.reqIndices.length===1?``:`s`}</span>`,r+=`</div>`;for(let t=e.reqIndices.length-1;t>=0;t--)r+=A(e.reqIndices[t]);t++}}else for(let e=n.reqIndices.length-1;e>=0;e--)r+=A(n.reqIndices[e]);let p=new Date(n.startTime).toLocaleTimeString(),m=new Date(n.endTime).toLocaleTimeString(),h=p===m?p:`${p} – ${m}`;r+=`<div class="group-time">${h}</div>`,r+=`</div></div>`}e.innerHTML=r}function M(){let e=document.getElementById(`detailBody`),n=document.getElementById(`detailTitle`),r=document.getElementById(`detailMeta`);if(t.selectedReq===null||!t.reqs[t.selectedReq]){n.textContent=`Segment Breakdown`,r.textContent=``,e.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=t.reqs[t.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>`),!t.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>`}e.innerHTML=a}async function le(e,t){return(await fetch(`/api/content/${e}/${t}`)).json()}async function ue(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 N(e){z({type:`set_active_profile`,profile:e})}async function P(e,n,r){try{let i=await ue(e,n,r);return i.success&&(t.profiles[e]=i.profile,F(),N(e)),i}catch(e){return console.error(`Failed to save profile:`,e),{error:e.message}}}function F(){let e=document.getElementById(`profileSelect`),n=document.getElementById(`filterBadge`),r=t.activeProfile;e.innerHTML=``;for(let n of Object.keys(t.profiles)){let t=document.createElement(`option`);t.value=n,t.textContent=n,n===r&&(t.selected=!0),e.appendChild(t)}let i=r!==`All Tools`;e.className=`profile-select`+(i?` filtering`:``),n.style.display=i?`inline`:`none`}function I(e,n){let r=n.groupId;if(r==null)return;if(!t.groups[r]){t.groups[r]={id:r,reqIndices:[],sessions:{},startTime:n.timestamp,endTime:n.timestamp};for(let e of Object.keys(t.expandedGroups))t.expandedGroups[e]=!1;t.expandedGroups[r]=!0}let i=t.groups[r];i.reqIndices.push(e),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(e)}function L(){t.groups={},t.expandedGroups={};for(let e=0;e<t.reqs.length;e++)I(e,t.reqs[e]);let e=Object.keys(t.groups).map(Number);if(e.length>0){let n=Math.max(...e);for(let r of e)t.expandedGroups[r]=r===n}}var R;function z(e){R&&R.readyState===1&&R.send(JSON.stringify(e))}function B(){let e=location.protocol===`https:`?`wss:`:`ws:`,n=location.port===`5173`?`localhost:4455`:location.host;R=new WebSocket(`${e}//${n}`),R.onopen=()=>{t.connected=!0,E()},R.onclose=()=>{t.connected=!1,E(),setTimeout(B,2e3)},R.onmessage=e=>{let n=JSON.parse(e.data);if(n.type===`connected`){t.premium=!!n.premium,n.profiles&&(t.profiles=n.profiles),n.activeProfile&&(t.activeProfile=n.activeProfile),n.routerMode!=null&&(t.routerMode=n.routerMode),F(),w();return}if(n.type===`request`){n.toolsUsed&&n.toolsUsed.length&&n.toolsUsed.forEach(e=>t.toolsUsed.add(e)),t.reqs.push(n),t.reqs.length>50?(t.reqs.splice(0,t.reqs.length-50),L()):I(t.reqs.length-1,n);let e=t.selectedReq!==null;e||(t.selectedReq=t.reqs.length-1),w({skipDetail:e}),x(t)}if(n.type===`token_count_update`){let e=t.reqs.find(e=>e.turn===n.turn);e&&(e.exactInputTokens=n.exactInputTokens,e.segments=n.segments,e.totalEstimatedTokens=n.exactInputTokens,e.estimatedCost=n.estimatedCost,e.tokenCountSource=`count_tokens`,w({skipDetail:!(t.selectedReq!==null&&t.reqs[t.selectedReq]?.turn===n.turn)}),x(t))}if(n.type===`response_complete`){let e=n.turn==null?t.reqs[t.reqs.length-1]:t.reqs.find(e=>e.turn===n.turn);e&&(e.actualUsage=n.usage,e.stopReason=n.stopReason,n.cost&&(e.actualCost=n.cost,ne(n.cost.totalCost)),n.toolsUsed&&(e.toolsUsed=n.toolsUsed,n.toolsUsed.forEach(e=>t.toolsUsed.add(e))),w({skipDetail:!(t.selectedReq!==null&&t.reqs[t.selectedReq]?.turn===n.turn)}),x(t))}if(n.type===`router_decision`){let e=t.reqs.find(e=>e.turn===n.turn);if(e){let r=!e.router;if(e.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 t=d(e.model);ie(n.estimated_tokens_saved/1e6*t*.1,n.estimated_tokens_saved)}t.selectedReq!==null&&t.reqs[t.selectedReq]?.turn===n.turn&&M(),x(t)}}n.type===`router_mode_changed`&&(t.routerMode=n.mode,E()),n.type===`profiles_updated`&&(t.profiles=n.profiles||{},t.activeProfile=n.active||`All Tools`,F()),n.type===`active_profile_changed`&&(t.activeProfile=n.active||`All Tools`,F())}}async function V(e){let r=t.reqs[t.selectedReq];if(!r)return;let i=r.segments[e];if(!i)return;n.segment=i,n.segIndex=e,n.view=i.type===`tools`?`tools`:`formatted`,n.fullContent=``,n.loading=!0,n.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 t=await le(r.turn,e);if(t.content){n.fullContent=t.content;let e=document.querySelector(`.modal-stat:nth-child(2) .modal-stat-value`);if(e&&(e.textContent=t.content.length.toLocaleString()),i.type===`tools`)try{n.parsedTools=JSON.parse(t.content)}catch{n.parsedTools=null}}else n.fullContent=i.preview||`(content no longer available — request may have been evicted)`}catch(e){console.error(`Failed to fetch segment content:`,e),n.fullContent=i.preview||`(failed to load content)`}n.loading=!1,W()}function H(){document.getElementById(`modalOverlay`).classList.remove(`open`),n.segment=null,n.parsedTools=null}function U(e,t){n.view=e,document.querySelectorAll(`.modal-toolbar button`).forEach(e=>e.classList.remove(`active`)),t&&t.classList.add(`active`),W()}function W(){let e=document.getElementById(`modalBody`),t=n.fullContent;if(n.loading)return;if(n.view===`tools`&&n.parsedTools){de(e);return}if(n.view===`raw`){e.innerHTML=`<div class="modal-content" style="white-space:pre-wrap;word-break:break-all">${f(t)}</div>`;return}let r=t;try{let e=JSON.parse(t);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 de(e){let r=n.parsedTools;if(!r||!r.length){e.innerHTML=`<div style="padding:20px;color:var(--text3)">No tools found</div>`;return}let i=t.profiles[t.activeProfile],a=t.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(e){for(let[n,r]of e){let e=[...r].sort((e,t)=>(0,o.estimateToolTokens)(t)-(0,o.estimateToolTokens)(e)),s=n===`other`?`Other`:n.charAt(0).toUpperCase()+n.slice(1),u=e.reduce((e,t)=>e+(0,o.estimateToolTokens)(t),0),d=e.filter(e=>p(e.name,i,a)).length,m=d===e.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}/${e.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 e){let e=p(r.name,i,a),s=(0,o.estimateToolTokens)(r),u=(r.description||``).slice(0,120),d=!t.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)}" ${e?`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 _=t.reqs[t.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>`,e.innerHTML=c,G()}function fe(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 pe(e,t){document.querySelectorAll(`.modal-tools input[type="checkbox"][data-server="${e}"]:not(.tool-group-checkbox)`).forEach(e=>{e.checked=t}),G()}function me(){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 he(e,t){document.querySelectorAll(`.modal-tools input[data-server="${e}"]:not(.tool-group-checkbox)`).forEach(e=>{e.checked=t}),G()}function ge(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)`),t=n.parsedTools;if(!t||!e.length)return;let r=0,i=0,a=0;e.forEach(e=>{let n=t.find(t=>t.name===e.dataset.tool);if(!n)return;let s=(0,o.estimateToolTokens)(n);e.checked?(r+=s,a++):i+=s}),me();let s=document.getElementById(`toolsSavings`);s&&i>0?s.innerHTML=`<div class="savings-banner">Disabling ${t.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>${t.length}</strong> tools enabled`)}async function _e(){let e=t.reqs[t.selectedReq],n=e?.toolsUsed||[];if(n.length!==0&&(await P(`Req ${e?.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 ve(){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 P(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(),t=document.getElementById(`modalBody`);if(!e){W();return}if(n.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=n.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>`,t.innerHTML=a}function q(){navigator.clipboard.writeText(n.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 ye(){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 be(){X(Y())}function Z(e){t.selectedReq=e,D(),k(),M(),x(t)}function Q(){t.reqs=[],t.selectedReq=null,t.groups={},t.expandedGroups={},w(),x(t)}function xe(){t.groupView=!t.groupView,k(),x(t)}function Se(e){t.expandedGroups[e]=!t.expandedGroups[e],k()}function Ce(){t.reqs.length!==0&&document.getElementById(`exportMenu`)?.classList.toggle(`open`)}function we(){t.reqs.length!==0&&(C(te(t),`jannal-session-${new Date().toISOString().slice(0,19).replace(/[:-]/g,``)}.json`),document.getElementById(`exportMenu`)?.classList.remove(`open`))}function Te(){t.reqs.length!==0&&(C(S(t),`jannal-session-${new Date().toISOString().slice(0,19).replace(/[:-]/g,``)}.csv`),document.getElementById(`exportMenu`)?.classList.remove(`open`))}window.openModal=V,window.closeModal=H,window.setModalView=U,window.selectReq=Z,window.clearReqs=Q,window.toggleGroup=Se,window.onProfileChange=N,window.toggleAllTools=ge,window.toggleGroupTools=he,window.toggleGroupAccordion=fe,window.toggleGroupCheckbox=pe,window.onToolToggle=G,window.saveCurrentAsProfile=ve,window.createProfileFromThisTurn=_e,window.copyClaudeCommand=se,window.filterModalContent=K,window.copyModalContent=q;var $=null;function Ee(e){let n=document.getElementById(`globalSearchResults`);if(!e||e.length<2){n.classList.remove(`open`),n.innerHTML=``;return}clearTimeout($),$=setTimeout(async()=>{try{let r=await(await fetch(`/api/search?q=${encodeURIComponent(e)}`)).json();if(r.results.length===0){n.innerHTML=`<div class="search-no-results">No matches found</div>`,n.classList.add(`open`);return}let i=e.toLowerCase();n.innerHTML=r.results.map(e=>{let n=t.reqs.findIndex(t=>t.turn===e.turnId),r=n>=0?`Req ${t.reqs[n].turn}`:`Req ${e.turnId}`,a=e.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="${e.segIndex}">
|
|
21
|
-
<div class="search-result-turn">${r} · segment ${e.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`,()=>{ye(),w()}),document.getElementById(`profileSelect`).addEventListener(`change`,e=>{N(e.target.value)}),document.getElementById(`clearBtn`).addEventListener(`click`,Q),document.getElementById(`viewToggleBtn`).addEventListener(`click`,xe),document.getElementById(`exportBtn`).addEventListener(`click`,Ce),document.getElementById(`exportMenu`)?.addEventListener(`click`,e=>{let t=e.target.closest(`.export-option`);t&&(t.dataset.format===`json`?we():t.dataset.format===`csv`&&Te())}),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 e=>{let n=e.target.closest(`.router-popover-opt`);if(!n||!t.premium)return;let r=n.dataset.mode;if(r===t.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&&(t.routerMode=r,E())}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&&H()}),document.getElementById(`modalCloseBtn`).addEventListener(`click`,H),document.getElementById(`formattedBtn`).addEventListener(`click`,e=>{U(`formatted`,e.target)}),document.getElementById(`rawBtn`).addEventListener(`click`,e=>{U(`raw`,e.target)}),document.getElementById(`toolsViewBtn`).addEventListener(`click`,e=>{U(`tools`,e.target)}),document.getElementById(`modalSearch`).addEventListener(`input`,K),document.getElementById(`copyBtn`).addEventListener(`click`,q),document.getElementById(`globalSearch`).addEventListener(`input`,e=>{Ee(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),V(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`),H())}),be(),ee(t),L(),B(),w();
|