Studio: persist chat toggles and preserve custom sampling (#5587)
* fix: persist chat toggles and preserve custom sampling * fix: keep Qwen reload sampling aligned with Think state * fix: avoid persisting Qwen reasoning defaults * studio: keep Kimi search off when thinking defaults on * fix: avoid persisting Kimi-enforced chat toggles
This commit is contained in:
parent
63d69ee7e9
commit
6c52697ad0
5 changed files with 115 additions and 28 deletions
|
|
@ -67,7 +67,12 @@ import {
|
|||
RegisterCompareHandle,
|
||||
SharedComposer,
|
||||
} from "./shared-composer";
|
||||
import { useChatRuntimeStore } from "./stores/chat-runtime-store";
|
||||
import {
|
||||
CHAT_CODE_TOOLS_ENABLED_KEY,
|
||||
CHAT_TOOLS_ENABLED_KEY,
|
||||
loadOptionalBool,
|
||||
useChatRuntimeStore,
|
||||
} from "./stores/chat-runtime-store";
|
||||
import { useExternalProvidersStore } from "./stores/external-providers-store";
|
||||
import { buildChatTourSteps } from "./tour";
|
||||
import type { ChatView, MessageRecord } from "./types";
|
||||
|
|
@ -740,6 +745,13 @@ export function ChatPage(): ReactElement {
|
|||
supportsBuiltinWebSearch &&
|
||||
(provider?.providerType === "anthropic" ||
|
||||
provider?.providerType === "openai");
|
||||
const storedToolsEnabled = loadOptionalBool(CHAT_TOOLS_ENABLED_KEY);
|
||||
const storedCodeToolsEnabled = loadOptionalBool(CHAT_CODE_TOOLS_ENABLED_KEY);
|
||||
const nextToolsEnabled = supportsBuiltinWebSearch
|
||||
? isKimi
|
||||
? false
|
||||
: (storedToolsEnabled ?? searchOnByDefault)
|
||||
: false;
|
||||
useChatRuntimeStore.setState({
|
||||
supportsReasoning: reasoningCaps.supportsReasoning,
|
||||
reasoningAlwaysOn: reasoningCaps.reasoningAlwaysOn,
|
||||
|
|
@ -765,8 +777,10 @@ export function ChatPage(): ReactElement {
|
|||
supportsTools: false,
|
||||
supportsBuiltinWebSearch,
|
||||
supportsBuiltinCodeExecution,
|
||||
toolsEnabled: searchOnByDefault,
|
||||
codeToolsEnabled: false,
|
||||
toolsEnabled: nextToolsEnabled,
|
||||
codeToolsEnabled: supportsBuiltinCodeExecution
|
||||
? (storedCodeToolsEnabled ?? false)
|
||||
: false,
|
||||
});
|
||||
}, [externalProviders, inferenceParams.checkpoint]);
|
||||
const canCompare = useMemo(() => {
|
||||
|
|
@ -940,6 +954,15 @@ export function ChatPage(): ReactElement {
|
|||
supportsBuiltinWebSearch &&
|
||||
(selectedProvider?.providerType === "anthropic" ||
|
||||
selectedProvider?.providerType === "openai");
|
||||
const storedToolsEnabled = loadOptionalBool(CHAT_TOOLS_ENABLED_KEY);
|
||||
const storedCodeToolsEnabled = loadOptionalBool(
|
||||
CHAT_CODE_TOOLS_ENABLED_KEY,
|
||||
);
|
||||
const nextToolsEnabled = supportsBuiltinWebSearch
|
||||
? isKimi
|
||||
? false
|
||||
: (storedToolsEnabled ?? searchOnByDefault)
|
||||
: false;
|
||||
useChatRuntimeStore.setState({
|
||||
activeGgufVariant: null,
|
||||
ggufContextLength: null,
|
||||
|
|
@ -969,8 +992,10 @@ export function ChatPage(): ReactElement {
|
|||
supportsTools: false,
|
||||
supportsBuiltinWebSearch,
|
||||
supportsBuiltinCodeExecution,
|
||||
toolsEnabled: searchOnByDefault,
|
||||
codeToolsEnabled: false,
|
||||
toolsEnabled: nextToolsEnabled,
|
||||
codeToolsEnabled: supportsBuiltinCodeExecution
|
||||
? (storedCodeToolsEnabled ?? false)
|
||||
: false,
|
||||
...(stillOnOpenRouterFree ? {} : { lastOpenRouterChosenModel: null }),
|
||||
});
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import {
|
|||
} from "../api/chat-api";
|
||||
import { formatEta, formatRate } from "../utils/format-transfer";
|
||||
import {
|
||||
CHAT_REASONING_ENABLED_KEY,
|
||||
loadOptionalBool,
|
||||
type ReasoningEffort,
|
||||
useChatRuntimeStore,
|
||||
} from "../stores/chat-runtime-store";
|
||||
|
|
@ -273,6 +275,10 @@ export function useChatModelRuntime() {
|
|||
}
|
||||
|
||||
// Restore reasoning/tools support flags and context length
|
||||
const hydratingExistingModel =
|
||||
selectedCheckpoint !== statusRes.active_model ||
|
||||
useChatRuntimeStore.getState().activeGgufVariant !==
|
||||
(statusRes.gguf_variant ?? null);
|
||||
const supportsReasoning = statusRes.supports_reasoning ?? false;
|
||||
const reasoningAlwaysOn = statusRes.reasoning_always_on ?? false;
|
||||
const reasoningStyle = statusRes.reasoning_style ?? "enable_thinking";
|
||||
|
|
@ -282,6 +288,9 @@ export function useChatModelRuntime() {
|
|||
: (["low", "medium", "high"] as const);
|
||||
const supportsPreserveThinking = statusRes.supports_preserve_thinking ?? false;
|
||||
const supportsTools = statusRes.supports_tools ?? false;
|
||||
const storedReasoningEnabled = loadOptionalBool(
|
||||
CHAT_REASONING_ENABLED_KEY,
|
||||
);
|
||||
const currentGgufContextLength = statusRes.is_gguf
|
||||
? (statusRes.context_length ?? null)
|
||||
: null;
|
||||
|
|
@ -363,7 +372,11 @@ export function useChatModelRuntime() {
|
|||
});
|
||||
|
||||
// Set reasoning default for Qwen3.5/3.6 small models
|
||||
if (supportsReasoning) {
|
||||
if (
|
||||
supportsReasoning &&
|
||||
hydratingExistingModel &&
|
||||
storedReasoningEnabled === null
|
||||
) {
|
||||
let reasoningDefault = true;
|
||||
const mid = statusRes.active_model.toLowerCase();
|
||||
if (mid.includes("qwen3.5") || mid.includes("qwen3.6")) {
|
||||
|
|
@ -372,7 +385,7 @@ export function useChatModelRuntime() {
|
|||
reasoningDefault = false;
|
||||
}
|
||||
}
|
||||
useChatRuntimeStore.getState().setReasoningEnabled(reasoningDefault);
|
||||
useChatRuntimeStore.setState({ reasoningEnabled: reasoningDefault });
|
||||
}
|
||||
} else if (!statusRes.active_model && !isExternalSelectionActive) {
|
||||
useChatRuntimeStore.setState({
|
||||
|
|
@ -466,6 +479,9 @@ export function useChatModelRuntime() {
|
|||
const previousCheckpoint = currentCheckpoint;
|
||||
const previousVariant =
|
||||
useChatRuntimeStore.getState().activeGgufVariant ?? null;
|
||||
const reloadingSameModel =
|
||||
previousCheckpoint === modelId &&
|
||||
(ggufVariant ?? null) === (previousVariant ?? null);
|
||||
const previousModel = previousCheckpoint
|
||||
? models.find((entry) => entry.id === previousCheckpoint)
|
||||
: undefined;
|
||||
|
|
@ -651,6 +667,8 @@ export function useChatModelRuntime() {
|
|||
const keepCustomCtx = null;
|
||||
const reasoningAlwaysOn = loadResponse.reasoning_always_on ?? false;
|
||||
const reasoningStyle = loadResponse.reasoning_style ?? "enable_thinking";
|
||||
const supportsReasoning = loadResponse.supports_reasoning ?? false;
|
||||
const supportsTools = loadResponse.supports_tools ?? false;
|
||||
const reasoningEffortLevels =
|
||||
reasoningStyle === "reasoning_effort"
|
||||
? (["low", "medium", "high"] as const)
|
||||
|
|
@ -660,23 +678,34 @@ export function useChatModelRuntime() {
|
|||
existingReasoningEffort,
|
||||
);
|
||||
const ggufMaxContextLength = reportedMaxCtx;
|
||||
const nextReasoningEnabled = reasoningAlwaysOn
|
||||
? true
|
||||
: reloadingSameModel && supportsReasoning
|
||||
? stateBeforeUnload.reasoningEnabled
|
||||
: reasoningDefault;
|
||||
useChatRuntimeStore.setState({
|
||||
ggufContextLength: nativeCtx,
|
||||
ggufMaxContextLength,
|
||||
ggufNativeContextLength: reportedNativeCtx,
|
||||
modelRequiresTrustRemoteCode:
|
||||
loadResponse.requires_trust_remote_code ?? false,
|
||||
supportsReasoning: loadResponse.supports_reasoning ?? false,
|
||||
supportsReasoning,
|
||||
reasoningAlwaysOn,
|
||||
reasoningEnabled: reasoningAlwaysOn ? true : reasoningDefault,
|
||||
reasoningEnabled: nextReasoningEnabled,
|
||||
reasoningStyle,
|
||||
supportsReasoningOff: reasoningStyle !== "reasoning_effort",
|
||||
reasoningEffortLevels,
|
||||
reasoningEffort: clampedReasoningEffort,
|
||||
supportsPreserveThinking: loadResponse.supports_preserve_thinking ?? false,
|
||||
supportsTools: loadResponse.supports_tools ?? false,
|
||||
toolsEnabled: loadResponse.supports_tools ?? false,
|
||||
codeToolsEnabled: loadResponse.supports_tools ?? false,
|
||||
supportsTools,
|
||||
toolsEnabled:
|
||||
reloadingSameModel && supportsTools
|
||||
? stateBeforeUnload.toolsEnabled
|
||||
: supportsTools,
|
||||
codeToolsEnabled:
|
||||
reloadingSameModel && supportsTools
|
||||
? stateBeforeUnload.codeToolsEnabled
|
||||
: supportsTools,
|
||||
kvCacheDtype: loadedKv,
|
||||
loadedKvCacheDtype: loadedKv,
|
||||
speculativeType: loadedSpec,
|
||||
|
|
@ -700,7 +729,7 @@ export function useChatModelRuntime() {
|
|||
const mid = modelId.toLowerCase();
|
||||
const needsPresencePenalty =
|
||||
mid.includes("qwen3.5") || mid.includes("qwen3.6");
|
||||
const p = reasoningDefault
|
||||
const p = nextReasoningEnabled
|
||||
? {
|
||||
temperature: 0.6,
|
||||
topP: 0.95,
|
||||
|
|
|
|||
|
|
@ -944,7 +944,7 @@ export function SharedComposer({
|
|||
// Mutual exclusion: turning thinking on for a
|
||||
// Kimi model forces the web_search builtin off.
|
||||
if (isKimiExternal && toolsEnabled) {
|
||||
setToolsEnabled(false);
|
||||
setToolsEnabled(false, { persist: false });
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
@ -973,7 +973,7 @@ export function SharedComposer({
|
|||
// requires thinking off, so turning thinking on flips
|
||||
// the Search pill off (and vice versa).
|
||||
if (isKimiExternal && next && toolsEnabled) {
|
||||
setToolsEnabled(false);
|
||||
setToolsEnabled(false, { persist: false });
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
|
|
@ -1041,7 +1041,7 @@ export function SharedComposer({
|
|||
// back on when Search goes off — mutual exclusion that
|
||||
// mirrors what the backend enforces.
|
||||
if (isKimiExternal) {
|
||||
setReasoningEnabled(!next);
|
||||
setReasoningEnabled(!next, { persist: false });
|
||||
applyQwenThinkingParams(!next);
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,11 @@ const HF_TOKEN_KEY = "unsloth_hf_token";
|
|||
const INFERENCE_PARAMS_KEY = "unsloth_chat_inference_params";
|
||||
const CHAT_ACTIVE_PRESET_KEY = "unsloth_chat_active_preset";
|
||||
const CHAT_ACTIVE_PRESET_SOURCE_KEY = "unsloth_chat_active_preset_source";
|
||||
export const CHAT_REASONING_ENABLED_KEY = "unsloth_chat_reasoning_enabled";
|
||||
const REASONING_EFFORT_KEY = "unsloth_reasoning_effort";
|
||||
const PRESERVE_THINKING_KEY = "unsloth_preserve_thinking";
|
||||
export const CHAT_TOOLS_ENABLED_KEY = "unsloth_chat_tools_enabled";
|
||||
export const CHAT_CODE_TOOLS_ENABLED_KEY = "unsloth_chat_code_tools_enabled";
|
||||
|
||||
export type ReasoningStyle = "enable_thinking" | "reasoning_effort";
|
||||
export type ReasoningEffort =
|
||||
|
|
@ -62,13 +65,18 @@ function canUseStorage(): boolean {
|
|||
}
|
||||
|
||||
function loadBool(key: string, fallback: boolean): boolean {
|
||||
if (!canUseStorage()) return fallback;
|
||||
const raw = loadOptionalBool(key);
|
||||
return raw ?? fallback;
|
||||
}
|
||||
|
||||
export function loadOptionalBool(key: string): boolean | null {
|
||||
if (!canUseStorage()) return null;
|
||||
try {
|
||||
const raw = localStorage.getItem(key);
|
||||
if (raw === null) return fallback;
|
||||
if (raw === null) return null;
|
||||
return raw === "true";
|
||||
} catch {
|
||||
return fallback;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -294,12 +302,18 @@ type ChatRuntimeStore = {
|
|||
setActiveThreadId: (threadId: string | null) => void;
|
||||
setSettingsPanelOpen: (open: boolean) => void;
|
||||
clearCheckpoint: () => void;
|
||||
setReasoningEnabled: (enabled: boolean) => void;
|
||||
setReasoningEnabled: (
|
||||
enabled: boolean,
|
||||
options?: { persist?: boolean },
|
||||
) => void;
|
||||
setLastOpenRouterChosenModel: (chosen: string | null) => void;
|
||||
setReasoningStyle: (style: ReasoningStyle) => void;
|
||||
setReasoningEffort: (effort: ReasoningEffort) => void;
|
||||
setPreserveThinking: (value: boolean) => void;
|
||||
setToolsEnabled: (enabled: boolean) => void;
|
||||
setToolsEnabled: (
|
||||
enabled: boolean,
|
||||
options?: { persist?: boolean },
|
||||
) => void;
|
||||
setCodeToolsEnabled: (enabled: boolean) => void;
|
||||
setToolStatus: (status: string | null) => void;
|
||||
setGeneratingStatus: (status: string | null) => void;
|
||||
|
|
@ -333,7 +347,7 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set) => ({
|
|||
modelRequiresTrustRemoteCode: false,
|
||||
supportsReasoning: false,
|
||||
reasoningAlwaysOn: false,
|
||||
reasoningEnabled: true,
|
||||
reasoningEnabled: loadBool(CHAT_REASONING_ENABLED_KEY, true),
|
||||
reasoningStyle: "enable_thinking",
|
||||
reasoningEffort: loadReasoningEffort("medium"),
|
||||
supportsReasoningOff: false,
|
||||
|
|
@ -344,8 +358,8 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set) => ({
|
|||
supportsTools: false,
|
||||
supportsBuiltinWebSearch: false,
|
||||
supportsBuiltinCodeExecution: false,
|
||||
toolsEnabled: false,
|
||||
codeToolsEnabled: false,
|
||||
toolsEnabled: loadBool(CHAT_TOOLS_ENABLED_KEY, false),
|
||||
codeToolsEnabled: loadBool(CHAT_CODE_TOOLS_ENABLED_KEY, false),
|
||||
toolStatus: null,
|
||||
generatingStatus: null,
|
||||
autoHealToolCalls: loadBool(AUTO_HEAL_TOOL_CALLS_KEY, true),
|
||||
|
|
@ -473,7 +487,13 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set) => ({
|
|||
chatTemplateOverride: null,
|
||||
loadedChatTemplateOverride: null,
|
||||
})),
|
||||
setReasoningEnabled: (reasoningEnabled) => set({ reasoningEnabled }),
|
||||
setReasoningEnabled: (reasoningEnabled, options) =>
|
||||
set(() => {
|
||||
if (options?.persist !== false) {
|
||||
saveBool(CHAT_REASONING_ENABLED_KEY, reasoningEnabled);
|
||||
}
|
||||
return { reasoningEnabled };
|
||||
}),
|
||||
setLastOpenRouterChosenModel: (lastOpenRouterChosenModel) =>
|
||||
set({ lastOpenRouterChosenModel }),
|
||||
setReasoningStyle: (reasoningStyle) => set({ reasoningStyle }),
|
||||
|
|
@ -493,8 +513,18 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set) => ({
|
|||
saveBool(PRESERVE_THINKING_KEY, preserveThinking);
|
||||
return { preserveThinking };
|
||||
}),
|
||||
setToolsEnabled: (toolsEnabled) => set({ toolsEnabled }),
|
||||
setCodeToolsEnabled: (codeToolsEnabled) => set({ codeToolsEnabled }),
|
||||
setToolsEnabled: (toolsEnabled, options) =>
|
||||
set(() => {
|
||||
if (options?.persist !== false) {
|
||||
saveBool(CHAT_TOOLS_ENABLED_KEY, toolsEnabled);
|
||||
}
|
||||
return { toolsEnabled };
|
||||
}),
|
||||
setCodeToolsEnabled: (codeToolsEnabled) =>
|
||||
set(() => {
|
||||
saveBool(CHAT_CODE_TOOLS_ENABLED_KEY, codeToolsEnabled);
|
||||
return { codeToolsEnabled };
|
||||
}),
|
||||
setToolStatus: (toolStatus) => set({ toolStatus }),
|
||||
setGeneratingStatus: (generatingStatus) => set({ generatingStatus }),
|
||||
setAutoHealToolCalls: (autoHealToolCalls) =>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ import { useChatRuntimeStore } from "../stores/chat-runtime-store";
|
|||
export function applyQwenThinkingParams(thinkingOn: boolean): void {
|
||||
const store = useChatRuntimeStore.getState();
|
||||
const checkpoint = store.params.checkpoint?.toLowerCase() ?? "";
|
||||
if (!checkpoint.includes("qwen3")) {
|
||||
if (
|
||||
!checkpoint.includes("qwen3") ||
|
||||
store.activePresetSource !== "builtin-default"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const needsPresencePenalty =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue