fix: always show chat tool icons (#4525)
* fix: always show chat tool icons, gray out when model doesn't support them Tool icons (Think, Search, Code) were hidden unless a model was loaded and supported those features. Now they're always visible so users can see and pre-select them. If a loaded model doesn't support a feature, the button gets grayed out and disabled instead of being removed. * refactor: centralize Qwen thinking params in store * fix: disable tool buttons when no model is loaded Change disabled condition from `modelLoaded && !supportsX` to `!modelLoaded || !supportsX` so buttons are grayed out both when no model is loaded and when the loaded model lacks the capability. * Fix Qwen3 param clobbering and restore SuggestionItem capability guards - Revert setReasoningEnabled() in the store to a pure boolean setter. Moving the Qwen3 param logic into it caused reconnect/load/refresh paths (which also call setReasoningEnabled) to silently overwrite user-customized or server-provided temperature/topP/topK/minP. - Restore applyQwenThinkingParams() as a standalone function called only from explicit user toggle click handlers in thread.tsx and shared-composer.tsx, matching the pre-PR behavior. - Re-add supportsReasoning/supportsTools guards in the SuggestionItem click handler so that clicking a suggestion card only activates tool toggles the loaded model actually supports. --------- Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
parent
77b21333fb
commit
3dc212e218
2 changed files with 98 additions and 84 deletions
|
|
@ -132,13 +132,7 @@ const SuggestionItem: FC = () => {
|
|||
const prompt = useAuiState(({ suggestion }) => suggestion.prompt);
|
||||
const isDisabled = useAuiState(({ thread }) => thread.isDisabled);
|
||||
const isRunning = useAuiState(({ thread }) => thread.isRunning);
|
||||
const supportsTools = useChatRuntimeStore((s) => s.supportsTools);
|
||||
const supportsReasoning = useChatRuntimeStore((s) => s.supportsReasoning);
|
||||
const allTools = SUGGESTION_TOOLS[prompt] ?? [];
|
||||
const tools = allTools.filter((tool) => {
|
||||
if (tool === "thinking") return supportsReasoning;
|
||||
return supportsTools;
|
||||
});
|
||||
const tools = SUGGESTION_TOOLS[prompt] ?? [];
|
||||
|
||||
return (
|
||||
<button
|
||||
|
|
@ -335,9 +329,6 @@ function applyQwenThinkingParams(thinkingOn: boolean): void {
|
|||
const store = useChatRuntimeStore.getState();
|
||||
const checkpoint = store.params.checkpoint?.toLowerCase() ?? "";
|
||||
if (!checkpoint.includes("qwen3")) return;
|
||||
// Qwen3 & Qwen3.5 share the same recommended settings:
|
||||
// Thinking ON (general): temp=1.0, top_p=0.95, top_k=20
|
||||
// Thinking OFF (general): temp=0.7, top_p=0.8, top_k=20
|
||||
const params = thinkingOn
|
||||
? { temperature: 0.6, topP: 0.95, topK: 20, minP: 0.0 }
|
||||
: { temperature: 0.7, topP: 0.8, topK: 20, minP: 0.0 };
|
||||
|
|
@ -345,15 +336,18 @@ function applyQwenThinkingParams(thinkingOn: boolean): void {
|
|||
}
|
||||
|
||||
const ReasoningToggle: FC = () => {
|
||||
const modelLoaded = useChatRuntimeStore(
|
||||
(s) => !!s.params.checkpoint && !s.modelLoading,
|
||||
);
|
||||
const supportsReasoning = useChatRuntimeStore((s) => s.supportsReasoning);
|
||||
const reasoningEnabled = useChatRuntimeStore((s) => s.reasoningEnabled);
|
||||
const setReasoningEnabled = useChatRuntimeStore((s) => s.setReasoningEnabled);
|
||||
|
||||
if (!supportsReasoning) return null;
|
||||
const disabled = !modelLoaded || !supportsReasoning;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
const next = !reasoningEnabled;
|
||||
setReasoningEnabled(next);
|
||||
|
|
@ -361,13 +355,15 @@ const ReasoningToggle: FC = () => {
|
|||
}}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
reasoningEnabled
|
||||
? "bg-primary/10 text-primary hover:bg-primary/20"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted-foreground/15",
|
||||
disabled
|
||||
? "cursor-not-allowed opacity-40"
|
||||
: reasoningEnabled
|
||||
? "bg-primary/10 text-primary hover:bg-primary/20"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted-foreground/15",
|
||||
)}
|
||||
aria-label={reasoningEnabled ? "Disable thinking" : "Enable thinking"}
|
||||
>
|
||||
{reasoningEnabled ? (
|
||||
{reasoningEnabled && !disabled ? (
|
||||
<LightbulbIcon className="size-3.5" />
|
||||
) : (
|
||||
<LightbulbOffIcon className="size-3.5" />
|
||||
|
|
@ -378,21 +374,26 @@ const ReasoningToggle: FC = () => {
|
|||
};
|
||||
|
||||
const WebSearchToggle: FC = () => {
|
||||
const modelLoaded = useChatRuntimeStore(
|
||||
(s) => !!s.params.checkpoint && !s.modelLoading,
|
||||
);
|
||||
const supportsTools = useChatRuntimeStore((s) => s.supportsTools);
|
||||
const toolsEnabled = useChatRuntimeStore((s) => s.toolsEnabled);
|
||||
const setToolsEnabled = useChatRuntimeStore((s) => s.setToolsEnabled);
|
||||
|
||||
if (!supportsTools) return null;
|
||||
const disabled = !modelLoaded || !supportsTools;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => setToolsEnabled(!toolsEnabled)}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
toolsEnabled
|
||||
? "bg-primary/10 text-primary hover:bg-primary/20"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted-foreground/15",
|
||||
disabled
|
||||
? "cursor-not-allowed opacity-40"
|
||||
: toolsEnabled
|
||||
? "bg-primary/10 text-primary hover:bg-primary/20"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted-foreground/15",
|
||||
)}
|
||||
aria-label={toolsEnabled ? "Disable web search" : "Enable web search"}
|
||||
>
|
||||
|
|
@ -403,23 +404,28 @@ const WebSearchToggle: FC = () => {
|
|||
};
|
||||
|
||||
const CodeToolsToggle: FC = () => {
|
||||
const modelLoaded = useChatRuntimeStore(
|
||||
(s) => !!s.params.checkpoint && !s.modelLoading,
|
||||
);
|
||||
const supportsTools = useChatRuntimeStore((s) => s.supportsTools);
|
||||
const codeToolsEnabled = useChatRuntimeStore((s) => s.codeToolsEnabled);
|
||||
const setCodeToolsEnabled = useChatRuntimeStore(
|
||||
(s) => s.setCodeToolsEnabled,
|
||||
);
|
||||
|
||||
if (!supportsTools) return null;
|
||||
const disabled = !modelLoaded || !supportsTools;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => setCodeToolsEnabled(!codeToolsEnabled)}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
codeToolsEnabled
|
||||
? "bg-primary/10 text-primary hover:bg-primary/20"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted-foreground/15",
|
||||
disabled
|
||||
? "cursor-not-allowed opacity-40"
|
||||
: codeToolsEnabled
|
||||
? "bg-primary/10 text-primary hover:bg-primary/20"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted-foreground/15",
|
||||
)}
|
||||
aria-label={codeToolsEnabled ? "Disable code execution" : "Enable code execution"}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -237,6 +237,9 @@ export function SharedComposer({
|
|||
const checkpoint = s.params.checkpoint;
|
||||
return s.models.find((m) => m.id === checkpoint);
|
||||
});
|
||||
const modelLoaded = useChatRuntimeStore(
|
||||
(s) => !!s.params.checkpoint && !s.modelLoading,
|
||||
);
|
||||
const supportsReasoning = useChatRuntimeStore((s) => s.supportsReasoning);
|
||||
const reasoningEnabled = useChatRuntimeStore((s) => s.reasoningEnabled);
|
||||
const setReasoningEnabled = useChatRuntimeStore((s) => s.setReasoningEnabled);
|
||||
|
|
@ -245,6 +248,8 @@ export function SharedComposer({
|
|||
const setToolsEnabled = useChatRuntimeStore((s) => s.setToolsEnabled);
|
||||
const codeToolsEnabled = useChatRuntimeStore((s) => s.codeToolsEnabled);
|
||||
const setCodeToolsEnabled = useChatRuntimeStore((s) => s.setCodeToolsEnabled);
|
||||
const reasoningDisabled = !modelLoaded || !supportsReasoning;
|
||||
const toolsDisabled = !modelLoaded || !supportsTools;
|
||||
const setPendingAudioStore = useChatRuntimeStore((s) => s.setPendingAudio);
|
||||
const clearPendingAudioStore = useChatRuntimeStore((s) => s.clearPendingAudio);
|
||||
|
||||
|
|
@ -519,70 +524,73 @@ export function SharedComposer({
|
|||
</TooltipIconButton>
|
||||
</>
|
||||
)}
|
||||
{supportsReasoning && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const next = !reasoningEnabled;
|
||||
setReasoningEnabled(next);
|
||||
// Qwen3/3.5: adjust params for thinking on/off
|
||||
const store = useChatRuntimeStore.getState();
|
||||
const cp = store.params.checkpoint?.toLowerCase() ?? "";
|
||||
if (cp.includes("qwen3")) {
|
||||
const p = next
|
||||
? { temperature: 0.6, topP: 0.95, topK: 20, minP: 0.0 }
|
||||
: { temperature: 0.7, topP: 0.8, topK: 20, minP: 0.0 };
|
||||
store.setParams({ ...store.params, ...p });
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center gap-0.5 rounded-full px-2 py-0.5 text-xs font-medium transition-colors",
|
||||
reasoningEnabled
|
||||
<button
|
||||
type="button"
|
||||
disabled={reasoningDisabled}
|
||||
onClick={() => {
|
||||
const next = !reasoningEnabled;
|
||||
setReasoningEnabled(next);
|
||||
// Qwen3/3.5: adjust params for thinking on/off
|
||||
const store = useChatRuntimeStore.getState();
|
||||
const cp = store.params.checkpoint?.toLowerCase() ?? "";
|
||||
if (cp.includes("qwen3")) {
|
||||
const p = next
|
||||
? { temperature: 0.6, topP: 0.95, topK: 20, minP: 0.0 }
|
||||
: { temperature: 0.7, topP: 0.8, topK: 20, minP: 0.0 };
|
||||
store.setParams({ ...store.params, ...p });
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center gap-0.5 rounded-full px-2 py-0.5 text-xs font-medium transition-colors",
|
||||
reasoningDisabled
|
||||
? "cursor-not-allowed opacity-40"
|
||||
: reasoningEnabled
|
||||
? "bg-primary/10 text-primary hover:bg-primary/20"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted-foreground/15",
|
||||
)}
|
||||
aria-label={reasoningEnabled ? "Disable thinking" : "Enable thinking"}
|
||||
>
|
||||
{reasoningEnabled ? (
|
||||
<LightbulbIcon className="size-3" />
|
||||
) : (
|
||||
<LightbulbOffIcon className="size-3" />
|
||||
)}
|
||||
<span>Think</span>
|
||||
</button>
|
||||
)}
|
||||
{supportsTools && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setToolsEnabled(!toolsEnabled)}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
toolsEnabled
|
||||
)}
|
||||
aria-label={reasoningEnabled ? "Disable thinking" : "Enable thinking"}
|
||||
>
|
||||
{reasoningEnabled && !reasoningDisabled ? (
|
||||
<LightbulbIcon className="size-3" />
|
||||
) : (
|
||||
<LightbulbOffIcon className="size-3" />
|
||||
)}
|
||||
<span>Think</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={toolsDisabled}
|
||||
onClick={() => setToolsEnabled(!toolsEnabled)}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
toolsDisabled
|
||||
? "cursor-not-allowed opacity-40"
|
||||
: toolsEnabled
|
||||
? "bg-primary/10 text-primary hover:bg-primary/20"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted-foreground/15",
|
||||
)}
|
||||
aria-label={toolsEnabled ? "Disable web search" : "Enable web search"}
|
||||
>
|
||||
<GlobeIcon className="size-3.5" />
|
||||
<span>Search</span>
|
||||
</button>
|
||||
)}
|
||||
{supportsTools && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCodeToolsEnabled(!codeToolsEnabled)}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
codeToolsEnabled
|
||||
)}
|
||||
aria-label={toolsEnabled ? "Disable web search" : "Enable web search"}
|
||||
>
|
||||
<GlobeIcon className="size-3.5" />
|
||||
<span>Search</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={toolsDisabled}
|
||||
onClick={() => setCodeToolsEnabled(!codeToolsEnabled)}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
toolsDisabled
|
||||
? "cursor-not-allowed opacity-40"
|
||||
: codeToolsEnabled
|
||||
? "bg-primary/10 text-primary hover:bg-primary/20"
|
||||
: "bg-muted text-muted-foreground hover:bg-muted-foreground/15",
|
||||
)}
|
||||
aria-label={codeToolsEnabled ? "Disable code execution" : "Enable code execution"}
|
||||
>
|
||||
<TerminalIcon className="size-3.5" />
|
||||
<span>Code</span>
|
||||
</button>
|
||||
)}
|
||||
)}
|
||||
aria-label={codeToolsEnabled ? "Disable code execution" : "Enable code execution"}
|
||||
>
|
||||
<TerminalIcon className="size-3.5" />
|
||||
<span>Code</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{dictationSupported && (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue