From ec5bc1d2139b1519a793161f85f4bea33d41c951 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Wed, 31 Dec 2025 08:10:57 +0800 Subject: [PATCH] fix: add OpenAI API key validation in settings page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add validation for OpenAI API key format in the Models settings component: - Add validateOpenAIKey function to utils.tsx that validates keys start with "sk-" and are at least 20 characters - Add validation state and error handling in Models.tsx - Display validation error message using CustomInput's error props - Prevent form submission when validation fails - Clear validation errors when user modifies the input Closes #53 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 Signed-off-by: majiayu000 <1835304752@qq.com> --- .../app/settings/SettingsOptions/Models.tsx | 30 +++++++++++++++++++ gui/src/app/utils.tsx | 7 +++++ 2 files changed, 37 insertions(+) diff --git a/gui/src/app/settings/SettingsOptions/Models.tsx b/gui/src/app/settings/SettingsOptions/Models.tsx index 86e3ef0c..de6f07f5 100644 --- a/gui/src/app/settings/SettingsOptions/Models.tsx +++ b/gui/src/app/settings/SettingsOptions/Models.tsx @@ -6,9 +6,13 @@ import toast from 'react-hot-toast'; import CustomInput from '@/components/CustomInput/CustomInput'; import imagePath from '@/app/imagePath'; import { ModelsList } from '../../../../types/settingTypes'; +import { validateOpenAIKey } from '@/app/utils'; export default function Models() { const [modelsList, setModelsList] = useState(null); + const [validationErrors, setValidationErrors] = useState< + Record + >({}); const modelDetails = { 'gpt-4o': { text: 'Open AI API Key (gpt-4o)', icon: imagePath.openAIIcon }, 'claude-3': { @@ -17,7 +21,24 @@ export default function Models() { }, }; + const validateApiKeys = (): boolean => { + const errors: Record = {}; + + modelsList?.forEach((model) => { + if (model.model_name === 'gpt-4o' && !validateOpenAIKey(model.api_key)) { + errors[model.model_name] = + 'Invalid OpenAI API key. Key must start with "sk-" and be at least 20 characters.'; + } + }); + + setValidationErrors(errors); + return Object.keys(errors).length === 0; + }; + const handleButtonClick = () => { + if (!validateApiKeys()) { + return; + } toCreateOrUpdateLLMAPIKey().then().catch(); }; @@ -30,6 +51,13 @@ export default function Models() { : model, ) || null, ); + if (validationErrors[model_name]) { + setValidationErrors((prev) => { + const newErrors = { ...prev }; + delete newErrors[model_name]; + return newErrors; + }); + } }; useEffect(() => { @@ -94,6 +122,8 @@ export default function Models() { icon={model.icon} iconCSS={'size-4'} alt={`${model.model_name}_icon`} + isError={!!validationErrors[model.model_name]} + errorMessage={validationErrors[model.model_name]} /> ))} diff --git a/gui/src/app/utils.tsx b/gui/src/app/utils.tsx index f07633a1..e1dbe312 100644 --- a/gui/src/app/utils.tsx +++ b/gui/src/app/utils.tsx @@ -179,6 +179,13 @@ export function validateEmail(email: string) { return emailRegex.test(email); } +export function validateOpenAIKey(apiKey: string): boolean { + if (!apiKey || apiKey.trim() === '') { + return true; + } + return apiKey.startsWith('sk-') && apiKey.length >= 20; +} + export function handleStoryInReviewIssue(data: { story: { reason: any }; }): StoryInReviewIssue {