forked from cardosofelipe/fast-next-template
fix(agents): properly initialize form with API data defaults
Root cause: The demo data's model_params was missing `top_p`, but the Zod schema required all three fields (temperature, max_tokens, top_p). This caused silent validation failures when editing agent types. Fixes: 1. Add getInitialValues() that ensures all required fields have defaults 2. Handle nested validation errors in handleFormError (e.g., model_params.top_p) 3. Add useEffect to reset form when agentType changes 4. Add console.error logging for debugging validation failures 5. Update demo data to include top_p in all agent types The form now properly initializes with safe defaults for any missing fields from the API response, preventing silent validation failures. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,8 @@
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 4096
|
||||
"max_tokens": 4096,
|
||||
"top_p": 0.95
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base"],
|
||||
"tool_permissions": {
|
||||
@@ -29,7 +30,8 @@
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 8192
|
||||
"max_tokens": 8192,
|
||||
"top_p": 0.95
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base"],
|
||||
"tool_permissions": {
|
||||
@@ -49,7 +51,8 @@
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.6,
|
||||
"max_tokens": 8192
|
||||
"max_tokens": 8192,
|
||||
"top_p": 0.95
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
|
||||
"tool_permissions": {
|
||||
@@ -69,7 +72,8 @@
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.3,
|
||||
"max_tokens": 16384
|
||||
"max_tokens": 16384,
|
||||
"top_p": 0.95
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
|
||||
"tool_permissions": {
|
||||
@@ -89,7 +93,8 @@
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.4,
|
||||
"max_tokens": 8192
|
||||
"max_tokens": 8192,
|
||||
"top_p": 0.95
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
|
||||
"tool_permissions": {
|
||||
@@ -109,7 +114,8 @@
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.4,
|
||||
"max_tokens": 8192
|
||||
"max_tokens": 8192,
|
||||
"top_p": 0.95
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
|
||||
"tool_permissions": {
|
||||
|
||||
@@ -57,28 +57,37 @@ export function AgentTypeForm({
|
||||
const [activeTab, setActiveTab] = useState('basic');
|
||||
const [expertiseInput, setExpertiseInput] = useState('');
|
||||
|
||||
// Build initial values with proper defaults for missing fields
|
||||
const getInitialValues = useCallback((): AgentTypeCreateFormValues => {
|
||||
if (!agentType) return defaultAgentTypeValues;
|
||||
|
||||
// Ensure model_params has all required fields with proper defaults
|
||||
const modelParams = agentType.model_params ?? {};
|
||||
const safeModelParams = {
|
||||
temperature: typeof modelParams.temperature === 'number' ? modelParams.temperature : 0.7,
|
||||
max_tokens: typeof modelParams.max_tokens === 'number' ? modelParams.max_tokens : 8192,
|
||||
top_p: typeof modelParams.top_p === 'number' ? modelParams.top_p : 0.95,
|
||||
};
|
||||
|
||||
return {
|
||||
name: agentType.name,
|
||||
slug: agentType.slug,
|
||||
description: agentType.description,
|
||||
expertise: agentType.expertise ?? [],
|
||||
personality_prompt: agentType.personality_prompt ?? '',
|
||||
primary_model: agentType.primary_model ?? 'claude-opus-4-5-20251101',
|
||||
fallback_models: agentType.fallback_models ?? [],
|
||||
model_params: safeModelParams,
|
||||
mcp_servers: agentType.mcp_servers ?? [],
|
||||
tool_permissions: agentType.tool_permissions ?? {},
|
||||
is_active: agentType.is_active ?? false,
|
||||
};
|
||||
}, [agentType]);
|
||||
|
||||
// Always use create schema for validation - editing requires all fields too
|
||||
const form = useForm<AgentTypeCreateFormValues>({
|
||||
resolver: zodResolver(agentTypeCreateSchema),
|
||||
defaultValues: agentType
|
||||
? {
|
||||
name: agentType.name,
|
||||
slug: agentType.slug,
|
||||
description: agentType.description,
|
||||
expertise: agentType.expertise,
|
||||
personality_prompt: agentType.personality_prompt,
|
||||
primary_model: agentType.primary_model,
|
||||
fallback_models: agentType.fallback_models,
|
||||
model_params: (agentType.model_params ?? {
|
||||
temperature: 0.7,
|
||||
max_tokens: 8192,
|
||||
top_p: 0.95,
|
||||
}) as AgentTypeCreateFormValues['model_params'],
|
||||
mcp_servers: agentType.mcp_servers,
|
||||
tool_permissions: agentType.tool_permissions,
|
||||
is_active: agentType.is_active,
|
||||
}
|
||||
: defaultAgentTypeValues,
|
||||
defaultValues: getInitialValues(),
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -90,29 +99,58 @@ export function AgentTypeForm({
|
||||
formState: { errors },
|
||||
} = form;
|
||||
|
||||
// Helper to extract first error message from nested errors
|
||||
const getFirstErrorMessage = useCallback(
|
||||
(formErrors: FieldErrors<AgentTypeCreateFormValues>): { field: string; message: string } => {
|
||||
for (const key of Object.keys(formErrors)) {
|
||||
const error = formErrors[key as keyof typeof formErrors];
|
||||
if (!error) continue;
|
||||
|
||||
// Handle nested errors (like model_params.temperature)
|
||||
if (typeof error === 'object' && !('message' in error)) {
|
||||
for (const nestedKey of Object.keys(error)) {
|
||||
const nestedError = (error as Record<string, { message?: string }>)[nestedKey];
|
||||
if (nestedError?.message) {
|
||||
return { field: `${key}.${nestedKey}`, message: nestedError.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle direct error
|
||||
if ('message' in error && error.message) {
|
||||
return { field: key, message: error.message as string };
|
||||
}
|
||||
}
|
||||
return { field: 'unknown', message: 'Validation error' };
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Handle form validation errors - show toast with first error
|
||||
const handleFormError = useCallback((formErrors: FieldErrors<AgentTypeCreateFormValues>) => {
|
||||
// Find the first error and show it
|
||||
const firstErrorKey = Object.keys(formErrors)[0] as keyof AgentTypeCreateFormValues;
|
||||
if (firstErrorKey) {
|
||||
const error = formErrors[firstErrorKey];
|
||||
const message = error && 'message' in error ? error.message : 'Validation error';
|
||||
const handleFormError = useCallback(
|
||||
(formErrors: FieldErrors<AgentTypeCreateFormValues>) => {
|
||||
// Log for debugging
|
||||
console.error('[AgentTypeForm] Validation errors:', formErrors);
|
||||
|
||||
const { field, message } = getFirstErrorMessage(formErrors);
|
||||
toast.error('Please fix form errors', {
|
||||
description: `${String(firstErrorKey)}: ${message}`,
|
||||
description: `${field}: ${message}`,
|
||||
});
|
||||
|
||||
// Navigate to the tab containing the error
|
||||
if (['name', 'slug', 'description', 'expertise', 'is_active'].includes(firstErrorKey)) {
|
||||
const topLevelField = field.split('.')[0];
|
||||
if (['name', 'slug', 'description', 'expertise', 'is_active'].includes(topLevelField)) {
|
||||
setActiveTab('basic');
|
||||
} else if (['primary_model', 'fallback_models', 'model_params'].includes(firstErrorKey)) {
|
||||
} else if (['primary_model', 'fallback_models', 'model_params'].includes(topLevelField)) {
|
||||
setActiveTab('model');
|
||||
} else if (['mcp_servers', 'tool_permissions'].includes(firstErrorKey)) {
|
||||
} else if (['mcp_servers', 'tool_permissions'].includes(topLevelField)) {
|
||||
setActiveTab('permissions');
|
||||
} else if (firstErrorKey === 'personality_prompt') {
|
||||
} else if (topLevelField === 'personality_prompt') {
|
||||
setActiveTab('personality');
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
},
|
||||
[getFirstErrorMessage]
|
||||
);
|
||||
|
||||
const watchName = watch('name');
|
||||
/* istanbul ignore next -- defensive fallback, expertise always has default */
|
||||
@@ -120,6 +158,14 @@ export function AgentTypeForm({
|
||||
/* istanbul ignore next -- defensive fallback, mcp_servers always has default */
|
||||
const watchMcpServers = watch('mcp_servers') || [];
|
||||
|
||||
// Reset form when agentType changes (e.g., switching to edit mode)
|
||||
useEffect(() => {
|
||||
if (agentType) {
|
||||
const values = getInitialValues();
|
||||
form.reset(values);
|
||||
}
|
||||
}, [agentType?.id, form, getInitialValues]);
|
||||
|
||||
// Auto-generate slug from name for new agent types
|
||||
useEffect(() => {
|
||||
if (!isEditing && watchName) {
|
||||
|
||||
Reference in New Issue
Block a user