260 lines
7.5 KiB
TypeScript
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;
|