Bind permanence stills to named lock keys instead of coded categories.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -220,7 +220,7 @@ const emptyLabel = computed(() => tab.value === 'input'
|
|||||||
? 'No uploaded stills in this folder yet.'
|
? 'No uploaded stills in this folder yet.'
|
||||||
: 'No generated stills in this folder yet.')
|
: 'No generated stills in this folder yet.')
|
||||||
const pickerHint = computed(() => {
|
const pickerHint = computed(() => {
|
||||||
if (props.stillOnly) return 'Pick a still, then label it Subject / Vehicle / Interior / Object on the strip. That name is what the shot prompt inherits.'
|
if (props.stillOnly) return 'Pick a still, then type a short name on the strip (or pick a GLOBAL LOCKS key). That name is what the shot prompt inherits.'
|
||||||
return tab.value === 'input'
|
return tab.value === 'input'
|
||||||
? 'Uploaded stills only. Generated frames and edits live under Output.'
|
? 'Uploaded stills only. Generated frames and edits live under Output.'
|
||||||
: 'Generated stills from image edits. Video frames stay with their clips.'
|
: 'Generated stills from image edits. Video frames stay with their clips.'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div
|
<div
|
||||||
v-for="(item, index) in refs"
|
v-for="(item, index) in refs"
|
||||||
:key="item.stillId + '-' + index"
|
:key="item.stillId + '-' + index"
|
||||||
class="flex w-14 flex-col items-center gap-0.5"
|
class="flex w-16 flex-col items-center gap-0.5"
|
||||||
>
|
>
|
||||||
<span class="relative h-12 w-12 overflow-hidden rounded-lg bg-zinc-900">
|
<span class="relative h-12 w-12 overflow-hidden rounded-lg bg-zinc-900">
|
||||||
<span v-if="hidden" class="sensitive-glow block h-full w-full" />
|
<span v-if="hidden" class="sensitive-glow block h-full w-full" />
|
||||||
@@ -30,15 +30,21 @@
|
|||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<select
|
<input
|
||||||
class="w-full truncate rounded border border-white/10 bg-zinc-950 px-0.5 py-0 text-[10px] text-zinc-400"
|
class="w-full truncate rounded border border-white/10 bg-zinc-950 px-0.5 py-0 text-center text-[10px] text-zinc-400 outline-none focus:border-amber-300/40"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:value="item.label"
|
:value="item.label"
|
||||||
@change="emit('updateLabel', index, ($event.target as HTMLSelectElement).value)"
|
:list="nameListId"
|
||||||
|
:title="item.label || 'Name'"
|
||||||
|
placeholder="Name"
|
||||||
|
maxlength="24"
|
||||||
|
autocomplete="off"
|
||||||
|
@input="emit('updateLabel', index, sanitizeRefLabel(($event.target as HTMLInputElement).value))"
|
||||||
>
|
>
|
||||||
<option v-for="option in labelOptions(item.label)" :key="option" :value="option">{{ option }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
<datalist :id="nameListId">
|
||||||
|
<option v-for="option in listNames" :key="option" :value="option" />
|
||||||
|
</datalist>
|
||||||
<button
|
<button
|
||||||
v-if="!disabled && refs.length < max"
|
v-if="!disabled && refs.length < max"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -54,10 +60,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PERMANENCE_REF_LABELS, type PermanenceRef } from '~/utils/globalLocks'
|
import { computed, useId } from 'vue'
|
||||||
|
import { sanitizeRefLabel, type PermanenceRef } from '~/utils/globalLocks'
|
||||||
|
|
||||||
withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
refs: PermanenceRef[]
|
refs: PermanenceRef[]
|
||||||
|
nameOptions?: string[]
|
||||||
label?: string
|
label?: string
|
||||||
hint?: string
|
hint?: string
|
||||||
hidden?: boolean
|
hidden?: boolean
|
||||||
@@ -66,8 +74,9 @@ withDefaults(defineProps<{
|
|||||||
addTitle?: string
|
addTitle?: string
|
||||||
cta?: string
|
cta?: string
|
||||||
}>(), {
|
}>(), {
|
||||||
|
nameOptions: () => [],
|
||||||
max: 6,
|
max: 6,
|
||||||
addTitle: 'Choose a still to label',
|
addTitle: 'Choose a still to name',
|
||||||
cta: 'Add still'
|
cta: 'Add still'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -77,8 +86,19 @@ const emit = defineEmits<{
|
|||||||
updateLabel: [index: number, label: string]
|
updateLabel: [index: number, label: string]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
function labelOptions(current: string) {
|
const nameListId = useId()
|
||||||
if (PERMANENCE_REF_LABELS.includes(current as typeof PERMANENCE_REF_LABELS[number])) return [...PERMANENCE_REF_LABELS]
|
|
||||||
return [...PERMANENCE_REF_LABELS, current]
|
const listNames = computed(() => {
|
||||||
|
const seen = new Set<string>()
|
||||||
|
const names: string[] = []
|
||||||
|
for (const name of [...props.nameOptions, ...props.refs.map(item => item.label)]) {
|
||||||
|
const trimmed = String(name || '').trim()
|
||||||
|
if (!trimmed) continue
|
||||||
|
const id = trimmed.toLowerCase()
|
||||||
|
if (seen.has(id)) continue
|
||||||
|
seen.add(id)
|
||||||
|
names.push(trimmed)
|
||||||
}
|
}
|
||||||
|
return names
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+28
-15
@@ -279,18 +279,19 @@
|
|||||||
<div class="rounded-xl border border-white/10 bg-zinc-950/40 px-3 py-2.5">
|
<div class="rounded-xl border border-white/10 bg-zinc-950/40 px-3 py-2.5">
|
||||||
<PermanenceRefStrip
|
<PermanenceRefStrip
|
||||||
:refs="familyPermanenceRefs"
|
:refs="familyPermanenceRefs"
|
||||||
|
:name-options="lockBindingKeys"
|
||||||
label="Permanence refs · all shots"
|
label="Permanence refs · all shots"
|
||||||
hint="Shared stills"
|
hint="Name + still"
|
||||||
cta="Add permanence refs"
|
cta="Add permanence refs"
|
||||||
add-title="Choose a still to label for every shot"
|
add-title="Choose a still to name for every shot"
|
||||||
:hidden="hideInputPreview"
|
:hidden="hideInputPreview"
|
||||||
@add-ref="openPermanencePicker()"
|
@add-ref="openPermanencePicker()"
|
||||||
@remove="removeFamilyRef"
|
@remove="removeFamilyRef"
|
||||||
@update-label="updateFamilyRefLabel"
|
@update-label="updateFamilyRefLabel"
|
||||||
/>
|
/>
|
||||||
<p class="mt-2 text-[11px] leading-relaxed text-zinc-500">
|
<p class="mt-2 text-[11px] leading-relaxed text-zinc-500">
|
||||||
Add stills, then label each Subject / Vehicle / Interior / Object. Those names are injected into locks and
|
Name each still, or pick a GLOBAL LOCKS key. Shot prompts inherit those names.
|
||||||
<span class="text-zinc-300">[INHERITS]</span>, so a shot can say “same Subject, same Vehicle.”
|
The generate start still stays the first frame — attach it here only if you want a named alias.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-[11px] leading-relaxed text-zinc-500">{{ permanenceComfyNote }}</p>
|
<p class="mt-1 text-[11px] leading-relaxed text-zinc-500">{{ permanenceComfyNote }}</p>
|
||||||
<p v-if="familyRefLabelLine" class="mt-1 text-[11px] text-amber-200/80">{{ familyRefLabelLine }}</p>
|
<p v-if="familyRefLabelLine" class="mt-1 text-[11px] text-amber-200/80">{{ familyRefLabelLine }}</p>
|
||||||
@@ -311,10 +312,11 @@
|
|||||||
<PermanenceRefStrip
|
<PermanenceRefStrip
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
:refs="shotPermanenceRefs[shot.n] || []"
|
:refs="shotPermanenceRefs[shot.n] || []"
|
||||||
|
:name-options="lockBindingKeys"
|
||||||
label="This shot only"
|
label="This shot only"
|
||||||
hint="Extra stills"
|
hint="Extra stills"
|
||||||
cta="Add still"
|
cta="Add still"
|
||||||
add-title="Choose a still for this shot only"
|
add-title="Choose a still to name for this shot only"
|
||||||
:hidden="hideInputPreview"
|
:hidden="hideInputPreview"
|
||||||
:max="4"
|
:max="4"
|
||||||
@add-ref="openPermanencePicker(shot.n)"
|
@add-ref="openPermanencePicker(shot.n)"
|
||||||
@@ -352,10 +354,11 @@
|
|||||||
<PermanenceRefStrip
|
<PermanenceRefStrip
|
||||||
class="mt-3"
|
class="mt-3"
|
||||||
:refs="shotPermanenceRefs[index + 2] || []"
|
:refs="shotPermanenceRefs[index + 2] || []"
|
||||||
|
:name-options="lockBindingKeys"
|
||||||
label="This extension only"
|
label="This extension only"
|
||||||
hint="Extra stills"
|
hint="Extra stills"
|
||||||
cta="Add still"
|
cta="Add still"
|
||||||
add-title="Choose a still for this extension only"
|
add-title="Choose a still to name for this extension only"
|
||||||
:hidden="hideInputPreview"
|
:hidden="hideInputPreview"
|
||||||
:max="4"
|
:max="4"
|
||||||
@add-ref="openPermanencePicker(index + 2)"
|
@add-ref="openPermanencePicker(index + 2)"
|
||||||
@@ -1189,6 +1192,7 @@
|
|||||||
v-if="entry.group.parts.length === 1 && !entry.group.head.hideThumbnail"
|
v-if="entry.group.parts.length === 1 && !entry.group.head.hideThumbnail"
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
:refs="entry.group.head.permanenceRefs || []"
|
:refs="entry.group.head.permanenceRefs || []"
|
||||||
|
:name-options="clipBindingKeys(entry.group.head)"
|
||||||
cta="Add still"
|
cta="Add still"
|
||||||
:hidden="entry.group.head.hideInput || hideInputPreview"
|
:hidden="entry.group.head.hideInput || hideInputPreview"
|
||||||
@add-ref="openPermanencePicker(undefined, entry.group.head.id)"
|
@add-ref="openPermanencePicker(undefined, entry.group.head.id)"
|
||||||
@@ -1263,6 +1267,7 @@
|
|||||||
v-if="!item.hideThumbnail"
|
v-if="!item.hideThumbnail"
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
:refs="item.permanenceRefs || []"
|
:refs="item.permanenceRefs || []"
|
||||||
|
:name-options="clipBindingKeys(item, entry.group.head)"
|
||||||
cta="Add still"
|
cta="Add still"
|
||||||
:hidden="item.hideInput || hideInputPreview"
|
:hidden="item.hideInput || hideInputPreview"
|
||||||
@add-ref="openPermanencePicker(undefined, item.id)"
|
@add-ref="openPermanencePicker(undefined, item.id)"
|
||||||
@@ -1537,7 +1542,9 @@ import {
|
|||||||
extractGlobalLocks,
|
extractGlobalLocks,
|
||||||
isBlankGlobalLocks,
|
isBlankGlobalLocks,
|
||||||
nextPermanenceLabel,
|
nextPermanenceLabel,
|
||||||
|
parseLockBindingKeys,
|
||||||
permanenceLabels,
|
permanenceLabels,
|
||||||
|
sanitizeRefLabel,
|
||||||
type PermanenceRef
|
type PermanenceRef
|
||||||
} from '~/utils/globalLocks'
|
} from '~/utils/globalLocks'
|
||||||
import {
|
import {
|
||||||
@@ -1871,12 +1878,13 @@ const shotPermanenceRefs = ref<Record<number, PermanenceRef[]>>({})
|
|||||||
const LOCKS_STORE = 'aigen-global-locks'
|
const LOCKS_STORE = 'aigen-global-locks'
|
||||||
const REFS_STORE = 'aigen-permanence-refs'
|
const REFS_STORE = 'aigen-permanence-refs'
|
||||||
const pickerHeading = computed(() => {
|
const pickerHeading = computed(() => {
|
||||||
if (pickerKind.value === 'permanence') return 'Choose a permanence still'
|
if (pickerKind.value === 'permanence') return 'Choose a still to name'
|
||||||
if (pickerSlot.value === 'main') return studioMode.value === 'edit' ? 'Choose image 1' : 'Choose an image'
|
if (pickerSlot.value === 'main') return studioMode.value === 'edit' ? 'Choose image 1' : 'Choose an image'
|
||||||
if (pickerSlot.value === 'editRef') return 'Choose image 2'
|
if (pickerSlot.value === 'editRef') return 'Choose image 2'
|
||||||
return `Choose Picture ${Number(pickerSlot.value) + 2}`
|
return `Choose Picture ${Number(pickerSlot.value) + 2}`
|
||||||
})
|
})
|
||||||
const permanencePicker = computed(() => pickerKind.value === 'permanence')
|
const permanencePicker = computed(() => pickerKind.value === 'permanence')
|
||||||
|
const lockBindingKeys = computed(() => parseLockBindingKeys(globalLocks.value))
|
||||||
const familyRefLabelLine = computed(() => {
|
const familyRefLabelLine = computed(() => {
|
||||||
const labels = permanenceLabels(familyPermanenceRefs.value)
|
const labels = permanenceLabels(familyPermanenceRefs.value)
|
||||||
if (!labels.length) return ''
|
if (!labels.length) return ''
|
||||||
@@ -1884,9 +1892,9 @@ const familyRefLabelLine = computed(() => {
|
|||||||
})
|
})
|
||||||
const permanenceComfyNote = computed(() => {
|
const permanenceComfyNote = computed(() => {
|
||||||
if (studioMode.value === 'video' && videoWorkflow.value === 'v2' && useIdentityRefs.value) {
|
if (studioMode.value === 'video' && videoWorkflow.value === 'v2' && useIdentityRefs.value) {
|
||||||
return 'Identity-ref is on: Subject stills can map to Picture 2–5 as extra views of the same person only — never a second character or a vehicle.'
|
return 'Identity-ref still only maps extra views of the start-still person, never a second character. Permanence stills name the lock text; they are not Picture 2–5.'
|
||||||
}
|
}
|
||||||
return 'Last-frame I2V: these refs are prompt anchors (and last-frame continuity), not extra MiniMax pictures. Turn on identity-ref only for one person.'
|
return 'Last-frame I2V does not send these stills to MiniMax as extra pictures; they name what the lock text is talking about. Identity-ref still only maps extra views of the start-still person, never a second character.'
|
||||||
})
|
})
|
||||||
let videoTimer: ReturnType<typeof setInterval> | null = null
|
let videoTimer: ReturnType<typeof setInterval> | null = null
|
||||||
let editTimer: ReturnType<typeof setInterval> | null = null
|
let editTimer: ReturnType<typeof setInterval> | null = null
|
||||||
@@ -2928,15 +2936,20 @@ function openPermanencePicker(shot?: number, clipId?: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clipBindingKeys(clip: LibraryClip, fallback?: LibraryClip) {
|
||||||
|
return parseLockBindingKeys(clip.globalLocks || fallback?.globalLocks || '')
|
||||||
|
}
|
||||||
|
|
||||||
function addPermanenceRef(stillId: string, shot?: number) {
|
function addPermanenceRef(stillId: string, shot?: number) {
|
||||||
|
const keys = lockBindingKeys.value
|
||||||
if (typeof shot === 'number') {
|
if (typeof shot === 'number') {
|
||||||
const current = shotPermanenceRefs.value[shot] || []
|
const current = shotPermanenceRefs.value[shot] || []
|
||||||
if (current.some(item => item.stillId === stillId) || current.length >= 4) return
|
if (current.some(item => item.stillId === stillId) || current.length >= 4) return
|
||||||
shotPermanenceRefs.value = { ...shotPermanenceRefs.value, [shot]: [...current, { stillId, label: nextPermanenceLabel(current) }] }
|
shotPermanenceRefs.value = { ...shotPermanenceRefs.value, [shot]: [...current, { stillId, label: nextPermanenceLabel([...familyPermanenceRefs.value, ...current], keys) }] }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (familyPermanenceRefs.value.some(item => item.stillId === stillId) || familyPermanenceRefs.value.length >= 6) return
|
if (familyPermanenceRefs.value.some(item => item.stillId === stillId) || familyPermanenceRefs.value.length >= 6) return
|
||||||
familyPermanenceRefs.value = [...familyPermanenceRefs.value, { stillId, label: nextPermanenceLabel(familyPermanenceRefs.value) }]
|
familyPermanenceRefs.value = [...familyPermanenceRefs.value, { stillId, label: nextPermanenceLabel(familyPermanenceRefs.value, keys) }]
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFamilyRef(index: number) {
|
function removeFamilyRef(index: number) {
|
||||||
@@ -2944,7 +2957,7 @@ function removeFamilyRef(index: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateFamilyRefLabel(index: number, label: string) {
|
function updateFamilyRefLabel(index: number, label: string) {
|
||||||
familyPermanenceRefs.value = familyPermanenceRefs.value.map((item, i) => i === index ? { ...item, label } : item)
|
familyPermanenceRefs.value = familyPermanenceRefs.value.map((item, i) => i === index ? { ...item, label: sanitizeRefLabel(label) } : item)
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeShotRef(shot: number, index: number) {
|
function removeShotRef(shot: number, index: number) {
|
||||||
@@ -2956,7 +2969,7 @@ function removeShotRef(shot: number, index: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateShotRefLabel(shot: number, index: number, label: string) {
|
function updateShotRefLabel(shot: number, index: number, label: string) {
|
||||||
const current = (shotPermanenceRefs.value[shot] || []).map((item, i) => i === index ? { ...item, label } : item)
|
const current = (shotPermanenceRefs.value[shot] || []).map((item, i) => i === index ? { ...item, label: sanitizeRefLabel(label) } : item)
|
||||||
shotPermanenceRefs.value = { ...shotPermanenceRefs.value, [shot]: current }
|
shotPermanenceRefs.value = { ...shotPermanenceRefs.value, [shot]: current }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2976,7 +2989,7 @@ function removeClipRef(clip: LibraryClip, index: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateClipRefLabel(clip: LibraryClip, index: number, label: string) {
|
function updateClipRefLabel(clip: LibraryClip, index: number, label: string) {
|
||||||
void persistClipRefs(clip, (clip.permanenceRefs || []).map((item, i) => i === index ? { ...item, label } : item))
|
void persistClipRefs(clip, (clip.permanenceRefs || []).map((item, i) => i === index ? { ...item, label: sanitizeRefLabel(label) } : item))
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPickedPermanence(still: { id: string }) {
|
function onPickedPermanence(still: { id: string }) {
|
||||||
@@ -2994,7 +3007,7 @@ function onPickedPermanence(still: { id: string }) {
|
|||||||
pickerOpen.value = false
|
pickerOpen.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
void persistClipRefs(clip, [...(clip.permanenceRefs || []), { stillId: still.id, label: nextPermanenceLabel(clip.permanenceRefs || []) }])
|
void persistClipRefs(clip, [...(clip.permanenceRefs || []), { stillId: still.id, label: nextPermanenceLabel(clip.permanenceRefs || [], parseLockBindingKeys(clip.globalLocks || '')) }])
|
||||||
pickerOpen.value = false
|
pickerOpen.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-15
@@ -1,7 +1,3 @@
|
|||||||
export const PERMANENCE_REF_LABELS = ['Subject', 'Vehicle', 'Interior', 'Object'] as const
|
|
||||||
|
|
||||||
export type PermanenceRefLabel = (typeof PERMANENCE_REF_LABELS)[number]
|
|
||||||
|
|
||||||
export interface PermanenceRef {
|
export interface PermanenceRef {
|
||||||
stillId: string
|
stillId: string
|
||||||
label: string
|
label: string
|
||||||
@@ -9,8 +5,6 @@ export interface PermanenceRef {
|
|||||||
|
|
||||||
export const DEFAULT_GLOBAL_LOCKS = `[GLOBAL LOCKS]
|
export const DEFAULT_GLOBAL_LOCKS = `[GLOBAL LOCKS]
|
||||||
Subject:
|
Subject:
|
||||||
Vehicle:
|
|
||||||
Interior:
|
|
||||||
Object:
|
Object:
|
||||||
Extras:
|
Extras:
|
||||||
Camera:
|
Camera:
|
||||||
@@ -18,7 +12,28 @@ Speech:
|
|||||||
Never:
|
Never:
|
||||||
State:`
|
State:`
|
||||||
|
|
||||||
const LOCK_FIELD = /^(Subject|Vehicle|Interior|Object|Extras|Camera|Speech|Never|State)\s*:?\s*/i
|
const KNOWN_LOCK_KEYS = 'Subject|Vehicle|Interior|Object|Extras|Camera|Speech|Never|State'
|
||||||
|
const LOCK_FIELD = new RegExp(`^(?:(?:${KNOWN_LOCK_KEYS})\\s*:?|[A-Za-z][A-Za-z0-9 _-]{0,22}\\s*:)\\s*`, 'i')
|
||||||
|
const LOCK_KEY_LINE = /^([A-Za-z][A-Za-z0-9 _-]{0,22})\s*:/
|
||||||
|
const SKIP_BINDING_KEYS = /^(speech|never|state)$/i
|
||||||
|
|
||||||
|
export function parseLockBindingKeys(text: string): string[] {
|
||||||
|
const keys: string[] = []
|
||||||
|
const seen = new Set<string>()
|
||||||
|
for (const line of String(text || '').replace(/\r\n/g, '\n').split('\n')) {
|
||||||
|
const trimmed = line.trim()
|
||||||
|
if (!trimmed || trimmed.startsWith('[')) continue
|
||||||
|
const match = trimmed.match(LOCK_KEY_LINE)
|
||||||
|
if (!match) continue
|
||||||
|
const key = match[1].replace(/\s+/g, ' ').trim()
|
||||||
|
if (!key || SKIP_BINDING_KEYS.test(key)) continue
|
||||||
|
const id = key.toLowerCase()
|
||||||
|
if (seen.has(id)) continue
|
||||||
|
seen.add(id)
|
||||||
|
keys.push(key)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizePermanenceRefs(raw: unknown, limit = 8): PermanenceRef[] {
|
export function normalizePermanenceRefs(raw: unknown, limit = 8): PermanenceRef[] {
|
||||||
if (!Array.isArray(raw)) return []
|
if (!Array.isArray(raw)) return []
|
||||||
@@ -36,15 +51,12 @@ export function normalizePermanenceRefs(raw: unknown, limit = 8): PermanenceRef[
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeRefLabel(raw: unknown) {
|
export function sanitizeRefLabel(raw: unknown) {
|
||||||
const label = String(raw || '').trim().slice(0, 24)
|
return String(raw || '').replace(/\s+/g, ' ').trim().slice(0, 24)
|
||||||
if (!label) return 'Object'
|
|
||||||
const match = PERMANENCE_REF_LABELS.find(item => item.toLowerCase() === label.toLowerCase())
|
|
||||||
return match || label
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nextPermanenceLabel(existing: PermanenceRef[]) {
|
export function nextPermanenceLabel(existing: PermanenceRef[], lockKeys: string[] = []) {
|
||||||
const used = new Set(existing.map(item => item.label))
|
const used = new Set(existing.map(item => item.label.trim().toLowerCase()).filter(Boolean))
|
||||||
return PERMANENCE_REF_LABELS.find(label => !used.has(label)) || 'Object'
|
return lockKeys.find(key => !used.has(key.trim().toLowerCase())) || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mergePermanenceRefs(family?: PermanenceRef[] | null, shot?: PermanenceRef[] | null) {
|
export function mergePermanenceRefs(family?: PermanenceRef[] | null, shot?: PermanenceRef[] | null) {
|
||||||
@@ -90,7 +102,7 @@ export function resolveGlobalLocks(locks: string, firstPrompt: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractState(locks: string) {
|
function extractState(locks: string) {
|
||||||
const match = String(locks || '').match(/(?:^|\n)\s*State:\s*(.+?)(?=\n\s*(?:Subject|Vehicle|Interior|Object|Extras|Camera|Speech|Never)\s*:|\n\s*\[|$)/is)
|
const match = String(locks || '').match(/(?:^|\n)\s*State:\s*(.+?)(?=\n\s*[A-Za-z][A-Za-z0-9 _-]{0,22}\s*:|\n\s*\[|$)/is)
|
||||||
return String(match?.[1] || '').replace(/\s+/g, ' ').trim()
|
return String(match?.[1] || '').replace(/\s+/g, ' ').trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user