fix(copilot): simplify toggle visibility — hide mode/model on session, always show dry-run; icon-only for inactive model state

This commit is contained in:
majdyz
2026-04-15 17:59:53 +07:00
parent 25e34829bc
commit 43e8159822
4 changed files with 33 additions and 86 deletions

View File

@@ -218,25 +218,19 @@ export function ChatInput({
onFilesSelected={handleFilesSelected}
disabled={isBusy}
/>
{showModeToggle &&
!isStreaming &&
(!hasSession || copilotChatMode === "extended_thinking") && (
<ModeToggleButton
mode={copilotChatMode}
onToggle={handleToggleMode}
readOnly={hasSession}
/>
)}
{showModeToggle &&
!isStreaming &&
(!hasSession || copilotLlmModel === "advanced") && (
<ModelToggleButton
model={copilotLlmModel}
onToggle={handleToggleModel}
readOnly={hasSession}
/>
)}
{showDryRunToggle && (!hasSession || isDryRun) && (
{showModeToggle && !isStreaming && !hasSession && (
<ModeToggleButton
mode={copilotChatMode}
onToggle={handleToggleMode}
/>
)}
{showModeToggle && !isStreaming && !hasSession && (
<ModelToggleButton
model={copilotLlmModel}
onToggle={handleToggleModel}
/>
)}
{showDryRunToggle && (
<DryRunToggleButton
isDryRun={isDryRun}
isStreaming={isStreaming}

View File

@@ -7,37 +7,28 @@ import type { CopilotMode } from "../../../store";
interface Props {
mode: CopilotMode;
onToggle: () => void;
readOnly?: boolean;
}
export function ModeToggleButton({ mode, onToggle, readOnly = false }: Props) {
export function ModeToggleButton({ mode, onToggle }: Props) {
const isExtended = mode === "extended_thinking";
return (
<button
type="button"
aria-pressed={isExtended}
disabled={readOnly}
onClick={readOnly ? undefined : onToggle}
onClick={onToggle}
className={cn(
"inline-flex min-h-11 min-w-11 items-center justify-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors",
isExtended
? "bg-purple-100 text-purple-900 hover:bg-purple-200 disabled:hover:bg-purple-100"
: "bg-amber-100 text-amber-900 hover:bg-amber-200 disabled:hover:bg-amber-100",
readOnly && "cursor-default opacity-70",
? "bg-purple-100 text-purple-900 hover:bg-purple-200"
: "bg-amber-100 text-amber-900 hover:bg-amber-200",
)}
aria-label={
readOnly
? `${isExtended ? "Extended Thinking" : "Fast"} mode active for this session`
: isExtended
? "Switch to Fast mode"
: "Switch to Extended Thinking mode"
isExtended ? "Switch to Fast mode" : "Switch to Extended Thinking mode"
}
title={
readOnly
? `${isExtended ? "Extended Thinking" : "Fast"} mode active for this session`
: isExtended
? "Extended Thinking mode — deeper reasoning (click to switch to Fast mode)"
: "Fast mode — quicker responses (click to switch to Extended Thinking)"
isExtended
? "Extended Thinking mode — deeper reasoning (click to switch to Fast mode)"
: "Fast mode — quicker responses (click to switch to Extended Thinking)"
}
>
{isExtended ? (

View File

@@ -7,45 +7,32 @@ import type { CopilotLlmModel } from "../../../store";
interface Props {
model: CopilotLlmModel;
onToggle: () => void;
readOnly?: boolean;
}
export function ModelToggleButton({
model,
onToggle,
readOnly = false,
}: Props) {
export function ModelToggleButton({ model, onToggle }: Props) {
const isAdvanced = model === "advanced";
return (
<button
type="button"
aria-pressed={isAdvanced}
disabled={readOnly}
onClick={readOnly ? undefined : onToggle}
onClick={onToggle}
className={cn(
"inline-flex min-h-11 min-w-11 items-center justify-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors",
isAdvanced
? "bg-sky-100 text-sky-900 hover:bg-sky-200 disabled:hover:bg-sky-100"
: "bg-neutral-100 text-neutral-700 hover:bg-neutral-200 disabled:hover:bg-neutral-100",
readOnly && "cursor-default opacity-70",
? "bg-sky-100 text-sky-900 hover:bg-sky-200"
: "bg-neutral-100 text-neutral-700 hover:bg-neutral-200",
)}
aria-label={
readOnly
? `${isAdvanced ? "Advanced" : "Standard"} model active for this session`
: isAdvanced
? "Switch to Standard model"
: "Switch to Advanced model"
isAdvanced ? "Switch to Standard model" : "Switch to Advanced model"
}
title={
readOnly
? `${isAdvanced ? "Advanced" : "Standard"} model active for this session`
: isAdvanced
? "Advanced model — highest capability (click to switch to Standard)"
: "Standard model — click to switch to Advanced"
isAdvanced
? "Advanced model — highest capability (click to switch to Standard)"
: "Standard model — click to switch to Advanced"
}
>
<Cpu size={14} />
{isAdvanced ? "Advanced" : "Standard"}
{isAdvanced && "Advanced"}
</button>
);
}

View File

@@ -5,9 +5,10 @@ import { ModelToggleButton } from "../ModelToggleButton";
afterEach(cleanup);
describe("ModelToggleButton", () => {
it("shows Standard label when model is standard", () => {
it("shows no text label when model is standard", () => {
render(<ModelToggleButton model="standard" onToggle={vi.fn()} />);
expect(screen.getByText("Standard")).toBeTruthy();
expect(screen.queryByText("Standard")).toBeNull();
expect(screen.queryByText("Advanced")).toBeNull();
});
it("shows Advanced label when model is advanced", () => {
@@ -33,30 +34,4 @@ describe("ModelToggleButton", () => {
const btn = screen.getByLabelText("Switch to Standard model");
expect(btn.getAttribute("aria-pressed")).toBe("true");
});
it("is disabled when readOnly", () => {
render(<ModelToggleButton model="advanced" onToggle={vi.fn()} readOnly />);
expect(screen.getByRole("button").hasAttribute("disabled")).toBe(true);
});
it("does not call onToggle when readOnly", () => {
const onToggle = vi.fn();
render(<ModelToggleButton model="standard" onToggle={onToggle} readOnly />);
fireEvent.click(screen.getByRole("button"));
expect(onToggle).not.toHaveBeenCalled();
});
it("shows session-locked title when readOnly and advanced", () => {
render(<ModelToggleButton model="advanced" onToggle={vi.fn()} readOnly />);
expect(
screen.getByTitle("Advanced model active for this session"),
).toBeDefined();
});
it("shows session-locked title when readOnly and standard", () => {
render(<ModelToggleButton model="standard" onToggle={vi.fn()} readOnly />);
expect(
screen.getByTitle("Standard model active for this session"),
).toBeDefined();
});
});