+
+
+
+ {title}
+ {subtitle}
+
+ {children}
+
+ )
+}
+
+function MaterialCard({
+ job,
+ index,
+ active,
+ onClick,
+ onDelete,
+}: {
+ job: Job
+ index: number
+ active: boolean
+ onClick: () => void
+ onDelete?: () => void
+}) {
+ const tone = statusTone(job)
+ return (
+
+ )
+}
+
+function Metric({ label, value, compact }: { label: string; value: string; compact?: boolean }) {
+ return (
+
@@ -367,37 +538,19 @@ function Metric({ label, value }: { label: string; value: string }) {
function SectionTitle({ icon, title }: { icon: ReactNode; title: string }) {
return (
-
+
{icon}
{title}
-
+
)
}
-function WorkflowCard({
- icon,
- title,
- ready,
- running,
- children,
-}: {
- icon: ReactNode
- title: string
- ready: boolean
- running?: boolean
- children: ReactNode
-}) {
+function StatusPill({ ready, running }: { ready: boolean; running?: boolean }) {
return (
-
-
-
-
- {running ? : ready ? : }
- {running ? "运行中" : ready ? "已就绪" : "待处理"}
-
-
- {children}
-
+
+ {running ? : ready ? : }
+ {running ? "运行中" : ready ? "已就绪" : "待处理"}
+
)
}
@@ -417,7 +570,7 @@ function ActionButton({
type="button"
disabled={disabled}
onClick={onClick}
- className={`inline-flex h-9 items-center justify-center gap-1.5 rounded-md px-3 text-[12px] font-semibold transition disabled:cursor-not-allowed disabled:opacity-40 ${variant === "solid" ? "bg-white text-black hover:bg-white/90" : "border border-white/10 bg-white/[0.04] text-white/72 hover:border-white/25 hover:text-white"}`}
+ className={`inline-flex h-10 cursor-pointer items-center justify-center gap-1.5 rounded-md px-3 text-[12px] font-semibold transition disabled:cursor-not-allowed disabled:opacity-40 ${variant === "solid" ? "bg-white text-black hover:bg-white/90" : "border border-white/10 bg-white/[0.04] text-white/72 hover:border-white/25 hover:text-white"}`}
>
{children}
@@ -426,7 +579,7 @@ function ActionButton({
function EmptyState({ text }: { text: string }) {
return (
-
+
{text}
)
@@ -436,19 +589,19 @@ function SceneRow({
job,
frame,
order,
+ selected,
+ onToggle,
onJobUpdate,
- onGenerateVideo,
}: {
job: Job
frame: KeyFrame
order: number
+ selected: boolean
+ onToggle: () => void
onJobUpdate: (job: Job) => void
- onGenerateVideo: (frameIdx: number, scene: StoryboardScene, model: string) => Promise
| void
}) {
const [scene, setScene] = useState(() => ({ ...emptyScene(), ...(frame.storyboard ?? {}) }))
- const [model, setModel] = useState<(typeof VIDEO_MODELS)[number]["value"]>("seedance")
const [saving, setSaving] = useState(false)
- const [generating, setGenerating] = useState(false)
useEffect(() => {
setScene({ ...emptyScene(), ...(frame.storyboard ?? {}) })
@@ -469,65 +622,176 @@ function SceneRow({
}
}
- const generate = async () => {
- setGenerating(true)
- try {
- await save()
- await onGenerateVideo(frame.index, scene, model)
- } finally {
- setGenerating(false)
- }
- }
-
return (
-
-
-

+
+
+
{frameLabel(frame, order)}
-
+
-
{frame.description?.scene || "未识别画面内容"}
+
{frame.description?.scene || "等待生成新的分镜文字"}