Procházet zdrojové kódy

feat:系统初始化页面改造

韩洋 před 3 týdny
rodič
revize
f6979f4135

+ 2 - 1
default/rsbuild.config.ts

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

+ 12 - 1
default/src/features/setup/api.ts

@@ -42,11 +42,22 @@ export function buildSetupPayload(
   values: SetupFormValues,
   values: SetupFormValues,
   rootInitialized: boolean
   rootInitialized: boolean
 ) {
 ) {
-  const { usageMode, ...rest } = values
+  const { usageMode, resource, role, ...rest } = values
 
 
   const basePayload = {
   const basePayload = {
     SelfUseModeEnabled: usageMode === 'self',
     SelfUseModeEnabled: usageMode === 'self',
     DemoSiteEnabled: usageMode === 'demo',
     DemoSiteEnabled: usageMode === 'demo',
+    QuotaForNewUser: resource.quotaForNewUser,
+    PreConsumedQuota: resource.preConsumedQuota,
+    QuotaPerUnit: resource.quotaPerUnit,
+    EnabledModules: resource.enabledModules,
+    DefaultRoles: role.roles.map((r) => ({
+      key: r.key,
+      name: r.name,
+      description: r.description,
+      is_locked: r.isLocked,
+      permission_codes: Object.values(r.permissions).flat(),
+    })),
   }
   }
 
 
   if (rootInitialized) {
   if (rootInitialized) {

+ 62 - 35
default/src/features/setup/components/complete-step.tsx

@@ -19,9 +19,9 @@ For commercial licensing, please contact support@quantumnous.com
 import { CheckCircle2 } from 'lucide-react'
 import { CheckCircle2 } from 'lucide-react'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 
 
-import { StatusBadge } from '@/components/status-badge'
 import { Separator } from '@/components/ui/separator'
 import { Separator } from '@/components/ui/separator'
 
 
+import { DEFAULT_ROLES, PERMISSION_MODULES } from '../constants'
 import type { SetupFormValues, SetupStatus } from '../types'
 import type { SetupFormValues, SetupStatus } from '../types'
 
 
 interface CompleteStepProps {
 interface CompleteStepProps {
@@ -29,26 +29,10 @@ interface CompleteStepProps {
   values: SetupFormValues
   values: SetupFormValues
 }
 }
 
 
-const USAGE_MODE_LABEL_KEYS: Record<SetupFormValues['usageMode'], string> = {
-  external: 'External operations mode',
-  self: 'Personal use mode',
-  demo: 'Demo site mode',
-}
-
-const DATABASE_VARIANT: Record<
-  string,
-  'info' | 'success' | 'warning' | 'neutral'
-> = {
-  sqlite: 'warning',
-  mysql: 'success',
-  postgres: 'success',
-}
-
 export function CompleteStep({ status, values }: CompleteStepProps) {
 export function CompleteStep({ status, values }: CompleteStepProps) {
-  const { t } = useTranslation()
-  const usageLabelKey = USAGE_MODE_LABEL_KEYS[values.usageMode]
-  const dbType = status?.database_type ?? 'Unknown'
-  const databaseVariant = DATABASE_VARIANT[dbType.toLowerCase()] ?? 'neutral'
+  const { t, i18n } = useTranslation()
+  const isZh = i18n.language.startsWith('zh')
+  const resource = values.resource
 
 
   return (
   return (
     <div className='flex flex-col items-center gap-6 text-center'>
     <div className='flex flex-col items-center gap-6 text-center'>
@@ -70,15 +54,12 @@ export function CompleteStep({ status, values }: CompleteStepProps) {
         <dl className='grid gap-6'>
         <dl className='grid gap-6'>
           <div className='space-y-1.5'>
           <div className='space-y-1.5'>
             <dt className='text-muted-foreground text-xs font-medium tracking-wide uppercase'>
             <dt className='text-muted-foreground text-xs font-medium tracking-wide uppercase'>
-              {t('Database')}
+              {t('Administrator account')}
             </dt>
             </dt>
-            <dd className='flex flex-wrap items-center gap-2'>
-              <span className='text-sm font-semibold'>{dbType}</span>
-              <StatusBadge
-                label={dbType}
-                variant={databaseVariant}
-                copyable={false}
-              />
+            <dd className='text-sm font-semibold'>
+              {status?.root_init
+                ? t('Existing account will be reused')
+                : values.username || t('Not set yet')}
             </dd>
             </dd>
           </div>
           </div>
 
 
@@ -86,12 +67,28 @@ export function CompleteStep({ status, values }: CompleteStepProps) {
 
 
           <div className='space-y-1.5'>
           <div className='space-y-1.5'>
             <dt className='text-muted-foreground text-xs font-medium tracking-wide uppercase'>
             <dt className='text-muted-foreground text-xs font-medium tracking-wide uppercase'>
-              {t('Administrator account')}
+              {t('System resource configuration')}
             </dt>
             </dt>
-            <dd className='text-sm font-semibold'>
-              {status?.root_init
-                ? t('Existing account will be reused')
-                : values.username || t('Not set yet')}
+            <dd className='text-sm'>
+              <dl className='grid gap-1.5'>
+                <div className='flex flex-col gap-1.5'>
+                  <dt className='text-muted-foreground'>
+                    {t('Enabled Modules')}
+                  </dt>
+                  <dd className='flex flex-wrap gap-1.5'>
+                    {PERMISSION_MODULES.filter((m) =>
+                      resource.enabledModules.includes(m.moduleCode)
+                    ).map((m) => (
+                      <span
+                        key={m.moduleCode}
+                        className='bg-muted inline-flex rounded px-2 py-0.5 text-xs font-medium'
+                      >
+                        {m.moduleName}
+                      </span>
+                    ))}
+                  </dd>
+                </div>
+              </dl>
             </dd>
             </dd>
           </div>
           </div>
 
 
@@ -99,9 +96,39 @@ export function CompleteStep({ status, values }: CompleteStepProps) {
 
 
           <div className='space-y-1.5'>
           <div className='space-y-1.5'>
             <dt className='text-muted-foreground text-xs font-medium tracking-wide uppercase'>
             <dt className='text-muted-foreground text-xs font-medium tracking-wide uppercase'>
-              {t('Usage mode')}
+              {t('System role configuration')}
             </dt>
             </dt>
-            <dd className='text-sm font-semibold'>{t(usageLabelKey)}</dd>
+            <dd className='text-sm'>
+              <dl className='grid gap-3'>
+                {values.role.roles.map((role, idx) => {
+                  const defaultRole = DEFAULT_ROLES[idx]
+                  const permCount = Object.values(role.permissions).reduce(
+                    (sum, arr) => sum + arr.length,
+                    0
+                  )
+                  return (
+                    <div
+                      key={role.key}
+                      className='flex items-center justify-between gap-4'
+                    >
+                      <dt className='text-muted-foreground'>
+                        {isZh
+                          ? defaultRole?.name ?? role.name
+                          : defaultRole?.nameEn ?? role.name}
+                        <span className='text-muted-foreground/60 ml-1 text-xs'>
+                          ({isZh
+                            ? defaultRole?.description ?? role.description
+                            : defaultRole?.descriptionEn ?? role.description})
+                        </span>
+                      </dt>
+                      <dd className='text-muted-foreground text-xs'>
+                        {t('{{count}} permissions', { count: permCount })}
+                      </dd>
+                    </div>
+                  )
+                })}
+              </dl>
+            </dd>
           </div>
           </div>
         </dl>
         </dl>
       </div>
       </div>

+ 105 - 0
default/src/features/setup/components/resource-step.tsx

@@ -0,0 +1,105 @@
+/*
+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 { Lock } from 'lucide-react'
+import type { UseFormReturn } from 'react-hook-form'
+import { useTranslation } from 'react-i18next'
+
+import {
+  FormDescription,
+  FormField,
+  FormItem,
+  FormLabel,
+  FormMessage,
+} from '@/components/ui/form'
+import { cn } from '@/lib/utils'
+
+import { PERMISSION_MODULES } from '../constants'
+import type { SetupFormValues } from '../types'
+
+interface ResourceStepProps {
+  form: UseFormReturn<SetupFormValues>
+}
+
+export function ResourceStep({ form }: ResourceStepProps) {
+  const { t } = useTranslation()
+
+  const toggleModule = (code: string, checked: boolean) => {
+    const current = form.getValues('resource.enabledModules')
+    const next = checked
+      ? [...current, code]
+      : current.filter((c) => c !== code)
+    form.setValue('resource.enabledModules', next, { shouldDirty: true })
+  }
+
+  return (
+    <div className='space-y-6'>
+      {/* --- Module checkboxes --- */}
+      <FormField
+        control={form.control}
+        name='resource.enabledModules'
+        render={({ field }) => (
+          <FormItem>
+            <FormLabel>{t('Menu Modules')}</FormLabel>
+            <FormDescription>
+              {t('Select which modules are visible to users.')}
+            </FormDescription>
+            <div className='grid gap-2 sm:grid-cols-2 lg:grid-cols-3'>
+              {PERMISSION_MODULES.map((mod) => {
+                const checked = field.value.includes(mod.moduleCode)
+                const disabled = mod.isOnlyOpenForRoot
+                return (
+                  <label
+                    key={mod.moduleCode}
+                    className={cn(
+                      'hover:bg-muted/65 flex items-center gap-2.5 rounded-lg border px-3 py-2.5 text-sm transition-colors',
+                      checked
+                        ? 'border-primary/45 bg-primary/5'
+                        : 'border-transparent bg-background/60',
+                      disabled && 'pointer-events-none opacity-70'
+                    )}
+                  >
+                    <input
+                      type='checkbox'
+                      checked={checked}
+                      disabled={disabled}
+                      onChange={(e) =>
+                        toggleModule(mod.moduleCode, e.target.checked)
+                      }
+                      className='size-4 rounded border-input accent-primary'
+                    />
+                    <span className='flex-1'>
+                      <span className='font-medium'>{mod.moduleName}</span>
+                      <span className='text-muted-foreground ml-1.5 text-xs'>
+                        {mod.moduleLabel}
+                      </span>
+                    </span>
+                    {disabled && (
+                      <Lock className='text-muted-foreground/60 size-3 shrink-0' />
+                    )}
+                  </label>
+                )
+              })}
+            </div>
+            <FormMessage />
+          </FormItem>
+        )}
+      />
+    </div>
+  )
+}

+ 308 - 0
default/src/features/setup/components/role-step.tsx

@@ -0,0 +1,308 @@
+/*
+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 { Lock, Plus, Trash2 } from 'lucide-react'
+import { useState } from 'react'
+import type { UseFormReturn } from 'react-hook-form'
+import { useTranslation } from 'react-i18next'
+
+import {
+  Accordion,
+  AccordionContent,
+  AccordionItem,
+  AccordionTrigger,
+} from '@/components/ui/accordion'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { Switch } from '@/components/ui/switch'
+import { cn } from '@/lib/utils'
+
+import {
+  DEFAULT_ROLES,
+  PERMISSION_TREE,
+  type PermissionModuleItem,
+} from '../constants'
+import type { SetupFormValues } from '../types'
+
+interface RoleStepProps {
+  form: UseFormReturn<SetupFormValues>
+}
+
+/** Keys of the three built-in roles that cannot be deleted. */
+const BUILTIN_KEYS = new Set(DEFAULT_ROLES.map((r) => r.key))
+
+export function RoleStep({ form }: RoleStepProps) {
+  const { t, i18n } = useTranslation()
+  const isZh = i18n.language.startsWith('zh')
+  const roles = form.watch('role.roles')
+  const enabledModules = form.watch('resource.enabledModules')
+  const [expandedKeys, setExpandedKeys] = useState<string[]>(['root'])
+
+  /** Only show permission modules that are enabled in the resource step. */
+  const visibleModules = PERMISSION_TREE.filter((mod) =>
+    enabledModules.includes(mod.moduleCode)
+  )
+
+  /** Toggle all children of a module on/off for a specific role. */
+  const toggleModuleAll = (
+    roleIndex: number,
+    mod: PermissionModuleItem,
+    checked: boolean
+  ) => {
+    const current = form.getValues(`role.roles.${roleIndex}.permissions`)
+    form.setValue(
+      `role.roles.${roleIndex}.permissions`,
+      {
+        ...current,
+        [mod.moduleCode]: checked
+          ? mod.children.map((c) => c.frontPermCode)
+          : [],
+      },
+      { shouldDirty: true }
+    )
+  }
+
+  /** Toggle a single child permission for a specific role. */
+  const toggleChild = (
+    roleIndex: number,
+    moduleCode: string,
+    childCode: string,
+    checked: boolean
+  ) => {
+    const current = form.getValues(`role.roles.${roleIndex}.permissions`)
+    const existing = current[moduleCode] ?? []
+    const next = checked
+      ? [...existing, childCode]
+      : existing.filter((c) => c !== childCode)
+    form.setValue(
+      `role.roles.${roleIndex}.permissions`,
+      { ...current, [moduleCode]: next },
+      { shouldDirty: true }
+    )
+  }
+
+  /** Add a new custom role with an empty permission set. */
+  const addRole = () => {
+    const current = form.getValues('role.roles')
+    const newKey = `custom-${Date.now()}`
+    form.setValue(
+      'role.roles',
+      [
+        ...current,
+        {
+          key: newKey,
+          name: '',
+          description: '',
+          isLocked: false,
+          permissions: {},
+        },
+      ],
+      { shouldDirty: true }
+    )
+    setExpandedKeys((prev) => [...prev, newKey])
+  }
+
+  /** Remove a custom role by index. */
+  const removeRole = (roleIndex: number) => {
+    const current = form.getValues('role.roles')
+    const removed = current[roleIndex]
+    form.setValue(
+      'role.roles',
+      current.filter((_, i) => i !== roleIndex),
+      { shouldDirty: true }
+    )
+    setExpandedKeys((prev) => prev.filter((k) => k !== removed.key))
+  }
+
+  return (
+    <div className='space-y-2'>
+      <Accordion
+        value={expandedKeys}
+        onValueChange={setExpandedKeys}
+        className='gap-2'
+      >
+        {roles.map((role, roleIndex) => {
+          const defaultRole = DEFAULT_ROLES[roleIndex]
+          const isBuiltin = BUILTIN_KEYS.has(role.key)
+          const isLocked = role.isLocked
+          const rolePermissions = role.permissions ?? {}
+
+          return (
+            <AccordionItem
+              key={role.key}
+              value={role.key}
+              className='bg-card rounded-lg border px-4'
+            >
+              <AccordionTrigger>
+                <div className='flex items-center gap-2'>
+                  <span className='text-sm font-semibold'>
+                    {isBuiltin
+                      ? (isZh ? defaultRole.name : defaultRole.nameEn)
+                      : role.name || t('New Role')
+                    }
+                  </span>
+                  <span className='text-muted-foreground text-xs'>
+                    {isBuiltin
+                      ? (isZh
+                        ? defaultRole.description
+                        : defaultRole.descriptionEn)
+                      : role.description
+                    }
+                  </span>
+                  {isLocked && (
+                    <Lock className='text-muted-foreground/60 size-3' />
+                  )}
+                </div>
+              </AccordionTrigger>
+              <AccordionContent>
+                {/* Editable name/description for custom roles */}
+                {!isBuiltin && (
+                  <div className='mb-3 grid gap-2 sm:grid-cols-2'>
+                    <Input
+                      placeholder={t('Role Name')}
+                      value={role.name}
+                      onChange={(e) =>
+                        form.setValue(
+                          `role.roles.${roleIndex}.name`,
+                          e.target.value,
+                          { shouldDirty: true }
+                        )
+                      }
+                    />
+                    <Input
+                      placeholder={t('Role Description')}
+                      value={role.description}
+                      onChange={(e) =>
+                        form.setValue(
+                          `role.roles.${roleIndex}.description`,
+                          e.target.value,
+                          { shouldDirty: true }
+                        )
+                      }
+                    />
+                  </div>
+                )}
+
+                <div className='grid gap-2 sm:grid-cols-2 lg:grid-cols-3'>
+                  {visibleModules.map((mod) => {
+                    const selected = rolePermissions[mod.moduleCode] ?? []
+                    const allChildCodes = mod.children.map(
+                      (c) => c.frontPermCode
+                    )
+                    const isAllSelected =
+                      allChildCodes.length > 0 &&
+                      allChildCodes.every((c) => selected.includes(c))
+
+                    return (
+                      <div
+                        key={mod.moduleCode}
+                        className={cn(
+                          'rounded-md border p-2.5',
+                          isAllSelected
+                            ? 'border-primary/40 bg-primary/5'
+                            : 'border-border'
+                        )}
+                      >
+                        <div className='mb-2 flex items-center justify-between gap-2'>
+                          <span className='text-xs font-medium'>
+                            {isZh ? mod.moduleName : mod.moduleLabel}
+                          </span>
+                          <Switch
+                            checked={isAllSelected}
+                            disabled={isLocked}
+                            onCheckedChange={(checked) =>
+                              toggleModuleAll(roleIndex, mod, checked)
+                            }
+                            className='scale-75'
+                          />
+                        </div>
+                        <div className='flex flex-wrap gap-1.5'>
+                          {mod.children.map((child) => {
+                            const checked = selected.includes(
+                              child.frontPermCode
+                            )
+                            return (
+                              <label
+                                key={child.frontPermCode}
+                                className={cn(
+                                  'inline-flex cursor-pointer items-center gap-1 rounded px-1.5 py-0.5 text-xs transition-colors',
+                                  checked
+                                    ? 'bg-primary/10 text-primary'
+                                    : 'text-muted-foreground',
+                                  isLocked && 'pointer-events-none'
+                                )}
+                              >
+                                <input
+                                  type='checkbox'
+                                  checked={checked}
+                                  disabled={isLocked}
+                                  onChange={(e) =>
+                                    toggleChild(
+                                      roleIndex,
+                                      mod.moduleCode,
+                                      child.frontPermCode,
+                                      e.target.checked
+                                    )
+                                  }
+                                  className='size-3 rounded border-input accent-primary'
+                                />
+                                {isZh
+                                  ? child.permName
+                                  : child.permLabel}
+                              </label>
+                            )
+                          })}
+                        </div>
+                      </div>
+                    )
+                  })}
+                </div>
+
+                {/* Delete button for custom roles */}
+                {!isBuiltin && (
+                  <div className='mt-3 flex justify-end'>
+                    <Button
+                      type='button'
+                      variant='ghost'
+                      size='sm'
+                      onClick={() => removeRole(roleIndex)}
+                    >
+                      <Trash2 className='mr-1 size-3.5' />
+                      {t('Delete Role')}
+                    </Button>
+                  </div>
+                )}
+              </AccordionContent>
+            </AccordionItem>
+          )
+        })}
+      </Accordion>
+
+      <Button
+        type='button'
+        variant='outline'
+        size='sm'
+        onClick={addRole}
+        className='w-full border-dashed'
+      >
+        <Plus className='mr-1 size-4' />
+        {t('Add Role')}
+      </Button>
+    </div>
+  )
+}

+ 300 - 0
default/src/features/setup/constants.ts

@@ -0,0 +1,300 @@
+/*
+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
+*/
+
+/* ------------------------------------------------------------------ */
+/*  Module-level permission data (flat list, for resource step)        */
+/* ------------------------------------------------------------------ */
+
+/** A selectable menu module shown on the resource configuration step. */
+export interface PermissionModule {
+  /** Module name used as the checkbox label (already localized in zh). */
+  moduleName: string
+  /** Module code, e.g. `route.dashboard.view`. */
+  moduleCode: string
+  /** English label for the module. */
+  moduleLabel: string
+  /** When 1 the module is root-only: checked & disabled by default. */
+  isOnlyOpenForRoot: boolean
+}
+
+/**
+ * Static list of permission modules sourced from
+ * `docs/permission-identifiers.json`.
+ */
+export const PERMISSION_MODULES: PermissionModule[] = [
+  { moduleName: '数据看板', moduleCode: 'route.dashboard.view', moduleLabel: 'Dashboard', isOnlyOpenForRoot: false },
+  { moduleName: '配额管理', moduleCode: 'route.quota.view', moduleLabel: 'Quota Management', isOnlyOpenForRoot: false },
+  { moduleName: '渠道管理', moduleCode: 'route.channel.view', moduleLabel: 'Provider Channels', isOnlyOpenForRoot: true },
+  { moduleName: '模型管理', moduleCode: 'route.models.view', moduleLabel: 'Model Management', isOnlyOpenForRoot: true },
+  { moduleName: '模型定价', moduleCode: 'route.model-pricing.view', moduleLabel: 'Model Pricing', isOnlyOpenForRoot: true },
+  { moduleName: '分组管理', moduleCode: 'route.groups.view', moduleLabel: 'Group Management', isOnlyOpenForRoot: true },
+  { moduleName: '企业列表', moduleCode: 'route.tenant.view', moduleLabel: 'Tenants', isOnlyOpenForRoot: false },
+  { moduleName: '个人资料', moduleCode: 'route.profile.view', moduleLabel: 'Profile', isOnlyOpenForRoot: false },
+  { moduleName: 'API密钥', moduleCode: 'route.keys.view', moduleLabel: 'API Keys', isOnlyOpenForRoot: false },
+  { moduleName: '模型列表', moduleCode: 'route.pricing.view', moduleLabel: 'Models', isOnlyOpenForRoot: false },
+  { moduleName: '调用日志', moduleCode: 'route.usage-logs.view', moduleLabel: 'API Logs', isOnlyOpenForRoot: false },
+  { moduleName: '审计日志', moduleCode: 'route.audit-logs.view', moduleLabel: 'Audit Logs', isOnlyOpenForRoot: false },
+  { moduleName: '用户管理', moduleCode: 'route.user.view', moduleLabel: 'Users', isOnlyOpenForRoot: false },
+  { moduleName: '角色管理', moduleCode: 'route.role.view', moduleLabel: 'Roles & Permissions', isOnlyOpenForRoot: false },
+  { moduleName: '系统信息', moduleCode: 'route.settings.view', moduleLabel: 'System Information', isOnlyOpenForRoot: true },
+]
+
+/** Default enabled module codes: all modules enabled by default. */
+export const DEFAULT_ENABLED_MODULE_CODES = PERMISSION_MODULES.map(
+  (m) => m.moduleCode
+)
+
+/* ------------------------------------------------------------------ */
+/*  Permission tree with children (for role step)                      */
+/* ------------------------------------------------------------------ */
+
+/** A button-level permission inside a module. */
+export interface PermissionChildItem {
+  permName: string
+  frontPermCode: string
+  permLabel: string
+}
+
+/** A permission module with its child (button) permissions. */
+export interface PermissionModuleItem extends PermissionModule {
+  forRoleTemplateType: number
+  children: PermissionChildItem[]
+}
+
+/**
+ * Full permission tree sourced from `docs/permission-identifiers.json`,
+ * including button-level children.
+ */
+export const PERMISSION_TREE: PermissionModuleItem[] = [
+  {
+    moduleName: '数据看板', moduleCode: 'route.dashboard.view', moduleLabel: 'Dashboard',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [{ permName: '查看', frontPermCode: 'dashboard.view', permLabel: 'View Dashboard' }],
+  },
+  {
+    moduleName: '配额管理', moduleCode: 'route.quota.view', moduleLabel: 'Quota Management',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [{ permName: '查看', frontPermCode: 'quota.view', permLabel: 'View Quota Management' }],
+  },
+  {
+    moduleName: '渠道管理', moduleCode: 'route.channel.view', moduleLabel: 'Provider Channels',
+    isOnlyOpenForRoot: true, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'channel.view', permLabel: 'View Provider Channels' },
+      { permName: '新增', frontPermCode: 'channel.create', permLabel: 'Create Provider Channel' },
+      { permName: '编辑', frontPermCode: 'channel.update', permLabel: 'Update Provider Channel' },
+      { permName: '删除', frontPermCode: 'channel.delete', permLabel: 'Delete Provider Channel' },
+      { permName: '禁用', frontPermCode: 'channel.disable', permLabel: 'Disable Provider Channel' },
+    ],
+  },
+  {
+    moduleName: '模型管理', moduleCode: 'route.models.view', moduleLabel: 'Model Management',
+    isOnlyOpenForRoot: true, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'models.view', permLabel: 'View Model Management' },
+      { permName: '编辑', frontPermCode: 'models.update', permLabel: 'Update Model' },
+      { permName: '禁用', frontPermCode: 'models.disable', permLabel: 'Disable Model' },
+    ],
+  },
+  {
+    moduleName: '模型定价', moduleCode: 'route.model-pricing.view', moduleLabel: 'Model Pricing',
+    isOnlyOpenForRoot: true, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'model-pricing.view', permLabel: 'View Model Pricing' },
+      { permName: '新增', frontPermCode: 'model-pricing.add', permLabel: 'Add Model Pricing' },
+      { permName: '编辑', frontPermCode: 'model-pricing.edit', permLabel: 'Edit Model Pricing' },
+      { permName: '删除', frontPermCode: 'model-pricing.delete', permLabel: 'Delete Model Pricing' },
+    ],
+  },
+  {
+    moduleName: '分组管理', moduleCode: 'route.groups.view', moduleLabel: 'Group Management',
+    isOnlyOpenForRoot: true, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'groups.view', permLabel: 'View Group Management' },
+      { permName: '新增', frontPermCode: 'groups.create', permLabel: 'Create Group' },
+      { permName: '编辑', frontPermCode: 'groups.update', permLabel: 'Update Group' },
+      { permName: '删除', frontPermCode: 'groups.delete', permLabel: 'Delete Group' },
+    ],
+  },
+  {
+    moduleName: '企业列表', moduleCode: 'route.tenant.view', moduleLabel: 'Tenants',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'tenant.view', permLabel: 'View Tenants' },
+      { permName: '新增', frontPermCode: 'tenant.create', permLabel: 'Create Tenant' },
+      { permName: '编辑', frontPermCode: 'tenant.update', permLabel: 'Update Tenant' },
+      { permName: '禁用', frontPermCode: 'tenant.disable', permLabel: 'Disable Tenant' },
+    ],
+  },
+  {
+    moduleName: '个人资料', moduleCode: 'route.profile.view', moduleLabel: 'Profile',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [{ permName: '查看', frontPermCode: 'profile.view', permLabel: 'View Profile' }],
+  },
+  {
+    moduleName: 'API密钥', moduleCode: 'route.keys.view', moduleLabel: 'API Keys',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [
+      { permName: '查看', frontPermCode: 'keys.view', permLabel: 'View API Keys' },
+      { permName: '新增', frontPermCode: 'keys.create', permLabel: 'Create API Key' },
+      { permName: '编辑', frontPermCode: 'keys.update', permLabel: 'Update API Key' },
+      { permName: '删除', frontPermCode: 'keys.delete', permLabel: 'Delete API Key' },
+      { permName: '禁用', frontPermCode: 'keys.disable', permLabel: 'Disable API Key' },
+    ],
+  },
+  {
+    moduleName: '模型列表', moduleCode: 'route.pricing.view', moduleLabel: 'Models',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [{ permName: '查看', frontPermCode: 'pricing.view', permLabel: 'View Models' }],
+  },
+  {
+    moduleName: '调用日志', moduleCode: 'route.usage-logs.view', moduleLabel: 'API Logs',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [
+      { permName: '查看(仅自己)', frontPermCode: 'usage-logs.view', permLabel: 'View API Logs' },
+      { permName: '查看(全部数据)', frontPermCode: 'usage-logs.all', permLabel: 'View All API Logs' },
+    ],
+  },
+  {
+    moduleName: '审计日志', moduleCode: 'route.audit-logs.view', moduleLabel: 'Audit Logs',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [
+      { permName: '查看(仅自己)', frontPermCode: 'audit-logs.view', permLabel: 'View Audit Logs' },
+      { permName: '查看(全部数据)', frontPermCode: 'audit-logs.all', permLabel: 'View All Audit Logs' },
+    ],
+  },
+  {
+    moduleName: '用户管理', moduleCode: 'route.user.view', moduleLabel: 'Users',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'user.view', permLabel: 'View Users' },
+      { permName: '新增', frontPermCode: 'user.create', permLabel: 'Create User' },
+      { permName: '编辑', frontPermCode: 'user.update', permLabel: 'Update User' },
+      { permName: '删除', frontPermCode: 'user.delete', permLabel: 'Delete User' },
+      { permName: '禁用', frontPermCode: 'user.disable', permLabel: 'Disable User' },
+    ],
+  },
+  {
+    moduleName: '角色管理', moduleCode: 'route.role.view', moduleLabel: 'Roles & Permissions',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 30,
+    children: [
+      { permName: '查看', frontPermCode: 'role.view', permLabel: 'View Roles & Permissions' },
+      { permName: '新增', frontPermCode: 'role.create', permLabel: 'Create Role' },
+      { permName: '编辑', frontPermCode: 'role.update', permLabel: 'Update Role' },
+      { permName: '删除', frontPermCode: 'role.delete', permLabel: 'Delete Role' },
+    ],
+  },
+  {
+    moduleName: '系统信息', moduleCode: 'route.settings.view', moduleLabel: 'System Information',
+    isOnlyOpenForRoot: true, forRoleTemplateType: 20,
+    children: [{ permName: '查看', frontPermCode: 'settings.view', permLabel: 'View System Information' }],
+  },
+]
+
+/* ------------------------------------------------------------------ */
+/*  Default roles                                                      */
+/* ------------------------------------------------------------------ */
+
+/** A default role displayed on the role configuration step. */
+export interface DefaultRoleConfig {
+  /** Stable identifier for the role. */
+  key: string
+  /** Display name (Chinese). */
+  name: string
+  /** Display name (English). */
+  nameEn: string
+  /** Description (Chinese). */
+  description: string
+  /** Description (English). */
+  descriptionEn: string
+  /** When true the role's permissions cannot be modified. */
+  isLocked: boolean
+  /**
+   * Default permission map: moduleCode -> array of frontPermCode values
+   * for the enabled children.
+   */
+  permissions: Record<string, string[]>
+}
+
+/** Build a permission map where every module has all its children enabled. */
+function allPermissions(modules: PermissionModuleItem[]): Record<string, string[]> {
+  const result: Record<string, string[]> = {}
+  for (const mod of modules) {
+    result[mod.moduleCode] = mod.children.map((c) => c.frontPermCode)
+  }
+  return result
+}
+
+/** Build a view-only permission map for the given modules. */
+function viewOnlyPermissions(modules: PermissionModuleItem[]): Record<string, string[]> {
+  const result: Record<string, string[]> = {}
+  for (const mod of modules) {
+    const viewChildren = mod.children.filter(
+      (c) => c.permName === '查看' || c.permName.startsWith('查看')
+    )
+    if (viewChildren.length > 0) {
+      result[mod.moduleCode] = viewChildren.map((c) => c.frontPermCode)
+    }
+  }
+  return result
+}
+
+/** Admin-level modules: forRoleTemplateType 10 + 20 + 30. */
+const ADMIN_MODULES = PERMISSION_TREE.filter(
+  (m) => m.forRoleTemplateType <= 30
+)
+
+/** User-level modules: forRoleTemplateType 10 only. */
+const USER_MODULES = PERMISSION_TREE.filter(
+  (m) => m.forRoleTemplateType === 10
+)
+
+/**
+ * Three default roles pre-configured with sensible permission sets.
+ * Root is locked; admin and common-user are editable.
+ */
+export const DEFAULT_ROLES: DefaultRoleConfig[] = [
+  {
+    key: 'root',
+    name: '超级管理员',
+    nameEn: 'Super Admin',
+    description: '拥有系统全部权限,不可修改',
+    descriptionEn: 'Full system access, locked',
+    isLocked: true,
+    permissions: allPermissions(PERMISSION_TREE),
+  },
+  {
+    key: 'admin',
+    name: '管理员',
+    nameEn: 'Admin',
+    description: '拥有管理模块的全部权限',
+    descriptionEn: 'Full management module access',
+    isLocked: false,
+    permissions: allPermissions(ADMIN_MODULES),
+  },
+  {
+    key: 'common',
+    name: '普通用户',
+    nameEn: 'Common User',
+    description: '仅拥有基础查看权限',
+    descriptionEn: 'Basic view-only access',
+    isLocked: false,
+    permissions: viewOnlyPermissions(USER_MODULES),
+  },
+]

+ 31 - 51
default/src/features/setup/setup-wizard.tsx

@@ -42,23 +42,24 @@ import { cn } from '@/lib/utils'
 import { buildSetupPayload, getSetupStatus, submitSetup } from './api'
 import { buildSetupPayload, getSetupStatus, submitSetup } from './api'
 import { AdminStep } from './components/admin-step'
 import { AdminStep } from './components/admin-step'
 import { CompleteStep } from './components/complete-step'
 import { CompleteStep } from './components/complete-step'
-import { DatabaseStep } from './components/database-step'
+import { ResourceStep } from './components/resource-step'
+import { RoleStep } from './components/role-step'
 import { StepNavigation } from './components/step-navigation'
 import { StepNavigation } from './components/step-navigation'
-import { UsageModeStep } from './components/usage-mode-step'
+import { DEFAULT_ENABLED_MODULE_CODES, DEFAULT_ROLES } from './constants'
 import type { SetupFormValues, SetupStatus } from './types'
 import type { SetupFormValues, SetupStatus } from './types'
 
 
 const STEPS = [
 const STEPS = [
-  {
-    titleKey: 'Database check',
-    descriptionKey: 'Verify your database connection',
-  },
   {
   {
     titleKey: 'Administrator account',
     titleKey: 'Administrator account',
     descriptionKey: 'Create credentials for the root user',
     descriptionKey: 'Create credentials for the root user',
   },
   },
   {
   {
-    titleKey: 'Usage mode',
-    descriptionKey: 'Choose how the platform will operate',
+    titleKey: 'System resource configuration',
+    descriptionKey: 'Configure default resource limits',
+  },
+  {
+    titleKey: 'System role configuration',
+    descriptionKey: 'Set up system default roles',
   },
   },
   {
   {
     titleKey: 'Review & initialize',
     titleKey: 'Review & initialize',
@@ -71,6 +72,21 @@ const DEFAULT_FORM_VALUES: SetupFormValues = {
   password: '',
   password: '',
   confirmPassword: '',
   confirmPassword: '',
   usageMode: 'external',
   usageMode: 'external',
+  resource: {
+    quotaForNewUser: 0,
+    preConsumedQuota: 0,
+    quotaPerUnit: 500000,
+    enabledModules: DEFAULT_ENABLED_MODULE_CODES,
+  },
+  role: {
+    roles: DEFAULT_ROLES.map((r) => ({
+      key: r.key,
+      name: r.name,
+      description: r.description,
+      isLocked: r.isLocked,
+      permissions: { ...r.permissions },
+    })),
+  },
 }
 }
 
 
 export function SetupWizard() {
 export function SetupWizard() {
@@ -139,29 +155,8 @@ export function SetupWizard() {
 
 
     setSetupStatus(status)
     setSetupStatus(status)
     setCurrentStep(0)
     setCurrentStep(0)
-
-    // Pre-fill usage mode if backend echoes it
-    if (status.SelfUseModeEnabled) {
-      form.setValue('usageMode', 'self', {
-        shouldDirty: false,
-        shouldTouch: false,
-        shouldValidate: false,
-      })
-    } else if (status.DemoSiteEnabled) {
-      form.setValue('usageMode', 'demo', {
-        shouldDirty: false,
-        shouldTouch: false,
-        shouldValidate: false,
-      })
-    } else {
-      form.setValue('usageMode', 'external', {
-        shouldDirty: false,
-        shouldTouch: false,
-        shouldValidate: false,
-      })
-    }
     // eslint-disable-next-line react-hooks/exhaustive-deps
     // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [statusResponse, navigate, form])
+  }, [statusResponse, navigate])
 
 
   useEffect(() => {
   useEffect(() => {
     if (!setupStatus) return
     if (!setupStatus) return
@@ -188,9 +183,6 @@ export function SetupWizard() {
 
 
   const currentStepComponent = useMemo(() => {
   const currentStepComponent = useMemo(() => {
     if (currentStep === 0) {
     if (currentStep === 0) {
-      return <DatabaseStep status={setupStatus} />
-    }
-    if (currentStep === 1) {
       return (
       return (
         <AdminStep
         <AdminStep
           form={form}
           form={form}
@@ -198,8 +190,11 @@ export function SetupWizard() {
         />
         />
       )
       )
     }
     }
+    if (currentStep === 1) {
+      return <ResourceStep form={form} />
+    }
     if (currentStep === 2) {
     if (currentStep === 2) {
-      return <UsageModeStep form={form} />
+      return <RoleStep form={form} />
     }
     }
     return <CompleteStep status={setupStatus} values={watchedValues} />
     return <CompleteStep status={setupStatus} values={watchedValues} />
   }, [currentStep, setupStatus, form, watchedValues])
   }, [currentStep, setupStatus, form, watchedValues])
@@ -241,22 +236,8 @@ export function SetupWizard() {
     return true
     return true
   }
   }
 
 
-  const validateUsageModeStep = () => {
-    const usageMode = form.getValues('usageMode')
-    if (!usageMode) {
-      form.setError('usageMode', {
-        type: 'manual',
-        message: t('Select a usage mode to continue'),
-      })
-      toast.error(t('Select a usage mode to continue'))
-      return false
-    }
-    return true
-  }
-
   const handleNextStep = () => {
   const handleNextStep = () => {
-    if (currentStep === 1 && !validateAdminStep()) return
-    if (currentStep === 2 && !validateUsageModeStep()) return
+    if (currentStep === 0 && !validateAdminStep()) return
 
 
     setCurrentStep((step) => Math.min(step + 1, STEPS.length - 1))
     setCurrentStep((step) => Math.min(step + 1, STEPS.length - 1))
   }
   }
@@ -267,8 +248,7 @@ export function SetupWizard() {
 
 
   const handleSubmit = async () => {
   const handleSubmit = async () => {
     const adminValid = validateAdminStep()
     const adminValid = validateAdminStep()
-    const usageValid = validateUsageModeStep()
-    if (!adminValid || !usageValid) return
+    if (!adminValid) return
 
 
     const payload = buildSetupPayload(
     const payload = buildSetupPayload(
       form.getValues(),
       form.getValues(),

+ 34 - 0
default/src/features/setup/types.ts

@@ -27,11 +27,45 @@ export interface SetupStatus {
   DemoSiteEnabled?: boolean
   DemoSiteEnabled?: boolean
 }
 }
 
 
+/** Fields collected on the "System resource configuration" step. */
+export interface ResourceConfigValues {
+  /** Default quota granted to newly registered users. */
+  quotaForNewUser: number
+  /** Quota pre-consumed before a request is billed. */
+  preConsumedQuota: number
+  /** How many quota units equal one unit of display currency. */
+  quotaPerUnit: number
+  /** Enabled menu module codes selected on this step. */
+  enabledModules: string[]
+}
+
+/** A single role's form values on the role configuration step. */
+export interface RoleFormValues {
+  /** Stable key (root / admin / common). */
+  key: string
+  /** Role display name. */
+  name: string
+  /** Role description. */
+  description: string
+  /** Whether the role is locked (permissions cannot be modified). */
+  isLocked: boolean
+  /** moduleCode -> array of enabled frontPermCode values. */
+  permissions: Record<string, string[]>
+}
+
+/** Fields collected on the "System role configuration" step. */
+export interface RoleConfigValues {
+  /** The three default roles with their permission sets. */
+  roles: RoleFormValues[]
+}
+
 export interface SetupFormValues {
 export interface SetupFormValues {
   username: string
   username: string
   password: string
   password: string
   confirmPassword: string
   confirmPassword: string
   usageMode: SetupUsageMode
   usageMode: SetupUsageMode
+  resource: ResourceConfigValues
+  role: RoleConfigValues
 }
 }
 
 
 export interface SetupResponse {
 export interface SetupResponse {

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

@@ -68,6 +68,7 @@
     "{{count}} models": "{{count}} models",
     "{{count}} models": "{{count}} models",
     "{{count}} months ago": "{{count}} months ago",
     "{{count}} months ago": "{{count}} months ago",
     "{{count}} override": "{{count}} override",
     "{{count}} override": "{{count}} override",
+    "{{count}} permissions": "{{count}} permissions",
     "{{count}} selected targets available for bulk copy.": "{{count}} selected targets available for bulk copy.",
     "{{count}} selected targets available for bulk copy.": "{{count}} selected targets available for bulk copy.",
     "{{count}} tiers": "{{count}} tiers",
     "{{count}} tiers": "{{count}} tiers",
     "{{count}} Uptime Kuma groups will be removed from the list.": "{{count}} Uptime Kuma groups will be removed from the list.",
     "{{count}} Uptime Kuma groups will be removed from the list.": "{{count}} Uptime Kuma groups will be removed from the list.",
@@ -225,6 +226,7 @@
     "Add ratio override": "Add ratio override",
     "Add ratio override": "Add ratio override",
     "Add route": "Add route",
     "Add route": "Add route",
     "Add Row": "Add Row",
     "Add Row": "Add Row",
+    "Add Role": "Add Role",
     "Add Rule": "Add Rule",
     "Add Rule": "Add Rule",
     "Add rule group": "Add rule group",
     "Add rule group": "Add rule group",
     "Add rules for a user group": "Add rules for a user group",
     "Add rules for a user group": "Add rules for a user group",
@@ -347,6 +349,7 @@
     "Allow upstream callbacks": "Allow upstream callbacks",
     "Allow upstream callbacks": "Allow upstream callbacks",
     "Allow users to check in daily for random quota rewards": "Allow users to check in daily for random quota rewards",
     "Allow users to check in daily for random quota rewards": "Allow users to check in daily for random quota rewards",
     "Allow users to enter promo codes": "Allow users to enter promo codes",
     "Allow users to enter promo codes": "Allow users to enter promo codes",
+    "Allow User Registration": "Allow User Registration",
     "Allow users to log in with password": "Allow users to log in with password",
     "Allow users to log in with password": "Allow users to log in with password",
     "Allow users to register and sign in with Passkey (WebAuthn)": "Allow users to register and sign in with Passkey (WebAuthn)",
     "Allow users to register and sign in with Passkey (WebAuthn)": "Allow users to register and sign in with Passkey (WebAuthn)",
     "Allow users to sign in with Discord": "Allow users to sign in with Discord",
     "Allow users to sign in with Discord": "Allow users to sign in with Discord",
@@ -1034,6 +1037,7 @@
     "Confirm password": "Confirm password",
     "Confirm password": "Confirm password",
     "Confirm Payment": "Confirm Payment",
     "Confirm Payment": "Confirm Payment",
     "Confirm Selection": "Confirm Selection",
     "Confirm Selection": "Confirm Selection",
+    "Configure default resource limits": "Configure default resource limits",
     "Confirm settings and finish setup": "Confirm settings and finish setup",
     "Confirm settings and finish setup": "Confirm settings and finish setup",
     "confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
     "confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
     "Confirm Unbind": "Confirm Unbind",
     "Confirm Unbind": "Confirm Unbind",
@@ -1291,6 +1295,8 @@
     "Default Collapse Sidebar": "Default Collapse Sidebar",
     "Default Collapse Sidebar": "Default Collapse Sidebar",
     "Default consumption chart": "Default consumption chart",
     "Default consumption chart": "Default consumption chart",
     "Default Max Tokens": "Default Max Tokens",
     "Default Max Tokens": "Default Max Tokens",
+    "Default Quota for New Users": "Default Quota for New Users",
+    "Default Role Name": "Default Role Name",
     "Default model call chart": "Default model call chart",
     "Default model call chart": "Default model call chart",
     "Default range": "Default range",
     "Default range": "Default range",
     "Default Responses API version, if empty, will use the API version above": "Default Responses API version, if empty, will use the API version above",
     "Default Responses API version, if empty, will use the API version above": "Default Responses API version, if empty, will use the API version above",
@@ -1538,6 +1544,7 @@
     "e.g., v2.1": "e.g., v2.1",
     "e.g., v2.1": "e.g., v2.1",
     "Each backup code can only be used once.": "Each backup code can only be used once.",
     "Each backup code can only be used once.": "Each backup code can only be used once.",
     "Each item must be an object with a single key-value pair.": "Each item must be an object with a single key-value pair.",
     "Each item must be an object with a single key-value pair.": "Each item must be an object with a single key-value pair.",
+    "e.g. Common User": "e.g. Common User",
     "Each item must have exactly one key-value pair.": "Each item must have exactly one key-value pair.",
     "Each item must have exactly one key-value pair.": "Each item must have exactly one key-value pair.",
     "Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "Each line represents one keyword. Leave blank to disable the list but keep the switch states.",
     "Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "Each line represents one keyword. Leave blank to disable the list but keep the switch states.",
     "Each matrix cell is one rule: users of this row group pay this ratio when billed as this column group. In JSON the row is the outer key and the column is the inner key.": "Each matrix cell is one rule: users of this row group pay this ratio when billed as this column group. In JSON the row is the outer key and the column is the inner key.",
     "Each matrix cell is one rule: users of this row group pay this ratio when billed as this column group. In JSON the row is the outer key and the column is the inner key.": "Each matrix cell is one rule: users of this row group pay this ratio when billed as this column group. In JSON the row is the outer key and the column is the inner key.",
@@ -1625,6 +1632,7 @@
     "Enable SSL/TLS": "Enable SSL/TLS",
     "Enable SSL/TLS": "Enable SSL/TLS",
     "Enable SSRF Protection": "Enable SSRF Protection",
     "Enable SSRF Protection": "Enable SSRF Protection",
     "Enable STARTTLS": "Enable STARTTLS",
     "Enable STARTTLS": "Enable STARTTLS",
+    "Enable self-service sign-up for new users.": "Enable self-service sign-up for new users.",
     "Enable streaming mode for the test request.": "Enable streaming mode for the test request.",
     "Enable streaming mode for the test request.": "Enable streaming mode for the test request.",
     "Enable Telegram OAuth": "Enable Telegram OAuth",
     "Enable Telegram OAuth": "Enable Telegram OAuth",
     "Enable test mode for Creem payments": "Enable test mode for Creem payments",
     "Enable test mode for Creem payments": "Enable test mode for Creem payments",
@@ -1641,6 +1649,7 @@
     "Enabled all channels with tag: {{tag}}": "Enabled all channels with tag: {{tag}}",
     "Enabled all channels with tag: {{tag}}": "Enabled all channels with tag: {{tag}}",
     "Enabled channels with tag {{tag}}": "Enabled channels with tag {{tag}}",
     "Enabled channels with tag {{tag}}": "Enabled channels with tag {{tag}}",
     "Enabled Status": "Enabled Status",
     "Enabled Status": "Enabled Status",
+    "Enabled Modules": "Enabled Modules",
     "Enabling...": "Enabling...",
     "Enabling...": "Enabling...",
     "Encourages introducing new topics": "Encourages introducing new topics",
     "Encourages introducing new topics": "Encourages introducing new topics",
     "Encourages new topics": "Encourages new topics",
     "Encourages new topics": "Encourages new topics",
@@ -2293,6 +2302,7 @@
     "How to reset my quota?": "How to reset my quota?",
     "How to reset my quota?": "How to reset my quota?",
     "How to select keys: random or sequential polling": "How to select keys: random or sequential polling",
     "How to select keys: random or sequential polling": "How to select keys: random or sequential polling",
     "How will you use the platform?": "How will you use the platform?",
     "How will you use the platform?": "How will you use the platform?",
+    "How many quota units equal one unit of display currency.": "How many quota units equal one unit of display currency.",
     "https://api.day.app/yourkey/{{title}}/{{content}}": "https://api.day.app/yourkey/{{title}}/{{content}}",
     "https://api.day.app/yourkey/{{title}}/{{content}}": "https://api.day.app/yourkey/{{title}}/{{content}}",
     "https://api.example.com": "https://api.example.com",
     "https://api.example.com": "https://api.example.com",
     "https://ark.ap-southeast.bytepluses.com": "https://ark.ap-southeast.bytepluses.com",
     "https://ark.ap-southeast.bytepluses.com": "https://ark.ap-southeast.bytepluses.com",
@@ -2675,6 +2685,7 @@
     "Memory Threshold (%)": "Memory Threshold (%)",
     "Memory Threshold (%)": "Memory Threshold (%)",
     "Merchant ID": "Merchant ID",
     "Merchant ID": "Merchant ID",
     "Merchant ID is required": "Merchant ID is required",
     "Merchant ID is required": "Merchant ID is required",
+    "Menu Modules": "Menu Modules",
     "Merge into Other": "Merge into Other",
     "Merge into Other": "Merge into Other",
     "Message Priority": "Message Priority",
     "Message Priority": "Message Priority",
     "Metadata": "Metadata",
     "Metadata": "Metadata",
@@ -2886,6 +2897,7 @@
     "New password": "New password",
     "New password": "New password",
     "New Password": "New Password",
     "New Password": "New Password",
     "New password must be different from current password": "New password must be different from current password",
     "New password must be different from current password": "New password must be different from current password",
+    "New Role": "New Role",
     "New User Quota": "New User Quota",
     "New User Quota": "New User Quota",
     "New version available: {{version}}": "New version available: {{version}}",
     "New version available: {{version}}": "New version available: {{version}}",
     "NewAPI": "NewAPI",
     "NewAPI": "NewAPI",
@@ -3495,6 +3507,7 @@
     "Powerful API Management Platform": "Powerful API Management Platform",
     "Powerful API Management Platform": "Powerful API Management Platform",
     "Pre-Consume for Free Models": "Pre-Consume for Free Models",
     "Pre-Consume for Free Models": "Pre-Consume for Free Models",
     "Pre-consumed": "Pre-consumed",
     "Pre-consumed": "Pre-consumed",
+    "Pre-consumed Quota": "Pre-consumed Quota",
     "Pre-Consumed Quota": "Pre-Consumed Quota",
     "Pre-Consumed Quota": "Pre-Consumed Quota",
     "Preference saved as {{pref}}, but no active subscription. Wallet will be used automatically.": "Preference saved as {{pref}}, but no active subscription. Wallet will be used automatically.",
     "Preference saved as {{pref}}, but no active subscription. Wallet will be used automatically.": "Preference saved as {{pref}}, but no active subscription. Wallet will be used automatically.",
     "Preferences": "Preferences",
     "Preferences": "Preferences",
@@ -3646,9 +3659,11 @@
     "Quota": "Quota",
     "Quota": "Quota",
     "Quota ({{currency}})": "Quota ({{currency}})",
     "Quota ({{currency}})": "Quota ({{currency}})",
     "Quota adjusted successfully": "Quota adjusted successfully",
     "Quota adjusted successfully": "Quota adjusted successfully",
+    "Quota automatically granted to each new user upon registration.": "Quota automatically granted to each new user upon registration.",
     "Quota clamped": "Quota clamped",
     "Quota clamped": "Quota clamped",
     "Quota consumed before charging users": "Quota consumed before charging users",
     "Quota consumed before charging users": "Quota consumed before charging users",
     "Quota Distribution": "Quota Distribution",
     "Quota Distribution": "Quota Distribution",
+    "Quota deducted before a request is processed to prevent overuse.": "Quota deducted before a request is processed to prevent overuse.",
     "Quota given to invited users": "Quota given to invited users",
     "Quota given to invited users": "Quota given to invited users",
     "Quota given to invited users ({{formattedQuota}})": "Quota given to invited users ({{formattedQuota}})",
     "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": "Quota given to users who invite others",
@@ -3657,6 +3672,7 @@
     "Quota must be a positive number": "Quota must be a positive number",
     "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 must be zero or greater": "Quota must be zero or greater",
     "Quota Per Unit": "Quota Per Unit",
     "Quota Per Unit": "Quota Per Unit",
+    "Quota per Unit": "Quota per Unit",
     "Quota reminder (tokens)": "Quota reminder (tokens)",
     "Quota reminder (tokens)": "Quota reminder (tokens)",
     "Quota Reset": "Quota Reset",
     "Quota Reset": "Quota Reset",
     "Quota saturation protection triggered": "Quota saturation protection triggered",
     "Quota saturation protection triggered": "Quota saturation protection triggered",
@@ -3857,6 +3873,7 @@
     "Requests will be forwarded to this worker. Trailing slashes are removed automatically.": "Requests will be forwarded to this worker. Trailing slashes are removed automatically.",
     "Requests will be forwarded to this worker. Trailing slashes are removed automatically.": "Requests will be forwarded to this worker. Trailing slashes are removed automatically.",
     "Requests:": "Requests:",
     "Requests:": "Requests:",
     "Require email verification for new accounts": "Require email verification for new accounts",
     "Require email verification for new accounts": "Require email verification for new accounts",
+    "Require email verification when users register.": "Require email verification when users register.",
     "Require job success before follow-up actions": "Require job success before follow-up actions",
     "Require job success before follow-up actions": "Require job success before follow-up actions",
     "Require login to view models": "Require login to view models",
     "Require login to view models": "Require login to view models",
     "Require login to view rankings": "Require login to view rankings",
     "Require login to view rankings": "Require login to view rankings",
@@ -4165,6 +4182,7 @@
     "Select time granularity": "Select time granularity",
     "Select time granularity": "Select time granularity",
     "Select type": "Select type",
     "Select type": "Select type",
     "Select vendor": "Select vendor",
     "Select vendor": "Select vendor",
+    "Select which modules are visible to users.": "Select which modules are visible to users.",
     "Select user groups and models for this account": "Select user groups and models for this account",
     "Select user groups and models for this account": "Select user groups and models for this account",
     "Selectable groups": "Selectable groups",
     "Selectable groups": "Selectable groups",
     "selected": "selected",
     "selected": "selected",
@@ -4216,6 +4234,7 @@
     "Set the language used across the interface": "Set the language used across the interface",
     "Set the language used across the interface": "Set the language used across the interface",
     "Set the user's role (cannot be Root)": "Set the user's role (cannot be Root)",
     "Set the user's role (cannot be Root)": "Set the user's role (cannot be Root)",
     "Set these values in the provider application before enabling login.": "Set these values in the provider application before enabling login.",
     "Set these values in the provider application before enabling login.": "Set these values in the provider application before enabling login.",
+    "Set up system default roles": "Set up system default roles",
     "Setting Key": "Setting Key",
     "Setting Key": "Setting Key",
     "Setting saved": "Setting saved",
     "Setting saved": "Setting saved",
     "Setting up 2FA...": "Setting up 2FA...",
     "Setting up 2FA...": "Setting up 2FA...",
@@ -4470,6 +4489,8 @@
     "System settings": "System settings",
     "System settings": "System settings",
     "System Settings": "System Settings",
     "System Settings": "System Settings",
     "System setup wizard": "System setup wizard",
     "System setup wizard": "System setup wizard",
+    "System resource configuration": "System resource configuration",
+    "System role configuration": "System role configuration",
     "System task records": "System task records",
     "System task records": "System task records",
     "System Tasks": "System Tasks",
     "System Tasks": "System Tasks",
     "System Version": "System Version",
     "System Version": "System Version",
@@ -4562,6 +4583,7 @@
     "The requested chat preset does not exist or has been removed.": "The requested chat preset does not exist or has been removed.",
     "The requested chat preset does not exist or has been removed.": "The requested chat preset does not exist or has been removed.",
     "The reset request stays disabled until a credit is available.": "The reset request stays disabled until a credit is available.",
     "The reset request stays disabled until a credit is available.": "The reset request stays disabled until a credit is available.",
     "The setup wizard will use this database during initialization.": "The setup wizard will use this database during initialization.",
     "The setup wizard will use this database during initialization.": "The setup wizard will use this database during initialization.",
+    "The role automatically assigned to newly registered users.": "The role automatically assigned to newly registered users.",
     "The site is not available at the moment.": "The site is not available at the moment.",
     "The site is not available at the moment.": "The site is not available at the moment.",
     "The slug is appended to the URL:": "The slug is appended to the URL:",
     "The slug is appended to the URL:": "The slug is appended to the URL:",
     "The sync will fetch missing models and vendors from the selected source. Existing records are updated only when you approve conflicts.": "The sync will fetch missing models and vendors from the selected source. Existing records are updated only when you approve conflicts.",
     "The sync will fetch missing models and vendors from the selected source. Existing records are updated only when you approve conflicts.": "The sync will fetch missing models and vendors from the selected source. Existing records are updated only when you approve conflicts.",

+ 22 - 0
default/src/i18n/locales/zh.json

@@ -68,6 +68,7 @@
     "{{count}} models": "{{count}} 个模型",
     "{{count}} models": "{{count}} 个模型",
     "{{count}} months ago": "{{count}} 个月前",
     "{{count}} months ago": "{{count}} 个月前",
     "{{count}} override": "{{count}} 个覆盖",
     "{{count}} override": "{{count}} 个覆盖",
+    "{{count}} permissions": "{{count}} 个权限",
     "{{count}} selected targets available for bulk copy.": "已选择 {{count}} 个目标,可用于批量复制。",
     "{{count}} selected targets available for bulk copy.": "已选择 {{count}} 个目标,可用于批量复制。",
     "{{count}} tiers": "{{count}} 档",
     "{{count}} tiers": "{{count}} 档",
     "{{count}} Uptime Kuma groups will be removed from the list.": "将从列表中移除 {{count}} 个 Uptime Kuma 分组。",
     "{{count}} Uptime Kuma groups will be removed from the list.": "将从列表中移除 {{count}} 个 Uptime Kuma 分组。",
@@ -225,6 +226,7 @@
     "Add ratio override": "添加倍率覆盖",
     "Add ratio override": "添加倍率覆盖",
     "Add route": "添加路由",
     "Add route": "添加路由",
     "Add Row": "添加行",
     "Add Row": "添加行",
+    "Add Role": "添加角色",
     "Add Rule": "添加规则",
     "Add Rule": "添加规则",
     "Add rule group": "新增规则组",
     "Add rule group": "新增规则组",
     "Add rules for a user group": "为用户分组添加规则",
     "Add rules for a user group": "为用户分组添加规则",
@@ -350,6 +352,7 @@
     "Allow upstream callbacks": "允许上游回调",
     "Allow upstream callbacks": "允许上游回调",
     "Allow users to check in daily for random quota rewards": "允许用户每日签到获取随机额度奖励",
     "Allow users to check in daily for random quota rewards": "允许用户每日签到获取随机额度奖励",
     "Allow users to enter promo codes": "允许用户输入促销代码",
     "Allow users to enter promo codes": "允许用户输入促销代码",
+    "Allow User Registration": "允许用户注册",
     "Allow users to log in with password": "允许用户使用密码登录",
     "Allow users to log in with password": "允许用户使用密码登录",
     "Allow users to register and sign in with Passkey (WebAuthn)": "允许用户使用通行密钥 (WebAuthn) 注册和登录",
     "Allow users to register and sign in with Passkey (WebAuthn)": "允许用户使用通行密钥 (WebAuthn) 注册和登录",
     "Allow users to sign in with Discord": "允许用户使用 Discord 登录",
     "Allow users to sign in with Discord": "允许用户使用 Discord 登录",
@@ -1037,6 +1040,7 @@
     "Confirm password": "确认密码",
     "Confirm password": "确认密码",
     "Confirm Payment": "确认付款",
     "Confirm Payment": "确认付款",
     "Confirm Selection": "确认选择",
     "Confirm Selection": "确认选择",
+    "Configure default resource limits": "配置默认资源限制",
     "Confirm settings and finish setup": "确认设置并完成安装",
     "Confirm settings and finish setup": "确认设置并完成安装",
     "confirm that I bear legal responsibility arising from deployment": "确认承担因部署",
     "confirm that I bear legal responsibility arising from deployment": "确认承担因部署",
     "Confirm Unbind": "确认解绑",
     "Confirm Unbind": "确认解绑",
@@ -1294,6 +1298,8 @@
     "Default Collapse Sidebar": "默认折叠侧边栏",
     "Default Collapse Sidebar": "默认折叠侧边栏",
     "Default consumption chart": "默认消耗分布图",
     "Default consumption chart": "默认消耗分布图",
     "Default Max Tokens": "默认最大 Token 数",
     "Default Max Tokens": "默认最大 Token 数",
+    "Default Quota for New Users": "新用户默认配额",
+    "Default Role Name": "默认角色名称",
     "Default model call chart": "默认模型调用图",
     "Default model call chart": "默认模型调用图",
     "Default range": "默认范围",
     "Default range": "默认范围",
     "Default Responses API version, if empty, will use the API version above": "默认响应 API 版本,如果为空,将使用上面的 API 版本",
     "Default Responses API version, if empty, will use the API version above": "默认响应 API 版本,如果为空,将使用上面的 API 版本",
@@ -1541,6 +1547,7 @@
     "e.g., v2.1": "例如,v2.1",
     "e.g., v2.1": "例如,v2.1",
     "Each backup code can only be used once.": "每个备份代码只能使用一次。",
     "Each backup code can only be used once.": "每个备份代码只能使用一次。",
     "Each item must be an object with a single key-value pair.": "每个条目必须是包含单个键值对的对象。",
     "Each item must be an object with a single key-value pair.": "每个条目必须是包含单个键值对的对象。",
+    "e.g. Common User": "例如:普通用户",
     "Each item must have exactly one key-value pair.": "每个条目必须恰好包含一个键值对。",
     "Each item must have exactly one key-value pair.": "每个条目必须恰好包含一个键值对。",
     "Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "每行代表一个关键词。留空以禁用列表,但保留开关状态。",
     "Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "每行代表一个关键词。留空以禁用列表,但保留开关状态。",
     "Each matrix cell is one rule: users of this row group pay this ratio when billed as this column group. In JSON the row is the outer key and the column is the inner key.": "矩阵的每个单元格是一条规则:该行用户分组的用户按该列分组计费时使用此倍率。在 JSON 中行是外层键,列是内层键。",
     "Each matrix cell is one rule: users of this row group pay this ratio when billed as this column group. In JSON the row is the outer key and the column is the inner key.": "矩阵的每个单元格是一条规则:该行用户分组的用户按该列分组计费时使用此倍率。在 JSON 中行是外层键,列是内层键。",
@@ -1628,6 +1635,7 @@
     "Enable SSL/TLS": "启用 SSL/TLS",
     "Enable SSL/TLS": "启用 SSL/TLS",
     "Enable SSRF Protection": "启用 SSRF 保护",
     "Enable SSRF Protection": "启用 SSRF 保护",
     "Enable STARTTLS": "启用 STARTTLS",
     "Enable STARTTLS": "启用 STARTTLS",
+    "Enable self-service sign-up for new users.": "允许新用户自助注册。",
     "Enable streaming mode for the test request.": "为测试请求启用流式模式。",
     "Enable streaming mode for the test request.": "为测试请求启用流式模式。",
     "Enable Telegram OAuth": "启用 Telegram OAuth",
     "Enable Telegram OAuth": "启用 Telegram OAuth",
     "Enable test mode for Creem payments": "启用 Creem 支付测试模式",
     "Enable test mode for Creem payments": "启用 Creem 支付测试模式",
@@ -1644,6 +1652,7 @@
     "Enabled all channels with tag: {{tag}}": "已启用标签「{{tag}}」下的所有渠道",
     "Enabled all channels with tag: {{tag}}": "已启用标签「{{tag}}」下的所有渠道",
     "Enabled channels with tag {{tag}}": "启用标签为 {{tag}} 的渠道",
     "Enabled channels with tag {{tag}}": "启用标签为 {{tag}} 的渠道",
     "Enabled Status": "启用状态",
     "Enabled Status": "启用状态",
+    "Enabled Modules": "已启用模块",
     "Enabling...": "正在启用...",
     "Enabling...": "正在启用...",
     "Encourages introducing new topics": "鼓励引入新话题",
     "Encourages introducing new topics": "鼓励引入新话题",
     "Encourages new topics": "鼓励讨论新话题",
     "Encourages new topics": "鼓励讨论新话题",
@@ -2295,6 +2304,7 @@
     "How to reset my quota?": "如何重置我的配额?",
     "How to reset my quota?": "如何重置我的配额?",
     "How to select keys: random or sequential polling": "密钥选择方式:随机或顺序轮询",
     "How to select keys: random or sequential polling": "密钥选择方式:随机或顺序轮询",
     "How will you use the platform?": "您将如何使用本平台?",
     "How will you use the platform?": "您将如何使用本平台?",
+    "How many quota units equal one unit of display currency.": "多少配额单位等于一个显示货币单位。",
     "https://api.day.app/yourkey/{{title}}/{{content}}": "https://api.day.app/yourkey/{{title}}/{{content}}",
     "https://api.day.app/yourkey/{{title}}/{{content}}": "https://api.day.app/yourkey/{{title}}/{{content}}",
     "https://api.example.com": "https://api.example.com",
     "https://api.example.com": "https://api.example.com",
     "https://ark.ap-southeast.bytepluses.com": "https://ark.ap-southeast.bytepluses.com",
     "https://ark.ap-southeast.bytepluses.com": "https://ark.ap-southeast.bytepluses.com",
@@ -2677,6 +2687,7 @@
     "Memory Threshold (%)": "内存阈值 (%)",
     "Memory Threshold (%)": "内存阈值 (%)",
     "Merchant ID": "商户 ID",
     "Merchant ID": "商户 ID",
     "Merchant ID is required": "商户 ID 为必填项",
     "Merchant ID is required": "商户 ID 为必填项",
+    "Menu Modules": "菜单模块",
     "Merge into Other": "合并为其他",
     "Merge into Other": "合并为其他",
     "Message Priority": "消息优先级",
     "Message Priority": "消息优先级",
     "Metadata": "元信息",
     "Metadata": "元信息",
@@ -2888,6 +2899,7 @@
     "New password": "新密码",
     "New password": "新密码",
     "New Password": "新密码",
     "New Password": "新密码",
     "New password must be different from current password": "新密码必须与当前密码不同",
     "New password must be different from current password": "新密码必须与当前密码不同",
+    "New Role": "新角色",
     "New User Quota": "新用户配额",
     "New User Quota": "新用户配额",
     "New version available: {{version}}": "有新版本可用:{{version}}",
     "New version available: {{version}}": "有新版本可用:{{version}}",
     "NewAPI": "NewAPI",
     "NewAPI": "NewAPI",
@@ -3498,6 +3510,7 @@
     "Powerful API Management Platform": "强大的 API 管理平台",
     "Powerful API Management Platform": "强大的 API 管理平台",
     "Pre-Consume for Free Models": "免费模型预消耗",
     "Pre-Consume for Free Models": "免费模型预消耗",
     "Pre-consumed": "预扣费",
     "Pre-consumed": "预扣费",
+    "Pre-consumed Quota": "预扣配额",
     "Pre-Consumed Quota": "预消耗配额",
     "Pre-Consumed Quota": "预消耗配额",
     "Preference saved as {{pref}}, but no active subscription. Wallet will be used automatically.": "已保存偏好为{{pref}},当前无生效订阅,将自动使用钱包",
     "Preference saved as {{pref}}, but no active subscription. Wallet will be used automatically.": "已保存偏好为{{pref}},当前无生效订阅,将自动使用钱包",
     "Preferences": "偏好设置",
     "Preferences": "偏好设置",
@@ -3649,8 +3662,10 @@
     "Quota": "额度",
     "Quota": "额度",
     "Quota ({{currency}})": "额度 ({{currency}})",
     "Quota ({{currency}})": "额度 ({{currency}})",
     "Quota adjusted successfully": "调整额度成功",
     "Quota adjusted successfully": "调整额度成功",
+    "Quota automatically granted to each new user upon registration.": "每位新用户注册时自动获得的配额。",
     "Quota clamped": "额度已钳制",
     "Quota clamped": "额度已钳制",
     "Quota consumed before charging users": "向用户收费前消耗的配额",
     "Quota consumed before charging users": "向用户收费前消耗的配额",
+    "Quota deducted before a request is processed to prevent overuse.": "在请求处理前扣除的配额,以防止超额使用。",
     "Quota Distribution": "消耗分布",
     "Quota Distribution": "消耗分布",
     "Quota given to invited users": "授予被邀请用户的配额",
     "Quota given to invited users": "授予被邀请用户的配额",
     "Quota given to invited users ({{formattedQuota}})": "授予被邀请用户的配额({{formattedQuota}})",
     "Quota given to invited users ({{formattedQuota}})": "授予被邀请用户的配额({{formattedQuota}})",
@@ -3660,6 +3675,7 @@
     "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": "每单位配额",
+    "Quota per Unit": "每单位配额",
     "Quota reminder (tokens)": "配额提醒(token)",
     "Quota reminder (tokens)": "配额提醒(token)",
     "Quota Reset": "额度重置",
     "Quota Reset": "额度重置",
     "Quota saturation protection triggered": "额度饱和保护已触发",
     "Quota saturation protection triggered": "额度饱和保护已触发",
@@ -3860,6 +3876,7 @@
     "Requests will be forwarded to this worker. Trailing slashes are removed automatically.": "请求将被转发到此 Worker。末尾的斜杠会自动移除。",
     "Requests will be forwarded to this worker. Trailing slashes are removed automatically.": "请求将被转发到此 Worker。末尾的斜杠会自动移除。",
     "Requests:": "请求:",
     "Requests:": "请求:",
     "Require email verification for new accounts": "要求新账户验证邮箱",
     "Require email verification for new accounts": "要求新账户验证邮箱",
+    "Require email verification when users register.": "用户注册时要求验证邮箱。",
     "Require job success before follow-up actions": "在后续操作前要求任务成功",
     "Require job success before follow-up actions": "在后续操作前要求任务成功",
     "Require login to view models": "要求登录才能查看模型",
     "Require login to view models": "要求登录才能查看模型",
     "Require login to view rankings": "要求登录才能查看排行榜",
     "Require login to view rankings": "要求登录才能查看排行榜",
@@ -4168,6 +4185,7 @@
     "Select time granularity": "选择时间粒度",
     "Select time granularity": "选择时间粒度",
     "Select type": "选择类型",
     "Select type": "选择类型",
     "Select vendor": "选择供应商",
     "Select vendor": "选择供应商",
+    "Select which modules are visible to users.": "选择对用户可见的模块。",
     "Select user groups and models for this account": "选择此账户的用户分组和模型",
     "Select user groups and models for this account": "选择此账户的用户分组和模型",
     "Selectable groups": "可选分组",
     "Selectable groups": "可选分组",
     "selected": "已选择",
     "selected": "已选择",
@@ -4219,6 +4237,7 @@
     "Set the language used across the interface": "设置界面显示语言",
     "Set the language used across the interface": "设置界面显示语言",
     "Set the user's role (cannot be Root)": "设置用户角色(不能是 Root)",
     "Set the user's role (cannot be Root)": "设置用户角色(不能是 Root)",
     "Set these values in the provider application before enabling login.": "启用登录前,请先在提供商应用中设置这些值。",
     "Set these values in the provider application before enabling login.": "启用登录前,请先在提供商应用中设置这些值。",
+    "Set up system default roles": "设置系统默认角色",
     "Setting Key": "配置项",
     "Setting Key": "配置项",
     "Setting saved": "设置已保存",
     "Setting saved": "设置已保存",
     "Setting up 2FA...": "正在设置 2FA...",
     "Setting up 2FA...": "正在设置 2FA...",
@@ -4473,6 +4492,8 @@
     "System settings": "系统设置",
     "System settings": "系统设置",
     "System Settings": "系统设置",
     "System Settings": "系统设置",
     "System setup wizard": "系统设置向导",
     "System setup wizard": "系统设置向导",
+    "System resource configuration": "系统资源配置",
+    "System role configuration": "系统角色配置",
     "System task records": "系统任务记录",
     "System task records": "系统任务记录",
     "System Tasks": "系统任务",
     "System Tasks": "系统任务",
     "System Version": "系统版本",
     "System Version": "系统版本",
@@ -4565,6 +4586,7 @@
     "The requested chat preset does not exist or has been removed.": "请求的聊天预设不存在或已被删除。",
     "The requested chat preset does not exist or has been removed.": "请求的聊天预设不存在或已被删除。",
     "The reset request stays disabled until a credit is available.": "没有可用次数时,重置请求会保持禁用。",
     "The reset request stays disabled until a credit is available.": "没有可用次数时,重置请求会保持禁用。",
     "The setup wizard will use this database during initialization.": "设置向导将在初始化过程中使用此数据库。",
     "The setup wizard will use this database during initialization.": "设置向导将在初始化过程中使用此数据库。",
+    "The role automatically assigned to newly registered users.": "自动分配给新注册用户的角色。",
     "The site is not available at the moment.": "该站点目前不可用。",
     "The site is not available at the moment.": "该站点目前不可用。",
     "The slug is appended to the URL:": "别名将附加到 URL:",
     "The slug is appended to the URL:": "别名将附加到 URL:",
     "The sync will fetch missing models and vendors from the selected source. Existing records are updated only when you approve conflicts.": "同步将从选定的源获取缺失的模型和供应商。仅在您批准冲突时才会更新现有记录。",
     "The sync will fetch missing models and vendors from the selected source. Existing records are updated only when you approve conflicts.": "同步将从选定的源获取缺失的模型和供应商。仅在您批准冲突时才会更新现有记录。",