| 1 | import type { LayoutMasterTemplate } from '@shared/layout-master' |
| 2 | import type { DesignContract } from '@shared/generation' |
| 3 | import type { SlideSizePreset } from '@shared/slide-size' |
| 4 | import { |
| 5 | buildCanvasScenarioContentRules, |
| 6 | buildCanvasScenarioDeliveryGuard |
| 7 | } from './canvas-scenario' |
| 8 | import { formatDesignContract } from './shared' |
| 9 | |
| 10 | export const buildGenerationImageIntentRules = (args: { |
| 11 | visualEnabled: boolean |
| 12 | template: LayoutMasterTemplate |
| 13 | hasStyleImageDirection?: boolean |
| 14 | }): string => { |
| 15 | if (!args.visualEnabled) { |
| 16 | return [ |
| 17 | 'Automatic image generation is disabled for this session.', |
| 18 | 'Do not emit data-img-request or any other data-img-* attribute.' |
| 19 | ].join('\n') |
| 20 | } |
| 21 | |
| 22 | if (!args.hasStyleImageDirection) { |
| 23 | return [ |
| 24 | 'Automatic image generation is enabled, but the active style does not declare image-generation direction.', |
| 25 | 'Do not emit image requests for this page. Continue using the style skill for CSS, SVG, and other page-native visual treatment.' |
| 26 | ].join('\n') |
| 27 | } |
| 28 | |
| 29 | const imageSlots = args.template.slots.filter((slot) => slot.role === 'visual' && slot.image) |
| 30 | if (imageSlots.length === 0) { |
| 31 | return [ |
| 32 | 'Automatic image generation is enabled, but this layout has no image-capable visual slot.', |
| 33 | 'Do not emit image requests for this page.' |
| 34 | ].join('\n') |
| 35 | } |
| 36 | |
| 37 | return [ |
| 38 | 'Automatic image generation is enabled for this session and the active style supports it.', |
| 39 | 'Before writing HTML, decide independently whether this page needs one image: request it when the active style skill\'s 配图 strategy matches the page and an identifiable subject, scene, evidence, or atmosphere improves the message. The strategy is a positive signal, not a quota.', |
| 40 | 'Do not request an image merely because the style or layout allows it. For pages led by data, relationships, steps, comparison, timelines, frameworks, or precise conclusions, use page-native CSS, SVG, charts, and diagrams instead.', |
| 41 | 'For one selected image-capable visual slot, add the empty data-img-request attribute to the same rendered element that has data-ppt-slot. Add data-img-placement="background" only when the image must become that slot\'s background; otherwise omit it for a normal visual image. Do not add JSON, scripts, request IDs, image prompts, data-img-slot, data-img-placeholder, or data-img-finalization.', |
| 42 | 'Example: <div data-ppt-slot="metric-visual" data-img-request class="aspect-square ...">...</div>. The system has a dedicated image director that creates the image prompt and derives all technical fields from this layout slot.', |
| 43 | 'Use only one image request per page. Do not reference a generated image path during this first pass. After generation, the system places the local asset directly into this slot without rewriting the rest of the page.', |
| 44 | 'Allowed image slots:', |
| 45 | ...imageSlots.map( |
| 46 | (slot) => |
| 47 | `- ${slot.id}: ${slot.image!.role}, ${slot.image!.policy}, ${slot.image!.aspectHint || 'no aspect hint'}` |
| 48 | ), |
| 49 | 'The dedicated image director receives the active style image direction separately. Do not write or infer an image prompt in this pass.' |
| 50 | ].join('\n') |
| 51 | } |
| 52 | |
| 53 | export const buildGenerationImageLayoutRefinementPrompt = (args: { |
| 54 | pageId: string |
| 55 | referenceRangeBound?: boolean |
| 56 | slideSize?: SlideSizePreset |
| 57 | designContract?: DesignContract |
| 58 | layoutPrompt?: string |
| 59 | assets: Array<{ |
| 60 | slotId: string |
| 61 | layoutSlotId: string |
| 62 | relativePath: string |
| 63 | role: string |
| 64 | prompt?: string |
| 65 | }> |
| 66 | }): string => |
| 67 | [ |
| 68 | 'Refine this complete slide after automatic image placement.', |
| 69 | `First read the current ${args.pageId}.html. It already contains the generated local image in its intended layout slot.`, |
| 70 | `Use edit_file on /${args.pageId}.html for minimal, targeted replacements only. Do not use write_file, update_single_page_file, or update_page_file.`, |
| 71 | 'Review the whole composition at the slide viewport: correct overflow, preserve actual nonzero gaps between independent modules, improve type hierarchy, rebalance the image area against the text, and ensure the visual is cropped and contained deliberately.', |
| 72 | 'Preserve the slide meaning, all required data-ppt-slot attributes, data-block-id attributes, and every listed generated asset path. Do not create new image requests, remove a generated asset, use staging or remote paths, or modify another page.', |
| 73 | 'You may adjust layout, CSS classes, sizing, flex/grid distribution, image object-fit/object-position, text-safe overlays, and local decoration when needed for a balanced final slide.', |
| 74 | args.layoutPrompt |
| 75 | ? `Keep the page's selected layout family as a creative direction, not a fixed grid:\n${args.layoutPrompt}` |
| 76 | : '', |
| 77 | args.designContract |
| 78 | ? `Keep the active deck design contract coherent:\n${formatDesignContract(args.designContract)}` |
| 79 | : '', |
| 80 | args.slideSize |
| 81 | ? [ |
| 82 | 'Keep the image refinement consistent with this canvas scenario:', |
| 83 | buildCanvasScenarioContentRules(args.slideSize, { |
| 84 | referenceTextLocked: Boolean(args.referenceRangeBound) |
| 85 | }), |
| 86 | buildCanvasScenarioDeliveryGuard(args.slideSize, { |
| 87 | referenceTextLocked: Boolean(args.referenceRangeBound) |
| 88 | }) |
| 89 | ].join('\n') |
| 90 | : '', |
| 91 | args.referenceRangeBound |
| 92 | ? 'Reference Range Content Boundary applies: preserve source facts, qualifiers, relationships, uncertainty, and business conclusions. You may adjust layout, styling, and visual hierarchy; do not introduce outside facts or change the source meaning. For density, first clarify hierarchy, group related material, and use compact layout; scale only bounded internal modules as a final measure; never scale the page root, section/page shell, `main[data-role="content"]`, or canvas.' |
| 93 | : '', |
| 94 | 'Generated assets:', |
| 95 | ...args.assets.map( |
| 96 | (asset) => |
| 97 | `- ${asset.relativePath}: slot ${asset.slotId}, layout slot ${asset.layoutSlotId}, role ${asset.role}${asset.prompt ? `, image description: ${asset.prompt}` : ''}` |
| 98 | ) |
| 99 | ].join('\n') |
| 100 |