auto-save 2026-05-12 19:25 (~4)
This commit is contained in:
@@ -237,6 +237,13 @@
|
||||
"message": "auto-save 2026-05-12 19:14 (~3)",
|
||||
"hash": "30a4c46",
|
||||
"files_changed": 3
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-12T19:20:15+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-12 19:20 (~3)",
|
||||
"hash": "5a86328",
|
||||
"files_changed": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -240,10 +240,10 @@ export default function Home() {
|
||||
<ThemeToggle />
|
||||
</header>
|
||||
|
||||
{/* 左侧:竖向 tile 看板(窄版) */}
|
||||
{/* 左侧:竖向 tile 看板(极窄) */}
|
||||
<aside
|
||||
className="relative z-10 flex-shrink-0 border-r border-white/5 bg-black/20 backdrop-blur-xl overflow-y-auto"
|
||||
style={{ width: 144 }}
|
||||
style={{ width: 108 }}
|
||||
>
|
||||
<Dashboard data={nodeData} />
|
||||
</aside>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
import { useRef, useState, type ReactNode } from "react"
|
||||
import { useEffect, useRef, useState, type ReactNode } from "react"
|
||||
import { createPortal } from "react-dom"
|
||||
import {
|
||||
Link2, Upload, Download, Scissors, Image as ImageIcon,
|
||||
Mic, Languages, FileEdit, Sparkles, Film, FileVideo, Loader2, Plus, Check,
|
||||
@@ -80,6 +81,8 @@ export function Dashboard({ data }: Props) {
|
||||
const [videoT, setVideoT] = useState(0)
|
||||
const [addingFrame, setAddingFrame] = useState(false)
|
||||
const [expanded, setExpanded] = useState<Set<string>>(new Set())
|
||||
const [mounted, setMounted] = useState(false)
|
||||
useEffect(() => setMounted(true), [])
|
||||
const tileRefs = useRef<Record<string, HTMLButtonElement | null>>({})
|
||||
const fileRef = useRef<HTMLInputElement>(null)
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
@@ -149,40 +152,35 @@ export function Dashboard({ data }: Props) {
|
||||
ref={(el) => { tileRefs.current[t.key] = el }}
|
||||
type="button"
|
||||
onClick={() => toggleTile(t.key)}
|
||||
title={colSummary[t.key]}
|
||||
className={`group w-full rounded-md overflow-hidden border flex items-stretch transition ${
|
||||
title={`${t.title} · ${colSummary[t.key]}`}
|
||||
className={`group w-full rounded-md overflow-hidden border flex items-center transition relative ${
|
||||
isOpen ? "border-violet-400/70 ring-2 ring-violet-400/40 shadow-lg shadow-violet-500/20" : "border-white/10 hover:border-white/20"
|
||||
}`}
|
||||
style={{ height: 30 }}
|
||||
style={{ height: 28 }}
|
||||
>
|
||||
<div className="px-1.5 flex items-center gap-1.5 flex-1 min-w-0" style={{ background: TYPE_GRAD[t.type] }}>
|
||||
<div className="px-1.5 flex items-center gap-1 flex-1 min-w-0" style={{ background: TYPE_GRAD[t.type] }}>
|
||||
<span className="text-white shrink-0">{t.icon}</span>
|
||||
<span className="text-white text-[11.5px] font-medium truncate">{t.title}</span>
|
||||
</div>
|
||||
<div className="px-1.5 flex items-center bg-black/40 shrink-0">
|
||||
<span className={`h-1.5 w-1.5 rounded-full ${STATE_DOT[state]}`} />
|
||||
<span className="text-white text-[11px] font-medium truncate">{t.title}</span>
|
||||
</div>
|
||||
<span className={`absolute right-1.5 h-1.5 w-1.5 rounded-full ${STATE_DOT[state]} ring-2 ring-black/40`} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col gap-1.5 p-2.5">
|
||||
<div className="text-[9.5px] uppercase tracking-[0.25em] text-[var(--text-faint)] px-1 pb-0.5">Pipeline</div>
|
||||
<div className="h-full flex flex-col gap-1 p-1.5">
|
||||
{/* 主线:input / download / split */}
|
||||
<Tile tkey="input" />
|
||||
<Tile tkey="download" />
|
||||
<Tile tkey="split" />
|
||||
{/* 分叉:上路 关键帧/生图/生视频 */}
|
||||
<div className="border-l-2 border-violet-400/30 pl-2 ml-2 space-y-1.5">
|
||||
<div className="text-[9px] uppercase tracking-widest text-[var(--text-faint)]">视频路</div>
|
||||
<div className="border-l-2 border-violet-400/30 pl-1.5 ml-1 space-y-1">
|
||||
<Tile tkey="keyframe" />
|
||||
<Tile tkey="imagegen" />
|
||||
<Tile tkey="videogen" />
|
||||
</div>
|
||||
{/* 分叉:下路 转录/翻译/改写 */}
|
||||
<div className="border-l-2 border-pink-400/30 pl-2 ml-2 space-y-1.5">
|
||||
<div className="text-[9px] uppercase tracking-widest text-[var(--text-faint)]">音频路</div>
|
||||
<div className="border-l-2 border-pink-400/30 pl-1.5 ml-1 space-y-1">
|
||||
<Tile tkey="asr" />
|
||||
<Tile tkey="translate" />
|
||||
<Tile tkey="rewrite" />
|
||||
@@ -190,11 +188,11 @@ export function Dashboard({ data }: Props) {
|
||||
{/* 合流 */}
|
||||
<Tile tkey="compose" />
|
||||
|
||||
{/* 展开面板 — 离 sidebar 一段距离,明显"飞出"悬浮 */}
|
||||
{expanded.size > 0 && (
|
||||
{/* 展开面板 — 用 portal 渲染到 body 避免 backdrop-filter 影响 fixed 定位 */}
|
||||
{expanded.size > 0 && mounted && createPortal(
|
||||
<div
|
||||
className="fixed z-40"
|
||||
style={{ left: 168, top: 16, bottom: 16, width: 400 }}
|
||||
className="fixed z-[100]"
|
||||
style={{ left: 130, top: 16, bottom: 16, width: 400 }}
|
||||
>
|
||||
{TILES.filter((t) => expanded.has(t.key)).map((t) => (
|
||||
<section
|
||||
@@ -225,7 +223,8 @@ export function Dashboard({ data }: Props) {
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -272,7 +272,7 @@ export function KeyframeNode({ data, selected }: any) {
|
||||
key={f.index}
|
||||
onClick={(e) => { e.stopPropagation(); d.onExpandFrame(f.index) }}
|
||||
title={`第 ${f.index + 1} 张 · ${f.timestamp.toFixed(1)}s · 点击放大`}
|
||||
className={`group relative overflow-hidden rounded-md border transition shadow-lg hover:scale-110 hover:-translate-y-1 ${
|
||||
className={`group relative rounded-md border transition shadow-lg hover:-translate-y-0.5 ${
|
||||
isSel
|
||||
? "border-emerald-400 ring-2 ring-emerald-400/60"
|
||||
: "border-white/30 dark:border-white/20"
|
||||
@@ -282,15 +282,39 @@ export function KeyframeNode({ data, selected }: any) {
|
||||
<img
|
||||
src={frameUrl(jobId, f.index)}
|
||||
alt={`frame ${f.index}`}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
className="absolute inset-0 w-full h-full object-cover rounded-md"
|
||||
/>
|
||||
{isSel && (
|
||||
<div className="absolute inset-0 bg-emerald-400/15" />
|
||||
<div className="absolute inset-0 bg-emerald-400/15 rounded-md pointer-events-none" />
|
||||
)}
|
||||
{/* 时间戳 */}
|
||||
<div className="absolute bottom-0 right-0 bg-black/70 text-white text-[8.5px] font-mono px-1 py-0.5 leading-none">
|
||||
<div className="absolute bottom-0 right-0 bg-black/70 text-white text-[8.5px] font-mono px-1 py-0.5 leading-none rounded-bl rounded-br-md">
|
||||
{f.timestamp.toFixed(1)}s
|
||||
</div>
|
||||
|
||||
{/* Hover 大图预览 */}
|
||||
<div
|
||||
className="pointer-events-none absolute opacity-0 group-hover:opacity-100 scale-90 group-hover:scale-100 transition-all duration-200 z-[60]"
|
||||
style={{
|
||||
bottom: "calc(100% + 8px)",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
transformOrigin: "bottom center",
|
||||
}}
|
||||
>
|
||||
<div className="rounded-xl overflow-hidden border border-white/20 shadow-2xl bg-black">
|
||||
<img
|
||||
src={frameUrl(jobId, f.index)}
|
||||
alt={`preview ${f.index}`}
|
||||
className="block"
|
||||
style={{ width: KEYFRAME_WIDTH, height: "auto", maxHeight: "70vh" }}
|
||||
/>
|
||||
<div className="flex items-center justify-between px-2.5 py-1.5 bg-black/60 backdrop-blur-md">
|
||||
<span className="text-white text-[11px]">分镜 {f.index + 1}</span>
|
||||
<span className="text-white/60 text-[10px] font-mono">{f.timestamp.toFixed(2)}s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user