various Validation Block buffs (#3919)
This commit is contained in:
92
skyvern-frontend/src/components/NoticeMe.tsx
Normal file
92
skyvern-frontend/src/components/NoticeMe.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
trigger: "render" | "viewport";
|
||||||
|
}
|
||||||
|
|
||||||
|
function NoticeMe({ children, trigger }: React.PropsWithChildren<Props>) {
|
||||||
|
const [shouldAnimate, setShouldAnimate] = useState(trigger === "render");
|
||||||
|
const [shouldHide, setShouldHide] = useState(false);
|
||||||
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
|
const hasExitedRef = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (trigger !== "viewport") return;
|
||||||
|
|
||||||
|
const element = elementRef.current;
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
// Element is visible in viewport
|
||||||
|
if (hasExitedRef.current) {
|
||||||
|
// Force animation restart by removing then re-adding class
|
||||||
|
setShouldHide(false);
|
||||||
|
setShouldAnimate(false);
|
||||||
|
// Use setTimeout to ensure the class is removed before re-adding
|
||||||
|
setTimeout(() => {
|
||||||
|
setShouldAnimate(true);
|
||||||
|
}, 10);
|
||||||
|
hasExitedRef.current = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Element is NOT visible in viewport (completely outside)
|
||||||
|
setShouldAnimate(false);
|
||||||
|
setShouldHide(true);
|
||||||
|
hasExitedRef.current = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: 0,
|
||||||
|
rootMargin: "0px",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
observer.observe(element);
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, [trigger]);
|
||||||
|
|
||||||
|
const getAnimationClass = () => {
|
||||||
|
if (shouldHide) return "notice-me-hidden";
|
||||||
|
if (shouldAnimate) return "notice-me-animate";
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
ref={elementRef}
|
||||||
|
className={getAnimationClass()}
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<style>{`
|
||||||
|
.notice-me-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-me-animate {
|
||||||
|
animation: notice-fade-up 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes notice-fade-up {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { NoticeMe };
|
||||||
@@ -61,7 +61,7 @@ function MicroDropdown({ selections, selected, onChange }: Props) {
|
|||||||
<div ref={dropdownRef} className="relative inline-block">
|
<div ref={dropdownRef} className="relative inline-block">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div
|
<div
|
||||||
className="relative inline-flex p-0 text-xs text-slate-400"
|
className="relative inline-flex p-0 text-xs text-[#00d2ff]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isOpen && dropdownRef.current) {
|
if (!isOpen && dropdownRef.current) {
|
||||||
const rect = dropdownRef.current.getBoundingClientRect();
|
const rect = dropdownRef.current.getBoundingClientRect();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
|
|
||||||
import { getClient } from "@/api/AxiosClient";
|
import { getClient } from "@/api/AxiosClient";
|
||||||
import { ProxyLocation, Status } from "@/api/types";
|
import { ProxyLocation, Status } from "@/api/types";
|
||||||
|
import { NoticeMe } from "@/components/NoticeMe";
|
||||||
import { StatusBadge } from "@/components/StatusBadge";
|
import { StatusBadge } from "@/components/StatusBadge";
|
||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
import { useLogging } from "@/hooks/useLogging";
|
import { useLogging } from "@/hooks/useLogging";
|
||||||
@@ -531,24 +532,26 @@ function NodeHeader({
|
|||||||
<span className="text-xs text-slate-400">
|
<span className="text-xs text-slate-400">
|
||||||
{transmutations.blockTitle}
|
{transmutations.blockTitle}
|
||||||
</span>
|
</span>
|
||||||
<MicroDropdown
|
<NoticeMe trigger="viewport">
|
||||||
selections={[
|
<MicroDropdown
|
||||||
transmutations.self,
|
selections={[
|
||||||
...transmutations.others.map((t) => t.label),
|
transmutations.self,
|
||||||
]}
|
...transmutations.others.map((t) => t.label),
|
||||||
selected={transmutations.self}
|
]}
|
||||||
onChange={(label) => {
|
selected={transmutations.self}
|
||||||
const transmutation = transmutations.others.find(
|
onChange={(label) => {
|
||||||
(t) => t.label === label,
|
const transmutation = transmutations.others.find(
|
||||||
);
|
(t) => t.label === label,
|
||||||
|
);
|
||||||
|
|
||||||
if (!transmutation) {
|
if (!transmutation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
transmuteNodeCallback(nodeId, transmutation.nodeName);
|
transmuteNodeCallback(nodeId, transmutation.nodeName);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</NoticeMe>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-xs text-slate-400">{blockTitle}</span>
|
<span className="text-xs text-slate-400">{blockTitle}</span>
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ const nodeLibraryItems: Array<{
|
|||||||
className="size-6"
|
className="size-6"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
title: "Validation Block",
|
title: "AI or Human Validation",
|
||||||
description: "Validate completion criteria",
|
description: "Have an AI or Human validate the state of the screen",
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* The Human Interaction block can be had via a transmutation of the
|
* The Human Interaction block can be had via a transmutation of the
|
||||||
|
|||||||
Reference in New Issue
Block a user