feat: share unlogin.perf: link format and model ui

This commit is contained in:
archer
2023-05-19 10:26:30 +08:00
parent a62a9c4067
commit 246ee973ec
15 changed files with 283 additions and 394 deletions

View File

@@ -33,7 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase();
let startTime = Date.now();
const { model, showModelDetail, userOpenAiKey, systemAuthKey, userId } = await authShareChat({
const { model, userOpenAiKey, systemAuthKey, userId } = await authShareChat({
shareId,
password
});

View File

@@ -16,7 +16,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const userId = await authToken(req);
await authModel({
modelId,
userId
userId,
authOwner: false
});
const { _id } = await ShareChat.create({

View File

@@ -36,7 +36,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 校验使用权限
const { model } = await authModel({
modelId: shareChat.modelId,
userId: String(shareChat.userId)
userId: String(shareChat.userId),
authOwner: false
});
jsonRes<InitShareChatResponse>(res, {

View File

@@ -0,0 +1,46 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { id } = req.query as {
id: string;
};
if (!id) {
throw new Error('缺少参数');
}
// 凭证校验
const userId = await authToken(req);
await connectToDatabase();
const data = await KB.findOne({
_id: id,
userId
});
if (!data) {
throw new Error('kb is not exist');
}
jsonRes(res, {
data: {
_id: data._id,
avatar: data.avatar,
name: data.name,
userId: data.userId,
updateTime: data.updateTime,
tags: data.tags.join(' ')
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -24,18 +24,18 @@ import { useSelectFile } from '@/hooks/useSelectFile';
import { useConfirm } from '@/hooks/useConfirm';
import { compressImg } from '@/utils/file';
import DataCard from './DataCard';
import { getErrText } from '@/utils/tools';
const Detail = ({ kbId }: { kbId: string }) => {
const { toast } = useToast();
const router = useRouter();
const InputRef = useRef<HTMLInputElement>(null);
const { setLastKbId, KbDetail, getKbDetail, loadKbList, myKbList } = useUserStore();
const { Loading, setIsLoading } = useLoading();
const { setLastKbId, kbDetail, getKbDetail, loadKbList, myKbList } = useUserStore();
const [btnLoading, setBtnLoading] = useState(false);
const [refresh, setRefresh] = useState(false);
const { getValues, formState, setValue, reset, register, handleSubmit } = useForm<KbItemType>({
defaultValues: KbDetail
defaultValues: kbDetail
});
const { openConfirm, ConfirmChild } = useConfirm({
content: '确认删除该知识库?数据将无法恢复,请确认!'
@@ -46,7 +46,7 @@ const Detail = ({ kbId }: { kbId: string }) => {
multiple: false
});
const { isLoading } = useQuery([kbId, myKbList], () => getKbDetail(kbId), {
useQuery([kbId, myKbList], () => getKbDetail(kbId), {
onSuccess(res) {
kbId && setLastKbId(kbId);
if (res) {
@@ -58,17 +58,18 @@ const Detail = ({ kbId }: { kbId: string }) => {
},
onError(err: any) {
toast({
title: err?.message || '获取AI助手异常',
title: getErrText(err, '获取AI助手异常'),
status: 'error'
});
loadKbList(true);
setLastKbId('');
router.replace('/model');
router.replace(`/kb?kbId=${myKbList[0]?._id || ''}`);
}
});
/* 点击删除 */
const onclickDelKb = useCallback(async () => {
setIsLoading(true);
setBtnLoading(true);
try {
await delKbById(kbId);
toast({
@@ -83,8 +84,8 @@ const Detail = ({ kbId }: { kbId: string }) => {
status: 'error'
});
}
setIsLoading(false);
}, [setIsLoading, kbId, toast, router, myKbList, loadKbList]);
setBtnLoading(false);
}, [setBtnLoading, kbId, toast, router, myKbList, loadKbList]);
const saveSubmitSuccess = useCallback(
async (data: KbItemType) => {
@@ -155,7 +156,7 @@ const Detail = ({ kbId }: { kbId: string }) => {
<Box fontWeight={'bold'} fontSize={'2xl'} flex={1}>
</Box>
{KbDetail._id && (
{kbDetail._id && (
<>
<Button
isLoading={btnLoading}
@@ -237,7 +238,6 @@ const Detail = ({ kbId }: { kbId: string }) => {
</Card>
<File onSelect={onSelectFile} />
<ConfirmChild />
<Loading loading={isLoading} fixed={false} />
</Box>
);
};

View File

@@ -27,7 +27,6 @@ import {
Table,
Thead,
Tbody,
Tfoot,
Tr,
Th,
Td,
@@ -48,22 +47,24 @@ import { useRouter } from 'next/router';
import { defaultShareChat } from '@/constants/model';
import type { ShareChatEditType } from '@/types/model';
import type { ModelSchema } from '@/types/mongoSchema';
import { formatTimeToChatTime, useCopyData } from '@/utils/tools';
import { formatTimeToChatTime, useCopyData, getErrText } from '@/utils/tools';
import MyIcon from '@/components/Icon';
import { useGlobalStore } from '@/store/global';
import { useUserStore } from '@/store/user';
import type { KbItemType } from '@/types/plugin';
const ModelEditForm = ({
formHooks,
isOwner,
canRead,
handleDelModel
}: {
formHooks: UseFormReturn<ModelSchema>;
isOwner: boolean;
canRead: boolean;
handleDelModel: () => void;
}) => {
const { modelId } = useRouter().query as { modelId: string };
const router = useRouter();
const { modelId } = router.query as { modelId: string };
const [refresh, setRefresh] = useState(false);
const { toast } = useToast();
const { setLoading } = useGlobalStore();
@@ -112,7 +113,7 @@ const ModelEditForm = ({
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : '头像选择异常',
title: getErrText(err, '头像选择异常'),
status: 'warning'
});
}
@@ -144,8 +145,12 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
copyData(url, '已复制分享地址');
resetShareChat(defaultShareChat);
} catch (error) {
console.log(error);
} catch (err) {
toast({
title: getErrText(err, '创建分享链接异常'),
status: 'warning'
});
console.log(err);
}
setLoading(false);
},
@@ -156,7 +161,8 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
onCloseCreateShareChat,
refetchShareChatList,
resetShareChat,
setLoading
setLoading,
toast
]
);
@@ -175,7 +181,13 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<>
{kbs.map((item) =>
item ? (
<Card key={item._id} p={3} mt={3}>
<Card
key={item._id}
p={3}
mt={3}
cursor={'pointer'}
onClick={() => router.push(`/kb?kbId=${item._id}`)}
>
<Flex alignItems={'center'}>
<Image
src={item.avatar}
@@ -193,7 +205,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
)}
</>
);
}, [getValues, kbList]);
}, [getValues, kbList, router]);
return (
<>
@@ -202,13 +214,13 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<Box fontWeight={'bold'}></Box>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 80px'} w={0}>
modelId:
modelId
</Box>
<Box>{getValues('_id')}</Box>
<Box userSelect={'all'}>{getValues('_id')}</Box>
</Flex>
<Flex mt={4} alignItems={'center'}>
<Box flex={'0 0 80px'} w={0}>
:
</Box>
<Image
src={getValues('avatar') || '/icon/logo.png'}
@@ -224,7 +236,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<FormControl mt={4}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'} w={0}>
:
</Box>
<Input
isDisabled={!isOwner}
@@ -237,7 +249,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<Flex alignItems={'center'} mt={5}>
<Box flex={'0 0 80px'} w={0}>
:
</Box>
<Select
isDisabled={!isOwner}
@@ -256,7 +268,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
</Flex>
<Flex alignItems={'center'} mt={5}>
<Box flex={'0 0 80px'} w={0}>
:
</Box>
<Box>
{formatPrice(ChatModelMap[getValues('chat.chatModel')]?.price, 1000)}
@@ -271,7 +283,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
</Flex>
{isOwner && (
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 120px'}>AI和知识库</Box>
<Box flex={'0 0 100px'}>AI助手</Box>
<Button
colorScheme={'gray'}
variant={'outline'}
@@ -284,80 +296,82 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
)}
</Card>
{/* model effect */}
<Card p={4}>
<Box fontWeight={'bold'}></Box>
<FormControl mt={4}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'} w={0}>
<Box as={'span'} mr={2}>
{canRead && (
<Card p={4}>
<Box fontWeight={'bold'}></Box>
<FormControl mt={4}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'} w={0}>
<Box as={'span'} mr={2}>
</Box>
<Tooltip label={'温度越高,模型的发散能力越强;温度越低,内容越严谨。'}>
<QuestionOutlineIcon />
</Tooltip>
</Box>
<Tooltip label={'温度越高,模型的发散能力越强;温度越低,内容越严谨。'}>
<QuestionOutlineIcon />
</Tooltip>
</Box>
<Slider
aria-label="slider-ex-1"
min={0}
max={10}
step={1}
value={getValues('chat.temperature')}
isDisabled={!isOwner}
onChange={(e) => {
setValue('chat.temperature', e);
setRefresh(!refresh);
}}
>
<SliderMark
<Slider
aria-label="slider-ex-1"
min={0}
max={10}
step={1}
value={getValues('chat.temperature')}
textAlign="center"
bg="myBlue.600"
color="white"
w={'18px'}
h={'18px'}
borderRadius={'100px'}
fontSize={'xs'}
transform={'translate(-50%, -200%)'}
isDisabled={!isOwner}
onChange={(e) => {
setValue('chat.temperature', e);
setRefresh(!refresh);
}}
>
{getValues('chat.temperature')}
</SliderMark>
<SliderTrack>
<SliderFilledTrack bg={'myBlue.700'} />
</SliderTrack>
<SliderThumb />
</Slider>
</Flex>
</FormControl>
{getValues('chat.relatedKbs').length > 0 && (
<Flex mt={4} alignItems={'center'}>
<Box mr={4} whiteSpace={'nowrap'}>
&emsp;
</Box>
<Select
isDisabled={!isOwner}
{...register('chat.searchMode', { required: '搜索模式不能为空' })}
>
{Object.entries(ModelVectorSearchModeMap).map(([key, { text }]) => (
<option key={key} value={key}>
{text}
</option>
))}
</Select>
</Flex>
)}
<SliderMark
value={getValues('chat.temperature')}
textAlign="center"
bg="myBlue.600"
color="white"
w={'18px'}
h={'18px'}
borderRadius={'100px'}
fontSize={'xs'}
transform={'translate(-50%, -200%)'}
>
{getValues('chat.temperature')}
</SliderMark>
<SliderTrack>
<SliderFilledTrack bg={'myBlue.700'} />
</SliderTrack>
<SliderThumb />
</Slider>
</Flex>
</FormControl>
{getValues('chat.relatedKbs').length > 0 && (
<Flex mt={4} alignItems={'center'}>
<Box mr={4} whiteSpace={'nowrap'}>
&emsp;
</Box>
<Select
isDisabled={!isOwner}
{...register('chat.searchMode', { required: '搜索模式不能为空' })}
>
{Object.entries(ModelVectorSearchModeMap).map(([key, { text }]) => (
<option key={key} value={key}>
{text}
</option>
))}
</Select>
</Flex>
)}
<Box mt={4}>
<Box mb={1}></Box>
<Textarea
rows={8}
maxLength={-1}
isDisabled={!isOwner}
placeholder={'模型默认的 prompt 词,通过调整该内容,可以引导模型聊天方向。'}
{...register('chat.systemPrompt')}
/>
</Box>
</Card>
<Box mt={4}>
<Box mb={1}></Box>
<Textarea
rows={8}
maxLength={-1}
isDisabled={!isOwner}
placeholder={'模型默认的 prompt 词,通过调整该内容,可以引导模型聊天方向。'}
{...register('chat.systemPrompt')}
/>
</Box>
</Card>
)}
{isOwner && (
<>
{/* model share setting */}
@@ -419,90 +433,90 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
</Flex>
<RenderSelectedKbList />
</Card>
{/* shareChat */}
<Card p={4} gridColumnStart={1} gridColumnEnd={[2, 3]}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'}>
<Tooltip label="可以直接分享该模型给其他用户去进行对话对方无需登录即可直接进行对话。注意这个功能会消耗你账号的tokens。请保管好链接和密码。">
<QuestionOutlineIcon ml={1} />
</Tooltip>
Beta
</Box>
<Button
size={'sm'}
variant={'outline'}
colorScheme={'myBlue'}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: '最多创建10组'
}
: {})}
onClick={onOpenCreateShareChat}
>
</Button>
</Flex>
<TableContainer mt={1} minH={'100px'}>
<Table variant={'simple'} w={'100%'}>
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th></Th>
<Th>tokens消耗</Th>
<Th>使</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{shareChatList.map((item) => (
<Tr key={item._id}>
<Td>{item.name}</Td>
<Td>{item.password === '1' ? '已开启' : '未使用'}</Td>
<Td>{item.maxContext}</Td>
<Td>{formatTokens(item.tokens)}</Td>
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
<Td>
<Flex>
<MyIcon
mr={3}
name="copy"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'myBlue.600' }}
onClick={() => {
const url = `${location.origin}/chat/share?shareId=${item._id}`;
copyData(url, '已复制分享地址');
}}
/>
<MyIcon
name="delete"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'red' }}
onClick={async () => {
setLoading(true);
try {
await delShareChatById(item._id);
refetchShareChatList();
} catch (error) {
console.log(error);
}
setLoading(false);
}}
/>
</Flex>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Card>
</>
)}
{/* shareChat */}
<Card p={4} gridColumnStart={1} gridColumnEnd={[2, 3]}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'}>
<Tooltip label="可以直接分享该模型给其他用户去进行对话对方无需登录即可直接进行对话。注意这个功能会消耗你账号的tokens。请保管好链接和密码。">
<QuestionOutlineIcon ml={1} />
</Tooltip>
Beta
</Box>
<Button
size={'sm'}
variant={'outline'}
colorScheme={'myBlue'}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: '最多创建10组'
}
: {})}
onClick={onOpenCreateShareChat}
>
</Button>
</Flex>
<TableContainer mt={1} minH={'100px'}>
<Table variant={'simple'} w={'100%'}>
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th></Th>
<Th>tokens消耗</Th>
<Th>使</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{shareChatList.map((item) => (
<Tr key={item._id}>
<Td>{item.name}</Td>
<Td>{item.password === '1' ? '已开启' : '未使用'}</Td>
<Td>{item.maxContext}</Td>
<Td>{formatTokens(item.tokens)}</Td>
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
<Td>
<Flex>
<MyIcon
mr={3}
name="copy"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'myBlue.600' }}
onClick={() => {
const url = `${location.origin}/chat/share?shareId=${item._id}`;
copyData(url, '已复制分享地址');
}}
/>
<MyIcon
name="delete"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'red' }}
onClick={async () => {
setLoading(true);
try {
await delShareChatById(item._id);
refetchShareChatList();
} catch (error) {
console.log(error);
}
setLoading(false);
}}
/>
</Flex>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Card>
{/* create shareChat modal */}
<Modal isOpen={isOpenCreateShareChat} onClose={onCloseCreateShareChat}>
<ModalOverlay />

View File

@@ -121,12 +121,17 @@ const ModelDetail = ({ modelId, isPc }: { modelId: string; isPc: boolean }) => {
);
useEffect(() => {
return () => {
saveUpdateModel();
window.onbeforeunload = (e) => {
e.preventDefault();
e.returnValue = '内容已修改,确认离开页面吗?';
};
}, []);
return canRead ? (
return () => {
window.onbeforeunload = null;
};
}, [router]);
return (
<Box h={'100%'} p={5} overflow={'overlay'} position={'relative'}>
{/* 头部 */}
<Card px={6} py={3}>
@@ -197,14 +202,15 @@ const ModelDetail = ({ modelId, isPc }: { modelId: string; isPc: boolean }) => {
)}
</Card>
<Grid mt={5} gridTemplateColumns={['1fr', '1fr 1fr']} gridGap={5}>
<ModelEditForm formHooks={formHooks} handleDelModel={handleDelModel} isOwner={isOwner} />
<ModelEditForm
formHooks={formHooks}
handleDelModel={handleDelModel}
isOwner={isOwner}
canRead={canRead}
/>
</Grid>
<Loading loading={isLoading} fixed={false} />
</Box>
) : (
<Box h={'100%'} p={5}>
</Box>
);
};