- @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
// ---------------------------------------------------------------------------