diff --git a/frontend/src/components/ai-elements/code-block.tsx b/frontend/src/components/ai-elements/code-block.tsx index c04602380..38a82cfe0 100644 --- a/frontend/src/components/ai-elements/code-block.tsx +++ b/frontend/src/components/ai-elements/code-block.tsx @@ -1,6 +1,7 @@ "use client"; import { Button } from "@/components/ui/button"; +import { writeTextToClipboard } from "@/core/clipboard"; import { cn } from "@/lib/utils"; import { CheckIcon, CopyIcon } from "lucide-react"; import { @@ -146,20 +147,20 @@ export const CodeBlockCopyButton = ({ const [isCopied, setIsCopied] = useState(false); const { code } = useContext(CodeBlockContext); - const copyToClipboard = async () => { - if (typeof window === "undefined" || !navigator?.clipboard?.writeText) { - onError?.(new Error("Clipboard API not available")); - return; - } + const copyToClipboard = () => { + void (async () => { + const didCopy = await writeTextToClipboard(code); + if (!didCopy) { + onError?.(new Error("Clipboard API not available")); + return; + } - try { - await navigator.clipboard.writeText(code); setIsCopied(true); onCopy?.(); setTimeout(() => setIsCopied(false), timeout); - } catch (error) { + })().catch((error) => { onError?.(error as Error); - } + }); }; const Icon = isCopied ? CheckIcon : CopyIcon; diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx index 46ae18441..70d1d2d88 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -38,6 +38,7 @@ import { HTML_PREVIEW_SCROLL_MESSAGE_SOURCE, } from "@/core/artifacts/preview"; import { urlOfArtifact } from "@/core/artifacts/utils"; +import { writeTextToClipboard } from "@/core/clipboard"; import { useI18n } from "@/core/i18n/hooks"; import { findToolCallResult } from "@/core/messages/utils"; import { installSkill } from "@/core/skills/api"; @@ -237,14 +238,20 @@ export function ArtifactFileDetail({ icon={CopyIcon} label={t.clipboard.copyToClipboard} disabled={!content} - onClick={async () => { - try { - await navigator.clipboard.writeText(visibleContent ?? ""); + onClick={() => { + void (async () => { + const didCopy = await writeTextToClipboard( + visibleContent ?? "", + ); + if (!didCopy) { + toast.error(t.clipboard.failedToCopyToClipboard); + return; + } + toast.success(t.clipboard.copiedToClipboard); - } catch (error) { - toast.error("Failed to copy to clipboard"); - console.error(error); - } + })().catch(() => { + toast.error(t.clipboard.failedToCopyToClipboard); + }); }} tooltip={t.clipboard.copyToClipboard} /> diff --git a/frontend/src/components/workspace/copy-button.tsx b/frontend/src/components/workspace/copy-button.tsx index 13d331b57..e85313cb7 100644 --- a/frontend/src/components/workspace/copy-button.tsx +++ b/frontend/src/components/workspace/copy-button.tsx @@ -1,7 +1,9 @@ import { CheckIcon, CopyIcon } from "lucide-react"; import { useCallback, useState, type ComponentProps } from "react"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; +import { writeTextToClipboard } from "@/core/clipboard"; import { useI18n } from "@/core/i18n/hooks"; import { Tooltip } from "./tooltip"; @@ -15,10 +17,19 @@ export function CopyButton({ const { t } = useI18n(); const [copied, setCopied] = useState(false); const handleCopy = useCallback(() => { - void navigator.clipboard.writeText(clipboardData); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }, [clipboardData]); + void (async () => { + const didCopy = await writeTextToClipboard(clipboardData); + if (!didCopy) { + toast.error(t.clipboard.failedToCopyToClipboard); + return; + } + + setCopied(true); + setTimeout(() => setCopied(false), 2000); + })().catch(() => { + toast.error(t.clipboard.failedToCopyToClipboard); + }); + }, [clipboardData, t.clipboard.failedToCopyToClipboard]); return (