FE implementation of InteractionNode (#3821)
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { getClient } from "@/api/AxiosClient";
|
||||
import { Status as WorkflowRunStatus } from "@/api/types";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||
import { cn } from "@/util/utils";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
||||
import { HumanInteractionBlock } from "../types/workflowTypes";
|
||||
|
||||
interface Props {
|
||||
humanInteractionBlock: HumanInteractionBlock;
|
||||
}
|
||||
|
||||
export function WorkflowRunHumanInteraction({ humanInteractionBlock }: Props) {
|
||||
const credentialGetter = useCredentialGetter();
|
||||
const queryClient = useQueryClient();
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const isPaused =
|
||||
workflowRun && workflowRun.status === WorkflowRunStatus.Paused;
|
||||
|
||||
const buttonLayout =
|
||||
humanInteractionBlock.positive_descriptor.length < 8 &&
|
||||
humanInteractionBlock.negative_descriptor.length < 8
|
||||
? "inline"
|
||||
: "stacked";
|
||||
|
||||
const approveMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!workflowRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = await getClient(credentialGetter, "sans-api-v1");
|
||||
|
||||
return await client.post(
|
||||
`/workflows/runs/${workflowRun.workflow_run_id}/continue`,
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["workflowRun"],
|
||||
});
|
||||
|
||||
toast({
|
||||
variant: "success",
|
||||
title: `${humanInteractionBlock.positive_descriptor}`,
|
||||
description: `Successfully chose: ${humanInteractionBlock.positive_descriptor}`,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Interaction Failed",
|
||||
description: error.message,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const rejectMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!workflowRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = await getClient(credentialGetter);
|
||||
|
||||
return await client.post(
|
||||
`/workflows/runs/${workflowRun.workflow_run_id}/cancel`,
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["workflowRun"],
|
||||
});
|
||||
|
||||
toast({
|
||||
variant: "success",
|
||||
title: `${humanInteractionBlock.negative_descriptor}`,
|
||||
description: `Successfully chose: ${humanInteractionBlock.negative_descriptor}`,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Interaction Failed",
|
||||
description: error.message,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (!isPaused) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-4 flex flex-col gap-4 rounded-md bg-slate-elevation4 p-4">
|
||||
<div className="text-sm">{humanInteractionBlock.instructions}</div>
|
||||
<div
|
||||
className={cn("flex gap-2", {
|
||||
"justify-between": buttonLayout === "inline",
|
||||
"flex-col": buttonLayout === "stacked",
|
||||
})}
|
||||
>
|
||||
<Button variant="destructive" onClick={() => rejectMutation.mutate()}>
|
||||
<div>{humanInteractionBlock.negative_descriptor}</div>
|
||||
</Button>
|
||||
<Button variant="default" onClick={() => approveMutation.mutate()}>
|
||||
<div>{humanInteractionBlock.positive_descriptor}</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,12 @@ import {
|
||||
CubeIcon,
|
||||
ExternalLinkIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import { useCallback } from "react";
|
||||
import { useParams, Link } from "react-router-dom";
|
||||
|
||||
import { Status } from "@/api/types";
|
||||
import { formatDuration, toDuration } from "@/routes/workflows/utils";
|
||||
import { cn } from "@/util/utils";
|
||||
import { workflowBlockTitle } from "../editor/nodes/types";
|
||||
import { WorkflowBlockIcon } from "../editor/nodes/WorkflowBlockIcon";
|
||||
import {
|
||||
@@ -20,14 +26,16 @@ import {
|
||||
ActionItem,
|
||||
WorkflowRunOverviewActiveElement,
|
||||
} from "./WorkflowRunOverview";
|
||||
import { cn } from "@/util/utils";
|
||||
import { isTaskVariantBlock } from "../types/workflowTypes";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useCallback } from "react";
|
||||
import { Status } from "@/api/types";
|
||||
import { formatDuration, toDuration } from "@/routes/workflows/utils";
|
||||
import { ThoughtCard } from "./ThoughtCard";
|
||||
import { useWorkflowQuery } from "../hooks/useWorkflowQuery";
|
||||
import { ObserverThought } from "../types/workflowRunTypes";
|
||||
import {
|
||||
HumanInteractionBlock,
|
||||
isTaskVariantBlock,
|
||||
type WorkflowApiResponse,
|
||||
} from "../types/workflowTypes";
|
||||
import { WorkflowRunHumanInteraction } from "./WorkflowRunHumanInteraction";
|
||||
|
||||
type Props = {
|
||||
activeItem: WorkflowRunOverviewActiveElement;
|
||||
block: WorkflowRunBlock;
|
||||
@@ -37,6 +45,28 @@ type Props = {
|
||||
onThoughtCardClick: (thought: ObserverThought) => void;
|
||||
};
|
||||
|
||||
const getHumanInteractionBlock = (
|
||||
block: WorkflowRunBlock,
|
||||
workflow?: WorkflowApiResponse,
|
||||
): HumanInteractionBlock | null => {
|
||||
if (block.block_type !== "human_interaction") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!workflow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const blocks = workflow.workflow_definition.blocks;
|
||||
const candidate = blocks.find((b) => b.label === block.label);
|
||||
|
||||
if (!candidate || candidate.block_type !== "human_interaction") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return candidate as HumanInteractionBlock;
|
||||
};
|
||||
|
||||
function WorkflowRunTimelineBlockItem({
|
||||
activeItem,
|
||||
block,
|
||||
@@ -45,6 +75,12 @@ function WorkflowRunTimelineBlockItem({
|
||||
onActionClick,
|
||||
onThoughtCardClick,
|
||||
}: Props) {
|
||||
const { workflowPermanentId } = useParams();
|
||||
const { data: workflow } = useWorkflowQuery({
|
||||
workflowPermanentId,
|
||||
});
|
||||
|
||||
const humanInteractionBlock = getHumanInteractionBlock(block, workflow);
|
||||
const actions = block.actions ?? [];
|
||||
|
||||
const hasActiveAction =
|
||||
@@ -167,6 +203,12 @@ function WorkflowRunTimelineBlockItem({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{humanInteractionBlock && (
|
||||
<WorkflowRunHumanInteraction
|
||||
humanInteractionBlock={humanInteractionBlock}
|
||||
/>
|
||||
)}
|
||||
|
||||
{actions.map((action, index) => {
|
||||
return (
|
||||
<ActionCard
|
||||
|
||||
Reference in New Issue
Block a user