import { PageAction } from '@/config'; import { categoryOptions, modelCategoriesMap } from '@/pages/llmodels/config'; import { CheckCircleFilled, LoadingOutlined, WarningFilled } from '@ant-design/icons'; import { AutoComplete, Select as SealSelect } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Form, Tooltip } from 'antd'; import React, { useMemo } from 'react'; import styled from 'styled-components'; import { useFormContext } from '../config/form-context'; import { FormData, ProviderModel } from '../config/types'; import { useTestProviderModel } from '../hooks/use-query-provider-models'; const SelectWrapper = styled.div` width: 100%; display: grid; grid-template-columns: 265px 160px max-content; align-items: center; gap: 8px; .icon-wrapper { display: flex; align-items: center; height: 100%; } `; interface ModelItemProps { onOpenChange?: (open: boolean) => void; onChange: (data: ProviderModel) => void; providerModelList: Global.BaseOption< string, { category: string; accessible: boolean; } >[]; selectedModelList: ProviderModel[]; item: ProviderModel; loading?: boolean; } const ModelItem: React.FC = ({ onOpenChange, onChange, loading, providerModelList, selectedModelList, item }) => { const intl = useIntl(); const form = Form.useFormInstance(); const { runTestModel, loading: testLoading } = useTestProviderModel(); const { id, action, currentData, getCustomConfig } = useFormContext(); const [openTip, setOpenTip] = React.useState(false); const generateCurrentAPIKey = (currentAPIKey: string) => { if ( [PageAction.EDIT, PageAction.COPY].includes(action) && currentAPIKey === currentData?.api_tokens?.[0]?.hash ) { return undefined; } return currentAPIKey; }; const generateID = () => { if (action === PageAction.EDIT || action === PageAction.COPY) { return id!; } return 0; }; const handleTestModel = async () => { const proxyConfigEnabled = form.getFieldValue('proxy_enabled'); const customConfig = getCustomConfig?.(); const res = await runTestModel({ id: generateID(), data: { model_name: item.name, api_token: generateCurrentAPIKey( form.getFieldValue('api_key') ) as string, proxy_url: proxyConfigEnabled ? form.getFieldValue('proxy_url') || null : null, config: { type: form.getFieldValue(['config', 'type']) || '', ...customConfig } } }); onChange({ ...item, accessible: res.accessible }); }; const handleOnChange = (value: string, option: any) => { onChange({ ...option, name: value }); }; const handleOnCategoryChange = (value: string) => { onChange({ ...item, category: value }); }; const handleOnBlur = (e: any) => { const value = e.target.value; const isDuplicate = selectedModelList.some( (model) => model.name === value && model !== item ); setOpenTip(isDuplicate); // if duplicate, clear the value, and close the tooltip after 2 seconds if (isDuplicate) { onChange({ ...item, name: '' }); } }; const renderSuffixIcon = () => { if (testLoading) { return ; } if (item.accessible === true) { return ( ); } if (item.accessible === false) { return ( ); } return null; }; // filter out already selected models, but keep the current one const selectedModelSet = useMemo(() => { return new Set(selectedModelList?.map((model) => model.name)); }, [selectedModelList]); const filteredOptions = useMemo(() => { return providerModelList.filter((model) => { return model.value === item.name || !selectedModelSet.has(model.value); }); }, [providerModelList, item.name, selectedModelSet]); React.useEffect(() => { if (openTip) { const timerId = setTimeout(() => setOpenTip(false), 2000); return () => clearTimeout(timerId); } return () => {}; }, [openTip]); return ( { return ( option!.value .toLowerCase() .includes(inputValue.toLowerCase()) || option.label ?.toString() .toLowerCase() .includes(inputValue.toLowerCase()) ); } }} onOpenChange={onOpenChange} suffixIcon={renderSuffixIcon()} value={item.name} onChange={handleOnChange} onBlur={handleOnBlur} options={filteredOptions} placeholder={intl.formatMessage({ id: 'providers.table.models' })} /> ); }; export default ModelItem;