@lingjingai/script-editor 0.0.1 → 0.1.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 CHANGED
@@ -20,12 +20,7 @@
20
20
  ```ts
21
21
  import "@lingjingai/script-editor/style.css";
22
22
  ```
23
- - **构建条件**:
24
- - 默认走 `dist`(`tsup` 产物,编译好的 JS + d.ts),消费方无需编译包内 TS。
25
- - 想直接吃 `src` + HMR(monorepo 开发)→ 打包器加 `development` 解析条件。Vite:
26
- ```ts
27
- resolve: { conditions: ["development"] }
28
- ```
23
+ - **构建产物**:消费方一律走 `dist`(`tsup` 编译好的 JS + d.ts + style.css + paper-fibers.png),无需编译包内 TS。`exports` 不带 `development` 条件(早期版本带过,会让 Vite **dev 模式**去找未发布的 `src/` 而报 `Failed to resolve import .../style.css` —— `0.1.1+` 已修)。
29
24
 
30
25
  ---
31
26
 
package/dist/index.d.ts CHANGED
@@ -264,6 +264,36 @@ type ScriptFormat = "standard" | "asian";
264
264
 
265
265
  type SaveStatus = "idle" | "saving" | "saved" | "error";
266
266
 
267
+ /**
268
+ * Reference focus — the INVERSE of "quote to chat". The host pushes a
269
+ * ScriptReferenceFocusRequest (e.g. when a chat message / comment that quotes
270
+ * the script is clicked); the editor maps the reference's `source` to a
271
+ * StudioSelection and scrolls/selects it. `requestId` is a nonce so re-focusing
272
+ * the same target re-triggers.
273
+ */
274
+
275
+ /** Where a quoted reference points INTO the script. All fields optional/loose
276
+ * to tolerate host payloads (numbers may arrive as strings). */
277
+ interface ScriptReferenceLocator {
278
+ id?: string;
279
+ text?: string;
280
+ source?: {
281
+ kind?: string;
282
+ id?: string;
283
+ episodeIdx?: number | string;
284
+ sceneIdx?: number | string;
285
+ itemIdx?: number | string;
286
+ field?: string;
287
+ } | null;
288
+ }
289
+ interface ScriptReferenceFocusRequest {
290
+ /** nonce — bump to re-focus the same target */
291
+ requestId: number;
292
+ reference: ScriptReferenceLocator;
293
+ }
294
+ /** Map a reference locator → the selection the editor should focus, or null. */
295
+ declare function selectionFromScriptReference(reference: ScriptReferenceLocator): StudioSelection | null;
296
+
267
297
  /**
268
298
  * Coarse, pure diff between a baseline ScriptProject (pre-AI-revision) and the
269
299
  * current one. Surfaces changed top-level fields and changed/added scenes so
@@ -310,6 +340,15 @@ interface AssetDeleteRequest {
310
340
  interface DiffAdapter {
311
341
  /** the script as it was BEFORE the AI revision (null = no pending diff) */
312
342
  baseline: ScriptProject | null;
343
+ /** whether the baseline is durably persisted ("ok") or only in memory
344
+ * ("memory", e.g. localStorage write failed → lost on reload). Surfaced as a
345
+ * hint in the diff capsule. Optional. */
346
+ baselineStatus?: "ok" | "memory";
347
+ /** advance the baseline by a patch — drives PER-CHANGE "accept": the editor
348
+ * rewrites the baseline so one scene matches current, dropping just that
349
+ * change from the diff. When provided, per-scene accept/reject affordances
350
+ * appear on each changed scene. */
351
+ onPatchBaseline?: (patch: (prev: ScriptProject) => ScriptProject) => void;
313
352
  /** accept all AI changes — host drops the baseline */
314
353
  onAcceptAll?: () => void;
315
354
  /** reject all AI changes — restore the baseline as the working value */
@@ -328,10 +367,6 @@ interface AssetAdapter {
328
367
  }) => void;
329
368
  refresh?: () => void;
330
369
  }
331
- /** Agent bridge: send a chat message to the host's assistant. */
332
- interface AgentAdapter {
333
- sendMessage: (text: string) => void;
334
- }
335
370
  /** A reference (quoted text / selected block) to push into the chat composer. */
336
371
  interface ChatReference {
337
372
  id: string;
@@ -341,7 +376,6 @@ interface ChatReference {
341
376
  interface ScriptEditorAdapters {
342
377
  diff?: DiffAdapter;
343
378
  asset?: AssetAdapter;
344
- agent?: AgentAdapter;
345
379
  onAddChatReferences?: (refs: ChatReference[]) => void;
346
380
  onRequestAssetDelete?: (target: AssetDeleteRequest) => void;
347
381
  }
@@ -365,8 +399,11 @@ interface ScriptEditorProps extends ScriptEditorAdapters {
365
399
  * last one closes — lets the host pause adopting external revisions without
366
400
  * reaching into the package's DOM / class names. */
367
401
  onEditingChange?: (editing: boolean) => void;
402
+ /** host-pushed "jump into the script" signal (inverse of quote-to-chat):
403
+ * bump requestId + supply a reference → editor selects & scrolls to it. */
404
+ referenceFocusRequest?: ScriptReferenceFocusRequest | null;
368
405
  }
369
- declare function ScriptEditor({ value, onEdit, selection: controlledSelection, onSelectionChange, format: controlledFormat, onFormatChange, disabled, className, dark, saveStatus, canUndo, canRedo, onUndo, onRedo, onEditingChange, diff, asset, agent, onAddChatReferences, onRequestAssetDelete, }: ScriptEditorProps): react.JSX.Element;
406
+ declare function ScriptEditor({ value, onEdit, selection: controlledSelection, onSelectionChange, format: controlledFormat, onFormatChange, disabled, className, dark, saveStatus, canUndo, canRedo, onUndo, onRedo, onEditingChange, referenceFocusRequest, diff, asset, onAddChatReferences, onRequestAssetDelete, }: ScriptEditorProps): react.JSX.Element;
370
407
 
371
408
  /**
372
409
  * Pure ScriptProject mutations.
@@ -593,4 +630,4 @@ declare function DeleteConfirmButton({ onConfirm, disabled, targetScope, title,
593
630
  description?: string;
594
631
  }): react.JSX.Element;
595
632
 
596
- export { type AgentAdapter, type AssetAdapter, type AssetDeleteRequest, type AssetState, type ChangeSet, type ChatReference, DeleteConfirmButton, type DiffAdapter, EMPTY_SELECTION, EditableText, type EpisodeWorkspaceView, type FieldChange, Popover, type SaveStatus, type Scene, type SceneContext, type SceneEnvironment, type ScriptActor, ScriptContentType, type ScriptData, ScriptEditor, type ScriptEditorAdapters, type ScriptEditorProps, type ScriptEpisode, type ScriptFormat, type ScriptItem, type ScriptLocation, type ScriptProject, type ScriptProp, type ScriptSpeaker, type ScriptTargetKind, SelectField, type StateChange, type StudioSelection, type StudioSelectionFrom, type StudioSelectionOrigin, type TransitionPrompt, buildSelectionFrom, computeChangeSet, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, index as mutations, sceneChangeKey, selectionTargetKey, withOrigin };
633
+ export { type AssetAdapter, type AssetDeleteRequest, type AssetState, type ChangeSet, type ChatReference, DeleteConfirmButton, type DiffAdapter, EMPTY_SELECTION, EditableText, type EpisodeWorkspaceView, type FieldChange, Popover, type SaveStatus, type Scene, type SceneContext, type SceneEnvironment, type ScriptActor, ScriptContentType, type ScriptData, ScriptEditor, type ScriptEditorAdapters, type ScriptEditorProps, type ScriptEpisode, type ScriptFormat, type ScriptItem, type ScriptLocation, type ScriptProject, type ScriptProp, type ScriptReferenceFocusRequest, type ScriptReferenceLocator, type ScriptSpeaker, type ScriptTargetKind, SelectField, type StateChange, type StudioSelection, type StudioSelectionFrom, type StudioSelectionOrigin, type TransitionPrompt, buildSelectionFrom, computeChangeSet, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, index as mutations, sceneChangeKey, selectionFromScriptReference, selectionTargetKey, withOrigin };
package/dist/index.js CHANGED
@@ -650,6 +650,7 @@ function CanvasTopBar({
650
650
  function SaveCapsule({
651
651
  saveStatus = "idle",
652
652
  diffCount = 0,
653
+ baselineStatus,
653
654
  canApplyDiff,
654
655
  onDiffPrev,
655
656
  onDiffNext,
@@ -677,6 +678,7 @@ function SaveCapsule({
677
678
  ] }),
678
679
  /* @__PURE__ */ jsx("span", { className: "lj-se-capsule-count", children: diffCount }),
679
680
  /* @__PURE__ */ jsx("span", { className: "lj-se-capsule-muted", children: "\u6539\u52A8" }),
681
+ baselineStatus === "memory" ? /* @__PURE__ */ jsx("span", { className: "lj-se-capsule-warn", title: "\u57FA\u7EBF\u4EC5\u5B58\u4E8E\u5185\u5B58,\u5237\u65B0\u9875\u9762\u4F1A\u4E22\u5931", children: "\u4EC5\u672C\u6B21" }) : null,
680
682
  onDiffPrev ? /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-capsule-nav", title: "\u4E0A\u4E00\u5904", onClick: onDiffPrev, children: /* @__PURE__ */ jsx(ChevronLeft, { className: "lj-se-ic-3", strokeWidth: 2.25 }) }) : null,
681
683
  onDiffNext ? /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-capsule-nav", title: "\u4E0B\u4E00\u5904", onClick: onDiffNext, children: /* @__PURE__ */ jsx(ChevronRight, { className: "lj-se-ic-3", strokeWidth: 2.25 }) }) : null,
682
684
  onRejectAll ? /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-capsule-reject", disabled: !canApplyDiff, onClick: onRejectAll, children: "\u5168\u90E8\u62D2\u7EDD" }) : null,
@@ -2919,6 +2921,7 @@ function ScriptStream({
2919
2921
  disabled,
2920
2922
  selection,
2921
2923
  changedSceneKeys,
2924
+ sceneDiff,
2922
2925
  onEdit,
2923
2926
  onSelectionChange,
2924
2927
  onRequestAssetDelete
@@ -3060,22 +3063,48 @@ function ScriptStream({
3060
3063
  const scene = value.episodes?.[item.episodeIdx]?.scenes?.[item.sceneIdx];
3061
3064
  if (!scene) return null;
3062
3065
  const focused = selection.kind === "scene" && selection.episodeIdx === item.episodeIdx && selection.sceneIdx === item.sceneIdx;
3063
- const changed = changedSceneKeys?.has(`${item.episodeIdx}:${item.sceneId}`) ?? false;
3064
- return /* @__PURE__ */ jsx("div", { className: "lj-se-stream-row", "data-quote-source": true, children: /* @__PURE__ */ jsx("div", { className: cn("lj-se-paper-seg", paperEdge(item.key), changed && "is-changed"), children: /* @__PURE__ */ jsx(
3065
- SceneEditor,
3066
- {
3067
- episodeIndex: item.episodeIdx,
3068
- sceneIndex: item.sceneIdx,
3069
- scene,
3070
- maps,
3071
- format,
3072
- disabled,
3073
- focused,
3074
- onEdit,
3075
- onNavigateAsset: select,
3076
- onRequestAssetDelete
3077
- }
3078
- ) }) });
3066
+ const diffKey = `${item.episodeIdx}:${item.sceneId}`;
3067
+ const changed = changedSceneKeys?.has(diffKey) ?? false;
3068
+ const diffKind = !disabled ? sceneDiff?.kindByKey.get(diffKey) : void 0;
3069
+ return /* @__PURE__ */ jsx("div", { className: "lj-se-stream-row", "data-quote-source": true, children: /* @__PURE__ */ jsxs("div", { className: cn("lj-se-paper-seg", paperEdge(item.key), changed && "is-changed"), children: [
3070
+ diffKind ? /* @__PURE__ */ jsxs("div", { className: "lj-se-scenediff", children: [
3071
+ /* @__PURE__ */ jsx("span", { className: cn("lj-se-scenediff-tag", diffKind === "added" && "is-added"), children: diffKind === "added" ? "AI \u65B0\u589E" : "AI \u6539\u52A8" }),
3072
+ /* @__PURE__ */ jsx("span", { className: "lj-se-scenediff-spacer" }),
3073
+ /* @__PURE__ */ jsx(
3074
+ "button",
3075
+ {
3076
+ type: "button",
3077
+ className: "lj-se-scenediff-reject",
3078
+ onClick: () => sceneDiff.onReject(item.episodeIdx, item.sceneIdx, item.sceneId),
3079
+ children: "\u62D2\u7EDD"
3080
+ }
3081
+ ),
3082
+ /* @__PURE__ */ jsx(
3083
+ "button",
3084
+ {
3085
+ type: "button",
3086
+ className: "lj-se-scenediff-accept",
3087
+ onClick: () => sceneDiff.onAccept(item.episodeIdx, item.sceneId),
3088
+ children: "\u63A5\u53D7"
3089
+ }
3090
+ )
3091
+ ] }) : null,
3092
+ /* @__PURE__ */ jsx(
3093
+ SceneEditor,
3094
+ {
3095
+ episodeIndex: item.episodeIdx,
3096
+ sceneIndex: item.sceneIdx,
3097
+ scene,
3098
+ maps,
3099
+ format,
3100
+ disabled,
3101
+ focused,
3102
+ onEdit,
3103
+ onNavigateAsset: select,
3104
+ onRequestAssetDelete
3105
+ }
3106
+ )
3107
+ ] }) });
3079
3108
  }
3080
3109
  case "add-episode":
3081
3110
  if (disabled) return null;
@@ -3238,6 +3267,76 @@ function computeChangeSet(baseline, current) {
3238
3267
  const count = (title ? 1 : 0) + (worldview ? 1 : 0) + (style ? 1 : 0) + changedScenes.size + removedCount;
3239
3268
  return { title, worldview, style, changedScenes, removedCount, count };
3240
3269
  }
3270
+ function acceptSceneIntoBaseline(baseline, episodeId, episodeIdx, scene) {
3271
+ const episodes = (baseline.episodes ?? []).slice();
3272
+ const id = episodeId?.trim();
3273
+ let ei = id ? episodes.findIndex((e) => e.episode_id?.trim() === id) : -1;
3274
+ if (ei < 0) ei = episodeIdx >= 0 && episodeIdx < episodes.length ? episodeIdx : -1;
3275
+ if (ei < 0) {
3276
+ episodes.push({ episode_id: episodeId, scenes: [scene] });
3277
+ return { ...baseline, episodes };
3278
+ }
3279
+ const ep = episodes[ei];
3280
+ const scenes = (ep.scenes ?? []).slice();
3281
+ const sid = (scene.scene_id ?? "").trim();
3282
+ const si = sid ? scenes.findIndex((s) => (s.scene_id ?? "").trim() === sid) : -1;
3283
+ if (si >= 0) scenes[si] = scene;
3284
+ else scenes.push(scene);
3285
+ episodes[ei] = { ...ep, scenes };
3286
+ return { ...baseline, episodes };
3287
+ }
3288
+ function rejectSceneToBaseline(current, episodeIdx, sceneIdx, baselineScene) {
3289
+ const episodes = (current.episodes ?? []).slice();
3290
+ const ep = episodes[episodeIdx];
3291
+ if (!ep) return current;
3292
+ const scenes = (ep.scenes ?? []).slice();
3293
+ if (sceneIdx < 0 || sceneIdx >= scenes.length) return current;
3294
+ if (baselineScene) scenes[sceneIdx] = baselineScene;
3295
+ else scenes.splice(sceneIdx, 1);
3296
+ episodes[episodeIdx] = { ...ep, scenes };
3297
+ return { ...current, episodes };
3298
+ }
3299
+ function findBaselineScene(baseline, episodeId, episodeIdx, sceneId) {
3300
+ if (!baseline) return null;
3301
+ const ep = matchEpisode(baseline.episodes, { episode_id: episodeId }, episodeIdx);
3302
+ if (!ep) return null;
3303
+ const sid = sceneId.trim();
3304
+ if (!sid) return null;
3305
+ return (ep.scenes ?? []).find((s) => (s.scene_id ?? "").trim() === sid) ?? null;
3306
+ }
3307
+
3308
+ // src/reference.ts
3309
+ function asNumber(value) {
3310
+ if (typeof value === "number" && Number.isFinite(value)) return value;
3311
+ if (typeof value === "string" && value.trim()) {
3312
+ const parsed = Number(value);
3313
+ if (Number.isFinite(parsed)) return parsed;
3314
+ }
3315
+ return null;
3316
+ }
3317
+ function asString(value) {
3318
+ return typeof value === "string" ? value.trim() : "";
3319
+ }
3320
+ function selectionFromScriptReference(reference) {
3321
+ const source = reference.source;
3322
+ if (!source) return null;
3323
+ const sourceKind = asString(source.kind);
3324
+ const sourceId = asString(source.id);
3325
+ if (sourceKind === "actor" && sourceId) return { kind: "actor", actorId: sourceId };
3326
+ if (sourceKind === "location" && sourceId) return { kind: "location", locationId: sourceId };
3327
+ if (sourceKind === "prop" && sourceId) return { kind: "prop", propId: sourceId };
3328
+ const episodeIdx = asNumber(source.episodeIdx);
3329
+ const sceneIdx = asNumber(source.sceneIdx);
3330
+ if (episodeIdx !== null && sceneIdx !== null) {
3331
+ return { kind: "scene", episodeIdx, sceneIdx };
3332
+ }
3333
+ if (episodeIdx !== null) return { kind: "episode", episodeIdx };
3334
+ const field = asString(source.field);
3335
+ if (field === "title" || field === "worldview" || field === "style") {
3336
+ return { kind: "worldview" };
3337
+ }
3338
+ return null;
3339
+ }
3241
3340
  function QuoteLayer({
3242
3341
  containerRef,
3243
3342
  onAddChatReferences
@@ -3333,9 +3432,9 @@ function ScriptEditor({
3333
3432
  onUndo,
3334
3433
  onRedo,
3335
3434
  onEditingChange,
3435
+ referenceFocusRequest,
3336
3436
  diff,
3337
3437
  asset,
3338
- agent,
3339
3438
  onAddChatReferences,
3340
3439
  onRequestAssetDelete
3341
3440
  }) {
@@ -3404,6 +3503,33 @@ function ScriptEditor({
3404
3503
  },
3405
3504
  [changedSelections, diffCursor, setSelection]
3406
3505
  );
3506
+ const onAcceptScene = useCallback(
3507
+ (episodeIdx, sceneId) => {
3508
+ if (!diff?.onPatchBaseline) return;
3509
+ const ep = value.episodes?.[episodeIdx];
3510
+ const scene = ep?.scenes?.find((s) => (s.scene_id ?? "").trim() === sceneId.trim());
3511
+ if (!scene) return;
3512
+ diff.onPatchBaseline((prev) => acceptSceneIntoBaseline(prev, ep?.episode_id, episodeIdx, scene));
3513
+ },
3514
+ [diff, value]
3515
+ );
3516
+ const onRejectScene = useCallback(
3517
+ (episodeIdx, sceneIdx, sceneId) => {
3518
+ const ep = value.episodes?.[episodeIdx];
3519
+ const base = findBaselineScene(diff?.baseline ?? null, ep?.episode_id, episodeIdx, sceneId);
3520
+ onEdit((cur) => rejectSceneToBaseline(cur, episodeIdx, sceneIdx, base));
3521
+ },
3522
+ [diff, value, onEdit]
3523
+ );
3524
+ const sceneDiff = useMemo(
3525
+ () => diff?.onPatchBaseline && changeSet.changedScenes.size > 0 ? { kindByKey: changeSet.changedScenes, onAccept: onAcceptScene, onReject: onRejectScene } : void 0,
3526
+ [diff?.onPatchBaseline, changeSet, onAcceptScene, onRejectScene]
3527
+ );
3528
+ useEffect(() => {
3529
+ if (!referenceFocusRequest) return;
3530
+ const sel = selectionFromScriptReference(referenceFocusRequest.reference);
3531
+ if (sel) setSelection(withOrigin(sel, "reference"));
3532
+ }, [referenceFocusRequest, setSelection]);
3407
3533
  const contextLabel = buildContextLabel(value, selection);
3408
3534
  const returnTarget = resolveReturnTarget(value, selection);
3409
3535
  const rootClass = ["lj-se-root", dark ? "dark" : "", className].filter(Boolean).join(" ");
@@ -3415,6 +3541,7 @@ function ScriptEditor({
3415
3541
  disabled,
3416
3542
  selection,
3417
3543
  changedSceneKeys,
3544
+ sceneDiff,
3418
3545
  onEdit,
3419
3546
  onSelectionChange: setSelection,
3420
3547
  onRequestAssetDelete
@@ -3451,6 +3578,7 @@ function ScriptEditor({
3451
3578
  {
3452
3579
  saveStatus,
3453
3580
  diffCount: diff ? changeSet.count : 0,
3581
+ baselineStatus: diff?.baselineStatus,
3454
3582
  canApplyDiff: !!(diff?.onAcceptAll || diff?.onRejectAll),
3455
3583
  onDiffPrev: changedSelections.length > 0 ? () => navDiff(-1) : void 0,
3456
3584
  onDiffNext: changedSelections.length > 0 ? () => navDiff(1) : void 0,
@@ -3563,6 +3691,6 @@ __export(mutations_exports, {
3563
3691
  updateScenePropState: () => updateScenePropState
3564
3692
  });
3565
3693
 
3566
- export { DeleteConfirmButton, EMPTY_SELECTION, EditableText, Popover, ScriptContentType, ScriptEditor, SelectField, buildSelectionFrom, computeChangeSet, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, mutations_exports as mutations, sceneChangeKey, selectionTargetKey, withOrigin };
3694
+ export { DeleteConfirmButton, EMPTY_SELECTION, EditableText, Popover, ScriptContentType, ScriptEditor, SelectField, buildSelectionFrom, computeChangeSet, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, mutations_exports as mutations, sceneChangeKey, selectionFromScriptReference, selectionTargetKey, withOrigin };
3567
3695
  //# sourceMappingURL=index.js.map
3568
3696
  //# sourceMappingURL=index.js.map