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)
This commit is contained in:
balibabu
2025-11-10 15:09:33 +08:00
committed by GitHub
parent 2b9145948f
commit 0879b6af2c
15 changed files with 101 additions and 58 deletions

View File

@ -1,3 +1,4 @@
import { KeyInput } from '@/components/key-input';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import {
@ -112,10 +113,10 @@ const AddFieldButton: FC<AddFieldButtonProps> = ({
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
</div> </div>
<Input <KeyInput
id={fieldNameId} id={fieldNameId}
value={fieldName} value={fieldName}
onChange={(e) => setFieldName(e.target.value)} onChange={setFieldName}
placeholder={t.fieldNamePlaceholder} placeholder={t.fieldNamePlaceholder}
className="font-mono text-sm w-full" className="font-mono text-sm w-full"
required required

View File

@ -1,3 +1,4 @@
import { KeyInput } from '@/components/key-input';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { ChevronDown, ChevronRight, X } from 'lucide-react'; import { ChevronDown, ChevronRight, X } from 'lucide-react';
@ -114,9 +115,9 @@ export const SchemaPropertyEditor: React.FC<SchemaPropertyEditorProps> = ({
<div className="flex items-center gap-2 grow min-w-0 overflow-visible"> <div className="flex items-center gap-2 grow min-w-0 overflow-visible">
<div className="flex items-center gap-2 min-w-0 grow overflow-visible"> <div className="flex items-center gap-2 min-w-0 grow overflow-visible">
{isEditingName ? ( {isEditingName ? (
<Input <KeyInput
value={tempName} value={tempName}
onChange={(e) => setTempName(e.target.value)} onChange={setTempName}
onBlur={handleNameSubmit} onBlur={handleNameSubmit}
onKeyDown={(e) => e.key === 'Enter' && handleNameSubmit()} onKeyDown={(e) => e.key === 'Enter' && handleNameSubmit()}
className="h-8 text-sm font-medium min-w-[120px] max-w-full z-10" className="h-8 text-sm font-medium min-w-[120px] max-w-full z-10"

View File

@ -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<InputProps, 'onChange'>;
export const KeyInput = forwardRef<HTMLInputElement, KeyInputProps>(
function KeyInput({ value, onChange, searchValue = /[^a-zA-Z0-9_]/g }, ref) {
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value ?? '';
const filteredValue = value.replace(searchValue, '');
onChange?.(filteredValue);
},
[onChange, searchValue],
);
return <Input value={value} onChange={handleChange} ref={ref} />;
},
);

View File

@ -45,7 +45,7 @@ export interface DSL {
messages?: Message[]; messages?: Message[];
reference?: IReference[]; reference?: IReference[];
globals: Record<string, any>; globals: Record<string, any>;
variables: Record<string, GobalVariableType>; variables: Record<string, GlobalVariableType>;
retrieval: IReference[]; retrieval: IReference[];
} }
@ -285,7 +285,7 @@ export interface IPipeLineListRequest {
canvas_category?: AgentCategory; canvas_category?: AgentCategory;
} }
export interface GobalVariableType { export interface GlobalVariableType {
name: string; name: string;
value: any; value: any;
description: string; description: string;

View File

@ -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', 'Variable name can only contain letters and underscores',
variableDescription: 'Variable Description', variableDescription: 'Variable Description',
defaultValue: 'Default Value', defaultValue: 'Default Value',
gobalVariable: 'Global Variable', conversationVariable: 'Conversation variable',
recommended: 'Recommended', recommended: 'Recommended',
customerSupport: 'Customer Support', customerSupport: 'Customer Support',
marketing: 'Marketing', marketing: 'Marketing',

View File

@ -933,7 +933,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
variableNameMessage: '名称只能包含字母和下划线', variableNameMessage: '名称只能包含字母和下划线',
variableDescription: '变量的描述', variableDescription: '变量的描述',
defaultValue: '默认值', defaultValue: '默认值',
gobalVariable: '全局变量', conversationVariable: '会话变量',
recommended: '推荐', recommended: '推荐',
customerSupport: '客户支持', customerSupport: '客户支持',
marketing: '营销', marketing: '营销',

View File

@ -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<HTMLInputElement>) => {
const value = e.target.value ?? '';
const filteredValue = value.replace(searchValue, '');
onChange?.(filteredValue);
},
[onChange, searchValue],
);
return <Input value={value} onChange={handleChange} />;
}

View File

@ -1,3 +1,4 @@
import { KeyInput } from '@/components/key-input';
import { SelectWithSearch } from '@/components/originui/select-with-search'; import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form'; import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -8,7 +9,6 @@ import { ReactNode } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form'; import { useFieldArray, useFormContext } from 'react-hook-form';
import { DataOperationsOperatorOptions } from '../../constant'; import { DataOperationsOperatorOptions } from '../../constant';
import { DynamicFormHeader } from '../components/dynamic-fom-header'; import { DynamicFormHeader } from '../components/dynamic-fom-header';
import { KeyInput } from '../components/key-input';
import { PromptEditor } from '../components/prompt-editor'; import { PromptEditor } from '../components/prompt-editor';
type SelectKeysProps = { type SelectKeysProps = {

View File

@ -1,3 +1,4 @@
import { KeyInput } from '@/components/key-input';
import { RAGFlowFormItem } from '@/components/ragflow-form'; import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
@ -5,7 +6,6 @@ import { X } from 'lucide-react';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form'; import { useFieldArray, useFormContext } from 'react-hook-form';
import { DynamicFormHeader } from '../components/dynamic-fom-header'; import { DynamicFormHeader } from '../components/dynamic-fom-header';
import { KeyInput } from '../components/key-input';
import { PromptEditor } from '../components/prompt-editor'; import { PromptEditor } from '../components/prompt-editor';
type SelectKeysProps = { type SelectKeysProps = {

View File

@ -14,7 +14,7 @@ import {
} from '@/components/ui/sheet'; } from '@/components/ui/sheet';
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { useFetchAgent } from '@/hooks/use-agent-request'; 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 { cn } from '@/lib/utils';
import { t } from 'i18next'; import { t } from 'i18next';
import { Trash2 } from 'lucide-react'; import { Trash2 } from 'lucide-react';
@ -85,7 +85,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
const param = { const param = {
...(data.dsl?.variables || {}), ...(data.dsl?.variables || {}),
[value.name]: value, [value.name]: value,
} as Record<string, GobalVariableType>; } as Record<string, GlobalVariableType>;
const res = await saveGraph(undefined, { const res = await saveGraph(undefined, {
gobalVariables: param, gobalVariables: param,
@ -100,7 +100,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
const handleDeleteGobalVariable = async (key: string) => { const handleDeleteGobalVariable = async (key: string) => {
const param = { const param = {
...(data.dsl?.variables || {}), ...(data.dsl?.variables || {}),
} as Record<string, GobalVariableType>; } as Record<string, GlobalVariableType>;
delete param[key]; delete param[key];
const res = await saveGraph(undefined, { const res = await saveGraph(undefined, {
gobalVariables: param, gobalVariables: param,
@ -124,7 +124,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
> >
<SheetHeader className="p-5"> <SheetHeader className="p-5">
<SheetTitle className="flex items-center gap-2.5"> <SheetTitle className="flex items-center gap-2.5">
{t('flow.gobalVariable')} {t('flow.conversationVariable')}
</SheetTitle> </SheetTitle>
</SheetHeader> </SheetHeader>
@ -185,7 +185,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
</div> </div>
</SheetContent> </SheetContent>
<Modal <Modal
title={t('flow.add') + t('flow.gobalVariable')} title={t('flow.add') + t('flow.conversationVariable')}
open={visible} open={visible}
onCancel={hideAddModal} onCancel={hideAddModal}
showfooter={false} showfooter={false}

View File

@ -1,5 +1,5 @@
import { useFetchAgent } from '@/hooks/use-agent-request'; import { useFetchAgent } from '@/hooks/use-agent-request';
import { GobalVariableType } from '@/interfaces/database/agent'; import { GlobalVariableType } from '@/interfaces/database/agent';
import { RAGFlowNodeType } from '@/interfaces/database/flow'; import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { Operator } from '../constant'; import { Operator } from '../constant';
@ -13,7 +13,7 @@ export const useBuildDslData = () => {
const buildDslData = useCallback( const buildDslData = useCallback(
( (
currentNodes?: RAGFlowNodeType[], currentNodes?: RAGFlowNodeType[],
otherParam?: { gobalVariables: Record<string, GobalVariableType> }, otherParam?: { gobalVariables: Record<string, GlobalVariableType> },
) => { ) => {
const nodesToProcess = currentNodes ?? nodes; const nodesToProcess = currentNodes ?? nodes;

View File

@ -6,6 +6,7 @@ import { DefaultOptionType } from 'antd/es/select';
import { t } from 'i18next'; import { t } from 'i18next';
import { isEmpty, toLower } from 'lodash'; import { isEmpty, toLower } from 'lodash';
import get from 'lodash/get'; import get from 'lodash/get';
import { MessageCircleCode } from 'lucide-react';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { import {
AgentDialogueMode, AgentDialogueMode,
@ -141,6 +142,38 @@ export function useBuildBeginVariableOptions() {
return options; 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: <span>{t('flow.conversationVariable')}</span>,
title: t('flow.conversationVariable'),
options: Object.entries(conversationVariables).map(([key, value]) => {
const keyWithPrefix = `${Env}${key}`;
return {
label: keyWithPrefix,
parentLabel: <span>{t('flow.conversationVariable')}</span>,
icon: <MessageCircleCode className="size-3" />,
value: keyWithPrefix,
type: value.type,
};
}),
},
];
}, [conversationVariables]);
return options;
}
export const useBuildVariableOptions = (nodeId?: string, parentId?: string) => { export const useBuildVariableOptions = (nodeId?: string, parentId?: string) => {
const nodeOutputOptions = useBuildNodeOutputOptions(nodeId); const nodeOutputOptions = useBuildNodeOutputOptions(nodeId);
const parentNodeOutputOptions = useBuildNodeOutputOptions(parentId); const parentNodeOutputOptions = useBuildNodeOutputOptions(parentId);
@ -157,22 +190,32 @@ export function useBuildQueryVariableOptions(n?: RAGFlowNodeType) {
const { data } = useFetchAgent(); const { data } = useFetchAgent();
const node = useContext(AgentFormContext) || n; const node = useContext(AgentFormContext) || n;
const options = useBuildVariableOptions(node?.id, node?.parentId); const options = useBuildVariableOptions(node?.id, node?.parentId);
const conversationOptions = useBuildConversationVariableOptions();
const nextOptions = useMemo(() => { const nextOptions = useMemo(() => {
const globals = data?.dsl?.globals ?? {}; const globals = data?.dsl?.globals ?? {};
const globalOptions = Object.entries(globals).map(([key, value]) => ({ const globalOptions = Object.entries(globals)
label: key, .filter(([key]) => !key.startsWith(Env))
value: key, .map(([key, value]) => ({
icon: <OperatorIcon name={Operator.Begin} className="block" />, label: key,
parentLabel: <span>{t('flow.beginInput')}</span>, value: key,
type: Array.isArray(value) icon: <OperatorIcon name={Operator.Begin} className="block" />,
? `${VariableType.Array}${key === AgentGlobals.SysFiles ? '<file>' : ''}` parentLabel: <span>{t('flow.beginInput')}</span>,
: typeof value, type: Array.isArray(value)
})); ? `${VariableType.Array}${key === AgentGlobals.SysFiles ? '<file>' : ''}`
: typeof value,
}));
return [ return [
{ ...options[0], options: [...options[0]?.options, ...globalOptions] }, {
...options[0],
options: [...options[0]?.options, ...globalOptions],
},
...options.slice(1), ...options.slice(1),
...conversationOptions,
]; ];
}, [data.dsl?.globals, options]); }, [conversationOptions, data?.dsl?.globals, options]);
return nextOptions; return nextOptions;
} }

View File

@ -3,7 +3,7 @@ import {
useResetAgent, useResetAgent,
useSetAgent, useSetAgent,
} from '@/hooks/use-agent-request'; } from '@/hooks/use-agent-request';
import { GobalVariableType } from '@/interfaces/database/agent'; import { GlobalVariableType } from '@/interfaces/database/agent';
import { RAGFlowNodeType } from '@/interfaces/database/flow'; import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { useDebounceEffect } from 'ahooks'; import { useDebounceEffect } from 'ahooks';
@ -21,7 +21,7 @@ export const useSaveGraph = (showMessage: boolean = true) => {
const saveGraph = useCallback( const saveGraph = useCallback(
async ( async (
currentNodes?: RAGFlowNodeType[], currentNodes?: RAGFlowNodeType[],
otherParam?: { gobalVariables: Record<string, GobalVariableType> }, otherParam?: { gobalVariables: Record<string, GlobalVariableType> },
) => { ) => {
return setAgent({ return setAgent({
id, id,

View File

@ -218,7 +218,7 @@ export default function Agent() {
onClick={() => showGobalParamSheet()} onClick={() => showGobalParamSheet()}
loading={loading} loading={loading}
> >
{t('flow.gobalVariable')} {t('flow.conversationVariable')}
</ButtonLoading> </ButtonLoading>
<Button variant={'secondary'} onClick={handleButtonRunClick}> <Button variant={'secondary'} onClick={handleButtonRunClick}>
<CirclePlay /> <CirclePlay />

View File

@ -1,6 +1,6 @@
import { import {
DSL, DSL,
GobalVariableType, GlobalVariableType,
IAgentForm, IAgentForm,
ICategorizeForm, ICategorizeForm,
ICategorizeItem, ICategorizeItem,
@ -350,7 +350,7 @@ export const buildDslComponentsByGraph = (
export const buildDslGobalVariables = ( export const buildDslGobalVariables = (
dsl: DSL, dsl: DSL,
gobalVariables?: Record<string, GobalVariableType>, gobalVariables?: Record<string, GlobalVariableType>,
) => { ) => {
if (!gobalVariables) { if (!gobalVariables) {
return { globals: dsl.globals, variables: dsl.variables || {} }; return { globals: dsl.globals, variables: dsl.variables || {} };