From 554679154974a702d8a4bc5bc937b61fd250a0a1 Mon Sep 17 00:00:00 2001 From: XiaoFeng <117837368+Fzhiyu1@users.noreply.github.com> Date: Fri, 9 Jan 2026 17:02:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Plan=E5=8D=A1=E7=89=87=E6=94=AF?= =?UTF-8?q?=E6=8C=81Markdown=E6=B8=B2=E6=9F=93=E5=92=8C=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E6=AD=A5=E9=AA=A4=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加renderPlanMarkdown函数,支持标题、列表、表格、代码块等 - 添加renderPlanSteps函数,智能解析JSON格式步骤对象 - 步骤显示模块名、描述、输入输出、逻辑等详细信息 - 添加plan-summary和step-details样式 --- src/views/planCard.ts | 195 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 185 insertions(+), 10 deletions(-) diff --git a/src/views/planCard.ts b/src/views/planCard.ts index c7fd3d1..bb8d3f2 100644 --- a/src/views/planCard.ts +++ b/src/views/planCard.ts @@ -4,6 +4,7 @@ * 功能说明: * - 显示执行计划的卡片界面 * - 包含计划标题、摘要和步骤列表 + * - 摘要支持 Markdown 格式渲染 * - 提供确认执行、修改计划、取消等操作按钮 */ @@ -43,11 +44,62 @@ export function getPlanCardStyles(): string { padding: 16px; } .plan-summary { - color: var(--vscode-descriptionForeground); + color: var(--vscode-foreground); margin-bottom: 12px; font-size: 13px; - line-height: 1.5; + 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: 24px; + } + .plan-summary li { margin: 4px 0; } + .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; } @@ -58,6 +110,15 @@ export function getPlanCardStyles(): string { 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; } @@ -158,6 +219,117 @@ export function getPlanCardStyles(): string { */ export function getPlanCardScript(): string { return ` + // 简单的 Markdown 渲染函数 + function renderPlanMarkdown(text) { + if (!text) return ''; + + let html = text; + + // 转义 HTML 特殊字符(保留换行) + html = html.replace(/&/g, '&') + .replace(//g, '>'); + + // 代码块 (\`\`\`code\`\`\`) + html = html.replace(/\\x60\\x60\\x60([\\s\\S]*?)\\x60\\x60\\x60/g, '
$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 = ''; + headers.forEach(h => table += ''); + table += ''; + rows.forEach(row => { + table += ''; + row.forEach(cell => table += ''); + table += ''; + }); + table += '
' + h + '
' + cell + '
'; + return table; + }); + + // 标题 + html = html.replace(/^#### (.+)$/gm, '

$1

'); + html = html.replace(/^### (.+)$/gm, '

$1

'); + html = html.replace(/^## (.+)$/gm, '

$1

'); + html = html.replace(/^# (.+)$/gm, '

$1

'); + + // 粗体和斜体 + html = html.replace(/\\*\\*(.+?)\\*\\*/g, '$1'); + html = html.replace(/\\*(.+?)\\*/g, '$1'); + + // 无序列表 + html = html.replace(/^[\\s]*[-*] (.+)$/gm, '
  • $1
  • '); + html = html.replace(/(
  • .*<\\/li>\\n?)+/g, ''); + + // 有序列表 + html = html.replace(/^[\\s]*\\d+\\. (.+)$/gm, '
  • $1
  • '); + + // 段落(连续的非空行) + html = html.replace(/^(?!<[hupolt]|$)(.+)$/gm, '

    $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 += '

    ' + details.join(' | ') + '
    '; + } + + return '
    ' + content + '
    '; + } + + // 普通字符串 + return '
    ' + step + '
    '; + }).join(''); + } + // 渲染计划卡片(在 updateSegmentsRealtime 中使用) function renderPlanCardInSegment(segment, segmentDiv, answeredQuestions) { segmentDiv.className += ' segment-plan'; @@ -170,9 +342,8 @@ export function getPlanCardScript(): string { segmentDiv.classList.add('answered'); } - const stepsHtml = (segment.planSteps || []).map((step, i) => - \`
    \${step}
    \` - ).join(''); + // 解析并渲染步骤 + const stepsHtml = renderPlanSteps(segment.planSteps || []); // 选项按钮 const options = ['确认执行', '修改计划', '取消']; @@ -181,6 +352,9 @@ export function getPlanCardScript(): string { return \`\`; }).join(''); + // 渲染 Markdown 格式的摘要 + const summaryHtml = renderPlanMarkdown(segment.planSummary || ''); + segmentDiv.innerHTML = \`
    @@ -188,7 +362,7 @@ export function getPlanCardScript(): string { \${segment.planTitle || '执行计划'}
    -
    \${segment.planSummary || ''}
    +
    \${summaryHtml}
    \${stepsHtml}
    @@ -250,9 +424,10 @@ export function getPlanCardScript(): string { // 渲染计划卡片(在 renderSegments 中使用) function renderPlanCardStatic(segment, segmentDiv) { segmentDiv.className += ' segment-plan'; - const stepsHtml = (segment.planSteps || []).map((step, i) => - \`
    \${step}
    \` - ).join(''); + const stepsHtml = renderPlanSteps(segment.planSteps || []); + + // 渲染 Markdown 格式的摘要 + const summaryHtml = renderPlanMarkdown(segment.planSummary || ''); segmentDiv.innerHTML = \`
    @@ -261,7 +436,7 @@ export function getPlanCardScript(): string { \${segment.planTitle || '执行计划'}
    -
    \${segment.planSummary || ''}
    +
    \${summaryHtml}
    \${stepsHtml}