Files
Genarrative/apps/ai-game-creator-shell/src/view/project-development/resourceDependencyGraphModel.ts
T
menghao 62e0fe94ef
Project CI / Repository checks (push) Successful in 1m2s
Project CI / Frontend tests (push) Successful in 3m5s
Project CI / Backend tests (push) Successful in 3m41s
Project CI / Native shell tests (push) Successful in 13m7s
资源卡依赖关系及类型分类预览 (#129)
完成资源卡按类型和按依赖分类展现的功能

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/129
Co-authored-by: menghao <mh18530625731@163.com>
Co-committed-by: menghao <mh18530625731@163.com>
2026-08-05 19:15:46 +08:00

266 lines
8.7 KiB
TypeScript

export type ProjectResourceGraphNodeInput = {
resourceId: string;
manifestAssetId: string | null;
producerTaskId: string | null;
};
export type ProjectResourceReferenceEdge = {
id: string;
kind: 'asset-reference';
sourceResourceId: string;
targetResourceId: string;
cyclic: boolean;
};
export type ProjectResourceTaskFlow = {
id: string;
kind: 'task-flow';
sourceTaskId: string;
targetTaskId: string;
sourceResourceIds: string[];
targetResourceIds: string[];
cyclic: boolean;
};
export type ProjectResourceConnectionIndexDto = {
resourceId: string;
upstreamReferenceResourceIds: string[];
downstreamReferenceResourceIds: string[];
referenceEdgeIds: string[];
taskFlowIds: string[];
};
export type ProjectResourceProducerAssignment = {
resourceId: string;
taskId: string;
};
export type ProjectResourceDependencyDepth = {
resourceId: string;
dependencyDepth: number;
};
export type ProjectResourceGraphReadModel = {
resourceIds: string[];
referenceEdges: ProjectResourceReferenceEdge[];
taskFlows: ProjectResourceTaskFlow[];
connectionIndex: ProjectResourceConnectionIndexDto[];
producerAssignments: ProjectResourceProducerAssignment[];
dependencyDepths: ProjectResourceDependencyDepth[];
unresolvedReferenceResourceIds: string[];
cyclicResourceIds: string[];
cyclicTaskIds: string[];
producerMappingTruncated: boolean;
};
type ProjectResourceConnectionIndex = {
upstreamReferenceResourceIds: ReadonlySet<string>;
downstreamReferenceResourceIds: ReadonlySet<string>;
referenceEdgeIds: ReadonlySet<string>;
taskFlowIds: ReadonlySet<string>;
};
export type ProjectResourceGraph = {
resourceIds: ReadonlySet<string>;
referenceEdges: ProjectResourceReferenceEdge[];
referenceEdgeById: ReadonlyMap<string, ProjectResourceReferenceEdge>;
taskFlows: ProjectResourceTaskFlow[];
taskFlowById: ReadonlyMap<string, ProjectResourceTaskFlow>;
connectionIndex: ReadonlyMap<string, ProjectResourceConnectionIndex>;
producerTaskIdByResourceId: ReadonlyMap<string, string>;
dependencyDepthByResourceId: ReadonlyMap<string, number>;
unresolvedReferenceResourceIds: string[];
cyclicResourceIds: ReadonlySet<string>;
cyclicTaskIds: ReadonlySet<string>;
producerMappingTruncated: boolean;
};
export type ProjectResourceGraphNeighbors = {
upstreamResourceIds: ReadonlySet<string>;
downstreamResourceIds: ReadonlySet<string>;
connectedEdgeIds: ReadonlySet<string>;
};
const emptyStringSet: ReadonlySet<string> = new Set<string>();
const emptyStringMap: ReadonlyMap<string, string> = new Map<string, string>();
const emptyNumberMap: ReadonlyMap<string, number> = new Map<string, number>();
const emptyConnectionMap: ReadonlyMap<string, ProjectResourceConnectionIndex> =
new Map<string, ProjectResourceConnectionIndex>();
const emptyTaskFlowMap: ReadonlyMap<string, ProjectResourceTaskFlow> = new Map<
string,
ProjectResourceTaskFlow
>();
const emptyReferenceEdgeMap: ReadonlyMap<string, ProjectResourceReferenceEdge> =
new Map<string, ProjectResourceReferenceEdge>();
export const EMPTY_PROJECT_RESOURCE_GRAPH_NEIGHBORS: ProjectResourceGraphNeighbors =
{
upstreamResourceIds: emptyStringSet,
downstreamResourceIds: emptyStringSet,
connectedEdgeIds: emptyStringSet,
};
export const EMPTY_PROJECT_RESOURCE_GRAPH: ProjectResourceGraph = {
resourceIds: emptyStringSet,
referenceEdges: [],
referenceEdgeById: emptyReferenceEdgeMap,
taskFlows: [],
taskFlowById: emptyTaskFlowMap,
connectionIndex: emptyConnectionMap,
producerTaskIdByResourceId: emptyStringMap,
dependencyDepthByResourceId: emptyNumberMap,
unresolvedReferenceResourceIds: [],
cyclicResourceIds: emptyStringSet,
cyclicTaskIds: emptyStringSet,
producerMappingTruncated: false,
};
function uniqueSorted(values: Iterable<string>) {
return Array.from(new Set(values)).sort((left, right) =>
left < right ? -1 : left > right ? 1 : 0,
);
}
export function normalizeProjectResourceGraph(
readModel: ProjectResourceGraphReadModel,
): ProjectResourceGraph {
const producerMappingTruncated = Boolean(readModel.producerMappingTruncated);
const resourceIds = new Set(uniqueSorted(readModel.resourceIds));
const referenceEdges = readModel.referenceEdges
.filter(
(edge) =>
edge.kind === 'asset-reference' &&
resourceIds.has(edge.sourceResourceId) &&
resourceIds.has(edge.targetResourceId),
)
.sort((left, right) => left.id.localeCompare(right.id));
const referenceEdgeIds = new Set(referenceEdges.map((edge) => edge.id));
const taskFlows = (producerMappingTruncated ? [] : readModel.taskFlows)
.flatMap((flow) => {
if (flow.kind !== 'task-flow') {
return [];
}
const sourceResourceIds = uniqueSorted(
flow.sourceResourceIds.filter((resourceId) =>
resourceIds.has(resourceId),
),
);
const targetResourceIds = uniqueSorted(
flow.targetResourceIds.filter((resourceId) =>
resourceIds.has(resourceId),
),
);
return sourceResourceIds.length > 0 && targetResourceIds.length > 0
? [{ ...flow, sourceResourceIds, targetResourceIds }]
: [];
})
.sort((left, right) => left.id.localeCompare(right.id));
const taskFlowIds = new Set(taskFlows.map((flow) => flow.id));
const connectionIndex = new Map<string, ProjectResourceConnectionIndex>();
for (const index of readModel.connectionIndex) {
if (!resourceIds.has(index.resourceId)) {
continue;
}
connectionIndex.set(index.resourceId, {
upstreamReferenceResourceIds: new Set(
index.upstreamReferenceResourceIds.filter((resourceId) =>
resourceIds.has(resourceId),
),
),
downstreamReferenceResourceIds: new Set(
index.downstreamReferenceResourceIds.filter((resourceId) =>
resourceIds.has(resourceId),
),
),
referenceEdgeIds: new Set(
index.referenceEdgeIds.filter((edgeId) => referenceEdgeIds.has(edgeId)),
),
taskFlowIds: new Set(
index.taskFlowIds.filter((flowId) => taskFlowIds.has(flowId)),
),
});
}
const producerTaskIdByResourceId = new Map<string, string>();
const dependencyDepthByResourceId = new Map<string, number>();
for (const assignment of producerMappingTruncated
? []
: readModel.producerAssignments) {
if (!resourceIds.has(assignment.resourceId) || !assignment.taskId) {
continue;
}
producerTaskIdByResourceId.set(assignment.resourceId, assignment.taskId);
}
for (const depth of readModel.dependencyDepths) {
if (
resourceIds.has(depth.resourceId) &&
Number.isSafeInteger(depth.dependencyDepth) &&
depth.dependencyDepth >= 0
) {
dependencyDepthByResourceId.set(
depth.resourceId,
Math.max(
dependencyDepthByResourceId.get(depth.resourceId) ?? 0,
depth.dependencyDepth,
),
);
}
}
return {
resourceIds,
referenceEdges,
referenceEdgeById: new Map(referenceEdges.map((edge) => [edge.id, edge])),
taskFlows,
taskFlowById: new Map(taskFlows.map((flow) => [flow.id, flow])),
connectionIndex,
producerTaskIdByResourceId,
dependencyDepthByResourceId,
unresolvedReferenceResourceIds: uniqueSorted(
readModel.unresolvedReferenceResourceIds,
),
cyclicResourceIds: new Set(
readModel.cyclicResourceIds.filter((resourceId) =>
resourceIds.has(resourceId),
),
),
cyclicTaskIds: new Set(
producerMappingTruncated ? [] : readModel.cyclicTaskIds,
),
producerMappingTruncated,
};
}
export function projectResourceGraphNeighbors(
graph: ProjectResourceGraph,
resourceId: string | null,
): ProjectResourceGraphNeighbors {
if (!resourceId || !graph.resourceIds.has(resourceId)) {
return EMPTY_PROJECT_RESOURCE_GRAPH_NEIGHBORS;
}
const index = graph.connectionIndex.get(resourceId);
if (!index) {
return EMPTY_PROJECT_RESOURCE_GRAPH_NEIGHBORS;
}
const upstreamResourceIds = new Set(index.upstreamReferenceResourceIds);
const downstreamResourceIds = new Set(index.downstreamReferenceResourceIds);
const connectedEdgeIds = new Set(index.referenceEdgeIds);
for (const flowId of index.taskFlowIds) {
const flow = graph.taskFlowById.get(flowId);
if (!flow) {
continue;
}
if (flow.targetResourceIds.includes(resourceId)) {
flow.sourceResourceIds.forEach((id) => upstreamResourceIds.add(id));
connectedEdgeIds.add(flow.id);
}
if (flow.sourceResourceIds.includes(resourceId)) {
flow.targetResourceIds.forEach((id) => downstreamResourceIds.add(id));
connectedEdgeIds.add(flow.id);
}
}
upstreamResourceIds.delete(resourceId);
downstreamResourceIds.delete(resourceId);
return { upstreamResourceIds, downstreamResourceIds, connectedEdgeIds };
}