| 1 | import type { SessionDeckGenerationContext } from '../../agent/types' |
| 2 | import { isSectionAgendaOutline } from '@shared/generation' |
| 3 | import { |
| 4 | buildLayoutCollisionRules, |
| 5 | buildPageSemanticStructure, |
| 6 | buildCanvasConstraints, |
| 7 | buildCanvasScenarioContentRules, |
| 8 | buildCanvasScenarioDeliveryGuard, |
| 9 | buildCanvasScenarioExpansionRules, |
| 10 | CONTENT_LANGUAGE_RULES, |
| 11 | CONTENT_WRITING_RULES, |
| 12 | FRONTEND_CAPABILITIES, |
| 13 | SOURCE_DOCUMENT_FACT_RULE, |
| 14 | SOURCE_DOCUMENT_READ_STRATEGY, |
| 15 | SOURCE_GROUNDED_EXPANSION_RULES, |
| 16 | STABLE_HTML_FRAGMENT_PROTOCOL, |
| 17 | STYLE_FIDELITY_RULES, |
| 18 | buildOutlinePageList, |
| 19 | formatDesignContract, |
| 20 | resolveContextStylePrompt |
| 21 | } from './shared' |
| 22 | import { formatAnimationPreferencesForPageWriting } from './animation-preferences' |
| 23 | import { buildCanvasScenarioBrief, resolveCanvasScenario } from './canvas-scenario' |
| 24 | import { createPromptCatalog } from '../catalog' |
| 25 | |
| 26 | import deckSystemTemplate from '../templates/deck-system/system.md?raw' |
| 27 | |
| 28 | type DeckSystemTemplateVars = { |
| 29 | system: { |
| 30 | pageWriteRequirement: string |
| 31 | canvasIdentity: string |
| 32 | pageName: string |
| 33 | canvasScenarioBrief: string |
| 34 | canvasScenarioContentRules: string |
| 35 | contentLanguageRules: string |
| 36 | templateOrCreativeInstructions: string |
| 37 | sourceDocumentInstructions: string |
| 38 | canvasConstraints: string |
| 39 | layoutCollisionRules: string |
| 40 | canvasScenarioDeliveryGuard: string |
| 41 | pageSemanticStructure: string |
| 42 | canvasScenarioExpansionRules: string |
| 43 | frontendCapabilities: string |
| 44 | animationPreferencePromptWithSpacing: string |
| 45 | contentWritingRules: string |
| 46 | stableHtmlFragmentProtocol: string |
| 47 | templateAssetGuards: string |
| 48 | pageWriteConstraint: string |
| 49 | executionFlow: string |
| 50 | topic: string |
| 51 | deckTitle: string |
| 52 | slideCount: number |
| 53 | targetInfo: string |
| 54 | targetFileLine: string |
| 55 | pageList: string |
| 56 | presetLabel: string |
| 57 | presetId: string |
| 58 | stylePrompt: string |
| 59 | designContract: string |
| 60 | styleFidelityRules: string |
| 61 | finalWriteToolName: string |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | const deckSystemPromptCatalog = createPromptCatalog<DeckSystemTemplateVars>({ |
| 66 | system: deckSystemTemplate.trimEnd() |
| 67 | }) |
| 68 | |
| 69 | export function buildDeckAgentSystemPrompt( |
| 70 | styleId: string | null | undefined, |
| 71 | context: SessionDeckGenerationContext |
| 72 | ): string { |
| 73 | void styleId |
| 74 | const { presetLabel, presetId, stylePrompt } = resolveContextStylePrompt(context) |
| 75 | const pageList = buildOutlinePageList(context) |
| 76 | const statusLanguage = context.appLocale === 'en' ? 'English' : 'Simplified Chinese' |
| 77 | |
| 78 | const targetInfo = context.selectedPageId |
| 79 | ? `This run may only modify: ${context.selectedPageId}` |
| 80 | : context.selectPageIds?.length |
| 81 | ? `This run may only modify selected pages: ${context.selectPageIds.join(', ')}` |
| 82 | : 'This run may modify all pages.' |
| 83 | const targetPagePath = |
| 84 | context.selectedPageId && context.pageFileMap[context.selectedPageId] |
| 85 | ? `/${context.selectedPageId}.html` |
| 86 | : undefined |
| 87 | const isSinglePageTask = |
| 88 | context.mode !== 'edit' && |
| 89 | (Boolean(context.selectedPageId) || |
| 90 | (Array.isArray(context.selectPageIds) && context.selectPageIds.length === 1) || |
| 91 | (Array.isArray(context.allowedPageIds) && context.allowedPageIds.length === 1) || |
| 92 | context.outlineTitles.length === 1) |
| 93 | const isSectionAgendaSinglePageTask = |
| 94 | isSinglePageTask && |
| 95 | context.outlineItems.length === 1 && |
| 96 | isSectionAgendaOutline(context.outlineItems[0]?.contentOutline || '') |
| 97 | const isTemplateGeneration = context.templatePageReadRequired === true |
| 98 | const singlePageWriteToolName = isTemplateGeneration |
| 99 | ? 'update_template_page_file' |
| 100 | : 'update_single_page_file' |
| 101 | const step3Instruction = isSinglePageTask |
| 102 | ? context.templatePageReadRequired |
| 103 | ? '3. Required: after reading the target template page with read_file, call update_template_page_file(pageId=target page, content). A final text response without the read_file + update_template_page_file sequence is a failed generation.' |
| 104 | : '3. Required: call update_single_page_file(pageId=target page, content). A final text response without this tool call is a failed generation.' |
| 105 | : '3. Call update_page_file(content) page by page. For multi-page generation, write each target page file in order. You may pass pageId to override automatic targeting.' |
| 106 | const sourceDocumentPaths = isSectionAgendaSinglePageTask |
| 107 | ? [] |
| 108 | : (context.sourceDocumentPaths || []).filter(Boolean) |
| 109 | const isRetryMode = context.mode === 'retry' |
| 110 | const animationPreferencePrompt = formatAnimationPreferencesForPageWriting( |
| 111 | context.animationPreferences |
| 112 | ) |
| 113 | const canvasScenario = resolveCanvasScenario(context.slideSize) |
| 114 | const sourceDocumentInstructions = |
| 115 | sourceDocumentPaths.length > 0 |
| 116 | ? `\n\n${[ |
| 117 | '## Source documents (highest-priority content evidence)', |
| 118 | 'This session comes from user-uploaded documents. Generated content must prioritize source-document facts; do not rely only on the summary or page outline.', |
| 119 | 'Single-page prompts may include program-side retrieved snippets.', |
| 120 | SOURCE_DOCUMENT_READ_STRATEGY, |
| 121 | 'If snippets are insufficient, conflicting, or missing key facts, follow the source-reading skill against these source documents:', |
| 122 | ...sourceDocumentPaths.map((docPath) => `- ${docPath}`), |
| 123 | SOURCE_DOCUMENT_FACT_RULE, |
| 124 | SOURCE_GROUNDED_EXPANSION_RULES, |
| 125 | isRetryMode |
| 126 | ? '- This is a failed-slide retry. Match source material only around the failed slide title and outline; do not reconstruct the whole deck outline.' |
| 127 | : "- This is initial page generation. Follow the established page outline slide by slide; do not prematurely insert other slides' material.", |
| 128 | 'If the source document conflicts with additional user requirements, follow the user requirements. If the page outline conflicts with source details, follow source-document facts.' |
| 129 | ].join('\n')}` |
| 130 | : '' |
| 131 | const templateOrCreativeInstructions = isTemplateGeneration |
| 132 | ? [ |
| 133 | '## 模板还原优先', |
| 134 | '- 当前是模板生成,不追求重新设计、视觉惊喜或主动变化。', |
| 135 | '- 每页先继承目标模板页的页面角色、版式骨架、背景/装饰层、留白节奏、字体尺度、组件形状和配色关系。', |
| 136 | '- 只替换旧业务内容:标题、正文、指标、图表数据、案例、结论和与新主题冲突的内容素材。', |
| 137 | '- 为适配新内容可以做局部微调,但不能把模板页改成另一套构图或另一套视觉系统。', |
| 138 | '- 如果内容放不下,优先压缩文案/合并模块,不要通过新增大量卡片或重排整页来破坏模板。' |
| 139 | ].join('\n') |
| 140 | : [ |
| 141 | '## 创意变化', |
| 142 | '- 在统一风格内制造每页的视觉惊喜:变化主视觉位置、标题进入方式、信息节奏、留白比例或局部装饰语言。', |
| 143 | '- 每页至少有一个清晰的视觉焦点,可以是关键数字、图表、概念符号、时间节点或一句核心判断。', |
| 144 | '- 惊喜感服务于内容理解;不要为了变化加入无关装饰、复杂嵌套、遮挡文字或难以维护的结构。', |
| 145 | `- 同一套 ${canvasScenario.sequenceName} 内避免连续页面使用完全相同的标题位置、卡片网格和背景分区。` |
| 146 | ].join('\n') |
| 147 | const templateAssetGuards = isTemplateGeneration |
| 148 | ? [ |
| 149 | '- In template generation, dropping inspected background images, decorative layers, CSS url(...) references, masks, overlays, or the containers that render them is a failed generation unless the user explicitly requested removal.', |
| 150 | '- Because page write tools rebuild the slide from your submitted fragment, include the required template background/decorative layers or exact local asset references inside that fragment.', |
| 151 | '' |
| 152 | ].join('\n') |
| 153 | : '' |
| 154 | const pageWriteConstraint = isSinglePageTask |
| 155 | ? `- 不要调用 edit_file / write_file / update_page_file${ |
| 156 | isTemplateGeneration ? ' / update_single_page_file' : '' |
| 157 | };单页任务必须调用 ${singlePageWriteToolName}(pageId, content) 并成功落盘后才能最终回复` |
| 158 | : '- 不要调用 edit_file / write_file 直接覆盖页面文件,统一用 update_page_file(content)' |
| 159 | const executionFlow = isSinglePageTask |
| 160 | ? context.templatePageReadRequired |
| 161 | ? [ |
| 162 | `1. Mandatory first action: call read_file(path="${targetPagePath || '/<pageId>.html'}", offset=0, limit=1200) to inspect the copied template page before writing.`, |
| 163 | '2. Preserve the inspected page visual system: background images, texture images, decorative assets, masks, overlays, CSS background-image/url(...) references, <img src>, SVG image href, font scale, spacing rhythm, color language, and structural wrappers unless the user explicitly asks to remove them.', |
| 164 | ' Background/decorative assets are template skeleton, not stale business content; replacing facts and text must not remove the visual shell.', |
| 165 | ` The content fragment you pass to ${singlePageWriteToolName} must explicitly carry those required layers or exact local asset references.`, |
| 166 | sourceDocumentPaths.length > 0 |
| 167 | ? `3. Required before writing: follow the source-reading skill for targeted source inspection (${sourceDocumentPaths.join(', ')}).` |
| 168 | : '3. Analyze the new slide content requirements from the context provided.', |
| 169 | step3Instruction, |
| 170 | '4. Send a short summary as your final response.' |
| 171 | ].join('\n') |
| 172 | : [ |
| 173 | sourceDocumentPaths.length > 0 |
| 174 | ? `1. Required before writing: follow the source-reading skill for targeted source inspection (${sourceDocumentPaths.join(', ')}).` |
| 175 | : '1. Analyze the slide requirements from the context provided.', |
| 176 | step3Instruction, |
| 177 | '3. Send a short summary as your final response.' |
| 178 | ].join('\n') |
| 179 | : [ |
| 180 | '1. get_session_context — read the session context and constraints', |
| 181 | sourceDocumentPaths.length > 0 |
| 182 | ? `2. Use retrieved source-document snippets as an index, follow the source-reading skill for targeted source inspection (${sourceDocumentPaths.join(', ')}), then call report_generation_status('Analyzing request', ...)` |
| 183 | : "2. report_generation_status('Analyzing request', ...) — report start", |
| 184 | ` report_generation_status labels and details must be written in ${statusLanguage}, because they are application UI logs.`, |
| 185 | ' This status/log language is independent from deck content language. Deck content must still follow the Content language rules.', |
| 186 | ' progress must be a numeric literal such as 10, 35, or 88. Do not pass strings such as "10".', |
| 187 | ' Progress must be detailed and monotonic. Suggested ranges: Analyzing request (8-18) / Reading context (18-30) / Writing pages (30-88, linear by page) / Verifying (88-96) / Completed (98-100).', |
| 188 | ' Report once for each major action so the UI does not stay silent for too long.', |
| 189 | step3Instruction, |
| 190 | '4. verify_completion() — check whether target pages are filled', |
| 191 | "5. If pages are still empty, continue filling them, then report_generation_status('Generation completed', ...)" |
| 192 | ].join('\n') |
| 193 | return deckSystemPromptCatalog.render('system', { |
| 194 | pageWriteRequirement: isTemplateGeneration |
| 195 | ? 'You MUST call update_template_page_file to write the current template page.' |
| 196 | : 'You MUST call update_single_page_file (single-page) or update_page_file (multi-page) to write every page.', |
| 197 | canvasIdentity: canvasScenario.identity, |
| 198 | pageName: canvasScenario.pageName, |
| 199 | canvasScenarioBrief: buildCanvasScenarioBrief(context.slideSize), |
| 200 | canvasScenarioContentRules: buildCanvasScenarioContentRules(context.slideSize), |
| 201 | contentLanguageRules: CONTENT_LANGUAGE_RULES, |
| 202 | templateOrCreativeInstructions, |
| 203 | sourceDocumentInstructions, |
| 204 | canvasConstraints: buildCanvasConstraints(context.slideSize), |
| 205 | layoutCollisionRules: buildLayoutCollisionRules(context.slideSize), |
| 206 | canvasScenarioDeliveryGuard: buildCanvasScenarioDeliveryGuard(context.slideSize), |
| 207 | pageSemanticStructure: buildPageSemanticStructure(context.slideSize), |
| 208 | canvasScenarioExpansionRules: buildCanvasScenarioExpansionRules(context.slideSize), |
| 209 | frontendCapabilities: FRONTEND_CAPABILITIES, |
| 210 | animationPreferencePromptWithSpacing: animationPreferencePrompt |
| 211 | ? `${animationPreferencePrompt}\n\n` |
| 212 | : '', |
| 213 | contentWritingRules: CONTENT_WRITING_RULES, |
| 214 | stableHtmlFragmentProtocol: STABLE_HTML_FRAGMENT_PROTOCOL, |
| 215 | templateAssetGuards, |
| 216 | pageWriteConstraint, |
| 217 | executionFlow, |
| 218 | topic: context.topic, |
| 219 | deckTitle: context.deckTitle, |
| 220 | slideCount: context.outlineTitles.length, |
| 221 | targetInfo, |
| 222 | targetFileLine: targetPagePath ? `Target file: ${targetPagePath}` : '', |
| 223 | pageList, |
| 224 | presetLabel, |
| 225 | presetId, |
| 226 | stylePrompt, |
| 227 | designContract: formatDesignContract(context.designContract), |
| 228 | styleFidelityRules: STYLE_FIDELITY_RULES, |
| 229 | finalWriteToolName: isTemplateGeneration |
| 230 | ? 'update_template_page_file' |
| 231 | : 'update_single_page_file (or update_page_file)' |
| 232 | }) |
| 233 | } |
| 234 |