From 61e2bbe0e4936c46740944d965e87f79218f4969 Mon Sep 17 00:00:00 2001 From: WuYongJie <549727955@qq.com> Date: Wed, 4 Nov 2026 10:26:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=B2=92=E5=AD=90=E5=8A=A8?= =?UTF-8?q?=E7=94=BB=E5=8A=A0=E8=BD=BD=E4=B8=8E=E7=94=9F=E5=9B=BE=20524=20?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 风格化粒子动画依赖外部 CDN 的 Three.js,全站同域脚本策略会阻止其加载,导致 THREE 未定义。现在使用同域 ES module,保留原 Three.js 0.160.0、动画参数及 MIT 许可。 扶苏在 2026-11-04 09:44 收到的 gpt-image-2.5 HTTP 524 响应是 Cloudflare HTML 页面,错误正文被直接保存和显示。现在将这类响应显示为结果未确认的超时提示;已有历史消息在渲染时同样归一化,不改动原始记录。524 不触发自动模型切换,避免原任务结果未确认时再次生成。普通 HTML 文档和代码示例保持原样。 验证:原生产 CSP 下的本地浏览器验证粒子有可见像素、连续帧变化、窗口缩放及无外部请求、无脚本错误;637 项辅助测试通过、1 项原有跳过,包含供应商 524 只提交一次、结果未确认、不自动切换及正常 503 行为。TypeScript、改动文件 lint、凭据、日志脱敏、环境、元数据、测试与制品检查通过。 已知问题:全量 lint 仍有 1004 项历史诊断。524 的根因是上游长请求超时,本次修复错误展示与未确认结果的处理,不承诺消除线路侧偶发超时。 --- components/elements/response.tsx | 6 ++- components/message.tsx | 3 +- lib/ai/providers/vip98k.ts | 5 +- lib/ai/runtime/model-health.ts | 3 +- lib/request-error.ts | 26 ++++++++++ public/particle-scatter-effect.html | 4 +- public/three/0.160.0/LICENSE | 21 ++++++++ public/three/0.160.0/three.module.min.js | 6 +++ ...mage-provider-error-classification.test.ts | 51 ++++++++++++++++++- 9 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 public/three/0.160.0/LICENSE create mode 100644 public/three/0.160.0/three.module.min.js diff --git a/components/elements/response.tsx b/components/elements/response.tsx index 932d24c9e..83126a680 100644 --- a/components/elements/response.tsx +++ b/components/elements/response.tsx @@ -18,6 +18,7 @@ import { ResponsePdfDownloadButton } from "@/components/elements/response-pdf-do import { buildImageProxyUrl } from "@/lib/image-delivery"; import { splitMarkdownImages } from "@/lib/canvas/markdown-image-segments"; import { stableStreamingMarkdown } from "@/lib/canvas/markdown-image-streaming"; +import { getGatewayErrorPageMessage } from "@/lib/request-error"; import { Collapsible, CollapsibleContent, @@ -1028,7 +1029,10 @@ export const Response = ({ const rawText = typeof props.children === "string" - ? normalizeMarkdownContent(stableStreamingMarkdown(props.children, isMarkdownStreaming)) + ? normalizeMarkdownContent(stableStreamingMarkdown( + getGatewayErrorPageMessage(props.children) || props.children, + isMarkdownStreaming + )) : null; const shouldShowPdfDownload = Boolean(enablePdfDownload && rawText && hasDownloadableReportContent(rawText)); diff --git a/components/message.tsx b/components/message.tsx index 790b935c4..cabc216ba 100644 --- a/components/message.tsx +++ b/components/message.tsx @@ -7,6 +7,7 @@ import type { Vote } from "@/lib/db/schema"; import type { ChatMessage } from "@/lib/types"; import { cn, sanitizeText } from "@/lib/utils"; import { extractImageUrlsFromText } from "@/lib/image-url"; +import { getGatewayErrorPageMessage } from "@/lib/request-error"; import { useDataStream } from "./data-stream-provider"; import { DocumentToolResult } from "./document"; import { DocumentPreview } from "./document-preview"; @@ -521,7 +522,7 @@ const PurePreviewMessage = ({ const displayModelName = extractedModelName || inferredFallbackModelName; const filteredText = message.role === "assistant" - ? appendImageDoneSummary(contentWithoutBadge) + ? getGatewayErrorPageMessage(contentWithoutBadge) || appendImageDoneSummary(contentWithoutBadge) : contentWithoutBadge; const useAssistantTypewriter = message.role === "assistant" && diff --git a/lib/ai/providers/vip98k.ts b/lib/ai/providers/vip98k.ts index 386ad542b..b123fb6ab 100644 --- a/lib/ai/providers/vip98k.ts +++ b/lib/ai/providers/vip98k.ts @@ -48,6 +48,7 @@ import { VIP98K_REFERENCE_STAGING_KEY_PREFIX, } from "@/lib/ai/vip98k-reference-staging"; import { createLogger } from "@/lib/observability/logger"; +import { getUserFacingImageGenerationErrorMessage } from "@/lib/request-error"; import { createDeferredUploadedImage } from "@/lib/storage/image-archive"; import { IMAGE_UPLOAD_MAX_BYTES, @@ -1188,9 +1189,9 @@ async function submitTask(params: { continue; } if ((response.status === 409 && /idempotency\s+key.*(?:still\s+in\s+progress|processing)/i.test(errorMessage)) - || [502, 504].includes(response.status) + || [502, 504, 524].includes(response.status) || /pending\s+safe\s+reconciliation|request\s+outcome\s+(?:is\s+)?unknown|final\s+outcome\s+could\s+not\s+be\s+confirmed/i.test(errorMessage)) - throw new ProviderTaskUncertainError(`供应商提交结果未确认,可能仍在处理,请勿重复生成:${errorMessage} (HTTP ${response.status})`); + throw new ProviderTaskUncertainError(`供应商提交结果未确认,可能仍在处理,请勿重复生成:${getUserFacingImageGenerationErrorMessage({ message: errorMessage, status: response.status })} (HTTP ${response.status})`); throw new Error(`${errorMessage} (HTTP ${response.status})`); } diff --git a/lib/ai/runtime/model-health.ts b/lib/ai/runtime/model-health.ts index 22ab80038..b1403b5a0 100644 --- a/lib/ai/runtime/model-health.ts +++ b/lib/ai/runtime/model-health.ts @@ -12,7 +12,7 @@ import { isMediaInputFailure, isNonProviderRequestStatus, } from "@/lib/ai/runtime/request-failure-classification"; -import { isProviderBalanceError, isRateLimitError } from "@/lib/request-error"; +import { isGatewayResponseTimeout, isProviderBalanceError, isRateLimitError } from "@/lib/request-error"; export type AiModelHealthEventType = | "success" @@ -343,6 +343,7 @@ export function classifyAiModelHealthFailure(error: unknown): { } export function shouldFailoverAiModelError(error: unknown) { + if (isGatewayResponseTimeout(error)) return false; const classification = classifyAiModelHealthFailure(error); const normalized = getErrorMessage(error).toLowerCase(); const imageProviderTaskFailed = diff --git a/lib/request-error.ts b/lib/request-error.ts index b0dc5d3e6..3a060050f 100644 --- a/lib/request-error.ts +++ b/lib/request-error.ts @@ -175,6 +175,28 @@ function getRequestErrorText(error: unknown) { return candidates.join(" ").slice(0, 4000); } +export function getGatewayErrorPageMessage(content: string): string | null { + if (!/^\s*(?: pattern.test(message)); @@ -203,6 +225,8 @@ export function getUserFacingImageGenerationErrorMessage( error: unknown, fallbackMessage = "图片生成失败,请稍后重试", ) { + const gatewayMessage = getPublicGatewayErrorMessage(error); + if (gatewayMessage) return gatewayMessage; if (isProviderBalanceError(error)) { return "图片服务商线路余额或额度不足,请切换模型或联系管理员;与您的积分余额无关。"; } @@ -227,6 +251,8 @@ export function getUserFacingRequestErrorMessage( rateLimitMessage?: string; } ) { + const gatewayMessage = getPublicGatewayErrorMessage(error); + if (gatewayMessage) return gatewayMessage; if (isProviderBalanceError(error)) { return "模型服务商线路余额或额度不足,请切换模型或联系管理员。"; } diff --git a/public/particle-scatter-effect.html b/public/particle-scatter-effect.html index e7579bfba..ce94ef695 100644 --- a/public/particle-scatter-effect.html +++ b/public/particle-scatter-effect.html @@ -4,7 +4,6 @@