refactor: 重构消息区域模块化架构
- 将 messageArea.ts 拆分为多个独立模块 - 新增 messageRenderer.ts:消息渲染逻辑 - 新增 messageStyles.ts:样式定义 - 新增 questionHandler.ts:问题处理 - 新增 segmentRenderer.ts:分段渲染 - 新增 textFormatter.ts:文本格式化 - 新增 toolHelpers.ts:工具辅助函数
This commit is contained in:
File diff suppressed because it is too large
Load Diff
215
src/views/messageRenderer.ts
Normal file
215
src/views/messageRenderer.ts
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
/**
|
||||||
|
* 消息渲染脚本模块
|
||||||
|
* 功能:消息渲染、滚动控制、工具状态显示
|
||||||
|
* 依赖:toolHelpers, textFormatter, waveformPreviewContent, agentCard, planCard, codeHighlight
|
||||||
|
* 使用场景:webview 中的消息显示逻辑
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { collapseIconSvg } from "../constants/toolIcons";
|
||||||
|
import { getWaveformPreviewScript } from "./waveformPreviewContent";
|
||||||
|
import { getAgentCardScript } from "./agentCard";
|
||||||
|
import { getPlanCardScript } from "./planCard";
|
||||||
|
import { getCodeHighlightScript } from "../components/codeHighlight";
|
||||||
|
|
||||||
|
export function getMessageRendererScript(): string {
|
||||||
|
return `
|
||||||
|
${getAgentCardScript()}
|
||||||
|
${getPlanCardScript()}
|
||||||
|
|
||||||
|
const toolCollapseStates = new Map();
|
||||||
|
let shouldAutoScroll = true;
|
||||||
|
let lastScrollHeight = 0;
|
||||||
|
|
||||||
|
function isUserNearBottom() {
|
||||||
|
const threshold = 50;
|
||||||
|
return messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight < threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
messagesEl.addEventListener('scroll', () => {
|
||||||
|
const isAtBottom = isUserNearBottom();
|
||||||
|
if (isAtBottom) {
|
||||||
|
shouldAutoScroll = true;
|
||||||
|
} else {
|
||||||
|
if (messagesEl.scrollHeight === lastScrollHeight) {
|
||||||
|
shouldAutoScroll = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastScrollHeight = messagesEl.scrollHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
function smartScrollToBottom() {
|
||||||
|
if (shouldAutoScroll) {
|
||||||
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||||||
|
lastScrollHeight = messagesEl.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMessage(text, sender) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = \`message \${sender}-message\`;
|
||||||
|
if (sender === 'bot') {
|
||||||
|
const messageContent = document.createElement('div');
|
||||||
|
messageContent.textContent = text;
|
||||||
|
div.appendChild(messageContent);
|
||||||
|
const actionsDiv = document.createElement('div');
|
||||||
|
actionsDiv.className = 'message-actions';
|
||||||
|
const copyBtn = document.createElement('button');
|
||||||
|
copyBtn.className = 'action-btn';
|
||||||
|
copyBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M761.088 715.3152a38.7072 38.7072 0 0 1 0-77.4144 37.4272 37.4272 0 0 0 37.4272-37.4272V265.0112a37.4272 37.4272 0 0 0-37.4272-37.4272H425.6256a37.4272 37.4272 0 0 0-37.4272 37.4272 38.7072 38.7072 0 1 1-77.4144 0 115.0976 115.0976 0 0 1 114.8416-114.8416h335.4624a115.0976 115.0976 0 0 1 114.8416 114.8416v335.4624a115.0976 115.0976 0 0 1-114.8416 114.8416z" fill="currentColor"/><path d="M589.4656 883.0976H268.1856a121.1392 121.1392 0 0 1-121.2928-121.2928v-322.56a121.1392 121.1392 0 0 1 121.2928-121.344h321.28a121.1392 121.1392 0 0 1 121.2928 121.2928v322.56c1.28 67.1232-54.1696 121.344-121.2928 121.344zM268.1856 395.3152a43.52 43.52 0 0 0-43.8784 43.8784v322.56a43.52 43.52 0 0 0 43.8784 43.8784h321.28a43.52 43.52 0 0 0 43.8784-43.8784v-322.56a43.52 43.52 0 0 0-43.8784-43.8784z" fill="currentColor"/></svg><span class="action-tooltip">复制</span>\`;
|
||||||
|
copyBtn.onclick = () => copyMessage(text, copyBtn);
|
||||||
|
const likeBtn = document.createElement('button');
|
||||||
|
likeBtn.className = 'action-btn';
|
||||||
|
likeBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M923.5 411.2c-28.6-33.9-72.1-53-116.4-51.1h-68c6.4-31.6 10.1-63.9 11.2-96v-0.8c-0.5-60.9-18.7-112-51.2-144-22.6-22.2-50.8-33.7-81.5-33.3-38.3 0-69.1 11.5-91.7 34.2-26.5 26.5-39.9 66.8-39.8 119.6 0.1 40.1-19.4 83.4-52.1 115.9-32 31.8-71.7 49.3-111.8 49.3H295.6c-3 0-6 0.3-8.9 0.8v-1.2H140.8c-39.7 0-72.2 32.5-72.2 72.2v392.9c0 39.7 32.5 72.2 72.2 72.2h146.8v-0.6c2.9 0.4 5.9 0.7 8.9 0.7h464.7c33.3-0.8 65.6-13 91.1-34.4s43.1-51.1 49.6-83.8l52.3-289.1c9.4-43.4-2.1-89.6-30.7-123.5zM147.7 843.7v-344c0-9 7.3-16.3 16.3-16.3h70.4V860H164c-9 0-16.3-7.3-16.3-16.3z m726.4-324.9l-0.2 0.6-51.7 290.3c-6.7 29.1-32.3 50.2-62.2 51.3l-4.9 0.2-0.4 0.3h-440V486h7.3c61.4 0 121-25.7 168.1-72.4 48.6-48.2 76.5-111.7 76.5-174.2-0.1-31.5 4.9-51.8 15.3-62.2 7.4-7.4 18.7-10.8 35.8-10.8h0.2c9-0.1 17.4 3.6 24.9 11 16.3 16.2 25.7 47.3 25.8 85.4-1.2 41.8-7.9 83.3-19.9 123.4l-21.6 54.3h181.5c24.5-0.6 48.2 8.9 65.1 26.1 16.9 17.2 25.3 40.8 23 64.9z" fill="currentColor"/></svg><span class="action-tooltip">点赞</span>\`;
|
||||||
|
likeBtn.onclick = () => toggleLike(likeBtn);
|
||||||
|
const dislikeBtn = document.createElement('button');
|
||||||
|
dislikeBtn.className = 'action-btn';
|
||||||
|
dislikeBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M360 640c60.992 40.107 88 87.381 88 149.333 0 5.611 0.427 12.864 1.579 21.462a174.933 174.933 0 0 0 11.2 42.666c19.584 48.192 60.864 83.008 119.018 85.12 28.843 2.56 60.886-3.584 91.414-25.045 58.709-41.237 81.706-117.248 69.973-230.87h48.15V640v42.667c17.173 0 38.4-2.475 61.823-10.667 66.304-23.125 107.627-84.117 84.928-162.816l-53.674-254.55a213.333 213.333 0 0 0-208.747-169.3H207.019a85.333 85.333 0 0 0-85.078 78.783l-29.546 384A85.333 85.333 0 0 0 177.493 640H360z m-61.333-109.333v24H177.493l29.526-384h91.648v360z m85.333-360h289.664a128 128 0 0 1 125.227 101.589l54.442 258.07c21.334 67.007-64 67.007-64 67.007H640c64 277.334-54.613 256-54.613 256-52.054 0-52.054-64-52.054-64 0-92.8-43.264-167.082-129.77-222.805A42.667 42.667 0 0 1 384 530.667v-360z" fill="currentColor"/></svg><span class="action-tooltip">点踩</span>\`;
|
||||||
|
dislikeBtn.onclick = () => toggleDislike(dislikeBtn);
|
||||||
|
actionsDiv.appendChild(copyBtn);
|
||||||
|
actionsDiv.appendChild(likeBtn);
|
||||||
|
actionsDiv.appendChild(dislikeBtn);
|
||||||
|
div.appendChild(actionsDiv);
|
||||||
|
} else {
|
||||||
|
const parts = text.split(' ');
|
||||||
|
const filePaths = [];
|
||||||
|
const textParts = [];
|
||||||
|
parts.forEach(part => {
|
||||||
|
if (part.includes('/') || part.includes('\\\\') || /\\.[a-zA-Z0-9]+$/.test(part) || /:[0-9]+-[0-9]+$/.test(part)) {
|
||||||
|
filePaths.push(part);
|
||||||
|
} else {
|
||||||
|
textParts.push(part);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (filePaths.length > 0) {
|
||||||
|
div.innerHTML = filePaths.map(fp => window.createFilePathTag ? window.createFilePathTag(fp) : fp).join('') + ' ' + textParts.join(' ');
|
||||||
|
} else {
|
||||||
|
div.textContent = text;
|
||||||
|
}
|
||||||
|
hideHeaderIfNeeded();
|
||||||
|
}
|
||||||
|
messagesEl.appendChild(div);
|
||||||
|
smartScrollToBottom();
|
||||||
|
checkHeaderVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideHeaderIfNeeded() {
|
||||||
|
checkHeaderVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyMessage(text, button) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
const originalHTML = button.innerHTML;
|
||||||
|
button.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474c-6.1-7.7-15.3-12.2-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1 0.4-12.8-6.3-12.8z" fill="currentColor"/></svg>\`;
|
||||||
|
setTimeout(() => {
|
||||||
|
button.innerHTML = originalHTML;
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLike(button) {
|
||||||
|
const isActive = button.classList.contains('active');
|
||||||
|
const parent = button.parentElement;
|
||||||
|
parent.querySelectorAll('.action-btn').forEach(btn => btn.classList.remove('active'));
|
||||||
|
if (!isActive) {
|
||||||
|
button.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDislike(button) {
|
||||||
|
const isActive = button.classList.contains('active');
|
||||||
|
const parent = button.parentElement;
|
||||||
|
parent.querySelectorAll('.action-btn').forEach(btn => btn.classList.remove('active'));
|
||||||
|
if (!isActive) {
|
||||||
|
button.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOrCreateStreamingMessage(text) {
|
||||||
|
hideLoadingIndicator();
|
||||||
|
if (!currentStreamingMessage) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'message bot-message streaming';
|
||||||
|
const messageContent = document.createElement('div');
|
||||||
|
messageContent.className = 'message-content';
|
||||||
|
messageContent.textContent = text;
|
||||||
|
div.appendChild(messageContent);
|
||||||
|
messagesEl.appendChild(div);
|
||||||
|
currentStreamingMessage = div;
|
||||||
|
} else {
|
||||||
|
const messageContent = currentStreamingMessage.querySelector('.message-content');
|
||||||
|
if (messageContent) {
|
||||||
|
messageContent.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
smartScrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalizeStreamingMessage(finalText) {
|
||||||
|
if (currentStreamingMessage) {
|
||||||
|
const messageContent = currentStreamingMessage.querySelector('.message-content');
|
||||||
|
if (messageContent) {
|
||||||
|
messageContent.textContent = finalText;
|
||||||
|
}
|
||||||
|
currentStreamingMessage.classList.remove('streaming');
|
||||||
|
const actionsDiv = document.createElement('div');
|
||||||
|
actionsDiv.className = 'message-actions';
|
||||||
|
const copyBtn = document.createElement('button');
|
||||||
|
copyBtn.className = 'action-btn';
|
||||||
|
copyBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
||||||
|
copyBtn.onclick = () => copyMessage(finalText, copyBtn);
|
||||||
|
actionsDiv.appendChild(copyBtn);
|
||||||
|
currentStreamingMessage.appendChild(actionsDiv);
|
||||||
|
currentStreamingMessage = null;
|
||||||
|
}
|
||||||
|
smartScrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoadingIndicator(text) {
|
||||||
|
hideLoadingIndicator();
|
||||||
|
loadingIndicator = document.createElement('div');
|
||||||
|
loadingIndicator.className = 'message bot-message loading-message';
|
||||||
|
loadingIndicator.innerHTML = \`
|
||||||
|
<div class="loading-dots">
|
||||||
|
<span></span><span></span><span></span>
|
||||||
|
</div>
|
||||||
|
<span class="loading-text">\${text}</span>
|
||||||
|
\`;
|
||||||
|
messagesEl.appendChild(loadingIndicator);
|
||||||
|
smartScrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoadingIndicator() {
|
||||||
|
if (loadingIndicator) {
|
||||||
|
loadingIndicator.remove();
|
||||||
|
loadingIndicator = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToolStatus(toolName, status, detail) {
|
||||||
|
const statusIcons = {
|
||||||
|
start: '🔧',
|
||||||
|
complete: '✅',
|
||||||
|
error: '❌'
|
||||||
|
};
|
||||||
|
const statusTexts = {
|
||||||
|
start: '正在执行',
|
||||||
|
complete: '执行完成',
|
||||||
|
error: '执行失败'
|
||||||
|
};
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = \`message tool-status tool-\${status}\`;
|
||||||
|
div.innerHTML = \`
|
||||||
|
<span class="tool-icon">\${statusIcons[status]}</span>
|
||||||
|
<span class="tool-name">\${getToolDisplayName(toolName)}</span>
|
||||||
|
<span class="tool-status-text">\${statusTexts[status]}</span>
|
||||||
|
\${detail ? \`<div class="tool-detail">\${detail}</div>\` : ''}
|
||||||
|
\`;
|
||||||
|
messagesEl.appendChild(div);
|
||||||
|
smartScrollToBottom();
|
||||||
|
checkHeaderVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
${getWaveformPreviewScript()}
|
||||||
|
${getCodeHighlightScript()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
622
src/views/messageStyles.ts
Normal file
622
src/views/messageStyles.ts
Normal file
@ -0,0 +1,622 @@
|
|||||||
|
/**
|
||||||
|
* 消息样式模块
|
||||||
|
* 功能:提供消息区域的所有 CSS 样式
|
||||||
|
* 依赖:agentCard, planCard, codeHighlight, waveformPreviewContent
|
||||||
|
* 使用场景:webview 样式注入
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getAgentCardStyles } from "./agentCard";
|
||||||
|
import { getPlanCardStyles } from "./planCard";
|
||||||
|
import { getCodeHighlightStyles } from "../components/codeHighlight";
|
||||||
|
import { getWaveformPreviewContent } from "./waveformPreviewContent";
|
||||||
|
|
||||||
|
export function getMessageAreaStyles(): string {
|
||||||
|
return `
|
||||||
|
.messages {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.user-message {
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--vscode-button-secondaryBackground);
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
margin-left: auto;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
.bot-message {
|
||||||
|
padding: 0;
|
||||||
|
text-align: left;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
max-width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.message-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-left: 10px;
|
||||||
|
opacity: 0.85;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
.message-actions:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.action-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.action-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.action-btn svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
.action-btn.active {
|
||||||
|
color: var(--vscode-button-background);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.action-btn .action-tooltip {
|
||||||
|
visibility: hidden;
|
||||||
|
width: auto;
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
padding: 4px 8px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
bottom: 125%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) translateY(5px);
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-size: 12px;white-space: nowrap;pointer-events: none;
|
||||||
|
}
|
||||||
|
.action-btn .action-tooltip::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -5px;
|
||||||
|
border-width: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #1e1e1e transparent transparent transparent;
|
||||||
|
}
|
||||||
|
.action-btn .action-tooltip::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -6px;
|
||||||
|
border-width: 6px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: rgba(255, 255, 255, 0.2) transparent transparent transparent;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
.action-btn:hover .action-tooltip {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.streaming .message-content {
|
||||||
|
border-right: 2px solid var(--vscode-focusBorder);
|
||||||
|
animation: blink 1s infinite;
|
||||||
|
}
|
||||||
|
@keyframes blink {
|
||||||
|
0%, 50% { border-color: var(--vscode-focusBorder); }
|
||||||
|
51%, 100% { border-color: transparent; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-message {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
}
|
||||||
|
.loading-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;}
|
||||||
|
.loading-dots span {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--vscode-focusBorder);
|
||||||
|
animation: loadingDot 1.4s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
.loading-dots span:nth-child(1) { animation-delay: 0s; }
|
||||||
|
.loading-dots span:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
.loading-dots span:nth-child(3) { animation-delay: 0.4s; }
|
||||||
|
@keyframes loadingDot {
|
||||||
|
0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
|
||||||
|
40% { transform: scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
.loading-text {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: 4px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--vscode-textBlockQuote-background);
|
||||||
|
}
|
||||||
|
.tool-status.tool-start {
|
||||||
|
border-left: 3px solid var(--vscode-charts-blue);
|
||||||
|
}
|
||||||
|
.tool-status.tool-complete {
|
||||||
|
border-left: 3px solid var(--vscode-charts-green);
|
||||||
|
}
|
||||||
|
.tool-status.tool-error {
|
||||||
|
border-left: 3px solid var(--vscode-charts-red);
|
||||||
|
}
|
||||||
|
.tool-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.tool-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
.tool-status-text {
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
}
|
||||||
|
.tool-detail {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
max-height: 100px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-message {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.question-text {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.question-options {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.question-option {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #007ACC;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #007ACC;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.question-option:hover {
|
||||||
|
background: #005a9e;
|
||||||
|
border-color: #005a9e;
|
||||||
|
}
|
||||||
|
.question-option.selected {
|
||||||
|
background: #007ACC;
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: #007ACC;
|
||||||
|
}
|
||||||
|
.question-message.answered .question-option:not(.selected) {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.custom-input-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.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: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.custom-submit {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.custom-submit:hover {
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
}
|
||||||
|
.question-message.answered .custom-input-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segmented-message {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.message-segment {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
.segment-text {
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment-text h1,
|
||||||
|
.segment-text h2,
|
||||||
|
.segment-text h3,
|
||||||
|
.question-text h1,
|
||||||
|
.question-text h2,
|
||||||
|
.question-text h3 {
|
||||||
|
margin: 0px 0 -10px 0;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
.segment-text h1,
|
||||||
|
.question-text h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
border-bottom: 1px solid var(--vscode-panel-border);
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.segment-text h2,
|
||||||
|
.question-text h2 {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
.segment-text h3,
|
||||||
|
.question-text h3 {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
.segment-text ul,
|
||||||
|
.segment-text ol,
|
||||||
|
.question-text ul,
|
||||||
|
.question-text ol {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
.segment-text li,
|
||||||
|
.question-text li {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.segment-text strong,
|
||||||
|
.question-text strong {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
.segment-text em,
|
||||||
|
.question-text em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.segment-text a,
|
||||||
|
.question-text a {
|
||||||
|
color: var(--vscode-textLink-foreground);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.segment-text a:hover,
|
||||||
|
.question-text a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.segment-text p,
|
||||||
|
.question-text p {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
.segment-text code,
|
||||||
|
.question-text code {
|
||||||
|
background: var(--vscode-textCodeBlock-background);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: var(--vscode-editor-font-family);
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment-tool {
|
||||||
|
margin: 4px 0;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.segment-tool.low-profile {
|
||||||
|
margin: 25px 0px;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.tool-segment-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tool-segment-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.tool-segment-name {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.tool-segment-result {
|
||||||
|
display: inline;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
margin-left: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
.tool-collapse-icon {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tool-collapse-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.icon-expanded svg path {
|
||||||
|
fill: #007ACC !important;
|
||||||
|
}
|
||||||
|
.tool-segment-header.collapsed .tool-collapse-icon {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
.tool-segment-header:not(.collapsed) .tool-collapse-icon {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
.tool-file-write-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-file-write-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-file-read-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-file-read-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-file-delete-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-file-delete-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-syntax-check-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-syntax-check-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-search-code-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-search-code-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-save-knowledge-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-save-knowledge-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-simulation-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-simulation-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-waveform-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-waveform-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-knowledge-load-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-knowledge-load-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-state-transition-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-state-transition-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-segment-content {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.3s ease;
|
||||||
|
}
|
||||||
|
.tool-segment-content.collapsed {
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
.tool-segment-description {
|
||||||
|
margin: 25px 0 0 0px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.segment-tool.low-profile .tool-segment-header {
|
||||||
|
opacity: 0.65;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.segment-tool.low-profile .tool-segment-icon {
|
||||||
|
opacity: 0.55;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.segment-tool.low-profile .tool-segment-name {
|
||||||
|
font-weight: 300;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.segment-tool.low-profile .tool-segment-result {
|
||||||
|
opacity: 0.7;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.segment-question {
|
||||||
|
background: var(--vscode-textBlockQuote-background);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 12px 35px;
|
||||||
|
border-left: 3px solid var(--vscode-charts-orange);
|
||||||
|
}
|
||||||
|
.segment-question .question-text {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.segment-question .question-options {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.segment-question .question-option {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #3d3f41;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #474747;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.segment-question .question-option:hover {
|
||||||
|
background: #005a9e;
|
||||||
|
border-color: #005a9e;
|
||||||
|
}
|
||||||
|
.segment-question .question-option.selected {
|
||||||
|
background: #007ACC;
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: #007ACC;
|
||||||
|
}
|
||||||
|
.segment-question.answered .question-option:not(.selected) {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.segment-question .custom-input-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.segment-question .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: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-left: -20px;
|
||||||
|
}
|
||||||
|
.segment-question .custom-submit {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.segment-question .custom-submit:hover {
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
}
|
||||||
|
.segment-question.answered .custom-input-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.question-segment .question-text {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.question-segment .question-options {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.question-opt {
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: var(--vscode-button-secondaryBackground);
|
||||||
|
color: var(--vscode-button-secondaryForeground);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;}
|
||||||
|
|
||||||
|
${getAgentCardStyles()}
|
||||||
|
|
||||||
|
${getPlanCardStyles()}
|
||||||
|
|
||||||
|
${getCodeHighlightStyles()}
|
||||||
|
|
||||||
|
${getWaveformPreviewContent()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
118
src/views/questionHandler.ts
Normal file
118
src/views/questionHandler.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* 问题处理脚本模块
|
||||||
|
* 功能:用户问题交互逻辑
|
||||||
|
* 依赖:textFormatter
|
||||||
|
* 使用场景:webview 中的问题回答处理
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getQuestionHandlerScript(): string {
|
||||||
|
return `
|
||||||
|
const answeredQuestions = new Map();
|
||||||
|
|
||||||
|
function handleQuestionAnswer(askId, answer, questionDiv) {
|
||||||
|
console.log('[WebView] 用户选择答案:', askId, answer);
|
||||||
|
questionDiv.classList.add('answered');
|
||||||
|
const options = questionDiv.querySelectorAll('.question-option');
|
||||||
|
options.forEach(opt => {
|
||||||
|
if (opt.textContent === answer) {
|
||||||
|
opt.classList.add('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'submitAnswer',
|
||||||
|
askId: askId,
|
||||||
|
selected: [answer],
|
||||||
|
customInput: answer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleQuestionAnswerInSegment(askId, answer, segmentDiv) {
|
||||||
|
console.log('[WebView] 段落中用户选择答案:', askId, answer);
|
||||||
|
answeredQuestions.set(askId, answer);
|
||||||
|
segmentDiv.classList.add('answered');
|
||||||
|
const options = segmentDiv.querySelectorAll('.question-option');
|
||||||
|
options.forEach(opt => {
|
||||||
|
if (opt.getAttribute('data-option') === answer) {
|
||||||
|
opt.classList.add('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const customContainer = segmentDiv.querySelector('.custom-input-container');
|
||||||
|
if (customContainer) {
|
||||||
|
customContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'submitAnswer',
|
||||||
|
askId: askId,
|
||||||
|
selected: [answer],
|
||||||
|
customInput: answer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMultiQuestionAnswer(askId, answers, segmentDiv) {
|
||||||
|
console.log('[WebView] 多问题答案提交:', askId, answers);
|
||||||
|
answeredQuestions.set(askId, answers);
|
||||||
|
segmentDiv.classList.add('answered');
|
||||||
|
const inputs = segmentDiv.querySelectorAll('input');
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.disabled = true;
|
||||||
|
if (input.checked) {
|
||||||
|
const label = input.closest('.question-option');
|
||||||
|
if (label) {
|
||||||
|
label.classList.add('selected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const submitBtn = segmentDiv.querySelector('.custom-submit');
|
||||||
|
if (submitBtn) {
|
||||||
|
submitBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'submitAnswer',
|
||||||
|
askId: askId,
|
||||||
|
answers: answers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showQuestion(askId, question, options) {
|
||||||
|
console.log('[WebView] showQuestion 被调用:', askId, question, options);
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'message bot-message question-message';
|
||||||
|
div.setAttribute('data-ask-id', askId);
|
||||||
|
const questionText = document.createElement('div');
|
||||||
|
questionText.className = 'question-text';
|
||||||
|
questionText.textContent = question;
|
||||||
|
div.appendChild(questionText);
|
||||||
|
const optionsContainer = document.createElement('div');
|
||||||
|
optionsContainer.className = 'question-options';
|
||||||
|
options.forEach((option, index) => {
|
||||||
|
const optionBtn = document.createElement('button');
|
||||||
|
optionBtn.className = 'question-option';
|
||||||
|
optionBtn.textContent = option;
|
||||||
|
optionBtn.onclick = () => handleQuestionAnswer(askId, option, div);
|
||||||
|
optionsContainer.appendChild(optionBtn);
|
||||||
|
});
|
||||||
|
div.appendChild(optionsContainer);
|
||||||
|
const customContainer = document.createElement('div');
|
||||||
|
customContainer.className = 'custom-input-container';
|
||||||
|
const customInput = document.createElement('input');
|
||||||
|
customInput.type = 'text';
|
||||||
|
customInput.className = 'custom-input';
|
||||||
|
customInput.placeholder = '输入其他答案...';
|
||||||
|
const customSubmit = document.createElement('button');
|
||||||
|
customSubmit.className = 'custom-submit';
|
||||||
|
customSubmit.textContent = '提交';
|
||||||
|
customSubmit.onclick = () => {
|
||||||
|
const customValue = customInput.value.trim();
|
||||||
|
if (customValue) {
|
||||||
|
handleQuestionAnswer(askId, customValue, div);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
customContainer.appendChild(customInput);
|
||||||
|
customContainer.appendChild(customSubmit);
|
||||||
|
div.appendChild(customContainer);
|
||||||
|
messagesEl.appendChild(div);
|
||||||
|
smartScrollToBottom();
|
||||||
|
checkHeaderVisibility();
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
274
src/views/segmentRenderer.ts
Normal file
274
src/views/segmentRenderer.ts
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
/**
|
||||||
|
* 分段消息渲染脚本模块
|
||||||
|
* 功能:实时更新分段消息、工具调用展示
|
||||||
|
* 依赖:toolHelpers, textFormatter, waveformPreviewContent
|
||||||
|
* 使用场景:webview 中的分段消息渲染
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getSegmentRendererScript(): string {
|
||||||
|
return `
|
||||||
|
function updateSegmentsRealtime(segments, isComplete) {
|
||||||
|
if (isComplete && (!segments || segments.length === 0)) {
|
||||||
|
currentSegmentedMessage = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!segments || segments.length === 0) return;
|
||||||
|
|
||||||
|
if (!currentSegmentedMessage) {
|
||||||
|
if (currentStreamingMessage) {
|
||||||
|
currentStreamingMessage.remove();
|
||||||
|
currentStreamingMessage = null;
|
||||||
|
}
|
||||||
|
const toolStatuses = messagesEl.querySelectorAll('.tool-status');
|
||||||
|
toolStatuses.forEach(el => el.remove());
|
||||||
|
const lastSegmented = messagesEl.querySelector('.segmented-message:last-child');
|
||||||
|
if (lastSegmented && !lastSegmented.querySelector('.message-actions')) {
|
||||||
|
currentSegmentedMessage = lastSegmented;
|
||||||
|
} else {
|
||||||
|
currentSegmentedMessage = document.createElement('div');
|
||||||
|
currentSegmentedMessage.className = 'message bot-message segmented-message';
|
||||||
|
messagesEl.appendChild(currentSegmentedMessage);
|
||||||
|
}
|
||||||
|
renderedSegmentCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentSegmentedMessage) {
|
||||||
|
const toolHeaders = currentSegmentedMessage.querySelectorAll('.tool-segment-header[data-collapsible="true"]');
|
||||||
|
toolHeaders.forEach((header, idx) => {
|
||||||
|
const isCollapsed = header.classList.contains('collapsed');
|
||||||
|
toolCollapseStates.set(idx, isCollapsed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSegmentedMessage.innerHTML = '';
|
||||||
|
|
||||||
|
const mergedSegments = [];
|
||||||
|
let i = 0;
|
||||||
|
while (i < segments.length) {
|
||||||
|
const segment = segments[i];
|
||||||
|
if (segment.type === 'tool') {
|
||||||
|
let count = 1;
|
||||||
|
while (i + count < segments.length &&
|
||||||
|
segments[i + count].type === 'tool' &&
|
||||||
|
segments[i + count].toolName === segment.toolName) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
mergedSegments.push({ ...segment, toolCount: count });
|
||||||
|
i += count;
|
||||||
|
} else {
|
||||||
|
mergedSegments.push(segment);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let toolIndex = 0;
|
||||||
|
mergedSegments.forEach((segment, index) => {
|
||||||
|
const segmentDiv = document.createElement('div');
|
||||||
|
segmentDiv.className = 'message-segment segment-' + segment.type;
|
||||||
|
|
||||||
|
if (segment.type === 'text' && segment.content) {
|
||||||
|
segmentDiv.className += ' segment-text';
|
||||||
|
segmentDiv.innerHTML = formatText(segment.content);
|
||||||
|
} else if (segment.type === 'tool') {
|
||||||
|
if (segment.toolName === 'spawnExplorer') return;
|
||||||
|
segmentDiv.className += ' low-profile';
|
||||||
|
const toolResult = segment.toolResult || '';
|
||||||
|
const toolCount = segment.toolCount || 1;
|
||||||
|
const countSuffix = toolCount > 1 ? \` x\${toolCount}\` : '';
|
||||||
|
const toolDescription = segment.toolDescription || '';
|
||||||
|
const shouldCollapse = toolResult && toolResult.length > 60;
|
||||||
|
const savedState = toolCollapseStates.get(toolIndex);
|
||||||
|
const isCollapsed = savedState !== undefined ? savedState : shouldCollapse;
|
||||||
|
const currentToolIndex = toolIndex;
|
||||||
|
toolIndex++;
|
||||||
|
|
||||||
|
segmentDiv.innerHTML = \`
|
||||||
|
<div class="tool-segment-header\${isCollapsed ? ' collapsed' : ''}" data-collapsible="\${shouldCollapse}" data-tool-index="\${currentToolIndex}">
|
||||||
|
\${shouldCollapse ? \`<span class="tool-collapse-icon">\${collapseIconSvg}</span>\` : getToolIcon(segment.toolName)}
|
||||||
|
<span class="tool-segment-name">\${getToolDisplayName(segment.toolName) || '工具'}\${countSuffix}</span>
|
||||||
|
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
|
||||||
|
</div>
|
||||||
|
\${shouldCollapse ? \`<div class="tool-segment-content\${isCollapsed ? ' collapsed' : ''}" style="max-height:\${isCollapsed ? '0' : 'none'}"><span class="tool-segment-result" style="display:block;white-space:pre-wrap;max-width:100%;margin-top:8px;margin-left:18px;">\${toolResult}</span></div>\` : ''}
|
||||||
|
\${toolDescription ? \`<p class="tool-segment-description">\${toolDescription}</p>\` : ''}
|
||||||
|
\`;
|
||||||
|
|
||||||
|
if (segment.toolName === 'simulation' && segment.toolStatus === 'success') {
|
||||||
|
if (typeof createWaveformPreview === 'function') {
|
||||||
|
const vcdPaths = parseMultiVcdPaths(segment.toolResult);
|
||||||
|
if (vcdPaths.length > 0) {
|
||||||
|
vcdPaths.forEach(vcdInfo => {
|
||||||
|
const waveformPreview = createWaveformPreview(vcdInfo.path, vcdInfo.name);
|
||||||
|
segmentDiv.appendChild(waveformPreview);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let vcdPath = segment.vcdFilePath;
|
||||||
|
if (!vcdPath && segment.toolResult) {
|
||||||
|
const match = String(segment.toolResult).match(/(?:路径\\s*[::]\\s*|已生成[::]\\s*)(.+\\.vcd)/);
|
||||||
|
if (match && match[1]) {
|
||||||
|
vcdPath = match[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (vcdPath) {
|
||||||
|
const fileName = segment.fileName || vcdPath.split(/[\\\\\\/]/).pop() || 'waveform.vcd';
|
||||||
|
const waveformPreview = createWaveformPreview(vcdPath, fileName);
|
||||||
|
segmentDiv.appendChild(waveformPreview);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[VCD Preview] createWaveformPreview function not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldCollapse) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const header = segmentDiv.querySelector('.tool-segment-header');
|
||||||
|
const content = segmentDiv.querySelector('.tool-segment-content');
|
||||||
|
if (header && content) {
|
||||||
|
header.addEventListener('click', function() {
|
||||||
|
const isCollapsed = header.classList.contains('collapsed');
|
||||||
|
const toolIdx = parseInt(header.getAttribute('data-tool-index') || '0');
|
||||||
|
if (isCollapsed) {
|
||||||
|
header.classList.remove('collapsed');
|
||||||
|
content.classList.remove('collapsed');
|
||||||
|
content.style.maxHeight = content.scrollHeight + 'px';
|
||||||
|
toolCollapseStates.set(toolIdx, false);
|
||||||
|
} else {
|
||||||
|
header.classList.add('collapsed');
|
||||||
|
content.classList.add('collapsed');
|
||||||
|
content.style.maxHeight = '0';
|
||||||
|
toolCollapseStates.set(toolIdx, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
} else if (segment.type === 'question') {
|
||||||
|
segmentDiv.className += ' segment-question';
|
||||||
|
const questions = segment.questions || (segment.question ? [{
|
||||||
|
question: segment.question,
|
||||||
|
options: segment.options || [],
|
||||||
|
multiSelect: false
|
||||||
|
}] : []);
|
||||||
|
const isAnswered = answeredQuestions.has(segment.askId);
|
||||||
|
const savedAnswers = answeredQuestions.get(segment.askId) || {};
|
||||||
|
if (isAnswered) {
|
||||||
|
segmentDiv.classList.add('answered');
|
||||||
|
}
|
||||||
|
|
||||||
|
const questionsHtml = questions.map((q, qIndex) => {
|
||||||
|
const inputType = q.multiSelect ? 'checkbox' : 'radio';
|
||||||
|
const inputName = \`q\${qIndex}\`;
|
||||||
|
const selectedAnswers = savedAnswers[qIndex] || [];
|
||||||
|
let optionsHtml;
|
||||||
|
if (!q.options || q.options.length === 0) {
|
||||||
|
const savedText = selectedAnswers[0] || '';
|
||||||
|
optionsHtml = \`<textarea class="question-text-input" name="\${inputName}" placeholder="请输入您的答案..." style="width:100%;min-height:80px;padding:8px;border:1px solid var(--vscode-input-border);border-radius:4px;background:var(--vscode-input-background);color:var(--vscode-input-foreground);resize:vertical;" \${isAnswered ? 'disabled' : ''}>\${savedText}</textarea>\`;
|
||||||
|
} else {
|
||||||
|
optionsHtml = q.options.map(opt => {
|
||||||
|
const isSelected = selectedAnswers.includes(opt);
|
||||||
|
return \`<label class="question-option\${isSelected ? ' selected' : ''}" style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:5px 5px 5px 0;">
|
||||||
|
<input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}>
|
||||||
|
<span>\${opt}</span>
|
||||||
|
</label>\`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
return \`
|
||||||
|
<div class="question-item" data-question-index="\${qIndex}" style="margin-bottom:12px;">
|
||||||
|
<div class="question-text" style="margin-bottom:8px;">\${formatText(q.question)}</div>
|
||||||
|
<div class="question-options">\${optionsHtml}</div>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
segmentDiv.innerHTML = \`
|
||||||
|
\${questionsHtml}
|
||||||
|
<button class="custom-submit" style="display:\${isAnswered ? 'none' : 'block'};margin-top:8px;padding:8px 16px;background:var(--vscode-button-background);color:var(--vscode-button-foreground);border:none;border-radius:6px;cursor:pointer;">提交答案</button>
|
||||||
|
\`;
|
||||||
|
|
||||||
|
if (!isAnswered) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const submitBtn = segmentDiv.querySelector('.custom-submit');
|
||||||
|
if (submitBtn) {
|
||||||
|
submitBtn.addEventListener('click', function() {
|
||||||
|
const answers = {};
|
||||||
|
questions.forEach((q, qIndex) => {
|
||||||
|
const textarea = segmentDiv.querySelector(\`textarea[name="q\${qIndex}"]\`);
|
||||||
|
if (textarea) {
|
||||||
|
const value = textarea.value.trim();
|
||||||
|
answers[qIndex] = value ? [value] : [];
|
||||||
|
} else {
|
||||||
|
const inputs = segmentDiv.querySelectorAll(\`input[name="q\${qIndex}"]:checked\`);
|
||||||
|
answers[qIndex] = Array.from(inputs).map(input => input.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
handleMultiQuestionAnswer(segment.askId, answers, segmentDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
} else if (segment.type === 'agent') {
|
||||||
|
renderAgentCard(segment, segmentDiv);
|
||||||
|
} else if (segment.type === 'plan') {
|
||||||
|
renderPlanCardInSegment(segment, segmentDiv, answeredQuestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSegmentedMessage.appendChild(segmentDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isComplete) {
|
||||||
|
console.log('[WebView] 对话完成,添加操作按钮');
|
||||||
|
const actionsDiv = document.createElement('div');
|
||||||
|
actionsDiv.className = 'message-actions';
|
||||||
|
const copyBtn = document.createElement('button');
|
||||||
|
copyBtn.className = 'action-btn';
|
||||||
|
copyBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M761.088 715.3152a38.7072 38.7072 0 0 1 0-77.4144 37.4272 37.4272 0 0 0 37.4272-37.4272V265.0112a37.4272 37.4272 0 0 0-37.4272-37.4272H425.6256a37.4272 37.4272 0 0 0-37.4272 37.4272 38.7072 38.7072 0 1 1-77.4144 0 115.0976 115.0976 0 0 1 114.8416-114.8416h335.4624a115.0976 115.0976 0 0 1 114.8416 114.8416v335.4624a115.0976 115.0976 0 0 1-114.8416 114.8416z" fill="currentColor"/><path d="M589.4656 883.0976H268.1856a121.1392 121.1392 0 0 1-121.2928-121.2928v-322.56a121.1392 121.1392 0 0 1 121.2928-121.344h321.28a121.1392 121.1392 0 0 1 121.2928 121.2928v322.56c1.28 67.1232-54.1696 121.344-121.2928 121.344zM268.1856 395.3152a43.52 43.52 0 0 0-43.8784 43.8784v322.56a43.52 43.52 0 0 0 43.8784 43.8784h321.28a43.52 43.52 0 0 0 43.8784-43.8784v-322.56a43.52 43.52 0 0 0-43.8784-43.8784z" fill="currentColor"/></svg><span class="action-tooltip">复制</span>\`;
|
||||||
|
copyBtn.onclick = () => {
|
||||||
|
const textContent = segments
|
||||||
|
.filter(s => s.type === 'text' && s.content)
|
||||||
|
.map(s => s.content)
|
||||||
|
.join('\\n');
|
||||||
|
copyMessage(textContent, copyBtn);
|
||||||
|
};
|
||||||
|
const likeBtn = document.createElement('button');
|
||||||
|
likeBtn.className = 'action-btn';
|
||||||
|
likeBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M923.5 411.2c-28.6-33.9-72.1-53-116.4-51.1h-68c6.4-31.6 10.1-63.9 11.2-96v-0.8c-0.5-60.9-18.7-112-51.2-144-22.6-22.2-50.8-33.7-81.5-33.3-38.3 0-69.1 11.5-91.7 34.2-26.5 26.5-39.9 66.8-39.8 119.6 0.1 40.1-19.4 83.4-52.1 115.9-32 31.8-71.7 49.3-111.8 49.3H295.6c-3 0-6 0.3-8.9 0.8v-1.2H140.8c-39.7 0-72.2 32.5-72.2 72.2v392.9c0 39.7 32.5 72.2 72.2 72.2h146.8v-0.6c2.9 0.4 5.9 0.7 8.9 0.7h464.7c33.3-0.8 65.6-13 91.1-34.4s43.1-51.1 49.6-83.8l52.3-289.1c9.4-43.4-2.1-89.6-30.7-123.5zM147.7 843.7v-344c0-9 7.3-16.3 16.3-16.3h70.4V860H164c-9 0-16.3-7.3-16.3-16.3z m726.4-324.9l-0.2 0.6-51.7 290.3c-6.7 29.1-32.3 50.2-62.2 51.3l-4.9 0.2-0.4 0.3h-440V486h7.3c61.4 0 121-25.7 168.1-72.4 48.6-48.2 76.5-111.7 76.5-174.2-0.1-31.5 4.9-51.8 15.3-62.2 7.4-7.4 18.7-10.8 35.8-10.8h0.2c9-0.1 17.4 3.6 24.9 11 16.3 16.2 25.7 47.3 25.8 85.4-1.2 41.8-7.9 83.3-19.9 123.4l-21.6 54.3h181.5c24.5-0.6 48.2 8.9 65.1 26.1 16.9 17.2 25.3 40.8 23 64.9z" fill="currentColor"/></svg><span class="action-tooltip">点赞</span>\`;
|
||||||
|
likeBtn.onclick = () => toggleLike(likeBtn);
|
||||||
|
const dislikeBtn = document.createElement('button');
|
||||||
|
dislikeBtn.className = 'action-btn';
|
||||||
|
dislikeBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M360 640c60.992 40.107 88 87.381 88 149.333 0 5.611 0.427 12.864 1.579 21.462a174.933 174.933 0 0 0 11.2 42.666c19.584 48.192 60.864 83.008 119.018 85.12 28.843 2.56 60.886-3.584 91.414-25.045 58.709-41.237 81.706-117.248 69.973-230.87h48.15V640v42.667c17.173 0 38.4-2.475 61.823-10.667 66.304-23.125 107.627-84.117 84.928-162.816l-53.674-254.55a213.333 213.333 0 0 0-208.747-169.3H207.019a85.333 85.333 0 0 0-85.078 78.783l-29.546 384A85.333 85.333 0 0 0 177.493 640H360z m-61.333-109.333v24H177.493l29.526-384h91.648v360z m85.333-360h289.664a128 128 0 0 1 125.227 101.589l54.442 258.07c21.334 67.007-64 67.007-64 67.007H640c64 277.334-54.613 256-54.613 256-52.054 0-52.054-64-52.054-64 0-92.8-43.264-167.082-129.77-222.805A42.667 42.667 0 0 1 384 530.667v-360z" fill="currentColor"/></svg><span class="action-tooltip">点踩</span>\`;
|
||||||
|
dislikeBtn.onclick = () => toggleDislike(dislikeBtn);
|
||||||
|
actionsDiv.appendChild(copyBtn);
|
||||||
|
actionsDiv.appendChild(likeBtn);
|
||||||
|
actionsDiv.appendChild(dislikeBtn);
|
||||||
|
currentSegmentedMessage.appendChild(actionsDiv);
|
||||||
|
currentSegmentedMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
smartScrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSegments(segments) {
|
||||||
|
console.log('[WebView] renderSegments 被调用, segments:', segments);
|
||||||
|
if (!segments || segments.length === 0) {
|
||||||
|
console.log('[WebView] segments 为空,跳过渲染');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentStreamingMessage) {
|
||||||
|
console.log('[WebView] 移除流式消息');
|
||||||
|
currentStreamingMessage.remove();
|
||||||
|
currentStreamingMessage = null;
|
||||||
|
}
|
||||||
|
const toolStatuses = messagesEl.querySelectorAll('.tool-status');
|
||||||
|
console.log('[WebView] 找到工具状态消息数量:', toolStatuses.length);
|
||||||
|
toolStatuses.forEach(el => {
|
||||||
|
console.log('[WebView] 移除工具状态消息:', el.className);
|
||||||
|
el.remove();
|
||||||
|
});
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = 'message bot-message segmented-message';
|
||||||
|
updateSegmentsRealtime(segments, true);
|
||||||
|
messagesEl.appendChild(container);
|
||||||
|
smartScrollToBottom();
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
56
src/views/textFormatter.ts
Normal file
56
src/views/textFormatter.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* 文本格式化模块
|
||||||
|
* 功能:Markdown 文本转 HTML
|
||||||
|
* 依赖:无
|
||||||
|
* 使用场景:消息内容格式化显示
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function formatText(text: string): string {
|
||||||
|
if (!text) return "";
|
||||||
|
|
||||||
|
let html = text;
|
||||||
|
|
||||||
|
const codeBlocks: string[] = [];
|
||||||
|
html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
|
||||||
|
const language = lang || "plaintext";
|
||||||
|
const escapedCode = code
|
||||||
|
.trim()
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">");
|
||||||
|
const placeholder = `___CODE_BLOCK_${codeBlocks.length}___`;
|
||||||
|
codeBlocks.push(`<pre><code class="language-${language}">${escapedCode}</code></pre>`);
|
||||||
|
return placeholder;
|
||||||
|
});
|
||||||
|
|
||||||
|
const inlineCodes: string[] = [];
|
||||||
|
html = html.replace(/`([^`]+)`/g, (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.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
|
||||||
|
html = html.replace(/^### (.+)$/gm, "<h3>$1</h3>");
|
||||||
|
html = html.replace(/^## (.+)$/gm, "<h2>$1</h2>");
|
||||||
|
html = html.replace(/^# (.+)$/gm, "<h1>$1</h1>");
|
||||||
|
html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
||||||
|
html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
|
||||||
|
html = html.replace(/^[\-\*] (.+)$/gm, "<li>$1</li>");
|
||||||
|
html = html.replace(/(<li>.*<\/li>\n?)+/g, "<ul>$&</ul>");
|
||||||
|
html = html.replace(/^\d+\. (.+)$/gm, "<li>$1</li>");
|
||||||
|
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;
|
||||||
|
}
|
||||||
106
src/views/toolHelpers.ts
Normal file
106
src/views/toolHelpers.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* 工具辅助函数模块
|
||||||
|
* 功能:工具图标、名称映射、VCD 路径解析
|
||||||
|
* 依赖:toolIcons
|
||||||
|
* 使用场景:工具调用显示
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
fileWriteIconSvg,
|
||||||
|
fileReadIconSvg,
|
||||||
|
fileDeleteIconSvg,
|
||||||
|
syntaxCheckIconSvg,
|
||||||
|
SearchCode,
|
||||||
|
saveKnowledgeIconSvg,
|
||||||
|
simulationIconSvg,
|
||||||
|
waveformIconSvg,
|
||||||
|
knowledgeLoadIconSvg,
|
||||||
|
stateTransitionIconSvg,
|
||||||
|
userQuestionIconSvg,
|
||||||
|
updateStageIconSvg,
|
||||||
|
successIconSvg,
|
||||||
|
} from "../constants/toolIcons";
|
||||||
|
|
||||||
|
export function getToolIcon(toolName: string): string {
|
||||||
|
const iconMap: Record<string, string> = {
|
||||||
|
file_read: fileReadIconSvg,
|
||||||
|
file_write: fileWriteIconSvg,
|
||||||
|
file_delete: fileDeleteIconSvg,
|
||||||
|
file_list: SearchCode,
|
||||||
|
syntax_check: syntaxCheckIconSvg,
|
||||||
|
simulation: simulationIconSvg,
|
||||||
|
waveform_summary: waveformIconSvg,
|
||||||
|
knowledge_save: saveKnowledgeIconSvg,
|
||||||
|
knowledge_load: knowledgeLoadIconSvg,
|
||||||
|
queryKnowledgeSummary: knowledgeLoadIconSvg,
|
||||||
|
queryRules: knowledgeLoadIconSvg,
|
||||||
|
setModule: fileWriteIconSvg,
|
||||||
|
addSignal: fileWriteIconSvg,
|
||||||
|
addSignalExample: fileWriteIconSvg,
|
||||||
|
validateKnowledgeGraph: syntaxCheckIconSvg,
|
||||||
|
querySignals: SearchCode,
|
||||||
|
addPlan: fileWriteIconSvg,
|
||||||
|
addEdge: fileWriteIconSvg,
|
||||||
|
showPlan: SearchCode,
|
||||||
|
addRule: fileWriteIconSvg,
|
||||||
|
updateNode: fileWriteIconSvg,
|
||||||
|
addStateTransition: stateTransitionIconSvg,
|
||||||
|
askUser: userQuestionIconSvg,
|
||||||
|
updatePhase: updateStageIconSvg,
|
||||||
|
iverilog: successIconSvg,
|
||||||
|
};
|
||||||
|
return iconMap[toolName] || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToolDisplayName(toolName: string): string {
|
||||||
|
const toolNameMap: Record<string, string> = {
|
||||||
|
file_read: "已完成文件读取",
|
||||||
|
file_write: "已完成文件写入",
|
||||||
|
file_delete: "已完成文件删除",
|
||||||
|
file_list: "已检索代码文件",
|
||||||
|
syntax_check: "已完成语法检查",
|
||||||
|
simulation: "已完成仿真",
|
||||||
|
waveform_summary: "已完成波形分析",
|
||||||
|
knowledge_save: "已保存知识库",
|
||||||
|
knowledge_load: "已加载知识库",
|
||||||
|
queryKnowledgeSummary: "已查询知识摘要",
|
||||||
|
queryRules: "已查询规则",
|
||||||
|
setModule: "已设置模块",
|
||||||
|
addSignal: "信号分析完成",
|
||||||
|
addSignalExample: "信号示例处理完成",
|
||||||
|
validateKnowledgeGraph: "已验证知识图谱",
|
||||||
|
querySignals: "已查询信号",
|
||||||
|
addPlan: "已添加计划",
|
||||||
|
addEdge: "已添加边",
|
||||||
|
showPlan: "已显示计划",
|
||||||
|
addRule: "已添加规则",
|
||||||
|
updateNode: "已更新节点",
|
||||||
|
addStateTransition: "已添加状态转换",
|
||||||
|
spawnExplorer: "代码探索",
|
||||||
|
spawnDebugger: "波形调试",
|
||||||
|
askUser: "用户提问",
|
||||||
|
updatePhase: "已更新阶段",
|
||||||
|
iverilog: "已完成编译",
|
||||||
|
};
|
||||||
|
return toolNameMap[toolName] || toolName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseMultiVcdPaths(toolResult: string): Array<{ name: string; path: string }> {
|
||||||
|
if (!toolResult) return [];
|
||||||
|
const result = String(toolResult);
|
||||||
|
|
||||||
|
const vcdListMatch = result.match(/VCD 文件列表:[\s\S]*?(?=\n\n|$)/);
|
||||||
|
if (!vcdListMatch) return [];
|
||||||
|
|
||||||
|
const paths: Array<{ name: string; path: string }> = [];
|
||||||
|
const lineRegex = /- (\w+): ([^\n]+)/g;
|
||||||
|
let match;
|
||||||
|
while ((match = lineRegex.exec(vcdListMatch[0])) !== null) {
|
||||||
|
const name = match[1];
|
||||||
|
const pathOrError = match[2].trim();
|
||||||
|
if (!pathOrError.startsWith("失败")) {
|
||||||
|
paths.push({ name: name + ".vcd", path: pathOrError });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user