19 Commits

Author SHA1 Message Date
531d140b99 refactor: 优化错误提示信息
- 将"当前访问人数过多"改为更通用的"处理用户消息失败"
   - 移除错误消息中的  emoji,保持简洁
2026-03-09 15:55:51 +08:00
97b8e8aa7d refactor: 更新后端服务地址占位符 2026-03-09 15:34:41 +08:00
4ed998e937 feat: 添加后端服务地址自定义配置功能
- 在设置面板添加后端服务地址配置项
   - 支持保存、加载和重置自定义配置
   - 配置持久化存储,重启后保留
   - 添加 SSE 请求日志用于验证配置
2026-03-09 15:29:56 +08:00
ad0f0336d5 feat: 移除用户信息、余额检查和登录过期提示
- 隐藏用户信息显示和退出登录按钮
   - 删除发送消息前的余额检查逻辑
   - 删除对话完成后的余额更新逻辑
   - 注释掉所有登录过期弹窗提示
   - 移除用户服务和余额服务的初始化调用
2026-03-09 14:28:41 +08:00
7cde4fa138 refactor: 优化代码格式和用户提示
- 统一代码格式化(Prettier)
- 将 iverilog 相关错误提示改为 'IC Coder编译器'
- 优化后端服务错误提示为 '当前访问人数过多,请稍后重试'
- 修复代码风格一致性问题
2026-03-09 11:10:56 +08:00
1b7259d1c1 feat:排除打包项目中的waveform_trace文件中的无关文档 2026-03-09 10:41:17 +08:00
09ff812562 feat:修复 Windows vvp 解析问题
- 修复 iverilog 生成的 .vvp 文件 shebang 导致 Windows 解析失败
2026-03-07 18:41:42 +08:00
e7c631d532 feat: 优化文档结构
- 将文档移至 docs/ 目录统一管理
   - 更新 .vscodeignore 排除规则
2026-03-07 18:41:14 +08:00
06573e37d7 feat: 优化 webpack 打包配置
- 添加自动模式切换(开发/生产)
   - 启用 Tree Shaking 移除未使用代码
   - 加快编译速度(transpileOnly)
   - 添加打包体积监控
   - 自动清理旧文件
   - 添加打包优化文档
2026-03-06 18:27:56 +08:00
d740f4da44 feat: 支持文件路径标签带行号点击跳转
- 前端解析 file.v:1-2 格式,提取文件名和行号
   - 新增 openFilePathTag 命令,支持智能文件查找
   - 修复模板字符串中正则表达式转义问题
   - 不影响现有 openFile 和 diff 功能
2026-03-06 16:24:21 +08:00
f24bd38ec7 feat: 优化上下文项显示和识别逻辑
- 支持显示所有类型的上下文项(文件和代码片段)
   - 增强路径识别,支持代码片段格式(文件名:行号-行号)
2026-03-06 15:40:59 +08:00
45934baf0a feat: 添加上下文项点击功能
- 文件类型可点击打开文件
   - 代码片段可点击打开文件并选中对应代码
   - 文件夹类型不可点击
2026-03-06 10:13:27 +08:00
4384ee53c5 fix: 修复关闭面板后快捷键无法自动打开面板的问题
- 通过 try-catch 检测 webview 是否真正可用
   - 修复 panel._isDisposed 检测不准确的问题
   - 增加异常捕获防止发送消息时崩溃
   - 延长消息发送延迟至 500ms 确保面板加载完成
2026-03-06 10:05:52 +08:00
d89c326be5 Merge branch 'feat/DeleteConfirmation' into feat/codeToChat 2026-03-06 09:16:12 +08:00
2dccb4f871 update:changelog.md 2026-03-06 09:11:33 +08:00
a9ddf3074e 1.0.12 2026-03-06 09:10:51 +08:00
db087bb184 update:更新changelog.md 2026-03-06 09:10:09 +08:00
5e9083041f fix: 修复多选问题提交后选中项不显示高亮的问题 2026-03-06 09:08:38 +08:00
be0555d6bc feat:codeToChat 2026-03-06 08:59:02 +08:00
29 changed files with 928 additions and 795 deletions

View File

@ -18,10 +18,15 @@ node_modules/**
# 文档(避免中文文件名打包问题) # 文档(避免中文文件名打包问题)
docs/** docs/**
PUBLISH.md CLAUDE.md
# 只排除 waveform_trace 的 src/dist 目录 # 只排除 waveform_trace 的 src/dist 目录
tools/waveform_trace/src/dist/** tools/waveform_trace/src/**
tools/iverilog/examples/**
tools/iverilog/INSTALL.md
tools/iverilog/README.md
tools/iverilog/DOWNLOAD_INSTRUCTIONS.md
# Git 相关 # Git 相关
.git/** .git/**

View File

@ -2,6 +2,12 @@
所有重要的项目变更都将记录在此文件中。 所有重要的项目变更都将记录在此文件中。
## [1.0.12] - 2026-03-06
### 新增
- 支持 AskUserQuestion 多问题和多选功能
## [1.0.9] - 2026-03-04 ## [1.0.9] - 2026-03-04
### 优化 ### 优化

View File

@ -0,0 +1,42 @@
# 代码快速添加到对话功能
## 功能说明
选中代码后,通过右键菜单/小灯泡/快捷键Ctrl+Shift+I将代码作为上下文添加到聊天面板输入框上方。
## 实现方式
### 1. Code Action Provider
`src/providers/codeActionProvider.ts` - 提供小灯泡菜单选项
### 2. 命令注册
`src/extension.ts` - 注册 `ic-coder.addCodeToChat` 命令,发送消息到 webview
### 3. 全局引用
`src/panels/ICHelperPanel.ts` - 保存 panel 到 `(global as any).currentICHelperPanel`
### 4. 上下文显示
`src/views/contextDisplay.ts` - 添加 `code` 类型支持和 `addCodeContext` 消息处理
### 5. 配置
`package.json` - 配置命令、右键菜单、快捷键
## 用户体验
1. 选中代码
2. 右键/小灯泡/Ctrl+Shift+I
3. 代码显示为上下文项:`文件名.v:10-25` 📄
4. 输入问题发送(代码自动作为上下文)
## 数据结构
代码上下文存储为 JSON
```json
{
"fileName": "路径",
"startLine": 10,
"endLine": 25,
"code": "代码内容",
"languageId": "verilog"
}
```

View File

@ -0,0 +1,55 @@
# Webpack 打包优化说明
## 优化内容
### 1. 自动模式切换
- 开发模式:保持源码可读性
- 生产模式:自动压缩代码
### 2. 性能优化
- **Tree Shaking**:移除未使用的代码
- **transpileOnly**:跳过类型检查,加快编译速度
- **自动清理**:每次打包自动删除旧文件
### 3. 体积监控
- 单文件超过 2MB 会发出警告
- 帮助及时发现打包体积问题
## 使用方法
### 开发模式
```bash
# 编译(不压缩)
pnpm run compile
# 监听模式(自动重新编译)
pnpm run watch
```
### 生产模式
```bash
# Windows
set NODE_ENV=production && pnpm run package
# macOS/Linux
NODE_ENV=production pnpm run package
```
## 打包结果
- **输出目录**`dist/`
- **入口文件**`dist/extension.js`
- **静态资源**`dist/assets/`
## 性能对比
| 模式 | 体积 | 编译速度 | Source Map |
|------|------|----------|------------|
| 开发 | 较大 | 快 | 完整 |
| 生产 | 小 | 较慢 | 隐藏 |
## 注意事项
1. 开发时使用 `pnpm run watch`,修改代码自动重新编译
2. 发布前必须使用生产模式打包
3. 如果打包体积超过 2MB检查是否引入了不必要的依赖

View File

@ -2,7 +2,7 @@
"name": "iccoder", "name": "iccoder",
"displayName": "IC Coder: Agentic Verilog Platform", "displayName": "IC Coder: Agentic Verilog Platform",
"description": "Agentic Verilog Coding Platform for Real-World FPGAs", "description": "Agentic Verilog Coding Platform for Real-World FPGAs",
"version": "1.0.11", "version": "1.0.12",
"publisher": "ICCoderAgenticVerilogPlatform", "publisher": "ICCoderAgenticVerilogPlatform",
"engines": { "engines": {
"vscode": "^1.80.0" "vscode": "^1.80.0"
@ -54,6 +54,28 @@
"command": "ic-coder.testNotification", "command": "ic-coder.testNotification",
"title": "测试系统通知", "title": "测试系统通知",
"category": "IC Coder" "category": "IC Coder"
},
{
"command": "ic-coder.addCodeToChat",
"title": "添加到 IC Coder 对话",
"category": "IC Coder"
}
],
"menus": {
"editor/context": [
{
"command": "ic-coder.addCodeToChat",
"when": "editorHasSelection",
"group": "9_cutcopypaste"
}
]
},
"keybindings": [
{
"command": "ic-coder.addCodeToChat",
"key": "ctrl+l",
"mac": "cmd+l",
"when": "editorTextFocus && editorHasSelection"
} }
], ],
"viewsContainers": { "viewsContainers": {

View File

@ -29,6 +29,9 @@ export interface IccoderConfig {
serviceTier: ServiceTier; serviceTier: ServiceTier;
} }
/** 自定义配置缓存 */
let customConfig: Partial<IccoderConfig> | null = null;
/** 环境配置 */ /** 环境配置 */
const ENV_CONFIG: Record<Environment, IccoderConfig> = { const ENV_CONFIG: Record<Environment, IccoderConfig> = {
/** 本地开发环境 - 通过 Gateway 路由 */ /** 本地开发环境 - 通过 Gateway 路由 */
@ -38,7 +41,7 @@ const ENV_CONFIG: Record<Environment, IccoderConfig> = {
loginUrl: "http://localhost/login", loginUrl: "http://localhost/login",
timeout: 300000, timeout: 300000,
userId: "default-user", userId: "default-user",
serviceTier: "max", // 默认使用 max serviceTier: "max",
}, },
/** 测试服务器环境 - 通过 Gateway 路由 */ /** 测试服务器环境 - 通过 Gateway 路由 */
test: { test: {
@ -60,6 +63,13 @@ const ENV_CONFIG: Record<Environment, IccoderConfig> = {
}, },
}; };
/**
* 设置自定义配置
*/
export function setCustomConfig(config: Partial<IccoderConfig>) {
customConfig = config;
}
/** /**
* 获取当前环境 * 获取当前环境
*/ */
@ -71,7 +81,14 @@ export function getCurrentEnv(): Environment {
* 获取配置项 * 获取配置项
*/ */
export function getConfig(): IccoderConfig { export function getConfig(): IccoderConfig {
return { ...ENV_CONFIG[CURRENT_ENV] }; const baseConfig = { ...ENV_CONFIG[CURRENT_ENV] };
// 合并自定义配置(空字符串表示使用默认)
if (customConfig?.backendUrl && customConfig.backendUrl !== '') {
baseConfig.backendUrl = customConfig.backendUrl;
}
return baseConfig;
} }
/** /**

View File

@ -5,46 +5,78 @@ import { VCDViewerPanel, VCDViewerEditorProvider } from "./panels/VCDViewerPanel
import { ChatHistoryManager } from "./utils/chatHistoryManager"; import { ChatHistoryManager } from "./utils/chatHistoryManager";
import { ICCoderAuthenticationProvider } from "./services/icCoderAuthProvider"; import { ICCoderAuthenticationProvider } from "./services/icCoderAuthProvider";
import { VCDFileServer } from "./services/vcdFileServer"; import { VCDFileServer } from "./services/vcdFileServer";
import { initUserService } from "./services/userService";
import { initCreditsService } from "./services/creditsService";
import { isTokenExpired } from "./utils/jwtUtils"; import { isTokenExpired } from "./utils/jwtUtils";
import { NotificationService } from "./services/notificationService"; import { NotificationService } from "./services/notificationService";
import { InvitationService } from "./services/invitationService"; import { InvitationService } from "./services/invitationService";
import { ICCoderCodeActionProvider } from "./providers/codeActionProvider";
import { setCustomConfig } from "./config/settings";
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
console.log("🎉 IC Coder 插件已激活!"); console.log("🎉 IC Coder 插件已激活!");
// 加载保存的配置
const savedSettings = context.globalState.get('generalSettings') as any;
if (savedSettings?.backendUrl) {
setCustomConfig({
backendUrl: savedSettings.backendUrl,
});
}
// 创建装饰类型(代码旁边的提示)
const decorationType = vscode.window.createTextEditorDecorationType({
after: {
contentText: ' Ctrl+L 添加到 IC Coder 对话',
color: '#888',
fontStyle: 'italic',
margin: '0 0 0 1em'
}
});
// 更新装饰
const updateDecorations = () => {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
if (!editor.selection.isEmpty) {
const range = new vscode.Range(editor.selection.end, editor.selection.end);
const decoration = { range };
editor.setDecorations(decorationType, [decoration]);
} else {
editor.setDecorations(decorationType, []);
}
};
context.subscriptions.push(
vscode.window.onDidChangeTextEditorSelection(updateDecorations),
vscode.window.onDidChangeActiveTextEditor(updateDecorations)
);
updateDecorations();
// 初始化通知服务 // 初始化通知服务
const notificationService = NotificationService.getInstance(context); const notificationService = NotificationService.getInstance(context);
console.log('[Extension] 通知服务已初始化'); console.log('[Extension] 通知服务已初始化');
// 【关键】在创建 AuthProvider 之前,先检查并清除过期的 session // 【已禁用】登录和 token 验证 - 无需登录即可使用
const storedSessions = context.globalState.get<any[]>('icCoderSessions', []); // const storedSessions = context.globalState.get<any[]>('icCoderSessions', []);
console.log('[Extension] 检查 sessions 数量:', storedSessions.length); // console.log('[Extension] 检查 sessions 数量:', storedSessions.length);
//
if (storedSessions.length > 0) { // if (storedSessions.length > 0) {
const session = storedSessions[0]; // const session = storedSessions[0];
const token = session.accessToken; // const token = session.accessToken;
console.log('[Extension] 检查 token 是否过期...'); // console.log('[Extension] 检查 token 是否过期...');
//
if (token) { // if (token) {
const expired = isTokenExpired(token); // const expired = isTokenExpired(token);
console.log('[Extension] token 过期检查结果:', expired); // console.log('[Extension] token 过期检查结果:', expired);
//
if (expired) { // if (expired) {
// 必须等待清除完成后再创建 AuthProvider // await context.globalState.update('icCoderSessions', []);
await context.globalState.update('icCoderSessions', []); // await context.globalState.update('icCoderUserInfo', undefined);
await context.globalState.update('icCoderUserInfo', undefined); // console.log('[Extension] Token 已过期,已清除所有登录状态');
console.log('[Extension] Token 已过期,已清除所有登录状态'); // }
} // }
} // }
}
// 初始化用户服务
initUserService(context);
// 初始化 Credits 服务
initCreditsService(context);
// 初始化 VCD 文件服务器 // 初始化 VCD 文件服务器
const vcdFileServer = new VCDFileServer(context.extensionUri); const vcdFileServer = new VCDFileServer(context.extensionUri);
@ -59,25 +91,18 @@ export async function activate(context: vscode.ExtensionContext) {
dispose: () => vcdFileServer.stop() dispose: () => vcdFileServer.stop()
}); });
// 注册 Authentication Provider(此时 icCoderSessions 已经被清除) // 【已禁用】Authentication Provider 注册 - 无需登录
const authProvider = new ICCoderAuthenticationProvider(context); const authProvider = new ICCoderAuthenticationProvider(context);
context.subscriptions.push( // context.subscriptions.push(
vscode.authentication.registerAuthenticationProvider( // vscode.authentication.registerAuthenticationProvider(
"iccoder", // "iccoder",
"IC Coder", // "IC Coder",
authProvider // authProvider
) // )
); // );
// 检查登录状态,如果已登录则自动打开聊天面板 // 【已禁用】登录状态检查 - 直接打开聊天面板
vscode.authentication.getSession("iccoder", [], { createIfNone: false }) vscode.commands.executeCommand("ic-coder.openChat");
.then((session) => {
if (session) {
vscode.commands.executeCommand("ic-coder.openChat");
}
}, () => {
// 未登录,不做任何操作
});
// 注册命令:打开助手面板 // 注册命令:打开助手面板
const openPanelCommand = vscode.commands.registerCommand( const openPanelCommand = vscode.commands.registerCommand(
@ -250,6 +275,81 @@ export async function activate(context: vscode.ExtensionContext) {
} }
); );
// 注册命令:将选中代码添加到对话
const addCodeToChat = vscode.commands.registerCommand(
"ic-coder.addCodeToChat",
async () => {
console.log('[addCodeToChat] 命令触发');
const editor = vscode.window.activeTextEditor;
if (!editor) {
console.log('[addCodeToChat] 没有活动编辑器');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
if (!selectedText) {
vscode.window.showWarningMessage("请先选择代码");
return;
}
const fileName = editor.document.fileName;
const startLine = selection.start.line + 1;
const endLine = selection.end.line + 1;
// 检查是否已有打开的面板
let panel = (global as any).currentICHelperPanel;
let needCreatePanel = false;
if (!panel) {
needCreatePanel = true;
} else {
// 尝试访问 webview如果抛出异常说明已销毁
try {
const _ = panel.webview;
} catch (e) {
needCreatePanel = true;
}
}
console.log('[addCodeToChat] 需要创建面板:', needCreatePanel);
if (needCreatePanel) {
console.log('[addCodeToChat] 正在打开面板...');
await showICHelperPanel(context);
panel = (global as any).currentICHelperPanel;
console.log('[addCodeToChat] 面板打开后状态:', panel ? '成功' : '失败');
// 如果面板仍未创建(如未登录),直接返回
if (!panel) {
console.log('[addCodeToChat] 面板创建失败,退出');
return;
}
}
// 发送代码上下文
console.log('[addCodeToChat] 准备发送代码到面板');
setTimeout(() => {
try {
if (panel?.webview) {
console.log('[addCodeToChat] 发送 addCodeContext 消息');
panel.webview.postMessage({
command: 'addCodeContext',
fileName,
startLine,
endLine,
code: selectedText,
languageId: editor.document.languageId
});
}
} catch (e) {
console.log('[addCodeToChat] 发送消息失败:', e);
}
}, 500);
}
);
// 注册命令:查看会话历史 // 注册命令:查看会话历史
// TODO: 这些命令需要根据新的任务架构重新实现 // TODO: 这些命令需要根据新的任务架构重新实现
// 暂时注释掉,等待重新实现 // 暂时注释掉,等待重新实现
@ -312,6 +412,13 @@ export async function activate(context: vscode.ExtensionContext) {
// 注册 VCD 自定义编辑器 // 注册 VCD 自定义编辑器
const vcdEditorProvider = VCDViewerEditorProvider.register(context, vcdFileServer); const vcdEditorProvider = VCDViewerEditorProvider.register(context, vcdFileServer);
// 注册 Code Action Provider
const codeActionProvider = vscode.languages.registerCodeActionsProvider(
{ scheme: 'file' },
new ICCoderCodeActionProvider(),
{ providedCodeActionKinds: [vscode.CodeActionKind.RefactorRewrite] }
);
// 添加到订阅 // 添加到订阅
context.subscriptions.push( context.subscriptions.push(
openPanelCommand, openPanelCommand,
@ -322,6 +429,7 @@ export async function activate(context: vscode.ExtensionContext) {
logoutCommand, logoutCommand,
changeInvitationCodeCommand, changeInvitationCodeCommand,
testNotificationCommand, testNotificationCommand,
addCodeToChat,
// testTrialUserCommand, // testTrialUserCommand,
// testExpiredUserCommand, // testExpiredUserCommand,
// TODO: 等待重新实现这些命令 // TODO: 等待重新实现这些命令
@ -332,7 +440,8 @@ export async function activate(context: vscode.ExtensionContext) {
// clearHistoryCommand, // clearHistoryCommand,
// searchSessionCommand, // searchSessionCommand,
viewRegistration, viewRegistration,
vcdEditorProvider vcdEditorProvider,
codeActionProvider
); );
} }

View File

@ -18,50 +18,12 @@ import {
startChangeSession, startChangeSession,
handleOpenFileDiff, handleOpenFileDiff,
} from "../utils/messageHandler"; } from "../utils/messageHandler";
import { setCustomConfig } from "../config/settings";
import { compactDialog } from "../services/apiClient"; import { compactDialog } from "../services/apiClient";
import { VCDViewerPanel } from "./VCDViewerPanel"; import { VCDViewerPanel } from "./VCDViewerPanel";
import { ChatHistoryManager } from "../utils/chatHistoryManager"; import { ChatHistoryManager } from "../utils/chatHistoryManager";
import { MessageType } from "../types/chatHistory"; import { MessageType } from "../types/chatHistory";
import { getCachedUserInfo } from "../services/userService";
import { isTokenExpired } from "../utils/jwtUtils"; import { isTokenExpired } from "../utils/jwtUtils";
import { setBalanceUpdateCallback } from "../services/creditsService";
/**
* 获取会员等级图标 URI
*/
function getTierIconUri(
webview: vscode.Webview,
context: vscode.ExtensionContext,
tierCode?: string,
): string | undefined {
if (!tierCode) {
return undefined;
}
const tierIconMap: Record<string, string> = {
BASIC: "free.png",
TRIAL: "PRO-Try.png",
ADVANCED: "PRO.png",
PROFESSIONAL: "PRO+.png",
};
const iconFile = tierIconMap[tierCode];
if (!iconFile) {
return undefined;
}
const iconUri = webview.asWebviewUri(
vscode.Uri.joinPath(
context.extensionUri,
"dist",
"assets",
"titleIcon",
iconFile,
),
);
return iconUri.toString();
}
/** /**
* 创建并显示 IC 助手面板 * 创建并显示 IC 助手面板
@ -70,63 +32,35 @@ export async function showICHelperPanel(
context: vscode.ExtensionContext, context: vscode.ExtensionContext,
viewColumn?: vscode.ViewColumn, viewColumn?: vscode.ViewColumn,
) { ) {
// 检查 token 是否过期 // 创建WebView面板
let token: string | undefined; // try {
try { // const session = await vscode.authentication.getSession("iccoder", [], {
const session = await vscode.authentication.getSession("iccoder", [], { // createIfNone: false,
createIfNone: false, // });
}); // if (!session) {
token = session?.accessToken; // vscode.window
} catch (error) { // .showWarningMessage("请先登录后再使用 IC Coder", "立即登录")
console.warn("[ICHelperPanel] 获取 session 失败:", error); // .then((selection) => {
} // if (selection === "立即登录") {
// vscode.commands.executeCommand("ic-coder.login", {
if (token && isTokenExpired(token)) { // forceReauth: true,
// 清除过期的 session // });
await context.globalState.update("icCoderSessions", []); // }
await context.globalState.update("icCoderUserInfo", undefined); // });
// return;
const action = await vscode.window.showWarningMessage( // }
"登录已过期,请重新登录", // } catch (error) {
"立即登录", // vscode.window
); // .showWarningMessage("请先登录后再使用 IC Coder", "立即登录")
if (action === "立即登录") { // .then((selection) => {
vscode.commands.executeCommand("ic-coder.login", { // if (selection === "立即登录") {
forceReauth: true, // vscode.commands.executeCommand("ic-coder.login", {
}); // forceReauth: true,
} // });
return; // }
} // });
// return;
// 检查用户是否已登录 // }
try {
const session = await vscode.authentication.getSession("iccoder", [], {
createIfNone: false,
});
if (!session) {
vscode.window
.showWarningMessage("请先登录后再使用 IC Coder", "立即登录")
.then((selection) => {
if (selection === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true,
});
}
});
return;
}
} catch (error) {
vscode.window
.showWarningMessage("请先登录后再使用 IC Coder", "立即登录")
.then((selection) => {
if (selection === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true,
});
}
});
return;
}
// 创建WebView面板 // 创建WebView面板
const panel = vscode.window.createWebviewPanel( const panel = vscode.window.createWebviewPanel(
@ -143,6 +77,9 @@ export async function showICHelperPanel(
}, },
); );
// 保存 panel 引用到全局
(global as any).currentICHelperPanel = panel;
// 为面板生成唯一ID // 为面板生成唯一ID
const panelId = `panel_${Date.now()}_${Math.random() const panelId = `panel_${Date.now()}_${Math.random()
.toString(36) .toString(36)
@ -227,81 +164,6 @@ export async function showICHelperPanel(
logoUri.toString(), logoUri.toString(),
); );
// 获取并发送用户信息到 webview
try {
// 优先使用缓存的用户信息
let userInfo = getCachedUserInfo();
if (userInfo) {
// 使用缓存的用户信息
console.log("[ICHelperPanel] 使用缓存的用户信息:", userInfo);
console.log("[ICHelperPanel] Credits 余额:", userInfo.credits);
const tierIconUrl = getTierIconUri(
panel.webview,
context,
userInfo.membership?.tierCode,
);
const messageData = {
command: "updateUserInfo",
userInfo: {
userId: userInfo.userId,
nickname: userInfo.nickname,
username: userInfo.username,
credits: userInfo.credits,
membership: userInfo.membership,
},
tierIconUrl: tierIconUrl,
};
console.log("[ICHelperPanel] 发送用户信息到前端:", messageData);
panel.webview.postMessage(messageData);
} else {
// 如果没有缓存,从 session 中获取
const session = await vscode.authentication.getSession("iccoder", [], {
createIfNone: false,
});
if (session) {
console.log(
"[ICHelperPanel] 从 session 获取用户信息, account:",
session.account,
);
panel.webview.postMessage({
command: "updateUserInfo",
userInfo: {
userId: session.account.id,
nickname: session.account.label,
username: session.account.label,
},
});
}
}
} catch (error) {
console.error("[ICHelperPanel] 获取用户信息失败:", error);
}
// 设置余额更新回调
setBalanceUpdateCallback((balance: number) => {
const userInfo = getCachedUserInfo();
if (userInfo) {
userInfo.credits = balance;
const tierIconUrl = getTierIconUri(
panel.webview,
context,
userInfo.membership?.tierCode,
);
panel.webview.postMessage({
command: "updateUserInfo",
userInfo: {
userId: userInfo.userId,
nickname: userInfo.nickname,
username: userInfo.username,
credits: balance,
membership: userInfo.membership,
},
tierIconUrl: tierIconUrl,
});
}
});
// 检查是否有待发送的消息 // 检查是否有待发送的消息
const pendingMessage = context.globalState.get("pendingMessage") as any; const pendingMessage = context.globalState.get("pendingMessage") as any;
if (pendingMessage) { if (pendingMessage) {
@ -491,6 +353,78 @@ export async function showICHelperPanel(
// 退出登录 // 退出登录
vscode.commands.executeCommand("ic-coder.logout"); vscode.commands.executeCommand("ic-coder.logout");
break; break;
case "openFile":
// 打开文件
if (message.filePath) {
const path = require('path');
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
const fullPath = path.isAbsolute(message.filePath) || !workspaceFolder
? message.filePath
: vscode.Uri.joinPath(workspaceFolder.uri, message.filePath).fsPath;
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc);
});
}
break;
case "openFileWithSelection":
// 打开文件并选中代码
if (message.filePath) {
const path = require('path');
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
const fullPath = path.isAbsolute(message.filePath) || !workspaceFolder
? message.filePath
: vscode.Uri.joinPath(workspaceFolder.uri, message.filePath).fsPath;
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc).then(editor => {
const start = new vscode.Position(message.startLine - 1, 0);
const end = new vscode.Position(message.endLine - 1, doc.lineAt(message.endLine - 1).text.length);
editor.selection = new vscode.Selection(start, end);
editor.revealRange(new vscode.Range(start, end));
});
});
}
break;
case "openFilePathTag":
// 打开文件路径标签(智能查找)
if (message.filePath) {
const path = require('path');
const fs = require('fs');
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
let fullPath = message.filePath;
// 如果是相对路径且工作区存在
if (!path.isAbsolute(message.filePath) && workspaceFolder) {
const candidatePath = vscode.Uri.joinPath(workspaceFolder.uri, message.filePath).fsPath;
// 检查文件是否存在
if (fs.existsSync(candidatePath)) {
fullPath = candidatePath;
} else {
// 尝试在工作区中搜索该文件
const fileName = path.basename(message.filePath);
const files = await vscode.workspace.findFiles(`**/${fileName}`, '**/node_modules/**', 1);
if (files.length > 0) {
fullPath = files[0].fsPath;
}
}
}
if (message.startLine && message.endLine) {
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc).then(editor => {
const start = new vscode.Position(message.startLine - 1, 0);
const end = new vscode.Position(message.endLine - 1, doc.lineAt(message.endLine - 1).text.length);
editor.selection = new vscode.Selection(start, end);
editor.revealRange(new vscode.Range(start, end));
});
});
} else {
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc);
});
}
}
break;
case "acceptChange": case "acceptChange":
// 采纳变更 // 采纳变更
if (message.changeId) { if (message.changeId) {
@ -510,71 +444,17 @@ export async function showICHelperPanel(
} }
break; break;
case "checkInvitationCode": case "checkInvitationCode":
// 检查邀请码验证状态 // 【已禁用】检查邀请码验证状态 - 现在所有用户都可以直接使用
{ {
// 先检查是否是试用用户 // 直接返回已验证,无需登录和邀请码
const { getCachedUserInfo } = require("../services/userService"); panel.webview.postMessage({
const userInfo = getCachedUserInfo(); command: "invitationCodeStatus",
verified: true,
if (userInfo?.isPluginTrial === true) { });
// 试用用户,跳过邀请码验证,直接返回已验证
console.log("[ICHelperPanel] 试用用户,跳过邀请码验证");
panel.webview.postMessage({
command: "invitationCodeStatus",
verified: true,
});
} else {
// 正式用户,检查邀请码
const {
InvitationService,
} = require("../services/invitationService");
const isVerified = await InvitationService.isVerified(context);
panel.webview.postMessage({
command: "invitationCodeStatus",
verified: isVerified,
});
}
} }
break; break;
case "checkWelcomeModal": case "checkWelcomeModal":
// 检查是否需要显示欢迎弹窗 // 【已禁用】检查是否需要显示欢迎弹窗 - 无需登录,不显示欢迎弹窗
{
console.log("[ICHelperPanel] 收到 checkWelcomeModal 消息");
const userInfo = getCachedUserInfo();
console.log("[ICHelperPanel] 用户信息:", userInfo);
console.log("[ICHelperPanel] isPluginTrial:", userInfo?.isPluginTrial);
console.log("[ICHelperPanel] pluginTrialExpiresAt:", userInfo?.pluginTrialExpiresAt);
if (userInfo?.isPluginTrial === true) {
// undefined 表示无效,不显示
if (userInfo.pluginTrialExpiresAt === undefined) {
console.log("[ICHelperPanel] pluginTrialExpiresAt 未设置,不显示欢迎弹窗");
break;
}
// null 表示长期有效,显示弹窗
// 有值则检查是否过期
if (userInfo.pluginTrialExpiresAt !== null) {
const now = Date.now();
const isExpired = now >= userInfo.pluginTrialExpiresAt;
console.log("[ICHelperPanel] 是否过期:", isExpired);
if (isExpired) {
console.log("[ICHelperPanel] 试用已过期,不显示欢迎弹窗");
break;
}
}
// 未过期或长期有效(null),显示欢迎弹窗
console.log("[ICHelperPanel] ✅ 发送 showWelcomeModal 命令到前端");
panel.webview.postMessage({
command: "showWelcomeModal",
});
} else {
console.log("[ICHelperPanel] 非试用用户");
}
}
break; break;
case "checkTrialExpiration": case "checkTrialExpiration":
// 检查试用期是否过期 // 检查试用期是否过期
@ -589,37 +469,13 @@ export async function showICHelperPanel(
} }
break; break;
case "verifyInvitationCode": case "verifyInvitationCode":
// 验证邀请码 // 【已禁用】验证邀请码 - 无需邀请码验证
{ {
const { // 直接返回验证成功
InvitationService, panel.webview.postMessage({
} = require("../services/invitationService"); command: "invitationCodeVerified",
const result = await InvitationService.verifyCode(message.code); success: true,
});
if (result.success) {
// 验证成功,保存状态
await InvitationService.saveVerificationStatus(
context,
message.code,
);
panel.webview.postMessage({
command: "invitationCodeVerified",
success: true,
});
// 延迟显示欢迎弹窗,确保邀请码弹窗已关闭
setTimeout(() => {
panel.webview.postMessage({
command: "showNdtWelcomeModal",
});
}, 300);
} else {
// 验证失败,返回错误信息
panel.webview.postMessage({
command: "invitationCodeVerified",
success: false,
message: result.message,
});
}
} }
break; break;
case "openICCoder": case "openICCoder":
@ -829,6 +685,21 @@ export async function showICHelperPanel(
// 退出登录(前端已有确认对话框) // 退出登录(前端已有确认对话框)
vscode.commands.executeCommand("ic-coder.logout"); vscode.commands.executeCommand("ic-coder.logout");
break; break;
case "saveGeneralSettings":
// 保存通用设置
context.globalState.update('generalSettings', message.settings);
// 更新运行时配置(包括清空)
setCustomConfig({ backendUrl: message.settings.backendUrl || '' });
vscode.window.showInformationMessage('设置已保存');
break;
case "loadGeneralSettings":
// 加载通用设置
const settings = context.globalState.get('generalSettings');
panel.webview.postMessage({
command: 'loadedGeneralSettings',
settings: settings
});
break;
} }
}, },
undefined, undefined,

View File

@ -0,0 +1,26 @@
/**
* Code Action Provider - 为选中代码提供快捷操作
* 功能:在小灯泡菜单中显示"添加到 IC Coder 对话"选项
*/
import * as vscode from 'vscode';
export class ICCoderCodeActionProvider implements vscode.CodeActionProvider {
provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range
): vscode.CodeAction[] {
const selectedText = document.getText(range);
if (!selectedText) return [];
const action = new vscode.CodeAction(
'💬 添加到 IC Coder 对话',
vscode.CodeActionKind.RefactorRewrite
);
action.command = {
command: 'ic-coder.addCodeToChat',
title: '添加到对话'
};
return [action];
}
}

View File

@ -30,7 +30,6 @@ import type {
import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient"; import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient";
import { ChatHistoryManager } from "../utils/chatHistoryManager"; import { ChatHistoryManager } from "../utils/chatHistoryManager";
import { getUserIdFromToken, isTokenExpired } from "../utils/jwtUtils"; import { getUserIdFromToken, isTokenExpired } from "../utils/jwtUtils";
import { updateCachedBalance } from "./creditsService";
/** /**
* 消息段落类型 * 消息段落类型
@ -444,6 +443,7 @@ export class DialogSession {
const expired = isTokenExpired(session.accessToken); const expired = isTokenExpired(session.accessToken);
if (expired === true) { if (expired === true) {
console.error("[DialogSession] token 已过期,需要重新登录"); console.error("[DialogSession] token 已过期,需要重新登录");
/*
vscode.window vscode.window
.showErrorMessage("登录已过期,请重新登录", "重新登录") .showErrorMessage("登录已过期,请重新登录", "重新登录")
.then((selection) => { .then((selection) => {
@ -453,6 +453,7 @@ export class DialogSession {
}); });
} }
}); });
*/
throw new Error("登录已过期,请重新登录"); throw new Error("登录已过期,请重新登录");
} }
@ -899,6 +900,7 @@ export class DialogSession {
data.message.includes("LOGIN_EXPIRED") || data.message.includes("LOGIN_EXPIRED") ||
data.message.includes("登录状态已过期") data.message.includes("登录状态已过期")
) { ) {
/*
vscode.window vscode.window
.showErrorMessage("登录状态已过期,请重新登录", "重新登录") .showErrorMessage("登录状态已过期,请重新登录", "重新登录")
.then((selection) => { .then((selection) => {
@ -908,6 +910,7 @@ export class DialogSession {
}); });
} }
}); });
*/
// 登录过期错误已处理,不再传递给外部 // 登录过期错误已处理,不再传递给外部
return; return;
} }
@ -1017,8 +1020,9 @@ export class DialogSession {
data.remainingCredits data.remainingCredits
); );
// 更新余额缓存 // 更新余额缓存
updateCachedBalance(data.remainingCredits); // updateCachedBalance(data.remainingCredits);
// 资源点余额低于阈值时弹窗提醒 // 资源点余额低于阈值时弹窗提醒
/*
const LOW_CREDIT_THRESHOLD = 5; const LOW_CREDIT_THRESHOLD = 5;
if (data.remainingCredits < LOW_CREDIT_THRESHOLD) { if (data.remainingCredits < LOW_CREDIT_THRESHOLD) {
vscode.window vscode.window
@ -1030,13 +1034,13 @@ export class DialogSession {
) )
.then((selection) => { .then((selection) => {
if (selection === "去充值") { if (selection === "去充值") {
// 打开充值页面
vscode.env.openExternal( vscode.env.openExternal(
vscode.Uri.parse("https://iccoder.com/recharge") vscode.Uri.parse("https://iccoder.com/memberCenter")
); );
} }
}); });
} }
*/
}, },
onOpen: () => { onOpen: () => {

View File

@ -2,7 +2,6 @@ import * as vscode from "vscode";
import * as http from "http"; import * as http from "http";
import * as path from "path"; import * as path from "path";
import * as fs from "fs"; import * as fs from "fs";
import { onTokenReceived, type UserInfo, clearUserInfo } from "./userService";
import { getConfig } from "../config/settings"; import { getConfig } from "../config/settings";
import { resetInvitationVerification } from "./apiClient"; import { resetInvitationVerification } from "./apiClient";
@ -85,7 +84,7 @@ export class ICCoderAuthenticationProvider
const oldSession = this._sessions[0]; const oldSession = this._sessions[0];
this._sessions = []; this._sessions = [];
await this.saveSessions(); await this.saveSessions();
await clearUserInfo(); // await clearUserInfo();
this._onDidChangeSessions.fire({ this._onDidChangeSessions.fire({
added: [], added: [],
removed: [oldSession], removed: [oldSession],
@ -97,15 +96,15 @@ export class ICCoderAuthenticationProvider
const token = await this.login(); const token = await this.login();
// 获取到 token 后立即调用用户信息接口 // 获取到 token 后立即调用用户信息接口
const userInfo = await onTokenReceived(token); // const userInfo = await onTokenReceived(token);
// 创建会话 // 创建会话
const session: vscode.AuthenticationSession = { const session: vscode.AuthenticationSession = {
id: this.generateSessionId(), id: this.generateSessionId(),
accessToken: token, accessToken: token,
account: { account: {
id: userInfo?.userId || "iccoder-user", id: "user",
label: userInfo?.nickname || userInfo?.username || "IC Coder 用户", label: "IC Coder User",
}, },
scopes: [...scopes], scopes: [...scopes],
}; };
@ -158,7 +157,7 @@ export class ICCoderAuthenticationProvider
await this.saveSessions(); await this.saveSessions();
// 3. 清除用户信息缓存 // 3. 清除用户信息缓存
await clearUserInfo(); // await clearUserInfo();
// 4. 触发会话变化事件 // 4. 触发会话变化事件
this._onDidChangeSessions.fire({ this._onDidChangeSessions.fire({
@ -182,14 +181,14 @@ export class ICCoderAuthenticationProvider
*/ */
async clearSessionsForRelogin(): Promise<void> { async clearSessionsForRelogin(): Promise<void> {
if (this._sessions.length === 0) { if (this._sessions.length === 0) {
await clearUserInfo(); // await clearUserInfo();
return; return;
} }
const removed = [...this._sessions]; const removed = [...this._sessions];
this._sessions = []; this._sessions = [];
await this.saveSessions(); await this.saveSessions();
await clearUserInfo(); // await clearUserInfo();
this._onDidChangeSessions.fire({ this._onDidChangeSessions.fire({
added: [], added: [],

View File

@ -161,7 +161,16 @@ export async function startStreamDialog(
const body = JSON.stringify(request); const body = JSON.stringify(request);
console.log(`[SSE] 开始流式对话: taskId=${request.taskId}, mode=${request.mode}, url=${urlString}`); console.log('[SSE] 请求详情:', {
url: urlString,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'text/event-stream',
hasToken: !!request.token,
},
body: request
});
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const options: http.RequestOptions = { const options: http.RequestOptions = {

View File

@ -291,7 +291,7 @@ async function executeSyntaxCheck(
// 检查 iverilog 是否可用 // 检查 iverilog 是否可用
const iverilogCheck = await checkIverilogAvailable(context.extensionPath); const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
if (!iverilogCheck.available) { if (!iverilogCheck.available) {
throw new Error(`iverilog 不可用: ${iverilogCheck.message}`); throw new Error(`IC Coder编译器不可用: ${iverilogCheck.message}`);
} }
// 创建临时文件 // 创建临时文件
@ -372,7 +372,7 @@ async function executeIverilog(
// 检查 iverilog 是否可用 // 检查 iverilog 是否可用
const iverilogCheck = await checkIverilogAvailable(context.extensionPath); const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
if (!iverilogCheck.available) { if (!iverilogCheck.available) {
throw new Error(`iverilog 不可用: ${iverilogCheck.message}`); throw new Error(`IC Coder编译器不可用: ${iverilogCheck.message}`);
} }
// 获取工作目录 // 获取工作目录

View File

@ -335,43 +335,21 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
// 保存到持久化存储 // 保存到持久化存储
await saveUserInfo(userInfo); await saveUserInfo(userInfo);
// 判断是否是插件试用用户 // 【已禁用】试用用户和欢迎弹窗逻辑 - 无需登录
console.log('[UserService] 检查用户类型isPluginTrial:', userInfo.isPluginTrial); // if (userInfo.isPluginTrial === true && userInfo.pluginTrialExpiresAt !== null && userInfo.pluginTrialExpiresAt !== undefined) {
console.log('[UserService] extensionContext 是否存在:', !!extensionContext); // const now = Date.now();
// const isExpired = now >= userInfo.pluginTrialExpiresAt;
if (userInfo.isPluginTrial === true && userInfo.pluginTrialExpiresAt !== null && userInfo.pluginTrialExpiresAt !== undefined) { // if (isExpired) {
// 检查是否过期 // console.log('[UserService] 试用已过期,将显示邀请码弹窗');
const now = Date.now(); // } else {
const isExpired = now >= userInfo.pluginTrialExpiresAt; // const hasWelcomed = extensionContext?.globalState.get('pluginTrialWelcomed');
console.log('[UserService] 试用到期时间:', new Date(userInfo.pluginTrialExpiresAt).toLocaleString()); // if (!hasWelcomed && extensionContext) {
console.log('[UserService] 当前时间:', new Date(now).toLocaleString()); // await extensionContext.globalState.update('showWelcomeModal', true);
console.log('[UserService] 是否过期:', isExpired); // await extensionContext.globalState.update('pluginTrialWelcomed', true);
// console.log('[UserService] ✅ 已设置欢迎弹窗标记 showWelcomeModal=true');
if (isExpired) { // }
// 已过期:显示邀请码弹窗 // }
console.log('[UserService] 试用已过期,将显示邀请码弹窗'); // }
} else {
// 未过期:显示欢迎弹窗
const hasWelcomed = extensionContext?.globalState.get('pluginTrialWelcomed');
console.log('[UserService] 是否已显示过欢迎弹窗:', hasWelcomed);
if (!hasWelcomed && extensionContext) {
await extensionContext.globalState.update('showWelcomeModal', true);
await extensionContext.globalState.update('pluginTrialWelcomed', true);
console.log('[UserService] ✅ 已设置欢迎弹窗标记 showWelcomeModal=true');
const checkMark = extensionContext.globalState.get('showWelcomeModal');
console.log('[UserService] 验证标记:', checkMark);
} else if (!extensionContext) {
console.error('[UserService] ❌ extensionContext 为 null无法设置标记');
} else {
console.log('[UserService] 已经显示过欢迎弹窗,跳过');
}
}
} else {
// isPluginTrial=false 或 enterpriseTrialExpires 为 null显示邀请码弹窗
console.log('[UserService] 非试用用户或无过期时间,将显示邀请码弹窗');
}
return userInfo; return userInfo;
} catch (error) { } catch (error) {

View File

@ -7,7 +7,7 @@ import { promisify } from "util";
function execCommand( function execCommand(
command: string, command: string,
args: string[], args: string[],
options: { cwd: string; env?: any } options: { cwd: string; env?: any },
): Promise<{ stdout: string; stderr: string }> { ): Promise<{ stdout: string; stderr: string }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 在 Windows 上,如果路径包含空格,不使用 shell直接用 spawn // 在 Windows 上,如果路径包含空格,不使用 shell直接用 spawn
@ -23,25 +23,25 @@ function execCommand(
let stderr = ""; let stderr = "";
// 在 Windows 上使用 GBK 编码解码输出 // 在 Windows 上使用 GBK 编码解码输出
const encoding = process.platform === 'win32' ? 'gbk' : 'utf8'; const encoding = process.platform === "win32" ? "gbk" : "utf8";
child.stdout.on("data", (data) => { child.stdout.on("data", (data) => {
try { try {
// 尝试使用 iconv-lite 解码(如果可用) // 尝试使用 iconv-lite 解码(如果可用)
const iconv = require('iconv-lite'); const iconv = require("iconv-lite");
stdout += iconv.decode(data, encoding); stdout += iconv.decode(data, encoding);
} catch { } catch {
// 如果 iconv-lite 不可用,使用默认解码 // 如果 iconv-lite 不可用,使用默认解码
stdout += data.toString('utf8'); stdout += data.toString("utf8");
} }
}); });
child.stderr.on("data", (data) => { child.stderr.on("data", (data) => {
try { try {
const iconv = require('iconv-lite'); const iconv = require("iconv-lite");
stderr += iconv.decode(data, encoding); stderr += iconv.decode(data, encoding);
} catch { } catch {
stderr += data.toString('utf8'); stderr += data.toString("utf8");
} }
}); });
@ -93,7 +93,7 @@ export interface VCDGenerationResult {
* 检查项目中的 Verilog 文件完整性 * 检查项目中的 Verilog 文件完整性
*/ */
export async function checkVerilogProject( export async function checkVerilogProject(
projectPath: string projectPath: string,
): Promise<VerilogProjectCheck> { ): Promise<VerilogProjectCheck> {
const result: VerilogProjectCheck = { const result: VerilogProjectCheck = {
isComplete: false, isComplete: false,
@ -164,7 +164,7 @@ export async function checkVerilogProject(
return result; return result;
} catch (error) { } catch (error) {
result.errors.push( result.errors.push(
`检查项目时出错: ${error instanceof Error ? error.message : "未知错误"}` `检查项目时出错: ${error instanceof Error ? error.message : "未知错误"}`,
); );
return result; return result;
} }
@ -209,12 +209,30 @@ async function getIverilogPath(extensionPath: string): Promise<string> {
let iverilogBin = ""; let iverilogBin = "";
if (platform === "win32") { if (platform === "win32") {
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog.exe"); iverilogBin = path.join(
extensionPath,
"tools",
"iverilog",
"bin",
"iverilog.exe",
);
} else if (platform === "darwin") { } else if (platform === "darwin") {
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog"); iverilogBin = path.join(
extensionPath,
"tools",
"iverilog",
"bin",
"iverilog",
);
} else { } else {
// Linux // Linux
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog"); iverilogBin = path.join(
extensionPath,
"tools",
"iverilog",
"bin",
"iverilog",
);
} }
// 如果插件包中没有,尝试使用系统安装的 iverilog // 如果插件包中没有,尝试使用系统安装的 iverilog
@ -258,7 +276,7 @@ async function getVvpPath(extensionPath: string): Promise<string> {
*/ */
export async function generateVCD( export async function generateVCD(
projectPath: string, projectPath: string,
extensionPath: string extensionPath: string,
): Promise<VCDGenerationResult> { ): Promise<VCDGenerationResult> {
try { try {
// 1. 检查项目完整性 // 1. 检查项目完整性
@ -302,12 +320,27 @@ export async function generateVCD(
} catch (error: any) { } catch (error: any) {
return { return {
success: false, success: false,
message: `iverilog 编译失败:\n${error.message}`, message: `IC Coder编译器编译失败:\n${error.message}`,
stderr: error.stderr, stderr: error.stderr,
stdout: error.stdout, stdout: error.stdout,
}; };
} }
// 6.5. 删除 shebang 行(修复 Windows 上的 vvp 解析错误)
try {
const fs = require("fs");
const vvpContent = fs.readFileSync(outputFile, "utf8");
const lines = vvpContent.split("\n");
if (lines.length > 0 && lines[0].startsWith("#!")) {
const cleanedContent = lines.slice(1).join("\n");
fs.writeFileSync(outputFile, cleanedContent, "utf8");
console.log("已删除 .vvp 文件的 shebang 行");
}
} catch (error) {
console.warn("删除 shebang 失败,继续执行:", error);
}
// 7. 执行仿真生成 VCD // 7. 执行仿真生成 VCD
const simArgs = [outputFile]; const simArgs = [outputFile];
console.log("执行仿真命令:", vvpPath, simArgs.join(" ")); console.log("执行仿真命令:", vvpPath, simArgs.join(" "));
@ -331,13 +364,17 @@ export async function generateVCD(
const projectUri = vscode.Uri.file(projectPath); const projectUri = vscode.Uri.file(projectPath);
const entries = await vscode.workspace.fs.readDirectory(projectUri); const entries = await vscode.workspace.fs.readDirectory(projectUri);
const vcdFiles = entries const vcdFiles = entries
.filter(([fileName, fileType]) => fileType === vscode.FileType.File && fileName.endsWith('.vcd')) .filter(
([fileName, fileType]) =>
fileType === vscode.FileType.File && fileName.endsWith(".vcd"),
)
.map(([fileName]) => fileName); .map(([fileName]) => fileName);
if (vcdFiles.length === 0) { if (vcdFiles.length === 0) {
return { return {
success: false, success: false,
message: "VCD 文件未生成。请确保 testbench 中包含 $dumpfile 和 $dumpvars 语句。", message:
"VCD 文件未生成。请确保 testbench 中包含 $dumpfile 和 $dumpvars 语句。",
stdout: simResult.stdout, stdout: simResult.stdout,
}; };
} }
@ -373,7 +410,7 @@ export async function generateVCD(
* 检查 iverilog 是否可用 * 检查 iverilog 是否可用
*/ */
export async function checkIverilogAvailable( export async function checkIverilogAvailable(
extensionPath: string extensionPath: string,
): Promise<{ available: boolean; version?: string; message: string }> { ): Promise<{ available: boolean; version?: string; message: string }> {
try { try {
const iverilogPath = await getIverilogPath(extensionPath); const iverilogPath = await getIverilogPath(extensionPath);
@ -385,7 +422,7 @@ export async function checkIverilogAvailable(
} catch (error) { } catch (error) {
return { return {
available: false, available: false,
message: `iverilog 不可用。未找到文件: ${iverilogPath}`, message: `IC Coder编译器不可用。未找到文件: ${iverilogPath}`,
}; };
} }
@ -404,12 +441,12 @@ export async function checkIverilogAvailable(
return { return {
available: true, available: true,
version: version, version: version,
message: `iverilog 可用: ${version}`, message: `IC Coder编译器可用: ${version}`,
}; };
} catch (error: any) { } catch (error: any) {
return { return {
available: false, available: false,
message: `iverilog 执行失败: ${error.message}\n${error.stderr || ""}`, message: `IC Coder编译器执行失败: ${error.message}\n${error.stderr || ""}`,
}; };
} }
} }
@ -418,8 +455,8 @@ export async function checkIverilogAvailable(
* 要 dump 的模块定义 * 要 dump 的模块定义
*/ */
export interface DumpModule { export interface DumpModule {
name: string; // 模块名(用于 VCD 文件名和宏名) name: string; // 模块名(用于 VCD 文件名和宏名)
path: string; // 实例路径(如 dut.u_tx path: string; // 实例路径(如 dut.u_tx
} }
/** /**
@ -444,10 +481,11 @@ export interface MultiVCDResult {
function injectConditionalDump( function injectConditionalDump(
tbContent: string, tbContent: string,
dumpModules: DumpModule[], dumpModules: DumpModule[],
vcdDir: string vcdDir: string,
): string { ): string {
// 匹配 $dumpfile 和 $dumpvars 语句(可能跨多行) // 匹配 $dumpfile 和 $dumpvars 语句(可能跨多行)
const dumpPattern = /(\$dumpfile\s*\([^)]+\)\s*;[\s\S]*?\$dumpvars\s*\([^)]+\)\s*;)/g; const dumpPattern =
/(\$dumpfile\s*\([^)]+\)\s*;[\s\S]*?\$dumpvars\s*\([^)]+\)\s*;)/g;
// 生成条件编译代码 // 生成条件编译代码
const conditionalCode = generateConditionalDumpCode(dumpModules, vcdDir); const conditionalCode = generateConditionalDumpCode(dumpModules, vcdDir);
@ -469,7 +507,7 @@ function injectConditionalDump(
*/ */
function generateConditionalDumpCode( function generateConditionalDumpCode(
dumpModules: DumpModule[], dumpModules: DumpModule[],
vcdDir: string vcdDir: string,
): string { ): string {
if (dumpModules.length === 0) { if (dumpModules.length === 0) {
return '$dumpfile("output.vcd");\n $dumpvars(0, dut);'; return '$dumpfile("output.vcd");\n $dumpvars(0, dut);';
@ -480,7 +518,7 @@ function generateConditionalDumpCode(
dumpModules.forEach((module, index) => { dumpModules.forEach((module, index) => {
const macroName = `DUMP_${module.name.toUpperCase()}`; const macroName = `DUMP_${module.name.toUpperCase()}`;
const vcdPath = `${vcdDir}/${module.name}.vcd`; const vcdPath = `${vcdDir}/${module.name}.vcd`;
const directive = index === 0 ? '`ifdef' : '`elsif'; const directive = index === 0 ? "`ifdef" : "`elsif";
lines.push(`${directive} ${macroName}`); lines.push(`${directive} ${macroName}`);
lines.push(` $dumpfile("${vcdPath}");`); lines.push(` $dumpfile("${vcdPath}");`);
@ -488,12 +526,12 @@ function generateConditionalDumpCode(
}); });
// 添加默认分支(使用第一个模块) // 添加默认分支(使用第一个模块)
lines.push('`else'); lines.push("`else");
lines.push(` $dumpfile("${vcdDir}/${dumpModules[0].name}.vcd");`); lines.push(` $dumpfile("${vcdDir}/${dumpModules[0].name}.vcd");`);
lines.push(` $dumpvars(1, ${dumpModules[0].path});`); lines.push(` $dumpvars(1, ${dumpModules[0].path});`);
lines.push('`endif'); lines.push("`endif");
return lines.join('\n'); return lines.join("\n");
} }
/** /**
@ -504,10 +542,10 @@ export async function generateMultiVCD(
extensionPath: string, extensionPath: string,
tbPath: string, tbPath: string,
dumpModules: DumpModule[], dumpModules: DumpModule[],
vcdDir: string = 'vcd' vcdDir: string = "vcd",
): Promise<MultiVCDResult> { ): Promise<MultiVCDResult> {
const results: MultiVCDResult['vcdFiles'] = []; const results: MultiVCDResult["vcdFiles"] = [];
let allStdout = ''; let allStdout = "";
try { try {
// 1. 创建 vcd 目录 // 1. 创建 vcd 目录
@ -520,16 +558,21 @@ export async function generateMultiVCD(
} }
// 2. 读取原始 testbench // 2. 读取原始 testbench
const tbFullPath = path.isAbsolute(tbPath) ? tbPath : path.join(projectPath, tbPath); const tbFullPath = path.isAbsolute(tbPath)
? tbPath
: path.join(projectPath, tbPath);
const tbUri = vscode.Uri.file(tbFullPath); const tbUri = vscode.Uri.file(tbFullPath);
const tbBytes = await vscode.workspace.fs.readFile(tbUri); const tbBytes = await vscode.workspace.fs.readFile(tbUri);
const originalTb = Buffer.from(tbBytes).toString('utf-8'); const originalTb = Buffer.from(tbBytes).toString("utf-8");
// 3. 注入条件编译代码 // 3. 注入条件编译代码
const modifiedTb = injectConditionalDump(originalTb, dumpModules, vcdDir); const modifiedTb = injectConditionalDump(originalTb, dumpModules, vcdDir);
await vscode.workspace.fs.writeFile(tbUri, Buffer.from(modifiedTb, 'utf-8')); await vscode.workspace.fs.writeFile(
tbUri,
Buffer.from(modifiedTb, "utf-8"),
);
console.log('[generateMultiVCD] Testbench 已修改,开始多次仿真...'); console.log("[generateMultiVCD] Testbench 已修改,开始多次仿真...");
// 4. 获取工具路径 // 4. 获取工具路径
const iverilogPath = await getIverilogPath(extensionPath); const iverilogPath = await getIverilogPath(extensionPath);
@ -554,27 +597,34 @@ export async function generateMultiVCD(
// 编译(带宏定义) // 编译(带宏定义)
const compileArgs = [ const compileArgs = [
`-D${macroName}`, `-D${macroName}`,
"-o", outputFile, "-o",
...projectCheck.allVerilogFiles outputFile,
...projectCheck.allVerilogFiles,
]; ];
await execCommand(iverilogPath, compileArgs, { cwd: projectPath, env }); await execCommand(iverilogPath, compileArgs, { cwd: projectPath, env });
// 仿真 // 仿真
const simResult = await execCommand(vvpPath, [outputFile], { cwd: projectPath, env }); const simResult = await execCommand(vvpPath, [outputFile], {
cwd: projectPath,
env,
});
allStdout += `\n[${module.name}] ${simResult.stdout}`; allStdout += `\n[${module.name}] ${simResult.stdout}`;
results.push({ results.push({
moduleName: module.name, moduleName: module.name,
vcdPath: vcdPath, vcdPath: vcdPath,
success: true success: true,
}); });
} catch (error: any) { } catch (error: any) {
console.error(`[generateMultiVCD] 模块 ${module.name} 仿真失败:`, error.message); console.error(
`[generateMultiVCD] 模块 ${module.name} 仿真失败:`,
error.message,
);
results.push({ results.push({
moduleName: module.name, moduleName: module.name,
vcdPath: vcdPath, vcdPath: vcdPath,
success: false, success: false,
error: error.message error: error.message,
}); });
// 继续执行其他模块 // 继续执行其他模块
} }
@ -587,19 +637,18 @@ export async function generateMultiVCD(
// 忽略 // 忽略
} }
const successCount = results.filter(r => r.success).length; const successCount = results.filter((r) => r.success).length;
return { return {
success: successCount > 0, success: successCount > 0,
vcdFiles: results, vcdFiles: results,
message: `生成完成:${successCount}/${dumpModules.length} 个 VCD 文件成功`, message: `生成完成:${successCount}/${dumpModules.length} 个 VCD 文件成功`,
stdout: allStdout stdout: allStdout,
}; };
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
vcdFiles: results, vcdFiles: results,
message: `生成多 VCD 文件失败: ${error instanceof Error ? error.message : '未知错误'}` message: `生成多 VCD 文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
}; };
} }
} }

View File

@ -19,10 +19,6 @@ import { dialogManager, DialogSession } from "../services/dialogService";
import { userInteractionManager } from "../services/userInteraction"; import { userInteractionManager } from "../services/userInteraction";
import { healthCheck } from "../services/apiClient"; import { healthCheck } from "../services/apiClient";
import { isTokenExpired } from "./jwtUtils"; import { isTokenExpired } from "./jwtUtils";
import {
checkBalanceBeforeSend,
fetchBalance,
} from "../services/creditsService";
import { optimizePrompt } from "../services/promptOptimizeService"; import { optimizePrompt } from "../services/promptOptimizeService";
import { NotificationService } from "../services/notificationService"; import { NotificationService } from "../services/notificationService";
import { TrialExpirationService } from "../services/trialExpirationService"; import { TrialExpirationService } from "../services/trialExpirationService";
@ -41,7 +37,11 @@ let currentSession: DialogSession | null = null;
/** 最后一个活跃的 taskId用于压缩等操作 */ /** 最后一个活跃的 taskId用于压缩等操作 */
let lastTaskId: string | null = null; let lastTaskId: string | null = null;
async function trackFileChange(filePath: string, oldContent: string, newContent: string): Promise<void> { async function trackFileChange(
filePath: string,
oldContent: string,
newContent: string,
): Promise<void> {
try { try {
changeTracker.trackChange(filePath, oldContent, newContent); changeTracker.trackChange(filePath, oldContent, newContent);
} catch (error) { } catch (error) {
@ -58,17 +58,19 @@ export async function handleUserMessage(
extensionPath?: string, extensionPath?: string,
mode?: RunMode, mode?: RunMode,
serviceTier?: ServiceTier, // 服务等级参数 serviceTier?: ServiceTier, // 服务等级参数
contextItems?: Array<{ id: number; type: string; path: string }> // 上下文项参数 contextItems?: Array<{ id: number; type: string; path: string }>, // 上下文项参数
) { ) {
console.log("收到用户消息:", text); console.log("收到用户消息:", text);
// 检查 token 是否过期 // 【已禁用】检查 token 是否过期 - 无需登录
const context = (panel as any).__context; const context = (panel as any).__context;
if (context) { if (false && context) {
// 从 session 中获取 token // 从 session 中获取 token
let token: string | undefined; let token: string | undefined;
try { try {
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false }); const session = await vscode.authentication.getSession("iccoder", [], {
createIfNone: false,
});
token = session?.accessToken; token = session?.accessToken;
} catch (error) { } catch (error) {
console.warn("[MessageHandler] 获取 session 失败:", error); console.warn("[MessageHandler] 获取 session 失败:", error);
@ -78,20 +80,20 @@ export async function handleUserMessage(
console.warn("[MessageHandler] 未登录,阻止发送"); console.warn("[MessageHandler] 未登录,阻止发送");
// 保存待发送的消息 // 保存待发送的消息
await context.globalState.update('pendingMessage', { await context.globalState.update("pendingMessage", {
text, text,
mode, mode,
serviceTier, serviceTier,
timestamp: Date.now() timestamp: Date.now(),
}); });
// 显示弹窗提示 // 显示弹窗提示
const action = await vscode.window.showWarningMessage( const action = await vscode.window.showWarningMessage(
'请先登录后再发送消息', "请先登录后再发送消息",
'立即登录' "立即登录",
); );
if (action === '立即登录') { if (action === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", { vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true, forceReauth: true,
}); });
@ -106,32 +108,34 @@ export async function handleUserMessage(
return; return;
} }
if (isTokenExpired(token)) { if (token && isTokenExpired(token as string)) {
console.warn("[MessageHandler] Token 已过期,阻止发送"); console.warn("[MessageHandler] Token 已过期,阻止发送");
// 保存待发送的消息 // 保存待发送的消息
await context.globalState.update('pendingMessage', { await context.globalState.update("pendingMessage", {
text, text,
mode, mode,
serviceTier, serviceTier,
timestamp: Date.now() timestamp: Date.now(),
}); });
// 清除过期的 session // 清除过期的 session
await context.globalState.update('icCoderSessions', []); await context.globalState.update("icCoderSessions", []);
await context.globalState.update('icCoderUserInfo', undefined); await context.globalState.update("icCoderUserInfo", undefined);
// 显示弹窗提示 // 显示弹窗提示
/*
const action = await vscode.window.showWarningMessage( const action = await vscode.window.showWarningMessage(
'登录已过期,请重新登录', "登录已过期,请重新登录",
'立即登录' "立即登录",
); );
if (action === '立即登录') { if (action === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", { vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true, forceReauth: true,
}); });
} }
*/
// 恢复输入状态 // 恢复输入状态
panel.webview.postMessage({ panel.webview.postMessage({
@ -183,29 +187,6 @@ export async function handleUserMessage(
return; return;
} }
// 发送前检测余额
const balanceCheck = await checkBalanceBeforeSend();
if (!balanceCheck.allowed) {
console.warn("[MessageHandler] 余额不足,阻止发送:", balanceCheck.message);
// 显示错误提示
const selection = await vscode.window.showWarningMessage(
balanceCheck.message || "资源点余额不足",
"去充值"
);
if (selection === "去充值") {
vscode.env.openExternal(
vscode.Uri.parse("https://iccoder.com/memberCenter")
);
}
// 恢复输入状态
panel.webview.postMessage({
command: "updateSegments",
segments: [],
isComplete: true,
});
return;
}
// 尝试使用后端服务 // 尝试使用后端服务
if (useBackendService && extensionPath) { if (useBackendService && extensionPath) {
try { try {
@ -216,14 +197,14 @@ export async function handleUserMessage(
mode, mode,
undefined, undefined,
serviceTier, serviceTier,
contextItems contextItems,
); );
return; return;
} catch (error) { } catch (error) {
console.error("后端服务不可用:", error); console.error("处理用户消息失败:", error);
panel.webview.postMessage({ panel.webview.postMessage({
command: "updateStatus", command: "updateStatus",
text: "后端服务不可用", text: "处理用户消息失败,请稍后重试",
type: "error", type: "error",
}); });
// 恢复输入状态 // 恢复输入状态
@ -254,7 +235,7 @@ async function handleUserMessageWithBackend(
mode?: RunMode, mode?: RunMode,
reuseTaskId?: string, // 可选,复用现有 taskId用于 Plan 模式确认后继续执行) reuseTaskId?: string, // 可选,复用现有 taskId用于 Plan 模式确认后继续执行)
serviceTier?: ServiceTier, // 服务等级参数 serviceTier?: ServiceTier, // 服务等级参数
contextItems?: Array<{ id: number; type: string; path: string }> // 上下文项参数 contextItems?: Array<{ id: number; type: string; path: string }>, // 上下文项参数
): Promise<void> { ): Promise<void> {
const historyManager = ChatHistoryManager.getInstance(); const historyManager = ChatHistoryManager.getInstance();
@ -262,7 +243,7 @@ async function handleUserMessageWithBackend(
let enhancedText = text; let enhancedText = text;
if (contextItems && contextItems.length > 0) { if (contextItems && contextItems.length > 0) {
console.log("[MessageHandler] 处理上下文项:", contextItems.length); console.log("[MessageHandler] 处理上下文项:", contextItems.length);
const paths = contextItems.map(item => item.path).join('\n'); const paths = contextItems.map((item) => item.path).join("\n");
enhancedText = `${paths}\n\n${text}`; enhancedText = `${paths}\n\n${text}`;
} }
@ -273,7 +254,7 @@ async function handleUserMessageWithBackend(
// 创建会话dialogManager 会自动处理旧会话的中止) // 创建会话dialogManager 会自动处理旧会话的中止)
currentSession = dialogManager.createSession( currentSession = dialogManager.createSession(
extensionPath, extensionPath,
taskIdToUse || undefined taskIdToUse || undefined,
); );
// 保存 taskId 用于后续操作(如压缩) // 保存 taskId 用于后续操作(如压缩)
lastTaskId = currentSession.getTaskId(); lastTaskId = currentSession.getTaskId();
@ -281,7 +262,7 @@ async function handleUserMessageWithBackend(
"[MessageHandler] 创建会话: taskId=", "[MessageHandler] 创建会话: taskId=",
lastTaskId, lastTaskId,
"来源=", "来源=",
taskIdToUse ? "historyManager" : "新生成" taskIdToUse ? "historyManager" : "新生成",
); );
// 显示状态栏 // 显示状态栏
@ -324,7 +305,10 @@ async function handleUserMessageWithBackend(
// 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新 // 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新
}, },
onQuestion: (askId: string, questions: import("../types/api").QuestionItem[]) => { onQuestion: (
askId: string,
questions: import("../types/api").QuestionItem[],
) => {
// 只更新状态栏,问题显示由 onSegmentUpdate 统一处理 // 只更新状态栏,问题显示由 onSegmentUpdate 统一处理
panel.webview.postMessage({ panel.webview.postMessage({
command: "updateStatus", command: "updateStatus",
@ -351,17 +335,6 @@ async function handleUserMessageWithBackend(
console.error("[MessageHandler] 保存AI响应历史失败:", error); console.error("[MessageHandler] 保存AI响应历史失败:", error);
} }
// 对话完成后重新获取余额(因为已经消耗了 Credits
try {
console.log("[MessageHandler] 对话完成,重新获取余额...");
const newBalance = await fetchBalance();
if (newBalance !== null) {
console.log("[MessageHandler] 余额已更新:", newBalance);
}
} catch (error) {
console.error("[MessageHandler] 获取余额失败:", error);
}
// 尝试更新面板(如果面板已关闭,这些操作会失败,但不影响数据保存) // 尝试更新面板(如果面板已关闭,这些操作会失败,但不影响数据保存)
try { try {
// 隐藏状态栏 // 隐藏状态栏
@ -379,18 +352,21 @@ async function handleUserMessageWithBackend(
// 发送系统通知 - AI 响应完成 // 发送系统通知 - AI 响应完成
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.success( notificationService.success(
'IC Coder - AI 响应完成', "IC Coder - AI 响应完成",
'您的问题已得到回复,点击查看详情', "您的问题已得到回复,点击查看详情",
() => { () => {
// 点击通知时聚焦到面板 // 点击通知时聚焦到面板
panel.reveal(); panel.reveal();
} },
); );
// 发送代码变更到前端 // 发送代码变更到前端
sendChangesToWebview(panel); sendChangesToWebview(panel);
} catch (error) { } catch (error) {
console.warn("[MessageHandler] 更新面板失败(面板可能已关闭):", error); console.warn(
"[MessageHandler] 更新面板失败(面板可能已关闭):",
error,
);
} }
resolve(); resolve();
@ -402,7 +378,7 @@ async function handleUserMessageWithBackend(
}); });
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveMessage", command: "receiveMessage",
text: `错误: ${message}`, text: `错误: ${message}`,
}); });
// 恢复输入状态 // 恢复输入状态
panel.webview.postMessage({ panel.webview.postMessage({
@ -458,7 +434,7 @@ async function handleUserMessageWithBackend(
}, },
}, },
mode, mode,
serviceTier // 传递服务等级 serviceTier, // 传递服务等级
); );
}); });
} }
@ -470,7 +446,7 @@ export async function handleUserAnswer(
askId: string, askId: string,
selected?: string[], selected?: string[],
customInput?: string, customInput?: string,
answers?: { [questionIndex: string]: string[] } answers?: { [questionIndex: string]: string[] },
): Promise<void> { ): Promise<void> {
if (currentSession) { if (currentSession) {
await currentSession.submitAnswer(askId, selected, customInput, answers); await currentSession.submitAnswer(askId, selected, customInput, answers);
@ -540,7 +516,7 @@ export async function handlePlanAction(
action: string, action: string,
planTitle: string, planTitle: string,
extensionPath: string, extensionPath: string,
serviceTier?: ServiceTier serviceTier?: ServiceTier,
): Promise<void> { ): Promise<void> {
console.log( console.log(
"[handlePlanAction] action:", "[handlePlanAction] action:",
@ -548,7 +524,7 @@ export async function handlePlanAction(
"planTitle:", "planTitle:",
planTitle, planTitle,
"serviceTier:", "serviceTier:",
serviceTier serviceTier,
); );
switch (action) { switch (action) {
@ -564,7 +540,7 @@ export async function handlePlanAction(
`请按照刚才的计划执行:${planTitle}`, `请按照刚才的计划执行:${planTitle}`,
extensionPath, extensionPath,
"agent", "agent",
serviceTier serviceTier,
); );
break; break;
@ -581,7 +557,7 @@ export async function handlePlanAction(
`请根据以下建议修改计划:${modification}`, `请根据以下建议修改计划:${modification}`,
extensionPath, extensionPath,
"plan", "plan",
serviceTier serviceTier,
); );
} }
break; break;
@ -636,7 +612,7 @@ function parseFileOperation(text: string): {
// 匹配重命名文件:将 xxx.ts 重命名为 yyy.ts 或 把 xxx.ts 改名为 yyy.ts优先匹配避免被修改匹配 // 匹配重命名文件:将 xxx.ts 重命名为 yyy.ts 或 把 xxx.ts 改名为 yyy.ts优先匹配避免被修改匹配
const renameMatch = lowerText.match( const renameMatch = lowerText.match(
/(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/ /(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/,
); );
if (renameMatch) { if (renameMatch) {
const oldPath = renameMatch[1].trim(); const oldPath = renameMatch[1].trim();
@ -653,7 +629,7 @@ function parseFileOperation(text: string): {
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb" // 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
// 格式3: 将 xxx.ts 文件 'aaa' 替换为 'bbb' // 格式3: 将 xxx.ts 文件 'aaa' 替换为 'bbb'
const replaceMatch1 = lowerText.match( const replaceMatch1 = lowerText.match(
/在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/ /在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/,
); );
if (replaceMatch1) { if (replaceMatch1) {
const filePath = replaceMatch1[1].trim(); const filePath = replaceMatch1[1].trim();
@ -669,7 +645,7 @@ function parseFileOperation(text: string): {
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb" // 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
const replaceMatch2 = lowerText.match( const replaceMatch2 = lowerText.match(
/(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/ /(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/,
); );
if (replaceMatch2) { if (replaceMatch2) {
const filePath = replaceMatch2[1].trim(); const filePath = replaceMatch2[1].trim();
@ -718,7 +694,7 @@ async function handleFileOperation(
newPath?: string; newPath?: string;
searchText?: string; searchText?: string;
replaceText?: string; replaceText?: string;
} },
) { ) {
const historyManager = ChatHistoryManager.getInstance(); const historyManager = ChatHistoryManager.getInstance();
@ -734,7 +710,7 @@ async function handleFileOperation(
text: responseText, text: responseText,
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
`文件创建成功: ${operation.filePath}` `文件创建成功: ${operation.filePath}`,
); );
await historyManager.addAiMessage(responseText); await historyManager.addAiMessage(responseText);
break; break;
@ -747,7 +723,7 @@ async function handleFileOperation(
text: responseText, text: responseText,
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
`文件删除成功: ${operation.filePath}` `文件删除成功: ${operation.filePath}`,
); );
await historyManager.addAiMessage(responseText); await historyManager.addAiMessage(responseText);
break; break;
@ -783,7 +759,7 @@ async function handleFileOperation(
text: responseText, text: responseText,
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
`文件重命名成功: ${operation.filePath}${operation.newPath}` `文件重命名成功: ${operation.filePath}${operation.newPath}`,
); );
await historyManager.addAiMessage(responseText); await historyManager.addAiMessage(responseText);
break; break;
@ -792,21 +768,29 @@ async function handleFileOperation(
if (!operation.searchText || !operation.replaceText) { if (!operation.searchText || !operation.replaceText) {
throw new Error("缺少替换内容"); throw new Error("缺少替换内容");
} }
const oldContentBeforeReplace = await readFileContent(operation.filePath); const oldContentBeforeReplace = await readFileContent(
operation.filePath,
);
await replaceFile( await replaceFile(
operation.filePath, operation.filePath,
operation.searchText, operation.searchText,
operation.replaceText operation.replaceText,
);
const newContentAfterReplace = await readFileContent(
operation.filePath,
);
await trackFileChange(
operation.filePath,
oldContentBeforeReplace,
newContentAfterReplace,
); );
const newContentAfterReplace = await readFileContent(operation.filePath);
await trackFileChange(operation.filePath, oldContentBeforeReplace, newContentAfterReplace);
responseText = `✅ 文件内容替换成功: ${operation.filePath}`; responseText = `✅ 文件内容替换成功: ${operation.filePath}`;
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveMessage", command: "receiveMessage",
text: responseText, text: responseText,
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
`文件内容替换成功: ${operation.filePath}` `文件内容替换成功: ${operation.filePath}`,
); );
await historyManager.addAiMessage(responseText); await historyManager.addAiMessage(responseText);
break; break;
@ -866,7 +850,7 @@ function getDefaultContent(filePath: string): string {
*/ */
export async function handleReadFile( export async function handleReadFile(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
filePath: string filePath: string,
) { ) {
try { try {
const content = await readFileContent(filePath); const content = await readFileContent(filePath);
@ -890,7 +874,7 @@ export async function handleCreateFile(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
filePath: string, filePath: string,
content: string, content: string,
overwrite: boolean = false //是否覆盖 overwrite: boolean = false, //是否覆盖
) { ) {
try { try {
if (overwrite) { if (overwrite) {
@ -909,11 +893,14 @@ export async function handleCreateFile(
// 发送系统通知 // 发送系统通知
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.success( notificationService.success(
'IC Coder - 文件创建', "IC Coder - 文件创建",
`文件已创建: ${path.basename(filePath)}`, `文件已创建: ${path.basename(filePath)}`,
() => { () => {
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath)); vscode.commands.executeCommand(
} "vscode.open",
vscode.Uri.file(filePath),
);
},
); );
} catch (error) { } catch (error) {
panel.webview.postMessage({ panel.webview.postMessage({
@ -921,7 +908,7 @@ export async function handleCreateFile(
error: error instanceof Error ? error.message : "创建文件失败", error: error instanceof Error ? error.message : "创建文件失败",
}); });
vscode.window.showErrorMessage( vscode.window.showErrorMessage(
`创建文件失败: ${error instanceof Error ? error.message : "未知错误"}` `创建文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
); );
} }
} }
@ -932,7 +919,7 @@ export async function handleCreateFile(
export async function handleUpdateFile( export async function handleUpdateFile(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
filePath: string, filePath: string,
content: string content: string,
) { ) {
try { try {
const oldContent = await readFileContent(filePath); const oldContent = await readFileContent(filePath);
@ -948,8 +935,8 @@ export async function handleUpdateFile(
// 发送系统通知 // 发送系统通知
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.info( notificationService.info(
'IC Coder - 文件更新', "IC Coder - 文件更新",
`文件已更新: ${path.basename(filePath)}` `文件已更新: ${path.basename(filePath)}`,
); );
} catch (error) { } catch (error) {
panel.webview.postMessage({ panel.webview.postMessage({
@ -957,7 +944,7 @@ export async function handleUpdateFile(
error: error instanceof Error ? error.message : "更新文件失败", error: error instanceof Error ? error.message : "更新文件失败",
}); });
vscode.window.showErrorMessage( vscode.window.showErrorMessage(
`更新文件失败: ${error instanceof Error ? error.message : "未知错误"}` `更新文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
); );
} }
} }
@ -968,7 +955,7 @@ export async function handleUpdateFile(
export async function handleRenameFile( export async function handleRenameFile(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
oldPath: string, oldPath: string,
newPath: string newPath: string,
) { ) {
try { try {
await renameFile(oldPath, newPath); await renameFile(oldPath, newPath);
@ -979,7 +966,7 @@ export async function handleRenameFile(
message: "文件重命名成功", message: "文件重命名成功",
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
`文件重命名成功: ${oldPath}${newPath}` `文件重命名成功: ${oldPath}${newPath}`,
); );
} catch (error) { } catch (error) {
panel.webview.postMessage({ panel.webview.postMessage({
@ -987,7 +974,7 @@ export async function handleRenameFile(
error: error instanceof Error ? error.message : "重命名文件失败", error: error instanceof Error ? error.message : "重命名文件失败",
}); });
vscode.window.showErrorMessage( vscode.window.showErrorMessage(
`重命名文件失败: ${error instanceof Error ? error.message : "未知错误"}` `重命名文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
); );
} }
} }
@ -999,7 +986,7 @@ export async function handleReplaceInFile(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
filePath: string, filePath: string,
searchText: string, searchText: string,
replaceText: string replaceText: string,
) { ) {
try { try {
const oldContent = await readFileContent(filePath); const oldContent = await readFileContent(filePath);
@ -1018,7 +1005,7 @@ export async function handleReplaceInFile(
error: error instanceof Error ? error.message : "替换文件内容失败", error: error instanceof Error ? error.message : "替换文件内容失败",
}); });
vscode.window.showErrorMessage( vscode.window.showErrorMessage(
`替换文件内容失败: ${error instanceof Error ? error.message : "未知错误"}` `替换文件内容失败: ${error instanceof Error ? error.message : "未知错误"}`,
); );
} }
} }
@ -1063,7 +1050,7 @@ function isVCDGenerationCommand(text: string): boolean {
*/ */
async function handleVCDGeneration( async function handleVCDGeneration(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
extensionPath: string extensionPath: string,
) { ) {
try { try {
// 获取当前工作区路径 // 获取当前工作区路径
@ -1090,7 +1077,7 @@ async function handleVCDGeneration(
if (!iverilogCheck.available) { if (!iverilogCheck.available) {
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveMessage", command: "receiveMessage",
text: `${iverilogCheck.message}\n\n请参考插件文档安装 iverilog 工具`, text: `${iverilogCheck.message}`,
}); });
vscode.window.showErrorMessage(iverilogCheck.message); vscode.window.showErrorMessage(iverilogCheck.message);
return; return;
@ -1165,12 +1152,15 @@ async function handleVCDGeneration(
// 发送系统通知 // 发送系统通知
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.success( notificationService.success(
'IC Coder - 仿真完成', "IC Coder - 仿真完成",
`VCD 文件已生成: ${fileName}`, `VCD 文件已生成: ${fileName}`,
() => { () => {
// 点击通知时打开 VCD 查看器 // 点击通知时打开 VCD 查看器
vscode.commands.executeCommand('ic-coder.openVCDViewer', result.vcdFilePath); vscode.commands.executeCommand(
} "ic-coder.openVCDViewer",
result.vcdFilePath,
);
},
); );
} else { } else {
panel.webview.postMessage({ panel.webview.postMessage({
@ -1199,12 +1189,12 @@ async function handleVCDGeneration(
// 发送系统通知 // 发送系统通知
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.error( notificationService.error(
'IC Coder - 仿真失败', "IC Coder - 仿真失败",
'VCD 文件生成失败,请查看错误信息', "VCD 文件生成失败,请查看错误信息",
() => { () => {
// 点击通知时聚焦到面板 // 点击通知时聚焦到面板
panel.reveal(); panel.reveal();
} },
); );
} }
} catch (error) { } catch (error) {
@ -1222,11 +1212,11 @@ async function handleVCDGeneration(
// 发送系统通知 // 发送系统通知
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.error( notificationService.error(
'IC Coder - 仿真错误', "IC Coder - 仿真错误",
error instanceof Error ? error.message : '生成 VCD 文件时出错', error instanceof Error ? error.message : "生成 VCD 文件时出错",
() => { () => {
panel.reveal(); panel.reveal();
} },
); );
} }
} }
@ -1236,7 +1226,7 @@ async function handleVCDGeneration(
*/ */
export async function handleOptimizePrompt( export async function handleOptimizePrompt(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
prompt: string prompt: string,
): Promise<void> { ): Promise<void> {
console.log("[MessageHandler] ========== 收到提示词优化请求 =========="); console.log("[MessageHandler] ========== 收到提示词优化请求 ==========");
console.log("[MessageHandler] prompt:", prompt); console.log("[MessageHandler] prompt:", prompt);
@ -1268,7 +1258,7 @@ export async function handleOptimizePrompt(
*/ */
export async function handleAcceptChange( export async function handleAcceptChange(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
changeId: string changeId: string,
) { ) {
try { try {
const success = await changeTracker.acceptChange(changeId); const success = await changeTracker.acceptChange(changeId);
@ -1276,14 +1266,14 @@ export async function handleAcceptChange(
panel.webview.postMessage({ panel.webview.postMessage({
command: "changeAccepted", command: "changeAccepted",
changeId: changeId, changeId: changeId,
success: true success: true,
}); });
} else { } else {
panel.webview.postMessage({ panel.webview.postMessage({
command: "changeAccepted", command: "changeAccepted",
changeId: changeId, changeId: changeId,
success: false, success: false,
error: "采纳变更失败" error: "采纳变更失败",
}); });
} }
} catch (error) { } catch (error) {
@ -1292,7 +1282,7 @@ export async function handleAcceptChange(
command: "changeAccepted", command: "changeAccepted",
changeId: changeId, changeId: changeId,
success: false, success: false,
error: String(error) error: String(error),
}); });
} }
} }
@ -1302,7 +1292,7 @@ export async function handleAcceptChange(
*/ */
export async function handleRejectChange( export async function handleRejectChange(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
changeId: string changeId: string,
) { ) {
try { try {
const success = await changeTracker.rejectChange(changeId); const success = await changeTracker.rejectChange(changeId);
@ -1310,14 +1300,14 @@ export async function handleRejectChange(
panel.webview.postMessage({ panel.webview.postMessage({
command: "changeRejected", command: "changeRejected",
changeId: changeId, changeId: changeId,
success: true success: true,
}); });
} else { } else {
panel.webview.postMessage({ panel.webview.postMessage({
command: "changeRejected", command: "changeRejected",
changeId: changeId, changeId: changeId,
success: false, success: false,
error: "拒绝变更失败" error: "拒绝变更失败",
}); });
} }
} catch (error) { } catch (error) {
@ -1326,7 +1316,7 @@ export async function handleRejectChange(
command: "changeRejected", command: "changeRejected",
changeId: changeId, changeId: changeId,
success: false, success: false,
error: String(error) error: String(error),
}); });
} }
} }
@ -1337,18 +1327,18 @@ export async function handleRejectChange(
export function sendChangesToWebview(panel: vscode.WebviewPanel) { export function sendChangesToWebview(panel: vscode.WebviewPanel) {
const session = changeTracker.endSession(); const session = changeTracker.endSession();
if (session && session.changes.length > 0) { if (session && session.changes.length > 0) {
const changesWithDiff = session.changes.map(change => { const changesWithDiff = session.changes.map((change) => {
const diffLines = generateDiff(change.oldContent, change.newContent); const diffLines = generateDiff(change.oldContent, change.newContent);
const diffHtml = renderDiffHtml(diffLines); const diffHtml = renderDiffHtml(diffLines);
return { return {
...change, ...change,
diffHtml diffHtml,
}; };
}); });
panel.webview.postMessage({ panel.webview.postMessage({
command: "showChanges", command: "showChanges",
changes: changesWithDiff changes: changesWithDiff,
}); });
} }
} }
@ -1365,62 +1355,67 @@ export function startChangeSession(sessionId: string) {
*/ */
export async function handleOpenFileDiff( export async function handleOpenFileDiff(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
changeId: string changeId: string,
) { ) {
try { try {
const session = changeTracker.getCurrentSession(); const session = changeTracker.getCurrentSession();
if (!session) { if (!session) {
vscode.window.showErrorMessage('没有找到变更会话'); vscode.window.showErrorMessage("没有找到变更会话");
return; return;
} }
const change = session.changes.find(c => c.changeId === changeId); const change = session.changes.find((c) => c.changeId === changeId);
if (!change) { if (!change) {
vscode.window.showErrorMessage('没有找到该变更'); vscode.window.showErrorMessage("没有找到该变更");
return; return;
} }
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) { if (!workspaceFolder) {
vscode.window.showErrorMessage('没有打开的工作区'); vscode.window.showErrorMessage("没有打开的工作区");
return; return;
} }
// 创建临时文件用于对比 // 创建临时文件用于对比
const filePath = change.filePath; const filePath = change.filePath;
const absolutePath = vscode.Uri.file( const absolutePath = vscode.Uri.file(
path.join(workspaceFolder.uri.fsPath, filePath) path.join(workspaceFolder.uri.fsPath, filePath),
); );
// 创建虚拟文档显示旧内容 // 创建虚拟文档显示旧内容
const oldUri = vscode.Uri.parse( const oldUri = vscode.Uri.parse(
`ic-coder-diff:${filePath}.old?${changeId}` `ic-coder-diff:${filePath}.old?${changeId}`,
).with({ scheme: 'ic-coder-diff' }); ).with({ scheme: "ic-coder-diff" });
// 注册文档内容提供者(如果还没注册) // 注册文档内容提供者(如果还没注册)
if (!(global as any).__diffProviderRegistered) { if (!(global as any).__diffProviderRegistered) {
const provider = new (class implements vscode.TextDocumentContentProvider { const provider = new (class
implements vscode.TextDocumentContentProvider
{
provideTextDocumentContent(uri: vscode.Uri): string { provideTextDocumentContent(uri: vscode.Uri): string {
const changeId = uri.query; const changeId = uri.query;
const session = changeTracker.getCurrentSession(); const session = changeTracker.getCurrentSession();
const change = session?.changes.find(c => c.changeId === changeId); const change = session?.changes.find((c) => c.changeId === changeId);
return change?.oldContent || ''; return change?.oldContent || "";
} }
})(); })();
vscode.workspace.registerTextDocumentContentProvider('ic-coder-diff', provider); vscode.workspace.registerTextDocumentContentProvider(
"ic-coder-diff",
provider,
);
(global as any).__diffProviderRegistered = true; (global as any).__diffProviderRegistered = true;
} }
// 打开 diff 编辑器 // 打开 diff 编辑器
await vscode.commands.executeCommand( await vscode.commands.executeCommand(
'vscode.diff', "vscode.diff",
oldUri, oldUri,
absolutePath, absolutePath,
`${filePath} (变更对比)` `${filePath} (变更对比)`,
); );
} catch (error) { } catch (error) {
console.error('[MessageHandler] 打开 diff 失败:', error); console.error("[MessageHandler] 打开 diff 失败:", error);
vscode.window.showErrorMessage(`打开 diff 失败: ${error}`); vscode.window.showErrorMessage(`打开 diff 失败: ${error}`);
} }
} }

View File

@ -13,6 +13,7 @@ import {
abortCurrentDialog, abortCurrentDialog,
handleOptimizePrompt, handleOptimizePrompt,
} from "../utils/messageHandler"; } from "../utils/messageHandler";
import { setCustomConfig } from "../config/settings";
/** /**
* 创建并显示IC 侧边栏视图 * 创建并显示IC 侧边栏视图
@ -141,6 +142,21 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
case "optimizePrompt": case "optimizePrompt":
handleOptimizePrompt(panel, message.prompt); handleOptimizePrompt(panel, message.prompt);
break; break;
// 保存通用设置
case "saveGeneralSettings":
context.globalState.update('generalSettings', message.settings);
// 更新运行时配置(包括清空)
setCustomConfig({ backendUrl: message.settings.backendUrl || '' });
vscode.window.showInformationMessage('设置已保存');
break;
// 加载通用设置
case "loadGeneralSettings":
const settings = context.globalState.get('generalSettings');
panel.webview.postMessage({
command: 'loadedGeneralSettings',
settings: settings
});
break;
} }
}, },
undefined, undefined,
@ -158,52 +174,21 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
private readonly extensionUri: vscode.Uri, private readonly extensionUri: vscode.Uri,
private readonly context: vscode.ExtensionContext private readonly context: vscode.ExtensionContext
) { ) {
// 监听认证状态变化 // 【已禁用】监听认证状态变化 - 无需登录
this.context.subscriptions.push(
vscode.authentication.onDidChangeSessions((e) => {
if (e.provider.id === "iccoder") {
this.refreshLoginStatus();
}
})
);
} }
/** /**
* 刷新登录状态并更新视图 * 【已禁用】刷新登录状态并更新视图 - 无需登录
*/ */
private async refreshLoginStatus(): Promise<void> { private async refreshLoginStatus(): Promise<void> {
if (this._view) { // 无需刷新登录状态
const isLoggedIn = await this.checkLoginStatus();
this._view.webview.html = this.getWebviewContent(
this._view.webview,
isLoggedIn
);
}
} }
/** /**
* 检查登录状态(使用 Authentication API * 【已禁用】检查登录状态 - 无需登录
*/ */
private async checkLoginStatus(): Promise<boolean> { private async checkLoginStatus(): Promise<boolean> {
try { return true; // 始终返回已登录状态
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
console.log("[ICViewProvider] 检查登录状态, session:", session ? "存在" : "不存在");
if (!session) {
return false;
}
// 检查 token 是否过期
const expired = isTokenExpired(session.accessToken);
console.log("[ICViewProvider] token 过期检查结果:", expired);
// 只有明确过期才认为未登录,无法判断时认为已登录
if (expired === true) {
console.log("[ICViewProvider] Token 已过期");
return false;
}
return true;
} catch (error) {
console.log("[ICViewProvider] 检查登录状态失败:", error);
return false;
}
} }
resolveWebviewView(webviewView: vscode.WebviewView) { resolveWebviewView(webviewView: vscode.WebviewView) {
@ -223,30 +208,8 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
console.log('[ICViewProvider] Webview options 已设置'); console.log('[ICViewProvider] Webview options 已设置');
console.log('[ICViewProvider] extensionUri:', this.extensionUri.toString()); console.log('[ICViewProvider] extensionUri:', this.extensionUri.toString());
// 【关键修复】先设置默认 HTML避免一直加载 // 【已禁用】登录检查 - 直接显示"开始使用"按钮
try { webviewView.webview.html = this.getWebviewContent(webviewView.webview, true);
const html = this.getWebviewContent(webviewView.webview, false);
console.log('[ICViewProvider] HTML 内容已生成,长度:', html.length);
webviewView.webview.html = html;
console.log('[ICViewProvider] HTML 已设置到 webview');
} catch (error) {
console.error('[ICViewProvider] 设置 HTML 失败:', error);
}
// 异步检查登录状态并更新 UI
this.checkLoginStatus()
.then((isLoggedIn) => {
console.log('[ICViewProvider] 登录状态检查完成:', isLoggedIn);
webviewView.webview.html = this.getWebviewContent(
webviewView.webview,
isLoggedIn
);
})
.catch((error) => {
console.error('[ICViewProvider] 检查登录状态失败:', error);
// 即使失败也显示未登录状态
webviewView.webview.html = this.getWebviewContent(webviewView.webview, false);
});
// 处理侧边栏的消息 // 处理侧边栏的消息
webviewView.webview.onDidReceiveMessage( webviewView.webview.onDidReceiveMessage(
@ -266,6 +229,20 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
if (message.url) { if (message.url) {
vscode.env.openExternal(vscode.Uri.parse(message.url)); vscode.env.openExternal(vscode.Uri.parse(message.url));
} }
} else if (message.command === "saveGeneralSettings") {
// 保存通用设置
this.context.globalState.update('generalSettings', message.settings);
if (message.settings.backendUrl) {
setCustomConfig({ backendUrl: message.settings.backendUrl });
}
vscode.window.showInformationMessage('设置已保存');
} else if (message.command === "loadGeneralSettings") {
// 加载通用设置
const settings = this.context.globalState.get('generalSettings');
webviewView.webview.postMessage({
command: 'loadedGeneralSettings',
settings: settings
});
} }
}, },
undefined, undefined,
@ -338,7 +315,7 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
<img src="${logoUri}" alt="IC Coder" width="120" /> <img src="${logoUri}" alt="IC Coder" width="120" />
<h2>欢迎使用 IC Coder</h2> <h2>欢迎使用 IC Coder</h2>
${isLoggedIn ${isLoggedIn
? '<button class="btn" onclick="openChat()">开始创作</button>' ? '<button class="btn" onclick="openChat()">开始使用</button>'
: '<button class="btn" onclick="login()">登录账户</button>' : '<button class="btn" onclick="login()">登录账户</button>'
} }
</div> </div>

View File

@ -51,7 +51,11 @@ export function getContextDisplayStyles(): string {
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.context-item:hover { .context-item.clickable {
cursor: pointer;
}
.context-item.clickable:hover {
background: var(--vscode-list-hoverBackground); background: var(--vscode-list-hoverBackground);
} }
@ -126,6 +130,11 @@ export function getContextDisplayScript(): string {
return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32z" fill="currentColor"/></svg>'; return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32z" fill="currentColor"/></svg>';
} }
// 获取代码图标 SVG
function getCodeIcon() {
return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z m-484.7-122.1l39.6-39.5 113.1 113.1-39.6 39.5-113.1-113.1z m226.4-290.2l113.1 113.1-39.6 39.5-113.1-113.1 39.6-39.5z" fill="currentColor"/></svg>';
}
// 获取删除图标 SVG // 获取删除图标 SVG
function getRemoveIcon() { function getRemoveIcon() {
return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9c-4.4 5.2-.7 13.1 6.1 13.1h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z" fill="currentColor"/></svg>'; return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9c-4.4 5.2-.7 13.1 6.1 13.1h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z" fill="currentColor"/></svg>';
@ -172,13 +181,17 @@ export function getContextDisplayScript(): string {
case 'folder': icon = getFolderIcon(); break; case 'folder': icon = getFolderIcon(); break;
case 'image': icon = getImageIcon(); break; case 'image': icon = getImageIcon(); break;
case 'document': icon = getDocumentIcon(); break; case 'document': icon = getDocumentIcon(); break;
case 'code': icon = getCodeIcon(); break;
} }
const clickable = item.type !== 'folder' ? 'clickable' : '';
const onclick = item.type !== 'folder' ? \`onclick="window.handleContextItemClick(\${item.id})"\` : '';
return \` return \`
<div class="context-item" title="\${item.path}"> <div class="context-item \${clickable}" title="\${item.path || item.displayPath}" \${onclick}>
\${icon} \${icon}
<span class="context-item-name">\${item.displayPath || getFileName(item.path)}</span> <span class="context-item-name">\${item.displayPath || getFileName(item.path)}</span>
<span class="context-item-remove" onclick="removeContextItem(\${item.id})"> <span class="context-item-remove" onclick="event.stopPropagation(); removeContextItem(\${item.id})">
\${getRemoveIcon()} \${getRemoveIcon()}
</span> </span>
</div> </div>
@ -186,6 +199,27 @@ export function getContextDisplayScript(): string {
}).join(''); }).join('');
} }
// 全局访问函数
window.handleContextItemClick = function(id) {
const item = contextItems.find(i => i.id === id);
if (!item || item.type === 'folder') return;
if (item.type === 'code') {
const codeData = JSON.parse(item.path);
vscode.postMessage({
command: 'openFileWithSelection',
filePath: codeData.fileName,
startLine: codeData.startLine,
endLine: codeData.endLine
});
} else {
vscode.postMessage({
command: 'openFile',
filePath: item.path
});
}
};
// 处理后端返回的文件选择结果 // 处理后端返回的文件选择结果
window.addEventListener('message', event => { window.addEventListener('message', event => {
const message = event.data; const message = event.data;
@ -211,6 +245,18 @@ export function getContextDisplayScript(): string {
message.documents.forEach(doc => addContextItem('document', doc)); message.documents.forEach(doc => addContextItem('document', doc));
} }
break; break;
case 'addCodeContext':
// 添加代码上下文
const displayName = \`\${message.fileName.split(/[\\\\/]/).pop()}:\${message.startLine}-\${message.endLine}\`;
const codeData = {
fileName: message.fileName,
startLine: message.startLine,
endLine: message.endLine,
code: message.code,
languageId: message.languageId
};
addContextItem('code', JSON.stringify(codeData), displayName);
break;
} }
}); });

View File

@ -47,11 +47,7 @@ export function getConversationHistoryBarContent(): string {
</svg> </svg>
</button> </button>
<div class="user-info-container"> <div class="user-info-container" style="display: none;">
<button class="user-avatar-icon-button" id="userAvatarIconButton" style="display: none;" title="查看用户信息" onclick="openUserDetailModal()">
${userAvatarIconSvg}
</button>
${getUserInfoComponentContent()}
</div> </div>
<div class='setting'> <div class='setting'>

View File

@ -46,10 +46,21 @@ export function getFilePathTagScript(): string {
return ` return `
// 处理文件路径标签点击 // 处理文件路径标签点击
function handleFilePathClick(filePath) { function handleFilePathClick(filePath) {
vscode.postMessage({ // 解析文件路径,支持 file.v:5-8 格式
command: 'openFile', const match = filePath.match(/^(.+?):(\\d+)-(\\d+)$/);
filePath: filePath if (match) {
}); vscode.postMessage({
command: 'openFilePathTag',
filePath: match[1],
startLine: parseInt(match[2]),
endLine: parseInt(match[3])
});
} else {
vscode.postMessage({
command: 'openFilePathTag',
filePath: filePath
});
}
} }
// 创建文件路径标签 // 创建文件路径标签

View File

@ -4,75 +4,15 @@
export function getGeneralSettingsComponentContent(): string { export function getGeneralSettingsComponentContent(): string {
return ` return `
<div class="general-settings"> <div class="general-settings">
<h3 class="settings-section-title">通用设置</h3> <h3 class="settings-section-title">后端服务配置</h3>
<div class="settings-section"> <div class="settings-section">
<div class="settings-item"> <div class="settings-item">
<div class="settings-item-header"> <div class="settings-item-header">
<label class="settings-item-label">主题</label> <label class="settings-item-label">后端服务地址</label>
<span class="settings-item-description">选择界面主题</span> <span class="settings-item-description">自定义后端 API 地址</span>
</div> </div>
<select class="settings-select" id="themeSelect"> <input type="text" class="settings-input-text" id="backendUrlInput" placeholder="https://iccoder.com">
<option value="auto">跟随系统</option>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
</div>
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">语言</label>
<span class="settings-item-description">选择界面语言</span>
</div>
<select class="settings-select" id="languageSelect">
<option value="zh-CN">简体中文</option>
<option value="en-US">English</option>
</select>
</div>
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">自动保存</label>
<span class="settings-item-description">自动保存会话历史</span>
</div>
<label class="settings-switch">
<input type="checkbox" id="autoSaveCheckbox" checked>
<span class="settings-switch-slider"></span>
</label>
</div>
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">显示时间戳</label>
<span class="settings-item-description">在消息中显示时间戳</span>
</div>
<label class="settings-switch">
<input type="checkbox" id="showTimestampCheckbox">
<span class="settings-switch-slider"></span>
</label>
</div>
</div>
<div class="settings-section">
<h4 class="settings-subsection-title">编辑器设置</h4>
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">字体大小</label>
<span class="settings-item-description">设置编辑器字体大小</span>
</div>
<input type="number" class="settings-input" id="fontSizeInput" value="14" min="10" max="24">
</div>
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">代码高亮</label>
<span class="settings-item-description">启用代码语法高亮</span>
</div>
<label class="settings-switch">
<input type="checkbox" id="syntaxHighlightCheckbox" checked>
<span class="settings-switch-slider"></span>
</label>
</div> </div>
</div> </div>
@ -176,6 +116,21 @@ export function getGeneralSettingsComponentStyles(): string {
border-color: var(--vscode-focusBorder); border-color: var(--vscode-focusBorder);
} }
.settings-input-text {
width: 300px;
padding: 6px 12px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
font-size: 13px;
outline: none;
}
.settings-input-text:focus {
border-color: var(--vscode-focusBorder);
}
.settings-switch { .settings-switch {
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -270,57 +225,37 @@ export function getGeneralSettingsComponentScript(): string {
// 保存通用设置 // 保存通用设置
function saveGeneralSettings() { function saveGeneralSettings() {
const settings = { const settings = {
theme: document.getElementById('themeSelect').value, backendUrl: document.getElementById('backendUrlInput').value,
language: document.getElementById('languageSelect').value,
autoSave: document.getElementById('autoSaveCheckbox').checked,
showTimestamp: document.getElementById('showTimestampCheckbox').checked,
fontSize: document.getElementById('fontSizeInput').value,
syntaxHighlight: document.getElementById('syntaxHighlightCheckbox').checked,
}; };
// 发送消息到扩展
vscode.postMessage({ vscode.postMessage({
command: 'saveGeneralSettings', command: 'saveGeneralSettings',
settings: settings settings: settings
}); });
// 显示保存成功提示
console.log('通用设置已保存', settings); console.log('通用设置已保存', settings);
closeSettingsModal();
} }
// 重置通用设置 // 重置通用设置
function resetGeneralSettings() { function resetGeneralSettings() {
document.getElementById('themeSelect').value = 'auto'; document.getElementById('backendUrlInput').value = '';
document.getElementById('languageSelect').value = 'zh-CN';
document.getElementById('autoSaveCheckbox').checked = true; // 清空保存的配置
document.getElementById('showTimestampCheckbox').checked = false; vscode.postMessage({
document.getElementById('fontSizeInput').value = '14'; command: 'saveGeneralSettings',
document.getElementById('syntaxHighlightCheckbox').checked = true; settings: { backendUrl: '' }
});
console.log('通用设置已重置为默认值'); console.log('通用设置已重置为默认值');
closeSettingsModal();
} }
// 加载通用设置 // 加载通用设置
function loadGeneralSettings(settings) { function loadGeneralSettings(settings) {
if (!settings) return; if (!settings) return;
if (settings.backendUrl) {
if (settings.theme) { document.getElementById('backendUrlInput').value = settings.backendUrl;
document.getElementById('themeSelect').value = settings.theme;
}
if (settings.language) {
document.getElementById('languageSelect').value = settings.language;
}
if (settings.autoSave !== undefined) {
document.getElementById('autoSaveCheckbox').checked = settings.autoSave;
}
if (settings.showTimestamp !== undefined) {
document.getElementById('showTimestampCheckbox').checked = settings.showTimestamp;
}
if (settings.fontSize) {
document.getElementById('fontSizeInput').value = settings.fontSize;
}
if (settings.syntaxHighlight !== undefined) {
document.getElementById('syntaxHighlightCheckbox').checked = settings.syntaxHighlight;
} }
} }
`; `;

View File

@ -432,15 +432,14 @@ export function getInputAreaScript(): string {
// 获取上下文项 // 获取上下文项
const contextItems = window.getContextItems ? window.getContextItems() : []; const contextItems = window.getContextItems ? window.getContextItems() : [];
// 构建显示消息:如果有上下文文件,添加文件路径前缀 // 构建显示消息:如果有上下文,添加路径前缀
let displayText = text; let displayText = text;
if (contextItems.length > 0) { if (contextItems.length > 0) {
const filePaths = contextItems const contextPaths = contextItems
.filter(item => item.type === 'file')
.map(item => item.displayPath || item.path) .map(item => item.displayPath || item.path)
.join(' '); .join(' ');
if (filePaths) { if (contextPaths) {
displayText = filePaths + ' ' + text; displayText = contextPaths + ' ' + text;
} }
} }

View File

@ -863,8 +863,8 @@ export function getMessageAreaScript(): string {
const textParts = []; const textParts = [];
parts.forEach(part => { parts.forEach(part => {
// 判断是否为文件路径:包含路径分隔符文件扩展名 // 判断是否为文件路径或代码片段:包含路径分隔符文件扩展名或代码片段格式(文件名:行号-行号)
if (part.includes('/') || part.includes('\\\\') || /\\.[a-zA-Z0-9]+$/.test(part)) { if (part.includes('/') || part.includes('\\\\') || /\\.[a-zA-Z0-9]+$/.test(part) || /:[0-9]+-[0-9]+$/.test(part)) {
filePaths.push(part); filePaths.push(part);
} else { } else {
textParts.push(part); textParts.push(part);
@ -1211,7 +1211,7 @@ export function getMessageAreaScript(): string {
const optionsHtml = q.options.map(opt => { const optionsHtml = q.options.map(opt => {
const isSelected = selectedAnswers.includes(opt); const isSelected = selectedAnswers.includes(opt);
return \`<label style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px 0;"> return \`<label class="question-option\${isSelected ? ' selected' : ''}" style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px 0;">
<input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}> <input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}>
<span>\${opt}</span> <span>\${opt}</span>
</label>\`; </label>\`;
@ -1729,9 +1729,18 @@ export function getMessageAreaScript(): string {
// 标记问题已回答 // 标记问题已回答
segmentDiv.classList.add('answered'); segmentDiv.classList.add('answered');
// 禁用所有输入 // 禁用所有输入并保持选中状态的高亮
const inputs = segmentDiv.querySelectorAll('input'); const inputs = segmentDiv.querySelectorAll('input');
inputs.forEach(input => input.disabled = true); inputs.forEach(input => {
input.disabled = true;
// 确保选中的选项保持高亮
if (input.checked) {
const label = input.closest('.question-option');
if (label) {
label.classList.add('selected');
}
}
});
// 隐藏提交按钮 // 隐藏提交按钮
const submitBtn = segmentDiv.querySelector('.custom-submit'); const submitBtn = segmentDiv.querySelector('.custom-submit');

View File

@ -203,6 +203,8 @@ export function getSettingsComponentScript(): string {
const modal = document.getElementById('settingsModal'); const modal = document.getElementById('settingsModal');
if (modal) { if (modal) {
modal.classList.add('active'); modal.classList.add('active');
// 请求加载设置
vscode.postMessage({ command: 'loadGeneralSettings' });
} }
} }

View File

@ -9,57 +9,7 @@
*/ */
export function getUserInfoComponentContent(): string { export function getUserInfoComponentContent(): string {
return ` return `
<div class="user-info-wrapper"> <div class="user-info-wrapper" style="display: none;">
<!-- 用户详情下拉面板 -->
<div class="user-detail-dropdown" id="userDetailDropdown">
<div class="user-detail-content">
<div class="user-detail-header">
<div class="user-info-row">
<div class="user-avatar-small clickable" id="userAvatarClickable">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" fill="currentColor"/>
</svg>
</div>
<div class="user-name-tier">
<div class="user-detail-name clickable" id="userDetailName">加载中...</div>
<img class="tier-icon-inline" id="tierIconInline" style="display: none;" />
</div>
</div>
<!-- 升级到Pro按钮 (仅BASIC会员显示) -->
<!-- <div class="upgrade-pro-wrapper" id="upgradeProWrapper" style="display: none;">
<button class="upgrade-pro-btn" id="upgradeProBtn">升级到 Pro</button>
</div> -->
</div>
<div class="user-detail-body">
<!-- <div class="user-detail-item">
<span class="detail-label">剩余 Credits</span>
<span class="detail-value" id="creditsDetail">-</span>
</div> -->
<div class="user-detail-item logout-item" id="logoutItem">
<span class="detail-label">账户管理</span>
<span class="detail-value logout-link">退出登录</span>
</div>
</div>
</div>
</div>
</div>
<!-- 退出登录确认对话框 -->
<div class="logout-confirm-modal" id="logoutConfirmModal">
<div class="logout-confirm-overlay"></div>
<div class="logout-confirm-content">
<div class="logout-confirm-header">
<h3>确认退出</h3>
</div>
<div class="logout-confirm-body">
<p>确定要退出登录吗?</p>
</div>
<div class="logout-confirm-footer">
<button class="logout-confirm-btn logout-cancel-btn" id="logoutCancelBtn">取消</button>
<button class="logout-confirm-btn logout-ok-btn" id="logoutOkBtn">确定</button>
</div>
</div>
</div> </div>
`; `;
} }

View File

@ -903,6 +903,13 @@ export function getWebviewContent(
} }
break; break;
case 'loadedGeneralSettings':
// 加载通用设置
if (typeof loadGeneralSettings === 'function') {
loadGeneralSettings(message.settings);
}
break;
default: default:
console.log('[WebView] 未处理的消息类型:', message.command); console.log('[WebView] 未处理的消息类型:', message.command);
} }

View File

@ -10,24 +10,23 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
/** @type WebpackConfig */ /** @type WebpackConfig */
const extensionConfig = { const extensionConfig = {
target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ target: 'node',
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') mode: process.env.NODE_ENV === 'production' ? 'production' : 'none',
entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ entry: './src/extension.ts',
output: { output: {
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'extension.js', filename: 'extension.js',
libraryTarget: 'commonjs2' libraryTarget: 'commonjs2',
clean: true // 自动清理旧文件
}, },
externals: { externals: {
vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ vscode: 'commonjs vscode',
'node-notifier': 'commonjs node-notifier' // node-notifier 依赖原生模块,必须排除 'node-notifier': 'commonjs node-notifier'
// modules added here also need to be added in the .vscodeignore file
}, },
resolve: { resolve: {
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader extensions: ['.ts', '.js'],
extensions: ['.ts', '.js'] mainFields: ['module', 'main']
}, },
module: { module: {
rules: [ rules: [
@ -36,15 +35,21 @@ const extensionConfig = {
exclude: /node_modules/, exclude: /node_modules/,
use: [ use: [
{ {
loader: 'ts-loader' loader: 'ts-loader',
options: {
transpileOnly: true, // 加快编译速度
compilerOptions: {
sourceMap: true
}
}
} }
] ]
} }
] ]
}, },
devtool: 'nosources-source-map', devtool: process.env.NODE_ENV === 'production' ? 'hidden-source-map' : 'nosources-source-map',
infrastructureLogging: { infrastructureLogging: {
level: "log", // enables logging required for problem matchers level: "log",
}, },
plugins: [ plugins: [
new CopyWebpackPlugin({ new CopyWebpackPlugin({
@ -52,6 +57,15 @@ const extensionConfig = {
{ from: 'src/assets', to: 'assets' } { from: 'src/assets', to: 'assets' }
] ]
}) })
] ],
optimization: {
minimize: process.env.NODE_ENV === 'production',
usedExports: true // Tree Shaking
},
performance: {
hints: 'warning',
maxAssetSize: 2 * 1024 * 1024, // 2MB
maxEntrypointSize: 2 * 1024 * 1024
}
}; };
module.exports = [ extensionConfig ]; module.exports = [ extensionConfig ];