Codex/generation task reliability #20
@@ -55,6 +55,7 @@ import { useCanvasHistory, type CanvasHistorySnapshot } from "./useCanvasHistory
|
||||
import { useCanvasKeyboard } from "./useCanvasKeyboard";
|
||||
import { useCanvasNodeDrag } from "./useCanvasNodeDrag";
|
||||
import { useCanvasGeneration, addCanvasGenKeepalive, removeCanvasGenKeepalive } from "./useCanvasGeneration";
|
||||
import { useCanvasAssetSummary, useCanvasVisibleNodes } from "./useCanvasDerivedState";
|
||||
import {
|
||||
toHappyHorseDisplayModel,
|
||||
} from "../../utils/happyHorseRouting";
|
||||
@@ -578,17 +579,7 @@ function CanvasPage({
|
||||
// Save immediately when user leaves page or switches tab (placed after runCanvasAutoSave definition)
|
||||
// — see useEffect below near runCanvasAutoSave
|
||||
|
||||
const canvasAssets = useMemo(
|
||||
() => 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 { canvasAssets, assetCountsByCategory } = useCanvasAssetSummary(serverAssets);
|
||||
const shouldShowEmptyProjectState =
|
||||
projectsLoaded && projects.length === 0 && !projectId && workflow.source === "blank" && workflow.nodes.length === 0;
|
||||
const isWaitingForProjects = isAuthenticated && !projectsLoaded;
|
||||
@@ -2650,16 +2641,17 @@ function CanvasPage({
|
||||
setConnectorDrag(null);
|
||||
};
|
||||
|
||||
const collapsedPackageNodeKeys = useMemo(
|
||||
() => new Set(
|
||||
nodePackages.flatMap((nodePackage) =>
|
||||
nodePackage.collapsed ? nodePackage.nodeIds.map((node) => getCanvasSelectionKey(node)) : []
|
||||
)
|
||||
),
|
||||
[nodePackages],
|
||||
);
|
||||
const isNodeCollapsedInPackage = (kind: CanvasNodeKind, id: string) =>
|
||||
collapsedPackageNodeKeys.has(getCanvasSelectionKey({ kind, id }));
|
||||
const {
|
||||
isNodeCollapsedInPackage,
|
||||
visibleTextNodes,
|
||||
visibleImageNodes,
|
||||
visibleVideoNodes,
|
||||
} = useCanvasVisibleNodes({
|
||||
textNodes,
|
||||
imageNodes,
|
||||
videoNodes,
|
||||
nodePackages,
|
||||
});
|
||||
const isLinkCollapsedInPackage = (link: { sourceKind: CanvasNodeKind; sourceNodeId: string; targetKind: CanvasNodeKind; targetNodeId: string }) =>
|
||||
isNodeCollapsedInPackage(link.sourceKind, link.sourceNodeId) ||
|
||||
isNodeCollapsedInPackage(link.targetKind, link.targetNodeId);
|
||||
@@ -2697,18 +2689,6 @@ function CanvasPage({
|
||||
return positionedLink ? [positionedLink] : [];
|
||||
}),
|
||||
].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 =
|
||||
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