19 Commits

Author SHA1 Message Date
47f95afabb Merge branch 'feat/codeToChat' into feat/ningDeShiDai 2026-03-10 20:53:31 +08:00
7fe87e515b feat:新增对话结束后添加结束语句 2026-03-10 20:53:13 +08:00
081ddec55c Merge branch 'feat/ningDeShiDai' of https://git.pengyejiatu.com/pengyejiatu/IC-Coder-Plugin into feat/ningDeShiDai 2026-03-10 19:18:06 +08:00
12c2f634bd Merge branch 'feat/codeToChat' into feat/ningDeShiDai 2026-03-10 19:05:45 +08:00
790110ba7e feat: 添加示例刷新按钮
- 在示例标题旁添加刷新按钮
   - 点击从13个示例中随机选择2个替换当前显示
   - 添加500ms节流防止频繁点击
   - 优化按钮交互动画效果
2026-03-10 19:04:45 +08:00
b9dc631bf7 Merge branch 'feat/ningDeShiDai' of https://git.pengyejiatu.com/pengyejiatu/Ic-coder-plugin into feat/ningDeShiDai 2026-03-10 19:01:38 +08:00
6425496d2e feat: 离线部署模式改进和 SystemVerilog 支持
- 添加离线模式仿真模拟:识别代码生成完成消息后自动模拟仿真流程
- 启用 iverilog SystemVerilog 2012 标准支持(-g2012)
- 优化进度条显示逻辑

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 18:56:53 +08:00
fd5a01c67f Merge branch 'feat/codeToChat' into feat/ningDeShiDai 2026-03-10 18:40:13 +08:00
29e80ce296 feat: 优化消息处理和界面显示
- 增强 messageHandler 消息处理逻辑
   - 优化 messageArea 显示效果
   - 改进 webviewContent 界面交互
2026-03-10 18:39:50 +08:00
c244a308d7 style:将spec改为specification 2026-03-10 17:08:36 +08:00
a25d68f527 feat: 添加用户手册只读预览功能
- 新增 UserManualPanel 组件实现只读预览
   - 支持 Markdown 完整渲染(表格、代码块、图片、分隔线)
   - 优化排版和字体大小
   - 用户无法编辑手册内容
2026-03-10 17:01:16 +08:00
77b54aebf0 feat: 添加用户手册功能
- 新增用户手册 Markdown 文档及配套截图
   - 新增打开用户手册命令
   - 在侧边栏和主面板中集成用户手册入口
   - 优化用户手册打开方式,支持 Markdown 预览
2026-03-10 16:29:37 +08:00
840436eb36 refactor: 优化设置面板和模型提示文案
- 移除设置面板中的规则配置标签页
   - 更新模型选择器提示文案为"FPGA专属微调模型"
2026-03-10 14:23:37 +08:00
f5dd7534f0 feat:将模型选中固定为max模型
- 并且鼠标移动上去显示“IC Coder自研顶尖微调模型“
2026-03-10 14:13:20 +08:00
ebb9de5294 tyle:修改了聊天面板的样式及删除了用户反馈和web端的跳转 2026-03-10 14:02:44 +08:00
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
34 changed files with 1125 additions and 1081 deletions

251
media/USER_MANUAL.md Normal file
View File

@ -0,0 +1,251 @@
# IC Coder 插件端用户手册
欢迎**宁德时代新能源科技股份有限公司**的各位专家使用 IC Coder 企业版!
本手册旨在为贵公司提供插件的安装、配置及使用指导,帮助您快速上手并充分发挥工具的功能。
在使用过程中,如遇任何功能异常、性能问题或操作疑问,欢迎随时联系我们,我们将在第一时间为您提供支持。
若贵司有特定的业务场景或个性化功能需求,也可直接与我们沟通,我们会为贵司进行定制化开发。
| 功能 | 说明 |
| ----------------------- | ---------------------------------------------------- |
| 自研微调模型 | IC Coder 自研顶尖 Max 微调模型,专为 FPGA 开发 |
| 高质量 Verilog 代码生成 | 根据自然语言描述生成符合规范的 Verilog 代码 |
| 自动语法检查 | 自动检测代码语法错误并自动修复 |
| 自动仿真 | 内置编译器,自动编译和仿真 |
| 波形检查工具 | 内置 VCD 波形查看器,自动波形解析 |
| 自主检查迭代 | AI 自动检查代码问题并迭代优化 |
| Diff 功能 | 快速比较代码差异,追踪 AI 修改,可一键确认和撤销修改 |
| Code Action 快捷操作 | Ctrl+L 快捷键或右键菜单快速添加代码到对话 |
| 支持上下文连续对话 | 多轮对话AI 记住之前的交互内容 |
| 会话历史管理 | 自动保存对话记录,随时恢复历史会话 |
## IC Coder 快速入门指南
### 系统要求
- **Visual Studio Code**: 版本 >= 1.60.0
各位专家也可以通过这个链接下载VSCode [下载链接](https://code.visualstudio.com/)
---
### 安装步骤
#### 步骤 1通过 VSIX 文件安装(推荐)
1. **获取安装包**
- 确保您已经获得 `iccoder-Trial-1.0.vsix` 文件
2. **打开 VS Code**
- 启动 Visual Studio Code
3. **安装插件**
有以下三种安装方式:
**方式 A通过命令面板**
-`Ctrl+Shift+P` (Windows/Linux) 或 `Cmd+Shift+P` (macOS) 打开命令面板
- 输入 `Extensions: Install from VSIX...`
- 选择 `iccoder-Trial-1.0.vsix` 文件
- 等待安装完成
![安装方式1.png](./manual/安装方式1.png)
**方式 B通过扩展视图**
- 点击左侧活动栏的扩展图标(或按 `Ctrl+Shift+X`
- 点击扩展视图右上角的 `...` (更多操作)
- 选择 `从 VSIX 安装...`
- 选择 `iccoder-Trial-1.0.vsix` 文件
![安装方式2.png](./manual/安装方式2.png)
**方式 C通过命令行**
```bash
code --install-extension iccoder-Trial-1.0.vsix
```
4. **重启 VS Code**
- 安装完成后,建议重启 VS Code 以确保插件正常加载
#### 步骤 2打开 IC Coder 界面
**登录后会自动打开**,手动打开也有以下几种方式:
**方式 1通过侧边栏**
- 点击左侧活动栏的 IC Coder 图标
- 侧边栏会显示 IC Coder 聊天界面
![侧边栏打开.png](./manual/侧边栏打开.png)
**方式 2通过命令面板**
- 按 `Ctrl+Shift+P` (Windows/Linux) 或 `Cmd+Shift+P` (macOS)
- 输入以下命令之一:
- `IC Coder: 打开聊天` - 打开聊天界面
- `打开 IC Coder 助手` - 打开助手面板
![命令面板打开.png](./manual/命令面板打开.png)
---
#### 步骤 3开始使用
插件已预配置好后端服务,安装后即可直接使用,无需手动配置。
![聊天界面.png](./manual/聊天界面.png)
### 故障排除
#### 问题 :插件无法安装
**症状**:安装 VSIX 文件时报错
#### 解决方案:
- 确认 VS Code 版本 >= 1.60.0
- 检查 VSIX 文件是否完整(未损坏)
- 尝试以管理员权限运行 VS Code
- 清除 VS Code 缓存后重试
### 完整使用流程示例
下面通过一个完整的案例,展示如何使用 IC Coder 从需求到代码生成的全过程。
#### 步骤 1输入设计需求
在对话框中输入设计需求,例如:
```
我需要设计一个 8 位加法器,要求有进位输入和进位输出
```
点击**发送**按钮。
![输入需求.png](./manual/输入需求.png)
#### 步骤 2AI 询问补充信息
AI 会根据需求,询问一些关键的设计细节。例如:
- 是否需要溢出检测?
- 时钟频率要求是多少?
- 是否需要流水线设计?
您只需要根据实际需求**选择相应的选项或者直接输入需求**即可AI 会根据您的选择生成最合适的设计方案。
#### 步骤 3确认 AI 生成的任务列表
AI 会根据您的需求和补充信息生成一个详细的任务列表Todo List
![确认任务.png](./manual/确认任务.png)
仔细查看任务列表,确认无误后点击**确认**按钮AI 将开始执行。
#### 步骤 4观察 AI 执行过程
AI 开始工作后,您可以在对话框中实时看到所有执行步骤:
![观察执行过程.png](./manual/观察执行过程.png)
每个步骤完成后,任务列表中对应的项目会被标记为完成状态。
#### 步骤 5仿真运行与结果查看
当 AI 完成代码生成后,会自动运行仿真验证:
![仿真运行结果.png](./manual/仿真运行结果.png)
#### 步骤 6查看生成的文件
所有生成的文件会自动保存到您的工作目录中:
```
project/
├── src/
│ └── tb_adder_8bit.v # RTL 设计文件
├── sim/
│ └── tb_adder_8bit # 测试平台文件
└── tb_adder_8bit.vcd # 波形文件
```
您可以在 VS Code 的文件资源管理器中直接打开这些文件进行查看或修改。
#### 步骤8继续对话
如果您对给出的结果不太满意您可以告诉IC Coder您想具体修改的地方或者文件
#### 使用流程总结
整个使用流程可以概括为:
- **输入需求** → 在对话框中描述您的设计需求
- **回答问题** → 根据 AI 的询问选择合适的选项
- **确认任务** → 查看并确认 AI 生成的任务列表
- **观察执行** → 实时查看 AI 的所有执行步骤
- **查看结果** → 仿真成功后查看生成的文件
#### 使用提示
**如何描述需求更准确?**
- **明确功能**:清楚说明模块要实现什么功能
- **指定参数**:说明位宽、时钟频率等关键参数
- **特殊要求**:如果有特殊的时序要求或接口规范,请明确说明
**示例:**
```
好的描述:设计一个 16 位的 FIFO深度为 256支持异步读写
不够清晰:帮我写一个 FIFO
```
#### 常见问题
**Q: 仿真失败了怎么办?**
A: AI 会根据错误自动修复代码并重新仿真。
**Q: 可以修改生成的代码吗?**
A: 可以,可以直接编辑文件,然后告诉 AI 重新运行仿真。
**Q: 可以导入已有的代码吗?**
A: 可以,在工作区中打开对应的代码文件夹,然后直接在对话中告诉 AI 您要修改或优化哪个文件AI 会读取并进行修改。
**Q: 如何查看 AI 的思考过程?**
A: 在执行过程中AI 会实时显示每一步的操作和决策依据。
**Q: 如何保存对话历史?**
A: 对话历史会自动保存在本地,可以点击历史对话查看历史会话记录。
---
### 卸载插件
如需卸载插件:
1. 打开扩展视图
2. 找到 "IC Coder" 插件
3. 点击卸载按钮
4. 重启 VS Code
---
### 注意事项
1. **需提前打开一个文件夹作为工作区**,否则会准确的为您服务
![打开文件夹.png](./manual/打开文件夹.png)
2. **开箱即用**
- 插件已预配置后端服务,无需手动设置
- 安装后即可直接使用所有功能
---
**祝您使用愉快!如有问题欢迎反馈。**

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

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

View File

@ -2,19 +2,27 @@ import * as vscode from "vscode";
import { ICViewProvider } from "./views/ICViewProvider";
import { showICHelperPanel } from "./panels/ICHelperPanel";
import { VCDViewerPanel, VCDViewerEditorProvider } from "./panels/VCDViewerPanel";
import { UserManualPanel } from "./panels/UserManualPanel";
import { ChatHistoryManager } from "./utils/chatHistoryManager";
import { ICCoderAuthenticationProvider } from "./services/icCoderAuthProvider";
import { VCDFileServer } from "./services/vcdFileServer";
import { initUserService } from "./services/userService";
import { initCreditsService } from "./services/creditsService";
import { isTokenExpired } from "./utils/jwtUtils";
import { NotificationService } from "./services/notificationService";
import { InvitationService } from "./services/invitationService";
import { ICCoderCodeActionProvider } from "./providers/codeActionProvider";
import { setCustomConfig } from "./config/settings";
export async function activate(context: vscode.ExtensionContext) {
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: {
@ -50,33 +58,26 @@ export async function activate(context: vscode.ExtensionContext) {
const notificationService = NotificationService.getInstance(context);
console.log('[Extension] 通知服务已初始化');
// 【关键】在创建 AuthProvider 之前,先检查并清除过期的 session
const storedSessions = context.globalState.get<any[]>('icCoderSessions', []);
console.log('[Extension] 检查 sessions 数量:', storedSessions.length);
if (storedSessions.length > 0) {
const session = storedSessions[0];
const token = session.accessToken;
console.log('[Extension] 检查 token 是否过期...');
if (token) {
const expired = isTokenExpired(token);
console.log('[Extension] token 过期检查结果:', expired);
if (expired) {
// 必须等待清除完成后再创建 AuthProvider
await context.globalState.update('icCoderSessions', []);
await context.globalState.update('icCoderUserInfo', undefined);
console.log('[Extension] Token 已过期,已清除所有登录状态');
}
}
}
// 初始化用户服务
initUserService(context);
// 初始化 Credits 服务
initCreditsService(context);
// 【已禁用】登录和 token 验证 - 无需登录即可使用
// const storedSessions = context.globalState.get<any[]>('icCoderSessions', []);
// console.log('[Extension] 检查 sessions 数量:', storedSessions.length);
//
// if (storedSessions.length > 0) {
// const session = storedSessions[0];
// const token = session.accessToken;
// console.log('[Extension] 检查 token 是否过期...');
//
// if (token) {
// const expired = isTokenExpired(token);
// console.log('[Extension] token 过期检查结果:', expired);
//
// if (expired) {
// await context.globalState.update('icCoderSessions', []);
// await context.globalState.update('icCoderUserInfo', undefined);
// console.log('[Extension] Token 已过期,已清除所有登录状态');
// }
// }
// }
// 初始化 VCD 文件服务器
const vcdFileServer = new VCDFileServer(context.extensionUri);
@ -91,25 +92,18 @@ export async function activate(context: vscode.ExtensionContext) {
dispose: () => vcdFileServer.stop()
});
// 注册 Authentication Provider(此时 icCoderSessions 已经被清除)
// 【已禁用】Authentication Provider 注册 - 无需登录
const authProvider = new ICCoderAuthenticationProvider(context);
context.subscriptions.push(
vscode.authentication.registerAuthenticationProvider(
"iccoder",
"IC Coder",
authProvider
)
);
// context.subscriptions.push(
// vscode.authentication.registerAuthenticationProvider(
// "iccoder",
// "IC Coder",
// authProvider
// )
// );
// 检查登录状态,如果已登录则自动打开聊天面板
vscode.authentication.getSession("iccoder", [], { createIfNone: false })
.then((session) => {
if (session) {
// 【已禁用】登录状态检查 - 直接打开聊天面板
vscode.commands.executeCommand("ic-coder.openChat");
}
}, () => {
// 未登录,不做任何操作
});
// 注册命令:打开助手面板
const openPanelCommand = vscode.commands.registerCommand(
@ -188,6 +182,14 @@ export async function activate(context: vscode.ExtensionContext) {
}
);
// 注册命令:打开用户手册
const openUserManualCommand = vscode.commands.registerCommand(
"ic-coder.openUserManual",
() => {
UserManualPanel.render(context.extensionUri);
}
);
// 注册命令:用户登录
const loginCommand = vscode.commands.registerCommand(
"ic-coder.login",
@ -432,6 +434,7 @@ export async function activate(context: vscode.ExtensionContext) {
openChatCommand,
openVCDViewerCommand,
openVCDViewerInBrowserCommand,
openUserManualCommand,
loginCommand,
logoutCommand,
changeInvitationCodeCommand,

View File

@ -18,50 +18,12 @@ import {
startChangeSession,
handleOpenFileDiff,
} from "../utils/messageHandler";
import { setCustomConfig } from "../config/settings";
import { compactDialog } from "../services/apiClient";
import { VCDViewerPanel } from "./VCDViewerPanel";
import { ChatHistoryManager } from "../utils/chatHistoryManager";
import { MessageType } from "../types/chatHistory";
import { getCachedUserInfo } from "../services/userService";
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 助手面板
@ -70,63 +32,35 @@ export async function showICHelperPanel(
context: vscode.ExtensionContext,
viewColumn?: vscode.ViewColumn,
) {
// 检查 token 是否过期
let token: string | undefined;
try {
const session = await vscode.authentication.getSession("iccoder", [], {
createIfNone: false,
});
token = session?.accessToken;
} catch (error) {
console.warn("[ICHelperPanel] 获取 session 失败:", error);
}
if (token && isTokenExpired(token)) {
// 清除过期的 session
await context.globalState.update("icCoderSessions", []);
await context.globalState.update("icCoderUserInfo", undefined);
const action = await vscode.window.showWarningMessage(
"登录已过期,请重新登录",
"立即登录",
);
if (action === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true,
});
}
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面板
// 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面板
const panel = vscode.window.createWebviewPanel(
@ -230,81 +164,6 @@ export async function showICHelperPanel(
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;
if (pendingMessage) {
@ -585,71 +444,17 @@ export async function showICHelperPanel(
}
break;
case "checkInvitationCode":
// 检查邀请码验证状态
// 【已禁用】检查邀请码验证状态 - 现在所有用户都可以直接使用
{
// 先检查是否是试用用户
const { getCachedUserInfo } = require("../services/userService");
const userInfo = getCachedUserInfo();
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;
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;
case "checkTrialExpiration":
// 检查试用期是否过期
@ -664,37 +469,13 @@ export async function showICHelperPanel(
}
break;
case "verifyInvitationCode":
// 验证邀请码
// 【已禁用】验证邀请码 - 无需邀请码验证
{
const {
InvitationService,
} = require("../services/invitationService");
const result = await InvitationService.verifyCode(message.code);
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;
case "openICCoder":
@ -711,7 +492,7 @@ export async function showICHelperPanel(
break;
case "openUserManual":
// 打开用户手册
vscode.env.openExternal(vscode.Uri.parse("https://www.iccoder.com"));
vscode.commands.executeCommand("ic-coder.openUserManual");
break;
case "openUserFeedback":
// 打开用户反馈二维码弹窗
@ -904,6 +685,21 @@ export async function showICHelperPanel(
// 退出登录(前端已有确认对话框)
vscode.commands.executeCommand("ic-coder.logout");
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,

View File

@ -0,0 +1,181 @@
/**
* 用户手册只读预览面板
*/
import * as vscode from "vscode";
export class UserManualPanel {
public static currentPanel: UserManualPanel | undefined;
private readonly _panel: vscode.WebviewPanel;
private _disposables: vscode.Disposable[] = [];
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
this._panel = panel;
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
this._update(extensionUri);
}
public static render(extensionUri: vscode.Uri) {
if (UserManualPanel.currentPanel) {
UserManualPanel.currentPanel._panel.reveal(vscode.ViewColumn.One);
} else {
const panel = vscode.window.createWebviewPanel(
"userManual",
"IC Coder 用户手册",
vscode.ViewColumn.One,
{
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(extensionUri, "media")],
},
);
UserManualPanel.currentPanel = new UserManualPanel(panel, extensionUri);
}
}
private async _update(extensionUri: vscode.Uri) {
const manualPath = vscode.Uri.joinPath(
extensionUri,
"media",
"USER_MANUAL.md",
);
const markdown = await vscode.workspace.fs.readFile(manualPath);
const content = Buffer.from(markdown).toString("utf-8");
this._panel.webview.html = await this._getHtmlContent(
content,
extensionUri,
);
}
private async _getHtmlContent(
markdown: string,
extensionUri: vscode.Uri,
): Promise<string> {
let inCodeBlock = false;
let inTable = false;
let tableRows: string[] = [];
const lines: string[] = [];
// 先处理图片
markdown = markdown.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => {
const imgUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(extensionUri, "media", src),
);
return `<img src="${imgUri}" alt="${alt}">`;
});
markdown.split("\n").forEach((line) => {
// 代码块
if (line.startsWith("```")) {
if (inCodeBlock) {
lines.push("</code></pre>");
inCodeBlock = false;
} else {
lines.push("<pre><code>");
inCodeBlock = true;
}
return;
}
if (inCodeBlock) {
lines.push(line);
return;
}
// 表格
if (line.startsWith("|")) {
if (!inTable) inTable = true;
tableRows.push(line);
return;
} else if (inTable) {
// 表格结束
const headers = tableRows[0]
.split("|")
.filter((c) => c.trim())
.map((h) => `<th>${h.trim()}</th>`)
.join("");
const body = tableRows
.slice(2)
.map(
(r) =>
"<tr>" +
r
.split("|")
.filter((c) => c.trim())
.map((c) => `<td>${c.trim()}</td>`)
.join("") +
"</tr>",
)
.join("");
lines.push(
`<table><thead><tr>${headers}</tr></thead><tbody>${body}</tbody></table>`,
);
tableRows = [];
inTable = false;
}
// 其他行
if (line === "---") lines.push("<hr>");
else if (line.startsWith("#### "))
lines.push(`<h4>${line.slice(5)}</h4>`);
else if (line.startsWith("### ")) lines.push(`<h3>${line.slice(4)}</h3>`);
else if (line.startsWith("## ")) lines.push(`<h2>${line.slice(3)}</h2>`);
else if (line.startsWith("# ")) lines.push(`<h1>${line.slice(2)}</h1>`);
else if (line.startsWith("- "))
lines.push(
`<li>${line.slice(2).replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")}</li>`,
);
else if (line.trim() === "") lines.push("<p></p>");
else
lines.push(
`<p>${line.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>')}</p>`,
);
});
const html = lines
.join("\n")
.replace(/((?:<li>.*<\/li>\n?)+)/g, "<ul>$1</ul>");
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
padding: 40px;
line-height: 1.8;
font-size: 16px;
max-width: 1000px;
margin: 0 auto;
}
h1 { font-size: 2em; border-bottom: 3px solid #ddd; padding-bottom: 15px; margin: 30px 0 20px; }
h2 { font-size: 1.6em; margin-top: 40px; border-bottom: 2px solid #eee; padding-bottom: 10px; }
h3 { font-size: 1.3em; margin-top: 30px; }
h4 { font-size: 1.1em; margin-top: 20px; font-weight: 600; }
p { margin: 15px 0; }
img { display: block; margin: 30px auto; max-width: 100%; border: 1px solid #ddd; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
table { border-collapse: collapse; width: 100%; margin: 25px 0; font-size: 15px; }
th, td { border: 1px solid #ddd; padding: 12px 16px; text-align: left; }
th { background: #636363; font-weight: 600; }
tr:hover { background: #636363; }
ul { margin: 15px 0; padding-left: 30px; }
li { margin: 8px 0; margin-left: 40px;}
pre { background: #2f2f2f; padding: 20px; border-radius: 6px; overflow-x: auto; margin: 20px 0; border: 1px solid #e0e0e0; }
code { font-family: 'Consolas', 'Monaco', monospace; font-size: 14px; line-height: 1.6; }
hr { border: none; border-top: 2px solid #e0e0e0; margin: 30px 0; }
a { color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
strong { font-weight: 600; color: #e5e5e5; }
</style>
</head>
<body>${html}</body>
</html>`;
}
public dispose() {
UserManualPanel.currentPanel = undefined;
this._panel.dispose();
while (this._disposables.length) {
this._disposables.pop()?.dispose();
}
}
}

View File

@ -30,7 +30,6 @@ import type {
import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient";
import { ChatHistoryManager } from "../utils/chatHistoryManager";
import { getUserIdFromToken, isTokenExpired } from "../utils/jwtUtils";
import { updateCachedBalance } from "./creditsService";
/**
* 消息段落类型
@ -129,6 +128,8 @@ export class DialogSession {
private currentTextSegment: MessageSegment | null = null;
private completeCallback: ((segments: MessageSegment[]) => void) | null =
null; // 保存完成回调,用于 abort 时触发
private consecutiveToolErrors = 0; // 连续工具错误计数
private readonly MAX_CONSECUTIVE_ERRORS = 5; // 最大连续错误次数
constructor(extensionPath: string, existingTaskId?: string) {
// 支持复用现有 taskId用于 Plan 模式确认后继续执行)
@ -444,6 +445,7 @@ export class DialogSession {
const expired = isTokenExpired(session.accessToken);
if (expired === true) {
console.error("[DialogSession] token 已过期,需要重新登录");
/*
vscode.window
.showErrorMessage("登录已过期,请重新登录", "重新登录")
.then((selection) => {
@ -453,6 +455,7 @@ export class DialogSession {
});
}
});
*/
throw new Error("登录已过期,请重新登录");
}
@ -899,6 +902,7 @@ export class DialogSession {
data.message.includes("LOGIN_EXPIRED") ||
data.message.includes("登录状态已过期")
) {
/*
vscode.window
.showErrorMessage("登录状态已过期,请重新登录", "重新登录")
.then((selection) => {
@ -908,6 +912,7 @@ export class DialogSession {
});
}
});
*/
// 登录过期错误已处理,不再传递给外部
return;
}
@ -1017,8 +1022,9 @@ export class DialogSession {
data.remainingCredits
);
// 更新余额缓存
updateCachedBalance(data.remainingCredits);
// updateCachedBalance(data.remainingCredits);
// 资源点余额低于阈值时弹窗提醒
/*
const LOW_CREDIT_THRESHOLD = 5;
if (data.remainingCredits < LOW_CREDIT_THRESHOLD) {
vscode.window
@ -1030,13 +1036,13 @@ export class DialogSession {
)
.then((selection) => {
if (selection === "去充值") {
// 打开充值页面
vscode.env.openExternal(
vscode.Uri.parse("https://iccoder.com/recharge")
vscode.Uri.parse("https://iccoder.com/memberCenter")
);
}
});
}
*/
},
onOpen: () => {

View File

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

View File

@ -161,7 +161,16 @@ export async function startStreamDialog(
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) => {
const options: http.RequestOptions = {

View File

@ -6,7 +6,7 @@ import * as vscode from "vscode";
import * as path from "path";
import * as os from "os";
import * as fs from "fs";
import { readFileContent, readDirectory } from "../utils/readFiles";
import { readFileContent, readDirectory, listDirectory } from "../utils/readFiles";
import { createOrOverwriteFile } from "../utils/createFiles";
import { resolveWorkspaceFilePath, showFileDiff } from "../utils/fileDiff";
import { changeTracker } from "./changeTracker";
@ -126,8 +126,9 @@ export async function executeToolCall(
// 提交成功结果
const result = createSuccessResult(callId, resultText);
console.log(`[ToolExecutor] 准备提交结果: ${toolName}, callId=${callId}`);
await submitToolResult(result);
console.log(`[ToolExecutor] 工具执行成功: ${toolName}, callId=${callId}`);
console.log(`[ToolExecutor] 结果提交成功: ${toolName}, callId=${callId}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "未知错误";
console.error(
@ -136,8 +137,20 @@ export async function executeToolCall(
);
// 提交错误结果
try {
const result = createBusinessErrorResult(callId, errorMessage);
console.log(`[ToolExecutor] 准备提交错误结果: ${toolName}, callId=${callId}`);
await submitToolResult(result);
console.log(`[ToolExecutor] 错误结果提交成功: ${toolName}, callId=${callId}`);
} catch (submitError) {
console.error(
`[ToolExecutor] 提交错误结果失败: ${toolName}, callId=${callId}`,
submitError,
);
throw submitError;
}
// 重新抛出原始错误,让调用方知道工具执行失败
throw error;
}
}
@ -274,10 +287,8 @@ async function executeFileList(args: FileListArgs): Promise<string> {
const dirPath = args.path || ".";
const extensions = args.extension ? [args.extension] : undefined;
const files = await readDirectory(dirPath, extensions);
const fileList = files.map((f) => f.path).join("\n");
return fileList || "(目录为空)";
const files = await listDirectory(dirPath, extensions);
return files.join("\n") || "(目录为空)";
}
/**

View File

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

View File

@ -201,6 +201,33 @@ async function findVerilogFiles(dir: string): Promise<string[]> {
return verilogFiles;
}
/**
* 递归查找目录下所有 VCD 文件
*/
async function findVcdFilesRecursive(dir: string): Promise<string[]> {
const vcdFiles: string[] = [];
async function searchDir(currentDir: string) {
const dirUri = vscode.Uri.file(currentDir);
const entries = await vscode.workspace.fs.readDirectory(dirUri);
for (const [fileName, fileType] of entries) {
const filePath = path.join(currentDir, fileName);
if (fileType === vscode.FileType.Directory) {
if (!fileName.startsWith(".") && fileName !== "node_modules") {
await searchDir(filePath);
}
} else if (fileType === vscode.FileType.File && fileName.endsWith(".vcd")) {
vcdFiles.push(filePath);
}
}
}
await searchDir(dir);
return vcdFiles;
}
/**
* 获取 iverilog 可执行文件路径
*/
@ -304,8 +331,8 @@ export async function generateVCD(
IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"),
};
// 5. 构建 iverilog 编译参数
const compileArgs = ["-o", outputFile, ...projectCheck.allVerilogFiles];
// 5. 构建 iverilog 编译参数(启用 SystemVerilog 2012 标准)
const compileArgs = ["-g2012", "-o", outputFile, ...projectCheck.allVerilogFiles];
console.log("执行编译命令:", iverilogPath, compileArgs.join(" "));
console.log("IVERILOG_ROOT:", env.IVERILOG_ROOT);
@ -317,7 +344,10 @@ export async function generateVCD(
cwd: projectPath,
env: env,
});
console.log("编译成功stdout:", compileResult.stdout);
console.log("编译成功stderr:", compileResult.stderr);
} catch (error: any) {
console.error("编译失败:", error);
return {
success: false,
message: `IC Coder编译器编译失败:\n${error.message}`,
@ -326,9 +356,20 @@ export async function generateVCD(
};
}
// 6.1 检查 .vvp 文件是否生成
const fs = require("fs");
if (!fs.existsSync(outputFile)) {
return {
success: false,
message: `编译未生成 .vvp 文件: ${outputFile}`,
stderr: compileResult.stderr,
stdout: compileResult.stdout,
};
}
console.log("已生成 .vvp 文件:", outputFile);
// 6.5. 删除 shebang 行(修复 Windows 上的 vvp 解析错误)
try {
const fs = require("fs");
const vvpContent = fs.readFileSync(outputFile, "utf8");
const lines = vvpContent.split("\n");
@ -336,9 +377,62 @@ export async function generateVCD(
const cleanedContent = lines.slice(1).join("\n");
fs.writeFileSync(outputFile, cleanedContent, "utf8");
console.log("已删除 .vvp 文件的 shebang 行");
} else {
console.log(".vvp 文件无 shebang 行,跳过");
}
} catch (error) {
console.warn("删除 shebang 失败,继续执行:", error);
console.error("删除 shebang 失败:", error);
return {
success: false,
message: `处理 .vvp 文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
};
}
// 6.6. 检查并创建 VCD 输出目录,并处理 Windows 路径问题
try {
const tbPath = projectCheck.testbenchFile;
if (tbPath && fs.existsSync(tbPath)) {
const tbContent = fs.readFileSync(tbPath, "utf8");
const dumpfileMatch = tbContent.match(/\$dumpfile\s*\(\s*["']([^"']+)["']\s*\)/);
if (dumpfileMatch) {
const vcdPath = dumpfileMatch[1];
const vcdDir = path.dirname(vcdPath);
console.log(`testbench 中的 VCD 路径: ${vcdPath}`);
if (vcdDir && vcdDir !== "." && vcdDir !== "") {
const vcdDirPath = path.join(projectPath, vcdDir);
console.log(`检查 VCD 目录: ${vcdDirPath}`);
if (!fs.existsSync(vcdDirPath)) {
fs.mkdirSync(vcdDirPath, { recursive: true });
console.log(`已创建 VCD 输出目录: ${vcdDirPath}`);
} else {
console.log(`VCD 目录已存在: ${vcdDirPath}`);
}
// Windows 兼容性:修改 .vvp 文件中的路径,将正斜杠替换为反斜杠
if (process.platform === "win32" && vcdPath.includes("/")) {
const vvpContent = fs.readFileSync(outputFile, "utf8");
const windowsPath = vcdPath.replace(/\//g, "\\\\");
const modifiedContent = vvpContent.replace(
new RegExp(`"${vcdPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`, 'g'),
`"${windowsPath}"`
);
fs.writeFileSync(outputFile, modifiedContent, "utf8");
console.log(`已修正 VCD 路径: ${vcdPath} -> ${windowsPath}`);
}
} else {
console.log("VCD 文件在根目录,无需创建子目录");
}
} else {
console.warn("testbench 中未找到 $dumpfile 语句");
}
}
} catch (error) {
console.error("处理 VCD 路径失败:", error);
return {
success: false,
message: `处理 VCD 路径失败: ${error instanceof Error ? error.message : "未知错误"}`,
};
}
// 7. 执行仿真生成 VCD
@ -351,7 +445,11 @@ export async function generateVCD(
cwd: projectPath,
env: env,
});
console.log("仿真执行完成");
console.log("仿真 stdout:", simResult.stdout);
console.log("仿真 stderr:", simResult.stderr);
} catch (error: any) {
console.error("仿真失败:", error);
return {
success: false,
message: `VVP 仿真失败:\n${error.message}`,
@ -361,16 +459,38 @@ export async function generateVCD(
}
// 8. 查找生成的 VCD 文件
const projectUri = vscode.Uri.file(projectPath);
const entries = await vscode.workspace.fs.readDirectory(projectUri);
const vcdFiles = entries
.filter(
([fileName, fileType]) =>
fileType === vscode.FileType.File && fileName.endsWith(".vcd"),
)
.map(([fileName]) => fileName);
let vcdFile: string | null = null;
if (vcdFiles.length === 0) {
// 8.1 尝试从 testbench 中提取 VCD 路径
try {
const fs = require("fs");
const tbPath = projectCheck.testbenchFile;
if (tbPath && fs.existsSync(tbPath)) {
const tbContent = fs.readFileSync(tbPath, "utf8");
const dumpfileMatch = tbContent.match(/\$dumpfile\s*\(\s*["']([^"']+)["']\s*\)/);
if (dumpfileMatch) {
const vcdPath = dumpfileMatch[1];
const absoluteVcdPath = path.join(projectPath, vcdPath);
if (fs.existsSync(absoluteVcdPath)) {
vcdFile = absoluteVcdPath;
console.log(`找到 VCD 文件(从 testbench: ${vcdFile}`);
}
}
}
} catch (error) {
console.warn("从 testbench 提取 VCD 路径失败:", error);
}
// 8.2 如果未找到,递归搜索项目目录
if (!vcdFile) {
const foundFiles = await findVcdFilesRecursive(projectPath);
if (foundFiles.length > 0) {
vcdFile = foundFiles[0];
console.log(`找到 VCD 文件(递归搜索): ${vcdFile}`);
}
}
if (!vcdFile) {
return {
success: false,
message:
@ -379,9 +499,6 @@ export async function generateVCD(
};
}
// 使用找到的第一个 VCD 文件
const vcdFile = path.join(projectPath, vcdFiles[0]);
// 9. 清理中间文件
try {
const outputUri = vscode.Uri.file(outputFile);
@ -594,8 +711,9 @@ export async function generateMultiVCD(
console.log(`[generateMultiVCD] 仿真模块: ${module.name} (${macroName})`);
try {
// 编译(带宏定义)
// 编译(带宏定义,启用 SystemVerilog 2012 标准
const compileArgs = [
"-g2012",
`-D${macroName}`,
"-o",
outputFile,

View File

@ -19,10 +19,6 @@ import { dialogManager, DialogSession } from "../services/dialogService";
import { userInteractionManager } from "../services/userInteraction";
import { healthCheck } from "../services/apiClient";
import { isTokenExpired } from "./jwtUtils";
import {
checkBalanceBeforeSend,
fetchBalance,
} from "../services/creditsService";
import { optimizePrompt } from "../services/promptOptimizeService";
import { NotificationService } from "../services/notificationService";
import { TrialExpirationService } from "../services/trialExpirationService";
@ -41,6 +37,9 @@ let currentSession: DialogSession | null = null;
/** 最后一个活跃的 taskId用于压缩等操作 */
let lastTaskId: string | null = null;
/** 离线模式仿真模拟标志(防止重复触发) */
let offlineSimulationTriggered = false;
async function trackFileChange(
filePath: string,
oldContent: string,
@ -66,9 +65,9 @@ export async function handleUserMessage(
) {
console.log("收到用户消息:", text);
// 检查 token 是否过期
// 【已禁用】检查 token 是否过期 - 无需登录
const context = (panel as any).__context;
if (context) {
if (false && context) {
// 从 session 中获取 token
let token: string | undefined;
try {
@ -112,7 +111,7 @@ export async function handleUserMessage(
return;
}
if (isTokenExpired(token)) {
if (token && isTokenExpired(token as string)) {
console.warn("[MessageHandler] Token 已过期,阻止发送");
// 保存待发送的消息
@ -128,6 +127,7 @@ export async function handleUserMessage(
await context.globalState.update("icCoderUserInfo", undefined);
// 显示弹窗提示
/*
const action = await vscode.window.showWarningMessage(
"登录已过期,请重新登录",
"立即登录",
@ -138,6 +138,7 @@ export async function handleUserMessage(
forceReauth: true,
});
}
*/
// 恢复输入状态
panel.webview.postMessage({
@ -189,29 +190,6 @@ export async function handleUserMessage(
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) {
try {
@ -226,10 +204,10 @@ export async function handleUserMessage(
);
return;
} catch (error) {
console.error("当前访问人数过多,请稍后重试:", error);
console.error("处理用户消息失败:", error);
panel.webview.postMessage({
command: "updateStatus",
text: "当前访问人数过多,请稍后重试",
text: "处理用户消息失败,请稍后重试",
type: "error",
});
// 恢复输入状态
@ -283,6 +261,8 @@ async function handleUserMessageWithBackend(
);
// 保存 taskId 用于后续操作(如压缩)
lastTaskId = currentSession.getTaskId();
// 重置离线模式仿真标志(新会话开始)
offlineSimulationTriggered = false;
console.log(
"[MessageHandler] 创建会话: taskId=",
lastTaskId,
@ -306,11 +286,58 @@ async function handleUserMessageWithBackend(
},
onSegmentUpdate: (segments) => {
// 过滤掉包含 [调用工具:xxx] 的段落
const filteredSegments = segments.filter(seg => {
if (seg.type === 'text' && typeof seg.content === 'string') {
return !/\[调用工具:.+?\]/.test(seg.content);
}
return true;
});
// 实时发送段落更新,按后端返回顺序展示
panel.webview.postMessage({
command: "updateSegments",
segments: segments,
segments: filteredSegments,
});
// 【离线部署模式】检测代码生成完成消息,模拟仿真流程
if (!offlineSimulationTriggered) {
const hasCompletionMessage = segments.some(seg =>
seg.type === 'text' &&
seg.content?.includes('【代码生成完成】') &&
seg.content?.includes('语法检查:已通过')
);
if (hasCompletionMessage) {
offlineSimulationTriggered = true;
console.log('[离线模式] 检测到代码生成完成,开始模拟仿真流程');
// 立即点亮 Simulation 阶段
panel.webview.postMessage({
type: "updateProgress",
step: "simulation"
});
// 随机延时 8-13 秒后完成仿真
const simulationDelay = 8000 + Math.random() * 5000;
setTimeout(() => {
console.log('[离线模式] 模拟仿真完成,进入 Done 阶段');
// Simulation 完成,进入 Done
panel.webview.postMessage({
type: "updateProgress",
step: "done"
});
// 再延时 1 秒完成所有步骤
setTimeout(() => {
console.log('[离线模式] 所有阶段完成');
panel.webview.postMessage({
type: "completeProgress"
});
}, 1000);
}, simulationDelay);
}
}
},
onToolStart: (toolName) => {
@ -360,17 +387,6 @@ async function handleUserMessageWithBackend(
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 {
// 隐藏状态栏
@ -385,6 +401,11 @@ async function handleUserMessageWithBackend(
isComplete: true,
});
// 发送任务完成消息
panel.webview.postMessage({
command: "taskComplete",
});
// 发送系统通知 - AI 响应完成
const notificationService = NotificationService.getInstance();
notificationService.success(
@ -414,7 +435,7 @@ async function handleUserMessageWithBackend(
});
panel.webview.postMessage({
command: "receiveMessage",
text: `错误: ${message}`,
text: `错误: ${message}`,
});
// 恢复输入状态
panel.webview.postMessage({

View File

@ -129,6 +129,62 @@ export async function readDirectory(
}
}
/**
* 列出目录下的文件和文件夹(不读取内容,仅返回路径)
*/
export async function listDirectory(
dirPath: string,
extensions?: string[]
): Promise<string[]> {
try {
// 如果是相对路径,转换为绝对路径
let absolutePath = dirPath;
if (!path.isAbsolute(dirPath)) {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders && workspaceFolders.length > 0) {
absolutePath = path.join(workspaceFolders[0].uri.fsPath, dirPath);
}
}
const dirUri = vscode.Uri.file(absolutePath);
// 检查目录是否存在
try {
const stat = await vscode.workspace.fs.stat(dirUri);
if (stat.type !== vscode.FileType.Directory) {
throw new Error(`路径不是目录: ${absolutePath}`);
}
} catch (error) {
throw new Error(`目录不存在: ${absolutePath}`);
}
// 读取目录内容
const entries = await vscode.workspace.fs.readDirectory(dirUri);
const results: string[] = [];
for (const [fileName, fileType] of entries) {
if (fileType === vscode.FileType.Directory) {
results.push(fileName + '/');
} else if (fileType === vscode.FileType.File) {
// 扩展名过滤
if (extensions && extensions.length > 0) {
const ext = path.extname(fileName);
// 规范化扩展名(支持 "v" 和 ".v" 两种格式)
const normalizedExts = extensions.map(e => e.startsWith('.') ? e : '.' + e);
if (!normalizedExts.includes(ext)) {
continue;
}
}
results.push(fileName);
}
}
return results;
} catch (error) {
throw error;
}
}
/**
* 获取文件信息
*/

View File

@ -13,6 +13,7 @@ import {
abortCurrentDialog,
handleOptimizePrompt,
} from "../utils/messageHandler";
import { setCustomConfig } from "../config/settings";
/**
* 创建并显示IC 侧边栏视图
@ -124,6 +125,10 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
case "showWarning":
vscode.window.showWarningMessage(message.message);
break;
// 新增:打开用户手册
case "openUserManual":
vscode.commands.executeCommand("ic-coder.openUserManual");
break;
// 新增:处理用户回答
case "submitAnswer":
handleUserAnswer(
@ -141,6 +146,21 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
case "optimizePrompt":
handleOptimizePrompt(panel, message.prompt);
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,
@ -158,52 +178,21 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
private readonly extensionUri: vscode.Uri,
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> {
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> {
try {
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;
}
return true; // 始终返回已登录状态
}
resolveWebviewView(webviewView: vscode.WebviewView) {
@ -223,30 +212,8 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
console.log('[ICViewProvider] Webview options 已设置');
console.log('[ICViewProvider] extensionUri:', this.extensionUri.toString());
// 【关键修复】先设置默认 HTML避免一直加载
try {
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.html = this.getWebviewContent(webviewView.webview, true);
// 处理侧边栏的消息
webviewView.webview.onDidReceiveMessage(
@ -261,11 +228,28 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
} else if (message.command === "openICCoder") {
// 打开 IC Coder 官网
vscode.env.openExternal(vscode.Uri.parse('https://www.iccoder.com'));
} else if (message.command === "openUserManual") {
// 打开用户手册
vscode.commands.executeCommand("ic-coder.openUserManual");
} else if (message.command === "openExternalUrl") {
// 打开外部链接
if (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,
@ -338,7 +322,7 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
<img src="${logoUri}" alt="IC Coder" width="120" />
<h2>欢迎使用 IC Coder</h2>
${isLoggedIn
? '<button class="btn" onclick="openChat()">开始创作</button>'
? '<button class="btn" onclick="openChat()">开始使用</button>'
: '<button class="btn" onclick="login()">登录账户</button>'
}
</div>

View File

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

View File

@ -4,7 +4,14 @@
export function getExampleShowcaseContent(): string {
return `
<div class="example-showcase" id="exampleShowcase">
<div class="showcase-header">
<div class="showcase-title">示例</div>
<button class="refresh-button" onclick="refreshExamples()" title="换一批">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.5 2V8M21.5 8H15.5M21.5 8L18 4.5C16.7429 3.24286 15.1767 2.35596 13.4606 1.93597C11.7446 1.51598 9.94736 1.57986 8.26381 2.12059C6.58027 2.66131 5.07831 3.65985 3.91872 4.99987C2.75913 6.33989 1.98648 7.96902 1.68 9.71M2.5 22V16M2.5 16H8.5M2.5 16L6 19.5C7.25714 20.7571 8.82331 21.644 10.5394 22.064C12.2554 22.484 14.0526 22.4201 15.7362 21.8794C17.4197 21.3387 18.9217 20.3401 20.0813 19.0001C21.2409 17.6601 22.0135 16.031 22.32 14.29" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div class="example-cards">
<div class="example-card" onclick="sendExample(0)">
<div class="example-icon">
@ -34,14 +41,6 @@ export function getExampleShowcaseContent(): string {
</div>
</div>
</div>
<div class="web-link">
<a href="https://iccoder.com" target="_blank" class="web-link-button">
<span class="link-icon">🌐</span>
<span>IC Coder Web端</span>
<span class="link-arrow">→</span>
</a>
</div>
</div>
`;
}
@ -62,12 +61,44 @@ export function getExampleShowcaseStyles(): string {
display: none;
}
.showcase-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.showcase-title {
font-size: 14px;
font-weight: 600;
color: var(--vscode-foreground);
margin-bottom: 12px;
text-align: left;
}
.refresh-button {
background: transparent;
border: none;
color: var(--vscode-foreground);
cursor: pointer;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.2s ease;
opacity: 0.6;
}
.refresh-button:hover {
opacity: 1;
background: var(--vscode-input-background);
}
.refresh-button svg {
transition: transform 0.3s ease;
}
.refresh-button:active svg {
transform: rotate(180deg);
}
.example-cards {
@ -165,41 +196,6 @@ export function getExampleShowcaseStyles(): string {
-webkit-box-orient: vertical;
}
.web-link {
display: flex;
justify-content: center;
padding-top: 20px;
border-top: 1px solid var(--vscode-panel-border);
margin-top: 8px;
}
.web-link-button {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: transparent;
border: none;
text-decoration: none;
font-size: 14px;
font-weight: 600;
transition: all 0.2s ease;
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 50%, #a855f7 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
outline: none;
}
.web-link-button:focus {
outline: none;
}
.web-link-button:hover {
transform: translateY(-1px);
opacity: 0.8;
}
.link-icon {
font-size: 16px;
}
@ -208,10 +204,6 @@ export function getExampleShowcaseStyles(): string {
font-size: 16px;
transition: transform 0.2s ease;
}
.web-link-button:hover .link-arrow {
transform: translateX(3px);
}
`;
}
@ -220,15 +212,74 @@ export function getExampleShowcaseStyles(): string {
*/
export function getExampleShowcaseScript(): string {
return `
// 示例文本数组
const exampleTexts = [
'生成一个SPI控制器',
'生成一个GMII接口的以太网UDP通信模块'
// 所有可用的示例
const allExamples = [
'设计一个算术逻辑单元,完成常见运算',
'实现一个优先编码器,多个输入同时有效时,只输出优先级最高的那个编号',
'实现一个译码器,把二进制编号转换成 one-hot 输出',
'实现一个移位寄存器,完成串行/并行数据移位与装载',
'实现一个按键消抖模块,解决机械按键抖动问题',
'实现一个跑马灯控制器,控制 LED 形成不同流动效果',
'实现一个序列检测器,检测串行输入中是否出现指定比特序列',
'实现一个LFSR 伪随机数发生器',
'实现一个自动售货机,模拟一个简单售货逻辑',
'实现一个交通灯控制器,控制两方向交通灯的切换',
'实现一个先进先出的数据缓冲区',
'单端口 RAM 读写控制器',
'实现一个移位加法乘法器,不用 * 运算符'
];
// 当前显示的示例文本
let exampleTexts = ['生成一个SPI控制器', '生成一个GMII接口的以太网UDP通信模块'];
// 存储待发送的示例索引
let pendingExampleIndex = -1;
// 节流控制
let refreshing = false;
// 刷新示例
function refreshExamples() {
if (refreshing) return;
refreshing = true;
const used = new Set();
const newExamples = [];
while (newExamples.length < 2) {
const idx = Math.floor(Math.random() * allExamples.length);
if (!used.has(idx)) {
used.add(idx);
newExamples.push(allExamples[idx]);
}
}
exampleTexts = newExamples;
updateExampleCards();
setTimeout(() => { refreshing = false; }, 500);
}
// 更新示例卡片显示
function updateExampleCards() {
const container = document.querySelector('.example-cards');
if (!container) return;
container.innerHTML = exampleTexts.map((text, i) => \`
<div class="example-card" onclick="sendExample(\${i})">
<div class="example-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 13H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 17H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 9H9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="example-content">
<div class="example-title">\${text}</div>
</div>
</div>
\`).join('');
}
// 直接发送示例消息
function sendExample(index) {
// 先检查邀请码验证状态

View File

@ -4,75 +4,15 @@
export function getGeneralSettingsComponentContent(): string {
return `
<div class="general-settings">
<h3 class="settings-section-title">通用设置</h3>
<h3 class="settings-section-title">后端服务配置</h3>
<div class="settings-section">
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">主题</label>
<span class="settings-item-description">选择界面主题</span>
<label class="settings-item-label">后端服务地址</label>
<span class="settings-item-description">自定义后端 API 地址</span>
</div>
<select class="settings-select" id="themeSelect">
<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>
<input type="text" class="settings-input-text" id="backendUrlInput" placeholder="https://iccoder.com">
</div>
</div>
@ -176,6 +116,21 @@ export function getGeneralSettingsComponentStyles(): string {
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 {
position: relative;
display: inline-block;
@ -270,57 +225,37 @@ export function getGeneralSettingsComponentScript(): string {
// 保存通用设置
function saveGeneralSettings() {
const settings = {
theme: document.getElementById('themeSelect').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,
backendUrl: document.getElementById('backendUrlInput').value,
};
// 发送消息到扩展
vscode.postMessage({
command: 'saveGeneralSettings',
settings: settings
});
// 显示保存成功提示
console.log('通用设置已保存', settings);
closeSettingsModal();
}
// 重置通用设置
function resetGeneralSettings() {
document.getElementById('themeSelect').value = 'auto';
document.getElementById('languageSelect').value = 'zh-CN';
document.getElementById('autoSaveCheckbox').checked = true;
document.getElementById('showTimestampCheckbox').checked = false;
document.getElementById('fontSizeInput').value = '14';
document.getElementById('syntaxHighlightCheckbox').checked = true;
document.getElementById('backendUrlInput').value = '';
// 清空保存的配置
vscode.postMessage({
command: 'saveGeneralSettings',
settings: { backendUrl: '' }
});
console.log('通用设置已重置为默认值');
closeSettingsModal();
}
// 加载通用设置
function loadGeneralSettings(settings) {
if (!settings) return;
if (settings.theme) {
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;
if (settings.backendUrl) {
document.getElementById('backendUrlInput').value = settings.backendUrl;
}
}
`;

View File

@ -382,7 +382,7 @@ export function getMessageAreaStyles(): string {
}
/* 低调显示的工具调用 - 移除边距和背景 */
.segment-tool.low-profile {
margin: 5px 0px;
margin: 25px 0px;
padding: 0;
background: none;
}
@ -549,7 +549,7 @@ export function getMessageAreaStyles(): string {
max-height: 0;
}
.tool-segment-description {
margin: 6px 0 0 0px;
margin: 25px 0 0 0px;
font-size: 0.9rem;
color: var(--vscode-descriptionForeground);
line-height: 1.4;
@ -590,9 +590,9 @@ export function getMessageAreaStyles(): string {
}
.segment-question .question-option {
padding: 8px 16px;
background: #007ACC;
background: #3d3f41;
color: #ffffff;
border: 1px solid #007ACC;
border: 1px solid #474747;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
@ -1211,7 +1211,7 @@ export function getMessageAreaScript(): string {
const optionsHtml = q.options.map(opt => {
const isSelected = selectedAnswers.includes(opt);
return \`<label class="question-option\${isSelected ? ' selected' : ''}" 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:5px 5px 5px 0;">
<input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}>
<span>\${opt}</span>
</label>\`;

View File

@ -9,66 +9,16 @@ export function getModelSelectorContent(
autoIcon: string = "",
liteIcon: string = "",
syIcon: string = "",
maxIcon: string = ""
maxIcon: string = "",
): string {
return `
<!-- 模型选择 -->
<div class="tooltip">
<div class="custom-select" id="modelSelect">
<div class="select-trigger" onclick="toggleModelDropdown()">
<span class="select-value" id="modelValue">Auto</span>
<svg class="select-arrow" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M507.8 727.728a30.016 30.016 0 0 1-21.288-8.824L231.104 463.496a30.088 30.088 0 0 1 0-42.568 30.088 30.088 0 0 1 42.568 0l234.128 234.128 234.16-234.128a30.088 30.088 0 0 1 42.568 0 30.088 30.088 0 0 1 0 42.568L529.08 718.904a30 30 0 0 1-21.28 8.824z" fill="#8a8a8a"/>
</svg>
<div class="model-display">
<img src="${maxIcon || ""}" class="model-icon" alt="Max" style="display: ${maxIcon ? "block" : "none"};">
<span class="model-label">Max</span>
</div>
<div class="select-dropdown" id="modelDropdown">
<div class="select-option selected" data-value="auto" onclick="selectModel('auto', 'Auto')">
${
autoIcon
? `<img src="${autoIcon}" class="model-icon" alt="Auto">`
: ""
}
<div class="option-content">
<span class="option-label">Auto</span>
<span class="option-desc">智能匹配最优模型</span>
</div>
</div>
<div class="select-option" data-value="lite" onclick="selectModel('lite', 'Lite')">
${
liteIcon
? `<img src="${liteIcon}" class="model-icon" alt="Lite">`
: ""
}
<div class="option-content">
<span class="option-label">Lite</span>
<span class="option-desc">基础模型,快速相应,适合简单任务</span>
</div>
</div>
<div class="select-option" data-value="syntaxic" onclick="selectModel('syntaxic', 'Syntaxic')">
${
syIcon
? `<img src="${syIcon}" class="model-icon" alt="Syntaxic">`
: ""
}
<div class="option-content">
<span class="option-label">Syntaxic</span>
<span class="option-desc">均衡成本和性能节省credits同时保持可靠输出</span>
</div>
</div>
<div class="select-option" data-value="max" onclick="selectModel('max', 'Max')">
${
maxIcon
? `<img src="${maxIcon}" class="model-icon" alt="Max">`
: ""
}
<div class="option-content">
<span class="option-label">Max</span>
<span class="option-desc">最强性能,质量优先,适合复杂任务</span>
</div>
</div>
</div>
</div>
<span class="tooltiptext">选择模型</span>
<span class="tooltiptext">IC Coder自研FPGA专属微调模型</span>
</div>
`;
}
@ -78,72 +28,16 @@ export function getModelSelectorContent(
*/
export function getModelSelectorStyles(): string {
return `
/* 自定义下拉框样式 */
.custom-select {
position: relative;
user-select: none;
}
.select-trigger {
.model-display {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
background: var(--vscode-input-background);
color: var(--vscode-foreground);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background 0.2s ease;
}
.select-trigger:hover {
background: var(--vscode-list-hoverBackground);
}
.select-value {
white-space: nowrap;
}
.select-arrow {
width: 12px;
height: 12px;
flex-shrink: 0;
transition: transform 0.2s ease;
}
.custom-select.active .select-arrow {
transform: rotate(180deg);
}
.select-dropdown {
position: absolute;
bottom: calc(100% + 2px);
left: 0;
min-width: 100%;
background: var(--vscode-dropdown-background);
border: 1px solid var(--vscode-dropdown-border);
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1100;
display: none;
overflow: visible;
}
.custom-select.active .select-dropdown {
display: block;
}
/* 模型选择器的选项样式 */
#modelDropdown .select-option {
position: relative;
padding: 8px 12px;
cursor: pointer;
transition: background 0.2s ease;
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
}
#modelDropdown .select-option:hover {
background: rgba(128, 128, 128, 0.3);
}
#modelDropdown .select-option.selected {
background: rgba(128, 128, 128, 0.5);
color: var(--vscode-foreground);
cursor: default;
}
.model-icon {
width: 16px;
@ -151,21 +45,7 @@ export function getModelSelectorStyles(): string {
flex-shrink: 0;
object-fit: contain;
}
.option-content {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1;
}
.option-label {
font-size: 13px;
color: var(--vscode-foreground);
font-weight: 500;
white-space: nowrap;
}
.option-desc {
font-size: 11px;
color: var(--vscode-descriptionForeground);
.model-label {
white-space: nowrap;
}
`;
@ -176,58 +56,9 @@ export function getModelSelectorStyles(): string {
*/
export function getModelSelectorScript(): string {
return `
// 模型选择相关变量
let currentModel = 'auto';
// 切换模型下拉框显示/隐藏
function toggleModelDropdown() {
const modelSelect = document.getElementById('modelSelect');
const customSelect = document.getElementById('customSelect');
if (modelSelect) {
modelSelect.classList.toggle('active');
// 关闭模式下拉框
if (customSelect) {
customSelect.classList.remove('active');
}
}
}
// 选择模型
function selectModel(value, label) {
currentModel = value;
const modelValue = document.getElementById('modelValue');
if (modelValue) {
modelValue.textContent = label;
}
// 更新选中状态
const options = document.querySelectorAll('#modelDropdown .select-option');
options.forEach(option => {
if (option.getAttribute('data-value') === value) {
option.classList.add('selected');
} else {
option.classList.remove('selected');
}
});
// 关闭下拉框
const modelSelect = document.getElementById('modelSelect');
if (modelSelect) {
modelSelect.classList.remove('active');
}
}
// 点击外部关闭模型下拉框
document.addEventListener('click', (event) => {
const modelSelect = document.getElementById('modelSelect');
if (modelSelect && !modelSelect.contains(event.target)) {
modelSelect.classList.remove('active');
}
});
// 获取当前选中的模型
// 获取当前选中的模型(固定为 max
function getCurrentModel() {
return currentModel;
return 'max';
}
`;
}

View File

@ -1,6 +1,6 @@
/**
* 更多选项组件
* 包含用户手册和用户反馈入口
* 包含用户手册入口
*/
/**
@ -28,40 +28,10 @@ export function getMoreOptionsComponentContent(): string {
<div class="option-desc">查看使用文档和帮助</div>
</div>
</div>
<div class="more-option-item" id="userFeedbackOption">
<div class="option-icon">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z" fill="currentColor"/>
</svg>
</div>
<div class="option-text">
<div class="option-label">用户反馈</div>
<div class="option-desc">提交问题和建议</div>
</div>
</div>
</div>
</div>
</div>
<!-- 用户反馈二维码弹窗 -->
<div class="feedback-qrcode-modal" id="feedbackQRCodeModal">
<div class="feedback-qrcode-overlay" onclick="closeFeedbackQRCode()"></div>
<div class="feedback-qrcode-content">
<div class="feedback-qrcode-header">
<span class="feedback-qrcode-title">用户反馈</span>
<button class="feedback-qrcode-close" onclick="closeFeedbackQRCode()">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="currentColor"/>
</svg>
</button>
</div>
<div class="feedback-qrcode-body">
<img class="feedback-qrcode-image" id="feedbackQRCodeImage" alt="微信二维码" />
<p class="feedback-qrcode-text">扫描二维码添加微信反馈</p>
</div>
</div>
</div>
</div>
`;
}
@ -163,125 +133,6 @@ export function getMoreOptionsComponentStyles(): string {
.option-desc {
display: none;
}
/* 用户反馈二维码弹窗 */
.feedback-qrcode-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 20000;
align-items: center;
justify-content: center;
}
.feedback-qrcode-modal.active {
display: flex;
animation: fadeIn 0.2s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.feedback-qrcode-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
cursor: pointer;
}
.feedback-qrcode-content {
position: relative;
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-widget-border);
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
max-width: 400px;
width: 90%;
animation: slideUp 0.2s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.feedback-qrcode-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--vscode-widget-border);
}
.feedback-qrcode-title {
font-size: 14px;
font-weight: 600;
color: var(--vscode-foreground);
}
.feedback-qrcode-close {
width: 28px;
height: 28px;
padding: 0;
background: transparent;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s ease;
}
.feedback-qrcode-close:hover {
background: var(--vscode-toolbar-hoverBackground);
}
.feedback-qrcode-close svg {
width: 16px;
height: 16px;
color: var(--vscode-foreground);
}
.feedback-qrcode-body {
padding: 24px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.feedback-qrcode-image {
width: 200px;
height: 200px;
border: 1px solid var(--vscode-widget-border);
border-radius: 8px;
}
.feedback-qrcode-text {
margin: 0;
font-size: 13px;
color: var(--vscode-descriptionForeground);
text-align: center;
}
`;
}
@ -331,29 +182,6 @@ export function getMoreOptionsComponentScript(): string {
closeMoreOptionsDropdown();
}
// 打开用户反馈
function openUserFeedback() {
console.log('打开用户反馈');
vscode.postMessage({ command: 'openUserFeedback' });
closeMoreOptionsDropdown();
}
// 显示用户反馈二维码弹窗
function showFeedbackQRCode() {
const modal = document.getElementById('feedbackQRCodeModal');
if (modal) {
modal.classList.add('active');
}
}
// 关闭用户反馈二维码弹窗
function closeFeedbackQRCode() {
const modal = document.getElementById('feedbackQRCodeModal');
if (modal) {
modal.classList.remove('active');
}
}
// 绑定更多选项事件
document.addEventListener('DOMContentLoaded', () => {
// 绑定用户手册选项
@ -362,12 +190,6 @@ export function getMoreOptionsComponentScript(): string {
userManualOption.addEventListener('click', openUserManual);
}
// 绑定用户反馈选项
const userFeedbackOption = document.getElementById('userFeedbackOption');
if (userFeedbackOption) {
userFeedbackOption.addEventListener('click', openUserFeedback);
}
// 点击页面其他地方关闭下拉面板
document.addEventListener('click', (e) => {
const dropdown = document.getElementById('moreOptionsDropdown');

View File

@ -25,7 +25,7 @@ export function getProgressBarContent(): string {
<span class="step-number">1</span>
<span class="step-check">✓</span>
</div>
<div class="step-label">Spec</div>
<div class="step-label">Specification</div>
</div>
<div class="progress-line"></div>

View File

@ -3,11 +3,6 @@ import {
getGeneralSettingsComponentStyles,
getGeneralSettingsComponentScript,
} from "./generalSettingsComponent";
import {
getRulesSettingsComponentContent,
getRulesSettingsComponentStyles,
getRulesSettingsComponentScript,
} from "./rulesSettingsComponent";
/**
* 获取设置面板的 HTML 内容
@ -31,18 +26,13 @@ export function getSettingsComponentContent(): string {
<button class="settings-nav-item active" data-tab="general" onclick="switchSettingsTab('general')">
通用
</button>
<button class="settings-nav-item" data-tab="rules" onclick="switchSettingsTab('rules')">
规则
</button>
</div>
<div class="settings-content">
<div class="settings-tab-content active" id="generalSettings">
${getGeneralSettingsComponentContent()}
</div>
<div class="settings-tab-content" id="rulesSettings">
${getRulesSettingsComponentContent()}
</div>
</div>
</div>
</div>
@ -186,7 +176,6 @@ export function getSettingsComponentStyles(): string {
}
${getGeneralSettingsComponentStyles()}
${getRulesSettingsComponentStyles()}
`;
}
@ -196,13 +185,14 @@ export function getSettingsComponentStyles(): string {
export function getSettingsComponentScript(): string {
return `
${getGeneralSettingsComponentScript()}
${getRulesSettingsComponentScript()}
// 打开设置面板
function openSettingsModal() {
const modal = document.getElementById('settingsModal');
if (modal) {
modal.classList.add('active');
// 请求加载设置
vscode.postMessage({ command: 'loadGeneralSettings' });
}
}

View File

@ -9,57 +9,7 @@
*/
export function getUserInfoComponentContent(): string {
return `
<div class="user-info-wrapper">
<!-- 用户详情下拉面板 -->
<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 class="user-info-wrapper" style="display: none;">
</div>
`;
}

View File

@ -18,6 +18,11 @@ import {
getMessageAreaScript,
} from "./messageArea";
import { getAgentCardStyles, getAgentCardScript } from "./agentCard";
import {
getMoreOptionsComponentContent,
getMoreOptionsComponentStyles,
getMoreOptionsComponentScript,
} from "./moreOptionsComponent";
import {
getProgressBarContent,
getProgressBarStyles,
@ -110,6 +115,7 @@ export function getWebviewContent(
}
${getMessageAreaStyles()}
${getAgentCardStyles()}
${getMoreOptionsComponentStyles()}
${getWaveformPreviewContent()}
${getConversationHistoryBarStyles()}
${getProgressBarStyles()}
@ -375,16 +381,32 @@ export function getWebviewContent(
font-size: 13px;
color: var(--vscode-descriptionForeground);
}
.status-bar #statusText {
background: linear-gradient(90deg,
var(--vscode-descriptionForeground) 0%,
var(--vscode-foreground) 50%,
var(--vscode-descriptionForeground) 100%);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: textShimmer 2s linear infinite;
}
@keyframes textShimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--vscode-charts-blue);
animation: statusPulse 1.5s ease-in-out infinite;
box-shadow: 0 0 8px currentColor;
}
@keyframes statusPulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.8); }
0%, 100% { opacity: 1; transform: scale(1.2); }
50% { opacity: 0.3; transform: scale(0.6); }
}
.status-bar.working .status-indicator {
background: var(--vscode-charts-orange);
@ -488,13 +510,17 @@ export function getWebviewContent(
${getNdtWelcomeModalContent(logoUri)}
${getExpiredModalContent(logoUri)}
<div class="header">
<div style="display: flex; align-items: center; justify-content: center;">
<div style="display: flex; align-items: flex-end; justify-content: center">
<img src="${logoUri}" alt="IC Coder" style="max-width: 100%; height: auto; max-height: 80px;" />
<span style="font-size: 23px; font-weight: bold; background: linear-gradient(to bottom, #b2e4ff, #42bcff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin: 0 0 14px -16px;">企业版</span>
</div>
<p style="font-size: 16px; margin-top: 12px; line-height: 1.5;">
The <span style="background: linear-gradient(to right, #42bcff, #4A9EFF); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: bold;">Agentic AI</span> Verilog Coding Platform,
<span style="display: block; margin-top: 8px;">将芯片设计与验证的效率提升至少20倍</span>
<p style="font-size: 16px; margin-top: 8px; line-height: 1.5;">
The <span style="background: linear-gradient(to right, #42bcff, #4A9EFF); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: bold;">Agentic AI</span> Verilog Coding Platform
<span style="display: block; margin-top: 8px;">将FPGA研发效率提升至少20倍</span>
</p>
<div style="margin-top: 16px; padding: 8px 20px; background: linear-gradient(135deg, rgba(255, 215, 0, 0.15), rgba(255, 165, 0, 0.15)); border: 1px solid rgba(255, 215, 0, 0.3); border-radius: 6px;">
<p style="font-size: 13px; margin: 0; background: linear-gradient(135deg, #FFD700, #FFA500); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 600; letter-spacing: 1px;">宁德时代专属定制版</p>
</div>
</div>
<div class="chat-container">
@ -754,6 +780,11 @@ export function getWebviewContent(
hideLoadingIndicator();
break;
case 'taskComplete':
// 显示任务完成提示
addMessage('✅ 任务已完成', 'bot');
break;
case 'workspaceStatus':
// 更新工作区状态
if (typeof hasWorkspace !== 'undefined') {
@ -903,6 +934,13 @@ export function getWebviewContent(
}
break;
case 'loadedGeneralSettings':
// 加载通用设置
if (typeof loadGeneralSettings === 'function') {
loadGeneralSettings(message.settings);
}
break;
default:
console.log('[WebView] 未处理的消息类型:', message.command);
}
@ -910,6 +948,7 @@ export function getWebviewContent(
${getMessageAreaScript()}
${getAgentCardScript()}
${getMoreOptionsComponentScript()}
${getWaveformPreviewScript()}
${getConversationHistoryBarScript()}
${getProgressBarScript()}