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}
-
- )}
-
-
- ))}
-
+
)}
{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}
+
+ )}
+
+
+ ))}
+
+ );
+}