| ... | @@ -206,6 +206,7 @@ import { | ... | @@ -206,6 +206,7 @@ import { |
| 206 | import { useOpenPrLink } from "~/lib/openPullRequestLink"; | 206 | import { useOpenPrLink } from "~/lib/openPullRequestLink"; |
| 207 | import { | 207 | import { |
| 208 | collectInlineContextIds, | 208 | collectInlineContextIds, |
| | 209 | stripInlineContextReferences, |
| 209 | type ComposerContextReference, | 210 | type ComposerContextReference, |
| 210 | ensureInlineContextReferences, | 211 | ensureInlineContextReferences, |
| 211 | formatInlineContextReference, | 212 | formatInlineContextReference, |
| ... | @@ -5241,6 +5242,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -5241,6 +5242,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5241 | options?: { | 5242 | options?: { |
| 5242 | readonly source?: ChatFileAttachment["source"]; | 5243 | readonly source?: ChatFileAttachment["source"]; |
| 5243 | readonly selection?: { start: number; end: number }; | 5244 | readonly selection?: { start: number; end: number }; |
| | 5245 | readonly skipImageInlineChip?: boolean; |
| 5244 | }, | 5246 | }, |
| 5245 | ): Promise<boolean> => { | 5247 | ): Promise<boolean> => { |
| 5246 | if (!activeThreadId || files.length === 0 || isRevertingCheckpointRef.current) return false; | 5248 | if (!activeThreadId || files.length === 0 || isRevertingCheckpointRef.current) return false; |
| ... | @@ -5260,6 +5262,20 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -5260,6 +5262,20 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5260 | // large image is being compressed, and the attachments and errors belong | 5262 | // large image is being compressed, and the attachments and errors belong |
| 5261 | // to the thread the paste happened in. | 5263 | // to the thread the paste happened in. |
| 5262 | const threadId = activeThreadId; | 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 | // Validation happens synchronously so concurrent pastes see each other: | 5280 | // Validation happens synchronously so concurrent pastes see each other: |
| 5265 | // accepted files reserve their attachment slots (via the pending counter) | 5281 | // accepted files reserve their attachment slots (via the pending counter) |
| ... | @@ -5416,7 +5432,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -5416,7 +5432,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5416 | : [], | 5432 | : [], |
| 5417 | ); | 5433 | ); |
| 5418 | const storedImages = nextImages.filter((image) => storedImageIds.has(image.id)); | 5434 | const storedImages = nextImages.filter((image) => storedImageIds.has(image.id)); |
| 5419 | if (storedImages.length > 0) { | 5435 | if (storedImages.length > 0 && imageAttachmentsGetChips) { |
| 5420 | insertedAny = | 5436 | insertedAny = |
| 5421 | insertAttachmentReferences(storedImages.map(imageContextReference)) || insertedAny; | 5437 | insertAttachmentReferences(storedImages.map(imageContextReference)) || insertedAny; |
| 5422 | } | 5438 | } |
| ... | @@ -5444,6 +5460,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -5444,6 +5460,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5444 | /** | 5460 | /** |
| 5445 | * Chips for freshly attached files land at the caret; when the editor cannot take | 5461 | * Chips for freshly attached files land at the caret; when the editor cannot take |
| 5446 | * input (approval, pending questions) they are appended so the file is never invisible. | 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 | const insertAttachmentReferences = ( | 5466 | const insertAttachmentReferences = ( |
| 5449 | references: ReadonlyArray<ComposerContextReference>, | 5467 | references: ReadonlyArray<ComposerContextReference>, |
| ... | @@ -5582,7 +5600,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -5582,7 +5600,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5582 | ) { | 5600 | ) { |
| 5583 | event.preventDefault(); | 5601 | event.preventDefault(); |
| 5584 | event.stopPropagation(); | 5602 | event.stopPropagation(); |
| 5585 | void addComposerAttachments(files); | 5603 | void addComposerAttachments(files, { skipImageInlineChip: bypassAutoAttachment }); |
| 5586 | return; | 5604 | return; |
| 5587 | } | 5605 | } |
| 5588 | | 5606 | |