Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,9 @@ const CONST = {

GENERATED_RULE: {
PROMPT_MAX_LENGTH: 6000,

// The slowest answers in the eval take about 13 seconds. After this time, the agent rule saves without an answer.
AGENT_RULE_CHECK_TIMEOUT_MS: 20000,
STATE: {
RULE: 'rule',
UNSUPPORTED: 'unsupported',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9204,6 +9204,8 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
describeRuleForConcierge: 'Beschreiben Sie Ihre Regel und Concierge erstellt sie',
gotIt: 'Verstanden',
createRule: 'Regel erstellen',
saveAnyway: 'Trotzdem speichern',
checkingRule: 'Ihre Regel wird geprüft...',
fallbackBanner:
'Dies ist keine Standardregel, daher haben wir sie als Agentenregel erstellt. Agentenregeln greifen, nachdem sich eine Ausgabe oder ein Bericht geändert hat, zum Beispiel indem sie die Ausgabe zurückhalten oder den Bericht ablehnen.',
fallbackBannerWithExplanation: (explanation: string) => `Dies ist keine Standardregel, daher haben wir sie als Agentenregel formuliert. ${explanation}`,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9667,6 +9667,8 @@ ${reportName}`,
describeRuleHeadline: 'Περιγράψτε τον κανόνα σας',
describeRuleForConcierge: 'Περιγράψτε τον κανόνα σας και το Concierge θα τον δημιουργήσει',
createRule: 'Δημιουργία κανόνα',
saveAnyway: 'Αποθήκευση ούτως ή άλλως',
checkingRule: 'Γίνεται έλεγχος του κανόνα σας...',
fallbackBanner:
'Αυτός δεν είναι ένας τυπικός κανόνας, οπότε τον διαμορφώσαμε ως κανόνα πράκτορα. Οι κανόνες πράκτορα εφαρμόζονται αφού αλλάξει μια δαπάνη ή μια αναφορά, για παράδειγμα κρατώντας τη δαπάνη ή απορρίπτοντας την αναφορά.',
fallbackBannerWithExplanation: (explanation: string) => `Αυτός δεν είναι ένας τυπικός κανόνας, οπότε τον συντάξαμε ως κανόνα εκπροσώπου. ${explanation}`,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9984,6 +9984,8 @@ const translations = {
describeRuleForConcierge: 'Describe your rule and Concierge will build it',
disclaimer: 'AI agents can make mistakes.',
createRule: 'Create rule',
saveAnyway: 'Save anyway',
checkingRule: 'Checking your rule...',
fallbackBanner:
"This isn't a standard rule, so we drafted it as an agent rule. Agent rules act after an expense or report changes, for example by holding the expense or rejecting the report.",
fallbackBannerWithExplanation: (explanation: string) => `This isn't a standard rule, so we drafted it as an agent rule. ${explanation}`,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9209,6 +9209,8 @@ ${reportName}`,
describeRuleForConcierge: 'Describe tu regla y Concierge la creará',
gotIt: 'Entendido',
createRule: 'Crear regla',
saveAnyway: 'Guardar de todos modos',
checkingRule: 'Comprobando tu regla...',
fallbackBanner:
'Esta no es una regla estándar, así que la redactamos como una regla de agente. Las reglas de agente actúan después de que un gasto o informe cambia, por ejemplo reteniendo el gasto o rechazando el informe.',
fallbackBannerWithExplanation: (explanation: string) => `Esta no es una regla estándar, así que la redactamos como una regla de agente. ${explanation}`,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9228,6 +9228,8 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
describeRuleForConcierge: 'Décrivez votre règle et Concierge la créera',
gotIt: 'Compris',
createRule: 'Créer une règle',
saveAnyway: 'Enregistrer quand même',
checkingRule: 'Vérification de votre règle...',
fallbackBanner:
'Ce n’est pas une règle standard, nous l’avons donc rédigée comme une règle d’agent. Les règles d’agent agissent après la modification d’une dépense ou d’une note de frais, par exemple en bloquant la dépense ou en rejetant la note de frais.',
fallbackBannerWithExplanation: (explanation: string) => `Ce n’est pas une règle standard, nous l’avons donc rédigée comme une règle d’agent. ${explanation}`,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9156,6 +9156,8 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
describeRuleForConcierge: 'Descrivi la tua regola e Concierge la creerà',
gotIt: 'Capito',
createRule: 'Crea regola',
saveAnyway: 'Salva comunque',
checkingRule: 'Verifica della tua regola in corso...',
fallbackBanner:
'Questa non è una regola standard, quindi l’abbiamo creata come regola agente. Le regole agente agiscono dopo che una spesa o un report vengono modificati, ad esempio bloccando la spesa o rifiutando il report.',
fallbackBannerWithExplanation: (explanation: string) => `Questa non è una regola standard, quindi l’abbiamo formulata come regola dell’agente. ${explanation}`,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9035,6 +9035,8 @@ ${reportName}`,
describeRuleForConcierge: 'ルールの内容を入力すると、Concierge が自動作成します',
gotIt: '了解しました',
createRule: 'ルールを作成',
saveAnyway: 'このまま保存',
checkingRule: 'ルールを確認しています...',
fallbackBanner:
'これは標準ルールではないため、エージェントルールとして作成しました。エージェントルールは、経費やレポートに変更があった後に動作し、例えば経費を保留にしたり、レポートを却下したりします。',
fallbackBannerWithExplanation: (explanation: string) => `これは標準ルールではないため、エージェントルールとして作成しました。${explanation}`,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9128,6 +9128,8 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
describeRuleForConcierge: 'Beschrijf je regel en Concierge bouwt hem voor je',
gotIt: 'Begrepen',
createRule: 'Regel maken',
saveAnyway: 'Toch opslaan',
checkingRule: 'Je regel wordt gecontroleerd...',
fallbackBanner:
'Dit is geen standaardregel, dus hebben we hem opgesteld als een agentregel. Agentregels treden in werking nadat een uitgave of rapport verandert, bijvoorbeeld door de uitgave vast te houden of het rapport af te wijzen.',
fallbackBannerWithExplanation: (explanation: string) => `Dit is geen standaardregel, dus hebben we het opgesteld als een agentregel. ${explanation}`,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9155,6 +9155,8 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
describeRuleForConcierge: 'Opisz swoją regułę, a Concierge ją utworzy',
gotIt: 'Jasne',
createRule: 'Utwórz regułę',
saveAnyway: 'Zapisz mimo to',
checkingRule: 'Sprawdzanie reguły...',
fallbackBanner:
'To nie jest standardowa reguła, więc przygotowaliśmy ją jako regułę agenta. Reguły agenta działają po zmianie wydatku lub raportu, na przykład wstrzymując wydatek albo odrzucając raport.',
fallbackBannerWithExplanation: (explanation: string) => `To nie jest standardowa reguła, więc przygotowaliśmy ją jako regułę agenta. ${explanation}`,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9143,6 +9143,8 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
describeRuleForConcierge: 'Descreva sua regra e o Concierge vai criá-la',
gotIt: 'Entendi',
createRule: 'Criar regra',
saveAnyway: 'Salvar mesmo assim',
checkingRule: 'Verificando sua regra...',
fallbackBanner:
'Esta não é uma regra padrão, então a criamos como uma regra de agente. As regras de agente entram em ação depois que uma despesa ou um relatório é alterado, por exemplo, ao reter a despesa ou rejeitar o relatório.',
fallbackBannerWithExplanation: (explanation: string) => `Esta não é uma regra padrão, então nós a elaboramos como uma regra de agente. ${explanation}`,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8823,6 +8823,8 @@ ${reportName}`,
describeRuleForConcierge: '描述你的规则,Concierge 会帮你创建',
gotIt: '知道了',
createRule: '创建规则',
saveAnyway: '仍然保存',
checkingRule: '正在检查你的规则...',
fallbackBanner: '这不是一条标准规则,所以我们将其制定为代理规则。代理规则会在报销或报表发生变更后生效,例如暂扣该报销或驳回该报表。',
fallbackBannerWithExplanation: (explanation: string) => `这不是一条标准规则,因此我们将其起草为代理规则。${explanation}`,
},
Expand Down
43 changes: 42 additions & 1 deletion src/libs/actions/Policy/Rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,45 @@ function generateRule(policyID: string, prompt: string, ruleType?: GeneratedRule
return generationID;
}

type AgentRuleFormID = typeof ONYXKEYS.FORMS.ADD_AGENT_RULE_FORM | typeof ONYXKEYS.FORMS.EDIT_AGENT_RULE_FORM;

/**
* Asks Concierge whether RuleBot can carry out an agent rule before the rule saves. The agent rule form shows its loading
* state until the answer arrives under `ONYXKEYS.GENERATED_RULE`. A failed request gets a `failed` answer, so the rule
* still saves.
* @returns the generationID the answer carries
*/
function checkAgentRulePrompt(formID: AgentRuleFormID, policyID: string, prompt: string): string {
const generationID = rand64();
type CheckAgentRuleKey = AgentRuleFormID | typeof ONYXKEYS.GENERATED_RULE;

const optimisticData: Array<OnyxUpdate<CheckAgentRuleKey>> = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: formID,
value: {isLoading: true},
},
];
const failureData: Array<OnyxUpdate<CheckAgentRuleKey>> = [
{
onyxMethod: Onyx.METHOD.SET,
key: ONYXKEYS.GENERATED_RULE,
value: {generationID, state: CONST.GENERATED_RULE.STATE.FAILED},
},
];

const parameters: GenerateRuleParams = {policyID, generationID, prompt};

API.write(WRITE_COMMANDS.GENERATE_RULE, parameters, {optimisticData, failureData});

return generationID;
}

/** Stops the loading state of an agent rule form */
function stopAgentRuleCheckLoading(formID: AgentRuleFormID) {
Onyx.merge(formID, {isLoading: false});
}

/** Shows on the prompt form why a description did not become a rule */
function setNewRulePromptError(state: GeneratedRuleState) {
setNewRulePromptErrorMessage(PROMPT_ERROR_BY_STATE[state] ?? 'common.genericErrorMessage');
Expand Down Expand Up @@ -682,9 +721,11 @@ export {
clearGeneratedRule,
clearNewRulePrompt,
stopNewRulePromptLoading,
checkAgentRulePrompt,
stopAgentRuleCheckLoading,
updatePolicyAgentRule,
deletePolicyAgentRule,
clearMerchantRuleErrors,
clearPolicyAgentRuleErrors,
};
export type {ImportedMerchantRule};
export type {AgentRuleFormID, ImportedMerchantRule};
52 changes: 34 additions & 18 deletions src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,26 @@ function AddAgentRulePage({

const hasDraftPrompt = !!draftValues?.[INPUT_IDS.PROMPT]?.trim();
const [selectedSuggestion, setSelectedSuggestion] = useState<SuggestedAgentRule>();

// A new suggestion replaces the text of the Write tab. Mount a new Write tab, so that a check of the old text stops
// and cannot save it.
const [suggestionPickCount, setSuggestionPickCount] = useState(0);
const isDraftFromFallback = !!generationID && !selectedSuggestion;
const fallbackExplanation = generatedRule?.generationID === generationID ? generatedRule?.explanation : undefined;

// A suggestion or a draft of the describe flow saves without a check while the admin leaves its text unchanged
const [fallbackDraftPrompt, setFallbackDraftPrompt] = useState<string>();
const checkedPrompt = selectedSuggestion?.prompt ?? fallbackDraftPrompt;

// The draft can load after the first render. Open Write one time only, because the admin must still be able to go
// to Suggestions.
const [didOpenTabForDraft, setDidOpenTabForDraft] = useState(false);
if (!didOpenTabForDraft && hasDraftPrompt) {
setDidOpenTabForDraft(true);
setActiveTab(CONST.TAB.AGENT_RULE.WRITE);
if (generationID) {
setFallbackDraftPrompt(draftValues?.[INPUT_IDS.PROMPT]);
}
}

// Reset the active tab to Suggestions when the workspace changes.
Expand Down Expand Up @@ -132,6 +143,7 @@ function AddAgentRulePage({
}
setDraftValues(ONYXKEYS.FORMS.ADD_AGENT_RULE_FORM, {[INPUT_IDS.PROMPT]: suggestion.prompt});
setSelectedSuggestion(suggestion);
setSuggestionPickCount((count) => count + 1);
setActiveTab(CONST.TAB.AGENT_RULE.WRITE);
};

Expand Down Expand Up @@ -254,24 +266,28 @@ function AddAgentRulePage({
onSelectSuggestion={selectSuggestion}
/>
</View>
{activeTab === CONST.TAB.AGENT_RULE.WRITE && (
<>
{isDraftFromFallback && (
<Banner
text={
fallbackExplanation
? translate('workspace.rules.agentRules.fallbackBannerWithExplanation', fallbackExplanation)
: translate('workspace.rules.agentRules.fallbackBanner')
}
icon={icons.Info}
shouldShowIcon
containerStyles={[styles.mh5, styles.mb3, styles.p4, styles.br3]}
textStyles={[styles.breakWord, styles.textSupporting]}
/>
)}
<AddAgentRuleWriteTab onSave={saveRule} />
</>
)}
{/* The Write tab stays mounted on Suggestions, so that a running check and its answer stay with the admin's text */}
<View style={[styles.flex1, activeTab !== CONST.TAB.AGENT_RULE.WRITE && styles.dNone]}>
{isDraftFromFallback && (
<Banner
text={
fallbackExplanation
? translate('workspace.rules.agentRules.fallbackBannerWithExplanation', fallbackExplanation)
: translate('workspace.rules.agentRules.fallbackBanner')
}
icon={icons.Info}
shouldShowIcon
containerStyles={[styles.mh5, styles.mb3, styles.p4, styles.br3]}
textStyles={[styles.breakWord, styles.textSupporting]}
/>
)}
<AddAgentRuleWriteTab
key={suggestionPickCount}
policyID={policyID}
checkedPrompt={checkedPrompt}
onSave={saveRule}
/>
</View>
</View>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
Expand Down
Loading
Loading