Files
param-hub-python/templates/admin.html
hubian dfd7234fd6 fix: 修复模型排序错误,改名为大模型管理
- 修复智能添加创建的不完整数据导致排序错误
- 添加安全排序函数处理缺失字段
- 改名:模型管理 → 大模型管理
- 清理测试数据
2026-04-11 02:06:06 +08:00

987 lines
66 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ParamHub - 参数百科</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style>
.sidebar-link { transition: all 0.2s; }
.sidebar-link:hover { background: #374151; }
.sidebar-link.active { background: #4f46e5; color: white; }
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<!-- 侧边栏 -->
<aside class="fixed left-0 top-0 w-64 h-full bg-gray-800 text-white overflow-y-auto">
<div class="p-4 border-b border-gray-700">
<div class="flex items-center gap-2">
<i class="ri-dashboard-3-line text-2xl"></i>
<span class="text-xl font-bold">ParamHub</span>
</div>
<div class="text-sm text-gray-400 mt-1">后台管理</div>
</div>
<nav class="p-4">
<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">
<i class="ri-external-link-line"></i>
<span>访问前台</span>
</a>
</div>
</nav>
</aside>
<!-- 主内容 -->
<main class="ml-64 p-8">
<!-- 概览 -->
<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" 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" 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>
</div>
</section>
<!-- 分类管理 -->
<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"><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-center 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="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>
<div class="flex gap-2">
<button onclick="openSmartAddModal('dynamic')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</button>
<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>
<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>
<div class="flex gap-2">
<button onclick="openSmartAddModal('model')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-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>
<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">厂商</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>
<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="7" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
<!-- GPU管理 -->
<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>
<div class="flex gap-2">
<button onclick="openSmartAddModal('gpu')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</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>手动添加</button>
</div>
</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">厂商</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>
<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="7" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
<!-- CPU管理 -->
<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>
<div class="flex gap-2">
<button onclick="openSmartAddModal('cpu')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</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>手动添加</button>
</div>
</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">厂商</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>
<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="7" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
<!-- 知识库管理 -->
<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"><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">全部</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">
<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>
<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-knowledge-table"><tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
</main>
<!-- 编辑弹窗 -->
<div id="editModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
<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>
</div>
<div id="modalContent" class="p-6"></div>
<div class="p-6 border-t flex justify-end gap-4 sticky bottom-0 bg-white">
<button onclick="closeModal()" class="px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300">取消</button>
<button onclick="saveItem()" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">保存</button>
</div>
</div>
</div>
<!-- 智能添加弹窗 -->
<div id="smartAddModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-xl max-w-3xl 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"><i class="ri-magic-line mr-2 text-orange-600"></i>智能添加</h2>
<button onclick="closeSmartAddModal()" class="text-gray-400 hover:text-gray-600"><i class="ri-close-line text-2xl"></i></button>
</div>
<div class="p-6">
<p class="text-sm text-gray-500 mb-4">粘贴产品信息文本AI将自动解析并提取结构化数据。支持各种格式的产品介绍、规格参数、价格信息等。</p>
<textarea id="smartAddText" rows="8" class="w-full p-4 border border-gray-200 rounded-lg focus:outline-none focus:border-orange-400 text-gray-700" placeholder="粘贴产品信息文本...
示例:
GPT-4是OpenAI发布的大语言模型参数量约1.8万亿支持128K上下文MMLU分数86.4,输入价格$0.03/1K tokens输出价格$0.06/1K tokens商业许可。"></textarea>
<div id="smartAddPreview" class="mt-4 hidden">
<h3 class="text-sm font-semibold text-gray-700 mb-2">解析结果预览:</h3>
<div class="bg-gray-50 rounded-lg p-4 text-sm text-gray-600" id="smartAddResult"></div>
</div>
</div>
<div class="p-6 border-t flex justify-end gap-4 sticky bottom-0 bg-white">
<button onclick="closeSmartAddModal()" class="px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300">取消</button>
<button onclick="smartAddSubmit()" id="smartAddBtn" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-1"></i>智能解析并添加</button>
</div>
</div>
</div>
<!-- 原始数据查看弹窗 -->
<div id="rawDataModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-xl max-w-3xl 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"><i class="ri-file-text-line mr-2"></i>原始数据</h2>
<button onclick="closeRawDataModal()" class="text-gray-400 hover:text-gray-600"><i class="ri-close-line text-2xl"></i></button>
</div>
<div id="rawDataContent" class="p-6">
<div class="bg-gray-50 rounded-lg p-4 text-sm text-gray-700 whitespace-pre-wrap" id="rawDataText"></div>
</div>
</div>
</div>
<script>
let currentType = '';
let currentId = '';
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'
};
// 价格格式化函数
function formatPrice(item) {
// 支持多种价格格式
const currency = item.currency || 'USD';
const symbols = { USD: '$', CNY: '¥', EUR: '€', JPY: '¥', GBP: '£' };
const symbol = symbols[currency] || currency;
// 价格区间支持
const minPrice = item.min_price || item.price_usd_min || item.price_min;
const maxPrice = item.max_price || item.price_usd_max || item.price_max;
const singlePrice = item.price_usd || item.price_cny || item.price;
// 单位处理
const unit = item.price_unit || '';
if (minPrice && maxPrice) {
// 价格区间
return `${symbol}${formatNumber(minPrice)}-${formatNumber(maxPrice)}${unit ? ' ' + unit : ''}`;
} else if (singlePrice) {
return `${symbol}${formatNumber(singlePrice)}${unit ? ' ' + unit : ''}`;
} else {
return '-';
}
}
// 数字格式化
function formatNumber(num) {
if (!num) return '0';
if (num >= 10000) {
return (num / 10000).toFixed(1) + '万';
}
if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num.toLocaleString();
}
// 初始化
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-cpu-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) {
document.querySelectorAll('section').forEach(s => s.classList.add('hidden'));
document.getElementById('section-' + section).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="${section}"]`);
if (activeLink) {
activeLink.classList.add('active', 'text-white');
activeLink.classList.remove('text-gray-300');
}
if (section === 'categories') loadAdminCategories();
if (section === 'models') loadAdminModels();
if (section === 'gpus') loadAdminGpus();
if (section === 'cpus') loadAdminCpus();
if (section === 'knowledge') loadAdminKnowledge();
}
// 加载概览统计
async function loadOverview() {
const res = await fetch('/api/stats');
const data = await res.json();
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-cpu-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('')
: '<div class="text-gray-400">暂无数据</div>';
}
// 加载分类列表
async function loadAdminCategories() {
const res = await fetch('/api/categories');
categories = await res.json();
if (categories.length === 0) {
document.getElementById('admin-categories-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>';
return;
}
document.getElementById('admin-categories-table').innerHTML = categories.map(c => `
<tr class="border-b hover:bg-gray-50 ${c.visible === false ? 'bg-gray-100 opacity-60' : ''}">
<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 text-center">
<button onclick="toggleVisible('category', '${c.id}')" class="${c.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80">
<i class="${c.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
</button>
</td>
<td class="px-4 py-3 text-center">
<button onclick="editItem('category', '${c.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
<button onclick="deleteItem('category', '${c.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td>
</tr>
`).join('');
}
// 加载模型列表
async function loadAdminModels() {
const res = await fetch('/api/models?all=1');
const models = await res.json();
if (models.length === 0) { document.getElementById('admin-models-table').innerHTML = '<tr><td colspan="7" 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 ${m.visible === false ? 'bg-gray-100 opacity-60' : ''}">
<td class="px-4 py-3 font-medium text-gray-800">${m.name}</td>
<td class="px-4 py-3 text-gray-600">${m.organization}</td>
<td class="px-4 py-3">${m.parameters}B</td>
<td class="px-4 py-3 text-gray-600">${m.context_length || '-'}</td>
<td class="px-4 py-3">${m.is_open_source ? '<span class="text-green-600">开源</span>' : '<span class="text-gray-600">商业</span>'}</td>
<td class="px-4 py-3 text-center">
<button onclick="toggleVisible('model', '${m.id}')" class="${m.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80">
<i class="${m.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
</button>
${m.raw_text ? `<button onclick="showRawData('${m.id}', 'model')" class="text-gray-400 hover:text-gray-600 ml-1" title="查看原始数据"><i class="ri-file-text-line"></i></button>` : ''}
</td>
<td class="px-4 py-3 text-center">
<button onclick="editItem('model', '${m.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
<button onclick="deleteItem('model', '${m.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td>
</tr>
`).join('');
}
// 加载GPU列表
async function loadAdminGpus() {
const res = await fetch('/api/gpus?all=1');
const gpus = await res.json();
if (gpus.length === 0) { document.getElementById('admin-gpus-table').innerHTML = '<tr><td colspan="7" 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 ${g.visible === false ? 'bg-gray-100 opacity-60' : ''}">
<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">${formatPrice(g)}</td>
<td class="px-4 py-3 text-center">
<button onclick="toggleVisible('gpu', '${g.id}')" class="${g.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80">
<i class="${g.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
</button>
${g.raw_text ? `<button onclick="showRawData('${g.id}', 'gpu')" class="text-gray-400 hover:text-gray-600 ml-1" title="查看原始数据"><i class="ri-file-text-line"></i></button>` : ''}
</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>
<button onclick="deleteItem('gpu', '${g.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td>
</tr>
`).join('');
}
// 加载CPU列表
async function loadAdminCpus() {
const res = await fetch('/api/cpus?all=1');
const cpus = await res.json();
if (cpus.length === 0) { document.getElementById('admin-cpus-table').innerHTML = '<tr><td colspan="7" 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 ${c.visible === false ? 'bg-gray-100 opacity-60' : ''}">
<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">${formatPrice(c)}</td>
<td class="px-4 py-3 text-center">
<button onclick="toggleVisible('cpu', '${c.id}')" class="${c.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80">
<i class="${c.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
</button>
${c.raw_text ? `<button onclick="showRawData('${c.id}', 'cpu')" class="text-gray-400 hover:text-gray-600 ml-1" title="查看原始数据"><i class="ri-file-text-line"></i></button>` : ''}
</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>
<button onclick="deleteItem('cpu', '${c.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
</td>
</tr>
`).join('');
}
// 加载知识列表
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('');
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; }
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>`;
}).join('');
}
function filterKnowledge(category) { currentFilter = category; loadAdminKnowledge(); }
// 打开添加弹窗
function openAddModal(type) {
currentType = type;
currentId = '';
currentData = {};
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, dynamic: getDynamicForm};
document.getElementById('modalContent').innerHTML = forms[type]();
document.getElementById('editModal').classList.remove('hidden');
}
// 编辑项
async function editItem(type, id) {
currentType = type;
currentId = 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);
}
// 保存项
async function saveItem() {
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', 'min_price', 'max_price', 'release_year', 'cores', 'threads', 'base_clock_ghz', 'boost_clock_ghz', 'l3_cache_mb', 'tdp_watts', 'order', 'price'];
if (numFields.includes(key)) data[key] = parseFloat(value);
else if (key === 'is_open_source' || key === 'visible') data[key] = value === 'true';
else data[key] = value;
}
});
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) });
} else {
await fetch(`/api/${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
}
closeModal();
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 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">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><label class="text-sm text-gray-600 mb-1 block">是否显示</label><select name="visible" class="w-full px-3 py-2 border rounded-lg">
<option value="true" ${data.visible !== false ? 'selected' : ''}>显示</option>
<option value="false" ${data.visible === false ? 'selected' : ''}>隐藏</option>
</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>
</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>`;
}
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>`;
}
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">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><select name="currency" class="w-full px-3 py-2 border rounded-lg">
<option value="USD" ${data.currency === 'USD' || !data.currency ? 'selected' : ''}>美元 (USD)</option>
<option value="CNY" ${data.currency === 'CNY' ? 'selected' : ''}>人民币 (CNY)</option>
<option value="EUR" ${data.currency === 'EUR' ? 'selected' : ''}>欧元 (EUR)</option>
</select></div>
<div><label class="text-sm text-gray-600 mb-1 block">价格</label><input type="number" name="price_usd" value="${data.price_usd || ''}" 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="min_price" value="${data.min_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="max_price" value="${data.max_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="text" name="price_unit" value="${data.price_unit || ''}" placeholder="如: 万" 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><select name="currency" class="w-full px-3 py-2 border rounded-lg">
<option value="USD" ${data.currency === 'USD' || !data.currency ? 'selected' : ''}>美元 (USD)</option>
<option value="CNY" ${data.currency === 'CNY' ? 'selected' : ''}>人民币 (CNY)</option>
<option value="EUR" ${data.currency === 'EUR' ? 'selected' : ''}>欧元 (EUR)</option>
</select></div>
<div><label class="text-sm text-gray-600 mb-1 block">价格</label><input type="number" name="price_usd" value="${data.price_usd || ''}" 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="min_price" value="${data.min_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="max_price" value="${data.max_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="text" name="price_unit" value="${data.price_unit || ''}" placeholder="如: 万" 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('smartAddModal').addEventListener('click', function(e) { if (e.target === this) closeSmartAddModal(); });
document.getElementById('rawDataModal').addEventListener('click', function(e) { if (e.target === this) closeRawDataModal(); });
// ============ 智能添加功能 ============
let smartAddType = '';
function openSmartAddModal(type) {
smartAddType = type;
document.getElementById('smartAddText').value = '';
document.getElementById('smartAddPreview').classList.add('hidden');
document.getElementById('smartAddModal').classList.remove('hidden');
}
function closeSmartAddModal() {
document.getElementById('smartAddModal').classList.add('hidden');
}
async function smartAddSubmit() {
const text = document.getElementById('smartAddText').value.trim();
if (!text) {
alert('请粘贴产品信息文本');
return;
}
const btn = document.getElementById('smartAddBtn');
btn.disabled = true;
btn.innerHTML = '<i class="ri-loader-4-line animate-spin mr-1"></i>解析中...';
try {
let endpoint;
if (smartAddType === 'model') endpoint = '/api/models/smart-add';
else if (smartAddType === 'gpu') endpoint = '/api/gpus/smart-add';
else if (smartAddType === 'cpu') endpoint = '/api/cpus/smart-add';
else if (smartAddType === 'dynamic') endpoint = `/api/items/${dynamicCategoryId}/smart-add`;
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
const data = await res.json();
if (data.error) {
alert('解析失败: ' + data.error);
} else {
// 显示解析结果
document.getElementById('smartAddPreview').classList.remove('hidden');
document.getElementById('smartAddResult').innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
// 关闭弹窗并刷新列表
closeSmartAddModal();
if (smartAddType === 'dynamic') showDynamicCategory(dynamicCategoryId);
else {
const loaders = {model: loadAdminModels, gpu: loadAdminGpus, cpu: loadAdminCpus};
loaders[smartAddType]();
}
loadOverview();
alert('智能添加成功!数据已自动解析并保存。');
}
} catch (e) {
alert('请求失败: ' + e.message);
}
btn.disabled = false;
btn.innerHTML = '<i class="ri-magic-line mr-1"></i>智能解析并添加';
}
// ============ 显示切换功能 ============
async function toggleVisible(type, id) {
let endpoint;
if (type === 'category') endpoint = `/api/categories/${id}/visible`;
else if (type === 'model') endpoint = `/api/models/${id}/visible`;
else if (type === 'gpu') endpoint = `/api/gpus/${id}/visible`;
else if (type === 'cpu') endpoint = `/api/cpus/${id}/visible`;
else if (type === 'dynamic') endpoint = `/api/items/${dynamicCategoryId}/${id}/visible`;
try {
await fetch(endpoint, { method: 'POST' });
// 刷新列表
if (type === 'dynamic') showDynamicCategory(dynamicCategoryId);
else {
const loaders = {category: loadAdminCategories, model: loadAdminModels, gpu: loadAdminGpus, cpu: loadAdminCpus};
loaders[type]();
}
} catch (e) {
alert('切换失败: ' + e.message);
}
}
// ============ 原始数据查看 ============
async function showRawData(id, type) {
let endpoint;
if (type === 'model') endpoint = `/api/models/${id}`;
else if (type === 'gpu') endpoint = `/api/gpus/${id}`;
else if (type === 'cpu') endpoint = `/api/cpus/${id}`;
try {
const res = await fetch(endpoint);
const data = await res.json();
let content = '';
if (data.raw_text) {
content = `【原始文本】\n${data.raw_text}\n\n【解析数据】\n${JSON.stringify(data, null, 2)}`;
} else {
content = JSON.stringify(data, null, 2);
}
document.getElementById('rawDataText').textContent = content;
document.getElementById('rawDataModal').classList.remove('hidden');
} catch (e) {
alert('获取数据失败');
}
}
function closeRawDataModal() {
document.getElementById('rawDataModal').classList.add('hidden');
}
init();
</script>
</body>
</html>