feat(npcs): show multiple character overlays in session playback

Toggle NPCs independently from the characters window, keep one shared dim/close-all, and scroll descriptions for all selected characters.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-21 13:23:19 +08:00
parent f4c0ac1438
commit 1829191410
8 changed files with 538 additions and 121 deletions
+24 -12
View File
@@ -20,6 +20,7 @@ import { useSceneDarknessState } from '../shared/effects/useSceneDarknessState';
import { DEFAULT_MATERIALS_OVERLAY_LAYOUT, DEFAULT_NPCS_OVERLAY_LAYOUT } from '../../shared/types';
import { MaterialOverlay } from '../shared/materials/MaterialOverlay';
import { useMaterialsOverlayState } from '../shared/materials/useMaterialsOverlayState';
import { NpcsSceneOverlay } from '../shared/npcs/NpcsSceneOverlay';
import { useNpcsOverlayState } from '../shared/npcs/useNpcsOverlayState';
import { Button } from '../shared/ui/controls';
import { Surface } from '../shared/ui/Surface';
@@ -1611,15 +1612,24 @@ export function ControlApp() {
);
})()}
{(() => {
const activeNpc =
session?.project && npcsOverlay?.activeNpcId
? (session.project.npcs ?? []).find((n) => n.id === npcsOverlay.activeNpcId)
: undefined;
if (!activeNpc) return null;
const project = session?.project;
const activeIds = npcsOverlay?.activeNpcIds ?? [];
if (!project || activeIds.length === 0) return null;
const items = activeIds
.map((id) => {
const npc = (project.npcs ?? []).find((n) => n.id === id);
if (!npc) return null;
return {
npcId: npc.id,
assetId: npc.avatarAssetId,
layout: npcsOverlay?.layouts[id] ?? DEFAULT_NPCS_OVERLAY_LAYOUT,
};
})
.filter((x): x is NonNullable<typeof x> => x !== null);
if (items.length === 0) return null;
return (
<MaterialOverlay
assetId={activeNpc.avatarAssetId}
layout={npcsOverlay?.layout ?? DEFAULT_NPCS_OVERLAY_LAYOUT}
<NpcsSceneOverlay
items={items}
editable
zoomTool={npcsOverlay?.zoomTool ?? null}
showClose
@@ -1627,11 +1637,13 @@ export function ControlApp() {
onClose={() => {
void npcsApi.dispatch({ kind: 'hide' });
}}
onLayoutChange={(layout) => {
void npcsApi.dispatch({ kind: 'layout.set', layout });
onLayoutChange={(npcId, layout) => {
void npcsApi.dispatch({ kind: 'layout.set', npcId, layout });
}}
onZoomAt={(nx, ny) => {
void npcsApi.dispatch({ kind: 'zoomAt', nx, ny });
onZoomAt={(npcId, nx, ny) => {
void npcsApi.dispatch(
npcId ? { kind: 'zoomAt', nx, ny, npcId } : { kind: 'zoomAt', nx, ny },
);
}}
/>
);