feat: v1.1.0 新增智能添加和展示开关功能

功能更新:
- 新增智能添加功能:粘贴文本自动解析为结构化数据
- 新增展示开关:各分类和产品支持显示/隐藏控制
- 保留原始数据:智能添加的产品保留raw_text字段
- 优化价格显示:支持多币种、价格区间、单位
- 修复图标问题:CPU图标改为ri-cpu-line
- 新增favicon:所有页面添加浏览器标签图标

技术改进:
- 新增大模型API集成(LLM Proxy)
- 新增smart-add API接口
- 新增visible字段和toggle API
- 优化前端表格显示
This commit is contained in:
2026-04-11 01:54:28 +08:00
parent 1b22718d5a
commit d28ad1a292
13 changed files with 904 additions and 124 deletions

View File

@@ -3,7 +3,9 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台管理 - ParamHub</title>
<title>ParamHub - 参数百科</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<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>
@@ -65,7 +67,7 @@
<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>
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
</tr>
</thead>
@@ -78,7 +80,10 @@
<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 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>
@@ -89,7 +94,10 @@
<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"><i class="ri-add-line mr-2"></i>添加模型</button>
<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">
@@ -100,10 +108,11 @@
<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="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
<tbody id="admin-models-table"><tr><td colspan="7" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
@@ -112,7 +121,10 @@
<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"><i class="ri-add-line mr-2"></i>添加GPU</button>
<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">
@@ -123,10 +135,11 @@
<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="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
<tbody id="admin-gpus-table"><tr><td colspan="7" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
@@ -135,7 +148,10 @@
<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"><i class="ri-add-line mr-2"></i>添加CPU</button>
<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">
@@ -146,10 +162,11 @@
<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="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
<tbody id="admin-cpus-table"><tr><td colspan="7" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
</table>
</div>
</section>
@@ -200,6 +217,44 @@
</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 = '';
@@ -217,6 +272,43 @@
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();
@@ -242,7 +334,7 @@
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'}
'cpus': {id: 'cpus', name: 'CPU管理', icon: 'ri-cpu-line'}
};
let html = fixedItems.map(item => `
@@ -377,7 +469,7 @@
</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 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>
@@ -417,12 +509,16 @@
}
document.getElementById('admin-categories-table').innerHTML = categories.map(c => `
<tr class="border-b hover:bg-gray-50">
<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">${c.order || 0}</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>
@@ -433,16 +529,22 @@
// 加载模型列表
async function loadAdminModels() {
const res = await fetch('/api/models');
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="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="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">
<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>
@@ -453,16 +555,22 @@
// 加载GPU列表
async function loadAdminGpus() {
const res = await fetch('/api/gpus');
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="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="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">
<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">$${g.price_usd || '-'}</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>
@@ -473,16 +581,22 @@
// 加载CPU列表
async function loadAdminCpus() {
const res = await fetch('/api/cpus');
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="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="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">
<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">$${c.price_usd || '-'}</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>
@@ -583,9 +697,9 @@
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'];
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') data[key] = value === 'true';
else if (key === 'is_open_source' || key === 'visible') data[key] = value === 'true';
else data[key] = value;
}
});
@@ -635,6 +749,16 @@
<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>`;
}
</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>`;
@@ -697,7 +821,15 @@
<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><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>
@@ -716,13 +848,145 @@
<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><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>

View File

@@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ category.name }} - ParamHub</title>
<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">
</head>

View File

@@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>对比工具 - ParamHub</title>
<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">
</head>
@@ -40,7 +41,7 @@
<i class="ri-cpu-line mr-2"></i>GPU对比
</button>
<button onclick="setCompareType('cpu')" id="btnCpu" class="px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300">
<i class="ri-memory-line mr-2"></i>CPU对比
<i class="ri-cpu-line mr-2"></i>CPU对比
</button>
</div>
</div>

View File

@@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CPU数据库 - ParamHub</title>
<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">
</head>
@@ -24,7 +25,7 @@
<main class="max-w-7xl mx-auto px-4 py-8">
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
<i class="ri-memory-line text-purple-600"></i>
<i class="ri-cpu-line text-purple-600"></i>
CPU数据库
</h1>
<p class="text-gray-500 mt-1">处理器规格参数一览</p>

View File

@@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPU数据库 - ParamHub</title>
<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">
</head>

View File

@@ -4,6 +4,7 @@
<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">
</head>
@@ -58,7 +59,7 @@
<!-- 热门模型 -->
<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>
<i class="ri-cpu-line text-indigo-600"></i>
热门模型
</h2>
<div id="latestModels" class="grid grid-cols-2 gap-4">
@@ -146,7 +147,7 @@
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: 'cpus_count', label: 'CPU处理器', icon: 'ri-cpu-line', color: 'purple'},
{key: 'knowledge_count', label: '知识条目', icon: 'ri-book-open-line', color: 'teal'}
];

View File

@@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>知识库 - ParamHub</title>
<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">
</head>
@@ -75,7 +76,7 @@
<!-- 显存计算 -->
<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-memory-line text-orange-600"></i>
<i class="ri-cpu-line text-orange-600"></i>
如何计算显存需求?
</h2>
<p class="text-gray-600 leading-relaxed">

View File

@@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模型数据库 - ParamHub</title>
<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">
</head>

View File

@@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实用工具 - ParamHub</title>
<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">
</head>
@@ -33,7 +34,7 @@
<!-- 显存计算器 -->
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
<i class="ri-memory-line text-green-600"></i>
<i class="ri-cpu-line text-green-600"></i>
显存计算器
</h2>
<p class="text-gray-500 mb-4">计算大模型所需的显存大小并推荐合适的GPU</p>