فهرست منبع

feat:页面菜单模块调整,国际化调整

韩洋 3 هفته پیش
والد
کامیت
6070bd7109
34فایلهای تغییر یافته به همراه845 افزوده شده و 486 حذف شده
  1. 141 71
      default/docs/permission-identifiers.json
  2. 2 1
      default/rsbuild.config.ts
  3. 1 1
      default/src/components/layout/components/nav-group.tsx
  4. 18 14
      default/src/components/layout/components/section-page-layout.tsx
  5. 4 2
      default/src/components/layout/components/system-brand.tsx
  6. 1 7
      default/src/components/layout/config/system-settings.config.ts
  7. 4 1
      default/src/components/ui/dropdown-menu.tsx
  8. 1 1
      default/src/features/channels/index.tsx
  9. 1 2
      default/src/features/dashboard/index.tsx
  10. 1 0
      default/src/features/groups/components/groups-mutate-drawer.tsx
  11. 3 11
      default/src/features/groups/components/groups-primary-buttons.tsx
  12. 21 16
      default/src/features/groups/components/groups-table.tsx
  13. 1 9
      default/src/features/groups/index.tsx
  14. 141 0
      default/src/features/model-pricing/index.tsx
  15. 1 46
      default/src/features/models/components/data-table-row-actions.tsx
  16. 8 78
      default/src/features/models/components/drawers/model-mutate-drawer.tsx
  17. 0 29
      default/src/features/models/components/models-columns.tsx
  18. 1 102
      default/src/features/models/components/models-primary-buttons.tsx
  19. 2 20
      default/src/features/models/components/models-table.tsx
  20. 1 1
      default/src/features/pricing/index.tsx
  21. 244 0
      default/src/features/quota/index.tsx
  22. 1 1
      default/src/features/roles/index.tsx
  23. 53 15
      default/src/features/system-info/index.tsx
  24. 1 1
      default/src/features/tenants/index.tsx
  25. 1 1
      default/src/features/usage-logs/index.tsx
  26. 1 1
      default/src/features/usage-logs/section-registry.tsx
  27. 48 33
      default/src/hooks/use-sidebar-data.ts
  28. 11 0
      default/src/i18n/locales/en.json
  29. 17 6
      default/src/i18n/locales/zh.json
  30. 5 2
      default/src/i18n/static-keys.ts
  31. 43 0
      default/src/routeTree.gen.ts
  32. 25 0
      default/src/routes/_authenticated/model-pricing/index.tsx
  33. 39 0
      default/src/routes/_authenticated/quota/index.tsx
  34. 3 14
      default/src/routes/_authenticated/system-info/index.tsx

+ 141 - 71
default/docs/permission-identifiers.json

@@ -1,6 +1,6 @@
 [
   {
-    "module_name": "总览",
+    "module_name": "数据看板",
     "module_code": "route.dashboard.view",
     "module_label": "Dashboard",
     "is_only_open_for_root": 0,
@@ -14,10 +14,25 @@
       }
     ]
   },
+  {
+    "module_name": "配额管理",
+    "module_code": "route.quota.view",
+    "module_label": "Quota Management",
+    "is_only_open_for_root": 0,
+    "for_role_template_type": 10,
+    "children": [
+      {
+        "perm_name": "查看",
+        "front_perm_code": "quota.view",
+        "api_perm_code": "",
+        "perm_label": "View Quota Management"
+      }
+    ]
+  },
   {
     "module_name": "渠道管理",
     "module_code": "route.channel.view",
-    "module_label": "Channel Management",
+    "module_label": "Provider Channels",
     "is_only_open_for_root": 1,
     "for_role_template_type": 20,
     "children": [
@@ -25,31 +40,31 @@
         "perm_name": "查看",
         "front_perm_code": "channel.view",
         "api_perm_code": "",
-        "perm_label": "View Channel"
+        "perm_label": "View Provider Channels"
       },
       {
         "perm_name": "新增",
         "front_perm_code": "channel.create",
         "api_perm_code": "",
-        "perm_label": "Create Channel"
+        "perm_label": "Create Provider Channel"
       },
       {
         "perm_name": "编辑",
         "front_perm_code": "channel.update",
         "api_perm_code": "",
-        "perm_label": "Update Channel"
+        "perm_label": "Update Provider Channel"
       },
       {
         "perm_name": "删除",
         "front_perm_code": "channel.delete",
         "api_perm_code": "",
-        "perm_label": "Delete Channel"
+        "perm_label": "Delete Provider Channel"
       },
       {
         "perm_name": "禁用",
         "front_perm_code": "channel.disable",
         "api_perm_code": "",
-        "perm_label": "Disable Channel"
+        "perm_label": "Disable Provider Channel"
       }
     ]
   },
@@ -64,26 +79,16 @@
         "perm_name": "查看",
         "front_perm_code": "models.view",
         "api_perm_code": "",
-        "perm_label": "View Model"
-      },
-      {
-        "perm_name": "新增",
-        "front_perm_code": "models.create",
-        "api_perm_code": "",
-        "perm_label": "Create Model"
+        "perm_label": "View Model Management"
       },
+ 
       {
         "perm_name": "编辑",
         "front_perm_code": "models.update",
         "api_perm_code": "",
         "perm_label": "Update Model"
       },
-      {
-        "perm_name": "删除",
-        "front_perm_code": "models.delete",
-        "api_perm_code": "",
-        "perm_label": "Delete Model"
-      },
+
       {
         "perm_name": "禁用",
         "front_perm_code": "models.disable",
@@ -93,81 +98,74 @@
     ]
   },
   {
-    "module_name": "平台管理",
-    "module_code": "route.user.view",
-    "module_label": "User Management",
+    "module_name": "模型定价",
+    "module_code": "route.model-pricing.view",
+    "module_label": "Model Pricing",
     "is_only_open_for_root": 0,
     "for_role_template_type": 20,
     "children": [
       {
         "perm_name": "查看",
-        "front_perm_code": "user.view",
-        "api_perm_code": "",
-        "perm_label": "View User"
+        "front_perm_code": "model-pricing.view",
+        "api_perm_code": "",
+        "perm_label": "View Model Pricing"
+      },{
+        "perm_name":"新增",
+        "front_perm_code":"model-pricing.add",
+        "api_perm_code":"",
+        "perm_label":"Add Model Pricing"
       },
       {
-        "perm_name": "新增",
-        "front_perm_code": "user.create",
-        "api_perm_code": "",
-        "perm_label": "Create User"
+        "perm_name":"编辑",
+        "front_perm_code":"model-pricing.edit",
+        "api_perm_code":"",
+        "perm_label":"Edit Model Pricing"
       },
       {
-        "perm_name": "编辑",
-        "front_perm_code": "user.update",
-        "api_perm_code": "",
-        "perm_label": "Update User"
-      },
-      {
-        "perm_name": "删除",
-        "front_perm_code": "user.delete",
-        "api_perm_code": "",
-        "perm_label": "Delete User"
-      },
-      {
-        "perm_name": "禁用",
-        "front_perm_code": "user.disable",
-        "api_perm_code": "",
-        "perm_label": "Disable User"
+        "perm_name":"删除",
+        "front_perm_code":"model-pricing.delete",
+        "api_perm_code":"",
+        "perm_label":"Delete Model Pricing"
       }
     ]
   },
   {
-    "module_name": "平台角色",
-    "module_code": "route.role.view",
-    "module_label": "Role Management",
+    "module_name": "分组管理",
+    "module_code": "route.groups.view",
+    "module_label": "Group Management",
     "is_only_open_for_root": 0,
-    "for_role_template_type": 30,
+    "for_role_template_type": 20,
     "children": [
       {
         "perm_name": "查看",
-        "front_perm_code": "role.view",
+        "front_perm_code": "groups.view",
         "api_perm_code": "",
-        "perm_label": "View Role"
+        "perm_label": "View Group Management"
       },
       {
         "perm_name": "新增",
-        "front_perm_code": "role.create",
+        "front_perm_code": "groups.create",
         "api_perm_code": "",
-        "perm_label": "Create Role"
+        "perm_label": "Create Group"
       },
       {
         "perm_name": "编辑",
-        "front_perm_code": "role.update",
+        "front_perm_code": "groups.update",
         "api_perm_code": "",
-        "perm_label": "Update Role"
+        "perm_label": "Update Group"
       },
       {
         "perm_name": "删除",
-        "front_perm_code": "role.delete",
+        "front_perm_code": "groups.delete",
         "api_perm_code": "",
-        "perm_label": "Delete Role"
+        "perm_label": "Delete Group"
       }
     ]
   },
   {
-    "module_name": "租户管理",
+    "module_name": "企业列表",
     "module_code": "route.tenant.view",
-    "module_label": "Tenant Management",
+    "module_label": "Tenants",
     "is_only_open_for_root": 0,
     "for_role_template_type": 20,
     "children": [
@@ -175,7 +173,7 @@
         "perm_name": "查看",
         "front_perm_code": "tenant.view",
         "api_perm_code": "",
-        "perm_label": "View Tenant"
+        "perm_label": "View Tenants"
       },
       {
         "perm_name": "新增",
@@ -198,7 +196,7 @@
     ]
   },
   {
-    "module_name": "个人信息",
+    "module_name": "个人资料",
     "module_code": "route.profile.view",
     "module_label": "Profile",
     "is_only_open_for_root": 0,
@@ -213,7 +211,7 @@
     ]
   },
   {
-    "module_name": "API 密钥",
+    "module_name": "API密钥",
     "module_code": "route.keys.view",
     "module_label": "API Keys",
     "is_only_open_for_root": 0,
@@ -254,7 +252,7 @@
   {
     "module_name": "模型列表",
     "module_code": "route.pricing.view",
-    "module_label": "Model List",
+    "module_label": "Models",
     "is_only_open_for_root": 0,
     "for_role_template_type": 10,
     "children": [
@@ -262,14 +260,14 @@
         "perm_name": "查看",
         "front_perm_code": "pricing.view",
         "api_perm_code": "",
-        "perm_label": "View pricing List"
+        "perm_label": "View Models"
       }
     ]
   },
   {
     "module_name": "调用日志",
     "module_code": "route.usage-logs.view",
-    "module_label": "Usage Logs",
+    "module_label": "API Logs",
     "is_only_open_for_root": 0,
     "for_role_template_type": 10,
     "children": [
@@ -277,13 +275,13 @@
         "perm_name": "查看(仅自己)",
         "front_perm_code": "usage-logs.view",
         "api_perm_code": "",
-        "perm_label": "View Usage Logs"
+        "perm_label": "View API Logs"
       },
       {
         "perm_name": "查看(全部数据)",
         "front_perm_code": "usage-logs.all",
         "api_perm_code": "",
-        "perm_label": "View All Usage Logs"
+        "perm_label": "View All API Logs"
       }
     ]
   },
@@ -309,9 +307,81 @@
     ]
   },
   {
-    "module_name": "系统设置",
+    "module_name": "用户管理",
+    "module_code": "route.user.view",
+    "module_label": "Users",
+    "is_only_open_for_root": 0,
+    "for_role_template_type": 20,
+    "children": [
+      {
+        "perm_name": "查看",
+        "front_perm_code": "user.view",
+        "api_perm_code": "",
+        "perm_label": "View Users"
+      },
+      {
+        "perm_name": "新增",
+        "front_perm_code": "user.create",
+        "api_perm_code": "",
+        "perm_label": "Create User"
+      },
+      {
+        "perm_name": "编辑",
+        "front_perm_code": "user.update",
+        "api_perm_code": "",
+        "perm_label": "Update User"
+      },
+      {
+        "perm_name": "删除",
+        "front_perm_code": "user.delete",
+        "api_perm_code": "",
+        "perm_label": "Delete User"
+      },
+      {
+        "perm_name": "禁用",
+        "front_perm_code": "user.disable",
+        "api_perm_code": "",
+        "perm_label": "Disable User"
+      }
+    ]
+  },
+  {
+    "module_name": "角色管理",
+    "module_code": "route.role.view",
+    "module_label": "Roles & Permissions",
+    "is_only_open_for_root": 0,
+    "for_role_template_type": 30,
+    "children": [
+      {
+        "perm_name": "查看",
+        "front_perm_code": "role.view",
+        "api_perm_code": "",
+        "perm_label": "View Roles & Permissions"
+      },
+      {
+        "perm_name": "新增",
+        "front_perm_code": "role.create",
+        "api_perm_code": "",
+        "perm_label": "Create Role"
+      },
+      {
+        "perm_name": "编辑",
+        "front_perm_code": "role.update",
+        "api_perm_code": "",
+        "perm_label": "Update Role"
+      },
+      {
+        "perm_name": "删除",
+        "front_perm_code": "role.delete",
+        "api_perm_code": "",
+        "perm_label": "Delete Role"
+      }
+    ]
+  },
+  {
+    "module_name": "系统信息",
     "module_code": "route.settings.view",
-    "module_label": "System Settings",
+    "module_label": "System Information",
     "is_only_open_for_root": 1,
     "for_role_template_type": 20,
     "children": [
@@ -319,7 +389,7 @@
         "perm_name": "查看",
         "front_perm_code": "settings.view",
         "api_perm_code": "",
-        "perm_label": "View System Settings"
+        "perm_label": "View System Information"
       }
     ]
   }

+ 2 - 1
default/rsbuild.config.ts

@@ -13,7 +13,8 @@ export default defineConfig(({ envMode }) => {
   const serverUrl =
     process.env.VITE_REACT_APP_SERVER_URL ||
     env.rawPublicVars.VITE_REACT_APP_SERVER_URL ||
-    'http://localhost:3000'
+    // 'http://192.168.100.26:3000',
+    'http://47.117.92.63:8081'
 
   const isProd = envMode === 'production'
   const devProxy = Object.fromEntries(

+ 1 - 1
default/src/components/layout/components/nav-group.tsx

@@ -66,7 +66,7 @@ export function NavGroup({ title, items }: NavGroupProps) {
 
   return (
     <SidebarGroup className='px-2 py-1'>
-      <SidebarGroupLabel className='text-muted-foreground/70 px-2 text-[11px] font-medium tracking-wider uppercase'>
+      <SidebarGroupLabel className='text-muted-foreground/70 px-2 text-[11px] font-medium tracking-wider'>
         {title}
       </SidebarGroupLabel>
       <SidebarMenu>

+ 18 - 14
default/src/components/layout/components/section-page-layout.tsx

@@ -79,23 +79,27 @@ export function SectionPageLayout(props: SectionPageLayoutProps) {
   return (
     <PageFooterProvider container={footerContainer}>
       <Main>
-        <div className='shrink-0 px-3 pt-3 pb-2.5 sm:px-4 sm:pt-5 sm:pb-3'>
-          {breadcrumb != null && (
+        {breadcrumb != null && (
+          <div className='shrink-0 px-3 pt-3 sm:px-4 sm:pt-5'>
             <div className='mb-2 sm:mb-3'>{breadcrumb}</div>
-          )}
-          <div className='flex flex-wrap items-center justify-between gap-x-3 gap-y-2 sm:gap-x-4'>
-            <div className='min-w-0 flex-1'>
-              <h2 className='truncate text-base font-bold tracking-tight sm:text-lg'>
-                {title}
-              </h2>
-            </div>
-            {actions != null && (
-              <div className='flex shrink-0 flex-wrap items-center justify-end gap-2 sm:gap-x-4'>
-                {actions}
+          </div>
+        )}
+        {(title != null || actions != null) && (
+          <div className='shrink-0 px-3 pt-3 pb-2.5 sm:px-4 sm:pt-5 sm:pb-3'>
+            <div className='flex flex-wrap items-center justify-between gap-x-3 gap-y-2 sm:gap-x-4'>
+              <div className='min-w-0 flex-1'>
+                <h2 className='truncate text-base font-bold tracking-tight sm:text-lg'>
+                  {title}
+                </h2>
               </div>
-            )}
+              {actions != null && (
+                <div className='flex shrink-0 flex-wrap items-center justify-end gap-2 sm:gap-x-4'>
+                  {actions}
+                </div>
+              )}
+            </div>
           </div>
-        </div>
+        )}
 
         <div
           className={

+ 4 - 2
default/src/components/layout/components/system-brand.tsx

@@ -46,13 +46,15 @@ type SystemBrandProps = {
  * - sidebar: stacked card in the sidebar header (display only)
  */
 export function SystemBrand(props: SystemBrandProps) {
-  const { t } = useTranslation()
+  const { t, i18n } = useTranslation()
   const { status } = useStatus()
   const { logo } = useSystemConfig()
 
   const variant = props.variant ?? 'sidebar'
   const name =
-    status?.system_name || props.defaultName || '灵慧云智Token管理平台'
+    i18n.language === 'en'
+      ? 'Token Operations Center'
+      : status?.system_name || props.defaultName || '灵慧云智Token管理平台'
   const version =
     status?.version || props.defaultVersion || t('Unknown version')
 

+ 1 - 7
default/src/components/layout/config/system-settings.config.ts

@@ -17,9 +17,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact support@quantumnous.com
 */
 import type { TFunction } from 'i18next'
-import { CreditCard, Settings } from 'lucide-react'
+import { Settings } from 'lucide-react'
 
-import { getBillingSectionNavItems } from '@/features/system-settings/billing/section-registry.tsx'
 import { getSiteSectionNavItems } from '@/features/system-settings/site/section-registry.tsx'
 
 import type { NavGroup, SidebarView } from '../types'
@@ -42,11 +41,6 @@ function getSystemSettingsNavGroups(t: TFunction): NavGroup[] {
           icon: Settings,
           items: getSiteSectionNavItems(t),
         },
-        {
-          title: t('Billing & Payment'),
-          icon: CreditCard,
-          items: getBillingSectionNavItems(t),
-        },
       ],
     },
   ]

+ 4 - 1
default/src/components/ui/dropdown-menu.tsx

@@ -17,7 +17,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact support@quantumnous.com
 */
 import { Menu as MenuPrimitive } from '@base-ui/react/menu'
-import { ArrowRight01Icon, Tick02Icon } from '@hugeicons/core-free-icons'
+import {
+  ArrowRight01Icon,
+  Tick02Icon,
+} from '@hugeicons/core-free-icons'
 import { HugeiconsIcon } from '@hugeicons/react'
 import * as React from 'react'
 

+ 1 - 1
default/src/features/channels/index.tsx

@@ -33,7 +33,7 @@ export function Channels() {
       <SectionPageLayout fixedContent>
         <SectionPageLayout.Title>
           <span className='flex min-w-0 items-center gap-2'>
-            <span className='truncate'>{t('Channels')}</span>
+            <span className='truncate'>{t('Provider Channels')}</span>
           </span>
         </SectionPageLayout.Title>
         <SectionPageLayout.Actions>

+ 1 - 2
default/src/features/dashboard/index.tsx

@@ -37,7 +37,6 @@ import { useAuthStore } from '@/stores/auth-store'
 
 import { ModelsChartPreferences } from './components/models/models-chart-preferences'
 import { ModelsFilter } from './components/models/models-filter-dialog'
-// import { OverviewDashboard } from './components/overview/overview-dashboard'
 import { DEFAULT_TIME_GRANULARITY } from './constants'
 import {
   buildDefaultDashboardFilters,
@@ -283,7 +282,7 @@ export function Dashboard() {
 
   return (
     <SectionPageLayout>
-      <SectionPageLayout.Title>{t(meta.titleKey)}</SectionPageLayout.Title>
+      <SectionPageLayout.Title>{t('Dashboard')}</SectionPageLayout.Title>
       <SectionPageLayout.Content>
         <div className='border-border/70 bg-card/62 relative space-y-4 overflow-hidden rounded-3xl border p-3 shadow-[0_18px_46px_color-mix(in_oklch,var(--primary)_9%,transparent)] ring-1 ring-white/70 backdrop-blur-sm sm:space-y-5 sm:p-4 dark:ring-white/10'>
           <div className='bg-primary/12 pointer-events-none absolute -top-24 right-8 size-52 rounded-full blur-3xl' />

+ 1 - 0
default/src/features/groups/components/groups-mutate-drawer.tsx

@@ -77,6 +77,7 @@ export function GroupsMutateDrawer({
   const { t } = useTranslation()
   const isUpdate = !!currentRow
   const { triggerRefresh } = useGroups()
+  const queryClient = useQueryClient()
   const [isSubmitting, setIsSubmitting] = useState(false)
 
   const form = useForm<GroupFormValues>({

+ 3 - 11
default/src/features/groups/components/groups-primary-buttons.tsx

@@ -18,22 +18,15 @@ For commercial licensing, please contact support@quantumnous.com
 */
 
 import { Plus } from 'lucide-react'
-import type { Table } from '@tanstack/react-table'
 import { useTranslation } from 'react-i18next'
 
-import { DataTableViewOptions } from '@/components/data-table'
 import { Button } from '@/components/ui/button'
 import { PERMISSION_CODES, hasPermissionCode } from '@/lib/admin-permissions'
 import { useAuthStore } from '@/stores/auth-store'
 
-import type { Group } from '../types'
 import { useGroups } from './groups-provider'
 
-type GroupsPrimaryButtonsProps = {
-  table: Table<Group>
-}
-
-export function GroupsPrimaryButtons(props: GroupsPrimaryButtonsProps) {
+export function GroupsPrimaryButtons() {
   const { t } = useTranslation()
   const { setOpen, setCurrentRow } = useGroups()
   const currentUser = useAuthStore((s) => s.auth.user)
@@ -48,14 +41,13 @@ export function GroupsPrimaryButtons(props: GroupsPrimaryButtonsProps) {
   }
 
   return (
-    <div className='flex gap-2'>
-      <DataTableViewOptions table={props.table} />
+    <>
       {canCreateGroup && (
         <Button size='sm' onClick={handleCreate}>
           <Plus className='h-4 w-4' />
           {t('Create Group')}
         </Button>
       )}
-    </div>
+    </>
   )
 }

+ 21 - 16
default/src/features/groups/components/groups-table.tsx

@@ -18,20 +18,25 @@ For commercial licensing, please contact support@quantumnous.com
 */
 
 import { useQuery } from '@tanstack/react-query'
+import { getRouteApi } from '@tanstack/react-router'
 import { useTranslation } from 'react-i18next'
 import { toast } from 'sonner'
 
 import {
   DataTablePage,
+  DataTableViewOptions,
   useDataTable,
 } from '@/components/data-table'
 import { useMediaQuery } from '@/hooks'
 import { useTableUrlState } from '@/hooks/use-table-url-state'
 
 import { getGroups } from '../../groups/api'
+import { GroupsPrimaryButtons } from './groups-primary-buttons'
 import { useGroupsColumns } from './groups-columns'
 import { useGroups } from './groups-provider'
 
+const route = getRouteApi('/_authenticated/groups/')
+
 export function GroupsTable() {
   const { t } = useTranslation()
   const columns = useGroupsColumns()
@@ -39,14 +44,16 @@ export function GroupsTable() {
   const isMobile = useMediaQuery('(max-width: 640px)')
 
   const {
-    globalFilter,
-    onGlobalFilterChange,
     columnFilters,
     onColumnFiltersChange,
+    globalFilter,
+    onGlobalFilterChange,
     pagination,
     onPaginationChange,
     ensurePageInRange,
   } = useTableUrlState({
+    search: route.useSearch(),
+    navigate: route.useNavigate(),
     pagination: { defaultPage: 1, defaultPageSize: isMobile ? 10 : 20 },
     globalFilter: { enabled: true, key: 'filter' },
   })
@@ -56,7 +63,6 @@ export function GroupsTable() {
       'groups',
       pagination.pageIndex + 1,
       pagination.pageSize,
-      globalFilter,
       refreshTrigger,
     ],
     queryFn: async () => {
@@ -94,16 +100,6 @@ export function GroupsTable() {
     manualPagination: true,
     manualFiltering: true,
     totalCount: data?.total || 0,
-    globalFilterFn: (row, _columnId, filterValue) => {
-      const searchValue = String(filterValue).toLowerCase()
-      const fields = [
-        row.original.group_name,
-        row.original.group_desc || '',
-      ]
-      return fields.some((field) =>
-        String(field || '').toLowerCase().includes(searchValue)
-      )
-    },
     ensurePageInRange,
   })
 
@@ -117,9 +113,18 @@ export function GroupsTable() {
       emptyDescription={t('No groups available. Try adjusting your search.')}
       skeletonKeyPrefix='groups-skeleton'
       applyHeaderSize
-      toolbarProps={{
-        searchPlaceholder: t('Filter by group name or description...'),
-      }}
+      toolbarProps={null}
+      toolbar={
+        <div className='flex flex-wrap items-center justify-between gap-x-3 gap-y-2 sm:gap-x-4'>
+          <h2 className='truncate text-base font-bold tracking-tight sm:text-lg'>
+            {t('Group Management')}
+          </h2>
+          <div className='flex shrink-0 flex-wrap items-center justify-end gap-2 sm:gap-x-4'>
+            <GroupsPrimaryButtons />
+            <DataTableViewOptions table={table} />
+          </div>
+        </div>
+      }
     />
   )
 }

+ 1 - 9
default/src/features/groups/index.tsx

@@ -17,27 +17,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact support@quantumnous.com
 */
 
-import { useTranslation } from 'react-i18next'
-
 import { SectionPageLayout } from '@/components/layout'
 
 import { GroupsDeleteDialog } from './components/groups-delete-dialog'
 import { GroupsMutateDrawer } from './components/groups-mutate-drawer'
-import { GroupsPrimaryButtons } from './components/groups-primary-buttons'
 import { GroupsProvider, useGroups } from './components/groups-provider'
 import { GroupsTable } from './components/groups-table'
 
 function GroupsContent() {
-  const { t } = useTranslation()
   const { open, setOpen, currentRow } = useGroups()
 
   return (
     <>
-      <SectionPageLayout fixedContent>
-        <SectionPageLayout.Title>{t('Group Management')}</SectionPageLayout.Title>
-        <SectionPageLayout.Actions>
-          <GroupsPrimaryButtons />
-        </SectionPageLayout.Actions>
+      <SectionPageLayout>
         <SectionPageLayout.Content>
           <GroupsTable />
         </SectionPageLayout.Content>

+ 141 - 0
default/src/features/model-pricing/index.tsx

@@ -0,0 +1,141 @@
+/*
+Copyright (C) 2023-2026 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+import { useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { SectionPageLayout } from '@/components/layout'
+
+import { SettingsPageProvider } from '@/features/system-settings/components/settings-page-context'
+import { useSystemOptions, getOptionValue } from '@/features/system-settings/hooks/use-system-options'
+import { getBillingSectionContent, getBillingSectionMeta, BILLING_DEFAULT_SECTION } from '@/features/system-settings/billing/section-registry'
+import type { BillingSettings } from '@/features/system-settings/types'
+
+const defaultBillingSettings: BillingSettings = {
+  QuotaForNewUser: 0,
+  PreConsumedQuota: 0,
+  QuotaForInviter: 0,
+  QuotaForInvitee: 0,
+  TopUpLink: '',
+  'general_setting.docs_link': '',
+  'quota_setting.enable_free_model_pre_consume': true,
+  QuotaPerUnit: 500000,
+  USDExchangeRate: 7,
+  'general_setting.quota_display_type': 'USD',
+  'general_setting.custom_currency_symbol': '¤',
+  'general_setting.custom_currency_exchange_rate': 1,
+  DisplayInCurrencyEnabled: true,
+  DisplayTokenStatEnabled: true,
+  ModelPrice: '',
+  ModelRatio: '',
+  CacheRatio: '',
+  CreateCacheRatio: '',
+  CompletionRatio: '',
+  ImageRatio: '',
+  AudioRatio: '',
+  AudioCompletionRatio: '',
+  ExposeRatioEnabled: false,
+  'billing_setting.billing_mode': '{}',
+  'billing_setting.billing_expr': '{}',
+  'tool_price_setting.prices': '{}',
+  TopupGroupRatio: '',
+  GroupRatio: '',
+  UserUsableGroups: '',
+  GroupGroupRatio: '',
+  AutoGroups: '',
+  DefaultUseAutoGroup: false,
+  'group_ratio_setting.group_special_usable_group': '{}',
+  PayAddress: '',
+  EpayId: '',
+  EpayKey: '',
+  Price: 7.3,
+  MinTopUp: 1,
+  CustomCallbackAddress: '',
+  PayMethods: '',
+  'payment_setting.amount_options': '',
+  'payment_setting.amount_discount': '',
+  'payment_setting.compliance_confirmed': false,
+  'payment_setting.compliance_terms_version': '',
+  'payment_setting.compliance_confirmed_at': 0,
+  'payment_setting.compliance_confirmed_by': 0,
+  'payment_setting.compliance_confirmed_ip': '',
+  StripeApiSecret: '',
+  StripeWebhookSecret: '',
+  StripePriceId: '',
+  StripeUnitPrice: 8.0,
+  StripeMinTopUp: 1,
+  StripePromotionCodesEnabled: false,
+  CreemApiKey: '',
+  CreemWebhookSecret: '',
+  CreemTestMode: false,
+  CreemProducts: '[]',
+  WaffoEnabled: false,
+  WaffoApiKey: '',
+  WaffoPrivateKey: '',
+  WaffoPublicCert: '',
+  WaffoSandboxPublicCert: '',
+  WaffoSandboxApiKey: '',
+  WaffoSandboxPrivateKey: '',
+  WaffoSandbox: false,
+  WaffoMerchantId: '',
+  WaffoCurrency: 'USD',
+  WaffoUnitPrice: 1,
+  WaffoMinTopUp: 1,
+  WaffoNotifyUrl: '',
+  WaffoReturnUrl: '',
+  WaffoPayMethods: '[]',
+  WaffoPancakeMerchantID: '',
+  WaffoPancakePrivateKey: '',
+  WaffoPancakeReturnURL: '',
+  WaffoPancakeStoreID: '',
+  WaffoPancakeProductID: '',
+  'checkin_setting.enabled': false,
+  'checkin_setting.min_quota': 1000,
+  'checkin_setting.max_quota': 10000,
+}
+
+export function ModelPricingPage() {
+  const { t } = useTranslation()
+  const { data, isLoading } = useSystemOptions()
+
+  const settings = useMemo(
+    () => getOptionValue(data?.data, defaultBillingSettings) as BillingSettings,
+    [data?.data]
+  )
+
+  const sectionMeta = getBillingSectionMeta(BILLING_DEFAULT_SECTION)
+
+  const content = getBillingSectionContent(BILLING_DEFAULT_SECTION, settings)
+
+  return (
+    <SectionPageLayout>
+      <SectionPageLayout.Title>{t(sectionMeta.titleKey)}</SectionPageLayout.Title>
+      <SectionPageLayout.Content>
+        <div className='flex h-full min-h-0 w-full flex-col gap-4'>
+          {isLoading ? (
+            <div className='text-muted-foreground flex min-h-40 items-center justify-center text-sm'>
+              {t('Loading settings...')}
+            </div>
+          ) : (
+            content
+          )}
+        </div>
+      </SectionPageLayout.Content>
+    </SectionPageLayout>
+  )
+}

+ 1 - 46
default/src/features/models/components/data-table-row-actions.tsx

@@ -18,17 +18,10 @@ For commercial licensing, please contact support@quantumnous.com
 */
 import { useQueryClient } from '@tanstack/react-query'
 import type { Row } from '@tanstack/react-table'
-import { Pencil, Power, PowerOff, Trash2 } from 'lucide-react'
-import { useState } from 'react'
+import { Pencil, Power, PowerOff } from 'lucide-react'
 import { useTranslation } from 'react-i18next'
 
-import { ConfirmDialog } from '@/components/confirm-dialog'
-import { DataTableRowActionMenu } from '@/components/data-table/core/row-action-menu'
 import { Button } from '@/components/ui/button'
-import {
-  DropdownMenuItem,
-  DropdownMenuShortcut,
-} from '@/components/ui/dropdown-menu'
 import {
   Tooltip,
   TooltipContent,
@@ -38,7 +31,6 @@ import { PERMISSION_CODES, hasPermissionCode } from '@/lib/admin-permissions'
 import { useAuthStore } from '@/stores/auth-store'
 
 import {
-  handleDeleteModel,
   handleToggleModelStatus,
   isModelEnabled,
 } from '../lib'
@@ -54,7 +46,6 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
   const model = row.original
   const { setOpen, setCurrentRow } = useModels()
   const queryClient = useQueryClient()
-  const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
   const currentUser = useAuthStore((s) => s.auth.user)
 
   const isEnabled = isModelEnabled(model)
@@ -62,10 +53,6 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
     currentUser,
     PERMISSION_CODES.MODELS_UPDATE
   )
-  const canDeleteModel = hasPermissionCode(
-    currentUser,
-    PERMISSION_CODES.MODELS_DELETE
-  )
   const canDisableModel = hasPermissionCode(
     currentUser,
     PERMISSION_CODES.MODELS_DISABLE
@@ -125,39 +112,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
         </Tooltip>
       )}
 
-      {canDeleteModel && (
-        <DataTableRowActionMenu ariaLabel={t('Open menu')}>
-          <DropdownMenuItem
-            onSelect={(e) => {
-              e.preventDefault()
-              setDeleteConfirmOpen(true)
-            }}
-            className='text-destructive focus:text-destructive'
-          >
-            {t('Delete')}
-            <DropdownMenuShortcut>
-              <Trash2 size={16} />
-            </DropdownMenuShortcut>
-          </DropdownMenuItem>
-        </DataTableRowActionMenu>
-      )}
 
-      <ConfirmDialog
-        open={deleteConfirmOpen}
-        onOpenChange={setDeleteConfirmOpen}
-        title={t('Delete Model')}
-        desc={t(
-          'Are you sure you want to delete model "{{name}}"? This action cannot be undone.',
-          { name: model.model_name }
-        )}
-        confirmText={t('Delete')}
-        destructive
-        handleConfirm={() => {
-          if (!canDeleteModel) return
-          handleDeleteModel(model.id, queryClient)
-          setDeleteConfirmOpen(false)
-        }}
-      />
     </div>
   )
 }

+ 8 - 78
default/src/features/models/components/drawers/model-mutate-drawer.tsx

@@ -51,8 +51,6 @@ import {
   FormMessage,
 } from '@/components/ui/form'
 import { Input } from '@/components/ui/input'
-import { Label } from '@/components/ui/label'
-import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
 import {
   Select,
   SelectContent,
@@ -82,7 +80,7 @@ import type { ModelSettings } from '@/features/system-settings/types'
 import { safeJsonParse } from '@/features/system-settings/utils/json-parser'
 
 import { createModel, updateModel, getModel, getVendors } from '../../api'
-import { getNameRuleOptions, ENDPOINT_TEMPLATES } from '../../constants'
+import { ENDPOINT_TEMPLATES } from '../../constants'
 import { modelsQueryKeys, vendorsQueryKeys, parseModelTags } from '../../lib'
 import type { Model } from '../../types'
 
@@ -625,17 +623,17 @@ export function ModelMutateDrawer({
 
           toast.success(
             isEditing
-              ? 'Model updated successfully'
-              : 'Model created successfully'
+              ? t('Model updated successfully')
+              : t('Model created successfully')
           )
           queryClient.invalidateQueries({ queryKey: modelsQueryKeys.lists() })
           queryClient.invalidateQueries({ queryKey: ['system-options'] })
           onOpenChange(false)
         } else {
-          toast.error(response.message || 'Operation failed')
+          toast.error(response.message || t('Operation failed'))
         }
       } catch (error: unknown) {
-        toast.error((error as Error)?.message || 'Operation failed')
+        toast.error((error as Error)?.message || t('Operation failed'))
       } finally {
         setIsSubmitting(false)
       }
@@ -700,6 +698,7 @@ export function ModelMutateDrawer({
                       <Input
                         placeholder={t('deepseek, qwen-plus, etc.')}
                         {...field}
+                        disabled={isEditing}
                       />
                     </FormControl>
                     <FormDescription>
@@ -811,52 +810,6 @@ export function ModelMutateDrawer({
               />
             </SideDrawerSection>
 
-            {/* Matching Configuration */}
-            <SideDrawerSection>
-              <h3 className='text-sm font-semibold'>{t('Matching Rules')}</h3>
-
-              <FormField
-                control={form.control}
-                name='name_rule'
-                render={({ field }) => (
-                  <FormItem>
-                    <FormLabel>{t('Name Rule')}</FormLabel>
-                    <FormControl>
-                      <RadioGroup
-                        onValueChange={(value) =>
-                          field.onChange(Number.parseInt(value))
-                        }
-                        value={String(field.value)}
-                        className='grid grid-cols-2 gap-4'
-                      >
-                        {getNameRuleOptions(t).map((option) => (
-                          <div
-                            key={option.value}
-                            className='flex items-center space-x-2'
-                          >
-                            <RadioGroupItem
-                              value={String(option.value)}
-                              id={`rule-${option.value}`}
-                            />
-                            <Label
-                              htmlFor={`rule-${option.value}`}
-                              className='cursor-pointer font-normal'
-                            >
-                              {option.label}
-                            </Label>
-                          </div>
-                        ))}
-                      </RadioGroup>
-                    </FormControl>
-                    <FormDescription>
-                      {t('How this model name should match requests')}
-                    </FormDescription>
-                    <FormMessage />
-                  </FormItem>
-                )}
-              />
-            </SideDrawerSection>
-
             {/* Endpoints Configuration */}
             <SideDrawerSection>
               <div className='flex items-center justify-between'>
@@ -914,9 +867,9 @@ export function ModelMutateDrawer({
               />
             </SideDrawerSection>
 
-            {/* Status & Sync */}
+            {/* Status */}
             <SideDrawerSection>
-              <h3 className='text-sm font-semibold'>{t('Status & Sync')}</h3>
+              <h3 className='text-sm font-semibold'>{t('Status')}</h3>
 
               <FormField
                 control={form.control}
@@ -940,29 +893,6 @@ export function ModelMutateDrawer({
                   </FormItem>
                 )}
               />
-
-              <FormField
-                control={form.control}
-                name='sync_official'
-                render={({ field }) => (
-                  <FormItem className={sideDrawerSwitchItemClassName()}>
-                    <div className='flex flex-col gap-0.5'>
-                      <FormLabel className='text-base'>
-                        {t('Official Sync')}
-                      </FormLabel>
-                      <FormDescription>
-                        {t('Sync this model with official upstream')}
-                      </FormDescription>
-                    </div>
-                    <FormControl>
-                      <Switch
-                        checked={field.value}
-                        onCheckedChange={field.onChange}
-                      />
-                    </FormControl>
-                  </FormItem>
-                )}
-              />
             </SideDrawerSection>
           </form>
         </Form>

+ 0 - 29
default/src/features/models/components/models-columns.tsx

@@ -409,35 +409,6 @@ export function useModelsColumns(vendors: Vendor[] = []): ColumnDef<Model>[] {
       enableSorting: false,
     },
 
-    // Sync Official column
-    {
-      accessorKey: 'sync_official',
-      header: t('Official Sync'),
-      meta: { mobileHidden: true },
-      cell: ({ row }) => {
-        const syncOfficial = row.getValue('sync_official') as number
-        return (
-          <StatusBadge
-            variant={syncOfficial === 1 ? 'success' : 'warning'}
-            size='sm'
-            copyable={false}
-            className='-ml-1.5 max-w-none shrink-0'
-          >
-            {syncOfficial === 1 ? t('Official Sync') : t('No Sync')}
-          </StatusBadge>
-        )
-      },
-      filterFn: (row, id, value) => {
-        if (!value || value.length === 0 || value.includes('all')) return true
-        const syncOfficial = row.getValue(id) as number
-        if (value.includes('yes')) return syncOfficial === 1
-        if (value.includes('no')) return syncOfficial !== 1
-        return false
-      },
-      size: 100,
-      enableSorting: false,
-    },
-
     // Created Time column
     {
       accessorKey: 'created_time',

+ 1 - 102
default/src/features/models/components/models-primary-buttons.tsx

@@ -16,107 +16,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 For commercial licensing, please contact support@quantumnous.com
 */
-import {
-  Plus,
-  MoreHorizontal,
-  RefreshCw,
-  List,
-  Building2,
-  AlertCircle,
-} from 'lucide-react'
-import { useTranslation } from 'react-i18next'
-
-import { Button } from '@/components/ui/button'
-import {
-  DropdownMenu,
-  DropdownMenuContent,
-  DropdownMenuItem,
-  DropdownMenuSeparator,
-  DropdownMenuShortcut,
-  DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu'
-import { PERMISSION_CODES, hasPermissionCode } from '@/lib/admin-permissions'
-import { useAuthStore } from '@/stores/auth-store'
-
-import { useModels } from './models-provider'
-
 export function ModelsPrimaryButtons() {
-  const { t } = useTranslation()
-  const { setOpen, setCurrentRow } = useModels()
-  const currentUser = useAuthStore((s) => s.auth.user)
-  const canCreateModel = hasPermissionCode(
-    currentUser,
-    PERMISSION_CODES.MODELS_CREATE
-  )
-
-  const handleCreateModel = () => {
-    setCurrentRow(null)
-    setOpen('create-model')
-  }
-
-  const handleMissingModels = () => {
-    setOpen('missing-models')
-  }
-
-  const handleSync = () => {
-    setOpen('sync-wizard')
-  }
-
-  const handlePrefillGroups = () => {
-    setOpen('prefill-groups')
-  }
-
-  const handleManageVendors = () => {
-    setOpen('create-vendor') // Will be a separate vendors management dialog
-  }
-
-  return (
-    <div className='flex items-center gap-2'>
-      {/* Create Model */}
-      {canCreateModel && (
-        <Button onClick={handleCreateModel} size='sm'>
-          <Plus className='h-4 w-4' />
-          {t('Add Model')}
-        </Button>
-      )}
-
-      {/* More Actions */}
-      <DropdownMenu>
-        <DropdownMenuTrigger render={<Button variant='outline' size='sm' />}>
-          <MoreHorizontal className='h-4 w-4' />
-        </DropdownMenuTrigger>
-        <DropdownMenuContent align='end' className='w-56'>
-          <DropdownMenuItem onClick={handleMissingModels}>
-            {t('Missing Models')}
-            <DropdownMenuShortcut>
-              <AlertCircle className='h-4 w-4' />
-            </DropdownMenuShortcut>
-          </DropdownMenuItem>
-
-          <DropdownMenuItem onClick={handleSync}>
-            {t('Sync Upstream')}
-            <DropdownMenuShortcut>
-              <RefreshCw className='h-4 w-4' />
-            </DropdownMenuShortcut>
-          </DropdownMenuItem>
-
-          <DropdownMenuSeparator />
-
-          <DropdownMenuItem onClick={handlePrefillGroups}>
-            {t('Prefill Groups')}
-            <DropdownMenuShortcut>
-              <List className='h-4 w-4' />
-            </DropdownMenuShortcut>
-          </DropdownMenuItem>
-
-          <DropdownMenuItem onClick={handleManageVendors}>
-            {t('Manage Vendors')}
-            <DropdownMenuShortcut>
-              <Building2 className='h-4 w-4' />
-            </DropdownMenuShortcut>
-          </DropdownMenuItem>
-        </DropdownMenuContent>
-      </DropdownMenu>
-    </div>
-  )
+  return null
 }

+ 2 - 20
default/src/features/models/components/models-table.tsx

@@ -29,7 +29,6 @@ import { getModels, searchModels, getVendors } from '../api'
 import {
   DEFAULT_PAGE_SIZE,
   getModelStatusOptions,
-  getSyncStatusOptions,
 } from '../constants'
 import { modelsQueryKeys, vendorsQueryKeys } from '../lib'
 import { DataTableBulkActions } from './data-table-bulk-actions'
@@ -63,7 +62,6 @@ export function ModelsTable() {
     columnFilters: [
       { columnId: 'status', searchKey: 'status', type: 'array' },
       { columnId: 'vendor_id', searchKey: 'vendor', type: 'array' },
-      { columnId: 'sync_official', searchKey: 'sync', type: 'array' },
     ],
   })
 
@@ -72,9 +70,6 @@ export function ModelsTable() {
     (columnFilters.find((f) => f.id === 'status')?.value as string[]) || []
   const vendorFilter =
     (columnFilters.find((f) => f.id === 'vendor_id')?.value as string[]) || []
-  const syncFilter =
-    (columnFilters.find((f) => f.id === 'sync_official')?.value as string[]) ||
-    []
 
   // Fetch vendors for filter
   const { data: vendorsData } = useQuery({
@@ -105,17 +100,12 @@ export function ModelsTable() {
     statusFilter.length > 0 && !statusFilter.includes('all')
       ? statusFilter[0]
       : undefined
-  const syncFilterValue =
-    syncFilter.length > 0 && !syncFilter.includes('all')
-      ? syncFilter[0]
-      : undefined
 
-  // Use search API whenever any filter is active so status/sync are applied server-side
+  // Use search API whenever any filter is active so status are applied server-side
   const shouldSearch = Boolean(
     globalFilter?.trim() ||
     activeVendorFilter ||
-    statusFilterValue ||
-    syncFilterValue
+    statusFilterValue
   )
 
   // Fetch models data
@@ -125,7 +115,6 @@ export function ModelsTable() {
       keyword: globalFilter,
       vendor: activeVendorFilter,
       status: statusFilterValue,
-      sync_official: syncFilterValue,
       p: pagination.pageIndex + 1,
       page_size: pagination.pageSize,
     }),
@@ -135,7 +124,6 @@ export function ModelsTable() {
           keyword: globalFilter,
           vendor: activeVendorFilter,
           status: statusFilterValue,
-          sync_official: syncFilterValue,
           p: pagination.pageIndex + 1,
           page_size: pagination.pageSize,
         })
@@ -216,12 +204,6 @@ export function ModelsTable() {
             options: vendorFilterOptions,
             singleSelect: true,
           },
-          {
-            columnId: 'sync_official',
-            title: t('Official Sync'),
-            options: [...getSyncStatusOptions(t)],
-            singleSelect: true,
-          },
         ],
       }}
       bulkActions={<DataTableBulkActions table={table} />}

+ 1 - 1
default/src/features/pricing/index.tsx

@@ -171,7 +171,7 @@ export function PricingContent({
       <PageTransition className='relative mx-auto w-full max-w-[1800px] px-3 pt-16 pb-8 sm:px-6 sm:pt-2 sm:pb-10 xl:px-8'>
         <header className='shrink-0 px-3 pt-3 pb-2 sm:px-4 sm:pt-5 sm:pb-2.5'>
           <h2 className='text-base font-bold tracking-tight sm:text-lg'>
-            {t('Model Square')}
+            {t('Models')}
           </h2>
         </header>
 

+ 244 - 0
default/src/features/quota/index.tsx

@@ -0,0 +1,244 @@
+/*
+Copyright (C) 2023-2026 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+import {
+  Boxes,
+  TrendingUp,
+  TrendingDown,
+  Wallet,
+  type LucideIcon,
+} from 'lucide-react'
+
+import { Card, CardContent } from '@/components/ui/card'
+import {
+  Table,
+  TableBody,
+  TableCell,
+  TableFooter,
+  TableHead,
+  TableHeader,
+  TableRow,
+} from '@/components/ui/table'
+import { cn } from '@/lib/utils'
+
+type QuotaRow = {
+  name: string
+  allocated: number
+  used: number
+  remaining: number
+  color: string
+  badge: string
+  highlighted?: boolean
+}
+
+const quotaSummary: {
+  label: string
+  value: string
+  icon: LucideIcon
+  iconClass: string
+}[] = [
+  {
+    label: '配额总额',
+    value: '¥50,000',
+    icon: Boxes,
+    iconClass: 'bg-blue-500/15 text-blue-600',
+  },
+  {
+    label: '已消耗额',
+    value: '¥43,850',
+    icon: TrendingUp,
+    iconClass: 'bg-orange-500/15 text-orange-600',
+  },
+  {
+    label: '剩余额度',
+    value: '¥6,150',
+    icon: Wallet,
+    iconClass: 'bg-emerald-500/15 text-emerald-600',
+  },
+  {
+    label: '已分配下级总额',
+    value: '87',
+    icon: TrendingDown,
+    iconClass: 'bg-orange-500/15 text-orange-600',
+  },
+]
+
+const tenantQuotaRows: QuotaRow[] = [
+  {
+    name: '研发一部',
+    allocated: 17500,
+    used: 12100,
+    remaining: 5400,
+    color: 'bg-blue-600',
+    badge: '研',
+  },
+  {
+    name: '研发二部',
+    allocated: 12500,
+    used: 11500,
+    remaining: 1000,
+    color: 'bg-violet-600',
+    badge: '研',
+    highlighted: true,
+  },
+  {
+    name: '产品部',
+    allocated: 10000,
+    used: 7200,
+    remaining: 2800,
+    color: 'bg-cyan-500',
+    badge: '产',
+  },
+]
+
+const platformQuotaRows: QuotaRow[] = [
+  {
+    name: 'OpenAI 主池',
+    allocated: 120000,
+    used: 86700,
+    remaining: 33300,
+    color: 'bg-slate-700',
+    badge: '平',
+  },
+  {
+    name: 'Claude 专用池',
+    allocated: 80000,
+    used: 62800,
+    remaining: 17200,
+    color: 'bg-emerald-600',
+    badge: '平',
+  },
+]
+
+function formatQuota(value: number): string {
+  return `¥${value.toLocaleString('en-US')}`
+}
+
+function QuotaTable(props: { rows: QuotaRow[] }) {
+  const totalAllocated = props.rows.reduce((sum, row) => sum + row.allocated, 0)
+  const totalUsed = props.rows.reduce((sum, row) => sum + row.used, 0)
+  const totalRemaining = props.rows.reduce((sum, row) => sum + row.remaining, 0)
+
+  return (
+    <div className='overflow-x-auto rounded-md border'>
+      <Table className='min-w-[560px]'>
+        <TableHeader>
+          <TableRow className='bg-muted/40 hover:bg-muted/40'>
+            <TableHead className='h-9 px-4 text-xs'>名称</TableHead>
+            <TableHead className='h-9 px-4 text-xs'>分配额度</TableHead>
+            <TableHead className='h-9 px-4 text-xs'>已消费</TableHead>
+            <TableHead className='h-9 px-4 text-xs'>剩余</TableHead>
+          </TableRow>
+        </TableHeader>
+        <TableBody>
+          {props.rows.map((row) => (
+            <TableRow
+              key={row.name}
+              className={cn(
+                'hover:bg-muted/30',
+                row.highlighted && 'bg-orange-50/80'
+              )}
+            >
+              <TableCell className='px-4 py-3 align-middle'>
+                <span className='flex items-center gap-3'>
+                  <span
+                    className={cn(
+                      'flex size-7 items-center justify-center rounded-md text-xs font-bold text-white',
+                      row.color
+                    )}
+                    aria-hidden='true'
+                  >
+                    {row.badge}
+                  </span>
+                  <span className='font-medium'>{row.name}</span>
+                </span>
+              </TableCell>
+              <TableCell className='px-4 py-3 align-middle tabular-nums'>
+                {formatQuota(row.allocated)}
+              </TableCell>
+              <TableCell className='px-4 py-3 align-middle tabular-nums'>
+                {formatQuota(row.used)}
+              </TableCell>
+              <TableCell className='px-4 py-3 align-middle tabular-nums'>
+                {formatQuota(row.remaining)}
+              </TableCell>
+            </TableRow>
+          ))}
+        </TableBody>
+        <TableFooter>
+          <TableRow className='hover:bg-muted/30'>
+            <TableCell className='px-4 py-3 font-bold'>合计</TableCell>
+            <TableCell className='px-4 py-3 font-bold tabular-nums'>
+              {formatQuota(totalAllocated)}
+            </TableCell>
+            <TableCell className='px-4 py-3 font-bold tabular-nums'>
+              {formatQuota(totalUsed)}
+            </TableCell>
+            <TableCell className='px-4 py-3 font-bold tabular-nums'>
+              {formatQuota(totalRemaining)}
+            </TableCell>
+          </TableRow>
+        </TableFooter>
+      </Table>
+    </div>
+  )
+}
+
+export function QuotaManagement() {
+  return (
+    <div className='space-y-6'>
+      <div className='grid gap-4 sm:grid-cols-2 xl:grid-cols-4'>
+        {quotaSummary.map((item) => {
+          const Icon = item.icon
+          return (
+            <Card key={item.label} size='sm'>
+              <CardContent className='flex items-center gap-4'>
+                <span
+                  className={cn(
+                    'flex size-10 shrink-0 items-center justify-center rounded-lg',
+                    item.iconClass
+                  )}
+                >
+                  <Icon className='size-5' aria-hidden='true' />
+                </span>
+                <div className='min-w-0'>
+                  <div className='text-muted-foreground text-xs font-medium'>
+                    {item.label}
+                  </div>
+                  <div className='mt-1 text-2xl font-bold tracking-tight tabular-nums'>
+                    {item.value}
+                  </div>
+                </div>
+              </CardContent>
+            </Card>
+          )
+        })}
+      </div>
+
+      <section className='space-y-3'>
+        <h3 className='text-sm font-semibold'>租户配额管理</h3>
+        <QuotaTable rows={tenantQuotaRows} />
+      </section>
+
+      <section className='space-y-3'>
+        <h3 className='text-sm font-semibold'>平台配额管理</h3>
+        <QuotaTable rows={platformQuotaRows} />
+      </section>
+    </div>
+  )
+}

+ 1 - 1
default/src/features/roles/index.tsx

@@ -1327,7 +1327,7 @@ export function RolesSettings() {
 
   return (
     <SectionPageLayout fixedContent>
-      <SectionPageLayout.Title>{t('Platform Roles')}</SectionPageLayout.Title>
+      <SectionPageLayout.Title>{t('Roles & Permissions')}</SectionPageLayout.Title>
       <SectionPageLayout.Actions>
         <div className='flex items-center gap-3'>
           <div className='relative'>

+ 53 - 15
default/src/features/system-info/index.tsx

@@ -16,31 +16,69 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 For commercial licensing, please contact support@quantumnous.com
 */
+import { useMemo } from 'react'
 import { useTranslation } from 'react-i18next'
 
 import { SectionPageLayout } from '@/components/layout'
-import { Badge } from '@/components/ui/badge'
+import { SettingsPageProvider } from '@/features/system-settings/components/settings-page-context'
+import { useSystemOptions, getOptionValue } from '@/features/system-settings/hooks/use-system-options'
+import { SystemInfoSection } from '@/features/system-settings/general/system-info-section'
+import type { SiteSettings } from '@/features/system-settings/types'
 
-import { SystemInstancesPanel } from './components/system-instances-panel'
-import { SystemTasksPanel } from './components/system-tasks-panel'
+const defaultSiteSettings: SiteSettings = {
+  'theme.frontend': 'default',
+  Notice: '',
+  SystemName: '灵慧云智Token管理平台',
+  Logo: '',
+  Footer: '',
+  About: '',
+  HomePageContent: '',
+  ServerAddress: '',
+  'legal.user_agreement': '',
+  'legal.privacy_policy': '',
+  HeaderNavModules: '',
+  SidebarModulesAdmin: '',
+}
 
-export function SystemInfo() {
+export function SystemInfoPage() {
   const { t } = useTranslation()
+  const { data, isLoading } = useSystemOptions()
+
+  const settings = useMemo(
+    () => getOptionValue(data?.data, defaultSiteSettings) as SiteSettings,
+    [data?.data]
+  )
 
   return (
     <SectionPageLayout>
-      <SectionPageLayout.Title>
-        <span className='inline-flex min-w-0 items-center gap-2'>
-          <span className='truncate'>{t('System Info')}</span>
-          <Badge variant='outline' className='shrink-0'>
-            Root
-          </Badge>
-        </span>
-      </SectionPageLayout.Title>
+      <SectionPageLayout.Title>{t('System Information')}</SectionPageLayout.Title>
       <SectionPageLayout.Content>
-        <div className='space-y-4'>
-          <SystemInstancesPanel />
-          <SystemTasksPanel />
+        <div className='flex h-full min-h-0 w-full flex-col gap-4'>
+          {isLoading ? (
+            <div className='text-muted-foreground flex min-h-40 items-center justify-center text-sm'>
+              {t('Loading settings...')}
+            </div>
+          ) : (
+            <SettingsPageProvider actionsContainer={null} suppressSectionHeader>
+              <SystemInfoSection
+                defaultValues={{
+                  theme: {
+                    frontend: settings['theme.frontend'] as 'default' | 'classic',
+                  },
+                  SystemName: settings.SystemName,
+                  Logo: settings.Logo,
+                  Footer: settings.Footer,
+                  About: settings.About,
+                  HomePageContent: settings.HomePageContent,
+                  ServerAddress: settings.ServerAddress,
+                  legal: {
+                    user_agreement: settings['legal.user_agreement'],
+                    privacy_policy: settings['legal.privacy_policy'],
+                  },
+                }}
+              />
+            </SettingsPageProvider>
+          )}
         </div>
       </SectionPageLayout.Content>
     </SectionPageLayout>

+ 1 - 1
default/src/features/tenants/index.tsx

@@ -1481,7 +1481,7 @@ export function Tenants() {
   return (
     <SectionPageLayout fixedContent>
       <SectionPageLayout.Title>
-        {t('Tenant Management')}
+        {t('Tenants')}
       </SectionPageLayout.Title>
       <SectionPageLayout.Actions>
         <div className='flex items-center gap-3'>

+ 1 - 1
default/src/features/usage-logs/index.tsx

@@ -45,7 +45,7 @@ const TASK_LOG_SECTIONS = ['drawing', 'task'] as const
 
 const SECTION_META: Record<UsageLogsSectionId, { titleKey: string }> = {
   common: {
-    titleKey: 'Call Logs',
+    titleKey: 'API Logs',
   },
   audit: {
     titleKey: 'Audit Logs',

+ 1 - 1
default/src/features/usage-logs/section-registry.tsx

@@ -24,7 +24,7 @@ import { createSectionRegistry } from '@/features/system-settings/utils/section-
 const USAGE_LOGS_SECTIONS = [
   {
     id: 'common',
-    titleKey: 'Call Logs',
+    titleKey: 'API Logs',
     build: () => null, // Content is rendered directly in the page component
   },
   {

+ 48 - 33
default/src/hooks/use-sidebar-data.ts

@@ -19,19 +19,23 @@ For commercial licensing, please contact support@quantumnous.com
 import {
   // Activity,
   Box,
+  Boxes,
   Building2,
   // CreditCard,
   FileText,
   // FlaskConical,
+  Gauge,
   Key,
   LayoutDashboard,
   // MessageSquare,
   Radio,
   // ServerCog,
   Settings,
+  Tags,
   // Ticket,
   User,
   Users,
+  UsersRound,
   ShieldCheck,
   // Wallet,
 } from 'lucide-react'
@@ -78,6 +82,11 @@ export function useSidebarData(): SidebarData {
             icon: LayoutDashboard,
             permissionCode: PERMISSION_CODES.DASHBOARD_VIEW,
           },
+          {
+            title: t('Quota Management'),
+            url: '/quota',
+            icon: Gauge,
+          },
         ],
       },
       {
@@ -85,7 +94,7 @@ export function useSidebarData(): SidebarData {
         title: t('Platform Management'),
         items: [
           {
-            title: t('Channel Management'),
+            title: t('Provider Channels'),
             url: '/channels',
             icon: Radio,
             permissionCode: PERMISSION_CODES.CHANNEL_VIEW,
@@ -97,44 +106,33 @@ export function useSidebarData(): SidebarData {
             permissionCode: PERMISSION_CODES.MODELS_VIEW,
           },
           {
-            title: t('Platform Users'),
-            url: '/users',
-            icon: Users,
-            permissionCode: PERMISSION_CODES.USER_VIEW,
+            title: t('Model Pricing'),
+            url: '/model-pricing',
+            icon: Tags,
           },
           {
-            title: t('Platform Roles'),
-            url: '/roles',
-            icon: ShieldCheck,
-            permissionCode: PERMISSION_CODES.ROLE_VIEW,
+            title: t('Group Management'),
+            url: '/groups',
+            icon: Boxes,
           },
+        ],
+      },
+      {
+        id: 'enterprise',
+        title: t('Tenant Management'),
+        items: [
           {
-            title: t('Tenant Management'),
+            title: t('Tenants'),
             url: '/tenants',
             icon: Building2,
             permissionCode: PERMISSION_CODES.TENANT_VIEW,
           },
-          // {
-          //   title: t('Redemption Codes'),
-          //   url: '/redemption-codes',
-          //   icon: Ticket,
-          // },
-          // {
-          //   title: t('Subscriptions'),
-          //   url: '/subscriptions',
-          //   icon: CreditCard,
-          // },
-          // {
-          //   title: t('System Info'),
-          //   url: '/system-info',
-          //   icon: ServerCog,
-          //   requiredRole: ROLE.SUPER_ADMIN,
-          // },
         ],
       },
+  
       {
         id: 'personal-management',
-        title: t('Personal Management'),
+        title: t('Account & Access'),
         items: [
           {
             title: t('Profile'),
@@ -149,7 +147,7 @@ export function useSidebarData(): SidebarData {
             permissionCode: PERMISSION_CODES.KEYS_VIEW,
           },
           {
-            title: t('Model List'),
+            title: t('Models'),
             url: '/pricing',
             icon: Box,
             permissionCode: PERMISSION_CODES.PRICING_VIEW,
@@ -163,10 +161,10 @@ export function useSidebarData(): SidebarData {
       },
       {
         id: 'statistics-analysis',
-        title: t('Statistics Analysis'),
+        title: t('Monitoring & Logs'),
         items: [
           {
-            title: t('Call Logs'),
+            title: t('API Logs'),
             url: '/usage-logs/common',
             icon: FileText,
             permissionCode: PERMISSION_CODES.USAGE_LOGS_VIEW,
@@ -179,14 +177,31 @@ export function useSidebarData(): SidebarData {
           },
         ],
       },
+      {
+        id: 'permission',
+        title: t('Access Control'),
+        items: [
+          {
+            title: t('Users'),
+            url: '/users',
+            icon: Users,
+            permissionCode: PERMISSION_CODES.USER_VIEW,
+          },
+          {
+            title: t('Roles & Permissions'),
+            url: '/roles',
+            icon: ShieldCheck,
+            permissionCode: PERMISSION_CODES.ROLE_VIEW,
+          },
+        ],
+      },
       {
         id: 'admin-system',
         title: t('System Administration'),
         items: [
           {
-            title: t('System Settings'),
-            url: '/system-settings/site',
-            activeUrls: ['/system-settings'],
+            title: t('System Information'),
+            url: '/system-info',
             icon: Settings,
             permissionCode: PERMISSION_CODES.SETTINGS_VIEW,
           },

+ 11 - 0
default/src/i18n/locales/en.json

@@ -139,12 +139,14 @@
     "Accepts a JSON array of model identifiers that support the Imagine API.": "Accepts a JSON array of model identifiers that support the Imagine API.",
     "Accepts comma-separated status codes and inclusive ranges.": "Accepts comma-separated status codes and inclusive ranges.",
     "Access a vast selection of models via a standard, unified API protocol. Power AI applications, manage digital assets, and connect the Future.": "Access a vast selection of models via a standard, unified API protocol. Power AI applications, manage digital assets, and connect the Future.",
+    "Access Control": "Access Control",
     "Access Denied Message": "Access Denied Message",
     "Access Forbidden": "Access Forbidden",
     "Access Policy (JSON)": "Access Policy (JSON)",
     "Access previous conversations and start new ones.": "Access previous conversations and start new ones.",
     "Access Token": "Access Token",
     "AccessKey / SecretAccessKey": "AccessKey / SecretAccessKey",
+    "Account & Access": "Account & Access",
     "Account Binding Management": "Account Binding Management",
     "Account Bindings": "Account Bindings",
     "Account created! Please sign in": "Account created! Please sign in",
@@ -416,6 +418,7 @@
     "API Key mode: use APIKey|Region": "API Key mode: use APIKey|Region",
     "API Key updated successfully": "API Key updated successfully",
     "API Keys": "API Keys",
+    "API Logs": "API Logs",
     "API Private Key": "API Private Key",
     "API Requests": "API Requests",
     "API secret": "API secret",
@@ -2707,6 +2710,7 @@
     "model billing support": "model billing support",
     "Model Call Analytics": "Call Analytics",
     "Model context usage": "Model context usage",
+    "Model created successfully": "Model created successfully",
     "Model deleted": "Model deleted",
     "Model deleted successfully": "Model deleted successfully",
     "Model Deployment": "Model Deployment",
@@ -2752,6 +2756,7 @@
     "Model Tags": "Model Tags",
     "Model to use for testing": "Model to use for testing",
     "Model to use when testing channel connectivity": "Model to use when testing channel connectivity",
+    "Model updated successfully": "Model updated successfully",
     "Model Version *": "Model Version *",
     "Model-scoped only": "Model-scoped only",
     "model(s) selected out of": "model(s) selected out of",
@@ -2784,6 +2789,7 @@
     "Monitor balance, usage, and request volume": "Monitor balance, usage, and request volume",
     "Monitored relay requests": "Monitored relay requests",
     "Monitoring & Alerts": "Monitoring & Alerts",
+    "Monitoring & Logs": "Monitoring & Logs",
     "Month": "Month",
     "Month number": "Month number",
     "Monthly": "Monthly",
@@ -3570,6 +3576,7 @@
     "Profile": "Profile",
     "Profile updated successfully": "Profile updated successfully",
     "Programming": "Programming",
+    "Provider Channels": "Provider Channels",
     "Progress": "Progress",
     "Project": "Project",
     "Promote": "Promote",
@@ -3638,6 +3645,7 @@
     "Quota given to invited users ({{formattedQuota}})": "Quota given to invited users ({{formattedQuota}})",
     "Quota given to users who invite others": "Quota given to users who invite others",
     "Quota given to users who invite others ({{formattedQuota}})": "Quota given to users who invite others ({{formattedQuota}})",
+    "Quota Management": "Quota Management",
     "Quota must be a positive number": "Quota must be a positive number",
     "Quota must be zero or greater": "Quota must be zero or greater",
     "Quota Per Unit": "Quota Per Unit",
@@ -5266,6 +5274,7 @@
     "Zhipu V4": "Zhipu V4",
     "Zoom": "Zoom",
     "Platform Roles": "Platform Roles",
+    "Roles & Permissions": "Roles & Permissions",
     "Role Name": "Role Name",
     "Role Description": "Role Description",
     "Permission Modules": "Permission Modules",
@@ -5328,6 +5337,8 @@
     "Are you sure you want to delete role {{name}}?": "Are you sure you want to delete role {{name}}?",
     "Keys": "Keys",
     "Tenant Management": "Tenant Management",
+    "Tenant List": "Tenant List",
+    "Tenants": "Tenants",
     "Tenant Name": "Tenant Name",
     "Tenant Code": "Tenant Code",
     "Tenant Status": "Tenant Status",

+ 17 - 6
default/src/i18n/locales/zh.json

@@ -139,12 +139,14 @@
     "Accepts a JSON array of model identifiers that support the Imagine API.": "接受支持 Imagine API 的模型标识符的 JSON 数组。",
     "Accepts comma-separated status codes and inclusive ranges.": "接受逗号分隔的状态码和包含性范围。",
     "Access a vast selection of models via a standard, unified API protocol. Power AI applications, manage digital assets, and connect the Future.": "通过统一、标准的接口协议接入海量模型。承载 AI 应用,高效管理数字资产,连接未来。",
+    "Access Control": "权限管理",
     "Access Denied Message": "访问被拒绝消息",
     "Access Forbidden": "禁止访问",
     "Access Policy (JSON)": "访问策略 (JSON)",
     "Access previous conversations and start new ones.": "访问之前的对话并开始新的对话。",
     "Access Token": "访问令牌",
     "AccessKey / SecretAccessKey": "AccessKey / SecretAccessKey",
+    "Account & Access": "个人信息",
     "Account Binding Management": "账户绑定管理",
     "Account Bindings": "账户绑定",
     "Account created! Please sign in": "账户已创建!请登录",
@@ -413,6 +415,7 @@
     "API Key mode: use APIKey|Region": "API Key 模式:使用 APIKey|Region",
     "API Key updated successfully": "API 密钥更新成功",
     "API Keys": "API 密钥",
+    "API Logs": "调用日志",
     "API Private Key": "API 私钥",
     "API Requests": "API 请求",
     "API secret": "API 秘钥",
@@ -2704,6 +2707,7 @@
     "model billing support": "模型计费支持",
     "Model Call Analytics": "调用分析",
     "Model context usage": "模型上下文用量",
+    "Model created successfully": "模型创建成功",
     "Model deleted": "模型已删除",
     "Model deleted successfully": "模型删除成功",
     "Model Deployment": "模型部署",
@@ -2748,13 +2752,14 @@
     "Model Square": "模型列表",
     "Model Tags": "模型标签",
     "Model to use for testing": "用于测试的模型",
-    "Model to use when testing channel connectivity": "测试渠道连接时使用的模型",
+    "Model to use when testing channel connectivity": "测试渠道连通性时使用的模型",
+    "Model updated successfully": "模型更新成功",
     "Model Version *": "模型版本 *",
     "Model-scoped only": "仅模型分流",
     "model(s) selected out of": "已选模型(共)",
     "model(s)? This action cannot be undone.": "模型?此操作无法撤销。",
     "models": "个模型",
-    "Models": "模型",
+    "Models": "模型列表",
     "Models management": "模型管理",
     "Models *": "模型 *",
     "Models & Groups": "模型与分组",
@@ -2782,6 +2787,7 @@
     "Monitor balance, usage, and request volume": "监控余额、用量和请求量",
     "Monitored relay requests": "已监控的中继请求",
     "Monitoring & Alerts": "监控与警报",
+    "Monitoring & Logs": "统计分析",
     "Month": "本月",
     "Month number": "月份",
     "Monthly": "每月",
@@ -3404,7 +3410,7 @@
     "Planned maintenance on Friday at 22:00 UTC...": "计划于周五 22:00 UTC 进行维护...",
     "Platform": "平台",
     "Platform Management": "平台管理",
-    "Platform Users": "平台用户",
+    "Platform Users": "用户管理",
     "Personal Management": "个人管理",
     "Model List": "模型列表",
     "Statistics Analysis": "统计分析",
@@ -3568,6 +3574,7 @@
     "Profile": "个人资料",
     "Profile updated successfully": "个人资料更新成功",
     "Programming": "编程",
+    "Provider Channels": "渠道管理",
     "Progress": "进度",
     "Project": "项目",
     "Promote": "提升",
@@ -3636,6 +3643,7 @@
     "Quota given to invited users ({{formattedQuota}})": "授予被邀请用户的配额({{formattedQuota}})",
     "Quota given to users who invite others": "授予邀请其他用户的配额",
     "Quota given to users who invite others ({{formattedQuota}})": "授予邀请其他用户的配额({{formattedQuota}})",
+    "Quota Management": "配额管理",
     "Quota must be a positive number": "配额必须是正数",
     "Quota must be zero or greater": "额度不能为负数",
     "Quota Per Unit": "每单位配额",
@@ -5019,7 +5027,7 @@
     "Username must be at least 3 characters": "用户名至少需要 3 个字符",
     "Username must be at most {{max}} characters": "用户名最多 {{max}} 个字符",
     "Username or Email": "用户名或电子邮件",
-    "Users": "用户",
+    "Users": "用户管理",
     "Users call the model on the left. The platform forwards the request to the upstream model on the right.": "用户调用左侧的模型。平台将请求转发给右侧的上游模型。",
     "Users in {{group}}": "{{group}} 分组的用户",
     "Users must wait for a successful drawing before upscales or variations.": "用户必须等待成功的绘图完成,才能进行放大或变体。",
@@ -5263,7 +5271,8 @@
     "Zhipu": "智谱",
     "Zhipu V4": "智谱 V4",
     "Zoom": "缩放",
-    "Platform Roles": "平台角色",
+    "Platform Roles": "角色管理",
+    "Roles & Permissions": "角色管理",
     "Role Name": "角色名称",
     "Role Description": "角色描述",
     "Permission Modules": "权限模块数",
@@ -5325,7 +5334,9 @@
     "Own": "仅自己",
     "Are you sure you want to delete role {{name}}?": "确定要删除角色“{{name}}”吗?",
     "Keys": "API 密钥",
-    "Tenant Management": "租户管理",
+    "Tenant Management": "企业管理",
+    "Tenant List": "企业列表",
+    "Tenants": "企业列表",
     "Tenant Name": "租户名称",
     "Tenant Code": "租户编码",
     "Tenant Status": "租户状态",

+ 5 - 2
default/src/i18n/static-keys.ts

@@ -473,9 +473,12 @@ export const STATIC_I18N_KEYS = [
   'System data statistics',
   'Platform Management',
   'Platform Users',
-  'Personal Management',
+  'Account & Access',
   'Model List',
-  'Statistics Analysis',
+  'Monitoring & Logs',
+  'Access Control',
+  'Quota Management',
+  'Tenant Management',
   'Flow',
   'Flow Filters',
   'Filter the traffic flow view by time range and user.',

+ 43 - 0
default/src/routeTree.gen.ts

@@ -46,9 +46,11 @@ import { Route as AuthenticatedSystemInfoIndexRouteImport } from './routes/_auth
 import { Route as AuthenticatedSubscriptionsIndexRouteImport } from './routes/_authenticated/subscriptions/index'
 import { Route as AuthenticatedRolesIndexRouteImport } from './routes/_authenticated/roles/index'
 import { Route as AuthenticatedRedemptionCodesIndexRouteImport } from './routes/_authenticated/redemption-codes/index'
+import { Route as AuthenticatedQuotaIndexRouteImport } from './routes/_authenticated/quota/index'
 import { Route as AuthenticatedProfileIndexRouteImport } from './routes/_authenticated/profile/index'
 import { Route as AuthenticatedPlaygroundIndexRouteImport } from './routes/_authenticated/playground/index'
 import { Route as AuthenticatedModelsIndexRouteImport } from './routes/_authenticated/models/index'
+import { Route as AuthenticatedModelPricingIndexRouteImport } from './routes/_authenticated/model-pricing/index'
 import { Route as AuthenticatedKeysIndexRouteImport } from './routes/_authenticated/keys/index'
 import { Route as AuthenticatedGroupsIndexRouteImport } from './routes/_authenticated/groups/index'
 import { Route as AuthenticatedDashboardIndexRouteImport } from './routes/_authenticated/dashboard/index'
@@ -268,6 +270,11 @@ const AuthenticatedRedemptionCodesIndexRoute =
     path: '/redemption-codes/',
     getParentRoute: () => AuthenticatedRouteRoute,
   } as any)
+const AuthenticatedQuotaIndexRoute = AuthenticatedQuotaIndexRouteImport.update({
+  id: '/quota/',
+  path: '/quota/',
+  getParentRoute: () => AuthenticatedRouteRoute,
+} as any)
 const AuthenticatedProfileIndexRoute =
   AuthenticatedProfileIndexRouteImport.update({
     id: '/profile/',
@@ -286,6 +293,12 @@ const AuthenticatedModelsIndexRoute =
     path: '/models/',
     getParentRoute: () => AuthenticatedRouteRoute,
   } as any)
+const AuthenticatedModelPricingIndexRoute =
+  AuthenticatedModelPricingIndexRouteImport.update({
+    id: '/model-pricing/',
+    path: '/model-pricing/',
+    getParentRoute: () => AuthenticatedRouteRoute,
+  } as any)
 const AuthenticatedKeysIndexRoute = AuthenticatedKeysIndexRouteImport.update({
   id: '/keys/',
   path: '/keys/',
@@ -477,9 +490,11 @@ export interface FileRoutesByFullPath {
   '/dashboard/': typeof AuthenticatedDashboardIndexRoute
   '/groups/': typeof AuthenticatedGroupsIndexRoute
   '/keys/': typeof AuthenticatedKeysIndexRoute
+  '/model-pricing/': typeof AuthenticatedModelPricingIndexRoute
   '/models/': typeof AuthenticatedModelsIndexRoute
   '/playground/': typeof AuthenticatedPlaygroundIndexRoute
   '/profile/': typeof AuthenticatedProfileIndexRoute
+  '/quota/': typeof AuthenticatedQuotaIndexRoute
   '/redemption-codes/': typeof AuthenticatedRedemptionCodesIndexRoute
   '/roles/': typeof AuthenticatedRolesIndexRoute
   '/subscriptions/': typeof AuthenticatedSubscriptionsIndexRoute
@@ -542,9 +557,11 @@ export interface FileRoutesByTo {
   '/dashboard': typeof AuthenticatedDashboardIndexRoute
   '/groups': typeof AuthenticatedGroupsIndexRoute
   '/keys': typeof AuthenticatedKeysIndexRoute
+  '/model-pricing': typeof AuthenticatedModelPricingIndexRoute
   '/models': typeof AuthenticatedModelsIndexRoute
   '/playground': typeof AuthenticatedPlaygroundIndexRoute
   '/profile': typeof AuthenticatedProfileIndexRoute
+  '/quota': typeof AuthenticatedQuotaIndexRoute
   '/redemption-codes': typeof AuthenticatedRedemptionCodesIndexRoute
   '/roles': typeof AuthenticatedRolesIndexRoute
   '/subscriptions': typeof AuthenticatedSubscriptionsIndexRoute
@@ -611,9 +628,11 @@ export interface FileRoutesById {
   '/_authenticated/dashboard/': typeof AuthenticatedDashboardIndexRoute
   '/_authenticated/groups/': typeof AuthenticatedGroupsIndexRoute
   '/_authenticated/keys/': typeof AuthenticatedKeysIndexRoute
+  '/_authenticated/model-pricing/': typeof AuthenticatedModelPricingIndexRoute
   '/_authenticated/models/': typeof AuthenticatedModelsIndexRoute
   '/_authenticated/playground/': typeof AuthenticatedPlaygroundIndexRoute
   '/_authenticated/profile/': typeof AuthenticatedProfileIndexRoute
+  '/_authenticated/quota/': typeof AuthenticatedQuotaIndexRoute
   '/_authenticated/redemption-codes/': typeof AuthenticatedRedemptionCodesIndexRoute
   '/_authenticated/roles/': typeof AuthenticatedRolesIndexRoute
   '/_authenticated/subscriptions/': typeof AuthenticatedSubscriptionsIndexRoute
@@ -679,9 +698,11 @@ export interface FileRouteTypes {
     | '/dashboard/'
     | '/groups/'
     | '/keys/'
+    | '/model-pricing/'
     | '/models/'
     | '/playground/'
     | '/profile/'
+    | '/quota/'
     | '/redemption-codes/'
     | '/roles/'
     | '/subscriptions/'
@@ -744,9 +765,11 @@ export interface FileRouteTypes {
     | '/dashboard'
     | '/groups'
     | '/keys'
+    | '/model-pricing'
     | '/models'
     | '/playground'
     | '/profile'
+    | '/quota'
     | '/redemption-codes'
     | '/roles'
     | '/subscriptions'
@@ -812,9 +835,11 @@ export interface FileRouteTypes {
     | '/_authenticated/dashboard/'
     | '/_authenticated/groups/'
     | '/_authenticated/keys/'
+    | '/_authenticated/model-pricing/'
     | '/_authenticated/models/'
     | '/_authenticated/playground/'
     | '/_authenticated/profile/'
+    | '/_authenticated/quota/'
     | '/_authenticated/redemption-codes/'
     | '/_authenticated/roles/'
     | '/_authenticated/subscriptions/'
@@ -1127,6 +1152,13 @@ declare module '@tanstack/react-router' {
       preLoaderRoute: typeof AuthenticatedRedemptionCodesIndexRouteImport
       parentRoute: typeof AuthenticatedRouteRoute
     }
+    '/_authenticated/quota/': {
+      id: '/_authenticated/quota/'
+      path: '/quota'
+      fullPath: '/quota/'
+      preLoaderRoute: typeof AuthenticatedQuotaIndexRouteImport
+      parentRoute: typeof AuthenticatedRouteRoute
+    }
     '/_authenticated/profile/': {
       id: '/_authenticated/profile/'
       path: '/profile'
@@ -1148,6 +1180,13 @@ declare module '@tanstack/react-router' {
       preLoaderRoute: typeof AuthenticatedModelsIndexRouteImport
       parentRoute: typeof AuthenticatedRouteRoute
     }
+    '/_authenticated/model-pricing/': {
+      id: '/_authenticated/model-pricing/'
+      path: '/model-pricing'
+      fullPath: '/model-pricing/'
+      preLoaderRoute: typeof AuthenticatedModelPricingIndexRouteImport
+      parentRoute: typeof AuthenticatedRouteRoute
+    }
     '/_authenticated/keys/': {
       id: '/_authenticated/keys/'
       path: '/keys'
@@ -1428,9 +1467,11 @@ interface AuthenticatedRouteRouteChildren {
   AuthenticatedDashboardIndexRoute: typeof AuthenticatedDashboardIndexRoute
   AuthenticatedGroupsIndexRoute: typeof AuthenticatedGroupsIndexRoute
   AuthenticatedKeysIndexRoute: typeof AuthenticatedKeysIndexRoute
+  AuthenticatedModelPricingIndexRoute: typeof AuthenticatedModelPricingIndexRoute
   AuthenticatedModelsIndexRoute: typeof AuthenticatedModelsIndexRoute
   AuthenticatedPlaygroundIndexRoute: typeof AuthenticatedPlaygroundIndexRoute
   AuthenticatedProfileIndexRoute: typeof AuthenticatedProfileIndexRoute
+  AuthenticatedQuotaIndexRoute: typeof AuthenticatedQuotaIndexRoute
   AuthenticatedRedemptionCodesIndexRoute: typeof AuthenticatedRedemptionCodesIndexRoute
   AuthenticatedRolesIndexRoute: typeof AuthenticatedRolesIndexRoute
   AuthenticatedSubscriptionsIndexRoute: typeof AuthenticatedSubscriptionsIndexRoute
@@ -1454,9 +1495,11 @@ const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = {
   AuthenticatedDashboardIndexRoute: AuthenticatedDashboardIndexRoute,
   AuthenticatedGroupsIndexRoute: AuthenticatedGroupsIndexRoute,
   AuthenticatedKeysIndexRoute: AuthenticatedKeysIndexRoute,
+  AuthenticatedModelPricingIndexRoute: AuthenticatedModelPricingIndexRoute,
   AuthenticatedModelsIndexRoute: AuthenticatedModelsIndexRoute,
   AuthenticatedPlaygroundIndexRoute: AuthenticatedPlaygroundIndexRoute,
   AuthenticatedProfileIndexRoute: AuthenticatedProfileIndexRoute,
+  AuthenticatedQuotaIndexRoute: AuthenticatedQuotaIndexRoute,
   AuthenticatedRedemptionCodesIndexRoute:
     AuthenticatedRedemptionCodesIndexRoute,
   AuthenticatedRolesIndexRoute: AuthenticatedRolesIndexRoute,

+ 25 - 0
default/src/routes/_authenticated/model-pricing/index.tsx

@@ -0,0 +1,25 @@
+/*
+Copyright (C) 2023-2026 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+import { createFileRoute } from '@tanstack/react-router'
+
+import { ModelPricingPage } from '@/features/model-pricing'
+
+export const Route = createFileRoute('/_authenticated/model-pricing/')({
+  component: ModelPricingPage,
+})

+ 39 - 0
default/src/routes/_authenticated/quota/index.tsx

@@ -0,0 +1,39 @@
+/*
+Copyright (C) 2023-2026 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+import { createFileRoute } from '@tanstack/react-router'
+
+import { SectionPageLayout } from '@/components/layout'
+import { QuotaManagement } from '@/features/quota'
+import { useTranslation } from 'react-i18next'
+
+function QuotaPage() {
+  const { t } = useTranslation()
+  return (
+    <SectionPageLayout>
+      <SectionPageLayout.Title>{t('Quota Management')}</SectionPageLayout.Title>
+      <SectionPageLayout.Content>
+        <QuotaManagement />
+      </SectionPageLayout.Content>
+    </SectionPageLayout>
+  )
+}
+
+export const Route = createFileRoute('/_authenticated/quota/')({
+  component: QuotaPage,
+})

+ 3 - 14
default/src/routes/_authenticated/system-info/index.tsx

@@ -16,21 +16,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 For commercial licensing, please contact support@quantumnous.com
 */
-import { createFileRoute, redirect } from '@tanstack/react-router'
+import { createFileRoute } from '@tanstack/react-router'
 
-import { SystemInfo } from '@/features/system-info'
-import { ROLE } from '@/lib/roles'
-import { useAuthStore } from '@/stores/auth-store'
+import { SystemInfoPage } from '@/features/system-info'
 
 export const Route = createFileRoute('/_authenticated/system-info/')({
-  beforeLoad: () => {
-    const { auth } = useAuthStore.getState()
-
-    if (auth.user?.role !== ROLE.SUPER_ADMIN) {
-      throw redirect({
-        to: '/403',
-      })
-    }
-  },
-  component: SystemInfo,
+  component: SystemInfoPage,
 })