feat: center total token usage, auto-resize settings panel to 1080px, clear blue color on cache, add model breakdown
This commit is contained in:
parent
2b15144ae5
commit
a151fcba93
6 changed files with 683 additions and 365 deletions
|
|
@ -28,6 +28,7 @@ async function parseSessionFile(filePath) {
|
|||
}
|
||||
const isZstd = filePath.endsWith(".zstd");
|
||||
const days = {};
|
||||
const sessionModels = {};
|
||||
let sessionId = path.basename(path.dirname(filePath));
|
||||
let title = "";
|
||||
let createdAt = stat.birthtimeMs || stat.mtimeMs;
|
||||
|
|
@ -74,6 +75,7 @@ async function parseSessionFile(filePath) {
|
|||
const cacheRead = Number(usage.cacheReadTokens) || 0;
|
||||
const cacheWrite = Number(usage.cacheWriteTokens) || 0;
|
||||
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]) {
|
||||
days[dateKey] = {
|
||||
totalTokens: 0,
|
||||
|
|
@ -90,6 +92,14 @@ async function parseSessionFile(filePath) {
|
|||
days[dateKey].cacheReadTokens += cacheRead;
|
||||
days[dateKey].cacheWriteTokens += cacheWrite;
|
||||
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;
|
||||
totalInputTokens += input;
|
||||
totalOutputTokens += output;
|
||||
|
|
@ -143,7 +153,8 @@ async function parseSessionFile(filePath) {
|
|||
outputTokens: totalOutputTokens,
|
||||
cacheReadTokens: totalCacheReadTokens,
|
||||
turns: totalTurns,
|
||||
days
|
||||
days,
|
||||
models: sessionModels
|
||||
};
|
||||
fileCache.set(filePath, { mtimeMs: stat.mtimeMs, session });
|
||||
return session;
|
||||
|
|
@ -157,6 +168,7 @@ async function aggregateTokens(customSessionsRoot) {
|
|||
const parsedSessions = (await Promise.all(files.map(parseSessionFile))).filter(Boolean);
|
||||
parsedSessions.sort((a, b) => b.createdAt - a.createdAt);
|
||||
const days = {};
|
||||
const allModelsMap = {};
|
||||
for (const session of parsedSessions) {
|
||||
for (const [date, stats] of Object.entries(session.days)) {
|
||||
if (!days[date]) {
|
||||
|
|
@ -187,6 +199,18 @@ async function aggregateTokens(customSessionsRoot) {
|
|||
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)) {
|
||||
if (day.sessions) {
|
||||
|
|
@ -225,6 +249,15 @@ async function aggregateTokens(customSessionsRoot) {
|
|||
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 dailyAverage = activeDays > 0 ? Math.round(totalTokens / activeDays) : 0;
|
||||
const now = /* @__PURE__ */ new Date();
|
||||
|
|
@ -284,6 +317,7 @@ async function aggregateTokens(customSessionsRoot) {
|
|||
},
|
||||
days,
|
||||
sessions: parsedSessions,
|
||||
models,
|
||||
startDate,
|
||||
endDate
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
380
lib/client.js
380
lib/client.js
|
|
@ -72,7 +72,23 @@ function HeatmapInnerView() {
|
|||
const [hoveredDay, setHoveredDay] = (0, import_react.useState)(null);
|
||||
const [hoveredBar, setHoveredBar] = (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 = () => {
|
||||
setLoading(true);
|
||||
fetch("/api/dsh-token-heatmap/stats").then((r) => {
|
||||
|
|
@ -95,6 +111,7 @@ function HeatmapInnerView() {
|
|||
return {
|
||||
summary: data.summary,
|
||||
days: data.days,
|
||||
models: data.models || [],
|
||||
sessionTitle: "All Sessions (Global)"
|
||||
};
|
||||
}
|
||||
|
|
@ -103,6 +120,7 @@ function HeatmapInnerView() {
|
|||
return {
|
||||
summary: data.summary,
|
||||
days: data.days,
|
||||
models: data.models || [],
|
||||
sessionTitle: "All Sessions (Global)"
|
||||
};
|
||||
}
|
||||
|
|
@ -138,6 +156,15 @@ function HeatmapInnerView() {
|
|||
else lvl = 4;
|
||||
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 {
|
||||
summary: {
|
||||
totalTokens,
|
||||
|
|
@ -152,6 +179,7 @@ function HeatmapInnerView() {
|
|||
dailyAverage
|
||||
},
|
||||
days: leveledDays,
|
||||
models: sessionModels,
|
||||
sessionTitle: session.title
|
||||
};
|
||||
}, [data, selectedSessionId]);
|
||||
|
|
@ -207,11 +235,6 @@ function HeatmapInnerView() {
|
|||
});
|
||||
return headers;
|
||||
}, [calendarGrid]);
|
||||
(0, import_react.useEffect)(() => {
|
||||
if (scrollContainerRef.current) {
|
||||
scrollContainerRef.current.scrollLeft = scrollContainerRef.current.scrollWidth;
|
||||
}
|
||||
}, [calendarGrid]);
|
||||
const chartData = (0, import_react.useMemo)(() => {
|
||||
if (!activeView) return [];
|
||||
const now = /* @__PURE__ */ new Date();
|
||||
|
|
@ -270,12 +293,13 @@ function HeatmapInnerView() {
|
|||
return /* @__PURE__ */ import_react.default.createElement(
|
||||
"div",
|
||||
{
|
||||
"data-dsh-heatmap": "true",
|
||||
style: {
|
||||
boxSizing: "border-box",
|
||||
width: "100%",
|
||||
maxWidth: "960px",
|
||||
maxWidth: "1020px",
|
||||
margin: "0 auto",
|
||||
padding: "16px 0 40px",
|
||||
padding: "8px 0 40px",
|
||||
fontFamily: "inherit",
|
||||
color: "var(--dsw-alias-label-primary, #eeeeee)"
|
||||
}
|
||||
|
|
@ -289,7 +313,7 @@ function HeatmapInnerView() {
|
|||
justifyContent: "space-between",
|
||||
flexWrap: "wrap",
|
||||
gap: 14,
|
||||
marginBottom: 20
|
||||
marginBottom: 16
|
||||
}
|
||||
},
|
||||
/* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement(
|
||||
|
|
@ -297,7 +321,7 @@ function HeatmapInnerView() {
|
|||
{
|
||||
style: {
|
||||
margin: 0,
|
||||
fontSize: 22,
|
||||
fontSize: 20,
|
||||
fontWeight: 650,
|
||||
letterSpacing: "-0.02em",
|
||||
display: "flex",
|
||||
|
|
@ -325,12 +349,12 @@ function HeatmapInnerView() {
|
|||
"p",
|
||||
{
|
||||
style: {
|
||||
margin: "5px 0 0",
|
||||
fontSize: 13,
|
||||
margin: "4px 0 0",
|
||||
fontSize: 12,
|
||||
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(
|
||||
"select",
|
||||
|
|
@ -376,6 +400,149 @@ function HeatmapInnerView() {
|
|||
"\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(
|
||||
"div",
|
||||
{
|
||||
|
|
@ -383,24 +550,10 @@ function HeatmapInnerView() {
|
|||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
|
||||
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(
|
||||
"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: 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(
|
||||
|
|
@ -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: 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: {
|
||||
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",
|
||||
{
|
||||
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)"
|
||||
padding: "18px 20px",
|
||||
marginBottom: 18,
|
||||
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)"
|
||||
}
|
||||
},
|
||||
/* @__PURE__ */ import_react.default.createElement(
|
||||
|
|
@ -466,133 +634,57 @@ function HeatmapInnerView() {
|
|||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 14,
|
||||
fontSize: 13,
|
||||
fontWeight: 600
|
||||
marginBottom: 12
|
||||
}
|
||||
},
|
||||
/* @__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)")
|
||||
/* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 13, fontWeight: 650, color: "var(--dsw-alias-label-primary, #ffffff)" } }, "Model Usage Breakdown"),
|
||||
/* @__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",
|
||||
{
|
||||
ref: scrollContainerRef,
|
||||
style: {
|
||||
width: "100%",
|
||||
overflowX: "auto",
|
||||
WebkitOverflowScrolling: "touch",
|
||||
paddingBottom: "10px"
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
fontSize: 12,
|
||||
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(
|
||||
"div",
|
||||
{
|
||||
style: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginTop: 14,
|
||||
fontSize: 11,
|
||||
color: "var(--dsw-alias-label-tertiary, #a0a0a0)"
|
||||
width: `${Math.max(1, m.percentage)}%`,
|
||||
height: "100%",
|
||||
background: "#336dfa",
|
||||
borderRadius: 3,
|
||||
transition: "width 0.3s ease"
|
||||
}
|
||||
},
|
||||
/* @__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(
|
||||
"div",
|
||||
{
|
||||
style: {
|
||||
marginTop: 18,
|
||||
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
|
||||
border: "1px solid var(--dsw-alias-border-l2, #494949)",
|
||||
borderRadius: 12,
|
||||
padding: "20px 22px",
|
||||
padding: "18px 20px",
|
||||
position: "relative",
|
||||
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)"
|
||||
}
|
||||
|
|
@ -609,8 +701,8 @@ function HeatmapInnerView() {
|
|||
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", { 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", 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: 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",
|
||||
{
|
||||
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",
|
||||
{
|
||||
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)" } }, "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",
|
||||
{
|
||||
key: idx,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -22,6 +22,16 @@ export interface DayStats {
|
|||
sessions?: DaySessionDetail[];
|
||||
}
|
||||
|
||||
export interface ModelUsage {
|
||||
model: string;
|
||||
totalTokens: number;
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
cacheReadTokens: number;
|
||||
turns: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
export interface SessionSummary {
|
||||
id: string;
|
||||
title: string;
|
||||
|
|
@ -32,6 +42,7 @@ export interface SessionSummary {
|
|||
cacheReadTokens: number;
|
||||
turns: number;
|
||||
days: Record<string, DayStats>;
|
||||
models?: Record<string, { totalTokens: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; turns: number }>;
|
||||
}
|
||||
|
||||
export interface SummaryStats {
|
||||
|
|
@ -51,6 +62,7 @@ export interface HeatmapResponse {
|
|||
summary: SummaryStats;
|
||||
days: Record<string, DayStats>;
|
||||
sessions: SessionSummary[];
|
||||
models?: ModelUsage[];
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
|
|
@ -78,6 +90,15 @@ const DEEPSEEK_LEVELS = [
|
|||
"#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() {
|
||||
const [data, setData] = useState<HeatmapResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
|
@ -95,7 +116,27 @@ function HeatmapInnerView() {
|
|||
turns: 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 = () => {
|
||||
setLoading(true);
|
||||
|
|
@ -126,6 +167,7 @@ function HeatmapInnerView() {
|
|||
return {
|
||||
summary: data.summary,
|
||||
days: data.days,
|
||||
models: data.models || [],
|
||||
sessionTitle: "All Sessions (Global)",
|
||||
};
|
||||
}
|
||||
|
|
@ -135,6 +177,7 @@ function HeatmapInnerView() {
|
|||
return {
|
||||
summary: data.summary,
|
||||
days: data.days,
|
||||
models: data.models || [],
|
||||
sessionTitle: "All Sessions (Global)",
|
||||
};
|
||||
}
|
||||
|
|
@ -180,6 +223,21 @@ function HeatmapInnerView() {
|
|||
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 {
|
||||
summary: {
|
||||
totalTokens,
|
||||
|
|
@ -194,6 +252,7 @@ function HeatmapInnerView() {
|
|||
dailyAverage,
|
||||
},
|
||||
days: leveledDays,
|
||||
models: sessionModels,
|
||||
sessionTitle: session.title,
|
||||
};
|
||||
}, [data, selectedSessionId]);
|
||||
|
|
@ -262,12 +321,6 @@ function HeatmapInnerView() {
|
|||
return headers;
|
||||
}, [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)
|
||||
const chartData = useMemo(() => {
|
||||
if (!activeView) return [];
|
||||
|
|
@ -351,12 +404,13 @@ function HeatmapInnerView() {
|
|||
|
||||
return (
|
||||
<div
|
||||
data-dsh-heatmap="true"
|
||||
style={{
|
||||
boxSizing: "border-box",
|
||||
width: "100%",
|
||||
maxWidth: "960px",
|
||||
maxWidth: "1020px",
|
||||
margin: "0 auto",
|
||||
padding: "16px 0 40px",
|
||||
padding: "8px 0 40px",
|
||||
fontFamily: "inherit",
|
||||
color: "var(--dsw-alias-label-primary, #eeeeee)",
|
||||
}}
|
||||
|
|
@ -369,14 +423,14 @@ function HeatmapInnerView() {
|
|||
justifyContent: "space-between",
|
||||
flexWrap: "wrap",
|
||||
gap: 14,
|
||||
marginBottom: 20,
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h2
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: 22,
|
||||
fontSize: 20,
|
||||
fontWeight: 650,
|
||||
letterSpacing: "-0.02em",
|
||||
display: "flex",
|
||||
|
|
@ -401,12 +455,12 @@ function HeatmapInnerView() {
|
|||
</h2>
|
||||
<p
|
||||
style={{
|
||||
margin: "5px 0 0",
|
||||
fontSize: 13,
|
||||
margin: "4px 0 0",
|
||||
fontSize: 12,
|
||||
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>
|
||||
</div>
|
||||
|
||||
|
|
@ -464,37 +518,210 @@ function HeatmapInnerView() {
|
|||
</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
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
|
||||
gap: 12,
|
||||
marginBottom: 20,
|
||||
marginTop: 18,
|
||||
marginBottom: 18,
|
||||
}}
|
||||
>
|
||||
{/* Card 1: Total Tokens */}
|
||||
<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 */}
|
||||
{/* Card 1: Cache Hit Ratio (CLEARED BLUE COLOR -> Neutral/Clean White) */}
|
||||
<div
|
||||
style={{
|
||||
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 }}>
|
||||
Cache Hit Ratio
|
||||
</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}%
|
||||
</div>
|
||||
<div style={{ fontSize: 11, color: "var(--dsw-alias-label-secondary, #b9b9b9)" }}>
|
||||
|
|
@ -515,7 +742,7 @@ function HeatmapInnerView() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card 3: Daily Average / Turns */}
|
||||
{/* Card 2: Daily Average / Turns */}
|
||||
<div
|
||||
style={{
|
||||
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
|
||||
|
|
@ -536,7 +763,7 @@ function HeatmapInnerView() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card 4: Streak / Activity */}
|
||||
{/* Card 3: Streak / Activity */}
|
||||
<div
|
||||
style={{
|
||||
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)}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Heatmap Card (Auto-resizes responsive SVG panel, GitHub style) */}
|
||||
<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)",
|
||||
}}
|
||||
>
|
||||
{/* Card 4: Peak Day */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 14,
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
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)",
|
||||
}}
|
||||
>
|
||||
<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 style={{ fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 }}>
|
||||
Peak Volume Day
|
||||
</div>
|
||||
<div style={{ fontSize: 20, fontWeight: 700, margin: "6px 0 2px", color: "var(--dsw-alias-label-primary, #ffffff)" }}>
|
||||
{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>
|
||||
|
||||
{/* Generously sized GitHub-Style Calendar with smooth horizontal scroll */}
|
||||
{calendarGrid && (
|
||||
{/* Top Models Breakdown Section */}
|
||||
{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
|
||||
ref={scrollContainerRef}
|
||||
style={{
|
||||
width: "100%",
|
||||
overflowX: "auto",
|
||||
WebkitOverflowScrolling: "touch",
|
||||
paddingBottom: "10px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 12,
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: "920px", width: "100%", display: "inline-block" }}>
|
||||
<svg
|
||||
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 style={{ fontSize: 13, fontWeight: 650, color: "var(--dsw-alias-label-primary, #ffffff)" }}>
|
||||
Model Usage Breakdown
|
||||
</div>
|
||||
|
||||
{/* Bottom Legend */}
|
||||
<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 style={{ fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)" }}>
|
||||
{activeView.models.length} model{activeView.models.length === 1 ? "" : "s"} tracked
|
||||
</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 */}
|
||||
<div
|
||||
style={{
|
||||
marginTop: 18,
|
||||
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
|
||||
border: "1px solid var(--dsw-alias-border-l2, #494949)",
|
||||
borderRadius: 12,
|
||||
padding: "20px 22px",
|
||||
padding: "18px 20px",
|
||||
position: "relative",
|
||||
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)",
|
||||
}}
|
||||
|
|
@ -759,10 +899,10 @@ function HeatmapInnerView() {
|
|||
}}
|
||||
>
|
||||
<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
|
||||
</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
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -771,11 +911,11 @@ function HeatmapInnerView() {
|
|||
{/* 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: 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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -814,7 +954,7 @@ function HeatmapInnerView() {
|
|||
|
||||
{/* The SVG Timeline Bar Chart */}
|
||||
<div style={{ width: "100%", overflowX: "auto", WebkitOverflowScrolling: "touch" }}>
|
||||
<div style={{ minWidth: "720px", width: "100%" }}>
|
||||
<div style={{ minWidth: "680px", width: "100%" }}>
|
||||
<svg
|
||||
viewBox="0 0 880 180"
|
||||
style={{ width: "100%", height: "180px", display: "block", overflow: "visible" }}
|
||||
|
|
@ -1029,7 +1169,7 @@ function HeatmapInnerView() {
|
|||
</div>
|
||||
<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>
|
||||
<strong style={{ color: "#38bdf8" }}>{formatNumber(inspectedDayStats.cacheReadTokens)}</strong> (
|
||||
<strong>{formatNumber(inspectedDayStats.cacheReadTokens)}</strong> (
|
||||
{Math.round((inspectedDayStats.cacheReadTokens / inspectedDayStats.totalTokens) * 100)}%)
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1038,7 +1178,7 @@ function HeatmapInnerView() {
|
|||
{/* Sessions List for that Day */}
|
||||
<div>
|
||||
<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>
|
||||
|
||||
{inspectedDayStats?.sessions && inspectedDayStats.sessions.length > 0 ? (
|
||||
|
|
@ -1085,7 +1225,7 @@ function HeatmapInnerView() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Floating Hover Tooltip */}
|
||||
{/* Floating Hover Tooltip for Calendar Day */}
|
||||
{hoveredDay && tooltipPos && !selectedDate && (
|
||||
<div
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -25,6 +25,16 @@ export interface DayStats {
|
|||
sessions?: DaySessionDetail[];
|
||||
}
|
||||
|
||||
export interface ModelUsage {
|
||||
model: string;
|
||||
totalTokens: number;
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
cacheReadTokens: number;
|
||||
turns: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
export interface SessionSummary {
|
||||
id: string;
|
||||
title: string;
|
||||
|
|
@ -35,6 +45,7 @@ export interface SessionSummary {
|
|||
cacheReadTokens: number;
|
||||
turns: number;
|
||||
days: Record<string, DayStats>;
|
||||
models?: Record<string, { totalTokens: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; turns: number }>;
|
||||
}
|
||||
|
||||
export interface SummaryStats {
|
||||
|
|
@ -54,6 +65,7 @@ export interface HeatmapResponse {
|
|||
summary: SummaryStats;
|
||||
days: Record<string, DayStats>;
|
||||
sessions: SessionSummary[];
|
||||
models: ModelUsage[];
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
|
|
@ -93,6 +105,7 @@ async function parseSessionFile(filePath: string): Promise<SessionSummary | null
|
|||
|
||||
const isZstd = filePath.endsWith(".zstd");
|
||||
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 title = "";
|
||||
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 cacheWrite = Number(usage.cacheWriteTokens) || 0;
|
||||
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]) {
|
||||
days[dateKey] = {
|
||||
|
|
@ -155,6 +169,15 @@ async function parseSessionFile(filePath: string): Promise<SessionSummary | null
|
|||
days[dateKey].cacheWriteTokens += cacheWrite;
|
||||
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;
|
||||
totalInputTokens += input;
|
||||
totalOutputTokens += output;
|
||||
|
|
@ -212,6 +235,7 @@ async function parseSessionFile(filePath: string): Promise<SessionSummary | null
|
|||
cacheReadTokens: totalCacheReadTokens,
|
||||
turns: totalTurns,
|
||||
days,
|
||||
models: sessionModels,
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
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 [date, stats] of Object.entries(session.days)) {
|
||||
|
|
@ -262,6 +287,19 @@ export async function aggregateTokens(customSessionsRoot?: string): Promise<Heat
|
|||
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
|
||||
|
|
@ -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 dailyAverage = activeDays > 0 ? Math.round(totalTokens / activeDays) : 0;
|
||||
|
||||
|
|
@ -388,6 +439,7 @@ export async function aggregateTokens(customSessionsRoot?: string): Promise<Heat
|
|||
},
|
||||
days,
|
||||
sessions: parsedSessions,
|
||||
models,
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue