diff --git a/components/chat-message-display.tsx b/components/chat-message-display.tsx index 44aa588..b4c5be8 100644 --- a/components/chat-message-display.tsx +++ b/components/chat-message-display.tsx @@ -230,6 +230,12 @@ export function ChatMessageDisplay({ const [expandedTools, setExpandedTools] = useState>( {}, ) + const [copiedToolCallId, setCopiedToolCallId] = useState( + null, + ) + const [copyFailedToolCallId, setCopyFailedToolCallId] = useState< + string | null + >(null) const [copiedMessageId, setCopiedMessageId] = useState(null) const [copyFailedMessageId, setCopyFailedMessageId] = useState< string | null @@ -245,12 +251,38 @@ export function ChatMessageDisplay({ Record >({}) - const copyMessageToClipboard = async (messageId: string, text: string) => { + const setCopyState = ( + messageId: string, + isToolCall: boolean, + isSuccess: boolean, + ) => { + if (isSuccess) { + if (isToolCall) { + setCopiedToolCallId(messageId) + setTimeout(() => setCopiedToolCallId(null), 2000) + } else { + setCopiedMessageId(messageId) + setTimeout(() => setCopiedMessageId(null), 2000) + } + } else { + if (isToolCall) { + setCopyFailedToolCallId(messageId) + setTimeout(() => setCopyFailedToolCallId(null), 2000) + } else { + setCopyFailedMessageId(messageId) + setTimeout(() => setCopyFailedMessageId(null), 2000) + } + } + } + + const copyMessageToClipboard = async ( + messageId: string, + text: string, + isToolCall = false, + ) => { try { await navigator.clipboard.writeText(text) - - setCopiedMessageId(messageId) - setTimeout(() => setCopiedMessageId(null), 2000) + setCopyState(messageId, isToolCall, true) } catch (err) { // Fallback for non-secure contexts (HTTP) or permission denied const textarea = document.createElement("textarea") @@ -266,13 +298,11 @@ export function ChatMessageDisplay({ if (!success) { throw new Error("Copy command failed") } - setCopiedMessageId(messageId) - setTimeout(() => setCopiedMessageId(null), 2000) + setCopyState(messageId, isToolCall, true) } catch (fallbackErr) { console.error("Failed to copy message:", fallbackErr) toast.error(dict.chat.failedToCopyDetail) - setCopyFailedMessageId(messageId) - setTimeout(() => setCopyFailedMessageId(null), 2000) + setCopyState(messageId, isToolCall, false) } finally { document.body.removeChild(textarea) } @@ -641,6 +671,7 @@ export function ChatMessageDisplay({ const { state, input, output } = part const isExpanded = expandedTools[callId] ?? true const toolName = part.type?.replace("tool-", "") + const isCopied = copiedToolCallId === callId const toggleExpanded = () => { setExpandedTools((prev) => ({ @@ -662,6 +693,35 @@ export function ChatMessageDisplay({ } } + const handleCopy = () => { + let textToCopy = "" + + if (input && typeof input === "object") { + if (input.xml) { + textToCopy = input.xml + } else if ( + input.operations && + Array.isArray(input.operations) + ) { + textToCopy = JSON.stringify(input.operations, null, 2) + } else if (Object.keys(input).length > 0) { + textToCopy = JSON.stringify(input, null, 2) + } + } + + if ( + output && + toolName === "get_shape_library" && + typeof output === "string" + ) { + textToCopy = output + } + + if (textToCopy) { + copyMessageToClipboard(callId, textToCopy, true) + } + } + return (
)} {state === "output-available" && ( - - Complete - + <> + + {dict.tools.complete} + + {isExpanded && ( + + )} + )} {state === "output-error" && (() => {