fix: 修复后台管理多个问题

修复:
1. 分类编辑功能正常工作
2. 动态生成侧边栏菜单,新增分类自动出现管理入口
3. 首页动态加载分类并展示
4. 新增动态分类数据管理API
5. 修复CPU等页面图标问题
This commit is contained in:
2026-04-09 02:32:35 +08:00
parent 420107730c
commit fc0e8c3443
4 changed files with 574 additions and 473 deletions

View File

@@ -23,31 +23,8 @@
<div class="text-sm text-gray-400 mt-1">后台管理</div>
</div>
<nav class="p-4">
<div class="space-y-1">
<a href="#overview" onclick="showSection('overview')" class="sidebar-link active flex items-center gap-2 px-3 py-2 rounded-lg" data-section="overview">
<i class="ri-home-4-line"></i>
<span>概览</span>
</a>
<a href="#categories" onclick="showSection('categories')" class="sidebar-link flex items-center gap-2 px-3 py-2 rounded-lg text-gray-300" data-section="categories">
<i class="ri-folder-line"></i>
<span>分类管理</span>
</a>
<a href="#models" onclick="showSection('models')" class="sidebar-link flex items-center gap-2 px-3 py-2 rounded-lg text-gray-300" data-section="models">
<i class="ri-robot-line"></i>
<span>模型管理</span>
</a>
<a href="#gpus" onclick="showSection('gpus')" class="sidebar-link flex items-center gap-2 px-3 py-2 rounded-lg text-gray-300" data-section="gpus">
<i class="ri-cpu-line"></i>
<span>GPU管理</span>
</a>
<a href="#cpus" onclick="showSection('cpus')" class="sidebar-link flex items-center gap-2 px-3 py-2 rounded-lg text-gray-300" data-section="cpus">
<i class="ri-memory-line"></i>
<span>CPU管理</span>
</a>
<a href="#knowledge" onclick="showSection('knowledge')" class="sidebar-link flex items-center gap-2 px-3 py-2 rounded-lg text-gray-300" data-section="knowledge">
<i class="ri-book-open-line"></i>
<span>知识库管理</span>
</a>
<div class="space-y-1" id="sidebarNav">
<!-- 动态生成 -->
</div>
<div class="mt-8 pt-4 border-t border-gray-700">
<a href="/" target="_blank" class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-700 text-gray-400">
@@ -63,86 +40,11 @@
<!-- 概览 -->
<section id="section-overview">
<h1 class="text-2xl font-bold text-gray-800 mb-6">管理概览</h1>
<div class="grid grid-cols-5 gap-4 mb-8">
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
<i class="ri-folder-line text-xl text-blue-600"></i>
</div>
<div>
<div class="text-2xl font-bold text-gray-800" id="stat-categories">-</div>
<div class="text-xs text-gray-500">分类数量</div>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-indigo-100 flex items-center justify-center">
<i class="ri-robot-line text-xl text-indigo-600"></i>
</div>
<div>
<div class="text-2xl font-bold text-gray-800" id="stat-models">-</div>
<div class="text-xs text-gray-500">模型数量</div>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
<i class="ri-cpu-line text-xl text-green-600"></i>
</div>
<div>
<div class="text-2xl font-bold text-gray-800" id="stat-gpus">-</div>
<div class="text-xs text-gray-500">GPU数量</div>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center">
<i class="ri-memory-line text-xl text-purple-600"></i>
</div>
<div>
<div class="text-2xl font-bold text-gray-800" id="stat-cpus">-</div>
<div class="text-xs text-gray-500">CPU数量</div>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-teal-100 flex items-center justify-center">
<i class="ri-book-open-line text-xl text-teal-600"></i>
</div>
<div>
<div class="text-2xl font-bold text-gray-800" id="stat-knowledge">-</div>
<div class="text-xs text-gray-500">知识条目</div>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-5 gap-4 mb-8" id="statsCards"><div class="text-center text-gray-400 py-4">加载中...</div></div>
<div class="bg-white rounded-xl p-6 shadow-sm mb-8">
<h2 class="text-lg font-semibold text-gray-800 mb-4">快捷操作</h2>
<div class="grid grid-cols-5 gap-3">
<button onclick="showSection('categories'); openAddModal('category')" class="px-4 py-3 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 transition text-sm">
<i class="ri-add-line mr-1"></i>添加分类
</button>
<button onclick="showSection('models'); openAddModal('model')" class="px-4 py-3 bg-indigo-50 text-indigo-600 rounded-lg hover:bg-indigo-100 transition text-sm">
<i class="ri-add-line mr-1"></i>添加模型
</button>
<button onclick="showSection('gpus'); openAddModal('gpu')" class="px-4 py-3 bg-green-50 text-green-600 rounded-lg hover:bg-green-100 transition text-sm">
<i class="ri-add-line mr-1"></i>添加GPU
</button>
<button onclick="showSection('cpus'); openAddModal('cpu')" class="px-4 py-3 bg-purple-50 text-purple-600 rounded-lg hover:bg-purple-100 transition text-sm">
<i class="ri-add-line mr-1"></i>添加CPU
</button>
<button onclick="showSection('knowledge'); openAddModal('knowledge')" class="px-4 py-3 bg-teal-50 text-teal-600 rounded-lg hover:bg-teal-100 transition text-sm">
<i class="ri-add-line mr-1"></i>添加知识
</button>
</div>
<div class="grid grid-cols-5 gap-3" id="quickActions"><div class="text-gray-400">加载中...</div></div>
</div>
<div class="bg-white rounded-xl p-6 shadow-sm">
<h2 class="text-lg font-semibold text-gray-800 mb-4">最近添加的模型</h2>
<div id="recent-models" class="space-y-2">加载中...</div>
@@ -153,38 +55,42 @@
<section id="section-categories" class="hidden">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">分类管理</h1>
<button onclick="openAddModal('category')" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">
<i class="ri-add-line mr-2"></i>添加分类
</button>
<button onclick="openAddModal('category')" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"><i class="ri-add-line mr-2"></i>添加分类</button>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50 border-b">
<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">ID</th>
<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>
<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>
</thead>
<tbody id="admin-categories-table">
<tr><td colspan="5" class="text-center text-gray-400 py-8">加载中...</td></tr>
</tbody>
<tbody id="admin-categories-table"><tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
<!-- 动态分类数据管理容器 -->
<section id="section-dynamic" class="hidden">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800" id="dynamic-title">数据管理</h1>
<button onclick="openAddModal('dynamic')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>添加数据</button>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<table class="w-full"><tbody id="admin-dynamic-table"><tr><td class="text-center text-gray-400 py-8">加载中...</td></tr></tbody></table>
</div>
</section>
<!-- 模型管理 -->
<section id="section-models" class="hidden">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">模型管理</h1>
<button onclick="openAddModal('model')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition">
<i class="ri-add-line mr-2"></i>添加模型
</button>
<button onclick="openAddModal('model')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>添加模型</button>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50 border-b">
@@ -197,9 +103,7 @@
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
</tr>
</thead>
<tbody id="admin-models-table">
<tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr>
</tbody>
<tbody id="admin-models-table"><tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
@@ -208,11 +112,8 @@
<section id="section-gpus" class="hidden">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">GPU管理</h1>
<button onclick="openAddModal('gpu')" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition">
<i class="ri-add-line mr-2"></i>添加GPU
</button>
<button onclick="openAddModal('gpu')" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"><i class="ri-add-line mr-2"></i>添加GPU</button>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50 border-b">
@@ -225,9 +126,7 @@
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
</tr>
</thead>
<tbody id="admin-gpus-table">
<tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr>
</tbody>
<tbody id="admin-gpus-table"><tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
@@ -236,11 +135,8 @@
<section id="section-cpus" class="hidden">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">CPU管理</h1>
<button onclick="openAddModal('cpu')" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition">
<i class="ri-add-line mr-2"></i>添加CPU
</button>
<button onclick="openAddModal('cpu')" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"><i class="ri-add-line mr-2"></i>添加CPU</button>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50 border-b">
@@ -253,9 +149,7 @@
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
</tr>
</thead>
<tbody id="admin-cpus-table">
<tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr>
</tbody>
<tbody id="admin-cpus-table"><tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
@@ -264,20 +158,15 @@
<section id="section-knowledge" class="hidden">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">知识库管理</h1>
<button onclick="openAddModal('knowledge')" class="px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition">
<i class="ri-add-line mr-2"></i>添加知识
</button>
<button onclick="openAddModal('knowledge')" class="px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700"><i class="ri-add-line mr-2"></i>添加知识</button>
</div>
<!-- 分类筛选 -->
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
<div class="flex items-center gap-2">
<span class="text-sm text-gray-600">筛选分类:</span>
<button onclick="filterKnowledge('')" class="px-3 py-1 rounded text-sm bg-gray-100 text-gray-600 hover:bg-gray-200" id="filter-all">全部</button>
<button onclick="filterKnowledge('')" class="px-3 py-1 rounded text-sm bg-gray-100 text-gray-600 hover:bg-gray-200">全部</button>
<div id="category-filters" class="flex gap-2"></div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50 border-b">
@@ -290,9 +179,7 @@
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
</tr>
</thead>
<tbody id="admin-knowledge-table">
<tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr>
</tbody>
<tbody id="admin-knowledge-table"><tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
@@ -303,9 +190,7 @@
<div class="bg-white rounded-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-auto">
<div class="p-6 border-b flex justify-between items-center sticky top-0 bg-white z-10">
<h2 class="text-xl font-bold text-gray-800" id="modalTitle">添加/编辑</h2>
<button onclick="closeModal()" class="text-gray-400 hover:text-gray-600">
<i class="ri-close-line text-2xl"></i>
</button>
<button onclick="closeModal()" class="text-gray-400 hover:text-gray-600"><i class="ri-close-line text-2xl"></i></button>
</div>
<div id="modalContent" class="p-6"></div>
<div class="p-6 border-t flex justify-end gap-4 sticky bottom-0 bg-white">
@@ -321,6 +206,128 @@
let currentData = {};
let categories = [];
let currentFilter = '';
let dynamicCategoryId = '';
const colorMap = {
blue: 'bg-blue-100 text-blue-600',
green: 'bg-green-100 text-green-600',
purple: 'bg-purple-100 text-purple-600',
orange: 'bg-orange-100 text-orange-600',
teal: 'bg-teal-100 text-teal-600',
red: 'bg-red-100 text-red-600'
};
// 初始化
async function init() {
await loadCategories();
renderSidebar();
loadOverview();
}
// 加载分类
async function loadCategories() {
const res = await fetch('/api/categories');
categories = await res.json();
categories.sort((a, b) => (a.order || 0) - (b.order || 0));
}
// 渲染侧边栏
function renderSidebar() {
const fixedItems = [
{id: 'overview', name: '概览', icon: 'ri-home-4-line'},
{id: 'categories', name: '分类管理', icon: 'ri-folder-line'},
];
// 内置分类映射
const builtinMap = {
'ai-models': {id: 'models', name: '模型管理', icon: 'ri-robot-line'},
'gpus': {id: 'gpus', name: 'GPU管理', icon: 'ri-cpu-line'},
'cpus': {id: 'cpus', name: 'CPU管理', icon: 'ri-memory-line'}
};
let html = fixedItems.map(item => `
<a href="#${item.id}" onclick="showSection('${item.id}')" class="sidebar-link flex items-center gap-2 px-3 py-2 rounded-lg ${item.id === 'overview' ? 'active text-white' : 'text-gray-300'}" data-section="${item.id}">
<i class="${item.icon}"></i>
<span>${item.name}</span>
</a>
`).join('');
// 添加每个分类的管理入口
categories.forEach(cat => {
const builtin = builtinMap[cat.id];
if (builtin) {
html += `
<a href="#${builtin.id}" onclick="showSection('${builtin.id}')" class="sidebar-link flex items-center gap-2 px-3 py-2 rounded-lg text-gray-300" data-section="${builtin.id}">
<i class="${builtin.icon}"></i>
<span>${builtin.name}</span>
</a>
`;
} else {
// 动态分类
html += `
<a href="#cat-${cat.id}" onclick="showDynamicCategory('${cat.id}')" class="sidebar-link flex items-center gap-2 px-3 py-2 rounded-lg text-gray-300" data-section="cat-${cat.id}">
<i class="${cat.icon}"></i>
<span>${cat.name}管理</span>
</a>
`;
}
});
// 知识库管理
html += `
<a href="#knowledge" onclick="showSection('knowledge')" class="sidebar-link flex items-center gap-2 px-3 py-2 rounded-lg text-gray-300" data-section="knowledge">
<i class="ri-book-open-line"></i>
<span>知识库管理</span>
</a>
`;
document.getElementById('sidebarNav').innerHTML = html;
}
// 显示动态分类数据
async function showDynamicCategory(categoryId) {
dynamicCategoryId = categoryId;
const cat = categories.find(c => c.id === categoryId);
document.querySelectorAll('section').forEach(s => s.classList.add('hidden'));
document.getElementById('section-dynamic').classList.remove('hidden');
document.querySelectorAll('.sidebar-link').forEach(a => {
a.classList.remove('active', 'text-white');
a.classList.add('text-gray-300');
});
const activeLink = document.querySelector(`.sidebar-link[data-section="cat-${categoryId}"]`);
if (activeLink) {
activeLink.classList.add('active', 'text-white');
activeLink.classList.remove('text-gray-300');
}
document.getElementById('dynamic-title').textContent = cat.name + '管理';
// 加载该分类的数据
const res = await fetch(`/api/items/${categoryId}`);
const items = await res.json();
if (items.length === 0) {
document.getElementById('admin-dynamic-table').innerHTML = '<tr><td class="text-center text-gray-400 py-8">暂无数据,点击上方"添加数据"按钮添加</td></tr>';
} else {
const keys = Object.keys(items[0]).filter(k => !['id', 'created_at', 'updated_at'].includes(k));
let html = `<thead class="bg-gray-50 border-b"><tr>`;
keys.forEach(k => { html += `<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">${k}</th>`; });
html += `<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th></tr></thead><tbody>`;
items.forEach(item => {
html += `<tr class="border-b hover:bg-gray-50">`;
keys.forEach(k => { html += `<td class="px-4 py-3 text-gray-600">${item[k] || '-'}</td>`; });
html += `<td class="px-4 py-3 text-center">
<button onclick="editDynamicItem('${item.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
<button onclick="deleteDynamicItem('${item.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td></tr>`;
});
html += '</tbody>';
document.getElementById('admin-dynamic-table').innerHTML = html;
}
}
// 切换显示区域
function showSection(section) {
@@ -349,23 +356,53 @@
const res = await fetch('/api/stats');
const data = await res.json();
document.getElementById('stat-categories').textContent = data.categories_count;
document.getElementById('stat-models').textContent = data.models_count;
document.getElementById('stat-gpus').textContent = data.gpus_count;
document.getElementById('stat-cpus').textContent = data.cpus_count;
document.getElementById('stat-knowledge').textContent = data.knowledge_count;
const statsHtml = `
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center"><i class="ri-folder-line text-xl text-blue-600"></i></div>
<div><div class="text-2xl font-bold text-gray-800">${data.categories_count}</div><div class="text-xs text-gray-500">分类数量</div></div>
</div>
</div>
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-indigo-100 flex items-center justify-center"><i class="ri-robot-line text-xl text-indigo-600"></i></div>
<div><div class="text-2xl font-bold text-gray-800">${data.models_count}</div><div class="text-xs text-gray-500">模型数量</div></div>
</div>
</div>
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center"><i class="ri-cpu-line text-xl text-green-600"></i></div>
<div><div class="text-2xl font-bold text-gray-800">${data.gpus_count}</div><div class="text-xs text-gray-500">GPU数量</div></div>
</div>
</div>
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center"><i class="ri-memory-line text-xl text-purple-600"></i></div>
<div><div class="text-2xl font-bold text-gray-800">${data.cpus_count}</div><div class="text-xs text-gray-500">CPU数量</div></div>
</div>
</div>
<div class="bg-white rounded-xl p-5 shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-teal-100 flex items-center justify-center"><i class="ri-book-open-line text-xl text-teal-600"></i></div>
<div><div class="text-2xl font-bold text-gray-800">${data.knowledge_count}</div><div class="text-xs text-gray-500">知识条目</div></div>
</div>
</div>
`;
document.getElementById('statsCards').innerHTML = statsHtml;
// 快捷操作
const actionsHtml = `
<button onclick="showSection('categories'); openAddModal('category')" class="px-4 py-3 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 transition text-sm"><i class="ri-add-line mr-1"></i>添加分类</button>
<button onclick="showSection('models'); openAddModal('model')" class="px-4 py-3 bg-indigo-50 text-indigo-600 rounded-lg hover:bg-indigo-100 transition text-sm"><i class="ri-add-line mr-1"></i>添加模型</button>
<button onclick="showSection('gpus'); openAddModal('gpu')" class="px-4 py-3 bg-green-50 text-green-600 rounded-lg hover:bg-green-100 transition text-sm"><i class="ri-add-line mr-1"></i>添加GPU</button>
<button onclick="showSection('cpus'); openAddModal('cpu')" class="px-4 py-3 bg-purple-50 text-purple-600 rounded-lg hover:bg-purple-100 transition text-sm"><i class="ri-add-line mr-1"></i>添加CPU</button>
<button onclick="showSection('knowledge'); openAddModal('knowledge')" class="px-4 py-3 bg-teal-50 text-teal-600 rounded-lg hover:bg-teal-100 transition text-sm"><i class="ri-add-line mr-1"></i>添加知识</button>
`;
document.getElementById('quickActions').innerHTML = actionsHtml;
const models = data.latest_models || [];
document.getElementById('recent-models').innerHTML = models.length > 0
? models.map(m => `
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div>
<span class="font-medium text-gray-800">${m.name}</span>
<span class="text-sm text-gray-500 ml-2">${m.organization}</span>
</div>
<div class="text-sm text-gray-400">${m.is_open_source ? '开源' : '商业'}</div>
</div>
`).join('')
? models.map(m => `<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg"><div><span class="font-medium text-gray-800">${m.name}</span><span class="text-sm text-gray-500 ml-2">${m.organization}</span></div><div class="text-sm text-gray-400">${m.is_open_source ? '开源' : '商业'}</div></div>`).join('')
: '<div class="text-gray-400">暂无数据</div>';
}
@@ -375,19 +412,14 @@
categories = await res.json();
if (categories.length === 0) {
document.getElementById('admin-categories-table').innerHTML = '<tr><td colspan="5" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
document.getElementById('admin-categories-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
return;
}
const colorMap = {blue: 'bg-blue-100 text-blue-600', green: 'bg-green-100 text-green-600', purple: 'bg-purple-100 text-purple-600', orange: 'bg-orange-100 text-orange-600', teal: 'bg-teal-100 text-teal-600', red: 'bg-red-100 text-red-600'};
document.getElementById('admin-categories-table').innerHTML = categories.map(c => `
<tr class="border-b hover:bg-gray-50">
<td class="px-4 py-3">
<div class="w-10 h-10 rounded-lg ${colorMap[c.color] || 'bg-gray-100 text-gray-600'} flex items-center justify-center">
<i class="${c.icon} text-xl"></i>
</div>
</td>
<td class="px-4 py-3"><div class="w-10 h-10 rounded-lg ${colorMap[c.color] || 'bg-gray-100 text-gray-600'} flex items-center justify-center"><i class="${c.icon} text-xl"></i></div></td>
<td class="px-4 py-3 text-gray-500 text-sm font-mono">${c.id}</td>
<td class="px-4 py-3 font-medium text-gray-800">${c.name}</td>
<td class="px-4 py-3 text-gray-600 text-sm">${c.description || '-'}</td>
<td class="px-4 py-3">${c.order || 0}</td>
@@ -403,12 +435,7 @@
async function loadAdminModels() {
const res = await fetch('/api/models');
const models = await res.json();
if (models.length === 0) {
document.getElementById('admin-models-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
return;
}
if (models.length === 0) { document.getElementById('admin-models-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
document.getElementById('admin-models-table').innerHTML = models.map(m => `
<tr class="border-b hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-800">${m.name}</td>
@@ -428,18 +455,13 @@
async function loadAdminGpus() {
const res = await fetch('/api/gpus');
const gpus = await res.json();
if (gpus.length === 0) {
document.getElementById('admin-gpus-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
return;
}
if (gpus.length === 0) { document.getElementById('admin-gpus-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
document.getElementById('admin-gpus-table').innerHTML = gpus.map(g => `
<tr class="border-b hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-800">${g.name}</td>
<td class="px-4 py-3 text-gray-600">${g.manufacturer}</td>
<td class="px-4 py-3">${g.memory_gb}GB</td>
<td class="px-4 py-3 text-gray-600">${g.architecture}</td>
<td class="px-4 py-3 text-gray-600">${g.architecture || '-'}</td>
<td class="px-4 py-3 text-gray-600">$${g.price_usd || '-'}</td>
<td class="px-4 py-3 text-center">
<button onclick="editItem('gpu', '${g.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
@@ -453,18 +475,13 @@
async function loadAdminCpus() {
const res = await fetch('/api/cpus');
const cpus = await res.json();
if (cpus.length === 0) {
document.getElementById('admin-cpus-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
return;
}
if (cpus.length === 0) { document.getElementById('admin-cpus-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
document.getElementById('admin-cpus-table').innerHTML = cpus.map(c => `
<tr class="border-b hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-800">${c.name}</td>
<td class="px-4 py-3 text-gray-600">${c.manufacturer}</td>
<td class="px-4 py-3">${c.cores}/${c.threads}</td>
<td class="px-4 py-3 text-gray-600">${c.base_clock_ghz}-${c.boost_clock_ghz}GHz</td>
<td class="px-4 py-3 text-gray-600">${c.base_clock_ghz || '-'}-${c.boost_clock_ghz || '-'}GHz</td>
<td class="px-4 py-3 text-gray-600">$${c.price_usd || '-'}</td>
<td class="px-4 py-3 text-center">
<button onclick="editItem('cpu', '${c.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
@@ -476,63 +493,43 @@
// 加载知识列表
async function loadAdminKnowledge() {
// 先加载分类
const catRes = await fetch('/api/categories');
categories = await catRes.json();
document.getElementById('category-filters').innerHTML = categories.map(c => `<button onclick="filterKnowledge('${c.id}')" class="px-3 py-1 rounded text-sm bg-gray-100 text-gray-600 hover:bg-gray-200">${c.name}</button>`).join('');
// 渲染分类筛选按钮
document.getElementById('category-filters').innerHTML = categories.map(c => `
<button onclick="filterKnowledge('${c.id}')" class="px-3 py-1 rounded text-sm bg-gray-100 text-gray-600 hover:bg-gray-200" id="filter-${c.id}">${c.name}</button>
`).join('');
// 加载知识
let url = '/api/knowledge';
if (currentFilter) url += `?category=${currentFilter}`;
const res = await fetch(url);
const knowledge = await res.json();
if (knowledge.length === 0) {
document.getElementById('admin-knowledge-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
return;
}
if (knowledge.length === 0) { document.getElementById('admin-knowledge-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
document.getElementById('admin-knowledge-table').innerHTML = knowledge.map(k => {
const cat = categories.find(c => c.id === k.category);
return `
<tr class="border-b hover:bg-gray-50">
<td class="px-4 py-3"><i class="${k.icon || 'ri-file-list-line'} text-xl text-gray-400"></i></td>
<td class="px-4 py-3 font-medium text-gray-800">${k.title}</td>
<td class="px-4 py-3 text-gray-600">${cat ? cat.name : '-'}</td>
<td class="px-4 py-3 text-gray-500 text-sm max-w-xs truncate">${k.content || '-'}</td>
<td class="px-4 py-3">${k.order || 0}</td>
<td class="px-4 py-3 text-center">
<button onclick="editItem('knowledge', '${k.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
<button onclick="deleteItem('knowledge', '${k.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td>
</tr>
`;
return `<tr class="border-b hover:bg-gray-50">
<td class="px-4 py-3"><i class="${k.icon || 'ri-file-list-line'} text-xl text-gray-400"></i></td>
<td class="px-4 py-3 font-medium text-gray-800">${k.title}</td>
<td class="px-4 py-3 text-gray-600">${cat ? cat.name : '-'}</td>
<td class="px-4 py-3 text-gray-500 text-sm max-w-xs truncate">${k.content || '-'}</td>
<td class="px-4 py-3">${k.order || 0}</td>
<td class="px-4 py-3 text-center">
<button onclick="editItem('knowledge', '${k.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
<button onclick="deleteItem('knowledge', '${k.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td>
</tr>`;
}).join('');
}
// 筛选知识
function filterKnowledge(category) {
currentFilter = category;
loadAdminKnowledge();
}
function filterKnowledge(category) { currentFilter = category; loadAdminKnowledge(); }
// 打开添加弹窗
function openAddModal(type) {
currentType = type;
currentId = '';
currentData = {};
const titles = {category: '分类', model: '模型', gpu: 'GPU', cpu: 'CPU', knowledge: '知识'};
const titles = {category: '分类', model: '模型', gpu: 'GPU', cpu: 'CPU', knowledge: '知识', dynamic: '数据'};
document.getElementById('modalTitle').textContent = '添加' + titles[type];
const forms = {category: getCategoryForm, model: getModelForm, gpu: getGpuForm, cpu: getCpuForm, knowledge: getKnowledgeForm};
const forms = {category: getCategoryForm, model: getModelForm, gpu: getGpuForm, cpu: getCpuForm, knowledge: getKnowledgeForm, dynamic: getDynamicForm};
document.getElementById('modalContent').innerHTML = forms[type]();
document.getElementById('editModal').classList.remove('hidden');
}
@@ -540,29 +537,43 @@
async function editItem(type, id) {
currentType = type;
currentId = id;
const res = await fetch(`/api/${type === 'category' ? 'categories' : type === 'knowledge' ? 'knowledge' : type + 's'}/${id}`);
const endpoint = type === 'category' ? 'categories' : type === 'knowledge' ? 'knowledge' : type + 's';
const res = await fetch(`/api/${endpoint}/${id}`);
currentData = await res.json();
const titles = {category: '分类', model: '模型', gpu: 'GPU', cpu: 'CPU', knowledge: '知识'};
document.getElementById('modalTitle').textContent = '编辑' + titles[type];
const forms = {category: getCategoryForm, model: getModelForm, gpu: getGpuForm, cpu: getCpuForm, knowledge: getKnowledgeForm};
document.getElementById('modalContent').innerHTML = forms[type](currentData);
document.getElementById('editModal').classList.remove('hidden');
}
// 编辑动态分类数据
async function editDynamicItem(id) {
currentType = 'dynamic';
currentId = id;
const res = await fetch(`/api/items/${dynamicCategoryId}/${id}`);
currentData = await res.json();
document.getElementById('modalTitle').textContent = '编辑数据';
document.getElementById('modalContent').innerHTML = getDynamicForm(currentData);
document.getElementById('editModal').classList.remove('hidden');
}
// 删除项
async function deleteItem(type, id) {
if (!confirm('确定删除?')) return;
const endpoint = type === 'category' ? 'categories' : type === 'knowledge' ? 'knowledge' : type + 's';
await fetch(`/api/${endpoint}/${id}`, { method: 'DELETE' });
const loaders = {category: loadAdminCategories, model: loadAdminModels, gpu: loadAdminGpus, cpu: loadAdminCpus, knowledge: loadAdminKnowledge};
loaders[type]();
loadOverview();
if (type === 'category') { await loadCategories(); renderSidebar(); }
}
// 删除动态分类数据
async function deleteDynamicItem(id) {
if (!confirm('确定删除?')) return;
await fetch(`/api/items/${dynamicCategoryId}/${id}`, { method: 'DELETE' });
showDynamicCategory(dynamicCategoryId);
}
// 保存项
@@ -570,192 +581,150 @@
const form = document.getElementById('itemForm');
const formData = new FormData(form);
const data = {};
formData.forEach((value, key) => {
if (value) {
const numFields = ['parameters', 'context_length', 'mmlu', 'humaneval', 'input_price', 'output_price',
'memory_gb', 'cuda_cores', 'tensor_cores', 'memory_bandwidth_gbs',
'fp32_tflops', 'fp16_tflops', 'int8_perf_tops', 'price_usd', 'release_year',
'cores', 'threads', 'base_clock_ghz', 'boost_clock_ghz', 'l3_cache_mb', 'tdp_watts', 'order'];
if (numFields.includes(key)) {
data[key] = parseFloat(value);
} else if (key === 'is_open_source') {
data[key] = value === 'true';
} else {
data[key] = value;
}
const numFields = ['parameters', 'context_length', 'mmlu', 'humaneval', 'input_price', 'output_price', 'memory_gb', 'cuda_cores', 'tensor_cores', 'memory_bandwidth_gbs', 'fp32_tflops', 'fp16_tflops', 'int8_perf_tops', 'price_usd', 'release_year', 'cores', 'threads', 'base_clock_ghz', 'boost_clock_ghz', 'l3_cache_mb', 'tdp_watts', 'order'];
if (numFields.includes(key)) data[key] = parseFloat(value);
else if (key === 'is_open_source') data[key] = value === 'true';
else data[key] = value;
}
});
const endpoint = currentType === 'category' ? 'categories' : currentType === 'knowledge' ? 'knowledge' : currentType + 's';
if (currentType === 'dynamic') {
data.category_id = dynamicCategoryId;
}
let endpoint;
if (currentType === 'category') endpoint = 'categories';
else if (currentType === 'knowledge') endpoint = 'knowledge';
else if (currentType === 'dynamic') endpoint = `items/${dynamicCategoryId}`;
else endpoint = currentType + 's';
if (currentId) {
await fetch(`/api/${endpoint}/${currentId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
await fetch(`/api/${endpoint}/${currentId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
} else {
await fetch(`/api/${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
await fetch(`/api/${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
}
closeModal();
const loaders = {category: loadAdminCategories, model: loadAdminModels, gpu: loadAdminGpus, cpu: loadAdminCpus, knowledge: loadAdminKnowledge};
loaders[currentType]();
if (currentType === 'dynamic') showDynamicCategory(dynamicCategoryId);
else {
const loaders = {category: loadAdminCategories, model: loadAdminModels, gpu: loadAdminGpus, cpu: loadAdminCpus, knowledge: loadAdminKnowledge};
loaders[currentType]();
}
loadOverview();
if (currentType === 'category') { await loadCategories(); renderSidebar(); }
}
function closeModal() {
document.getElementById('editModal').classList.add('hidden');
}
function closeModal() { document.getElementById('editModal').classList.add('hidden'); }
// 表单模板
function getCategoryForm(data = {}) {
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">名称 *</label>
<input type="text" name="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" name="icon" value="${data.icon || 'ri-folder-line'}" placeholder="ri-folder-line" class="w-full px-3 py-2 border rounded-lg">
</div>
<div>
<label class="text-sm text-gray-600 mb-1 block">颜色</label>
<select name="color" class="w-full px-3 py-2 border rounded-lg">
<option value="blue" ${data.color === 'blue' ? 'selected' : ''}>蓝色</option>
<option value="green" ${data.color === 'green' ? 'selected' : ''}>绿色</option>
<option value="purple" ${data.color === 'purple' ? 'selected' : ''}>紫色</option>
<option value="orange" ${data.color === 'orange' ? 'selected' : ''}>橙色</option>
<option value="teal" ${data.color === 'teal' ? 'selected' : ''}>青色</option>
<option value="red" ${data.color === 'red' ? 'selected' : ''}>红色</option>
</select>
</div>
<div>
<label class="text-sm text-gray-600 mb-1 block">排序</label>
<input type="number" name="order" value="${data.order || 0}" class="w-full px-3 py-2 border rounded-lg">
</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>
</form>
`;
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>
<div><label class="text-sm text-gray-600 mb-1 block">名称 *</label><input type="text" name="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" name="icon" value="${data.icon || 'ri-folder-line'}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">颜色</label><select name="color" class="w-full px-3 py-2 border rounded-lg">
<option value="blue" ${data.color === 'blue' ? 'selected' : ''}>蓝色</option>
<option value="green" ${data.color === 'green' ? 'selected' : ''}>绿色</option>
<option value="purple" ${data.color === 'purple' ? 'selected' : ''}>紫色</option>
<option value="orange" ${data.color === 'orange' ? 'selected' : ''}>橙色</option>
<option value="teal" ${data.color === 'teal' ? 'selected' : ''}>青色</option>
<option value="red" ${data.color === 'red' ? 'selected' : ''}>红色</option>
</select></div>
<div><label class="text-sm text-gray-600 mb-1 block">排序</label><input type="number" name="order" value="${data.order || 0}" class="w-full px-3 py-2 border rounded-lg"></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>
</form>`;
}
function getKnowledgeForm(data = {}) {
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">标题 *</label>
<input type="text" name="title" value="${data.title || ''}" required class="w-full px-3 py-2 border rounded-lg">
</div>
<div>
<label class="text-sm text-gray-600 mb-1 block">分类</label>
<select name="category" class="w-full px-3 py-2 border rounded-lg">
<option value="">选择分类</option>
${categories.map(c => `<option value="${c.id}" ${data.category === c.id ? 'selected' : ''}>${c.name}</option>`).join('')}
</select>
</div>
<div>
<label class="text-sm text-gray-600 mb-1 block">图标</label>
<input type="text" name="icon" value="${data.icon || 'ri-file-list-line'}" 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="number" name="order" value="${data.order || 0}" class="w-full px-3 py-2 border rounded-lg">
</div>
</div>
<div>
<label class="text-sm text-gray-600 mb-1 block">简介 *</label>
<textarea name="content" rows="2" required class="w-full px-3 py-2 border rounded-lg">${data.content || ''}</textarea>
</div>
<div>
<label class="text-sm text-gray-600 mb-1 block">详细内容</label>
<textarea name="detail" rows="6" class="w-full px-3 py-2 border rounded-lg">${data.detail || ''}</textarea>
</div>
</form>
`;
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">标题 *</label><input type="text" name="title" value="${data.title || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">分类</label><select name="category" class="w-full px-3 py-2 border rounded-lg"><option value="">选择分类</option>${categories.map(c => `<option value="${c.id}" ${data.category === c.id ? 'selected' : ''}>${c.name}</option>`).join('')}</select></div>
<div><label class="text-sm text-gray-600 mb-1 block">标</label><input type="text" name="icon" value="${data.icon || 'ri-file-list-line'}" 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="number" name="order" value="${data.order || 0}" class="w-full px-3 py-2 border rounded-lg"></div>
</div>
<div><label class="text-sm text-gray-600 mb-1 block">简介 *</label><textarea name="content" rows="2" required class="w-full px-3 py-2 border rounded-lg">${data.content || ''}</textarea></div>
<div><label class="text-sm text-gray-600 mb-1 block">详细内容</label><textarea name="detail" rows="6" class="w-full px-3 py-2 border rounded-lg">${data.detail || ''}</textarea></div>
</form>`;
}
function getDynamicForm(data = {}) {
const cat = categories.find(c => c.id === dynamicCategoryId);
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">名称 *</label><input type="text" name="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" name="brand" value="${data.brand || ''}" 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="number" name="price" value="${data.price || ''}" step="0.01" 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="number" name="year" value="${data.year || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
</div>
<div><label class="text-sm text-gray-600 mb-1 block">参数JSON格式</label><textarea name="specs" rows="4" class="w-full px-3 py-2 border rounded-lg font-mono text-sm" placeholder='{"key": "value"}'>${data.specs || ''}</textarea></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>
</form>`;
}
function getModelForm(data = {}) {
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">名称 *</label><input type="text" name="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" name="organization" value="${data.organization || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">参数量(B) *</label><input type="number" name="parameters" value="${data.parameters || ''}" 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" name="architecture" value="${data.architecture || ''}" 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="number" name="context_length" value="${data.context_length || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">类型</label><select name="is_open_source" class="w-full px-3 py-2 border rounded-lg"><option value="false" ${!data.is_open_source ? 'selected' : ''}>商业</option><option value="true" ${data.is_open_source ? 'selected' : ''}>开源</option></select></div>
<div><label class="text-sm text-gray-600 mb-1 block">MMLU分数</label><input type="number" name="mmlu" value="${data.mmlu || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">HumanEval分数</label><input type="number" name="humaneval" value="${data.humaneval || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">输价格($/1K)</label><input type="number" name="input_price" value="${data.input_price || ''}" step="0.001" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">输出价格($/1K)</label><input type="number" name="output_price" value="${data.output_price || ''}" step="0.001" 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" name="license" value="${data.license || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
</div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
</form>
`;
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">名称 *</label><input type="text" name="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" name="organization" value="${data.organization || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">参数量(B) *</label><input type="number" name="parameters" value="${data.parameters || ''}" 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" name="architecture" value="${data.architecture || ''}" 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="number" name="context_length" value="${data.context_length || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">类型</label><select name="is_open_source" class="w-full px-3 py-2 border rounded-lg"><option value="false" ${!data.is_open_source ? 'selected' : ''}>商业</option><option value="true" ${data.is_open_source ? 'selected' : ''}>开源</option></select></div>
<div><label class="text-sm text-gray-600 mb-1 block">MMLU分数</label><input type="number" name="mmlu" value="${data.mmlu || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">HumanEval分数</label><input type="number" name="humaneval" value="${data.humaneval || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">输入价格($/1K)</label><input type="number" name="input_price" value="${data.input_price || ''}" step="0.001" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">输价格($/1K)</label><input type="number" name="output_price" value="${data.output_price || ''}" step="0.001" 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" name="license" value="${data.license || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
</div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
</form>`;
}
function getGpuForm(data = {}) {
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">名称 *</label><input type="text" name="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" name="manufacturer" value="${data.manufacturer || ''}" 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" name="architecture" value="${data.architecture || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">显存(GB) *</label><input type="number" name="memory_gb" value="${data.memory_gb || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">CUDA核心</label><input type="number" name="cuda_cores" value="${data.cuda_cores || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">Tensor核心</label><input type="number" name="tensor_cores" value="${data.tensor_cores || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">显存带宽(GB/s)</label><input type="number" name="memory_bandwidth_gbs" value="${data.memory_bandwidth_gbs || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">FP32性能(TF)</label><input type="number" name="fp32_tflops" value="${data.fp32_tflops || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">FP16性能(TF)</label><input type="number" name="fp16_tflops" value="${data.fp16_tflops || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">INT8性能(TOP)</label><input type="number" name="int8_perf_tops" value="${data.int8_perf_tops || ''}" 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="number" name="price_usd" value="${data.price_usd || ''}" 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="number" name="release_year" value="${data.release_year || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
</div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
</form>
`;
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">名称 *</label><input type="text" name="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" name="manufacturer" value="${data.manufacturer || ''}" 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" name="architecture" value="${data.architecture || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">显存(GB) *</label><input type="number" name="memory_gb" value="${data.memory_gb || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">CUDA核心</label><input type="number" name="cuda_cores" value="${data.cuda_cores || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">Tensor核心</label><input type="number" name="tensor_cores" value="${data.tensor_cores || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">显存带宽(GB/s)</label><input type="number" name="memory_bandwidth_gbs" value="${data.memory_bandwidth_gbs || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">FP16性能(TF)</label><input type="number" name="fp16_tflops" value="${data.fp16_tflops || ''}" step="0.1" 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="number" name="price_usd" value="${data.price_usd || ''}" 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="number" name="release_year" value="${data.release_year || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
</div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
</form>`;
}
function getCpuForm(data = {}) {
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">名称 *</label><input type="text" name="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" name="manufacturer" value="${data.manufacturer || ''}" 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" name="architecture" value="${data.architecture || ''}" 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="number" name="cores" value="${data.cores || ''}" 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="number" name="threads" value="${data.threads || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">基础频率(GHz)</label><input type="number" name="base_clock_ghz" value="${data.base_clock_ghz || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">加速频率(GHz)</label><input type="number" name="boost_clock_ghz" value="${data.boost_clock_ghz || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">L3缓存(MB)</label><input type="number" name="l3_cache_mb" value="${data.l3_cache_mb || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">TDP功耗(W)</label><input type="number" name="tdp_watts" value="${data.tdp_watts || ''}" 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="number" name="price_usd" value="${data.price_usd || ''}" 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="number" name="release_year" value="${data.release_year || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
</div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
</form>
`;
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">名称 *</label><input type="text" name="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" name="manufacturer" value="${data.manufacturer || ''}" 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" name="architecture" value="${data.architecture || ''}" 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="number" name="cores" value="${data.cores || ''}" 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="number" name="threads" value="${data.threads || ''}" required class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">基础频率(GHz)</label><input type="number" name="base_clock_ghz" value="${data.base_clock_ghz || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">加速频率(GHz)</label><input type="number" name="boost_clock_ghz" value="${data.boost_clock_ghz || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">L3缓存(MB)</label><input type="number" name="l3_cache_mb" value="${data.l3_cache_mb || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
<div><label class="text-sm text-gray-600 mb-1 block">TDP功耗(W)</label><input type="number" name="tdp_watts" value="${data.tdp_watts || ''}" 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="number" name="price_usd" value="${data.price_usd || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
</div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
</form>`;
}
document.getElementById('editModal').addEventListener('click', function(e) {
if (e.target === this) closeModal();
});
document.getElementById('editModal').addEventListener('click', function(e) { if (e.target === this) closeModal(); });
loadOverview();
init();
</script>
</body>
</html>

View File

@@ -16,7 +16,7 @@
<span class="text-xl font-bold text-gray-800">ParamHub</span>
<span class="text-sm text-gray-500">参数百科</span>
</div>
<div class="flex gap-6 text-sm">
<div class="flex gap-6 text-sm" id="navLinks">
<a href="/" class="text-indigo-600 font-medium">首页</a>
<a href="/models" class="text-gray-600 hover:text-indigo-600">模型</a>
<a href="/gpus" class="text-gray-600 hover:text-indigo-600">GPU</a>
@@ -35,7 +35,7 @@
<div class="flex gap-4">
<div class="flex-1 relative">
<i class="ri-search-line absolute left-4 top-1/2 -translate-y-1/2 text-gray-400"></i>
<input type="text" id="searchInput" placeholder="搜索模型、GPU、CPU..."
<input type="text" id="searchInput" placeholder="搜索参数..."
class="w-full pl-12 pr-4 py-3 border border-gray-200 rounded-lg focus:outline-none focus:border-indigo-400 text-lg"
onkeyup="if(event.key==='Enter')search()">
</div>
@@ -45,70 +45,23 @@
</div>
</div>
<!-- 统计卡片 -->
<div class="grid grid-cols-3 gap-6 mb-8" id="statsCards">
<div class="bg-gradient-to-r from-blue-500 to-blue-600 rounded-xl p-6 text-white">
<div class="flex items-center gap-4">
<i class="ri-robot-line text-4xl"></i>
<div>
<div class="text-3xl font-bold" id="modelsCount">-</div>
<div class="text-sm opacity-80">AI模型</div>
</div>
</div>
</div>
<div class="bg-gradient-to-r from-green-500 to-green-600 rounded-xl p-6 text-white">
<div class="flex items-center gap-4">
<i class="ri-cpu-line text-4xl"></i>
<div>
<div class="text-3xl font-bold" id="gpusCount">-</div>
<div class="text-sm opacity-80">GPU显卡</div>
</div>
</div>
</div>
<div class="bg-gradient-to-r from-purple-500 to-purple-600 rounded-xl p-6 text-white">
<div class="flex items-center gap-4">
<i class="ri-memory-line text-4xl"></i>
<div>
<div class="text-3xl font-bold" id="cpusCount">-</div>
<div class="text-sm opacity-80">CPU处理器</div>
</div>
</div>
<!-- 统计卡片 - 动态生成 -->
<div class="grid grid-cols-4 gap-4 mb-8" id="statsCards">
<div class="text-center text-gray-400 py-8">加载中...</div>
</div>
<!-- 分类快捷入口 - 动态生成 -->
<div class="mb-8">
<h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
<i class="ri-folder-line text-indigo-600"></i>
参数分类
</h2>
<div id="categoryCards" class="grid grid-cols-4 gap-4">
<div class="text-center text-gray-400 py-8">加载中...</div>
</div>
</div>
<!-- 快捷入口 -->
<div class="grid grid-cols-4 gap-6 mb-8">
<a href="/models" class="bg-white rounded-xl shadow-sm p-6 hover:shadow-md transition group">
<div class="text-center">
<i class="ri-robot-line text-5xl text-blue-500 group-hover:scale-110 transition"></i>
<div class="mt-4 font-medium text-gray-800">模型数据库</div>
<div class="text-sm text-gray-500 mt-1">GPT、Llama、Claude...</div>
</div>
</a>
<a href="/gpus" class="bg-white rounded-xl shadow-sm p-6 hover:shadow-md transition group">
<div class="text-center">
<i class="ri-cpu-line text-5xl text-green-500 group-hover:scale-110 transition"></i>
<div class="mt-4 font-medium text-gray-800">GPU数据库</div>
<div class="text-sm text-gray-500 mt-1">H100、A100、4090...</div>
</div>
</a>
<a href="/tools" class="bg-white rounded-xl shadow-sm p-6 hover:shadow-md transition group">
<div class="text-center">
<i class="ri-calculator-line text-5xl text-orange-500 group-hover:scale-110 transition"></i>
<div class="mt-4 font-medium text-gray-800">实用工具</div>
<div class="text-sm text-gray-500 mt-1">显存计算器...</div>
</div>
</a>
<a href="/compare" class="bg-white rounded-xl shadow-sm p-6 hover:shadow-md transition group">
<div class="text-center">
<i class="ri-git-merge-line text-5xl text-purple-500 group-hover:scale-110 transition"></i>
<div class="mt-4 font-medium text-gray-800">对比工具</div>
<div class="text-sm text-gray-500 mt-1">多维度对比</div>
</div>
</a>
</div>
<!-- 最新模型 -->
<!-- 热门模型 -->
<div class="bg-white rounded-xl shadow-sm p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
<i class="ri-flashlight-line text-indigo-600"></i>
@@ -126,43 +79,123 @@
</footer>
<script>
// 加载统计数据
async function loadStats() {
const res = await fetch('/api/stats');
const data = await res.json();
let categories = [];
const colorMap = {
blue: {bg: 'bg-blue-500', gradient: 'from-blue-500 to-blue-600', icon: 'text-blue-500'},
green: {bg: 'bg-green-500', gradient: 'from-green-500 to-green-600', icon: 'text-green-500'},
purple: {bg: 'bg-purple-500', gradient: 'from-purple-500 to-purple-600', icon: 'text-purple-500'},
orange: {bg: 'bg-orange-500', gradient: 'from-orange-500 to-orange-600', icon: 'text-orange-500'},
teal: {bg: 'bg-teal-500', gradient: 'from-teal-500 to-teal-600', icon: 'text-teal-500'},
red: {bg: 'bg-red-500', gradient: 'from-red-500 to-red-600', icon: 'text-red-500'}
};
// 加载分类和数据统计
async function loadData() {
// 并行加载分类和统计
const [catRes, statsRes] = await Promise.all([
fetch('/api/categories'),
fetch('/api/stats')
]);
categories = await catRes.json();
const stats = await statsRes.json();
document.getElementById('modelsCount').textContent = data.models_count;
document.getElementById('gpusCount').textContent = data.gpus_count;
document.getElementById('cpusCount').textContent = data.cpus_count;
// 渲染统计卡片
renderStatsCards(stats);
// 最新模型
if (data.latest_models && data.latest_models.length > 0) {
const html = data.latest_models.map(m => `
<a href="/models" class="flex items-center gap-4 p-4 rounded-lg hover:bg-gray-50 transition">
<div class="w-12 h-12 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-600 font-bold">
${m.name.substring(0, 2)}
// 渲染分类卡片
renderCategoryCards();
// 渲染热门模型
renderLatestModels(stats.latest_models || []);
}
// 渲染统计卡片
function renderStatsCards(stats) {
const statItems = [
{key: 'models_count', label: 'AI模型', icon: 'ri-robot-line', color: 'blue'},
{key: 'gpus_count', label: 'GPU显卡', icon: 'ri-cpu-line', color: 'green'},
{key: 'cpus_count', label: 'CPU处理器', icon: 'ri-memory-line', color: 'purple'},
{key: 'knowledge_count', label: '知识条目', icon: 'ri-book-open-line', color: 'teal'}
];
document.getElementById('statsCards').innerHTML = statItems.map(item => {
const c = colorMap[item.color] || colorMap.blue;
return `
<div class="bg-gradient-to-r ${c.gradient} rounded-xl p-5 text-white">
<div class="flex items-center gap-3">
<i class="${item.icon} text-3xl opacity-80"></i>
<div>
<div class="text-2xl font-bold">${stats[item.key] || 0}</div>
<div class="text-sm opacity-80">${item.label}</div>
</div>
</div>
<div class="flex-1">
<div class="font-medium text-gray-800">${m.name}</div>
<div class="text-sm text-gray-500">${m.organization} | ${m.parameters}B参数</div>
</div>
<div class="text-sm text-gray-400">${m.is_open_source ? '开源' : '商业'}</div>
</a>
`).join('');
document.getElementById('latestModels').innerHTML = html;
</div>
`;
}).join('');
}
// 渲染分类卡片
function renderCategoryCards() {
if (categories.length === 0) {
document.getElementById('categoryCards').innerHTML = '<div class="col-span-4 text-center text-gray-400 py-8">暂无分类</div>';
return;
}
// 内置页面映射
const pageMap = {
'ai-models': '/models',
'gpus': '/gpus',
'cpus': '/cpus'
};
document.getElementById('categoryCards').innerHTML = categories.map(cat => {
const c = colorMap[cat.color] || colorMap.blue;
const link = pageMap[cat.id] || `/category/${cat.id}`;
return `
<a href="${link}" class="bg-white rounded-xl shadow-sm p-5 hover:shadow-md transition group">
<div class="text-center">
<div class="w-14 h-14 rounded-xl ${c.bg} bg-opacity-10 flex items-center justify-center mx-auto group-hover:scale-110 transition">
<i class="${cat.icon} text-3xl ${c.icon}"></i>
</div>
<div class="mt-3 font-medium text-gray-800">${cat.name}</div>
<div class="text-xs text-gray-500 mt-1 truncate">${cat.description || ''}</div>
</div>
</a>
`;
}).join('');
}
// 渲染热门模型
function renderLatestModels(models) {
if (models.length === 0) {
document.getElementById('latestModels').innerHTML = '<div class="col-span-2 text-center text-gray-400 py-8">暂无数据</div>';
return;
}
document.getElementById('latestModels').innerHTML = models.map(m => `
<a href="/models" class="flex items-center gap-4 p-4 rounded-lg hover:bg-gray-50 transition">
<div class="w-12 h-12 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-600 font-bold">
${m.name.substring(0, 2)}
</div>
<div class="flex-1">
<div class="font-medium text-gray-800">${m.name}</div>
<div class="text-sm text-gray-500">${m.organization} | ${m.parameters}B参数</div>
</div>
<div class="text-sm text-gray-400">${m.is_open_source ? '开源' : '商业'}</div>
</a>
`).join('');
}
// 搜索
async function search() {
const keyword = document.getElementById('searchInput').value.trim();
if (!keyword) return;
window.location.href = `/models?q=${encodeURIComponent(keyword)}`;
}
// 初始化
loadStats();
loadData();
</script>
</body>
</html>