mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-21 11:43:56 +00:00
4.8.10 perf (#2378)
* perf: helpline code * fix: prompt call stream=false response prefix * fix: app chat log auth * perf: new chat i18n * fix: milvus dataset cannot export data * perf: doc intro
This commit is contained in:
@@ -20,4 +20,10 @@ weight: 816
|
||||
## V4.8.10 更新说明
|
||||
|
||||
1. 新增 - 模板市场
|
||||
2.
|
||||
2. 新增 - 工作流节点拖动自动对齐吸附
|
||||
3. 新增 - 用户选择节点(Debug 模式暂未支持)
|
||||
4. 商业版新增 - 飞书机器人接入
|
||||
5. 商业版新增 - 公众号接入接入
|
||||
6. 修复 - Prompt 模式调用工具,stream=false 模式下,会携带 0: 开头标记。
|
||||
7. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情。
|
||||
8. 修复 - 选择 Milvus 部署时,无法导出知识库。
|
||||
|
@@ -411,6 +411,7 @@ const parseAnswer = (
|
||||
str = str.trim();
|
||||
// 首先,使用正则表达式提取TOOL_ID和TOOL_ARGUMENTS
|
||||
const prefixReg = /^1(:|:)/;
|
||||
const answerPrefixReg = /^0(:|:)/;
|
||||
|
||||
if (prefixReg.test(str)) {
|
||||
const toolString = sliceJsonStr(str);
|
||||
@@ -432,7 +433,7 @@ const parseAnswer = (
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
answer: str
|
||||
answer: str.replace(answerPrefixReg, '')
|
||||
};
|
||||
}
|
||||
};
|
||||
|
2
projects/app/src/global/core/chat/api.d.ts
vendored
2
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -30,7 +30,7 @@ export type InitChatResponse = {
|
||||
chatId?: string;
|
||||
appId: string;
|
||||
userAvatar?: string;
|
||||
title: string;
|
||||
title?: string;
|
||||
variables: Record<string, any>;
|
||||
history: ChatItemType[];
|
||||
app: {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { InitChatResponse } from './api';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
|
||||
export const defaultChatData: InitChatResponse = {
|
||||
chatId: '',
|
||||
appId: '',
|
||||
@@ -12,7 +12,7 @@ export const defaultChatData: InitChatResponse = {
|
||||
type: AppTypeEnum.simple,
|
||||
pluginInputs: []
|
||||
},
|
||||
title: i18nT('chat:new_chat'),
|
||||
title: '',
|
||||
variables: {},
|
||||
history: []
|
||||
};
|
||||
|
@@ -1,11 +1,15 @@
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import {
|
||||
ManagePermissionVal,
|
||||
ReadPermissionVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
|
||||
export type getResDataQuery = OutLinkChatAuthProps & {
|
||||
chatId?: string;
|
||||
@@ -25,12 +29,24 @@ async function handler(
|
||||
if (!appId || !chatId || !dataId) {
|
||||
return {};
|
||||
}
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
...req.query,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
// 1. Un login api: share chat, team chat
|
||||
// 2. Login api: account chat, chat log
|
||||
try {
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
...req.query,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
} catch (error) {
|
||||
await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: ManagePermissionVal
|
||||
});
|
||||
}
|
||||
|
||||
const chatData = await MongoChatItem.findOne({
|
||||
appId,
|
||||
|
@@ -14,7 +14,7 @@ import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { transformPreviewHistories } from '@/global/core/chat/utils';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
|
||||
async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
@@ -62,7 +62,7 @@ async function handler(
|
||||
return {
|
||||
chatId,
|
||||
appId,
|
||||
title: chat?.title || i18nT('chat:new_chat'),
|
||||
title: chat?.title,
|
||||
userAvatar: undefined,
|
||||
variables: chat?.variables || {},
|
||||
history: app.type === AppTypeEnum.plugin ? histories : transformPreviewHistories(histories),
|
||||
|
@@ -18,90 +18,84 @@ import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { transformPreviewHistories } from '@/global/core/chat/utils';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
|
||||
let { chatId, shareId, outLinkUid } = req.query as InitOutLinkChatProps;
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
let { chatId, shareId, outLinkUid } = req.query as InitOutLinkChatProps;
|
||||
|
||||
// auth link permission
|
||||
const { shareChat, uid, appId } = await authOutLink({ shareId, outLinkUid });
|
||||
// auth link permission
|
||||
const { shareChat, uid, appId } = await authOutLink({ shareId, outLinkUid });
|
||||
|
||||
// auth app permission
|
||||
const [tmb, chat, app] = await Promise.all([
|
||||
MongoTeamMember.findById(shareChat.tmbId, '_id userId').populate('userId', 'avatar').lean(),
|
||||
MongoChat.findOne({ appId, chatId, shareId }).lean(),
|
||||
MongoApp.findById(appId).lean()
|
||||
]);
|
||||
// auth app permission
|
||||
const [tmb, chat, app] = await Promise.all([
|
||||
MongoTeamMember.findById(shareChat.tmbId, '_id userId').populate('userId', 'avatar').lean(),
|
||||
MongoChat.findOne({ appId, chatId, shareId }).lean(),
|
||||
MongoApp.findById(appId).lean()
|
||||
]);
|
||||
|
||||
if (!app) {
|
||||
throw new Error(AppErrEnum.unExist);
|
||||
}
|
||||
if (!app) {
|
||||
throw new Error(AppErrEnum.unExist);
|
||||
}
|
||||
|
||||
// auth chat permission
|
||||
if (chat && chat.outLinkUid !== uid) {
|
||||
throw new Error(ChatErrEnum.unAuthChat);
|
||||
}
|
||||
// auth chat permission
|
||||
if (chat && chat.outLinkUid !== uid) {
|
||||
throw new Error(ChatErrEnum.unAuthChat);
|
||||
}
|
||||
|
||||
const [{ histories }, { nodes }] = await Promise.all([
|
||||
getChatItems({
|
||||
appId: app._id,
|
||||
chatId,
|
||||
limit: 30,
|
||||
field: `dataId obj value userGoodFeedback userBadFeedback ${
|
||||
shareChat.responseDetail || app.type === AppTypeEnum.plugin
|
||||
? `adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
: ''
|
||||
} `
|
||||
}),
|
||||
getAppLatestVersion(app._id, app)
|
||||
]);
|
||||
const [{ histories }, { nodes }] = await Promise.all([
|
||||
getChatItems({
|
||||
appId: app._id,
|
||||
chatId,
|
||||
limit: 30,
|
||||
field: `dataId obj value userGoodFeedback userBadFeedback ${
|
||||
shareChat.responseDetail || app.type === AppTypeEnum.plugin
|
||||
? `adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
: ''
|
||||
} `
|
||||
}),
|
||||
getAppLatestVersion(app._id, app)
|
||||
]);
|
||||
|
||||
// pick share response field
|
||||
app.type !== AppTypeEnum.plugin &&
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
||||
}
|
||||
});
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
chatId,
|
||||
appId: app._id,
|
||||
title: chat?.title || i18nT('chat:new_chat'),
|
||||
//@ts-ignore
|
||||
userAvatar: tmb?.userId?.avatar,
|
||||
variables: chat?.variables || {},
|
||||
history: app.type === AppTypeEnum.plugin ? histories : transformPreviewHistories(histories),
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig: app.chatConfig,
|
||||
systemConfigNode: getGuideModule(nodes),
|
||||
storeVariables: chat?.variableList,
|
||||
storeWelcomeText: chat?.welcomeText,
|
||||
isPublicFetch: false
|
||||
}),
|
||||
chatModels: getChatModelNameListByModules(nodes),
|
||||
name: app.name,
|
||||
avatar: app.avatar,
|
||||
intro: app.intro,
|
||||
type: app.type,
|
||||
pluginInputs:
|
||||
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)
|
||||
?.inputs ?? []
|
||||
}
|
||||
// pick share response field
|
||||
app.type !== AppTypeEnum.plugin &&
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
chatId,
|
||||
appId: app._id,
|
||||
title: chat?.title,
|
||||
//@ts-ignore
|
||||
userAvatar: tmb?.userId?.avatar,
|
||||
variables: chat?.variables || {},
|
||||
history: app.type === AppTypeEnum.plugin ? histories : transformPreviewHistories(histories),
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig: app.chatConfig,
|
||||
systemConfigNode: getGuideModule(nodes),
|
||||
storeVariables: chat?.variableList,
|
||||
storeWelcomeText: chat?.welcomeText,
|
||||
isPublicFetch: false
|
||||
}),
|
||||
chatModels: getChatModelNameListByModules(nodes),
|
||||
name: app.name,
|
||||
avatar: app.avatar,
|
||||
intro: app.intro,
|
||||
type: app.type,
|
||||
pluginInputs:
|
||||
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)
|
||||
?.inputs ?? []
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: '10mb'
|
||||
|
@@ -18,92 +18,85 @@ import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { transformPreviewHistories } from '@/global/core/chat/utils';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
let { teamId, appId, chatId, teamToken } = req.query as InitTeamChatProps;
|
||||
|
||||
let { teamId, appId, chatId, teamToken } = req.query as InitTeamChatProps;
|
||||
if (!teamId || !appId || !teamToken) {
|
||||
throw new Error('teamId, appId, teamToken are required');
|
||||
}
|
||||
|
||||
if (!teamId || !appId || !teamToken) {
|
||||
throw new Error('teamId, appId, teamToken are required');
|
||||
}
|
||||
const { uid } = await authTeamSpaceToken({
|
||||
teamId,
|
||||
teamToken
|
||||
});
|
||||
|
||||
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()
|
||||
]);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (!app) {
|
||||
throw new Error(AppErrEnum.unExist);
|
||||
}
|
||||
// auth chat permission
|
||||
if (chat && chat.outLinkUid !== uid) {
|
||||
throw new Error(ChatErrEnum.unAuthChat);
|
||||
}
|
||||
|
||||
// auth chat permission
|
||||
if (chat && chat.outLinkUid !== uid) {
|
||||
throw new Error(ChatErrEnum.unAuthChat);
|
||||
}
|
||||
// get app and history
|
||||
const [{ histories }, { nodes }] = await Promise.all([
|
||||
getChatItems({
|
||||
appId,
|
||||
chatId,
|
||||
limit: 30,
|
||||
field: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
}),
|
||||
getAppLatestVersion(app._id, app)
|
||||
]);
|
||||
|
||||
// get app and history
|
||||
const [{ histories }, { nodes }] = await Promise.all([
|
||||
getChatItems({
|
||||
appId,
|
||||
chatId,
|
||||
limit: 30,
|
||||
field: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
}),
|
||||
getAppLatestVersion(app._id, app)
|
||||
]);
|
||||
|
||||
// pick share response field
|
||||
app.type !== AppTypeEnum.plugin &&
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
||||
}
|
||||
});
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
chatId,
|
||||
appId,
|
||||
title: chat?.title || i18nT('chat:new_chat'),
|
||||
userAvatar: team?.avatar,
|
||||
variables: chat?.variables || {},
|
||||
history: app.type === AppTypeEnum.plugin ? histories : transformPreviewHistories(histories),
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig: app.chatConfig,
|
||||
systemConfigNode: getGuideModule(nodes),
|
||||
storeVariables: chat?.variableList,
|
||||
storeWelcomeText: chat?.welcomeText,
|
||||
isPublicFetch: false
|
||||
}),
|
||||
chatModels: getChatModelNameListByModules(nodes),
|
||||
name: app.name,
|
||||
avatar: app.avatar,
|
||||
intro: app.intro,
|
||||
type: app.type,
|
||||
pluginInputs:
|
||||
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)
|
||||
?.inputs ?? []
|
||||
}
|
||||
// pick share response field
|
||||
app.type !== AppTypeEnum.plugin &&
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
chatId,
|
||||
appId,
|
||||
title: chat?.title,
|
||||
userAvatar: team?.avatar,
|
||||
variables: chat?.variables || {},
|
||||
history: app.type === AppTypeEnum.plugin ? histories : transformPreviewHistories(histories),
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig: app.chatConfig,
|
||||
systemConfigNode: getGuideModule(nodes),
|
||||
storeVariables: chat?.variableList,
|
||||
storeWelcomeText: chat?.welcomeText,
|
||||
isPublicFetch: false
|
||||
}),
|
||||
chatModels: getChatModelNameListByModules(nodes),
|
||||
name: app.name,
|
||||
avatar: app.avatar,
|
||||
intro: app.intro,
|
||||
type: app.type,
|
||||
pluginInputs:
|
||||
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)
|
||||
?.inputs ?? []
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: '10mb'
|
||||
|
@@ -18,7 +18,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
datasetId: string;
|
||||
};
|
||||
|
||||
if (!datasetId || !global.pgClient) {
|
||||
if (!datasetId) {
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
}
|
||||
|
||||
|
@@ -89,7 +89,7 @@ function HelperLinesRenderer({ horizontal, vertical }: HelperLinesProps) {
|
||||
drawCross(node.right * transform[2] + transform[0], y, 5 * zoom);
|
||||
});
|
||||
}
|
||||
}, [width, height, transform, horizontal, vertical]);
|
||||
}, [width, height, transform, horizontal, vertical, zoom]);
|
||||
|
||||
return <canvas ref={canvasRef} style={canvasStyle} />;
|
||||
}
|
||||
|
@@ -3,14 +3,6 @@ import { WorkflowContext } from '../../context';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useCallback } from 'react';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { Node, NodePositionChange, XYPosition } from 'reactflow';
|
||||
import { THelperLine } from '@fastgpt/global/core/workflow/type';
|
||||
|
||||
type GetHelperLinesResult = {
|
||||
horizontal?: THelperLine;
|
||||
vertical?: THelperLine;
|
||||
snapPosition: Partial<XYPosition>;
|
||||
};
|
||||
|
||||
export const useWorkflowUtils = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -40,235 +32,8 @@ export const useWorkflowUtils = () => {
|
||||
[nodeList]
|
||||
);
|
||||
|
||||
const getHelperLines = (
|
||||
change: NodePositionChange,
|
||||
nodes: Node[],
|
||||
distance = 8
|
||||
): GetHelperLinesResult => {
|
||||
const nodeA = nodes.find((node) => node.id === change.id);
|
||||
|
||||
if (!nodeA || !change.position) {
|
||||
return {
|
||||
horizontal: undefined,
|
||||
vertical: undefined,
|
||||
snapPosition: { x: undefined, y: undefined }
|
||||
};
|
||||
}
|
||||
|
||||
const nodeABounds = {
|
||||
left: change.position.x,
|
||||
right: change.position.x + (nodeA.width ?? 0),
|
||||
top: change.position.y,
|
||||
bottom: change.position.y + (nodeA.height ?? 0),
|
||||
width: nodeA.width ?? 0,
|
||||
height: nodeA.height ?? 0,
|
||||
centerX: change.position.x + (nodeA.width ?? 0) / 2,
|
||||
centerY: change.position.y + (nodeA.height ?? 0) / 2
|
||||
};
|
||||
|
||||
let horizontalDistance = distance;
|
||||
let verticalDistance = distance;
|
||||
|
||||
return nodes
|
||||
.filter((node) => node.id !== nodeA.id)
|
||||
.reduce<GetHelperLinesResult>(
|
||||
(result, nodeB) => {
|
||||
if (!result.vertical) {
|
||||
result.vertical = {
|
||||
position: nodeABounds.centerX,
|
||||
nodes: []
|
||||
};
|
||||
}
|
||||
|
||||
if (!result.horizontal) {
|
||||
result.horizontal = {
|
||||
position: nodeABounds.centerY,
|
||||
nodes: []
|
||||
};
|
||||
}
|
||||
|
||||
const nodeBBounds = {
|
||||
left: nodeB.position.x,
|
||||
right: nodeB.position.x + (nodeB.width ?? 0),
|
||||
top: nodeB.position.y,
|
||||
bottom: nodeB.position.y + (nodeB.height ?? 0),
|
||||
width: nodeB.width ?? 0,
|
||||
height: nodeB.height ?? 0,
|
||||
centerX: nodeB.position.x + (nodeB.width ?? 0) / 2,
|
||||
centerY: nodeB.position.y + (nodeB.height ?? 0) / 2
|
||||
};
|
||||
|
||||
const distanceLeftLeft = Math.abs(nodeABounds.left - nodeBBounds.left);
|
||||
const distanceRightRight = Math.abs(nodeABounds.right - nodeBBounds.right);
|
||||
const distanceLeftRight = Math.abs(nodeABounds.left - nodeBBounds.right);
|
||||
const distanceRightLeft = Math.abs(nodeABounds.right - nodeBBounds.left);
|
||||
const distanceTopTop = Math.abs(nodeABounds.top - nodeBBounds.top);
|
||||
const distanceBottomTop = Math.abs(nodeABounds.bottom - nodeBBounds.top);
|
||||
const distanceBottomBottom = Math.abs(nodeABounds.bottom - nodeBBounds.bottom);
|
||||
const distanceTopBottom = Math.abs(nodeABounds.top - nodeBBounds.bottom);
|
||||
const distanceCenterXCenterX = Math.abs(nodeABounds.centerX - nodeBBounds.centerX);
|
||||
const distanceCenterYCenterY = Math.abs(nodeABounds.centerY - nodeBBounds.centerY);
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|
|
||||
// |
|
||||
// |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceLeftLeft < verticalDistance) {
|
||||
result.snapPosition.x = nodeBBounds.left;
|
||||
result.vertical.position = nodeBBounds.left;
|
||||
result.vertical.nodes = [nodeABounds, nodeBBounds];
|
||||
verticalDistance = distanceLeftLeft;
|
||||
} else if (distanceLeftLeft === verticalDistance) {
|
||||
result.vertical.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|
|
||||
// |
|
||||
// |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceRightRight < verticalDistance) {
|
||||
result.snapPosition.x = nodeBBounds.right - nodeABounds.width;
|
||||
result.vertical.position = nodeBBounds.right;
|
||||
result.vertical.nodes = [nodeABounds, nodeBBounds];
|
||||
verticalDistance = distanceRightRight;
|
||||
} else if (distanceRightRight === verticalDistance) {
|
||||
result.vertical.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|
|
||||
// |
|
||||
// |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceLeftRight < verticalDistance) {
|
||||
result.snapPosition.x = nodeBBounds.right;
|
||||
result.vertical.position = nodeBBounds.right;
|
||||
result.vertical.nodes = [nodeABounds, nodeBBounds];
|
||||
verticalDistance = distanceLeftRight;
|
||||
} else if (distanceLeftRight === verticalDistance) {
|
||||
result.vertical.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|
|
||||
// |
|
||||
// |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceRightLeft < verticalDistance) {
|
||||
result.snapPosition.x = nodeBBounds.left - nodeABounds.width;
|
||||
result.vertical.position = nodeBBounds.left;
|
||||
result.vertical.nodes = [nodeABounds, nodeBBounds];
|
||||
verticalDistance = distanceRightLeft;
|
||||
} else if (distanceRightLeft === verticalDistance) {
|
||||
result.vertical.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A | | B |
|
||||
// |___________| |___________|
|
||||
if (distanceTopTop < horizontalDistance) {
|
||||
result.snapPosition.y = nodeBBounds.top;
|
||||
result.horizontal.position = nodeBBounds.top;
|
||||
result.horizontal.nodes = [nodeABounds, nodeBBounds];
|
||||
horizontalDistance = distanceTopTop;
|
||||
} else if (distanceTopTop === horizontalDistance) {
|
||||
result.horizontal.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|_________________
|
||||
// | |
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceBottomTop < horizontalDistance) {
|
||||
result.snapPosition.y = nodeBBounds.top - nodeABounds.height;
|
||||
result.horizontal.position = nodeBBounds.top;
|
||||
result.horizontal.nodes = [nodeABounds, nodeBBounds];
|
||||
horizontalDistance = distanceBottomTop;
|
||||
} else if (distanceBottomTop === horizontalDistance) {
|
||||
result.horizontal.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾| |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A | | B |
|
||||
// |___________|_____|___________|
|
||||
if (distanceBottomBottom < horizontalDistance) {
|
||||
result.snapPosition.y = nodeBBounds.bottom - nodeABounds.height;
|
||||
result.horizontal.position = nodeBBounds.bottom;
|
||||
result.horizontal.nodes = [nodeABounds, nodeBBounds];
|
||||
horizontalDistance = distanceBottomBottom;
|
||||
} else if (distanceBottomBottom === horizontalDistance) {
|
||||
result.horizontal.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// | |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||
// | A |
|
||||
// |___________|
|
||||
if (distanceTopBottom < horizontalDistance) {
|
||||
result.snapPosition.y = nodeBBounds.bottom;
|
||||
result.horizontal.position = nodeBBounds.bottom;
|
||||
result.horizontal.nodes = [nodeABounds, nodeBBounds];
|
||||
horizontalDistance = distanceTopBottom;
|
||||
} else if (distanceTopBottom === horizontalDistance) {
|
||||
result.horizontal.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|
|
||||
// |
|
||||
// |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceCenterXCenterX < verticalDistance) {
|
||||
result.snapPosition.x = nodeBBounds.centerX - nodeABounds.width / 2;
|
||||
result.vertical.position = nodeBBounds.centerX;
|
||||
result.vertical.nodes = [nodeABounds, nodeBBounds];
|
||||
verticalDistance = distanceCenterXCenterX;
|
||||
} else if (distanceCenterXCenterX === verticalDistance) {
|
||||
result.vertical.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾| |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |----| B |
|
||||
// |___________| |___________|
|
||||
if (distanceCenterYCenterY < horizontalDistance) {
|
||||
result.snapPosition.y = nodeBBounds.centerY - nodeABounds.height / 2;
|
||||
result.horizontal.position = nodeBBounds.centerY;
|
||||
result.horizontal.nodes = [nodeABounds, nodeBBounds];
|
||||
horizontalDistance = distanceCenterYCenterY;
|
||||
} else if (distanceCenterYCenterY === horizontalDistance) {
|
||||
result.horizontal.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
{ snapPosition: { x: undefined, y: undefined } } as GetHelperLinesResult
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
computedNewNodeName,
|
||||
getHelperLines
|
||||
computedNewNodeName
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import {
|
||||
Connection,
|
||||
NodeChange,
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
EdgeChange,
|
||||
Edge,
|
||||
applyNodeChanges,
|
||||
Node
|
||||
Node,
|
||||
NodePositionChange,
|
||||
XYPosition
|
||||
} from 'reactflow';
|
||||
import { EDGE_TYPE } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import 'reactflow/dist/style.css';
|
||||
@@ -17,7 +19,242 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useKeyboard } from './useKeyboard';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import { useWorkflowUtils } from './useUtils';
|
||||
import { THelperLine } from '@fastgpt/global/core/workflow/type';
|
||||
|
||||
/*
|
||||
Compute helper lines for snapping nodes to each other
|
||||
Refer: https://reactflow.dev/examples/interaction/helper-lines
|
||||
*/
|
||||
type GetHelperLinesResult = {
|
||||
horizontal?: THelperLine;
|
||||
vertical?: THelperLine;
|
||||
snapPosition: Partial<XYPosition>;
|
||||
};
|
||||
const computeHelperLines = (
|
||||
change: NodePositionChange,
|
||||
nodes: Node[],
|
||||
distance = 8 // distance to snap
|
||||
): GetHelperLinesResult => {
|
||||
const nodeA = nodes.find((node) => node.id === change.id);
|
||||
|
||||
if (!nodeA || !change.position) {
|
||||
return {
|
||||
horizontal: undefined,
|
||||
vertical: undefined,
|
||||
snapPosition: { x: undefined, y: undefined }
|
||||
};
|
||||
}
|
||||
|
||||
const nodeABounds = {
|
||||
left: change.position.x,
|
||||
right: change.position.x + (nodeA.width ?? 0),
|
||||
top: change.position.y,
|
||||
bottom: change.position.y + (nodeA.height ?? 0),
|
||||
width: nodeA.width ?? 0,
|
||||
height: nodeA.height ?? 0,
|
||||
centerX: change.position.x + (nodeA.width ?? 0) / 2,
|
||||
centerY: change.position.y + (nodeA.height ?? 0) / 2
|
||||
};
|
||||
|
||||
let horizontalDistance = distance;
|
||||
let verticalDistance = distance;
|
||||
|
||||
return nodes
|
||||
.filter((node) => node.id !== nodeA.id)
|
||||
.reduce<GetHelperLinesResult>(
|
||||
(result, nodeB) => {
|
||||
if (!result.vertical) {
|
||||
result.vertical = {
|
||||
position: nodeABounds.centerX,
|
||||
nodes: []
|
||||
};
|
||||
}
|
||||
|
||||
if (!result.horizontal) {
|
||||
result.horizontal = {
|
||||
position: nodeABounds.centerY,
|
||||
nodes: []
|
||||
};
|
||||
}
|
||||
|
||||
const nodeBBounds = {
|
||||
left: nodeB.position.x,
|
||||
right: nodeB.position.x + (nodeB.width ?? 0),
|
||||
top: nodeB.position.y,
|
||||
bottom: nodeB.position.y + (nodeB.height ?? 0),
|
||||
width: nodeB.width ?? 0,
|
||||
height: nodeB.height ?? 0,
|
||||
centerX: nodeB.position.x + (nodeB.width ?? 0) / 2,
|
||||
centerY: nodeB.position.y + (nodeB.height ?? 0) / 2
|
||||
};
|
||||
|
||||
const distanceLeftLeft = Math.abs(nodeABounds.left - nodeBBounds.left);
|
||||
const distanceRightRight = Math.abs(nodeABounds.right - nodeBBounds.right);
|
||||
const distanceLeftRight = Math.abs(nodeABounds.left - nodeBBounds.right);
|
||||
const distanceRightLeft = Math.abs(nodeABounds.right - nodeBBounds.left);
|
||||
const distanceTopTop = Math.abs(nodeABounds.top - nodeBBounds.top);
|
||||
const distanceBottomTop = Math.abs(nodeABounds.bottom - nodeBBounds.top);
|
||||
const distanceBottomBottom = Math.abs(nodeABounds.bottom - nodeBBounds.bottom);
|
||||
const distanceTopBottom = Math.abs(nodeABounds.top - nodeBBounds.bottom);
|
||||
const distanceCenterXCenterX = Math.abs(nodeABounds.centerX - nodeBBounds.centerX);
|
||||
const distanceCenterYCenterY = Math.abs(nodeABounds.centerY - nodeBBounds.centerY);
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|
|
||||
// |
|
||||
// |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceLeftLeft < verticalDistance) {
|
||||
result.snapPosition.x = nodeBBounds.left;
|
||||
result.vertical.position = nodeBBounds.left;
|
||||
result.vertical.nodes = [nodeABounds, nodeBBounds];
|
||||
verticalDistance = distanceLeftLeft;
|
||||
} else if (distanceLeftLeft === verticalDistance) {
|
||||
result.vertical.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|
|
||||
// |
|
||||
// |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceRightRight < verticalDistance) {
|
||||
result.snapPosition.x = nodeBBounds.right - nodeABounds.width;
|
||||
result.vertical.position = nodeBBounds.right;
|
||||
result.vertical.nodes = [nodeABounds, nodeBBounds];
|
||||
verticalDistance = distanceRightRight;
|
||||
} else if (distanceRightRight === verticalDistance) {
|
||||
result.vertical.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|
|
||||
// |
|
||||
// |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceLeftRight < verticalDistance) {
|
||||
result.snapPosition.x = nodeBBounds.right;
|
||||
result.vertical.position = nodeBBounds.right;
|
||||
result.vertical.nodes = [nodeABounds, nodeBBounds];
|
||||
verticalDistance = distanceLeftRight;
|
||||
} else if (distanceLeftRight === verticalDistance) {
|
||||
result.vertical.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|
|
||||
// |
|
||||
// |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceRightLeft < verticalDistance) {
|
||||
result.snapPosition.x = nodeBBounds.left - nodeABounds.width;
|
||||
result.vertical.position = nodeBBounds.left;
|
||||
result.vertical.nodes = [nodeABounds, nodeBBounds];
|
||||
verticalDistance = distanceRightLeft;
|
||||
} else if (distanceRightLeft === verticalDistance) {
|
||||
result.vertical.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A | | B |
|
||||
// |___________| |___________|
|
||||
if (distanceTopTop < horizontalDistance) {
|
||||
result.snapPosition.y = nodeBBounds.top;
|
||||
result.horizontal.position = nodeBBounds.top;
|
||||
result.horizontal.nodes = [nodeABounds, nodeBBounds];
|
||||
horizontalDistance = distanceTopTop;
|
||||
} else if (distanceTopTop === horizontalDistance) {
|
||||
result.horizontal.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|_________________
|
||||
// | |
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceBottomTop < horizontalDistance) {
|
||||
result.snapPosition.y = nodeBBounds.top - nodeABounds.height;
|
||||
result.horizontal.position = nodeBBounds.top;
|
||||
result.horizontal.nodes = [nodeABounds, nodeBBounds];
|
||||
horizontalDistance = distanceBottomTop;
|
||||
} else if (distanceBottomTop === horizontalDistance) {
|
||||
result.horizontal.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾| |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A | | B |
|
||||
// |___________|_____|___________|
|
||||
if (distanceBottomBottom < horizontalDistance) {
|
||||
result.snapPosition.y = nodeBBounds.bottom - nodeABounds.height;
|
||||
result.horizontal.position = nodeBBounds.bottom;
|
||||
result.horizontal.nodes = [nodeABounds, nodeBBounds];
|
||||
horizontalDistance = distanceBottomBottom;
|
||||
} else if (distanceBottomBottom === horizontalDistance) {
|
||||
result.horizontal.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// | |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||
// | A |
|
||||
// |___________|
|
||||
if (distanceTopBottom < horizontalDistance) {
|
||||
result.snapPosition.y = nodeBBounds.bottom;
|
||||
result.horizontal.position = nodeBBounds.bottom;
|
||||
result.horizontal.nodes = [nodeABounds, nodeBBounds];
|
||||
horizontalDistance = distanceTopBottom;
|
||||
} else if (distanceTopBottom === horizontalDistance) {
|
||||
result.horizontal.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |
|
||||
// |___________|
|
||||
// |
|
||||
// |
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | B |
|
||||
// |___________|
|
||||
if (distanceCenterXCenterX < verticalDistance) {
|
||||
result.snapPosition.x = nodeBBounds.centerX - nodeABounds.width / 2;
|
||||
result.vertical.position = nodeBBounds.centerX;
|
||||
result.vertical.nodes = [nodeABounds, nodeBBounds];
|
||||
verticalDistance = distanceCenterXCenterX;
|
||||
} else if (distanceCenterXCenterX === verticalDistance) {
|
||||
result.vertical.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
// |‾‾‾‾‾‾‾‾‾‾‾| |‾‾‾‾‾‾‾‾‾‾‾|
|
||||
// | A |----| B |
|
||||
// |___________| |___________|
|
||||
if (distanceCenterYCenterY < horizontalDistance) {
|
||||
result.snapPosition.y = nodeBBounds.centerY - nodeABounds.height / 2;
|
||||
result.horizontal.position = nodeBBounds.centerY;
|
||||
result.horizontal.nodes = [nodeABounds, nodeBBounds];
|
||||
horizontalDistance = distanceCenterYCenterY;
|
||||
} else if (distanceCenterYCenterY === horizontalDistance) {
|
||||
result.horizontal.nodes.push(nodeBBounds);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
{ snapPosition: { x: undefined, y: undefined } } as GetHelperLinesResult
|
||||
);
|
||||
};
|
||||
|
||||
export const useWorkflow = () => {
|
||||
const { toast } = useToast();
|
||||
@@ -35,65 +272,81 @@ export const useWorkflow = () => {
|
||||
onNodesChange,
|
||||
setEdges,
|
||||
onEdgesChange,
|
||||
setHoverEdgeId,
|
||||
setHelperLineHorizontal,
|
||||
setHelperLineVertical
|
||||
setHoverEdgeId
|
||||
} = useContextSelector(WorkflowContext, (v) => v);
|
||||
|
||||
const { getHelperLines } = useWorkflowUtils();
|
||||
/* helper line */
|
||||
const [helperLineHorizontal, setHelperLineHorizontal] = useState<THelperLine>();
|
||||
const [helperLineVertical, setHelperLineVertical] = useState<THelperLine>();
|
||||
|
||||
const customApplyNodeChanges = useCallback((changes: NodeChange[], nodes: Node[]): Node[] => {
|
||||
setHelperLineHorizontal(undefined);
|
||||
setHelperLineVertical(undefined);
|
||||
const customApplyNodeChanges = (changes: NodeChange[], nodes: Node[]): Node[] => {
|
||||
const positionChange =
|
||||
changes[0].type === 'position' && changes[0].dragging ? changes[0] : undefined;
|
||||
|
||||
if (
|
||||
changes.length === 1 &&
|
||||
changes[0].type === 'position' &&
|
||||
changes[0].dragging &&
|
||||
changes[0].position
|
||||
) {
|
||||
const helperLines = getHelperLines(changes[0], nodes);
|
||||
if (changes.length === 1 && positionChange?.position) {
|
||||
// 只判断,3000px 内的 nodes,并按从近到远的顺序排序
|
||||
const filterNodes = nodes
|
||||
.filter((node) => {
|
||||
if (!positionChange.position) return false;
|
||||
|
||||
changes[0].position.x = helperLines.snapPosition.x ?? changes[0].position.x;
|
||||
changes[0].position.y = helperLines.snapPosition.y ?? changes[0].position.y;
|
||||
return (
|
||||
Math.abs(node.position.x - positionChange.position.x) <= 3000 &&
|
||||
Math.abs(node.position.y - positionChange.position.y) <= 3000
|
||||
);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (!positionChange.position) return 0;
|
||||
return (
|
||||
Math.abs(a.position.x - positionChange.position.x) +
|
||||
Math.abs(a.position.y - positionChange.position.y) -
|
||||
Math.abs(b.position.x - positionChange.position.x) -
|
||||
Math.abs(b.position.y - positionChange.position.y)
|
||||
);
|
||||
})
|
||||
.slice(0, 15);
|
||||
|
||||
const helperLines = computeHelperLines(positionChange, filterNodes);
|
||||
|
||||
positionChange.position.x = helperLines.snapPosition.x ?? positionChange.position.x;
|
||||
positionChange.position.y = helperLines.snapPosition.y ?? positionChange.position.y;
|
||||
|
||||
setHelperLineHorizontal(helperLines.horizontal);
|
||||
setHelperLineVertical(helperLines.vertical);
|
||||
} else {
|
||||
setHelperLineHorizontal(undefined);
|
||||
setHelperLineVertical(undefined);
|
||||
}
|
||||
|
||||
return applyNodeChanges(changes, nodes);
|
||||
}, []);
|
||||
};
|
||||
|
||||
/* node */
|
||||
const handleNodesChange = useCallback(
|
||||
(changes: NodeChange[]) => {
|
||||
setNodes((nodes) => customApplyNodeChanges(changes, nodes));
|
||||
const handleNodesChange = (changes: NodeChange[]) => {
|
||||
setNodes((nodes) => customApplyNodeChanges(changes, nodes));
|
||||
|
||||
for (const change of changes) {
|
||||
if (change.type === 'remove') {
|
||||
const node = nodes.find((n) => n.id === change.id);
|
||||
if (node && node.data.forbidDelete) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:core.workflow.Can not delete node')
|
||||
});
|
||||
} else {
|
||||
return onOpenConfirmDeleteNode(() => {
|
||||
onNodesChange(changes);
|
||||
setEdges((state) =>
|
||||
state.filter((edge) => edge.source !== change.id && edge.target !== change.id)
|
||||
);
|
||||
})();
|
||||
}
|
||||
} else if (change.type === 'select' && change.selected === false && isDowningCtrl) {
|
||||
change.selected = true;
|
||||
for (const change of changes) {
|
||||
if (change.type === 'remove') {
|
||||
const node = nodes.find((n) => n.id === change.id);
|
||||
if (node && node.data.forbidDelete) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:core.workflow.Can not delete node')
|
||||
});
|
||||
} else {
|
||||
return onOpenConfirmDeleteNode(() => {
|
||||
onNodesChange(changes);
|
||||
setEdges((state) =>
|
||||
state.filter((edge) => edge.source !== change.id && edge.target !== change.id)
|
||||
);
|
||||
})();
|
||||
}
|
||||
} else if (change.type === 'select' && change.selected === false && isDowningCtrl) {
|
||||
change.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
onNodesChange(changes);
|
||||
},
|
||||
[isDowningCtrl, nodes, onNodesChange, onOpenConfirmDeleteNode, setEdges, t, toast]
|
||||
);
|
||||
onNodesChange(changes);
|
||||
};
|
||||
const handleEdgeChange = useCallback(
|
||||
(changes: EdgeChange[]) => {
|
||||
onEdgesChange(changes.filter((change) => change.type !== 'remove'));
|
||||
@@ -163,7 +416,11 @@ export const useWorkflow = () => {
|
||||
onConnect,
|
||||
customOnConnect,
|
||||
onEdgeMouseEnter,
|
||||
onEdgeMouseLeave
|
||||
onEdgeMouseLeave,
|
||||
helperLineHorizontal,
|
||||
setHelperLineHorizontal,
|
||||
helperLineVertical,
|
||||
setHelperLineVertical
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -64,8 +64,7 @@ const edgeTypes = {
|
||||
};
|
||||
|
||||
const Workflow = () => {
|
||||
const { nodes, edges, reactFlowWrapper, helperLineHorizontal, helperLineVertical } =
|
||||
useContextSelector(WorkflowContext, (v) => v);
|
||||
const { nodes, edges, reactFlowWrapper } = useContextSelector(WorkflowContext, (v) => v);
|
||||
|
||||
const {
|
||||
ConfirmDeleteModal,
|
||||
@@ -75,7 +74,9 @@ const Workflow = () => {
|
||||
onConnectEnd,
|
||||
customOnConnect,
|
||||
onEdgeMouseEnter,
|
||||
onEdgeMouseLeave
|
||||
onEdgeMouseLeave,
|
||||
helperLineHorizontal,
|
||||
helperLineVertical
|
||||
} = useWorkflow();
|
||||
|
||||
const {
|
||||
@@ -85,7 +86,7 @@ const Workflow = () => {
|
||||
} = useDisclosure();
|
||||
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<>
|
||||
<Box
|
||||
flex={'1 0 0'}
|
||||
h={0}
|
||||
@@ -143,11 +144,19 @@ const Workflow = () => {
|
||||
</Box>
|
||||
|
||||
<ConfirmDeleteModal />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Render = () => {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<Workflow />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Workflow);
|
||||
export default React.memo(Render);
|
||||
|
||||
const FlowController = React.memo(function FlowController() {
|
||||
const { fitView } = useReactFlow();
|
||||
|
@@ -48,7 +48,6 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { formatTime2HM, formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
|
||||
import type { InitProps } from '@/pages/app/detail/components/PublishHistoriesSlider';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { THelperLine } from '@fastgpt/global/core/workflow/type';
|
||||
|
||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||
|
||||
@@ -136,12 +135,6 @@ type WorkflowContextType = {
|
||||
historiesDefaultData?: InitProps;
|
||||
setHistoriesDefaultData: React.Dispatch<React.SetStateAction<undefined | InitProps>>;
|
||||
|
||||
// helper line
|
||||
helperLineHorizontal?: THelperLine;
|
||||
setHelperLineHorizontal: React.Dispatch<React.SetStateAction<THelperLine | undefined>>;
|
||||
helperLineVertical?: THelperLine;
|
||||
setHelperLineVertical: React.Dispatch<React.SetStateAction<THelperLine | undefined>>;
|
||||
|
||||
// chat test
|
||||
setWorkflowTestData: React.Dispatch<
|
||||
React.SetStateAction<
|
||||
@@ -267,14 +260,6 @@ export const WorkflowContext = createContext<WorkflowContextType>({
|
||||
setHistoriesDefaultData: function (value: React.SetStateAction<InitProps | undefined>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
helperLineHorizontal: undefined,
|
||||
setHelperLineHorizontal: function (value: React.SetStateAction<THelperLine | undefined>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
helperLineVertical: undefined,
|
||||
setHelperLineVertical: function (value: React.SetStateAction<THelperLine | undefined>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
getNodeDynamicInputs: function (nodeId: string): FlowNodeInputItemType[] {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
@@ -742,11 +727,6 @@ const WorkflowContextProvider = ({
|
||||
/* Version histories */
|
||||
const [historiesDefaultData, setHistoriesDefaultData] = useState<InitProps>();
|
||||
|
||||
/* helper line */
|
||||
const [helperLineHorizontal, setHelperLineHorizontal] = useState<THelperLine | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [helperLineVertical, setHelperLineVertical] = useState<THelperLine | undefined>(undefined);
|
||||
/* event bus */
|
||||
useEffect(() => {
|
||||
eventBus.on(EventNameEnum.requestWorkflowStore, () => {
|
||||
@@ -816,12 +796,6 @@ const WorkflowContextProvider = ({
|
||||
historiesDefaultData,
|
||||
setHistoriesDefaultData,
|
||||
|
||||
// helper line
|
||||
helperLineHorizontal,
|
||||
setHelperLineHorizontal,
|
||||
helperLineVertical,
|
||||
setHelperLineVertical,
|
||||
|
||||
// chat test
|
||||
setWorkflowTestData
|
||||
};
|
||||
|
@@ -36,6 +36,7 @@ const ChatHeader = ({
|
||||
apps?: AppListItemType[];
|
||||
onRouteToAppDetail?: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const isPlugin = chatData.app.type === AppTypeEnum.plugin;
|
||||
const { isPc } = useSystem();
|
||||
|
||||
@@ -50,7 +51,11 @@ const ChatHeader = ({
|
||||
>
|
||||
{isPc ? (
|
||||
<>
|
||||
<PcHeader title={chatData.title} chatModels={chatData.app.chatModels} history={history} />
|
||||
<PcHeader
|
||||
title={chatData.title || t('chat:new_chat')}
|
||||
chatModels={chatData.app.chatModels}
|
||||
history={history}
|
||||
/>
|
||||
<Box flex={1} />
|
||||
</>
|
||||
) : (
|
||||
|
@@ -70,7 +70,7 @@ export async function authChatCrud({
|
||||
|
||||
if (!chat) return { id: outLinkUid };
|
||||
|
||||
// auth req
|
||||
// auth req
|
||||
const { teamId, tmbId, permission } = await authUserPer({
|
||||
...props,
|
||||
per: ReadPermissionVal
|
||||
@@ -81,7 +81,7 @@ export async function authChatCrud({
|
||||
if (permission.isOwner) return { uid: outLinkUid };
|
||||
if (String(tmbId) === String(chat.tmbId)) return { uid: outLinkUid };
|
||||
|
||||
// admin
|
||||
// Admin can manage all chat
|
||||
if (per === WritePermissionVal && permission.hasManagePer) return { uid: outLinkUid };
|
||||
|
||||
return Promise.reject(ChatErrEnum.unAuthChat);
|
||||
|
Reference in New Issue
Block a user