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,
|
ggufMaxContextLength: null,
|
||||||
ggufNativeContextLength: null,
|
ggufNativeContextLength: null,
|
||||||
activeNativePathToken: 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,
|
supportsReasoning: reasoningCaps.supportsReasoning,
|
||||||
reasoningAlwaysOn: reasoningCaps.reasoningAlwaysOn,
|
reasoningAlwaysOn: reasoningCaps.reasoningAlwaysOn,
|
||||||
reasoningStyle: reasoningCaps.reasoningStyle,
|
reasoningStyle: reasoningCaps.reasoningStyle,
|
||||||
|
|
@ -1161,7 +1166,11 @@ export function ChatPage(): ReactElement {
|
||||||
if (!saved) return;
|
if (!saved) return;
|
||||||
viewBeforeCompareRef.current = null;
|
viewBeforeCompareRef.current = null;
|
||||||
navigate({ to: "/chat", search: saved });
|
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 =
|
const threadId =
|
||||||
saved.thread ?? useChatRuntimeStore.getState().activeThreadId;
|
saved.thread ?? useChatRuntimeStore.getState().activeThreadId;
|
||||||
if (threadId) {
|
if (threadId) {
|
||||||
|
|
@ -1175,7 +1184,35 @@ export function ChatPage(): ReactElement {
|
||||||
const usage = metadata?.contextUsage as ReturnType<
|
const usage = metadata?.contextUsage as ReturnType<
|
||||||
typeof useChatRuntimeStore.getState
|
typeof useChatRuntimeStore.getState
|
||||||
>["contextUsage"];
|
>["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) => {
|
.catch((error) => {
|
||||||
if (!isExpectedBackgroundChatStorageError(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
|
// mount, and a stale persisted local id would race against the
|
||||||
// freshly-loaded model. See LAST_EXTERNAL_CHECKPOINT_KEY notes.
|
// freshly-loaded model. See LAST_EXTERNAL_CHECKPOINT_KEY notes.
|
||||||
saveLastExternalCheckpoint(isExternalModelId(modelId) ? modelId : null);
|
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 {
|
return {
|
||||||
params: {
|
params: {
|
||||||
...state.params,
|
...state.params,
|
||||||
checkpoint: modelId,
|
checkpoint: modelId,
|
||||||
},
|
},
|
||||||
activeGgufVariant: ggufVariant ?? null,
|
activeGgufVariant: ggufVariant ?? null,
|
||||||
|
...(checkpointChanged ? { contextUsage: null } : {}),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
setActiveThreadId: (activeThreadId) =>
|
setActiveThreadId: (activeThreadId) =>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue