feat(admin-web): show centered modal in Chinese when AI points are insufficient

- Add `ai_points_insufficient` / `ai_points_unavailable` Chinese mappings in
  errors.ts so the toast copy is localized.
- Add `showAiPointsInsufficientModal()` helper that renders a centered
  `Modal.confirm` with localized title/body and a "查看点数明细" action that
  routes to `/account/ai-points`. The helper is debounced so concurrent
  failed requests do not stack multiple modals.
- Trigger the modal globally from the admin-web response interceptor when
  the backend returns `ai_points_insufficient`, so every AI-generating
  endpoint surfaces the same prominent prompt without per-callsite changes.
- Drop the English `detail` for ai_points_insufficient in `formatError` so
  the secondary toast stays pure Chinese.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 01:31:02 +08:00
parent 635782041a
commit b6cc12cefe
2 changed files with 42 additions and 0 deletions
+6
View File
@@ -315,6 +315,12 @@ apiClient.raw.interceptors.response.use(
markStoredMembershipBlocked(error.message) markStoredMembershipBlocked(error.message)
} }
if (error instanceof ApiClientError && error.message === 'ai_points_insufficient') {
void import('./errors').then(({ showAiPointsInsufficientModal }) => {
showAiPointsInsufficientModal()
})
}
return Promise.reject(error) return Promise.reject(error)
}, },
) )
+36
View File
@@ -1,5 +1,8 @@
import { Modal } from 'ant-design-vue'
import { ApiClientError } from '@geo/http-client' import { ApiClientError } from '@geo/http-client'
import { router } from '@/router'
const authSessionErrorMessages = new Set([ const authSessionErrorMessages = new Set([
'not_authenticated', 'not_authenticated',
'missing_bearer_token', 'missing_bearer_token',
@@ -28,6 +31,8 @@ const errorMessageMap: Record<string, string> = {
tenant_scope_missing: '租户上下文缺失', tenant_scope_missing: '租户上下文缺失',
query_failed: '数据读取失败', query_failed: '数据读取失败',
quota_insufficient: '生成额度不足', quota_insufficient: '生成额度不足',
ai_points_insufficient: 'AI 点数余额不足',
ai_points_unavailable: 'AI 点数服务暂不可用,请稍后重试',
trial_plan_expired: '免费试用已到期,请联系管理员开通', trial_plan_expired: '免费试用已到期,请联系管理员开通',
subscription_required: '当前租户尚未开通有效套餐,请联系管理员', subscription_required: '当前租户尚未开通有效套餐,请联系管理员',
subscription_inactive: '当前租户套餐已失效,请联系管理员', subscription_inactive: '当前租户套餐已失效,请联系管理员',
@@ -89,6 +94,34 @@ const errorMessageMap: Record<string, string> = {
unknown_error: '发生未知错误', unknown_error: '发生未知错误',
} }
export function isAiPointsInsufficient(error: unknown): boolean {
return error instanceof ApiClientError && error.message === 'ai_points_insufficient'
}
let aiPointsModalVisible = false
export function showAiPointsInsufficientModal(): void {
if (aiPointsModalVisible) {
return
}
aiPointsModalVisible = true
Modal.confirm({
title: 'AI 点数不足',
content: '当前账号的 AI 点数余额已耗尽,无法继续生成内容。请升级套餐或等待下月点数重置。',
okText: '查看点数明细',
cancelText: '我知道了',
centered: true,
okButtonProps: { type: 'primary' },
onOk: () => {
void router.push('/account/ai-points')
},
afterClose: () => {
aiPointsModalVisible = false
},
})
}
export function isHandledAuthError(error: unknown): boolean { export function isHandledAuthError(error: unknown): boolean {
return ( return (
error instanceof ApiClientError && error instanceof ApiClientError &&
@@ -104,6 +137,9 @@ export function formatError(error: unknown): string {
if (error instanceof ApiClientError) { if (error instanceof ApiClientError) {
const message = errorMessageMap[error.message] ?? error.message.replaceAll('_', ' ') const message = errorMessageMap[error.message] ?? error.message.replaceAll('_', ' ')
if (isAiPointsInsufficient(error)) {
return message
}
return error.detail ? `${message}: ${error.detail}` : message return error.detail ? `${message}: ${error.detail}` : message
} }