Spaces:
Sleeping
Sleeping
File size: 4,975 Bytes
05c5ed5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | import { useCopy } from "@/hooks/use-copy";
import { VercelAIWorkflowToolStreamingResult } from "app-types/workflow";
import equal from "lib/equal";
import { AlertTriangleIcon, Check, Copy, Loader2, XIcon } from "lucide-react";
import { memo, useEffect, useMemo, useRef } from "react";
import { Alert, AlertDescription, AlertTitle } from "ui/alert";
import { Button } from "ui/button";
import JsonView from "ui/json-view";
import { NodeResultPopup } from "../workflow/node-result-popup";
import { cn } from "lib/utils";
import { NodeIcon } from "../workflow/node-icon";
import { TextShimmer } from "ui/text-shimmer";
interface WorkflowInvocationProps {
result: VercelAIWorkflowToolStreamingResult;
}
function PureWorkflowInvocation({ result }: WorkflowInvocationProps) {
const { copied, copy } = useCopy();
const savedResult = useRef<VercelAIWorkflowToolStreamingResult>(result);
const output = useMemo(() => {
if (result.status == "running") return null;
if (result.status == "fail")
return (
<Alert variant={"destructive"} className="border-destructive">
<AlertTriangleIcon className="size-3" />
<AlertTitle>{result?.error?.name || "ERROR"}</AlertTitle>
<AlertDescription>{result.error?.message}</AlertDescription>
</Alert>
);
if (!result.result) return null;
return (
<div className="w-full bg-card p-4 border text-xs rounded-lg text-muted-foreground">
<div className="flex items-center">
<h5 className="text-muted-foreground font-medium select-none">
Response
</h5>
<div className="flex-1" />
{copied ? (
<Check className="size-3" />
) : (
<Button
variant="ghost"
size="icon"
className="size-3 text-muted-foreground"
onClick={() => copy(JSON.stringify(result.result))}
>
<Copy className="size-3" />
</Button>
)}
</div>
<div className="p-2 max-h-[300px] overflow-y-auto">
<JsonView data={result.result} />
</div>
</div>
);
}, [result.status, result.error, result.result, copied]);
useEffect(() => {
if (result.status == "running") {
savedResult.current = result;
}
}, [result]);
return (
<div className="w-full flex flex-col gap-1">
{result.history.map((item, i) => {
const result = item.result || savedResult.current.history[i]?.result;
return (
<NodeResultPopup
key={item.id}
disabled={!result}
history={{
name: item.name,
status: item.status,
startedAt: item.startedAt,
endedAt: item.endedAt,
error: item.error?.message,
result,
}}
>
<div
key={item.id}
className={cn(
"flex items-center gap-2 text-sm rounded-sm px-2 py-1.5 relative",
item.status == "fail" && "text-destructive",
!!result && "cursor-pointer hover:bg-secondary",
)}
>
<div className="border rounded overflow-hidden">
<NodeIcon
type={item.kind}
iconClassName="size-3"
className="rounded-none"
/>
</div>
{item.status == "running" ? (
<TextShimmer className="font-semibold">
{`${item.name} Running...`}
</TextShimmer>
) : (
<span className="font-semibold">{item.name}</span>
)}
<span
className={cn(
"ml-auto text-xs",
item.status != "fail" && "text-muted-foreground",
)}
>
{item.status != "running" &&
((item.endedAt! - item.startedAt!) / 1000).toFixed(2)}
</span>
{item.status == "success" ? (
<Check className="size-3" />
) : item.status == "fail" ? (
<XIcon className="size-3" />
) : (
<Loader2 className="size-3 animate-spin" />
)}
</div>
</NodeResultPopup>
);
})}
<div className="mt-2">{output}</div>
</div>
);
}
function areEqual(
prev: WorkflowInvocationProps,
next: WorkflowInvocationProps,
) {
if (prev.result.status != next.result.status) return false;
if (prev.result.error?.message != next.result.error?.message) return false;
if (prev.result.result != next.result.result) return false;
if (!equal(prev.result.history, next.result.history)) return false;
if (!equal(prev.result.result, next.result.result)) return false;
return true;
}
export const WorkflowInvocation = memo(PureWorkflowInvocation, areEqual);
|