| 1 | import { |
| 2 | CONTENT_LANGUAGE_RULES, |
| 3 | SOURCE_MATERIAL_PLANNING_RULES, |
| 4 | STABLE_HTML_FRAGMENT_PROTOCOL |
| 5 | } from "./shared"; |
| 6 | import type { DeckEditScope, SelectedElementRuntimeContext } from '@shared/generation' |
| 7 | import { INDEX_TRANSITION_TYPES } from "../../../../shared/index-transition"; |
| 8 | import { formatSelectedElementRuntimeContext } from '../selected-element-context' |
| 9 | |
| 10 | const hasSourceMaterialCue = (value: string): boolean => |
| 11 | /参考文档|源文档|参考资料|源资料|解析.*大纲|建议大纲|每页要点|必须保留|reference[-\s]?document|source[-\s]?document|source[-\s]?material|recommended outline|per-page points|facts\/metrics/i.test( |
| 12 | value |
| 13 | ); |
| 14 | |
| 15 | export function buildPlanningUserPrompt(args: { |
| 16 | topic: string; |
| 17 | totalPages: number; |
| 18 | userMessage: string; |
| 19 | hasSourceMaterials?: boolean; |
| 20 | }): string { |
| 21 | const hasExplicitPageHint = /第\s*\d+\s*页|(?:page|slide)\s*\d+/i.test(args.userMessage); |
| 22 | const sourcePlanningRules = args.hasSourceMaterials || hasSourceMaterialCue(args.userMessage) |
| 23 | ? SOURCE_MATERIAL_PLANNING_RULES |
| 24 | : ""; |
| 25 | return [ |
| 26 | `Topic: ${args.topic}`, |
| 27 | `Target slide count: ${args.totalPages}`, |
| 28 | `Return exactly ${args.totalPages} slides, no more and no fewer.`, |
| 29 | hasExplicitPageHint ? "The user provided page/slide hints. Preserve that pagination intent when possible." : "", |
| 30 | "", |
| 31 | "Plan each slide title, key points, and layout intent. Use short phrases, not long paragraphs.", |
| 32 | sourcePlanningRules, |
| 33 | "Output must be a JSON array. Each item must be exactly { title, keyPoints, layoutIntent }; keyPoints must contain 1-10 strings.", |
| 34 | `The array length must be exactly ${args.totalPages}.`, |
| 35 | "User requirements:", |
| 36 | args.userMessage, |
| 37 | ].join("\n"); |
| 38 | } |
| 39 | |
| 40 | export function buildDesignContractUserPrompt(): string { |
| 41 | return [ |
| 42 | "Generate the unified visual contract only from the style specification in the system prompt.", |
| 43 | "Do not use the topic, outline, or user requirements for content planning. The design contract is only for visual consistency.", |
| 44 | "The theme field must describe visual mood, such as calm academic editorial or organic biophilic report. Do not turn it into the deck topic or a slide title.", |
| 45 | ].join("\n"); |
| 46 | } |
| 47 | |
| 48 | export function buildEditUserPrompt(args: { |
| 49 | userMessage: string; |
| 50 | editScope?: DeckEditScope; |
| 51 | selectedPageId?: string; |
| 52 | selectedPageNumber?: number; |
| 53 | selectedSelector?: string; |
| 54 | elementTag?: string; |
| 55 | elementText?: string; |
| 56 | selectedElementContext?: SelectedElementRuntimeContext; |
| 57 | existingPageIds?: string[]; |
| 58 | }): string { |
| 59 | const isContainerScope = args.editScope === "presentation-container"; |
| 60 | const isDeckScope = args.editScope === "deck"; |
| 61 | const selector = args.selectedSelector?.trim(); |
| 62 | |
| 63 | if (isContainerScope) { |
| 64 | return [ |
| 65 | "Apply the following edit instruction only to the presentation container:", |
| 66 | "", |
| 67 | args.userMessage, |
| 68 | "", |
| 69 | CONTENT_LANGUAGE_RULES, |
| 70 | "", |
| 71 | "Edit scope: presentation-container", |
| 72 | "Target file: index.html", |
| 73 | "Do not modify any /<pageId>.html files.", |
| 74 | "Only set page transitions through set_index_transition(type, durationMs).", |
| 75 | `Allowed type values: ${INDEX_TRANSITION_TYPES.join(", ")}. durationMs range: 120-1200, except none uses 0.`, |
| 76 | args.existingPageIds?.length ? `Existing pages: ${args.existingPageIds.join(", ")}` : "", |
| 77 | ].join("\n"); |
| 78 | } |
| 79 | |
| 80 | const elementDesc = |
| 81 | args.elementTag |
| 82 | ? `Target element: <${args.elementTag}>${args.elementText ? `"${args.elementText}"` : ""}` |
| 83 | : ""; |
| 84 | |
| 85 | return [ |
| 86 | isDeckScope |
| 87 | ? "Apply the following edit instruction to the relevant /<pageId>.html files. You may edit multiple pages, but must not modify index.html:" |
| 88 | : "Apply the following edit instruction only to the specified page content. Do not modify other pages:", |
| 89 | "", |
| 90 | args.userMessage, |
| 91 | "", |
| 92 | CONTENT_LANGUAGE_RULES, |
| 93 | "", |
| 94 | args.selectedPageId |
| 95 | ? `Target page: ${args.selectedPageId} (slide ${args.selectedPageNumber ?? "?"})` |
| 96 | : isDeckScope |
| 97 | ? "Target pages: all /<pageId>.html files relevant to the instruction" |
| 98 | : "Target page: all pages", |
| 99 | selector ? `Target element CSS selector: ${selector}` : "", |
| 100 | elementDesc ? `Target element description: ${elementDesc}` : "", |
| 101 | formatSelectedElementRuntimeContext(args.selectedElementContext), |
| 102 | selector || elementDesc |
| 103 | ? "Location and edit protocol:" |
| 104 | : "", |
| 105 | selector || elementDesc |
| 106 | ? "- First use read_file to inspect the target page HTML source." |
| 107 | : "", |
| 108 | selector |
| 109 | ? `- Search the source with grep for key selector parts such as classes or attributes: ${selector}` |
| 110 | : "", |
| 111 | elementDesc |
| 112 | ? `- Search the source with grep for the element text: ${elementDesc}` |
| 113 | : "", |
| 114 | selector || elementDesc |
| 115 | ? "- Use the selector and element text together to confirm the exact target node in the source." |
| 116 | : "", |
| 117 | selector || elementDesc |
| 118 | ? "- Use edit_file(old_string, new_string) to modify the target node's HTML string directly." |
| 119 | : "", |
| 120 | selector || elementDesc |
| 121 | ? "- old_string must be large enough to be unique in the file; new_string should contain only the modified portion." |
| 122 | : "", |
| 123 | selector || elementDesc |
| 124 | ? "- Modify only the target node's text, classes, or local styles. Do not change surrounding structure." |
| 125 | : "", |
| 126 | selector || elementDesc |
| 127 | ? "- Do not rewrite the whole page, drift unrelated styles, or perform broad global replacements." |
| 128 | : "", |
| 129 | !selector && !elementDesc |
| 130 | ? "Edit strategy:" |
| 131 | : "", |
| 132 | !selector && !elementDesc |
| 133 | ? "- If this is a local change, preserve the existing layout and update only the necessary local part." |
| 134 | : "", |
| 135 | !selector && !elementDesc |
| 136 | ? "- If the user asks for relayout/redesign/restructure, you may rewrite the whole page fragment." |
| 137 | : "", |
| 138 | !selector && !elementDesc |
| 139 | ? "- For full-page rewrites, follow the Stable HTML fragment protocol strictly. Do not rebuild the page shell." |
| 140 | : "", |
| 141 | !selector && !elementDesc ? STABLE_HTML_FRAGMENT_PROTOCOL : "", |
| 142 | args.existingPageIds?.length ? `Existing pages: ${args.existingPageIds.join(", ")}` : "", |
| 143 | ].join("\n"); |
| 144 | } |
| 145 |