Polish video canvas surface consistency
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-05 03:00:29 +08:00
parent a787a97221
commit e60177c177
5 changed files with 98 additions and 97 deletions

View File

@ -419,19 +419,19 @@ const AGENT_RESULT_MODE_LABELS: Record<AgentGenerationResultModeType, string> =
sku_replacement: "SKU 替换", sku_replacement: "SKU 替换",
}; };
const VIDEO_CANVAS_PANEL_CLASS = const VIDEO_CANVAS_PANEL_CLASS =
"pointer-events-auto overflow-hidden rounded-[28px] border border-border/60 bg-background shadow-2xl"; "pointer-events-auto overflow-hidden rounded-[28px] border border-slate-200 bg-white text-foreground shadow-[0_20px_50px_-28px_rgba(15,23,42,0.24)] dark:border-zinc-800 dark:bg-zinc-950";
const VIDEO_CANVAS_PANEL_BADGE_CLASS = const VIDEO_CANVAS_PANEL_BADGE_CLASS =
"border-border/60 bg-background text-foreground/75"; "border-slate-200 bg-white text-foreground/75 dark:border-zinc-800 dark:bg-zinc-950";
const VIDEO_CANVAS_SUBSECTION_CLASS = const VIDEO_CANVAS_SUBSECTION_CLASS =
"rounded-2xl border border-border/60 bg-background p-3"; "rounded-2xl border border-slate-200 bg-white p-3 dark:border-zinc-800 dark:bg-zinc-950";
const VIDEO_CANVAS_TOOLBAR_CLASS = const VIDEO_CANVAS_TOOLBAR_CLASS =
"flex items-center gap-1.5 rounded-[28px] border border-border/60 bg-background px-2.5 py-1.5 shadow-2xl"; "flex items-center gap-1.5 rounded-[28px] border border-slate-200 bg-white px-2.5 py-1.5 shadow-[0_18px_40px_-24px_rgba(15,23,42,0.24)] dark:border-zinc-800 dark:bg-zinc-950";
const VIDEO_CANVAS_TOOLBAR_BUTTON_CLASS = const VIDEO_CANVAS_TOOLBAR_BUTTON_CLASS =
"h-9 w-9 rounded-xl border border-transparent text-foreground/75 hover:border-border/60 hover:bg-background hover:text-foreground"; "h-9 w-9 rounded-xl border border-transparent text-foreground/75 hover:border-slate-200 hover:bg-slate-50 hover:text-foreground dark:hover:border-zinc-800 dark:hover:bg-zinc-900";
const VIDEO_CANVAS_TOOLBAR_ACTIVE_BUTTON_CLASS = const VIDEO_CANVAS_TOOLBAR_ACTIVE_BUTTON_CLASS =
"border-border/60 bg-background text-foreground shadow-sm hover:bg-background"; "border-slate-200 bg-white text-foreground shadow-sm hover:bg-slate-50 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-900";
const VIDEO_CANVAS_COMPACT_CONTROLS_CLASS = const VIDEO_CANVAS_COMPACT_CONTROLS_CLASS =
"rounded-full border border-border/60 bg-background px-2 py-1 shadow-sm"; "rounded-full border border-slate-200 bg-white px-2 py-1 shadow-sm dark:border-zinc-800 dark:bg-zinc-950";
function buildScopedCanvasChatId(projectId: string, scope: "agent-mode") { function buildScopedCanvasChatId(projectId: string, scope: "agent-mode") {
const normalized = String(projectId || "").trim().toLowerCase().replace(/-/g, ""); const normalized = String(projectId || "").trim().toLowerCase().replace(/-/g, "");
@ -7095,8 +7095,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div> <div>
<CardTitle className="text-base">{selectedVideoWorkflowDefinition.label}</CardTitle> <CardTitle className="text-base">{selectedVideoWorkflowDefinition.label}</CardTitle>
<CardDescription className="mt-1"> <CardDescription className="mt-1 line-clamp-2">
参考 APP 里这个节点后面通常会接这些分支,点击后会自动放到右侧并连线。 常用下游分支会放在这里,点击后自动放到右侧并连线。
</CardDescription> </CardDescription>
</div> </div>
<Badge variant="outline" className={VIDEO_CANVAS_PANEL_BADGE_CLASS}> <Badge variant="outline" className={VIDEO_CANVAS_PANEL_BADGE_CLASS}>
@ -7110,7 +7110,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
key={`${selectedVideoWorkflowDefinition.type}-${item.type}`} key={`${selectedVideoWorkflowDefinition.type}-${item.type}`}
size="sm" size="sm"
variant="secondary" variant="secondary"
className="rounded-full border border-border/60 bg-background hover:bg-background" className="rounded-full border border-slate-200 bg-white hover:bg-slate-50 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-900"
onClick={() => handleAddWorkflowNode(item.type)} onClick={() => handleAddWorkflowNode(item.type)}
> >
{item.label} {item.label}
@ -7126,7 +7126,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div> <div>
<CardTitle className="text-base">工作流执行</CardTitle> <CardTitle className="text-base">工作流执行</CardTitle>
<CardDescription className="mt-1"> <CardDescription className="mt-1 line-clamp-2">
{selectedWorkflowActionHint} {selectedWorkflowActionHint}
</CardDescription> </CardDescription>
</div> </div>
@ -7200,7 +7200,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
<div className="text-[11px] uppercase tracking-[0.16em] text-muted-foreground"> <div className="text-[11px] uppercase tracking-[0.16em] text-muted-foreground">
上游提示 上游提示
</div> </div>
<div className="mt-2 text-sm leading-6 text-foreground"> <div className="mt-2 line-clamp-4 whitespace-pre-line text-sm leading-6 text-foreground">
{selectedWorkflowPrompt.trim() {selectedWorkflowPrompt.trim()
? selectedWorkflowPrompt ? selectedWorkflowPrompt
: "当前还没有抽取到可执行提示词,先在上游接入脚本、分镜或参考素材。"} : "当前还没有抽取到可执行提示词,先在上游接入脚本、分镜或参考素材。"}
@ -7217,7 +7217,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
{selectedWorkflowLinkedAgentProject.title || selectedWorkflowLinkedAgentProject.id} {selectedWorkflowLinkedAgentProject.title || selectedWorkflowLinkedAgentProject.id}
</div> </div>
<div className="mt-1 text-xs leading-5 text-muted-foreground"> <div className="mt-1 text-xs leading-5 text-muted-foreground">
再次点击下方按钮会把当前视频画布上下文同步进去,并直接打开该项目。 再点下方按钮会同步当前视频画布上下文,并直接打开该项目。
</div> </div>
</div> </div>
) : null} ) : null}
@ -7245,7 +7245,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
variant="secondary" variant="secondary"
onClick={() => void handleOpenSelectedWorkflowInAgent()} onClick={() => void handleOpenSelectedWorkflowInAgent()}
disabled={isAnyWorkflowExecutionRunning} disabled={isAnyWorkflowExecutionRunning}
className="w-full rounded-2xl border border-border/60 bg-background hover:bg-background" className="w-full rounded-2xl border border-slate-200 bg-white hover:bg-slate-50 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-900"
> >
{isSelectedWorkflowAgentRunning ? ( {isSelectedWorkflowAgentRunning ? (
<> <>
@ -7267,7 +7267,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
variant="secondary" variant="secondary"
onClick={() => void handleCreateSelectedAgentResultBranches()} onClick={() => void handleCreateSelectedAgentResultBranches()}
disabled={isAnyWorkflowExecutionRunning} disabled={isAnyWorkflowExecutionRunning}
className="w-full rounded-2xl border border-border/60 bg-background hover:bg-background" className="w-full rounded-2xl border border-slate-200 bg-white hover:bg-slate-50 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-900"
> >
{isSelectedWorkflowBranchRunning ? ( {isSelectedWorkflowBranchRunning ? (
<> <>
@ -7286,7 +7286,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
variant="secondary" variant="secondary"
onClick={() => void handleRunStoryboardBatch()} onClick={() => void handleRunStoryboardBatch()}
disabled={isAnyWorkflowExecutionRunning} disabled={isAnyWorkflowExecutionRunning}
className="w-full rounded-2xl border border-border/60 bg-background hover:bg-background" className="w-full rounded-2xl border border-slate-200 bg-white hover:bg-slate-50 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-900"
> >
{isSelectedWorkflowBatchRunning ? ( {isSelectedWorkflowBatchRunning ? (
<> <>

View File

@ -66,10 +66,10 @@ export function CanvasWorkspace({
const WORKFLOW_HANDLE_SIZE = 16; const WORKFLOW_HANDLE_SIZE = 16;
const isWorkflowCanvas = workspaceVariant === "video"; const isWorkflowCanvas = workspaceVariant === "video";
const overlayPanelClassName = isWorkflowCanvas const overlayPanelClassName = isWorkflowCanvas
? "rounded-full border border-border/60 bg-background px-3 py-1.5 text-xs text-muted-foreground shadow-sm" ? "rounded-full border border-slate-200 bg-white px-3 py-1.5 text-xs text-muted-foreground shadow-sm dark:border-zinc-800 dark:bg-zinc-950"
: "bg-background/80 backdrop-blur-sm rounded-md px-2 py-1 text-xs text-muted-foreground"; : "bg-background/80 backdrop-blur-sm rounded-md px-2 py-1 text-xs text-muted-foreground";
const zoomOverlayClassName = isWorkflowCanvas const zoomOverlayClassName = isWorkflowCanvas
? "absolute bottom-4 right-4 rounded-full border border-border/60 bg-background shadow-sm" ? "absolute bottom-4 right-4 rounded-full border border-slate-200 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-950"
: "absolute bottom-4 right-4 bg-background/80 backdrop-blur-sm rounded-lg flex items-center gap-1 px-1 py-1 shadow-sm border border-border/50"; : "absolute bottom-4 right-4 bg-background/80 backdrop-blur-sm rounded-lg flex items-center gap-1 px-1 py-1 shadow-sm border border-border/50";
type SnapGuideLines = { type SnapGuideLines = {
@ -1325,7 +1325,7 @@ export function CanvasWorkspace({
className={cn( className={cn(
"px-4 py-2 text-sm text-primary shadow-sm", "px-4 py-2 text-sm text-primary shadow-sm",
isWorkflowCanvas isWorkflowCanvas
? "rounded-full border border-primary/20 bg-background" ? "rounded-full border border-primary/20 bg-white dark:bg-zinc-950"
: "rounded-md bg-background/90" : "rounded-md bg-background/90"
)} )}
> >
@ -1525,8 +1525,8 @@ export function CanvasWorkspace({
{showEmptyState && nodes.length === 0 && ( {showEmptyState && nodes.length === 0 && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none"> <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
{isWorkflowCanvas ? ( {isWorkflowCanvas ? (
<div className="rounded-[30px] border border-border/60 bg-background/90 px-8 py-6 text-center shadow-[0_24px_60px_-34px_rgba(15,23,42,0.35)] backdrop-blur-xl"> <div className="rounded-[30px] border border-slate-200 bg-white px-8 py-6 text-center shadow-[0_24px_60px_-34px_rgba(15,23,42,0.18)] dark:border-zinc-800 dark:bg-zinc-950">
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-2xl border border-border/60 bg-background text-foreground shadow-sm"> <div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-2xl border border-slate-200 bg-white text-foreground shadow-sm dark:border-zinc-800 dark:bg-zinc-950">
<GitBranch className="h-5 w-5" /> <GitBranch className="h-5 w-5" />
</div> </div>
<p className="text-lg font-semibold text-foreground">{workspaceLabel}还是空的</p> <p className="text-lg font-semibold text-foreground">{workspaceLabel}还是空的</p>

View File

@ -2630,7 +2630,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<div <div
ref={containerRef} ref={containerRef}
className={cn( className={cn(
"relative w-full h-full bg-card rounded-[4px] overflow-hidden border shadow-sm transition-colors", "relative h-full w-full overflow-hidden rounded-[4px] border bg-white shadow-sm transition-colors dark:bg-zinc-950",
isSelected isSelected
? "rounded-none border-[2px] border-[#27a8ff] shadow-none" ? "rounded-none border-[2px] border-[#27a8ff] shadow-none"
: "border-transparent" : "border-transparent"
@ -2644,7 +2644,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
{isCompareMode && hasCompareSource ? ( {isCompareMode && hasCompareSource ? (
<div <div
ref={compareFrameRef} ref={compareFrameRef}
className="relative h-full w-full overflow-hidden bg-muted/30 select-none cursor-col-resize" className="relative h-full w-full cursor-col-resize select-none overflow-hidden bg-slate-50 dark:bg-zinc-900"
style={{ touchAction: "none" }} style={{ touchAction: "none" }}
data-canvas-no-drag="true" data-canvas-no-drag="true"
onPointerDown={(event) => { onPointerDown={(event) => {
@ -2661,7 +2661,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
clipPath: `inset(0 ${100 - compareSplitPercent}% 0 0)`, clipPath: `inset(0 ${100 - compareSplitPercent}% 0 0)`,
}} }}
> >
<div className="absolute inset-0 bg-muted/30" /> <div className="absolute inset-0 bg-slate-50 dark:bg-zinc-900" />
<img <img
src={displayImageSrc} src={displayImageSrc}
alt={displayTitle} alt={displayTitle}
@ -2682,7 +2682,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
clipPath: `inset(0 0 0 ${compareSplitPercent}%)`, clipPath: `inset(0 0 0 ${compareSplitPercent}%)`,
}} }}
> >
<div className="absolute inset-0 bg-muted/30" /> <div className="absolute inset-0 bg-slate-50 dark:bg-zinc-900" />
<img <img
src={displayCompareSrc} src={displayCompareSrc}
alt={`${displayTitle}-原图`} alt={`${displayTitle}-原图`}
@ -2698,20 +2698,20 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
/> />
</div> </div>
<div <div
className="pointer-events-none absolute inset-y-0 z-30 w-[2px] bg-white/90 shadow-[0_0_0_1px_rgba(0,0,0,0.45),0_0_14px_rgba(255,255,255,0.55)]" className="pointer-events-none absolute inset-y-0 z-30 w-[2px] bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.45),0_0_10px_rgba(255,255,255,0.45)]"
style={{ left: `${compareSplitPercent}%`, transform: "translateX(-50%)" }} style={{ left: `${compareSplitPercent}%`, transform: "translateX(-50%)" }}
/> />
<div <div
className="pointer-events-none absolute top-1/2 z-40 h-7 w-7 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white/85 bg-black/70 text-white flex items-center justify-center" className="pointer-events-none absolute top-1/2 z-40 flex h-7 w-7 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-white bg-slate-950 text-white"
style={{ left: `${compareSplitPercent}%` }} style={{ left: `${compareSplitPercent}%` }}
aria-hidden="true" aria-hidden="true"
> >
&lt;&gt; &lt;&gt;
</div> </div>
<div className="pointer-events-none absolute left-2 top-2 rounded bg-black/55 px-1.5 py-0.5 text-[10px] text-white"> <div className="pointer-events-none absolute left-2 top-2 rounded bg-slate-950 px-1.5 py-0.5 text-[10px] text-white">
新图 新图
</div> </div>
<div className="pointer-events-none absolute right-2 top-2 rounded bg-black/55 px-1.5 py-0.5 text-[10px] text-white"> <div className="pointer-events-none absolute right-2 top-2 rounded bg-slate-950 px-1.5 py-0.5 text-[10px] text-white">
原图 原图
</div> </div>
</div> </div>
@ -2722,7 +2722,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
src={displayImageSrc} src={displayImageSrc}
alt={displayTitle} alt={displayTitle}
className={cn( className={cn(
"w-full h-full object-contain bg-muted/30", "h-full w-full object-contain bg-slate-50 dark:bg-zinc-900",
boxCutoutMode ? "cursor-crosshair" : isThisNodePicking ? "cursor-crosshair" : "cursor-pointer" boxCutoutMode ? "cursor-crosshair" : isThisNodePicking ? "cursor-crosshair" : "cursor-pointer"
)} )}
draggable={false} draggable={false}
@ -2870,19 +2870,19 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
)} )}
</div> </div>
) : data.status === 'generating' ? ( ) : data.status === 'generating' ? (
<div className="w-full h-full flex flex-col items-center justify-center bg-muted select-none"> <div className="flex h-full w-full flex-col items-center justify-center bg-slate-50 select-none dark:bg-zinc-900">
<Loader2 className="h-8 w-8 animate-spin text-primary mb-2" /> <Loader2 className="h-8 w-8 animate-spin text-primary mb-2" />
<span className="text-sm text-muted-foreground text-center px-3 select-none"> <span className="text-sm text-muted-foreground text-center px-3 select-none">
{detailProgressLabel || "生成中..."} {detailProgressLabel || "生成中..."}
</span> </span>
</div> </div>
) : data.status === 'failed' ? ( ) : data.status === 'failed' ? (
<div className="w-full h-full flex flex-col items-center justify-center bg-destructive/10 select-none"> <div className="flex h-full w-full flex-col items-center justify-center bg-red-50 select-none dark:bg-red-950/40">
<AlertCircle className="h-8 w-8 text-destructive mb-2" /> <AlertCircle className="h-8 w-8 text-destructive mb-2" />
<span className="text-sm text-destructive select-none">生成失败</span> <span className="text-sm text-destructive select-none">生成失败</span>
</div> </div>
) : ( ) : (
<div className="w-full h-full flex flex-col items-center justify-center bg-muted select-none"> <div className="flex h-full w-full flex-col items-center justify-center bg-slate-50 select-none dark:bg-zinc-900">
<ImagePlus className="h-8 w-8 text-muted-foreground mb-2" /> <ImagePlus className="h-8 w-8 text-muted-foreground mb-2" />
<span className="text-sm text-muted-foreground mb-3 select-none">点击上传图片</span> <span className="text-sm text-muted-foreground mb-3 select-none">点击上传图片</span>
{/* 隐藏的文件输入 */} {/* 隐藏的文件输入 */}
@ -2906,7 +2906,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
{/* 处理中遮罩 */} {/* 处理中遮罩 */}
{isProcessing && ( {isProcessing && (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center"> <div className="absolute inset-0 flex items-center justify-center bg-slate-950">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<Loader2 className="h-8 w-8 animate-spin text-white mb-2" /> <Loader2 className="h-8 w-8 animate-spin text-white mb-2" />
<span className="text-sm text-white">处理中...</span> <span className="text-sm text-white">处理中...</span>
@ -3000,14 +3000,14 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
{/* 提示词显示 */} {/* 提示词显示 */}
{data.prompt && isHovered && !isProcessing && ( {data.prompt && isHovered && !isProcessing && (
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-3"> <div className="absolute bottom-0 left-0 right-0 border-t border-slate-800 bg-slate-950 p-3">
<p className="text-xs text-white line-clamp-2">{data.prompt}</p> <p className="text-xs text-white line-clamp-2">{data.prompt}</p>
</div> </div>
)} )}
{data.note && !isSelected && ( {data.note && !isSelected && (
<div <div
className="pointer-events-none absolute top-2 left-2 inline-flex items-center gap-1 rounded-full bg-amber-100/95 px-2 py-1 text-[11px] text-amber-700 shadow-sm" className="pointer-events-none absolute top-2 left-2 inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-1 text-[11px] text-amber-700 shadow-sm"
title="该图片含有备注" title="该图片含有备注"
> >
<StickyNote className="h-3 w-3" /> <StickyNote className="h-3 w-3" />
@ -3019,7 +3019,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
type="button" type="button"
onClick={() => setSnapshotDialogOpen(true)} onClick={() => setSnapshotDialogOpen(true)}
className={cn( className={cn(
"absolute left-2 inline-flex items-center gap-1 rounded-full bg-black/70 px-2 py-1 text-[11px] text-white shadow-sm transition-colors hover:bg-black/80", "absolute left-2 inline-flex items-center gap-1 rounded-full bg-slate-950 px-2 py-1 text-[11px] text-white shadow-sm transition-colors hover:bg-black",
data.note && !isSelected ? "top-10" : "top-2" data.note && !isSelected ? "top-10" : "top-2"
)} )}
title={`执行快照:${agentExecutionSnapshotId}`} title={`执行快照:${agentExecutionSnapshotId}`}
@ -3102,7 +3102,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<TooltipProvider delayDuration={200}> <TooltipProvider delayDuration={200}>
<div <div
ref={toolbarRef} ref={toolbarRef}
className="fixed z-[9999] flex items-center rounded-[24px] border border-slate-200 bg-white/95 px-2 py-2 text-gray-800 shadow-[0_14px_38px_rgba(15,23,42,0.16)] backdrop-blur-md dark:border-slate-700 dark:bg-slate-900/95 dark:text-gray-100" className="fixed z-[9999] flex items-center rounded-[24px] border border-slate-200 bg-white px-2 py-2 text-gray-800 shadow-[0_14px_38px_rgba(15,23,42,0.16)] dark:border-slate-700 dark:bg-slate-900 dark:text-gray-100"
style={{ style={{
top: toolbarPosition.top, top: toolbarPosition.top,
left: toolbarPosition.left, left: toolbarPosition.left,
@ -3136,7 +3136,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
disabled={isProcessing} disabled={isProcessing}
className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
onClick={() => handleAction('enhance')} onClick={() => handleAction('enhance')}
> >
<span className="mr-1.5 rounded-md border border-slate-300 px-1.5 text-[11px] leading-5 text-slate-600 dark:border-slate-500 dark:text-slate-100">HD</span> <span className="mr-1.5 rounded-md border border-slate-300 px-1.5 text-[11px] leading-5 text-slate-600 dark:border-slate-500 dark:text-slate-100">HD</span>
@ -3147,7 +3147,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
disabled={isProcessing} disabled={isProcessing}
className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
onClick={() => handleAction('enhance-pro')} onClick={() => handleAction('enhance-pro')}
> >
<Sparkles className="mr-1.5 h-4 w-4" /> <Sparkles className="mr-1.5 h-4 w-4" />
@ -3158,7 +3158,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
disabled={isProcessing} disabled={isProcessing}
className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
onClick={() => handleAction('rmbg')} onClick={() => handleAction('rmbg')}
> >
<Scissors className="mr-1.5 h-4 w-4" /> <Scissors className="mr-1.5 h-4 w-4" />
@ -3168,7 +3168,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
disabled={isProcessing} disabled={isProcessing}
className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
onClick={() => handleAction('watermark')} onClick={() => handleAction('watermark')}
> >
<Eraser className="mr-1.5 h-4 w-4" /> <Eraser className="mr-1.5 h-4 w-4" />
@ -3178,7 +3178,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
disabled={isProcessing} disabled={isProcessing}
className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
onClick={() => handleAction('resize')} onClick={() => handleAction('resize')}
> >
<Maximize2 className="mr-1.5 h-4 w-4" /> <Maximize2 className="mr-1.5 h-4 w-4" />
@ -3188,7 +3188,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
disabled={isProcessing || isDetectingText || isApplyingTextEdit} disabled={isProcessing || isDetectingText || isApplyingTextEdit}
className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
onClick={() => handleAction('text-edit')} onClick={() => handleAction('text-edit')}
> >
<Type className="mr-1.5 h-4 w-4" /> <Type className="mr-1.5 h-4 w-4" />
@ -3198,7 +3198,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
disabled={isProcessing} disabled={isProcessing}
className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
onClick={() => handleAction('angle')} onClick={() => handleAction('angle')}
> >
<Box className="mr-1.5 h-4 w-4" /> <Box className="mr-1.5 h-4 w-4" />
@ -3208,7 +3208,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
disabled={isProcessing} disabled={isProcessing}
className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 rounded-xl px-3 text-[13px] font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
onClick={() => handleAction('layer-split')} onClick={() => handleAction('layer-split')}
> >
<Layers className="mr-1.5 h-4 w-4" /> <Layers className="mr-1.5 h-4 w-4" />
@ -3223,7 +3223,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
variant="ghost" variant="ghost"
size="icon" size="icon"
disabled={isProcessing} disabled={isProcessing}
className="h-10 w-10 rounded-xl text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 w-10 rounded-xl text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
</Button> </Button>
@ -3260,7 +3260,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
variant="ghost" variant="ghost"
size="icon" size="icon"
disabled={isProcessing} disabled={isProcessing}
className="h-10 w-10 rounded-xl text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 w-10 rounded-xl text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
onClick={handleDownload} onClick={handleDownload}
> >
<Download className="h-4 w-4" /> <Download className="h-4 w-4" />
@ -3273,7 +3273,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
{isSelected && !isMultiSelected && data.src && boxCutoutMode && typeof document !== "undefined" && createPortal( {isSelected && !isMultiSelected && data.src && boxCutoutMode && typeof document !== "undefined" && createPortal(
<div <div
ref={boxCutoutToolbarRef} ref={boxCutoutToolbarRef}
className="fixed z-[9999] flex items-center rounded-[24px] border border-slate-200 bg-white/95 px-2 py-2 text-gray-800 shadow-[0_14px_38px_rgba(15,23,42,0.16)] backdrop-blur-md dark:border-slate-700 dark:bg-slate-900/95 dark:text-gray-100" className="fixed z-[9999] flex items-center rounded-[24px] border border-slate-200 bg-white px-2 py-2 text-gray-800 shadow-[0_14px_38px_rgba(15,23,42,0.16)] dark:border-slate-700 dark:bg-slate-900 dark:text-gray-100"
style={{ style={{
top: boxCutoutToolbarPosition.top, top: boxCutoutToolbarPosition.top,
left: boxCutoutToolbarPosition.left, left: boxCutoutToolbarPosition.left,
@ -3288,7 +3288,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
onPointerDown={(event) => event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()}
onClick={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()}
> >
<div className="mr-1 inline-flex h-10 shrink-0 items-center gap-1.5 rounded-xl bg-slate-100 px-3 text-[13px] font-medium text-slate-700 dark:bg-white/10 dark:text-slate-100"> <div className="mr-1 inline-flex h-10 shrink-0 items-center gap-1.5 rounded-xl bg-slate-100 px-3 text-[13px] font-medium text-slate-700 dark:bg-zinc-800 dark:text-slate-100">
<Scan className="h-4 w-4 text-[#27a8ff]" /> <Scan className="h-4 w-4 text-[#27a8ff]" />
<span className="whitespace-nowrap">{boxCutoutHintText}</span> <span className="whitespace-nowrap">{boxCutoutHintText}</span>
</div> </div>
@ -3312,7 +3312,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-10 w-10 shrink-0 rounded-xl text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-white/10" className="h-10 w-10 shrink-0 rounded-xl text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
onClick={() => resetBoxCutoutState()} onClick={() => resetBoxCutoutState()}
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
@ -3359,7 +3359,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
{/* 文字编辑面板(显示在图片节点旁边) */} {/* 文字编辑面板(显示在图片节点旁边) */}
{isSelected && textEditPanelOpen && data.src && typeof document !== "undefined" && createPortal( {isSelected && textEditPanelOpen && data.src && typeof document !== "undefined" && createPortal(
<div <div
className="fixed z-[9999] w-[360px] rounded-xl border border-border bg-background/90 backdrop-blur shadow-2xl" className="fixed z-[9999] w-[360px] rounded-xl border border-slate-200 bg-white shadow-2xl dark:border-zinc-800 dark:bg-zinc-950"
style={{ style={{
top: getTextPanelPosition().top, top: getTextPanelPosition().top,
left: getTextPanelPosition().left, left: getTextPanelPosition().left,
@ -3402,7 +3402,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
)} )}
{!isDetectingText && textEditItems.length === 0 && !textEditError && ( {!isDetectingText && textEditItems.length === 0 && !textEditError && (
<div className="rounded-md bg-muted/50 text-muted-foreground text-xs px-3 py-3"> <div className="rounded-md bg-slate-50 px-3 py-3 text-xs text-muted-foreground dark:bg-zinc-900">
未识别到可编辑文字,可点击“重新识别”再试一次。 未识别到可编辑文字,可点击“重新识别”再试一次。
</div> </div>
)} )}
@ -3436,7 +3436,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
))} ))}
{deletedTextItems.length > 0 && ( {deletedTextItems.length > 0 && (
<div className="rounded-md border border-dashed border-orange-300 bg-orange-50/60 px-3 py-2 text-xs text-orange-700"> <div className="rounded-md border border-dashed border-orange-300 bg-orange-50 px-3 py-2 text-xs text-orange-700 dark:border-orange-900 dark:bg-orange-950/40 dark:text-orange-200">
已标记删除 {deletedTextItems.length} 条文字,应用时会明确告诉生图模型移除这些文字并修复背景。 已标记删除 {deletedTextItems.length} 条文字,应用时会明确告诉生图模型移除这些文字并修复背景。
</div> </div>
)} )}
@ -3501,7 +3501,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
> >
{/* 关闭按钮 */} {/* 关闭按钮 */}
<button <button
className="absolute top-4 right-4 p-2 rounded-full bg-white/10 hover:bg-white/20 transition-colors" className="absolute top-4 right-4 rounded-full bg-slate-950 p-2 transition-colors hover:bg-black"
onClick={() => setFullscreenPreviewOpen(false)} onClick={() => setFullscreenPreviewOpen(false)}
> >
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">

View File

@ -138,8 +138,8 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
return ( return (
<div <div
className={cn( className={cn(
"relative w-full h-full bg-card rounded-lg overflow-hidden border shadow-sm", "relative h-full w-full overflow-hidden rounded-lg border bg-white shadow-sm dark:bg-zinc-950",
isSelected && "border-primary" isSelected ? "border-primary" : "border-slate-200 dark:border-zinc-800"
)} )}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
@ -150,7 +150,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
<video <video
ref={videoRef} ref={videoRef}
src={data.src} src={data.src}
className="w-full h-full object-cover" className="h-full w-full bg-slate-950 object-cover"
muted={isMuted} muted={isMuted}
loop loop
onTimeUpdate={handleTimeUpdate} onTimeUpdate={handleTimeUpdate}
@ -159,12 +159,12 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
{/* 播放控制层 */} {/* 播放控制层 */}
{isHovered && ( {isHovered && (
<div className="absolute inset-0 bg-black/30 flex flex-col justify-end"> <div className="absolute inset-0 flex flex-col justify-end">
{/* 中央播放按钮 */} {/* 中央播放按钮 */}
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-14 w-14 rounded-full bg-black/50 hover:bg-black/70 text-white" className="absolute left-1/2 top-1/2 h-14 w-14 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white/20 bg-slate-950 text-white hover:bg-black"
onClick={togglePlay} onClick={togglePlay}
> >
{isPlaying ? ( {isPlaying ? (
@ -175,7 +175,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
</Button> </Button>
{/* 底部控制栏 */} {/* 底部控制栏 */}
<div className="p-3 space-y-2"> <div className="m-3 space-y-2 rounded-2xl border border-slate-800 bg-slate-950 p-3">
{/* 进度条 */} {/* 进度条 */}
<Slider <Slider
value={[progress]} value={[progress]}
@ -191,7 +191,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-7 w-7 text-white hover:bg-white/20" className="h-7 w-7 text-white hover:bg-slate-800"
onClick={togglePlay} onClick={togglePlay}
> >
{isPlaying ? ( {isPlaying ? (
@ -203,7 +203,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-7 w-7 text-white hover:bg-white/20" className="h-7 w-7 text-white hover:bg-slate-800"
onClick={toggleMute} onClick={toggleMute}
> >
{isMuted ? ( {isMuted ? (
@ -219,7 +219,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
)} )}
</div> </div>
) : data.status === 'generating' ? ( ) : data.status === 'generating' ? (
<div className="w-full h-full flex flex-col items-center justify-center bg-muted select-none"> <div className="flex h-full w-full flex-col items-center justify-center bg-slate-50 select-none dark:bg-zinc-900">
<Loader2 className="h-8 w-8 animate-spin text-primary mb-2" /> <Loader2 className="h-8 w-8 animate-spin text-primary mb-2" />
<span className="text-sm text-muted-foreground select-none">生成中...</span> <span className="text-sm text-muted-foreground select-none">生成中...</span>
{data.progress && ( {data.progress && (
@ -227,12 +227,12 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
)} )}
</div> </div>
) : data.status === 'failed' ? ( ) : data.status === 'failed' ? (
<div className="w-full h-full flex flex-col items-center justify-center bg-destructive/10 select-none"> <div className="flex h-full w-full flex-col items-center justify-center bg-red-50 select-none dark:bg-red-950/40">
<AlertCircle className="h-8 w-8 text-destructive mb-2" /> <AlertCircle className="h-8 w-8 text-destructive mb-2" />
<span className="text-sm text-destructive select-none">生成失败</span> <span className="text-sm text-destructive select-none">生成失败</span>
</div> </div>
) : ( ) : (
<label className="w-full h-full flex flex-col items-center justify-center bg-muted cursor-pointer hover:bg-muted/80 transition-colors select-none"> <label className="flex h-full w-full cursor-pointer flex-col items-center justify-center bg-slate-50 transition-colors select-none hover:bg-slate-100 dark:bg-zinc-900 dark:hover:bg-zinc-800">
<Video className="h-8 w-8 text-muted-foreground mb-2" /> <Video className="h-8 w-8 text-muted-foreground mb-2" />
<span className="text-sm text-muted-foreground select-none">点击上传视频</span> <span className="text-sm text-muted-foreground select-none">点击上传视频</span>
<input <input
@ -275,7 +275,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
<button <button
type="button" type="button"
onClick={() => setSnapshotDialogOpen(true)} onClick={() => setSnapshotDialogOpen(true)}
className="absolute top-2 left-2 inline-flex items-center gap-1 rounded-full bg-black/70 px-2 py-1 text-[11px] text-white shadow-sm transition-colors hover:bg-black/80" className="absolute top-2 left-2 inline-flex items-center gap-1 rounded-full bg-slate-950 px-2 py-1 text-[11px] text-white shadow-sm transition-colors hover:bg-black"
title={`执行快照:${agentExecutionSnapshotId}`} title={`执行快照:${agentExecutionSnapshotId}`}
> >
快照 {shortAgentExecutionSnapshotId} 快照 {shortAgentExecutionSnapshotId}
@ -289,7 +289,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
{/* 提示词显示 */} {/* 提示词显示 */}
{data.prompt && isHovered && ( {data.prompt && isHovered && (
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-3"> <div className="absolute bottom-0 left-0 right-0 border-t border-slate-800 bg-slate-950 p-3">
<p className="text-xs text-white line-clamp-2">{data.prompt}</p> <p className="text-xs text-white line-clamp-2">{data.prompt}</p>
</div> </div>
)} )}

View File

@ -143,43 +143,44 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
const batchFailedCount = Number(metadata.batchFailedCount || 0); const batchFailedCount = Number(metadata.batchFailedCount || 0);
const batchMode = const batchMode =
String(metadata.batchMode || "").trim() === "gen-video" ? "视频批量" : "图片批量"; String(metadata.batchMode || "").trim() === "gen-video" ? "视频批量" : "图片批量";
const accentGlow = toAlphaColor(accentColor, 0.18) || "rgba(59, 130, 246, 0.12)";
const accentLine = toAlphaColor(accentColor, 0.3) || "rgba(59, 130, 246, 0.2)";
const sectionClassName = const sectionClassName =
"rounded-2xl border border-border/60 bg-background p-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.3)]"; "rounded-2xl border border-slate-200 bg-white p-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.78)] dark:border-zinc-800 dark:bg-zinc-950";
const mutedKickerClassName = const mutedKickerClassName =
"text-[11px] uppercase tracking-[0.18em] text-muted-foreground/80"; "text-[11px] uppercase tracking-[0.18em] text-muted-foreground/80";
const itemClassName = "rounded-xl border border-border/50 bg-background px-3 py-2"; const itemClassName =
const badgeClassName = "border-border/60 bg-background text-foreground/80"; "rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 dark:border-zinc-800 dark:bg-zinc-900";
const badgeClassName =
"border-slate-200 bg-white text-foreground/80 dark:border-zinc-800 dark:bg-zinc-950";
const accentBarColor = toAlphaColor(accentColor, 0.9) || accentColor;
return ( return (
<div <div
className={cn( className={cn(
"relative h-full w-full overflow-hidden rounded-[28px] border border-border/60 bg-background shadow-[0_22px_55px_-30px_rgba(15,23,42,0.38)] transition-all", "relative h-full w-full overflow-hidden rounded-[28px] border border-slate-200 bg-white shadow-[0_22px_55px_-30px_rgba(15,23,42,0.18)] transition-all dark:border-zinc-800 dark:bg-zinc-950",
isSelected && isSelected &&
"border-primary/55 ring-2 ring-primary/15 shadow-[0_26px_60px_-28px_rgba(59,130,246,0.28)]" "border-primary/55 ring-2 ring-primary/15 shadow-[0_24px_56px_-30px_rgba(59,130,246,0.24)]"
)} )}
> >
<div <div
className="pointer-events-none absolute inset-0" className="pointer-events-none absolute inset-x-0 top-0 h-1"
style={{ style={{ backgroundColor: accentBarColor }}
backgroundImage: `radial-gradient(circle at top left, ${accentGlow} 0%, transparent 42%), linear-gradient(180deg, ${accentLine} 0%, transparent 18%)`,
}}
/> />
<div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-border/70" /> <div className="pointer-events-none absolute inset-x-0 top-1 h-px bg-slate-200 dark:bg-zinc-800" />
<div className="relative flex h-full flex-col gap-4 p-4"> <div className="relative flex h-full flex-col gap-4 p-4">
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium text-muted-foreground"> <div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
<span className="flex h-8 w-8 items-center justify-center rounded-full border border-border/60 bg-background shadow-sm"> <span className="flex h-8 w-8 items-center justify-center rounded-full border border-slate-200 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-950">
<GroupIcon className="h-4 w-4" /> <GroupIcon className="h-4 w-4" />
</span> </span>
<span>{groupMeta.label}</span> <span>{groupMeta.label}</span>
</div> </div>
<div> <div>
<h3 className="text-base font-semibold leading-tight text-foreground">{title}</h3> <h3 className="text-base font-semibold leading-tight text-foreground">{title}</h3>
<p className="mt-1 text-sm leading-6 text-muted-foreground">{description}</p> <p className="mt-1 line-clamp-2 text-sm leading-6 text-muted-foreground">
{description}
</p>
</div> </div>
</div> </div>
<Badge variant="outline" className={badgeClassName}> <Badge variant="outline" className={badgeClassName}>
@ -190,8 +191,8 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
<div className={sectionClassName}> <div className={sectionClassName}>
<div className={mutedKickerClassName}>节点标识</div> <div className={mutedKickerClassName}>节点标识</div>
<div className="mt-1 font-mono text-xs text-foreground/80">{data.templateType}</div> <div className="mt-1 font-mono text-xs text-foreground/80">{data.templateType}</div>
<p className="mt-2 text-sm leading-6 text-muted-foreground"> <p className="mt-2 line-clamp-3 text-sm leading-6 text-muted-foreground">
{data.summary || "把这个节点接在上游素材之后,用来承接处理或输出。"} {data.summary || "接在上游素材之后,用来处理或输出结果。"}
</p> </p>
</div> </div>
@ -227,7 +228,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
) : null} ) : null}
{previewUrl ? ( {previewUrl ? (
<div className="overflow-hidden rounded-2xl border border-border/60 bg-slate-950 shadow-[0_16px_40px_-28px_rgba(15,23,42,0.65)]"> <div className="overflow-hidden rounded-2xl border border-slate-200 bg-slate-950 shadow-[0_16px_40px_-28px_rgba(15,23,42,0.65)] dark:border-zinc-800">
{previewKind === "video" ? ( {previewKind === "video" ? (
<video <video
src={previewUrl} src={previewUrl}
@ -261,9 +262,9 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
return ( return (
<div <div
key={`${data.templateType}-compare-${index}`} key={`${data.templateType}-compare-${index}`}
className="overflow-hidden rounded-2xl border border-border/60 bg-background" className="overflow-hidden rounded-2xl border border-slate-200 bg-white dark:border-zinc-800 dark:bg-zinc-950"
> >
<div className="border-b border-border/60 px-3 py-2"> <div className="border-b border-slate-200 px-3 py-2 dark:border-zinc-800">
<div className="text-[11px] uppercase tracking-[0.16em] text-muted-foreground"> <div className="text-[11px] uppercase tracking-[0.16em] text-muted-foreground">
{String(compareItem.label || `对比 ${index + 1}`)} {String(compareItem.label || `对比 ${index + 1}`)}
</div> </div>
@ -304,7 +305,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
<div className="text-xs font-semibold text-foreground/90"> <div className="text-xs font-semibold text-foreground/90">
{String(shot.title || `镜头 ${index + 1}`)} {String(shot.title || `镜头 ${index + 1}`)}
</div> </div>
<div className="mt-1 text-xs leading-5 text-muted-foreground"> <div className="mt-1 line-clamp-2 text-xs leading-5 text-muted-foreground">
{String(shot.description || "")} {String(shot.description || "")}
</div> </div>
</div> </div>
@ -352,7 +353,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
<div className="text-[11px] uppercase tracking-[0.16em] text-muted-foreground"> <div className="text-[11px] uppercase tracking-[0.16em] text-muted-foreground">
{label} {label}
</div> </div>
<div className="mt-1 text-xs leading-5 text-foreground/85"> <div className="mt-1 line-clamp-3 text-xs leading-5 text-foreground/85">
{value} {value}
</div> </div>
</div> </div>
@ -372,7 +373,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
{analysisHighlights.map((item, index) => ( {analysisHighlights.map((item, index) => (
<div <div
key={`${data.templateType}-analysis-${index}`} key={`${data.templateType}-analysis-${index}`}
className={cn(itemClassName, "text-xs leading-5 text-muted-foreground")} className={cn(itemClassName, "line-clamp-3 text-xs leading-5 text-muted-foreground")}
> >
{String(item || "")} {String(item || "")}
</div> </div>
@ -392,7 +393,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
<div className="text-xs font-semibold text-foreground/90"> <div className="text-xs font-semibold text-foreground/90">
{String(character.name || character.title || `角色 ${index + 1}`)} {String(character.name || character.title || `角色 ${index + 1}`)}
</div> </div>
<div className="mt-1 text-xs leading-5 text-muted-foreground"> <div className="mt-1 line-clamp-3 text-xs leading-5 text-muted-foreground">
{String(character.description || character.prompt || "")} {String(character.description || character.prompt || "")}
</div> </div>
</div> </div>
@ -413,7 +414,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
<div className="text-xs font-semibold text-foreground/90"> <div className="text-xs font-semibold text-foreground/90">
{String(scene.name || scene.title || `场景 ${index + 1}`)} {String(scene.name || scene.title || `场景 ${index + 1}`)}
</div> </div>
<div className="mt-1 text-xs leading-5 text-muted-foreground"> <div className="mt-1 line-clamp-3 text-xs leading-5 text-muted-foreground">
{String(scene.description || scene.prompt || "")} {String(scene.description || scene.prompt || "")}
</div> </div>
</div> </div>