mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
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 <noreply@hapi.run> * 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 <noreply@hapi.run> --------- Co-authored-by: HAPI <noreply@hapi.run>
This commit is contained in:
@@ -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 (
|
||||
<MessagePrimitive.Root className={userBubbleClass}>
|
||||
<MessagePrimitive.Root className={`${userBubbleClass} group/msg`}>
|
||||
<div className="flex items-end gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
{hasText && <LazyRainbowText text={text} />}
|
||||
{hasAttachments && <MessageAttachments attachments={attachments} />}
|
||||
</div>
|
||||
{status ? (
|
||||
<div className="shrink-0 self-end pb-0.5">
|
||||
<MessageStatusIndicator status={status} onRetry={onRetry} />
|
||||
{(hasText || status) && (
|
||||
<div className="shrink-0 self-end pb-0.5 flex items-center gap-1">
|
||||
{hasText && (
|
||||
<button
|
||||
type="button"
|
||||
title="Copy"
|
||||
className="opacity-60 sm:opacity-0 sm:group-hover/msg:opacity-100 transition-[opacity,background-color] p-0.5 rounded hover:bg-[var(--app-subtle-bg)]"
|
||||
onClick={() => copy(text)}
|
||||
>
|
||||
{copied
|
||||
? <CheckIcon className="h-3.5 w-3.5 text-green-500" />
|
||||
: <CopyIcon className="h-3.5 w-3.5 text-[var(--app-hint)]" />}
|
||||
</button>
|
||||
)}
|
||||
{status && <MessageStatusIndicator status={status} onRetry={onRetry} />}
|
||||
</div>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
</MessagePrimitive.Root>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user