forked from cardosofelipe/fast-next-template
feat(frontend): implement agent configuration pages (#41)
- Add agent types list page with search and filter functionality - Add agent type detail/edit page with tabbed interface - Create AgentTypeForm component with React Hook Form + Zod validation - Implement model configuration (temperature, max tokens, top_p) - Add MCP permission management with checkboxes - Include personality prompt editor textarea - Create TanStack Query hooks for agent-types API - Add useDebounce hook for search optimization - Comprehensive unit tests for all components (68 tests) Components: - AgentTypeList: Grid view with status badges, expertise tags - AgentTypeDetail: Full detail view with model config, MCP permissions - AgentTypeForm: Create/edit with 4 tabs (Basic, Model, Permissions, Personality) Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
194
frontend/src/app/[locale]/(authenticated)/agents/[id]/page.tsx
Normal file
194
frontend/src/app/[locale]/(authenticated)/agents/[id]/page.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Agent Type Detail/Edit Page
|
||||
*
|
||||
* Displays agent type details with options to edit, duplicate, or deactivate.
|
||||
* Handles 'new' as a special ID to show the create form.
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { toast } from 'sonner';
|
||||
import { AgentTypeDetail, AgentTypeForm } from '@/components/agents';
|
||||
import {
|
||||
useAgentType,
|
||||
useCreateAgentType,
|
||||
useUpdateAgentType,
|
||||
useDeactivateAgentType,
|
||||
useDuplicateAgentType,
|
||||
} from '@/lib/api/hooks/useAgentTypes';
|
||||
import type { AgentTypeCreateFormValues } from '@/lib/validations/agentType';
|
||||
|
||||
type ViewMode = 'detail' | 'edit' | 'create';
|
||||
|
||||
export default function AgentTypeDetailPage() {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const id = params.id as string;
|
||||
|
||||
// Determine initial view mode
|
||||
const isNew = id === 'new';
|
||||
const [viewMode, setViewMode] = useState<ViewMode>(isNew ? 'create' : 'detail');
|
||||
|
||||
// Fetch agent type data (skip if creating new)
|
||||
const {
|
||||
data: agentType,
|
||||
isLoading,
|
||||
error,
|
||||
} = useAgentType(isNew ? null : id);
|
||||
|
||||
// Mutations
|
||||
const createMutation = useCreateAgentType();
|
||||
const updateMutation = useUpdateAgentType();
|
||||
const deactivateMutation = useDeactivateAgentType();
|
||||
const duplicateMutation = useDuplicateAgentType();
|
||||
|
||||
// Handle navigation back to list
|
||||
const handleBack = useCallback(() => {
|
||||
if (viewMode === 'edit') {
|
||||
setViewMode('detail');
|
||||
} else {
|
||||
router.push('/agents');
|
||||
}
|
||||
}, [router, viewMode]);
|
||||
|
||||
// Handle edit button click
|
||||
const handleEdit = useCallback(() => {
|
||||
setViewMode('edit');
|
||||
}, []);
|
||||
|
||||
// Handle form submission for create/update
|
||||
const handleSubmit = useCallback(
|
||||
async (data: AgentTypeCreateFormValues) => {
|
||||
try {
|
||||
if (isNew || viewMode === 'create') {
|
||||
// Create new agent type
|
||||
const result = await createMutation.mutateAsync({
|
||||
name: data.name,
|
||||
slug: data.slug,
|
||||
description: data.description,
|
||||
expertise: data.expertise,
|
||||
personality_prompt: data.personality_prompt,
|
||||
primary_model: data.primary_model,
|
||||
fallback_models: data.fallback_models,
|
||||
model_params: data.model_params,
|
||||
mcp_servers: data.mcp_servers,
|
||||
tool_permissions: data.tool_permissions,
|
||||
is_active: data.is_active,
|
||||
});
|
||||
toast.success('Agent type created', {
|
||||
description: `${result.name} has been created successfully`,
|
||||
});
|
||||
router.push(`/agents/${result.id}`);
|
||||
} else {
|
||||
// Update existing agent type
|
||||
const result = await updateMutation.mutateAsync({
|
||||
id,
|
||||
data: {
|
||||
name: data.name,
|
||||
slug: data.slug,
|
||||
description: data.description,
|
||||
expertise: data.expertise,
|
||||
personality_prompt: data.personality_prompt,
|
||||
primary_model: data.primary_model,
|
||||
fallback_models: data.fallback_models,
|
||||
model_params: data.model_params,
|
||||
mcp_servers: data.mcp_servers,
|
||||
tool_permissions: data.tool_permissions,
|
||||
is_active: data.is_active,
|
||||
},
|
||||
});
|
||||
toast.success('Agent type updated', {
|
||||
description: `${result.name} has been updated successfully`,
|
||||
});
|
||||
setViewMode('detail');
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'An error occurred';
|
||||
toast.error('Failed to save agent type', { description: message });
|
||||
}
|
||||
},
|
||||
[id, isNew, viewMode, createMutation, updateMutation, router]
|
||||
);
|
||||
|
||||
// Handle duplicate
|
||||
const handleDuplicate = useCallback(async () => {
|
||||
if (!agentType) return;
|
||||
|
||||
try {
|
||||
const result = await duplicateMutation.mutateAsync(agentType);
|
||||
toast.success('Agent type duplicated', {
|
||||
description: `${result.name} has been created`,
|
||||
});
|
||||
router.push(`/agents/${result.id}`);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'An error occurred';
|
||||
toast.error('Failed to duplicate agent type', { description: message });
|
||||
}
|
||||
}, [agentType, duplicateMutation, router]);
|
||||
|
||||
// Handle deactivate
|
||||
const handleDeactivate = useCallback(async () => {
|
||||
try {
|
||||
await deactivateMutation.mutateAsync(id);
|
||||
toast.success('Agent type deactivated', {
|
||||
description: 'The agent type has been deactivated',
|
||||
});
|
||||
router.push('/agents');
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'An error occurred';
|
||||
toast.error('Failed to deactivate agent type', { description: message });
|
||||
}
|
||||
}, [id, deactivateMutation, router]);
|
||||
|
||||
// Handle cancel from form
|
||||
const handleCancel = useCallback(() => {
|
||||
if (isNew) {
|
||||
router.push('/agents');
|
||||
} else {
|
||||
setViewMode('detail');
|
||||
}
|
||||
}, [isNew, router]);
|
||||
|
||||
// Show error state
|
||||
if (error && !isNew) {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-6">
|
||||
<AgentTypeDetail
|
||||
agentType={null}
|
||||
onBack={handleBack}
|
||||
onEdit={handleEdit}
|
||||
onDuplicate={handleDuplicate}
|
||||
onDeactivate={handleDeactivate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Render based on view mode
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-6">
|
||||
{(viewMode === 'create' || viewMode === 'edit') && (
|
||||
<AgentTypeForm
|
||||
agentType={viewMode === 'edit' ? agentType ?? undefined : undefined}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
isSubmitting={createMutation.isPending || updateMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
{viewMode === 'detail' && (
|
||||
<AgentTypeDetail
|
||||
agentType={agentType ?? null}
|
||||
isLoading={isLoading}
|
||||
onBack={handleBack}
|
||||
onEdit={handleEdit}
|
||||
onDuplicate={handleDuplicate}
|
||||
onDeactivate={handleDeactivate}
|
||||
isDeactivating={deactivateMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user