From 0879b6af2c53590eb2fe676c94f9097fe44f6879 Mon Sep 17 00:00:00 2001 From: balibabu Date: Mon, 10 Nov 2025 15:09:33 +0800 Subject: [PATCH] Feat: Globally defined conversation variables can be selected in the operator's query variables. #10427 (#11135) ### What problem does this PR solve? Feat: Globally defined conversation variables can be selected in the operator's query variables. #10427 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- .../schema-editor/add-field-button.tsx | 5 +- .../schema-editor/schema-property-editor.tsx | 5 +- web/src/components/key-input.tsx | 23 +++++++ web/src/interfaces/database/agent.ts | 4 +- web/src/locales/en.ts | 2 +- web/src/locales/zh.ts | 2 +- .../pages/agent/form/components/key-input.tsx | 25 ------- .../data-operations-form/filter-values.tsx | 2 +- .../form/data-operations-form/updates.tsx | 2 +- .../agent/gobal-variable-sheet/index.tsx | 10 +-- web/src/pages/agent/hooks/use-build-dsl.ts | 4 +- .../pages/agent/hooks/use-get-begin-query.tsx | 65 +++++++++++++++---- web/src/pages/agent/hooks/use-save-graph.ts | 4 +- web/src/pages/agent/index.tsx | 2 +- web/src/pages/agent/utils.ts | 4 +- 15 files changed, 101 insertions(+), 58 deletions(-) create mode 100644 web/src/components/key-input.tsx delete mode 100644 web/src/pages/agent/form/components/key-input.tsx diff --git a/web/src/components/jsonjoy-builder/components/schema-editor/add-field-button.tsx b/web/src/components/jsonjoy-builder/components/schema-editor/add-field-button.tsx index b890fcec4..7a25705f9 100644 --- a/web/src/components/jsonjoy-builder/components/schema-editor/add-field-button.tsx +++ b/web/src/components/jsonjoy-builder/components/schema-editor/add-field-button.tsx @@ -1,3 +1,4 @@ +import { KeyInput } from '@/components/key-input'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { @@ -112,10 +113,10 @@ const AddFieldButton: FC = ({ - setFieldName(e.target.value)} + onChange={setFieldName} placeholder={t.fieldNamePlaceholder} className="font-mono text-sm w-full" required diff --git a/web/src/components/jsonjoy-builder/components/schema-editor/schema-property-editor.tsx b/web/src/components/jsonjoy-builder/components/schema-editor/schema-property-editor.tsx index c821ab840..f95031e9c 100644 --- a/web/src/components/jsonjoy-builder/components/schema-editor/schema-property-editor.tsx +++ b/web/src/components/jsonjoy-builder/components/schema-editor/schema-property-editor.tsx @@ -1,3 +1,4 @@ +import { KeyInput } from '@/components/key-input'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { ChevronDown, ChevronRight, X } from 'lucide-react'; @@ -114,9 +115,9 @@ export const SchemaPropertyEditor: React.FC = ({
{isEditingName ? ( - setTempName(e.target.value)} + onChange={setTempName} onBlur={handleNameSubmit} onKeyDown={(e) => e.key === 'Enter' && handleNameSubmit()} className="h-8 text-sm font-medium min-w-[120px] max-w-full z-10" diff --git a/web/src/components/key-input.tsx b/web/src/components/key-input.tsx new file mode 100644 index 000000000..4c6c2f822 --- /dev/null +++ b/web/src/components/key-input.tsx @@ -0,0 +1,23 @@ +import { Input, InputProps } from '@/components/ui/input'; +import { ChangeEvent, forwardRef, useCallback } from 'react'; + +type KeyInputProps = { + value?: string; + onChange?: (value: string) => void; + searchValue?: string | RegExp; +} & Omit; + +export const KeyInput = forwardRef( + function KeyInput({ value, onChange, searchValue = /[^a-zA-Z0-9_]/g }, ref) { + const handleChange = useCallback( + (e: ChangeEvent) => { + const value = e.target.value ?? ''; + const filteredValue = value.replace(searchValue, ''); + onChange?.(filteredValue); + }, + [onChange, searchValue], + ); + + return ; + }, +); diff --git a/web/src/interfaces/database/agent.ts b/web/src/interfaces/database/agent.ts index 1733b4e74..f1a658fef 100644 --- a/web/src/interfaces/database/agent.ts +++ b/web/src/interfaces/database/agent.ts @@ -45,7 +45,7 @@ export interface DSL { messages?: Message[]; reference?: IReference[]; globals: Record; - variables: Record; + variables: Record; retrieval: IReference[]; } @@ -285,7 +285,7 @@ export interface IPipeLineListRequest { canvas_category?: AgentCategory; } -export interface GobalVariableType { +export interface GlobalVariableType { name: string; value: any; description: string; diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 49541b286..f7c4aa982 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -985,7 +985,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s 'Variable name can only contain letters and underscores', variableDescription: 'Variable Description', defaultValue: 'Default Value', - gobalVariable: 'Global Variable', + conversationVariable: 'Conversation variable', recommended: 'Recommended', customerSupport: 'Customer Support', marketing: 'Marketing', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 1615544eb..1b9b46b87 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -933,7 +933,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 variableNameMessage: '名称只能包含字母和下划线', variableDescription: '变量的描述', defaultValue: '默认值', - gobalVariable: '全局变量', + conversationVariable: '会话变量', recommended: '推荐', customerSupport: '客户支持', marketing: '营销', diff --git a/web/src/pages/agent/form/components/key-input.tsx b/web/src/pages/agent/form/components/key-input.tsx deleted file mode 100644 index da780f08e..000000000 --- a/web/src/pages/agent/form/components/key-input.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Input } from '@/components/ui/input'; -import { ChangeEvent, useCallback } from 'react'; - -type KeyInputProps = { - value?: string; - onChange?: (value: string) => void; - searchValue?: string | RegExp; -}; - -export function KeyInput({ - value, - onChange, - searchValue = /[^a-zA-Z0-9_]/g, -}: KeyInputProps) { - const handleChange = useCallback( - (e: ChangeEvent) => { - const value = e.target.value ?? ''; - const filteredValue = value.replace(searchValue, ''); - onChange?.(filteredValue); - }, - [onChange, searchValue], - ); - - return ; -} diff --git a/web/src/pages/agent/form/data-operations-form/filter-values.tsx b/web/src/pages/agent/form/data-operations-form/filter-values.tsx index ae167e57d..4cd4f4299 100644 --- a/web/src/pages/agent/form/data-operations-form/filter-values.tsx +++ b/web/src/pages/agent/form/data-operations-form/filter-values.tsx @@ -1,3 +1,4 @@ +import { KeyInput } from '@/components/key-input'; import { SelectWithSearch } from '@/components/originui/select-with-search'; import { RAGFlowFormItem } from '@/components/ragflow-form'; import { Button } from '@/components/ui/button'; @@ -8,7 +9,6 @@ import { ReactNode } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; import { DataOperationsOperatorOptions } from '../../constant'; import { DynamicFormHeader } from '../components/dynamic-fom-header'; -import { KeyInput } from '../components/key-input'; import { PromptEditor } from '../components/prompt-editor'; type SelectKeysProps = { diff --git a/web/src/pages/agent/form/data-operations-form/updates.tsx b/web/src/pages/agent/form/data-operations-form/updates.tsx index 886b7507b..07a200aa6 100644 --- a/web/src/pages/agent/form/data-operations-form/updates.tsx +++ b/web/src/pages/agent/form/data-operations-form/updates.tsx @@ -1,3 +1,4 @@ +import { KeyInput } from '@/components/key-input'; import { RAGFlowFormItem } from '@/components/ragflow-form'; import { Button } from '@/components/ui/button'; import { Separator } from '@/components/ui/separator'; @@ -5,7 +6,6 @@ import { X } from 'lucide-react'; import { ReactNode } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; import { DynamicFormHeader } from '../components/dynamic-fom-header'; -import { KeyInput } from '../components/key-input'; import { PromptEditor } from '../components/prompt-editor'; type SelectKeysProps = { diff --git a/web/src/pages/agent/gobal-variable-sheet/index.tsx b/web/src/pages/agent/gobal-variable-sheet/index.tsx index dbea2721f..f56a75d1b 100644 --- a/web/src/pages/agent/gobal-variable-sheet/index.tsx +++ b/web/src/pages/agent/gobal-variable-sheet/index.tsx @@ -14,7 +14,7 @@ import { } from '@/components/ui/sheet'; import { useSetModalState } from '@/hooks/common-hooks'; import { useFetchAgent } from '@/hooks/use-agent-request'; -import { GobalVariableType } from '@/interfaces/database/agent'; +import { GlobalVariableType } from '@/interfaces/database/agent'; import { cn } from '@/lib/utils'; import { t } from 'i18next'; import { Trash2 } from 'lucide-react'; @@ -85,7 +85,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => { const param = { ...(data.dsl?.variables || {}), [value.name]: value, - } as Record; + } as Record; const res = await saveGraph(undefined, { gobalVariables: param, @@ -100,7 +100,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => { const handleDeleteGobalVariable = async (key: string) => { const param = { ...(data.dsl?.variables || {}), - } as Record; + } as Record; delete param[key]; const res = await saveGraph(undefined, { gobalVariables: param, @@ -124,7 +124,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => { > - {t('flow.gobalVariable')} + {t('flow.conversationVariable')} @@ -185,7 +185,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
{ const buildDslData = useCallback( ( currentNodes?: RAGFlowNodeType[], - otherParam?: { gobalVariables: Record }, + otherParam?: { gobalVariables: Record }, ) => { const nodesToProcess = currentNodes ?? nodes; diff --git a/web/src/pages/agent/hooks/use-get-begin-query.tsx b/web/src/pages/agent/hooks/use-get-begin-query.tsx index 56e3216e6..7f1958cd1 100644 --- a/web/src/pages/agent/hooks/use-get-begin-query.tsx +++ b/web/src/pages/agent/hooks/use-get-begin-query.tsx @@ -6,6 +6,7 @@ import { DefaultOptionType } from 'antd/es/select'; import { t } from 'i18next'; import { isEmpty, toLower } from 'lodash'; import get from 'lodash/get'; +import { MessageCircleCode } from 'lucide-react'; import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { AgentDialogueMode, @@ -141,6 +142,38 @@ export function useBuildBeginVariableOptions() { return options; } +const Env = 'env.'; + +export function useBuildConversationVariableOptions() { + const { data } = useFetchAgent(); + + const conversationVariables = useMemo( + () => data?.dsl?.variables ?? {}, + [data?.dsl?.variables], + ); + + const options = useMemo(() => { + return [ + { + label: {t('flow.conversationVariable')}, + title: t('flow.conversationVariable'), + options: Object.entries(conversationVariables).map(([key, value]) => { + const keyWithPrefix = `${Env}${key}`; + return { + label: keyWithPrefix, + parentLabel: {t('flow.conversationVariable')}, + icon: , + value: keyWithPrefix, + type: value.type, + }; + }), + }, + ]; + }, [conversationVariables]); + + return options; +} + export const useBuildVariableOptions = (nodeId?: string, parentId?: string) => { const nodeOutputOptions = useBuildNodeOutputOptions(nodeId); const parentNodeOutputOptions = useBuildNodeOutputOptions(parentId); @@ -157,22 +190,32 @@ export function useBuildQueryVariableOptions(n?: RAGFlowNodeType) { const { data } = useFetchAgent(); const node = useContext(AgentFormContext) || n; const options = useBuildVariableOptions(node?.id, node?.parentId); + + const conversationOptions = useBuildConversationVariableOptions(); + const nextOptions = useMemo(() => { const globals = data?.dsl?.globals ?? {}; - const globalOptions = Object.entries(globals).map(([key, value]) => ({ - label: key, - value: key, - icon: , - parentLabel: {t('flow.beginInput')}, - type: Array.isArray(value) - ? `${VariableType.Array}${key === AgentGlobals.SysFiles ? '' : ''}` - : typeof value, - })); + const globalOptions = Object.entries(globals) + .filter(([key]) => !key.startsWith(Env)) + .map(([key, value]) => ({ + label: key, + value: key, + icon: , + parentLabel: {t('flow.beginInput')}, + type: Array.isArray(value) + ? `${VariableType.Array}${key === AgentGlobals.SysFiles ? '' : ''}` + : typeof value, + })); + return [ - { ...options[0], options: [...options[0]?.options, ...globalOptions] }, + { + ...options[0], + options: [...options[0]?.options, ...globalOptions], + }, ...options.slice(1), + ...conversationOptions, ]; - }, [data.dsl?.globals, options]); + }, [conversationOptions, data?.dsl?.globals, options]); return nextOptions; } diff --git a/web/src/pages/agent/hooks/use-save-graph.ts b/web/src/pages/agent/hooks/use-save-graph.ts index fc5bf0c0a..e59b99193 100644 --- a/web/src/pages/agent/hooks/use-save-graph.ts +++ b/web/src/pages/agent/hooks/use-save-graph.ts @@ -3,7 +3,7 @@ import { useResetAgent, useSetAgent, } from '@/hooks/use-agent-request'; -import { GobalVariableType } from '@/interfaces/database/agent'; +import { GlobalVariableType } from '@/interfaces/database/agent'; import { RAGFlowNodeType } from '@/interfaces/database/flow'; import { formatDate } from '@/utils/date'; import { useDebounceEffect } from 'ahooks'; @@ -21,7 +21,7 @@ export const useSaveGraph = (showMessage: boolean = true) => { const saveGraph = useCallback( async ( currentNodes?: RAGFlowNodeType[], - otherParam?: { gobalVariables: Record }, + otherParam?: { gobalVariables: Record }, ) => { return setAgent({ id, diff --git a/web/src/pages/agent/index.tsx b/web/src/pages/agent/index.tsx index e8c1155ed..83a5def54 100644 --- a/web/src/pages/agent/index.tsx +++ b/web/src/pages/agent/index.tsx @@ -218,7 +218,7 @@ export default function Agent() { onClick={() => showGobalParamSheet()} loading={loading} > - {t('flow.gobalVariable')} + {t('flow.conversationVariable')}