Files
admin-ui/src/views/home/utils/wsMessage.ts
T

185 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ===== 首页 WebSocket 流式消息解析工具 =====
// 服务端推送统一为 JSON 文本帧(后端 ReAct 事件协议):
// {"type":"model_call","message":"模型思考","data":{"step":1,"maxStep":15}} 模型思考(step 开始)
// {"type":"tool_call","message":"调用工具","data":{"tool":"web_search","arguments":{}}} 模型请求调用工具
// {"type":"tool_result","message":"工具返回","data":{"result":"..."}} 工具返回结果
// {"type":"reasoning_chunk","message":"思考中","data":{"delta":"你"}} 思考内容增量(逐 chunk)
// {"type":"answer_chunk","message":"回答中","data":{"delta":"你"}} 回答内容增量(逐 chunk)
// {"type":"answer","message":"作答完成","data":{"answer":"..."}} 最终回答
// {"type":"node_start","message":"开始执行(2/4): xxx","data":{"nodeCount":4,"nodeId":"..","nodeIndex":2,"nodeName":".."}} 工作流节点开始(进度推进)
// {"type":"node_complete","message":"执行完成(2/4): xxx","data":同上} 工作流节点完成(进度推进)
// {"type":"error","message":"...","error":"节点失败详情"} 执行失败(error 字段含详细原因)
// 完成/失败判定均以 type 为准,不再用正则猜测文本。
export interface WsStreamMessage {
type: string;
message?: string;
data?: any;
payload?: any;
// 失败详情:error 事件携带的详细原因(含节点名与具体失败信息),比 message 更具体
error?: string;
}
/** 后端 ReAct WebSocket 事件类型常量 */
export const WsEventType = {
ModelCall: 'model_call', // 模型思考(step 开始)
ToolCall: 'tool_call', // 模型请求调用工具
ToolResult: 'tool_result', // 工具返回结果
Answer: 'answer', // 最终回答
AnswerChunk: 'answer_chunk', // 回答内容增量(逐 chunk
RoundStart: 'round_start', // 本轮问答开始(后端收到提问后第一帧,data.recordId 为本次问答记录 id
ReasoningChunk: 'reasoning_chunk', // 思考内容增量(逐 chunk
NodeStart: 'node_start', // 工作流节点开始(进度推进)
NodeComplete: 'node_complete', // 工作流节点完成(进度推进)
FlowComplete: 'flow_complete', // 工作流执行完成(后端完成信号,日志末尾事件)
Error: 'error', // 出错
} as const;
/**
* 解析服务端推送帧。
* 非 JSON、无 type 的帧返回 null(调用方忽略)。
*/
export function parseWsMessage(raw: any): WsStreamMessage | null {
let data: any = raw;
if (typeof raw === 'string') {
try {
data = JSON.parse(raw);
} catch {
return null;
}
}
if (!data || typeof data !== 'object' || typeof data.type !== 'string') return null;
return data;
}
/** answer_chunk / reasoning_chunk → data.delta 流式增量 */
export function getDelta(msg: WsStreamMessage): string {
return typeof msg?.data?.delta === 'string' ? msg.data.delta : '';
}
/** answer → data.answer 最终答案 */
export function getAnswer(msg: WsStreamMessage): string {
return typeof msg?.data?.answer === 'string' ? msg.data.answer : '';
}
/** round_start → data.recordId 本次问答记录 id(19 位雪花大整数,契约要求字符串形式,兼容数字类型防御性转 string) */
export function getRecordId(msg: WsStreamMessage): string {
const rid = msg?.data?.recordId;
if (typeof rid === 'string' || typeof rid === 'number') return String(rid);
return '';
}
/** model_call → { step, maxStep },缺失时 maxStep 为 0 */
export function getStepInfo(msg: WsStreamMessage): { step: number; maxStep: number } | null {
const step = msg?.data?.step;
if (typeof step !== 'number') return null;
const maxStep = msg?.data?.maxStep;
return { step, maxStep: typeof maxStep === 'number' ? maxStep : 0 };
}
/** 工作流节点进度:node_start / node_complete → { current, total, nodeName },非节点事件返回 null */
export function getNodeProgress(msg: WsStreamMessage): { current: number; total: number; nodeName: string } | null {
const t = msg?.type;
if (t !== WsEventType.NodeStart && t !== WsEventType.NodeComplete) return null;
const d = msg?.data;
const nodeIndex = d?.nodeIndex;
const nodeCount = d?.nodeCount;
if (typeof nodeIndex !== 'number' || typeof nodeCount !== 'number') return null;
return {
current: nodeIndex,
total: nodeCount,
nodeName: typeof d?.nodeName === 'string' ? d.nodeName : '',
};
}
/** 工作流执行节点步骤(node_start/node_complete/error 事件驱动,渲染表单卡片执行过程区) */
export interface WorkflowNodeStep {
nodeId: string;
nodeName: string;
nodeIndex: number;
status: 'pending' | 'running' | 'done' | 'failed';
}
/** 工作流执行节点进度(挂在表单卡片消息 formProgress 上) */
export interface WorkflowNodeProgress {
nodes: WorkflowNodeStep[];
}
/** 工作流节点事件解析结果:node_start / node_complete → { nodeId, nodeName, nodeIndex, phase } */
export interface WorkflowNodeEvent {
nodeId: string;
nodeName: string;
nodeIndex: number;
phase: 'start' | 'complete';
}
/**
* 解析工作流节点事件(node_start / node_complete)。
* 返回完整节点信息(nodeId/nodeName/nodeIndex/phase),供执行过程区步骤列表驱动;非节点事件返回 null。
*/
export function getNodeEvent(msg: WsStreamMessage): WorkflowNodeEvent | null {
const t = msg?.type;
if (t !== WsEventType.NodeStart && t !== WsEventType.NodeComplete) return null;
const d = msg?.data;
const nodeIndex = d?.nodeIndex;
if (typeof nodeIndex !== 'number') return null;
return {
nodeId: typeof d?.nodeId === 'string' ? d.nodeId : '',
nodeName: typeof d?.nodeName === 'string' ? d.nodeName : '',
nodeIndex,
phase: t === WsEventType.NodeStart ? 'start' : 'complete',
};
}
/** tool_call → 工具名称(tool / toolName / name 依次尝试,缺失返回空串) */
export function getToolCallName(msg: WsStreamMessage): string {
const d = msg?.data;
if (!d || typeof d !== 'object') return '';
const name = d.tool ?? d.toolName ?? d.name;
return typeof name === 'string' ? name : '';
}
/** tool_result → 工具返回文本(result / output / content / message 依次尝试,对象则序列化) */
export function getToolResultText(msg: WsStreamMessage): string {
const d = msg?.data;
if (typeof d === 'string') return d;
if (!d || typeof d !== 'object') return '';
const raw = d.result ?? d.output ?? d.content ?? d.message;
if (typeof raw === 'string') return raw;
if (raw != null) {
try {
return JSON.stringify(raw);
} catch {
return '';
}
}
return '';
}
/** error → 错误文案(message 优先展示简洁业务提示;error 详细原因仅在 message 缺失时兜底) */
export function getErrorText(msg: WsStreamMessage): string {
// 只显示 message 的消息(用户确认),error 详细原因不再作为界面展示内容
if (typeof msg?.message === 'string' && msg.message) return msg.message;
if (typeof msg?.error === 'string' && msg.error) return msg.error;
const d = msg?.data;
if (typeof d === 'string') return d;
if (d && typeof d === 'object') {
const nested = d.message ?? d.error ?? d.msg;
if (typeof nested === 'string' && nested) return nested;
}
return '执行失败,请重试';
}
/** error 详细原因(error 字段优先展示失败详情;message 仅在 error 缺失时兜底)——供工作流失败卡片「查看失败原因」使用 */
export function getErrorDetail(msg: WsStreamMessage): string {
if (typeof msg?.error === 'string' && msg.error) return msg.error;
if (typeof msg?.message === 'string' && msg.message) return msg.message;
const d = msg?.data;
if (typeof d === 'string') return d;
if (d && typeof d === 'object') {
const nested = d.message ?? d.error ?? d.msg;
if (typeof nested === 'string' && nested) return nested;
}
return '执行失败,请重试';
}