首页 round_start 记录 id 实时绑定 + 用户消息重生成修复
- wsMessage 新增 RoundStart 事件与 getRecordId 解析(兼容数字/字符串) - 普通对话:round_start 首帧把 recordId 绑定到本轮用户消息与 AI 气泡,实时即可删除/重生成 - 工作流:round_start 把 recordId 绑定到表单卡片与结果消息(共享同一记录) - 修复用户消息重新生成时气泡消失(runChat 不补用户消息,重生成时先重新追加) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -77,9 +77,12 @@
|
||||
<el-icon><Delete /></el-icon>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 用户消息操作栏(hover 显示,仅删除) -->
|
||||
<div v-if="msg.isUser && msg.recordId" class="msg-actions">
|
||||
<button class="msg-action is-danger" title="删除对话" @click="emit('delete', msg)">
|
||||
<!-- 用户消息操作栏(hover 显示:重新生成 + 删除) -->
|
||||
<div v-if="msg.isUser" class="msg-actions">
|
||||
<button class="msg-action" title="重新生成" @click="emit('regenerate', msg)">
|
||||
<el-icon><RefreshRight /></el-icon>
|
||||
</button>
|
||||
<button v-if="msg.recordId" class="msg-action is-danger" title="删除对话" @click="emit('delete', msg)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -95,7 +95,7 @@ import { applyHomeFormValues, buildOutputsFromUrls } from './utils/flowDsl';
|
||||
import type { WorkflowOutput } from './utils/flowDsl';
|
||||
import { getChatModel, listModelManage } from '/@/api/settings/modelConfigV2';
|
||||
import { connectSessionSocket, sendAgentStart, sendWorkflowStart, sendCancel } from './utils/wsExecute';
|
||||
import { parseWsMessage, getDelta, getAnswer, getErrorText, getNodeProgress, getToolCallName, getToolResultText } from './utils/wsMessage';
|
||||
import { parseWsMessage, getDelta, getAnswer, getRecordId, getErrorText, getNodeProgress, getToolCallName, getToolResultText } from './utils/wsMessage';
|
||||
import type { ExecutionTreeItem } from '/@/api/settings/creation';
|
||||
import {
|
||||
getExecutionList,
|
||||
@@ -785,8 +785,8 @@ const handleRetry = async (msg: ChatMessage) => {
|
||||
const list = sessionMessages.value.get(sid) || [];
|
||||
const idx = list.findIndex((m) => m.id === msg.id);
|
||||
if (idx < 0) return;
|
||||
// 待重发的用户问题:优先失败消息上记录的原文,其次向上找最近一条用户消息
|
||||
let question = msg.retryQuestion || '';
|
||||
// 待重发的用户问题:用户消息直接用自身内容;失败消息优先记录的原文,其次向上找最近一条用户消息
|
||||
let question = msg.isUser ? msg.content : (msg.retryQuestion || '');
|
||||
if (!question) {
|
||||
for (let i = idx - 1; i >= 0; i--) {
|
||||
if (list[i].isUser) {
|
||||
@@ -796,8 +796,16 @@ const handleRetry = async (msg: ChatMessage) => {
|
||||
}
|
||||
}
|
||||
if (!question) return;
|
||||
// 移除失败消息,重新执行
|
||||
list.splice(idx, 1);
|
||||
// 移除待重生成的消息:用户消息连同紧随其后的 AI 回复一并移除(避免旧回复残留错位);AI 回复只移除自身
|
||||
if (msg.isUser && idx + 1 < list.length && !list[idx + 1].isUser && list[idx + 1].type !== 'form') {
|
||||
list.splice(idx, 2);
|
||||
} else {
|
||||
list.splice(idx, 1);
|
||||
}
|
||||
// 用户消息重新生成:runChat 只追加 AI 气泡、不会补用户消息,这里先把用户问题重新追加进消息流,避免用户气泡消失
|
||||
if (msg.isUser) {
|
||||
list.push({ id: 'msg-' + Date.now() + '-user', content: question, time: formatTime(new Date()), isUser: true });
|
||||
}
|
||||
sendingSessions[sid] = true;
|
||||
session.status = 'executing';
|
||||
const sessionId = resolveSessionIdForConnect(session);
|
||||
@@ -893,6 +901,8 @@ const runWorkflow = async (
|
||||
}
|
||||
|
||||
let finished = false;
|
||||
// 本轮执行记录 id:round_start 首帧填充,绑定到表单卡片与结果消息
|
||||
let roundRecordId = '';
|
||||
const finishExec = (success: boolean, errorMsg?: string, outputs?: WorkflowOutput[]) => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
@@ -909,6 +919,9 @@ const runWorkflow = async (
|
||||
content: success ? (outputs?.length ? '✅ 执行完成' : '✅ 执行完成,本次未生成产出文件') : `❌ ${errorMsg || '执行失败,请重试或联系管理员'}`,
|
||||
time: formatTime(new Date()),
|
||||
isUser: false,
|
||||
// 本轮记录 id(round_start 首帧填充):与表单卡片共享同一记录,删除该记录即删除整组
|
||||
recordId: roundRecordId || undefined,
|
||||
recordType: roundRecordId ? 'workflow' : undefined,
|
||||
outputs: outputs || [],
|
||||
};
|
||||
addMessage(resultMsg);
|
||||
@@ -953,7 +966,14 @@ const runWorkflow = async (
|
||||
// 优先按 ReAct 结构化事件判定完成/失败
|
||||
const msg = parseWsMessage(raw);
|
||||
if (msg) {
|
||||
if (msg.type === 'error') {
|
||||
if (msg.type === 'round_start') {
|
||||
// 本轮执行记录 id:后端收到启动帧后第一帧返回,绑定到表单卡片与结果消息
|
||||
roundRecordId = getRecordId(msg);
|
||||
if (roundRecordId) {
|
||||
formMsg.recordId = roundRecordId;
|
||||
formMsg.recordType = 'workflow';
|
||||
}
|
||||
} else if (msg.type === 'error') {
|
||||
finishExec(false, getErrorText(msg));
|
||||
} else if (msg.type === 'flow_complete') {
|
||||
// flow_complete 为后端新增的工作流完成信号,事件自带产出文件 URL 列表 → 直接渲染产出卡片
|
||||
@@ -1236,6 +1256,25 @@ const runChat = async (sid: string, sessionId: string, message: string) => {
|
||||
const aiIdx = list ? list.findIndex((m) => m.id === aiMsgId) : -1;
|
||||
const ai = aiIdx >= 0 && list ? list[aiIdx] : null;
|
||||
|
||||
// 本轮问答记录 id:后端收到提问后第一帧返回(round_start),后续流式帧都属于这条记录
|
||||
// 实时阶段即可绑定 recordId,使实时气泡支持删除/重生成,无需等 session/get 刷新
|
||||
if (msg.type === 'round_start') {
|
||||
const recordId = getRecordId(msg);
|
||||
if (recordId) {
|
||||
// 本轮用户消息:从尾部向上找第一条未绑定记录 id 的用户消息(新发送/重生成的都在末尾)
|
||||
const l = sessionMessages.value.get(sid);
|
||||
if (l) {
|
||||
for (let i = l.length - 1; i >= 0; i--) {
|
||||
if (l[i].isUser && !l[i].recordId) {
|
||||
l[i].recordId = recordId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ai && !ai.recordId) ai.recordId = recordId;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 模型思考(step 开始):思考区为空时给出占位提示(虚化),并记录计时起点
|
||||
if (msg.type === 'model_call') {
|
||||
if (ai && !ai.thinking && !ai.content) {
|
||||
|
||||
@@ -27,6 +27,7 @@ export const WsEventType = {
|
||||
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', // 工作流节点完成(进度推进)
|
||||
@@ -61,6 +62,13 @@ 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;
|
||||
|
||||
Reference in New Issue
Block a user