feat(npcs): add groups, storyline bindings, and Foundry import

Nested NPC groups with color, graph filter, and scene/storyline binding; Foundry worlds/modules import actors into groups; storyline merge asks on NPC name conflicts and reports NPC counts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-20 13:19:22 +08:00
parent 37ba855faf
commit f4c0ac1438
41 changed files with 5210 additions and 418 deletions
+50 -2
View File
@@ -10,6 +10,8 @@ import type {
MediaAsset,
NpcId,
NpcRelationId,
NpcGroupId,
NpcBinding,
NpcsOverlayEvent,
NpcsOverlayState,
Project,
@@ -22,6 +24,7 @@ import type {
VideoPlaybackState,
} from '../types';
import type {
NpcImportResolution,
SceneImportResolution,
StorylineImportMergeReport,
StorylineLabels,
@@ -66,6 +69,9 @@ export const ipcChannels = {
pickNpcAvatar: 'project.pickNpcAvatar',
upsertNpcRelation: 'project.upsertNpcRelation',
deleteNpcRelation: 'project.deleteNpcRelation',
upsertNpcGroup: 'project.upsertNpcGroup',
deleteNpcGroup: 'project.deleteNpcGroup',
setNpcGroupsOrder: 'project.setNpcGroupsOrder',
importScenePreview: 'project.importScenePreview',
clearScenePreview: 'project.clearScenePreview',
assetFileUrl: 'project.assetFileUrl',
@@ -90,6 +96,8 @@ export const ipcChannels = {
mergeImportZip: 'project.mergeImportZip',
mergeImportFromProject: 'project.mergeImportFromProject',
importZipFromPath: 'project.importZipFromPath',
importFoundry: 'project.importFoundry',
pickFoundrySource: 'project.pickFoundrySource',
deleteProject: 'project.deleteProject',
importZipProgress: 'project.importZipProgress',
exportZipProgress: 'project.exportZipProgress',
@@ -297,11 +305,24 @@ export type IpcInvokeMap = {
| { canceled: false; filePath: string; previewDataUrl: string };
};
[ipcChannels.project.upsertNpc]: {
req: { npcId?: NpcId; name: string; description?: string; filePath?: string };
req: {
npcId?: NpcId;
name: string;
description?: string;
filePath?: string;
groupId?: NpcGroupId | null;
binding?: NpcBinding;
};
res: { project: Project };
};
[ipcChannels.project.updateNpcFields]: {
req: { npcId: NpcId; name?: string; description?: string };
req: {
npcId: NpcId;
name?: string;
description?: string;
groupId?: NpcGroupId | null;
binding?: NpcBinding;
};
res: { project: Project };
};
[ipcChannels.project.updateNpcPosition]: {
@@ -335,6 +356,23 @@ export type IpcInvokeMap = {
req: { relationId: NpcRelationId };
res: { project: Project };
};
[ipcChannels.project.upsertNpcGroup]: {
req: {
groupId?: NpcGroupId;
name: string;
color?: string;
parentId?: NpcGroupId | null;
};
res: { project: Project };
};
[ipcChannels.project.deleteNpcGroup]: {
req: { groupId: NpcGroupId };
res: { project: Project };
};
[ipcChannels.project.setNpcGroupsOrder]: {
req: { groupIds: NpcGroupId[] };
res: { project: Project };
};
[ipcChannels.project.importScenePreview]: {
req: { sceneId: SceneId; filePath?: string };
res: { project: Project; assetId: AssetId | null; background: boolean };
@@ -438,6 +476,7 @@ export type IpcInvokeMap = {
filePath: string;
storylineSelections: StorylineSelection[];
sceneResolutions: SceneImportResolution[];
npcResolutions?: NpcImportResolution[];
};
res: { project: Project; report: StorylineImportMergeReport };
};
@@ -446,6 +485,7 @@ export type IpcInvokeMap = {
sourceProjectId: ProjectId;
storylineSelections: StorylineSelection[];
sceneResolutions: SceneImportResolution[];
npcResolutions?: NpcImportResolution[];
};
res: { project: Project; report: StorylineImportMergeReport };
};
@@ -453,6 +493,14 @@ export type IpcInvokeMap = {
req: { filePath: string };
res: { project: Project };
};
[ipcChannels.project.pickFoundrySource]: {
req: { mode: 'folder' | 'archive' };
res: { canceled: true } | { canceled: false; sourcePath: string };
};
[ipcChannels.project.importFoundry]: {
req: { sourcePath: string };
res: { project: Project };
};
[ipcChannels.project.exportZip]: {
req: { projectId: ProjectId; storylineSelections: StorylineSelection[]; labels: StorylineLabels };
res: { canceled: true } | { canceled: false };