import { ChevronDown, PlusCircle } from 'lucide-react'; import type { EmbeddingModelCard, KbEmbeddingProvider } from '@/api'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { useTranslation } from '@/i18n/useI18n.ts'; interface SelectedEmbedding { type: string; credentialId: string; model: string; card: EmbeddingModelCard; } interface Props { value?: { type: string; credential_id: string; model: string } | null; providers: KbEmbeddingProvider[]; loading?: boolean; onChange?: (selected: SelectedEmbedding) => void; onAddCredential?: () => void; /** Override the trigger label shown when no model is selected. */ placeholder?: string; } /** * Embedding model picker. Consumes a pre-grouped, server-filtered * list of providers (one entry per credential) and emits the chosen * model's full :class:`EmbeddingModelCard` so the caller can derive * the dimension separately. */ export function EmbeddingSelect({ value, providers, loading, onChange, onAddCredential, placeholder, }: Props) { const { t } = useTranslation(); // Group providers by credential type for the existing two-level UI // (provider type → credential → models). const groups: Record = {}; for (const provider of providers) { const type = (provider.credential.data.type as string) ?? 'unknown'; if (!groups[type]) groups[type] = []; groups[type].push(provider); } const hasOptions = Object.keys(groups).length > 0; const handleSelect = (type: string, credentialId: string, card: EmbeddingModelCard) => { onChange?.({ type, credentialId, model: card.name, card }); }; const displayLabel = value?.model ? value.model : loading ? t('embedding-select.loading') : (placeholder ?? t('embedding-select.placeholder')); return ( {!loading && !hasOptions ? (

{t('embedding-select.empty.title')}

{t('embedding-select.empty.description')}

) : ( Object.entries(groups).map(([type, items], idx) => { const isSingle = items.length === 1; return ( {idx > 0 && } {type.replace(/_credential$/, '')} {isSingle ? items[0].models.map((m) => ( handleSelect(type, items[0].credential.id, m) } > {m.label} )) : items.map(({ credential, models }) => { const credName = (credential.data.name as string) || credential.id.slice(0, 8); return ( {credName} {models.map((m) => ( handleSelect( type, credential.id, m, ) } > {m.label} ))} ); })} ); }) )} {t('embedding-select.addCredential')}
); }