diff --git a/app/api/canvas/detail-plan/route.ts b/app/api/canvas/detail-plan/route.ts index 9e66dd918..993d98aba 100644 --- a/app/api/canvas/detail-plan/route.ts +++ b/app/api/canvas/detail-plan/route.ts @@ -244,6 +244,19 @@ const TITLE_META_TERMS = [ "证据背书", ]; +const RENDER_META_PATTERNS: RegExp[] = [ + /第\s*\d+\s*屏/gi, + /\bscreen\s*\d+\b/gi, + /\bframe\s*\d+\b/gi, + /核心机制拆解/gi, + /机制拆解/gi, + /差异强化/gi, + /证据收口/gi, + /核心收益可证/gi, + /方案拆解/gi, + /信任收口/gi, +]; + function hasPlanningTitleMeta(value: string): boolean { const text = String(value || "").trim(); if (!text) return false; @@ -259,6 +272,14 @@ function stripAgentPlanningPhrases(value: string): string { return result.replace(/\s+/g, " ").trim(); } +function stripRenderMetaTerms(value: string): string { + let result = String(value || ""); + for (const pattern of RENDER_META_PATTERNS) { + result = result.replace(pattern, " "); + } + return result.replace(/\s+/g, " ").trim(); +} + function desensitizeAudienceCopy(value: string, language: "zh" | "en"): string { let result = String(value || ""); if (!result) return result; @@ -1245,6 +1266,45 @@ function sanitizePlannedScreens( return fallbackScreens.map((fallback, index) => sanitizeScreen(raw[index], fallback, language)); } +function sanitizeScreensForRender(params: { + screens: DetailPlanScreen[]; + coreFocus: DetailCoreFocusContext; + language: "zh" | "en"; +}): DetailPlanScreen[] { + const { screens, coreFocus, language } = params; + return screens.map((screen, index) => { + const cleanedTitle = stripRenderMetaTerms(screen.title); + const title = + cleanedTitle && !hasPlanningTitleMeta(cleanedTitle) + ? cleanedTitle + : index === 0 + ? coreFocus.firstScreenTitle + : language === "zh" + ? "舒适升级" + : "Comfort Upgrade"; + const subtitleRaw = stripRenderMetaTerms(screen.subtitle); + const subtitle = + subtitleRaw || (index === 0 ? coreFocus.firstScreenSubtitle : screen.subtitle); + return { + ...screen, + title, + subtitle, + objective: stripRenderMetaTerms(screen.objective), + copy: stripRenderMetaTerms(screen.copy), + layoutNote: stripRenderMetaTerms(screen.layoutNote), + modelReason: stripRenderMetaTerms(screen.modelReason), + bundleReason: stripRenderMetaTerms(screen.bundleReason), + visualPrompt: stripRenderMetaTerms(screen.visualPrompt), + fabe: { + b: stripRenderMetaTerms(screen.fabe.b), + f: stripRenderMetaTerms(screen.fabe.f), + a: stripRenderMetaTerms(screen.fabe.a), + e: stripRenderMetaTerms(screen.fabe.e), + }, + }; + }); +} + function sanitizeGlobalStyle( raw: unknown, fallback: DetailPlanResponse["globalStyle"], @@ -1875,7 +1935,12 @@ export async function POST(request: Request) { const shouldUsePreflightPlan = qualityChecksAfterPreflight.passed && preflightPassed; const shouldFallback = !shouldUsePreflightPlan; - const screens = shouldFallback ? fallback.screens : preflightScreens; + const screensRaw = shouldFallback ? fallback.screens : preflightScreens; + const screens = sanitizeScreensForRender({ + screens: screensRaw, + coreFocus, + language, + }); const globalStyle = shouldFallback ? fallback.globalStyle : preflight.candidateGlobalStyle; const qualityIssuesMerged = [ ...new Set([