feat: [90250] add agent avatar functionality#90689
Conversation
This comment has been minimized.
This comment has been minimized.
dbb0d28 to
d40662a
Compare
|
🚧 @NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here. |
|
@ZhenjaHorbach Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb8d37d1bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
🚧 @NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
25f70a9 to
5e32c4e
Compare
|
@NicolasBonet Optimistic handling got missed here, can you add it? Screen.Recording.2026-05-14.at.11.55.00.PM.mov |
|
Looks like some backend changes are pending for the scenario when avatar is changed via the agent copilot mode. Screen.Recording.2026-05-14.at.11.59.24.PM.mov |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-05-15.at.12.07.31.AM.movAndroid: mWeb ChromeScreen.Recording.2026-05-15.at.12.08.17.AM.moviOS: HybridAppiOS: mWeb SafariScreen.Recording.2026-05-15.at.12.03.32.AM.movMacOS: Chrome / SafariScreen.Recording.2026-05-15.at.12.02.24.AM.mov |
803c382 to
e587dd0
Compare
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/de.ts b/src/languages/de.ts
index 5eb15103..4e1a485b 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -2746,7 +2746,7 @@ ${amount} für ${merchant} – ${date}`,
genericUpdate: 'Beim Aktualisieren dieses Agenten ist ein Problem aufgetreten',
updateName: 'Beim Aktualisieren des Namens dieser Vertretung ist ein Problem aufgetreten',
updatePrompt: 'Beim Aktualisieren der Anweisungen dieses Agenten ist ein Problem aufgetreten',
- updateAvatar: 'Beim Aktualisieren des Avatars dieser Vertretung ist ein Problem aufgetreten',
+ updateAvatar: 'Beim Aktualisieren des Avatars dieses Agenten ist ein Problem aufgetreten',
},
},
addAgentPage: {
@@ -2754,7 +2754,7 @@ ${amount} für ${merchant} – ${date}`,
agentName: 'Name der Ansprechperson',
instructions: 'Eigene Anweisungen schreiben',
createAgent: 'Agent erstellen',
- editAvatar: 'Profilbild wechseln',
+ editAvatar: 'Avatar bearbeiten',
defaultAgentName: (displayName: string) => `Agent*in von ${displayName}`,
defaultPrompt:
'Lehne Ausgaben ab, die für Glücksspiele, Kinobesuche oder andere offensichtlich nicht geschäftliche Zwecke sind.\n\nErinnere den:die Nutzer:in daran, immer ein Belegfoto beizufügen, auf dem das Trinkgeld klar erkennbar ist.\n\nGenehmige den Bericht, wenn er früheren Berichten derselben Person sehr ähnlich ist.\n\nLehne Berichte mit mehr als 500 $ an Reisekosten ab.',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 5684e269..e620b8ae 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2618,7 +2618,7 @@ ${amount} para ${merchant} - ${date}`,
genericUpdate: 'Hubo un problema al actualizar este agente',
updateName: 'Hubo un problema al actualizar el nombre de este agente',
updatePrompt: 'Hubo un problema al actualizar las instrucciones de este agente',
- updateAvatar: 'Hubo un problema al actualizar el avatar de este agente',
+ updateAvatar: 'Se ha producido un problema al actualizar el avatar de este agente',
},
},
addAgentPage: {
@@ -2626,7 +2626,7 @@ ${amount} para ${merchant} - ${date}`,
agentName: 'Nombre del agente',
instructions: 'Escribe instrucciones personalizadas',
createAgent: 'Crear agente',
- editAvatar: 'Cambiar avatar',
+ editAvatar: 'Editar avatar',
defaultAgentName: (displayName: string) => `Agente de ${displayName}`,
defaultPrompt:
'Rechazar gastos por juegos de azar, películas u otras razones claramente no comerciales.\n\nRecordar al usuario que siempre incluya una imagen del recibo que muestre claramente la propina.\n\nAprobar el informe si es muy similar a informes anteriores del mismo usuario.\n\nRechazar informes con más de $500 en gastos de viaje.',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 7e66d676..af9163d9 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -2752,7 +2752,7 @@ ${amount} pour ${merchant} - ${date}`,
genericUpdate: 'Un problème est survenu lors de la mise à jour de cet agent',
updateName: 'Un problème est survenu lors de la mise à jour du nom de cet agent',
updatePrompt: 'Un problème est survenu lors de la mise à jour des instructions de cet agent',
- updateAvatar: "Un problème est survenu lors de la mise à jour de l'avatar de cet agent",
+ updateAvatar: 'Un problème est survenu lors de la mise à jour de l’avatar de cet agent',
},
},
addAgentPage: {
@@ -2760,7 +2760,7 @@ ${amount} pour ${merchant} - ${date}`,
agentName: 'Nom de l’agent',
instructions: 'Rédiger des instructions personnalisées',
createAgent: 'Créer un agent',
- editAvatar: "Changer d'avatar",
+ editAvatar: 'Modifier l’avatar',
defaultAgentName: (displayName: string) => `Agent de ${displayName}`,
defaultPrompt:
'Rejeter les dépenses liées aux jeux d’argent, aux films ou à d’autres motifs manifestement non professionnels.\n\nRappeler à l’utilisateur d’inclure systématiquement une image du reçu où le pourboire est clairement visible.\n\nApprouver le rapport s’il est très similaire aux rapports précédents du même utilisateur.\n\nRejeter les rapports contenant plus de 500 $ de frais de déplacement.',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index e9498117..f9d3439d 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -2742,7 +2742,7 @@ ${amount} per ${merchant} - ${date}`,
genericUpdate: "Si è verificato un problema durante l'aggiornamento di questo agente",
updateName: "Si è verificato un problema durante l'aggiornamento del nome di questo agente",
updatePrompt: "Si è verificato un problema durante l'aggiornamento delle istruzioni di questo agente",
- updateAvatar: "Si è verificato un problema durante l'aggiornamento dell'avatar di questo agente",
+ updateAvatar: 'Si è verificato un problema durante l’aggiornamento dell’avatar di questo agente',
},
},
addAgentPage: {
@@ -2750,7 +2750,7 @@ ${amount} per ${merchant} - ${date}`,
agentName: 'Nome agente',
instructions: 'Scrivi istruzioni personalizzate',
createAgent: 'Crea agente',
- editAvatar: 'Cambia avatar',
+ editAvatar: 'Modifica avatar',
defaultAgentName: (displayName: string) => `Agente di ${displayName}`,
defaultPrompt:
"Rifiuta le spese relative a gioco d'azzardo, cinema o altri motivi chiaramente non legati all'attività.\n\nRicorda all'utente di includere sempre un'immagine della ricevuta in cui la mancia sia ben visibile.\n\nApprova il report se è molto simile ai report precedenti dello stesso utente.\n\nRifiuta i report con più di 500 $ di spese di viaggio.",
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index f432558b..3332e8fe 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -2722,7 +2722,7 @@ ${date} の ${merchant} への ${amount}`,
agentName: 'エージェント名',
instructions: 'カスタム指示を作成',
createAgent: 'エージェントを作成',
- editAvatar: 'アバターを切り替え',
+ editAvatar: 'アバターを編集',
defaultAgentName: (displayName: string) => `${displayName} さんの代理人`,
defaultPrompt:
'ギャンブル、映画、またはその他明らかにビジネス目的ではない理由による経費は却下します。\n\nチップの金額が明確にわかるレシート画像を必ず添付するよう、ユーザーにリマインドします。\n\n同じユーザーの過去のレポートと非常によく似ている場合は、そのレポートを承認します。\n\n出張費が500ドルを超えるレポートは却下します。',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 7a2511da..c7f418f6 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -2738,7 +2738,7 @@ ${amount} voor ${merchant} - ${date}`,
genericUpdate: 'Er is een probleem opgetreden bij het bijwerken van deze agent',
updateName: 'Er is een probleem opgetreden bij het bijwerken van de naam van deze agent',
updatePrompt: 'Er is een probleem opgetreden bij het bijwerken van de instructies van deze agent',
- updateAvatar: 'Er is een probleem opgetreden bij het bijwerken van de avatar van deze agent',
+ updateAvatar: 'Er is een probleem opgetreden bij het bijwerken van de avatar van deze medewerker',
},
},
addAgentPage: {
@@ -2746,7 +2746,7 @@ ${amount} voor ${merchant} - ${date}`,
agentName: 'Naam medewerker',
instructions: 'Schrijf aangepaste instructies',
createAgent: 'Agent aanmaken',
- editAvatar: 'Profielavatar wisselen',
+ editAvatar: 'Avatar bewerken',
defaultAgentName: (displayName: string) => `Agent van ${displayName}`,
defaultPrompt:
'Wijs declaraties af die zijn voor gokken, films of andere duidelijk niet-zakelijke redenen.\n\nHerinner de gebruiker eraan altijd een bonafbeelding toe te voegen waarop de fooi duidelijk is.\n\nKeur het verslag goed als het sterk lijkt op eerdere verslagen van dezelfde gebruiker.\n\nWijs verslagen af met meer dan $500 aan reiskosten.',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index c88084c9..09a38b6e 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -2741,7 +2741,7 @@ ${amount} dla ${merchant} - ${date}`,
agentName: 'Nazwa agenta',
instructions: 'Napisz niestandardowe instrukcje',
createAgent: 'Utwórz agenta',
- editAvatar: 'Zmień awatar',
+ editAvatar: 'Edytuj awatar',
defaultAgentName: (displayName: string) => `Agent ${displayName}`,
defaultPrompt:
'Odrzucaj wydatki związane z hazardem, filmami lub innymi oczywistymi celami niezwiązanymi z działalnością biznesową.\n\nPrzypominaj użytkownikowi, aby zawsze dołączał zdjęcie paragonu, na którym wysokość napiwku jest wyraźnie widoczna.\n\nZatwierdź raport, jeśli jest bardzo podobny do wcześniejszych raportów tego samego użytkownika.\n\nOdrzucaj raporty zawierające więcej niż 500 USD wydatków na podróże.',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index f415f972..ce8b53f5 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -2741,7 +2741,7 @@ ${amount} para ${merchant} - ${date}`,
agentName: 'Nome do agente',
instructions: 'Escrever instruções personalizadas',
createAgent: 'Criar agente',
- editAvatar: 'Trocar avatar',
+ editAvatar: 'Editar avatar',
defaultAgentName: (displayName: string) => `Agente de ${displayName}`,
defaultPrompt:
'Rejeite despesas relacionadas a jogos de azar, cinema ou outros motivos claramente não relacionados ao negócio.\n\nLembre o usuário de sempre incluir uma imagem do recibo em que a gorjeta fique clara.\n\nAprove o relatório se ele for muito semelhante a relatórios anteriores do mesmo usuário.\n\nRejeite relatórios com mais de US$ 500 em despesas de viagem.',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index fe8909d0..6e697eae 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -2664,7 +2664,7 @@ ${amount},商户:${merchant} - 日期:${date}`,
genericUpdate: '更新此代理时出现问题',
updateName: '更新此代理名称时出现问题',
updatePrompt: '更新此代理的说明时出现问题',
- updateAvatar: '更新此代理的头像时出现问题',
+ updateAvatar: '更新此代理人的头像时出现问题',
},
},
addAgentPage: {
@@ -2672,7 +2672,7 @@ ${amount},商户:${merchant} - 日期:${date}`,
agentName: '代理名称',
instructions: '编写自定义说明',
createAgent: '创建代理',
- editAvatar: '切换头像',
+ editAvatar: '编辑头像',
defaultAgentName: (displayName: string) => `${displayName} 的代理人`,
defaultPrompt:
'拒绝与赌博、电影或其他明显非商务原因相关的报销。\n\n提醒用户务必附上一张能清楚显示小费金额的收据图片。\n\n如果报销报告与同一用户之前的报告非常相似,则批准该报告。\n\n拒绝包含超过 500 美元差旅费用的报销报告。',
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
|
🚧 @NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here. |
…cks for name, prompt, and avatar into a single boolean
…tyles in AvatarSelector and EditAgentAvatarPage
…URI is used for custom avatars
… custom avatar URIs
…vatarCatalog and EditAgentAvatarPage
… and language updates
…andling during agent creation
…to handle avatar selection and navigation
…n and improve navigation handling in AddAgentAvatarPage
…e AddAgentPage for avatar handling
…ction and refine mock implementations in related tests
…solution across components
…dant effect execution
4bdc823 to
e61227f
Compare
|
|
||
| const initialPresetID = getInitialPresetID(); | ||
|
|
||
| const handleSave = useCallback((params: OnSaveParams) => { |
There was a problem hiding this comment.
❌ CLEAN-REACT-PATTERNS-0 (docs)
React Compiler is enabled and this file compiles successfully. The useCallback on line 25 is redundant -- the compiler automatically memoizes closures based on their captured variables. Manual memoization adds noise and can interfere with the compiler's optimization model.
Remove useCallback and use a plain function:
const handleSave = (params: OnSaveParams) => {
if ('customExpensifyAvatarID' in params) {
setPendingAvatar({type: 'preset', id: params.customExpensifyAvatarID});
} else {
setPendingAvatar({type: 'file', file: params.file, uri: params.uri});
}
Navigation.goBack(ROUTES.SETTINGS_AGENTS_ADD);
};Reviewed at: e61227f | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
|
|
||
| const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: (list) => list?.[accountID]}); | ||
|
|
||
| const initialBotAvatar = useMemo(() => { |
There was a problem hiding this comment.
❌ CLEAN-REACT-PATTERNS-0 (docs)
React Compiler is enabled and this file compiles successfully. The useMemo wrapping initialBotAvatar is redundant -- the compiler automatically caches derived values based on their dependencies.
Remove useMemo and compute the value directly:
const initialBotAvatar = initialPresetID ? (botAvatars.find((av) => botAvatarIDs.get(av) === initialPresetID) ?? null) : null;Reviewed at: e61227f | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
| onSave({file: imageData.file, uri: imageData.uri}); | ||
| return; | ||
| } | ||
| updateAgentAvatar(accountID, {file: imageData.file, uri: imageData.uri}, personalDetails?.avatar); |
There was a problem hiding this comment.
❌ PERF-2 (docs)
The handleSave function is missing a return statement after the updateAgentAvatar + Navigation.goBack calls on lines 134-135. When imageData.file is truthy and onSave is falsy, the code calls updateAgentAvatar and Navigation.goBack but then falls through to the if (selectedBotAvatar) check on line 138. While these states are mutually exclusive in practice (selecting a file clears selectedBotAvatar), the missing return makes the control flow unclear and fragile.
Add a return after Navigation.goBack(fallbackRoute) on line 135:
if (imageData.file) {
if (onSave) {
onSave({file: imageData.file, uri: imageData.uri});
return;
}
updateAgentAvatar(accountID, {file: imageData.file, uri: imageData.uri}, personalDetails?.avatar);
Navigation.goBack(fallbackRoute);
return; // <-- add early return
}Reviewed at: e61227f | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
|
🚧 @NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/NicolasBonet in version: 9.3.76-0 🚀
|
|
No help site changes are required for this PR. This PR adds agent avatar functionality (setting/editing avatars for custom agents under Account > Agents). I searched Since the base Agents feature isn't documented on the help site yet, there's no article to update with the new avatar functionality. |
|
🚀 Deployed to production by https://github.com/roryabraham in version: 9.3.77-3 🚀
|
Explanation of Change
Fixed Issues
$ #90250
PROPOSAL: https://expensify.enterprise.slack.com/docs/T03SC9DTT/F0AKV1FPD41?focus_section_id=temp:C:PDZ6783461e5cc6405c96fb1f944
Tests
Offline tests
If you choose offline mode the avatar update can stale while connection is recovered.
QA Steps
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps./** comment above it */thisproperly so there are no scoping issues (i.e. foronClick={this.submit}the methodthis.submitshould be bound tothisin the constructor)thisare necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);ifthis.submitis never passed to a component event handler likeonClick)Screenshots/Videos
Android: Native
ScreenRecording_05-13-2026.11-47-59_1.MP4
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
ScreenRecording_05-13-2026.11-47-59_1.MP4