From 30aa30952d69fdeacd16b5043371d689398e5914 Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Mon, 23 Mar 2026 14:21:15 +0100 Subject: [PATCH 1/3] Reload billing data after ai-bot responds with "out of credit" error --- .../ai-bot/lib/matrix/response-publisher.ts | 3 +- packages/ai-bot/lib/responder.ts | 7 ++- packages/ai-bot/main.ts | 1 + .../components/ai-assistant/message/index.gts | 52 +++++++++++++++++++ .../app/components/matrix/room-message.gts | 3 ++ packages/host/app/components/matrix/room.gts | 1 + .../app/lib/matrix-classes/message-builder.ts | 12 +++++ .../host/app/lib/matrix-classes/message.ts | 2 + packages/runtime-common/ai/matrix-utils.ts | 3 ++ packages/runtime-common/matrix-constants.ts | 1 + 10 files changed, 82 insertions(+), 3 deletions(-) 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..76ecfc7055b 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,37 @@ class ScrollPosition extends Modifier { } } +interface ReloadBillingOnInsertSignature { + Args: { + Named: { + shouldReloadBillingData: boolean; + reload: () => void; + }; + }; +} + +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 +364,12 @@ export default class AiAssistantMessage extends Component { } } + private reloadBillingDataTask = task(async () => { + if (!this.billingService.loadingSubscriptionData) { + await this.billingService.loadSubscriptionData(); + } + }); +