feat: 同步角色选角与首帧身份约束
This commit is contained in:
@@ -337,7 +337,7 @@ describe('视觉风格与主体身份工作区', () => {
|
||||
expect(wrapper!.text()).toContain('2 个形态')
|
||||
expect(fetcher.mock.calls.some(([url]) => String(url).endsWith('/subjects/subject-db-1/identity'))).toBe(true)
|
||||
expect(fetcher.mock.calls.some(([url]) => String(url).endsWith('/identity/images'))).toBe(false)
|
||||
expect(button('生成身份参考图').disabled).toBe(true)
|
||||
expect(button('生成选角候选').disabled).toBe(true)
|
||||
expect(button('AI 生成身份').disabled).toBe(false)
|
||||
})
|
||||
|
||||
@@ -353,7 +353,7 @@ describe('视觉风格与主体身份工作区', () => {
|
||||
await mountAssets('identity')
|
||||
expect(button('AI 重新生成身份').disabled).toBe(true)
|
||||
expect(button('保存主体身份').disabled).toBe(false)
|
||||
button('生成身份参考图').click()
|
||||
button('生成辅助身份图').click()
|
||||
await flushPromises()
|
||||
expect(button('确认生成身份图').disabled).toBe(true)
|
||||
document.querySelector<HTMLInputElement>('#identity-image-cost')!.click()
|
||||
@@ -362,7 +362,7 @@ describe('视觉风格与主体身份工作区', () => {
|
||||
await flushPromises()
|
||||
const post = fetcher.mock.calls.find(([, init]) => init?.method === 'POST')!
|
||||
expect(post[0]).toBe('/api/subjects/subject-db-1/identity/images')
|
||||
expect(JSON.parse(post[1]!.body as string)).toEqual({ provider: 'seedream', viewType: 'primary' })
|
||||
expect(JSON.parse(post[1]!.body as string)).toEqual({ provider: 'seedream', viewType: 'front' })
|
||||
})
|
||||
|
||||
it('无项目风格时禁止生成身份和图片,但仍可人工保存身份', async () => {
|
||||
@@ -377,7 +377,7 @@ describe('视觉风格与主体身份工作区', () => {
|
||||
)
|
||||
await mountAssets('identity')
|
||||
expect(button('AI 重新生成身份').disabled).toBe(true)
|
||||
expect(button('生成身份参考图').disabled).toBe(true)
|
||||
expect(button('生成选角候选').disabled).toBe(true)
|
||||
expect(button('保存主体身份').disabled).toBe(false)
|
||||
})
|
||||
|
||||
@@ -399,19 +399,21 @@ describe('视觉风格与主体身份工作区', () => {
|
||||
})
|
||||
vi.stubGlobal('fetch', fetcher)
|
||||
await mountAssets('identity')
|
||||
await confirmGeneration('补齐项目身份文本')
|
||||
await confirmGeneration('补齐全部主体身份文本')
|
||||
const post = fetcher.mock.calls.find(([, init]) => init?.method === 'POST')!
|
||||
expect(post[0]).toBe('/api/projects/page-test-project/subject-identities/generate')
|
||||
expect(JSON.parse(post[1]!.body as string)).toEqual({ force: false, concurrency: 3 })
|
||||
expect(wrapper!.text()).toContain('部分主体生成失败')
|
||||
expect(wrapper!.text()).toContain('本次存在部分失败')
|
||||
expect(wrapper!.text()).toContain('含锁定 1')
|
||||
expect(wrapper!.text()).toContain('文本模型超时')
|
||||
})
|
||||
|
||||
it('身份候选母版切换需确认,只发送正确 Subject ID 的 anchor PUT', async () => {
|
||||
let selected = false
|
||||
const form = formFixture()
|
||||
form.subject.module = 'scene'
|
||||
const fetcher = vi.fn<typeof fetch>().mockImplementation(async (url, init) => {
|
||||
if (String(url).endsWith('/subject-forms')) return jsonResponse([formFixture()])
|
||||
if (String(url).endsWith('/subject-forms')) return jsonResponse([form])
|
||||
if (String(url).endsWith('/visual-style')) return jsonResponse(styleFixture())
|
||||
if (init?.method === 'PUT') {
|
||||
selected = true
|
||||
@@ -434,6 +436,63 @@ describe('视觉风格与主体身份工作区', () => {
|
||||
expect(wrapper!.text()).toContain('当前身份母版')
|
||||
})
|
||||
|
||||
it('Character 确认选角原子切换 Anchor 并锁定 Identity,不调用普通母版接口', async () => {
|
||||
let locked = false
|
||||
let selected = false
|
||||
const candidate = identityImageFixture({ id: 'casting-1', enabled: false, isAnchor: false })
|
||||
const fetcher = vi.fn<typeof fetch>().mockImplementation(async (url, init) => {
|
||||
const path = String(url)
|
||||
if (path.endsWith('/subject-forms')) return jsonResponse([formFixture()])
|
||||
if (path.endsWith('/visual-style')) return jsonResponse(styleFixture())
|
||||
if (path.endsWith('/character-casting/readiness'))
|
||||
return jsonResponse({
|
||||
total: 1,
|
||||
ready: locked ? 1 : 0,
|
||||
missingIdentity: 0,
|
||||
missingAnchor: 0,
|
||||
candidatePending: locked ? 0 : 1,
|
||||
unlocked: 0,
|
||||
locked: locked ? 1 : 0,
|
||||
items: [
|
||||
{
|
||||
subjectId: 'subject-db-1',
|
||||
subjectRef: '@CH0001',
|
||||
subjectName: '林知夏',
|
||||
status: locked ? 'ready' : 'candidate_pending',
|
||||
identityId: 'identity-db-1',
|
||||
candidateImages: locked ? undefined : [{ id: 'casting-1', imageUrl: candidate.imageUrl }],
|
||||
anchorImageId: locked ? 'casting-1' : undefined,
|
||||
anchorImageUrl: locked ? candidate.imageUrl : undefined,
|
||||
isLocked: locked
|
||||
}
|
||||
]
|
||||
})
|
||||
if (path.endsWith('/identity/images'))
|
||||
return jsonResponse([{ ...candidate, enabled: selected, isAnchor: selected }])
|
||||
if (init?.method === 'PUT' && path.endsWith('/casting')) {
|
||||
locked = true
|
||||
selected = true
|
||||
return jsonResponse({
|
||||
identity: { ...identityFixture({ isLocked: true }), images: undefined },
|
||||
anchor: { ...candidate, enabled: true }
|
||||
})
|
||||
}
|
||||
return jsonResponse(identityFixture({ isLocked: locked }))
|
||||
})
|
||||
vi.stubGlobal('fetch', fetcher)
|
||||
await mountAssets('identity')
|
||||
expect(wrapper!.text()).toContain('等待确认演员')
|
||||
button('确认选角并锁定').click()
|
||||
await flushPromises()
|
||||
button('确认演员选择').click()
|
||||
await flushPromises()
|
||||
const put = fetcher.mock.calls.find(([, init]) => init?.method === 'PUT')!
|
||||
expect(put[0]).toBe('/api/subjects/subject-db-1/identity/images/casting-1/casting')
|
||||
expect(fetcher.mock.calls.some(([url]) => String(url).endsWith('/anchor'))).toBe(false)
|
||||
expect(wrapper!.text()).toContain('选角状态:选角已完成')
|
||||
expect(wrapper!.text()).toContain('演员身份已确认并锁定')
|
||||
})
|
||||
|
||||
it('切换主体会取消旧查询,迟到的身份结果不覆盖当前主体', async () => {
|
||||
let finish!: (value: Response) => void
|
||||
const form = formFixture()
|
||||
@@ -485,7 +544,7 @@ describe('视觉风格与主体身份工作区', () => {
|
||||
provided.data.value!.project = { ...fixture, status: 'generating' }
|
||||
await flushPromises()
|
||||
expect(button('保存主体身份').disabled).toBe(true)
|
||||
expect(button('生成身份参考图').disabled).toBe(true)
|
||||
expect(button('生成选角候选').disabled).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user