@@ -66,7 +66,9 @@ export function ControlBar({
6666 onRun : ( prompt : string , model : string ) => void ;
6767 onReset : ( ) => void ;
6868} ) {
69- const [ prompt , setPrompt ] = useState ( "Create a TypeScript CLI tool that converts celsius to fahrenheit with input validation" ) ;
69+ const [ prompt , setPrompt ] = useState (
70+ "Create a TypeScript CLI tool that converts celsius to fahrenheit with input validation"
71+ ) ;
7072 const [ model , setModel ] = useState ( "sonnet-4.5" ) ;
7173
7274 const isDisabled = runState . status === "starting" || runState . status === "running" ;
@@ -77,71 +79,103 @@ export function ControlBar({
7779 onRun ( prompt . trim ( ) , model ) ;
7880 }
7981
82+ function handleKeyDown ( e : React . KeyboardEvent < HTMLTextAreaElement > ) {
83+ if ( e . key === "Enter" && ! e . shiftKey ) {
84+ e . preventDefault ( ) ;
85+ if ( ! prompt . trim ( ) || isDisabled ) return ;
86+ onRun ( prompt . trim ( ) , model ) ;
87+ }
88+ }
89+
8090 return (
81- < form onSubmit = { handleSubmit } className = "flex flex-col gap-3" >
82- < div className = "flex gap-3" >
83- < input
84- type = "text"
85- value = { prompt }
86- onChange = { ( e ) => setPrompt ( e . target . value ) }
87- disabled = { isDisabled }
88- placeholder = "Describe what to create..."
89- className = "flex-1 bg-[var(--terminal-bg)] border border-[var(--terminal-border)] rounded-lg px-4 py-2.5 text-sm text-white placeholder:text-white/30 focus:outline-none focus:border-white/30 disabled:opacity-50 font-[family-name:var(--font-geist-mono)]"
90- />
91-
92- < select
93- value = { model }
94- onChange = { ( e ) => setModel ( e . target . value ) }
95- disabled = { isDisabled }
96- className = "bg-[var(--terminal-bg)] border border-[var(--terminal-border)] rounded-lg px-3 py-2.5 text-sm text-white/80 focus:outline-none focus:border-white/30 disabled:opacity-50"
97- >
98- { models . map ( ( m ) => (
99- < option key = { m . value } value = { m . value } > { m . label } </ option >
100- ) ) }
101- </ select >
102-
103- { runState . status === "complete" || runState . status === "failed" ? (
104- < button
105- type = "button"
106- onClick = { onReset }
107- className = "px-5 py-2.5 rounded-lg bg-white/10 text-white text-sm font-medium hover:bg-white/15 transition-colors"
108- >
109- New run
110- </ button >
111- ) : (
112- < button
113- type = "submit"
114- disabled = { isDisabled || ! prompt . trim ( ) }
115- className = "px-5 py-2.5 rounded-lg bg-white text-black text-sm font-medium hover:bg-white/90 transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
116- >
117- { runState . status === "starting" ? "Starting..." : "Run" }
118- </ button >
119- ) }
120- </ div >
121-
122- < div className = "flex items-center gap-2" >
123- < StatusDot status = { runState . status } />
124- < span className = "text-xs text-white/40" >
125- { runState . status === "idle" && "Ready" }
126- { runState . status === "starting" && "Triggering task..." }
127- { runState . status === "running" && "Agent is working..." }
128- { runState . status === "complete" && "Complete" }
129- { runState . status === "failed" && `Failed: ${ runState . error } ` }
130- </ span >
131- </ div >
132- </ form >
91+ < div >
92+ < form onSubmit = { handleSubmit } >
93+ < div className = "bg-white/[0.018] backdrop-blur-[16px] rounded-2xl border border-border p-1.5 transition-colors focus-within:border-border-strong" >
94+ < textarea
95+ value = { prompt }
96+ onChange = { ( e ) => setPrompt ( e . target . value ) }
97+ onKeyDown = { handleKeyDown }
98+ disabled = { isDisabled }
99+ placeholder = "Describe what to build..."
100+ rows = { 2 }
101+ className = "w-full bg-transparent resize-none px-4 py-3.5 text-sm text-text placeholder:text-dim focus:outline-none disabled:opacity-40 font-mono leading-relaxed"
102+ />
103+
104+ < div className = "flex items-center justify-between px-3 pb-2.5 pt-1" >
105+ < div className = "flex items-center gap-1.5" >
106+ { models . map ( ( m ) => (
107+ < button
108+ key = { m . value }
109+ type = "button"
110+ disabled = { isDisabled }
111+ onClick = { ( ) => setModel ( m . value ) }
112+ className = { `border transition-all duration-150 ease cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed px-3 py-1.5 rounded-lg text-xs font-medium ${
113+ model === m . value
114+ ? "bg-accent-soft border-[rgba(45,212,191,0.15)] text-accent"
115+ : "border-border text-muted hover:not-disabled:border-border-strong hover:not-disabled:bg-white/[0.025]"
116+ } `}
117+ >
118+ { m . label }
119+ </ button >
120+ ) ) }
121+ </ div >
122+
123+ { runState . status === "complete" || runState . status === "failed" ? (
124+ < button
125+ type = "button"
126+ onClick = { onReset }
127+ className = "px-4 py-2 rounded-lg border border-border text-muted text-xs font-medium hover:border-border-strong hover:text-text transition-all"
128+ >
129+ New run
130+ </ button >
131+ ) : (
132+ < button
133+ type = "submit"
134+ disabled = { isDisabled || ! prompt . trim ( ) }
135+ className = "bg-linear-to-br from-accent to-[#25B5A3] text-surface font-semibold transition-all duration-200 ease-[cubic-bezier(0.16,1,0.3,1)] hover:not-disabled:shadow-[0_0_24px_rgba(45,212,191,0.3),0_2px_8px_rgba(0,0,0,0.3)] hover:not-disabled:-translate-y-px active:not-disabled:translate-y-0 disabled:opacity-25 disabled:cursor-not-allowed px-5 py-2 rounded-lg text-sm"
136+ >
137+ { runState . status === "starting" ? (
138+ < span className = "flex items-center gap-2" >
139+ < Spinner />
140+ Starting...
141+ </ span >
142+ ) : (
143+ "Run"
144+ ) }
145+ </ button >
146+ ) }
147+ </ div >
148+ </ div >
149+ </ form >
150+
151+ { runState . status === "starting" && (
152+ < div className = "mt-3 flex items-center gap-2 px-2" >
153+ < span className = "w-1.5 h-1.5 rounded-full bg-accent animate-pulse" />
154+ < span className = "text-xs text-dim" > Triggering task...</ span >
155+ </ div >
156+ ) }
157+
158+ { runState . status === "failed" && (
159+ < div className = "mt-3 rounded-lg border border-red-500/20 bg-red-500/5 px-4 py-3 flex items-center gap-2.5" >
160+ < span className = "w-1.5 h-1.5 rounded-full bg-red-400 shrink-0" />
161+ < span className = "text-xs text-red-400 font-mono" > { runState . error } </ span >
162+ </ div >
163+ ) }
164+ </ div >
133165 ) ;
134166}
135167
136- function StatusDot ( { status } : { status : RunState [ "status" ] } ) {
137- const color =
138- status === "running" || status === "starting"
139- ? "bg-yellow-400 animate-pulse"
140- : status === "complete"
141- ? "bg-green-400"
142- : status === "failed"
143- ? "bg-red-400"
144- : "bg-white/20" ;
145-
146- return < div className = { `w-2 h-2 rounded-full ${ color } ` } /> ;
168+ function Spinner ( ) {
169+ return (
170+ < svg className = "animate-spin h-3.5 w-3.5" viewBox = "0 0 24 24" fill = "none" >
171+ < circle className = "opacity-20" cx = "12" cy = "12" r = "10" stroke = "currentColor" strokeWidth = "3" />
172+ < path
173+ className = "opacity-80"
174+ d = "M4 12a8 8 0 018-8"
175+ stroke = "currentColor"
176+ strokeWidth = "3"
177+ strokeLinecap = "round"
178+ />
179+ </ svg >
180+ ) ;
147181}
0 commit comments