Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ import {
import { useOpenPrLink } from "~/lib/openPullRequestLink";
import {
collectInlineContextIds,
stripInlineContextReferences,
type ComposerContextReference,
ensureInlineContextReferences,
formatInlineContextReference,
Expand Down Expand Up @@ -5234,6 +5235,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
options?: {
readonly source?: ChatFileAttachment["source"];
readonly selection?: { start: number; end: number };
readonly skipImageInlineChip?: boolean;
},
): Promise<boolean> => {
if (!activeThreadId || files.length === 0 || isRevertingCheckpointRef.current) return false;
Expand All @@ -5253,6 +5255,20 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
// large image is being compressed, and the attachments and errors belong
// to the thread the paste happened in.
const threadId = activeThreadId;
// Images landing with no prose live on the shelf with no chip. Read before
// the awaits below: compression is async and the prompt may change while it
// runs. An explicit selection replace and states where the editor refuses
// input (connecting, approval, pending questions, project selection) still
// get chips so the image is never invisible, unless paste-as-text explicitly
// requests no inline image chip.
const imageAttachmentsGetChips =
!options?.skipImageInlineChip &&
(options?.selection !== undefined ||
isConnecting ||
isComposerApprovalState ||
pendingUserInputs.length > 0 ||
projectSelectionRequired ||
stripInlineContextReferences(promptRef.current).trim().length > 0);

// Validation happens synchronously so concurrent pastes see each other:
// accepted files reserve their attachment slots (via the pending counter)
Expand Down Expand Up @@ -5409,7 +5425,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
: [],
);
const storedImages = nextImages.filter((image) => storedImageIds.has(image.id));
if (storedImages.length > 0) {
if (storedImages.length > 0 && imageAttachmentsGetChips) {
insertedAny =
insertAttachmentReferences(storedImages.map(imageContextReference)) || insertedAny;
}
Expand Down Expand Up @@ -5437,6 +5453,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
/**
* Chips for freshly attached files land at the caret; when the editor cannot take
* input (approval, pending questions) they are appended so the file is never invisible.
* Images skip this when they land with no prose and the editor takes input:
* the shelf thumbnail is enough.
*/
const insertAttachmentReferences = (
references: ReadonlyArray<ComposerContextReference>,
Expand Down Expand Up @@ -5575,7 +5593,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
) {
event.preventDefault();
event.stopPropagation();
void addComposerAttachments(files);
void addComposerAttachments(files, { skipImageInlineChip: bypassAutoAttachment });
return;
}

Expand Down
Loading