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