feat: add AI prompt section to profile settings for agent accounts#90823
feat: add AI prompt section to profile settings for agent accounts#90823NicolasBonet wants to merge 23 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
8edf06a to
79d6fdc
Compare
This comment has been minimized.
This comment has been minimized.
|
🚧 @NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here. |
54abe64 to
34b7940
Compare
|
🚧 @NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here. |
This comment has been minimized.
This comment has been minimized.
|
@parasharrajat 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] |
d04a338 to
316fed6
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 316fed6d28
ℹ️ 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. |
This comment has been minimized.
This comment has been minimized.
| read(READ_COMMANDS.OPEN_AGENTS_PAGE, null); | ||
| } | ||
|
|
||
| function readAgentPrompt(agentAccountID: number) { |
There was a problem hiding this comment.
Out of curiosity, why a ReadAgentPrompt command rather than an OpenProfilePage command. I don't have a super strong opinion, but the action the user is taking is opening the profile page, so that is what made sense to me.
There was a problem hiding this comment.
Because that is not using the recently introduced READ_AGENT_PROMPT command, which is new and with this change should be usable as it's: https://github.com/Expensify/Web-Expensify/pull/52950
| ]; | ||
|
|
||
| write(WRITE_COMMANDS.UPDATE_AGENT_PROMPT, {agentAccountID: accountID, prompt}, {optimisticData, successData, failureData}); | ||
| write(WRITE_COMMANDS.UPDATE_AGENT_PROMPT, {agentAccountID: accountID, reportID: 0, prompt}, {optimisticData, successData, failureData}); |
There was a problem hiding this comment.
why do we need reportID param?
There was a problem hiding this comment.
That is outdated, we are no longer needing that as the backend changed!
|
Oopsy doopsy.... @puneetlath, can you give my account access for this feature?
|
…romptSection for character limit handling This commit introduces a new AGENT_PROMPT_LIMIT constant set to 300, enhancing the management of character limits for agent prompts. The EditAgentPage is updated to include character limit handling for the prompt title, while the AgentAIPromptSection is modified to utilize a ScrollView for better display of prompts, ensuring improved user experience and consistency across components.
…ge transition This commit adds a loading state check in the AgentAIPromptSection component to prevent premature navigation to the profile page during app loading. The useEffect hook is updated to include the isLoadingApp dependency, ensuring that the openProfilePage function is only called when the app is fully loaded. This change enhances the user experience by avoiding potential issues during copilot transitions.
…stant for improved consistency
This commit introduces the containsHtmlTag function to the ValidationUtils module, which checks for non-whitelisted HTML-like tags in strings. The function is integrated into the AgentAIPromptSection and EditPromptPage components to prevent invalid characters in user inputs. Additionally, unit tests are added to ensure the functionality works as expected, enhancing input validation across the application.
…o reflect new subtitle: 'Write custom instructions'
a2de05f to
969e35a
Compare
|
@puneetlath updated that line:
|
🦜 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 d9b5d047..6f9f7a53 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1962,10 +1962,10 @@ const translations: TranslationDeepObject<typeof en> = {
},
aiPromptSection: {
title: 'KI-Aufforderung',
- subtitle: 'Eigene Anweisungen schreiben',
- prompt: 'Aufforderung',
+ subtitle: 'Eigene Anweisungen verfassen',
+ prompt: 'Eingabeaufforderung',
editPrompt: 'Eingabeaufforderung bearbeiten',
- promptCannotBeEmpty: 'Eingabe darf nicht leer sein',
+ promptCannotBeEmpty: 'Eingabeaufforderung darf nicht leer sein',
},
},
securityPage: {
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 55ba3e69..e8e16d7a 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1848,11 +1848,11 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Estos detalles se utilizan para viajes y pagos. Nunca se mostrarán en tu perfil público.',
},
aiPromptSection: {
- title: 'Indicaciones de IA',
- subtitle: 'Escribe instrucciones personalizadas',
+ title: 'Indicación para IA',
+ subtitle: 'Escribir instrucciones personalizadas',
prompt: 'Indicador',
editPrompt: 'Editar mensaje',
- promptCannotBeEmpty: 'El campo de indicaciones no puede estar vacío',
+ promptCannotBeEmpty: 'El mensaje no puede estar vacío',
},
},
securityPage: {
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 15a9581c..d2a589c4 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1966,10 +1966,10 @@ const translations: TranslationDeepObject<typeof en> = {
},
aiPromptSection: {
title: 'Invite IA',
- subtitle: 'Rédiger des instructions personnalisées',
+ subtitle: 'Écrire des instructions personnalisées',
prompt: 'Invite',
- editPrompt: "Modifier l'invite",
- promptCannotBeEmpty: "L'invite ne peut pas être vide",
+ editPrompt: 'Modifier l’invite',
+ promptCannotBeEmpty: 'L’invite ne peut pas être vide',
},
},
securityPage: {
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index d2426569..2fd1b308 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1938,13 +1938,7 @@ const translations: TranslationDeepObject<typeof en> = {
title: '非公開',
subtitle: 'これらの詳細は、旅行や支払いのために使用されます。あなたの公開プロフィールに表示されることは決してありません。',
},
- aiPromptSection: {
- title: 'AIプロンプト',
- subtitle: 'カスタム指示を作成',
- prompt: 'プロンプト',
- editPrompt: 'プロンプトを編集',
- promptCannotBeEmpty: 'プロンプトを入力してください',
- },
+ aiPromptSection: {title: 'AI プロンプト', subtitle: 'カスタム手順を作成', prompt: 'プロンプト', editPrompt: 'プロンプトを編集', promptCannotBeEmpty: 'プロンプトを入力してください'},
},
securityPage: {
title: 'セキュリティオプション',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 6c13c6dc..cda8baac 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1954,7 +1954,7 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Deze gegevens worden gebruikt voor reizen en betalingen. Ze worden nooit getoond op je openbare profiel.',
},
aiPromptSection: {
- title: 'AI-prompt',
+ title: 'AI-opdracht',
subtitle: 'Schrijf aangepaste instructies',
prompt: 'Prompt',
editPrompt: 'Prompt bewerken',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 26f850eb..73e735a2 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1954,13 +1954,7 @@ const translations: TranslationDeepObject<typeof en> = {
title: 'Prywatne',
subtitle: 'Te dane są używane do podróży i płatności. Nigdy nie są wyświetlane w Twoim publicznym profilu.',
},
- aiPromptSection: {
- title: 'Prompt AI',
- subtitle: 'Napisz niestandardowe instrukcje',
- prompt: 'Zachęta',
- editPrompt: 'Edytuj podpowiedź',
- promptCannotBeEmpty: 'Pole nie może być puste',
- },
+ aiPromptSection: {title: 'Prompt AI', subtitle: 'Napisz własne instrukcje', prompt: 'Monit', editPrompt: 'Edytuj podpowiedź', promptCannotBeEmpty: 'Pole nie może być puste'},
},
securityPage: {
title: 'Opcje zabezpieczeń',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index b0a570c6..121eaa21 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1956,7 +1956,7 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Escrever instruções personalizadas',
prompt: 'Prompt',
editPrompt: 'Editar prompt',
- promptCannotBeEmpty: 'O prompt não pode ficar vazio',
+ promptCannotBeEmpty: 'O prompt não pode estar vazio',
},
},
securityPage: {
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 0bece09d..6227597f 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1899,7 +1899,7 @@ const translations: TranslationDeepObject<typeof en> = {
title: '私人',
subtitle: '这些详细信息用于旅行和付款。它们绝不会显示在你的公开资料中。',
},
- aiPromptSection: {title: 'AI 提示', subtitle: '编写自定义说明', prompt: '提示', editPrompt: '编辑提示', promptCannotBeEmpty: '提示不能为空'},
+ aiPromptSection: {title: 'AI 提示词', subtitle: '编写自定义说明', prompt: '提示', editPrompt: '编辑提示', promptCannotBeEmpty: '提示不能为空'},
},
securityPage: {
title: '安全选项',
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
|
Great, thanks. @parasharrajat can you complete your review? I don't think we're going to get any design feedback this week and let's not block on it. We'll still see if they have any feedback on the PR next week and if so, we can address in a follow-up. |
Reviewer Checklist
Screenshots🔲 iOS / native🔲 iOS / Safari🔲 MacOS / Chrome23.05.2026_14.45.57_REC.mp4🔲 Android / native23.05.2026_15.44.54_REC.mp4 |
|
BUG; iOS Safari web: Noticed one scrolling issue on iOS Safari. Sometimes, scrolling the box adds whitespace below the input. I think this can be solved. This does not happen on other screens. 23.05.2026_14.48.04_REC.mp4 |
|
BUG: iOS: Screen does not scroll when focusing on the edit prompt input. Input remains hidden behind the keyboard. 23.05.2026_14.53.27_REC.mp4 |
|
🚧 @dubielzyk-expensify has triggered a test Expensify/App build. You can view the workflow run here. |
This comment has been minimized.
This comment has been minimized.
|
Why do we need an explicit "Edit prompt" button here? I guess I haven't seen this pattern elsewhere in the app before so I'm mostly just trying to understand how it's supposed to work. Like why not just show the editable text area and then show a Save/Cancel button if we detect that changes are made? |
|
We could for sure. The thinking was that you'd avoid any sort of mistypes or dialogs to discard. The main goal was to avoid the push input here, but if we wanna stay super consistent then having it as a push input or just an editable text area makes the most sense. Let me know what you're preference is. |
|
I guess the argument to avoid a push input is to reduce clicks. Having a "Edit" button is the same amount of clicks as the push input. So I think just having it be an editable textarea with a conditional Save/Cancel might be better? I don't feel strongly though, I am totally happy to go with what you have planned - just wanted to call that out. |
Yeah more about perception than clicks cause you're right.
We can go with that. @dannymcclain for thoughts as well |
|
No strong feelings from me. I like the explicitness of the Also I agree that the edit button and the push row are technically the same number of clicks, but agree with Jon that it's more about the perception haha. |
Can you give more steps or pre-conditions to reproduce this? I have tried, but I definitely cannot reproduce this one ever in Safari iOS
Fixed: Screen.Recording.2026-05-25.at.3.01.22.PM.mov |
…arentScrollViewRef from ProfilePage to manage scrolling behavior. Update imports and types for better clarity and functionality.
|
🚧 @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! 🧪🧪
|






Explanation of Change
This update introduces a new section in the profile settings where agent accounts can view and edit their AI prompt. The section includes a title, subtitle, and functionality to save or cancel edits. Additionally, translations for the new section have been added in multiple languages. The changes also include updates to the Agent actions to support the new prompt functionality.
Fixed Issues
$ #90244
PROPOSAL: https://expensify.enterprise.slack.com/docs/T03SC9DTT/F0AKV1FPD41?focus_section_id=temp:C:PDZa4644f0f9a49433fa55e205e5
Tests
Offline tests
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
screen-20260525-152533-1779740701083.mp4
Android: mWeb Chrome
screen-20260525-152851-1779740885647.mp4
iOS: Native
ScreenRecording_05-25-2026.15-19-42_1.MP4
iOS: mWeb Safari
ScreenRecording_05-25-2026.15-19-42_1.MP4
MacOS: Chrome / Safari