feat:搭建本地存储会话历史的框架
- 将会话历史存储在C:\Users\admin\.iccoder文件下 - 在里面又会创建多个文件夹进行存储
This commit is contained in:
279
docs/会话历史管理功能文档.md
Normal file
279
docs/会话历史管理功能文档.md
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
# IC Coder 插件 - 会话历史管理功能实现文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本次更新为 IC Coder 插件添加了完整的会话历史管理功能,实现了基于任务(Task)的对话历史持久化存储,采用 LangChain4j 兼容的消息格式。
|
||||||
|
|
||||||
|
## 核心功能
|
||||||
|
|
||||||
|
### 1. 会话历史管理系统
|
||||||
|
|
||||||
|
#### 存储架构
|
||||||
|
- **存储位置**: `~/.iccoder/projects/{项目路径编码}/{taskId}/`
|
||||||
|
- **项目路径编码规则**:
|
||||||
|
- 移除冒号 `:`
|
||||||
|
- 将斜杠 `/` 和反斜杠 `\` 替换为 `--`
|
||||||
|
- 示例: `C:\Users\admin\Documents\Project` → `C--Users-admin-Documents-Project`
|
||||||
|
|
||||||
|
#### 文件结构
|
||||||
|
每个任务目录包含三个文件:
|
||||||
|
```
|
||||||
|
~/.iccoder/projects/{编码后的项目路径}/{taskId}/
|
||||||
|
├── meta.json # 任务元数据
|
||||||
|
├── conversation.json # 对话历史
|
||||||
|
└── conversation_meta.jsonl # 对话轮次元数据(JSONL格式)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 新增类型定义 (`src/types/chatHistory.ts`)
|
||||||
|
|
||||||
|
#### 消息类型
|
||||||
|
- **MessageType 枚举**: `SYSTEM`, `USER`, `AI`, `TOOL_EXECUTION_RESULT`
|
||||||
|
- **消息接口**:
|
||||||
|
- `SystemMessage`: 系统消息
|
||||||
|
- `UserMessage`: 用户消息(支持多内容块)
|
||||||
|
- `AiMessage`: AI 回复(支持工具调用和思考过程)
|
||||||
|
- `ToolExecutionResultMessage`: 工具执行结果
|
||||||
|
|
||||||
|
#### 元数据结构
|
||||||
|
- **TaskMeta**: 任务元数据
|
||||||
|
- 任务 ID、名称、项目路径
|
||||||
|
- 创建/更新时间
|
||||||
|
- Token 使用统计
|
||||||
|
|
||||||
|
- **ConversationMeta**: 对话轮次元数据
|
||||||
|
- 轮次 ID、时间戳
|
||||||
|
- Token 使用量
|
||||||
|
- 模型信息、响应耗时
|
||||||
|
|
||||||
|
### 3. ChatHistoryManager 单例类 (`src/utils/chatHistoryManager.ts`)
|
||||||
|
|
||||||
|
#### 核心方法
|
||||||
|
|
||||||
|
**任务管理**:
|
||||||
|
- `createTask(projectPath, taskName)`: 创建新任务
|
||||||
|
- `switchTask(projectPath, taskId)`: 切换到指定任务
|
||||||
|
- `listProjectTasks(projectPath)`: 列出项目所有任务
|
||||||
|
- `getCurrentTaskSession()`: 获取当前任务完整会话
|
||||||
|
|
||||||
|
**消息记录**:
|
||||||
|
- `addUserMessage(text)`: 添加用户消息
|
||||||
|
- `addAiMessage(text, toolRequests?)`: 添加 AI 消息
|
||||||
|
- `addSystemMessage(text)`: 添加系统消息
|
||||||
|
- `recordTurnMeta(turnId, usage, model, duration)`: 记录对话轮次元数据
|
||||||
|
|
||||||
|
**自动化功能**:
|
||||||
|
- 自动创建存储目录
|
||||||
|
- 首次使用时自动创建默认任务
|
||||||
|
- 自动更新任务时间戳和 Token 统计
|
||||||
|
|
||||||
|
### 4. 集成到消息处理流程 (`src/utils/messageHandler.ts`)
|
||||||
|
|
||||||
|
#### 修改点
|
||||||
|
- 在 `handleUserMessage()` 中自动记录用户消息
|
||||||
|
- 在所有 AI 回复点自动记录 AI 消息
|
||||||
|
- 支持文件操作、VCD 生成等场景的历史记录
|
||||||
|
|
||||||
|
#### 记录场景
|
||||||
|
- 普通对话消息
|
||||||
|
- 文件操作(创建、删除、读取、更新、重命名、替换)
|
||||||
|
- VCD 文件生成流程
|
||||||
|
- 错误消息
|
||||||
|
|
||||||
|
### 5. 插件初始化 (`src/extension.ts`)
|
||||||
|
|
||||||
|
- 导入 `ChatHistoryManager`
|
||||||
|
- 插件激活时自动初始化历史管理器
|
||||||
|
- 为后续命令实现预留接口(已在 `package.json` 中定义)
|
||||||
|
|
||||||
|
## 预留命令(待实现)
|
||||||
|
|
||||||
|
在 `package.json` 中已定义以下命令,但在 `extension.ts` 中暂时注释:
|
||||||
|
- `ic-coder.viewHistory`: 查看会话历史
|
||||||
|
- `ic-coder.newSession`: 新建会话
|
||||||
|
- `ic-coder.exportSession`: 导出当前会话
|
||||||
|
- `ic-coder.deleteSession`: 删除会话
|
||||||
|
- `ic-coder.clearHistory`: 清空会话历史
|
||||||
|
- `ic-coder.searchSession`: 搜索会话
|
||||||
|
|
||||||
|
## 技术特点
|
||||||
|
|
||||||
|
1. **单例模式**: 确保全局唯一的历史管理器实例
|
||||||
|
2. **自动初始化**: 首次使用时自动创建必要的目录和文件
|
||||||
|
3. **LangChain4j 兼容**: 消息格式与 LangChain4j 保持一致
|
||||||
|
4. **跨平台支持**: 使用 VSCode API 处理文件系统,支持 Windows/Linux/macOS
|
||||||
|
5. **错误处理**: 完善的异常捕获和错误提示
|
||||||
|
6. **增量更新**: 使用 JSONL 格式追加对话元数据,避免重写整个文件
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取历史管理器实例
|
||||||
|
const historyManager = ChatHistoryManager.getInstance();
|
||||||
|
|
||||||
|
// 创建新任务
|
||||||
|
await historyManager.createTask('/path/to/project', '实现计数器模块');
|
||||||
|
|
||||||
|
// 记录对话
|
||||||
|
await historyManager.addUserMessage('帮我创建一个计数器');
|
||||||
|
await historyManager.addAiMessage('好的,我来帮你创建计数器模块...');
|
||||||
|
|
||||||
|
// 记录轮次元数据
|
||||||
|
await historyManager.recordTurnMeta(1, {
|
||||||
|
inputTokens: 100,
|
||||||
|
outputTokens: 200,
|
||||||
|
totalTokens: 300
|
||||||
|
}, 'gpt-4', 2.5);
|
||||||
|
|
||||||
|
// 获取当前会话
|
||||||
|
const session = await historyManager.getCurrentTaskSession();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据格式示例
|
||||||
|
|
||||||
|
### meta.json
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"taskId": "task_20231215_abc123",
|
||||||
|
"taskName": "实现计数器模块",
|
||||||
|
"projectPath": "D:\\ICCoderPlugin\\ic-coder",
|
||||||
|
"createdAt": "2023-12-15T10:30:00.000Z",
|
||||||
|
"updatedAt": "2023-12-15T11:45:00.000Z",
|
||||||
|
"stats": {
|
||||||
|
"credits": 0,
|
||||||
|
"totalTokens": 5000,
|
||||||
|
"inputTokens": 2000,
|
||||||
|
"outputTokens": 3000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### conversation.json
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "USER",
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"type": "TEXT",
|
||||||
|
"text": "帮我创建一个计数器"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "AI",
|
||||||
|
"text": "好的,我来帮你创建计数器模块..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### conversation_meta.jsonl
|
||||||
|
```jsonl
|
||||||
|
{"turnId":1,"timestamp":"2023-12-15T10:30:00.000Z","usage":{"inputTokens":100,"outputTokens":200,"totalTokens":300},"model":"gpt-4","duration":2.5}
|
||||||
|
{"turnId":2,"timestamp":"2023-12-15T10:32:00.000Z","usage":{"inputTokens":150,"outputTokens":250,"totalTokens":400},"model":"gpt-4","duration":3.2}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件修改清单
|
||||||
|
|
||||||
|
### 新增文件
|
||||||
|
1. `src/types/chatHistory.ts` - 类型定义文件
|
||||||
|
2. `src/utils/chatHistoryManager.ts` - 会话历史管理器
|
||||||
|
|
||||||
|
### 修改文件
|
||||||
|
1. `src/extension.ts` - 导入 ChatHistoryManager
|
||||||
|
2. `src/utils/messageHandler.ts` - 集成历史记录功能
|
||||||
|
3. `package.json` - 定义会话管理相关命令
|
||||||
|
|
||||||
|
## 关键代码位置
|
||||||
|
|
||||||
|
### 用户消息记录
|
||||||
|
- 文件: `src/utils/messageHandler.ts:29-30`
|
||||||
|
- 代码: `await historyManager.addUserMessage(text);`
|
||||||
|
|
||||||
|
### AI 消息记录
|
||||||
|
- 文件: `src/utils/messageHandler.ts:54`
|
||||||
|
- 代码: `await historyManager.addAiMessage(reply);`
|
||||||
|
|
||||||
|
### 文件操作历史记录
|
||||||
|
- 文件: `src/utils/messageHandler.ts:194, 207, 217, 227, 243, 263`
|
||||||
|
- 各种文件操作后都会记录 AI 消息
|
||||||
|
|
||||||
|
### 任务自动创建
|
||||||
|
- 文件: `src/utils/chatHistoryManager.ts:269-279`
|
||||||
|
- 方法: `ensureCurrentTask()`
|
||||||
|
|
||||||
|
## 后续开发建议
|
||||||
|
|
||||||
|
1. **实现预留的会话管理命令**
|
||||||
|
- 查看历史:展示任务列表和对话内容
|
||||||
|
- 新建会话:创建新任务并切换
|
||||||
|
- 导出会话:支持 Markdown、JSON 等格式
|
||||||
|
- 删除会话:删除指定任务目录
|
||||||
|
- 清空历史:清空所有会话数据
|
||||||
|
- 搜索会话:按关键词搜索对话内容
|
||||||
|
|
||||||
|
2. **增强功能**
|
||||||
|
- 添加会话统计和可视化面板
|
||||||
|
- 支持多任务并行管理
|
||||||
|
- 实现会话备份和恢复功能
|
||||||
|
- 添加会话标签和分类
|
||||||
|
- 支持会话合并和拆分
|
||||||
|
|
||||||
|
3. **性能优化**
|
||||||
|
- 实现会话数据的懒加载
|
||||||
|
- 添加缓存机制减少文件读写
|
||||||
|
- 优化大型会话的加载速度
|
||||||
|
|
||||||
|
4. **用户体验**
|
||||||
|
- 添加会话切换的快捷键
|
||||||
|
- 在状态栏显示当前任务信息
|
||||||
|
- 提供会话导入功能
|
||||||
|
- 支持会话模板
|
||||||
|
|
||||||
|
## 测试建议
|
||||||
|
|
||||||
|
1. **单元测试**
|
||||||
|
- 测试项目路径编码/解码
|
||||||
|
- 测试任务创建和切换
|
||||||
|
- 测试消息添加和读取
|
||||||
|
- 测试元数据更新
|
||||||
|
|
||||||
|
2. **集成测试**
|
||||||
|
- 测试完整的对话流程
|
||||||
|
- 测试文件操作的历史记录
|
||||||
|
- 测试跨会话切换
|
||||||
|
- 测试异常情况处理
|
||||||
|
|
||||||
|
3. **性能测试**
|
||||||
|
- 测试大量消息的读写性能
|
||||||
|
- 测试多任务并发访问
|
||||||
|
- 测试长时间运行的稳定性
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **数据安全**
|
||||||
|
- 会话数据存储在用户主目录,确保权限正确
|
||||||
|
- 敏感信息不应记录到历史中
|
||||||
|
- 定期清理过期会话数据
|
||||||
|
|
||||||
|
2. **兼容性**
|
||||||
|
- 确保跨平台路径处理正确
|
||||||
|
- 注意文件编码问题(统一使用 UTF-8)
|
||||||
|
- 保持与 LangChain4j 格式的兼容性
|
||||||
|
|
||||||
|
3. **错误处理**
|
||||||
|
- 文件系统操作都应有异常捕获
|
||||||
|
- 提供友好的错误提示
|
||||||
|
- 记录详细的错误日志
|
||||||
|
|
||||||
|
## 版本信息
|
||||||
|
|
||||||
|
- **功能版本**: v1.0.0
|
||||||
|
- **实现日期**: 2025-12-15
|
||||||
|
- **兼容版本**: VSCode ^1.107.0
|
||||||
|
- **依赖**: 无额外依赖,仅使用 VSCode API
|
||||||
|
|
||||||
|
## 参考资料
|
||||||
|
|
||||||
|
- [LangChain4j 消息格式](https://docs.langchain4j.dev/)
|
||||||
|
- [VSCode 文件系统 API](https://code.visualstudio.com/api/references/vscode-api#FileSystem)
|
||||||
|
- [JSONL 格式规范](http://jsonlines.org/)
|
||||||
30
package.json
30
package.json
@ -42,6 +42,36 @@
|
|||||||
"command": "ic-coder.openVCDViewer",
|
"command": "ic-coder.openVCDViewer",
|
||||||
"title": "打开 VCD 波形查看器",
|
"title": "打开 VCD 波形查看器",
|
||||||
"category": "IC Coder"
|
"category": "IC Coder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "ic-coder.viewHistory",
|
||||||
|
"title": "查看会话历史",
|
||||||
|
"category": "IC Coder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "ic-coder.newSession",
|
||||||
|
"title": "新建会话",
|
||||||
|
"category": "IC Coder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "ic-coder.exportSession",
|
||||||
|
"title": "导出当前会话",
|
||||||
|
"category": "IC Coder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "ic-coder.deleteSession",
|
||||||
|
"title": "删除会话",
|
||||||
|
"category": "IC Coder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "ic-coder.clearHistory",
|
||||||
|
"title": "清空会话历史",
|
||||||
|
"category": "IC Coder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "ic-coder.searchSession",
|
||||||
|
"title": "搜索会话",
|
||||||
|
"category": "IC Coder"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"viewsContainers": {
|
"viewsContainers": {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import * as vscode from "vscode";
|
|||||||
import { ICViewProvider } from "./views/ICViewProvider";
|
import { ICViewProvider } from "./views/ICViewProvider";
|
||||||
import { showICHelperPanel } from "./panels/ICHelperPanel";
|
import { showICHelperPanel } from "./panels/ICHelperPanel";
|
||||||
import { VCDViewerPanel } from "./panels/VCDViewerPanel";
|
import { VCDViewerPanel } from "./panels/VCDViewerPanel";
|
||||||
|
import { ChatHistoryManager } from "./utils/chatHistoryManager";
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
console.log("🎉 IC Coder 插件已激活!");
|
console.log("🎉 IC Coder 插件已激活!");
|
||||||
@ -50,6 +51,53 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 注册命令:查看会话历史
|
||||||
|
// TODO: 这些命令需要根据新的任务架构重新实现
|
||||||
|
// 暂时注释掉,等待重新实现
|
||||||
|
/*
|
||||||
|
const viewHistoryCommand = vscode.commands.registerCommand(
|
||||||
|
"ic-coder.viewHistory",
|
||||||
|
() => {
|
||||||
|
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const newSessionCommand = vscode.commands.registerCommand(
|
||||||
|
"ic-coder.newSession",
|
||||||
|
() => {
|
||||||
|
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const exportSessionCommand = vscode.commands.registerCommand(
|
||||||
|
"ic-coder.exportSession",
|
||||||
|
() => {
|
||||||
|
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteSessionCommand = vscode.commands.registerCommand(
|
||||||
|
"ic-coder.deleteSession",
|
||||||
|
() => {
|
||||||
|
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearHistoryCommand = vscode.commands.registerCommand(
|
||||||
|
"ic-coder.clearHistory",
|
||||||
|
() => {
|
||||||
|
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const searchSessionCommand = vscode.commands.registerCommand(
|
||||||
|
"ic-coder.searchSession",
|
||||||
|
() => {
|
||||||
|
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
// 注册侧边栏视图
|
// 注册侧边栏视图
|
||||||
const viewProvider = new ICViewProvider(context.extensionUri);
|
const viewProvider = new ICViewProvider(context.extensionUri);
|
||||||
const viewRegistration = vscode.window.registerWebviewViewProvider(
|
const viewRegistration = vscode.window.registerWebviewViewProvider(
|
||||||
@ -62,6 +110,13 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
openPanelCommand,
|
openPanelCommand,
|
||||||
openChatCommand,
|
openChatCommand,
|
||||||
openVCDViewerCommand,
|
openVCDViewerCommand,
|
||||||
|
// TODO: 等待重新实现这些命令
|
||||||
|
// viewHistoryCommand,
|
||||||
|
// newSessionCommand,
|
||||||
|
// exportSessionCommand,
|
||||||
|
// deleteSessionCommand,
|
||||||
|
// clearHistoryCommand,
|
||||||
|
// searchSessionCommand,
|
||||||
viewRegistration
|
viewRegistration
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
115
src/types/chatHistory.ts
Normal file
115
src/types/chatHistory.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* 消息类型枚举(对应 LangChain4j 格式)
|
||||||
|
*/
|
||||||
|
export enum MessageType {
|
||||||
|
SYSTEM = "SYSTEM",
|
||||||
|
USER = "USER",
|
||||||
|
AI = "AI",
|
||||||
|
TOOL_EXECUTION_RESULT = "TOOL_EXECUTION_RESULT"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具执行请求
|
||||||
|
*/
|
||||||
|
export interface ToolExecutionRequest {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
arguments: string; // JSON字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息内容(用于USER消息)
|
||||||
|
*/
|
||||||
|
export interface MessageContent {
|
||||||
|
type: "TEXT";
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础消息接口
|
||||||
|
*/
|
||||||
|
export interface BaseMessage {
|
||||||
|
type: MessageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统消息
|
||||||
|
*/
|
||||||
|
export interface SystemMessage extends BaseMessage {
|
||||||
|
type: MessageType.SYSTEM;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户消息
|
||||||
|
*/
|
||||||
|
export interface UserMessage extends BaseMessage {
|
||||||
|
type: MessageType.USER;
|
||||||
|
contents: MessageContent[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI消息
|
||||||
|
*/
|
||||||
|
export interface AiMessage extends BaseMessage {
|
||||||
|
type: MessageType.AI;
|
||||||
|
text?: string;
|
||||||
|
toolExecutionRequests?: ToolExecutionRequest[];
|
||||||
|
thinking?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具执行结果消息
|
||||||
|
*/
|
||||||
|
export interface ToolExecutionResultMessage extends BaseMessage {
|
||||||
|
type: MessageType.TOOL_EXECUTION_RESULT;
|
||||||
|
id: string;
|
||||||
|
toolName: string;
|
||||||
|
text: string; // JSON字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 联合消息类型
|
||||||
|
*/
|
||||||
|
export type ChatMessage = SystemMessage | UserMessage | AiMessage | ToolExecutionResultMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话轮次元数据
|
||||||
|
*/
|
||||||
|
export interface ConversationMeta {
|
||||||
|
turnId: number;
|
||||||
|
timestamp: string; // ISO 8601格式
|
||||||
|
usage?: {
|
||||||
|
inputTokens?: number;
|
||||||
|
outputTokens?: number;
|
||||||
|
totalTokens?: number;
|
||||||
|
};
|
||||||
|
model?: string;
|
||||||
|
duration?: number; // 响应耗时(秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务元数据(meta.json)
|
||||||
|
*/
|
||||||
|
export interface TaskMeta {
|
||||||
|
taskId: string;
|
||||||
|
taskName: string;
|
||||||
|
projectPath: string;
|
||||||
|
createdAt: string; // ISO 8601格式
|
||||||
|
updatedAt: string; // ISO 8601格式
|
||||||
|
stats: {
|
||||||
|
credits: number;
|
||||||
|
totalTokens: number;
|
||||||
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务会话(包含所有相关数据)
|
||||||
|
*/
|
||||||
|
export interface TaskSession {
|
||||||
|
meta: TaskMeta;
|
||||||
|
messages: ChatMessage[]; // conversation.json的内容
|
||||||
|
conversationMeta: ConversationMeta[]; // conversation_meta.jsonl的内容
|
||||||
|
}
|
||||||
497
src/utils/chatHistoryManager.ts
Normal file
497
src/utils/chatHistoryManager.ts
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
import {
|
||||||
|
ChatMessage,
|
||||||
|
TaskMeta,
|
||||||
|
TaskSession,
|
||||||
|
ConversationMeta,
|
||||||
|
MessageType,
|
||||||
|
UserMessage,
|
||||||
|
AiMessage,
|
||||||
|
SystemMessage
|
||||||
|
} from '../types/chatHistory';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话历史管理器
|
||||||
|
* 按照设计文档实现:~/.iccoder/projects/{项目路径编码}/{taskId}/
|
||||||
|
*/
|
||||||
|
export class ChatHistoryManager {
|
||||||
|
private static instance: ChatHistoryManager;
|
||||||
|
private baseDir: string; // ~/.iccoder
|
||||||
|
private currentTaskId: string | null = null;
|
||||||
|
private currentProjectPath: string | null = null;
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
// 设置存储路径: ~/.iccoder
|
||||||
|
const userHome = process.env.USERPROFILE || process.env.HOME || '';
|
||||||
|
this.baseDir = path.join(userHome, '.iccoder');
|
||||||
|
this.ensureBaseDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目路径编码
|
||||||
|
* 规则:
|
||||||
|
* - 替换 \ 和 / 为 --
|
||||||
|
* - 替换 : 为空
|
||||||
|
* 例如:C:\Users\admin\Documents\Project -> C--Users-admin-Documents-Project
|
||||||
|
*/
|
||||||
|
private encodeProjectPath(projectPath: string): string {
|
||||||
|
return projectPath
|
||||||
|
.replace(/:/g, '') // 移除冒号
|
||||||
|
.replace(/[/\\]/g, '--'); // 替换斜杠为 --
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成任务ID
|
||||||
|
* 格式:task_{date}_{sequence}
|
||||||
|
*/
|
||||||
|
private generateTaskId(): string {
|
||||||
|
const date = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
||||||
|
const sequence = Math.random().toString(36).substr(2, 6);
|
||||||
|
return `task_${date}_${sequence}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取任务目录路径
|
||||||
|
*/
|
||||||
|
private getTaskDir(projectPath: string, taskId: string): string {
|
||||||
|
const encodedPath = this.encodeProjectPath(projectPath);
|
||||||
|
return path.join(this.baseDir, 'projects', encodedPath, taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保基础目录存在
|
||||||
|
*/
|
||||||
|
private async ensureBaseDir(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const uri = vscode.Uri.file(this.baseDir);
|
||||||
|
try {
|
||||||
|
await vscode.workspace.fs.stat(uri);
|
||||||
|
} catch {
|
||||||
|
// 目录不存在,创建它
|
||||||
|
await vscode.workspace.fs.createDirectory(uri);
|
||||||
|
console.log(`创建存储目录: ${this.baseDir}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("创建存储目录失败:", error);
|
||||||
|
vscode.window.showErrorMessage("创建会话历史存储目录失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保任务目录存在
|
||||||
|
*/
|
||||||
|
private async ensureTaskDir(taskDir: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const uri = vscode.Uri.file(taskDir);
|
||||||
|
try {
|
||||||
|
await vscode.workspace.fs.stat(uri);
|
||||||
|
} catch {
|
||||||
|
// 目录不存在,创建它
|
||||||
|
await vscode.workspace.fs.createDirectory(uri);
|
||||||
|
console.log(`创建任务目录: ${taskDir}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("创建任务目录失败:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单例实例
|
||||||
|
*/
|
||||||
|
public static getInstance(): ChatHistoryManager {
|
||||||
|
if (!ChatHistoryManager.instance) {
|
||||||
|
ChatHistoryManager.instance = new ChatHistoryManager();
|
||||||
|
}
|
||||||
|
return ChatHistoryManager.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新任务
|
||||||
|
*/
|
||||||
|
public async createTask(projectPath: string, taskName: string): Promise<TaskMeta> {
|
||||||
|
const taskId = this.generateTaskId();
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
|
||||||
|
const meta: TaskMeta = {
|
||||||
|
taskId,
|
||||||
|
taskName,
|
||||||
|
projectPath,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
stats: {
|
||||||
|
credits: 0,
|
||||||
|
totalTokens: 0,
|
||||||
|
inputTokens: 0,
|
||||||
|
outputTokens: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.currentTaskId = taskId;
|
||||||
|
this.currentProjectPath = projectPath;
|
||||||
|
|
||||||
|
// 创建任务目录
|
||||||
|
const taskDir = this.getTaskDir(projectPath, taskId);
|
||||||
|
await this.ensureTaskDir(taskDir);
|
||||||
|
|
||||||
|
// 保存 meta.json
|
||||||
|
await this.saveTaskMeta(meta);
|
||||||
|
|
||||||
|
// 初始化空的 conversation.json
|
||||||
|
await this.saveConversation([]);
|
||||||
|
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存任务元数据
|
||||||
|
*/
|
||||||
|
private async saveTaskMeta(meta: TaskMeta): Promise<void> {
|
||||||
|
if (!this.currentTaskId || !this.currentProjectPath) {
|
||||||
|
throw new Error("没有当前任务");
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
|
||||||
|
const metaPath = path.join(taskDir, 'meta.json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uri = vscode.Uri.file(metaPath);
|
||||||
|
const content = Buffer.from(JSON.stringify(meta, null, 2), 'utf-8');
|
||||||
|
await vscode.workspace.fs.writeFile(uri, content);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("保存任务元数据失败:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载任务元数据
|
||||||
|
*/
|
||||||
|
private async loadTaskMeta(): Promise<TaskMeta | null> {
|
||||||
|
if (!this.currentTaskId || !this.currentProjectPath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
|
||||||
|
const metaPath = path.join(taskDir, 'meta.json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uri = vscode.Uri.file(metaPath);
|
||||||
|
const content = await vscode.workspace.fs.readFile(uri);
|
||||||
|
const data = Buffer.from(content).toString('utf-8');
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (error) {
|
||||||
|
// 文件不存在或读取失败
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存对话历史(conversation.json)
|
||||||
|
*/
|
||||||
|
private async saveConversation(messages: ChatMessage[]): Promise<void> {
|
||||||
|
if (!this.currentTaskId || !this.currentProjectPath) {
|
||||||
|
throw new Error("没有当前任务");
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
|
||||||
|
const conversationPath = path.join(taskDir, 'conversation.json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uri = vscode.Uri.file(conversationPath);
|
||||||
|
const content = Buffer.from(JSON.stringify(messages, null, 2), 'utf-8');
|
||||||
|
await vscode.workspace.fs.writeFile(uri, content);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("保存对话历史失败:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载对话历史
|
||||||
|
*/
|
||||||
|
private async loadConversation(): Promise<ChatMessage[]> {
|
||||||
|
if (!this.currentTaskId || !this.currentProjectPath) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
|
||||||
|
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');
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (error) {
|
||||||
|
// 文件不存在或读取失败
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追加对话元数据(conversation_meta.jsonl)
|
||||||
|
*/
|
||||||
|
private async appendConversationMeta(meta: ConversationMeta): Promise<void> {
|
||||||
|
if (!this.currentTaskId || !this.currentProjectPath) {
|
||||||
|
throw new Error("没有当前任务");
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
|
||||||
|
const metaPath = path.join(taskDir, 'conversation_meta.jsonl');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uri = vscode.Uri.file(metaPath);
|
||||||
|
const line = JSON.stringify(meta) + '\n';
|
||||||
|
|
||||||
|
// 读取现有内容
|
||||||
|
let existingContent = '';
|
||||||
|
try {
|
||||||
|
const content = await vscode.workspace.fs.readFile(uri);
|
||||||
|
existingContent = Buffer.from(content).toString('utf-8');
|
||||||
|
} catch {
|
||||||
|
// 文件不存在,忽略错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// 追加新内容
|
||||||
|
const newContent = existingContent + line;
|
||||||
|
await vscode.workspace.fs.writeFile(uri, Buffer.from(newContent, 'utf-8'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("追加对话元数据失败:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保有当前任务,如果没有则自动创建
|
||||||
|
*/
|
||||||
|
private async ensureCurrentTask(): Promise<void> {
|
||||||
|
if (!this.currentTaskId || !this.currentProjectPath) {
|
||||||
|
// 获取当前工作区路径
|
||||||
|
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||||
|
if (workspacePath) {
|
||||||
|
await this.createTask(workspacePath, "默认任务");
|
||||||
|
} else {
|
||||||
|
throw new Error("没有打开的工作区,无法创建任务");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加用户消息
|
||||||
|
*/
|
||||||
|
public async addUserMessage(text: string): Promise<void> {
|
||||||
|
await this.ensureCurrentTask();
|
||||||
|
const messages = await this.loadConversation();
|
||||||
|
|
||||||
|
const userMessage: UserMessage = {
|
||||||
|
type: MessageType.USER,
|
||||||
|
contents: [{ type: "TEXT", text }]
|
||||||
|
};
|
||||||
|
|
||||||
|
messages.push(userMessage);
|
||||||
|
await this.saveConversation(messages);
|
||||||
|
|
||||||
|
// 更新任务元数据
|
||||||
|
await this.updateTaskTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加AI消息
|
||||||
|
*/
|
||||||
|
public async addAiMessage(text: string, toolRequests?: any[]): Promise<void> {
|
||||||
|
await this.ensureCurrentTask();
|
||||||
|
const messages = await this.loadConversation();
|
||||||
|
|
||||||
|
const aiMessage: AiMessage = {
|
||||||
|
type: MessageType.AI,
|
||||||
|
text,
|
||||||
|
toolExecutionRequests: toolRequests
|
||||||
|
};
|
||||||
|
|
||||||
|
messages.push(aiMessage);
|
||||||
|
await this.saveConversation(messages);
|
||||||
|
|
||||||
|
// 更新任务元数据
|
||||||
|
await this.updateTaskTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加系统消息
|
||||||
|
*/
|
||||||
|
public async addSystemMessage(text: string): Promise<void> {
|
||||||
|
await this.ensureCurrentTask();
|
||||||
|
const messages = await this.loadConversation();
|
||||||
|
|
||||||
|
const systemMessage: SystemMessage = {
|
||||||
|
type: MessageType.SYSTEM,
|
||||||
|
text
|
||||||
|
};
|
||||||
|
|
||||||
|
messages.push(systemMessage);
|
||||||
|
await this.saveConversation(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录对话轮次元数据
|
||||||
|
*/
|
||||||
|
public async recordTurnMeta(
|
||||||
|
turnId: number,
|
||||||
|
usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number },
|
||||||
|
model?: string,
|
||||||
|
duration?: number
|
||||||
|
): Promise<void> {
|
||||||
|
const meta: ConversationMeta = {
|
||||||
|
turnId,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
usage,
|
||||||
|
model,
|
||||||
|
duration
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.appendConversationMeta(meta);
|
||||||
|
|
||||||
|
// 更新任务统计
|
||||||
|
if (usage) {
|
||||||
|
await this.updateTaskStats(usage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新任务时间戳
|
||||||
|
*/
|
||||||
|
private async updateTaskTimestamp(): Promise<void> {
|
||||||
|
const meta = await this.loadTaskMeta();
|
||||||
|
if (meta) {
|
||||||
|
meta.updatedAt = new Date().toISOString();
|
||||||
|
await this.saveTaskMeta(meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新任务统计
|
||||||
|
*/
|
||||||
|
private async updateTaskStats(usage: { inputTokens?: number; outputTokens?: number; totalTokens?: number }): Promise<void> {
|
||||||
|
const meta = await this.loadTaskMeta();
|
||||||
|
if (meta) {
|
||||||
|
meta.stats.inputTokens += usage.inputTokens || 0;
|
||||||
|
meta.stats.outputTokens += usage.outputTokens || 0;
|
||||||
|
meta.stats.totalTokens += usage.totalTokens || 0;
|
||||||
|
meta.updatedAt = new Date().toISOString();
|
||||||
|
await this.saveTaskMeta(meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前任务会话
|
||||||
|
*/
|
||||||
|
public async getCurrentTaskSession(): Promise<TaskSession | null> {
|
||||||
|
const meta = await this.loadTaskMeta();
|
||||||
|
if (!meta) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = await this.loadConversation();
|
||||||
|
const conversationMeta = await this.loadConversationMeta();
|
||||||
|
|
||||||
|
return {
|
||||||
|
meta,
|
||||||
|
messages,
|
||||||
|
conversationMeta
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载对话元数据
|
||||||
|
*/
|
||||||
|
private async loadConversationMeta(): Promise<ConversationMeta[]> {
|
||||||
|
if (!this.currentTaskId || !this.currentProjectPath) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
|
||||||
|
const metaPath = path.join(taskDir, 'conversation_meta.jsonl');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uri = vscode.Uri.file(metaPath);
|
||||||
|
const content = await vscode.workspace.fs.readFile(uri);
|
||||||
|
const data = Buffer.from(content).toString('utf-8');
|
||||||
|
return data
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => line.trim())
|
||||||
|
.map(line => JSON.parse(line));
|
||||||
|
} catch (error) {
|
||||||
|
// 文件不存在或读取失败
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列出项目的所有任务
|
||||||
|
*/
|
||||||
|
public async listProjectTasks(projectPath: string): Promise<TaskMeta[]> {
|
||||||
|
const encodedPath = this.encodeProjectPath(projectPath);
|
||||||
|
const projectDir = path.join(this.baseDir, 'projects', encodedPath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uri = vscode.Uri.file(projectDir);
|
||||||
|
const entries = await vscode.workspace.fs.readDirectory(uri);
|
||||||
|
|
||||||
|
const tasks: TaskMeta[] = [];
|
||||||
|
|
||||||
|
for (const [taskId, type] of entries) {
|
||||||
|
if (type === vscode.FileType.Directory) {
|
||||||
|
const metaPath = path.join(projectDir, taskId, 'meta.json');
|
||||||
|
try {
|
||||||
|
const metaUri = vscode.Uri.file(metaPath);
|
||||||
|
const content = await vscode.workspace.fs.readFile(metaUri);
|
||||||
|
const data = Buffer.from(content).toString('utf-8');
|
||||||
|
tasks.push(JSON.parse(data));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`加载任务 ${taskId} 失败:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks.sort((a, b) =>
|
||||||
|
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// 目录不存在
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换到指定任务
|
||||||
|
*/
|
||||||
|
public async switchTask(projectPath: string, taskId: string): Promise<boolean> {
|
||||||
|
const taskDir = this.getTaskDir(projectPath, taskId);
|
||||||
|
const metaPath = path.join(taskDir, 'meta.json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uri = vscode.Uri.file(metaPath);
|
||||||
|
await vscode.workspace.fs.stat(uri);
|
||||||
|
this.currentProjectPath = projectPath;
|
||||||
|
this.currentTaskId = taskId;
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前任务ID
|
||||||
|
*/
|
||||||
|
public getCurrentTaskId(): string | null {
|
||||||
|
return this.currentTaskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取基础目录
|
||||||
|
*/
|
||||||
|
public getBaseDir(): string {
|
||||||
|
return this.baseDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ import {
|
|||||||
checkVerilogProject,
|
checkVerilogProject,
|
||||||
checkIverilogAvailable,
|
checkIverilogAvailable,
|
||||||
} from "./iverilogRunner";
|
} from "./iverilogRunner";
|
||||||
|
import { ChatHistoryManager } from "./chatHistoryManager";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理用户消息
|
* 处理用户消息
|
||||||
@ -24,6 +25,10 @@ export async function handleUserMessage(
|
|||||||
) {
|
) {
|
||||||
console.log("收到用户消息:", text);
|
console.log("收到用户消息:", text);
|
||||||
|
|
||||||
|
// 记录用户消息到历史
|
||||||
|
const historyManager = ChatHistoryManager.getInstance();
|
||||||
|
await historyManager.addUserMessage(text);
|
||||||
|
|
||||||
// 检查是否是 VCD 生成命令
|
// 检查是否是 VCD 生成命令
|
||||||
if (isVCDGenerationCommand(text)) {
|
if (isVCDGenerationCommand(text)) {
|
||||||
await handleVCDGeneration(panel, extensionPath || "");
|
await handleVCDGeneration(panel, extensionPath || "");
|
||||||
@ -44,6 +49,10 @@ export async function handleUserMessage(
|
|||||||
// 普通消息处理
|
// 普通消息处理
|
||||||
console.log("作为普通消息处理");
|
console.log("作为普通消息处理");
|
||||||
const reply = getMockReply(text);
|
const reply = getMockReply(text);
|
||||||
|
|
||||||
|
// 记录助手回复到历史
|
||||||
|
await historyManager.addAiMessage(reply);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "receiveMessage",
|
command: "receiveMessage",
|
||||||
@ -166,28 +175,36 @@ async function handleFileOperation(
|
|||||||
replaceText?: string;
|
replaceText?: string;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
const historyManager = ChatHistoryManager.getInstance();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let responseText = "";
|
||||||
|
|
||||||
switch (operation.type) {
|
switch (operation.type) {
|
||||||
case "create":
|
case "create":
|
||||||
await createFile(operation.filePath, operation.content || "");
|
await createFile(operation.filePath, operation.content || "");
|
||||||
|
responseText = `✅ 文件创建成功: ${operation.filePath}`;
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "receiveMessage",
|
command: "receiveMessage",
|
||||||
text: `✅ 文件创建成功: ${operation.filePath}`,
|
text: responseText,
|
||||||
});
|
});
|
||||||
vscode.window.showInformationMessage(
|
vscode.window.showInformationMessage(
|
||||||
`文件创建成功: ${operation.filePath}`
|
`文件创建成功: ${operation.filePath}`
|
||||||
);
|
);
|
||||||
|
await historyManager.addAiMessage(responseText);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "delete":
|
case "delete":
|
||||||
await deleteFile(operation.filePath);
|
await deleteFile(operation.filePath);
|
||||||
|
responseText = `✅ 文件删除成功: ${operation.filePath}`;
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "receiveMessage",
|
command: "receiveMessage",
|
||||||
text: `✅ 文件删除成功: ${operation.filePath}`,
|
text: responseText,
|
||||||
});
|
});
|
||||||
vscode.window.showInformationMessage(
|
vscode.window.showInformationMessage(
|
||||||
`文件删除成功: ${operation.filePath}`
|
`文件删除成功: ${operation.filePath}`
|
||||||
);
|
);
|
||||||
|
await historyManager.addAiMessage(responseText);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "read":
|
case "read":
|
||||||
@ -197,6 +214,7 @@ async function handleFileOperation(
|
|||||||
content: content,
|
content: content,
|
||||||
filePath: operation.filePath,
|
filePath: operation.filePath,
|
||||||
});
|
});
|
||||||
|
await historyManager.addAiMessage(`读取文件: ${operation.filePath}`);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "update":
|
case "update":
|
||||||
@ -206,6 +224,7 @@ async function handleFileOperation(
|
|||||||
content: currentContent,
|
content: currentContent,
|
||||||
filePath: operation.filePath,
|
filePath: operation.filePath,
|
||||||
});
|
});
|
||||||
|
await historyManager.addAiMessage(`编辑文件: ${operation.filePath}`);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "rename":
|
case "rename":
|
||||||
@ -213,13 +232,15 @@ async function handleFileOperation(
|
|||||||
throw new Error("缺少新文件名");
|
throw new Error("缺少新文件名");
|
||||||
}
|
}
|
||||||
await renameFile(operation.filePath, operation.newPath);
|
await renameFile(operation.filePath, operation.newPath);
|
||||||
|
responseText = `✅ 文件重命名成功: ${operation.filePath} → ${operation.newPath}`;
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "receiveMessage",
|
command: "receiveMessage",
|
||||||
text: `✅ 文件重命名成功: ${operation.filePath} → ${operation.newPath}`,
|
text: responseText,
|
||||||
});
|
});
|
||||||
vscode.window.showInformationMessage(
|
vscode.window.showInformationMessage(
|
||||||
`文件重命名成功: ${operation.filePath} → ${operation.newPath}`
|
`文件重命名成功: ${operation.filePath} → ${operation.newPath}`
|
||||||
);
|
);
|
||||||
|
await historyManager.addAiMessage(responseText);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "replace":
|
case "replace":
|
||||||
@ -231,13 +252,15 @@ async function handleFileOperation(
|
|||||||
operation.searchText,
|
operation.searchText,
|
||||||
operation.replaceText
|
operation.replaceText
|
||||||
);
|
);
|
||||||
|
responseText = `✅ 文件内容替换成功: ${operation.filePath}`;
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "receiveMessage",
|
command: "receiveMessage",
|
||||||
text: `✅ 文件内容替换成功: ${operation.filePath}`,
|
text: responseText,
|
||||||
});
|
});
|
||||||
vscode.window.showInformationMessage(
|
vscode.window.showInformationMessage(
|
||||||
`文件内容替换成功: ${operation.filePath}`
|
`文件内容替换成功: ${operation.filePath}`
|
||||||
);
|
);
|
||||||
|
await historyManager.addAiMessage(responseText);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -247,6 +270,7 @@ async function handleFileOperation(
|
|||||||
text: `❌ ${errorMsg}`,
|
text: `❌ ${errorMsg}`,
|
||||||
});
|
});
|
||||||
vscode.window.showErrorMessage(errorMsg);
|
vscode.window.showErrorMessage(errorMsg);
|
||||||
|
await historyManager.addAiMessage(`❌ ${errorMsg}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user