diff --git a/.gitea/workflows/deploy-beta.yml b/.gitea/workflows/deploy-beta.yml new file mode 100644 index 0000000..59d6d03 --- /dev/null +++ b/.gitea/workflows/deploy-beta.yml @@ -0,0 +1,37 @@ +name: Deploy Beta + +on: + workflow_dispatch: {} + +jobs: + unit-tests: + runs-on: self-hosted + defaults: + run: + working-directory: llm-fe + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test:ci + + deploy-beta: + needs: unit-tests + runs-on: self-hosted + env: + SERVER_INFRA_ROOT: /home/westfarn/Documents/repos/server-infra + steps: + - name: Deploy chat_web_app beta + run: | + "$SERVER_INFRA_ROOT/scripts/deploy.sh" \ + --app chat_web_app \ + --env beta \ + --ref "${{ gitea.sha }}" diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml new file mode 100644 index 0000000..bb055e3 --- /dev/null +++ b/.gitea/workflows/deploy-prod.yml @@ -0,0 +1,22 @@ +name: Deploy Prod + +# Runs after Unit Tests completes on master. Direct pushes only (not PRs). +on: + workflow_run: + workflows: [Unit Tests] + types: [completed] + branches: [master] + +jobs: + deploy: + if: gitea.event.workflow_run.conclusion == 'success' && gitea.event.workflow_run.event == 'push' + runs-on: self-hosted + env: + SERVER_INFRA_ROOT: /home/westfarn/Documents/repos/server-infra + steps: + - name: Deploy chat_web_app prod + run: | + "$SERVER_INFRA_ROOT/scripts/deploy.sh" \ + --app chat_web_app \ + --env prod \ + --ref "${{ gitea.event.workflow_run.head_sha }}" diff --git a/.gitea/workflows/unit-tests.yml b/.gitea/workflows/unit-tests.yml new file mode 100644 index 0000000..00c774f --- /dev/null +++ b/.gitea/workflows/unit-tests.yml @@ -0,0 +1,28 @@ +name: Unit Tests + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + test: + runs-on: self-hosted + defaults: + run: + working-directory: llm-fe + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test:ci diff --git a/README.md b/README.md index e69de29..65e5e82 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,167 @@ +# Chat Web App (`chat_web_app`) + +Frontend for [chat.aimloperations.com](https://chat.aimloperations.com) — a React + +TypeScript single-page app for the AI ML Operations chat product. + +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`. + +## Stack + +- **React 18**, **TypeScript**, **Create React App** (`react-scripts` 5) +- **Material UI (MUI) 5** + Emotion for components and theming +- **React Router 6** for routing +- **Axios** for REST against `chat_backend` +- **WebSocket** for live chat (`REACT_APP_BACKEND_WS_API_BASE_URL`) +- **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 application (npm root) + ├── package.json + ├── .env.development # Local dev API/WS URLs + ├── .env.beta + ├── .env.production + ├── public/ + └── src/ + ├── axiosApi.js # Axios instances (JWT + CSRF) + ├── App.tsx + ├── index.tsx + └── llm-fe/ + ├── pages/ # Route-level views + ├── components/ # Shared & feature UI + ├── contexts/ # Auth, account, WebSocket, theme + └── 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**) + +## 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 | +|------|-----------|---------| +| `.env.development` | `npm start` | `http://127.0.0.1:8001` | +| `.env.production` | `npm run build` / `build:prod` | `https://chatbackend.aimloperations.com` | +| `.env.beta` | `npm run build:beta` | `https://beta.chatbackend.aimloperations.com` | + +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. + +Optional local overrides (gitignored): `.env.local`, `.env.development.local`, +`.env.production.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**. + +## 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 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`: + +```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/`: + +| Workflow | Trigger | Behavior | +|----------|---------|----------| +| `unit-tests.yml` | push/PR to `master` | `npm ci` + `npm run test:ci` in `llm-fe/` | +| `deploy-prod.yml` | after green **Unit Tests** on `master` **push** | `server-infra/scripts/deploy.sh --app chat_web_app --env prod --ref ` | +| `deploy-beta.yml` | `workflow_dispatch` | tests, then deploy `--env beta` | + +Deploy flow (on each webserver listed in `host_apps`): + +1. Git checkout at the pinned ref under `/opt/apps/src/chat_web_app_` +2. `npm ci` in `llm-fe/` +3. `npm run build:` → writes the static tree under `/var/www/...` +4. **web-static** nginx serves that root on the host port (prod **8082**, beta **8083** if enabled) + +Manual deploy from the control node: + +```bash +~/Documents/repos/server-infra/scripts/deploy.sh \ + --app chat_web_app --env prod --ref +``` + +NPM (not managed by Ansible) should balance public chat domains to: + +- `adama:8082` + `roslin:8082` + `ai-server-4080:8082` (prod) + +See [server-infra IMPLEMENTATION.md](https://git.aimloperations.com/ai_ml_operations/server-infra). + +## 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 diff --git a/llm-fe/.env.beta b/llm-fe/.env.beta new file mode 100644 index 0000000..bfcd820 --- /dev/null +++ b/llm-fe/.env.beta @@ -0,0 +1,2 @@ +REACT_APP_BACKEND_REST_API_BASE_URL=https://beta.chatbackend.aimloperations.com/api/ +REACT_APP_BACKEND_WS_API_BASE_URL=wss://beta.chatbackend.aimloperations.com/ws/chat_again/ diff --git a/llm-fe/package.json b/llm-fe/package.json index 4073984..a6714c1 100644 --- a/llm-fe/package.json +++ b/llm-fe/package.json @@ -40,6 +40,8 @@ "scripts": { "start": "NODE_ENV=development react-scripts start", "build": "NODE_ENV=production react-scripts build", + "build:prod": "NODE_ENV=production react-scripts build && mkdir -p /var/www/prod.chat.aimloperations/html && cp -r ./build/* /var/www/prod.chat.aimloperations/html/", + "build:beta": "bash -c 'set -a; source .env.beta; set +a; NODE_ENV=production react-scripts build' && mkdir -p /var/www/beta.chat.aimloperations/html && cp -r ./build/* /var/www/beta.chat.aimloperations/html/", "test": "NODE_ENV=development react-scripts test", "test:ci": "CI=true NODE_ENV=development react-scripts test --watchAll=false --coverage=false", "eject": "react-scripts eject" diff --git a/llm-fe/src/axiosApi.js b/llm-fe/src/axiosApi.js index 79fbd08..5eab397 100644 --- a/llm-fe/src/axiosApi.js +++ b/llm-fe/src/axiosApi.js @@ -1,9 +1,7 @@ import axios from "axios"; const Cookies = require("js-cookie"); -const baseURL = "http://localhost:8011/api/"; -//const baseURL = 'https://chatbackend.aimloperations.com/api/'; -//const baseURL = process.env.REACT_APP_BACKEND_REST_API_BASE_URL; +const baseURL = process.env.REACT_APP_BACKEND_REST_API_BASE_URL; export const axiosInstance = axios.create({ baseURL: baseURL, diff --git a/llm-fe/src/llm-fe/contexts/WebSocketContext.js b/llm-fe/src/llm-fe/contexts/WebSocketContext.js index b86292d..7abc398 100644 --- a/llm-fe/src/llm-fe/contexts/WebSocketContext.js +++ b/llm-fe/src/llm-fe/contexts/WebSocketContext.js @@ -70,11 +70,7 @@ function WebSocketProvider({ children }) { useEffect(() => { /* WS initialization and cleanup */ if (account) { - ws.current = new WebSocket(`ws://localhost:8011/ws/chat_again/`); - //ws.current = new WebSocket(`ws://localhost:8011/ws/conditional_chat/`); - //ws.current = new WebSocket('wss://chatbackend.aimloperations.com/ws/chat_again/') - //ws.current = new WebSocket('wss://chatbackend.aimloperations.com/ws/conditional_chat/') - //ws.current = process.env.REACT_APP_BACKEND_WS_API_BASE_URL; + ws.current = new WebSocket(process.env.REACT_APP_BACKEND_WS_API_BASE_URL); ws.current.onopen = () => { setSocket(ws.current); diff --git a/readme.md b/readme.md deleted file mode 100644 index 4c02e75..0000000 --- a/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -# Chat Bot Frontend - -## Setup - -To run -```console -npm start -``` - -## TODO - - [ ] Fix the refresh issue - - [ ] Make the account page work - - [ ] Make a manage user card for the account - - [ ] Get a working conversation screen - - [ ] Move the 'JWT ' to a variable \ No newline at end of file