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 badges
  • beta-testers: Purple badges
  • finance-team: Green badges
  • ONE: Blue badges
  • dev-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

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;
}