import { type ColumnDef } from '@tanstack/react-table'
import { useTranslation } from 'react-i18next'
import { formatTimestampToDate } from '@/lib/format'
import { getLobeIcon } from '@/lib/lobe-icon'
import { Checkbox } from '@/components/ui/checkbox'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { DataTableColumnHeader } from '@/components/data-table/column-header'
import { GroupBadge } from '@/components/group-badge'
import { StatusBadge } from '@/components/status-badge'
import {
getModelStatusConfig,
getNameRuleConfig,
getQuotaTypeConfig,
} from '../constants'
import { parseModelTags, formatEndpointsDisplay } from '../lib'
import type { Model, Vendor } from '../types'
import { DataTableRowActions } from './data-table-row-actions'
import { DescriptionCell } from './description-cell'
/**
* Render limited items with "and X more" indicator
*/
function renderLimitedItems(
items: React.ReactNode[],
maxDisplay: number = 2
): React.ReactNode {
if (items.length === 0)
return -
const displayed = items.slice(0, maxDisplay)
const remaining = items.length - maxDisplay
return (
{displayed}
{remaining > 0 && (
)}
)
}
/**
* Generate models columns configuration
*/
export function useModelsColumns(vendors: Vendor[] = []): ColumnDef[] {
const { t } = useTranslation()
// Get translated configs
const NAME_RULE_CONFIG = getNameRuleConfig(t)
const MODEL_STATUS_CONFIG = getModelStatusConfig(t)
const QUOTA_TYPE_CONFIG = getQuotaTypeConfig(t)
const vendorMap: Record = {}
vendors.forEach((v) => {
vendorMap[v.id] = v
})
return [
// Checkbox column
{
id: 'select',
header: ({ table }) => (
table.toggleAllPageRowsSelected(!!value)}
aria-label='Select all'
/>
),
cell: ({ row }) => (
row.toggleSelected(!!value)}
aria-label='Select row'
/>
),
enableSorting: false,
enableHiding: false,
size: 40,
},
// ID column
{
accessorKey: 'id',
meta: { label: t('ID'), mobileHidden: true },
header: ({ column }) => (
),
cell: ({ row }) => {
const id = row.getValue('id') as number
return (
)
},
size: 80,
},
// Icon column
{
accessorKey: 'icon',
meta: { label: t('Icon'), mobileHidden: true },
header: t('Icon'),
cell: ({ row }) => {
const model = row.original
const iconKey =
model.icon ||
vendorMap[model.vendor_id || 0]?.icon ||
model.model_name?.[0] ||
'N'
const icon = getLobeIcon(iconKey, 20)
return {icon}
},
size: 70,
enableSorting: false,
},
// Model Name column
{
accessorKey: 'model_name',
meta: { label: t('Model Name'), mobileTitle: true },
header: ({ column }) => (
),
cell: ({ row }) => {
const name = row.getValue('model_name') as string
return (
)
},
minSize: 200,
},
// Name Rule column
{
accessorKey: 'name_rule',
meta: { label: t('Match Type') },
header: ({ column }) => (
),
cell: ({ row }) => {
const rule = row.getValue('name_rule') as 0 | 1 | 2 | 3
const model = row.original
const config = NAME_RULE_CONFIG[rule]
let label = config.label
if (rule !== 0 && model.matched_count) {
label = `${config.label} (${model.matched_count})`
}
const badge = (
)
// Show tooltip with matched models for non-exact rules
if (
rule !== 0 &&
model.matched_models &&
model.matched_models.length > 0
) {
const matchedBadges = model.matched_models.map((m, idx) => (
))
return (
{badge}
{matchedBadges}
)
}
return badge
},
size: 140,
enableSorting: false,
},
// Status column
{
accessorKey: 'status',
meta: { label: t('Status'), mobileBadge: true },
header: t('Status'),
cell: ({ row }) => {
const status = row.getValue('status') as number
const config =
MODEL_STATUS_CONFIG[status as 0 | 1] || MODEL_STATUS_CONFIG[0]
return (
)
},
filterFn: (row, id, value) => {
if (!value || value.length === 0 || value.includes('all')) return true
const status = row.getValue(id) as number
if (value.includes('enabled')) return status === 1
if (value.includes('disabled')) return status !== 1
return false
},
size: 120,
enableSorting: false,
},
// Vendor column
{
accessorKey: 'vendor_id',
meta: { label: t('Vendor') },
header: t('Vendor'),
cell: ({ row }) => {
const vendorId = row.getValue('vendor_id') as number
const vendor = vendorMap[vendorId]
if (!vendor) {
return -
}
const icon = vendor.icon ? getLobeIcon(vendor.icon, 14) : null
return (
{icon}
)
},
filterFn: (row, id, value) => {
if (!value || value.length === 0 || value.includes('all')) return true
return value.includes(String(row.getValue(id)))
},
size: 150,
enableSorting: false,
},
// Description column
{
accessorKey: 'description',
meta: { label: t('Description'), mobileHidden: true },
header: t('Description'),
cell: ({ row }) => {
const description = row.getValue('description') as string
const modelName = row.getValue('model_name') as string
return (
)
},
size: 150,
enableSorting: false,
},
// Tags column
{
accessorKey: 'tags',
meta: { label: t('Tags'), mobileHidden: true },
header: t('Tags'),
cell: ({ row }) => {
const tags = row.getValue('tags') as string
const tagArray = parseModelTags(tags)
if (tagArray.length === 0) {
return -
}
const tagBadges = tagArray.map((tag, idx) => (
))
return (
{renderLimitedItems(tagBadges, 2)}
{tagArray.length > 2 && (
{tagBadges}
)}
)
},
size: 150,
enableSorting: false,
},
// Endpoints column
{
accessorKey: 'endpoints',
meta: { label: t('Endpoints'), mobileHidden: true },
header: t('Endpoints'),
cell: ({ row }) => {
const endpoints = row.getValue('endpoints') as string
const endpointArray = formatEndpointsDisplay(endpoints)
if (endpointArray.length === 0) {
return -
}
const endpointBadges = endpointArray.map((ep, idx) => (
))
return (
{renderLimitedItems(endpointBadges, 2)}
{endpointArray.length > 2 && (
{endpointBadges}
)}
)
},
size: 150,
enableSorting: false,
},
// Bound Channels column
{
accessorKey: 'bound_channels',
meta: { label: t('Bound Channels'), mobileHidden: true },
header: t('Bound Channels'),
cell: ({ row }) => {
const channels = row.getValue('bound_channels') as Array<{
id: number
name: string
type?: number
status?: number
}>
if (!channels || channels.length === 0) {
return -
}
const channelBadges = channels.map((c, idx) => (
))
return (
{renderLimitedItems(channelBadges, 2)}
{channels.length > 2 && (
{channelBadges}
)}
)
},
size: 150,
enableSorting: false,
},
// Enable Groups column
{
accessorKey: 'enable_groups',
meta: { label: t('Enable Groups'), mobileHidden: true },
header: ({ column }) => (
),
cell: ({ row }) => {
const groups = row.getValue('enable_groups') as string[]
if (!groups || groups.length === 0) {
return -
}
const groupBadges = groups.map((g) => (
))
return (
{renderLimitedItems(groupBadges, 2)}
{groups.length > 2 && (
{groupBadges}
)}
)
},
size: 150,
enableSorting: false,
},
// Quota Types column
{
accessorKey: 'quota_types',
meta: { label: t('Quota Types'), mobileHidden: true },
header: t('Quota Types'),
cell: ({ row }) => {
const quotaTypes = row.getValue('quota_types') as number[]
if (!quotaTypes || quotaTypes.length === 0) {
return -
}
const quotaBadges = quotaTypes.map((qt, idx) => {
const config = QUOTA_TYPE_CONFIG[qt]
return (
)
})
return (
{renderLimitedItems(quotaBadges, 2)}
{quotaTypes.length > 2 && (
{quotaBadges}
)}
)
},
size: 150,
enableSorting: false,
},
// Sync Official column
{
accessorKey: 'sync_official',
meta: { label: t('Official Sync'), mobileHidden: true },
header: t('Official Sync'),
cell: ({ row }) => {
const syncOfficial = row.getValue('sync_official') as number
return (
)
},
filterFn: (row, id, value) => {
if (!value || value.length === 0 || value.includes('all')) return true
const syncOfficial = row.getValue(id) as number
if (value.includes('yes')) return syncOfficial === 1
if (value.includes('no')) return syncOfficial !== 1
return false
},
size: 120,
enableSorting: false,
},
// Created Time column
{
accessorKey: 'created_time',
meta: { label: t('Created'), mobileHidden: true },
header: ({ column }) => (
),
cell: ({ row }) => {
const timestamp = row.getValue('created_time') as number
return (
{formatTimestampToDate(timestamp)}
)
},
size: 180,
},
// Updated Time column
{
accessorKey: 'updated_time',
meta: { label: t('Updated'), mobileHidden: true },
header: ({ column }) => (
),
cell: ({ row }) => {
const timestamp = row.getValue('updated_time') as number
return (
{formatTimestampToDate(timestamp)}
)
},
size: 180,
},
// Actions column
{
id: 'actions',
cell: ({ row }) => {
return
},
size: 100,
enableSorting: false,
enableHiding: false,
},
]
}