import { aiproxyIdMap } from '@/global/aiproxy/constants'; import { ChannelInfoType } from '@/global/aiproxy/type'; import { Box, BoxProps, Button, Flex, Input, MenuItemProps, ModalBody, ModalFooter, useDisclosure, Menu, MenuButton, MenuList, MenuItem, HStack, useOutsideClick } from '@chakra-ui/react'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import MyModal from '@fastgpt/web/components/common/MyModal'; import MySelect from '@fastgpt/web/components/common/MySelect'; import { useTranslation } from 'next-i18next'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { AddModelButton } from '../AddModelBox'; import dynamic from 'next/dynamic'; import { SystemModelItemType } from '@fastgpt/service/core/ai/type'; import { ModelTypeEnum } from '@fastgpt/global/core/ai/model'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { getSystemModelList } from '@/web/core/ai/config'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { getModelProvider } from '@fastgpt/global/core/ai/provider'; import MyIcon from '@fastgpt/web/components/common/Icon'; import MyAvatar from '@fastgpt/web/components/common/Avatar'; import MyTag from '@fastgpt/web/components/common/Tag/index'; import { useCopyData } from '@fastgpt/web/hooks/useCopyData'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; import { getChannelProviders, postCreateChannel, putChannel } from '@/web/core/ai/channel'; import CopyBox from '@fastgpt/web/components/common/String/CopyBox'; const ModelEditModal = dynamic(() => import('../AddModelBox').then((mod) => mod.ModelEditModal)); const LabelStyles: BoxProps = { fontSize: 'sm', color: 'myGray.900', flex: '0 0 70px' }; const EditChannelModal = ({ defaultConfig, onClose, onSuccess }: { defaultConfig: ChannelInfoType; onClose: () => void; onSuccess: () => void; }) => { const { t } = useTranslation(); const { defaultModels } = useSystemStore(); const isEdit = defaultConfig.id !== 0; const { register, handleSubmit, watch, setValue } = useForm({ defaultValues: defaultConfig }); const providerType = watch('type'); const { data: providerList = [] } = useRequest2( () => getChannelProviders().then((res) => { return Object.entries(res) .map(([key, value]) => { const mapData = aiproxyIdMap[key as any] ?? { label: value.name, provider: 'Other' }; const provider = getModelProvider(mapData.provider); return { order: provider.order, defaultBaseUrl: value.defaultBaseUrl, keyHelp: value.keyHelp, icon: mapData?.avatar ?? provider.avatar, label: t(mapData.label as any), value: Number(key) }; }) .sort((a, b) => a.order - b.order); }), { manual: false } ); const selectedProvider = useMemo(() => { const res = providerList.find((item) => item.value === providerType); return res; }, [providerList, providerType]); const [editModelData, setEditModelData] = useState(); const onCreateModel = (type: ModelTypeEnum) => { const defaultModel = defaultModels[type]; setEditModelData({ ...defaultModel, model: '', name: '', charsPointsPrice: 0, inputPrice: undefined, outputPrice: undefined, isCustom: true, isActive: true, // @ts-ignore type }); }; const models = watch('models'); const { data: systemModelList = [], runAsync: refreshSystemModelList, loading: loadingModels } = useRequest2(getSystemModelList, { manual: false }); const modelList = useMemo(() => { const currentProvider = aiproxyIdMap[providerType]?.provider; return systemModelList .map((item) => { const provider = getModelProvider(item.provider); return { provider: item.provider, icon: provider.avatar, label: item.model, value: item.model }; }) .sort((a, b) => { // sort by provider, same provider first if (a.provider === currentProvider && b.provider !== currentProvider) return -1; if (a.provider !== currentProvider && b.provider === currentProvider) return 1; return 0; }); }, [providerType, systemModelList]); const modelMapping = watch('model_mapping'); const { runAsync: onSubmit, loading: loadingCreate } = useRequest2( (data: ChannelInfoType) => { if (data.models.length === 0) { return Promise.reject(t('account_model:selected_model_empty')); } return isEdit ? putChannel(data) : postCreateChannel(data); }, { onSuccess() { onSuccess(); onClose(); }, successToast: isEdit ? t('common:common.Update Success') : t('common:common.Create Success'), manual: true } ); const isLoading = loadingModels || loadingCreate; return ( <> {/* Chnnel name */} {t('account_model:channel_name')} {/* Provider */} {t('account_model:channel_type')} { setValue('type', val); }} /> {/* Model */} {t('account_model:model')}({models.length}) { setValue('models', val); }} /> {/* Mapping */} {t('account_model:mapping')} { if (!val) { setValue('model_mapping', {}); } else { try { setValue('model_mapping', JSON.parse(val)); } catch (error) {} } }} /> {/* url and key */} {t('account_model:base_url')} {selectedProvider && ( {'('} {t('account_model:default_url')}: {selectedProvider?.defaultBaseUrl || ''} {')'} )} {t('account_model:api_key')} {selectedProvider?.keyHelp && ( {'('} {t('account_model:key_type')} {selectedProvider.keyHelp} {')'} )} {!!editModelData && ( setEditModelData(undefined)} /> )} ); }; export default EditChannelModal; type SelectProps = { list: { icon?: string; label: string; value: string; }[]; value: string[]; onSelect: (val: string[]) => void; }; const menuItemStyles: MenuItemProps = { borderRadius: 'sm', py: 2, display: 'flex', alignItems: 'center', _hover: { backgroundColor: 'myGray.100' }, _notLast: { mb: 0.5 } }; const MultipleSelect = ({ value = [], list = [], onSelect }: SelectProps) => { const ref = useRef(null); const BoxRef = useRef(null); const { t } = useTranslation(); const { isOpen, onOpen, onClose } = useDisclosure(); const { copyData } = useCopyData(); const [search, setSearch] = useState(''); const onclickItem = useCallback( (val: string) => { if (value.includes(val)) { onSelect(value.filter((i) => i !== val)); } else { onSelect([...value, val]); BoxRef.current?.scrollTo({ top: BoxRef.current.scrollHeight }); } setSearch(''); }, [value, onSelect] ); const filterUnSelected = useMemo(() => { return list .filter((item) => !value.includes(item.value)) .filter((item) => { if (!search) return true; const regx = new RegExp(search, 'i'); return regx.test(item.label); }); }, [list, value, search]); useOutsideClick({ ref, handler: () => { onClose(); } }); return ( { onOpen(); setSearch(''); } })} > {value.length === 0 ? ( {t('account_model:select_model_placeholder')} ) : ( {value.map((item) => ( { e.stopPropagation(); copyData(item, t('account_model:copy_model_id_success')); }} > {item} { e.stopPropagation(); onclickItem(item); }} /> ))} {isOpen && ( setSearch(e.target.value)} placeholder={t('account_model:search_model')} onClick={(e) => { e.stopPropagation(); }} /> )} )} {filterUnSelected.map((item, i) => { return ( { onclickItem(item.value); }} whiteSpace={'pre-wrap'} fontSize={'sm'} gap={2} {...menuItemStyles} > {item.icon && } {item.label} ); })} ); };