From 903e7fc0ecf2ba14f2aac97391de47c3dcff06ff Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 8 May 2026 20:36:36 +0000 Subject: [PATCH] feat(addie): add copy response button to web chat Closes #4266 https://claude.ai/code/session_01UoAgNhNUnrKHaXAZqHjHyg --- .changeset/4266-addie-copy-response-button.md | 4 + server/public/chat.html | 74 ++++++++++++++++++- 2 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 .changeset/4266-addie-copy-response-button.md diff --git a/.changeset/4266-addie-copy-response-button.md b/.changeset/4266-addie-copy-response-button.md new file mode 100644 index 0000000000..322f82a2f8 --- /dev/null +++ b/.changeset/4266-addie-copy-response-button.md @@ -0,0 +1,4 @@ +--- +--- + +Add "Copy response" button to Addie web chat interface. Each assistant message now shows a copy button alongside feedback controls; clicking copies the raw markdown to clipboard with a brief "Copied!" confirmation. diff --git a/server/public/chat.html b/server/public/chat.html index cf45be1c45..43cc99625f 100644 --- a/server/public/chat.html +++ b/server/public/chat.html @@ -814,6 +814,30 @@ color: white; } + .copy-btn { + background: none; + border: 1px solid var(--color-border); + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + font-size: 12px; + color: var(--color-text-muted); + transition: all 0.2s; + margin-left: auto; + flex-shrink: 0; + } + + .copy-btn:hover { + background: var(--color-bg-subtle); + color: var(--color-brand); + border-color: var(--color-brand); + } + + .copy-btn.copied { + color: var(--color-success-600); + border-color: var(--color-success-500); + } + .feedback-expand { background: none; border: none; @@ -3759,7 +3783,7 @@

Ask Addie

// Add feedback controls for assistant messages if (role === 'assistant' && messageId) { - const feedbackDiv = createFeedbackUI(messageId); + const feedbackDiv = createFeedbackUI(messageId, content); contentDiv.appendChild(feedbackDiv); } @@ -3773,8 +3797,30 @@

Ask Addie

return messageDiv; } + function fallbackCopy(text, btn) { + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + btn.textContent = 'Copied!'; + btn.classList.add('copied'); + setTimeout(() => { + btn.textContent = 'Copy'; + btn.classList.remove('copied'); + }, 1500); + } catch (e) { + console.error('Copy to clipboard failed:', e); + } finally { + document.body.removeChild(textarea); + } + } + // Create feedback UI for a message - function createFeedbackUI(messageId) { + function createFeedbackUI(messageId, rawContent) { const container = document.createElement('div'); container.className = 'message-feedback'; container.innerHTML = ` @@ -3857,6 +3903,28 @@

Ask Addie

showFeedbackSuccess(container); }); + if (rawContent) { + const copyBtn = document.createElement('button'); + copyBtn.className = 'copy-btn'; + copyBtn.textContent = 'Copy'; + copyBtn.title = 'Copy response'; + copyBtn.addEventListener('click', () => { + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(rawContent).then(() => { + copyBtn.textContent = 'Copied!'; + copyBtn.classList.add('copied'); + setTimeout(() => { + copyBtn.textContent = 'Copy'; + copyBtn.classList.remove('copied'); + }, 1500); + }).catch(() => fallbackCopy(rawContent, copyBtn)); + } else { + fallbackCopy(rawContent, copyBtn); + } + }); + container.appendChild(copyBtn); + } + return container; } @@ -4134,7 +4202,7 @@

Ask Addie

// Add feedback controls if (messageId) { - const feedbackDiv = createFeedbackUI(messageId); + const feedbackDiv = createFeedbackUI(messageId, fullContent); contentDiv.appendChild(feedbackDiv); }