Browse Source

多选时不会自动播放视频, 模型过滤处理

feature/video-erase
Luckyu_js 1 week ago
parent
commit
9c070fcfae
  1. 7
      src/components/canvas/NodeToolSelectionController.tsx
  2. 9
      src/components/nodes/GenerateVideoNode.tsx
  3. 7
      src/components/nodes/VideoInputNode.tsx
  4. 4
      src/store/modelStore.ts
  5. 12
      src/store/nodeToolStore.ts

7
src/components/canvas/NodeToolSelectionController.tsx

@ -31,6 +31,7 @@ function selectSoleSelectedNodeId(nodes: WorkflowNode[]): string | null {
*/
export function NodeToolSelectionController() {
const setSelectionTool = useNodeToolStore((state) => state.setSelectionTool);
const setSoleSelectedNodeId = useNodeToolStore((state) => state.setSoleSelectedNodeId);
const { nodeId, tool } = useWorkflowStore(
useShallow((state) => {
@ -46,5 +47,11 @@ export function NodeToolSelectionController() {
setSelectionTool(nodeId, tool);
}, [setSelectionTool, nodeId, tool]);
// 单选 id 与派生面板分开写:前者对所有单选节点生效(如视频加载/播放),
// 后者仅在能派生 composer/高清时才有值。
useEffect(() => {
setSoleSelectedNodeId(nodeId);
}, [setSoleSelectedNodeId, nodeId]);
return null;
}

9
src/components/nodes/GenerateVideoNode.tsx

@ -26,7 +26,6 @@ import { formatUserFacingError } from "@/utils/userFacingErrors";
import {
readVideoGenerationConfig,
} from "@/utils/generationConfig";
import { useSelectedNodeCount } from "@/hooks/useSelectedNodeCount";
import { NodeActionCapsule, type NodeActionCapsuleAction } from "./NodeActionCapsule";
import { SINGLE_INPUT_HANDLE_ID, SINGLE_OUTPUT_HANDLE_ID } from "@/utils/nodeHandles";
import { SaveToAssetLibraryModal } from "@/components/SaveToAssetLibraryModal";
@ -399,10 +398,10 @@ export function GenerateVideoNodeView({ id, data, selected, suppressEmptyStateDe
prevStatusRef.current = nodeData.status;
}, [nodeData.status, displayError, t]);
const selectedNodeCount = useSelectedNodeCount();
const isSoleSelected = useNodeToolStore((state) => state.soleSelectedNodeId === id);
const activeNodeTool = useNodeToolStore((state) => getActiveNodeTool(state, id));
const showSelectedActions = Boolean(selected && selectedNodeCount === 1 && nodeData.outputVideo && !activeNodeTool);
const showEmptyStateDelete = Boolean(selected && selectedNodeCount === 1 && !nodeData.outputVideo && !suppressEmptyStateDelete);
const showSelectedActions = Boolean(isSoleSelected && nodeData.outputVideo && !activeNodeTool);
const showEmptyStateDelete = Boolean(isSoleSelected && !nodeData.outputVideo && !suppressEmptyStateDelete);
const emptyStateActions: NodeActionCapsuleAction[] = [
{
key: "delete",
@ -577,7 +576,7 @@ export function GenerateVideoNodeView({ id, data, selected, suppressEmptyStateDe
key={`${nodeData.videoHistory?.[nodeData.selectedVideoHistoryIndex || 0]?.id ?? nodeData.outputVideo}-${videoRetryNonce}`}
src={nodeData.outputVideo}
poster={nodeData.outputVideoCover}
selected={selected}
selected={isSoleSelected}
isRunning={isRunning || nodeData.status === "loading"}
loop
muted

7
src/components/nodes/VideoInputNode.tsx

@ -16,7 +16,6 @@ import { AssetPickerModal, PopiAssetItem } from "@/components/AssetPickerModal";
import { defaultNodeDimensions } from "@/constants/nodeDimensions";
import { uploadFileToGatewayMedia } from "@/utils/gatewayMediaUpload";
import { getPopiVideoAssetPreview } from "@/lib/popiAssetApi";
import { useSelectedNodeCount } from "@/hooks/useSelectedNodeCount";
import { NodeActionCapsule, type NodeActionCapsuleAction } from "./NodeActionCapsule";
import { SaveToAssetLibraryModal } from "@/components/SaveToAssetLibraryModal";
import { getAutoMediaElementClassName, getAutoMediaFrameClassName } from "./mediaAutoSize";
@ -305,9 +304,9 @@ export function VideoInputNodeView({ id, data, selected }: VideoInputNodeViewPro
switchNodeTool(id, "videoBoxErase");
}, [id, nodeData.video, switchNodeTool]);
const selectedNodeCount = useSelectedNodeCount();
const isSoleSelected = useNodeToolStore((state) => state.soleSelectedNodeId === id);
const activeNodeTool = useNodeToolStore((state) => getActiveNodeTool(state, id));
const showSelectedActions = Boolean(selected && selectedNodeCount === 1 && !activeNodeTool);
const showSelectedActions = Boolean(isSoleSelected && !activeNodeTool);
const selectedActions: NodeActionCapsuleAction[] = hasVideoAsset
? [
{
@ -448,7 +447,7 @@ export function VideoInputNodeView({ id, data, selected }: VideoInputNodeViewPro
key={`${nodeData.video ?? previewVideoPoster ?? "empty"}-${videoRetryNonce}`}
src={nodeData.video}
poster={previewVideoPoster}
selected={selected}
selected={isSoleSelected}
isRunning={isUploading}
className="absolute inset-0 h-full w-full"
videoClassName={getAutoMediaElementClassName(nodeData.dimensions, "transition-opacity duration-150")}

4
src/store/modelStore.ts

@ -80,8 +80,8 @@ function hasPopiKind(model: ProviderModel, kind: PopiModelMediaKind): boolean {
function groupPopiModels(models: ProviderModel[]): Record<PopiModelKind, ProviderModel[]> {
return {
image: models.filter((model) => !getMetadataSubTypes(model).some((subType) => subType === 107 || subType === 110 || subType === 112 || subType === 113) && hasPopiKind(model, "image")),
video: models.filter((model) => hasPopiKind(model, "video")),
image: models.filter((model) => getMetadataSubTypes(model).some((subType) => subType === 102 || subType === 103)),
video: models.filter((model) => getMetadataSubTypes(model).some((subType) => subType === 202 || subType === 203 || subType === 204)),
audio: models.filter((model) => hasPopiKind(model, "audio")),
"3d": models.filter((model) => hasCapability(model, ["text-to-3d", "image-to-3d"])),
llm: models.filter((model) => hasPopiKind(model, "llm") || Number(model.metadata?.type) === 5),

12
src/store/nodeToolStore.ts

@ -57,10 +57,17 @@ interface NodeToolState {
/** 当前单选节点派生出的面板(生成 composer / 高清),由选区控制器唯一写入。 */
selectionNodeId: string | null;
selectionTool: SelectionToolType | null;
/**
* id0 null
* `soleSelectedNodeId === id` O(N²)
* useSelectedNodeCount/
*/
soleSelectedNodeId: string | null;
openTool: (nodeId: string, tool: NodeToolType) => void;
switchTool: (nodeId: string, tool: NodeToolType) => void;
setSplitGridSelection: (selection: SplitGridSelection | null) => void;
setSelectionTool: (nodeId: string | null, tool: SelectionToolType | null) => void;
setSoleSelectedNodeId: (nodeId: string | null) => void;
closeTool: () => void;
closeToolForNode: (nodeId: string) => void;
hideTool: () => void;
@ -102,6 +109,7 @@ export const useNodeToolStore = create<NodeToolState>()((set, get) => ({
splitGridSelection: null,
selectionNodeId: null,
selectionTool: null,
soleSelectedNodeId: null,
openTool: (nodeId, tool) => set({ activeNodeId: nodeId, activeTool: tool, visible: true, splitGridSelection: null }),
switchTool: (nodeId, tool) => set({ activeNodeId: nodeId, activeTool: tool, visible: true, splitGridSelection: null }),
setSplitGridSelection: (selection) => set({ splitGridSelection: selection }),
@ -111,6 +119,10 @@ export const useNodeToolStore = create<NodeToolState>()((set, get) => ({
if (state.selectionNodeId === nextNodeId && state.selectionTool === tool) return;
set({ selectionNodeId: nextNodeId, selectionTool: tool });
},
setSoleSelectedNodeId: (nodeId) => {
if (get().soleSelectedNodeId === nodeId) return;
set({ soleSelectedNodeId: nodeId });
},
// 关闭仅清理显式工具状态,保留 selection* —— 关掉 crop 等工具后,选区派生的
// composer 能凭 selectionTool 自动重新出现,无需反向重开逻辑。
closeTool: () => set({ activeNodeId: null, activeTool: null, visible: false, splitGridSelection: null }),

Loading…
Cancel
Save