authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-27 14:06:09-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-02-27 14:06:09-08:00
log0b8e098c105297bc9564dc8bb792af21ec91ba4a
treeb64848d1acf139851af03bd726e36291c18825bd
parent7c6a0f869db16660df64c7710a52ab7cfd7f9076
signature Signed by PGP key B5690EEEBB952194

copy on hover, better button treatments (#110)


1 files changed, 40 insertions(+), 12 deletions(-)

apps/web/src/components/ChatView.tsx+40-12
...@@ -83,6 +83,8 @@ import {...@@ -83,6 +83,8 @@ import {
83 LockOpenIcon,83 LockOpenIcon,
84 Undo2Icon,84 Undo2Icon,
85 XIcon,85 XIcon,
86 CopyIcon,
87 CheckIcon,
86} from "lucide-react";88} from "lucide-react";
87import { Button } from "./ui/button";89import { Button } from "./ui/button";
88import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select";90import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select";
...@@ -2333,6 +2335,28 @@ const PendingApprovalsPanel = memo(function PendingApprovalsPanel({...@@ -2333,6 +2335,28 @@ const PendingApprovalsPanel = memo(function PendingApprovalsPanel({
2333 );2335 );
2334});2336});
23352337
2338const 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
2336interface MessagesTimelineProps {2360interface MessagesTimelineProps {
2337 hasMessages: boolean;2361 hasMessages: boolean;
2338 isWorking: boolean;2362 isWorking: boolean;
...@@ -2549,7 +2573,7 @@ const MessagesTimeline = memo(function MessagesTimeline({...@@ -2549,7 +2573,7 @@ const MessagesTimeline = memo(function MessagesTimeline({
2549 const canRevertAgentWork = revertTurnCountByUserMessageId.has(row.message.id);2573 const canRevertAgentWork = revertTurnCountByUserMessageId.has(row.message.id);
2550 return (2574 return (
2551 <div className="flex justify-end">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 {userImages.length > 0 && (2577 {userImages.length > 0 && (
2554 <div className="mb-2 grid max-w-[420px] grid-cols-2 gap-2">2578 <div className="mb-2 grid max-w-[420px] grid-cols-2 gap-2">
2555 {userImages.map(2579 {userImages.map(
...@@ -2583,17 +2607,21 @@ const MessagesTimeline = memo(function MessagesTimeline({...@@ -2583,17 +2607,21 @@ const MessagesTimeline = memo(function MessagesTimeline({
2583 </pre>2607 </pre>
2584 )}2608 )}
2585 <div className="mt-1.5 flex items-center justify-end gap-2">2609 <div className="mt-1.5 flex items-center justify-end gap-2">
2586 {canRevertAgentWork && (2610 <div className="flex items-center gap-1.5 opacity-0 transition-opacity duration-200 focus-within:opacity-100 group-hover:opacity-100">
2587 <Button2611 {row.message.text && <MessageCopyButton text={row.message.text} />}
2588 type="button"2612 {canRevertAgentWork && (
2589 size="xs"2613 <Button
2590 variant="outline"2614 type="button"
2591 disabled={isRevertingCheckpoint || isWorking}2615 size="xs"
2592 onClick={() => onRevertUserMessage(row.message.id)}2616 variant="outline"
2593 >2617 disabled={isRevertingCheckpoint || isWorking}
2594 <Undo2Icon className="size-3" />2618 onClick={() => onRevertUserMessage(row.message.id)}
2595 </Button>2619 title="Revert to this message"
2596 )}2620 >
2621 <Undo2Icon className="size-3" />
2622 </Button>
2623 )}
2624 </div>
2597 <p className="text-right text-[10px] text-muted-foreground/30">2625 <p className="text-right text-[10px] text-muted-foreground/30">
2598 {formatTimestamp(row.message.createdAt)}2626 {formatTimestamp(row.message.createdAt)}
2599 </p>2627 </p>