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:
135
frontend/src/components/workflow/specs.ts
Normal file
135
frontend/src/components/workflow/specs.ts
Normal 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: {} },
|
||||
],
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user