| 1 | import type { SessionDeckGenerationContext } from '../../agent/types' |
| 2 | import { formatLayoutIntentPrompt } from '@shared/layout-intent' |
| 3 | import { isSectionAgendaOutline } from '@shared/generation' |
| 4 | import { CHART_SKILL_NAME, formatSkillUsageRequirement } from '../../../product-skills/contract' |
| 5 | import { |
| 6 | buildCanvasScenarioContentRules, |
| 7 | buildCanvasScenarioDeliveryGuard, |
| 8 | buildCanvasScenarioExpansionRules, |
| 9 | buildLayoutCollisionRules, |
| 10 | buildPageSemanticStructure, |
| 11 | buildCanvasConstraints, |
| 12 | CONTENT_LANGUAGE_RULES, |
| 13 | FRONTEND_CAPABILITIES, |
| 14 | SOURCE_DOCUMENT_FACT_RULE, |
| 15 | SOURCE_DOCUMENT_LOCATE_THEN_READ_RULE, |
| 16 | SOURCE_DOCUMENT_READ_STRATEGY, |
| 17 | SOURCE_GROUNDED_EXPANSION_RULES, |
| 18 | SOURCE_UNSUPPORTED_CLAIMS, |
| 19 | STABLE_HTML_FRAGMENT_PROTOCOL |
| 20 | } from './shared' |
| 21 | import { buildCanvasScenarioBrief, resolveCanvasScenario } from './canvas-scenario' |
| 22 | |
| 23 | export function buildSinglePageGenerationPrompt(args: { |
| 24 | topic: string |
| 25 | deckTitle: string |
| 26 | pageId: string |
| 27 | pageNumber: number |
| 28 | pageTitle: string |
| 29 | pageOutline: string |
| 30 | slideSize: import('@shared/slide-size').SlideSizePreset |
| 31 | layoutIntent?: SessionDeckGenerationContext['outlineItems'][number]['layoutIntent'] |
| 32 | layoutId?: SessionDeckGenerationContext['outlineItems'][number]['layoutId'] |
| 33 | layoutPrompt?: SessionDeckGenerationContext['outlineItems'][number]['layoutPrompt'] |
| 34 | sourceDocumentPaths?: string[] |
| 35 | referenceDocumentSnippets?: string |
| 36 | isRetryMode?: boolean |
| 37 | writeToolName?: 'update_single_page_file' | 'update_template_page_file' |
| 38 | retryContext?: { |
| 39 | attempt: number |
| 40 | maxRetries: number |
| 41 | previousError: string |
| 42 | } |
| 43 | }): string { |
| 44 | const writeToolName = args.writeToolName || 'update_single_page_file' |
| 45 | const previousError = args.retryContext?.previousError || '' |
| 46 | const shouldMentionChartFix = |
| 47 | /chart|canvas|PPT\.createChart/i.test(previousError) |
| 48 | const shouldMentionWriteToolFix = |
| 49 | /页面未写入|没有成功调用|not written|update_single_page_file|update_template_page_file|占位|placeholder/i.test( |
| 50 | previousError |
| 51 | ) |
| 52 | const shouldMentionTemplateSkeletonFix = |
| 53 | /模板骨架|skeleton|background\/decorative|背景\/装饰资源|CSS url|SVG image|local asset/i.test( |
| 54 | previousError |
| 55 | ) |
| 56 | const isSectionAgendaPage = isSectionAgendaOutline(args.pageOutline || '') |
| 57 | const retryInstructions = args.retryContext |
| 58 | ? [ |
| 59 | '', |
| 60 | 'Retry fixes to prioritize:', |
| 61 | `- This is retry ${args.retryContext.attempt}/${args.retryContext.maxRetries}.`, |
| 62 | `- Previous failure: ${previousError}`, |
| 63 | '- Output only a complete creative page fragment. The write tool will add section/main/content semantics when they are missing. Do not output a full document, page shell, or runtime scripts.', |
| 64 | shouldMentionWriteToolFix |
| 65 | ? `- The previous attempt did not write the target page. You must call ${writeToolName}(pageId="${args.pageId}", content=...) before any final response; do not only describe the HTML in the final response.` |
| 66 | : '', |
| 67 | shouldMentionTemplateSkeletonFix |
| 68 | ? '- The previous attempt dropped template skeleton resources. Reread the target template page, find the missing local asset references from the error, and include the corresponding background/decorative layers in the next write.' |
| 69 | : '', |
| 70 | '- Before calling the write tool, mentally validate that the main containers are closed and that no tag is left unfinished at the end.', |
| 71 | '- If the previous issue was unclosed tags, do not patch the broken fragment. Rewrite a simpler, shallower fragment from scratch: one root div, no page shell (section[data-page-scaffold], main[data-role="content"], or runtime frame), grid/flex direct children, aim for 3 nesting levels and avoid exceeding 4, fewer wrappers, fewer modules.', |
| 72 | '- If the previous issue was page shell structure, do not include .ppt-page-root, .ppt-page-content, .ppt-page-fit-scope, or data-ppt-guard-root anywhere, including CSS selectors, class names, scripts, and comments.', |
| 73 | shouldMentionChartFix |
| 74 | ? `- The previous issue involved chart API usage. Before repairing or writing chart code: ${formatSkillUsageRequirement(CHART_SKILL_NAME)}` |
| 75 | : '' |
| 76 | ].filter(Boolean) |
| 77 | : [] |
| 78 | const sourceDocumentInstructions = |
| 79 | !isSectionAgendaPage && args.sourceDocumentPaths && args.sourceDocumentPaths.length > 0 |
| 80 | ? args.referenceDocumentSnippets && args.referenceDocumentSnippets.trim().length > 0 |
| 81 | ? [ |
| 82 | '', |
| 83 | args.referenceDocumentSnippets.trim(), |
| 84 | '', |
| 85 | 'Source document requirements:', |
| 86 | '- This slide already has program-side retrieved snippets.', |
| 87 | `- Source document paths: ${args.sourceDocumentPaths.join(', ')}`, |
| 88 | SOURCE_DOCUMENT_READ_STRATEGY, |
| 89 | SOURCE_DOCUMENT_FACT_RULE, |
| 90 | SOURCE_GROUNDED_EXPANSION_RULES, |
| 91 | args.isRetryMode |
| 92 | ? '- This is a failed-slide retry. Match source material only around this slide title and content points; do not reconstruct the whole deck outline.' |
| 93 | : '' |
| 94 | ].filter(Boolean) |
| 95 | : [ |
| 96 | '', |
| 97 | 'Source document requirements:', |
| 98 | `- Source document paths: ${args.sourceDocumentPaths.join(', ')}`, |
| 99 | '- No retrieved snippets matched this slide.', |
| 100 | SOURCE_DOCUMENT_LOCATE_THEN_READ_RULE, |
| 101 | '- First extract keywords, business objects, time points, system names, and metrics from this slide title and content points; then match relevant source passages.', |
| 102 | '- Do not copy the whole document indiscriminately.', |
| 103 | SOURCE_DOCUMENT_FACT_RULE, |
| 104 | SOURCE_GROUNDED_EXPANSION_RULES, |
| 105 | args.isRetryMode |
| 106 | ? '- This is a failed-slide retry. Match source material only around this slide title and content points; do not reconstruct the whole deck outline.' |
| 107 | : '' |
| 108 | ].filter(Boolean) |
| 109 | : [] |
| 110 | const hasSourceRange = /Source range:\s*lines\s+\d+\s*-\s*\d+/i.test(args.pageOutline || '') |
| 111 | const canvasScenario = resolveCanvasScenario(args.slideSize) |
| 112 | const sourceRangeInstructions = |
| 113 | !isSectionAgendaPage && args.sourceDocumentPaths && args.sourceDocumentPaths.length > 0 && hasSourceRange |
| 114 | ? [ |
| 115 | '', |
| 116 | 'Range-bound source reading:', |
| 117 | '- Content points include a Source range. Before writing this slide, inspect that source heading/range first through the source-reading skill.', |
| 118 | '- Use retrieved snippets as an index only. If snippets are missing or broad, the Source range remains the primary content boundary.', |
| 119 | '- Do not pull facts from unrelated sections just because they match keywords.' |
| 120 | ] |
| 121 | : [] |
| 122 | const sectionAgendaInstructions = isSectionAgendaPage |
| 123 | ? [ |
| 124 | '', |
| 125 | 'Section agenda page requirements:', |
| 126 | '- This slide is a chapter agenda/table-of-contents page.', |
| 127 | '- Use only the child topic names already listed in Content points.', |
| 128 | '- Do not inspect, retrieve, cite, summarize, or expand from the source document for this slide.', |
| 129 | '- Keep it as a presentation agenda: chapter title plus concise child-topic list.' |
| 130 | ] |
| 131 | : [] |
| 132 | return [ |
| 133 | `Generate and write only this ${canvasScenario.pageName}. Do not modify other pages.`, |
| 134 | '', |
| 135 | buildCanvasScenarioBrief(args.slideSize), |
| 136 | '', |
| 137 | buildCanvasScenarioContentRules(args.slideSize), |
| 138 | '', |
| 139 | `Topic: ${args.topic}`, |
| 140 | `Deck title: ${args.deckTitle}`, |
| 141 | `Target page: ${args.pageId} (slide ${args.pageNumber})`, |
| 142 | `Slide title: ${args.pageTitle}`, |
| 143 | `Content points: ${args.pageOutline || 'Expand from the topic with moderate information density.'}`, |
| 144 | args.layoutIntent ? formatLayoutIntentPrompt(args.layoutIntent) : '', |
| 145 | args.layoutId && args.layoutPrompt ? args.layoutPrompt : '', |
| 146 | ...sectionAgendaInstructions, |
| 147 | ...sourceDocumentInstructions, |
| 148 | ...sourceRangeInstructions, |
| 149 | '', |
| 150 | CONTENT_LANGUAGE_RULES, |
| 151 | '', |
| 152 | buildPageSemanticStructure(args.slideSize), |
| 153 | '', |
| 154 | buildCanvasConstraints(args.slideSize), |
| 155 | '', |
| 156 | buildLayoutCollisionRules(args.slideSize), |
| 157 | '', |
| 158 | buildCanvasScenarioDeliveryGuard(args.slideSize), |
| 159 | '', |
| 160 | FRONTEND_CAPABILITIES, |
| 161 | '', |
| 162 | STABLE_HTML_FRAGMENT_PROTOCOL, |
| 163 | ...retryInstructions, |
| 164 | '', |
| 165 | buildCanvasScenarioExpansionRules(args.slideSize), |
| 166 | '', |
| 167 | 'Required content enrichment decision before writing:', |
| 168 | '- First use the Canvas scenario rules to decide the page form and focal message; then use the scenario expansion rules only to decide whether the content itself needs enrichment or optimization.', |
| 169 | '- If the content points are only a title, one short sentence, or 1-2 seed phrases, the page is thin: enrich the warranted structure before writing the final content.', |
| 170 | '- If the content points already contain enough facts, the page is not thin: group and compress instead of adding more visible modules.', |
| 171 | '- This content decision happens before animation and final HTML; animation is downstream only and must follow the current canvas scenario, source grounding, and warranted content enrichment.', |
| 172 | '', |
| 173 | 'Expansion selection guardrails:', |
| 174 | '- Treat content points as short seed phrases, not as a checklist that must become one visible card/row per point. Decide which points are primary, grouped support, compact annotations, or lower-priority detail based on the slide title, source range, and available space.', |
| 175 | '- When source documents are present, expansion must be source-grounded through SOURCE_GROUNDED_EXPANSION_RULES: if inspected material is thin, enrich the slide from inspected material; if it is dense, summarize and group.', |
| 176 | `- Do not add generic industry framing, unsupported ${SOURCE_UNSUPPORTED_CLAIMS}, or polished-sounding conclusions that are absent from the source document.`, |
| 177 | '- Do not duplicate the same source facts in multiple large modules. If a fact appears in a timeline/table/chart, do not repeat it again as a separate summary card unless it is the single hero message of the slide.', |
| 178 | '- When there are many same-level points, preserve the main meaning by grouping related points and surfacing only the amount that fits a real slide with breathing room.', |
| 179 | '- When the user provided an explicit list of same-level topics, keep them distinct only where the layout allows; otherwise group under shared headings instead of creating equal-weight modules for every item.', |
| 180 | '- Prefer visualization-friendly expression. When points involve trends, comparisons, or proportions, use charts or data cards when appropriate.', |
| 181 | '', |
| 182 | 'Single-slide tool constraints:', |
| 183 | `- Required action: call ${writeToolName}(pageId="${args.pageId}", content=complete creative page fragment).`, |
| 184 | `- This is not optional. A final text response without a successful ${writeToolName} tool call means the slide is not generated.`, |
| 185 | '- Do not call update_page_file. In this single-slide run it is intentionally not available.', |
| 186 | writeToolName === 'update_template_page_file' |
| 187 | ? '- Do not call update_single_page_file. This template run exposes update_template_page_file instead.' |
| 188 | : '', |
| 189 | '- content must be a complete creative page fragment. The tool will wrap it with section[data-page-scaffold], main[data-role="content"], editable data-block-id attributes, and the runtime page frame when needed.', |
| 190 | '- The content must not contain <!doctype>, <html>, <head>, <body>, .ppt-page-root, .ppt-page-content, .ppt-page-fit-scope, or data-ppt-guard-root.', |
| 191 | '- The content must be complete and balanced: close your main layout containers and leave no unfinished trailing tags.', |
| 192 | '- After the tool call succeeds, final response should be a short summary only. Do not paste the HTML in the final response.', |
| 193 | '- Do not modify other pages.', |
| 194 | '', |
| 195 | 'Tool context (pre-injected):', |
| 196 | `- Target file: ${args.pageId}.html (virtual path: /${args.pageId}.html)`, |
| 197 | '- Agent workspace root: /' |
| 198 | ].join('\n') |
| 199 | } |
| 200 |