Update parameter hint (#1628)

This commit is contained in:
Shuchang Zheng
2025-01-24 00:45:48 +08:00
committed by GitHub
parent 99adf68444
commit a7752de6a9
16 changed files with 224 additions and 141 deletions

View File

@@ -3,43 +3,14 @@ import { cn } from "@/util/utils";
import { Input } from "./ui/input"; import { Input } from "./ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect"; import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect";
import { useEdges, useNodes } from "@xyflow/react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "./ui/tooltip";
type Props = Omit<React.ComponentProps<typeof Input>, "onChange"> & { type Props = Omit<React.ComponentProps<typeof Input>, "onChange"> & {
onChange: (value: string) => void; onChange: (value: string) => void;
nodeId: string; nodeId: string;
isFirstInputInNode?: boolean;
}; };
function WorkflowBlockInput(props: Props) { function WorkflowBlockInput(props: Props) {
const { nodeId, onChange, ...inputProps } = props; const { nodeId, onChange, ...inputProps } = props;
const edges = useEdges();
const nodes = useNodes();
function isInsideFirstNode() {
const node = nodes.find((node) => node.id === nodeId);
if (!node) {
return;
}
const incomingEdge = edges.find((edge) => edge.target === node.id);
if (!incomingEdge) {
return;
}
const source = incomingEdge.source;
const sourceNode = nodes.find((node) => node.id === source);
if (!sourceNode) {
return;
}
return !node.parentId && sourceNode.type === "start";
}
const showInputTooltip = isInsideFirstNode() && props.isFirstInputInNode;
return ( return (
<div className="relative"> <div className="relative">
@@ -52,19 +23,11 @@ function WorkflowBlockInput(props: Props) {
/> />
<div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center"> <div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center">
<Popover> <Popover>
<TooltipProvider> <PopoverTrigger asChild>
<Tooltip open={showInputTooltip}> <div className="rounded p-1 hover:bg-muted">
<TooltipTrigger asChild> <PlusIcon className="size-4" />
<PopoverTrigger asChild> </div>
<div className="rounded p-1 hover:bg-muted"> </PopoverTrigger>
<PlusIcon className="size-4" />
</div>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent>Add parameters using the + button</TooltipContent>
</Tooltip>
</TooltipProvider>
<PopoverContent> <PopoverContent>
<WorkflowBlockParameterSelect <WorkflowBlockParameterSelect
nodeId={nodeId} nodeId={nodeId}

View File

@@ -3,13 +3,6 @@ import { cn } from "@/util/utils";
import { AutoResizingTextarea } from "./AutoResizingTextarea/AutoResizingTextarea"; import { AutoResizingTextarea } from "./AutoResizingTextarea/AutoResizingTextarea";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect"; import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "./ui/tooltip";
import { useEdges, useNodes } from "@xyflow/react";
type Props = Omit< type Props = Omit<
React.ComponentProps<typeof AutoResizingTextarea>, React.ComponentProps<typeof AutoResizingTextarea>,
@@ -17,32 +10,10 @@ type Props = Omit<
> & { > & {
onChange: (value: string) => void; onChange: (value: string) => void;
nodeId: string; nodeId: string;
isFirstInputInNode?: boolean;
}; };
function WorkflowBlockInputTextarea(props: Props) { function WorkflowBlockInputTextarea(props: Props) {
const { nodeId, onChange, ...textAreaProps } = props; const { nodeId, onChange, ...textAreaProps } = props;
const edges = useEdges();
const nodes = useNodes();
function isInsideFirstNode() {
const node = nodes.find((node) => node.id === nodeId);
if (!node) {
return;
}
const incomingEdge = edges.find((edge) => edge.target === node.id);
if (!incomingEdge) {
return;
}
const source = incomingEdge.source;
const sourceNode = nodes.find((node) => node.id === source);
if (!sourceNode) {
return;
}
return !node.parentId && sourceNode.type === "start";
}
const showInputTooltip = isInsideFirstNode() && props.isFirstInputInNode;
return ( return (
<div className="relative"> <div className="relative">
@@ -55,18 +26,11 @@ function WorkflowBlockInputTextarea(props: Props) {
/> />
<div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center"> <div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center">
<Popover> <Popover>
<TooltipProvider> <PopoverTrigger asChild>
<Tooltip open={showInputTooltip}> <div className="rounded p-1 hover:bg-muted">
<TooltipTrigger asChild> <PlusIcon className="size-4" />
<PopoverTrigger asChild> </div>
<div className="rounded p-1 hover:bg-muted"> </PopoverTrigger>
<PlusIcon className="size-4" />
</div>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent>Add parameters using the + button</TooltipContent>
</Tooltip>
</TooltipProvider>
<PopoverContent> <PopoverContent>
<WorkflowBlockParameterSelect <WorkflowBlockParameterSelect
nodeId={nodeId} nodeId={nodeId}

View File

@@ -0,0 +1,32 @@
import { Edge, useEdges, useNodes } from "@xyflow/react";
import { AppNode } from "../nodes";
function isFirstNode(nodes: Array<AppNode>, edges: Array<Edge>, id: string) {
const node = nodes.find((node) => node.id === id);
if (!node) {
return false; // doesn't make sense but for TS
}
const incomingEdge = edges.find((edge) => edge.target === node.id);
if (!incomingEdge) {
return false;
}
const source = incomingEdge.source;
const sourceNode = nodes.find((node) => node.id === source);
if (!sourceNode) {
return false;
}
return !node.parentId && sourceNode.type === "start";
}
type Props = {
id: string;
};
function useIsFirstBlockInWorkflow({ id }: Props): boolean {
const nodes = useNodes<AppNode>();
const edges = useEdges();
return isFirstNode(nodes, edges, id);
}
export { useIsFirstBlockInWorkflow };

View File

@@ -34,6 +34,7 @@ import { WorkflowBlockTypes } from "@/routes/workflows/types/workflowTypes";
import { AppNode } from ".."; import { AppNode } from "..";
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils"; import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect"; import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
const urlTooltip = const urlTooltip =
"The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off."; "The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off.";
@@ -75,6 +76,8 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
return ( return (
<div> <div>
<Handle <Handle
@@ -117,12 +120,19 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
</header> </header>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300">URL</Label> <div className="flex gap-2">
<HelpTooltip content={urlTooltip} /> <Label className="text-xs text-slate-300">URL</Label>
<HelpTooltip content={urlTooltip} />
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<WorkflowBlockInputTextarea <WorkflowBlockInputTextarea
isFirstInputInNode
nodeId={id} nodeId={id}
onChange={(value) => { onChange={(value) => {
handleChange("url", value); handleChange("url", value);

View File

@@ -32,6 +32,7 @@ import { AppNode } from "..";
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils"; import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect"; import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect";
import { WorkflowDataSchemaInputGroup } from "@/components/DataSchemaInputGroup/WorkflowDataSchemaInputGroup"; import { WorkflowDataSchemaInputGroup } from "@/components/DataSchemaInputGroup/WorkflowDataSchemaInputGroup";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) { function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -54,6 +55,8 @@ function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
const edges = useEdges(); const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id); const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
function handleChange(key: string, value: unknown) { function handleChange(key: string, value: unknown) {
if (!editable) { if (!editable) {
return; return;
@@ -100,16 +103,23 @@ function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
/> />
</header> </header>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300"> <div className="flex gap-2">
Data Extraction Goal <Label className="text-xs text-slate-300">
</Label> Data Extraction Goal
<HelpTooltip </Label>
content={helpTooltips["extraction"]["dataExtractionGoal"]} <HelpTooltip
/> content={helpTooltips["extraction"]["dataExtractionGoal"]}
/>
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<WorkflowBlockInputTextarea <WorkflowBlockInputTextarea
isFirstInputInNode
nodeId={id} nodeId={id}
onChange={(value) => { onChange={(value) => {
if (!editable) { if (!editable) {

View File

@@ -32,6 +32,7 @@ import type { FileDownloadNode } from "./types";
import { AppNode } from ".."; import { AppNode } from "..";
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils"; import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect"; import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
const urlTooltip = const urlTooltip =
"The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off."; "The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off.";
@@ -65,6 +66,8 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
const edges = useEdges(); const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id); const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
function handleChange(key: string, value: unknown) { function handleChange(key: string, value: unknown) {
if (!editable) { if (!editable) {
return; return;
@@ -114,12 +117,18 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
</header> </header>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300">URL</Label> <div className="flex gap-2">
<HelpTooltip content={urlTooltip} /> <Label className="text-xs text-slate-300">URL</Label>
<HelpTooltip content={urlTooltip} />
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<WorkflowBlockInputTextarea <WorkflowBlockInputTextarea
isFirstInputInNode
nodeId={id} nodeId={id}
onChange={(value) => { onChange={(value) => {
handleChange("url", value); handleChange("url", value);

View File

@@ -1,5 +1,4 @@
import { HelpTooltip } from "@/components/HelpTooltip"; import { HelpTooltip } from "@/components/HelpTooltip";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback"; import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler"; import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler";
@@ -11,6 +10,8 @@ import { EditableNodeTitle } from "../components/EditableNodeTitle";
import { NodeActionMenu } from "../NodeActionMenu"; import { NodeActionMenu } from "../NodeActionMenu";
import { WorkflowBlockIcon } from "../WorkflowBlockIcon"; import { WorkflowBlockIcon } from "../WorkflowBlockIcon";
import { type FileParserNode } from "./types"; import { type FileParserNode } from "./types";
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function FileParserNode({ id, data }: NodeProps<FileParserNode>) { function FileParserNode({ id, data }: NodeProps<FileParserNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -23,6 +24,8 @@ function FileParserNode({ id, data }: NodeProps<FileParserNode>) {
initialValue: data.label, initialValue: data.label,
}); });
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
return ( return (
<div> <div>
<Handle <Handle
@@ -65,18 +68,27 @@ function FileParserNode({ id, data }: NodeProps<FileParserNode>) {
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300">File URL</Label> <div className="flex gap-2">
<HelpTooltip content={helpTooltips["fileParser"]["fileUrl"]} /> <Label className="text-xs text-slate-300">File URL</Label>
<HelpTooltip content={helpTooltips["fileParser"]["fileUrl"]} />
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<Input
<WorkflowBlockInput
nodeId={id}
value={inputs.fileUrl} value={inputs.fileUrl}
onChange={(event) => { onChange={(value) => {
if (!data.editable) { if (!data.editable) {
return; return;
} }
setInputs({ ...inputs, fileUrl: event.target.value }); setInputs({ ...inputs, fileUrl: value });
updateNodeData(id, { fileUrl: event.target.value }); updateNodeData(id, { fileUrl: value });
}} }}
className="nopan text-xs" className="nopan text-xs"
/> />

View File

@@ -34,6 +34,7 @@ import type { LoginNode } from "./types";
import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect"; import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect";
import { AppNode } from ".."; import { AppNode } from "..";
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils"; import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function LoginNode({ id, data }: NodeProps<LoginNode>) { function LoginNode({ id, data }: NodeProps<LoginNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -60,6 +61,7 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
const nodes = useNodes<AppNode>(); const nodes = useNodes<AppNode>();
const edges = useEdges(); const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id); const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
function handleChange(key: string, value: unknown) { function handleChange(key: string, value: unknown) {
if (!editable) { if (!editable) {
@@ -111,12 +113,19 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
</header> </header>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300">URL</Label> <div className="flex gap-2">
<HelpTooltip content={helpTooltips["login"]["url"]} /> <Label className="text-xs text-slate-300">URL</Label>
<HelpTooltip content={helpTooltips["login"]["url"]} />
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<WorkflowBlockInputTextarea <WorkflowBlockInputTextarea
isFirstInputInNode
nodeId={id} nodeId={id}
onChange={(value) => { onChange={(value) => {
handleChange("url", value); handleChange("url", value);

View File

@@ -19,6 +19,7 @@ import { NodeActionMenu } from "../NodeActionMenu";
import { WorkflowBlockIcon } from "../WorkflowBlockIcon"; import { WorkflowBlockIcon } from "../WorkflowBlockIcon";
import type { LoopNode } from "./types"; import type { LoopNode } from "./types";
import { useState } from "react"; import { useState } from "react";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function LoopNode({ id, data }: NodeProps<LoopNode>) { function LoopNode({ id, data }: NodeProps<LoopNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -32,6 +33,8 @@ function LoopNode({ id, data }: NodeProps<LoopNode>) {
}); });
const deleteNodeCallback = useDeleteNodeCallback(); const deleteNodeCallback = useDeleteNodeCallback();
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
const children = nodes.filter((node) => node.parentId === id); const children = nodes.filter((node) => node.parentId === id);
const furthestDownChild: Node | null = children.reduce( const furthestDownChild: Node | null = children.reduce(
(acc, child) => { (acc, child) => {
@@ -99,12 +102,18 @@ function LoopNode({ id, data }: NodeProps<LoopNode>) {
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300">Loop Value</Label> <div className="flex gap-2">
<HelpTooltip content={helpTooltips["loop"]["loopValue"]} /> <Label className="text-xs text-slate-300">Loop Value</Label>
<HelpTooltip content={helpTooltips["loop"]["loopValue"]} />
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<WorkflowBlockInput <WorkflowBlockInput
isFirstInputInNode
nodeId={id} nodeId={id}
value={inputs.loopVariableReference} value={inputs.loopVariableReference}
onChange={(value) => { onChange={(value) => {

View File

@@ -34,6 +34,7 @@ import type { NavigationNode } from "./types";
import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect"; import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect";
import { AppNode } from ".."; import { AppNode } from "..";
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils"; import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function NavigationNode({ id, data }: NodeProps<NavigationNode>) { function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -63,6 +64,8 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
const edges = useEdges(); const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id); const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
function handleChange(key: string, value: unknown) { function handleChange(key: string, value: unknown) {
if (!editable) { if (!editable) {
return; return;
@@ -113,12 +116,19 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
</header> </header>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300">URL</Label> <div className="flex gap-2">
<HelpTooltip content={helpTooltips["navigation"]["url"]} /> <Label className="text-xs text-slate-300">URL</Label>
<HelpTooltip content={helpTooltips["navigation"]["url"]} />
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<WorkflowBlockInputTextarea <WorkflowBlockInputTextarea
isFirstInputInNode
nodeId={id} nodeId={id}
onChange={(value) => { onChange={(value) => {
handleChange("url", value); handleChange("url", value);

View File

@@ -13,6 +13,7 @@ import { dataSchemaExampleForFileExtraction } from "../types";
import { WorkflowBlockIcon } from "../WorkflowBlockIcon"; import { WorkflowBlockIcon } from "../WorkflowBlockIcon";
import { type PDFParserNode } from "./types"; import { type PDFParserNode } from "./types";
import { WorkflowDataSchemaInputGroup } from "@/components/DataSchemaInputGroup/WorkflowDataSchemaInputGroup"; import { WorkflowDataSchemaInputGroup } from "@/components/DataSchemaInputGroup/WorkflowDataSchemaInputGroup";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function PDFParserNode({ id, data }: NodeProps<PDFParserNode>) { function PDFParserNode({ id, data }: NodeProps<PDFParserNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -34,6 +35,8 @@ function PDFParserNode({ id, data }: NodeProps<PDFParserNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
return ( return (
<div> <div>
<Handle <Handle
@@ -76,12 +79,18 @@ function PDFParserNode({ id, data }: NodeProps<PDFParserNode>) {
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300">File URL</Label> <div className="flex gap-2">
<HelpTooltip content={helpTooltips["pdfParser"]["fileUrl"]} /> <Label className="text-xs text-slate-300">File URL</Label>
<HelpTooltip content={helpTooltips["pdfParser"]["fileUrl"]} />
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<WorkflowBlockInput <WorkflowBlockInput
isFirstInputInNode
nodeId={id} nodeId={id}
value={inputs.fileUrl} value={inputs.fileUrl}
onChange={(value) => { onChange={(value) => {

View File

@@ -13,6 +13,7 @@ import { WorkflowBlockIcon } from "../WorkflowBlockIcon";
import { type SendEmailNode } from "./types"; import { type SendEmailNode } from "./types";
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput"; import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea"; import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function SendEmailNode({ id, data }: NodeProps<SendEmailNode>) { function SendEmailNode({ id, data }: NodeProps<SendEmailNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -36,6 +37,8 @@ function SendEmailNode({ id, data }: NodeProps<SendEmailNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
return ( return (
<div> <div>
<Handle <Handle
@@ -77,9 +80,15 @@ function SendEmailNode({ id, data }: NodeProps<SendEmailNode>) {
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-xs text-slate-300">Recipients</Label> <div className="flex justify-between">
<Label className="text-xs text-slate-300">Recipients</Label>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div>
<WorkflowBlockInput <WorkflowBlockInput
isFirstInputInNode
nodeId={id} nodeId={id}
onChange={(value) => { onChange={(value) => {
handleChange("recipients", value); handleChange("recipients", value);

View File

@@ -35,6 +35,7 @@ import { WorkflowBlockIcon } from "../WorkflowBlockIcon";
import { ParametersMultiSelect } from "./ParametersMultiSelect"; import { ParametersMultiSelect } from "./ParametersMultiSelect";
import type { TaskNode } from "./types"; import type { TaskNode } from "./types";
import { WorkflowDataSchemaInputGroup } from "@/components/DataSchemaInputGroup/WorkflowDataSchemaInputGroup"; import { WorkflowDataSchemaInputGroup } from "@/components/DataSchemaInputGroup/WorkflowDataSchemaInputGroup";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function TaskNode({ id, data }: NodeProps<TaskNode>) { function TaskNode({ id, data }: NodeProps<TaskNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -43,6 +44,7 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
const nodes = useNodes<AppNode>(); const nodes = useNodes<AppNode>();
const edges = useEdges(); const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id); const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
const [label, setLabel] = useNodeLabelChangeHandler({ const [label, setLabel] = useNodeLabelChangeHandler({
id, id,
@@ -121,12 +123,18 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
<AccordionContent className="pl-[1.5rem] pr-1"> <AccordionContent className="pl-[1.5rem] pr-1">
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300">URL</Label> <div className="flex gap-2">
<HelpTooltip content={helpTooltips["task"]["url"]} /> <Label className="text-xs text-slate-300">URL</Label>
<HelpTooltip content={helpTooltips["task"]["url"]} />
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<WorkflowBlockInputTextarea <WorkflowBlockInputTextarea
isFirstInputInNode
nodeId={id} nodeId={id}
onChange={(value) => { onChange={(value) => {
handleChange("url", value); handleChange("url", value);

View File

@@ -24,6 +24,7 @@ import { type TextPromptNode } from "./types";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea"; import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { WorkflowDataSchemaInputGroup } from "@/components/DataSchemaInputGroup/WorkflowDataSchemaInputGroup"; import { WorkflowDataSchemaInputGroup } from "@/components/DataSchemaInputGroup/WorkflowDataSchemaInputGroup";
import { dataSchemaExampleValue } from "../types"; import { dataSchemaExampleValue } from "../types";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function TextPromptNode({ id, data }: NodeProps<TextPromptNode>) { function TextPromptNode({ id, data }: NodeProps<TextPromptNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -51,6 +52,8 @@ function TextPromptNode({ id, data }: NodeProps<TextPromptNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
return ( return (
<div> <div>
<Handle <Handle
@@ -92,12 +95,19 @@ function TextPromptNode({ id, data }: NodeProps<TextPromptNode>) {
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300">Prompt</Label> <div className="flex gap-2">
<HelpTooltip content={helpTooltips["textPrompt"]["prompt"]} /> <Label className="text-xs text-slate-300">Prompt</Label>
<HelpTooltip content={helpTooltips["textPrompt"]["prompt"]} />
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<WorkflowBlockInputTextarea <WorkflowBlockInputTextarea
isFirstInputInNode
nodeId={id} nodeId={id}
onChange={(value) => { onChange={(value) => {
handleChange("prompt", value); handleChange("prompt", value);

View File

@@ -32,6 +32,7 @@ import type { ValidationNode } from "./types";
import { AppNode } from ".."; import { AppNode } from "..";
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils"; import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect"; import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function ValidationNode({ id, data }: NodeProps<ValidationNode>) { function ValidationNode({ id, data }: NodeProps<ValidationNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -58,6 +59,8 @@ function ValidationNode({ id, data }: NodeProps<ValidationNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
return ( return (
<div> <div>
<Handle <Handle
@@ -99,9 +102,15 @@ function ValidationNode({ id, data }: NodeProps<ValidationNode>) {
/> />
</header> </header>
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-xs text-slate-300">Complete if...</Label> <div className="flex justify-between">
<Label className="text-xs text-slate-300">Complete if...</Label>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div>
<WorkflowBlockInputTextarea <WorkflowBlockInputTextarea
isFirstInputInNode
nodeId={id} nodeId={id}
onChange={(value) => { onChange={(value) => {
handleChange("completeCriterion", value); handleChange("completeCriterion", value);

View File

@@ -11,6 +11,7 @@ import { NodeActionMenu } from "../NodeActionMenu";
import { WorkflowBlockIcon } from "../WorkflowBlockIcon"; import { WorkflowBlockIcon } from "../WorkflowBlockIcon";
import type { WaitNode } from "./types"; import type { WaitNode } from "./types";
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput"; import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
function WaitNode({ id, data }: NodeProps<WaitNode>) { function WaitNode({ id, data }: NodeProps<WaitNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
@@ -32,6 +33,8 @@ function WaitNode({ id, data }: NodeProps<WaitNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
return ( return (
<div> <div>
<Handle <Handle
@@ -73,15 +76,22 @@ function WaitNode({ id, data }: NodeProps<WaitNode>) {
/> />
</header> </header>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex justify-between">
<Label className="text-xs text-slate-300"> <div className="flex gap-2">
Wait Time (in seconds) <Label className="text-xs text-slate-300">
</Label> Wait Time (in seconds)
<HelpTooltip content={helpTooltips["wait"]["waitInSeconds"]} /> </Label>
<HelpTooltip content={helpTooltips["wait"]["waitInSeconds"]} />
</div>
{isFirstWorkflowBlock ? (
<div className="flex justify-end text-xs text-slate-400">
Tip: Use the {"+"} button to add parameters!
</div>
) : null}
</div> </div>
<WorkflowBlockInput <WorkflowBlockInput
nodeId={id} nodeId={id}
isFirstInputInNode
type="number" type="number"
min="1" min="1"
max="300" max="300"