Restore character names on Prompt Builder compile lines.
Character lines again use Name: description with outfit and facing features; set names and framing labels stay uncopied. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -123,7 +123,7 @@
|
||||
maxlength="120"
|
||||
placeholder="Name"
|
||||
>
|
||||
<p class="mt-1 text-[11px] text-zinc-500">Label only — not copied into the prompt.</p>
|
||||
<p class="mt-1 text-[11px] text-zinc-500">Copied as “Name: …” when this character is on.</p>
|
||||
</label>
|
||||
<label class="block text-sm">
|
||||
<span class="mb-1 block text-[11px] text-zinc-500">Base description</span>
|
||||
|
||||
@@ -37,12 +37,15 @@ const {
|
||||
} = await import(pathToFileURL(tmp).href)
|
||||
unlinkSync(tmp)
|
||||
|
||||
test('formatCharacterLine copies base and outfit, never name', () => {
|
||||
assert.equal(formatCharacterLine({ name: 'Ada', description: 'tall' }), 'tall')
|
||||
test('formatCharacterLine copies name, description, and outfit', () => {
|
||||
assert.equal(formatCharacterLine({ name: 'Ada', description: 'tall' }), 'Ada: tall')
|
||||
assert.equal(
|
||||
formatCharacterLine({ name: 'Ada', description: 'tall' }, { name: 'Work', description: 'blue jacket' }),
|
||||
'tall. Outfit: blue jacket'
|
||||
'Ada: tall. Outfit: blue jacket'
|
||||
)
|
||||
assert.equal(formatCharacterLine({ name: '', description: 'tall' }), 'tall')
|
||||
assert.ok(!formatCharacterLine({ name: '', description: 'tall' }).includes(':'))
|
||||
assert.equal(formatCharacterLine({ name: 'Ada', description: '' }), 'Ada')
|
||||
})
|
||||
|
||||
test('stripBangs removes inline ignore spans and cleans commas', () => {
|
||||
@@ -178,8 +181,7 @@ test('legacy sequence without sets still assembles', () => {
|
||||
updatedAt: 1
|
||||
})
|
||||
assert.deepEqual(legacy.sets, [])
|
||||
assert.equal(assembleSequenceText(legacy), 'oil\n\ntall\n\nwalk')
|
||||
assert.ok(!assembleSequenceText(legacy).includes('Ada'))
|
||||
assert.equal(assembleSequenceText(legacy), 'oil\n\nAda: tall\n\nwalk')
|
||||
})
|
||||
|
||||
test('cloneSceneForNext copies setStates including framingId', () => {
|
||||
@@ -208,8 +210,7 @@ test('character outfit and off still work with sets', () => {
|
||||
characterStates: [{ characterId: 'c1', on: true, outfitId: 'o1' }],
|
||||
prompt: 'hi'
|
||||
}, [ada], [])
|
||||
assert.equal(text, 'tall. Outfit: red dress\n\nhi')
|
||||
assert.ok(!text.includes('Ada'))
|
||||
assert.equal(text, 'Ada: tall. Outfit: red dress\n\nhi')
|
||||
})
|
||||
|
||||
test('ensureSceneStatesForCharacters turns new character on only for current scene', () => {
|
||||
@@ -244,7 +245,7 @@ test('sectionOrder settings then prompt then characters; empty style and set omi
|
||||
}
|
||||
const order = ['settings', 'prompt', 'characters']
|
||||
const text = assembleSceneText(scene, [ada], [set], order)
|
||||
assert.equal(text, 'soft light\n\nlooks up\n\ntall')
|
||||
assert.equal(text, 'soft light\n\nlooks up\n\nAda: tall')
|
||||
assert.ok(!text.includes('open yard'))
|
||||
assert.ok(!text.includes('Courtyard'))
|
||||
assert.ok(!text.includes('wide'))
|
||||
@@ -295,11 +296,10 @@ test('legacy sequence without sectionOrder keeps default order and \\n\\n\\n joi
|
||||
const full = assembleSequenceText(legacy)
|
||||
assert.equal(
|
||||
full,
|
||||
'oil\n\nday\n\nstone hall\n\ntall\n\nwalk\n\n\nink\n\ntall\n\nstop'
|
||||
'oil\n\nday\n\nstone hall\n\nAda: tall\n\nwalk\n\n\nink\n\nAda: tall\n\nstop'
|
||||
)
|
||||
assert.ok(!full.includes('Hall'))
|
||||
assert.ok(!full.includes('wide'))
|
||||
assert.ok(!full.includes('Ada'))
|
||||
})
|
||||
|
||||
test('bang strip applies to set framing and character text before join', () => {
|
||||
@@ -335,12 +335,11 @@ test('bang strip applies to set framing and character text before join', () => {
|
||||
characterStates: [{ characterId: 'c1', on: true, outfitId: null }],
|
||||
prompt: ''
|
||||
}, [ada], [set])
|
||||
assert.equal(text, 'courtyard, dusk\n\nyoung woman, linen robe')
|
||||
assert.equal(text, 'courtyard, dusk\n\nAda: young woman, linen robe')
|
||||
assert.ok(!text.includes('brown eyes'))
|
||||
assert.ok(!text.includes('scar'))
|
||||
assert.ok(!text.includes('prop note'))
|
||||
assert.ok(!text.includes('!'))
|
||||
assert.ok(!text.includes('Ada'))
|
||||
assert.ok(!text.includes('Yard'))
|
||||
})
|
||||
|
||||
@@ -375,17 +374,17 @@ test('facing away drops eyes; feature stays omitted with camera reason; out emit
|
||||
}
|
||||
|
||||
const away = compileCharacterForFacing(ada, 'away')
|
||||
assert.equal(away.text, 'young woman, long braid, linen robe')
|
||||
assert.equal(away.text, 'Ada: young woman, long braid, linen robe')
|
||||
assert.equal(away.omittedFeatures.length, 1)
|
||||
assert.equal(away.omittedFeatures[0].text, 'brown eyes')
|
||||
assert.equal(away.omittedFeatures[0].reason, 'camera')
|
||||
|
||||
const toward = compileCharacterForFacing(ada, 'toward')
|
||||
assert.equal(toward.text, 'young woman, brown eyes, linen robe')
|
||||
assert.equal(toward.text, 'Ada: young woman, brown eyes, linen robe')
|
||||
assert.ok(toward.omittedFeatures.some(item => item.text === 'long braid'))
|
||||
|
||||
const hands = compileCharacterForFacing(ada, 'hands')
|
||||
assert.equal(hands.text, 'young woman, linen robe')
|
||||
assert.equal(hands.text, 'Ada: young woman, linen robe')
|
||||
assert.ok(hands.omittedFeatures.some(item => item.text === 'brown eyes'))
|
||||
|
||||
const out = compileCharacterForFacing(ada, 'out')
|
||||
@@ -405,7 +404,35 @@ test('facing away drops eyes; feature stays omitted with camera reason; out emit
|
||||
characterStates: [{ characterId: 'c1', on: true, outfitId: null }],
|
||||
prompt: 'walks'
|
||||
}, [ada], [set])
|
||||
assert.equal(sceneText, 'stone hall\n\nyoung woman, long braid, linen robe\n\nwalks')
|
||||
assert.equal(sceneText, 'stone hall\n\nAda: young woman, long braid, linen robe\n\nwalks')
|
||||
assert.ok(!sceneText.includes('brown eyes'))
|
||||
assert.ok(!sceneText.includes('Hall'))
|
||||
})
|
||||
|
||||
test('Britt line includes name, colon, description, and outfit; nameless has no stray colon', () => {
|
||||
const britt = {
|
||||
...emptyCharacter(),
|
||||
id: 'c-britt',
|
||||
name: 'Britt',
|
||||
description: 'a beautiful 40 year old woman with strawberry blond hair',
|
||||
features: []
|
||||
}
|
||||
const gown = { ...emptyOutfit(), id: 'o1', name: 'gown', description: 'a purple evening gown' }
|
||||
const line = compileCharacterForFacing(britt, 'toward', gown).text
|
||||
assert.equal(
|
||||
line,
|
||||
'Britt: a beautiful 40 year old woman with strawberry blond hair. Outfit: a purple evening gown'
|
||||
)
|
||||
assert.ok(line.includes('Britt'))
|
||||
assert.ok(line.includes(':'))
|
||||
assert.ok(line.includes('strawberry blond hair'))
|
||||
assert.ok(line.includes('Outfit: a purple evening gown'))
|
||||
|
||||
const nameless = compileCharacterForFacing({
|
||||
...emptyCharacter(),
|
||||
name: '',
|
||||
description: 'a figure in shadow'
|
||||
}, 'toward').text
|
||||
assert.equal(nameless, 'a figure in shadow')
|
||||
assert.ok(!nameless.includes(':'))
|
||||
})
|
||||
|
||||
+18
-10
@@ -53,9 +53,9 @@ export type PromptSequenceFeature = {
|
||||
|
||||
export type PromptSequenceCharacter = {
|
||||
id: string
|
||||
/** Label only — never copied into the prompt. */
|
||||
/** Copied into the prompt as "Name: …" when the character is on and facing is not out. */
|
||||
name: string
|
||||
/** Base description; always included when in frame (facing !== out). */
|
||||
/** Base description; included with the name when in frame (facing !== out). */
|
||||
description: string
|
||||
features: PromptSequenceFeature[]
|
||||
outfits: PromptSequenceOutfit[]
|
||||
@@ -283,7 +283,8 @@ export type CompiledCharacterFacing = {
|
||||
|
||||
/**
|
||||
* Compile one character for a camera facing.
|
||||
* Name is never copied. Facing "out" emits nothing.
|
||||
* Name is copied ("Name: description"). Facing "out" emits nothing.
|
||||
* Feature text appends after the description, before the outfit.
|
||||
*/
|
||||
export function compileCharacterForFacing(
|
||||
character: {
|
||||
@@ -318,15 +319,18 @@ export function compileCharacterForFacing(
|
||||
return { text: '', omittedFeatures, ignoredBangs }
|
||||
}
|
||||
|
||||
const parts: string[] = []
|
||||
const base = stripBangs(String(character?.description || ''))
|
||||
if (base) parts.push(base)
|
||||
const name = stripBangs(String(character?.name || ''))
|
||||
const description = stripBangs(String(character?.description || ''))
|
||||
let head = ''
|
||||
if (name && description) head = `${name}: ${description}`
|
||||
else head = name || description
|
||||
|
||||
const featureParts: string[] = []
|
||||
for (const feature of features) {
|
||||
if (!feature?.enabled) continue
|
||||
const cleaned = stripBangs(String(feature.text || ''))
|
||||
if (isFeatureVisibleForFacing(feature, facing)) {
|
||||
if (cleaned) parts.push(cleaned)
|
||||
if (cleaned) featureParts.push(cleaned)
|
||||
} else {
|
||||
omittedFeatures.push({
|
||||
id: String(feature.id || newId()),
|
||||
@@ -336,10 +340,14 @@ export function compileCharacterForFacing(
|
||||
}
|
||||
}
|
||||
|
||||
let text = parts.join(', ')
|
||||
let text = head
|
||||
if (featureParts.length) {
|
||||
const featureText = featureParts.join(', ')
|
||||
text = text ? `${text}, ${featureText}` : featureText
|
||||
}
|
||||
const outfitText = stripBangs(outfitPromptText(outfit))
|
||||
if (outfitText) {
|
||||
text = text ? `${text}. Outfit: ${outfitText}` : outfitText
|
||||
text = text ? `${text}. Outfit: ${outfitText}` : `Outfit: ${outfitText}`
|
||||
}
|
||||
return { text, omittedFeatures, ignoredBangs }
|
||||
}
|
||||
@@ -389,7 +397,7 @@ export function cloneSceneForNext(previous: PromptSequenceScene, nextIndex: numb
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy helper: base description (+ optional outfit). Name is never copied.
|
||||
* Legacy helper for name/description/outfit (+ optional facing features).
|
||||
* Prefer compileCharacterForFacing for feature-aware compile.
|
||||
*/
|
||||
export function formatCharacterLine(
|
||||
|
||||
Reference in New Issue
Block a user