Show permanent tooltip for parameters on the first block of the workflow (#1616)
This commit is contained in:
@@ -3,14 +3,43 @@ import { cn } from "@/util/utils";
|
||||
import { Input } from "./ui/input";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||
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"> & {
|
||||
onChange: (value: string) => void;
|
||||
nodeId: string;
|
||||
isFirstInputInNode?: boolean;
|
||||
};
|
||||
|
||||
function WorkflowBlockInput(props: 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 (
|
||||
<div className="relative">
|
||||
@@ -23,11 +52,19 @@ function WorkflowBlockInput(props: Props) {
|
||||
/>
|
||||
<div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="rounded p-1 hover:bg-muted" title="Add a parameter">
|
||||
<PlusIcon className="size-4" />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={showInputTooltip}>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="rounded p-1 hover:bg-muted">
|
||||
<PlusIcon className="size-4" />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Add parameters using the + button</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<PopoverContent>
|
||||
<WorkflowBlockParameterSelect
|
||||
nodeId={nodeId}
|
||||
|
||||
@@ -3,6 +3,13 @@ import { cn } from "@/util/utils";
|
||||
import { AutoResizingTextarea } from "./AutoResizingTextarea/AutoResizingTextarea";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||
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<
|
||||
React.ComponentProps<typeof AutoResizingTextarea>,
|
||||
@@ -10,10 +17,32 @@ type Props = Omit<
|
||||
> & {
|
||||
onChange: (value: string) => void;
|
||||
nodeId: string;
|
||||
isFirstInputInNode?: boolean;
|
||||
};
|
||||
|
||||
function WorkflowBlockInputTextarea(props: 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 (
|
||||
<div className="relative">
|
||||
@@ -26,11 +55,18 @@ function WorkflowBlockInputTextarea(props: Props) {
|
||||
/>
|
||||
<div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="rounded p-1 hover:bg-muted" title="Add a parameter">
|
||||
<PlusIcon className="size-4" />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={showInputTooltip}>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="rounded p-1 hover:bg-muted">
|
||||
<PlusIcon className="size-4" />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Add parameters using the + button</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<PopoverContent>
|
||||
<WorkflowBlockParameterSelect
|
||||
nodeId={nodeId}
|
||||
|
||||
@@ -108,6 +108,7 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
|
||||
<HelpTooltip content={urlTooltip} />
|
||||
</div>
|
||||
<WorkflowBlockInputTextarea
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("url", value);
|
||||
|
||||
@@ -97,6 +97,7 @@ function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
|
||||
/>
|
||||
</div>
|
||||
<WorkflowBlockInputTextarea
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
if (!editable) {
|
||||
|
||||
@@ -105,6 +105,7 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||
<HelpTooltip content={urlTooltip} />
|
||||
</div>
|
||||
<WorkflowBlockInputTextarea
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("url", value);
|
||||
|
||||
@@ -102,6 +102,7 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||
<HelpTooltip content={helpTooltips["login"]["url"]} />
|
||||
</div>
|
||||
<WorkflowBlockInputTextarea
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("url", value);
|
||||
|
||||
@@ -104,6 +104,7 @@ function LoopNode({ id, data }: NodeProps<LoopNode>) {
|
||||
<HelpTooltip content={helpTooltips["loop"]["loopValue"]} />
|
||||
</div>
|
||||
<WorkflowBlockInput
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
value={inputs.loopVariableReference}
|
||||
onChange={(value) => {
|
||||
|
||||
@@ -104,6 +104,7 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||
<HelpTooltip content={helpTooltips["navigation"]["url"]} />
|
||||
</div>
|
||||
<WorkflowBlockInputTextarea
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("url", value);
|
||||
|
||||
@@ -82,6 +82,7 @@ function PDFParserNode({ id, data }: NodeProps<PDFParserNode>) {
|
||||
<HelpTooltip content={helpTooltips["pdfParser"]["fileUrl"]} />
|
||||
</div>
|
||||
<WorkflowBlockInput
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
value={inputs.fileUrl}
|
||||
onChange={(value) => {
|
||||
|
||||
@@ -79,6 +79,7 @@ function SendEmailNode({ id, data }: NodeProps<SendEmailNode>) {
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs text-slate-300">Recipients</Label>
|
||||
<WorkflowBlockInput
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("recipients", value);
|
||||
|
||||
@@ -125,6 +125,7 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||
<HelpTooltip content={helpTooltips["task"]["url"]} />
|
||||
</div>
|
||||
<WorkflowBlockInputTextarea
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("url", value);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@@ -24,6 +23,7 @@ import { NodeActionMenu } from "../NodeActionMenu";
|
||||
import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect";
|
||||
import { WorkflowBlockIcon } from "../WorkflowBlockIcon";
|
||||
import { type TextPromptNode } from "./types";
|
||||
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||
|
||||
function TextPromptNode({ id, data }: NodeProps<TextPromptNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -88,13 +88,15 @@ function TextPromptNode({ id, data }: NodeProps<TextPromptNode>) {
|
||||
<Label className="text-xs text-slate-300">Prompt</Label>
|
||||
<HelpTooltip content={helpTooltips["textPrompt"]["prompt"]} />
|
||||
</div>
|
||||
<AutoResizingTextarea
|
||||
onChange={(event) => {
|
||||
<WorkflowBlockInputTextarea
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
if (!editable) {
|
||||
return;
|
||||
}
|
||||
setInputs({ ...inputs, prompt: event.target.value });
|
||||
updateNodeData(id, { prompt: event.target.value });
|
||||
setInputs({ ...inputs, prompt: value });
|
||||
updateNodeData(id, { prompt: value });
|
||||
}}
|
||||
value={inputs.prompt}
|
||||
placeholder="What do you want to generate?"
|
||||
|
||||
@@ -88,6 +88,7 @@ function ValidationNode({ id, data }: NodeProps<ValidationNode>) {
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs text-slate-300">Complete if...</Label>
|
||||
<WorkflowBlockInputTextarea
|
||||
isFirstInputInNode
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("completeCriterion", value);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
||||
import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler";
|
||||
@@ -11,6 +10,7 @@ import { EditableNodeTitle } from "../components/EditableNodeTitle";
|
||||
import { NodeActionMenu } from "../NodeActionMenu";
|
||||
import { WorkflowBlockIcon } from "../WorkflowBlockIcon";
|
||||
import type { WaitNode } from "./types";
|
||||
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
|
||||
|
||||
function WaitNode({ id, data }: NodeProps<WaitNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -79,16 +79,18 @@ function WaitNode({ id, data }: NodeProps<WaitNode>) {
|
||||
</Label>
|
||||
<HelpTooltip content={helpTooltips["wait"]["waitInSeconds"]} />
|
||||
</div>
|
||||
<Input
|
||||
<WorkflowBlockInput
|
||||
nodeId={id}
|
||||
isFirstInputInNode
|
||||
type="number"
|
||||
min="1"
|
||||
max="300"
|
||||
value={inputs.waitInSeconds}
|
||||
onChange={(event) => {
|
||||
onChange={(value) => {
|
||||
if (!editable) {
|
||||
return;
|
||||
}
|
||||
handleChange("waitInSeconds", Number(event.target.value));
|
||||
handleChange("waitInSeconds", Number(value));
|
||||
}}
|
||||
className="nopan text-xs"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user