mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
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:
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
23
web/src/components/key-input.tsx
Normal file
23
web/src/components/key-input.tsx
Normal 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} />;
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -933,7 +933,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
variableNameMessage: '名称只能包含字母和下划线',
|
variableNameMessage: '名称只能包含字母和下划线',
|
||||||
variableDescription: '变量的描述',
|
variableDescription: '变量的描述',
|
||||||
defaultValue: '默认值',
|
defaultValue: '默认值',
|
||||||
gobalVariable: '全局变量',
|
conversationVariable: '会话变量',
|
||||||
recommended: '推荐',
|
recommended: '推荐',
|
||||||
customerSupport: '客户支持',
|
customerSupport: '客户支持',
|
||||||
marketing: '营销',
|
marketing: '营销',
|
||||||
|
|||||||
@ -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} />;
|
|
||||||
}
|
|
||||||
@ -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 = {
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 />
|
||||||
|
|||||||
@ -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 || {} };
|
||||||
|
|||||||
Reference in New Issue
Block a user