From 756ead0a203e61f3c011e40a2b1f8af961cc8b15 Mon Sep 17 00:00:00 2001 From: bill Date: Tue, 18 Nov 2025 18:02:29 +0800 Subject: [PATCH] Feat: Add a switch to control the display of structured output to the agent form. --- web/src/pages/agent/constant/index.tsx | 13 +--- web/src/pages/agent/form/agent-form/index.tsx | 65 ++++++++++++++----- .../use-show-structured-output-dialog.ts | 14 ++-- .../agent/form/agent-form/use-watch-change.ts | 17 ++++- .../pages/agent/form/components/output.tsx | 13 +++- 5 files changed, 84 insertions(+), 38 deletions(-) diff --git a/web/src/pages/agent/constant/index.tsx b/web/src/pages/agent/constant/index.tsx index 87217a999..bdd5a0763 100644 --- a/web/src/pages/agent/constant/index.tsx +++ b/web/src/pages/agent/constant/index.tsx @@ -463,21 +463,14 @@ export const initialAgentValues = { tools: [], mcp: [], cite: true, + showStructuredOutput: false, + [AgentStructuredOutputField]: {}, outputs: { - // structured_output: { - // topic: { - // type: 'string', - // description: - // 'default:general. The category of the search.news is useful for retrieving real-time updates, particularly about politics, sports, and major current events covered by mainstream media sources. general is for broader, more general-purpose searches that may include a wide range of sources.', - // enum: ['general', 'news'], - // default: 'general', - // }, - // }, content: { type: 'string', value: '', }, - [AgentStructuredOutputField]: {}, + // [AgentStructuredOutputField]: {}, }, }; diff --git a/web/src/pages/agent/form/agent-form/index.tsx b/web/src/pages/agent/form/agent-form/index.tsx index 12b713f93..b683a281f 100644 --- a/web/src/pages/agent/form/agent-form/index.tsx +++ b/web/src/pages/agent/form/agent-form/index.tsx @@ -6,6 +6,7 @@ import { import { LlmSettingSchema } from '@/components/llm-setting-items/next'; import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item'; import { SelectWithSearch } from '@/components/originui/select-with-search'; +import { RAGFlowFormItem } from '@/components/ragflow-form'; import { Button } from '@/components/ui/button'; import { Form, @@ -15,6 +16,7 @@ import { FormLabel, } from '@/components/ui/form'; import { Input, NumberInput } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; import { Separator } from '@/components/ui/separator'; import { Switch } from '@/components/ui/switch'; import { LlmModelType } from '@/constants/knowledge'; @@ -26,9 +28,9 @@ import { useTranslation } from 'react-i18next'; import { z } from 'zod'; import { AgentExceptionMethod, + AgentStructuredOutputField, NodeHandleId, VariableType, - initialAgentValues, } from '../../constant'; import { INextOperatorForm } from '../../interface'; import useGraphStore from '../../store'; @@ -71,18 +73,20 @@ const FormSchema = z.object({ exception_default_value: z.string().optional(), ...LargeModelFilterFormSchema, cite: z.boolean().optional(), + showStructuredOutput: z.boolean().optional(), + [AgentStructuredOutputField]: z.record(z.any()), }); export type AgentFormSchemaType = z.infer; -const outputList = buildOutputList(initialAgentValues.outputs); - function AgentForm({ node }: INextOperatorForm) { const { t } = useTranslation(); const { edges, deleteEdgesBySourceAndSourceHandle } = useGraphStore( (state) => state, ); + const outputList = buildOutputList(node?.data.form.outputs); + const defaultValues = useValues(node); const { extraOptions } = useBuildPromptExtraPromptOptions(edges, node?.id); @@ -112,13 +116,18 @@ function AgentForm({ node }: INextOperatorForm) { name: 'exception_method', }); + const showStructuredOutput = useWatch({ + control: form.control, + name: 'showStructuredOutput', + }); + const { initialStructuredOutput, showStructuredOutputDialog, structuredOutputDialogVisible, hideStructuredOutputDialog, handleStructuredOutputDialogOk, - } = useShowStructuredOutputDialog(node?.id); + } = useShowStructuredOutputDialog(form); useEffect(() => { if (exceptionMethod !== AgentExceptionMethod.Goto) { @@ -275,18 +284,42 @@ function AgentForm({ node }: INextOperatorForm) { )} - -
-
- {t('flow.structuredOutput.structuredOutput')} - -
- -
+ + + + + + {(field) => ( +
+ + +
+ )} +
+
+ {showStructuredOutput && ( +
+
+ {t('flow.structuredOutput.structuredOutput')} + +
+ + +
+ )} {structuredOutputDialogVisible && ( diff --git a/web/src/pages/agent/form/agent-form/use-show-structured-output-dialog.ts b/web/src/pages/agent/form/agent-form/use-show-structured-output-dialog.ts index 19e38cefe..3d492f724 100644 --- a/web/src/pages/agent/form/agent-form/use-show-structured-output-dialog.ts +++ b/web/src/pages/agent/form/agent-form/use-show-structured-output-dialog.ts @@ -1,27 +1,25 @@ import { JSONSchema } from '@/components/jsonjoy-builder'; +import { AgentStructuredOutputField } from '@/constants/agent'; import { useSetModalState } from '@/hooks/common-hooks'; import { useCallback } from 'react'; -import useGraphStore from '../../store'; +import { UseFormReturn } from 'react-hook-form'; -export function useShowStructuredOutputDialog(nodeId?: string) { +export function useShowStructuredOutputDialog(form: UseFormReturn) { const { visible: structuredOutputDialogVisible, showModal: showStructuredOutputDialog, hideModal: hideStructuredOutputDialog, } = useSetModalState(); - const { updateNodeForm, getNode } = useGraphStore((state) => state); - const initialStructuredOutput = getNode(nodeId)?.data.form.outputs.structured; + const initialStructuredOutput = form.getValues(AgentStructuredOutputField); const handleStructuredOutputDialogOk = useCallback( (values: JSONSchema) => { // Sync data to canvas - if (nodeId) { - updateNodeForm(nodeId, values, ['outputs', 'structured']); - } + form.setValue(AgentStructuredOutputField, values); hideStructuredOutputDialog(); }, - [hideStructuredOutputDialog, nodeId, updateNodeForm], + [form, hideStructuredOutputDialog], ); return { diff --git a/web/src/pages/agent/form/agent-form/use-watch-change.ts b/web/src/pages/agent/form/agent-form/use-watch-change.ts index 98b0ecf31..08011cc6d 100644 --- a/web/src/pages/agent/form/agent-form/use-watch-change.ts +++ b/web/src/pages/agent/form/agent-form/use-watch-change.ts @@ -1,6 +1,7 @@ +import { omit } from 'lodash'; import { useEffect } from 'react'; import { UseFormReturn, useWatch } from 'react-hook-form'; -import { PromptRole } from '../../constant'; +import { AgentStructuredOutputField, PromptRole } from '../../constant'; import useGraphStore from '../../store'; export function useWatchFormChange(id?: string, form?: UseFormReturn) { @@ -16,6 +17,20 @@ export function useWatchFormChange(id?: string, form?: UseFormReturn) { prompts: [{ role: PromptRole.User, content: values.prompts }], }; + if (values.showStructuredOutput) { + nextValues = { + ...nextValues, + outputs: { + ...values.outputs, + [AgentStructuredOutputField]: values[AgentStructuredOutputField], + }, + }; + } else { + nextValues = { + ...nextValues, + outputs: omit(values.outputs, [AgentStructuredOutputField]), + }; + } updateNodeForm(id, nextValues); } }, [form?.formState.isDirty, id, updateNodeForm, values]); diff --git a/web/src/pages/agent/form/components/output.tsx b/web/src/pages/agent/form/components/output.tsx index 68551ab3c..73058b67b 100644 --- a/web/src/pages/agent/form/components/output.tsx +++ b/web/src/pages/agent/form/components/output.tsx @@ -1,6 +1,7 @@ import { RAGFlowFormItem } from '@/components/ragflow-form'; import { Input } from '@/components/ui/input'; import { t } from 'i18next'; +import { PropsWithChildren } from 'react'; import { z } from 'zod'; export type OutputType = { @@ -11,7 +12,7 @@ export type OutputType = { type OutputProps = { list: Array; isFormRequired?: boolean; -}; +} & PropsWithChildren; export function transferOutputs(outputs: Record) { return Object.entries(outputs).map(([key, value]) => ({ @@ -24,10 +25,16 @@ export const OutputSchema = { outputs: z.record(z.any()), }; -export function Output({ list, isFormRequired = false }: OutputProps) { +export function Output({ + list, + isFormRequired = false, + children, +}: OutputProps) { return (
-
{t('flow.output')}
+
+ {t('flow.output')} {children} +
    {list.map((x, idx) => (