diff --git a/.memory/worklog.json b/.memory/worklog.json index e600be4..fdae515 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -2089,6 +2089,19 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 1 项未提交变更 · 最近提交:auto-save 2026-05-13 18:18 (~5)", "files_changed": 1 + }, + { + "ts": "2026-05-13T18:24:27+08:00", + "type": "commit", + "message": "auto-save 2026-05-13 18:24 (~3)", + "hash": "9a9c0cc", + "files_changed": 3 + }, + { + "ts": "2026-05-13T10:29:28Z", + "type": "session-heartbeat", + "message": "Codex 会话活跃 · 最近命令:codex · 2 项未提交变更 · 最近提交:auto-save 2026-05-13 18:24 (~3)", + "files_changed": 2 } ] } diff --git a/web/components/lightbox.tsx b/web/components/lightbox.tsx index d0fdd14..ca91ac6 100644 --- a/web/components/lightbox.tsx +++ b/web/components/lightbox.tsx @@ -1,10 +1,10 @@ "use client" import { useEffect, useRef, useState } from "react" import { createPortal } from "react-dom" -import { X, ChevronLeft, ChevronRight, Check, Sparkles, Wand2, Loader2, Eye, RefreshCw, Plus, Sparkle, Crop } from "lucide-react" +import { X, ChevronLeft, ChevronRight, Check, Sparkles, Wand2, Loader2, Eye, RefreshCw, Plus, Sparkle, Crop, Copy, PencilLine, Trash2, Save } from "lucide-react" import { frameUrl, cleanedFrameUrl, cutoutUrl, - describeFrame, cleanupFrame, applyCleanedFrame, discardCleanedFrame, addElement, deleteElement, cutoutElement, deleteCutout, + describeFrame, cleanupFrame, applyCleanedFrame, discardCleanedFrame, addElement, updateElement, deleteElement, cutoutElement, deleteCutout, pushStoryboardImage, type KeyFrame, type Job, type ImageRef, } from "@/lib/api" @@ -31,6 +31,12 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o const [cuttingId, setCuttingId] = useState(null) const [addingZh, setAddingZh] = useState(false) const [addInput, setAddInput] = useState("") + const [editingElement, setEditingElement] = useState<{ + id: string + name_zh: string + name_en: string + position: string + } | null>(null) const [mounted, setMounted] = useState(false) // 画框模式 + 多选区(相对坐标 0-1) type Region = { x: number; y: number; w: number; h: number } @@ -217,11 +223,28 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o try { const updated = await deleteElement(jobId, f.index, id) onJobUpdate?.(updated) + if (editingElement?.id === id) setEditingElement(null) } catch (e) { toast.error("删除失败:" + (e instanceof Error ? e.message : String(e))) } } + const handleUpdateElement = async () => { + if (!editingElement || !editingElement.name_zh.trim()) return + try { + const updated = await updateElement(jobId, f.index, editingElement.id, { + name_zh: editingElement.name_zh, + name_en: editingElement.name_en, + position: editingElement.position, + }) + onJobUpdate?.(updated) + setEditingElement(null) + toast.success("元素已更新") + } catch (e) { + toast.error("更新失败:" + (e instanceof Error ? e.message : String(e))) + } + } + const handleCutout = async (id: string) => { setCuttingId(id) try { @@ -629,7 +652,10 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o {elements.length > 0 && ( · {elements.length} )} - → 给下一步「分镜头编排」 + → 先修正,再给「分镜头编排」 + +
+ Vision 识别只是候选。提取错了可以改名、删图、删元素、再提取;点提取图本身不会离开当前页面。
{elements.length > 0 && ( @@ -642,50 +668,116 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o : (e.cutout_id ? [e.cutout_id] : []) const hasAny = cutouts.length > 0 const hasRegion = !!e.region + const isEditing = editingElement?.id === e.id return (
{/* 顶部:名字 + 操作 */} -
-
-
- {e.name_zh} - {e.source === "auto" && ( - auto - )} - {e.source === "region" && ( - box - )} - {cutouts.length > 0 && ( - · {cutouts.length} 张 +
+ {isEditing ? ( +
+ setEditingElement({ ...editingElement, name_zh: ev.target.value })} + placeholder="元素中文名" + className="w-full rounded-md border border-white/15 bg-black/35 px-2 py-1.5 text-[12px] text-white outline-none placeholder:text-white/30 focus:border-violet-300/60" + /> + setEditingElement({ ...editingElement, name_en: ev.target.value })} + placeholder="英文提取提示,可手动修正" + className="w-full rounded-md border border-white/15 bg-black/35 px-2 py-1.5 text-[11px] font-mono text-white outline-none placeholder:text-white/30 focus:border-violet-300/60" + /> + setEditingElement({ ...editingElement, position: ev.target.value })} + placeholder="位置 / 备注,例如:画面左下角、手里拿着" + className="w-full rounded-md border border-white/15 bg-black/35 px-2 py-1.5 text-[11px] text-white outline-none placeholder:text-white/30 focus:border-violet-300/60" + /> +
+ ) : ( +
+
+ {e.name_zh} + {e.source === "auto" && ( + auto + )} + {e.source === "region" && ( + box + )} + {cutouts.length > 0 && ( + · {cutouts.length} 张 + )} +
+
+ {e.name_en || (无英文,可点改名补充)} +
+ {e.position && ( +
+ {e.position} +
)}
-
- {e.name_en || (无英文)} -
+ )} + +
+ {isEditing ? ( + <> + + + + ) : ( + <> + + + + + )}
- - {/* 提取按钮 — AI 补全完整元素(每次累积一张) */} - - - {/* 删除整条元素 */} -
{/* 多张提取图横向 grid */} @@ -699,52 +791,48 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o return (
- +
{`${e.name_zh} - +
{/* 序号 */}
#{ci + 1}
- {/* 复制按钮:常驻可见 */} - {onCopyImage && ( - - )} - {/* 删除该张 — 仅 v2 多图支持,老 fallback 不显示 */} - {e.cutouts && e.cutouts.length > 0 && ( - - )} +
+ {onCopyImage && ( + + )} + {e.cutouts && e.cutouts.length > 0 && ( + + )} +
) })}