Browse Source

图片加载失败 二次加载

feature/handle
TianYun 1 month ago
parent
commit
3247a8c3ae
  1. 2
      next.config.ts
  2. 6
      src/components/nodes/GenerateImageNode.tsx
  3. 18
      src/components/nodes/GenerateVideoNode.tsx
  4. 7
      src/components/nodes/ImageInputNode.tsx
  5. 17
      src/components/nodes/VideoInputNode.tsx

2
next.config.ts

@ -1,7 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactStrictMode: true,
reactStrictMode: false,
output: "standalone",
allowedDevOrigins: ["192.168.77.32"],
experimental: {

6
src/components/nodes/GenerateImageNode.tsx

@ -131,11 +131,7 @@ export function GenerateImageNodeView({ id, data, selected }: GenerateImageNodeV
const [displayFallbackImage, setDisplayFallbackImage] = useState<string | null>(null);
const displayImage = displayFallbackImage || preferredDisplayImage;
const adaptiveOutputImage = useAdaptiveImageSrc(displayImage, id);
const outputImageSrc = adaptiveOutputImage
? imageRetryNonce > 0 && /^https?:\/\//i.test(adaptiveOutputImage) && !adaptiveOutputImage.includes("?")
? `${adaptiveOutputImage}${adaptiveOutputImage.includes("?") ? "&" : "?"}canvasRetry=${imageRetryNonce}`
: adaptiveOutputImage
: null;
const outputImageSrc = adaptiveOutputImage;
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
const popiserverImageModels = useModelStore((state) => state.byKind.image);
// Use stable selector for API keys to prevent unnecessary re-fetches

18
src/components/nodes/GenerateVideoNode.tsx

@ -56,6 +56,7 @@ const VIDEO_BASIC_PARAMETER_NAMES = [
...VIDEO_DURATION_PARAMETER_NAMES,
...VIDEO_RESOLUTION_PARAMETER_NAMES,
];
const VIDEO_LOAD_RETRY_DELAYS_MS = [600, 1600];
function isSameSelectedModel(a: SelectedModel | undefined, b: SelectedModel | undefined): boolean {
return Boolean(a?.provider && b?.provider && a.provider === b.provider && a.modelId === b.modelId);
@ -159,6 +160,7 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV
const [isBrowseDialogOpen, setIsBrowseDialogOpen] = useState(false);
const [isLoadingCarouselVideo, setIsLoadingCarouselVideo] = useState(false);
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const [videoRetryNonce, setVideoRetryNonce] = useState(0);
const [settingsTab, setSettingsTab] = useState<"primary" | "fallback">("primary");
const showFallbackSettings = !popiProviderMode && !!nodeData.fallbackModel;
@ -171,6 +173,10 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV
const videoBlobUrl = useVideoBlobUrl(nodeData.outputVideo ?? null);
const videoAutoplayRef = useVideoAutoplay(id, selected);
useEffect(() => {
setVideoRetryNonce(0);
}, [videoBlobUrl]);
// Inline parameters infrastructure
const { inlineParametersEnabled } = useInlineParameters();
// Video generation settings now live in the selected-node composer. Rendering
@ -938,13 +944,23 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV
<>
<video
ref={videoAutoplayRef}
key={nodeData.videoHistory?.[nodeData.selectedVideoHistoryIndex || 0]?.id}
key={`${nodeData.videoHistory?.[nodeData.selectedVideoHistoryIndex || 0]?.id ?? nodeData.outputVideo}-${videoRetryNonce}`}
src={videoBlobUrl ?? undefined}
controls
loop
muted
className="w-full h-full object-contain"
playsInline
onLoadedMetadata={() => setVideoRetryNonce(0)}
onError={() => {
const retryIndex = videoRetryNonce;
const retryDelay = VIDEO_LOAD_RETRY_DELAYS_MS[retryIndex];
if (retryDelay !== undefined && videoBlobUrl) {
window.setTimeout(() => {
setVideoRetryNonce((current) => current === retryIndex ? current + 1 : current);
}, retryDelay);
}
}}
onDoubleClick={(e) => {
e.preventDefault();
e.stopPropagation();

7
src/components/nodes/ImageInputNode.tsx

@ -105,12 +105,7 @@ export function ImageInputNodeView({ id, data, selected, renderInputHandle = fal
const [imageLoadFailed, setImageLoadFailed] = useState(false);
const isUploading = nodeData.uploadStatus === "loading";
const displayImage = displayFallbackImage || preferredDisplayImage;
const imageSrc = displayImage
? imageRetryNonce > 0 && /^https?:\/\//i.test(displayImage) && !displayImage.includes("?")
? `${displayImage}${displayImage.includes("?") ? "&" : "?"}canvasRetry=${imageRetryNonce}`
: displayImage
: null;
const imageSrc = displayImage;
useEffect(() => {
setDisplayFallbackImage(null);

17
src/components/nodes/VideoInputNode.tsx

@ -29,6 +29,7 @@ export interface VideoInputNodeViewProps {
const MAX_FILE_SIZE = 200 * 1024 * 1024; // 200MB
const ACCEPTED_FORMATS = "video/mp4,video/webm,video/quicktime";
const ACCEPTED_MIME_TYPES = ACCEPTED_FORMATS.split(",");
const VIDEO_LOAD_RETRY_DELAYS_MS = [600, 1600];
function getVideoNodeTitle(id: string, data: VideoInputNodeData, videoLabel: string) {
if (data.customTitle) return data.customTitle;
@ -75,11 +76,16 @@ export function VideoInputNodeView({ id, data, selected }: VideoInputNodeViewPro
const prevFitKeyRef = useRef<string | null>(null);
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const [isAssetPickerOpen, setIsAssetPickerOpen] = useState(false);
const [videoRetryNonce, setVideoRetryNonce] = useState(0);
const isUploading = nodeData.uploadStatus === "loading";
// Use blob URL for efficient playback of large base64 videos
const playbackUrl = useVideoBlobUrl(nodeData.video ?? null);
useEffect(() => {
setVideoRetryNonce(0);
}, [playbackUrl]);
useEffect(() => {
if (!nodeData.video || !nodeData.dimensions) {
prevFitKeyRef.current = null;
@ -387,11 +393,22 @@ export function VideoInputNodeView({ id, data, selected }: VideoInputNodeViewPro
className="relative group w-full h-full overflow-clip rounded-lg cursor-zoom-in"
>
<video
key={`${playbackUrl ?? nodeData.previewVideoPoster ?? "empty"}-${videoRetryNonce}`}
src={playbackUrl ?? undefined}
poster={nodeData.previewVideoPoster}
controls={Boolean(nodeData.video)}
className="w-full h-full object-contain rounded-lg"
preload="metadata"
onLoadedMetadata={() => setVideoRetryNonce(0)}
onError={() => {
const retryIndex = videoRetryNonce;
const retryDelay = VIDEO_LOAD_RETRY_DELAYS_MS[retryIndex];
if (retryDelay !== undefined && playbackUrl) {
window.setTimeout(() => {
setVideoRetryNonce((current) => current === retryIndex ? current + 1 : current);
}, retryDelay);
}
}}
/>
{nodeData.isOptional && (
<span className="absolute bottom-2 left-2 text-[9px] font-medium text-neutral-300 bg-black/50 px-1.5 py-0.5 rounded">

Loading…
Cancel
Save