feat: 增强 AI 入库预处理功能,支持联网补全和来源展示

This commit is contained in:
2026-03-13 21:26:20 +08:00
parent 8e0bd4f995
commit c193caa88c
4 changed files with 550 additions and 45 deletions

View File

@@ -80,6 +80,10 @@
<form method="post" id="quick-inbound-form" action="{% if box.box_type == 'bag' %}{{ url_for('add_bag_items_batch', box_id=box.id) }}{% else %}{{ url_for('quick_inbound', box_id=box.id) }}{% endif %}">
<input type="hidden" id="ai-inbound-mode" value="{% if box.box_type == 'bag' %}bag{% else %}box{% endif %}">
<textarea class="batch-input" id="quick-inbound-lines" name="lines" rows="8" placeholder="10K-0603, 电阻10K 0603, 500, 1%, 常用\n100nF-0603, 电容100nF 0603, 300, 50V X7R, 去耦"></textarea>
<label>
<input type="checkbox" id="ai-inbound-web-search" checked>
联网补全(适合只有简短描述、来源非嘉立创的物料)
</label>
<p class="hint">建议: part_no 用厂家型号name 用品类+型号specification 只写关键参数。</p>
<p class="hint" id="ai-inbound-status" aria-live="polite"></p>
<section class="ai-preview" id="ai-inbound-preview" hidden>
@@ -102,12 +106,24 @@
</section>
<div class="actions">
<button class="btn btn-light" type="button" id="ai-inbound-parse-btn">AI预处理并预览</button>
<button class="btn btn-light" type="button" id="ai-inbound-show-sources-btn" disabled>查看补全来源</button>
<button class="btn btn-light" type="button" id="ai-inbound-export-invalid-btn" disabled>导出异常行</button>
<button class="btn" type="submit">确认导入</button>
</div>
</form>
</div>
</div>
<div class="modal-backdrop" id="ai-inbound-sources-modal" hidden>
<div class="modal-card panel" role="dialog" aria-modal="true" aria-labelledby="ai-inbound-sources-title">
<div class="group-title-row">
<h2 id="ai-inbound-sources-title">联网补全来源明细</h2>
<button class="btn btn-light" type="button" id="close-ai-inbound-sources">关闭</button>
</div>
<p class="hint">仅作为补全参考,请以实物参数、数据手册或采购页面为准。</p>
<div id="ai-inbound-sources-body" class="ai-source-list"></div>
</div>
</div>
</section>
<aside class="entry-sidebar">
@@ -242,15 +258,21 @@
(function () {
var parseBtn = document.getElementById('ai-inbound-parse-btn');
var showSourcesBtn = document.getElementById('ai-inbound-show-sources-btn');
var sourcesModal = document.getElementById('ai-inbound-sources-modal');
var closeSourcesBtn = document.getElementById('close-ai-inbound-sources');
var sourcesBody = document.getElementById('ai-inbound-sources-body');
var exportInvalidBtn = document.getElementById('ai-inbound-export-invalid-btn');
var textarea = document.getElementById('quick-inbound-lines');
var modeInput = document.getElementById('ai-inbound-mode');
var webSearchInput = document.getElementById('ai-inbound-web-search');
var status = document.getElementById('ai-inbound-status');
var preview = document.getElementById('ai-inbound-preview');
var previewBody = document.getElementById('ai-inbound-preview-body');
var latestRows = [];
var latestWebContext = [];
if (!parseBtn || !textarea || !status || !preview || !previewBody || !exportInvalidBtn) {
if (!parseBtn || !textarea || !status || !preview || !previewBody || !exportInvalidBtn || !showSourcesBtn || !sourcesBody) {
return;
}
@@ -302,6 +324,79 @@
preview.hidden = false;
}
function renderWebContext(contextRows) {
latestWebContext = Array.isArray(contextRows) ? contextRows : [];
showSourcesBtn.disabled = latestWebContext.length <= 0;
if (!latestWebContext.length) {
sourcesBody.innerHTML = '<p class="hint">当前没有联网补全来源可展示。</p>';
return;
}
var html = latestWebContext.map(function (lineRow) {
var lineNo = lineRow.line_no || '-';
var query = escapeHtml(lineRow.query || '');
var sources = Array.isArray(lineRow.sources) ? lineRow.sources : [];
var sourceHtml = sources.map(function (src) {
var title = escapeHtml(src.title || '未命名来源');
var snippet = escapeHtml(src.snippet || '');
var url = (src.url || '').trim();
var reliabilityLevel = (src.reliability_level || 'medium').toLowerCase();
var reliabilityLabel = escapeHtml(src.reliability_label || '中可信');
var reliabilityReason = escapeHtml(src.reliability_reason || '来源类型未知,建议人工确认');
var domain = escapeHtml(src.domain || '未知域名');
var link = url ? '<a href="' + escapeHtml(url) + '" target="_blank" rel="noopener">打开来源</a>' : '<span class="muted">无链接</span>';
return '<li>'
+ '<div class="source-head"><strong>' + title + '</strong><span class="source-badge source-' + reliabilityLevel + '">' + reliabilityLabel + '</span></div>'
+ '<p>' + snippet + '</p>'
+ '<p class="hint">依据: ' + reliabilityReason + '</p>'
+ '<p class="hint">域名: ' + domain + '</p>'
+ link
+ '</li>';
}).join('');
if (!sourceHtml) {
sourceHtml = '<li><span class="muted">无可用来源</span></li>';
}
return '<section class="panel">'
+ '<h3>第 ' + escapeHtml(lineNo) + ' 行</h3>'
+ '<p class="hint">检索词: ' + query + '</p>'
+ '<ul class="ai-source-items">' + sourceHtml + '</ul>'
+ '</section>';
}).join('');
sourcesBody.innerHTML = html;
}
function closeSourcesModal() {
if (!sourcesModal) {
return;
}
sourcesModal.hidden = true;
document.body.classList.remove('modal-open');
}
showSourcesBtn.addEventListener('click', function () {
if (!sourcesModal || showSourcesBtn.disabled) {
return;
}
sourcesModal.hidden = false;
document.body.classList.add('modal-open');
});
if (closeSourcesBtn) {
closeSourcesBtn.addEventListener('click', closeSourcesModal);
}
if (sourcesModal) {
sourcesModal.addEventListener('click', function (event) {
if (event.target === sourcesModal) {
closeSourcesModal();
}
});
}
parseBtn.addEventListener('click', function () {
var lines = (textarea.value || '').trim();
if (!lines) {
@@ -311,6 +406,7 @@
}
parseBtn.disabled = true;
showSourcesBtn.disabled = true;
exportInvalidBtn.disabled = true;
status.textContent = '正在进行 AI 预处理...';
status.classList.remove('ok');
@@ -318,6 +414,7 @@
var payload = new URLSearchParams();
payload.set('lines', lines);
payload.set('mode', modeInput ? modeInput.value : 'box');
payload.set('use_web_search', webSearchInput && webSearchInput.checked ? '1' : '0');
fetch('{{ url_for('ai_inbound_parse') }}', {
method: 'POST',
@@ -334,6 +431,7 @@
});
}).then(function (data) {
renderRows(data.rows || []);
renderWebContext(data.web_context || []);
if (data.normalized_lines) {
textarea.value = data.normalized_lines;
}
@@ -416,6 +514,12 @@
status.textContent = '已导出异常行 ' + invalidRows.length + ' 条';
status.classList.add('ok');
});
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape') {
closeSourcesModal();
}
});
})();
</script>
</body>