feat: model share market
This commit is contained in:
@@ -39,7 +39,7 @@ import InputModal, { FormData as InputDataType } from './InputDataModal';
|
||||
const SelectFileModal = dynamic(() => import('./SelectFileModal'));
|
||||
const SelectCsvModal = dynamic(() => import('./SelectCsvModal'));
|
||||
|
||||
const ModelDataCard = ({ modelId }: { modelId: string }) => {
|
||||
const ModelDataCard = ({ modelId, isOwner }: { modelId: string; isOwner: boolean }) => {
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const lastSearch = useRef('');
|
||||
const [searchText, setSearchText] = useState('');
|
||||
@@ -133,50 +133,53 @@ const ModelDataCard = ({ modelId }: { modelId: string }) => {
|
||||
<Flex>
|
||||
<Box fontWeight={'bold'} fontSize={'lg'} flex={1} mr={2}>
|
||||
模型数据: {total}组
|
||||
<Box as={'span'} fontSize={'sm'}>
|
||||
(测试版本)
|
||||
</Box>
|
||||
</Box>
|
||||
<IconButton
|
||||
icon={<RepeatIcon />}
|
||||
aria-label={'refresh'}
|
||||
variant={'outline'}
|
||||
mr={4}
|
||||
size={'sm'}
|
||||
onClick={() => refetchData(pageNum)}
|
||||
/>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
mr={2}
|
||||
size={'sm'}
|
||||
isLoading={isLoadingExport}
|
||||
title={'换行数据导出时,会进行格式转换'}
|
||||
onClick={() => onclickExport()}
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
<Menu autoSelect={false}>
|
||||
<MenuButton as={Button} size={'sm'}>
|
||||
导入
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
setEditInputData({
|
||||
a: '',
|
||||
q: ''
|
||||
})
|
||||
}
|
||||
{isOwner && (
|
||||
<>
|
||||
<IconButton
|
||||
icon={<RepeatIcon />}
|
||||
aria-label={'refresh'}
|
||||
variant={'outline'}
|
||||
mr={4}
|
||||
size={'sm'}
|
||||
onClick={() => refetchData(pageNum)}
|
||||
/>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
mr={2}
|
||||
size={'sm'}
|
||||
isLoading={isLoadingExport}
|
||||
title={'换行数据导出时,会进行格式转换'}
|
||||
onClick={() => onclickExport()}
|
||||
>
|
||||
手动输入
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onOpenSelectFileModal}>文本/文件拆分</MenuItem>
|
||||
<MenuItem onClick={onOpenSelectCsvModal}>csv 问答对导入</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
导出
|
||||
</Button>
|
||||
<Menu autoSelect={false}>
|
||||
<MenuButton as={Button} size={'sm'}>
|
||||
导入
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
setEditInputData({
|
||||
a: '',
|
||||
q: ''
|
||||
})
|
||||
}
|
||||
>
|
||||
手动输入
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onOpenSelectFileModal}>文本/文件拆分</MenuItem>
|
||||
<MenuItem onClick={onOpenSelectCsvModal}>csv 问答对导入</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex mt={4}>
|
||||
{splitDataLen > 0 && <Box fontSize={'xs'}>{splitDataLen}条数据正在拆分,请耐心等待...</Box>}
|
||||
{isOwner && splitDataLen > 0 && (
|
||||
<Box fontSize={'xs'}>{splitDataLen}条数据正在拆分,请耐心等待...</Box>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
<Input
|
||||
maxW={'240px'}
|
||||
@@ -207,7 +210,7 @@ const ModelDataCard = ({ modelId }: { modelId: string }) => {
|
||||
<Th>{'匹配的知识点'}</Th>
|
||||
<Th>补充知识</Th>
|
||||
<Th>状态</Th>
|
||||
<Th>操作</Th>
|
||||
{isOwner && <Th>操作</Th>}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -220,33 +223,35 @@ const ModelDataCard = ({ modelId }: { modelId: string }) => {
|
||||
<Box {...tdStyles.current}>{item.a || '-'}</Box>
|
||||
</Td>
|
||||
<Td>{ModelDataStatusMap[item.status]}</Td>
|
||||
<Td>
|
||||
<IconButton
|
||||
mr={5}
|
||||
icon={<EditIcon />}
|
||||
variant={'outline'}
|
||||
aria-label={'delete'}
|
||||
size={'sm'}
|
||||
onClick={() =>
|
||||
setEditInputData({
|
||||
dataId: item.id,
|
||||
q: item.q,
|
||||
a: item.a
|
||||
})
|
||||
}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<DeleteIcon />}
|
||||
variant={'outline'}
|
||||
colorScheme={'gray'}
|
||||
aria-label={'delete'}
|
||||
size={'sm'}
|
||||
onClick={async () => {
|
||||
await delOneModelData(item.id);
|
||||
refetchData(pageNum);
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
{isOwner && (
|
||||
<Td>
|
||||
<IconButton
|
||||
mr={5}
|
||||
icon={<EditIcon />}
|
||||
variant={'outline'}
|
||||
aria-label={'delete'}
|
||||
size={'sm'}
|
||||
onClick={() =>
|
||||
setEditInputData({
|
||||
dataId: item.id,
|
||||
q: item.q,
|
||||
a: item.a
|
||||
})
|
||||
}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<DeleteIcon />}
|
||||
variant={'outline'}
|
||||
colorScheme={'gray'}
|
||||
aria-label={'delete'}
|
||||
size={'sm'}
|
||||
onClick={async () => {
|
||||
await delOneModelData(item.id);
|
||||
refetchData(pageNum);
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
SliderMark,
|
||||
Tooltip,
|
||||
Button,
|
||||
Select
|
||||
Select,
|
||||
Grid,
|
||||
Switch
|
||||
} from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
@@ -25,10 +27,12 @@ import { useConfirm } from '@/hooks/useConfirm';
|
||||
const ModelEditForm = ({
|
||||
formHooks,
|
||||
canTrain,
|
||||
isOwner,
|
||||
handleDelModel
|
||||
}: {
|
||||
formHooks: UseFormReturn<ModelSchema>;
|
||||
canTrain: boolean;
|
||||
isOwner: boolean;
|
||||
handleDelModel: () => void;
|
||||
}) => {
|
||||
const { openConfirm, ConfirmChild } = useConfirm({
|
||||
@@ -40,27 +44,26 @@ const ModelEditForm = ({
|
||||
return (
|
||||
<>
|
||||
<Card p={4}>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
<Box fontWeight={'bold'}>基本信息</Box>
|
||||
</Flex>
|
||||
<Box fontWeight={'bold'}>基本信息</Box>
|
||||
<FormControl mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'} w={0}>
|
||||
名称:
|
||||
</Box>
|
||||
<Input
|
||||
isDisabled={!isOwner}
|
||||
{...register('name', {
|
||||
required: '展示名称不能为空'
|
||||
})}
|
||||
></Input>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 80px'} w={0}>
|
||||
modelId:
|
||||
</Box>
|
||||
<Box>{getValues('_id')}</Box>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 80px'} w={0}>
|
||||
modelId:
|
||||
</Box>
|
||||
<Box>{getValues('_id')}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 80px'} w={0}>
|
||||
模型类型:
|
||||
@@ -79,17 +82,25 @@ const ModelEditForm = ({
|
||||
元/1K tokens(包括上下文和回答)
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 150px'}>删除模型和数据集</Box>
|
||||
<Button
|
||||
colorScheme={'gray'}
|
||||
variant={'outline'}
|
||||
size={'sm'}
|
||||
onClick={openConfirm(handleDelModel)}
|
||||
>
|
||||
删除模型
|
||||
</Button>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 80px'} w={0}>
|
||||
收藏人数:
|
||||
</Box>
|
||||
<Box>{getValues('share.collection')}人</Box>
|
||||
</Flex>
|
||||
{isOwner && (
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 150px'}>删除模型和知识库</Box>
|
||||
<Button
|
||||
colorScheme={'gray'}
|
||||
variant={'outline'}
|
||||
size={'sm'}
|
||||
onClick={openConfirm(handleDelModel)}
|
||||
>
|
||||
删除模型
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
</Card>
|
||||
<Card p={4}>
|
||||
<Box fontWeight={'bold'}>模型效果</Box>
|
||||
@@ -110,6 +121,7 @@ const ModelEditForm = ({
|
||||
max={10}
|
||||
step={1}
|
||||
value={getValues('temperature')}
|
||||
isDisabled={!isOwner}
|
||||
onChange={(e) => {
|
||||
setValue('temperature', e);
|
||||
setRefresh(!refresh);
|
||||
@@ -139,7 +151,10 @@ const ModelEditForm = ({
|
||||
<FormControl mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>搜索模式</Box>
|
||||
<Select {...register('search.mode', { required: '搜索模式不能为空' })}>
|
||||
<Select
|
||||
isDisabled={!isOwner}
|
||||
{...register('search.mode', { required: '搜索模式不能为空' })}
|
||||
>
|
||||
{Object.entries(ModelVectorSearchModeMap).map(([key, { text }]) => (
|
||||
<option key={key} value={key}>
|
||||
{text}
|
||||
@@ -154,15 +169,73 @@ const ModelEditForm = ({
|
||||
<Textarea
|
||||
rows={6}
|
||||
maxLength={-1}
|
||||
{...register('systemPrompt')}
|
||||
isDisabled={!isOwner}
|
||||
placeholder={
|
||||
canTrain
|
||||
? '训练的模型会根据知识库内容,生成一部分系统提示词,因此在对话时需要消耗更多的 tokens。你可以增加提示词,让效果更符合预期。例如: \n1. 请根据知识库内容回答用户问题。\n2. 知识库是电影《铃芽之旅》的内容,根据知识库内容回答。无关问题,拒绝回复!'
|
||||
: '模型默认的 prompt 词,通过调整该内容,可以生成一个限定范围的模型。\n注意,改功能会影响对话的整体朝向!'
|
||||
}
|
||||
{...register('systemPrompt')}
|
||||
/>
|
||||
</Box>
|
||||
</Card>
|
||||
{isOwner && (
|
||||
<Card p={4} gridColumnStart={[1, 1]} gridColumnEnd={[2, 3]}>
|
||||
<Box fontWeight={'bold'}>分享设置</Box>
|
||||
|
||||
<Grid gridTemplateColumns={['1fr', '1fr 410px']} gridGap={5}>
|
||||
<Box>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box mr={3}>模型分享:</Box>
|
||||
<Switch
|
||||
isChecked={getValues('share.isShare')}
|
||||
onChange={() => {
|
||||
setValue('share.isShare', !getValues('share.isShare'));
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
<Box ml={12} mr={3}>
|
||||
分享模型细节:
|
||||
</Box>
|
||||
<Switch
|
||||
isChecked={getValues('share.isShareDetail')}
|
||||
onChange={() => {
|
||||
setValue('share.isShareDetail', !getValues('share.isShareDetail'));
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Box mt={5}>
|
||||
<Box>模型介绍</Box>
|
||||
<Textarea
|
||||
mt={1}
|
||||
rows={6}
|
||||
maxLength={150}
|
||||
{...register('share.intro')}
|
||||
placeholder={'介绍模型的功能、场景等,吸引更多人来使用!最多150字。'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
textAlign={'justify'}
|
||||
fontSize={'sm'}
|
||||
border={'1px solid #f4f4f4'}
|
||||
borderRadius={'sm'}
|
||||
p={3}
|
||||
>
|
||||
<Box fontWeight={'bold'}>Tips</Box>
|
||||
<Box mt={1} as={'ul'} pl={4}>
|
||||
<li>
|
||||
开启模型分享后,你的模型将会出现在共享市场,可供 FastGpt
|
||||
所有用户使用。用户使用时不会消耗你的 tokens,而是消耗使用者的 tokens。
|
||||
</li>
|
||||
<li>开启分享详情后,其他用户可以查看该模型的特有数据:温度、提示词和数据集。</li>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* <Card p={4}>
|
||||
<Box fontWeight={'bold'}>安全策略</Box>
|
||||
<FormControl mt={2}>
|
||||
|
||||
@@ -5,11 +5,12 @@ import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { Card, Box, Flex, Button, Tag, Grid } from '@chakra-ui/react';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { formatModelStatus, ModelStatusEnum, modelList, defaultModel } from '@/constants/model';
|
||||
import { formatModelStatus, modelList, defaultModel } from '@/constants/model';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useScreen } from '@/hooks/useScreen';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useUserStore } from '@/store/user';
|
||||
|
||||
const ModelEditForm = dynamic(() => import('./components/ModelEditForm'));
|
||||
const ModelDataCard = dynamic(() => import('./components/ModelDataCard'));
|
||||
@@ -18,6 +19,7 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const { isPc } = useScreen();
|
||||
const { userInfo } = useUserStore();
|
||||
const { setLoading } = useGlobalStore();
|
||||
|
||||
const [model, setModel] = useState<ModelSchema>(defaultModel);
|
||||
@@ -30,21 +32,24 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
return !!(openai && openai.trainName);
|
||||
}, [model]);
|
||||
|
||||
const isOwner = useMemo(() => model.userId === userInfo?._id, [model.userId, userInfo?._id]);
|
||||
|
||||
/* 加载模型数据 */
|
||||
const loadModel = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getModelById(modelId);
|
||||
// console.log(res);
|
||||
res.security.expiredTime /= 60 * 60 * 1000;
|
||||
setModel(res);
|
||||
formHooks.reset(res);
|
||||
} catch (err) {
|
||||
console.log('error->', err);
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: err?.message || '获取模型异常',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
return null;
|
||||
}, [formHooks, modelId, setLoading]);
|
||||
}, [formHooks, modelId, setLoading, toast]);
|
||||
|
||||
useQuery([modelId], loadModel);
|
||||
|
||||
@@ -59,22 +64,19 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
status: 'success'
|
||||
});
|
||||
router.replace('/model/list');
|
||||
} catch (err) {
|
||||
console.log('error->', err);
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: err?.message || '删除失败',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
}, [setLoading, model, router, toast]);
|
||||
|
||||
/* 点前往聊天预览页 */
|
||||
const handlePreviewChat = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
router.push(`/chat?modelId=${modelId}`);
|
||||
} catch (err) {
|
||||
console.log('error->', err);
|
||||
}
|
||||
setLoading(false);
|
||||
}, [setLoading, router, modelId]);
|
||||
router.push(`/chat?modelId=${modelId}`);
|
||||
}, [router, modelId]);
|
||||
|
||||
// 提交保存模型修改
|
||||
const saveSubmitSuccess = useCallback(
|
||||
@@ -86,6 +88,7 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
systemPrompt: data.systemPrompt,
|
||||
temperature: data.temperature,
|
||||
search: data.search,
|
||||
share: data.share,
|
||||
service: data.service,
|
||||
security: data.security
|
||||
});
|
||||
@@ -93,11 +96,10 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
title: '更新成功',
|
||||
status: 'success'
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('error->', err);
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: err as string,
|
||||
status: 'success'
|
||||
title: err?.message || '更新失败',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
@@ -151,9 +153,11 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
<Button variant={'outline'} onClick={handlePreviewChat}>
|
||||
对话体验
|
||||
</Button>
|
||||
<Button ml={4} onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}>
|
||||
保存修改
|
||||
</Button>
|
||||
{isOwner && (
|
||||
<Button ml={4} onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}>
|
||||
保存修改
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
) : (
|
||||
<>
|
||||
@@ -169,19 +173,26 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
<Button variant={'outline'} onClick={handlePreviewChat}>
|
||||
对话体验
|
||||
</Button>
|
||||
<Button ml={4} onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}>
|
||||
保存修改
|
||||
</Button>
|
||||
{isOwner && (
|
||||
<Button ml={4} onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}>
|
||||
保存修改
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
<Grid mt={5} gridTemplateColumns={['1fr', '1fr 1fr']} gridGap={5}>
|
||||
<ModelEditForm formHooks={formHooks} handleDelModel={handleDelModel} canTrain={canTrain} />
|
||||
<ModelEditForm
|
||||
formHooks={formHooks}
|
||||
handleDelModel={handleDelModel}
|
||||
canTrain={canTrain}
|
||||
isOwner={isOwner}
|
||||
/>
|
||||
|
||||
{canTrain && !!model._id && (
|
||||
<Card p={4} gridColumnStart={[1, 1]} gridColumnEnd={[2, 3]}>
|
||||
<ModelDataCard modelId={model._id} />
|
||||
<ModelDataCard modelId={model._id} isOwner={isOwner} />
|
||||
</Card>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user