feat(frontend): Disallow webhook+input or webhook+webhook in the same graph (#8861)
- Resolves #8853 Disallow combining webhook block with another webhook or input block, because we can't run those. Our current approach to validating the input for a graph's starting nodes prohibits such cases. Demo: https://github.com/user-attachments/assets/ac098765-bb5f-4218-8cd4-ad992b1b8cda ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Add a webhook-triggered block -> can't add another, also can't add an input block - [x] Add an input block -> can't add a webhook-triggered block --------- Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
This commit is contained in:
parent
abf73e8d66
commit
e3cf605e9b
|
@ -673,6 +673,7 @@ const FlowEditor: React.FC<{
|
|||
blocks={availableNodes}
|
||||
addBlock={addNode}
|
||||
flows={availableFlows}
|
||||
nodes={nodes}
|
||||
/>
|
||||
}
|
||||
botChildren={
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import React, { useState, useCallback } from "react";
|
||||
import React, { useState, useMemo } from "react";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { TextRenderer } from "@/components/ui/render";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { CustomNode } from "@/components/CustomNode";
|
||||
import { beautifyString } from "@/lib/utils";
|
||||
import {
|
||||
Popover,
|
||||
|
@ -31,6 +32,7 @@ interface BlocksControlProps {
|
|||
) => void;
|
||||
pinBlocksPopover: boolean;
|
||||
flows: GraphMeta[];
|
||||
nodes: CustomNode[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,15 +49,23 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
|||
addBlock,
|
||||
pinBlocksPopover,
|
||||
flows,
|
||||
nodes,
|
||||
}) => {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
|
||||
const getFilteredBlockList = (): Block[] => {
|
||||
const graphHasWebhookNodes = nodes.some(
|
||||
(n) => n.data.uiType == BlockUIType.WEBHOOK,
|
||||
);
|
||||
const graphHasInputNodes = nodes.some(
|
||||
(n) => n.data.uiType == BlockUIType.INPUT,
|
||||
);
|
||||
|
||||
const filteredAvailableBlocks = useMemo(() => {
|
||||
const blockList = blocks
|
||||
.filter((b) => b.uiType !== BlockUIType.AGENT)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
const agentList = flows.map(
|
||||
const agentBlockList = flows.map(
|
||||
(flow) =>
|
||||
({
|
||||
id: SpecialBlockID.AGENT,
|
||||
|
@ -80,7 +90,7 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
|||
);
|
||||
|
||||
return blockList
|
||||
.concat(agentList)
|
||||
.concat(agentBlockList)
|
||||
.filter(
|
||||
(block: Block) =>
|
||||
(block.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
|
@ -92,8 +102,29 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
|||
.includes(searchQuery.toLowerCase())) &&
|
||||
(!selectedCategory ||
|
||||
block.categories.some((cat) => cat.category === selectedCategory)),
|
||||
);
|
||||
};
|
||||
)
|
||||
.map((block) => ({
|
||||
...block,
|
||||
notAvailable:
|
||||
(block.uiType == BlockUIType.WEBHOOK &&
|
||||
graphHasWebhookNodes &&
|
||||
"Agents can only have one webhook-triggered block") ||
|
||||
(block.uiType == BlockUIType.WEBHOOK &&
|
||||
graphHasInputNodes &&
|
||||
"Webhook-triggered blocks can't be used together with input blocks") ||
|
||||
(block.uiType == BlockUIType.INPUT &&
|
||||
graphHasWebhookNodes &&
|
||||
"Input blocks can't be used together with a webhook-triggered block") ||
|
||||
null,
|
||||
}));
|
||||
}, [
|
||||
blocks,
|
||||
flows,
|
||||
searchQuery,
|
||||
selectedCategory,
|
||||
graphHasInputNodes,
|
||||
graphHasWebhookNodes,
|
||||
]);
|
||||
|
||||
const resetFilters = React.useCallback(() => {
|
||||
setSearchQuery("");
|
||||
|
@ -190,14 +221,20 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
|||
className="h-[60vh]"
|
||||
data-id="blocks-control-scroll-area"
|
||||
>
|
||||
{getFilteredBlockList().map((block) => (
|
||||
{filteredAvailableBlocks.map((block) => (
|
||||
<Card
|
||||
key={block.uiKey || block.id}
|
||||
className="m-2 my-4 flex h-20 cursor-pointer shadow-none hover:shadow-lg"
|
||||
className={`m-2 my-4 flex h-20 shadow-none ${
|
||||
block.notAvailable
|
||||
? "cursor-not-allowed opacity-50"
|
||||
: "cursor-pointer hover:shadow-lg"
|
||||
}`}
|
||||
data-id={`block-card-${block.id}`}
|
||||
onClick={() =>
|
||||
!block.notAvailable &&
|
||||
addBlock(block.id, block.name, block?.hardcodedValues || {})
|
||||
}
|
||||
title={block.notAvailable ?? undefined}
|
||||
>
|
||||
<div
|
||||
className={`-ml-px h-full w-3 rounded-l-xl ${getPrimaryCategoryColor(block.categories)}`}
|
||||
|
|
Loading…
Reference in New Issue