diff --git a/packages/ai-bot/lib/matrix/response-publisher.ts b/packages/ai-bot/lib/matrix/response-publisher.ts index ad327c38bd6..f688b64daa3 100644 --- a/packages/ai-bot/lib/matrix/response-publisher.ts +++ b/packages/ai-bot/lib/matrix/response-publisher.ts @@ -176,12 +176,13 @@ export default class MatrixResponsePublisher { return sendOperation; } - async sendError(error: any) { + async sendError(error: any, opts?: { reloadBillingData?: boolean }) { sendErrorEvent( this.client, this.roomId, error, this.originalResponseEventId, + opts, ); } diff --git a/packages/ai-bot/lib/responder.ts b/packages/ai-bot/lib/responder.ts index f08ab099f60..4b20a7b92b1 100644 --- a/packages/ai-bot/lib/responder.ts +++ b/packages/ai-bot/lib/responder.ts @@ -181,7 +181,10 @@ export class Responder { }; } - async onError(error: OpenAIError | string) { + async onError( + error: OpenAIError | string, + opts?: { reloadBillingData?: boolean }, + ) { Sentry.captureException(error, { extra: { roomId: this.matrixResponsePublisher.roomId, @@ -191,7 +194,7 @@ export class Responder { if (this.responseState.isStreamingFinished) { return; } - return await this.matrixResponsePublisher.sendError(error); + return await this.matrixResponsePublisher.sendError(error, opts); } async flush() { diff --git a/packages/ai-bot/main.ts b/packages/ai-bot/main.ts index e5fe60d403d..f7fcda49cea 100644 --- a/packages/ai-bot/main.ts +++ b/packages/ai-bot/main.ts @@ -442,6 +442,7 @@ Common issues are: // Careful when changing this message, it's used in the UI as a detection of whether to show the "Buy credits" button. return responder.onError( `You need a minimum of ${MINIMUM_AI_CREDITS_TO_CONTINUE} credits to continue using the AI bot. Please upgrade to a larger plan, or top up your account.`, + { reloadBillingData: true }, ); } diff --git a/packages/host/app/components/ai-assistant/message/index.gts b/packages/host/app/components/ai-assistant/message/index.gts index 5594614ef29..54ae38a6991 100644 --- a/packages/host/app/components/ai-assistant/message/index.gts +++ b/packages/host/app/components/ai-assistant/message/index.gts @@ -2,10 +2,13 @@ import type { TemplateOnlyComponent } from '@ember/component/template-only'; import { registerDestructor } from '@ember/destroyable'; import { hash } from '@ember/helper'; import { action } from '@ember/object'; +import { scheduleOnce } from '@ember/runloop'; import { service } from '@ember/service'; import type { SafeString } from '@ember/template'; import Component from '@glimmer/component'; +import { task } from 'ember-concurrency'; +import perform from 'ember-concurrency/helpers/perform'; import Modifier from 'ember-modifier'; import throttle from 'lodash/throttle'; @@ -45,6 +48,7 @@ interface Signature { isFromAssistant: boolean; isStreaming: boolean; isLastAssistantMessage: boolean; + isMostRecentMessage?: boolean; userMessageThisMessageIsRespondingTo?: Message; profileAvatar?: ComponentLike; collectionResource?: ReturnType; @@ -64,6 +68,7 @@ interface Signature { element: HTMLElement; }) => void; errorMessage?: string; + reloadBillingData?: boolean; isDebugMessage?: boolean; isPending?: boolean; retryAction?: () => void; @@ -234,6 +239,40 @@ class ScrollPosition extends Modifier { } } +interface ReloadBillingOnInsertSignature { + Args: { + Named: { + shouldReloadBillingData: boolean; + reload: () => void; + }; + }; +} + +// In the future if we implement subscription to credit consumption, we can remove this modifier +// It's currently used to reload the billing data when an out of credits error message is shown so that we can +// conditionally display the "buy more credits" button, or "credits added" message + retry button +class ReloadBillingOnInsert extends Modifier { + private hasReloaded = false; + + private runReload(reload: () => void) { + reload(); + } + + modify( + _element: HTMLElement, + _positional: [], + { + shouldReloadBillingData, + reload, + }: ReloadBillingOnInsertSignature['Args']['Named'], + ) { + if (shouldReloadBillingData && !this.hasReloaded) { + this.hasReloaded = true; + scheduleOnce('afterRender', this, this.runReload, reload); + } + } +} + function isThinkingMessage(s: string | null | undefined) { if (!s) { return false; @@ -328,6 +367,12 @@ export default class AiAssistantMessage extends Component { } } + private reloadBillingDataTask = task(async () => { + if (!this.billingService.loadingSubscriptionData) { + await this.billingService.loadSubscriptionData(); + } + }); +