feat: refine UI, improve UX, optimize the analysis pipeline, and add character standing positions
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { buildInsertPanelLocationsDescription } from '@/lib/novel-promotion/insert-panel-prompt-context'
|
||||
|
||||
describe('insert panel prompt context', () => {
|
||||
it('injects available slots for related selected location images', () => {
|
||||
const text = buildInsertPanelLocationsDescription(
|
||||
[
|
||||
{
|
||||
name: '餐厅',
|
||||
images: [
|
||||
{
|
||||
isSelected: true,
|
||||
description: '长方形饭桌位于画面中央',
|
||||
availableSlots: JSON.stringify([
|
||||
'饭桌左侧靠桌边的位置',
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '客厅',
|
||||
images: [{ isSelected: true, description: '不会被选中' }],
|
||||
},
|
||||
],
|
||||
['餐厅'],
|
||||
)
|
||||
|
||||
expect(text).toContain('餐厅: 长方形饭桌位于画面中央')
|
||||
expect(text).toContain('可站位置:')
|
||||
expect(text).toContain('饭桌左侧靠桌边的位置')
|
||||
expect(text).not.toContain('客厅')
|
||||
})
|
||||
})
|
||||
@@ -17,6 +17,7 @@ vi.mock('@/components/story-input/StoryInputComposer', () => ({
|
||||
default: ({
|
||||
minRows,
|
||||
maxHeightViewportRatio,
|
||||
textareaClassName,
|
||||
topRight,
|
||||
footer,
|
||||
secondaryActions,
|
||||
@@ -24,6 +25,7 @@ vi.mock('@/components/story-input/StoryInputComposer', () => ({
|
||||
}: {
|
||||
minRows: number
|
||||
maxHeightViewportRatio: number
|
||||
textareaClassName?: string
|
||||
topRight?: React.ReactNode
|
||||
footer?: React.ReactNode
|
||||
secondaryActions?: React.ReactNode
|
||||
@@ -33,6 +35,7 @@ vi.mock('@/components/story-input/StoryInputComposer', () => ({
|
||||
{
|
||||
'data-min-rows': String(minRows),
|
||||
'data-max-height-ratio': String(maxHeightViewportRatio),
|
||||
'data-textarea-class': textareaClassName,
|
||||
},
|
||||
topRight,
|
||||
footer,
|
||||
@@ -79,6 +82,7 @@ describe('NovelInputStage', () => {
|
||||
expect(html).toContain('StoryInputComposer')
|
||||
expect(html).toContain('data-min-rows="8"')
|
||||
expect(html).toContain('data-max-height-ratio="0.5"')
|
||||
expect(html).toContain('data-textarea-class="px-0 pt-0 pb-3 align-top"')
|
||||
expect(html).toContain('aiWrite.trigger')
|
||||
expect(html).toContain('AiWriteModal')
|
||||
expect(html).not.toContain('storyInput.wordCount 0')
|
||||
|
||||
81
tests/unit/novel-promotion/stage-readiness.test.ts
Normal file
81
tests/unit/novel-promotion/stage-readiness.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
hasScriptArtifacts,
|
||||
hasStoryboardArtifacts,
|
||||
hasVideoArtifacts,
|
||||
resolveEpisodeStageArtifacts,
|
||||
} from '@/lib/novel-promotion/stage-readiness'
|
||||
|
||||
describe('stage readiness', () => {
|
||||
it('treats script as ready only when at least one clip has non-empty screenplay', () => {
|
||||
expect(hasScriptArtifacts([])).toBe(false)
|
||||
expect(hasScriptArtifacts([
|
||||
{ id: 'clip-1', summary: '', location: null, characters: null, props: null, content: 'a', screenplay: '' },
|
||||
])).toBe(false)
|
||||
expect(hasScriptArtifacts([
|
||||
{ id: 'clip-1', summary: '', location: null, characters: null, props: null, content: 'a', screenplay: ' {"scenes":[]}' },
|
||||
])).toBe(true)
|
||||
})
|
||||
|
||||
it('treats storyboard as ready only when at least one storyboard has panels', () => {
|
||||
expect(hasStoryboardArtifacts([])).toBe(false)
|
||||
expect(hasStoryboardArtifacts([{ panels: [] }])).toBe(false)
|
||||
expect(hasStoryboardArtifacts([{ panels: [{ id: 'panel-1' }] }])).toBe(true)
|
||||
})
|
||||
|
||||
it('treats video as ready only when at least one panel has videoUrl', () => {
|
||||
expect(hasVideoArtifacts([{ panels: [{ id: 'panel-1', videoUrl: '' }] }])).toBe(false)
|
||||
expect(hasVideoArtifacts([{ panels: [{ id: 'panel-1', videoUrl: 'https://example.com/video.mp4' }] }])).toBe(true)
|
||||
})
|
||||
|
||||
it('derives full episode stage artifacts from persisted outputs', () => {
|
||||
const readiness = resolveEpisodeStageArtifacts({
|
||||
novelText: 'story',
|
||||
clips: [
|
||||
{ id: 'clip-1', summary: '', location: null, characters: null, props: null, content: 'a', screenplay: '{"scenes":[]}' },
|
||||
],
|
||||
storyboards: [
|
||||
{
|
||||
id: 'sb-1',
|
||||
episodeId: 'ep-1',
|
||||
clipId: 'clip-1',
|
||||
storyboardTextJson: null,
|
||||
panelCount: 1,
|
||||
storyboardImageUrl: null,
|
||||
panels: [{
|
||||
id: 'panel-1',
|
||||
storyboardId: 'sb-1',
|
||||
panelIndex: 0,
|
||||
panelNumber: 1,
|
||||
shotType: null,
|
||||
cameraMove: null,
|
||||
description: null,
|
||||
location: null,
|
||||
characters: null,
|
||||
props: null,
|
||||
srtSegment: null,
|
||||
srtStart: null,
|
||||
srtEnd: null,
|
||||
duration: null,
|
||||
imagePrompt: null,
|
||||
imageUrl: null,
|
||||
imageHistory: null,
|
||||
videoPrompt: null,
|
||||
videoUrl: 'https://example.com/video.mp4',
|
||||
photographyRules: null,
|
||||
actingNotes: null,
|
||||
}],
|
||||
},
|
||||
],
|
||||
voiceLines: [{ id: 'voice-1' }],
|
||||
})
|
||||
|
||||
expect(readiness).toEqual({
|
||||
hasStory: true,
|
||||
hasScript: true,
|
||||
hasStoryboard: true,
|
||||
hasVideo: true,
|
||||
hasVoice: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user