style:对话界面的样式优化
- 代码高亮 - 间距调整 - 工具调用的样式调整
This commit is contained in:
237
src/components/codeHighlight.ts
Normal file
237
src/components/codeHighlight.ts
Normal 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();
|
||||
}
|
||||
`;
|
||||
}
|
||||
@ -172,15 +172,8 @@ export function getAgentCardScript(): string {
|
||||
const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄';
|
||||
const displayName = getAgentToolDisplayName(step.toolName);
|
||||
const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : '';
|
||||
// 为技术性工具调用添加低调样式(用户看不懂的)
|
||||
const lowProfileTools = [
|
||||
'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';
|
||||
// 所有工具调用都使用低调样式
|
||||
const stepClass = 'agent-step low-profile';
|
||||
return \`<div class="\${stepClass}"><span class="step-icon">\${icon}</span><span class="step-name">\${displayName}</span><span class="step-result">\${result}</span></div>\`;
|
||||
}).join('');
|
||||
|
||||
|
||||
@ -30,6 +30,10 @@ import {
|
||||
} from "./waveformPreviewContent";
|
||||
import { getAgentCardStyles, getAgentCardScript } from "./agentCard";
|
||||
import { getPlanCardStyles, getPlanCardScript } from "./planCard";
|
||||
import {
|
||||
getCodeHighlightStyles,
|
||||
getCodeHighlightScript,
|
||||
} from "../components/codeHighlight";
|
||||
|
||||
/**
|
||||
* 获取消息区域的 HTML 内容
|
||||
@ -294,7 +298,7 @@ export function getMessageAreaStyles(): string {
|
||||
padding: 0;
|
||||
}
|
||||
.message-segment {
|
||||
padding: 10px 22px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.segment-text {
|
||||
line-height: 1.6;
|
||||
@ -319,37 +323,14 @@ export function getMessageAreaStyles(): string {
|
||||
.segment-text h3 {
|
||||
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 ol {
|
||||
margin: 8px 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
.segment-text li {
|
||||
margin: 4px 0;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
line-height: 0.4;
|
||||
}
|
||||
.segment-text strong {
|
||||
font-weight: 600;
|
||||
@ -375,7 +356,7 @@ export function getMessageAreaStyles(): string {
|
||||
}
|
||||
/* 低调显示的工具调用 - 移除边距和背景 */
|
||||
.segment-tool.low-profile {
|
||||
margin: 2px 0;
|
||||
margin: 2px 20px;
|
||||
padding: 0;
|
||||
background: none;
|
||||
}
|
||||
@ -541,7 +522,7 @@ export function getMessageAreaStyles(): string {
|
||||
/* 低调显示的工具调用样式 */
|
||||
.segment-tool.low-profile .tool-segment-header {
|
||||
opacity: 0.65;
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.segment-tool.low-profile .tool-segment-icon {
|
||||
opacity: 0.55;
|
||||
@ -642,6 +623,8 @@ export function getMessageAreaStyles(): string {
|
||||
|
||||
${getPlanCardStyles()}
|
||||
|
||||
${getCodeHighlightStyles()}
|
||||
|
||||
${getWaveformPreviewContent()}
|
||||
`;
|
||||
}
|
||||
@ -1008,17 +991,8 @@ export function getMessageAreaScript(): string {
|
||||
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 toolResult = segment.toolResult || '';
|
||||
@ -1270,17 +1244,8 @@ export function getMessageAreaScript(): string {
|
||||
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 toolResult = segment.toolResult || '';
|
||||
@ -1406,21 +1371,41 @@ export function getMessageAreaScript(): string {
|
||||
function formatText(text) {
|
||||
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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
// 不再手动高亮,让 highlight.js 处理
|
||||
const placeholder = \`___CODE_BLOCK_\${codeBlocks.length}___\`;
|
||||
codeBlocks.push('<pre><code class="language-' + language + '">' + escapedCode + '</code></pre>');
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// 提取行内代码(避免被转义)
|
||||
const inlineCodes = [];
|
||||
html = html.replace(/\`([^\`]+)\`/g, function(match, code) {
|
||||
const escapedCode = code
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
const placeholder = \`___INLINE_CODE_\${inlineCodes.length}___\`;
|
||||
inlineCodes.push('<code>' + escapedCode + '</code>');
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// 转义其他 HTML 特殊字符
|
||||
html = html
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
|
||||
// 处理代码块(三个反引号包裹的代码)
|
||||
html = html.replace(/\`\`\`(\\w+)?\\n([\\s\\S]*?)\`\`\`/g, function(match, lang, code) {
|
||||
const language = lang || 'plaintext';
|
||||
return '<pre><code class="language-' + language + '">' + code.trim() + '</code></pre>';
|
||||
});
|
||||
|
||||
// 处理行内代码(单个反引号包裹)
|
||||
html = html.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
|
||||
|
||||
// 处理标题 ### Title
|
||||
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
||||
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
||||
@ -1442,9 +1427,19 @@ export function getMessageAreaScript(): string {
|
||||
// 处理链接 [text](url)
|
||||
html = html.replace(/\\[([^\\]]+)\\]\\(([^\\)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
|
||||
// 处理换行
|
||||
// 处理换行(在恢复代码块之前)
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1594,5 +1589,7 @@ export function getMessageAreaScript(): string {
|
||||
}
|
||||
|
||||
${getWaveformPreviewScript()}
|
||||
|
||||
${getCodeHighlightScript()}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import {
|
||||
getProgressBarStyles,
|
||||
getProgressBarScript,
|
||||
} from "./progressBar";
|
||||
import { getHighlightJsLinks } from "../components/codeHighlight";
|
||||
import { getCurrentEnv } from "../config/settings";
|
||||
/**
|
||||
* 获取 WebView 面板的 HTML 内容
|
||||
@ -44,6 +45,7 @@ export function getWebviewContent(
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>IC Coder</title>
|
||||
${getHighlightJsLinks()}
|
||||
<style>
|
||||
body {
|
||||
font-family: var(--vscode-font-family);
|
||||
@ -269,7 +271,7 @@ export function getWebviewContent(
|
||||
padding: 0;
|
||||
}
|
||||
.message-segment {
|
||||
padding: 10px 22px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.segment-text {
|
||||
line-height: 1.6;
|
||||
|
||||
Reference in New Issue
Block a user