mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-18 09:24:03 +00:00
V4.8.16 dev (#3431)
* feat: add feishu & yuque dataset (#3379) * feat: add feishu & yuque dataset * fix ts * fix ts * move type position * fix * fix: merge interface * fix * feat: dingtalk sso support (#3408) * fix: optional sso state * feat: dingtalk bot * feat: dingtalk sso login * chore: move i18n to user namespace * feat: dingtalk bot integration (#3415) * feat: dingtalk bot integration * docs: config dingtalk bot * feat:sear XNG服务 (#3413) * feat:sear XNG服务 * 补充了courseUrl * 添加了官方文档 * 错误时返回情况修正了一下 * Tracks (#3420) * feat: node intro * feat: add domain track * dingding sso login * perf: api dataset code and add doc * feat: tracks * feat: searXNG plugins * fix: ts * feat: delete node tracks (#3423) * fix: dingtalk bot GET verification (#3424) * 4.8.16 test: fix: plugin inputs render;fix: ui offset (#3426) * fix: ui offset * perf: dingding talk * fix: plugin inputs render * feat: menu all folder (#3429) * fix: recall code --------- Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: a.e. <49438478+I-Info@users.noreply.github.com> Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com>
This commit is contained in:
@@ -75,7 +75,6 @@
|
||||
"@faker-js/faker": "^9.0.3",
|
||||
"@shelf/jest-mongodb": "^4.3.2",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@types/faker": "^6.6.9",
|
||||
"@types/formidable": "^2.0.5",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/jsonwebtoken": "^9.0.3",
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 143 KiB |
@@ -2,7 +2,7 @@ import {
|
||||
PushDatasetDataChunkProps,
|
||||
PushDatasetDataResponse
|
||||
} from '@fastgpt/global/core/dataset/api';
|
||||
import { APIFileServer } from '@fastgpt/global/core/dataset/apiDataset';
|
||||
import { APIFileServer, FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset';
|
||||
import {
|
||||
DatasetSearchModeEnum,
|
||||
DatasetSourceReadTypeEnum,
|
||||
@@ -27,6 +27,8 @@ export type CreateDatasetParams = {
|
||||
vectorModel?: string;
|
||||
agentModel?: string;
|
||||
apiServer?: APIFileServer;
|
||||
feishuServer?: FeishuServer;
|
||||
yuqueServer?: YuqueServer;
|
||||
};
|
||||
|
||||
export type RebuildEmbeddingProps = {
|
||||
|
@@ -29,7 +29,7 @@ async function handler(req: ApiRequestProps<PreviewContextProps>, res: NextApiRe
|
||||
throw new Error('fileId is empty');
|
||||
}
|
||||
|
||||
const { teamId, apiServer } = await (async () => {
|
||||
const { teamId, apiServer, feishuServer, yuqueServer } = await (async () => {
|
||||
if (type === DatasetSourceReadTypeEnum.fileLocal) {
|
||||
const res = await authCollectionFile({
|
||||
req,
|
||||
@@ -51,7 +51,9 @@ async function handler(req: ApiRequestProps<PreviewContextProps>, res: NextApiRe
|
||||
});
|
||||
return {
|
||||
teamId: dataset.teamId,
|
||||
apiServer: dataset.apiServer
|
||||
apiServer: dataset.apiServer,
|
||||
feishuServer: dataset.feishuServer,
|
||||
yuqueServer: dataset.yuqueServer
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -62,6 +64,8 @@ async function handler(req: ApiRequestProps<PreviewContextProps>, res: NextApiRe
|
||||
isQAImport,
|
||||
selector,
|
||||
apiServer,
|
||||
feishuServer,
|
||||
yuqueServer,
|
||||
externalFileId
|
||||
});
|
||||
|
||||
|
49
projects/app/src/pages/api/common/tracks/push.ts
Normal file
49
projects/app/src/pages/api/common/tracks/push.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { TrackEnum } from '@fastgpt/global/common/middle/tracks/constants';
|
||||
import { TrackModel } from '@fastgpt/service/common/middle/tracks/schema';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { useReqFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequencyLimit';
|
||||
|
||||
export type pushQuery = {};
|
||||
|
||||
export type pushBody = {
|
||||
event: TrackEnum;
|
||||
data: any;
|
||||
};
|
||||
|
||||
export type pushResponse = {};
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<pushBody, pushQuery>,
|
||||
res: ApiResponseType<any>
|
||||
): Promise<pushResponse> {
|
||||
if (!global.feConfigs?.isPlus) return {};
|
||||
|
||||
const { teamId, tmbId, userId } = await authCert({
|
||||
req,
|
||||
authToken: true
|
||||
});
|
||||
|
||||
const data = {
|
||||
teamId,
|
||||
tmbId,
|
||||
uid: userId,
|
||||
event: req.body.event,
|
||||
data: req.body.data
|
||||
};
|
||||
|
||||
addLog.info('Push tracks', data);
|
||||
return TrackModel.create(data);
|
||||
}
|
||||
|
||||
export default NextAPI(useReqFrequencyLimit(1, 5), handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: {
|
||||
sizeLimit: '5kb'
|
||||
}
|
||||
}
|
||||
};
|
@@ -16,6 +16,7 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||
|
||||
export type CreateAppBody = {
|
||||
parentId?: ParentIdType;
|
||||
@@ -35,7 +36,7 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const [{ teamId, tmbId }] = await Promise.all([
|
||||
const [{ teamId, tmbId, userId }] = await Promise.all([
|
||||
authUserPer({ req, authToken: true, per: WritePermissionVal }),
|
||||
...(parentId
|
||||
? [authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true })]
|
||||
@@ -62,6 +63,13 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
username: user?.username
|
||||
});
|
||||
|
||||
pushTrack.createApp({
|
||||
type,
|
||||
uid: userId,
|
||||
teamId,
|
||||
tmbId
|
||||
});
|
||||
|
||||
return appId;
|
||||
}
|
||||
|
||||
|
@@ -16,6 +16,8 @@ import { findAppAndAllChildren } from '@fastgpt/service/core/app/controller';
|
||||
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
|
||||
import { ClientSession } from '@fastgpt/service/common/mongo';
|
||||
import { deleteChatFiles } from '@fastgpt/service/core/chat/controller';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
|
||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const { appId } = req.query as { appId: string };
|
||||
@@ -25,12 +27,20 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
}
|
||||
|
||||
// Auth owner (folder owner, can delete all apps in the folder)
|
||||
const { teamId } = await authApp({ req, authToken: true, appId, per: OwnerPermissionVal });
|
||||
const { teamId, tmbId, userId, app } = await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: OwnerPermissionVal
|
||||
});
|
||||
|
||||
await onDelOneApp({
|
||||
teamId,
|
||||
appId
|
||||
});
|
||||
|
||||
// Tracks
|
||||
pushTrack.countAppNodes({ teamId, tmbId, uid: userId, appId });
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
@@ -8,6 +8,7 @@ import { NextAPI } from '@/service/middleware/entry';
|
||||
import { onCreateApp, type CreateAppBody } from '../create';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||
|
||||
export type createHttpPluginQuery = {};
|
||||
|
||||
@@ -28,7 +29,11 @@ async function handler(
|
||||
return Promise.reject('缺少参数');
|
||||
}
|
||||
|
||||
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||
const { teamId, tmbId, userId } = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
// create http plugin folder
|
||||
@@ -62,6 +67,13 @@ async function handler(
|
||||
}
|
||||
});
|
||||
|
||||
pushTrack.createApp({
|
||||
type: AppTypeEnum.httpPlugin,
|
||||
uid: userId,
|
||||
teamId,
|
||||
tmbId
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@@ -45,7 +45,9 @@ async function handler(
|
||||
const { nodes, chatConfig } = await getAppLatestVersion(app._id, app);
|
||||
|
||||
const pluginInputs =
|
||||
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ?? [];
|
||||
chat?.pluginInputs ??
|
||||
nodes?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ??
|
||||
[];
|
||||
|
||||
return {
|
||||
chatId,
|
||||
|
@@ -35,6 +35,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
const { nodes, chatConfig } = await getAppLatestVersion(app._id, app);
|
||||
const pluginInputs =
|
||||
chat?.pluginInputs ??
|
||||
nodes?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ??
|
||||
[];
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
@@ -56,9 +60,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
avatar: app.avatar,
|
||||
intro: app.intro,
|
||||
type: app.type,
|
||||
pluginInputs:
|
||||
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)
|
||||
?.inputs ?? []
|
||||
pluginInputs
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -43,6 +43,10 @@ async function handler(req: ApiRequestProps<InitTeamChatProps>, res: NextApiResp
|
||||
|
||||
// get app and history
|
||||
const { nodes, chatConfig } = await getAppLatestVersion(app._id, app);
|
||||
const pluginInputs =
|
||||
chat?.pluginInputs ??
|
||||
nodes?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ??
|
||||
[];
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
@@ -64,9 +68,7 @@ async function handler(req: ApiRequestProps<InitTeamChatProps>, res: NextApiResp
|
||||
avatar: app.avatar,
|
||||
intro: app.intro,
|
||||
type: app.type,
|
||||
pluginInputs:
|
||||
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)
|
||||
?.inputs ?? []
|
||||
pluginInputs
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import { getFeishuAndYuqueDatasetFileList } from '@/service/core/dataset/apiDataset/controller';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { APIFileItem } from '@fastgpt/global/core/dataset/apiDataset';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
@@ -26,11 +28,21 @@ async function handler(req: NextApiRequest) {
|
||||
});
|
||||
|
||||
const apiServer = dataset.apiServer;
|
||||
if (!apiServer) {
|
||||
return Promise.reject('apiServer is required');
|
||||
const feishuServer = dataset.feishuServer;
|
||||
const yuqueServer = dataset.yuqueServer;
|
||||
|
||||
if (apiServer) {
|
||||
return useApiDatasetRequest({ apiServer }).listFiles({ searchKey, parentId });
|
||||
}
|
||||
if (feishuServer || yuqueServer) {
|
||||
return getFeishuAndYuqueDatasetFileList({
|
||||
feishuServer,
|
||||
yuqueServer,
|
||||
parentId
|
||||
});
|
||||
}
|
||||
|
||||
return useApiDatasetRequest({ apiServer }).listFiles({ searchKey, parentId });
|
||||
return Promise.reject(DatasetErrEnum.noApiServer);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
@@ -34,12 +34,8 @@ async function handler(req: NextApiRequest): CreateCollectionResponse {
|
||||
});
|
||||
|
||||
const apiServer = dataset.apiServer;
|
||||
if (!apiServer) {
|
||||
return Promise.reject('Api server not found');
|
||||
}
|
||||
if (!apiFileId) {
|
||||
return Promise.reject('ApiFileId not found');
|
||||
}
|
||||
const feishuServer = dataset.feishuServer;
|
||||
const yuqueServer = dataset.yuqueServer;
|
||||
|
||||
// Auth same apiFileId
|
||||
const storeCol = await MongoDatasetCollection.findOne(
|
||||
@@ -57,6 +53,8 @@ async function handler(req: NextApiRequest): CreateCollectionResponse {
|
||||
|
||||
const content = await readApiServerFileContent({
|
||||
apiServer,
|
||||
feishuServer,
|
||||
yuqueServer,
|
||||
apiFileId,
|
||||
teamId
|
||||
});
|
||||
|
@@ -66,7 +66,9 @@ async function handler(
|
||||
return {
|
||||
type: DatasetSourceReadTypeEnum.apiFile,
|
||||
sourceId: collection.apiFileId,
|
||||
apiServer: collection.datasetId.apiServer
|
||||
apiServer: collection.datasetId.apiServer,
|
||||
feishuServer: collection.datasetId.feishuServer,
|
||||
yuqueServer: collection.datasetId.yuqueServer
|
||||
};
|
||||
}
|
||||
if (collection.type === DatasetCollectionTypeEnum.externalFile) {
|
||||
|
@@ -12,6 +12,7 @@ import { AIChatItemType, ChatHistoryItemResType } from '@fastgpt/global/core/cha
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller';
|
||||
import { useApiDatasetRequest } from '@fastgpt/service/core/dataset/apiDataset/api';
|
||||
import { POST } from '@fastgpt/service/common/api/plusRequest';
|
||||
|
||||
export type readCollectionSourceQuery = {};
|
||||
|
||||
@@ -148,11 +149,25 @@ async function handler(
|
||||
}
|
||||
if (collection.type === DatasetCollectionTypeEnum.apiFile && collection.apiFileId) {
|
||||
const apiServer = collection.datasetId.apiServer;
|
||||
if (!apiServer) return Promise.reject('apiServer not found');
|
||||
const feishuServer = collection.datasetId.feishuServer;
|
||||
const yuqueServer = collection.datasetId.yuqueServer;
|
||||
|
||||
return useApiDatasetRequest({ apiServer }).getFilePreviewUrl({
|
||||
apiFileId: collection.apiFileId
|
||||
});
|
||||
if (apiServer) {
|
||||
return useApiDatasetRequest({ apiServer }).getFilePreviewUrl({
|
||||
apiFileId: collection.apiFileId
|
||||
});
|
||||
}
|
||||
|
||||
if (feishuServer || yuqueServer) {
|
||||
return POST<string>(`/core/dataset/systemApiDataset`, {
|
||||
type: 'read',
|
||||
apiFileId: collection.apiFileId,
|
||||
feishuServer,
|
||||
yuqueServer
|
||||
});
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
if (collection.type === DatasetCollectionTypeEnum.externalFile) {
|
||||
if (collection.externalFileId && collection.datasetId.externalReadUrl) {
|
||||
|
@@ -10,6 +10,7 @@ import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||
|
||||
export type DatasetCreateQuery = {};
|
||||
export type DatasetCreateBody = CreateDatasetParams;
|
||||
@@ -26,11 +27,13 @@ async function handler(
|
||||
avatar,
|
||||
vectorModel = global.vectorModels[0].model,
|
||||
agentModel = getDatasetModel().model,
|
||||
apiServer
|
||||
apiServer,
|
||||
feishuServer,
|
||||
yuqueServer
|
||||
} = req.body;
|
||||
|
||||
// auth
|
||||
const [{ teamId, tmbId }] = await Promise.all([
|
||||
const [{ teamId, tmbId, userId }] = await Promise.all([
|
||||
authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
@@ -70,7 +73,16 @@ async function handler(
|
||||
agentModel,
|
||||
avatar,
|
||||
type,
|
||||
apiServer
|
||||
apiServer,
|
||||
feishuServer,
|
||||
yuqueServer
|
||||
});
|
||||
|
||||
pushTrack.createDataset({
|
||||
type,
|
||||
teamId,
|
||||
tmbId,
|
||||
uid: userId
|
||||
});
|
||||
|
||||
return _id;
|
||||
|
@@ -36,6 +36,19 @@ async function handler(req: ApiRequestProps<Query>): Promise<DatasetItemType> {
|
||||
authorization: ''
|
||||
}
|
||||
: undefined,
|
||||
yuqueServer: dataset.yuqueServer
|
||||
? {
|
||||
userId: dataset.yuqueServer.userId,
|
||||
token: ''
|
||||
}
|
||||
: undefined,
|
||||
feishuServer: dataset.feishuServer
|
||||
? {
|
||||
appId: dataset.feishuServer.appId,
|
||||
appSecret: '',
|
||||
folderToken: dataset.feishuServer.folderToken
|
||||
}
|
||||
: undefined,
|
||||
permission,
|
||||
vectorModel: getVectorModel(dataset.vectorModel),
|
||||
agentModel: getLLMModel(dataset.agentModel)
|
||||
|
@@ -50,7 +50,7 @@ async function handler(
|
||||
throw new Error('chunkSize is too large, should be less than 30000');
|
||||
}
|
||||
|
||||
const { teamId, apiServer } = await (async () => {
|
||||
const { teamId, apiServer, feishuServer, yuqueServer } = await (async () => {
|
||||
if (type === DatasetSourceReadTypeEnum.fileLocal) {
|
||||
const res = await authCollectionFile({
|
||||
req,
|
||||
@@ -72,7 +72,9 @@ async function handler(
|
||||
});
|
||||
return {
|
||||
teamId: dataset.teamId,
|
||||
apiServer: dataset.apiServer
|
||||
apiServer: dataset.apiServer,
|
||||
feishuServer: dataset.feishuServer,
|
||||
yuqueServer: dataset.yuqueServer
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -83,6 +85,8 @@ async function handler(
|
||||
selector,
|
||||
isQAImport,
|
||||
apiServer,
|
||||
feishuServer,
|
||||
yuqueServer,
|
||||
externalFileId
|
||||
});
|
||||
|
||||
|
@@ -57,6 +57,8 @@ async function handler(
|
||||
websiteConfig,
|
||||
externalReadUrl,
|
||||
apiServer,
|
||||
yuqueServer,
|
||||
feishuServer,
|
||||
status,
|
||||
autoSync
|
||||
} = req.body;
|
||||
@@ -124,6 +126,13 @@ async function handler(
|
||||
...(!!apiServer?.authorization && {
|
||||
'apiServer.authorization': apiServer.authorization
|
||||
}),
|
||||
...(!!yuqueServer?.userId && { 'yuqueServer.userId': yuqueServer.userId }),
|
||||
...(!!yuqueServer?.token && { 'yuqueServer.token': yuqueServer.token }),
|
||||
...(!!feishuServer?.appId && { 'feishuServer.appId': feishuServer.appId }),
|
||||
...(!!feishuServer?.appSecret && { 'feishuServer.appSecret': feishuServer.appSecret }),
|
||||
...(!!feishuServer?.folderToken && {
|
||||
'feishuServer.folderToken': feishuServer.folderToken
|
||||
}),
|
||||
...(isMove && { inheritPermission: true }),
|
||||
...(typeof autoSync === 'boolean' && { autoSync })
|
||||
},
|
||||
|
@@ -0,0 +1,27 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { POST } from '@fastgpt/service/common/api/plusRequest';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
|
||||
export type OutLinkDingtalkQuery = any;
|
||||
export type OutLinkDingtalkBody = any;
|
||||
export type OutLinkFeishuResponse = {};
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<OutLinkDingtalkBody, OutLinkDingtalkQuery>,
|
||||
res: ApiResponseType<any>
|
||||
): Promise<any> {
|
||||
if (req.method === 'GET') {
|
||||
return {
|
||||
success: true
|
||||
};
|
||||
}
|
||||
// send to pro
|
||||
const { token } = req.query;
|
||||
const result = await POST<any>(`support/outLink/dingtalk/${token}`, req.body, {
|
||||
headers: req.headers as any
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
@@ -9,13 +9,11 @@ async function handler(
|
||||
req: ApiRequestProps<OutLinkOffiAccountBody, OutLinkOffiAccountQuery>,
|
||||
res: ApiResponseType<any>
|
||||
): Promise<any> {
|
||||
const { token, type } = req.query;
|
||||
const { token } = req.query;
|
||||
const result = await plusRequest({
|
||||
method: req.method,
|
||||
url: `support/outLink/offiaccount/${token}`,
|
||||
params: {
|
||||
...req.query,
|
||||
type
|
||||
},
|
||||
params: req.query,
|
||||
data: req.body
|
||||
});
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import type { PostLoginProps } from '@fastgpt/global/support/user/api.d';
|
||||
import { UserStatusEnum } from '@fastgpt/global/support/user/constant';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { useReqFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequencyLimit';
|
||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { username, password } = req.body as PostLoginProps;
|
||||
@@ -47,6 +48,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
lastLoginTmbId: userDetail.team.tmbId
|
||||
});
|
||||
|
||||
pushTrack.login({
|
||||
type: 'password',
|
||||
uid: user._id,
|
||||
teamId: userDetail.team.teamId,
|
||||
tmbId: userDetail.team.tmbId
|
||||
});
|
||||
|
||||
const token = createJWT({
|
||||
...userDetail,
|
||||
isRoot: username === 'root'
|
||||
|
@@ -10,7 +10,6 @@ import dynamic from 'next/dynamic';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
|
||||
import CloseIcon from '@fastgpt/web/components/common/Icon/close';
|
||||
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { PcHeader } from '@/pages/chat/components/ChatHeader';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
@@ -22,6 +21,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
||||
const ChatBox = dynamic(() => import('@/components/core/chat/ChatContainer/ChatBox'));
|
||||
|
||||
type Props = {
|
||||
appId: string;
|
||||
@@ -148,10 +148,12 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
|
||||
)}
|
||||
|
||||
{/* Chat container */}
|
||||
<Box pt={2} flex={'1 0 0'}>
|
||||
<Box pt={2} flex={'1 0 0'} h={0}>
|
||||
{isPlugin ? (
|
||||
<Box px={5} pt={2} h={'100%'}>
|
||||
<PluginRunBox appId={appId} chatId={chatId} />
|
||||
<Box h={'100%'} overflow={'auto'}>
|
||||
<Box px={5} py={2}>
|
||||
<PluginRunBox appId={appId} chatId={chatId} />
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<ChatBox
|
||||
|
@@ -0,0 +1,152 @@
|
||||
import React from 'react';
|
||||
import { Flex, Box, Button, ModalBody, Input, Link } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import type { DingtalkAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { createShareChat, updateShareChat } from '@/web/support/outLink/api';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import BasicInfo from '../components/BasicInfo';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
|
||||
const DingTalkEditModal = ({
|
||||
appId,
|
||||
defaultData,
|
||||
onClose,
|
||||
onCreate,
|
||||
onEdit,
|
||||
isEdit = false
|
||||
}: {
|
||||
appId: string;
|
||||
defaultData: OutLinkEditType<DingtalkAppType>;
|
||||
onClose: () => void;
|
||||
onCreate: (id: string) => void;
|
||||
onEdit: () => void;
|
||||
isEdit?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
register,
|
||||
setValue,
|
||||
handleSubmit: submitShareChat
|
||||
} = useForm({
|
||||
defaultValues: defaultData
|
||||
});
|
||||
|
||||
const { runAsync: onclickCreate, loading: creating } = useRequest2(
|
||||
(e: Omit<OutLinkEditType<DingtalkAppType>, 'appId' | 'type'>) =>
|
||||
createShareChat({
|
||||
...e,
|
||||
appId,
|
||||
type: PublishChannelEnum.dingtalk,
|
||||
app: {
|
||||
clientId: e?.app?.clientId?.trim(),
|
||||
clientSecret: e.app?.clientSecret?.trim()
|
||||
}
|
||||
}),
|
||||
{
|
||||
errorToast: t('common:common.Create Failed'),
|
||||
successToast: t('common:common.Create Success'),
|
||||
onSuccess: onCreate
|
||||
}
|
||||
);
|
||||
|
||||
const { runAsync: onclickUpdate, loading: updating } = useRequest2(
|
||||
(e) =>
|
||||
updateShareChat({
|
||||
...e,
|
||||
app: {
|
||||
clientId: e?.app?.clientId?.trim(),
|
||||
clientSecret: e.app?.clientSecret?.trim()
|
||||
}
|
||||
}),
|
||||
{
|
||||
errorToast: t('common:common.Update Failed'),
|
||||
successToast: t('common:common.Update Success'),
|
||||
onSuccess: onEdit
|
||||
}
|
||||
);
|
||||
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc="common/dingtalkFill"
|
||||
title={
|
||||
isEdit ? t('publish:dingtalk.edit_modal_title') : t('publish:dingtalk.create_modal_title')
|
||||
}
|
||||
minW={['auto', '60rem']}
|
||||
>
|
||||
<ModalBody display={'grid'} gridTemplateColumns={['1fr', '1fr 1fr']} fontSize={'14px'} p={0}>
|
||||
<Box p={8} h={['auto', '400px']} borderRight={'base'}>
|
||||
<BasicInfo register={register} setValue={setValue} defaultData={defaultData} />
|
||||
</Box>
|
||||
<Flex p={8} h={['auto', '400px']} flexDirection="column" gap={6}>
|
||||
<Flex alignItems="center">
|
||||
<Box color="myGray.600">{t('publish:dingtalk.api')}</Box>
|
||||
{feConfigs?.docUrl && (
|
||||
<Link
|
||||
href={
|
||||
feConfigs.openAPIDocUrl ||
|
||||
getDocPath('/docs/use-cases/external-integration/dingtalk/')
|
||||
}
|
||||
target={'_blank'}
|
||||
ml={2}
|
||||
color={'primary.500'}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon w={'17px'} h={'17px'} name="book" mr="1" />
|
||||
{t('common:common.Read document')}
|
||||
</Flex>
|
||||
</Link>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 6.25rem'} required>
|
||||
Client ID
|
||||
</FormLabel>
|
||||
<Input
|
||||
placeholder={'Client ID'}
|
||||
{...register('app.clientId', {
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 6.25rem'} required>
|
||||
Client Secret
|
||||
</FormLabel>
|
||||
<Input
|
||||
placeholder={'Client Secret'}
|
||||
{...register('app.clientSecret', {
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Box flex={1}></Box>
|
||||
|
||||
<Flex justifyContent={'end'}>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common:common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
isLoading={creating || updating}
|
||||
onClick={submitShareChat((data) =>
|
||||
isEdit ? onclickUpdate(data) : onclickCreate(data)
|
||||
)}
|
||||
>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DingTalkEditModal;
|
@@ -0,0 +1,248 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
Button,
|
||||
TableContainer,
|
||||
Table,
|
||||
Thead,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
Tbody,
|
||||
useDisclosure,
|
||||
Link,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import { getShareChatList, delShareChatById } from '@/web/support/outLink/api';
|
||||
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
|
||||
import { defaultDingtalkOutlinkForm } from '@/web/core/app/constants';
|
||||
import type { DingtalkAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import dayjs from 'dayjs';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
|
||||
const DingTalkEditModal = dynamic(() => import('./DingTalkEditModal'));
|
||||
const ShowShareLinkModal = dynamic(() => import('../components/showShareLinkModal'));
|
||||
|
||||
const DingTalk = ({ appId }: { appId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const [editDingTalkLinkData, setEditDingTalkLinkData] =
|
||||
useState<OutLinkEditType<DingtalkAppType>>();
|
||||
const [isEdit, setIsEdit] = useState<boolean>(false);
|
||||
|
||||
const baseUrl = useMemo(
|
||||
() => feConfigs?.customApiDomain || `${location.origin}/api`,
|
||||
[feConfigs?.customApiDomain]
|
||||
);
|
||||
|
||||
const {
|
||||
data: shareChatList = [],
|
||||
loading: isFetching,
|
||||
runAsync: refetchShareChatList
|
||||
} = useRequest2(
|
||||
() => getShareChatList<DingtalkAppType>({ appId, type: PublishChannelEnum.dingtalk }),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const {
|
||||
onOpen: openShowShareLinkModal,
|
||||
isOpen: showShareLinkModalOpen,
|
||||
onClose: closeShowShareLinkModal
|
||||
} = useDisclosure();
|
||||
|
||||
const [showShareLink, setShowShareLink] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<Box position={'relative'} pt={3} px={5} minH={'50vh'}>
|
||||
<Flex justifyContent={'space-between'} flexDirection="row">
|
||||
<HStack>
|
||||
<Box fontWeight={'bold'} fontSize={['md', 'lg']}>
|
||||
{t('publish:dingtalk.title')}
|
||||
</Box>
|
||||
{feConfigs?.docUrl && (
|
||||
<Link
|
||||
href={
|
||||
feConfigs.openAPIDocUrl ||
|
||||
getDocPath('/docs/use-cases/external-integration/dingtalk/')
|
||||
}
|
||||
target={'_blank'}
|
||||
color={'primary.500'}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name="book" mr="1" w={'1rem'} />
|
||||
{t('common:common.Read document')}
|
||||
</Flex>
|
||||
</Link>
|
||||
)}
|
||||
</HStack>
|
||||
<Button
|
||||
variant={'primary'}
|
||||
colorScheme={'blue'}
|
||||
size={['sm', 'md']}
|
||||
leftIcon={<MyIcon name={'common/addLight'} w="1.25rem" color="white" />}
|
||||
ml={3}
|
||||
{...(shareChatList.length >= 10
|
||||
? {
|
||||
isDisabled: true,
|
||||
title: t('common:core.app.share.Amount limit tip')
|
||||
}
|
||||
: {})}
|
||||
onClick={() => {
|
||||
setEditDingTalkLinkData(defaultDingtalkOutlinkForm);
|
||||
setIsEdit(false);
|
||||
}}
|
||||
>
|
||||
{t('common:add_new')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<TableContainer mt={3}>
|
||||
<Table variant={'simple'} w={'100%'} overflowX={'auto'} fontSize={'sm'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('common:common.Name')}</Th>
|
||||
<Th>{t('common:support.outlink.Usage points')}</Th>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Th>{t('common:core.app.share.Ip limit title')}</Th>
|
||||
<Th>{t('common:common.Expired Time')}</Th>
|
||||
</>
|
||||
)}
|
||||
<Th>{t('common:common.Last use time')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{shareChatList.map((item) => (
|
||||
<Tr key={item._id}>
|
||||
<Td>{item.name}</Td>
|
||||
<Td>
|
||||
{Math.round(item.usagePoints)}
|
||||
{feConfigs?.isPlus
|
||||
? `${
|
||||
item.limit?.maxUsagePoints && item.limit.maxUsagePoints > -1
|
||||
? ` / ${item.limit.maxUsagePoints}`
|
||||
: ` / ${t('common:common.Unlimited')}`
|
||||
}`
|
||||
: ''}
|
||||
</Td>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Td>{item?.limit?.QPM || '-'}</Td>
|
||||
<Td>
|
||||
{item?.limit?.expiredTime
|
||||
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
|
||||
: '-'}
|
||||
</Td>
|
||||
</>
|
||||
)}
|
||||
<Td>
|
||||
{item.lastTime
|
||||
? t(formatTimeToChatTime(item.lastTime) as any).replace('#', ':')
|
||||
: t('common:common.Un used')}
|
||||
</Td>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setShowShareLink(`${baseUrl}/support/outLink/dingtalk/${item.shareId}`);
|
||||
openShowShareLinkModal();
|
||||
}}
|
||||
size={'sm'}
|
||||
mr={3}
|
||||
variant={'whitePrimary'}
|
||||
>
|
||||
{t('publish:request_address')}
|
||||
</Button>
|
||||
<MyMenu
|
||||
Button={
|
||||
<MyIcon
|
||||
name={'more'}
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
w={'14px'}
|
||||
p={2}
|
||||
/>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
label: t('common:common.Edit'),
|
||||
icon: 'edit',
|
||||
onClick: () => {
|
||||
setEditDingTalkLinkData({
|
||||
_id: item._id,
|
||||
name: item.name,
|
||||
limit: item.limit,
|
||||
app: item.app,
|
||||
responseDetail: item.responseDetail,
|
||||
defaultResponse: item.defaultResponse,
|
||||
immediateResponse: item.immediateResponse
|
||||
});
|
||||
setIsEdit(true);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common:common.Delete'),
|
||||
icon: 'delete',
|
||||
onClick: async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await delShareChatById(item._id);
|
||||
refetchShareChatList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{editDingTalkLinkData && (
|
||||
<DingTalkEditModal
|
||||
appId={appId}
|
||||
defaultData={editDingTalkLinkData}
|
||||
onCreate={() => Promise.all([refetchShareChatList(), setEditDingTalkLinkData(undefined)])}
|
||||
onEdit={() => Promise.all([refetchShareChatList(), setEditDingTalkLinkData(undefined)])}
|
||||
onClose={() => setEditDingTalkLinkData(undefined)}
|
||||
isEdit={isEdit}
|
||||
/>
|
||||
)}
|
||||
{shareChatList.length === 0 && !isFetching && (
|
||||
<EmptyTip text={t('common:core.app.share.Not share link')}></EmptyTip>
|
||||
)}
|
||||
<Loading loading={isFetching} fixed={false} />
|
||||
{showShareLinkModalOpen && (
|
||||
<ShowShareLinkModal
|
||||
shareLink={showShareLink ?? ''}
|
||||
onClose={closeShowShareLinkModal}
|
||||
img="/imgs/outlink/dingtalk-copylink-instruction.png"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(DingTalk);
|
@@ -16,6 +16,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
const Link = dynamic(() => import('./Link'));
|
||||
const API = dynamic(() => import('./API'));
|
||||
const FeiShu = dynamic(() => import('./FeiShu'));
|
||||
const DingTalk = dynamic(() => import('./DingTalk'));
|
||||
// const Wecom = dynamic(() => import('./Wecom'));
|
||||
const OffiAccount = dynamic(() => import('./OffiAccount'));
|
||||
|
||||
@@ -48,6 +49,13 @@ const OutLink = () => {
|
||||
value: PublishChannelEnum.feishu,
|
||||
isProFn: true
|
||||
},
|
||||
{
|
||||
icon: 'common/dingtalkFill',
|
||||
title: t('publish:dingtalk.bot'),
|
||||
desc: t('publish:dingtalk.bot_desc'),
|
||||
value: PublishChannelEnum.dingtalk,
|
||||
isProFn: true
|
||||
},
|
||||
// {
|
||||
// icon: 'core/app/publish/wecom',
|
||||
// title: t('publish:wecom.bot'),
|
||||
@@ -114,6 +122,7 @@ const OutLink = () => {
|
||||
)}
|
||||
{linkType === PublishChannelEnum.apikey && <API appId={appId} />}
|
||||
{linkType === PublishChannelEnum.feishu && <FeiShu appId={appId} />}
|
||||
{linkType === PublishChannelEnum.dingtalk && <DingTalk appId={appId} />}
|
||||
{/* {linkType === PublishChannelEnum.wecom && <Wecom appId={appId} />} */}
|
||||
{linkType === PublishChannelEnum.officialAccount && <OffiAccount appId={appId} />}
|
||||
</Flex>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
|
||||
@@ -8,12 +8,14 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { WorkflowNodeEdgeContext } from '../../context/workflowInitContext';
|
||||
import { WorkflowEventContext } from '../../context/workflowEventContext';
|
||||
import { WorkflowContext } from '../../context';
|
||||
|
||||
const ContextMenu = () => {
|
||||
const { t } = useTranslation();
|
||||
const setNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setNodes);
|
||||
const menu = useContextSelector(WorkflowEventContext, (v) => v.menu);
|
||||
const setMenu = useContextSelector(WorkflowEventContext, (ctx) => ctx.setMenu);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
const newNode = nodeTemplate2FlowNode({
|
||||
@@ -22,38 +24,44 @@ const ContextMenu = () => {
|
||||
t
|
||||
});
|
||||
|
||||
return (
|
||||
!!menu && (
|
||||
<Box position="relative">
|
||||
<Box
|
||||
position="absolute"
|
||||
top={`${menu.top - 6}px`}
|
||||
left={`${menu.left + 10}px`}
|
||||
width={0}
|
||||
height={0}
|
||||
borderLeft="6px solid transparent"
|
||||
borderRight="6px solid transparent"
|
||||
borderBottom="6px solid white"
|
||||
zIndex={2}
|
||||
filter="drop-shadow(0px -1px 2px rgba(0, 0, 0, 0.1))"
|
||||
/>
|
||||
const allUnFolded = useMemo(() => {
|
||||
return !!menu ? nodeList.some((node) => node.isFolded) : false;
|
||||
}, [nodeList, menu]);
|
||||
|
||||
return !!menu ? (
|
||||
<Box position="relative">
|
||||
<Box
|
||||
position="absolute"
|
||||
top={`${menu.top - 6}px`}
|
||||
left={`${menu.left + 10}px`}
|
||||
width={0}
|
||||
height={0}
|
||||
borderLeft="6px solid transparent"
|
||||
borderRight="6px solid transparent"
|
||||
borderBottom="6px solid white"
|
||||
zIndex={2}
|
||||
filter="drop-shadow(0px -1px 2px rgba(0, 0, 0, 0.1))"
|
||||
/>
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={menu.top}
|
||||
left={menu.left}
|
||||
bg={'white'}
|
||||
w={'120px'}
|
||||
rounded={'md'}
|
||||
boxShadow={'0px 2px 4px 0px #A1A7B340'}
|
||||
className="context-menu"
|
||||
color={'myGray.600'}
|
||||
p={1}
|
||||
zIndex={10}
|
||||
>
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
top={menu.top}
|
||||
left={menu.left}
|
||||
bg={'white'}
|
||||
w={'120px'}
|
||||
height={9}
|
||||
p={1}
|
||||
rounded={'md'}
|
||||
boxShadow={'0px 2px 4px 0px #A1A7B340'}
|
||||
className="context-menu"
|
||||
alignItems={'center'}
|
||||
color={'myGray.600'}
|
||||
px={2}
|
||||
py={1}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'primary.500'
|
||||
}}
|
||||
borderRadius={'sm'}
|
||||
_hover={{ bg: 'myGray.50', color: 'primary.500' }}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
setNodes((state) => {
|
||||
@@ -67,16 +75,41 @@ const ContextMenu = () => {
|
||||
return newState;
|
||||
});
|
||||
}}
|
||||
zIndex={1}
|
||||
>
|
||||
<MyIcon name="comment" w={'16px'} h={'16px'} ml={1} />
|
||||
<MyIcon name="comment" w={'1rem'} ml={1} />
|
||||
<Box fontSize={'12px'} fontWeight={'500'} ml={1.5}>
|
||||
{t('workflow:context_menu.add_comment')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex
|
||||
mt={1}
|
||||
alignItems={'center'}
|
||||
px={2}
|
||||
py={1}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'sm'}
|
||||
_hover={{ bg: 'myGray.50', color: 'primary.500' }}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
setNodes((state) => {
|
||||
return state.map((node) => ({
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
isFolded: !allUnFolded
|
||||
}
|
||||
}));
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon name="common/select" w={'1rem'} ml={1} />
|
||||
<Box fontSize={'12px'} fontWeight={'500'} ml={1.5}>
|
||||
{allUnFolded ? t('workflow:unFoldAll') : t('workflow:foldAll')}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default React.memo(ContextMenu);
|
||||
|
@@ -126,7 +126,6 @@ const Workflow = () => {
|
||||
<NodeTemplatesModal isOpen={isOpenTemplate} onClose={onCloseTemplate} />
|
||||
</>
|
||||
|
||||
<ContextMenu />
|
||||
<ReactFlow
|
||||
ref={reactFlowWrapper}
|
||||
fitView
|
||||
@@ -162,6 +161,7 @@ const Workflow = () => {
|
||||
: {})}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
>
|
||||
<ContextMenu />
|
||||
<FlowController />
|
||||
<HelperLines horizontal={helperLineHorizontal} vertical={helperLineVertical} />
|
||||
</ReactFlow>
|
||||
|
@@ -282,7 +282,7 @@ const NodeCard = (props: Props) => {
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
{intro && <NodeIntro nodeId={nodeId} intro={intro} />}
|
||||
<NodeIntro nodeId={nodeId} intro={intro} />
|
||||
</Box>
|
||||
)}
|
||||
<MenuRender nodeId={nodeId} menuForbid={menuForbid} nodeList={nodeList} />
|
||||
@@ -580,7 +580,7 @@ const NodeIntro = React.memo(function NodeIntro({
|
||||
<>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontSize={'sm'} color={'myGray.500'} flex={'1 0 0'}>
|
||||
{t(intro as any)}
|
||||
{t(intro as any) || t('app:node_not_intro')}
|
||||
</Box>
|
||||
{NodeIsTool && (
|
||||
<Flex
|
||||
|
@@ -33,6 +33,7 @@ import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import SearchInput from '../../../../../../../packages/web/components/common/Input/SearchInput/index';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { webPushTrack } from '@/web/common/middle/tracks/utils';
|
||||
|
||||
type TemplateAppType = AppTypeEnum | 'all';
|
||||
|
||||
@@ -98,6 +99,13 @@ const TemplateMarketModal = ({
|
||||
modules: templateDetail.workflow.nodes || [],
|
||||
edges: templateDetail.workflow.edges || [],
|
||||
chatConfig: templateDetail.workflow.chatConfig
|
||||
}).then((res) => {
|
||||
webPushTrack.useAppTemplate({
|
||||
id,
|
||||
name: templateDetail.name
|
||||
});
|
||||
|
||||
return res;
|
||||
});
|
||||
},
|
||||
{
|
||||
|
166
projects/app/src/pages/dataset/component/ApiDatasetForm.tsx
Normal file
166
projects/app/src/pages/dataset/component/ApiDatasetForm.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import React from 'react';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { Flex, Input } from '@chakra-ui/react';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type {
|
||||
APIFileServer,
|
||||
FeishuServer,
|
||||
YuqueServer
|
||||
} from '@fastgpt/global/core/dataset/apiDataset';
|
||||
|
||||
const ApiDatasetForm = ({
|
||||
type,
|
||||
form
|
||||
}: {
|
||||
type: `${DatasetTypeEnum}`;
|
||||
form: UseFormReturn<
|
||||
{
|
||||
apiServer?: APIFileServer;
|
||||
feishuServer?: FeishuServer;
|
||||
yuqueServer?: YuqueServer;
|
||||
},
|
||||
any
|
||||
>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register } = form;
|
||||
|
||||
return (
|
||||
<>
|
||||
{type === DatasetTypeEnum.apiDataset && (
|
||||
<>
|
||||
<Flex mt={6}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
{t('dataset:api_url')}
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={t('dataset:api_url')}
|
||||
maxLength={200}
|
||||
{...register('apiServer.baseUrl', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={6}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
Authorization
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={t('dataset:request_headers')}
|
||||
maxLength={200}
|
||||
{...register('apiServer.authorization')}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{type === DatasetTypeEnum.feishu && (
|
||||
<>
|
||||
<Flex mt={6}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
App ID
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={'App ID'}
|
||||
maxLength={200}
|
||||
{...register('feishuServer.appId', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={6}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
App Secret
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={'App Secret'}
|
||||
maxLength={200}
|
||||
{...register('feishuServer.appSecret', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={6}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
Folder Token
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={'Folder Token'}
|
||||
maxLength={200}
|
||||
{...register('feishuServer.folderToken', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{type === DatasetTypeEnum.yuque && (
|
||||
<>
|
||||
<Flex mt={6}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
User ID
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={'Token'}
|
||||
maxLength={200}
|
||||
{...register('yuqueServer.userId', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={6}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
Token
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={'Token'}
|
||||
maxLength={200}
|
||||
{...register('yuqueServer.token', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiDatasetForm;
|
@@ -386,7 +386,9 @@ const Header = ({}: {}) => {
|
||||
/>
|
||||
)}
|
||||
{/* apiDataset */}
|
||||
{datasetDetail?.type === DatasetTypeEnum.apiDataset && (
|
||||
{(datasetDetail?.type === DatasetTypeEnum.apiDataset ||
|
||||
datasetDetail?.type === DatasetTypeEnum.feishu ||
|
||||
datasetDetail?.type === DatasetTypeEnum.yuque) && (
|
||||
<Flex
|
||||
px={3.5}
|
||||
py={2}
|
||||
|
@@ -177,8 +177,7 @@ const Upload = () => {
|
||||
router.replace({
|
||||
query: {
|
||||
datasetId: datasetDetail._id,
|
||||
currentTab: retrainNewCollectionId.current ? TabEnum.dataCard : TabEnum.collectionCard,
|
||||
collectionId: retrainNewCollectionId.current
|
||||
currentTab: TabEnum.collectionCard
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@@ -49,23 +49,26 @@ const CustomAPIFileInput = () => {
|
||||
parentId: '',
|
||||
parentName: ''
|
||||
});
|
||||
const [parentUuid, setParentUuid] = useState<string>('');
|
||||
const [paths, setPaths] = useState<ParentTreePathItemType[]>([]);
|
||||
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
|
||||
const { data: fileList = [], loading } = useRequest2(
|
||||
async () =>
|
||||
datasetDetail?.apiServer
|
||||
? getApiDatasetFileList({
|
||||
datasetId: datasetDetail._id,
|
||||
parentId: parent?.parentId,
|
||||
searchKey: searchKey
|
||||
})
|
||||
: [],
|
||||
async () => {
|
||||
return getApiDatasetFileList({
|
||||
datasetId: datasetDetail._id,
|
||||
parentId: parent?.parentId,
|
||||
searchKey: searchKey
|
||||
});
|
||||
},
|
||||
{
|
||||
refreshDeps: [datasetDetail._id, datasetDetail.apiServer, parent, searchKey],
|
||||
throttleWait: 500,
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const { data: existIdList = [] } = useRequest2(
|
||||
() => getApiDatasetFileListExistId({ datasetId: datasetDetail._id }),
|
||||
{
|
||||
@@ -90,6 +93,7 @@ const CustomAPIFileInput = () => {
|
||||
datasetId: datasetDetail._id,
|
||||
parentId: file?.id
|
||||
});
|
||||
|
||||
const subFiles = await getFilesRecursively(folderFiles);
|
||||
allFiles.push(...subFiles);
|
||||
} else {
|
||||
@@ -125,6 +129,7 @@ const CustomAPIFileInput = () => {
|
||||
const handleItemClick = useCallback(
|
||||
(item: APIFileItem) => {
|
||||
if (item.type === 'folder') {
|
||||
setPaths((state) => [...state, { parentId: item.id, parentName: item.name }]);
|
||||
return setParent({
|
||||
parentId: item.id,
|
||||
parentName: item.name
|
||||
@@ -138,7 +143,7 @@ const CustomAPIFileInput = () => {
|
||||
setSelectFiles((state) => [...state, item]);
|
||||
}
|
||||
},
|
||||
[selectFiles, setSelectFiles]
|
||||
[selectFiles]
|
||||
);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
@@ -151,8 +156,6 @@ const CustomAPIFileInput = () => {
|
||||
}
|
||||
}, [fileList, selectFiles]);
|
||||
|
||||
const paths = useMemo(() => [parent || { parentId: '', parentName: '' }], [parent]);
|
||||
|
||||
return (
|
||||
<MyBox isLoading={loading} position="relative" h="full">
|
||||
<Flex flexDirection={'column'} h="full">
|
||||
@@ -160,21 +163,21 @@ const CustomAPIFileInput = () => {
|
||||
<FolderPath
|
||||
paths={paths}
|
||||
onClick={(parentId) => {
|
||||
if (parentId !== parent?.parentId) {
|
||||
setParent({
|
||||
parentId,
|
||||
parentName: ''
|
||||
});
|
||||
}
|
||||
const index = paths.findIndex((item) => item.parentId === parentId);
|
||||
|
||||
setParent(paths[index]);
|
||||
setPaths(paths.slice(0, index + 1));
|
||||
}}
|
||||
/>
|
||||
<Box w={'240px'}>
|
||||
<SearchInput
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
placeholder={t('common:core.workflow.template.Search')}
|
||||
/>
|
||||
</Box>
|
||||
{datasetDetail.apiServer && (
|
||||
<Box w={'240px'}>
|
||||
<SearchInput
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
placeholder={t('common:core.workflow.template.Search')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
<Box flex={1} overflowY="auto" mb={16}>
|
||||
<Box ml={2} mt={3}>
|
||||
|
@@ -1,15 +1,23 @@
|
||||
import React from 'react';
|
||||
import { ModalFooter, ModalBody, Input, Button, Flex } from '@chakra-ui/react';
|
||||
import { ModalFooter, ModalBody, Button, Flex } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal/index';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { APIFileServer } from '@fastgpt/global/core/dataset/apiDataset';
|
||||
import { APIFileServer, FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset';
|
||||
import ApiDatasetForm from '@/pages/dataset/component/ApiDatasetForm';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import { datasetTypeCourseMap } from '@/web/core/dataset/constants';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
export type EditAPIDatasetInfoFormType = {
|
||||
id: string;
|
||||
apiServer?: APIFileServer;
|
||||
yuqueServer?: YuqueServer;
|
||||
feishuServer?: FeishuServer;
|
||||
};
|
||||
|
||||
const EditAPIDatasetInfoModal = ({
|
||||
@@ -24,7 +32,11 @@ const EditAPIDatasetInfoModal = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { register, handleSubmit } = useForm<EditAPIDatasetInfoFormType>({
|
||||
|
||||
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
|
||||
const type = datasetDetail.type;
|
||||
|
||||
const form = useForm<EditAPIDatasetInfoFormType>({
|
||||
defaultValues: defaultForm
|
||||
});
|
||||
|
||||
@@ -44,43 +56,24 @@ const EditAPIDatasetInfoModal = ({
|
||||
return (
|
||||
<MyModal isOpen onClose={onClose} w={'450px'} iconSrc="modal/edit" title={title}>
|
||||
<ModalBody>
|
||||
<Flex>
|
||||
{datasetTypeCourseMap[type] && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
justifyContent={'flex-end'}
|
||||
color={'primary.600'}
|
||||
fontSize={'sm'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => window.open(getDocPath(datasetTypeCourseMap[type]), '_blank')}
|
||||
>
|
||||
{t('dataset:api_url')}
|
||||
<MyIcon name={'book'} w={4} mr={0.5} />
|
||||
{t('common:Instructions')}
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={t('dataset:api_url')}
|
||||
maxLength={200}
|
||||
{...register('apiServer.baseUrl', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={6}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
Authorization
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={t('dataset:request_headers')}
|
||||
maxLength={200}
|
||||
{...register('apiServer.authorization')}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{/* @ts-ignore */}
|
||||
<ApiDatasetForm type={type} form={form} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button isLoading={loading} onClick={handleSubmit(onSave)} px={6}>
|
||||
<Button isLoading={loading} onClick={form.handleSubmit(onSave)} px={6}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
@@ -29,13 +29,12 @@ import {
|
||||
} from '@/web/core/dataset/api/collaborator';
|
||||
import DatasetTypeTag from '@/components/core/dataset/DatasetTypeTag';
|
||||
import dynamic from 'next/dynamic';
|
||||
import EditAPIDatasetInfoModal, {
|
||||
EditAPIDatasetInfoFormType
|
||||
} from './components/EditApiServiceModal';
|
||||
import type { EditAPIDatasetInfoFormType } from './components/EditApiServiceModal';
|
||||
import { EditResourceInfoFormType } from '@/components/common/Modal/EditResourceModal';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal'));
|
||||
const EditAPIDatasetInfoModal = dynamic(() => import('./components/EditApiServiceModal'));
|
||||
|
||||
const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -326,6 +325,56 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{datasetDetail.type === DatasetTypeEnum.yuque && (
|
||||
<>
|
||||
<Box w={'100%'} alignItems={'center'} pt={4}>
|
||||
<Flex justifyContent={'space-between'} mb={1}>
|
||||
<FormLabel fontSize={'mini'} fontWeight={'500'}>
|
||||
{t('dataset:yuque_dataset_config')}
|
||||
</FormLabel>
|
||||
<MyIcon
|
||||
name={'edit'}
|
||||
w={'14px'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
setEditedAPIDataset({
|
||||
id: datasetDetail._id,
|
||||
yuqueServer: datasetDetail.yuqueServer
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
<Box fontSize={'mini'}>{datasetDetail.yuqueServer?.userId}</Box>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{datasetDetail.type === DatasetTypeEnum.feishu && (
|
||||
<>
|
||||
<Box w={'100%'} alignItems={'center'} pt={4}>
|
||||
<Flex justifyContent={'space-between'} mb={1}>
|
||||
<FormLabel fontSize={'mini'} fontWeight={'500'}>
|
||||
{t('dataset:feishu_dataset_config')}
|
||||
</FormLabel>
|
||||
<MyIcon
|
||||
name={'edit'}
|
||||
w={'14px'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
setEditedAPIDataset({
|
||||
id: datasetDetail._id,
|
||||
feishuServer: datasetDetail.feishuServer
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
<Box fontSize={'mini'}>{datasetDetail.feishuServer?.folderToken}</Box>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{datasetDetail.permission.hasManagePer && (
|
||||
@@ -384,12 +433,14 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
{editedAPIDataset && (
|
||||
<EditAPIDatasetInfoModal
|
||||
{...editedAPIDataset}
|
||||
title={t('common:dataset.Edit API Service')}
|
||||
title={t('dataset:edit_dataset_config')}
|
||||
onClose={() => setEditedAPIDataset(undefined)}
|
||||
onEdit={(data) =>
|
||||
updateDataset({
|
||||
id: datasetId,
|
||||
apiServer: data.apiServer
|
||||
apiServer: data.apiServer,
|
||||
yuqueServer: data.yuqueServer,
|
||||
feishuServer: data.feishuServer
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
@@ -22,11 +22,15 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import ComplianceTip from '@/components/common/ComplianceTip/index';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { datasetTypeCourseMap } from '@/web/core/dataset/constants';
|
||||
import ApiDatasetForm from '../../component/ApiDatasetForm';
|
||||
|
||||
export type CreateDatasetType =
|
||||
| DatasetTypeEnum.dataset
|
||||
| DatasetTypeEnum.apiDataset
|
||||
| DatasetTypeEnum.websiteDataset;
|
||||
| DatasetTypeEnum.websiteDataset
|
||||
| DatasetTypeEnum.feishu
|
||||
| DatasetTypeEnum.yuque;
|
||||
|
||||
const CreateModal = ({
|
||||
onClose,
|
||||
@@ -43,35 +47,45 @@ const CreateModal = ({
|
||||
const { vectorModelList, datasetModelList } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const databaseNameMap = useMemo(() => {
|
||||
const datasetTypeMap = useMemo(() => {
|
||||
return {
|
||||
[DatasetTypeEnum.dataset]: t('dataset:common_dataset'),
|
||||
[DatasetTypeEnum.apiDataset]: t('dataset:api_file'),
|
||||
[DatasetTypeEnum.websiteDataset]: t('dataset:website_dataset')
|
||||
[DatasetTypeEnum.dataset]: {
|
||||
name: t('dataset:common_dataset'),
|
||||
icon: 'core/dataset/commonDatasetColor'
|
||||
},
|
||||
[DatasetTypeEnum.apiDataset]: {
|
||||
name: t('dataset:api_file'),
|
||||
icon: 'core/dataset/externalDatasetColor'
|
||||
},
|
||||
[DatasetTypeEnum.websiteDataset]: {
|
||||
name: t('dataset:website_dataset'),
|
||||
icon: 'core/dataset/websiteDatasetColor'
|
||||
},
|
||||
[DatasetTypeEnum.feishu]: {
|
||||
name: t('dataset:feishu_dataset'),
|
||||
icon: 'core/dataset/feishuDatasetColor'
|
||||
},
|
||||
[DatasetTypeEnum.yuque]: {
|
||||
name: t('dataset:yuque_dataset'),
|
||||
icon: 'core/dataset/yuqueDatasetColor'
|
||||
}
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
const iconMap = useMemo(() => {
|
||||
return {
|
||||
[DatasetTypeEnum.dataset]: 'core/dataset/commonDatasetColor',
|
||||
[DatasetTypeEnum.apiDataset]: 'core/dataset/externalDatasetColor',
|
||||
[DatasetTypeEnum.websiteDataset]: 'core/dataset/websiteDatasetColor'
|
||||
};
|
||||
}, []);
|
||||
|
||||
const filterNotHiddenVectorModelList = vectorModelList.filter((item) => !item.hidden);
|
||||
|
||||
const { register, setValue, handleSubmit, watch } = useForm<CreateDatasetParams>({
|
||||
const form = useForm<CreateDatasetParams>({
|
||||
defaultValues: {
|
||||
parentId,
|
||||
type: type || DatasetTypeEnum.dataset,
|
||||
avatar: iconMap[type] || 'core/dataset/commonDatasetColor',
|
||||
avatar: datasetTypeMap[type].icon,
|
||||
name: '',
|
||||
intro: '',
|
||||
vectorModel: filterNotHiddenVectorModelList[0].model,
|
||||
agentModel: datasetModelList[0].model
|
||||
}
|
||||
});
|
||||
const { register, setValue, handleSubmit, watch } = form;
|
||||
const avatar = watch('avatar');
|
||||
const vectorModel = watch('vectorModel');
|
||||
const agentModel = watch('agentModel');
|
||||
@@ -119,8 +133,14 @@ const CreateModal = ({
|
||||
<MyModal
|
||||
title={
|
||||
<Flex alignItems={'center'} ml={-3}>
|
||||
<Avatar w={'20px'} h={'20px'} borderRadius={'xs'} src={iconMap[type]} pr={'10px'} />
|
||||
{t('common:core.dataset.Create dataset', { name: databaseNameMap[type] })}
|
||||
<Avatar
|
||||
w={'20px'}
|
||||
h={'20px'}
|
||||
borderRadius={'xs'}
|
||||
src={datasetTypeMap[type].icon}
|
||||
pr={'10px'}
|
||||
/>
|
||||
{t('common:core.dataset.Create dataset', { name: datasetTypeMap[type].name })}
|
||||
</Flex>
|
||||
}
|
||||
isOpen
|
||||
@@ -134,16 +154,14 @@ const CreateModal = ({
|
||||
<Box color={'myGray.900'} fontWeight={500} fontSize={'sm'}>
|
||||
{t('common:common.Set Name')}
|
||||
</Box>
|
||||
{type === DatasetTypeEnum.apiDataset && (
|
||||
{datasetTypeCourseMap[type] && (
|
||||
<Flex
|
||||
as={'span'}
|
||||
alignItems={'center'}
|
||||
color={'primary.600'}
|
||||
fontSize={'sm'}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
window.open(getDocPath('/docs/guide/knowledge_base/api_dataset/'), '_blank')
|
||||
}
|
||||
onClick={() => window.open(getDocPath(datasetTypeCourseMap[type]), '_blank')}
|
||||
>
|
||||
<MyIcon name={'book'} w={4} mr={0.5} />
|
||||
{t('common:Instructions')}
|
||||
@@ -242,44 +260,8 @@ const CreateModal = ({
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{type === DatasetTypeEnum.apiDataset && (
|
||||
<>
|
||||
<Flex mt={6}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
{t('dataset:api_url')}
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={t('dataset:api_url')}
|
||||
maxLength={200}
|
||||
{...register('apiServer.baseUrl', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={6}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flex={['', '0 0 110px']}
|
||||
color={'myGray.900'}
|
||||
fontWeight={500}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
Authorization
|
||||
</Flex>
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={t('dataset:request_headers')}
|
||||
maxLength={200}
|
||||
{...register('apiServer.authorization')}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{/* @ts-ignore */}
|
||||
<ApiDatasetForm type={type} form={form} />
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter px={9}>
|
||||
|
@@ -24,6 +24,14 @@ const SideTag = ({ type, ...props }: { type: `${DatasetTypeEnum}` } & FlexProps)
|
||||
[DatasetTypeEnum.apiDataset]: {
|
||||
icon: 'core/dataset/externalDatasetOutline',
|
||||
label: t('dataset:api_file')
|
||||
},
|
||||
[DatasetTypeEnum.feishu]: {
|
||||
icon: 'core/dataset/feishuDatasetOutline',
|
||||
label: t('dataset:feishu_dataset')
|
||||
},
|
||||
[DatasetTypeEnum.yuque]: {
|
||||
icon: 'core/dataset/yuqueDatasetOutline',
|
||||
label: t('dataset:yuque_dataset')
|
||||
}
|
||||
};
|
||||
}, [t]);
|
||||
|
@@ -64,7 +64,10 @@ const Dataset = () => {
|
||||
|
||||
const onSelectDatasetType = useCallback(
|
||||
(e: CreateDatasetType) => {
|
||||
if (!feConfigs?.isPlus && e === DatasetTypeEnum.websiteDataset) {
|
||||
if (
|
||||
!feConfigs?.isPlus &&
|
||||
[DatasetTypeEnum.websiteDataset, DatasetTypeEnum.feishu, DatasetTypeEnum.yuque].includes(e)
|
||||
) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:common.system.Commercial version function')
|
||||
@@ -168,6 +171,18 @@ const Dataset = () => {
|
||||
label: t('dataset:website_dataset'),
|
||||
description: t('dataset:website_dataset_desc'),
|
||||
onClick: () => onSelectDatasetType(DatasetTypeEnum.websiteDataset)
|
||||
},
|
||||
{
|
||||
icon: 'core/dataset/feishuDatasetColor',
|
||||
label: t('dataset:feishu_dataset'),
|
||||
description: t('dataset:feishu_dataset_desc'),
|
||||
onClick: () => onSelectDatasetType(DatasetTypeEnum.feishu)
|
||||
},
|
||||
{
|
||||
icon: 'core/dataset/yuqueDatasetColor',
|
||||
label: t('dataset:yuque_dataset'),
|
||||
description: t('dataset:yuque_dataset_desc'),
|
||||
onClick: () => onSelectDatasetType(DatasetTypeEnum.yuque)
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -6,7 +6,7 @@ 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 { Dispatch, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import I18nLngSelector from '@/components/Select/I18nLngSelector';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
@@ -42,6 +42,16 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(feConfigs?.oauth?.dingtalk
|
||||
? [
|
||||
{
|
||||
label: t('user:login.Dingtalk'),
|
||||
provider: OAuthEnum.dingtalk,
|
||||
icon: 'common/dingtalkFill',
|
||||
redirectUrl: `https://login.dingtalk.com/oauth2/auth?client_id=${feConfigs?.oauth?.dingtalk}&redirect_uri=${redirectUri}&state=${state.current}&response_type=code&scope=openid&prompt=consent`
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(feConfigs?.oauth?.google
|
||||
? [
|
||||
{
|
||||
@@ -85,8 +95,10 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
|
||||
: [])
|
||||
];
|
||||
|
||||
const show_oauth =
|
||||
!sessionStorage.getItem('bd_vid') && !!(feConfigs?.sso?.url || oAuthList.length > 0);
|
||||
const show_oauth = useMemo(
|
||||
() => !sessionStorage.getItem('bd_vid') && !!(feConfigs?.sso?.url || oAuthList.length > 0),
|
||||
[feConfigs?.sso?.url, oAuthList.length]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} h={'100%'}>
|
||||
|
@@ -45,16 +45,6 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
|
||||
|
||||
const { runAsync: onclickRegister, loading: requesting } = useRequest2(
|
||||
async ({ username, password, code }: RegisterType) => {
|
||||
const fastgpt_sem = (() => {
|
||||
try {
|
||||
return sessionStorage.getItem('fastgpt_sem')
|
||||
? JSON.parse(sessionStorage.getItem('fastgpt_sem')!)
|
||||
: undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
|
||||
loginSuccess(
|
||||
await postRegister({
|
||||
username,
|
||||
@@ -62,7 +52,16 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
|
||||
password,
|
||||
inviterId: localStorage.getItem('inviterId') || undefined,
|
||||
bd_vid: sessionStorage.getItem('bd_vid') || undefined,
|
||||
fastgpt_sem: fastgpt_sem
|
||||
fastgpt_sem: (() => {
|
||||
try {
|
||||
return sessionStorage.getItem('fastgpt_sem')
|
||||
? JSON.parse(sessionStorage.getItem('fastgpt_sem')!)
|
||||
: undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
})(),
|
||||
sourceDomain: sessionStorage.getItem('sourceDomain') || undefined
|
||||
})
|
||||
);
|
||||
|
||||
|
@@ -43,7 +43,18 @@ const provider = () => {
|
||||
type: loginStore?.provider as `${OAuthEnum}`,
|
||||
code,
|
||||
callbackUrl: `${location.origin}/login/provider`,
|
||||
inviterId: localStorage.getItem('inviterId') || undefined
|
||||
inviterId: localStorage.getItem('inviterId') || undefined,
|
||||
bd_vid: sessionStorage.getItem('bd_vid') || undefined,
|
||||
fastgpt_sem: (() => {
|
||||
try {
|
||||
return sessionStorage.getItem('fastgpt_sem')
|
||||
? JSON.parse(sessionStorage.getItem('fastgpt_sem')!)
|
||||
: undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
})(),
|
||||
sourceDomain: sessionStorage.getItem('sourceDomain') || undefined
|
||||
});
|
||||
|
||||
if (!res) {
|
||||
@@ -79,12 +90,8 @@ const provider = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!code ||
|
||||
!loginStore ||
|
||||
(loginStore.provider !== OAuthEnum.sso && (!loginStore.state || !state))
|
||||
)
|
||||
return;
|
||||
console.log('SSO', { loginStore, code, state });
|
||||
if (!code || !loginStore) return;
|
||||
|
||||
if (isOauthLogging) return;
|
||||
|
||||
|
@@ -0,0 +1,15 @@
|
||||
import { GetApiDatasetFileListResponse } from '@/pages/api/core/dataset/apiDataset/list';
|
||||
import { FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset';
|
||||
import { POST } from '@fastgpt/service/common/api/plusRequest';
|
||||
|
||||
export const getFeishuAndYuqueDatasetFileList = async (data: {
|
||||
feishuServer?: FeishuServer;
|
||||
yuqueServer?: YuqueServer;
|
||||
parentId?: string;
|
||||
}) => {
|
||||
const res = await POST<GetApiDatasetFileListResponse>('/core/dataset/systemApiDataset', {
|
||||
type: 'list',
|
||||
...data
|
||||
});
|
||||
return res;
|
||||
};
|
21
projects/app/src/web/common/middle/tracks/utils.ts
Normal file
21
projects/app/src/web/common/middle/tracks/utils.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { POST } from '@/web/common/api/request';
|
||||
import { TrackEnum } from '@fastgpt/global/common/middle/tracks/constants';
|
||||
import { useSystemStore } from '../../system/useSystemStore';
|
||||
|
||||
const createTrack = ({ event, data }: { event: TrackEnum; data: any }) => {
|
||||
if (!useSystemStore.getState()?.feConfigs?.isPlus) return;
|
||||
|
||||
return POST('/common/tracks/push', {
|
||||
event,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
export const webPushTrack = {
|
||||
useAppTemplate: (data: { id: string; name: string }) => {
|
||||
return createTrack({
|
||||
event: TrackEnum.useAppTemplate,
|
||||
data
|
||||
});
|
||||
}
|
||||
};
|
@@ -8,7 +8,12 @@ import { TrackEventName } from '../common/system/constants';
|
||||
|
||||
export const useInitApp = () => {
|
||||
const router = useRouter();
|
||||
const { hiId, bd_vid, k } = router.query as { hiId?: string; bd_vid?: string; k?: string };
|
||||
const { hiId, bd_vid, k, sourceDomain } = router.query as {
|
||||
hiId?: string;
|
||||
bd_vid?: string;
|
||||
k?: string;
|
||||
sourceDomain?: string;
|
||||
};
|
||||
const { loadGitStar, setInitd, feConfigs } = useSystemStore();
|
||||
const [scripts, setScripts] = useState<FastGPTFeConfigsType['scripts']>([]);
|
||||
const [title, setTitle] = useState(process.env.SYSTEM_NAME || 'AI');
|
||||
@@ -61,7 +66,16 @@ export const useInitApp = () => {
|
||||
hiId && localStorage.setItem('inviterId', hiId);
|
||||
bd_vid && sessionStorage.setItem('bd_vid', bd_vid);
|
||||
k && sessionStorage.setItem('fastgpt_sem', JSON.stringify({ keyword: k }));
|
||||
}, [bd_vid, hiId, k]);
|
||||
|
||||
const formatSourceDomain = (() => {
|
||||
if (sourceDomain) return sourceDomain;
|
||||
return document.referrer;
|
||||
})();
|
||||
console.log(formatSourceDomain, '-=-=');
|
||||
if (formatSourceDomain && !sessionStorage.getItem('sourceDomain')) {
|
||||
sessionStorage.setItem('sourceDomain', formatSourceDomain);
|
||||
}
|
||||
}, [bd_vid, hiId, k, sourceDomain]);
|
||||
|
||||
return {
|
||||
feConfigs,
|
||||
|
@@ -1,6 +1,10 @@
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
|
||||
import type { FeishuAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
|
||||
import type {
|
||||
DingtalkAppType,
|
||||
FeishuAppType,
|
||||
OutLinkEditType
|
||||
} from '@fastgpt/global/support/outLink/type.d';
|
||||
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
|
||||
export const defaultApp: AppDetailType = {
|
||||
_id: '',
|
||||
@@ -39,6 +43,14 @@ export const defaultFeishuOutLinkForm: OutLinkEditType<FeishuAppType> = {
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultDingtalkOutlinkForm: OutLinkEditType<DingtalkAppType> = {
|
||||
name: '',
|
||||
limit: {
|
||||
QPM: 100,
|
||||
maxUsagePoints: -1
|
||||
}
|
||||
};
|
||||
|
||||
export enum TTSTypeEnum {
|
||||
none = 'none',
|
||||
web = 'web',
|
||||
|
@@ -66,6 +66,7 @@ import type {
|
||||
listExistIdQuery,
|
||||
listExistIdResponse
|
||||
} from '@/pages/api/core/dataset/apiDataset/listExistId';
|
||||
import { FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset';
|
||||
|
||||
/* ======================== dataset ======================= */
|
||||
export const getDatasets = (data: GetDatasetListBody) =>
|
||||
|
@@ -65,3 +65,13 @@ export enum ImportProcessWayEnum {
|
||||
auto = 'auto',
|
||||
custom = 'custom'
|
||||
}
|
||||
|
||||
export const datasetTypeCourseMap: Record<`${DatasetTypeEnum}`, string> = {
|
||||
[DatasetTypeEnum.folder]: '',
|
||||
[DatasetTypeEnum.dataset]: '',
|
||||
[DatasetTypeEnum.apiDataset]: '/docs/guide/knowledge_base/api_dataset/',
|
||||
[DatasetTypeEnum.websiteDataset]: '/docs/guide/knowledge_base/websync/',
|
||||
[DatasetTypeEnum.feishu]: '/docs/guide/knowledge_base/lark_dataset/',
|
||||
[DatasetTypeEnum.yuque]: '/docs/guide/knowledge_base/yuque_dataset/',
|
||||
[DatasetTypeEnum.externalFile]: ''
|
||||
};
|
||||
|
@@ -121,6 +121,19 @@ export const DatasetPageContextProvider = ({
|
||||
baseUrl: data.apiServer.baseUrl,
|
||||
authorization: ''
|
||||
}
|
||||
: undefined,
|
||||
yuqueServer: data.yuqueServer
|
||||
? {
|
||||
userId: data.yuqueServer.userId,
|
||||
token: ''
|
||||
}
|
||||
: undefined,
|
||||
feishuServer: data.feishuServer
|
||||
? {
|
||||
appId: data.feishuServer.appId,
|
||||
appSecret: '',
|
||||
folderToken: data.feishuServer.folderToken
|
||||
}
|
||||
: undefined
|
||||
}));
|
||||
}
|
||||
|
@@ -1,3 +1,6 @@
|
||||
.react-flow {
|
||||
overflow: visible;
|
||||
}
|
||||
.react-flow__panel.react-flow__attribution {
|
||||
z-index: 0;
|
||||
left: 0;
|
||||
|
@@ -9,7 +9,10 @@ import type {
|
||||
OauthLoginProps,
|
||||
PostLoginProps
|
||||
} from '@fastgpt/global/support/user/api.d';
|
||||
import { GetWXLoginQRResponse } from '@fastgpt/global/support/user/login/api.d';
|
||||
import {
|
||||
AccountRegisterBody,
|
||||
GetWXLoginQRResponse
|
||||
} from '@fastgpt/global/support/user/login/api.d';
|
||||
|
||||
export const sendAuthCode = (data: {
|
||||
username: string;
|
||||
@@ -33,16 +36,7 @@ export const postRegister = ({
|
||||
inviterId,
|
||||
bd_vid,
|
||||
fastgpt_sem
|
||||
}: {
|
||||
username: string;
|
||||
code: string;
|
||||
password: string;
|
||||
inviterId?: string;
|
||||
bd_vid?: string;
|
||||
fastgpt_sem?: {
|
||||
keyword: string;
|
||||
};
|
||||
}) =>
|
||||
}: AccountRegisterBody) =>
|
||||
POST<ResLogin>(`/proApi/support/user/account/register/emailAndPhone`, {
|
||||
username,
|
||||
code,
|
||||
|
Reference in New Issue
Block a user