# Bina Plus — System Documentation

**Version:** v2 (main branch + local sandbox)
**Updated:** 2026-04-27
**Local commit:** `2f3b74c` — *Local snapshot v0: env configured, dynamic URL, replica set*

> **For repo conventions and recent V4 work, read [`AGENTS.md`](./AGENTS.md) and [`SESSION_NOTES.md`](./SESSION_NOTES.md) first.** This document is the deep architectural reference (auth, streaming SSE, image flow, admin) and is mostly *upstream-accurate* — it predates several V4 changes (char-by-char typing animation, debug panel, brand-level model picker, beta whitelist). Section 12 ("Integrating the Unified Chat POC") is **already done in V4**.

---

## Table of Contents

1. [Architecture Overview](#1-architecture-overview)
2. [Running the Stack](#2-running-the-stack)
3. [Database Layout](#3-database-layout)
4. [Backend API Reference](#4-backend-api-reference)
5. [Authentication](#5-authentication)
6. [Chat Flow (Streaming SSE)](#6-chat-flow-streaming-sse)
7. [Image Generation Flow](#7-image-generation-flow)
8. [Models & Providers](#8-models--providers)
9. [Frontend Architecture (frontend_v3)](#9-frontend-architecture-frontend_v3)
10. [Admin Panel (admin_frontend)](#10-admin-panel-admin_frontend)
11. [How the Existing Chat Screen Works](#11-how-the-existing-chat-screen-works)
12. [Integrating the Unified Chat POC](#12-integrating-the-unified-chat-poc)

---

## 1. Architecture Overview

```
┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│  frontend_v3     │  │  admin_frontend  │  │  backend (Nest)  │
│  Vue 3 / Vue CLI │  │  Vue 3 / Vite    │  │  REST + SSE +    │
│  Port 1814       │  │  Port 1815       │  │  socket.io       │
│                  │  │                  │  │  Port 1813       │
└────────┬─────────┘  └────────┬─────────┘  └────────┬─────────┘
         │                     │                     │
         └─────────────────────┴─────────────────────┘
                               │
                  ┌────────────┴────────────┐
                  │   MongoDB (replica set) │
                  │   localhost:27017/rs0   │
                  │   db: binaplus-local    │
                  └─────────────────────────┘
                               │
                  ┌────────────┴────────────────────┐
                  │  External AI providers          │
                  │  OpenAI · Anthropic · Google    │
                  │  Replicate · OpenRouter · Stab. │
                  └─────────────────────────────────┘
```

**Key technologies**

- **Backend:** NestJS 10 (Node 18), MongoDB via Mongoose, Socket.io
- **Frontend:** Vue 3 + Vue CLI 5 (port 1814) — main user app
- **Admin:** Vue 3 + Vite 7 (Node 20+, port 1815)
- **Storage:** Google Cloud Storage / S3 for images and uploads
- **Streaming:** Server-Sent Events for chat tokens, Socket.io for image generation status

---

## 2. Running the Stack

### Backend
```bash
cd /home/user/bina_plus_workspace/main/backend
npm run start:dev     # NODE_ENV=stg, watch mode, port 1813
```
Loads `.env.stg` (and falls back to `.env`).

### Frontend (user-facing)
```bash
cd /home/user/bina_plus_workspace/main/frontend_v3
npm run serve          # vue-cli-service serve, port 1814
```

### Admin
```bash
cd /home/user/bina_plus_workspace/main/admin_frontend
PATH=/usr/local/n/versions/node/20.19.6/bin:$PATH npm run dev   # Vite, port 1815
```
Vite ≥7 requires Node 20+. The system has Node 18 by default; Node 20 lives at `/usr/local/n/versions/node/20.19.6/`.

### MongoDB
```bash
sudo systemctl start mongod    # uses /etc/mongod.conf
mongosh --eval "rs.status().myState"   # 1 = PRIMARY (replica set ready)
```
Replica set `rs0` is single-node; required because the backend uses MongoDB transactions which fail on standalone instances.

### Logs

| Service | Tail file |
|---|---|
| Backend | `/tmp/v2_backend.log` |
| Frontend | `/tmp/v2_frontend.log` |
| Admin | `/tmp/v2_admin.log` |

---

## 3. Database Layout

**Connection string:** `mongodb://localhost:27017/?replicaSet=rs0`
**DB name:** `binaplus-local`

### Important collections

| Collection | What it holds | Read by | Written by |
|---|---|---|---|
| `users` | User accounts (28K) | All auth flows | `auth/register`, `users/update` |
| `subscriptions` | Plan + usage per user (28K) | Quota guards, `subscriptions/my-subscription` | `subscriptions/*` |
| `aimodelgroups` | **Embedded** model definitions, grouped by provider | `app-config`, `studio`, `chat` route resolution | Admin |
| `chathistories` | Conversations + history array | `chat/get-conversation`, `chat/get-conversations` | `chat/create-conversation` (via `ChatHistoryStreamService.saveInitialConversation`) |
| `imagehistories` | Generated images metadata + URLs | `image/get-image/:id`, `image/get-all-images` | `image/generate-text-to-image`, `image/generate-image-to-image` (background → socket emits) |
| `prompts` | Suggested prompt cards on home screen | Cached at boot | Admin |
| `configs` | System-wide config | Cached | Admin |
| `filterprompts`, `filterinstructions`, `filtersettings`, `filterbatches`, `filtertestresults` | SafeMode filtering rules | Filter pipeline | Admin |
| `featurerequests`, `generalfeedbacks`, `feedbacks` | User feedback | Admin reads | User submits |
| `adminusers` | Admin auth (separate from `users`) | Admin login | Admin self-edit |
| `verifications` | Email/OTP tokens | Auth flow | Auth flow |
| `storagefiles` | Uploaded file metadata | `storage/*` | Upload flow |
| `useranalytics`, `companyanalytics` | Logged events | Admin reports | Implicit (client logs `/company-analytics/log-analytics`) |

### Note: `aimodels` is empty
Models live **inline** as an array under each `aimodelgroups` document (`group.models[]`). There used to be a separate `aimodels` collection but it was migrated. When code calls `aiModelsService.getAiModelById(id)` it reads from the cache built from `aimodelgroups`.

---

## 4. Backend API Reference

Base URL: `http://localhost:1813/api` (from frontend it's resolved dynamically via `window.location.hostname`).

### Auth

| Method | Path | Body | Auth | Returns |
|---|---|---|---|---|
| POST | `/auth/login` | `{ email, password }` | — | `{ accessToken }` |
| POST | `/auth/register` | `{ email, password, name, ... }` | — | user + token |
| POST | `/auth/google-login` | `{ idToken }` | — | user + token |
| GET | `/auth/me` | — | Bearer | current user |
| POST | `/auth/forgot-password` | `{ email }` | — | sends email |

JWT expires in 365d. Stored as `localStorage.authToken` on the frontend.

### App Config

| GET `/app-config` | Returns user (if authed via cookie/header) + `aiModels` (groups) + flags. Called once at app boot. |
|---|---|

### Chat

| Method | Path | Body | Notes |
|---|---|---|---|
| GET | `/chat/get-conversations` | — | List for sidebar |
| GET | `/chat/get-conversation/:id` | — | Full history |
| POST | **`/chat/create-conversation`** | `{ conversationId, chat: {...} }` | **SSE stream**, see §6 |
| POST | `/chat/regenerate-conversation` | similar | SSE stream |
| POST | `/chat/retry-conversation` | similar | SSE stream |
| POST | `/conversation/edit-chat-stream` | similar | SSE stream |
| POST | `/chat/transcribe-audio` | multipart `audio` | Whisper transcription |
| PATCH | `/chat/update-conversation/:id` | partial update | Rename, favorite |
| POST | `/chat/update-message` | edit a message | |

### Images

| Method | Path | Body | Notes |
|---|---|---|---|
| POST | **`/image/generate-text-to-image`** | FormData: `prompt, model, samples, style_preset?` | Returns imageId immediately, background processes — see §7 |
| POST | **`/image/generate-image-to-image`** | multipart with `file` + same params | Same flow |
| GET | `/image/get-image/:id` | — | Fetch result/status |
| GET | `/image/get-all-images` | query | List user's images |
| DELETE | `/image/delete-image/:historyId/:imageId` | — | Delete one |

### Subscriptions

| GET `/subscriptions/my-subscription` | Current user's plan + remaining quotas |
| PATCH `/subscriptions/update-model` | `{ model }` saves user's preferred chat model |

### Other endpoints

- `/users/*` — profile updates
- `/storage/upload`, `/storage/delete/:id`
- `/document-scenarios/*` — Smart Flow (multi-step doc gen)
- `/smart-agents/*` — Robots
- `/ai-voice/*` — Realtime voice
- `/gpts/*` — Custom GPTs
- `/feedback/*`, `/general-feedback/*`, `/feature-request/*`
- `/filter-*` (test/instruction/setting/prompt/batch) — SafeMode admin
- `/admin/*` — admin-only CRUD

### Admin endpoints

Mounted at `/api/admin/...`:
- `/admin/auth/me`
- `/admin/users` (paginated, filter)
- `/admin/ai-models/groups`, `/admin/ai-models/models/:id`
- `/admin/prompts`
- _(In v1 we added: `/admin/quick-tiles` — see §12)_

---

## 5. Authentication

JWT-based. Token stored in `localStorage.authToken`.

**Frontend interceptor:** `src/services/api.js` automatically attaches `Authorization: Bearer <token>` to every request. On `401` it clears the token and redirects to `/login`.

**Backend guards:**
- `JwtAuthGuard` — validates token, populates `@CurrentUser()`
- `RolesGuard` — checks `@Roles(...)` decorator
- `MessageLimitGuard` — checks subscription quotas
- `ChatGuardsModule`, `ImageGuardsModule` — pipeline-specific

**User roles:** `user`, `admin`, `super_admin`.

---

## 6. Chat Flow (Streaming SSE)

This is the heart of the chat. **Read carefully if you're integrating Unified Chat.**

### Frontend send

`src/components/chat/ChatDetailsWrapper.vue` → `onSendPrompt(data)`:

```js
const payload = {
  conversationId: this.isNewChat ? '' : this.currentConversationId,
  chat: { content, model, file?, ... }
};
this.addUserMessage(data);
this.addLoadingMessage();
const response = await chatService.createStreamMessage(payload, abortSignal);
await this.handleStreamResponse(response, payload);
```

`chatService.createStreamMessage` is a plain `fetch()` (not axios) because we need to read the body as a stream. It hits `POST /api/chat/create-conversation`.

### Backend dispatch

`chat-history.controller.ts` → `createChat()`:

```ts
const chatService = await this.aiModelsService.selectChatService(user?.subscription?.aiModel);
// Returns one of: 'openai' | 'anthropic' | 'gemini' | 'x-ai' | 'perplexity' | 'openrouter'
```

It then routes to the matching streaming service:
- `chatHistoryStreamService` (OpenAI / GPT)
- `chatHistoryClaudeStreamService`
- `chatHistoryGeminiStreamService`
- `chatHistoryXAiStreamService`
- `chatHistoryPerplexityStreamService` (also handles OpenRouter)

Each service:
1. Creates or fetches the conversation document (uses **MongoDB transactions** — hence the replica set requirement)
2. Builds the prompt with system instructions (`CHAT_GPT_MAIN_PROMPT_INSTRUCTION` etc.)
3. Streams chunks back over the HTTP response, prefixed with markers:
   - `BINA_STREAM_RESPONSE_DIVIDER<24-char-conversationId>BINA_STREAM_RESPONSE_DIVIDER` — sent at the start so client knows the new conversation ID
   - `BINA_STREAM_REASONING_START...BINA_STREAM_REASONING_END` — wraps reasoning chunks (used by o1, deepseek-r1)

### Frontend stream consumption

`handleStreamResponse(response)` in `ChatDetailsWrapper.vue`:

```js
const reader = response.body.getReader();
let buffer = '';
while (!done) {
  const { value } = await reader.read();
  const chunk = new TextDecoder().decode(value);
  const result = chatService.processStreamData(chunk); // extracts id + reasoning + actualData
  if (result.id) this.currentConversationId = result.id;
  if (result.actualData) {
    buffer += result.actualData;
    this.chatData.history.at(-1).content = buffer;
  }
}
```

`chatService.processStreamData()` strips the markers and returns clean text for display.

---

## 7. Image Generation Flow

### Frontend send

`src/components/image/ImageCreations.vue` → `onGenerate({ type, payload })`:

```js
const formData = new FormData();
formData.append('prompt', payload.prompt);
formData.append('model', payload.model);
formData.append('samples', payload.samples || 1);
if (payload.style_preset) formData.append('style_preset', payload.style_preset);

const res = await api.post('/image/generate-text-to-image', formData);
const imageId = res?.data?._id;
this.$router.push({ name: 'image-result', params: { id: imageId } });
```

### Backend dispatch

`image-history.controller.ts` → `generateImage()` returns `imageId` synchronously (record created in `imagehistories` with `status: 'loading'`), then schedules the actual generation in the background.

Routing: `GenerateImageService.routeImageGeneration(model, payload, user, type)` reads `model.providerKey` and dispatches to one of:
- `chatgpt-image-provider.adapter.ts` (DALL-E)
- `gemini-image-provider.adapter.ts`
- `FluxService` (Replicate-based — Flux 1.1 Pro, Flux Dev, Flux Schnell, Flux Pro)
- `StabilityAiService` (SD 3.5 / SD Ultra / SD Core)
- `ReplicaService` (Recraft, Ideogram via Replicate)

> ⚠️ **Known issue:** Flux models in DB had `providerKey` like `flux-schnell` but Replicate requires the format `owner/name` (e.g., `black-forest-labs/flux-schnell`). The fix script lives at `/home/user/bina_plus/backend/scripts/fix-flux-provider-keys.js`. If Flux is added to this DB, run the script.

> ⚠️ **Known issue:** `STABILITY_AI_KEY` returns 401. SD models won't generate until a fresh key is set in `.env.stg`.

### Status updates

Once generation finishes (or fails), the backend:
1. Updates the `imagehistories` document (`status: 'success'` or `'blocked'`, fills `images[].url`)
2. Emits a socket event to the user's room:
   ```js
   server.to(userId).emit('image', { _id, type, status, cause? });
   ```

### Frontend listens

`ImageCreations.vue` mounted hook:

```js
socketService.onEvent('image', (data) => {
  if (data._id !== this.imageId) return;
  if (data.status === 'success') this.fetchImage(data._id);
  if (data.status === 'blocked') this.handleBlocked(data.cause);
});
```

The image record is fetched via `GET /image/get-image/:id` and rendered.

---

## 8. Models & Providers

Models are managed in the `aimodelgroups` collection (admin-editable). Each group:

```js
{
  id: 'openai',
  name: 'GPT',
  icon: { url: 'https://cdn...png' },
  models: [
    {
      id: 'gpt-4o',                 // unique key the frontend uses
      name: 'GPT-4o',               // display name
      providerKey: 'gpt-4o',        // what the SDK call gets
      isSubscription: true,         // gated by paid plan
      contextWindow: 128000,
      maxOutputTokens: 16384,
      isActive: true,
      order: 1,
      canGenerateText: true,
      canReadImage: true,
      showInChat: true,             // appears in chat selector
      showInTextToImg: false,
      showInImgToImg: false,
      isSupportTools: true,
      isSupportedTemperature: true
    },
    ...
  ]
}
```

### Key fields driving UX

- `showInChat` → appears in chat model selector
- `showInTextToImg` → appears in image creator text-to-image tab
- `showInImgToImg` → appears in image-to-image tab
- `isSubscription` → free users see it as locked
- `canReadImage` → enables image upload in chat for that model

### How the backend picks a chat service

`aiModelsService.selectChatService(modelId)`:
1. Looks up the model
2. Reads `providerKey` to derive the provider (`gpt-4o` → `openai`, `claude-...` → `anthropic`, etc.)
3. Returns the provider name string

### Existing groups (in this DB)

`chatgpt`, `claude`, `gemini`, `x-ai`, `perplexity`, `minimax`, `black-forest-labs` — note: `flux`, `stability`, `openrouter`, `replica` groups do **not** exist in this DB. The current main DB has a slimmer set than the `bina_plus/` workspace had.

---

## 9. Frontend Architecture (frontend_v3)

### Routes (`src/router/index.js`)

| Path | Component | Notes |
|---|---|---|
| `/` `/chat` | `Chat` (`views/chat.vue`) | Main chat home + active chat |
| `/chat/:conversationId` | `Chat` | Specific conversation |
| `/login`, `/register` | `Login`, `Register` | |
| `/account-settings` | `AccountSettings` | Profile, language |
| `/subscription` | `Subscription` | Plan management |
| `/image/creations`, `/image/creations/:tab/:id`, `/image/history` | `ImagePage` | Image creator + gallery |
| `/gpts` | `GPTsPage` | Custom GPTs |
| `/document-analysis` | `DocumentAnalysis` | |
| `/document-creator` | `DocumentCreator` | |
| `/ai-tools` | `AITools` (lazy) | Smart Flow tools |
| `/ai-voice` | `AIVoice` (lazy) | Realtime voice |
| `/smart-agents` | `SmartAgents` | Bots |
| `/adminv2/document-scenarios` | `DocumentScenariosAdmin` | Internal admin for Smart Flow |

### Stores

Vuex (`src/store`):
- `state.user` — current user (set after `auth/me`)
- `state.aiModels` — full groups array (loaded once at boot from `/app-config`)
- `state.lang` — `'HB'` or `'EN'`
- `state.themeColor` — for mobile theme-color meta

### Services

- `api.js` — axios instance with interceptors. **Now uses `window.location.hostname`** dynamically.
- `chatService.js` — chat endpoints, SSE parsing helpers (`processStreamData`)
- `imageService.js` — image CRUD + reference image holder
- `socketService.js` — socket.io connection. **Now uses `window.location.hostname`** dynamically.
- `modelService.js` — model lookup helpers (chat / text2img / img2img)
- `subscriptionService.js`, `analyticsService.js`, `errorsService.js`, etc.

### Component layout

- `App.vue` mounts `Sidebar`, `Header`, and `<router-view>`
- Sidebar has links to chat, ai-tools, image, gpts, etc.
- `chat.vue` wraps `ModelSelectorByCompany` + (`ChatDetails` for active conversation, `ChatHomePage` for empty state) + sliding `ChatHistory`
- `ChatDetailsWrapper.vue` is the workhorse — handles streaming, retries, edits

---

## 10. Admin Panel (admin_frontend)

Separate Vue 3 + Vite app (port 1815). Uses `VITE_API_BASE_URL` env to point at backend.

### Login
`/login` page → `POST /api/admin/auth/login` (NOTE: actually posts to `/admin/auth/login` via `appSettings.getUserApiUrl = /admin/auth/me`, but the login endpoint is `auth/login` for the user table; check the LoginView to confirm).

### Panels

Defined in `src/admin_panels/*` — each panel exports a config that drives `TableView` + `FormView`:
- `users/`, `admins/` — user tables
- `ai_models/` — model groups + models
- `config/` — app configs
- `prompts/` — chat suggestions
- `test/` — sandbox

Generic table/form via `TableView.vue` and `FormView.vue` — read the panel's `config.js` to understand fields.

---

## 11. How the Existing Chat Screen Works

The user-facing chat screen is **two states inside one route**:

### Empty (`ChatHomePage.vue`)
Shown when `$route.params.conversationId` is missing. It contains:
- `ChatHomeTitle` — "Hi, what's on your mind?"
- `ChatHomeCards` — featured prompt cards (loaded from `state.prompts`)
- `ChatMessagePrompt` — the input box (same component used in active chat)
- On submit: pushes `/chat/<newId>` once `ChatDetailsWrapper` returns the new id from the stream

### Active (`ChatDetails.vue` → `ChatDetailsWrapper.vue`)
Shown when `:conversationId` exists. It mounts:
- `ChatMessagesBody` — scrollable history
- `ChatMessagePrompt` — input
- Drag-and-drop overlay
- Per-message: `ChatMessageUserItem`, `ChatSystemMessage`, `ChatBotReplyModelTabs`, `MessageResources`

The wrapper owns the chat state, the abort controller, and the SSE consumption loop.

### Where models come from

`ModelSelectorByCompany.vue` reads `store.state.aiModels` (loaded from `/app-config`) and shows groups with `showInChat: true` filtered. Selection is persisted via `PATCH /subscriptions/update-model`.

---

## 12. Integrating the Unified Chat POC

The POC lives in `/home/user/bina_plus/backup-unified-chat-v2-20260427-0027/`. It contains:
- `frontend_v3/src/views/UnifiedChat.vue` (single-file component, ~1600 lines)
- `frontend_v3/src/services/quickTilesService.js`
- backend `quick-tiles` module + admin module
- `backend/scripts/fix-flux-provider-keys.js`

### What the Unified Chat does

1. **Hero mode** when no messages — large title, centered input, 8 capability tiles below
2. **Chat mode** when messages exist — bubbles + bottom input
3. Tiles fetched from `GET /api/quick-tiles` (driven by `quicktiles` collection)
4. Each tile bundles: mode (chat/image), model, style, aspect ratio, prompt prefix/suffix, system prompt
5. Auto-detects "create image" intent in user input and switches to image mode
6. Image messages render inline in the chat with edit/regenerate/variation/upscale actions

### Steps to integrate

#### A. Backend — copy the `quick-tiles` module

```bash
SRC=/home/user/bina_plus/backup-unified-chat-v2-20260427-0027
cp -r $SRC/backend/src/modules/quick-tiles \
      /home/user/bina_plus_workspace/main/backend/src/modules/

cp -r $SRC/backend/src/admin/modules/admin-quick-tiles \
      /home/user/bina_plus_workspace/main/backend/src/admin/modules/
```

Then register in `app.module.ts`:
```ts
import { QuickTilesModule } from './modules/quick-tiles/quick-tiles.module';
@Module({
  imports: [..., QuickTilesModule]
})
```

And in `admin/admin.module.ts`:
```ts
import { AdminQuickTilesModule } from './modules/admin-quick-tiles/admin-quick-tiles.module';
@Module({
  imports: [..., AdminQuickTilesModule]
})
```

The first time the backend starts after this, `QuickTilesService.onModuleInit()` will seed 8 default tiles into `quicktiles` collection.

#### B. Frontend — copy the page + service

```bash
cp $SRC/frontend_v3/src/views/UnifiedChat.vue \
   /home/user/bina_plus_workspace/main/frontend_v3/src/views/

cp $SRC/frontend_v3/src/services/quickTilesService.js \
   /home/user/bina_plus_workspace/main/frontend_v3/src/services/
```

Add a route in `src/router/index.js`:
```js
const UnifiedChat = () => import('../views/UnifiedChat.vue')

// in routes array:
{ path: '/unified-chat', name: 'unified-chat', component: UnifiedChat,
  meta: { target: 1, title: 'Unified Chat', requiresAuth: true } }
```

Add a sidebar link if desired — `src/components/sidebar.vue` (look for the existing nav array).

#### C. Hook tiles to real models

In the production DB the seed leaves `modelId` empty. To get the full effect, an admin should set `modelId` per tile to a real `aimodelgroups.models[].id`:

```js
// example in admin GUI or via API:
PUT /api/admin/quick-tiles/photo
{ "modelId": "flux-1.1-pro", "stylePresetKey": "realistic" }
```

#### D. Real API wiring (already in `UnifiedChat.vue`)

The `UnifiedChat.vue` we backed up already calls the real endpoints:
- Text → `chatService.createStreamMessage()` with SSE consumption (mirrors `ChatDetailsWrapper`)
- Image → `POST /image/generate-text-to-image` + socket subscription on `image` event + `GET /image/get-image/:id` on success

It uses the same auth token from `localStorage.authToken` and the same `socketService`.

#### E. Replace the home chat (optional)

If you want **Unified Chat to be the default chat experience** instead of an additional page:
1. In `router/index.js` change `'/chat'` and `'/'` to point at `UnifiedChat` instead of `Chat`.
2. Keep the old `Chat` available at `/legacy-chat` while you transition.
3. The Unified Chat already handles both empty and active states (Hero vs Chat mode), so it's a drop-in replacement once tiles are configured.

#### F. Admin GUI for tiles (TODO)

Not yet built. The pattern to follow is in `/adminv2/document-scenarios` (`DocumentScenariosAdmin.vue`). Endpoints are already in place — `/api/admin/quick-tiles` supports list, get, create, update, delete, toggle, reorder.

A minimal Vue component would:
- `GET /api/admin/quick-tiles` to list
- Render a draggable list (vuedraggable) of cards
- Edit dialog with all fields (mode dropdown, model picker, style preset picker, prompt fields, etc.)
- `PUT/POST/DELETE` endpoints to persist
- `POST /api/admin/quick-tiles/reorder` after a drag

---

## Appendix A — File Reference Cheat Sheet

| Need to change... | Edit... |
|---|---|
| Chat dispatch logic (which provider to call) | `backend/src/modules/chat-history/chat-history.controller.ts` |
| Chat streaming format | `backend/src/modules/chat-history/services/*` |
| Image dispatch | `backend/src/modules/image-history/services/generate-image.service.ts` |
| Image provider adapters | `backend/src/modules/image-history/services/adapters/` |
| Model definitions | DB collection `aimodelgroups` (or admin GUI) |
| Auth | `backend/src/modules/auth/` |
| User store | `backend/src/modules/users/` |
| API base URL (frontend) | `frontend_v3/.env.development` + `.env.local` |
| Socket URL | same — `VUE_APP_URL` |
| Add a new chat screen | new `views/*.vue` + route in `router/index.js` |
| Add a new admin endpoint | `backend/src/admin/modules/admin-*` + register in `admin.module.ts` |

---

## Appendix B — Stream Markers

Backend writes to the SSE stream using these literal markers. Don't change them without updating both sides:

```
BINA_STREAM_RESPONSE_DIVIDER<24-char-id>BINA_STREAM_RESPONSE_DIVIDER
BINA_STREAM_REASONING_START
BINA_STREAM_REASONING_END
```

Parser: `frontend_v3/src/services/chatService.js` → `processStreamData(chunk)`.

---

## Appendix C — Common Pitfalls

1. **`Transaction numbers are only allowed on a replica set member or mongos`** — mongo not running as replica set. Fix in `/etc/mongod.conf`:
   ```
   replication:
     replSetName: "rs0"
   ```
   then `rs.initiate()` once.

2. **`Cannot find module ...`** after pulling — run `npm install` in both `backend/` and `frontend_v3/`. After git pulls always re-install.

3. **`localhost:3000` errors in browser** — `.env.development` takes precedence over `.env.local` in Vue CLI for mode-specific overrides. Make sure both files have the same correct `VUE_APP_API_URL`.

4. **`window.location.hostname` undefined** — happens during SSR. Our app is fully client-side, so safe; the dynamic-URL fallback handles it anyway.

5. **`Invalid reference to model version: flux-schnell`** — Flux model `providerKey` not in `owner/name` format. Run `backend/scripts/fix-flux-provider-keys.js` (in v1 backup).

6. **CORS errors** — backend currently uses `Access-Control-Allow-Origin: *`. If the frontend is served over HTTPS and the API over HTTP, the browser will still block. Use the same scheme on both, or terminate TLS at a proxy.

7. **WebSocket fails** — socket.io connects to `process.env.VUE_APP_URL`. If you set `VUE_APP_URL` to the **frontend** port, sockets won't reach backend. Always point it to the backend port. (We fixed this; the URL is now resolved dynamically.)
