feat: responsive SVG calendar, GitHub-style outline glow, interactive day inspector with session breakdown
This commit is contained in:
parent
992983b9e2
commit
2e3f1d8a25
6 changed files with 487 additions and 211 deletions
|
|
@ -166,7 +166,8 @@ async function aggregateTokens(customSessionsRoot) {
|
||||||
outputTokens: 0,
|
outputTokens: 0,
|
||||||
cacheReadTokens: 0,
|
cacheReadTokens: 0,
|
||||||
cacheWriteTokens: 0,
|
cacheWriteTokens: 0,
|
||||||
turns: 0
|
turns: 0,
|
||||||
|
sessions: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
days[date].totalTokens += stats.totalTokens;
|
days[date].totalTokens += stats.totalTokens;
|
||||||
|
|
@ -175,6 +176,21 @@ async function aggregateTokens(customSessionsRoot) {
|
||||||
days[date].cacheReadTokens += stats.cacheReadTokens;
|
days[date].cacheReadTokens += stats.cacheReadTokens;
|
||||||
days[date].cacheWriteTokens += stats.cacheWriteTokens;
|
days[date].cacheWriteTokens += stats.cacheWriteTokens;
|
||||||
days[date].turns += stats.turns;
|
days[date].turns += stats.turns;
|
||||||
|
days[date].sessions.push({
|
||||||
|
sessionId: session.id,
|
||||||
|
sessionTitle: session.title,
|
||||||
|
createdAt: session.createdAt,
|
||||||
|
totalTokens: stats.totalTokens,
|
||||||
|
inputTokens: stats.inputTokens,
|
||||||
|
outputTokens: stats.outputTokens,
|
||||||
|
cacheReadTokens: stats.cacheReadTokens,
|
||||||
|
turns: stats.turns
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const day of Object.values(days)) {
|
||||||
|
if (day.sessions) {
|
||||||
|
day.sessions.sort((a, b) => b.totalTokens - a.totalTokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const positiveValues = Object.values(days).map((d) => d.totalTokens).filter((t) => t > 0).sort((a, b) => a - b);
|
const positiveValues = Object.values(days).map((d) => d.totalTokens).filter((t) => t > 0).sort((a, b) => a - b);
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
247
lib/client.js
247
lib/client.js
|
|
@ -50,7 +50,6 @@ function formatExact(num) {
|
||||||
return num.toLocaleString();
|
return num.toLocaleString();
|
||||||
}
|
}
|
||||||
var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||||
var DAY_LABELS = ["", "Mon", "", "Wed", "", "Fri", ""];
|
|
||||||
var DEEPSEEK_LEVELS = [
|
var DEEPSEEK_LEVELS = [
|
||||||
"#0f1117",
|
"#0f1117",
|
||||||
// Level 0: Black / dark void tile
|
// Level 0: Black / dark void tile
|
||||||
|
|
@ -63,23 +62,12 @@ var DEEPSEEK_LEVELS = [
|
||||||
"#4f85ff"
|
"#4f85ff"
|
||||||
// Level 4: Bright electric DeepSeek blue
|
// Level 4: Bright electric DeepSeek blue
|
||||||
];
|
];
|
||||||
var TILE_SHADOWS = [
|
|
||||||
"none",
|
|
||||||
// Level 0: Black / flat
|
|
||||||
"none",
|
|
||||||
// Level 1: Dark deep blue / flat
|
|
||||||
"none",
|
|
||||||
// Level 2: Mid blue / flat
|
|
||||||
"0 0 4px rgba(51, 109, 250, 0.4)",
|
|
||||||
// Level 3: Faint brand blue edge
|
|
||||||
"0 0 10px rgba(79, 133, 255, 0.85), 0 0 2px rgba(255, 255, 255, 0.5)"
|
|
||||||
// Level 4: Bright electric blue glow
|
|
||||||
];
|
|
||||||
function HeatmapView() {
|
function HeatmapView() {
|
||||||
const [data, setData] = (0, import_react.useState)(null);
|
const [data, setData] = (0, import_react.useState)(null);
|
||||||
const [loading, setLoading] = (0, import_react.useState)(true);
|
const [loading, setLoading] = (0, import_react.useState)(true);
|
||||||
const [error, setError] = (0, import_react.useState)(null);
|
const [error, setError] = (0, import_react.useState)(null);
|
||||||
const [selectedSessionId, setSelectedSessionId] = (0, import_react.useState)("all");
|
const [selectedSessionId, setSelectedSessionId] = (0, import_react.useState)("all");
|
||||||
|
const [selectedDate, setSelectedDate] = (0, import_react.useState)(null);
|
||||||
const [hoveredDay, setHoveredDay] = (0, import_react.useState)(null);
|
const [hoveredDay, setHoveredDay] = (0, import_react.useState)(null);
|
||||||
const [tooltipPos, setTooltipPos] = (0, import_react.useState)(null);
|
const [tooltipPos, setTooltipPos] = (0, import_react.useState)(null);
|
||||||
const fetchStats = () => {
|
const fetchStats = () => {
|
||||||
|
|
@ -240,6 +228,7 @@ function HeatmapView() {
|
||||||
if (!data || !activeView) return null;
|
if (!data || !activeView) return null;
|
||||||
const { summary } = activeView;
|
const { summary } = activeView;
|
||||||
const cacheHitPercent = summary.totalTokens > 0 ? Math.round(summary.totalCacheReadTokens / summary.totalTokens * 100) : 0;
|
const cacheHitPercent = summary.totalTokens > 0 ? Math.round(summary.totalCacheReadTokens / summary.totalTokens * 100) : 0;
|
||||||
|
const inspectedDayStats = selectedDate ? activeView.days[selectedDate] : null;
|
||||||
return /* @__PURE__ */ import_react.default.createElement(
|
return /* @__PURE__ */ import_react.default.createElement(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
|
|
@ -309,7 +298,10 @@ function HeatmapView() {
|
||||||
"select",
|
"select",
|
||||||
{
|
{
|
||||||
value: selectedSessionId,
|
value: selectedSessionId,
|
||||||
onChange: (e) => setSelectedSessionId(e.target.value),
|
onChange: (e) => {
|
||||||
|
setSelectedSessionId(e.target.value);
|
||||||
|
setSelectedDate(null);
|
||||||
|
},
|
||||||
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)",
|
||||||
|
|
@ -426,7 +418,6 @@ function HeatmapView() {
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
padding: "20px 22px",
|
padding: "20px 22px",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
overflowX: "auto",
|
|
||||||
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)"
|
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -445,103 +436,185 @@ function HeatmapView() {
|
||||||
/* @__PURE__ */ import_react.default.createElement("span", null, "Contribution Calendar"),
|
/* @__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)")
|
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: { display: "inline-block", minWidth: 780 } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", marginLeft: 32, marginBottom: 4, height: 16 } }, monthHeaders.map((m, i) => /* @__PURE__ */ import_react.default.createElement(
|
calendarGrid && /* @__PURE__ */ import_react.default.createElement("div", { style: { width: "100%", overflow: "hidden" } }, /* @__PURE__ */ import_react.default.createElement(
|
||||||
"div",
|
"svg",
|
||||||
{
|
{
|
||||||
key: i,
|
viewBox: "0 0 740 112",
|
||||||
style: {
|
style: {
|
||||||
position: "relative",
|
width: "100%",
|
||||||
left: m.col * 14,
|
height: "auto",
|
||||||
width: 0,
|
display: "block",
|
||||||
whiteSpace: "nowrap",
|
overflow: "visible"
|
||||||
fontSize: 10,
|
|
||||||
color: "var(--dsw-alias-label-tertiary, #a0a0a0)"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
m.label
|
monthHeaders.map((m, i) => /* @__PURE__ */ import_react.default.createElement(
|
||||||
))), /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", gap: 6 } }, /* @__PURE__ */ import_react.default.createElement(
|
"text",
|
||||||
"div",
|
|
||||||
{
|
|
||||||
style: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: 3,
|
|
||||||
width: 26,
|
|
||||||
fontSize: 9,
|
|
||||||
color: "var(--dsw-alias-label-tertiary, #a0a0a0)",
|
|
||||||
textAlign: "right",
|
|
||||||
paddingTop: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DAY_LABELS.map((lbl, idx) => /* @__PURE__ */ import_react.default.createElement("div", { key: idx, style: { height: 11, lineHeight: "11px" } }, lbl))
|
|
||||||
), /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", gap: 3 } }, calendarGrid.map((week, colIdx) => /* @__PURE__ */ import_react.default.createElement("div", { key: colIdx, style: { display: "flex", flexDirection: "column", gap: 3 } }, week.map((day, rowIdx) => {
|
|
||||||
if (!day) {
|
|
||||||
return /* @__PURE__ */ import_react.default.createElement("div", { key: rowIdx, style: { width: 11, height: 11 } });
|
|
||||||
}
|
|
||||||
const lvl = day.stats.level || 0;
|
|
||||||
const bg = DEEPSEEK_LEVELS[lvl];
|
|
||||||
const isHovered = hoveredDay?.date === day.date;
|
|
||||||
const currentShadow = isHovered ? lvl >= 3 ? "0 0 12px rgba(79, 133, 255, 0.95), 0 0 2px #ffffff" : "0 0 4px rgba(51, 109, 250, 0.4)" : TILE_SHADOWS[lvl];
|
|
||||||
return /* @__PURE__ */ import_react.default.createElement(
|
|
||||||
"div",
|
|
||||||
{
|
{
|
||||||
key: rowIdx,
|
key: i,
|
||||||
onMouseEnter: (e) => {
|
x: 30 + m.col * 13.2,
|
||||||
setHoveredDay({ date: day.date, stats: day.stats });
|
y: 10,
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
fontSize: 10,
|
||||||
setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
|
fill: "var(--dsw-alias-label-tertiary, #888888)",
|
||||||
},
|
fontFamily: "inherit"
|
||||||
onMouseLeave: () => {
|
},
|
||||||
setHoveredDay(null);
|
m.label
|
||||||
setTooltipPos(null);
|
)),
|
||||||
},
|
/* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 38, fontSize: 9, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Mon"),
|
||||||
style: {
|
/* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 64, fontSize: 9, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Wed"),
|
||||||
width: 11,
|
/* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 90, fontSize: 9, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Fri"),
|
||||||
height: 11,
|
calendarGrid.map((week, colIdx) => /* @__PURE__ */ import_react.default.createElement("g", { key: colIdx, transform: `translate(${28 + colIdx * 13.2}, 16)` }, week.map((day, rowIdx) => {
|
||||||
borderRadius: 2,
|
if (!day) return null;
|
||||||
backgroundColor: bg,
|
const lvl = day.stats.level || 0;
|
||||||
boxShadow: currentShadow,
|
const bg = DEEPSEEK_LEVELS[lvl];
|
||||||
border: lvl === 0 ? "1px solid rgba(255, 255, 255, 0.05)" : "1px solid transparent",
|
const isSelected = selectedDate === day.date;
|
||||||
cursor: "pointer",
|
const isHovered = hoveredDay?.date === day.date;
|
||||||
transform: isHovered ? "scale(1.15)" : "scale(1)",
|
const strokeColor = isSelected ? "#ffffff" : isHovered ? "#5686fe" : lvl === 0 ? "rgba(255, 255, 255, 0.05)" : "transparent";
|
||||||
zIndex: isHovered ? 5 : 1,
|
const strokeWidth = isSelected ? 1.5 : isHovered ? 1.2 : 1;
|
||||||
transition: "all 0.12s ease",
|
return /* @__PURE__ */ import_react.default.createElement(
|
||||||
position: "relative"
|
"rect",
|
||||||
|
{
|
||||||
|
key: rowIdx,
|
||||||
|
x: 0,
|
||||||
|
y: rowIdx * 13,
|
||||||
|
width: 10.5,
|
||||||
|
height: 10.5,
|
||||||
|
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(
|
), /* @__PURE__ */ import_react.default.createElement(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "flex-end",
|
justifyContent: "space-between",
|
||||||
gap: 6,
|
|
||||||
marginTop: 14,
|
marginTop: 14,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: "var(--dsw-alias-label-tertiary, #a0a0a0)"
|
color: "var(--dsw-alias-label-tertiary, #a0a0a0)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/* @__PURE__ */ import_react.default.createElement("span", null, "Less"),
|
/* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 11 } }, "Tip: Click any day to inspect sessions and token breakdown"),
|
||||||
DEEPSEEK_LEVELS.map((c, i) => /* @__PURE__ */ import_react.default.createElement(
|
/* @__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",
|
"div",
|
||||||
{
|
{
|
||||||
key: i,
|
key: i,
|
||||||
style: {
|
style: {
|
||||||
width: 11,
|
width: 10.5,
|
||||||
height: 11,
|
height: 10.5,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
backgroundColor: c,
|
backgroundColor: c,
|
||||||
boxShadow: TILE_SHADOWS[i],
|
border: i === 0 ? "1px solid rgba(255, 255, 255, 0.05)" : "none",
|
||||||
border: i === 0 ? "1px solid rgba(255, 255, 255, 0.05)" : "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("span", null, "More")
|
|
||||||
))
|
))
|
||||||
),
|
),
|
||||||
hoveredDay && tooltipPos && /* @__PURE__ */ import_react.default.createElement(
|
selectedDate && /* @__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: "18px 20px",
|
||||||
|
boxShadow: "0 6px 20px rgba(0, 0, 0, 0.25)",
|
||||||
|
animation: "fadeIn 0.15s ease"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* @__PURE__ */ import_react.default.createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
borderBottom: "1px solid var(--dsw-alias-border-l1, #3a3a3a)",
|
||||||
|
paddingBottom: 12,
|
||||||
|
marginBottom: 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 16, fontWeight: 700, color: "var(--dsw-alias-label-primary, #ffffff)" } }, (/* @__PURE__ */ new Date(selectedDate + "T00:00:00")).toLocaleDateString(void 0, {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric"
|
||||||
|
})), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #b9b9b9)", marginTop: 2 } }, inspectedDayStats && inspectedDayStats.totalTokens > 0 ? /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement("strong", { style: { color: "#4f85ff" } }, formatExact(inspectedDayStats.totalTokens)), " total tokens across", " ", /* @__PURE__ */ import_react.default.createElement("strong", null, inspectedDayStats.turns), " turns") : "No activity recorded")),
|
||||||
|
/* @__PURE__ */ import_react.default.createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: () => setSelectedDate(null),
|
||||||
|
style: {
|
||||||
|
background: "transparent",
|
||||||
|
border: "none",
|
||||||
|
color: "var(--dsw-alias-label-tertiary)",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: 16,
|
||||||
|
padding: "4px 8px",
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
title: "Close day view"
|
||||||
|
},
|
||||||
|
"\u2715"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
inspectedDayStats && inspectedDayStats.totalTokens > 0 && /* @__PURE__ */ import_react.default.createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
display: "flex",
|
||||||
|
gap: 12,
|
||||||
|
flexWrap: "wrap",
|
||||||
|
marginBottom: 16,
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* @__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", 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(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
key: idx,
|
||||||
|
style: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
padding: "10px 14px",
|
||||||
|
background: "var(--dsw-alias-bg-layer-1, #1e1e1e)",
|
||||||
|
border: "1px solid var(--dsw-alias-border-l1, #3a3a3a)",
|
||||||
|
borderRadius: 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* @__PURE__ */ import_react.default.createElement("div", { style: { minWidth: 0, flex: 1, paddingRight: 16 } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { fontWeight: 600, fontSize: 13, color: "var(--dsw-alias-label-primary, #ffffff)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, sess.sessionTitle), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", marginTop: 2 } }, "Started ", new Date(sess.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }), " \xB7 ID: ", sess.sessionId.slice(0, 10))),
|
||||||
|
/* @__PURE__ */ import_react.default.createElement("div", { style: { textAlign: "right", flexShrink: 0 } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { fontWeight: 700, fontSize: 13, color: "#4f85ff" } }, formatNumber(sess.totalTokens), " tokens"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary)", marginTop: 2 } }, sess.turns, " turns \xB7 ", formatNumber(sess.inputTokens + sess.outputTokens), " billed"))
|
||||||
|
))) : /* @__PURE__ */ import_react.default.createElement("div", { style: { padding: "16px", textAlign: "center", color: "var(--dsw-alias-label-tertiary)", fontSize: 12, background: "var(--dsw-alias-bg-layer-1)", borderRadius: 8 } }, "No session records found for this date."))
|
||||||
|
),
|
||||||
|
hoveredDay && tooltipPos && !selectedDate && /* @__PURE__ */ import_react.default.createElement(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
|
|
@ -567,7 +640,7 @@ function HeatmapView() {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric"
|
day: "numeric"
|
||||||
})),
|
})),
|
||||||
hoveredDay.stats.totalTokens === 0 ? /* @__PURE__ */ import_react.default.createElement("div", { style: { color: "var(--dsw-alias-label-tertiary)" } }, "No activity recorded") : /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { style: { color: "#4f85ff", fontWeight: 600 } }, formatExact(hoveredDay.stats.totalTokens), " tokens"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary)", marginTop: 2 } }, hoveredDay.stats.turns, " requests / steps"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 10, color: "var(--dsw-alias-label-tertiary)", marginTop: 4 } }, "Billed: ", formatNumber(hoveredDay.stats.inputTokens + hoveredDay.stats.outputTokens), " \xB7 Cache: ", formatNumber(hoveredDay.stats.cacheReadTokens)))
|
hoveredDay.stats.totalTokens === 0 ? /* @__PURE__ */ import_react.default.createElement("div", { style: { color: "var(--dsw-alias-label-tertiary)" } }, "No activity recorded") : /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { style: { color: "#4f85ff", fontWeight: 600 } }, formatExact(hoveredDay.stats.totalTokens), " tokens"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary)", marginTop: 2 } }, hoveredDay.stats.turns, " requests / steps"), /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 10, color: "var(--dsw-alias-label-tertiary)", marginTop: 4 } }, "Click to inspect sessions on this day"))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,16 @@
|
||||||
import React, { useEffect, useState, useMemo } from "react";
|
import React, { useEffect, useState, useMemo } from "react";
|
||||||
|
|
||||||
|
export interface DaySessionDetail {
|
||||||
|
sessionId: string;
|
||||||
|
sessionTitle: string;
|
||||||
|
createdAt: number;
|
||||||
|
totalTokens: number;
|
||||||
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
|
cacheReadTokens: number;
|
||||||
|
turns: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DayStats {
|
export interface DayStats {
|
||||||
totalTokens: number;
|
totalTokens: number;
|
||||||
inputTokens: number;
|
inputTokens: number;
|
||||||
|
|
@ -8,6 +19,7 @@ export interface DayStats {
|
||||||
cacheWriteTokens: number;
|
cacheWriteTokens: number;
|
||||||
turns: number;
|
turns: number;
|
||||||
level?: number;
|
level?: number;
|
||||||
|
sessions?: DaySessionDetail[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionSummary {
|
export interface SessionSummary {
|
||||||
|
|
@ -55,7 +67,7 @@ function formatExact(num: number): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||||
const DAY_LABELS = ["", "Mon", "", "Wed", "", "Fri", ""];
|
const DAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
|
|
||||||
// Simple monochromatic gradient: Black -> Vibrant DeepSeek Blue
|
// Simple monochromatic gradient: Black -> Vibrant DeepSeek Blue
|
||||||
const DEEPSEEK_LEVELS = [
|
const DEEPSEEK_LEVELS = [
|
||||||
|
|
@ -66,20 +78,12 @@ const DEEPSEEK_LEVELS = [
|
||||||
"#4f85ff", // Level 4: Bright electric DeepSeek blue
|
"#4f85ff", // Level 4: Bright electric DeepSeek blue
|
||||||
];
|
];
|
||||||
|
|
||||||
// Glow effect strictly for the bright blue contribution tiles
|
|
||||||
const TILE_SHADOWS = [
|
|
||||||
"none", // Level 0: Black / flat
|
|
||||||
"none", // Level 1: Dark deep blue / flat
|
|
||||||
"none", // Level 2: Mid blue / flat
|
|
||||||
"0 0 4px rgba(51, 109, 250, 0.4)", // Level 3: Faint brand blue edge
|
|
||||||
"0 0 10px rgba(79, 133, 255, 0.85), 0 0 2px rgba(255, 255, 255, 0.5)", // Level 4: Bright electric blue glow
|
|
||||||
];
|
|
||||||
|
|
||||||
export function HeatmapView() {
|
export function HeatmapView() {
|
||||||
const [data, setData] = useState<HeatmapResponse | null>(null);
|
const [data, setData] = useState<HeatmapResponse | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [selectedSessionId, setSelectedSessionId] = useState<string>("all");
|
const [selectedSessionId, setSelectedSessionId] = useState<string>("all");
|
||||||
|
const [selectedDate, setSelectedDate] = useState<string | null>(null);
|
||||||
const [hoveredDay, setHoveredDay] = useState<{ date: string; stats: DayStats } | null>(null);
|
const [hoveredDay, setHoveredDay] = useState<{ date: string; stats: DayStats } | null>(null);
|
||||||
const [tooltipPos, setTooltipPos] = useState<{ x: number; y: number } | null>(null);
|
const [tooltipPos, setTooltipPos] = useState<{ x: number; y: number } | null>(null);
|
||||||
|
|
||||||
|
|
@ -125,7 +129,6 @@ export function HeatmapView() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recalculate summary for this specific session
|
|
||||||
let totalTokens = session.totalTokens;
|
let totalTokens = session.totalTokens;
|
||||||
let totalInputTokens = session.inputTokens;
|
let totalInputTokens = session.inputTokens;
|
||||||
let totalOutputTokens = session.outputTokens;
|
let totalOutputTokens = session.outputTokens;
|
||||||
|
|
@ -141,7 +144,6 @@ export function HeatmapView() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-level for this session
|
|
||||||
const positiveValues = Object.values(session.days)
|
const positiveValues = Object.values(session.days)
|
||||||
.map((d) => d.totalTokens)
|
.map((d) => d.totalTokens)
|
||||||
.filter((t) => t > 0)
|
.filter((t) => t > 0)
|
||||||
|
|
@ -288,6 +290,8 @@ export function HeatmapView() {
|
||||||
? Math.round((summary.totalCacheReadTokens / summary.totalTokens) * 100)
|
? Math.round((summary.totalCacheReadTokens / summary.totalTokens) * 100)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
const inspectedDayStats = selectedDate ? activeView.days[selectedDate] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -356,7 +360,10 @@ export function HeatmapView() {
|
||||||
<span style={{ fontSize: 12, color: "var(--dsw-alias-label-tertiary)" }}>Session:</span>
|
<span style={{ fontSize: 12, color: "var(--dsw-alias-label-tertiary)" }}>Session:</span>
|
||||||
<select
|
<select
|
||||||
value={selectedSessionId}
|
value={selectedSessionId}
|
||||||
onChange={(e) => setSelectedSessionId(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setSelectedSessionId(e.target.value);
|
||||||
|
setSelectedDate(null);
|
||||||
|
}}
|
||||||
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)",
|
||||||
|
|
@ -494,7 +501,7 @@ export function HeatmapView() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Heatmap Card */}
|
{/* Heatmap Card (Auto-resizes responsive SVG panel, GitHub style) */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
|
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
|
||||||
|
|
@ -502,7 +509,6 @@ export function HeatmapView() {
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
padding: "20px 22px",
|
padding: "20px 22px",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
overflowX: "auto",
|
|
||||||
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)",
|
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -524,135 +530,285 @@ export function HeatmapView() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* SVG Heatmap */}
|
{/* Responsive GitHub-Style Calendar SVG (scales with container width) */}
|
||||||
{calendarGrid && (
|
{calendarGrid && (
|
||||||
<div style={{ display: "inline-block", minWidth: 780 }}>
|
<div style={{ width: "100%", overflow: "hidden" }}>
|
||||||
{/* Month labels */}
|
<svg
|
||||||
<div style={{ display: "flex", marginLeft: 32, marginBottom: 4, height: 16 }}>
|
viewBox="0 0 740 112"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "auto",
|
||||||
|
display: "block",
|
||||||
|
overflow: "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Month labels */}
|
||||||
{monthHeaders.map((m, i) => (
|
{monthHeaders.map((m, i) => (
|
||||||
<div
|
<text
|
||||||
key={i}
|
key={i}
|
||||||
style={{
|
x={30 + m.col * 13.2}
|
||||||
position: "relative",
|
y={10}
|
||||||
left: m.col * 14,
|
fontSize={10}
|
||||||
width: 0,
|
fill="var(--dsw-alias-label-tertiary, #888888)"
|
||||||
whiteSpace: "nowrap",
|
fontFamily="inherit"
|
||||||
fontSize: 10,
|
|
||||||
color: "var(--dsw-alias-label-tertiary, #a0a0a0)",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{m.label}
|
{m.label}
|
||||||
</div>
|
</text>
|
||||||
))}
|
))}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Grid with day labels */}
|
|
||||||
<div style={{ display: "flex", gap: 6 }}>
|
|
||||||
{/* Day of week labels */}
|
{/* Day of week labels */}
|
||||||
<div
|
<text x={2} y={38} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
|
||||||
style={{
|
Mon
|
||||||
display: "flex",
|
</text>
|
||||||
flexDirection: "column",
|
<text x={2} y={64} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
|
||||||
gap: 3,
|
Wed
|
||||||
width: 26,
|
</text>
|
||||||
fontSize: 9,
|
<text x={2} y={90} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
|
||||||
color: "var(--dsw-alias-label-tertiary, #a0a0a0)",
|
Fri
|
||||||
textAlign: "right",
|
</text>
|
||||||
paddingTop: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{DAY_LABELS.map((lbl, idx) => (
|
|
||||||
<div key={idx} style={{ height: 11, lineHeight: "11px" }}>
|
|
||||||
{lbl}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Columns of weeks */}
|
{/* Day Tiles */}
|
||||||
<div style={{ display: "flex", gap: 3 }}>
|
{calendarGrid.map((week, colIdx) => (
|
||||||
{calendarGrid.map((week, colIdx) => (
|
<g key={colIdx} transform={`translate(${28 + colIdx * 13.2}, 16)`}>
|
||||||
<div key={colIdx} style={{ display: "flex", flexDirection: "column", gap: 3 }}>
|
{week.map((day, rowIdx) => {
|
||||||
{week.map((day, rowIdx) => {
|
if (!day) return null;
|
||||||
if (!day) {
|
|
||||||
return <div key={rowIdx} style={{ width: 11, height: 11 }} />;
|
|
||||||
}
|
|
||||||
const lvl = day.stats.level || 0;
|
|
||||||
const bg = DEEPSEEK_LEVELS[lvl];
|
|
||||||
const isHovered = hoveredDay?.date === day.date;
|
|
||||||
const currentShadow = isHovered
|
|
||||||
? (lvl >= 3 ? "0 0 12px rgba(79, 133, 255, 0.95), 0 0 2px #ffffff" : "0 0 4px rgba(51, 109, 250, 0.4)")
|
|
||||||
: TILE_SHADOWS[lvl];
|
|
||||||
|
|
||||||
return (
|
const lvl = day.stats.level || 0;
|
||||||
<div
|
const bg = DEEPSEEK_LEVELS[lvl];
|
||||||
key={rowIdx}
|
const isSelected = selectedDate === day.date;
|
||||||
onMouseEnter={(e) => {
|
const isHovered = hoveredDay?.date === day.date;
|
||||||
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);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
width: 11,
|
|
||||||
height: 11,
|
|
||||||
borderRadius: 2,
|
|
||||||
backgroundColor: bg,
|
|
||||||
boxShadow: currentShadow,
|
|
||||||
border:
|
|
||||||
lvl === 0
|
|
||||||
? "1px solid rgba(255, 255, 255, 0.05)"
|
|
||||||
: "1px solid transparent",
|
|
||||||
cursor: "pointer",
|
|
||||||
transform: isHovered ? "scale(1.15)" : "scale(1)",
|
|
||||||
zIndex: isHovered ? 5 : 1,
|
|
||||||
transition: "all 0.12s ease",
|
|
||||||
position: "relative",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Legend & Footnote */}
|
// GitHub-style glow & outline
|
||||||
|
const strokeColor = isSelected
|
||||||
|
? "#ffffff"
|
||||||
|
: isHovered
|
||||||
|
? "#5686fe"
|
||||||
|
: lvl === 0
|
||||||
|
? "rgba(255, 255, 255, 0.05)"
|
||||||
|
: "transparent";
|
||||||
|
|
||||||
|
const strokeWidth = isSelected ? 1.5 : isHovered ? 1.2 : 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<rect
|
||||||
|
key={rowIdx}
|
||||||
|
x={0}
|
||||||
|
y={rowIdx * 13}
|
||||||
|
width={10.5}
|
||||||
|
height={10.5}
|
||||||
|
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
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "flex-end",
|
justifyContent: "space-between",
|
||||||
gap: 6,
|
|
||||||
marginTop: 14,
|
marginTop: 14,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: "var(--dsw-alias-label-tertiary, #a0a0a0)",
|
color: "var(--dsw-alias-label-tertiary, #a0a0a0)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>Less</span>
|
<span style={{ fontSize: 11 }}>
|
||||||
{DEEPSEEK_LEVELS.map((c, i) => (
|
Tip: Click any day to inspect sessions and token breakdown
|
||||||
<div
|
</span>
|
||||||
key={i}
|
|
||||||
style={{
|
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
||||||
width: 11,
|
<span>Less</span>
|
||||||
height: 11,
|
{DEEPSEEK_LEVELS.map((c, i) => (
|
||||||
borderRadius: 2,
|
<div
|
||||||
backgroundColor: c,
|
key={i}
|
||||||
boxShadow: TILE_SHADOWS[i],
|
style={{
|
||||||
border: i === 0 ? "1px solid rgba(255, 255, 255, 0.05)" : "none",
|
width: 10.5,
|
||||||
}}
|
height: 10.5,
|
||||||
/>
|
borderRadius: 2,
|
||||||
))}
|
backgroundColor: c,
|
||||||
<span>More</span>
|
border: i === 0 ? "1px solid rgba(255, 255, 255, 0.05)" : "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>
|
||||||
|
|
||||||
|
{/* Day Inspection Detail Drawer (Opens when any day is clicked) */}
|
||||||
|
{selectedDate && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 18,
|
||||||
|
background: "var(--dsw-alias-bg-layer-2, #2c2c2c)",
|
||||||
|
border: "1px solid var(--dsw-alias-border-l2, #494949)",
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: "18px 20px",
|
||||||
|
boxShadow: "0 6px 20px rgba(0, 0, 0, 0.25)",
|
||||||
|
animation: "fadeIn 0.15s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header of the Day Detail */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
borderBottom: "1px solid var(--dsw-alias-border-l1, #3a3a3a)",
|
||||||
|
paddingBottom: 12,
|
||||||
|
marginBottom: 14,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 16, fontWeight: 700, color: "var(--dsw-alias-label-primary, #ffffff)" }}>
|
||||||
|
{new Date(selectedDate + "T00:00:00").toLocaleDateString(undefined, {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: "var(--dsw-alias-label-secondary, #b9b9b9)", marginTop: 2 }}>
|
||||||
|
{inspectedDayStats && inspectedDayStats.totalTokens > 0 ? (
|
||||||
|
<span>
|
||||||
|
<strong style={{ color: "#4f85ff" }}>{formatExact(inspectedDayStats.totalTokens)}</strong> total tokens across{" "}
|
||||||
|
<strong>{inspectedDayStats.turns}</strong> turns
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
"No activity recorded"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedDate(null)}
|
||||||
|
style={{
|
||||||
|
background: "transparent",
|
||||||
|
border: "none",
|
||||||
|
color: "var(--dsw-alias-label-tertiary)",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: 16,
|
||||||
|
padding: "4px 8px",
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
title="Close day view"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Breakdown Pills for the day */}
|
||||||
|
{inspectedDayStats && inspectedDayStats.totalTokens > 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: 12,
|
||||||
|
flexWrap: "wrap",
|
||||||
|
marginBottom: 16,
|
||||||
|
fontSize: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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)" }}>Billed Input: </span>
|
||||||
|
<strong>{formatNumber(inspectedDayStats.inputTokens)}</strong>
|
||||||
|
</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)" }}>Output Generated: </span>
|
||||||
|
<strong>{formatNumber(inspectedDayStats.outputTokens)}</strong>
|
||||||
|
</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> (
|
||||||
|
{Math.round((inspectedDayStats.cacheReadTokens / inspectedDayStats.totalTokens) * 100)}%)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 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})
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{inspectedDayStats?.sessions && inspectedDayStats.sessions.length > 0 ? (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 8, maxHeight: 320, overflowY: "auto" }}>
|
||||||
|
{inspectedDayStats.sessions.map((sess, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
padding: "10px 14px",
|
||||||
|
background: "var(--dsw-alias-bg-layer-1, #1e1e1e)",
|
||||||
|
border: "1px solid var(--dsw-alias-border-l1, #3a3a3a)",
|
||||||
|
borderRadius: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ minWidth: 0, flex: 1, paddingRight: 16 }}>
|
||||||
|
<div style={{ fontWeight: 600, fontSize: 13, color: "var(--dsw-alias-label-primary, #ffffff)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||||
|
{sess.sessionTitle}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 11, color: "var(--dsw-alias-label-tertiary, #a0a0a0)", marginTop: 2 }}>
|
||||||
|
Started {new Date(sess.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} · ID: {sess.sessionId.slice(0, 10)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ textAlign: "right", flexShrink: 0 }}>
|
||||||
|
<div style={{ fontWeight: 700, fontSize: 13, color: "#4f85ff" }}>
|
||||||
|
{formatNumber(sess.totalTokens)} tokens
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 11, color: "var(--dsw-alias-label-secondary)", marginTop: 2 }}>
|
||||||
|
{sess.turns} turns · {formatNumber(sess.inputTokens + sess.outputTokens)} billed
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ padding: "16px", textAlign: "center", color: "var(--dsw-alias-label-tertiary)", fontSize: 12, background: "var(--dsw-alias-bg-layer-1)", borderRadius: 8 }}>
|
||||||
|
No session records found for this date.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Floating Hover Tooltip */}
|
{/* Floating Hover Tooltip */}
|
||||||
{hoveredDay && tooltipPos && (
|
{hoveredDay && tooltipPos && !selectedDate && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
|
|
@ -690,7 +846,7 @@ export function HeatmapView() {
|
||||||
{hoveredDay.stats.turns} requests / steps
|
{hoveredDay.stats.turns} requests / steps
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 10, color: "var(--dsw-alias-label-tertiary)", marginTop: 4 }}>
|
<div style={{ fontSize: 10, color: "var(--dsw-alias-label-tertiary)", marginTop: 4 }}>
|
||||||
Billed: {formatNumber(hoveredDay.stats.inputTokens + hoveredDay.stats.outputTokens)} · Cache: {formatNumber(hoveredDay.stats.cacheReadTokens)}
|
Click to inspect sessions on this day
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,17 @@ import path from "node:path";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
|
|
||||||
|
export interface DaySessionDetail {
|
||||||
|
sessionId: string;
|
||||||
|
sessionTitle: string;
|
||||||
|
createdAt: number;
|
||||||
|
totalTokens: number;
|
||||||
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
|
cacheReadTokens: number;
|
||||||
|
turns: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DayStats {
|
export interface DayStats {
|
||||||
totalTokens: number;
|
totalTokens: number;
|
||||||
inputTokens: number;
|
inputTokens: number;
|
||||||
|
|
@ -11,6 +22,7 @@ export interface DayStats {
|
||||||
cacheWriteTokens: number;
|
cacheWriteTokens: number;
|
||||||
turns: number;
|
turns: number;
|
||||||
level?: number; // 0..4
|
level?: number; // 0..4
|
||||||
|
sessions?: DaySessionDetail[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionSummary {
|
export interface SessionSummary {
|
||||||
|
|
@ -229,6 +241,7 @@ export async function aggregateTokens(customSessionsRoot?: string): Promise<Heat
|
||||||
cacheReadTokens: 0,
|
cacheReadTokens: 0,
|
||||||
cacheWriteTokens: 0,
|
cacheWriteTokens: 0,
|
||||||
turns: 0,
|
turns: 0,
|
||||||
|
sessions: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
days[date].totalTokens += stats.totalTokens;
|
days[date].totalTokens += stats.totalTokens;
|
||||||
|
|
@ -237,6 +250,24 @@ export async function aggregateTokens(customSessionsRoot?: string): Promise<Heat
|
||||||
days[date].cacheReadTokens += stats.cacheReadTokens;
|
days[date].cacheReadTokens += stats.cacheReadTokens;
|
||||||
days[date].cacheWriteTokens += stats.cacheWriteTokens;
|
days[date].cacheWriteTokens += stats.cacheWriteTokens;
|
||||||
days[date].turns += stats.turns;
|
days[date].turns += stats.turns;
|
||||||
|
|
||||||
|
days[date].sessions!.push({
|
||||||
|
sessionId: session.id,
|
||||||
|
sessionTitle: session.title,
|
||||||
|
createdAt: session.createdAt,
|
||||||
|
totalTokens: stats.totalTokens,
|
||||||
|
inputTokens: stats.inputTokens,
|
||||||
|
outputTokens: stats.outputTokens,
|
||||||
|
cacheReadTokens: stats.cacheReadTokens,
|
||||||
|
turns: stats.turns,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort sessions within each day by totalTokens descending
|
||||||
|
for (const day of Object.values(days)) {
|
||||||
|
if (day.sessions) {
|
||||||
|
day.sessions.sort((a, b) => b.totalTokens - a.totalTokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue