10 Commits

14 changed files with 650 additions and 193 deletions

View File

@ -268,6 +268,44 @@ pnpm vsce publish 0.0.3
4. 执行发布命令
5. 验证市场上的插件是否正常
## 更新流程
1. 修改版本号
手动修改 修改package.json文件
命令修改
```bash
#补丁版本 1.0.0 -> 1.0.1)
pnpm version patch
#次要版本 (1.0.0 -> 1.1.0)
pnpm version minor
#主要版本 (1.0.0 -> 2.0.0)
pnpm version major
```
2. 打包
```bash
#先build
pnpm run build
#后打包成.vsix
pnpm vsce package --no-dependencies
```
3. 手动上传/命令上传
- https://marketplace.visualstudio.com/ 在这个里面手动上传 更新就选择update
- 命令上传vsce publish
---
## 常见问题

View File

@ -1,40 +1,83 @@
## 什么是 IC Coder
## 什么是IC Coder
**IC Coder** 是一款 **专注于真实 FPGA 研发的 Verilog 智能体编程平台**。我们立志于用 AI 重塑 FPGA 研发效率,让 FPGA 开发者们,都能享受到 AI 发展所带来的科技福利!目标成为全球最好用的 **LLM 生成 Verilog**的平台!
IC Coder是一款**The Agentic AI Verilog Coding Platform自主式人工智能 Verilog 编码平台**。我们立志于用AI重塑芯片开发者的效率将芯片设计与验证的效率提升至少20倍让芯片开发者们都能享受到AI发展所带来的科技福利目标成为全球最好用的"LLM生成Verilog"的平台!
从 WEB 端到插件端IC Coder 智能体架构完成了**全新升级**,采用当前主流的**层级架构**设计,这种高内聚、低耦合的架构特性,不仅支持更多功能扩展,更预留了充足的迭代空间。当前,插件端拥有了调用本地工具的能力,不再是单纯代码生成的智能体,升级为拥有**语法校验、波形逻辑检查**等工具的**全流程 Verilog 编程智能体平台**,给用户带来更沉浸的**Vibe Verilog Coding**体验。
![iccoder](https://s41.ax1x.com/2026/01/27/pZR7ScF.png)
## 输入需求 对话补充需求
### 核心技术架构
**无需**输入完整需求,放心交给智能体补充完善
**我们采用全球顶尖的大语言模型**加上自研的针对芯片设计领域深度优化的微调模型为代码生成提供强大的AI能力支撑
## Plan 模型下确认设计文档
**核心技术栈**包括:
**确定**好用户需求以及相关参数后,整理并输出一份 FPGA 开发**设计文档**。Plan 模式下用户可以**进一步**与 IC Coder 沟通需求,或**直接修改**设计文档。
- **多智能体架构Multi-Agent System**多个专业化AI智能体协同工作分别负责架构设计、代码生成、验证测试等不同环节
- **增强上下文引擎**:智能理解和管理大规模设计上下文,确保生成代码的一致性和准确性
- **自研EDA工具集**完整的仿真、综合、时序分析工具链无缝集成到AI工作流中
这些技术共同支撑着从需求分析、架构设计、代码生成到验证调试的全流程智能化开发体验。
![流程图](https://s41.ax1x.com/2026/01/27/pZR7CnJ.png)
## 自动搭建电路架构
根据需求自动搭建电路架构,并将电路信号关系结构化
IC Coder能够根据自然语言描述的设计需求自动生成完整的电路架构。系统会:
## 自动仿真
- **智能解析需求**:理解功能规格、性能指标、接口要求等设计约束
- **自动模块划分**:根据功能将设计合理拆分为多个子模块,确保模块化和可复用性
- **生成层次结构**:建立清晰的模块层次关系,自动处理模块间的信号连接
- **结构化信号管理**:将所有电路信号关系进行结构化表示,包括数据流向、控制逻辑、时序关系等
- **可视化展示**:以图形化方式展示整体架构,便于理解和审查设计方案
自主搭建 Testbench 仿真平台,自动运行仿真生成波形
![自动搭建电路架构](https://s41.ax1x.com/2026/01/27/pZRII4f.png)
## 实时跟随
## AI自主仿真
实时展示全流程执行细节,与智能体协同随时反馈,让 AI 开发更清晰、高效
IC Coder提供完全自动化的仿真验证流程无需手动编写测试代码
## VCD 波形解析
- **智能Testbench生成**:根据设计模块自动生成完整的测试平台,包括激励生成、时钟复位、接口驱动等
- **测试用例自动化**:根据设计规格自动生成覆盖各种场景的测试用例,包括正常功能、边界条件、异常情况等
- **一键运行仿真**:自动调用集成仿真器执行仿真
- **波形自动生成**仿真完成后自动生成VCD、波形文件便于后续分析
- **实时进度反馈**:仿真过程中实时显示执行状态和日志信息
自动解析 VCD 波形文件,自动根据需求,检查是否存在逻辑错误
![自动仿真](https://s41.ax1x.com/2026/01/27/pZRI5UP.png)
## 自主代码迭代
## AI自主代码迭代
根据波形解析结果,自动对代码进行优化,然后重新仿真并解析波形,如此迭代,直到仿真无误
IC Coder实现了真正的自主式开发循环能够持续优化代码直到满足设计要求
- **智能问题诊断**:根据波形分析结果,自动定位代码中的问题根源
- **自动代码修复**:针对发现的问题自动生成修复方案并更新代码
- **迭代验证循环**:修复后自动重新运行仿真和波形分析,验证问题是否解决
- **持续优化**:如果仍存在问题,继续分析和修复,形成闭环迭代
- **收敛保证**:智能判断迭代进展,避免无效循环,确保最终收敛到正确设计
- **全程可追溯**:记录每次迭代的修改内容和验证结果,便于回溯和审查
这种自主迭代能力大幅减少了人工调试时间,让设计验证过程更加高效可靠。
## 随时可掌控
IC Coder提供透明化的开发过程让用户始终掌握AI的工作状态
- **实时流程展示**:可视化展示当前执行到哪个阶段(需求分析、架构设计、代码生成、仿真验证等)
- **详细执行日志**记录每一步操作的详细信息包括AI的思考过程、决策依据、执行结果
- **人机协同交互**:在关键决策点支持用户介入,可随时提供反馈、调整方向或修改参数
- **进度实时追踪**:显示任务完成进度、预计剩余步骤,让开发过程更加可预期
- **智能建议系统**AI主动提供优化建议和替代方案用户可选择采纳或自定义
- **即时响应机制**支持随时暂停、恢复或调整AI的工作流程
这种透明可控的设计理念让AI开发不再是"黑盒",而是真正的智能协作伙伴。
## 多层次安全保障
默认本地存储与云端即时加密保障隐私,真正做到了代码全链路加密传输、云端零存储
IC Coder将数据安全和隐私保护作为核心设计原则提供企业级的安全保障
- **本地优先存储**:所有设计文件默认存储在本地,用户完全掌控自己的代码资产
- **全链路加密传输**与云端通信采用TLS/SSL加密确保数据传输过程中不被窃取或篡改
- **云端零存储策略**:云端服务器不保存用户的源代码,仅处理加密后的临时数据,处理完成后立即销毁
- **定制化部署选项**:支持企业私有云或本地部署,满足高安全等级需求
真正做到了代码全链路加密传输、云端零存储让芯片设计企业可以放心使用AI工具。
## 反馈

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 252 KiB

View File

@ -2,7 +2,7 @@
"name": "iccoder",
"displayName": "IC Coder: Agentic Verilog Platform",
"description": "Agentic Verilog Coding Platform for Real-World FPGAs",
"version": "1.0.2",
"version": "1.0.3",
"publisher": "ICCoderAgenticVerilogPlatform",
"engines": {
"vscode": "^1.80.0"

View File

@ -162,6 +162,11 @@ export async function showICHelperPanel(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "QRCode", "wx.png")
);
// 获取Logo URI
const logoUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "media", "homepage-logo.png")
);
// 设置HTML内容
panel.webview.html = getWebviewContent(
iconUri.toString(),
@ -169,7 +174,8 @@ export async function showICHelperPanel(
liteIconUri.toString(),
syIconUri.toString(),
maxIconUri.toString(),
qrCodeUri.toString()
qrCodeUri.toString(),
logoUri.toString()
);
// 获取并发送用户信息到 webview

View File

@ -2,18 +2,28 @@
* API 客户端
* 封装与后端的 HTTP 通信
*/
import * as vscode from 'vscode';
import * as https from 'https';
import * as http from 'http';
import { URL } from 'url';
import { getApiUrl, getConfig } from '../config/settings';
import type { ToolCallResult, AnswerRequest, ToolResultResponse, AnswerResponse, ToolConfirmResponse, UserInfoResponse, InvitationVerifyRequest, InvitationVerifyResponse, InvitationStatusResponse } from '../types/api';
import * as vscode from "vscode";
import * as https from "https";
import * as http from "http";
import { URL } from "url";
import { getApiUrl, getConfig } from "../config/settings";
import type {
ToolCallResult,
AnswerRequest,
ToolResultResponse,
AnswerResponse,
ToolConfirmResponse,
UserInfoResponse,
InvitationVerifyRequest,
InvitationVerifyResponse,
InvitationStatusResponse,
} from "../types/api";
/**
* HTTP 请求选项
*/
interface RequestOptions {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
method: "GET" | "POST" | "PUT" | "DELETE";
headers?: Record<string, string>;
body?: unknown;
timeout?: number;
@ -24,7 +34,9 @@ interface RequestOptions {
*/
async function getAuthToken(): Promise<string | undefined> {
try {
const session = await vscode.authentication.getSession('iccoder', [], { silent: true });
const session = await vscode.authentication.getSession("iccoder", [], {
silent: true,
});
return session?.accessToken;
} catch {
return undefined;
@ -41,7 +53,7 @@ async function request<T>(path: string, options: RequestOptions): Promise<T> {
// 自动获取 Token
const token = await getAuthToken();
const isHttps = url.protocol === 'https:';
const isHttps = url.protocol === "https:";
const httpModule = isHttps ? https : http;
const requestOptions: http.RequestOptions = {
@ -50,49 +62,84 @@ async function request<T>(path: string, options: RequestOptions): Promise<T> {
path: url.pathname + url.search,
method: options.method,
headers: {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
...options.headers
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
},
timeout: options.timeout || timeout
timeout: options.timeout || timeout,
};
console.log("[HTTP] 请求详情:", {
url: url.toString(),
method: options.method,
headers: requestOptions.headers,
hasToken: !!token,
body: options.body,
});
return new Promise((resolve, reject) => {
const req = httpModule.request(requestOptions, (res) => {
let data = '';
let data = "";
res.on('data', (chunk) => {
// console.log('[HTTP] 响应状态码:', res.statusCode);
// console.log('[HTTP] 响应头:', res.headers);
res.on("data", (chunk) => {
data += chunk;
});
res.on('end', () => {
res.on("end", () => {
console.log("[HTTP] 响应体:", data);
try {
const json = JSON.parse(data);
// console.log('[HTTP] 解析后的响应:', JSON.stringify(json, null, 2));
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
console.log("[HTTP] 请求成功");
resolve(json as T);
} else {
reject(new Error(json.error || json.message || `HTTP ${res.statusCode}`));
console.error("[HTTP] 请求失败:", {
statusCode: res.statusCode,
error: json.error,
message: json.message,
msg: json.msg,
});
reject(
new Error(
json.error ||
json.message ||
json.msg ||
`HTTP ${res.statusCode}`,
),
);
}
} catch (e) {
// console.error('[HTTP] 解析响应失败:', e);
// console.error('[HTTP] 原始响应:', data);
reject(new Error(`解析响应失败: ${data}`));
}
});
});
req.on('error', (error) => {
req.on("error", (error) => {
// console.error('[HTTP] 请求错误:', error);
reject(error);
});
req.on('timeout', () => {
req.on("timeout", () => {
// console.error('[HTTP] 请求超时');
req.destroy();
reject(new Error('请求超时'));
reject(new Error("请求超时"));
});
if (options.body) {
req.write(JSON.stringify(options.body));
const bodyStr = JSON.stringify(options.body);
// console.log('[HTTP] 发送请求体:', bodyStr);
req.write(bodyStr);
}
req.end();
// console.log('[HTTP] 请求已发送');
});
}
@ -100,11 +147,13 @@ async function request<T>(path: string, options: RequestOptions): Promise<T> {
* 提交工具执行结果
* POST /api/tool/result
*/
export async function submitToolResult(result: ToolCallResult): Promise<ToolResultResponse> {
export async function submitToolResult(
result: ToolCallResult,
): Promise<ToolResultResponse> {
console.log(`[API] 提交工具结果: callId=${result.id}`);
return request<ToolResultResponse>('/api/tool/result', {
method: 'POST',
body: result
return request<ToolResultResponse>("/api/tool/result", {
method: "POST",
body: result,
});
}
@ -112,11 +161,13 @@ export async function submitToolResult(result: ToolCallResult): Promise<ToolResu
* 提交用户回答
* POST /api/task/answer
*/
export async function submitAnswer(answer: AnswerRequest): Promise<AnswerResponse> {
export async function submitAnswer(
answer: AnswerRequest,
): Promise<AnswerResponse> {
console.log(`[API] 提交用户回答: askId=${answer.askId}`);
return request<AnswerResponse>('/api/task/answer', {
method: 'POST',
body: answer
return request<AnswerResponse>("/api/task/answer", {
method: "POST",
body: answer,
});
}
@ -124,11 +175,15 @@ export async function submitAnswer(answer: AnswerRequest): Promise<AnswerRespons
* 提交工具确认响应Ask 模式)
* POST /api/tool/confirm
*/
export async function submitToolConfirm(response: ToolConfirmResponse): Promise<ToolResultResponse> {
console.log(`[API] 提交工具确认: confirmId=${response.confirmId}, approved=${response.approved}`);
return request<ToolResultResponse>('/api/tool/confirm', {
method: 'POST',
body: response
export async function submitToolConfirm(
response: ToolConfirmResponse,
): Promise<ToolResultResponse> {
console.log(
`[API] 提交工具确认: confirmId=${response.confirmId}, approved=${response.approved}`,
);
return request<ToolResultResponse>("/api/tool/confirm", {
method: "POST",
body: response,
});
}
@ -137,9 +192,9 @@ export async function submitToolConfirm(response: ToolConfirmResponse): Promise<
* GET /api/dialog/health
*/
export async function healthCheck(): Promise<{ status: string }> {
return request<{ status: string }>('/api/dialog/health', {
method: 'GET',
timeout: 5000
return request<{ status: string }>("/api/dialog/health", {
method: "GET",
timeout: 5000,
});
}
@ -166,9 +221,9 @@ export interface StopDialogResponse {
*/
export async function stopDialog(taskId: string): Promise<StopDialogResponse> {
console.log(`[API] 停止对话: taskId=${taskId}`);
return request<StopDialogResponse>('/api/dialog/stop', {
method: 'POST',
body: { taskId }
return request<StopDialogResponse>("/api/dialog/stop", {
method: "POST",
body: { taskId },
});
}
@ -184,11 +239,13 @@ export interface CompactDialogResponse {
* 手动压缩对话历史
* POST /api/dialog/compact
*/
export async function compactDialog(taskId: string): Promise<CompactDialogResponse> {
export async function compactDialog(
taskId: string,
): Promise<CompactDialogResponse> {
console.log(`[API] 压缩对话: taskId=${taskId}`);
return request<CompactDialogResponse>('/api/dialog/compact', {
method: 'POST',
body: { taskId }
return request<CompactDialogResponse>("/api/dialog/compact", {
method: "POST",
body: { taskId },
});
}
@ -197,37 +254,44 @@ export async function compactDialog(taskId: string): Promise<CompactDialogRespon
*/
export function createSuccessResult(id: number, text: string): ToolCallResult {
return {
jsonrpc: '2.0',
jsonrpc: "2.0",
id,
result: {
content: [{ type: 'text', text }],
isError: false
}
content: [{ type: "text", text }],
isError: false,
},
};
}
/**
* 创建业务错误的工具结果(如编译失败)
*/
export function createBusinessErrorResult(id: number, errorMessage: string): ToolCallResult {
export function createBusinessErrorResult(
id: number,
errorMessage: string,
): ToolCallResult {
return {
jsonrpc: '2.0',
jsonrpc: "2.0",
id,
result: {
content: [{ type: 'text', text: errorMessage }],
isError: true
}
content: [{ type: "text", text: errorMessage }],
isError: true,
},
};
}
/**
* 创建系统错误的工具结果
*/
export function createSystemErrorResult(id: number, code: number, message: string): ToolCallResult {
export function createSystemErrorResult(
id: number,
code: number,
message: string,
): ToolCallResult {
return {
jsonrpc: '2.0',
jsonrpc: "2.0",
id,
error: { code, message }
error: { code, message },
};
}
@ -236,9 +300,9 @@ export function createSystemErrorResult(id: number, code: number, message: strin
* GET /system/user/getInfo
*/
export async function getUserInfo(): Promise<UserInfoResponse> {
console.log('[API] 获取用户信息');
return request<UserInfoResponse>('/system/user/getInfo', {
method: 'GET'
console.log("[API] 获取用户信息");
return request<UserInfoResponse>("/system/user/getInfo", {
method: "GET",
});
}
@ -253,25 +317,45 @@ export interface CreditBalanceResponse {
* 查询用户资源点余额
* GET /api/dialog/balance?userId=xxx
*/
export async function getCreditBalance(userId: string): Promise<CreditBalanceResponse> {
console.log('[API] 查询余额: userId=', userId);
return request<CreditBalanceResponse>(`/api/dialog/balance?userId=${userId}`, {
method: 'GET',
timeout: 5000
});
export async function getCreditBalance(
userId: string,
): Promise<CreditBalanceResponse> {
console.log("[API] 查询余额: userId=", userId);
return request<CreditBalanceResponse>(
`/api/dialog/balance?userId=${userId}`,
{
method: "GET",
timeout: 5000,
},
);
}
/**
* 验证邀请码
* POST /api/invitation/verify
*/
export async function verifyInvitationCode(code: string): Promise<InvitationVerifyResponse> {
console.log('[API] 验证邀请码');
export async function verifyInvitationCode(
code: string,
): Promise<InvitationVerifyResponse> {
// console.log('[API] 验证邀请码 - 开始');
console.log("[API] 邀请码:", code);
const body: InvitationVerifyRequest = { code };
return request<InvitationVerifyResponse>('/api/invitation/verify', {
method: 'POST',
body
});
console.log("[API] 请求体:", JSON.stringify(body));
try {
const response = await request<InvitationVerifyResponse>(
"/api/invitation/verify",
{
method: "POST",
body,
},
);
console.log("[API] 验证邀请码 - 响应:", JSON.stringify(response));
return response;
} catch (error) {
console.error("[API] 验证邀请码 - 错误:", error);
throw error;
}
}
/**
@ -279,8 +363,33 @@ export async function verifyInvitationCode(code: string): Promise<InvitationVeri
* GET /api/invitation/status
*/
export async function checkInvitationStatus(): Promise<InvitationStatusResponse> {
console.log('[API] 查询邀请码验证状态');
return request<InvitationStatusResponse>('/api/invitation/status', {
method: 'GET'
console.log("[API] 查询邀请码验证状态");
return request<InvitationStatusResponse>("/api/invitation/status", {
method: "GET",
});
}
/**
* 重置邀请码验证状态(退出登录时调用)
* POST /api/invitation/reset
*/
export async function resetInvitationVerification(): Promise<{
code: number;
msg: string;
}> {
console.log("[API] 重置邀请码验证状态");
try {
const response = await request<{ code: number; msg: string }>(
"/api/invitation/reset",
{
method: "POST",
},
);
console.log("[API] 重置邀请码验证状态 - 响应:", JSON.stringify(response));
return response;
} catch (error) {
console.warn("[API] 重置邀请码验证状态 - 错误:", error);
// 即使失败也不影响退出登录流程
throw error;
}
}

View File

@ -41,6 +41,7 @@ export interface MessageSegment {
toolName?: string;
toolStatus?: "running" | "success" | "error";
toolResult?: string;
toolDescription?: string;
askId?: string;
question?: string;
options?: string[];
@ -180,7 +181,8 @@ export class DialogSession {
private updateToolSegment(
toolName: string,
status: "success" | "error",
result?: string
result?: string,
description?: string
): void {
// 找到最后一个匹配的工具段落
for (let i = this.segments.length - 1; i >= 0; i--) {
@ -192,6 +194,9 @@ export class DialogSession {
) {
seg.toolStatus = status;
seg.toolResult = result;
if (description !== undefined) {
seg.toolDescription = description;
}
break;
}
}
@ -590,7 +595,7 @@ export class DialogSession {
},
onToolComplete: (data) => {
this.updateToolSegment(data.tool_name, "success", data.result);
this.updateToolSegment(data.tool_name, "success", data.result, data.description);
callbacks.onToolComplete?.(data.tool_name, data.result);
// 实时发送段落更新
callbacks.onSegmentUpdate?.(this.segments);

View File

@ -4,6 +4,7 @@ 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";
/**
* IC Coder Authentication Provider
@ -142,13 +143,24 @@ export class ICCoderAuthenticationProvider
const sessionIndex = this._sessions.findIndex((s) => s.id === sessionId);
if (sessionIndex > -1) {
const session = this._sessions[sessionIndex];
// 1. 先调用后端重置邀请码验证状态
try {
await resetInvitationVerification();
console.log("[AuthProvider] 邀请码验证状态已重置");
} catch (error) {
console.warn("[AuthProvider] 重置邀请码验证状态失败,但继续退出流程:", error);
// 即使失败也继续退出流程
}
// 2. 清除本地 session
this._sessions.splice(sessionIndex, 1);
await this.saveSessions();
// 清除用户信息缓存
// 3. 清除用户信息缓存
await clearUserInfo();
// 触发会话变化事件
// 4. 触发会话变化事件
this._onDidChangeSessions.fire({
added: [],
removed: [session],

View File

@ -1,8 +1,8 @@
/**
* 邀请码验证服务
*/
import * as vscode from 'vscode';
import { verifyInvitationCode, checkInvitationStatus } from './apiClient';
import * as vscode from "vscode";
import { verifyInvitationCode, checkInvitationStatus } from "./apiClient";
/**
* 邀请码验证服务类
@ -13,34 +13,51 @@ export class InvitationService {
*/
static async isVerified(context: vscode.ExtensionContext): Promise<boolean> {
// 【临时】使用本地验证,不调用后端
const localVerified = context.globalState.get<boolean>('invitationCodeVerified');
const localVerified = context.globalState.get<boolean>(
"invitationCodeVerified",
);
return localVerified || false;
}
/**
* 验证邀请码
*/
static async verifyCode(code: string): Promise<{ success: boolean; message: string }> {
static async verifyCode(
code: string,
): Promise<{ success: boolean; message: string }> {
try {
console.log('[InvitationService] 验证邀请码:', code);
// console.log('[InvitationService] ========== 开始验证邀请码 ==========');
// console.log('[InvitationService] 邀请码:', code);
// console.log('[InvitationService] 邀请码长度:', code.length);
const response = await verifyInvitationCode(code);
// console.log('[InvitationService] 收到响应:', JSON.stringify(response, null, 2));
// console.log('[InvitationService] 响应代码:', response.code);
// console.log('[InvitationService] 响应消息:', response.msg);
// console.log('[InvitationService] 验证结果:', response.data?.verified);
if (response.code === 200 && response.data?.verified) {
console.log("[InvitationService] ✓ 验证成功");
return {
success: true,
message: response.msg || '验证成功'
message: response.msg || "验证成功",
};
} else {
console.log("[InvitationService] ✗ 验证失败");
return {
success: false,
message: response.msg || '验证失败'
message: response.msg || "验证失败",
};
}
} catch (error: any) {
console.error('[InvitationService] 验证邀请码失败:', error);
// console.error('[InvitationService] ========== 验证邀请码异常 ==========');
// console.error('[InvitationService] 错误类型:', error.constructor.name);
// console.error('[InvitationService] 错误消息:', error.message);
// console.error('[InvitationService] 错误堆栈:', error.stack);
return {
success: false,
message: error.message || '网络连接失败,请检查网络后重试'
message: error.message || "网络连接失败,请检查网络后重试",
};
}
}
@ -51,20 +68,25 @@ export class InvitationService {
static async saveVerificationStatus(
context: vscode.ExtensionContext,
code: string,
verifiedTime?: string
verifiedTime?: string,
): Promise<void> {
await context.globalState.update('invitationCodeVerified', true);
await context.globalState.update('invitationCode', code);
await context.globalState.update('invitationVerifiedTime', verifiedTime || new Date().toISOString());
await context.globalState.update("invitationCodeVerified", true);
await context.globalState.update("invitationCode", code);
await context.globalState.update(
"invitationVerifiedTime",
verifiedTime || new Date().toISOString(),
);
}
/**
* 清除验证状态(用于退出登录或更换邀请码)
*/
static async clearVerificationStatus(context: vscode.ExtensionContext): Promise<void> {
await context.globalState.update('invitationCodeVerified', undefined);
await context.globalState.update('invitationCode', undefined);
await context.globalState.update('invitationVerifiedTime', undefined);
static async clearVerificationStatus(
context: vscode.ExtensionContext,
): Promise<void> {
await context.globalState.update("invitationCodeVerified", undefined);
await context.globalState.update("invitationCode", undefined);
await context.globalState.update("invitationVerifiedTime", undefined);
}
/**
@ -72,18 +94,18 @@ export class InvitationService {
*/
static async showInputDialog(): Promise<string | undefined> {
const code = await vscode.window.showInputBox({
prompt: '请输入邀请码以继续使用 IC Coder',
placeHolder: '例如INVITE2024ABC',
prompt: "请输入邀请码以继续使用 IC Coder",
placeHolder: "例如INVITE2024ABC",
ignoreFocusOut: true,
validateInput: (value) => {
if (!value || value.trim().length === 0) {
return '邀请码不能为空';
return "邀请码不能为空";
}
if (value.trim().length < 6) {
return '邀请码格式不正确';
return "邀请码格式不正确";
}
return null;
}
},
});
return code?.trim();

View File

@ -96,6 +96,7 @@ export interface ToolStartEvent {
export interface ToolCompleteEvent {
tool_name: string;
result: string;
description?: string;
}
/** tool_error 事件数据 */

View File

@ -59,13 +59,25 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Max.png")
);
// 获取二维码图片URI
const qrCodeUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "QRCode", "wx.png")
);
// 获取Logo URI
const logoUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "media", "homepage-logo.png")
);
// 设置HTML内容
panel.webview.html = getWebviewContent(
iconUri.toString(),
autoIconUri.toString(),
liteIconUri.toString(),
syIconUri.toString(),
maxIconUri.toString()
maxIconUri.toString(),
qrCodeUri.toString(),
logoUri.toString()
);
// 处理消息

View File

@ -5,37 +5,57 @@
/**
* 获取邀请码弹窗的 HTML 内容
*/
export function getInvitationModalContent(qrCodeUri?: string): string {
export function getInvitationModalContent(
qrCodeUri?: string,
logoUri?: string,
): string {
return `
<!-- 邀请码验证弹窗 -->
<div id="invitationModal" class="invitation-modal" style="display: none;">
<div class="invitation-modal-overlay"></div>
<div class="invitation-modal-content">
${logoUri ? `<img src="${logoUri}" class="invitation-logo-corner" alt="IC Coder" />` : ""}
<button id="invitationCloseBtn" class="invitation-close-btn" title="关闭">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M1 1L13 13M13 1L1 13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
<div class="invitation-modal-header">
<h2>验证邀请码</h2>
<p class="invitation-modal-subtitle">仅供企业端用户和内部人员使用</p>
<!-- <div class="invitation-icon">🔐</div> -->
<h2>欢迎使用 IC Coder</h2>
<p class="invitation-modal-subtitle">目前IC Coder插件端仅供企业端付费用户使用2026年3月起会逐步开放给所有用户使用~</p>
</div>
<div class="invitation-modal-body">
<div class="invitation-qrcode-section">
<p class="invitation-qrcode-text">欢迎企业端用户扫码添加微信获取邀请码</p>
<img src="${qrCodeUri}" alt="微信二维码" class="invitation-qrcode-image" />
<div class="invitation-qrcode-wrapper">
<img src="${qrCodeUri}" alt="微信二维码" class="invitation-qrcode-image" />
</div>
<p class="invitation-qrcode-text">欢迎扫码添加微信,填写《企业试用申请表》获取邀请码,与我们一起加速芯片设计与验证吧!</p>
</div>
<div class="invitation-divider">
</div>
<div class="invitation-input-section">
<label class="invitation-input-label">邀请码</label>
<input
type="text"
id="invitationCodeInput"
class="invitation-code-input"
placeholder="请输入邀请码"
placeholder="请输入您的邀请码"
maxlength="20"
/>
<div id="invitationError" class="invitation-error" style="display: none;"></div>
<button id="invitationSubmitBtn" class="invitation-btn invitation-btn-primary">
<span>立即验证</span>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 3L11 8L6 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
<div class="invitation-modal-footer">
<button id="invitationSubmitBtn" class="invitation-btn invitation-btn-primary">
验证
</button>
</div>
</div>
</div>
`;
@ -57,6 +77,8 @@ export function getInvitationModalStyles(): string {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
font-family: var(--vscode-font-family, "Segoe UI", Tahoma, Geneva, Verdana, sans-serif);
}
.invitation-modal-overlay {
@ -65,77 +87,164 @@ export function getInvitationModalStyles(): string {
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.invitation-modal-content {
position: relative;
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
width: 90%;
max-width: 400px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
animation: modalSlideIn 0.3s ease-out;
border: 1px solid var(--vscode-widget-border);
border-radius: 12px;
width: 100%;
max-width: 420px;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
animation: modalSlideIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
overflow: hidden;
display: flex;
flex-direction: column;
}
.invitation-close-btn {
position: absolute;
top: 16px;
right: 16px;
width: 32px;
height: 32px;
border: none;
background: transparent;
color: var(--vscode-descriptionForeground);
cursor: pointer;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
z-index: 10;
}
.invitation-logo-corner {
position: absolute;
top: 16px;
left: 24px;
height: 40px;
width: auto;
opacity: 0.9;
z-index: 10;
}
.invitation-close-btn:hover {
background: var(--vscode-toolbar-hoverBackground);
color: var(--vscode-foreground);
}
.invitation-close-btn:active {
transform: scale(0.95);
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-20px);
transform: translateY(20px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0);
transform: translateY(0) scale(1);
}
}
.invitation-modal-header {
padding: 24px 24px 16px;
border-bottom: 1px solid var(--vscode-panel-border);
padding: 60px 32px 20px;
text-align: center;
}
.invitation-modal-header h2 {
margin: 0;
font-size: 18px;
margin: 0 0 12px;
font-size: 20px;
font-weight: 600;
color: var(--vscode-foreground);
}
.invitation-modal-subtitle {
margin: 8px 0 0;
margin: 0;
font-size: 13px;
color: var(--vscode-descriptionForeground);
line-height: 1.5;
}
.invitation-modal-body {
padding: 24px;
padding: 0 32px 32px;
}
.invitation-qrcode-section {
text-align: center;
margin-bottom: 24px;
background: var(--vscode-editor-inactiveSelectionBackground);
padding: 20px;
border-radius: 8px;
margin-top: 10px;
}
.invitation-qrcode-text {
margin: 0 0 16px;
font-size: 13px;
color: var(--vscode-foreground);
line-height: 1.5;
.invitation-qrcode-wrapper {
display: inline-block;
padding: 8px;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.invitation-qrcode-image {
width: 200px;
height: 200px;
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
background: #fff;
width: 150px;
height: 150px;
display: block;
}
.invitation-qrcode-text {
margin-top: 12px;
font-size: 12px;
color: var(--vscode-descriptionForeground);
line-height: 1.5;
}
.invitation-divider {
display: flex;
align-items: center;
margin: 20px 0;
color: var(--vscode-descriptionForeground);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.invitation-divider::before,
.invitation-divider::after {
content: '';
flex: 1;
height: 1px;
background: var(--vscode-widget-border);
opacity: 0.5;
}
.invitation-divider span {
padding: 0 12px;
}
.invitation-input-section {
margin-top: 24px;
display: flex;
flex-direction: column;
gap: 12px;
}
.invitation-input-label {
font-size: 13px;
font-weight: 600;
color: var(--vscode-foreground);
}
.invitation-code-input {
@ -145,63 +254,74 @@ export function getInvitationModalStyles(): string {
border: 1px solid var(--vscode-input-border);
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border-radius: 4px;
border-radius: 6px;
outline: none;
transition: border-color 0.2s;
box-sizing: border-box;
transition: border-color 0.2s;
}
.invitation-code-input:focus {
border-color: var(--vscode-focusBorder);
/* box-shadow: 0 0 0 2px var(--vscode-focusBorder); */
}
.invitation-code-input::placeholder {
color: var(--vscode-input-placeholderForeground);
color: var(--vscode-input-placeholderForeground);
}
.invitation-error {
margin-top: 12px;
padding: 8px 12px;
font-size: 13px;
font-size: 12px;
color: var(--vscode-errorForeground);
background: var(--vscode-inputValidation-errorBackground);
border: 1px solid var(--vscode-inputValidation-errorBorder);
border-radius: 4px;
border-radius: 6px;
animation: shakeError 0.4s ease-in-out;
}
.invitation-modal-footer {
padding: 16px 24px;
border-top: 1px solid var(--vscode-panel-border);
display: flex;
justify-content: flex-end;
@keyframes shakeError {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-6px); }
75% { transform: translateX(6px); }
}
.invitation-btn {
padding: 8px 20px;
font-size: 13px;
width: 100%;
padding: 10px 16px;
font-size: 14px;
font-weight: 600;
border: none;
border-radius: 4px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
outline: none;
}
.invitation-btn-primary {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
transition: all 0.2s;
}
.invitation-btn-primary:hover {
.invitation-btn:hover {
background: var(--vscode-button-hoverBackground);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.invitation-btn-primary:active {
transform: scale(0.98);
.invitation-btn:active {
transform: translateY(0);
}
.invitation-btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
.invitation-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.invitation-btn svg {
width: 16px;
height: 16px;
}
`;
}
@ -216,6 +336,7 @@ export function getInvitationModalScript(): string {
const modal = document.getElementById('invitationModal');
const input = document.getElementById('invitationCodeInput');
const submitBtn = document.getElementById('invitationSubmitBtn');
const closeBtn = document.getElementById('invitationCloseBtn');
const errorDiv = document.getElementById('invitationError');
// 显示邀请码弹窗
@ -270,6 +391,16 @@ export function getInvitationModalScript(): string {
// 点击提交按钮
submitBtn.addEventListener('click', submitInvitationCode);
// 点击关闭按钮
if (closeBtn) {
closeBtn.addEventListener('click', function(e) {
console.log('[InvitationModal] Close button clicked');
e.preventDefault();
e.stopPropagation();
hideInvitationModal();
});
}
// 回车键提交
input.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
@ -277,6 +408,11 @@ export function getInvitationModalScript(): string {
}
});
// 点击遮罩层关闭弹窗
document.querySelector('.invitation-modal-overlay').addEventListener('click', function() {
hideInvitationModal();
});
// 阻止点击弹窗内容时关闭
document.querySelector('.invitation-modal-content').addEventListener('click', function(e) {
e.stopPropagation();

View File

@ -542,6 +542,12 @@ export function getMessageAreaStyles(): string {
.tool-segment-content.collapsed {
max-height: 0;
}
.tool-segment-description {
margin: 2px 0 0 0px;
font-size: 12px;
color: #fff;
line-height: 1.4;
}
/* 低调显示的工具调用样式 */
.segment-tool.low-profile .tool-segment-header {
opacity: 0.65;
@ -1064,6 +1070,7 @@ export function getMessageAreaScript(): string {
const toolResult = segment.toolResult || '';
const toolCount = segment.toolCount || 1;
const countSuffix = toolCount > 1 ? \` x\${toolCount}\` : '';
const toolDescription = segment.toolDescription || '';
// 检查工具结果是否过长(超过一行显示不下)
const shouldCollapse = toolResult && toolResult.length > 60;
@ -1081,6 +1088,7 @@ export function getMessageAreaScript(): string {
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
</div>
\${shouldCollapse ? \`<div class="tool-segment-content\${isCollapsed ? ' collapsed' : ''}" style="max-height:\${isCollapsed ? '0' : 'none'}"><span class="tool-segment-result" style="display:block;white-space:pre-wrap;max-width:100%;margin-top:8px;margin-left:18px;">\${toolResult}</span></div>\` : ''}
\${toolDescription ? \`<p class="tool-segment-description">\${toolDescription}</p>\` : ''}
\`;
// 如果是仿真工具且成功完成,尝试添加波形预览
@ -1328,6 +1336,7 @@ export function getMessageAreaScript(): string {
const toolResult = segment.toolResult || '';
const toolCount = segment.toolCount || 1;
const countSuffix = toolCount > 1 ? \` x\${toolCount}\` : '';
const toolDescription = segment.toolDescription || '';
// 检查工具结果是否过长(超过一行显示不下)
const shouldCollapse = toolResult && toolResult.length > 60;
@ -1339,6 +1348,7 @@ export function getMessageAreaScript(): string {
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
</div>
\${shouldCollapse ? \`<div class="tool-segment-content collapsed"><span class="tool-segment-result" style="display:block;white-space:pre-wrap;max-width:100%;margin-top:8px;margin-left:18px;">\${toolResult}</span></div>\` : ''}
\${toolDescription ? \`<p class="tool-segment-description">\${toolDescription}</p>\` : ''}
\`;
// 如果是仿真工具且成功完成,尝试添加波形预览

View File

@ -39,7 +39,8 @@ export function getWebviewContent(
liteIconUri?: string,
syIconUri?: string,
maxIconUri?: string,
qrCodeUri?: string
qrCodeUri?: string,
logoUri?: string,
): string {
// 获取当前环境,只在 dev 和 test 环境下显示快速操作按钮
const currentEnv = getCurrentEnv();
@ -399,18 +400,80 @@ export function getWebviewContent(
.quick-btn:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
/* 响应式调整 */
@media (max-height: 600px) {
.header {
/* 使用 clamp 动态调整内边距: 最小值 5px, 理想值 2vh, 最大值 20px */
padding: clamp(5px, 2vh, 20px) 20px;
flex: 0 0 auto;
min-height: auto;
}
.header img {
/* 使用 clamp 动态调整图片高度: 最小值 40px, 理想值 10vh, 最大值 60px */
max-height: clamp(40px, 10vh, 60px) !important;
}
.header p {
/* 使用 clamp 动态调整字体大小 */
font-size: clamp(12px, 2.5vh, 14px) !important;
margin-top: clamp(4px, 1.5vh, 8px) !important;
line-height: 1.2 !important;
margin-bottom: clamp(4px, 1.5vh, 8px) !important;
}
.quick-actions {
margin-bottom: 5px;
gap: 5px;
}
.quick-btn {
padding: 4px 8px;
font-size: 12px;
}
.chat-container {
padding: 0 10px 10px 10px;
}
}
/* 高度极小时隐藏描述文本 */
@media (max-height: 450px) {
.header p {
display: none !important;
}
.header {
padding: 4px;
}
.quick-actions {
margin-bottom: 4px;
}
}
@media (max-width: 480px) {
.header h1 {
font-size: 24px;
}
.header p {
font-size: 14px;
}
.quick-actions {
justify-content: center;
}
.chat-container {
padding: 0 10px 10px 10px;
}
}
</style>
</head>
<body>
${getConversationHistoryBarContent()}
${getProgressBarContent()}
${getInvitationModalContent(qrCodeUri)}
${getInvitationModalContent(qrCodeUri, logoUri)}
<div class="header">
<div style="display: flex; align-items: center; justify-content: center; gap: 15px;">
<img src="${iconUri}" alt="IC Coder" style="width: 48px; height: 48px;" />
<h1 style="margin: 0; font-size: 36px;">IC Coder</h1>
<div style="display: flex; align-items: center; justify-content: center;">
<img src="${logoUri}" alt="IC Coder" style="max-width: 100%; height: auto; max-height: 80px;" />
</div>
<p style="font-size: 16px; margin-top: 12px;">专注于真实FPGA研发的Verilog智能体编程平台</p>
<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>
</div>
<div class="chat-container">