fex:尝试修复流式显示工具调用不穿插显示的问题

This commit is contained in:
XiaoFeng
2025-12-17 10:03:40 +08:00
parent c21ad95963
commit 6c5d470bad
3 changed files with 305 additions and 41 deletions

View File

@ -681,6 +681,74 @@ export function getWebviewContent(iconUri?: string): string {
display: none;
}
/* 分段消息样式 */
.segmented-message {
padding: 0;
}
.message-segment {
padding: 10px 14px;
}
.segment-text {
line-height: 1.6;
}
.segment-tool {
background: var(--vscode-textBlockQuote-background);
border-radius: 6px;
margin: 8px 0;
padding: 10px 14px;
}
.tool-segment-header {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.tool-segment-icon {
font-size: 14px;
}
.tool-segment-name {
font-weight: 500;
color: var(--vscode-foreground);
}
.tool-segment-result {
margin-top: 6px;
font-size: 12px;
color: var(--vscode-descriptionForeground);
padding-left: 22px;
}
.segment-tool.tool-success {
border-left: 3px solid var(--vscode-charts-green);
}
.segment-tool.tool-error {
border-left: 3px solid var(--vscode-charts-red);
}
.segment-tool.tool-running {
border-left: 3px solid var(--vscode-charts-blue);
}
.segment-question {
background: var(--vscode-textBlockQuote-background);
border-radius: 6px;
margin: 8px 0;
padding: 12px 14px;
border-left: 3px solid var(--vscode-charts-orange);
}
.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;
}
/* 状态栏样式 */
.status-bar {
display: flex;
@ -842,12 +910,15 @@ export function getWebviewContent(iconUri?: string): string {
</div>
<script>
console.log('[WebView] 脚本开始执行');
const vscode = acquireVsCodeApi();
console.log('[WebView] vscode API 已获取');
const messagesEl = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const modeSelect = document.getElementById('modeSelect');
const filePathInput = document.getElementById('filePathInput');
const fileContentEl = document.getElementById('fileContent');
console.log('[WebView] DOM 元素已获取, messagesEl:', !!messagesEl);
const errorMessageEl = document.getElementById('errorMessage');
const fileEditorSection = document.getElementById('fileEditorSection');
const fileEditorTextarea = document.getElementById('fileEditorTextarea');
@ -1147,6 +1218,10 @@ export function getWebviewContent(iconUri?: string): string {
addMessage(message.text, 'bot');
}
break;
case 'receiveSegments':
// 渲染分段消息
renderSegments(message.segments);
break;
case 'updateStreamingMessage':
// 流式更新消息
updateOrCreateStreamingMessage(message.text);
@ -1209,7 +1284,7 @@ export function getWebviewContent(iconUri?: string): string {
messageContent.textContent = text;
div.appendChild(messageContent);
messagesContainer.appendChild(div);
messagesEl.appendChild(div);
currentStreamingMessage = div;
} else {
// 更新现有消息内容
@ -1220,7 +1295,7 @@ export function getWebviewContent(iconUri?: string): string {
}
// 滚动到底部
messagesContainer.scrollTop = messagesContainer.scrollHeight;
messagesEl.scrollTop = messagesEl.scrollHeight;
}
// 完成流式消息
@ -1246,7 +1321,7 @@ export function getWebviewContent(iconUri?: string): string {
currentStreamingMessage = null;
}
messagesContainer.scrollTop = messagesContainer.scrollHeight;
messagesEl.scrollTop = messagesEl.scrollHeight;
}
// 显示加载指示器
@ -1261,8 +1336,8 @@ export function getWebviewContent(iconUri?: string): string {
</div>
<span class="loading-text">\${text}</span>
\`;
messagesContainer.appendChild(loadingIndicator);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
messagesEl.appendChild(loadingIndicator);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
// 隐藏加载指示器
@ -1292,6 +1367,94 @@ export function getWebviewContent(iconUri?: string): string {
}
}
// 渲染分段消息
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';
segments.forEach((segment, index) => {
const segmentDiv = document.createElement('div');
segmentDiv.className = 'message-segment segment-' + segment.type;
if (segment.type === 'text' && segment.content) {
segmentDiv.innerHTML = formatText(segment.content);
} else if (segment.type === 'tool') {
const statusIcon = segment.toolStatus === 'success' ? '✅' :
segment.toolStatus === 'error' ? '❌' : '🔧';
const statusClass = 'tool-' + (segment.toolStatus || 'running');
segmentDiv.className += ' ' + statusClass;
segmentDiv.innerHTML = \`
<div class="tool-segment-header">
<span class="tool-segment-icon">\${statusIcon}</span>
<span class="tool-segment-name">\${segment.toolName || '工具'}</span>
</div>
\${segment.toolResult ? \`<div class="tool-segment-result">\${segment.toolResult}</div>\` : ''}
\`;
} else if (segment.type === 'question') {
segmentDiv.innerHTML = \`
<div class="question-segment">
<div class="question-text">\${segment.question || ''}</div>
<div class="question-options">
\${(segment.options || []).map(opt => \`<span class="question-opt">\${opt}</span>\`).join('')}
</div>
</div>
\`;
}
container.appendChild(segmentDiv);
});
// 添加操作按钮
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 = () => {
const textContent = segments
.filter(s => s.type === 'text' && s.content)
.map(s => s.content)
.join('\\n');
copyMessage(textContent, copyBtn);
};
actionsDiv.appendChild(copyBtn);
container.appendChild(actionsDiv);
messagesEl.appendChild(container);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
// 格式化文本(简单的换行处理)
function formatText(text) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\\n/g, '<br>');
}
// 添加工具状态消息
function addToolStatus(toolName, status, detail) {
const statusIcons = {
@ -1313,8 +1476,8 @@ export function getWebviewContent(iconUri?: string): string {
<span class="tool-status-text">\${statusTexts[status]}</span>
\${detail ? \`<div class="tool-detail">\${detail}</div>\` : ''}
\`;
messagesContainer.appendChild(div);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
// 显示用户问题
@ -1357,8 +1520,8 @@ export function getWebviewContent(iconUri?: string): string {
}
};
messagesContainer.appendChild(div);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
// 提交用户回答