auto-save 2026-05-12 23:21 (~2)
This commit is contained in:
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user