3 Commits

Author SHA1 Message Date
394faa4328 fix:修复手动暂停导致历史记录重复的bug
- 统一对话历史保存入口,移除手动暂停时的重复写入逻辑。手动暂停仅保留一条历史记录,并附带中止标记。
2026-03-20 18:54:21 +08:00
4e06d08106 feat:优化会话历史中工具执行请求的处理逻辑
- 实现工具执行请求的检测和处理
- 关联工具执行结果与请求
- 跳过已处理的工具执行结果消息
- 构建包含工具执行信息的segments
2026-03-20 18:26:14 +08:00
3b0dca6467 fix: 修复历史记录点击重复显示问题
- 重构 conversationHelper.ts 中的 selectConversation 函数
- 消除全局 segments 状态导致的消息重复累积
- 为每个 AI 消息单独处理 segments,避免跨消息影响
- 简化消息处理逻辑,提高代码可读性
2026-03-20 16:49:40 +08:00
6 changed files with 71 additions and 71 deletions

View File

@ -42,9 +42,9 @@ const ENV_CONFIG: Record<Environment, IccoderConfig> = {
}, },
/** 测试服务器环境 - 通过 Gateway 路由 */ /** 测试服务器环境 - 通过 Gateway 路由 */
test: { test: {
backendUrl: "http://192.168.1.108:2029/iccoder", backendUrl: "http://192.168.1.134:2233",
backendUrlStrongeLoop: "http://192.168.1.108:2029", backendUrlStrongeLoop: "http://192.168.1.134:2233",
loginUrl: "http://192.168.1.108:2005/login", loginUrl: "http://192.168.1.134/login",
timeout: 60000, timeout: 60000,
userId: "default-user", userId: "default-user",
serviceTier: "max", serviceTier: "max",

View File

@ -90,21 +90,12 @@ export async function selectConversation(
panel.webview.postMessage({ command: "clearChat" }); panel.webview.postMessage({ command: "clearChat" });
const segments: any[] = [];
let i = 0; let i = 0;
while (i < taskSession.messages.length) { while (i < taskSession.messages.length) {
const message = taskSession.messages[i]; const message = taskSession.messages[i];
if (message.type === MessageType.USER) { if (message.type === MessageType.USER) {
if (segments.length > 0) {
panel.webview.postMessage({
command: "receiveSegments",
segments: [...segments],
});
segments.length = 0;
}
const textContent = message.contents?.find((c) => c.type === "TEXT"); const textContent = message.contents?.find((c) => c.type === "TEXT");
if (textContent && "text" in textContent) { if (textContent && "text" in textContent) {
panel.webview.postMessage({ panel.webview.postMessage({
@ -115,12 +106,15 @@ export async function selectConversation(
i++; i++;
} else if (message.type === MessageType.AI) { } else if (message.type === MessageType.AI) {
if (message.segments && message.segments.length > 0) { if (message.segments && message.segments.length > 0) {
// 直接发送 segments
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveSegments", command: "receiveSegments",
segments: message.segments, segments: message.segments,
}); });
i++;
} else { } else {
// 构建当前 AI 消息的 segments 并发送
const segments: any[] = [];
if (message.text) { if (message.text) {
segments.push({ type: "text", content: message.text }); segments.push({ type: "text", content: message.text });
} }
@ -138,7 +132,7 @@ export async function selectConversation(
nextMsg.id === toolReq.id nextMsg.id === toolReq.id
) { ) {
toolResult = nextMsg.text; toolResult = nextMsg.text;
i++; i++; // 跳过工具执行结果消息
} }
} }
@ -151,64 +145,26 @@ export async function selectConversation(
} }
} }
i++; if (segments.length > 0) {
panel.webview.postMessage({
while (i < taskSession.messages.length) { command: "receiveSegments",
const nextMsg = taskSession.messages[i]; segments: segments,
if (nextMsg.type === MessageType.USER) { });
break;
}
if (nextMsg.type === MessageType.AI) {
if (nextMsg.segments && nextMsg.segments.length > 0) {
break;
}
if (nextMsg.text) {
segments.push({ type: "text", content: nextMsg.text });
}
if (
nextMsg.toolExecutionRequests &&
nextMsg.toolExecutionRequests.length > 0
) {
for (const toolReq of nextMsg.toolExecutionRequests) {
let toolResult = "";
if (i + 1 < taskSession.messages.length) {
const resultMsg = taskSession.messages[i + 1];
if (
resultMsg.type === MessageType.TOOL_EXECUTION_RESULT &&
resultMsg.id === toolReq.id
) {
toolResult = resultMsg.text;
i++;
}
}
segments.push({
type: "tool",
toolName: toolReq.name,
askId: toolReq.id,
toolResult: toolResult,
});
}
}
i++;
} else if (nextMsg.type === MessageType.TOOL_EXECUTION_RESULT) {
i++;
} else {
i++;
}
} }
} }
} else {
i++; i++;
} else {
// 处理其他类型的消息(如 SYSTEM, TOOL_EXECUTION_RESULT 等)
if (message.type === MessageType.TOOL_EXECUTION_RESULT) {
// 工具执行结果已经在上面的 AI 消息处理中被处理了,这里跳过
i++;
} else {
// 其他类型消息,如 SYSTEM
i++;
}
} }
} }
if (segments.length > 0) {
panel.webview.postMessage({
command: "receiveSegments",
segments: segments,
});
}
// 发送任务完成消息(历史记录) // 发送任务完成消息(历史记录)
panel.webview.postMessage({ panel.webview.postMessage({
command: "taskCompleteHistory", command: "taskCompleteHistory",

View File

@ -45,6 +45,8 @@ export interface MessageSegment {
toolDescription?: string; toolDescription?: string;
askId?: string; askId?: string;
questions?: import("../types/api").QuestionItem[]; questions?: import("../types/api").QuestionItem[];
answered?: boolean;
answers?: { [questionIndex: string]: string[] };
// 智能体相关字段 // 智能体相关字段
agentId?: string; agentId?: string;
agentName?: string; agentName?: string;
@ -125,6 +127,7 @@ export class DialogSession {
private toolContext: ToolExecutorContext; private toolContext: ToolExecutorContext;
private accumulatedText = ""; private accumulatedText = "";
private isActive = false; private isActive = false;
private wasAbortedByUser = false;
private hasCompleted = false; // 标记是否已收到 complete 事件 private hasCompleted = false; // 标记是否已收到 complete 事件
private segments: MessageSegment[] = []; private segments: MessageSegment[] = [];
private currentTextSegment: MessageSegment | null = null; private currentTextSegment: MessageSegment | null = null;
@ -216,6 +219,10 @@ export class DialogSession {
return this.isActive; return this.isActive;
} }
get abortedByUser(): boolean {
return this.wasAbortedByUser;
}
/** /**
* 加载知识图谱数据 * 加载知识图谱数据
* 从 .iccoder/knowledge.json 读取 * 从 .iccoder/knowledge.json 读取
@ -1076,6 +1083,7 @@ export class DialogSession {
abort(): void { abort(): void {
// 先标记完成,防止 onClose 重复触发 // 先标记完成,防止 onClose 重复触发
const wasActive = this.isActive; const wasActive = this.isActive;
this.wasAbortedByUser = true;
this.hasCompleted = true; this.hasCompleted = true;
this.isActive = false; this.isActive = false;
@ -1125,6 +1133,7 @@ export class DialogSession {
// 直接调用 receiveAnswer传递 taskId 作为 fallbackTaskId // 直接调用 receiveAnswer传递 taskId 作为 fallbackTaskId
// 如果 pendingQuestions 中有问题,走正常流程 // 如果 pendingQuestions 中有问题,走正常流程
// 如果没有receiveAnswer 会使用 fallbackTaskId 直接发送到后端 // 如果没有receiveAnswer 会使用 fallbackTaskId 直接发送到后端
this.markQuestionAnswered(askId, selected, customInput, answers);
await userInteractionManager.receiveAnswer( await userInteractionManager.receiveAnswer(
askId, askId,
selected, selected,
@ -1133,6 +1142,30 @@ export class DialogSession {
this.taskId this.taskId
); );
} }
private markQuestionAnswered(
askId: string,
selected?: string[],
customInput?: string,
answers?: { [questionIndex: string]: string[] }
): void {
const normalizedAnswers =
answers && Object.keys(answers).length > 0
? answers
: { "0": customInput ? [customInput] : selected || [] };
for (let i = this.segments.length - 1; i >= 0; i--) {
const segment = this.segments[i];
if (
segment.askId === askId &&
(segment.type === "question" || segment.type === "plan")
) {
segment.answered = true;
segment.answers = normalizedAnswers;
break;
}
}
}
} }
/** /**

View File

@ -362,7 +362,11 @@ async function handleUserMessageWithBackend(
.map((s) => s.content) .map((s) => s.content)
.join("\n"); .join("\n");
await historyManager.addAiMessage(textContent, undefined, segments); const finalText = currentSession?.abortedByUser
? `${textContent}\n\n[对话已被用户中止]`
: textContent;
await historyManager.addAiMessage(finalText, undefined, segments);
console.log("[MessageHandler] AI响应已保存到历史记录"); console.log("[MessageHandler] AI响应已保存到历史记录");
} catch (error) { } catch (error) {
console.error("[MessageHandler] 保存AI响应历史失败:", error); console.error("[MessageHandler] 保存AI响应历史失败:", error);
@ -506,9 +510,11 @@ export async function handleUserAnswer(
* 中止当前对话 * 中止当前对话
*/ */
export async function abortCurrentDialog(): Promise<void> { export async function abortCurrentDialog(): Promise<void> {
if (currentSession) { const session = currentSession;
if (false && session) {
// 历史保存统一走 onComplete避免手动中止时重复写入
// 保存当前已有的对话内容 // 保存当前已有的对话内容
const segments = currentSession.getSegments(); const segments = session!.getSegments();
if (segments && segments.length > 0) { if (segments && segments.length > 0) {
try { try {
const historyManager = ChatHistoryManager.getInstance(); const historyManager = ChatHistoryManager.getInstance();

View File

@ -147,8 +147,10 @@ export function getSegmentRendererScript(): string {
options: segment.options || [], options: segment.options || [],
multiSelect: false multiSelect: false
}] : []); }] : []);
const isAnswered = answeredQuestions.has(segment.askId); const segmentAnswers = segment.answers || {};
const savedAnswers = answeredQuestions.get(segment.askId) || {}; const runtimeAnswers = answeredQuestions.get(segment.askId) || {};
const savedAnswers = Object.keys(segmentAnswers).length > 0 ? segmentAnswers : runtimeAnswers;
const isAnswered = segment.answered === true || answeredQuestions.has(segment.askId);
if (isAnswered) { if (isAnswered) {
segmentDiv.classList.add('answered'); segmentDiv.classList.add('answered');
} }

View File

@ -894,6 +894,9 @@ export function getWebviewContent(
if (messagesContainer) { if (messagesContainer) {
messagesContainer.innerHTML = ''; messagesContainer.innerHTML = '';
} }
if (typeof answeredQuestions !== 'undefined' && answeredQuestions.clear) {
answeredQuestions.clear();
}
// 重置输入框布局到居中 // 重置输入框布局到居中
if (typeof window.resetInputAreaLayout === 'function') { if (typeof window.resetInputAreaLayout === 'function') {
window.resetInputAreaLayout(); window.resetInputAreaLayout();