auto-save 2026-05-13 00:00 (~5)

This commit is contained in:
2026-05-13 00:00:53 +08:00
parent fd4c78f697
commit 9957274a5f
5 changed files with 71 additions and 30 deletions

View File

@@ -496,6 +496,13 @@
"message": "auto-save 2026-05-12 23:49 (~2)",
"hash": "25a1e63",
"files_changed": 2
},
{
"ts": "2026-05-12T23:55:21+08:00",
"type": "commit",
"message": "auto-save 2026-05-12 23:55 (~2)",
"hash": "fd4c78f",
"files_changed": 2
}
]
}

View File

@@ -15,7 +15,6 @@ import {
import { ThemeToggle } from "@/components/theme-toggle"
import { Dashboard } from "@/components/dashboard"
import { addManualFrame, analyzeJob, createJob, getJob, uploadJob, type Job } from "@/lib/api"
import { FrameLightbox } from "@/components/lightbox"
import { VideoLightbox } from "@/components/video-lightbox"
const NODE_TYPES = {
@@ -215,15 +214,18 @@ export default function Home() {
submitting,
analyzing,
selectedFrames,
expandedFrame,
onSubmitUrl: handleSubmit,
onUploadFile: handleUpload,
onAnalyze: handleAnalyze,
onToggleFrame: handleToggleFrame,
onExpandFrame: setExpandedFrame,
onCloseExpandedFrame: () => setExpandedFrame(null),
onAddManualFrame: handleAddManualFrame,
onOpenVideoLightbox: () => setVideoLightboxOpen(true),
onSwitchJob: handleSwitchJob,
}), [job, jobs, activeJobId, submitting, analyzing, selectedFrames, handleSubmit, handleUpload, handleAnalyze, handleToggleFrame, handleAddManualFrame, handleSwitchJob])
onJobUpdate: setJob as any,
}), [job, jobs, activeJobId, submitting, analyzing, selectedFrames, expandedFrame, handleSubmit, handleUpload, handleAnalyze, handleToggleFrame, handleAddManualFrame, handleSwitchJob, setJob])
// 用 useNodesState 让 ReactFlow 自己管位置(避免轮询时重置 drag
const [nodes, setNodes, onNodesChange] = useNodesState<Node>(
@@ -305,19 +307,7 @@ export default function Home() {
<Toaster theme="system" position="bottom-center" />
{/* Lightbox 看大图 */}
{job && (
<FrameLightbox
jobId={job.id}
frames={job.frames}
activeIndex={expandedFrame}
selected={selectedFrames}
onClose={() => setExpandedFrame(null)}
onChange={setExpandedFrame}
onToggleSelect={handleToggleFrame}
onJobUpdate={setJob}
/>
)}
{/* FrameLightbox 已嵌入 dashboard 的 keyframe drawerembedded mode不再独立浮动 */}
{/* Video lightbox — InputNode 缩略图点击进入 */}
<VideoLightbox

View File

@@ -8,6 +8,7 @@ import {
} from "lucide-react"
import { type Job, frameUrl, videoUrl } from "@/lib/api"
import { type NodeData } from "@/components/nodes"
import { FrameLightbox } from "@/components/lightbox"
type ColType = "input" | "process" | "ai" | "output"
const TYPE_GRAD: Record<ColType, string> = {
@@ -142,7 +143,17 @@ export function Dashboard({ data }: Props) {
const toggleTile = (key: string) => {
setExpanded((prev) => (prev.has(key) ? new Set() : new Set([key])))
}
const closeTile = (_key: string) => setExpanded(new Set())
const closeTile = (_key: string) => {
setExpanded(new Set())
data.onCloseExpandedFrame()
}
// 点关键帧缩略图时onExpandFrame 触发),自动打开 keyframe drawer
useEffect(() => {
if (data.expandedFrame !== null && !expanded.has("keyframe")) {
setExpanded(new Set(["keyframe"]))
}
}, [data.expandedFrame])
const Tile = ({ tkey }: { tkey: string }) => {
const t = TILES.find((x) => x.key === tkey)!
@@ -187,13 +198,20 @@ export function Dashboard({ data }: Props) {
{/* 合流 */}
<Tile tkey="compose" />
{/* 展开面板 — 用 portal 渲染到 body 避免 backdrop-filter 影响 fixed 定位 */}
{/* 展开面板 — keyframe 有选中帧时变宽容纳 lightbox */}
{expanded.size > 0 && mounted && createPortal(
<div
className="fixed z-[100]"
style={{ left: 130, top: 16, bottom: 16, width: 400 }}
style={{
left: 130,
top: 16,
bottom: 16,
width: expanded.has("keyframe") && data.expandedFrame !== null ? 760 : 400,
}}
>
{TILES.filter((t) => expanded.has(t.key)).map((t) => (
{TILES.filter((t) => expanded.has(t.key)).map((t) => {
const isKeyframeWithExpand = t.key === "keyframe" && data.expandedFrame !== null
return (
<section
key={t.key}
className="rounded-2xl border border-white/15 bg-black/60 backdrop-blur-2xl overflow-hidden flex flex-col h-full"
@@ -218,10 +236,25 @@ export function Dashboard({ data }: Props) {
</button>
</div>
<div className="overflow-y-auto p-3 bg-black/20 flex-1">
{renderSection(t.key)}
{isKeyframeWithExpand && data.job ? (
<FrameLightbox
embedded
jobId={data.job.id}
frames={data.job.frames}
activeIndex={data.expandedFrame}
selected={data.selectedFrames}
onClose={data.onCloseExpandedFrame}
onChange={data.onExpandFrame}
onToggleSelect={data.onToggleFrame}
onJobUpdate={data.onJobUpdate}
/>
) : (
renderSection(t.key)
)}
</div>
</section>
))}
)
})}
</div>,
document.body
)}

View File

@@ -14,9 +14,10 @@ interface Props {
onChange: (idx: number) => void
onToggleSelect: (idx: number) => void
onJobUpdate?: (job: Job) => void
embedded?: boolean // true=嵌入到容器里(无 fixedfalse=独立浮动卡(默认)
}
export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect, onJobUpdate }: Props) {
export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect, onJobUpdate, embedded = false }: Props) {
const [extractPrompt, setExtractPrompt] = useState("")
const [describing, setDescribing] = useState(false)
const [mounted, setMounted] = useState(false)
@@ -66,11 +67,14 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
navigator.clipboard.writeText(text).then(() => toast.success("已复制"))
}
return createPortal(
const content = (
<div
onClick={(e) => e.stopPropagation()}
className="fixed z-[100] rounded-2xl border border-white/15 bg-black/70 backdrop-blur-2xl overflow-hidden flex flex-col"
style={{
className={`rounded-2xl border border-white/15 overflow-hidden flex flex-col ${embedded ? "" : "fixed z-[100] bg-black/70 backdrop-blur-2xl"}`}
style={embedded ? {
height: "100%",
background: "transparent",
} : {
top: 80,
right: 16,
width: 740,
@@ -79,8 +83,11 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
animation: "drawer-in 0.24s cubic-bezier(0.32, 0.72, 0, 1)",
}}
>
{/* 顶部工具栏 — 切换 / 关闭 */}
<div className="flex items-center justify-between px-4 py-2 border-b border-white/10 bg-white/[0.03]">
{/* 顶部工具栏 — 切换 / 关闭,用 keyframe 橙红配色 */}
<div
className="flex items-center justify-between px-4 py-2 text-white"
style={{ background: "linear-gradient(135deg, #f59e0b, #ef4444)" }}
>
<div className="flex items-center gap-2">
<button
onClick={(e) => { e.stopPropagation(); if (arrayPos > 0) onChange(frames[arrayPos - 1].index) }}
@@ -262,9 +269,10 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
</div>
<div className="px-4 py-1.5 text-[10px] text-white/40 font-mono text-center border-t border-white/5 bg-white/[0.02]">
/ · Space · ESC ·
/ · Space · ESC
</div>
</div>,
document.body,
</div>
)
return embedded ? content : createPortal(content, document.body)
}

View File

@@ -15,14 +15,17 @@ export interface NodeData {
submitting: boolean
analyzing: boolean
selectedFrames: Set<number>
expandedFrame: number | null
onSubmitUrl: (url: string) => void
onUploadFile: (file: File) => void
onAnalyze: () => void
onToggleFrame: (idx: number) => void
onExpandFrame: (idx: number) => void
onCloseExpandedFrame: () => void
onAddManualFrame: (t: number) => void
onOpenVideoLightbox: () => void
onSwitchJob: (id: string) => void
onJobUpdate: (j: Job) => void
}
/* ---- 状态映射工具 ---- */