Skip to content

Commit 79faadc

Browse files
committed
feat(web): add tool selection dialog
1 parent 9c5142f commit 79faadc

16 files changed

Lines changed: 445 additions & 17 deletions

apps/api/src/nodes/node-tool-provider.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,12 @@ export class NodeToolProvider implements ToolProvider {
172172
const nodeTypes = this.nodeRegistry.getNodeTypes();
173173
const tools: ToolDefinition[] = [];
174174

175-
for (const nodeType of nodeTypes) {
175+
// Filter to only include nodes that can be used as tools
176+
const toolNodeTypes = nodeTypes.filter(
177+
(nodeType) => nodeType.asTool === true
178+
);
179+
180+
for (const nodeType of toolNodeTypes) {
176181
try {
177182
const tool = await this.getToolDefinition(nodeType.type);
178183
tools.push(tool);

apps/api/src/nodes/text/hermes-2-pro-mistral-7b-node.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,18 @@ export class Hermes2ProMistral7BNode extends ExecutableNode {
172172
}
173173

174174
// Check if we have function calls to enable embedded mode
175-
const toolDefinitions = await this.convertFunctionCallsToToolDefinitions(
175+
const toolsDefinitions = await this.convertFunctionCallsToToolDefinitions(
176176
tools as ToolReference[],
177177
context
178178
);
179179

180180
let result: AiTextGenerationOutput;
181181
let executedToolCalls: any[] = [];
182182

183-
if (tools.length > 0) {
183+
if (toolsDefinitions.length > 0) {
184184
const toolCallTracker = new ToolCallTracker();
185185
const trackedToolDefinitions =
186-
toolCallTracker.wrapToolDefinitions(toolDefinitions);
186+
toolCallTracker.wrapToolDefinitions(toolsDefinitions);
187187

188188
result = await runWithTools(
189189
// @ts-ignore

apps/api/src/nodes/text/llama-3-3-70b-instruct-fp8-fast-node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export class Llama3370BInstructFastNode extends ExecutableNode {
168168
let result: AiTextGenerationOutput;
169169
let executedToolCalls: any[] = [];
170170

171-
if (tools.length > 0) {
171+
if (toolsDefinitions.length > 0) {
172172
const toolCallTracker = new ToolCallTracker();
173173
const trackedToolDefinitions =
174174
toolCallTracker.wrapToolDefinitions(toolsDefinitions);

apps/api/src/nodes/text/llama-4-scout-17b-16e-instruct-node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export class Llama4Scout17B16EInstructNode extends ExecutableNode {
183183
| AiTextGenerationOutput;
184184
let executedToolCalls: any[] = [];
185185

186-
if (tools.length > 0) {
186+
if (toolsDefinitions.length > 0) {
187187
const toolCallTracker = new ToolCallTracker();
188188
const trackedToolDefinitions =
189189
toolCallTracker.wrapToolDefinitions(toolsDefinitions);

apps/api/src/nodes/text/mistral-small-3-1-24b-instruct-node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export class MistralSmall31_24BInstructNode extends ExecutableNode {
182182
| AiTextGenerationOutput;
183183
let executedToolCalls: any[] = [];
184184

185-
if (tools.length > 0) {
185+
if (toolsDefinitions.length > 0) {
186186
const toolCallTracker = new ToolCallTracker();
187187
const trackedToolDefinitions =
188188
toolCallTracker.wrapToolDefinitions(toolsDefinitions);

apps/web/src/components/workflow/use-workflow-state.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ interface UseWorkflowStateProps {
3434
validateConnection?: (connection: Connection) => boolean;
3535
createObjectUrl: (objectReference: ObjectReference) => string;
3636
readonly?: boolean;
37+
nodeTemplates?: NodeTemplate[];
3738
}
3839

3940
type NodeExecutionUpdate = {
@@ -214,16 +215,39 @@ export function useWorkflowState({
214215
validateConnection = () => true,
215216
createObjectUrl,
216217
readonly = false,
218+
nodeTemplates = [],
217219
}: UseWorkflowStateProps): UseWorkflowStateReturn {
218220
// State management
219-
const [nodes, setNodes, onNodesChange] =
220-
useNodesState<ReactFlowNode<WorkflowNodeType>>(initialNodes);
221+
const [nodes, setNodes, onNodesChange] = useNodesState<
222+
ReactFlowNode<WorkflowNodeType>
223+
>(
224+
initialNodes.map((node) => ({
225+
...node,
226+
data: {
227+
...node.data,
228+
nodeTemplates,
229+
},
230+
}))
231+
);
221232
const [edges, setEdges, onEdgesChange] =
222233
useEdgesState<ReactFlowEdge<WorkflowEdgeType>>(initialEdges);
223234
const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance<
224235
ReactFlowNode<WorkflowNodeType>,
225236
ReactFlowEdge<WorkflowEdgeType>
226237
> | null>(null);
238+
239+
// Update nodes when nodeTemplates changes
240+
useEffect(() => {
241+
setNodes((currentNodes) =>
242+
currentNodes.map((node) => ({
243+
...node,
244+
data: {
245+
...node.data,
246+
nodeTemplates,
247+
},
248+
}))
249+
);
250+
}, [nodeTemplates, setNodes]);
227251
const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false);
228252
const [connectionValidationState, setConnectionValidationState] =
229253
useState<ConnectionValidationState>("default");
@@ -534,13 +558,16 @@ export function useWorkflowState({
534558
outputs: template.outputs,
535559
executionState: "idle" as NodeExecutionState,
536560
nodeType: template.type,
561+
functionCalling: template.functionCalling,
562+
asTool: template.asTool,
563+
nodeTemplates,
537564
createObjectUrl,
538565
},
539566
};
540567

541568
setNodes((nds) => nds.concat(newNode));
542569
},
543-
[reactFlowInstance, setNodes, createObjectUrl]
570+
[reactFlowInstance, setNodes, createObjectUrl, nodeTemplates]
544571
);
545572

546573
// Unified function to update node execution data

apps/web/src/components/workflow/workflow-builder.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export function WorkflowBuilder({
133133
validateConnection,
134134
createObjectUrl,
135135
readonly,
136+
nodeTemplates,
136137
});
137138

138139
// Track input and output connections

apps/web/src/components/workflow/workflow-node.tsx

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
StickyNoteIcon,
2323
TriangleIcon,
2424
TypeIcon,
25+
WrenchIcon,
2526
} from "lucide-react";
2627
import { createElement, memo, useEffect, useState } from "react";
2728

@@ -43,9 +44,11 @@ import { WebcamWidget } from "./widgets/webcam-widget";
4344
import { createWidgetConfig } from "./widgets/widget-factory";
4445
import { updateNodeInput, useWorkflow } from "./workflow-context";
4546
import { WorkflowOutputRenderer } from "./workflow-output-renderer";
47+
import { ToolReference, WorkflowToolSelector } from "./workflow-tool-selector";
4648
import {
4749
InputOutputType,
4850
NodeExecutionState,
51+
NodeTemplate,
4952
WorkflowParameter,
5053
} from "./workflow-types";
5154

@@ -56,8 +59,11 @@ export interface WorkflowNodeType {
5659
error?: string | null;
5760
executionState: NodeExecutionState;
5861
nodeType?: string;
62+
functionCalling?: boolean;
63+
asTool?: boolean;
5964
dragHandle?: string;
6065
createObjectUrl: (objectReference: ObjectReference) => string;
66+
nodeTemplates?: NodeTemplate[];
6167
}
6268

6369
const TypeBadge = ({
@@ -184,6 +190,7 @@ export const WorkflowNode = memo(
184190
const { updateNodeData, readonly, expandedOutputs } = useWorkflow();
185191
const [showOutputs, setShowOutputs] = useState(false);
186192
const [showError, setShowError] = useState(true);
193+
const [isToolSelectorOpen, setIsToolSelectorOpen] = useState(false);
187194
const hasVisibleOutputs = data.outputs.some((output) => !output.hidden);
188195
const canShowOutputs =
189196
hasVisibleOutputs &&
@@ -263,6 +270,35 @@ export const WorkflowNode = memo(
263270
setSelectedInput(null);
264271
};
265272

273+
const handleToolIconClick = (e: React.MouseEvent) => {
274+
e.stopPropagation();
275+
if (readonly) return;
276+
setIsToolSelectorOpen(true);
277+
};
278+
279+
const handleToolSelectorClose = () => {
280+
setIsToolSelectorOpen(false);
281+
};
282+
283+
const handleToolsSelect = (tools: ToolReference[]) => {
284+
if (readonly || !updateNodeData) return;
285+
286+
// Find the tools input parameter
287+
const toolsInput = data.inputs.find((input) => input.id === "tools");
288+
if (toolsInput) {
289+
updateNodeInput(id, toolsInput.id, tools, data.inputs, updateNodeData);
290+
}
291+
};
292+
293+
// Get current selected tools from the tools input
294+
const getCurrentSelectedTools = (): ToolReference[] => {
295+
const toolsInput = data.inputs.find((input) => input.id === "tools");
296+
if (toolsInput && Array.isArray(toolsInput.value)) {
297+
return toolsInput.value as ToolReference[];
298+
}
299+
return [];
300+
};
301+
266302
return (
267303
<TooltipProvider>
268304
<div
@@ -281,11 +317,19 @@ export const WorkflowNode = memo(
281317
{/* Header */}
282318
<div
283319
className={cn(
284-
"px-1 py-1 flex justify-center items-center border-b hover:cursor-grab active:cursor-grabbing",
320+
"px-1 py-1 flex justify-between items-center border-b hover:cursor-grab active:cursor-grabbing",
285321
"workflow-node-drag-handle"
286322
)}
287323
>
288-
<h3 className="text-xs font-medium truncate">{data.name}</h3>
324+
<div className="flex items-center gap-1 flex-1 min-w-0">
325+
{data.functionCalling && (
326+
<WrenchIcon
327+
className="h-3 w-3 text-blue-600 shrink-0 cursor-pointer hover:text-blue-700 transition-colors"
328+
onClick={handleToolIconClick}
329+
/>
330+
)}
331+
<h3 className="text-xs font-medium truncate">{data.name}</h3>
332+
</div>
289333
</div>
290334

291335
{/* Widget */}
@@ -436,6 +480,16 @@ export const WorkflowNode = memo(
436480
onClose={handleDialogClose}
437481
readonly={readonly}
438482
/>
483+
484+
{data.functionCalling && (
485+
<WorkflowToolSelector
486+
open={isToolSelectorOpen}
487+
onClose={handleToolSelectorClose}
488+
onSelect={handleToolsSelect}
489+
templates={data.nodeTemplates || []}
490+
selectedTools={getCurrentSelectedTools()}
491+
/>
492+
)}
439493
</TooltipProvider>
440494
);
441495
}

0 commit comments

Comments
 (0)