style:对话界面的样式优化

- 代码高亮
- 间距调整
- 工具调用的样式调整
This commit is contained in:
Roe-xin
2026-01-08 16:10:41 +08:00
parent 5577fe17bb
commit 0bcdc615e3
4 changed files with 301 additions and 72 deletions

View File

@ -0,0 +1,237 @@
/**
* 代码高亮组件
*
* 功能说明:
* - 使用 highlight.js 提供专业的代码语法高亮
* - 支持多种编程语言Verilog, JavaScript, Python 等)
* - 提供行内代码和代码块的不同样式
* - 自动检测语言类型
*/
/**
* 获取 highlight.js 的 CDN 链接
*/
export function getHighlightJsLinks(): string {
return `
<!-- Highlight.js CSS (VS Code Dark+ 主题) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs2015.min.css">
<!-- Highlight.js 核心库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<!-- Verilog 语言支持 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/verilog.min.js"></script>
`;
}
/**
* 获取代码高亮的样式
*/
export function getCodeHighlightStyles(): string {
return `
/* 代码块基础样式 */
.segment-text pre {
background: var(--vscode-textCodeBlock-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
padding: 12px;
overflow-x: auto;
margin: 12px 0;
position: relative;
white-space: pre;
}
.segment-text pre code {
background: transparent !important;
padding: 0;
border: none;
display: block;
line-height: 1.5;
white-space: pre;
font-family: 'Courier New', Consolas, 'Monaco', monospace;
font-size: 0.9em;
}
/* 行内代码样式 */
.segment-text code:not(pre code) {
background: var(--vscode-textCodeBlock-background);
padding: 2px 6px;
border-radius: 3px;
color: var(--vscode-textPreformat-foreground);
border: 1px solid var(--vscode-panel-border);
font-family: 'Courier New', Consolas, 'Monaco', monospace;
font-size: 0.9em;
}
/* 覆盖 highlight.js 的背景色,使用 VSCode 主题色 */
.segment-text pre code.hljs {
background: transparent !important;
padding: 0 !important;
}
/* 代码块语言标签 */
.code-block-wrapper {
position: relative;
margin: 12px 0;
}
.code-language-label {
position: absolute;
top: 8px;
right: 8px;
background: var(--vscode-badge-background);
color: var(--vscode-badge-foreground);
padding: 2px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
opacity: 0.8;
z-index: 1;
}
/* 代码块复制按钮 */
.code-copy-btn {
position: absolute;
top: 8px;
right: 8px;
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border: 1px solid var(--vscode-button-border);
border-radius: 4px;
padding: 4px 8px;
font-size: 11px;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s ease;
z-index: 2;
}
.code-block-wrapper:hover .code-copy-btn {
opacity: 1;
}
.code-copy-btn:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
.code-copy-btn.copied {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
/* 代码块滚动条样式 */
.segment-text pre::-webkit-scrollbar {
height: 8px;
}
.segment-text pre::-webkit-scrollbar-track {
background: var(--vscode-scrollbarSlider-background);
border-radius: 4px;
}
.segment-text pre::-webkit-scrollbar-thumb {
background: var(--vscode-scrollbarSlider-hoverBackground);
border-radius: 4px;
}
.segment-text pre::-webkit-scrollbar-thumb:hover {
background: var(--vscode-scrollbarSlider-activeBackground);
}
`;
}
/**
* 获取代码高亮的脚本
*/
export function getCodeHighlightScript(): string {
return `
/**
* 使用 highlight.js 进行代码高亮
*/
function highlightCodeBlocks() {
// 等待 highlight.js 加载完成
if (typeof hljs === 'undefined') {
setTimeout(highlightCodeBlocks, 100);
return;
}
const codeBlocks = document.querySelectorAll('.segment-text pre code:not(.hljs)');
codeBlocks.forEach((block) => {
hljs.highlightElement(block);
});
}
/**
* 为代码块添加复制按钮
*/
function enhanceCodeBlocks() {
const codeBlocks = document.querySelectorAll('.segment-text pre code');
codeBlocks.forEach((codeElement) => {
const preElement = codeElement.parentElement;
if (!preElement || preElement.classList.contains('enhanced')) {
return;
}
// 标记为已增强,避免重复处理
preElement.classList.add('enhanced');
// 应用语法高亮
if (typeof hljs !== 'undefined' && !codeElement.classList.contains('hljs')) {
hljs.highlightElement(codeElement);
}
// 创建包装器
const wrapper = document.createElement('div');
wrapper.className = 'code-block-wrapper';
preElement.parentNode.insertBefore(wrapper, preElement);
wrapper.appendChild(preElement);
// 添加复制按钮
const copyBtn = document.createElement('button');
copyBtn.className = 'code-copy-btn';
copyBtn.textContent = '复制';
copyBtn.onclick = function() {
const code = codeElement.textContent;
navigator.clipboard.writeText(code).then(() => {
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = '复制';
copyBtn.classList.remove('copied');
}, 2000);
});
};
wrapper.appendChild(copyBtn);
});
}
/**
* 监听 DOM 变化,自动增强新添加的代码块
*/
function observeCodeBlocks() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.addedNodes.length > 0) {
enhanceCodeBlocks();
}
});
});
observer.observe(document.getElementById('messages'), {
childList: true,
subtree: true
});
}
// 初始化代码块增强
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
enhanceCodeBlocks();
observeCodeBlocks();
});
} else {
enhanceCodeBlocks();
observeCodeBlocks();
}
`;
}

View File

@ -172,15 +172,8 @@ export function getAgentCardScript(): string {
const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄'; const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄';
const displayName = getAgentToolDisplayName(step.toolName); const displayName = getAgentToolDisplayName(step.toolName);
const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : ''; const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : '';
// 为技术性工具调用添加低调样式(用户看不懂的) // 所有工具调用都使用低调样式
const lowProfileTools = [ const stepClass = 'agent-step low-profile';
'knowledge_save', 'knowledge_load',
'queryKnowledgeSummary', 'queryRules', 'querySignals',
'setModule', 'addSignal', 'addSignalExample',
'validateKnowledgeGraph', 'addPlan', 'addEdge',
'showPlan', 'spawnExplorer'
];
const stepClass = lowProfileTools.includes(step.toolName) ? 'agent-step low-profile' : 'agent-step';
return \`<div class="\${stepClass}"><span class="step-icon">\${icon}</span><span class="step-name">\${displayName}</span><span class="step-result">\${result}</span></div>\`; return \`<div class="\${stepClass}"><span class="step-icon">\${icon}</span><span class="step-name">\${displayName}</span><span class="step-result">\${result}</span></div>\`;
}).join(''); }).join('');

View File

@ -30,6 +30,10 @@ import {
} from "./waveformPreviewContent"; } from "./waveformPreviewContent";
import { getAgentCardStyles, getAgentCardScript } from "./agentCard"; import { getAgentCardStyles, getAgentCardScript } from "./agentCard";
import { getPlanCardStyles, getPlanCardScript } from "./planCard"; import { getPlanCardStyles, getPlanCardScript } from "./planCard";
import {
getCodeHighlightStyles,
getCodeHighlightScript,
} from "../components/codeHighlight";
/** /**
* 获取消息区域的 HTML 内容 * 获取消息区域的 HTML 内容
@ -294,7 +298,7 @@ export function getMessageAreaStyles(): string {
padding: 0; padding: 0;
} }
.message-segment { .message-segment {
padding: 10px 22px; padding: 10px 20px;
} }
.segment-text { .segment-text {
line-height: 1.6; line-height: 1.6;
@ -319,37 +323,14 @@ export function getMessageAreaStyles(): string {
.segment-text h3 { .segment-text h3 {
font-size: 1.1em; font-size: 1.1em;
} }
.segment-text pre {
background: var(--vscode-textCodeBlock-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
padding: 12px;
overflow-x: auto;
margin: 12px 0;
}
.segment-text code {
font-family: 'Courier New', Consolas, monospace;
font-size: 0.9em;
}
.segment-text pre code {
background: transparent;
padding: 0;
border: none;
}
.segment-text code:not(pre code) {
background: var(--vscode-textCodeBlock-background);
padding: 2px 6px;
border-radius: 3px;
color: var(--vscode-textPreformat-foreground);
}
.segment-text ul, .segment-text ul,
.segment-text ol { .segment-text ol {
margin: 8px 0; margin: 8px 0;
padding-left: 24px; padding-left: 24px;
} }
.segment-text li { .segment-text li {
margin: 4px 0; margin: 0;
line-height: 1.6; line-height: 0.4;
} }
.segment-text strong { .segment-text strong {
font-weight: 600; font-weight: 600;
@ -375,7 +356,7 @@ export function getMessageAreaStyles(): string {
} }
/* 低调显示的工具调用 - 移除边距和背景 */ /* 低调显示的工具调用 - 移除边距和背景 */
.segment-tool.low-profile { .segment-tool.low-profile {
margin: 2px 0; margin: 2px 20px;
padding: 0; padding: 0;
background: none; background: none;
} }
@ -541,7 +522,7 @@ export function getMessageAreaStyles(): string {
/* 低调显示的工具调用样式 */ /* 低调显示的工具调用样式 */
.segment-tool.low-profile .tool-segment-header { .segment-tool.low-profile .tool-segment-header {
opacity: 0.65; opacity: 0.65;
font-size: 11px; font-size: 12px;
} }
.segment-tool.low-profile .tool-segment-icon { .segment-tool.low-profile .tool-segment-icon {
opacity: 0.55; opacity: 0.55;
@ -642,6 +623,8 @@ export function getMessageAreaStyles(): string {
${getPlanCardStyles()} ${getPlanCardStyles()}
${getCodeHighlightStyles()}
${getWaveformPreviewContent()} ${getWaveformPreviewContent()}
`; `;
} }
@ -1008,17 +991,8 @@ export function getMessageAreaScript(): string {
return; return;
} }
// 为技术性工具调用添加低调样式 // 所有工具调用都使用低调样式
const lowProfileTools = [
'knowledge_save', 'knowledge_load',
'queryKnowledgeSummary', 'queryRules', 'querySignals',
'setModule', 'addSignal', 'addSignalExample',
'validateKnowledgeGraph', 'addPlan', 'addEdge',
'showPlan', 'addRule', 'updateNode', 'addStateTransition'
];
if (lowProfileTools.includes(segment.toolName)) {
segmentDiv.className += ' low-profile'; segmentDiv.className += ' low-profile';
}
const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧'; const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧';
const toolResult = segment.toolResult || ''; const toolResult = segment.toolResult || '';
@ -1270,17 +1244,8 @@ export function getMessageAreaScript(): string {
return; return;
} }
// 为技术性工具调用添加低调样式 // 所有工具调用都使用低调样式
const lowProfileTools = [
'knowledge_save', 'knowledge_load',
'queryKnowledgeSummary', 'queryRules', 'querySignals',
'setModule', 'addSignal', 'addSignalExample',
'validateKnowledgeGraph', 'addPlan', 'addEdge',
'showPlan', 'addRule', 'updateNode', 'addStateTransition'
];
if (lowProfileTools.includes(segment.toolName)) {
segmentDiv.className += ' low-profile'; segmentDiv.className += ' low-profile';
}
const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧'; const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧';
const toolResult = segment.toolResult || ''; const toolResult = segment.toolResult || '';
@ -1406,20 +1371,40 @@ export function getMessageAreaScript(): string {
function formatText(text) { function formatText(text) {
if (!text) return ''; if (!text) return '';
// 先转义 HTML 特殊字符 let html = text;
let html = text
// 先提取并处理代码块(避免被转义)
const codeBlocks = [];
html = html.replace(/\`\`\`(\\w+)?\\n([\\s\\S]*?)\`\`\`/g, function(match, lang, code) {
const language = lang || 'plaintext';
// 转义代码内容
const escapedCode = code.trim()
.replace(/&/g, '&amp;') .replace(/&/g, '&amp;')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;'); .replace(/>/g, '&gt;');
// 不再手动高亮,让 highlight.js 处理
// 处理代码块(三个反引号包裹的代码) const placeholder = \`___CODE_BLOCK_\${codeBlocks.length}___\`;
html = html.replace(/\`\`\`(\\w+)?\\n([\\s\\S]*?)\`\`\`/g, function(match, lang, code) { codeBlocks.push('<pre><code class="language-' + language + '">' + escapedCode + '</code></pre>');
const language = lang || 'plaintext'; return placeholder;
return '<pre><code class="language-' + language + '">' + code.trim() + '</code></pre>';
}); });
// 处理行内代码(单个反引号包裹 // 提取行内代码(避免被转义
html = html.replace(/\`([^\`]+)\`/g, '<code>$1</code>'); const inlineCodes = [];
html = html.replace(/\`([^\`]+)\`/g, function(match, code) {
const escapedCode = code
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
const placeholder = \`___INLINE_CODE_\${inlineCodes.length}___\`;
inlineCodes.push('<code>' + escapedCode + '</code>');
return placeholder;
});
// 转义其他 HTML 特殊字符
html = html
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// 处理标题 ### Title // 处理标题 ### Title
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>'); html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
@ -1442,9 +1427,19 @@ export function getMessageAreaScript(): string {
// 处理链接 [text](url) // 处理链接 [text](url)
html = html.replace(/\\[([^\\]]+)\\]\\(([^\\)]+)\\)/g, '<a href="$2" target="_blank">$1</a>'); html = html.replace(/\\[([^\\]]+)\\]\\(([^\\)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
// 处理换行 // 处理换行(在恢复代码块之前)
html = html.replace(/\\n/g, '<br>'); html = html.replace(/\\n/g, '<br>');
// 恢复代码块(在最后恢复,避免被其他处理影响)
codeBlocks.forEach((block, index) => {
html = html.replace(\`___CODE_BLOCK_\${index}___\`, block);
});
// 恢复行内代码
inlineCodes.forEach((code, index) => {
html = html.replace(\`___INLINE_CODE_\${index}___\`, code);
});
return html; return html;
} }
@ -1594,5 +1589,7 @@ export function getMessageAreaScript(): string {
} }
${getWaveformPreviewScript()} ${getWaveformPreviewScript()}
${getCodeHighlightScript()}
`; `;
} }

View File

@ -23,6 +23,7 @@ import {
getProgressBarStyles, getProgressBarStyles,
getProgressBarScript, getProgressBarScript,
} from "./progressBar"; } from "./progressBar";
import { getHighlightJsLinks } from "../components/codeHighlight";
import { getCurrentEnv } from "../config/settings"; import { getCurrentEnv } from "../config/settings";
/** /**
* 获取 WebView 面板的 HTML 内容 * 获取 WebView 面板的 HTML 内容
@ -44,6 +45,7 @@ export function getWebviewContent(
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IC Coder</title> <title>IC Coder</title>
${getHighlightJsLinks()}
<style> <style>
body { body {
font-family: var(--vscode-font-family); font-family: var(--vscode-font-family);
@ -269,7 +271,7 @@ export function getWebviewContent(
padding: 0; padding: 0;
} }
.message-segment { .message-segment {
padding: 10px 22px; padding: 10px 20px;
} }
.segment-text { .segment-text {
line-height: 1.6; line-height: 1.6;