diff --git a/src/views/webviewContent.ts b/src/views/webviewContent.ts index 6551c7c..2b4fd18 100644 --- a/src/views/webviewContent.ts +++ b/src/views/webviewContent.ts @@ -538,6 +538,148 @@ export function getWebviewContent(iconUri?: string): string { 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: var(--vscode-button-secondaryBackground); + color: var(--vscode-button-secondaryForeground); + border: 1px solid var(--vscode-button-border); + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + } + .question-option:hover { + background: var(--vscode-button-secondaryHoverBackground); + } + .question-option.selected { + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + } + .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; + }
@@ -947,12 +1089,44 @@ export function getWebviewContent(iconUri?: string): string { } }); + // 流式消息相关状态 + let currentStreamingMessage = null; + let loadingIndicator = null; + window.addEventListener('message', event => { const message = event.data; + console.log('[WebView] 收到消息:', message.command, message); switch (message.command) { case 'receiveMessage': - addMessage(message.text, 'bot'); + // 完成流式消息或普通消息 + if (currentStreamingMessage) { + finalizeStreamingMessage(message.text); + } else { + addMessage(message.text, 'bot'); + } + break; + case 'updateStreamingMessage': + // 流式更新消息 + updateOrCreateStreamingMessage(message.text); + break; + case 'showLoading': + showLoadingIndicator(message.text || '正在思考...'); + break; + case 'hideLoading': + hideLoadingIndicator(); + break; + case 'toolStart': + addToolStatus(message.toolName, 'start'); + break; + case 'toolComplete': + addToolStatus(message.toolName, 'complete', message.result); + break; + case 'toolError': + addToolStatus(message.toolName, 'error', message.error); + break; + case 'showQuestion': + showUserQuestion(message.askId, message.question, message.options); break; case 'fileContent': displayFileContent(message.content, message.filePath); @@ -972,6 +1146,163 @@ export function getWebviewContent(iconUri?: string): string { } }); + // 更新或创建流式消息 + 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); + + messagesContainer.appendChild(div); + currentStreamingMessage = div; + } else { + // 更新现有消息内容 + const messageContent = currentStreamingMessage.querySelector('.message-content'); + if (messageContent) { + messageContent.textContent = text; + } + } + + // 滚动到底部 + messagesContainer.scrollTop = messagesContainer.scrollHeight; + } + + // 完成流式消息 + 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 = ''; + copyBtn.onclick = () => copyMessage(finalText, copyBtn); + actionsDiv.appendChild(copyBtn); + + currentStreamingMessage.appendChild(actionsDiv); + currentStreamingMessage = null; + } + + messagesContainer.scrollTop = messagesContainer.scrollHeight; + } + + // 显示加载指示器 + function showLoadingIndicator(text) { + hideLoadingIndicator(); + + loadingIndicator = document.createElement('div'); + loadingIndicator.className = 'message bot-message loading-message'; + loadingIndicator.innerHTML = \` +