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 路由 */
test: {
backendUrl: "http://192.168.1.108:2029/iccoder",
backendUrlStrongeLoop: "http://192.168.1.108:2029",
loginUrl: "http://192.168.1.108:2005/login",
backendUrl: "http://192.168.1.134:2233",
backendUrlStrongeLoop: "http://192.168.1.134:2233",
loginUrl: "http://192.168.1.134/login",
timeout: 60000,
userId: "default-user",
serviceTier: "max",

View File

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

View File

@ -45,6 +45,8 @@ export interface MessageSegment {
toolDescription?: string;
askId?: string;
questions?: import("../types/api").QuestionItem[];
answered?: boolean;
answers?: { [questionIndex: string]: string[] };
// 智能体相关字段
agentId?: string;
agentName?: string;
@ -125,6 +127,7 @@ export class DialogSession {
private toolContext: ToolExecutorContext;
private accumulatedText = "";
private isActive = false;
private wasAbortedByUser = false;
private hasCompleted = false; // 标记是否已收到 complete 事件
private segments: MessageSegment[] = [];
private currentTextSegment: MessageSegment | null = null;
@ -216,6 +219,10 @@ export class DialogSession {
return this.isActive;
}
get abortedByUser(): boolean {
return this.wasAbortedByUser;
}
/**
* 加载知识图谱数据
* 从 .iccoder/knowledge.json 读取
@ -1076,6 +1083,7 @@ export class DialogSession {
abort(): void {
// 先标记完成,防止 onClose 重复触发
const wasActive = this.isActive;
this.wasAbortedByUser = true;
this.hasCompleted = true;
this.isActive = false;
@ -1125,6 +1133,7 @@ export class DialogSession {
// 直接调用 receiveAnswer传递 taskId 作为 fallbackTaskId
// 如果 pendingQuestions 中有问题,走正常流程
// 如果没有receiveAnswer 会使用 fallbackTaskId 直接发送到后端
this.markQuestionAnswered(askId, selected, customInput, answers);
await userInteractionManager.receiveAnswer(
askId,
selected,
@ -1133,6 +1142,30 @@ export class DialogSession {
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)
.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响应已保存到历史记录");
} catch (error) {
console.error("[MessageHandler] 保存AI响应历史失败:", error);
@ -506,9 +510,11 @@ export async function handleUserAnswer(
* 中止当前对话
*/
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) {
try {
const historyManager = ChatHistoryManager.getInstance();

View File

@ -147,8 +147,10 @@ export function getSegmentRendererScript(): string {
options: segment.options || [],
multiSelect: false
}] : []);
const isAnswered = answeredQuestions.has(segment.askId);
const savedAnswers = answeredQuestions.get(segment.askId) || {};
const segmentAnswers = segment.answers || {};
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) {
segmentDiv.classList.add('answered');
}

View File

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