feat: new ui
This commit is contained in:
171
src/pages/model/components/ModelList.tsx
Normal file
171
src/pages/model/components/ModelList.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { Box, Flex, useTheme, Input, IconButton, Tooltip, Image } from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import { useRouter } from 'next/router';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { postCreateModel } from '@/api/model';
|
||||
import { useLoading } from '@/hooks/useLoading';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useUserStore } from '@/store/user';
|
||||
|
||||
const ModelList = ({ modelId }: { modelId: string }) => {
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const { myModels, myCollectionModels, loadMyModels, refreshModel } = useUserStore();
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
/* 加载模型 */
|
||||
const { isLoading } = useQuery(['loadModels'], () => loadMyModels(false));
|
||||
|
||||
const onclickCreateModel = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const id = await postCreateModel({ name: `AI助手${myModels.length + 1}` });
|
||||
toast({
|
||||
title: '创建成功',
|
||||
status: 'success'
|
||||
});
|
||||
refreshModel.freshMyModels();
|
||||
router.push(`/model?modelId=${id}`);
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: typeof err === 'string' ? err : err.message || '出现了意外',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [myModels.length, refreshModel, router, setIsLoading, toast]);
|
||||
|
||||
const models = useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
label: '我的',
|
||||
list: myModels.filter((item) =>
|
||||
new RegExp(searchText, 'ig').test(item.name + item.systemPrompt)
|
||||
)
|
||||
},
|
||||
{
|
||||
label: '收藏',
|
||||
list: myCollectionModels.filter((item) =>
|
||||
new RegExp(searchText, 'ig').test(item.name + item.systemPrompt)
|
||||
)
|
||||
}
|
||||
].filter((item) => item.list.length > 0),
|
||||
[myCollectionModels, myModels, searchText]
|
||||
);
|
||||
|
||||
const totalModels = useMemo(
|
||||
() => models.reduce((sum, item) => sum + item.list.length, 0),
|
||||
[models]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
position={'relative'}
|
||||
flexDirection={'column'}
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
bg={'white'}
|
||||
borderRight={['', theme.borders.base]}
|
||||
>
|
||||
<Flex w={'90%'} my={5} mx={'auto'}>
|
||||
<Flex flex={1} mr={2} position={'relative'} alignItems={'center'}>
|
||||
<Input
|
||||
h={'32px'}
|
||||
placeholder="搜索 AI 助手"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
/>
|
||||
{searchText && (
|
||||
<MyIcon
|
||||
zIndex={10}
|
||||
position={'absolute'}
|
||||
right={3}
|
||||
name={'closeSolid'}
|
||||
w={'16px'}
|
||||
h={'16px'}
|
||||
color={'myGray.500'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => setSearchText('')}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Tooltip label={'新建一个AI助手'}>
|
||||
<IconButton
|
||||
h={'32px'}
|
||||
icon={<AddIcon />}
|
||||
aria-label={''}
|
||||
variant={'outline'}
|
||||
onClick={onclickCreateModel}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
<Box flex={'1 0 0'} h={0} overflow={'overlay'}>
|
||||
{models.map((item) => (
|
||||
<Box key={item.label} _notFirst={{ mt: 5 }}>
|
||||
<Box fontWeight={'bold'} pl={5}>
|
||||
{item.label}
|
||||
</Box>
|
||||
{item.list.map((item) => (
|
||||
<Flex
|
||||
key={item._id}
|
||||
position={'relative'}
|
||||
alignItems={['flex-start', 'center']}
|
||||
p={3}
|
||||
mb={[2, 0]}
|
||||
cursor={'pointer'}
|
||||
transition={'background-color .2s ease-in'}
|
||||
borderLeft={['', '5px solid transparent']}
|
||||
_hover={{
|
||||
backgroundColor: ['', '#dee0e3']
|
||||
}}
|
||||
{...(modelId === item._id
|
||||
? {
|
||||
backgroundColor: '#eff0f1',
|
||||
borderLeftColor: 'myBlue.600'
|
||||
}
|
||||
: {})}
|
||||
onClick={() => {
|
||||
if (item._id === modelId) return;
|
||||
router.push(`/model?modelId=${item._id}`);
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={item.avatar || '/icon/logo.png'}
|
||||
alt=""
|
||||
w={'34px'}
|
||||
maxH={'50px'}
|
||||
objectFit={'contain'}
|
||||
/>
|
||||
<Box flex={'1 0 0'} w={0} ml={3}>
|
||||
<Box className="textEllipsis" color={'myGray.1000'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
<Box className="textEllipsis" color={'myGray.400'} fontSize={'sm'}>
|
||||
{item.systemPrompt || '这个模型没有提示词~'}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{!isLoading && totalModels === 0 && (
|
||||
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'30vh'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
还没有 AI 助手~
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelList;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useState, useRef } from 'react';
|
||||
import React, { useCallback, useState, useRef, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
TableContainer,
|
||||
@@ -53,6 +53,7 @@ const ModelDataCard = ({ modelId, isOwner }: { modelId: string; isOwner: boolean
|
||||
whiteSpace: 'pre-wrap',
|
||||
overflowY: 'auto'
|
||||
});
|
||||
|
||||
const {
|
||||
data: modelDataList,
|
||||
isLoading,
|
||||
@@ -66,9 +67,14 @@ const ModelDataCard = ({ modelId, isOwner }: { modelId: string; isOwner: boolean
|
||||
params: {
|
||||
modelId,
|
||||
searchText
|
||||
}
|
||||
},
|
||||
defaultRequest: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
getData(1);
|
||||
}, [modelId, getData]);
|
||||
|
||||
const [editInputData, setEditInputData] = useState<InputDataType>();
|
||||
|
||||
const {
|
||||
@@ -54,9 +54,10 @@ const ModelEditForm = ({
|
||||
if (!file) return;
|
||||
try {
|
||||
const base64 = await compressImg({
|
||||
file
|
||||
file,
|
||||
maxW: 40,
|
||||
maxH: 60
|
||||
});
|
||||
|
||||
setValue('avatar', base64);
|
||||
setRefresh((state) => !state);
|
||||
} catch (err: any) {
|
||||
@@ -184,7 +185,7 @@ const ModelEditForm = ({
|
||||
<SliderMark
|
||||
value={getValues('chat.temperature')}
|
||||
textAlign="center"
|
||||
bg="blue.500"
|
||||
bg="myBlue.600"
|
||||
color="white"
|
||||
w={'18px'}
|
||||
h={'18px'}
|
||||
@@ -195,7 +196,7 @@ const ModelEditForm = ({
|
||||
{getValues('chat.temperature')}
|
||||
</SliderMark>
|
||||
<SliderTrack>
|
||||
<SliderFilledTrack />
|
||||
<SliderFilledTrack bg={'myBlue.700'} />
|
||||
</SliderTrack>
|
||||
<SliderThumb />
|
||||
</Slider>
|
||||
@@ -97,7 +97,7 @@ const SelectJsonModal = ({
|
||||
my={3}
|
||||
cursor={'pointer'}
|
||||
textDecoration={'underline'}
|
||||
color={'blue.600'}
|
||||
color={'myBlue.600'}
|
||||
onClick={() =>
|
||||
fileDownload({
|
||||
text: csvTemplate,
|
||||
@@ -1,72 +1,70 @@
|
||||
import React, { useCallback, useState, useMemo, useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getModelById, delModelById, putModelById } from '@/api/model';
|
||||
import { delModelById, putModelById } from '@/api/model';
|
||||
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, defaultModel } from '@/constants/model';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useScreen } from '@/hooks/useScreen';
|
||||
import { formatModelStatus } from '@/constants/model';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { useLoading } from '@/hooks/useLoading';
|
||||
import ModelEditForm from './components/ModelEditForm';
|
||||
import ModelDataCard from './components/ModelDataCard';
|
||||
|
||||
const ModelEditForm = dynamic(() => import('./components/ModelEditForm'));
|
||||
const ModelDataCard = dynamic(() => import('./components/ModelDataCard'));
|
||||
|
||||
const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
const ModelDetail = ({ modelId, isPc }: { modelId: string; isPc: boolean }) => {
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const { isPc } = useScreen();
|
||||
const { userInfo } = useUserStore();
|
||||
const { setLoading } = useGlobalStore();
|
||||
const { userInfo, modelDetail, loadModelDetail, refreshModel, setLastModelId } = useUserStore();
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const [btnLoading, setBtnLoading] = useState(false);
|
||||
|
||||
const [model, setModel] = useState<ModelSchema>(defaultModel);
|
||||
const formHooks = useForm<ModelSchema>({
|
||||
defaultValues: model
|
||||
defaultValues: modelDetail
|
||||
});
|
||||
|
||||
const isOwner = useMemo(() => model.userId === userInfo?._id, [model.userId, userInfo?._id]);
|
||||
|
||||
/* 加载模型数据 */
|
||||
const loadModel = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getModelById(modelId);
|
||||
setModel(res);
|
||||
formHooks.reset(res);
|
||||
} catch (err: any) {
|
||||
// load model data
|
||||
const { isLoading } = useQuery([modelId], () => loadModelDetail(modelId), {
|
||||
onSuccess(res) {
|
||||
res && formHooks.reset(res);
|
||||
modelId && setLastModelId(modelId);
|
||||
},
|
||||
onError(err: any) {
|
||||
toast({
|
||||
title: err?.message || '获取模型异常',
|
||||
status: 'error'
|
||||
});
|
||||
setLastModelId('');
|
||||
refreshModel.freshMyModels();
|
||||
router.replace('/model');
|
||||
}
|
||||
setLoading(false);
|
||||
return null;
|
||||
}, [formHooks, modelId, setLoading, toast]);
|
||||
});
|
||||
|
||||
useQuery([modelId], loadModel);
|
||||
const isOwner = useMemo(
|
||||
() => modelDetail.userId === userInfo?._id,
|
||||
[modelDetail.userId, userInfo?._id]
|
||||
);
|
||||
|
||||
/* 点击删除 */
|
||||
const handleDelModel = useCallback(async () => {
|
||||
if (!model) return;
|
||||
setLoading(true);
|
||||
if (!modelDetail) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await delModelById(model._id);
|
||||
await delModelById(modelDetail._id);
|
||||
toast({
|
||||
title: '删除成功',
|
||||
status: 'success'
|
||||
});
|
||||
router.replace('/model/list');
|
||||
refreshModel.removeModelDetail(modelDetail._id);
|
||||
router.replace('/model');
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: err?.message || '删除失败',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
}, [setLoading, model, router, toast]);
|
||||
setIsLoading(false);
|
||||
}, [modelDetail, setIsLoading, toast, refreshModel, router]);
|
||||
|
||||
/* 点前往聊天预览页 */
|
||||
const handlePreviewChat = useCallback(async () => {
|
||||
@@ -76,7 +74,7 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
// 提交保存模型修改
|
||||
const saveSubmitSuccess = useCallback(
|
||||
async (data: ModelSchema) => {
|
||||
setLoading(true);
|
||||
setBtnLoading(true);
|
||||
try {
|
||||
await putModelById(data._id, {
|
||||
name: data.name,
|
||||
@@ -89,15 +87,16 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
title: '更新成功',
|
||||
status: 'success'
|
||||
});
|
||||
refreshModel.updateModelDetail(data);
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: err?.message || '更新失败',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
setBtnLoading(false);
|
||||
},
|
||||
[setLoading, toast]
|
||||
[refreshModel, toast]
|
||||
);
|
||||
// 提交保存表单失败
|
||||
const saveSubmitError = useCallback(() => {
|
||||
@@ -131,23 +130,31 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box h={'100%'} p={5} overflow={'overlay'} position={'relative'}>
|
||||
{/* 头部 */}
|
||||
<Card px={6} py={3}>
|
||||
{isPc ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontSize={'xl'} fontWeight={'bold'}>
|
||||
{model.name}
|
||||
{modelDetail.name}
|
||||
</Box>
|
||||
<Tag ml={2} variant="solid" colorScheme={formatModelStatus[model.status].colorTheme}>
|
||||
{formatModelStatus[model.status].text}
|
||||
<Tag
|
||||
ml={2}
|
||||
variant="solid"
|
||||
colorScheme={formatModelStatus[modelDetail.status].colorTheme}
|
||||
>
|
||||
{formatModelStatus[modelDetail.status].text}
|
||||
</Tag>
|
||||
<Box flex={1} />
|
||||
<Button variant={'outline'} onClick={handlePreviewChat}>
|
||||
对话体验
|
||||
开始对话
|
||||
</Button>
|
||||
{isOwner && (
|
||||
<Button ml={4} onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}>
|
||||
<Button
|
||||
isLoading={btnLoading}
|
||||
ml={4}
|
||||
onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}
|
||||
>
|
||||
保存修改
|
||||
</Button>
|
||||
)}
|
||||
@@ -156,20 +163,21 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
<>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}>
|
||||
{model?.name}
|
||||
{modelDetail.name}
|
||||
</Box>
|
||||
<Tag ml={2} colorScheme={formatModelStatus[model.status].colorTheme}>
|
||||
{formatModelStatus[model.status].text}
|
||||
<Tag ml={2} colorScheme={formatModelStatus[modelDetail.status].colorTheme}>
|
||||
{formatModelStatus[modelDetail.status].text}
|
||||
</Tag>
|
||||
</Flex>
|
||||
<Box mt={4} textAlign={'right'}>
|
||||
<Button variant={'outline'} size={'sm'} onClick={handlePreviewChat}>
|
||||
对话体验
|
||||
开始对话
|
||||
</Button>
|
||||
{isOwner && (
|
||||
<Button
|
||||
ml={4}
|
||||
size={'sm'}
|
||||
isLoading={btnLoading}
|
||||
onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}
|
||||
>
|
||||
保存修改
|
||||
@@ -182,22 +190,13 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
<Grid mt={5} gridTemplateColumns={['1fr', '1fr 1fr']} gridGap={5}>
|
||||
<ModelEditForm formHooks={formHooks} handleDelModel={handleDelModel} isOwner={isOwner} />
|
||||
|
||||
{modelId && (
|
||||
<Card p={4} gridColumnStart={[1, 1]} gridColumnEnd={[2, 3]}>
|
||||
<ModelDataCard modelId={modelId} isOwner={isOwner} />
|
||||
</Card>
|
||||
)}
|
||||
<Card p={4} gridColumnStart={[1, 1]} gridColumnEnd={[2, 3]}>
|
||||
<ModelDataCard modelId={modelId} isOwner={isOwner} />
|
||||
</Card>
|
||||
</Grid>
|
||||
</>
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelDetail;
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
const modelId = context.query?.modelId || '';
|
||||
|
||||
return {
|
||||
props: { modelId }
|
||||
};
|
||||
}
|
||||
47
src/pages/model/index.tsx
Normal file
47
src/pages/model/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useScreen } from '@/hooks/useScreen';
|
||||
import { useRouter } from 'next/router';
|
||||
import ModelList from './components/ModelList';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useUserStore } from '@/store/user';
|
||||
|
||||
const ModelDetail = dynamic(() => import('./components/detail/index'));
|
||||
|
||||
const Model = ({ modelId, isPcDevice }: { modelId: string; isPcDevice: boolean }) => {
|
||||
const router = useRouter();
|
||||
const { isPc } = useScreen({
|
||||
defaultIsPc: isPcDevice
|
||||
});
|
||||
const { lastModelId } = useUserStore();
|
||||
|
||||
// redirect modelId
|
||||
useEffect(() => {
|
||||
if (isPc && !modelId && lastModelId) {
|
||||
router.replace(`/model?modelId=${lastModelId}`);
|
||||
}
|
||||
}, [isPc, lastModelId, modelId, router]);
|
||||
|
||||
return (
|
||||
<Flex h={'100%'} position={'relative'}>
|
||||
{/* 模型列表 */}
|
||||
{(isPc || !modelId) && (
|
||||
<Box w={['100%', '250px']}>
|
||||
<ModelList modelId={modelId} />
|
||||
</Box>
|
||||
)}
|
||||
<Box flex={1} h={'100%'}>
|
||||
{modelId && <ModelDetail modelId={modelId} isPc={isPc} />}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Model;
|
||||
|
||||
Model.getInitialProps = ({ query, req }: any) => {
|
||||
return {
|
||||
modelId: query?.modelId || '',
|
||||
isPcDevice: !/Mobile/.test(req ? req.headers['user-agent'] : navigator.userAgent)
|
||||
};
|
||||
};
|
||||
@@ -1,76 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Box, Button, Flex, Tag } from '@chakra-ui/react';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { formatModelStatus } from '@/constants/model';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ChatModelMap } from '@/constants/model';
|
||||
|
||||
const ModelPhoneList = ({
|
||||
models,
|
||||
handlePreviewChat
|
||||
}: {
|
||||
models: ModelSchema[];
|
||||
handlePreviewChat: (_: string) => void;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
router.prefetch('/chat');
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<Box borderRadius={'md'} overflow={'hidden'} mb={5}>
|
||||
{models.map((model) => (
|
||||
<Box
|
||||
key={model._id}
|
||||
_notFirst={{ borderTop: '1px solid rgba(0,0,0,0.1)' }}
|
||||
px={6}
|
||||
py={3}
|
||||
backgroundColor={'white'}
|
||||
>
|
||||
<Flex alignItems={'flex-start'}>
|
||||
<Box flex={'1 0 0'} w={0} fontSize={'lg'} fontWeight={'bold'}>
|
||||
{model.name}
|
||||
</Box>
|
||||
<Tag
|
||||
colorScheme={formatModelStatus[model.status].colorTheme}
|
||||
variant="solid"
|
||||
px={3}
|
||||
size={'md'}
|
||||
>
|
||||
{formatModelStatus[model.status].text}
|
||||
</Tag>
|
||||
</Flex>
|
||||
<Flex mt={5}>
|
||||
<Box flex={'0 0 100px'}>对话模型: </Box>
|
||||
<Box color={'blackAlpha.500'}>{ChatModelMap[model.chat.chatModel].name}</Box>
|
||||
</Flex>
|
||||
<Flex mt={5}>
|
||||
<Box flex={'0 0 100px'}>模型温度: </Box>
|
||||
<Box color={'blackAlpha.500'}>{model.chat.temperature}</Box>
|
||||
</Flex>
|
||||
<Flex mt={5} justifyContent={'flex-end'}>
|
||||
<Button
|
||||
mr={3}
|
||||
variant={'outline'}
|
||||
w={'100px'}
|
||||
size={'sm'}
|
||||
onClick={() => handlePreviewChat(model._id)}
|
||||
>
|
||||
体验
|
||||
</Button>
|
||||
<Button
|
||||
size={'sm'}
|
||||
w={'100px'}
|
||||
onClick={() => router.push(`/model/detail?modelId=${model._id}`)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelPhoneList;
|
||||
@@ -1,120 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Tag,
|
||||
Card,
|
||||
Box
|
||||
} from '@chakra-ui/react';
|
||||
import { formatModelStatus } from '@/constants/model';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ChatModelMap } from '@/constants/model';
|
||||
|
||||
const ModelTable = ({
|
||||
models = [],
|
||||
handlePreviewChat
|
||||
}: {
|
||||
models: ModelSchema[];
|
||||
handlePreviewChat: (_: string) => void;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const columns = [
|
||||
{
|
||||
title: '模型名',
|
||||
key: 'name',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '对话模型',
|
||||
key: 'service',
|
||||
render: (model: ModelSchema) => (
|
||||
<Box fontWeight={'bold'} whiteSpace={'pre-wrap'} maxW={'200px'}>
|
||||
{ChatModelMap[model.chat.chatModel].name}
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '温度',
|
||||
key: 'temperature',
|
||||
render: (model: ModelSchema) => <>{model.chat.temperature}</>
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
render: (item: ModelSchema) => (
|
||||
<Tag
|
||||
colorScheme={formatModelStatus[item.status]?.colorTheme}
|
||||
variant="solid"
|
||||
px={3}
|
||||
size={'md'}
|
||||
>
|
||||
{formatModelStatus[item.status]?.text}
|
||||
</Tag>
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
title: '操作',
|
||||
key: 'control',
|
||||
render: (item: ModelSchema) => (
|
||||
<>
|
||||
<Button mr={3} onClick={() => handlePreviewChat(item._id)}>
|
||||
对话
|
||||
</Button>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
onClick={() => router.push(`/model/detail?modelId=${item._id}`)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
router.prefetch('/chat');
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<Card py={3}>
|
||||
<TableContainer>
|
||||
<Table variant={'simple'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
{columns.map((item) => (
|
||||
<Th key={item.key}>{item.title}</Th>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{models.map((item) => (
|
||||
<Tr key={item._id}>
|
||||
{columns.map((col) => (
|
||||
<Td key={col.key}>
|
||||
{col.render
|
||||
? col.render(item)
|
||||
: !!col.dataIndex
|
||||
? // @ts-ignore nextline
|
||||
item[col.dataIndex]
|
||||
: ''}
|
||||
</Td>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelTable;
|
||||
@@ -1,90 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Box, Button, Flex, Card } from '@chakra-ui/react';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { useRouter } from 'next/router';
|
||||
import ModelTable from './components/ModelTable';
|
||||
import ModelPhoneList from './components/ModelPhoneList';
|
||||
import { useScreen } from '@/hooks/useScreen';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useLoading } from '@/hooks/useLoading';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { postCreateModel } from '@/api/model';
|
||||
|
||||
const modelList = () => {
|
||||
const { toast } = useToast();
|
||||
const { isPc } = useScreen();
|
||||
const router = useRouter();
|
||||
const { myModels, getMyModels } = useUserStore();
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
|
||||
/* 加载模型 */
|
||||
const { isLoading } = useQuery(['loadModels'], getMyModels);
|
||||
|
||||
const handleCreateModel = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const id = await postCreateModel({ name: `模型${myModels.length}` });
|
||||
toast({
|
||||
title: '创建成功',
|
||||
status: 'success'
|
||||
});
|
||||
router.push(`/model/detail?modelId=${id}`);
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: typeof err === 'string' ? err : err.message || '出现了意外',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [myModels.length, router, setIsLoading, toast]);
|
||||
|
||||
/* 点前往聊天预览页 */
|
||||
const handlePreviewChat = useCallback(
|
||||
async (modelId: string) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
router.push(`/chat?modelId=${modelId}`, undefined, {
|
||||
shallow: true
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.log('error->', err);
|
||||
toast({
|
||||
title: err.message || '出现一些异常',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
},
|
||||
[router, setIsLoading, toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box position={'relative'}>
|
||||
{/* 头部 */}
|
||||
<Card px={6} py={3}>
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'}>
|
||||
<Box fontWeight={'bold'} fontSize={'xl'}>
|
||||
模型列表
|
||||
</Box>
|
||||
|
||||
<Button flex={'0 0 145px'} variant={'outline'} onClick={handleCreateModel}>
|
||||
新建模型
|
||||
</Button>
|
||||
</Flex>
|
||||
</Card>
|
||||
{/* 表单 */}
|
||||
<Box mt={5} position={'relative'}>
|
||||
{isPc ? (
|
||||
<ModelTable models={myModels} handlePreviewChat={handlePreviewChat} />
|
||||
) : (
|
||||
<ModelPhoneList models={myModels} handlePreviewChat={handlePreviewChat} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Loading loading={isLoading} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default modelList;
|
||||
@@ -44,7 +44,7 @@ const ShareModelList = ({
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
color={model.isCollection ? 'blue.600' : 'alphaBlack.700'}
|
||||
color={model.isCollection ? 'myBlue.700' : 'blackAlpha.700'}
|
||||
onClick={() => onclickCollection(model._id)}
|
||||
>
|
||||
<MyIcon
|
||||
@@ -58,7 +58,7 @@ const ShareModelList = ({
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'outline'}
|
||||
w={'80px'}
|
||||
w={['60px', '80px']}
|
||||
onClick={() => router.push(`/chat?modelId=${model._id}`)}
|
||||
>
|
||||
体验
|
||||
@@ -67,8 +67,8 @@ const ShareModelList = ({
|
||||
<Button
|
||||
ml={4}
|
||||
size={'sm'}
|
||||
w={'80px'}
|
||||
onClick={() => router.push(`/model/detail?modelId=${model._id}`)}
|
||||
w={['60px', '80px']}
|
||||
onClick={() => router.push(`/model?modelId=${model._id}`)}
|
||||
>
|
||||
详情
|
||||
</Button>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useLoading } from '@/hooks/useLoading';
|
||||
import { getShareModelList, triggerModelCollection, getCollectionModels } from '@/api/model';
|
||||
import { usePagination } from '@/hooks/usePagination';
|
||||
import type { ShareModelItem } from '@/types/model';
|
||||
|
||||
import { useUserStore } from '@/store/user';
|
||||
import ShareModelList from './components/list';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
@@ -12,6 +12,7 @@ const modelList = () => {
|
||||
const { Loading } = useLoading();
|
||||
const lastSearch = useRef('');
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const { refreshModel } = useUserStore();
|
||||
|
||||
/* 加载模型 */
|
||||
const { data, isLoading, Pagination, getData, pageNum } = usePagination<ShareModelItem>({
|
||||
@@ -41,15 +42,16 @@ const modelList = () => {
|
||||
await triggerModelCollection(modelId);
|
||||
getData(pageNum);
|
||||
refetchCollection();
|
||||
refreshModel.removeModelDetail(modelId);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
[getData, pageNum, refetchCollection]
|
||||
[getData, pageNum, refetchCollection, refreshModel]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box py={[5, 10]} px={'5vw'}>
|
||||
<Card px={6} py={3}>
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'}>
|
||||
<Box fontWeight={'bold'} fontSize={'xl'}>
|
||||
@@ -105,7 +107,7 @@ const modelList = () => {
|
||||
</Card>
|
||||
|
||||
<Loading loading={isLoading} />
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user