返回 JoyAI-Echo
1 import type { StoryProfile, WorkplaceData } from "@/lib/types";
2
3 /** Canonical overlay keys: `story` or `beat:{shotId}`. */
4 export type FieldPath = "story" | `beat:${number}`;
5
6 export type LeaseMode = "follow" | "held";
7
8 export type EditorSegment = {
9 id: string;
10 shotId: number;
11 text: string;
12 };
13
14 export type CommitAction =
15 | { type: "confirm_story" }
16 | { type: "start_generation" }
17 | { type: "start_merge" }
18 | {
19 type: "split_shot";
20 shotId: number;
21 beforeText: string;
22 afterText: string;
23 }
24 | { type: "merge_shot"; shotId: number; mergedText: string }
25 | { type: "delete_shot"; shotId: number };
26
27 export type WorkplaceEditorState = {
28 server: WorkplaceData | null;
29 overlay: Partial<Record<FieldPath, string>>;
30 lease: Partial<Record<FieldPath, LeaseMode>>;
31 dirty: Partial<Record<FieldPath, boolean>>;
32 committing: boolean;
33 };
34
35 export type WorkplaceMutations = {
36 saveStory: (storyMd: string) => Promise<void>;
37 saveStoryProfile: (profile: StoryProfile) => Promise<void>;
38 confirmStory: (storyMd?: string) => Promise<void>;
39 startGeneration: () => Promise<void>;
40 startMerge: () => Promise<void>;
41 splitShot: (
42 shotId: number,
43 payload: { before_text: string; after_text: string },
44 ) => Promise<void>;
45 mergeShotUp: (shotId: number, mergedText?: string) => Promise<void>;
46 deleteShot: (shotId: number) => Promise<void>;
47 };
48
49 export function beatPath(shotId: number): FieldPath {
50 return `beat:${shotId}`;
51 }
52
53 export function parseBeatPath(path: FieldPath): number | null {
54 if (path === "story") return null;
55 const id = Number(path.slice("beat:".length));
56 return Number.isFinite(id) ? id : null;
57 }
58
59 export function segmentIdForShot(shotId: number): string {
60 return `shot_${String(shotId).padStart(3, "0")}`;
61 }
62
62 lines TYPESCRIPT