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:
Reinier van der Leer 2024-12-12 14:56:28 +01:00 committed by GitHub
parent abf73e8d66
commit e3cf605e9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 46 additions and 8 deletions

View File

@ -673,6 +673,7 @@ const FlowEditor: React.FC<{
blocks={availableNodes}
addBlock={addNode}
flows={availableFlows}
nodes={nodes}
/>
}
botChildren={

View File

@ -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)}`}