返回 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 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
234 lines TYPESCRIPT