fix: correct admin sidebar model links
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "供应商",
|
||||
|
||||
Reference in New Issue
Block a user