From 4993354969cd16035bdecf0e8514c7fb7a916925 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 22 Feb 2026 12:32:52 +0800 Subject: [PATCH] feat(canvas): strengthen detail first-screen naming and poster constraints --- app/api/canvas/detail-plan/route.ts | 146 ++++++++++++++++++++++---- app/api/canvas/detail-review/route.ts | 11 ++ components/canvas/CanvasChatPanel.tsx | 9 +- 3 files changed, 143 insertions(+), 23 deletions(-) diff --git a/app/api/canvas/detail-plan/route.ts b/app/api/canvas/detail-plan/route.ts index d6efa4bd3..4d56af35a 100644 --- a/app/api/canvas/detail-plan/route.ts +++ b/app/api/canvas/detail-plan/route.ts @@ -173,6 +173,7 @@ type DetailPlanPreflight = { type DetailCoreFocusContext = { anchor: string; primaryNeed: string; + namingToken: string; firstScreenTitle: string; firstScreenSubtitle: string; focusTerms: string[]; @@ -395,7 +396,7 @@ function toColloquialZhStem(stem: string): string { return next; } -function buildZhCoreNamingTitle(brief: DetailBrief, anchor: string, primaryNeed: string): string { +function buildZhCoreNamingWord(brief: DetailBrief, anchor: string, primaryNeed: string): string { const suffix = inferZhProductSuffix(brief.productName); let stem = toColloquialZhStem(inferZhNamingStem(anchor, primaryNeed)); if (!suffix) return compactZhTitle(anchor); @@ -417,6 +418,43 @@ function buildZhCoreNamingTitle(brief: DetailBrief, anchor: string, primaryNeed: return title; } +function inferZhNeedTag(params: { + anchor: string; + primaryNeed: string; + expectedText: string; + scenario: string; +}): string { + const source = `${params.anchor} ${params.primaryNeed} ${params.expectedText} ${params.scenario}`; + const rules: Array<{ pattern: RegExp; phrase: string }> = [ + { pattern: /自拍|拍摄|防抖|不抖|稳拍/, phrase: "自拍不手抖" }, + { pattern: /过敏|低敏|敏感|红痒/, phrase: "敏肌不红痒" }, + { pattern: /漏|防漏|整夜|夜用/, phrase: "整夜不漏尿" }, + { pattern: /腰|护腰|久坐|腰酸/, phrase: "久坐不腰酸" }, + { pattern: /收纳|省空间|不占地/, phrase: "收纳不占地" }, + { pattern: /透气|不闷|呼吸感/, phrase: "久用不闷热" }, + { pattern: /清洁|去污|去渍|好洗/, phrase: "去污不费劲" }, + { pattern: /便携|轻便|小巧|出门/, phrase: "出门更轻松" }, + ]; + const hit = rules.find((item) => item.pattern.test(source)); + if (hit) return hit.phrase; + + const compactNeed = compactZhTitle(params.expectedText || params.primaryNeed || params.anchor); + if (!compactNeed) return "用着更省心"; + if (compactNeed.length <= 4) return `${compactNeed}更省心`; + return `${compactNeed.slice(0, 6)}更省心`; +} + +function buildZhNameNeedTitle(params: { namingToken: string; needTag: string }): string { + const left = String(params.namingToken || "").trim() || "好用款"; + let right = String(params.needTag || "").trim() || "用着更省心"; + right = right.replace(/[。!!??]+$/g, ""); + let title = `${left},${right}`; + if (title.length > 20) { + title = `${left},${right.slice(0, Math.max(4, 19 - left.length))}`; + } + return title; +} + function buildCoreFocusContext(brief: DetailBrief, language: "zh" | "en"): DetailCoreFocusContext { const anchorRaw = brief.coreSellingPoint || @@ -442,21 +480,35 @@ function buildCoreFocusContext(brief: DetailBrief, language: "zh" | "en"): Detai return { anchor: anchorRaw, primaryNeed, + namingToken: title, firstScreenTitle: title, firstScreenSubtitle: subtitle, focusTerms, }; } - const title = buildZhCoreNamingTitle(brief, anchorRaw, primaryNeed); + const namingToken = buildZhCoreNamingWord(brief, anchorRaw, primaryNeed); + const needTag = inferZhNeedTag({ + anchor: anchorRaw, + primaryNeed, + expectedText, + scenario, + }); + const title = buildZhNameNeedTitle({ namingToken, needTag }); const subtitle = `在${scenario}场景,不用${riskText}也能${expectedText}。`; return { anchor: anchorRaw, primaryNeed, + namingToken, firstScreenTitle: title, firstScreenSubtitle: subtitle, focusTerms: Array.from( - new Set([...focusTerms, ...splitToCoreTerms(title, language)]) + new Set([ + ...focusTerms, + ...splitToCoreTerms(title, language), + ...splitToCoreTerms(namingToken, language), + ...splitToCoreTerms(needTag, language), + ]) ), }; } @@ -467,13 +519,19 @@ function containsCoreTerms(text: string, terms: string[]): boolean { return terms.some((term) => source.includes(String(term || "").toLowerCase())); } +function hasFirstScreenPosterSignal(screen?: DetailPlanScreen): boolean { + if (!screen) return false; + const merged = `${screen.objective} ${screen.layoutNote} ${screen.visualPrompt}`.toLowerCase(); + return /海报|封面|主视觉|kv|hero|poster|cover/.test(merged); +} + function enforceCoreFocusOnScreens(params: { screens: DetailPlanScreen[]; coreFocus: DetailCoreFocusContext; language: "zh" | "en"; }): DetailPlanScreen[] { const { screens, coreFocus, language } = params; - const namingWord = coreFocus.firstScreenTitle; + const namingWord = coreFocus.namingToken || coreFocus.firstScreenTitle; return screens.map((screen, index) => { const mergedText = `${screen.title} ${screen.subtitle} ${screen.copy} ${screen.fabe.b}`; const focused = containsCoreTerms(mergedText, coreFocus.focusTerms); @@ -481,11 +539,16 @@ function enforceCoreFocusOnScreens(params: { language === "en" ? `Core selling-point anchor: ${coreFocus.anchor}. This frame must serve this same anchor and avoid topic drift.` : `核心卖点锚点:${coreFocus.anchor}。本屏必须服务同一核心卖点,不得偏题。`; + const firstScreenPosterLine = + language === "en" + ? "Screen 1 must be a hero poster cover: single dominant product visual, cinematic light, strong focal contrast, clean headline-safe top area, no collage." + : "首屏必须是封面海报感:单一主视觉主体、电影级光影、强焦点对比、顶部清晰标题安全区,禁止拼贴说明图。"; let title = screen.title; let subtitle = screen.subtitle; let objective = screen.objective; let copy = screen.copy; + let layoutNote = screen.layoutNote; let benefit = screen.fabe.b; if (index === 0) { @@ -498,6 +561,14 @@ function enforceCoreFocusOnScreens(params: { subtitle = containsCoreTerms(`${subtitle} ${copy}`, coreFocus.focusTerms) ? subtitle : coreFocus.firstScreenSubtitle; + objective = + language === "en" + ? `Hero poster cover for the One selling-point in one glance, instantly answer why it is worth buying.` + : "封面海报化呈现The One核心卖点,一眼回答“为什么值得买”。"; + layoutNote = + language === "en" + ? "Poster composition: product hero occupies 55-70% visual weight; top text safe area is clean; avoid dense multi-module layout." + : "海报封面构图:产品主体占画面55-70%视觉权重,顶部标题区留白清晰,避免密集小模块拼贴。"; } if (!containsCoreTerms(objective, coreFocus.focusTerms)) { @@ -534,13 +605,20 @@ function enforceCoreFocusOnScreens(params: { subtitle, objective, copy, + layoutNote, fabe: { ...screen.fabe, b: benefit, }, - visualPrompt: screen.visualPrompt.includes(coreFocus.anchor) - ? screen.visualPrompt - : `${screen.visualPrompt} ${promptCoreLine}`, + visualPrompt: (() => { + let nextPrompt = screen.visualPrompt.includes(coreFocus.anchor) + ? screen.visualPrompt + : `${screen.visualPrompt} ${promptCoreLine}`; + if (index === 0 && !/海报|封面|主视觉|kv|hero|poster|cover/i.test(nextPrompt)) { + nextPrompt = `${nextPrompt} ${firstScreenPosterLine}`; + } + return nextPrompt; + })(), }; }); } @@ -564,7 +642,7 @@ function buildTypographySpec(language: "zh" | "en") { return { fontFamily: "Noto Sans SC / PingFang SC / Microsoft YaHei", title: - "主标题 64px、字重700、行高1.15,建议单行,最多2行,控制在12字以内,不得出现“第X屏”", + "主标题 64px、字重700、行高1.15,建议1-2行,首屏控制在6-20字(命名词+需求句),不得出现“第X屏”", subtitle: "副标题 34px、字重600、行高1.25,位于主标题下方16px,控制在30字以内", body: @@ -792,14 +870,14 @@ function buildFallbackScreens(params: { const defaultObjectives = language === "en" ? [ - "Hero scene with one core value", + "Hero poster cover with one core value", "Pain-point resonance and trigger moment", "Solution structure and key mechanism", "Differentiation reinforcement", "Evidence and trust closing", ] : [ - "首屏主视觉聚焦单一核心卖点", + "首屏海报主视觉聚焦单一核心卖点", "痛点共鸣与触发场景放大", "解决方案与关键机制说明", "差异化优势重复强化", @@ -826,7 +904,7 @@ function buildFallbackScreens(params: { return Array.from({ length: count }).map((_, index) => { const screenType = screenTypeSequence[index] || (index % 3 === 0 ? "B" : index % 3 === 1 ? "A" : "C"); - const objective = + let objective = defaultObjectives[index] || (language === "en" ? `Extended detail frame ${index + 1}` @@ -858,7 +936,7 @@ function buildFallbackScreens(params: { : language === "en" ? `${productLabel} turns key pain points into visible outcomes under the anchor "${coreFocus.anchor}", keeping one consistent decision narrative.` : `${productLabel}围绕“${coreFocus.anchor}”将关键痛点转为可感知结果,形成统一的购买决策路径。`; - const layoutNote = + let layoutNote = isBundleScreen ? language === "en" ? "Main product remains primary; companion products are secondary with clear hierarchy and no visual clutter." @@ -897,6 +975,17 @@ function buildFallbackScreens(params: { e: "使用检测、认证、对比图等证据收尾。", }; + if (index === 0 && !isBundleScreen) { + objective = + language === "en" + ? "Poster-style hero cover for the One selling-point, making the buying reason obvious in one glance." + : "海报级首屏封面,聚焦The One核心卖点,一眼说明购买理由。"; + layoutNote = + language === "en" + ? "Hero poster layout: dominant single product focal point, cinematic lighting and contrast, clear top headline safe area, no collage cards." + : "首屏海报布局:单一主体强焦点,电影感光影与对比,顶部标题区清晰,禁止拼贴卡片式说明。"; + } + const visualPrompt = language === "en" ? [ @@ -906,6 +995,9 @@ function buildFallbackScreens(params: { `Main scene: ${mainScenario}; core demand: ${coreNeed}.`, "Audience profile is internal strategy only; do not expose explicit age/gender labels in visible copy.", `Core selling-point anchor: ${coreFocus.anchor}; this frame must stay aligned to this anchor.`, + index === 0 + ? "Screen 1 hero-cover rule: this must look like a poster key visual with one dominant subject, strong focus contrast, and no collage cards." + : "", `Positioning statement: ${positioning}.`, `Global style: ${globalStyle.designLanguage}.`, `Typography: ${globalStyle.typography.fontFamily}; ${globalStyle.typography.title}; ${globalStyle.typography.subtitle}; ${globalStyle.typography.body}.`, @@ -930,6 +1022,9 @@ function buildFallbackScreens(params: { `主场景:${mainScenario};核心需求:${coreNeed}。`, "人群画像仅用于内部策略,画面可见文案不得直写年龄/性别标签。", `核心卖点锚点:${coreFocus.anchor},本屏文案必须围绕该锚点。`, + index === 0 + ? "首屏封面规则:必须是海报主视觉(非说明图拼贴),单一主体大图、强聚焦、高级商业海报质感。" + : "", `一句话定位:${positioning}。`, `全局风格:${globalStyle.designLanguage}。`, `字体规范:${globalStyle.typography.fontFamily};${globalStyle.typography.title};${globalStyle.typography.subtitle};${globalStyle.typography.body}。`, @@ -1143,6 +1238,9 @@ function evaluatePlanQuality(params: { if (firstFive.length >= 1 && firstFive[0]?.screenType !== "B") { issues.push("第1屏应为B类场景共鸣页。"); } + if (!hasFirstScreenPosterSignal(firstFive[0])) { + issues.push("首屏缺少“封面海报感”约束(主视觉/海报/封面/KV),需强化首屏主视觉表现。"); + } if (firstFive.length >= 2 && firstFive[1]?.screenType !== "A") { issues.push("第2屏建议为A类技术公信页。"); } @@ -1186,11 +1284,17 @@ function evaluatePlanQuality(params: { const titleLengthOutOfRange = screens.some((screen, index) => { const len = visibleTitleLength(screen.title); - const minLen = index === 0 ? 3 : 4; - return len < minLen || len > 10; + const minLen = index === 0 ? 6 : 4; + const maxLen = index === 0 ? 20 : 10; + return len < minLen || len > maxLen; }); if (titleLengthOutOfRange) { - issues.push("存在分镜主标题长度不符合规范(首屏3-10字,其余4-10字)。"); + issues.push("存在分镜主标题长度不符合规范(首屏6-20字,其余4-10字)。"); + } + + const firstTitle = String(screens[0]?.title || "").trim(); + if (firstTitle && !/[,,::·|]/.test(firstTitle)) { + issues.push("首屏主标题需采用“命名词+需求句”结构(示例:甩甩杆,自拍不手抖)。"); } const subtitleTooLong = screens.some((screen) => { @@ -1225,7 +1329,7 @@ function evaluatePlanQuality(params: { issues.push("多屏文案偏离核心卖点,整套详情需围绕同一核心卖点展开。"); } - const namingWord = String(firstScreen?.title || "").trim(); + const namingWord = String(coreFocus.namingToken || firstScreen?.title || "").trim(); if (namingWord) { const repeatCount = screens .slice(1) @@ -1332,6 +1436,7 @@ async function runDualDirectorPlanPreflight(params: { "禁用表达:主标题禁止出现“核心价值/场景痛点/方案拆解/细节证明/信任收口/卖点延展”等节奏词。", "禁用表达:对外文案不得直写具体年龄/性别标签(如“25-35岁女性”),必须改为场景化表达。", "必须维持 790x1300 固定规格、全套统一字体层级与统一风格。", + "首屏必须具备“封面海报感”:单一主视觉、强焦点、清晰标题区,不得做拼贴式说明页。", `整套文案必须围绕同一核心卖点锚点:“${params.coreFocus.anchor}”,不得跑题。`, params.hasModelImage ? "已提供模特图:仅在必要分镜使用且保持同一人物一致性。" @@ -1517,7 +1622,7 @@ export async function POST(request: Request) { "必须遵循“详情升级文案方案”:定位卡 -> 卖点到买点转译 -> 黄金五屏 -> FABE闭环。", `核心卖点锚点(整套必须围绕):${coreFocus.anchor}`, `首屏标题参考(必须承接核心卖点):${coreFocus.firstScreenTitle}`, - "首屏命名规则:用核心卖点给产品命名(如“甩甩杆/安敏裤”),避免通用标题。", + "首屏命名规则:标题必须是“名字+需求一句话”(如“甩甩杆,自拍不手抖”),不能只给一个名字。", "命名风格要求:大白话、易记、朗朗上口,未来可直接成为用户搜索词。", referenceImageNotes.length > 0 ? `参考图说明(按上传顺序):${referenceImageNotes.join(" | ")}` @@ -1560,11 +1665,12 @@ export async function POST(request: Request) { `3) 所有分镜风格统一、构图统一、文案排版风格统一。主标题/副标题/正文的字体家族、字重、字号层级、行距必须全套统一。\n` + `4) 每屏尺寸固定 790x1300 像素,输出提示词必须显式包含该尺寸约束。\n` + `5) 结构遵循“黄金五屏原则”:屏1-2核心卖点,屏3-4差异强化,屏5证据背书;更多屏作为延展。\n` + - `5.1) 首屏必须输出“The One核心卖点标题”(3-10字)+ 单句结果副标题,直接回答“为什么值得买”。\n` + + `5.1) 首屏主标题必须是“命名词+需求句”结构(6-20字,例如“甩甩杆,自拍不手抖”),并配一条结果副标题,直接回答“为什么值得买”。\n` + `5.2) 全套每一屏都必须服务同一核心卖点锚点,不允许出现与核心卖点无关的新主叙事。\n` + `5.3) 中文首屏标题优先使用“核心卖点命名词+品类后缀”结构(如:甩甩杆、安敏裤)。\n` + `5.4) 命名词必须大白话、好记、可口头传播,避免“智能/科技/方案/系统”等抽象词。\n` + `5.5) 后续至少2屏在副标题或正文复现该命名词,强化用户检索记忆。\n` + + `5.6) 首屏必须是封面海报构图:单一主体主视觉、强对比焦点、电影感光影、顶部标题安全区;禁止拼贴说明页。\n` + `6) 每屏都要有 FABE 闭环(B利益/F特征/A优势/E证据),且文案符合电商转化表达。\n` + `7) 文案用词需贴近用户语言,不写空泛形容词,不泄露系统流程。\n` + `7.1) 必须执行“卖点→用户任务(JTBD)→买点句”转译,买点句优先用“不用___也能___ / 在___场景也能___”句式。\n` + @@ -1573,7 +1679,7 @@ export async function POST(request: Request) { `8) 对每屏输出 useModel 与 modelReason:useModel=true 表示该屏必须融入固定模特图人物;false 表示该屏禁止出现人物。\n` + `9) 对每屏输出 screenType(A/B/C):A=技术公信,B=场景共鸣,C=细节佐证。\n` + `9.1) 前3屏默认节奏:屏1=场景痛点共鸣(B),屏2=买点总括,屏3=证据验证(对比/实测/口碑/认证)。\n` + - `10) 主标题必须是卖点语义,不得出现“第1屏/Screen 1”等编号标识,建议4-10字。\n` + + `10) 主标题必须是卖点语义,不得出现“第1屏/Screen 1”等编号标识;首屏6-20字(命名词+需求句),其余屏建议4-10字。\n` + `10.1) 主标题禁止使用规划节奏词:核心价值/场景痛点/方案拆解/细节证明/信任收口/卖点延展。\n` + `11) 在 globalStyle.typography 中写明明确字号与字重(示例:主标题64px/700,副标题34px/600,正文24px/500),并在每屏 layoutNote 里落实排版层级。\n` + `11.1) 副标题必须为单句结果句,禁止写成长段说明。\n` + @@ -1585,7 +1691,7 @@ export async function POST(request: Request) { ? `14) 已提供搭配产品图:必须新增且仅新增1屏“场景化产品搭配展示”,该屏设定 bundleShowcase=true,并在 bundleReason 说明搭配策略;其余屏设为 bundleShowcase=false。\n` : `14) 未提供搭配产品图:所有分镜均设置 bundleShowcase=false。\n`) + `输出格式:\n` + - `{"summary":"简述","globalStyle":{"designLanguage":"一句话风格方向","typography":{"fontFamily":"字体家族","title":"主标题规范","subtitle":"副标题规范","body":"正文规范"},"layout":{"grid":"栅格","safeArea":"安全区","rhythm":"留白节奏"},"palette":"配色规范"},"screens":[{"index":1,"screenType":"B","objective":"本屏目标","title":"3-8字标题","subtitle":"一句副标题","copy":"正文文案","layoutNote":"排版说明","useModel":false,"modelReason":"是否需要人物及原因","bundleShowcase":false,"bundleReason":"是否为搭配展示页及原因","fabe":{"b":"利益","f":"特征","a":"优势","e":"证据"},"visualPrompt":"用于生图的完整提示词,必须包含790x1300与统一字体排版"}]}`, + `{"summary":"简述","globalStyle":{"designLanguage":"一句话风格方向","typography":{"fontFamily":"字体家族","title":"主标题规范","subtitle":"副标题规范","body":"正文规范"},"layout":{"grid":"栅格","safeArea":"安全区","rhythm":"留白节奏"},"palette":"配色规范"},"screens":[{"index":1,"screenType":"B","objective":"本屏目标","title":"首屏示例:甩甩杆,自拍不手抖(6-20字)","subtitle":"一句副标题","copy":"正文文案","layoutNote":"排版说明","useModel":false,"modelReason":"是否需要人物及原因","bundleShowcase":false,"bundleReason":"是否为搭配展示页及原因","fabe":{"b":"利益","f":"特征","a":"优势","e":"证据"},"visualPrompt":"用于生图的完整提示词,必须包含790x1300与统一字体排版"}]}`, temperature: 0.25, maxTokens: 1800, } as any); diff --git a/app/api/canvas/detail-review/route.ts b/app/api/canvas/detail-review/route.ts index d756e2e72..a90fd54f9 100644 --- a/app/api/canvas/detail-review/route.ts +++ b/app/api/canvas/detail-review/route.ts @@ -90,6 +90,7 @@ function buildFallbackPrompt(params: { screenPrompt: string; layoutNote: string; language: "zh" | "en"; + isFirstScreen: boolean; }) { if (params.language === "en") { return [ @@ -99,6 +100,9 @@ function buildFallbackPrompt(params: { "Typography lock: title 64px/700, subtitle 34px/600, body 24px/500, keep the same hierarchy used in all other frames.", "Text safe-area lock: y=64~360, avoid text overlap with product hero.", params.layoutNote ? `Layout fix note: ${params.layoutNote}` : "", + params.isFirstScreen + ? "Screen 1 hero-cover lock: poster-like key visual, single dominant product focal point, clear top headline safe area, no collage modules." + : "", "Do not put 'Screen X' text in the title.", "Marketing conversion lock: pain point -> benefit -> trust proof path must be immediately understandable.", "Preserve product identity and style consistency with previous frames.", @@ -113,6 +117,9 @@ function buildFallbackPrompt(params: { "字体锁定:主标题64px/700,副标题34px/600,正文24px/500,全套层级与其它屏一致。", "文字安全区锁定:y=64~360,避免与产品主体重叠。", params.layoutNote ? `排版修正建议:${params.layoutNote}` : "", + params.isFirstScreen + ? "首屏封面锁定:必须是海报主视觉,单一主体强焦点,顶部标题区清晰,禁止拼贴卡片说明。" + : "", "主标题不得出现“第X屏”等编号词。", "营销转化锁定:用户能快速读懂“痛点-利益-证据”路径,避免只好看不转化。", "保持产品外观与整体风格连续一致。", @@ -160,6 +167,7 @@ export async function POST(request: Request) { screenPrompt, layoutNote, language, + isFirstScreen: Number(body.screen?.index || 0) === 1, }); if (!reviewerConfig) { @@ -216,6 +224,9 @@ export async function POST(request: Request) { `- Screen subtitle: ${String(body.screen?.subtitle || "").trim()}`, `- Screen copy: ${String(body.screen?.copy || "").trim()}`, `- Layout note: ${layoutNote}`, + Number(body.screen?.index || 0) === 1 + ? "- Screen 1 must keep poster hero quality: single dominant focal point, clear headline zone, no collage." + : "", screenPrompt ? `- Source prompt: ${screenPrompt}` : "", language === "en" ? "Return strict JSON: {\"pass\":boolean,\"score\":0-100,\"designDirectorScore\":0-100,\"marketingDirectorScore\":0-100,\"issues\":[\"...\"],\"layoutFixes\":[\"...\"],\"typographyFixes\":[\"...\"],\"conversionFixes\":[\"...\"],\"revisedPrompt\":\"...\"}" diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 1424b24ce..9d7d5bdb4 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -3833,6 +3833,9 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF screen?.screenType ? `分镜类型:${screen.screenType}(A=技术公信,B=场景共鸣,C=细节佐证)` : "", + index === 0 + ? "首屏封面规则:必须做成海报主视觉,单一主体强焦点,产品主体占画面55%-70%,顶部标题区清晰留白,禁止拼贴式说明图。" + : "", useModelForScene ? `人物规则:本屏涉及人物场景,固定使用提供的模特图同一人物(面部、发型、体型、服装风格保持一致)。` : "人物规则:本屏不要出现人物或人体部位,只展示产品与场景元素。", @@ -5298,7 +5301,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF !shouldShowImageLoading; const renderModelSelectContent = () => ( - +
{selectableModels.map((model) => ( {isSessionMenuOpen && ( -
+
聊天窗口记录
{chatSessions.length === 0 ? ( @@ -6910,7 +6913,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF align="start" side="top" sideOffset={10} - className="w-[156px] rounded-xl border border-[#d6d9df] bg-[#f4f5f7] p-1.5 shadow-[0_8px_18px_rgba(15,23,42,0.14)]" + className="w-[156px] rounded-xl border border-[#d6d9df] bg-[#f4f5f7]/90 p-1.5 shadow-[0_8px_18px_rgba(15,23,42,0.14)]" > handleSelectGenerationMode("agent")}