2025-06-08 18:01:02 -04:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2025 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
2025-06-09 20:25:37 -04:00
|
|
|
import React, {
|
|
|
|
|
createContext,
|
|
|
|
|
useContext,
|
|
|
|
|
useState,
|
|
|
|
|
useMemo,
|
|
|
|
|
useCallback,
|
|
|
|
|
} from 'react';
|
2025-06-08 18:01:02 -04:00
|
|
|
|
2025-06-09 20:25:37 -04:00
|
|
|
import { type GenerateContentResponseUsageMetadata } from '@google/genai';
|
|
|
|
|
|
|
|
|
|
// --- Interface Definitions ---
|
|
|
|
|
|
|
|
|
|
interface CumulativeStats {
|
|
|
|
|
turnCount: number;
|
|
|
|
|
promptTokenCount: number;
|
|
|
|
|
candidatesTokenCount: number;
|
|
|
|
|
totalTokenCount: number;
|
|
|
|
|
cachedContentTokenCount: number;
|
|
|
|
|
toolUsePromptTokenCount: number;
|
|
|
|
|
thoughtsTokenCount: number;
|
2025-06-08 18:01:02 -04:00
|
|
|
}
|
|
|
|
|
|
2025-06-09 20:25:37 -04:00
|
|
|
interface LastTurnStats {
|
|
|
|
|
metadata: GenerateContentResponseUsageMetadata;
|
|
|
|
|
// TODO(abhipatel12): Add apiTime, etc. here in a future step.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface SessionStatsState {
|
|
|
|
|
sessionStartTime: Date;
|
|
|
|
|
cumulative: CumulativeStats;
|
|
|
|
|
lastTurn: LastTurnStats | null;
|
|
|
|
|
isNewTurnForAggregation: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Defines the final "value" of our context, including the state
|
|
|
|
|
// and the functions to update it.
|
|
|
|
|
interface SessionStatsContextValue {
|
|
|
|
|
stats: SessionStatsState;
|
|
|
|
|
startNewTurn: () => void;
|
|
|
|
|
addUsage: (metadata: GenerateContentResponseUsageMetadata) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Context Definition ---
|
|
|
|
|
|
|
|
|
|
const SessionStatsContext = createContext<SessionStatsContextValue | undefined>(
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
2025-06-08 18:01:02 -04:00
|
|
|
|
2025-06-09 20:25:37 -04:00
|
|
|
// --- Provider Component ---
|
|
|
|
|
|
|
|
|
|
export const SessionStatsProvider: React.FC<{ children: React.ReactNode }> = ({
|
2025-06-08 18:01:02 -04:00
|
|
|
children,
|
|
|
|
|
}) => {
|
2025-06-09 20:25:37 -04:00
|
|
|
const [stats, setStats] = useState<SessionStatsState>({
|
|
|
|
|
sessionStartTime: new Date(),
|
|
|
|
|
cumulative: {
|
|
|
|
|
turnCount: 0,
|
|
|
|
|
promptTokenCount: 0,
|
|
|
|
|
candidatesTokenCount: 0,
|
|
|
|
|
totalTokenCount: 0,
|
|
|
|
|
cachedContentTokenCount: 0,
|
|
|
|
|
toolUsePromptTokenCount: 0,
|
|
|
|
|
thoughtsTokenCount: 0,
|
|
|
|
|
},
|
|
|
|
|
lastTurn: null,
|
|
|
|
|
isNewTurnForAggregation: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// A single, internal worker function to handle all metadata aggregation.
|
|
|
|
|
const aggregateTokens = useCallback(
|
|
|
|
|
(metadata: GenerateContentResponseUsageMetadata) => {
|
|
|
|
|
setStats((prevState) => {
|
|
|
|
|
const { isNewTurnForAggregation } = prevState;
|
|
|
|
|
const newCumulative = { ...prevState.cumulative };
|
|
|
|
|
|
|
|
|
|
newCumulative.candidatesTokenCount +=
|
|
|
|
|
metadata.candidatesTokenCount ?? 0;
|
|
|
|
|
newCumulative.thoughtsTokenCount += metadata.thoughtsTokenCount ?? 0;
|
|
|
|
|
newCumulative.totalTokenCount += metadata.totalTokenCount ?? 0;
|
|
|
|
|
|
|
|
|
|
if (isNewTurnForAggregation) {
|
|
|
|
|
newCumulative.promptTokenCount += metadata.promptTokenCount ?? 0;
|
|
|
|
|
newCumulative.cachedContentTokenCount +=
|
|
|
|
|
metadata.cachedContentTokenCount ?? 0;
|
|
|
|
|
newCumulative.toolUsePromptTokenCount +=
|
|
|
|
|
metadata.toolUsePromptTokenCount ?? 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...prevState,
|
|
|
|
|
cumulative: newCumulative,
|
|
|
|
|
lastTurn: { metadata },
|
|
|
|
|
isNewTurnForAggregation: false,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const startNewTurn = useCallback(() => {
|
|
|
|
|
setStats((prevState) => ({
|
|
|
|
|
...prevState,
|
|
|
|
|
cumulative: {
|
|
|
|
|
...prevState.cumulative,
|
|
|
|
|
turnCount: prevState.cumulative.turnCount + 1,
|
|
|
|
|
},
|
|
|
|
|
isNewTurnForAggregation: true,
|
|
|
|
|
}));
|
|
|
|
|
}, []);
|
2025-06-08 18:01:02 -04:00
|
|
|
|
|
|
|
|
const value = useMemo(
|
|
|
|
|
() => ({
|
2025-06-09 20:25:37 -04:00
|
|
|
stats,
|
|
|
|
|
startNewTurn,
|
|
|
|
|
addUsage: aggregateTokens,
|
2025-06-08 18:01:02 -04:00
|
|
|
}),
|
2025-06-09 20:25:37 -04:00
|
|
|
[stats, startNewTurn, aggregateTokens],
|
2025-06-08 18:01:02 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
2025-06-09 20:25:37 -04:00
|
|
|
<SessionStatsContext.Provider value={value}>
|
|
|
|
|
{children}
|
|
|
|
|
</SessionStatsContext.Provider>
|
2025-06-08 18:01:02 -04:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-09 20:25:37 -04:00
|
|
|
// --- Consumer Hook ---
|
|
|
|
|
|
|
|
|
|
export const useSessionStats = () => {
|
|
|
|
|
const context = useContext(SessionStatsContext);
|
|
|
|
|
if (context === undefined) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
'useSessionStats must be used within a SessionStatsProvider',
|
|
|
|
|
);
|
2025-06-08 18:01:02 -04:00
|
|
|
}
|
|
|
|
|
return context;
|
|
|
|
|
};
|