pi-context-map 0.4.4 → 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/CHANGELOG.md +5 -0
- package/extensions/generator.ts +74 -6
- package/extensions/index.ts +19 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-06-15
|
|
4
|
+
### Features
|
|
5
|
+
- **Dark mode toggle**: Light/dark theme switcher in the report header. Preference saved to localStorage and restored on next load. Theme persists across SSE live updates.
|
|
6
|
+
- **Dynamic context window**: Replaced hardcoded 128k with actual context window size from Pi system via `ctx.getContextUsage()`. Now accurately reflects your model's real context limit (200k, 128k, etc.).
|
|
7
|
+
|
|
3
8
|
## [0.4.4] - 2026-06-15
|
|
4
9
|
### Bug Fixes
|
|
5
10
|
- **Fixed SSE rendering**: Changed `document.replaceChild(document.documentElement)` to `document.body.innerHTML` replacement. CSS and JavaScript no longer render as visible text on the page.
|
package/extensions/generator.ts
CHANGED
|
@@ -14,9 +14,10 @@ export class ReportGenerator {
|
|
|
14
14
|
public static generateHTML(
|
|
15
15
|
composition: ContextComposition,
|
|
16
16
|
insights: Insight[],
|
|
17
|
+
contextWindow: number = 128_000,
|
|
17
18
|
): string {
|
|
18
19
|
const total = composition.total.tokens;
|
|
19
|
-
const usagePercent = total > 0 ? Math.round((total /
|
|
20
|
+
const usagePercent = total > 0 ? Math.round((total / contextWindow) * 100) : 0;
|
|
20
21
|
|
|
21
22
|
const fileCards = composition.files_detail
|
|
22
23
|
.map(
|
|
@@ -90,6 +91,26 @@ export class ReportGenerator {
|
|
|
90
91
|
--seg-files: #007aff;
|
|
91
92
|
--seg-summaries: #34c759;
|
|
92
93
|
}
|
|
94
|
+
[data-theme="dark"] {
|
|
95
|
+
--canvas: #0a0a0b;
|
|
96
|
+
--canvas-alt: #141415;
|
|
97
|
+
--surface: #1a1a1c;
|
|
98
|
+
--hairline: #2c2c2e;
|
|
99
|
+
--hairline-soft: rgba(255,255,255,0.06);
|
|
100
|
+
--ink: #f5f5f7;
|
|
101
|
+
--ink-secondary: #a1a1a6;
|
|
102
|
+
--ink-tertiary: #6e6e73;
|
|
103
|
+
--ink-quaternary: #48484a;
|
|
104
|
+
--accent: #2997ff;
|
|
105
|
+
--accent-hover: #40a9ff;
|
|
106
|
+
--accent-soft: rgba(41,151,255,0.12);
|
|
107
|
+
--success: #30d158;
|
|
108
|
+
--success-soft: rgba(48,209,88,0.12);
|
|
109
|
+
--warning: #ff9f0a;
|
|
110
|
+
--warning-soft: rgba(255,159,10,0.12);
|
|
111
|
+
--danger: #ff453a;
|
|
112
|
+
--danger-soft: rgba(255,69,58,0.12);
|
|
113
|
+
}
|
|
93
114
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
94
115
|
body {
|
|
95
116
|
background: var(--canvas);
|
|
@@ -482,8 +503,15 @@ h2:first-of-type { margin-top: 48px; }
|
|
|
482
503
|
<div class="container">
|
|
483
504
|
|
|
484
505
|
<header>
|
|
485
|
-
|
|
486
|
-
|
|
506
|
+
<div style="display:flex;align-items:center;gap:12px;margin-bottom:20px;flex-wrap:wrap;">
|
|
507
|
+
<div class="live-badge"><span class="dot"></span>Live</div>
|
|
508
|
+
<button id="themeToggle" style="display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border:1px solid var(--hairline);border-radius:20px;background:var(--surface);color:var(--ink-secondary);font:inherit;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s;white-space:nowrap;" aria-label="Toggle theme">
|
|
509
|
+
<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>
|
|
510
|
+
<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>
|
|
511
|
+
<span id="themeLabel">Dark</span>
|
|
512
|
+
</button>
|
|
513
|
+
</div>
|
|
514
|
+
<h1>Context Profiler</h1>
|
|
487
515
|
<p class="subtitle">Session context window breakdown with actionable recommendations</p>
|
|
488
516
|
|
|
489
517
|
<div class="stats">
|
|
@@ -500,8 +528,8 @@ h2:first-of-type { margin-top: 48px; }
|
|
|
500
528
|
<span class="stat-label">Alerts</span>
|
|
501
529
|
</div>
|
|
502
530
|
<div class="stat">
|
|
503
|
-
<span class="stat-value">${
|
|
504
|
-
<span class="stat-label">
|
|
531
|
+
<span class="stat-value">${(contextWindow / 1000).toFixed(0)}k</span>
|
|
532
|
+
<span class="stat-label">Context Window</span>
|
|
505
533
|
</div>
|
|
506
534
|
</div>
|
|
507
535
|
|
|
@@ -515,7 +543,7 @@ h2:first-of-type { margin-top: 48px; }
|
|
|
515
543
|
</svg>
|
|
516
544
|
</div>
|
|
517
545
|
<div class="usage-label">
|
|
518
|
-
${usagePercent}% of
|
|
546
|
+
${usagePercent}% of ${(contextWindow / 1000).toFixed(0)}k window
|
|
519
547
|
<small>${usagePercent > 80 ? "Compaction recommended" : usagePercent > 60 ? "Monitor usage" : "Healthy"}</small>
|
|
520
548
|
</div>
|
|
521
549
|
</div>
|
|
@@ -594,6 +622,25 @@ h2:first-of-type { margin-top: 48px; }
|
|
|
594
622
|
if (filter) filter.addEventListener('change', update);
|
|
595
623
|
update();
|
|
596
624
|
|
|
625
|
+
// Theme toggle
|
|
626
|
+
var toggle = document.getElementById('themeToggle');
|
|
627
|
+
var label = document.getElementById('themeLabel');
|
|
628
|
+
var sunIcon = document.getElementById('themeIconSun');
|
|
629
|
+
var moonIcon = document.getElementById('themeIconMoon');
|
|
630
|
+
function applyTheme(t) {
|
|
631
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
632
|
+
localStorage.setItem('context-map-theme', t);
|
|
633
|
+
if (label) label.textContent = t === 'dark' ? 'Light' : 'Dark';
|
|
634
|
+
if (sunIcon) sunIcon.style.display = t === 'dark' ? '' : 'none';
|
|
635
|
+
if (moonIcon) moonIcon.style.display = t === 'dark' ? 'none' : '';
|
|
636
|
+
}
|
|
637
|
+
var saved = localStorage.getItem('context-map-theme');
|
|
638
|
+
applyTheme(saved || 'light');
|
|
639
|
+
if (toggle) toggle.addEventListener('click', function() {
|
|
640
|
+
var cur = document.documentElement.getAttribute('data-theme');
|
|
641
|
+
applyTheme(cur === 'dark' ? 'light' : 'dark');
|
|
642
|
+
});
|
|
643
|
+
|
|
597
644
|
// Insight toggles
|
|
598
645
|
var btns = document.querySelectorAll('.insight-header');
|
|
599
646
|
for (var j = 0; j < btns.length; j++) {
|
|
@@ -612,6 +659,8 @@ h2:first-of-type { margin-top: 48px; }
|
|
|
612
659
|
try {
|
|
613
660
|
var p = JSON.parse(e.data);
|
|
614
661
|
if (p.html) {
|
|
662
|
+
// Preserve current theme before replacing body
|
|
663
|
+
var currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
|
|
615
664
|
// Only replace the body content to preserve <style> and <script>
|
|
616
665
|
var d = new DOMParser().parseFromString(p.html, 'text/html');
|
|
617
666
|
var newBody = d.querySelector('body');
|
|
@@ -622,6 +671,25 @@ h2:first-of-type { margin-top: 48px; }
|
|
|
622
671
|
if (newTitle) {
|
|
623
672
|
document.title = newTitle.textContent || document.title;
|
|
624
673
|
}
|
|
674
|
+
// Restore theme after body replacement
|
|
675
|
+
applyTheme(currentTheme);
|
|
676
|
+
// Re-bind new theme toggle button
|
|
677
|
+
var newToggle = document.getElementById('themeToggle');
|
|
678
|
+
if (newToggle) {
|
|
679
|
+
newToggle.addEventListener('click', function() {
|
|
680
|
+
var cur = document.documentElement.getAttribute('data-theme');
|
|
681
|
+
applyTheme(cur === 'dark' ? 'light' : 'dark');
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
// Re-bind new file search/filter
|
|
685
|
+
var ns = document.getElementById('fileSearch');
|
|
686
|
+
var nf = document.getElementById('fileFilter');
|
|
687
|
+
if (ns) ns.addEventListener('input', update);
|
|
688
|
+
if (nf) nf.addEventListener('change', update);
|
|
689
|
+
// Re-query cards after body replacement
|
|
690
|
+
cards = Array.from(document.getElementById('fileGrid').querySelectorAll('.file-card'));
|
|
691
|
+
total = cards.length;
|
|
692
|
+
update();
|
|
625
693
|
}
|
|
626
694
|
} catch(_) {}
|
|
627
695
|
};
|
package/extensions/index.ts
CHANGED
|
@@ -31,12 +31,22 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
31
31
|
// session messages in Pi. (pi as any).session?.messages does NOT exist.
|
|
32
32
|
let sessionMessages: AgentMessage[] = [];
|
|
33
33
|
let currentTurn = 0;
|
|
34
|
+
let contextWindow = 128_000; // fallback, updated from Pi system
|
|
34
35
|
|
|
35
|
-
// Capture messages before each LLM call
|
|
36
|
-
pi.on("context", (event: any) => {
|
|
36
|
+
// Capture messages and context info before each LLM call
|
|
37
|
+
pi.on("context", (event: any, ctx: any) => {
|
|
37
38
|
if (event?.messages && Array.isArray(event.messages)) {
|
|
38
39
|
sessionMessages = event.messages;
|
|
39
40
|
}
|
|
41
|
+
// Fetch actual context window from Pi system
|
|
42
|
+
try {
|
|
43
|
+
const usage = ctx?.getContextUsage?.();
|
|
44
|
+
if (usage?.contextWindow && usage.contextWindow > 0) {
|
|
45
|
+
contextWindow = usage.contextWindow;
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
// Ignore — keep fallback
|
|
49
|
+
}
|
|
40
50
|
});
|
|
41
51
|
|
|
42
52
|
// Track turns
|
|
@@ -52,7 +62,11 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
52
62
|
const messages = sessionMessages.length > 0 ? sessionMessages : [];
|
|
53
63
|
const composition = analyzer.analyzeByType(messages, currentTurn);
|
|
54
64
|
const insights = InsightEngine.generate(composition);
|
|
55
|
-
const html = ReportGenerator.generateHTML(
|
|
65
|
+
const html = ReportGenerator.generateHTML(
|
|
66
|
+
composition,
|
|
67
|
+
insights,
|
|
68
|
+
contextWindow,
|
|
69
|
+
);
|
|
56
70
|
|
|
57
71
|
// Write to disk
|
|
58
72
|
try {
|
|
@@ -135,14 +149,14 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
135
149
|
const { composition, insights } = await runAnalysis();
|
|
136
150
|
const usagePercent =
|
|
137
151
|
composition.total.tokens > 0
|
|
138
|
-
? Math.round((composition.total.tokens /
|
|
152
|
+
? Math.round((composition.total.tokens / contextWindow) * 100)
|
|
139
153
|
: 0;
|
|
140
154
|
const summary =
|
|
141
155
|
`Context: ${composition.total.tokens.toLocaleString()} tokens total. ` +
|
|
142
156
|
`System ${composition.system.percent}%, Tools ${composition.tools.percent}%, ` +
|
|
143
157
|
`History ${composition.history.percent}%, Files ${composition.files.percent}%, ` +
|
|
144
158
|
`Summaries ${composition.summaries.percent}%. ` +
|
|
145
|
-
`Usage: ${usagePercent}% of
|
|
159
|
+
`Usage: ${usagePercent}% of ${(contextWindow / 1000).toFixed(0)}k window. ` +
|
|
146
160
|
`${insights.length} insight(s) generated.`;
|
|
147
161
|
return {
|
|
148
162
|
type: "text" as const,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-context-map",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Professional context profiler for Pi that visualizes the session context window, token distribution, and integrates with Nexus packages for actionable insights.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|