Add workflow editor and node types support in frontend and backend

- Introduce a new workflow editor component for visualizing and managing workflows, allowing users to add nodes and define connections.
- Implement backend support for node types, including validation and constraints for workflow graphs.
- Add new API endpoints for retrieving node types and their specifications.
- Enhance the AssistantPage to integrate the workflow editor, enabling users to create and edit workflows directly.
- Update frontend components to support new workflow functionalities, including condition edges and generic nodes.
- Refactor existing code to accommodate the new workflow features and improve overall structure.
This commit is contained in:
Xin Wang
2026-06-15 10:12:41 +08:00
parent 0309c154b5
commit c2a39257ff
15 changed files with 1733 additions and 54 deletions

View File

@@ -0,0 +1,135 @@
/**
* 工作流节点的前端类型与运行期规格。
*
* 节点「目录」(有哪些类型、各自的字段与约束)由后端 /api/node-types 提供,
* 通过 useNodeSpecs 拉取后用 toRuntimeSpec 转成带 React 组件(图标)的运行期规格。
* 本文件只保留:类型定义、默认图、图标/配色解析。新增节点类型改后端即可。
*/
import * as LucideIcons from "lucide-react";
import { Circle, type LucideIcon } from "lucide-react";
import type { NodeSpecDto } from "@/lib/api";
export type WorkflowNodeType = "startCall" | "agentNode" | "endCall";
export type WorkflowNodeData = {
/** 节点显示名 */
name: string;
/** 开场白(仅 startCall) */
greeting?: string;
/** 节点提示词 */
prompt?: string;
/** 允许打断(仅 agentNode) */
allowInterrupt?: boolean;
[key: string]: unknown;
};
export type FieldSpec = {
key: string;
label: string;
type: "text" | "textarea" | "switch";
required?: boolean;
};
/** 解析后的运行期节点规格(DTO + 解析出的 React 图标 + 派生句柄) */
export type RuntimeNodeSpec = {
type: string;
displayName: string;
description: string;
icon: LucideIcon;
accent: string;
addable: boolean;
/** 入边句柄(开始节点没有) */
hasTarget: boolean;
/** 出边句柄(结束节点没有) */
hasSource: boolean;
fields: FieldSpec[];
};
/** 渐变 token → CSS 变量名(图标底色用),未知配色回落到 sky */
export const ACCENT_VAR: Record<string, string> = {
mint: "--gradient-mint",
sky: "--gradient-sky",
rose: "--gradient-rose",
peach: "--gradient-peach",
lavender: "--gradient-lavender",
};
export function accentVar(accent: string): string {
return ACCENT_VAR[accent] ?? ACCENT_VAR.sky;
}
/** 按名字解析 Lucide 图标,找不到回落到 Circle(对齐 dograh resolveIcon)。 */
export function resolveIcon(name: string): LucideIcon {
const icons = LucideIcons as unknown as Record<string, LucideIcon>;
return icons[name] ?? Circle;
}
/** 后端 DTO → 运行期规格。hasTarget/hasSource 由入/出边上限派生。 */
export function toRuntimeSpec(dto: NodeSpecDto): RuntimeNodeSpec {
return {
type: dto.name,
displayName: dto.displayName,
description: dto.description,
icon: resolveIcon(dto.icon),
accent: dto.accent,
addable: dto.addable,
hasTarget: dto.constraints.maxIncoming !== 0,
hasSource: dto.constraints.maxOutgoing !== 0,
fields: dto.fields.map((f) => ({
key: f.key,
label: f.label,
type: f.type,
required: f.required,
})),
};
}
export type NodeSpecMap = Record<string, RuntimeNodeSpec>;
export type WorkflowGraph = {
nodes: Array<{
id: string;
type: WorkflowNodeType;
position: { x: number; y: number };
data: WorkflowNodeData;
}>;
edges: Array<{
id: string;
source: string;
target: string;
data?: { condition?: string; label?: string };
}>;
viewport?: { x: number; y: number; zoom: number };
};
/** 新建工作流的默认图:开始 → 智能体 → 结束 */
export function defaultGraph(): WorkflowGraph {
return {
nodes: [
{
id: "start",
type: "startCall",
position: { x: 80, y: 160 },
data: { name: "开始", greeting: "你好,我是 AI 视频助手,有什么可以帮你?" },
},
{
id: "agent-1",
type: "agentNode",
position: { x: 400, y: 160 },
data: { name: "智能体节点", prompt: "", allowInterrupt: true },
},
{
id: "end",
type: "endCall",
position: { x: 720, y: 160 },
data: { name: "结束", prompt: "感谢你的来电,再见!" },
},
],
edges: [
{ id: "e-start-agent", source: "start", target: "agent-1", data: {} },
{ id: "e-agent-end", source: "agent-1", target: "end", data: {} },
],
};
}