|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import React, { |
|
|
createContext, |
|
|
useContext, |
|
|
useState, |
|
|
useMemo, |
|
|
useCallback, |
|
|
} from 'react'; |
|
|
|
|
|
import { type GenerateContentResponseUsageMetadata } from '@google/genai'; |
|
|
|
|
|
|
|
|
|
|
|
export interface CumulativeStats { |
|
|
turnCount: number; |
|
|
promptTokenCount: number; |
|
|
candidatesTokenCount: number; |
|
|
totalTokenCount: number; |
|
|
cachedContentTokenCount: number; |
|
|
toolUsePromptTokenCount: number; |
|
|
thoughtsTokenCount: number; |
|
|
apiTimeMs: number; |
|
|
} |
|
|
|
|
|
interface SessionStatsState { |
|
|
sessionStartTime: Date; |
|
|
cumulative: CumulativeStats; |
|
|
currentTurn: CumulativeStats; |
|
|
currentResponse: CumulativeStats; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
interface SessionStatsContextValue { |
|
|
stats: SessionStatsState; |
|
|
startNewTurn: () => void; |
|
|
addUsage: ( |
|
|
metadata: GenerateContentResponseUsageMetadata & { apiTimeMs?: number }, |
|
|
) => void; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const SessionStatsContext = createContext<SessionStatsContextValue | undefined>( |
|
|
undefined, |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const addTokens = ( |
|
|
target: CumulativeStats, |
|
|
source: GenerateContentResponseUsageMetadata & { apiTimeMs?: number }, |
|
|
) => { |
|
|
target.candidatesTokenCount += source.candidatesTokenCount ?? 0; |
|
|
target.thoughtsTokenCount += source.thoughtsTokenCount ?? 0; |
|
|
target.totalTokenCount += source.totalTokenCount ?? 0; |
|
|
target.apiTimeMs += source.apiTimeMs ?? 0; |
|
|
target.promptTokenCount += source.promptTokenCount ?? 0; |
|
|
target.cachedContentTokenCount += source.cachedContentTokenCount ?? 0; |
|
|
target.toolUsePromptTokenCount += source.toolUsePromptTokenCount ?? 0; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
export const SessionStatsProvider: React.FC<{ children: React.ReactNode }> = ({ |
|
|
children, |
|
|
}) => { |
|
|
const [stats, setStats] = useState<SessionStatsState>({ |
|
|
sessionStartTime: new Date(), |
|
|
cumulative: { |
|
|
turnCount: 0, |
|
|
promptTokenCount: 0, |
|
|
candidatesTokenCount: 0, |
|
|
totalTokenCount: 0, |
|
|
cachedContentTokenCount: 0, |
|
|
toolUsePromptTokenCount: 0, |
|
|
thoughtsTokenCount: 0, |
|
|
apiTimeMs: 0, |
|
|
}, |
|
|
currentTurn: { |
|
|
turnCount: 0, |
|
|
promptTokenCount: 0, |
|
|
candidatesTokenCount: 0, |
|
|
totalTokenCount: 0, |
|
|
cachedContentTokenCount: 0, |
|
|
toolUsePromptTokenCount: 0, |
|
|
thoughtsTokenCount: 0, |
|
|
apiTimeMs: 0, |
|
|
}, |
|
|
currentResponse: { |
|
|
turnCount: 0, |
|
|
promptTokenCount: 0, |
|
|
candidatesTokenCount: 0, |
|
|
totalTokenCount: 0, |
|
|
cachedContentTokenCount: 0, |
|
|
toolUsePromptTokenCount: 0, |
|
|
thoughtsTokenCount: 0, |
|
|
apiTimeMs: 0, |
|
|
}, |
|
|
}); |
|
|
|
|
|
|
|
|
const aggregateTokens = useCallback( |
|
|
( |
|
|
metadata: GenerateContentResponseUsageMetadata & { apiTimeMs?: number }, |
|
|
) => { |
|
|
setStats((prevState) => { |
|
|
const newCumulative = { ...prevState.cumulative }; |
|
|
const newCurrentTurn = { ...prevState.currentTurn }; |
|
|
const newCurrentResponse = { |
|
|
turnCount: 0, |
|
|
promptTokenCount: 0, |
|
|
candidatesTokenCount: 0, |
|
|
totalTokenCount: 0, |
|
|
cachedContentTokenCount: 0, |
|
|
toolUsePromptTokenCount: 0, |
|
|
thoughtsTokenCount: 0, |
|
|
apiTimeMs: 0, |
|
|
}; |
|
|
|
|
|
|
|
|
addTokens(newCurrentTurn, metadata); |
|
|
addTokens(newCumulative, metadata); |
|
|
addTokens(newCurrentResponse, metadata); |
|
|
|
|
|
return { |
|
|
...prevState, |
|
|
cumulative: newCumulative, |
|
|
currentTurn: newCurrentTurn, |
|
|
currentResponse: newCurrentResponse, |
|
|
}; |
|
|
}); |
|
|
}, |
|
|
[], |
|
|
); |
|
|
|
|
|
const startNewTurn = useCallback(() => { |
|
|
setStats((prevState) => ({ |
|
|
...prevState, |
|
|
cumulative: { |
|
|
...prevState.cumulative, |
|
|
turnCount: prevState.cumulative.turnCount + 1, |
|
|
}, |
|
|
currentTurn: { |
|
|
turnCount: 0, |
|
|
promptTokenCount: 0, |
|
|
candidatesTokenCount: 0, |
|
|
totalTokenCount: 0, |
|
|
cachedContentTokenCount: 0, |
|
|
toolUsePromptTokenCount: 0, |
|
|
thoughtsTokenCount: 0, |
|
|
apiTimeMs: 0, |
|
|
}, |
|
|
currentResponse: { |
|
|
turnCount: 0, |
|
|
promptTokenCount: 0, |
|
|
candidatesTokenCount: 0, |
|
|
totalTokenCount: 0, |
|
|
cachedContentTokenCount: 0, |
|
|
toolUsePromptTokenCount: 0, |
|
|
thoughtsTokenCount: 0, |
|
|
apiTimeMs: 0, |
|
|
}, |
|
|
})); |
|
|
}, []); |
|
|
|
|
|
const value = useMemo( |
|
|
() => ({ |
|
|
stats, |
|
|
startNewTurn, |
|
|
addUsage: aggregateTokens, |
|
|
}), |
|
|
[stats, startNewTurn, aggregateTokens], |
|
|
); |
|
|
|
|
|
return ( |
|
|
<SessionStatsContext.Provider value={value}> |
|
|
{children} |
|
|
</SessionStatsContext.Provider> |
|
|
); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
export const useSessionStats = () => { |
|
|
const context = useContext(SessionStatsContext); |
|
|
if (context === undefined) { |
|
|
throw new Error( |
|
|
'useSessionStats must be used within a SessionStatsProvider', |
|
|
); |
|
|
} |
|
|
return context; |
|
|
}; |
|
|
|