From a52bdf0b7e5f6f6909e269ad69dfd4e491ad2c06 Mon Sep 17 00:00:00 2001 From: balibabu Date: Mon, 3 Nov 2025 12:30:30 +0800 Subject: [PATCH] Feat: The structured output of the variable query can also be clicked. #10866 (#10952) ### What problem does this PR solve? Feat: The structured output of the variable query can also be clicked. #10866 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/pages/agent/constant/index.tsx | 8 ++++ .../prompt-editor/variable-picker-plugin.tsx | 5 +++ .../components/select-with-secondary-menu.tsx | 7 ++++ .../structured-output-secondary-menu.tsx | 17 ++++++--- .../hooks/use-build-structured-output.ts | 10 ++--- .../utils/filter-agent-structured-output.ts | 37 +++++++++++++------ 6 files changed, 61 insertions(+), 23 deletions(-) diff --git a/web/src/pages/agent/constant/index.tsx b/web/src/pages/agent/constant/index.tsx index 8ac12185f..f8a0c5d79 100644 --- a/web/src/pages/agent/constant/index.tsx +++ b/web/src/pages/agent/constant/index.tsx @@ -929,3 +929,11 @@ export const HALF_PLACEHOLDER_NODE_HEIGHT = export const DROPDOWN_HORIZONTAL_OFFSET = 28; export const DROPDOWN_VERTICAL_OFFSET = 74; export const PREVENT_CLOSE_DELAY = 300; + +export enum JsonSchemaDataType { + String = 'string', + Number = 'number', + Boolean = 'boolean', + Array = 'array', + Object = 'object', +} diff --git a/web/src/pages/agent/form/components/prompt-editor/variable-picker-plugin.tsx b/web/src/pages/agent/form/components/prompt-editor/variable-picker-plugin.tsx index 4e2fa9008..94d272b5f 100644 --- a/web/src/pages/agent/form/components/prompt-editor/variable-picker-plugin.tsx +++ b/web/src/pages/agent/form/components/prompt-editor/variable-picker-plugin.tsx @@ -36,6 +36,7 @@ import { useShowSecondaryMenu, } from '@/pages/agent/hooks/use-build-structured-output'; import { useBuildQueryVariableOptions } from '@/pages/agent/hooks/use-get-begin-query'; +import { hasJsonSchemaChild } from '@/pages/agent/utils/filter-agent-structured-output'; import { PromptIdentity } from '../../agent-form/use-build-prompt-options'; import { StructuredOutputSecondaryMenu } from '../structured-output-secondary-menu'; import { ProgrammaticTag } from './constant'; @@ -109,6 +110,10 @@ function VariablePickerMenuItem({ if (shouldShowSecondary) { const filteredStructuredOutput = filterStructuredOutput(x.value); + if (!hasJsonSchemaChild(filteredStructuredOutput)) { + return null; + } + return ( { + if (isEmpty(type) || type === JsonSchemaDataType.Object) { + click(data); + } + }, [click, data, type]); + const renderAgentStructuredOutput = useCallback( (values: any, option: { label: ReactNode; value: string }) => { if (isPlainObject(values) && 'properties' in values) { @@ -56,7 +63,7 @@ export function StructuredOutputSecondaryMenu({ {key} {dataType} - {dataType === 'object' && + {dataType === JsonSchemaDataType.Object && renderAgentStructuredOutput(value, nextOption)} ); @@ -74,7 +81,7 @@ export function StructuredOutputSecondaryMenu({
  • click(data)} + onClick={handleMenuClick} className="hover:bg-bg-card py-1 px-2 text-text-primary rounded-sm text-sm flex justify-between items-center" > {data.label} diff --git a/web/src/pages/agent/hooks/use-build-structured-output.ts b/web/src/pages/agent/hooks/use-build-structured-output.ts index 33d40f9d3..ba5f6b873 100644 --- a/web/src/pages/agent/hooks/use-build-structured-output.ts +++ b/web/src/pages/agent/hooks/use-build-structured-output.ts @@ -26,12 +26,10 @@ export function useShowSecondaryMenu() { } export function useFilterStructuredOutputByValue() { - const { getOperatorTypeFromId, getNode, clickedNodeId } = useGraphStore( - (state) => state, - ); + const { getNode } = useGraphStore((state) => state); const filterStructuredOutput = useCallback( - (value: string) => { + (value: string, type?: string) => { const node = getNode(getNodeId(value)); const structuredOutput = get( node, @@ -40,12 +38,12 @@ export function useFilterStructuredOutputByValue() { const filteredStructuredOutput = filterAgentStructuredOutput( structuredOutput, - getOperatorTypeFromId(clickedNodeId), + type, ); return filteredStructuredOutput; }, - [clickedNodeId, getNode, getOperatorTypeFromId], + [getNode], ); return filterStructuredOutput; diff --git a/web/src/pages/agent/utils/filter-agent-structured-output.ts b/web/src/pages/agent/utils/filter-agent-structured-output.ts index 56e3ec21f..107598a4a 100644 --- a/web/src/pages/agent/utils/filter-agent-structured-output.ts +++ b/web/src/pages/agent/utils/filter-agent-structured-output.ts @@ -1,6 +1,6 @@ import { JSONSchema } from '@/components/jsonjoy-builder'; -import { Operator } from '@/constants/agent'; -import { isPlainObject } from 'lodash'; +import { get, isPlainObject } from 'lodash'; +import { JsonSchemaDataType } from '../constant'; // Loop operators can only accept variables of type list. @@ -8,6 +8,7 @@ import { isPlainObject } from 'lodash'; export function filterLoopOperatorInput( structuredOutput: JSONSchema, + type: string, path = [], ) { if (typeof structuredOutput === 'boolean') { @@ -23,9 +24,9 @@ export function filterLoopOperatorInput( (pre, [key, value]) => { if ( typeof value !== 'boolean' && - (value.type === 'array' || hasArrayChild(value)) + (value.type === type || hasArrayChild(value)) ) { - pre[key] = filterLoopOperatorInput(value, path); + pre[key] = filterLoopOperatorInput(value, type, path); } return pre; }, @@ -40,7 +41,7 @@ export function filterLoopOperatorInput( export function filterAgentStructuredOutput( structuredOutput: JSONSchema, - operator?: string, + type?: string, ) { if (typeof structuredOutput === 'boolean') { return structuredOutput; @@ -49,8 +50,8 @@ export function filterAgentStructuredOutput( structuredOutput.properties && isPlainObject(structuredOutput.properties) ) { - if (operator === Operator.Iteration) { - return filterLoopOperatorInput(structuredOutput); + if (type) { + return filterLoopOperatorInput(structuredOutput, type); } return structuredOutput; @@ -59,13 +60,16 @@ export function filterAgentStructuredOutput( return structuredOutput; } -export function hasArrayChild(data: Record | Array) { +export function hasSpecificTypeChild( + data: Record | Array, + type: string, +) { if (Array.isArray(data)) { for (const value of data) { - if (isPlainObject(value) && value.type === 'array') { + if (isPlainObject(value) && value.type === type) { return true; } - if (hasArrayChild(value)) { + if (hasSpecificTypeChild(value, type)) { return true; } } @@ -73,11 +77,11 @@ export function hasArrayChild(data: Record | Array) { if (isPlainObject(data)) { for (const value of Object.values(data)) { - if (isPlainObject(value) && value.type === 'array') { + if (isPlainObject(value) && value.type === type) { return true; } - if (hasArrayChild(value)) { + if (hasSpecificTypeChild(value, type)) { return true; } } @@ -85,3 +89,12 @@ export function hasArrayChild(data: Record | Array) { return false; } + +export function hasArrayChild(data: Record | Array) { + return hasSpecificTypeChild(data, JsonSchemaDataType.Array); +} + +export function hasJsonSchemaChild(data: JSONSchema) { + const properties = get(data, 'properties') ?? {}; + return isPlainObject(properties) && Object.keys(properties).length > 0; +}