Browse Source

视频高清处理

feature/video-erase
Luckyu_js 1 week ago
parent
commit
5d63e3ed2d
  1. 4
      src/components/__tests__/GenerateAudioNode.test.tsx
  2. 4
      src/components/__tests__/GenerateImageNode.test.tsx
  3. 53
      src/components/__tests__/GenerateVideoNode.test.tsx
  4. 4
      src/components/__tests__/GenerationComposer.test.tsx
  5. 4
      src/components/__tests__/LLMGenerateNode.test.tsx
  6. 4
      src/components/__tests__/ModelSearchDialog.test.tsx
  7. 7
      src/components/canvas/ActiveVideoEditHost.tsx
  8. 7
      src/components/composer/NodeHighDefinitionPanel.tsx
  9. 13
      src/components/nodes/GenerateVideoNode.tsx
  10. 6
      src/components/nodes/VideoInputNode.tsx
  11. 36
      src/store/__tests__/modelStore.test.ts
  12. 38
      src/store/modelStore.ts
  13. 1
      src/utils/highDefinitionVideoNodes.ts

4
src/components/__tests__/GenerateAudioNode.test.tsx

@ -36,6 +36,10 @@ vi.mock("@/store/modelStore", () => ({
multiAngle: [], multiAngle: [],
highDefinition: [], highDefinition: [],
outpainting: [], outpainting: [],
inpainting: [],
videoEnhance: [],
videoSmartErase: [],
videoBoxErase: [],
}, },
all: [ all: [
...mockPopiserverModelLists.image.models, ...mockPopiserverModelLists.image.models,

4
src/components/__tests__/GenerateImageNode.test.tsx

@ -51,6 +51,10 @@ vi.mock("@/store/modelStore", () => ({
multiAngle: [], multiAngle: [],
highDefinition: [], highDefinition: [],
outpainting: [], outpainting: [],
inpainting: [],
videoEnhance: [],
videoSmartErase: [],
videoBoxErase: [],
}, },
all: [ all: [
...mockPopiserverModelLists.image.models, ...mockPopiserverModelLists.image.models,

53
src/components/__tests__/GenerateVideoNode.test.tsx

@ -55,6 +55,10 @@ vi.mock("@/store/modelStore", () => ({
multiAngle: [], multiAngle: [],
highDefinition: [], highDefinition: [],
outpainting: [], outpainting: [],
inpainting: [],
videoEnhance: [],
videoSmartErase: [],
videoBoxErase: [],
}, },
all: [ all: [
...mockPopiserverModelLists.image.models, ...mockPopiserverModelLists.image.models,
@ -1138,6 +1142,55 @@ describe("GenerateVideoNode", () => {
String(call[0]).startsWith("/api/models/99") String(call[0]).startsWith("/api/models/99")
)).toBe(false); )).toBe(false);
}); });
it("does not auto-switch a high-definition derived node to a default video model", async () => {
// 高清派生节点用的是 206 增强模型(不在 byKind.video 生成模型列表里)。
// 自动选型必须跳过派生视频节点,否则会把模型替换成默认视频模型并多查一次其详情。
mockFetch.mockImplementation((url) => {
const requestUrl = String(url);
if (requestUrl.startsWith("/api/models?")) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
models: [
{ id: "32", name: "First Popi Video", provider: "popiserver", capabilities: ["text-to-video"] },
],
}),
});
}
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ parameters: [], inputs: [] }),
});
});
mockPopiserverModelLists.video.models = [
{ id: "32", name: "First Popi Video", provider: "popiserver", capabilities: ["text-to-video"], description: null },
];
render(
<TestWrapper>
<GenerateVideoNode {...createNodeProps({
selectedModel: { provider: "popiserver", modelId: "206-enhance", displayName: "画质增强" },
derivedVideo: { sourceNodeId: "source-node", operation: "highDefinition" },
} as Partial<GenerateVideoNodeData>)} />
</TestWrapper>
);
// 给 effect 一个执行机会
await new Promise((resolve) => setTimeout(resolve, 0));
// 不应把节点模型替换成默认视频模型(id 32)
expect(mockUpdateNodeData).not.toHaveBeenCalledWith(
"test-node-1",
expect.objectContaining({
selectedModel: expect.objectContaining({ modelId: "32" }),
})
);
// 不应多查默认模型 32 的详情
expect(mockFetch.mock.calls.some((call) =>
String(call[0]).startsWith("/api/models/32")
)).toBe(false);
});
}); });
describe("Fetch Models on Mount", () => { describe("Fetch Models on Mount", () => {

4
src/components/__tests__/GenerationComposer.test.tsx

@ -770,6 +770,10 @@ describe("GenerationComposer", () => {
multiAngle: [], multiAngle: [],
highDefinition: [], highDefinition: [],
outpainting: [], outpainting: [],
inpainting: [],
videoEnhance: [],
videoSmartErase: [],
videoBoxErase: [],
}, },
detailsById: {}, detailsById: {},
detailLoadingById: {}, detailLoadingById: {},

4
src/components/__tests__/LLMGenerateNode.test.tsx

@ -32,6 +32,10 @@ vi.mock("@/store/modelStore", () => ({
multiAngle: [], multiAngle: [],
highDefinition: [], highDefinition: [],
outpainting: [], outpainting: [],
inpainting: [],
videoEnhance: [],
videoSmartErase: [],
videoBoxErase: [],
}, },
detailsById: {}, detailsById: {},
detailLoadingById: {}, detailLoadingById: {},

4
src/components/__tests__/ModelSearchDialog.test.tsx

@ -60,6 +60,10 @@ vi.mock("@/store/modelStore", () => ({
multiAngle: mockPopiModels.filter((model) => model.metadata?.subType === 113), multiAngle: mockPopiModels.filter((model) => model.metadata?.subType === 113),
highDefinition: mockPopiModels.filter((model) => model.metadata?.subType === 107), highDefinition: mockPopiModels.filter((model) => model.metadata?.subType === 107),
outpainting: mockPopiModels.filter((model) => model.metadata?.subType === 112), outpainting: mockPopiModels.filter((model) => model.metadata?.subType === 112),
inpainting: mockPopiModels.filter((model) => model.metadata?.subType === 110),
videoEnhance: mockPopiModels.filter((model) => model.metadata?.subType === 206),
videoSmartErase: mockPopiModels.filter((model) => model.metadata?.subType === 207),
videoBoxErase: mockPopiModels.filter((model) => model.metadata?.subType === 208),
}, },
initializePopiModels: vi.fn(), initializePopiModels: vi.fn(),
refreshPopiModels: mockRefreshPopiModels, refreshPopiModels: mockRefreshPopiModels,

7
src/components/canvas/ActiveVideoEditHost.tsx

@ -24,7 +24,8 @@ export function ActiveVideoEditHost() {
const targetNodeId = isVideoEditTool ? activeNodeId : null; const targetNodeId = isVideoEditTool ? activeNodeId : null;
const frame = useNodeCanvasFrame(targetNodeId); const frame = useNodeCanvasFrame(targetNodeId);
const videoModels = useModelStore((state) => state.byKind.video); const videoSmartEraseModels = useModelStore((state) => state.byKind.videoSmartErase);
const videoBoxEraseModels = useModelStore((state) => state.byKind.videoBoxErase);
const videoSource = frame ? getNodeVideoSource(frame.node) : null; const videoSource = frame ? getNodeVideoSource(frame.node) : null;
@ -50,14 +51,14 @@ export function ActiveVideoEditHost() {
<VideoBoxEraseSession <VideoBoxEraseSession
nodeId={node.id} nodeId={node.id}
dimensions={sessionDimensions} dimensions={sessionDimensions}
models={videoModels} models={videoBoxEraseModels}
onClose={closeTool} onClose={closeTool}
/> />
) : ( ) : (
<VideoSmartEraseSession <VideoSmartEraseSession
nodeId={node.id} nodeId={node.id}
dimensions={sessionDimensions} dimensions={sessionDimensions}
models={videoModels} models={videoSmartEraseModels}
onClose={closeTool} onClose={closeTool}
/> />
)} )}

7
src/components/composer/NodeHighDefinitionPanel.tsx

@ -20,7 +20,6 @@ import { buildHighDefinitionParameters, chooseHighDefinitionModel, HIGH_DEFINITI
import { import {
chooseHighDefinitionVideoModel, chooseHighDefinitionVideoModel,
isHighDefinitionDerivedVideoNode, isHighDefinitionDerivedVideoNode,
isHighDefinitionVideoModel,
} from "@/utils/highDefinitionVideoNodes"; } from "@/utils/highDefinitionVideoNodes";
import { toSelectedModel } from "@/utils/selectedModel"; import { toSelectedModel } from "@/utils/selectedModel";
@ -74,11 +73,7 @@ export function NodeHighDefinitionPanel({ node }: NodeHighDefinitionPanelProps)
const isRunning = useWorkflowStore((state) => state.runningNodeIds.has(node.id)); const isRunning = useWorkflowStore((state) => state.runningNodeIds.has(node.id));
const isVideoHighDefinitionNode = isHighDefinitionDerivedVideoNode(node.data); const isVideoHighDefinitionNode = isHighDefinitionDerivedVideoNode(node.data);
const highDefinitionModels = useModelStore((state) => state.byKind.highDefinition); const highDefinitionModels = useModelStore((state) => state.byKind.highDefinition);
const videoModels = useModelStore((state) => state.byKind.video); const videoEnhanceModels = useModelStore((state) => state.byKind.videoEnhance);
const videoEnhanceModels = useMemo(
() => videoModels.filter(isHighDefinitionVideoModel),
[videoModels]
);
const fetchModelDetail = useModelStore((state) => state.fetchModelDetail); const fetchModelDetail = useModelStore((state) => state.fetchModelDetail);
const data = node.data as SmartImageNodeData | SmartVideoNodeData; const data = node.data as SmartImageNodeData | SmartVideoNodeData;
const hydratedKeyRef = useRef<string | null>(null); const hydratedKeyRef = useRef<string | null>(null);

13
src/components/nodes/GenerateVideoNode.tsx

@ -33,7 +33,7 @@ import { asMediaDimensions, getAutoMediaElementClassName, getAutoMediaFrameClass
import { CustomVideoPlayer } from "@/components/media/CustomVideoPlayer"; import { CustomVideoPlayer } from "@/components/media/CustomVideoPlayer";
import { createVideoFrameSmartImageNode } from "@/utils/videoFrameNode"; import { createVideoFrameSmartImageNode } from "@/utils/videoFrameNode";
import { chooseHighDefinitionVideoModel, createHighDefinitionVideoNode } from "@/utils/highDefinitionVideoNodes"; import { chooseHighDefinitionVideoModel, createHighDefinitionVideoNode } from "@/utils/highDefinitionVideoNodes";
import { isSmartVideoAutoTaskNode } from "@/utils/smartMediaMode"; import { getDerivedMediaInfo } from "@/utils/derivedMediaNodes";
import { getActiveNodeTool, useNodeToolStore } from "@/store/nodeToolStore"; import { getActiveNodeTool, useNodeToolStore } from "@/store/nodeToolStore";
import { createVideoEraseActionPanel } from "@/components/media/MediaEditActionDropdown"; import { createVideoEraseActionPanel } from "@/components/media/MediaEditActionDropdown";
import { VideoEraseMenuIcon } from "@/components/media/MediaToolbarIcons"; import { VideoEraseMenuIcon } from "@/components/media/MediaToolbarIcons";
@ -68,6 +68,7 @@ export function GenerateVideoNodeView({ id, data, selected, suppressEmptyStateDe
const updateMediaNodeData = useWorkflowStore((state) => state.updateMediaNodeData); const updateMediaNodeData = useWorkflowStore((state) => state.updateMediaNodeData);
const updateVideoPreference = useGenerationPreferenceStore((state) => state.updateVideoPreference); const updateVideoPreference = useGenerationPreferenceStore((state) => state.updateVideoPreference);
const popiserverVideoModels = useModelStore((state) => state.byKind.video); const popiserverVideoModels = useModelStore((state) => state.byKind.video);
const popiserverVideoEnhanceModels = useModelStore((state) => state.byKind.videoEnhance);
const [isBrowseDialogOpen, setIsBrowseDialogOpen] = useState(false); const [isBrowseDialogOpen, setIsBrowseDialogOpen] = useState(false);
const [isLoadingCarouselVideo, setIsLoadingCarouselVideo] = useState(false); const [isLoadingCarouselVideo, setIsLoadingCarouselVideo] = useState(false);
const [isPreviewOpen, setIsPreviewOpen] = useState(false); const [isPreviewOpen, setIsPreviewOpen] = useState(false);
@ -115,9 +116,9 @@ export function GenerateVideoNodeView({ id, data, selected, suppressEmptyStateDe
useEffect(() => { useEffect(() => {
if (modelOptions.length === 0) return; if (modelOptions.length === 0) return;
// 视频派生节点使用内置模型或不需要生成模型(裁剪为本地处理),不参与 // 视频派生节点(裁剪/擦除/高清)使用各自的内置或专属模型,不参与自动选型,
// 自动选型,否则会被替换成默认视频生成模型、丢失派生语义。 // 否则会被替换成默认视频生成模型、丢失派生语义并多触发一次默认模型详情请求
if (isSmartVideoAutoTaskNode(nodeData)) return; if (getDerivedMediaInfo(nodeData)?.field === "derivedVideo") return;
const selectedModelExists = modelOptions.some((model) => model.id === nodeConfig.selectedModel?.modelId); const selectedModelExists = modelOptions.some((model) => model.id === nodeConfig.selectedModel?.modelId);
if (nodeConfig.selectedModel?.provider === currentProvider && nodeConfig.selectedModel.modelId && selectedModelExists) return; if (nodeConfig.selectedModel?.provider === currentProvider && nodeConfig.selectedModel.modelId && selectedModelExists) return;
const selectedModel = chooseModelFromList(modelOptions, nodeConfig.selectedModel); const selectedModel = chooseModelFromList(modelOptions, nodeConfig.selectedModel);
@ -363,12 +364,12 @@ export function GenerateVideoNodeView({ id, data, selected, suppressEmptyStateDe
y: currentPosition.y, y: currentPosition.y,
}, },
dimensions: asStrictMediaDimensions(nodeData.dimensions), dimensions: asStrictMediaDimensions(nodeData.dimensions),
selectedModel: chooseHighDefinitionVideoModel(popiserverVideoModels), selectedModel: chooseHighDefinitionVideoModel(popiserverVideoEnhanceModels),
addNode, addNode,
onConnect, onConnect,
selectSingleNode, selectSingleNode,
}); });
}, [addNode, getNodeById, id, nodeData.dimensions, nodeData.outputVideo, onConnect, popiserverVideoModels, selectSingleNode]); }, [addNode, getNodeById, id, nodeData.dimensions, nodeData.outputVideo, onConnect, popiserverVideoEnhanceModels, selectSingleNode]);
const switchNodeTool = useNodeToolStore((state) => state.switchTool); const switchNodeTool = useNodeToolStore((state) => state.switchTool);

6
src/components/nodes/VideoInputNode.tsx

@ -87,7 +87,7 @@ export function VideoInputNodeView({ id, data, selected }: VideoInputNodeViewPro
const getNodeById = useWorkflowStore((state) => state.getNodeById); const getNodeById = useWorkflowStore((state) => state.getNodeById);
const selectSingleNode = useWorkflowStore((state) => state.selectSingleNode); const selectSingleNode = useWorkflowStore((state) => state.selectSingleNode);
const regenerateNode = useWorkflowStore((state) => state.regenerateNode); const regenerateNode = useWorkflowStore((state) => state.regenerateNode);
const popiserverVideoModels = useModelStore((state) => state.byKind.video); const popiserverVideoEnhanceModels = useModelStore((state) => state.byKind.videoEnhance);
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [isPreviewOpen, setIsPreviewOpen] = useState(false); const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const [isAssetPickerOpen, setIsAssetPickerOpen] = useState(false); const [isAssetPickerOpen, setIsAssetPickerOpen] = useState(false);
@ -280,12 +280,12 @@ export function VideoInputNodeView({ id, data, selected }: VideoInputNodeViewPro
y: currentPosition.y, y: currentPosition.y,
}, },
dimensions: nodeData.dimensions, dimensions: nodeData.dimensions,
selectedModel: chooseHighDefinitionVideoModel(popiserverVideoModels), selectedModel: chooseHighDefinitionVideoModel(popiserverVideoEnhanceModels),
addNode, addNode,
onConnect, onConnect,
selectSingleNode, selectSingleNode,
}); });
}, [addNode, getNodeById, id, nodeData.dimensions, nodeData.video, onConnect, popiserverVideoModels, selectSingleNode]); }, [addNode, getNodeById, id, nodeData.dimensions, nodeData.video, onConnect, popiserverVideoEnhanceModels, selectSingleNode]);
const switchNodeTool = useNodeToolStore((state) => state.switchTool); const switchNodeTool = useNodeToolStore((state) => state.switchTool);

36
src/store/__tests__/modelStore.test.ts

@ -1,7 +1,11 @@
import { beforeEach, describe, expect, it } from "vitest"; import { beforeEach, describe, expect, it } from "vitest";
import { useModelStore } from "@/store/modelStore"; import { useModelStore } from "@/store/modelStore";
import type { ProviderModel } from "@/lib/providers/types"; import type { ProviderModel } from "@/lib/providers/types";
import { POPI_VIDEO_SUBTYPE_ENHANCE } from "@/constants/generationTask"; import {
POPI_VIDEO_SUBTYPE_ENHANCE,
POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE,
POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE_PRO,
} from "@/constants/generationTask";
function model(id: string, subType: number): ProviderModel { function model(id: string, subType: number): ProviderModel {
return { return {
@ -32,6 +36,9 @@ describe("modelStore", () => {
highDefinition: [], highDefinition: [],
outpainting: [], outpainting: [],
inpainting: [], inpainting: [],
videoEnhance: [],
videoSmartErase: [],
videoBoxErase: [],
}, },
detailsById: {}, detailsById: {},
detailLoadingById: {}, detailLoadingById: {},
@ -75,7 +82,7 @@ describe("modelStore", () => {
expect(byKind.image).toEqual([regularImage]); expect(byKind.image).toEqual([regularImage]);
}); });
it("groups subtype 206 models into the video category", () => { it("groups subtype 206/207/208 models into their dedicated video tool categories", () => {
const regularImage = model("regular-image", 103); const regularImage = model("regular-image", 103);
const videoEnhance = { const videoEnhance = {
...model("video-enhance", POPI_VIDEO_SUBTYPE_ENHANCE), ...model("video-enhance", POPI_VIDEO_SUBTYPE_ENHANCE),
@ -86,11 +93,32 @@ describe("modelStore", () => {
subTypes: [POPI_VIDEO_SUBTYPE_ENHANCE], subTypes: [POPI_VIDEO_SUBTYPE_ENHANCE],
}, },
} satisfies ProviderModel; } satisfies ProviderModel;
const videoSmartErase = {
...model("video-smart-erase", POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE),
capabilities: ["image-to-video"],
metadata: {
type: 2,
subType: POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE,
subTypes: [POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE],
},
} satisfies ProviderModel;
const videoBoxErase = {
...model("video-box-erase", POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE_PRO),
capabilities: ["image-to-video"],
metadata: {
type: 2,
subType: POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE_PRO,
subTypes: [POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE_PRO],
},
} satisfies ProviderModel;
useModelStore.getState().setPopiModels([regularImage, videoEnhance]); useModelStore.getState().setPopiModels([regularImage, videoEnhance, videoSmartErase, videoBoxErase]);
const byKind = useModelStore.getState().byKind; const byKind = useModelStore.getState().byKind;
expect(byKind.video).toEqual([videoEnhance]); expect(byKind.videoEnhance).toEqual([videoEnhance]);
expect(byKind.videoSmartErase).toEqual([videoSmartErase]);
expect(byKind.videoBoxErase).toEqual([videoBoxErase]);
expect(byKind.video).toEqual([]);
expect(byKind.image).toEqual([regularImage]); expect(byKind.image).toEqual([regularImage]);
}); });
}); });

38
src/store/modelStore.ts

@ -1,12 +1,32 @@
import { create } from "zustand"; import { create } from "zustand";
import type { ModelInput, ModelParameter, ProviderModel, ModelCapability } from "@/lib/providers/types"; import type { ModelInput, ModelParameter, ProviderModel, ModelCapability } from "@/lib/providers/types";
import {
POPI_VIDEO_SUBTYPE_ENHANCE,
POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE,
POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE_PRO,
POPI_VIDEO_SUBTYPE_FIRST_LAST_FRAME,
POPI_VIDEO_SUBTYPE_IMAGE_TO_VIDEO,
POPI_VIDEO_SUBTYPE_OMNI_REFERENCE,
} from "@/constants/generationTask";
import { import {
getPopiCapabilitiesForKind, getPopiCapabilitiesForKind,
getPopiKindForSubType, getPopiKindForSubType,
type PopiModelMediaKind, type PopiModelMediaKind,
} from "@/utils/popiModelClassification"; } from "@/utils/popiModelClassification";
export type PopiModelKind = "image" | "video" | "audio" | "3d" | "llm" | "multiAngle" | "highDefinition" | "outpainting" | "inpainting"; export type PopiModelKind =
| "image"
| "video"
| "audio"
| "3d"
| "llm"
| "multiAngle"
| "highDefinition"
| "outpainting"
| "inpainting"
| "videoEnhance"
| "videoSmartErase"
| "videoBoxErase";
export type PopiModelExtensionFields = Record<string, unknown> | unknown[]; export type PopiModelExtensionFields = Record<string, unknown> | unknown[];
interface ModelListState { interface ModelListState {
@ -55,6 +75,9 @@ const emptyByKind: Record<PopiModelKind, ProviderModel[]> = {
highDefinition: [], highDefinition: [],
outpainting: [], outpainting: [],
inpainting: [], inpainting: [],
videoEnhance: [],
videoSmartErase: [],
videoBoxErase: [],
}; };
let popiModelsRequest: Promise<ProviderModel[]> | null = null; let popiModelsRequest: Promise<ProviderModel[]> | null = null;
@ -78,10 +101,18 @@ function hasPopiKind(model: ProviderModel, kind: PopiModelMediaKind): boolean {
return hasCapability(model, getPopiCapabilitiesForKind(kind)); return hasCapability(model, getPopiCapabilitiesForKind(kind));
} }
function hasAnySubType(model: ProviderModel, subTypes: number[]): boolean {
return getMetadataSubTypes(model).some((subType) => subTypes.includes(subType));
}
function groupPopiModels(models: ProviderModel[]): Record<PopiModelKind, ProviderModel[]> { function groupPopiModels(models: ProviderModel[]): Record<PopiModelKind, ProviderModel[]> {
return { return {
image: models.filter((model) => getMetadataSubTypes(model).some((subType) => subType === 102 || subType === 103)), 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)), video: models.filter((model) => hasAnySubType(model, [
POPI_VIDEO_SUBTYPE_IMAGE_TO_VIDEO,
POPI_VIDEO_SUBTYPE_OMNI_REFERENCE,
POPI_VIDEO_SUBTYPE_FIRST_LAST_FRAME,
])),
audio: models.filter((model) => hasPopiKind(model, "audio")), audio: models.filter((model) => hasPopiKind(model, "audio")),
"3d": models.filter((model) => hasCapability(model, ["text-to-3d", "image-to-3d"])), "3d": models.filter((model) => hasCapability(model, ["text-to-3d", "image-to-3d"])),
llm: models.filter((model) => hasPopiKind(model, "llm") || Number(model.metadata?.type) === 5), llm: models.filter((model) => hasPopiKind(model, "llm") || Number(model.metadata?.type) === 5),
@ -89,6 +120,9 @@ function groupPopiModels(models: ProviderModel[]): Record<PopiModelKind, Provide
highDefinition: models.filter((model) => getMetadataSubTypes(model).includes(107)), highDefinition: models.filter((model) => getMetadataSubTypes(model).includes(107)),
outpainting: models.filter((model) => getMetadataSubTypes(model).includes(112)), outpainting: models.filter((model) => getMetadataSubTypes(model).includes(112)),
inpainting: models.filter((model) => getMetadataSubTypes(model).includes(110)), inpainting: models.filter((model) => getMetadataSubTypes(model).includes(110)),
videoEnhance: models.filter((model) => hasAnySubType(model, [POPI_VIDEO_SUBTYPE_ENHANCE])),
videoSmartErase: models.filter((model) => hasAnySubType(model, [POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE])),
videoBoxErase: models.filter((model) => hasAnySubType(model, [POPI_VIDEO_SUBTYPE_ERASE_SUBTITLE_PRO])),
}; };
} }

1
src/utils/highDefinitionVideoNodes.ts

@ -12,6 +12,7 @@ export const HIGH_DEFINITION_VIDEO_FALLBACK_MODEL: SelectedModel = {
modelId: "", modelId: "",
displayName: "画质增强", displayName: "画质增强",
capabilities: ["image-to-video"], capabilities: ["image-to-video"],
isSupportVideos: true,
type: 2, type: 2,
subType: POPI_VIDEO_SUBTYPE_ENHANCE, subType: POPI_VIDEO_SUBTYPE_ENHANCE,
subTypes: [POPI_VIDEO_SUBTYPE_ENHANCE], subTypes: [POPI_VIDEO_SUBTYPE_ENHANCE],

Loading…
Cancel
Save