ToolConfigurator Component
Overview
The ToolConfigurator component provides individual tool configuration interfaces with advanced validation, field filtering, and specialized input types. It works as a companion to the ToolSelector Component to handle the detailed configuration of each tool’s specific parameters.
Location: src/components/tools/ToolConfigurator.tsx
Dependencies: Tag-based access control, Firebase authentication, UI components
Lines of Code: ~547
Core Functionality
Configuration Management
- Dynamic field rendering based on tool requirements
- Tag-based field filtering for access control
- Real-time validation with required field checking
- Specialized input types including assistant multiselect
- Conditional field display based on other field values
Interface Definition
interface ToolConfiguratorProps {
toolId: string;
currentConfig?: Record<string, any>;
onConfigUpdate: (toolId: string, config: Record<string, any>) => void;
configFields: FilteredConfigField[];
onToolEnable?: (toolId: string) => void;
availableTags?: Tag[];
availableAssistants?: AssistantDisplay[];
}
Configuration Field Types
Standard Fields
Text Input
{
key: 'customDatastoreId',
label: 'Custom Data Store ID',
type: 'text',
placeholder: 'e.g. my-custom-datastore',
required: false
}
Select Dropdown
{
key: 'datastore_id',
label: 'Data Store ID from Vertex AI Search',
type: 'select',
options: [
{ label: 'Aitana Public Welcome', value: 'aitana_public_welcome' },
{ label: 'Custom Data Store', value: 'custom' }
],
required: true
}
Checkbox
{
key: 'no-stream',
label: 'Disable streaming',
type: 'checkbox',
placeholder: 'Disable intermediate answers',
required: false
}
Radio Buttons
{
key: 'call_strategy',
label: 'Calling Strategy',
type: 'radio',
defaultValue: 'parallel',
options: [
{ label: 'Parallel - Call all assistants simultaneously', value: 'parallel' },
{ label: 'Sequential - Call assistants in order', value: 'sequential' }
]
}
Specialized Fields
Assistant Multiselect
The most advanced field type, providing a sophisticated interface for selecting and ordering multiple AI assistants:
{
key: 'selected_assistants',
label: 'Select Assistants to Call',
type: 'assistant_multiselect',
placeholder: 'Choose from your accessible assistants',
required: true
}
Features:
- Visual assistant cards with avatars and names
- Drag-and-drop ordering for sequential calls
- Real-time strategy indication (parallel vs sequential)
- Automatic validation for minimum selections
Tag-Based Access Control
Configuration fields support tag-based filtering to show/hide options based on user permissions:
interface TagRestrictedOption {
label: string;
value: string;
tags?: string[]; // Required tags to see this option
}
// Example with tag restrictions
options: [
{ label: 'Public Data Store', value: 'public' },
{ label: 'Admin Data Store', value: 'admin', tags: ['admin-tools'] },
{ label: 'Beta Feature', value: 'beta', tags: ['beta-testers'] }
] as TagRestrictedOption[]
Tag Color Coding:
admin-tools: Red badgesbeta-testers: Purple badgesfinance-team: Green badgesONE: Blue badgesdev-team: Default gray
Conditional Field Logic
The configurator implements sophisticated conditional field display:
Custom Field Dependencies
// Show custom bucket URL only when 'custom' is selected
if (field.key === 'customBucketUrl' && localConfig.bucketUrl !== 'custom') {
return false;
}
// Show Anthropic web search only for Anthropic models
if (field.key === 'enable_anthropic_web_search' &&
!localConfig.smart_tool?.startsWith('anthropic')) {
return false;
}
Tool-Specific Logic
Document Search Agent Presets:
if (toolId === 'document_search_agent') {
if (processedConfig.preset_config === 'energy_docs') {
processedConfig.bucketUrl = 'aitana-documents-bucket';
processedConfig.datastore_id = 'aitana3';
} else if (processedConfig.preset_config === 'public_demo') {
processedConfig.bucketUrl = 'aitana-public-documents';
processedConfig.datastore_id = 'aitana_public_welcome';
}
}
Validation System
Required Field Validation
const areRequiredFieldsFilled = (): boolean => {
return configFields
.filter(field => field.required)
.every(field => {
const value = localConfig[field.key];
// Checkbox validation
if (field.type === 'checkbox') return true;
// Conditional requirement validation
if (field.key === 'customBucketUrl') {
return localConfig.bucketUrl !== 'custom' ||
(value && value.trim() !== '');
}
// Assistant multiselect validation
if (field.type === 'assistant_multiselect') {
return Array.isArray(value) && value.length > 0;
}
// Standard validation
return value && value.trim() !== '';
});
};
Tool-Specific Validation
Different tools have unique validation requirements:
File Browser: Bucket URL required unless using preset Vertex Search: Datastore ID required unless using custom Aitana Agent: At least one assistant must be selected
Configuration Processing
Save Processing Pipeline
When configurations are saved, they go through a multi-stage processing pipeline:
const handleSave = () => {
let processedConfig = { ...localConfig };
// 1. Handle custom field logic
if (toolId === 'file-browser') {
if (processedConfig.bucketUrl === 'custom') {
processedConfig.bucketUrl = processedConfig.customBucketUrl || '';
}
delete processedConfig.customBucketUrl;
}
// 2. Handle preset configurations
if (toolId === 'document_search_agent') {
// Apply preset configurations
}
// 3. Handle assistant selection
if (toolId === 'aitana-agent') {
processedConfig.assistant_ids = processedConfig.selected_assistants.join(', ');
processedConfig.emissary_configs = buildEmissaryConfigs();
delete processedConfig.selected_assistants;
}
// 4. Update parent and auto-enable tool
onConfigUpdate(toolId, processedConfig);
onToolEnable?.(toolId);
};
Assistant Configuration Building
For multi-agent tools, the configurator builds comprehensive assistant configurations:
const emissaryConfigs = processedConfig.selected_assistants.map((assistantId: string) => {
const assistant = availableAssistants.find(a => a.assistantId === assistantId);
return {
assistant_id: assistantId,
name: assistant.name,
template_id: assistant.templateId,
description: assistant.templateId || 'Custom Assistant',
avatar: assistant.avatar,
owner_email: assistant.ownerEmail,
initial_message: assistant.initialMessage || '',
initial_instructions: assistant.initialInstructions || '',
tools: assistant.tools || [],
tool_configs: assistant.toolConfigs || {},
initial_documents: assistant.initialDocuments || [],
access_control: assistant.accessControl || { type: 'private' },
usage_count: assistant.usageCount || 0,
created_at: assistant.createdAt,
last_accessed_at: assistant.lastAccessedAt
};
});
UI States
Collapsed State
When not editing, shows a minimal status indicator:
<div className="flex items-center justify-between p-2 mt-2 bg-muted rounded-lg">
<div className="text-xs text-muted-foreground">
{isConfigured ? 'Configured' : 'Needs configuration'}
</div>
<Button
variant={isConfigured ? "ghost" : "default"}
size="sm"
onClick={() => setIsEditing(true)}
>
<Settings2 className="h-4 w-4" />
{!isConfigured && <span>Configure</span>}
</Button>
</div>
Expanded State
Shows full configuration interface with all relevant fields:
<Card className="mt-2 overflow-visible">
<CardContent className="p-3 space-y-3 overflow-visible">
{/* Dynamic field rendering */}
{filteredFields.map(field => (
<div key={field.key} className="space-y-1">
<Label className="text-xs">{field.label}</Label>
{/* Field-specific input component */}
</div>
))}
{/* Save/Cancel controls */}
<div className="flex justify-end gap-2 pt-2">
<Button variant="outline" size="sm" onClick={cancel}>
Cancel
</Button>
<Button size="sm" onClick={handleSave} disabled={!areRequiredFieldsFilled()}>
Save
</Button>
</div>
</CardContent>
</Card>
Assistant Multiselect Interface
The most complex UI element, providing comprehensive assistant management:
Available Assistants Section
<ScrollArea className="h-24 border rounded-md p-2">
{availableAssistants.map(assistant => (
<div
key={assistant.assistantId}
className="flex items-center gap-2 p-1.5 rounded border cursor-pointer"
onClick={() => toggleAssistant(assistant.assistantId)}
>
<Avatar className="h-5 w-5">
<AvatarImage src={assistant.avatar} alt={assistant.name} />
<AvatarFallback>{assistant.name[0]}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="text-xs font-medium truncate">{assistant.name}</div>
</div>
<div className={`w-3.5 h-3.5 rounded border ${
isSelected ? 'bg-primary border-primary' : 'border-gray-300'
}`}>
{isSelected && <Check className="w-2.5 h-2.5 text-white" />}
</div>
</div>
))}
</ScrollArea>
Call Order Management
<div className="space-y-2 border rounded-md p-2 bg-muted/10">
{selectedAssistants.map((assistantId, index) => (
<div key={assistantId} className="flex items-center gap-2 p-2 border rounded-md">
<div className="w-6 h-6 rounded-full bg-primary text-primary-foreground text-xs">
{index + 1}
</div>
{/* Assistant info */}
<div className="flex items-center gap-2 flex-1">
<Avatar className="h-5 w-5">
<AvatarImage src={assistant.avatar} />
<AvatarFallback>{assistant.name[0]}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium truncate">{assistant.name}</div>
</div>
</div>
{/* Order controls */}
<div className="flex items-center gap-1">
<Button onClick={moveUp} disabled={index === 0}>
<ArrowUp className="h-3 w-3" />
</Button>
<Button onClick={moveDown} disabled={isLast}>
<ArrowDown className="h-3 w-3" />
</Button>
<Button onClick={remove} className="text-destructive">×</Button>
</div>
</div>
))}
</div>
Integration with ToolSelector
The configurator integrates seamlessly with the ToolSelector:
// In ToolSelector component
{tool.configFields && (
<ToolConfigurator
toolId={tool.id}
currentConfig={toolConfigs[tool.id]}
onConfigUpdate={handleConfigUpdate}
configFields={filterConfigFieldsByTags(
tool.configFields,
availableTags,
user
)}
availableTags={availableTags}
availableAssistants={availableAssistants}
onToolEnable={(toolId) => {
if (!selectedTools.includes(toolId)) {
onChange([...selectedTools, toolId]);
}
}}
/>
)}
Common Configuration Patterns
Data Source Tools
// Typical data source configuration
{
key: 'datastore_id',
label: 'Data Store Selection',
type: 'select',
options: [
{ label: 'Public Demo', value: 'public' },
{ label: 'Company Docs', value: 'company', tags: ['admin-tools'] },
{ label: 'Custom', value: 'custom' }
],
required: true
}
Output Tools
// Typical output tool configuration
{
key: 'default_theme',
label: 'Default Theme',
type: 'select',
options: [
{ label: 'Modern', value: 'modern' },
{ label: 'Minimal', value: 'minimal' },
{ label: 'Classic', value: 'classic' }
],
required: false
}
Behavior Modification Tools
// Advanced model selection
{
key: 'smart_tool',
label: 'Pick Model',
type: 'select',
options: [
{ label: 'Anthropic 4.0 Opus', value: 'anthropic-40-opus', tags: ['admin-tools'] },
{ label: 'Anthropic 4.0', value: 'anthropic-40' },
{ label: 'Gemini 2.5', value: 'gemini', tags: ['beta-testers'] }
],
required: false
}
Error Handling
Validation Errors
// Toast notifications for configuration errors
toast({
title: "Configuration Required",
description: `Please configure "${tool?.name}" before enabling it.`,
variant: "destructive",
});
Field-Level Validation
// Real-time field validation
const isFieldValid = (field: ConfigField, value: any): boolean => {
if (!field.required) return true;
switch (field.type) {
case 'checkbox':
return true; // Always valid
case 'assistant_multiselect':
return Array.isArray(value) && value.length > 0;
default:
return value && value.toString().trim() !== '';
}
};
Testing
The ToolConfigurator has comprehensive test coverage:
# Test specific functionality
npm test src/components/__tests__/ToolConfigurator*.test.tsx
# Test integration with ToolSelector
npm test src/components/__tests__/ToolSelector-tag-config-fields.test.tsx
Related Documentation
- ToolSelector Component - Main tool selection interface
- Tag-Based Tool Config - Access control system
- How to Add Tools to Aitana - Development guide
API Reference
Props
| Prop | Type | Required | Description |
|---|---|---|---|
toolId |
string |
Yes | Unique identifier for the tool |
currentConfig |
Record<string, any> |
No | Current configuration values |
onConfigUpdate |
(toolId: string, config: Record<string, any>) => void |
Yes | Config update callback |
configFields |
FilteredConfigField[] |
Yes | Available configuration fields |
onToolEnable |
(toolId: string) => void |
No | Auto-enable callback |
availableTags |
Tag[] |
No | Available tags for display |
availableAssistants |
AssistantDisplay[] |
No | Available assistants for multiselect |
Types
export interface ToolConfig {
id: string;
config: Record<string, any>;
}
export interface ConfigField {
key: string;
label: string;
type: 'text' | 'number' | 'select' | 'checkbox' | 'radio' | 'assistant_multiselect';
placeholder?: string;
options?: { label: string; value: string }[];
required?: boolean;
defaultValue?: any;
}