| ... | ... | @@ -111,6 +111,7 @@ import { SidebarTrigger } from "./ui/sidebar"; |
| 111 | 111 | import { newCommandId, newMessageId } from "~/lib/utils"; |
| 112 | 112 | import { readNativeApi } from "~/nativeApi"; |
| 113 | 113 | import { useAppSettings } from "../appSettings"; |
| 114 | import { clamp } from "effect/Number"; |
| 114 | 115 | |
| 115 | 116 | function formatMessageMeta(createdAt: string, duration: string | null): string { |
| 116 | 117 | if (!duration) return formatTimestamp(createdAt); |
| ... | ... | @@ -2338,13 +2339,7 @@ const MessageCopyButton = memo(function MessageCopyButton({ text }: { text: stri |
| 2338 | 2339 | }, [text]); |
| 2339 | 2340 | |
| 2340 | 2341 | return ( |
| 2341 | | <Button |
| 2342 | | type="button" |
| 2343 | | size="xs" |
| 2344 | | variant="outline" |
| 2345 | | onClick={handleCopy} |
| 2346 | | title="Copy message" |
| 2347 | | > |
| 2342 | <Button type="button" size="xs" variant="outline" onClick={handleCopy} title="Copy message"> |
| 2348 | 2343 | {copied ? <CheckIcon className="size-3 text-success" /> : <CopyIcon className="size-3" />} |
| 2349 | 2344 | </Button> |
| 2350 | 2345 | ); |
| ... | ... | @@ -2499,7 +2494,10 @@ const MessagesTimeline = memo(function MessagesTimeline({ |
| 2499 | 2494 | return firstCurrentTurnRowIndex; |
| 2500 | 2495 | }, [activeTurnInProgress, activeTurnStartedAt, rows]); |
| 2501 | 2496 | |
| 2502 | | const virtualizedRowCount = Math.max(0, Math.min(rows.length, firstUnvirtualizedRowIndex)); |
| 2497 | const virtualizedRowCount = clamp(firstUnvirtualizedRowIndex, { |
| 2498 | minimum: 0, |
| 2499 | maximum: rows.length, |
| 2500 | }); |
| 2503 | 2501 | |
| 2504 | 2502 | const rowVirtualizer = useVirtualizer({ |
| 2505 | 2503 | count: virtualizedRowCount, |
| ... | ... | @@ -2594,25 +2592,29 @@ const MessagesTimeline = memo(function MessagesTimeline({ |
| 2594 | 2592 | <div className="group relative max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-secondary px-4 py-3"> |
| 2595 | 2593 | {userImages.length > 0 && ( |
| 2596 | 2594 | <div className="mb-2 grid max-w-[420px] grid-cols-2 gap-2"> |
| 2597 | | {userImages.map((image: NonNullable<TimelineMessage["attachments"]>[number]) => ( |
| 2598 | | <div |
| 2599 | | key={image.id} |
| 2600 | | className="overflow-hidden rounded-lg border border-border/80 bg-background/70" |
| 2601 | | > |
| 2602 | | {image.previewUrl ? ( |
| 2603 | | <img |
| 2604 | | src={image.previewUrl} |
| 2605 | | alt={image.name} |
| 2606 | | className="h-full max-h-[220px] w-full cursor-zoom-in object-cover" |
| 2607 | | onClick={() => onImageExpand({ src: image.previewUrl!, name: image.name })} |
| 2608 | | /> |
| 2609 | | ) : ( |
| 2610 | | <div className="flex min-h-[72px] items-center justify-center px-2 py-3 text-center text-[11px] text-muted-foreground/70"> |
| 2611 | | {image.name} |
| 2612 | | </div> |
| 2613 | | )} |
| 2614 | | </div> |
| 2615 | | ))} |
| 2595 | {userImages.map( |
| 2596 | (image: NonNullable<TimelineMessage["attachments"]>[number]) => ( |
| 2597 | <div |
| 2598 | key={image.id} |
| 2599 | className="overflow-hidden rounded-lg border border-border/80 bg-background/70" |
| 2600 | > |
| 2601 | {image.previewUrl ? ( |
| 2602 | <img |
| 2603 | src={image.previewUrl} |
| 2604 | alt={image.name} |
| 2605 | className="h-full max-h-[220px] w-full cursor-zoom-in object-cover" |
| 2606 | onClick={() => |
| 2607 | onImageExpand({ src: image.previewUrl!, name: image.name }) |
| 2608 | } |
| 2609 | /> |
| 2610 | ) : ( |
| 2611 | <div className="flex min-h-[72px] items-center justify-center px-2 py-3 text-center text-[11px] text-muted-foreground/70"> |
| 2612 | {image.name} |
| 2613 | </div> |
| 2614 | )} |
| 2615 | </div> |
| 2616 | ), |
| 2617 | )} |
| 2616 | 2618 | </div> |
| 2617 | 2619 | )} |
| 2618 | 2620 | {row.message.text && ( |