From 997d4f4129a8f8334657870c59c2dd574025b3dc Mon Sep 17 00:00:00 2001 From: Alex Crouch Date: Mon, 8 Dec 2025 17:47:05 +1100 Subject: [PATCH] feat(ui): add copy button to message items Add a Copy button that allows users to copy raw message contents (text and reasoning parts) to clipboard. The button appears on all messages - alongside Revert/Fork for user messages, and standalone for assistant messages. --- packages/ui/src/components/message-item.tsx | 41 ++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/message-item.tsx b/packages/ui/src/components/message-item.tsx index 29a32c63..e9d9dc8e 100644 --- a/packages/ui/src/components/message-item.tsx +++ b/packages/ui/src/components/message-item.tsx @@ -1,4 +1,4 @@ -import { For, Show } from "solid-js" +import { For, Show, createSignal } from "solid-js" import type { MessageInfo, ClientPart } from "../types/message" import { partHasRenderableText } from "../types/message" import type { MessageRecord } from "../stores/message-v2/types" @@ -18,6 +18,7 @@ interface MessageItemProps { } export default function MessageItem(props: MessageItemProps) { + const [copied, setCopied] = createSignal(false) const isUser = () => props.record.role === "user" const createdTimestamp = () => props.messageInfo?.time?.created ?? props.record.createdAt @@ -143,6 +144,22 @@ interface MessageItemProps { } } + const getRawContent = () => { + return props.parts + .filter(part => part.type === "text" || part.type === "reasoning") + .map(part => (part as { text?: string }).text || "") + .filter(text => text.trim().length > 0) + .join("\n\n") + } + + const handleCopy = async () => { + const content = getRawContent() + if (!content) return + await navigator.clipboard.writeText(content) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + if (!isUser() && !hasContent()) { return null } @@ -218,8 +235,30 @@ interface MessageItemProps { Fork + + + +