Skip to content

Commit 165f4ff

Browse files
authored
refactor(web): use area instead of wrench icons for functionCalling nodes
131 move tool selection in a dedicated space between the header and the properties
2 parents 0d1cee5 + b6f23a6 commit 165f4ff

8 files changed

Lines changed: 126 additions & 56 deletions

File tree

apps/web/src/components/docs/node-card.tsx

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DynamicIcon } from "lucide-react/dynamic.mjs";
44
import { useState } from "react";
55

66
import { Badge } from "@/components/ui/badge";
7+
import { NodeTags } from "@/components/workflow/node-tags";
78
import { Button } from "@/components/ui/button";
89
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
910
import {
@@ -40,15 +41,10 @@ function NodeDetailsDialog({
4041
<div className="flex-1">
4142
<div className="flex items-center gap-2">
4243
{nodeType.name}
43-
{nodeType.tags.map((tag, index) => (
44-
<Badge
45-
key={index}
46-
variant="secondary"
47-
className={getTagColor([tag])}
48-
>
49-
{tag}
50-
</Badge>
51-
))}
44+
<NodeTags
45+
tags={nodeType.tags}
46+
functionCalling={nodeType.functionCalling}
47+
/>
5248
</div>
5349
</div>
5450
</DialogTitle>
@@ -218,7 +214,7 @@ export function NodeCard({ nodeType, variant = "card" }: NodeCardProps) {
218214
<div className="flex items-center gap-3 min-w-0 flex-1">
219215
<DynamicIcon
220216
name={nodeType.icon as any}
221-
className="h-4 w-4 text-blue-600 shrink-0"
217+
className="h-4 w-4 text-blue-500 shrink-0"
222218
/>
223219
<CardTitle className="text-base font-semibold leading-tight truncate">
224220
{nodeType.name}
@@ -278,23 +274,16 @@ export function NodeCard({ nodeType, variant = "card" }: NodeCardProps) {
278274
<div className="flex items-center gap-3 min-w-0 flex-1">
279275
<DynamicIcon
280276
name={nodeType.icon as any}
281-
className="h-4 w-4 text-blue-600 shrink-0"
277+
className="h-4 w-4 text-blue-500 shrink-0"
282278
/>
283279
<CardTitle className="text-base font-semibold leading-tight truncate">
284280
{nodeType.name}
285281
</CardTitle>
286282
</div>
287-
<div className="flex gap-1 shrink-0">
288-
{nodeType.tags.map((tag, index) => (
289-
<Badge
290-
key={index}
291-
variant="secondary"
292-
className={`${getTagColor([tag])} text-xs`}
293-
>
294-
{tag}
295-
</Badge>
296-
))}
297-
</div>
283+
<NodeTags
284+
tags={nodeType.tags}
285+
functionCalling={nodeType.functionCalling}
286+
/>
298287
</div>
299288
</CardHeader>
300289
<CardContent className="pt-0">

apps/web/src/components/docs/nodes-browser.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,12 @@ export function NodesBrowser() {
4040

4141
// Filter by tag (check if any tag matches)
4242
if (selectedTag) {
43-
filtered = filtered.filter((nodeType) =>
44-
nodeType.tags.includes(selectedTag)
45-
);
43+
filtered = filtered.filter((nodeType) => {
44+
if (selectedTag === "Tools") {
45+
return !!nodeType.functionCalling;
46+
}
47+
return nodeType.tags.includes(selectedTag);
48+
});
4649
}
4750

4851
return filtered.sort((a, b) => a.name.localeCompare(b.name));
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Badge } from "@/components/ui/badge";
2+
import { getTagColor } from "@/utils/tag-colors";
3+
import { cn } from "@/utils/utils";
4+
5+
export interface NodeTagsProps {
6+
tags: string[];
7+
functionCalling?: boolean;
8+
className?: string;
9+
size?: "sm" | "xs";
10+
}
11+
12+
export function NodeTags({
13+
tags,
14+
functionCalling,
15+
className,
16+
size = "xs",
17+
}: NodeTagsProps) {
18+
const allTags = [...tags, ...(functionCalling ? ["Tools"] : [])];
19+
20+
if (allTags.length === 0) return null;
21+
22+
return (
23+
<div className={cn("flex gap-1 flex-wrap", className)}>
24+
{allTags.map((tag, index) => (
25+
<Badge
26+
key={`${tag}-${index}`}
27+
variant="secondary"
28+
className={cn(
29+
getTagColor(tag),
30+
size === "xs" ? "text-xs" : "text-sm"
31+
)}
32+
>
33+
{tag}
34+
</Badge>
35+
))}
36+
</div>
37+
);
38+
}

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

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Search } from "lucide-react";
33
import { DynamicIcon } from "lucide-react/dynamic.mjs";
44
import { useState } from "react";
55

6-
import { Badge } from "@/components/ui/badge";
6+
import { NodeTags } from "./node-tags";
77
import {
88
Dialog,
99
DialogContent,
@@ -16,7 +16,6 @@ import { TagFilterButtons } from "@/components/ui/tag-filter-buttons";
1616
import { useKeyboardNavigation } from "@/hooks/use-keyboard-navigation";
1717
import { useSearch } from "@/hooks/use-search";
1818
import { useTagCounts } from "@/hooks/use-tag-counts";
19-
import { getTagColor } from "@/utils/tag-colors";
2019
import { cn } from "@/utils/utils";
2120

2221
import type { NodeTemplate } from "./workflow-types";
@@ -53,6 +52,9 @@ export function WorkflowNodeSelector({
5352

5453
// Filter templates based on search results and selected tag
5554
const filteredTemplates = searchResults.filter((template) => {
55+
if (selectedTag === "Tools") {
56+
return !!template.functionCalling;
57+
}
5658
const matchesTag = !selectedTag || template.tags.includes(selectedTag);
5759
return matchesTag;
5860
});
@@ -160,22 +162,15 @@ export function WorkflowNodeSelector({
160162
<div className="flex items-center gap-3 mb-2">
161163
<DynamicIcon
162164
name={template.icon as any}
163-
className="h-4 w-4 text-blue-600 shrink-0 [&svg>path]:stroke-2"
165+
className="h-4 w-4 text-blue-500 shrink-0 [&svg>path]:stroke-2"
164166
/>
165167
<h3 className="font-semibold text-base leading-tight truncate">
166168
{template.name}
167169
</h3>
168-
<div className="flex gap-1 shrink-0">
169-
{template.tags.map((tag, index) => (
170-
<Badge
171-
key={index}
172-
variant="secondary"
173-
className={`${getTagColor([tag])} text-xs`}
174-
>
175-
{tag}
176-
</Badge>
177-
))}
178-
</div>
170+
<NodeTags
171+
tags={template.tags}
172+
functionCalling={template.functionCalling}
173+
/>
179174
</div>
180175
{template.description && (
181176
<p className="text-sm text-muted-foreground leading-relaxed">

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

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
StickyNoteIcon,
2323
TriangleIcon,
2424
TypeIcon,
25-
WrenchIcon,
2625
} from "lucide-react";
2726
// @ts-ignore - https://github.com/lucide-icons/lucide/issues/2867#issuecomment-2847105863
2827
import { DynamicIcon } from "lucide-react/dynamic.mjs";
@@ -273,12 +272,6 @@ export const WorkflowNode = memo(
273272
setSelectedInput(null);
274273
};
275274

276-
const handleToolIconClick = (e: React.MouseEvent) => {
277-
e.stopPropagation();
278-
if (readonly) return;
279-
setIsToolSelectorOpen(true);
280-
};
281-
282275
const handleToolSelectorClose = () => {
283276
setIsToolSelectorOpen(false);
284277
};
@@ -327,18 +320,62 @@ export const WorkflowNode = memo(
327320
<div className="flex items-center gap-1 flex-1 min-w-0">
328321
<DynamicIcon
329322
name={data.icon as any}
330-
className="mx-1 h-3 w-3 text-blue-600 shrink-0"
323+
className="mx-1 h-3 w-3 text-blue-500 shrink-0"
331324
/>
332325
<h3 className="text-xs font-medium truncate">{data.name}</h3>
333-
{data.functionCalling && (
334-
<WrenchIcon
335-
className="h-3 w-3 text-blue-600 shrink-0 cursor-pointer hover:text-blue-700 transition-colors"
336-
onClick={handleToolIconClick}
337-
/>
338-
)}
339326
</div>
340327
</div>
341328

329+
{/* Tools bar (between header and body) */}
330+
{data.functionCalling && (
331+
<div
332+
className="px-1 py-1 border-b nodrag flex flex-wrap items-start gap-1"
333+
onClick={(e) => {
334+
e.stopPropagation();
335+
if (readonly) return;
336+
setIsToolSelectorOpen(true);
337+
}}
338+
>
339+
{(() => {
340+
const selectedTools = getCurrentSelectedTools();
341+
if (!selectedTools.length) {
342+
return (
343+
<span className="text-[10px] text-neutral-500">
344+
Click to select tools
345+
</span>
346+
);
347+
}
348+
349+
return (
350+
<div className="flex flex-wrap items-center gap-1">
351+
{selectedTools.map((tool, idx) => {
352+
const tpl = (data.nodeTemplates || []).find(
353+
(t) => t.id === tool.identifier
354+
);
355+
return (
356+
<span
357+
key={`${tool.identifier}-${idx}`}
358+
className="inline-flex items-center gap-1 px-1 py-[2px] rounded bg-neutral-100 text-[10px] text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300"
359+
>
360+
{tpl?.icon ? (
361+
<DynamicIcon
362+
name={tpl.icon as any}
363+
className="h-3 w-3"
364+
/>
365+
) : null}
366+
<span className="truncate max-w-[84px]">
367+
{tpl?.name || tool.identifier}
368+
</span>
369+
</span>
370+
);
371+
})}
372+
<span className="text-[10px] text-blue-500 ml-1">Edit</span>
373+
</div>
374+
);
375+
})()}
376+
</div>
377+
)}
378+
342379
{/* Widget */}
343380
{!readonly &&
344381
widgetConfig &&

apps/web/src/components/workflow/workflow-tool-selector.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export function WorkflowToolSelector({
197197
? "bg-accent border-primary/50"
198198
: "hover:bg-accent/50",
199199
isSelected &&
200-
"bg-blue-50 border-blue-300 dark:bg-blue-950/20 dark:border-blue-700"
200+
"bg-blue-50 border-blue-500 dark:bg-blue-950/20"
201201
)}
202202
onClick={() => handleTemplateToggle(template.id)}
203203
onMouseEnter={() => {
@@ -223,7 +223,7 @@ export function WorkflowToolSelector({
223223
<div className="flex items-center gap-3 mb-2">
224224
<DynamicIcon
225225
name={template.icon as any}
226-
className="h-4 w-4 text-blue-600 shrink-0"
226+
className="h-4 w-4 text-blue-500 shrink-0"
227227
/>
228228
<h3 className="font-semibold text-base leading-tight truncate">
229229
{template.name}

apps/web/src/hooks/use-tag-counts.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ export interface ItemWithCategory {
1313
category: string;
1414
}
1515

16-
export function useTagCounts<T extends ItemWithTags>(items: T[]): TagCount[] {
16+
export function useTagCounts<
17+
T extends ItemWithTags & { functionCalling?: boolean },
18+
>(items: T[]): TagCount[] {
1719
return useMemo(() => {
1820
const counts: Record<string, number> = {};
1921

@@ -24,6 +26,11 @@ export function useTagCounts<T extends ItemWithTags>(items: T[]): TagCount[] {
2426
counts[tag] = (counts[tag] || 0) + 1;
2527
}
2628
});
29+
30+
// Add synthetic tag for function calling support
31+
if ((item as any).functionCalling) {
32+
counts["Tools"] = (counts["Tools"] || 0) + 1;
33+
}
2734
});
2835

2936
return Object.entries(counts)

apps/web/src/utils/tag-colors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function getTagColor(tagOrTags: string | string[]): string {
1616
parameter: "bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-200",
1717
document: "bg-teal-100 text-teal-800 dark:bg-teal-900 dark:text-teal-200",
1818
email: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900 dark:text-cyan-200",
19+
tools: "bg-transparent text-blue-500 border-blue-500",
1920
};
2021

2122
return (

0 commit comments

Comments
 (0)