Demo is for Mahindra, so the console carries their branding and their cars: BE 6, XEV 9e and XEV 9S replace the placeholder models, in step with the voice agent's own brief — a model offered in the dropdown that the agent has never heard of is a call where she has to improvise. Brand, persona (Meera) and the demo credentials move into config.ts rather than being scattered through the pages. Accent colour follows. Login is simplified: credentials are prefilled and the button takes focus, so signing in during a demo is one keystroke. Both fields stay editable — this is a dev demo tenant, not a real account. useState is explicitly typed <string>: DEMO_LOGIN is `as const`, so inferring from it narrowed the state to a literal type and setEmail/setPassword refused any other value. tsc -b and vite build both clean.
76 lines
3.0 KiB
Markdown
76 lines
3.0 KiB
Markdown
# Mahindra EV Sales Desk Console
|
|
|
|
Two-screen operator console for the **live call-transfer demo**: place an
|
|
outbound Mahindra EV sales call, and watch it transfer to a human colleague mid-call.
|
|
|
|
Backs onto Zino app **583** (org 900000104, `dev`). Line-up: **BE 6**, **XEV 9e**, **XEV 9S**. Built on the frontgen React
|
|
scaffold — Vite + Tailwind + React Router + TanStack Query + `src/zino-sdk`.
|
|
|
|
## The two screens
|
|
|
|
**Place a call** — lead name, model, and the two numbers: the **customer** Meera
|
|
dials, and the **colleague** the transfer bridges to. Submitting starts a
|
|
`Lead Outreach` workflow instance; its trigger places the call server-side.
|
|
|
|
**Live monitor** — polls the leads record view every 3s. `Transferred to human`
|
|
is the transfer having fired.
|
|
|
|
## Running locally
|
|
|
|
```bash
|
|
npm install
|
|
echo 'VITE_ZINO_API_URL=https://dev-apps.getzino.in' >> .env
|
|
npm run dev
|
|
```
|
|
|
|
Sign in with an app user of app 583 (e.g. `salesops@mahindra-ev.demo` / `Mahindra@2026` (prefilled on the login screen)).
|
|
|
|
`VITE_ZINO_API_URL` is only read from `.env` in **dev**. A production build
|
|
requires the `config.js` the server writes at placement time, and throws if it
|
|
is absent — a promoted artifact must never fall back to the build machine's
|
|
value and quietly call the wrong backend.
|
|
|
|
## API notes worth keeping
|
|
|
|
The base URL is the **root** (`https://dev-apps.getzino.in`), not an app-scoped
|
|
path, because login and the app APIs live at different depths:
|
|
|
|
| Call | Path |
|
|
|---|---|
|
|
| Login | `POST /usr/login` |
|
|
| Start a lead | `POST /app/583/start` |
|
|
| Leads list | `POST /app/583/view/recordview` |
|
|
|
|
**`src/api.ts` bypasses two SDK methods on purpose** — both are SDK bugs, not
|
|
preferences:
|
|
|
|
- `workflows.startWorkflow()` posts `workflow_id`, but core's `StartRequest`
|
|
reads **`workflow_uuid`** and rejects the call outright.
|
|
- `views.getTabularView()` issues `GET /view/recordview` — the legacy unscoped
|
|
route. The app-scoped one is **POST** `/app/{id}/view/recordview` with the
|
|
paging/filters in the body.
|
|
|
|
Auth still goes through the SDK, so there is one token and one session.
|
|
|
|
## Why the browser holds no API key
|
|
|
|
Screen 1 submits a workflow activity as the signed-in user. The workflow's own
|
|
trigger then places the call using the agent key held in its server-side config.
|
|
The `zvk_` key never reaches the browser.
|
|
|
|
## The per-call transfer number
|
|
|
|
`handover_number` rides on the lead into the call's `context`, and agents-backend
|
|
prefers it over anything configured on the agent — the agent's numbers are the
|
|
fallback for calls that don't name one. Requires an agents-backend carrying the
|
|
per-call override; without it the field is sent, silently ignored, and the call
|
|
transfers to the agent's default number instead.
|
|
|
|
## Known gap
|
|
|
|
The live transcript and the co-pilot's suggestions live in **agents-backend**,
|
|
which the app-user token does not reach — the SDK talks to the renderer's
|
|
workflow/view services. So the monitor shows call *status* live, but not the
|
|
turn-by-turn feed. Closing that needs either a new endpoint or the trigger
|
|
writing transcript lines onto the instance.
|