251 lines
9.4 KiB
TypeScript
251 lines
9.4 KiB
TypeScript
/*
|
|
* Copyright 2025 coze-dev Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { get, debounce } from 'lodash-es';
|
|
import { inject, injectable } from 'inversify';
|
|
import { type FlowNodeEntity } from '@flowgram-adapter/free-layout-editor';
|
|
import { FlowNodeFormData } from '@flowgram-adapter/free-layout-editor';
|
|
import { WorkflowDocument } from '@flowgram-adapter/free-layout-editor';
|
|
import { Emitter, type Event } from '@flowgram-adapter/common';
|
|
import {
|
|
MessageBizType,
|
|
MessageOperateType,
|
|
StandardNodeType,
|
|
} from '@coze-workflow/base';
|
|
import type { WsMessageProps } from '@coze-project-ide/framework/src/types';
|
|
import { getFlags } from '@coze-arch/bot-flags';
|
|
|
|
import {
|
|
WorkflowGlobalStateEntity,
|
|
WorkflowDependencyStateEntity,
|
|
} from '@/entities';
|
|
import { DependencySourceType } from '@/constants';
|
|
|
|
import { WorkflowSaveService } from './workflow-save-service';
|
|
import { TestRunState, WorkflowRunService } from './workflow-run-service';
|
|
import { WorkflowOperationService } from './workflow-operation-service';
|
|
import { WorkflowModelsService } from './workflow-models-service';
|
|
|
|
export const bizTypeToDependencyTypeMap = {
|
|
[MessageBizType.Workflow]: DependencySourceType.Workflow,
|
|
[MessageBizType.Plugin]: DependencySourceType.Plugin,
|
|
[MessageBizType.Dataset]: DependencySourceType.DataSet,
|
|
[MessageBizType.Database]: DependencySourceType.DataBase,
|
|
};
|
|
|
|
const DEBOUNCE_TIME = 2000;
|
|
export interface DependencyStore {
|
|
refreshModalVisible: boolean;
|
|
setRefreshModalVisible: (visible: boolean) => void;
|
|
}
|
|
|
|
interface SubworkflowVersionChangeProps {
|
|
subWorkflowId: string;
|
|
}
|
|
|
|
@injectable()
|
|
export class WorkflowDependencyService {
|
|
@inject(WorkflowModelsService) modelsService: WorkflowModelsService;
|
|
@inject(WorkflowDocument) protected workflowDocument: WorkflowDocument;
|
|
@inject(WorkflowGlobalStateEntity) globalState: WorkflowGlobalStateEntity;
|
|
@inject(WorkflowSaveService) saveService: WorkflowSaveService;
|
|
@inject(WorkflowOperationService) operationService: WorkflowOperationService;
|
|
@inject(WorkflowRunService) testRunService: WorkflowRunService;
|
|
@inject(WorkflowDependencyStateEntity)
|
|
dependencyEntity: WorkflowDependencyStateEntity;
|
|
|
|
onDependencyChangeEmitter = new Emitter<WsMessageProps | undefined>();
|
|
onDependencyChange: Event<WsMessageProps | undefined> =
|
|
this.onDependencyChangeEmitter.event;
|
|
|
|
onSubWrokflowVersionChangeEmitter = new Emitter<
|
|
SubworkflowVersionChangeProps | undefined
|
|
>();
|
|
onSubWrokflowVersionChange: Event<SubworkflowVersionChangeProps | undefined> =
|
|
this.onSubWrokflowVersionChangeEmitter.event;
|
|
|
|
/**
|
|
* Possible badcases:
|
|
* - The save interface returns slowly, resulting in the refresh pop-up window of the long chain refresh before the version conflict error, but this scenario is relatively limited, and it is necessary to optimize it later.
|
|
* - Canvas interface returns slowly, resulting in a long chain push notification message consistent with the current version, resulting in an additional refresh.
|
|
* Avoid this problem by judging the version number again before refreshing.
|
|
*/
|
|
private workflowDocumentReload = debounce(
|
|
(callback, messageVersion?: bigint) => {
|
|
const isVersionNewer =
|
|
messageVersion && this.dependencyEntity.saveVersion > messageVersion;
|
|
if (this.dependencyEntity.refreshModalVisible || isVersionNewer) {
|
|
return;
|
|
}
|
|
callback?.();
|
|
},
|
|
DEBOUNCE_TIME,
|
|
);
|
|
|
|
updateDependencySources(props: WsMessageProps, callback?: () => void) {
|
|
const FLAGS = getFlags();
|
|
if (!FLAGS['bot.automation.project_multi_tab']) {
|
|
return;
|
|
}
|
|
|
|
const allNodes = this.workflowDocument.getAllNodes();
|
|
const llmNodes = allNodes.filter(
|
|
n => n.flowNodeType === StandardNodeType.LLM,
|
|
);
|
|
|
|
// LLM Node Skill Update
|
|
llmNodes?.forEach(node => {
|
|
const formData = node.getData(FlowNodeFormData);
|
|
const formValue = formData.toJSON();
|
|
const llmSkillIdFields = [
|
|
{ field: 'fcParam.pluginFCParam.pluginList', key: 'api_id' },
|
|
{ field: 'fcParam.knowledgeFCParam.knowledgeList', key: 'id' },
|
|
{ field: 'fcParam.workflowFCParam.workflowList', key: 'workflow_id' },
|
|
];
|
|
|
|
const skillNeedRefresh = llmSkillIdFields.some(subSkill => {
|
|
const subSkillList = get(formValue.inputs, subSkill.field) ?? [];
|
|
return subSkillList.find(
|
|
(item: unknown) => get(item, subSkill.key) === props?.resId,
|
|
);
|
|
});
|
|
|
|
if (skillNeedRefresh) {
|
|
const nextProps: WsMessageProps = {
|
|
...props,
|
|
extra: {
|
|
nodeIds: [node.id],
|
|
},
|
|
};
|
|
this.onDependencyChangeEmitter.fire(nextProps);
|
|
}
|
|
});
|
|
const { saveVersion } = this.dependencyEntity;
|
|
|
|
const dependencyHandlers: {
|
|
[key in DependencySourceType]: (
|
|
nodes: FlowNodeEntity[],
|
|
source?: WsMessageProps,
|
|
) => Promise<void> | void;
|
|
} = {
|
|
[DependencySourceType.Workflow]: (_nodes, resource) => {
|
|
// The current workflow is updated on other pages
|
|
if (resource?.resId === this.globalState.workflowId) {
|
|
// If you modify the workflow name or description, saveVersion will not be updated
|
|
const isMetaUpdate =
|
|
resource?.operateType === MessageOperateType.MetaUpdate;
|
|
// Switch workflow type scene needs to be refreshed
|
|
const isFlowModeChange =
|
|
resource?.extra?.flowMode !== undefined &&
|
|
this.globalState.flowMode.toString() !== resource.extra.flowMode;
|
|
const metaUpdateNeedRefresh = isMetaUpdate && isFlowModeChange;
|
|
// No refresh required during practice runs
|
|
const isTestRunning =
|
|
this.testRunService.testRunState === TestRunState.Executing ||
|
|
this.testRunService.testRunState === TestRunState.Paused;
|
|
// The current version is larger than the saved version of other pages and does not need to be refreshed.
|
|
const resourceVersion = BigInt(resource?.saveVersion ?? 0);
|
|
const isCurVersionNewer = saveVersion > resourceVersion;
|
|
if (
|
|
isCurVersionNewer ||
|
|
this.dependencyEntity.refreshModalVisible ||
|
|
isTestRunning
|
|
) {
|
|
if (metaUpdateNeedRefresh) {
|
|
this.workflowDocumentReload(callback);
|
|
}
|
|
return;
|
|
}
|
|
this.workflowDocumentReload(callback, resourceVersion);
|
|
return;
|
|
}
|
|
|
|
const subWorkflowNodes = _nodes.filter(
|
|
n => n.flowNodeType === StandardNodeType.SubWorkflow,
|
|
);
|
|
const needUpdateNodeIds: string[] = [];
|
|
subWorkflowNodes?.forEach(node => {
|
|
const formData = node.getData(FlowNodeFormData);
|
|
const formValue = formData.toJSON();
|
|
const { workflowId } = formValue.inputs;
|
|
// The child workflows within the current workflow are updated
|
|
if (resource?.resId === workflowId) {
|
|
needUpdateNodeIds.push(workflowId);
|
|
}
|
|
});
|
|
if (!needUpdateNodeIds.length) {
|
|
return;
|
|
}
|
|
const nextProps: WsMessageProps = {
|
|
...props,
|
|
extra: {
|
|
nodeIds: needUpdateNodeIds,
|
|
},
|
|
};
|
|
this.onDependencyChangeEmitter.fire(nextProps);
|
|
},
|
|
[DependencySourceType.Plugin]: (nodes, resource) => {
|
|
const apiNodes = nodes.filter(
|
|
n => n.flowNodeType === StandardNodeType.Api,
|
|
);
|
|
const needUpdateNodeIds: string[] = [];
|
|
apiNodes?.forEach(node => {
|
|
const formData = node.getData(FlowNodeFormData);
|
|
const formValue = formData.toJSON();
|
|
const apiParam = formValue.inputs.apiParam.find(
|
|
param => param.name === 'apiID',
|
|
);
|
|
const apiID = apiParam.input.value.content ?? '';
|
|
if (apiID === resource?.resId) {
|
|
needUpdateNodeIds.push(node.id);
|
|
}
|
|
});
|
|
if (!needUpdateNodeIds.length) {
|
|
return;
|
|
}
|
|
const nextProps: WsMessageProps = {
|
|
...props,
|
|
extra: {
|
|
nodeIds: needUpdateNodeIds,
|
|
},
|
|
};
|
|
this.onDependencyChangeEmitter.fire(nextProps);
|
|
},
|
|
[DependencySourceType.DataSet]: (_, resource) => {
|
|
// Clear the knowledge base cache
|
|
this.globalState.sharedDataSetStore.clearDataSetInfosMap();
|
|
this.onDependencyChangeEmitter.fire(resource);
|
|
},
|
|
[DependencySourceType.DataBase]: (_, resource) => {
|
|
this.onDependencyChangeEmitter.fire(resource);
|
|
},
|
|
[DependencySourceType.LLM]: (_, resource) => {
|
|
this.onDependencyChangeEmitter.fire(resource);
|
|
},
|
|
};
|
|
|
|
if (props?.bizType) {
|
|
// Set the refresh method in the refresh pop-up window
|
|
this.dependencyEntity.setRefreshFunc(() => {
|
|
callback?.();
|
|
});
|
|
|
|
const dependencyType = bizTypeToDependencyTypeMap[props.bizType];
|
|
dependencyHandlers[dependencyType](allNodes, props);
|
|
}
|
|
}
|
|
}
|