首页工作流表单卡片化闭环:执行进度、输入禁用与取消入口

- socket 节点事件推进表单卡片执行进度,失败详情展示详细原因
- 工作流执行时禁用输入框,取消入口移到卡片 footer,卡片定格可重试
- 工作流 ModelRequestParams 按详情原样透传后端
- 会话内产出文件卡片展示(预览/下载/删除),移除顶部结果卡片区

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-15 21:36:33 +08:00
co-authored by Claude
parent dc20a6a829
commit bb15c053e6
9 changed files with 495 additions and 376 deletions
+25 -2
View File
@@ -6,7 +6,9 @@
// {"type":"reasoning_chunk","message":"思考中","data":{"delta":"你"}} 思考内容增量(逐 chunk)
// {"type":"answer_chunk","message":"回答中","data":{"delta":"你"}} 回答内容增量(逐 chunk)
// {"type":"answer","message":"作答完成","data":{"answer":"..."}} 最终回答
// {"type":"error","message":"..."} 执行失败
// {"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 {
@@ -14,6 +16,8 @@ export interface WsStreamMessage {
message?: string;
data?: any;
payload?: any;
// 失败详情:error 事件携带的详细原因(含节点名与具体失败信息),比 message 更具体
error?: string;
}
/** 后端 ReAct WebSocket 事件类型常量 */
@@ -24,6 +28,8 @@ export const WsEventType = {
Answer: 'answer', // 最终回答
AnswerChunk: 'answer_chunk', // 回答内容增量(逐 chunk
ReasoningChunk: 'reasoning_chunk', // 思考内容增量(逐 chunk
NodeStart: 'node_start', // 工作流节点开始(进度推进)
NodeComplete: 'node_complete', // 工作流节点完成(进度推进)
Error: 'error', // 出错
} as const;
@@ -62,6 +68,21 @@ export function getStepInfo(msg: WsStreamMessage): { step: number; maxStep: numb
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 : '',
};
}
/** tool_call → 工具名称(tool / toolName / name 依次尝试,缺失返回空串) */
export function getToolCallName(msg: WsStreamMessage): string {
const d = msg?.data;
@@ -87,8 +108,10 @@ export function getToolResultText(msg: WsStreamMessage): string {
return '';
}
/** error → 错误文案(message 优先,其次 data.message / data.error / data.msg */
/** error → 错误文案(error 字段为详细原因优先;其次 message;再 data.message / data.error / data.msg */
export function getErrorText(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;