From 8d66e75bea099e0d62a0fc7b42724cc38678cacc Mon Sep 17 00:00:00 2001 From: Priyanka Punukollu Date: Tue, 24 Feb 2026 10:56:07 -0600 Subject: [PATCH] feat(ai-chat): add auto-seed banner for empty portfolios When the agent returns an empty-portfolio response (e.g. after a new Google OAuth sign-in), a blue banner appears offering to load 18 demo activities. Clicking 'Load demo data' calls POST /agent/seed with the user's own bearer token so the seeded data belongs to their account. Also adds the /seed URL construction in the component constructor. Co-authored-by: Cursor --- .../components/ai-chat/ai-chat.component.html | 14 ++++++ .../components/ai-chat/ai-chat.component.scss | 45 ++++++++++++++++++ .../components/ai-chat/ai-chat.component.ts | 46 +++++++++++++++++++ 3 files changed, 105 insertions(+) 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 c80f28ea9..67a03a4dc 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 @@ -29,6 +29,20 @@
{{ successBanner }}
} + + @if (showSeedBanner && !isSeeding) { +
+ Your portfolio is empty. Load demo data to try the AI? +
+ + +
+
+ } + @if (isSeeding) { +
⏳ Loading demo portfolio…
+ } +
@for (msg of messages; track $index) { 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 8b8c76104..a619c41bb 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 @@ -159,6 +159,51 @@ color: #a7f3d0; } } + + &--seed { + background: #eff6ff; + color: #1e40af; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.5rem; + font-weight: 500; + + :host-context(.theme-dark) & { + background: #1e3a5f; + color: #bfdbfe; + } + } + + &__actions { + display: flex; + gap: 0.4rem; + flex-shrink: 0; + } + + &__btn { + border: none; + border-radius: 6px; + padding: 0.25rem 0.65rem; + font-size: 0.8rem; + font-weight: 600; + cursor: pointer; + transition: opacity 0.15s; + + &:hover { opacity: 0.85; } + + &--primary { + background: #2563eb; + color: #fff; + } + + &--dismiss { + background: transparent; + color: inherit; + opacity: 0.6; + } + } } @keyframes slideDown { 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 97ba793f1..4555f5a5d 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 @@ -50,6 +50,8 @@ export class GfAiChatComponent implements OnDestroy { public inputValue = ''; public messages: ChatMessage[] = []; public successBanner = ''; + public showSeedBanner = false; + public isSeeding = false; // Write confirmation state private pendingWrite: Record | null = null; @@ -57,6 +59,7 @@ export class GfAiChatComponent implements OnDestroy { private readonly AGENT_URL: string; private readonly FEEDBACK_URL: string; + private readonly SEED_URL: string; public constructor( private changeDetectorRef: ChangeDetectorRef, @@ -67,6 +70,7 @@ export class GfAiChatComponent implements OnDestroy { const base = (environment.agentUrl ?? '/agent').replace(/\/$/, ''); this.AGENT_URL = `${base}/chat`; this.FEEDBACK_URL = `${base}/feedback`; + this.SEED_URL = `${base}/seed`; } public ngOnDestroy() {} @@ -166,6 +170,15 @@ export class GfAiChatComponent implements OnDestroy { this.awaitingConfirmation = data.awaiting_confirmation; this.pendingWrite = data.pending_write; + // Detect an empty portfolio and offer to seed demo data + const emptyPortfolioHints = ['0 holdings', '0 positions', 'no holdings', 'no positions', 'empty portfolio', 'no transactions', '0.00 (0.0%)']; + const isEmptyPortfolio = emptyPortfolioHints.some((hint) => + data.response.toLowerCase().includes(hint) + ); + if (isEmptyPortfolio && !this.showSeedBanner) { + this.showSeedBanner = true; + } + if (isWriteSuccess) { this.successBanner = '✅ Transaction recorded successfully'; setTimeout(() => { @@ -192,6 +205,39 @@ export class GfAiChatComponent implements OnDestroy { }); } + public seedPortfolio(): void { + this.isSeeding = true; + this.showSeedBanner = false; + this.changeDetectorRef.markForCheck(); + + const body: { bearer_token?: string } = {}; + const userToken = this.tokenStorageService.getToken(); + if (userToken) { + body.bearer_token = userToken; + } + + this.http.post<{ success: boolean; message: string }>(this.SEED_URL, body).subscribe({ + next: (data) => { + this.isSeeding = false; + if (data.success) { + this.messages.push({ + role: 'assistant', + content: `🌱 **Demo portfolio loaded!** I've added 18 transactions across AAPL, MSFT, NVDA, GOOGL, AMZN, and VTI spanning 2021–2024. Try asking "how is my portfolio doing?" to see your analysis.` + }); + } else { + this.messages.push({ role: 'assistant', content: '⚠️ Could not load demo data. Please try again.' }); + } + this.changeDetectorRef.markForCheck(); + this.scrollToBottom(); + }, + error: () => { + this.isSeeding = false; + this.messages.push({ role: 'assistant', content: '⚠️ Could not reach the seeding endpoint. Make sure the agent is running.' }); + this.changeDetectorRef.markForCheck(); + } + }); + } + public giveFeedback( msgIndex: number, rating: 1 | -1