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 (
-
-
- )}
+
-
-
+
+
+
)
}
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 {