2026-06-15 10:12:41 +08:00
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-13 16:13:27 +08:00
|
|
|
|
* Workflow v3 edge: LLM judgement, variable expression, or deterministic default.
|
2026-06-15 10:12:41 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
|
BaseEdge,
|
|
|
|
|
|
EdgeLabelRenderer,
|
2026-07-13 16:13:27 +08:00
|
|
|
|
getBezierPath,
|
2026-06-15 10:12:41 +08:00
|
|
|
|
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 })));
|
2026-07-13 16:13:27 +08:00
|
|
|
|
const [path, labelX, labelY] = getBezierPath({
|
2026-06-15 10:12:41 +08:00
|
|
|
|
sourceX,
|
|
|
|
|
|
sourceY,
|
|
|
|
|
|
sourcePosition,
|
|
|
|
|
|
targetX,
|
|
|
|
|
|
targetY,
|
|
|
|
|
|
targetPosition,
|
2026-07-13 16:13:27 +08:00
|
|
|
|
curvature: 0.28,
|
2026-06-15 10:12:41 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-13 16:13:27 +08:00
|
|
|
|
const mode = (data?.mode as string) || "always";
|
|
|
|
|
|
const label = (
|
|
|
|
|
|
(data?.label as string) ||
|
|
|
|
|
|
(mode === "llm" ? (data?.condition as string) : "") ||
|
|
|
|
|
|
(mode === "expression" ? "变量表达式" : "默认路径")
|
|
|
|
|
|
).toString().trim();
|
2026-06-15 10:12:41 +08:00
|
|
|
|
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 };
|