Add Prompt Builder sequence section order.

Copied scene text and the scene editor follow a persisted sectionOrder, with legacy saves keeping the default style/settings/set/characters/prompt order.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-10-04 07:07:43 -05:00
co-authored by Cursor
parent b6eef6a511
commit 45e97cc1c3
6 changed files with 1113 additions and 878 deletions
+45 -9
View File
@@ -50,9 +50,18 @@ export type PromptSequenceScene = {
prompt: string
}
/** Scene prompt sections. Default copy order. */
export const PROMPT_SEQUENCE_SECTION_KEYS = ['style', 'settings', 'set', 'characters', 'prompt'] as const
export type PromptSequenceSectionKey = (typeof PROMPT_SEQUENCE_SECTION_KEYS)[number]
export const DEFAULT_PROMPT_SEQUENCE_SECTION_ORDER: PromptSequenceSectionKey[] = [
...PROMPT_SEQUENCE_SECTION_KEYS
]
export type PromptSequence = {
id: string
name: string
/** Order of scene sections when assembling / in the editor. */
sectionOrder: PromptSequenceSectionKey[]
characters: PromptSequenceCharacter[]
sets: PromptSequenceSet[]
scenes: PromptSequenceScene[]
@@ -60,6 +69,26 @@ export type PromptSequence = {
updatedAt: number
}
/** Valid unique keys in given order; missing keys append in default order. */
export function normalizeSectionOrder(raw: unknown): PromptSequenceSectionKey[] {
const allowed = new Set<string>(PROMPT_SEQUENCE_SECTION_KEYS)
const seen = new Set<PromptSequenceSectionKey>()
const ordered: PromptSequenceSectionKey[] = []
if (Array.isArray(raw)) {
for (const item of raw) {
const key = String(item || '').trim()
if (!allowed.has(key) || seen.has(key as PromptSequenceSectionKey)) continue
const typed = key as PromptSequenceSectionKey
seen.add(typed)
ordered.push(typed)
}
}
for (const key of DEFAULT_PROMPT_SEQUENCE_SECTION_ORDER) {
if (!seen.has(key)) ordered.push(key)
}
return ordered
}
export const PROMPT_SEQUENCE_NAME_MAX = 80
export const PROMPT_SEQUENCE_FIELD_MAX = 50_000
export const PROMPT_SEQUENCE_TITLE_MAX = 60
@@ -206,7 +235,8 @@ export function assembleSceneText(
characters?: Array<{ name?: string; description?: string }>
},
sequenceCharacters?: PromptSequenceCharacter[],
sequenceSets?: PromptSequenceSet[]
sequenceSets?: PromptSequenceSet[],
sectionOrder?: PromptSequenceSectionKey[] | unknown
) {
const setBlock = sequenceSets?.length
? assembleSetBlock(scene, sequenceSets)
@@ -217,13 +247,15 @@ export function assembleSceneText(
.map(character => formatCharacterLine(character))
.filter(Boolean)
.join('\n')
const parts = [
String(scene.style || '').trim(),
String(scene.settings || '').trim(),
setBlock,
characterBlock,
String(scene.prompt || '').trim()
].filter(Boolean)
const blocks: Record<PromptSequenceSectionKey, string> = {
style: String(scene.style || '').trim(),
settings: String(scene.settings || '').trim(),
set: setBlock,
characters: characterBlock,
prompt: String(scene.prompt || '').trim()
}
const order = normalizeSectionOrder(sectionOrder)
const parts = order.map(key => blocks[key]).filter(Boolean)
return parts.join('\n\n')
}
@@ -233,6 +265,7 @@ export function assembleSceneText(
* Sequence name is never included.
*/
export function assembleSequenceText(sequence: {
sectionOrder?: PromptSequenceSectionKey[] | unknown
characters?: PromptSequenceCharacter[]
sets?: PromptSequenceSet[]
scenes?: Array<{
@@ -246,11 +279,13 @@ export function assembleSequenceText(sequence: {
}) {
const characters = sequence.characters || []
const sets = sequence.sets || []
const sectionOrder = normalizeSectionOrder(sequence.sectionOrder)
return (sequence.scenes || [])
.map(scene => assembleSceneText(
scene,
characters.length ? characters : undefined,
sets.length ? sets : undefined
sets.length ? sets : undefined,
sectionOrder
))
.filter(Boolean)
.join('\n\n\n')
@@ -478,6 +513,7 @@ export function normalizePromptSequence(raw: unknown): PromptSequence | null {
return {
id: String(rec.id || '').trim() || newId(),
name,
sectionOrder: normalizeSectionOrder(rec.sectionOrder),
characters,
sets,
scenes,