diff --git a/web/app/components/workflow/nodes/_base/components/variable/use-match-schema-type.spec.ts b/web/app/components/workflow/nodes/_base/components/variable/use-match-schema-type.spec.ts new file mode 100644 index 0000000000..f5bdbe6e19 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/use-match-schema-type.spec.ts @@ -0,0 +1,71 @@ +import { deepEqualByType } from './use-match-schema-type' + +describe('deepEqualByType', () => { + it('should return true for identical primitive types', () => { + expect(deepEqualByType({ type: 'string' }, { type: 'string' })).toBe(true) + expect(deepEqualByType({ type: 'number' }, { type: 'number' })).toBe(true) + }) + + it('should return false for different primitive types', () => { + expect(deepEqualByType({ type: 'string' }, { type: 'number' })).toBe(false) + }) + + it('should ignore values and only compare types', () => { + expect(deepEqualByType({ type: 'string', value: 'hello' }, { type: 'string', value: 'world' })).toBe(true) + expect(deepEqualByType({ type: 'number', value: 42 }, { type: 'number', value: 100 })).toBe(true) + }) + + it('should return true for structural differences but no types', () => { + expect(deepEqualByType({ type: 'string', other: { b: 'xxx' } }, { type: 'string', other: 'xxx' })).toBe(true) + expect(deepEqualByType({ type: 'string', other: { b: 'xxx' } }, { type: 'string' })).toBe(true) + }) + + it('should handle nested objects with same structure and types', () => { + const obj1 = { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + address: { + type: 'object', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + }, + }, + } + const obj2 = { + type: 'object', + properties: { + name: { type: 'string', value: 'Alice' }, + age: { type: 'number', value: 30 }, + address: { + type: 'object', + properties: { + street: { type: 'string', value: '123 Main St' }, + city: { type: 'string', value: 'Wonderland' }, + }, + }, + }, + } + expect(deepEqualByType(obj1, obj2)).toBe(true) + }) + it('should return false for nested objects with different structures', () => { + const obj1 = { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + }, + } + const obj2 = { + type: 'object', + properties: { + name: { type: 'string' }, + address: { type: 'string' }, + }, + } + expect(deepEqualByType(obj1, obj2)).toBe(false) + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/variable/use-match-schema-type.ts b/web/app/components/workflow/nodes/_base/components/variable/use-match-schema-type.ts new file mode 100644 index 0000000000..8c6a47a735 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/use-match-schema-type.ts @@ -0,0 +1,31 @@ +type AnyObj = Record | null + +// only compare type in object +export function deepEqualByType(a: AnyObj, b: AnyObj): boolean { + const isObj = (x: any): x is object => x !== null && typeof x === 'object' + + const cmp = (x: AnyObj, y: AnyObj): boolean => { + const ox = isObj(x) + const oy = isObj(y) + if (!ox && !oy) return true // both primitives → ignore values + if (ox !== oy) { // ignore the object without type + if(ox && !('type' in (x as object))) + return true + return !!(oy && !('type' in (y as object))) + } + // check current `type` + const tx = (x as any).type + const ty = (y as any).type + if (tx !== ty) return false + + // recurse into all keys + const keys = new Set([...Object.keys(x as object), ...Object.keys(y as object)]) + for (const k of keys) { + if (k === 'type') continue // already checked + if (!cmp((x as any)[k], (y as any)[k])) return false + } + return true + } + + return cmp(a, b) +} diff --git a/web/app/components/workflow/utils/tool.ts b/web/app/components/workflow/utils/tool.ts index bad6680169..d37ed43da5 100644 --- a/web/app/components/workflow/utils/tool.ts +++ b/web/app/components/workflow/utils/tool.ts @@ -62,6 +62,7 @@ export const getOutputVariableAlias = (variable: Record) => { } export const wrapStructuredVarItem = (outputItem: any): StructuredOutput => { const dataType = Type.object + // console.log(outputItem) return { schema: { type: dataType,