fix: 修复流式输出展开状态被覆盖
- 保留智能体思考内容的手动展开与关闭选择 - 工作流试运行默认跟随当前节点,并允许用户自由多开或关闭
This commit is contained in:
@@ -36,7 +36,8 @@ 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 activeNames = ref<string[]>([]);
|
||||
const userControlledExpansion = ref(false);
|
||||
const confirmParams = ref<any>({});
|
||||
// 定义一个对象来存储所有的 form 实例,key 为 node.key
|
||||
const formRefs = ref<Record<string, FormInstance>>({});
|
||||
@@ -47,11 +48,14 @@ 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);
|
||||
function handleManualExpansionChange() {
|
||||
if (!props.expandAll) {
|
||||
userControlledExpansion.value = true;
|
||||
}
|
||||
return activeName.value === nodeKey;
|
||||
}
|
||||
|
||||
function isExpandedNode(nodeKey: string) {
|
||||
return activeNames.value.includes(nodeKey);
|
||||
}
|
||||
|
||||
function hasNodePayloadChanged(previous: any, current: any) {
|
||||
@@ -104,11 +108,12 @@ watch(
|
||||
}
|
||||
nodeStatusMap.value[nodeId] = currentNodeState;
|
||||
if (
|
||||
!userControlledExpansion.value &&
|
||||
!props.expandAll &&
|
||||
previousStatus !== currentStatus &&
|
||||
shouldAutoExpandStatus(currentStatus)
|
||||
) {
|
||||
activeName.value = nodeId;
|
||||
activeNames.value = [nodeId];
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -121,7 +126,8 @@ watch(
|
||||
isChainError.value = false;
|
||||
confirmBtnLoading.value = false;
|
||||
chainErrMsg.value = '';
|
||||
activeName.value = props.expandAll ? [] : '';
|
||||
userControlledExpansion.value = false;
|
||||
activeNames.value = [];
|
||||
},
|
||||
);
|
||||
watch(
|
||||
@@ -129,9 +135,16 @@ watch(
|
||||
(newVal) => {
|
||||
const nextNodes = Array.isArray(newVal) ? [...newVal] : [];
|
||||
nodes.value = nextNodes;
|
||||
activeName.value = props.expandAll
|
||||
? nextNodes.map((node: any) => node.key)
|
||||
: '';
|
||||
|
||||
if (props.expandAll) {
|
||||
activeNames.value = nextNodes.map((node: any) => node.key);
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeKeys = new Set(nextNodes.map((node: any) => node.key));
|
||||
activeNames.value = activeNames.value.filter((nodeKey) =>
|
||||
nodeKeys.has(nodeKey),
|
||||
);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -181,9 +194,9 @@ function handleConfirm(node: any) {
|
||||
<ElAlert v-if="chainErrMsg" :title="chainErrMsg" type="error" />
|
||||
</div>
|
||||
<ElCollapse
|
||||
v-model="activeName"
|
||||
:accordion="!props.expandAll"
|
||||
v-model="activeNames"
|
||||
expand-icon-position="left"
|
||||
@change="handleManualExpansionChange"
|
||||
>
|
||||
<ElCollapseItem
|
||||
v-for="node in displayNodes"
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import WorkflowSteps from '../WorkflowSteps.vue';
|
||||
|
||||
function createWorkflowNodes() {
|
||||
return [
|
||||
{
|
||||
key: 'node-a',
|
||||
label: '节点 A',
|
||||
original: {
|
||||
data: {},
|
||||
type: 'llmNode',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'node-b',
|
||||
label: '节点 B',
|
||||
original: {
|
||||
data: {},
|
||||
type: 'llmNode',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function mountWorkflowSteps() {
|
||||
return mount(WorkflowSteps, {
|
||||
props: {
|
||||
initSignal: false,
|
||||
nodeJson: createWorkflowNodes(),
|
||||
pollingData: undefined,
|
||||
workflowId: 'workflow-1',
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
ConfirmItem: true,
|
||||
ConfirmItemMulti: true,
|
||||
ShowJson: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getCollapseItems(wrapper: ReturnType<typeof mountWorkflowSteps>) {
|
||||
return wrapper.findAll('.el-collapse-item');
|
||||
}
|
||||
|
||||
describe('workflowSteps', () => {
|
||||
it('用户手动展开或关闭后,节点刷新和轮询不会覆盖当前选择', async () => {
|
||||
const wrapper = mountWorkflowSteps();
|
||||
|
||||
await wrapper.setProps({
|
||||
pollingData: {
|
||||
nodes: {
|
||||
'node-a': { status: 1 },
|
||||
},
|
||||
status: 1,
|
||||
},
|
||||
});
|
||||
expect(getCollapseItems(wrapper)[0]?.classes()).toContain('is-active');
|
||||
|
||||
await wrapper.setProps({
|
||||
pollingData: {
|
||||
nodes: {
|
||||
'node-a': { result: { text: 'A 完成' }, status: 20 },
|
||||
'node-b': { status: 1 },
|
||||
},
|
||||
status: 1,
|
||||
},
|
||||
});
|
||||
expect(getCollapseItems(wrapper)[0]?.classes()).not.toContain('is-active');
|
||||
expect(getCollapseItems(wrapper)[1]?.classes()).toContain('is-active');
|
||||
|
||||
await getCollapseItems(wrapper)[0]
|
||||
?.get('.el-collapse-item__header')
|
||||
.trigger('click');
|
||||
expect(getCollapseItems(wrapper)[0]?.classes()).toContain('is-active');
|
||||
expect(getCollapseItems(wrapper)[1]?.classes()).toContain('is-active');
|
||||
|
||||
await wrapper.setProps({
|
||||
nodeJson: createWorkflowNodes(),
|
||||
pollingData: {
|
||||
nodes: {
|
||||
'node-a': { result: { text: 'A 完成' }, status: 20 },
|
||||
'node-b': { result: { text: 'B 完成' }, status: 20 },
|
||||
},
|
||||
status: 20,
|
||||
},
|
||||
});
|
||||
expect(getCollapseItems(wrapper)[0]?.classes()).toContain('is-active');
|
||||
expect(getCollapseItems(wrapper)[1]?.classes()).toContain('is-active');
|
||||
|
||||
await getCollapseItems(wrapper)[0]
|
||||
?.get('.el-collapse-item__header')
|
||||
.trigger('click');
|
||||
expect(getCollapseItems(wrapper)[0]?.classes()).not.toContain('is-active');
|
||||
expect(getCollapseItems(wrapper)[1]?.classes()).toContain('is-active');
|
||||
|
||||
await wrapper.setProps({
|
||||
pollingData: {
|
||||
nodes: {
|
||||
'node-a': { message: '节点执行失败', status: 21 },
|
||||
'node-b': { result: { text: 'B 完成' }, status: 20 },
|
||||
},
|
||||
status: 21,
|
||||
},
|
||||
});
|
||||
expect(getCollapseItems(wrapper)[0]?.classes()).not.toContain('is-active');
|
||||
expect(getCollapseItems(wrapper)[1]?.classes()).toContain('is-active');
|
||||
});
|
||||
|
||||
it('新一轮试运行会清除手动选择并恢复自动跟随', async () => {
|
||||
const wrapper = mountWorkflowSteps();
|
||||
|
||||
await getCollapseItems(wrapper)[1]
|
||||
?.get('.el-collapse-item__header')
|
||||
.trigger('click');
|
||||
expect(getCollapseItems(wrapper)[1]?.classes()).toContain('is-active');
|
||||
|
||||
await wrapper.setProps({ initSignal: true });
|
||||
expect(
|
||||
getCollapseItems(wrapper).every((item) => !item.classes('is-active')),
|
||||
).toBe(true);
|
||||
|
||||
await wrapper.setProps({
|
||||
pollingData: {
|
||||
nodes: {
|
||||
'node-a': { status: 1 },
|
||||
},
|
||||
status: 1,
|
||||
},
|
||||
});
|
||||
expect(getCollapseItems(wrapper)[0]?.classes()).toContain('is-active');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user