Repository navigation
feat(compose): reply with AI from the conversation and an instruction - #577
prudenciooficial wants to merge 21 commits into
Conversation
When replying, the AI menu offers "Reply with AI". The user types what
the reply should say ("approve the artwork", "decline politely and ask
for a new proof") and the AI writes it from the conversation so far:
the thread up to the message being answered, oldest first, with the
user's own messages marked and the quoted copies of earlier messages
cut out. A reply opened from a list that is not grouped into
conversations does not carry the thread, so it is looked up from the
message being answered; when nothing can be loaded, the quoted original
the composer already holds is used.
The conversation is read once per composer, so rewording the
instruction and generating again only asks the AI. The request stays
within the chat proxy's limits: at most ten messages, the newest kept
first and the oldest shortened or left out.
The conversation is mail other people wrote, so the prompt tells the
model to ignore instructions inside it, and Apply now escapes the
suggestion before it reaches the editor. Before, a suggestion containing
markup, such as a link, became real markup in the user's message; this
applies to the existing actions too.
The two copies of the suggestion panel (phone and desktop layouts) are
now one component.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reading pane's toolbar gets a "Reply with AI" button next to Reply, and each open message in the conversation view gets one beside Reply all. It opens the reply with the AI panel already waiting for the instruction, focused, so the user types what to say and nothing else. It is shown only when the AI's compose feature is on and the user writes in rich text, since the plain-text composer has no AI panel. A message reopened by Undo send is the user's own text, so it reopens without the panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/ai-reply # Conflicts: # frontend/src/components/ComposeModal.jsx
…eat/ai-reply # Conflicts: # frontend/src/components/ComposeModal.jsx
With maathimself#556 a message can go out without its signature while the signature text is kept. The reply prompt asked the AI to leave the closing to a signature whenever one existed, so with the signature turned off the reply ended with no name. It now asks for the sender's name unless the signature is on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…maathimself#577) The fork's copy of maathimself#577, squashed from the feat/ai-reply branch as of 843711e, while that pull request awaits review. "Reply with AI" sits in the reading pane toolbar, on each open message in the conversation view and in the composer's AI menu when replying. The user types what the reply should say; the AI writes it from the thread up to the message being answered, with quotes cut and the user's own messages marked. Apply escapes the suggestion before it reaches the editor. With the signature turned off (maathimself#556) the reply signs with the sender's name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/ai-reply # Conflicts: # frontend/src/components/MessagePane.render.test.js
The thread now marks its drafts (is_draft). A saved reply draft is part of the thread but was never sent, so the AI was shown it as a message the user had written in the conversation. It is left out now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/ai-reply # Conflicts: # frontend/src/utils/composeFromMessage.js
…eat/ai-reply # Conflicts: # frontend/src/components/ComposeModal.jsx
…eat/ai-reply # Conflicts: # frontend/src/components/ConversationPane.render.test.js
Write draft wrote straight away, from the subject and recipients alone. It now opens the same instruction field as Reply with AI: the user says what the email should say, and with the field left empty the AI writes from the subject on its own, as before. The new prompt signs with the sender's name when there is no signature, instead of leaving a "[Your name]" placeholder. In a reply the menu offers Reply with AI in its place, since a draft from the subject knows nothing of the conversation. Apply turned blank lines between paragraphs into adjacent paragraphs, which the editor sets almost touching, so a suggestion that read well in the panel ran together in the message. A blank line is now an empty paragraph, as Enter pressed twice leaves it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/ai-reply # Conflicts: # frontend/src/components/ConversationPane.render.test.js
…eat/ai-reply # Conflicts: # frontend/src/components/ComposeModal.jsx # frontend/src/locales/cs.json # frontend/src/locales/de.json # frontend/src/locales/en.json # frontend/src/locales/es.json # frontend/src/locales/fr.json # frontend/src/locales/it.json # frontend/src/locales/ko.json # frontend/src/locales/pl.json # frontend/src/locales/ptBR.json # frontend/src/locales/ru.json # frontend/src/locales/zhCN.json
|
Thanks for the work on this. I'm not going to merge it as it stands: it's a new feature that sends received mail to the AI provider, and per CONTRIBUTING a feature like this needs an issue first so we can agree on the direction. It also isn't on the plan for 4.1 (scheduled send, sorting and pinned emails, the full-width list and category totals). If you'd like to pursue it, please open an issue describing it. A few things would need to change for it to land later:
Two parts are useful on their own: escaping the suggestion when it's applied, and keeping blank lines between paragraphs. If you split those into a small PR, I'll review it separately. I'm leaving this one open for now. |
…eat/ai-reply # Conflicts: # frontend/src/components/ComposeModal.render.test.js
htmlToText and stripQuotedReply used patterns that search ahead from every position, so a crafted message took time growing with the square of its size: 320 KB of "<div " held the tab for about 50 seconds once Reply with AI was clicked, and a long line starting "Am" with many "schrieb" for about 9. HTML is now read by a scanner that looks at each character a bounded number of times, and quote headers are matched by how the line starts and ends. A "<" not followed by a letter, "/" or "!" is kept as text, as a browser keeps it. Reported in review by maathimself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thank you for the review and the clear direction.
I'll leave this PR as it is until we agree on the issue. My English is basic, so this reply was written with the help of Claude (Claude Code). |
…maathimself#577) The fork's copy of maathimself#577, squashed from the feat/ai-reply branch as of 29a16d0, while that pull request waits on its issue (maathimself#652). "Reply with AI" sits in the reading pane toolbar, on each open message in the conversation view and in the composer's AI menu when replying; Write draft takes an instruction too. The AI writes from the thread up to the message being answered, with quotes cut, drafts left out and the user's own messages marked, parsed in linear time. Apply escapes the suggestion and keeps its blank lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/ai-reply # Conflicts: # frontend/src/locales/cs.json # frontend/src/locales/de.json # frontend/src/locales/en.json # frontend/src/locales/es.json # frontend/src/locales/fr.json # frontend/src/locales/it.json # frontend/src/locales/pl.json # frontend/src/locales/ptBR.json # frontend/src/locales/ru.json # frontend/src/locales/zhCN.json
…I switch Since maathimself#644 each AI request says what it is for, and the server refuses it when the administrator has turned that off. Write draft writes from what the user typed, so it asks as the writing assistant. Reply with AI writes from received mail, which until now only summaries sent to the provider, so it asks under summaries and is offered only when both are on. A refusal shows why, as the other AI actions do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uest limit Messages were cut at 12,000 and 6,000 characters, and older ones left out past 24,000, without telling the model or the user. As since maathimself#633, the conversation now goes whole when it fits one AI request. When it does not, the oldest messages are left out first, and a message being answered too long by itself is cut at a paragraph or sentence break with cutPoint, now exported from aiActions.js. The model is told how many messages were left out and how much of a cut one it is reading, and the panel says the suggestion is based on the latest part of the conversation. Also from the review of maathimself#651, which carries the same code: the draft reaches Improve, Shorten and Grammar with one newline per paragraph, and Apply trims trailing spaces line by line instead of with a quadratic pattern. Reported in review by maathimself and Monkey7539. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
An update on the three points from your review, while #652 waits for your view:
My English is basic, so this reply was written with the help of Claude (Claude Code). |
Summary
Adds Reply with AI: the user says in a few words what the reply should say ("approve the artwork and ask for the print date") and the AI writes it from the conversation so far. The existing compose AI actions draft from the subject and recipients, or rewrite what is already typed; none of them reads the message being answered.
It is offered where a reply starts: a Reply with AI button in the reading pane toolbar, one on each open message in the conversation view, and an entry in the composer's AI menu when replying. The composer opens with the instruction field focused. The suggestion streams into the existing AI panel with Apply and Dismiss, and the instruction stays editable to ask again in other words.
Write draft in the composer's AI menu, for a new email, now asks for an instruction too: the user says what the email should say, and with the field left empty the AI writes from the subject and recipients, as before. In a reply the menu offers Reply with AI in its place, since a draft from the subject knows nothing of the conversation.
No backend change: it uses the existing
/api/ai/chatproxy and the thread and body endpoints. It is shown only when the AI's compose feature is on and the user writes in rich text, since the plain-text composer has no AI panel.Changes
utils/aiReply.js(new) builds the request:thread_id, so the thread is found throughresolve-messagefrom the answered Message-ID. A message with no thread is read on its own, and if nothing loads, the quoted original the composer already holds is used.>lines, the signature separator).ComposeModal.jsx: the action and the instruction field. The conversation is loaded once per composer, so rewording only asks the AI again. The two copies of the suggestion panel (phone and desktop layouts) are now oneComposeAiPanelcomponent.MessagePane.jsx,ConversationMessageCard.jsx,ConversationPane.jsx,composeFromMessage.js: the buttons, and anaiReplyflag that opens the composer with the panel. A message reopened by Undo send drops the flag.buildDraftPromptfor Write draft: the instruction, the subject and the recipients. Like the reply, it signs with the sender's name when there is no signature (or it is turned off, feat: add a compose signature toggle and mailbox default #556) instead of leaving a "[Your name]" placeholder.is_draft) are left out of what the AI reads: a saved reply draft was never sent.Testing
aiReply.test.js). Render tests: the reply is written from the thread and the instruction; Apply keeps the quoted original; rewording does not reload the thread; markup in a suggestion stays text; the button opens the composer with the instruction focused; it is not offered without the compose feature or in plain-text mode; Undo does not reopen the panel. The tests for escaping, the thread lookup, the single load, the focus and Undo were checked to fail with that part reverted.Contributor License Agreement
By submitting this pull request I confirm that:
third-party material and confirmed it is compatible with the CLA).
My English is basic, so this description was written with help from Claude.
🤖 Generated with the help of Claude Code