# Hesychia (`chat_web_app`)

Frontend for **[Hesychia](https://hesychia.ai)** — a React + TypeScript single-page
app for a calm AI assistant experience (quiet, rest, stillness).

Public product domain: **hesychia.ai** (replacing `chat.aimloperations.com`).

Companion backend: [chat_backend](https://git.aimloperations.com/ai_ml_operations/chat_backend)

Production serve is **node-static** (not Docker). Hosts build with npm into a
document root; the shared **web-static** nginx container from
[server-infra](https://git.aimloperations.com/ai_ml_operations/server-infra) serves
those assets. Same pattern as `dta_webapp`.

Android ships the same CRA `build/` via **Capacitor** (display name **Hesychia**;
see `llm-fe/ANDROID.md`).

## Domain migration

| Role | Current (still live) | Target |
|------|----------------------|--------|
| Web UI | `chat.aimloperations.com` | `hesychia.ai` |
| REST / WS API | `chatbackend.aimloperations.com` | TBD under hesychia.ai (infra cutover) |
| Deploy webroot | `/var/www/{env}.chat.aimloperations/html` | Keep until `server-infra` renames |

Committed `.env.production` / `.env.beta` still point at the **current**
`*.aimloperations.com` API hosts so builds keep working. Flip those URLs (and
deploy webroots / NPM) when DNS and `server-infra` cut over to hesychia.ai.

## Stack

- **React 18**, **TypeScript**, **Create React App** (`react-scripts` 5)
- **Material UI (MUI) 5** + Emotion for components and theming
- **React Router 6** for routing (`BrowserRouter` on web; `HashRouter` under Capacitor)
- **Axios** for REST against `chat_backend` (JWT `Authorization` header)
- **WebSocket** for live chat (`REACT_APP_BACKEND_WS_API_BASE_URL`)
- **Capacitor 7** for Android (and later iOS) WebView shells
- **Jest** + React Testing Library for unit tests

## Repository layout

```
chat_web_app/
├── README.md
├── .gitea/workflows/          # Unit tests + deploy-prod / deploy-beta
└── llm-fe/                    # CRA + Capacitor npm root
    ├── package.json
    ├── capacitor.config.ts
    ├── ANDROID.md             # Android build, signing, Play Store notes
    ├── android/               # Native Android project (committed)
    ├── assets/                # Source icon/splash for @capacitor/assets
    ├── .env.development       # Local dev API/WS URLs
    ├── .env.beta
    ├── .env.production
    ├── .env.mobile            # Capacitor shell API/WS URLs
    ├── public/
    └── src/
        ├── axiosApi.js        # Axios instances (JWT Authorization)
        ├── App.tsx
        ├── index.tsx
        └── llm-fe/
            ├── pages/         # Route-level views
            ├── components/    # Shared & feature UI
            ├── contexts/      # Auth, account, WebSocket, theme
            ├── platform/      # Capacitor routing / native chrome
            └── ui-kit/        # MD* design-system wrappers
```

## Prerequisites

- **Node.js 20** (matches CI)
- **npm** (lockfile: `llm-fe/package-lock.json`)
- Running [chat_backend](https://git.aimloperations.com/ai_ml_operations/chat_backend)
  locally for full chat/auth (default in `.env.development`: port **8001**)
- For Android: JDK 17+, Android Studio / SDK (see `llm-fe/ANDROID.md`)

## Local setup

### 1. Clone and install

```bash
git clone ssh://git@git.aimloperations.com:30009/ai_ml_operations/chat_web_app.git
cd chat_web_app/llm-fe
npm ci
```

### 2. Configure environment

CRA loads mode-specific env files. Variables must be prefixed with `REACT_APP_`
to be visible in the browser bundle.

| File | Used when | Backend (current) |
|------|-----------|-------------------|
| `.env.development` | `npm start` | `http://127.0.0.1:8001` |
| `.env.production` | `npm run build` / `build:prod` | `https://chatbackend.aimloperations.com` + prod Tianji ID |
| `.env.beta` | `npm run build:beta` | `https://beta.chatbackend.aimloperations.com` + beta Tianji ID |
| `.env.mobile` | `npm run build:mobile` | prod by default (override for beta shell) |

Each committed env sets `REACT_APP_DEPLOY_ENV` (`production` / `beta`) and `REACT_APP_TIANJI_WEBSITE_ID`.

Required keys (already set in the committed env files):

```env
REACT_APP_BACKEND_REST_API_BASE_URL=http://127.0.0.1:8001/api/
REACT_APP_BACKEND_WS_API_BASE_URL=ws://127.0.0.1:8001/ws/chat_again/
```

These are **baked into the JS at build time**. There is no host secret file for
this frontend (unlike Django apps under `~/Documents/secrets/`). Change the
committed `.env.*` files if API domains change, then redeploy.

### SSO (Google / Microsoft)

OAuth client IDs/secrets live in **chat_backend** env (`GOOGLE_OAUTH_*`,
`MICROSOFT_OAUTH_*`). The SPA only redirects to
`/api/auth/oauth/<provider>/start/` and lands on `/auth/callback/` with JWTs.
SSO buttons appear when `/public/settings/` reports the provider as configured.

**Capacitor / mobile:** IdP redirect URIs must match the backend callback URL
(not the WebView origin). Custom URL schemes / in-app browser for native OAuth
are out of scope for the initial web SSO; use the web app for Google/Microsoft
sign-in until native redirect handling is added.

Optional local overrides (gitignored): `.env.local`, `.env.development.local`,
`.env.production.local`, `.env.mobile.local`.

### 3. Start the backend

From the `chat_backend` repo (adjust to however that service is run locally):

```bash
# example — follow chat_backend README for the current command
cd chat_backend
# run Django on the port matching .env.development (8001)
```

### 4. Start the dev server

```bash
cd llm-fe
npm start
```

Open **http://localhost:3000**.

### 5. Android (Capacitor)

```bash
cd llm-fe
npm run build:mobile
npm run android:open
```

Full signing / Play Store notes: [`llm-fe/ANDROID.md`](llm-fe/ANDROID.md).

## npm scripts

| Command | Purpose |
|---------|---------|
| `npm start` | CRA dev server (port 3000) |
| `npm run build` | Production build into `llm-fe/build/` |
| `npm run build:prod` | Prod build + copy to `/var/www/prod.chat.aimloperations/html` |
| `npm run build:beta` | Beta env build + copy to `/var/www/beta.chat.aimloperations/html` |
| `npm run build:mobile` | Mobile env build + `cap sync android` |
| `npm run android:open` | Open `android/` in Android Studio |
| `npm run android:sync` | `cap sync android` only |
| `npm run assets:generate` | Regenerate Android icons/splash from `assets/` |
| `npm test` | Jest watch mode |
| `npm run test:ci` | Single CI run (`CI=true`, no watch) |

`build:prod` / `build:beta` are intended for deploy hosts (or a machine that can
write those `/var/www/...` paths). Paths must match
`server-infra` `app_catalog.chat_web_app.webroot_pattern` until renamed:

```text
/var/www/{env}.chat.aimloperations/html
```

## Tests

```bash
cd llm-fe
npm test          # watch
npm run test:ci   # one-shot (matches Gitea Unit Tests)
```

## CI / deployment

Gitea Actions workflows in `.gitea/workflows/` (same pattern as `dta_webapp`):

| Workflow | Trigger | Behavior |
|----------|---------|----------|
| `unit-tests.yml` | push/PR to `master` | `npm ci` + `npm run test:ci` in `llm-fe/` |
| `deploy-beta.yml` | **push** to `master` (after unit tests in-workflow) | `server-infra/scripts/deploy.sh --app chat_web_app --env beta --ref <sha>` |
| `deploy-prod.yml` | **manual** `workflow_dispatch` only | tests, then deploy `--env prod` |

Push/merge to `master` auto-deploys **beta** only. Prod requires the Gitea **Run workflow** button on Deploy Prod.

Deploy flow (on each webserver listed in `host_apps`):

1. Git checkout at the pinned ref under `/opt/apps/src/chat_web_app_<env>`
2. `npm ci` in `llm-fe/`
3. `npm run build:<env>` → writes the static tree under `/var/www/...`
4. **web-static** nginx serves that root on the host port (prod **8082**, beta **8083**)

Manual deploy from the control node:

```bash
~/Documents/repos/server-infra/scripts/deploy.sh \
  --app chat_web_app --env prod --ref <sha>
```

NPM (not managed by Ansible) should balance public Hesychia domains to:

- `adama:8082` + `roslin:8082` + `ai-server-4080:8082` (prod)
- beta hosts on **8083** / `beta.chat.aimloperations.com` (see server-infra)

See [server-infra IMPLEMENTATION.md](https://git.aimloperations.com/ai_ml_operations/server-infra).

### Tianji analytics

| Build | Env file | Website ID |
|-------|----------|------------|
| prod | `.env.production` | `REACT_APP_TIANJI_WEBSITE_ID` (prod site) |
| beta | `.env.beta` | distinct beta site ID (set after creating website in Tianji) |

- Script: `https://tianji.aimloperations.com/tracker.js`
- **Page views** always load on prod/beta (no consent gate)
- **Custom events / identify** require analytics consent (`AnalyticsConsentBanner` + `trackEvent` / `identifyUser`)
- Consent choice stored in `localStorage` key `hesychia_analytics_consent_v1` (`granted` / `denied`)
- Banner links to `/terms_of_service/#analytics` for the always-on vs optional split
- Event catalog: [`llm-fe/ANALYTICS.md`](llm-fe/ANALYTICS.md)

## Related repos

- **Backend:** [chat_backend](https://git.aimloperations.com/ai_ml_operations/chat_backend) — Django API + WebSocket
- **Infra:** [server-infra](https://git.aimloperations.com/ai_ml_operations/server-infra) — `app_catalog`, `host_apps`, `deploy.sh`, web-static
- **Pattern reference:** [dta_webapp](https://git.aimloperations.com/Ditch_The_Agent/dta_webapp) — node-static frontend deploy
