From cfaf704652a6cb76f88f1d5ae6a987646fd2f6a8 Mon Sep 17 00:00:00 2001 From: m4 Date: Thu, 30 Jul 2026 10:41:32 +0800 Subject: [PATCH] feat(webui): segmented default/temperature/top_p sampling picker in the chat bar Co-Authored-By: Claude Opus 4.7 --- src/app/components/ChatInterface.tsx | 185 ++++++++++++++++++++------- 1 file changed, 136 insertions(+), 49 deletions(-) diff --git a/src/app/components/ChatInterface.tsx b/src/app/components/ChatInterface.tsx index 9fe3027..b497122 100644 --- a/src/app/components/ChatInterface.tsx +++ b/src/app/components/ChatInterface.tsx @@ -101,7 +101,11 @@ import { WorkspaceFileDialog } from "@/app/components/WorkspaceFileDialog"; import { MemoryFileDialog } from "@/app/components/MemoryFileDialog"; import { FILE_LINK_EVENT, type FileLinkEventDetail } from "@/lib/fileLink"; import { notifyWorkspaceChanged } from "@/lib/workspaceEvents"; -import type { ModelRef, ReasoningEffort } from "@/lib/modelRegistry"; +import type { + ModelRef, + ReasoningEffort, + SamplingOverride, +} from "@/lib/modelRegistry"; import { modelRefLabel, useAvailableModels, @@ -2255,7 +2259,7 @@ export const ChatInterface = React.memo( if (modelSelection === "inherit") return; try { await setModelSelection({ - primary: modelSelection.primary, + ...modelSelection, reasoning_effort: effort, }); toast.success( @@ -2274,70 +2278,36 @@ export const ChatInterface = React.memo( /> )} {currentModel && ( - { if (modelSelection === "inherit") return; try { await setModelSelection({ ...modelSelection, - temperature: value, + sampling_override: value, }); toast.success( value === null - ? "Temperature back to the registry default." - : `Temperature set to ${value.toFixed(2)}.` + ? "Sampling back to the registry default." + : value.kind === "temperature" + ? `Temperature set to ${value.value.toFixed(2)} (top_p omitted).` + : `Top P set to ${value.value.toFixed(2)} (temperature omitted).` ); } catch (err) { toast.error( err instanceof Error - ? `Couldn't update temperature: ${err.message}` - : "Couldn't update temperature — try again." - ); - } - }} - /> - )} - {currentModel && ( - { - if (modelSelection === "inherit") return; - try { - await setModelSelection({ - ...modelSelection, - top_p: value, - }); - toast.success( - value === null - ? "Top P back to the registry default." - : `Top P set to ${value.toFixed(2)}.` - ); - } catch (err) { - toast.error( - err instanceof Error - ? `Couldn't update top P: ${err.message}` - : "Couldn't update top P — try again." + ? `Couldn't update sampling: ${err.message}` + : "Couldn't update sampling — try again." ); } }} @@ -2777,3 +2747,120 @@ function GenerationParamSlider({ ); } + +function SamplingOverridePicker({ + disabled, + override, + fallbackTemperature, + fallbackTopP, + onCommit, +}: { + disabled: boolean; + /** Current per-thread override; null means the registry defaults apply. */ + override: SamplingOverride | null; + /** Registry-effective defaults used to anchor each slider. */ + fallbackTemperature: number | null; + fallbackTopP: number | null; + onCommit: (value: SamplingOverride | null) => void | Promise; +}) { + const mode: "default" | "temperature" | "top_p" = override?.kind ?? "default"; + + const select = (next: "default" | "temperature" | "top_p") => { + if (next === mode) return; + if (next === "default") { + void onCommit(null); + return; + } + const value = + next === "temperature" + ? (override?.kind === "temperature" ? override.value : null) ?? + fallbackTemperature ?? + 1 + : (override?.kind === "top_p" ? override.value : null) ?? + fallbackTopP ?? + 1; + void onCommit({ kind: next, value }); + }; + + const modes = [ + { key: "default", label: "默认" }, + { key: "temperature", label: "T" }, + { key: "top_p", label: "P" }, + ] as const; + + return ( + + {/* ≥sm: segmented control */} + + {modes.map(({ key, label }) => ( + + ))} + + {/* + select(event.target.value as "default" | "temperature" | "top_p") + } + className="sm:hidden rounded border border-border bg-transparent px-1 py-0.5 text-xs text-muted-foreground" + > + {modes.map(({ key, label }) => ( + + ))} + + {mode === "temperature" && ( + + void onCommit(value === null ? null : { kind: "temperature", value }) + } + /> + )} + {mode === "top_p" && ( + + void onCommit(value === null ? null : { kind: "top_p", value }) + } + /> + )} + + ); +}