|
@@ -472,6 +472,31 @@
|
|
|
|
|
|
|
|
// ---------- 数据 ----------
|
|
// ---------- 数据 ----------
|
|
|
|
|
|
|
|
|
|
+ function buildParams(force) {
|
|
|
|
|
+ const params = new URLSearchParams();
|
|
|
|
|
+ if (state.from) params.set('from', state.from);
|
|
|
|
|
+ if (state.to) params.set('to', state.to);
|
|
|
|
|
+ if (state.schools.length) params.set('schools', state.schools.join(','));
|
|
|
|
|
+ if (state.shops.length) params.set('shops', state.shops.join(','));
|
|
|
|
|
+ if (force) params.set('ai', '1');
|
|
|
|
|
+ return params;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 先秒开已生成的报告,再在后台让 AI 重写一版(只尝试一次) */
|
|
|
|
|
+ async function upgradeToAi(seq) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const params = buildParams(false);
|
|
|
|
|
+ params.set('ai', '1');
|
|
|
|
|
+ const { res, body } = await api('/api/ops/report?' + params.toString());
|
|
|
|
|
+ if (!res.ok || !body || !body.report || seq !== state.loadSeq) return;
|
|
|
|
|
+ const report = body.report;
|
|
|
|
|
+ if (report.ai && report.ai.source === 'llm') {
|
|
|
|
|
+ state.report = report;
|
|
|
|
|
+ renderReport(report);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch { /* 后台升级失败保持规则版 */ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
async function load(force) {
|
|
async function load(force) {
|
|
|
if (state.loading) { state.pending = true; return; }
|
|
if (state.loading) { state.pending = true; return; }
|
|
|
state.loading = true;
|
|
state.loading = true;
|
|
@@ -481,13 +506,8 @@
|
|
|
if (saveBtn) saveBtn.disabled = true;
|
|
if (saveBtn) saveBtn.disabled = true;
|
|
|
if (page && !state.report) page.innerHTML = '<div class="rp-loading">正在生成报告…</div>';
|
|
if (page && !state.report) page.innerHTML = '<div class="rp-loading">正在生成报告…</div>';
|
|
|
try {
|
|
try {
|
|
|
- const params = new URLSearchParams();
|
|
|
|
|
- if (state.from) params.set('from', state.from);
|
|
|
|
|
- if (state.to) params.set('to', state.to);
|
|
|
|
|
- if (state.schools.length) params.set('schools', state.schools.join(','));
|
|
|
|
|
- if (state.shops.length) params.set('shops', state.shops.join(','));
|
|
|
|
|
- params.set('ai', '1'); // 默认走 AI,失败自动退回规则版
|
|
|
|
|
- const { res, body } = await api('/api/ops/report?' + params.toString());
|
|
|
|
|
|
|
+ const seq = ++state.loadSeq;
|
|
|
|
|
+ const { res, body } = await api('/api/ops/report?' + buildParams(force).toString());
|
|
|
if (!res.ok) throw new Error((body && (body.detail || body.error)) || ('HTTP ' + res.status));
|
|
if (!res.ok) throw new Error((body && (body.detail || body.error)) || ('HTTP ' + res.status));
|
|
|
if (body.empty) {
|
|
if (body.empty) {
|
|
|
page.innerHTML = '<div class="rp-loading">所选范围没有数据,请先导入明细。</div>';
|
|
page.innerHTML = '<div class="rp-loading">所选范围没有数据,请先导入明细。</div>';
|
|
@@ -495,6 +515,10 @@
|
|
|
}
|
|
}
|
|
|
state.report = body.report;
|
|
state.report = body.report;
|
|
|
renderReport(state.report);
|
|
renderReport(state.report);
|
|
|
|
|
+ if (!force && body.report.ai && body.report.ai.source !== 'llm' && !state.aiUpgradeTried) {
|
|
|
|
|
+ state.aiUpgradeTried = true;
|
|
|
|
|
+ upgradeToAi(seq);
|
|
|
|
|
+ }
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
page.innerHTML = `<div class="rp-loading">报告生成失败:${esc(e.message || e)}</div>`;
|
|
page.innerHTML = `<div class="rp-loading">报告生成失败:${esc(e.message || e)}</div>`;
|
|
|
} finally {
|
|
} finally {
|