From 11c408ce0f992951322c599cee3a253873fa8d2d Mon Sep 17 00:00:00 2001 From: Roe-xin Date: Thu, 12 Mar 2026 18:00:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=8C=89=E9=92=AE=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加任务完成图标和状态提示 - 消息操作按钮改为内联显示 - 优化复制功能获取消息内容 --- src/constants/toolIcons.ts | 5 ++ src/panels/helpers/conversationHelper.ts | 5 ++ src/views/messageRenderer.ts | 13 +++-- src/views/messageStyles.ts | 12 +++- src/views/segmentRenderer.ts | 70 ++++++++++++------------ src/views/webviewContent.ts | 42 +++++++++++++- 6 files changed, 102 insertions(+), 45 deletions(-) diff --git a/src/constants/toolIcons.ts b/src/constants/toolIcons.ts index dc4352b..4a61fcd 100644 --- a/src/constants/toolIcons.ts +++ b/src/constants/toolIcons.ts @@ -200,3 +200,8 @@ export const setting = ``; + +/** + * 任务完成的图标svg + */ +export const taskCompleteIconSvg = ``; diff --git a/src/panels/helpers/conversationHelper.ts b/src/panels/helpers/conversationHelper.ts index 6280f8a..2550f2f 100644 --- a/src/panels/helpers/conversationHelper.ts +++ b/src/panels/helpers/conversationHelper.ts @@ -209,6 +209,11 @@ export async function selectConversation( }); } + // 发送任务完成消息(历史记录) + panel.webview.postMessage({ + command: "taskCompleteHistory", + }); + vscode.window.showInformationMessage( `已加载会话: ${taskSession.meta.taskName}`, ); diff --git a/src/views/messageRenderer.ts b/src/views/messageRenderer.ts index 277b707..662b3f7 100644 --- a/src/views/messageRenderer.ts +++ b/src/views/messageRenderer.ts @@ -48,11 +48,10 @@ export function getMessageRendererScript(): string { 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 messageContent = document.createElement('span'); + messageContent.textContent = text; const copyBtn = document.createElement('button'); copyBtn.className = 'action-btn'; copyBtn.innerHTML = \`复制\`; @@ -65,6 +64,7 @@ export function getMessageRendererScript(): string { dislikeBtn.className = 'action-btn'; dislikeBtn.innerHTML = \`点踩\`; dislikeBtn.onclick = () => toggleDislike(dislikeBtn); + actionsDiv.appendChild(messageContent); actionsDiv.appendChild(copyBtn); actionsDiv.appendChild(likeBtn); actionsDiv.appendChild(dislikeBtn); @@ -97,7 +97,12 @@ export function getMessageRendererScript(): string { } function copyMessage(text, button) { - navigator.clipboard.writeText(text).then(() => { + // 从按钮的父消息元素中获取实际文本内容 + const messageDiv = button.closest('.message'); + const messageContent = messageDiv?.querySelector('.message-content, div:not(.message-actions)'); + const textToCopy = messageContent ? messageContent.textContent : text; + + navigator.clipboard.writeText(textToCopy).then(() => { const originalHTML = button.innerHTML; button.innerHTML = \`\`; setTimeout(() => { diff --git a/src/views/messageStyles.ts b/src/views/messageStyles.ts index 2e75afa..39f28ca 100644 --- a/src/views/messageStyles.ts +++ b/src/views/messageStyles.ts @@ -38,12 +38,18 @@ export function getMessageAreaStyles(): string { position: relative; } .message-actions { - display: flex; + display: inline-flex; gap: 8px; - margin-top: 12px; - margin-left: 10px; + margin-left: 12px; opacity: 0.85; transition: opacity 0.2s ease; + vertical-align: middle; + align-items: center; + } + .message-actions > span { + display: inline-flex; + align-items: center; + gap: 4px; } .message-actions:hover { opacity: 1; diff --git a/src/views/segmentRenderer.ts b/src/views/segmentRenderer.ts index 7a49689..171aeef 100644 --- a/src/views/segmentRenderer.ts +++ b/src/views/segmentRenderer.ts @@ -8,11 +8,7 @@ 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 (!isComplete && (!segments || segments.length === 0)) return; if (!currentSegmentedMessage) { if (currentStreamingMessage) { @@ -40,11 +36,13 @@ export function getSegmentRendererScript(): string { }); } - currentSegmentedMessage.innerHTML = ''; + if (!isComplete) { + currentSegmentedMessage.innerHTML = ''; + } const mergedSegments = []; let i = 0; - while (i < segments.length) { + while (i < (segments?.length || 0)) { const segment = segments[i]; if (segment.type === 'tool') { let count = 1; @@ -216,34 +214,10 @@ export function getSegmentRendererScript(): string { }); 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 = \`复制\`; - 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 = \`点赞\`; - likeBtn.onclick = () => toggleLike(likeBtn); - const dislikeBtn = document.createElement('button'); - dislikeBtn.className = 'action-btn'; - dislikeBtn.innerHTML = \`点踩\`; - dislikeBtn.onclick = () => toggleDislike(dislikeBtn); - actionsDiv.appendChild(copyBtn); - actionsDiv.appendChild(likeBtn); - actionsDiv.appendChild(dislikeBtn); - currentSegmentedMessage.appendChild(actionsDiv); currentSegmentedMessage = null; } + smartScrollToBottom(); } @@ -264,10 +238,34 @@ export function getSegmentRendererScript(): string { 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); + updateSegmentsRealtime(segments, false); + + // 历史消息渲染完成后添加操作按钮 + if (currentSegmentedMessage) { + const actionsDiv = document.createElement('div'); + actionsDiv.className = 'message-actions'; + const copyBtn = document.createElement('button'); + copyBtn.className = 'action-btn'; + copyBtn.innerHTML = \`复制\`; + 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 = \`点赞\`; + likeBtn.onclick = () => toggleLike(likeBtn); + const dislikeBtn = document.createElement('button'); + dislikeBtn.className = 'action-btn'; + dislikeBtn.innerHTML = \`点踩\`; + dislikeBtn.onclick = () => toggleDislike(dislikeBtn); + actionsDiv.appendChild(copyBtn); + actionsDiv.appendChild(likeBtn); + actionsDiv.appendChild(dislikeBtn); + currentSegmentedMessage.appendChild(actionsDiv); + currentSegmentedMessage = null; + } + smartScrollToBottom(); } `; diff --git a/src/views/webviewContent.ts b/src/views/webviewContent.ts index baddbc9..c31941b 100644 --- a/src/views/webviewContent.ts +++ b/src/views/webviewContent.ts @@ -25,6 +25,7 @@ import { } from "./progressBar"; import { getHighlightJsLinks } from "../components/codeHighlight"; import { getCurrentEnv } from "../config/settings"; +import { taskCompleteIconSvg } from "../constants/toolIcons"; import { getInvitationModalContent, getInvitationModalStyles, @@ -545,6 +546,9 @@ export function getWebviewContent( const modeSelect = document.getElementById('modeSelect'); const messagesEl = document.getElementById('messages'); + // 图标常量 + const taskCompleteIconSvg = ${JSON.stringify(taskCompleteIconSvg)}; + // 全局变量 let currentStreamingMessage = null; let loadingIndicator = null; @@ -769,10 +773,44 @@ export function getWebviewContent( // 隐藏加载指示器 hideLoadingIndicator(); break; - case 'taskComplete': // 显示任务完成提示 - addMessage('✅ 任务已完成', 'bot'); + const taskDiv = document.createElement('div'); + taskDiv.className = 'message bot-message'; + const taskActionsDiv = document.createElement('div'); + taskActionsDiv.className = 'message-actions'; + const taskMessageContent = document.createElement('span'); + taskMessageContent.innerHTML = taskCompleteIconSvg + ' 任务完成'; + const taskCopyBtn = document.createElement('button'); + taskCopyBtn.className = 'action-btn'; + taskCopyBtn.innerHTML = \`复制\`; + taskCopyBtn.onclick = () => { + // 获取前一个 AI 消息的内容 + const prevMessage = taskDiv.previousElementSibling; + if (prevMessage && prevMessage.classList.contains('bot-message')) { + const textContent = prevMessage.textContent || ''; + copyMessage(textContent, taskCopyBtn); + } + }; + const taskLikeBtn = document.createElement('button'); + taskLikeBtn.className = 'action-btn'; + taskLikeBtn.innerHTML = \`点赞\`; + taskLikeBtn.onclick = () => toggleLike(taskLikeBtn); + const taskDislikeBtn = document.createElement('button'); + taskDislikeBtn.className = 'action-btn'; + taskDislikeBtn.innerHTML = \`点踩\`; + taskDislikeBtn.onclick = () => toggleDislike(taskDislikeBtn); + taskActionsDiv.appendChild(taskMessageContent); + taskActionsDiv.appendChild(taskCopyBtn); + taskActionsDiv.appendChild(taskLikeBtn); + taskActionsDiv.appendChild(taskDislikeBtn); + taskDiv.appendChild(taskActionsDiv); + messagesEl.appendChild(taskDiv); + messagesEl.scrollTop = messagesEl.scrollHeight; + break; + + case 'taskCompleteHistory': + // 历史记录不显示任务完成提示 break; case 'workspaceStatus':