ev-sales-desk-console/README.md
Yashas 1669bb755d feat: EV Sales Desk console — place a transferring call, watch it live
Two screens over Zino app 583:

  Place a call — lead name, model, and the two numbers that matter: the
  customer Aria dials, and the colleague the transfer bridges to. Submits the
  Add Lead workflow activity as the signed-in user; the workflow's trigger
  places the call server-side, so the zvk_ agent key never reaches the browser.

  Live monitor — polls the leads record view every 3s. "Transferred to human"
  is call_end_reason == handover, called out on its own because it is the thing
  the demo exists to show.

src/api.ts deliberately bypasses two SDK methods; both are SDK bugs:
  - workflows.startWorkflow() posts workflow_id, but core's StartRequest reads
    workflow_uuid and rejects the call.
  - views.getTabularView() does GET /view/recordview, the legacy unscoped route;
    the app-scoped one is POST /app/{id}/view/recordview with the query in the
    body.
Auth still goes through the SDK, so there is one token and one session.

Base URL is the root, not an app path: login is /usr/login while the app APIs
are under /app/583, so they sit at different depths.

Frontgen scaffold generated by the service's own ScaffoldReactProject, so the
build pipeline conventions (relative base, BASE_HREF placeholder, runtime
config.js) are byte-identical rather than reimplemented.

vite build + tsc -b both clean.
2026-08-26 22:58:25 +05:30

76 lines
2.9 KiB
Markdown

# EV Sales Desk Console
Two-screen operator console for the **live call-transfer demo**: place an
outbound AI sales call, and watch it transfer to a human colleague mid-call.
Backs onto Zino app **583** (org 900000104, `dev`). 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** Aria
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@verta-motors.demo`).
`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.