fix: correct admin sidebar model links
This commit is contained in:
@@ -53,12 +53,12 @@ export function getSystemSettingsNavGroups(t: TFunction): NavGroup[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Models'),
|
title: t('Models'),
|
||||||
url: '/models',
|
url: '/models/metadata',
|
||||||
icon: Boxes,
|
icon: Boxes,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Model deployments'),
|
title: t('Model deployments'),
|
||||||
url: '/deployments',
|
url: '/models/deployments',
|
||||||
icon: Cpu,
|
icon: Cpu,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -88,12 +88,12 @@ export function getSystemSettingsNavGroups(t: TFunction): NavGroup[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Vendors'),
|
title: t('Vendors'),
|
||||||
url: '/system-settings/integrations',
|
url: '/models/vendors',
|
||||||
icon: Store,
|
icon: Store,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('All usage logs'),
|
title: t('All usage logs'),
|
||||||
url: '/usage-logs',
|
url: '/usage-logs/common',
|
||||||
icon: ClipboardList,
|
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 { ModelsPrimaryButtons } from './components/models-primary-buttons'
|
||||||
import { ModelsProvider, useModels } from './components/models-provider'
|
import { ModelsProvider, useModels } from './components/models-provider'
|
||||||
import { ModelsTable } from './components/models-table'
|
import { ModelsTable } from './components/models-table'
|
||||||
|
import { VendorsTable } from './components/vendors-table'
|
||||||
import { useModelDeploymentSettings } from './hooks/use-model-deployment-settings'
|
import { useModelDeploymentSettings } from './hooks/use-model-deployment-settings'
|
||||||
import { deploymentsQueryKeys } from './lib'
|
import { deploymentsQueryKeys } from './lib'
|
||||||
import {
|
import {
|
||||||
@@ -36,13 +37,17 @@ const SECTION_META: Record<
|
|||||||
titleKey: 'Deployments',
|
titleKey: 'Deployments',
|
||||||
descriptionKey: 'Manage model deployments',
|
descriptionKey: 'Manage model deployments',
|
||||||
},
|
},
|
||||||
|
vendors: {
|
||||||
|
titleKey: 'Vendors',
|
||||||
|
descriptionKey: 'Manage model vendors',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModelsContent() {
|
function ModelsContent() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { tabCategory, setTabCategory } = useModels()
|
const { tabCategory, setTabCategory, setOpen } = useModels()
|
||||||
const params = route.useParams()
|
const params = route.useParams()
|
||||||
const activeSection = (params.section ??
|
const activeSection = (params.section ??
|
||||||
MODELS_DEFAULT_SECTION) as ModelsSectionId
|
MODELS_DEFAULT_SECTION) as ModelsSectionId
|
||||||
@@ -116,6 +121,16 @@ function ModelsContent() {
|
|||||||
<SectionPageLayout.Actions>
|
<SectionPageLayout.Actions>
|
||||||
{activeSection === 'metadata' ? (
|
{activeSection === 'metadata' ? (
|
||||||
<ModelsPrimaryButtons />
|
<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'>
|
<Button onClick={() => setCreateDeploymentOpen(true)} size='sm'>
|
||||||
<Plus className='h-4 w-4' />
|
<Plus className='h-4 w-4' />
|
||||||
@@ -136,6 +151,8 @@ function ModelsContent() {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
{activeSection === 'metadata' ? (
|
{activeSection === 'metadata' ? (
|
||||||
<ModelsTable />
|
<ModelsTable />
|
||||||
|
) : activeSection === 'vendors' ? (
|
||||||
|
<VendorsTable />
|
||||||
) : (
|
) : (
|
||||||
<DeploymentAccessGuard
|
<DeploymentAccessGuard
|
||||||
loading={deploymentLoading}
|
loading={deploymentLoading}
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ const MODELS_SECTIONS = [
|
|||||||
descriptionKey: 'Manage model deployments',
|
descriptionKey: 'Manage model deployments',
|
||||||
build: () => null, // Content is rendered directly in the page component
|
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
|
] as const
|
||||||
|
|
||||||
export type ModelsSectionId = (typeof MODELS_SECTIONS)[number]['id']
|
export type ModelsSectionId = (typeof MODELS_SECTIONS)[number]['id']
|
||||||
|
|||||||
+1
-1
@@ -286,7 +286,7 @@ export type SyncSource = 'official' | 'config'
|
|||||||
/**
|
/**
|
||||||
* Model tab type
|
* Model tab type
|
||||||
*/
|
*/
|
||||||
export type ModelTabCategory = 'metadata' | 'deployments'
|
export type ModelTabCategory = 'metadata' | 'deployments' | 'vendors'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deployment entity from API
|
* Deployment entity from API
|
||||||
|
|||||||
@@ -2039,6 +2039,7 @@
|
|||||||
"Manage local models for:": "Manage local models for:",
|
"Manage local models for:": "Manage local models for:",
|
||||||
"Manage model deployments": "Manage model deployments",
|
"Manage model deployments": "Manage model deployments",
|
||||||
"Manage model metadata and configuration": "Manage model metadata and configuration",
|
"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 multi-key status and configuration for this channel": "Manage multi-key status and configuration for this channel",
|
||||||
"Manage Ollama Models": "Manage Ollama Models",
|
"Manage Ollama Models": "Manage Ollama Models",
|
||||||
"Manage redemption codes for quota top-up": "Manage redemption codes for quota top-up",
|
"Manage redemption codes for quota top-up": "Manage redemption codes for quota top-up",
|
||||||
@@ -3852,6 +3853,10 @@
|
|||||||
"Vary (Subtle)": "Vary (Subtle)",
|
"Vary (Subtle)": "Vary (Subtle)",
|
||||||
"Vendor": "Vendor",
|
"Vendor": "Vendor",
|
||||||
"Vendor deleted successfully": "Vendor deleted successfully",
|
"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 Name *": "Vendor Name *",
|
||||||
"Vendor:": "Vendor:",
|
"Vendor:": "Vendor:",
|
||||||
"Vendors": "Vendors",
|
"Vendors": "Vendors",
|
||||||
|
|||||||
@@ -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 multi-key status and configuration for this channel": "管理此渠道的多密钥状态和配置",
|
"Manage multi-key status and configuration for this channel": "管理此渠道的多密钥状态和配置",
|
||||||
"Manage Ollama Models": "管理 Ollama 模型",
|
"Manage Ollama Models": "管理 Ollama 模型",
|
||||||
"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": "删除供应商",
|
||||||
|
"Are you sure you want to delete this vendor?": "确定要删除这个供应商吗?",
|
||||||
|
"Create a vendor to group model metadata.": "创建供应商用于归类模型元信息。",
|
||||||
|
"No vendors found": "未找到供应商",
|
||||||
"Vendor Name *": "供应商名称 *",
|
"Vendor Name *": "供应商名称 *",
|
||||||
"Vendor:": "供应商:",
|
"Vendor:": "供应商:",
|
||||||
"Vendors": "供应商",
|
"Vendors": "供应商",
|
||||||
|
|||||||
Reference in New Issue
Block a user