From d60348572ed79cc5cdbb03a224ab7938c6976e18 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Mon, 29 Dec 2025 14:55:26 +0800 Subject: [PATCH] feat: llm node support tools --- .../nodes/_base/components/layout/box.tsx | 3 + .../_base/components/layout/field-title.tsx | 4 +- .../nodes/_base/components/layout/group.tsx | 3 + .../nodes/_base/hooks/use-node-crud.ts | 26 +++++++++ .../nodes/llm/components/tools/index.tsx | 58 ++++++++++++++----- .../llm/components/tools/max-iterations.tsx | 40 +++++++++++++ .../llm/components/tools/use-node-tools.ts | 23 ++++++++ .../components/workflow/nodes/llm/panel.tsx | 7 +++ .../components/workflow/nodes/llm/types.ts | 15 +++++ 9 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 web/app/components/workflow/nodes/llm/components/tools/max-iterations.tsx create mode 100644 web/app/components/workflow/nodes/llm/components/tools/use-node-tools.ts diff --git a/web/app/components/workflow/nodes/_base/components/layout/box.tsx b/web/app/components/workflow/nodes/_base/components/layout/box.tsx index 62e709efc6..fbff9366fe 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/box.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/box.tsx @@ -6,17 +6,20 @@ export type BoxProps = { className?: string children?: ReactNode withBorderBottom?: boolean + withBorderTop?: boolean } export const Box = memo(({ className, children, withBorderBottom, + withBorderTop, }: BoxProps) => { return (
diff --git a/web/app/components/workflow/nodes/_base/components/layout/field-title.tsx b/web/app/components/workflow/nodes/_base/components/layout/field-title.tsx index e5e8fe950d..2e581a0b9b 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/field-title.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/field-title.tsx @@ -9,6 +9,7 @@ import { cn } from '@/utils/classnames' export type FieldTitleProps = { title?: string + className?: string operation?: ReactNode subTitle?: string | ReactNode tooltip?: string @@ -19,6 +20,7 @@ export type FieldTitleProps = { } export const FieldTitle = memo(({ title, + className, operation, subTitle, tooltip, @@ -31,7 +33,7 @@ export const FieldTitle = memo(({ const collapsedMerged = collapsed !== undefined ? collapsed : collapsedLocal return ( -
+
{ diff --git a/web/app/components/workflow/nodes/_base/components/layout/group.tsx b/web/app/components/workflow/nodes/_base/components/layout/group.tsx index 6e35cb7b69..7cca898b44 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/group.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/group.tsx @@ -6,17 +6,20 @@ export type GroupProps = { className?: string children?: ReactNode withBorderBottom?: boolean + withBorderTop?: boolean } export const Group = memo(({ className, children, withBorderBottom, + withBorderTop, }: GroupProps) => { return (
diff --git a/web/app/components/workflow/nodes/_base/hooks/use-node-crud.ts b/web/app/components/workflow/nodes/_base/hooks/use-node-crud.ts index d1741f0bbb..cb3a898387 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-node-crud.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-node-crud.ts @@ -1,4 +1,6 @@ import type { CommonNodeType } from '@/app/components/workflow/types' +import { useCallback } from 'react' +import { useStoreApi } from 'reactflow' import { useNodeDataUpdate } from '@/app/components/workflow/hooks' const useNodeCrud = (id: string, data: CommonNodeType) => { @@ -18,3 +20,27 @@ const useNodeCrud = (id: string, data: CommonNodeType) => { } export default useNodeCrud + +export const useNodeCurdKit = (id: string) => { + const store = useStoreApi() + const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate() + + const getNodeData = useCallback(() => { + const { getNodes } = store.getState() + const nodes = getNodes() + + return nodes.find(node => node.id === id) + }, [store, id]) + + const handleNodeDataUpdate = useCallback((data: Partial>) => { + handleNodeDataUpdateWithSyncDraft({ + id, + data, + }) + }, [id, handleNodeDataUpdateWithSyncDraft]) + + return { + getNodeData, + handleNodeDataUpdate, + } +} diff --git a/web/app/components/workflow/nodes/llm/components/tools/index.tsx b/web/app/components/workflow/nodes/llm/components/tools/index.tsx index 4b6fc3960d..a6e5fefc75 100644 --- a/web/app/components/workflow/nodes/llm/components/tools/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/tools/index.tsx @@ -1,28 +1,54 @@ +import type { ToolValue } from '@/app/components/workflow/block-selector/types' import { memo } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' -import Field from '@/app/components/workflow/nodes/_base/components/field' +import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector' +import { BoxGroup } from '@/app/components/workflow/nodes/_base/components/layout' +import MaxIterations from './max-iterations' +import { useNodeTools } from './use-node-tools' const i18nPrefix = 'workflow.nodes.llm' -const Tools = () => { +type ToolsProps = { + nodeId: string + tools?: ToolValue[] + maxIterations?: number +} +const Tools = ({ + nodeId, + tools = [], + maxIterations = 10, +}: ToolsProps) => { const { t } = useTranslation() + const { + handleToolsChange, + handleMaxIterationsChange, + } = useNodeTools(nodeId) return ( - - - )} + -
-
Tools
-
-
+ + + ) } diff --git a/web/app/components/workflow/nodes/llm/components/tools/max-iterations.tsx b/web/app/components/workflow/nodes/llm/components/tools/max-iterations.tsx new file mode 100644 index 0000000000..3f1a4b7130 --- /dev/null +++ b/web/app/components/workflow/nodes/llm/components/tools/max-iterations.tsx @@ -0,0 +1,40 @@ +import { memo } from 'react' +import { InputNumber } from '@/app/components/base/input-number' +import Slider from '@/app/components/base/slider' +import Tooltip from '@/app/components/base/tooltip' + +type MaxIterationsProps = { + value?: number + onChange?: (value: number) => void +} +const MaxIterations = ({ value = 10, onChange }: MaxIterationsProps) => { + return ( +
+
Max Iterations
+ +
+ {})} + min={1} + max={99} + step={1} + /> +
+ {})} + min={1} + max={99} + step={1} + /> +
+ ) +} + +export default memo(MaxIterations) diff --git a/web/app/components/workflow/nodes/llm/components/tools/use-node-tools.ts b/web/app/components/workflow/nodes/llm/components/tools/use-node-tools.ts new file mode 100644 index 0000000000..37b2e8f252 --- /dev/null +++ b/web/app/components/workflow/nodes/llm/components/tools/use-node-tools.ts @@ -0,0 +1,23 @@ +import type { LLMNodeType } from '../../types' +import type { ToolValue } from '@/app/components/workflow/block-selector/types' +import { useNodeCurdKit } from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' + +export const useNodeTools = (nodeId: string) => { + const { handleNodeDataUpdate } = useNodeCurdKit(nodeId) + + const handleToolsChange = (tools: ToolValue[]) => { + handleNodeDataUpdate({ + tools, + }) + } + const handleMaxIterationsChange = (maxIterations: number) => { + handleNodeDataUpdate({ + max_iterations: maxIterations, + }) + } + + return { + handleToolsChange, + handleMaxIterationsChange, + } +} diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index fd20b1a2bb..0f9d51cf5b 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -22,6 +22,7 @@ import VarReferencePicker from '../_base/components/variable/var-reference-picke import ConfigPrompt from './components/config-prompt' import ReasoningFormatConfig from './components/reasoning-format-config' import StructureOutput from './components/structure-output' +import Tools from './components/tools' import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.llm' @@ -233,6 +234,12 @@ const Panel: FC> = ({ )} + + {/* Vision: GPT4-vision and so on */} + settings?: Record + extra?: Record +} + export type LLMNodeType = CommonNodeType & { model: ModelConfig prompt_template: PromptItem[] | PromptItem @@ -18,6 +31,8 @@ export type LLMNodeType = CommonNodeType & { structured_output_enabled?: boolean structured_output?: StructuredOutput reasoning_format?: 'tagged' | 'separated' + tools?: ToolValue[] + max_iterations?: number } export enum Type {