20260429_232813-fix: video frame display pipeline — default project seed, presigned URLs, Canvas/FrameTimeline real frames, upload-parse flow

This commit is contained in:
2026-04-29 23:42:18 +08:00
parent 51f1a60216
commit 35d6e1503c
16 changed files with 454 additions and 56 deletions

View File

@@ -1,5 +1,6 @@
import React from 'react';
import React, { useEffect } from 'react';
import { useStore } from '../store/useStore';
import { getProjectFrames, parseMedia } from '../lib/api';
import { CanvasArea } from './CanvasArea';
import { ToolsPalette } from './ToolsPalette';
import { OntologyInspector } from './OntologyInspector';
@@ -8,6 +9,61 @@ import { FrameTimeline } from './FrameTimeline';
export function VideoWorkspace({ onNavigateToAI }: { onNavigateToAI?: () => void }) {
const activeTool = useStore((state) => state.activeTool);
const setActiveTool = useStore((state) => state.setActiveTool);
const currentProject = useStore((state) => state.currentProject);
const frames = useStore((state) => state.frames);
const currentFrameIndex = useStore((state) => state.currentFrameIndex);
const setFrames = useStore((state) => state.setFrames);
const setCurrentFrame = useStore((state) => state.setCurrentFrame);
useEffect(() => {
if (!currentProject?.id) return;
let cancelled = false;
const loadFrames = async () => {
try {
const data = await getProjectFrames(String(currentProject.id));
if (cancelled) return;
if (data.length === 0 && currentProject.video_path) {
// No frames yet but video exists → trigger parsing
try {
await parseMedia(String(currentProject.id));
if (cancelled) return;
const fresh = await getProjectFrames(String(currentProject.id));
if (cancelled) return;
setFrames(fresh.map((f) => ({
id: String(f.id),
projectId: String(f.project_id),
index: f.frame_index,
url: f.image_url,
width: f.width ?? 0,
height: f.height ?? 0,
})));
setCurrentFrame(0);
} catch (err) {
console.error('Parse failed:', err);
}
} else {
setFrames(data.map((f) => ({
id: String(f.id),
projectId: String(f.project_id),
index: f.frame_index,
url: f.image_url,
width: f.width ?? 0,
height: f.height ?? 0,
})));
setCurrentFrame(0);
}
} catch (err) {
console.error('Failed to load frames:', err);
}
};
loadFrames();
return () => { cancelled = true; };
}, [currentProject?.id, setFrames, setCurrentFrame]);
const currentFrameUrl = frames[currentFrameIndex]?.url || '';
return (
<div className="w-full h-full flex flex-col bg-[#0a0a0a]">
@@ -16,7 +72,7 @@ export function VideoWorkspace({ onNavigateToAI }: { onNavigateToAI?: () => void
<div className="flex items-center gap-4">
<h2 className="text-xs font-semibold uppercase tracking-widest text-gray-400"></h2>
<div className="h-4 w-px bg-white/10"></div>
<span className="text-sm text-white font-mono">Autonomous_Nav_Cam_Left.mp4</span>
<span className="text-sm text-white font-mono">{currentProject?.name || '未选择项目'}</span>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-1.5 text-[10px] uppercase font-medium">
@@ -37,7 +93,7 @@ export function VideoWorkspace({ onNavigateToAI }: { onNavigateToAI?: () => void
<div className="flex-1 relative flex items-center justify-center p-8 bg-[#151515] overflow-hidden">
<div className="relative w-full h-full bg-[#1e1e1e] border border-white/5 shadow-2xl rounded-sm">
<CanvasArea activeTool={activeTool} />
<CanvasArea activeTool={activeTool} frameUrl={currentFrameUrl} />
</div>
</div>