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 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
380
lib/client.js
380
lib/client.js
|
|
@ -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
|
|
@ -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={{
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue