/* ===== Screens Part 4: Longform Agent ===== */ const MONTX_DEFAULT_SKILL = `# MONTX Digital Editorial Design System 你是顶级数字媒体画册设计师。你的任务是严格执行 **Avant-garde Bauhaus Grid(先锋建筑与包豪斯网格)** 风格排版,专用于静态长图图文设计。 ## 1. 核心视觉哲学 - **工业秩序感**:利用粗犷的几何分割替代诗意留白。 - **硬核对比**:大面积纯色块撞色(酒红 vs 骨白),严禁使用渐变、毛玻璃或阴影。 - **图文分离**:图片必须全通栏撑满,文字区块独立呈现,绝不允许在图片上覆盖文字。 ## 2. 品牌规格 (Style Tokens) - **画布限制**:最外层容器 \`width: 1080px\`,居中对齐。 - **色彩规范 (Strict)**: - 全局背景:\`#E1E0D8\` (骨白) - 强调色/模块色:\`#580018\` (酒红) - 文本色:\`#000000\` (黑) 于骨白底;\`#FFFFFF\` (白) 于酒红底。 - **字体规范**: - 英文:\`Helvetica\` - 中文:\`SourceHanSansCN-Bold\` (思源黑体加粗) - 字号:正文 \`34px - 38px\`,大标题 \`110px+\`,小标题 \`40px - 70px\`。 - **线条规范**:装饰线及边框统一使用酒红色,厚度为 \`12px\` 或 \`24px\`。 ## 3. 结构组件库 (Components) ### A. Header (页眉) - 顶部 \`24px\` 酒红实线封顶。 - 标题 \`MONTX V10\` 单行大写,使用 \`title-giant\` (110px) 样式。 - 下方紧跟 \`12px\` 装饰线及引言段落。 ### B. Image Block (全通栏图片) - 属性:\`w-full block\`,\`referrerpolicy="no-referrer"\`。 - 禁止:圆角、外边距、拼图。 ### C. Narrative Block (文本模块) - **水印背景**:使用极其巨大的实心数字(如 01, 02),颜色 \`#580018\`,透明度 \`10%\`。 - **排版逻辑**: - 章节标题:酒红底白字块,内边距 \`px-16 py-8\`。 - 正文段落:左侧 \`12px\` 酒红实心垂直线(border-l),字体样式为常规权重,严禁单字加粗。 ### D. Grid Content (网格化内容) - 采用 \`grid-cols-12\` 结构。 - 左侧(4-5列)通常为酒红纯色块,用于平衡视觉权重。 - 右侧(7-8列)为文字细分项,使用 \`border-t-[2px]\` 进行横向切割。 ### E. Footer (页脚) - 背景:\`#580018\`。 - 内容:单行大标题 + 官网链接。 ## 4. 交付约束 - **Single-File Mandate**:所有 HTML、Tailwind CSS 必须集成在一个文件内。 - **No Interaction**:移除所有 hover、动画、滚动效果。 - **Text Weight**:除标题外,所有段落文本强制执行 \`font-weight: normal\`,禁止任何形式的单字加粗。`; const LONGFORM_DEFAULT_STYLE_TEMPLATE_ID = 'montx-default'; const LONGFORM_CUSTOM_STYLE_TEMPLATE_ID = 'custom-upload'; const LONGFORM_DEFAULT_STYLE_TEMPLATE = { id: LONGFORM_DEFAULT_STYLE_TEMPLATE_ID, name: 'MONTX 默认', fileName: 'MONTX_DEFAULT_SKILL.md', markdown: MONTX_DEFAULT_SKILL, }; const LONGFORM_NARRATIVE_MODES = [ { id: 'brand_product', label: '品牌 + 产品', description: '先建立 MONTX 的判断,再用 P10/V10 作为证据落点。' }, { id: 'brand', label: '品牌向', description: '强调 MONTX 的世界观、价值主张和表达边界,避免过度产品广告化。' }, { id: 'product', label: '产品向', description: '围绕所选产品的能力、场景、证据和边界展开。' }, { id: 'brief_only', label: '仅 Brief', description: '优先执行当前 brief,不主动加入品牌或产品叙事。' }, ]; const getLongformNarrativeMode = (mode = 'brand_product') => LONGFORM_NARRATIVE_MODES.find(item => item.id === mode) || LONGFORM_NARRATIVE_MODES[0]; const MONTX_PRODUCT_PROFILES = [ { id: 'montx-p10', name: 'P10', aliases: ['MONTX P10', 'P10', 'Pickup 01', '未来先锋皮卡', '全域移动地空母舰'], positioning: 'MONTX P10 是概念阶段的未来先锋皮卡,以“全域移动地空母舰”承载地空双栖、全域适应与机能美学叙事。', audience: ['跨界多用途尝鲜型用户', '户外探索者', '科考/摄影/露营/指挥等复合场景用户'], tone: ['开拓', '机能', '先锋', '克制', '概念边界清晰'], requiredClaims: ['标注概念车叙事', '强调全域适应/全域智能/全域美学', '把载人飞行器等表达限定为概念愿景'], forbiddenClaims: ['量产承诺', '完全自动驾驶', '无人驾驶', '绝对安全', '已上市/可交付', '确定配置/价格/交付时间'], narrative: { positioning: 'P10 是征服地表与天空的“全域移动地空母舰”,将皮卡从单一装载工具升维为支持立体出行的未来装备。', coreCapabilities: ['地空双栖概念', '四轮分布式驱动电机', '智能底盘与底盘扭矩矢量控制', '车顶/车斗模块化', '战机灰机能涂装与雨林线'], userScenarios: ['越野穿越', '摄影/科考', '露营', '指挥车', '无人区探索', '空中侦察/补给概念'], proofPoints: ['地空一体视觉张力', '全域机能美学', '主动生态伴随', '从工具到平台的升维'], comparisonBoundaries: ['不做竞品压制', '不把概念车能力写成量产承诺', '不暗示自动驾驶替代驾驶员'], requiredMentions: ['概念车', '全域移动地空母舰', '全域适应', '全域智能', '全域美学'], forbiddenClaims: ['量产承诺', '完全自动驾驶', '无人驾驶', '绝对安全', '已上市/可交付', '确定配置/价格/交付时间'], }, source: 'gac-brand-assets-2026-04', }, { id: 'montx-v10', name: 'V10', aliases: ['MONTX V10', 'V10', '概念 VAN', '全域移动整备舱', '数字游民大本营'], positioning: 'MONTX V10 是概念阶段的全域移动整备舱,面向数字游民、户外探索与移动办公/旅居复合场景。', audience: ['数字游民', '户外探索者', '移动办公用户', '长周期旅居用户', '跨界多用途尝鲜型用户'], tone: ['理性浪漫', '工业秩序感', '克制', '温度感', '概念边界清晰'], requiredClaims: ['标注概念车叙事', '说明移动办公/旅居第三空间', '强调模块化空间与文明秩序感'], forbiddenClaims: ['量产承诺', '完全自动驾驶', '替代驾驶员', '绝对安全', '已上市/可交付', '确定配置/价格/交付时间'], narrative: { positioning: 'V10 是能在任意场景下切换工作、生活与旅行的“全域移动整备舱”,是数字游民的大本营和户外探险移动的家。', coreCapabilities: ['线控滑移方向盘概念', '智能双舱', '模块化座舱', '工作/社交/睡眠场景切换', '洗漱系统/模块化收纳/基础卫浴功能', '时间感知自动进化概念'], userScenarios: ['移动办公', '长周期旅居', '荒野驻留', '社交沙龙', '户外探险', '跨市场左右舵切换概念'], proofPoints: ['工业硬装与户外软装融合', '四开式尾门展开', '自循环基地', '隐形管家', '文明秩序与尊严'], comparisonBoundaries: ['不把自动切换、无感调节写成量产承诺', '不暗示无人驾驶或替代驾驶员', '不使用夸张竞品对比'], requiredMentions: ['概念车', '全域移动整备舱', '数字游民', '移动办公/旅居第三空间', '工业秩序感'], forbiddenClaims: ['量产承诺', '完全自动驾驶', '替代驾驶员', '绝对安全', '已上市/可交付', '确定配置/价格/交付时间'], }, source: 'gac-brand-assets-2026-04', }, ]; const LONGFORM_BRANDS = [ { id: 'montx', name: 'MONTX', activeSkillId: 'montx-digital-editorial', products: MONTX_PRODUCT_PROFILES, narrative: { positioning: 'MONTX / 领越是广汽领程面向全球市场的“全域跨界新物种”,服务希望一部车满足商业、生活与探索多重需求的用户。', worldview: '移动出行世界中,场景、功能与用途的边界正在融合和重塑;MONTX 以跨界开拓精神,让用户在事业与生活之间获得更自由的全域场景体验。', valueProposition: '以一车多能、自由切换、全域适应和共创生态,帮助用户拓展事业与生活的边界。', voice: ['开拓', '全域', '机能', '先锋', '共创', '克制', '概念边界清晰'], requiredThemes: ['全域跨界新物种', 'New Breed. No Boundaries.', '一车多能', '自由切换', '全域智能', '全域适应', '全域美学', '共创更自由美好的事业与生活'], forbiddenAngles: ['把概念车写成量产承诺', '完全自动驾驶承诺', '无人驾驶暗示', '绝对安全', '过度玩梗', '轻浮自嘲', '竞品碾压', '财富自由夸大承诺'], proofTypes: ['品牌核心讯息', '产品概念车叙事', '真实用户场景', '共创机制', '视觉规范', '领导讲话/新闻稿'], }, constraints: [ { id: 'concept-boundary', label: '概念车能力必须标注边界', severity: 'required', source: 'gac-brand-assets-2026-04', description: 'P10/V10 的载人飞行器、线控滑移方向盘、时间感知自动进化等表达必须标注为概念车/愿景叙事,不能写成量产承诺。', defaultLocked: true, forbiddenTerms: ['已量产', '即将交付', '确定上市', '标配', '价格'] }, { id: 'no-autonomous-overclaim', label: '禁止自动驾驶过度承诺', severity: 'required', source: 'gac-brand-assets-2026-04', description: '涉及自动识别、自动接管、主动调整、无人区侦察等表达时,不得暗示完全自动驾驶、无人驾驶、替代驾驶员或绝对安全。', defaultLocked: true, forbiddenTerms: ['完全自动驾驶', '无人驾驶', '替代驾驶员', '绝对安全', '解放双手'] }, { id: 'niuma-context-only', label: '“牛马”仅限共创项目语境', severity: 'required', source: 'gac-brand-assets-2026-04', description: '“牛马”只允许出现在“牛马野逍遥”共创计划相关语境,不进入品牌常规口吻,不替用户自嘲。', defaultLocked: true, forbiddenTerms: ['牛马'] }, { id: 'wealth-freedom-caution', label: '慎用财富自由', severity: 'required', source: 'gac-brand-assets-2026-04', description: '“财富自由”只能作为讲话稿历史素材引用,常规生成中需避免金融收益、成本收益或商业成功的夸大承诺。', defaultLocked: true, forbiddenTerms: ['财富自由'] }, { id: 'no-gradient', label: '禁止渐变 / 毛玻璃 / 阴影', severity: 'required', source: 'brand-policy', description: '保持硬核对比和纯色块,不使用柔化视觉效果。', defaultLocked: true }, { id: 'no-image-text-overlay', label: '禁止图片压字', severity: 'required', source: 'brand-policy', description: '图片必须独立呈现,文字区块与图片分离。', defaultLocked: true }, { id: 'full-bleed-image', label: '图片必须全通栏', severity: 'required', source: 'brand-policy', description: '图片模块以 1080px 宽度铺满,不做拼图和局部圆角。', defaultLocked: true }, { id: 'no-rounded-collage', label: '禁止圆角与拼图', severity: 'required', source: 'brand-policy', description: 'Image Block 必须 w-full block,不加圆角、边距、拼接。', defaultLocked: true }, { id: 'normal-body-weight', label: '正文禁止单字加粗', severity: 'required', source: 'brand-policy', description: '除标题外,段落文本强制 normal,不做局部加粗强调。', defaultLocked: true }, { id: 'canvas-1080', label: '画布宽度固定 1080px', severity: 'required', source: 'brand-policy', description: 'HTML 预览与 PNG 导出目标均为 1080px 宽。', defaultLocked: true }, { id: 'single-file-html', label: '单文件 HTML', severity: 'required', source: 'brand-policy', description: '所有样式集成在导出的 HTML 中,便于后续转图。', defaultLocked: true }, { id: 'no-interaction', label: '无交互动画', severity: 'required', source: 'brand-policy', description: '长图图文为静态交付,移除 hover、动画和滚动效果。', defaultLocked: true }, ], }, { id: 'aurelia', name: 'Aurelia Motors', activeSkillId: 'aurelia-editorial-reserved', products: [ { id: 'aurelia-ax7', name: 'AX-7', aliases: ['Aurelia AX-7', 'AX-7'], positioning: 'Aurelia Motors 旗舰纯电轿跑 SUV 首发产品,承载静奢、东方家庭和高端智能出行叙事。', audience: ['中高端家庭用户', '纯电 SUV 关注者', '品牌首发关注者'], tone: ['静奢', '克制', '高级', '家庭场景'], requiredClaims: ['旗舰首发', '静奢气质', '家庭第二台车场景'], forbiddenClaims: ['促销秒杀', '赛道碾压', '暴力加速'], narrative: { positioning: 'AX-7 是 Aurelia Motors 的旗舰首发产品,承担品牌进入中高端纯电轿跑 SUV 市场的叙事任务。', coreCapabilities: ['静奢设计', '家庭场景舒适性', '高端纯电体验'], userScenarios: ['家庭第二台车', '城市周末出行', '品牌首发认知建立'], proofPoints: ['设计语言', '空间与舒适性', '纯电体验', '品牌调性'], comparisonBoundaries: ['不以赛道和暴力加速作为主叙事', '不做低价促销化表达'], requiredMentions: ['AX-7 旗舰首发', '静奢', '家庭场景'], forbiddenClaims: ['促销秒杀', '碾压竞品', '暴力加速'], }, source: 'workspace-seed', }, { id: 'aurelia-ax5', name: 'AX-5', aliases: ['Aurelia AX-5', 'AX-5'], positioning: 'Aurelia Motors 城市家庭产品线。', audience: ['城市家庭用户'], tone: ['克制', '实用', '温和'], requiredClaims: ['城市家庭', '日常使用'], forbiddenClaims: ['赛道化表达'], narrative: { positioning: 'AX-5 面向城市家庭日常使用。' }, source: 'workspace-seed', }, { id: 'aurelia-concept-s', name: 'Concept-S', aliases: ['Aurelia Concept-S', 'Concept-S'], positioning: 'Aurelia Motors 概念产品线,用于未来设计方向表达。', audience: ['品牌关注者', '设计媒体'], tone: ['前瞻', '克制', '设计感'], requiredClaims: ['未来设计方向'], forbiddenClaims: ['量产承诺'], narrative: { positioning: 'Concept-S 用于表达 Aurelia Motors 的未来设计方向。' }, source: 'workspace-seed', }, ], narrative: { positioning: 'Aurelia Motors 是中高端纯电出行品牌,强调静奢、克制和东方家庭场景。', worldview: '电动豪华不应依赖喧闹性能叙事,而应通过克制设计、家庭场景和长期信任建立心智。', valueProposition: '以静奢设计和可靠体验服务高品质家庭出行。', voice: ['克制', '静奢', '高级', '温和'], requiredThemes: ['静奢', '东方家庭', '纯电体验', '长期信任'], forbiddenAngles: ['促销化表达', '赛道暴力叙事', '竞品碾压'], proofTypes: ['设计语言', '家庭场景', '用户洞察', '产品体验证据'], }, constraints: [ { id: 'aurelia-tone', label: '克制 / 静奢 / 非促销', severity: 'required', source: 'brand-policy', description: '避免硬广、秒杀、碾压等转化型表达。', defaultLocked: true }, { id: 'aurelia-no-racing', label: '回避赛车对比', severity: 'required', source: 'brand-policy', description: '不以赛道、暴力加速、竞品压制作为主叙事。', defaultLocked: true }, ], }, ]; const LONGFORM_STEPS = ['需求输入', '内容推演', '图片分配', 'HTML 预览', 'PNG 导出']; const LONGFORM_BLOCK_TYPES = [ { id: 'hero', label: '开场 Hero', type: 'Header', layoutModule: 'Header', description: '主标题、导语和首张主视觉,负责建立第一屏判断。', required: true }, { id: 'narrative', label: '叙事段落', type: 'Narrative', layoutModule: 'Narrative Block', description: '观点推进、用户问题、背景说明或论证正文。', required: true }, { id: 'tech_breakdown', label: '技术拆解', type: 'Narrative', layoutModule: 'Narrative Block', description: '把复杂机制拆成可理解的步骤、原理和边界。', required: false }, { id: 'image_proof', label: '图片证据', type: 'Image', layoutModule: 'Image Block', description: '用于承载真实场景、产品图、道路图或视觉证据组。', required: false }, { id: 'metrics', label: '指标信息', type: 'Grid', layoutModule: 'Grid Content', description: '适合参数、能力清单、场景覆盖和高密度信息。', required: false }, { id: 'compare', label: '对比解释', type: 'Grid', layoutModule: 'Grid Content', description: '适合能力边界、前后对比、同类方案差异说明。', required: false }, { id: 'timeline', label: '流程时间线', type: 'Narrative', layoutModule: 'Narrative Block', description: '适合流程、决策链路、阶段递进和使用步骤。', required: false }, { id: 'cta', label: '收束 CTA', type: 'Footer', layoutModule: 'Footer', description: '结尾行动、品牌收束、官网或预约入口。', required: true }, ]; const getLongformBlockType = (section = {}) => { if (typeof section === 'string') return LONGFORM_BLOCK_TYPES.find(block => block.id === section) || LONGFORM_BLOCK_TYPES[1]; if (section.blockType) return getLongformBlockType(section.blockType); const haystack = `${section.type || ''} ${section.layoutModule || ''} ${section.title || ''}`.toLowerCase(); if (/header|hero|开场|标题/.test(haystack)) return LONGFORM_BLOCK_TYPES[0]; if (/footer|cta|官网|预约|收束/.test(haystack)) return LONGFORM_BLOCK_TYPES.find(block => block.id === 'cta'); if (/image|图片|证据|场景/.test(haystack)) return LONGFORM_BLOCK_TYPES.find(block => block.id === 'image_proof'); if (/grid|指标|参数|覆盖|清单/.test(haystack)) return LONGFORM_BLOCK_TYPES.find(block => block.id === 'metrics'); if (/对比|差异|竞品/.test(haystack)) return LONGFORM_BLOCK_TYPES.find(block => block.id === 'compare'); if (/流程|路径|时间线|阶段|链路/.test(haystack)) return LONGFORM_BLOCK_TYPES.find(block => block.id === 'timeline'); if (/技术|原理|算法|传感器|决策/.test(haystack)) return LONGFORM_BLOCK_TYPES.find(block => block.id === 'tech_breakdown'); return LONGFORM_BLOCK_TYPES[1]; }; const normalizeLongformSection = (section = {}, index = 0) => { const block = getLongformBlockType(section); return { id: section.id || `${block.type.toLowerCase()}-${Date.now()}-${index}`, type: block.type, blockType: block.id, title: section.title || LONGFORM_DEFAULT_SECTIONS[index % LONGFORM_DEFAULT_SECTIONS.length]?.title || block.label, body: section.body || '', imageIds: Array.isArray(section.imageIds) ? section.imageIds : [], layoutModule: block.layoutModule, imageHint: section.imageHint || '', }; }; const LONGFORM_DEFAULT_SECTIONS = [ { id: 'hero', type: 'Header', blockType: 'hero', title: 'MONTX V10', body: '一篇面向微信公众号的长图文,先建立观点,再用强秩序视觉把阅读节奏压实。', imageIds: [], layoutModule: 'Header', imageHint: '可放置一张建立主题气质的全通栏主视觉,避免文字压图。' }, { id: 's1', type: 'Narrative', blockType: 'narrative', title: '为什么现在需要一篇长文', body: '用户不是缺少信息,而是缺少一个被组织过的判断。长图文的任务,是把分散需求压缩成可阅读、可转发、可记忆的结构。', imageIds: [], layoutModule: 'Narrative Block', imageHint: '选择能证明问题现场或用户情境的图片,作为段落后的全通栏证据。' }, { id: 's2', type: 'Image', blockType: 'image_proof', title: '场景证据', body: '图片用于承载情绪和现实感,必须全通栏展示,不在图上叠字。', imageIds: [], layoutModule: 'Image Block', imageHint: '优先放最有信息量的实拍图,可连续添加多张形成证据组。' }, { id: 's3', type: 'Grid', blockType: 'metrics', title: '内容推演框架', body: '目标读者 / 核心论点 / 证据链 / CTA 被拆成网格化模块,便于后续转成长图。', imageIds: [], layoutModule: 'Grid Content', imageHint: '适合补充细节、对比、局部特写或流程节点图片。' }, { id: 'footer', type: 'Footer', blockType: 'cta', title: '前往官网', body: 'montx.example.com', imageIds: [], layoutModule: 'Footer', imageHint: '通常不配图;如需品牌收束,可放产品或品牌环境图。' }, ]; const LONGFORM_MOCK_TASKS = [ { id: 'lf-ax-001', brandId: 'montx', productId: 'montx-v10', narrativeMode: 'brand_product', title: 'MONTX V10 旅行编辑长图', prompt: '为微信公众号设计一篇面向高净值旅行用户的长图文,主题是大溪地海岛资产与现代生活方式。', status: 'HTML 已生成', activeStyleTemplateId: LONGFORM_DEFAULT_STYLE_TEMPLATE_ID, skillMarkdown: MONTX_DEFAULT_SKILL, images: [], sections: LONGFORM_DEFAULT_SECTIONS, html: '', messages: [ { role: 'assistant', text: '把这篇公众号长图文的目标、读者、语气和必须覆盖的观点发给我。我会先推演结构,再把它拆成可排版的 section。' }, { role: 'user', text: '为微信公众号设计一篇面向高净值旅行用户的长图文,主题是大溪地海岛资产与现代生活方式。' }, ], overrides: [], qa: { score: 92, issues: ['2 个图片位待上传'], exportReady: true }, updatedAt: '2026-04-27 09:40', }, { id: 'lf-ax-002', brandId: 'montx', productId: 'montx-v10', narrativeMode: 'brand', title: '城市更新观点稿', prompt: '从设计媒体视角写一篇观点长文,解释为什么工业秩序感正在回到数字出版。', status: '待上传图片', activeStyleTemplateId: LONGFORM_DEFAULT_STYLE_TEMPLATE_ID, skillMarkdown: MONTX_DEFAULT_SKILL, images: [], sections: LONGFORM_DEFAULT_SECTIONS.map((s, i) => ({ ...s, id: `${s.id}-2`, title: i === 0 ? 'GRID IS BACK' : s.title })), html: '', messages: [ { role: 'assistant', text: '请给我这次长图文的目标和读者,我会推演文章结构。' }, { role: 'user', text: '从设计媒体视角写一篇观点长文,解释为什么工业秩序感正在回到数字出版。' }, ], overrides: [{ constraintId: 'full-bleed-image', reason: '客户要求保留一张证件式人物肖像,暂不全通栏。', createdAt: '2026-04-27 10:12' }], qa: { score: 76, issues: ['存在 1 项解除约束'], exportReady: true }, updatedAt: '2026-04-27 10:12', }, ]; const longformId = (prefix = 'lf') => `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2, 7)}`; const MONTX_EXPORT_CSS = ` .montx-canvas{width:1080px;margin:0 auto;background:var(--lf-bg,#E1E0D8);color:var(--lf-text,#000);font-family:var(--lf-font,Helvetica,Arial,"Source Han Sans CN","Noto Sans SC",sans-serif);overflow:hidden;} .montx-topline{height:24px;background:var(--lf-accent,#580018);} .montx-giant{padding:58px 64px 24px;font-size:118px;line-height:.9;font-weight:900;letter-spacing:-.06em;text-transform:uppercase;} .montx-rule{height:12px;background:var(--lf-accent,#580018);margin:0 64px 36px;} .montx-lead{padding:0 64px 72px;font-size:38px;line-height:1.35;font-weight:400;} .montx-image{display:block;width:100%;border:0;border-radius:0;margin:0;} .montx-narrative{position:relative;min-height:360px;padding:88px 64px 92px;} .montx-watermark{position:absolute;right:48px;top:18px;font-size:220px;line-height:1;color:var(--lf-watermark,rgba(88,0,24,.1));font-weight:900;} .montx-section-title{display:inline-block;background:var(--lf-accent,#580018);color:var(--lf-inverse,#fff);font-size:54px;line-height:1;font-weight:900;padding:18px 28px;margin-bottom:36px;} .montx-body{position:relative;z-index:1;border-left:12px solid var(--lf-accent,#580018);padding-left:34px;font-size:36px;line-height:1.55;font-weight:400;} .montx-grid{display:grid;grid-template-columns:4fr 8fr;border-top:24px solid var(--lf-accent,#580018);border-bottom:24px solid var(--lf-accent,#580018);} .montx-grid-left{background:var(--lf-accent,#580018);color:var(--lf-inverse,#fff);font-size:64px;line-height:1.05;font-weight:900;padding:54px 44px;} .montx-grid-right{padding:50px 58px;font-size:34px;line-height:1.5;font-weight:400;} .montx-footer{background:var(--lf-accent,#580018);color:var(--lf-inverse,#fff);padding:60px 64px 72px;} .montx-footer h2{margin:0 0 18px;font-size:86px;line-height:.95;font-weight:900;letter-spacing:-.04em;} .montx-footer p{margin:0;font-size:34px;font-weight:400;} `; const escapeHtml = (value = '') => String(value) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); const inferLongformConstraintCategory = (constraint = {}) => { const text = `${constraint.label || ''} ${constraint.description || ''}`.toLowerCase(); if (/图片|image|圆角|拼图|压字|全通栏/.test(text)) return 'image'; if (/字号|画布|宽度|html|动画|交互|渐变|阴影|毛玻璃|layout|style/.test(text)) return 'style'; if (/自动驾驶|解放双手|承诺|竞品|促销|话术|claim/.test(text)) return 'claim'; return 'brand'; }; const normalizeLongformConstraint = (constraint, index = 0) => ({ id: constraint.id || longformId('guard'), label: constraint.label || `品牌护栏 ${index + 1}`, description: constraint.description || '', severity: constraint.severity || 'required', category: constraint.category || inferLongformConstraintCategory(constraint), scope: constraint.scope || ['generation', 'preview', 'export', 'qa'], defaultLocked: constraint.defaultLocked !== false, enabled: constraint.enabled !== false, forbiddenTerms: constraint.forbiddenTerms || [], requiredTerms: constraint.requiredTerms || [], rules: constraint.rules || {}, examples: constraint.examples || { bad: [], good: [] }, source: constraint.source || 'brand-policy', createdAt: constraint.createdAt || 'seed', updatedAt: constraint.updatedAt || constraint.createdAt || 'seed', }); const longformListFromValue = (value = '') => Array.isArray(value) ? value : String(value || '').split(/[;;,,、\n]/).map(item => item.trim()).filter(Boolean); const normalizeBrandNarrative = (narrative = {}) => ({ positioning: narrative.positioning || '', worldview: narrative.worldview || '', valueProposition: narrative.valueProposition || '', voice: longformListFromValue(narrative.voice), requiredThemes: longformListFromValue(narrative.requiredThemes), forbiddenAngles: longformListFromValue(narrative.forbiddenAngles), proofTypes: longformListFromValue(narrative.proofTypes), }); const normalizeProductNarrative = (narrative = {}, product = {}) => ({ positioning: narrative.positioning || product.positioning || '', coreCapabilities: longformListFromValue(narrative.coreCapabilities), userScenarios: longformListFromValue(narrative.userScenarios), proofPoints: longformListFromValue(narrative.proofPoints), comparisonBoundaries: longformListFromValue(narrative.comparisonBoundaries), requiredMentions: longformListFromValue(narrative.requiredMentions || product.requiredClaims), forbiddenClaims: longformListFromValue(narrative.forbiddenClaims || product.forbiddenClaims), }); const normalizeLongformProduct = (product = {}, index = 0) => ({ id: product.id || longformId('product'), name: product.name || `Product ${index + 1}`, aliases: longformListFromValue(product.aliases), positioning: product.positioning || '', audience: longformListFromValue(product.audience), tone: longformListFromValue(product.tone), requiredClaims: longformListFromValue(product.requiredClaims), forbiddenClaims: longformListFromValue(product.forbiddenClaims), narrative: normalizeProductNarrative(product.narrative, product), source: product.source || 'brand-product-profile', updatedAt: product.updatedAt || product.createdAt || 'seed', }); const normalizeLongformBrandProfiles = (brands = LONGFORM_BRANDS) => brands.map(brand => ({ ...brand, narrative: normalizeBrandNarrative(brand.narrative), products: (Array.isArray(brand.products) ? brand.products : brand.id === 'montx' ? MONTX_PRODUCT_PROFILES : []).map(normalizeLongformProduct), constraints: (brand.constraints || []).map(normalizeLongformConstraint), })); const normalizeSharedBrandProfiles = (brands) => { const source = brands || window.BrandMemoryStore?.DEFAULT_BRAND_PROFILES || LONGFORM_BRANDS; return window.BrandMemoryStore?.normalizeBrandProfiles ? window.BrandMemoryStore.normalizeBrandProfiles(source) : normalizeLongformBrandProfiles(source); }; const getLongformBrand = (brandId, brands) => { const source = brands || window.BrandMemoryStore?.DEFAULT_BRAND_PROFILES || LONGFORM_BRANDS; return window.BrandMemoryStore?.getBrandProfile ? window.BrandMemoryStore.getBrandProfile(brandId, source) : source.find(b => b.id === brandId) || source[0] || LONGFORM_BRANDS[0]; }; const getLongformProduct = (brand, productId) => { if (window.BrandMemoryStore?.getProductProfile) return window.BrandMemoryStore.getProductProfile(brand, productId); const products = brand?.products || []; return products.find(product => product.id === productId) || products[0] || null; }; const inferLongformProductId = (task = {}, brand) => { const text = `${task.productId || ''} ${task.title || ''} ${task.prompt || ''} ${(task.sections || []).map(section => section.title).join(' ')}`.toLowerCase(); return (brand?.products || []).find(product => { const names = [product.name, ...(product.aliases || [])].filter(Boolean).map(item => item.toLowerCase()); return names.some(name => text.includes(name)); })?.id || ''; }; const normalizeLongformTask = (task, brands = LONGFORM_BRANDS) => { const brand = getLongformBrand(task.brandId, brands); const inferredProductId = inferLongformProductId(task, brand); const product = getLongformProduct(brand, task.productId || inferredProductId); return { ...task, images: Array.isArray(task.images) ? task.images : [], overrides: Array.isArray(task.overrides) ? task.overrides : [], messages: Array.isArray(task.messages) ? task.messages : [], productId: task.productId || product?.id || '', narrativeMode: task.narrativeMode || 'brand_product', sections: (task.sections || LONGFORM_DEFAULT_SECTIONS).map(normalizeLongformSection), }; }; const parseSkillMarkdown = (markdown = '') => { const title = markdown.match(/^#\s+(.+)$/m)?.[1]?.trim() || 'Untitled Skill'; const hex = Array.from(new Set(markdown.match(/#[0-9A-Fa-f]{6}/g) || [])); const width = markdown.match(/width:\s*(\d+)px/)?.[1] || markdown.match(/画布限制.*?(\d+)px/)?.[1] || '1080'; const fontLines = (markdown.match(/(?:Helvetica|SourceHanSansCN|思源黑体|字体规范|font-weight)/g) || []).slice(0, 6); const components = Array.from(markdown.matchAll(/^###\s+(.+)$/gm)).map(m => m[1].trim()); const bans = Array.from(markdown.matchAll(/(?:禁止|严禁|移除)([^。\n]+)/g)).map(m => m[0].trim()).slice(0, 8); const delivery = Array.from(markdown.matchAll(/\*\*([^*]+)\*\*:([^。\n]+)/g)).map(m => `${m[1]}:${m[2]}`).filter(s => /Mandate|Interaction|Weight|交付|Text/.test(s)); return { title, hex, width, fontLines, components, bans, delivery, raw: markdown }; }; const LONGFORM_THEME_PRESETS = [ { key: /Neo Pop|新波普/i, bg: '#F7EA2E', accent: '#F92672', text: '#101010', inverse: '#FFFFFF', font: 'Impact, Helvetica, Arial, "Noto Sans SC", sans-serif' }, { key: /Nordic|北欧/i, bg: '#F4F0E8', accent: '#8FA99A', text: '#22302A', inverse: '#FFFFFF', font: 'Helvetica, Arial, "Noto Sans SC", sans-serif' }, { key: /Vogue|高奢/i, bg: '#F7F2EA', accent: '#1A1A1A', text: '#17120E', inverse: '#F7F2EA', font: 'Georgia, "Times New Roman", "Noto Serif SC", serif' }, { key: /Tech-Nomad|科技游牧/i, bg: '#071A1F', accent: '#00E0C7', text: '#EAFBF8', inverse: '#071A1F', font: 'Helvetica, Arial, "Noto Sans SC", sans-serif' }, { key: /Clean Data|清洁数据/i, bg: '#F4F7F8', accent: '#126E82', text: '#102027', inverse: '#FFFFFF', font: 'Helvetica, Arial, "Noto Sans SC", sans-serif' }, { key: /Cinematic|电影剧照/i, bg: '#111111', accent: '#B68D40', text: '#F2E8D8', inverse: '#111111', font: 'Georgia, "Times New Roman", "Noto Serif SC", serif' }, { key: /Human Documentary|人文纪实/i, bg: '#EFE2D2', accent: '#9A3A2D', text: '#211A16', inverse: '#FFF8EC', font: 'Georgia, "Noto Serif SC", serif' }, { key: /Archive Academia|档案学术/i, bg: '#ECE6D8', accent: '#2B3A67', text: '#1D2230', inverse: '#F7F1E5', font: 'Georgia, "Noto Serif SC", serif' }, { key: /Organic Nature|自然有机/i, bg: '#F1EAD9', accent: '#587A46', text: '#263322', inverse: '#FFF8EA', font: 'Helvetica, Arial, "Noto Sans SC", sans-serif' }, { key: /Bauhaus|包豪斯|MONTX/i, bg: '#E1E0D8', accent: '#580018', text: '#000000', inverse: '#FFFFFF', font: 'Helvetica, Arial, "Source Han Sans CN", "Noto Sans SC", sans-serif' }, ]; const LONGFORM_LAYOUT_PRESETS = [ { id: 'pop', className: 'lf-layout-pop', label: 'Neo Pop Poster', key: /Neo Pop|新波普/i }, { id: 'nordic', className: 'lf-layout-nordic', label: 'Nordic Minimal', key: /Nordic|北欧/i }, { id: 'vogue', className: 'lf-layout-vogue', label: 'Vogue Editorial', key: /Vogue|高奢/i }, { id: 'tech', className: 'lf-layout-tech', label: 'Tech Nomad', key: /Tech-Nomad|科技游牧/i }, { id: 'data', className: 'lf-layout-data', label: 'Clean Data Report', key: /Clean Data|清洁数据/i }, { id: 'cinematic', className: 'lf-layout-cinematic', label: 'Cinematic Storyboard', key: /Cinematic|电影剧照/i }, { id: 'documentary', className: 'lf-layout-documentary', label: 'Human Documentary', key: /Human Documentary|人文纪实/i }, { id: 'archive', className: 'lf-layout-archive', label: 'Archive Academia', key: /Archive Academia|档案学术/i }, { id: 'organic', className: 'lf-layout-organic', label: 'Organic Nature', key: /Organic Nature|自然有机/i }, { id: 'bauhaus', className: 'lf-layout-bauhaus', label: 'Bauhaus Grid', key: /Bauhaus|包豪斯|MONTX/i }, ]; const LONGFORM_FONT_SIZE_PRESETS = { 'lf-layout-bauhaus': { heroTitlePx: 118, leadPx: 38, sectionTitlePx: 54, bodyPx: 36, gridLeftPx: 64, gridRightPx: 34, footerTitlePx: 86, footerBodyPx: 34 }, 'lf-layout-nordic': { heroTitlePx: 74, leadPx: 30, sectionTitlePx: 38, bodyPx: 30, gridLeftPx: 38, gridRightPx: 29, footerTitlePx: 48, footerBodyPx: 34 }, 'lf-layout-pop': { heroTitlePx: 126, leadPx: 34, sectionTitlePx: 48, bodyPx: 31, gridLeftPx: 58, gridRightPx: 30, footerTitlePx: 86, footerBodyPx: 34 }, 'lf-layout-vogue': { heroTitlePx: 92, leadPx: 28, sectionTitlePx: 46, bodyPx: 29, gridLeftPx: 58, gridRightPx: 28, footerTitlePx: 86, footerBodyPx: 34 }, 'lf-layout-tech': { heroTitlePx: 96, leadPx: 30, sectionTitlePx: 34, bodyPx: 28, gridLeftPx: 42, gridRightPx: 28, footerTitlePx: 86, footerBodyPx: 34 }, 'lf-layout-data': { heroTitlePx: 76, leadPx: 28, sectionTitlePx: 32, bodyPx: 27, gridLeftPx: 38, gridRightPx: 27, footerTitlePx: 86, footerBodyPx: 34 }, 'lf-layout-cinematic': { heroTitlePx: 88, leadPx: 28, sectionTitlePx: 32, bodyPx: 29, gridLeftPx: 46, gridRightPx: 28, footerTitlePx: 86, footerBodyPx: 34 }, 'lf-layout-documentary': { heroTitlePx: 82, leadPx: 30, sectionTitlePx: 40, bodyPx: 30, gridLeftPx: 44, gridRightPx: 29, footerTitlePx: 86, footerBodyPx: 34 }, 'lf-layout-archive': { heroTitlePx: 70, leadPx: 28, sectionTitlePx: 36, bodyPx: 28, gridLeftPx: 34, gridRightPx: 27, footerTitlePx: 86, footerBodyPx: 34 }, 'lf-layout-organic': { heroTitlePx: 78, leadPx: 30, sectionTitlePx: 38, bodyPx: 30, gridLeftPx: 44, gridRightPx: 29, footerTitlePx: 86, footerBodyPx: 34 }, }; const LONGFORM_LAYOUT_CSS = ` .longform-render .montx-giant,.longform-render .montx-section-title,.longform-render .montx-footer h2{letter-spacing:0;} .longform-render.lf-text-center .montx-giant,.longform-render.lf-text-center .montx-lead,.longform-render.lf-text-center .montx-section-title,.longform-render.lf-text-center .montx-body,.longform-render.lf-text-center .montx-grid-left,.longform-render.lf-text-center .montx-grid-right,.longform-render.lf-text-center .montx-footer{text-align:center;} .longform-render.lf-text-center .montx-section-title,.longform-render.lf-text-center .montx-body,.longform-render.lf-text-center .montx-lead{margin-left:auto;margin-right:auto;} .longform-render.lf-text-center .montx-body,.longform-render.lf-text-center .montx-grid-right{border-left:0;padding-left:0;} .longform-render.lf-layout-nordic .montx-topline{height:0;} .longform-render.lf-layout-nordic .montx-giant{padding:96px 110px 18px;font-size:74px;line-height:1.08;font-weight:400;text-transform:none;} .longform-render.lf-layout-nordic .montx-rule{height:1px;margin:0 110px 42px;background:color-mix(in oklab,var(--lf-accent) 45%,var(--lf-bg));} .longform-render.lf-layout-nordic .montx-lead{padding:0 110px 96px;font-size:30px;line-height:1.75;} .longform-render.lf-layout-nordic .montx-narrative{min-height:0;padding:96px 120px;} .longform-render.lf-layout-nordic .montx-watermark{display:none;} .longform-render.lf-layout-nordic .montx-section-title{display:block;background:transparent;color:var(--lf-text);padding:0;margin:0 0 28px;font-size:38px;font-weight:400;border-bottom:1px solid color-mix(in oklab,var(--lf-accent) 35%,var(--lf-bg));padding-bottom:22px;} .longform-render.lf-layout-nordic .montx-body{border-left:0;padding-left:0;font-size:30px;line-height:1.85;} .longform-render.lf-layout-nordic .montx-grid{display:block;border:0;padding:92px 110px;background:color-mix(in oklab,var(--lf-accent) 8%,var(--lf-bg));} .longform-render.lf-layout-nordic .montx-grid-left{background:transparent;color:var(--lf-text);font-size:38px;padding:0 0 28px;font-weight:400;} .longform-render.lf-layout-nordic .montx-grid-right{padding:0;font-size:29px;line-height:1.8;} .longform-render.lf-layout-nordic .montx-footer{padding:92px 110px;background:var(--lf-bg);color:var(--lf-text);border-top:1px solid color-mix(in oklab,var(--lf-accent) 35%,var(--lf-bg));} .longform-render.lf-layout-nordic .montx-footer h2{font-size:48px;font-weight:400;} .longform-render.lf-layout-pop .montx-topline{height:42px;background:repeating-linear-gradient(90deg,var(--lf-accent) 0 90px,var(--lf-text) 90px 108px);} .longform-render.lf-layout-pop .montx-giant{padding:56px 48px 22px;font-size:126px;line-height:.88;text-transform:uppercase;} .longform-render.lf-layout-pop .montx-rule{height:0;margin:0 48px 24px;border-top:16px solid var(--lf-text);border-bottom:16px solid var(--lf-accent);} .longform-render.lf-layout-pop .montx-lead{padding:28px 48px 72px;font-size:34px;line-height:1.35;background:var(--lf-text);color:var(--lf-inverse);} .longform-render.lf-layout-pop .montx-narrative{padding:72px 54px;min-height:340px;border-top:10px solid var(--lf-text);} .longform-render.lf-layout-pop .montx-watermark{right:34px;top:18px;font-size:180px;color:color-mix(in oklab,var(--lf-accent) 30%,transparent);} .longform-render.lf-layout-pop .montx-section-title{font-size:48px;border:8px solid var(--lf-text);box-shadow:12px 12px 0 var(--lf-text);transform:rotate(-1deg);margin-bottom:44px;} .longform-render.lf-layout-pop .montx-body{border-left:0;padding:34px;font-size:31px;line-height:1.5;background:color-mix(in oklab,var(--lf-inverse) 88%,var(--lf-bg));outline:6px solid var(--lf-text);} .longform-render.lf-layout-pop .montx-grid{grid-template-columns:5fr 7fr;border:12px solid var(--lf-text);} .longform-render.lf-layout-pop .montx-grid-left{font-size:58px;padding:48px 36px;text-transform:uppercase;} .longform-render.lf-layout-pop .montx-grid-right{font-size:30px;padding:48px 42px;background:var(--lf-bg);} .longform-render.lf-layout-pop .montx-footer{border-top:18px solid var(--lf-text);} .longform-render.lf-layout-vogue .montx-topline{height:0;} .longform-render.lf-layout-vogue .montx-giant{padding:116px 88px 28px;font-size:92px;line-height:1.02;font-weight:400;text-transform:uppercase;} .longform-render.lf-layout-vogue .montx-rule{width:180px;height:1px;margin:0 88px 42px;background:var(--lf-text);} .longform-render.lf-layout-vogue .montx-lead{padding:0 88px 120px;font-size:28px;line-height:1.85;max-width:760px;} .longform-render.lf-layout-vogue .montx-image{margin:0 0 72px;} .longform-render.lf-layout-vogue .montx-narrative{padding:104px 88px 116px;min-height:0;} .longform-render.lf-layout-vogue .montx-watermark{font-size:92px;right:88px;top:92px;color:color-mix(in oklab,var(--lf-text) 10%,transparent);} .longform-render.lf-layout-vogue .montx-section-title{background:transparent;color:var(--lf-text);font-size:46px;font-weight:400;padding:0;margin:0 0 46px;max-width:720px;} .longform-render.lf-layout-vogue .montx-body{border-left:0;padding-left:0;font-size:29px;line-height:1.9;max-width:720px;} .longform-render.lf-layout-vogue .montx-grid{display:block;border:0;padding:104px 88px;background:color-mix(in oklab,var(--lf-text) 5%,var(--lf-bg));} .longform-render.lf-layout-vogue .montx-grid-left{background:transparent;color:var(--lf-text);font-size:58px;font-weight:400;padding:0 0 42px;} .longform-render.lf-layout-vogue .montx-grid-right{padding:0 0 0 180px;font-size:28px;line-height:1.85;border-left:1px solid var(--lf-text);} .longform-render.lf-layout-vogue .montx-footer{padding:110px 88px;background:var(--lf-text);color:var(--lf-bg);} .longform-render.lf-layout-tech{background:linear-gradient(180deg,var(--lf-bg),color-mix(in oklab,var(--lf-accent) 12%,var(--lf-bg)));} .longform-render.lf-layout-tech .montx-topline{height:18px;} .longform-render.lf-layout-tech .montx-giant{padding:58px 56px 22px;font-size:96px;line-height:.95;text-transform:uppercase;border-left:18px solid var(--lf-accent);} .longform-render.lf-layout-tech .montx-rule{height:2px;margin:0 56px 34px;background:var(--lf-accent);} .longform-render.lf-layout-tech .montx-lead{padding:0 56px 78px;font-size:30px;line-height:1.6;} .longform-render.lf-layout-tech .montx-narrative{padding:70px 56px;min-height:320px;border-top:1px solid color-mix(in oklab,var(--lf-accent) 50%,var(--lf-bg));} .longform-render.lf-layout-tech .montx-watermark{font-size:148px;color:color-mix(in oklab,var(--lf-accent) 20%,transparent);} .longform-render.lf-layout-tech .montx-section-title{background:transparent;color:var(--lf-accent);border:1px solid var(--lf-accent);font-size:34px;padding:14px 18px;} .longform-render.lf-layout-tech .montx-body{border-left:2px solid var(--lf-accent);font-size:28px;line-height:1.7;} .longform-render.lf-layout-tech .montx-grid{grid-template-columns:3fr 9fr;border-top:1px solid var(--lf-accent);border-bottom:1px solid var(--lf-accent);} .longform-render.lf-layout-tech .montx-grid-left{font-size:42px;padding:42px 32px;} .longform-render.lf-layout-tech .montx-grid-right{font-size:28px;padding:42px 46px;} .longform-render.lf-layout-data .montx-topline{height:12px;} .longform-render.lf-layout-data .montx-giant{padding:60px 64px 20px;font-size:76px;line-height:1.02;text-transform:none;} .longform-render.lf-layout-data .montx-rule{height:1px;margin:0 64px 28px;} .longform-render.lf-layout-data .montx-lead{padding:0 64px 58px;font-size:28px;line-height:1.6;} .longform-render.lf-layout-data .montx-narrative{padding:54px 64px;min-height:260px;border-top:1px solid color-mix(in oklab,var(--lf-accent) 25%,var(--lf-bg));} .longform-render.lf-layout-data .montx-watermark{font-size:72px;right:64px;top:54px;color:color-mix(in oklab,var(--lf-accent) 18%,transparent);} .longform-render.lf-layout-data .montx-section-title{background:transparent;color:var(--lf-accent);font-size:32px;padding:0;margin-bottom:20px;text-transform:uppercase;} .longform-render.lf-layout-data .montx-body{border-left:0;padding:26px 30px;font-size:27px;line-height:1.65;background:color-mix(in oklab,var(--lf-accent) 7%,var(--lf-bg));border-top:4px solid var(--lf-accent);} .longform-render.lf-layout-data .montx-grid{grid-template-columns:4fr 8fr;border-top:4px solid var(--lf-accent);border-bottom:1px solid var(--lf-accent);} .longform-render.lf-layout-data .montx-grid-left{font-size:38px;padding:36px;} .longform-render.lf-layout-data .montx-grid-right{font-size:27px;padding:36px;} .longform-render.lf-layout-cinematic .montx-topline{height:0;} .longform-render.lf-layout-cinematic .montx-giant{padding:110px 70px 28px;font-size:88px;line-height:.98;text-transform:uppercase;} .longform-render.lf-layout-cinematic .montx-rule{height:2px;margin:0 70px 36px;background:var(--lf-accent);} .longform-render.lf-layout-cinematic .montx-lead{padding:0 70px 104px;font-size:28px;line-height:1.8;} .longform-render.lf-layout-cinematic .montx-image{border-top:48px solid #000;border-bottom:48px solid #000;} .longform-render.lf-layout-cinematic .montx-narrative{padding:88px 70px;min-height:360px;} .longform-render.lf-layout-cinematic .montx-watermark{font-size:64px;right:70px;top:52px;color:var(--lf-accent);} .longform-render.lf-layout-cinematic .montx-section-title{background:transparent;color:var(--lf-accent);font-size:32px;padding:0;margin-bottom:34px;text-transform:uppercase;} .longform-render.lf-layout-cinematic .montx-body{border-left:0;padding:26px 0 0;font-size:29px;line-height:1.8;border-top:1px solid var(--lf-accent);} .longform-render.lf-layout-cinematic .montx-grid{display:block;border:0;padding:82px 70px;background:#000;} .longform-render.lf-layout-cinematic .montx-grid-left{background:transparent;color:var(--lf-accent);font-size:46px;padding:0 0 30px;} .longform-render.lf-layout-cinematic .montx-grid-right{padding:0;font-size:28px;line-height:1.75;color:var(--lf-text);} .longform-render.lf-layout-documentary .montx-topline{height:10px;} .longform-render.lf-layout-documentary .montx-giant{padding:72px 70px 28px;font-size:82px;line-height:1.05;text-transform:none;font-weight:400;} .longform-render.lf-layout-documentary .montx-rule{height:3px;margin:0 70px 34px;} .longform-render.lf-layout-documentary .montx-lead{padding:0 70px 84px;font-size:30px;line-height:1.75;} .longform-render.lf-layout-documentary .montx-narrative{padding:76px 70px;min-height:330px;} .longform-render.lf-layout-documentary .montx-watermark{left:70px;right:auto;top:54px;font-size:60px;color:var(--lf-accent);} .longform-render.lf-layout-documentary .montx-section-title{background:transparent;color:var(--lf-text);font-size:40px;padding:0 0 0 92px;margin-bottom:26px;} .longform-render.lf-layout-documentary .montx-body{border-left:0;padding-left:92px;font-size:30px;line-height:1.78;} .longform-render.lf-layout-documentary .montx-grid{display:block;border:0;padding:76px 70px;border-top:6px solid var(--lf-accent);} .longform-render.lf-layout-documentary .montx-grid-left{background:transparent;color:var(--lf-accent);font-size:44px;padding:0 0 26px;} .longform-render.lf-layout-documentary .montx-grid-right{padding:0;font-size:29px;line-height:1.75;} .longform-render.lf-layout-archive .montx-topline{height:16px;} .longform-render.lf-layout-archive .montx-giant{padding:70px 62px 22px;font-size:70px;line-height:1.08;text-transform:uppercase;border-bottom:1px solid var(--lf-accent);} .longform-render.lf-layout-archive .montx-rule{height:0;margin:0 62px 34px;} .longform-render.lf-layout-archive .montx-lead{padding:0 62px 74px;font-size:28px;line-height:1.75;} .longform-render.lf-layout-archive .montx-narrative{padding:66px 62px;min-height:310px;border-top:1px dashed var(--lf-accent);} .longform-render.lf-layout-archive .montx-watermark{left:62px;right:auto;top:42px;font-size:46px;color:var(--lf-accent);} .longform-render.lf-layout-archive .montx-section-title{background:transparent;color:var(--lf-text);font-size:36px;padding:0 0 0 78px;margin-bottom:22px;text-decoration:underline;text-underline-offset:8px;} .longform-render.lf-layout-archive .montx-body{border-left:1px solid var(--lf-accent);padding-left:26px;margin-left:78px;font-size:28px;line-height:1.75;} .longform-render.lf-layout-archive .montx-grid{grid-template-columns:3fr 9fr;border:1px solid var(--lf-accent);} .longform-render.lf-layout-archive .montx-grid-left{font-size:34px;padding:34px;background:transparent;color:var(--lf-accent);border-right:1px solid var(--lf-accent);} .longform-render.lf-layout-archive .montx-grid-right{font-size:27px;padding:34px;} .longform-render.lf-layout-organic .montx-topline{height:0;} .longform-render.lf-layout-organic .montx-giant{padding:82px 82px 24px;font-size:78px;line-height:1.08;text-transform:none;font-weight:500;} .longform-render.lf-layout-organic .montx-rule{height:10px;width:160px;margin:0 82px 36px;border-radius:20px;background:var(--lf-accent);} .longform-render.lf-layout-organic .montx-lead{padding:0 82px 88px;font-size:30px;line-height:1.78;} .longform-render.lf-layout-organic .montx-image{padding:0 44px 44px;box-sizing:border-box;} .longform-render.lf-layout-organic .montx-narrative{padding:82px;min-height:320px;} .longform-render.lf-layout-organic .montx-watermark{font-size:120px;color:color-mix(in oklab,var(--lf-accent) 16%,transparent);} .longform-render.lf-layout-organic .montx-section-title{background:color-mix(in oklab,var(--lf-accent) 16%,var(--lf-bg));color:var(--lf-text);font-size:38px;padding:18px 24px;margin-bottom:30px;} .longform-render.lf-layout-organic .montx-body{border-left:8px solid var(--lf-accent);font-size:30px;line-height:1.78;} .longform-render.lf-layout-organic .montx-grid{display:block;border:0;padding:82px;background:color-mix(in oklab,var(--lf-accent) 10%,var(--lf-bg));} .longform-render.lf-layout-organic .montx-grid-left{background:transparent;color:var(--lf-accent);font-size:44px;padding:0 0 28px;} .longform-render.lf-layout-organic .montx-grid-right{padding:0;font-size:29px;line-height:1.78;} `; const hexToRgb = (hex = '#580018') => { const clean = hex.replace('#', ''); const value = clean.length === 3 ? clean.split('').map(ch => ch + ch).join('') : clean; const num = Number.parseInt(value, 16); return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255, }; }; const getLongformTheme = (markdown = MONTX_DEFAULT_SKILL) => { const skill = typeof markdown === 'string' ? parseSkillMarkdown(markdown) : markdown; const preset = LONGFORM_THEME_PRESETS.find(item => item.key.test(skill.title)) || LONGFORM_THEME_PRESETS[LONGFORM_THEME_PRESETS.length - 1]; const bg = skill.hex[0] || preset.bg; const accent = skill.hex[1] || preset.accent; const text = skill.hex.find(color => ![bg, accent].includes(color)) || preset.text; const inverse = preset.inverse; const rgb = hexToRgb(accent); return { bg, accent, text, inverse, font: preset.font, watermark: `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.1)`, }; }; const getLongformLayoutPreset = (markdown = MONTX_DEFAULT_SKILL) => { const skill = typeof markdown === 'string' ? parseSkillMarkdown(markdown) : markdown; return LONGFORM_LAYOUT_PRESETS.find(item => item.key.test(skill.title)) || LONGFORM_LAYOUT_PRESETS[LONGFORM_LAYOUT_PRESETS.length - 1]; }; const getLongformThemeVars = (markdown = MONTX_DEFAULT_SKILL) => { const theme = getLongformTheme(markdown); return { '--lf-bg': theme.bg, '--lf-accent': theme.accent, '--lf-text': theme.text, '--lf-inverse': theme.inverse, '--lf-watermark': theme.watermark, '--lf-font': theme.font, }; }; const getLongformHeroSection = (task) => (task.sections || []).find(section => section.type === 'Header') || (task.sections || [])[0] || {}; const getLongformTextUnits = (text = '') => Array.from(String(text)).reduce((sum, ch) => { if (/[,。?!:;、,.!?;:\s]/.test(ch)) return sum + 0.45; if (/[\x00-\xff]/.test(ch)) return sum + 0.55; return sum + 1; }, 0); const getLongformSingleLineHeroPx = (title = '') => { const units = Math.max(1, getLongformTextUnits(title)); return Math.max(46, Math.min(118, Math.floor((900 / units) * 0.96))); }; const getLongformHeroTitleStyle = (task = {}) => { const overrides = task.layoutOverrides || {}; if (!overrides.heroTitleSingleLine) return {}; return { fontSize: `${overrides.heroTitleFontPx || getLongformSingleLineHeroPx(getLongformHeroSection(task).title)}px`, whiteSpace: 'nowrap', letterSpacing: '0', }; }; const getLongformRenderOverrideClass = (task = {}) => task.layoutOverrides?.textAlign === 'center' ? 'lf-text-center' : ''; const getLongformRenderTypography = (task = {}) => { const layout = getLongformLayoutPreset(task.skillMarkdown || MONTX_DEFAULT_SKILL); const preset = LONGFORM_FONT_SIZE_PRESETS[layout.className] || LONGFORM_FONT_SIZE_PRESETS['lf-layout-bauhaus']; return { ...preset, heroTitlePx: task.layoutOverrides?.heroTitleSingleLine ? (task.layoutOverrides.heroTitleFontPx || getLongformSingleLineHeroPx(getLongformHeroSection(task).title)) : preset.heroTitlePx, layoutId: layout.id, layoutLabel: layout.label, }; }; const extractLongformTypographyRules = (task = {}, brand = LONGFORM_BRANDS[0]) => { const markdown = task.skillMarkdown || MONTX_DEFAULT_SKILL; const rules = {}; const sources = []; const heroMin = markdown.match(/(?:大标题|主标题|标题)[^\n\d]{0,24}(\d+)\s*px\s*\+/i)?.[1]; const bodyRange = markdown.match(/正文[^\n\d]{0,24}(\d+)\s*px\s*(?:-|到|至|~)\s*(\d+)\s*px?/i); const sectionRange = markdown.match(/(?:小标题|章节标题)[^\n\d]{0,24}(\d+)\s*px\s*(?:-|到|至|~)\s*(\d+)\s*px?/i); if (heroMin) { rules.heroTitleMinPx = Number(heroMin); sources.push(`style skill: 大标题 ${heroMin}px+`); } if (bodyRange) { rules.bodyFontMinPx = Number(bodyRange[1]); rules.bodyFontMaxPx = Number(bodyRange[2]); sources.push(`style skill: 正文 ${bodyRange[1]}-${bodyRange[2]}px`); } if (sectionRange) { rules.sectionTitleMinPx = Number(sectionRange[1]); rules.sectionTitleMaxPx = Number(sectionRange[2]); sources.push(`style skill: 小标题 ${sectionRange[1]}-${sectionRange[2]}px`); } getActiveLongformConstraints(brand).forEach(rule => { if (!rule.rules) return; if (rule.rules.heroTitleMinPx) { rules.heroTitleMinPx = rule.rules.heroTitleMinPx; sources.push(`品牌护栏「${rule.label}」: 主标题 ${rule.rules.heroTitleMinPx}px+`); } if (rule.rules.bodyFontMinPx && rule.rules.bodyFontMaxPx) { rules.bodyFontMinPx = rule.rules.bodyFontMinPx; rules.bodyFontMaxPx = rule.rules.bodyFontMaxPx; sources.push(`品牌护栏「${rule.label}」: 正文 ${rule.rules.bodyFontMinPx}-${rule.rules.bodyFontMaxPx}px`); } }); const normalBodyRule = getActiveLongformConstraints(brand).find(rule => /正文.*加粗|font-weight|单字加粗|normal/i.test(`${rule.label} ${rule.description}`)); if (normalBodyRule) sources.push(`品牌护栏「${normalBodyRule.label}」: 正文 normal,不做局部加粗`); return { rules, sources }; }; const buildLongformTypographyAudit = (task = {}, brand = LONGFORM_BRANDS[0]) => { const typography = getLongformRenderTypography(task); const { rules, sources } = extractLongformTypographyRules(task, brand); const issues = []; if (rules.heroTitleMinPx && typography.heroTitlePx < rules.heroTitleMinPx) { issues.push(`主标题 ${typography.heroTitlePx}px 低于 ${rules.heroTitleMinPx}px+`); } const bodyTargets = [ ['正文', typography.bodyPx], ['网格正文', typography.gridRightPx], ['引言', typography.leadPx], ]; bodyTargets.forEach(([label, px]) => { if (rules.bodyFontMinPx && px < rules.bodyFontMinPx) issues.push(`${label} ${px}px 低于正文下限 ${rules.bodyFontMinPx}px`); if (rules.bodyFontMaxPx && px > rules.bodyFontMaxPx) issues.push(`${label} ${px}px 高于正文上限 ${rules.bodyFontMaxPx}px`); }); if (rules.sectionTitleMinPx && typography.sectionTitlePx < rules.sectionTitleMinPx) { issues.push(`小标题 ${typography.sectionTitlePx}px 低于 ${rules.sectionTitleMinPx}px`); } if (rules.sectionTitleMaxPx && typography.sectionTitlePx > rules.sectionTitleMaxPx) { issues.push(`小标题 ${typography.sectionTitlePx}px 高于 ${rules.sectionTitleMaxPx}px`); } return { typography, rules, sources, issues }; }; const LONGFORM_PR_REVIEW_RULES = [ { level: 'S3', token: '玻璃心', label: '不要默认用户玻璃心' }, { level: 'S2', token: '你误会了', label: '不要和用户争夺解释权' }, { level: 'S2', token: '本意不是', label: '避免先解释品牌本意' }, { level: 'S2', token: '过度解读', label: '避免指责用户解读' }, { level: 'S2', token: '牛马', label: '品牌不能替用户自嘲' }, { level: 'S2', token: '买不起', label: '避免轻视用户购买能力' }, { level: 'S2', token: '秒杀', label: '高风险促销/比较表达' }, { level: 'S2', token: '解放双手', label: '智能驾驶高风险承诺' }, { level: 'S2', token: '完全自动驾驶', label: '自动驾驶边界风险' }, { level: 'S1', token: '很快', label: '模糊能力表达,建议量化' }, ]; const buildLongformPrAudit = (task = {}) => { const text = [ task.title || '', task.prompt || '', ...(task.sections || []).flatMap(section => [section.title || '', section.body || '']), ].join('\n'); const hits = LONGFORM_PR_REVIEW_RULES.filter(rule => text.includes(rule.token)); const rank = { S0: 0, S1: 1, S2: 2, S3: 3, S4: 4 }; const level = hits.reduce((max, hit) => rank[hit.level] > rank[max] ? hit.level : max, 'S0'); const action = { S0: '无明显舆情风险,仅需监测', S1: '轻微争议,建议优化表达', S2: '中度舆情风险,建议修改内容并准备回应', S3: '严重舆情风险,建议统一口径并正式说明', S4: '重大危机,需启动危机公关机制', }[level]; return { level, hits, action }; }; const styleObjectToHtml = (style = {}) => { const css = Object.entries(style).map(([key, value]) => `${key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`)}:${value}`).join(';'); return css ? ` style="${escapeHtml(css)}"` : ''; }; const buildLongformThemeCss = (markdown = MONTX_DEFAULT_SKILL, selector = '.montx') => { const theme = getLongformTheme(markdown); return `${selector}{--lf-bg:${theme.bg};--lf-accent:${theme.accent};--lf-text:${theme.text};--lf-inverse:${theme.inverse};--lf-watermark:${theme.watermark};--lf-font:${theme.font};}`; }; const buildLongformHtml = (task, brand) => { const theme = getLongformTheme(task.skillMarkdown || MONTX_DEFAULT_SKILL); const layout = getLongformLayoutPreset(task.skillMarkdown || MONTX_DEFAULT_SKILL); const overrideClass = getLongformRenderOverrideClass(task); const cssSource = `${MONTX_EXPORT_CSS}\n${LONGFORM_LAYOUT_CSS}`; const css = `body{margin:0;background:${theme.bg};}${buildLongformThemeCss(task.skillMarkdown || MONTX_DEFAULT_SKILL)}${cssSource .replaceAll('.montx-canvas', '.montx') .replaceAll('.montx-topline', '.topline') .replaceAll('.montx-giant', '.giant') .replaceAll('.montx-rule', '.rule') .replaceAll('.montx-lead', '.lead') .replaceAll('.montx-image', '.image') .replaceAll('.montx-narrative', '.narrative') .replaceAll('.montx-watermark', '.watermark') .replaceAll('.montx-section-title', '.section-title') .replaceAll('.montx-body', '.body') .replaceAll('.montx-grid-left', '.grid-left') .replaceAll('.montx-grid-right', '.grid-right') .replaceAll('.montx-grid', '.grid') .replaceAll('.montx-footer', '.footer')}`; const sections = task.sections.map((s, i) => { const imgs = task.images.filter(img => (s.imageIds || []).includes(img.id)); if (s.type === 'Header') { return `
${escapeHtml(s.title)}
${escapeHtml(s.body)}
${buildLongformImageHtml(imgs)}`; } if (s.type === 'Image') { return imgs.length ? buildLongformImageHtml(imgs) : `
${String(i).padStart(2, '0')}
${escapeHtml(s.title)}
${escapeHtml(s.body)}

[ 图片待上传并分配 ]
`; } if (s.type === 'Grid') { return `
${escapeHtml(s.title)}
${escapeHtml(s.body)}
${buildLongformImageHtml(imgs)}`; } if (s.type === 'Footer') { return `${buildLongformImageHtml(imgs)}`; } return `
${String(i).padStart(2, '0')}
${escapeHtml(s.title)}
${escapeHtml(s.body)}
${buildLongformImageHtml(imgs)}`; }).join('\n'); return `${escapeHtml(task.title)}
${sections}
`; }; const getLongformGuardrailInput = (task = {}) => [ task.title, task.prompt, task.briefing?.coreQuestion, task.briefing?.tone, ...(task.sections || []).flatMap(section => [section.title, section.body]), ].filter(Boolean).join('\n'); const calculateLongformQa = (task, brand) => { const validSectionIds = new Set((task.sections || []).map(section => section.id)); const unassigned = (task.images || []).filter(img => !img.assignedSectionId || !validSectionIds.has(img.assignedSectionId)).length; const overrides = (task.overrides || []).length; const widthIssue = getActiveLongformConstraints(brand).find(c => c.id === 'canvas-1080') ? 0 : 1; const typographyAudit = buildLongformTypographyAudit(task, brand); const prAudit = buildLongformPrAudit(task); const guardrailAudit = window.BrandMemoryStore?.runBrandGuardrails ? window.BrandMemoryStore.runBrandGuardrails(getLongformGuardrailInput(task), { brand, brandId: brand?.id, productId: task.productId, campaignId: task.campaignId, campaignName: task.campaignName, scope: 'longform', }) : { violations: [], requiredFixes: [] }; const normalizedSections = (task.sections || []).map(normalizeLongformSection); const blockIds = new Set(normalizedSections.map(section => section.blockType)); const issues = []; if (unassigned) issues.push(`${unassigned} 张图片未分配 section`); if (overrides) issues.push(`${overrides} 项品牌护栏已申请解除`); if (widthIssue) issues.push('缺少 1080px 画布规则'); if (!task.skillMarkdown) issues.push('尚未上传或选择 markdown skill'); if (!blockIds.has('hero')) issues.push('缺少开场 Hero 模块'); if (!blockIds.has('cta')) issues.push('缺少收束 CTA 模块'); if (['product', 'brand_product'].includes(task.narrativeMode || 'brand_product') && !getLongformProduct(brand, task.productId)) { issues.push('叙事模式需要绑定产品 Profile'); } typographyAudit.issues.forEach(issue => issues.push(`字体约束:${issue}`)); if (!['S0', 'S1'].includes(prAudit.level)) issues.push(`PR 舆情审核:${prAudit.level} ${prAudit.action}`); guardrailAudit.violations.forEach(item => issues.push(`品牌护栏:${item.message}`)); return { score: Math.max(52, 96 - unassigned * 6 - overrides * 10 - (task.skillMarkdown ? 0 : 18) - typographyAudit.issues.length * 6 - guardrailAudit.requiredFixes.length * 10 - (prAudit.level === 'S1' ? 4 : prAudit.level === 'S2' ? 10 : prAudit.level === 'S3' ? 18 : prAudit.level === 'S4' ? 28 : 0) - (!blockIds.has('hero') ? 8 : 0) - (!blockIds.has('cta') ? 6 : 0) - ((['product', 'brand_product'].includes(task.narrativeMode || 'brand_product') && !getLongformProduct(brand, task.productId)) ? 8 : 0)), issues, exportReady: Boolean(task.sections.length && task.skillMarkdown), guardrailAudit, }; }; const buildLongformInspect = (task, brand, skill) => { const sections = (task.sections || []).map(normalizeLongformSection); const validSectionIds = new Set(sections.map(section => section.id)); const assignedImages = (task.images || []).filter(img => img.assignedSectionId && validSectionIds.has(img.assignedSectionId)); const unassignedImages = (task.images || []).filter(img => !img.assignedSectionId || !validSectionIds.has(img.assignedSectionId)); const blockIds = new Set(sections.map(section => section.blockType)); const typographyAudit = buildLongformTypographyAudit(task, brand); const prAudit = buildLongformPrAudit(task); const guardrailAudit = window.BrandMemoryStore?.runBrandGuardrails ? window.BrandMemoryStore.runBrandGuardrails(getLongformGuardrailInput(task), { brand, brandId: brand?.id, productId: task.productId, campaignId: task.campaignId, campaignName: task.campaignName, scope: 'longform', }) : { passed: true, violations: [], checkedRules: getActiveLongformConstraints(brand).length }; const makeItem = (label, value, state = 'ok', detail = '') => ({ label, value, state, detail }); const groups = [ { id: 'brief', title: 'Brief Readiness', items: [ makeItem('标题', task.title || '未命名', task.title ? 'ok' : 'warn'), makeItem('结构化 Brief', task.briefing ? '已解析' : '未导入', task.briefing ? 'ok' : 'warn', task.briefing?.coreQuestion || '可先导入/粘贴 Brief 以提高稳定性'), makeItem('叙事模式', getLongformNarrativeMode(task.narrativeMode).label, 'ok'), makeItem('产品 Profile', getLongformProduct(brand, task.productId)?.name || '未绑定', getLongformProduct(brand, task.productId) ? 'ok' : 'warn'), ], }, { id: 'dsl', title: 'Block DSL', items: [ makeItem('Section 数量', `${sections.length}`, sections.length >= 5 && sections.length <= 8 ? 'ok' : 'warn', '建议 5-8 个 section'), makeItem('开场 Hero', blockIds.has('hero') ? '存在' : '缺失', blockIds.has('hero') ? 'ok' : 'warn'), makeItem('收束 CTA', blockIds.has('cta') ? '存在' : '缺失', blockIds.has('cta') ? 'ok' : 'warn'), makeItem('模块类型', `${blockIds.size} 类`, blockIds.size >= 3 ? 'ok' : 'warn', '建议包含叙事、证据/图片、信息或 CTA 等类型'), ], }, { id: 'image', title: 'Image Mapping', items: [ makeItem('图片总数', `${(task.images || []).length}`, (task.images || []).length ? 'ok' : 'warn'), makeItem('已分配', `${assignedImages.length}/${(task.images || []).length}`, unassignedImages.length ? 'warn' : 'ok'), makeItem('图片全通栏', getActiveLongformConstraints(brand).some(rule => rule.id === 'full-bleed-image') ? '护栏启用' : '未启用', getActiveLongformConstraints(brand).some(rule => rule.id === 'full-bleed-image') ? 'ok' : 'warn'), makeItem('图片压字', '未检测到', 'ok'), ], }, { id: 'wechat', title: 'WeChat Compatibility', items: [ makeItem('画布宽度', `${skill.width || 1080}px`, String(skill.width || '1080') === '1080' ? 'ok' : 'warn'), makeItem('单文件 HTML', getActiveLongformConstraints(brand).some(rule => rule.id === 'single-file-html') ? '护栏启用' : '未启用', getActiveLongformConstraints(brand).some(rule => rule.id === 'single-file-html') ? 'ok' : 'warn'), makeItem('公众号 HTML 复制', '未接入', 'idle', '当前仅导出整张 PNG,富文本复制留到下一步'), makeItem('PNG 长图', task.html ? '可导出' : '待生成', task.html ? 'ok' : 'warn'), ], }, { id: 'policy', title: 'Brand QA', items: [ makeItem('品牌护栏', `${guardrailAudit.checkedRules} active`, guardrailAudit.violations.length ? 'warn' : 'ok', guardrailAudit.violations.map(item => item.message).join(';')), makeItem('Override', `${(task.overrides || []).length}`, (task.overrides || []).length ? 'warn' : 'ok'), makeItem('字号约束', typographyAudit.issues.length ? `${typographyAudit.issues.length} risks` : '通过', typographyAudit.issues.length ? 'warn' : 'ok', typographyAudit.issues.join(';')), makeItem('QA Score', `${task.qa?.score || 0}`, (task.qa?.score || 0) >= 80 ? 'ok' : 'warn'), ], }, { id: 'pr', title: 'PR Reputation QA', items: [ makeItem('PR Skill', 'Chief Public Relations Officer', 'ok'), makeItem('舆情等级', prAudit.level, ['S0', 'S1'].includes(prAudit.level) ? 'ok' : 'warn', prAudit.action), makeItem('风险表达', prAudit.hits.length ? `${prAudit.hits.length} hits` : '未命中', prAudit.hits.length ? 'warn' : 'ok', prAudit.hits.map(hit => `${hit.token}: ${hit.label}`).join(';')), makeItem('回应原则', '先情绪后事实', 'ok', '不说“你误会了 / 本意不是 / 过度解读”;品牌不与用户争夺解释权'), ], }, ]; return { groups, issues: groups.flatMap(group => group.items.filter(item => item.state === 'warn').map(item => `${group.title}:${item.label} ${item.value}`)), }; }; const normalizeModelSections = (sections = []) => { const allowedTypes = new Set(['Header', 'Narrative', 'Image', 'Grid', 'Footer']); const fallbackModule = { Header: 'Header', Narrative: 'Narrative Block', Image: 'Image Block', Grid: 'Grid Content', Footer: 'Footer' }; return sections.slice(0, 8).map((section, i) => { const type = allowedTypes.has(section.type) ? section.type : (i === 0 ? 'Header' : i === sections.length - 1 ? 'Footer' : 'Narrative'); return normalizeLongformSection({ id: `${type.toLowerCase()}-${Date.now()}-${i}`, type, blockType: section.blockType, title: section.title || LONGFORM_DEFAULT_SECTIONS[i % LONGFORM_DEFAULT_SECTIONS.length].title, body: section.body || '', imageIds: [], layoutModule: section.layoutModule || fallbackModule[type], imageHint: section.imageHint || '', }, i); }); }; const initialLongformMessages = (task) => task.messages?.length ? task.messages : [ { role: 'assistant', text: '把这篇公众号长图文的目标、读者、语气和必须覆盖的观点发给我。我会先推演结构,再把它拆成可排版的 section。' }, { role: 'user', text: task.prompt }, ]; const buildLocalLongformDraft = (promptOverride = '', fallbackTitle = '') => { const title = promptOverride.split(/[,。,.\n]/)[0].replace(/^为微信公众号设计一篇/, '').slice(0, 24) || fallbackTitle || 'LONGFORM V10'; const sections = LONGFORM_DEFAULT_SECTIONS.map((s, i) => ({ ...s, id: `${s.type.toLowerCase()}-${i}-${Date.now()}`, title: i === 0 ? title : s.title, body: i === 1 ? `基于输入需求:「${promptOverride}」。Longform Agent 将先确定读者、论点和证据,再交给 MONTX 网格系统完成长图表达。` : s.body, })).map(normalizeLongformSection); return { title, sections }; }; const syncLongformSectionsWithImages = (sections = [], images = []) => sections.map(section => ({ ...section, imageIds: images.filter(img => img.assignedSectionId === section.id).map(img => img.id), })); const getLongformImageHint = (section, index = 0) => { if (section?.imageHint) return section.imageHint; const hints = { Header: '建议使用一张建立主题气质的全通栏主视觉,不在图片上压字。', Narrative: '建议补充能证明段落观点的真实场景、人物或环境图片。', Image: '建议放置最强证据图,可连续添加多张形成视觉证据组。', Grid: '建议选择细节、对比、步骤或局部特写,辅助网格化信息拆解。', Footer: '通常不配图;如需收束,可使用品牌环境图或产品全景。', }; return hints[section?.type] || `建议选择与第 ${index + 1} 段标题和正文证据直接相关的图片。`; }; const buildLongformImageHtml = (images = []) => images .map(img => `${escapeHtml(img.name)}`) .join(''); const findLongformConstraint = (brand, query = '') => { const clean = query.trim().toLowerCase(); if (!clean) return null; return (brand.constraints || []).find(rule => { const haystack = `${rule.id} ${rule.label} ${rule.description}`.toLowerCase(); return haystack.includes(clean) || clean.includes(rule.label.toLowerCase()); }) || null; }; const getActiveLongformConstraints = (brand, scope = 'longform') => window.BrandMemoryStore?.getActiveConstraints ? window.BrandMemoryStore.getActiveConstraints(brand, scope) : (brand?.constraints || []).filter(rule => rule.enabled !== false); const extractLongformTerms = (text = '') => { const quoted = Array.from(text.matchAll(/[“"「『《]([^”"」』》]+)[”"」』》]/g)).map(match => match[1].trim()); const known = ['完全自动驾驶', '解放双手', '无人驾驶', '绝对安全', '碾压竞品', '秒杀', '促销', '渐变', '毛玻璃', '阴影', '图片压字', '圆角拼图']; return Array.from(new Set([...quoted, ...known.filter(term => text.includes(term))])); }; const inferLongformGuardrailRules = (text = '') => { const rules = {}; const heroMin = text.match(/(?:主标题|大标题|标题)[^\d]*(\d+)\s*px?\+?/i)?.[1]; const bodyRange = text.match(/正文[^\d]*(\d+)\s*(?:-|到|至|~)\s*(\d+)\s*px?/i); const canvasWidth = text.match(/(?:画布|宽度)[^\d]*(\d+)\s*px?/i)?.[1]; if (heroMin) rules.heroTitleMinPx = Number(heroMin); if (bodyRange) { rules.bodyFontMinPx = Number(bodyRange[1]); rules.bodyFontMaxPx = Number(bodyRange[2]); } if (canvasWidth) rules.canvasWidthPx = Number(canvasWidth); return rules; }; const buildLongformGuardrailDraft = (input = '', brand) => { const text = input.trim(); if (!text) return null; const matched = findLongformConstraint(brand, text); const isDisable = /(停用|禁用|关闭|取消|不再启用|disable)/i.test(text); const isEdit = /(修改|改成|改为|更新|调整|编辑|补充)/i.test(text) || (matched && !isDisable); const terms = extractLongformTerms(text); const rules = inferLongformGuardrailRules(text); let label = '自定义品牌护栏'; if (/字号|主标题|大标题|正文|画布|px/i.test(text)) label = '字号与画布约束'; else if (/自动驾驶|解放双手|无人驾驶|承诺|安全/.test(text)) label = '禁止自动驾驶过度承诺'; else if (/图片|压字|全通栏|圆角|拼图/.test(text)) label = '图片使用约束'; else if (/渐变|毛玻璃|阴影|视觉|风格/.test(text)) label = '视觉风格约束'; const operation = isDisable ? 'disable' : isEdit && matched ? 'edit' : 'add'; const target = matched ? matched.id : ''; return { id: longformId('draft'), operation, target, targetLabel: matched?.label || '', constraint: normalizeLongformConstraint({ id: operation === 'add' ? longformId('guard') : matched?.id, label: operation === 'edit' && matched ? matched.label : label, description: text, severity: 'required', category: inferLongformConstraintCategory({ label, description: text }), source: 'user-natural-language', forbiddenTerms: /(禁止|避免|不要|不得|不能|风险|承诺)/.test(text) ? terms : [], requiredTerms: /(必须|需要|保持|统一|强制)/.test(text) ? terms : [], rules, createdAt: new Date().toLocaleString('zh-CN', { hour12: false }), updatedAt: new Date().toLocaleString('zh-CN', { hour12: false }), }), }; }; const runLongformLayoutCommand = (text, task) => { const normalized = text.trim(); const updatedAt = new Date().toLocaleString('zh-CN', { hour12: false }); const wantsHeroSingleLine = /(主标题|大标题|标题)/.test(normalized) && /(一行|单行|缩小|放一行|不换行)/.test(normalized); const wantsTextCenter = /(所有|全部|全局|整篇|全文|文字|文本|正文|标题).*(居中|居中显示)|居中显示|文字居中/.test(normalized); const wantsTextDefault = /(取消|恢复|默认).*(居中|对齐)|左对齐|恢复默认对齐|默认对齐/.test(normalized); if (wantsTextCenter || wantsTextDefault) { const layoutOverrides = { ...(task.layoutOverrides || {}), textAlignRequest: normalized, updatedAt, }; if (wantsTextDefault) { delete layoutOverrides.textAlign; } else { layoutOverrides.textAlign = 'center'; } return { taskPatch: { layoutOverrides, status: '排版已调整' }, reply: wantsTextDefault ? '已恢复默认文字对齐方式。正文和 section 内容保持不变。' : '已将长图预览中的标题、正文、段落标题和页脚文字设置为居中显示。正文和 section 内容保持不变。', }; } if (wantsHeroSingleLine) { const title = getLongformHeroSection(task).title || task.title || ''; const fontPx = getLongformSingleLineHeroPx(title); const layoutOverrides = { ...(task.layoutOverrides || {}), heroTitleSingleLine: true, heroTitleFontPx: fontPx, heroTitleRequest: normalized, updatedAt, }; return { taskPatch: { layoutOverrides, status: '排版已调整' }, reply: `已把主标题设置为单行适配,当前估算字号为 ${fontPx}px。正文和 section 内容保持不变。注意:如果低于 MONTX 大标题 110px+ 的品牌约束,QA 会标记风险。`, }; } return null; }; const runLongformQaCommand = (text, task, brand) => { const normalized = text.trim(); const isTypographyQuestion = /(字体|字号|字重|font|主标题|大标题|正文|小标题).*(约束|限制|规范|合规|符合|检查|多少|多大|被约束|有没有)|(?:约束|限制|规范|合规|符合|检查).*(字体|字号|字重|主标题|大标题|正文|小标题)/i.test(normalized); if (!isTypographyQuestion) return null; const audit = buildLongformTypographyAudit(task, brand); const family = getLongformTheme(task.skillMarkdown || MONTX_DEFAULT_SKILL).font; const sourceText = audit.sources.length ? audit.sources.join(';') : '当前未解析到明确字号约束'; const valueText = [ `主标题 ${audit.typography.heroTitlePx}px`, `引言 ${audit.typography.leadPx}px`, `正文 ${audit.typography.bodyPx}px`, `网格正文 ${audit.typography.gridRightPx}px`, `小标题 ${audit.typography.sectionTitlePx}px`, ].join(';'); const conclusion = audit.issues.length ? `结论:存在 ${audit.issues.length} 项风险:${audit.issues.join(';')}。` : '结论:当前可解析的字体/字号约束均通过。'; return { taskPatch: {}, reply: `可以检查。当前字体族为 ${family}。约束来源:${sourceText}。当前渲染:${audit.typography.layoutLabel},${valueText}。${conclusion}`, }; }; const classifyLongformIntentLocal = (input = '') => { const text = String(input).trim(); const hasStyleKeyword = /(字体|字号|字重|font|样式|排版|布局|居中|对齐|主标题|大标题|正文|小标题|颜色|色彩|模板|风格|style)/i.test(text); const hasQuestionSignal = /(是否|有没有|有无|吗|么|多少|多大|为什么|检查|合规|符合|约束|限制|规范|被约束)/.test(text); const hasAdjustmentSignal = /(改|修改|调整|设为|设置|变成|换成|缩小|放大|居中|左对齐|右对齐|一行|单行|不换行|恢复|取消)/.test(text); if (/^(查看护栏|解除护栏[::]|恢复护栏[::])/.test(text)) { return { type: 'brand_guardrail', target: 'brand_guardrail', shouldRegenerateContent: false, shouldPreserveCopy: true, confidence: 0.98, rationale: '本地规则识别为品牌护栏命令', source: 'local' }; } if (hasStyleKeyword && hasQuestionSignal) { return { type: 'style_query', target: 'typography_or_style', shouldRegenerateContent: false, shouldPreserveCopy: true, confidence: 0.9, rationale: '本地规则识别为字体或样式问询', source: 'local' }; } if (hasStyleKeyword && hasAdjustmentSignal) { return { type: 'style_adjustment', target: 'typography_or_layout', shouldRegenerateContent: false, shouldPreserveCopy: true, confidence: 0.9, rationale: '本地规则识别为字体或样式调整', source: 'local' }; } if (/(配图|图片|图池|image|主图|插图)/i.test(text) && /(分配|添加|选择|一键|换|删除|移动)/.test(text)) { return { type: 'image_assignment', target: 'images', shouldRegenerateContent: false, shouldPreserveCopy: true, confidence: 0.82, rationale: '本地规则识别为图片分配操作', source: 'local' }; } return { type: 'content_generation', target: 'longform_content', shouldRegenerateContent: true, shouldPreserveCopy: false, confidence: 0.68, rationale: '本地规则未识别为局部操作,默认进入内容推演', source: 'local' }; }; const normalizeLongformIntent = (intent = {}, fallbackText = '') => { const allowed = new Set(['content_generation', 'style_query', 'style_adjustment', 'brand_guardrail', 'image_assignment', 'export_or_draft', 'unknown']); const local = classifyLongformIntentLocal(fallbackText); const type = allowed.has(intent.type) ? intent.type : local.type; const shouldPreserveCopy = Boolean(intent.shouldPreserveCopy || ['style_query', 'style_adjustment', 'brand_guardrail', 'image_assignment', 'export_or_draft'].includes(type)); return { type, target: intent.target || local.target || 'unknown', shouldRegenerateContent: shouldPreserveCopy ? false : Boolean(intent.shouldRegenerateContent || type === 'content_generation'), shouldPreserveCopy, confidence: Number(intent.confidence || local.confidence || 0), rationale: intent.rationale || local.rationale || '', source: intent.source || 'unknown', modelError: intent.modelError || '', }; }; const protectLongformIntent = (intent, text) => { const normalized = normalizeLongformIntent(intent, text); const local = classifyLongformIntentLocal(text); if (local.shouldPreserveCopy && normalized.shouldRegenerateContent) { return { ...local, source: `${normalized.source || 'model'} + local-safety`, rationale: `本地安全闸覆盖:${local.rationale}`, modelIntent: normalized, }; } return normalized; }; const applyLongformGuardrailDraft = (brand, draft) => { if (!draft) return brand; const updatedAt = new Date().toLocaleString('zh-CN', { hour12: false }); if (draft.operation === 'disable') { return { ...brand, constraints: brand.constraints.map(rule => rule.id === draft.target ? { ...rule, enabled: false, updatedAt } : rule), }; } if (draft.operation === 'edit') { return { ...brand, constraints: brand.constraints.map(rule => rule.id === draft.target ? { ...rule, description: draft.constraint.description, category: draft.constraint.category, forbiddenTerms: Array.from(new Set([...(rule.forbiddenTerms || []), ...(draft.constraint.forbiddenTerms || [])])), requiredTerms: Array.from(new Set([...(rule.requiredTerms || []), ...(draft.constraint.requiredTerms || [])])), rules: { ...(rule.rules || {}), ...(draft.constraint.rules || {}) }, enabled: true, updatedAt, } : rule), }; } return { ...brand, constraints: [...brand.constraints, { ...draft.constraint, updatedAt }], }; }; const LONGFORM_MODEL_STAGE = { idle: '待输入需求', classifying: 'LLM 判断需求类型', importing: '导入测试素材', imported: '测试素材已导入', thinking: '解析需求 / DeepSeek 调用中', success: '结构生成完成 / HTML 已生成', fallback: '本地兜底 / 失败原因', }; const LongformDB = { open() { return new Promise((resolve, reject) => { const req = indexedDB.open('aurelia-longform-agent', 1); req.onupgradeneeded = () => req.result.createObjectStore('kv'); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); }, async get(key) { try { const db = await LongformDB.open(); return await new Promise((resolve, reject) => { const req = db.transaction('kv', 'readonly').objectStore('kv').get(key); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } catch (e) { console.warn('Longform IndexedDB read failed', e); return null; } }, async set(key, value) { try { const db = await LongformDB.open(); await new Promise((resolve, reject) => { const req = db.transaction('kv', 'readwrite').objectStore('kv').put(value, key); req.onsuccess = () => resolve(); req.onerror = () => reject(req.error); }); } catch (e) { console.warn('Longform IndexedDB write failed', e); } }, }; const useLongformTasks = () => { const [tasks, setTasks] = React.useState([]); const [ready, setReady] = React.useState(false); React.useEffect(() => { let mounted = true; LongformDB.get('tasks').then(saved => { if (!mounted) return; const initialSource = saved?.length ? saved : LONGFORM_MOCK_TASKS; const initial = initialSource.map(task => { const normalized = normalizeLongformTask(task); return { ...normalized, html: normalized.html || buildLongformHtml(normalized, getLongformBrand(normalized.brandId)) }; }); setTasks(initial); setReady(true); }); return () => { mounted = false; }; }, []); React.useEffect(() => { if (ready) LongformDB.set('tasks', tasks); }, [ready, tasks]); return { tasks, setTasks, ready }; }; const useLongformBrandProfiles = () => { if (window.BrandMemoryStore?.useBrandMemoryProfiles) return window.BrandMemoryStore.useBrandMemoryProfiles(); const [brands, setBrands] = React.useState(() => normalizeSharedBrandProfiles()); const [ready, setReady] = React.useState(false); React.useEffect(() => { let mounted = true; LongformDB.get('brandProfiles').then(saved => { if (!mounted) return; setBrands(normalizeSharedBrandProfiles(saved?.length ? saved : LONGFORM_BRANDS)); setReady(true); }); return () => { mounted = false; }; }, []); React.useEffect(() => { if (ready) LongformDB.set('brandProfiles', brands); }, [ready, brands]); return { brands, setBrands, ready }; }; const useLongformStyleTemplates = () => { const [remoteTemplates, setRemoteTemplates] = React.useState([]); React.useEffect(() => { let mounted = true; fetch('/api/longform/style-templates') .then(response => response.ok ? response.json() : Promise.reject(new Error(`模板服务返回 ${response.status}`))) .then(data => { if (mounted) setRemoteTemplates(Array.isArray(data.styleTemplates) ? data.styleTemplates : []); }) .catch(error => { console.warn('Longform style templates unavailable', error); if (mounted) setRemoteTemplates([]); }); return () => { mounted = false; }; }, []); return React.useMemo(() => [LONGFORM_DEFAULT_STYLE_TEMPLATE, ...remoteTemplates], [remoteTemplates]); }; const getActiveStyleTemplateId = (task, styleTemplates) => { if (task.activeStyleTemplateId) return task.activeStyleTemplateId; const matched = styleTemplates.find(template => template.markdown === task.skillMarkdown); if (matched) return matched.id; return task.skillMarkdown === MONTX_DEFAULT_SKILL ? LONGFORM_DEFAULT_STYLE_TEMPLATE_ID : LONGFORM_CUSTOM_STYLE_TEMPLATE_ID; }; const LongformAgent = ({ workspaceContext }) => { const { tasks, setTasks, ready } = useLongformTasks(); const { brands, setBrands, ready: brandsReady } = useLongformBrandProfiles(); const styleTemplates = useLongformStyleTemplates(); const [selectedId, setSelectedId] = React.useState(null); const selected = tasks.find(t => t.id === selectedId); const upsertTask = (task) => { const normalizedTask = normalizeLongformTask(task, brands); const brand = getLongformBrand(normalizedTask.brandId, brands); const qa = calculateLongformQa(normalizedTask, brand); const next = { ...normalizedTask, qa, html: buildLongformHtml({ ...normalizedTask, qa }, brand), updatedAt: new Date().toLocaleString('zh-CN', { hour12: false }) }; setTasks(prev => prev.map(t => t.id === task.id ? next : t)); return next; }; const createTask = (brandId = workspaceContext?.brandId || 'montx', productId = '') => { const brand = getLongformBrand(brandId, brands); const product = getLongformProduct(brand, productId); const task = { id: longformId(), brandId, productId: product?.id || '', narrativeMode: 'brand_product', title: '未命名长图文推演', prompt: '输入这篇微信公众号长图文的目标、读者、语气、必须覆盖的观点。', status: '推演中', activeStyleTemplateId: LONGFORM_DEFAULT_STYLE_TEMPLATE_ID, skillMarkdown: MONTX_DEFAULT_SKILL, images: [], sections: LONGFORM_DEFAULT_SECTIONS.map(s => ({ ...s, id: `${s.id}-${Date.now()}` })), html: '', messages: [ { role: 'assistant', text: '把这篇公众号长图文的目标、读者、语气和必须覆盖的观点发给我。我会先推演结构,再把它拆成可排版的 section。' }, { role: 'user', text: '输入这篇微信公众号长图文的目标、读者、语气、必须覆盖的观点。' }, ], overrides: [], qa: { score: 90, issues: [], exportReady: true }, updatedAt: new Date().toLocaleString('zh-CN', { hour12: false }), }; const saved = { ...task, html: buildLongformHtml(task, brand) }; setTasks(prev => [saved, ...prev]); setSelectedId(saved.id); }; const updateBrand = (brandId, nextBrand) => setBrands(prev => prev.map(brand => brand.id === brandId ? normalizeSharedBrandProfiles([nextBrand])[0] : brand)); if (!ready || !brandsReady) { return
正在加载 Longform 草稿…
; } return selected ? ( setSelectedId(null)} onChange={upsertTask}/> ) : ( ); }; const LongformList = ({ tasks, brands, workspaceContext, onUpdateBrand, onNew, onOpen }) => { const [brandId, setBrandId] = React.useState(workspaceContext?.brandId || 'montx'); const [guardrailExpanded, setGuardrailExpanded] = React.useState(false); React.useEffect(() => { if (workspaceContext?.brandId) setBrandId(workspaceContext.brandId); }, [workspaceContext?.brandId]); const brand = getLongformBrand(brandId, brands); const defaultProduct = getLongformProduct(brand); const activeConstraints = getActiveLongformConstraints(brand); const visibleConstraints = guardrailExpanded ? brand.constraints : brand.constraints.slice(0, 4); const brandTasks = tasks.filter(t => t.brandId === brandId); const overrideCount = brandTasks.filter(t => t.overrides.length).length; const riskCount = brandTasks.filter(t => t.qa.issues?.length || t.overrides.length).length; React.useEffect(() => { window.__LONGFORM_THINKING_CONTEXT = { mode: 'list', tasks, brand, exportState: '' }; window.dispatchEvent(new CustomEvent('longform-thinking-change')); }, [tasks, brandId, brands]); return (

Longform Agent · 长图文内容工厂

多品牌长文推演、品牌护栏治理、markdown style skill、图片分配与整张 PNG 导出。

Workspace · {brand.name} · {(brand.products || []).length} products share memory

继承策略 · Workspace Policy

来自品牌记忆 · {brand.name} · {activeConstraints.length} active
product.name).join(' / ') || '未配置'}/> 0}/>
{(brand.products || []).map(product => (
{brand.name} {product.name} {product.positioning || '暂无产品定位'}
{tasks.filter(task => task.productId === product.id).length} tasks
))}
{visibleConstraints.map(c => (
{c.label} {c.description}
{c.enabled === false ? 'disabled' : c.category}
))}
Longform 只消费当前 Workspace 的品牌/产品叙事与护栏;规则编辑已归属到「品牌记忆」。

长图文推演列表

{tasks.length} drafts
IndexedDB autosave
{tasks.map(task => { const taskBrand = getLongformBrand(task.brandId, brands); const taskProduct = getLongformProduct(taskBrand, task.productId); return (
onOpen(task.id)}>
{taskBrand.name} {taskProduct && {taskProduct.name}} {getLongformNarrativeMode(task.narrativeMode).label}

{task.title}

{task.prompt}

{parseSkillMarkdown(task.skillMarkdown).title} {task.images.length} 图 {task.sections.length} 段 QA {task.qa.score}
{task.overrides.length ? 'Override' : task.status} {task.updatedAt}
); })}
); }; const GuardrailMaintenancePanel = ({ brand, onApply }) => { const [input, setInput] = React.useState(''); const [draft, setDraft] = React.useState(null); const parseDraft = () => setDraft(buildLongformGuardrailDraft(input, brand)); const applyDraft = () => { if (!draft) return; onApply(applyLongformGuardrailDraft(brand, draft)); setInput(''); setDraft(null); }; return (
护栏维护 自然语言会先解析成变更草案,确认后才写入品牌 profile。
IndexedDB policy
{brand.constraints.map(rule => (
{rule.enabled === false ? 'disabled' : rule.category} {rule.label}

{rule.description}

))}