Enhance workflow engine and integration in backend and frontend

- Introduce a new WorkflowEngine class to manage workflow graphs, enabling dynamic node-based interactions.
- Update AssistantConfig to include a graph field for workflow definitions, allowing for flexible configuration.
- Modify pipeline execution to support workflow-driven dialogue, integrating node transitions and system prompts based on active nodes.
- Enhance frontend components to visualize active nodes and provide debugging capabilities, including highlighting the current node during interactions.
- Refactor existing components to accommodate new workflow functionalities and improve overall user experience.
This commit is contained in:
Xin Wang
2026-06-15 15:32:10 +08:00
parent c2a39257ff
commit aae0342a57
10 changed files with 361 additions and 16 deletions
@@ -10,12 +10,18 @@ import { Pencil, Trash2 } from "lucide-react";
import { useContext } from "react";
import { cn } from "@/lib/utils";
import { NodeActionContext, NodeSpecsContext } from "./context";
import {
ActiveNodeContext,
NodeActionContext,
NodeSpecsContext,
} from "./context";
import { accentVar, type WorkflowNodeData } from "./specs";
export function GenericNode({ id, type, data, selected }: NodeProps) {
const specs = useContext(NodeSpecsContext);
const actions = useContext(NodeActionContext);
const activeNodeId = useContext(ActiveNodeContext);
const isActive = activeNodeId === id;
const spec = specs[type as string];
if (!spec) return null;
@@ -30,9 +36,11 @@ export function GenericNode({ id, type, data, selected }: NodeProps) {
data-node-id={id}
className={cn(
"group relative w-[228px] rounded-2xl border bg-card p-4 shadow-sm transition-all",
selected
? "border-primary ring-2 ring-primary/30"
: "border-hairline hover:shadow-md",
isActive
? "border-success ring-2 ring-success/60"
: selected
? "border-primary ring-2 ring-primary/30"
: "border-hairline hover:shadow-md",
)}
>
{spec.hasTarget && (
@@ -43,6 +51,13 @@ export function GenericNode({ id, type, data, selected }: NodeProps) {
/>
)}
{isActive && (
<div className="absolute -top-3 left-3 flex items-center gap-1.5 rounded-full bg-success px-2 py-0.5 text-[10px] font-medium text-on-primary shadow-sm">
<span className="h-1.5 w-1.5 animate-pulse rounded-full bg-on-primary" />
对话中
</div>
)}
{/* 悬停/选中时出现的操作按钮 */}
<div
className={cn(
@@ -50,6 +50,7 @@ import { Textarea } from "@/components/ui/textarea";
import { edgeTypes } from "./ConditionEdge";
import {
ActiveNodeContext,
EdgeActionContext,
NodeActionContext,
NodeSpecsContext,
@@ -81,6 +82,8 @@ export type WorkflowEditorProps = {
settings: WorkflowSettings;
onSettingsChange: (settings: WorkflowSettings) => void;
modelOptions: { llm: ModelOption[]; asr: ModelOption[]; tts: ModelOption[] };
/** 调试通话中当前激活的节点 id(用于高亮)。 */
activeNodeId?: string | null;
};
let nodeSeq = 0;
@@ -127,6 +130,7 @@ function Canvas({
settings,
onSettingsChange,
modelOptions,
activeNodeId,
specsByType,
}: WorkflowEditorProps & { specsByType: NodeSpecMap }) {
const initial = useMemo(() => toFlow(value ?? defaultGraph()), [value]);
@@ -252,6 +256,7 @@ function Canvas({
return (
<NodeSpecsContext.Provider value={specsByType}>
<ActiveNodeContext.Provider value={activeNodeId ?? null}>
<NodeActionContext.Provider value={nodeActions}>
<EdgeActionContext.Provider value={edgeActions}>
<div className="h-full w-full overflow-hidden rounded-2xl border border-hairline bg-canvas-soft">
@@ -446,6 +451,7 @@ function Canvas({
</div>
</EdgeActionContext.Provider>
</NodeActionContext.Provider>
</ActiveNodeContext.Provider>
</NodeSpecsContext.Provider>
);
}
@@ -21,3 +21,6 @@ const noop: ElementActions = { edit: () => {}, remove: () => {} };
export const NodeActionContext = createContext<ElementActions>(noop);
export const EdgeActionContext = createContext<ElementActions>(noop);
/** 调试通话中当前激活的节点 id(画布据此高亮),无激活为 null。 */
export const ActiveNodeContext = createContext<string | null>(null);