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

258 lines
12 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>快速搜索</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<header class="hero slim">
<div>
<h1>快速搜索</h1>
<p>支持自然语言搜索,自动映射到料号、名称、规格和备注组合查询</p>
</div>
<nav class="hero-actions">
<a class="btn btn-light" href="{{ url_for('index') }}">返回首页</a>
<a class="btn btn-light" href="{{ url_for('stats_page') }}">统计页</a>
</nav>
</header>
<main class="container">
{% if error %}
<p class="alert">{{ error }}</p>
{% endif %}
{% if notice %}
<p class="notice">{{ notice }}</p>
{% endif %}
<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="如 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">
<table>
<thead>
<tr>
<th>料号</th>
<th>名称</th>
<th>规格</th>
<th>库存</th>
<th>位置</th>
<th>匹配说明</th>
<th>跳转</th>
<th>出库</th>
</tr>
</thead>
<tbody>
{% for row in results %}
{% set c = row.component %}
<tr>
<td>{{ c.part_no }}</td>
<td>{{ c.name }}</td>
<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>
</td>
</tr>
{% else %}
<tr>
<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>
(function () {
var searchInput = document.getElementById('search-input');
var searchForm = document.getElementById('search-form');
if (searchInput && searchForm) {
searchInput.focus();
searchInput.addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
event.preventDefault();
searchForm.requestSubmit();
}
});
}
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') {
event.preventDefault();
var form = input.closest('form');
if (form) {
form.requestSubmit();
}
}
});
});
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>
</html>