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 {
8383 LockOpenIcon,
8484 Undo2Icon,
8585 XIcon,
86 CopyIcon,
87 CheckIcon,
8688} from "lucide-react";
8789import { Button } from "./ui/button";
8890import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select";
......@@ -2333,6 +2335,28 @@ const PendingApprovalsPanel = memo(function PendingApprovalsPanel({
23332335 );
23342336});
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
23362360interface MessagesTimelineProps {
23372361 hasMessages: boolean;
23382362 isWorking: boolean;
......@@ -2549,7 +2573,7 @@ const MessagesTimeline = memo(function MessagesTimeline({
25492573 const canRevertAgentWork = revertTurnCountByUserMessageId.has(row.message.id);
25502574 return (
25512575 <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">
25532577 {userImages.length > 0 && (
25542578 <div className="mb-2 grid max-w-[420px] grid-cols-2 gap-2">
25552579 {userImages.map(
......@@ -2583,17 +2607,21 @@ const MessagesTimeline = memo(function MessagesTimeline({
25832607 </pre>
25842608 )}
25852609 <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>
25972625 <p className="text-right text-[10px] text-muted-foreground/30">
25982626 {formatTimestamp(row.message.createdAt)}
25992627 </p>