Enhance workflow engine and frontend components with transition speech support
- Introduce edge transition speech functionality in the WorkflowEngine to provide optional speech during node transitions. - Update pipeline execution to utilize the new transition speech feature, enhancing user experience by masking delays during transitions. - Modify frontend components to support transition speech in edge specifications, allowing users to define and edit transition speech for edges. - Refactor edge handling logic in the WorkflowEditor to accommodate the new transition speech field, improving workflow management capabilities.
This commit is contained in:
@@ -346,9 +346,18 @@ async def run_pipeline(transport, cfg: AssistantConfig) -> None:
|
|||||||
await emit_node_active(target)
|
await emit_node_active(target)
|
||||||
apply_node(target)
|
apply_node(target)
|
||||||
|
|
||||||
def make_transition_handler(target: str):
|
async def speak_transition(edge: dict | None) -> None:
|
||||||
|
"""切换瞬间播报过渡语(可选),掩盖切节点/新一轮生成的延迟。不写入上下文。"""
|
||||||
|
speech = engine.edge_transition_speech(edge)
|
||||||
|
if speech:
|
||||||
|
await worker.queue_frame(TTSSpeakFrame(speech, append_to_context=False))
|
||||||
|
|
||||||
|
def make_transition_handler(edge: dict):
|
||||||
|
target = edge.get("target")
|
||||||
|
|
||||||
async def handler(params):
|
async def handler(params):
|
||||||
logger.info(f"LLM 触发转移 → {engine.name(target)}")
|
logger.info(f"LLM 触发转移 → {engine.name(target)}")
|
||||||
|
await speak_transition(edge)
|
||||||
await go_to_node(target)
|
await go_to_node(target)
|
||||||
# 返回工具结果,pipecat 随即在新节点的提示/工具下继续生成
|
# 返回工具结果,pipecat 随即在新节点的提示/工具下继续生成
|
||||||
await params.result_callback({"status": "ok"})
|
await params.result_callback({"status": "ok"})
|
||||||
@@ -372,6 +381,7 @@ async def run_pipeline(transport, cfg: AssistantConfig) -> None:
|
|||||||
)
|
)
|
||||||
if target and target != wf_state["current"]:
|
if target and target != wf_state["current"]:
|
||||||
logger.info(f"文本兜底触发转移 → {engine.name(target)}")
|
logger.info(f"文本兜底触发转移 → {engine.name(target)}")
|
||||||
|
await speak_transition(engine.find_edge(wf_state["current"], target))
|
||||||
# 仅切换节点提示/工具,下一轮用户输入即在新节点处理
|
# 仅切换节点提示/工具,下一轮用户输入即在新节点处理
|
||||||
await go_to_node(target)
|
await go_to_node(target)
|
||||||
|
|
||||||
@@ -379,10 +389,9 @@ async def run_pipeline(transport, cfg: AssistantConfig) -> None:
|
|||||||
# 由各节点的 context.tools 控制当前可见哪些)。
|
# 由各节点的 context.tools 控制当前可见哪些)。
|
||||||
if workflow_active:
|
if workflow_active:
|
||||||
for edge in engine.edges:
|
for edge in engine.edges:
|
||||||
target = edge.get("target")
|
if edge.get("target"):
|
||||||
if target:
|
|
||||||
llm.register_function(
|
llm.register_function(
|
||||||
engine.edge_fn_name(edge), make_transition_handler(target)
|
engine.edge_fn_name(edge), make_transition_handler(edge)
|
||||||
)
|
)
|
||||||
apply_node(wf_state["current"]) # 设初始节点的提示与工具
|
apply_node(wf_state["current"]) # 设初始节点的提示与工具
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,18 @@ class WorkflowEngine:
|
|||||||
def edge_condition(self, edge: dict) -> str:
|
def edge_condition(self, edge: dict) -> str:
|
||||||
return (edge.get("data") or {}).get("condition") or ""
|
return (edge.get("data") or {}).get("condition") or ""
|
||||||
|
|
||||||
|
def edge_transition_speech(self, edge: dict | None) -> str:
|
||||||
|
"""命中该边、切换节点瞬间播报的过渡语(可选,掩盖延迟,不写入上下文)。"""
|
||||||
|
if not edge:
|
||||||
|
return ""
|
||||||
|
return (edge.get("data") or {}).get("transition_speech") or ""
|
||||||
|
|
||||||
|
def find_edge(self, source: str | None, target: str | None) -> dict | None:
|
||||||
|
for edge in self.edges:
|
||||||
|
if edge.get("source") == source and edge.get("target") == target:
|
||||||
|
return edge
|
||||||
|
return None
|
||||||
|
|
||||||
def edge_description(self, edge: dict) -> str:
|
def edge_description(self, edge: dict) -> str:
|
||||||
"""作为转移函数的 description 交给 LLM:满足该条件时模型应调用此函数。"""
|
"""作为转移函数的 description 交给 LLM:满足该条件时模型应调用此函数。"""
|
||||||
cond = self.edge_condition(edge)
|
cond = self.edge_condition(edge)
|
||||||
|
|||||||
@@ -120,7 +120,12 @@ function fromFlow(nodes: Node[], edges: Edge[]): WorkflowGraph {
|
|||||||
id: e.id,
|
id: e.id,
|
||||||
source: e.source,
|
source: e.source,
|
||||||
target: e.target,
|
target: e.target,
|
||||||
data: (e.data as { condition?: string; label?: string }) ?? {},
|
data:
|
||||||
|
(e.data as {
|
||||||
|
condition?: string;
|
||||||
|
label?: string;
|
||||||
|
transition_speech?: string;
|
||||||
|
}) ?? {},
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -238,7 +243,14 @@ function Canvas({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const updateEdgeData = useCallback(
|
const updateEdgeData = useCallback(
|
||||||
(id: string, patch: { condition?: string; label?: string }) => {
|
(
|
||||||
|
id: string,
|
||||||
|
patch: {
|
||||||
|
condition?: string;
|
||||||
|
label?: string;
|
||||||
|
transition_speech?: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
setEdges((es) =>
|
setEdges((es) =>
|
||||||
es.map((e) =>
|
es.map((e) =>
|
||||||
e.id === id ? { ...e, data: { ...(e.data ?? {}), ...patch } } : e,
|
e.id === id ? { ...e, data: { ...(e.data ?? {}), ...patch } } : e,
|
||||||
@@ -677,12 +689,23 @@ function EdgeForm({
|
|||||||
onDelete,
|
onDelete,
|
||||||
}: {
|
}: {
|
||||||
edge: Edge;
|
edge: Edge;
|
||||||
onSave: (patch: { condition: string; label: string }) => void;
|
onSave: (patch: {
|
||||||
|
condition: string;
|
||||||
|
label: string;
|
||||||
|
transition_speech: string;
|
||||||
|
}) => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
}) {
|
}) {
|
||||||
const data = (edge.data ?? {}) as { condition?: string; label?: string };
|
const data = (edge.data ?? {}) as {
|
||||||
|
condition?: string;
|
||||||
|
label?: string;
|
||||||
|
transition_speech?: string;
|
||||||
|
};
|
||||||
const [label, setLabel] = useState(data.label ?? "");
|
const [label, setLabel] = useState(data.label ?? "");
|
||||||
const [condition, setCondition] = useState(data.condition ?? "");
|
const [condition, setCondition] = useState(data.condition ?? "");
|
||||||
|
const [transitionSpeech, setTransitionSpeech] = useState(
|
||||||
|
data.transition_speech ?? "",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -718,6 +741,22 @@ function EdgeForm({
|
|||||||
className="resize-none border-hairline-strong bg-background text-foreground placeholder:text-muted-soft"
|
className="resize-none border-hairline-strong bg-background text-foreground placeholder:text-muted-soft"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium text-foreground">
|
||||||
|
过渡语(可选)
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
rows={2}
|
||||||
|
value={transitionSpeech}
|
||||||
|
placeholder="切换到下一节点前先说的一句过渡语,用于掩盖延迟,例如:好的,我帮你转接。"
|
||||||
|
onChange={(e) => setTransitionSpeech(e.target.value)}
|
||||||
|
className="resize-none border-hairline-strong bg-background text-foreground placeholder:text-muted-soft"
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-muted-soft">
|
||||||
|
命中该条件、切换节点的瞬间播报,不写入对话上下文。
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter className="flex-row justify-between sm:justify-between">
|
<DialogFooter className="flex-row justify-between sm:justify-between">
|
||||||
@@ -729,7 +768,13 @@ function EdgeForm({
|
|||||||
<Trash2 size={15} />
|
<Trash2 size={15} />
|
||||||
删除连线
|
删除连线
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => onSave({ condition, label })}>保存条件</Button>
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
onSave({ condition, label, transition_speech: transitionSpeech })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
保存条件
|
||||||
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export type WorkflowGraph = {
|
|||||||
id: string;
|
id: string;
|
||||||
source: string;
|
source: string;
|
||||||
target: string;
|
target: string;
|
||||||
data?: { condition?: string; label?: string };
|
data?: { condition?: string; label?: string; transition_speech?: string };
|
||||||
}>;
|
}>;
|
||||||
viewport?: { x: number; y: number; zoom: number };
|
viewport?: { x: number; y: number; zoom: number };
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user