11import { memo , type ReactNode , useEffectEvent , useLayoutEffect , useRef , useState } from "react" ;
2- import { Link , Surface , Tooltip , toast } from "@heroui/react" ;
2+ import { Link , Surface , Tooltip } from "@heroui/react" ;
33import { useLingui } from "@lingui/react/macro" ;
4- import { ChevronDown , ChevronUp , Copy } from "lucide-react" ;
4+ import { ChevronDown , ChevronUp } from "lucide-react" ;
55import type { CanonicalContentBlock , MessageItemPayload } from "@/shared/contracts" ;
6- import { friendlyError } from "@/shared/messages" ;
76import { AttachmentBar } from "@/renderer/components/composer/AttachmentBar" ;
87import { openAttachmentLightbox } from "@/renderer/components/composer/ImageLightbox" ;
98import type { Attachment } from "@/renderer/components/composer/useAttachments" ;
@@ -20,6 +19,7 @@ import { normalizeChatProjectPath } from "../../chatPathUtils";
2019import { openUserMessageActions } from "../../userMessageActions" ;
2120import { CheckpointRevertButton , type CheckpointRevertRequest } from "../CheckpointRevertControls" ;
2221import { chatPromptSurfaceClass } from "./chatMessageSurface" ;
22+ import { CopyTextButton } from "./CopyTextButton" ;
2323import { InlineFilePathChip } from "./InlineFilePathChip" ;
2424import { ItemMarkdown } from "./ItemMarkdown" ;
2525import { extractSelectorPayloads } from "./SelectorBadge" ;
@@ -273,72 +273,13 @@ export const UserMessage = memo(function UserMessage({ item, checkpointRevert }:
273273 onRequestRevert = { checkpointRevert . onRequestRevert }
274274 />
275275 ) : null }
276- < CopyUserMessageButton text = { rawText } />
276+ < CopyTextButton text = { rawText } label = { t `Copy message` } />
277277 </ div >
278278 ) : null }
279279 </ Surface >
280280 ) ;
281281} ) ;
282282
283- function CopyUserMessageButton ( { text } : { text : string } ) {
284- const { t } = useLingui ( ) ;
285- const [ copyState , setCopyState ] = useState < "idle" | "copied" > ( "idle" ) ;
286- const [ isTooltipOpen , setIsTooltipOpen ] = useState ( false ) ;
287- const resetTimerRef = useRef < number | null > ( null ) ;
288- const labelResetTimerRef = useRef < number | null > ( null ) ;
289-
290- useLayoutEffect (
291- ( ) => ( ) => {
292- if ( resetTimerRef . current !== null ) window . clearTimeout ( resetTimerRef . current ) ;
293- if ( labelResetTimerRef . current !== null ) window . clearTimeout ( labelResetTimerRef . current ) ;
294- } ,
295- [ ] ,
296- ) ;
297-
298- return (
299- < Tooltip delay = { 300 } isOpen = { isTooltipOpen } onOpenChange = { setIsTooltipOpen } >
300- < Tooltip . Trigger >
301- < button
302- type = "button"
303- aria-label = { copyState === "copied" ? t `Copied` : t `Copy message` }
304- className = "flex size-5 items-center justify-center rounded text-muted/70 transition-colors hover:bg-foreground/5 hover:text-foreground"
305- onClick = { ( event ) => {
306- event . stopPropagation ( ) ;
307- navigator . clipboard
308- . writeText ( text )
309- . then ( ( ) => {
310- setCopyState ( "copied" ) ;
311- setIsTooltipOpen ( true ) ;
312- if ( resetTimerRef . current !== null ) {
313- window . clearTimeout ( resetTimerRef . current ) ;
314- }
315- if ( labelResetTimerRef . current !== null ) {
316- window . clearTimeout ( labelResetTimerRef . current ) ;
317- }
318- resetTimerRef . current = window . setTimeout ( ( ) => {
319- setIsTooltipOpen ( false ) ;
320- resetTimerRef . current = null ;
321- labelResetTimerRef . current = window . setTimeout ( ( ) => {
322- setCopyState ( "idle" ) ;
323- labelResetTimerRef . current = null ;
324- } , 200 ) ;
325- } , 1200 ) ;
326- } )
327- . catch ( ( error : unknown ) => {
328- toast . danger ( friendlyError ( error ) ) ;
329- } ) ;
330- } }
331- >
332- < Copy className = "size-3" />
333- </ button >
334- </ Tooltip . Trigger >
335- < Tooltip . Content placement = "top" >
336- { copyState === "copied" ? t `Copied` : t `Copy message` }
337- </ Tooltip . Content >
338- </ Tooltip >
339- ) ;
340- }
341-
342283const LEADING_SLASH_COMMAND_RE = / ^ \/ ( [ A - Z a - z ] [ A - Z a - z 0 - 9 _ - ] * ) ( \s + | $ ) / ;
343284
344285function extractLeadingSlashCommand ( text : string ) : { slashCommand : string | null ; body : string } {
0 commit comments