diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.test.ts b/apps/web/src/components/chat/MessagesTimeline.logic.test.ts index c9aec993bcd86..4c7eba620b5d1 100644 --- a/apps/web/src/components/chat/MessagesTimeline.logic.test.ts +++ b/apps/web/src/components/chat/MessagesTimeline.logic.test.ts @@ -1450,10 +1450,12 @@ describe("deriveMessagesTimelineRows", () => { expect(byId.get("prompt")).not.toHaveProperty("groupedWithNext"); expect(byId.get("first-steer")).toMatchObject({ groupedWithNext: true, + groupedWithPrevious: false, continuesSteer: false, }); expect(byId.get("second-steer")).toMatchObject({ groupedWithNext: false, + groupedWithPrevious: true, continuesSteer: true, }); }); diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.ts b/apps/web/src/components/chat/MessagesTimeline.logic.ts index 1f22bc1c14978..2fe811db4d18f 100644 --- a/apps/web/src/components/chat/MessagesTimeline.logic.ts +++ b/apps/web/src/components/chat/MessagesTimeline.logic.ts @@ -597,6 +597,8 @@ type MessagesTimelineRowContent = revertTurnCount?: number | undefined; /** Another of your messages follows directly, so the two sit closer together. */ groupedWithNext?: boolean; + /** Another of your messages sits directly above, so the joined corners tighten. */ + groupedWithPrevious?: boolean; /** Set when the message above is a steer too, so the label shows once. */ continuesSteer?: boolean; } @@ -2146,12 +2148,11 @@ export function deriveMessagesTimelineRows(input: { if (!timelineRowIsOwnUserMessage(row)) return row; const previous = result[index - 1]; const groupedWithNext = timelineRowIsOwnUserMessage(result[index + 1]); + const groupedWithPrevious = timelineRowIsOwnUserMessage(previous); const continuesSteer = - timelineRowIsOwnUserMessage(previous) && - messageIsSteer(row.message) && - messageIsSteer(previous.message); - if (!groupedWithNext && !continuesSteer) return row; - return { ...row, groupedWithNext, continuesSteer }; + groupedWithPrevious && messageIsSteer(row.message) && messageIsSteer(previous.message); + if (!groupedWithNext && !groupedWithPrevious) return row; + return { ...row, groupedWithNext, groupedWithPrevious, continuesSteer }; }); } @@ -2508,6 +2509,7 @@ function isRowUnchanged(a: MessagesTimelineRow, b: MessagesTimelineRow): boolean a.assistantTurnDiffSummary === bm.assistantTurnDiffSummary && a.revertTurnCount === bm.revertTurnCount && a.groupedWithNext === bm.groupedWithNext && + a.groupedWithPrevious === bm.groupedWithPrevious && a.continuesSteer === bm.continuesSteer ); } diff --git a/apps/web/src/components/chat/TimelineUserMessageRow.tsx b/apps/web/src/components/chat/TimelineUserMessageRow.tsx index 48086d40b4bf3..e73f5d1d1e9a7 100644 --- a/apps/web/src/components/chat/TimelineUserMessageRow.tsx +++ b/apps/web/src/components/chat/TimelineUserMessageRow.tsx @@ -74,6 +74,7 @@ import { import { chatMarkdownClipboardPayload } from "../../markdown-clipboard"; import type { ChatMarkdownContextReference } from "../ChatMarkdown"; import { cn } from "~/lib/utils"; +import { useMediaQuery } from "~/hooks/useMediaQuery"; import { formatChatTimestampTooltip, formatDayAwareTimestamp } from "../../timestampFormat"; import { WorkflowOpeningBlock } from "./WorkflowOpeningBlock"; import { TimelineRowCtx, TimelineRowActivityCtx, type TimelineRow } from "./timelineRowContext"; @@ -174,9 +175,11 @@ function UserFileCard({ function UserVideoAttachment({ file, standalone = false, + cornerClassName, }: { readonly file: ChatFileAttachment; readonly standalone?: boolean; + readonly cornerClassName?: string | undefined; }) { const ctx = use(TimelineRowCtx); // Row updates rebuild attachment objects, so the asset follows only the @@ -198,7 +201,7 @@ function UserVideoAttachment({ className={cn( "flex aspect-[4/3] w-full items-center justify-center bg-black px-2 py-3 text-center text-2xs text-white/70", standalone - ? cn(MEDIA_EDGE_CLASS_NAME, "rounded-2xl") + ? cn(MEDIA_EDGE_CLASS_NAME, "rounded-2xl", cornerClassName) : "rounded-lg border border-border/80", )} > @@ -233,7 +236,9 @@ function UserVideoAttachment({ /> ); return standalone ? ( -