Add workflow support and enhance runtime configuration in models and services
- Introduce RuntimeModelResource and RuntimeKnowledgeBase classes to manage workflow resources. - Update AssistantConfig to include workflow_model_resources and workflow_knowledge_bases for better integration. - Refactor validation and processing logic in routes and services to accommodate workflow types. - Implement dynamic variable support for workflow assistants and enhance graph normalization. - Add ToolExecutor for reusable tool execution across different assistant types. - Update various services to ensure compatibility with new workflow features and improve error handling.
This commit is contained in:
@@ -53,13 +53,6 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from "@/components/ui/sheet";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -478,10 +471,15 @@ export function AssistantPage(props: AssistantPageProps) {
|
||||
defaultGraph(),
|
||||
);
|
||||
const [workflowSettings, setWorkflowSettings] = useState<WorkflowSettings>({
|
||||
globalPrompt: defaultGraph().settings.globalPrompt,
|
||||
allowInterrupt: true,
|
||||
turnConfig: defaultTurnConfig(),
|
||||
});
|
||||
const [debugOpen, setDebugOpen] = useState(false);
|
||||
const [workflowDynamicVariableDefinitions, setWorkflowDynamicVariableDefinitions] =
|
||||
useState<Record<string, DynamicVariableDefinition>>({});
|
||||
const [workflowDebugOpen, setWorkflowDebugOpen] = useState(false);
|
||||
const [workflowEditingNodeId, setWorkflowEditingNodeId] = useState<string | null>(null);
|
||||
const [workflowEditingEdgeId, setWorkflowEditingEdgeId] = useState<string | null>(null);
|
||||
const [activeNodeId, setActiveNodeId] = useState<string | null>(null);
|
||||
const [dynamicVariablesOpen, setDynamicVariablesOpen] = useState(false);
|
||||
|
||||
@@ -708,6 +706,7 @@ export function AssistantPage(props: AssistantPageProps) {
|
||||
name: workflowName,
|
||||
graph: workflowGraph,
|
||||
settings: workflowSettings,
|
||||
dynamicVariableDefinitions: workflowDynamicVariableDefinitions,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -849,19 +848,24 @@ export function AssistantPage(props: AssistantPageProps) {
|
||||
: defaultGraph();
|
||||
const wfSettings: WorkflowSettings = {
|
||||
llm: assistant.modelResourceIds.LLM,
|
||||
asr: assistant.modelResourceIds.ASR,
|
||||
tts: assistant.modelResourceIds.TTS,
|
||||
asr: graph.settings?.defaultAsrResourceId || assistant.modelResourceIds.ASR,
|
||||
tts: graph.settings?.defaultTtsResourceId || assistant.modelResourceIds.TTS,
|
||||
globalPrompt: graph.settings?.globalPrompt ?? "",
|
||||
allowInterrupt: assistant.enableInterrupt,
|
||||
turnConfig: assistant.turnConfig,
|
||||
};
|
||||
setWorkflowName(assistant.name);
|
||||
setWorkflowGraph(graph);
|
||||
setWorkflowSettings(wfSettings);
|
||||
setWorkflowDynamicVariableDefinitions(
|
||||
assistant.dynamicVariableDefinitions ?? {},
|
||||
);
|
||||
setSavedSnapshot(
|
||||
JSON.stringify({
|
||||
name: assistant.name,
|
||||
graph,
|
||||
settings: wfSettings,
|
||||
dynamicVariableDefinitions: assistant.dynamicVariableDefinitions ?? {},
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -913,6 +917,7 @@ export function AssistantPage(props: AssistantPageProps) {
|
||||
...(workflowSettings.tts ? { TTS: workflowSettings.tts } : {}),
|
||||
},
|
||||
graph: workflowGraph as unknown as Record<string, unknown>,
|
||||
dynamicVariableDefinitions: workflowDynamicVariableDefinitions,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -932,6 +937,7 @@ export function AssistantPage(props: AssistantPageProps) {
|
||||
name: workflowName,
|
||||
graph: workflowGraph,
|
||||
settings: workflowSettings,
|
||||
dynamicVariableDefinitions: workflowDynamicVariableDefinitions,
|
||||
})
|
||||
: null;
|
||||
const dirty =
|
||||
@@ -1377,7 +1383,11 @@ export function AssistantPage(props: AssistantPageProps) {
|
||||
|
||||
<div className="flex shrink-0 gap-2">
|
||||
{saveError && (
|
||||
<span className="self-center text-xs text-destructive">
|
||||
<span
|
||||
role="alert"
|
||||
title={saveError}
|
||||
className="line-clamp-2 max-w-[min(42vw,560px)] self-center text-right text-sm leading-5 text-destructive"
|
||||
>
|
||||
{saveError}
|
||||
</span>
|
||||
)}
|
||||
@@ -1385,7 +1395,11 @@ export function AssistantPage(props: AssistantPageProps) {
|
||||
variant="outline"
|
||||
className="gap-2 border-hairline-strong text-foreground hover:bg-surface-strong"
|
||||
disabled={!editingId}
|
||||
onClick={() => setDebugOpen(true)}
|
||||
onClick={() => {
|
||||
setWorkflowEditingNodeId(null);
|
||||
setWorkflowEditingEdgeId(null);
|
||||
setWorkflowDebugOpen(true);
|
||||
}}
|
||||
>
|
||||
<Bug size={16} />
|
||||
调试
|
||||
@@ -1411,43 +1425,49 @@ export function AssistantPage(props: AssistantPageProps) {
|
||||
onChange={setWorkflowGraph}
|
||||
settings={workflowSettings}
|
||||
onSettingsChange={setWorkflowSettings}
|
||||
onOpenDynamicVariables={() => setDynamicVariablesOpen(true)}
|
||||
editingNodeId={workflowEditingNodeId}
|
||||
onEditingNodeIdChange={setWorkflowEditingNodeId}
|
||||
editingEdgeId={workflowEditingEdgeId}
|
||||
onEditingEdgeIdChange={setWorkflowEditingEdgeId}
|
||||
debugOpen={workflowDebugOpen}
|
||||
onDebugOpenChange={(open) => {
|
||||
setWorkflowDebugOpen(open);
|
||||
if (!open) setActiveNodeId(null);
|
||||
}}
|
||||
debugPanel={
|
||||
<DebugDrawer
|
||||
overlay
|
||||
assistantId={editingId}
|
||||
onClose={() => {
|
||||
setWorkflowDebugOpen(false);
|
||||
setActiveNodeId(null);
|
||||
}}
|
||||
hasUnsavedChanges={dirty}
|
||||
onNodeActive={setActiveNodeId}
|
||||
dynamicVariablesEnabled
|
||||
dynamicVariableDefinitions={workflowDynamicVariableDefinitions}
|
||||
/>
|
||||
}
|
||||
activeNodeId={activeNodeId}
|
||||
modelOptions={{
|
||||
llm: credOptions("LLM"),
|
||||
asr: credOptions("ASR"),
|
||||
tts: credOptions("TTS"),
|
||||
}}
|
||||
toolOptions={tools
|
||||
.filter((tool) => tool.status === "active")
|
||||
.map((tool) => ({ value: tool.id, label: tool.name }))}
|
||||
knowledgeOptions={kbOptions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Sheet
|
||||
open={debugOpen}
|
||||
onOpenChange={(open) => {
|
||||
setDebugOpen(open);
|
||||
if (!open) setActiveNodeId(null);
|
||||
}}
|
||||
modal={false}
|
||||
>
|
||||
<SheetContent
|
||||
side="right"
|
||||
showOverlay={false}
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
className="w-[440px] gap-0 border-l border-hairline bg-card p-0 sm:max-w-[440px]"
|
||||
>
|
||||
<SheetHeader className="sr-only">
|
||||
<SheetTitle>语音调试</SheetTitle>
|
||||
<SheetDescription>
|
||||
与当前助手进行语音对话调试,画布会高亮正在激活的节点。
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<DebugDrawer
|
||||
assistantId={editingId}
|
||||
asSheet
|
||||
hasUnsavedChanges={dirty}
|
||||
onNodeActive={setActiveNodeId}
|
||||
/>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<DynamicVariablesDialog
|
||||
open={dynamicVariablesOpen}
|
||||
onOpenChange={setDynamicVariablesOpen}
|
||||
definitions={workflowDynamicVariableDefinitions}
|
||||
onChange={setWorkflowDynamicVariableDefinitions}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2132,7 +2152,8 @@ function SegmentedIconButton({
|
||||
|
||||
function DebugDrawer({
|
||||
assistantId,
|
||||
asSheet = false,
|
||||
overlay = false,
|
||||
onClose,
|
||||
hasUnsavedChanges = false,
|
||||
onNodeActive,
|
||||
vision = false,
|
||||
@@ -2140,7 +2161,8 @@ function DebugDrawer({
|
||||
dynamicVariableDefinitions = {},
|
||||
}: {
|
||||
assistantId: string | null;
|
||||
asSheet?: boolean;
|
||||
overlay?: boolean;
|
||||
onClose?: () => void;
|
||||
hasUnsavedChanges?: boolean;
|
||||
onNodeActive?: (nodeId: string | null) => void;
|
||||
vision?: boolean;
|
||||
@@ -2179,14 +2201,25 @@ function DebugDrawer({
|
||||
[camera, preview],
|
||||
);
|
||||
|
||||
const containerClass = asSheet
|
||||
? "flex h-full min-w-0 flex-1 flex-col overflow-hidden"
|
||||
: "hidden min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-hairline bg-card shadow-sm lg:flex";
|
||||
|
||||
return (
|
||||
<aside className={containerClass}>
|
||||
<div className="flex shrink-0 items-center justify-between gap-3 border-b border-hairline px-5 py-3">
|
||||
<aside
|
||||
className={overlay
|
||||
? "flex h-full min-w-0 flex-1 flex-col overflow-hidden bg-card"
|
||||
: "hidden min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-hairline bg-card shadow-sm lg:flex"}
|
||||
>
|
||||
<div className={`flex min-h-14 shrink-0 items-center justify-between gap-3 border-b border-hairline py-3 ${overlay ? "px-4" : "px-5"}`}>
|
||||
<div className="flex min-w-0 items-center gap-2.5">
|
||||
{overlay && onClose && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="关闭调试预览"
|
||||
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={onClose}
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
)}
|
||||
<div className="shrink-0 text-sm font-medium text-foreground">
|
||||
调试与预览
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* 条件边。边携带 condition(自然语言条件,LLM 据此决定是否走这条路径)与
|
||||
* label(日志里识别该路径的短标签)。悬停/选中时在标签旁显示「编辑 / 删除」按钮。
|
||||
* Workflow v3 edge: LLM judgement, variable expression, or deterministic default.
|
||||
*/
|
||||
|
||||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
getSmoothStepPath,
|
||||
getBezierPath,
|
||||
type EdgeProps,
|
||||
useReactFlow,
|
||||
} from "@xyflow/react";
|
||||
@@ -36,20 +35,22 @@ export function ConditionEdge({
|
||||
// 点击标签:只选中这条边(露出编辑/删除按钮),不直接进入编辑。
|
||||
const selectThisEdge = () =>
|
||||
setEdges((eds) => eds.map((e) => ({ ...e, selected: e.id === id })));
|
||||
const [path, labelX, labelY] = getSmoothStepPath({
|
||||
const [path, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
borderRadius: 8,
|
||||
offset: 20,
|
||||
curvature: 0.28,
|
||||
});
|
||||
|
||||
const label = ((data?.label as string) || (data?.condition as string) || "")
|
||||
.toString()
|
||||
.trim();
|
||||
const mode = (data?.mode as string) || "always";
|
||||
const label = (
|
||||
(data?.label as string) ||
|
||||
(mode === "llm" ? (data?.condition as string) : "") ||
|
||||
(mode === "expression" ? "变量表达式" : "默认路径")
|
||||
).toString().trim();
|
||||
const expanded = hovered || selected;
|
||||
|
||||
return (
|
||||
|
||||
@@ -27,9 +27,20 @@ export function GenericNode({ id, type, data, selected }: NodeProps) {
|
||||
|
||||
const nodeData = data as WorkflowNodeData;
|
||||
const Icon = spec.icon;
|
||||
const preview = (nodeData.greeting || nodeData.prompt || "")
|
||||
const preview = (nodeData.greeting || nodeData.prompt || nodeData.message || "")
|
||||
.toString()
|
||||
.trim();
|
||||
const meta = type === "agent"
|
||||
? [
|
||||
nodeData.contextPolicy === "fresh" ? "独立上下文" : "继承上下文",
|
||||
`${nodeData.toolIds?.length ?? 0} 工具`,
|
||||
nodeData.knowledgeBaseId ? "知识库" : null,
|
||||
nodeData.asrResourceId ? "独立 ASR" : null,
|
||||
nodeData.ttsResourceId ? "独立 TTS" : null,
|
||||
].filter(Boolean)
|
||||
: type === "action" && nodeData.toolId
|
||||
? ["确定性工具"]
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -46,7 +57,7 @@ export function GenericNode({ id, type, data, selected }: NodeProps) {
|
||||
{spec.hasTarget && (
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
position={Position.Top}
|
||||
className="!h-3 !w-3 !border-[3px] !border-card !bg-muted-soft"
|
||||
/>
|
||||
)}
|
||||
@@ -86,7 +97,7 @@ export function GenericNode({ id, type, data, selected }: NodeProps) {
|
||||
>
|
||||
<Pencil size={13} />
|
||||
</button>
|
||||
{type !== "startCall" && (
|
||||
{type !== "start" && (
|
||||
<button
|
||||
type="button"
|
||||
title="删除节点"
|
||||
@@ -129,11 +140,22 @@ export function GenericNode({ id, type, data, selected }: NodeProps) {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{meta.length > 0 && (
|
||||
<div className="mt-3 flex flex-wrap gap-1.5">
|
||||
{meta.map((item) => (
|
||||
<span key={item as string} className="rounded-full bg-surface-strong px-2 py-0.5 text-[10px] text-muted-foreground">
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{spec.hasSource && (
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
className="!h-3 !w-3 !border-[3px] !border-card !bg-primary"
|
||||
position={Position.Bottom}
|
||||
title="拖到节点或画布空白处"
|
||||
className="!h-3 !w-3 !border-[3px] !border-card !bg-primary transition-transform hover:!scale-125"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -141,8 +163,9 @@ export function GenericNode({ id, type, data, selected }: NodeProps) {
|
||||
}
|
||||
|
||||
export const nodeTypes = {
|
||||
startCall: GenericNode,
|
||||
agentNode: GenericNode,
|
||||
endCall: GenericNode,
|
||||
globalNode: GenericNode,
|
||||
start: GenericNode,
|
||||
agent: GenericNode,
|
||||
action: GenericNode,
|
||||
handoff: GenericNode,
|
||||
end: GenericNode,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,36 +1,62 @@
|
||||
/**
|
||||
* 工作流节点的前端类型与运行期规格。
|
||||
*
|
||||
* 节点「目录」(有哪些类型、各自的字段与约束)由后端 /api/node-types 提供,
|
||||
* 通过 useNodeSpecs 拉取后用 toRuntimeSpec 转成带 React 组件(图标)的运行期规格。
|
||||
* 本文件只保留:类型定义、默认图、图标/配色解析。新增节点类型改后端即可。
|
||||
*/
|
||||
/** Workflow v3 public graph types and defaults. */
|
||||
|
||||
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"
|
||||
| "globalNode";
|
||||
export type WorkflowNodeType = "start" | "agent" | "action" | "handoff" | "end";
|
||||
export type ContextPolicy = "inherit" | "fresh";
|
||||
export type KnowledgeMode = "automatic" | "on_demand" | "disabled";
|
||||
export type EdgeMode = "llm" | "expression" | "always";
|
||||
export type ExpressionOperator =
|
||||
| "eq"
|
||||
| "neq"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "contains"
|
||||
| "in"
|
||||
| "exists";
|
||||
|
||||
export type WorkflowNodeData = {
|
||||
/** 节点显示名 */
|
||||
name: string;
|
||||
/** 开场白(仅 startCall) */
|
||||
greeting?: string;
|
||||
/** 节点提示词 */
|
||||
prompt?: string;
|
||||
/** 允许打断(仅 agentNode) */
|
||||
allowInterrupt?: boolean;
|
||||
/** 是否合并全局节点提示词(start/agent 默认开启,end 默认关闭) */
|
||||
addGlobalPrompt?: boolean;
|
||||
contextPolicy?: ContextPolicy;
|
||||
toolIds?: string[];
|
||||
knowledgeBaseId?: string;
|
||||
knowledgeMode?: KnowledgeMode;
|
||||
knowledgeTopN?: number;
|
||||
knowledgeScoreThreshold?: number;
|
||||
asrResourceId?: string;
|
||||
ttsResourceId?: string;
|
||||
toolId?: string;
|
||||
arguments?: Record<string, unknown>;
|
||||
resultAssignments?: Record<string, string>;
|
||||
targetType?: "ai" | "human" | "queue" | "phone";
|
||||
target?: string;
|
||||
message?: string;
|
||||
scope?: "flow" | "session";
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type ExpressionRule = {
|
||||
variable: string;
|
||||
operator: ExpressionOperator;
|
||||
value?: unknown;
|
||||
};
|
||||
|
||||
export type WorkflowEdgeData = {
|
||||
mode: EdgeMode;
|
||||
priority: number;
|
||||
condition?: string;
|
||||
expression?: { combinator: "and" | "or"; rules: ExpressionRule[] };
|
||||
label?: string;
|
||||
transitionSpeech?: string;
|
||||
};
|
||||
|
||||
export type FieldSpec = {
|
||||
key: string;
|
||||
label: string;
|
||||
@@ -39,7 +65,6 @@ export type FieldSpec = {
|
||||
default?: unknown;
|
||||
};
|
||||
|
||||
/** 解析后的运行期节点规格(DTO + 解析出的 React 图标 + 派生句柄) */
|
||||
export type RuntimeNodeSpec = {
|
||||
type: string;
|
||||
displayName: string;
|
||||
@@ -47,9 +72,7 @@ export type RuntimeNodeSpec = {
|
||||
icon: LucideIcon;
|
||||
accent: string;
|
||||
addable: boolean;
|
||||
/** 入边句柄(开始节点没有) */
|
||||
hasTarget: boolean;
|
||||
/** 出边句柄(结束节点没有) */
|
||||
hasSource: boolean;
|
||||
constraints: {
|
||||
minIncoming?: number;
|
||||
@@ -62,7 +85,6 @@ export type RuntimeNodeSpec = {
|
||||
fields: FieldSpec[];
|
||||
};
|
||||
|
||||
/** 渐变 token → CSS 变量名(图标底色用),未知配色回落到 sky */
|
||||
export const ACCENT_VAR: Record<string, string> = {
|
||||
mint: "--gradient-mint",
|
||||
sky: "--gradient-sky",
|
||||
@@ -75,13 +97,11 @@ 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,
|
||||
@@ -93,12 +113,12 @@ export function toRuntimeSpec(dto: NodeSpecDto): RuntimeNodeSpec {
|
||||
hasTarget: dto.constraints.maxIncoming !== 0,
|
||||
hasSource: dto.constraints.maxOutgoing !== 0,
|
||||
constraints: dto.constraints,
|
||||
fields: dto.fields.map((f) => ({
|
||||
key: f.key,
|
||||
label: f.label,
|
||||
type: f.type,
|
||||
required: f.required,
|
||||
default: f.default,
|
||||
fields: dto.fields.map((field) => ({
|
||||
key: field.key,
|
||||
label: field.label,
|
||||
type: field.type,
|
||||
required: field.required,
|
||||
default: field.default,
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -106,6 +126,12 @@ export function toRuntimeSpec(dto: NodeSpecDto): RuntimeNodeSpec {
|
||||
export type NodeSpecMap = Record<string, RuntimeNodeSpec>;
|
||||
|
||||
export type WorkflowGraph = {
|
||||
specVersion: 3;
|
||||
settings: {
|
||||
globalPrompt: string;
|
||||
defaultAsrResourceId: string;
|
||||
defaultTtsResourceId: string;
|
||||
};
|
||||
nodes: Array<{
|
||||
id: string;
|
||||
type: WorkflowNodeType;
|
||||
@@ -116,62 +142,72 @@ export type WorkflowGraph = {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
data?: { condition?: string; label?: string; transition_speech?: string };
|
||||
data: WorkflowEdgeData;
|
||||
}>;
|
||||
viewport?: { x: number; y: number; zoom: number };
|
||||
};
|
||||
|
||||
/** 新建工作流的默认图:全局规则 + 开始 → 智能体 → 结束 */
|
||||
export function defaultGraph(): WorkflowGraph {
|
||||
return {
|
||||
specVersion: 3,
|
||||
settings: {
|
||||
globalPrompt:
|
||||
"你是一个友好、专业的语音助手。请使用简短、自然、适合口语表达的句子。",
|
||||
defaultAsrResourceId: "",
|
||||
defaultTtsResourceId: "",
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: "start",
|
||||
type: "startCall",
|
||||
position: { x: 100, y: 120 },
|
||||
type: "start",
|
||||
position: { x: 360, y: 60 },
|
||||
data: {
|
||||
name: "开始",
|
||||
greeting: "你好,我是 AI 视频助手,有什么可以帮你?",
|
||||
prompt: "了解用户的需求,并在信息明确后进入下一节点。",
|
||||
allowInterrupt: true,
|
||||
addGlobalPrompt: true,
|
||||
name: "Start",
|
||||
greeting: "你好,我是 AI 视频助手,有什么可以帮你?",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "agent-1",
|
||||
type: "agentNode",
|
||||
position: { x: 420, y: 120 },
|
||||
type: "agent",
|
||||
position: { x: 360, y: 300 },
|
||||
data: {
|
||||
name: "智能体节点",
|
||||
prompt: "根据用户需求提供清晰、准确的帮助。",
|
||||
allowInterrupt: true,
|
||||
addGlobalPrompt: true,
|
||||
name: "Agent",
|
||||
prompt: "了解用户需求并提供清晰、准确的帮助。",
|
||||
contextPolicy: "inherit",
|
||||
toolIds: [],
|
||||
knowledgeMode: "disabled",
|
||||
knowledgeTopN: 5,
|
||||
knowledgeScoreThreshold: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "end",
|
||||
type: "endCall",
|
||||
position: { x: 740, y: 120 },
|
||||
type: "end",
|
||||
position: { x: 360, y: 540 },
|
||||
data: {
|
||||
name: "结束",
|
||||
prompt: "总结已经完成的事项,礼貌道别并结束通话。",
|
||||
addGlobalPrompt: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "global",
|
||||
type: "globalNode",
|
||||
position: { x: 100, y: 400 },
|
||||
data: {
|
||||
name: "全局设定",
|
||||
prompt:
|
||||
"你是一个友好、专业的语音助手。请使用简短、自然、适合口语表达的句子。",
|
||||
name: "End",
|
||||
message: "感谢你的来电,再见。",
|
||||
scope: "session",
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{ id: "e-start-agent", source: "start", target: "agent-1", data: {} },
|
||||
{ id: "e-agent-end", source: "agent-1", target: "end", data: {} },
|
||||
{
|
||||
id: "e-start-agent",
|
||||
source: "start",
|
||||
target: "agent-1",
|
||||
data: { mode: "always", priority: 0 },
|
||||
},
|
||||
{
|
||||
id: "e-agent-end",
|
||||
source: "agent-1",
|
||||
target: "end",
|
||||
data: {
|
||||
mode: "llm",
|
||||
priority: 10,
|
||||
condition: "当前阶段任务已经完成,适合结束会话",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user