feat: add AI chat error handling for billing and API key errors (#16797)
## Summary This PR adds user-friendly error handling for AI chat features, specifically for **billing credits exhausted** and **API key not configured** errors. ## Changes ### Backend - Added `BILLING_CREDITS_EXHAUSTED` exception code with 402 status - Added `API_KEY_NOT_CONFIGURED` exception code with 503 status - Added billing check before AI chat streaming in `agent-chat.controller.ts` - Added error code to HTTP exception response body for frontend error type detection - Created `AgentRestApiExceptionFilter` for agent-specific errors ### Frontend - Created `AIChatBanner` - reusable banner component for error/warning messages - Created `AIChatCreditsExhaustedMessage` - shows upgrade prompts based on user permissions - Created `AIChatApiKeyNotConfiguredMessage` - shows configuration guidance with docs link - Created `AIChatErrorRenderer` - encapsulates error type switching logic (fixes nested ternary) - Created `AIChatStandaloneError` - displays errors when there are no messages - Split `aiChatErrorUtils.ts` into separate files (1 export per file): - `AIChatErrorCode.ts` - `extractErrorCode.ts` - `isAIChatErrorOfType.ts` - `isBillingCreditsExhaustedError.ts` - `isApiKeyNotConfiguredError.ts` - Added comprehensive test coverage (27 tests) ### Other - Updated trial period banner messaging ## Testing - All lint checks pass - All 27 new tests pass - TypeScript typecheck passes
This commit is contained in:
+7
-6
@@ -21,11 +21,11 @@ import {
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { handleException } from 'src/engine/utils/global-exception-handler.util';
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
interface RequestAndParams {
|
||||
request: Request | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
params: any;
|
||||
params: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
const getErrorNameFromStatusCode = (statusCode: number) => {
|
||||
@@ -34,6 +34,8 @@ const getErrorNameFromStatusCode = (statusCode: number) => {
|
||||
return 'BadRequestException';
|
||||
case 401:
|
||||
return 'UnauthorizedException';
|
||||
case 402:
|
||||
return 'PaymentRequiredException';
|
||||
case 403:
|
||||
return 'ForbiddenException';
|
||||
case 404:
|
||||
@@ -66,13 +68,11 @@ export class HttpExceptionHandlerService {
|
||||
|
||||
handleError = (
|
||||
exception: Error | HttpException,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
response: Response<any, Record<string, any>>,
|
||||
response: Response,
|
||||
errorCode?: number,
|
||||
user?: ExceptionHandlerUser,
|
||||
workspace?: ExceptionHandlerWorkspace,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): Response<any, Record<string, any>> | undefined => {
|
||||
): Response | undefined => {
|
||||
const params = this.request?.params;
|
||||
|
||||
if (params?.workspaceId) {
|
||||
@@ -121,6 +121,7 @@ export class HttpExceptionHandlerService {
|
||||
statusCode,
|
||||
error: exception.name ?? getErrorNameFromStatusCode(statusCode),
|
||||
messages: [exception?.message],
|
||||
code: exception instanceof CustomException ? exception.code : undefined,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user