Przeglądaj źródła

feat:分组管理前端页面修改,日志页面修改

韩洋 1 miesiąc temu
rodzic
commit
305a781659

+ 1 - 0
classic/.eslintcache

@@ -0,0 +1 @@
+[{"D:\\workspace\\new_project\\Linwit-ai-token-platform\\classic\\src\\pages\\Setting\\Ratio\\GroupRatioSettings.jsx":"1"},{"size":32743,"mtime":1785751371348,"results":"2","hashOfConfig":"3"},{"filePath":"4","messages":"5","suppressedMessages":"6","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"176cglu","D:\\workspace\\new_project\\Linwit-ai-token-platform\\classic\\src\\pages\\Setting\\Ratio\\GroupRatioSettings.jsx",[],[]]

+ 60 - 88
default/src/features/system-settings/models/group-ratio-form.tsx

@@ -16,8 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 For commercial licensing, please contact support@quantumnous.com
 */
-import { Code2, Eye, HelpCircle } from 'lucide-react'
-import { memo, useCallback, useMemo, useState, type ReactNode } from 'react'
+import React, { memo, useCallback, useMemo } from 'react'
 import type { UseFormReturn } from 'react-hook-form'
 import { useTranslation } from 'react-i18next'
 
@@ -57,13 +56,13 @@ import {
   SettingsSwitchContent,
   SettingsSwitchItem,
 } from '../components/settings-form-layout'
-import { SettingsPageActionsPortal } from '../components/settings-page-context'
 import { safeJsonParse } from '../utils/json-parser'
 import { GroupRatioVisualEditor } from './group-ratio-visual-editor'
 import { GroupSpecialUsableRulesEditor } from './group-special-usable-editor'
 
 const showGroupSpecialUsableRules = false
 const showDefaultUseAutoGroup = false
+const showGroupJsonEditor = false
 
 type GroupFormValues = {
   GroupRatio: string
@@ -87,8 +86,6 @@ export const GroupRatioForm = memo(function GroupRatioForm({
   isSaving,
 }: GroupRatioFormProps) {
   const { t } = useTranslation()
-  const [editMode, setEditMode] = useState<'visual' | 'json'>('visual')
-  const [guideOpen, setGuideOpen] = useState(false)
 
   const handleFieldChange = useCallback(
     (field: keyof GroupFormValues, value: string) => {
@@ -100,10 +97,6 @@ export const GroupRatioForm = memo(function GroupRatioForm({
     [form]
   )
 
-  const toggleEditMode = useCallback(() => {
-    setEditMode((prev) => (prev === 'visual' ? 'json' : 'visual'))
-  }, [])
-
   const watchedGroupRatio = form.watch('GroupRatio')
   const watchedUserUsableGroups = form.watch('UserUsableGroups')
   const watchedTopupGroupRatio = form.watch('TopupGroupRatio')
@@ -131,89 +124,68 @@ export const GroupRatioForm = memo(function GroupRatioForm({
 
   return (
     <div className='space-y-6'>
-      <div className='flex flex-wrap justify-end gap-2'>
-        <Button variant='outline' size='sm' onClick={() => setGuideOpen(true)}>
-          <HelpCircle className='mr-2 h-4 w-4' />
-          {t('Usage guide')}
-        </Button>
-        <Button variant='outline' size='sm' onClick={toggleEditMode}>
-          {editMode === 'visual' ? (
-            <>
-              <Code2 className='mr-2 h-4 w-4' />
-              {t('Switch to JSON')}
-            </>
-          ) : (
-            <>
-              <Eye className='mr-2 h-4 w-4' />
-              {t('Switch to Visual')}
-            </>
-          )}
-        </Button>
-      </div>
-
-      <GroupPricingGuide open={guideOpen} onOpenChange={setGuideOpen} />
+      <GroupPricingGuide open={false} onOpenChange={() => undefined} />
 
       <Form {...form}>
-        <SettingsPageActionsPortal>
-          <Button
-            type='button'
-            size='sm'
-            onClick={form.handleSubmit(onSave)}
-            disabled={isSaving}
-          >
-            {isSaving ? t('Saving...') : t('Save group ratios')}
-          </Button>
-        </SettingsPageActionsPortal>
-        {editMode === 'visual' ? (
-          <div className='space-y-6'>
-            <GroupRatioVisualEditor
-              groupRatio={form.watch('GroupRatio')}
-              topupGroupRatio={form.watch('TopupGroupRatio')}
-              userUsableGroups={form.watch('UserUsableGroups')}
-              groupGroupRatio={form.watch('GroupGroupRatio')}
-              autoGroups={form.watch('AutoGroups')}
-              groupSpecialUsableGroup={form.watch('GroupSpecialUsableGroup')}
-              onChange={(field, value) =>
-                handleFieldChange(field as keyof GroupFormValues, value)
+        <div className='space-y-6'>
+          <GroupRatioVisualEditor
+            groupRatio={form.watch('GroupRatio')}
+            topupGroupRatio={form.watch('TopupGroupRatio')}
+            userUsableGroups={form.watch('UserUsableGroups')}
+            groupGroupRatio={form.watch('GroupGroupRatio')}
+            autoGroups={form.watch('AutoGroups')}
+            groupSpecialUsableGroup={form.watch('GroupSpecialUsableGroup')}
+            onChange={(field, value) =>
+              handleFieldChange(field as keyof GroupFormValues, value)
+            }
+            actionSlot={
+              <Button
+                type='button'
+                size='sm'
+                onClick={form.handleSubmit(onSave)}
+                disabled={isSaving}
+              >
+                {isSaving ? t('Saving...') : t('Save')}
+              </Button>
+            }
+          />
+
+          {showGroupSpecialUsableRules && (
+            <GroupSpecialUsableRulesEditor
+              value={form.watch('GroupSpecialUsableGroup')}
+              groupOptions={groupNames}
+              onChange={(value) =>
+                handleFieldChange('GroupSpecialUsableGroup', value)
               }
             />
+          )}
 
-            {showGroupSpecialUsableRules && (
-              <GroupSpecialUsableRulesEditor
-                value={form.watch('GroupSpecialUsableGroup')}
-                groupOptions={groupNames}
-                onChange={(value) =>
-                  handleFieldChange('GroupSpecialUsableGroup', value)
-                }
-              />
-            )}
-
-            {showDefaultUseAutoGroup && (
-              <FormField
-                control={form.control}
-                name='DefaultUseAutoGroup'
-                render={({ field }) => (
-                  <SettingsSwitchItem>
-                    <SettingsSwitchContent>
-                      <FormLabel>{t('Default to auto groups')}</FormLabel>
-                      <FormDescription>
-                        {t(
-                          'When enabled, newly created tokens start in the first auto group.'
-                        )}
-                      </FormDescription>
-                    </SettingsSwitchContent>
-                    <FormControl>
-                      <Switch
-                        checked={field.value}
-                        onCheckedChange={field.onChange}
-                      />
-                    </FormControl>
-                  </SettingsSwitchItem>
-                )}
-              />
-            )}
-          </div>
-        ) : (
+          {showDefaultUseAutoGroup && (
+            <FormField
+              control={form.control}
+              name='DefaultUseAutoGroup'
+              render={({ field }) => (
+                <SettingsSwitchItem>
+                  <SettingsSwitchContent>
+                    <FormLabel>{t('Default to auto groups')}</FormLabel>
+                    <FormDescription>
+                      {t(
+                        'When enabled, newly created tokens start in the first auto group.'
+                      )}
+                    </FormDescription>
+                  </SettingsSwitchContent>
+                  <FormControl>
+                    <Switch
+                      checked={field.value}
+                      onCheckedChange={field.onChange}
+                    />
+                  </FormControl>
+                </SettingsSwitchItem>
+              )}
+            />
+          )}
+        </div>
+        {showGroupJsonEditor && (
           <SettingsForm onSubmit={form.handleSubmit(onSave)}>
             <FormField
               control={form.control}
@@ -383,7 +355,7 @@ function GuideStepRow({
   children,
 }: {
   chip: string
-  children: ReactNode
+  children: React.ReactNode
 }) {
   return (
     <div className='flex items-start gap-2.5 text-sm leading-6'>

+ 19 - 55
default/src/features/system-settings/models/group-ratio-visual-editor.tsx

@@ -24,7 +24,7 @@ import {
   Plus,
   Trash2,
 } from 'lucide-react'
-import { useState, useMemo, useEffect, useCallback, memo } from 'react'
+import React, { useState, useMemo, useEffect, useCallback, memo } from 'react'
 import { useTranslation } from 'react-i18next'
 
 import { StaticDataTable } from '@/components/data-table/static/static-data-table'
@@ -44,7 +44,6 @@ import {
   CardHeader,
   CardTitle,
 } from '@/components/ui/card'
-import { Checkbox } from '@/components/ui/checkbox'
 import {
   Collapsible,
   CollapsibleContent,
@@ -78,6 +77,7 @@ type GroupRatioVisualEditorProps = {
   autoGroups: string
   groupSpecialUsableGroup: string
   onChange: (field: string, value: string) => void
+  actionSlot?: React.ReactNode
 }
 
 type GroupPricingRow = {
@@ -260,6 +260,7 @@ export const GroupRatioVisualEditor = memo(function GroupRatioVisualEditor({
   autoGroups,
   groupSpecialUsableGroup,
   onChange,
+  actionSlot,
 }: GroupRatioVisualEditorProps) {
   const { t } = useTranslation()
   const [detailGroup, setDetailGroup] = useState<string | null>(null)
@@ -331,6 +332,7 @@ export const GroupRatioVisualEditor = memo(function GroupRatioVisualEditor({
         topupGroupRatio={topupGroupRatio}
         onChange={onChange}
         onShowDetail={setDetailGroup}
+        actionSlot={actionSlot}
       />
 
       {showGroupPricingAdvancedSections && (
@@ -425,6 +427,7 @@ type GroupPricingTableProps = {
   topupGroupRatio: string
   onChange: (field: string, value: string) => void
   onShowDetail: (name: string) => void
+  actionSlot?: React.ReactNode
 }
 
 function GroupPricingTable({
@@ -433,6 +436,7 @@ function GroupPricingTable({
   topupGroupRatio,
   onChange,
   onShowDetail,
+  actionSlot,
 }: GroupPricingTableProps) {
   const { t } = useTranslation()
   const [rows, setRows] = useState<GroupPricingRow[]>(() =>
@@ -527,16 +531,14 @@ function GroupPricingTable({
         <div className='flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between'>
           <div>
             <CardTitle>{t('Group Management')}</CardTitle>
-            <CardDescription>
-              {t(
-                'All group names live here. Ratio applies when calls are billed as this group; top-up ratio applies to users whose account is in this group.'
-              )}
-            </CardDescription>
           </div>
-          <Button onClick={addRow} size='sm' className='sm:self-start'>
-            <Plus className='mr-2 h-4 w-4' />
-            {t('Add group')}
-          </Button>
+          <div className='flex flex-wrap items-center justify-end gap-2 sm:self-start'>
+            {actionSlot}
+            <Button onClick={addRow} size='sm'>
+              <Plus className='mr-2 h-4 w-4' />
+              {t('Add group')}
+            </Button>
+          </div>
         </div>
       </CardHeader>
       <CardContent>
@@ -578,57 +580,19 @@ function GroupPricingTable({
                 ),
               },
               {
-                id: 'topup-ratio',
-                header: t('Top-up ratio'),
-                className: 'w-28',
+                id: 'description',
+                header: t('Description'),
+                className: 'min-w-56',
                 cell: (row) => (
                   <Input
-                    type='number'
-                    min={0}
-                    step={0.1}
-                    value={row.topupRatio}
-                    placeholder={t('Not set')}
+                    value={row.description}
+                    placeholder={t('Group description')}
                     onChange={(event) =>
-                      updateRow(row._id, 'topupRatio', event.target.value)
+                      updateRow(row._id, 'description', event.target.value)
                     }
                   />
                 ),
               },
-              {
-                id: 'selectable',
-                header: t('User selectable'),
-                className: 'w-28 text-center',
-                cell: (row) => (
-                  <div className='flex justify-center'>
-                    <Checkbox
-                      checked={row.selectable}
-                      onCheckedChange={(checked) =>
-                        updateRow(row._id, 'selectable', checked === true)
-                      }
-                      aria-label={t('User selectable')}
-                    />
-                  </div>
-                ),
-              },
-              {
-                id: 'description',
-                header: t('Description'),
-                className: 'min-w-56',
-                cell: (row) =>
-                  row.selectable ? (
-                    <Input
-                      value={row.description}
-                      placeholder={t('Group description')}
-                      onChange={(event) =>
-                        updateRow(row._id, 'description', event.target.value)
-                      }
-                    />
-                  ) : (
-                    <span className='text-muted-foreground px-3 text-sm'>
-                      -
-                    </span>
-                  ),
-              },
               {
                 id: 'actions',
                 header: t('Actions'),

+ 12 - 1
default/src/features/usage-logs/api.ts

@@ -18,7 +18,6 @@ For commercial licensing, please contact support@quantumnous.com
 */
 import { api } from '@/lib/api'
 
-import { buildQueryParams } from './lib/utils'
 import type {
   GetLogsParams,
   GetLogsResponse,
@@ -29,6 +28,18 @@ import type {
   UserInfo,
 } from './types'
 
+function buildQueryParams(params: Record<string, unknown>): URLSearchParams {
+  const queryParams = new URLSearchParams()
+
+  Object.entries(params).forEach(([key, value]) => {
+    if (value !== undefined && value !== null && value !== '') {
+      queryParams.append(key, String(value))
+    }
+  })
+
+  return queryParams
+}
+
 // ============================================================================
 // Generic API Helpers
 // ============================================================================

+ 133 - 86
default/src/features/usage-logs/components/common-logs-filter-bar.tsx

@@ -38,7 +38,6 @@ import {
   TooltipTrigger,
 } from '@/components/ui/tooltip'
 
-import { LOG_TYPE_ALL_VALUE, LOG_TYPE_FILTERS } from '../constants'
 import { buildSearchParams } from '../lib/filter'
 import { getDefaultTimeRange } from '../lib/utils'
 import type { CommonLogFilters } from '../types'
@@ -49,37 +48,29 @@ import {
   LogsFilterInput,
   LogsFilterToolbar,
 } from './logs-filter-toolbar'
+import {
+  LOG_TYPE_ALL_VALUE,
+  LOG_TYPE_ENUM,
+  LOG_TYPE_FILTERS,
+} from '../constants'
 import { useLogsViewScope, useUsageLogsContext } from './usage-logs-provider'
 
 const route = getRouteApi('/_authenticated/usage-logs/$section')
-
-type LogTypeValue = (typeof LOG_TYPE_FILTERS)[number]['value']
-const logTypeValueSet = new Set<string>(
-  LOG_TYPE_FILTERS.map((type) => type.value)
+const auditLogTypeItems = LOG_TYPE_FILTERS.filter(
+  (type) => type.value !== String(LOG_TYPE_ENUM.CONSUME)
 )
+const auditLogTypeValues = new Set(auditLogTypeItems.map((type) => type.value))
 
 type CommonLogDraft = {
   sourceKey: string
   filters: CommonLogFilters
-  logType: LogTypeValue
-}
-
-function isLogTypeValue(value: string): value is LogTypeValue {
-  return logTypeValueSet.has(value)
-}
-
-function getLogTypeValue(value: unknown): LogTypeValue {
-  return Array.isArray(value) &&
-    value.length === 1 &&
-    typeof value[0] === 'string' &&
-    isLogTypeValue(value[0])
-    ? value[0]
-    : LOG_TYPE_ALL_VALUE
 }
 
 function buildSearchSourceKey(values: {
   startTime?: unknown
   endTime?: unknown
+  consumeType?: unknown
+  type?: unknown
   channel?: unknown
   model?: unknown
   token?: unknown
@@ -87,11 +78,12 @@ function buildSearchSourceKey(values: {
   username?: unknown
   requestId?: unknown
   upstreamRequestId?: unknown
-  type?: unknown
 }) {
   return [
     values.startTime,
     values.endTime,
+    values.consumeType,
+    Array.isArray(values.type) ? values.type.join(',') : values.type,
     values.channel,
     values.model,
     values.token,
@@ -99,12 +91,19 @@ function buildSearchSourceKey(values: {
     values.username,
     values.requestId,
     values.upstreamRequestId,
-    Array.isArray(values.type) ? values.type.join(',') : values.type,
   ]
     .map((value) => String(value ?? ''))
     .join('\u001f')
 }
 
+function getAuditLogType(value: unknown): string {
+  const candidate = Array.isArray(value) ? value[0] : value
+  if (typeof candidate === 'string' && auditLogTypeValues.has(candidate)) {
+    return candidate
+  }
+  return LOG_TYPE_ALL_VALUE
+}
+
 interface CommonLogsFilterBarProps<TData> {
   table: Table<TData>
 }
@@ -125,6 +124,8 @@ export function CommonLogsFilterBar<TData>(
     const sourceValues = {
       startTime: searchParams.startTime,
       endTime: searchParams.endTime,
+      consumeType: searchParams.consumeType,
+      type: searchParams.type,
       channel: searchParams.channel,
       model: searchParams.model,
       token: searchParams.token,
@@ -132,13 +133,14 @@ export function CommonLogsFilterBar<TData>(
       username: searchParams.username,
       requestId: searchParams.requestId,
       upstreamRequestId: searchParams.upstreamRequestId,
-      type: searchParams.type,
     }
     const filters: CommonLogFilters = {
       startTime: searchParams.startTime
         ? new Date(searchParams.startTime)
         : start,
       endTime: searchParams.endTime ? new Date(searchParams.endTime) : end,
+      consumeType: searchParams.consumeType || 'all',
+      logType: getAuditLogType(searchParams.type),
       channel: searchParams.channel || undefined,
       model: searchParams.model || undefined,
       token: searchParams.token || undefined,
@@ -150,11 +152,12 @@ export function CommonLogsFilterBar<TData>(
     return {
       sourceKey: buildSearchSourceKey(sourceValues),
       filters,
-      logType: getLogTypeValue(searchParams.type),
     }
   }, [
     searchParams.startTime,
     searchParams.endTime,
+    searchParams.consumeType,
+    searchParams.type,
     searchParams.channel,
     searchParams.model,
     searchParams.token,
@@ -162,13 +165,13 @@ export function CommonLogsFilterBar<TData>(
     searchParams.username,
     searchParams.requestId,
     searchParams.upstreamRequestId,
-    searchParams.type,
   ])
   const [draft, setDraft] = useState<CommonLogDraft>(() => searchState)
   const activeDraft =
     draft.sourceKey === searchState.sourceKey ? draft : searchState
   const filters = activeDraft.filters
-  const logType = activeDraft.logType
+  const params = route.useParams()
+  const section = params.section === 'audit' ? 'audit' : 'common'
 
   const handleChange = useCallback(
     (field: keyof CommonLogFilters, value: Date | string | undefined) => {
@@ -178,7 +181,6 @@ export function CommonLogsFilterBar<TData>(
         return {
           sourceKey: searchState.sourceKey,
           filters: { ...base.filters, [field]: value },
-          logType: base.logType,
         }
       })
     },
@@ -189,42 +191,48 @@ export function CommonLogsFilterBar<TData>(
     const filterParams = buildSearchParams(filters, 'common')
     navigate({
       to: '/usage-logs/$section',
-      params: { section: 'common' },
+      params: { section },
       search: {
         ...filterParams,
-        type: [logType],
+        ...(section === 'audit' && {
+          type: [filters.logType || LOG_TYPE_ALL_VALUE],
+        }),
         page: 1,
       },
     })
     queryClient.invalidateQueries({ queryKey: ['logs'] })
     queryClient.invalidateQueries({ queryKey: ['usage-logs-stats'] })
-  }, [filters, logType, navigate, queryClient])
+  }, [filters, navigate, queryClient, section])
 
   const handleReset = useCallback(() => {
     const { start, end } = getDefaultTimeRange()
-    const resetFilters: CommonLogFilters = { startTime: start, endTime: end }
+    const resetFilters: CommonLogFilters = {
+      startTime: start,
+      endTime: end,
+      consumeType: 'all',
+      logType: LOG_TYPE_ALL_VALUE,
+    }
     const resetSearch = {
-      type: [LOG_TYPE_ALL_VALUE],
       startTime: start.getTime(),
       endTime: end.getTime(),
     }
     setDraft({
       sourceKey: buildSearchSourceKey(resetSearch),
       filters: resetFilters,
-      logType: LOG_TYPE_ALL_VALUE,
     })
 
     navigate({
       to: '/usage-logs/$section',
-      params: { section: 'common' },
+      params: { section },
       search: {
         page: 1,
         ...resetSearch,
+        ...(section === 'audit' && { type: [LOG_TYPE_ALL_VALUE] }),
       },
     })
     queryClient.invalidateQueries({ queryKey: ['logs'] })
     queryClient.invalidateQueries({ queryKey: ['usage-logs-stats'] })
-  }, [navigate, queryClient])
+  }, [navigate, queryClient, section])
 
   const handleKeyDown = useCallback(
     (e: React.KeyboardEvent) => {
@@ -233,6 +241,8 @@ export function CommonLogsFilterBar<TData>(
     [handleApply]
   )
 
+  const showConsumeTypeFilter = section === 'common'
+  const showAuditLogTypeFilter = section === 'audit'
   const hasExpandedFilters =
     !!filters.token ||
     !!filters.username ||
@@ -240,9 +250,13 @@ export function CommonLogsFilterBar<TData>(
     !!filters.requestId ||
     !!filters.upstreamRequestId
 
-  const hasTypeFilter = logType !== LOG_TYPE_ALL_VALUE
   const hasAdditionalFilters =
-    !!filters.model || !!filters.group || hasTypeFilter || hasExpandedFilters
+    (showConsumeTypeFilter && (filters.consumeType ?? 'all') !== 'all') ||
+    (section === 'audit' &&
+      (filters.logType || LOG_TYPE_ALL_VALUE) !== LOG_TYPE_ALL_VALUE) ||
+    !!filters.model ||
+    !!filters.group ||
+    hasExpandedFilters
 
   const expandedFilterCount = [
     filters.token,
@@ -252,17 +266,19 @@ export function CommonLogsFilterBar<TData>(
     filters.upstreamRequestId,
   ].filter(Boolean).length
   const sensitiveType = sensitiveVisible ? 'text' : 'password'
-  const logTypeItems = useMemo(
-    () =>
-      LOG_TYPE_FILTERS.map((type) => ({
-        value: type.value,
-        label: t(type.label),
-      })),
-    [t]
-  )
-  const logTypeLabel =
-    logTypeItems.find((type) => type.value === logType)?.label ?? t('All Types')
-
+  const consumeTypeItems = [
+    { value: 'all', label: t('All') },
+    { value: 'platform', label: t('Platform') },
+    { value: 'user', label: t('User') },
+  ]
+  const consumeTypeLabel =
+    consumeTypeItems.find((item) => item.value === filters.consumeType)?.label ??
+    t('All')
+  const auditLogTypeLabel =
+    t(
+      auditLogTypeItems.find((item) => item.value === filters.logType)?.label ??
+        'All Types'
+    )
   const statsBar = (
     <div className='flex flex-wrap items-center gap-2'>
       <CommonLogsStats />
@@ -301,6 +317,63 @@ export function CommonLogsFilterBar<TData>(
       />
     </LogsFilterField>
   )
+  const consumeTypeFilter = (
+    <LogsFilterField>
+      <Select
+        items={consumeTypeItems}
+        value={filters.consumeType || 'all'}
+        onValueChange={(value) => {
+          handleChange(
+            'consumeType',
+            value === 'platform' || value === 'user' ? value : 'all'
+          )
+        }}
+      >
+        <SelectTrigger>
+          <SelectValue>{consumeTypeLabel}</SelectValue>
+        </SelectTrigger>
+        <SelectContent alignItemWithTrigger={false}>
+          <SelectGroup>
+            {consumeTypeItems.map((item) => (
+              <SelectItem key={item.value} value={item.value}>
+                {item.label}
+              </SelectItem>
+            ))}
+          </SelectGroup>
+        </SelectContent>
+      </Select>
+    </LogsFilterField>
+  )
+  const auditLogTypeFilter = (
+    <LogsFilterField>
+      <Select
+        items={auditLogTypeItems.map((item) => ({
+          value: item.value,
+          label: t(item.label),
+        }))}
+        value={filters.logType || LOG_TYPE_ALL_VALUE}
+        onValueChange={(value) => {
+          handleChange(
+            'logType',
+            auditLogTypeValues.has(value) ? value : LOG_TYPE_ALL_VALUE
+          )
+        }}
+      >
+        <SelectTrigger>
+          <SelectValue>{auditLogTypeLabel}</SelectValue>
+        </SelectTrigger>
+        <SelectContent alignItemWithTrigger={false}>
+          <SelectGroup>
+            {auditLogTypeItems.map((item) => (
+              <SelectItem key={item.value} value={item.value}>
+                {t(item.label)}
+              </SelectItem>
+            ))}
+          </SelectGroup>
+        </SelectContent>
+      </Select>
+    </LogsFilterField>
+  )
   const modelFilter = (
     <LogsFilterField>
       <LogsFilterInput
@@ -322,42 +395,6 @@ export function CommonLogsFilterBar<TData>(
       />
     </LogsFilterField>
   )
-  const typeFilter = (
-    <LogsFilterField>
-      <Select
-        items={logTypeItems}
-        value={logType}
-        onValueChange={(value) => {
-          const nextLogType =
-            value !== null && isLogTypeValue(value) ? value : LOG_TYPE_ALL_VALUE
-          setDraft((current) => {
-            const base =
-              current.sourceKey === searchState.sourceKey
-                ? current
-                : searchState
-            return {
-              sourceKey: searchState.sourceKey,
-              filters: base.filters,
-              logType: nextLogType,
-            }
-          })
-        }}
-      >
-        <SelectTrigger>
-          <SelectValue>{logTypeLabel}</SelectValue>
-        </SelectTrigger>
-        <SelectContent alignItemWithTrigger={false}>
-          <SelectGroup>
-            {LOG_TYPE_FILTERS.map((type) => (
-              <SelectItem key={type.value} value={type.value}>
-                {t(type.label)}
-              </SelectItem>
-            ))}
-          </SelectGroup>
-        </SelectContent>
-      </Select>
-    </LogsFilterField>
-  )
   const advancedFilters = (
     <>
       <LogsFilterField>
@@ -417,9 +454,10 @@ export function CommonLogsFilterBar<TData>(
       primaryFilters={
         <>
           {dateRangeFilter}
+          {showConsumeTypeFilter && consumeTypeFilter}
+          {showAuditLogTypeFilter && auditLogTypeFilter}
           {modelFilter}
           {groupFilter}
-          {typeFilter}
         </>
       }
       advancedFilters={advancedFilters}
@@ -428,12 +466,21 @@ export function CommonLogsFilterBar<TData>(
         <>
           {modelFilter}
           {groupFilter}
-          {typeFilter}
           {advancedFilters}
         </>
       }
       mobileFilterCount={
-        [filters.model, filters.group, hasTypeFilter].filter(Boolean).length +
+        [
+          showConsumeTypeFilter && filters.consumeType !== 'all'
+            ? filters.consumeType
+            : undefined,
+          showAuditLogTypeFilter &&
+          (filters.logType || LOG_TYPE_ALL_VALUE) !== LOG_TYPE_ALL_VALUE
+            ? filters.logType
+            : undefined,
+          filters.model,
+          filters.group,
+        ].filter(Boolean).length +
         expandedFilterCount
       }
       hasAdvancedActiveFilters={hasExpandedFilters}

+ 13 - 2
default/src/features/usage-logs/components/common-logs-stats.tsx

@@ -25,7 +25,7 @@ import { formatLogQuota } from '@/lib/format'
 import { cn } from '@/lib/utils'
 
 import { getLogStats, getUserLogStats } from '../api'
-import { DEFAULT_LOG_STATS } from '../constants'
+import { DEFAULT_LOG_STATS, LOG_TYPE_ENUM } from '../constants'
 import { buildApiParams } from '../lib/utils'
 import { useLogsViewScope, useUsageLogsContext } from './usage-logs-provider'
 
@@ -51,10 +51,11 @@ export function CommonLogsStats() {
   const { t } = useTranslation()
   const { isAdminView: isAdmin } = useLogsViewScope()
   const searchParams = route.useSearch()
+  const routeParams = route.useParams()
   const { sensitiveVisible } = useUsageLogsContext()
 
   const { data: stats, isLoading } = useQuery({
-    queryKey: ['usage-logs-stats', isAdmin, searchParams],
+    queryKey: ['usage-logs-stats', isAdmin, searchParams, routeParams.section],
     queryFn: async () => {
       const params = buildApiParams({
         page: 1,
@@ -63,6 +64,16 @@ export function CommonLogsStats() {
         columnFilters: [],
         isAdmin,
       })
+      params.type =
+        routeParams.section === 'audit'
+          ? (params.type ?? LOG_TYPE_ENUM.UNKNOWN)
+          : LOG_TYPE_ENUM.CONSUME
+      if (routeParams.section === 'audit') {
+        delete params.consume_type
+        if (params.type === LOG_TYPE_ENUM.CONSUME) {
+          params.type = LOG_TYPE_ENUM.UNKNOWN
+        }
+      }
 
       const result = isAdmin
         ? await getLogStats(params)

+ 3 - 1
default/src/features/usage-logs/components/usage-logs-mobile-card.tsx

@@ -503,7 +503,9 @@ export function UsageLogsMobileList<TData>({
               tintClass
             )}
           >
-            {logCategory === 'common' && <CommonLogsCard cells={cells} />}
+            {(logCategory === 'common' || logCategory === 'audit') && (
+              <CommonLogsCard cells={cells} />
+            )}
             {logCategory === 'task' && <TaskLogsCard cells={cells} />}
             {logCategory === 'drawing' && <DrawingLogsCard cells={cells} />}
           </div>

+ 15 - 7
default/src/features/usage-logs/components/usage-logs-table.tsx

@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
 */
 import { useQuery } from '@tanstack/react-query'
 import { getRouteApi } from '@tanstack/react-router'
-import { type ColumnDef } from '@tanstack/react-table'
+import type { ColumnDef } from '@tanstack/react-table'
 import { useTranslation } from 'react-i18next'
 import { toast } from 'sonner'
 
@@ -64,7 +64,10 @@ function getColumnVisibilityStorageKey(
 }
 
 function deserializeLogTypeFilter(value: unknown): unknown[] {
-  const values = Array.isArray(value) ? value : value ? [value] : []
+  if (Array.isArray(value)) {
+    return value.filter((item) => String(item) !== LOG_TYPE_ALL_VALUE)
+  }
+  const values = value ? [value] : []
   return values.filter((item) => String(item) !== LOG_TYPE_ALL_VALUE)
 }
 
@@ -174,7 +177,8 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
     ensurePageInRange,
   })
 
-  const isCommon = logCategory === 'common'
+  const usesCommonLogsLayout =
+    logCategory === 'common' || logCategory === 'audit'
 
   return (
     <DataTablePage
@@ -199,7 +203,7 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
         />
       }
       toolbar={
-        isCommon ? (
+        usesCommonLogsLayout ? (
           <CommonLogsFilterBar table={table} />
         ) : (
           <TaskLogsFilterBar table={table} logCategory={logCategory} />
@@ -210,8 +214,10 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
           | number
           | undefined
         let tintClass =
-          isCommon && logType != null ? (logTypeRowTint[logType] ?? '') : ''
-        if (isCommon && isAdmin) {
+          usesCommonLogsLayout && logType != null
+            ? (logTypeRowTint[logType] ?? '')
+            : ''
+        if (usesCommonLogsLayout && isAdmin) {
           const other = parseLogOther(
             ((row.original as Record<string, unknown>).other as string) ?? ''
           )
@@ -225,7 +231,9 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
             key={row.id}
             row={row}
             className={cn('transition-colors', tintClass)}
-            getColumnClassName={() => (isCommon ? 'py-2' : 'py-3.5')}
+            getColumnClassName={() =>
+              usesCommonLogsLayout ? 'py-2' : 'py-3.5'
+            }
           />
         )
       }}

+ 8 - 4
default/src/features/usage-logs/index.tsx

@@ -45,7 +45,10 @@ const TASK_LOG_SECTIONS = ['drawing', 'task'] as const
 
 const SECTION_META: Record<UsageLogsSectionId, { titleKey: string }> = {
   common: {
-    titleKey: 'Common Logs',
+    titleKey: 'Call Logs',
+  },
+  audit: {
+    titleKey: 'Audit Logs',
   },
   drawing: {
     titleKey: 'Drawing Logs',
@@ -117,10 +120,11 @@ function UsageLogsContent() {
     [setViewScope]
   )
 
-  const pageMeta =
-    activeCategory === 'common' ? SECTION_META.common : SECTION_META.task
+  const pageMeta = SECTION_META[activeCategory]
   const showTaskSwitcher =
-    activeCategory !== 'common' && visibleSections.length > 1
+    activeCategory !== 'common' &&
+    activeCategory !== 'audit' &&
+    visibleSections.length > 1
 
   return (
     <>

+ 1 - 0
default/src/features/usage-logs/lib/columns.ts

@@ -40,6 +40,7 @@ export function useColumnsByCategory(
   const taskColumns = useTaskLogsColumns(isAdmin)
 
   switch (logCategory) {
+    case 'audit':
     case 'common':
       return commonColumns
     case 'drawing':

+ 4 - 0
default/src/features/usage-logs/lib/filter.ts

@@ -50,6 +50,10 @@ export function buildSearchParams(
       const commonFilters = filters as CommonLogFilters
       return {
         ...baseParams,
+        ...(commonFilters.consumeType &&
+          commonFilters.consumeType !== 'all' && {
+            consumeType: commonFilters.consumeType,
+          }),
         ...(commonFilters.model && { model: commonFilters.model }),
         ...(commonFilters.token && { token: commonFilters.token }),
         ...(commonFilters.group && { group: commonFilters.group }),

+ 0 - 1
default/src/features/usage-logs/lib/index.ts

@@ -41,7 +41,6 @@ export {
   getLogTypeConfig,
   isPerCallBilling,
   getDefaultTimeRange,
-  buildQueryParams,
   buildBaseParams,
   buildApiParams,
   fetchLogsByCategory,

+ 14 - 19
default/src/features/usage-logs/lib/utils.ts

@@ -31,6 +31,7 @@ import {
   LOG_TYPES,
   DISPLAYABLE_LOG_TYPES,
   TIMING_LOG_TYPES,
+  LOG_TYPE_ENUM,
 } from '../constants'
 import type {
   GetLogsParams,
@@ -91,24 +92,6 @@ function timestampToSeconds(ms: number): number {
   return Math.floor(ms / 1000)
 }
 
-/**
- * Build query parameters from filters
- */
-export function buildQueryParams(
-  params: Record<string, unknown>
-): URLSearchParams {
-  const queryParams = new URLSearchParams()
-
-  Object.entries(params).forEach(([key, value]) => {
-    // Keep 0 as a valid value, only filter out undefined, null, and empty string
-    if (value !== undefined && value !== null && value !== '') {
-      queryParams.append(key, String(value))
-    }
-  })
-
-  return queryParams
-}
-
 /**
  * Build time range parameters with default values
  * Shared logic for all log types
@@ -200,6 +183,9 @@ export function buildApiParams(config: {
     p: page,
     page_size: pageSize,
     ...(searchParams.type ? { type: processType(searchParams.type) } : {}),
+    ...(searchParams.consumeType && searchParams.consumeType !== 'all'
+      ? { consume_type: String(searchParams.consumeType) }
+      : {}),
     ...(searchParams.model ? { model_name: String(searchParams.model) } : {}),
     ...(searchParams.token ? { token_name: String(searchParams.token) } : {}),
     ...(searchParams.group ? { group: String(searchParams.group) } : {}),
@@ -262,7 +248,7 @@ export async function fetchLogsByCategory(
   const { logCategory, isAdmin, page, pageSize, searchParams, columnFilters } =
     config
 
-  if (logCategory === 'common') {
+  if (logCategory === 'common' || logCategory === 'audit') {
     const params = buildApiParams({
       page,
       pageSize,
@@ -270,6 +256,15 @@ export async function fetchLogsByCategory(
       columnFilters,
       isAdmin,
     })
+    if (logCategory === 'common') {
+      params.type = LOG_TYPE_ENUM.CONSUME
+    }
+    if (logCategory === 'audit') {
+      delete params.consume_type
+      if (params.type === LOG_TYPE_ENUM.CONSUME) {
+        params.type = LOG_TYPE_ENUM.UNKNOWN
+      }
+    }
     return isAdmin ? await getAllLogs(params) : await getUserLogs(params)
   }
 

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

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

+ 4 - 1
default/src/features/usage-logs/types.ts

@@ -28,7 +28,7 @@ import type { UsageLog } from './data/schema'
 /**
  * Log category for different log types
  */
-export type LogCategory = 'common' | 'drawing' | 'task'
+export type LogCategory = 'common' | 'audit' | 'drawing' | 'task'
 
 // ============================================================================
 // Filter Types
@@ -47,6 +47,8 @@ export interface CommonFilters {
  * Common logs specific filters
  */
 export interface CommonLogFilters extends CommonFilters {
+  consumeType?: 'all' | 'platform' | 'user'
+  logType?: string
   model?: string
   token?: string
   group?: string
@@ -304,6 +306,7 @@ export interface GetLogsParams {
   p?: number
   page_size?: number
   type?: number
+  consume_type?: string
   username?: string
   token_name?: string
   model_name?: string

+ 25 - 19
default/src/hooks/use-sidebar-data.ts

@@ -35,9 +35,9 @@ import {
   ShieldCheck,
   // Wallet,
 } from 'lucide-react'
-// import { useTranslation } from 'react-i18next'
+import { useTranslation } from 'react-i18next'
 
-import { type SidebarData } from '@/components/layout/types'
+import type { SidebarData } from '@/components/layout/types'
 import { PERMISSION_CODES } from '@/lib/admin-permissions'
 // import { ROLE } from '@/lib/roles'
 
@@ -48,7 +48,7 @@ import { PERMISSION_CODES } from '@/lib/admin-permissions'
  * registered in `layout/lib/sidebar-view-registry.ts`.
  */
 export function useSidebarData(): SidebarData {
-  // const { t } = useTranslation()
+  const { t } = useTranslation()
 
   return {
     navGroups: [
@@ -70,10 +70,10 @@ export function useSidebarData(): SidebarData {
       // },
       {
         id: 'overview',
-        title: '总览',
+        title: t('Overview'),
         items: [
           {
-            title: '数据看板',
+            title: t('Dashboard'),
             url: '/dashboard/models',
             icon: LayoutDashboard,
             permissionCode: PERMISSION_CODES.DASHBOARD_VIEW,
@@ -82,34 +82,34 @@ export function useSidebarData(): SidebarData {
       },
       {
         id: 'admin',
-        title: '平台管理',
+        title: t('Platform Management'),
         items: [
           {
-            title: '渠道管理',
+            title: t('Channel Management'),
             url: '/channels',
             icon: Radio,
             permissionCode: PERMISSION_CODES.CHANNEL_VIEW,
           },
           {
-            title: '模型管理',
+            title: t('Model Management'),
             url: '/models/metadata',
             icon: Box,
             permissionCode: PERMISSION_CODES.MODELS_VIEW,
           },
           {
-            title: '平台管理',
+            title: t('Platform Management'),
             url: '/users',
             icon: Users,
             permissionCode: PERMISSION_CODES.USER_VIEW,
           },
           {
-            title: '平台角色',
+            title: t('Platform Roles'),
             url: '/roles',
             icon: ShieldCheck,
             permissionCode: PERMISSION_CODES.ROLE_VIEW,
           },
           {
-            title: '租户管理',
+            title: t('Tenant Management'),
             url: '/tenants',
             icon: Building2,
             permissionCode: PERMISSION_CODES.TENANT_VIEW,
@@ -134,22 +134,22 @@ export function useSidebarData(): SidebarData {
       },
       {
         id: 'personal-management',
-        title: '个人管理',
+        title: t('Personal Management'),
         items: [
           {
-            title: '个人信息',
+            title: t('Profile'),
             url: '/profile',
             icon: User,
             permissionCode: PERMISSION_CODES.PROFILE_VIEW,
           },
           {
-            title: 'API密钥',
+            title: t('API Keys'),
             url: '/keys',
             icon: Key,
             permissionCode: PERMISSION_CODES.KEYS_VIEW,
           },
           {
-            title: '模型列表',
+            title: t('Model List'),
             url: '/pricing',
             icon: Box,
             permissionCode: PERMISSION_CODES.PRICING_VIEW,
@@ -163,22 +163,28 @@ export function useSidebarData(): SidebarData {
       },
       {
         id: 'statistics-analysis',
-        title: '统计分析',
+        title: t('Statistics Analysis'),
         items: [
           {
-            title: '调用日志',
+            title: t('Call Logs'),
             url: '/usage-logs/common',
             icon: FileText,
             permissionCode: PERMISSION_CODES.USAGE_LOGS_VIEW,
           },
+          {
+            title: t('Audit Logs'),
+            url: '/usage-logs/audit',
+            icon: FileText,
+            permissionCode: PERMISSION_CODES.USAGE_LOGS_VIEW,
+          },
         ],
       },
       {
         id: 'admin-system',
-        title: '系统管理',
+        title: t('System Administration'),
         items: [
           {
-            title: '系统设置',
+            title: t('System Settings'),
             url: '/system-settings/site',
             activeUrls: ['/system-settings'],
             icon: Settings,

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

@@ -913,6 +913,8 @@
     "Common": "Common",
     "Common Keys": "Common Keys",
     "Common Logs": "Common Logs",
+    "Call Logs": "Call Logs",
+    "Audit Logs": "Audit Logs",
     "Common pitfall: the user group base ratio is NOT a personal discount. It only applies when the user group itself is the billing group.": "Common pitfall: the user group base ratio is NOT a personal discount. It only applies when the user group itself is the billing group.",
     "Common ports include 25, 465, and 587": "Common ports include 25, 465, and 587",
     "Common User": "Common User",
@@ -3355,6 +3357,10 @@
     "Plan title is required": "Plan title is required",
     "Planned maintenance on Friday at 22:00 UTC...": "Planned maintenance on Friday at 22:00 UTC...",
     "Platform": "Platform",
+    "Platform Management": "Platform Management",
+    "Personal Management": "Personal Management",
+    "Model List": "Model List",
+    "Statistics Analysis": "Statistics Analysis",
     "Playground": "Playground",
     "Playground and chat functions": "Playground and chat functions",
     "Playground experiments and live conversations.": "Playground experiments and live conversations.",

+ 6 - 0
default/src/i18n/locales/fr.json

@@ -912,6 +912,8 @@
     "Common": "Commun",
     "Common Keys": "Clés courantes",
     "Common Logs": "Journaux courants",
+    "Call Logs": "Journaux d'appel",
+    "Audit Logs": "Journaux d'audit",
     "Common pitfall: the user group base ratio is NOT a personal discount. It only applies when the user group itself is the billing group.": "Piège courant : le taux de base du groupe d’utilisateurs n’est PAS une remise personnelle. Il ne s’applique que lorsque ce groupe est lui-même le groupe de facturation.",
     "Common ports include 25, 465, and 587": "Les ports courants incluent 25, 465 et 587",
     "Common User": "Utilisateur commun",
@@ -3351,6 +3353,10 @@
     "Planned maintenance on Friday at 22:00 UTC...": "Maintenance planifiée vendredi à 22:00 UTC...",
     "Platform": "Plateforme",
     "Tenant": "Locataire",
+    "Platform Management": "Gestion de la plateforme",
+    "Personal Management": "Gestion personnelle",
+    "Model List": "Liste des modèles",
+    "Statistics Analysis": "Analyse statistique",
     "Playground": "Aire de jeux",
     "Playground and chat functions": "Fonctions de terrain de jeu et de discussion",
     "Playground experiments and live conversations.": "Expériences Playground et conversations en direct.",

+ 6 - 0
default/src/i18n/locales/ja.json

@@ -912,6 +912,8 @@
     "Common": "共通",
     "Common Keys": "よく使うキー",
     "Common Logs": "一般的なログ",
+    "Call Logs": "呼び出しログ",
+    "Audit Logs": "監査ログ",
     "Common pitfall: the user group base ratio is NOT a personal discount. It only applies when the user group itself is the billing group.": "よくある誤解:ユーザーグループの基本倍率は個人割引ではありません。ユーザーグループ自体が課金グループになる場合にのみ適用されます。",
     "Common ports include 25, 465, and 587": "一般的なポートには 25, 465, 587 が含まれます",
     "Common User": "一般ユーザー",
@@ -3351,6 +3353,10 @@
     "Planned maintenance on Friday at 22:00 UTC...": "金曜日 22:00 UTC に計画メンテナンスがあります...",
     "Platform": "プラットフォーム",
     "Tenant": "テナント",
+    "Platform Management": "プラットフォーム管理",
+    "Personal Management": "個人管理",
+    "Model List": "モデル一覧",
+    "Statistics Analysis": "統計分析",
     "Playground": "プレイグラウンド",
     "Playground and chat functions": "プレイグラウンドとチャット機能",
     "Playground experiments and live conversations.": "Playgroundの実験とライブ会話。",

+ 6 - 0
default/src/i18n/locales/ru.json

@@ -912,6 +912,8 @@
     "Common": "Общие",
     "Common Keys": "Часто используемые ключи",
     "Common Logs": "Общие журналы",
+    "Call Logs": "Журналы вызовов",
+    "Audit Logs": "Журналы аудита",
     "Common pitfall: the user group base ratio is NOT a personal discount. It only applies when the user group itself is the billing group.": "Частая ошибка: базовый коэффициент группы пользователя — НЕ персональная скидка. Он применяется, только когда эта группа сама является тарифной.",
     "Common ports include 25, 465, and 587": "Распространенные порты включают 25, 465 и 587",
     "Common User": "Обычный пользователь",
@@ -3351,6 +3353,10 @@
     "Planned maintenance on Friday at 22:00 UTC...": "Запланированное обслуживание в пятницу в 22:00 UTC...",
     "Platform": "Платформа",
     "Tenant": "Арендатор",
+    "Platform Management": "Управление платформой",
+    "Personal Management": "Личное управление",
+    "Model List": "Список моделей",
+    "Statistics Analysis": "Статистический анализ",
     "Playground": "Песочница",
     "Playground and chat functions": "Функции песочницы и чата",
     "Playground experiments and live conversations.": "Эксперименты в песочнице и живые беседы.",

+ 6 - 0
default/src/i18n/locales/vi.json

@@ -912,6 +912,8 @@
     "Common": "Chung",
     "Common Keys": "Khóa thường dùng",
     "Common Logs": "Logarit thập phân",
+    "Call Logs": "Nhật ký gọi",
+    "Audit Logs": "Nhật ký kiểm toán",
     "Common pitfall: the user group base ratio is NOT a personal discount. It only applies when the user group itself is the billing group.": "Lỗi thường gặp: hệ số cơ bản của nhóm người dùng KHÔNG phải giảm giá cá nhân. Nó chỉ áp dụng khi chính nhóm người dùng là nhóm tính phí.",
     "Common ports include 25, 465, and 587": "Các cổng phổ biến bao gồm 25, 465 và 587",
     "Common User": "Người dùng thông thường",
@@ -3351,6 +3353,10 @@
     "Planned maintenance on Friday at 22:00 UTC...": "Bảo trì theo kế hoạch vào thứ Sáu lúc 22:00 UTC...",
     "Platform": "Nền tảng",
     "Tenant": "Người thuê",
+    "Platform Management": "Quản lý nền tảng",
+    "Personal Management": "Quản lý cá nhân",
+    "Model List": "Danh sách mô hình",
+    "Statistics Analysis": "Phân tích thống kê",
     "Playground": "Sân chơi",
     "Playground and chat functions": "Chức năng sân chơi và trò chuyện",
     "Playground experiments and live conversations.": "Các thí nghiệm Playground và trò chuyện trực tiếp.",

+ 6 - 0
default/src/i18n/locales/zh-TW.json

@@ -912,6 +912,8 @@
     "Common": "通用",
     "Common Keys": "常用 Key",
     "Common Logs": "通用日誌",
+    "Call Logs": "調用日誌",
+    "Audit Logs": "審計日誌",
     "Common pitfall: the user group base ratio is NOT a personal discount. It only applies when the user group itself is the billing group.": "常見誤區:用戶分組的基礎倍率不是個人折扣,只有當用戶分組本身就是收費分組時才會生效。",
     "Common ports include 25, 465, and 587": "常用端口包括 25、465 和 587",
     "Common User": "普通用戶",
@@ -3351,6 +3353,10 @@
     "Planned maintenance on Friday at 22:00 UTC...": "計劃於週五 22:00 UTC 進行維護...",
     "Platform": "平台",
     "Tenant": "租戶",
+    "Platform Management": "平台管理",
+    "Personal Management": "個人管理",
+    "Model List": "模型列表",
+    "Statistics Analysis": "統計分析",
     "Playground": "遊樂場",
     "Playground and chat functions": "操練場和聊天功能",
     "Playground experiments and live conversations.": "Playground 實驗和實時對話。",

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

@@ -913,6 +913,8 @@
     "Common": "通用",
     "Common Keys": "常用 Key",
     "Common Logs": "通用日志",
+    "Call Logs": "调用日志",
+    "Audit Logs": "审计日志",
     "Common pitfall: the user group base ratio is NOT a personal discount. It only applies when the user group itself is the billing group.": "常见误区:用户分组的基础倍率不是个人折扣,只有当用户分组本身就是计费分组时才会生效。",
     "Common ports include 25, 465, and 587": "常用端口包括 25、465 和 587",
     "Common User": "普通用户",
@@ -3356,6 +3358,10 @@
     "Plan title is required": "套餐标题为必填项",
     "Planned maintenance on Friday at 22:00 UTC...": "计划于周五 22:00 UTC 进行维护...",
     "Platform": "平台",
+    "Platform Management": "平台管理",
+    "Personal Management": "个人管理",
+    "Model List": "模型列表",
+    "Statistics Analysis": "统计分析",
     "Playground": "游乐场",
     "Playground and chat functions": "操练场和聊天功能",
     "Playground experiments and live conversations.": "Playground 实验和实时对话。",

+ 6 - 0
default/src/i18n/static-keys.ts

@@ -471,6 +471,10 @@ export const STATIC_I18N_KEYS = [
   'Data management and log viewing',
   'Dashboard',
   'System data statistics',
+  'Platform Management',
+  'Personal Management',
+  'Model List',
+  'Statistics Analysis',
   'Flow',
   'Flow Filters',
   'Filter the traffic flow view by time range and user.',
@@ -483,6 +487,8 @@ export const STATIC_I18N_KEYS = [
   'API token management',
   'Usage Logs',
   'API usage records',
+  'Call Logs',
+  'Audit Logs',
   'Drawing Logs',
   'Drawing task records',
   'Task Logs',

+ 6 - 2
default/src/routes/_authenticated/usage-logs/$section.tsx

@@ -39,6 +39,7 @@ const usageLogsSearchSchema = z.object({
   page: z.number().optional().catch(1),
   pageSize: z.number().optional().catch(undefined),
   type: logTypeSearchSchema.optional(),
+  consumeType: z.enum(['all', 'platform', 'user']).optional().catch('all'),
   filter: z.string().optional().catch(''),
   model: z.string().optional().catch(''),
   token: z.string().optional().catch(''),
@@ -67,11 +68,14 @@ export const Route = createFileRoute('/_authenticated/usage-logs/$section')({
         params: { section: USAGE_LOGS_DEFAULT_SECTION },
       })
     }
-    // type 仅 common 使用,非 common 时清掉 URL 里的 type
     const hasTypeSearch = Array.isArray(search?.type)
       ? search.type.length > 0
       : search?.type != null && search.type !== ''
-    if (params.section !== 'common' && hasTypeSearch) {
+    if (
+      params.section !== 'common' &&
+      params.section !== 'audit' &&
+      hasTypeSearch
+    ) {
       throw redirect({
         to: '/usage-logs/$section',
         params: { section: params.section },