@@ -36,6 +36,7 @@ import {
3636 PromptTemplateVersionConflictError ,
3737} from "../domain/template/prompt-template" ;
3838import { InMemoryUsageGuardrail } from "../domain/usage/usage-guardrail" ;
39+ import { InMemorySceneShotPlanner } from "../domain/script/scene-shot-planner" ;
3940import { ProviderAdapterRegistry } from "../providers/adapter-registry" ;
4041import { type ProviderName } from "../providers/types" ;
4142import { InMemoryProviderKeyStore } from "../security/provider-key-store" ;
@@ -75,6 +76,7 @@ interface PromptTemplateRefs {
7576export interface ApiState {
7677 readonly projects : Map < string , Map < string , ShotRecord > > ;
7778 readonly shotToProject : Map < string , string > ;
79+ readonly shotToScene : Map < string , string > ;
7880 readonly taskRepo : InMemoryGenerationTaskRepository ;
7981 readonly callbackEventStore : InMemoryCallbackEventStore ;
8082 readonly deadLetterStore : InMemoryDeadLetterStore ;
@@ -85,6 +87,7 @@ export interface ApiState {
8587 readonly providerProfileStore : InMemoryProjectProviderProfileStore ;
8688 readonly promptTemplateStore : InMemoryPromptTemplateStore ;
8789 readonly usageGuardrail : InMemoryUsageGuardrail ;
90+ readonly sceneShotPlanner : InMemorySceneShotPlanner ;
8891}
8992
9093const importSchema = z . object ( {
@@ -95,6 +98,22 @@ const importSchema = z.object({
9598 } ) ,
9699 ) ,
97100} ) ;
101+ const scriptScenePlanSchema = z
102+ . object ( {
103+ scenes : z
104+ . array (
105+ z
106+ . object ( {
107+ sceneId : z . string ( ) . min ( 1 ) ,
108+ content : z . string ( ) . min ( 1 ) ,
109+ targetShotCount : z . number ( ) . int ( ) . positive ( ) . max ( 8 ) . optional ( ) ,
110+ } )
111+ . strict ( ) ,
112+ )
113+ . min ( 1 ) ,
114+ regenerateSceneIds : z . array ( z . string ( ) . min ( 1 ) ) . optional ( ) ,
115+ } )
116+ . strict ( ) ;
98117
99118const generateSchema = z . object ( {
100119 provider : z . enum ( [ "runway" , "kling" ] ) . optional ( ) ,
@@ -252,6 +271,7 @@ export function createApiState(): ApiState {
252271 return {
253272 projects : new Map ( ) ,
254273 shotToProject : new Map ( ) ,
274+ shotToScene : new Map ( ) ,
255275 taskRepo : new InMemoryGenerationTaskRepository ( ) ,
256276 callbackEventStore : new InMemoryCallbackEventStore ( ) ,
257277 deadLetterStore : new InMemoryDeadLetterStore ( ) ,
@@ -262,6 +282,7 @@ export function createApiState(): ApiState {
262282 providerProfileStore : new InMemoryProjectProviderProfileStore ( ) ,
263283 promptTemplateStore : new InMemoryPromptTemplateStore ( ) ,
264284 usageGuardrail : new InMemoryUsageGuardrail ( ) ,
285+ sceneShotPlanner : new InMemorySceneShotPlanner ( ) ,
265286 } ;
266287}
267288
@@ -309,6 +330,7 @@ export function createApp(state = createApiState()) {
309330 mjAssets : [ ] ,
310331 } ) ;
311332 state . shotToProject . set ( input . id , projectId ) ;
333+ state . shotToScene . delete ( input . id ) ;
312334 }
313335
314336 state . projects . set ( projectId , existing ) ;
@@ -320,6 +342,64 @@ export function createApp(state = createApiState()) {
320342 } ) ;
321343 } ) ;
322344
345+ app . post ( "/projects/:id/script-scenes/plan" , ( req : Request , res : Response ) => {
346+ const parsed = scriptScenePlanSchema . safeParse ( req . body ) ;
347+ if ( ! parsed . success ) return res . status ( 400 ) . json ( { message : "Invalid request" , errors : parsed . error . issues } ) ;
348+
349+ const projectId = routeId ( req ) ;
350+ const projectShots = state . projects . get ( projectId ) ?? new Map < string , ShotRecord > ( ) ;
351+ state . projects . set ( projectId , projectShots ) ;
352+ state . providerProfileStore . ensure ( projectId ) ;
353+
354+ const planned = state . sceneShotPlanner . plan ( projectId , parsed . data . scenes , new Set ( parsed . data . regenerateSceneIds ?? [ ] ) ) ;
355+
356+ for ( const scene of planned ) {
357+ for ( const oldShotId of scene . replacedShotIds ) {
358+ projectShots . delete ( oldShotId ) ;
359+ state . shotToProject . delete ( oldShotId ) ;
360+ state . shotToScene . delete ( oldShotId ) ;
361+ }
362+ for ( const draft of scene . shotDrafts ) {
363+ const base = createShotCandidateState ( draft . shotId ) ;
364+ projectShots . set ( draft . shotId , {
365+ ...base ,
366+ shotId : draft . shotId ,
367+ status : "draft" ,
368+ prompt : draft . prompt ,
369+ promptTemplateRefs : { } ,
370+ mjPrompts : [ ] ,
371+ mjAssets : [ ] ,
372+ } ) ;
373+ state . shotToProject . set ( draft . shotId , projectId ) ;
374+ state . shotToScene . set ( draft . shotId , scene . sceneId ) ;
375+ }
376+ appendActivityEvent ( state , {
377+ projectId,
378+ type : "script_scene_planned" ,
379+ source : "user" ,
380+ payload : {
381+ sceneId : scene . sceneId ,
382+ version : scene . version ,
383+ shotCount : scene . shotDrafts . length ,
384+ changed : scene . changed ,
385+ } ,
386+ } ) ;
387+ }
388+
389+ return res . status ( 200 ) . json ( {
390+ projectId,
391+ plannedSceneCount : planned . length ,
392+ generatedShotCount : planned . reduce ( ( acc , scene ) => acc + scene . shotDrafts . length , 0 ) ,
393+ plans : planned . map ( ( scene ) => ( {
394+ sceneId : scene . sceneId ,
395+ version : scene . version ,
396+ changed : scene . changed ,
397+ updatedAt : scene . updatedAt ,
398+ shotDrafts : scene . shotDrafts ,
399+ } ) ) ,
400+ } ) ;
401+ } ) ;
402+
323403 app . post ( "/providers/callbacks" , ( req : Request , res : Response ) => {
324404 const parsed = providerCallbackSchema . safeParse ( req . body ) ;
325405 if ( ! parsed . success ) return res . status ( 400 ) . json ( { message : "Invalid request" , errors : parsed . error . issues } ) ;
@@ -585,6 +665,7 @@ export function createApp(state = createApiState()) {
585665 const projectId = routeId ( req ) ;
586666 const shots = [ ...( state . projects . get ( projectId ) ?. values ( ) ?? [ ] ) ] . map ( ( shot ) => ( {
587667 shotId : shot . shotId ,
668+ sceneId : state . shotToScene . get ( shot . shotId ) ?? null ,
588669 status : shot . status ,
589670 prompt : shot . prompt ,
590671 promptTemplateRefs : shot . promptTemplateRefs ,
0 commit comments