added the copy on mac
This commit is contained in:
parent
fa555c72e8
commit
5ba8edf9fe
2 changed files with 74 additions and 21 deletions
|
|
@ -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 />
|
||||
|
|
|
|||
44
studio/frontend/src/lib/copy-to-clipboard.ts
Normal file
44
studio/frontend/src/lib/copy-to-clipboard.ts
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue