auto-save 2026-05-12 23:21 (~2)

This commit is contained in:
2026-05-12 23:22:02 +08:00
parent affe4870f0
commit 5cc3f6508c
2 changed files with 122 additions and 30 deletions

View File

@@ -1,6 +1,6 @@
"use client"
import { useEffect } from "react"
import { X, ChevronLeft, ChevronRight, Check } from "lucide-react"
import { useEffect, useState } from "react"
import { X, ChevronLeft, ChevronRight, Check, Sparkles, Wand2, Loader2, Eye } from "lucide-react"
import { frameUrl, type KeyFrame } from "@/lib/api"
interface Props {
@@ -14,13 +14,15 @@ interface Props {
}
export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect }: Props) {
const [extractPrompt, setExtractPrompt] = useState("")
useEffect(() => {
if (activeIndex === null) return
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose()
if (e.key === "ArrowLeft" && activeIndex > 0) onChange(activeIndex - 1)
if (e.key === "ArrowRight" && activeIndex < frames.length - 1) onChange(activeIndex + 1)
if (e.key === " " || e.key === "Enter") {
if ((e.key === " " || e.key === "Enter") && (e.target as HTMLElement).tagName !== "INPUT" && (e.target as HTMLElement).tagName !== "TEXTAREA") {
e.preventDefault()
onToggleSelect(activeIndex)
}
@@ -41,7 +43,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
{/* 关闭 */}
<button
onClick={(e) => { e.stopPropagation(); onClose() }}
className="absolute top-5 right-5 h-10 w-10 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center"
className="absolute top-5 right-5 h-10 w-10 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center z-10"
aria-label="关闭"
>
<X className="h-5 w-5" />
@@ -51,7 +53,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
{activeIndex > 0 && (
<button
onClick={(e) => { e.stopPropagation(); onChange(activeIndex - 1) }}
className="absolute left-5 h-12 w-12 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center"
className="absolute left-5 top-1/2 -translate-y-1/2 h-12 w-12 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center z-10"
>
<ChevronLeft className="h-6 w-6" />
</button>
@@ -59,38 +61,121 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
{activeIndex < frames.length - 1 && (
<button
onClick={(e) => { e.stopPropagation(); onChange(activeIndex + 1) }}
className="absolute right-5 h-12 w-12 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center"
className="absolute right-5 top-1/2 -translate-y-1/2 h-12 w-12 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center z-10"
>
<ChevronRight className="h-6 w-6" />
</button>
)}
{/* 大图 — 关键帧是静态素材(传给生图节点垫图用),不播放视频 */}
<div onClick={(e) => e.stopPropagation()} className="flex flex-col items-center gap-4 max-w-[92vw] max-h-[92vh]">
<img
src={frameUrl(jobId, f.index)}
alt={`frame ${f.index}`}
className="max-w-[88vw] max-h-[72vh] rounded-xl shadow-2xl object-contain"
/>
<div className="flex items-center gap-4 text-white">
<div className="font-mono text-sm tabular-nums">
{String(f.index + 1).padStart(2, "0")} / {String(frames.length).padStart(2, "0")}
<span className="mx-3 text-white/40">·</span>
<span className="text-white/70">{f.timestamp.toFixed(2)}s</span>
{/* 主体:左大图 + 右识别面板 */}
<div
onClick={(e) => e.stopPropagation()}
className="flex gap-4 max-w-[92vw] max-h-[92vh] items-start"
>
{/* 左侧:大图 + 底部 meta */}
<div className="flex flex-col items-center gap-3 flex-shrink-0">
<img
src={frameUrl(jobId, f.index)}
alt={`frame ${f.index}`}
className="rounded-xl shadow-2xl object-contain"
style={{ maxWidth: "60vw", maxHeight: "80vh" }}
/>
<div className="flex items-center gap-3 text-white">
<div className="font-mono text-sm tabular-nums text-white/80">
{String(f.index + 1).padStart(2, "0")} / {String(frames.length).padStart(2, "0")}
<span className="mx-2 text-white/40">·</span>
<span className="text-white/60">{f.timestamp.toFixed(2)}s</span>
</div>
<button
onClick={() => onToggleSelect(f.index)}
className={`px-4 py-1.5 rounded-lg text-[13px] font-medium inline-flex items-center gap-2 transition ${
isSelected
? "bg-emerald-500 text-white hover:bg-emerald-400"
: "bg-white/10 text-white hover:bg-white/20"
}`}
>
<Check className="h-4 w-4" />
{isSelected ? "已选用" : "选用此帧"}
</button>
</div>
<button
onClick={() => onToggleSelect(f.index)}
className={`px-4 py-2 rounded-lg text-sm font-medium inline-flex items-center gap-2 transition ${
isSelected
? "bg-emerald-500 text-white hover:bg-emerald-400"
: "bg-white/10 text-white hover:bg-white/20"
}`}
>
<Check className="h-4 w-4" />
{isSelected ? "已选用(点取消)" : "选用此帧"}
</button>
<div className="text-[10.5px] text-white/40 font-mono">/ · Space · ESC </div>
</div>
{/* 右侧:识别 + 提取面板 */}
<div
className="flex flex-col gap-3 overflow-y-auto rounded-2xl border border-white/15 bg-black/40 backdrop-blur-xl p-4"
style={{ width: 340, maxHeight: "80vh" }}
>
{/* 识别到的元素 */}
<section>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1.5 text-white text-[12.5px] font-semibold">
<Eye className="h-3.5 w-3.5" />
</div>
<button
disabled
className="text-[10.5px] text-white/40 px-2 py-0.5 rounded border border-white/10 cursor-not-allowed"
title="待 vision 模型接入"
>
</button>
</div>
<div className="rounded-lg border border-dashed border-white/15 bg-white/[0.03] p-3 text-[11.5px] text-white/50 leading-relaxed">
<div className="flex items-center gap-1.5 text-white/40 mb-1.5">
<Loader2 className="h-3 w-3 animate-spin" />
Vision
</div>
<ul className="mt-1.5 ml-3 space-y-0.5 text-white/45">
<li> / / </li>
<li> / </li>
<li> / / </li>
</ul>
<div className="mt-2 text-[10.5px] text-white/30 font-mono">
Gemini Vision · SKG image
</div>
</div>
</section>
{/* 自定义提取 */}
<section>
<div className="flex items-center gap-1.5 mb-2 text-white text-[12.5px] font-semibold">
<Sparkles className="h-3.5 w-3.5" />
</div>
<textarea
value={extractPrompt}
onChange={(e) => setExtractPrompt(e.target.value)}
placeholder="比如:最右边那个白瓶子 / 中间的胶囊"
rows={2}
disabled
className="w-full text-[12px] px-2.5 py-1.5 rounded-md bg-black/40 border border-white/15 outline-none text-white placeholder:text-white/30 resize-none disabled:opacity-50 focus:ring-2 focus:ring-violet-400/50"
/>
<button
disabled
className="mt-2 w-full text-[12px] py-1.5 rounded-md bg-violet-500/60 text-white inline-flex items-center justify-center gap-1.5 cursor-not-allowed disabled:opacity-50"
title="待 image edit 接入"
>
<Wand2 className="h-3.5 w-3.5" />
</button>
<div className="mt-1.5 text-[10px] text-white/30 font-mono">
nano-banana-pro image edit
</div>
</section>
{/* 已提取 */}
<section>
<div className="flex items-center gap-1.5 mb-2 text-white text-[12.5px] font-semibold">
<Check className="h-3.5 w-3.5" />
</div>
<div className="rounded-lg border border-dashed border-white/10 bg-white/[0.02] p-3 text-[11px] text-white/40 text-center">
·
</div>
</section>
</div>
<div className="text-[11px] text-white/40 font-mono">/ · Space · ESC </div>
</div>
</div>
)