From 4c649610c9cebe39856d7a973478931676c5c09c Mon Sep 17 00:00:00 2001 From: teernisse Date: Mon, 23 Feb 2026 09:36:50 -0500 Subject: [PATCH] feat: add live activity charts to TUI overview tab Add a new "Row 2.5" to the overview tab between the trend chart and model/activity panels, showing two side-by-side live activity charts: - Today: 24-bar hourly token histogram with 12h-format labels (12a, 1a, ... 11p). Header shows total tokens consumed today. - Last Hour: 12-bar five-minute token histogram with relative-time labels (-55, -50, ... -5, now). Header shows tokens in the last 60 minutes. Both charts use BarChart with theme-colored bars (Blue for today, Accent for last hour) and adapt height in compact layouts. Helper functions hourLabels24() and minuteLabels() generate the X-axis label arrays. Co-Authored-By: Claude Opus 4.6 --- internal/tui/tab_overview.go | 76 ++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/internal/tui/tab_overview.go b/internal/tui/tab_overview.go index badb61b..930c301 100644 --- a/internal/tui/tab_overview.go +++ b/internal/tui/tab_overview.go @@ -70,6 +70,59 @@ func (a App) renderOverviewTab(cw int) string { b.WriteString("\n") } + // Row 2.5: Live Activity (Today + Last Hour) + liveHalves := components.LayoutRow(cw, 2) + liveChartH := 8 + if a.isCompactLayout() { + liveChartH = 6 + } + + // Left: Today's hourly activity + var todayCard string + if len(a.todayHourly) > 0 { + hourVals := make([]float64, 24) + var todayTotal int64 + for i, h := range a.todayHourly { + hourVals[i] = float64(h.Tokens) + todayTotal += h.Tokens + } + todayCard = components.ContentCard( + fmt.Sprintf("Today (%s)", cli.FormatTokens(todayTotal)), + components.BarChart(hourVals, hourLabels24(), t.Blue, components.CardInnerWidth(liveHalves[0]), liveChartH), + liveHalves[0], + ) + } + + // Right: Last hour's 5-minute activity + var lastHourCard string + if len(a.lastHour) > 0 { + minVals := make([]float64, 12) + var hourTotal int64 + for i, m := range a.lastHour { + minVals[i] = float64(m.Tokens) + hourTotal += m.Tokens + } + lastHourCard = components.ContentCard( + fmt.Sprintf("Last Hour (%s)", cli.FormatTokens(hourTotal)), + components.BarChart(minVals, minuteLabels(), t.Accent, components.CardInnerWidth(liveHalves[1]), liveChartH), + liveHalves[1], + ) + } + + if a.isCompactLayout() { + if todayCard != "" { + b.WriteString(todayCard) + b.WriteString("\n") + } + if lastHourCard != "" { + b.WriteString(lastHourCard) + b.WriteString("\n") + } + } else { + b.WriteString(components.CardRow([]string{todayCard, lastHourCard})) + b.WriteString("\n") + } + // Row 3: Model Split + Activity Patterns halves := components.LayoutRow(cw, 2) innerW := components.CardInnerWidth(halves[0]) @@ -182,3 +235,26 @@ func (a App) renderOverviewTab(cw int) string { return b.String() } + +// hourLabels24 returns X-axis labels for 24 hourly buckets (one per hour). +func hourLabels24() []string { + labels := make([]string, 24) + for i := 0; i < 24; i++ { + h := i % 12 + if h == 0 { + h = 12 + } + suffix := "a" + if i >= 12 { + suffix = "p" + } + labels[i] = fmt.Sprintf("%d%s", h, suffix) + } + return labels +} + +// minuteLabels returns X-axis labels for 12 five-minute buckets (one per bucket). +// Bucket 0 is oldest (55-60 min ago), bucket 11 is newest (0-5 min ago). +func minuteLabels() []string { + return []string{"-55", "-50", "-45", "-40", "-35", "-30", "-25", "-20", "-15", "-10", "-5", "now"} +}