| ... | ... | @@ -206,6 +206,7 @@ import { |
| 206 | 206 | import { useOpenPrLink } from "~/lib/openPullRequestLink"; |
| 207 | 207 | import { |
| 208 | 208 | collectInlineContextIds, |
| 209 | stripInlineContextReferences, |
| 209 | 210 | type ComposerContextReference, |
| 210 | 211 | ensureInlineContextReferences, |
| 211 | 212 | formatInlineContextReference, |
| ... | ... | @@ -5241,6 +5242,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5241 | 5242 | options?: { |
| 5242 | 5243 | readonly source?: ChatFileAttachment["source"]; |
| 5243 | 5244 | readonly selection?: { start: number; end: number }; |
| 5245 | readonly skipImageInlineChip?: boolean; |
| 5244 | 5246 | }, |
| 5245 | 5247 | ): Promise<boolean> => { |
| 5246 | 5248 | if (!activeThreadId || files.length === 0 || isRevertingCheckpointRef.current) return false; |
| ... | ... | @@ -5260,6 +5262,20 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5260 | 5262 | // large image is being compressed, and the attachments and errors belong |
| 5261 | 5263 | // to the thread the paste happened in. |
| 5262 | 5264 | const threadId = activeThreadId; |
| 5265 | // Images landing with no prose live on the shelf with no chip. Read before |
| 5266 | // the awaits below: compression is async and the prompt may change while it |
| 5267 | // runs. An explicit selection replace and states where the editor refuses |
| 5268 | // input (connecting, approval, pending questions, project selection) still |
| 5269 | // get chips so the image is never invisible, unless paste-as-text explicitly |
| 5270 | // requests no inline image chip. |
| 5271 | const imageAttachmentsGetChips = |
| 5272 | !options?.skipImageInlineChip && |
| 5273 | (options?.selection !== undefined || |
| 5274 | isConnecting || |
| 5275 | isComposerApprovalState || |
| 5276 | pendingUserInputs.length > 0 || |
| 5277 | projectSelectionRequired || |
| 5278 | stripInlineContextReferences(promptRef.current).trim().length > 0); |
| 5263 | 5279 | |
| 5264 | 5280 | // Validation happens synchronously so concurrent pastes see each other: |
| 5265 | 5281 | // accepted files reserve their attachment slots (via the pending counter) |
| ... | ... | @@ -5416,7 +5432,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5416 | 5432 | : [], |
| 5417 | 5433 | ); |
| 5418 | 5434 | const storedImages = nextImages.filter((image) => storedImageIds.has(image.id)); |
| 5419 | | if (storedImages.length > 0) { |
| 5435 | if (storedImages.length > 0 && imageAttachmentsGetChips) { |
| 5420 | 5436 | insertedAny = |
| 5421 | 5437 | insertAttachmentReferences(storedImages.map(imageContextReference)) || insertedAny; |
| 5422 | 5438 | } |
| ... | ... | @@ -5444,6 +5460,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5444 | 5460 | /** |
| 5445 | 5461 | * Chips for freshly attached files land at the caret; when the editor cannot take |
| 5446 | 5462 | * input (approval, pending questions) they are appended so the file is never invisible. |
| 5463 | * Images skip this when they land with no prose and the editor takes input: |
| 5464 | * the shelf thumbnail is enough. |
| 5447 | 5465 | */ |
| 5448 | 5466 | const insertAttachmentReferences = ( |
| 5449 | 5467 | references: ReadonlyArray<ComposerContextReference>, |
| ... | ... | @@ -5582,7 +5600,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5582 | 5600 | ) { |
| 5583 | 5601 | event.preventDefault(); |
| 5584 | 5602 | event.stopPropagation(); |
| 5585 | | void addComposerAttachments(files); |
| 5603 | void addComposerAttachments(files, { skipImageInlineChip: bypassAutoAttachment }); |
| 5586 | 5604 | return; |
| 5587 | 5605 | } |
| 5588 | 5606 | |