Compare commits

...

10 commits

13 changed files with 169 additions and 135 deletions

View file

@ -1,65 +1,49 @@
# dsh-plugin-token-heatmap # token-contribution
Visualizes your daily token consumption, prompt intensity, cache hit efficiency, and streaks across all sessions in a 52-week contribution calendar. > GitHub-style daily token usage contribution calendar, model analytics, and timeline trends for [DeepSeek Harness](https://github.com/deepseek-ai/dsh).
---
## Features
- **52-Week GitHub Contribution Calendar:** 7 rows × 52 columns tracking daily token activity with 4-level color intensity.
- **Detailed Summary Cards:**
- **Total Tokens:** Complete volume of input, output, and cached tokens.
- **Cache Hit Ratio:** Percentage of tokens served from KV cache reads.
- **Daily Average & Active Days:** Real workload metrics.
- **Streak Tracker:** Current and longest active day streaks.
- **Peak Day Callout:** Date and token count for your heaviest day.
- **Dual Color Palette:** Switch between **GitHub Emerald** and **DeepSeek Indigo**.
- **Interactive Tooltips:** Hover over any day to see token breakdown (billed input/output vs. cache reads and step counts).
- **Session Token Meter Integration:** Integrates into session stats pills with a *"View 12-Month Activity Heatmap"* button that opens the interactive modal dialog anywhere.
- **High-Performance Parser:** Aggregates zstandard (`.jsonl.zstd`) session logs with timestamp and mtime caching for near-instant rendering.
- **DeepSeek Native Theme:** Automatically adapts to DSH dark and light themes using `--dsw-alias-*` design tokens.
--- ---
## Installation ## Installation
### Method 1: Using pnpm in your DSH Web profile Install with a single command via the official DSH CLI:
Inside your DSH profile directory: ```bash
dsh plugin --profile web add token-contribution
```
*(To remove later: `dsh plugin --profile web remove token-contribution`)*
### Manual Alternative (pnpm)
```bash ```bash
cd ~/.dsh/profiles/web cd ~/.dsh/profiles/web
pnpm add dsh-plugin-token-heatmap pnpm add token-contribution
``` ```
Then add `"token-contribution"` to `bundles` in `~/.dsh/profiles/web/package.json`.
Then add `"dsh-plugin-token-heatmap"` to `~/.dsh/profiles/web/package.json` under `dsh.profile.bundles`: ---
```json ## Preview
{
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-plugin-token-heatmap"
]
}
}
}
```
### Method 2: Local Development Link <p align="center">
<img src="./assets/preview-overview.png" alt="Token Heatmap Overview" width="44%" />
&nbsp;
<img src="./assets/preview-trends.png" alt="Model Usage Breakdown & Trends" width="44%" />
</p>
Clone the repository locally: ---
```bash ## Features
git clone https://github.com/oiupoyt/dsh-plugin-token-heatmap.git ~/projects/dsh-plugin-token-heatmap
cd ~/projects/dsh-plugin-token-heatmap
npm run build
cd ~/.dsh/profiles/web - **52-Week GitHub Contribution Calendar:** 7 rows × 52 columns tracking daily token activity with a smooth monochromatic gradient anchored on DeepSeek's official brand blue (`#3964FE`).
pnpm add --link ~/projects/dsh-plugin-token-heatmap - **Interactive Day Inspector:** Click on any day to slide open an inspection drawer showing every session active on that date, exact timestamps, and token breakdown.
``` - **Top Model Usage Breakdown:** Real-time distribution showing tokens, percentages, and request steps for every model used across sessions.
- **Token Usage Trends Timeline:** Stacked bar chart comparing daily Billed Tokens vs. Cache Reads, with **14D / 30D / 90D** range filters and hover tooltips.
- **Global & Per-Session Filtering:** Toggle between aggregate global history or inspect token activity for individual sessions.
- **Auto-Expanding Panel:** Smoothly widens the DSH Settings dialog to 1180px so the calendar and charts fit cleanly with zero scrollbars.
- **Session Token Meter Integration:** Injects a clean action button into the in-session token usage dialog to open the heatmap directly from active sessions.
- **High-Performance Parser:** Recursively aggregates local `.jsonl.zstd` logs with timestamp and mtime caching for instant loading.
--- ---
@ -67,10 +51,10 @@ pnpm add --link ~/projects/dsh-plugin-token-heatmap
1. Open the DeepSeek Harness Web GUI (`http://127.0.0.1:3080`). 1. Open the DeepSeek Harness Web GUI (`http://127.0.0.1:3080`).
2. Go to **Settings** (gear icon at the bottom left) → **Token Heatmap**. 2. Go to **Settings** (gear icon at the bottom left) → **Token Heatmap**.
3. Or during any session, click on the token stats pill at the bottom of the composer to view token details and tap **"View 12-Month Activity Heatmap →"**. 3. Or during any session, click on the token stats pill at the bottom of the composer and tap **"View Token Usage Heatmap →"**.
--- ---
## License ## License
MIT © [oiupoyt](https://github.com/oiupoyt) MIT © [oiupoyt](https://git.oiupoyt.space/oiupoyt)

BIN
assets/preview-overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

BIN
assets/preview-trends.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View file

@ -1,4 +1,4 @@
# dsh-token-heatmap bundle patch # token-contribution bundle patch
- insert: - insert:
- id: token-heatmap - id: token-contribution
name: 'dsh-token-heatmap' name: 'token-contribution'

View file

@ -1,4 +1,4 @@
window.__ModuleLoader__.load({ id: "dsh-token-heatmap", factory: (require) => { var module = { exports: {} }; var exports = module.exports; window.__ModuleLoader__.load({ id: "token-contribution", factory: (require) => { var module = { exports: {} }; var exports = module.exports;
var __create = Object.create; var __create = Object.create;
var __defProp = Object.defineProperty; var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@ -72,16 +72,20 @@ function HeatmapInnerView() {
const [hoveredDay, setHoveredDay] = (0, import_react.useState)(null); const [hoveredDay, setHoveredDay] = (0, import_react.useState)(null);
const [hoveredBar, setHoveredBar] = (0, import_react.useState)(null); const [hoveredBar, setHoveredBar] = (0, import_react.useState)(null);
const [tooltipPos, setTooltipPos] = (0, import_react.useState)(null); const [tooltipPos, setTooltipPos] = (0, import_react.useState)(null);
const rootRef = (0, import_react.useRef)(null);
(0, import_react.useEffect)(() => { (0, import_react.useEffect)(() => {
if (typeof document === "undefined") return; if (typeof document === "undefined") return;
const panel = document.querySelector('[class*="_panel"]'); const findPanel = () => {
return rootRef.current?.closest('[class*="_panel"]') || document.querySelector(".VOzbGW_panel") || document.querySelector('[class*="_panel"]');
};
const panel = findPanel();
if (panel) { if (panel) {
const prevWidth = panel.style.width; const prevWidth = panel.style.width;
const prevMaxWidth = panel.style.maxWidth; const prevMaxWidth = panel.style.maxWidth;
const prevTransition = panel.style.transition; const prevTransition = panel.style.transition;
panel.style.transition = "width 0.25s cubic-bezier(0.16, 1, 0.3, 1), max-width 0.25s ease"; panel.style.transition = "width 0.25s cubic-bezier(0.16, 1, 0.3, 1), max-width 0.25s ease";
panel.style.width = "min(1080px, calc(100vw - 32px))"; panel.style.setProperty("width", "min(1180px, calc(100vw - 32px))", "important");
panel.style.maxWidth = "calc(100vw - 32px)"; panel.style.setProperty("max-width", "calc(100vw - 32px)", "important");
return () => { return () => {
panel.style.width = prevWidth; panel.style.width = prevWidth;
panel.style.maxWidth = prevMaxWidth; panel.style.maxWidth = prevMaxWidth;
@ -91,7 +95,7 @@ function HeatmapInnerView() {
}, []); }, []);
const fetchStats = () => { const fetchStats = () => {
setLoading(true); setLoading(true);
fetch("/api/dsh-token-heatmap/stats").then((r) => { fetch("/api/token-contribution/stats").then((r) => {
if (!r.ok) throw new Error(`HTTP error ${r.status}`); if (!r.ok) throw new Error(`HTTP error ${r.status}`);
return r.json(); return r.json();
}).then((res) => { }).then((res) => {
@ -294,6 +298,7 @@ function HeatmapInnerView() {
"div", "div",
{ {
"data-dsh-heatmap": "true", "data-dsh-heatmap": "true",
ref: rootRef,
style: { style: {
boxSizing: "border-box", boxSizing: "border-box",
width: "100%", width: "100%",
@ -447,10 +452,10 @@ function HeatmapInnerView() {
calendarGrid && /* @__PURE__ */ import_react.default.createElement("div", { style: { width: "100%", display: "flex", flexDirection: "column", alignItems: "center" } }, /* @__PURE__ */ import_react.default.createElement( calendarGrid && /* @__PURE__ */ import_react.default.createElement("div", { style: { width: "100%", display: "flex", flexDirection: "column", alignItems: "center" } }, /* @__PURE__ */ import_react.default.createElement(
"svg", "svg",
{ {
viewBox: "0 0 770 120", viewBox: "0 0 880 136",
style: { style: {
width: "100%", width: "100%",
maxWidth: "770px", maxWidth: "880px",
height: "auto", height: "auto",
display: "block", display: "block",
overflow: "visible" overflow: "visible"
@ -460,35 +465,35 @@ function HeatmapInnerView() {
"text", "text",
{ {
key: i, key: i,
x: 28 + m.col * 13.8, x: 32 + m.col * 15.6,
y: 10, y: 11,
fontSize: 10, fontSize: 11,
fill: "var(--dsw-alias-label-tertiary, #888888)", fill: "var(--dsw-alias-label-tertiary, #888888)",
fontFamily: "inherit" fontFamily: "inherit"
}, },
m.label m.label
)), )),
/* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 39, fontSize: 9, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Mon"), /* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 45, fontSize: 10, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Mon"),
/* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 67, fontSize: 9, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Wed"), /* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 76, fontSize: 10, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Wed"),
/* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 95, fontSize: 9, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Fri"), /* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 107, fontSize: 10, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Fri"),
calendarGrid.map((week, colIdx) => /* @__PURE__ */ import_react.default.createElement("g", { key: colIdx, transform: `translate(${28 + colIdx * 13.8}, 18)` }, week.map((day, rowIdx) => { calendarGrid.map((week, colIdx) => /* @__PURE__ */ import_react.default.createElement("g", { key: colIdx, transform: `translate(${32 + colIdx * 15.6}, 18)` }, week.map((day, rowIdx) => {
if (!day) return null; if (!day) return null;
const lvl = day.stats.level || 0; const lvl = day.stats.level || 0;
const bg = DEEPSEEK_LEVELS[lvl]; const bg = DEEPSEEK_LEVELS[lvl];
const isSelected = selectedDate === day.date; const isSelected = selectedDate === day.date;
const isHovered = hoveredDay?.date === day.date; const isHovered = hoveredDay?.date === day.date;
const strokeColor = isSelected ? "#ffffff" : isHovered ? "#5686fe" : lvl === 0 ? "rgba(255, 255, 255, 0.06)" : "transparent"; const strokeColor = isSelected ? "#ffffff" : isHovered ? "#5686fe" : lvl === 0 ? "rgba(255, 255, 255, 0.06)" : "transparent";
const strokeWidth = isSelected ? 1.6 : isHovered ? 1.3 : 1; const strokeWidth = isSelected ? 1.8 : isHovered ? 1.4 : 1;
return /* @__PURE__ */ import_react.default.createElement( return /* @__PURE__ */ import_react.default.createElement(
"rect", "rect",
{ {
key: rowIdx, key: rowIdx,
x: 0, x: 0,
y: rowIdx * 14, y: rowIdx * 15.6,
width: 11, width: 12,
height: 11, height: 12,
rx: 2, rx: 2.5,
ry: 2, ry: 2.5,
fill: bg, fill: bg,
stroke: strokeColor, stroke: strokeColor,
strokeWidth, strokeWidth,
@ -517,7 +522,7 @@ function HeatmapInnerView() {
{ {
style: { style: {
width: "100%", width: "100%",
maxWidth: "770px", maxWidth: "880px",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
@ -532,9 +537,9 @@ function HeatmapInnerView() {
{ {
key: i, key: i,
style: { style: {
width: 11, width: 12,
height: 11, height: 12,
borderRadius: 2, borderRadius: 2.5,
backgroundColor: c, backgroundColor: c,
border: i === 0 ? "1px solid rgba(255, 255, 255, 0.06)" : "none", border: i === 0 ? "1px solid rgba(255, 255, 255, 0.06)" : "none",
filter: i === 4 ? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" : "none" filter: i === 4 ? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" : "none"
@ -1112,7 +1117,7 @@ function HeatmapModal({ open, onClose }) {
} }
// src/client/index.tsx // src/client/index.tsx
var name = "dsh-plugin-token-heatmap-client"; var name = "token-contribution-client";
var inject = ["slots"]; var inject = ["slots"];
function apply(ctx) { function apply(ctx) {
if (ctx.slots && typeof ctx.slots.inject === "function") { if (ctx.slots && typeof ctx.slots.inject === "function") {
@ -1120,9 +1125,9 @@ function apply(ctx) {
return ctx.slots.register( return ctx.slots.register(
{ {
name: "settings.section", name: "settings.section",
id: "token-heatmap", id: "token-contribution",
order: 14, order: 14,
label: () => "Token Heatmap", label: () => "Token Contribution",
icon: "chart" icon: "chart"
}, },
HeatmapView HeatmapView
@ -1135,18 +1140,34 @@ function apply(ctx) {
(0, import_react3.useEffect)(() => { (0, import_react3.useEffect)(() => {
const handleOpen = () => setIsOpen(true); const handleOpen = () => setIsOpen(true);
const handleClose = () => setIsOpen(false); const handleClose = () => setIsOpen(false);
window.addEventListener("dsh:open-token-contribution", handleOpen);
window.addEventListener("dsh:open-token-heatmap", handleOpen); window.addEventListener("dsh:open-token-heatmap", handleOpen);
window.addEventListener("keydown", (e) => { window.addEventListener("keydown", (e) => {
if (e.key === "Escape") handleClose(); if (e.key === "Escape") handleClose();
}); });
return () => { return () => {
window.removeEventListener("dsh:open-token-contribution", handleOpen);
window.removeEventListener("dsh:open-token-heatmap", handleOpen); window.removeEventListener("dsh:open-token-heatmap", handleOpen);
}; };
}, []); }, []);
return /* @__PURE__ */ import_react3.default.createElement(HeatmapModal, { open: isOpen, onClose: () => setIsOpen(false) }); return /* @__PURE__ */ import_react3.default.createElement(HeatmapModal, { open: isOpen, onClose: () => setIsOpen(false) });
}; };
if (!document.getElementById("dsh-token-heatmap-panel-resize")) {
const resizeStyle = document.createElement("style");
resizeStyle.id = "dsh-token-heatmap-panel-resize";
resizeStyle.textContent = `
/* Dynamically widen DSH Settings panel when Token Heatmap is active */
.VOzbGW_panel:has([data-dsh-heatmap]),
[class*="_panel"]:has([data-dsh-heatmap]) {
width: min(1180px, calc(100vw - 32px)) !important;
max-width: calc(100vw - 32px) !important;
transition: width 0.25s cubic-bezier(0.16, 1, 0.3, 1), max-width 0.25s ease !important;
}
`;
document.head.appendChild(resizeStyle);
}
const modalContainer = document.createElement("div"); const modalContainer = document.createElement("div");
modalContainer.id = "dsh-token-heatmap-modal-root"; modalContainer.id = "token-contribution-modal-root";
document.body.appendChild(modalContainer); document.body.appendChild(modalContainer);
const root = (0, import_client.createRoot)(modalContainer); const root = (0, import_client.createRoot)(modalContainer);
root.render(/* @__PURE__ */ import_react3.default.createElement(ModalController, null)); root.render(/* @__PURE__ */ import_react3.default.createElement(ModalController, null));
@ -1161,7 +1182,7 @@ function apply(ctx) {
const button = document.createElement("button"); const button = document.createElement("button");
button.className = "dsh-heatmap-trigger-btn"; button.className = "dsh-heatmap-trigger-btn";
button.type = "button"; button.type = "button";
button.innerHTML = "<span>View Token Usage Heatmap \u2192</span>"; button.innerHTML = "<span>View Token Contribution \u2192</span>";
button.style.cssText = ` button.style.cssText = `
display: flex; display: flex;
align-items: center; align-items: center;
@ -1199,16 +1220,16 @@ function apply(ctx) {
settingsTrigger.click(); settingsTrigger.click();
setTimeout(() => { setTimeout(() => {
const heatmapTab = Array.from(document.querySelectorAll("*")).find( const heatmapTab = Array.from(document.querySelectorAll("*")).find(
(el) => el.textContent?.trim() === "Token Heatmap" && (el.tagName === "BUTTON" || el.tagName === "DIV" || el.getAttribute("role") === "tab" || el.classList.contains("ant-list-item")) (el) => (el.textContent?.trim() === "Token Contribution" || el.textContent?.trim() === "Token Heatmap") && (el.tagName === "BUTTON" || el.tagName === "DIV" || el.getAttribute("role") === "tab" || el.classList.contains("ant-list-item"))
); );
if (heatmapTab) { if (heatmapTab) {
heatmapTab.click(); heatmapTab.click();
} else { } else {
window.dispatchEvent(new CustomEvent("dsh:open-token-heatmap")); window.dispatchEvent(new CustomEvent("dsh:open-token-contribution"));
} }
}, 120); }, 120);
} else { } else {
window.dispatchEvent(new CustomEvent("dsh:open-token-heatmap")); window.dispatchEvent(new CustomEvent("dsh:open-token-contribution"));
} }
}; };
dialogContainer.appendChild(button); dialogContainer.appendChild(button);

File diff suppressed because one or more lines are too long

View file

@ -1,10 +1,10 @@
const name = "dsh-plugin-token-heatmap"; const name = "token-contribution";
const inject = ["webServer"]; const inject = ["webServer"];
function apply(ctx) { function apply(ctx) {
ctx.effect( ctx.effect(
() => ctx.webServer.register({ () => ctx.webServer.register({
kind: "prefix", kind: "prefix",
path: "/api/dsh-token-heatmap", path: "/api/token-contribution",
handler: async (req, res) => { handler: async (req, res) => {
const url = new URL(req.url, "http://localhost"); const url = new URL(req.url, "http://localhost");
const pathname = url.pathname.replace(/\/+$/, ""); const pathname = url.pathname.replace(/\/+$/, "");
@ -16,7 +16,7 @@ function apply(ctx) {
res.end(); res.end();
return; return;
} }
if (req.method === "GET" && (pathname === "/api/dsh-token-heatmap" || pathname === "/api/dsh-token-heatmap/stats")) { if (req.method === "GET" && (pathname === "/api/token-contribution" || pathname === "/api/token-contribution/stats" || pathname === "/api/dsh-token-heatmap/stats")) {
try { try {
const { aggregateTokens } = await import(`./aggregator.js?t=${Date.now()}`); const { aggregateTokens } = await import(`./aggregator.js?t=${Date.now()}`);
const data = await aggregateTokens(); const data = await aggregateTokens();

View file

@ -1,7 +1,7 @@
{ {
"version": 3, "version": 3,
"sources": ["../src/server/index.ts"], "sources": ["../src/server/index.ts"],
"sourcesContent": ["export const name = \"dsh-plugin-token-heatmap\";\nexport const inject = [\"webServer\"];\n\nexport function apply(ctx: any) {\n ctx.effect(() =>\n ctx.webServer.register({\n kind: \"prefix\",\n path: \"/api/dsh-token-heatmap\",\n handler: async (req: any, res: any) => {\n const url = new URL(req.url, \"http://localhost\");\n const pathname = url.pathname.replace(/\\/+$/, \"\");\n\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method === \"GET\" && (pathname === \"/api/dsh-token-heatmap\" || pathname === \"/api/dsh-token-heatmap/stats\")) {\n try {\n const { aggregateTokens } = await import(`./aggregator.js?t=${Date.now()}`);\n const data = await aggregateTokens();\n res.writeHead(200, {\n \"Content-Type\": \"application/json; charset=utf-8\",\n \"Cache-Control\": \"no-cache\",\n });\n res.end(JSON.stringify(data));\n } catch (err: any) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: err?.message || \"Failed to aggregate token stats\" }));\n }\n return;\n }\n\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n },\n })\n );\n}\n"], "sourcesContent": ["export const name = \"token-contribution\";\nexport const inject = [\"webServer\"];\n\nexport function apply(ctx: any) {\n ctx.effect(() =>\n ctx.webServer.register({\n kind: \"prefix\",\n path: \"/api/token-contribution\",\n handler: async (req: any, res: any) => {\n const url = new URL(req.url, \"http://localhost\");\n const pathname = url.pathname.replace(/\\/+$/, \"\");\n\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method === \"GET\" && (pathname === \"/api/token-contribution\" || pathname === \"/api/token-contribution/stats\" || pathname === \"/api/dsh-token-heatmap/stats\")) {\n try {\n const { aggregateTokens } = await import(`./aggregator.js?t=${Date.now()}`);\n const data = await aggregateTokens();\n res.writeHead(200, {\n \"Content-Type\": \"application/json; charset=utf-8\",\n \"Cache-Control\": \"no-cache\",\n });\n res.end(JSON.stringify(data));\n } catch (err: any) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: err?.message || \"Failed to aggregate token stats\" }));\n }\n return;\n }\n\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n },\n })\n );\n}\n"],
"mappings": "AAAO,MAAM,OAAO;AACb,MAAM,SAAS,CAAC,WAAW;AAE3B,SAAS,MAAM,KAAU;AAC9B,MAAI;AAAA,IAAO,MACT,IAAI,UAAU,SAAS;AAAA,MACrB,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,OAAO,KAAU,QAAa;AACrC,cAAM,MAAM,IAAI,IAAI,IAAI,KAAK,kBAAkB;AAC/C,cAAM,WAAW,IAAI,SAAS,QAAQ,QAAQ,EAAE;AAEhD,YAAI,UAAU,+BAA+B,GAAG;AAChD,YAAI,UAAU,gCAAgC,cAAc;AAC5D,YAAI,UAAU,gCAAgC,cAAc;AAE5D,YAAI,IAAI,WAAW,WAAW;AAC5B,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI;AACR;AAAA,QACF;AAEA,YAAI,IAAI,WAAW,UAAU,aAAa,4BAA4B,aAAa,iCAAiC;AAClH,cAAI;AACF,kBAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,qBAAqB,KAAK,IAAI,CAAC;AACxE,kBAAM,OAAO,MAAM,gBAAgB;AACnC,gBAAI,UAAU,KAAK;AAAA,cACjB,gBAAgB;AAAA,cAChB,iBAAiB;AAAA,YACnB,CAAC;AACD,gBAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAAA,UAC9B,SAAS,KAAU;AACjB,gBAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,gBAAI,IAAI,KAAK,UAAU,EAAE,OAAO,KAAK,WAAW,kCAAkC,CAAC,CAAC;AAAA,UACtF;AACA;AAAA,QACF;AAEA,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,YAAY,CAAC,CAAC;AAAA,MAChD;AAAA,IACF,CAAC;AAAA,EACH;AACF;", "mappings": "AAAO,MAAM,OAAO;AACb,MAAM,SAAS,CAAC,WAAW;AAE3B,SAAS,MAAM,KAAU;AAC9B,MAAI;AAAA,IAAO,MACT,IAAI,UAAU,SAAS;AAAA,MACrB,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,OAAO,KAAU,QAAa;AACrC,cAAM,MAAM,IAAI,IAAI,IAAI,KAAK,kBAAkB;AAC/C,cAAM,WAAW,IAAI,SAAS,QAAQ,QAAQ,EAAE;AAEhD,YAAI,UAAU,+BAA+B,GAAG;AAChD,YAAI,UAAU,gCAAgC,cAAc;AAC5D,YAAI,UAAU,gCAAgC,cAAc;AAE5D,YAAI,IAAI,WAAW,WAAW;AAC5B,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI;AACR;AAAA,QACF;AAEA,YAAI,IAAI,WAAW,UAAU,aAAa,6BAA6B,aAAa,mCAAmC,aAAa,iCAAiC;AACnK,cAAI;AACF,kBAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,qBAAqB,KAAK,IAAI,CAAC;AACxE,kBAAM,OAAO,MAAM,gBAAgB;AACnC,gBAAI,UAAU,KAAK;AAAA,cACjB,gBAAgB;AAAA,cAChB,iBAAiB;AAAA,YACnB,CAAC;AACD,gBAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAAA,UAC9B,SAAS,KAAU;AACjB,gBAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,gBAAI,IAAI,KAAK,UAAU,EAAE,OAAO,KAAK,WAAW,kCAAkC,CAAC,CAAC;AAAA,UACtF;AACA;AAAA,QACF;AAEA,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,YAAY,CAAC,CAAC;AAAA,MAChD;AAAA,IACF,CAAC;AAAA,EACH;AACF;",
"names": [] "names": []
} }

View file

@ -1,7 +1,7 @@
{ {
"name": "dsh-token-heatmap", "name": "token-contribution",
"version": "0.1.1", "version": "0.1.0",
"description": "GitHub-style token usage contribution heatmap and analytics for DeepSeek Harness", "description": "GitHub-style token usage contribution calendar and analytics for DeepSeek Harness",
"type": "module", "type": "module",
"main": "./lib/index.js", "main": "./lib/index.js",
"exports": { "exports": {
@ -13,7 +13,8 @@
"lib", "lib",
"cordis.patch.yml", "cordis.patch.yml",
"README.md", "README.md",
"LICENSE" "LICENSE",
"assets"
], ],
"keywords": [ "keywords": [
"deepseek-harness", "deepseek-harness",
@ -46,10 +47,10 @@
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.oiupoyt.space/oiupoyt/dsh-plugin-token-heatmap.git" "url": "https://git.oiupoyt.space/oiupoyt/token-contribution.git"
}, },
"bugs": { "bugs": {
"url": "https://git.oiupoyt.space/oiupoyt/dsh-plugin-token-heatmap/issues" "url": "https://git.oiupoyt.space/oiupoyt/token-contribution/issues"
}, },
"homepage": "https://git.oiupoyt.space/oiupoyt/dsh-plugin-token-heatmap#readme" "homepage": "https://git.oiupoyt.space/oiupoyt/token-contribution#readme"
} }

View file

@ -4,7 +4,7 @@ console.log("Building server backend...");
execSync("npx esbuild src/server/index.ts src/server/aggregator.ts --outdir=lib --platform=node --format=esm --target=node20 --sourcemap", { stdio: "inherit" }); execSync("npx esbuild src/server/index.ts src/server/aggregator.ts --outdir=lib --platform=node --format=esm --target=node20 --sourcemap", { stdio: "inherit" });
console.log("Building client frontend bundle..."); console.log("Building client frontend bundle...");
const banner = 'window.__ModuleLoader__.load({ id: "dsh-token-heatmap", factory: (require) => { var module = { exports: {} }; var exports = module.exports;'; const banner = 'window.__ModuleLoader__.load({ id: "token-contribution", factory: (require) => { var module = { exports: {} }; var exports = module.exports;';
const footer = 'return module.exports; } });'; const footer = 'return module.exports; } });';
execSync(`npx esbuild src/client/index.tsx --outfile=lib/client.js --platform=browser --format=cjs --target=es2022 --bundle --external:react --external:react-dom --external:react-dom/client --banner:js='${banner}' --footer:js='${footer}' --sourcemap`, { stdio: "inherit" }); execSync(`npx esbuild src/client/index.tsx --outfile=lib/client.js --platform=browser --format=cjs --target=es2022 --bundle --external:react --external:react-dom --external:react-dom/client --banner:js='${banner}' --footer:js='${footer}' --sourcemap`, { stdio: "inherit" });

View file

@ -116,19 +116,28 @@ function HeatmapInnerView() {
turns: number; turns: number;
} | null>(null); } | null>(null);
const [tooltipPos, setTooltipPos] = useState<{ x: number; y: number } | null>(null); const [tooltipPos, setTooltipPos] = useState<{ x: number; y: number } | null>(null);
const rootRef = useRef<HTMLDivElement>(null);
// Auto-resize the settings dialog panel smoothly on mount so it comfortably fits the content // Dynamically enlarge the DSH Settings dialog panel so the calendar fits perfectly without a scrollbar
useEffect(() => { useEffect(() => {
if (typeof document === "undefined") return; if (typeof document === "undefined") return;
const panel = document.querySelector('[class*="_panel"]') as HTMLElement | null; const findPanel = () => {
return (
rootRef.current?.closest('[class*="_panel"]') ||
document.querySelector('.VOzbGW_panel') ||
document.querySelector('[class*="_panel"]')
) as HTMLElement | null;
};
const panel = findPanel();
if (panel) { if (panel) {
const prevWidth = panel.style.width; const prevWidth = panel.style.width;
const prevMaxWidth = panel.style.maxWidth; const prevMaxWidth = panel.style.maxWidth;
const prevTransition = panel.style.transition; const prevTransition = panel.style.transition;
panel.style.transition = "width 0.25s cubic-bezier(0.16, 1, 0.3, 1), max-width 0.25s ease"; panel.style.transition = "width 0.25s cubic-bezier(0.16, 1, 0.3, 1), max-width 0.25s ease";
panel.style.width = "min(1080px, calc(100vw - 32px))"; panel.style.setProperty("width", "min(1180px, calc(100vw - 32px))", "important");
panel.style.maxWidth = "calc(100vw - 32px)"; panel.style.setProperty("max-width", "calc(100vw - 32px)", "important");
return () => { return () => {
panel.style.width = prevWidth; panel.style.width = prevWidth;
@ -140,7 +149,7 @@ function HeatmapInnerView() {
const fetchStats = () => { const fetchStats = () => {
setLoading(true); setLoading(true);
fetch("/api/dsh-token-heatmap/stats") fetch("/api/token-contribution/stats")
.then((r) => { .then((r) => {
if (!r.ok) throw new Error(`HTTP error ${r.status}`); if (!r.ok) throw new Error(`HTTP error ${r.status}`);
return r.json(); return r.json();
@ -405,6 +414,7 @@ function HeatmapInnerView() {
return ( return (
<div <div
data-dsh-heatmap="true" data-dsh-heatmap="true"
ref={rootRef}
style={{ style={{
boxSizing: "border-box", boxSizing: "border-box",
width: "100%", width: "100%",
@ -570,14 +580,14 @@ function HeatmapInnerView() {
)} )}
</div> </div>
{/* Clean, centered SVG Calendar Grid (Fits panel smoothly) */} {/* Clean, centered SVG Calendar Grid (Fits 1180px panel with zero scrollbars) */}
{calendarGrid && ( {calendarGrid && (
<div style={{ width: "100%", display: "flex", flexDirection: "column", alignItems: "center" }}> <div style={{ width: "100%", display: "flex", flexDirection: "column", alignItems: "center" }}>
<svg <svg
viewBox="0 0 770 120" viewBox="0 0 880 136"
style={{ style={{
width: "100%", width: "100%",
maxWidth: "770px", maxWidth: "880px",
height: "auto", height: "auto",
display: "block", display: "block",
overflow: "visible", overflow: "visible",
@ -587,9 +597,9 @@ function HeatmapInnerView() {
{monthHeaders.map((m, i) => ( {monthHeaders.map((m, i) => (
<text <text
key={i} key={i}
x={28 + m.col * 13.8} x={32 + m.col * 15.6}
y={10} y={11}
fontSize={10} fontSize={11}
fill="var(--dsw-alias-label-tertiary, #888888)" fill="var(--dsw-alias-label-tertiary, #888888)"
fontFamily="inherit" fontFamily="inherit"
> >
@ -598,19 +608,19 @@ function HeatmapInnerView() {
))} ))}
{/* Day of week labels */} {/* Day of week labels */}
<text x={2} y={39} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit"> <text x={2} y={45} fontSize={10} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Mon Mon
</text> </text>
<text x={2} y={67} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit"> <text x={2} y={76} fontSize={10} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Wed Wed
</text> </text>
<text x={2} y={95} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit"> <text x={2} y={107} fontSize={10} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Fri Fri
</text> </text>
{/* Day Tiles: centered and proportioned */} {/* Day Tiles: comfortable 12px with 15.6px step */}
{calendarGrid.map((week, colIdx) => ( {calendarGrid.map((week, colIdx) => (
<g key={colIdx} transform={`translate(${28 + colIdx * 13.8}, 18)`}> <g key={colIdx} transform={`translate(${32 + colIdx * 15.6}, 18)`}>
{week.map((day, rowIdx) => { {week.map((day, rowIdx) => {
if (!day) return null; if (!day) return null;
@ -628,17 +638,17 @@ function HeatmapInnerView() {
? "rgba(255, 255, 255, 0.06)" ? "rgba(255, 255, 255, 0.06)"
: "transparent"; : "transparent";
const strokeWidth = isSelected ? 1.6 : isHovered ? 1.3 : 1; const strokeWidth = isSelected ? 1.8 : isHovered ? 1.4 : 1;
return ( return (
<rect <rect
key={rowIdx} key={rowIdx}
x={0} x={0}
y={rowIdx * 14} y={rowIdx * 15.6}
width={11} width={12}
height={11} height={12}
rx={2} rx={2.5}
ry={2} ry={2.5}
fill={bg} fill={bg}
stroke={strokeColor} stroke={strokeColor}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
@ -676,7 +686,7 @@ function HeatmapInnerView() {
<div <div
style={{ style={{
width: "100%", width: "100%",
maxWidth: "770px", maxWidth: "880px",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
@ -695,9 +705,9 @@ function HeatmapInnerView() {
<div <div
key={i} key={i}
style={{ style={{
width: 11, width: 12,
height: 11, height: 12,
borderRadius: 2, borderRadius: 2.5,
backgroundColor: c, backgroundColor: c,
border: i === 0 ? "1px solid rgba(255, 255, 255, 0.06)" : "none", border: i === 0 ? "1px solid rgba(255, 255, 255, 0.06)" : "none",
filter: i === 4 ? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" : "none", filter: i === 4 ? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" : "none",

View file

@ -3,19 +3,19 @@ import { createRoot } from "react-dom/client";
import { HeatmapView } from "./HeatmapView.js"; import { HeatmapView } from "./HeatmapView.js";
import { HeatmapModal } from "./HeatmapModal.js"; import { HeatmapModal } from "./HeatmapModal.js";
export const name = "dsh-plugin-token-heatmap-client"; export const name = "token-contribution-client";
export const inject = ["slots"]; export const inject = ["slots"];
export function apply(ctx: any) { export function apply(ctx: any) {
// 1. Register under Settings -> "Token Heatmap" // 1. Register under Settings -> "Token Contribution"
if (ctx.slots && typeof ctx.slots.inject === "function") { if (ctx.slots && typeof ctx.slots.inject === "function") {
ctx.slots.inject("settings.section", () => { ctx.slots.inject("settings.section", () => {
return ctx.slots.register( return ctx.slots.register(
{ {
name: "settings.section", name: "settings.section",
id: "token-heatmap", id: "token-contribution",
order: 14, order: 14,
label: () => "Token Heatmap", label: () => "Token Contribution",
icon: "chart", icon: "chart",
}, },
HeatmapView HeatmapView
@ -25,8 +25,24 @@ export function apply(ctx: any) {
// 2. Global Modal Host & In-Session Token Meter Integration // 2. Global Modal Host & In-Session Token Meter Integration
if (typeof document !== "undefined") { if (typeof document !== "undefined") {
// Inject dynamic resizing stylesheet for settings dialog
if (!document.getElementById("dsh-token-heatmap-panel-resize")) {
const resizeStyle = document.createElement("style");
resizeStyle.id = "dsh-token-heatmap-panel-resize";
resizeStyle.textContent = `
/* Dynamically widen DSH Settings panel when Token Heatmap is active */
.VOzbGW_panel:has([data-dsh-heatmap]),
[class*="_panel"]:has([data-dsh-heatmap]) {
width: min(1180px, calc(100vw - 32px)) !important;
max-width: calc(100vw - 32px) !important;
transition: width 0.25s cubic-bezier(0.16, 1, 0.3, 1), max-width 0.25s ease !important;
}
`;
document.head.appendChild(resizeStyle);
}
const modalContainer = document.createElement("div"); const modalContainer = document.createElement("div");
modalContainer.id = "dsh-token-heatmap-modal-root"; modalContainer.id = "token-contribution-modal-root";
document.body.appendChild(modalContainer); document.body.appendChild(modalContainer);
const root = createRoot(modalContainer); const root = createRoot(modalContainer);
@ -38,12 +54,14 @@ export function apply(ctx: any) {
const handleOpen = () => setIsOpen(true); const handleOpen = () => setIsOpen(true);
const handleClose = () => setIsOpen(false); const handleClose = () => setIsOpen(false);
window.addEventListener("dsh:open-token-contribution", handleOpen);
window.addEventListener("dsh:open-token-heatmap", handleOpen); window.addEventListener("dsh:open-token-heatmap", handleOpen);
window.addEventListener("keydown", (e) => { window.addEventListener("keydown", (e) => {
if (e.key === "Escape") handleClose(); if (e.key === "Escape") handleClose();
}); });
return () => { return () => {
window.removeEventListener("dsh:open-token-contribution", handleOpen);
window.removeEventListener("dsh:open-token-heatmap", handleOpen); window.removeEventListener("dsh:open-token-heatmap", handleOpen);
}; };
}, []); }, []);
@ -70,7 +88,7 @@ export function apply(ctx: any) {
const button = document.createElement("button"); const button = document.createElement("button");
button.className = "dsh-heatmap-trigger-btn"; button.className = "dsh-heatmap-trigger-btn";
button.type = "button"; button.type = "button";
button.innerHTML = "<span>View Token Usage Heatmap →</span>"; button.innerHTML = "<span>View Token Contribution →</span>";
button.style.cssText = ` button.style.cssText = `
display: flex; display: flex;
align-items: center; align-items: center;
@ -112,18 +130,18 @@ export function apply(ctx: any) {
setTimeout(() => { setTimeout(() => {
const heatmapTab = Array.from(document.querySelectorAll("*")).find( const heatmapTab = Array.from(document.querySelectorAll("*")).find(
(el) => (el) =>
el.textContent?.trim() === "Token Heatmap" && (el.textContent?.trim() === "Token Contribution" || el.textContent?.trim() === "Token Heatmap") &&
(el.tagName === "BUTTON" || el.tagName === "DIV" || el.getAttribute("role") === "tab" || el.classList.contains("ant-list-item")) (el.tagName === "BUTTON" || el.tagName === "DIV" || el.getAttribute("role") === "tab" || el.classList.contains("ant-list-item"))
); );
if (heatmapTab) { if (heatmapTab) {
(heatmapTab as HTMLElement).click(); (heatmapTab as HTMLElement).click();
} else { } else {
window.dispatchEvent(new CustomEvent("dsh:open-token-heatmap")); window.dispatchEvent(new CustomEvent("dsh:open-token-contribution"));
} }
}, 120); }, 120);
} else { } else {
// Open direct interactive modal // Open direct interactive modal
window.dispatchEvent(new CustomEvent("dsh:open-token-heatmap")); window.dispatchEvent(new CustomEvent("dsh:open-token-contribution"));
} }
}; };

View file

@ -1,11 +1,11 @@
export const name = "dsh-plugin-token-heatmap"; export const name = "token-contribution";
export const inject = ["webServer"]; export const inject = ["webServer"];
export function apply(ctx: any) { export function apply(ctx: any) {
ctx.effect(() => ctx.effect(() =>
ctx.webServer.register({ ctx.webServer.register({
kind: "prefix", kind: "prefix",
path: "/api/dsh-token-heatmap", path: "/api/token-contribution",
handler: async (req: any, res: any) => { handler: async (req: any, res: any) => {
const url = new URL(req.url, "http://localhost"); const url = new URL(req.url, "http://localhost");
const pathname = url.pathname.replace(/\/+$/, ""); const pathname = url.pathname.replace(/\/+$/, "");
@ -20,7 +20,7 @@ export function apply(ctx: any) {
return; return;
} }
if (req.method === "GET" && (pathname === "/api/dsh-token-heatmap" || pathname === "/api/dsh-token-heatmap/stats")) { if (req.method === "GET" && (pathname === "/api/token-contribution" || pathname === "/api/token-contribution/stats" || pathname === "/api/dsh-token-heatmap/stats")) {
try { try {
const { aggregateTokens } = await import(`./aggregator.js?t=${Date.now()}`); const { aggregateTokens } = await import(`./aggregator.js?t=${Date.now()}`);
const data = await aggregateTokens(); const data = await aggregateTokens();