Skip to content

Commit 167416b

Browse files
committed
Add input validation error indicator
1 parent 8bf9095 commit 167416b

1 file changed

Lines changed: 33 additions & 9 deletions

File tree

webapp/_webapp/src/views/settings/sections/api-key-settings.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,17 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
116116
const [inputPrice, setInputPrice] = useState<number>(customModel?.inputPrice || 0);
117117
const [outputPrice, setOutputPrice] = useState<number>(customModel?.outputPrice || 0);
118118
const [modelName, setModelName] = useState(customModel?.name || "");
119+
const [isModelNameValid, setIsModelNameValid] = useState(true);
120+
const [isSlugValid, setIsSlugValid] = useState(true);
121+
const [isBaseUrlValid, setIsBaseUrlValid] = useState(true);
122+
const [isApiKeyValid, setIsApiKeyValid] = useState(true);
119123

120124
const borderedInputClassName = "rnd-cancel px-2 py-1 border !border-gray-200 dark:!border-default-200 rounded-md";
121125
const baseClassName = "bg-transparent p-1 focus:outline-none disabled:opacity-70 disabled:cursor-not-allowed";
122126
const modelNameInputClassName = `${baseClassName} ${isEditing || isNew ? borderedInputClassName : ""} text-sm text-default-900 font-medium flex-1 truncate mr-1`;
123127
const labelClassName = `${baseClassName} text-xs text-default-900 w-auto`;
124128
const detailInputClassName = `${baseClassName} ${isEditing || isNew ? borderedInputClassName : ""} flex-1 noselect focus:outline-none text-xs text-default-700 placeholder:text-default-400`;
129+
const errorInputClassName = "!border-red-500 focus:!border-red-500";
125130

126131
const handleOnChange = (isDelete: boolean) => {
127132
if (
@@ -130,6 +135,10 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
130135
baseUrl.trim().length < 1 ||
131136
apiKey.trim().length < 1
132137
) {
138+
setIsModelNameValid(modelName.trim().length > 0);
139+
setIsSlugValid(slug.trim().length > 0);
140+
setIsBaseUrlValid(baseUrl.trim().length > 0);
141+
setIsApiKeyValid(apiKey.trim().length > 0);
133142
return;
134143
}
135144

@@ -157,19 +166,24 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
157166
setMaxOutput(0);
158167
setInputPrice(0);
159168
setOutputPrice(0);
169+
} else {
170+
setIsEditing(false);
160171
}
161172
};
162173

163174
return (
164175
<div className="flex flex-col w-full pl-1">
165176
<div className="flex flex-row justify-between">
166177
<input
167-
className={modelNameInputClassName}
178+
className={`${modelNameInputClassName} ${!isModelNameValid && errorInputClassName}`}
168179
value={modelName}
169180
placeholder="My Model"
170181
type="text"
171182
disabled={!isEditing}
172-
onChange={(e) => setModelName(e.target.value)}
183+
onChange={(e) => {
184+
setIsModelNameValid(true);
185+
setModelName(e.target.value);
186+
}}
173187
></input>
174188

175189
{isNew ? (
@@ -185,8 +199,9 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
185199
onClick={() => {
186200
if (isEditing) {
187201
handleOnChange(false);
202+
} else {
203+
setIsEditing(true);
188204
}
189-
setIsEditing((i) => !i);
190205
}}
191206
className="p-1 hover:bg-default-100 rounded"
192207
>
@@ -205,36 +220,45 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
205220
<div className="flex flex-row mt-[4px]">
206221
<label className={labelClassName}>Slug</label>
207222
<input
208-
className={detailInputClassName}
223+
className={`${detailInputClassName} ${!isSlugValid && errorInputClassName}`}
209224
value={slug}
210225
placeholder="e.g., gemini-2.5-flash"
211226
type="text"
212227
disabled={!isEditing}
213-
onChange={(e) => setSlug(e.target.value)}
228+
onChange={(e) => {
229+
setIsSlugValid(true);
230+
setSlug(e.target.value);
231+
}}
214232
/>
215233
</div>
216234

217235
<div className="flex flex-row mt-[4px]">
218236
<label className={labelClassName}>Base URL</label>
219237
<input
220-
className={detailInputClassName}
238+
className={`${detailInputClassName} ${!isBaseUrlValid && errorInputClassName}`}
221239
value={baseUrl}
222240
placeholder="An OpenAI-compatible endpoint"
223241
type="text"
224242
disabled={!isEditing}
225-
onChange={(e) => setBaseUrl(e.target.value)}
243+
onChange={(e) => {
244+
setIsBaseUrlValid(true);
245+
setBaseUrl(e.target.value);
246+
}}
226247
/>
227248
</div>
228249

229250
<div className="flex flex-row mt-[4px]">
230251
<label className={labelClassName}>API Key</label>
231252
<input
232-
className={detailInputClassName}
253+
className={`${detailInputClassName} ${!isApiKeyValid && errorInputClassName}`}
233254
value={apiKey}
234255
placeholder="Your API Key"
235256
type={!isEditing && !isNew ? "password" : "text"}
236257
disabled={!isEditing}
237-
onChange={(e) => setApiKey(e.target.value)}
258+
onChange={(e) => {
259+
setIsApiKeyValid(true);
260+
setApiKey(e.target.value);
261+
}}
238262
/>
239263
</div>
240264

0 commit comments

Comments
 (0)