feat: implement tool grouping in ComposerAction for improved UI organization

This commit is contained in:
Anish Sarkar 2026-03-17 15:18:58 +05:30
parent 591bd6bb46
commit e5180aa0a3

View file

@ -614,6 +614,32 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
); );
}, [filteredTools, disabledTools]); }, [filteredTools, disabledTools]);
const groupedTools = useMemo(() => {
if (!filteredTools) return [];
const toolsByName = new Map(filteredTools.map((t) => [t.name, t]));
const result: { label: string; tools: typeof filteredTools }[] = [];
const placed = new Set<string>();
for (const group of TOOL_GROUPS) {
const matched = group.tools.flatMap((name) => {
const tool = toolsByName.get(name);
if (!tool) return [];
placed.add(name);
return [tool];
});
if (matched.length > 0) {
result.push({ label: group.label, tools: matched });
}
}
const ungrouped = filteredTools.filter((t) => !placed.has(t.name));
if (ungrouped.length > 0) {
result.push({ label: "Other", tools: ungrouped });
}
return result;
}, [filteredTools]);
useEffect(() => { useEffect(() => {
hydrateDisabled(); hydrateDisabled();
}, [hydrateDisabled]); }, [hydrateDisabled]);
@ -669,7 +695,12 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
</span> </span>
</div> </div>
<div className="overflow-y-auto pb-6" onScroll={handleToolsScroll}> <div className="overflow-y-auto pb-6" onScroll={handleToolsScroll}>
{filteredTools?.map((tool) => { {groupedTools.map((group) => (
<div key={group.label}>
<div className="px-4 pt-3 pb-1 text-xs text-muted-foreground/80 font-medium select-none">
{group.label}
</div>
{group.tools.map((tool) => {
const isDisabled = disabledTools.includes(tool.name); const isDisabled = disabledTools.includes(tool.name);
const ToolIcon = getToolIcon(tool.name); const ToolIcon = getToolIcon(tool.name);
return ( return (
@ -689,6 +720,8 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
</div> </div>
); );
})} })}
</div>
))}
{!filteredTools?.length && ( {!filteredTools?.length && (
<div className="px-4 py-6 text-center text-sm text-muted-foreground"> <div className="px-4 py-6 text-center text-sm text-muted-foreground">
Loading tools... Loading tools...
@ -744,7 +777,12 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
WebkitMaskImage: `linear-gradient(to bottom, ${toolsScrollPos === "top" ? "black" : "transparent"}, black 16px, black calc(100% - 16px), ${toolsScrollPos === "bottom" ? "black" : "transparent"})`, WebkitMaskImage: `linear-gradient(to bottom, ${toolsScrollPos === "top" ? "black" : "transparent"}, black 16px, black calc(100% - 16px), ${toolsScrollPos === "bottom" ? "black" : "transparent"})`,
}} }}
> >
{filteredTools?.map((tool) => { {groupedTools.map((group) => (
<div key={group.label}>
<div className="px-2.5 sm:px-3 pt-2 pb-0.5 text-[10px] sm:text-xs text-muted-foreground/80 font-normal select-none">
{group.label}
</div>
{group.tools.map((tool) => {
const isDisabled = disabledTools.includes(tool.name); const isDisabled = disabledTools.includes(tool.name);
const ToolIcon = getToolIcon(tool.name); const ToolIcon = getToolIcon(tool.name);
const row = ( const row = (
@ -769,6 +807,8 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
</Tooltip> </Tooltip>
); );
})} })}
</div>
))}
{!filteredTools?.length && ( {!filteredTools?.length && (
<div className="px-3 py-4 text-center text-xs text-muted-foreground"> <div className="px-3 py-4 text-center text-xs text-muted-foreground">
Loading tools... Loading tools...
@ -783,7 +823,7 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
type="button" type="button"
onClick={() => toggleTool("web_search")} onClick={() => toggleTool("web_search")}
className={cn( className={cn(
"rounded-full transition-all flex items-center gap-1 px-2 py-1 border h-8", "rounded-full transition-all flex items-center gap-1 px-2 py-1 border h-8 select-none",
isWebSearchEnabled isWebSearchEnabled
? "bg-sky-500/15 border-sky-500/60 text-sky-500" ? "bg-sky-500/15 border-sky-500/60 text-sky-500"
: "bg-transparent border-transparent text-muted-foreground hover:text-foreground" : "bg-transparent border-transparent text-muted-foreground hover:text-foreground"
@ -891,6 +931,22 @@ function formatToolName(name: string): string {
.join(" "); .join(" ");
} }
const TOOL_GROUPS: { label: string; tools: string[] }[] = [
{
label: "Research",
tools: ["search_knowledge_base", "search_surfsense_docs", "scrape_webpage", "link_preview"],
},
{
label: "Generate",
tools: ["generate_podcast", "generate_report", "generate_image", "display_image"],
},
{
label: "Memory",
tools: ["save_memory", "recall_memory"],
},
];
const MessageError: FC = () => { const MessageError: FC = () => {
return ( return (
<MessagePrimitive.Error> <MessagePrimitive.Error>