返回 oh-my-ppt
edit-system.ts
根目录 / src / main / agent-runtime / prompt / composers / edit-system.ts
1 import type { SessionDeckGenerationContext } from '../../agent/types'
2 import { progressText } from '@shared/progress'
3 import { INDEX_TRANSITION_TYPES } from '../../../../shared/index-transition'
4 import {
5 buildCanvasScenarioContentRules,
6 buildCanvasScenarioDeliveryGuard,
7 buildCanvasScenarioExpansionRules,
8 buildLayoutCollisionRules,
9 buildPageSemanticStructure,
10 buildCanvasConstraints,
11 buildContentWritingRules,
12 CONTENT_LANGUAGE_RULES,
13 buildReferenceRangeContentBoundaryRules,
14 FRONTEND_CAPABILITIES,
15 SOURCE_DOCUMENT_FACT_RULE,
16 SOURCE_DOCUMENT_READ_STRATEGY,
17 SOURCE_GROUNDED_EXPANSION_RULES,
18 STABLE_HTML_FRAGMENT_PROTOCOL,
19 STYLE_FIDELITY_RULES,
20 buildOutlinePageList,
21 formatDesignContract,
22 resolveContextStylePrompt
23 } from './shared'
24 import { buildCanvasScenarioBrief, resolveCanvasScenario } from './canvas-scenario'
25 import { createPromptCatalog } from '../catalog'
26 import { formatSelectedElementRuntimeContext } from '../selected-element-context'
27
28 import containerTemplate from '../templates/edit-system/container.md?raw'
29 import deckTemplate from '../templates/edit-system/deck.md?raw'
30 import selectorTemplate from '../templates/edit-system/selector.md?raw'
31 import singlePageTemplate from '../templates/edit-system/single-page.md?raw'
32
33 type EditSystemTemplateVars = {
34 container: {
35 contentLanguageRules: string
36 indexTransitionTypes: string
37 analyzingEditRequestLabel: string
38 editCompletedLabel: string
39 statusLanguage: string
40 presetLabel: string
41 presetId: string
42 stylePrompt: string
43 designContractSection: string
44 sourceDocumentSection: string
45 topic: string
46 deckTitle: string
47 existingInfo: string
48 pageList: string
49 }
50 selector: {
51 contentLanguageRules: string
52 presetLabel: string
53 presetId: string
54 stylePrompt: string
55 designContractSection: string
56 canvasConstraints: string
57 layoutCollisionRules: string
58 canvasScenarioContentRules: string
59 canvasScenarioDeliveryGuard: string
60 pageSemanticStructure: string
61 frontendCapabilities: string
62 sourceDocumentSection: string
63 analyzingEditRequestLabel: string
64 editCompletedLabel: string
65 statusLanguage: string
66 topic: string
67 deckTitle: string
68 targetInfo: string
69 targetFileLine: string
70 selectorInfo: string
71 elementInfo: string
72 elementRuntimeContextInfo: string
73 existingInfo: string
74 pageList: string
75 }
76 singlePage: {
77 canvasEditIdentity: string
78 targetPageId: string
79 canvasScenarioBrief: string
80 canvasScenarioContentRules: string
81 contentLanguageRules: string
82 contentWritingRules: string
83 stableHtmlFragmentProtocol: string
84 canvasScenarioExpansionRules: string
85 canvasConstraints: string
86 layoutCollisionRules: string
87 canvasScenarioDeliveryGuard: string
88 pageSemanticStructure: string
89 frontendCapabilities: string
90 sourceDocumentSection: string
91 analyzingEditRequestLabel: string
92 editCompletedLabel: string
93 statusLanguage: string
94 topic: string
95 deckTitle: string
96 targetInfo: string
97 targetFileLine: string
98 existingInfo: string
99 pageList: string
100 presetLabel: string
101 presetId: string
102 stylePrompt: string
103 designContractSection: string
104 styleFidelityRules: string
105 }
106 deck: {
107 canvasEditIdentity: string
108 canvasScenarioBrief: string
109 canvasScenarioContentRules: string
110 contentLanguageRules: string
111 contentWritingRules: string
112 stableHtmlFragmentProtocol: string
113 canvasScenarioExpansionRules: string
114 canvasConstraints: string
115 layoutCollisionRules: string
116 canvasScenarioDeliveryGuard: string
117 pageSemanticStructure: string
118 frontendCapabilities: string
119 sourceDocumentSection: string
120 analyzingEditRequestLabel: string
121 editCompletedLabel: string
122 statusLanguage: string
123 topic: string
124 deckTitle: string
125 explicitTargetInfo: string
126 existingInfo: string
127 pageList: string
128 presetLabel: string
129 presetId: string
130 stylePrompt: string
131 designContractSection: string
132 styleFidelityRules: string
133 }
134 }
135
136 const editSystemPromptCatalog = createPromptCatalog<EditSystemTemplateVars>({
137 container: containerTemplate.trimEnd(),
138 selector: selectorTemplate.trimEnd(),
139 singlePage: singlePageTemplate.trimEnd(),
140 deck: deckTemplate.trimEnd()
141 })
142
143 const buildOptionalSection = (content: string): string => (content ? `\n\n${content}` : '')
144
145 const buildDesignContractSection = (
146 context: SessionDeckGenerationContext,
147 label: string
148 ): string =>
149 context.designContract
150 ? buildOptionalSection(`${label}\n${formatDesignContract(context.designContract)}`)
151 : ''
152
153 /** Selects the scoped edit prompt; the individual composers own their allowed tools. */
154 export function buildEditAgentSystemPrompt(
155 styleId: string | null | undefined,
156 context: SessionDeckGenerationContext
157 ): string {
158 const isContainerScopeEdit =
159 context.mode === 'edit' && context.editScope === 'presentation-container'
160 const isDeckScopeEdit = context.mode === 'edit' && context.editScope === 'deck'
161 const hasSelector = Boolean(context.selectedSelector?.trim())
162
163 if (isContainerScopeEdit) return buildContainerEditPrompt(styleId, context)
164 if (hasSelector) return buildSelectorEditPrompt(styleId, context)
165 if (isDeckScopeEdit) return buildDeckEditPrompt(styleId, context)
166 return buildSinglePageEditPrompt(styleId, context)
167 }
168
169 function buildSourceDocumentEditInstructions(
170 context: SessionDeckGenerationContext,
171 options?: { includeExpansion?: boolean }
172 ): string {
173 const sourceDocumentPaths = (context.sourceDocumentPaths || []).filter(Boolean)
174 if (sourceDocumentPaths.length === 0) return ''
175 const referenceTextLocked = Boolean(
176 context.referenceDocumentPath && context.pageReferenceContext
177 )
178 if (referenceTextLocked && context.referenceDocumentPath) {
179 return [
180 buildReferenceRangeContentBoundaryRules(
181 context.referenceDocumentPath,
182 context.pageReferenceContext,
183 { sourceReadRequired: 'when-content-changes' }
184 ),
185 '- Any changed content must remain within the page source range and preserve factual relationships, qualifiers, uncertainty, and conclusion strength.'
186 ].join('\n')
187 }
188 const lines = [
189 '## Source documents (content evidence)',
190 'The session has user-imported reference documents. When the edit changes slide facts, examples, metrics, terminology, conclusions, or source-backed page content, use the source document as the authority.',
191 `- sourceDocumentPaths: ${sourceDocumentPaths.join(', ')}`,
192 SOURCE_DOCUMENT_READ_STRATEGY,
193 '- For pure visual/style-only edits, do not reread the source document unless the user asks for source-backed content changes.',
194 SOURCE_DOCUMENT_FACT_RULE
195 ]
196 if (options?.includeExpansion) lines.push(SOURCE_GROUNDED_EXPANSION_RULES)
197 return lines.join('\n')
198 }
199
200 function buildContainerEditPrompt(
201 styleId: string | null | undefined,
202 context: SessionDeckGenerationContext
203 ): string {
204 void styleId
205 const { presetLabel, presetId, stylePrompt } = resolveContextStylePrompt(context)
206 return editSystemPromptCatalog.render('container', {
207 contentLanguageRules: CONTENT_LANGUAGE_RULES,
208 indexTransitionTypes: INDEX_TRANSITION_TYPES.join(' / '),
209 analyzingEditRequestLabel: progressText(context.appLocale, 'understanding'),
210 editCompletedLabel: progressText(context.appLocale, 'completed'),
211 statusLanguage: context.appLocale === 'en' ? 'English' : 'Simplified Chinese',
212 presetLabel,
213 presetId,
214 stylePrompt,
215 designContractSection: buildDesignContractSection(
216 context,
217 '设计契约(本次演示的统一视觉参考):'
218 ),
219 sourceDocumentSection: buildOptionalSection(buildSourceDocumentEditInstructions(context)),
220 topic: context.topic,
221 deckTitle: context.deckTitle,
222 existingInfo: context.existingPageIds?.length
223 ? `Existing page IDs: ${context.existingPageIds.join(', ')}`
224 : '',
225 pageList: buildOutlinePageList(context)
226 })
227 }
228
229 function buildSelectorEditPrompt(
230 styleId: string | null | undefined,
231 context: SessionDeckGenerationContext
232 ): string {
233 void styleId
234 const { presetLabel, presetId, stylePrompt } = resolveContextStylePrompt(context)
235 const targetPagePath =
236 context.selectedPageId && context.pageFileMap[context.selectedPageId]
237 ? `/${context.selectedPageId}.html`
238 : undefined
239 const referenceTextLocked = Boolean(
240 context.referenceDocumentPath && context.pageReferenceContext
241 )
242 return editSystemPromptCatalog.render('selector', {
243 contentLanguageRules: CONTENT_LANGUAGE_RULES,
244 presetLabel,
245 presetId,
246 stylePrompt,
247 designContractSection: buildDesignContractSection(
248 context,
249 '设计契约(本次演示的统一视觉参考,修改时保持协调即可):'
250 ),
251 canvasConstraints: buildCanvasConstraints(context.slideSize, { referenceTextLocked }),
252 layoutCollisionRules: buildLayoutCollisionRules(context.slideSize),
253 canvasScenarioContentRules: buildCanvasScenarioContentRules(context.slideSize, {
254 referenceTextLocked
255 }),
256 canvasScenarioDeliveryGuard: buildCanvasScenarioDeliveryGuard(context.slideSize, {
257 referenceTextLocked
258 }),
259 pageSemanticStructure: buildPageSemanticStructure(context.slideSize),
260 frontendCapabilities: FRONTEND_CAPABILITIES,
261 sourceDocumentSection: buildOptionalSection(buildSourceDocumentEditInstructions(context)),
262 analyzingEditRequestLabel: progressText(context.appLocale, 'understanding'),
263 editCompletedLabel: progressText(context.appLocale, 'completed'),
264 statusLanguage: context.appLocale === 'en' ? 'English' : 'Simplified Chinese',
265 topic: context.topic,
266 deckTitle: context.deckTitle,
267 targetInfo: context.selectedPageId
268 ? `Target page: ${context.selectedPageId} (slide ${context.selectedPageNumber ?? '?'})`
269 : 'Target page: infer from the user message.',
270 targetFileLine: targetPagePath ? `Target file: ${targetPagePath}` : '',
271 selectorInfo: `Target element selector: ${context.selectedSelector}`,
272 elementInfo: context.elementTag
273 ? `Target element: <${context.elementTag}>${context.elementText ? `"${context.elementText}"` : ''}`
274 : '',
275 elementRuntimeContextInfo: formatSelectedElementRuntimeContext(context.selectedElementContext),
276 existingInfo: context.existingPageIds?.length
277 ? `Existing page IDs: ${context.existingPageIds.join(', ')}`
278 : '',
279 pageList: buildOutlinePageList(context)
280 })
281 }
282
283 function buildSinglePageEditPrompt(
284 styleId: string | null | undefined,
285 context: SessionDeckGenerationContext
286 ): string {
287 void styleId
288 const { presetLabel, presetId, stylePrompt } = resolveContextStylePrompt(context)
289 const targetPageId = context.selectedPageId || context.allowedPageIds?.[0] || ''
290 const canvasScenario = resolveCanvasScenario(context.slideSize)
291 const referenceTextLocked = Boolean(
292 context.referenceDocumentPath && context.pageReferenceContext
293 )
294 return editSystemPromptCatalog.render('singlePage', {
295 canvasEditIdentity: canvasScenario.editIdentity,
296 targetPageId,
297 canvasScenarioBrief: buildCanvasScenarioBrief(context.slideSize),
298 canvasScenarioContentRules: buildCanvasScenarioContentRules(context.slideSize, {
299 referenceTextLocked
300 }),
301 contentLanguageRules: CONTENT_LANGUAGE_RULES,
302 contentWritingRules: buildContentWritingRules({ referenceTextLocked }),
303 stableHtmlFragmentProtocol: STABLE_HTML_FRAGMENT_PROTOCOL,
304 canvasScenarioExpansionRules: buildCanvasScenarioExpansionRules(context.slideSize, {
305 referenceTextLocked
306 }),
307 canvasConstraints: buildCanvasConstraints(context.slideSize, { referenceTextLocked }),
308 layoutCollisionRules: buildLayoutCollisionRules(context.slideSize),
309 canvasScenarioDeliveryGuard: buildCanvasScenarioDeliveryGuard(context.slideSize, {
310 referenceTextLocked
311 }),
312 pageSemanticStructure: buildPageSemanticStructure(context.slideSize),
313 frontendCapabilities: FRONTEND_CAPABILITIES,
314 sourceDocumentSection: buildOptionalSection(
315 buildSourceDocumentEditInstructions(context, { includeExpansion: true })
316 ),
317 analyzingEditRequestLabel: progressText(context.appLocale, 'understanding'),
318 editCompletedLabel: progressText(context.appLocale, 'completed'),
319 statusLanguage: context.appLocale === 'en' ? 'English' : 'Simplified Chinese',
320 topic: context.topic,
321 deckTitle: context.deckTitle,
322 targetInfo: `Target page: ${targetPageId} (slide ${context.selectedPageNumber ?? '?'})`,
323 targetFileLine: targetPageId ? `Target file: /${targetPageId}.html` : '',
324 existingInfo: context.existingPageIds?.length
325 ? `Existing page IDs: ${context.existingPageIds.join(', ')}`
326 : '',
327 pageList: buildOutlinePageList(context),
328 presetLabel,
329 presetId,
330 stylePrompt,
331 designContractSection: buildDesignContractSection(
332 context,
333 '设计契约(本次演示的统一视觉参考,修改时保持协调即可):'
334 ),
335 styleFidelityRules: STYLE_FIDELITY_RULES
336 })
337 }
338
339 function buildDeckEditPrompt(
340 styleId: string | null | undefined,
341 context: SessionDeckGenerationContext
342 ): string {
343 void styleId
344 const { presetLabel, presetId, stylePrompt } = resolveContextStylePrompt(context)
345 const canvasScenario = resolveCanvasScenario(context.slideSize)
346 const explicitTargetInfo = context.selectPageIds?.length
347 ? `Selected page ids from UI (hard target): ${context.selectPageIds.join(', ')}`
348 : 'Target pages: all relevant /<pageId>.html files'
349 const referenceTextLocked = Boolean(
350 context.referenceDocumentPath && context.pageReferenceContext
351 )
352 return editSystemPromptCatalog.render('deck', {
353 canvasEditIdentity: canvasScenario.editIdentity,
354 canvasScenarioBrief: buildCanvasScenarioBrief(context.slideSize),
355 canvasScenarioContentRules: buildCanvasScenarioContentRules(context.slideSize, {
356 referenceTextLocked
357 }),
358 contentLanguageRules: CONTENT_LANGUAGE_RULES,
359 contentWritingRules: buildContentWritingRules({ referenceTextLocked }),
360 stableHtmlFragmentProtocol: STABLE_HTML_FRAGMENT_PROTOCOL,
361 canvasScenarioExpansionRules: buildCanvasScenarioExpansionRules(context.slideSize, {
362 referenceTextLocked
363 }),
364 canvasConstraints: buildCanvasConstraints(context.slideSize, { referenceTextLocked }),
365 layoutCollisionRules: buildLayoutCollisionRules(context.slideSize),
366 canvasScenarioDeliveryGuard: buildCanvasScenarioDeliveryGuard(context.slideSize, {
367 referenceTextLocked
368 }),
369 pageSemanticStructure: buildPageSemanticStructure(context.slideSize),
370 frontendCapabilities: FRONTEND_CAPABILITIES,
371 sourceDocumentSection: buildOptionalSection(
372 buildSourceDocumentEditInstructions(context, { includeExpansion: true })
373 ),
374 analyzingEditRequestLabel: progressText(context.appLocale, 'understanding'),
375 editCompletedLabel: progressText(context.appLocale, 'completed'),
376 statusLanguage: context.appLocale === 'en' ? 'English' : 'Simplified Chinese',
377 topic: context.topic,
378 deckTitle: context.deckTitle,
379 explicitTargetInfo,
380 existingInfo: context.existingPageIds?.length
381 ? `Existing page IDs: ${context.existingPageIds.join(', ')}`
382 : '',
383 pageList: buildOutlinePageList(context),
384 presetLabel,
385 presetId,
386 stylePrompt,
387 designContractSection: buildDesignContractSection(
388 context,
389 '设计契约(本次演示的统一视觉参考,修改时保持协调即可):'
390 ),
391 styleFidelityRules: STYLE_FIDELITY_RULES
392 })
393 }
394
394 lines TYPESCRIPT