From db2f21191c3a617c2927721de8d5c4786086db92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Fri, 3 Jul 2026 16:49:32 +0800 Subject: [PATCH] fix(frontend): handle clipboard fallback and markdown word export --- .../src/components/ai-elements/code-block.tsx | 24 +++++----- .../artifacts/artifact-file-detail.tsx | 9 ++-- .../src/components/workspace/copy-button.tsx | 32 ++++++++------ frontend-web/src/core/clipboard.ts | 44 +++++++++++++++++++ .../strategy-components/MarkdownRenderer.tsx | 7 +-- .../components/chat-qa/GeneralQA.tsx | 7 +-- 6 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 frontend-web/src/core/clipboard.ts diff --git a/frontend-web/src/components/ai-elements/code-block.tsx b/frontend-web/src/components/ai-elements/code-block.tsx index d490eeda..bce6fe3e 100644 --- a/frontend-web/src/components/ai-elements/code-block.tsx +++ b/frontend-web/src/components/ai-elements/code-block.tsx @@ -1,7 +1,8 @@ "use client"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { copyTextToClipboard } from "@/core/clipboard"; +import { cn } from "@/lib/utils"; import { CheckIcon, CopyIcon } from "lucide-react"; import { type ComponentProps, @@ -144,18 +145,13 @@ export const CodeBlockCopyButton = ({ ...props }: CodeBlockCopyButtonProps) => { 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; - } - - try { - await navigator.clipboard.writeText(code); - setIsCopied(true); - onCopy?.(); + const { code } = useContext(CodeBlockContext); + + const copyToClipboard = async () => { + try { + await copyTextToClipboard(code); + setIsCopied(true); + onCopy?.(); setTimeout(() => setIsCopied(false), timeout); } catch (error) { onError?.(error as Error); diff --git a/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx index 73f1f786..3cc2a027 100644 --- a/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -28,7 +28,7 @@ import { ArtifactHeader, ArtifactTitle, } from "@/components/ai-elements/artifact"; -import { Select, SelectItem } from "@/components/ui/select"; +import { Select, SelectItem } from "@/components/ui/select"; import { SelectContent, SelectGroup, @@ -39,7 +39,8 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { CodeEditor } from "@/components/workspace/code-editor"; import { publishArtifactToKnowledge } from "@/core/agents/api"; import { useAgent } from "@/core/agents/hooks"; -import { apiFetch } from "@/core/api"; +import { apiFetch } from "@/core/api"; +import { copyTextToClipboard } from "@/core/clipboard"; import { downloadArtifactFile, } from "@/core/artifacts/download"; @@ -682,7 +683,7 @@ export function ArtifactFileDetail({ disabled={!content} onClick={async () => { try { - await navigator.clipboard.writeText(displayContent ?? ""); + await copyTextToClipboard(displayContent ?? ""); toast.success(t.clipboard.copiedToClipboard); } catch (error) { toast.error("Failed to copy to clipboard"); @@ -692,7 +693,7 @@ export function ArtifactFileDetail({ tooltip={t.clipboard.copyToClipboard} /> )} - {!useWriteFileStreaming && language === "markdown" && ( + {language === "markdown" && ( & { clipboardData: string; }) { - const { t } = useI18n(); - const [copied, setCopied] = useState(false); - const handleCopy = useCallback(() => { - void navigator.clipboard.writeText(clipboardData); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }, [clipboardData]); - return ( + const { t } = useI18n(); + const [copied, setCopied] = useState(false); + const handleCopy = useCallback(async () => { + try { + await copyTextToClipboard(clipboardData); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + toast.error(t.clipboard.failedToCopyToClipboard); + } + }, [clipboardData, t.clipboard.failedToCopyToClipboard]); + return (