From e7896a660f901ff9b839f4061e9e1ebee9138e3a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 12:36:08 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20MDX=20=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E7=89=87=E6=B8=B2=E6=9F=93=E4=B8=BA=20[objec?= =?UTF-8?q?t=20Object]=20=E5=8F=8A=E5=BB=BA=E8=AE=AE=E6=8E=A5=E5=8F=A3=20J?= =?UTF-8?q?SON=20=E8=A7=A3=E6=9E=90=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mdx-components: remark-image 把本地相对路径图片编译成静态 import, src 是 StaticImageData 对象,原生 直接透传导致 src="[object Object]", 文档内本地图片全部裂图,并触发大量 /docs/.../[object Object] 404 - /api/suggestions: 归一化 key 后的全角冒号与缺失引号;整体 JSON.parse 失败时逐个提取对象,兼容编号列表、多数组拼接等 LLM 输出 - docs/resolve: 后端 404(无历史映射)是正常结论,不再按 error 记录; 403 / 5xx 仍记录 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014zBGrQgQnx1eLA9jQfSJWD --- app/[locale]/docs/[...slug]/page.tsx | 4 +++- app/api/suggestions/route.ts | 22 +++++++++++++++++++++- mdx-components.tsx | 10 +++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/[locale]/docs/[...slug]/page.tsx b/app/[locale]/docs/[...slug]/page.tsx index 49fbc004..f8c79764 100644 --- a/app/[locale]/docs/[...slug]/page.tsx +++ b/app/[locale]/docs/[...slug]/page.tsx @@ -63,7 +63,9 @@ async function resolveDocPath( } return null; } - // 非 3xx:记录真实状态,便于从 Vercel 日志定位(CF 403 / 后端异常等) + // 404 = 后端无历史映射,是正常的"真 404"结论,不算故障 + if (res.status === 404) return null; + // 其余非 3xx:记录真实状态,便于从 Vercel 日志定位(CF 403 / 后端异常等) console.error("[docs/resolve] non-redirect status", { path: strippedPath, status: res.status, diff --git a/app/api/suggestions/route.ts b/app/api/suggestions/route.ts index a07f1c2f..2fa23003 100644 --- a/app/api/suggestions/route.ts +++ b/app/api/suggestions/route.ts @@ -159,6 +159,11 @@ export async function POST(req: Request) { // 修复大模型可能生成的中文引号 cleanedText = cleanedText.replace(/“/g, '"').replace(/”/g, '"'); + // 修复 key 后的全角冒号 / 冒号被包进引号,如 "action:"xx" 或 "action:"xx" + cleanedText = cleanedText.replace( + /"(title|label|action)"?\s*[::]\s*"/g, + '"$1":"', + ); // 尝试仅提取数组部分,防止 AI 返回了前缀描述文本 const arrayMatch = cleanedText.match(/\[[\s\S]*\]/); @@ -166,7 +171,22 @@ export async function POST(req: Request) { cleanedText = arrayMatch[0]; } - questions = JSON.parse(cleanedText); + try { + questions = JSON.parse(cleanedText); + } catch (parseError) { + // 编号列表 / 多个数组拼接等非法外壳:逐个提取对象 + const objects = (cleanedText.match(/\{[^{}]*\}/g) ?? []).flatMap( + (chunk) => { + try { + return [JSON.parse(chunk)]; + } catch { + return []; + } + }, + ); + if (objects.length === 0) throw parseError; + questions = objects; + } } catch (e) { console.error("解析建议 JSON 失败:", e, "原始文本:", text); diff --git a/mdx-components.tsx b/mdx-components.tsx index e5655539..4f6f781a 100644 --- a/mdx-components.tsx +++ b/mdx-components.tsx @@ -30,7 +30,15 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, // 覆盖默认的 img 映射,使用原生 标签而不是 Next.js Image 组件 - img: (props) => {props.alt, + // 本地相对路径图片会被 remark-image 编译成静态 import,src 是 + // StaticImageData 对象而不是字符串,直接透传会渲染成 "[object Object]" + img: ({ src, ...props }) => { + const resolved = + src && typeof src === "object" && "src" in src + ? (src as { src: string }).src + : src; + return {props.alt; + }, ...components, }; }