专长:增强 AI 设置和搜索功能

- 更新了 AI 设置页面,统一了 AI 补充、自然语言搜索、入站预处理和标签标准化的配置。
- 在编辑页面新增了 AI 标签和笔记标准化板块,包括建议预览及应用功能。
- 改进的搜索页面,支持带有示例的自然语言查询和模糊选择下拉菜单。
- 增强的搜索结果显示,包含更多匹配信息和查看 AI 搜索过程的模态。
- 更新新组件样式,优化布局以提升用户体验。
This commit is contained in:
2026-03-13 19:47:29 +08:00
parent 168f5fe49c
commit 8e0bd4f995
6 changed files with 1149 additions and 37 deletions

View File

@@ -10,7 +10,7 @@
<header class="hero slim">
<div>
<h1>快速搜索</h1>
<p>料号名称搜索,点击可跳转到对应位置并直接出库</p>
<p>支持自然语言搜索,自动映射到料号名称、规格和备注组合查询</p>
</div>
<nav class="hero-actions">
<a class="btn btn-light" href="{{ url_for('index') }}">返回首页</a>
@@ -28,12 +28,57 @@
<section class="panel">
<form id="search-form" method="get" action="{{ url_for('search_page') }}" class="search-row">
<input id="search-input" type="search" name="q" placeholder="输入料号或名称" value="{{ keyword }}" aria-label="搜索关键字">
<input id="search-input" type="search" name="q" placeholder="如 3.3V 稳压芯片、0805 常用电阻、USB 相关器件" value="{{ keyword }}" aria-label="搜索关键字">
<select id="fuzziness-select" name="fuzziness" aria-label="匹配宽松度">
{% for key, profile in fuzziness_profiles.items() %}
<option value="{{ key }}" {% if fuzziness == key %}selected{% endif %}>{{ profile.label }}</option>
{% endfor %}
</select>
<button class="btn" type="submit">搜索</button>
</form>
<div class="search-examples">
<button class="chip" type="button" data-example="3.3V 稳压芯片">3.3V 稳压芯片</button>
<button class="chip" type="button" data-example="0805 常用电阻">0805 常用电阻</button>
<button class="chip" type="button" data-example="USB 相关器件">USB 相关器件</button>
</div>
<p class="hint">出库只需要输入数量,系统会自动扣减库存并记录统计。</p>
<p class="hint">当前宽松度: {{ fuzziness_profiles[fuzziness].label }}(严格更精准,宽松更容易召回)</p>
</section>
{% if search_plan %}
<section class="panel search-analysis">
<div class="group-title-row">
<h2>搜索解析</h2>
<div class="actions">
<span class="hint">模式: {{ 'AI解析' if search_plan.mode == 'ai' else '规则解析' }}</span>
<button class="btn btn-light" type="button" id="show-search-trace">查看AI过程</button>
</div>
</div>
<p class="hint">{{ search_plan.summary }}</p>
{% if search_parse_notice %}
<p class="notice">{{ search_parse_notice }}</p>
{% endif %}
<div class="search-map">
<div>
<strong>料号</strong>
<p>{{ ' / '.join(search_plan.field_map.part_no) if search_plan.field_map.part_no else '-' }}</p>
</div>
<div>
<strong>名称</strong>
<p>{{ ' / '.join(search_plan.field_map.name) if search_plan.field_map.name else '-' }}</p>
</div>
<div>
<strong>规格</strong>
<p>{{ ' / '.join(search_plan.field_map.specification) if search_plan.field_map.specification else '-' }}</p>
</div>
<div>
<strong>备注</strong>
<p>{{ ' / '.join(search_plan.field_map.note) if search_plan.field_map.note else '-' }}</p>
</div>
</div>
</section>
{% endif %}
<section class="panel">
<h2>搜索结果</h2>
<div class="table-wrap">
@@ -45,6 +90,7 @@
<th>规格</th>
<th>库存</th>
<th>位置</th>
<th>匹配说明</th>
<th>跳转</th>
<th>出库</th>
</tr>
@@ -58,10 +104,32 @@
<td>{{ c.specification or '-' }}</td>
<td>{{ c.quantity }}</td>
<td>{{ row.box_name }} / {{ row.slot_code }}</td>
<td>
<div>{{ row.match_summary }}</div>
<div class="hint">综合分: {{ '%.1f'|format(row.match_score) }}</div>
{% if row.matched_terms %}
<div class="match-tags">
{% for term in row.matched_terms %}
<span class="tag">{{ term }}</span>
{% endfor %}
</div>
{% endif %}
{% if row.fuzzy_matches %}
<details class="fuzzy-details">
<summary>模糊命中详情</summary>
<ul>
{% for item in row.fuzzy_matches %}
<li>{{ item.term }} ({{ item.score }})</li>
{% endfor %}
</ul>
</details>
{% endif %}
</td>
<td><a class="btn btn-light" href="{{ row.edit_url }}">进入位置</a></td>
<td>
<form method="post" action="{{ url_for('quick_outbound', component_id=c.id) }}" class="search-outbound-form">
<input type="hidden" name="q" value="{{ keyword }}">
<input type="hidden" name="fuzziness" value="{{ fuzziness }}">
<input type="number" name="amount" min="1" step="1" placeholder="数量" required class="outbound-amount">
<button class="btn" type="submit">出库</button>
</form>
@@ -69,13 +137,47 @@
</tr>
{% else %}
<tr>
<td colspan="7">{% if keyword %}未找到匹配元件{% else %}先输入关键字进行搜索{% endif %}</td>
<td colspan="8">{% if keyword %}未找到匹配元件{% else %}先输入关键字进行搜索{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
<div class="modal-backdrop" id="search-trace-modal" hidden>
<div class="modal-card panel" role="dialog" aria-modal="true" aria-labelledby="search-trace-title">
<div class="group-title-row">
<h2 id="search-trace-title">AI 搜索工作过程</h2>
<button class="btn btn-light" type="button" id="close-search-trace">关闭</button>
</div>
{% if search_trace %}
<ol class="trace-steps">
<li>收到自然语言输入: {{ search_trace.query }}</li>
<li>规则拆分候选字段,生成 fallback 计划</li>
<li>{% if search_trace.used_ai %}调用 AI 解析并返回字段映射{% else %}未调用 AI参数未配置{% endif %}</li>
<li>{% if search_trace.used_fallback %}最终回退规则计划{% else %}最终采用 AI 计划{% endif %}</li>
<li>对每条库存记录执行多字段模糊评分并排序</li>
<li>当前宽松度: {{ search_trace.fuzziness_label if search_trace.fuzziness_label else '-' }}</li>
</ol>
{% if search_trace.ai_error %}
<p class="alert">AI 错误: {{ search_trace.ai_error }}</p>
{% endif %}
<h3>最终计划</h3>
<pre class="trace-block">{{ search_plan|tojson(indent=2) }}</pre>
<h3>规则兜底计划</h3>
<pre class="trace-block">{{ search_trace.fallback_plan|tojson(indent=2) }}</pre>
{% if search_trace.ai_raw %}
<h3>AI 原始返回</h3>
<pre class="trace-block">{{ search_trace.ai_raw }}</pre>
{% endif %}
{% else %}
<p class="hint">当前没有可展示的过程数据。</p>
{% endif %}
</div>
</div>
</main>
<script>
@@ -92,6 +194,16 @@
});
}
document.querySelectorAll('[data-example]').forEach(function (button) {
button.addEventListener('click', function () {
if (!searchInput || !searchForm) {
return;
}
searchInput.value = button.getAttribute('data-example') || '';
searchForm.requestSubmit();
});
});
document.querySelectorAll('.outbound-amount').forEach(function (input) {
input.addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
@@ -103,6 +215,42 @@
}
});
});
var traceOpenBtn = document.getElementById('show-search-trace');
var traceCloseBtn = document.getElementById('close-search-trace');
var traceModal = document.getElementById('search-trace-modal');
function closeTraceModal() {
if (!traceModal) {
return;
}
traceModal.hidden = true;
document.body.classList.remove('modal-open');
}
if (traceOpenBtn && traceModal) {
traceOpenBtn.addEventListener('click', function () {
traceModal.hidden = false;
document.body.classList.add('modal-open');
});
}
if (traceCloseBtn) {
traceCloseBtn.addEventListener('click', closeTraceModal);
}
if (traceModal) {
traceModal.addEventListener('click', function (event) {
if (event.target === traceModal) {
closeTraceModal();
}
});
}
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape') {
closeTraceModal();
}
});
})();
</script>
</body>