返回 JoyAI-Echo
generation.ts
1 import type { WorkplaceData, WorkplaceShot } from "@/lib/types";
2
3 const GENERATED_STATUSES = new Set([
4 "generated",
5 "review_pass",
6 "approved",
7 ]);
8
9 export function shotReferenceIds(shot: WorkplaceShot): number[] {
10 return shot.planned_reference_shot_ids ?? shot.reference_shot_ids ?? [];
11 }
12
13 export function isShotGenerated(shot: WorkplaceShot): boolean {
14 if (shot.has_video || !!shot.video?.url) return true;
15 return GENERATED_STATUSES.has(shot.status);
16 }
17
18 /** Hide/edit-lock future-shot Memory until the Agent has produced its draft. */
19 export function isMemoryRecommendationReady(
20 workplace: WorkplaceData,
21 shot: WorkplaceShot,
22 ): boolean {
23 if (shot.shot_id <= 1 || workplace.auto_generate || isShotGenerated(shot)) {
24 return true;
25 }
26 const memoryWorkflowActive = workplace.shots.some(
27 (candidate) => candidate.memory_review != null,
28 );
29 if (!memoryWorkflowActive) return true;
30 return (
31 shot.memory_slots_configured === true ||
32 shot.memory_recommendation_source === "agent"
33 );
34 }
35
36 export function missingReferenceGenerations(
37 workplace: WorkplaceData,
38 shot: WorkplaceShot,
39 ): number[] {
40 const refs = shotReferenceIds(shot);
41 if (refs.length === 0) return [];
42 const byId = new Map(workplace.shots.map((item) => [item.shot_id, item]));
43 return refs.filter((refId) => {
44 const refShot = byId.get(refId);
45 return !refShot || !isShotGenerated(refShot);
46 });
47 }
48
49 export function previousShotApprovalMessage(
50 workplace: WorkplaceData,
51 shot: WorkplaceShot,
52 ): string | null {
53 if (shot.shot_id <= 1) return null;
54 const previous = [...workplace.shots]
55 .filter((item) => item.shot_id < shot.shot_id)
56 .sort((left, right) => right.shot_id - left.shot_id)[0];
57 const previousId = previous?.shot_id ?? shot.shot_id - 1;
58 const videoAccepted =
59 previous?.accepted === true && previous.status === "approved";
60 const memoryAccepted = previous?.memory_review?.status === "approved";
61 if (videoAccepted && memoryAccepted) return null;
62 return `Approve Shot ${previousId} and confirm its memory before generating Shot ${shot.shot_id}.`;
63 }
64
65 /** Amber hint shown on Shot 1 until its video + Memory are both confirmed. */
66 export function shotOneWorkflowHint(
67 workplace: WorkplaceData,
68 ): string | null {
69 const hasLaterShot = workplace.shots.some((item) => item.shot_id >= 2);
70 if (!hasLaterShot) return null;
71 const shotOne = workplace.shots.find((item) => item.shot_id === 1);
72 if (!shotOne) return null;
73 const videoAccepted =
74 shotOne.accepted === true && shotOne.status === "approved";
75 const memoryAccepted = shotOne.memory_review?.status === "approved";
76 if (videoAccepted && memoryAccepted) return null;
77 return "Approve Shot 1 and confirm its memory before generating Shot 2.";
78 }
79
80 export function canGenerateShot(
81 workplace: WorkplaceData | null,
82 shot: WorkplaceShot,
83 ): boolean {
84 if (!workplace?.references_ready) return false;
85 if (!shot.references_planned) return false;
86 if (!isMemoryRecommendationReady(workplace, shot)) return false;
87 if (
88 shot.shot_id > 1 &&
89 workplace.stage === "awaiting_memory_build" &&
90 workplace.shots.some((candidate) => candidate.memory_review != null) &&
91 shot.memory_slots_configured !== true
92 ) {
93 return false;
94 }
95 if (["queued", "generated", "review_pass", "approved"].includes(shot.status)) {
96 return false;
97 }
98 if (previousShotApprovalMessage(workplace, shot)) return false;
99 return missingReferenceGenerations(workplace, shot).length === 0;
100 }
101
102 export function referenceDependencyMessage(
103 shot: WorkplaceShot,
104 missing: number[],
105 ): string {
106 const refs = missing.join("、");
107 return `Shot ${shot.shot_id} depends on Shot ${refs}. Generate the reference shot first.`;
108 }
109
110 export function formatReferenceLabel(referenceIds: number[]): string {
111 if (referenceIds.length === 0) return "No reference shots";
112 return `Reference shots: ${referenceIds.join(", ")}`;
113 }
114
114 lines TYPESCRIPT