10 Commits

Author SHA1 Message Date
ea19dfcbe6 fix: 修复 waveform_trace 工具执行失败和类型错误
- 修复 waveform_trace 工具因 stderr 输出导致的误判失败
   - 修复 messageHandler onQuestion 回调的类型签名错误
2026-03-05 17:25:29 +08:00
fa55e32153 feat: 支持 AskUserQuestion 多问题和多选功能
- 新增 QuestionItem 类型支持单个问题配置(question/options/multiSelect)
   - AskUserEvent 改为 questions 数组支持多问题
   - AnswerRequest 新增 answers 字段支持多问题答案提交
   - 前端渲染支持单选按钮(radio)和多选复选框(checkbox)
   - 答案格式:{\"0\": [\"选项1\"], \"1\": [\"选项A\", \"选项B\"]}
   - 保持向后兼容旧的单问题格式
2026-03-05 16:58:59 +08:00
f6b1f5c45a 1.0.11 2026-03-05 10:39:30 +08:00
1f9a1822c9 fix: 修复打包后图片资源无法加载的问题
- 配置 webpack copy-webpack-plugin 将 src/assets 复制到 dist/assets
- 更新所有图片引用路径从 src/assets 改为 dist/assets
- 修改 localResourceRoots 配置以允许访问 dist/assets
2026-03-05 10:38:40 +08:00
63015c6bbc fix:排除 PUBLISH.md,避免敏感信息被打包 2026-03-05 09:34:44 +08:00
24b30df992 fix: 排除 docs 目录避免中文文件名打包冲突 2026-03-05 09:24:27 +08:00
67b1003831 style:将宁德时代欢迎弹窗换成全企业的 2026-03-04 22:19:14 +08:00
00d37bdaf0 1.0.9 2026-03-04 21:08:08 +08:00
c5fcb1427e Update:README.md 2026-03-04 21:07:50 +08:00
9118ebd662 feat:企业欢迎弹窗优化 2026-03-04 18:58:18 +08:00
15 changed files with 284 additions and 142 deletions

View File

@ -16,6 +16,10 @@ out/test/**
# 依赖 # 依赖
node_modules/** node_modules/**
# 文档(避免中文文件名打包问题)
docs/**
PUBLISH.md
# 只排除 waveform_trace 的 src/dist 目录 # 只排除 waveform_trace 的 src/dist 目录
tools/waveform_trace/src/dist/** tools/waveform_trace/src/dist/**

View File

@ -2,6 +2,19 @@
所有重要的项目变更都将记录在此文件中。 所有重要的项目变更都将记录在此文件中。
## [1.0.9] - 2026-03-04
### 优化
- 将工具折叠图标颜色从蓝色改为灰色
- 统一使用蓝色主题色
- 优化打包配置,排除重复的 exe 文件
### 修复
- 修复代码变更继续对话查找不到之前的代码变更信息的 bug
- 修复对话展示两遍的问题
## [1.0.8] - 2026-03-03 ## [1.0.8] - 2026-03-03
### 新增 ### 新增

View File

@ -2,7 +2,7 @@
"name": "iccoder", "name": "iccoder",
"displayName": "IC Coder: Agentic Verilog Platform", "displayName": "IC Coder: Agentic Verilog Platform",
"description": "Agentic Verilog Coding Platform for Real-World FPGAs", "description": "Agentic Verilog Coding Platform for Real-World FPGAs",
"version": "1.0.8", "version": "1.0.11",
"publisher": "ICCoderAgenticVerilogPlatform", "publisher": "ICCoderAgenticVerilogPlatform",
"engines": { "engines": {
"vscode": "^1.80.0" "vscode": "^1.80.0"
@ -135,6 +135,7 @@
"@vscode/test-cli": "^0.0.12", "@vscode/test-cli": "^0.0.12",
"@vscode/test-electron": "^2.5.2", "@vscode/test-electron": "^2.5.2",
"@vscode/vsce": "^3.7.1", "@vscode/vsce": "^3.7.1",
"copy-webpack-plugin": "^14.0.0",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"ts-loader": "^9.5.4", "ts-loader": "^9.5.4",
"typescript": "^5.9.3", "typescript": "^5.9.3",

24
pnpm-lock.yaml generated
View File

@ -57,6 +57,9 @@ importers:
'@vscode/vsce': '@vscode/vsce':
specifier: ^3.7.1 specifier: ^3.7.1
version: 3.7.1 version: 3.7.1
copy-webpack-plugin:
specifier: ^14.0.0
version: 14.0.0(webpack@5.103.0)
eslint: eslint:
specifier: ^9.39.1 specifier: ^9.39.1
version: 9.39.1 version: 9.39.1
@ -823,6 +826,12 @@ packages:
convert-source-map@2.0.0: convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
copy-webpack-plugin@14.0.0:
resolution: {integrity: sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==}
engines: {node: '>= 20.9.0'}
peerDependencies:
webpack: ^5.1.0
core-util-is@1.0.3: core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@ -1928,6 +1937,10 @@ packages:
serialize-javascript@6.0.2: serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
serialize-javascript@7.0.4:
resolution: {integrity: sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==}
engines: {node: '>=20.0.0'}
setimmediate@1.0.5: setimmediate@1.0.5:
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
@ -3297,6 +3310,15 @@ snapshots:
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
copy-webpack-plugin@14.0.0(webpack@5.103.0):
dependencies:
glob-parent: 6.0.2
normalize-path: 3.0.0
schema-utils: 4.3.3
serialize-javascript: 7.0.4
tinyglobby: 0.2.15
webpack: 5.103.0(webpack-cli@6.0.1)
core-util-is@1.0.3: {} core-util-is@1.0.3: {}
cross-spawn@7.0.6: cross-spawn@7.0.6:
@ -4431,6 +4453,8 @@ snapshots:
dependencies: dependencies:
randombytes: 2.1.0 randombytes: 2.1.0
serialize-javascript@7.0.4: {}
setimmediate@1.0.5: {} setimmediate@1.0.5: {}
shallow-clone@3.0.1: shallow-clone@3.0.1:

View File

@ -53,7 +53,7 @@ function getTierIconUri(
const iconUri = webview.asWebviewUri( const iconUri = webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"titleIcon", "titleIcon",
iconFile, iconFile,
@ -138,7 +138,7 @@ export async function showICHelperPanel(
retainContextWhenHidden: true, retainContextWhenHidden: true,
localResourceRoots: [ localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, "media"), vscode.Uri.joinPath(context.extensionUri, "media"),
vscode.Uri.joinPath(context.extensionUri, "src", "assets"), vscode.Uri.joinPath(context.extensionUri, "dist", "assets"),
], ],
}, },
); );
@ -166,7 +166,7 @@ export async function showICHelperPanel(
const autoIconUri = panel.webview.asWebviewUri( const autoIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"model", "model",
"Auto.png", "Auto.png",
@ -175,7 +175,7 @@ export async function showICHelperPanel(
const liteIconUri = panel.webview.asWebviewUri( const liteIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"model", "model",
"lite.png", "lite.png",
@ -184,7 +184,7 @@ export async function showICHelperPanel(
const syIconUri = panel.webview.asWebviewUri( const syIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"model", "model",
"Sy.png", "Sy.png",
@ -193,7 +193,7 @@ export async function showICHelperPanel(
const maxIconUri = panel.webview.asWebviewUri( const maxIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"model", "model",
"Max.png", "Max.png",
@ -204,7 +204,7 @@ export async function showICHelperPanel(
const qrCodeUri = panel.webview.asWebviewUri( const qrCodeUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"QRCode", "QRCode",
"wx.png", "wx.png",
@ -436,6 +436,7 @@ export async function showICHelperPanel(
message.askId, message.askId,
message.selected, message.selected,
message.customInput, message.customInput,
message.answers
); );
break; break;
// 新增:中止对话 // 新增:中止对话

View File

@ -96,7 +96,7 @@ export interface DialogCallbacks {
summary: string summary: string
) => void; ) => void;
/** 显示问题ask_user */ /** 显示问题ask_user */
onQuestion?: (askId: string, question: string, options: string[]) => void; onQuestion?: (askId: string, questions: import("../types/api").QuestionItem[]) => void;
/** 实时更新段落(流式过程中) */ /** 实时更新段落(流式过程中) */
onSegmentUpdate?: (segments: MessageSegment[]) => void; onSegmentUpdate?: (segments: MessageSegment[]) => void;
/** 对话完成,返回所有段落 */ /** 对话完成,返回所有段落 */
@ -673,7 +673,7 @@ export class DialogSession {
options: ["确认执行", "取消"], options: ["确认执行", "取消"],
multiSelect: false multiSelect: false
}] }]
}, } as AskUserEvent,
this.taskId this.taskId
); );
@ -727,7 +727,7 @@ export class DialogSession {
}; };
try { try {
await userInteractionManager.handleAskUser( await userInteractionManager.handleAskUser(
planEvent, planEvent as AskUserEvent,
this.taskId this.taskId
); );
} catch (error) { } catch (error) {
@ -866,7 +866,10 @@ export class DialogSession {
askId: data.askId, askId: data.askId,
questions: data.questions, questions: data.questions,
}); });
// 实时发送段落更新(包含问题)
callbacks.onSegmentUpdate?.(this.segments); callbacks.onSegmentUpdate?.(this.segments);
// 同时调用 onQuestion 用于更新状态栏等
callbacks.onQuestion?.(data.askId, data.questions);
try { try {
await userInteractionManager.handleAskUser(data, this.taskId); await userInteractionManager.handleAskUser(data, this.taskId);
} catch (error) { } catch (error) {
@ -1113,15 +1116,18 @@ export class DialogSession {
*/ */
async submitAnswer( async submitAnswer(
askId: string, askId: string,
answers?: { [key: string]: string[] },
selected?: string[], selected?: string[],
customInput?: string customInput?: string,
answers?: { [questionIndex: string]: string[] }
): Promise<void> { ): Promise<void> {
// 直接调用 receiveAnswer传递 taskId 作为 fallbackTaskId
// 如果 pendingQuestions 中有问题,走正常流程
// 如果没有receiveAnswer 会使用 fallbackTaskId 直接发送到后端
await userInteractionManager.receiveAnswer( await userInteractionManager.receiveAnswer(
askId, askId,
answers,
selected, selected,
customInput, customInput,
answers,
this.taskId this.taskId
); );
} }

View File

@ -48,6 +48,10 @@ export class UserInteractionManager {
console.log(`[UserInteraction] 收到问题: askId=${askId}, count=${questions.length}`); console.log(`[UserInteraction] 收到问题: askId=${askId}, count=${questions.length}`);
// 注意:问题显示已经通过 dialogService 的 onSegmentUpdate 统一处理
// 这里不再单独发送 showQuestion 命令,避免重复显示
// 创建 Promise 等待用户回答
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.pendingQuestions.set(askId, { this.pendingQuestions.set(askId, {
askId, askId,
@ -61,6 +65,7 @@ export class UserInteractionManager {
reject reject
}); });
// 设置超时2小时
setTimeout(() => { setTimeout(() => {
if (this.pendingQuestions.has(askId)) { if (this.pendingQuestions.has(askId)) {
this.pendingQuestions.delete(askId); this.pendingQuestions.delete(askId);
@ -73,34 +78,37 @@ export class UserInteractionManager {
/** /**
* 处理用户提交的回答(从 WebView 调用) * 处理用户提交的回答(从 WebView 调用)
* @param askId 问题ID * @param askId 问题ID
* @param selected 选中的选项(旧格式)
* @param customInput 自定义输入(旧格式)
* @param answers 新格式:按问题索引的答案 * @param answers 新格式:按问题索引的答案
* @param selected 旧格式:选中的选项 * @param fallbackTaskId 当问题不存在时使用的 taskId用于直接发送到后端
* @param customInput 旧格式:自定义输入
* @param fallbackTaskId 当问题不存在时使用的 taskId
*/ */
async receiveAnswer( async receiveAnswer(
askId: string, askId: string,
answers?: { [key: string]: string[] },
selected?: string[], selected?: string[],
customInput?: string, customInput?: string,
answers?: { [questionIndex: string]: string[] },
fallbackTaskId?: string fallbackTaskId?: string
): Promise<void> { ): Promise<void> {
const pending = this.pendingQuestions.get(askId); const pending = this.pendingQuestions.get(askId);
// 构建答案字符串 // 构建答案字符串
let answer = ''; let answer = '';
if (answers) { if (answers && Object.keys(answers).length > 0) {
// 新格式:多问题答案
answer = Object.entries(answers) answer = Object.entries(answers)
.sort(([a], [b]) => parseInt(a) - parseInt(b)) .sort(([a], [b]) => parseInt(a) - parseInt(b))
.map(([_, vals]) => vals.join('; ')) .map(([_, vals]) => vals.join('; '))
.join(' | '); .join(' | ');
} else { } else {
// 旧格式:单问题答案
answer = customInput || selected?.join(', ') || ''; answer = customInput || selected?.join(', ') || '';
} }
if (!pending) { if (!pending) {
// 问题不存在(可能是页面刷新或会话切换后),尝试直接发送到后端
if (fallbackTaskId) { if (fallbackTaskId) {
console.log(`[UserInteraction] 问题不在 pendingQuestions 中,直接发送到后端: askId=${askId}`); console.log(`[UserInteraction] 问题不在 pendingQuestions 中,直接发送到后端: askId=${askId}, taskId=${fallbackTaskId}`);
await this.submitUserAnswer(askId, fallbackTaskId, answer, answers); await this.submitUserAnswer(askId, fallbackTaskId, answer, answers);
} else { } else {
console.warn(`[UserInteraction] 问题不存在且无 fallbackTaskId: askId=${askId}`); console.warn(`[UserInteraction] 问题不存在且无 fallbackTaskId: askId=${askId}`);
@ -109,7 +117,11 @@ export class UserInteractionManager {
} }
console.log(`[UserInteraction] 收到用户回答: askId=${askId}, answer=${answer}`); console.log(`[UserInteraction] 收到用户回答: askId=${askId}, answer=${answer}`);
// 移除待处理问题
this.pendingQuestions.delete(askId); this.pendingQuestions.delete(askId);
// 触发 resolve
pending.resolve(answer); pending.resolve(answer);
} }
@ -120,7 +132,7 @@ export class UserInteractionManager {
askId: string, askId: string,
taskId: string, taskId: string,
answer: string, answer: string,
answers?: { [key: string]: string[] } answers?: { [questionIndex: string]: string[] }
): Promise<void> { ): Promise<void> {
// 检查是否是工具确认类型的问题 // 检查是否是工具确认类型的问题
if (askId.startsWith('tool_confirm_')) { if (askId.startsWith('tool_confirm_')) {

View File

@ -361,8 +361,8 @@ export interface AnswerRequest {
selected?: string[]; selected?: string[];
/** 自定义输入内容(旧格式,兼容) */ /** 自定义输入内容(旧格式,兼容) */
customInput?: string; customInput?: string;
/** 新格式:按问题索引的答案key 为问题下标 "0","1"value 为选中项列表 */ /** 新格式:按问题索引的答案 */
answers?: { [key: string]: string[] }; answers?: { [questionIndex: string]: string[] };
} }
/** 用户回答响应 */ /** 用户回答响应 */

View File

@ -324,7 +324,7 @@ async function handleUserMessageWithBackend(
// 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新 // 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新
}, },
onQuestion: (askId, question, options) => { onQuestion: (askId: string, questions: import("../types/api").QuestionItem[]) => {
// 只更新状态栏,问题显示由 onSegmentUpdate 统一处理 // 只更新状态栏,问题显示由 onSegmentUpdate 统一处理
panel.webview.postMessage({ panel.webview.postMessage({
command: "updateStatus", command: "updateStatus",
@ -468,12 +468,12 @@ async function handleUserMessageWithBackend(
*/ */
export async function handleUserAnswer( export async function handleUserAnswer(
askId: string, askId: string,
answers?: { [key: string]: string[] },
selected?: string[], selected?: string[],
customInput?: string customInput?: string,
answers?: { [questionIndex: string]: string[] }
): Promise<void> { ): Promise<void> {
if (currentSession) { if (currentSession) {
await currentSession.submitAnswer(askId, answers, selected, customInput); await currentSession.submitAnswer(askId, selected, customInput, answers);
} }
} }

View File

@ -92,7 +92,8 @@ export async function executeWaveformTrace(
child.on('close', (code: number | null) => { child.on('close', (code: number | null) => {
if (code === 0) { if (code === 0) {
resolve(stdout); // 成功时返回 stdout忽略 stderr 中的进度信息
resolve(stdout || stderr);
} else { } else {
reject(new Error( reject(new Error(
`waveform_trace 执行失败 (code=${code}):\n${stderr || stdout}` `waveform_trace 执行失败 (code=${code}):\n${stderr || stdout}`

View File

@ -28,7 +28,7 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
retainContextWhenHidden: true, retainContextWhenHidden: true,
localResourceRoots: [ localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, "media"), vscode.Uri.joinPath(context.extensionUri, "media"),
vscode.Uri.joinPath(context.extensionUri, "src", "assets") vscode.Uri.joinPath(context.extensionUri, "dist", "assets")
], ],
} }
); );
@ -47,21 +47,21 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
// 获取模型图标URI // 获取模型图标URI
const autoIconUri = panel.webview.asWebviewUri( const autoIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Auto.png") vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "Auto.png")
); );
const liteIconUri = panel.webview.asWebviewUri( const liteIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "lite.png") vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "lite.png")
); );
const syIconUri = panel.webview.asWebviewUri( const syIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Sy.png") vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "Sy.png")
); );
const maxIconUri = panel.webview.asWebviewUri( const maxIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Max.png") vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "Max.png")
); );
// 获取二维码图片URI // 获取二维码图片URI
const qrCodeUri = panel.webview.asWebviewUri( const qrCodeUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "QRCode", "wx.png") vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "QRCode", "wx.png")
); );
// 获取Logo URI // 获取Logo URI
@ -128,17 +128,9 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
case "submitAnswer": case "submitAnswer":
handleUserAnswer( handleUserAnswer(
message.askId, message.askId,
message.answers,
message.selected, message.selected,
message.customInput message.customInput,
); message.answers
break;
case "userAnswer":
handleUserAnswer(
message.askId,
message.answers,
message.selected,
message.customInput
); );
break; break;
// 新增:中止对话 // 新增:中止对话

View File

@ -1188,56 +1188,60 @@ export function getMessageAreaScript(): string {
} else if (segment.type === 'question') { } else if (segment.type === 'question') {
segmentDiv.className += ' segment-question'; segmentDiv.className += ' segment-question';
// 兼容旧格式:如果有 segment.question转换为 questions 数组
const questions = segment.questions || (segment.question ? [{
question: segment.question,
options: segment.options || [],
multiSelect: false
}] : []);
// 检查是否已回答
const isAnswered = answeredQuestions.has(segment.askId); const isAnswered = answeredQuestions.has(segment.askId);
const selectedAnswer = answeredQuestions.get(segment.askId); const savedAnswers = answeredQuestions.get(segment.askId) || {};
if (isAnswered) { if (isAnswered) {
segmentDiv.classList.add('answered'); segmentDiv.classList.add('answered');
} }
const questions = segment.questions || []; // 渲染多个问题
const questionsHtml = questions.map((q, qIdx) => { const questionsHtml = questions.map((q, qIndex) => {
const inputType = q.multiSelect ? 'checkbox' : 'radio'; const inputType = q.multiSelect ? 'checkbox' : 'radio';
const inputName = 'q_' + segment.askId + '_' + qIdx; const inputName = \`q\${qIndex}\`;
const optionsHtml = q.options.map((opt, optIdx) => { const selectedAnswers = savedAnswers[qIndex] || [];
const escapedOpt = opt.replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return '<label class="question-option">' + const optionsHtml = q.options.map(opt => {
'<input type="' + inputType + '" name="' + inputName + '" value="' + escapedOpt + '" data-q-idx="' + qIdx + '" data-opt-idx="' + optIdx + '" ' + (isAnswered ? 'disabled' : '') + ' />' + const isSelected = selectedAnswers.includes(opt);
'<span>' + opt + '</span>' + return \`<label style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px 0;">
'</label>'; <input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}>
<span>\${opt}</span>
</label>\`;
}).join(''); }).join('');
return '<div class="question-item">' +
'<div class="question-text">' + formatText(q.question) + '</div>' + return \`
'<div class="question-options">' + optionsHtml + '</div>' + <div class="question-item" data-question-index="\${qIndex}" style="margin-bottom:12px;">
'</div>'; <div class="question-text" style="margin-bottom:8px;">\${formatText(q.question)}</div>
<div class="question-options">\${optionsHtml}</div>
</div>
\`;
}).join(''); }).join('');
segmentDiv.innerHTML = questionsHtml + segmentDiv.innerHTML = \`
'<div class="custom-input-container" style="display: ' + (isAnswered ? 'none' : 'flex') + ';">' + \${questionsHtml}
'<button class="custom-submit">提交答案</button>' + <button class="custom-submit" style="display:\${isAnswered ? 'none' : 'block'};margin-top:8px;padding:8px 16px;background:var(--vscode-button-background);color:var(--vscode-button-foreground);border:none;border-radius:6px;cursor:pointer;">提交答案</button>
'</div>'; \`;
// 只在未回答时添加事件监听
if (!isAnswered) { if (!isAnswered) {
setTimeout(() => { setTimeout(() => {
const submitBtn = segmentDiv.querySelector('.custom-submit'); const submitBtn = segmentDiv.querySelector('.custom-submit');
if (submitBtn) { if (submitBtn) {
submitBtn.addEventListener('click', function() { submitBtn.addEventListener('click', function() {
const answers = {}; const answers = {};
questions.forEach((q, qIdx) => { questions.forEach((q, qIndex) => {
const inputs = segmentDiv.querySelectorAll('input[data-q-idx="' + qIdx + '"]:checked'); const inputs = segmentDiv.querySelectorAll(\`input[name="q\${qIndex}"]:checked\`);
answers[qIdx] = Array.from(inputs).map(inp => (inp as HTMLInputElement).value); answers[qIndex] = Array.from(inputs).map(input => input.value);
}); });
vscode.postMessage({ handleMultiQuestionAnswer(segment.askId, answers, segmentDiv);
command: 'userAnswer',
askId: segment.askId,
answers: answers,
taskId: currentTaskId
});
answeredQuestions.set(segment.askId, JSON.stringify(answers));
segmentDiv.classList.add('answered');
segmentDiv.querySelectorAll('input').forEach(inp => (inp as HTMLInputElement).disabled = true);
const container = segmentDiv.querySelector('.custom-input-container') as HTMLElement;
if (container) container.style.display = 'none';
}); });
} }
}, 0); }, 0);
@ -1438,15 +1442,14 @@ export function getMessageAreaScript(): string {
}, 0); }, 0);
} }
} else if (segment.type === 'question') { } else if (segment.type === 'question') {
const questions = segment.questions || []; segmentDiv.innerHTML = \`
const questionsHtml = questions.map(q => { <div class="question-segment">
const optionsHtml = q.options.map(opt => '<span class="question-opt">' + opt + '</span>').join(''); <div class="question-text">\${formatText(segment.question || '')}</div>
return '<div class="question-item">' + <div class="question-options">
'<div class="question-text">' + formatText(q.question) + '</div>' + \${(segment.options || []).map(opt => \`<span class="question-opt">\${opt}</span>\`).join('')}
'<div class="question-options">' + optionsHtml + '</div>' + </div>
'</div>'; </div>
}).join(''); \`;
segmentDiv.innerHTML = '<div class="question-segment">' + questionsHtml + '</div>';
} else if (segment.type === 'agent') { } else if (segment.type === 'agent') {
// 智能体卡片渲染 // 智能体卡片渲染
renderAgentCard(segment, segmentDiv); renderAgentCard(segment, segmentDiv);
@ -1716,6 +1719,34 @@ export function getMessageAreaScript(): string {
}); });
} }
// 处理多问题答案提交
function handleMultiQuestionAnswer(askId, answers, segmentDiv) {
console.log('[WebView] 多问题答案提交:', askId, answers);
// 保存答案到 Map 中
answeredQuestions.set(askId, answers);
// 标记问题已回答
segmentDiv.classList.add('answered');
// 禁用所有输入
const inputs = segmentDiv.querySelectorAll('input');
inputs.forEach(input => input.disabled = true);
// 隐藏提交按钮
const submitBtn = segmentDiv.querySelector('.custom-submit');
if (submitBtn) {
submitBtn.style.display = 'none';
}
// 发送答案到后端
vscode.postMessage({
command: 'submitAnswer',
askId: askId,
answers: answers
});
}
${getWaveformPreviewScript()} ${getWaveformPreviewScript()}
${getCodeHighlightScript()} ${getCodeHighlightScript()}

View File

@ -18,7 +18,7 @@ export function getNdtWelcomeModalContent(logoUri?: string): string {
<div class="ndt-welcome-modal-header"> <div class="ndt-welcome-modal-header">
<div class="ndt-welcome-icon">🎉</div> <div class="ndt-welcome-icon">🎉</div>
<h2>欢迎宁德时代新能源科技股份有限公司<span style="white-space: nowrap;">的各位专家</span>使用 IC Coder</h2> <h2>欢迎企业<span style="white-space: nowrap;">的各位专家</span>使用 IC Coder</h2>
</div> </div>
<div class="ndt-welcome-modal-body"> <div class="ndt-welcome-modal-body">

View File

@ -19,40 +19,41 @@ export function getWelcomeModalContent(logoUri?: string): string {
<div class="welcome-modal-header"> <div class="welcome-modal-header">
<div class="welcome-icon">🎉</div> <div class="welcome-icon">🎉</div>
<h2>欢迎使用 IC Coder</h2> <h2>欢迎使用 IC Coder</h2>
<p class="welcome-modal-subtitle">您已成功激活 15 天试用期,让我们开始探索 IC Coder 的强大功能吧!</p>
</div> </div>
<div class="welcome-modal-body"> <div class="welcome-modal-body">
<div class="welcome-step"> <!-- 试用期提示 -->
<div class="welcome-step-icon">📝</div> <div class="trial-banner">
<div class="welcome-step-content"> <span>您已获得 <strong>5 天企业版试用期</strong>企业版试用期内Credits用量无限并可无限制使用所有功能</span>
<h3>步骤 1打开聊天面板</h3> </div>
<p>点击侧边栏的 IC Coder 图标,或使用命令面板搜索 "IC Coder: Open Chat"</p>
<!-- IC Coder 简介 -->
<div class="intro-section">
<h3 class="section-title">关于 IC Coder</h3>
<p class="intro-text">IC Coder是一款The Agentic AI Verilog Coding Platform自主式人工智能 Verilog 编码平台。我们采用全球顶尖的IC Coder自研芯片设计微调模型为代码生成提供强大的AI能力支撑。</p>
<div class="features">
<div class="feature-item">
<span class="feature-text">多智能体架构Multi-Agent System多个专业化AI智能体协同工作分别负责架构设计、代码生成、验证测试等不同环节</span>
</div>
<div class="feature-item">
<span class="feature-text">增强上下文引擎:智能理解和管理大规模设计上下文,确保生成代码的一致性和准确性</span>
</div>
<div class="feature-item">
<span class="feature-text">AI自主仿真IC Coder提供完全自动化的仿真验证流程无需手动编写测试代码</span>
</div>
</div> </div>
</div> </div>
<div class="welcome-step"> <!-- 按钮组 -->
<div class="welcome-step-icon">💬</div> <div class="button-group">
<div class="welcome-step-content"> <button id="welcomeStartBtn" class="welcome-btn welcome-btn-primary">
<h3>步骤 2输入您的需求</h3> <span>开始使用</span>
<p>描述您想要生成的 Verilog 代码或需要帮助的问题AI 将为您提供专业的解决方案</p> <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
</div> <path d="M6 3L11 8L6 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div> </div>
<div class="welcome-step">
<div class="welcome-step-icon">🔬</div>
<div class="welcome-step-content">
<h3>步骤 3运行仿真</h3>
<p>使用 "生成 VCD" 命令运行 iverilog 仿真,并通过波形查看器查看仿真结果</p>
</div>
</div>
<button id="welcomeStartBtn" class="welcome-btn welcome-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> </div>
</div> </div>
@ -96,7 +97,7 @@ export function getWelcomeModalStyles(): string {
border: 1px solid var(--vscode-widget-border); border: 1px solid var(--vscode-widget-border);
border-radius: 12px; border-radius: 12px;
width: 100%; width: 100%;
max-width: 500px; max-width: 480px;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5); box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
animation: modalSlideIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1); animation: modalSlideIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
overflow: hidden; overflow: hidden;
@ -124,9 +125,10 @@ export function getWelcomeModalStyles(): string {
.welcome-modal-header h2 { .welcome-modal-header h2 {
margin: 0 0 12px; margin: 0 0 12px;
font-size: 24px; font-size: 20px;
font-weight: 600; font-weight: 600;
color: var(--vscode-foreground); color: var(--vscode-foreground);
line-height: 1.4;
} }
.welcome-modal-subtitle { .welcome-modal-subtitle {
@ -140,37 +142,70 @@ export function getWelcomeModalStyles(): string {
padding: 0 32px 32px; padding: 0 32px 32px;
} }
.welcome-step { /* 试用期横幅 */
.trial-banner {
display: flex; display: flex;
gap: 16px; align-items: center;
margin: 20px 0; justify-content: center;
padding: 16px; padding: 12px 16px;
background: var(--vscode-editor-inactiveSelectionBackground); background: var(--vscode-editor-inactiveSelectionBackground);
border-radius: 8px; border-radius: 8px;
border-left: 4px solid var(--vscode-textLink-foreground); margin-bottom: 20px;
}
.welcome-step-icon {
font-size: 24px;
flex-shrink: 0;
}
.welcome-step-content h3 {
margin: 0 0 8px;
font-size: 15px;
font-weight: 600;
color: var(--vscode-textLink-foreground);
}
.welcome-step-content p {
margin: 0;
font-size: 13px; font-size: 13px;
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
line-height: 1.5; border-left: 3px solid var(--vscode-textLink-foreground);
}
.trial-banner strong {
color: var(--vscode-textLink-foreground);
font-weight: 600;
}
/* IC Coder 简介区域 */
.intro-section {
margin-bottom: 24px;
}
.section-title {
margin: 0 0 12px;
font-size: 15px;
font-weight: 600;
color: var(--vscode-foreground);
}
.intro-text {
margin: 0 0 16px;
font-size: 13px;
color: var(--vscode-descriptionForeground);
line-height: 1.6;
}
.features {
display: flex;
flex-direction: column;
gap: 10px;
}
.feature-item {
padding: 10px 12px;
background: var(--vscode-editor-inactiveSelectionBackground);
border-radius: 6px;
font-size: 13px;
color: var(--vscode-foreground);
border-left: 2px solid var(--vscode-textLink-foreground);
}
.feature-text {
display: block;
}
/* 按钮组 */
.button-group {
display: flex;
} }
.welcome-btn { .welcome-btn {
width: 100%; flex: 1;
padding: 12px 16px; padding: 12px 16px;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
@ -181,18 +216,32 @@ export function getWelcomeModalStyles(): string {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 8px; gap: 8px;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
transition: all 0.2s; transition: all 0.2s;
margin-top: 24px;
} }
.welcome-btn:hover { .welcome-btn-primary {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
.welcome-btn-primary:hover {
background: var(--vscode-button-hoverBackground); background: var(--vscode-button-hoverBackground);
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,0.2); box-shadow: 0 2px 8px rgba(0,0,0,0.2);
} }
.welcome-btn-secondary {
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border: 1px solid var(--vscode-button-border);
}
.welcome-btn-secondary:hover {
background: var(--vscode-button-secondaryHoverBackground);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.welcome-btn:active { .welcome-btn:active {
transform: translateY(0); transform: translateY(0);
} }

View File

@ -3,6 +3,7 @@
'use strict'; 'use strict';
const path = require('path'); const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
//@ts-check //@ts-check
/** @typedef {import('webpack').Configuration} WebpackConfig **/ /** @typedef {import('webpack').Configuration} WebpackConfig **/
@ -45,5 +46,12 @@ const extensionConfig = {
infrastructureLogging: { infrastructureLogging: {
level: "log", // enables logging required for problem matchers level: "log", // enables logging required for problem matchers
}, },
plugins: [
new CopyWebpackPlugin({
patterns: [
{ from: 'src/assets', to: 'assets' }
]
})
]
}; };
module.exports = [ extensionConfig ]; module.exports = [ extensionConfig ];