Compare commits
34 Commits
feat/Delet
...
feat/ningD
| Author | SHA1 | Date | |
|---|---|---|---|
| 47f95afabb | |||
| 7fe87e515b | |||
| 081ddec55c | |||
| 12c2f634bd | |||
| 790110ba7e | |||
| b9dc631bf7 | |||
| 6425496d2e | |||
| fd5a01c67f | |||
| 29e80ce296 | |||
| c244a308d7 | |||
| a25d68f527 | |||
| 77b54aebf0 | |||
| 840436eb36 | |||
| f5dd7534f0 | |||
| ebb9de5294 | |||
| 531d140b99 | |||
| 97b8e8aa7d | |||
| 4ed998e937 | |||
| ad0f0336d5 | |||
| 7cde4fa138 | |||
| 1b7259d1c1 | |||
| 09ff812562 | |||
| e7c631d532 | |||
| 06573e37d7 | |||
| d740f4da44 | |||
| f24bd38ec7 | |||
| 45934baf0a | |||
| 4384ee53c5 | |||
| d89c326be5 | |||
| 2dccb4f871 | |||
| a9ddf3074e | |||
| db087bb184 | |||
| 5e9083041f | |||
| be0555d6bc |
@ -18,10 +18,15 @@ node_modules/**
|
||||
|
||||
# 文档(避免中文文件名打包问题)
|
||||
docs/**
|
||||
PUBLISH.md
|
||||
CLAUDE.md
|
||||
|
||||
# 只排除 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/**
|
||||
|
||||
@ -2,6 +2,12 @@
|
||||
|
||||
所有重要的项目变更都将记录在此文件中。
|
||||
|
||||
## [1.0.12] - 2026-03-06
|
||||
|
||||
### 新增
|
||||
|
||||
- 支持 AskUserQuestion 多问题和多选功能
|
||||
|
||||
## [1.0.9] - 2026-03-04
|
||||
|
||||
### 优化
|
||||
|
||||
42
docs/code-to-chat-feature.md
Normal 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"
|
||||
}
|
||||
```
|
||||
55
docs/webpack-optimization.md
Normal 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,检查是否引入了不必要的依赖
|
||||
251
media/USER_MANUAL.md
Normal 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` 文件
|
||||
- 等待安装完成
|
||||
|
||||

|
||||
|
||||
**方式 B:通过扩展视图**
|
||||
- 点击左侧活动栏的扩展图标(或按 `Ctrl+Shift+X`)
|
||||
- 点击扩展视图右上角的 `...` (更多操作)
|
||||
- 选择 `从 VSIX 安装...`
|
||||
- 选择 `iccoder-Trial-1.0.vsix` 文件
|
||||
|
||||

|
||||
|
||||
**方式 C:通过命令行**
|
||||
|
||||
```bash
|
||||
code --install-extension iccoder-Trial-1.0.vsix
|
||||
```
|
||||
|
||||
4. **重启 VS Code**
|
||||
- 安装完成后,建议重启 VS Code 以确保插件正常加载
|
||||
|
||||
#### 步骤 2:打开 IC Coder 界面
|
||||
|
||||
**登录后会自动打开**,手动打开也有以下几种方式:
|
||||
|
||||
**方式 1:通过侧边栏**
|
||||
|
||||
- 点击左侧活动栏的 IC Coder 图标
|
||||
- 侧边栏会显示 IC Coder 聊天界面
|
||||
|
||||

|
||||
|
||||
**方式 2:通过命令面板**
|
||||
|
||||
- 按 `Ctrl+Shift+P` (Windows/Linux) 或 `Cmd+Shift+P` (macOS)
|
||||
- 输入以下命令之一:
|
||||
- `IC Coder: 打开聊天` - 打开聊天界面
|
||||
- `打开 IC Coder 助手` - 打开助手面板
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
#### 步骤 3:开始使用
|
||||
|
||||
插件已预配置好后端服务,安装后即可直接使用,无需手动配置。
|
||||
|
||||

|
||||
|
||||
### 故障排除
|
||||
|
||||
#### 问题 :插件无法安装
|
||||
|
||||
**症状**:安装 VSIX 文件时报错
|
||||
|
||||
#### 解决方案:
|
||||
|
||||
- 确认 VS Code 版本 >= 1.60.0
|
||||
|
||||
- 检查 VSIX 文件是否完整(未损坏)
|
||||
|
||||
- 尝试以管理员权限运行 VS Code
|
||||
|
||||
- 清除 VS Code 缓存后重试
|
||||
|
||||
### 完整使用流程示例
|
||||
|
||||
下面通过一个完整的案例,展示如何使用 IC Coder 从需求到代码生成的全过程。
|
||||
|
||||
#### 步骤 1:输入设计需求
|
||||
|
||||
在对话框中输入设计需求,例如:
|
||||
|
||||
```
|
||||
我需要设计一个 8 位加法器,要求有进位输入和进位输出
|
||||
```
|
||||
|
||||
点击**发送**按钮。
|
||||
|
||||

|
||||
|
||||
#### 步骤 2:AI 询问补充信息
|
||||
|
||||
AI 会根据需求,询问一些关键的设计细节。例如:
|
||||
|
||||
- 是否需要溢出检测?
|
||||
- 时钟频率要求是多少?
|
||||
- 是否需要流水线设计?
|
||||
|
||||
您只需要根据实际需求**选择相应的选项或者直接输入需求**即可,AI 会根据您的选择生成最合适的设计方案。
|
||||
|
||||
#### 步骤 3:确认 AI 生成的任务列表
|
||||
|
||||
AI 会根据您的需求和补充信息,生成一个详细的任务列表(Todo List)
|
||||
|
||||

|
||||
|
||||
仔细查看任务列表,确认无误后点击**确认**按钮,AI 将开始执行。
|
||||
|
||||
#### 步骤 4:观察 AI 执行过程
|
||||
|
||||
AI 开始工作后,您可以在对话框中实时看到所有执行步骤:
|
||||
|
||||

|
||||
|
||||
每个步骤完成后,任务列表中对应的项目会被标记为完成状态。
|
||||
|
||||
#### 步骤 5:仿真运行与结果查看
|
||||
|
||||
当 AI 完成代码生成后,会自动运行仿真验证:
|
||||
|
||||

|
||||
|
||||
#### 步骤 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. **需提前打开一个文件夹作为工作区**,否则会准确的为您服务
|
||||
|
||||

|
||||
|
||||
2. **开箱即用**
|
||||
- 插件已预配置后端服务,无需手动设置
|
||||
- 安装后即可直接使用所有功能
|
||||
|
||||
---
|
||||
|
||||
**祝您使用愉快!如有问题欢迎反馈。**
|
||||
BIN
media/manual/仿真运行结果.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
media/manual/侧边栏打开.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
media/manual/命令面板打开.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
media/manual/安装方式1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
media/manual/安装方式2.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
media/manual/打开文件夹.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
media/manual/确认任务.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
media/manual/聊天界面.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
media/manual/观察执行过程.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
media/manual/输入需求.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
24
package.json
@ -2,7 +2,7 @@
|
||||
"name": "iccoder",
|
||||
"displayName": "IC Coder: Agentic Verilog Platform",
|
||||
"description": "Agentic Verilog Coding Platform for Real-World FPGAs",
|
||||
"version": "1.0.11",
|
||||
"version": "1.0.12",
|
||||
"publisher": "ICCoderAgenticVerilogPlatform",
|
||||
"engines": {
|
||||
"vscode": "^1.80.0"
|
||||
@ -54,6 +54,28 @@
|
||||
"command": "ic-coder.testNotification",
|
||||
"title": "测试系统通知",
|
||||
"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": {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
211
src/extension.ts
@ -2,49 +2,82 @@ 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: {
|
||||
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);
|
||||
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);
|
||||
@ -59,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(
|
||||
@ -156,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",
|
||||
@ -250,6 +284,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: 这些命令需要根据新的任务架构重新实现
|
||||
// 暂时注释掉,等待重新实现
|
||||
@ -312,16 +421,25 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// 注册 VCD 自定义编辑器
|
||||
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(
|
||||
openPanelCommand,
|
||||
openChatCommand,
|
||||
openVCDViewerCommand,
|
||||
openVCDViewerInBrowserCommand,
|
||||
openUserManualCommand,
|
||||
loginCommand,
|
||||
logoutCommand,
|
||||
changeInvitationCodeCommand,
|
||||
testNotificationCommand,
|
||||
addCodeToChat,
|
||||
// testTrialUserCommand,
|
||||
// testExpiredUserCommand,
|
||||
// TODO: 等待重新实现这些命令
|
||||
@ -332,7 +450,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// clearHistoryCommand,
|
||||
// searchSessionCommand,
|
||||
viewRegistration,
|
||||
vcdEditorProvider
|
||||
vcdEditorProvider,
|
||||
codeActionProvider
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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(
|
||||
@ -143,6 +77,9 @@ export async function showICHelperPanel(
|
||||
},
|
||||
);
|
||||
|
||||
// 保存 panel 引用到全局
|
||||
(global as any).currentICHelperPanel = panel;
|
||||
|
||||
// 为面板生成唯一ID
|
||||
const panelId = `panel_${Date.now()}_${Math.random()
|
||||
.toString(36)
|
||||
@ -227,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) {
|
||||
@ -491,6 +353,78 @@ export async function showICHelperPanel(
|
||||
// 退出登录
|
||||
vscode.commands.executeCommand("ic-coder.logout");
|
||||
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":
|
||||
// 采纳变更
|
||||
if (message.changeId) {
|
||||
@ -510,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":
|
||||
// 检查试用期是否过期
|
||||
@ -589,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":
|
||||
@ -636,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":
|
||||
// 打开用户反馈二维码弹窗
|
||||
@ -829,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,
|
||||
|
||||
181
src/panels/UserManualPanel.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/providers/codeActionProvider.ts
Normal 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];
|
||||
}
|
||||
}
|
||||
@ -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: () => {
|
||||
|
||||
@ -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: [],
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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") || "(目录为空)";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -291,7 +302,7 @@ async function executeSyntaxCheck(
|
||||
// 检查 iverilog 是否可用
|
||||
const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
|
||||
if (!iverilogCheck.available) {
|
||||
throw new Error(`iverilog 不可用: ${iverilogCheck.message}`);
|
||||
throw new Error(`IC Coder编译器不可用: ${iverilogCheck.message}`);
|
||||
}
|
||||
|
||||
// 创建临时文件
|
||||
@ -372,7 +383,7 @@ async function executeIverilog(
|
||||
// 检查 iverilog 是否可用
|
||||
const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
|
||||
if (!iverilogCheck.available) {
|
||||
throw new Error(`iverilog 不可用: ${iverilogCheck.message}`);
|
||||
throw new Error(`IC Coder编译器不可用: ${iverilogCheck.message}`);
|
||||
}
|
||||
|
||||
// 获取工作目录
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -7,7 +7,7 @@ import { promisify } from "util";
|
||||
function execCommand(
|
||||
command: string,
|
||||
args: string[],
|
||||
options: { cwd: string; env?: any }
|
||||
options: { cwd: string; env?: any },
|
||||
): Promise<{ stdout: string; stderr: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 在 Windows 上,如果路径包含空格,不使用 shell,直接用 spawn
|
||||
@ -23,25 +23,25 @@ function execCommand(
|
||||
let stderr = "";
|
||||
|
||||
// 在 Windows 上使用 GBK 编码解码输出
|
||||
const encoding = process.platform === 'win32' ? 'gbk' : 'utf8';
|
||||
const encoding = process.platform === "win32" ? "gbk" : "utf8";
|
||||
|
||||
child.stdout.on("data", (data) => {
|
||||
try {
|
||||
// 尝试使用 iconv-lite 解码(如果可用)
|
||||
const iconv = require('iconv-lite');
|
||||
const iconv = require("iconv-lite");
|
||||
stdout += iconv.decode(data, encoding);
|
||||
} catch {
|
||||
// 如果 iconv-lite 不可用,使用默认解码
|
||||
stdout += data.toString('utf8');
|
||||
stdout += data.toString("utf8");
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr.on("data", (data) => {
|
||||
try {
|
||||
const iconv = require('iconv-lite');
|
||||
const iconv = require("iconv-lite");
|
||||
stderr += iconv.decode(data, encoding);
|
||||
} catch {
|
||||
stderr += data.toString('utf8');
|
||||
stderr += data.toString("utf8");
|
||||
}
|
||||
});
|
||||
|
||||
@ -93,7 +93,7 @@ export interface VCDGenerationResult {
|
||||
* 检查项目中的 Verilog 文件完整性
|
||||
*/
|
||||
export async function checkVerilogProject(
|
||||
projectPath: string
|
||||
projectPath: string,
|
||||
): Promise<VerilogProjectCheck> {
|
||||
const result: VerilogProjectCheck = {
|
||||
isComplete: false,
|
||||
@ -164,7 +164,7 @@ export async function checkVerilogProject(
|
||||
return result;
|
||||
} catch (error) {
|
||||
result.errors.push(
|
||||
`检查项目时出错: ${error instanceof Error ? error.message : "未知错误"}`
|
||||
`检查项目时出错: ${error instanceof Error ? error.message : "未知错误"}`,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
@ -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 可执行文件路径
|
||||
*/
|
||||
@ -209,12 +236,30 @@ async function getIverilogPath(extensionPath: string): Promise<string> {
|
||||
let iverilogBin = "";
|
||||
|
||||
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") {
|
||||
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog");
|
||||
iverilogBin = path.join(
|
||||
extensionPath,
|
||||
"tools",
|
||||
"iverilog",
|
||||
"bin",
|
||||
"iverilog",
|
||||
);
|
||||
} else {
|
||||
// Linux
|
||||
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog");
|
||||
iverilogBin = path.join(
|
||||
extensionPath,
|
||||
"tools",
|
||||
"iverilog",
|
||||
"bin",
|
||||
"iverilog",
|
||||
);
|
||||
}
|
||||
|
||||
// 如果插件包中没有,尝试使用系统安装的 iverilog
|
||||
@ -258,7 +303,7 @@ async function getVvpPath(extensionPath: string): Promise<string> {
|
||||
*/
|
||||
export async function generateVCD(
|
||||
projectPath: string,
|
||||
extensionPath: string
|
||||
extensionPath: string,
|
||||
): Promise<VCDGenerationResult> {
|
||||
try {
|
||||
// 1. 检查项目完整性
|
||||
@ -286,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);
|
||||
@ -299,15 +344,97 @@ 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: `iverilog 编译失败:\n${error.message}`,
|
||||
message: `IC Coder编译器编译失败:\n${error.message}`,
|
||||
stderr: error.stderr,
|
||||
stdout: error.stdout,
|
||||
};
|
||||
}
|
||||
|
||||
// 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 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 行");
|
||||
} else {
|
||||
console.log(".vvp 文件无 shebang 行,跳过");
|
||||
}
|
||||
} catch (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
|
||||
const simArgs = [outputFile];
|
||||
console.log("执行仿真命令:", vvpPath, simArgs.join(" "));
|
||||
@ -318,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}`,
|
||||
@ -328,23 +459,46 @@ 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: "VCD 文件未生成。请确保 testbench 中包含 $dumpfile 和 $dumpvars 语句。",
|
||||
message:
|
||||
"VCD 文件未生成。请确保 testbench 中包含 $dumpfile 和 $dumpvars 语句。",
|
||||
stdout: simResult.stdout,
|
||||
};
|
||||
}
|
||||
|
||||
// 使用找到的第一个 VCD 文件
|
||||
const vcdFile = path.join(projectPath, vcdFiles[0]);
|
||||
|
||||
// 9. 清理中间文件
|
||||
try {
|
||||
const outputUri = vscode.Uri.file(outputFile);
|
||||
@ -373,7 +527,7 @@ export async function generateVCD(
|
||||
* 检查 iverilog 是否可用
|
||||
*/
|
||||
export async function checkIverilogAvailable(
|
||||
extensionPath: string
|
||||
extensionPath: string,
|
||||
): Promise<{ available: boolean; version?: string; message: string }> {
|
||||
try {
|
||||
const iverilogPath = await getIverilogPath(extensionPath);
|
||||
@ -385,7 +539,7 @@ export async function checkIverilogAvailable(
|
||||
} catch (error) {
|
||||
return {
|
||||
available: false,
|
||||
message: `iverilog 不可用。未找到文件: ${iverilogPath}`,
|
||||
message: `IC Coder编译器不可用。未找到文件: ${iverilogPath}`,
|
||||
};
|
||||
}
|
||||
|
||||
@ -404,12 +558,12 @@ export async function checkIverilogAvailable(
|
||||
return {
|
||||
available: true,
|
||||
version: version,
|
||||
message: `iverilog 可用: ${version}`,
|
||||
message: `IC Coder编译器可用: ${version}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
available: false,
|
||||
message: `iverilog 执行失败: ${error.message}\n${error.stderr || ""}`,
|
||||
message: `IC Coder编译器执行失败: ${error.message}\n${error.stderr || ""}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -444,10 +598,11 @@ export interface MultiVCDResult {
|
||||
function injectConditionalDump(
|
||||
tbContent: string,
|
||||
dumpModules: DumpModule[],
|
||||
vcdDir: string
|
||||
vcdDir: string,
|
||||
): string {
|
||||
// 匹配 $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);
|
||||
@ -469,7 +624,7 @@ function injectConditionalDump(
|
||||
*/
|
||||
function generateConditionalDumpCode(
|
||||
dumpModules: DumpModule[],
|
||||
vcdDir: string
|
||||
vcdDir: string,
|
||||
): string {
|
||||
if (dumpModules.length === 0) {
|
||||
return '$dumpfile("output.vcd");\n $dumpvars(0, dut);';
|
||||
@ -480,7 +635,7 @@ function generateConditionalDumpCode(
|
||||
dumpModules.forEach((module, index) => {
|
||||
const macroName = `DUMP_${module.name.toUpperCase()}`;
|
||||
const vcdPath = `${vcdDir}/${module.name}.vcd`;
|
||||
const directive = index === 0 ? '`ifdef' : '`elsif';
|
||||
const directive = index === 0 ? "`ifdef" : "`elsif";
|
||||
|
||||
lines.push(`${directive} ${macroName}`);
|
||||
lines.push(` $dumpfile("${vcdPath}");`);
|
||||
@ -488,12 +643,12 @@ function generateConditionalDumpCode(
|
||||
});
|
||||
|
||||
// 添加默认分支(使用第一个模块)
|
||||
lines.push('`else');
|
||||
lines.push("`else");
|
||||
lines.push(` $dumpfile("${vcdDir}/${dumpModules[0].name}.vcd");`);
|
||||
lines.push(` $dumpvars(1, ${dumpModules[0].path});`);
|
||||
lines.push('`endif');
|
||||
lines.push("`endif");
|
||||
|
||||
return lines.join('\n');
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -504,10 +659,10 @@ export async function generateMultiVCD(
|
||||
extensionPath: string,
|
||||
tbPath: string,
|
||||
dumpModules: DumpModule[],
|
||||
vcdDir: string = 'vcd'
|
||||
vcdDir: string = "vcd",
|
||||
): Promise<MultiVCDResult> {
|
||||
const results: MultiVCDResult['vcdFiles'] = [];
|
||||
let allStdout = '';
|
||||
const results: MultiVCDResult["vcdFiles"] = [];
|
||||
let allStdout = "";
|
||||
|
||||
try {
|
||||
// 1. 创建 vcd 目录
|
||||
@ -520,16 +675,21 @@ export async function generateMultiVCD(
|
||||
}
|
||||
|
||||
// 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 tbBytes = await vscode.workspace.fs.readFile(tbUri);
|
||||
const originalTb = Buffer.from(tbBytes).toString('utf-8');
|
||||
const originalTb = Buffer.from(tbBytes).toString("utf-8");
|
||||
|
||||
// 3. 注入条件编译代码
|
||||
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. 获取工具路径
|
||||
const iverilogPath = await getIverilogPath(extensionPath);
|
||||
@ -551,30 +711,38 @@ export async function generateMultiVCD(
|
||||
console.log(`[generateMultiVCD] 仿真模块: ${module.name} (${macroName})`);
|
||||
|
||||
try {
|
||||
// 编译(带宏定义)
|
||||
// 编译(带宏定义,启用 SystemVerilog 2012 标准)
|
||||
const compileArgs = [
|
||||
"-g2012",
|
||||
`-D${macroName}`,
|
||||
"-o", outputFile,
|
||||
...projectCheck.allVerilogFiles
|
||||
"-o",
|
||||
outputFile,
|
||||
...projectCheck.allVerilogFiles,
|
||||
];
|
||||
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}`;
|
||||
|
||||
results.push({
|
||||
moduleName: module.name,
|
||||
vcdPath: vcdPath,
|
||||
success: true
|
||||
success: true,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(`[generateMultiVCD] 模块 ${module.name} 仿真失败:`, error.message);
|
||||
console.error(
|
||||
`[generateMultiVCD] 模块 ${module.name} 仿真失败:`,
|
||||
error.message,
|
||||
);
|
||||
results.push({
|
||||
moduleName: module.name,
|
||||
vcdPath: vcdPath,
|
||||
success: false,
|
||||
error: error.message
|
||||
error: error.message,
|
||||
});
|
||||
// 继续执行其他模块
|
||||
}
|
||||
@ -587,19 +755,18 @@ export async function generateMultiVCD(
|
||||
// 忽略
|
||||
}
|
||||
|
||||
const successCount = results.filter(r => r.success).length;
|
||||
const successCount = results.filter((r) => r.success).length;
|
||||
return {
|
||||
success: successCount > 0,
|
||||
vcdFiles: results,
|
||||
message: `生成完成:${successCount}/${dumpModules.length} 个 VCD 文件成功`,
|
||||
stdout: allStdout
|
||||
stdout: allStdout,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
vcdFiles: results,
|
||||
message: `生成多 VCD 文件失败: ${error instanceof Error ? error.message : '未知错误'}`
|
||||
message: `生成多 VCD 文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,7 +37,14 @@ let currentSession: DialogSession | null = null;
|
||||
/** 最后一个活跃的 taskId(用于压缩等操作) */
|
||||
let lastTaskId: string | null = null;
|
||||
|
||||
async function trackFileChange(filePath: string, oldContent: string, newContent: string): Promise<void> {
|
||||
/** 离线模式仿真模拟标志(防止重复触发) */
|
||||
let offlineSimulationTriggered = false;
|
||||
|
||||
async function trackFileChange(
|
||||
filePath: string,
|
||||
oldContent: string,
|
||||
newContent: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
changeTracker.trackChange(filePath, oldContent, newContent);
|
||||
} catch (error) {
|
||||
@ -58,17 +61,19 @@ export async function handleUserMessage(
|
||||
extensionPath?: string,
|
||||
mode?: RunMode,
|
||||
serviceTier?: ServiceTier, // 服务等级参数
|
||||
contextItems?: Array<{ id: number; type: string; path: string }> // 上下文项参数
|
||||
contextItems?: Array<{ id: number; type: string; path: string }>, // 上下文项参数
|
||||
) {
|
||||
console.log("收到用户消息:", text);
|
||||
|
||||
// 检查 token 是否过期
|
||||
// 【已禁用】检查 token 是否过期 - 无需登录
|
||||
const context = (panel as any).__context;
|
||||
if (context) {
|
||||
if (false && context) {
|
||||
// 从 session 中获取 token
|
||||
let token: string | undefined;
|
||||
try {
|
||||
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
|
||||
const session = await vscode.authentication.getSession("iccoder", [], {
|
||||
createIfNone: false,
|
||||
});
|
||||
token = session?.accessToken;
|
||||
} catch (error) {
|
||||
console.warn("[MessageHandler] 获取 session 失败:", error);
|
||||
@ -78,20 +83,20 @@ export async function handleUserMessage(
|
||||
console.warn("[MessageHandler] 未登录,阻止发送");
|
||||
|
||||
// 保存待发送的消息
|
||||
await context.globalState.update('pendingMessage', {
|
||||
await context.globalState.update("pendingMessage", {
|
||||
text,
|
||||
mode,
|
||||
serviceTier,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// 显示弹窗提示
|
||||
const action = await vscode.window.showWarningMessage(
|
||||
'请先登录后再发送消息',
|
||||
'立即登录'
|
||||
"请先登录后再发送消息",
|
||||
"立即登录",
|
||||
);
|
||||
|
||||
if (action === '立即登录') {
|
||||
if (action === "立即登录") {
|
||||
vscode.commands.executeCommand("ic-coder.login", {
|
||||
forceReauth: true,
|
||||
});
|
||||
@ -106,32 +111,34 @@ export async function handleUserMessage(
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTokenExpired(token)) {
|
||||
if (token && isTokenExpired(token as string)) {
|
||||
console.warn("[MessageHandler] Token 已过期,阻止发送");
|
||||
|
||||
// 保存待发送的消息
|
||||
await context.globalState.update('pendingMessage', {
|
||||
await context.globalState.update("pendingMessage", {
|
||||
text,
|
||||
mode,
|
||||
serviceTier,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// 清除过期的 session
|
||||
await context.globalState.update('icCoderSessions', []);
|
||||
await context.globalState.update('icCoderUserInfo', undefined);
|
||||
await context.globalState.update("icCoderSessions", []);
|
||||
await context.globalState.update("icCoderUserInfo", undefined);
|
||||
|
||||
// 显示弹窗提示
|
||||
/*
|
||||
const action = await vscode.window.showWarningMessage(
|
||||
'登录已过期,请重新登录',
|
||||
'立即登录'
|
||||
"登录已过期,请重新登录",
|
||||
"立即登录",
|
||||
);
|
||||
|
||||
if (action === '立即登录') {
|
||||
if (action === "立即登录") {
|
||||
vscode.commands.executeCommand("ic-coder.login", {
|
||||
forceReauth: true,
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
// 恢复输入状态
|
||||
panel.webview.postMessage({
|
||||
@ -183,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 {
|
||||
@ -216,14 +200,14 @@ export async function handleUserMessage(
|
||||
mode,
|
||||
undefined,
|
||||
serviceTier,
|
||||
contextItems
|
||||
contextItems,
|
||||
);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error("后端服务不可用:", error);
|
||||
console.error("处理用户消息失败:", error);
|
||||
panel.webview.postMessage({
|
||||
command: "updateStatus",
|
||||
text: "后端服务不可用",
|
||||
text: "处理用户消息失败,请稍后重试",
|
||||
type: "error",
|
||||
});
|
||||
// 恢复输入状态
|
||||
@ -254,7 +238,7 @@ async function handleUserMessageWithBackend(
|
||||
mode?: RunMode,
|
||||
reuseTaskId?: string, // 可选,复用现有 taskId(用于 Plan 模式确认后继续执行)
|
||||
serviceTier?: ServiceTier, // 服务等级参数
|
||||
contextItems?: Array<{ id: number; type: string; path: string }> // 上下文项参数
|
||||
contextItems?: Array<{ id: number; type: string; path: string }>, // 上下文项参数
|
||||
): Promise<void> {
|
||||
const historyManager = ChatHistoryManager.getInstance();
|
||||
|
||||
@ -262,7 +246,7 @@ async function handleUserMessageWithBackend(
|
||||
let enhancedText = text;
|
||||
if (contextItems && contextItems.length > 0) {
|
||||
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}`;
|
||||
}
|
||||
|
||||
@ -273,15 +257,17 @@ async function handleUserMessageWithBackend(
|
||||
// 创建会话(dialogManager 会自动处理旧会话的中止)
|
||||
currentSession = dialogManager.createSession(
|
||||
extensionPath,
|
||||
taskIdToUse || undefined
|
||||
taskIdToUse || undefined,
|
||||
);
|
||||
// 保存 taskId 用于后续操作(如压缩)
|
||||
lastTaskId = currentSession.getTaskId();
|
||||
// 重置离线模式仿真标志(新会话开始)
|
||||
offlineSimulationTriggered = false;
|
||||
console.log(
|
||||
"[MessageHandler] 创建会话: taskId=",
|
||||
lastTaskId,
|
||||
"来源=",
|
||||
taskIdToUse ? "historyManager" : "新生成"
|
||||
taskIdToUse ? "historyManager" : "新生成",
|
||||
);
|
||||
|
||||
// 显示状态栏
|
||||
@ -300,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) => {
|
||||
@ -324,7 +357,10 @@ async function handleUserMessageWithBackend(
|
||||
// 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新
|
||||
},
|
||||
|
||||
onQuestion: (askId: string, questions: import("../types/api").QuestionItem[]) => {
|
||||
onQuestion: (
|
||||
askId: string,
|
||||
questions: import("../types/api").QuestionItem[],
|
||||
) => {
|
||||
// 只更新状态栏,问题显示由 onSegmentUpdate 统一处理
|
||||
panel.webview.postMessage({
|
||||
command: "updateStatus",
|
||||
@ -351,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 {
|
||||
// 隐藏状态栏
|
||||
@ -376,21 +401,29 @@ async function handleUserMessageWithBackend(
|
||||
isComplete: true,
|
||||
});
|
||||
|
||||
// 发送任务完成消息
|
||||
panel.webview.postMessage({
|
||||
command: "taskComplete",
|
||||
});
|
||||
|
||||
// 发送系统通知 - AI 响应完成
|
||||
const notificationService = NotificationService.getInstance();
|
||||
notificationService.success(
|
||||
'IC Coder - AI 响应完成',
|
||||
'您的问题已得到回复,点击查看详情',
|
||||
"IC Coder - AI 响应完成",
|
||||
"您的问题已得到回复,点击查看详情",
|
||||
() => {
|
||||
// 点击通知时聚焦到面板
|
||||
panel.reveal();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 发送代码变更到前端
|
||||
sendChangesToWebview(panel);
|
||||
} catch (error) {
|
||||
console.warn("[MessageHandler] 更新面板失败(面板可能已关闭):", error);
|
||||
console.warn(
|
||||
"[MessageHandler] 更新面板失败(面板可能已关闭):",
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
resolve();
|
||||
@ -402,7 +435,7 @@ async function handleUserMessageWithBackend(
|
||||
});
|
||||
panel.webview.postMessage({
|
||||
command: "receiveMessage",
|
||||
text: `❌ 错误: ${message}`,
|
||||
text: `错误: ${message}`,
|
||||
});
|
||||
// 恢复输入状态
|
||||
panel.webview.postMessage({
|
||||
@ -458,7 +491,7 @@ async function handleUserMessageWithBackend(
|
||||
},
|
||||
},
|
||||
mode,
|
||||
serviceTier // 传递服务等级
|
||||
serviceTier, // 传递服务等级
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -470,7 +503,7 @@ export async function handleUserAnswer(
|
||||
askId: string,
|
||||
selected?: string[],
|
||||
customInput?: string,
|
||||
answers?: { [questionIndex: string]: string[] }
|
||||
answers?: { [questionIndex: string]: string[] },
|
||||
): Promise<void> {
|
||||
if (currentSession) {
|
||||
await currentSession.submitAnswer(askId, selected, customInput, answers);
|
||||
@ -540,7 +573,7 @@ export async function handlePlanAction(
|
||||
action: string,
|
||||
planTitle: string,
|
||||
extensionPath: string,
|
||||
serviceTier?: ServiceTier
|
||||
serviceTier?: ServiceTier,
|
||||
): Promise<void> {
|
||||
console.log(
|
||||
"[handlePlanAction] action:",
|
||||
@ -548,7 +581,7 @@ export async function handlePlanAction(
|
||||
"planTitle:",
|
||||
planTitle,
|
||||
"serviceTier:",
|
||||
serviceTier
|
||||
serviceTier,
|
||||
);
|
||||
|
||||
switch (action) {
|
||||
@ -564,7 +597,7 @@ export async function handlePlanAction(
|
||||
`请按照刚才的计划执行:${planTitle}`,
|
||||
extensionPath,
|
||||
"agent",
|
||||
serviceTier
|
||||
serviceTier,
|
||||
);
|
||||
break;
|
||||
|
||||
@ -581,7 +614,7 @@ export async function handlePlanAction(
|
||||
`请根据以下建议修改计划:${modification}`,
|
||||
extensionPath,
|
||||
"plan",
|
||||
serviceTier
|
||||
serviceTier,
|
||||
);
|
||||
}
|
||||
break;
|
||||
@ -636,7 +669,7 @@ function parseFileOperation(text: string): {
|
||||
|
||||
// 匹配重命名文件:将 xxx.ts 重命名为 yyy.ts 或 把 xxx.ts 改名为 yyy.ts(优先匹配,避免被修改匹配)
|
||||
const renameMatch = lowerText.match(
|
||||
/(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/
|
||||
/(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/,
|
||||
);
|
||||
if (renameMatch) {
|
||||
const oldPath = renameMatch[1].trim();
|
||||
@ -653,7 +686,7 @@ function parseFileOperation(text: string): {
|
||||
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
|
||||
// 格式3: 将 xxx.ts 文件 'aaa' 替换为 'bbb'
|
||||
const replaceMatch1 = lowerText.match(
|
||||
/在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/
|
||||
/在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/,
|
||||
);
|
||||
if (replaceMatch1) {
|
||||
const filePath = replaceMatch1[1].trim();
|
||||
@ -669,7 +702,7 @@ function parseFileOperation(text: string): {
|
||||
|
||||
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
|
||||
const replaceMatch2 = lowerText.match(
|
||||
/(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/
|
||||
/(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/,
|
||||
);
|
||||
if (replaceMatch2) {
|
||||
const filePath = replaceMatch2[1].trim();
|
||||
@ -718,7 +751,7 @@ async function handleFileOperation(
|
||||
newPath?: string;
|
||||
searchText?: string;
|
||||
replaceText?: string;
|
||||
}
|
||||
},
|
||||
) {
|
||||
const historyManager = ChatHistoryManager.getInstance();
|
||||
|
||||
@ -734,7 +767,7 @@ async function handleFileOperation(
|
||||
text: responseText,
|
||||
});
|
||||
vscode.window.showInformationMessage(
|
||||
`文件创建成功: ${operation.filePath}`
|
||||
`文件创建成功: ${operation.filePath}`,
|
||||
);
|
||||
await historyManager.addAiMessage(responseText);
|
||||
break;
|
||||
@ -747,7 +780,7 @@ async function handleFileOperation(
|
||||
text: responseText,
|
||||
});
|
||||
vscode.window.showInformationMessage(
|
||||
`文件删除成功: ${operation.filePath}`
|
||||
`文件删除成功: ${operation.filePath}`,
|
||||
);
|
||||
await historyManager.addAiMessage(responseText);
|
||||
break;
|
||||
@ -783,7 +816,7 @@ async function handleFileOperation(
|
||||
text: responseText,
|
||||
});
|
||||
vscode.window.showInformationMessage(
|
||||
`文件重命名成功: ${operation.filePath} → ${operation.newPath}`
|
||||
`文件重命名成功: ${operation.filePath} → ${operation.newPath}`,
|
||||
);
|
||||
await historyManager.addAiMessage(responseText);
|
||||
break;
|
||||
@ -792,21 +825,29 @@ async function handleFileOperation(
|
||||
if (!operation.searchText || !operation.replaceText) {
|
||||
throw new Error("缺少替换内容");
|
||||
}
|
||||
const oldContentBeforeReplace = await readFileContent(operation.filePath);
|
||||
const oldContentBeforeReplace = await readFileContent(
|
||||
operation.filePath,
|
||||
);
|
||||
await replaceFile(
|
||||
operation.filePath,
|
||||
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}`;
|
||||
panel.webview.postMessage({
|
||||
command: "receiveMessage",
|
||||
text: responseText,
|
||||
});
|
||||
vscode.window.showInformationMessage(
|
||||
`文件内容替换成功: ${operation.filePath}`
|
||||
`文件内容替换成功: ${operation.filePath}`,
|
||||
);
|
||||
await historyManager.addAiMessage(responseText);
|
||||
break;
|
||||
@ -866,7 +907,7 @@ function getDefaultContent(filePath: string): string {
|
||||
*/
|
||||
export async function handleReadFile(
|
||||
panel: vscode.WebviewPanel,
|
||||
filePath: string
|
||||
filePath: string,
|
||||
) {
|
||||
try {
|
||||
const content = await readFileContent(filePath);
|
||||
@ -890,7 +931,7 @@ export async function handleCreateFile(
|
||||
panel: vscode.WebviewPanel,
|
||||
filePath: string,
|
||||
content: string,
|
||||
overwrite: boolean = false //是否覆盖
|
||||
overwrite: boolean = false, //是否覆盖
|
||||
) {
|
||||
try {
|
||||
if (overwrite) {
|
||||
@ -909,11 +950,14 @@ export async function handleCreateFile(
|
||||
// 发送系统通知
|
||||
const notificationService = NotificationService.getInstance();
|
||||
notificationService.success(
|
||||
'IC Coder - 文件创建',
|
||||
"IC Coder - 文件创建",
|
||||
`文件已创建: ${path.basename(filePath)}`,
|
||||
() => {
|
||||
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath));
|
||||
}
|
||||
vscode.commands.executeCommand(
|
||||
"vscode.open",
|
||||
vscode.Uri.file(filePath),
|
||||
);
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
panel.webview.postMessage({
|
||||
@ -921,7 +965,7 @@ export async function handleCreateFile(
|
||||
error: error instanceof Error ? error.message : "创建文件失败",
|
||||
});
|
||||
vscode.window.showErrorMessage(
|
||||
`创建文件失败: ${error instanceof Error ? error.message : "未知错误"}`
|
||||
`创建文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -932,7 +976,7 @@ export async function handleCreateFile(
|
||||
export async function handleUpdateFile(
|
||||
panel: vscode.WebviewPanel,
|
||||
filePath: string,
|
||||
content: string
|
||||
content: string,
|
||||
) {
|
||||
try {
|
||||
const oldContent = await readFileContent(filePath);
|
||||
@ -948,8 +992,8 @@ export async function handleUpdateFile(
|
||||
// 发送系统通知
|
||||
const notificationService = NotificationService.getInstance();
|
||||
notificationService.info(
|
||||
'IC Coder - 文件更新',
|
||||
`文件已更新: ${path.basename(filePath)}`
|
||||
"IC Coder - 文件更新",
|
||||
`文件已更新: ${path.basename(filePath)}`,
|
||||
);
|
||||
} catch (error) {
|
||||
panel.webview.postMessage({
|
||||
@ -957,7 +1001,7 @@ export async function handleUpdateFile(
|
||||
error: error instanceof Error ? error.message : "更新文件失败",
|
||||
});
|
||||
vscode.window.showErrorMessage(
|
||||
`更新文件失败: ${error instanceof Error ? error.message : "未知错误"}`
|
||||
`更新文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -968,7 +1012,7 @@ export async function handleUpdateFile(
|
||||
export async function handleRenameFile(
|
||||
panel: vscode.WebviewPanel,
|
||||
oldPath: string,
|
||||
newPath: string
|
||||
newPath: string,
|
||||
) {
|
||||
try {
|
||||
await renameFile(oldPath, newPath);
|
||||
@ -979,7 +1023,7 @@ export async function handleRenameFile(
|
||||
message: "文件重命名成功",
|
||||
});
|
||||
vscode.window.showInformationMessage(
|
||||
`文件重命名成功: ${oldPath} → ${newPath}`
|
||||
`文件重命名成功: ${oldPath} → ${newPath}`,
|
||||
);
|
||||
} catch (error) {
|
||||
panel.webview.postMessage({
|
||||
@ -987,7 +1031,7 @@ export async function handleRenameFile(
|
||||
error: error instanceof Error ? error.message : "重命名文件失败",
|
||||
});
|
||||
vscode.window.showErrorMessage(
|
||||
`重命名文件失败: ${error instanceof Error ? error.message : "未知错误"}`
|
||||
`重命名文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -999,7 +1043,7 @@ export async function handleReplaceInFile(
|
||||
panel: vscode.WebviewPanel,
|
||||
filePath: string,
|
||||
searchText: string,
|
||||
replaceText: string
|
||||
replaceText: string,
|
||||
) {
|
||||
try {
|
||||
const oldContent = await readFileContent(filePath);
|
||||
@ -1018,7 +1062,7 @@ export async function handleReplaceInFile(
|
||||
error: error instanceof Error ? error.message : "替换文件内容失败",
|
||||
});
|
||||
vscode.window.showErrorMessage(
|
||||
`替换文件内容失败: ${error instanceof Error ? error.message : "未知错误"}`
|
||||
`替换文件内容失败: ${error instanceof Error ? error.message : "未知错误"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1063,7 +1107,7 @@ function isVCDGenerationCommand(text: string): boolean {
|
||||
*/
|
||||
async function handleVCDGeneration(
|
||||
panel: vscode.WebviewPanel,
|
||||
extensionPath: string
|
||||
extensionPath: string,
|
||||
) {
|
||||
try {
|
||||
// 获取当前工作区路径
|
||||
@ -1090,7 +1134,7 @@ async function handleVCDGeneration(
|
||||
if (!iverilogCheck.available) {
|
||||
panel.webview.postMessage({
|
||||
command: "receiveMessage",
|
||||
text: `❌ ${iverilogCheck.message}\n\n请参考插件文档安装 iverilog 工具。`,
|
||||
text: `❌ ${iverilogCheck.message}。`,
|
||||
});
|
||||
vscode.window.showErrorMessage(iverilogCheck.message);
|
||||
return;
|
||||
@ -1165,12 +1209,15 @@ async function handleVCDGeneration(
|
||||
// 发送系统通知
|
||||
const notificationService = NotificationService.getInstance();
|
||||
notificationService.success(
|
||||
'IC Coder - 仿真完成',
|
||||
"IC Coder - 仿真完成",
|
||||
`VCD 文件已生成: ${fileName}`,
|
||||
() => {
|
||||
// 点击通知时打开 VCD 查看器
|
||||
vscode.commands.executeCommand('ic-coder.openVCDViewer', result.vcdFilePath);
|
||||
}
|
||||
vscode.commands.executeCommand(
|
||||
"ic-coder.openVCDViewer",
|
||||
result.vcdFilePath,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
panel.webview.postMessage({
|
||||
@ -1199,12 +1246,12 @@ async function handleVCDGeneration(
|
||||
// 发送系统通知
|
||||
const notificationService = NotificationService.getInstance();
|
||||
notificationService.error(
|
||||
'IC Coder - 仿真失败',
|
||||
'VCD 文件生成失败,请查看错误信息',
|
||||
"IC Coder - 仿真失败",
|
||||
"VCD 文件生成失败,请查看错误信息",
|
||||
() => {
|
||||
// 点击通知时聚焦到面板
|
||||
panel.reveal();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@ -1222,11 +1269,11 @@ async function handleVCDGeneration(
|
||||
// 发送系统通知
|
||||
const notificationService = NotificationService.getInstance();
|
||||
notificationService.error(
|
||||
'IC Coder - 仿真错误',
|
||||
error instanceof Error ? error.message : '生成 VCD 文件时出错',
|
||||
"IC Coder - 仿真错误",
|
||||
error instanceof Error ? error.message : "生成 VCD 文件时出错",
|
||||
() => {
|
||||
panel.reveal();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1236,7 +1283,7 @@ async function handleVCDGeneration(
|
||||
*/
|
||||
export async function handleOptimizePrompt(
|
||||
panel: vscode.WebviewPanel,
|
||||
prompt: string
|
||||
prompt: string,
|
||||
): Promise<void> {
|
||||
console.log("[MessageHandler] ========== 收到提示词优化请求 ==========");
|
||||
console.log("[MessageHandler] prompt:", prompt);
|
||||
@ -1268,7 +1315,7 @@ export async function handleOptimizePrompt(
|
||||
*/
|
||||
export async function handleAcceptChange(
|
||||
panel: vscode.WebviewPanel,
|
||||
changeId: string
|
||||
changeId: string,
|
||||
) {
|
||||
try {
|
||||
const success = await changeTracker.acceptChange(changeId);
|
||||
@ -1276,14 +1323,14 @@ export async function handleAcceptChange(
|
||||
panel.webview.postMessage({
|
||||
command: "changeAccepted",
|
||||
changeId: changeId,
|
||||
success: true
|
||||
success: true,
|
||||
});
|
||||
} else {
|
||||
panel.webview.postMessage({
|
||||
command: "changeAccepted",
|
||||
changeId: changeId,
|
||||
success: false,
|
||||
error: "采纳变更失败"
|
||||
error: "采纳变更失败",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@ -1292,7 +1339,7 @@ export async function handleAcceptChange(
|
||||
command: "changeAccepted",
|
||||
changeId: changeId,
|
||||
success: false,
|
||||
error: String(error)
|
||||
error: String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1302,7 +1349,7 @@ export async function handleAcceptChange(
|
||||
*/
|
||||
export async function handleRejectChange(
|
||||
panel: vscode.WebviewPanel,
|
||||
changeId: string
|
||||
changeId: string,
|
||||
) {
|
||||
try {
|
||||
const success = await changeTracker.rejectChange(changeId);
|
||||
@ -1310,14 +1357,14 @@ export async function handleRejectChange(
|
||||
panel.webview.postMessage({
|
||||
command: "changeRejected",
|
||||
changeId: changeId,
|
||||
success: true
|
||||
success: true,
|
||||
});
|
||||
} else {
|
||||
panel.webview.postMessage({
|
||||
command: "changeRejected",
|
||||
changeId: changeId,
|
||||
success: false,
|
||||
error: "拒绝变更失败"
|
||||
error: "拒绝变更失败",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@ -1326,7 +1373,7 @@ export async function handleRejectChange(
|
||||
command: "changeRejected",
|
||||
changeId: changeId,
|
||||
success: false,
|
||||
error: String(error)
|
||||
error: String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1337,18 +1384,18 @@ export async function handleRejectChange(
|
||||
export function sendChangesToWebview(panel: vscode.WebviewPanel) {
|
||||
const session = changeTracker.endSession();
|
||||
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 diffHtml = renderDiffHtml(diffLines);
|
||||
return {
|
||||
...change,
|
||||
diffHtml
|
||||
diffHtml,
|
||||
};
|
||||
});
|
||||
|
||||
panel.webview.postMessage({
|
||||
command: "showChanges",
|
||||
changes: changesWithDiff
|
||||
changes: changesWithDiff,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1365,62 +1412,67 @@ export function startChangeSession(sessionId: string) {
|
||||
*/
|
||||
export async function handleOpenFileDiff(
|
||||
panel: vscode.WebviewPanel,
|
||||
changeId: string
|
||||
changeId: string,
|
||||
) {
|
||||
try {
|
||||
const session = changeTracker.getCurrentSession();
|
||||
if (!session) {
|
||||
vscode.window.showErrorMessage('没有找到变更会话');
|
||||
vscode.window.showErrorMessage("没有找到变更会话");
|
||||
return;
|
||||
}
|
||||
|
||||
const change = session.changes.find(c => c.changeId === changeId);
|
||||
const change = session.changes.find((c) => c.changeId === changeId);
|
||||
if (!change) {
|
||||
vscode.window.showErrorMessage('没有找到该变更');
|
||||
vscode.window.showErrorMessage("没有找到该变更");
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
if (!workspaceFolder) {
|
||||
vscode.window.showErrorMessage('没有打开的工作区');
|
||||
vscode.window.showErrorMessage("没有打开的工作区");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建临时文件用于对比
|
||||
const filePath = change.filePath;
|
||||
const absolutePath = vscode.Uri.file(
|
||||
path.join(workspaceFolder.uri.fsPath, filePath)
|
||||
path.join(workspaceFolder.uri.fsPath, filePath),
|
||||
);
|
||||
|
||||
// 创建虚拟文档显示旧内容
|
||||
const oldUri = vscode.Uri.parse(
|
||||
`ic-coder-diff:${filePath}.old?${changeId}`
|
||||
).with({ scheme: 'ic-coder-diff' });
|
||||
`ic-coder-diff:${filePath}.old?${changeId}`,
|
||||
).with({ scheme: "ic-coder-diff" });
|
||||
|
||||
// 注册文档内容提供者(如果还没注册)
|
||||
if (!(global as any).__diffProviderRegistered) {
|
||||
const provider = new (class implements vscode.TextDocumentContentProvider {
|
||||
const provider = new (class
|
||||
implements vscode.TextDocumentContentProvider
|
||||
{
|
||||
provideTextDocumentContent(uri: vscode.Uri): string {
|
||||
const changeId = uri.query;
|
||||
const session = changeTracker.getCurrentSession();
|
||||
const change = session?.changes.find(c => c.changeId === changeId);
|
||||
return change?.oldContent || '';
|
||||
const change = session?.changes.find((c) => c.changeId === changeId);
|
||||
return change?.oldContent || "";
|
||||
}
|
||||
})();
|
||||
|
||||
vscode.workspace.registerTextDocumentContentProvider('ic-coder-diff', provider);
|
||||
vscode.workspace.registerTextDocumentContentProvider(
|
||||
"ic-coder-diff",
|
||||
provider,
|
||||
);
|
||||
(global as any).__diffProviderRegistered = true;
|
||||
}
|
||||
|
||||
// 打开 diff 编辑器
|
||||
await vscode.commands.executeCommand(
|
||||
'vscode.diff',
|
||||
"vscode.diff",
|
||||
oldUri,
|
||||
absolutePath,
|
||||
`${filePath} (变更对比)`
|
||||
`${filePath} (变更对比)`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('[MessageHandler] 打开 diff 失败:', error);
|
||||
console.error("[MessageHandler] 打开 diff 失败:", error);
|
||||
vscode.window.showErrorMessage(`打开 diff 失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*/
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -51,7 +51,11 @@ export function getContextDisplayStyles(): string {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.context-item:hover {
|
||||
.context-item.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.context-item.clickable:hover {
|
||||
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>';
|
||||
}
|
||||
|
||||
// 获取代码图标 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
|
||||
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>';
|
||||
@ -172,13 +181,17 @@ export function getContextDisplayScript(): string {
|
||||
case 'folder': icon = getFolderIcon(); break;
|
||||
case 'image': icon = getImageIcon(); 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 \`
|
||||
<div class="context-item" title="\${item.path}">
|
||||
<div class="context-item \${clickable}" title="\${item.path || item.displayPath}" \${onclick}>
|
||||
\${icon}
|
||||
<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()}
|
||||
</span>
|
||||
</div>
|
||||
@ -186,6 +199,27 @@ export function getContextDisplayScript(): string {
|
||||
}).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 => {
|
||||
const message = event.data;
|
||||
@ -211,6 +245,18 @@ export function getContextDisplayScript(): string {
|
||||
message.documents.forEach(doc => addContextItem('document', doc));
|
||||
}
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -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) {
|
||||
// 先检查邀请码验证状态
|
||||
|
||||
@ -46,11 +46,22 @@ export function getFilePathTagScript(): string {
|
||||
return `
|
||||
// 处理文件路径标签点击
|
||||
function handleFilePathClick(filePath) {
|
||||
// 解析文件路径,支持 file.v:5-8 格式
|
||||
const match = filePath.match(/^(.+?):(\\d+)-(\\d+)$/);
|
||||
if (match) {
|
||||
vscode.postMessage({
|
||||
command: 'openFile',
|
||||
command: 'openFilePathTag',
|
||||
filePath: match[1],
|
||||
startLine: parseInt(match[2]),
|
||||
endLine: parseInt(match[3])
|
||||
});
|
||||
} else {
|
||||
vscode.postMessage({
|
||||
command: 'openFilePathTag',
|
||||
filePath: filePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 创建文件路径标签
|
||||
window.createFilePathTag = function(filePath) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -432,15 +432,14 @@ export function getInputAreaScript(): string {
|
||||
// 获取上下文项
|
||||
const contextItems = window.getContextItems ? window.getContextItems() : [];
|
||||
|
||||
// 构建显示消息:如果有上下文文件,添加文件路径前缀
|
||||
// 构建显示消息:如果有上下文项,添加路径前缀
|
||||
let displayText = text;
|
||||
if (contextItems.length > 0) {
|
||||
const filePaths = contextItems
|
||||
.filter(item => item.type === 'file')
|
||||
const contextPaths = contextItems
|
||||
.map(item => item.displayPath || item.path)
|
||||
.join(' ');
|
||||
if (filePaths) {
|
||||
displayText = filePaths + ' ' + text;
|
||||
if (contextPaths) {
|
||||
displayText = contextPaths + ' ' + text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
@ -863,8 +863,8 @@ export function getMessageAreaScript(): string {
|
||||
const textParts = [];
|
||||
|
||||
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);
|
||||
} else {
|
||||
textParts.push(part);
|
||||
@ -1211,7 +1211,7 @@ export function getMessageAreaScript(): string {
|
||||
|
||||
const optionsHtml = q.options.map(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:5px 5px 5px 0;">
|
||||
<input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}>
|
||||
<span>\${opt}</span>
|
||||
</label>\`;
|
||||
@ -1729,9 +1729,18 @@ export function getMessageAreaScript(): string {
|
||||
// 标记问题已回答
|
||||
segmentDiv.classList.add('answered');
|
||||
|
||||
// 禁用所有输入
|
||||
// 禁用所有输入并保持选中状态的高亮
|
||||
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');
|
||||
|
||||
@ -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';
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -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()}
|
||||
|
||||
@ -10,24 +10,23 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
/** @type WebpackConfig */
|
||||
const extensionConfig = {
|
||||
target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
|
||||
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
|
||||
target: 'node',
|
||||
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: {
|
||||
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'extension.js',
|
||||
libraryTarget: 'commonjs2'
|
||||
libraryTarget: 'commonjs2',
|
||||
clean: true // 自动清理旧文件
|
||||
},
|
||||
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/
|
||||
'node-notifier': 'commonjs node-notifier' // node-notifier 依赖原生模块,必须排除
|
||||
// modules added here also need to be added in the .vscodeignore file
|
||||
vscode: 'commonjs vscode',
|
||||
'node-notifier': 'commonjs node-notifier'
|
||||
},
|
||||
resolve: {
|
||||
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
|
||||
extensions: ['.ts', '.js']
|
||||
extensions: ['.ts', '.js'],
|
||||
mainFields: ['module', 'main']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
@ -36,15 +35,21 @@ const extensionConfig = {
|
||||
exclude: /node_modules/,
|
||||
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: {
|
||||
level: "log", // enables logging required for problem matchers
|
||||
level: "log",
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
@ -52,6 +57,15 @@ const extensionConfig = {
|
||||
{ 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 ];
|
||||