fix: correct admin sidebar model links

This commit is contained in:
gongzhiyong
2026-05-26 16:42:29 +08:00
parent a80c3e0c35
commit d02685dd5e
7 changed files with 205 additions and 6 deletions
@@ -53,12 +53,12 @@ export function getSystemSettingsNavGroups(t: TFunction): NavGroup[] {
},
{
title: t('Models'),
url: '/models',
url: '/models/metadata',
icon: Boxes,
},
{
title: t('Model deployments'),
url: '/deployments',
url: '/models/deployments',
icon: Cpu,
},
{
@@ -88,12 +88,12 @@ export function getSystemSettingsNavGroups(t: TFunction): NavGroup[] {
},
{
title: t('Vendors'),
url: '/system-settings/integrations',
url: '/models/vendors',
icon: Store,
},
{
title: t('All usage logs'),
url: '/usage-logs',
url: '/usage-logs/common',
icon: ClipboardList,
},
],
@@ -0,0 +1,166 @@
import { useMemo, useState } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Edit, Search, Trash2 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import { Skeleton } from '@/components/ui/skeleton'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { TableEmpty } from '@/components/data-table'
import { getVendors } from '../api'
import { handleDeleteVendor, vendorsQueryKeys } from '../lib'
import type { Vendor } from '../types'
import { useModels } from './models-provider'
export function VendorsTable() {
const { t } = useTranslation()
const queryClient = useQueryClient()
const { setOpen, setCurrentVendor } = useModels()
const [filter, setFilter] = useState('')
const [vendorToDelete, setVendorToDelete] = useState<Vendor | null>(null)
const { data, isLoading, isFetching } = useQuery({
queryKey: vendorsQueryKeys.list({ page_size: 1000 }),
queryFn: () => getVendors({ page_size: 1000 }),
})
const vendors = useMemo(() => data?.data?.items || [], [data?.data?.items])
const filteredVendors = useMemo(() => {
const keyword = filter.trim().toLowerCase()
if (!keyword) return vendors
return vendors.filter((vendor) => {
const haystack = [vendor.name, vendor.description, vendor.icon]
.filter(Boolean)
.join(' ')
.toLowerCase()
return haystack.includes(keyword)
})
}, [filter, vendors])
const handleEdit = (vendor: Vendor) => {
setCurrentVendor(vendor)
setOpen('update-vendor')
}
const handleDelete = () => {
if (!vendorToDelete) return
void handleDeleteVendor(vendorToDelete.id, queryClient, () =>
setVendorToDelete(null)
)
}
return (
<>
<div className='space-y-4'>
<div className='relative max-w-sm'>
<Search className='text-muted-foreground absolute top-2.5 left-2.5 h-4 w-4' />
<Input
value={filter}
onChange={(event) => setFilter(event.target.value)}
placeholder={t('Search vendors...')}
className='pl-8'
/>
</div>
<div
className={`overflow-hidden rounded-md border transition-opacity duration-150 ${
isFetching && !isLoading ? 'pointer-events-none opacity-50' : ''
}`}
>
<Table>
<TableHeader>
<TableRow>
<TableHead className='w-20'>ID</TableHead>
<TableHead>{t('Vendor')}</TableHead>
<TableHead>{t('Description')}</TableHead>
<TableHead>{t('Icon')}</TableHead>
<TableHead>{t('Status')}</TableHead>
<TableHead className='w-28 text-right'>
{t('Actions')}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
Array.from({ length: 5 }, (_, rowIndex) => (
<TableRow key={`vendor-skeleton-${rowIndex}`}>
{Array.from({ length: 6 }, (_, cellIndex) => (
<TableCell key={cellIndex}>
<Skeleton className='h-4 w-full max-w-40 rounded-sm' />
</TableCell>
))}
</TableRow>
))
) : filteredVendors.length === 0 ? (
<TableEmpty
colSpan={6}
title={t('No vendors found')}
description={t('Create a vendor to group model metadata.')}
/>
) : (
filteredVendors.map((vendor) => (
<TableRow key={vendor.id}>
<TableCell className='font-mono text-sm'>
{vendor.id}
</TableCell>
<TableCell className='font-medium'>{vendor.name}</TableCell>
<TableCell className='text-muted-foreground max-w-md truncate'>
{vendor.description || '-'}
</TableCell>
<TableCell className='font-mono text-sm'>
{vendor.icon || '-'}
</TableCell>
<TableCell>
{vendor.status === 1 ? t('Enabled') : t('Disabled')}
</TableCell>
<TableCell className='text-right'>
<div className='flex justify-end gap-1'>
<Button
type='button'
variant='ghost'
size='icon'
aria-label={t('Edit')}
onClick={() => handleEdit(vendor)}
>
<Edit className='h-4 w-4' />
</Button>
<Button
type='button'
variant='ghost'
size='icon'
aria-label={t('Delete')}
className='text-destructive hover:text-destructive'
onClick={() => setVendorToDelete(vendor)}
>
<Trash2 className='h-4 w-4' />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</div>
<ConfirmDialog
open={Boolean(vendorToDelete)}
onOpenChange={(open) => !open && setVendorToDelete(null)}
title={t('Delete Vendor')}
desc={t('Are you sure you want to delete this vendor?')}
confirmText={t('Delete')}
destructive
handleConfirm={handleDelete}
/>
</>
)
}
+18 -1
View File
@@ -14,6 +14,7 @@ import { ModelsDialogs } from './components/models-dialogs'
import { ModelsPrimaryButtons } from './components/models-primary-buttons'
import { ModelsProvider, useModels } from './components/models-provider'
import { ModelsTable } from './components/models-table'
import { VendorsTable } from './components/vendors-table'
import { useModelDeploymentSettings } from './hooks/use-model-deployment-settings'
import { deploymentsQueryKeys } from './lib'
import {
@@ -36,13 +37,17 @@ const SECTION_META: Record<
titleKey: 'Deployments',
descriptionKey: 'Manage model deployments',
},
vendors: {
titleKey: 'Vendors',
descriptionKey: 'Manage model vendors',
},
}
function ModelsContent() {
const { t } = useTranslation()
const navigate = useNavigate()
const queryClient = useQueryClient()
const { tabCategory, setTabCategory } = useModels()
const { tabCategory, setTabCategory, setOpen } = useModels()
const params = route.useParams()
const activeSection = (params.section ??
MODELS_DEFAULT_SECTION) as ModelsSectionId
@@ -116,6 +121,16 @@ function ModelsContent() {
<SectionPageLayout.Actions>
{activeSection === 'metadata' ? (
<ModelsPrimaryButtons />
) : activeSection === 'vendors' ? (
<Button
onClick={() => {
setOpen('create-vendor')
}}
size='sm'
>
<Plus className='h-4 w-4' />
{t('Create Vendor')}
</Button>
) : (
<Button onClick={() => setCreateDeploymentOpen(true)} size='sm'>
<Plus className='h-4 w-4' />
@@ -136,6 +151,8 @@ function ModelsContent() {
</Tabs>
{activeSection === 'metadata' ? (
<ModelsTable />
) : activeSection === 'vendors' ? (
<VendorsTable />
) : (
<DeploymentAccessGuard
loading={deploymentLoading}
@@ -16,6 +16,12 @@ const MODELS_SECTIONS = [
descriptionKey: 'Manage model deployments',
build: () => null, // Content is rendered directly in the page component
},
{
id: 'vendors',
titleKey: 'Vendors',
descriptionKey: 'Manage model vendors',
build: () => null, // Content is rendered directly in the page component
},
] as const
export type ModelsSectionId = (typeof MODELS_SECTIONS)[number]['id']
+1 -1
View File
@@ -286,7 +286,7 @@ export type SyncSource = 'official' | 'config'
/**
* Model tab type
*/
export type ModelTabCategory = 'metadata' | 'deployments'
export type ModelTabCategory = 'metadata' | 'deployments' | 'vendors'
/**
* Deployment entity from API
+5
View File
@@ -2039,6 +2039,7 @@
"Manage local models for:": "Manage local models for:",
"Manage model deployments": "Manage model deployments",
"Manage model metadata and configuration": "Manage model metadata and configuration",
"Manage model vendors": "Manage model vendors",
"Manage multi-key status and configuration for this channel": "Manage multi-key status and configuration for this channel",
"Manage Ollama Models": "Manage Ollama Models",
"Manage redemption codes for quota top-up": "Manage redemption codes for quota top-up",
@@ -3852,6 +3853,10 @@
"Vary (Subtle)": "Vary (Subtle)",
"Vendor": "Vendor",
"Vendor deleted successfully": "Vendor deleted successfully",
"Delete Vendor": "Delete Vendor",
"Are you sure you want to delete this vendor?": "Are you sure you want to delete this vendor?",
"Create a vendor to group model metadata.": "Create a vendor to group model metadata.",
"No vendors found": "No vendors found",
"Vendor Name *": "Vendor Name *",
"Vendor:": "Vendor:",
"Vendors": "Vendors",
+5
View File
@@ -2039,6 +2039,7 @@
"Manage local models for:": "管理本地模型:",
"Manage model deployments": "管理模型部署",
"Manage model metadata and configuration": "管理模型元信息和配置",
"Manage model vendors": "管理模型供应商",
"Manage multi-key status and configuration for this channel": "管理此渠道的多密钥状态和配置",
"Manage Ollama Models": "管理 Ollama 模型",
"Manage redemption codes for quota top-up": "管理用于配额充值的兑换码",
@@ -3852,6 +3853,10 @@
"Vary (Subtle)": "弱变换",
"Vendor": "供应商",
"Vendor deleted successfully": "供应商删除成功",
"Delete Vendor": "删除供应商",
"Are you sure you want to delete this vendor?": "确定要删除这个供应商吗?",
"Create a vendor to group model metadata.": "创建供应商用于归类模型元信息。",
"No vendors found": "未找到供应商",
"Vendor Name *": "供应商名称 *",
"Vendor:": "供应商:",
"Vendors": "供应商",