From ad4df369eaac27cd656b5669afdbd3f8086061f0 Mon Sep 17 00:00:00 2001 From: Haoqing Wang <78337154+hqhq1025@users.noreply.github.com> Date: Tue, 24 Mar 2026 14:16:19 +0800 Subject: [PATCH] feat(web): add copy button to user messages (#349) * feat(web): add copy button to user messages Add a small copy button to user message bubbles for easy text copying, especially useful on mobile where selecting text is difficult. - Mobile: button always visible (opacity-60) - Desktop: button appears on hover - Uses existing useCopyToClipboard hook with haptic feedback - Conditionally rendered to avoid empty container spacing via [HAPI](https://hapi.run) Co-Authored-By: HAPI * fix(web): use valid CSS property in copy button transition transition-[opacity,colors] is invalid because 'colors' is not a CSS property (only Tailwind's utility class 'transition-colors' expands it). Use 'background-color' instead so the hover background transition actually works. via [HAPI](https://hapi.run) Co-Authored-By: HAPI --------- Co-authored-by: HAPI --- .../AssistantChat/messages/UserMessage.tsx | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/web/src/components/AssistantChat/messages/UserMessage.tsx b/web/src/components/AssistantChat/messages/UserMessage.tsx index 1b1cf4f9..93aa8f26 100644 --- a/web/src/components/AssistantChat/messages/UserMessage.tsx +++ b/web/src/components/AssistantChat/messages/UserMessage.tsx @@ -5,9 +5,12 @@ import type { HappyChatMessageMetadata } from '@/lib/assistant-runtime' import { MessageStatusIndicator } from '@/components/AssistantChat/messages/MessageStatusIndicator' import { MessageAttachments } from '@/components/AssistantChat/messages/MessageAttachments' import { CliOutputBlock } from '@/components/CliOutputBlock' +import { CopyIcon, CheckIcon } from '@/components/icons' +import { useCopyToClipboard } from '@/hooks/useCopyToClipboard' export function HappyUserMessage() { const ctx = useHappyChatContext() + const { copied, copy } = useCopyToClipboard() const role = useAssistantState(({ message }) => message.role) const text = useAssistantState(({ message }) => { if (message.role !== 'user') return '' @@ -58,17 +61,29 @@ export function HappyUserMessage() { const hasAttachments = attachments && attachments.length > 0 return ( - +
{hasText && } {hasAttachments && }
- {status ? ( -
- + {(hasText || status) && ( +
+ {hasText && ( + + )} + {status && }
- ) : null} + )}
)