feat(ui): add thinking mode selector to floating prompt input

Add comprehensive thinking mode selection with 5 levels (Auto, Think, Think Hard, Think Harder, Ultrathink) to the FloatingPromptInput component. Features include:

- New ThinkingModeIndicator component with visual level bars
- Thinking mode picker with tooltips and descriptions
- Automatic phrase appending to prompts based on selected mode
- Brain icon integration and enhanced UI layout
- State management for thinking mode selection

This enhancement allows users to control Claude's reasoning depth directly from the prompt interface.
This commit is contained in:
Mufeed VH
2025-06-25 05:24:25 +05:30
parent fa691dfad0
commit b44612204e

View File

@@ -7,12 +7,14 @@ import {
ChevronUp,
Sparkles,
Zap,
Square
Square,
Brain
} from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Popover } from "@/components/ui/popover";
import { Textarea } from "@/components/ui/textarea";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { FilePicker } from "./FilePicker";
import { ImagePreview } from "./ImagePreview";
import { type FileEntry } from "@/lib/api";
@@ -53,6 +55,78 @@ export interface FloatingPromptInputRef {
addImage: (imagePath: string) => void;
}
/**
* Thinking mode type definition
*/
type ThinkingMode = "auto" | "think" | "think_hard" | "think_harder" | "ultrathink";
/**
* Thinking mode configuration
*/
type ThinkingModeConfig = {
id: ThinkingMode;
name: string;
description: string;
level: number; // 0-4 for visual indicator
phrase?: string; // The phrase to append
};
const THINKING_MODES: ThinkingModeConfig[] = [
{
id: "auto",
name: "Auto",
description: "Let Claude decide",
level: 0
},
{
id: "think",
name: "Think",
description: "Basic reasoning",
level: 1,
phrase: "think"
},
{
id: "think_hard",
name: "Think Hard",
description: "Deeper analysis",
level: 2,
phrase: "think hard"
},
{
id: "think_harder",
name: "Think Harder",
description: "Extensive reasoning",
level: 3,
phrase: "think harder"
},
{
id: "ultrathink",
name: "Ultrathink",
description: "Maximum computation",
level: 4,
phrase: "ultrathink"
}
];
/**
* ThinkingModeIndicator component - Shows visual indicator bars for thinking level
*/
const ThinkingModeIndicator: React.FC<{ level: number }> = ({ level }) => {
return (
<div className="flex items-center gap-0.5">
{[1, 2, 3, 4].map((i) => (
<div
key={i}
className={cn(
"w-1 h-3 rounded-full transition-colors",
i <= level ? "bg-blue-500" : "bg-muted"
)}
/>
))}
</div>
);
};
type Model = {
id: "sonnet" | "opus";
name: string;
@@ -100,8 +174,10 @@ const FloatingPromptInputInner = (
) => {
const [prompt, setPrompt] = useState("");
const [selectedModel, setSelectedModel] = useState<"sonnet" | "opus">(defaultModel);
const [selectedThinkingMode, setSelectedThinkingMode] = useState<ThinkingMode>("auto");
const [isExpanded, setIsExpanded] = useState(false);
const [modelPickerOpen, setModelPickerOpen] = useState(false);
const [thinkingModePickerOpen, setThinkingModePickerOpen] = useState(false);
const [showFilePicker, setShowFilePicker] = useState(false);
const [filePickerQuery, setFilePickerQuery] = useState("");
const [cursorPosition, setCursorPosition] = useState(0);
@@ -260,7 +336,15 @@ const FloatingPromptInputInner = (
const handleSend = () => {
if (prompt.trim() && !isLoading && !disabled) {
onSend(prompt.trim(), selectedModel);
let finalPrompt = prompt.trim();
// Append thinking phrase if not auto mode
const thinkingMode = THINKING_MODES.find(m => m.id === selectedThinkingMode);
if (thinkingMode && thinkingMode.phrase) {
finalPrompt = `${finalPrompt}\n\n${thinkingMode.phrase}.`;
}
onSend(finalPrompt, selectedModel);
setPrompt("");
setEmbeddedImages([]);
}
@@ -440,17 +524,81 @@ const FloatingPromptInputInner = (
/>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">Model:</span>
<Button
variant="outline"
size="sm"
onClick={() => setModelPickerOpen(!modelPickerOpen)}
className="gap-2"
>
{selectedModelData.icon}
{selectedModelData.name}
</Button>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">Model:</span>
<Button
variant="outline"
size="sm"
onClick={() => setModelPickerOpen(!modelPickerOpen)}
className="gap-2"
>
{selectedModelData.icon}
{selectedModelData.name}
</Button>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">Thinking:</span>
<Popover
trigger={
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={() => setThinkingModePickerOpen(!thinkingModePickerOpen)}
className="gap-2"
>
<Brain className="h-4 w-4" />
<ThinkingModeIndicator
level={THINKING_MODES.find(m => m.id === selectedThinkingMode)?.level || 0}
/>
</Button>
</TooltipTrigger>
<TooltipContent>
<p className="font-medium">{THINKING_MODES.find(m => m.id === selectedThinkingMode)?.name || "Auto"}</p>
<p className="text-xs text-muted-foreground">{THINKING_MODES.find(m => m.id === selectedThinkingMode)?.description}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
}
content={
<div className="w-[280px] p-1">
{THINKING_MODES.map((mode) => (
<button
key={mode.id}
onClick={() => {
setSelectedThinkingMode(mode.id);
setThinkingModePickerOpen(false);
}}
className={cn(
"w-full flex items-start gap-3 p-3 rounded-md transition-colors text-left",
"hover:bg-accent",
selectedThinkingMode === mode.id && "bg-accent"
)}
>
<Brain className="h-4 w-4 mt-0.5" />
<div className="flex-1 space-y-1">
<div className="font-medium text-sm">
{mode.name}
</div>
<div className="text-xs text-muted-foreground">
{mode.description}
</div>
</div>
<ThinkingModeIndicator level={mode.level} />
</button>
))}
</div>
}
open={thinkingModePickerOpen}
onOpenChange={setThinkingModePickerOpen}
align="start"
side="top"
/>
</div>
</div>
<Button
@@ -541,6 +689,66 @@ const FloatingPromptInputInner = (
side="top"
/>
{/* Thinking Mode Picker */}
<Popover
trigger={
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="default"
disabled={isLoading || disabled}
className="gap-2"
>
<Brain className="h-4 w-4" />
<ThinkingModeIndicator
level={THINKING_MODES.find(m => m.id === selectedThinkingMode)?.level || 0}
/>
</Button>
</TooltipTrigger>
<TooltipContent>
<p className="font-medium">{THINKING_MODES.find(m => m.id === selectedThinkingMode)?.name || "Auto"}</p>
<p className="text-xs text-muted-foreground">{THINKING_MODES.find(m => m.id === selectedThinkingMode)?.description}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
}
content={
<div className="w-[280px] p-1">
{THINKING_MODES.map((mode) => (
<button
key={mode.id}
onClick={() => {
setSelectedThinkingMode(mode.id);
setThinkingModePickerOpen(false);
}}
className={cn(
"w-full flex items-start gap-3 p-3 rounded-md transition-colors text-left",
"hover:bg-accent",
selectedThinkingMode === mode.id && "bg-accent"
)}
>
<Brain className="h-4 w-4 mt-0.5" />
<div className="flex-1 space-y-1">
<div className="font-medium text-sm">
{mode.name}
</div>
<div className="text-xs text-muted-foreground">
{mode.description}
</div>
</div>
<ThinkingModeIndicator level={mode.level} />
</button>
))}
</div>
}
open={thinkingModePickerOpen}
onOpenChange={setThinkingModePickerOpen}
align="start"
side="top"
/>
{/* Prompt Input */}
<div className="flex-1 relative">
<Textarea