From 66848b6588e4b9a22e166ebfb2cd4ab952ec3ec9 Mon Sep 17 00:00:00 2001 From: Rob Bos Date: Sun, 1 Feb 2026 00:40:08 +0100 Subject: [PATCH 1/2] Show data for last month --- src/extension.ts | 105 ++++++++++++++++++++++---------- src/webview/details/main.ts | 96 +++++++++++++++++------------ src/webview/diagnostics/main.ts | 10 +-- 3 files changed, 136 insertions(+), 75 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index bffac67..078af8e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -34,31 +34,23 @@ interface EditorUsage { }; } +interface PeriodStats { + tokens: number; + sessions: number; + avgInteractionsPerSession: number; + avgTokensPerSession: number; + modelUsage: ModelUsage; + editorUsage: EditorUsage; + co2: number; + treesEquivalent: number; + waterUsage: number; + estimatedCost: number; +} + interface DetailedStats { - today: { - tokens: number; - sessions: number; - avgInteractionsPerSession: number; - avgTokensPerSession: number; - modelUsage: ModelUsage; - editorUsage: EditorUsage; - co2: number; - treesEquivalent: number; - waterUsage: number; - estimatedCost: number; - }; - month: { - tokens: number; - sessions: number; - avgInteractionsPerSession: number; - avgTokensPerSession: number; - modelUsage: ModelUsage; - editorUsage: EditorUsage; - co2: number; - treesEquivalent: number; - waterUsage: number; - estimatedCost: number; - }; + today: PeriodStats; + month: PeriodStats; + lastMonth: PeriodStats; lastUpdated: Date; } @@ -368,7 +360,7 @@ class CopilotTokenTracker implements vscode.Disposable { try { // Show the output channel so users can see what's happening this.outputChannel.show(true); - this.log('[DEBUG] clearCache() called'); + this.log('DEBUG clearCache() called'); this.log('Clearing session file cache...'); const cacheSize = this.sessionFileCache.size; @@ -377,6 +369,8 @@ class CopilotTokenTracker implements vscode.Disposable { // Reset diagnostics loaded flag so the diagnostics view will reload files this.diagnosticsHasLoadedFiles = false; this.diagnosticsCachedFiles = []; + // Clear cached computed stats so details panel doesn't show stale data + this.lastDetailedStats = undefined; this.log(`Cache cleared successfully. Removed ${cacheSize} entries.`); vscode.window.showInformationMessage('Cache cleared successfully. Reloading statistics...'); @@ -595,9 +589,13 @@ class CopilotTokenTracker implements vscode.Disposable { const now = new Date(); const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); + // Calculate last month boundaries + const lastMonthEnd = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999); // Last day of previous month + const lastMonthStart = new Date(lastMonthEnd.getFullYear(), lastMonthEnd.getMonth(), 1); const todayStats = { tokens: 0, sessions: 0, interactions: 0, modelUsage: {} as ModelUsage, editorUsage: {} as EditorUsage }; const monthStats = { tokens: 0, sessions: 0, interactions: 0, modelUsage: {} as ModelUsage, editorUsage: {} as EditorUsage }; + const lastMonthStats = { tokens: 0, sessions: 0, interactions: 0, modelUsage: {} as ModelUsage, editorUsage: {} as EditorUsage }; try { // Clean expired cache entries @@ -625,9 +623,9 @@ class CopilotTokenTracker implements vscode.Disposable { // Fast check: Get file stats first to avoid processing old files const fileStats = fs.statSync(sessionFile); - // Skip files modified before the current month (quick filter) + // Skip files modified before last month (quick filter) // This is the main performance optimization - filters out old sessions without reading file content - if (fileStats.mtime < monthStart) { + if (fileStats.mtime < lastMonthStart) { skippedFiles++; continue; } @@ -707,8 +705,36 @@ class CopilotTokenTracker implements vscode.Disposable { } } } + else if (lastActivity >= lastMonthStart && lastActivity <= lastMonthEnd) { + // Session is from last month - only track lastMonth stats + if (wasCached) { + cacheHits++; + } else { + cacheMisses++; + } + + lastMonthStats.tokens += tokens; + lastMonthStats.sessions += 1; + lastMonthStats.interactions += interactions; + + // Add editor usage to last month stats + if (!lastMonthStats.editorUsage[editorType]) { + lastMonthStats.editorUsage[editorType] = { tokens: 0, sessions: 0 }; + } + lastMonthStats.editorUsage[editorType].tokens += tokens; + lastMonthStats.editorUsage[editorType].sessions += 1; + + // Add model usage to last month stats + for (const [model, usage] of Object.entries(modelUsage)) { + if (!lastMonthStats.modelUsage[model]) { + lastMonthStats.modelUsage[model] = { inputTokens: 0, outputTokens: 0 }; + } + lastMonthStats.modelUsage[model].inputTokens += usage.inputTokens; + lastMonthStats.modelUsage[model].outputTokens += usage.outputTokens; + } + } else { - // Session is too old (no activity in current month), skip it + // Session is too old (no activity in current or last month), skip it skippedFiles++; } } catch (fileError) { @@ -716,9 +742,9 @@ class CopilotTokenTracker implements vscode.Disposable { } } - this.log(`✅ Analysis complete: Today ${todayStats.sessions} sessions, Month ${monthStats.sessions} sessions`); + this.log(`✅ Analysis complete: Today ${todayStats.sessions} sessions, Month ${monthStats.sessions} sessions, Last Month ${lastMonthStats.sessions} sessions`); if (skippedFiles > 0) { - this.log(`⏭️ Skipped ${skippedFiles} session file(s) (empty or no activity in current month)`); + this.log(`⏭️ Skipped ${skippedFiles} session file(s) (empty or no activity in recent months)`); } const totalCacheAccesses = cacheHits + cacheMisses; this.log(`💾 Cache performance: ${cacheHits} hits, ${cacheMisses} misses (${totalCacheAccesses > 0 ? ((cacheHits / totalCacheAccesses) * 100).toFixed(1) : 0}% hit rate)`); @@ -728,12 +754,15 @@ class CopilotTokenTracker implements vscode.Disposable { const todayCo2 = (todayStats.tokens / 1000) * this.co2Per1kTokens; const monthCo2 = (monthStats.tokens / 1000) * this.co2Per1kTokens; + const lastMonthCo2 = (lastMonthStats.tokens / 1000) * this.co2Per1kTokens; const todayWater = (todayStats.tokens / 1000) * this.waterUsagePer1kTokens; const monthWater = (monthStats.tokens / 1000) * this.waterUsagePer1kTokens; + const lastMonthWater = (lastMonthStats.tokens / 1000) * this.waterUsagePer1kTokens; const todayCost = this.calculateEstimatedCost(todayStats.modelUsage); const monthCost = this.calculateEstimatedCost(monthStats.modelUsage); + const lastMonthCost = this.calculateEstimatedCost(lastMonthStats.modelUsage); const result: DetailedStats = { today: { @@ -760,6 +789,18 @@ class CopilotTokenTracker implements vscode.Disposable { waterUsage: monthWater, estimatedCost: monthCost }, + lastMonth: { + tokens: lastMonthStats.tokens, + sessions: lastMonthStats.sessions, + avgInteractionsPerSession: lastMonthStats.sessions > 0 ? Math.round(lastMonthStats.interactions / lastMonthStats.sessions) : 0, + avgTokensPerSession: lastMonthStats.sessions > 0 ? Math.round(lastMonthStats.tokens / lastMonthStats.sessions) : 0, + modelUsage: lastMonthStats.modelUsage, + editorUsage: lastMonthStats.editorUsage, + co2: lastMonthCo2, + treesEquivalent: lastMonthCo2 / this.co2AbsorptionPerTreePerYear, + waterUsage: lastMonthWater, + estimatedCost: lastMonthCost + }, lastUpdated: now }; @@ -3218,7 +3259,7 @@ class CopilotTokenTracker implements vscode.Disposable { // Handle messages from the webview this.diagnosticsPanel.webview.onDidReceiveMessage(async (message) => { - this.log(`[DEBUG] Diagnostics webview message: ${JSON.stringify(message)}`); + this.log(`DEBUG Diagnostics webview message: ${JSON.stringify(message)}`); switch (message.command) { case 'copyReport': await vscode.env.clipboard.writeText(report); @@ -3278,7 +3319,7 @@ class CopilotTokenTracker implements vscode.Disposable { await this.showUsageAnalysis(); break; case 'clearCache': - this.log('[DEBUG] clearCache message received from diagnostics webview'); + this.log('DEBUG clearCache message received from diagnostics webview'); await this.clearCache(); // After clearing cache, refresh the diagnostic report if it's open if (this.diagnosticsPanel) { diff --git a/src/webview/details/main.ts b/src/webview/details/main.ts index d332f2b..93dcdb7 100644 --- a/src/webview/details/main.ts +++ b/src/webview/details/main.ts @@ -10,31 +10,23 @@ import tokenEstimatorsJson from '../../tokenEstimators.json'; type ModelUsage = Record; type EditorUsage = Record; +type PeriodStats = { + tokens: number; + sessions: number; + avgInteractionsPerSession: number; + avgTokensPerSession: number; + modelUsage: ModelUsage; + editorUsage: EditorUsage; + co2: number; + treesEquivalent: number; + waterUsage: number; + estimatedCost: number; +}; + type DetailedStats = { - today: { - tokens: number; - sessions: number; - avgInteractionsPerSession: number; - avgTokensPerSession: number; - modelUsage: ModelUsage; - editorUsage: EditorUsage; - co2: number; - treesEquivalent: number; - waterUsage: number; - estimatedCost: number; - }; - month: { - tokens: number; - sessions: number; - avgInteractionsPerSession: number; - avgTokensPerSession: number; - modelUsage: ModelUsage; - editorUsage: EditorUsage; - co2: number; - treesEquivalent: number; - waterUsage: number; - estimatedCost: number; - }; + today: PeriodStats; + month: PeriodStats; + lastMonth: PeriodStats; lastUpdated: string | Date; }; @@ -200,6 +192,7 @@ function buildMetricsSection( { icon: '📊', text: 'Metric' }, { icon: '📅', text: 'Today' }, { icon: '📈', text: 'This Month' }, + { icon: '📆', text: 'Last Month' }, { icon: '🌍', text: 'Projected Year' } ]; headers.forEach((h, idx) => { @@ -215,15 +208,15 @@ function buildMetricsSection( table.append(thead); const tbody = document.createElement('tbody'); - const rows: Array<{ label: string; icon: string; color?: string; today: string; month: string; projected: string }> = [ - { label: 'Tokens', icon: '🟣', color: '#c37bff', today: formatNumber(stats.today.tokens), month: formatNumber(stats.month.tokens), projected: formatNumber(projections.projectedTokens) }, - { label: 'Estimated cost', icon: '🪙', color: '#ffd166', today: formatCost(stats.today.estimatedCost), month: formatCost(stats.month.estimatedCost), projected: formatCost(projections.projectedCost) }, - { label: 'Sessions', icon: '📅', color: '#66aaff', today: formatNumber(stats.today.sessions), month: formatNumber(stats.month.sessions), projected: formatNumber(projections.projectedSessions) }, - { label: 'Average interactions/session', icon: '💬', color: '#8ce0ff', today: formatNumber(stats.today.avgInteractionsPerSession), month: formatNumber(stats.month.avgInteractionsPerSession), projected: '—' }, - { label: 'Average tokens/session', icon: '🔢', color: '#7ce38b', today: formatNumber(stats.today.avgTokensPerSession), month: formatNumber(stats.month.avgTokensPerSession), projected: '—' }, - { label: 'Estimated CO₂ (g)', icon: '🌱', color: '#7fe36f', today: `${formatFixed(stats.today.co2, 2)} g`, month: `${formatFixed(stats.month.co2, 2)} g`, projected: `${formatFixed(projections.projectedCo2, 2)} g` }, - { label: 'Estimated water (L)', icon: '💧', color: '#6fc3ff', today: `${formatFixed(stats.today.waterUsage, 3)} L`, month: `${formatFixed(stats.month.waterUsage, 3)} L`, projected: `${formatFixed(projections.projectedWater, 3)} L` }, - { label: 'Tree equivalent (yr)', icon: '🌳', color: '#9de67f', today: stats.today.treesEquivalent.toFixed(6), month: stats.month.treesEquivalent.toFixed(6), projected: projections.projectedTrees.toFixed(4) } + const rows: Array<{ label: string; icon: string; color?: string; today: string; month: string; lastMonth: string; projected: string }> = [ + { label: 'Tokens', icon: '🟣', color: '#c37bff', today: formatNumber(stats.today.tokens), month: formatNumber(stats.month.tokens), lastMonth: formatNumber(stats.lastMonth.tokens), projected: formatNumber(projections.projectedTokens) }, + { label: 'Estimated cost', icon: '🪙', color: '#ffd166', today: formatCost(stats.today.estimatedCost), month: formatCost(stats.month.estimatedCost), lastMonth: formatCost(stats.lastMonth.estimatedCost), projected: formatCost(projections.projectedCost) }, + { label: 'Sessions', icon: '📅', color: '#66aaff', today: formatNumber(stats.today.sessions), month: formatNumber(stats.month.sessions), lastMonth: formatNumber(stats.lastMonth.sessions), projected: formatNumber(projections.projectedSessions) }, + { label: 'Average interactions/session', icon: '💬', color: '#8ce0ff', today: formatNumber(stats.today.avgInteractionsPerSession), month: formatNumber(stats.month.avgInteractionsPerSession), lastMonth: formatNumber(stats.lastMonth.avgInteractionsPerSession), projected: '—' }, + { label: 'Average tokens/session', icon: '🔢', color: '#7ce38b', today: formatNumber(stats.today.avgTokensPerSession), month: formatNumber(stats.month.avgTokensPerSession), lastMonth: formatNumber(stats.lastMonth.avgTokensPerSession), projected: '—' }, + { label: 'Estimated CO₂ (g)', icon: '🌱', color: '#7fe36f', today: `${formatFixed(stats.today.co2, 2)} g`, month: `${formatFixed(stats.month.co2, 2)} g`, lastMonth: `${formatFixed(stats.lastMonth.co2, 2)} g`, projected: `${formatFixed(projections.projectedCo2, 2)} g` }, + { label: 'Estimated water (L)', icon: '💧', color: '#6fc3ff', today: `${formatFixed(stats.today.waterUsage, 3)} L`, month: `${formatFixed(stats.month.waterUsage, 3)} L`, lastMonth: `${formatFixed(stats.lastMonth.waterUsage, 3)} L`, projected: `${formatFixed(projections.projectedWater, 3)} L` }, + { label: 'Tree equivalent (yr)', icon: '🌳', color: '#9de67f', today: stats.today.treesEquivalent.toFixed(6), month: stats.month.treesEquivalent.toFixed(6), lastMonth: stats.lastMonth.treesEquivalent.toFixed(6), projected: projections.projectedTrees.toFixed(4) } ]; rows.forEach(row => { @@ -247,11 +240,15 @@ function buildMetricsSection( monthTd.className = 'value-right align-right'; monthTd.textContent = row.month; + const lastMonthTd = document.createElement('td'); + lastMonthTd.className = 'value-right align-right'; + lastMonthTd.textContent = row.lastMonth; + const projTd = document.createElement('td'); projTd.className = 'value-right align-right'; projTd.textContent = row.projected; - tr.append(labelTd, todayTd, monthTd, projTd); + tr.append(labelTd, todayTd, monthTd, lastMonthTd, projTd); tbody.append(tr); }); @@ -263,7 +260,8 @@ function buildMetricsSection( function buildEditorUsageSection(stats: DetailedStats): HTMLElement | null { const allEditors = new Set([ ...Object.keys(stats.today.editorUsage), - ...Object.keys(stats.month.editorUsage) + ...Object.keys(stats.month.editorUsage), + ...Object.keys(stats.lastMonth.editorUsage) ]); if (allEditors.size === 0) { @@ -272,6 +270,7 @@ function buildEditorUsageSection(stats: DetailedStats): HTMLElement | null { const todayTotal = Object.values(stats.today.editorUsage).reduce((sum, e) => sum + e.tokens, 0); const monthTotal = Object.values(stats.month.editorUsage).reduce((sum, e) => sum + e.tokens, 0); + const lastMonthTotal = Object.values(stats.lastMonth.editorUsage).reduce((sum, e) => sum + e.tokens, 0); const section = el('div', 'section'); const heading = el('h3'); @@ -287,6 +286,7 @@ function buildEditorUsageSection(stats: DetailedStats): HTMLElement | null { { icon: '📝', text: 'Editor' }, { icon: '📅', text: 'Today' }, { icon: '📈', text: 'This Month' }, + { icon: '📆', text: 'Last Month' }, { icon: '🌍', text: 'Projected Year' } ]; headers.forEach((h, idx) => { @@ -306,8 +306,10 @@ function buildEditorUsageSection(stats: DetailedStats): HTMLElement | null { Array.from(allEditors).sort().forEach(editor => { const todayUsage = stats.today.editorUsage[editor] || { tokens: 0, sessions: 0 }; const monthUsage = stats.month.editorUsage[editor] || { tokens: 0, sessions: 0 }; + const lastMonthUsage = stats.lastMonth.editorUsage[editor] || { tokens: 0, sessions: 0 }; const todayPercent = todayTotal > 0 ? (todayUsage.tokens / todayTotal) * 100 : 0; const monthPercent = monthTotal > 0 ? (monthUsage.tokens / monthTotal) * 100 : 0; + const lastMonthPercent = lastMonthTotal > 0 ? (lastMonthUsage.tokens / lastMonthTotal) * 100 : 0; const projectedTokens = Math.round(calculateProjection(monthUsage.tokens)); const projectedSessions = Math.round(calculateProjection(monthUsage.sessions)); @@ -330,13 +332,19 @@ function buildEditorUsageSection(stats: DetailedStats): HTMLElement | null { const monthSub = el('div', 'muted', `${formatPercent(monthPercent)} · ${monthUsage.sessions} sessions`); monthTd.append(monthSub); + const lastMonthTd = document.createElement('td'); + lastMonthTd.className = 'value-right align-right'; + lastMonthTd.textContent = formatNumber(lastMonthUsage.tokens); + const lastMonthSub = el('div', 'muted', `${formatPercent(lastMonthPercent)} · ${lastMonthUsage.sessions} sessions`); + lastMonthTd.append(lastMonthSub); + const projTd = document.createElement('td'); projTd.className = 'value-right align-right'; projTd.textContent = formatNumber(projectedTokens); const projSub = el('div', 'muted', `${projectedSessions} sessions`); projTd.append(projSub); - tr.append(labelTd, todayTd, monthTd, projTd); + tr.append(labelTd, todayTd, monthTd, lastMonthTd, projTd); tbody.append(tr); }); @@ -348,7 +356,8 @@ function buildEditorUsageSection(stats: DetailedStats): HTMLElement | null { function buildModelUsageSection(stats: DetailedStats): HTMLElement | null { const allModels = new Set([ ...Object.keys(stats.today.modelUsage), - ...Object.keys(stats.month.modelUsage) + ...Object.keys(stats.month.modelUsage), + ...Object.keys(stats.lastMonth.modelUsage) ]); if (allModels.size === 0) { @@ -369,6 +378,7 @@ function buildModelUsageSection(stats: DetailedStats): HTMLElement | null { { icon: '🧠', text: 'Model' }, { icon: '📅', text: 'Today' }, { icon: '📈', text: 'This Month' }, + { icon: '📆', text: 'Last Month' }, { icon: '🌍', text: 'Projected Year' } ]; headers.forEach((h, idx) => { @@ -388,13 +398,17 @@ function buildModelUsageSection(stats: DetailedStats): HTMLElement | null { Array.from(allModels).forEach(model => { const todayUsage = stats.today.modelUsage[model] || { inputTokens: 0, outputTokens: 0 }; const monthUsage = stats.month.modelUsage[model] || { inputTokens: 0, outputTokens: 0 }; + const lastMonthUsage = stats.lastMonth.modelUsage[model] || { inputTokens: 0, outputTokens: 0 }; const todayTotal = todayUsage.inputTokens + todayUsage.outputTokens; const monthTotal = monthUsage.inputTokens + monthUsage.outputTokens; + const lastMonthTotal = lastMonthUsage.inputTokens + lastMonthUsage.outputTokens; const projected = Math.round(calculateProjection(monthTotal)); const todayInputPct = todayTotal > 0 ? (todayUsage.inputTokens / todayTotal) * 100 : 0; const todayOutputPct = todayTotal > 0 ? (todayUsage.outputTokens / todayTotal) * 100 : 0; const monthInputPct = monthTotal > 0 ? (monthUsage.inputTokens / monthTotal) * 100 : 0; const monthOutputPct = monthTotal > 0 ? (monthUsage.outputTokens / monthTotal) * 100 : 0; + const lastMonthInputPct = lastMonthTotal > 0 ? (lastMonthUsage.inputTokens / lastMonthTotal) * 100 : 0; + const lastMonthOutputPct = lastMonthTotal > 0 ? (lastMonthUsage.outputTokens / lastMonthTotal) * 100 : 0; const charsPerToken = getCharsPerToken(model); const tr = document.createElement('tr'); @@ -416,11 +430,17 @@ function buildModelUsageSection(stats: DetailedStats): HTMLElement | null { const monthSub = el('div', 'muted', `↑${formatPercent(monthInputPct)} ↓${formatPercent(monthOutputPct)}`); monthTd.append(monthSub); + const lastMonthTd = document.createElement('td'); + lastMonthTd.className = 'value-right align-right'; + lastMonthTd.textContent = formatNumber(lastMonthTotal); + const lastMonthSub = el('div', 'muted', `↑${formatPercent(lastMonthInputPct)} ↓${formatPercent(lastMonthOutputPct)}`); + lastMonthTd.append(lastMonthSub); + const projTd = document.createElement('td'); projTd.className = 'value-right align-right'; projTd.textContent = formatNumber(projected); - tr.append(labelTd, todayTd, monthTd, projTd); + tr.append(labelTd, todayTd, monthTd, lastMonthTd, projTd); tbody.append(tr); }); diff --git a/src/webview/diagnostics/main.ts b/src/webview/diagnostics/main.ts index efedf35..d878209 100644 --- a/src/webview/diagnostics/main.ts +++ b/src/webview/diagnostics/main.ts @@ -734,7 +734,7 @@ function renderLayout(data: DiagnosticsData): void { btnTab.disabled = false; } - console.log('[DEBUG] Cache cleared confirmation received'); + console.log('DEBUG Cache cleared confirmation received'); // Re-enable buttons after a short delay and reset to original state setTimeout(() => { @@ -771,7 +771,7 @@ function renderLayout(data: DiagnosticsData): void { if (ageValue) { ageValue.textContent = '0 seconds ago'; } } } - console.log('[DEBUG] Cache refreshed with new data:', cacheInfo); + console.log('DEBUG Cache refreshed with new data:', cacheInfo); } } }); @@ -917,7 +917,7 @@ function setupStorageLinkHandlers(): void { } document.getElementById('btn-clear-cache')?.addEventListener('click', () => { - console.log('[DEBUG] Clear cache button clicked (report tab)'); + console.log('DEBUG Clear cache button clicked (report tab)'); const btn = document.getElementById('btn-clear-cache') as HTMLButtonElement | null; if (btn) { btn.style.background = '#d97706'; @@ -930,7 +930,7 @@ function setupStorageLinkHandlers(): void { }); document.getElementById('btn-clear-cache-tab')?.addEventListener('click', () => { - console.log('[DEBUG] Clear cache button clicked (cache tab)'); + console.log('DEBUG Clear cache button clicked (cache tab)'); const btn = document.getElementById('btn-clear-cache-tab') as HTMLButtonElement | null; if (btn) { btn.style.background = '#d97706'; @@ -947,7 +947,7 @@ function setupStorageLinkHandlers(): void { const target = event.target as HTMLElement; if (!target) { return; } if (target.id === 'btn-clear-cache' || target.id === 'btn-clear-cache-tab') { - console.log('[DEBUG] Clear cache button clicked via delegated handler', target.id); + console.log('DEBUG Clear cache button clicked via delegated handler', target.id); target.style.background = '#d97706'; target.innerHTML = 'Clearing...'; if (target instanceof HTMLButtonElement) { From 951f7eb0764e68c4ec72d53394f67eab73ffa9c8 Mon Sep 17 00:00:00 2001 From: Rob Bos Date: Sun, 1 Feb 2026 00:57:31 +0100 Subject: [PATCH 2/2] Show last month in details and last 30 days in chart --- src/extension.ts | 70 +++++++++++++++++++-------------------- src/webview/chart/main.ts | 4 +-- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 078af8e..393a249 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -813,7 +813,8 @@ class CopilotTokenTracker implements vscode.Disposable { private async calculateDailyStats(): Promise { const now = new Date(); - const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); + // Use last 30 days instead of current month for better chart visibility + const thirtyDaysAgo = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 30); // Map to store daily stats by date string (YYYY-MM-DD) const dailyStatsMap = new Map(); @@ -826,8 +827,8 @@ class CopilotTokenTracker implements vscode.Disposable { try { const fileStats = fs.statSync(sessionFile); - // Only process files modified in the current month - if (fileStats.mtime >= monthStart) { + // Only process files modified in the last 30 days + if (fileStats.mtime >= thirtyDaysAgo) { const tokens = await this.estimateTokensFromSessionCached(sessionFile, fileStats.mtime.getTime()); const interactions = await this.countInteractionsInSessionCached(sessionFile, fileStats.mtime.getTime()); const modelUsage = await this.getModelUsageFromSessionCached(sessionFile, fileStats.mtime.getTime()); @@ -880,42 +881,39 @@ class CopilotTokenTracker implements vscode.Disposable { // Convert map to array and sort by date let dailyStatsArray = Array.from(dailyStatsMap.values()).sort((a, b) => a.date.localeCompare(b.date)); - // Fill in missing dates between the first date and today - if (dailyStatsArray.length > 0) { - const firstDate = new Date(dailyStatsArray[0].date); - const today = new Date(); - - // Create a set of existing dates for quick lookup - const existingDates = new Set(dailyStatsArray.map(s => s.date)); - - // Generate all dates from first date to today - const allDates: string[] = []; - const currentDate = new Date(firstDate); - - while (currentDate <= today) { - const dateKey = this.formatDateKey(currentDate); - allDates.push(dateKey); - currentDate.setDate(currentDate.getDate() + 1); - } - - // Add missing dates with zero values - for (const dateKey of allDates) { - if (!existingDates.has(dateKey)) { - dailyStatsMap.set(dateKey, { - date: dateKey, - tokens: 0, - sessions: 0, - interactions: 0, - modelUsage: {}, - editorUsage: {} - }); - } + // Always fill in all 30 days to show complete chart + const today = new Date(); + + // Create a set of existing dates for quick lookup + const existingDates = new Set(dailyStatsArray.map(s => s.date)); + + // Generate all dates from 30 days ago to today + const allDates: string[] = []; + const currentDate = new Date(thirtyDaysAgo); + + while (currentDate <= today) { + const dateKey = this.formatDateKey(currentDate); + allDates.push(dateKey); + currentDate.setDate(currentDate.getDate() + 1); + } + + // Add missing dates with zero values + for (const dateKey of allDates) { + if (!existingDates.has(dateKey)) { + dailyStatsMap.set(dateKey, { + date: dateKey, + tokens: 0, + sessions: 0, + interactions: 0, + modelUsage: {}, + editorUsage: {} + }); } - - // Re-convert map to array and sort by date - dailyStatsArray = Array.from(dailyStatsMap.values()).sort((a, b) => a.date.localeCompare(b.date)); } + // Re-convert map to array and sort by date + dailyStatsArray = Array.from(dailyStatsMap.values()).sort((a, b) => a.date.localeCompare(b.date)); + return dailyStatsArray; } diff --git a/src/webview/chart/main.ts b/src/webview/chart/main.ts index 516f28d..ba78e9f 100644 --- a/src/webview/chart/main.ts +++ b/src/webview/chart/main.ts @@ -92,7 +92,7 @@ function renderLayout(data: InitialChartData): void { const header = el('div', 'header'); const headerLeft = el('div', 'header-left'); const icon = el('span', 'header-icon', '📈'); - const title = el('span', 'header-title', 'Token Usage Over Time'); + const title = el('span', 'header-title', 'Token Usage - Last 30 Days'); headerLeft.append(icon, title); const buttons = el('div', 'button-row'); buttons.append( @@ -140,7 +140,7 @@ function renderLayout(data: InitialChartData): void { chartShell.append(toggles, canvasWrap); chartSection.append(chartShell); - const footer = el('div', 'footer', `Day-by-day token usage for the current month\nLast updated: ${new Date(data.lastUpdated).toLocaleString()}\nUpdates automatically every 5 minutes.`); + const footer = el('div', 'footer', `Day-by-day token usage for the last 30 days\nLast updated: ${new Date(data.lastUpdated).toLocaleString()}\nUpdates automatically every 5 minutes.`); container.append(header, summarySection, chartSection, footer); root.append(style, container);