From 259fd169cb9d491f3680e19dd2566227c81dfe71 Mon Sep 17 00:00:00 2001 From: Mihir Penugonda Date: Sat, 7 Jun 2025 21:13:47 -0700 Subject: [PATCH] setup basic key validation --- frontend/components/APIKeyForm.tsx | 124 +++++++++++++++++++++++++---- frontend/stores/APIKeyStore.ts | 28 +++++++ lib/apiValidationService.ts | 85 ++++++++++++++++++++ 3 files changed, 223 insertions(+), 14 deletions(-) create mode 100644 lib/apiValidationService.ts diff --git a/frontend/components/APIKeyForm.tsx b/frontend/components/APIKeyForm.tsx index 86336e0..68dc851 100644 --- a/frontend/components/APIKeyForm.tsx +++ b/frontend/components/APIKeyForm.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { FieldError, useForm, UseFormRegister } from 'react-hook-form'; @@ -14,8 +14,10 @@ import { } from '@/frontend/components/ui/card'; import { Key } from 'lucide-react'; import { toast } from 'sonner'; -import { useAPIKeyStore } from '@/frontend/stores/APIKeyStore'; +import { useAPIKeyStore, Provider } from '@/frontend/stores/APIKeyStore'; import { Badge } from './ui/badge'; +import { APIValidationResult, validateAPIKey as validateAPIKeyService } from '@/lib/apiValidationService'; +import { CheckCircle, XCircle, Loader2 } from 'lucide-react'; const formSchema = z.object({ google: z.string().trim().min(1, { @@ -48,27 +50,82 @@ export default function APIKeyForm() { const Form = () => { const { keys, setKeys } = useAPIKeyStore(); + const [apiValidationResults, setApiValidationResults] = useState>({}); + const [validatingKeys, setValidatingKeys] = useState>(new Set()); const { register, handleSubmit, formState: { errors, isDirty }, reset, + watch, } = useForm({ resolver: zodResolver(formSchema), defaultValues: keys, }); + const watchedValues = watch(); + useEffect(() => { reset(keys); }, [keys, reset]); + useEffect(() => { + Object.keys(watchedValues).forEach(provider => { + if (apiValidationResults[provider]) { + setApiValidationResults(prev => { + const newResults = { ...prev }; + delete newResults[provider]; + return newResults; + }); + } + }); + }, [watchedValues, apiValidationResults]); + + + const validateWithAPI = useCallback(async (formValues: FormValues) => { + const providersToValidate = Object.entries(formValues).filter(([, value]) => + value && value.trim().length > 0 + ) as [Provider, string][]; + + setValidatingKeys(new Set(providersToValidate.map(([provider]) => provider))); + + const results: Record = {}; + + await Promise.all( + providersToValidate.map(async ([provider, apiKey]) => { + try { + results[provider] = await validateAPIKeyService(provider, apiKey); + } catch { + results[provider] = { isValid: false, error: 'Validation failed' }; + } + }) + ); + + setApiValidationResults(results); + setValidatingKeys(new Set()); + + return results; + }, []); + const onSubmit = useCallback( - (values: FormValues) => { + async (values: FormValues) => { + // First validate with APIs + const validationResults = await validateWithAPI(values); + + // Check if any validation failed + const hasErrors = Object.values(validationResults).some(result => !result.isValid); + + if (hasErrors) { + toast.error('Please fix API key errors before saving'); + return; + } + + // Only save if all validations pass setKeys(values); toast.success('API keys saved successfully'); }, - [setKeys] + [setKeys, validateWithAPI] ); return ( @@ -81,6 +138,8 @@ const Form = () => { placeholder="AIza..." register={register} error={errors.google} + apiValidation={apiValidationResults.google} + isValidating={validatingKeys.has('google')} required /> @@ -92,6 +151,8 @@ const Form = () => { placeholder="sk-or-..." register={register} error={errors.openrouter} + apiValidation={apiValidationResults.openrouter} + isValidating={validatingKeys.has('openrouter')} /> { placeholder="sk-..." register={register} error={errors.openai} + apiValidation={apiValidationResults.openai} + isValidating={validatingKeys.has('openai')} />