/** * 计划卡片组件 * * 功能说明: * - 显示执行计划的卡片界面 * - 包含计划标题、摘要和步骤列表 * - 摘要支持 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, '>'); // 标题(必须在转义之后、其他处理之前) html = html.replace(/^#### (.+)$/gm, '
$1');
// 行内代码 (\`code\`)
html = html.replace(/\\x60([^\\x60]+)\\x60/g, '$1');
// 表格处理
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 = '| ' + h + ' | '); table += '
|---|
| ' + cell + ' | '); table += '
$1
'); // 清理多余的空行 html = html.replace(/<\\/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 = '' + name + ''; 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 += '