frontend_nyla/src/components/ProviderSelector/ProviderSelector.tsx
2026-02-06 16:18:44 +01:00

260 lines
7.5 KiB
TypeScript

/**
* 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 } from 'react';
import { useBilling } from '../../hooks/useBilling';
import styles from './ProviderSelector.module.css';
// Provider display names
const PROVIDER_LABELS: Record<string, string> = {
anthropic: 'Anthropic (Claude)',
openai: 'OpenAI (GPT)',
perplexity: 'Perplexity',
tavily: 'Tavily (Web Search)',
internal: 'Internal',
};
// Provider icons (emojis for simplicity)
const PROVIDER_ICONS: Record<string, string> = {
anthropic: '🤖',
openai: '💬',
perplexity: '🔍',
tavily: '🌐',
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<ProviderSelectProps> = ({
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 (
<div className={`${styles.providerSelect} ${className || ''}`}>
{showLabel && <label className={styles.label}>{label}</label>}
<select
value={value}
onChange={(e) => onChange(e.target.value)}
disabled={disabled || loading}
className={styles.select}
>
<option value="">-- Auto --</option>
{providerOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
);
};
// ============================================================================
// MULTI SELECT COMPONENT (Checkbox List)
// ============================================================================
interface ProviderMultiSelectProps {
selectedProviders: string[];
onChange: (providers: string[]) => void;
disabled?: boolean;
className?: string;
label?: string;
showLabel?: boolean;
defaultExpanded?: boolean;
}
export const ProviderMultiSelect: React.FC<ProviderMultiSelectProps> = ({
selectedProviders,
onChange,
disabled = false,
className,
label = 'AI-Provider',
showLabel = true,
defaultExpanded = false,
}) => {
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
const { allowedProviders, loadAllowedProviders, loading } = useBilling();
useEffect(() => {
if (allowedProviders.length === 0 && !loading) {
loadAllowedProviders();
}
}, []);
const handleToggle = (provider: string) => {
if (selectedProviders.includes(provider)) {
onChange(selectedProviders.filter((p) => p !== provider));
} else {
onChange([...selectedProviders, provider]);
}
};
const handleSelectAll = () => {
onChange(allowedProviders);
};
const handleSelectNone = () => {
onChange([]);
};
// Summary text for collapsed view
const summaryText = useMemo(() => {
if (selectedProviders.length === 0) {
return 'Alle Provider';
}
if (selectedProviders.length === 1) {
return PROVIDER_LABELS[selectedProviders[0]] || selectedProviders[0];
}
return `${selectedProviders.length} Provider`;
}, [selectedProviders]);
// Summary icons for collapsed view
const summaryIcons = useMemo(() => {
if (selectedProviders.length === 0) {
return '🤖';
}
return selectedProviders.slice(0, 3).map(p => PROVIDER_ICONS[p] || '🔌').join('');
}, [selectedProviders]);
return (
<div className={`${styles.providerMultiSelect} ${className || ''} ${isExpanded ? styles.expanded : styles.collapsed}`}>
{/* Collapsible Header */}
<button
type="button"
className={styles.collapseHeader}
onClick={() => setIsExpanded(!isExpanded)}
disabled={disabled}
>
<span className={styles.summaryIcons}>{summaryIcons}</span>
<span className={styles.summaryText}>{summaryText}</span>
<span className={styles.expandIcon}>{isExpanded ? '▲' : '▼'}</span>
</button>
{/* Expandable Content */}
{isExpanded && (
<div className={styles.expandableContent}>
{showLabel && <label className={styles.label}>{label}</label>}
<div className={styles.selectActions}>
<button
type="button"
onClick={handleSelectAll}
disabled={disabled}
className={styles.actionButton}
>
Alle
</button>
<button
type="button"
onClick={handleSelectNone}
disabled={disabled}
className={styles.actionButton}
>
Keine
</button>
</div>
{loading ? (
<div className={styles.loading}>Lade Provider...</div>
) : (
<div className={styles.checkboxList}>
{allowedProviders.map((provider) => (
<label
key={provider}
className={`${styles.checkboxItem} ${disabled ? styles.disabled : ''}`}
>
<input
type="checkbox"
checked={selectedProviders.includes(provider)}
onChange={() => handleToggle(provider)}
disabled={disabled}
/>
<span className={styles.icon}>{PROVIDER_ICONS[provider] || '🔌'}</span>
<span className={styles.providerName}>
{PROVIDER_LABELS[provider] || provider}
</span>
</label>
))}
</div>
)}
{selectedProviders.length === 0 && !loading && (
<div className={styles.hint}>
Wenn keine Provider ausgewählt sind, werden alle erlaubten Provider verwendet.
</div>
)}
</div>
)}
</div>
);
};
// ============================================================================
// COMPACT PROVIDER BADGE LIST
// ============================================================================
interface ProviderBadgesProps {
providers: string[];
className?: string;
}
export const ProviderBadges: React.FC<ProviderBadgesProps> = ({
providers,
className,
}) => {
if (providers.length === 0) {
return <span className={styles.allProviders}>Alle Provider</span>;
}
return (
<div className={`${styles.providerBadges} ${className || ''}`}>
{providers.map((provider) => (
<span key={provider} className={styles.badge}>
{PROVIDER_ICONS[provider] || '🔌'} {PROVIDER_LABELS[provider] || provider}
</span>
))}
</div>
);
};
// Default export
export default ProviderSelect;