mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Fix: Optimized variable node display and Agent template multi-language support #3221 - Modified the VariableNode component to add parent label and icon properties - Updated the VariablePickerMenuPlugin to support displaying parent labels and icons - Adjusted useBuildNodeOutputOptions and useBuildBeginVariableOptions to pass new properties - Optimized the Agent TemplateCard component to switch the title and description based on the language ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
@ -235,7 +235,7 @@ function MarkdownContent({
|
||||
<HoverCardTrigger>
|
||||
<CircleAlert className="size-4 inline-block" />
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent>
|
||||
<HoverCardContent className="max-w-3xl">
|
||||
{renderPopoverContent(chunkIndex)}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
|
||||
@ -48,10 +48,16 @@ export interface IFlowTemplate {
|
||||
canvas_type: string;
|
||||
create_date: string;
|
||||
create_time: number;
|
||||
description: string;
|
||||
description: {
|
||||
en: string;
|
||||
zh: string;
|
||||
};
|
||||
dsl: DSL;
|
||||
id: string;
|
||||
title: string;
|
||||
title: {
|
||||
en: string;
|
||||
zh: string;
|
||||
};
|
||||
update_date: string;
|
||||
update_time: number;
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import { HeadingNode, QuoteNode } from '@lexical/rich-text';
|
||||
import {
|
||||
$getRoot,
|
||||
$getSelection,
|
||||
$nodesOfType,
|
||||
EditorState,
|
||||
Klass,
|
||||
LexicalNode,
|
||||
@ -135,9 +134,8 @@ export function PromptEditor({
|
||||
const onValueChange = useCallback(
|
||||
(editorState: EditorState) => {
|
||||
editorState?.read(() => {
|
||||
const listNodes = $nodesOfType(VariableNode); // to be removed
|
||||
// const listNodes = $nodesOfType(VariableNode); // to be removed
|
||||
// const allNodes = $dfs();
|
||||
console.log('🚀 ~ onChange ~ allNodes:', listNodes);
|
||||
|
||||
const text = $getRoot().getTextContent();
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import i18n from '@/locales/config';
|
||||
import { BeginId } from '@/pages/flow/constant';
|
||||
import { DecoratorNode, LexicalNode, NodeKey } from 'lexical';
|
||||
import { ReactNode } from 'react';
|
||||
@ -7,19 +6,36 @@ const prefix = BeginId + '@';
|
||||
export class VariableNode extends DecoratorNode<ReactNode> {
|
||||
__value: string;
|
||||
__label: string;
|
||||
key?: NodeKey;
|
||||
__parentLabel?: string | ReactNode;
|
||||
__icon?: ReactNode;
|
||||
|
||||
static getType(): string {
|
||||
return 'variable';
|
||||
}
|
||||
|
||||
static clone(node: VariableNode): VariableNode {
|
||||
return new VariableNode(node.__value, node.__label, node.__key);
|
||||
return new VariableNode(
|
||||
node.__value,
|
||||
node.__label,
|
||||
node.__key,
|
||||
node.__parentLabel,
|
||||
node.__icon,
|
||||
);
|
||||
}
|
||||
|
||||
constructor(value: string, label: string, key?: NodeKey) {
|
||||
constructor(
|
||||
value: string,
|
||||
label: string,
|
||||
key?: NodeKey,
|
||||
parent?: string | ReactNode,
|
||||
icon?: ReactNode,
|
||||
) {
|
||||
super(key);
|
||||
this.__value = value;
|
||||
this.__label = label;
|
||||
this.__parentLabel = parent;
|
||||
this.__icon = icon;
|
||||
}
|
||||
|
||||
createDOM(): HTMLElement {
|
||||
@ -35,17 +51,20 @@ export class VariableNode extends DecoratorNode<ReactNode> {
|
||||
|
||||
decorate(): ReactNode {
|
||||
let content: ReactNode = (
|
||||
<span className="text-blue-600">{this.__label}</span>
|
||||
<div className="text-blue-600">{this.__label}</div>
|
||||
);
|
||||
if (this.__value?.startsWith(prefix)) {
|
||||
if (this.__parentLabel) {
|
||||
content = (
|
||||
<div>
|
||||
<span>{i18n.t(`flow.begin`)}</span> / {content}
|
||||
<div className="flex items-center gap-1 text-text-primary ">
|
||||
<div>{this.__icon}</div>
|
||||
<div>{this.__parentLabel}</div>
|
||||
<div className="text-text-disabled mr-1">/</div>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="bg-gray-200 dark:bg-gray-400 text-primary inline-flex items-center rounded-md px-2 py-0">
|
||||
<div className="bg-gray-200 dark:bg-gray-400 text-sm inline-flex items-center rounded-md px-2 py-1">
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
@ -59,8 +78,10 @@ export class VariableNode extends DecoratorNode<ReactNode> {
|
||||
export function $createVariableNode(
|
||||
value: string,
|
||||
label: string,
|
||||
parentLabel: string | ReactNode,
|
||||
icon?: ReactNode,
|
||||
): VariableNode {
|
||||
return new VariableNode(value, label);
|
||||
return new VariableNode(value, label, undefined, parentLabel, icon);
|
||||
}
|
||||
|
||||
export function $isVariableNode(
|
||||
|
||||
@ -20,7 +20,13 @@ import {
|
||||
$isRangeSelection,
|
||||
TextNode,
|
||||
} from 'lexical';
|
||||
import React, { ReactElement, useCallback, useEffect, useRef } from 'react';
|
||||
import React, {
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import { $createVariableNode } from './variable-node';
|
||||
@ -31,11 +37,20 @@ import './index.css';
|
||||
class VariableInnerOption extends MenuOption {
|
||||
label: string;
|
||||
value: string;
|
||||
parentLabel: string | JSX.Element;
|
||||
icon?: ReactNode;
|
||||
|
||||
constructor(label: string, value: string) {
|
||||
constructor(
|
||||
label: string,
|
||||
value: string,
|
||||
parentLabel: string | JSX.Element,
|
||||
icon?: ReactNode,
|
||||
) {
|
||||
super(value);
|
||||
this.label = label;
|
||||
this.value = value;
|
||||
this.parentLabel = parentLabel;
|
||||
this.icon = icon;
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +126,6 @@ export default function VariablePickerMenuPlugin({
|
||||
|
||||
const buildNextOptions = useCallback(() => {
|
||||
let filteredOptions = options;
|
||||
|
||||
if (queryString) {
|
||||
const lowerQuery = queryString.toLowerCase();
|
||||
filteredOptions = options
|
||||
@ -131,23 +145,28 @@ export default function VariablePickerMenuPlugin({
|
||||
new VariableOption(
|
||||
x.label,
|
||||
x.title,
|
||||
x.options.map((y) => new VariableInnerOption(y.label, y.value)),
|
||||
x.options.map((y) => {
|
||||
return new VariableInnerOption(y.label, y.value, x.label, y.icon);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return nextOptions;
|
||||
}, [options, queryString]);
|
||||
|
||||
const findLabelByValue = useCallback(
|
||||
const findItemByValue = useCallback(
|
||||
(value: string) => {
|
||||
const children = options.reduce<Array<{ label: string; value: string }>>(
|
||||
(pre, cur) => {
|
||||
const children = options.reduce<
|
||||
Array<{
|
||||
label: string;
|
||||
value: string;
|
||||
parentLabel?: string | ReactNode;
|
||||
icon?: ReactNode;
|
||||
}>
|
||||
>((pre, cur) => {
|
||||
return pre.concat(cur.options);
|
||||
},
|
||||
[],
|
||||
);
|
||||
}, []);
|
||||
|
||||
return children.find((x) => x.value === value)?.label;
|
||||
return children.find((x) => x.value === value);
|
||||
},
|
||||
[options],
|
||||
);
|
||||
@ -168,13 +187,13 @@ export default function VariablePickerMenuPlugin({
|
||||
if (nodeToRemove) {
|
||||
nodeToRemove.remove();
|
||||
}
|
||||
|
||||
selection.insertNodes([
|
||||
$createVariableNode(
|
||||
const variableNode = $createVariableNode(
|
||||
(selectedOption as VariableInnerOption).value,
|
||||
selectedOption.label as string,
|
||||
),
|
||||
]);
|
||||
selectedOption.parentLabel as string | ReactNode,
|
||||
selectedOption.icon as ReactNode,
|
||||
);
|
||||
selection.insertNodes([variableNode]);
|
||||
|
||||
closeMenu();
|
||||
});
|
||||
@ -190,7 +209,6 @@ export default function VariablePickerMenuPlugin({
|
||||
const regex = /{([^}]*)}/g;
|
||||
let match;
|
||||
let lastIndex = 0;
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
const { 1: content, index, 0: template } = match;
|
||||
|
||||
@ -202,9 +220,17 @@ export default function VariablePickerMenuPlugin({
|
||||
}
|
||||
|
||||
// Add variable node or text node
|
||||
const label = findLabelByValue(content);
|
||||
if (label) {
|
||||
paragraph.append($createVariableNode(content, label));
|
||||
const nodeItem = findItemByValue(content);
|
||||
|
||||
if (nodeItem) {
|
||||
paragraph.append(
|
||||
$createVariableNode(
|
||||
content,
|
||||
nodeItem.label,
|
||||
nodeItem.parentLabel,
|
||||
nodeItem.icon,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
paragraph.append($createTextNode(template));
|
||||
}
|
||||
@ -225,7 +251,7 @@ export default function VariablePickerMenuPlugin({
|
||||
$getRoot().selectEnd();
|
||||
}
|
||||
},
|
||||
[findLabelByValue],
|
||||
[findItemByValue],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -6,7 +6,14 @@ import { DefaultOptionType } from 'antd/es/select';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty } from 'lodash';
|
||||
import get from 'lodash/get';
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
AgentDialogueMode,
|
||||
BeginId,
|
||||
@ -17,6 +24,7 @@ import {
|
||||
import { AgentFormContext } from '../context';
|
||||
import { buildBeginInputListFromObject } from '../form/begin-form/utils';
|
||||
import { BeginQuery } from '../interface';
|
||||
import OperatorIcon from '../operator-icon';
|
||||
import useGraphStore from '../store';
|
||||
|
||||
export function useSelectBeginNodeDataInputs() {
|
||||
@ -98,10 +106,14 @@ function filterAllUpstreamNodeIds(edges: Edge[], nodeIds: string[]) {
|
||||
export function buildOutputOptions(
|
||||
outputs: Record<string, any> = {},
|
||||
nodeId?: string,
|
||||
parentLabel?: string | ReactNode,
|
||||
icon?: ReactNode,
|
||||
) {
|
||||
return Object.keys(outputs).map((x) => ({
|
||||
label: x,
|
||||
value: `${nodeId}@${x}`,
|
||||
parentLabel,
|
||||
icon,
|
||||
type: outputs[x]?.type,
|
||||
}));
|
||||
}
|
||||
@ -127,7 +139,12 @@ export function useBuildNodeOutputOptions(nodeId?: string) {
|
||||
label: x.data.name,
|
||||
value: x.id,
|
||||
title: x.data.name,
|
||||
options: buildOutputOptions(x.data.form.outputs, x.id),
|
||||
options: buildOutputOptions(
|
||||
x.data.form.outputs,
|
||||
x.id,
|
||||
x.data.name,
|
||||
<OperatorIcon name={x.data.label as Operator} />,
|
||||
),
|
||||
}));
|
||||
}, [edges, nodeId, nodes]);
|
||||
|
||||
@ -162,9 +179,11 @@ export function useBuildBeginVariableOptions() {
|
||||
return [
|
||||
{
|
||||
label: <span>{t('flow.beginInput')}</span>,
|
||||
title: 'Begin Input',
|
||||
title: t('flow.beginInput'),
|
||||
options: inputs.map((x) => ({
|
||||
label: x.name,
|
||||
parentLabel: <span>{t('flow.beginInput')}</span>,
|
||||
icon: <OperatorIcon name={Operator.Begin} className="block" />,
|
||||
value: `begin@${x.key}`,
|
||||
type: transferToVariableType(x.type),
|
||||
})),
|
||||
@ -191,12 +210,13 @@ export function useBuildQueryVariableOptions(n?: RAGFlowNodeType) {
|
||||
const { data } = useFetchAgent();
|
||||
const node = useContext(AgentFormContext) || n;
|
||||
const options = useBuildVariableOptions(node?.id, node?.parentId);
|
||||
|
||||
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,
|
||||
|
||||
@ -64,7 +64,12 @@ const OperatorIcon = ({ name, className }: IProps) => {
|
||||
|
||||
if (name === Operator.Begin) {
|
||||
return (
|
||||
<div className="inline-block p-1 bg-accent-primary rounded-sm">
|
||||
<div
|
||||
className={cn(
|
||||
'inline-block p-1 bg-accent-primary rounded-sm',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<HousePlus className="rounded size-3" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -204,6 +204,7 @@ const useGraphStore = create<RFState>()(
|
||||
set({ nodes: nextNodes });
|
||||
},
|
||||
getNode: (id?: string | null) => {
|
||||
// console.log('getNode', id, get().nodes);
|
||||
return get().nodes.find((x) => x.id === id);
|
||||
},
|
||||
getOperatorTypeFromId: (id?: string | null) => {
|
||||
|
||||
@ -2,10 +2,10 @@ import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { IFlowTemplate } from '@/interfaces/database/flow';
|
||||
import i18n from '@/locales/config';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface IProps {
|
||||
data: IFlowTemplate;
|
||||
isCreate?: boolean;
|
||||
@ -18,6 +18,11 @@ export function TemplateCard({ data, showModal, isCreate = false }: IProps) {
|
||||
const handleClick = useCallback(() => {
|
||||
showModal(data);
|
||||
}, [data, showModal]);
|
||||
|
||||
const language = useMemo(() => {
|
||||
return i18n.language || 'en';
|
||||
}, []) as 'en' | 'zh';
|
||||
|
||||
return (
|
||||
<Card className="border-colors-outline-neutral-standard group relative min-h-40">
|
||||
<CardContent className="p-4 ">
|
||||
@ -38,11 +43,13 @@ export function TemplateCard({ data, showModal, isCreate = false }: IProps) {
|
||||
avatar={
|
||||
data.avatar ? data.avatar : 'https://github.com/shadcn.png'
|
||||
}
|
||||
name={data?.title || 'CN'}
|
||||
name={data?.title[language] || 'CN'}
|
||||
></RAGFlowAvatar>
|
||||
<div className="text-[18px] font-bold ">{data.title}</div>
|
||||
<div className="text-[18px] font-bold ">
|
||||
{data?.title[language]}
|
||||
</div>
|
||||
<p className="break-words">{data.description}</p>
|
||||
</div>
|
||||
<p className="break-words">{data?.description[language]}</p>
|
||||
<div className="group-hover:bg-gradient-to-t from-black/70 from-10% via-black/0 via-50% to-black/0 w-full h-full group-hover:block absolute top-0 left-0 hidden rounded-xl">
|
||||
<Button
|
||||
variant="default"
|
||||
|
||||
Reference in New Issue
Block a user