auto-save 2026-05-13 19:06 (~3)
This commit is contained in:
@@ -2155,6 +2155,13 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 3 项未提交变更 · 最近提交:auto-save 2026-05-13 18:51 (~1)",
|
||||
"files_changed": 3
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T19:01:14+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-13 18:57 (+1, ~3)",
|
||||
"hash": "aec7fda",
|
||||
"files_changed": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -830,6 +830,17 @@ api/main.py
|
||||
<h2>变更记录</h2>
|
||||
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
|
||||
<div class="changelog">
|
||||
<article class="change">
|
||||
<header>
|
||||
<h3>2026-05-13 · 元素改造 hover 预览改为节点内效果</h3>
|
||||
<span class="tag violet">StoryboardNode</span>
|
||||
</header>
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>上一版把元素预览用 <code>createPortal</code> 挂到 <code>body</code>,DevTools 里会出现额外 fixed 层,交互形态和关键帧节点不一致。</p>
|
||||
<p><strong>改动:</strong>改成和镜头拆解关键帧一致的节点内 <code>group-hover</code> 预览:hover 时在元素缩略图上方展示来源原帧 + 提取元素,不再向 <code>body</code> 插入预览层。</p>
|
||||
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code>;元素改造板块的 DOM 和交互效果更接近关键帧缩略图。</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="change">
|
||||
<header>
|
||||
<h3>2026-05-13 · 新增独立源码解析与协作地图</h3>
|
||||
@@ -849,7 +860,7 @@ api/main.py
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>元素改造节点上方小图 hover 没有像镜头拆解节点一样显示原图预览,并且首次修复时出现运行时错误。</p>
|
||||
<p><strong>原因:</strong>节点内部 overflow 裁剪了预览;随后 portal 预览里把变量写成了不存在的 <code>aspectRatio</code>。</p>
|
||||
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code>。现在 hover 展示来源原帧和提取元素,运行时异常已验证为 0。</p>
|
||||
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code>。该记录之后又改为节点内预览,见上方最新记录。</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="change">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { createPortal } from "react-dom"
|
||||
import { type NodeProps } from "@xyflow/react"
|
||||
import {
|
||||
Link2, Upload, Download, Scissors, Image as ImageIcon,
|
||||
@@ -599,14 +598,6 @@ const IMAGEGEN_WIDTH = 360
|
||||
export function StoryboardNode({ data, selected }: any) {
|
||||
const d: NodeData = data
|
||||
const job = d?.job
|
||||
const [hover, setHover] = useState<{
|
||||
rect: DOMRect
|
||||
name: string
|
||||
elementSrc: string
|
||||
frameSrc: string
|
||||
frameIdx: number
|
||||
timestamp: number
|
||||
} | null>(null)
|
||||
|
||||
// 上方浮条 = 所有 frame 的 elements 已提取图("分镜头编排"的输入素材)
|
||||
type ElPreview = { frameIdx: number; elementId: string; name: string; src: string; cid: string; frameSrc: string; timestamp: number }
|
||||
@@ -651,19 +642,8 @@ export function StoryboardNode({ data, selected }: any) {
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className="group relative rounded-md border border-violet-300/50 transition shadow-lg hover:-translate-y-0.5 bg-white overflow-hidden"
|
||||
className="group relative rounded-md border border-violet-300/50 transition shadow-lg hover:-translate-y-0.5 bg-white"
|
||||
style={{ aspectRatio: aspect }}
|
||||
onMouseEnter={(e) => {
|
||||
setHover({
|
||||
rect: e.currentTarget.getBoundingClientRect(),
|
||||
name: p.name,
|
||||
elementSrc: p.src,
|
||||
frameSrc: p.frameSrc,
|
||||
frameIdx: p.frameIdx,
|
||||
timestamp: p.timestamp,
|
||||
})
|
||||
}}
|
||||
onMouseLeave={() => setHover(null)}
|
||||
>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -675,7 +655,7 @@ export function StoryboardNode({ data, selected }: any) {
|
||||
d.onOpenWorkbench?.(p.frameIdx)
|
||||
}}
|
||||
title={`${p.name} · 来自分镜 ${p.frameIdx + 1} · 点击进入分镜编排`}
|
||||
className="absolute inset-0 w-full h-full"
|
||||
className="absolute inset-0 w-full h-full overflow-hidden rounded-md bg-white"
|
||||
>
|
||||
<img
|
||||
src={p.src}
|
||||
@@ -702,56 +682,43 @@ export function StoryboardNode({ data, selected }: any) {
|
||||
📋
|
||||
</button>
|
||||
)}
|
||||
{/* hover 预览 — 跟关键帧节点一样留在 ReactFlow 节点内部,不向 body 挂 fixed portal */}
|
||||
<div
|
||||
className="pointer-events-none absolute opacity-0 group-hover:opacity-100 scale-95 group-hover:scale-100 transition-all duration-150 z-[90]"
|
||||
style={{
|
||||
bottom: "calc(100% + 10px)",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
transformOrigin: "bottom center",
|
||||
}}
|
||||
>
|
||||
<div className="overflow-hidden rounded-lg border-2 border-violet-300/60 bg-black shadow-2xl" style={{ width: 340 }}>
|
||||
<div className="grid grid-cols-[1fr_104px] gap-0">
|
||||
<div className="bg-black">
|
||||
<div className="px-2 py-1 text-[10px] text-white/65">来源原帧</div>
|
||||
<div style={{ aspectRatio: aspect }}>
|
||||
<img src={p.frameSrc} alt="" className="h-full w-full object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-l border-white/10 bg-white">
|
||||
<div className="bg-black px-2 py-1 text-[10px] text-white/65">提取元素</div>
|
||||
<div className="flex h-[calc(100%-22px)] items-center justify-center p-2">
|
||||
<img src={p.src} alt="" className="max-h-full max-w-full object-contain" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 bg-black/90 px-2 py-1.5 text-[10.5px] text-white">
|
||||
<span className="truncate">{p.name}</span>
|
||||
<span className="shrink-0 font-mono text-white/55">分镜 {p.frameIdx + 1} · {p.timestamp.toFixed(2)}s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{job && hover && (() => {
|
||||
const w = 420
|
||||
const h = 300
|
||||
const gap = 12
|
||||
const centerX = hover.rect.left + hover.rect.width / 2
|
||||
const left = Math.max(12, Math.min(window.innerWidth - w - 12, centerX - w / 2))
|
||||
const top = hover.rect.top > h + gap
|
||||
? hover.rect.top - h - gap
|
||||
: Math.min(window.innerHeight - h - 12, hover.rect.bottom + gap)
|
||||
return createPortal(
|
||||
<div
|
||||
className="fixed z-[180] pointer-events-none"
|
||||
style={{
|
||||
left,
|
||||
top,
|
||||
width: w,
|
||||
animation: "drawer-in 0.18s cubic-bezier(0.32, 0.72, 0, 1)",
|
||||
}}
|
||||
>
|
||||
<div className="overflow-hidden rounded-xl border-2 border-violet-300/60 bg-black shadow-2xl">
|
||||
<div className="grid grid-cols-[1fr_128px] gap-0">
|
||||
<div className="bg-black">
|
||||
<div className="px-2 py-1 text-[10px] text-white/65">来源原帧</div>
|
||||
<div style={{ aspectRatio: aspect }}>
|
||||
<img src={hover.frameSrc} alt="" className="h-full w-full object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-l border-white/10 bg-white">
|
||||
<div className="bg-black px-2 py-1 text-[10px] text-white/65">提取元素</div>
|
||||
<div className="flex h-[calc(100%-22px)] items-center justify-center p-2">
|
||||
<img src={hover.elementSrc} alt="" className="max-h-full max-w-full object-contain" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 bg-black/90 px-2 py-1.5 text-[10.5px] text-white">
|
||||
<span className="truncate">{hover.name}</span>
|
||||
<span className="shrink-0 font-mono text-white/55">分镜 {hover.frameIdx + 1} · {hover.timestamp.toFixed(2)}s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
)
|
||||
})()}
|
||||
|
||||
<NodeShell
|
||||
type="ai" status={status}
|
||||
icon={<LayoutGrid className="h-4 w-4" />}
|
||||
|
||||
Reference in New Issue
Block a user