Codex/generation task reliability #20
@@ -55,6 +55,7 @@ import { useCanvasHistory, type CanvasHistorySnapshot } from "./useCanvasHistory
|
|||||||
import { useCanvasKeyboard } from "./useCanvasKeyboard";
|
import { useCanvasKeyboard } from "./useCanvasKeyboard";
|
||||||
import { useCanvasNodeDrag } from "./useCanvasNodeDrag";
|
import { useCanvasNodeDrag } from "./useCanvasNodeDrag";
|
||||||
import { useCanvasGeneration, addCanvasGenKeepalive, removeCanvasGenKeepalive } from "./useCanvasGeneration";
|
import { useCanvasGeneration, addCanvasGenKeepalive, removeCanvasGenKeepalive } from "./useCanvasGeneration";
|
||||||
|
import { useCanvasAssetSummary, useCanvasVisibleNodes } from "./useCanvasDerivedState";
|
||||||
import {
|
import {
|
||||||
toHappyHorseDisplayModel,
|
toHappyHorseDisplayModel,
|
||||||
} from "../../utils/happyHorseRouting";
|
} from "../../utils/happyHorseRouting";
|
||||||
@@ -578,17 +579,7 @@ function CanvasPage({
|
|||||||
// Save immediately when user leaves page or switches tab (placed after runCanvasAutoSave definition)
|
// Save immediately when user leaves page or switches tab (placed after runCanvasAutoSave definition)
|
||||||
// — see useEffect below near runCanvasAutoSave
|
// — see useEffect below near runCanvasAutoSave
|
||||||
|
|
||||||
const canvasAssets = useMemo(
|
const { canvasAssets, assetCountsByCategory } = useCanvasAssetSummary(serverAssets);
|
||||||
() => serverAssets.filter((asset) => asset.imageUrl),
|
|
||||||
[serverAssets],
|
|
||||||
);
|
|
||||||
const assetCountsByCategory = useMemo(() => {
|
|
||||||
const counts = new Map<string, number>();
|
|
||||||
for (const asset of serverAssets) {
|
|
||||||
counts.set(asset.type, (counts.get(asset.type) ?? 0) + 1);
|
|
||||||
}
|
|
||||||
return counts;
|
|
||||||
}, [serverAssets]);
|
|
||||||
const shouldShowEmptyProjectState =
|
const shouldShowEmptyProjectState =
|
||||||
projectsLoaded && projects.length === 0 && !projectId && workflow.source === "blank" && workflow.nodes.length === 0;
|
projectsLoaded && projects.length === 0 && !projectId && workflow.source === "blank" && workflow.nodes.length === 0;
|
||||||
const isWaitingForProjects = isAuthenticated && !projectsLoaded;
|
const isWaitingForProjects = isAuthenticated && !projectsLoaded;
|
||||||
@@ -2650,16 +2641,17 @@ function CanvasPage({
|
|||||||
setConnectorDrag(null);
|
setConnectorDrag(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const collapsedPackageNodeKeys = useMemo(
|
const {
|
||||||
() => new Set(
|
isNodeCollapsedInPackage,
|
||||||
nodePackages.flatMap((nodePackage) =>
|
visibleTextNodes,
|
||||||
nodePackage.collapsed ? nodePackage.nodeIds.map((node) => getCanvasSelectionKey(node)) : []
|
visibleImageNodes,
|
||||||
)
|
visibleVideoNodes,
|
||||||
),
|
} = useCanvasVisibleNodes({
|
||||||
[nodePackages],
|
textNodes,
|
||||||
);
|
imageNodes,
|
||||||
const isNodeCollapsedInPackage = (kind: CanvasNodeKind, id: string) =>
|
videoNodes,
|
||||||
collapsedPackageNodeKeys.has(getCanvasSelectionKey({ kind, id }));
|
nodePackages,
|
||||||
|
});
|
||||||
const isLinkCollapsedInPackage = (link: { sourceKind: CanvasNodeKind; sourceNodeId: string; targetKind: CanvasNodeKind; targetNodeId: string }) =>
|
const isLinkCollapsedInPackage = (link: { sourceKind: CanvasNodeKind; sourceNodeId: string; targetKind: CanvasNodeKind; targetNodeId: string }) =>
|
||||||
isNodeCollapsedInPackage(link.sourceKind, link.sourceNodeId) ||
|
isNodeCollapsedInPackage(link.sourceKind, link.sourceNodeId) ||
|
||||||
isNodeCollapsedInPackage(link.targetKind, link.targetNodeId);
|
isNodeCollapsedInPackage(link.targetKind, link.targetNodeId);
|
||||||
@@ -2697,18 +2689,6 @@ function CanvasPage({
|
|||||||
return positionedLink ? [positionedLink] : [];
|
return positionedLink ? [positionedLink] : [];
|
||||||
}),
|
}),
|
||||||
].filter((link) => !isLinkCollapsedInPackage(link));
|
].filter((link) => !isLinkCollapsedInPackage(link));
|
||||||
const visibleTextNodes = useMemo(
|
|
||||||
() => textNodes.filter((textNode) => !isNodeCollapsedInPackage("text", textNode.id)),
|
|
||||||
[collapsedPackageNodeKeys, textNodes],
|
|
||||||
);
|
|
||||||
const visibleImageNodes = useMemo(
|
|
||||||
() => imageNodes.filter((imageNode) => !isNodeCollapsedInPackage("image", imageNode.id)),
|
|
||||||
[collapsedPackageNodeKeys, imageNodes],
|
|
||||||
);
|
|
||||||
const visibleVideoNodes = useMemo(
|
|
||||||
() => videoNodes.filter((videoNode) => !isNodeCollapsedInPackage("video", videoNode.id)),
|
|
||||||
[collapsedPackageNodeKeys, videoNodes],
|
|
||||||
);
|
|
||||||
const pendingLinkPreview =
|
const pendingLinkPreview =
|
||||||
pendingLinkPort && pendingLinkPreviewPoint
|
pendingLinkPort && pendingLinkPreviewPoint
|
||||||
? (() => {
|
? (() => {
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { useCallback, useMemo } from "react";
|
||||||
|
import type { ServerAssetItem } from "../../api/assetClient";
|
||||||
|
import type {
|
||||||
|
CanvasImageNode,
|
||||||
|
CanvasNodeKind,
|
||||||
|
CanvasNodePackage,
|
||||||
|
CanvasTextNode,
|
||||||
|
CanvasVideoNode,
|
||||||
|
} from "./canvasTypes";
|
||||||
|
import { getCanvasSelectionKey } from "./canvasUtils";
|
||||||
|
|
||||||
|
export function useCanvasAssetSummary(serverAssets: ServerAssetItem[]) {
|
||||||
|
return useMemo(() => {
|
||||||
|
const canvasAssets: ServerAssetItem[] = [];
|
||||||
|
const assetCountsByCategory = new Map<string, number>();
|
||||||
|
|
||||||
|
for (const asset of serverAssets) {
|
||||||
|
if (asset.imageUrl) {
|
||||||
|
canvasAssets.push(asset);
|
||||||
|
}
|
||||||
|
assetCountsByCategory.set(asset.type, (assetCountsByCategory.get(asset.type) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { canvasAssets, assetCountsByCategory };
|
||||||
|
}, [serverAssets]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCanvasVisibleNodes({
|
||||||
|
textNodes,
|
||||||
|
imageNodes,
|
||||||
|
videoNodes,
|
||||||
|
nodePackages,
|
||||||
|
}: {
|
||||||
|
textNodes: CanvasTextNode[];
|
||||||
|
imageNodes: CanvasImageNode[];
|
||||||
|
videoNodes: CanvasVideoNode[];
|
||||||
|
nodePackages: CanvasNodePackage[];
|
||||||
|
}) {
|
||||||
|
const collapsedPackageNodeKeys = useMemo(
|
||||||
|
() => new Set(
|
||||||
|
nodePackages.flatMap((nodePackage) =>
|
||||||
|
nodePackage.collapsed ? nodePackage.nodeIds.map((node) => getCanvasSelectionKey(node)) : []
|
||||||
|
)
|
||||||
|
),
|
||||||
|
[nodePackages],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isNodeCollapsedInPackage = useCallback(
|
||||||
|
(kind: CanvasNodeKind, id: string) =>
|
||||||
|
collapsedPackageNodeKeys.has(getCanvasSelectionKey({ kind, id })),
|
||||||
|
[collapsedPackageNodeKeys],
|
||||||
|
);
|
||||||
|
|
||||||
|
const visibleTextNodes = useMemo(
|
||||||
|
() => textNodes.filter((textNode) => !collapsedPackageNodeKeys.has(getCanvasSelectionKey({ kind: "text", id: textNode.id }))),
|
||||||
|
[collapsedPackageNodeKeys, textNodes],
|
||||||
|
);
|
||||||
|
const visibleImageNodes = useMemo(
|
||||||
|
() => imageNodes.filter((imageNode) => !collapsedPackageNodeKeys.has(getCanvasSelectionKey({ kind: "image", id: imageNode.id }))),
|
||||||
|
[collapsedPackageNodeKeys, imageNodes],
|
||||||
|
);
|
||||||
|
const visibleVideoNodes = useMemo(
|
||||||
|
() => videoNodes.filter((videoNode) => !collapsedPackageNodeKeys.has(getCanvasSelectionKey({ kind: "video", id: videoNode.id }))),
|
||||||
|
[collapsedPackageNodeKeys, videoNodes],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
collapsedPackageNodeKeys,
|
||||||
|
isNodeCollapsedInPackage,
|
||||||
|
visibleTextNodes,
|
||||||
|
visibleImageNodes,
|
||||||
|
visibleVideoNodes,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user