Modern React application for the AI Virtual Agent Quickstart, providing an intuitive interface for managing AI agents, knowledge bases, and real-time chat interactions.
The frontend is built with modern React patterns and follows a clean, hook-based architecture:
src/
├── components/ # Reusable UI components (now using custom hooks)
│ ├── chat.tsx # Main chat interface with PatternFly Chatbot
│ ├── agent-*.tsx # Agent management components
│ ├── knowledge-base-*.tsx # Knowledge base management
│ └── ...
├── hooks/ # Custom React hooks (data fetching & state management)
│ ├── index.ts # Central export for all hooks
│ ├── useChat.ts # Chat functionality with SSE streaming
│ ├── useAgents.ts # Agent operations (CRUD + state)
│ ├── useKnowledgeBases.ts # Knowledge base operations
│ └── ... # More specialized hooks
├── services/ # API service layer (pure functions)
│ ├── agents.ts # Agent API calls
│ ├── knowledge-bases.ts # Knowledge base API calls
│ ├── chat-sessions.ts # Session API calls
│ └── ... # More service modules
├── routes/ # TanStack Router configuration
│ ├── index.tsx # Main chat page
│ └── config/ # Configuration pages
├── contexts/ # React contexts
│ └── UserContext.tsx # User authentication context
└── types/ # TypeScript type definitions
- React 18 - Core framework with modern hooks
- TypeScript - Type safety and development experience
- PatternFly - Enterprise-grade UI component library
- TanStack Router - Type-safe routing
- TanStack Query - Server state management with caching
- Vite - Build tool and development server
- Visual Agent Creation: Form-based agent configuration with real-time validation
- Tool Integration: Multi-select interface for tools (RAG, web search, MCP servers)
- Model Selection: Dynamic model dropdown with real-time availability
- Advanced Parameters: Collapsible accordion for sampling parameters
- Streaming Responses: Server-Sent Events for real-time message streaming
- Session Management: Persistent chat sessions with history sidebar
- Multiple Message Types: Support for text, tool usage, and error messages
- PatternFly Integration: Enterprise-grade chat UI components
- Status Tracking: Real-time status display (READY, PENDING, ORPHANED)
- Drag & Drop Upload: Intuitive file upload for document ingestion
- S3 Integration: Configure external document sources
- Progress Monitoring: Track ingestion pipeline progress
- User Profiles: User authentication and role management
- MCP Servers: External tool server configuration
- Model Servers: LLM provider management
- Guardrails: Safety shield configuration
- Node.js 18+ - Runtime environment
- npm - Package manager
-
Navigate to frontend directory:
cd frontend -
Install dependencies:
npm install
-
Start development server:
npm run dev
The application will be available at
http://localhost:5173
# Development
npm run dev # Start development server with hot reload
npm run build # Build for production
npm run preview # Preview production build
# Code Quality
npm run lint # ESLint code analysis
npm run lint:fix # Auto-fix linting issues
npm run format # Format code with Prettier
npm run format:check # Check code formatting
# Type Checking
npm run type-check # TypeScript compilation checkOur frontend uses a comprehensive set of custom hooks that encapsulate data fetching, state management, and business logic. This approach provides:
- 🔄 Consistent API: All hooks follow the same pattern
- 🎯 No Prop Drilling: Components access data directly
- 🧹 Clean Components: UI logic separated from data logic
- 🔒 Type Safety: Full TypeScript support
- ⚡ Performance: Automatic caching and optimization
All custom hooks follow a consistent interface:
const {
// Data
data,
isLoading,
error,
// Mutations
createItem,
deleteItem,
updateItem,
// Mutation states
isCreating,
isDeleting,
createError,
deleteError,
// Utilities
refreshData,
} = useCustomHook();Complete agent management:
export function AgentManagement() {
const {
agents, // Agent[]
isLoading, // boolean
error, // Error | null
createAgent, // (data: NewAgent) => Promise<Agent>
deleteAgent, // (id: string) => Promise<void>
isCreating, // boolean
isDeleting, // boolean
refreshAgents // () => void
} = useAgents();
return (
<div>
{isLoading && <Spinner />}
{agents?.map(agent => (
<AgentCard
key={agent.id}
agent={agent}
onDelete={() => deleteAgent(agent.id)}
/>
))}
</div>
);
}Knowledge base operations with status tracking:
export function KnowledgeBaseList() {
const {
knowledgeBases, // KnowledgeBaseWithStatus[]
isLoading, // boolean
createKnowledgeBase, // (data) => Promise<KnowledgeBase>
deleteKnowledgeBase, // (id) => Promise<void>
llamaStackKnowledgeBases, // LSKnowledgeBase[]
} = useKnowledgeBases();
// Component uses data directly, no prop drilling
}Real-time chat with streaming:
export function Chat() {
const {
messages, // ChatMessage[]
input, // string
isLoading, // boolean
sendMessage, // (content: string) => void
handleInputChange, // (event) => void
handleSubmit, // (event) => void
loadSession, // (sessionId: string) => Promise<void>
sessionId, // string | null
} = useChat(agentId);
// Real-time streaming, session management, all handled
}Components are now clean and focused on UI logic:
// ✅ Clean component using custom hooks
export function AgentList() {
// Single hook call provides everything needed
const { agents, isLoading, error } = useAgents();
if (isLoading) return <Spinner />;
if (error) return <Alert variant="danger">{error.message}</Alert>;
return (
<Flex direction={{ default: 'column' }}>
{agents?.map(agent => (
<AgentCard key={agent.id} agent={agent} />
))}
</Flex>
);
}Forms use hooks directly instead of complex prop passing:
// ✅ AgentForm manages its own data dependencies
export function AgentForm({ onSubmit, isSubmitting, onCancel }: AgentFormProps) {
// All data fetched internally
const { models, isLoadingModels, modelsError } = useModels();
const { llamaStackKnowledgeBases, isLoadingLlamaStack } = useKnowledgeBases();
const { tools, isLoading: isLoadingTools } = useTools();
const { shields, isLoading: isLoadingShields } = useShields();
// No more complex prop interfaces needed
return <Form>...</Form>;
}Our custom hooks wrap TanStack Query for optimal caching:
// Inside useAgents.ts
export const useAgents = () => {
const queryClient = useQueryClient();
// Automatic caching and background updates
const agentsQuery = useQuery<Agent[], Error>({
queryKey: ['agents'],
queryFn: fetchAgents,
});
// Optimistic updates with proper error handling
const createAgentMutation = useMutation<Agent, Error, NewAgent>({
mutationFn: createAgent,
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ['agents'] });
},
});
return {
agents: agentsQuery.data,
isLoading: agentsQuery.isLoading,
error: agentsQuery.error,
createAgent: createAgentMutation.mutateAsync,
isCreating: createAgentMutation.isPending,
};
};Pure API functions with no React dependencies:
// services/agents.ts - Pure functions
export const fetchAgents = async (): Promise<Agent[]> => {
const response = await fetch(AGENTS_API_ENDPOINT);
if (!response.ok) throw new Error('Network response was not ok');
return response.json() as Agent[];
};
export const createAgent = async (newAgent: NewAgent): Promise<Agent> => {
const response = await fetch(AGENTS_API_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newAgent),
});
if (!response.ok) throw new Error('Network response was not ok');
return response.json() as Agent[];
};The main chat interface using PatternFly's Chatbot component:
// Real-time streaming chat with session management
export function Chat() {
const {
messages,
sendMessage,
isLoading,
loadSession
} = useChat(selectedAgent);
// PatternFly Chatbot integration
return (
<Chatbot displayMode={ChatbotDisplayMode.embedded}>
<ChatbotContent>
<MessageBox messages={patternflyMessages} />
</ChatbotContent>
<ChatbotFooter>
<MessageBar onSendMessage={sendMessage} />
</ChatbotFooter>
</Chatbot>
);
}Self-contained form with internal data management:
export function AgentForm({ onSubmit, isSubmitting, onCancel }: AgentFormProps) {
// All dependencies managed internally
const { models, isLoadingModels, modelsError } = useModels();
const { llamaStackKnowledgeBases, isLoadingLlamaStack } = useKnowledgeBases();
const { tools, isLoading: isLoadingTools } = useTools();
const { shields, isLoading: isLoadingShields } = useShields();
// Form logic...
return <Form>...</Form>;
}interface Agent {
id: string;
name: string;
model_name: string;
prompt: string;
knowledge_base_ids: string[];
tools: ToolAssociationInfo[];
sampling_strategy: 'greedy' | 'top-p' | 'top-k';
// ... other properties
}
interface ChatMessage {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: Date;
}// Built into every hook
const { data, isLoading, error } = useAgents();
if (error) {
return <Alert variant="danger">{error.message}</Alert>;
}- Automatic Caching: TanStack Query handles intelligent caching
- Background Updates: Stale data updates in background
- Optimistic Updates: Immediate UI feedback
- Code Splitting: Route-based and component-based splitting
// ✅ Use hooks at component level
export function Component() {
const { data, isLoading, createItem } = useCustomHook();
// Avoid prop drilling
return <ChildComponent onAction={createItem} />;
}
// ✅ Handle loading and error states
export function Component() {
const { data, isLoading, error } = useCustomHook();
if (isLoading) return <Spinner />;
if (error) return <Alert variant="danger">{error.message}</Alert>;
if (!data) return <EmptyState />;
return <DataComponent data={data} />;
}interface ComponentProps {
// Minimal props - data comes from hooks
onAction?: () => void;
variant?: 'primary' | 'secondary';
}
export function Component({ onAction, variant = 'primary' }: ComponentProps) {
// 1. Hooks at the top
const { data, isLoading, error, performAction } = useCustomHook();
const [localState, setLocalState] = useState();
// 2. Event handlers
const handleAction = useCallback(() => {
void performAction();
onAction?.();
}, [performAction, onAction]);
// 3. Early returns for loading/error states
if (isLoading) return <Spinner />;
if (error) return <Alert variant="danger">{error.message}</Alert>;
// 4. Main render
return (
<PatternFlyComponent variant={variant}>
{data?.map(item => (
<ItemComponent key={item.id} item={item} onAction={handleAction} />
))}
</PatternFlyComponent>
);
}- TypeScript Strict Mode: Full type checking enabled
- ESLint Rules: React, TypeScript, and accessibility rules
- Prettier Formatting: Consistent code formatting
- Promise Handling: All promises properly handled with
voidor.catch()
- Hook Testing:
@testing-library/react-hooks - Component Testing: React Testing Library
- E2E Testing: Playwright or Cypress
- API Mocking: MSW for service layer testing
Hook dependency warnings:
# Check for missing dependencies in useCallback/useEffect
npm run lintType errors:
# Run type checking
npm run type-check
# Check for TypeScript configuration issues
npx tsc --noEmitPromise handling errors:
# All async operations should use void or proper error handling
void asyncOperation(); // For fire-and-forget
await asyncOperation().catch(handleError); // With error handlingBrowser Extensions:
- React Developer Tools: Component and hook debugging
- TanStack Query DevTools: Server state inspection
- Vite DevTools: Build analysis
VSCode Extensions:
- TypeScript Importer: Automatic import organization
- ESLint: Real-time code analysis
- Prettier: Automatic formatting
- Error Lens: Inline error display