返回 oh-my-ppt
generation-user.ts
根目录 / src / main / agent-runtime / prompt / composers / generation-user.ts
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
200 lines TYPESCRIPT