diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 0d50637dd8a5..6029414209c8 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -158,12 +158,7 @@ import { type ComposerBannerStackContent, type ComposerBannerStackItem, } from "./ComposerBannerStack"; -import { - compressImageForStash, - isHeicImageFile, - prepareImageForAttachment, -} from "../../lib/imageCompression"; -import { AttachmentImage } from "../media/AttachmentImage"; +import { compressImageForStash, prepareImageForAttachment } from "../../lib/imageCompression"; import { fileAttachmentTooLargeMessage, formatAttachmentSize, @@ -201,7 +196,6 @@ import { toKindScopedComposerContextId, } from "@t3tools/shared/composerContextReferences"; import { - getRestingComposerImagePreviewCounts, resolveRestingComposerControlsLayout, shouldAnimateComposerRestingTransition, shouldUseCompactComposerPrimaryActions, @@ -286,11 +280,12 @@ import { } from "./ComposerCommandMenu"; import { ComposerPendingApprovalActions } from "./ComposerPendingApprovalActions"; import { CompactComposerControlsMenu } from "./CompactComposerControlsMenu"; -import { ComposerImageThumbnail } from "./ComposerImageThumbnail"; import { ComposerAttachmentFileList, ComposerAttachmentMediaRow, ComposerFilePreviewDialog, + ComposerQueuedAttachmentRow, + ComposerRestingImagePreviews, } from "./ComposerAttachmentStrip"; import { ComposerPrimaryActions } from "./ComposerPrimaryActions"; import { ComposerPendingApprovalPanel } from "./ComposerPendingApprovalPanel"; @@ -344,7 +339,6 @@ import { submitComposerDraft, } from "./composerSubmission"; import { ComposerPromptLengthValidation } from "./ComposerPromptLengthValidation"; -import { PierreEntryIcon } from "./PierreEntryIcon"; import { pendingDraftWork } from "./pendingDraftWork"; import { importComposerThreadAttachment, @@ -989,7 +983,6 @@ import { PencilRulerIcon, PlayIcon, ShieldIcon, - XIcon, } from "lucide-react"; import { proposedPlanTitle } from "@t3tools/shared/proposedPlanText"; import { hasProviderSetup } from "./ProviderStatusBanner"; @@ -4830,85 +4823,24 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) measure(); return observeResize(restingActionsElement, measure); }, [isComposerResting, restingActionsElement]); - const restingImagePreviewCounts = getRestingComposerImagePreviewCounts( - standaloneComposerImages.length, - ); - const restingComposerImages = standaloneComposerImages.slice( - 0, - restingImagePreviewCounts.visibleCount, - ); + const showMoreRestingImages = () => { + if (isComposerCollapsedMobile) { + expandMobileComposer(); + return; + } + setIsComposerScrollCollapsed(false); + setIsComposerFocused(true); + scheduleComposerFocus(); + }; const collapsedComposerImagePreviews = - restingComposerImages.length > 0 ? ( -
- {restingComposerImages.map((image) => ( - - ))} - {restingImagePreviewCounts.overflowCount > 0 ? ( - - ) : null} -
+ standaloneComposerImages.length > 0 ? ( + ) : null; const { active: panelAnimationsActive, durationMs: panelAnimationDurationMs } = usePanelAnimationSettings(); @@ -6695,35 +6627,10 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) pendingUserInputs.length === 0 && editingQueuedAttachments !== null && editingQueuedAttachments.length > 0 && ( -
- {editingQueuedAttachments.map(({ attachment, url }) => ( -
- {attachment.type === "image" && url ? ( - {attachment.name} - ) : ( -
- {attachment.name} -
- )} - -
- ))} -
+ )} {composerAttachmentMediaRow} diff --git a/apps/web/src/components/chat/ComposerAttachmentStrip.tsx b/apps/web/src/components/chat/ComposerAttachmentStrip.tsx index 4cbbb39bd6b0..44e63eddce22 100644 --- a/apps/web/src/components/chat/ComposerAttachmentStrip.tsx +++ b/apps/web/src/components/chat/ComposerAttachmentStrip.tsx @@ -1,4 +1,5 @@ import type { + ChatAttachment, ChatFileAttachment, EnvironmentId, ScopedThreadRef, @@ -24,6 +25,7 @@ import { shouldAnimateSnapShotArrival, } from "../../lib/snapShotAnimation"; import { cn } from "~/lib/utils"; +import { getRestingComposerImagePreviewCounts } from "../composerFooterLayout"; import { RefreshIcon } from "~/components/ui/refresh-icon"; import { AttachmentFilePreview } from "../files/AttachmentFilePreview"; import { AttachmentImage } from "../media/AttachmentImage"; @@ -533,3 +535,122 @@ export function ComposerFilePreviewDialog({ ); } + +/** Thumbnails beside the single-line prompt while the composer rests. */ +export function ComposerRestingImagePreviews({ + images, + allImages, + resolvedTheme, + onExpandImage, + onShowMore, +}: { + readonly images: ReadonlyArray; + readonly allImages: ReadonlyArray; + readonly resolvedTheme: "light" | "dark"; + readonly onExpandImage: (preview: ExpandedImagePreview) => void; + readonly onShowMore: () => void; +}) { + const counts = getRestingComposerImagePreviewCounts(images.length); + const visibleImages = images.slice(0, counts.visibleCount); + return ( +
+ {visibleImages.map((image) => ( + + ))} + {counts.overflowCount > 0 ? ( + + ) : null} +
+ ); +} + +/** Attachments of the queued message being edited; they are already uploaded. */ +export function ComposerQueuedAttachmentRow({ + attachments, + onRemove, +}: { + readonly attachments: ReadonlyArray<{ + readonly attachment: ChatAttachment; + readonly url: string | null; + }>; + readonly onRemove: (attachmentId: string) => void; +}) { + return ( +
+ {attachments.map(({ attachment, url }) => ( +
+ {attachment.type === "image" && url ? ( + {attachment.name} + ) : ( +
+ {attachment.name} +
+ )} + +
+ ))} +
+ ); +}