源码
packages/ui/src/components/ToolFailure.tsx
// Error tooltip presentation follows ZCode ToolCallBlocks/ToolLayout.tsx (Apache-2.0).
import * as Tooltip from "@radix-ui/react-tooltip";
import { Check, Copy } from "lucide-react";
import { useEffect, useRef, useState } from "react";
export function ToolFailure({ text, onCopy }: { text: string; onCopy?: (text: string) => Promise<void> }) {
const [copied, setCopied] = useState(false);
const [error, setError] = useState(false);
const resetCopy = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
useEffect(() => () => clearTimeout(resetCopy.current), []);
return (
<Tooltip.Provider delayDuration={0}>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<span className="tool-failure">执行失败</span>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
side="top"
align="start"
sideOffset={4}
collisionPadding={12}
className="tool-error-tooltip"
>
<pre className="tool-error-content">{text}</pre>
<button
type="button"
className="tool-error-copy"
disabled={!onCopy}
aria-label={copied ? "已复制错误" : "复制错误详情"}
title={copied ? "已复制" : "复制错误详情"}
onClick={async (event) => {
event.preventDefault();
event.stopPropagation();
try {
await onCopy?.(text);
setCopied(true);
setError(false);
clearTimeout(resetCopy.current);
resetCopy.current = setTimeout(() => setCopied(false), 1500);
} catch {
setError(true);
}
}}
>
{copied ? <Check size={12} /> : <Copy size={12} />}
</button>
{error && <small role="alert">复制失败,请重试</small>}
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
}