feat: expand novel IP and production workflows

This commit is contained in:
www
2026-09-18 08:14:05 +02:00
parent b2ae4600b4
commit d9c81a3ac0
235 changed files with 117971 additions and 2721 deletions
+20
View File
@@ -1,5 +1,22 @@
import type { ScriptStatus, StoryboardStatus } from './script.types';
export class GenerateEpisodeScriptDto {
provider_code?: string;
prompt_override?: string;
request_params_override?: {
max_output_tokens?: number;
temperature?: number;
timeout_ms?: number;
};
}
export class GenerateStoryboardDto {
provider_code?: string;
video_provider_code?: string;
storyboard_mode?: 'visual_board' | 'shot_list';
force?: boolean;
}
export class UpdateEpisodeScriptDto {
script_text?: string;
narration_text?: string;
@@ -21,5 +38,8 @@ export class UpdateStoryboardShotDto {
duration?: number;
prompt_text?: string;
negative_prompt?: string;
transition_to_next?: string;
transition_to_next_duration?: number;
transition_to_next_reason?: string;
status?: StoryboardStatus;
}
+90
View File
@@ -34,13 +34,85 @@ export interface SafeStoryboardShot {
camera_motion: string | null;
effect_type: string | null;
duration: number | null;
scene_type: string | null;
importance_score: number | null;
emotion_score: number | null;
action_score: number | null;
route_tier: string | null;
prompt_text: string | null;
negative_prompt: string | null;
live_action_desc: string | null;
actor_action: string | null;
camera_instruction: string | null;
performance_instruction: string | null;
video_prompt: string | null;
transition_to_next: string | null;
transition_to_next_duration: number | null;
transition_to_next_reason: string | null;
keyframe_asset_id: string | null;
video_clip_asset_id: string | null;
video_status: string | null;
status: string;
created_at: string;
updated_at: string;
}
export interface SafeStoryboardVisualBoardCell {
shot_id: string;
shot_no: number;
board_no: number;
row_no: number;
cell_no: number;
global_cell_no: number;
scene_name: string | null;
shot_type: string | null;
visual_desc: string | null;
action_desc: string | null;
dialogue_text: string | null;
duration: number | null;
required_assets_text: string;
}
export interface SafeStoryboardVisualBoardGroup {
group_no: number;
board_no: number;
row_no: number;
cell_range: string;
shot_ids: string[];
shot_nos: number[];
suggested_duration: number;
prompt_text: string;
negative_prompt: string;
cells: SafeStoryboardVisualBoardCell[];
}
export interface SafeStoryboardVisualBoard {
board_no: number;
grid_mode: 25;
title: string;
prompt_text: string;
negative_prompt: string;
cells: SafeStoryboardVisualBoardCell[];
}
export interface SafeStoryboardVisualBoardPlan {
mode: 'visual_board';
grid_mode: 25;
group_size: 5;
board_count: number;
total_cells: number;
provider_code: string | null;
provider_label: string;
provider_duration_range: {
min: number;
max: number;
allowed: number[];
};
boards: SafeStoryboardVisualBoard[];
groups: SafeStoryboardVisualBoardGroup[];
copy_package_text: string;
}
export function toSafeEpisodeScript(script: EpisodeScript): SafeEpisodeScript {
return {
id: script.id.toString(),
@@ -72,8 +144,26 @@ export function toSafeStoryboardShot(shot: StoryboardShot): SafeStoryboardShot {
camera_motion: shot.camera_motion,
effect_type: shot.effect_type,
duration: shot.duration ? Number(shot.duration.toString()) : null,
scene_type: shot.scene_type,
importance_score: shot.importance_score,
emotion_score: shot.emotion_score,
action_score: shot.action_score,
route_tier: shot.route_tier,
prompt_text: shot.prompt_text,
negative_prompt: shot.negative_prompt,
live_action_desc: shot.live_action_desc,
actor_action: shot.actor_action,
camera_instruction: shot.camera_instruction,
performance_instruction: shot.performance_instruction,
video_prompt: shot.video_prompt,
transition_to_next: shot.transition_to_next,
transition_to_next_duration: shot.transition_to_next_duration
? Number(shot.transition_to_next_duration.toString())
: null,
transition_to_next_reason: shot.transition_to_next_reason,
keyframe_asset_id: shot.keyframe_asset_id?.toString() ?? null,
video_clip_asset_id: shot.video_clip_asset_id?.toString() ?? null,
video_status: shot.video_status,
status: shot.status,
created_at: shot.created_at.toISOString(),
updated_at: shot.updated_at.toISOString()
+31 -5
View File
@@ -7,12 +7,18 @@ import {
Param,
Patch,
Post,
Query,
UseGuards
} from '@nestjs/common';
import { CurrentUser } from '../auth/current-user.decorator';
import type { AuthRequestUser } from '../auth/auth.types';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { UpdateEpisodeScriptDto, UpdateStoryboardShotDto } from './script.dto';
import {
GenerateEpisodeScriptDto,
GenerateStoryboardDto,
UpdateEpisodeScriptDto,
UpdateStoryboardShotDto
} from './script.dto';
import { ScriptsService } from './scripts.service';
@Controller()
@@ -23,9 +29,19 @@ export class ScriptsController {
@Post('episodes/:episodeId/script/generate')
generateScript(
@CurrentUser() user: AuthRequestUser,
@Param('episodeId') episodeId: string
@Param('episodeId') episodeId: string,
@Body() dto: GenerateEpisodeScriptDto
) {
return this.scriptsService.generateScript(user, episodeId);
return this.scriptsService.generateScript(user, episodeId, dto);
}
@Post('episodes/:episodeId/script/request-preview')
previewScriptRequest(
@CurrentUser() user: AuthRequestUser,
@Param('episodeId') episodeId: string,
@Body() dto: GenerateEpisodeScriptDto
) {
return this.scriptsService.previewScriptRequest(user, episodeId, dto);
}
@Get('episodes/:episodeId/script')
@@ -56,9 +72,10 @@ export class ScriptsController {
@Post('episodes/:episodeId/storyboard/generate')
generateStoryboard(
@CurrentUser() user: AuthRequestUser,
@Param('episodeId') episodeId: string
@Param('episodeId') episodeId: string,
@Body() dto: GenerateStoryboardDto
) {
return this.scriptsService.generateStoryboard(user, episodeId);
return this.scriptsService.generateStoryboard(user, episodeId, dto);
}
@Get('episodes/:episodeId/storyboard')
@@ -69,6 +86,15 @@ export class ScriptsController {
return this.scriptsService.getStoryboard(user, episodeId);
}
@Get('episodes/:episodeId/storyboard/visual-board-plan')
getStoryboardVisualBoardPlan(
@CurrentUser() user: AuthRequestUser,
@Param('episodeId') episodeId: string,
@Query('video_provider_code') videoProviderCode?: string
) {
return this.scriptsService.getStoryboardVisualBoardPlan(user, episodeId, videoProviderCode);
}
@Patch('storyboard-shots/:shotId')
updateStoryboardShot(
@CurrentUser() user: AuthRequestUser,
+2 -1
View File
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { AuthModule } from '../auth/auth.module';
import { PrismaModule } from '../prisma/prisma.module';
import { ProvidersModule } from '../providers/providers.module';
import { ScriptsController } from './scripts.controller';
import { ScriptsService } from './scripts.service';
@Module({
imports: [AuthModule, PrismaModule],
imports: [AuthModule, PrismaModule, ProvidersModule],
controllers: [ScriptsController],
providers: [ScriptsService],
exports: [ScriptsService]
+1 -1
View File
@@ -399,7 +399,7 @@ describe('ScriptsService', () => {
where: { id: 10n },
data: { status: 'storyboard_generating' }
});
expect(tx.storyboardShot.createMany.mock.calls[0][0].data).toHaveLength(10);
expect(tx.storyboardShot.createMany.mock.calls[0][0].data).toHaveLength(2);
expect(tx.storyboardShot.createMany.mock.calls[0][0].data[0]).toEqual(
expect.objectContaining({
project_id: 10n,
File diff suppressed because it is too large Load Diff