cc-haha/desktop/src/api/activityStats.ts
程序员阿江(Relakkes) ace7f7b657 feat: add desktop token usage activity view
Expose local Claude Code CLI transcript usage in Settings so users can inspect recent token consumption and daily activity without leaving the desktop app.

The page uses server-side transcript aggregation for session, message, tool, model-token, and subagent token data. Daily token buckets use assistant message timestamps, and daily session counts use active parent sessions for the same date bucket so resumed sessions and cross-midnight work do not produce token-only days. Cache accounting is bumped to v5 to force recomputation under the corrected daily semantics.

Constraint: Usage data must come from local Claude Code CLI transcripts rather than mock/demo data.
Constraint: Desktop navigation keeps Token usage directly above Diagnostics.
Rejected: Bucket all token usage by session start date | hides resumed-session and cross-midnight consumption from the actual day it was spent.
Confidence: high
Scope-risk: moderate
Directive: Keep daily token and daily session counts on the same date-bucketing semantics.
Tested: bun run check:desktop
Tested: bun run check:server
Tested: Browser verification for Token usage in English and Chinese locale date labels
Not-tested: Full bun run verify quality gate
2026-05-10 11:37:49 +08:00

71 lines
1.7 KiB
TypeScript

import { api } from './client'
export type ActivityStatsRange = '7d' | '30d' | 'all'
export type DailyActivity = {
date: string
messageCount: number
sessionCount: number
toolCallCount: number
}
export type DailyModelTokens = {
date: string
tokensByModel: Record<string, number>
}
export type StreakInfo = {
currentStreak: number
longestStreak: number
currentStreakStart: string | null
longestStreakStart: string | null
longestStreakEnd: string | null
}
export type SessionStats = {
sessionId: string
duration: number
messageCount: number
timestamp: string
}
export type ActivityStats = {
totalSessions: number
totalMessages: number
totalDays: number
activeDays: number
streaks: StreakInfo
dailyActivity: DailyActivity[]
dailyModelTokens: DailyModelTokens[]
longestSession: SessionStats | null
modelUsage: Record<string, unknown>
firstSessionDate: string | null
lastSessionDate: string | null
peakActivityDay: string | null
peakActivityHour: number | null
totalSpeculationTimeSavedMs: number
}
export type ActivityStatsApiResponse = {
stats: ActivityStats
range: ActivityStatsRange
generatedAt: string
}
export type ActivityStatsResponse = ActivityStats & {
range: ActivityStatsRange
generatedAt: string
}
export const activityStatsApi = {
async getStats(range: ActivityStatsRange = 'all'): Promise<ActivityStatsResponse> {
const suffix = range === 'all' ? '' : `/${range}`
const response = await api.get<ActivityStatsApiResponse>(`/api/activity-stats${suffix}`, { timeout: 120_000 })
return {
...response.stats,
range: response.range,
generatedAt: response.generatedAt,
}
},
}