From 3df3ce4b1af262d12429d5a53f7dfce7ef57d58c Mon Sep 17 00:00:00 2001 From: kang Date: Thu, 14 May 2026 03:04:09 +0800 Subject: [PATCH] auto-save 2026-05-14 03:03 (~3) --- .memory/worklog.json | 19 +++++ docs/source-analysis.html | 12 +++ web/components/nodes/index.tsx | 137 +++++++++++++++++++++++++++++---- 3 files changed, 153 insertions(+), 15 deletions(-) diff --git a/.memory/worklog.json b/.memory/worklog.json index 1ce08fc..1e71c23 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -2979,6 +2979,25 @@ "type": "session-heartbeat", "message": "Claude 会话活跃 · 最近命令:claude · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 02:52 (~4)", "files_changed": 2 + }, + { + "ts": "2026-05-14T02:58:36+08:00", + "type": "commit", + "message": "auto-save 2026-05-14 02:58 (~6)", + "hash": "bdbaf75", + "files_changed": 6 + }, + { + "ts": "2026-05-13T18:58:48Z", + "type": "session-heartbeat", + "message": "Codex 会话活跃 · 最近命令:codex · 1 项未提交变更 · 最近提交:auto-save 2026-05-14 02:58 (~6)", + "files_changed": 1 + }, + { + "ts": "2026-05-13T19:03:11Z", + "type": "session-heartbeat", + "message": "Claude 会话活跃 · 最近命令:claude · 3 项未提交变更 · 最近提交:auto-save 2026-05-14 02:58 (~6)", + "files_changed": 3 } ] } diff --git a/docs/source-analysis.html b/docs/source-analysis.html index 37c7a00..c1c980b 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -816,6 +816,18 @@ api/main.py

变更记录

这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。

+
+
+

2026-05-14 · 删除确认改为页面内分层交互

+ Canvas + UX +
+
+

问题:浏览器原生删除确认会突然出现在页面顶部,和无限画布的操作上下文割裂;图片类素材如果每次删除都确认,也会拖慢快速整理素材的节奏。

+

改动:输入视频和生成视频任务删除改为画布内确认层,背景轻遮罩并支持点击背景 / Esc 取消;关键帧和元素提取图属于可快速整理的图片素材,点击删除后直接执行。

+

影响:web/components/nodes/index.tsxdocs/source-analysis.html。视频删除仍走既有删除接口;图片删除仍走原有回调,只调整确认策略。

+
+

2026-05-14 · 输入视频缩略图支持删除整个 job

diff --git a/web/components/nodes/index.tsx b/web/components/nodes/index.tsx index caec67a..65fc598 100644 --- a/web/components/nodes/index.tsx +++ b/web/components/nodes/index.tsx @@ -311,6 +311,73 @@ function FloatingThumbnailStrip({ ) } +function DeleteConfirmDialog({ + title, + description, + confirmLabel = "删除", + onCancel, + onConfirm, +}: { + title: string + description: string + confirmLabel?: string + onCancel: () => void + onConfirm: () => void +}) { + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onCancel() + } + document.addEventListener("keydown", onKeyDown) + return () => document.removeEventListener("keydown", onKeyDown) + }, [onCancel]) + + if (typeof document === "undefined") return null + + return createPortal( +
{ + e.stopPropagation() + if (e.target === e.currentTarget) onCancel() + }} + > +
e.stopPropagation()} + > +
+
+ +
+
{title}
+
+

{description}

+
+ + +
+
+
, + document.body, + ) +} + /* ============================================================ 1. InputNode — TK 链接 / 上传 ============================================================ */ @@ -322,6 +389,7 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an const [videoExpanded, setVideoExpanded] = useState(false) const [hoverPreviewJob, setHoverPreviewJob] = useState | null>(null) const [pinnedPreviewJob, setPinnedPreviewJob] = useState | null>(null) + const [deleteJobTarget, setDeleteJobTarget] = useState(null) const rootRef = useRef(null) const fileRef = useRef(null) const videoRef = useRef(null) @@ -417,9 +485,7 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an type="button" onClick={(e) => { e.stopPropagation() - if (confirm(`删除视频任务 ${j.id.slice(0, 8)}?源视频、关键帧、元素提取图和生成视频都会一并删除。`)) { - d.onDeleteJob?.(j.id) - } + setDeleteJobTarget(j) }} title="删除这个输入视频" className="absolute right-1.5 top-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-rose-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-rose-400" @@ -433,6 +499,20 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an )} + {deleteJobTarget && ( + setDeleteJobTarget(null)} + onConfirm={() => { + const id = deleteJobTarget.id + setDeleteJobTarget(null) + d.onDeleteJob?.(id) + }} + /> + )} + {(() => { const anchor = pinnedPreviewJob ?? hoverPreviewJob if (!anchor || videoExpanded) return null @@ -714,6 +794,7 @@ export function VisualLabNode({ data, selected }: any) { const [hoverPreview, setHoverPreview] = useState | null>(null) const [pinnedPreview, setPinnedPreview] = useState | null>(null) + const [deleteVideoTarget, setDeleteVideoTarget] = useState<{ id: string; label: string; caption: string } | null>(null) const rootRef = useRef(null) const previews: VisualPreview[] = [ @@ -885,7 +966,7 @@ export function VisualLabNode({ data, selected }: any) { type="button" onClick={(e) => { e.stopPropagation() - if (confirm(`删除${p.label}?相关清洗 / 抠图 / 生成图都会一并清除。`)) d.onDeleteFrame?.(p.frameIdx) + d.onDeleteFrame?.(p.frameIdx) }} title="删除该关键帧" className="absolute top-1.5 right-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-rose-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-rose-400" @@ -899,9 +980,7 @@ export function VisualLabNode({ data, selected }: any) { type="button" onClick={(e) => { e.stopPropagation() - if (confirm(`删除元素提取图「${p.label}」?该 cutout 文件会被移除。`)) { - d.onDeleteCutout?.(p.frameIdx, p.elementId, p.cutoutId) - } + d.onDeleteCutout?.(p.frameIdx, p.elementId, p.cutoutId) }} title="删除该提取图" className="absolute top-1.5 right-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-rose-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-rose-400" @@ -915,7 +994,7 @@ export function VisualLabNode({ data, selected }: any) { type="button" onClick={(e) => { e.stopPropagation() - d.onDeleteVideo?.(p.videoId) + setDeleteVideoTarget({ id: p.videoId, label: p.label, caption: p.caption }) }} className="absolute right-1.5 top-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-rose-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-rose-400" title="删除这个视频任务" @@ -929,6 +1008,20 @@ export function VisualLabNode({ data, selected }: any) { )} + {deleteVideoTarget && ( + setDeleteVideoTarget(null)} + onConfirm={() => { + const id = deleteVideoTarget.id + setDeleteVideoTarget(null) + d.onDeleteVideo?.(id) + }} + /> + )} + {(() => { const anchor = pinnedPreview ?? hoverPreview if (!anchor) return null @@ -1107,9 +1200,7 @@ export function KeyframeNode({ data, selected }: any) {