v4.6.9-alpha (#918)

Co-authored-by: Mufei <327958099@qq.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-03-04 00:05:25 +08:00
committed by GitHub
parent f9f0b4bffd
commit 42a8184ea0
153 changed files with 4906 additions and 4307 deletions

View File

@@ -56,8 +56,8 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
{...register('newPsw', {
required: true,
maxLength: {
value: 20,
message: '密码最少 4 位最多 20 位'
value: 60,
message: '密码最少 4 位最多 60 位'
}
})}
></Input>
@@ -70,8 +70,8 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
{...register('confirmPsw', {
required: true,
maxLength: {
value: 20,
message: '密码最少 4 位最多 20 位'
value: 60,
message: '密码最少 4 位最多 60 位'
}
})}
></Input>

View File

@@ -25,8 +25,9 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
[usage.list]
);
const { hasModel, hasCharsLen, hasDuration } = useMemo(() => {
const { hasModel, hasToken, hasCharsLen, hasDuration } = useMemo(() => {
let hasModel = false;
let hasToken = false;
let hasCharsLen = false;
let hasDuration = false;
let hasDataLen = false;
@@ -36,6 +37,9 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
hasModel = true;
}
if (typeof item.tokens === 'number') {
hasToken = true;
}
if (typeof item.charsLength === 'number') {
hasCharsLen = true;
}
@@ -46,6 +50,7 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
return {
hasModel,
hasToken,
hasCharsLen,
hasDuration,
hasDataLen
@@ -91,9 +96,9 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
<Tr>
<Th>{t('support.wallet.usage.Module name')}</Th>
{hasModel && <Th>{t('support.wallet.usage.Ai model')}</Th>}
{hasToken && <Th>{t('support.wallet.usage.Token Length')}</Th>}
{hasCharsLen && <Th>{t('support.wallet.usage.Text Length')}</Th>}
{hasDuration && <Th>{t('support.wallet.usage.Duration')}</Th>}
<Th>{t('support.wallet.usage.Total points')}</Th>
</Tr>
</Thead>
@@ -102,6 +107,7 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
<Tr key={i}>
<Td>{t(item.moduleName)}</Td>
{hasModel && <Td>{item.model ?? '-'}</Td>}
{hasToken && <Td>{item.tokens ?? '-'}</Td>}
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
<Td>{formatNumber(item.amount)}</Td>

View File

@@ -4,6 +4,11 @@ import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoUsage } from '@fastgpt/service/support/wallet/usage/schema';
import { connectionMongo } from '@fastgpt/service/common/mongo';
import { checkFiles } from '../timerTask/dataset/checkInValidDatasetFiles';
import { addHours } from 'date-fns';
import { checkInvalid as checkInvalidImg } from '../timerTask/dataset/checkInvalidDatasetImage';
import { checkInvalidCollection } from '../timerTask/dataset/checkInvalidMongoCollection';
import { checkInvalidVector } from '../timerTask/dataset/checkInvalidVector';
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -21,6 +26,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
(async () => {
try {
console.log('执行脏数据清理任务');
const end = addHours(new Date(), -1);
const start = addHours(new Date(), -360 * 24);
await checkFiles(start, end);
await checkInvalidImg(start, end);
await checkInvalidCollection(start, end);
await checkInvalidVector(start, end);
console.log('执行脏数据清理任务完毕');
} catch (error) {
console.log('执行脏数据清理任务出错了');
}
})();
jsonRes(res, {
message: 'success'
});

View File

@@ -1,16 +1,16 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { uploadMongoImg } from '@fastgpt/service/common/file/image/controller';
import { UploadImgProps } from '@fastgpt/global/common/file/api';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { shareId, ...body } = req.body as UploadImgProps;
const body = req.body as UploadImgProps;
const { teamId } = await authCertOrShareId({ req, shareId, authToken: true });
const { teamId } = await authChatCert({ req, authToken: true });
const data = await uploadMongoImg({
teamId,

View File

@@ -4,8 +4,6 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { readFileSync, readdirSync } from 'fs';
import type { InitDateResponse } from '@/global/common/api/systemRes';
import type { FastGPTConfigFileType } from '@fastgpt/global/common/system/types/index.d';
import { getTikTokenEnc } from '@fastgpt/global/common/string/tiktoken';
import { initHttpAgent } from '@fastgpt/service/common/middle/httpAgent';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { getFastGPTConfigFromDB } from '@fastgpt/service/common/system/config/controller';
import { connectToDatabase } from '@/service/mongo';
@@ -63,7 +61,6 @@ export async function getInitConfig() {
await connectToDatabase();
await Promise.all([
initGlobal(),
initSystemConfig(),
// getSimpleModeTemplates(),
getSystemVersion(),
@@ -84,18 +81,6 @@ export async function getInitConfig() {
}
}
export function initGlobal() {
if (global.communityPlugins) return;
global.communityPlugins = [];
global.simpleModeTemplates = [];
global.qaQueueLen = global.qaQueueLen ?? 0;
global.vectorQueueLen = global.vectorQueueLen ?? 0;
// init tikToken
getTikTokenEnc();
initHttpAgent();
}
export async function initSystemConfig() {
// load config
const [dbConfig, fileConfig] = await Promise.all([
@@ -125,7 +110,6 @@ export async function initSystemConfig() {
// set config
initFastGPTConfig(config);
global.systemEnv = config.systemEnv;
console.log({
feConfigs: global.feConfigs,

View File

@@ -1,14 +1,14 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { startQueue } from '@/service/utils/tools';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { startTrainingQueue } from '@/service/core/dataset/training/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
await authCert({ req, authToken: true });
startQueue();
startTrainingQueue();
} catch (error) {}
jsonRes(res);
}

View File

@@ -4,22 +4,21 @@ import { connectToDatabase } from '@/service/mongo';
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
import { authChatCert } from '@/service/support/permission/auth/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { messages, shareId } = req.body as CreateQuestionGuideParams;
const { messages } = req.body as CreateQuestionGuideParams;
const { tmbId, teamId } = await authCertOrShareId({
const { tmbId, teamId } = await authChatCert({
req,
authToken: true,
shareId
authToken: true
});
const qgModel = global.llmModels[0];
const { result, charsLength } = await createQuestionGuide({
const { result, tokens } = await createQuestionGuide({
messages,
model: qgModel.model
});
@@ -29,7 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
pushQuestionGuideUsage({
charsLength,
tokens,
teamId,
tmbId
});

View File

@@ -7,12 +7,13 @@ import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { ClearHistoriesProps } from '@/global/core/chat/api';
import { authOutLink } from '@/service/support/permission/auth/outLink';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
/* clear chat history */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, shareId, outLinkUid } = req.query as ClearHistoriesProps;
const { appId, shareId, outLinkUid, teamId, teamToken } = req.query as ClearHistoriesProps;
let chatAppId = appId;
@@ -26,6 +27,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
outLinkUid: uid
};
}
if (teamId && teamToken) {
const { uid } = await authTeamSpaceToken({ teamId, teamToken });
return {
teamId,
appId,
outLinkUid: uid
};
}
if (appId) {
const { tmbId } = await authCert({ req, authToken: true });

View File

@@ -4,14 +4,15 @@ import { connectToDatabase } from '@/service/mongo';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { getHistoriesProps } from '@/global/core/chat/api';
import { GetHistoriesProps } from '@/global/core/chat/api';
import { authOutLink } from '@/service/support/permission/auth/outLink';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, shareId, outLinkUid } = req.body as getHistoriesProps;
const { appId, shareId, outLinkUid, teamId, teamToken } = req.body as GetHistoriesProps;
const limit = shareId && outLinkUid ? 20 : 30;
@@ -28,10 +29,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
};
}
if (appId && outLinkUid) {
if (appId && teamId && teamToken) {
const { uid } = await authTeamSpaceToken({ teamId, teamToken });
return {
shareId,
outLinkUid: outLinkUid,
teamId,
appId,
outLinkUid: uid,
source: ChatSourceEnum.team
};
}

View File

@@ -4,7 +4,7 @@ import { connectToDatabase } from '@/service/mongo';
import { GetChatSpeechProps } from '@/global/core/chat/api.d';
import { text2Speech } from '@fastgpt/service/core/ai/audio/speech';
import { pushAudioSpeechUsage } from '@/service/support/wallet/usage/push';
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { authType2UsageSource } from '@/service/support/wallet/usage/utils';
import { getAudioSpeechModel } from '@fastgpt/service/core/ai/model';
import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
@@ -19,13 +19,13 @@ import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { ttsConfig, input, shareId } = req.body as GetChatSpeechProps;
const { ttsConfig, input } = req.body as GetChatSpeechProps;
if (!ttsConfig.model || !ttsConfig.voice) {
throw new Error('model or voice not found');
}
const { teamId, tmbId, authType } = await authCertOrShareId({ req, authToken: true, shareId });
const { teamId, tmbId, authType } = await authChatCert({ req, authToken: true });
const ttsModel = getAudioSpeechModel(ttsConfig.model);
const voiceData = ttsModel.voices?.find((item) => item.value === ttsConfig.voice);

View File

@@ -1,37 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { chatByTeamProps } from '@/global/core/chat/api.d';
import axios from 'axios';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { selectShareResponse } from '@/utils/service/core/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { teamId, appId, outLinkUid } = req.query as chatByTeamProps;
const history = await MongoChatItem.find({
appId: appId,
outLinkUid: outLinkUid,
teamId: teamId
});
jsonRes(res, {
data: history
});
} catch (err) {
jsonRes(res, {
code: 500,
data: req.query,
error: err
});
}
}
export const config = {
api: {
responseLimit: '10mb'
}
};

View File

@@ -9,7 +9,7 @@ import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { authOutLink } from '@/service/support/permission/auth/outLink';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { selectShareResponse } from '@/utils/service/core/chat';
import { selectSimpleChatResponse } from '@/utils/service/core/chat';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
@@ -50,7 +50,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// pick share response field
history.forEach((item) => {
item.responseData = selectShareResponse({ responseData: item.responseData });
item.responseData = selectSimpleChatResponse({ responseData: item.responseData });
});
jsonRes<InitChatResponse>(res, {

View File

@@ -4,59 +4,57 @@ import { connectToDatabase } from '@/service/mongo';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
import type { InitChatResponse, InitTeamChatProps } from '@/global/core/chat/api.d';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { selectSimpleChatResponse } from '@/utils/service/core/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { appId, chatId, outLinkUid } = req.query as {
chatId?: string;
appId?: string;
outLinkUid?: string;
};
let { teamId, appId, chatId, teamToken } = req.query as InitTeamChatProps;
if (!appId) {
return jsonRes(res, {
code: 501,
message: "You don't have an app yet"
});
if (!teamId || !appId || !teamToken) {
throw new Error('teamId, appId, teamToken are required');
}
// auth app permission
const [chat, app] = await Promise.all([
// authApp({
// req,
// authToken: false,
// appId,
// per: 'r'
// }),
chatId ? MongoChat.findOne({ appId, chatId }) : undefined,
const { uid } = await authTeamSpaceToken({
teamId,
teamToken
});
const [team, chat, app] = await Promise.all([
MongoTeam.findById(teamId, 'name avatar').lean(),
MongoChat.findOne({ teamId, appId, chatId }).lean(),
MongoApp.findById(appId).lean()
]);
if (!app) {
throw new Error(AppErrEnum.unExist);
}
// auth chat permission
// if (chat && chat.outLinkUid !== outLinkUid) {
// throw new Error(ChatErrEnum.unAuthChat);
// }
// // auth chat permission
// if (chat && !app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
// throw new Error(ChatErrEnum.unAuthChat);
// }
if (chat && chat.outLinkUid !== uid) {
throw new Error(ChatErrEnum.unAuthChat);
}
// get app and history
const { history } = await getChatItems({
appId,
chatId,
limit: 30,
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${ModuleOutputKeyEnum.responseData}`
field: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${ModuleOutputKeyEnum.responseData}`
});
// pick share response field
history.forEach((item) => {
item.responseData = selectSimpleChatResponse({ responseData: item.responseData });
});
jsonRes<InitChatResponse>(res, {
@@ -64,7 +62,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
chatId,
appId,
title: chat?.title || '新对话',
userAvatar: undefined,
userAvatar: team?.avatar,
variables: chat?.variables || {},
history,
app: {

View File

@@ -8,9 +8,9 @@ import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq';
import { PagingData } from '@/types';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { startQueue } from '@/service/utils/tools';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { DatasetDataCollectionName } from '@fastgpt/service/core/dataset/data/schema';
import { startTrainingQueue } from '@/service/core/dataset/training/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -158,7 +158,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
);
if (data.find((item) => item.trainingAmount > 0)) {
startQueue();
startTrainingQueue();
}
// count collections

View File

@@ -75,7 +75,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
a: formatA
});
const { insertId, charsLength } = await insertData2Dataset({
const { insertId, tokens } = await insertData2Dataset({
teamId,
tmbId,
datasetId,
@@ -90,7 +90,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
pushGenerateVectorUsage({
teamId,
tmbId,
charsLength,
tokens,
model: vectorModelData.model
});

View File

@@ -34,7 +34,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
insertLen: 1
});
const { charsLength } = await updateData2Dataset({
const { tokens } = await updateData2Dataset({
dataId: id,
q,
a,
@@ -45,7 +45,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
pushGenerateVectorUsage({
teamId,
tmbId,
charsLength,
tokens,
model: vectorModel
});

View File

@@ -58,7 +58,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
extensionBg: datasetSearchExtensionBg
});
const { searchRes, charsLength, ...result } = await searchDatasetData({
const { searchRes, tokens, ...result } = await searchDatasetData({
teamId,
reRankQuery: rewriteQuery,
queries: concatQueries,
@@ -74,14 +74,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const { totalPoints } = pushGenerateVectorUsage({
teamId,
tmbId,
charsLength,
tokens,
model: dataset.vectorModel,
source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt,
...(aiExtensionResult &&
extensionModel && {
extensionModel: extensionModel.name,
extensionCharsLength: aiExtensionResult.charsLength
extensionTokens: aiExtensionResult.tokens
})
});
if (apikey) {

View File

@@ -0,0 +1,91 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import {
delFileByFileIdList,
getGFSCollection
} from '@fastgpt/service/common/file/gridfs/controller';
import { addLog } from '@fastgpt/service/common/system/log';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { addHours } from 'date-fns';
/*
check dataset.files data. If there is no match in dataset.collections, delete it
可能异常情况
1. 上传了文件,未成功创建集合
*/
let deleteFileAmount = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { startHour = 24, endHour = 1 } = req.body as {
startHour?: number;
endHour?: number;
limit?: number;
};
await authCert({ req, authRoot: true });
await connectToDatabase();
// start: now - maxDay, end: now - 3 day
const start = addHours(new Date(), -startHour);
const end = addHours(new Date(), -endHour);
deleteFileAmount = 0;
console.log(start, end);
await checkFiles(start, end);
jsonRes(res, {
data: deleteFileAmount,
message: 'success'
});
} catch (error) {
addLog.error(`check valid dataset files error`, error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function checkFiles(start: Date, end: Date) {
const collection = getGFSCollection('dataset');
const where = {
uploadDate: { $gte: start, $lte: end }
};
// 1. get all file _id
const files = await collection
.find(where, {
projection: {
metadata: 1,
_id: 1
}
})
.toArray();
console.log('total files', files.length);
let index = 0;
for await (const file of files) {
try {
// 2. find fileId in dataset.collections
const hasCollection = await MongoDatasetCollection.countDocuments({
teamId: file.metadata.teamId,
fileId: file._id
});
// 3. if not found, delete file
if (hasCollection === 0) {
await delFileByFileIdList({ bucketName: 'dataset', fileIdList: [String(file._id)] });
console.log('delete file', file._id);
deleteFileAmount++;
}
index++;
index % 100 === 0 && console.log(index);
} catch (error) {
console.log(error);
}
}
console.log(`检测完成,共删除 ${deleteFileAmount} 个无效文件`);
}

View File

@@ -0,0 +1,88 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { addLog } from '@fastgpt/service/common/system/log';
import { addHours } from 'date-fns';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { MongoImage } from '@fastgpt/service/common/file/image/schema';
/*
检测无效的数据集图片
可能异常情况:
1. 上传文件过程中,上传了图片,但是最终没有创建数据集。
*/
let deleteImageAmount = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const {
startHour = 72,
endHour = 24,
limit = 10
} = req.body as { startHour?: number; endHour?: number; limit?: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
// start: now - maxDay, end: now - 3 day
const start = addHours(new Date(), -startHour);
const end = addHours(new Date(), -endHour);
deleteImageAmount = 0;
await checkInvalid(start, end, limit);
jsonRes(res, {
data: deleteImageAmount
});
} catch (error) {
addLog.error(`check Invalid user error`, error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function checkInvalid(start: Date, end: Date, limit = 50) {
const images = await MongoImage.find(
{
createTime: {
$gte: start,
$lte: end
},
'metadata.relatedId': { $exists: true }
},
'_id teamId metadata'
);
console.log('total images', images.length);
let index = 0;
for await (const image of images) {
try {
// 1. 检测是否有对应的集合
const collection = await MongoDatasetCollection.findOne(
{
teamId: image.teamId,
'metadata.relatedImgId': image.metadata?.relatedId
},
'_id'
);
if (!collection) {
await image.deleteOne();
deleteImageAmount++;
}
index++;
index % 100 === 0 && console.log(index);
} catch (error) {
console.log(error);
}
}
console.log(`检测完成,共删除 ${deleteImageAmount} 个无效图片`);
}

View File

@@ -0,0 +1,96 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { addLog } from '@fastgpt/service/common/system/log';
import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { addHours } from 'date-fns';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
/*
检测无效的 Mongo 数据
异常情况:
1. 训练过程删除知识库,可能导致还会有新的数据插入,导致无效。
*/
let deleteAmount = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { startHour = 3, endHour = 1 } = req.body as { startHour?: number; endHour?: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
// start: now - maxDay, end: now - endHour
const start = addHours(new Date(), -startHour);
const end = addHours(new Date(), -endHour);
deleteAmount = 0;
await checkInvalidCollection(start, end);
jsonRes(res, {
data: deleteAmount,
message: 'success'
});
} catch (error) {
addLog.error(`check Invalid user error`, error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function checkInvalidCollection(start: Date, end: Date) {
// 1. 获取时间范围的所有data
const rows = await MongoDatasetData.find(
{
updateTime: {
$gte: start,
$lte: end
}
},
'_id teamId collectionId'
).lean();
// 2. 合并所有的collectionId
const map = new Map<string, { teamId: string; collectionId: string }>();
for (const item of rows) {
const collectionId = String(item.collectionId);
if (!map.has(collectionId)) {
map.set(collectionId, { teamId: item.teamId, collectionId });
}
}
const list = Array.from(map.values());
console.log('total collections', list.length);
let index = 0;
for await (const item of list) {
try {
// 3. 查看该collection是否存在不存在则删除对应的数据
const collection = await MongoDatasetCollection.findOne({ _id: item.collectionId });
if (!collection) {
const result = await Promise.all([
MongoDatasetTraining.deleteMany({
teamId: item.teamId,
collectionId: item.collectionId
}),
MongoDatasetData.deleteMany({
teamId: item.teamId,
collectionId: item.collectionId
}),
deleteDatasetDataVector({
teamId: item.teamId,
collectionIds: [String(item.collectionId)]
})
]);
console.log(result);
console.log('collection is not found', item);
continue;
}
} catch (error) {}
console.log(++index);
}
}

View File

@@ -0,0 +1,86 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { addLog } from '@fastgpt/service/common/system/log';
import {
deleteDatasetDataVector,
getVectorDataByTime
} from '@fastgpt/service/common/vectorStore/controller';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { addHours } from 'date-fns';
/*
检测无效的 Vector 数据.
异常情况:
1. 插入数据时vector成功mongo失败
2. 更新数据,也会有插入 vector
*/
let deletedVectorAmount = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { startHour = 5, endHour = 1 } = req.body as { startHour?: number; endHour?: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
// start: now - maxDay, end: now - endHour
const start = addHours(new Date(), -startHour);
const end = addHours(new Date(), -endHour);
deletedVectorAmount = 0;
await checkInvalidVector(start, end);
jsonRes(res, {
data: deletedVectorAmount,
message: 'success'
});
} catch (error) {
addLog.error(`check Invalid user error`, error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function checkInvalidVector(start: Date, end: Date) {
// 1. get all vector data
const rows = await getVectorDataByTime(start, end);
console.log('total data', rows.length);
let index = 0;
for await (const item of rows) {
if (!item.teamId || !item.datasetId || !item.id) {
console.log('error data', item);
continue;
}
try {
// 2. find dataset.data
const hasData = await MongoDatasetData.countDocuments({
teamId: item.teamId,
datasetId: item.datasetId,
'indexes.dataId': item.id
});
// 3. if not found, delete vector
if (hasData === 0) {
await deleteDatasetDataVector({
teamId: item.teamId,
id: item.id
});
console.log('delete vector data', item.id);
deletedVectorAmount++;
}
index++;
index % 100 === 0 && console.log(index);
} catch (error) {
console.log(error);
}
}
console.log(`检测完成,共删除 ${deletedVectorAmount} 个无效 向量 数据`);
}

View File

@@ -1,12 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { getUploadModel } from '@fastgpt/service/common/file/multer';
import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
import fs from 'fs';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
import { authChatCert } from '@/service/support/permission/auth/chat';
const upload = getUploadModel({
maxSize: 2
@@ -18,12 +18,20 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
try {
const {
file,
data: { duration }
} = await upload.doUpload<{ duration: number; shareId?: string }>(req, res);
data: { duration, teamId: spaceTeamId, teamToken }
} = await upload.doUpload<{
duration: number;
shareId?: string;
teamId?: string;
teamToken?: string;
}>(req, res);
req.body.teamId = spaceTeamId;
req.body.teamToken = teamToken;
filePaths = [file.path];
const { teamId, tmbId } = await authCert({ req, authToken: true });
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
if (!global.whisperModel) {
throw new Error('whisper model not found');

View File

@@ -18,31 +18,28 @@ import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink'
import { pushResult2Remote, addOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
import requestIp from 'request-ip';
import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools';
import { authTeamShareChatStart } from '@/service/support/permission/auth/teamChat';
import { selectShareResponse } from '@/utils/service/core/chat';
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
import { selectSimpleChatResponse } from '@/utils/service/core/chat';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { connectToDatabase } from '@/service/mongo';
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { autChatCrud } from '@/service/support/permission/auth/chat';
import { UserModelSchema } from '@fastgpt/global/support/user/type';
import { AppSchema } from '@fastgpt/global/core/app/type';
import { AuthOutLinkChatProps } from '@fastgpt/global/support/outLink/api';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
type FastGptWebChatProps = {
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
appId?: string;
};
type FastGptShareChatProps = {
shareId?: string;
outLinkUid?: string;
};
type FastGptTeamShareChatProps = {
shareTeamId?: string;
outLinkUid?: string;
};
export type Props = ChatCompletionCreateParams &
FastGptWebChatProps &
FastGptShareChatProps &
FastGptTeamShareChatProps & {
OutLinkChatAuthProps & {
messages: ChatMessageItemType[];
stream?: boolean;
detail?: boolean;
@@ -53,6 +50,18 @@ export type ChatResponseType = {
quoteLen?: number;
};
type AuthResponseType = {
teamId: string;
tmbId: string;
user: UserModelSchema;
app: AppSchema;
responseDetail?: boolean;
authType: `${AuthUserTypeEnum}`;
apikey?: string;
canWrite: boolean;
outLinkUserId?: string;
};
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
res.on('close', () => {
res.end();
@@ -65,9 +74,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const {
chatId,
appId,
shareTeamId,
// share chat
shareId,
outLinkUid,
// team chat
teamId: spaceTeamId,
teamToken,
stream = false,
detail = false,
messages = [],
@@ -100,136 +112,44 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
if (!question) {
throw new Error('Question is empty');
}
/* auth app permission */
/*
1. auth app permission
2. auth balance
3. get app
4. parse outLink token
*/
const { teamId, tmbId, user, app, responseDetail, authType, apikey, canWrite, outLinkUserId } =
await (async () => {
// share chat
if (shareId && outLinkUid) {
const { teamId, tmbId, user, appId, authType, responseDetail, uid } =
await authOutLinkChatStart({
shareId,
ip: originIp,
outLinkUid,
question: question.value
});
const app = await MongoApp.findById(appId);
if (!app) {
return Promise.reject('app is empty');
}
return {
teamId,
tmbId,
user,
app,
responseDetail,
apikey: '',
authType,
canWrite: false,
outLinkUserId: uid
};
}
// team Apps share
if (shareTeamId && appId && outLinkUid) {
const { user, uid, tmbId } = await authTeamShareChatStart({
teamId: shareTeamId,
ip: originIp,
return authShareChat({
shareId,
outLinkUid,
chatId,
ip: originIp,
question: question.value
});
const app = await MongoApp.findById(appId);
if (!app) {
return Promise.reject('app is empty');
}
return {
teamId: shareTeamId,
tmbId,
user,
app,
responseDetail: detail,
authType: AuthUserTypeEnum.token,
apikey: '',
canWrite: false,
outLinkUserId: uid
};
}
// team space chat
if (spaceTeamId && appId && teamToken) {
return authTeamSpaceChat({
teamId: spaceTeamId,
teamToken,
appId,
chatId
});
}
const {
appId: apiKeyAppId,
teamId,
tmbId,
authType,
apikey
} = await authCert({
/* parse req: api or token */
return authHeaderRequest({
req,
authToken: true,
authApiKey: true
});
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
// openapi key
if (authType === AuthUserTypeEnum.apikey) {
if (!apiKeyAppId) {
return Promise.reject(
'Key is error. You need to use the app key rather than the account key.'
);
}
const app = await MongoApp.findById(apiKeyAppId);
if (!app) {
return Promise.reject('app is empty');
}
return {
teamId,
tmbId,
user,
app,
responseDetail: detail,
apikey,
authType,
canWrite: true
};
}
// token auth
if (!appId) {
return Promise.reject('appId is empty');
}
const { app, canWrite } = await authApp({
req,
authToken: true,
appId,
per: 'r'
chatId,
detail
});
return {
teamId,
tmbId,
user,
app,
responseDetail: detail,
apikey,
authType,
canWrite: canWrite || false
};
})();
// auth chat permission
await autChatCrud({
req,
authToken: true,
authApiKey: true,
appId: app._id,
chatId,
shareId,
shareTeamId,
outLinkUid,
per: 'w'
});
// get and concat history
const { history } = await getChatItems({
appId: app._id,
@@ -237,7 +157,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
limit: 30,
field: `dataId obj value`
});
const concatHistories = history.concat(chatMessages);
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;
@@ -263,13 +182,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// save chat
if (chatId) {
const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId);
await saveChat({
chatId,
appId: app._id,
teamId,
tmbId: tmbId,
variables,
updateUseTime: !shareId && String(tmbId) === String(app.tmbId), // owner update use time
updateUseTime: isOwnerUse, // owner update use time
shareId,
outLinkUid: outLinkUserId,
source: (() => {
@@ -279,6 +199,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
if (authType === 'apikey') {
return ChatSourceEnum.api;
}
if (spaceTeamId) {
return ChatSourceEnum.team;
}
return ChatSourceEnum.online;
})(),
content: [
@@ -299,7 +222,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`);
/* select fe response field */
const feResponseData = canWrite ? responseData : selectShareResponse({ responseData });
const feResponseData = canWrite ? responseData : selectSimpleChatResponse({ responseData });
if (stream) {
responseWrite({
@@ -382,3 +305,162 @@ export const config = {
responseLimit: '20mb'
}
};
const authShareChat = async ({
chatId,
...data
}: AuthOutLinkChatProps & {
shareId: string;
chatId?: string;
}): Promise<AuthResponseType> => {
const { teamId, tmbId, user, appId, authType, responseDetail, uid } =
await authOutLinkChatStart(data);
const app = await MongoApp.findById(appId).lean();
if (!app) {
return Promise.reject('app is empty');
}
// get chat
const chat = await MongoChat.findOne({ appId, chatId }).lean();
if (chat && (chat.shareId !== data.shareId || chat.outLinkUid !== uid)) {
return Promise.reject(ChatErrEnum.unAuthChat);
}
return {
teamId,
tmbId,
user,
app,
responseDetail,
apikey: '',
authType,
canWrite: false,
outLinkUserId: uid
};
};
const authTeamSpaceChat = async ({
appId,
teamId,
teamToken,
chatId
}: {
appId: string;
teamId: string;
teamToken: string;
chatId?: string;
}): Promise<AuthResponseType> => {
const { uid } = await authTeamSpaceToken({
teamId,
teamToken
});
const app = await MongoApp.findById(appId).lean();
if (!app) {
return Promise.reject('app is empty');
}
const [chat, { user }] = await Promise.all([
MongoChat.findOne({ appId, chatId }).lean(),
getUserChatInfoAndAuthTeamPoints(app.tmbId)
]);
if (chat && (String(chat.teamId) !== teamId || chat.outLinkUid !== uid)) {
return Promise.reject(ChatErrEnum.unAuthChat);
}
return {
teamId,
tmbId: app.tmbId,
user,
app,
responseDetail: true,
authType: AuthUserTypeEnum.outLink,
apikey: '',
canWrite: false,
outLinkUserId: uid
};
};
const authHeaderRequest = async ({
req,
appId,
chatId,
detail
}: {
req: NextApiRequest;
appId?: string;
chatId?: string;
detail?: boolean;
}): Promise<AuthResponseType> => {
const {
appId: apiKeyAppId,
teamId,
tmbId,
authType,
apikey,
canWrite: apiKeyCanWrite
} = await authCert({
req,
authToken: true,
authApiKey: true
});
const { app, canWrite } = await (async () => {
if (authType === AuthUserTypeEnum.apikey) {
if (!apiKeyAppId) {
return Promise.reject(
'Key is error. You need to use the app key rather than the account key.'
);
}
const app = await MongoApp.findById(apiKeyAppId);
if (!app) {
return Promise.reject('app is empty');
}
appId = String(app._id);
return {
app,
canWrite: apiKeyCanWrite
};
} else {
// token auth
if (!appId) {
return Promise.reject('appId is empty');
}
const { app, canWrite } = await authApp({
req,
authToken: true,
appId,
per: 'r'
});
return {
app,
canWrite: canWrite
};
}
})();
const [{ user }, chat] = await Promise.all([
getUserChatInfoAndAuthTeamPoints(tmbId),
MongoChat.findOne({ appId, chatId }).lean()
]);
if (chat && (String(chat.teamId) !== teamId || String(chat.tmbId) !== tmbId)) {
return Promise.reject(ChatErrEnum.unAuthChat);
}
return {
teamId,
tmbId,
user,
app,
responseDetail: detail,
apikey,
authType,
canWrite
};
};

View File

@@ -36,7 +36,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
await checkTeamAIPoints(teamId);
const { charsLength, vectors } = await getVectorsByText({
const { tokens, vectors } = await getVectorsByText({
input: query,
model: getVectorModel(model)
});
@@ -50,15 +50,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
})),
model,
usage: {
prompt_tokens: charsLength,
total_tokens: charsLength
prompt_tokens: tokens,
total_tokens: tokens
}
});
const { totalPoints } = pushGenerateVectorUsage({
teamId,
tmbId,
charsLength,
tokens,
model,
billId,
source: getUsageSourceByAuthType({ authType })

View File

@@ -136,7 +136,7 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
/>
{/* config */}
<Grid gridTemplateColumns={['repeat(3,1fr)']} gridGap={4} my={5}>
<Grid gridTemplateColumns={['repeat(2,1fr)', 'repeat(3,1fr)']} gridGap={4} my={5}>
<Flex {...gridItemStyle}>
<Box flex={1}>{t('core.app.outLink.Show History')}</Box>
<Switch {...register('showHistory')} />

View File

@@ -14,10 +14,6 @@ import {
ModalBody,
Input,
Switch,
Menu,
MenuButton,
MenuList,
MenuItem,
Link
} from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';

View File

@@ -13,7 +13,7 @@ import PermissionIconText from '@/components/support/permission/IconText';
import dynamic from 'next/dynamic';
import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import TagsEditModal from './tagsEditModal';
import TagsEditModal from './TagsEditModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
const InfoModal = dynamic(() => import('../InfoModal'));
@@ -156,9 +156,7 @@ const AppCard = ({ appId }: { appId: string }) => {
{settingAppInfo && (
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
)}
{TeamTagsSet && (
<TagsEditModal appDetail={appDetail} onClose={() => setTeamTagsSet(undefined)} />
)}
{TeamTagsSet && <TagsEditModal onClose={() => setTeamTagsSet(undefined)} />}
</>
);
};

View File

@@ -0,0 +1,143 @@
import React, { useState } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import {
Button,
Flex,
Box,
ModalFooter,
ModalBody,
Menu,
MenuButton,
HStack,
Tag,
TagCloseButton,
MenuList,
Input,
MenuOptionGroup,
MenuItemOption,
TagLabel
} from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useRequest } from '@/web/common/hooks/useRequest';
import { getTeamsTags } from '@/web/support/user/team/api';
import { useQuery } from '@tanstack/react-query';
const TagsEditModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { appDetail } = useAppStore();
const { toast } = useToast();
const { replaceAppDetail } = useAppStore();
const [selectedTags, setSelectedTags] = useState<string[]>(appDetail?.teamTags || []);
// submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async () => {
await replaceAppDetail(appDetail._id, {
teamTags: selectedTags
});
},
onSuccess() {
onClose();
toast({
title: t('common.Update Success'),
status: 'success'
});
},
errorToast: t('common.Update Failed')
});
const { data: teamTags = [] } = useQuery(['getTeamsTags'], getTeamsTags);
const [searchKey, setSearchKey] = useState('');
const filterTeamTags = teamTags.filter((item) => {
return item.label.includes(searchKey);
}, []);
return (
<MyModal
style={{ width: '900px' }}
isOpen
onClose={onClose}
iconSrc="/imgs/module/ai.svg"
title={t('core.app.Team tags')}
>
<ModalBody>
<Box mb={3} fontWeight="semibold">
{t('团队标签')}
</Box>
<Menu closeOnSelect={false}>
<MenuButton className="menu-btn" maxHeight={'250'} w={'100%'}>
<Flex
alignItems={'center'}
borderWidth={'1px'}
borderColor={'borderColor.base'}
borderRadius={'md'}
px={3}
py={2}
flexWrap={'wrap'}
minH={'50px'}
gap={3}
>
{teamTags.map((item, index) => {
const key: string = item?.key;
if (selectedTags.indexOf(key as never) > -1) {
return (
<Tag key={index} size={'md'} colorScheme="blue" borderRadius="full">
<TagLabel>{item.label}</TagLabel>
<TagCloseButton />
</Tag>
);
}
})}
</Flex>
</MenuButton>
<MenuList>
<Box px={2}>
<Input
m={'auto'}
placeholder={t('core.app.Search team tags')}
value={searchKey}
onChange={(e) => {
setSearchKey(e.target.value);
}}
/>
</Box>
<Box maxH={'300px'} overflow={'auto'} mt={1}>
<MenuOptionGroup
defaultValue={selectedTags}
type="checkbox"
onChange={(e) => {
//@ts-ignore
setSelectedTags(e);
}}
>
{filterTeamTags.map((item) => {
return (
<MenuItemOption
key={item.key}
value={item.key}
borderRadius={'md'}
_hover={{ bg: 'myGray.100' }}
>
{item?.label}
</MenuItemOption>
);
})}
</MenuOptionGroup>
</Box>
</MenuList>
</Menu>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={btnLoading} onClick={(e) => saveSubmitSuccess(e)}>
{t('common.Save')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default TagsEditModal;

View File

@@ -1,103 +0,0 @@
import React, { useCallback, useState, useEffect } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { Button, Flex, Box, ModalFooter, ModalBody } from '@chakra-ui/react';
import TagsEdit from '@/components/TagEdit';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useRequest } from '@/web/common/hooks/useRequest';
import { getTeamsTags } from '@/web/support/user/team/api';
const TagsEditModal = ({ appDetail, onClose }: { appDetail?: any; onClose: () => void }) => {
const { t } = useTranslation();
const [teamsTags, setTeamTags] = useState<Array<TeamTagsSchema>>([]);
const [selectedTags, setSelectedTags] = useState(appDetail?.teamTags);
const { toast } = useToast();
const { replaceAppDetail } = useAppStore();
// submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async () => {
await replaceAppDetail(appDetail._id, {
teamTags: selectedTags
});
},
onSuccess() {
onClose();
toast({
title: t('common.Update Success'),
status: 'success'
});
},
errorToast: t('common.Update Failed')
});
//
// // 点击选择标签
// const clickTag = (tagId :Number) => {
// const index = selectedTags.indexOf(tagId);
// if (index === -1) {
// // 如果 num 不在数组 arr 中,添加它
// setSelectedTags([tagId,...selectedTags])
// } else {
// const _selectedTags = [...selectedTags];
// _selectedTags.splice(index, 1);
// console.log('_selectedTags',_selectedTags);
// // 如果 num 已经在数组 arr 中,移除它
// setSelectedTags(_selectedTags);
// }
// }
useEffect(() => {
// get team tags
getTeamsTags(appDetail?.teamId).then((res: any) => {
setTeamTags(res?.list);
});
}, []);
return (
<MyModal
style={{ width: '900px' }}
isOpen
onClose={onClose}
iconSrc="/imgs/module/ai.svg"
title={'标签管理'}
>
<ModalBody>
{/* <HStack spacing={2}>
{teamsTags.map((item,index) => {
return <Tag
key={index}
size={'md'}
variant='outline'
colorScheme={selectedTags.indexOf(item._id) > -1 ? 'green':'blue' }
onClick={() => clickTag(item._id)}
>
{item.label}
</Tag>
})}
</HStack> */}
<Flex width={'100%'} alignItems={'center'}>
<Box mb={3} mr={3} fontWeight="semibold">
{t('团队标签')}
</Box>
<TagsEdit
defaultValues={selectedTags}
teamsTags={teamsTags}
setSelectedTags={(item: Array<string>) => setSelectedTags(item)}
/>
</Flex>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={btnLoading} onClick={(e) => saveSubmitSuccess(e)}>
{t('common.Save')}
</Button>
</ModalFooter>
</ModalBody>
</MyModal>
);
};
export default TagsEditModal;

View File

@@ -1,45 +1,52 @@
import React from 'react';
import { Flex, Box, IconButton } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@/components/Avatar';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { AppListItemType } from '@fastgpt/global/core/app/type';
const SliderApps = ({ appId }: { appId: string }) => {
const SliderApps = ({
showExist = true,
apps,
activeAppId
}: {
showExist?: boolean;
apps: AppListItemType[];
activeAppId: string;
}) => {
const { t } = useTranslation();
const router = useRouter();
const { myApps, loadMyApps } = useAppStore();
useQuery(['loadModels'], () => loadMyApps(false));
return (
<Flex flexDirection={'column'} h={'100%'}>
<Box px={5} py={4}>
<Flex
alignItems={'center'}
cursor={'pointer'}
py={2}
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.200' }}
onClick={() => router.push('/app/list')}
>
<IconButton
mr={3}
icon={<MyIcon name={'common/backFill'} w={'18px'} color={'primary.500'} />}
bg={'white'}
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
size={'smSquare'}
borderRadius={'50%'}
aria-label={''}
/>
{t('core.chat.Exit Chat')}
</Flex>
{showExist && (
<Flex
alignItems={'center'}
cursor={'pointer'}
py={2}
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.200' }}
onClick={() => router.push('/app/list')}
>
<IconButton
mr={3}
icon={<MyIcon name={'common/backFill'} w={'18px'} color={'primary.500'} />}
bg={'white'}
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
size={'smSquare'}
borderRadius={'50%'}
aria-label={''}
/>
{t('core.chat.Exit Chat')}
</Flex>
)}
</Box>
<Box flex={'1 0 0'} h={0} px={5} overflow={'overlay'}>
{myApps.map((item) => (
{apps.map((item) => (
<Flex
key={item._id}
py={2}
@@ -48,7 +55,7 @@ const SliderApps = ({ appId }: { appId: string }) => {
cursor={'pointer'}
borderRadius={'md'}
alignItems={'center'}
{...(item._id === appId
{...(item._id === activeAppId
? {
bg: 'white',
boxShadow: 'md'
@@ -60,6 +67,8 @@ const SliderApps = ({ appId }: { appId: string }) => {
onClick: () => {
router.replace({
query: {
...router.query,
chatId: '',
appId: item._id
}
});

View File

@@ -129,6 +129,8 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
[appId, chatId, histories, pushHistory, router, setChatData, t, updateHistory]
);
useQuery(['loadModels'], () => loadMyApps(false));
// get chat app info
const loadChatInfo = useCallback(
async ({
@@ -251,7 +253,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
{/* pc show myself apps */}
{isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
<SliderApps appId={appId} />
<SliderApps apps={myApps} activeAppId={appId} />
</Box>
)}

View File

@@ -22,22 +22,29 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useTranslation } from 'next-i18next';
import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { POST } from '@/web/common/api/request';
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import MyBox from '@/components/common/MyBox';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
const OutLink = ({
shareId,
chatId,
showHistory,
authToken
authToken,
appName,
appIntro,
appAvatar
}: {
shareId: string;
chatId: string;
showHistory: '0' | '1';
authToken?: string;
appName?: string;
appIntro?: string;
appAvatar?: string;
}) => {
const { t } = useTranslation();
const router = useRouter();
@@ -150,7 +157,18 @@ const OutLink = ({
return { responseText, responseData, isNewChat: forbidRefresh.current };
},
[chatId, shareId, outLinkUid, setChatData, appId, pushHistory, router, histories, updateHistory]
[
chatId,
shareId,
outLinkUid,
t,
setChatData,
appId,
pushHistory,
router,
histories,
updateHistory
]
);
const loadChatInfo = useCallback(
@@ -235,28 +253,6 @@ const OutLink = ({
setIdEmbed(window !== top);
}, []);
// todo:4.6.4 init: update local chat history, add outLinkUid
useEffect(() => {
const activeHistory = shareChatHistory.filter((item) => !item.delete);
if (!localUId || !shareId || activeHistory.length === 0) return;
(async () => {
try {
await POST('/core/chat/initLocalShareHistoryV464', {
shareId,
outLinkUid: localUId,
chatIds: shareChatHistory.map((item) => item.chatId)
});
clearLocalHistory();
// router.reload();
} catch (error) {
toast({
status: 'warning',
title: getErrText(error, t('core.shareChat.Init Error'))
});
}
})();
}, [clearLocalHistory, localUId, router, shareChatHistory, shareId, t, toast]);
return (
<PageContainer
{...(isEmbed
@@ -264,7 +260,9 @@ const OutLink = ({
: { p: [0, 5] })}
>
<Head>
<title>{chatData.app.name}</title>
<title>{appName || chatData.app?.name}</title>
<meta name="description" content={appIntro} />
<link rel="icon" href={appAvatar || chatData.app?.avatar} />
</Head>
<MyBox
isLoading={isFetching}
@@ -397,12 +395,31 @@ export async function getServerSideProps(context: any) {
const showHistory = context?.query?.showHistory || '1';
const authToken = context?.query?.authToken || '';
const app = await (async () => {
try {
const app = (await MongoOutLink.findOne(
{
shareId
},
'appId'
)
.populate('appId', 'name avatar intro')
.lean()) as OutLinkWithAppType;
return app;
} catch (error) {
return undefined;
}
})();
return {
props: {
shareId,
chatId,
showHistory,
authToken,
appName: app?.appId?.name,
appAvatar: app?.appId?.avatar,
appIntro: app?.appId?.intro,
...(await serviceSideProps(context))
}
};

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef } from 'react';
import Head from 'next/head';
import { getTeamChatInfo } from '@/web/core/chat/api';
import { useRouter } from 'next/router';
@@ -11,13 +11,12 @@ import {
DrawerContent,
useTheme
} from '@chakra-ui/react';
import Avatar from '@/components/Avatar';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SideBar from '@/components/SideBar';
import PageContainer from '@/components/PageContainer';
import { getChatListById } from '@/web/core/chat/api';
import { getMyTokensApps } from '@/web/core/chat/api';
import ChatHistorySlider from './components/ChatHistorySlider';
import ChatHeader from './components/ChatHeader';
import { serviceSideProps } from '@/web/common/utils/i18n';
@@ -25,112 +24,49 @@ import { useTranslation } from 'next-i18next';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { customAlphabet } from 'nanoid';
import { useLoading } from '@/web/common/hooks/useLoading';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
import { streamFetch } from '@/web/common/api/fetch';
import { useTeamShareChatStore } from '@/web/core/chat/storeTeamChat';
import type {
ChatHistoryItemType,
chatAppListSchema,
teamInfoType
} from '@fastgpt/global/core/chat/type.d';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { POST } from '@/web/common/api/request';
import { getErrText } from '@fastgpt/global/common/error/utils';
import MyBox from '@/components/common/MyBox';
import SliderApps from './components/SliderApps';
const OutLink = ({
shareTeamId,
teamId,
appId,
chatId,
authToken
teamToken
}: {
shareTeamId: string;
teamId: string;
appId: string;
chatId: string;
authToken: string;
teamToken: string;
}) => {
type routerQueryType = {
chatId?: string;
appId?: string;
shareTeamId: string;
authToken?: string;
};
const { t } = useTranslation();
const router = useRouter();
const { toast } = useToast();
const theme = useTheme();
const [myApps, setMyApps] = useState<Array<any>>([]);
const { isPc } = useSystemStore();
const ChatBoxRef = useRef<ComponentRef>(null);
const [teamInfo, setTeamInfo] = useState<teamInfoType>();
const { Loading, setIsLoading } = useLoading();
const forbidRefresh = useRef(false);
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const {
chatData,
setChatData,
histories,
loadHistories,
lastChatAppId,
setLastChatAppId,
lastChatId,
setLastChatId,
pushHistory,
updateHistory,
delOneHistory,
chatData,
setChatData,
delOneHistoryItem,
clearHistories
} = useChatStore();
const {
localUId,
teamShareChatHistory, // abandon
clearLocalHistory // abandon
} = useTeamShareChatStore();
const outLinkUid: string = authToken || localUId;
// 纯网络获取流程
const loadApps = useCallback(async () => {
try {
if (!shareTeamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return;
}
// 根据获取历史记录列表
const res = await getChatListById({ shareTeamId, authToken });
const { apps, teamInfo } = res;
setMyApps(apps);
setTeamInfo(teamInfo);
if (apps.length <= 0) {
return toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
}
if (!apps.find((obj) => obj._id === appId)) {
toast({
status: 'warning',
title: 'you do not have this App'
});
router.replace({
query: {
appId: apps[0]?._id,
shareTeamId,
authToken: authToken
} as routerQueryType
});
}
} catch (error: any) {
toast({
status: 'warning',
title: error?.message
});
}
}, [appId, authToken, router, shareTeamId, t, toast]);
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
@@ -142,14 +78,14 @@ const OutLink = ({
messages: prompts,
variables,
appId,
shareTeamId,
outLinkUid: outLinkUid,
teamId,
teamToken,
chatId: completionChatId
},
onMessage: generatingMessage,
abortCtrl: controller
});
console.log(responseData);
const newTitle =
chatContentReplaceBlock(prompts[0].content).slice(0, 20) ||
prompts[1]?.value?.slice(0, 20) ||
@@ -169,11 +105,9 @@ const OutLink = ({
forbidRefresh.current = true;
router.replace({
query: {
chatId: completionChatId,
appId,
shareTeamId,
authToken: authToken
} as routerQueryType
...router.query,
chatId: completionChatId
}
});
}
} else {
@@ -183,7 +117,9 @@ const OutLink = ({
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle
title: newTitle,
teamId,
teamToken
});
}
// update chat window
@@ -195,227 +131,146 @@ const OutLink = ({
return { responseText, responseData, isNewChat: forbidRefresh.current };
},
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory]
[
appId,
teamToken,
chatId,
histories,
pushHistory,
router,
setChatData,
teamId,
t,
updateHistory
]
);
const { isFetching } = useQuery(['init', appId, shareTeamId], async () => {
console.log('res', 3);
if (!shareTeamId) {
/* replace router query to last chat */
useEffect(() => {
if ((!chatId || !appId) && (lastChatId || lastChatAppId)) {
router.replace({
query: {
...router.query,
chatId: chatId || lastChatId,
appId: appId || lastChatAppId
}
});
}
}, []);
// get chat app list
const loadApps = useCallback(async () => {
try {
const apps = await getMyTokensApps({ teamId, teamToken });
if (apps.length <= 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return [];
}
// if app id not exist, redirect to first app
if (!appId || !apps.find((item) => item._id === appId)) {
router.replace({
query: {
...router.query,
appId: apps[0]?._id
}
});
}
return apps;
} catch (error: any) {
toast({
status: 'warning',
title: getErrText(error)
});
}
return [];
}, [appId, teamToken, router, teamId, t, toast]);
const { data: myApps = [], isLoading: isLoadingApps } = useQuery(['initApps', teamId], () => {
if (!teamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
title: t('support.user.team.tag.Have not opened')
});
return;
}
return shareTeamId && loadApps();
return loadApps();
});
// load histories
useQuery(['loadHistories', appId], () => {
if (shareTeamId && appId) {
return loadHistories({ appId, outLinkUid });
if (teamId && appId) {
return loadHistories({ teamId, appId, teamToken: teamToken });
}
return;
});
// 初始化聊天框
useQuery(['init', { appId, chatId }], () => {
if (!shareTeamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return;
}
if (myApps.length > 0 && myApps.findIndex((obj) => obj._id === appId) === -1) {
toast({
status: 'warning',
title: 'you do not have this App'
});
return;
}
// pc: redirect to latest model chat
if (!appId && lastChatAppId) {
return router.replace({
query: {
appId: lastChatAppId,
chatId: lastChatId,
shareTeamId,
authToken: authToken
} as routerQueryType
});
}
if (!appId && myApps[0]) {
return router.replace({
query: {
appId: myApps[0]._id,
chatId: lastChatId,
shareTeamId,
authToken: authToken
} as routerQueryType
});
}
if (!appId) {
(async () => {
const { apps = [] } = await getChatListById({ shareTeamId, authToken });
setMyApps(apps);
if (apps.length === 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
} else {
router.replace({
query: {
appId: apps[0]._id,
chatId: lastChatId,
shareTeamId,
authToken: authToken
} as routerQueryType
});
}
})();
return;
}
// store id
appId && setLastChatAppId(appId);
setLastChatId(chatId);
return loadChatInfo({
appId,
chatId,
loading: appId !== chatData.appId
});
});
// get chat app info
const loadChatInfo = useCallback(
async ({
appId,
chatId,
loading = false
}: {
appId: string;
chatId: string;
loading?: boolean;
}) => {
try {
if (!shareTeamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return;
}
loading && setIsLoading(true);
const res = await getTeamChatInfo({ appId, chatId, outLinkUid });
console.log('res', res);
const history = res.history.map((item) => ({
...item,
status: ChatStatusEnum.finish
}));
const loadChatInfo = useCallback(async () => {
try {
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken: teamToken });
setChatData({
...res,
history
});
const history = res.history.map((item) => ({
...item,
status: ChatStatusEnum.finish
}));
// have records.
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
// reset all chat tore
setLastChatAppId('');
setLastChatId('');
toast({
title: t('core.chat.Failed to initialize chat'),
status: 'error'
});
if (e?.code === 501) {
//router.replace('/app/list');
} else if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
} as routerQueryType
});
}
setChatData({
...res,
history
});
// have records.
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
setIsLoading(false);
} catch (e: any) {
toast({
title: t('core.chat.Failed to initialize chat'),
status: 'error'
});
if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
});
}
}
return null;
}, [teamId, appId, chatId, teamToken, setChatData, toast, t, router]);
const { isFetching } = useQuery(['init', teamId, appId, chatId], () => {
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
},
[setIsLoading, setChatData, router, setLastChatAppId, setLastChatId, toast]
);
// 监测路由改变
useEffect(() => {
const activeHistory = teamShareChatHistory.filter((item) => !item.delete);
if (!localUId || !shareTeamId || activeHistory.length === 0) return;
(async () => {
try {
await POST('/core/chat/initLocalShareHistoryV464', {
outLinkUid: localUId,
chatIds: teamShareChatHistory.map((item) => item.chatId)
});
clearLocalHistory();
// router.reload();
} catch (error) {
toast({
status: 'warning',
title: t('core.shareChat.Init Error')
});
}
})();
}, [clearLocalHistory, localUId, router, teamShareChatHistory, shareTeamId, t, toast]);
}
if (teamId && appId) {
return loadChatInfo();
}
return null;
});
return (
<Flex h={'100%'}>
<MyBox display={'flex'} h={'100%'} isLoading={isLoadingApps || isFetching}>
<Head>
<title>{chatData.app.name}</title>
</Head>
{/* pc show myself apps */}
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
<Flex flexDirection={'column'} h={'100%'}>
<Box flex={'1 0 0'} h={0} px={5} py={4} overflow={'overlay'}>
{myApps &&
myApps.map((item) => (
<Flex
key={item._id}
py={2}
px={3}
mb={3}
cursor={'pointer'}
borderRadius={'md'}
alignItems={'center'}
{...(item._id === appId
? {
bg: 'white',
boxShadow: 'md'
}
: {
_hover: {
bg: 'myGray.200'
},
onClick: () => {
router.replace({
query: {
appId: item._id,
shareTeamId,
authToken: authToken
} as routerQueryType
});
}
})}
>
<Avatar src={item.avatar} w={'24px'} />
<Box ml={2} className={'textEllipsis'}>
{item.name}
</Box>
</Flex>
))}
</Box>
</Flex>
</Box>
{isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
<SliderApps showExist={false} apps={myApps} activeAppId={appId} />
</Box>
)}
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{((children: React.ReactNode) => {
@@ -449,36 +304,35 @@ const OutLink = ({
onChangeChat={(chatId) => {
router.replace({
query: {
chatId: chatId || '',
appId,
shareTeamId,
authToken: authToken
} as routerQueryType
...router.query,
chatId: chatId || ''
}
});
if (!isPc) {
onCloseSlider();
}
}}
onDelHistory={(e) => delOneHistory({ ...e, appId })}
onDelHistory={(e) => delOneHistory({ ...e, appId, teamId, teamToken })}
onClearHistory={() => {
clearHistories({ appId });
clearHistories({ appId, teamId, teamToken });
router.replace({
query: {
appId,
shareTeamId,
authToken: authToken
} as routerQueryType
...router.query,
chatId: ''
}
});
}}
onSetHistoryTop={(e) => {
updateHistory({ ...e, appId });
updateHistory({ ...e, teamId, teamToken, appId });
}}
onSetCustomTitle={async (e) => {
updateHistory({
appId,
chatId: e.chatId,
title: e.title,
customTitle: e.title
customTitle: e.title,
teamId,
teamToken
});
}}
/>
@@ -496,8 +350,8 @@ const OutLink = ({
appAvatar={chatData.app.avatar}
appName={chatData.app.name}
history={chatData.history}
showHistory={true}
onOpenSlider={onOpenSlider}
showHistory
/>
{/* chat box */}
<Box flex={1}>
@@ -512,33 +366,33 @@ const OutLink = ({
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={(e) =>
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, outLinkUid })
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, teamId, teamToken })
}
appId={chatData.appId}
shareTeamId={shareTeamId}
chatId={chatId}
outLinkUid={outLinkUid}
teamId={teamId}
teamToken={teamToken}
/>
</Box>
</Flex>
</Flex>
</PageContainer>
</Flex>
</MyBox>
);
};
export async function getServerSideProps(context: any) {
const shareTeamId = context?.query?.shareTeamId || '';
const teamId = context?.query?.teamId || '';
const appId = context?.query?.appId || '';
const chatId = context?.query?.chatId || '';
const authToken: string = context?.query?.authToken || '';
const teamToken: string = context?.query?.teamToken || '';
return {
props: {
shareTeamId,
teamId,
appId,
chatId,
authToken,
teamToken,
...(await serviceSideProps(context))
}
};

View File

@@ -95,6 +95,20 @@ const Provider = ({
const customSplitChar = processParamsForm.watch('customSplitChar');
const modeStaticParams = {
[TrainingModeEnum.auto]: {
chunkOverlapRatio: 0.2,
maxChunkSize: 2048,
minChunkSize: 100,
autoChunkSize: vectorModel?.defaultToken ? vectorModel?.defaultToken * 2 : 1024,
chunkSize: vectorModel?.defaultToken ? vectorModel?.defaultToken * 2 : 1024,
showChunkInput: false,
showPromptInput: false,
charsPointsPrice: agentModel.charsPointsPrice,
priceTip: t('core.dataset.import.Auto mode Estimated Price Tips', {
price: agentModel.charsPointsPrice
}),
uploadRate: 100
},
[TrainingModeEnum.chunk]: {
chunkSizeField: 'embeddingChunkSize' as ChunkSizeFieldType,
chunkOverlapRatio: 0.2,
@@ -149,10 +163,17 @@ const Provider = ({
[sources]
);
const predictPoints = useMemo(() => {
if (mode === TrainingModeEnum.qa) {
return +(((totalChunkChars * 1.5) / 1000) * agentModel.charsPointsPrice).toFixed(2);
const totalTokensPredict = totalChunkChars / 1000;
if (mode === TrainingModeEnum.auto) {
const price = totalTokensPredict * 1.3 * agentModel.charsPointsPrice;
return +price.toFixed(2);
}
return +((totalChunkChars / 1000) * vectorModel.charsPointsPrice).toFixed(2);
if (mode === TrainingModeEnum.qa) {
const price = totalTokensPredict * 1.2 * agentModel.charsPointsPrice;
return +price.toFixed(2);
}
return +(totalTokensPredict * vectorModel.charsPointsPrice).toFixed(2);
}, [agentModel.charsPointsPrice, mode, totalChunkChars, vectorModel.charsPointsPrice]);
const totalChunks = useMemo(
() => sources.reduce((sum, file) => sum + file.chunks.length, 0),

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
Box,
Flex,
@@ -60,6 +60,15 @@ function DataProcess({
onClose: onCloseCustomPrompt
} = useDisclosure();
const trainingModeList = useMemo(() => {
const list = Object.entries(TrainingTypeMap);
return list.filter(([key, value]) => {
if (feConfigs?.isPlus) return true;
return value.isPlus;
});
}, [feConfigs?.isPlus]);
useEffect(() => {
if (showPreviewChunks) {
splitSources2Chunks();
@@ -79,7 +88,7 @@ function DataProcess({
{t('core.dataset.import.Training mode')}
</Box>
<LeftRadio
list={Object.entries(TrainingTypeMap).map(([key, value]) => ({
list={trainingModeList.map(([key, value]) => ({
title: t(value.label),
value: key,
tooltip: t(value.tooltip)
@@ -91,7 +100,7 @@ function DataProcess({
setValue('mode', e);
setRefresh(!refresh);
}}
gridTemplateColumns={'1fr 1fr'}
gridTemplateColumns={'repeat(3,1fr)'}
defaultBg="white"
activeBg="white"
/>

View File

@@ -1,7 +1,7 @@
import React, { useState, Dispatch, useCallback } from 'react';
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { PageTypeEnum } from '@/constants/user';
import { LoginPageTypeEnum } from '@/constants/user';
import { postFindPassword } from '@/web/support/user/api';
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
import type { ResLogin } from '@/global/support/api/userRes.d';
@@ -9,7 +9,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
interface Props {
setPageType: Dispatch<`${PageTypeEnum}`>;
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
loginSuccess: (e: ResLogin) => void;
}
@@ -181,7 +181,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
color={'primary.700'}
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('login')}
onClick={() => setPageType(LoginPageTypeEnum.passwordLogin)}
>
</Box>

View File

@@ -1,251 +0,0 @@
import React, { useState, Dispatch, useCallback, useRef } from 'react';
import {
FormControl,
Flex,
Input,
Button,
Divider,
AbsoluteCenter,
Box,
Link,
useTheme
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { useRouter } from 'next/router';
import { PageTypeEnum } from '@/constants/user';
import { OAuthEnum } from '@fastgpt/global/support/user/constant';
import { postLogin } from '@/web/support/user/api';
import type { ResLogin } from '@/global/support/api/userRes';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { customAlphabet } from 'nanoid';
import { getDocPath } from '@/web/common/system/doc';
import Avatar from '@/components/Avatar';
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
import { useTranslation } from 'next-i18next';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
interface Props {
setPageType: Dispatch<`${PageTypeEnum}`>;
loginSuccess: (e: ResLogin) => void;
}
interface LoginFormType {
username: string;
password: string;
}
const LoginForm = ({ setPageType, loginSuccess }: Props) => {
const { t } = useTranslation();
const router = useRouter();
const theme = useTheme();
const { lastRoute = '/app/list' } = router.query as { lastRoute: string };
const { toast } = useToast();
const { setLoginStore, feConfigs } = useSystemStore();
const {
register,
handleSubmit,
formState: { errors }
} = useForm<LoginFormType>();
const [requesting, setRequesting] = useState(false);
const onclickLogin = useCallback(
async ({ username, password }: LoginFormType) => {
setRequesting(true);
try {
loginSuccess(
await postLogin({
username,
password
})
);
toast({
title: '登录成功',
status: 'success'
});
} catch (error: any) {
toast({
title: error.message || '登录异常',
status: 'error'
});
}
setRequesting(false);
},
[loginSuccess, toast]
);
const redirectUri = `${location.origin}/login/provider`;
const state = useRef(nanoid());
const oAuthList = [
...(feConfigs?.oauth?.github
? [
{
label: t('support.user.login.Github'),
provider: OAuthEnum.github,
icon: 'common/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
? [
{
label: t('support.user.login.Google'),
provider: OAuthEnum.google,
icon: 'common/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`
}
]
: [])
];
const isCommunityVersion = feConfigs?.show_register === false && feConfigs?.show_git;
return (
<Flex flexDirection={'column'} h={'100%'}>
<Flex alignItems={'center'}>
<Flex
w={['48px', '56px']}
h={['48px', '56px']}
bg={'myGray.25'}
borderRadius={'xl'}
borderWidth={'1.5px'}
borderColor={'borderColor.base'}
alignItems={'center'}
justifyContent={'center'}
>
<Avatar src={LOGO_ICON} w={'30px'} />
</Flex>
<Box ml={3} fontSize={['2xl', '3xl']} fontWeight={'bold'}>
{feConfigs?.systemTitle}
</Box>
</Flex>
<Box
mt={'42px'}
onKeyDown={(e) => {
if (e.keyCode === 13 && !e.shiftKey && !requesting) {
handleSubmit(onclickLogin)();
}
}}
>
<FormControl isInvalid={!!errors.username}>
<Input
bg={'myGray.50'}
placeholder={isCommunityVersion ? '使用root用户登录' : '邮箱/手机号/用户名'}
{...register('username', {
required: '邮箱/手机号/用户名不能为空'
})}
></Input>
</FormControl>
<FormControl mt={6} isInvalid={!!errors.password}>
<Input
bg={'myGray.50'}
type={'password'}
placeholder={isCommunityVersion ? 'root密码为你设置的环境变量' : '密码'}
{...register('password', {
required: '密码不能为空',
maxLength: {
value: 20,
message: '密码最多 20 位'
}
})}
></Input>
</FormControl>
{feConfigs?.docUrl && (
<Box mt={7} fontSize={'sm'}>
使{' '}
<Link
href={getDocPath('/docs/agreement/disclaimer/')}
target={'_blank'}
color={'primary.500'}
>
</Link>
</Box>
)}
<Button
type="submit"
my={6}
w={'100%'}
size={['md', 'lg']}
colorScheme="blue"
isLoading={requesting}
onClick={handleSubmit(onclickLogin)}
>
{t('home.Login')}
</Button>
{feConfigs?.show_register && (
<>
<Flex align={'center'} justifyContent={'flex-end'} color={'primary.700'}>
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('forgetPassword')}
fontSize="sm"
>
?
</Box>
<Box mx={3} h={'16px'} w={'1.5px'} bg={'myGray.250'}></Box>
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('register')}
fontSize="sm"
>
</Box>
</Flex>
</>
)}
</Box>
<Box flex={1} />
{/* oauth */}
{feConfigs?.show_register && oAuthList.length > 0 && (
<>
<Box position={'relative'}>
<Divider />
<AbsoluteCenter bg="white" px="4" color={'myGray.500'}>
or
</AbsoluteCenter>
</Box>
<Box mt={8}>
{oAuthList.map((item) => (
<Box key={item.provider} _notFirst={{ mt: 4 }}>
<Button
variant={'whitePrimary'}
w={'100%'}
h={'42px'}
leftIcon={
<MyIcon
name={item.icon as any}
w={'20px'}
cursor={'pointer'}
color={'myGray.800'}
/>
}
onClick={() => {
setLoginStore({
provider: item.provider,
lastRoute,
state: state.current
});
router.replace(item.redirectUrl, '_self');
}}
>
{item.label}
</Button>
</Box>
))}
</Box>
</>
)}
</Flex>
);
};
export default LoginForm;

View File

@@ -0,0 +1,171 @@
import React, { useState, Dispatch, useCallback } from 'react';
import { FormControl, Flex, Input, Button, Box, Link } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { LoginPageTypeEnum } from '@/constants/user';
import { postLogin } from '@/web/support/user/api';
import type { ResLogin } from '@/global/support/api/userRes';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { getDocPath } from '@/web/common/system/doc';
import { useTranslation } from 'next-i18next';
import FormLayout from './components/FormLayout';
interface Props {
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
loginSuccess: (e: ResLogin) => void;
}
interface LoginFormType {
username: string;
password: string;
}
const LoginForm = ({ setPageType, loginSuccess }: Props) => {
const { t } = useTranslation();
const { toast } = useToast();
const { feConfigs } = useSystemStore();
const {
register,
handleSubmit,
formState: { errors }
} = useForm<LoginFormType>();
const [requesting, setRequesting] = useState(false);
const onclickLogin = useCallback(
async ({ username, password }: LoginFormType) => {
setRequesting(true);
try {
loginSuccess(
await postLogin({
username,
password
})
);
toast({
title: '登录成功',
status: 'success'
});
} catch (error: any) {
toast({
title: error.message || '登录异常',
status: 'error'
});
}
setRequesting(false);
},
[loginSuccess, toast]
);
const isCommunityVersion = feConfigs?.show_register === false && !feConfigs?.isPlus;
const loginOptions = [
feConfigs?.show_phoneLogin ? t('support.user.login.Phone number') : '',
feConfigs?.show_emailLogin ? t('support.user.login.Email') : '',
t('support.user.login.Username')
].filter(Boolean);
const placeholder = isCommunityVersion
? t('support.user.login.Root login')
: loginOptions.join('/');
return (
<FormLayout setPageType={setPageType} pageType={LoginPageTypeEnum.passwordLogin}>
<Box
mt={'42px'}
onKeyDown={(e) => {
if (e.keyCode === 13 && !e.shiftKey && !requesting) {
handleSubmit(onclickLogin)();
}
}}
>
<FormControl isInvalid={!!errors.username}>
<Input
bg={'myGray.50'}
placeholder={placeholder}
{...register('username', {
required: true
})}
></Input>
</FormControl>
<FormControl mt={6} isInvalid={!!errors.password}>
<Input
bg={'myGray.50'}
type={'password'}
placeholder={
isCommunityVersion
? t('support.user.login.Root password placeholder')
: t('support.user.login.Password')
}
{...register('password', {
required: true,
maxLength: {
value: 60,
message: '密码最多 60 位'
}
})}
></Input>
</FormControl>
{feConfigs?.docUrl && (
<Flex alignItems={'center'} mt={7} fontSize={'sm'}>
{t('support.user.login.Policy tip')}
<Link
ml={1}
href={getDocPath('/docs/agreement/terms/')}
target={'_blank'}
color={'primary.500'}
>
{t('support.user.login.Terms')}
</Link>
<Box mx={1}>{t('support.user.login.And')}</Box>
<Link
href={getDocPath('/docs/agreement/privacy/')}
target={'_blank'}
color={'primary.500'}
>
{t('support.user.login.Privacy')}
</Link>
</Flex>
)}
<Button
type="submit"
my={6}
w={'100%'}
size={['md', 'lg']}
colorScheme="blue"
isLoading={requesting}
onClick={handleSubmit(onclickLogin)}
>
{t('home.Login')}
</Button>
{feConfigs?.show_register && (
<>
<Flex align={'center'} justifyContent={'flex-end'} color={'primary.700'}>
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('forgetPassword')}
fontSize="sm"
>
{t('support.user.login.Forget Password')}
</Box>
<Box mx={3} h={'16px'} w={'1.5px'} bg={'myGray.250'}></Box>
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('register')}
fontSize="sm"
>
{t('support.user.login.Register')}
</Box>
</Flex>
</>
)}
</Box>
</FormLayout>
);
};
export default LoginForm;

View File

@@ -0,0 +1,60 @@
import React, { Dispatch } from 'react';
import { LoginPageTypeEnum } from '@/constants/user';
import type { ResLogin } from '@/global/support/api/userRes';
import { Box, Center, Image } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { getWXLoginQR, getWXLoginResult } from '@/web/support/user/api';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useRouter } from 'next/router';
import { useToast } from '@fastgpt/web/hooks/useToast';
import FormLayout from './components/FormLayout';
import { useTranslation } from 'next-i18next';
import Loading from '@/components/Loading';
interface Props {
loginSuccess: (e: ResLogin) => void;
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
}
const WechatForm = ({ setPageType, loginSuccess }: Props) => {
const { t } = useTranslation();
const { toast } = useToast();
const { data: wechatInfo } = useQuery(['getWXLoginQR'], getWXLoginQR, {
onError(err) {
toast({
status: 'warning',
title: getErrText(err, '获取二维码失败')
});
}
});
useQuery(['getWXLoginResult', wechatInfo?.code], () => getWXLoginResult(wechatInfo?.code || ''), {
refetchInterval: 3 * 1000,
enabled: !!wechatInfo?.code,
onSuccess(data: ResLogin) {
loginSuccess(data);
}
});
return (
<FormLayout setPageType={setPageType} pageType={LoginPageTypeEnum.wechat}>
<Box>
<Box w={'full'} textAlign={'center'} pt={5}>
{t('support.user.login.Wx qr login')}
</Box>
<Box p={5} display={'flex'} w={'full'} justifyContent={'center'}>
{wechatInfo?.codeUrl ? (
<Image w="200px" src={wechatInfo?.codeUrl} alt="qrcode"></Image>
) : (
<Center w={200} h={200} position={'relative'}>
<Loading fixed={false} />
</Center>
)}
</Box>
</Box>
</FormLayout>
);
};
export default WechatForm;

View File

@@ -0,0 +1,136 @@
import Divider from '@/components/core/module/Flow/components/modules/Divider';
import { LoginPageTypeEnum } from '@/constants/user';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { AbsoluteCenter, Box, Button, Flex, Image } from '@chakra-ui/react';
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
import { OAuthEnum } from '@fastgpt/global/support/user/constant';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { customAlphabet } from 'nanoid';
import { useRouter } from 'next/router';
import { Dispatch, useRef } from 'react';
import { useTranslation } from 'react-i18next';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
interface Props {
children: React.ReactNode;
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
pageType: `${LoginPageTypeEnum}`;
}
const FormLayout = ({ children, setPageType, pageType }: Props) => {
const { t } = useTranslation();
const router = useRouter();
const { setLoginStore, feConfigs } = useSystemStore();
const { lastRoute = '/app/list' } = router.query as { lastRoute: string };
const state = useRef(nanoid());
const redirectUri = `${location.origin}/login/provider`;
const oAuthList = [
...(feConfigs?.oauth?.github
? [
{
label: t('support.user.login.Github'),
provider: OAuthEnum.github,
icon: 'common/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
? [
{
label: t('support.user.login.Google'),
provider: OAuthEnum.google,
icon: 'common/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`
}
]
: []),
...(feConfigs?.oauth?.wechat && pageType !== LoginPageTypeEnum.wechat
? [
{
label: t('support.user.login.Wechat'),
provider: OAuthEnum.wechat,
icon: 'common/wechatFill',
pageType: LoginPageTypeEnum.wechat
}
]
: []),
...(pageType !== LoginPageTypeEnum.passwordLogin
? [
{
label: t('support.user.login.Password login'),
provider: LoginPageTypeEnum.passwordLogin,
icon: 'support/account/passwordLogin',
pageType: LoginPageTypeEnum.passwordLogin
}
]
: [])
];
return (
<Flex flexDirection={'column'} h={'100%'}>
<Flex alignItems={'center'}>
<Flex
w={['48px', '56px']}
h={['48px', '56px']}
bg={'myGray.25'}
borderRadius={'xl'}
borderWidth={'1.5px'}
borderColor={'borderColor.base'}
alignItems={'center'}
justifyContent={'center'}
>
<Image src={LOGO_ICON} w={'24px'} alt={'icon'} />
</Flex>
<Box ml={3} fontSize={['2xl', '3xl']} fontWeight={'bold'}>
{feConfigs?.systemTitle}
</Box>
</Flex>
{children}
<Box flex={1} />
{feConfigs?.show_register && oAuthList.length > 0 && (
<>
<Box position={'relative'}>
<Divider />
<AbsoluteCenter bg="white" px="4" color={'myGray.500'}>
or
</AbsoluteCenter>
</Box>
<Box mt={8}>
{oAuthList.map((item) => (
<Box key={item.provider} _notFirst={{ mt: 4 }}>
<Button
variant={'whitePrimary'}
w={'100%'}
h={'42px'}
leftIcon={
<MyIcon
name={item.icon as any}
w={'20px'}
cursor={'pointer'}
color={'myGray.800'}
/>
}
onClick={() => {
item.redirectUrl &&
setLoginStore({
provider: item.provider,
lastRoute,
state: state.current
});
item.redirectUrl && router.replace(item.redirectUrl, '_self');
item.pageType && setPageType(item.pageType);
}}
>
{item.label}
</Button>
</Box>
))}
</Box>
</>
)}
</Flex>
);
};
export default FormLayout;

View File

@@ -1,7 +1,7 @@
import React, { useState, Dispatch, useCallback } from 'react';
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { PageTypeEnum } from '@/constants/user';
import { LoginPageTypeEnum } from '@/constants/user';
import { postRegister } from '@/web/support/user/api';
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
import type { ResLogin } from '@/global/support/api/userRes';
@@ -13,7 +13,7 @@ import { useTranslation } from 'next-i18next';
interface Props {
loginSuccess: (e: ResLogin) => void;
setPageType: Dispatch<`${PageTypeEnum}`>;
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
}
interface RegisterType {
@@ -196,7 +196,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
color={'primary.700'}
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('login')}
onClick={() => setPageType(LoginPageTypeEnum.passwordLogin)}
>
</Box>

View File

@@ -1,25 +1,28 @@
import React, { useState, useCallback, useEffect } from 'react';
import { Box, Flex, Image, useDisclosure } from '@chakra-ui/react';
import { PageTypeEnum } from '@/constants/user';
import { Box, Center, Flex, useDisclosure } from '@chakra-ui/react';
import { LoginPageTypeEnum } from '@/constants/user';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useChatStore } from '@/web/core/chat/storeChat';
import LoginForm from './components/LoginForm';
import LoginForm from './components/LoginForm/LoginForm';
import dynamic from 'next/dynamic';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { clearToken, setToken } from '@/web/support/user/auth';
import CommunityModal from '@/components/CommunityModal';
import Script from 'next/script';
import Loading from '@/components/Loading';
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
const WechatForm = dynamic(() => import('./components/LoginForm/WechatForm'));
const Login = () => {
const router = useRouter();
const { lastRoute = '' } = router.query as { lastRoute: string };
const { feConfigs } = useSystemStore();
const [pageType, setPageType] = useState<`${PageTypeEnum}`>(PageTypeEnum.login);
const [pageType, setPageType] = useState<`${LoginPageTypeEnum}`>();
const { setUserInfo } = useUserStore();
const { setLastChatId, setLastChatAppId } = useChatStore();
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -39,11 +42,12 @@ const Login = () => {
[lastRoute, router, setLastChatId, setLastChatAppId, setUserInfo]
);
function DynamicComponent({ type }: { type: `${PageTypeEnum}` }) {
function DynamicComponent({ type }: { type: `${LoginPageTypeEnum}` }) {
const TypeMap = {
[PageTypeEnum.login]: LoginForm,
[PageTypeEnum.register]: RegisterForm,
[PageTypeEnum.forgetPassword]: ForgetPasswordForm
[LoginPageTypeEnum.passwordLogin]: LoginForm,
[LoginPageTypeEnum.register]: RegisterForm,
[LoginPageTypeEnum.forgetPassword]: ForgetPasswordForm,
[LoginPageTypeEnum.wechat]: WechatForm
};
const Component = TypeMap[type];
@@ -51,6 +55,13 @@ const Login = () => {
return <Component setPageType={setPageType} loginSuccess={loginSuccess} />;
}
/* default login type */
useEffect(() => {
if (!feConfigs.oauth) return;
setPageType(
feConfigs.oauth?.wechat ? LoginPageTypeEnum.wechat : LoginPageTypeEnum.passwordLogin
);
}, [feConfigs.oauth, feConfigs.oauth?.wechat]);
useEffect(() => {
clearToken();
router.prefetch('/app/list');
@@ -87,7 +98,13 @@ const Login = () => {
]}
>
<Box w={['100%', '380px']} flex={'1 0 0'}>
<DynamicComponent type={pageType} />
{pageType ? (
<DynamicComponent type={pageType} />
) : (
<Center w={'full'} h={'full'} position={'relative'}>
<Loading fixed={false} />
</Center>
)}
</Box>
{feConfigs?.concatMd && (
<Box

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Box, Button, Flex, Grid } from '@chakra-ui/react';
import { Box, Flex, Grid } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
const FAQ = () => {
@@ -15,7 +15,7 @@ const FAQ = () => {
},
{
title: '什么是AI积分',
desc: '每次调用AI模型时都会消耗一定的AI积分。具体的计算标准可参考上方的“AI 积分计算标准”。\n1 字符=1中英文字符和标点符号会去掉换行和空格符号计算字符时包含对话上下文与知识库引用。'
desc: '每次调用AI模型时都会消耗一定的AI积分。具体的计算标准可参考上方的“AI 积分计算标准”。\nToken计算采用GPT3.5相同公式1Token≈0.7中文字符≈0.9英文单词连续出现的字符可能被认为是1个Tokens。'
},
{
title: 'AI积分会过期么',

View File

@@ -42,7 +42,7 @@ const Points = () => {
{llmModelList?.map((item, i) => (
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
<Box flex={'1 0 0'}>{item.name}</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000 Tokens</Box>
</Flex>
))}
</Box>
@@ -67,7 +67,7 @@ const Points = () => {
{vectorModelList?.map((item, i) => (
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
<Box flex={'1 0 0'}>{item.name}</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000 Tokens</Box>
</Flex>
))}
</Box>