authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-27 19:14:30-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-27 19:27:05-08:00
logaa267ebb0597ed8e3f367410ba4636465c1643f6
tree2f80af647bb982cae0c43cbb9d80383955897762
parent1a33f25970445cfd2367eefaffe41f52abcabee0

clamp


1 files changed, 29 insertions(+), 27 deletions(-)

apps/web/src/components/ChatView.tsx+29-27
......@@ -111,6 +111,7 @@ import { SidebarTrigger } from "./ui/sidebar";
111111import { newCommandId, newMessageId } from "~/lib/utils";
112112import { readNativeApi } from "~/nativeApi";
113113import { useAppSettings } from "../appSettings";
114import { clamp } from "effect/Number";
114115
115116function formatMessageMeta(createdAt: string, duration: string | null): string {
116117 if (!duration) return formatTimestamp(createdAt);
......@@ -2338,13 +2339,7 @@ const MessageCopyButton = memo(function MessageCopyButton({ text }: { text: stri
23382339 }, [text]);
23392340
23402341 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">
23482343 {copied ? <CheckIcon className="size-3 text-success" /> : <CopyIcon className="size-3" />}
23492344 </Button>
23502345 );
......@@ -2499,7 +2494,10 @@ const MessagesTimeline = memo(function MessagesTimeline({
24992494 return firstCurrentTurnRowIndex;
25002495 }, [activeTurnInProgress, activeTurnStartedAt, rows]);
25012496
2502 const virtualizedRowCount = Math.max(0, Math.min(rows.length, firstUnvirtualizedRowIndex));
2497 const virtualizedRowCount = clamp(firstUnvirtualizedRowIndex, {
2498 minimum: 0,
2499 maximum: rows.length,
2500 });
25032501
25042502 const rowVirtualizer = useVirtualizer({
25052503 count: virtualizedRowCount,
......@@ -2594,25 +2592,29 @@ const MessagesTimeline = memo(function MessagesTimeline({
25942592 <div className="group relative max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-secondary px-4 py-3">
25952593 {userImages.length > 0 && (
25962594 <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 )}
26162618 </div>
26172619 )}
26182620 {row.message.text && (