feat: Reply with euria - #2986
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an Euria-assisted reply flow from message threads, reusing the existing AI prompt and proposition UI.
Changes:
- Adds an AI Reply action and prompt bottom sheet.
- Reuses AI proposition generation with message context arguments.
- Extracts the AI prompt into a reusable custom view.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
new_message_navigation.xml |
Adds the optional message UID argument. |
main_navigation.xml |
Registers the reply prompt and proposition destinations. |
view_ai_prompt.xml |
Hosts the reusable prompt view. |
view_ai_prompt_content.xml |
Updates the prompt layout header. |
bottom_sheet_ask_euria_actions.xml |
Adds the Reply action. |
ic_reply_euria.xml |
Adds the Euria reply icon. |
AiPromptView.kt |
Encapsulates prompt UI behavior. |
AiPropositionFragment.kt |
Supports generation from a thread message. |
AiPromptFragment.kt |
Adopts the reusable prompt view. |
ThreadFragment.kt |
Coordinates the new reply navigation flow. |
EuriaPromptBottomSheet.kt |
Implements the thread reply prompt. |
AskEuriaBottomSheetDialog.kt |
Handles Reply action selection. |
64cfff3 to
555b467
Compare
6003d48 to
becf9f6
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (2)
app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPropositionFragment.kt:325
- This conversion treats every body as HTML. For
text/plainmessages,htmlToText()removes newline characters before parsing, which concatenates lines; for HTML replies it also retains quoted history instead of using the existing quote-aware conversion inNewMessageViewModel. Reuse a sharedBody.asPlainText()implementation so Euria receives the same clean context as the composer flow.
aiViewModel.previousMessageBodyPlainText = message?.body?.value?.htmlToText()
app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPropositionFragment.kt:152
- Reading
currentMailbox.valueonly once can leavemailboxuninitialized when this destination is restored before the asynchronous LiveData query emits. Generation then accessesmailbox.uuidand crashes. Await a non-null mailbox (or initialize and generate in one coroutine) instead of returning permanently on the initial null value.
mainViewModel.currentMailbox.value ?: return@launch
a6f568a to
dd955e6
Compare
b6a2bd8 to
e78ddbb
Compare
| ) : FrameLayout(context, attrs, defStyleAttr) { | ||
|
|
||
| private val binding = ViewAiPromptContentBinding.inflate(LayoutInflater.from(context), this, true) | ||
| private var onPromptChanged: ((String) -> Unit)? = null |
There was a problem hiding this comment.
You can remove this variable and directly use the one you pass as argument in the doAfterTextChanged.
Also as bind could theoretically be called several time you would stack listeners to the button because they aren't cleared before adding.
(But this would be a dev error, so i'm not for complexifying the code to avoid a simple dev error)
| } | ||
|
|
||
| if (binding.reply.trailingContent != TrailingContent.None) { | ||
| binding.reply.setOnClickListener { |
There was a problem hiding this comment.
I think opening the kSuite bottomsheet should dismiss this one.
Like the mail or threadActionBottomsheet
| private val navigationArgs: AiPropositionFragmentArgs by navArgs() | ||
|
|
||
| private var currentRequestJob: Job? = null | ||
| lateinit var mailbox: Mailbox |
There was a problem hiding this comment.
Never use lateinit, it's "old code" and it's crash prone.
Use a nullable variable if it's absolutely necessary
But here you can just use a by lazy (in a lot of cases lateinit can be replace by by lazy)
There was a problem hiding this comment.
I'm still pretty annoyed by the fact that we use this fragment in both activities instead of opening this from the newMessageActivity directly :/
And this force us to do this kind of manipulation with mailbox from one or the other viewModel
…fore starting generation
…my request" button
1641440 to
027671c
Compare
| val mailbox = mainViewModel.currentMailbox.value | ||
| val kSuite = mailbox?.kSuite | ||
|
|
||
| val matomoName = MatomoName.ReplyWithEuria.value |
There was a problem hiding this comment.
You don't need this variable anymore
| Summarize("summarize"), | ||
| Translate("translate"), | ||
| AskEuria("askEuria"), | ||
| AskEuriaShortCut("askEuriaShortCut"), |
| suspend fun Body.asPlainText(): String? { | ||
| // TODO: When the API handles blank characters, remove ifBlank | ||
| return when (type) { |
There was a problem hiding this comment.
Is it programmed that the back support this ?
88f472a to
b98a51f
Compare
ffff0aa to
246237b
Compare
| isAdmin = mailbox?.isAdmin ?: false, | ||
| matomoName = matomoName, | ||
| substituteClassName = ThreadListFragment::class.java.name, | ||
| onAvailable = { handleStandardReplyAction(messageUid) } |
There was a problem hiding this comment.
onAvailable isn't really clear, maybe onFeatureAvailable ?
e2c8fd5 to
36d645d
Compare
…n opening euria prompt dialog # Conflicts: # app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt
|



No description provided.