/** * View layer over contextStatsStore for the context usage gauge: adds * color levels, transient detail formatting, on-demand /props fetching * and model loading on top of the store's token stats. */ import { useProcessingState } from './use-processing-state.svelte'; import { colorLevelFromPercent } from '$lib/components/app/chat/ChatForm/ChatFormContextGauge/context-gauge'; import { STATS_UNITS } from '$lib/constants'; import { ColorLevel } from '$lib/enums'; import { contextStatsStore, modelsStore } from '$lib/stores'; export interface UseContextGaugeReturn { readonly activeModelId: string | null; readonly isActiveModelLoaded: boolean; readonly isActiveModelLoading: boolean; readonly contextTotal: number | null; readonly contextUsed: number; readonly contextAvailable: number | null; readonly currentRead: number; readonly currentFresh: number; readonly currentCache: number; readonly currentOutput: number; readonly kvTotal: number; readonly cumulativeRead: number; readonly cumulativeOutput: number; readonly cumulativeCacheTotal: number; readonly averageTokensPerSecond: number | null; readonly contextPercent: number | null; readonly colorLevel: ColorLevel; readonly transientDetails: string[]; readonly hasAnyUsage: boolean; loadModel(): Promise; startMonitoring(): void; } const TRANSIENT_DETAILS_EXCLUDED_PREFIXES = ['Context:', 'Output:']; function filterTransientDetails(raw: string[]): string[] { return raw.filter((detail) => { if (TRANSIENT_DETAILS_EXCLUDED_PREFIXES.some((prefix) => detail.startsWith(prefix))) { return false; } return !detail.includes(STATS_UNITS.TOKENS_PER_SECOND); }); } export function useContextGauge(): UseContextGaugeReturn { const processingState = useProcessingState(); // Pull /props on demand so n_ctx surfaces before the first chat request. $effect(() => { const modelId = contextStatsStore.activeModelId; if (modelId && contextStatsStore.isActiveModelLoaded) { const cached = modelsStore.props.getModelProps(modelId); if (!cached) { void modelsStore.props.fetchModelProps(modelId); } } }); const colorLevel = $derived(colorLevelFromPercent(contextStatsStore.contextPercent)); // Drop lines the surrounding Context / Output / speed rows already render. const transientDetails = $derived(filterTransientDetails(processingState.getTechnicalDetails())); const hasAnyUsage = $derived( contextStatsStore.cumulativeRead > 0 || contextStatsStore.cumulativeOutput > 0 || contextStatsStore.currentRead > 0 || contextStatsStore.currentOutput > 0 || contextStatsStore.averageTokensPerSecond !== null || transientDetails.length > 0 ); async function loadModel() { const modelId = contextStatsStore.activeModelId; if (!modelId || contextStatsStore.isActiveModelLoading) return; try { await modelsStore.status.load(modelId); } catch { // toast already surfaced by modelsStore.status.load } } return { get activeModelId() { return contextStatsStore.activeModelId; }, get averageTokensPerSecond() { return contextStatsStore.averageTokensPerSecond; }, get colorLevel() { return colorLevel; }, get contextAvailable() { return contextStatsStore.contextAvailable; }, get contextPercent() { return contextStatsStore.contextPercent; }, get contextTotal() { return contextStatsStore.contextTotal; }, get contextUsed() { return contextStatsStore.contextUsed; }, get cumulativeCacheTotal() { return contextStatsStore.cumulativeCacheTotal; }, get cumulativeOutput() { return contextStatsStore.cumulativeOutput; }, get cumulativeRead() { return contextStatsStore.cumulativeRead; }, get currentCache() { return contextStatsStore.currentCache; }, get currentFresh() { return contextStatsStore.currentFresh; }, get currentOutput() { return contextStatsStore.currentOutput; }, get currentRead() { return contextStatsStore.currentRead; }, get hasAnyUsage() { return hasAnyUsage; }, get isActiveModelLoaded() { return contextStatsStore.isActiveModelLoaded; }, get isActiveModelLoading() { return contextStatsStore.isActiveModelLoading; }, get kvTotal() { return contextStatsStore.kvTotal; }, loadModel, startMonitoring: () => processingState.startMonitoring(), get transientDetails() { return transientDetails; } }; }