From bf7594ccd807e68dde804cef8721fcf23e65103c Mon Sep 17 00:00:00 2001 From: liangxu Date: Tue, 28 Apr 2026 11:34:07 +0800 Subject: [PATCH] feat(admin-web): expose AI point balance and usage ledger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaces the new AI point quota in the workspace shell and adds a Personal Center → AI Point Usage page that shows balance, base-char rule, and the paginated reservation/refund history. Updates shared types for the QuotaSummary and ledger payloads. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/admin-web/src/i18n/messages/en-US.ts | 39 ++ apps/admin-web/src/i18n/messages/zh-CN.ts | 39 ++ apps/admin-web/src/layouts/AppShell.vue | 24 +- apps/admin-web/src/lib/api.ts | 4 + apps/admin-web/src/router/index.ts | 10 + apps/admin-web/src/views/AIPointUsageView.vue | 394 ++++++++++++++++++ packages/shared-types/src/index.ts | 30 ++ 7 files changed, 536 insertions(+), 4 deletions(-) create mode 100644 apps/admin-web/src/views/AIPointUsageView.vue diff --git a/apps/admin-web/src/i18n/messages/en-US.ts b/apps/admin-web/src/i18n/messages/en-US.ts index 7decf6d..a098283 100644 --- a/apps/admin-web/src/i18n/messages/en-US.ts +++ b/apps/admin-web/src/i18n/messages/en-US.ts @@ -86,6 +86,8 @@ const enUS = { kolManage: "Prompt Management", kolDashboard: "Dashboard", kolProfile: "Profile", + personalCenter: "Personal Center", + aiPointUsage: "AI Point Usage", }, auth: { welcomeBack: "Welcome back", @@ -117,10 +119,31 @@ const enUS = { quotaTitle: "Plan & Quota", quotaStatus: "Healthy", remainingQuota: "Article quota", + aiPoints: "AI points", resetAt: "Reset at", userFallback: "Admin", logout: "Logout", planFallback: "Free Plan", + aiUsageTitle: "AI Point Usage", + aiUsageRecent: "Recent Usage", + aiUsageEmpty: "No AI point usage yet", + aiUsageBase: "Tiered every {base} chars", + aiUsageRule: "{chars} chars / base {base}", + aiUsagePendingHelp: "Points have been reserved. They will be confirmed once the AI task completes; if generation fails, the points are returned automatically.", + aiUsageTypes: { + article_selection_optimize: "Article Selection Optimize", + template_analyze: "AI Analysis", + template_title_generate: "AI Title Generation", + template_outline_generate: "AI Outline Generation", + kol_prompt_generate: "KOL Prompt Generation", + kol_prompt_optimize: "KOL Prompt Optimize", + }, + aiUsageStatus: { + pending: "Processing", + completed: "Charged", + refunded: "Refunded", + failed: "Failed", + }, }, route: { login: { @@ -183,6 +206,22 @@ const enUS = { title: "Image Management", description: "Manage image assets, including folders, reference analysis, and cover library usage.", }, + aiPoints: { + title: "AI Point Usage", + description: "Review AI point balance, cycle rules, and recent usage records.", + }, + }, + aiPoints: { + balance: "Available Points", + baseChars: "Base Characters", + ledgerTitle: "Usage Ledger", + ledgerLimit: "Latest {limit} records", + ledgerTotal: "{total} records", + table: { + usageType: "Usage", + points: "Points", + requestChars: "Metered Chars", + }, }, kol: { marketplace: { diff --git a/apps/admin-web/src/i18n/messages/zh-CN.ts b/apps/admin-web/src/i18n/messages/zh-CN.ts index 228acf4..6720bb2 100644 --- a/apps/admin-web/src/i18n/messages/zh-CN.ts +++ b/apps/admin-web/src/i18n/messages/zh-CN.ts @@ -86,6 +86,8 @@ const zhCN = { kolManage: "提示词管理", kolDashboard: "数据看板", kolProfile: "个人主页", + personalCenter: "个人中心", + aiPointUsage: "AI 点数明细", }, auth: { welcomeBack: "欢迎回来", @@ -117,10 +119,31 @@ const zhCN = { quotaTitle: "套餐与额度", quotaStatus: "状态正常", remainingQuota: "生成文章", + aiPoints: "AI 点数", resetAt: "重置时间", userFallback: "管理员", logout: "退出", planFallback: "免费版", + aiUsageTitle: "AI 点数明细", + aiUsageRecent: "最近消耗", + aiUsageEmpty: "暂无 AI 点数流水", + aiUsageBase: "每 {base} 字进一档", + aiUsageRule: "{chars} 字 / 基准 {base}", + aiUsagePendingHelp: "点数已先行占用,AI 任务完成后会确认为扣除;如果生成失败,系统会自动退回点数。", + aiUsageTypes: { + article_selection_optimize: "文章片段优化", + template_analyze: "AI 分析", + template_title_generate: "AI 生成标题", + template_outline_generate: "AI 生成大纲", + kol_prompt_generate: "KOL 模版生成", + kol_prompt_optimize: "KOL 模版优化", + }, + aiUsageStatus: { + pending: "处理中", + completed: "已扣除", + refunded: "已退回", + failed: "失败", + }, }, route: { login: { @@ -183,6 +206,22 @@ const zhCN = { title: "图片管理", description: "管理图片素材库,支持分组管理、引用分析与封面图库引用。", }, + aiPoints: { + title: "AI 点数明细", + description: "查看当前套餐 AI 点数余额、周期规则和最近消耗流水。", + }, + }, + aiPoints: { + balance: "可用点数", + baseChars: "基准字数", + ledgerTitle: "消耗流水", + ledgerLimit: "最近 {limit} 条记录", + ledgerTotal: "共 {total} 条记录", + table: { + usageType: "使用场景", + points: "点数", + requestChars: "计费字数", + }, }, kol: { marketplace: { diff --git a/apps/admin-web/src/layouts/AppShell.vue b/apps/admin-web/src/layouts/AppShell.vue index cec90c0..54590b7 100644 --- a/apps/admin-web/src/layouts/AppShell.vue +++ b/apps/admin-web/src/layouts/AppShell.vue @@ -3,12 +3,12 @@ import { AppstoreOutlined, BookOutlined, GlobalOutlined, + HistoryOutlined, RadarChartOutlined, SearchOutlined, PictureOutlined, DownOutlined, LogoutOutlined, - TranslationOutlined, } from "@ant-design/icons-vue"; import { useQuery } from "@tanstack/vue-query"; import { computed, type Component } from "vue"; @@ -30,6 +30,7 @@ const quotaQuery = useQuery({ }); const userInitial = computed(() => authStore.user?.name?.slice(0, 1).toUpperCase() ?? "A"); +const quotaSummary = computed(() => quotaQuery.data.value); const selectedKeys = computed(() => { if (route.meta.navKey === null) { return []; @@ -125,6 +126,13 @@ const navSections = computed(() => { { key: "/kol/marketplace", label: t("nav.kolMarketplace") }, ], }, + { + key: "personalCenter", + title: t("nav.personalCenter"), + items: [ + { key: "/account/ai-points", label: t("nav.aiPointUsage"), icon: HistoryOutlined }, + ], + }, ]; if (authStore.isActiveKol) { @@ -279,10 +287,13 @@ async function handleLogout(): Promise {
- {{ quotaQuery.data.value?.plan_name || t("shell.planFallback") }} + {{ quotaSummary?.plan_name || t("shell.planFallback") }} - {{ t("shell.remainingQuota") }}: {{ quotaQuery.data.value?.balance ?? '--' }} - / {{ quotaQuery.data.value?.total_quota ?? '--' }} + {{ t("shell.remainingQuota") }}: {{ quotaSummary?.balance ?? '--' }} + / {{ quotaSummary?.total_quota ?? '--' }} + · + {{ t("shell.aiPoints") }}: {{ quotaSummary?.ai_points_balance ?? '--' }} + / {{ quotaSummary?.ai_points_total ?? '--' }}
@@ -485,6 +496,11 @@ async function handleLogout(): Promise { display: inline-block; } +.quota-pill-divider { + color: #bfbfbf; + margin: 0 6px; +} + .user-dropdown-trigger { display: flex; align-items: center; diff --git a/apps/admin-web/src/lib/api.ts b/apps/admin-web/src/lib/api.ts index ff68dca..b74d613 100644 --- a/apps/admin-web/src/lib/api.ts +++ b/apps/admin-web/src/lib/api.ts @@ -7,6 +7,7 @@ import type { ArticleListResponse, CreateArticleRequest, ArticleVersion, + AIPointUsageListResponse, GenerateImitationRequest, GenerateImitationResponse, AuthTokens, @@ -293,6 +294,9 @@ export const workspaceApi = { quotaSummary() { return apiClient.get("/api/tenant/workspace/quota-summary"); }, + aiPointUsage(params?: { page?: number; page_size?: number; limit?: number; offset?: number }) { + return apiClient.get("/api/tenant/workspace/ai-point-usage", { params }); + }, templateCards() { return apiClient.get("/api/tenant/workspace/template-cards"); }, diff --git a/apps/admin-web/src/router/index.ts b/apps/admin-web/src/router/index.ts index 7693741..2c6817c 100644 --- a/apps/admin-web/src/router/index.ts +++ b/apps/admin-web/src/router/index.ts @@ -231,6 +231,16 @@ const router = createRouter({ navKey: "/articles/templates", }, }, + { + path: "account/ai-points", + name: "account-ai-points", + component: () => import("@/views/AIPointUsageView.vue"), + meta: { + titleKey: "route.aiPoints.title", + descriptionKey: "route.aiPoints.description", + navKey: "/account/ai-points", + }, + }, ], }, ], diff --git a/apps/admin-web/src/views/AIPointUsageView.vue b/apps/admin-web/src/views/AIPointUsageView.vue new file mode 100644 index 0000000..d33e886 --- /dev/null +++ b/apps/admin-web/src/views/AIPointUsageView.vue @@ -0,0 +1,394 @@ + + + + + diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts index 41c8786..abc5e37 100644 --- a/packages/shared-types/src/index.ts +++ b/packages/shared-types/src/index.ts @@ -48,6 +48,8 @@ export interface MembershipInfo { end_at: string | null; article_generation_limit: number; article_quota_cycle: string; + ai_points_monthly: number; + ai_point_base_chars: number; company_limit: number; image_storage_bytes: number; contact_admin_on_expiry: boolean; @@ -353,6 +355,34 @@ export interface QuotaSummary { used_quota: number; balance: number; reset_at: string | null; + ai_points_total: number; + ai_points_used: number; + ai_points_balance: number; + ai_point_base_chars: number; + ai_points_reset_at: string | null; +} + +export interface AIPointUsageLog { + id: number; + usage_type: string; + resource_type: string | null; + resource_id: number | null; + resource_uid: string | null; + request_chars: number; + base_chars: number; + points: number; + status: string; + model: string | null; + error_message: string | null; + completed_at: string | null; + created_at: string; +} + +export interface AIPointUsageListResponse { + items: AIPointUsageLog[]; + total: number; + page: number; + page_size: number; } export interface TemplateCard {