File upload Phase 2: attachment chips and sent-message token
What does this MR do and why?
This MR implements the Figma design for file attachments in Duo Agentic Chat: square thumbnail tiles of about 80px with no text, both in the prompt composer and above the text of the sent user message. It is Phase 2 part 1 of #631138 (closed) (parent: #628537), and everything is behind the dap_web_chat_file_attachments feature flag.
AttachmentChip is extracted from attachment_previews.vue and restyled as the tile. It shows only the thumbnail, with the filename and size in the tooltip and alt text, and the remove button sits over the corner. A viewOnly prop drops the remove button, and a generic image icon is used when there is no preview. For the sent message, attachmentsIn(message) and hasAttachments(message) are added to utils/messages_utils.js, and a new widget, plugins/attachments/components/message_attachments_prompt.vue, wraps duo-ui's UserMessage and renders the tiles in its #header slot. This is the same pattern as the goal badge from !257820 (merged). The plugin is registered in plugins/index.js behind the flag.
The frontend sends files over the websocket as additional_context entries with category: 'attachments', base64 included. The flow service strips the bytes and echoes one reference per file on the turn's ui_chat_log, carrying the filename and mime type but no bytes and no size. The category arrives lower case live, but after a reload the checkpoint GraphQL query returns it as the enum ATTACHMENTS, so the match is case-insensitive.
To show real thumbnails on the sent message, the state manager pins the previews (data URLs, without the raw base64 that went to the model) on the pending user message under an attachments key. The store's ADD_MESSAGE spreads the echoed turn over the pending message and the echo never sets that key, so the previews survive the round trip. The echoed references pair with the previews by position, since the flow service numbers them in send order. The result is real thumbnails for every turn sent in the current page session. After a reload only the references exist, so the tile falls back to the generic image icon with the filename in the tooltip. That limitation is deliberate and is the subject of the follow-up below.
duo-ui already listed the echoed references under its collapsed "Included references" on the user bubble, so the widget removes the attachment ones from that list to avoid showing a file twice. Other context items (issues, page context, AGENTS.md) still appear there. The work item mentions Storybook coverage in duo-ui, which implies the chip lives there. It stays in this repo because both consumers, the composer and the sent-message widget, are here, and it can move later if another client needs it.
Follow-up
For thumbnails to survive a reload, the image has to be stored somewhere addressable and the echoed reference has to carry a URL. Storing base64 in the checkpoint is not an option, because every checkpoint copies the whole chat log. The likely shape is that the frontend uploads the image to Rails through the existing uploads system and gets a URL, the request to the flow service carries that URL in the attachment metadata alongside the bytes, the flow service keeps it in the echoed reference, and the tile renders from the URL. This touches Rails and the flow service and raises retention and access questions, so it will be planned with the Phase 2 owner on #628537.
References
- Related to #631138 (closed)
- Phase 2 parent: #628537
- Goal badge pattern: !257820 (merged)
- Design: https://www.figma.com/design/VqpH0V6joIOokfeXS71Yqz/Duo-Agent-Platform-Side-Panel?node-id=19171-399854&m=dev&focus-id=19171-400499
Screenshots or screen recordings
| Before | After |
|---|---|
![]() |
![]() |
How to set up and validate locally
- In the Rails console, enable the flag with
Feature.enable(:dap_web_chat_file_attachments). - Open Duo Agentic Chat in a project.
- Attach one or two PNG or JPEG images in the composer. Each shows as a square tile with its thumbnail; the filename and size are in the tooltip, and the remove button sits over the corner.
- Send the message. The user bubble shows the same tiles above the text, with no remove button, and "Included references" no longer lists the files.
- Reload the page. The tiles are still on the sent message, now with a generic image icon instead of the thumbnail and the filename in the tooltip.
- Run
yarn jest ee/spec/frontend/ai/duo_agentic_chat/plugins ee/spec/frontend/ai/duo_agentic_chat/utils ee/spec/frontend/ai/duo_agentic_chat/components/prompt_composer ee/spec/frontend/ai/duo_agentic_chat/components/duo_agentic_chat_state_manager_spec.js.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

