Scope contextUsage to active checkpoint
Three follow-ups on #5736 so the relaxed external-provider render gate does not show stale token / cache stats from a different model: 1) setCheckpoint now clears contextUsage on a real checkpoint change. setActiveThreadId and clearCheckpoint already did this; the most-traveled transition path (the user switching models from the picker) leaked the prior turn's counts because they were never cleared. 2) The external-selection branch in chat-page.tsx now also clears contextUsage at the same time it nulls ggufContextLength / activeNativePathToken. Without this an in-session switch from a local model to an external provider would visibly carry the previous local turn's counters into the new provider's bar. 3) exitCompare's rehydration is now scoped: restore the saved usage only when the message's modelId matches the active checkpoint AND, for local turns where a context window is known, when the saved total fits inside that window. Without this the bar could render a stale local-model usage on top of an external provider, or an oversized usage object that exceeds the now- active window. Typecheck clean.
This commit is contained in:
parent
b2644661ca
commit
042e0ac43c
2 changed files with 49 additions and 2 deletions
|
|
@ -1037,6 +1037,11 @@ export function ChatPage(): ReactElement {
|
|||
ggufMaxContextLength: null,
|
||||
ggufNativeContextLength: null,
|
||||
activeNativePathToken: null,
|
||||
// External selection arrives mid-session: also clear any
|
||||
// pre-existing per-turn usage from the previous model so the
|
||||
// relaxed external-provider render gate does not show stale
|
||||
// counts until the next completion overwrites them.
|
||||
contextUsage: null,
|
||||
supportsReasoning: reasoningCaps.supportsReasoning,
|
||||
reasoningAlwaysOn: reasoningCaps.reasoningAlwaysOn,
|
||||
reasoningStyle: reasoningCaps.reasoningStyle,
|
||||
|
|
@ -1161,7 +1166,11 @@ export function ChatPage(): ReactElement {
|
|||
if (!saved) return;
|
||||
viewBeforeCompareRef.current = null;
|
||||
navigate({ to: "/chat", search: saved });
|
||||
// Restore context usage from the active thread's last assistant message.
|
||||
// Restore context usage from the active thread's last assistant
|
||||
// message, but only if it was produced by the SAME checkpoint the
|
||||
// user is now sitting on. Without this guard the relaxed render
|
||||
// gate would show stale token / cache stats from a different
|
||||
// provider or a local turn that exceeds the active GGUF window.
|
||||
const threadId =
|
||||
saved.thread ?? useChatRuntimeStore.getState().activeThreadId;
|
||||
if (threadId) {
|
||||
|
|
@ -1175,7 +1184,35 @@ export function ChatPage(): ReactElement {
|
|||
const usage = metadata?.contextUsage as ReturnType<
|
||||
typeof useChatRuntimeStore.getState
|
||||
>["contextUsage"];
|
||||
if (usage) useChatRuntimeStore.getState().setContextUsage(usage);
|
||||
if (!usage) return;
|
||||
const store = useChatRuntimeStore.getState();
|
||||
const activeCheckpoint = store.params.checkpoint;
|
||||
const usageModelId =
|
||||
(usage as { modelId?: unknown }).modelId;
|
||||
// Scope by modelId when the saved usage carries one (the
|
||||
// chat-adapter stamps it on every external + local turn).
|
||||
if (
|
||||
typeof usageModelId === "string" &&
|
||||
usageModelId &&
|
||||
activeCheckpoint &&
|
||||
usageModelId !== activeCheckpoint
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// For local llama-server turns, also require that the
|
||||
// restored prompt count fits inside the active context
|
||||
// window. Skip the check when the window is unknown (e.g.
|
||||
// external provider, no ggufContextLength) so the existing
|
||||
// external-provider rendering path stays intact.
|
||||
const limit = store.ggufContextLength;
|
||||
if (
|
||||
typeof limit === "number" &&
|
||||
limit > 0 &&
|
||||
(usage.totalTokens ?? 0) > limit
|
||||
) {
|
||||
return;
|
||||
}
|
||||
store.setContextUsage(usage);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!isExpectedBackgroundChatStorageError(error)) {
|
||||
|
|
|
|||
|
|
@ -706,12 +706,22 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set, get) => ({
|
|||
// mount, and a stale persisted local id would race against the
|
||||
// freshly-loaded model. See LAST_EXTERNAL_CHECKPOINT_KEY notes.
|
||||
saveLastExternalCheckpoint(isExternalModelId(modelId) ? modelId : null);
|
||||
// Clear any stale per-turn usage when the active model changes.
|
||||
// The context bar render gate was relaxed for external providers
|
||||
// (no longer requires ggufContextLength), so leaving a previous
|
||||
// turn's counters around would visibly show old token / cache
|
||||
// stats from a different model until the next completion
|
||||
// overwrites them. setActiveThreadId / clearCheckpoint already
|
||||
// do this; keep the invariant on the most-traveled transition
|
||||
// path too.
|
||||
const checkpointChanged = state.params.checkpoint !== modelId;
|
||||
return {
|
||||
params: {
|
||||
...state.params,
|
||||
checkpoint: modelId,
|
||||
},
|
||||
activeGgufVariant: ggufVariant ?? null,
|
||||
...(checkpointChanged ? { contextUsage: null } : {}),
|
||||
};
|
||||
}),
|
||||
setActiveThreadId: (activeThreadId) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue