added the copy on mac

This commit is contained in:
samit 2026-02-20 00:49:38 -08:00
commit 5ba8edf9fe
2 changed files with 74 additions and 21 deletions

View file

@ -8,6 +8,7 @@ import { Reasoning, ReasoningGroup } from "@/components/assistant-ui/reasoning";
import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { Button } from "@/components/ui/button";
import { copyToClipboard } from "@/lib/copy-to-clipboard";
import { cn } from "@/lib/utils";
import {
ActionBarMorePrimitive,
@ -38,7 +39,7 @@ import {
RefreshCwIcon,
SquareIcon,
} from "lucide-react";
import { type FC, useRef } from "react";
import { type FC, useRef, useState } from "react";
export const Thread: FC<{ hideComposer?: boolean; hideWelcome?: boolean }> = ({
hideComposer,
@ -275,6 +276,32 @@ const AssistantMessage: FC = () => {
);
};
const COPY_RESET_MS = 2000;
const CopyButton: FC = () => {
const aui = useAui();
const [copied, setCopied] = useState(false);
const resetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const handleCopy = () => {
const text = aui.message().getCopyText();
if (copyToClipboard(text)) {
setCopied(true);
if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);
resetTimeoutRef.current = setTimeout(() => {
setCopied(false);
resetTimeoutRef.current = null;
}, COPY_RESET_MS);
}
};
return (
<TooltipIconButton tooltip="Copy" onClick={handleCopy}>
{copied ? <CheckIcon /> : <CopyIcon />}
</TooltipIconButton>
);
};
const AssistantActionBar: FC = () => {
return (
<ActionBarPrimitive.Root
@ -283,16 +310,7 @@ const AssistantActionBar: FC = () => {
autohideFloat="single-branch"
className="aui-assistant-action-bar-root col-start-3 row-start-2 -ml-1 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm"
>
<ActionBarPrimitive.Copy asChild={true}>
<TooltipIconButton tooltip="Copy">
<AuiIf condition={({ message }) => message.isCopied}>
<CheckIcon />
</AuiIf>
<AuiIf condition={({ message }) => !message.isCopied}>
<CopyIcon />
</AuiIf>
</TooltipIconButton>
</ActionBarPrimitive.Copy>
<CopyButton />
<ActionBarPrimitive.Reload asChild={true}>
<TooltipIconButton tooltip="Refresh">
<RefreshCwIcon />
@ -352,16 +370,7 @@ const UserActionBar: FC = () => {
autohide="not-last"
className="aui-user-action-bar-root flex items-center"
>
<ActionBarPrimitive.Copy asChild={true}>
<TooltipIconButton tooltip="Copy">
<AuiIf condition={({ message }) => message.isCopied}>
<CheckIcon />
</AuiIf>
<AuiIf condition={({ message }) => !message.isCopied}>
<CopyIcon />
</AuiIf>
</TooltipIconButton>
</ActionBarPrimitive.Copy>
<CopyButton />
<ActionBarPrimitive.Edit asChild={true}>
<TooltipIconButton tooltip="Edit" className="aui-user-action-edit">
<PencilIcon />

View file

@ -0,0 +1,44 @@
/**
* Copy text to clipboard in a way that works on Mac/Safari.
* Uses a synchronous textarea + execCommand fallback so the copy runs in the
* same user gesture as the click (required by Safari's clipboard security).
*/
export function copyToClipboard(text: string): boolean {
if (typeof text !== "string" || text.length === 0) {
return false;
}
// Synchronous fallback: works in Safari/Mac when clipboard API fails
// because it runs entirely within the user gesture (click) stack.
if (document.queryCommandSupported?.("copy") !== false) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.top = "0";
textarea.style.left = "0";
textarea.style.opacity = "0";
textarea.setAttribute("aria-hidden", "true");
document.body.appendChild(textarea);
textarea.focus({ preventScroll: true });
textarea.select();
try {
const ok = document.execCommand("copy");
document.body.removeChild(textarea);
return ok;
} catch {
document.body.removeChild(textarea);
return false;
}
}
// Modern API only when fallback not available (e.g. non-browser)
if (typeof navigator?.clipboard?.writeText === "function") {
navigator.clipboard.writeText(text).then(
() => {},
() => {}
);
return true;
}
return false;
}