auto-save 2026-05-14 02:30 (+2, ~4)

This commit is contained in:
2026-05-14 02:31:01 +08:00
parent eace01e94a
commit 95fbb0cbc6
6 changed files with 364 additions and 56 deletions

View File

@@ -380,3 +380,32 @@
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--divider); border-radius: 4px; }
::-webkit-scrollbar-track { background: transparent; }
/* 节点内缩略图浮条(横滚)专用:加粗 + 紫色高亮 + 大可拖区,避免在画布 zoom 下拖不到 */
.react-flow__node .overflow-x-auto {
scrollbar-color: rgba(167, 139, 250, 0.65) rgba(255, 255, 255, 0.08);
scrollbar-width: auto;
}
.react-flow__node .overflow-x-auto::-webkit-scrollbar {
height: 14px;
}
.react-flow__node .overflow-x-auto::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.06);
border-radius: 8px;
margin: 0 4px;
border: 1px solid rgba(255, 255, 255, 0.08);
}
.react-flow__node .overflow-x-auto::-webkit-scrollbar-thumb {
background: rgba(167, 139, 250, 0.55);
border-radius: 8px;
border: 2px solid rgba(255, 255, 255, 0.18);
min-width: 48px;
background-clip: padding-box;
}
.react-flow__node .overflow-x-auto::-webkit-scrollbar-thumb:hover {
background: rgba(167, 139, 250, 0.85);
border-color: rgba(255, 255, 255, 0.3);
}
.react-flow__node .overflow-x-auto::-webkit-scrollbar-thumb:active {
background: rgba(217, 70, 239, 0.95);
}

View File

@@ -9,13 +9,11 @@ import {
import { Toaster, toast } from "sonner"
import { LayoutGrid } from "lucide-react"
import {
InputNode, KeyframeNode, AudioNode,
StoryboardNode, VideoGenNode, ComposeNode, KeyframePanelNode,
InputNode, VisualLabNode, AudioNode,
ComposeNode, KeyframePanelNode,
type NodeData,
} from "@/components/nodes"
import { ThemeToggle } from "@/components/theme-toggle"
import { StoryboardBar } from "@/components/storyboard-bar"
import { StoryboardWorkbench } from "@/components/storyboard-workbench"
import {
addManualFrame, analyzeJob, createJob, getJob, listJobs, uploadJob, deleteFrame, deleteGeneratedImage,
deleteGeneratedVideo, deleteCutout, generateStoryboardVideo,
@@ -25,10 +23,8 @@ import { VideoLightbox } from "@/components/video-lightbox"
const NODE_TYPES = {
input: InputNode,
keyframe: KeyframeNode,
visual: VisualLabNode,
audio: AudioNode,
storyboard: StoryboardNode,
videogen: VideoGenNode,
compose: ComposeNode,
keyframePanel: KeyframePanelNode,
}
@@ -36,15 +32,13 @@ const NODE_TYPES = {
const KEYFRAME_PANEL_ID = "keyframe-detail-panel"
// 合并 input + download + split 为一个节点
// 分叉:上路 input → keyframe → storyboard → videogen
// 分叉:上路 input → visual lab
// 下路 input → audio ──────────────────────────→ compose
const LAYOUT: Array<{ id: string; type: keyof typeof NODE_TYPES; x: number; y: number; w: number }> = [
{ id: "input", type: "input", x: 40, y: 240, w: 320 },
{ id: "keyframe", type: "keyframe", x: 460, y: 60, w: 360 },
{ id: "visual", type: "visual", x: 460, y: 60, w: 620 },
{ id: "audio", type: "audio", x: 460, y: 440, w: 320 },
{ id: "storyboard", type: "storyboard", x: 880, y: 60, w: 360 },
{ id: "videogen", type: "videogen", x: 1260, y: 60, w: 280 },
{ id: "compose", type: "compose", x: 1640, y: 240, w: 320 },
{ id: "compose", type: "compose", x: 1160, y: 240, w: 320 },
]
const NODE_SIZES_KEY = "skg-tk:node-sizes:v2"
@@ -74,11 +68,9 @@ function loadNodePins(): string[] {
}
const EDGES_RAW: Array<[string, string]> = [
["input", "keyframe"],
["input", "visual"],
["input", "audio"],
["keyframe", "storyboard"],
["storyboard", "videogen"],
["videogen", "compose"],
["visual", "compose"],
["audio", "compose"],
]
@@ -521,9 +513,7 @@ export default function Home() {
// 按管线列分组(顶 → 底):图层 1 输入 → 5 合成
const COLUMNS: string[][] = [
["input"],
["keyframe", "audio"],
["storyboard"],
["videogen"],
["visual", "audio"],
["compose"],
]
const GAP_X = 80
@@ -602,11 +592,11 @@ export default function Home() {
let shouldFocusNewPanel = false
setNodes((prev) => {
const keyframeNode = prev.find((n) => n.id === "keyframe")
const visualNode = prev.find((n) => n.id === "visual")
const inputNode = prev.find((n) => n.id === "input")
const defaultPosition = {
x: (inputNode?.position.x ?? 40) - 820,
y: (keyframeNode?.position.y ?? 60),
y: (visualNode?.position.y ?? 60),
}
const exists = prev.some((n) => n.id === KEYFRAME_PANEL_ID)
if (exists) {
@@ -637,7 +627,7 @@ export default function Home() {
if (shouldFocusNewPanel) {
window.setTimeout(() => {
flowRef.current?.fitView?.({
nodes: [{ id: KEYFRAME_PANEL_ID }, { id: "keyframe" }],
nodes: [{ id: KEYFRAME_PANEL_ID }, { id: "visual" }],
padding: 0.18,
duration: 260,
})
@@ -649,11 +639,10 @@ export default function Home() {
useEffect(() => {
const doneOf: Record<string, boolean> = {
input: !!job?.video_url,
keyframe: !!job && job.frames.length > 0,
visual: !!job && (job.frames.length > 0 || (job.generated_videos?.length ?? 0) > 0),
asr: !!job && job.transcript.length > 0,
translate: !!job && (job.transcript.some((s) => s.zh) ?? false),
rewrite: !!job && (job.transcript.some((s) => s.zh) ?? false),
storyboard: selectedFrames.size > 0,
}
setEdges((prev) => prev.map((e) => ({ ...e, animated: !!doneOf[e.source] })))
}, [job, setEdges])