@lingjingai/script-editor 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/INTEGRATION.md ADDED
@@ -0,0 +1,132 @@
1
+ # @lingjingai/script-editor 接入清单
2
+
3
+ 把这个剧本编辑器接进一个**新的宿主项目**(例如旧的 agent-frontend / monolj)时,照这份清单逐项过即可。
4
+
5
+ ## 0. 它是什么(先记住这一点)
6
+
7
+ 一个**纯组件**:数据契约只有 `value: ScriptProject` + `onEdit(patch)`,所有业务(diff / 资产配图 / agent / 批注)经 **adapter 注入**,包内**零 `fetch` / `/api` / `axios`**。所以"接入"= 喂数据 + 接几个 adapter + 引一份 CSS,不是黑盒。
8
+
9
+ ---
10
+
11
+ ## 1. 安装 / 引入
12
+
13
+ - **peer 依赖**:`react` / `react-dom` `^18 || ^19`。
14
+ - **自带依赖**(会被一起装):`lucide-react`、`react-virtuoso`。
15
+ - **本包尚未发布到 npm**(`workspace` 包,version `0.0.1`)。外部项目三选一:
16
+ - **(a) monorepo / workspace**:`"@lingjingai/script-editor": "workspace:*"`。
17
+ - **(b) tarball**:在本包目录 `npm run build && npm pack`,把 `.tgz` 拷过去 `npm i ./xxx.tgz`。
18
+ - **(c) 发私有 npm** 后正常 `npm i`。
19
+ - **必须引入 CSS**(组件靠自带样式,不依赖 Tailwind):
20
+ ```ts
21
+ import "@lingjingai/script-editor/style.css";
22
+ ```
23
+ - **构建条件**:
24
+ - 默认走 `dist`(`tsup` 产物,编译好的 JS + d.ts),消费方无需编译包内 TS。
25
+ - 想直接吃 `src` + HMR(monorepo 开发)→ 打包器加 `development` 解析条件。Vite:
26
+ ```ts
27
+ resolve: { conditions: ["development"] }
28
+ ```
29
+
30
+ ---
31
+
32
+ ## 2. 最小用法
33
+
34
+ ```tsx
35
+ import { ScriptEditor } from "@lingjingai/script-editor";
36
+ import "@lingjingai/script-editor/style.css";
37
+
38
+ function Host({ script, setScript }) {
39
+ return <ScriptEditor value={script} onEdit={setScript} />;
40
+ }
41
+ ```
42
+
43
+ `onEdit` 的签名是 **`(patch: (prev: ScriptProject) => ScriptProject) => void`** —— 宿主拿到新值后自己 `setState` + 持久化。
44
+
45
+ ---
46
+
47
+ ## 3. 数据契约 ⚠️ 最关键,**必须逐字段比对**
48
+
49
+ `value: ScriptProject`(类型从包导出:`import type { ScriptProject, Scene, ScriptItem } from "@lingjingai/script-editor"`)。
50
+
51
+ - **结构**:`episodes[].scenes[].actions[]`(每个 action 是一个 `ScriptItem`)+ 顶层字典 `actors[] / locations[] / props[] / speakers[]` + `title / worldview / style`。
52
+ - **双存储**:场景的角色/道具/地点引用同时可能存在 `scene.context.{actors,locations,props}` 与镜像 `scene.{actors,locations,props}`。包内 `getSceneContext` 取**非空那一侧**(context 非空优先)。你的数据**有其一即可**,两边都给时保持一致。
53
+ - **`scene_id` 稳定不可变**:资产引用按 `scene_id` 关联;拆分 / 合并 / 插入 / 删除等结构操作**不会重排 `scene_id`**。要求 `scene_id` 在同一集内唯一(否则按 id 跳转会落到最后一个同名场)。
54
+ - **和旧项目比对的高危字段**:
55
+ - `ScriptItem`: `type`(枚举见 `ScriptContentType`:Dialogue/Action/InnerThought/Narration)、`content`、`actor_id`、`speaker_id`、`speakers[]`、`delivery`(single/simultaneous/overlap/group)、`lines[]`、`emotion`、`state_changes[]`、`transition_prompt`。
56
+ - `environment`: `space`(`interior` / `exterior`)、`time`(`day` / `night` / `dawn` / `dusk`)。
57
+ - `AssetState`: `state_id` / `state_name` / `description`。
58
+ - 字段对不齐时:要么转一层适配器把旧数据映射成 `ScriptProject`,要么改后端产出格式。**不要**在包里加兼容分支。
59
+
60
+ ---
61
+
62
+ ## 4. Props / Adapter 清单(`ScriptEditorProps`)
63
+
64
+ **必填**
65
+ - `value: ScriptProject`
66
+ - `onEdit: (patch) => void`
67
+
68
+ **受控可选**
69
+ - `selection` / `onSelectionChange`(`StudioSelection`,不传则内部自管)
70
+ - `format` / `onFormatChange`(`"standard" | "asian"`)
71
+ - `disabled`、`dark`、`className`
72
+
73
+ **HUD(顶栏)**
74
+ - `saveStatus`:`"idle" | "saving" | "saved" | "error"`
75
+ - `canUndo` / `canRedo` / `onUndo` / `onRedo`
76
+
77
+ **编辑信号**
78
+ - `onEditingChange(editing: boolean)`:有外部改稿(AI / 协同)时,用它在"正编辑某字段"期间暂停采纳远端,避免覆盖。见 §6。
79
+
80
+ **业务 adapter**
81
+ - `diff?: { baseline: ScriptProject | null; onAcceptAll?; onRejectAll? }`
82
+ 改稿前的快照给 `baseline`,包内算出 baseline→当前 的变更并渲染"接受/拒绝全部 + 上一处/下一处"浮层。无改稿时传 `null`。
83
+ - `asset?: { payload?; isConnected?; postAction?; refresh? }`
84
+ 资产**展示**用 `payload`;**配图生成 / 上传(原 `/api/actors`、COS 那套)走 `postAction`,留在宿主实现**。v1 可整个不传(只读展示)。
85
+ - `agent?: { sendMessage(text) }`
86
+ - `onAddChatReferences(refs)`:"引用选段到对话",`refs: { id; label; text }[]`。
87
+ - `onRequestAssetDelete(target)`:删资产请求,交宿主确认/执行。
88
+
89
+ ---
90
+
91
+ ## 5. 自动保存 / 持久化(宿主负责)
92
+
93
+ 包**不碰存储**。参考实现:`apps/web/src/features/script-editor/useScriptDocument.ts`
94
+ - `onEdit` → `setState` + **防抖 PUT(800ms)** + 乐观并发(`baseRevision`,冲突报 `error`)。
95
+ - 内置 **undo/redo 快照栈**,经 `canUndo` / `onUndo` 等接 HUD。
96
+
97
+ ---
98
+
99
+ ## 6. AI 改稿探测(宿主侧,当前是**轮询权宜**)
100
+
101
+ 当前参考实现每 **8s** `GET /revision`:版本号比本地新、且整份内容 ≠ 自己上次保存的 → 快照 `diffBaseline` + 采纳远端,触发 §4 的 diff 浮层。`onEditingChange` / `saving` / `dirty` / 已有 diff 作为 `blocked()` 守卫,保证"正编辑/正保存时不采纳"。
102
+
103
+ **局限**:最高 8s 延迟、持续空轮询、靠"版本+内容差"**猜**作者(分不清 agent / 另一个标签页)。
104
+
105
+ **建议替换为推送**:后端在 agent 写完 script 时推一条事件(走现成的 WebSocket 通道,最好带"改了哪几场"),宿主收到再触发 `diffBaseline` —— 即时、无空轮询、作者明确。**这块全在宿主,不动包**:把触发点从"轮询判定"换成"收到事件"即可。
106
+
107
+ ---
108
+
109
+ ## 7. 主题 / 样式
110
+
111
+ - 自带 CSS,**不依赖 Tailwind**。chrome token 继承宿主 ui-kit 的 CSS 变量(`--background` 等),缺失则 `oklch` 兜底。
112
+ - 纸张纹理 `paper-fibers.png` 随包发布(`sideEffects: ["*.css"]`,确保打包器不 tree-shake 掉 CSS)。
113
+ - `dark` prop 切深色。
114
+
115
+ ---
116
+
117
+ ## 8. 已知边界 / v2(别当 bug)
118
+
119
+ - 并发编辑是 **last-writer-wins**,无 3-way 合并(`EditableText` 在用户已改动时不采纳外部值,提交即覆盖)。
120
+ - 无协同编辑(Yjs)。
121
+ - 无自动化测试(目前靠 tsc/tsup/vite 编译 + 人工验证)。
122
+ - diff 探测靠轮询(见 §6)。
123
+
124
+ ---
125
+
126
+ ## 9. 直接抄的参考实现
127
+
128
+ 宿主接线的"标准答案"就在本仓:
129
+ - `apps/web/src/features/script-editor/ScriptEditor.tsx` —— adapter 接线 + 格式持久化
130
+ - `apps/web/src/features/script-editor/useScriptDocument.ts` —— 自动保存 + undo/redo + 8s 轮询探测
131
+ - `apps/web/src/features/script-editor/script-output-api.ts` —— 后端存取
132
+ - `apps/web/src/main.tsx` / `apps/web/vite.config.ts` —— CSS 引入 + `development` 解析条件