Restyle video canvas editor UI
This commit is contained in:
parent
cd54fcf792
commit
b542a80f89
@ -732,19 +732,19 @@ const AGENT_RESULT_MODE_LABELS: Record<AgentGenerationResultModeType, string> =
|
||||
sku_replacement: "SKU 替换",
|
||||
};
|
||||
const VIDEO_CANVAS_PANEL_CLASS =
|
||||
"pointer-events-auto overflow-hidden rounded-[24px] border border-slate-200 bg-white text-foreground shadow-[0_16px_36px_-24px_rgba(15,23,42,0.2)] dark:border-zinc-800 dark:bg-zinc-950";
|
||||
"pointer-events-auto overflow-hidden rounded-[24px] border border-[#dbe2ee] bg-white text-[#16181d] shadow-[0_0_0_1px_rgba(219,226,238,0.42),0_24px_52px_rgba(22,24,29,0.08)] dark:border-zinc-800 dark:bg-zinc-950";
|
||||
const VIDEO_CANVAS_PANEL_BADGE_CLASS =
|
||||
"border-slate-200 bg-slate-50 text-foreground/70 dark:border-zinc-800 dark:bg-zinc-900";
|
||||
"border-[#d7dff0] bg-[#f6f8ff] text-[#5b6678] dark:border-zinc-800 dark:bg-zinc-900";
|
||||
const VIDEO_CANVAS_SUBSECTION_CLASS =
|
||||
"rounded-[18px] border border-slate-200 bg-slate-50 p-3 dark:border-zinc-800 dark:bg-zinc-900";
|
||||
"rounded-[18px] border border-[#e2e8f2] bg-[#f8faff] p-3.5 dark:border-zinc-800 dark:bg-zinc-900";
|
||||
const VIDEO_CANVAS_TOOLBAR_CLASS =
|
||||
"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";
|
||||
"flex items-center gap-1.5 rounded-[26px] border border-[#dbe2ee] bg-white px-2.5 py-1.5 shadow-[0_0_0_1px_rgba(219,226,238,0.42),0_20px_42px_rgba(22,24,29,0.08)] dark:border-zinc-800 dark:bg-zinc-950";
|
||||
const VIDEO_CANVAS_TOOLBAR_BUTTON_CLASS =
|
||||
"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";
|
||||
"h-9 w-9 rounded-[14px] border border-transparent text-[#5b6678] hover:border-[#d9e1f2] hover:bg-[#f7f9ff] hover:text-[#16181d] dark:hover:border-zinc-800 dark:hover:bg-zinc-900";
|
||||
const VIDEO_CANVAS_TOOLBAR_ACTIVE_BUTTON_CLASS =
|
||||
"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";
|
||||
"border-[#d6def7] bg-[#eef2ff] text-[#3954db] shadow-[0_0_0_1px_rgba(91,118,254,0.14)] hover:bg-[#e7edff] dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-900";
|
||||
const VIDEO_CANVAS_COMPACT_CONTROLS_CLASS =
|
||||
"rounded-full border border-slate-200 bg-white px-2 py-1 shadow-sm dark:border-zinc-800 dark:bg-zinc-950";
|
||||
"rounded-full border border-[#dbe2ee] bg-white px-2 py-1 shadow-[0_0_0_1px_rgba(219,226,238,0.36),0_12px_26px_rgba(22,24,29,0.06)] dark:border-zinc-800 dark:bg-zinc-950";
|
||||
|
||||
function buildScopedCanvasChatId(projectId: string, scope: "agent-mode") {
|
||||
const normalized = String(projectId || "")
|
||||
@ -1356,8 +1356,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
: null;
|
||||
const videoModuleSidebarWidth = isVideoMode
|
||||
? isVideoModuleSidebarCollapsed
|
||||
? 56
|
||||
: 228
|
||||
? 60
|
||||
: 264
|
||||
: 0;
|
||||
const selectedWorkflowPanelLeft = isVideoMode
|
||||
? videoModuleSidebarWidth + 24
|
||||
@ -8249,27 +8249,34 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-hidden rounded-[24px] border border-slate-200 bg-white shadow-[0_18px_40px_-28px_rgba(15,23,42,0.18)] dark:border-zinc-800 dark:bg-zinc-950",
|
||||
isVideoModuleSidebarCollapsed ? "w-14" : "w-[228px]"
|
||||
"overflow-hidden rounded-[24px] border border-[#dbe2ee] bg-white shadow-[0_0_0_1px_rgba(219,226,238,0.42),0_24px_52px_rgba(22,24,29,0.08)] dark:border-zinc-800 dark:bg-zinc-950",
|
||||
isVideoModuleSidebarCollapsed ? "w-[60px]" : "w-[264px]"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between border-slate-200 border-b px-3 py-2.5 dark:border-zinc-800">
|
||||
<div className="flex items-center justify-between border-b border-[#e8edf5] px-3 py-3 dark:border-zinc-800">
|
||||
{isVideoModuleSidebarCollapsed ? (
|
||||
<div className="flex w-full justify-center">
|
||||
<Layers className="h-5 w-5 text-slate-500 dark:text-zinc-400" />
|
||||
<span className="flex h-9 w-9 items-center justify-center rounded-[18px] border border-[#dde5f4] bg-[#f6f8ff] text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.24)]">
|
||||
<Layers className="h-4 w-4" />
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="flex h-8 w-8 items-center justify-center rounded-2xl border border-slate-200 bg-slate-50 text-slate-500 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300">
|
||||
<span className="flex h-9 w-9 items-center justify-center rounded-[18px] border border-[#dbe3ff] bg-[#eef2ff] text-[#5b76fe] shadow-[0_0_0_1px_rgba(91,118,254,0.08)] dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300">
|
||||
<Layers className="h-4 w-4" />
|
||||
</span>
|
||||
<div className="font-semibold text-foreground text-sm">
|
||||
模块
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-[#16181d]">
|
||||
视频模块
|
||||
</div>
|
||||
<div className="text-[11px] text-[#6f7483]">
|
||||
工作流分层添加
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
className="h-8 w-8 rounded-xl text-muted-foreground hover:bg-slate-50 hover:text-foreground dark:hover:bg-zinc-900"
|
||||
className="h-8 w-8 rounded-[14px] text-[#6f7483] hover:bg-[#f7f9ff] hover:text-[#16181d] dark:hover:bg-zinc-900"
|
||||
onClick={() =>
|
||||
setIsVideoModuleSidebarCollapsed((prev) => !prev)
|
||||
}
|
||||
@ -8286,12 +8293,12 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
</div>
|
||||
|
||||
{isVideoModuleSidebarCollapsed ? (
|
||||
<div className="flex flex-col items-center gap-2 px-2 py-2">
|
||||
<div className="flex flex-col items-center gap-2 px-2 py-2.5">
|
||||
{VIDEO_CANVAS_SIDEBAR_GROUPS.map((group) => {
|
||||
const GroupIcon = group.icon;
|
||||
return (
|
||||
<button
|
||||
className="flex h-10 w-10 items-center justify-center rounded-2xl border border-slate-200 bg-white font-semibold text-slate-500 text-xs transition-colors hover:bg-slate-50 hover:text-slate-800 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400 dark:hover:bg-zinc-900 dark:hover:text-zinc-100"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-[16px] border border-[#dde5f4] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.26)] transition-colors hover:bg-[#f4f7ff] hover:text-[#3954db] dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400 dark:hover:bg-zinc-900 dark:hover:text-zinc-100"
|
||||
key={group.id}
|
||||
onClick={() => {
|
||||
setIsVideoModuleSidebarCollapsed(false);
|
||||
@ -8310,7 +8317,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
</div>
|
||||
) : (
|
||||
<ScrollArea className="max-h-[calc(100vh-164px)]">
|
||||
<div className="space-y-2 p-2">
|
||||
<div className="space-y-3 p-3">
|
||||
{VIDEO_CANVAS_SIDEBAR_GROUPS.map((group) => {
|
||||
const GroupIcon = group.icon;
|
||||
return (
|
||||
@ -8324,23 +8331,28 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
}
|
||||
open={videoModuleSectionOpen[group.id] !== false}
|
||||
>
|
||||
<div className="rounded-[20px] border border-slate-200 bg-slate-50 dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<div className="overflow-hidden rounded-[20px] border border-[#e6ebf3] bg-[#fcfdff] dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<CollapsibleTrigger className="flex w-full items-center justify-between gap-3 px-3 py-2.5 text-left">
|
||||
<div className="flex min-w-0 items-center gap-2.5">
|
||||
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-2xl border border-slate-200 bg-white text-slate-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-300">
|
||||
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-[15px] border border-[#e3e8f2] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.3)] dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-300">
|
||||
<GroupIcon className="h-4 w-4" />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate font-medium text-foreground text-sm">
|
||||
{group.label}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="block truncate text-sm font-medium text-[#16181d]">
|
||||
{group.label}
|
||||
</span>
|
||||
<span className="block truncate text-[11px] text-[#7a8395]">
|
||||
{group.description}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
<span className="rounded-full border border-[#e2e7f1] bg-white px-2 py-0.5 text-[11px] text-[#6f7483] dark:border-zinc-800 dark:bg-zinc-950">
|
||||
{group.nodeTypes.length}
|
||||
</span>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-4 w-4 shrink-0 text-muted-foreground transition-transform",
|
||||
"h-4 w-4 shrink-0 text-[#6f7483] transition-transform",
|
||||
videoModuleSectionOpen[group.id] !== false
|
||||
? "rotate-0"
|
||||
: "-rotate-90"
|
||||
@ -8349,8 +8361,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="border-slate-200 border-t px-2 pt-2 pb-2 dark:border-zinc-800">
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
<div className="border-t border-[#edf1f7] px-2.5 pb-2.5 pt-2 dark:border-zinc-800">
|
||||
<div className="space-y-1">
|
||||
{group.nodeTypes.map((type) => {
|
||||
const definition =
|
||||
getVideoWorkflowNodeDefinition(type);
|
||||
@ -8359,7 +8371,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
getVideoWorkflowQuickConnectIcon(type);
|
||||
return (
|
||||
<button
|
||||
className="flex items-center gap-2 rounded-2xl border border-slate-200 bg-white px-2.5 py-2 text-left transition-colors hover:bg-slate-50 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-900"
|
||||
className="flex w-full items-center gap-2.5 rounded-[16px] border border-transparent bg-transparent px-2.5 py-2 text-left transition-all hover:border-[#d8e2ff] hover:bg-[#f4f7ff] hover:shadow-[0_0_0_1px_rgba(91,118,254,0.08)] dark:hover:bg-zinc-950"
|
||||
key={type}
|
||||
onClick={() =>
|
||||
handleAddWorkflowNode(type)
|
||||
@ -8368,14 +8380,15 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border border-slate-200 bg-slate-50 text-slate-600 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300"
|
||||
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-[14px] border border-white text-slate-600 shadow-[0_0_0_1px_rgba(219,226,238,0.32)] dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300"
|
||||
style={{
|
||||
color: definition.accentColor,
|
||||
backgroundColor: `${definition.accentColor}18`,
|
||||
}}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate font-medium text-foreground text-xs">
|
||||
<span className="min-w-0 flex-1 truncate text-[13px] font-medium text-[#16181d]">
|
||||
{definition.label}
|
||||
</span>
|
||||
</button>
|
||||
@ -8427,9 +8440,9 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
d={`M 0 0 C ${cp1X} 0, ${cp2X} ${y}, ${targetX} ${y}`}
|
||||
fill="none"
|
||||
key={`quick-connect-line-${item.type}`}
|
||||
stroke="rgba(59,130,246,0.9)"
|
||||
stroke="rgba(91,118,254,0.92)"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="2.5"
|
||||
strokeWidth="2.25"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -8437,7 +8450,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
|
||||
<button
|
||||
aria-label="展开关联模块"
|
||||
className="-translate-x-1/2 -translate-y-1/2 pointer-events-auto absolute top-0 left-0 flex h-16 w-16 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 shadow-[0_16px_32px_-18px_rgba(15,23,42,0.28)] transition-colors hover:bg-slate-50 hover:text-slate-700 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-300 dark:hover:bg-zinc-900 dark:hover:text-zinc-100"
|
||||
className="-translate-x-1/2 -translate-y-1/2 pointer-events-auto absolute top-0 left-0 flex h-14 w-14 items-center justify-center rounded-[20px] border border-[#d7dff1] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.34),0_18px_34px_rgba(22,24,29,0.12)] transition-colors hover:bg-[#f5f8ff] hover:text-[#3954db] dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-300 dark:hover:bg-zinc-900 dark:hover:text-zinc-100"
|
||||
onClick={() =>
|
||||
setVideoCanvasInsertMenu({
|
||||
screenPosition: {
|
||||
@ -8470,7 +8483,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
return (
|
||||
<button
|
||||
aria-label={item.label}
|
||||
className="-translate-x-1/2 -translate-y-1/2 pointer-events-auto absolute flex h-14 w-14 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 shadow-[0_14px_30px_-18px_rgba(15,23,42,0.24)] transition-all hover:scale-105 hover:bg-slate-50 hover:text-slate-800 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-300 dark:hover:bg-zinc-900 dark:hover:text-zinc-100"
|
||||
className="-translate-x-1/2 -translate-y-1/2 pointer-events-auto absolute flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#dbe2ee] bg-white text-[#566377] shadow-[0_0_0_1px_rgba(219,226,238,0.34),0_14px_26px_rgba(22,24,29,0.1)] transition-all hover:scale-[1.03] hover:border-[#cfdcff] hover:bg-[#f5f8ff] hover:text-[#3954db] dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-300 dark:hover:bg-zinc-900 dark:hover:text-zinc-100"
|
||||
key={`quick-connect-node-${item.type}`}
|
||||
onClick={() => handleAddWorkflowNode(item.type)}
|
||||
style={{
|
||||
@ -8491,7 +8504,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
selectedVideoWorkflowDefinition &&
|
||||
selectedWorkflowIsActionable ? (
|
||||
<div
|
||||
className="pointer-events-none absolute top-16 z-20 flex w-[min(388px,calc(100vw-32px))] flex-col gap-3"
|
||||
className="pointer-events-none absolute top-16 z-20 flex w-[min(360px,calc(100vw-32px))] flex-col gap-3"
|
||||
style={{ left: `${selectedWorkflowPanelLeft}px` }}
|
||||
>
|
||||
{selectedWorkflowIsActionable ? (
|
||||
@ -8637,7 +8650,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Button
|
||||
className="w-full rounded-2xl shadow-sm"
|
||||
className="w-full rounded-[18px] bg-[#5b76fe] text-white shadow-[0_16px_30px_-18px_rgba(91,118,254,0.56)] hover:bg-[#4965ef]"
|
||||
disabled={isAnyWorkflowExecutionRunning}
|
||||
onClick={() => void handleRunSelectedWorkflowNode()}
|
||||
type="button"
|
||||
@ -8654,7 +8667,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
|
||||
{selectedVideoWorkflowType === "custom-agent" ? (
|
||||
<Button
|
||||
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"
|
||||
className="w-full rounded-[18px] border border-[#dbe2ee] bg-white text-[#202430] hover:bg-[#f7f9ff] dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-900"
|
||||
disabled={isAnyWorkflowExecutionRunning}
|
||||
onClick={() => void handleOpenSelectedWorkflowInAgent()}
|
||||
type="button"
|
||||
@ -8676,7 +8689,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
{selectedVideoWorkflowType === "custom-agent" &&
|
||||
selectedWorkflowAgentResultAssets.length > 0 ? (
|
||||
<Button
|
||||
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"
|
||||
className="w-full rounded-[18px] border border-[#dbe2ee] bg-white text-[#202430] hover:bg-[#f7f9ff] dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-900"
|
||||
disabled={isAnyWorkflowExecutionRunning}
|
||||
onClick={() =>
|
||||
void handleCreateSelectedAgentResultBranches()
|
||||
@ -8697,7 +8710,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
|
||||
{selectedStoryboardCanBatchExecute ? (
|
||||
<Button
|
||||
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"
|
||||
className="w-full rounded-[18px] border border-[#dbe2ee] bg-white text-[#202430] hover:bg-[#f7f9ff] dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-900"
|
||||
disabled={isAnyWorkflowExecutionRunning}
|
||||
onClick={() => void handleRunStoryboardBatch()}
|
||||
type="button"
|
||||
@ -8715,7 +8728,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
className="w-full rounded-2xl shadow-sm"
|
||||
className="w-full rounded-[18px] border border-[#f0d1d8] bg-[#fff7f8] text-[#d24f63] shadow-none hover:bg-[#ffeff2]"
|
||||
disabled={isAnyWorkflowExecutionRunning}
|
||||
onClick={handleDeleteSelectedWorkflowNode}
|
||||
type="button"
|
||||
|
||||
@ -66,15 +66,15 @@ export function CanvasWorkspace({
|
||||
const IMAGE_HANDLE_SIZE = 9.6;
|
||||
const IMAGE_HANDLE_OFFSET = IMAGE_HANDLE_SIZE / 2;
|
||||
const SNAP_GUIDE_COLOR = "#ff8a3d";
|
||||
const WORKFLOW_EDGE_COLOR = "#94a3b8";
|
||||
const WORKFLOW_EDGE_ACTIVE_COLOR = "#334155";
|
||||
const WORKFLOW_EDGE_COLOR = "#8fa3ff";
|
||||
const WORKFLOW_EDGE_ACTIVE_COLOR = "#5b76fe";
|
||||
const WORKFLOW_HANDLE_SIZE = 16;
|
||||
const isWorkflowCanvas = workspaceVariant === "video";
|
||||
const overlayPanelClassName = isWorkflowCanvas
|
||||
? "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"
|
||||
? "rounded-full border border-[#dbe2ee] bg-white px-3.5 py-1.5 text-xs font-medium text-[#627089] shadow-[0_0_0_1px_rgba(219,226,238,0.32),0_12px_28px_rgba(22,24,29,0.06)] 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";
|
||||
const zoomOverlayClassName = isWorkflowCanvas
|
||||
? "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 rounded-full border border-[#dbe2ee] bg-white shadow-[0_0_0_1px_rgba(219,226,238,0.32),0_12px_28px_rgba(22,24,29,0.06)] 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";
|
||||
|
||||
type SnapGuideLines = {
|
||||
@ -1251,10 +1251,10 @@ export function CanvasWorkspace({
|
||||
data-canvas-input-handle="true"
|
||||
data-node-id={node.id}
|
||||
className={cn(
|
||||
"absolute top-1/2 z-20 rounded-full border-2 border-background/95 bg-foreground shadow-[0_10px_20px_-14px_rgba(15,23,42,0.7)] transition-all",
|
||||
"absolute top-1/2 z-20 rounded-full border border-[#d7dff1] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.34),0_12px_22px_rgba(22,24,29,0.1)] transition-all",
|
||||
tool === "draw"
|
||||
? "scale-100 opacity-100 ring-4 ring-primary/10"
|
||||
: "scale-90 opacity-75"
|
||||
? "scale-100 opacity-100 ring-4 ring-[#5b76fe]/14"
|
||||
: "scale-95 opacity-90"
|
||||
)}
|
||||
style={{
|
||||
left: -(WORKFLOW_HANDLE_SIZE / 2),
|
||||
@ -1272,10 +1272,10 @@ export function CanvasWorkspace({
|
||||
type="button"
|
||||
data-canvas-no-drag="true"
|
||||
className={cn(
|
||||
"absolute top-1/2 z-20 rounded-full border-2 border-background/95 bg-foreground shadow-[0_10px_20px_-14px_rgba(15,23,42,0.7)] transition-all",
|
||||
"absolute top-1/2 z-20 rounded-full border border-[#d7dff1] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.34),0_12px_22px_rgba(22,24,29,0.1)] transition-all",
|
||||
tool === "draw"
|
||||
? "scale-100 opacity-100 ring-4 ring-primary/10"
|
||||
: "scale-90 opacity-75"
|
||||
? "scale-100 opacity-100 ring-4 ring-[#5b76fe]/14"
|
||||
: "scale-95 opacity-90"
|
||||
)}
|
||||
style={{
|
||||
right: -(WORKFLOW_HANDLE_SIZE / 2),
|
||||
@ -1353,7 +1353,7 @@ export function CanvasWorkspace({
|
||||
className="pointer-events-none absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(circle at top left, rgba(59,130,246,0.08), transparent 24%), radial-gradient(circle at bottom right, rgba(148,163,184,0.1), transparent 26%)",
|
||||
"radial-gradient(circle at top left, rgba(91,118,254,0.05), transparent 26%), radial-gradient(circle at bottom right, rgba(164,176,220,0.08), transparent 26%)",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
@ -1572,17 +1572,14 @@ export function CanvasWorkspace({
|
||||
{showEmptyState && nodes.length === 0 && (
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
{isWorkflowCanvas ? (
|
||||
<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-slate-200 bg-white text-foreground shadow-sm dark:border-zinc-800 dark:bg-zinc-950">
|
||||
<div className="rounded-[26px] border border-[#dbe2ee] bg-white px-7 py-5 text-center shadow-[0_0_0_1px_rgba(219,226,238,0.34),0_24px_52px_rgba(22,24,29,0.08)] dark:border-zinc-800 dark:bg-zinc-950">
|
||||
<div className="mx-auto mb-3 flex h-11 w-11 items-center justify-center rounded-[18px] border border-[#dde5f4] bg-[#f6f8ff] text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.22)] dark:border-zinc-800 dark:bg-zinc-950">
|
||||
<GitBranch className="h-5 w-5" />
|
||||
</div>
|
||||
<p className="text-lg font-semibold text-foreground">{workspaceLabel}还是空的</p>
|
||||
<p className="mt-2 max-w-[360px] text-sm leading-6 text-muted-foreground">
|
||||
<p className="text-lg font-semibold text-[#16181d]">{workspaceLabel}还是空的</p>
|
||||
<p className="mt-2 max-w-[360px] text-sm leading-6 text-[#647086]">
|
||||
{emptyStateDescription}
|
||||
</p>
|
||||
<p className="mt-3 text-xs text-muted-foreground/80">
|
||||
可以先从左上角模板起手,也可以直接添加节点后用 `L` 连线。
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-muted-foreground">
|
||||
@ -1602,7 +1599,7 @@ export function CanvasWorkspace({
|
||||
className={cn(
|
||||
"h-8 gap-1.5 px-3 text-xs shadow-sm border border-border/60",
|
||||
isWorkflowCanvas &&
|
||||
"rounded-full bg-background"
|
||||
"rounded-full border-[#dbe2ee] bg-white text-[#3b4860] shadow-[0_0_0_1px_rgba(219,226,238,0.26)] hover:bg-[#f7f9ff]"
|
||||
)}
|
||||
onClick={handleDownloadSelectedImages}
|
||||
disabled={isDownloadSubmitting}
|
||||
|
||||
@ -2858,10 +2858,10 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"relative h-full w-full overflow-hidden rounded-[4px] border bg-white shadow-sm transition-colors dark:bg-zinc-950",
|
||||
"relative h-full w-full overflow-hidden rounded-[24px] border border-[#dbe2ee] bg-white shadow-[0_0_0_1px_rgba(219,226,238,0.34),0_16px_34px_rgba(22,24,29,0.08)] transition-all dark:border-zinc-800 dark:bg-zinc-950",
|
||||
isSelected
|
||||
? "rounded-none border-[#27a8ff] border-[2px] shadow-none"
|
||||
: "border-transparent"
|
||||
? "border-[#5b76fe] ring-2 ring-[#5b76fe]/14 shadow-[0_0_0_1px_rgba(91,118,254,0.14),0_20px_38px_rgba(91,118,254,0.12)]"
|
||||
: "hover:border-[#cfd9ea]"
|
||||
)}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
@ -2935,15 +2935,15 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="-translate-x-1/2 -translate-y-1/2 pointer-events-none absolute top-1/2 z-40 flex h-7 w-7 items-center justify-center rounded-full border border-white bg-slate-950 text-white"
|
||||
className="-translate-x-1/2 -translate-y-1/2 pointer-events-none absolute top-1/2 z-40 flex h-7 w-7 items-center justify-center rounded-full border border-[#dbe2ee] bg-white text-[#556072] shadow-[0_0_0_1px_rgba(219,226,238,0.26)]"
|
||||
style={{ left: `${compareSplitPercent}%` }}
|
||||
>
|
||||
<>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute top-2 left-2 rounded bg-slate-950 px-1.5 py-0.5 text-[10px] text-white">
|
||||
<div className="pointer-events-none absolute top-2 left-2 rounded-full border border-[#dbe2ee] bg-white px-2 py-0.5 text-[10px] text-[#556072] shadow-[0_0_0_1px_rgba(219,226,238,0.22)]">
|
||||
新图
|
||||
</div>
|
||||
<div className="pointer-events-none absolute top-2 right-2 rounded bg-slate-950 px-1.5 py-0.5 text-[10px] text-white">
|
||||
<div className="pointer-events-none absolute top-2 right-2 rounded-full border border-[#dbe2ee] bg-white px-2 py-0.5 text-[10px] text-[#556072] shadow-[0_0_0_1px_rgba(219,226,238,0.22)]">
|
||||
原图
|
||||
</div>
|
||||
</div>
|
||||
@ -3091,7 +3091,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="inline-flex items-center justify-center rounded-full border border-white/80 bg-black/78 text-white transition-colors hover:bg-black/88"
|
||||
className="inline-flex items-center justify-center rounded-full border border-[#dbe2ee] bg-white text-[#556072] shadow-[0_0_0_1px_rgba(219,226,238,0.26)] transition-colors hover:bg-[#f7f9ff] hover:text-[#16181d]"
|
||||
style={{
|
||||
width: `${compareButtonVisualSize}px`,
|
||||
height: `${compareButtonVisualSize}px`,
|
||||
@ -3124,24 +3124,35 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
)}
|
||||
</div>
|
||||
) : data.status === "generating" ? (
|
||||
<div className="flex h-full w-full select-none flex-col items-center justify-center bg-slate-50 dark:bg-zinc-900">
|
||||
<Loader2 className="mb-2 h-8 w-8 animate-spin text-primary" />
|
||||
<span className="select-none px-3 text-center text-muted-foreground text-sm">
|
||||
<div className="flex h-full w-full select-none flex-col items-center justify-center bg-[#f7f9fc] px-6 text-center dark:bg-zinc-900">
|
||||
<span className="mb-3 flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#dde5f4] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.22)]">
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
</span>
|
||||
<span className="select-none px-3 text-sm font-medium text-[#3f4b63]">
|
||||
{detailProgressLabel || "生成中..."}
|
||||
</span>
|
||||
<span className="mt-1 text-xs text-[#7a8395]">图片处理中</span>
|
||||
</div>
|
||||
) : data.status === "failed" ? (
|
||||
<div className="flex h-full w-full select-none flex-col items-center justify-center bg-red-50 dark:bg-red-950/40">
|
||||
<AlertCircle className="mb-2 h-8 w-8 text-destructive" />
|
||||
<span className="select-none text-destructive text-sm">
|
||||
<div className="flex h-full w-full select-none flex-col items-center justify-center bg-[#fff7f8] px-6 text-center dark:bg-red-950/40">
|
||||
<span className="mb-3 flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#f2d7dd] bg-white text-[#d24f63] shadow-[0_0_0_1px_rgba(242,215,221,0.22)]">
|
||||
<AlertCircle className="h-5 w-5" />
|
||||
</span>
|
||||
<span className="select-none text-sm font-medium text-[#c84b5e]">
|
||||
生成失败
|
||||
</span>
|
||||
<span className="mt-1 text-xs text-[#b77480]">请重新上传或再试一次</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full w-full select-none flex-col items-center justify-center bg-slate-50 dark:bg-zinc-900">
|
||||
<ImagePlus className="mb-2 h-8 w-8 text-muted-foreground" />
|
||||
<span className="mb-3 select-none text-muted-foreground text-sm">
|
||||
点击上传图片
|
||||
<div className="flex h-full w-full select-none flex-col items-center justify-center bg-[#f7f9fc] px-6 text-center dark:bg-zinc-900">
|
||||
<span className="mb-3 flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#dde5f4] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.22)]">
|
||||
<ImagePlus className="h-5 w-5" />
|
||||
</span>
|
||||
<span className="select-none text-sm font-medium text-[#3f4b63]">
|
||||
上传图片
|
||||
</span>
|
||||
<span className="mb-4 mt-1 select-none text-xs text-[#7a8395]">
|
||||
添加参考图、角色图或关键帧素材
|
||||
</span>
|
||||
{/* 隐藏的文件输入 */}
|
||||
<input
|
||||
@ -3153,7 +3164,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
/>
|
||||
{/* 选择文件按钮 */}
|
||||
<button
|
||||
className="cursor-pointer rounded-md bg-primary px-4 py-2 text-primary-foreground text-sm transition-colors hover:bg-primary/90"
|
||||
className="cursor-pointer rounded-[14px] border border-[#d7dff1] bg-white px-4 py-2 text-sm font-medium text-[#3954db] shadow-[0_0_0_1px_rgba(219,226,238,0.24)] transition-colors hover:bg-[#f4f7ff]"
|
||||
onClick={handleSelectFile}
|
||||
type="button"
|
||||
>
|
||||
@ -3164,10 +3175,12 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
|
||||
{/* 处理中遮罩 */}
|
||||
{isProcessing && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-slate-950">
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white">
|
||||
<div className="flex flex-col items-center">
|
||||
<Loader2 className="mb-2 h-8 w-8 animate-spin text-white" />
|
||||
<span className="text-sm text-white">处理中...</span>
|
||||
<span className="mb-3 flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#dde5f4] bg-[#f6f8ff] text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.22)]">
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
</span>
|
||||
<span className="text-sm font-medium text-[#3f4b63]">处理中...</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -3179,7 +3192,11 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
<div className="absolute top-2 right-2 flex items-center gap-1">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="h-7 w-7" size="icon" variant="secondary">
|
||||
<Button
|
||||
className="h-8 w-8 rounded-full border border-[#dbe2ee] bg-white text-[#5d687b] shadow-[0_0_0_1px_rgba(219,226,238,0.3)] hover:bg-[#f7f9ff] hover:text-[#16181d]"
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
>
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@ -3254,14 +3271,14 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
|
||||
{/* 提示词显示 */}
|
||||
{data.prompt && isHovered && !isProcessing && (
|
||||
<div className="absolute right-0 bottom-0 left-0 border-slate-800 border-t bg-slate-950 p-3">
|
||||
<p className="line-clamp-2 text-white text-xs">{data.prompt}</p>
|
||||
<div className="absolute right-3 bottom-3 left-3 rounded-[16px] border border-[#dbe2ee] bg-white px-3 py-2 shadow-[0_0_0_1px_rgba(219,226,238,0.26)]">
|
||||
<p className="line-clamp-2 text-xs leading-5 text-[#556072]">{data.prompt}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.note && !isSelected && (
|
||||
<div
|
||||
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"
|
||||
className="pointer-events-none absolute top-2 left-2 inline-flex items-center gap-1 rounded-full border border-[#f5e0a7] bg-white px-2 py-1 text-[11px] text-[#c48917] shadow-[0_0_0_1px_rgba(245,224,167,0.22)]"
|
||||
title="该图片含有备注"
|
||||
>
|
||||
<StickyNote className="h-3 w-3" />
|
||||
@ -3271,7 +3288,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
{shortAgentExecutionSnapshotId ? (
|
||||
<button
|
||||
className={cn(
|
||||
"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",
|
||||
"absolute left-2 inline-flex items-center gap-1 rounded-full border border-[#dbe2ee] bg-white px-2.5 py-1 text-[11px] text-[#556072] shadow-[0_0_0_1px_rgba(219,226,238,0.3)] transition-colors hover:bg-[#f7f9ff] hover:text-[#16181d]",
|
||||
data.note && !isSelected ? "top-10" : "top-2"
|
||||
)}
|
||||
onClick={() => setSnapshotDialogOpen(true)}
|
||||
|
||||
@ -138,8 +138,10 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative h-full w-full overflow-hidden rounded-lg border bg-white shadow-sm dark:bg-zinc-950",
|
||||
isSelected ? "border-primary" : "border-slate-200 dark:border-zinc-800"
|
||||
"relative h-full w-full overflow-hidden rounded-[24px] border border-[#dbe2ee] bg-white shadow-[0_0_0_1px_rgba(219,226,238,0.34),0_16px_34px_rgba(22,24,29,0.08)] dark:border-zinc-800 dark:bg-zinc-950",
|
||||
isSelected
|
||||
? "border-[#5b76fe] ring-2 ring-[#5b76fe]/14 shadow-[0_0_0_1px_rgba(91,118,254,0.14),0_20px_38px_rgba(91,118,254,0.12)]"
|
||||
: "hover:border-[#cfd9ea]"
|
||||
)}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
@ -164,7 +166,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
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"
|
||||
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 bg-white text-[#16181d] shadow-[0_12px_26px_rgba(0,0,0,0.28)] hover:bg-white"
|
||||
onClick={togglePlay}
|
||||
>
|
||||
{isPlaying ? (
|
||||
@ -175,7 +177,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
||||
</Button>
|
||||
|
||||
{/* 底部控制栏 */}
|
||||
<div className="m-3 space-y-2 rounded-2xl border border-slate-800 bg-slate-950 p-3">
|
||||
<div className="m-3 space-y-2 rounded-[20px] border border-[#dbe2ee] bg-white p-3 shadow-[0_0_0_1px_rgba(219,226,238,0.26)]">
|
||||
{/* 进度条 */}
|
||||
<Slider
|
||||
value={[progress]}
|
||||
@ -191,7 +193,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 text-white hover:bg-slate-800"
|
||||
className="h-7 w-7 text-[#3f4b63] hover:bg-[#f4f7ff]"
|
||||
onClick={togglePlay}
|
||||
>
|
||||
{isPlaying ? (
|
||||
@ -203,7 +205,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 text-white hover:bg-slate-800"
|
||||
className="h-7 w-7 text-[#3f4b63] hover:bg-[#f4f7ff]"
|
||||
onClick={toggleMute}
|
||||
>
|
||||
{isMuted ? (
|
||||
@ -219,22 +221,29 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
||||
)}
|
||||
</div>
|
||||
) : data.status === 'generating' ? (
|
||||
<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" />
|
||||
<span className="text-sm text-muted-foreground select-none">生成中...</span>
|
||||
<div className="flex h-full w-full flex-col items-center justify-center bg-[#f7f9fc] px-6 text-center select-none dark:bg-zinc-900">
|
||||
<span className="mb-3 flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#dde5f4] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.22)]">
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
</span>
|
||||
<span className="select-none text-sm font-medium text-[#3f4b63]">生成中...</span>
|
||||
{data.progress && (
|
||||
<span className="text-xs text-muted-foreground mt-1 select-none">{data.progress}%</span>
|
||||
<span className="mt-1 text-xs text-[#7a8395] select-none">{data.progress}%</span>
|
||||
)}
|
||||
</div>
|
||||
) : data.status === 'failed' ? (
|
||||
<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" />
|
||||
<span className="text-sm text-destructive select-none">生成失败</span>
|
||||
<div className="flex h-full w-full flex-col items-center justify-center bg-[#fff7f8] px-6 text-center select-none dark:bg-red-950/40">
|
||||
<span className="mb-3 flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#f2d7dd] bg-white text-[#d24f63] shadow-[0_0_0_1px_rgba(242,215,221,0.22)]">
|
||||
<AlertCircle className="h-5 w-5" />
|
||||
</span>
|
||||
<span className="text-sm font-medium text-[#c84b5e] select-none">生成失败</span>
|
||||
</div>
|
||||
) : (
|
||||
<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" />
|
||||
<span className="text-sm text-muted-foreground select-none">点击上传视频</span>
|
||||
<label className="flex h-full w-full cursor-pointer flex-col items-center justify-center bg-[#f7f9fc] px-6 text-center transition-colors select-none hover:bg-[#f2f5fb] dark:bg-zinc-900 dark:hover:bg-zinc-800">
|
||||
<span className="mb-3 flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#dde5f4] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.22)]">
|
||||
<Video className="h-5 w-5" />
|
||||
</span>
|
||||
<span className="text-sm font-medium text-[#3f4b63] select-none">上传视频</span>
|
||||
<span className="mt-1 text-xs text-[#7a8395] select-none">添加视频素材、参考视频或结果片段</span>
|
||||
<input
|
||||
type="file"
|
||||
accept="video/*"
|
||||
@ -249,7 +258,11 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
||||
<div className="absolute top-2 right-2 flex items-center gap-1">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="secondary" size="icon" className="h-7 w-7">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full border border-[#dbe2ee] bg-white text-[#5d687b] shadow-[0_0_0_1px_rgba(219,226,238,0.3)] hover:bg-[#f7f9ff] hover:text-[#16181d]"
|
||||
>
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@ -275,7 +288,7 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSnapshotDialogOpen(true)}
|
||||
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"
|
||||
className="absolute top-2 left-2 inline-flex items-center gap-1 rounded-full border border-[#dbe2ee] bg-white px-2.5 py-1 text-[11px] text-[#556072] shadow-[0_0_0_1px_rgba(219,226,238,0.3)] transition-colors hover:bg-[#f7f9ff] hover:text-[#16181d]"
|
||||
title={`执行快照:${agentExecutionSnapshotId}`}
|
||||
>
|
||||
快照 {shortAgentExecutionSnapshotId}
|
||||
@ -289,8 +302,8 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
||||
|
||||
{/* 提示词显示 */}
|
||||
{data.prompt && isHovered && (
|
||||
<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>
|
||||
<div className="absolute bottom-3 left-3 right-3 rounded-[16px] border border-[#dbe2ee] bg-white px-3 py-2 shadow-[0_0_0_1px_rgba(219,226,238,0.26)]">
|
||||
<p className="line-clamp-2 text-xs leading-5 text-[#556072]">{data.prompt}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -72,6 +72,44 @@ const GROUP_META = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
const GROUP_STYLE_MAP = {
|
||||
input: {
|
||||
surface: "#f4f7ff",
|
||||
border: "#dfe6ff",
|
||||
icon: "#e5ebff",
|
||||
},
|
||||
analysis: {
|
||||
surface: "#eefbf6",
|
||||
border: "#d6f1e6",
|
||||
icon: "#def6eb",
|
||||
},
|
||||
storyboard: {
|
||||
surface: "#fff7e1",
|
||||
border: "#f7e6b3",
|
||||
icon: "#ffefbf",
|
||||
},
|
||||
generation: {
|
||||
surface: "#fff1f4",
|
||||
border: "#f5d5dd",
|
||||
icon: "#ffe2e9",
|
||||
},
|
||||
character: {
|
||||
surface: "#f4efff",
|
||||
border: "#e4dbff",
|
||||
icon: "#ece5ff",
|
||||
},
|
||||
tool: {
|
||||
surface: "#f5f7fb",
|
||||
border: "#e4e8f0",
|
||||
icon: "#edf1f7",
|
||||
},
|
||||
output: {
|
||||
surface: "#eef6ff",
|
||||
border: "#d8e8ff",
|
||||
icon: "#deecff",
|
||||
},
|
||||
} as const;
|
||||
|
||||
const STATUS_LABELS = {
|
||||
pending: "待配置",
|
||||
generating: "处理中",
|
||||
@ -101,6 +139,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
const resolvedGroup = (data.group || definition?.group || "tool") as keyof typeof GROUP_META;
|
||||
const groupMeta = GROUP_META[resolvedGroup] || GROUP_META.tool;
|
||||
const GroupIcon = groupMeta.icon;
|
||||
const groupStyle = GROUP_STYLE_MAP[resolvedGroup] || GROUP_STYLE_MAP.tool;
|
||||
const accentColor = data.accentColor || definition?.accentColor || "#e2e8f0";
|
||||
const accentBarColor = toAlphaColor(accentColor, 0.92) || accentColor;
|
||||
const title = data.title || definition?.label || "工作流节点";
|
||||
@ -208,9 +247,9 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative h-full w-full overflow-hidden rounded-[22px] border border-slate-200 bg-white shadow-[0_18px_38px_-26px_rgba(15,23,42,0.18)] transition-all dark:border-zinc-800 dark:bg-zinc-950",
|
||||
"relative h-full w-full overflow-hidden rounded-[24px] border border-[#e0e2e8] bg-white shadow-[0_0_0_1px_rgba(224,226,232,0.32),0_16px_36px_rgba(28,28,30,0.08)] transition-all dark:border-zinc-800 dark:bg-zinc-950",
|
||||
isSelected &&
|
||||
"border-primary/60 ring-2 ring-primary/15 shadow-[0_20px_42px_-24px_rgba(59,130,246,0.24)]"
|
||||
"border-[#5b76fe]/60 ring-2 ring-[#5b76fe]/18 shadow-[0_0_0_1px_rgba(91,118,254,0.18),0_20px_40px_rgba(91,118,254,0.14)]"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
@ -219,19 +258,26 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
/>
|
||||
|
||||
<div className="flex h-full flex-col gap-3 p-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div
|
||||
className="rounded-[20px] border px-3 py-3"
|
||||
style={{
|
||||
backgroundColor: groupStyle.surface,
|
||||
borderColor: groupStyle.border,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<span
|
||||
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-slate-200 bg-slate-50 shadow-sm dark:border-zinc-800 dark:bg-zinc-900"
|
||||
style={{ color: accentColor }}
|
||||
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-[18px] border border-white/70 shadow-sm"
|
||||
style={{ color: accentColor, backgroundColor: groupStyle.icon }}
|
||||
>
|
||||
<GroupIcon className="h-4 w-4" />
|
||||
<GroupIcon className="h-4 w-4" />
|
||||
</span>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<span className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
|
||||
<span className="text-[11px] font-medium uppercase tracking-[0.14em] text-[#555a6a]">
|
||||
{groupMeta.label}
|
||||
</span>
|
||||
<Badge variant="outline" className={cn("h-6 rounded-full px-2 text-[11px]", statusClassName)}>
|
||||
@ -240,7 +286,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
</div>
|
||||
<h3 className="truncate text-sm font-semibold text-foreground">{title}</h3>
|
||||
{primaryText ? (
|
||||
<p className="mt-1 line-clamp-2 text-xs leading-5 text-muted-foreground">
|
||||
<p className="mt-1 line-clamp-2 text-xs leading-5 text-[#555a6a]">
|
||||
{primaryText}
|
||||
</p>
|
||||
) : null}
|
||||
@ -255,7 +301,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
event.stopPropagation();
|
||||
deleteNode(node.id);
|
||||
}}
|
||||
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-muted-foreground transition-colors hover:border-destructive/30 hover:bg-red-50 hover:text-destructive dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-destructive/40 dark:hover:bg-red-950/30"
|
||||
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-white/75 bg-white/92 text-[#555a6a] transition-colors hover:border-red-200 hover:bg-red-50 hover:text-destructive dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-destructive/40 dark:hover:bg-red-950/30"
|
||||
aria-label="删除模块"
|
||||
title="删除模块"
|
||||
>
|
||||
@ -265,9 +311,10 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{previewUrl ? (
|
||||
<div className="overflow-hidden rounded-2xl border border-slate-200 bg-slate-950 dark:border-zinc-800">
|
||||
<div className="overflow-hidden rounded-[20px] border border-[#dfe3ea] bg-[#1c1c1e] shadow-[0_0_0_1px_rgba(224,226,232,0.25)] dark:border-zinc-800">
|
||||
{previewKind === "video" ? (
|
||||
<video
|
||||
src={previewUrl}
|
||||
@ -285,7 +332,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
{comparePreviewItems.slice(0, 4).map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="overflow-hidden rounded-2xl border border-slate-200 bg-white dark:border-zinc-800 dark:bg-zinc-950"
|
||||
className="overflow-hidden rounded-[18px] border border-[#dfe3ea] bg-white shadow-[0_0_0_1px_rgba(224,226,232,0.25)] dark:border-zinc-800 dark:bg-zinc-950"
|
||||
>
|
||||
{item.kind === "video" ? (
|
||||
<video
|
||||
@ -307,7 +354,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
{compactMetrics.slice(0, 6).map((metric) => (
|
||||
<span
|
||||
key={`${data.templateType}-${metric}`}
|
||||
className="rounded-full border border-slate-200 bg-slate-50 px-2.5 py-1 text-[11px] font-medium text-foreground/80 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-200"
|
||||
className="rounded-full border border-[#d8def0] bg-[#f7f8fc] px-2.5 py-1 text-[11px] font-medium text-[#555a6a] dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-200"
|
||||
>
|
||||
{metric}
|
||||
</span>
|
||||
@ -320,7 +367,7 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
{compactDetailLines.map((line) => (
|
||||
<div
|
||||
key={`${data.templateType}-${line}`}
|
||||
className="rounded-2xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs leading-5 text-foreground/80 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-200"
|
||||
className="rounded-[18px] border border-[#e3e7ef] bg-white px-3 py-2 text-xs leading-5 text-[#555a6a] shadow-[0_0_0_1px_rgba(224,226,232,0.2)] dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-200"
|
||||
>
|
||||
{line}
|
||||
</div>
|
||||
@ -333,13 +380,13 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
quickConnectOutputs.slice(0, 4).map((item) => (
|
||||
<span
|
||||
key={`${data.templateType}-${item.type}`}
|
||||
className="rounded-full border border-dashed border-slate-200 px-2.5 py-1 text-[11px] text-muted-foreground dark:border-zinc-800"
|
||||
className="rounded-full border border-dashed border-[#d8def0] px-2.5 py-1 text-[11px] text-[#6f7483] dark:border-zinc-800"
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<span className="rounded-full border border-dashed border-slate-200 px-2.5 py-1 text-[11px] text-muted-foreground dark:border-zinc-800">
|
||||
<span className="rounded-full border border-dashed border-[#d8def0] px-2.5 py-1 text-[11px] text-[#6f7483] dark:border-zinc-800">
|
||||
结束节点
|
||||
</span>
|
||||
)}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user