feat: 支持工作流插件复用与试运行
- 新增工作流插件类型、发布快照同步、实时可用性与下线影响检查 - 收口绑定候选、分类权限、间接环路校验与运行态优雅降级 - 补齐管理端工作流插件配置、详情与试运行界面及定向测试
This commit is contained in:
@@ -4,11 +4,12 @@ import type { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Position } from '@element-plus/icons-vue';
|
||||
import { ElButton, ElForm, ElFormItem, ElInput, ElMessage } from 'element-plus';
|
||||
import { ElButton, ElForm, ElFormItem, ElMessage } from 'element-plus';
|
||||
|
||||
import { api } from '#/api/request';
|
||||
import ShowJson from '#/components/json/ShowJson.vue';
|
||||
import { $t } from '#/locales';
|
||||
import WorkflowFormItem from '#/views/ai/workflow/components/WorkflowFormItem.vue';
|
||||
|
||||
interface Props {
|
||||
workflowId: any;
|
||||
@@ -47,19 +48,10 @@ function submit() {
|
||||
<template>
|
||||
<div>
|
||||
<ElForm label-position="top" ref="singleRunForm" :model="runParams">
|
||||
<ElFormItem
|
||||
v-for="(item, idx) in node?.data.parameters"
|
||||
:prop="item.name"
|
||||
:key="idx"
|
||||
:label="item.description || item.name"
|
||||
:rules="[{ required: true, message: $t('message.required') }]"
|
||||
>
|
||||
<ElInput
|
||||
v-if="item.formType === 'input' || !item.formType"
|
||||
v-model="runParams[item.name]"
|
||||
:placeholder="item.formPlaceholder"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<WorkflowFormItem
|
||||
v-model:run-params="runParams"
|
||||
:parameters="node?.data.parameters || []"
|
||||
/>
|
||||
<ElFormItem>
|
||||
<ElButton
|
||||
type="primary"
|
||||
|
||||
@@ -29,16 +29,64 @@ export interface WorkflowStepsProps {
|
||||
nodeJson: any;
|
||||
initSignal?: boolean;
|
||||
pollingData?: any;
|
||||
expandAll?: boolean;
|
||||
}
|
||||
const props = defineProps<WorkflowStepsProps>();
|
||||
const emit = defineEmits(['resume']);
|
||||
const nodes = ref<any[]>([]);
|
||||
const nodeStatusMap = ref<Record<string, any>>({});
|
||||
const isChainError = ref(false);
|
||||
const activeName = ref<any>(props.expandAll ? [] : '');
|
||||
const confirmParams = ref<any>({});
|
||||
// 定义一个对象来存储所有的 form 实例,key 为 node.key
|
||||
const formRefs = ref<Record<string, FormInstance>>({});
|
||||
const confirmBtnLoading = ref(false);
|
||||
const chainErrMsg = ref('');
|
||||
|
||||
function shouldAutoExpandStatus(status: unknown) {
|
||||
return [1, 5, 20, 21].includes(Number(status));
|
||||
}
|
||||
|
||||
function isExpandedNode(nodeKey: string) {
|
||||
if (Array.isArray(activeName.value)) {
|
||||
return activeName.value.includes(nodeKey);
|
||||
}
|
||||
return activeName.value === nodeKey;
|
||||
}
|
||||
|
||||
function hasNodePayloadChanged(previous: any, current: any) {
|
||||
if (previous === current) {
|
||||
return false;
|
||||
}
|
||||
return JSON.stringify(previous ?? null) !== JSON.stringify(current ?? null);
|
||||
}
|
||||
|
||||
function hasNodeStateChanged(previous: any, current: any) {
|
||||
if (!previous) {
|
||||
return true;
|
||||
}
|
||||
if ((previous?.status || null) !== (current?.status || null)) {
|
||||
return true;
|
||||
}
|
||||
if ((previous?.message || '') !== (current?.message || '')) {
|
||||
return true;
|
||||
}
|
||||
if (hasNodePayloadChanged(previous?.result, current?.result)) {
|
||||
return true;
|
||||
}
|
||||
return hasNodePayloadChanged(
|
||||
previous?.suspendForParameters,
|
||||
current?.suspendForParameters,
|
||||
);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.pollingData,
|
||||
(newVal) => {
|
||||
const nodes = newVal.nodes;
|
||||
if (!newVal) {
|
||||
return;
|
||||
}
|
||||
const currentNodes = newVal.nodes || {};
|
||||
if (newVal.status === 21) {
|
||||
isChainError.value = true;
|
||||
chainErrMsg.value = newVal.message;
|
||||
@@ -46,9 +94,20 @@ watch(
|
||||
if (![20, 21].includes(newVal.status)) {
|
||||
confirmBtnLoading.value = false;
|
||||
}
|
||||
for (const nodeId in nodes) {
|
||||
nodeStatusMap.value[nodeId] = nodes[nodeId];
|
||||
if (nodes[nodeId].status === 5) {
|
||||
for (const nodeId in currentNodes) {
|
||||
const previousNodeState = nodeStatusMap.value[nodeId];
|
||||
const currentNodeState = currentNodes[nodeId];
|
||||
const previousStatus = previousNodeState?.status;
|
||||
const currentStatus = currentNodeState?.status;
|
||||
if (!hasNodeStateChanged(previousNodeState, currentNodeState)) {
|
||||
continue;
|
||||
}
|
||||
nodeStatusMap.value[nodeId] = currentNodeState;
|
||||
if (
|
||||
!props.expandAll &&
|
||||
previousStatus !== currentStatus &&
|
||||
shouldAutoExpandStatus(currentStatus)
|
||||
) {
|
||||
activeName.value = nodeId;
|
||||
}
|
||||
}
|
||||
@@ -62,14 +121,17 @@ watch(
|
||||
isChainError.value = false;
|
||||
confirmBtnLoading.value = false;
|
||||
chainErrMsg.value = '';
|
||||
activeName.value = props.expandAll ? [] : '';
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => props.nodeJson,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
nodes.value = [...newVal];
|
||||
}
|
||||
const nextNodes = Array.isArray(newVal) ? [...newVal] : [];
|
||||
nodes.value = nextNodes;
|
||||
activeName.value = props.expandAll
|
||||
? nextNodes.map((node: any) => node.key)
|
||||
: '';
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -79,18 +141,12 @@ const displayNodes = computed(() => {
|
||||
...nodeStatusMap.value[node.key],
|
||||
}));
|
||||
});
|
||||
const activeName = ref('1');
|
||||
const confirmParams = ref<any>({});
|
||||
// 定义一个对象来存储所有的 form 实例,key 为 node.key
|
||||
const formRefs = ref<Record<string, FormInstance>>({});
|
||||
// 动态设置 Ref 的辅助函数
|
||||
const setFormRef = (el: any, key: string) => {
|
||||
if (el) {
|
||||
formRefs.value[key] = el as FormInstance;
|
||||
}
|
||||
};
|
||||
const confirmBtnLoading = ref(false);
|
||||
const chainErrMsg = ref('');
|
||||
function getSelectMode(ops: any) {
|
||||
return ops.formType || 'radio';
|
||||
}
|
||||
@@ -124,7 +180,11 @@ function handleConfirm(node: any) {
|
||||
<div class="mb-1">
|
||||
<ElAlert v-if="chainErrMsg" :title="chainErrMsg" type="error" />
|
||||
</div>
|
||||
<ElCollapse v-model="activeName" accordion expand-icon-position="left">
|
||||
<ElCollapse
|
||||
v-model="activeName"
|
||||
:accordion="!props.expandAll"
|
||||
expand-icon-position="left"
|
||||
>
|
||||
<ElCollapseItem
|
||||
v-for="node in displayNodes"
|
||||
:key="node.key"
|
||||
@@ -165,64 +225,66 @@ function handleConfirm(node: any) {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="node.original.type === 'confirmNode'" class="p-2.5">
|
||||
<div class="mb-2 text-[16px] font-bold">
|
||||
{{ node.original.data.message }}
|
||||
</div>
|
||||
<ElForm
|
||||
:ref="(el) => setFormRef(el, node.key)"
|
||||
label-position="top"
|
||||
:model="confirmParams"
|
||||
>
|
||||
<template
|
||||
v-for="(ops, idx) in node.suspendForParameters"
|
||||
:key="idx"
|
||||
<template v-if="isExpandedNode(node.key)">
|
||||
<div v-if="node.original.type === 'confirmNode'" class="p-2.5">
|
||||
<div class="mb-2 text-[16px] font-bold">
|
||||
{{ node.original.data.message }}
|
||||
</div>
|
||||
<ElForm
|
||||
:ref="(el) => setFormRef(el, node.key)"
|
||||
label-position="top"
|
||||
:model="confirmParams"
|
||||
>
|
||||
<div class="header-container" v-if="ops.formType !== 'confirm'">
|
||||
<div class="blue-bar"> </div>
|
||||
<span>{{ ops.formLabel || $t('message.confirmItem') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="description-container"
|
||||
v-if="ops.formType !== 'confirm'"
|
||||
<template
|
||||
v-for="(ops, idx) in node.suspendForParameters"
|
||||
:key="idx"
|
||||
>
|
||||
{{ ops.formDescription }}
|
||||
</div>
|
||||
<ElFormItem
|
||||
v-if="ops.formType !== 'confirm'"
|
||||
:prop="ops.name"
|
||||
:rules="[{ required: true, message: $t('message.required') }]"
|
||||
>
|
||||
<ConfirmItem
|
||||
v-if="getSelectMode(ops) === 'radio'"
|
||||
v-model="confirmParams[ops.name]"
|
||||
:selection-data-type="ops.contentType || 'text'"
|
||||
:selection-data="ops.enums"
|
||||
/>
|
||||
<ConfirmItemMulti
|
||||
v-else
|
||||
v-model="confirmParams[ops.name]"
|
||||
:selection-data-type="ops.contentType || 'text'"
|
||||
:selection-data="ops.enums"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<ElFormItem v-if="node.suspendForParameters?.length > 0">
|
||||
<div class="flex justify-end">
|
||||
<ElButton
|
||||
:disabled="confirmBtnLoading"
|
||||
type="primary"
|
||||
@click="handleConfirm(node)"
|
||||
<div class="header-container" v-if="ops.formType !== 'confirm'">
|
||||
<div class="blue-bar"> </div>
|
||||
<span>{{ ops.formLabel || $t('message.confirmItem') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="description-container"
|
||||
v-if="ops.formType !== 'confirm'"
|
||||
>
|
||||
{{ $t('button.confirm') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</div>
|
||||
<div v-else>
|
||||
<ShowJson :value="node.result || node.message" />
|
||||
</div>
|
||||
{{ ops.formDescription }}
|
||||
</div>
|
||||
<ElFormItem
|
||||
v-if="ops.formType !== 'confirm'"
|
||||
:prop="ops.name"
|
||||
:rules="[{ required: true, message: $t('message.required') }]"
|
||||
>
|
||||
<ConfirmItem
|
||||
v-if="getSelectMode(ops) === 'radio'"
|
||||
v-model="confirmParams[ops.name]"
|
||||
:selection-data-type="ops.contentType || 'text'"
|
||||
:selection-data="ops.enums"
|
||||
/>
|
||||
<ConfirmItemMulti
|
||||
v-else
|
||||
v-model="confirmParams[ops.name]"
|
||||
:selection-data-type="ops.contentType || 'text'"
|
||||
:selection-data="ops.enums"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<ElFormItem v-if="node.suspendForParameters?.length > 0">
|
||||
<div class="flex justify-end">
|
||||
<ElButton
|
||||
:disabled="confirmBtnLoading"
|
||||
type="primary"
|
||||
@click="handleConfirm(node)"
|
||||
>
|
||||
{{ $t('button.confirm') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</div>
|
||||
<div v-else>
|
||||
<ShowJson :value="node.result || node.message" />
|
||||
</div>
|
||||
</template>
|
||||
</ElCollapseItem>
|
||||
</ElCollapse>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user