@@ -15,14 +15,22 @@ import {
1515 CollapsibleTrigger ,
1616} from "@/components/ui"
1717
18- import { deepResearchModels , ProviderId , ResearchSession , researchSessionSchema } from "./types"
18+ import {
19+ deepResearchModels ,
20+ ProviderId ,
21+ ResearchSession ,
22+ researchSessionSchema ,
23+ validateFirecrawlApiKey ,
24+ } from "./types"
1925import { useResearchSession } from "./useResearchSession"
2026import { useProvider } from "./useProvider"
2127import { Providers } from "./Providers"
2228import { Models } from "./Models"
2329
2430export const GetStarted = ( ) => {
2531 const [ isProvidersOpen , setIsProvidersOpen ] = useState ( false )
32+ const [ isValidatingFirecrawl , setIsValidatingFirecrawl ] = useState ( false )
33+ const [ firecrawlError , setFirecrawlError ] = useState < string | null > ( null )
2634 const { setSession } = useResearchSession ( )
2735 const { provider, providers, setProviderValue } = useProvider ( )
2836
@@ -44,10 +52,39 @@ export const GetStarted = () => {
4452 handleSubmit,
4553 control,
4654 setValue,
55+ getValues,
4756 formState : { errors } ,
4857 } = form
4958
50- const onSubmit = useCallback ( ( data : ResearchSession ) => setSession ( data ) , [ setSession ] )
59+ const onSubmit = useCallback (
60+ async ( data : ResearchSession ) => {
61+ // Check for empty key first
62+ if ( ! data . firecrawlApiKey ) {
63+ setFirecrawlError ( "Firecrawl API key is required." )
64+ setIsProvidersOpen ( true )
65+ return
66+ }
67+
68+ // Validate Firecrawl API key before submission
69+ setIsValidatingFirecrawl ( true )
70+ try {
71+ const isValid = await validateFirecrawlApiKey ( data . firecrawlApiKey )
72+ if ( ! isValid ) {
73+ setFirecrawlError ( "Invalid Firecrawl API key. Please check your credentials." )
74+ setIsProvidersOpen ( true )
75+ return
76+ }
77+ setFirecrawlError ( null )
78+ setSession ( data )
79+ } catch ( error ) {
80+ setFirecrawlError ( "Failed to validate Firecrawl API key. Please try again." )
81+ setIsProvidersOpen ( true )
82+ } finally {
83+ setIsValidatingFirecrawl ( false )
84+ }
85+ } ,
86+ [ setSession , setIsProvidersOpen ] ,
87+ )
5188
5289 useEffect ( ( ) => {
5390 setValue ( "providerId" , provider ?. providerId ?? ProviderId . OpenRouter )
@@ -117,14 +154,80 @@ export const GetStarted = () => {
117154 { ...field }
118155 type = "password"
119156 placeholder = "fc-..."
120- className = "flex-1"
121- onBlur = { ( ) => setProviderValue ( "firecrawlApiKey" , field . value ) }
157+ className = { cn (
158+ "flex-1" ,
159+ ( errors . firecrawlApiKey || firecrawlError ) &&
160+ "border-destructive" ,
161+ ) }
162+ onKeyDown = { async ( e ) => {
163+ if ( e . key === "Enter" ) {
164+ e . preventDefault ( ) // Prevent form submission
165+ if ( field . value ) {
166+ setIsValidatingFirecrawl ( true )
167+ setFirecrawlError ( null )
168+ try {
169+ const isValid = await validateFirecrawlApiKey (
170+ field . value ,
171+ )
172+ if ( ! isValid ) {
173+ setFirecrawlError (
174+ "Invalid Firecrawl API key. Please check your credentials." ,
175+ )
176+ }
177+ } catch ( error ) {
178+ setFirecrawlError (
179+ "Failed to validate Firecrawl API key. Please try again." ,
180+ )
181+ } finally {
182+ setIsValidatingFirecrawl ( false )
183+ }
184+ }
185+ }
186+ } }
187+ onBlur = { async ( ) => {
188+ setProviderValue ( "firecrawlApiKey" , field . value )
189+ if ( field . value ) {
190+ setIsValidatingFirecrawl ( true )
191+ setFirecrawlError ( null )
192+ try {
193+ const isValid = await validateFirecrawlApiKey (
194+ field . value ,
195+ )
196+ if ( ! isValid ) {
197+ setFirecrawlError (
198+ "Invalid Firecrawl API key. Please check your credentials." ,
199+ )
200+ }
201+ } catch ( error ) {
202+ setFirecrawlError (
203+ "Failed to validate Firecrawl API key. Please try again." ,
204+ )
205+ } finally {
206+ setIsValidatingFirecrawl ( false )
207+ }
208+ }
209+ } }
122210 />
211+ { ( errors . firecrawlApiKey || firecrawlError ) && (
212+ < div className = "text-destructive text-sm" >
213+ { firecrawlError || errors . firecrawlApiKey ?. message }
214+ </ div >
215+ ) }
216+ { isValidatingFirecrawl && (
217+ < div className = "text-muted-foreground text-sm" >
218+ Validating API key...
219+ </ div >
220+ ) }
123221 < div className = "flex flex-row items-center justify-between gap-2" >
124222 < div className = "text-muted-foreground" >
125223 Firecrawl turns websites into LLM-ready data.
126224 </ div >
127- < Button variant = "outline" size = "sm" asChild >
225+ < Button
226+ variant = "outline"
227+ size = "sm"
228+ asChild
229+ tabIndex = { - 1 } // Remove from tab order
230+ >
128231 < a href = "https://www.firecrawl.com/" > Get API Key</ a >
129232 </ Button >
130233 </ div >
@@ -213,7 +316,11 @@ export const GetStarted = () => {
213316 ) }
214317 />
215318 </ Card >
216- < Button variant = "default" size = "lg" type = "submit" >
319+ < Button
320+ variant = "default"
321+ size = "lg"
322+ type = "submit"
323+ disabled = { isValidatingFirecrawl || ! ! firecrawlError || ! getValues ( "firecrawlApiKey" ) } >
217324 < span className = "codicon codicon-rocket" />
218325 < span className = "font-bold text-lg" > Start</ span >
219326 </ Button >
@@ -223,6 +330,7 @@ export const GetStarted = () => {
223330 { error ?. message }
224331 </ div >
225332 ) ) }
333+ { firecrawlError && < div className = "text-red-500" > { firecrawlError } </ div > }
226334 </ div >
227335 </ form >
228336 </ FormProvider >
0 commit comments