google login and power share link (#292)
This commit is contained in:
@@ -124,10 +124,12 @@ const UserInfo = () => {
|
||||
h={['44px', '54px']}
|
||||
borderRadius={'50%'}
|
||||
border={theme.borders.base}
|
||||
overflow={'hidden'}
|
||||
p={'2px'}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
mb={2}
|
||||
>
|
||||
<Avatar src={userInfo?.avatar} w={'100%'} h={'100%'} />
|
||||
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser, authApp, authShareChat, AuthUserTypeEnum } from '@/service/utils/auth';
|
||||
import { authUser, authApp } from '@/service/utils/auth';
|
||||
import { sseErrRes, jsonRes } from '@/service/response';
|
||||
import { addLog, withNextCors } from '@/service/utils/tools';
|
||||
import { ChatRoleEnum, ChatSourceEnum, sseResponseEventEnum } from '@/constants/chat';
|
||||
@@ -29,6 +29,8 @@ import { ChatHistoryItemResType } from '@/types/chat';
|
||||
import { UserModelSchema } from '@/types/mongoSchema';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { getSystemTime } from '@/utils/user';
|
||||
import { authOutLinkChat } from '@/service/support/outLink/auth';
|
||||
import requestIp from 'request-ip';
|
||||
|
||||
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
|
||||
type FastGptWebChatProps = {
|
||||
@@ -82,14 +84,17 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
let startTime = Date.now();
|
||||
|
||||
/* user auth */
|
||||
const {
|
||||
let {
|
||||
// @ts-ignore
|
||||
responseDetail,
|
||||
user,
|
||||
userId,
|
||||
appId: authAppid,
|
||||
authType
|
||||
} = await (shareId
|
||||
? authShareChat({
|
||||
shareId
|
||||
? authOutLinkChat({
|
||||
shareId,
|
||||
ip: requestIp.getClientIp(req)
|
||||
})
|
||||
: authUser({ req, authBalance: true }));
|
||||
|
||||
@@ -112,6 +117,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
]);
|
||||
|
||||
const isOwner = !shareId && userId === String(app.userId);
|
||||
responseDetail = isOwner || responseDetail;
|
||||
|
||||
const prompts = history.concat(gptMessage2ChatType(messages));
|
||||
if (prompts[prompts.length - 1].obj === 'AI') {
|
||||
@@ -157,7 +163,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
appId,
|
||||
userId,
|
||||
variables,
|
||||
isOwner,
|
||||
isOwner, // owner update use time
|
||||
shareId,
|
||||
source: (() => {
|
||||
if (shareId) {
|
||||
@@ -197,7 +203,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
data: '[DONE]'
|
||||
});
|
||||
|
||||
if (isOwner && detail) {
|
||||
if (responseDetail && detail) {
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.appStreamResponse,
|
||||
|
||||
@@ -23,7 +23,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const thirtyMinutesAgo = new Date(
|
||||
Date.now() - (global.feConfigs?.exportLimitMinutes || 0) * 60 * 1000
|
||||
Date.now() - (global.feConfigs?.limit?.exportLimitMinutes || 0) * 60 * 1000
|
||||
);
|
||||
|
||||
// auth export times
|
||||
@@ -39,7 +39,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
);
|
||||
|
||||
if (!authTimes) {
|
||||
const minutes = `${global.feConfigs?.exportLimitMinutes || 0} 分钟`;
|
||||
const minutes = `${global.feConfigs?.limit?.exportLimitMinutes || 0} 分钟`;
|
||||
throw new Error(`上次导出未到 ${minutes},每 ${minutes}仅可导出一次。`);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const files = await bucket
|
||||
// 1 hours expired
|
||||
.find({
|
||||
uploadDate: { $lte: new Date(Date.now() - 60 * 1000) },
|
||||
uploadDate: { $lte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) },
|
||||
['metadata.kbId']: kbId,
|
||||
['metadata.userId']: userId
|
||||
})
|
||||
|
||||
@@ -5,14 +5,19 @@ import { authUser } from '@/service/utils/auth';
|
||||
import { GridFSStorage } from '@/service/lib/gridfs';
|
||||
import { PgClient } from '@/service/pg';
|
||||
import { PgTrainingTableName } from '@/constants/plugin';
|
||||
import { KbFileItemType } from '@/types/plugin';
|
||||
import { FileStatusEnum, OtherFileId } from '@/constants/kb';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
let { kbId, searchText } = req.query as { kbId: string; searchText: string };
|
||||
let {
|
||||
pageNum = 1,
|
||||
pageSize = 10,
|
||||
kbId,
|
||||
searchText
|
||||
} = req.body as { pageNum: number; pageSize: number; kbId: string; searchText: string };
|
||||
searchText = searchText.replace(/'/g, '');
|
||||
|
||||
// 凭证校验
|
||||
@@ -21,10 +26,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const gridFs = new GridFSStorage('dataset', userId);
|
||||
const bucket = gridFs.GridFSBucket();
|
||||
|
||||
const files = await bucket
|
||||
.find({ ['metadata.kbId']: kbId, ...(searchText && { filename: { $regex: searchText } }) })
|
||||
.sort({ _id: -1 })
|
||||
.toArray();
|
||||
const mongoWhere = {
|
||||
['metadata.kbId']: kbId,
|
||||
...(searchText && { filename: { $regex: searchText } })
|
||||
};
|
||||
const [files, total] = await Promise.all([
|
||||
bucket
|
||||
.find(mongoWhere)
|
||||
.sort({ _id: -1 })
|
||||
.skip((pageNum - 1) * pageSize)
|
||||
.limit(pageSize)
|
||||
.toArray(),
|
||||
mongoose.connection.db.collection('dataset.files').countDocuments(mongoWhere)
|
||||
]);
|
||||
|
||||
async function GetOtherData() {
|
||||
return {
|
||||
@@ -72,8 +86,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
})
|
||||
]);
|
||||
|
||||
jsonRes<KbFileItemType[]>(res, {
|
||||
data: data.flat().filter((item) => item.chunkLength > 0)
|
||||
jsonRes(res, {
|
||||
data: {
|
||||
pageNum,
|
||||
pageSize,
|
||||
data: data.flat().filter((item) => item.chunkLength > 0),
|
||||
total
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, OutLink } from '@/service/mongo';
|
||||
import { authApp, authUser } from '@/service/utils/auth';
|
||||
import type { ShareChatEditType } from '@/types/app';
|
||||
import type { OutLinkEditType } from '@/types/support/outLink';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { OutLinkTypeEnum } from '@/constants/chat';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
|
||||
@@ -10,8 +10,9 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
|
||||
/* create a shareChat */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { appId, name } = req.body as ShareChatEditType & {
|
||||
const { appId, ...props } = req.body as OutLinkEditType & {
|
||||
appId: string;
|
||||
type: `${OutLinkTypeEnum}`;
|
||||
};
|
||||
|
||||
await connectToDatabase();
|
||||
@@ -28,8 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
shareId,
|
||||
userId,
|
||||
appId,
|
||||
name,
|
||||
type: OutLinkTypeEnum.share
|
||||
...props
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
@@ -6,12 +6,12 @@ import { authUser } from '@/service/utils/auth';
|
||||
/* delete a shareChat by shareChatId */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
const { id } = req.query as {
|
||||
id: string;
|
||||
};
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
await OutLink.findOneAndRemove({
|
||||
@@ -6,7 +6,7 @@ import { authApp } from '@/service/utils/auth';
|
||||
import { HUMAN_ICON } from '@/constants/chat';
|
||||
import { getChatModelNameList, getSpecialModule } from '@/components/ChatBox/utils';
|
||||
|
||||
/* 初始化我的聊天框,需要身份验证 */
|
||||
/* init share chat window */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
let { shareId } = req.query as {
|
||||
@@ -7,12 +7,12 @@ import { hashPassword } from '@/service/utils/tools';
|
||||
/* get shareChat list by appId */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
const { appId } = req.query as {
|
||||
appId: string;
|
||||
};
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const data = await OutLink.find({
|
||||
@@ -22,15 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
_id: -1
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: data.map((item) => ({
|
||||
_id: item._id,
|
||||
shareId: item.shareId,
|
||||
name: item.name,
|
||||
total: item.total,
|
||||
lastTime: item.lastTime
|
||||
}))
|
||||
});
|
||||
jsonRes(res, { data });
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
25
client/src/pages/api/support/outLink/update.ts
Normal file
25
client/src/pages/api/support/outLink/update.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, OutLink } from '@/service/mongo';
|
||||
import type { OutLinkEditType } from '@/types/support/outLink';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
const { _id, name, responseDetail, limit } = req.body as OutLinkEditType & {};
|
||||
|
||||
await OutLink.findByIdAndUpdate(_id, {
|
||||
name,
|
||||
responseDetail,
|
||||
limit
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,9 @@ const defaultFeConfigs: FeConfigsType = {
|
||||
show_doc: true,
|
||||
systemTitle: 'FastGPT',
|
||||
authorText: 'Made by FastGPT Team.',
|
||||
exportLimitMinutes: 0,
|
||||
limit: {
|
||||
exportLimitMinutes: 0
|
||||
},
|
||||
scripts: []
|
||||
};
|
||||
const defaultChatModels = [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
@@ -10,45 +10,46 @@ import {
|
||||
Th,
|
||||
Td,
|
||||
Tbody,
|
||||
useDisclosure,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
FormControl,
|
||||
Input,
|
||||
useTheme
|
||||
useTheme,
|
||||
Switch,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem
|
||||
} from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { useLoading } from '@/hooks/useLoading';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getShareChatList, delShareChatById, createShareChat } from '@/api/chat';
|
||||
import {
|
||||
getShareChatList,
|
||||
delShareChatById,
|
||||
createShareChat,
|
||||
putShareChat
|
||||
} from '@/api/support/outLink';
|
||||
import { formatTimeToChatTime, useCopyData } from '@/utils/tools';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { defaultShareChat } from '@/constants/model';
|
||||
import type { ShareChatEditType } from '@/types/app';
|
||||
import { defaultOutLinkForm } from '@/constants/model';
|
||||
import type { OutLinkEditType } from '@/types/support/outLink';
|
||||
import { useRequest } from '@/hooks/useRequest';
|
||||
import { formatPrice } from '@/utils/user';
|
||||
import { OutLinkTypeEnum } from '@/constants/chat';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import MyRadio from '@/components/Radio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const Share = ({ appId }: { appId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const { copyData } = useCopyData();
|
||||
const {
|
||||
isOpen: isOpenCreateShareChat,
|
||||
onOpen: onOpenCreateShareChat,
|
||||
onClose: onCloseCreateShareChat
|
||||
} = useDisclosure();
|
||||
const {
|
||||
register: registerShareChat,
|
||||
getValues: getShareChatValues,
|
||||
setValue: setShareChatValues,
|
||||
handleSubmit: submitShareChat,
|
||||
reset: resetShareChat
|
||||
} = useForm({
|
||||
defaultValues: defaultShareChat
|
||||
});
|
||||
const [editLinkData, setEditLinkData] = useState<OutLinkEditType>();
|
||||
const { toast } = useToast();
|
||||
|
||||
const {
|
||||
isFetching,
|
||||
@@ -56,22 +57,6 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
refetch: refetchShareChatList
|
||||
} = useQuery(['initShareChatList', appId], () => getShareChatList(appId));
|
||||
|
||||
const { mutate: onclickCreateShareChat, isLoading: creating } = useRequest({
|
||||
mutationFn: async (e: ShareChatEditType) =>
|
||||
createShareChat({
|
||||
...e,
|
||||
appId
|
||||
}),
|
||||
errorToast: '创建分享链接异常',
|
||||
onSuccess(id) {
|
||||
onCloseCreateShareChat();
|
||||
refetchShareChatList();
|
||||
const url = `${location.origin}/chat/share?shareId=${id}`;
|
||||
copyData(url, '创建成功。已复制分享地址,可直接分享使用');
|
||||
resetShareChat(defaultShareChat);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Box position={'relative'} pt={[3, 5, 8]} px={[5, 8]} minH={'50vh'}>
|
||||
<Flex justifyContent={'space-between'}>
|
||||
@@ -79,7 +64,7 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
免登录窗口
|
||||
<MyTooltip
|
||||
forceShow
|
||||
label="可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的tokens。请保管好链接和密码。"
|
||||
label="可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的余额,请保管好链接!"
|
||||
>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
@@ -94,7 +79,7 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
title: '最多创建10组'
|
||||
}
|
||||
: {})}
|
||||
onClick={onOpenCreateShareChat}
|
||||
onClick={() => setEditLinkData(defaultOutLinkForm)}
|
||||
>
|
||||
创建新链接
|
||||
</Button>
|
||||
@@ -105,8 +90,14 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
<Tr>
|
||||
<Th>名称</Th>
|
||||
<Th>金额消耗</Th>
|
||||
<>
|
||||
<Th>金额限制</Th>
|
||||
<Th>IP限流(人/分钟)</Th>
|
||||
</>
|
||||
<Th>返回详情</Th>
|
||||
<Th>过期时间</Th>
|
||||
<Th>最后使用时间</Th>
|
||||
<Th>操作</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -114,60 +105,90 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
<Tr key={item._id}>
|
||||
<Td>{item.name}</Td>
|
||||
<Td>{formatPrice(item.total)}元</Td>
|
||||
{item.limit && (
|
||||
<>
|
||||
<Td>{item.limit?.credit > -1 ? `${item.limit?.credit}元` : '无限制'}</Td>
|
||||
<Td>{item.limit?.QPM}</Td>
|
||||
</>
|
||||
)}
|
||||
<Td>{item.responseDetail ? '✔' : '✖'}</Td>
|
||||
<Td>
|
||||
{item.limit?.expiredTime
|
||||
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
|
||||
: '-'}
|
||||
</Td>
|
||||
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
<MyTooltip label={'嵌入网页'}>
|
||||
<MyIcon
|
||||
mr={4}
|
||||
name="apiLight"
|
||||
w={'14px'}
|
||||
<Menu autoSelect={false} isLazy>
|
||||
<MenuButton
|
||||
_hover={{ bg: 'myWhite.600 ' }}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() => {
|
||||
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
|
||||
const src = `${location.origin}/js/iframe.js`;
|
||||
const script = `<script src="${src}" id="fastgpt-iframe" data-src="${url}" data-color="#4e83fd"></script>`;
|
||||
copyData(script, '已复制嵌入 Script,可在应用 HTML 底部嵌入', 3000);
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<MyTooltip label={'复制分享链接'}>
|
||||
<MyIcon
|
||||
mr={4}
|
||||
name="copy"
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() => {
|
||||
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
|
||||
copyData(url, '已复制分享链接,可直接分享使用');
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<MyTooltip label={'删除链接'}>
|
||||
<MyIcon
|
||||
name="delete"
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red' }}
|
||||
onClick={async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await delShareChatById(item._id);
|
||||
refetchShareChatList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<MyIcon name={'more'} w={'14px'} p={2} />
|
||||
</MenuButton>
|
||||
<MenuList color={'myGray.700'} minW={`120px !important`} zIndex={10}>
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
setEditLinkData({
|
||||
_id: item._id,
|
||||
name: item.name,
|
||||
responseDetail: item.responseDetail,
|
||||
limit: item.limit
|
||||
})
|
||||
}
|
||||
setIsLoading(false);
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
py={[2, 3]}
|
||||
>
|
||||
<MyIcon name={'edit'} w={['14px', '16px']} />
|
||||
<Box ml={[1, 2]}>{t('common.Edit')}</Box>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
|
||||
copyData(url, '已复制分享链接,可直接分享使用');
|
||||
}}
|
||||
py={[2, 3]}
|
||||
>
|
||||
<MyIcon name={'copy'} w={['14px', '16px']} />
|
||||
<Box ml={[1, 2]}>{t('common.Copy')}</Box>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
|
||||
const src = `${location.origin}/js/iframe.js`;
|
||||
const script = `<script src="${src}" id="fastgpt-iframe" data-src="${url}" data-color="#4e83fd"></script>`;
|
||||
copyData(script, '已复制嵌入 Script,可在应用 HTML 底部嵌入', 3000);
|
||||
}}
|
||||
py={[2, 3]}
|
||||
>
|
||||
<MyIcon name={'apiLight'} w={['14px', '16px']} />
|
||||
<Box ml={[1, 2]}>{t('outlink.Copy Iframe')}</Box>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await delShareChatById(item._id);
|
||||
refetchShareChatList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}}
|
||||
py={[2, 3]}
|
||||
>
|
||||
<MyIcon name={'delete'} w={['14px', '16px']} />
|
||||
<Box ml={[1, 2]}>{t('common.Delete')}</Box>
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{shareChatList.length === 0 && !isFetching && (
|
||||
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
@@ -176,56 +197,185 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{/* create shareChat modal */}
|
||||
<MyModal
|
||||
isOpen={isOpenCreateShareChat}
|
||||
onClose={onCloseCreateShareChat}
|
||||
title={'创建免登录窗口'}
|
||||
>
|
||||
<ModalBody>
|
||||
<FormControl>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 60px'} w={0}>
|
||||
名称:
|
||||
</Box>
|
||||
<Input
|
||||
placeholder="记录名字,仅用于展示"
|
||||
maxLength={20}
|
||||
{...registerShareChat('name', {
|
||||
required: '记录名称不能为空'
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onCloseCreateShareChat}>
|
||||
取消
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
isLoading={creating}
|
||||
onClick={submitShareChat((data) => onclickCreateShareChat(data))}
|
||||
>
|
||||
确认
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
{!!editLinkData && (
|
||||
<EditLinkModal
|
||||
appId={appId}
|
||||
type={'share'}
|
||||
defaultData={editLinkData}
|
||||
onCreate={(id) => {
|
||||
const url = `${location.origin}/chat/share?shareId=${id}`;
|
||||
copyData(url, '创建成功。已复制分享地址,可直接分享使用');
|
||||
refetchShareChatList();
|
||||
setEditLinkData(undefined);
|
||||
}}
|
||||
onEdit={() => {
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('common.Update Successful')
|
||||
});
|
||||
refetchShareChatList();
|
||||
setEditLinkData(undefined);
|
||||
}}
|
||||
onClose={() => setEditLinkData(undefined)}
|
||||
/>
|
||||
)}
|
||||
<Loading loading={isFetching} fixed={false} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
enum LinkTypeEnum {
|
||||
share = 'share',
|
||||
iframe = 'iframe'
|
||||
// edit link modal
|
||||
export function EditLinkModal({
|
||||
appId,
|
||||
type,
|
||||
defaultData,
|
||||
onClose,
|
||||
onCreate,
|
||||
onEdit
|
||||
}: {
|
||||
appId: string;
|
||||
type: `${OutLinkTypeEnum}`;
|
||||
defaultData: OutLinkEditType;
|
||||
onClose: () => void;
|
||||
onCreate: (id: string) => void;
|
||||
onEdit: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const isEdit = useMemo(() => !!defaultData._id, [defaultData]);
|
||||
const titleMap = useRef({
|
||||
create: {
|
||||
[OutLinkTypeEnum.share]: t('outlink.Create Share Window'),
|
||||
[OutLinkTypeEnum.iframe]: t('outlink.Create Ifrme Window')
|
||||
},
|
||||
edit: {
|
||||
[OutLinkTypeEnum.share]: t('outlink.Edit Share Window'),
|
||||
[OutLinkTypeEnum.iframe]: t('outlink.Edit Ifrme Link')
|
||||
}
|
||||
});
|
||||
const {
|
||||
register,
|
||||
setValue,
|
||||
handleSubmit: submitShareChat
|
||||
} = useForm({
|
||||
defaultValues: defaultData
|
||||
});
|
||||
|
||||
const { mutate: onclickCreate, isLoading: creating } = useRequest({
|
||||
mutationFn: async (e: OutLinkEditType) =>
|
||||
createShareChat({
|
||||
...e,
|
||||
appId,
|
||||
type
|
||||
}),
|
||||
errorToast: '创建链接异常',
|
||||
onSuccess: onCreate
|
||||
});
|
||||
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
|
||||
mutationFn: (e: OutLinkEditType) => {
|
||||
console.log(e);
|
||||
return putShareChat(e);
|
||||
},
|
||||
errorToast: '更新链接异常',
|
||||
onSuccess: onEdit
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={() => {}}
|
||||
title={isEdit ? titleMap.current.edit[type] : titleMap.current.create[type]}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 90px'}>{t('Name')}:</Box>
|
||||
<Input
|
||||
placeholder={t('outlink.Link Name') || 'Link Name'}
|
||||
maxLength={20}
|
||||
{...register('name', {
|
||||
required: t('common.Name is empty') || 'Name is empty'
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
QPM:
|
||||
<MyTooltip label={t('outlink.QPM Tips' || '')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Input
|
||||
max={1000}
|
||||
{...register('limit.QPM', {
|
||||
min: 0,
|
||||
max: 1000,
|
||||
valueAsNumber: true,
|
||||
required: t('outlink.QPM is empty') || ''
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('outlink.Max credit')}:
|
||||
<MyTooltip label={t('outlink.Max credit tips' || '')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Input
|
||||
{...register('limit.credit', {
|
||||
min: -1,
|
||||
max: 1000,
|
||||
valueAsNumber: true,
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('common.Expired Time')}:
|
||||
</Flex>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
defaultValue={
|
||||
defaultData.limit?.expiredTime
|
||||
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => {
|
||||
setValue('limit.expiredTime', new Date(e.target.value));
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('outlink.Response Detail')}:
|
||||
<MyTooltip label={t('outlink.Response Detail tips' || '')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Switch {...register('responseDetail')} size={'lg'} />
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
isLoading={creating || updating}
|
||||
onClick={submitShareChat((data) => (isEdit ? onclickUpdate(data) : onclickCreate(data)))}
|
||||
>
|
||||
确认
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
const OutLink = ({ appId }: { appId: string }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [linkType, setLinkType] = useState<`${LinkTypeEnum}`>(LinkTypeEnum.share);
|
||||
const [linkType, setLinkType] = useState<`${OutLinkTypeEnum}`>(OutLinkTypeEnum.share);
|
||||
|
||||
return (
|
||||
<Box pt={[1, 5]}>
|
||||
@@ -241,21 +391,21 @@ const OutLink = ({ appId }: { appId: string }) => {
|
||||
icon: 'outlink_share',
|
||||
title: '免登录窗口',
|
||||
desc: '分享链接给其他用户,无需登录即可直接进行使用',
|
||||
value: LinkTypeEnum.share
|
||||
value: OutLinkTypeEnum.share
|
||||
}
|
||||
// {
|
||||
// icon: 'outlink_iframe',
|
||||
// title: '网页嵌入',
|
||||
// desc: '嵌入到已有网页中,右下角会生成对话按键',
|
||||
// value: LinkTypeEnum.iframe
|
||||
// value: OutLinkTypeEnum.iframe
|
||||
// }
|
||||
]}
|
||||
value={linkType}
|
||||
onChange={(e) => setLinkType(e as `${LinkTypeEnum}`)}
|
||||
onChange={(e) => setLinkType(e as `${OutLinkTypeEnum}`)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{linkType === LinkTypeEnum.share && <Share appId={appId} />}
|
||||
{linkType === OutLinkTypeEnum.share && <Share appId={appId} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { initShareChatInfo } from '@/api/chat';
|
||||
import { initShareChatInfo } from '@/api/support/outLink';
|
||||
import { Box, Flex, useDisclosure, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
@@ -65,22 +65,6 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
||||
|
||||
const [editInputData, setEditInputData] = useState<InputDataType>();
|
||||
|
||||
const { data: { qaListLen = 0, vectorListLen = 0 } = {}, refetch: refetchTrainingData } =
|
||||
useQuery(['getModelSplitDataList', kbId], () => getTrainingData({ kbId, init: false }), {
|
||||
onError(err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
|
||||
const refetchData = useCallback(
|
||||
(num = pageNum) => {
|
||||
getData(num);
|
||||
refetchTrainingData();
|
||||
return null;
|
||||
},
|
||||
[getData, pageNum, refetchTrainingData]
|
||||
);
|
||||
|
||||
// get first page data
|
||||
const getFirstData = useCallback(
|
||||
debounce(() => {
|
||||
@@ -90,12 +74,6 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
||||
[]
|
||||
);
|
||||
|
||||
// interval get data
|
||||
useQuery(['refetchData'], () => refetchData(1), {
|
||||
refetchInterval: 5000,
|
||||
enabled: qaListLen > 0 || vectorListLen > 0
|
||||
});
|
||||
|
||||
// get file info
|
||||
const { data: fileInfo } = useQuery(['getFileInfo', fileId], () => getFileInfoById(fileId));
|
||||
const fileIcon = useMemo(
|
||||
@@ -122,7 +100,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
||||
filename
|
||||
});
|
||||
},
|
||||
successToast: `导出成功,下次导出需要 ${feConfigs.exportLimitMinutes} 分钟后`,
|
||||
successToast: `导出成功,下次导出需要 ${feConfigs?.limit?.exportLimitMinutes} 分钟后`,
|
||||
errorToast: '导出异常'
|
||||
});
|
||||
|
||||
@@ -136,7 +114,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
||||
fontSize={['sm', 'md']}
|
||||
alignItems={'center'}
|
||||
>
|
||||
<Image src={fileIcon} w={'16px'} mr={2} alt={''} />
|
||||
<Image src={fileIcon || '/imgs/files/file.svg'} w={'16px'} mr={2} alt={''} />
|
||||
{t(fileInfo?.filename || 'Filename')}
|
||||
</Flex>
|
||||
<Button
|
||||
@@ -172,15 +150,6 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
||||
<Box as={'span'} fontSize={['md', 'lg']}>
|
||||
{total}组
|
||||
</Box>
|
||||
<Box as={'span'}>
|
||||
{(qaListLen > 0 || vectorListLen > 0) && (
|
||||
<>
|
||||
({qaListLen > 0 ? `${qaListLen}条数据正在拆分,` : ''}
|
||||
{vectorListLen > 0 ? `${vectorListLen}条数据正在生成索引,` : ''}
|
||||
请耐心等待... )
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flex={1} mr={1} />
|
||||
<MyInput
|
||||
@@ -262,7 +231,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
await delOneKbDataByDataId(item.id);
|
||||
refetchData(pageNum);
|
||||
getData(pageNum);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: getErrText(error),
|
||||
@@ -297,7 +266,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
||||
kbId={kbId}
|
||||
defaultValues={editInputData}
|
||||
onClose={() => setEditInputData(undefined)}
|
||||
onSuccess={() => refetchData()}
|
||||
onSuccess={() => getData(pageNum)}
|
||||
/>
|
||||
)}
|
||||
<ConfirmModal />
|
||||
|
||||
@@ -11,9 +11,8 @@ import {
|
||||
Tbody,
|
||||
Image
|
||||
} from '@chakra-ui/react';
|
||||
import { getKbFiles, deleteKbFileById } from '@/api/plugins/kb';
|
||||
import { getKbFiles, deleteKbFileById, getTrainingData } from '@/api/plugins/kb';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { debounce } from 'lodash';
|
||||
import { formatFileSize } from '@/utils/tools';
|
||||
import { useConfirm } from '@/hooks/useConfirm';
|
||||
@@ -26,30 +25,47 @@ import { useRequest } from '@/hooks/useRequest';
|
||||
import { useLoading } from '@/hooks/useLoading';
|
||||
import { FileStatusEnum } from '@/constants/kb';
|
||||
import { useRouter } from 'next/router';
|
||||
import { usePagination } from '@/hooks/usePagination';
|
||||
import { KbFileItemType } from '@/types/plugin';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
const FileCard = ({ kbId }: { kbId: string }) => {
|
||||
const BoxRef = useRef<HTMLDivElement>(null);
|
||||
const lastSearch = useRef('');
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const { Loading } = useLoading();
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const { setLoading } = useGlobalStore();
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
content: t('kb.Confirm to delete the file')
|
||||
});
|
||||
|
||||
const {
|
||||
data: files = [],
|
||||
refetch,
|
||||
isInitialLoading
|
||||
} = useQuery(['getFiles', kbId], () => getKbFiles({ kbId, searchText }), {
|
||||
refetchInterval: 6000,
|
||||
refetchOnWindowFocus: true
|
||||
data: files,
|
||||
Pagination,
|
||||
total,
|
||||
isLoading,
|
||||
getData,
|
||||
pageNum,
|
||||
pageSize
|
||||
} = usePagination<KbFileItemType>({
|
||||
api: getKbFiles,
|
||||
pageSize: 40,
|
||||
params: {
|
||||
kbId,
|
||||
searchText
|
||||
},
|
||||
onChange() {
|
||||
if (BoxRef.current) {
|
||||
BoxRef.current.scrollTop = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const debounceRefetch = useCallback(
|
||||
debounce(() => {
|
||||
refetch();
|
||||
getData(1);
|
||||
lastSearch.current = searchText;
|
||||
}, 300),
|
||||
[]
|
||||
@@ -68,14 +84,19 @@ const FileCard = ({ kbId }: { kbId: string }) => {
|
||||
[files]
|
||||
);
|
||||
|
||||
const { mutate: onDeleteFile, isLoading } = useRequest({
|
||||
mutationFn: (fileId: string) =>
|
||||
deleteKbFileById({
|
||||
const { mutate: onDeleteFile } = useRequest({
|
||||
mutationFn: (fileId: string) => {
|
||||
setLoading(true);
|
||||
return deleteKbFileById({
|
||||
fileId,
|
||||
kbId
|
||||
}),
|
||||
});
|
||||
},
|
||||
onSuccess() {
|
||||
refetch();
|
||||
getData(pageNum);
|
||||
},
|
||||
onSettled() {
|
||||
setLoading(false);
|
||||
},
|
||||
successToast: t('common.Delete Success'),
|
||||
errorToast: t('common.Delete Failed')
|
||||
@@ -92,12 +113,37 @@ const FileCard = ({ kbId }: { kbId: string }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// training data
|
||||
const { data: { qaListLen = 0, vectorListLen = 0 } = {}, refetch: refetchTrainingData } =
|
||||
useQuery(['getModelSplitDataList', kbId], () => getTrainingData({ kbId, init: false }), {
|
||||
onError(err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
|
||||
useQuery(['refetchTrainingData'], refetchTrainingData, {
|
||||
refetchInterval: 8000,
|
||||
enabled: qaListLen > 0 || vectorListLen > 0
|
||||
});
|
||||
|
||||
return (
|
||||
<Box ref={BoxRef} position={'relative'} py={[1, 5]} h={'100%'} overflow={'overlay'}>
|
||||
<Flex justifyContent={'space-between'} px={5}>
|
||||
<Box fontWeight={'bold'} fontSize={'lg'} mr={2}>
|
||||
{t('kb.Files', { total: files.length })}
|
||||
<Box>
|
||||
<Box fontWeight={'bold'} fontSize={'lg'} mr={2}>
|
||||
{t('kb.Files', { total: files.length })}
|
||||
</Box>
|
||||
<Box as={'span'} fontSize={'sm'}>
|
||||
{(qaListLen > 0 || vectorListLen > 0) && (
|
||||
<>
|
||||
({qaListLen > 0 ? `${qaListLen}条数据正在拆分,` : ''}
|
||||
{vectorListLen > 0 ? `${vectorListLen}条数据正在生成索引,` : ''}
|
||||
请耐心等待... )
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Flex alignItems={'center'}>
|
||||
<MyInput
|
||||
leftIcon={
|
||||
@@ -112,12 +158,12 @@ const FileCard = ({ kbId }: { kbId: string }) => {
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (searchText === lastSearch.current) return;
|
||||
refetch();
|
||||
getData(1);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (searchText === lastSearch.current) return;
|
||||
if (e.key === 'Enter') {
|
||||
refetch();
|
||||
getData(1);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -198,9 +244,14 @@ const FileCard = ({ kbId }: { kbId: string }) => {
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{total > pageSize && (
|
||||
<Flex mt={2} justifyContent={'center'}>
|
||||
<Pagination />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<ConfirmModal />
|
||||
<Loading loading={isInitialLoading || isLoading} />
|
||||
<Loading loading={isLoading} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,7 +40,11 @@ const QAImport = ({ kbId }: { kbId: string }) => {
|
||||
|
||||
// price count
|
||||
const price = useMemo(() => {
|
||||
return formatPrice(files.reduce((sum, file) => sum + file.tokens, 0) * unitPrice * 1.3);
|
||||
const filesToken = files.reduce((sum, file) => sum + file.tokens, 0);
|
||||
const promptTokens = files.reduce((sum, file) => sum + file.chunks.length, 0) * 139;
|
||||
const totalToken = (filesToken + promptTokens) * 1.8;
|
||||
|
||||
return formatPrice(totalToken * unitPrice);
|
||||
}, [files, unitPrice]);
|
||||
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React, { useState, Dispatch, useCallback } from 'react';
|
||||
import React, { useState, Dispatch, useCallback, useRef } from 'react';
|
||||
import { FormControl, Flex, Input, Button, FormErrorMessage, Box } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PageTypeEnum } from '@/constants/user';
|
||||
import { OAuthEnum, PageTypeEnum } from '@/constants/user';
|
||||
import { postLogin } from '@/api/user';
|
||||
import type { ResLogin } from '@/api/response/user';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { feConfigs } from '@/store/static';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
|
||||
|
||||
interface Props {
|
||||
setPageType: Dispatch<`${PageTypeEnum}`>;
|
||||
@@ -58,18 +60,29 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
|
||||
[loginSuccess, toast]
|
||||
);
|
||||
|
||||
const onclickGit = useCallback(() => {
|
||||
setLoginStore({
|
||||
provider: 'git',
|
||||
lastRoute
|
||||
});
|
||||
router.replace(
|
||||
`https://github.com/login/oauth/authorize?client_id=${
|
||||
feConfigs?.gitLoginKey
|
||||
}&redirect_uri=${`${location.origin}/login/provider`}&scope=user:email%20read:user`,
|
||||
'_self'
|
||||
);
|
||||
}, [lastRoute, setLoginStore]);
|
||||
const redirectUri = `${location.origin}/login/provider`;
|
||||
const state = useRef(nanoid());
|
||||
|
||||
const oAuthList = [
|
||||
...(feConfigs?.oauth?.github
|
||||
? [
|
||||
{
|
||||
provider: OAuthEnum.github,
|
||||
icon: 'gitFill',
|
||||
redirectUrl: `https://github.com/login/oauth/authorize?client_id=${feConfigs?.oauth?.github}&redirect_uri=${redirectUri}&state=${state.current}&scope=user:email%20read:user`
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(feConfigs?.oauth?.google
|
||||
? [
|
||||
{
|
||||
provider: OAuthEnum.google,
|
||||
icon: 'googleFill',
|
||||
redirectUrl: `https://accounts.google.com/o/oauth2/v2/auth?client_id=${feConfigs?.oauth?.google}&redirect_uri=${redirectUri}&state=${state.current}&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20openid&include_granted_scopes=true`
|
||||
}
|
||||
]
|
||||
: [])
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -138,14 +151,24 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
|
||||
</Button>
|
||||
{feConfigs?.show_register && (
|
||||
<>
|
||||
<Flex mt={10} justifyContent={'center'} alignItems={'center'}>
|
||||
<MyIcon
|
||||
name="gitFill"
|
||||
w={'34px'}
|
||||
cursor={'pointer'}
|
||||
color={'myGray.800'}
|
||||
onClick={onclickGit}
|
||||
/>
|
||||
<Flex mt={10} justifyContent={'space-around'} alignItems={'center'}>
|
||||
{oAuthList.map((item) => (
|
||||
<MyIcon
|
||||
key={item.provider}
|
||||
name={item.icon as any}
|
||||
w={'34px'}
|
||||
cursor={'pointer'}
|
||||
color={'myGray.800'}
|
||||
onClick={() => {
|
||||
setLoginStore({
|
||||
provider: item.provider,
|
||||
lastRoute,
|
||||
state: state.current
|
||||
});
|
||||
router.replace(item.redirectUrl, '_self');
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -5,14 +5,14 @@ import { ResLogin } from '@/api/response/user';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { setToken } from '@/utils/user';
|
||||
import { gitLogin } from '@/api/user';
|
||||
import { oauthLogin } from '@/api/user';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import Loading from '@/components/Loading';
|
||||
import { serviceSideProps } from '@/utils/i18n';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getErrText } from '@/utils/tools';
|
||||
|
||||
const provider = ({ code }: { code: string }) => {
|
||||
const provider = ({ code, state }: { code: string; state: string }) => {
|
||||
const { loginStore } = useGlobalStore();
|
||||
const { setLastChatId, setLastChatAppId } = useChatStore();
|
||||
const { setUserInfo } = useUserStore();
|
||||
@@ -36,45 +36,55 @@ const provider = ({ code }: { code: string }) => {
|
||||
[setLastChatId, setLastChatAppId, setUserInfo, router, loginStore?.lastRoute]
|
||||
);
|
||||
|
||||
const authCode = useCallback(async () => {
|
||||
if (!code) return;
|
||||
if (!loginStore) {
|
||||
router.replace('/login');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await (async () => {
|
||||
if (loginStore.provider === 'git') {
|
||||
return gitLogin({
|
||||
code,
|
||||
inviterId: localStorage.getItem('inviterId') || undefined
|
||||
const authCode = useCallback(
|
||||
async (code: string) => {
|
||||
if (!loginStore) {
|
||||
router.replace('/login');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await oauthLogin({
|
||||
type: loginStore?.provider,
|
||||
code,
|
||||
callbackUrl: `${location.origin}/login/provider`,
|
||||
inviterId: localStorage.getItem('inviterId') || undefined
|
||||
});
|
||||
if (!res) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: '登录异常'
|
||||
});
|
||||
return setTimeout(() => {
|
||||
router.replace('/login');
|
||||
}, 1000);
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
if (!res) {
|
||||
loginSuccess(res);
|
||||
} catch (error) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: '登录异常'
|
||||
title: getErrText(error, '登录异常')
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
router.replace('/login');
|
||||
}, 1000);
|
||||
}
|
||||
loginSuccess(res);
|
||||
} catch (error) {
|
||||
},
|
||||
[loginStore, loginSuccess, router, toast]
|
||||
);
|
||||
|
||||
useQuery(['init', code], () => {
|
||||
if (!code) return;
|
||||
if (state !== loginStore?.state) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: getErrText(error, '登录异常')
|
||||
title: '安全校验失败'
|
||||
});
|
||||
setTimeout(() => {
|
||||
router.replace('/login');
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
}, [code, loginStore, loginSuccess]);
|
||||
|
||||
useQuery(['init', code], () => {
|
||||
authCode();
|
||||
authCode(code);
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -85,6 +95,7 @@ export async function getServerSideProps(content: any) {
|
||||
return {
|
||||
props: {
|
||||
code: content?.query?.code,
|
||||
state: content?.query?.state,
|
||||
...(await serviceSideProps(content))
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user