CoolFace
Apppublic

sykang16/agentic-wealth-intelligence

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
08_UI_DESIGN.md1580 linesDownload Raw Back to docs
1# UI Design & Structure2 3## Overview4 5This document outlines the user interface architecture for the Agentic Wealth Intelligence System. The UI serves as the primary interaction layer between users and the multi-agent backend, supporting both single-user demos and multi-user deployments.6 7---8 9## UI Strategy10 11### Development Approach: Progressive Enhancement12 13We adopt a **progressive enhancement strategy** that balances speed with sophistication:14 15```16Phase 1 (Week 1)     →  Phase 2 (Weeks 2-3)  →  Phase 3 (Weeks 4-6)17─────────────────       ──────────────────       ──────────────────18CLI Interface           Streamlit Web App        Multi-User Support19(Validation)            (MVP/Demo)               (Authentication & Isolation)20```21 22**Recommendation**: Focus on **Streamlit** (Phase 2) for demo, then add **multi-user support** (Phase 3).23 24---25 26## Phase 1: CLI Interface (Validation)27 28### Purpose29- Quick validation of agent logic30- No UI overhead31- Developer-friendly testing32 33### Implementation34 35```python36# src/cli/main.py37from rich.console import Console38from rich.prompt import Prompt39from rich.panel import Panel40from rich.markdown import Markdown41 42console = Console()43 44def main():45    console.print(Panel.fit(46        "[bold blue]Agentic Wealth Intelligence[/bold blue]\n"47        "Multi-Agent Financial Advisory System",48        border_style="blue"49    ))50    51    while True:52        user_input = Prompt.ask("\n[bold green]You[/bold green]")53        54        if user_input.lower() in ['exit', 'quit']:55            break56        57        # Call orchestrator agent58        console.print("[dim]Thinking...[/dim]")59        response = await orchestrator_agent.run(user_input)60        61        # Display response62        console.print(f"\n[bold cyan]Assistant ({response.agent})[/bold cyan]")63        console.print(Markdown(response.content))64        65        # Show any visualizations66        if response.chart_data:67            console.print("[dim]Chart data available (view in UI)[/dim]")68 69if __name__ == "__main__":70    import asyncio71    asyncio.run(main())72```73 74### Features75✅ Simple text-based interaction76✅ Color-coded output (Rich library)77✅ Markdown rendering for responses78✅ Quick iteration and testing79 80---81 82## Phase 2: Streamlit Web App (Recommended MVP)83 84 85### Streamlit Application Structure86 87```88ui/89├── streamlit_app.py              # Main entry point90├── pages/91│   ├── 1_Dashboard.py         # Portfolio overview92│   ├── 2_Chat.py              # Agent conversation93│   ├── 3_Profile.py           # Investment profile94│   └── 4_Recommendations.py   # Product recommendations95├── components/96│   ├── __init__.py97│   ├── auth.py                   # Authentication UI98│   ├── charts.py                 # Visualization helpers99│   ├── chat_ui.py                # Chat components100│   ├── portfolio_cards.py        # Dashboard widgets101│   └── metrics.py                # Financial metrics display102├── utils/103│   ├── __init__.py104│   ├── api_client.py             # Backend API calls105│   ├── state_manager.py          # Session state106│   ├── auth_manager.py           # User authentication107│   └── formatters.py             # Data formatting108└── config.py                     # UI configuration109```110 111---112 113### Main Application Entry Point114 115```python116# ui/streamlit_app.py117import streamlit as st118from utils.state_manager import initialize_session_state119from utils.auth_manager import check_authentication120from utils.api_client import APIClient121 122# Page configuration123st.set_page_config(124    page_title="Agentic Wealth Intelligence",125    page_icon="💰",126    layout="wide",127    initial_sidebar_state="expanded"128)129 130# Check authentication131if not check_authentication():132    st.stop()  # Will show login page133 134# Initialize session state135initialize_session_state()136 137# Get current user info138user_info = st.session_state.user_info139 140# Sidebar141with st.sidebar:142    st.title("💰 Wealth Intelligence")143    st.divider()144    145    # User info146    st.subheader(f"👤 {user_info['name']}")147    st.caption(f"ID: {user_info['user_id']}")148    149    # Logout button150    if st.button("🚪 Logout"):151        st.session_state.clear()152        st.rerun()153    154    st.divider()155    156    # Quick stats157    col1, col2 = st.columns(2)158    with col1:159        st.metric("Net Worth", "$125,430", "+5.2%")160    with col2:161        st.metric("Portfolio", "$98,450", "+3.1%")162    163    st.divider()164    165    # Navigation info166    st.caption("📊 Dashboard - Overview")167    st.caption("💬 Chat - Talk to AI")168    st.caption("👤 Profile - Your preferences")169    st.caption("💡 Recommendations - Personalized advice")170 171# Main content172st.title(f"Welcome back, {user_info['name']}!")173 174st.markdown("""175This multi-agent AI system helps you understand your finances and make informed investment decisions.176 177### Getting Started1781. **📊 Dashboard**: View your complete financial picture1792. **💬 Chat**: Ask questions about your portfolio1803. **👤 Profile**: Complete your investment profile1814. **💡 Recommendations**: Get personalized advice182""")183 184# Quick actions185col1, col2, col3 = st.columns(3)186 187with col1:188    if st.button("📊 View Dashboard", use_container_width=True):189        st.switch_page("pages/1_Dashboard.py")190 191with col2:192    if st.button("💬 Start Chat", use_container_width=True):193        st.switch_page("pages/2_Chat.py")194 195with col3:196    if st.button("💡 Get Advice", use_container_width=True):197        st.switch_page("pages/4_Recommendations.py")198```199 200---201 202### Page 1: Dashboard (Portfolio Overview)203 204```python205# ui/pages/1_Dashboard.py206import streamlit as st207import plotly.express as px208import plotly.graph_objects as go209from components.charts import create_allocation_chart, create_performance_chart210from components.portfolio_cards import render_account_cards211from utils.api_client import APIClient212from utils.auth_manager import require_authentication213 214st.set_page_config(page_title="Dashboard", page_icon="📊", layout="wide")215 216# Require authentication217require_authentication()218 219st.title("📊 Portfolio Dashboard")220 221# Initialize API client with user context222api_client = APIClient(user_id=st.session_state.user_info['user_id'])223 224# Fetch portfolio data225with st.spinner("Loading portfolio data..."):226    portfolio = api_client.get_portfolio()227 228# Top metrics row229col1, col2, col3, col4 = st.columns(4)230 231with col1:232    st.metric(233        "Total Assets",234        f"${portfolio.total_assets:,.2f}",235        f"{portfolio.asset_change_pct:+.1%}"236    )237 238with col2:239    st.metric(240        "Net Worth",241        f"${portfolio.net_worth:,.2f}",242        f"{portfolio.net_worth_change_pct:+.1%}"243    )244 245with col3:246    st.metric(247        "Liquidity Ratio",248        f"{portfolio.liquidity_ratio:.2f}",249        "Healthy" if portfolio.liquidity_ratio > 0.5 else "Low",250        delta_color="normal" if portfolio.liquidity_ratio > 0.5 else "inverse"251    )252 253with col4:254    st.metric(255        "Monthly Cashflow",256        f"${portfolio.monthly_cashflow:,.2f}",257        f"{portfolio.cashflow_change:+.1%}"258    )259 260st.divider()261 262# Charts row263col1, col2 = st.columns(2)264 265with col1:266    st.subheader("Asset Allocation")267    fig = create_allocation_chart(portfolio.allocation)268    st.plotly_chart(fig, use_container_width=True)269 270with col2:271    st.subheader("Portfolio Performance (6 Months)")272    fig = create_performance_chart(portfolio.performance_history)273    st.plotly_chart(fig, use_container_width=True)274 275st.divider()276 277# Accounts section278st.subheader("Accounts Overview")279render_account_cards(portfolio.accounts)280 281st.divider()282 283# Interactive query section284st.subheader("💬 Ask About Your Portfolio")285 286col1, col2 = st.columns([3, 1])287 288with col1:289    query = st.text_input(290        "Natural language query",291        placeholder="e.g., What's my debt-to-income ratio?",292        label_visibility="collapsed"293    )294 295with col2:296    if st.button("Ask Asset Agent", use_container_width=True):297        if query:298            with st.spinner("Analyzing..."):299                response = api_client.query_asset_agent(query)300                st.info(response.answer)301                302                # Show any generated charts303                if response.chart:304                    st.plotly_chart(response.chart)305```306 307---308 309### Page 2: Chat Interface (Multi-Agent Conversation)310 311```python312# ui/pages/2_Chat.py313import streamlit as st314from utils.api_client import APIClient315from components.chat_ui import render_message, show_agent_indicator316from utils.auth_manager import require_authentication317 318st.set_page_config(page_title="Chat", page_icon="💬", layout="wide")319 320# Require authentication321require_authentication()322 323st.title("💬 Chat with AI Agents")324 325# Initialize chat history per user326chat_key = f"messages_{st.session_state.user_info['user_id']}"327if chat_key not in st.session_state:328    st.session_state[chat_key] = []329    st.session_state[chat_key].append({330        "role": "assistant",331        "content": f"Hello {st.session_state.user_info['name']}! I'm your wealth intelligence assistant. I can help you understand your finances, complete your investment profile, or provide personalized recommendations. What would you like to know?",332        "agent": "orchestrator"333    })334 335# API client336api_client = APIClient(user_id=st.session_state.user_info['user_id'])337 338# Display chat messages339for message in st.session_state[chat_key]:340    with st.chat_message(message["role"]):341        # Show which agent is responding342        if message["role"] == "assistant":343            show_agent_indicator(message.get("agent", "orchestrator"))344        345        st.markdown(message["content"])346        347        # Display any charts or data348        if "chart" in message:349            st.plotly_chart(message["chart"], use_container_width=True)350        351        if "data" in message:352            with st.expander("View data"):353                st.json(message["data"])354 355# Chat input356if prompt := st.chat_input("Type your message here..."):357    # Add user message358    st.session_state[chat_key].append({359        "role": "user",360        "content": prompt361    })362    363    # Display user message364    with st.chat_message("user"):365        st.markdown(prompt)366    367    # Get agent response368    with st.chat_message("assistant"):369        with st.spinner("Thinking..."):370            response = api_client.chat(371                message=prompt,372                history=st.session_state[chat_key]373            )374            375            # Show active agent376            show_agent_indicator(response.agent)377            378            # Stream response (if supported)379            st.markdown(response.content)380            381            # Show any visualizations382            if response.chart:383                st.plotly_chart(response.chart, use_container_width=True)384    385    # Add assistant message to history386    st.session_state[chat_key].append({387        "role": "assistant",388        "content": response.content,389        "agent": response.agent,390        "chart": response.chart if hasattr(response, 'chart') else None391    })392 393# Sidebar: Conversation controls394with st.sidebar:395    st.subheader("Conversation")396    397    if st.button("🔄 New Conversation"):398        st.session_state[chat_key] = []399        st.rerun()400    401    if st.button("💾 Save Conversation"):402        # Save to file or database403        st.success("Conversation saved!")404    405    st.divider()406    407    # Show active agent context408    st.caption("**Active Context**")409    st.caption(f"Messages: {len(st.session_state[chat_key])}")410    411    # Agent activity412    if st.session_state[chat_key]:413        last_agent = st.session_state[chat_key][-1].get("agent", "unknown")414        st.caption(f"Last agent: {last_agent}")415```416 417---418 419### Page 3: Investment Profile420 421```python422# ui/pages/3_Profile.py423import streamlit as st424from utils.api_client import APIClient425from utils.auth_manager import require_authentication426 427st.set_page_config(page_title="Profile", page_icon="👤", layout="wide")428 429# Require authentication430require_authentication()431 432st.title("👤 Investment Profile")433 434api_client = APIClient(user_id=st.session_state.user_info['user_id'])435 436# Tabs for different modes437tab1, tab2 = st.tabs(["📝 Conversational Profiling", "📋 View Profile"])438 439with tab1:440    st.subheader("Complete Your Profile Through Conversation")441    st.info("💡 Our AI agent will ask you questions to understand your investment preferences. Just answer naturally!")442    443    # Profiling session key per user444    profiling_key = f"profiling_{st.session_state.user_info['user_id']}"445    446    # Start profiling conversation447    if st.button("🚀 Start Profiling Conversation", type="primary"):448        # Initialize profiling session449        st.session_state[profiling_key] = {450            "active": True,451            "messages": []452        }453        st.rerun()454    455    # Profiling conversation456    if st.session_state.get(profiling_key, {}).get("active", False):457        # Get profiling status458        status = api_client.get_profiling_status()459        460        # Progress indicator461        progress = status.filled_slots / status.total_slots462        st.progress(progress, text=f"Profile Completion: {progress*100:.0f}%")463        464        # Show which slots are filled465        with st.expander("Profile Status"):466            col1, col2 = st.columns(2)467            with col1:468                st.success(f"✅ Completed: {', '.join(status.filled_slots)}")469            with col2:470                st.warning(f"⏳ Remaining: {', '.join(status.missing_slots)}")471        472        st.divider()473        474        # Conversation interface (similar to chat)475        for msg in st.session_state[profiling_key]["messages"]:476            with st.chat_message(msg["role"]):477                st.markdown(msg["content"])478        479        # User input480        if response := st.chat_input("Your answer..."):481            # Add to messages482            st.session_state[profiling_key]["messages"].append({483                "role": "user",484                "content": response485            })486            487            # Send to profiling agent488            with st.spinner("Processing..."):489                agent_response = api_client.profiling_agent_step(response)490                491                st.session_state[profiling_key]["messages"].append({492                    "role": "assistant",493                    "content": agent_response.question or agent_response.summary494                })495                496                # Check if complete497                if agent_response.is_complete:498                    st.session_state[profiling_key]["active"] = False499                    st.success("✅ Profile completed!")500                    st.balloons()501            502            st.rerun()503 504with tab2:505    st.subheader("Your Investment Profile")506    507    # Fetch current profile508    profile = api_client.get_profile()509    510    if profile:511        # Display profile in organized sections512        col1, col2 = st.columns(2)513        514        with col1:515            st.markdown("### Risk Assessment")516            st.metric("Risk Tolerance", profile.risk_tolerance.title())517            st.metric("Loss Comfort (1-10)", profile.loss_comfort)518            519            st.markdown("### Time Horizon")520            st.metric("Investment Period", profile.investment_period.title())521            st.text_area("Liquidity Needs", profile.liquidity_needs, disabled=True)522        523        with col2:524            st.markdown("### Financial Situation")525            st.metric("Income Stability", profile.income_stability.title())526            st.metric("Emergency Fund", "Yes" if profile.emergency_fund else "No")527            st.metric("Debt Level", profile.debt_level.title())528            529            st.markdown("### Experience")530            st.metric("Level", profile.experience_level.title())531            st.text_area("Previous Investments", 532                        ", ".join(profile.previous_investments), 533                        disabled=True)534        535        st.divider()536        537        st.markdown("### Investment Goals")538        st.text_area("Primary Goal", profile.primary_goal, disabled=True)539        if profile.target_return:540            st.metric("Target Return", f"{profile.target_return}%")541        542        # Edit button543        if st.button("✏️ Update Profile"):544            st.session_state[profiling_key] = {"active": True, "messages": []}545            st.rerun()546    547    else:548        st.warning("No profile found. Please complete the conversational profiling.")549```550 551---552 553### Page 4: Recommendations554 555```python556# ui/pages/4_Recommendations.py557import streamlit as st558from utils.api_client import APIClient559from utils.auth_manager import require_authentication560 561st.set_page_config(page_title="Recommendations", page_icon="💡", layout="wide")562 563# Require authentication564require_authentication()565 566st.title("💡 Personalized Investment Recommendations")567 568api_client = APIClient(user_id=st.session_state.user_info['user_id'])569 570# Check if profile exists571profile = api_client.get_profile()572if not profile:573    st.warning("⚠️ Please complete your investment profile first.")574    if st.button("Go to Profile"):575        st.switch_page("pages/3_👤_Profile.py")576    st.stop()577 578# Get recommendations579with st.spinner("Generating personalized recommendations..."):580    recommendations = api_client.get_recommendations()581 582# Display recommendations583st.subheader(f"Based on your {profile.risk_tolerance} risk profile")584 585for idx, rec in enumerate(recommendations):586    with st.container():587        col1, col2 = st.columns([2, 1])588        589        with col1:590            st.markdown(f"### {idx+1}. {rec.product_name}")591            st.caption(f"{rec.product_type} | Ticker: {rec.ticker}")592            st.markdown(rec.description)593            594            # Rationale595            with st.expander("Why this recommendation?"):596                st.markdown(rec.rationale)597                st.caption(f"**Sources**: {', '.join(rec.sources)}")598        599        with col2:600            # Metrics601            st.metric("Expected Return", f"{rec.expected_return}%")602            st.metric("Risk Level", rec.risk_level.title())603            st.metric("Allocation", f"{rec.suggested_allocation}%")604            605            # Action buttons606            if st.button("📊 View Details", key=f"details_{idx}"):607                st.session_state.selected_product = rec.ticker608                # Show detailed modal609            610            if st.button("💬 Ask About This", key=f"ask_{idx}"):611                st.session_state.prefill_chat = f"Tell me more about {rec.product_name}"612                st.switch_page("pages/2_💬_Chat.py")613        614        st.divider()615 616# Market context617with st.expander("📰 Current Market Context"):618    st.markdown("### Recent Market News")619    for news in recommendations.market_context:620        st.markdown(f"- **{news.headline}** ({news.source})")621        st.caption(news.summary)622 623# Refresh button624if st.button("🔄 Refresh Recommendations"):625    st.rerun()626```627 628---629 630### Component Examples631 632```python633# ui/components/charts.py634import plotly.express as px635import plotly.graph_objects as go636 637def create_allocation_chart(allocation_data):638    """Create pie chart for asset allocation"""639    fig = px.pie(640        values=allocation_data.values(),641        names=allocation_data.keys(),642        title="Asset Allocation",643        hole=0.4644    )645    fig.update_traces(textposition='inside', textinfo='percent+label')646    return fig647 648def create_performance_chart(performance_history):649    """Create line chart for portfolio performance"""650    fig = go.Figure()651    652    fig.add_trace(go.Scatter(653        x=performance_history.dates,654        y=performance_history.values,655        mode='lines+markers',656        name='Portfolio Value',657        line=dict(color='#1f77b4', width=2)658    ))659    660    fig.update_layout(661        title="Portfolio Performance",662        xaxis_title="Date",663        yaxis_title="Value ($)",664        hovermode='x unified'665    )666    667    return fig668```669 670```python671# ui/components/chat_ui.py672import streamlit as st673 674def show_agent_indicator(agent_name):675    """Show which agent is responding"""676    agent_colors = {677        "orchestrator": "🎯",678        "asset_agent": "📊",679        "profiling_agent": "👤",680        "advisory_agent": "💡"681    }682    683    agent_labels = {684        "orchestrator": "Orchestrator",685        "asset_agent": "Asset Analyst",686        "profiling_agent": "Profile Guide",687        "advisory_agent": "Investment Advisor"688    }689    690    icon = agent_colors.get(agent_name, "🤖")691    label = agent_labels.get(agent_name, agent_name.title())692    693    st.caption(f"{icon} **{label}** is responding")694```695 696---697 698## Phase 3: Multi-User Support699 700### Overview701 702Phase 3 adds proper user management, authentication, and data isolation to support multiple users accessing the system simultaneously.703 704### Architecture Components705 706```707┌─────────────────────────────────────────────────┐708│              Frontend (Streamlit)               │709│  - Login/Registration UI                        │710│  - Session Management                           │711│  - User Context                                 │712└────────────────┬────────────────────────────────┘713                 │714                 │ HTTP/WebSocket715                 │716┌────────────────▼────────────────────────────────┐717│           Backend API (FastAPI)                 │718│  - User Authentication                          │719│  - JWT Token Management                         │720│  - User Context Middleware                      │721└────────────────┬────────────────────────────────┘722                 │723        ┌────────┴────────┐724        │                 │725┌───────▼──────┐  ┌──────▼────────┐726│  User DB     │  │  User Data DB │727│  (Auth)      │  │  (Portfolios, │728│              │  │   Profiles)   │729└──────────────┘  └───────────────┘730```731 732---733 734### User Authentication System735 736#### Database Schema737 738```python739# backend/src/auth/models.py740from sqlalchemy import Column, String, DateTime, Boolean741from sqlalchemy.orm import declarative_base742import uuid743from datetime import datetime744 745Base = declarative_base()746 747class User(Base):748    __tablename__ = "users"749    750    user_id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))751    email = Column(String, unique=True, nullable=False)752    username = Column(String, unique=True, nullable=False)753    hashed_password = Column(String, nullable=False)754    full_name = Column(String)755    756    created_at = Column(DateTime, default=datetime.utcnow)757    last_login = Column(DateTime)758    is_active = Column(Boolean, default=True)759    is_verified = Column(Boolean, default=False)760    761    # Preferences762    theme = Column(String, default="light")763    language = Column(String, default="en")764 765class UserSession(Base):766    __tablename__ = "user_sessions"767    768    session_id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))769    user_id = Column(String, nullable=False)770    access_token = Column(String, nullable=False)771    refresh_token = Column(String)772    773    created_at = Column(DateTime, default=datetime.utcnow)774    expires_at = Column(DateTime, nullable=False)775    last_activity = Column(DateTime, default=datetime.utcnow)776    777    ip_address = Column(String)778    user_agent = Column(String)779```780 781#### Authentication Backend782 783```python784# backend/src/auth/auth_service.py785from passlib.context import CryptContext786from jose import JWTError, jwt787from datetime import datetime, timedelta788from typing import Optional789 790pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")791 792SECRET_KEY = "your-secret-key-here"  # Use environment variable793ALGORITHM = "HS256"794ACCESS_TOKEN_EXPIRE_MINUTES = 30795 796class AuthService:797    @staticmethod798    def hash_password(password: str) -> str:799        """Hash a password"""800        return pwd_context.hash(password)801    802    @staticmethod803    def verify_password(plain_password: str, hashed_password: str) -> bool:804        """Verify a password against hash"""805        return pwd_context.verify(plain_password, hashed_password)806    807    @staticmethod808    def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):809        """Create JWT access token"""810        to_encode = data.copy()811        812        if expires_delta:813            expire = datetime.utcnow() + expires_delta814        else:815            expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)816        817        to_encode.update({"exp": expire})818        encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)819        return encoded_jwt820    821    @staticmethod822    def verify_token(token: str) -> Optional[dict]:823        """Verify and decode JWT token"""824        try:825            payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])826            return payload827        except JWTError:828            return None829    830    async def register_user(831        self, 832        email: str, 833        username: str, 834        password: str,835        full_name: str836    ) -> User:837        """Register a new user"""838        # Check if user exists839        existing_user = await self.get_user_by_email(email)840        if existing_user:841            raise ValueError("User with this email already exists")842        843        # Create user844        hashed_password = self.hash_password(password)845        user = User(846            email=email,847            username=username,848            hashed_password=hashed_password,849            full_name=full_name850        )851        852        # Save to database853        await self.db.save(user)854        return user855    856    async def authenticate_user(self, email: str, password: str) -> Optional[User]:857        """Authenticate user with email and password"""858        user = await self.get_user_by_email(email)859        860        if not user:861            return None862        863        if not self.verify_password(password, user.hashed_password):864            return None865        866        # Update last login867        user.last_login = datetime.utcnow()868        await self.db.save(user)869        870        return user871```872 873#### Authentication Endpoints874 875```python876# backend/src/api/routes/auth.py877from fastapi import APIRouter, Depends, HTTPException, status878from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm879from pydantic import BaseModel, EmailStr880 881router = APIRouter(prefix="/api/v1/auth", tags=["authentication"])882 883oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")884 885class UserRegister(BaseModel):886    email: EmailStr887    username: str888    password: str889    full_name: str890 891class UserLogin(BaseModel):892    email: EmailStr893    password: str894 895class Token(BaseModel):896    access_token: str897    token_type: str898    user_info: dict899 900@router.post("/register", response_model=Token)901async def register(user_data: UserRegister, auth_service: AuthService = Depends()):902    """Register a new user"""903    try:904        user = await auth_service.register_user(905            email=user_data.email,906            username=user_data.username,907            password=user_data.password,908            full_name=user_data.full_name909        )910        911        # Create access token912        access_token = auth_service.create_access_token(913            data={"sub": user.email, "user_id": user.user_id}914        )915        916        return Token(917            access_token=access_token,918            token_type="bearer",919            user_info={920                "user_id": user.user_id,921                "email": user.email,922                "username": user.username,923                "full_name": user.full_name924            }925        )926    927    except ValueError as e:928        raise HTTPException(929            status_code=status.HTTP_400_BAD_REQUEST,930            detail=str(e)931        )932 933@router.post("/login", response_model=Token)934async def login(935    form_data: OAuth2PasswordRequestForm = Depends(),936    auth_service: AuthService = Depends()937):938    """Login user"""939    user = await auth_service.authenticate_user(940        email=form_data.username,  # Using username field for email941        password=form_data.password942    )943    944    if not user:945        raise HTTPException(946            status_code=status.HTTP_401_UNAUTHORIZED,947            detail="Incorrect email or password",948            headers={"WWW-Authenticate": "Bearer"},949        )950    951    # Create access token952    access_token = auth_service.create_access_token(953        data={"sub": user.email, "user_id": user.user_id}954    )955    956    return Token(957        access_token=access_token,958        token_type="bearer",959        user_info={960            "user_id": user.user_id,961            "email": user.email,962            "username": user.username,963            "full_name": user.full_name964        }965    )966 967@router.get("/me")968async def get_current_user(token: str = Depends(oauth2_scheme), auth_service: AuthService = Depends()):969    """Get current user info"""970    payload = auth_service.verify_token(token)971    972    if not payload:973        raise HTTPException(974            status_code=status.HTTP_401_UNAUTHORIZED,975            detail="Could not validate credentials",976            headers={"WWW-Authenticate": "Bearer"},977        )978    979    user = await auth_service.get_user_by_id(payload["user_id"])980    981    if not user:982        raise HTTPException(status_code=404, detail="User not found")983    984    return {985        "user_id": user.user_id,986        "email": user.email,987        "username": user.username,988        "full_name": user.full_name989    }990 991@router.post("/logout")992async def logout(token: str = Depends(oauth2_scheme)):993    """Logout user (invalidate token)"""994    # In production, add token to blacklist995    return {"message": "Successfully logged out"}996```997 998---999 1000### Frontend Authentication Components1001 1002```python1003# ui/components/auth.py1004import streamlit as st1005import requests1006from typing import Optional1007 1008API_BASE_URL = "http://localhost:8000"1009 1010def show_login_page():1011    """Display login page"""1012    st.title("🔐 Login to Wealth Intelligence")1013    1014    tab1, tab2 = st.tabs(["Login", "Register"])1015    1016    with tab1:1017        with st.form("login_form"):1018            st.subheader("Welcome Back")1019            email = st.text_input("Email")1020            password = st.text_input("Password", type="password")1021            1022            col1, col2 = st.columns(2)1023            with col1:1024                submit = st.form_submit_button("Login", use_container_width=True)1025            with col2:1026                if st.form_submit_button("Forgot Password?", use_container_width=True):1027                    st.info("Password reset feature coming soon!")1028            1029            if submit:1030                if login_user(email, password):1031                    st.success("Login successful!")1032                    st.rerun()1033                else:1034                    st.error("Invalid email or password")1035    1036    with tab2:1037        with st.form("register_form"):1038            st.subheader("Create Account")1039            full_name = st.text_input("Full Name")1040            username = st.text_input("Username")1041            email = st.text_input("Email")1042            password = st.text_input("Password", type="password")1043            password_confirm = st.text_input("Confirm Password", type="password")1044            1045            submit = st.form_submit_button("Create Account", use_container_width=True)1046            1047            if submit:1048                if password != password_confirm:1049                    st.error("Passwords don't match")1050                elif len(password) < 8:1051                    st.error("Password must be at least 8 characters")1052                else:1053                    if register_user(email, username, password, full_name):1054                        st.success("Account created! You can now login.")1055                    else:1056                        st.error("Registration failed. Email may already exist.")1057 1058def login_user(email: str, password: str) -> bool:1059    """Login user and store token"""1060    try:1061        response = requests.post(1062            f"{API_BASE_URL}/api/v1/auth/login",1063            data={"username": email, "password": password}  # OAuth2 uses 'username'1064        )1065        1066        if response.status_code == 200:1067            data = response.json()1068            1069            # Store in session state1070            st.session_state.access_token = data["access_token"]1071            st.session_state.user_info = data["user_info"]1072            st.session_state.authenticated = True1073            1074            return True1075        1076        return False1077    1078    except Exception as e:1079        st.error(f"Login error: {str(e)}")1080        return False1081 1082def register_user(email: str, username: str, password: str, full_name: str) -> bool:1083    """Register new user"""1084    try:1085        response = requests.post(1086            f"{API_BASE_URL}/api/v1/auth/register",1087            json={1088                "email": email,1089                "username": username,1090                "password": password,1091                "full_name": full_name1092            }1093        )1094        1095        if response.status_code == 200:1096            return True1097        1098        return False1099    1100    except Exception as e:1101        st.error(f"Registration error: {str(e)}")1102        return False1103```1104 1105```python1106# ui/utils/auth_manager.py1107import streamlit as st1108from components.auth import show_login_page1109import requests1110 1111API_BASE_URL = "http://localhost:8000"1112 1113def check_authentication() -> bool:1114    """Check if user is authenticated"""1115    if not st.session_state.get("authenticated", False):1116        show_login_page()1117        return False1118    1119    # Verify token is still valid1120    if not verify_token():1121        st.session_state.authenticated = False1122        st.warning("Session expired. Please login again.")1123        show_login_page()1124        return False1125    1126    return True1127 1128def require_authentication():1129    """Decorator-like function to require authentication"""1130    if not check_authentication():1131        st.stop()1132 1133def verify_token() -> bool:1134    """Verify access token is still valid"""1135    if "access_token" not in st.session_state:1136        return False1137    1138    try:1139        response = requests.get(1140            f"{API_BASE_URL}/api/v1/auth/me",1141            headers={"Authorization": f"Bearer {st.session_state.access_token}"}1142        )1143        1144        return response.status_code == 2001145    1146    except:1147        return False1148 1149def logout():1150    """Logout current user"""1151    if "access_token" in st.session_state:1152        try:1153            requests.post(1154                f"{API_BASE_URL}/api/v1/auth/logout",1155                headers={"Authorization": f"Bearer {st.session_state.access_token}"}1156            )1157        except:1158            pass1159    1160    # Clear session1161    st.session_state.clear()1162```1163 1164---1165 1166### Data Isolation1167 1168#### User-Scoped Data Storage1169 1170```python1171# backend/src/data/user_data.py1172from typing import Optional1173from datetime import datetime1174 1175class UserDataManager:1176    """Manage user-specific data with proper isolation"""1177    1178    def __init__(self, db_session):1179        self.db = db_session1180    1181    async def get_user_portfolio(self, user_id: str) -> Optional[AssetPortfolio]:1182        """Get portfolio for specific user only"""1183        portfolio = await self.db.query(AssetPortfolio).filter(1184            AssetPortfolio.user_id == user_id1185        ).first()1186        1187        return portfolio1188    1189    async def get_user_profile(self, user_id: str) -> Optional[InvestmentProfile]:1190        """Get investment profile for specific user only"""1191        profile = await self.db.query(InvestmentProfile).filter(1192            InvestmentProfile.user_id == user_id1193        ).first()1194        1195        return profile1196    1197    async def save_user_data(self, user_id: str, data: dict):1198        """Save data for specific user"""1199        # Always verify user_id matches1200        if data.get("user_id") != user_id:

Showing the first 1,200 of 1580 lines. Download the file for the rest.