diff --git a/web/src/components/home-card.tsx b/web/src/components/home-card.tsx index 8782e3f00..c112c2ec2 100644 --- a/web/src/components/home-card.tsx +++ b/web/src/components/home-card.tsx @@ -13,8 +13,15 @@ interface IProps { onClick?: () => void; moreDropdown: React.ReactNode; sharedBadge?: ReactNode; + icon?: React.ReactNode; } -export function HomeCard({ data, onClick, moreDropdown, sharedBadge }: IProps) { +export function HomeCard({ + data, + onClick, + moreDropdown, + sharedBadge, + icon, +}: IProps) { return (
-
- {data.name} -
+
+
+ {data.name} +
+ {icon} +
{moreDropdown}
diff --git a/web/src/hooks/logic-hooks/navigate-hooks.ts b/web/src/hooks/logic-hooks/navigate-hooks.ts index 9aa0893f0..c86822edf 100644 --- a/web/src/hooks/logic-hooks/navigate-hooks.ts +++ b/web/src/hooks/logic-hooks/navigate-hooks.ts @@ -61,6 +61,13 @@ export const useNavigatePage = () => { [navigate], ); + const navigateToDataflow = useCallback( + (id: string) => () => { + navigate(`${Routes.DataFlow}/${id}`); + }, + [navigate], + ); + const navigateToAgentLogs = useCallback( (id: string) => () => { navigate(`${Routes.AgentLogPage}/${id}`); @@ -155,5 +162,6 @@ export const useNavigatePage = () => { navigateToAgentList, navigateToOldProfile, navigateToDataflowResult, + navigateToDataflow, }; }; diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index 17d5424ba..965f8cebf 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -271,6 +271,7 @@ export const useSetAgent = (showMessage: boolean = true) => { title?: string; dsl?: DSL; avatar?: string; + canvas_category?: string; }) => { const { data = {} } = await agentService.setCanvas(params); if (data.code === 0) { diff --git a/web/src/hooks/use-dataflow-request.ts b/web/src/hooks/use-dataflow-request.ts index ec01b764c..c6f8a57f1 100644 --- a/web/src/hooks/use-dataflow-request.ts +++ b/web/src/hooks/use-dataflow-request.ts @@ -1,6 +1,5 @@ import message from '@/components/ui/message'; import { IFlow } from '@/interfaces/database/agent'; -import { Operator } from '@/pages/data-flow/constant'; import dataflowService from '@/services/dataflow-service'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; @@ -14,41 +13,6 @@ export const enum DataflowApiAction { SetDataflow = 'setDataflow', } -export const EmptyDsl = { - graph: { - nodes: [ - { - id: Operator.Begin, - type: 'beginNode', - position: { - x: 50, - y: 200, - }, - data: { - label: 'Begin', - name: Operator.Begin, - }, - sourcePosition: 'left', - targetPosition: 'right', - }, - ], - edges: [], - }, - components: { - begin: { - obj: { - component_name: 'Begin', - params: {}, - }, - downstream: [], // other edge target is downstream, edge source is current node id - upstream: [], // edge source is upstream, edge target is current node id - }, - }, - retrieval: [], // reference - history: [], - path: [], -}; - export const useRemoveDataflow = () => { const queryClient = useQueryClient(); const { t } = useTranslation(); diff --git a/web/src/interfaces/database/agent.ts b/web/src/interfaces/database/agent.ts index 7994426ea..76947a92a 100644 --- a/web/src/interfaces/database/agent.ts +++ b/web/src/interfaces/database/agent.ts @@ -74,6 +74,7 @@ export declare interface IFlow { permission: string; nickname: string; operator_permission: number; + canvas_category: string; } export interface IFlowTemplate { diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx index ef7aed18a..e37f327c1 100644 --- a/web/src/pages/agent/constant.tsx +++ b/web/src/pages/agent/constant.tsx @@ -946,3 +946,8 @@ export enum AgentExceptionMethod { Comment = 'comment', Goto = 'goto', } + +export enum AgentCategory { + AgentCanvas = 'agent_canvas', + DataflowCanvas = 'dataflow_canvas', +} diff --git a/web/src/pages/agents/agent-card.tsx b/web/src/pages/agents/agent-card.tsx index e1aa20ac4..be20cc57b 100644 --- a/web/src/pages/agents/agent-card.tsx +++ b/web/src/pages/agents/agent-card.tsx @@ -1,8 +1,11 @@ import { HomeCard } from '@/components/home-card'; import { MoreButton } from '@/components/more-button'; import { SharedBadge } from '@/components/shared-badge'; +import { Button } from '@/components/ui/button'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { IFlow } from '@/interfaces/database/agent'; +import { DatabaseZap } from 'lucide-react'; +import { AgentCategory } from '../agent/constant'; import { AgentDropdown } from './agent-dropdown'; import { useRenameAgent } from './use-rename-agent'; @@ -11,7 +14,7 @@ export type DatasetCardProps = { } & Pick, 'showAgentRenameModal'>; export function AgentCard({ data, showAgentRenameModal }: DatasetCardProps) { - const { navigateToAgent } = useNavigatePage(); + const { navigateToAgent, navigateToDataflow } = useNavigatePage(); return ( } sharedBadge={{data.nickname}} - onClick={navigateToAgent(data?.id)} + onClick={ + data.canvas_category === AgentCategory.DataflowCanvas + ? navigateToDataflow(data.id) + : navigateToAgent(data?.id) + } + icon={ + data.canvas_category === AgentCategory.DataflowCanvas && ( + + ) + } /> ); } diff --git a/web/src/pages/agents/hooks/use-create-agent.ts b/web/src/pages/agents/hooks/use-create-agent.ts index 41b6ce424..01014335a 100644 --- a/web/src/pages/agents/hooks/use-create-agent.ts +++ b/web/src/pages/agents/hooks/use-create-agent.ts @@ -1,45 +1,39 @@ import { useSetModalState } from '@/hooks/common-hooks'; -import { useSetAgent } from '@/hooks/use-agent-request'; -import { EmptyDsl, useSetDataflow } from '@/hooks/use-dataflow-request'; +import { EmptyDsl, useSetAgent } from '@/hooks/use-agent-request'; +import { DSL } from '@/interfaces/database/agent'; +import { AgentCategory } from '@/pages/agent/constant'; import { useCallback } from 'react'; import { FlowType } from '../constant'; import { FormSchemaType } from '../create-agent-form'; export function useCreateAgentOrPipeline() { const { loading, setAgent } = useSetAgent(); - const { loading: dataflowLoading, setDataflow } = useSetDataflow(); const { visible: creatingVisible, hideModal: hideCreatingModal, showModal: showCreatingModal, } = useSetModalState(); - const createAgent = useCallback( - async (name: string) => { - return setAgent({ title: name, dsl: EmptyDsl }); - }, - [setAgent], - ); - const handleCreateAgentOrPipeline = useCallback( async (data: FormSchemaType) => { - if (data.type === FlowType.Agent) { - const ret = await createAgent(data.name); - if (ret.code === 0) { - hideCreatingModal(); - } - } else { - setDataflow({ - title: data.name, - dsl: EmptyDsl, - }); + const ret = await setAgent({ + title: data.name, + dsl: EmptyDsl as DSL, + canvas_category: + data.type === FlowType.Agent + ? AgentCategory.AgentCanvas + : AgentCategory.DataflowCanvas, + }); + + if (ret.code === 0) { + hideCreatingModal(); } }, - [createAgent, hideCreatingModal, setDataflow], + [hideCreatingModal, setAgent], ); return { - loading: loading || dataflowLoading, + loading: loading, creatingVisible, hideCreatingModal, showCreatingModal, diff --git a/web/src/pages/data-flow/canvas/node/dropdown/next-step-dropdown.tsx b/web/src/pages/data-flow/canvas/node/dropdown/next-step-dropdown.tsx index 2a2d9ab96..9f5259593 100644 --- a/web/src/pages/data-flow/canvas/node/dropdown/next-step-dropdown.tsx +++ b/web/src/pages/data-flow/canvas/node/dropdown/next-step-dropdown.tsx @@ -1,9 +1,3 @@ -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from '@/components/ui/accordion'; import { DropdownMenu, DropdownMenuContent, @@ -124,84 +118,16 @@ function AccordionOperators({ mousePosition?: { x: number; y: number }; }) { return ( - - - - {t('flow.foundation')} - - - - - - - - {t('flow.dialog')} - - - - - - - - {t('flow.flow')} - - - - - - - - {t('flow.dataManipulation')} - - - - - - - - {t('flow.tools')} - - - - - - + ); } diff --git a/web/src/pages/data-flow/constant.tsx b/web/src/pages/data-flow/constant.tsx index 30a153f38..df83d8429 100644 --- a/web/src/pages/data-flow/constant.tsx +++ b/web/src/pages/data-flow/constant.tsx @@ -307,7 +307,11 @@ export const initialWaitingDialogueValues = {}; export const initialChunkerValues = { outputs: {} }; -export const initialTokenizerValues = {}; +export const initialTokenizerValues = { + search_method: [], + filename_embd_weight: 0.1, + outputs: {}, +}; export const initialAgentValues = { ...initialLlmBaseValues, @@ -401,42 +405,10 @@ export const CategorizeAnchorPointPositions = [ // no connection lines are allowed between key and value export const RestrictedUpstreamMap = { [Operator.Begin]: [Operator.Relevant], - [Operator.Categorize]: [Operator.Begin, Operator.Categorize], - [Operator.Retrieval]: [Operator.Begin, Operator.Retrieval], - [Operator.Message]: [ - Operator.Begin, - Operator.Message, - Operator.Retrieval, - Operator.RewriteQuestion, - Operator.Categorize, - ], - [Operator.Relevant]: [Operator.Begin], - [Operator.RewriteQuestion]: [ - Operator.Begin, - Operator.Message, - Operator.RewriteQuestion, - Operator.Relevant, - ], - [Operator.KeywordExtract]: [ - Operator.Begin, - Operator.Message, - Operator.Relevant, - ], - [Operator.ExeSQL]: [Operator.Begin], - [Operator.Switch]: [Operator.Begin], - [Operator.Concentrator]: [Operator.Begin], - [Operator.Crawler]: [Operator.Begin], - [Operator.Note]: [], - [Operator.Invoke]: [Operator.Begin], - [Operator.Email]: [Operator.Begin], - [Operator.Iteration]: [Operator.Begin], - [Operator.IterationStart]: [Operator.Begin], - [Operator.Code]: [Operator.Begin], - [Operator.WaitingDialogue]: [Operator.Begin], - [Operator.Agent]: [Operator.Begin], - [Operator.StringTransform]: [Operator.Begin], - [Operator.UserFillUp]: [Operator.Begin], - [Operator.Tool]: [Operator.Begin], + [Operator.Parser]: [Operator.Begin], + [Operator.Splitter]: [Operator.Begin], + [Operator.HierarchicalMerger]: [Operator.Begin], + [Operator.Tokenizer]: [Operator.Begin], }; export const NodeMap = { @@ -529,3 +501,8 @@ export enum FileType { Video = 'video', Audio = 'audio', } + +export enum TokenizerSearchMethod { + Embedding = 'embedding', + FullText = 'full_text', +} diff --git a/web/src/pages/data-flow/form/hierarchical-merger-form/index.tsx b/web/src/pages/data-flow/form/hierarchical-merger-form/index.tsx index 8f92026ad..0a8405320 100644 --- a/web/src/pages/data-flow/form/hierarchical-merger-form/index.tsx +++ b/web/src/pages/data-flow/form/hierarchical-merger-form/index.tsx @@ -44,6 +44,8 @@ export const FormSchema = z.object({ ), }); +export type HierarchicalMergerFormSchemaType = z.infer; + type RegularExpressionsProps = { index: number; parentName: string; @@ -113,7 +115,7 @@ export function RegularExpressions({ const HierarchicalMergerForm = ({ node }: INextOperatorForm) => { const defaultValues = useFormValues(initialHierarchicalMergerValues, node); - const form = useForm>({ + const form = useForm({ defaultValues, resolver: zodResolver(FormSchema), }); diff --git a/web/src/pages/data-flow/form/parser-form/index.tsx b/web/src/pages/data-flow/form/parser-form/index.tsx index 9e199f9da..dafb5f0eb 100644 --- a/web/src/pages/data-flow/form/parser-form/index.tsx +++ b/web/src/pages/data-flow/form/parser-form/index.tsx @@ -62,11 +62,11 @@ export const FormSchema = z.object({ ), }); -export type FormSchemaType = z.infer; +export type ParserFormSchemaType = z.infer; function ParserItem({ name, index, fieldLength, remove }: ParserItemProps) { const { t } = useTranslation(); - const form = useFormContext(); + const form = useFormContext(); const ref = useRef(null); const isHovering = useHover(ref); diff --git a/web/src/pages/data-flow/form/splitter-form/index.tsx b/web/src/pages/data-flow/form/splitter-form/index.tsx index 83be6c559..9c000aeeb 100644 --- a/web/src/pages/data-flow/form/splitter-form/index.tsx +++ b/web/src/pages/data-flow/form/splitter-form/index.tsx @@ -29,10 +29,12 @@ export const FormSchema = z.object({ overlapped_percent: z.number(), // 0.0 - 0.3 }); +export type SplitterFormSchemaType = z.infer; + const SplitterForm = ({ node }: INextOperatorForm) => { const defaultValues = useFormValues(initialChunkerValues, node); - const form = useForm>({ + const form = useForm({ defaultValues, resolver: zodResolver(FormSchema), }); diff --git a/web/src/pages/data-flow/form/tokenizer-form/index.tsx b/web/src/pages/data-flow/form/tokenizer-form/index.tsx index b48581390..4efc7c2a3 100644 --- a/web/src/pages/data-flow/form/tokenizer-form/index.tsx +++ b/web/src/pages/data-flow/form/tokenizer-form/index.tsx @@ -1,94 +1,38 @@ -import { FormContainer } from '@/components/form-container'; -import NumberInput from '@/components/originui/number-input'; -import { SelectWithSearch } from '@/components/originui/select-with-search'; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@/components/ui/form'; -import { useTranslate } from '@/hooks/common-hooks'; +import { RAGFlowFormItem } from '@/components/ragflow-form'; +import { SliderInputFormField } from '@/components/slider-input-form-field'; +import { Form } from '@/components/ui/form'; +import { MultiSelect } from '@/components/ui/multi-select'; +import { buildOptions } from '@/utils/form'; import { zodResolver } from '@hookform/resolvers/zod'; import { memo } from 'react'; -import { useForm, useFormContext } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; import { z } from 'zod'; -import { initialChunkerValues } from '../../constant'; +import { initialTokenizerValues, TokenizerSearchMethod } from '../../constant'; import { useFormValues } from '../../hooks/use-form-values'; import { useWatchFormChange } from '../../hooks/use-watch-form-change'; import { INextOperatorForm } from '../../interface'; -import { GoogleCountryOptions, GoogleLanguageOptions } from '../../options'; import { buildOutputList } from '../../utils/build-output-list'; -import { ApiKeyField } from '../components/api-key-field'; import { FormWrapper } from '../components/form-wrapper'; import { Output } from '../components/output'; -import { QueryVariable } from '../components/query-variable'; -const outputList = buildOutputList(initialChunkerValues.outputs); - -export const GoogleFormPartialSchema = { - api_key: z.string(), - country: z.string(), - language: z.string(), -}; +const outputList = buildOutputList(initialTokenizerValues.outputs); export const FormSchema = z.object({ - ...GoogleFormPartialSchema, - q: z.string(), - start: z.number(), - num: z.number(), + search_method: z.array(z.string()).min(1), + filename_embd_weight: z.number(), }); -export function GoogleFormWidgets() { - const form = useFormContext(); - const { t } = useTranslate('flow'); - - return ( - <> - ( - - {t('country')} - - - - - - )} - /> - ( - - {t('language')} - - - - - - )} - /> - - ); -} +const SearchMethodOptions = buildOptions(TokenizerSearchMethod); const TokenizerForm = ({ node }: INextOperatorForm) => { - const { t } = useTranslate('flow'); - const defaultValues = useFormValues(initialChunkerValues, node); + const { t } = useTranslation(); + const defaultValues = useFormValues(initialTokenizerValues, node); const form = useForm>({ defaultValues, resolver: zodResolver(FormSchema), + mode: 'onChange', }); useWatchFormChange(node?.id, form); @@ -96,39 +40,22 @@ const TokenizerForm = ({ node }: INextOperatorForm) => { return (
- - - - - - ( - - {t('flowStart')} - - - - - - )} - /> - ( - - {t('flowNum')} - - - - - - )} - /> - - + + {(field) => ( + + )} + +
diff --git a/web/src/pages/data-flow/hooks/use-save-graph.ts b/web/src/pages/data-flow/hooks/use-save-graph.ts index 8ce4dc15e..f0c85a1c4 100644 --- a/web/src/pages/data-flow/hooks/use-save-graph.ts +++ b/web/src/pages/data-flow/hooks/use-save-graph.ts @@ -22,6 +22,7 @@ export const useSaveGraph = (showMessage: boolean = true) => { return setAgent({ id, title: data.title, + canvas_category: data.canvas_category, dsl: buildDslData(currentNodes), }); }, diff --git a/web/src/pages/data-flow/utils.ts b/web/src/pages/data-flow/utils.ts index c7d60dfd2..49bd18e2e 100644 --- a/web/src/pages/data-flow/utils.ts +++ b/web/src/pages/data-flow/utils.ts @@ -9,7 +9,15 @@ import { removeUselessFieldsFromValues } from '@/utils/form'; import { Edge, Node, XYPosition } from '@xyflow/react'; import { FormInstance, FormListFieldData } from 'antd'; import { humanId } from 'human-id'; -import { curry, get, intersectionWith, isEqual, omit, sample } from 'lodash'; +import { + curry, + get, + intersectionWith, + isEmpty, + isEqual, + omit, + sample, +} from 'lodash'; import pipe from 'lodash/fp/pipe'; import isObject from 'lodash/isObject'; import { @@ -18,6 +26,9 @@ import { NodeHandleId, Operator, } from './constant'; +import { HierarchicalMergerFormSchemaType } from './form/hierarchical-merger-form'; +import { ParserFormSchemaType } from './form/parser-form'; +import { SplitterFormSchemaType } from './form/splitter-form'; import { BeginQuery, IPosition } from './interface'; function buildAgentExceptionGoto(edges: Edge[], nodeId: string) { @@ -63,10 +74,7 @@ const buildComponentDownstreamOrUpstream = ( const removeUselessDataInTheOperator = curry( (operatorName: string, params: Record) => { - if ( - operatorName === Operator.Generate || - operatorName === Operator.Categorize - ) { + if (operatorName === Operator.Categorize) { return removeUselessFieldsFromValues(params, ''); } return params; @@ -151,6 +159,44 @@ export function isBottomSubAgent(edges: Edge[], nodeId?: string) { ); return !!edge; } +// Because the array of react-hook-form must be object data, +// it needs to be converted into a simple data type array required by the backend +function transformObjectArrayToPureArray( + list: Array>, + field: string, +) { + return Array.isArray(list) + ? list.filter((x) => !isEmpty(x[field])).map((y) => y[field]) + : []; +} + +function transformParserParams(params: ParserFormSchemaType) { + return params.parser.reduce< + Record + >((pre, cur) => { + if (cur.fileFormat) { + pre[cur.fileFormat] = omit(cur, 'fileFormat'); + } + return pre; + }, {}); +} + +function transformSplitterParams(params: SplitterFormSchemaType) { + return { + ...params, + delimiters: transformObjectArrayToPureArray(params.delimiters, 'value'), + }; +} + +function transformHierarchicalMergerParams( + params: HierarchicalMergerFormSchemaType, +) { + const levels = params.levels.map((x) => + transformObjectArrayToPureArray(x.expressions, 'expression'), + ); + + return { ...params, hierarchy: Number(params.hierarchy), levels }; +} // construct a dsl based on the node information of the graph export const buildDslComponentsByGraph = ( @@ -184,6 +230,18 @@ export const buildDslComponentsByGraph = ( params = buildCategorize(edges, nodes, id); break; + case Operator.Parser: + params = transformParserParams(params); + break; + + case Operator.Splitter: + params = transformSplitterParams(params); + break; + + case Operator.HierarchicalMerger: + params = transformHierarchicalMergerParams(params); + break; + default: break; } diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index 1fe0e73d0..c5e8fbefc 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -138,7 +138,7 @@ export default { // flow listTemplates: `${api_host}/canvas/templates`, listCanvas: `${api_host}/canvas/list`, - listCanvasTeam: `${api_host}/canvas/listteam`, + listCanvasTeam: `${api_host}/canvas/list`, getCanvas: `${api_host}/canvas/get`, getCanvasSSE: `${api_host}/canvas/getsse`, removeCanvas: `${api_host}/canvas/rm`,