From 8dac8562583a9376228f847f686282c3f84f74c6 Mon Sep 17 00:00:00 2001 From: MT-Mint <798521692@qq.com> Date: Mon, 23 Mar 2026 13:34:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=90=91=E4=B8=BB=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=8F=91=E9=80=81=E4=BF=A1=E6=81=AF=EF=BC=9B=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?user=E7=9A=84=E4=BF=A1=E6=81=AF=E7=A6=BB=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E5=A4=AA=E8=BF=9C=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/workspace/chats/[thread_id]/page.tsx | 2 +- .../src/components/ai-elements/code-block.tsx | 13 ++---- .../src/components/ai-elements/message.tsx | 2 +- .../artifacts/artifact-file-detail.tsx | 4 +- .../src/components/workspace/copy-button.tsx | 3 +- .../workspace/iframe-test-panel.tsx | 44 +++++++++++++++++++ .../components/workspace/recent-chat-list.tsx | 3 +- frontend/src/lib/utils.ts | 23 ++++++++++ 8 files changed, 79 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index e7bc36cf..c3d2697c 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -549,7 +549,7 @@ export default function ChatPage() { {/* MARK: 开发测试:iframe 通信功能测试面板 */} - {/* */} + ); diff --git a/frontend/src/components/ai-elements/code-block.tsx b/frontend/src/components/ai-elements/code-block.tsx index c0460238..0f227cc2 100644 --- a/frontend/src/components/ai-elements/code-block.tsx +++ b/frontend/src/components/ai-elements/code-block.tsx @@ -1,7 +1,7 @@ "use client"; import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; +import { cn, copyToClipboard } from "@/lib/utils"; import { CheckIcon, CopyIcon } from "lucide-react"; import { type ComponentProps, @@ -146,14 +146,9 @@ 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 handleCopy = async () => { try { - await navigator.clipboard.writeText(code); + await copyToClipboard(code); setIsCopied(true); onCopy?.(); setTimeout(() => setIsCopied(false), timeout); @@ -167,7 +162,7 @@ export const CodeBlockCopyButton = ({ return ( +
+ {isInIframe + ? "将通过 postMessage 请求父页面复制" + : "将直接调用 navigator.clipboard"} +
+ + + {/* 日志 */} {log.length > 0 && (
diff --git a/frontend/src/components/workspace/recent-chat-list.tsx b/frontend/src/components/workspace/recent-chat-list.tsx index 220aee22..546b1f46 100644 --- a/frontend/src/components/workspace/recent-chat-list.tsx +++ b/frontend/src/components/workspace/recent-chat-list.tsx @@ -39,6 +39,7 @@ import { } from "@/core/threads/hooks"; import { pathOfThread, titleOfThread } from "@/core/threads/utils"; import { env } from "@/env"; +import { copyToClipboard } from "@/lib/utils"; export function RecentChatList() { const { t } = useI18n(); @@ -102,7 +103,7 @@ export function RecentChatList() { const baseUrl = isLocalhost ? VERCEL_URL : window.location.origin; const shareUrl = `${baseUrl}/workspace/chats/${threadId}`; try { - await navigator.clipboard.writeText(shareUrl); + await copyToClipboard(shareUrl); toast.success(t.clipboard.linkCopied); } catch { toast.error(t.clipboard.failedToCopyToClipboard); diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index f8ff63a2..991b94bf 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -10,3 +10,26 @@ export const externalLinkClass = "text-primary underline underline-offset-2 hover:no-underline"; /** Link style without underline by default (e.g. for streaming/loading). */ export const externalLinkClassNoUnderline = "text-primary hover:underline"; + +/** + * Copy text to clipboard, using postMessage when in iframe. + * In iframe context, sends message to parent window to handle clipboard operation. + */ +export async function copyToClipboard(text: string): Promise { + const isInIframe = window.self !== window.top; + const message = { + type: "copyToClipboard", + data: text, + }; + + if (isInIframe && window.parent) { + // Request parent window to copy + window.parent.postMessage(message, "*"); + console.log("[copyToClipboard] iframe mode → postMessage to parent", message); + return; + } + + // Direct clipboard access when not in iframe + console.log("[copyToClipboard] direct mode", message); + await navigator.clipboard.writeText(text); +}