/** * ProviderSelector Component * * Wiederverwendbare Komponente zur Auswahl von AICore-Providern. * Kann im Chat Playground und Automation Editor verwendet werden. * * Features: * - Dropdown für Einzelauswahl * - Checkbox-Liste für Mehrfachauswahl * - Lädt verfügbare Provider aus dem Billing-System */ import React, { useEffect, useMemo, useState, useRef, useCallback } from 'react'; import { useBilling } from '../../hooks/useBilling'; import styles from './ProviderSelector.module.css'; // Provider display names const PROVIDER_LABELS: Record = { anthropic: 'Anthropic (Claude)', openai: 'OpenAI (GPT)', mistral: 'Mistral (Le Chat)', perplexity: 'Perplexity', tavily: 'Tavily (Web Search)', privatellm: 'Private LLM', internal: 'Internal', }; // Provider icons (emojis for simplicity) const PROVIDER_ICONS: Record = { anthropic: '🤖', openai: '💬', mistral: '🇫🇷', perplexity: '🔍', tavily: '🌐', privatellm: '🔒', internal: '🏠', }; // ============================================================================ // SINGLE SELECT COMPONENT // ============================================================================ interface ProviderSelectProps { value: string; onChange: (provider: string) => void; disabled?: boolean; className?: string; label?: string; showLabel?: boolean; } export const ProviderSelect: React.FC = ({ value, onChange, disabled = false, className, label = 'AI-Provider', showLabel = true, }) => { const { allowedProviders, loadAllowedProviders, loading } = useBilling(); useEffect(() => { if (allowedProviders.length === 0 && !loading) { loadAllowedProviders(); } }, []); const providerOptions = useMemo(() => { return allowedProviders.map((provider) => ({ value: provider, label: `${PROVIDER_ICONS[provider] || '🔌'} ${PROVIDER_LABELS[provider] || provider}`, })); }, [allowedProviders]); return (
{showLabel && }
); }; // ============================================================================ // MULTI SELECT COMPONENT (Checkbox List) // ============================================================================ interface ProviderMultiSelectProps { selectedProviders: string[]; onChange: (providers: string[]) => void; disabled?: boolean; className?: string; label?: string; showLabel?: boolean; defaultExpanded?: boolean; excludeByDefault?: string[]; } export const ProviderMultiSelect: React.FC = ({ selectedProviders, onChange, disabled = false, className, label = 'AI-Provider', showLabel = true, defaultExpanded = false, excludeByDefault = [], }) => { const [isExpanded, setIsExpanded] = useState(defaultExpanded); const [initialExcludeApplied, setInitialExcludeApplied] = useState(false); const containerRef = useRef(null); const { allowedProviders, loadAllowedProviders, loading } = useBilling(); useEffect(() => { if (allowedProviders.length === 0 && !loading) { loadAllowedProviders(); } }, []); // Apply default exclusions when providers first load useEffect(() => { if ( !initialExcludeApplied && allowedProviders.length > 0 && excludeByDefault.length > 0 && selectedProviders.length === 0 ) { const initialSelection = allowedProviders.filter( (p) => !excludeByDefault.includes(p) ); // Only apply if there's actually something to exclude if (initialSelection.length < allowedProviders.length) { onChange(initialSelection); } setInitialExcludeApplied(true); } }, [allowedProviders, excludeByDefault, initialExcludeApplied, selectedProviders.length, onChange]); // Click outside handler const handleClickOutside = useCallback((event: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setIsExpanded(false); } }, []); useEffect(() => { if (isExpanded) { document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); } }, [isExpanded, handleClickOutside]); // Effective selection: empty array = all providers active (no restriction) const effectiveSelection = selectedProviders.length === 0 ? allowedProviders : selectedProviders; // "Alle" is active when no restriction is set (empty array) OR all explicitly selected const isAllSelected = selectedProviders.length === 0 || (allowedProviders.length > 0 && selectedProviders.length === allowedProviders.length); const handleToggle = (provider: string) => { if (selectedProviders.length === 0) { // Currently "all active" (no restriction) -> make explicit: all except the toggled one onChange(allowedProviders.filter((p) => p !== provider)); } else if (selectedProviders.includes(provider)) { // Deactivate: remove from selection const remaining = selectedProviders.filter((p) => p !== provider); // If removing leaves all others selected, reset to [] (= all, no restriction) if (remaining.length === allowedProviders.length) { onChange([]); } else { onChange(remaining); } } else { // Activate: add to selection const updated = [...selectedProviders, provider]; // If all are now selected, reset to [] (= all, no restriction) if (updated.length === allowedProviders.length) { onChange([]); } else { onChange(updated); } } }; const handleSelectAll = () => { onChange([]); // Empty = all active, no restriction }; // Summary icon for button const summaryIcon = useMemo(() => { if (effectiveSelection.length === 1) { return PROVIDER_ICONS[effectiveSelection[0]] || '🔌'; } return '🤖'; }, [effectiveSelection]); return (
{/* Trigger Button - styled like iconButton */} {/* Dropdown Content */} {isExpanded && (
{showLabel &&
{label}
}
{loading ? (
Lade...
) : (
{allowedProviders.map((provider) => ( ))}
)} {isAllSelected && !loading && (
Alle Provider aktiv (kein Filter)
)}
)}
); }; // ============================================================================ // COMPACT PROVIDER BADGE LIST // ============================================================================ interface ProviderBadgesProps { providers: string[]; className?: string; } export const ProviderBadges: React.FC = ({ providers, className, }) => { if (providers.length === 0) { return Alle Provider; } return (
{providers.map((provider) => ( {PROVIDER_ICONS[provider] || '🔌'} {PROVIDER_LABELS[provider] || provider} ))}
); }; // Default export export default ProviderSelect;