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:
- Confirm the actual order of messages in the DOM
- Check if CSS is affecting visual order vs DOM order
- 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:
- User sends a greeting or generic message
- No tools are planned (
plannedTools.length === 0) - Policy route is
'direct'withblockReason: '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:
- Test with 1, 5, 10+ messages
- Verify new messages appear at top immediately after submission
- Confirm scroll position behavior (should stay at top or auto-scroll to newest)
- 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)
- Update
chat-page.component.tsto properly reverse message array - Add
AfterViewInithook and scroll-to-top logic - Update template with
#chatLogContainerreference - Test with various message counts
- Verify accessibility (aria-live still works correctly)
Step 2: Natural Language Responses (Backend)
- Refactor
createNoToolDirectResponse()inai-agent.policy.utils.ts - Add greeting, acknowledgment, and name-introduction pattern matching
- Add randomness to responses for variety
- Write unit tests for new response patterns
- Test with user queries from evals dataset
Step 3: User Name Memory (Backend)
- Extend
AiAgentUserPreferenceStateinterface withnamefield - Update
resolvePreferenceUpdate()to extract names - Update
setUserPreferences()to store names in Redis - Personalize responses when name is available
- Add tests for name extraction and storage
Step 4: Integration Testing
- End-to-end test: full conversation flow with greetings
- Verify message ordering works correctly
- Test name memory across multiple queries
- Test with existing evals dataset (ensure no regressions)
- 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
- Test
visibleMessagesgetter returns correct order - Test
createNoToolDirectResponse()with various inputs - Test name extraction patterns
- Test user preferences update logic
Integration Tests
- Test full chat flow with greeting → name → follow-up
- Test message ordering with many messages
- Test scrolling behavior
- 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