From e6b6bc3698b424045ad5c30aecc274264b215c6b Mon Sep 17 00:00:00 2001 From: Roe-xin Date: Wed, 31 Dec 2025 09:25:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8A=BD=E5=8F=96=20plan-card=20?= =?UTF-8?q?=E4=B8=BA=E7=8B=AC=E7=AB=8B=E7=BB=84=E4=BB=B6=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改动: 1. 创建独立的 planCard.ts 组件 - 导出 getPlanCardStyles() 和 getPlanCardScript() - 包含 renderPlanCardInSegment 和 renderPlanCardStatic 两个渲染函数 2. 优化 messageArea.ts - 移除原有的 plan-card 内联样式和脚本(约 80 行) - 引入 planCard 组件并调用其函数 - 添加 addRule 和 updateNode 工具映射 3. 优化计划卡片样式 - 调整外边距:segment-plan 从 8px 增加到 12px - 优化内边距:header 12px 16px,body 16px,actions 14px 16px - 改进按钮布局:使用垂直布局,按钮和输入框分行显示 - 增加元素间距:步骤间距 6px,按钮间距 10px - 添加行高 1.5 提升可读性 4. 添加 plannerIconSvg 图标 --- src/constants/toolIcons.ts | 5 + src/views/messageArea.ts | 217 ++--------------------------- src/views/planCard.ts | 273 +++++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+), 207 deletions(-) create mode 100644 src/views/planCard.ts diff --git a/src/constants/toolIcons.ts b/src/constants/toolIcons.ts index 18c4e1e..d7c0edd 100644 --- a/src/constants/toolIcons.ts +++ b/src/constants/toolIcons.ts @@ -76,3 +76,8 @@ export const stopIconSvg = ` */ export const agentIconSvg = ` `; + +/** + * planner 图标 SVG + */ +export const plannerIconSvg = ``; diff --git a/src/views/messageArea.ts b/src/views/messageArea.ts index bf5eef2..37432f6 100644 --- a/src/views/messageArea.ts +++ b/src/views/messageArea.ts @@ -22,6 +22,7 @@ import { getWaveformPreviewScript, } from "./waveformPreviewContent"; import { getAgentCardStyles, getAgentCardScript } from "./agentCard"; +import { getPlanCardStyles, getPlanCardScript } from "./planCard"; /** * 获取消息区域的 HTML 内容 @@ -532,87 +533,7 @@ export function getMessageAreaStyles(): string { ${getAgentCardStyles()} - /* 计划卡片样式 */ - .segment-plan { - margin: 8px 0; - } - .plan-card { - border: 1px solid var(--vscode-input-border); - border-radius: 8px; - overflow: hidden; - background: var(--vscode-editor-background); - } - .plan-header { - display: flex; - align-items: center; - gap: 8px; - padding: 10px 12px; - background: var(--vscode-sideBar-background); - border-bottom: 1px solid var(--vscode-input-border); - } - .plan-icon { - font-size: 18px; - } - .plan-title { - font-weight: 600; - font-size: 14px; - } - .plan-body { - padding: 12px; - } - .plan-summary { - color: var(--vscode-descriptionForeground); - margin-bottom: 10px; - font-size: 13px; - } - .plan-steps { - font-size: 13px; - } - .plan-step { - padding: 6px 8px; - margin-bottom: 4px; - background: var(--vscode-list-hoverBackground); - border-radius: 4px; - } - .plan-step:last-child { - margin-bottom: 0; - } - .step-num { - color: var(--vscode-textLink-foreground); - font-weight: 500; - margin-right: 4px; - } - .plan-actions { - display: flex; - gap: 8px; - padding: 12px; - border-top: 1px solid var(--vscode-input-border); - background: var(--vscode-sideBar-background); - } - .plan-btn { - padding: 6px 16px; - border-radius: 4px; - border: none; - cursor: pointer; - font-size: 12px; - font-weight: 500; - } - .plan-btn-confirm { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - } - .plan-btn-confirm:hover { - background: var(--vscode-button-hoverBackground); - } - .plan-btn-modify { - background: var(--vscode-input-background); - color: var(--vscode-foreground); - border: 1px solid var(--vscode-input-border); - } - .plan-btn-cancel { - background: transparent; - color: var(--vscode-descriptionForeground); - } + ${getPlanCardStyles()} ${getWaveformPreviewContent()} `; @@ -631,6 +552,8 @@ export function getMessageAreaScript(): string { ${getAgentCardScript()} + ${getPlanCardScript()} + // 工具名称映射 function getToolDisplayName(toolName) { const toolNameMap = { @@ -653,6 +576,8 @@ export function getMessageAreaScript(): string { 'addPlan': '已添加计划', 'addEdge': '已添加边', 'showPlan': '已显示计划', + 'addRule': '已添加规则', + 'updateNode': '已更新节点', 'spawnExplorer': '代码探索' }; return toolNameMap[toolName] || toolName; @@ -1026,92 +951,8 @@ export function getMessageAreaScript(): string { // 智能体卡片渲染 renderAgentCard(segment, segmentDiv); } else if (segment.type === 'plan') { - // 计划卡片渲染(类似 askUser) - segmentDiv.className += ' segment-plan'; - - // 检查是否已回答 - const isAnswered = answeredQuestions.has(segment.askId); - const selectedAnswer = answeredQuestions.get(segment.askId); - - if (isAnswered) { - segmentDiv.classList.add('answered'); - } - - const stepsHtml = (segment.planSteps || []).map((step, i) => - \`
\${i + 1}. \${step}
\` - ).join(''); - - // 选项按钮 - const options = ['确认执行', '修改计划', '取消']; - const optionsHtml = options.map(opt => { - const isSelected = isAnswered && opt === selectedAnswer; - return \`\`; - }).join(''); - - segmentDiv.innerHTML = \` -
-
- 📋 - \${segment.planTitle || '执行计划'} -
-
-
\${segment.planSummary || ''}
-
\${stepsHtml}
-
-
-
\${optionsHtml}
-
- - -
-
-
- \`; - - // 只在未回答时添加事件监听 - if (!isAnswered) { - setTimeout(() => { - const optionButtons = segmentDiv.querySelectorAll('.question-option'); - optionButtons.forEach(btn => { - btn.addEventListener('click', function() { - const option = this.getAttribute('data-option'); - // 发送答案到后端 - handleQuestionAnswerInSegment(segment.askId, option, segmentDiv); - // 同时发送 planAction 用于模式切换 - const actionMap = { - '确认执行': 'confirm', - '修改计划': 'modify', - '取消': 'cancel' - }; - vscode.postMessage({ - command: 'planAction', - action: actionMap[option] || option, - planTitle: segment.planTitle - }); - }); - }); - - const submitBtn = segmentDiv.querySelector('.custom-submit'); - const customInput = segmentDiv.querySelector('.custom-input'); - if (submitBtn && customInput) { - submitBtn.addEventListener('click', function() { - const customValue = customInput.value.trim(); - if (customValue) { - handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv); - } - }); - - customInput.addEventListener('keypress', function(e) { - if (e.key === 'Enter') { - const customValue = customInput.value.trim(); - if (customValue) { - handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv); - } - } - }); - } - }, 0); - } + // 计划卡片渲染(使用独立组件) + renderPlanCardInSegment(segment, segmentDiv, answeredQuestions); } currentSegmentedMessage.appendChild(segmentDiv); @@ -1262,46 +1103,8 @@ export function getMessageAreaScript(): string { // 智能体卡片渲染 renderAgentCard(segment, segmentDiv); } else if (segment.type === 'plan') { - // 计划卡片渲染 - segmentDiv.className += ' segment-plan'; - const stepsHtml = (segment.planSteps || []).map((step, i) => - \`
\${i + 1}. \${step}
\` - ).join(''); - - segmentDiv.innerHTML = \` -
-
- 📋 - \${segment.planTitle || '执行计划'} -
-
-
\${segment.planSummary || ''}
-
\${stepsHtml}
-
-
- - - -
-
- \`; - - // 绑定按钮事件 - setTimeout(() => { - const planCard = segmentDiv.querySelector('.plan-card'); - if (planCard) { - planCard.querySelectorAll('.plan-btn').forEach(btn => { - btn.addEventListener('click', (e) => { - const action = e.currentTarget?.dataset?.action; - vscode.postMessage({ - command: 'planAction', - action: action, - planTitle: segment.planTitle - }); - }); - }); - } - }, 0); + // 计划卡片渲染(使用独立组件) + renderPlanCardStatic(segment, segmentDiv); } container.appendChild(segmentDiv); diff --git a/src/views/planCard.ts b/src/views/planCard.ts new file mode 100644 index 0000000..6f2213a --- /dev/null +++ b/src/views/planCard.ts @@ -0,0 +1,273 @@ +/** + * 计划卡片组件 + * + * 功能说明: + * - 显示执行计划的卡片界面 + * - 包含计划标题、摘要和步骤列表 + * - 提供确认执行、修改计划、取消等操作按钮 + */ + +import { plannerIconSvg } from "../constants/toolIcons"; + +/** + * 获取计划卡片的样式 + */ +export function getPlanCardStyles(): string { + return ` + /* 计划卡片样式 */ + .segment-plan { + margin: 12px 0; + } + .plan-card { + border: 1px solid var(--vscode-input-border); + border-radius: 8px; + overflow: hidden; + background: var(--vscode-editor-background); + } + .plan-header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + background: var(--vscode-sideBar-background); + border-bottom: 1px solid var(--vscode-input-border); + } + .plan-icon { + font-size: 18px; + } + .plan-title { + font-weight: 600; + font-size: 14px; + } + .plan-body { + padding: 16px; + } + .plan-summary { + color: var(--vscode-descriptionForeground); + margin-bottom: 12px; + font-size: 13px; + line-height: 1.5; + } + .plan-steps { + font-size: 13px; + } + .plan-step { + padding: 8px 12px; + margin-bottom: 6px; + background: var(--vscode-list-hoverBackground); + border-radius: 4px; + line-height: 1.5; + } + .plan-step:last-child { + margin-bottom: 0; + } + .step-num { + color: var(--vscode-textLink-foreground); + font-weight: 500; + margin-right: 6px; + } + .plan-actions { + display: flex; + flex-direction: column; + gap: 10px; + padding: 14px 16px; + border-top: 1px solid var(--vscode-input-border); + background: var(--vscode-sideBar-background); + } + .plan-actions .question-options { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + .plan-btn { + padding: 8px 18px; + border-radius: 4px; + border: none; + cursor: pointer; + font-size: 12px; + font-weight: 500; + } + .plan-btn-confirm { + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + } + .plan-btn-confirm:hover { + background: var(--vscode-button-hoverBackground); + } + .plan-btn-modify { + background: var(--vscode-input-background); + color: var(--vscode-foreground); + border: 1px solid var(--vscode-input-border); + } + .plan-btn-cancel { + background: transparent; + color: var(--vscode-descriptionForeground); + } + .plan-actions .custom-input-container { + display: flex; + gap: 8px; + width: 100%; + } + .plan-actions .custom-input { + flex: 1; + padding: 8px 12px; + background: var(--vscode-input-background); + color: var(--vscode-input-foreground); + border: 1px solid var(--vscode-input-border); + border-radius: 4px; + font-size: 13px; + } + .plan-actions .custom-submit { + padding: 8px 18px; + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + } + .plan-actions .custom-submit:hover { + background: var(--vscode-button-hoverBackground); + } + `; +} + +/** + * 获取计划卡片的脚本 + */ +export function getPlanCardScript(): string { + return ` + // 渲染计划卡片(在 updateSegmentsRealtime 中使用) + function renderPlanCardInSegment(segment, segmentDiv, answeredQuestions) { + segmentDiv.className += ' segment-plan'; + + // 检查是否已回答 + const isAnswered = answeredQuestions.has(segment.askId); + const selectedAnswer = answeredQuestions.get(segment.askId); + + if (isAnswered) { + segmentDiv.classList.add('answered'); + } + + const stepsHtml = (segment.planSteps || []).map((step, i) => + \`
\${i + 1}. \${step}
\` + ).join(''); + + // 选项按钮 + const options = ['确认执行', '修改计划', '取消']; + const optionsHtml = options.map(opt => { + const isSelected = isAnswered && opt === selectedAnswer; + return \`\`; + }).join(''); + + segmentDiv.innerHTML = \` +
+
+ ${plannerIconSvg} + \${segment.planTitle || '执行计划'} +
+
+
\${segment.planSummary || ''}
+
\${stepsHtml}
+
+
+
\${optionsHtml}
+
+ + +
+
+
+ \`; + + // 只在未回答时添加事件监听 + if (!isAnswered) { + setTimeout(() => { + const optionButtons = segmentDiv.querySelectorAll('.question-option'); + optionButtons.forEach(btn => { + btn.addEventListener('click', function() { + const option = this.getAttribute('data-option'); + // 发送答案到后端 + handleQuestionAnswerInSegment(segment.askId, option, segmentDiv); + // 同时发送 planAction 用于模式切换 + const actionMap = { + '确认执行': 'confirm', + '修改计划': 'modify', + '取消': 'cancel' + }; + vscode.postMessage({ + command: 'planAction', + action: actionMap[option] || option, + planTitle: segment.planTitle + }); + }); + }); + + const submitBtn = segmentDiv.querySelector('.custom-submit'); + const customInput = segmentDiv.querySelector('.custom-input'); + if (submitBtn && customInput) { + submitBtn.addEventListener('click', function() { + const customValue = customInput.value.trim(); + if (customValue) { + handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv); + } + }); + + customInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + const customValue = customInput.value.trim(); + if (customValue) { + handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv); + } + } + }); + } + }, 0); + } + } + + // 渲染计划卡片(在 renderSegments 中使用) + function renderPlanCardStatic(segment, segmentDiv) { + segmentDiv.className += ' segment-plan'; + const stepsHtml = (segment.planSteps || []).map((step, i) => + \`
\${i + 1}. \${step}
\` + ).join(''); + + segmentDiv.innerHTML = \` +
+
+ 📋 + \${segment.planTitle || '执行计划'} +
+
+
\${segment.planSummary || ''}
+
\${stepsHtml}
+
+
+ + + +
+
+ \`; + + // 绑定按钮事件 + setTimeout(() => { + const planCard = segmentDiv.querySelector('.plan-card'); + if (planCard) { + planCard.querySelectorAll('.plan-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + const action = e.currentTarget?.dataset?.action; + vscode.postMessage({ + command: 'planAction', + action: action, + planTitle: segment.planTitle + }); + }); + }); + } + }, 0); + } + `; +}