Files
ai-video-fullstack/frontend/src/components/workflow/ConditionEdge.tsx
T

117 lines
3.5 KiB
TypeScript
Raw Normal View History

"use client";
/**
* 条件边。边携带 condition(自然语言条件,LLM 据此决定是否走这条路径)与
* label(日志里识别该路径的短标签)。悬停/选中时在标签旁显示「编辑 / 删除」按钮。
*/
import {
BaseEdge,
EdgeLabelRenderer,
getSmoothStepPath,
type EdgeProps,
useReactFlow,
} from "@xyflow/react";
import { Pencil, Trash2 } from "lucide-react";
import { useContext, useState } from "react";
import { cn } from "@/lib/utils";
import { EdgeActionContext } from "./context";
export function ConditionEdge({
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
data,
selected,
}: EdgeProps) {
const actions = useContext(EdgeActionContext);
const { setEdges } = useReactFlow();
const [hovered, setHovered] = useState(false);
// 点击标签:只选中这条边(露出编辑/删除按钮),不直接进入编辑。
const selectThisEdge = () =>
setEdges((eds) => eds.map((e) => ({ ...e, selected: e.id === id })));
const [path, labelX, labelY] = getSmoothStepPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
borderRadius: 8,
offset: 20,
});
const label = ((data?.label as string) || (data?.condition as string) || "")
.toString()
.trim();
const expanded = hovered || selected;
return (
<>
<BaseEdge
id={id}
path={path}
style={{
stroke: selected ? "var(--primary)" : "var(--muted-soft)",
strokeWidth: selected ? 2.5 : 1.5,
transition: "stroke 0.15s ease, stroke-width 0.15s ease",
}}
/>
<EdgeLabelRenderer>
<div
className="nodrag nopan pointer-events-auto absolute flex items-center gap-1"
style={{
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<button
type="button"
title="条件"
className={cn(
"max-w-[160px] truncate rounded-full border px-2.5 py-1 text-xs shadow-sm transition-colors",
label
? "border-hairline bg-card text-foreground hover:border-primary"
: "border-dashed border-hairline-strong bg-card/80 text-muted-soft hover:text-foreground",
selected && "border-primary ring-2 ring-primary/30",
)}
onClick={selectThisEdge}
>
{label || "+ 条件"}
</button>
{expanded && (
<button
type="button"
title="编辑条件"
className="flex h-6 w-6 items-center justify-center rounded-full border border-hairline bg-card text-muted-foreground shadow-sm transition-colors hover:text-foreground"
onClick={() => actions.edit(id)}
>
<Pencil size={12} />
</button>
)}
{expanded && (
<button
type="button"
title="删除连线"
className="flex h-6 w-6 items-center justify-center rounded-full border border-hairline bg-card text-muted-foreground shadow-sm transition-colors hover:text-destructive"
onClick={() => actions.remove(id)}
>
<Trash2 size={12} />
</button>
)}
</div>
</EdgeLabelRenderer>
</>
);
}
export const edgeTypes = { condition: ConditionEdge };