You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

14 KiB

Chat Page UI/UX Fixes - Implementation Plan

Problem Statement

The newly implemented dedicated chat page (/chat) has two critical UX issues that make it feel unnatural and hard to use:

Issue 1: Message Ordering

Current behavior: Messages appear with oldest on top, newest on bottom (standard chat log order). Expected behavior: Newest messages should appear on top, oldest on bottom (reverse chronological). Impact: Users have to scroll to see the latest response, which is the opposite of what they expect in a conversation view.

Issue 2: Robotic System Prompts

Current behavior: Generic queries like "hi", "hello", or "remember my name is Max" trigger canned, robotic responses like:

I am Ghostfolio AI. I can help with portfolio analysis, concentration risk, market prices, diversification options, and stress scenarios.
Try one of these:
- "Show my top holdings"
- "What is my concentration risk?"
- "Help me diversify with actionable options"

Expected behavior: Natural, conversational responses that acknowledge the user's input in a friendly way. For example:

  • User: "remember my name is Max" → Assistant: "Got it, Max! I'll remember that. What would you like to know about your portfolio?"
  • User: "hi" → Assistant: "Hello! I'm here to help with your portfolio. What's on your mind today?"

Impact: The current responses feel impersonal and automated, breaking the conversational flow and making users feel like they're interacting with a script rather than an assistant.

Root Cause Analysis

Message Ordering

Location: apps/client/src/app/pages/chat/chat-page.component.ts:99-101

public get visibleMessages() {
  return [...(this.currentConversation?.messages ?? [])].reverse();
}

The code already reverses messages, but this is being applied to the message array before it's displayed. The issue is that .reverse() reverses the array in place and returns the same array reference, which can cause issues with Angular's change detection. Additionally, the CSS or layout may be positioning messages incorrectly.

Verification needed:

  1. Confirm the actual order of messages in the DOM
  2. Check if CSS is affecting visual order vs DOM order
  3. Verify Angular's trackBy function is working correctly with reversed arrays

Robotic System Prompts

Location: apps/api/src/app/endpoints/ai/ai-agent.policy.utils.ts:336-342, 466

The createNoToolDirectResponse() function returns canned responses for queries that don't require tools. This is triggered when:

  1. User sends a greeting or generic message
  2. No tools are planned (plannedTools.length === 0)
  3. Policy route is 'direct' with blockReason: 'no_tool_query'

The responses are intentionally generic and informative, but they don't feel conversational or acknowledge the user's specific input.

Solution Architecture

Phase 1: Fix Message Ordering (Quick Win)

1.1 Update Message Display Logic

File: apps/client/src/app/pages/chat/chat-page.component.ts

Change:

public get visibleMessages() {
  return [...(this.currentConversation?.messages ?? [])].reverse();
}

To:

public get visibleMessages() {
  // Create a copy and reverse for newest-first display
  const messages = this.currentConversation?.messages ?? [];
  return [...messages].reverse();
}

Verification:

  1. Test with 1, 5, 10+ messages
  2. Verify new messages appear at top immediately after submission
  3. Confirm scroll position behavior (should stay at top or auto-scroll to newest)
  4. Check that trackBy function still works correctly

1.2 Add Auto-Scroll to Newest Message

File: apps/client/src/app/pages/chat/chat-page.component.ts

import { ElementRef, ViewChild, AfterViewInit } from '@angular/core';

export class GfChatPageComponent implements OnDestroy, OnInit, AfterViewInit {
  @ViewChild('chatLogContainer', { static: false })
  chatLogContainer: ElementRef<HTMLElement>;

  // ... existing code ...

  ngAfterViewInit() {
    // Scroll to top (newest message) when messages change
    this.visibleMessages; // Trigger change detection
  }

  private scrollToTop() {
    if (this.chatLogContainer) {
      this.chatLogContainer.nativeElement.scrollTop = 0;
    }
  }
}

Template update: apps/client/src/app/pages/chat/chat-page.component.html

<div aria-live="polite" #chatLogContainer class="chat-log" role="log">
  <!-- messages -->
</div>

Phase 2: Natural Language Responses for Non-Tool Queries

2.1 Update Backend Response Generation

File: apps/api/src/app/endpoints/ai/ai-agent.policy.utils.ts

Replace the createNoToolDirectResponse() function to generate more natural, contextual responses:

export function createNoToolDirectResponse(query: string): string {
  const normalizedQuery = query.toLowerCase().trim();

  // Greeting patterns
  const greetingPatterns = [
    /^(hi|hello|hey|hiya|greetings)/i,
    /^(good (morning|afternoon|evening))/i,
    /^(how are you|how's it going|what's up)/i
  ];

  // Name introduction patterns
  const nameIntroductionPatterns = [
    /(?:my name is|i'm|i am|call me)\s+(\w+)/i,
    /remember (?:that )?my name is\s+(\w+)/i
  ];

  // Check for greeting
  if (greetingPatterns.some(pattern => pattern.test(normalizedQuery))) {
    const greetings = [
      "Hello! I'm here to help with your portfolio analysis. What would you like to know?",
      "Hi! I can help you understand your portfolio better. What's on your mind?",
      "Hey there! Ready to dive into your portfolio? Just ask!"
    ];
    return greetings[Math.floor(Math.random() * greetings.length)];
  }

  // Check for name introduction
  const nameMatch = nameIntroductionPatterns.find(pattern =>
    pattern.test(normalizedQuery)
  );
  if (nameMatch) {
    const match = normalizedQuery.match(nameMatch);
    const name = match?.[1];
    if (name) {
      return `Nice to meet you, ${name.charAt(0).toUpperCase() + name.slice(1)}! I've got that saved. What would you like to know about your portfolio today?`;
    }
  }

  // Default helpful response (more conversational)
  const defaults = [
    "I'm here to help with your portfolio! You can ask me things like 'Show my top holdings' or 'What's my concentration risk?'",
    "Sure! I can analyze your portfolio, check concentration risks, look up market prices, and more. What would you like to explore?",
    "I'd be happy to help! Try asking about your holdings, risk analysis, or market data for your investments."
  ];

  return defaults[Math.floor(Math.random() * defaults.length)];
}

2.2 Add Context Awareness for Follow-up Queries

For users who say "thanks" or "ok" after a previous interaction, acknowledge it conversationally:

// Add to createNoToolDirectResponse
const acknowledgmentPatterns = [
  /^(thanks|thank you|thx|ty|ok|okay|great|awesome)/i
];

if (acknowledgmentPatterns.some(pattern => pattern.test(normalizedQuery))) {
  const acknowledgments = [
    "You're welcome! Let me know if you need anything else.",
    "Happy to help! What else would you like to know?",
    "Anytime! Feel free to ask if you have more questions."
  ];
  return acknowledgments[Math.floor(Math.random() * acknowledgments.length)];
}

2.3 Update Memory to Track User Name

When a user introduces themselves, store this in user preferences so it can be used in future responses:

File: apps/api/src/app/endpoints/ai/ai-agent.chat.helpers.ts

Extend the AiAgentUserPreferenceState interface:

export interface AiAgentUserPreferenceState {
  name?: string;  // Add this
  responseStyle?: 'concise' | 'detailed';
  updatedAt?: string;
}

Update resolvePreferenceUpdate() to extract and store user names:

export function resolvePreferenceUpdate({
  query,
  userPreferences
}: {
  query: string;
  userPreferences?: AiAgentUserPreferenceState;
}): {
  acknowledgement?: string;
  userPreferences: AiAgentUserPreferenceState;
} {
  const normalizedQuery = query.toLowerCase().trim();
  const nameMatch = normalizedQuery.match(/(?:my name is|i'm|i am|call me)\s+(\w+)/i);

  let name = userPreferences?.name;
  if (nameMatch && nameMatch[1]) {
    name = nameMatch[1];
  }

  return {
    userPreferences: {
      ...userPreferences,
      name,
      responseStyle: userPreferences?.responseStyle,
      updatedAt: new Date().toISOString()
    }
  };
}

Then personalize responses when we know the user's name:

// In createNoToolDirectResponse or buildAnswer
if (userPreferences?.name) {
  return `Hi ${userPreferences.name}! ${restOfResponse}`;
}

Implementation Steps

Step 1: Message Ordering Fix (Frontend)

  1. Update chat-page.component.ts to properly reverse message array
  2. Add AfterViewInit hook and scroll-to-top logic
  3. Update template with #chatLogContainer reference
  4. Test with various message counts
  5. Verify accessibility (aria-live still works correctly)

Step 2: Natural Language Responses (Backend)

  1. Refactor createNoToolDirectResponse() in ai-agent.policy.utils.ts
  2. Add greeting, acknowledgment, and name-introduction pattern matching
  3. Add randomness to responses for variety
  4. Write unit tests for new response patterns
  5. Test with user queries from evals dataset

Step 3: User Name Memory (Backend)

  1. Extend AiAgentUserPreferenceState interface with name field
  2. Update resolvePreferenceUpdate() to extract names
  3. Update setUserPreferences() to store names in Redis
  4. Personalize responses when name is available
  5. Add tests for name extraction and storage

Step 4: Integration Testing

  1. End-to-end test: full conversation flow with greetings
  2. Verify message ordering works correctly
  3. Test name memory across multiple queries
  4. Test with existing evals dataset (ensure no regressions)
  5. Manual QA: test natural language feels conversational

Success Criteria

Message Ordering

  • Newest messages appear at top of chat log
  • New messages immediately appear at top after submission
  • Scroll position stays at top (or auto-scrolls to newest)
  • Works correctly with 1, 5, 10, 50+ messages
  • Angular change detection works efficiently
  • Accessibility (screen readers) still function correctly

Natural Language Responses

  • Greetings ("hi", "hello") return friendly, varied responses
  • Name introduction ("my name is Max") acknowledges the name
  • Acknowledgments ("thanks", "ok") return polite follow-ups
  • Default non-tool queries are more conversational
  • User name is remembered across session
  • Responses use name when available ("Hi Max!")
  • No regressions in existing functionality

Code Quality

  • All changes pass existing tests
  • New unit tests for response patterns
  • No TypeScript errors
  • Code follows existing patterns
  • Documentation updated if needed

Risks & Mitigations

Risk 1: Breaking Change in Message Display

Risk: Reversing message order could confuse existing users or break other components. Mitigation:

  • Test thoroughly in staging before deploying
  • Consider adding a user preference for message order if feedback is negative
  • Monitor for bug reports after deploy

Risk 2: Overly Casual Tone

Risk: Making responses too casual could reduce perceived professionalism. Mitigation:

  • Keep responses friendly but not slangy
  • Avoid emojis (per project guidelines)
  • Maintain focus on portfolio/finance context
  • A/B test if unsure

Risk 3: Name Extraction False Positives

Risk: Pattern matching could incorrectly extract names from non-name sentences. Mitigation:

  • Use specific patterns (require "my name is", "call me", etc.)
  • Only capitalize first letter of extracted name
  • Don't persist name without confidence
  • Add tests for edge cases

Risk 4: Performance Impact

Risk: Adding pattern matching for every query could slow response times. Mitigation:

  • Patterns are simple regex (should be fast)
  • Only runs for non-tool queries (minority of cases)
  • Profile before and after if concerned
  • Could cache compiled regex patterns if needed

Testing Strategy

Unit Tests

  1. Test visibleMessages getter returns correct order
  2. Test createNoToolDirectResponse() with various inputs
  3. Test name extraction patterns
  4. Test user preferences update logic

Integration Tests

  1. Test full chat flow with greeting → name → follow-up
  2. Test message ordering with many messages
  3. Test scrolling behavior
  4. Test that tool-based queries still work correctly

Manual QA Checklist

  • Send "hi" → get friendly greeting
  • Send "my name is Max" → response acknowledges Max
  • Send another query → response uses "Max"
  • Send 5 messages → newest appears at top
  • Refresh page → order preserved
  • Ask portfolio question → still works
  • Send "thanks" → get polite acknowledgment

Rollout Plan

Phase 1: Frontend Message Ordering (Low Risk)

  • Deploy to staging
  • QA team tests
  • Deploy to production
  • Monitor for 24 hours

Phase 2: Natural Language Backend (Medium Risk)

  • Deploy to staging
  • QA team tests with various queries
  • Run evals dataset to check for regressions
  • Deploy to production with feature flag if needed
  • Monitor user feedback

Phase 3: Name Memory (Low Risk)

  • Deploy after Phase 2 is stable
  • Test name persistence across sessions
  • Deploy to production

Documentation Updates

  • Update AI service documentation with new response patterns
  • Add examples of natural language responses to docs
  • Document user preferences schema changes
  • Update any API documentation if relevant

Future Enhancements (Out of Scope)

  • More sophisticated NLP for intent detection
  • Sentiment analysis to adjust response tone
  • Multi-language support for greetings
  • User customization of response style
  • Quick suggestions based on conversation context