From ac0302b9e527a307ce4a5003fb374cf8927fc044 Mon Sep 17 00:00:00 2001 From: Priyanka Punukollu Date: Sat, 28 Feb 2026 08:10:10 -0600 Subject: [PATCH] fix: copy button works in all browsers with per-message success feedback Made-with: Cursor --- .../components/ai-chat/ai-chat.component.html | 17 ++++++- .../components/ai-chat/ai-chat.component.scss | 27 ++++++++++++ .../components/ai-chat/ai-chat.component.ts | 44 +++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/apps/client/src/app/components/ai-chat/ai-chat.component.html b/apps/client/src/app/components/ai-chat/ai-chat.component.html index 3c892aac2..7984816a8 100644 --- a/apps/client/src/app/components/ai-chat/ai-chat.component.html +++ b/apps/client/src/app/components/ai-chat/ai-chat.component.html @@ -172,7 +172,7 @@ @if (activeTab === 'chat') {
- @for (msg of messages; track $index) { + @for (msg of messages; track $index; let i = $index) {
+ + @if (msg.role === 'assistant') { + + } + @if (msg.comparisonCard) { diff --git a/apps/client/src/app/components/ai-chat/ai-chat.component.scss b/apps/client/src/app/components/ai-chat/ai-chat.component.scss index e61ed4807..b65fe26b7 100644 --- a/apps/client/src/app/components/ai-chat/ai-chat.component.scss +++ b/apps/client/src/app/components/ai-chat/ai-chat.component.scss @@ -446,6 +446,33 @@ } } +.ai-copy-btn { + margin-top: 0.25rem; + padding: 0.2rem 0.5rem; + font-size: 0.7rem; + font-weight: 500; + background: rgba(99, 102, 241, 0.1); + color: #6366f1; + border: 1px solid rgba(99, 102, 241, 0.25); + border-radius: 0.4rem; + cursor: pointer; + transition: background 0.15s ease; + + &:hover { + background: rgba(99, 102, 241, 0.2); + } + + :host-context(.theme-dark) & { + background: rgba(99, 102, 241, 0.15); + color: #a5b4fc; + border-color: rgba(99, 102, 241, 0.35); + + &:hover { + background: rgba(99, 102, 241, 0.25); + } + } +} + .ai-chip { display: inline-flex; align-items: center; diff --git a/apps/client/src/app/components/ai-chat/ai-chat.component.ts b/apps/client/src/app/components/ai-chat/ai-chat.component.ts index d39bd0ea8..68a4963d5 100644 --- a/apps/client/src/app/components/ai-chat/ai-chat.component.ts +++ b/apps/client/src/app/components/ai-chat/ai-chat.component.ts @@ -95,6 +95,7 @@ export class GfAiChatComponent implements OnInit, OnDestroy { public isSeeding = false; public enableRealEstate: boolean; public agentReachable: boolean | null = null; + public copySuccessMap: { [key: number]: boolean } = {}; // Activity log tab public activeTab: 'chat' | 'log' = 'chat'; @@ -503,6 +504,49 @@ export class GfAiChatComponent implements OnInit, OnDestroy { return tool.replace(/_/g, ' '); } + // --------------------------------------------------------------------------- + // Copy to clipboard + // --------------------------------------------------------------------------- + + public copyToClipboard(text: string, index: number): void { + const writeToClipboard = (t: string): Promise => { + if (navigator.clipboard && window.isSecureContext) { + return navigator.clipboard.writeText(t); + } + return new Promise((resolve, reject) => { + const textarea = document.createElement('textarea'); + textarea.value = t; + textarea.style.position = 'fixed'; + textarea.style.left = '-9999px'; + textarea.style.top = '-9999px'; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + try { + document.execCommand('copy'); + document.body.removeChild(textarea); + resolve(); + } catch (err) { + document.body.removeChild(textarea); + reject(err); + } + }); + }; + + writeToClipboard(text) + .then(() => { + this.copySuccessMap[index] = true; + setTimeout(() => { + this.copySuccessMap[index] = false; + this.changeDetectorRef.markForCheck(); + }, 1500); + this.changeDetectorRef.markForCheck(); + }) + .catch(() => { + console.warn('Copy failed'); + }); + } + // --------------------------------------------------------------------------- // Connection health check // ---------------------------------------------------------------------------