| ... | ... | @@ -83,6 +83,8 @@ import { |
| 83 | 83 | LockOpenIcon, |
| 84 | 84 | Undo2Icon, |
| 85 | 85 | XIcon, |
| 86 | CopyIcon, |
| 87 | CheckIcon, |
| 86 | 88 | } from "lucide-react"; |
| 87 | 89 | import { Button } from "./ui/button"; |
| 88 | 90 | import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select"; |
| ... | ... | @@ -2333,6 +2335,28 @@ const PendingApprovalsPanel = memo(function PendingApprovalsPanel({ |
| 2333 | 2335 | ); |
| 2334 | 2336 | }); |
| 2335 | 2337 | |
| 2338 | const MessageCopyButton = memo(function MessageCopyButton({ text }: { text: string }) { |
| 2339 | const [copied, setCopied] = useState(false); |
| 2340 | |
| 2341 | const handleCopy = useCallback(() => { |
| 2342 | void navigator.clipboard.writeText(text); |
| 2343 | setCopied(true); |
| 2344 | setTimeout(() => setCopied(false), 2000); |
| 2345 | }, [text]); |
| 2346 | |
| 2347 | return ( |
| 2348 | <Button |
| 2349 | type="button" |
| 2350 | size="xs" |
| 2351 | variant="outline" |
| 2352 | onClick={handleCopy} |
| 2353 | title="Copy message" |
| 2354 | > |
| 2355 | {copied ? <CheckIcon className="size-3 text-success" /> : <CopyIcon className="size-3" />} |
| 2356 | </Button> |
| 2357 | ); |
| 2358 | }); |
| 2359 | |
| 2336 | 2360 | interface MessagesTimelineProps { |
| 2337 | 2361 | hasMessages: boolean; |
| 2338 | 2362 | isWorking: boolean; |
| ... | ... | @@ -2549,7 +2573,7 @@ const MessagesTimeline = memo(function MessagesTimeline({ |
| 2549 | 2573 | const canRevertAgentWork = revertTurnCountByUserMessageId.has(row.message.id); |
| 2550 | 2574 | return ( |
| 2551 | 2575 | <div className="flex justify-end"> |
| 2552 | | <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-secondary px-4 py-3"> |
| 2576 | <div className="group relative max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-secondary px-4 py-3"> |
| 2553 | 2577 | {userImages.length > 0 && ( |
| 2554 | 2578 | <div className="mb-2 grid max-w-[420px] grid-cols-2 gap-2"> |
| 2555 | 2579 | {userImages.map( |
| ... | ... | @@ -2583,17 +2607,21 @@ const MessagesTimeline = memo(function MessagesTimeline({ |
| 2583 | 2607 | </pre> |
| 2584 | 2608 | )} |
| 2585 | 2609 | <div className="mt-1.5 flex items-center justify-end gap-2"> |
| 2586 | | {canRevertAgentWork && ( |
| 2587 | | <Button |
| 2588 | | type="button" |
| 2589 | | size="xs" |
| 2590 | | variant="outline" |
| 2591 | | disabled={isRevertingCheckpoint || isWorking} |
| 2592 | | onClick={() => onRevertUserMessage(row.message.id)} |
| 2593 | | > |
| 2594 | | <Undo2Icon className="size-3" /> |
| 2595 | | </Button> |
| 2596 | | )} |
| 2610 | <div className="flex items-center gap-1.5 opacity-0 transition-opacity duration-200 focus-within:opacity-100 group-hover:opacity-100"> |
| 2611 | {row.message.text && <MessageCopyButton text={row.message.text} />} |
| 2612 | {canRevertAgentWork && ( |
| 2613 | <Button |
| 2614 | type="button" |
| 2615 | size="xs" |
| 2616 | variant="outline" |
| 2617 | disabled={isRevertingCheckpoint || isWorking} |
| 2618 | onClick={() => onRevertUserMessage(row.message.id)} |
| 2619 | title="Revert to this message" |
| 2620 | > |
| 2621 | <Undo2Icon className="size-3" /> |
| 2622 | </Button> |
| 2623 | )} |
| 2624 | </div> |
| 2597 | 2625 | <p className="text-right text-[10px] text-muted-foreground/30"> |
| 2598 | 2626 | {formatTimestamp(row.message.createdAt)} |
| 2599 | 2627 | </p> |