feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
/*
* 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 { ContainerModule, injectable } from 'inversify';
import {
type PlaygroundContext,
type FlowNodeEntity,
type EntityManager,
FlowDocumentContribution,
EntityManagerContribution,
createFreeHistoryPlugin,
FormModelV2,
FlowNodeFormData,
createNodeContainerModules,
createNodeEntityDatas,
FlowDocumentContainerModule,
PlaygroundMockTools,
Playground,
loadPlugins,
bindContributions,
} from '@flowgram-adapter/free-layout-editor';
import {
type WorkflowDocument,
WorkflowDocumentContainerModule,
} from '@flowgram-adapter/free-layout-editor';
import { createWorkflowVariablePlugins } from '../src';
import { MockNodeRegistry } from './node.mock';
@injectable()
export class MockPlaygroundContext implements PlaygroundContext {
getNodeTemplateInfoByType() {
return {};
}
}
@injectable()
export class MockWorkflowForm
implements FlowDocumentContribution, EntityManagerContribution
{
registerDocument(document: WorkflowDocument): void {
document.registerNodeDatas(...createNodeEntityDatas());
}
registerEntityManager(entityManager: EntityManager): void {
const formModelFactory = (entity: FlowNodeEntity) =>
new FormModelV2(entity);
entityManager.registerEntityData(
FlowNodeFormData,
() =>
({
formModelFactory,
}) as any,
);
}
}
const testModule = new ContainerModule(bind => {
bindContributions(bind, MockWorkflowForm, [
FlowDocumentContribution,
EntityManagerContribution,
]);
bindContributions(bind, MockNodeRegistry, [FlowDocumentContribution]);
});
export function createContainer() {
const container = PlaygroundMockTools.createContainer([
FlowDocumentContainerModule,
WorkflowDocumentContainerModule,
...createNodeContainerModules(),
testModule,
]);
const playground = container.get(Playground);
loadPlugins(
[
createFreeHistoryPlugin({ enable: true, limit: 50 }),
...createWorkflowVariablePlugins(),
],
container,
);
playground.init();
return container;
}

View File

@@ -0,0 +1,104 @@
/*
* 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 {
WorkflowNodeInputVariablesData,
WorkflowNodeOutputVariablesData,
WorkflowNodeRefVariablesData,
} from 'src';
import { loopJSON } from '__tests__/workflow.mock';
import { createContainer } from '__tests__/create-container';
import { WorkflowDocument } from '@flowgram-adapter/free-layout-editor';
describe('test workflow node variables entity data', () => {
let workflowDocument: WorkflowDocument;
beforeEach(async () => {
const container = createContainer();
workflowDocument = container.get<WorkflowDocument>(WorkflowDocument);
await workflowDocument.fromJSON(loopJSON);
});
test('test workflow-node-input-variables-data', () => {
const nodes = workflowDocument.getAllNodes();
expect(
nodes.map(_node => {
const data = _node.getData(WorkflowNodeInputVariablesData);
return [
_node.id,
data.inputParameters,
data.inputVariables.map(_input => _input.refVariable?.viewType),
];
}),
).toMatchSnapshot();
});
test('test workflow-node-output-variables-data', () => {
const nodes = workflowDocument.getAllNodes();
expect(
nodes.map(_node => {
const data = _node.getData(WorkflowNodeOutputVariablesData);
return [
_node.id,
data.variables.map(
_variable => data.getVariableByKey(_variable.key)?.viewType,
),
];
}),
).toMatchSnapshot();
});
test('test workflow-node-ref-variables-data', () => {
const nodes = workflowDocument.getAllNodes();
expect(
nodes.map(_node => {
const data = _node.getData(WorkflowNodeRefVariablesData);
return [
_node.id,
data.refs,
Object.values(data.refVariables).map(_ref => _ref?.viewType),
data.hasGlobalRef,
];
}),
).toMatchSnapshot();
});
test('test batchUpdateRefs', () => {
const endNode = workflowDocument.getNode('end');
const data = endNode?.getData(WorkflowNodeRefVariablesData);
data?.batchUpdateRefs([
{
beforeKeyPath: ['llm_0'],
afterKeyPath: ['llm_test'],
},
{
beforeKeyPath: ['start', 'Object'],
afterKeyPath: ['start', 'Drilldown', 'Object'],
},
]);
expect(data?.refs).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,17 @@
/*
* 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.
*/
export default {};

View File

@@ -0,0 +1,91 @@
/*
* 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.
*/
/* eslint-disable max-params */
import { provideNodeOutputVariables } from 'src/form-extensions/variable-providers/provide-node-output-variables';
import { provideNodeBatchVariables } from 'src/form-extensions/variable-providers/provide-node-batch-variables';
import { provideMergeGroupVariables } from 'src/form-extensions/variable-providers/provide-merge-group-variables';
import { provideLoopOutputsVariables } from 'src/form-extensions/variable-providers/provide-loop-output-variables';
import { provideLoopInputsVariables } from 'src/form-extensions/variable-providers/provide-loop-input-variables';
import { type VariableProviderAbilityOptions } from '@flowgram-adapter/free-layout-editor';
import {
mockFullOutputs,
mockRefInputs,
mockMergeGroup,
} from '../variable.mock';
const createCase = <V>(
provider: VariableProviderAbilityOptions<V>,
value: V,
context: any = { node: { id: '12345' } },
extraInfo = '',
): {
key: string;
parse: (input: any, context: any) => any;
value: V;
context: any;
extraInfo: string;
} => ({
key: provider.key || '',
parse: provider.parse,
value,
context,
extraInfo,
});
describe('test variable providers', () => {
test.each([
createCase(provideLoopInputsVariables, {
inputParameters: mockRefInputs,
variableParameters: mockRefInputs,
}),
createCase(provideLoopOutputsVariables, mockRefInputs),
createCase(provideMergeGroupVariables, mockMergeGroup),
createCase(provideNodeBatchVariables, mockRefInputs, {
node: { id: '123456' },
formItem: { formModel: { getFormItemValueByPath: path => 'batch' } },
}),
createCase(provideNodeOutputVariables, mockFullOutputs),
createCase(
provideNodeBatchVariables,
mockRefInputs,
{
node: { id: '123456' },
formItem: {
formModel: { getFormItemValueByPath: path => 'not_batch' },
},
},
"shouldn't provide batch variables",
),
createCase(
provideNodeBatchVariables,
mockRefInputs,
{
node: { id: '123456' },
formItem: {
formModel: {
getFormItemValueByPath: path =>
path === '/batchMode' ? undefined : 'batch',
},
},
},
'batchMode in /inputs/batchMode',
),
])('test variable provider: $key $extraInfo', ({ parse, value, context }) => {
expect(parse?.(value, context)).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,86 @@
/*
* 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 React from 'react';
import { createEffectFromVariableProvider } from 'src/utils/variable-provider';
import { provideNodeOutputVariables } from 'src/form-extensions/variable-providers/provide-node-output-variables';
import { provideLoopOutputsVariables } from 'src/form-extensions/variable-providers/provide-loop-output-variables';
import { provideLoopInputsVariables } from 'src/form-extensions/variable-providers/provide-loop-input-variables';
import { provideMergeGroupVariablesEffect } from 'src';
import { injectable } from 'inversify';
import {
type WorkflowDocument,
type FlowDocumentContribution,
} from '@flowgram-adapter/free-layout-editor';
import { StandardNodeType } from '@coze-workflow/base/types';
@injectable()
export class MockNodeRegistry implements FlowDocumentContribution {
registerDocument(document: WorkflowDocument): void {
// Register Nodes
document.registerFlowNodes({
type: StandardNodeType.LLM,
formMeta: {
render: () => <div></div>,
effect: {
outputs: createEffectFromVariableProvider(provideNodeOutputVariables),
},
},
});
document.registerFlowNodes({
type: StandardNodeType.Start,
formMeta: {
render: () => <div></div>,
effect: {
outputs: createEffectFromVariableProvider(provideNodeOutputVariables),
},
},
});
document.registerFlowNodes({
type: StandardNodeType.End,
// getNodeInputParameters: () => [...allEndRefInputs, ...allConstantInputs],
formMeta: {
render: () => <div></div>,
},
});
document.registerFlowNodes({
type: StandardNodeType.Loop,
formMeta: {
render: () => <div></div>,
effect: {
inputs: createEffectFromVariableProvider(provideLoopInputsVariables),
outputs: createEffectFromVariableProvider(
provideLoopOutputsVariables,
),
},
},
});
document.registerFlowNodes({
type: StandardNodeType.VariableMerge,
formMeta: {
render: () => <div></div>,
effect: {
groups: provideMergeGroupVariablesEffect,
},
},
});
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 { WorkflowVariableService } from 'src';
import { loopJSON } from '__tests__/workflow.mock';
import { allKeyPaths } from '__tests__/variable.mock';
import { createContainer } from '__tests__/create-container';
import { WorkflowDocument } from '@flowgram-adapter/free-layout-editor';
describe('test variable service', () => {
let workflowDocument: WorkflowDocument;
let variableService: WorkflowVariableService;
beforeEach(async () => {
const container = createContainer();
workflowDocument = container.get<WorkflowDocument>(WorkflowDocument);
variableService = container.get<WorkflowVariableService>(
WorkflowVariableService,
);
await workflowDocument.fromJSON(loopJSON);
});
test('test get variable', () => {
expect(
allKeyPaths.map(_case => {
const workflowVariable = variableService.getWorkflowVariableByKeyPath(
_case,
{},
);
if (!workflowVariable) {
return [variableService.getViewVariableByKeyPath(_case, {})];
}
return [
variableService.getViewVariableByKeyPath(_case, {}),
workflowVariable.viewType,
workflowVariable.renderType,
workflowVariable.dtoMeta,
workflowVariable.refExpressionDTO,
workflowVariable.key,
workflowVariable.parentVariables.map(_field => _field.key),
workflowVariable.children.map(_field => _field.key),
workflowVariable.expressionPath,
workflowVariable.groupInfo,
workflowVariable.node.id,
];
}),
).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,81 @@
/*
* 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 { variableUtils, WorkflowVariableService } from 'src';
import { loopJSON } from '__tests__/workflow.mock';
import { allConstantInputs, allEndRefInputs } from '__tests__/variable.mock';
import { createContainer } from '__tests__/create-container';
import { WorkflowDocument } from '@flowgram-adapter/free-layout-editor';
import { ValueExpression } from '@coze-workflow/base';
import { mockFullOutputs } from './../variable.mock';
describe('test variable utils', () => {
let workflowDocument: WorkflowDocument;
let variableService: WorkflowVariableService;
beforeEach(async () => {
const container = createContainer();
workflowDocument = container.get<WorkflowDocument>(WorkflowDocument);
variableService = container.get<WorkflowVariableService>(
WorkflowVariableService,
);
await workflowDocument.fromJSON(loopJSON);
});
test('Ref VO DTO convert', () => {
const endNode = workflowDocument.getNode('end')!;
const allEndInputs = [...allEndRefInputs, ...allConstantInputs];
const dto = allEndInputs.map(_input =>
variableUtils.inputValueToDTO(_input, variableService, {
node: endNode,
}),
);
expect(dto).toMatchSnapshot();
const voBack = dto
.filter(Boolean)
.map(_dto => variableUtils.inputValueToVO(_dto!, variableService));
const availableEndInputs = allEndInputs.filter(
_ref => !ValueExpression.isEmpty(_ref.input),
);
expect(availableEndInputs.length).toBeLessThan(allEndInputs.length);
expect(voBack.length).toBe(availableEndInputs.length);
voBack.forEach((_vo, idx) => {
expect(_vo.name).toBe(availableEndInputs[idx].name);
});
});
test('Variable VO DTO convert', () => {
const dto = mockFullOutputs.map(_output =>
variableUtils.viewMetaToDTOMeta(_output),
);
expect(dto).toMatchSnapshot();
const voBack = dto.map(_dto => variableUtils.dtoMetaToViewMeta(_dto));
voBack.forEach((_vo, idx) => {
expect(_vo.type).toBe(mockFullOutputs[idx].type);
expect(_vo.name).toBe(mockFullOutputs[idx].name);
});
});
});

View File

@@ -0,0 +1,39 @@
/*
* 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 'reflect-metadata';
vi.mock('@coze-arch/i18n', () => ({
I18n: {
t: vi.fn(),
},
}));
vi.mock('@coze-arch/bot-flags', () => ({
getFlags: () => ({
'bot.automation.encapsulate': true,
}),
}));
vi.mock('@coze-arch/coze-design', () => ({
Typography: {
Text: vi.fn(),
},
withField: vi.fn(),
}));
vi.mock('@coze-workflow/components', () => ({}));
vi.stubGlobal('IS_DEV_MODE', true);
vi.stubGlobal('IS_OVERSEA', false);
vi.stubGlobal('IS_BOE', false);
vi.stubGlobal('REGION', 'cn');

View File

@@ -0,0 +1,147 @@
/*
* 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.
*/
/* eslint-disable security/detect-object-injection */
import { type InputItem } from 'src/form-extensions/variable-providers/common';
import {
ValueExpressionType,
ViewVariableType,
type ViewVariableMeta,
} from 'src';
import { type InputValueVO } from '@coze-workflow/base/types';
export const genInput = (name: string, keyPath: string[]): InputItem => ({
name,
input: {
type: ValueExpressionType.REF,
content: { keyPath },
},
});
export const mockKeyPaths = [
['start', 'String'],
['llm_0', 'Object'],
['start', 'Object', 'Image'],
['llm_0', 'Object', 'Number'],
['start', 'Array<Code>'],
['llm_0', 'Array<String>'],
['start', 'Array<Object>', 'Number'],
// Not existed
['llm_0', 'Array<String>', 'Number'],
['start', 'Object', 'Undefined'],
[],
];
export const mockMergeGroupKeyPaths = [
['merge_0', 'test_group_1'],
['merge_0', 'test_group_2'],
];
export const mockKeyPathsInsideLoop = [
['loop_0', 'loop_batch_llm_0__Object'],
['loop_0', 'loop_var_llm_0__Object'],
['loop_0', 'loop_batch_start__Array<Object>__Number'],
['loop_0', 'loop_var_start__Array<Code>'],
];
export const mockLoopOutputKeyPaths = [
['loop_0', 'loop_output_var_string'],
['loop_0', 'loop_output_string'],
['loop_0', 'loop_output_image'],
];
export const allKeyPaths = [
...mockKeyPaths,
...mockMergeGroupKeyPaths,
...mockKeyPathsInsideLoop,
...mockLoopOutputKeyPaths,
];
export const allEndRefInputs = [
...mockKeyPaths,
...mockMergeGroupKeyPaths,
...mockLoopOutputKeyPaths,
].map(_keyPath => genInput(_keyPath.join('__'), _keyPath));
export const allConstantInputs: InputValueVO[] = [
{
name: 'test_constant_no_raw_meta',
input: {
type: ValueExpressionType.LITERAL,
content: 'test_constant',
},
},
{
name: 'test_constant_image_url',
input: {
type: ValueExpressionType.LITERAL,
content: ['image_url'],
rawMeta: { type: ViewVariableType.ArrayImage },
},
},
{
name: 'test_constant_array_object',
input: {
type: ValueExpressionType.LITERAL,
content: [{ count: 0 }],
rawMeta: {
type: ViewVariableType.ArrayObject,
children: [
{
key: 'count',
name: 'count',
type: ViewVariableType.Number,
},
],
},
},
},
];
export const mockRefInputs: InputItem[] = mockKeyPaths.map(_keyPath =>
genInput(mockKeyPaths.join('__'), _keyPath),
);
export const mockRefValues = mockRefInputs.map(_input => _input.input);
export const mockMergeGroup = [
{
name: 'test_group_1',
variables: mockRefValues,
},
{
name: 'test_group_2',
variables: mockRefValues.reverse(),
},
];
export const mockFullOutputs: ViewVariableMeta[] =
ViewVariableType.getComplement([]).map(_type => ({
key: ViewVariableType.LabelMap[_type],
name: ViewVariableType.LabelMap[_type],
type: _type,
required: true,
description: 'test_description',
children: ViewVariableType.canDrilldown(_type)
? ViewVariableType.getComplement([]).map(_childType => ({
key: ViewVariableType.LabelMap[_childType],
name: ViewVariableType.LabelMap[_childType],
type: _childType,
required: true,
description: 'test_child_description',
}))
: [],
}));

View File

@@ -0,0 +1,210 @@
/*
* 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 { type WorkflowJSON } from '@flowgram-adapter/free-layout-editor';
import { StandardNodeType } from '@coze-workflow/base/types';
import {
allEndRefInputs,
genInput,
mockFullOutputs,
mockRefInputs,
mockMergeGroup,
} from './variable.mock';
/**
* start(start) -> llm_0 -> variable_merge_0 -> end(end)
* llm_2 -> llm_0
* llm_2 -> llm_3
*/
export const complexMock: WorkflowJSON = {
nodes: [
{
id: 'start',
type: StandardNodeType.Start,
meta: {
position: {
x: 180,
y: 26.700000000000017,
},
},
data: {
outputs: mockFullOutputs,
},
},
{
id: 'end',
type: StandardNodeType.End,
meta: {
position: {
x: 1674.1103135413448,
y: 40.63341482104891,
},
},
data: {
inputParameters: allEndRefInputs,
},
},
{
id: 'llm_0',
type: StandardNodeType.LLM,
meta: {
position: {
x: 918.2411574431025,
y: 109.52852376445134,
},
},
data: {
inputParameters: mockRefInputs,
outputs: mockFullOutputs,
},
},
{
id: 'merge_0',
type: StandardNodeType.VariableMerge,
data: {
groups: mockMergeGroup,
},
meta: {
position: {
x: 779.2423264779079,
y: -161.54447093414998,
},
},
},
{
id: 'llm_2',
type: StandardNodeType.LLM,
meta: {
position: {
x: -56.433897883820265,
y: 221.56518397907752,
},
},
},
{
id: 'llm_3',
type: StandardNodeType.LLM,
meta: {
position: {
x: 1072.169867226058,
y: 401.6455814216276,
},
},
},
{
id: '156471',
type: StandardNodeType.LLM,
meta: {
position: {
x: 914.727084798963,
y: 576.5804057333598,
},
},
},
],
edges: [
{
sourceNodeID: 'variable_merge_0',
targetNodeID: 'end',
},
{
sourceNodeID: 'start',
targetNodeID: 'llm_0',
},
{
sourceNodeID: 'llm_2',
targetNodeID: 'llm_0',
},
{
sourceNodeID: 'llm_0',
targetNodeID: 'variable_merge_0',
},
{
sourceNodeID: 'llm_2',
targetNodeID: 'llm_3',
},
],
};
export const loopJSON: WorkflowJSON = {
nodes: [
...complexMock.nodes,
{
id: 'loop_0',
type: StandardNodeType.Loop,
meta: {
position: { x: 1200, y: 0 },
},
data: {
inputs: {
inputParameters: mockRefInputs.map(_input => ({
..._input,
name: `loop_batch_${_input.name}`,
})),
variableParameters: mockRefInputs.map(_input => ({
..._input,
name: `loop_var_${_input.name}`,
})),
},
outputs: [
genInput('loop_output_var_string', [
'loop_0',
'loop_var_llm_0__Object',
]),
genInput('loop_output_string', ['llm_in_loop_0', 'String']),
genInput('loop_output_image', ['llm_in_loop_0', 'Object', 'Image']),
],
},
blocks: [
{
id: 'llm_in_loop_0',
type: StandardNodeType.LLM,
meta: {
position: { x: 400, y: 0 },
},
data: {
outputs: mockFullOutputs,
},
},
{
id: 'llm_in_loop_1',
type: StandardNodeType.LLM,
meta: {
position: { x: 500, y: 0 },
},
},
],
edges: [
{
sourceNodeID: 'llm_in_loop_0',
targetNodeID: 'llm_in_loop_1',
},
],
},
],
edges: [
...complexMock.edges,
{
sourceNodeID: 'llm_0',
targetNodeID: 'loop_0',
},
{
sourceNodeID: 'loop_0',
targetNodeID: 'end',
},
],
};