Files
mai-bot/dashboard/src/routes/config/model/components/ModelTable.tsx

157 lines
6.2 KiB
TypeScript

/**
* 模型列表 - 桌面端表格视图
*/
import React from 'react'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import { Pencil, Trash2 } from 'lucide-react'
import type { ModelInfo } from '../types'
interface ModelTableProps {
/** 当前页显示的模型 (分页后的) */
paginatedModels: ModelInfo[]
/** 所有模型列表 (未分页) */
allModels: ModelInfo[]
/** 过滤后的模型列表 */
filteredModels: ModelInfo[]
/** 已选中的模型索引集合 */
selectedModels: Set<number>
/** 编辑模型回调 */
onEdit: (model: ModelInfo, index: number) => void
/** 删除模型回调 */
onDelete: (index: number) => void
/** 切换选中状态回调 */
onToggleSelection: (index: number) => void
/** 切换全选回调 */
onToggleSelectAll: () => void
/** 检查模型是否被使用 */
isModelUsed: (modelName: string) => boolean
/** 搜索关键词 */
searchQuery: string
}
export const ModelTable = React.memo(function ModelTable({
paginatedModels,
allModels,
filteredModels,
selectedModels,
onEdit,
onDelete,
onToggleSelection,
onToggleSelectAll,
isModelUsed,
searchQuery,
}: ModelTableProps) {
return (
<div className="hidden md:block rounded-lg border bg-card overflow-hidden">
<div className="overflow-x-auto">
<Table aria-label="模型列表">
<TableHeader>
<TableRow>
<TableHead className="w-12">
<Checkbox
checked={selectedModels.size === filteredModels.length && filteredModels.length > 0}
onCheckedChange={onToggleSelectAll}
/>
</TableHead>
<TableHead className="w-14 text-center">使</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="w-14 text-center"></TableHead>
<TableHead className="text-center"></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{paginatedModels.length === 0 ? (
<TableRow>
<TableCell colSpan={10} className="text-center text-muted-foreground py-8">
{searchQuery ? '未找到匹配的模型' : '暂无模型配置'}
</TableCell>
</TableRow>
) : (
paginatedModels.map((model, displayIndex) => {
const actualIndex = allModels.findIndex(m => m === model)
const used = isModelUsed(model.name)
return (
<TableRow key={displayIndex}>
<TableCell>
<Checkbox
checked={selectedModels.has(actualIndex)}
onCheckedChange={() => onToggleSelection(actualIndex)}
/>
</TableCell>
<TableCell className="text-center">
<span
className={`mx-auto block h-3 w-3 rounded-full border ${
used
? 'border-green-500 bg-green-500 shadow-[0_0_0_3px_rgba(34,197,94,0.18)]'
: 'border-green-700/40 bg-green-950/20'
}`}
title={used ? '已使用' : '未使用'}
aria-label={used ? '已使用' : '未使用'}
/>
</TableCell>
<TableCell className="font-medium">{model.name}</TableCell>
<TableCell className="max-w-xs truncate" title={model.model_identifier}>
{model.model_identifier}
</TableCell>
<TableCell>{model.api_provider}</TableCell>
<TableCell className="text-center">
<span
className={`mx-auto block h-3 w-3 rounded-full border ${
model.visual
? 'border-green-500 bg-green-500 shadow-[0_0_0_3px_rgba(34,197,94,0.18)]'
: 'border-green-700/40 bg-green-950/20'
}`}
title={model.visual ? '已启用视觉' : '未启用视觉'}
aria-label={model.visual ? '已启用视觉' : '未启用视觉'}
/>
</TableCell>
<TableCell className="text-center">
{model.temperature != null ? model.temperature : <span className="text-muted-foreground">-</span>}
</TableCell>
<TableCell className="text-right">¥{model.price_in}/M</TableCell>
<TableCell className="text-right">¥{model.price_out}/M</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
<Button
variant="default"
size="sm"
onClick={() => onEdit(model, actualIndex)}
>
<Pencil className="h-4 w-4 mr-1" strokeWidth={2} fill="none" />
</Button>
<Button
size="sm"
onClick={() => onDelete(actualIndex)}
className="bg-red-600 hover:bg-red-700 text-white"
>
<Trash2 className="h-4 w-4 mr-1" strokeWidth={2} fill="none" />
</Button>
</div>
</TableCell>
</TableRow>
)
})
)}
</TableBody>
</Table>
</div>
</div>
)
})