@@ -7,12 +7,14 @@ import {
77 ChevronUp ,
88 Sparkles ,
99 Zap ,
10- Square
10+ Square ,
11+ Brain
1112} from "lucide-react" ;
1213import { cn } from "@/lib/utils" ;
1314import { Button } from "@/components/ui/button" ;
1415import { Popover } from "@/components/ui/popover" ;
1516import { Textarea } from "@/components/ui/textarea" ;
17+ import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from "@/components/ui/tooltip" ;
1618import { FilePicker } from "./FilePicker" ;
1719import { ImagePreview } from "./ImagePreview" ;
1820import { type FileEntry } from "@/lib/api" ;
@@ -53,6 +55,78 @@ export interface FloatingPromptInputRef {
5355 addImage : ( imagePath : string ) => void ;
5456}
5557
58+ /**
59+ * Thinking mode type definition
60+ */
61+ type ThinkingMode = "auto" | "think" | "think_hard" | "think_harder" | "ultrathink" ;
62+
63+ /**
64+ * Thinking mode configuration
65+ */
66+ type ThinkingModeConfig = {
67+ id : ThinkingMode ;
68+ name : string ;
69+ description : string ;
70+ level : number ; // 0-4 for visual indicator
71+ phrase ?: string ; // The phrase to append
72+ } ;
73+
74+ const THINKING_MODES : ThinkingModeConfig [ ] = [
75+ {
76+ id : "auto" ,
77+ name : "Auto" ,
78+ description : "Let Claude decide" ,
79+ level : 0
80+ } ,
81+ {
82+ id : "think" ,
83+ name : "Think" ,
84+ description : "Basic reasoning" ,
85+ level : 1 ,
86+ phrase : "think"
87+ } ,
88+ {
89+ id : "think_hard" ,
90+ name : "Think Hard" ,
91+ description : "Deeper analysis" ,
92+ level : 2 ,
93+ phrase : "think hard"
94+ } ,
95+ {
96+ id : "think_harder" ,
97+ name : "Think Harder" ,
98+ description : "Extensive reasoning" ,
99+ level : 3 ,
100+ phrase : "think harder"
101+ } ,
102+ {
103+ id : "ultrathink" ,
104+ name : "Ultrathink" ,
105+ description : "Maximum computation" ,
106+ level : 4 ,
107+ phrase : "ultrathink"
108+ }
109+ ] ;
110+
111+ /**
112+ * ThinkingModeIndicator component - Shows visual indicator bars for thinking level
113+ */
114+ const ThinkingModeIndicator : React . FC < { level : number } > = ( { level } ) => {
115+ return (
116+ < div className = "flex items-center gap-0.5" >
117+ { [ 1 , 2 , 3 , 4 ] . map ( ( i ) => (
118+ < div
119+ key = { i }
120+ className = { cn (
121+ "w-1 h-3 rounded-full transition-colors" ,
122+ i <= level ? "bg-blue-500" : "bg-muted"
123+ ) }
124+ />
125+ ) ) }
126+ </ div >
127+ ) ;
128+ } ;
129+
56130type Model = {
57131 id : "sonnet" | "opus" ;
58132 name : string ;
@@ -100,8 +174,10 @@ const FloatingPromptInputInner = (
100174) => {
101175 const [ prompt , setPrompt ] = useState ( "" ) ;
102176 const [ selectedModel , setSelectedModel ] = useState < "sonnet" | "opus" > ( defaultModel ) ;
177+ const [ selectedThinkingMode , setSelectedThinkingMode ] = useState < ThinkingMode > ( "auto" ) ;
103178 const [ isExpanded , setIsExpanded ] = useState ( false ) ;
104179 const [ modelPickerOpen , setModelPickerOpen ] = useState ( false ) ;
180+ const [ thinkingModePickerOpen , setThinkingModePickerOpen ] = useState ( false ) ;
105181 const [ showFilePicker , setShowFilePicker ] = useState ( false ) ;
106182 const [ filePickerQuery , setFilePickerQuery ] = useState ( "" ) ;
107183 const [ cursorPosition , setCursorPosition ] = useState ( 0 ) ;
@@ -260,7 +336,15 @@ const FloatingPromptInputInner = (
260336
261337 const handleSend = ( ) => {
262338 if ( prompt . trim ( ) && ! isLoading && ! disabled ) {
263- onSend ( prompt . trim ( ) , selectedModel ) ;
339+ let finalPrompt = prompt . trim ( ) ;
340+
341+ // Append thinking phrase if not auto mode
342+ const thinkingMode = THINKING_MODES . find ( m => m . id === selectedThinkingMode ) ;
343+ if ( thinkingMode && thinkingMode . phrase ) {
344+ finalPrompt = `${ finalPrompt } \n\n${ thinkingMode . phrase } .` ;
345+ }
346+
347+ onSend ( finalPrompt , selectedModel ) ;
264348 setPrompt ( "" ) ;
265349 setEmbeddedImages ( [ ] ) ;
266350 }
@@ -440,17 +524,81 @@ const FloatingPromptInputInner = (
440524 />
441525
442526 < div className = "flex items-center justify-between" >
443- < div className = "flex items-center gap-2" >
444- < span className = "text-xs text-muted-foreground" > Model:</ span >
445- < Button
446- variant = "outline"
447- size = "sm"
448- onClick = { ( ) => setModelPickerOpen ( ! modelPickerOpen ) }
449- className = "gap-2"
450- >
451- { selectedModelData . icon }
452- { selectedModelData . name }
453- </ Button >
527+ < div className = "flex items-center gap-4" >
528+ < div className = "flex items-center gap-2" >
529+ < span className = "text-xs text-muted-foreground" > Model:</ span >
530+ < Button
531+ variant = "outline"
532+ size = "sm"
533+ onClick = { ( ) => setModelPickerOpen ( ! modelPickerOpen ) }
534+ className = "gap-2"
535+ >
536+ { selectedModelData . icon }
537+ { selectedModelData . name }
538+ </ Button >
539+ </ div >
540+
541+ < div className = "flex items-center gap-2" >
542+ < span className = "text-xs text-muted-foreground" > Thinking:</ span >
543+ < Popover
544+ trigger = {
545+ < TooltipProvider >
546+ < Tooltip >
547+ < TooltipTrigger asChild >
548+ < Button
549+ variant = "outline"
550+ size = "sm"
551+ onClick = { ( ) => setThinkingModePickerOpen ( ! thinkingModePickerOpen ) }
552+ className = "gap-2"
553+ >
554+ < Brain className = "h-4 w-4" />
555+ < ThinkingModeIndicator
556+ level = { THINKING_MODES . find ( m => m . id === selectedThinkingMode ) ?. level || 0 }
557+ />
558+ </ Button >
559+ </ TooltipTrigger >
560+ < TooltipContent >
561+ < p className = "font-medium" > { THINKING_MODES . find ( m => m . id === selectedThinkingMode ) ?. name || "Auto" } </ p >
562+ < p className = "text-xs text-muted-foreground" > { THINKING_MODES . find ( m => m . id === selectedThinkingMode ) ?. description } </ p >
563+ </ TooltipContent >
564+ </ Tooltip >
565+ </ TooltipProvider >
566+ }
567+ content = {
568+ < div className = "w-[280px] p-1" >
569+ { THINKING_MODES . map ( ( mode ) => (
570+ < button
571+ key = { mode . id }
572+ onClick = { ( ) => {
573+ setSelectedThinkingMode ( mode . id ) ;
574+ setThinkingModePickerOpen ( false ) ;
575+ } }
576+ className = { cn (
577+ "w-full flex items-start gap-3 p-3 rounded-md transition-colors text-left" ,
578+ "hover:bg-accent" ,
579+ selectedThinkingMode === mode . id && "bg-accent"
580+ ) }
581+ >
582+ < Brain className = "h-4 w-4 mt-0.5" />
583+ < div className = "flex-1 space-y-1" >
584+ < div className = "font-medium text-sm" >
585+ { mode . name }
586+ </ div >
587+ < div className = "text-xs text-muted-foreground" >
588+ { mode . description }
589+ </ div >
590+ </ div >
591+ < ThinkingModeIndicator level = { mode . level } />
592+ </ button >
593+ ) ) }
594+ </ div >
595+ }
596+ open = { thinkingModePickerOpen }
597+ onOpenChange = { setThinkingModePickerOpen }
598+ align = "start"
599+ side = "top"
600+ />
601+ </ div >
454602 </ div >
455603
456604 < Button
@@ -541,6 +689,66 @@ const FloatingPromptInputInner = (
541689 side = "top"
542690 />
543691
692+ { /* Thinking Mode Picker */ }
693+ < Popover
694+ trigger = {
695+ < TooltipProvider >
696+ < Tooltip >
697+ < TooltipTrigger asChild >
698+ < Button
699+ variant = "outline"
700+ size = "default"
701+ disabled = { isLoading || disabled }
702+ className = "gap-2"
703+ >
704+ < Brain className = "h-4 w-4" />
705+ < ThinkingModeIndicator
706+ level = { THINKING_MODES . find ( m => m . id === selectedThinkingMode ) ?. level || 0 }
707+ />
708+ </ Button >
709+ </ TooltipTrigger >
710+ < TooltipContent >
711+ < p className = "font-medium" > { THINKING_MODES . find ( m => m . id === selectedThinkingMode ) ?. name || "Auto" } </ p >
712+ < p className = "text-xs text-muted-foreground" > { THINKING_MODES . find ( m => m . id === selectedThinkingMode ) ?. description } </ p >
713+ </ TooltipContent >
714+ </ Tooltip >
715+ </ TooltipProvider >
716+ }
717+ content = {
718+ < div className = "w-[280px] p-1" >
719+ { THINKING_MODES . map ( ( mode ) => (
720+ < button
721+ key = { mode . id }
722+ onClick = { ( ) => {
723+ setSelectedThinkingMode ( mode . id ) ;
724+ setThinkingModePickerOpen ( false ) ;
725+ } }
726+ className = { cn (
727+ "w-full flex items-start gap-3 p-3 rounded-md transition-colors text-left" ,
728+ "hover:bg-accent" ,
729+ selectedThinkingMode === mode . id && "bg-accent"
730+ ) }
731+ >
732+ < Brain className = "h-4 w-4 mt-0.5" />
733+ < div className = "flex-1 space-y-1" >
734+ < div className = "font-medium text-sm" >
735+ { mode . name }
736+ </ div >
737+ < div className = "text-xs text-muted-foreground" >
738+ { mode . description }
739+ </ div >
740+ </ div >
741+ < ThinkingModeIndicator level = { mode . level } />
742+ </ button >
743+ ) ) }
744+ </ div >
745+ }
746+ open = { thinkingModePickerOpen }
747+ onOpenChange = { setThinkingModePickerOpen }
748+ align = "start"
749+ side = "top"
750+ />
751+
544752 { /* Prompt Input */ }
545753 < div className = "flex-1 relative" >
546754 < Textarea
0 commit comments