diff --git a/web/src/components/next-markdown-content/index.tsx b/web/src/components/next-markdown-content/index.tsx index 605dd8b25..3555f0225 100644 --- a/web/src/components/next-markdown-content/index.tsx +++ b/web/src/components/next-markdown-content/index.tsx @@ -235,7 +235,7 @@ function MarkdownContent({ - + {renderPopoverContent(chunkIndex)} diff --git a/web/src/interfaces/database/flow.ts b/web/src/interfaces/database/flow.ts index 95968114e..a17d21558 100644 --- a/web/src/interfaces/database/flow.ts +++ b/web/src/interfaces/database/flow.ts @@ -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; } diff --git a/web/src/pages/agent/form/components/prompt-editor/index.tsx b/web/src/pages/agent/form/components/prompt-editor/index.tsx index 9659f02c1..caf6914b4 100644 --- a/web/src/pages/agent/form/components/prompt-editor/index.tsx +++ b/web/src/pages/agent/form/components/prompt-editor/index.tsx @@ -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(); diff --git a/web/src/pages/agent/form/components/prompt-editor/variable-node.tsx b/web/src/pages/agent/form/components/prompt-editor/variable-node.tsx index d8947727b..177c370c9 100644 --- a/web/src/pages/agent/form/components/prompt-editor/variable-node.tsx +++ b/web/src/pages/agent/form/components/prompt-editor/variable-node.tsx @@ -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 { __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 { decorate(): ReactNode { let content: ReactNode = ( - {this.__label} +
{this.__label}
); - if (this.__value?.startsWith(prefix)) { + if (this.__parentLabel) { content = ( -
- {i18n.t(`flow.begin`)} / {content} +
+
{this.__icon}
+
{this.__parentLabel}
+
/
+ {content}
); } return ( -
+
{content}
); @@ -59,8 +78,10 @@ export class VariableNode extends DecoratorNode { 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( diff --git a/web/src/pages/agent/form/components/prompt-editor/variable-picker-plugin.tsx b/web/src/pages/agent/form/components/prompt-editor/variable-picker-plugin.tsx index 6f0ca8e67..f429981c7 100644 --- a/web/src/pages/agent/form/components/prompt-editor/variable-picker-plugin.tsx +++ b/web/src/pages/agent/form/components/prompt-editor/variable-picker-plugin.tsx @@ -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>( - (pre, cur) => { - return pre.concat(cur.options); - }, - [], - ); + 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( - (selectedOption as VariableInnerOption).value, - selectedOption.label as string, - ), - ]); + 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(() => { 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 baecbaf27..83cda2078 100644 --- a/web/src/pages/agent/hooks/use-get-begin-query.tsx +++ b/web/src/pages/agent/hooks/use-get-begin-query.tsx @@ -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 = {}, 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, + , + ), })); }, [edges, nodeId, nodes]); @@ -162,9 +179,11 @@ export function useBuildBeginVariableOptions() { return [ { label: {t('flow.beginInput')}, - title: 'Begin Input', + title: t('flow.beginInput'), options: inputs.map((x) => ({ label: x.name, + parentLabel: {t('flow.beginInput')}, + icon: , 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: , + parentLabel: {t('flow.beginInput')}, type: Array.isArray(value) ? `${VariableType.Array}${key === AgentGlobals.SysFiles ? '' : ''}` : typeof value, diff --git a/web/src/pages/agent/operator-icon.tsx b/web/src/pages/agent/operator-icon.tsx index 52261f068..24e1bfd70 100644 --- a/web/src/pages/agent/operator-icon.tsx +++ b/web/src/pages/agent/operator-icon.tsx @@ -64,7 +64,12 @@ const OperatorIcon = ({ name, className }: IProps) => { if (name === Operator.Begin) { return ( -
+
); diff --git a/web/src/pages/agent/store.ts b/web/src/pages/agent/store.ts index 46745f995..e163ff8cb 100644 --- a/web/src/pages/agent/store.ts +++ b/web/src/pages/agent/store.ts @@ -204,6 +204,7 @@ const useGraphStore = create()( 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) => { diff --git a/web/src/pages/agents/template-card.tsx b/web/src/pages/agents/template-card.tsx index 610fda299..3a59e40c8 100644 --- a/web/src/pages/agents/template-card.tsx +++ b/web/src/pages/agents/template-card.tsx @@ -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 ( @@ -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'} > -
{data.title}
+
+ {data?.title[language]} +
-

{data.description}

+

{data?.description[language]}