Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 35 additions & 25 deletions src/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
prepareCompactionMessage,
type CommandHandlerContext,
} from "@/utils/chatCommands";
import { ToggleGroup } from "./ToggleGroup";
import { ToggleGroup, type ToggleOption } from "./ToggleGroup";
import { CUSTOM_EVENTS } from "@/constants/events";
import type { UIMode } from "@/types/mode";
import {
Expand Down Expand Up @@ -88,6 +88,25 @@ export interface ChatInputAPI {
appendText: (text: string) => void;
}

const MODE_OPTIONS: Array<ToggleOption<UIMode>> = [
{ value: "exec", label: "Exec", activeClassName: "bg-exec-mode text-white" },
{ value: "plan", label: "Plan", activeClassName: "bg-plan-mode text-white" },
];

const ModeHelpTooltip: React.FC = () => (
<TooltipWrapper inline>
<HelpIndicator>?</HelpIndicator>
<Tooltip className="tooltip" align="center" width="wide">
<strong>Exec Mode:</strong> AI edits files and execute commands
<br />
<br />
<strong>Plan Mode:</strong> AI proposes plans but does not edit files
<br />
<br />
Toggle with: {formatKeybind(KEYBINDS.TOGGLE_MODE)}
</Tooltip>
</TooltipWrapper>
);
export interface ChatInputProps {
workspaceId: string;
onMessageSent?: () => void; // Optional callback after successful send
Expand Down Expand Up @@ -847,18 +866,19 @@ export const ChatInput: React.FC<ChatInputProps> = ({
</TooltipWrapper>
</div>

{/* Thinking Slider - hide on small viewports */}
{/* Thinking Slider - slider hidden on narrow viewports, label always clickable */}
<div
className="flex items-center max-[550px]:hidden"
className="flex items-center [&_.thinking-slider]:max-[550px]:hidden"
data-component="ThinkingSliderGroup"
>
<ThinkingSliderComponent modelString={preferredModel} />
</div>

{/* Context 1M Checkbox - hide on smaller viewports */}
<div className="flex items-center max-[450px]:hidden" data-component="Context1MGroup">
{/* Context 1M Checkbox - always visible */}
<div className="flex items-center" data-component="Context1MGroup">
<Context1MCheckbox modelString={preferredModel} />
</div>

{preferredModel && (
<div className={hasTypedText ? "block" : "hidden"}>
<Suspense
Expand All @@ -875,6 +895,8 @@ export const ChatInput: React.FC<ChatInputProps> = ({
</Suspense>
</div>
)}

{/* Mode Switch - full version for wide viewports */}
<div className="ml-auto flex items-center gap-1.5 max-[550px]:hidden">
<div
className={cn(
Expand All @@ -886,27 +908,15 @@ export const ChatInput: React.FC<ChatInputProps> = ({
"[&>button:last-of-type]:bg-plan-mode [&>button:last-of-type]:text-white [&>button:last-of-type]:hover:bg-plan-mode-hover"
)}
>
<ToggleGroup<UIMode>
options={[
{ value: "exec", label: "Exec", activeClassName: "bg-exec-mode text-white" },
{ value: "plan", label: "Plan", activeClassName: "bg-plan-mode text-white" },
]}
value={mode}
onChange={setMode}
/>
<ToggleGroup<UIMode> options={MODE_OPTIONS} value={mode} onChange={setMode} />
</div>
<TooltipWrapper inline>
<HelpIndicator>?</HelpIndicator>
<Tooltip className="tooltip" align="center" width="wide">
<strong>Exec Mode:</strong> AI edits files and execute commands
<br />
<br />
<strong>Plan Mode:</strong> AI proposes plans but does not edit files
<br />
<br />
Toggle with: {formatKeybind(KEYBINDS.TOGGLE_MODE)}
</Tooltip>
</TooltipWrapper>
<ModeHelpTooltip />
</div>

{/* Mode Switch - compact version for narrow viewports */}
<div className="ml-auto hidden items-center gap-1.5 max-[550px]:flex">
<ToggleGroup<UIMode> options={MODE_OPTIONS} value={mode} onChange={setMode} compact />
<ModeHelpTooltip />
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Context1MCheckbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const Context1MCheckbox: React.FC<Context1MCheckboxProps> = ({ modelStrin
<div className="flex items-center gap-1.5">
<label className="text-foreground flex cursor-pointer items-center gap-1 truncate text-[10px] select-none hover:text-white">
<input type="checkbox" checked={use1M} onChange={(e) => setUse1M(e.target.checked)} />
1M Context
1M
</label>
<TooltipWrapper inline>
<span className="text-muted flex cursor-help items-center text-[10px] leading-none">?</span>
Expand Down
28 changes: 21 additions & 7 deletions src/components/ThinkingSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ export const ThinkingSliderComponent: React.FC<ThinkingControlProps> = ({ modelS
}
};

// Cycle through allowed thinking levels: off → low → medium → high → off
const cycleThinkingLevel = () => {
const currentIndex = THINKING_LEVELS.indexOf(thinkingLevel);
const nextIndex = (currentIndex + 1) % THINKING_LEVELS.length;
handleThinkingLevelChange(THINKING_LEVELS[nextIndex]);
};

return (
<TooltipWrapper>
<div className="flex items-center gap-2">
Expand Down Expand Up @@ -154,16 +161,23 @@ export const ThinkingSliderComponent: React.FC<ThinkingControlProps> = ({ modelS
} as React.CSSProperties
}
/>
<span
className="min-w-11 uppercase transition-all duration-200 select-none"
style={textStyle}
aria-live="polite"
<button
type="button"
onClick={cycleThinkingLevel}
className="cursor-pointer border-none bg-transparent p-0"
aria-label={`Thinking level: ${thinkingLevel}. Click to cycle.`}
>
{thinkingLevel}
</span>
<span
className="min-w-11 uppercase transition-all duration-200 select-none"
style={textStyle}
aria-live="polite"
>
{thinkingLevel}
</span>
</button>
</div>
<Tooltip align="center">
Thinking: {formatKeybind(KEYBINDS.TOGGLE_THINKING)} to toggle
Thinking: {formatKeybind(KEYBINDS.TOGGLE_THINKING)} to toggle. Click level to cycle.
</Tooltip>
</TooltipWrapper>
);
Expand Down
30 changes: 29 additions & 1 deletion src/components/ToggleGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,37 @@ interface ToggleGroupProps<T extends string> {
options: Array<ToggleOption<T>>;
value: T;
onChange: (value: T) => void;
compact?: boolean; // If true, show only active option as clickable badge
}

export function ToggleGroup<T extends string>({ options, value, onChange }: ToggleGroupProps<T>) {
export function ToggleGroup<T extends string>({
options,
value,
onChange,
compact = false,
}: ToggleGroupProps<T>) {
// Compact mode: show only active option, click cycles to next option
if (compact) {
const currentIndex = options.findIndex((opt) => opt.value === value);
const activeOption = options[currentIndex];
const nextOption = options[(currentIndex + 1) % options.length];

return (
<button
onClick={() => onChange(nextOption.value)}
type="button"
className={cn(
"px-2 py-1 text-[11px] font-sans rounded-sm border-none cursor-pointer transition-all duration-150",
"text-toggle-text-active bg-toggle-active font-medium",
activeOption?.activeClassName
)}
aria-label={`${activeOption.label} mode. Click to switch to ${nextOption.label}.`}
>
{activeOption.label}
</button>
);
}

return (
<div className="bg-toggle-bg flex gap-0 rounded">
{options.map((option) => {
Expand Down
Loading