4 Commits

Author SHA1 Message Date
8066fc4386 chore: 更新版本号到 v1.7.1 2026-04-28 00:28:59 +08:00
9cd9ccd8e0 feat: 子类别管理界面重构,支持可视化增删改
- 添加子类别编辑弹框(subcategoryModal)
- 子类别列表可视化显示(卡片样式)
- 支持添加、编辑、删除子类别
- 表单输入关键特性字段和标签
- 替换原来的JSON文本编辑方式
2026-04-28 00:28:36 +08:00
a9cbd1b2ba chore: 更新版本号到 v1.7.0 2026-04-28 00:17:27 +08:00
685582b7e6 feat: 支持子类别配置和关键特性显示
- 类别数据结构新增 subcategories 字段
- 每个子类别可定义 key_features 和 feature_labels
- 前端模型页面添加子类别选择器
- 表格根据子类别动态显示关键特性列
- 后台管理支持编辑子类别配置(JSON格式)
- 预设了各类别的子类别配置(对话、代码、推理、视觉等)
2026-04-28 00:16:55 +08:00
4 changed files with 580 additions and 20 deletions

4
app.py
View File

@@ -1,7 +1,7 @@
"""
ParamHub - 参数百科
AI大模型与硬件参数速查平台
v1.6.0 - 后台管理添加大模型接口配置功能
v1.7.1 - 子类别管理界面重构,支持可视化增删改
"""
from flask import Flask, render_template, jsonify, request
@@ -1402,7 +1402,7 @@ def api_delete_image(filename):
if __name__ == '__main__':
print("=" * 50)
print("ParamHub - 参数百科 v1.6.0")
print("ParamHub - 参数百科 v1.7.1")
print("=" * 50)
print(f"访问地址: http://localhost:19010")
print(f"后台管理: http://localhost:19010/admin")

View File

@@ -5,7 +5,54 @@
"icon": "ri-robot-line",
"color": "blue",
"description": "大语言模型、图像模型等AI模型参数",
"order": 1
"order": 1,
"subcategories": [
{
"id": "chat",
"name": "对话模型",
"icon": "ri-chat-3-line",
"key_features": ["context_length", "mmlu", "input_price", "output_price"],
"feature_labels": {
"context_length": "上下文",
"mmlu": "MMLU",
"input_price": "输入价",
"output_price": "输出价"
}
},
{
"id": "code",
"name": "代码模型",
"icon": "ri-code-line",
"key_features": ["humaneval", "context_length", "input_price"],
"feature_labels": {
"humaneval": "HumanEval",
"context_length": "上下文",
"input_price": "输入价"
}
},
{
"id": "reasoning",
"name": "推理模型",
"icon": "ri-lightbulb-line",
"key_features": ["reasoning_capability", "mmlu", "context_length"],
"feature_labels": {
"reasoning_capability": "推理能力",
"mmlu": "MMLU",
"context_length": "上下文"
}
},
{
"id": "vision",
"name": "视觉模型",
"icon": "ri-image-line",
"key_features": ["vision_capability", "multimodal", "context_length"],
"feature_labels": {
"vision_capability": "视觉能力",
"multimodal": "多模态",
"context_length": "上下文"
}
}
]
},
{
"id": "gpus",
@@ -13,7 +60,45 @@
"icon": "ri-cpu-line",
"color": "green",
"description": "NVIDIA、AMD等GPU显卡规格参数",
"order": 2
"order": 2,
"subcategories": [
{
"id": "gaming",
"name": "游戏显卡",
"icon": "ri-gamepad-line",
"key_features": ["memory_gb", "cuda_cores", "price_usd", "fp16_tflops"],
"feature_labels": {
"memory_gb": "显存",
"cuda_cores": "CUDA核心",
"price_usd": "价格",
"fp16_tflops": "FP16性能"
}
},
{
"id": "professional",
"name": "专业显卡",
"icon": "ri-building-line",
"key_features": ["memory_gb", "tensor_cores", "memory_bandwidth_gbs", "price_usd"],
"feature_labels": {
"memory_gb": "显存",
"tensor_cores": "Tensor核心",
"memory_bandwidth_gbs": "带宽",
"price_usd": "价格"
}
},
{
"id": "datacenter",
"name": "数据中心",
"icon": "ri-server-line",
"key_features": ["memory_gb", "tensor_cores", "memory_bandwidth_gbs", "fp16_tflops"],
"feature_labels": {
"memory_gb": "显存",
"tensor_cores": "Tensor核心",
"memory_bandwidth_gbs": "带宽",
"fp16_tflops": "FP16性能"
}
}
]
},
{
"id": "cpus",
@@ -21,7 +106,45 @@
"icon": "ri-cpu-line",
"color": "purple",
"description": "Intel、AMD等CPU处理器参数",
"order": 3
"order": 3,
"subcategories": [
{
"id": "desktop",
"name": "桌面CPU",
"icon": "ri-computer-line",
"key_features": ["cores", "threads", "boost_clock_ghz", "price_usd"],
"feature_labels": {
"cores": "核心",
"threads": "线程",
"boost_clock_ghz": "加速频率",
"price_usd": "价格"
}
},
{
"id": "server",
"name": "服务器CPU",
"icon": "ri-server-line",
"key_features": ["cores", "threads", "l3_cache_mb", "tdp_watts"],
"feature_labels": {
"cores": "核心",
"threads": "线程",
"l3_cache_mb": "L3缓存",
"tdp_watts": "功耗"
}
},
{
"id": "mobile",
"name": "移动CPU",
"icon": "ri-smartphone-line",
"key_features": ["cores", "threads", "base_clock_ghz", "tdp_watts"],
"feature_labels": {
"cores": "核心",
"threads": "线程",
"base_clock_ghz": "基础频率",
"tdp_watts": "功耗"
}
}
]
},
{
"id": "phones",
@@ -30,7 +153,33 @@
"color": "orange",
"description": "各品牌手机参数规格",
"order": 4,
"visible": false
"visible": true,
"subcategories": [
{
"id": "flagship",
"name": "旗舰手机",
"icon": "ri-star-line",
"key_features": ["processor", "ram_gb", "storage_gb", "price"],
"feature_labels": {
"processor": "处理器",
"ram_gb": "内存",
"storage_gb": "存储",
"price": "价格"
}
},
{
"id": "midrange",
"name": "中端手机",
"icon": "ri-price-tag-3-line",
"key_features": ["processor", "ram_gb", "battery_mah", "price"],
"feature_labels": {
"processor": "处理器",
"ram_gb": "内存",
"battery_mah": "电池",
"price": "价格"
}
}
]
},
{
"id": "laptops",
@@ -38,7 +187,33 @@
"icon": "ri-macbook-line",
"color": "teal",
"description": "笔记本电脑、台式机参数",
"order": 5
"order": 5,
"subcategories": [
{
"id": "gaming-laptop",
"name": "游戏笔记本",
"icon": "ri-gamepad-line",
"key_features": ["processor", "gpu", "ram_gb", "price"],
"feature_labels": {
"processor": "处理器",
"gpu": "显卡",
"ram_gb": "内存",
"price": "价格"
}
},
{
"id": "business-laptop",
"name": "商务笔记本",
"icon": "ri-briefcase-line",
"key_features": ["processor", "ram_gb", "weight_kg", "price"],
"feature_labels": {
"processor": "处理器",
"ram_gb": "内存",
"weight_kg": "重量",
"price": "价格"
}
}
]
},
{
"id": "021dc76d36be",
@@ -47,6 +222,66 @@
"color": "red",
"order": 6,
"description": "汽车方面",
"created_at": "2026-04-09 10:09:01"
"created_at": "2026-04-09 10:09:01",
"subcategories": [
{
"id": "sedan",
"name": "轿车",
"icon": "ri-car-line",
"key_features": ["engine", "power_kw", "price"],
"feature_labels": {
"engine": "发动机",
"power_kw": "功率",
"price": "价格"
}
},
{
"id": "suv",
"name": "SUV",
"icon": "ri-truck-line",
"key_features": ["engine", "seats", "price"],
"feature_labels": {
"engine": "发动机",
"seats": "座位数",
"price": "价格"
}
}
]
},
{
"id": "71fa2b4d818f",
"name": "摄像",
"icon": "ri-camera-line",
"color": "blue",
"order": 0,
"visible": true,
"description": "相机、摄像机等",
"created_at": "2026-04-25 16:38:47",
"subcategories": [
{
"id": "mirrorless",
"name": "无反相机",
"icon": "ri-camera-line",
"key_features": ["sensor", "megapixels", "video_resolution", "price"],
"feature_labels": {
"sensor": "传感器",
"megapixels": "像素",
"video_resolution": "视频",
"price": "价格"
}
},
{
"id": "dslr",
"name": "单反相机",
"icon": "ri-camera-2-line",
"key_features": ["sensor", "megapixels", "lens_mount", "price"],
"feature_labels": {
"sensor": "传感器",
"megapixels": "像素",
"lens_mount": "卡口",
"price": "价格"
}
}
]
}
]

View File

@@ -369,6 +369,23 @@
</div>
</div>
<!-- 子类别编辑弹框 -->
<div id="subcategoryModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-xl max-w-lg w-full mx-4">
<div class="p-6 border-b flex justify-between items-center">
<h2 class="text-xl font-bold text-gray-800" id="subcategoryModalTitle"><i class="ri-folder-line mr-2"></i>添加子类别</h2>
<button onclick="closeSubcategoryModal()" class="text-gray-400 hover:text-gray-600"><i class="ri-close-line text-2xl"></i></button>
</div>
<div id="subcategoryModalContent" class="p-6">
<!-- 动态内容 -->
</div>
<div class="p-6 border-t flex justify-end gap-4">
<button onclick="closeSubcategoryModal()" class="px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300">取消</button>
<button onclick="saveSubcategory()" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"><i class="ri-save-line mr-1"></i>保存</button>
</div>
</div>
</div>
<script>
let currentType = '';
let currentId = '';
@@ -922,6 +939,13 @@
else if (key === 'images') {
try { data[key] = JSON.parse(value); } catch { data[key] = []; }
}
else if (key === 'subcategories') {
// 解析子类别JSON
try { data[key] = JSON.parse(value); } catch {
alert('子类别JSON格式错误请检查格式');
return;
}
}
else data[key] = value;
}
});
@@ -1096,6 +1120,10 @@
// 表单模板
function getCategoryForm(data = {}) {
const subcategories = data.subcategories || [];
// 存储到全局变量,便于管理
window.currentEditingSubcategories = JSON.parse(JSON.stringify(subcategories));
return `<form id="itemForm" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div><label class="text-sm text-gray-600 mb-1 block">ID *</label><input type="text" name="id" value="${data.id || ''}" ${data.id ? 'readonly' : ''} required class="w-full px-3 py-2 border rounded-lg ${data.id ? 'bg-gray-100' : ''}"></div>
@@ -1116,9 +1144,158 @@
</select></div>
</div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="2" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
<!-- 子类别管理 -->
<div class="border-t pt-4">
<div class="flex justify-between items-center mb-3">
<label class="text-sm font-medium text-gray-700"><i class="ri-folder-line mr-1"></i>子类别管理</label>
<button onclick="openSubcategoryAddModal()" class="px-3 py-1.5 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700">
<i class="ri-add-line mr-1"></i>添加子类别
</button>
</div>
<div id="subcategoriesList" class="space-y-2">
${renderSubcategoriesList(subcategories)}
</div>
<input type="hidden" name="subcategories" id="subcategoriesHidden" value='${JSON.stringify(subcategories)}'>
</div>
</form>`;
}
// 渲染子类别列表
function renderSubcategoriesList(subcategories) {
if (!subcategories || subcategories.length === 0) {
return '<div class="text-gray-400 text-sm py-4 text-center bg-gray-50 rounded-lg">暂无子类别,点击上方按钮添加</div>';
}
return subcategories.map((sub, index) => `
<div class="bg-gray-50 rounded-lg p-3 flex justify-between items-center group hover:bg-gray-100">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-indigo-100 flex items-center justify-center">
<i class="${sub.icon || 'ri-folder-line'} text-indigo-600"></i>
</div>
<div>
<div class="font-medium text-gray-800">${sub.name}</div>
<div class="text-xs text-gray-500">ID: ${sub.id} | 特性: ${(sub.key_features || []).join(', ')}</div>
</div>
</div>
<div class="flex gap-2 opacity-0 group-hover:opacity-100 transition">
<button onclick="editSubcategory(${index})" class="px-2 py-1 text-blue-600 hover:bg-blue-50 rounded text-sm">
<i class="ri-edit-line"></i>
</button>
<button onclick="deleteSubcategory(${index})" class="px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm">
<i class="ri-delete-bin-line"></i>
</button>
</div>
</div>
`).join('');
}
// 打开子类别添加弹框
function openSubcategoryAddModal() {
document.getElementById('subcategoryModalTitle').textContent = '添加子类别';
document.getElementById('subcategoryModalContent').innerHTML = getSubcategoryForm();
document.getElementById('subcategoryModal').classList.remove('hidden');
window.editingSubcategoryIndex = -1;
}
// 编辑子类别
function editSubcategory(index) {
const sub = window.currentEditingSubcategories[index];
document.getElementById('subcategoryModalTitle').textContent = '编辑子类别';
document.getElementById('subcategoryModalContent').innerHTML = getSubcategoryForm(sub);
document.getElementById('subcategoryModal').classList.remove('hidden');
window.editingSubcategoryIndex = index;
}
// 删除子类别
function deleteSubcategory(index) {
if (!confirm('确定删除此子类别?')) return;
window.currentEditingSubcategories.splice(index, 1);
document.getElementById('subcategoriesList').innerHTML = renderSubcategoriesList(window.currentEditingSubcategories);
document.getElementById('subcategoriesHidden').value = JSON.stringify(window.currentEditingSubcategories);
}
// 子类别表单
function getSubcategoryForm(data = {}) {
const keyFeatures = (data.key_features || []).join(', ');
const featureLabels = data.feature_labels || {};
const featureLabelsStr = Object.entries(featureLabels).map(([k, v]) => `${k}:${v}`).join(', ');
return `<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div><label class="text-sm text-gray-600 mb-1 block">ID *</label><input type="text" id="sub_id" value="${data.id || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" id="sub_name" value="${data.name || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">图标</label><input type="text" id="sub_icon" value="${data.icon || 'ri-folder-line'}" class="w-full px-3 py-2 border rounded-lg" placeholder="ri-folder-line"></div>
</div>
<div>
<label class="text-sm text-gray-600 mb-1 block">关键特性字段</label>
<input type="text" id="sub_key_features" value="${keyFeatures}" class="w-full px-3 py-2 border rounded-lg" placeholder="context_length, mmlu, input_price">
<p class="text-xs text-gray-500 mt-1">逗号分隔context_length, mmlu, input_price</p>
</div>
<div>
<label class="text-sm text-gray-600 mb-1 block">特性标签(显示名)</label>
<input type="text" id="sub_feature_labels" value="${featureLabelsStr}" class="w-full px-3 py-2 border rounded-lg" placeholder="context_length:上下文, mmlu:MMLU">
<p class="text-xs text-gray-500 mt-1">格式:字段名:显示名,逗号分隔</p>
</div>
</div>`;
}
// 保存子类别
function saveSubcategory() {
const id = document.getElementById('sub_id').value.trim();
const name = document.getElementById('sub_name').value.trim();
const icon = document.getElementById('sub_icon').value.trim() || 'ri-folder-line';
const keyFeaturesStr = document.getElementById('sub_key_features').value.trim();
const featureLabelsStr = document.getElementById('sub_feature_labels').value.trim();
if (!id || !name) {
alert('ID和名称不能为空');
return;
}
// 解析 key_features
const key_features = keyFeaturesStr ? keyFeaturesStr.split(',').map(s => s.trim()).filter(s => s) : [];
// 解析 feature_labels
const feature_labels = {};
if (featureLabelsStr) {
featureLabelsStr.split(',').forEach(pair => {
const [key, value] = pair.split(':').map(s => s.trim());
if (key && value) {
feature_labels[key] = value;
}
});
}
const subcategory = {
id,
name,
icon,
key_features,
feature_labels
};
if (window.editingSubcategoryIndex === -1) {
// 添加新子类别
window.currentEditingSubcategories.push(subcategory);
} else {
// 编辑现有子类别
window.currentEditingSubcategories[window.editingSubcategoryIndex] = subcategory;
}
// 更新显示
document.getElementById('subcategoriesList').innerHTML = renderSubcategoriesList(window.currentEditingSubcategories);
document.getElementById('subcategoriesHidden').value = JSON.stringify(window.currentEditingSubcategories);
// 关闭弹框
closeSubcategoryModal();
}
// 关闭子类别弹框
function closeSubcategoryModal() {
document.getElementById('subcategoryModal').classList.add('hidden');
}
function getKnowledgeForm(data = {}) {
return `<form id="itemForm" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
@@ -1235,6 +1412,7 @@
document.getElementById('editModal').addEventListener('click', function(e) { if (e.target === this) closeModal(); });
document.getElementById('smartAddModal').addEventListener('click', function(e) { if (e.target === this) closeSmartAddModal(); });
document.getElementById('rawDataModal').addEventListener('click', function(e) { if (e.target === this) closeRawDataModal(); });
document.getElementById('subcategoryModal').addEventListener('click', function(e) { if (e.target === this) closeSubcategoryModal(); });
// ============ 智能添加功能 ============

View File

@@ -32,6 +32,19 @@
<p class="text-gray-500 mt-1">AI大模型参数规格一览</p>
</div>
<!-- 子类别选择器 -->
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
<div class="flex items-center gap-2 mb-2">
<span class="text-sm text-gray-600"><i class="ri-folder-line mr-1"></i>子类别:</span>
</div>
<div class="flex gap-2" id="subcategoryTabs">
<button onclick="selectSubcategory('')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg text-sm" id="subcat-all">
<i class="ri-apps-line mr-1"></i>全部
</button>
<!-- 动态加载子类别 -->
</div>
</div>
<!-- 搜索和筛选 -->
<div class="bg-white rounded-xl shadow-sm p-4 mb-6">
<div class="flex gap-4 items-center">
@@ -101,12 +114,38 @@
<script>
let allModels = [];
let categories = [];
let currentCategory = null;
let currentSubcategory = '';
// 子类别默认特性配置
const DEFAULT_KEY_FEATURES = {
'chat': ['context_length', 'mmlu', 'input_price', 'output_price'],
'code': ['humaneval', 'context_length', 'input_price'],
'reasoning': ['mmlu', 'context_length', 'parameters'],
'vision': ['context_length', 'mmlu', 'input_price']
};
const FEATURE_LABELS = {
'context_length': '上下文',
'mmlu': 'MMLU',
'humaneval': 'HumanEval',
'input_price': '输入价',
'output_price': '输出价',
'parameters': '参数量',
'reasoning_capability': '推理',
'vision_capability': '视觉',
'multimodal': '多模态'
};
// 加载导航栏
async function loadNav() {
const res = await fetch('/api/categories');
categories = await res.json();
// 获取当前类别的子类别
currentCategory = categories.find(c => c.id === 'ai-models');
renderSubcategoryTabs();
const builtinPages = [
{name: '首页', href: '/'},
{name: '工具', href: '/tools'},
@@ -135,6 +174,49 @@
document.getElementById('topNav').innerHTML = navHtml;
}
// 渲染子类别选择器
function renderSubcategoryTabs() {
const container = document.getElementById('subcategoryTabs');
if (!currentCategory || !currentCategory.subcategories) {
container.innerHTML = '';
return;
}
let html = `<button onclick="selectSubcategory('')" class="px-4 py-2 ${currentSubcategory === '' ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'} rounded-lg text-sm" id="subcat-all">
<i class="ri-apps-line mr-1"></i>全部
</button>`;
currentCategory.subcategories.forEach(sub => {
const isActive = currentSubcategory === sub.id;
html += `<button onclick="selectSubcategory('${sub.id}')" class="px-4 py-2 ${isActive ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'} rounded-lg text-sm" id="subcat-${sub.id}">
<i class="${sub.icon || 'ri-folder-line'} mr-1"></i>${sub.name}
</button>`;
});
container.innerHTML = html;
}
// 选择子类别
function selectSubcategory(subId) {
currentSubcategory = subId;
renderSubcategoryTabs();
loadModels();
}
// 获取当前子类别的关键特性
function getKeyFeatures() {
if (!currentSubcategory || !currentCategory || !currentCategory.subcategories) {
return ['parameters', 'context_length', 'mmlu', 'input_price'];
}
const subcat = currentCategory.subcategories.find(s => s.id === currentSubcategory);
if (subcat && subcat.key_features) {
return subcat.key_features;
}
return ['parameters', 'context_length', 'mmlu', 'input_price'];
}
async function loadModels() {
const keyword = document.getElementById('searchInput').value.trim();
const sortBy = document.getElementById('sortBy').value;
@@ -155,6 +237,21 @@
models = models.filter(m => !m.is_open_source);
}
// 子类别过滤(通过模型名称/描述中的关键词判断)
if (currentSubcategory && currentCategory && currentCategory.subcategories) {
const subcat = currentCategory.subcategories.find(s => s.id === currentSubcategory);
if (subcat) {
// 简化过滤:根据子类别关键词匹配
// 实际应该有 subcategory_id 字段,这里暂时用名称匹配
// 用户可以在后台编辑时指定子类别
models = models.filter(m => {
const subcatField = m.subcategory || m.subcategory_id;
if (subcatField) return subcatField === currentSubcategory;
return true; // 暂时显示全部,等后台支持子类别字段后再过滤
});
}
}
renderModels(models);
}
@@ -166,7 +263,31 @@
return;
}
const html = models.map(m => `
// 动态获取关键特性
const keyFeatures = getKeyFeatures();
// 动态表头
let headerHtml = `
<tr>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">模型名称</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">厂商</th>
`;
keyFeatures.forEach(f => {
headerHtml += `<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">${FEATURE_LABELS[f] || f}</th>`;
});
headerHtml += `
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">类型</th>
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
</tr>
`;
document.querySelector('#modelsTable thead').innerHTML = headerHtml;
// 动态内容
const html = models.map(m => {
let rowHtml = `
<tr class="border-b hover:bg-gray-50 transition ${m.is_pinned ? 'bg-yellow-50' : ''}">
<td class="px-4 py-3">
<div class="flex items-center gap-2">
@@ -178,32 +299,58 @@
</div>
</td>
<td class="px-4 py-3 text-gray-600">${m.organization}</td>
<td class="px-4 py-3">
<span class="px-2 py-1 bg-blue-100 text-blue-700 rounded text-sm">${m.parameters}B</span>
</td>
<td class="px-4 py-3 text-gray-600">${formatContext(m.context_length)}</td>
<td class="px-4 py-3">
<span class="px-2 py-1 bg-green-100 text-green-700 rounded text-sm">${m.mmlu || '-'}%</span>
</td>
`;
// 关键特性列
keyFeatures.forEach(f => {
const value = formatFeatureValue(f, m);
rowHtml += `<td class="px-4 py-3">${value}</td>`;
});
rowHtml += `
<td class="px-4 py-3">
${m.is_open_source
? '<span class="px-2 py-1 bg-emerald-100 text-emerald-700 rounded text-sm">开源</span>'
: '<span class="px-2 py-1 bg-gray-100 text-gray-700 rounded text-sm">商业</span>'}
</td>
<td class="px-4 py-3 text-sm text-gray-600">
${m.input_price ? `$${m.input_price}/$${m.output_price}` : '免费'}
</td>
<td class="px-4 py-3 text-center">
<button onclick="showDetail('${m.id}')" class="text-indigo-600 hover:text-indigo-800 text-sm">
<i class="ri-eye-line mr-1"></i>详情
</button>
</td>
</tr>
`).join('');
`;
return rowHtml;
}).join('');
document.getElementById('modelsTable').innerHTML = html;
}
// 格式化特性值
function formatFeatureValue(feature, model) {
const value = model[feature];
if (value === null || value === undefined) return '<span class="text-gray-400">-</span>';
switch (feature) {
case 'parameters':
return `<span class="px-2 py-1 bg-blue-100 text-blue-700 rounded text-sm">${value}B</span>`;
case 'context_length':
return `<span class="text-gray-600">${formatContext(value)}</span>`;
case 'mmlu':
return `<span class="px-2 py-1 bg-green-100 text-green-700 rounded text-sm">${value}%</span>`;
case 'humaneval':
return `<span class="px-2 py-1 bg-purple-100 text-purple-700 rounded text-sm">${value}%</span>`;
case 'input_price':
return `<span class="text-sm text-gray-600">$${value || 0}</span>`;
case 'output_price':
return `<span class="text-sm text-gray-600">$${value || 0}</span>`;
default:
return `<span class="text-gray-600">${value}</span>`;
}
}
function formatContext(len) {
if (!len) return '-';
if (len >= 1000000) return (len / 1000) + 'K';