feat: center total token usage, auto-resize settings panel to 1080px, clear blue color on cache, add model breakdown

This commit is contained in:
katR 2026-10-10 20:05:22 +05:30
parent 2b15144ae5
commit a151fcba93
6 changed files with 683 additions and 365 deletions

View file

@ -28,6 +28,7 @@ async function parseSessionFile(filePath) {
} }
const isZstd = filePath.endsWith(".zstd"); const isZstd = filePath.endsWith(".zstd");
const days = {}; const days = {};
const sessionModels = {};
let sessionId = path.basename(path.dirname(filePath)); let sessionId = path.basename(path.dirname(filePath));
let title = ""; let title = "";
let createdAt = stat.birthtimeMs || stat.mtimeMs; let createdAt = stat.birthtimeMs || stat.mtimeMs;
@ -74,6 +75,7 @@ async function parseSessionFile(filePath) {
const cacheRead = Number(usage.cacheReadTokens) || 0; const cacheRead = Number(usage.cacheReadTokens) || 0;
const cacheWrite = Number(usage.cacheWriteTokens) || 0; const cacheWrite = Number(usage.cacheWriteTokens) || 0;
const total = input + output + cacheRead; const total = input + output + cacheRead;
const model = obj.data?.message?.source?.model || obj.data?.model || obj.model || "gemini-3.8-flash-tiered";
if (!days[dateKey]) { if (!days[dateKey]) {
days[dateKey] = { days[dateKey] = {
totalTokens: 0, totalTokens: 0,
@ -90,6 +92,14 @@ async function parseSessionFile(filePath) {
days[dateKey].cacheReadTokens += cacheRead; days[dateKey].cacheReadTokens += cacheRead;
days[dateKey].cacheWriteTokens += cacheWrite; days[dateKey].cacheWriteTokens += cacheWrite;
days[dateKey].turns += 1; days[dateKey].turns += 1;
if (!sessionModels[model]) {
sessionModels[model] = { totalTokens: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, turns: 0 };
}
sessionModels[model].totalTokens += total;
sessionModels[model].inputTokens += input;
sessionModels[model].outputTokens += output;
sessionModels[model].cacheReadTokens += cacheRead;
sessionModels[model].turns += 1;
totalTokens += total; totalTokens += total;
totalInputTokens += input; totalInputTokens += input;
totalOutputTokens += output; totalOutputTokens += output;
@ -143,7 +153,8 @@ async function parseSessionFile(filePath) {
outputTokens: totalOutputTokens, outputTokens: totalOutputTokens,
cacheReadTokens: totalCacheReadTokens, cacheReadTokens: totalCacheReadTokens,
turns: totalTurns, turns: totalTurns,
days days,
models: sessionModels
}; };
fileCache.set(filePath, { mtimeMs: stat.mtimeMs, session }); fileCache.set(filePath, { mtimeMs: stat.mtimeMs, session });
return session; return session;
@ -157,6 +168,7 @@ async function aggregateTokens(customSessionsRoot) {
const parsedSessions = (await Promise.all(files.map(parseSessionFile))).filter(Boolean); const parsedSessions = (await Promise.all(files.map(parseSessionFile))).filter(Boolean);
parsedSessions.sort((a, b) => b.createdAt - a.createdAt); parsedSessions.sort((a, b) => b.createdAt - a.createdAt);
const days = {}; const days = {};
const allModelsMap = {};
for (const session of parsedSessions) { for (const session of parsedSessions) {
for (const [date, stats] of Object.entries(session.days)) { for (const [date, stats] of Object.entries(session.days)) {
if (!days[date]) { if (!days[date]) {
@ -187,6 +199,18 @@ async function aggregateTokens(customSessionsRoot) {
turns: stats.turns turns: stats.turns
}); });
} }
if (session.models) {
for (const [modelName, mStats] of Object.entries(session.models)) {
if (!allModelsMap[modelName]) {
allModelsMap[modelName] = { totalTokens: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, turns: 0 };
}
allModelsMap[modelName].totalTokens += mStats.totalTokens;
allModelsMap[modelName].inputTokens += mStats.inputTokens;
allModelsMap[modelName].outputTokens += mStats.outputTokens;
allModelsMap[modelName].cacheReadTokens += mStats.cacheReadTokens;
allModelsMap[modelName].turns += mStats.turns;
}
}
} }
for (const day of Object.values(days)) { for (const day of Object.values(days)) {
if (day.sessions) { if (day.sessions) {
@ -225,6 +249,15 @@ async function aggregateTokens(customSessionsRoot) {
peakDay = { date, tokens: stats.totalTokens, turns: stats.turns }; peakDay = { date, tokens: stats.totalTokens, turns: stats.turns };
} }
} }
const models = Object.entries(allModelsMap).map(([model, m]) => ({
model,
totalTokens: m.totalTokens,
inputTokens: m.inputTokens,
outputTokens: m.outputTokens,
cacheReadTokens: m.cacheReadTokens,
turns: m.turns,
percentage: totalTokens > 0 ? Math.round(m.totalTokens / totalTokens * 1e3) / 10 : 0
})).sort((a, b) => b.totalTokens - a.totalTokens);
const activeDays = Object.values(days).filter((d) => d.totalTokens > 0).length; const activeDays = Object.values(days).filter((d) => d.totalTokens > 0).length;
const dailyAverage = activeDays > 0 ? Math.round(totalTokens / activeDays) : 0; const dailyAverage = activeDays > 0 ? Math.round(totalTokens / activeDays) : 0;
const now = /* @__PURE__ */ new Date(); const now = /* @__PURE__ */ new Date();
@ -284,6 +317,7 @@ async function aggregateTokens(customSessionsRoot) {
}, },
days, days,
sessions: parsedSessions, sessions: parsedSessions,
models,
startDate, startDate,
endDate endDate
}; };

File diff suppressed because one or more lines are too long

View file

@ -72,7 +72,23 @@ 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 scrollContainerRef = (0, import_react.useRef)(null); (0, import_react.useEffect)(() => {
if (typeof document === "undefined") return;
const panel = document.querySelector('[class*="_panel"]');
if (panel) {
const prevWidth = panel.style.width;
const prevMaxWidth = panel.style.maxWidth;
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.width = "min(1080px, calc(100vw - 32px))";
panel.style.maxWidth = "calc(100vw - 32px)";
return () => {
panel.style.width = prevWidth;
panel.style.maxWidth = prevMaxWidth;
panel.style.transition = prevTransition;
};
}
}, []);
const fetchStats = () => { const fetchStats = () => {
setLoading(true); setLoading(true);
fetch("/api/dsh-token-heatmap/stats").then((r) => { fetch("/api/dsh-token-heatmap/stats").then((r) => {
@ -95,6 +111,7 @@ function HeatmapInnerView() {
return { return {
summary: data.summary, summary: data.summary,
days: data.days, days: data.days,
models: data.models || [],
sessionTitle: "All Sessions (Global)" sessionTitle: "All Sessions (Global)"
}; };
} }
@ -103,6 +120,7 @@ function HeatmapInnerView() {
return { return {
summary: data.summary, summary: data.summary,
days: data.days, days: data.days,
models: data.models || [],
sessionTitle: "All Sessions (Global)" sessionTitle: "All Sessions (Global)"
}; };
} }
@ -138,6 +156,15 @@ function HeatmapInnerView() {
else lvl = 4; else lvl = 4;
leveledDays[date] = { ...stats, level: lvl }; leveledDays[date] = { ...stats, level: lvl };
} }
const sessionModels = session.models ? Object.entries(session.models).map(([model, m]) => ({
model,
totalTokens: m.totalTokens,
inputTokens: m.inputTokens,
outputTokens: m.outputTokens,
cacheReadTokens: m.cacheReadTokens,
turns: m.turns,
percentage: totalTokens > 0 ? Math.round(m.totalTokens / totalTokens * 1e3) / 10 : 0
})).sort((a, b) => b.totalTokens - a.totalTokens) : [];
return { return {
summary: { summary: {
totalTokens, totalTokens,
@ -152,6 +179,7 @@ function HeatmapInnerView() {
dailyAverage dailyAverage
}, },
days: leveledDays, days: leveledDays,
models: sessionModels,
sessionTitle: session.title sessionTitle: session.title
}; };
}, [data, selectedSessionId]); }, [data, selectedSessionId]);
@ -207,11 +235,6 @@ function HeatmapInnerView() {
}); });
return headers; return headers;
}, [calendarGrid]); }, [calendarGrid]);
(0, import_react.useEffect)(() => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollLeft = scrollContainerRef.current.scrollWidth;
}
}, [calendarGrid]);
const chartData = (0, import_react.useMemo)(() => { const chartData = (0, import_react.useMemo)(() => {
if (!activeView) return []; if (!activeView) return [];
const now = /* @__PURE__ */ new Date(); const now = /* @__PURE__ */ new Date();
@ -270,12 +293,13 @@ function HeatmapInnerView() {
return /* @__PURE__ */ import_react.default.createElement( return /* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
"data-dsh-heatmap": "true",
style: { style: {
boxSizing: "border-box", boxSizing: "border-box",
width: "100%", width: "100%",
maxWidth: "960px", maxWidth: "1020px",
margin: "0 auto", margin: "0 auto",
padding: "16px 0 40px", padding: "8px 0 40px",
fontFamily: "inherit", fontFamily: "inherit",
color: "var(--dsw-alias-label-primary, #eeeeee)" color: "var(--dsw-alias-label-primary, #eeeeee)"
} }
@ -289,7 +313,7 @@ function HeatmapInnerView() {
justifyContent: "space-between", justifyContent: "space-between",
flexWrap: "wrap", flexWrap: "wrap",
gap: 14, gap: 14,
marginBottom: 20 marginBottom: 16
} }
}, },
/* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement(
@ -297,7 +321,7 @@ function HeatmapInnerView() {
{ {
style: { style: {
margin: 0, margin: 0,
fontSize: 22, fontSize: 20,
fontWeight: 650, fontWeight: 650,
letterSpacing: "-0.02em", letterSpacing: "-0.02em",
display: "flex", display: "flex",
@ -325,12 +349,12 @@ function HeatmapInnerView() {
"p", "p",
{ {
style: { style: {
margin: "5px 0 0", margin: "4px 0 0",
fontSize: 13, fontSize: 12,
color: "var(--dsw-alias-label-tertiary, #a0a0a0)" color: "var(--dsw-alias-label-tertiary, #a0a0a0)"
} }
}, },
"Daily token volume and request step intensity across sessions" "Daily token throughput and model breakdown across sessions"
)), )),
/* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6 } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-tertiary)" } }, "Session:"), /* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6 } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-tertiary)" } }, "Session:"), /* @__PURE__ */ import_react.default.createElement(
"select", "select",
@ -376,6 +400,149 @@ function HeatmapInnerView() {
"\u21BB Refresh" "\u21BB Refresh"
)) ))
), ),
/* @__PURE__ */ import_react.default.createElement(
"div",
{
style: {
textAlign: "center",
margin: "12px 0 20px",
padding: "16px 20px",
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 12,
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)"
}
},
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--dsw-alias-label-tertiary, #a0a0a0)", fontWeight: 600 } }, "Total Tokens Processed"),
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 36, fontWeight: 800, margin: "6px 0 2px", color: "#4f85ff", letterSpacing: "-0.03em" } }, formatExact(summary.totalTokens)),
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 13, color: "var(--dsw-alias-label-secondary, #b9b9b9)" } }, /* @__PURE__ */ import_react.default.createElement("strong", null, formatNumber(summary.totalTokens)), " tokens across ", summary.activeDays, " active days (", summary.totalTurns, " total requests)")
),
/* @__PURE__ */ import_react.default.createElement(
"div",
{
style: {
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 12,
padding: "18px 20px",
position: "relative",
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)"
}
},
/* @__PURE__ */ import_react.default.createElement(
"div",
{
style: {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 12,
fontSize: 13,
fontWeight: 600
}
},
/* @__PURE__ */ import_react.default.createElement("span", null, "Contribution Calendar"),
summary.peakDay && /* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 12, fontWeight: 400, color: "var(--dsw-alias-label-secondary)" } }, "Peak day: ", /* @__PURE__ */ import_react.default.createElement("strong", { style: { color: "var(--dsw-alias-label-primary)" } }, summary.peakDay.date), " (", formatNumber(summary.peakDay.tokens), " tokens)")
),
calendarGrid && /* @__PURE__ */ import_react.default.createElement("div", { style: { width: "100%", display: "flex", flexDirection: "column", alignItems: "center" } }, /* @__PURE__ */ import_react.default.createElement(
"svg",
{
viewBox: "0 0 770 120",
style: {
width: "100%",
maxWidth: "770px",
height: "auto",
display: "block",
overflow: "visible"
}
},
monthHeaders.map((m, i) => /* @__PURE__ */ import_react.default.createElement(
"text",
{
key: i,
x: 28 + m.col * 13.8,
y: 10,
fontSize: 10,
fill: "var(--dsw-alias-label-tertiary, #888888)",
fontFamily: "inherit"
},
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: 67, fontSize: 9, 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"),
calendarGrid.map((week, colIdx) => /* @__PURE__ */ import_react.default.createElement("g", { key: colIdx, transform: `translate(${28 + colIdx * 13.8}, 18)` }, week.map((day, rowIdx) => {
if (!day) return null;
const lvl = day.stats.level || 0;
const bg = DEEPSEEK_LEVELS[lvl];
const isSelected = selectedDate === day.date;
const isHovered = hoveredDay?.date === day.date;
const strokeColor = isSelected ? "#ffffff" : isHovered ? "#5686fe" : lvl === 0 ? "rgba(255, 255, 255, 0.06)" : "transparent";
const strokeWidth = isSelected ? 1.6 : isHovered ? 1.3 : 1;
return /* @__PURE__ */ import_react.default.createElement(
"rect",
{
key: rowIdx,
x: 0,
y: rowIdx * 14,
width: 11,
height: 11,
rx: 2,
ry: 2,
fill: bg,
stroke: strokeColor,
strokeWidth,
style: {
cursor: "pointer",
transition: "fill 0.12s ease, stroke 0.12s ease",
filter: isSelected ? "drop-shadow(0 0 6px rgba(86, 134, 254, 0.9))" : isHovered ? "drop-shadow(0 0 4px rgba(86, 134, 254, 0.7))" : lvl === 4 ? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" : "none"
},
onClick: () => {
setSelectedDate(isSelected ? null : day.date);
},
onMouseEnter: (e) => {
setHoveredDay({ date: day.date, stats: day.stats });
const rect = e.currentTarget.getBoundingClientRect();
setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
},
onMouseLeave: () => {
setHoveredDay(null);
setTooltipPos(null);
}
}
);
})))
), /* @__PURE__ */ import_react.default.createElement(
"div",
{
style: {
width: "100%",
maxWidth: "770px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginTop: 12,
fontSize: 11,
color: "var(--dsw-alias-label-tertiary, #a0a0a0)"
}
},
/* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 11 } }, "Tip: Click any day to inspect sessions and token breakdown"),
/* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6 } }, /* @__PURE__ */ import_react.default.createElement("span", null, "Less"), DEEPSEEK_LEVELS.map((c, i) => /* @__PURE__ */ import_react.default.createElement(
"div",
{
key: i,
style: {
width: 11,
height: 11,
borderRadius: 2,
backgroundColor: c,
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"
}
}
)), /* @__PURE__ */ import_react.default.createElement("span", null, "More"))
))
),
/* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
@ -383,24 +550,10 @@ function HeatmapInnerView() {
display: "grid", display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
gap: 12, gap: 12,
marginBottom: 20 marginTop: 18,
marginBottom: 18
} }
}, },
/* @__PURE__ */ import_react.default.createElement(
"div",
{
style: {
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 10,
padding: "14px 16px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)"
}
},
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 } }, "Total Tokens"),
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 24, fontWeight: 700, margin: "6px 0 2px", color: "#4f85ff" } }, formatNumber(summary.totalTokens)),
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" } }, formatExact(summary.totalTokens), " tokens")
),
/* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
@ -413,7 +566,7 @@ function HeatmapInnerView() {
} }
}, },
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 } }, "Cache Hit Ratio"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 } }, "Cache Hit Ratio"),
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 24, fontWeight: 700, margin: "6px 0 2px", color: "#38bdf8" } }, cacheHitPercent, "%"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 24, fontWeight: 700, margin: "6px 0 2px", color: "var(--dsw-alias-label-primary, #ffffff)" } }, cacheHitPercent, "%"),
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" } }, formatNumber(summary.totalCacheReadTokens), " cached reads") /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" } }, formatNumber(summary.totalCacheReadTokens), " cached reads")
), ),
/* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement(
@ -445,18 +598,33 @@ function HeatmapInnerView() {
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 } }, selectedSessionId === "all" ? "Active Streak" : "Active Days"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 } }, selectedSessionId === "all" ? "Active Streak" : "Active Days"),
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 24, fontWeight: 700, margin: "6px 0 2px", color: "var(--dsw-alias-label-primary, #ffffff)" } }, selectedSessionId === "all" ? `${summary.currentStreak} days` : `${summary.activeDays} days`), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 24, fontWeight: 700, margin: "6px 0 2px", color: "var(--dsw-alias-label-primary, #ffffff)" } }, selectedSessionId === "all" ? `${summary.currentStreak} days` : `${summary.activeDays} days`),
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" } }, selectedSessionId === "all" ? `Longest streak: ${summary.longestStreak} days` : `Session: ${activeView.sessionTitle.slice(0, 24)}`) /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" } }, selectedSessionId === "all" ? `Longest streak: ${summary.longestStreak} days` : `Session: ${activeView.sessionTitle.slice(0, 24)}`)
),
/* @__PURE__ */ import_react.default.createElement(
"div",
{
style: {
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 10,
padding: "14px 16px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)"
}
},
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 } }, "Peak Volume Day"),
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 20, fontWeight: 700, margin: "6px 0 2px", color: "var(--dsw-alias-label-primary, #ffffff)" } }, summary.peakDay ? formatNumber(summary.peakDay.tokens) : "None"),
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" } }, summary.peakDay ? summary.peakDay.date : "No activity")
) )
), ),
/* @__PURE__ */ import_react.default.createElement( activeView.models && activeView.models.length > 0 && /* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
style: { style: {
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)", background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)", border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 12, borderRadius: 12,
padding: "20px 22px", padding: "18px 20px",
position: "relative", marginBottom: 18,
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)" boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)"
} }
}, },
/* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement(
@ -466,133 +634,57 @@ function HeatmapInnerView() {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
marginBottom: 14, marginBottom: 12
fontSize: 13,
fontWeight: 600
} }
}, },
/* @__PURE__ */ import_react.default.createElement("span", null, "Contribution Calendar"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 13, fontWeight: 650, color: "var(--dsw-alias-label-primary, #ffffff)" } }, "Model Usage Breakdown"),
summary.peakDay && /* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 12, fontWeight: 400, color: "var(--dsw-alias-label-secondary)" } }, "Peak day: ", /* @__PURE__ */ import_react.default.createElement("strong", { style: { color: "var(--dsw-alias-label-primary)" } }, summary.peakDay.date), " (", formatNumber(summary.peakDay.tokens), " tokens)") /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)" } }, activeView.models.length, " model", activeView.models.length === 1 ? "" : "s", " tracked")
), ),
calendarGrid && /* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 10 } }, activeView.models.map((m) => /* @__PURE__ */ import_react.default.createElement("div", { key: m.model }, /* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
ref: scrollContainerRef,
style: { style: {
width: "100%", display: "flex",
overflowX: "auto", justifyContent: "space-between",
WebkitOverflowScrolling: "touch", fontSize: 12,
paddingBottom: "10px" marginBottom: 5
}
},
/* @__PURE__ */ import_react.default.createElement("span", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary)" } }, m.model),
/* @__PURE__ */ import_react.default.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, /* @__PURE__ */ import_react.default.createElement("strong", { style: { color: "#4f85ff" } }, formatNumber(m.totalTokens)), " (", m.percentage, "%) \xB7 ", m.turns, " requests")
), /* @__PURE__ */ import_react.default.createElement(
"div",
{
style: {
width: "100%",
height: 6,
background: "rgba(255, 255, 255, 0.06)",
borderRadius: 3,
overflow: "hidden"
} }
}, },
/* @__PURE__ */ import_react.default.createElement("div", { style: { minWidth: "920px", width: "100%", display: "inline-block" } }, /* @__PURE__ */ import_react.default.createElement(
"svg",
{
viewBox: "0 0 920 152",
style: {
width: "100%",
minWidth: "920px",
height: "152px",
display: "block",
overflow: "visible"
}
},
monthHeaders.map((m, i) => /* @__PURE__ */ import_react.default.createElement(
"text",
{
key: i,
x: 36 + m.col * 16.5,
y: 12,
fontSize: 11,
fill: "var(--dsw-alias-label-tertiary, #888888)",
fontFamily: "inherit"
},
m.label
)),
/* @__PURE__ */ import_react.default.createElement("text", { x: 4, y: 51, fontSize: 10, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Mon"),
/* @__PURE__ */ import_react.default.createElement("text", { x: 4, y: 85, fontSize: 10, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Wed"),
/* @__PURE__ */ import_react.default.createElement("text", { x: 4, y: 119, 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(${36 + colIdx * 16.5}, 22)` }, week.map((day, rowIdx) => {
if (!day) return null;
const lvl = day.stats.level || 0;
const bg = DEEPSEEK_LEVELS[lvl];
const isSelected = selectedDate === day.date;
const isHovered = hoveredDay?.date === day.date;
const strokeColor = isSelected ? "#ffffff" : isHovered ? "#5686fe" : lvl === 0 ? "rgba(255, 255, 255, 0.06)" : "transparent";
const strokeWidth = isSelected ? 2 : isHovered ? 1.5 : 1;
return /* @__PURE__ */ import_react.default.createElement(
"rect",
{
key: rowIdx,
x: 0,
y: rowIdx * 17,
width: 13,
height: 13,
rx: 2.5,
ry: 2.5,
fill: bg,
stroke: strokeColor,
strokeWidth,
style: {
cursor: "pointer",
transition: "fill 0.12s ease, stroke 0.12s ease",
filter: isSelected ? "drop-shadow(0 0 6px rgba(86, 134, 254, 0.9))" : isHovered ? "drop-shadow(0 0 4px rgba(86, 134, 254, 0.7))" : lvl === 4 ? "drop-shadow(0 0 4px rgba(79, 133, 255, 0.6))" : "none"
},
onClick: () => {
setSelectedDate(isSelected ? null : day.date);
},
onMouseEnter: (e) => {
setHoveredDay({ date: day.date, stats: day.stats });
const rect = e.currentTarget.getBoundingClientRect();
setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
},
onMouseLeave: () => {
setHoveredDay(null);
setTooltipPos(null);
}
}
);
})))
)),
/* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
style: { style: {
display: "flex", width: `${Math.max(1, m.percentage)}%`,
alignItems: "center", height: "100%",
justifyContent: "space-between", background: "#336dfa",
marginTop: 14, borderRadius: 3,
fontSize: 11, transition: "width 0.3s ease"
color: "var(--dsw-alias-label-tertiary, #a0a0a0)"
} }
}, }
/* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 11 } }, "Tip: Click any day to inspect sessions and token breakdown"),
/* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6 } }, /* @__PURE__ */ import_react.default.createElement("span", null, "Less"), DEEPSEEK_LEVELS.map((c, i) => /* @__PURE__ */ import_react.default.createElement(
"div",
{
key: i,
style: {
width: 12.5,
height: 12.5,
borderRadius: 2.5,
backgroundColor: c,
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"
}
}
)), /* @__PURE__ */ import_react.default.createElement("span", null, "More"))
) )
) ))))
), ),
/* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
style: { style: {
marginTop: 18,
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)", background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)", border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 12, borderRadius: 12,
padding: "20px 22px", padding: "18px 20px",
position: "relative", position: "relative",
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)" boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)"
} }
@ -609,8 +701,8 @@ function HeatmapInnerView() {
marginBottom: 16 marginBottom: 16
} }
}, },
/* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 14, fontWeight: 650, color: "var(--dsw-alias-label-primary, #ffffff)" } }, "Token Usage Trends"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 12, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", marginTop: 2 } }, "Daily billed vs cached token volume over time")), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 13, fontWeight: 650, color: "var(--dsw-alias-label-primary, #ffffff)" } }, "Token Usage Trends"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", marginTop: 2 } }, "Daily billed vs cached token volume over time")),
/* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap" } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 12, fontSize: 11, color: "var(--dsw-alias-label-secondary)" } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 5 } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { width: 9, height: 9, borderRadius: 2, background: "#4f85ff" } }), /* @__PURE__ */ import_react.default.createElement("span", null, "Billed Tokens")), /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 5 } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { width: 9, height: 9, borderRadius: 2, background: "#253b82" } }), /* @__PURE__ */ import_react.default.createElement("span", null, "Cache Reads"))), /* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap" } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 12, fontSize: 11, color: "var(--dsw-alias-label-secondary)" } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 5 } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { width: 8, height: 8, borderRadius: 2, background: "#4f85ff" } }), /* @__PURE__ */ import_react.default.createElement("span", null, "Billed Tokens")), /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 5 } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { width: 8, height: 8, borderRadius: 2, background: "#253b82" } }), /* @__PURE__ */ import_react.default.createElement("span", null, "Cache Reads"))), /* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
style: { style: {
@ -642,7 +734,7 @@ function HeatmapInnerView() {
)) ))
)) ))
), ),
/* @__PURE__ */ import_react.default.createElement("div", { style: { width: "100%", overflowX: "auto", WebkitOverflowScrolling: "touch" } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { minWidth: "720px", width: "100%" } }, /* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement("div", { style: { width: "100%", overflowX: "auto", WebkitOverflowScrolling: "touch" } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { minWidth: "680px", width: "100%" } }, /* @__PURE__ */ import_react.default.createElement(
"svg", "svg",
{ {
viewBox: "0 0 880 180", viewBox: "0 0 880 180",
@ -822,9 +914,9 @@ function HeatmapInnerView() {
}, },
/* @__PURE__ */ import_react.default.createElement("div", { style: { padding: "6px 12px", background: "var(--dsw-alias-bg-layer-1, #1e1e1e)", borderRadius: 6, border: "1px solid var(--dsw-alias-border-l1)" } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { color: "var(--dsw-alias-label-tertiary)" } }, "Billed Input: "), /* @__PURE__ */ import_react.default.createElement("strong", null, formatNumber(inspectedDayStats.inputTokens))), /* @__PURE__ */ import_react.default.createElement("div", { style: { padding: "6px 12px", background: "var(--dsw-alias-bg-layer-1, #1e1e1e)", borderRadius: 6, border: "1px solid var(--dsw-alias-border-l1)" } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { color: "var(--dsw-alias-label-tertiary)" } }, "Billed Input: "), /* @__PURE__ */ import_react.default.createElement("strong", null, formatNumber(inspectedDayStats.inputTokens))),
/* @__PURE__ */ import_react.default.createElement("div", { style: { padding: "6px 12px", background: "var(--dsw-alias-bg-layer-1, #1e1e1e)", borderRadius: 6, border: "1px solid var(--dsw-alias-border-l1)" } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { color: "var(--dsw-alias-label-tertiary)" } }, "Output Generated: "), /* @__PURE__ */ import_react.default.createElement("strong", null, formatNumber(inspectedDayStats.outputTokens))), /* @__PURE__ */ import_react.default.createElement("div", { style: { padding: "6px 12px", background: "var(--dsw-alias-bg-layer-1, #1e1e1e)", borderRadius: 6, border: "1px solid var(--dsw-alias-border-l1)" } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { color: "var(--dsw-alias-label-tertiary)" } }, "Output Generated: "), /* @__PURE__ */ import_react.default.createElement("strong", null, formatNumber(inspectedDayStats.outputTokens))),
/* @__PURE__ */ import_react.default.createElement("div", { style: { padding: "6px 12px", background: "var(--dsw-alias-bg-layer-1, #1e1e1e)", borderRadius: 6, border: "1px solid var(--dsw-alias-border-l1)" } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { color: "var(--dsw-alias-label-tertiary)" } }, "Cache Reads: "), /* @__PURE__ */ import_react.default.createElement("strong", { style: { color: "#38bdf8" } }, formatNumber(inspectedDayStats.cacheReadTokens)), " (", Math.round(inspectedDayStats.cacheReadTokens / inspectedDayStats.totalTokens * 100), "%)") /* @__PURE__ */ import_react.default.createElement("div", { style: { padding: "6px 12px", background: "var(--dsw-alias-bg-layer-1, #1e1e1e)", borderRadius: 6, border: "1px solid var(--dsw-alias-border-l1)" } }, /* @__PURE__ */ import_react.default.createElement("span", { style: { color: "var(--dsw-alias-label-tertiary)" } }, "Cache Reads: "), /* @__PURE__ */ import_react.default.createElement("strong", null, formatNumber(inspectedDayStats.cacheReadTokens)), " (", Math.round(inspectedDayStats.cacheReadTokens / inspectedDayStats.totalTokens * 100), "%)")
), ),
/* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary)", marginBottom: 10 } }, "Sessions Active on this Day (", inspectedDayStats?.sessions?.length || 0, ")"), inspectedDayStats?.sessions && inspectedDayStats.sessions.length > 0 ? /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8, maxHeight: 320, overflowY: "auto" } }, inspectedDayStats.sessions.map((sess, idx) => /* @__PURE__ */ import_react.default.createElement( /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary)", marginBottom: 10 } }, "Sessions Active on this Day (", (inspectedDayStats?.sessions || []).length, ")"), inspectedDayStats?.sessions && inspectedDayStats.sessions.length > 0 ? /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8, maxHeight: 320, overflowY: "auto" } }, inspectedDayStats.sessions.map((sess, idx) => /* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
key: idx, key: idx,

File diff suppressed because one or more lines are too long

View file

@ -22,6 +22,16 @@ export interface DayStats {
sessions?: DaySessionDetail[]; sessions?: DaySessionDetail[];
} }
export interface ModelUsage {
model: string;
totalTokens: number;
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;
turns: number;
percentage: number;
}
export interface SessionSummary { export interface SessionSummary {
id: string; id: string;
title: string; title: string;
@ -32,6 +42,7 @@ export interface SessionSummary {
cacheReadTokens: number; cacheReadTokens: number;
turns: number; turns: number;
days: Record<string, DayStats>; days: Record<string, DayStats>;
models?: Record<string, { totalTokens: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; turns: number }>;
} }
export interface SummaryStats { export interface SummaryStats {
@ -51,6 +62,7 @@ export interface HeatmapResponse {
summary: SummaryStats; summary: SummaryStats;
days: Record<string, DayStats>; days: Record<string, DayStats>;
sessions: SessionSummary[]; sessions: SessionSummary[];
models?: ModelUsage[];
startDate: string; startDate: string;
endDate: string; endDate: string;
} }
@ -78,6 +90,15 @@ const DEEPSEEK_LEVELS = [
"#4f85ff", // Level 4: Bright electric DeepSeek blue "#4f85ff", // Level 4: Bright electric DeepSeek blue
]; ];
// Clean depth for tiles (subtle glow on peak tiles only)
const TILE_SHADOWS = [
"none",
"none",
"none",
"0 0 3px rgba(51, 109, 250, 0.35)",
"0 0 6px rgba(79, 133, 255, 0.65)",
];
function HeatmapInnerView() { function HeatmapInnerView() {
const [data, setData] = useState<HeatmapResponse | null>(null); const [data, setData] = useState<HeatmapResponse | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@ -95,7 +116,27 @@ 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 scrollContainerRef = useRef<HTMLDivElement>(null);
// Auto-resize the settings dialog panel smoothly on mount so it comfortably fits the content
useEffect(() => {
if (typeof document === "undefined") return;
const panel = document.querySelector('[class*="_panel"]') as HTMLElement | null;
if (panel) {
const prevWidth = panel.style.width;
const prevMaxWidth = panel.style.maxWidth;
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.width = "min(1080px, calc(100vw - 32px))";
panel.style.maxWidth = "calc(100vw - 32px)";
return () => {
panel.style.width = prevWidth;
panel.style.maxWidth = prevMaxWidth;
panel.style.transition = prevTransition;
};
}
}, []);
const fetchStats = () => { const fetchStats = () => {
setLoading(true); setLoading(true);
@ -126,6 +167,7 @@ function HeatmapInnerView() {
return { return {
summary: data.summary, summary: data.summary,
days: data.days, days: data.days,
models: data.models || [],
sessionTitle: "All Sessions (Global)", sessionTitle: "All Sessions (Global)",
}; };
} }
@ -135,6 +177,7 @@ function HeatmapInnerView() {
return { return {
summary: data.summary, summary: data.summary,
days: data.days, days: data.days,
models: data.models || [],
sessionTitle: "All Sessions (Global)", sessionTitle: "All Sessions (Global)",
}; };
} }
@ -180,6 +223,21 @@ function HeatmapInnerView() {
leveledDays[date] = { ...stats, level: lvl }; leveledDays[date] = { ...stats, level: lvl };
} }
// Models for this session
const sessionModels: ModelUsage[] = session.models
? Object.entries(session.models)
.map(([model, m]) => ({
model,
totalTokens: m.totalTokens,
inputTokens: m.inputTokens,
outputTokens: m.outputTokens,
cacheReadTokens: m.cacheReadTokens,
turns: m.turns,
percentage: totalTokens > 0 ? Math.round((m.totalTokens / totalTokens) * 1000) / 10 : 0,
}))
.sort((a, b) => b.totalTokens - a.totalTokens)
: [];
return { return {
summary: { summary: {
totalTokens, totalTokens,
@ -194,6 +252,7 @@ function HeatmapInnerView() {
dailyAverage, dailyAverage,
}, },
days: leveledDays, days: leveledDays,
models: sessionModels,
sessionTitle: session.title, sessionTitle: session.title,
}; };
}, [data, selectedSessionId]); }, [data, selectedSessionId]);
@ -262,12 +321,6 @@ function HeatmapInnerView() {
return headers; return headers;
}, [calendarGrid]); }, [calendarGrid]);
useEffect(() => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollLeft = scrollContainerRef.current.scrollWidth;
}
}, [calendarGrid]);
// Daily token timeline chart data for the selected range (14, 30, or 90 days) // Daily token timeline chart data for the selected range (14, 30, or 90 days)
const chartData = useMemo(() => { const chartData = useMemo(() => {
if (!activeView) return []; if (!activeView) return [];
@ -351,12 +404,13 @@ function HeatmapInnerView() {
return ( return (
<div <div
data-dsh-heatmap="true"
style={{ style={{
boxSizing: "border-box", boxSizing: "border-box",
width: "100%", width: "100%",
maxWidth: "960px", maxWidth: "1020px",
margin: "0 auto", margin: "0 auto",
padding: "16px 0 40px", padding: "8px 0 40px",
fontFamily: "inherit", fontFamily: "inherit",
color: "var(--dsw-alias-label-primary, #eeeeee)", color: "var(--dsw-alias-label-primary, #eeeeee)",
}} }}
@ -369,14 +423,14 @@ function HeatmapInnerView() {
justifyContent: "space-between", justifyContent: "space-between",
flexWrap: "wrap", flexWrap: "wrap",
gap: 14, gap: 14,
marginBottom: 20, marginBottom: 16,
}} }}
> >
<div> <div>
<h2 <h2
style={{ style={{
margin: 0, margin: 0,
fontSize: 22, fontSize: 20,
fontWeight: 650, fontWeight: 650,
letterSpacing: "-0.02em", letterSpacing: "-0.02em",
display: "flex", display: "flex",
@ -401,12 +455,12 @@ function HeatmapInnerView() {
</h2> </h2>
<p <p
style={{ style={{
margin: "5px 0 0", margin: "4px 0 0",
fontSize: 13, fontSize: 12,
color: "var(--dsw-alias-label-tertiary, #a0a0a0)", color: "var(--dsw-alias-label-tertiary, #a0a0a0)",
}} }}
> >
Daily token volume and request step intensity across sessions Daily token throughput and model breakdown across sessions
</p> </p>
</div> </div>
@ -464,37 +518,210 @@ function HeatmapInnerView() {
</div> </div>
</div> </div>
{/* Summary Cards Row (Codex / OpenCode style) */} {/* Prominent Centered Total Token Usage Hero (Centered above the chart) */}
<div
style={{
textAlign: "center",
margin: "12px 0 20px",
padding: "16px 20px",
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 12,
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
}}
>
<div style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--dsw-alias-label-tertiary, #a0a0a0)", fontWeight: 600 }}>
Total Tokens Processed
</div>
<div style={{ fontSize: 36, fontWeight: 800, margin: "6px 0 2px", color: "#4f85ff", letterSpacing: "-0.03em" }}>
{formatExact(summary.totalTokens)}
</div>
<div style={{ fontSize: 13, color: "var(--dsw-alias-label-secondary, #b9b9b9)" }}>
<strong>{formatNumber(summary.totalTokens)}</strong> tokens across {summary.activeDays} active days ({summary.totalTurns} total requests)
</div>
</div>
{/* Contribution Calendar Card (Centered and scaled to fit without horizontal scrollbars) */}
<div
style={{
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 12,
padding: "18px 20px",
position: "relative",
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 12,
fontSize: 13,
fontWeight: 600,
}}
>
<span>Contribution Calendar</span>
{summary.peakDay && (
<span style={{ fontSize: 12, fontWeight: 400, color: "var(--dsw-alias-label-secondary)" }}>
Peak day: <strong style={{ color: "var(--dsw-alias-label-primary)" }}>{summary.peakDay.date}</strong> ({formatNumber(summary.peakDay.tokens)} tokens)
</span>
)}
</div>
{/* Clean, centered SVG Calendar Grid (Fits panel smoothly) */}
{calendarGrid && (
<div style={{ width: "100%", display: "flex", flexDirection: "column", alignItems: "center" }}>
<svg
viewBox="0 0 770 120"
style={{
width: "100%",
maxWidth: "770px",
height: "auto",
display: "block",
overflow: "visible",
}}
>
{/* Month labels */}
{monthHeaders.map((m, i) => (
<text
key={i}
x={28 + m.col * 13.8}
y={10}
fontSize={10}
fill="var(--dsw-alias-label-tertiary, #888888)"
fontFamily="inherit"
>
{m.label}
</text>
))}
{/* Day of week labels */}
<text x={2} y={39} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Mon
</text>
<text x={2} y={67} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Wed
</text>
<text x={2} y={95} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Fri
</text>
{/* Day Tiles: centered and proportioned */}
{calendarGrid.map((week, colIdx) => (
<g key={colIdx} transform={`translate(${28 + colIdx * 13.8}, 18)`}>
{week.map((day, rowIdx) => {
if (!day) return null;
const lvl = day.stats.level || 0;
const bg = DEEPSEEK_LEVELS[lvl];
const isSelected = selectedDate === day.date;
const isHovered = hoveredDay?.date === day.date;
// GitHub-style glow & outline
const strokeColor = isSelected
? "#ffffff"
: isHovered
? "#5686fe"
: lvl === 0
? "rgba(255, 255, 255, 0.06)"
: "transparent";
const strokeWidth = isSelected ? 1.6 : isHovered ? 1.3 : 1;
return (
<rect
key={rowIdx}
x={0}
y={rowIdx * 14}
width={11}
height={11}
rx={2}
ry={2}
fill={bg}
stroke={strokeColor}
strokeWidth={strokeWidth}
style={{
cursor: "pointer",
transition: "fill 0.12s ease, stroke 0.12s ease",
filter: isSelected
? "drop-shadow(0 0 6px rgba(86, 134, 254, 0.9))"
: isHovered
? "drop-shadow(0 0 4px rgba(86, 134, 254, 0.7))"
: lvl === 4
? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))"
: "none",
}}
onClick={() => {
setSelectedDate(isSelected ? null : day.date);
}}
onMouseEnter={(e) => {
setHoveredDay({ date: day.date, stats: day.stats });
const rect = e.currentTarget.getBoundingClientRect();
setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
}}
onMouseLeave={() => {
setHoveredDay(null);
setTooltipPos(null);
}}
/>
);
})}
</g>
))}
</svg>
{/* Bottom Legend */}
<div
style={{
width: "100%",
maxWidth: "770px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginTop: 12,
fontSize: 11,
color: "var(--dsw-alias-label-tertiary, #a0a0a0)",
}}
>
<span style={{ fontSize: 11 }}>
Tip: Click any day to inspect sessions and token breakdown
</span>
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
<span>Less</span>
{DEEPSEEK_LEVELS.map((c, i) => (
<div
key={i}
style={{
width: 11,
height: 11,
borderRadius: 2,
backgroundColor: c,
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",
}}
/>
))}
<span>More</span>
</div>
</div>
</div>
)}
</div>
{/* Secondary Metrics Grid (Under the chart, cache color cleared to neutral) */}
<div <div
style={{ style={{
display: "grid", display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
gap: 12, gap: 12,
marginBottom: 20, marginTop: 18,
marginBottom: 18,
}} }}
> >
{/* Card 1: Total Tokens */} {/* Card 1: Cache Hit Ratio (CLEARED BLUE COLOR -> Neutral/Clean White) */}
<div
style={{
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 10,
padding: "14px 16px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
}}
>
<div style={{ fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 }}>
Total Tokens
</div>
<div style={{ fontSize: 24, fontWeight: 700, margin: "6px 0 2px", color: "#4f85ff" }}>
{formatNumber(summary.totalTokens)}
</div>
<div style={{ fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" }}>
{formatExact(summary.totalTokens)} tokens
</div>
</div>
{/* Card 2: Cache Hit */}
<div <div
style={{ style={{
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)", background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
@ -507,7 +734,7 @@ function HeatmapInnerView() {
<div style={{ fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 }}> <div style={{ fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 }}>
Cache Hit Ratio Cache Hit Ratio
</div> </div>
<div style={{ fontSize: 24, fontWeight: 700, margin: "6px 0 2px", color: "#38bdf8" }}> <div style={{ fontSize: 24, fontWeight: 700, margin: "6px 0 2px", color: "var(--dsw-alias-label-primary, #ffffff)" }}>
{cacheHitPercent}% {cacheHitPercent}%
</div> </div>
<div style={{ fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" }}> <div style={{ fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" }}>
@ -515,7 +742,7 @@ function HeatmapInnerView() {
</div> </div>
</div> </div>
{/* Card 3: Daily Average / Turns */} {/* Card 2: Daily Average / Turns */}
<div <div
style={{ style={{
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)", background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
@ -536,7 +763,7 @@ function HeatmapInnerView() {
</div> </div>
</div> </div>
{/* Card 4: Streak / Activity */} {/* Card 3: Streak / Activity */}
<div <div
style={{ style={{
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)", background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
@ -556,194 +783,107 @@ function HeatmapInnerView() {
{selectedSessionId === "all" ? `Longest streak: ${summary.longestStreak} days` : `Session: ${activeView.sessionTitle.slice(0, 24)}`} {selectedSessionId === "all" ? `Longest streak: ${summary.longestStreak} days` : `Session: ${activeView.sessionTitle.slice(0, 24)}`}
</div> </div>
</div> </div>
</div>
{/* Heatmap Card (Auto-resizes responsive SVG panel, GitHub style) */} {/* Card 4: Peak Day */}
<div
style={{
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 12,
padding: "20px 22px",
position: "relative",
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)",
}}
>
<div <div
style={{ style={{
display: "flex", background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
alignItems: "center", border: "1px solid var(--dsw-alias-border-l2, #494949)",
justifyContent: "space-between", borderRadius: 10,
marginBottom: 14, padding: "14px 16px",
fontSize: 13, boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
fontWeight: 600,
}} }}
> >
<span>Contribution Calendar</span> <div style={{ fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 }}>
{summary.peakDay && ( Peak Volume Day
<span style={{ fontSize: 12, fontWeight: 400, color: "var(--dsw-alias-label-secondary)" }}> </div>
Peak day: <strong style={{ color: "var(--dsw-alias-label-primary)" }}>{summary.peakDay.date}</strong> ({formatNumber(summary.peakDay.tokens)} tokens) <div style={{ fontSize: 20, fontWeight: 700, margin: "6px 0 2px", color: "var(--dsw-alias-label-primary, #ffffff)" }}>
</span> {summary.peakDay ? formatNumber(summary.peakDay.tokens) : "None"}
)} </div>
<div style={{ fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" }}>
{summary.peakDay ? summary.peakDay.date : "No activity"}
</div>
</div> </div>
</div>
{/* Generously sized GitHub-Style Calendar with smooth horizontal scroll */} {/* Top Models Breakdown Section */}
{calendarGrid && ( {activeView.models && activeView.models.length > 0 && (
<div
style={{
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 12,
padding: "18px 20px",
marginBottom: 18,
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
}}
>
<div <div
ref={scrollContainerRef}
style={{ style={{
width: "100%", display: "flex",
overflowX: "auto", alignItems: "center",
WebkitOverflowScrolling: "touch", justifyContent: "space-between",
paddingBottom: "10px", marginBottom: 12,
}} }}
> >
<div style={{ minWidth: "920px", width: "100%", display: "inline-block" }}> <div style={{ fontSize: 13, fontWeight: 650, color: "var(--dsw-alias-label-primary, #ffffff)" }}>
<svg Model Usage Breakdown
viewBox="0 0 920 152"
style={{
width: "100%",
minWidth: "920px",
height: "152px",
display: "block",
overflow: "visible",
}}
>
{/* Month labels */}
{monthHeaders.map((m, i) => (
<text
key={i}
x={36 + m.col * 16.5}
y={12}
fontSize={11}
fill="var(--dsw-alias-label-tertiary, #888888)"
fontFamily="inherit"
>
{m.label}
</text>
))}
{/* Day of week labels */}
<text x={4} y={51} fontSize={10} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Mon
</text>
<text x={4} y={85} fontSize={10} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Wed
</text>
<text x={4} y={119} fontSize={10} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Fri
</text>
{/* Day Tiles: Generously sized 13px tiles with 16.5px step */}
{calendarGrid.map((week, colIdx) => (
<g key={colIdx} transform={`translate(${36 + colIdx * 16.5}, 22)`}>
{week.map((day, rowIdx) => {
if (!day) return null;
const lvl = day.stats.level || 0;
const bg = DEEPSEEK_LEVELS[lvl];
const isSelected = selectedDate === day.date;
const isHovered = hoveredDay?.date === day.date;
// GitHub-style glow & outline
const strokeColor = isSelected
? "#ffffff"
: isHovered
? "#5686fe"
: lvl === 0
? "rgba(255, 255, 255, 0.06)"
: "transparent";
const strokeWidth = isSelected ? 2 : isHovered ? 1.5 : 1;
return (
<rect
key={rowIdx}
x={0}
y={rowIdx * 17}
width={13}
height={13}
rx={2.5}
ry={2.5}
fill={bg}
stroke={strokeColor}
strokeWidth={strokeWidth}
style={{
cursor: "pointer",
transition: "fill 0.12s ease, stroke 0.12s ease",
filter: isSelected
? "drop-shadow(0 0 6px rgba(86, 134, 254, 0.9))"
: isHovered
? "drop-shadow(0 0 4px rgba(86, 134, 254, 0.7))"
: lvl === 4
? "drop-shadow(0 0 4px rgba(79, 133, 255, 0.6))"
: "none",
}}
onClick={() => {
setSelectedDate(isSelected ? null : day.date);
}}
onMouseEnter={(e) => {
setHoveredDay({ date: day.date, stats: day.stats });
const rect = e.currentTarget.getBoundingClientRect();
setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
}}
onMouseLeave={() => {
setHoveredDay(null);
setTooltipPos(null);
}}
/>
);
})}
</g>
))}
</svg>
</div> </div>
<div style={{ fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)" }}>
{/* Bottom Legend */} {activeView.models.length} model{activeView.models.length === 1 ? "" : "s"} tracked
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginTop: 14,
fontSize: 11,
color: "var(--dsw-alias-label-tertiary, #a0a0a0)",
}}
>
<span style={{ fontSize: 11 }}>
Tip: Click any day to inspect sessions and token breakdown
</span>
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
<span>Less</span>
{DEEPSEEK_LEVELS.map((c, i) => (
<div
key={i}
style={{
width: 12.5,
height: 12.5,
borderRadius: 2.5,
backgroundColor: c,
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",
}}
/>
))}
<span>More</span>
</div>
</div> </div>
</div> </div>
)}
</div> <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
{activeView.models.map((m) => (
<div key={m.model}>
<div
style={{
display: "flex",
justifyContent: "space-between",
fontSize: 12,
marginBottom: 5,
}}
>
<span style={{ fontWeight: 600, color: "var(--dsw-alias-label-primary)" }}>
{m.model}
</span>
<span style={{ color: "var(--dsw-alias-label-secondary)" }}>
<strong style={{ color: "#4f85ff" }}>{formatNumber(m.totalTokens)}</strong> ({m.percentage}%) · {m.turns} requests
</span>
</div>
<div
style={{
width: "100%",
height: 6,
background: "rgba(255, 255, 255, 0.06)",
borderRadius: 3,
overflow: "hidden",
}}
>
<div
style={{
width: `${Math.max(1, m.percentage)}%`,
height: "100%",
background: "#336dfa",
borderRadius: 3,
transition: "width 0.3s ease",
}}
/>
</div>
</div>
))}
</div>
</div>
)}
{/* Token Usage Trends Graph Card */} {/* Token Usage Trends Graph Card */}
<div <div
style={{ style={{
marginTop: 18,
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)", background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
border: "1px solid var(--dsw-alias-border-l2, #494949)", border: "1px solid var(--dsw-alias-border-l2, #494949)",
borderRadius: 12, borderRadius: 12,
padding: "20px 22px", padding: "18px 20px",
position: "relative", position: "relative",
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)", boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)",
}} }}
@ -759,10 +899,10 @@ function HeatmapInnerView() {
}} }}
> >
<div> <div>
<div style={{ fontSize: 14, fontWeight: 650, color: "var(--dsw-alias-label-primary, #ffffff)" }}> <div style={{ fontSize: 13, fontWeight: 650, color: "var(--dsw-alias-label-primary, #ffffff)" }}>
Token Usage Trends Token Usage Trends
</div> </div>
<div style={{ fontSize: 12, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", marginTop: 2 }}> <div style={{ fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", marginTop: 2 }}>
Daily billed vs cached token volume over time Daily billed vs cached token volume over time
</div> </div>
</div> </div>
@ -771,11 +911,11 @@ function HeatmapInnerView() {
{/* Legend: Billed vs Cached */} {/* Legend: Billed vs Cached */}
<div style={{ display: "flex", alignItems: "center", gap: 12, fontSize: 11, color: "var(--dsw-alias-label-secondary)" }}> <div style={{ display: "flex", alignItems: "center", gap: 12, fontSize: 11, color: "var(--dsw-alias-label-secondary)" }}>
<div style={{ display: "flex", alignItems: "center", gap: 5 }}> <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
<span style={{ width: 9, height: 9, borderRadius: 2, background: "#4f85ff" }} /> <span style={{ width: 8, height: 8, borderRadius: 2, background: "#4f85ff" }} />
<span>Billed Tokens</span> <span>Billed Tokens</span>
</div> </div>
<div style={{ display: "flex", alignItems: "center", gap: 5 }}> <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
<span style={{ width: 9, height: 9, borderRadius: 2, background: "#253b82" }} /> <span style={{ width: 8, height: 8, borderRadius: 2, background: "#253b82" }} />
<span>Cache Reads</span> <span>Cache Reads</span>
</div> </div>
</div> </div>
@ -814,7 +954,7 @@ function HeatmapInnerView() {
{/* The SVG Timeline Bar Chart */} {/* The SVG Timeline Bar Chart */}
<div style={{ width: "100%", overflowX: "auto", WebkitOverflowScrolling: "touch" }}> <div style={{ width: "100%", overflowX: "auto", WebkitOverflowScrolling: "touch" }}>
<div style={{ minWidth: "720px", width: "100%" }}> <div style={{ minWidth: "680px", width: "100%" }}>
<svg <svg
viewBox="0 0 880 180" viewBox="0 0 880 180"
style={{ width: "100%", height: "180px", display: "block", overflow: "visible" }} style={{ width: "100%", height: "180px", display: "block", overflow: "visible" }}
@ -1029,7 +1169,7 @@ function HeatmapInnerView() {
</div> </div>
<div style={{ padding: "6px 12px", background: "var(--dsw-alias-bg-layer-1, #1e1e1e)", borderRadius: 6, border: "1px solid var(--dsw-alias-border-l1)" }}> <div style={{ padding: "6px 12px", background: "var(--dsw-alias-bg-layer-1, #1e1e1e)", borderRadius: 6, border: "1px solid var(--dsw-alias-border-l1)" }}>
<span style={{ color: "var(--dsw-alias-label-tertiary)" }}>Cache Reads: </span> <span style={{ color: "var(--dsw-alias-label-tertiary)" }}>Cache Reads: </span>
<strong style={{ color: "#38bdf8" }}>{formatNumber(inspectedDayStats.cacheReadTokens)}</strong> ( <strong>{formatNumber(inspectedDayStats.cacheReadTokens)}</strong> (
{Math.round((inspectedDayStats.cacheReadTokens / inspectedDayStats.totalTokens) * 100)}%) {Math.round((inspectedDayStats.cacheReadTokens / inspectedDayStats.totalTokens) * 100)}%)
</div> </div>
</div> </div>
@ -1038,7 +1178,7 @@ function HeatmapInnerView() {
{/* Sessions List for that Day */} {/* Sessions List for that Day */}
<div> <div>
<div style={{ fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary)", marginBottom: 10 }}> <div style={{ fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary)", marginBottom: 10 }}>
Sessions Active on this Day ({inspectedDayStats?.sessions?.length || 0}) Sessions Active on this Day ({(inspectedDayStats?.sessions || []).length})
</div> </div>
{inspectedDayStats?.sessions && inspectedDayStats.sessions.length > 0 ? ( {inspectedDayStats?.sessions && inspectedDayStats.sessions.length > 0 ? (
@ -1085,7 +1225,7 @@ function HeatmapInnerView() {
</div> </div>
)} )}
{/* Floating Hover Tooltip */} {/* Floating Hover Tooltip for Calendar Day */}
{hoveredDay && tooltipPos && !selectedDate && ( {hoveredDay && tooltipPos && !selectedDate && (
<div <div
style={{ style={{

View file

@ -25,6 +25,16 @@ export interface DayStats {
sessions?: DaySessionDetail[]; sessions?: DaySessionDetail[];
} }
export interface ModelUsage {
model: string;
totalTokens: number;
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;
turns: number;
percentage: number;
}
export interface SessionSummary { export interface SessionSummary {
id: string; id: string;
title: string; title: string;
@ -35,6 +45,7 @@ export interface SessionSummary {
cacheReadTokens: number; cacheReadTokens: number;
turns: number; turns: number;
days: Record<string, DayStats>; days: Record<string, DayStats>;
models?: Record<string, { totalTokens: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; turns: number }>;
} }
export interface SummaryStats { export interface SummaryStats {
@ -54,6 +65,7 @@ export interface HeatmapResponse {
summary: SummaryStats; summary: SummaryStats;
days: Record<string, DayStats>; days: Record<string, DayStats>;
sessions: SessionSummary[]; sessions: SessionSummary[];
models: ModelUsage[];
startDate: string; startDate: string;
endDate: string; endDate: string;
} }
@ -93,6 +105,7 @@ async function parseSessionFile(filePath: string): Promise<SessionSummary | null
const isZstd = filePath.endsWith(".zstd"); const isZstd = filePath.endsWith(".zstd");
const days: Record<string, DayStats> = {}; const days: Record<string, DayStats> = {};
const sessionModels: Record<string, { totalTokens: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; turns: number }> = {};
let sessionId = path.basename(path.dirname(filePath)); let sessionId = path.basename(path.dirname(filePath));
let title = ""; let title = "";
let createdAt = stat.birthtimeMs || stat.mtimeMs; let createdAt = stat.birthtimeMs || stat.mtimeMs;
@ -137,6 +150,7 @@ async function parseSessionFile(filePath: string): Promise<SessionSummary | null
const cacheRead = Number(usage.cacheReadTokens) || 0; const cacheRead = Number(usage.cacheReadTokens) || 0;
const cacheWrite = Number(usage.cacheWriteTokens) || 0; const cacheWrite = Number(usage.cacheWriteTokens) || 0;
const total = input + output + cacheRead; const total = input + output + cacheRead;
const model = obj.data?.message?.source?.model || obj.data?.model || obj.model || "gemini-3.8-flash-tiered";
if (!days[dateKey]) { if (!days[dateKey]) {
days[dateKey] = { days[dateKey] = {
@ -155,6 +169,15 @@ async function parseSessionFile(filePath: string): Promise<SessionSummary | null
days[dateKey].cacheWriteTokens += cacheWrite; days[dateKey].cacheWriteTokens += cacheWrite;
days[dateKey].turns += 1; days[dateKey].turns += 1;
if (!sessionModels[model]) {
sessionModels[model] = { totalTokens: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, turns: 0 };
}
sessionModels[model].totalTokens += total;
sessionModels[model].inputTokens += input;
sessionModels[model].outputTokens += output;
sessionModels[model].cacheReadTokens += cacheRead;
sessionModels[model].turns += 1;
totalTokens += total; totalTokens += total;
totalInputTokens += input; totalInputTokens += input;
totalOutputTokens += output; totalOutputTokens += output;
@ -212,6 +235,7 @@ async function parseSessionFile(filePath: string): Promise<SessionSummary | null
cacheReadTokens: totalCacheReadTokens, cacheReadTokens: totalCacheReadTokens,
turns: totalTurns, turns: totalTurns,
days, days,
models: sessionModels,
}; };
fileCache.set(filePath, { mtimeMs: stat.mtimeMs, session }); fileCache.set(filePath, { mtimeMs: stat.mtimeMs, session });
@ -230,6 +254,7 @@ export async function aggregateTokens(customSessionsRoot?: string): Promise<Heat
parsedSessions.sort((a, b) => b.createdAt - a.createdAt); parsedSessions.sort((a, b) => b.createdAt - a.createdAt);
const days: Record<string, DayStats> = {}; const days: Record<string, DayStats> = {};
const allModelsMap: Record<string, { totalTokens: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; turns: number }> = {};
for (const session of parsedSessions) { for (const session of parsedSessions) {
for (const [date, stats] of Object.entries(session.days)) { for (const [date, stats] of Object.entries(session.days)) {
@ -262,6 +287,19 @@ export async function aggregateTokens(customSessionsRoot?: string): Promise<Heat
turns: stats.turns, turns: stats.turns,
}); });
} }
if (session.models) {
for (const [modelName, mStats] of Object.entries(session.models)) {
if (!allModelsMap[modelName]) {
allModelsMap[modelName] = { totalTokens: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, turns: 0 };
}
allModelsMap[modelName].totalTokens += mStats.totalTokens;
allModelsMap[modelName].inputTokens += mStats.inputTokens;
allModelsMap[modelName].outputTokens += mStats.outputTokens;
allModelsMap[modelName].cacheReadTokens += mStats.cacheReadTokens;
allModelsMap[modelName].turns += mStats.turns;
}
}
} }
// Sort sessions within each day by totalTokens descending // Sort sessions within each day by totalTokens descending
@ -315,6 +353,19 @@ export async function aggregateTokens(customSessionsRoot?: string): Promise<Heat
} }
} }
// Build sorted model breakdown array
const models: ModelUsage[] = Object.entries(allModelsMap)
.map(([model, m]) => ({
model,
totalTokens: m.totalTokens,
inputTokens: m.inputTokens,
outputTokens: m.outputTokens,
cacheReadTokens: m.cacheReadTokens,
turns: m.turns,
percentage: totalTokens > 0 ? Math.round((m.totalTokens / totalTokens) * 1000) / 10 : 0,
}))
.sort((a, b) => b.totalTokens - a.totalTokens);
const activeDays = Object.values(days).filter((d) => d.totalTokens > 0).length; const activeDays = Object.values(days).filter((d) => d.totalTokens > 0).length;
const dailyAverage = activeDays > 0 ? Math.round(totalTokens / activeDays) : 0; const dailyAverage = activeDays > 0 ? Math.round(totalTokens / activeDays) : 0;
@ -388,6 +439,7 @@ export async function aggregateTokens(customSessionsRoot?: string): Promise<Heat
}, },
days, days,
sessions: parsedSessions, sessions: parsedSessions,
models,
startDate, startDate,
endDate, endDate,
}; };