Clarify staged AI bulletin reads for city cards
Users saw fast-rule evidence, partial streams, DeepSeek completion, and fallback states as one blended AI status. The city evidence panel now names the current stage directly so a loading stream reads as fast judgment complete, a successful response reads as AI bulletin read complete, and incomplete responses explain that rule evidence is being used. Constraint: Keep the existing fast evidence path visible while DeepSeek streams in. Rejected: Label fallback as an AI failure | that incorrectly implies the card is broken even when rule evidence is valid. Confidence: high Scope-risk: narrow Reversibility: clean Tested: npm run build Not-tested: Manual browser timing of partial stream transitions.
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
- 新增 Qingdao / 青岛城市,结算锚点接入 Wunderground 青岛胶东国际机场 `ZSQD` 历史页,并补齐别名、时区、预热、官方来源和前端地区归类
|
- 新增 Qingdao / 青岛城市,结算锚点接入 Wunderground 青岛胶东国际机场 `ZSQD` 历史页,并补齐别名、时区、预热、官方来源和前端地区归类
|
||||||
- 城市决策卡顶部状态标签收口为 2-3 个高优先级信号,优先展示“实测突破 / 峰值窗口已过 / METAR 过旧 / AI 解读中 / 市场价格缺失 / 模型高度一致 / 需要等待下一报文”,让用户第一眼看到重点
|
- 城市决策卡顶部状态标签收口为 2-3 个高优先级信号,优先展示“实测突破 / 峰值窗口已过 / METAR 过旧 / AI 解读中 / 市场价格缺失 / 模型高度一致 / 需要等待下一报文”,让用户第一眼看到重点
|
||||||
|
- 城市决策卡 AI 机场报文区明确拆分“快速判断已完成,AI 正在补充机场报文细节… / AI 机场报文解读已完成 / AI 解读未完整返回,当前使用规则证据”三种状态,减少 fallback 与流式返回造成的误解
|
||||||
- 城市决策卡新增 AI 机场报文解读缓存说明:页面内存缓存保留 loading / 流式片段 / 最终结果,`localStorage` 保存最终成功 payload,后端 AI 缓存不再因 `local_time` 变化失效
|
- 城市决策卡新增 AI 机场报文解读缓存说明:页面内存缓存保留 loading / 流式片段 / 最终结果,`localStorage` 保存最终成功 payload,后端 AI 缓存不再因 `local_time` 变化失效
|
||||||
- 城市决策卡兜底文案明确标记“快速证据模式”,避免在 DeepSeek 未完整返回时误写成“AI 机场报文解读正常”
|
- 城市决策卡兜底文案明确标记“快速证据模式”,避免在 DeepSeek 未完整返回时误写成“AI 机场报文解读正常”
|
||||||
- 城市决策卡流式 AI 解读改为只请求 METAR/官方观测核心解读与判断依据,最高温中枢、模型一致性和风险清单由后端规则补齐,减少等待时间
|
- 城市决策卡流式 AI 解读改为只请求 METAR/官方观测核心解读与判断依据,最高温中枢、模型一致性和风险清单由后端规则补齐,减少等待时间
|
||||||
|
|||||||
@@ -368,23 +368,46 @@ function AiPinnedCityCard({
|
|||||||
isEn,
|
isEn,
|
||||||
isHkoObservation,
|
isHkoObservation,
|
||||||
});
|
});
|
||||||
|
const aiRuleEvidenceMode = Boolean(
|
||||||
|
aiForecast.status === "failed" ||
|
||||||
|
(aiForecast.status === "ready" && !aiCityForecast) ||
|
||||||
|
aiForecast.payload?.degraded ||
|
||||||
|
aiMeta?.fallback,
|
||||||
|
);
|
||||||
|
const aiReadInProgressText = isEn
|
||||||
|
? isHkoObservation
|
||||||
|
? "Fast read is ready; AI is adding HKO observation details..."
|
||||||
|
: "Fast read is ready; AI is adding airport bulletin details..."
|
||||||
|
: isHkoObservation
|
||||||
|
? "快速判断已完成,AI 正在补充香港天文台观测细节…"
|
||||||
|
: "快速判断已完成,AI 正在补充机场报文细节…";
|
||||||
|
const aiReadCompleteText = isEn
|
||||||
|
? isHkoObservation
|
||||||
|
? "AI HKO observation read is complete."
|
||||||
|
: "AI airport bulletin read is complete."
|
||||||
|
: isHkoObservation
|
||||||
|
? "AI 香港天文台观测解读已完成"
|
||||||
|
: "AI 机场报文解读已完成";
|
||||||
|
const aiRuleEvidenceText = isEn
|
||||||
|
? "AI read did not return completely; rule evidence is being used."
|
||||||
|
: "AI 解读未完整返回,当前使用规则证据";
|
||||||
const aiStatusLabel =
|
const aiStatusLabel =
|
||||||
aiForecast.status === "loading"
|
aiForecast.status === "loading"
|
||||||
? isEn
|
? isEn
|
||||||
? "AI reading"
|
? "Fast read ready"
|
||||||
: "AI 解读中"
|
: "快速判断已完成"
|
||||||
: aiForecast.status === "ready" && aiCityForecast
|
: aiForecast.status === "ready" && aiCityForecast
|
||||||
? aiForecast.payload?.degraded || aiMeta?.fallback
|
? aiRuleEvidenceMode
|
||||||
? isEn
|
? isEn
|
||||||
? "Rule fallback"
|
? "Rule evidence"
|
||||||
: "规则兜底"
|
: "规则证据模式"
|
||||||
: isEn
|
: isEn
|
||||||
? "AI ready"
|
? "AI read complete"
|
||||||
: "AI 已完成"
|
: "AI 解读已完成"
|
||||||
: aiForecast.status === "failed"
|
: aiRuleEvidenceMode
|
||||||
? isEn
|
? isEn
|
||||||
? "AI failed"
|
? "Rule evidence"
|
||||||
: "AI 失败"
|
: "规则证据模式"
|
||||||
: isEn
|
: isEn
|
||||||
? "AI pending"
|
? "AI pending"
|
||||||
: "AI 待返回";
|
: "AI 待返回";
|
||||||
@@ -392,11 +415,11 @@ function AiPinnedCityCard({
|
|||||||
aiForecast.status === "loading"
|
aiForecast.status === "loading"
|
||||||
? "blue"
|
? "blue"
|
||||||
: aiForecast.status === "ready" && aiCityForecast
|
: aiForecast.status === "ready" && aiCityForecast
|
||||||
? aiForecast.payload?.degraded || aiMeta?.fallback
|
? aiRuleEvidenceMode
|
||||||
? "amber"
|
? "amber"
|
||||||
: "green"
|
: "green"
|
||||||
: aiForecast.status === "failed"
|
: aiRuleEvidenceMode
|
||||||
? "red"
|
? "amber"
|
||||||
: "muted";
|
: "muted";
|
||||||
const marketFreshnessLabel =
|
const marketFreshnessLabel =
|
||||||
marketDecisionView.status === "ready"
|
marketDecisionView.status === "ready"
|
||||||
@@ -449,7 +472,7 @@ function AiPinnedCityCard({
|
|||||||
: null,
|
: null,
|
||||||
aiForecast.status === "loading"
|
aiForecast.status === "loading"
|
||||||
? {
|
? {
|
||||||
label: isEn ? "AI reading" : "AI 解读中",
|
label: isEn ? "Fast read ready" : "快速判断已完成",
|
||||||
tone: aiStatusTone,
|
tone: aiStatusTone,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
@@ -685,17 +708,14 @@ function AiPinnedCityCard({
|
|||||||
</div>
|
</div>
|
||||||
{aiForecast.status === "loading" ? (
|
{aiForecast.status === "loading" ? (
|
||||||
<>
|
<>
|
||||||
<p className={aiForecast.streamText ? "scan-ai-weather-summary" : undefined}>
|
<p className="scan-ai-weather-summary">
|
||||||
{localizedFinalJudgment ||
|
{aiReadInProgressText}
|
||||||
aiForecast.streamText ||
|
|
||||||
(isEn
|
|
||||||
? isHkoObservation
|
|
||||||
? "Fast read is ready; AI is adding HKO observation details..."
|
|
||||||
: "Fast read is ready; AI is adding airport bulletin details..."
|
|
||||||
: isHkoObservation
|
|
||||||
? "快速判断已完成,AI 正在补充香港天文台观测细节…"
|
|
||||||
: "快速判断已完成,AI 正在补充机场报文细节…")}
|
|
||||||
</p>
|
</p>
|
||||||
|
{localizedFinalJudgment || aiForecast.streamText ? (
|
||||||
|
<p className="scan-ai-city-muted">
|
||||||
|
{localizedFinalJudgment || aiForecast.streamText}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
<p className="scan-ai-city-muted">
|
<p className="scan-ai-city-muted">
|
||||||
{isEn
|
{isEn
|
||||||
? isHkoObservation
|
? isHkoObservation
|
||||||
@@ -709,11 +729,13 @@ function AiPinnedCityCard({
|
|||||||
) : aiForecast.status === "ready" && aiCityForecast ? (
|
) : aiForecast.status === "ready" && aiCityForecast ? (
|
||||||
<>
|
<>
|
||||||
<p className="scan-ai-weather-summary">
|
<p className="scan-ai-weather-summary">
|
||||||
{localizedFinalJudgment ||
|
{aiRuleEvidenceMode ? aiRuleEvidenceText : aiReadCompleteText}
|
||||||
(isEn ? "AI read returned without a final sentence." : "AI 已返回,但缺少最终判断。")}
|
|
||||||
</p>
|
</p>
|
||||||
<ul className="scan-ai-weather-bullets">
|
<ul className="scan-ai-weather-bullets">
|
||||||
{aiBullets.map((line, index) => (
|
{[
|
||||||
|
localizedFinalJudgment,
|
||||||
|
...aiBullets,
|
||||||
|
].filter((line) => String(line || "").trim()).map((line, index) => (
|
||||||
<li key={`${line}-${index}`}>{line}</li>
|
<li key={`${line}-${index}`}>{line}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -723,34 +745,22 @@ function AiPinnedCityCard({
|
|||||||
</>
|
</>
|
||||||
) : aiForecast.status === "ready" ? (
|
) : aiForecast.status === "ready" ? (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p className="scan-ai-weather-summary">
|
||||||
{aiForecast.payload?.status === "timeout"
|
{aiRuleEvidenceText}
|
||||||
? isEn
|
|
||||||
? "DeepSeek enhancement timed out. You can retry; city data and the right briefing were not refreshed."
|
|
||||||
: "DeepSeek 增强本次超时,可稍后重试;城市数据和右侧简报不会被刷新。"
|
|
||||||
: fallbackAiReason ||
|
|
||||||
(isEn
|
|
||||||
? "AI read is unavailable for this city right now."
|
|
||||||
: "该城市暂时没有可用的 AI 解读。")}
|
|
||||||
</p>
|
</p>
|
||||||
<ul className="scan-ai-weather-bullets">
|
<ul className="scan-ai-weather-bullets">
|
||||||
|
{fallbackAiReason ? <li>{fallbackAiReason}</li> : null}
|
||||||
<li>{localModelSupportNote}</li>
|
<li>{localModelSupportNote}</li>
|
||||||
<li>{rawObservationText}</li>
|
<li>{rawObservationText}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
) : aiForecast.status === "failed" ? (
|
) : aiForecast.status === "failed" ? (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p className="scan-ai-weather-summary">
|
||||||
{isEn
|
{aiRuleEvidenceText}
|
||||||
? isHkoObservation
|
|
||||||
? "AI read failed. Model support and the HKO observation remain as fallback context."
|
|
||||||
: "AI read failed. Model support and the raw METAR remain as fallback context."
|
|
||||||
: isHkoObservation
|
|
||||||
? "AI 解读失败。下方保留多模型支撑和香港天文台观测作为兜底上下文。"
|
|
||||||
: "AI 解读失败。下方保留多模型支撑和原始 METAR 作为兜底上下文。"}
|
|
||||||
{aiForecast.error ? ` ${aiForecast.error}` : ""}
|
|
||||||
</p>
|
</p>
|
||||||
<ul className="scan-ai-weather-bullets">
|
<ul className="scan-ai-weather-bullets">
|
||||||
|
{aiForecast.error ? <li>{aiForecast.error}</li> : null}
|
||||||
<li>{localModelSupportNote}</li>
|
<li>{localModelSupportNote}</li>
|
||||||
<li>{rawObservationText}</li>
|
<li>{rawObservationText}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user