feat:实现任务历史加载功能 - 完整还原对话样式
主要改进: 1. 实现selectConversation功能,支持点击任务历史列表加载会话 2. 优化会话存储格式,保存完整的segments信息(包括工具调用) 3. 添加旧格式到新格式的自动转换,兼容历史数据 4. 改进错误处理,自动清理无效的空任务目录 5. 优化路径编码逻辑,确保跨平台一致性 6. 前端支持clearChat、addUserMessage、addAiMessage命令 技术细节: - 扩展AiMessage数据结构,添加segments字段 - 修改messageHandler保存逻辑,将完整segments保存到一条消息 - 实现loadTaskSession方法,加载指定任务的完整会话 - 添加自动清理机制,删除无效的空任务目录
This commit is contained in:
@ -11,6 +11,8 @@ import {
|
||||
abortCurrentDialog,
|
||||
} from "../utils/messageHandler";
|
||||
import { VCDViewerPanel } from "./VCDViewerPanel";
|
||||
import { ChatHistoryManager } from "../utils/chatHistoryManager";
|
||||
import { MessageType } from "../types/chatHistory";
|
||||
|
||||
/**
|
||||
* 创建并显示 IC 助手面板
|
||||
@ -96,14 +98,14 @@ export function showICHelperPanel(
|
||||
showICHelperPanel(context, panel.viewColumn);
|
||||
break;
|
||||
case "loadConversationHistory":
|
||||
// 加载会话历史(暂未实现)
|
||||
panel.webview.postMessage({
|
||||
command: "conversationHistory",
|
||||
history: [],
|
||||
});
|
||||
// 加载会话历史(支持分页)
|
||||
loadConversationHistory(panel, message.offset || 0, message.limit || 10);
|
||||
break;
|
||||
case "selectConversation":
|
||||
// 选择会话(暂未实现)
|
||||
// 选择会话
|
||||
if (message.conversationId) {
|
||||
selectConversation(panel, message.conversationId, context.extensionPath);
|
||||
}
|
||||
break;
|
||||
// 新增:处理用户回答
|
||||
case "submitAnswer":
|
||||
@ -302,3 +304,222 @@ function parseVCDSignals(content: string, maxSignals: number = 3) {
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载会话历史(支持分页)
|
||||
*/
|
||||
async function loadConversationHistory(
|
||||
panel: vscode.WebviewPanel,
|
||||
offset: number = 0,
|
||||
limit: number = 10
|
||||
) {
|
||||
try {
|
||||
const historyManager = ChatHistoryManager.getInstance();
|
||||
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||
|
||||
if (!workspacePath) {
|
||||
// 没有打开的工作区,返回空历史
|
||||
panel.webview.postMessage({
|
||||
command: "conversationHistory",
|
||||
items: [],
|
||||
total: 0,
|
||||
hasMore: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取会话历史列表(支持分页)
|
||||
const result = await historyManager.getConversationHistoryList(
|
||||
workspacePath,
|
||||
offset,
|
||||
limit
|
||||
);
|
||||
|
||||
// 发送会话历史到前端
|
||||
panel.webview.postMessage({
|
||||
command: "conversationHistory",
|
||||
items: result.items,
|
||||
total: result.total,
|
||||
hasMore: result.hasMore,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("加载会话历史失败:", error);
|
||||
// 发生错误时返回空历史
|
||||
panel.webview.postMessage({
|
||||
command: "conversationHistory",
|
||||
items: [],
|
||||
total: 0,
|
||||
hasMore: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择并加载指定的会话
|
||||
*/
|
||||
async function selectConversation(
|
||||
panel: vscode.WebviewPanel,
|
||||
taskId: string,
|
||||
extensionPath: string
|
||||
) {
|
||||
try {
|
||||
const historyManager = ChatHistoryManager.getInstance();
|
||||
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||
|
||||
if (!workspacePath) {
|
||||
vscode.window.showErrorMessage("没有打开的工作区");
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载任务会话
|
||||
const taskSession = await historyManager.loadTaskSession(workspacePath, taskId);
|
||||
|
||||
if (!taskSession) {
|
||||
vscode.window.showErrorMessage(`加载任务 ${taskId} 失败: 任务不存在或数据损坏`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 切换到该任务
|
||||
const switched = await historyManager.switchTask(workspacePath, taskId);
|
||||
if (!switched) {
|
||||
vscode.window.showErrorMessage(`切换到任务 ${taskId} 失败`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空当前聊天界面
|
||||
panel.webview.postMessage({
|
||||
command: "clearChat"
|
||||
});
|
||||
|
||||
// 将会话历史消息转换为 segments 格式并发送到前端显示
|
||||
const segments: any[] = [];
|
||||
let i = 0;
|
||||
|
||||
while (i < taskSession.messages.length) {
|
||||
const message = taskSession.messages[i];
|
||||
|
||||
if (message.type === MessageType.USER) {
|
||||
// 用户消息 - 如果有累积的 segments,先发送
|
||||
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({
|
||||
command: "addUserMessage",
|
||||
text: textContent.text
|
||||
});
|
||||
}
|
||||
i++;
|
||||
} else if (message.type === MessageType.AI) {
|
||||
// AI消息 - 如果有 segments,直接使用
|
||||
if (message.segments && message.segments.length > 0) {
|
||||
panel.webview.postMessage({
|
||||
command: "receiveSegments",
|
||||
segments: message.segments
|
||||
});
|
||||
i++;
|
||||
} else {
|
||||
// 旧格式:需要转换为 segments
|
||||
// 收集连续的 AI 消息、工具调用和工具结果
|
||||
if (message.text) {
|
||||
segments.push({
|
||||
type: 'text',
|
||||
content: message.text
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否有工具调用
|
||||
if (message.toolExecutionRequests && message.toolExecutionRequests.length > 0) {
|
||||
for (const toolReq of message.toolExecutionRequests) {
|
||||
// 查找对应的工具执行结果
|
||||
let toolResult = '';
|
||||
if (i + 1 < taskSession.messages.length) {
|
||||
const nextMsg = taskSession.messages[i + 1];
|
||||
if (nextMsg.type === MessageType.TOOL_EXECUTION_RESULT &&
|
||||
nextMsg.id === toolReq.id) {
|
||||
toolResult = nextMsg.text;
|
||||
i++; // 跳过工具结果消息
|
||||
}
|
||||
}
|
||||
|
||||
segments.push({
|
||||
type: 'tool',
|
||||
toolName: toolReq.name,
|
||||
askId: toolReq.id,
|
||||
toolResult: toolResult
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
// 继续收集后续的 AI 消息,直到遇到用户消息或有 segments 的 AI 消息
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
// 发送剩余的 segments
|
||||
if (segments.length > 0) {
|
||||
panel.webview.postMessage({
|
||||
command: "receiveSegments",
|
||||
segments: segments
|
||||
});
|
||||
}
|
||||
|
||||
vscode.window.showInformationMessage(`已加载会话: ${taskSession.meta.taskName}`);
|
||||
} catch (error) {
|
||||
console.error("选择会话失败:", error);
|
||||
vscode.window.showErrorMessage(`加载会话失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,6 +56,7 @@ export interface AiMessage extends BaseMessage {
|
||||
text?: string;
|
||||
toolExecutionRequests?: ToolExecutionRequest[];
|
||||
thinking?: string;
|
||||
segments?: any[]; // 保存完整的 segments 信息用于还原显示
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -8,7 +8,8 @@ import {
|
||||
MessageType,
|
||||
UserMessage,
|
||||
AiMessage,
|
||||
SystemMessage
|
||||
SystemMessage,
|
||||
ToolExecutionResultMessage
|
||||
} from '../types/chatHistory';
|
||||
|
||||
/**
|
||||
@ -33,12 +34,13 @@ export class ChatHistoryManager {
|
||||
* 规则:
|
||||
* - 替换 \ 和 / 为 --
|
||||
* - 替换 : 为空
|
||||
* 例如:C:\Users\admin\Documents\Project -> C--Users-admin-Documents-Project
|
||||
* 例如:C:\Users\admin\Documents\Project -> C--Users--admin--Documents--Project
|
||||
*/
|
||||
private encodeProjectPath(projectPath: string): string {
|
||||
return projectPath
|
||||
.replace(/:/g, '') // 移除冒号
|
||||
.replace(/[/\\]/g, '--'); // 替换斜杠为 --
|
||||
.replace(/\\/g, '--') // 替换反斜杠为 --
|
||||
.replace(/\//g, '--') // 替换正斜杠为 --
|
||||
.replace(/:/g, ''); // 移除冒号
|
||||
}
|
||||
|
||||
/**
|
||||
@ -300,14 +302,15 @@ export class ChatHistoryManager {
|
||||
/**
|
||||
* 添加AI消息
|
||||
*/
|
||||
public async addAiMessage(text: string, toolRequests?: any[]): Promise<void> {
|
||||
public async addAiMessage(text: string, toolRequests?: any[], segments?: any[]): Promise<void> {
|
||||
await this.ensureCurrentTask();
|
||||
const messages = await this.loadConversation();
|
||||
|
||||
const aiMessage: AiMessage = {
|
||||
type: MessageType.AI,
|
||||
text,
|
||||
toolExecutionRequests: toolRequests
|
||||
toolExecutionRequests: toolRequests,
|
||||
segments // 保存完整的 segments 信息
|
||||
};
|
||||
|
||||
messages.push(aiMessage);
|
||||
@ -468,6 +471,20 @@ export class ChatHistoryManager {
|
||||
tasks.push(JSON.parse(data));
|
||||
} catch (error) {
|
||||
console.error(`加载任务 ${taskId} 失败:`, error);
|
||||
// 跳过无效的任务目录
|
||||
// 尝试清理空目录
|
||||
try {
|
||||
const taskDirUri = vscode.Uri.file(path.join(projectDir, taskId));
|
||||
const taskDirEntries = await vscode.workspace.fs.readDirectory(taskDirUri);
|
||||
if (taskDirEntries.length === 0) {
|
||||
// 目录为空,删除它
|
||||
await vscode.workspace.fs.delete(taskDirUri, { recursive: false });
|
||||
console.log(`已清理空任务目录: ${taskId}`);
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
// 清理失败,忽略错误
|
||||
console.warn(`清理任务目录 ${taskId} 失败:`, cleanupError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -512,4 +529,132 @@ export class ChatHistoryManager {
|
||||
public getBaseDir(): string {
|
||||
return this.baseDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载指定任务的会话内容
|
||||
* @param projectPath 项目路径
|
||||
* @param taskId 任务ID
|
||||
* @returns 任务会话内容,如果任务不存在则返回null
|
||||
*/
|
||||
public async loadTaskSession(projectPath: string, taskId: string): Promise<TaskSession | null> {
|
||||
const taskDir = this.getTaskDir(projectPath, taskId);
|
||||
const metaPath = path.join(taskDir, 'meta.json');
|
||||
|
||||
try {
|
||||
// 检查任务是否存在
|
||||
const metaUri = vscode.Uri.file(metaPath);
|
||||
const metaContent = await vscode.workspace.fs.readFile(metaUri);
|
||||
const meta: TaskMeta = JSON.parse(Buffer.from(metaContent).toString('utf-8'));
|
||||
|
||||
// 读取会话内容
|
||||
const conversationPath = path.join(taskDir, 'conversation.json');
|
||||
let messages: ChatMessage[] = [];
|
||||
try {
|
||||
const conversationUri = vscode.Uri.file(conversationPath);
|
||||
const conversationContent = await vscode.workspace.fs.readFile(conversationUri);
|
||||
messages = JSON.parse(Buffer.from(conversationContent).toString('utf-8'));
|
||||
} catch {
|
||||
// 会话文件不存在,使用空数组
|
||||
}
|
||||
|
||||
// 读取会话元数据
|
||||
const conversationMetaPath = path.join(taskDir, 'conversation_meta.jsonl');
|
||||
let conversationMeta: ConversationMeta[] = [];
|
||||
try {
|
||||
const metaUri = vscode.Uri.file(conversationMetaPath);
|
||||
const content = await vscode.workspace.fs.readFile(metaUri);
|
||||
const data = Buffer.from(content).toString('utf-8');
|
||||
conversationMeta = data
|
||||
.split('\n')
|
||||
.filter(line => line.trim())
|
||||
.map(line => JSON.parse(line));
|
||||
} catch {
|
||||
// 元数据文件不存在,使用空数组
|
||||
}
|
||||
|
||||
return {
|
||||
meta,
|
||||
messages,
|
||||
conversationMeta
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`加载任务 ${taskId} 的会话失败:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话历史列表(支持分页)
|
||||
* 返回格式:{ id: taskId, title: 第一句用户消息, timestamp: 创建时间 }
|
||||
* @param projectPath 项目路径
|
||||
* @param offset 偏移量(从第几条开始,默认0)
|
||||
* @param limit 每页数量(默认10条)
|
||||
* @returns { items: 历史列表, total: 总数, hasMore: 是否还有更多 }
|
||||
*/
|
||||
public async getConversationHistoryList(
|
||||
projectPath: string,
|
||||
offset: number = 0,
|
||||
limit: number = 10
|
||||
): Promise<{
|
||||
items: Array<{ id: string; title: string; timestamp: string }>;
|
||||
total: number;
|
||||
hasMore: boolean;
|
||||
}> {
|
||||
const tasks = await this.listProjectTasks(projectPath);
|
||||
const total = tasks.length;
|
||||
const historyList: Array<{ id: string; title: string; timestamp: string }> = [];
|
||||
|
||||
// 计算分页范围
|
||||
const start = offset;
|
||||
const end = Math.min(offset + limit, total);
|
||||
const limitedTasks = tasks.slice(start, end);
|
||||
|
||||
for (const task of limitedTasks) {
|
||||
// 读取该任务的 conversation.json 获取第一句用户消息
|
||||
const taskDir = this.getTaskDir(task.projectPath, task.taskId);
|
||||
const conversationPath = path.join(taskDir, 'conversation.json');
|
||||
|
||||
try {
|
||||
const uri = vscode.Uri.file(conversationPath);
|
||||
const content = await vscode.workspace.fs.readFile(uri);
|
||||
const data = Buffer.from(content).toString('utf-8');
|
||||
const messages: ChatMessage[] = JSON.parse(data);
|
||||
|
||||
// 找到第一条用户消息
|
||||
const firstUserMessage = messages.find(msg => msg.type === MessageType.USER) as UserMessage | undefined;
|
||||
|
||||
let title = '未命名会话';
|
||||
if (firstUserMessage && firstUserMessage.contents && firstUserMessage.contents.length > 0) {
|
||||
const textContent = firstUserMessage.contents.find(c => c.type === 'TEXT');
|
||||
if (textContent && 'text' in textContent) {
|
||||
// 截取前50个字符作为标题
|
||||
title = textContent.text.length > 50
|
||||
? textContent.text.substring(0, 50) + '...'
|
||||
: textContent.text;
|
||||
}
|
||||
}
|
||||
|
||||
historyList.push({
|
||||
id: task.taskId,
|
||||
title,
|
||||
timestamp: task.createdAt
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`读取任务 ${task.taskId} 的会话历史失败:`, error);
|
||||
// 如果读取失败,使用任务名称作为标题
|
||||
historyList.push({
|
||||
id: task.taskId,
|
||||
title: task.taskName || '未命名会话',
|
||||
timestamp: task.createdAt
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 返回分页结果
|
||||
return {
|
||||
items: historyList,
|
||||
total,
|
||||
hasMore: end < total
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,35 +170,16 @@ async function handleUserMessageWithBackend(
|
||||
});
|
||||
console.log('[MessageHandler] postMessage 返回值:', result);
|
||||
|
||||
// 按照 segments 顺序保存AI响应到历史记录
|
||||
// 保存完整的 segments 到历史记录
|
||||
try {
|
||||
// 遍历 segments,按顺序保存每个段落
|
||||
for (const segment of segments) {
|
||||
if (segment.type === 'text' && segment.content) {
|
||||
// 保存文本消息
|
||||
await historyManager.addAiMessage(segment.content);
|
||||
} else if (segment.type === 'tool') {
|
||||
// 保存工具调用请求(作为AI消息的一部分)
|
||||
const toolRequest = {
|
||||
id: segment.askId || `tool_${Date.now()}`,
|
||||
name: segment.toolName || '',
|
||||
arguments: ''
|
||||
};
|
||||
await historyManager.addAiMessage('', [toolRequest]);
|
||||
// 将完整的 segments 保存到一条 AI 消息中
|
||||
// 这样加载时可以完整还原对话样式
|
||||
const textContent = segments
|
||||
.filter(s => s.type === 'text' && s.content)
|
||||
.map(s => s.content)
|
||||
.join('\n');
|
||||
|
||||
// 如果有工具执行结果,保存工具执行结果
|
||||
if (segment.toolResult) {
|
||||
await historyManager.addToolExecutionResult(
|
||||
toolRequest.id,
|
||||
segment.toolName || '',
|
||||
segment.toolResult
|
||||
);
|
||||
}
|
||||
} else if (segment.type === 'question') {
|
||||
// 保存问题(作为AI消息)
|
||||
await historyManager.addAiMessage(segment.question || '');
|
||||
}
|
||||
}
|
||||
await historyManager.addAiMessage(textContent, undefined, segments);
|
||||
} catch (error) {
|
||||
console.warn("保存AI响应历史失败:", error);
|
||||
}
|
||||
|
||||
@ -111,6 +111,10 @@ export function getConversationHistoryBarStyles(): string {
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
border-bottom: 1px solid var(--vscode-panel-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.history-item:last-child {
|
||||
@ -124,15 +128,17 @@ export function getConversationHistoryBarStyles(): string {
|
||||
.history-item-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.history-item-time {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.history-empty {
|
||||
@ -142,6 +148,14 @@ export function getConversationHistoryBarStyles(): string {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.history-load-more {
|
||||
padding: 12px 16px;
|
||||
text-align: center;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 12px;
|
||||
border-top: 1px solid var(--vscode-panel-border);
|
||||
}
|
||||
|
||||
.new-conversation-button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
@ -199,6 +213,12 @@ export function getConversationHistoryBarScript(): string {
|
||||
// 会话历史相关变量
|
||||
let conversationHistory = [];
|
||||
let currentConversationId = null;
|
||||
let currentOffset = 0;
|
||||
let totalHistory = 0;
|
||||
let hasMoreHistory = false;
|
||||
let isLoadingHistory = false;
|
||||
const HISTORY_PAGE_SIZE = 10;
|
||||
const MAX_HISTORY_ITEMS = 100;
|
||||
|
||||
// 切换历史记录下拉菜单
|
||||
function toggleHistoryDropdown() {
|
||||
@ -211,33 +231,90 @@ export function getConversationHistoryBarScript(): string {
|
||||
} else {
|
||||
menu.classList.add('active');
|
||||
button.classList.add('active');
|
||||
// 加载会话历史
|
||||
loadConversationHistory();
|
||||
// 重置并加载会话历史
|
||||
resetAndLoadHistory();
|
||||
}
|
||||
}
|
||||
|
||||
// 加载会话历史
|
||||
function loadConversationHistory() {
|
||||
vscode.postMessage({ command: 'loadConversationHistory' });
|
||||
// 重置并加载会话历史
|
||||
function resetAndLoadHistory() {
|
||||
conversationHistory = [];
|
||||
currentOffset = 0;
|
||||
totalHistory = 0;
|
||||
hasMoreHistory = false;
|
||||
const historyList = document.getElementById('historyList');
|
||||
if (historyList) {
|
||||
historyList.innerHTML = '<div class="history-empty">加载中...</div>';
|
||||
}
|
||||
loadMoreHistory();
|
||||
}
|
||||
|
||||
// 渲染会话历史列表
|
||||
function renderConversationHistory(history) {
|
||||
conversationHistory = history;
|
||||
const historyList = document.getElementById('historyList');
|
||||
// 加载更多会话历史
|
||||
function loadMoreHistory() {
|
||||
if (isLoadingHistory || (currentOffset > 0 && !hasMoreHistory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!history || history.length === 0) {
|
||||
// 检查是否已达到最大数量
|
||||
if (currentOffset >= MAX_HISTORY_ITEMS) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoadingHistory = true;
|
||||
vscode.postMessage({
|
||||
command: 'loadConversationHistory',
|
||||
offset: currentOffset,
|
||||
limit: HISTORY_PAGE_SIZE
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染会话历史列表(支持追加)
|
||||
function renderConversationHistory(data) {
|
||||
isLoadingHistory = false;
|
||||
|
||||
if (!data || !data.items) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 追加新数据
|
||||
conversationHistory = conversationHistory.concat(data.items);
|
||||
totalHistory = data.total;
|
||||
hasMoreHistory = data.hasMore;
|
||||
currentOffset += data.items.length;
|
||||
|
||||
const historyList = document.getElementById('historyList');
|
||||
if (!historyList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果没有任何历史记录
|
||||
if (conversationHistory.length === 0) {
|
||||
historyList.innerHTML = '<div class="history-empty">暂无会话历史</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
historyList.innerHTML = history.map(item => \`
|
||||
<div class="history-item"
|
||||
onclick="selectConversation('\${item.id}')">
|
||||
// 渲染所有历史记录
|
||||
historyList.innerHTML = conversationHistory.map(item => \`
|
||||
<div class="history-item" onclick="selectConversation('\${item.id}')">
|
||||
<div class="history-item-title">\${item.title || '未命名会话'}</div>
|
||||
<div class="history-item-time">\${formatTime(item.timestamp)}</div>
|
||||
</div>
|
||||
\`).join('');
|
||||
|
||||
// 如果还有更多数据,添加"加载更多"提示
|
||||
if (hasMoreHistory && currentOffset < MAX_HISTORY_ITEMS) {
|
||||
historyList.innerHTML += \`
|
||||
<div class="history-load-more" id="loadMoreIndicator">
|
||||
<span>滚动加载更多...</span>
|
||||
</div>
|
||||
\`;
|
||||
} else if (currentOffset >= MAX_HISTORY_ITEMS && hasMoreHistory) {
|
||||
historyList.innerHTML += \`
|
||||
<div class="history-load-more">
|
||||
<span>已显示最近 \${MAX_HISTORY_ITEMS} 条记录</span>
|
||||
</div>
|
||||
\`;
|
||||
}
|
||||
}
|
||||
|
||||
// 选择会话
|
||||
@ -291,6 +368,22 @@ export function getConversationHistoryBarScript(): string {
|
||||
});
|
||||
}
|
||||
|
||||
// 监听下拉菜单滚动事件
|
||||
const historyDropdownMenu = document.getElementById('historyDropdownMenu');
|
||||
if (historyDropdownMenu) {
|
||||
historyDropdownMenu.addEventListener('scroll', () => {
|
||||
const menu = historyDropdownMenu;
|
||||
const scrollTop = menu.scrollTop;
|
||||
const scrollHeight = menu.scrollHeight;
|
||||
const clientHeight = menu.clientHeight;
|
||||
|
||||
// 当滚动到距离底部 50px 时,加载更多
|
||||
if (scrollHeight - scrollTop - clientHeight < 50) {
|
||||
loadMoreHistory();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 点击外部关闭下拉菜单
|
||||
document.addEventListener('click', (event) => {
|
||||
const container = document.querySelector('.history-dropdown-container');
|
||||
|
||||
@ -525,6 +525,37 @@ export function getWebviewContent(iconUri?: string): string {
|
||||
showQuestion(message.askId, message.question, message.options);
|
||||
break;
|
||||
|
||||
case 'conversationHistory':
|
||||
// 渲染会话历史列表(支持分页)
|
||||
renderConversationHistory({
|
||||
items: message.items || [],
|
||||
total: message.total || 0,
|
||||
hasMore: message.hasMore || false
|
||||
});
|
||||
break;
|
||||
|
||||
case 'clearChat':
|
||||
// 清空聊天界面
|
||||
const messagesContainer = document.getElementById('messages');
|
||||
if (messagesContainer) {
|
||||
messagesContainer.innerHTML = '';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addUserMessage':
|
||||
// 添加用户消息
|
||||
if (message.text) {
|
||||
addMessage(message.text, 'user');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addAiMessage':
|
||||
// 添加AI消息
|
||||
if (message.text) {
|
||||
addMessage(message.text, 'bot');
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('[WebView] 未处理的消息类型:', message.command);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user