返回 AiToEarn
promptLimits.ts
根目录 / project / aitoearn-web / src / components / draft-box / utils / promptLimits.ts
1 const DRAFT_PROMPT_LIMIT_PREFIX = '重要:'
2 const DRAFT_PROMPT_LIMIT_SEPARATOR = ','
3
4 export const DRAFT_TITLE_NO_EMOJI_LIMIT = '标题一定不要加表情。'
5
6 interface DraftPromptLimitTextOptions {
7 prefix?: string
8 separator?: string
9 }
10
11 interface CaptionPromptPriorityOptions {
12 userRequirementTitle: string
13 systemRequirementTitle: string
14 }
15
16 interface StripCaptionPromptSystemRequirementOptions {
17 userRequirementTitle?: string
18 systemRequirementTitle?: string
19 systemRequirementPrefix?: string
20 }
21
22 const DRAFT_PROMPT_LIMIT_PATTERNS = [
23 /^标题字数限制:.+$/,
24 /^描述字数限制:.+$/,
25 /^话题数量限制:.+$/,
26 /^标题字数不超过.+$/,
27 /^描述字数不超过.+$/,
28 /^话题数量不超过.+$/,
29 /^标题一定不要加表情。?$/,
30 ]
31
32 const CAPTION_PROMPT_SYSTEM_REQUIREMENT_PREFIXES = ['重要:', 'Important:', 'Important :', 'Wichtig:', '중요:']
33 const CAPTION_PROMPT_USER_SECTION_TITLE_PATTERNS = [
34 /用户文案要求/,
35 /User Caption Requirements/i,
36 /Benutzeranforderungen/i,
37 /Exigences utilisateur/i,
38 /ユーザー文案要件/,
39 /사용자 문안 요구사항/,
40 ]
41 const CAPTION_PROMPT_SYSTEM_SECTION_TITLE_PATTERNS = [
42 /默认提示词/,
43 /Default Prompt/i,
44 /Standard-Prompt/i,
45 /Prompt par défaut/i,
46 /デフォルトプロンプト/,
47 /기본 프롬프트/,
48 ]
49 const CAPTION_PROMPT_SECTION_TITLE_WRAPPER_PATTERN = /^(?:【.+】|\[.+\])$/
50 const CAPTION_PROMPT_SYSTEM_LIMIT_PATTERNS = [
51 /标题字数/,
52 /描述字数/,
53 /话题数量/,
54 /标题一定不要加表情/,
55 /Title must be no more/i,
56 /Description must be no more/i,
57 /Use no more than .* topics/i,
58 /Do not use emoji in the title/i,
59 /Der Titel darf höchstens/i,
60 /Die Beschreibung darf höchstens/i,
61 /höchstens .* Themen/i,
62 /Keine Emojis im Titel/i,
63 /Le titre ne doit pas dépasser/i,
64 /La description ne doit pas dépasser/i,
65 /au maximum .* sujets/i,
66 /emoji dans le titre/i,
67 /タイトルは.*文字以内/,
68 /説明は.*文字以内/,
69 /トピックは.*個以内/,
70 /タイトルに絵文字/,
71 /제목은 .*자 이내/,
72 /설명은 .*자 이내/,
73 /주제는 최대 .*개/,
74 /제목에는 이모지/,
75 ]
76
77 function getLastDraftPromptLimitMarkerIndex(prompt: string) {
78 return Math.max(
79 prompt.lastIndexOf(`\n\n${DRAFT_PROMPT_LIMIT_PREFIX}`),
80 prompt.lastIndexOf(`\r\n\r\n${DRAFT_PROMPT_LIMIT_PREFIX}`),
81 )
82 }
83
84 function getDraftPromptLimitMarkerLength(prompt: string, markerIndex: number) {
85 return prompt.startsWith(`\r\n\r\n${DRAFT_PROMPT_LIMIT_PREFIX}`, markerIndex)
86 ? `\r\n\r\n${DRAFT_PROMPT_LIMIT_PREFIX}`.length
87 : `\n\n${DRAFT_PROMPT_LIMIT_PREFIX}`.length
88 }
89
90 function isDraftPromptLimit(limit: string) {
91 return DRAFT_PROMPT_LIMIT_PATTERNS.some(pattern => pattern.test(limit))
92 }
93
94 function normalizePromptLineBreaks(prompt: string) {
95 return prompt.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
96 }
97
98 function isCaptionPromptSectionTitle(line: string, patterns: RegExp[]) {
99 const title = line.trim()
100 return CAPTION_PROMPT_SECTION_TITLE_WRAPPER_PATTERN.test(title)
101 && patterns.some(pattern => pattern.test(title))
102 }
103
104 function getFirstBlankLineMatch(prompt: string) {
105 return /\n\s*\n/.exec(prompt)
106 }
107
108 function getCaptionPromptSectionText(lines: string[], startIndex: number, endIndex: number) {
109 return lines.slice(startIndex + 1, endIndex).join('\n').trim()
110 }
111
112 function isSystemRequirementBlock(prompt: string, prefixes: string[]) {
113 if (!prefixes.some(prefix => prompt.startsWith(prefix)))
114 return false
115
116 return CAPTION_PROMPT_SYSTEM_LIMIT_PATTERNS.reduce((count, pattern) => (
117 pattern.test(prompt) ? count + 1 : count
118 ), 0) >= 2
119 }
120
121 function stripLeadingSystemRequirementBlock(prompt: string, prefixes: string[]) {
122 const blankLineMatch = getFirstBlankLineMatch(prompt)
123 if (!blankLineMatch)
124 return isSystemRequirementBlock(prompt, prefixes) ? '' : null
125
126 const firstBlock = prompt.slice(0, blankLineMatch.index).trim()
127 if (!isSystemRequirementBlock(firstBlock, prefixes))
128 return null
129
130 return prompt.slice(blankLineMatch.index + blankLineMatch[0].length).trim()
131 }
132
133 export function buildDraftPromptLimitText(limits: string[], options: DraftPromptLimitTextOptions = {}) {
134 const normalizedLimits = limits.map(limit => limit.trim()).filter(Boolean)
135 if (normalizedLimits.length === 0)
136 return ''
137
138 const prefix = options.prefix ?? DRAFT_PROMPT_LIMIT_PREFIX
139 const separator = options.separator ?? DRAFT_PROMPT_LIMIT_SEPARATOR
140 return `${prefix}${normalizedLimits.join(separator)}`
141 }
142
143 export function buildPromptWithDraftLimits(
144 basePrompt: string,
145 limits: string[],
146 options: DraftPromptLimitTextOptions = {},
147 ) {
148 const limitText = buildDraftPromptLimitText(limits, options)
149 if (!limitText)
150 return basePrompt
151
152 return `${basePrompt}\n\n${limitText}`
153 }
154
155 export function mergeCaptionPromptWithSystemRequirement(
156 captionPrompt: string,
157 systemRequirement: string,
158 options?: CaptionPromptPriorityOptions,
159 ) {
160 const normalizedSystemRequirement = systemRequirement.trim()
161 const normalizedCaptionPrompt = captionPrompt.trim()
162
163 if (!normalizedSystemRequirement)
164 return normalizedCaptionPrompt
165
166 if (!normalizedCaptionPrompt)
167 return normalizedSystemRequirement
168
169 if (!options)
170 return `${normalizedSystemRequirement}\n\n${normalizedCaptionPrompt}`
171
172 return [
173 `${options.userRequirementTitle.trim()}\n${normalizedCaptionPrompt}`,
174 `${options.systemRequirementTitle.trim()}\n${normalizedSystemRequirement}`,
175 ].filter(Boolean).join('\n\n')
176 }
177
178 export function stripCaptionPromptSystemRequirement(
179 captionPrompt: string,
180 options: StripCaptionPromptSystemRequirementOptions = {},
181 ) {
182 const normalizedCaptionPrompt = normalizePromptLineBreaks(captionPrompt).trim()
183 if (!normalizedCaptionPrompt)
184 return ''
185
186 const lines = normalizedCaptionPrompt.split('\n')
187 const userRequirementTitle = options.userRequirementTitle?.trim()
188 const systemRequirementTitle = options.systemRequirementTitle?.trim()
189
190 if (userRequirementTitle && systemRequirementTitle) {
191 const userTitleIndex = lines.findIndex(line => line.trim() === userRequirementTitle)
192 const systemTitleIndex = lines.findIndex((line, index) => index > userTitleIndex && line.trim() === systemRequirementTitle)
193 if (userTitleIndex >= 0 && systemTitleIndex > userTitleIndex)
194 return getCaptionPromptSectionText(lines, userTitleIndex, systemTitleIndex)
195
196 if (userTitleIndex >= 0)
197 return lines.slice(userTitleIndex + 1).join('\n').trim()
198 }
199
200 const userTitleIndex = lines.findIndex(line => isCaptionPromptSectionTitle(line, CAPTION_PROMPT_USER_SECTION_TITLE_PATTERNS))
201 const systemTitleIndex = lines.findIndex((line, index) => (
202 index > userTitleIndex && isCaptionPromptSectionTitle(line, CAPTION_PROMPT_SYSTEM_SECTION_TITLE_PATTERNS)
203 ))
204 if (userTitleIndex >= 0 && systemTitleIndex > userTitleIndex)
205 return getCaptionPromptSectionText(lines, userTitleIndex, systemTitleIndex)
206
207 if (userTitleIndex >= 0)
208 return lines.slice(userTitleIndex + 1).join('\n').trim()
209
210 const systemRequirementPrefixes = [
211 options.systemRequirementPrefix?.trim(),
212 ...CAPTION_PROMPT_SYSTEM_REQUIREMENT_PREFIXES,
213 ].filter((prefix): prefix is string => Boolean(prefix))
214
215 const captionPromptWithoutSystemRequirement = stripLeadingSystemRequirementBlock(
216 normalizedCaptionPrompt,
217 systemRequirementPrefixes,
218 )
219 if (captionPromptWithoutSystemRequirement !== null)
220 return captionPromptWithoutSystemRequirement
221
222 return normalizedCaptionPrompt
223 }
224
225 export function stripDraftPromptLimits(prompt: string) {
226 const trimmedPrompt = prompt.trimEnd()
227 const normalizedPrompt = normalizePromptLineBreaks(trimmedPrompt).trim()
228 const promptWithoutLeadingLimits = stripLeadingSystemRequirementBlock(
229 normalizedPrompt,
230 CAPTION_PROMPT_SYSTEM_REQUIREMENT_PREFIXES,
231 )
232 if (promptWithoutLeadingLimits !== null)
233 return promptWithoutLeadingLimits
234
235 const markerIndex = getLastDraftPromptLimitMarkerIndex(trimmedPrompt)
236 if (markerIndex < 0)
237 return prompt
238
239 const markerLength = getDraftPromptLimitMarkerLength(trimmedPrompt, markerIndex)
240 const limits = trimmedPrompt
241 .slice(markerIndex + markerLength)
242 .split(DRAFT_PROMPT_LIMIT_SEPARATOR)
243 .map(limit => limit.trim())
244 .filter(Boolean)
245
246 if (limits.length === 0 || limits.some(limit => !isDraftPromptLimit(limit)))
247 return prompt
248
249 return trimmedPrompt.slice(0, markerIndex)
250 }
251
251 lines TYPESCRIPT