/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useState, useMemo, useCallback } from 'react' import { ChevronsUpDown, Check, CpuIcon, LayersIcon } from 'lucide-react' import { useTranslation } from 'react-i18next' import { cn } from '@/lib/utils' import { useIsMobile } from '@/hooks/use-mobile' import { Button } from '@/components/ui/button' import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '@/components/ui/command' import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger, } from '@/components/ui/drawer' import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover' interface ModelOption { label: string value: string category?: string description?: string } interface GroupOption { label: string value: string ratio?: number desc?: string description?: string } interface ModelSelectorProps { selectedModel: string models: ModelOption[] onModelChange: (value: string) => void className?: string disabled?: boolean } interface GroupSelectorProps { selectedGroup: string groups: GroupOption[] onGroupChange: (value: string) => void className?: string disabled?: boolean } const ModelTriggerButton = React.forwardRef< React.ComponentRef, React.ComponentPropsWithoutRef & { currentLabel: string triggerClassName?: string isDisabled?: boolean } >(({ currentLabel, triggerClassName, isDisabled, ...props }, ref) => ( )) ModelTriggerButton.displayName = 'ModelTriggerButton' const GroupTriggerButton = React.forwardRef< React.ComponentRef, React.ComponentPropsWithoutRef & { currentLabel: string triggerClassName?: string isDisabled?: boolean } >(({ currentLabel, triggerClassName, isDisabled, ...props }, ref) => ( )) GroupTriggerButton.displayName = 'GroupTriggerButton' /** * Model Selector Component * Styled following Scira's form-component design patterns */ export const ModelSelector: React.FC = React.memo( ({ selectedModel, models, onModelChange, className, disabled = false }) => { const { t } = useTranslation() const [open, setOpen] = useState(false) const [searchQuery, setSearchQuery] = useState('') const isMobile = useIsMobile() const currentModel = useMemo( () => models.find((m) => m.value === selectedModel), [models, selectedModel] ) // Group models by category const groupedModels = useMemo( () => models.reduce( (acc, model) => { const category = model.category || t('Other') if (!acc[category]) { acc[category] = [] } acc[category].push(model) return acc }, {} as Record ), [models, t] ) // Filter models by search query const filteredModels = useMemo(() => { if (!searchQuery.trim()) return groupedModels const query = searchQuery.toLowerCase() const filtered: Record = {} Object.entries(groupedModels).forEach(([category, categoryModels]) => { const matches = categoryModels.filter( (m) => m.label.toLowerCase().includes(query) || m.value.toLowerCase().includes(query) || m.description?.toLowerCase().includes(query) ) if (matches.length > 0) { filtered[category] = matches } }) return filtered }, [groupedModels, searchQuery]) const handleModelChange = useCallback( (value: string) => { onModelChange(value) setOpen(false) setSearchQuery('') }, [onModelChange] ) // Shared command content const renderModelCommandContent = () => ( 1} shouldFilter={false} > {!isMobile && ( )} {t('No model found.')} {Object.keys(filteredModels).length === 0 ? (
{t('No model found.')}
) : ( Object.entries(filteredModels).map( ([category, categoryModels], categoryIndex) => ( {categoryIndex > 0 && (
)}
{t('{{category}} Models', { category })}
{categoryModels.map((model) => (
{model.label}
))} ) ) )} ) return ( <> {isMobile ? ( {t('Select Model')}
{renderModelCommandContent()}
) : ( } /> {renderModelCommandContent()} )} ) } ) ModelSelector.displayName = 'ModelSelector' /** * Group Selector Component * Styled following Scira's form-component design patterns */ export const GroupSelector: React.FC = React.memo( ({ selectedGroup, groups, onGroupChange, className, disabled = false }) => { const { t } = useTranslation() const [open, setOpen] = useState(false) const isMobile = useIsMobile() const currentGroup = useMemo( () => groups.find((g) => g.value === selectedGroup), [groups, selectedGroup] ) const handleGroupChange = useCallback( (value: string) => { onGroupChange(value) setOpen(false) }, [onGroupChange] ) // Shared command content const renderGroupCommandContent = () => ( { const group = groups.find((g) => g.value === value) if (!group || !search) return 1 const searchTerm = search.toLowerCase() const searchableFields = [ group.label, group.description || '', group.value, ] .join(' ') .toLowerCase() return searchableFields.includes(searchTerm) ? 1 : 0 }} > {t('No group found.')}
{t('Model Group')}
{groups.map((group) => (
{group.label} {(group.desc || group.description) && (
{group.desc || group.description} {group.ratio && ( <> {' · '} {t('Ratio: {{value}}', { value: group.ratio })} )}
)}
))}
) return ( <> {isMobile ? ( {t('Choose Group')}
{groups.map((group) => ( ))}
) : ( } /> {renderGroupCommandContent()} )} ) } ) GroupSelector.displayName = 'GroupSelector' // Export combined selector component export interface ModelGroupSelectorProps { // Model props selectedModel: string models: ModelOption[] onModelChange: (value: string) => void // Group props selectedGroup: string groups: GroupOption[] onGroupChange: (value: string) => void // Common props className?: string disabled?: boolean } /** * Combined Model and Group Selector Component * Provides both model and group selection in a unified interface */ export const ModelGroupSelector: React.FC = ({ selectedModel, models, onModelChange, selectedGroup, groups, onGroupChange, className, disabled = false, }) => { return (
) }