auto-save 2026-05-14 02:41 (~4)

This commit is contained in:
2026-05-14 02:42:05 +08:00
parent 2b7eb003c4
commit 43da9378cd
4 changed files with 54 additions and 15 deletions

View File

@@ -2927,6 +2927,19 @@
"type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令claude · 1 项未提交变更 · 最近提交auto-save 2026-05-14 02:30 (+2, ~4)",
"files_changed": 1
},
{
"ts": "2026-05-14T02:36:34+08:00",
"type": "commit",
"message": "auto-save 2026-05-14 02:36 (~2)",
"hash": "2b7eb00",
"files_changed": 2
},
{
"ts": "2026-05-13T18:38:48Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 4 项未提交变更 · 最近提交auto-save 2026-05-14 02:36 (~2)",
"files_changed": 4
}
]
}

View File

@@ -815,6 +815,18 @@ api/main.py
<h2>变更记录</h2>
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
<div class="changelog">
<article class="change">
<header>
<h3>2026-05-14 · 缩略图滑动条改为大号可拖轨道</h3>
<span class="tag violet">Canvas</span>
<span class="tag blue">Thumbnail</span>
</header>
<div class="body">
<p><strong>问题:</strong>节点上方缩略图横排内容多时,原生横向滚动条太细且在画布缩放下不容易点中,用户很难拖动。</p>
<p><strong>改动:</strong>新增 <code>FloatingThumbnailStrip</code> / <code>ThumbnailScrollRail</code>,在缩略图下方显示明显的大号紫色拖动轨道;轨道支持点击跳转、按住拖动和键盘左右移动,并用 <code>nodrag nopan</code> 避免误触发画布拖拽。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>web/app/globals.css</code><code>docs/source-analysis.html</code>。Input、VisualLab 以及保留的旧视觉节点缩略图条共用同一交互。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 三个视觉节点合并为画面工作台</h3>

View File

@@ -409,3 +409,23 @@
.react-flow__node .overflow-x-auto::-webkit-scrollbar-thumb:active {
background: rgba(217, 70, 239, 0.95);
}
.react-flow__node .thumbnail-strip {
scrollbar-width: none;
overscroll-behavior-x: contain;
}
.react-flow__node .thumbnail-strip::-webkit-scrollbar {
width: 0;
height: 0;
display: none;
}
.react-flow__node .thumbnail-scroll-rail {
touch-action: none;
}
.react-flow__node .thumbnail-scroll-rail:hover > div,
.react-flow__node .thumbnail-scroll-rail:focus-visible > div {
background: rgb(216, 180, 254);
}

View File

@@ -149,7 +149,7 @@ function ThumbnailScrollRail({
const max = Math.max(0, el.scrollWidth - el.clientWidth)
const visible = max > 2
const widthPct = visible
? Math.max(18, Math.min(92, (el.clientWidth / Math.max(el.scrollWidth, 1)) * 100))
? Math.max(26, Math.min(92, (el.clientWidth / Math.max(el.scrollWidth, 1)) * 100))
: 100
const leftPct = visible ? (el.scrollLeft / max) * (100 - widthPct) : 0
const next = {
@@ -207,10 +207,10 @@ function ThumbnailScrollRail({
aria-valuemax={rail.max}
aria-valuenow={rail.now}
tabIndex={0}
className={`thumbnail-scroll-rail nodrag nopan relative mt-2 h-5 rounded-full border shadow-[0_10px_24px_rgba(0,0,0,0.28)] outline-none transition ${
className={`thumbnail-scroll-rail nodrag nopan relative mt-2.5 h-9 rounded-full border shadow-[0_14px_28px_rgba(0,0,0,0.34),0_0_0_1px_rgba(255,255,255,0.08)] outline-none transition ${
dragging
? "cursor-grabbing border-violet-200/80 bg-violet-950/80 ring-2 ring-violet-300/70"
: "cursor-grab border-white/20 bg-black/50 hover:border-violet-300/75 hover:bg-violet-950/60 focus-visible:border-violet-200 focus-visible:ring-2 focus-visible:ring-violet-300/70"
? "cursor-grabbing border-violet-100/95 bg-violet-500/55 ring-2 ring-violet-100/80"
: "cursor-grab border-violet-200/70 bg-violet-500/32 hover:border-violet-100/90 hover:bg-violet-400/45 focus-visible:border-violet-100 focus-visible:ring-2 focus-visible:ring-violet-100/80"
}`}
onPointerDown={(e) => {
const el = scrollRef.current
@@ -282,7 +282,7 @@ function ThumbnailScrollRail({
}}
>
<div
className="absolute bottom-[3px] top-[3px] rounded-full bg-violet-300 shadow-[0_0_0_1px_rgba(255,255,255,0.45),0_0_18px_rgba(167,139,250,0.65)] transition-colors"
className="absolute bottom-[6px] top-[6px] rounded-full bg-white shadow-[0_0_0_1px_rgba(255,255,255,0.78),0_0_24px_rgba(216,180,254,0.86)] transition-colors"
style={{ left: `${rail.leftPct}%`, width: `${rail.widthPct}%` }}
/>
</div>
@@ -1537,10 +1537,7 @@ export function StoryboardNode({ data, selected }: any) {
<div ref={rootRef} className="relative" style={{ width: "100%", height: "100%" }}>
{/* 节点上方:所有元素 crop 图(编排输入素材)— 视觉类节点统一样板:单行横滚 + 左上复制 + 右上删除 + hover/click pin 大预览 */}
{elementCrops.length > 0 && job && (
<div
className="absolute left-0 right-0 flex items-end gap-1.5 overflow-x-auto pb-1.5"
style={{ bottom: "calc(100% + 12px)" }}
>
<FloatingThumbnailStrip label="元素缩略图横向滑动条">
{elementCrops.map((p) => {
const key = `${p.frameIdx}_${p.elementId}`
return (
@@ -1606,7 +1603,7 @@ export function StoryboardNode({ data, selected }: any) {
</div>
)
})}
</div>
</FloatingThumbnailStrip>
)}
{(() => {
@@ -1686,10 +1683,7 @@ export function VideoGenNode({ data, selected }: any) {
return (
<div ref={rootRef} className="relative" style={{ width: "100%", height: "100%" }}>
{videos.length > 0 && (
<div
className="absolute left-0 right-0 flex items-end gap-1.5 overflow-x-auto pb-1.5"
style={{ bottom: "calc(100% + 12px)" }}
>
<FloatingThumbnailStrip label="生成视频缩略图横向滑动条">
{videos.map((v, i) => {
const videoSrc = apiAssetUrl(v.url)
const posterSrc = apiAssetUrl(v.poster_url)
@@ -1771,7 +1765,7 @@ export function VideoGenNode({ data, selected }: any) {
</button>
</div>
)})}
</div>
</FloatingThumbnailStrip>
)}
{(() => {
if (!hoverPreviewVideo) return null