feat: 支持多图上传和智能解析产品参数

- 新增 /api/parse-images API 预览解析结果
- 智能添加支持多张图片上传和粘贴
- 支持一次解析出多个产品参数
- 所有类别(模型/GPU/CPU/动态分类)都支持图片解析
- 添加 vision_model 配置支持视觉模型
This commit is contained in:
2026-04-27 18:29:06 +08:00
parent 45190980a9
commit e2d35b6fa0
2 changed files with 431 additions and 110 deletions

View File

@@ -277,25 +277,52 @@
<!-- 智能添加弹窗 -->
<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="bg-white rounded-xl max-w-4xl 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>
<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 class="mb-6">
<p class="text-sm text-gray-500 mb-3">上传产品图片AI将自动识别并解析参数。支持一次上传多张图片可识别多个产品。</p>
<div class="flex flex-wrap gap-3 mb-3" id="smartImagePreviewArea">
<!-- 图片预览区 -->
</div>
<div class="flex gap-3">
<input type="file" id="smartImageInput" accept="image/*" multiple class="hidden" onchange="handleSmartImageUpload(event)">
<button onclick="document.getElementById('smartImageInput').click()" class="px-4 py-2 bg-orange-100 text-orange-600 rounded-lg hover:bg-orange-200 text-sm">
<i class="ri-image-add-line mr-1"></i>选择图片(支持多选)
</button>
<button onclick="pasteSmartImageFromClipboard()" class="px-4 py-2 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 text-sm">
<i class="ri-clipboard-line mr-1"></i>粘贴图片
</button>
<button onclick="clearSmartImages()" class="px-4 py-2 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 text-sm">
<i class="ri-delete-bin-line mr-1"></i>清空图片
</button>
</div>
<div class="mt-3 text-xs text-gray-400">
<i class="ri-information-line mr-1"></i>
已选择 <span id="smartImageCount">0</span> 张图片
</div>
</div>
<div class="border-t pt-4">
<label class="text-sm text-gray-600 mb-2 block">补充文本(可选)</label>
<textarea id="smartAddText" rows="4" class="w-full p-4 border border-gray-200 rounded-lg focus:outline-none focus:border-orange-400 text-gray-700" placeholder="可粘贴补充信息文本,与图片一起解析..."></textarea>
</div>
<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>
<h3 class="text-sm font-semibold text-gray-700 mb-2"><i class="ri-checkbox-circle-line text-green-600 mr-1"></i>解析结果预览:</h3>
<div class="bg-gray-50 rounded-lg p-4 text-sm text-gray-600" id="smartAddResult">
<!-- 解析结果显示 -->
</div>
<div class="mt-3 flex gap-2" id="smartAddActions">
<!-- 操作按钮 -->
</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>
<button onclick="previewSmartParse()" id="previewBtn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"><i class="ri-eye-line mr-1"></i>预览解析结果</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>
@@ -1151,11 +1178,15 @@ GPT-4是OpenAI发布的大语言模型参数量约1.8万亿支持128K上
// ============ 智能添加功能 ============
let smartAddType = '';
let smartAddImages = []; // 智能添加的图片列表
function openSmartAddModal(type) {
smartAddType = type;
smartAddImages = [];
document.getElementById('smartAddText').value = '';
document.getElementById('smartAddPreview').classList.add('hidden');
document.getElementById('smartImagePreviewArea').innerHTML = '';
document.getElementById('smartImageCount').textContent = '0';
document.getElementById('smartAddModal').classList.remove('hidden');
}
@@ -1163,16 +1194,158 @@ GPT-4是OpenAI发布的大语言模型参数量约1.8万亿支持128K上
document.getElementById('smartAddModal').classList.add('hidden');
}
// 处理智能添加图片上传
async function handleSmartImageUpload(event) {
const files = event.target.files;
for (let file of files) {
const formData = new FormData();
formData.append('file', file);
try {
const res = await fetch('/api/upload/image', {
method: 'POST',
body: formData
});
const data = await res.json();
if (data.success) {
smartAddImages.push(data.url);
updateSmartImagePreview();
}
} catch (e) {
alert('上传失败: ' + e.message);
}
}
event.target.value = '';
}
// 从剪贴板粘贴图片
async function pasteSmartImageFromClipboard() {
try {
const clipboardItems = await navigator.clipboard.read();
for (const item of clipboardItems) {
for (const type of item.types) {
if (type.startsWith('image/')) {
const blob = await item.getType(type);
const reader = new FileReader();
reader.onload = async (e) => {
const base64 = e.target.result;
const res = await fetch('/api/upload/image/base64', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ image: base64 })
});
const data = await res.json();
if (data.success) {
smartAddImages.push(data.url);
updateSmartImagePreview();
}
};
reader.readAsDataURL(blob);
}
}
}
} catch (e) {
alert('无法从剪贴板获取图片,请使用文件选择');
}
}
// 清空智能添加图片
function clearSmartImages() {
smartAddImages = [];
updateSmartImagePreview();
}
// 移除单张图片
function removeSmartImage(index) {
smartAddImages.splice(index, 1);
updateSmartImagePreview();
}
// 更新智能添加图片预览
function updateSmartImagePreview() {
const area = document.getElementById('smartImagePreviewArea');
const count = document.getElementById('smartImageCount');
area.innerHTML = smartAddImages.map((url, idx) => `
<div class="relative w-24 h-24 border rounded-lg overflow-hidden group">
<img src="${url}" class="w-full h-full object-cover">
<button onclick="removeSmartImage(${idx})" class="absolute top-1 right-1 w-6 h-6 bg-red-500 text-white rounded-full opacity-0 group-hover:opacity-100 transition flex items-center justify-center">
<i class="ri-close-line"></i>
</button>
</div>
`).join('');
count.textContent = smartAddImages.length;
}
// 预览解析结果
async function previewSmartParse() {
const text = document.getElementById('smartAddText').value.trim();
if (!text && smartAddImages.length === 0) {
alert('请上传图片或输入文本');
return;
}
const btn = document.getElementById('previewBtn');
btn.disabled = true;
btn.innerHTML = '<i class="ri-loader-4-line animate-spin mr-1"></i>解析中...';
try {
const res = await fetch('/api/parse-images', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: text,
images: smartAddImages,
category_type: smartAddType
})
});
const data = await res.json();
if (data.error) {
alert('解析失败: ' + data.error);
} else {
// 显示解析结果
document.getElementById('smartAddPreview').classList.remove('hidden');
let html = `<div class="mb-2 text-green-600"><i class="ri-checkbox-circle-line mr-1"></i>识别到 ${data.count} 个产品</div>`;
data.products.forEach((product, idx) => {
html += `
<div class="bg-white rounded-lg p-3 mb-2 border">
<div class="font-medium text-gray-800 mb-2">产品 ${idx + 1}: ${product.name || '未命名'}</div>
<div class="text-xs text-gray-600 grid grid-cols-2 gap-2">
${Object.entries(product).map(([k, v]) => `
<div><span class="text-gray-400">${k}:</span> ${v || '-'}</div>
`).join('')}
</div>
</div>
`;
});
document.getElementById('smartAddResult').innerHTML = html;
}
} catch (e) {
alert('请求失败: ' + e.message);
}
btn.disabled = false;
btn.innerHTML = '<i class="ri-eye-line mr-1"></i>预览解析结果';
}
async function smartAddSubmit() {
const text = document.getElementById('smartAddText').value.trim();
if (!text) {
alert('请粘贴产品信息文本');
if (!text && smartAddImages.length === 0) {
alert('请上传图片或输入文本');
return;
}
const btn = document.getElementById('smartAddBtn');
btn.disabled = true;
btn.innerHTML = '<i class="ri-loader-4-line animate-spin mr-1"></i>解析中...';
btn.innerHTML = '<i class="ri-loader-4-line animate-spin mr-1"></i>解析并保存中...';
try {
let endpoint;
@@ -1184,7 +1357,10 @@ GPT-4是OpenAI发布的大语言模型参数量约1.8万亿支持128K上
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
body: JSON.stringify({
text: text,
images: smartAddImages
})
});
const data = await res.json();
@@ -1194,26 +1370,38 @@ GPT-4是OpenAI发布的大语言模型参数量约1.8万亿支持128K上
} else {
// 显示解析结果
document.getElementById('smartAddPreview').classList.remove('hidden');
document.getElementById('smartAddResult').innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
let html = `<div class="mb-2 text-green-600"><i class="ri-checkbox-circle-fill mr-1"></i>成功添加 ${data.count} 个产品</div>`;
data.products.forEach((product, idx) => {
html += `
<div class="bg-white rounded-lg p-3 mb-2 border border-green-200">
<div class="font-medium text-gray-800 mb-2">产品 ${idx + 1}: ${product.name || '未命名'}</div>
<div class="text-xs text-gray-500">ID: ${product.id}</div>
</div>
`;
});
document.getElementById('smartAddResult').innerHTML = html;
// 关闭弹窗并刷新列表
closeSmartAddModal();
if (smartAddType === 'dynamic') showDynamicCategory(dynamicCategoryId);
else {
const loaders = {model: loadAdminModels, gpu: loadAdminGpus, cpu: loadAdminCpus};
loaders[smartAddType]();
}
loadOverview();
alert('智能添加成功!数据已自动解析并保存。');
setTimeout(() => {
closeSmartAddModal();
if (smartAddType === 'dynamic') showDynamicCategory(dynamicCategoryId);
else {
const loaders = {model: loadAdminModels, gpu: loadAdminGpus, cpu: loadAdminCpus};
loaders[smartAddType]();
}
loadOverview();
}, 1500);
}
} catch (e) {
alert('请求失败: ' + e.message);
}
btn.disabled = false;
btn.innerHTML = '<i class="ri-magic-line mr-1"></i>智能解析并添加';
btn.innerHTML = '<i class="ri-magic-line mr-1"></i>解析并添加';
}
// ============ 显示切换功能 ============