mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
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
71 lines
1.7 KiB
TypeScript
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,
|
|
}
|
|
},
|
|
}
|