{"version":3,"sources":["../call-control-sdk/lib/pages/caroVoiceAI/index.tsx"],"sourcesContent":["import React, { useMemo } from \"react\";\nimport { Box, Typography } from \"@mui/material\";\n\nimport { sdkStateManager } from \"../../hooks/sdk-state\";\n\n/**\n * Dashboard URL template. `{tenantId}` is replaced with the logged-in user's\n * tenant id from the login response (`tenantInfo.tenantId`). The token works\n * anywhere in the string, so the tenant can be moved between a path segment and a\n * query param by changing the env value alone:\n *   \".../executive-dashboard/{tenantId}\"\n *   \".../executive-dashboard?tenantId={tenantId}\"\n *\n * The AI voice app is deployed per tier on its own port (dev :3200, QA :3100), so\n * the template lives in env only — there is no URL in this source file to go stale:\n *   - `window.__ENV__.VITE_CARO_VOICE_AI_URL` — injected per container by\n *     docker-entrypoint.sh, so one image serves every tier;\n *   - `import.meta.env.VITE_CARO_VOICE_AI_URL` — baked at build time from\n *     .env / .env.qa, which is what local dev uses.\n * Runtime wins over build-time for the same reason as PHONE_IFRAME_BASE in\n * src/env.ts: a stale baked dev value must not override a configured container.\n *\n * NOTE: the dashboard must have a route that accepts the segment. As of the build\n * deployed on 2026-08-11 its router declares `executive-dashboard` with children\n * `break-down/:id` and `audit-report/:callUuid` only — no tenant param — so this\n * URL lands on that app's catch-all until they add a `:tenantId` child route.\n */\nconst CARO_VOICE_AI_URL_ENV_KEY = \"VITE_CARO_VOICE_AI_URL\";\n\n/** Non-blank string, or null — blank env values are absent values, not overrides. */\nconst nonBlank = (value: unknown): string | null =>\n\ttypeof value === \"string\" && value.trim() !== \"\" ? value.trim() : null;\n\n/**\n * Dashboard URL template from env, or null when neither source supplies one — an\n * unconfigured environment renders the \"not configured\" notice below rather than an\n * iframe pointed at a guessed host.\n */\nconst resolveCaroVoiceAIUrl = (): string | null => {\n\tconst sdkStateUrl = nonBlank(\n\t\tsdkStateManager.getState()?.urlConfig?.caroVoiceAIURL,\n\t);\n\tconst runtime =\n\t\ttypeof window === \"undefined\"\n\t\t\t? null\n\t\t\t: nonBlank((window as any).__ENV__?.[CARO_VOICE_AI_URL_ENV_KEY]);\n\tconst buildTime = nonBlank(import.meta.env?.VITE_CARO_VOICE_AI_URL);\n\treturn sdkStateUrl ?? runtime ?? buildTime;\n};\n\n/** Token in the URL template that the tenant id is substituted for. */\nconst TENANT_ID_PLACEHOLDER = \"{tenantId}\";\n\n/**\n * Legacy fallback: sessionStorage key a host app may mirror the login response's\n * `tenantInfo` (`{ tenantId, slug }`) under. Kept for hosts that already do this;\n * new integrations need nothing — the tenant comes from SDK state (see\n * resolveTenantId below).\n */\nconst TENANT_INFO_STORAGE_KEY = \"tenantInfo\";\n\n/** Tenant id from the mirrored login `tenantInfo`, or null when unavailable. */\nconst readTenantIdFromSession = (): string | null => {\n\tif (typeof sessionStorage === \"undefined\") return null;\n\tconst raw = sessionStorage.getItem(TENANT_INFO_STORAGE_KEY);\n\tif (!raw) return null;\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as { tenantId?: number | string } | null;\n\t\tconst id = parsed?.tenantId;\n\t\treturn id != null && id !== \"\" ? String(id) : null;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/**\n * Tenant for the dashboard, in order of precedence:\n *   1. an explicit `tenantId` prop, for hosts that need to override it;\n *   2. `tenantInfo.tenantId` from the login response, held on SDK state — this is\n *      the normal path and needs nothing from the host;\n *   3. the mirrored sessionStorage key, for hosts written against the old contract.\n *\n * SDK state is persisted, so (2) resolves on every mount — not just the one where\n * initSDK ran.\n */\nconst resolveTenantId = (tenantId?: number | string): string | null => {\n\tif (tenantId != null && tenantId !== \"\") return String(tenantId);\n\treturn sdkStateManager.getTenantId() ?? readTenantIdFromSession();\n};\n\nexport interface CaroVoiceAIPageProps {\n\t/** Tenant id for the dashboard. Overrides the one resolved from SDK state. */\n\ttenantId?: number | string;\n\t/**\n\t * URL template to embed; must contain `{tenantId}` to receive the tenant.\n\t * Overrides the env-resolved template (see resolveCaroVoiceAIUrl).\n\t */\n\turl?: string;\n}\n\n/**\n * Substitutes every `{tenantId}` in the template. An unresolved tenant becomes an\n * empty string rather than a literal \"{tenantId}\", which leaves a bare\n * \".../executive-dashboard/\" — the dashboard's own index route — instead of sending\n * it a path segment it can't match.\n */\nconst buildDashboardUrl = (template: string, tenantId?: number | string): string => {\n\tconst resolved = resolveTenantId(tenantId);\n\treturn template.split(TENANT_ID_PLACEHOLDER).join(encodeURIComponent(resolved ?? \"\"));\n};\n\nexport default function CaroVoiceAIPage({ tenantId, url }: CaroVoiceAIPageProps = {}) {\n\tconst src = useMemo(() => {\n\t\tconst template = nonBlank(url) ?? resolveCaroVoiceAIUrl();\n\t\treturn template === null ? null : buildDashboardUrl(template, tenantId);\n\t}, [url, tenantId]);\n\n\tif (src === null) {\n\t\treturn (\n\t\t\t<Box\n\t\t\t\tsx={{\n\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\theight: \"calc(100vh - 64px)\",\n\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\tp: 3,\n\t\t\t\t\ttextAlign: \"center\",\n\t\t\t\t\tcolor: \"text.secondary\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<Typography variant=\"body2\">\n\t\t\t\t\tCaro Voice AI is not configured for this environment. Set{\" \"}\n\t\t\t\t\t<Box component=\"code\" sx={{ fontFamily: \"monospace\" }}>\n\t\t\t\t\t\t{CARO_VOICE_AI_URL_ENV_KEY}\n\t\t\t\t\t</Box>{\" \"}\n\t\t\t\t\tand reload.\n\t\t\t\t</Typography>\n\t\t\t</Box>\n\t\t);\n\t}\n\n\treturn (\n\t\t<Box sx={{ width: \"100%\", height: \"calc(100vh - 64px)\" }}>\n\t\t\t<iframe\n\t\t\t\tsrc={src}\n\t\t\t\ttitle=\"Caro Voice AI\"\n\t\t\t\tstyle={{ width: \"100%\", height: \"100%\", border: \"none\" }}\n\t\t\t\tallow=\"microphone; camera; clipboard-read; clipboard-write\"\n\t\t\t/>\n\t\t</Box>\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA,SAAgB,eAAe;AAC/B,SAAS,KAAK,kBAAkB;AADhC;AAmII,SAEC,KAFD;AAxGJ,IAAM,4BAA4B;AAGlC,IAAM,WAAW,CAAC,UACjB,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,IAAI;AAOnE,IAAM,wBAAwB,MAAqB;AAtCnD;AAuCC,QAAM,cAAc;AAAA,KACnB,2BAAgB,SAAS,MAAzB,mBAA4B,cAA5B,mBAAuC;AAAA,EACxC;AACA,QAAM,UACL,OAAO,WAAW,cACf,OACA,UAAU,YAAe,YAAf,mBAAyB,0BAA0B;AACjE,QAAM,YAAY,UAAS,iBAAY,QAAZ,mBAAiB,sBAAsB;AAClE,UAAO,yCAAe,YAAf,YAA0B;AAClC;AAGA,IAAM,wBAAwB;AAQ9B,IAAM,0BAA0B;AAGhC,IAAM,0BAA0B,MAAqB;AACpD,MAAI,OAAO,mBAAmB,YAAa,QAAO;AAClD,QAAM,MAAM,eAAe,QAAQ,uBAAuB;AAC1D,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACH,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAM,KAAK,iCAAQ;AACnB,WAAO,MAAM,QAAQ,OAAO,KAAK,OAAO,EAAE,IAAI;AAAA,EAC/C,SAAQ;AACP,WAAO;AAAA,EACR;AACD;AAYA,IAAM,kBAAkB,CAAC,aAA8C;AArFvE;AAsFC,MAAI,YAAY,QAAQ,aAAa,GAAI,QAAO,OAAO,QAAQ;AAC/D,UAAO,qBAAgB,YAAY,MAA5B,YAAiC,wBAAwB;AACjE;AAkBA,IAAM,oBAAoB,CAAC,UAAkB,aAAuC;AACnF,QAAM,WAAW,gBAAgB,QAAQ;AACzC,SAAO,SAAS,MAAM,qBAAqB,EAAE,KAAK,mBAAmB,8BAAY,EAAE,CAAC;AACrF;AAEe,SAAR,gBAAiC,EAAE,UAAU,IAAI,IAA0B,CAAC,GAAG;AACrF,QAAM,MAAM,QAAQ,MAAM;AAhH3B;AAiHE,UAAM,YAAW,cAAS,GAAG,MAAZ,YAAiB,sBAAsB;AACxD,WAAO,aAAa,OAAO,OAAO,kBAAkB,UAAU,QAAQ;AAAA,EACvE,GAAG,CAAC,KAAK,QAAQ,CAAC;AAElB,MAAI,QAAQ,MAAM;AACjB,WACC;AAAA,MAAC;AAAA;AAAA,QACA,IAAI;AAAA,UACH,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,gBAAgB;AAAA,UAChB,GAAG;AAAA,UACH,WAAW;AAAA,UACX,OAAO;AAAA,QACR;AAAA,QAEA,+BAAC,cAAW,SAAQ,SAAQ;AAAA;AAAA,UAC+B;AAAA,UAC1D,oBAAC,OAAI,WAAU,QAAO,IAAI,EAAE,YAAY,YAAY,GAClD,qCACF;AAAA,UAAO;AAAA,UAAI;AAAA,WAEZ;AAAA;AAAA,IACD;AAAA,EAEF;AAEA,SACC,oBAAC,OAAI,IAAI,EAAE,OAAO,QAAQ,QAAQ,qBAAqB,GACtD;AAAA,IAAC;AAAA;AAAA,MACA;AAAA,MACA,OAAM;AAAA,MACN,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,MACvD,OAAM;AAAA;AAAA,EACP,GACD;AAEF;","names":[]}