734 lines
23 KiB
TypeScript
734 lines
23 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; }
|
||
.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: 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);
|
||
}
|
||
|
||
/* 阶段进度条样式 */
|
||
.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 || []) : '';
|
||
|
||
// 选项按钮
|
||
const options = ['确认执行', '修改计划', '取消'];
|
||
const optionsHtml = options.map(opt => {
|
||
const isSelected = isAnswered && opt === selectedAnswer;
|
||
return \`<button class="question-option\${isSelected ? ' selected' : ''}" data-option="\${opt}">\${opt}</button>\`;
|
||
}).join('');
|
||
|
||
// 渲染 Markdown 格式的摘要
|
||
const summaryHtml = renderPlanMarkdown(segment.planSummary || '');
|
||
|
||
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">
|
||
<div class="question-options" data-ask-id="\${segment.askId}">\${optionsHtml}</div>
|
||
<div class="custom-input-container" style="display: \${isAnswered ? 'none' : 'flex'};">
|
||
<input type="text" class="custom-input" placeholder="输入修改建议..." />
|
||
<button class="custom-submit">提交</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
\`;
|
||
|
||
// 只在未回答时添加事件监听
|
||
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,
|
||
model: getCurrentModel()
|
||
});
|
||
});
|
||
});
|
||
|
||
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';
|
||
|
||
// 判断是否有 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">
|
||
<button class="plan-btn plan-btn-confirm" data-action="confirm">确认执行</button>
|
||
<button class="plan-btn plan-btn-modify" data-action="modify">修改计划</button>
|
||
<button class="plan-btn plan-btn-cancel" data-action="cancel">取消</button>
|
||
</div>
|
||
</div>
|
||
\`;
|
||
|
||
// 绑定按钮事件
|
||
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,
|
||
model: getCurrentModel()
|
||
});
|
||
});
|
||
});
|
||
}
|
||
}, 0);
|
||
}
|
||
`;
|
||
}
|