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

View File

@ -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<SchemaPropertyEditorProps> = ({
<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">
{isEditingName ? (
<Input
<KeyInput
value={tempName}
onChange={(e) => 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"

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[];
reference?: IReference[];
globals: Record<string, any>;
variables: Record<string, GobalVariableType>;
variables: Record<string, GlobalVariableType>;
retrieval: IReference[];
}
@ -285,7 +285,7 @@ export interface IPipeLineListRequest {
canvas_category?: AgentCategory;
}
export interface GobalVariableType {
export interface GlobalVariableType {
name: string;
value: any;
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',
variableDescription: 'Variable Description',
defaultValue: 'Default Value',
gobalVariable: 'Global Variable',
conversationVariable: 'Conversation variable',
recommended: 'Recommended',
customerSupport: 'Customer Support',
marketing: 'Marketing',

View File

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

View File

@ -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 = {

View File

@ -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<string, GobalVariableType>;
} as Record<string, GlobalVariableType>;
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<string, GobalVariableType>;
} as Record<string, GlobalVariableType>;
delete param[key];
const res = await saveGraph(undefined, {
gobalVariables: param,
@ -124,7 +124,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
>
<SheetHeader className="p-5">
<SheetTitle className="flex items-center gap-2.5">
{t('flow.gobalVariable')}
{t('flow.conversationVariable')}
</SheetTitle>
</SheetHeader>
@ -185,7 +185,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
</div>
</SheetContent>
<Modal
title={t('flow.add') + t('flow.gobalVariable')}
title={t('flow.add') + t('flow.conversationVariable')}
open={visible}
onCancel={hideAddModal}
showfooter={false}

View File

@ -1,5 +1,5 @@
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 { useCallback } from 'react';
import { Operator } from '../constant';
@ -13,7 +13,7 @@ export const useBuildDslData = () => {
const buildDslData = useCallback(
(
currentNodes?: RAGFlowNodeType[],
otherParam?: { gobalVariables: Record<string, GobalVariableType> },
otherParam?: { gobalVariables: Record<string, GlobalVariableType> },
) => {
const nodesToProcess = currentNodes ?? nodes;

View File

@ -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: <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) => {
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: <OperatorIcon name={Operator.Begin} className="block" />,
parentLabel: <span>{t('flow.beginInput')}</span>,
type: Array.isArray(value)
? `${VariableType.Array}${key === AgentGlobals.SysFiles ? '<file>' : ''}`
: typeof value,
}));
const globalOptions = Object.entries(globals)
.filter(([key]) => !key.startsWith(Env))
.map(([key, value]) => ({
label: key,
value: key,
icon: <OperatorIcon name={Operator.Begin} className="block" />,
parentLabel: <span>{t('flow.beginInput')}</span>,
type: Array.isArray(value)
? `${VariableType.Array}${key === AgentGlobals.SysFiles ? '<file>' : ''}`
: 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;
}

View File

@ -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<string, GobalVariableType> },
otherParam?: { gobalVariables: Record<string, GlobalVariableType> },
) => {
return setAgent({
id,

View File

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

View File

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