20260429_232813-fix: video frame display pipeline — default project seed, presigned URLs, Canvas/FrameTimeline real frames, upload-parse flow
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user