9 Commits

Author SHA1 Message Date
d415d8ee4e feat: 更新后端服务地址为192.168.1.108 2025-12-30 15:02:28 +08:00
bd7a85b705 Merge remote-tracking branch 'origin/feat/Plugin-front-end' into feat/back-to-front 2025-12-30 09:42:23 +08:00
44bbcde5cf feat: 知识图谱工具支持 + 智能体事件处理
- dialogService: 添加智能体 SSE 事件处理
- toolExecutor: 添加 knowledge_save/knowledge_load 工具
- messageArea: 添加智能体消息渲染支持
- 添加 CLAUDE.md 项目配置
2025-12-30 09:40:04 +08:00
d4c726ea9c feat: 添加智能体卡片组件
- 新建 agentCard.ts 智能体卡片UI组件
- webviewContent.ts 集成样式和脚本
2025-12-29 09:22:34 +08:00
082ef923b2 feat: 添加智能体事件类型和SSE处理
- api.ts 添加4个智能体事件类型定义
- sseHandler.ts 添加智能体事件回调和分发
2025-12-29 09:22:26 +08:00
5cb68652f9 fix: file_list 工具现在同时返回文件和目录 2025-12-26 15:40:40 +08:00
9bfa774336 feat: 添加 knowledge_save/load 工具
- api.ts: 添加 knowledge_save/load 类型定义
- toolExecutor.ts: 实现知识图谱保存和加载功能
2025-12-26 11:41:38 +08:00
009da59d38 Merge remote-tracking branch 'origin/feat/Plugin-front-end' into feat/back-to-front 2025-12-25 16:18:05 +08:00
5ea5ddba6e feat: 添加 file_delete 工具支持 2025-12-25 16:17:31 +08:00
17 changed files with 937 additions and 20 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

9
.idea/IC-Coder-Plugin.iml generated Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

16
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,16 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
</JSCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="ENABLE_JAVADOC_FORMATTING" value="false" />
</JavaCodeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

6
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/IC-Coder-Plugin.iml" filepath="$PROJECT_DIR$/.idea/IC-Coder-Plugin.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

90
CLAUDE.md Normal file
View File

@ -0,0 +1,90 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
IC Coder Plugin 是一个 VS Code 扩展,为 Verilog/FPGA 开发提供智能辅助功能包括代码生成、文件操作、iverilog 仿真和 VCD 波形查看。
## Build Commands
```bash
# 安装依赖
pnpm install
# 编译 (开发模式)
pnpm run compile
# 监听模式编译
pnpm run watch
# 生产环境打包
pnpm run package
# 代码检查
pnpm run lint
# 运行测试
pnpm run test
# 编译测试文件
pnpm run compile-tests
```
## Development
- 按 F5 在 VS Code 中启动调试模式
- 使用 webpack 打包,入口文件为 `src/extension.ts`
- 输出目录为 `dist/`
## Architecture
```
src/
├── extension.ts # 插件入口,注册命令和视图
├── panels/
│ ├── ICHelperPanel.ts # 主聊天面板 (WebviewPanel)
│ └── VCDViewerPanel.ts # VCD 波形查看器面板
├── views/
│ ├── ICViewProvider.ts # 侧边栏视图提供者
│ └── webviewContent.ts # Webview HTML 内容 (大文件,使用搜索)
├── utils/
│ ├── messageHandler.ts # 消息处理核心逻辑 (大文件,使用搜索)
│ ├── iverilogRunner.ts # iverilog 编译和仿真执行
│ ├── chatHistoryManager.ts # 会话历史管理
│ ├── createFiles.ts # 文件创建工具
│ └── readFiles.ts # 文件读取工具
├── types/
│ └── chatHistory.ts # 消息类型定义 (LangChain4j 格式)
└── test/ # 测试文件
```
### Key Components
**消息流程**: Webview -> `onDidReceiveMessage` -> `messageHandler.ts` -> 后端处理 -> `panel.webview.postMessage` -> Webview
**消息类型** (`src/types/chatHistory.ts`):
- `MessageType.SYSTEM` / `USER` / `AI` / `TOOL_EXECUTION_RESULT`
- 兼容 LangChain4j 格式
**iverilog 集成** (`tools/iverilog/`):
- 内置 Windows x64 版本的 iverilog/vvp
- 通过 `IVERILOG_ROOT` 环境变量配置库路径
- 支持命令: "生成 VCD"、"运行仿真"、"生成波形"
## VS Code Extension Points
- 命令: `ic-coder.openPanel`, `ic-coder.openChat`, `ic-coder.openVCDViewer`
- 侧边栏视图: `ic-coder.mainView`
- 激活事件: `onLanguage:verilog`, `onLanguage:vhdl`, `onStartupFinished`
## Dependencies
- `vcdrom`, `vcd-stream`, `waveql` - VCD 波形处理
- `@wavedrom/doppler`, `onml` - 波形渲染
- `iconv-lite` - 编码转换
## Notes
- `webviewContent.ts``messageHandler.ts` 文件较大,建议使用搜索而非完整读取
- 当前仅支持 Windows 平台的 iverilog其他平台需用户自行安装

View File

@ -13,7 +13,7 @@ import type { DialogRequest, ToolCallRequest, AskUserEvent } from '../types/api'
* 消息段落类型 * 消息段落类型
*/ */
export interface MessageSegment { export interface MessageSegment {
type: 'text' | 'tool' | 'question'; type: 'text' | 'tool' | 'question' | 'agent';
content?: string; content?: string;
toolName?: string; toolName?: string;
toolStatus?: 'running' | 'success' | 'error'; toolStatus?: 'running' | 'success' | 'error';
@ -21,6 +21,22 @@ export interface MessageSegment {
askId?: string; askId?: string;
question?: string; question?: string;
options?: string[]; options?: string[];
// 智能体相关字段
agentId?: string;
agentName?: string;
agentStatus?: 'running' | 'completed' | 'error';
agentSteps?: AgentStep[];
}
/**
* 智能体执行步骤
*/
export interface AgentStep {
step: number;
toolName: string;
toolInput?: unknown;
toolResult?: string;
status: 'running' | 'completed' | 'error';
} }
/** /**
@ -164,28 +180,42 @@ export class DialogSession {
onToolCall: async (data: ToolCallRequest) => { onToolCall: async (data: ToolCallRequest) => {
const toolName = data.params.name; const toolName = data.params.name;
console.log('[DialogSession] onToolCall:', toolName); console.log('[DialogSession] onToolCall:', toolName);
// 检查是否已经有相同的工具段落(可能由 onToolStart 添加)
const lastToolSegment = this.segments.filter(s => s.type === 'tool').pop(); // 检查是否有活跃的智能体(如果有,工具执行会显示在智能体卡片内,不需要单独显示)
if (lastToolSegment && lastToolSegment.toolName === toolName && lastToolSegment.toolStatus === 'running') { const hasActiveAgent = this.segments.some(
console.log('[DialogSession] onToolCall: 跳过重复的工具段落:', toolName); s => s.type === 'agent' && s.agentStatus === 'running'
);
if (hasActiveAgent) {
console.log('[DialogSession] onToolCall: 智能体执行中,跳过工具段落:', toolName);
} else { } else {
this.addToolSegment(toolName, 'running'); // 检查是否已经有相同的工具段落(可能由 onToolStart 添加)
// 实时发送段落更新 const lastToolSegment = this.segments.filter(s => s.type === 'tool').pop();
callbacks.onSegmentUpdate?.(this.segments); if (lastToolSegment && lastToolSegment.toolName === toolName && lastToolSegment.toolStatus === 'running') {
console.log('[DialogSession] onToolCall: 跳过重复的工具段落:', toolName);
} else {
this.addToolSegment(toolName, 'running');
// 实时发送段落更新
callbacks.onSegmentUpdate?.(this.segments);
}
} }
// 注意:不在这里调用 callbacks.onToolStart避免与 onToolStart 事件重复 // 注意:不在这里调用 callbacks.onToolStart避免与 onToolStart 事件重复
try { try {
await executeToolCall(data, this.toolContext); await executeToolCall(data, this.toolContext);
this.updateToolSegment(toolName, 'success', '执行完成'); if (!hasActiveAgent) {
// 实时发送段落更新 this.updateToolSegment(toolName, 'success', '执行完成');
callbacks.onSegmentUpdate?.(this.segments); // 实时发送段落更新
callbacks.onSegmentUpdate?.(this.segments);
}
// 也不调用 callbacks.onToolComplete避免重复 // 也不调用 callbacks.onToolComplete避免重复
} catch (error) { } catch (error) {
const errorMsg = error instanceof Error ? error.message : '未知错误'; const errorMsg = error instanceof Error ? error.message : '未知错误';
this.updateToolSegment(toolName, 'error', errorMsg); if (!hasActiveAgent) {
this.updateToolSegment(toolName, 'error', errorMsg);
callbacks.onSegmentUpdate?.(this.segments);
}
callbacks.onToolError?.(toolName, errorMsg); callbacks.onToolError?.(toolName, errorMsg);
// 实时发送段落更新
callbacks.onSegmentUpdate?.(this.segments);
} }
}, },
@ -257,6 +287,69 @@ export class DialogSession {
callbacks.onNotification?.(data.message); callbacks.onNotification?.(data.message);
}, },
// 智能体事件处理
onAgentStart: (data) => {
console.log('[DialogSession] onAgentStart:', data.agentId);
this.finalizeTextSegment();
this.segments.push({
type: 'agent',
agentId: data.agentId,
agentName: data.agentName,
content: data.instruction,
agentStatus: 'running',
agentSteps: []
});
callbacks.onSegmentUpdate?.(this.segments);
},
onAgentProgress: (data) => {
console.log('[DialogSession] onAgentProgress:', data.agentId, data.step, data.status);
const agentSegment = this.segments.find(
s => s.type === 'agent' && s.agentId === data.agentId
);
if (agentSegment && agentSegment.agentSteps) {
if (data.status === 'running') {
agentSegment.agentSteps.push({
step: data.step,
toolName: data.toolName,
toolInput: data.toolInput,
status: 'running'
});
} else {
const step = agentSegment.agentSteps.find(s => s.step === data.step);
if (step) {
step.status = data.status;
step.toolResult = data.toolResult;
}
}
callbacks.onSegmentUpdate?.(this.segments);
}
},
onAgentComplete: (data) => {
console.log('[DialogSession] onAgentComplete:', data.agentId);
const agentSegment = this.segments.find(
s => s.type === 'agent' && s.agentId === data.agentId
);
if (agentSegment) {
agentSegment.agentStatus = 'completed';
agentSegment.content = data.summary;
callbacks.onSegmentUpdate?.(this.segments);
}
},
onAgentError: (data) => {
console.log('[DialogSession] onAgentError:', data.agentId, data.error);
const agentSegment = this.segments.find(
s => s.type === 'agent' && s.agentId === data.agentId
);
if (agentSegment) {
agentSegment.agentStatus = 'error';
agentSegment.content = data.error;
callbacks.onSegmentUpdate?.(this.segments);
}
},
onOpen: () => { onOpen: () => {
console.log('[DialogSession] SSE 连接已建立'); console.log('[DialogSession] SSE 连接已建立');
}, },

View File

@ -21,7 +21,11 @@ import type {
ToolErrorEvent, ToolErrorEvent,
WarningEvent, WarningEvent,
NotificationEvent, NotificationEvent,
DepthUpdateEvent DepthUpdateEvent,
AgentStartEvent,
AgentProgressEvent,
AgentCompleteEvent,
AgentErrorEvent
} from '../types/api'; } from '../types/api';
/** /**
@ -50,6 +54,14 @@ export interface SSECallbacks {
onNotification?: (data: NotificationEvent) => void; onNotification?: (data: NotificationEvent) => void;
/** 深度更新 */ /** 深度更新 */
onDepthUpdate?: (data: DepthUpdateEvent) => void; onDepthUpdate?: (data: DepthUpdateEvent) => void;
/** 子智能体启动 */
onAgentStart?: (data: AgentStartEvent) => void;
/** 子智能体进度 */
onAgentProgress?: (data: AgentProgressEvent) => void;
/** 子智能体完成 */
onAgentComplete?: (data: AgentCompleteEvent) => void;
/** 子智能体错误 */
onAgentError?: (data: AgentErrorEvent) => void;
/** 连接打开 */ /** 连接打开 */
onOpen?: () => void; onOpen?: () => void;
/** 连接关闭 */ /** 连接关闭 */
@ -283,6 +295,18 @@ function dispatchEvent(
case 'depth_update': case 'depth_update':
callbacks.onDepthUpdate?.(data as DepthUpdateEvent); callbacks.onDepthUpdate?.(data as DepthUpdateEvent);
break; break;
case 'agent_start':
callbacks.onAgentStart?.(data as AgentStartEvent);
break;
case 'agent_progress':
callbacks.onAgentProgress?.(data as AgentProgressEvent);
break;
case 'agent_complete':
callbacks.onAgentComplete?.(data as AgentCompleteEvent);
break;
case 'agent_error':
callbacks.onAgentError?.(data as AgentErrorEvent);
break;
default: default:
console.log(`[SSE] 未知事件类型: ${eventType}`, data); console.log(`[SSE] 未知事件类型: ${eventType}`, data);
} }

View File

@ -20,10 +20,13 @@ import type {
ToolName, ToolName,
FileReadArgs, FileReadArgs,
FileWriteArgs, FileWriteArgs,
FileDeleteArgs,
FileListArgs, FileListArgs,
SyntaxCheckArgs, SyntaxCheckArgs,
SimulationArgs, SimulationArgs,
WaveformSummaryArgs WaveformSummaryArgs,
KnowledgeSaveArgs,
KnowledgeLoadArgs
} from '../types/api'; } from '../types/api';
/** /**
@ -61,6 +64,9 @@ export async function executeToolCall(
case 'file_write': case 'file_write':
resultText = await executeFileWrite(args as unknown as FileWriteArgs); resultText = await executeFileWrite(args as unknown as FileWriteArgs);
break; break;
case 'file_delete':
resultText = await executeFileDelete(args as unknown as FileDeleteArgs);
break;
case 'file_list': case 'file_list':
resultText = await executeFileList(args as unknown as FileListArgs); resultText = await executeFileList(args as unknown as FileListArgs);
break; break;
@ -73,6 +79,12 @@ export async function executeToolCall(
case 'waveform_summary': case 'waveform_summary':
resultText = await executeWaveformSummary(args as unknown as WaveformSummaryArgs); resultText = await executeWaveformSummary(args as unknown as WaveformSummaryArgs);
break; break;
case 'knowledge_save':
resultText = await executeKnowledgeSave(args as unknown as KnowledgeSaveArgs);
break;
case 'knowledge_load':
resultText = await executeKnowledgeLoad();
break;
default: default:
throw new Error(`未知工具: ${toolName}`); throw new Error(`未知工具: ${toolName}`);
} }
@ -105,9 +117,59 @@ async function executeFileRead(args: FileReadArgs): Promise<string> {
*/ */
async function executeFileWrite(args: FileWriteArgs): Promise<string> { async function executeFileWrite(args: FileWriteArgs): Promise<string> {
await createOrOverwriteFile(args.path, args.content); await createOrOverwriteFile(args.path, args.content);
// Verilog 文件添加知识图谱提示
const isVerilogFile = args.path.endsWith('.v') || args.path.endsWith('.sv');
if (isVerilogFile) {
return `文件已写入: ${args.path}\n\n[提示] 如有新信号或规则,请更新知识图谱`;
}
return `文件已写入: ${args.path}`; return `文件已写入: ${args.path}`;
} }
/**
* 执行 file_delete 工具
* 删除指定路径的文件
*/
async function executeFileDelete(args: FileDeleteArgs): Promise<string> {
const filePath = args.path;
// 获取工作区路径
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
throw new Error('请先打开一个工作区');
}
const workspacePath = workspaceFolders[0].uri.fsPath;
// 解析文件路径(支持相对路径和绝对路径)
const absolutePath = path.isAbsolute(filePath)
? filePath
: path.join(workspacePath, filePath);
// 检查文件是否存在
if (!fs.existsSync(absolutePath)) {
throw new Error(`文件不存在: ${filePath}`);
}
// 检查是否为文件(不允许删除目录)
const stat = fs.statSync(absolutePath);
if (stat.isDirectory()) {
throw new Error(`不能删除目录,请指定文件路径: ${filePath}`);
}
// 删除文件
fs.unlinkSync(absolutePath);
// Verilog 文件添加知识图谱提示
const isVerilogFile = filePath.endsWith('.v') || filePath.endsWith('.sv');
if (isVerilogFile) {
return `文件已删除: ${filePath}\n\n[提示] 请删除知识图谱中相关节点`;
}
return `文件已删除: ${filePath}`;
}
/** /**
* 执行 file_list 工具 * 执行 file_list 工具
*/ */
@ -246,6 +308,53 @@ async function executeWaveformSummary(args: WaveformSummaryArgs): Promise<string
return `波形分析功能暂未实现。\n请求参数:\n- VCD文件: ${args.vcdPath}\n- 信号: ${args.signals}\n- 检查点: ${args.checkpoints || '无'}`; return `波形分析功能暂未实现。\n请求参数:\n- VCD文件: ${args.vcdPath}\n- 信号: ${args.signals}\n- 检查点: ${args.checkpoints || '无'}`;
} }
/**
* 执行 knowledge_save 工具
* 保存知识图谱到 .iccoder/knowledge.json
*/
async function executeKnowledgeSave(args: KnowledgeSaveArgs): Promise<string> {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
throw new Error('请先打开一个工作区');
}
const workspacePath = workspaceFolders[0].uri.fsPath;
const iccoderDir = path.join(workspacePath, '.iccoder');
const knowledgePath = path.join(iccoderDir, 'knowledge.json');
// 确保 .iccoder 目录存在
if (!fs.existsSync(iccoderDir)) {
fs.mkdirSync(iccoderDir, { recursive: true });
}
// 写入知识图谱
fs.writeFileSync(knowledgePath, args.data, 'utf-8');
return `知识图谱已保存: .iccoder/knowledge.json`;
}
/**
* 执行 knowledge_load 工具
* 从 .iccoder/knowledge.json 加载知识图谱
*/
async function executeKnowledgeLoad(): Promise<string> {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
throw new Error('请先打开一个工作区');
}
const workspacePath = workspaceFolders[0].uri.fsPath;
const knowledgePath = path.join(workspacePath, '.iccoder', 'knowledge.json');
// 如果文件不存在,返回空图谱
if (!fs.existsSync(knowledgePath)) {
return JSON.stringify({ directed: true, nodes: [], links: [] });
}
const content = fs.readFileSync(knowledgePath, 'utf-8');
return content;
}
/** /**
* 获取 iverilog 路径 * 获取 iverilog 路径
*/ */

View File

@ -30,6 +30,10 @@ export type SSEEventType =
| 'tool_complete' // 工具执行完成 | 'tool_complete' // 工具执行完成
| 'tool_error' // 工具执行错误 | 'tool_error' // 工具执行错误
| 'ask_user' // 向用户提问 | 'ask_user' // 向用户提问
| 'agent_start' // 子智能体启动
| 'agent_progress' // 子智能体进度
| 'agent_complete' // 子智能体完成
| 'agent_error' // 子智能体错误
| 'complete' // 对话完成 | 'complete' // 对话完成
| 'error' // 错误 | 'error' // 错误
| 'warning' // 警告 | 'warning' // 警告
@ -92,6 +96,45 @@ export interface DepthUpdateEvent {
depth: number; depth: number;
} }
// ============== 智能体事件类型 ==============
/** agent_start 事件数据 */
export interface AgentStartEvent {
agentId: string;
agentType: string;
agentName: string;
instruction: string;
timestamp: number;
}
/** agent_progress 事件数据 */
export interface AgentProgressEvent {
agentId: string;
step: number;
toolName: string;
toolInput?: unknown;
toolResult?: string;
status: 'running' | 'completed' | 'error';
timestamp: number;
}
/** agent_complete 事件数据 */
export interface AgentCompleteEvent {
agentId: string;
agentType: string;
summary: string;
stats: Record<string, unknown>;
timestamp: number;
}
/** agent_error 事件数据 */
export interface AgentErrorEvent {
agentId: string;
agentType: string;
error: string;
timestamp: number;
}
// ============== 工具调用协议 (MCP 格式) ============== // ============== 工具调用协议 (MCP 格式) ==============
/** /**
@ -192,10 +235,13 @@ export interface ToolResultResponse {
export type ToolName = export type ToolName =
| 'file_read' | 'file_read'
| 'file_write' | 'file_write'
| 'file_delete'
| 'file_list' | 'file_list'
| 'syntax_check' | 'syntax_check'
| 'simulation' | 'simulation'
| 'waveform_summary'; | 'waveform_summary'
| 'knowledge_save'
| 'knowledge_load';
/** file_read 工具参数 */ /** file_read 工具参数 */
export interface FileReadArgs { export interface FileReadArgs {
@ -208,6 +254,12 @@ export interface FileWriteArgs {
content: string; content: string;
} }
/** file_delete 工具参数 */
export interface FileDeleteArgs {
/** 要删除的文件路径 */
path: string;
}
/** file_list 工具参数 */ /** file_list 工具参数 */
export interface FileListArgs { export interface FileListArgs {
path?: string; path?: string;
@ -233,11 +285,25 @@ export interface WaveformSummaryArgs {
checkpoints?: string; checkpoints?: string;
} }
/** knowledge_save 工具参数 */
export interface KnowledgeSaveArgs {
/** 知识图谱 JSON 数据 */
data: string;
}
/** knowledge_load 工具参数 */
export interface KnowledgeLoadArgs {
// 无参数,直接读取 .iccoder/knowledge.json
}
/** 工具参数联合类型 */ /** 工具参数联合类型 */
export type ToolArgs = export type ToolArgs =
| FileReadArgs | FileReadArgs
| FileWriteArgs | FileWriteArgs
| FileDeleteArgs
| FileListArgs | FileListArgs
| SyntaxCheckArgs | SyntaxCheckArgs
| SimulationArgs | SimulationArgs
| WaveformSummaryArgs; | WaveformSummaryArgs
| KnowledgeSaveArgs
| KnowledgeLoadArgs;

View File

@ -94,6 +94,13 @@ export async function readDirectory(
const results = []; const results = [];
for (const [fileName, fileType] of entries) { for (const [fileName, fileType] of entries) {
// 处理目录
if (fileType === vscode.FileType.Directory) {
results.push({ path: fileName + '/', content: '[目录]', isDirectory: true });
continue;
}
// 处理文件
if (fileType === vscode.FileType.File) { if (fileType === vscode.FileType.File) {
// 如果指定了扩展名过滤 // 如果指定了扩展名过滤
if (extensions && extensions.length > 0) { if (extensions && extensions.length > 0) {
@ -108,7 +115,7 @@ export async function readDirectory(
const fileUri = vscode.Uri.file(filePath); const fileUri = vscode.Uri.file(filePath);
const contentBytes = await vscode.workspace.fs.readFile(fileUri); const contentBytes = await vscode.workspace.fs.readFile(fileUri);
const content = Buffer.from(contentBytes).toString("utf-8"); const content = Buffer.from(contentBytes).toString("utf-8");
results.push({ path: fileName, content }); results.push({ path: fileName, content, isDirectory: false });
} catch (error) { } catch (error) {
// 跳过无法读取的文件 // 跳过无法读取的文件
continue; continue;

311
src/views/agentCard.ts Normal file
View File

@ -0,0 +1,311 @@
/**
* 智能体卡片组件
*
* 功能说明:
* - 显示子智能体的执行过程
* - 支持展开/收起步骤详情
* - 显示执行状态和统计信息
*/
import type {
AgentStartEvent,
AgentProgressEvent,
AgentCompleteEvent,
AgentErrorEvent
} from '../types/api';
/**
* 获取智能体卡片样式
*/
export function getAgentCardStyles(): string {
return `
.agent-card {
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-input-border);
border-radius: 8px;
margin: 10px 0;
overflow: hidden;
}
.agent-card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: var(--vscode-sideBar-background);
cursor: pointer;
user-select: none;
}
.agent-card-header:hover {
background: var(--vscode-list-hoverBackground);
}
.agent-card-title {
display: flex;
align-items: center;
gap: 8px;
font-weight: 500;
}
.agent-card-icon {
font-size: 16px;
}
.agent-card-status {
font-size: 12px;
padding: 2px 6px;
border-radius: 4px;
}
.agent-card-status.running {
background: var(--vscode-inputValidation-infoBackground);
color: var(--vscode-inputValidation-infoForeground);
}
.agent-card-status.completed {
background: var(--vscode-testing-iconPassed);
color: white;
}
.agent-card-status.error {
background: var(--vscode-testing-iconFailed);
color: white;
}
.agent-card-toggle {
font-size: 12px;
color: var(--vscode-descriptionForeground);
}
.agent-card-body {
padding: 12px;
border-top: 1px solid var(--vscode-input-border);
}
.agent-card-body.collapsed {
display: none;
}
.agent-card-instruction {
font-size: 13px;
color: var(--vscode-descriptionForeground);
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px dashed var(--vscode-input-border);
}
.agent-steps {
font-size: 13px;
}
.agent-step {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 6px 0;
border-left: 2px solid var(--vscode-input-border);
padding-left: 12px;
margin-left: 6px;
}
.agent-step:last-child {
border-left-color: transparent;
}
.agent-step-icon {
flex-shrink: 0;
}
.agent-step-content {
flex: 1;
min-width: 0;
}
.agent-step-name {
font-weight: 500;
}
.agent-step-result {
font-size: 12px;
color: var(--vscode-descriptionForeground);
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.agent-card-summary {
font-size: 13px;
padding: 8px 12px;
background: var(--vscode-sideBar-background);
border-top: 1px solid var(--vscode-input-border);
}
.agent-card-error {
color: var(--vscode-errorForeground);
padding: 8px 12px;
background: var(--vscode-inputValidation-errorBackground);
}
`;
}
/**
* 渲染智能体卡片(启动状态)
*/
export function renderAgentCardStart(event: AgentStartEvent): string {
return `
<div class="agent-card" id="agent-${event.agentId}">
<div class="agent-card-header" onclick="toggleAgentCard('${event.agentId}')">
<div class="agent-card-title">
<span class="agent-card-icon">🤖</span>
<span>${event.agentName}</span>
<span class="agent-card-status running">执行中</span>
</div>
<span class="agent-card-toggle">▼</span>
</div>
<div class="agent-card-body" id="agent-body-${event.agentId}">
<div class="agent-card-instruction">指令:${escapeHtml(event.instruction)}</div>
<div class="agent-steps" id="agent-steps-${event.agentId}">
<!-- 步骤将动态添加 -->
</div>
</div>
</div>
`;
}
/**
* 渲染步骤项(进行中)
*/
export function renderAgentStepRunning(event: AgentProgressEvent): string {
const inputStr = event.toolInput ? JSON.stringify(event.toolInput) : '';
const shortInput = inputStr.length > 50 ? inputStr.substring(0, 50) + '...' : inputStr;
return `
<div class="agent-step" id="agent-step-${event.agentId}-${event.step}">
<span class="agent-step-icon">🔄</span>
<div class="agent-step-content">
<div class="agent-step-name">${event.toolName}</div>
<div class="agent-step-result">${escapeHtml(shortInput)}</div>
</div>
</div>
`;
}
/**
* 更新步骤项(完成)
*/
export function getStepCompleteUpdate(event: AgentProgressEvent): { icon: string; result: string } {
const result = event.toolResult || '';
const shortResult = result.length > 80 ? result.substring(0, 80) + '...' : result;
return {
icon: event.status === 'completed' ? '✅' : '❌',
result: shortResult
};
}
/**
* 获取智能体卡片脚本
*/
export function getAgentCardScript(): string {
return `
// 智能体状态存储
const agentStates = {};
// 切换智能体卡片展开/收起
function toggleAgentCard(agentId) {
const body = document.getElementById('agent-body-' + agentId);
const header = body?.previousElementSibling;
const toggle = header?.querySelector('.agent-card-toggle');
if (body && toggle) {
body.classList.toggle('collapsed');
toggle.textContent = body.classList.contains('collapsed') ? '▶' : '▼';
}
}
// 处理智能体启动事件
function handleAgentStart(event) {
agentStates[event.agentId] = {
status: 'running',
steps: []
};
// 在当前消息中添加智能体卡片
const currentMessage = document.querySelector('.bot-message:last-child .message-content');
if (currentMessage) {
currentMessage.insertAdjacentHTML('beforeend', renderAgentCardStart(event));
}
}
// 处理智能体进度事件
function handleAgentProgress(event) {
const stepsContainer = document.getElementById('agent-steps-' + event.agentId);
if (!stepsContainer) return;
if (event.status === 'running') {
// 添加新步骤
stepsContainer.insertAdjacentHTML('beforeend', renderAgentStepRunning(event));
} else if (event.status === 'completed') {
// 更新步骤状态
const stepEl = document.getElementById('agent-step-' + event.agentId + '-' + event.step);
if (stepEl) {
const iconEl = stepEl.querySelector('.agent-step-icon');
const resultEl = stepEl.querySelector('.agent-step-result');
if (iconEl) iconEl.textContent = '✅';
if (resultEl) {
const result = event.toolResult || '';
resultEl.textContent = result.length > 80 ? result.substring(0, 80) + '...' : result;
}
}
}
}
// 处理智能体完成事件
function handleAgentComplete(event) {
const card = document.getElementById('agent-' + event.agentId);
if (!card) return;
// 更新状态
const statusEl = card.querySelector('.agent-card-status');
if (statusEl) {
statusEl.className = 'agent-card-status completed';
statusEl.textContent = '完成';
}
// 添加摘要
const body = card.querySelector('.agent-card-body');
if (body) {
body.insertAdjacentHTML('beforeend',
'<div class="agent-card-summary">' + escapeHtml(event.summary) + '</div>'
);
}
// 自动收起
body?.classList.add('collapsed');
const toggle = card.querySelector('.agent-card-toggle');
if (toggle) toggle.textContent = '▶';
}
// 处理智能体错误事件
function handleAgentError(event) {
const card = document.getElementById('agent-' + event.agentId);
if (!card) return;
// 更新状态
const statusEl = card.querySelector('.agent-card-status');
if (statusEl) {
statusEl.className = 'agent-card-status error';
statusEl.textContent = '错误';
}
// 添加错误信息
const body = card.querySelector('.agent-card-body');
if (body) {
body.insertAdjacentHTML('beforeend',
'<div class="agent-card-error">错误:' + escapeHtml(event.error) + '</div>'
);
}
}
// HTML 转义
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
`;
}
/**
* HTML 转义(服务端使用)
*/
function escapeHtml(text: string): string {
if (!text) return '';
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}

View File

@ -528,6 +528,89 @@ export function getMessageAreaStyles(): string {
border-radius: 4px; border-radius: 4px;
font-size: 12px;} font-size: 12px;}
/* 智能体卡片样式 */
.segment-agent {
margin: 8px 0;
}
.agent-card {
border: 1px solid var(--vscode-input-border);
border-radius: 8px;
overflow: hidden;
background: var(--vscode-editor-background);
}
.agent-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--vscode-sideBar-background);
border-bottom: 1px solid var(--vscode-input-border);
}
.agent-icon {
font-size: 16px;
}
.agent-name {
font-weight: 500;
flex: 1;
}
.agent-status {
font-size: 11px;
padding: 2px 8px;
border-radius: 10px;
}
.agent-status.running {
background: var(--vscode-inputValidation-infoBackground);
color: var(--vscode-inputValidation-infoForeground);
}
.agent-status.completed {
background: #28a745;
color: white;
}
.agent-status.error {
background: #dc3545;
color: white;
}
.agent-body {
padding: 8px;
}
.agent-steps-container {
max-height: 150px;
overflow-y: auto;
font-size: 12px;
}
.agent-step {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
border-radius: 4px;
margin-bottom: 4px;
background: var(--vscode-list-hoverBackground);
}
.agent-step:last-child {
margin-bottom: 0;
}
.step-icon {
flex-shrink: 0;
}
.step-name {
font-weight: 500;
color: var(--vscode-foreground);
}
.step-result {
color: var(--vscode-descriptionForeground);
font-size: 11px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.agent-step-placeholder {
color: var(--vscode-descriptionForeground);
font-style: italic;
padding: 8px;
text-align: center;
}
${getWaveformPreviewContent()} ${getWaveformPreviewContent()}
`; `;
} }
@ -916,6 +999,41 @@ export function getMessageAreaScript(): string {
} }
}, 0); }, 0);
} }
} else if (segment.type === 'agent') {
// 智能体卡片渲染
segmentDiv.className += ' segment-agent';
const statusText = segment.agentStatus === 'completed' ? '完成'
: segment.agentStatus === 'error' ? '错误' : '执行中';
const statusClass = segment.agentStatus || 'running';
const stepsHtml = (segment.agentSteps || []).map(step => {
const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄';
const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : '';
return \`<div class="agent-step"><span class="step-icon">\${icon}</span><span class="step-name">\${step.toolName}</span><span class="step-result">\${result}</span></div>\`;
}).join('');
segmentDiv.innerHTML = \`
<div class="agent-card">
<div class="agent-header">
<span class="agent-icon">🤖</span>
<span class="agent-name">\${segment.agentName || '智能体'}</span>
<span class="agent-status \${statusClass}">\${statusText}</span>
</div>
<div class="agent-body">
<div class="agent-steps-container">
\${stepsHtml || '<div class="agent-step-placeholder">等待执行...</div>'}
</div>
</div>
</div>
\`;
// 自动滚动到最新步骤
setTimeout(() => {
const container = segmentDiv.querySelector('.agent-steps-container');
if (container) {
container.scrollTop = container.scrollHeight;
}
}, 0);
} }
currentSegmentedMessage.appendChild(segmentDiv); currentSegmentedMessage.appendChild(segmentDiv);
@ -1058,6 +1176,33 @@ export function getMessageAreaScript(): string {
</div> </div>
</div> </div>
\`; \`;
} else if (segment.type === 'agent') {
// 智能体卡片渲染
segmentDiv.className += ' segment-agent';
const statusText = segment.agentStatus === 'completed' ? '完成'
: segment.agentStatus === 'error' ? '错误' : '执行中';
const statusClass = segment.agentStatus || 'running';
const stepsHtml = (segment.agentSteps || []).map(step => {
const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄';
const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : '';
return \`<div class="agent-step"><span class="step-icon">\${icon}</span><span class="step-name">\${step.toolName}</span><span class="step-result">\${result}</span></div>\`;
}).join('');
segmentDiv.innerHTML = \`
<div class="agent-card">
<div class="agent-header">
<span class="agent-icon">🤖</span>
<span class="agent-name">\${segment.agentName || '智能体'}</span>
<span class="agent-status \${statusClass}">\${statusText}</span>
</div>
<div class="agent-body">
<div class="agent-steps-container">
\${stepsHtml || '<div class="agent-step-placeholder">等待执行...</div>'}
</div>
</div>
</div>
\`;
} }
container.appendChild(segmentDiv); container.appendChild(segmentDiv);

View File

@ -17,6 +17,10 @@ import {
getMessageAreaStyles, getMessageAreaStyles,
getMessageAreaScript, getMessageAreaScript,
} from "./messageArea"; } from "./messageArea";
import {
getAgentCardStyles,
getAgentCardScript,
} from "./agentCard";
/** /**
* 获取 WebView 面板的 HTML 内容 * 获取 WebView 面板的 HTML 内容
*/ */
@ -65,6 +69,7 @@ export function getWebviewContent(iconUri?: string): string {
padding: 0 20px 20px 20px; padding: 0 20px 20px 20px;
} }
${getMessageAreaStyles()} ${getMessageAreaStyles()}
${getAgentCardStyles()}
${getWaveformPreviewContent()} ${getWaveformPreviewContent()}
${getConversationHistoryBarStyles()} ${getConversationHistoryBarStyles()}
${getInputAreaStyles()} ${getInputAreaStyles()}
@ -393,6 +398,7 @@ export function getWebviewContent(iconUri?: string): string {
<button class="quick-btn" onclick="quickAction('counter')">生成计数器</button> <button class="quick-btn" onclick="quickAction('counter')">生成计数器</button>
<button class="quick-btn" onclick="quickAction('fsm')">生成状态机</button> <button class="quick-btn" onclick="quickAction('fsm')">生成状态机</button>
<button class="quick-btn" onclick="quickAction('testbench')">生成测试平台</button> <button class="quick-btn" onclick="quickAction('testbench')">生成测试平台</button>
<button class="quick-btn" onclick="quickAction('explore')">知识探索</button>
</div> </div>
${getInputAreaContent()} ${getInputAreaContent()}
@ -415,7 +421,8 @@ export function getWebviewContent(iconUri?: string): string {
const questions = { const questions = {
counter: '生成一个4位同步计数器', counter: '生成一个4位同步计数器',
fsm: '生成一个状态机', fsm: '生成一个状态机',
testbench: '生成测试平台' testbench: '生成测试平台',
explore: '请启动知识探索智能体,分析当前项目结构'
}; };
if (questions[type]) { if (questions[type]) {
messageInput.value = questions[type]; messageInput.value = questions[type];
@ -587,6 +594,7 @@ export function getWebviewContent(iconUri?: string): string {
}); });
${getMessageAreaScript()} ${getMessageAreaScript()}
${getAgentCardScript()}
${getWaveformPreviewScript()} ${getWaveformPreviewScript()}
${getConversationHistoryBarScript()} ${getConversationHistoryBarScript()}
${getInputAreaScript()} ${getInputAreaScript()}