@@ -13,6 +13,7 @@ import {
1313 ChevronLeftIcon ,
1414 ChevronRightIcon ,
1515 CopyIcon ,
16+ LockIcon ,
1617 PencilIcon ,
1718 RefreshCwIcon ,
1819 Square ,
@@ -29,6 +30,7 @@ import { SettingsDialog } from "@/app/components/assistant-ui/SettingsDialog";
2930import { MarkdownText } from "@/app/components/assistant-ui/markdown-text" ;
3031import { ToolFallback } from "@/app/components/assistant-ui/tool-fallback" ;
3132import { TooltipIconButton } from "@/app/components/assistant-ui/tooltip-icon-button" ;
33+ import { useAssistantSettings } from "@/app/hooks/useAssistantSettings" ;
3234import { Button } from "@/components/ui/button" ;
3335import { cn } from "@/lib/utils" ;
3436import { LazyMotion , MotionConfig , domAnimation } from "motion/react" ;
@@ -168,49 +170,101 @@ const ThreadWelcomeSuggestions: FC = () => {
168170} ;
169171
170172const Composer : FC = ( ) => {
173+ const [ isSettingsOpen , setIsSettingsOpen ] = useState ( false ) ;
174+ const { provider, openaiApiKey, geminiApiKey } = useAssistantSettings ( ) ;
175+ const activeKey = provider === "openai" ? openaiApiKey : geminiApiKey ;
176+ const hasActiveKey = activeKey . trim ( ) . length > 0 ;
177+ const providerLabel = provider === "gemini" ? "Google Gemini" : "OpenAI" ;
178+
171179 return (
172180 < div className = "aui-composer-wrapper sticky bottom-0 mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 overflow-visible rounded-t-3xl pb-4 md:pb-6" >
173181 < ThreadScrollToBottom />
174182 < ThreadPrimitive . Empty >
175183 < ThreadWelcomeSuggestions />
176184 </ ThreadPrimitive . Empty >
177- < ComposerPrimitive . Root className = "aui-composer-root relative flex w-full flex-col rounded-3xl border border-border bg-muted px-1 pt-2 shadow-[0_9px_9px_0px_rgba(0,0,0,0.01),0_2px_5px_0px_rgba(0,0,0,0.06)] dark:border-muted-foreground/15" >
185+ < ComposerPrimitive . Root
186+ className = "aui-composer-root relative flex w-full flex-col rounded-3xl border border-border bg-muted px-1 pt-2 shadow-[0_9px_9px_0px_rgba(0,0,0,0.01),0_2px_5px_0px_rgba(0,0,0,0.06)] dark:border-muted-foreground/15"
187+ aria-disabled = { ! hasActiveKey }
188+ data-key-required = { ! hasActiveKey }
189+ >
190+ { ! hasActiveKey && (
191+ < div className = "absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 rounded-3xl bg-background/90 px-6 text-center backdrop-blur-sm" >
192+ < p className = "text-sm text-muted-foreground" >
193+ Add your { providerLabel } API key in Settings to start chatting.
194+ </ p >
195+ < Button
196+ type = "button"
197+ size = "sm"
198+ onClick = { ( ) => setIsSettingsOpen ( true ) }
199+ >
200+ Open settings
201+ </ Button >
202+ </ div >
203+ ) }
178204 < ComposerAttachments />
179205 < ComposerPrimitive . Input
180206 placeholder = "Send a message..."
181207 className = "aui-composer-input mb-1 max-h-32 min-h-16 w-full resize-none bg-transparent px-3.5 pt-1.5 pb-3 text-base outline-none placeholder:text-muted-foreground focus:outline-primary"
182208 rows = { 1 }
183209 autoFocus
184210 aria-label = "Message input"
211+ disabled = { ! hasActiveKey }
212+ />
213+ < ComposerAction
214+ canSend = { hasActiveKey }
215+ isSettingsOpen = { isSettingsOpen }
216+ onOpenChange = { setIsSettingsOpen }
185217 />
186- < ComposerAction />
187218 </ ComposerPrimitive . Root >
188219 </ div >
189220 ) ;
190221} ;
191222
192- const ComposerAction : FC = ( ) => {
193- const [ isSettingsOpen , setIsSettingsOpen ] = useState ( false ) ;
223+ interface ComposerActionProps {
224+ canSend : boolean ;
225+ isSettingsOpen : boolean ;
226+ onOpenChange : ( open : boolean ) => void ;
227+ }
194228
229+ const ComposerAction : FC < ComposerActionProps > = ( {
230+ canSend,
231+ isSettingsOpen,
232+ onOpenChange,
233+ } ) => {
195234 return (
196235 < >
197236 < div className = "aui-composer-action-wrapper relative mx-1 mt-2 mb-2 flex items-center justify-between" >
198- < SettingsButton onClick = { ( ) => setIsSettingsOpen ( true ) } />
237+ < SettingsButton onClick = { ( ) => onOpenChange ( true ) } />
199238
200239 < ThreadPrimitive . If running = { false } >
201- < ComposerPrimitive . Send asChild >
240+ { canSend ? (
241+ < ComposerPrimitive . Send asChild >
242+ < TooltipIconButton
243+ tooltip = "Send message"
244+ side = "bottom"
245+ type = "submit"
246+ variant = "default"
247+ size = "icon"
248+ className = "aui-composer-send size-[34px] rounded-full p-1"
249+ aria-label = "Send message"
250+ >
251+ < ArrowUpIcon className = "aui-composer-send-icon size-5" />
252+ </ TooltipIconButton >
253+ </ ComposerPrimitive . Send >
254+ ) : (
202255 < TooltipIconButton
203- tooltip = "Send message "
256+ tooltip = "Configure an API key to enable sending "
204257 side = "bottom"
205- type = "submit "
258+ type = "button "
206259 variant = "default"
207260 size = "icon"
208261 className = "aui-composer-send size-[34px] rounded-full p-1"
209- aria-label = "Send message"
262+ aria-label = "Open assistant settings"
263+ onClick = { ( ) => onOpenChange ( true ) }
210264 >
211- < ArrowUpIcon className = "aui-composer-send-icon size-5" />
265+ < LockIcon className = "aui-composer-send-icon size-5" />
212266 </ TooltipIconButton >
213- </ ComposerPrimitive . Send >
267+ ) }
214268 </ ThreadPrimitive . If >
215269
216270 < ThreadPrimitive . If running >
@@ -227,10 +281,7 @@ const ComposerAction: FC = () => {
227281 </ ComposerPrimitive . Cancel >
228282 </ ThreadPrimitive . If >
229283 </ div >
230- < SettingsDialog
231- isOpen = { isSettingsOpen }
232- onOpenChange = { setIsSettingsOpen }
233- />
284+ < SettingsDialog isOpen = { isSettingsOpen } onOpenChange = { onOpenChange } />
234285 </ >
235286 ) ;
236287} ;
0 commit comments