794 lines
24 KiB
TypeScript
794 lines
24 KiB
TypeScript
/**
|
||
* 计划卡片组件
|
||
*
|
||
* 功能说明:
|
||
* - 显示执行计划的卡片界面
|
||
* - 包含计划标题、摘要和步骤列表
|
||
* - 摘要支持 Markdown 格式渲染
|
||
* - 提供确认执行、修改计划、取消等操作按钮
|
||
*/
|
||
|
||
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-foreground);
|
||
margin-bottom: 12px;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
}
|
||
/* Markdown 渲染样式 */
|
||
.plan-summary h1, .plan-summary h2, .plan-summary h3, .plan-summary h4 {
|
||
margin: 16px 0 8px 0;
|
||
font-weight: 600;
|
||
color: var(--vscode-foreground);
|
||
}
|
||
.plan-summary h1 { font-size: 18px; border-bottom: 1px solid var(--vscode-input-border); padding-bottom: 6px; }
|
||
.plan-summary h2 { font-size: 16px; }
|
||
.plan-summary h3 { font-size: 14px; }
|
||
.plan-summary h4 { font-size: 13px; }
|
||
.plan-summary p { margin: 8px 0; letter-spacing: 0.5px; }
|
||
.plan-summary ul, .plan-summary ol {
|
||
margin: 8px 0;
|
||
padding-left: 0;
|
||
}
|
||
.plan-summary li { margin: 4px 0 4px 27px; }
|
||
.plan-summary code {
|
||
background: var(--vscode-textCodeBlock-background);
|
||
padding: 2px 6px;
|
||
border-radius: 3px;
|
||
font-family: var(--vscode-editor-font-family);
|
||
font-size: 12px;
|
||
}
|
||
.plan-summary pre {
|
||
background: var(--vscode-textCodeBlock-background);
|
||
padding: 12px;
|
||
border-radius: 4px;
|
||
overflow-x: auto;
|
||
margin: 8px 0;
|
||
}
|
||
.plan-summary pre code {
|
||
background: none;
|
||
padding: 0;
|
||
}
|
||
.plan-summary table {
|
||
border-collapse: collapse;
|
||
width: 100%;
|
||
margin: 8px 0;
|
||
font-size: 12px;
|
||
}
|
||
.plan-summary th, .plan-summary td {
|
||
border: 1px solid var(--vscode-input-border);
|
||
padding: 6px 10px;
|
||
text-align: left;
|
||
}
|
||
.plan-summary th {
|
||
background: var(--vscode-sideBar-background);
|
||
font-weight: 600;
|
||
}
|
||
.plan-summary strong { font-weight: 600; }
|
||
.plan-summary em { font-style: italic; }
|
||
.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 strong {
|
||
color: var(--vscode-textLink-foreground);
|
||
}
|
||
.step-details {
|
||
margin-top: 4px;
|
||
font-size: 12px;
|
||
color: var(--vscode-descriptionForeground);
|
||
line-height: 1.4;
|
||
}
|
||
.plan-step:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
.step-checkbox {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 16px;
|
||
height: 16px;
|
||
margin-right: 8px;
|
||
border: 2px solid var(--vscode-textLink-foreground);
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
flex-shrink: 0;
|
||
opacity: 0.6;
|
||
transition: all 0.2s ease;
|
||
}
|
||
.step-checkbox.completed {
|
||
background: var(--vscode-textLink-foreground);
|
||
border-color: var(--vscode-textLink-foreground);
|
||
opacity: 1;
|
||
}
|
||
.step-checkbox.completed::after {
|
||
content: '✓';
|
||
color: var(--vscode-editor-background);
|
||
font-size: 11px;
|
||
font-weight: bold;
|
||
}
|
||
.plan-actions {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
padding: 14px 16px;
|
||
border-top: 1px solid var(--vscode-input-border);
|
||
background: var(--vscode-sideBar-background);
|
||
}
|
||
.plan-input-row {
|
||
display: flex;
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
.plan-input {
|
||
flex: 1;
|
||
padding: 10px 12px;
|
||
background: var(--vscode-input-background);
|
||
color: var(--vscode-input-foreground);
|
||
border: 1px solid var(--vscode-input-border);
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
box-sizing: border-box;
|
||
}
|
||
.plan-input:focus {
|
||
outline: none;
|
||
border-color: var(--vscode-focusBorder);
|
||
}
|
||
.plan-btn-row {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
.plan-btn {
|
||
padding: 8px 20px;
|
||
border-radius: 4px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
}
|
||
.plan-btn-submit {
|
||
background: var(--vscode-input-background);
|
||
color: var(--vscode-foreground);
|
||
border: 1px solid var(--vscode-input-border);
|
||
}
|
||
.plan-btn-submit:hover {
|
||
background: var(--vscode-list-hoverBackground);
|
||
}
|
||
.plan-btn-confirm {
|
||
background: #007ACC;
|
||
color: #ffffff;
|
||
}
|
||
.plan-btn-confirm:hover {
|
||
background: #005a9e;
|
||
}
|
||
.plan-btn-cancel {
|
||
background: transparent;
|
||
color: var(--vscode-descriptionForeground);
|
||
border: 1px solid var(--vscode-input-border);
|
||
}
|
||
.plan-btn-cancel:hover {
|
||
background: var(--vscode-list-hoverBackground);
|
||
}
|
||
.plan-answered {
|
||
padding: 12px 16px;
|
||
border-top: 1px solid var(--vscode-input-border);
|
||
background: var(--vscode-sideBar-background);
|
||
font-size: 13px;
|
||
}
|
||
.answered-label {
|
||
color: var(--vscode-descriptionForeground);
|
||
}
|
||
.answered-value {
|
||
color: var(--vscode-textLink-foreground);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 阶段进度条样式 */
|
||
.phase-progress {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12px 16px;
|
||
background: var(--vscode-sideBar-background);
|
||
border-bottom: 1px solid var(--vscode-input-border);
|
||
}
|
||
.phase-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 12px;
|
||
color: var(--vscode-descriptionForeground);
|
||
}
|
||
.phase-item.current {
|
||
color: var(--vscode-textLink-foreground);
|
||
font-weight: 600;
|
||
}
|
||
.phase-item.completed {
|
||
color: #4caf50;
|
||
}
|
||
.phase-item.skipped {
|
||
color: var(--vscode-descriptionForeground);
|
||
opacity: 0.6;
|
||
}
|
||
.phase-dot {
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 50%;
|
||
background: var(--vscode-input-border);
|
||
flex-shrink: 0;
|
||
}
|
||
.phase-dot.current {
|
||
background: var(--vscode-textLink-foreground);
|
||
box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.2);
|
||
}
|
||
.phase-dot.completed {
|
||
background: #4caf50;
|
||
}
|
||
.phase-dot.skipped {
|
||
background: var(--vscode-descriptionForeground);
|
||
opacity: 0.5;
|
||
}
|
||
.phase-line {
|
||
flex: 1;
|
||
height: 2px;
|
||
background: var(--vscode-input-border);
|
||
margin: 0 8px;
|
||
}
|
||
.phase-line.completed {
|
||
background: #4caf50;
|
||
}
|
||
|
||
/* 阶段列表样式 */
|
||
.plan-phases {
|
||
font-size: 13px;
|
||
}
|
||
.plan-phase {
|
||
margin-bottom: 12px;
|
||
border: 1px solid var(--vscode-input-border);
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
}
|
||
.plan-phase:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
.phase-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 10px 12px;
|
||
background: var(--vscode-list-hoverBackground);
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.phase-header:hover {
|
||
background: var(--vscode-list-activeSelectionBackground);
|
||
}
|
||
.phase-toggle {
|
||
font-size: 10px;
|
||
color: var(--vscode-descriptionForeground);
|
||
transition: transform 0.2s;
|
||
}
|
||
.phase-toggle.expanded {
|
||
transform: rotate(90deg);
|
||
}
|
||
.phase-name {
|
||
flex: 1;
|
||
font-weight: 500;
|
||
}
|
||
.phase-status {
|
||
font-size: 11px;
|
||
padding: 2px 8px;
|
||
border-radius: 10px;
|
||
background: var(--vscode-badge-background);
|
||
color: var(--vscode-badge-foreground);
|
||
}
|
||
.phase-status.current {
|
||
background: var(--vscode-textLink-foreground);
|
||
color: white;
|
||
}
|
||
.phase-status.skipped {
|
||
background: var(--vscode-descriptionForeground);
|
||
opacity: 0.6;
|
||
}
|
||
.phase-status.completed {
|
||
background: #4caf50;
|
||
color: white;
|
||
}
|
||
.phase-content {
|
||
padding: 0 12px;
|
||
max-height: 0;
|
||
overflow: hidden;
|
||
transition: max-height 0.3s ease, padding 0.3s ease;
|
||
}
|
||
.phase-content.expanded {
|
||
padding: 12px;
|
||
max-height: 500px;
|
||
}
|
||
.phase-reason {
|
||
font-size: 12px;
|
||
color: var(--vscode-descriptionForeground);
|
||
font-style: italic;
|
||
margin-bottom: 8px;
|
||
}
|
||
.phase-steps {
|
||
margin: 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
}
|
||
.phase-step-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
padding: 6px 0;
|
||
border-bottom: 1px solid var(--vscode-input-border);
|
||
}
|
||
.phase-step-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
.phase-step-checkbox {
|
||
width: 14px;
|
||
height: 14px;
|
||
border: 2px solid var(--vscode-textLink-foreground);
|
||
border-radius: 3px;
|
||
flex-shrink: 0;
|
||
margin-top: 2px;
|
||
}
|
||
.phase-step-text {
|
||
flex: 1;
|
||
}
|
||
.phase-step-name {
|
||
font-weight: 500;
|
||
color: var(--vscode-foreground);
|
||
}
|
||
.phase-step-desc {
|
||
font-size: 12px;
|
||
color: var(--vscode-descriptionForeground);
|
||
margin-top: 2px;
|
||
}
|
||
`;
|
||
}
|
||
|
||
/**
|
||
* 获取计划卡片的脚本
|
||
*/
|
||
export function getPlanCardScript(): string {
|
||
return `
|
||
// 简单的 Markdown 渲染函数
|
||
function renderPlanMarkdown(text) {
|
||
if (!text) return '';
|
||
|
||
let html = text;
|
||
|
||
// 转义 HTML 特殊字符(保留换行)
|
||
html = html.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>');
|
||
|
||
// 标题(必须在转义之后、其他处理之前)
|
||
html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>');
|
||
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
||
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
||
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
||
|
||
// 代码块 (\`\`\`code\`\`\`)
|
||
html = html.replace(/\\x60\\x60\\x60([\\s\\S]*?)\\x60\\x60\\x60/g, '<pre><code>$1</code></pre>');
|
||
|
||
// 行内代码 (\`code\`)
|
||
html = html.replace(/\\x60([^\\x60]+)\\x60/g, '<code>$1</code>');
|
||
|
||
// 表格处理
|
||
html = html.replace(/^\\|(.+)\\|\\s*\\n\\|[-:\\s|]+\\|\\s*\\n((?:\\|.+\\|\\s*\\n?)+)/gm, function(match, header, body) {
|
||
const headers = header.split('|').map(h => h.trim()).filter(h => h);
|
||
const rows = body.trim().split('\\n').map(row =>
|
||
row.split('|').map(cell => cell.trim()).filter(cell => cell)
|
||
);
|
||
|
||
let table = '<table><thead><tr>';
|
||
headers.forEach(h => table += '<th>' + h + '</th>');
|
||
table += '</tr></thead><tbody>';
|
||
rows.forEach(row => {
|
||
table += '<tr>';
|
||
row.forEach(cell => table += '<td>' + cell + '</td>');
|
||
table += '</tr>';
|
||
});
|
||
table += '</tbody></table>';
|
||
return table;
|
||
});
|
||
|
||
// 粗体和斜体
|
||
html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');
|
||
html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');
|
||
|
||
// 无序列表
|
||
html = html.replace(/^[\\s]*[-*] (.+)$/gm, '<li>$1</li>');
|
||
html = html.replace(/(<li>.*<\\/li>\\n?)+/g, '<ul>$&</ul>');
|
||
|
||
// 有序列表
|
||
html = html.replace(/^[\\s]*\\d+\\. (.+)$/gm, '<li>$1</li>');
|
||
|
||
// 段落(连续的非空行)
|
||
html = html.replace(/^(?!<[hupolt]|$)(.+)$/gm, '<p>$1</p>');
|
||
|
||
// 清理多余的空行
|
||
html = html.replace(/<p><\\/p>/g, '');
|
||
html = html.replace(/\\n{2,}/g, '\\n');
|
||
|
||
return html;
|
||
}
|
||
|
||
// 解析并渲染步骤列表
|
||
function renderPlanSteps(steps) {
|
||
if (!steps || steps.length === 0) return '';
|
||
|
||
// 尝试解析 JSON 格式的步骤
|
||
let parsedSteps = steps;
|
||
|
||
// 如果是单个字符串且看起来像 JSON 数组,尝试解析
|
||
if (steps.length === 1 && typeof steps[0] === 'string') {
|
||
const str = steps[0].trim();
|
||
if (str.startsWith('[') && str.endsWith(']')) {
|
||
try {
|
||
parsedSteps = JSON.parse(str);
|
||
} catch (e) {
|
||
// 解析失败,保持原样
|
||
}
|
||
}
|
||
}
|
||
|
||
return parsedSteps.map((step, i) => {
|
||
// 如果是对象,格式化显示
|
||
if (typeof step === 'object' && step !== null) {
|
||
const name = step.name || step.id || ('步骤 ' + (i + 1));
|
||
const desc = step.description || '';
|
||
const inputs = step.inputs || '';
|
||
const outputs = step.outputs || '';
|
||
const logic = step.logic || '';
|
||
|
||
let content = '<strong>' + name + '</strong>';
|
||
if (desc) content += ':' + desc;
|
||
|
||
let details = [];
|
||
if (inputs) details.push('输入: ' + inputs);
|
||
if (outputs) details.push('输出: ' + outputs);
|
||
if (logic) details.push('逻辑: ' + logic);
|
||
|
||
if (details.length > 0) {
|
||
content += '<div class="step-details">' + details.join(' | ') + '</div>';
|
||
}
|
||
|
||
return '<div class="plan-step"><span class="step-checkbox"></span>' + content + '</div>';
|
||
}
|
||
|
||
// 普通字符串
|
||
return '<div class="plan-step"><span class="step-checkbox"></span> ' + step + '</div>';
|
||
}).join('');
|
||
}
|
||
|
||
// 渲染阶段进度条
|
||
function renderPhaseProgress(phases) {
|
||
if (!phases || phases.length === 0) return '';
|
||
|
||
const phaseNames = { spec: 'Spec', design: 'Design', sim: 'Sim', done: 'Done' };
|
||
let html = '<div class="phase-progress">';
|
||
|
||
phases.forEach((phase, i) => {
|
||
const name = phaseNames[phase.id] || phase.name || phase.id;
|
||
const status = phase.status || 'pending';
|
||
|
||
html += \`<div class="phase-item \${status}">
|
||
<span class="phase-dot \${status}"></span>
|
||
<span>\${name}</span>
|
||
</div>\`;
|
||
|
||
// 添加连接线(最后一个不加)
|
||
if (i < phases.length - 1) {
|
||
const lineStatus = (status === 'completed' || status === 'skipped') ? 'completed' : '';
|
||
html += \`<div class="phase-line \${lineStatus}"></div>\`;
|
||
}
|
||
});
|
||
|
||
html += '</div>';
|
||
return html;
|
||
}
|
||
|
||
// 渲染阶段列表(两级结构)
|
||
function renderPlanPhases(phases) {
|
||
if (!phases || phases.length === 0) return '';
|
||
|
||
const statusLabels = {
|
||
skipped: '跳过',
|
||
completed: '已完成',
|
||
current: '当前',
|
||
pending: '待执行'
|
||
};
|
||
|
||
return phases.map((phase, i) => {
|
||
const status = phase.status || 'pending';
|
||
const statusLabel = statusLabels[status] || status;
|
||
const isExpanded = status === 'current';
|
||
const hasSteps = phase.steps && phase.steps.length > 0;
|
||
const hasReason = phase.reason && status === 'skipped';
|
||
|
||
let stepsHtml = '';
|
||
if (phase.steps && phase.steps.length > 0) {
|
||
stepsHtml = phase.steps.map(step => \`
|
||
<li class="phase-step-item">
|
||
<span class="phase-step-checkbox"></span>
|
||
<div class="phase-step-text">
|
||
<div class="phase-step-name">\${step.name || ''}</div>
|
||
\${step.description ? \`<div class="phase-step-desc">\${step.description}</div>\` : ''}
|
||
</div>
|
||
</li>
|
||
\`).join('');
|
||
}
|
||
|
||
return \`
|
||
<div class="plan-phase" data-phase-id="\${phase.id}">
|
||
<div class="phase-header" onclick="togglePhase(this)">
|
||
<span class="phase-toggle \${isExpanded ? 'expanded' : ''}">▶</span>
|
||
<span class="phase-name">\${phase.name || phase.id}</span>
|
||
<span class="phase-status \${status}">\${statusLabel}</span>
|
||
</div>
|
||
<div class="phase-content \${isExpanded ? 'expanded' : ''}">
|
||
\${hasReason ? \`<div class="phase-reason">\${phase.reason}</div>\` : ''}
|
||
\${hasSteps ? \`<ul class="phase-steps">\${stepsHtml}</ul>\` : ''}
|
||
\${!hasSteps && !hasReason ? '<div class="phase-reason">暂无步骤</div>' : ''}
|
||
</div>
|
||
</div>
|
||
\`;
|
||
}).join('');
|
||
}
|
||
|
||
// 切换阶段展开/折叠
|
||
function togglePhase(header) {
|
||
const toggle = header.querySelector('.phase-toggle');
|
||
const content = header.nextElementSibling;
|
||
toggle.classList.toggle('expanded');
|
||
content.classList.toggle('expanded');
|
||
}
|
||
|
||
// 渲染计划卡片(在 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');
|
||
}
|
||
|
||
// 判断是否有 phases(新格式)还是 steps(旧格式)
|
||
const hasPhases = segment.planPhases && segment.planPhases.length > 0;
|
||
|
||
// 渲染阶段进度条和阶段列表(新格式)
|
||
const progressHtml = hasPhases ? renderPhaseProgress(segment.planPhases) : '';
|
||
const phasesHtml = hasPhases ? renderPlanPhases(segment.planPhases) : '';
|
||
|
||
// 兼容旧格式:渲染步骤列表
|
||
const stepsHtml = !hasPhases ? renderPlanSteps(segment.planSteps || []) : '';
|
||
|
||
// 渲染 Markdown 格式的摘要
|
||
const summaryHtml = renderPlanMarkdown(segment.planSummary || '');
|
||
|
||
// 已回答时显示用户的选择
|
||
const answeredHtml = isAnswered ? \`
|
||
<div class="plan-answered">
|
||
<span class="answered-label">已回复:</span>
|
||
<span class="answered-value">\${selectedAnswer}</span>
|
||
</div>
|
||
\` : '';
|
||
|
||
segmentDiv.innerHTML = \`
|
||
<div class="plan-card">
|
||
<div class="plan-header">
|
||
<span class="plan-icon">${plannerIconSvg}</span>
|
||
<span class="plan-title">\${segment.planTitle || '执行计划'}</span>
|
||
</div>
|
||
\${progressHtml}
|
||
<div class="plan-body">
|
||
<div class="plan-summary">\${summaryHtml}</div>
|
||
\${hasPhases ? \`<div class="plan-phases">\${phasesHtml}</div>\` : \`<div class="plan-steps">\${stepsHtml}</div>\`}
|
||
</div>
|
||
<div class="plan-actions" data-ask-id="\${segment.askId}" style="display: \${isAnswered ? 'none' : 'flex'};">
|
||
<div class="plan-input-row">
|
||
<input type="text" class="plan-input" placeholder="输入修改建议..." />
|
||
<button class="plan-btn plan-btn-submit">提交修改</button>
|
||
</div>
|
||
<div class="plan-btn-row">
|
||
<button class="plan-btn plan-btn-confirm">确认执行</button>
|
||
<button class="plan-btn plan-btn-cancel">取消</button>
|
||
</div>
|
||
</div>
|
||
\${answeredHtml}
|
||
</div>
|
||
\`;
|
||
|
||
// 只在未回答时添加事件监听
|
||
if (!isAnswered) {
|
||
setTimeout(() => {
|
||
const submitBtn = segmentDiv.querySelector('.plan-btn-submit');
|
||
const confirmBtn = segmentDiv.querySelector('.plan-btn-confirm');
|
||
const cancelBtn = segmentDiv.querySelector('.plan-btn-cancel');
|
||
const planInput = segmentDiv.querySelector('.plan-input');
|
||
|
||
// 提交修改按钮
|
||
if (submitBtn && planInput) {
|
||
submitBtn.addEventListener('click', function() {
|
||
const inputValue = planInput.value.trim();
|
||
if (inputValue) {
|
||
handleQuestionAnswerInSegment(segment.askId, inputValue, segmentDiv);
|
||
}
|
||
});
|
||
|
||
// 回车键提交修改
|
||
planInput.addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
const inputValue = planInput.value.trim();
|
||
if (inputValue) {
|
||
handleQuestionAnswerInSegment(segment.askId, inputValue, segmentDiv);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 确认执行按钮
|
||
if (confirmBtn) {
|
||
confirmBtn.addEventListener('click', function() {
|
||
handleQuestionAnswerInSegment(segment.askId, '确认执行', segmentDiv);
|
||
});
|
||
}
|
||
|
||
// 取消按钮 - 直接中止对话,不发送给智能体
|
||
if (cancelBtn) {
|
||
cancelBtn.addEventListener('click', function() {
|
||
// 标记问题已回答
|
||
answeredQuestions.set(segment.askId, '取消');
|
||
segmentDiv.classList.add('answered');
|
||
|
||
// 隐藏操作按钮
|
||
const actionsDiv = segmentDiv.querySelector('.plan-actions');
|
||
if (actionsDiv) {
|
||
actionsDiv.style.display = 'none';
|
||
}
|
||
|
||
// 发送中止对话命令
|
||
vscode.postMessage({ command: 'abortDialog' });
|
||
});
|
||
}
|
||
}, 0);
|
||
}
|
||
}
|
||
|
||
// 渲染计划卡片(在 renderSegments 中使用)
|
||
function renderPlanCardStatic(segment, segmentDiv) {
|
||
segmentDiv.className += ' segment-plan';
|
||
|
||
// 判断是否有 phases(新格式)还是 steps(旧格式)
|
||
const hasPhases = segment.planPhases && segment.planPhases.length > 0;
|
||
|
||
// 渲染阶段进度条和阶段列表(新格式)
|
||
const progressHtml = hasPhases ? renderPhaseProgress(segment.planPhases) : '';
|
||
const phasesHtml = hasPhases ? renderPlanPhases(segment.planPhases) : '';
|
||
|
||
// 兼容旧格式:渲染步骤列表
|
||
const stepsHtml = !hasPhases ? renderPlanSteps(segment.planSteps || []) : '';
|
||
|
||
// 渲染 Markdown 格式的摘要
|
||
const summaryHtml = renderPlanMarkdown(segment.planSummary || '');
|
||
|
||
segmentDiv.innerHTML = \`
|
||
<div class="plan-card">
|
||
<div class="plan-header">
|
||
<!-- <span class="plan-icon">📋</span> -->
|
||
<span class="plan-title">\${segment.planTitle || '执行计划'}</span>
|
||
</div>
|
||
\${progressHtml}
|
||
<div class="plan-body">
|
||
<div class="plan-summary">\${summaryHtml}</div>
|
||
\${hasPhases ? \`<div class="plan-phases">\${phasesHtml}</div>\` : \`<div class="plan-steps">\${stepsHtml}</div>\`}
|
||
</div>
|
||
<div class="plan-actions" data-ask-id="\${segment.askId}">
|
||
<div class="plan-input-row">
|
||
<input type="text" class="plan-input" placeholder="输入修改建议..." />
|
||
<button class="plan-btn plan-btn-submit">提交修改</button>
|
||
</div>
|
||
<div class="plan-btn-row">
|
||
<button class="plan-btn plan-btn-confirm">确认执行</button>
|
||
<button class="plan-btn plan-btn-cancel">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
\`;
|
||
|
||
// 绑定按钮事件(静态渲染时也需要能响应)
|
||
setTimeout(() => {
|
||
const submitBtn = segmentDiv.querySelector('.plan-btn-submit');
|
||
const confirmBtn = segmentDiv.querySelector('.plan-btn-confirm');
|
||
const cancelBtn = segmentDiv.querySelector('.plan-btn-cancel');
|
||
const planInput = segmentDiv.querySelector('.plan-input');
|
||
|
||
// 提交修改按钮
|
||
if (submitBtn && planInput) {
|
||
submitBtn.addEventListener('click', function() {
|
||
const inputValue = planInput.value.trim();
|
||
if (inputValue) {
|
||
vscode.postMessage({
|
||
command: 'submitAnswer',
|
||
askId: segment.askId,
|
||
selected: [inputValue],
|
||
customInput: inputValue
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// 确认执行按钮
|
||
if (confirmBtn) {
|
||
confirmBtn.addEventListener('click', function() {
|
||
vscode.postMessage({
|
||
command: 'submitAnswer',
|
||
askId: segment.askId,
|
||
selected: ['确认执行'],
|
||
customInput: '确认执行'
|
||
});
|
||
});
|
||
}
|
||
|
||
// 取消按钮 - 直接中止对话
|
||
if (cancelBtn) {
|
||
cancelBtn.addEventListener('click', function() {
|
||
// 隐藏操作按钮
|
||
const actionsDiv = segmentDiv.querySelector('.plan-actions');
|
||
if (actionsDiv) {
|
||
actionsDiv.style.display = 'none';
|
||
}
|
||
// 发送中止对话命令
|
||
vscode.postMessage({ command: 'abortDialog' });
|
||
});
|
||
}
|
||
}, 0);
|
||
}
|
||
`;
|
||
}
|