Browse Source

删除旧字段

feature/071
yun 2 weeks ago
parent
commit
f46b06a75d
  1. 159
      src/components/nodes/ControlPanel.tsx
  2. 17
      src/store/generationPreferenceStore.ts
  3. 18
      src/store/utils/nodeDefaults.ts
  4. 12
      src/types/nodes.ts
  5. 7
      src/utils/defaultGenerationNodeModel.ts

159
src/components/nodes/ControlPanel.tsx

@ -4,7 +4,7 @@ import { useMemo, useState, useCallback, useEffect, useRef } from "react";
import { createPortal } from "react-dom";
import { Node } from "@xyflow/react";
import { useWorkflowStore, saveNanoBananaDefaults, useProviderApiKeys } from "@/store/workflowStore";
import { NodeType, NanoBananaNodeData, GenerateVideoNodeData, Generate3DNodeData, GenerateAudioNodeData, EaseCurveNodeData, ConditionalSwitchNodeData, AspectRatio, Resolution, ModelType, MODEL_DISPLAY_NAMES, ProviderType, SelectedModel, MatchMode, ConditionalSwitchRule } from "@/types";
import { NodeType, NanoBananaNodeData, Generate3DNodeData, GenerateAudioNodeData, EaseCurveNodeData, ConditionalSwitchNodeData, AspectRatio, Resolution, ModelType, MODEL_DISPLAY_NAMES, ProviderType, SelectedModel, MatchMode, ConditionalSwitchRule } from "@/types";
import { ProviderModel, ModelCapability } from "@/lib/providers/types";
import { ModelSearchDialog } from "@/components/modals/ModelSearchDialog";
import { ModelParameters } from "./ModelParameters";
@ -21,8 +21,6 @@ import { isPopiProviderMode, POPI_PROVIDER_ID } from "@/lib/providerMode";
import { appendPopiModelQueryParams } from "@/utils/popiModelFilters";
import { SINGLE_INPUT_HANDLE_ID } from "@/utils/nodeHandles";
import { toSelectedModel } from "@/utils/selectedModel";
import {
} from "@/utils/videoGenerationSettings";
// List of node types that have configurable parameters
const CONFIGURABLE_NODE_TYPES: NodeType[] = [
@ -150,9 +148,6 @@ export function ControlPanel() {
{(selectedNode.type === "smartImage" || selectedNode.type === "nanoBanana") && (
<GenerateImageControls node={selectedNode} />
)}
{selectedNode.type === "generateVideo" && (
<GenerateVideoControls node={selectedNode} />
)}
{selectedNode.type === "generate3d" && (
<Generate3DControls node={selectedNode} />
)}
@ -510,158 +505,6 @@ function GenerateImageControls({ node }: { node: Node }) {
);
}
// Generate Video Controls
function GenerateVideoControls({ node }: { node: Node }) {
const nodeData = node.data as GenerateVideoNodeData;
const { t } = useI18n();
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
const regenerateNode = useWorkflowStore((state) => state.regenerateNode);
const isRunning = useWorkflowStore((state) => state.isRunning);
const [isBrowseDialogOpen, setIsBrowseDialogOpen] = useState(false);
const popiProviderMode = isPopiProviderMode();
const currentProvider: ProviderType = nodeData.selectedModel?.provider || (popiProviderMode ? POPI_PROVIDER_ID : "fal");
const handleParametersChange = useCallback(
(parameters: Record<string, unknown>) => {
updateNodeData(node.id, { parameters });
},
[node.id, updateNodeData]
);
const handleBrowseModelSelect = useCallback((model: ProviderModel) => {
const newSelectedModel: SelectedModel = toSelectedModel(model);
updateNodeData(node.id, {
selectedModel: newSelectedModel,
resolution: nodeData.resolution ?? DEFAULT_VIDEO_RESOLUTION,
durationSeconds: nodeData.durationSeconds ?? DEFAULT_VIDEO_DURATION_SECONDS,
soundEnabled: getDefaultVideoSoundEnabled(newSelectedModel),
parameters: {},
});
setIsBrowseDialogOpen(false);
}, [node.id, nodeData.durationSeconds, nodeData.resolution, updateNodeData]);
const videoSoundSupported = modelSupportsVideoSound(nodeData.selectedModel);
const hiddenVideoParameterNames = videoSoundSupported
? VIDEO_FIRST_CLASS_PARAMETER_NAMES
: [...VIDEO_DURATION_PARAMETER_NAMES, ...VIDEO_RESOLUTION_PARAMETER_NAMES];
return (
<>
<div className="space-y-3">
{/* Model name + provider with link */}
<div className="border-t border-neutral-700 pt-3">
<div className="flex items-start gap-2">
<div className="flex-1 min-w-0">
<div className="text-sm text-neutral-100 truncate">
{nodeData.selectedModel?.displayName || t("node.selectModel")}
</div>
{!popiProviderMode && (
<div className="flex items-center gap-1 mt-0.5">
<span className="text-[10px] text-neutral-500 truncate">
{getProviderDisplayName(currentProvider)}
</span>
{nodeData.selectedModel?.modelId && (
<a
href={getModelPageUrl(currentProvider, nodeData.selectedModel.modelId) || "#"}
target="_blank"
rel="noopener noreferrer"
className="text-neutral-500 hover:text-neutral-300 transition-colors"
title={`View on ${getProviderDisplayName(currentProvider)}`}
onClick={(e) => {
if (!getModelPageUrl(currentProvider, nodeData.selectedModel?.modelId || "")) {
e.preventDefault();
}
}}
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
)}
</div>
)}
</div>
<button
onClick={() => setIsBrowseDialogOpen(true)}
className="nodrag nopan shrink-0 px-3 py-1.5 text-xs bg-neutral-700 hover:bg-neutral-600 border border-neutral-600 rounded text-neutral-300 transition-colors"
>
{t("common.browse")}
</button>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<label className="space-y-1 text-xs text-neutral-400">
<span>{t("node.duration")}</span>
<input
type="number"
min="4"
step="1"
value={nodeData.durationSeconds ?? DEFAULT_VIDEO_DURATION_SECONDS}
onChange={(event) => updateNodeData(node.id, { durationSeconds: normalizeVideoDurationSeconds(event.target.value) })}
className="nodrag nopan w-full rounded bg-neutral-800 px-2 py-1.5 text-neutral-100 outline-none focus:ring-1 focus:ring-neutral-600"
/>
</label>
<label className="space-y-1 text-xs text-neutral-400">
<span>{t("node.resolution")}</span>
<select
value={nodeData.resolution ?? DEFAULT_VIDEO_RESOLUTION}
onChange={(event) => updateNodeData(node.id, { resolution: event.target.value })}
className="nodrag nopan w-full rounded bg-neutral-800 px-2 py-1.5 text-neutral-100 outline-none focus:ring-1 focus:ring-neutral-600"
>
{VIDEO_RESOLUTION_OPTIONS.map((item) => (
<option key={item} value={item}>{item}</option>
))}
</select>
</label>
{videoSoundSupported && (
<label className="col-span-2 flex items-center gap-2 text-xs text-neutral-300">
<input
type="checkbox"
checked={nodeData.soundEnabled ?? true}
onChange={(event) => updateNodeData(node.id, { soundEnabled: event.target.checked })}
className="nodrag nopan h-3 w-3 rounded bg-neutral-800"
/>
<span>{t("node.sound")}</span>
</label>
)}
</div>
{nodeData.selectedModel?.modelId && (
<ModelParameters
modelId={nodeData.selectedModel.modelId}
provider={currentProvider}
parameters={nodeData.parameters || {}}
onParametersChange={handleParametersChange}
hiddenParameterNames={hiddenVideoParameterNames}
/>
)}
</div>
<div className="flex justify-end">
<button
onClick={() => regenerateNode(node.id)}
disabled={isRunning}
className="nodrag nopan inline-flex items-center gap-1.5 px-3 py-1.5 text-xs bg-neutral-700 hover:bg-neutral-600 border border-neutral-600 rounded text-neutral-300 disabled:opacity-40 disabled:pointer-events-none transition-colors"
>
<svg className="w-3 h-3" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z" /></svg>
{isRunning ? t("toolbar.running") : t("toolbar.run")}
</button>
</div>
{isBrowseDialogOpen && (
<ModelSearchDialog
isOpen={isBrowseDialogOpen}
onClose={() => setIsBrowseDialogOpen(false)}
onModelSelected={handleBrowseModelSelect}
initialCapabilityFilter="video"
/>
)}
</>
);
}
// Generate 3D Controls
function Generate3DControls({ node }: { node: Node }) {
const nodeData = node.data as Generate3DNodeData;

17
src/store/generationPreferenceStore.ts

@ -5,10 +5,6 @@ import {
createPopiserverDefaultImageModel,
createPopiserverDefaultVideoModel,
} from "@/store/utils/defaultImageModel";
import {
DEFAULT_VIDEO_DURATION_SECONDS,
DEFAULT_VIDEO_RESOLUTION,
} from "@/utils/videoGenerationSettings";
export type GenerationPreferenceType = "image" | "video" | "audio";
@ -25,10 +21,9 @@ export interface VideoGenerationPreference {
[key: string]: unknown;
type: "video";
model: SelectedModel;
aspectRatio: string;
resolution: string;
durationSeconds: number;
soundEnabled?: boolean;
parameters?: Record<string, unknown>;
extraTaskParams?: Record<string, unknown>;
subType?: number;
}
export interface AudioGenerationPreference {
@ -61,10 +56,8 @@ function createInitialVideoPreference(): VideoGenerationPreference {
return {
type: "video",
model: createPopiserverDefaultVideoModel(),
aspectRatio: "9:16",
resolution: DEFAULT_VIDEO_RESOLUTION,
durationSeconds: DEFAULT_VIDEO_DURATION_SECONDS,
soundEnabled: true,
parameters: {},
extraTaskParams: {},
};
}

18
src/store/utils/nodeDefaults.ts

@ -37,10 +37,6 @@ import {
import { loadNodeDefaults } from "./localStorage";
import { POPISERVER_DEFAULT_IMAGE_MODEL_ID } from "./defaultImageModel";
import { DEFAULT_POPISERVER_LLM_MODEL_ID } from "@/lib/llmModels";
import {
getDefaultVideoSoundEnabled,
normalizeVideoDurationSeconds,
} from "@/utils/videoGenerationSettings";
import { useGenerationPreferenceStore } from "@/store/generationPreferenceStore";
export {
DEFAULT_NODE_DIMENSIONS,
@ -154,8 +150,6 @@ export const createDefaultNodeData = (type: NodeType): WorkflowNodeData => {
case "smartVideo": {
const videoPreference = useGenerationPreferenceStore.getState().video;
const selectedModel = videoPreference.model;
const durationSeconds = normalizeVideoDurationSeconds(videoPreference.durationSeconds);
const soundEnabled = videoPreference.soundEnabled ?? getDefaultVideoSoundEnabled(selectedModel);
return {
video: null,
uploadStatus: "idle",
@ -167,11 +161,8 @@ export const createDefaultNodeData = (type: NodeType): WorkflowNodeData => {
inputImages: [],
inputPrompt: null,
outputVideo: null,
aspectRatio: videoPreference.aspectRatio as AspectRatio,
resolution: videoPreference.resolution,
durationSeconds,
soundEnabled,
selectedModel,
parameters: videoPreference.parameters ?? {},
status: "idle",
error: null,
videoHistory: [],
@ -252,17 +243,12 @@ export const createDefaultNodeData = (type: NodeType): WorkflowNodeData => {
case "generateVideo": {
const videoPreference = useGenerationPreferenceStore.getState().video;
const selectedModel = videoPreference.model;
const durationSeconds = normalizeVideoDurationSeconds(videoPreference.durationSeconds);
const soundEnabled = videoPreference.soundEnabled ?? getDefaultVideoSoundEnabled(selectedModel);
return {
inputImages: [],
inputPrompt: null,
outputVideo: null,
aspectRatio: videoPreference.aspectRatio as AspectRatio,
resolution: videoPreference.resolution,
durationSeconds,
soundEnabled,
selectedModel,
parameters: videoPreference.parameters ?? {},
status: "idle",
error: null,
videoHistory: [],

12
src/types/nodes.ts

@ -269,15 +269,11 @@ export interface GenerateVideoNodeData extends BaseNodeData {
outputVideoRemoteUrl?: string; // Original provider URL retained for fallback/re-download
outputVideoRemoteExpiresAt?: string; // Best-effort expiry timestamp for signed provider URLs
outputVideoStorageStatus?: MediaStorageStatus; // Local persistence state for provider URLs
aspectRatio?: AspectRatio;
resolution?: string;
durationSeconds?: number;
fps?: number;
soundEnabled?: boolean;
selectedModel?: SelectedModel; // Required for video generation (no legacy fallback)
lastUsedModel?: SelectedModel; // The model that produced the current output
referenceSubjectList?: ComposerReferenceSubject[];
parameters?: Record<string, unknown>; // Legacy mirror of config.parameters; model-specific dynamic parameters only
parameters?: Record<string, unknown>; // Model-specific dynamic parameters only
extraTaskParams?: Record<string, unknown>;
subType?: number;
modelSchemaLoading?: boolean;
@ -630,9 +626,9 @@ export interface GenerateVideoNodeDefaults {
modelId: string;
displayName: string;
};
resolution?: string;
durationSeconds?: number;
soundEnabled?: boolean;
parameters?: Record<string, unknown>;
extraTaskParams?: Record<string, unknown>;
subType?: number;
}
export interface Generate3DNodeDefaults {

7
src/utils/defaultGenerationNodeModel.ts

@ -11,7 +11,6 @@ import {
POPISERVER_DEFAULT_VIDEO_MODEL_ID,
} from "@/store/utils/defaultImageModel";
import { useGenerationPreferenceStore } from "@/store/generationPreferenceStore";
import { normalizeVideoDurationSeconds } from "@/utils/videoGenerationSettings";
import { toSelectedModel } from "@/utils/selectedModel";
type GenerationNodeType = "smartImage" | "smartVideo" | "smartAudio" | "smartText" | "nanoBanana" | "generateVideo" | "generateAudio" | "llmGenerate";
@ -90,15 +89,9 @@ async function buildGenerationPreferenceNodeData(type: NodeType): Promise<Partia
if (type === "smartVideo" || type === "generateVideo") {
const { type: _type, model: _model, ...preferenceConfig } = preferences.video;
const aspectRatio = preferences.video.aspectRatio as AspectRatio;
const selectedModel = resolvePreferenceModel("video", preferences.video.model);
const durationSeconds = normalizeVideoDurationSeconds(preferences.video.durationSeconds);
return {
...preferenceConfig,
aspectRatio,
resolution: preferences.video.resolution,
durationSeconds,
soundEnabled: preferences.video.soundEnabled,
selectedModel,
parameters: preferences.video.parameters ?? {},
} as Partial<WorkflowNodeData>;

Loading…
Cancel
Save