* fix if-else find variables (#92)

* fix if-else find variables

* change workflow output type

* fix tooltip style

* fix

* 4.8 (#93)

* api middleware

* perf: app version histories

* faq

* perf: value type show

* fix: ts

* fix: Run the same node multiple times

* feat: auto save workflow

* perf: auto save workflow

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-04-27 12:21:01 +08:00
committed by GitHub
parent c8412e7dc9
commit d407e87dd9
87 changed files with 1607 additions and 1779 deletions

View File

@@ -4,6 +4,7 @@ import cronParser from 'cron-parser';
export const formatTime2YMDHM = (time?: Date) =>
time ? dayjs(time).format('YYYY-MM-DD HH:mm') : '';
export const formatTime2YMD = (time?: Date) => (time ? dayjs(time).format('YYYY-MM-DD') : '');
export const formatTime2HM = (time: Date = new Date()) => dayjs(time).format('HH:mm');
/* cron time parse */
export const cronParser2Fields = (cronString: string) => {

View File

@@ -1,22 +0,0 @@
import type { LLMModelItemType } from '../ai/model.d';
import { AppTypeEnum } from './constants';
import { AppSchema } from './type';
export type CreateAppParams = {
name?: string;
avatar?: string;
type?: `${AppTypeEnum}`;
modules: AppSchema['modules'];
edges?: AppSchema['edges'];
};
export interface AppUpdateParams {
name?: string;
type?: `${AppTypeEnum}`;
avatar?: string;
intro?: string;
modules?: AppSchema['modules'];
edges?: AppSchema['edges'];
permission?: AppSchema['permission'];
teamTags?: AppSchema['teamTags'];
}

View File

@@ -6,7 +6,7 @@ import { VariableInputEnum } from '../workflow/constants';
import { SelectedDatasetType } from '../workflow/api';
import { DatasetSearchModeEnum } from '../dataset/constants';
import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d';
import { StoreEdgeItemType } from 'core/workflow/type/edge';
import { StoreEdgeItemType } from '../workflow/type/edge';
export interface AppSchema {
_id: string;
@@ -18,6 +18,7 @@ export interface AppSchema {
avatar: string;
intro: string;
updateTime: number;
modules: StoreNodeItemType[];
edges: StoreEdgeItemType[];

9
packages/global/core/app/version.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import { StoreNodeItemType } from '../workflow/type';
import { StoreEdgeItemType } from '../workflow/type/edge';
export type AppVersionSchemaType = {
appId: string;
time: Date;
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
};

View File

@@ -14,18 +14,21 @@ export enum WorkflowIOValueTypeEnum {
string = 'string',
number = 'number',
boolean = 'boolean',
object = 'object',
arrayString = 'arrayString',
arrayNumber = 'arrayNumber',
arrayBoolean = 'arrayBoolean',
arrayObject = 'arrayObject',
any = 'any',
chatHistory = 'chatHistory',
datasetQuote = 'datasetQuote',
dynamic = 'dynamic',
// plugin special type
selectApp = 'selectApp',
selectDataset = 'selectDataset',
// tool
tools = 'tools'
selectDataset = 'selectDataset'
}
/* reg: modulename key */
@@ -173,3 +176,5 @@ export enum RuntimeEdgeStatusEnum {
'active' = 'active',
'skipped' = 'skipped'
}
export const VARIABLE_NODE_ID = 'VARIABLE_NODE_ID';

View File

@@ -4,7 +4,7 @@ import { FlowNodeTypeEnum } from '../node/constant';
import { StoreNodeItemType } from '../type';
import { StoreEdgeItemType } from '../type/edge';
import { RuntimeEdgeItemType, RuntimeNodeItemType } from './type';
import { VARIABLE_NODE_ID } from '../../../../../projects/app/src/web/core/workflow/constants/index';
import { VARIABLE_NODE_ID } from '../constants';
export const initWorkflowEdgeStatus = (edges: StoreEdgeItemType[]): RuntimeEdgeItemType[] => {
return (

View File

@@ -10,16 +10,26 @@ import {
NodeOutputKeyEnum,
FlowNodeTemplateTypeEnum
} from '../../constants';
import { Input_Template_Dataset_Quote } from '../input';
import { getNanoid } from '../../../../common/string/tools';
import { getHandleConfig } from '../utils';
import { FlowNodeInputItemType } from '../../type/io.d';
export const getOneQuoteInputTemplate = (key = getNanoid()): FlowNodeInputItemType => ({
...Input_Template_Dataset_Quote,
const defaultQuoteKey = 'defaultQuoteKey';
export const getOneQuoteInputTemplate = ({
key = getNanoid(),
index
}: {
key?: string;
index: number;
}): FlowNodeInputItemType => ({
key,
renderTypeList: [FlowNodeInputTypeEnum.custom],
description: ''
renderTypeList: [FlowNodeInputTypeEnum.reference],
label: `引用${index}`,
debugLabel: '知识库引用',
canEdit: key !== defaultQuoteKey,
description: '',
valueType: WorkflowIOValueTypeEnum.datasetQuote
});
export const DatasetConcatModule: FlowNodeTemplateType = {
@@ -37,7 +47,7 @@ export const DatasetConcatModule: FlowNodeTemplateType = {
key: NodeInputKeyEnum.datasetMaxTokens,
renderTypeList: [FlowNodeInputTypeEnum.custom],
label: '最大 Tokens',
value: 1500,
value: 3000,
valueType: WorkflowIOValueTypeEnum.number
},
{
@@ -45,7 +55,7 @@ export const DatasetConcatModule: FlowNodeTemplateType = {
renderTypeList: [FlowNodeInputTypeEnum.custom],
label: ''
},
getOneQuoteInputTemplate()
getOneQuoteInputTemplate({ key: defaultQuoteKey, index: 1 })
],
outputs: [
{

View File

@@ -1,19 +1,12 @@
import type { NextApiResponse, NextApiHandler, NextApiRequest } from 'next';
import type { NextApiResponse, NextApiRequest } from 'next';
import NextCors from 'nextjs-cors';
export function withNextCors(handler: NextApiHandler): NextApiHandler {
return async function nextApiHandlerWrappedWithNextCors(
req: NextApiRequest,
res: NextApiResponse
) {
const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
const origin = req.headers.origin;
await NextCors(req, res, {
methods,
origin: origin,
optionsSuccessStatus: 200
});
return handler(req, res);
};
export async function withNextCors(req: NextApiRequest, res: NextApiResponse) {
const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
const origin = req.headers.origin;
await NextCors(req, res, {
methods,
origin: origin,
optionsSuccessStatus: 200
});
}

View File

@@ -18,9 +18,10 @@ export const jsonRes = <T = any>(
message?: string;
data?: T;
error?: any;
url?: string;
}
) => {
const { code = 200, message = '', data = null, error } = props || {};
const { code = 200, message = '', data = null, error, url } = props || {};
const errResponseKey = typeof error === 'string' ? error : error?.message;
// Specified error
@@ -47,7 +48,7 @@ export const jsonRes = <T = any>(
msg = error?.error?.message;
}
addLog.error(`response error: ${msg}`, error);
addLog.error(`Api response error: ${url}, ${msg}`, error);
}
res.status(code).json({

View File

@@ -169,7 +169,16 @@ class PgClass {
}
async query<T extends QueryResultRow = any>(sql: string) {
const pg = await connectPg();
return pg.query<T>(sql);
const start = Date.now();
return pg.query<T>(sql).then((res) => {
const time = Date.now() - start;
if (time > 300) {
addLog.warn(`pg query time: ${time}ms, sql: ${sql}`);
}
return res;
});
}
}

View File

@@ -0,0 +1,65 @@
import { AppSchema } from '@fastgpt/global/core/app/type';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getLLMModel } from '../ai/model';
import { MongoAppVersion } from './versionSchema';
export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined>({
nodes
}: {
nodes: T;
}) => {
if (nodes) {
let maxTokens = 3000;
nodes.forEach((item) => {
if (
item.flowNodeType === FlowNodeTypeEnum.chatNode ||
item.flowNodeType === FlowNodeTypeEnum.tools
) {
const model =
item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || '';
const chatModel = getLLMModel(model);
const quoteMaxToken = chatModel.quoteMaxToken || 3000;
maxTokens = Math.max(maxTokens, quoteMaxToken);
}
});
nodes.forEach((item) => {
if (item.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) {
item.inputs.forEach((input) => {
if (input.key === NodeInputKeyEnum.datasetMaxTokens) {
const val = input.value as number;
if (val > maxTokens) {
input.value = maxTokens;
}
}
});
}
});
}
return {
nodes
};
};
export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
const version = await MongoAppVersion.findOne({
appId
}).sort({
time: -1
});
if (version) {
return {
nodes: version.nodes,
edges: version.edges
};
}
return {
nodes: app?.modules || [],
edges: app?.edges || []
};
};

View File

@@ -8,7 +8,7 @@ import {
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
export const appCollectionName = 'apps';
export const AppCollectionName = 'apps';
const AppSchema = new Schema({
teamId: {
@@ -46,6 +46,8 @@ const AppSchema = new Schema({
type: Date,
default: () => new Date()
},
// tmp store
modules: {
type: Array,
default: []
@@ -92,6 +94,6 @@ try {
}
export const MongoApp: Model<AppType> =
models[appCollectionName] || model(appCollectionName, AppSchema);
models[AppCollectionName] || model(AppCollectionName, AppSchema);
MongoApp.syncIndexes();

View File

@@ -0,0 +1,36 @@
import { connectionMongo, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
export const AppVersionCollectionName = 'app.versions';
const AppVersionSchema = new Schema({
appId: {
type: Schema.Types.ObjectId,
ref: AppVersionCollectionName,
required: true
},
time: {
type: Date,
default: () => new Date()
},
nodes: {
type: Array,
default: []
},
edges: {
type: Array,
default: []
}
});
try {
AppVersionSchema.index({ appId: 1, time: -1 });
} catch (error) {
console.log(error);
}
export const MongoAppVersion: Model<AppVersionSchemaType> =
models[AppVersionCollectionName] || model(AppVersionCollectionName, AppVersionSchema);
MongoAppVersion.syncIndexes();

View File

@@ -7,7 +7,7 @@ import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { appCollectionName } from '../app/schema';
import { AppCollectionName } from '../app/schema';
import { userCollectionName } from '../../support/user/schema';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
@@ -40,7 +40,7 @@ const ChatItemSchema = new Schema({
},
appId: {
type: Schema.Types.ObjectId,
ref: appCollectionName,
ref: AppCollectionName,
required: true
},
time: {

View File

@@ -6,7 +6,7 @@ import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { appCollectionName } from '../app/schema';
import { AppCollectionName } from '../app/schema';
export const chatCollectionName = 'chat';
@@ -31,7 +31,7 @@ const ChatSchema = new Schema({
},
appId: {
type: Schema.Types.ObjectId,
ref: appCollectionName,
ref: AppCollectionName,
required: true
},
updateTime: {

View File

@@ -220,9 +220,16 @@ export async function dispatchWorkFlow({
).then((result) => {
const flat = result.flat();
if (flat.length === 0) return;
// update output
// Update the node output at the end of the run and get the next nodes
const nextNodes = flat.map((item) => nodeOutput(item.node, item.result)).flat();
return checkNodeCanRun(nextNodes);
// Remove repeat nodes(Make sure that the node is only executed once)
const filterNextNodes = nextNodes.filter(
(node, index, self) => self.findIndex((t) => t.nodeId === node.nodeId) === index
);
return checkNodeCanRun(filterNextNodes);
});
}
// 运行完一轮后,清除连线的状态,避免污染进程

View File

@@ -8,6 +8,7 @@ import {
} from '@fastgpt/global/core/workflow/template/system/ifElse/type';
import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type';
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime/utils';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.condition]: IfElseConditionType;
@@ -20,20 +21,21 @@ function checkCondition(condition: VariableConditionEnum, variableValue: any, va
[VariableConditionEnum.isNotEmpty]: () => !!variableValue,
[VariableConditionEnum.equalTo]: () => variableValue === value,
[VariableConditionEnum.notEqual]: () => variableValue !== value,
[VariableConditionEnum.greaterThan]: () => variableValue > Number(value),
[VariableConditionEnum.lessThan]: () => variableValue < Number(value),
[VariableConditionEnum.greaterThanOrEqualTo]: () => variableValue >= Number(value),
[VariableConditionEnum.lessThanOrEqualTo]: () => variableValue <= Number(value),
[VariableConditionEnum.include]: () => variableValue.includes(value),
[VariableConditionEnum.notInclude]: () => !variableValue.includes(value),
[VariableConditionEnum.startWith]: () => variableValue.startsWith(value),
[VariableConditionEnum.endWith]: () => variableValue.endsWith(value),
[VariableConditionEnum.lengthEqualTo]: () => variableValue.length === Number(value),
[VariableConditionEnum.lengthNotEqualTo]: () => variableValue.length !== Number(value),
[VariableConditionEnum.lengthGreaterThan]: () => variableValue.length > Number(value),
[VariableConditionEnum.lengthGreaterThanOrEqualTo]: () => variableValue.length >= Number(value),
[VariableConditionEnum.lengthLessThan]: () => variableValue.length < Number(value),
[VariableConditionEnum.lengthLessThanOrEqualTo]: () => variableValue.length <= Number(value)
[VariableConditionEnum.greaterThan]: () => Number(variableValue) > Number(value),
[VariableConditionEnum.lessThan]: () => Number(variableValue) < Number(value),
[VariableConditionEnum.greaterThanOrEqualTo]: () => Number(variableValue) >= Number(value),
[VariableConditionEnum.lessThanOrEqualTo]: () => Number(variableValue) <= Number(value),
[VariableConditionEnum.include]: () => variableValue?.includes(value),
[VariableConditionEnum.notInclude]: () => !variableValue?.includes(value),
[VariableConditionEnum.startWith]: () => variableValue?.startsWith(value),
[VariableConditionEnum.endWith]: () => variableValue?.endsWith(value),
[VariableConditionEnum.lengthEqualTo]: () => variableValue?.length === Number(value),
[VariableConditionEnum.lengthNotEqualTo]: () => variableValue?.length !== Number(value),
[VariableConditionEnum.lengthGreaterThan]: () => variableValue?.length > Number(value),
[VariableConditionEnum.lengthGreaterThanOrEqualTo]: () =>
variableValue?.length >= Number(value),
[VariableConditionEnum.lengthLessThan]: () => variableValue?.length < Number(value),
[VariableConditionEnum.lengthLessThanOrEqualTo]: () => variableValue?.length <= Number(value)
};
return (operations[condition] || (() => false))();
@@ -43,15 +45,18 @@ export const dispatchIfElse = async (props: Props): Promise<DispatchNodeResultTy
const {
params,
runtimeNodes,
variables,
node: { nodeId }
} = props;
const { condition, ifElseList } = params;
const listResult = ifElseList.map((item) => {
const { variable, condition: variableCondition, value } = item;
const variableValue = runtimeNodes
.find((node) => node.nodeId === variable[0])
?.outputs?.find((item) => item.id === variable[1])?.value;
const variableValue = getReferenceVariableValue({
value: variable,
variables,
nodes: runtimeNodes
});
return checkCondition(variableCondition as VariableConditionEnum, variableValue, value || '');
});

View File

@@ -6,7 +6,7 @@ import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { appCollectionName } from '../../core/app/schema';
import { AppCollectionName } from '../../core/app/schema';
const OutLinkSchema = new Schema({
shareId: {
@@ -25,7 +25,7 @@ const OutLinkSchema = new Schema({
},
appId: {
type: Schema.Types.ObjectId,
ref: appCollectionName,
ref: AppCollectionName,
required: true
},
type: {

View File

@@ -30,12 +30,10 @@ export async function authApp({
// get app
const app = await MongoApp.findOne({ _id: appId, teamId }).lean();
if (!app) {
return Promise.reject(AppErrEnum.unAuthApp);
return Promise.reject(AppErrEnum.unExist);
}
const isOwner =
role !== TeamMemberRoleEnum.visitor &&
(String(app.tmbId) === tmbId || role === TeamMemberRoleEnum.owner);
const isOwner = String(app.tmbId) === tmbId;
const canWrite =
isOwner ||
(app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor);

View File

@@ -40,6 +40,7 @@ export const iconPaths = {
'common/paramsLight': () => import('./icons/common/paramsLight.svg'),
'common/playFill': () => import('./icons/common/playFill.svg'),
'common/playLight': () => import('./icons/common/playLight.svg'),
'common/publishFill': () => import('./icons/common/publishFill.svg'),
'common/questionLight': () => import('./icons/common/questionLight.svg'),
'common/refreshLight': () => import('./icons/common/refreshLight.svg'),
'common/resultLight': () => import('./icons/common/resultLight.svg'),

View File

@@ -0,0 +1,6 @@
<svg t="1714186779322" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1213"
width="128" height="128">
<path
d="M957.78379975 134.65795581c-2.56331135 83.17185735-20.44423515 163.12467052-50.30317593 240.57510884-35.69958418 92.67796903-87.96862781 174.24030494-161.20251807 241.63355181-5.94396957 5.46839892-7.80783541 11.32361235-7.01830818 19.026796 2.5328431 24.92705029 4.5596486 49.91503711 7.12295994 74.84208611 5.9598665 58.12425835-14.32010468 105.71575589-61.01609761 139.47861502-50.06340414 36.20694738-102.1695094 69.6412786-153.84243498 103.55515469-32.57194606 21.38212813-72.60730269 1.98176462-76.2277327-36.80306673-4.17150847-44.53539301-7.45016355-89.16219079-10.5340864-133.78633995-1.53533793-22.33459502-0.93921859-22.52932723-22.48428636-25.38937392-57.18636407-7.56938794-101.94563325-34.8213021-132.68418171-84.27401429-15.81040175-25.46488176-23.6473811-53.61097493-26.40410034-83.18642866-0.81866989-8.86495278-4.3808133-11.13020574-12.70925895-11.59252939-46.28003447-2.57788397-92.52960201-5.3332789-138.74869996-8.8199132-38.44173211-2.92098321-57.93085045-43.61339568-37.32367695-77.56966271 14.4671474-23.86993165 29.87483771-47.17288918 44.81888136-70.76065808 16.1071371-25.38937393 32.07915418-50.85425569 48.21675953-76.21316007 29.53173846-46.41383019 72.1012638-71.48924752 126.5905208-71.93699984 31.2008721-0.23844748 62.46003078 5.27366671 93.64633157 8.64107663 5.89892998 0.64115892 10.0863341-0.16426396 14.16908774-4.73848387 72.39799786-81.24970362 162.00661664-136.08206117 263.446211-172.76457792 57.33473241-20.74096921 116.33859714-34.28479495 176.89237242-40.54271983 30.51467231-3.17400331 61.23864946-4.32120112 91.72285352 1.34060572 16.61450031 3.08392285 17.46363973 3.77012135 20.02562678 20.05609502C956.4869093 101.71509088 958.51238919 118.07524806 957.78379975 134.65795581L957.78379975 134.65795581zM813.20902898 339.81696881c0.87828208-71.05871775-57.73744384-132.1635715-127.410515-133.309445-72.35428259-1.20680999-135.02361821 51.61331338-136.73779142 134.08439962-1.44525748 68.77756914 60.61338619 131.07466028 129.88374722 131.55155523C752.69897026 472.67866149 812.29895435 413.94371248 813.20902898 339.81696881L813.20902898 339.81696881zM195.18722288 640.09538918c13.69219278 0.17883529 21.29204896 6.24335355 26.19479682 20.29321819 19.25067215 55.04033549 54.28127779 96.80443794 105.06002435 125.11479508 11.20438926 6.24335355 23.0207935 11.30904103 35.22401354 15.48187512 21.93320788 7.50845143 26.93928317 28.96476307 10.66788212 45.47328728-24.10705481 24.43558273-48.46977833 48.60357404-72.56226181 73.02590976-7.97209938 8.07542683-16.74829602 11.90383476-27.95268527 7.71775497-11.38322454-4.2470176-16.88209172-12.72383026-17.46363974-24.55480712-0.35767186-7.49520313-0.17883529-15.03544713-0.35767188-22.55846986-0.31263099-13.81274148-0.44775102-13.91739325-13.11197036-9.71541653-27.08765021 9.01464541-54.11701383 18.10347435-81.20466405 27.10354714-7.9866707 2.66796441-16.00248532 3.77012135-23.81032074-0.67030285-11.44416105-6.51226928-15.76403785-18.14851392-11.160674-32.46729429 8.99874849-27.93811395 18.43200097-55.71196266 27.64005428-83.57456879 3.99267318-12.09856827 3.7396531-12.35158706-9.26766421-12.55956629-7.83830367-0.11922438-15.70442566 0.01457133-23.54140371-0.37224318-11.4136928-0.55107846-19.5341592-6.09498652-23.75070856-16.94170392-4.14104022-10.56455466-1.49029705-19.77260668 6.19831398-27.54997384 25.15092515-25.40526957 50.42240031-50.68999172 75.6620816-76.00518086C182.68726879 642.31560258 188.64713398 639.45423026 195.18722288 640.09538918L195.18722288 640.09538918z"
p-id="1214"></path>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -54,21 +54,20 @@ const MultipleRowSelect = ({
bg: 'primary.50',
color: 'primary.600'
}}
onClick={() => {
const newValue = [...cloneValue];
newValue[index] = item.value;
setCloneValue(newValue);
if (!hasChildren) {
onSelect(newValue);
onClose();
}
}}
{...(item.value === selectedValue
? {
color: 'primary.600'
}
: {
onClick: () => {
const newValue = [...cloneValue];
newValue[index] = item.value;
setCloneValue(newValue);
if (!hasChildren) {
onSelect(newValue);
onClose();
}
}
})}
: {})}
>
{item.label}
</Flex>

View File

@@ -12,7 +12,7 @@ const MyTooltip = ({ children, forceShow = false, shouldWrapChildren = true, ...
<Tooltip
className="tooltip"
bg={'white'}
arrowShadowColor={' rgba(0,0,0,0.05)'}
arrowShadowColor={'rgba(0,0,0,0.05)'}
hasArrow
arrowSize={12}
offset={[-15, 15]}

View File

@@ -0,0 +1,24 @@
import { useTranslation } from 'next-i18next';
import { useEffect } from 'react';
export const useBeforeunload = (props?: { callback?: () => any; tip?: string }) => {
const { t } = useTranslation('common');
const { tip = t('Confirm to leave the page'), callback } = props || {};
useEffect(() => {
const listen =
process.env.NODE_ENV !== 'production'
? (e: any) => {
e.preventDefault();
e.returnValue = tip;
callback?.();
}
: () => {};
window.addEventListener('beforeunload', listen);
return () => {
window.removeEventListener('beforeunload', listen);
};
}, [tip, callback]);
};

View File

@@ -59,7 +59,7 @@ export const useConfirm = (props?: {
onClose,
ConfirmModal: useCallback(
({
closeText = t('common.Close'),
closeText = t('common.Cancel'),
confirmText = t('common.Confirm'),
isLoading,
bg,