# Project export: InvestAI

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: If AI’s taking your job, let it fund your freedom.
- Devpost: https://devpost.com/software/investai-be9hua
- GitHub: https://github.com/Hui-Hwoo/Invest-Agent.git
- Team: 1 GitHub contributor(s) — Hui Hwoo (5 commits)

## Devpost submission (written by the team)

### Inspiration

AI is powerful—we’ve all imagined a future where it helps us work while we just collect the paycheck. But if our ultimate goal is financial freedom, why stop there? Why not let AI skip the "work" altogether and go straight to making money for us? That’s the vision behind InvestAI: an autonomous agent that continuously adapts its trading strategies to follow market trends and profit directly from price movements—no job required.

### What it does

InvestAI generates algorithmic trading strategies for any stock you choose—and continuously updates them to adapt to market trends. It stays rational, processes massive amounts of data, filters out noise, and extracts the most valuable and truthful insights to help you make smarter investments. Got your own trading idea? InvestAI can iterate, verify, and evaluate it within an hour. Experience is no longer required—AI helps you capture the probabilities hidden in every possible outcome.

### Accomplishments we're proud of

We’ve proven that when you trust AI and give it time, it can discover profitable strategies for virtually any stock. In just two hours, InvestAI was able to generate a strategy that projects an annual return of nearly 20%. All we had to do was let the AI explore, experiment, and optimize—then come back to see the results.

### What's next

Next, we plan to expand the range of tradable assets and increase the trading frequency. This will give InvestAI more flexibility to explore diverse opportunities and generate more innovative, adaptive, and stable strategies. The goal: reduce risk, boost returns, and grow your wealth with even greater confidence.

## README (from the GitHub repository)

# InvestAI

<div align="center">
   <img src="./investAI.png" title="MoneyYesWorkNo" alt="MoneyYesWorkNo" width="30%">
   <p> If AI’s taking your job, let it fund your freedom. </p>
</div>

## Inspiration

AI is powerful—we’ve all imagined a future where it helps us work while we just collect the paycheck. But if our ultimate goal is financial freedom, why stop there? Why not let AI skip the "work" altogether and go straight to making money for us? That’s the vision behind InvestAI: an autonomous agent that continuously adapts its trading strategies to follow market trends and profit directly from price movements—no job required.

## What it does

InvestAI generates algorithmic trading strategies for any stock you choose—and continuously updates them to adapt to market trends. It stays rational, processes massive amounts of data, filters out noise, and extracts the most valuable and truthful insights to help you make smarter investments.

Got your own trading idea? InvestAI can iterate, verify, and evaluate it within an hour. Experience is no longer required—AI helps you capture the probabilities hidden in every possible outcome.

## Accomplishments that we're proud of

We’ve proven that when you trust AI and give it time, it can discover profitable strategies for virtually any stock. In just two hours, InvestAI was able to generate a strategy that projects an annual return of nearly 20%. All we had to do was let the AI explore, experiment, and optimize—then come back to see the results.

<div align="center">
   <img src="./result.png" title="Result" alt="Result" width="50%">
</div>

## What's next for InvestAI

Next, we plan to expand the range of tradable assets and increase the trading frequency. This will give InvestAI more flexibility to explore diverse opportunities and generate more innovative, adaptive, and stable strategies. The goal: reduce risk, boost returns, and grow your wealth with even greater confidence.

## Getting Started: Development and Local Testing

Follow these steps to get the application running locally for development and testing.

**1. Prerequisites:**

-   Node.js and npm (or yarn/pnpm)
-   Python 3.11+
-   **`GEMINI_API_KEY`**: The backend agent requires a Google Gemini API key.
    1.  Navigate to the `backend/` directory.
    2.  Create a file named `.env` by copying the `backend/.env.example` file.
    3.  Open the `.env` file and add your Gemini API key: `GEMINI_API_KEY="YOUR_ACTUAL_API_KEY"`

**2. Install Dependencies:**

**Backend:**

```bash
cd backend
pip install .
```

**Frontend:**

```bash
cd frontend
npm install
```

**3. Run Development Servers:**

**Backend & Frontend:**

```bash
make dev
```

This will run the backend and frontend development servers. Open your browser and navigate to the frontend development server URL (e.g., `http://localhost:5173/app`).

_Alternatively, you can run the backend and frontend development servers separately. For the backend, open a terminal in the `backend/` directory and run `langgraph dev`. The backend API will be available at `http://127.0.0.1:2024`. It will also open a browser window to the LangGraph UI. For the frontend, open a terminal in the `frontend/` directory and run `npm run dev`. The frontend will be available at `http://localhost:5173`._

## How the Backend Agent Works (High-Level)

The core of the backend is a LangGraph agent defined in `backend/src/agent/graph.py`. It follows these steps:

<img src="./agent.png" title="Agent Flow" alt="Agent Flow" width="50%">

1.  **Generate Initial Queries:** Based on your input, it generates a set of initial search queries using a Gemini model.
2.  **Web Research:** For each query, it uses the Gemini model with the Google Search API to find relevant web pages.
3.  **Reflection & Knowledge Gap Analysis:** The agent analyzes the search results to determine if the information is sufficient or if there are knowledge gaps. It uses a Gemini model for this reflection process.
4.  **Iterative Refinement:** If gaps are found or the information is insufficient, it generates follow-up queries and repeats the web research and reflection steps (up to a configured maximum number of loops).
5.  **Finalize Answer:** Once the research is deemed sufficient, the agent synthesizes the gathered information into a coherent answer, including citations from the web sources, using a Gemini model.

## CLI Example

For quick one-off questions you can execute the agent from the command line. The
script `backend/examples/cli_research.py` runs the LangGraph agent and prints the
final answer:

```bash
cd backend
python examples/cli_research.py "What are the latest trends in renewable energy?"
```

## Deployment

In production, the backend server serves the optimized static frontend build. LangGraph requires a Redis instance and a Postgres database. Redis is used as a pub-sub broker to enable streaming real time output from background runs. Postgres is used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics. For more details on how to deploy the backend server, take a look at the [LangGraph Documentation](https://langchain-ai.github.io/langgraph/concepts/deployment_options/). Below is an example of how to build a Docker image that includes the optimized frontend build and the backend server and run it via `docker-compose`.

_Note: For the docker-compose.yml example you need a LangSmith API key, you can get one from [LangSmith](https://smith.langchain.com/settings)._

_Note: If you are not running the docker-compose.yml example or exposing the backend server to the public internet, you should update the `apiUrl` in the `frontend/src/App.tsx` file to your host. Currently the `apiUrl` is set to `http://localhost:8123` for docker-compose or `http://localhost:2024` for development._

**1. Build the Docker Image:**

Run the following command from the **project root directory**:

```bash
docker build -t gemini-fullstack-langgraph -f Dockerfile .
```

**2. Run the Production Server:**

```bash
GEMINI_API_KEY=<your_gemini_api_key> LANGSMITH_API_KEY=<your_langsmith_api_key> docker-compose up
```

Open your browser and navigate to `http://localhost:8123/app/` to see the application. The API will be available at `http://localhost:8123`.

## Technologies Used

-   [React](https://reactjs.org/) (with [Vite](https://vitejs.dev/)) - For the frontend user interface.
-   [Tailwind CSS](https://tailwindcss.com/) - For styling.
-   [Shadcn UI](https://ui.shadcn.com/) - For components.
-   [LangGraph](https://github.com/langchain-ai/langgraph) - For building the backend research agent.
-   [Google Gemini](https://ai.google.dev/models/gemini) - LLM for query generation, reflection, and answer synthesis.

## License

This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details.


## Detected evidence (automated analysis)

Indexed codebase: 49 recognized source files, 164 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- LangChain (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Flask (technology) — claimed on Devpost, not found in the code
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (64 of 64)

```
.gitignore
AGENTS.md
backend/.env.example
backend/.gitignore
backend/langgraph.json
backend/LICENSE
backend/README.md
backend/requirements.txt
backend/setup.py
backend/src/agent/__init__.py
backend/src/agent/app.py
backend/src/agent/demo.py
backend/src/agent/graph.py
backend/src/agent/nodes/__init__.py
backend/src/agent/nodes/aggregate.py
backend/src/agent/nodes/container/__init__.py
backend/src/agent/nodes/container/container.py
backend/src/agent/nodes/container/data/data.csv
backend/src/agent/nodes/container/data/metrics.py
backend/src/agent/nodes/container/data/strategies/template.py
backend/src/agent/nodes/finish.py
backend/src/agent/nodes/implement.py
backend/src/agent/nodes/initialize/__init__.py
backend/src/agent/nodes/initialize/initialize.py
backend/src/agent/nodes/state.py
backend/src/agent/nodes/think.py
backend/src/agent/other/configuration.py
backend/src/agent/other/prompts.py
backend/src/agent/other/tools_and_schemas.py
backend/src/agent/other/utils.py
docker-compose.yml
Dockerfile
frontend/.gitignore
frontend/components.json
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/src/App.tsx
frontend/src/components/ActivityTimeline.tsx
frontend/src/components/ChatMessagesView.tsx
frontend/src/components/InputForm.tsx
frontend/src/components/ui/badge.tsx
frontend/src/components/ui/button.tsx
frontend/src/components/ui/card.tsx
frontend/src/components/ui/input.tsx
frontend/src/components/ui/scroll-area.tsx
frontend/src/components/ui/select.tsx
frontend/src/components/ui/tabs.tsx
frontend/src/components/ui/textarea.tsx
frontend/src/components/WelcomeScreen.tsx
frontend/src/global.css
frontend/src/lib/utils.ts
frontend/src/main.tsx
frontend/src/vite-env.d.ts
frontend/tsconfig.json
frontend/tsconfig.node.json
frontend/vite.config.ts
LICENSE
Makefile
README.md
strategies/strategy-1_1.py
strategies/strategy-2_1.py
strategies/strategy-3_1.py
strategies/template.py
```

### Dependencies

- backend/requirements.txt: annotated-types@==0.7.0, anthropic@==0.54.0, anyio@==4.9.0, backtrader@==1.9.78.123, blockbuster@==1.5.24, certifi@==2025.6.15, cffi@==1.17.1, charset-normalizer@==3.4.2, click@==8.2.1, cloudpickle@==3.1.1, contourpy@==1.3.2, cryptography@==44.0.3, cycler@==0.12.1, distro@==1.9.0, docker@==7.1.0, fastapi@==0.115.13, fonttools@==4.58.4, forbiddenfruit@==0.1.4, h11@==0.16.0, httpcore@==1.0.9, httpx@==0.28.1, idna@==3.10, jiter@==0.10.0, jsonpatch@==1.33, jsonpointer@==3.0.0, jsonschema_rs@==0.29.1, kiwisolver@==1.4.8, langchain-core@==0.3.66, langgraph@==0.4.8, langgraph-api@==0.2.61, langgraph-checkpoint@==2.1.0, langgraph-cli@==0.3.3, langgraph-prebuilt@==0.2.2, langgraph-runtime-inmem@==0.3.0, langgraph-sdk@==0.1.70, langsmith@==0.4.1, matplotlib@==3.10.3, numpy@==2.3.1, orjson@==3.10.18, ormsgpack@==1.10.0, packaging@==24.2, pandas@==2.3.0, pillow@==11.2.1, pycparser@==2.22, pydantic@==2.11.7, pydantic_core@==2.33.2, PyJWT@==2.10.1, pyparsing@==3.2.3, python-dateutil@==2.9.0.post0, python-dotenv@==1.1.0, pytz@==2025.2, PyYAML@==6.0.2, requests@==2.32.4, requests-toolbelt@==1.0.0, six@==1.17.0, sniffio@==1.3.1, sse-starlette@==2.1.3, starlette@==0.46.2, structlog@==25.4.0, tenacity@==9.1.2, truststore@==0.10.1, typing_extensions@==4.14.0, typing-inspection@==0.4.1, tzdata@==2025.2, urllib3@==2.5.0, uvicorn@==0.34.3, watchfiles@==1.1.0, xxhash@==3.5.0, zstandard@==0.23.0
- frontend/package.json: @eslint/js@^9.22.0, @langchain/core@^0.3.55, @langchain/langgraph-sdk@^0.0.74, @radix-ui/react-scroll-area@^1.2.8, @radix-ui/react-select@^2.2.4, @radix-ui/react-slot@^1.2.2, @radix-ui/react-tabs@^1.1.11, @radix-ui/react-tooltip@^1.2.6, @tailwindcss/vite@^4.1.5, @types/node@^22.15.17, @types/react@^19.1.2, @types/react-dom@^19.1.3, @vitejs/plugin-react-swc@^3.9.0, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9.22.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.19, globals@^16.0.0, lucide-react@^0.508.0, react@^19.0.0, react-dom@^19.0.0, react-markdown@^9.0.3, react-router-dom@^7.5.3, tailwind-merge@^3.2.0, tailwindcss@^4.1.5, tw-animate-css@^1.2.9, typescript@~5.7.2, typescript-eslint@^8.26.1, vite@^6.3.4

### Recent commits (newest first)

- docs: update README image width and add AGENTS.md, images, strategies
- Add demo video
- bug fix
- fix some bugs
- Add frontend
- workable backend
- updates to prompt
- Merge pull request #45 from smell-of-curry/patch-1
- Merge branch 'main' into patch-1
- Merge pull request #43 from nisaharan/main
- Merge pull request #35 from dkqjrm/chore/typo
- Merge pull request #17 from kahirokunn/fix/ime-input-form-submission
- Merge pull request #7 from CharlesCNorton/patch-1
- Merge pull request #5 from LeaderOnePro/main
- Merge pull request #76 from tigermlt/patch-1
- Merge pull request #86 from 7Gamil/Improve-image-scaling-on-desktop-view
- Merge pull request #90 from cscandore/query-writer-typo
- Merge pull request #15 from vietnamesekid/main
- Merge branch 'main' into main
- Merge pull request #44 from nandsha/docs/fix-python-version-requirement

## Key source files (fetched from GitHub, selected and truncated for size)

### AGENTS.md

```markdown
# AGENTS.md

This file provides guidance to agents (i.e., ADAL) when working with code in this repository.

## Scope & Purpose

This repository is a split frontend/backend project for **LLM-driven trading strategy generation and backtesting**:

- `frontend/`: React + Vite UI
- `backend/`: LangGraph workflow + Dockerized strategy compilation/evaluation
- Root: orchestration (`Makefile`, `Dockerfile`, `docker-compose.yml`)

Use this guide to quickly find commands, entry points, and non-obvious flow constraints.

---

## 1) Essential Commands (Verified)

## Root-level orchestration

```bash
# Start both frontend and backend (Makefile)
make dev

# Start frontend only
make dev-frontend

# Start backend only
make dev-backend
```

Source: `Makefile`

### Gotcha
- `make dev` runs `make dev-frontend & make dev-backend` in one shell; stopping one process may not gracefully stop the other.

---

## Frontend (`frontend/`)

```bash
cd frontend
npm install
npm run dev
npm run build
npm run lint
npm run preview
```

Source: `frontend/package.json`

### Single-test/subset tests
- No frontend test scripts are defined in `package.json` (no `test` command present).

---

## Backend (`backend/`)

```bash
cd backend

# Install backend package and dependencies
pip install .

# Run backend dev server (LangGraph API dev mode)
langgraph dev
```

Sources: root `README.md`, `Makefile`, `backend/setup.py`, `backend/langgraph.json`

### Single-test/subset tests
- No Python test suite was found (`**/*test*.py` matched none).
- Closest reproducible evaluation command is strategy metrics execution inside runner/container:
  ```bash
  python metrics.py --strategy-path strategies/strategy-<id>.py --result-path logs/res-<id>.json
  ```
  (this is executed inside the backend runner container by `implement.py`)

---

## Docker / production-like run

```bash
# Build image from project root
docker build -t gemini-fullstack-langgraph -f Dockerfile .

# Run full stack with Redis + Postgres + API
GEMINI_API_KEY=<key> LANGSMITH_API_KEY=<key> docker-compose up
```

Sources: root `README.md`, `Dockerfile`, `docker-compose.yml`

### Infra dependencies in compose
- Redis service: `langgraph-redis`
- Postgres service: `langgraph-postgres` (mapped host `5433 -> 5432`)
- API service: `langgraph-api` on port `8123`

---

## 2) Critical Gotchas (Read Before Editing)

1. **API key mismatch in docs vs runtime path**
   - Root README emphasizes `GEMINI_API_KEY`.
   - Actual graph workflow nodes use Anthropic client and require `ANTHROPIC_API_KEY` in `initialize.py`; it raises immediately if missing.

2. **Frontend currently ignores user stock input**
   - In `frontend/src/App.tsx`, submit always sends:
     ```ts
     stock_symbol: "QQQ"
     ```
   - User-entered text/effort/model values are collected but effectively not passed through for backend strategy generation.

3. **Frontend activity timeline expects different event names than backend graph**
   - UI listens for `generate_query`, `web_resear
[truncated — 6855 more characters]
```

### docker-compose.yml

```yaml
volumes:
  langgraph-data:
    driver: local
services:
  langgraph-redis:
    image: docker.io/redis:6
    container_name: langgraph-redis
    healthcheck:
      test: redis-cli ping
      interval: 5s
      timeout: 1s
      retries: 5
  langgraph-postgres:
    image: docker.io/postgres:16
    container_name: langgraph-postgres
    ports:
      - "5433:5432"
    environment:
      POSTGRES_DB: postgres
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - langgraph-data:/var/lib/postgresql/data
    healthcheck:
      test: pg_isready -U postgres
      start_period: 10s
      timeout: 1s
      retries: 5
      interval: 5s
  langgraph-api:
    image: gemini-fullstack-langgraph
    container_name: langgraph-api
    ports:
      - "8123:8000"
    depends_on:
      langgraph-redis:
        condition: service_healthy
      langgraph-postgres:
        condition: service_healthy
    environment:
      GEMINI_API_KEY: ${GEMINI_API_KEY}
      LANGSMITH_API_KEY: ${LANGSMITH_API_KEY}
      REDIS_URI: redis://langgraph-redis:6379
      POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable

```

### Dockerfile

```
# Stage 1: Build React Frontend
FROM node:20-alpine AS frontend-builder

# Set working directory for frontend
WORKDIR /app/frontend

# Copy frontend package files and install dependencies
COPY frontend/package.json ./
COPY frontend/package-lock.json ./
# If you use yarn or pnpm, adjust accordingly (e.g., copy yarn.lock or pnpm-lock.yaml and use yarn install or pnpm install)
RUN npm install

# Copy the rest of the frontend source code
COPY frontend/ ./

# Build the frontend
RUN npm run build

# Stage 2: Python Backend
FROM docker.io/langchain/langgraph-api:3.11

# -- Install UV --
# First install curl, then install UV using the standalone installer
RUN apt-get update && apt-get install -y curl && \
    curl -LsSf https://astral.sh/uv/install.sh | sh && \
    apt-get clean && rm -rf /var/lib/apt/lists/*
ENV PATH="/root/.local/bin:$PATH"
# -- End of UV installation --

# -- Copy built frontend from builder stage --
# The app.py expects the frontend build to be at ../frontend/dist relative to its own location.
# If app.py is at /deps/backend/src/agent/app.py, then ../frontend/dist resolves to /deps/frontend/dist.
COPY --from=frontend-builder /app/frontend/dist /deps/frontend/dist
# -- End of copying built frontend --

# -- Adding local package . --
ADD backend/ /deps/backend
# -- End of local package . --

# -- Installing all local dependencies using UV --
# First, we need to ensure pip is available for UV to use
RUN uv pip install --system pip setuptools wheel
# Install dependencies with UV, respecting constraints
RUN cd /deps/backend && \
    PYTHONDONTWRITEBYTECODE=1 UV_SYSTEM_PYTHON=1 uv pip install --system -c /api/constraints.txt -e .
# -- End of local dependencies install --
ENV LANGGRAPH_HTTP='{"app": "/deps/backend/src/agent/app.py:app"}'
ENV LANGSERVE_GRAPHS='{"agent": "/deps/backend/src/agent/graph.py:graph"}'

# -- Ensure user deps didn't inadvertently overwrite langgraph-api
# Create all required directories that the langgraph-api package expects
RUN mkdir -p /api/langgraph_api /api/langgraph_runtime /api/langgraph_license /api/langgraph_storage && \
    touch /api/langgraph_api/__init__.py /api/langgraph_runtime/__init__.py /api/langgraph_license/__init__.py /api/langgraph_storage/__init__.py
# Use pip for this specific package as it has poetry-based build requirements
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir --no-deps -e /api
# -- End of ensuring user deps didn't inadvertently overwrite langgraph-api --
# -- Removing pip from the final image (but keeping UV) --
RUN uv pip uninstall --system pip setuptools wheel && \
    rm -rf /usr/local/lib/python*/site-packages/pip* /usr/local/lib/python*/site-packages/setuptools* /usr/local/lib/python*/site-packages/wheel* && \
    find /usr/local/bin -name "pip*" -delete
# -- End of pip removal --

WORKDIR /deps/backend

```

### backend/requirements.txt

```
annotated-types==0.7.0
anthropic==0.54.0
anyio==4.9.0
backtrader==1.9.78.123
blockbuster==1.5.24
certifi==2025.6.15
cffi==1.17.1
charset-normalizer==3.4.2
click==8.2.1
cloudpickle==3.1.1
contourpy==1.3.2
cryptography==44.0.3
cycler==0.12.1
distro==1.9.0
docker==7.1.0
fastapi==0.115.13
fonttools==4.58.4
forbiddenfruit==0.1.4
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.10
jiter==0.10.0
jsonpatch==1.33
jsonpointer==3.0.0
jsonschema_rs==0.29.1
kiwisolver==1.4.8
langchain-core==0.3.66
langgraph==0.4.8
langgraph-api==0.2.61
langgraph-checkpoint==2.1.0
langgraph-cli==0.3.3
langgraph-prebuilt==0.2.2
langgraph-runtime-inmem==0.3.0
langgraph-sdk==0.1.70
langsmith==0.4.1
matplotlib==3.10.3
numpy==2.3.1
orjson==3.10.18
ormsgpack==1.10.0
packaging==24.2
pandas==2.3.0
pillow==11.2.1
pycparser==2.22
pydantic==2.11.7
pydantic_core==2.33.2
PyJWT==2.10.1
pyparsing==3.2.3
python-dateutil==2.9.0.post0
python-dotenv==1.1.0
pytz==2025.2
PyYAML==6.0.2
requests==2.32.4
requests-toolbelt==1.0.0
six==1.17.0
sniffio==1.3.1
sse-starlette==2.1.3
starlette==0.46.2
structlog==25.4.0
tenacity==9.1.2
truststore==0.10.1
typing-inspection==0.4.1
typing_extensions==4.14.0
tzdata==2025.2
urllib3==2.5.0
uvicorn==0.34.3
watchfiles==1.1.0
xxhash==3.5.0
zstandard==0.23.0

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@langchain/core": "^0.3.55",
    "@langchain/langgraph-sdk": "^0.0.74",
    "@radix-ui/react-scroll-area": "^1.2.8",
    "@radix-ui/react-select": "^2.2.4",
    "@radix-ui/react-slot": "^1.2.2",
    "@radix-ui/react-tabs": "^1.1.11",
    "@radix-ui/react-tooltip": "^1.2.6",
    "@tailwindcss/vite": "^4.1.5",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.508.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-markdown": "^9.0.3",
    "react-router-dom": "^7.5.3",
    "tailwind-merge": "^3.2.0",
    "tailwindcss": "^4.1.5"
  },
  "devDependencies": {
    "@eslint/js": "^9.22.0",
    "@types/node": "^22.15.17",
    "@types/react": "^19.1.2",
    "@types/react-dom": "^19.1.3",
    "@vitejs/plugin-react-swc": "^3.9.0",
    "eslint": "^9.22.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^16.0.0",
    "tw-animate-css": "^1.2.9",
    "typescript": "~5.7.2",
    "typescript-eslint": "^8.26.1",
    "vite": "^6.3.4"
  }
}

```

### frontend/src/main.tsx

```typescript
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import "./global.css";
import App from "./App.tsx";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>
);

```

### frontend/src/App.tsx

```typescript
import { useStream } from "@langchain/langgraph-sdk/react";
import type { Message } from "@langchain/langgraph-sdk";
import { useState, useEffect, useRef, useCallback } from "react";
import { ProcessedEvent } from "@/components/ActivityTimeline";
import { WelcomeScreen } from "@/components/WelcomeScreen";
import { ChatMessagesView } from "@/components/ChatMessagesView";
import { Button } from "@/components/ui/button";

export default function App() {
  const [processedEventsTimeline, setProcessedEventsTimeline] = useState<
    ProcessedEvent[]
  >([]);
  const [historicalActivities, setHistoricalActivities] = useState<
    Record<string, ProcessedEvent[]>
  >({});
  const scrollAreaRef = useRef<HTMLDivElement>(null);
  const hasFinalizeEventOccurredRef = useRef(false);
  const [error, setError] = useState<string | null>(null);
  const thread = useStream<{
    // messages: Message[];
    // initial_search_query_count: number;
    // max_research_loops: number;
    // reasoning_model: string;
    stock_symbol: string;
  }>({
    apiUrl: import.meta.env.DEV
      ? "http://localhost:2024"
      : "http://localhost:8123",
    assistantId: "agent",
    messagesKey: "messages",
    onUpdateEvent: (event: any) => {
      let processedEvent: ProcessedEvent | null = null;
      if (event.generate_query) {
        processedEvent = {
          title: "Generating Search Queries",
          data: event.generate_query?.search_query?.join(", ") || "",
        };
      } else if (event.web_research) {
        const sources = event.web_research.sources_gathered || [];
        const numSources = sources.length;
        const uniqueLabels = [
          ...new Set(sources.map((s: any) => s.label).filter(Boolean)),
        ];
        const exampleLabels = uniqueLabels.slice(0, 3).join(", ");
        processedEvent = {
          title: "Web Research",
          data: `Gathered ${numSources} sources. Related to: ${
            exampleLabels || "N/A"
          }.`,
        };
      } else if (event.reflection) {
        processedEvent = {
          title: "Reflection",
          data: "Analysing Web Research Results",
        };
      } else if (event.finalize_answer) {
        processedEvent = {
          title: "Finalizing Answer",
          data: "Composing and presenting the final answer.",
        };
        hasFinalizeEventOccurredRef.current = true;
      }
      if (processedEvent) {
        setProcessedEventsTimeline((prevEvents) => [
          ...prevEvents,
          processedEvent!,
        ]);
      }
    },
    onError: (error: any) => {
      setError(error.message);
    },
  });

  useEffect(() => {
    if (scrollAreaRef.current) {
      const scrollViewport = scrollAreaRef.current.querySelector(
        "[data-radix-scroll-area-viewport]"
      );
      if (scrollViewport) {
        scrollViewport.scrollTop = scrollViewport.scrollHeight;
      }
    }
  }, [thread.messages]);

  useEffect(() => {
    if (
      hasFinalizeEventOccurredRef.current &&
      !thread.isLoading &&
      thread.messages.length > 0
    ) {
      const lastMessage = thread.messages[thread.messages.length - 1];
      if (lastMessage && lastMessage.type === "ai" && lastMessage.id) {
        setHistoricalActivities((prev) => ({
          ...prev,
          [lastMessage.id!]: [...processedEventsTimeline],
        }));
      }
      hasFinalizeEventOccurredRef.current = false;
    }
  }, [thread.messages, thread.isLoading, processedEventsTimeline]);

  const handleSubmit = useCallback(
    (submittedInputValue: string, effort: string, model: string) => {
      if (!submittedInputValue.trim()) return;
      setProcessedEventsTimeline([]);
      hasFinalizeEventOccurredRef.current = false;

      // convert effort to, initial_search_query_count and max_research_loops
      // low means max 1 loop and 1 query
      // medium means max 3 loops and 3 queries
      // high means max 10 loops and 5 queries
      let initial_search_query_count = 0;
      let max_research_loops = 0;
      switch (effort) {
        case "low":
          initial_search_query_count = 1;
          max_research_loops = 1;
          break;
        case "medium":
          initial_search_query_count = 3;
          max_research_loops = 3;
          break;
        case "high":
          initial_search_query_count = 5;
          max_research_loops = 10;
          break;
      }

      const newMessages: Message[] = [
        ...(thread.messages || []),
        {
          type: "human",
          content: submittedInputValue,
          id: Date.now().toString(),
        },
      ];
      thread.submit({
        // messages: newMessages,
        // initial_search_query_count: initial_search_query_count,
        // max_research_loops: max_research_loops,
        // reasoning_model: model,
        stock_symbol: "QQQ",
      });
    },
    [thread]
  );

  const handleCancel = useCallback(() => {
    thread.stop();
    window.location.reload();
  }, [thread]);

  return (
    <div className="flex h-screen bg-neutral-800 text-neutral-100 font-sans antialiased">
      <main className="h-full w-full max-w-4xl mx-auto">
          {thread.messages.length === 0 ? (
            <WelcomeScreen
              handleSubmit={handleSubmit}
              isLoading={thread.isLoading}
              onCancel={handleCancel}
            />
          ) : error ? (
            <div className="flex flex-col items-center justify-center h-full">
              <div className="flex flex-col items-center justify-center gap-4">
                <h1 className="text-2xl text-red-400 font-bold">Error</h1>
                <p className="text-red-400">{JSON.stringify(error)}</p>

                <Button
                  variant="destructive"
                  onClick={() => window.location.reload()}
                >
                  Retry
                </Button>
              </div>
            </div>
          ) : (
            <ChatMessagesView
              messages={thread.messages}
     
[truncated — 335 more characters]
```

### backend/src/agent/app.py

```python
# mypy: disable - error - code = "no-untyped-def,misc"
import pathlib
from fastapi import FastAPI, Response
from fastapi.staticfiles import StaticFiles

# Define the FastAPI app
app = FastAPI()


def create_frontend_router(build_dir="../frontend/dist"):
    """Creates a router to serve the React frontend.

    Args:
        build_dir: Path to the React build directory relative to this file.

    Returns:
        A Starlette application serving the frontend.
    """
    build_path = pathlib.Path(__file__).parent.parent.parent / build_dir

    if not build_path.is_dir() or not (build_path / "index.html").is_file():
        print(
            f"WARN: Frontend build directory not found or incomplete at {build_path}. Serving frontend will likely fail."
        )
        # Return a dummy router if build isn't ready
        from starlette.routing import Route

        async def dummy_frontend(request):
            return Response(
                "Frontend not built. Run 'npm run build' in the frontend directory.",
                media_type="text/plain",
                status_code=503,
            )

        return Route("/{path:path}", endpoint=dummy_frontend)

    return StaticFiles(directory=build_path, html=True)


# Mount the frontend under /app to not conflict with the LangGraph API routes
app.mount(
    "/app",
    create_frontend_router(),
    name="frontend",
)

```

### backend/setup.py

```python
from setuptools import setup, find_packages

setup(
    name="invest-agent",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[
        line.strip()
        for line in open("requirements.txt").readlines()
        if line.strip() and not line.startswith("#")
    ],
    python_requires=">=3.8",
)

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

[40 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]