auto-save 2026-05-13 09:42 (~2)
This commit is contained in:
@@ -1131,6 +1131,19 @@
|
||||
"message": "auto-save 2026-05-13 09:31 (~3)",
|
||||
"hash": "fdc3162",
|
||||
"files_changed": 3
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T09:37:15+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-13 09:37 (~4)",
|
||||
"hash": "839a3f6",
|
||||
"files_changed": 4
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T01:37:36Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Claude 会话活跃 · 最近命令:claude · 1 项未提交变更 · 最近提交:auto-save 2026-05-13 09:37 (~4)",
|
||||
"files_changed": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { createPortal } from "react-dom"
|
||||
import { X, ChevronLeft, ChevronRight, Check, Sparkles, Wand2, Loader2, Eye, RefreshCw, Plus } from "lucide-react"
|
||||
import { frameUrl, describeFrame, translateText, type KeyFrame, type Job } from "@/lib/api"
|
||||
import { frameUrl, describeFrame, translateText, generateImage, generatedImageUrl, type KeyFrame, type Job } from "@/lib/api"
|
||||
import { toast } from "sonner"
|
||||
|
||||
type CustomItem = { id: string; zh: string; en: string; translating: boolean }
|
||||
@@ -21,6 +21,7 @@ interface Props {
|
||||
|
||||
export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect, onJobUpdate, embedded = false }: Props) {
|
||||
const [describing, setDescribing] = useState(false)
|
||||
const [generating, setGenerating] = useState(false)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
// 自定义提取元素 — 按 frame 隔离,切换 frame 后回到同一帧时还能看到之前加的
|
||||
const [customsByFrame, setCustomsByFrame] = useState<Record<number, CustomItem[]>>({})
|
||||
@@ -79,6 +80,35 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
const isSelected = selected.has(f.index)
|
||||
const desc = f.description
|
||||
|
||||
const handleGenerateMat = async () => {
|
||||
if (activeIndex === null || !f) return
|
||||
const base = f.description?.suggested_prompt?.trim()
|
||||
if (!base) {
|
||||
toast.error("请先识别此分镜(右上角『识别』按钮)")
|
||||
return
|
||||
}
|
||||
// 自动选用此帧 → ImageGenCard 才会渲染
|
||||
if (!selected.has(f.index)) onToggleSelect(f.index)
|
||||
|
||||
const extraEn = customs.filter((c) => c.en).map((c) => c.en).join(", ")
|
||||
setGenerating(true)
|
||||
try {
|
||||
const updated = await generateImage(jobId, f.index, {
|
||||
prompt: base,
|
||||
extra_prompt: extraEn,
|
||||
negative_prompt: "水印, @用户名, TikTok logo, 平台文字, 浮水印",
|
||||
model: "gemini-3-pro-image-preview",
|
||||
mode: "edit",
|
||||
})
|
||||
onJobUpdate?.(updated)
|
||||
toast.success(`分镜 ${f.index + 1} 垫图生成完成 · 已加入生图卡`)
|
||||
} catch (e) {
|
||||
toast.error("生图失败:" + (e instanceof Error ? e.message : String(e)))
|
||||
} finally {
|
||||
setGenerating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDescribe = async () => {
|
||||
setDescribing(true)
|
||||
try {
|
||||
@@ -310,25 +340,61 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
</div>
|
||||
|
||||
<button
|
||||
disabled
|
||||
className="mt-2 w-full text-[11.5px] py-1.5 rounded-md bg-violet-500/40 text-white inline-flex items-center justify-center gap-1.5 cursor-not-allowed disabled:opacity-50"
|
||||
title="即将上线:批量调 nano-banana-pro image edit"
|
||||
onClick={handleGenerateMat}
|
||||
disabled={generating || !desc?.suggested_prompt}
|
||||
className="mt-2 w-full text-[12px] py-2 rounded-md bg-gradient-to-r from-rose-500 to-pink-500 text-white hover:opacity-95 disabled:opacity-40 disabled:cursor-not-allowed inline-flex items-center justify-center gap-1.5 font-semibold transition"
|
||||
title={!desc?.suggested_prompt ? "先识别此分镜" : `合成元素到关键帧场景(${customs.length} 条自定义 + 识别结果)`}
|
||||
>
|
||||
<Wand2 className="h-3.5 w-3.5" />
|
||||
⚡ 批量快速提取(下一步实现)
|
||||
{generating ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Wand2 className="h-3.5 w-3.5" />}
|
||||
{generating ? "生成中…(5-15 秒)" : "⚡ 生成垫图"}
|
||||
</button>
|
||||
{!desc?.suggested_prompt && (
|
||||
<div className="mt-1 text-[10px] text-white/40 text-center">先识别此分镜,再生成垫图</div>
|
||||
)}
|
||||
{desc?.suggested_prompt && customs.length === 0 && (
|
||||
<div className="mt-1 text-[10px] text-white/35 text-center">未加自定义元素 · 将仅按识别结果生成</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>
|
||||
{/* 已生成的垫图(与生图卡同源) */}
|
||||
{f.generated_images && f.generated_images.length > 0 && (
|
||||
<section>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex items-center gap-1.5 text-white text-[12.5px] font-semibold">
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
已生成垫图
|
||||
<span className="text-[10px] text-white/35 font-mono">· {f.generated_images.length}</span>
|
||||
</div>
|
||||
<span className="text-[9.5px] text-white/35">同步到 →「生图」节点</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-1.5">
|
||||
{f.generated_images.map((g) => (
|
||||
<a
|
||||
key={g.id}
|
||||
href={generatedImageUrl(jobId, f.index, g.id)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
title={g.prompt}
|
||||
className={`relative aspect-square rounded-md overflow-hidden border-2 transition ${
|
||||
g.selected ? "border-emerald-400 ring-2 ring-emerald-400/40" : "border-white/15 hover:border-white/40"
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
src={generatedImageUrl(jobId, f.index, g.id)}
|
||||
alt={`gen ${g.id}`}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
{g.selected && (
|
||||
<div className="absolute top-1 right-1 bg-emerald-500 text-white rounded-full p-0.5">
|
||||
<Check className="h-2.5 w-2.5" />
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user