Refactor workflow agent and routing components for improved functionality
- Introduce WorkflowAgentStage to manage agent stage configurations and enhance interaction with the workflow engine. - Implement WorkflowEdgeEvaluator for priority-aware edge evaluation, improving routing decisions based on conditions and user turns. - Update WorkflowBrain to handle user turns and routing more effectively, ensuring agents cannot have only one default path. - Enhance CallEndCoordinator to track speech events and manage call termination based on queued speech. - Add new models and output handling for workflow interactions, improving clarity and maintainability. - Update tests to validate the new routing logic and agent behavior under various scenarios.
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
useNodesState,
|
||||
useReactFlow,
|
||||
} from "@xyflow/react";
|
||||
import { Braces, Plus, Settings2, X } from "lucide-react";
|
||||
import { Braces, Plus, Settings2 } from "lucide-react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@@ -26,13 +26,6 @@ import {
|
||||
} from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -40,6 +33,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
import { AddNodeDialog } from "./AddNodeDialog";
|
||||
import { edgeTypes } from "./ConditionEdge";
|
||||
import { hasDefaultPath, newEdgeData } from "./edge-rules";
|
||||
import {
|
||||
@@ -52,8 +46,8 @@ import { nodeTypes } from "./GenericNode";
|
||||
import { EdgeSettingsPanel } from "./panels/EdgeSettingsPanel";
|
||||
import { GlobalSettingsPanel } from "./panels/GlobalSettingsPanel";
|
||||
import { NodeSettingsPanel } from "./panels/NodeSettingsPanel";
|
||||
import { WorkflowSidePanel } from "./WorkflowSidePanel";
|
||||
import {
|
||||
accentVar,
|
||||
defaultGraph,
|
||||
type NodeSpecMap,
|
||||
type RuntimeNodeSpec,
|
||||
@@ -195,7 +189,9 @@ export function WorkflowCanvas({
|
||||
settings.tts,
|
||||
settings.toolIds,
|
||||
settings.knowledgeBaseId,
|
||||
settings.knowledgeRetrievalConfig,
|
||||
settings.knowledgeRetrievalConfig.mode,
|
||||
settings.knowledgeRetrievalConfig.topN,
|
||||
settings.knowledgeRetrievalConfig.scoreThreshold,
|
||||
settings.allowInterrupt,
|
||||
settings.turnConfig,
|
||||
]);
|
||||
@@ -203,6 +199,9 @@ export function WorkflowCanvas({
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
if (!connection.source || !connection.target) return;
|
||||
const sourceType = nodes.find(
|
||||
(node) => node.id === connection.source,
|
||||
)?.type;
|
||||
setEdges((eds) =>
|
||||
addEdge(
|
||||
{
|
||||
@@ -210,13 +209,13 @@ export function WorkflowCanvas({
|
||||
id: `e-${connection.source}-${connection.target}-${Date.now()}`,
|
||||
type: "condition",
|
||||
animated: true,
|
||||
data: newEdgeData(eds, connection.source),
|
||||
data: newEdgeData(eds, connection.source, sourceType),
|
||||
},
|
||||
eds,
|
||||
),
|
||||
);
|
||||
},
|
||||
[setEdges],
|
||||
[nodes, setEdges],
|
||||
);
|
||||
|
||||
// 连线约束:不能连入开始节点(无入边句柄),不能自连。
|
||||
@@ -277,7 +276,7 @@ export function WorkflowCanvas({
|
||||
target: id,
|
||||
type: "condition",
|
||||
animated: true,
|
||||
data: newEdgeData(currentEdges, source.id),
|
||||
data: newEdgeData(currentEdges, source.id, source.type),
|
||||
},
|
||||
currentEdges,
|
||||
);
|
||||
@@ -577,9 +576,11 @@ export function WorkflowCanvas({
|
||||
</ReactFlow>
|
||||
</section>
|
||||
|
||||
{/* 添加节点弹窗 */}
|
||||
<Dialog
|
||||
<AddNodeDialog
|
||||
open={addOpen}
|
||||
connectingFromSource={Boolean(addSourceId)}
|
||||
specs={addableSpecs}
|
||||
canAdd={canAddSpec}
|
||||
onOpenChange={(open) => {
|
||||
setAddOpen(open);
|
||||
if (!open) {
|
||||
@@ -587,131 +588,43 @@ export function WorkflowCanvas({
|
||||
setAddPosition(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent className="gap-0 overflow-hidden border border-hairline bg-card p-0 shadow-2xl sm:max-w-[500px]">
|
||||
<DialogHeader className="relative overflow-hidden border-b border-hairline px-6 py-6 pr-16">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute -right-14 -top-16 h-40 w-40 rounded-full opacity-40 blur-3xl"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(circle, var(--gradient-sky), transparent 68%)",
|
||||
}}
|
||||
/>
|
||||
<div className="relative flex items-start gap-4">
|
||||
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-surface-strong text-foreground">
|
||||
<Plus size={18} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="caption-label text-muted-soft">
|
||||
节点目录
|
||||
</div>
|
||||
<DialogTitle className="font-display display-sm mt-1 text-ink">
|
||||
添加节点
|
||||
</DialogTitle>
|
||||
<DialogDescription className="mt-2 leading-6">
|
||||
{addSourceId
|
||||
? "选择节点类型,将在当前节点下方创建并自动连接。"
|
||||
: "选择节点类型并添加到画布中央,随后可编辑内容并建立连线。"}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
<div className="flex max-h-[440px] flex-col gap-3 overflow-y-auto bg-canvas-soft/70 p-4">
|
||||
{addableSpecs.length === 0 ? (
|
||||
<p className="rounded-2xl border border-dashed border-hairline-strong bg-card px-4 py-8 text-center text-sm text-muted-soft">
|
||||
暂无可添加的节点类型。
|
||||
</p>
|
||||
) : null}
|
||||
{addableSpecs.map((spec) => {
|
||||
const Icon = spec.icon;
|
||||
const canAdd = canAddSpec(spec);
|
||||
return (
|
||||
<button
|
||||
key={spec.type}
|
||||
type="button"
|
||||
disabled={!canAdd}
|
||||
className="group relative flex items-start gap-4 overflow-hidden rounded-2xl border border-hairline bg-card p-4 text-left shadow-sm transition-[border-color,box-shadow,transform] hover:-translate-y-0.5 hover:border-hairline-strong hover:shadow-md disabled:cursor-not-allowed disabled:opacity-55 disabled:hover:translate-y-0 disabled:hover:border-hairline disabled:hover:shadow-sm"
|
||||
onClick={() => addNode(spec)}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="absolute left-5 right-5 top-0 h-px"
|
||||
style={{
|
||||
background: `linear-gradient(90deg, transparent, var(${accentVar(spec.accent)}), transparent)`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full text-foreground transition-transform group-hover:scale-105"
|
||||
style={{
|
||||
background: `color-mix(in srgb, var(${accentVar(spec.accent)}) 28%, var(--surface-strong))`,
|
||||
}}
|
||||
>
|
||||
<Icon size={17} />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
|
||||
<span>{spec.displayName}</span>
|
||||
{!canAdd && (
|
||||
<span className="rounded-full bg-surface-strong px-2 py-0.5 text-[10px] font-normal text-muted-foreground">
|
||||
已添加
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1 text-xs leading-5 text-muted-foreground">
|
||||
{spec.description}
|
||||
</div>
|
||||
</div>
|
||||
<Plus
|
||||
size={15}
|
||||
className="mt-1 shrink-0 text-muted-soft transition-colors group-hover:text-foreground"
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
onSelect={addNode}
|
||||
/>
|
||||
|
||||
{(debugOpen ||
|
||||
settingsOpen ||
|
||||
(editingNode && editingSpec) ||
|
||||
editingEdge) && (
|
||||
<aside className="absolute inset-y-0 right-0 z-40 flex w-1/2 flex-col overflow-hidden rounded-r-2xl border-l border-hairline bg-card shadow-2xl">
|
||||
<WorkflowSidePanel
|
||||
title={
|
||||
debugOpen
|
||||
? undefined
|
||||
: settingsOpen
|
||||
? "工作流设置"
|
||||
: editingEdge
|
||||
? "编辑连接条件"
|
||||
: `编辑${editingSpec?.displayName ?? "节点"}`
|
||||
}
|
||||
closeLabel={
|
||||
settingsOpen
|
||||
? "关闭工作流设置"
|
||||
: editingEdge
|
||||
? "关闭边编辑"
|
||||
: "关闭节点编辑"
|
||||
}
|
||||
onClose={
|
||||
debugOpen
|
||||
? undefined
|
||||
: () => {
|
||||
if (settingsOpen) setSettingsOpen(false);
|
||||
else if (editingEdge) onEditingEdgeIdChange(null);
|
||||
else onEditingNodeIdChange(null);
|
||||
}
|
||||
}
|
||||
>
|
||||
{debugOpen ? (
|
||||
debugPanel
|
||||
) : settingsOpen ||
|
||||
(editingNode && editingSpec) ||
|
||||
editingEdge ? (
|
||||
<>
|
||||
<div className="flex min-h-14 shrink-0 items-center gap-3 border-b border-hairline px-4 py-3">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={
|
||||
settingsOpen
|
||||
? "关闭工作流设置"
|
||||
: editingEdge
|
||||
? "关闭边编辑"
|
||||
: "关闭节点编辑"
|
||||
}
|
||||
title="关闭"
|
||||
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-hairline-strong bg-card text-muted-foreground shadow-sm transition-colors hover:text-foreground"
|
||||
onClick={() => {
|
||||
if (settingsOpen) setSettingsOpen(false);
|
||||
else if (editingEdge) onEditingEdgeIdChange(null);
|
||||
else onEditingNodeIdChange(null);
|
||||
}}
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
<h2 className="min-w-0 truncate text-sm font-medium text-foreground">
|
||||
{settingsOpen
|
||||
? "工作流设置"
|
||||
: editingEdge
|
||||
? "编辑连接条件"
|
||||
: `编辑${editingSpec?.displayName ?? "节点"}`}
|
||||
</h2>
|
||||
</div>
|
||||
) : (
|
||||
<div className="scrollbar-subtle min-h-0 flex-1 overflow-y-auto bg-canvas-soft px-4 pb-5 pt-4">
|
||||
{settingsOpen ? (
|
||||
<GlobalSettingsPanel
|
||||
@@ -741,6 +654,16 @@ export function WorkflowCanvas({
|
||||
<EdgeSettingsPanel
|
||||
key={editingEdge.id}
|
||||
edge={editingEdge}
|
||||
sourceType={
|
||||
nodes.find(
|
||||
(node) => node.id === editingEdge.source,
|
||||
)?.type as string | undefined
|
||||
}
|
||||
isOnlyOutgoing={
|
||||
edges.filter(
|
||||
(edge) => edge.source === editingEdge.source,
|
||||
).length === 1
|
||||
}
|
||||
hasOtherDefaultPath={hasDefaultPath(
|
||||
edges,
|
||||
editingEdge.source,
|
||||
@@ -752,9 +675,8 @@ export function WorkflowCanvas({
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</aside>
|
||||
)}
|
||||
</WorkflowSidePanel>
|
||||
)}
|
||||
</div>
|
||||
</EdgeActionContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user