feat:修改对话中的样式 + 欢迎宁德弹窗内容

This commit is contained in:
Roe-xin
2026-02-26 17:27:23 +08:00
parent 7ca2fa1bcc
commit c9e9df3825
3 changed files with 183 additions and 104 deletions

View File

@ -28,17 +28,17 @@ import { setBalanceUpdateCallback } from "../services/creditsService";
function getTierIconUri(
webview: vscode.Webview,
context: vscode.ExtensionContext,
tierCode?: string
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'
BASIC: "free.png",
TRIAL: "PRO-Try.png",
ADVANCED: "PRO.png",
PROFESSIONAL: "PRO+.png",
};
const iconFile = tierIconMap[tierCode];
@ -47,7 +47,13 @@ function getTierIconUri(
}
const iconUri = webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'src', 'assets', 'titleIcon', iconFile)
vscode.Uri.joinPath(
context.extensionUri,
"src",
"assets",
"titleIcon",
iconFile,
),
);
return iconUri.toString();
@ -58,27 +64,29 @@ function getTierIconUri(
*/
export async function showICHelperPanel(
context: vscode.ExtensionContext,
viewColumn?: vscode.ViewColumn
viewColumn?: vscode.ViewColumn,
) {
// 检查 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('[ICHelperPanel] 获取 session 失败:', error);
console.warn("[ICHelperPanel] 获取 session 失败:", error);
}
if (token && isTokenExpired(token)) {
// 清除过期的 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");
}
return;
@ -120,9 +128,9 @@ export async function showICHelperPanel(
retainContextWhenHidden: true,
localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, "media"),
vscode.Uri.joinPath(context.extensionUri, "src", "assets")
vscode.Uri.joinPath(context.extensionUri, "src", "assets"),
],
}
},
);
// 为面板生成唯一ID
@ -136,36 +144,66 @@ export async function showICHelperPanel(
panel.iconPath = vscode.Uri.joinPath(
context.extensionUri,
"media",
"icon.png"
"icon.png",
);
// 获取页面内图标URI
const iconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "media", "icon.png")
vscode.Uri.joinPath(context.extensionUri, "media", "icon.png"),
);
// 获取模型图标URI
const autoIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Auto.png")
vscode.Uri.joinPath(
context.extensionUri,
"src",
"assets",
"model",
"Auto.png",
),
);
const liteIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "lite.png")
vscode.Uri.joinPath(
context.extensionUri,
"src",
"assets",
"model",
"lite.png",
),
);
const syIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Sy.png")
vscode.Uri.joinPath(
context.extensionUri,
"src",
"assets",
"model",
"Sy.png",
),
);
const maxIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Max.png")
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")
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")
vscode.Uri.joinPath(context.extensionUri, "media", "homepage-logo.png"),
);
// 设置HTML内容
@ -176,7 +214,7 @@ export async function showICHelperPanel(
syIconUri.toString(),
maxIconUri.toString(),
qrCodeUri.toString(),
logoUri.toString()
logoUri.toString(),
);
// 获取并发送用户信息到 webview
@ -186,21 +224,25 @@ export async function showICHelperPanel(
if (userInfo) {
// 使用缓存的用户信息
console.log('[ICHelperPanel] 使用缓存的用户信息:', userInfo);
console.log('[ICHelperPanel] Credits 余额:', userInfo.credits);
const tierIconUrl = getTierIconUri(panel.webview, context, userInfo.membership?.tierCode);
console.log("[ICHelperPanel] 使用缓存的用户信息:", userInfo);
console.log("[ICHelperPanel] Credits 余额:", userInfo.credits);
const tierIconUrl = getTierIconUri(
panel.webview,
context,
userInfo.membership?.tierCode,
);
const messageData = {
command: 'updateUserInfo',
command: "updateUserInfo",
userInfo: {
userId: userInfo.userId,
nickname: userInfo.nickname,
username: userInfo.username,
credits: userInfo.credits,
membership: userInfo.membership
membership: userInfo.membership,
},
tierIconUrl: tierIconUrl
tierIconUrl: tierIconUrl,
};
console.log('[ICHelperPanel] 发送用户信息到前端:', messageData);
console.log("[ICHelperPanel] 发送用户信息到前端:", messageData);
panel.webview.postMessage(messageData);
} else {
// 如果没有缓存,从 session 中获取
@ -208,19 +250,22 @@ export async function showICHelperPanel(
createIfNone: false,
});
if (session) {
console.log('[ICHelperPanel] 从 session 获取用户信息, account:', session.account);
console.log(
"[ICHelperPanel] 从 session 获取用户信息, account:",
session.account,
);
panel.webview.postMessage({
command: 'updateUserInfo',
command: "updateUserInfo",
userInfo: {
userId: session.account.id,
nickname: session.account.label,
username: session.account.label
}
username: session.account.label,
},
});
}
}
} catch (error) {
console.error('[ICHelperPanel] 获取用户信息失败:', error);
console.error("[ICHelperPanel] 获取用户信息失败:", error);
}
// 设置余额更新回调
@ -228,36 +273,40 @@ export async function showICHelperPanel(
const userInfo = getCachedUserInfo();
if (userInfo) {
userInfo.credits = balance;
const tierIconUrl = getTierIconUri(panel.webview, context, userInfo.membership?.tierCode);
const tierIconUrl = getTierIconUri(
panel.webview,
context,
userInfo.membership?.tierCode,
);
panel.webview.postMessage({
command: 'updateUserInfo',
command: "updateUserInfo",
userInfo: {
userId: userInfo.userId,
nickname: userInfo.nickname,
username: userInfo.username,
credits: balance,
membership: userInfo.membership
membership: userInfo.membership,
},
tierIconUrl: tierIconUrl
tierIconUrl: tierIconUrl,
});
}
});
// 检查是否有待发送的消息
const pendingMessage = context.globalState.get('pendingMessage') as any;
const pendingMessage = context.globalState.get("pendingMessage") as any;
if (pendingMessage) {
console.log('[ICHelperPanel] 检测到待发送消息,准备自动发送');
console.log("[ICHelperPanel] 检测到待发送消息,准备自动发送");
// 清除待发送消息
await context.globalState.update('pendingMessage', undefined);
await context.globalState.update("pendingMessage", undefined);
// 延迟发送,确保面板已完全初始化
setTimeout(() => {
panel.webview.postMessage({
command: 'autoSendMessage',
command: "autoSendMessage",
text: pendingMessage.text,
mode: pendingMessage.mode,
serviceTier: pendingMessage.serviceTier
serviceTier: pendingMessage.serviceTier,
});
}, 500);
}
@ -279,12 +328,12 @@ export async function showICHelperPanel(
try {
const taskMeta = await historyManager.createTask(
workspacePath,
"新对话"
"新对话",
);
historyManager.setPanelTask(
panelId,
taskMeta.taskId,
workspacePath
workspacePath,
);
} catch (error) {
console.error("创建任务失败:", error);
@ -296,14 +345,15 @@ export async function showICHelperPanel(
historyManager.switchToPanelTask(panelId);
// 显示进度条
panel.webview.postMessage({ type: 'showProgress' });
panel.webview.postMessage({ type: "showProgress" });
handleUserMessage(
panel,
message.text,
context.extensionPath,
message.mode,
message.model // 传递服务等级
message.model, // 传递服务等级
message.contextItems, // 传递上下文项
);
break;
case "readFile":
@ -320,7 +370,7 @@ export async function showICHelperPanel(
panel,
message.filePath,
message.searchText,
message.replaceText
message.replaceText,
);
break;
case "insertCode":
@ -332,7 +382,10 @@ export async function showICHelperPanel(
case "openWaveformViewer":
// 在新列中打开波形查看器
if (message.vcdFilePath) {
vscode.commands.executeCommand('ic-coder.openVCDViewer', message.vcdFilePath);
vscode.commands.executeCommand(
"ic-coder.openVCDViewer",
message.vcdFilePath,
);
}
break;
case "getVCDInfo":
@ -350,7 +403,7 @@ export async function showICHelperPanel(
loadConversationHistory(
panel,
message.offset || 0,
message.limit || 10
message.limit || 10,
);
break;
case "selectConversation":
@ -359,7 +412,7 @@ export async function showICHelperPanel(
selectConversation(
panel,
message.conversationId,
context.extensionPath
context.extensionPath,
);
}
break;
@ -368,7 +421,7 @@ export async function showICHelperPanel(
void handleUserAnswer(
message.askId,
message.selected,
message.customInput
message.customInput,
);
break;
// 新增:中止对话
@ -432,18 +485,20 @@ export async function showICHelperPanel(
if (userInfo?.isPluginTrial === true) {
// 试用用户,跳过邀请码验证,直接返回已验证
console.log('[ICHelperPanel] 试用用户,跳过邀请码验证');
console.log("[ICHelperPanel] 试用用户,跳过邀请码验证");
panel.webview.postMessage({
command: "invitationCodeStatus",
verified: true
verified: true,
});
} else {
// 正式用户,检查邀请码
const { InvitationService } = require("../services/invitationService");
const {
InvitationService,
} = require("../services/invitationService");
const isVerified = await InvitationService.isVerified(context);
panel.webview.postMessage({
command: "invitationCodeStatus",
verified: isVerified
verified: isVerified,
});
}
}
@ -451,49 +506,63 @@ export async function showICHelperPanel(
case "checkWelcomeModal":
// 检查是否需要显示欢迎弹窗
{
console.log('[ICHelperPanel] 收到 checkWelcomeModal 消息');
const showWelcome = context.globalState.get('showWelcomeModal');
console.log('[ICHelperPanel] showWelcomeModal 标记值:', showWelcome);
console.log("[ICHelperPanel] 收到 checkWelcomeModal 消息");
const showWelcome = context.globalState.get("showWelcomeModal");
console.log(
"[ICHelperPanel] showWelcomeModal 标记值:",
showWelcome,
);
if (showWelcome) {
// 清除标记并显示欢迎弹窗
await context.globalState.update('showWelcomeModal', undefined);
console.log('[ICHelperPanel] ✅ 发送 showWelcomeModal 命令到前端');
await context.globalState.update("showWelcomeModal", undefined);
console.log(
"[ICHelperPanel] ✅ 发送 showWelcomeModal 命令到前端",
);
panel.webview.postMessage({
command: "showWelcomeModal"
command: "showWelcomeModal",
});
} else {
console.log('[ICHelperPanel] showWelcomeModal 标记为 false不显示弹窗');
console.log(
"[ICHelperPanel] showWelcomeModal 标记为 false不显示弹窗",
);
}
}
break;
case "checkTrialExpiration":
// 检查试用期是否过期
{
console.log('[ICHelperPanel] 收到 checkTrialExpiration 消息');
const { TrialExpirationService } = require("../services/trialExpirationService");
console.log("[ICHelperPanel] 收到 checkTrialExpiration 消息");
const {
TrialExpirationService,
} = require("../services/trialExpirationService");
const trialService = new TrialExpirationService(context, panel);
const isExpired = await trialService.checkExpiration();
console.log('[ICHelperPanel] 试用期过期状态:', isExpired);
console.log("[ICHelperPanel] 试用期过期状态:", isExpired);
}
break;
case "verifyInvitationCode":
// 验证邀请码
{
const { InvitationService } = require("../services/invitationService");
const {
InvitationService,
} = require("../services/invitationService");
const result = await InvitationService.verifyCode(message.code);
if (result.success) {
// 验证成功,保存状态
await InvitationService.saveVerificationStatus(context, message.code);
await InvitationService.saveVerificationStatus(
context,
message.code,
);
panel.webview.postMessage({
command: "invitationCodeVerified",
success: true
success: true,
});
// 延迟显示欢迎弹窗,确保邀请码弹窗已关闭
setTimeout(() => {
panel.webview.postMessage({
command: "showNdtWelcomeModal"
command: "showNdtWelcomeModal",
});
}, 300);
} else {
@ -501,7 +570,7 @@ export async function showICHelperPanel(
panel.webview.postMessage({
command: "invitationCodeVerified",
success: false,
message: result.message
message: result.message,
});
}
}
@ -512,7 +581,11 @@ export async function showICHelperPanel(
break;
case "openTutorial":
// 打开使用教程
vscode.env.openExternal(vscode.Uri.parse("https://www.iccoder.com/tutorial"));
vscode.env.openExternal(
vscode.Uri.parse(
"https://www.iccoder.com/guides/quick-start/first-task-plugin",
),
);
break;
case "openUserManual":
// 打开用户手册
@ -521,7 +594,7 @@ export async function showICHelperPanel(
case "openUserFeedback":
// 打开用户反馈二维码弹窗
panel.webview.postMessage({
command: "showFeedbackQRCode"
command: "showFeedbackQRCode",
});
break;
// 处理计划操作(只做模式切换,响应已通过 submitAnswer 发送)
@ -533,13 +606,16 @@ export async function showICHelperPanel(
mode: "agent",
});
// 注意:不再设置待执行计划;后端 LLM 会在同一对话中自动执行计划
} else if (message.action === "modify" || message.action === "cancel") {
} else if (
message.action === "modify" ||
message.action === "cancel"
) {
void handlePlanAction(
panel,
message.action,
message.planTitle || "",
context.extensionPath,
message.model
message.model,
);
}
break;
@ -555,7 +631,7 @@ export async function showICHelperPanel(
// 获取工作区所有文件
const files = await vscode.workspace.findFiles(
"**/*",
"**/node_modules/**"
"**/node_modules/**",
);
panel.webview.postMessage({
@ -585,7 +661,11 @@ export async function showICHelperPanel(
try {
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
if (item.isDirectory() && item.name !== "node_modules" && !item.name.startsWith(".")) {
if (
item.isDirectory() &&
item.name !== "node_modules" &&
!item.name.startsWith(".")
) {
const fullPath = path.join(dir, item.name);
const relativePath = path.relative(baseDir, fullPath);
folders.push({ path: fullPath, relativePath });
@ -614,7 +694,7 @@ export async function showICHelperPanel(
canSelectMany: true,
openLabel: "选择图片",
filters: {
"图片文件": ["png", "jpg", "jpeg", "gif", "bmp", "svg", "webp"],
: ["png", "jpg", "jpeg", "gif", "bmp", "svg", "webp"],
},
});
if (imageUris && imageUris.length > 0) {
@ -634,8 +714,8 @@ export async function showICHelperPanel(
canSelectMany: true,
openLabel: "选择文档",
filters: {
"文档文件": ["pdf", "doc", "docx", "txt", "md"],
"所有文件": ["*"],
: ["pdf", "doc", "docx", "txt", "md"],
: ["*"],
},
});
if (docUris && docUris.length > 0) {
@ -657,7 +737,7 @@ export async function showICHelperPanel(
vscode.window
.showWarningMessage(
"请先打开一个文件夹作为工作区,这样我就能更好地为您服务了 😊",
"打开文件夹"
"打开文件夹",
)
.then((selection) => {
if (selection === "打开文件夹") {
@ -679,16 +759,16 @@ export async function showICHelperPanel(
break;
case "openICCoder":
// 打开 IC Coder 官网
vscode.env.openExternal(vscode.Uri.parse('https://www.iccoder.com'));
vscode.env.openExternal(vscode.Uri.parse("https://www.iccoder.com"));
break;
case "logout":
// 退出登录(前端已有确认对话框)
vscode.commands.executeCommand('ic-coder.logout');
vscode.commands.executeCommand("ic-coder.logout");
break;
}
},
undefined,
context.subscriptions
context.subscriptions,
);
// 面板关闭时清理任务映射
@ -699,7 +779,7 @@ export async function showICHelperPanel(
historyManager.removePanelTask(panelId);
},
undefined,
context.subscriptions
context.subscriptions,
);
}
@ -709,7 +789,7 @@ export async function showICHelperPanel(
async function getVCDFileInfo(
panel: vscode.WebviewPanel,
vcdFilePath: string,
containerId: string
containerId: string,
) {
try {
const fs = require("fs");
@ -847,7 +927,7 @@ function parseVCDSignals(content: string, maxSignals: number = 3) {
if (signalDef.width === 1) {
// 单比特信号
const singleBitMatch = trimmedLine.match(
new RegExp(`^([01xz])${signalDef.identifier}$`)
new RegExp(`^([01xz])${signalDef.identifier}$`),
);
if (singleBitMatch) {
values.push({ time: currentTime, value: singleBitMatch[1] });
@ -855,7 +935,7 @@ function parseVCDSignals(content: string, maxSignals: number = 3) {
} else {
// 多比特信号
const multiBitMatch = trimmedLine.match(
new RegExp(`^b([01xz]+)\\s+${signalDef.identifier}$`)
new RegExp(`^b([01xz]+)\\s+${signalDef.identifier}$`),
);
if (multiBitMatch) {
values.push({ time: currentTime, value: multiBitMatch[1] });
@ -888,7 +968,7 @@ function parseVCDSignals(content: string, maxSignals: number = 3) {
async function loadConversationHistory(
panel: vscode.WebviewPanel,
offset: number = 0,
limit: number = 10
limit: number = 10,
) {
try {
const historyManager = ChatHistoryManager.getInstance();
@ -909,7 +989,7 @@ async function loadConversationHistory(
const result = await historyManager.getConversationHistoryList(
workspacePath,
offset,
limit
limit,
);
// 发送会话历史到前端
@ -937,7 +1017,7 @@ async function loadConversationHistory(
async function selectConversation(
panel: vscode.WebviewPanel,
taskId: string,
extensionPath: string
extensionPath: string,
) {
try {
const historyManager = ChatHistoryManager.getInstance();
@ -951,12 +1031,12 @@ async function selectConversation(
// 加载任务会话
const taskSession = await historyManager.loadTaskSession(
workspacePath,
taskId
taskId,
);
if (!taskSession) {
vscode.window.showErrorMessage(
`加载任务 ${taskId} 失败: 任务不存在或数据损坏`
`加载任务 ${taskId} 失败: 任务不存在或数据损坏`,
);
return;
}
@ -1117,7 +1197,7 @@ async function selectConversation(
}
vscode.window.showInformationMessage(
`已加载会话: ${taskSession.meta.taskName}`
`已加载会话: ${taskSession.meta.taskName}`,
);
} catch (error) {
console.error("选择会话失败:", error);

View File

@ -544,9 +544,9 @@ export function getMessageAreaStyles(): string {
max-height: 0;
}
.tool-segment-description {
margin: 2px 0 0 0px;
margin: 6px 0 0 0px;
font-size: 12px;
color: #fff;
color: #ccc;
line-height: 1.4;
}
/* 低调显示的工具调用样式 */

View File

@ -18,20 +18,19 @@ export function getNdtWelcomeModalContent(logoUri?: string): string {
<div class="ndt-welcome-modal-header">
<div class="ndt-welcome-icon">🎉</div>
<h2>欢迎宁德时代新能源科技股份有限公司的各位专家使用 IC Coder</h2>
<p class="ndt-welcome-modal-subtitle">感谢您选择 IC Coder 作为您的芯片设计助手</p>
<h2>欢迎宁德时代新能源科技股份有限公司<span style="white-space: nowrap;">的各位专家</span>使用 IC Coder</h2>
</div>
<div class="ndt-welcome-modal-body">
<!-- 试用期提示 -->
<div class="ndt-trial-banner">
<span>您已获得 <strong>5 天试用期</strong>,试用期内可无限制使用所有功能</span>
<span>您已获得 <strong>5 天企业版试用期</strong>企业版试用期内Credits用量无限可无限制使用所有功能</span>
</div>
<!-- IC Coder 简介 -->
<div class="ndt-intro-section">
<h3 class="ndt-section-title">关于 IC Coder</h3>
<p class="ndt-intro-text">IC Coder是一款The Agentic AI Verilog Coding Platform自主式人工智能 Verilog 编码平台)。我们采用全球顶尖的大语言模型,加上自研的针对芯片设计领域深度优化的微调模型为代码生成提供强大的AI能力支撑。</p>
<p class="ndt-intro-text">IC Coder是一款The Agentic AI Verilog Coding Platform自主式人工智能 Verilog 编码平台)。我们采用全球顶尖的IC Coder自研芯片设计微调模型为代码生成提供强大的AI能力支撑。</p>
<div class="ndt-features">
<div class="ndt-feature-item">
@ -41,7 +40,7 @@ export function getNdtWelcomeModalContent(logoUri?: string): string {
<span class="ndt-feature-text">增强上下文引擎:智能理解和管理大规模设计上下文,确保生成代码的一致性和准确性</span>
</div>
<div class="ndt-feature-item">
<span class="ndt-feature-text">自研EDA工具集完整的仿真、综合、时序分析工具链无缝集成到AI工作流中</span>
<span class="ndt-feature-text">AI自主仿真IC Coder提供完全自动化的仿真验证流程无需手动编写测试代码</span>
</div>
</div>
</div>