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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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 });
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
};
|
||||
@@ -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, {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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} 个无效文件`);
|
||||
}
|
||||
@@ -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} 个无效图片`);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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} 个无效 向量 数据`);
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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')} />
|
||||
|
||||
@@ -14,10 +14,6 @@ import {
|
||||
ModalBody,
|
||||
Input,
|
||||
Switch,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
Link
|
||||
} from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
|
||||
@@ -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)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
171
projects/app/src/pages/login/components/LoginForm/LoginForm.tsx
Normal file
171
projects/app/src/pages/login/components/LoginForm/LoginForm.tsx
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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积分会过期么?',
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user