# AI Portal

Internal landing page that fronts a stack of AI tools (chat + workflow apps),
all routed through company API keys with per-user budgets and observability.

See `AI_PORTAL_HANDOFF.md` for full architecture and rationale.

## Phase 0 — what's running

- **Landing page** (Next.js 16 + Auth.js v5) — this repo, `landing/`
- **Open WebUI** — already running on host (`127.0.0.1:8080`)
- **Dify stack** — already running (`docker_default` network, nginx on `:443/:8090`)
- **LiteLLM** — already running on `localai` network (`:4000`)
- **Langfuse** — not yet (compose stub left in `docker-compose.yml`)

## Local dev

```bash
cd landing
cp ../.env.example ../.env       # then edit secrets
pnpm install
pnpm dev                         # http://localhost:3000
```

Set at minimum:
- `AUTH_SECRET` — `openssl rand -base64 32`
- `DEV_PORTAL_PASSWORD` — any string; gates the dev login

## Production-style run (Docker)

From the `portal/` directory:

```bash
docker compose up -d --build
```

The landing service binds to `127.0.0.1:3010`. Caddy on the host fronts it at
`https://ai.klas.chat`.

## Auth

Two providers, both wired through Auth.js v5:

1. **Microsoft Entra ID** — production. Activated automatically when
   `AUTH_MICROSOFT_ENTRA_ID_ID` and `AUTH_MICROSOFT_ENTRA_ID_SECRET` are set.
2. **Dev Credentials** — single shared password from `DEV_PORTAL_PASSWORD`.
   Used only while Entra is not provisioned. Both can coexist; the login page
   shows whichever is configured.

All routes except `/login` and `/api/auth/*` are gated by `proxy.ts`.

## Adding a new tile

Edit `landing/src/data/apps.json` and add an entry:

```json
{
  "id": "my-app",
  "title": "My App",
  "description": "What it does in one line.",
  "category": "Documents",
  "icon": "FileText",
  "accent": "blue",
  "href": "https://dify.klas.chat/app/<id>"
}
```

Icon names come from [lucide-react](https://lucide.dev). Accents:
`violet`, `blue`, `cyan`, `emerald`, `amber`, `rose`. Use `"href": "#"` to
mark a tile as "Coming soon".

## What's next (post Phase 0)

1. Wire Open WebUI to use LiteLLM as its OpenAI-compatible endpoint.
2. Configure Dify model providers to point at LiteLLM (so spend is attributed).
3. Stand up Langfuse (uncomment in `docker-compose.yml`).
4. Map per-user identity → LiteLLM virtual keys.
5. Provision Microsoft Entra app registration; flip prod auth on.
