From 8a4340bc6cdf4527568452bd78764fb11d6a39ac Mon Sep 17 00:00:00 2001 From: nicolotognoni Date: Tue, 12 May 2026 12:43:19 +0200 Subject: [PATCH 1/6] fix(dashboard): preserve existing calls when new call arrives in SSE stream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `mergeCallPreserving` in `dashboard-app/src/hooks/useDashboardData.ts` rebuilt the calls array from the server snapshot via `next.map(...)`, so any call present in the previous UI state but missing from the next payload was silently dropped. With back-to-back calls, the SSE `call_start` refresh occasionally landed before the prior call propagated to `/api/dashboard/calls` and the row vanished from the SPA — regression reported as #124. The merge is now a true upsert: rows present in `prev` but absent from `next` are appended, so prior calls stay visible until the server snapshot stabilises. Server-side eviction (ring buffer of 500) bounds long-running sessions. Pure merge helpers extracted to `dashboard-app/src/hooks/mergeCalls.ts` and exercised by `dashboard-app/src/hooks/mergeCalls.test.ts` (added Vitest to the SPA so the helpers can be tested in isolation without a React harness). Refs #124. --- CHANGELOG.md | 17 +- dashboard-app/package-lock.json | 405 ++++++++++++++++++- dashboard-app/package.json | 4 +- dashboard-app/src/hooks/mergeCalls.test.ts | 138 +++++++ dashboard-app/src/hooks/mergeCalls.ts | 82 ++++ dashboard-app/src/hooks/useDashboardData.ts | 54 +-- dashboard-app/vitest.config.ts | 11 + libraries/python/getpatter/dashboard/ui.html | 2 +- libraries/typescript/package-lock.json | 4 +- libraries/typescript/src/dashboard/ui.html | 2 +- 10 files changed, 660 insertions(+), 59 deletions(-) create mode 100644 dashboard-app/src/hooks/mergeCalls.test.ts create mode 100644 dashboard-app/src/hooks/mergeCalls.ts create mode 100644 dashboard-app/vitest.config.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a45c01ef..fc895ae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ ## 0.6.1 (2026-05-12) +### Fixed — Dashboard SPA: live snapshot refresh dropped previously-visible calls when a new call started (#124) + +`mergeCallPreserving` in `dashboard-app/src/hooks/useDashboardData.ts` +replaced the UI array with the server snapshot via `next.map(...)`. When +a second call started back-to-back with the first, the SSE-triggered +refresh could land before `/api/dashboard/calls` reflected the prior +call (server publishes the SSE event ahead of the terminal write +completing), and the prior call vanished from the SPA even though it +was still in the server's ring buffer. The merge is now a true upsert: +calls present in `prev` but absent from `next` are appended, so the +prior row stays visible until the server snapshot stabilises. Pure +merge helpers extracted to `dashboard-app/src/hooks/mergeCalls.ts` with +unit coverage at `dashboard-app/src/hooks/mergeCalls.test.ts`; added a +minimal Vitest setup to `dashboard-app` so the SPA can exercise the +helper in isolation. + ### Changed — Cerebras usage-chunk fallback: INFO-once + DEBUG per iteration (Python + TypeScript parity) The char/4 fallback billing path in `services/llm_loop.py` / @@ -86,7 +102,6 @@ The convention is now uniform across both SDKs (locked in by tests): Negative deltas from clock skew or out-of-order timestamps are now clamped to `0` on both sides (the TypeScript side already did this; Python now does too). Files: `libraries/python/getpatter/services/metrics.py`, `libraries/python/getpatter/observability/metric_types.py` (docstring), `libraries/python/tests/test_metrics.py` (new `TestEOUMetricsEmission`), `libraries/typescript/tests/unit/metrics.test.ts` (new `emitEouMetrics field semantics` block). - ### Fixed — Barge-in bug bundle: 6.8s latency outliers, double-talk dispatch, stale anchors, firstMessage uninterruptible (Python + TypeScript parity) Real PSTN test (round 10f, 11 turns with user-initiated interruptions) surfaced four correlated bugs in the barge-in pipeline that the previous strategy work in 0.6.1 did not cover. Investigation report (`/private/tmp/.../a6fae04df253294f2.output`) traced all four to anchor mismanagement around the interrupt boundary plus an over-aggressive VAD threshold. diff --git a/dashboard-app/package-lock.json b/dashboard-app/package-lock.json index 974d8fb5..1ab62af1 100644 --- a/dashboard-app/package-lock.json +++ b/dashboard-app/package-lock.json @@ -17,7 +17,8 @@ "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.6.3", "vite": "^5.4.11", - "vite-plugin-singlefile": "^2.0.3" + "vite-plugin-singlefile": "^2.0.3", + "vitest": "^2.1.4" } }, "node_modules/@babel/code-frame": { @@ -1240,6 +1241,129 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.10.27", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", @@ -1300,6 +1424,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001792", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", @@ -1321,6 +1455,33 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1353,6 +1514,16 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.351", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.351.tgz", @@ -1360,6 +1531,13 @@ "dev": true, "license": "ISC" }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1409,6 +1587,26 @@ "node": ">=6" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1501,6 +1699,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -1511,6 +1716,16 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -1558,6 +1773,23 @@ "dev": true, "license": "MIT" }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1706,6 +1938,13 @@ "semver": "bin/semver.js" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1716,6 +1955,64 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1834,6 +2131,29 @@ } } }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vite-plugin-singlefile": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.3.tgz", @@ -1856,6 +2176,89 @@ } } }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/dashboard-app/package.json b/dashboard-app/package.json index c065f629..20cd78ba 100644 --- a/dashboard-app/package.json +++ b/dashboard-app/package.json @@ -9,6 +9,7 @@ "build": "tsc --noEmit && vite build", "preview": "vite preview", "lint": "tsc --noEmit", + "test": "vitest run", "sync": "node ./scripts/sync.mjs" }, "dependencies": { @@ -21,6 +22,7 @@ "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.6.3", "vite": "^5.4.11", - "vite-plugin-singlefile": "^2.0.3" + "vite-plugin-singlefile": "^2.0.3", + "vitest": "^2.1.4" } } diff --git a/dashboard-app/src/hooks/mergeCalls.test.ts b/dashboard-app/src/hooks/mergeCalls.test.ts new file mode 100644 index 00000000..17437a47 --- /dev/null +++ b/dashboard-app/src/hooks/mergeCalls.test.ts @@ -0,0 +1,138 @@ +import { describe, expect, it } from 'vitest'; +import type { CallRecord } from '../lib/api'; +import type { Call } from '../lib/mappers'; +import { mergeCallPreserving, mergeCalls } from './mergeCalls'; + +function record(callId: string, overrides: Partial = {}): CallRecord { + return { + call_id: callId, + caller: `from-${callId}`, + callee: `to-${callId}`, + direction: 'inbound', + started_at: 1000, + status: 'in-progress', + transcript: [], + turns: [], + metrics: null, + ...overrides, + }; +} + +function makeCall(id: string, overrides: Partial = {}): Call { + return { + id, + status: 'live', + direction: 'inbound', + from: `from-${id}`, + to: `to-${id}`, + carrier: 'twilio', + cost: {}, + ...overrides, + }; +} + +describe('mergeCalls', () => { + it('returns active before recent and dedupes by call_id', () => { + const active = [record('a', { status: 'in-progress' })]; + const recent = [record('b', { status: 'completed', ended_at: 1100 })]; + const result = mergeCalls(active, recent); + expect(result.map((c) => c.id)).toEqual(['a', 'b']); + }); + + it('active wins over recent when call_id appears in both', () => { + const active = [record('a', { status: 'in-progress', caller: 'live' })]; + const recent = [record('a', { status: 'completed', caller: 'stale' })]; + const result = mergeCalls(active, recent); + expect(result).toHaveLength(1); + expect(result[0].status).toBe('live'); + expect(result[0].from).toBe('live'); + }); +}); + +describe('mergeCallPreserving', () => { + it('regression #124: a second call_start refresh keeps the first call visible', () => { + // Step 1: call A is live, no recent. + const stateAfterAStart = mergeCallPreserving( + [], + mergeCalls([record('A', { status: 'in-progress' })], []), + ); + expect(stateAfterAStart.map((c) => c.id)).toEqual(['A']); + + // Step 2: call A ended, snapshot still includes it via /calls. + const stateAfterAEnd = mergeCallPreserving( + stateAfterAStart, + mergeCalls( + [], + [record('A', { status: 'completed', ended_at: 1100 })], + ), + ); + expect(stateAfterAEnd.map((c) => c.id)).toEqual(['A']); + expect(stateAfterAEnd[0].status).toBe('ended'); + + // Step 3: call B starts. The server SSE for call_start fires the refresh + // BEFORE the prior call A propagates to /api/dashboard/calls — simulate + // by having only B in the snapshot. Without the upsert, A would vanish. + const stateAfterBStart = mergeCallPreserving( + stateAfterAEnd, + mergeCalls([record('B', { status: 'in-progress' })], []), + ); + expect(stateAfterBStart.map((c) => c.id).sort()).toEqual(['A', 'B']); + }); + + it('upserts: next replaces prev for same id but unknown prev calls are kept', () => { + const prev: Call[] = [ + makeCall('A', { status: 'live', latencyP95: 250 }), + makeCall('B', { status: 'ended' }), + ]; + const next: Call[] = [makeCall('A', { status: 'ended', latencyP95: 280 })]; + const result = mergeCallPreserving(prev, next); + const a = result.find((c) => c.id === 'A')!; + const b = result.find((c) => c.id === 'B')!; + expect(a.status).toBe('ended'); + expect(a.latencyP95).toBe(280); + expect(b.status).toBe('ended'); + }); + + it('preserves rich fields the fresh payload omits', () => { + const prev: Call[] = [ + makeCall('A', { + latencyP95: 250, + latencyP50: 180, + sttAvg: 90, + ttsAvg: 110, + llmAvg: 320, + turnCount: 7, + agentResponseP50: 420, + agentResponseP95: 980, + cost: { llm: 0.01, stt: 0.002 }, + }), + ]; + const next: Call[] = [ + makeCall('A', { + status: 'ended', + cost: { llm: 0.012 }, + }), + ]; + const merged = mergeCallPreserving(prev, next); + const a = merged[0]; + expect(a.status).toBe('ended'); + expect(a.latencyP95).toBe(250); + expect(a.latencyP50).toBe(180); + expect(a.sttAvg).toBe(90); + expect(a.ttsAvg).toBe(110); + expect(a.llmAvg).toBe(320); + expect(a.turnCount).toBe(7); + expect(a.agentResponseP50).toBe(420); + expect(a.agentResponseP95).toBe(980); + expect(a.cost.llm).toBe(0.012); + expect(a.cost.stt).toBe(0.002); + }); + + it('two consecutive call_start SSE events for different ids end up with both visible', () => { + let state: Call[] = []; + state = mergeCallPreserving(state, mergeCalls([record('one')], [])); + expect(state.map((c) => c.id)).toEqual(['one']); + state = mergeCallPreserving(state, mergeCalls([record('two')], [])); + expect(state.map((c) => c.id).sort()).toEqual(['one', 'two']); + }); +}); diff --git a/dashboard-app/src/hooks/mergeCalls.ts b/dashboard-app/src/hooks/mergeCalls.ts new file mode 100644 index 00000000..efbb4b34 --- /dev/null +++ b/dashboard-app/src/hooks/mergeCalls.ts @@ -0,0 +1,82 @@ +// Pure SSE/refresh merge helpers extracted from useDashboardData so they can +// be unit-tested without a React harness. Both functions are immutable — +// callers must treat the returned arrays as readonly. + +import type { CallRecord } from '../lib/api'; +import { toUiCall, type Call } from '../lib/mappers'; + +/** + * Project the server's active + recent payloads into a single UI list with + * stable ordering: active calls first (live status surfaces at the top), + * then completed calls newest-first as the server returned them. Duplicate + * ``call_id`` is resolved active-wins-over-recent so a still-running call + * never gets a stale terminal row. + */ +export function mergeCalls(active: CallRecord[], recent: CallRecord[]): Call[] { + const seen = new Set(); + const merged: Call[] = []; + for (const record of active) { + if (seen.has(record.call_id)) continue; + seen.add(record.call_id); + merged.push(toUiCall(record)); + } + for (const record of recent) { + if (seen.has(record.call_id)) continue; + seen.add(record.call_id); + merged.push(toUiCall(record)); + } + return merged; +} + +/** + * Upsert a fresh snapshot of calls into the previous UI state by ``call_id``. + * + * Two reasons this is an upsert rather than a replace: + * + * 1. ``MetricsStore.updateCallStatus`` may write a synthetic terminal + * record with ``metrics: undefined`` ahead of the canonical + * ``recordCallEnd`` write (Twilio statusCallback racing the WS ``stop`` + * frame). The ``next.field ?? prev.field`` per-critical-field merge + * masks the race window so transcripts + latency don't blank out. See + * ``store.ts`` TODO(0.6.2) for the root-cause fix. + * + * 2. When a second call starts back-to-back with the first, the SSE + * ``call_start`` refresh occasionally lands with the freshly-ended call + * not yet visible in ``/api/dashboard/calls`` (the server publishes the + * SSE event for the new call before the prior call's terminal write + * completes, or pagination clips it). Replacing the array verbatim + * with the server response would drop the prior call from the UI even + * though it is still in the ring buffer — exactly the regression + * reported in #124. Treating ``prev`` as the union-anchor keeps the + * prior call visible until the server snapshot stabilises. + * + * The server's ``maxCalls`` ring buffer (default 500) bounds growth on + * long-lived sessions; the UI list is naturally bounded by what + * ``fetchCalls`` paginates plus whatever lives in ``prev`` from the + * current session. + */ +export function mergeCallPreserving(prev: Call[], next: Call[]): Call[] { + const prevById = new Map(prev.map((c) => [c.id, c])); + const nextIds = new Set(next.map((c) => c.id)); + const merged: Call[] = next.map((nc) => { + const pc = prevById.get(nc.id); + if (!pc) return nc; + return { + ...pc, + ...nc, + latencyP95: nc.latencyP95 ?? pc.latencyP95, + latencyP50: nc.latencyP50 ?? pc.latencyP50, + sttAvg: nc.sttAvg ?? pc.sttAvg, + ttsAvg: nc.ttsAvg ?? pc.ttsAvg, + llmAvg: nc.llmAvg ?? pc.llmAvg, + turnCount: nc.turnCount ?? pc.turnCount, + agentResponseP50: nc.agentResponseP50 ?? pc.agentResponseP50, + agentResponseP95: nc.agentResponseP95 ?? pc.agentResponseP95, + cost: { ...pc.cost, ...nc.cost }, + }; + }); + for (const pc of prev) { + if (!nextIds.has(pc.id)) merged.push(pc); + } + return merged; +} diff --git a/dashboard-app/src/hooks/useDashboardData.ts b/dashboard-app/src/hooks/useDashboardData.ts index 0b63fc19..5af3a174 100644 --- a/dashboard-app/src/hooks/useDashboardData.ts +++ b/dashboard-app/src/hooks/useDashboardData.ts @@ -14,9 +14,9 @@ import { fetchAggregates, fetchCalls, type Aggregates, - type CallRecord, } from '../lib/api'; -import { toUiCall, type Call } from '../lib/mappers'; +import type { Call } from '../lib/mappers'; +import { mergeCalls, mergeCallPreserving } from './mergeCalls'; export interface DashboardData { readonly calls: Call[]; @@ -38,56 +38,6 @@ const RELEVANT_EVENTS = [ 'call_end', ] as const; -function mergeCalls(active: CallRecord[], recent: CallRecord[]): Call[] { - const seen = new Set(); - const merged: Call[] = []; - for (const record of active) { - if (seen.has(record.call_id)) continue; - seen.add(record.call_id); - merged.push(toUiCall(record)); - } - for (const record of recent) { - if (seen.has(record.call_id)) continue; - seen.add(record.call_id); - merged.push(toUiCall(record)); - } - return merged; -} - -/** - * Merge a fresh snapshot of calls with the previous state, preserving any - * "rich" fields (transcripts, latency percentiles, cost breakdown) that the - * fresh payload happens to omit. - * - * The SDK-side ``MetricsStore.updateCallStatus`` may write a synthetic - * terminal record with ``metrics: undefined`` ahead of the canonical - * ``recordCallEnd`` write — when a Twilio statusCallback arrives before the - * WS ``stop`` frame. Without this merge, a live SSE refresh that pulled the - * synthetic record would wipe transcripts + latency from the prior call in - * the UI. ``next.field ?? prev.field`` per critical field masks the race - * window. See ``store.ts`` TODO(0.6.2) for the root-cause fix. - */ -function mergeCallPreserving(prev: Call[], next: Call[]): Call[] { - const prevById = new Map(prev.map((c) => [c.id, c])); - return next.map((nc) => { - const pc = prevById.get(nc.id); - if (!pc) return nc; - return { - ...pc, - ...nc, - latencyP95: nc.latencyP95 ?? pc.latencyP95, - latencyP50: nc.latencyP50 ?? pc.latencyP50, - sttAvg: nc.sttAvg ?? pc.sttAvg, - ttsAvg: nc.ttsAvg ?? pc.ttsAvg, - llmAvg: nc.llmAvg ?? pc.llmAvg, - turnCount: nc.turnCount ?? pc.turnCount, - agentResponseP50: nc.agentResponseP50 ?? pc.agentResponseP50, - agentResponseP95: nc.agentResponseP95 ?? pc.agentResponseP95, - cost: { ...pc.cost, ...nc.cost }, - }; - }); -} - function describeError(err: unknown): string { if (err instanceof Error) return err.message; return 'Unknown error'; diff --git a/dashboard-app/vitest.config.ts b/dashboard-app/vitest.config.ts new file mode 100644 index 00000000..171c2ca0 --- /dev/null +++ b/dashboard-app/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config'; + +// Vitest config separate from vite.config.ts so the SPA build (singlefile +// inline) is unaffected by the test runner. Only ``test`` files are picked +// up; the bundle still ships from src/ via vite.config.ts. +export default defineConfig({ + test: { + environment: 'node', + include: ['src/**/*.test.ts', 'src/**/*.test.tsx'], + }, +}); diff --git a/libraries/python/getpatter/dashboard/ui.html b/libraries/python/getpatter/dashboard/ui.html index 375ad9cc..c65888e3 100644 --- a/libraries/python/getpatter/dashboard/ui.html +++ b/libraries/python/getpatter/dashboard/ui.html @@ -54,7 +54,7 @@ `+l[s].replace(" at new "," at ");return e.displayName&&a.includes("")&&(a=a.replace("",e.displayName)),a}while(1<=s&&0<=i);break}}}finally{Dl=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?En(e):""}function uf(e){switch(e.tag){case 5:return En(e.type);case 16:return En("Lazy");case 13:return En("Suspense");case 19:return En("SuspenseList");case 0:case 2:case 15:return e=Il(e.type,!1),e;case 11:return e=Il(e.type.render,!1),e;case 1:return e=Il(e.type,!0),e;default:return""}}function po(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Ht:return"Fragment";case Ut:return"Portal";case ao:return"Profiler";case as:return"StrictMode";case co:return"Suspense";case fo:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Eu:return(e.displayName||"Context")+".Consumer";case ju:return(e._context.displayName||"Context")+".Provider";case cs:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case fs:return t=e.displayName||null,t!==null?t:po(e.type)||"Memo";case lt:t=e._payload,e=e._init;try{return po(e(t))}catch{}}return null}function af(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return po(t);case 8:return t===as?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function gt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Lu(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function cf(e){var t=Lu(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(s){r=""+s,o.call(this,s)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(s){r=""+s},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function pr(e){e._valueTracker||(e._valueTracker=cf(e))}function Pu(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Lu(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Wr(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function mo(e,t){var n=t.checked;return B({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function ri(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=gt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Tu(e,t){t=t.checked,t!=null&&us(e,"checked",t,!1)}function ho(e,t){Tu(e,t);var n=gt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?vo(e,t.type,n):t.hasOwnProperty("defaultValue")&&vo(e,t.type,gt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function li(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function vo(e,t,n){(t!=="number"||Wr(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Mn=Array.isArray;function en(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=mr.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Un(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Tn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},ff=["Webkit","ms","Moz","O"];Object.keys(Tn).forEach(function(e){ff.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Tn[t]=Tn[e]})});function Iu(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Tn.hasOwnProperty(e)&&Tn[e]?(""+t).trim():t+"px"}function Ou(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=Iu(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var df=B({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function wo(e,t){if(t){if(df[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(g(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(g(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(g(61))}if(t.style!=null&&typeof t.style!="object")throw Error(g(62))}}function xo(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var ko=null;function ds(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var So=null,tn=null,nn=null;function ii(e){if(e=sr(e)){if(typeof So!="function")throw Error(g(280));var t=e.stateNode;t&&(t=xl(t),So(e.stateNode,e.type,t))}}function Fu(e){tn?nn?nn.push(e):nn=[e]:tn=e}function Au(){if(tn){var e=tn,t=nn;if(nn=tn=null,ii(e),t)for(e=0;e>>=0,e===0?32:31-(Cf(e)/_f|0)|0}var hr=64,vr=4194304;function Ln(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Xr(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,o=e.pingedLanes,s=n&268435455;if(s!==0){var i=s&~l;i!==0?r=Ln(i):(o&=s,o!==0&&(r=Ln(o)))}else s=n&~l,s!==0?r=Ln(s):o!==0&&(r=Ln(o));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,o=t&-t,l>=o||l===16&&(o&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function lr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Ie(t),e[t]=n}function Mf(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Rn),vi=" ",yi=!1;function la(e,t){switch(e){case"keyup":return nd.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function oa(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Bt=!1;function ld(e,t){switch(e){case"compositionend":return oa(t);case"keypress":return t.which!==32?null:(yi=!0,vi);case"textInput":return e=t.data,e===vi&&yi?null:e;default:return null}}function od(e,t){if(Bt)return e==="compositionend"||!xs&&la(e,t)?(e=na(),Ir=ys=ut=null,Bt=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=ki(n)}}function aa(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?aa(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function ca(){for(var e=window,t=Wr();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Wr(e.document)}return t}function ks(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function md(e){var t=ca(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&aa(n.ownerDocument.documentElement,n)){if(r!==null&&ks(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,o=Math.min(r.start,l);r=r.end===void 0?o:Math.min(r.end,l),!e.extend&&o>r&&(l=r,r=o,o=l),l=Si(n,o);var s=Si(n,r);l&&s&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==s.node||e.focusOffset!==s.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),o>r?(e.addRange(t),e.extend(s.node,s.offset)):(t.setEnd(s.node,s.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Wt=null,Mo=null,In=null,Lo=!1;function Ci(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Lo||Wt==null||Wt!==Wr(r)||(r=Wt,"selectionStart"in r&&ks(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),In&&Yn(In,r)||(In=r,r=Jr(Mo,"onSelect"),0Yt||(e.current=Io[Yt],Io[Yt]=null,Yt--)}function O(e,t){Yt++,Io[Yt]=e.current,e.current=t}var wt={},oe=kt(wt),pe=kt(!1),zt=wt;function un(e,t){var n=e.type.contextTypes;if(!n)return wt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},o;for(o in n)l[o]=t[o];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function me(e){return e=e.childContextTypes,e!=null}function br(){$(pe),$(oe)}function Pi(e,t,n){if(oe.current!==wt)throw Error(g(168));O(oe,t),O(pe,n)}function wa(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(g(108,af(e)||"Unknown",l));return B({},n,r)}function el(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||wt,zt=oe.current,O(oe,e),O(pe,pe.current),!0}function Ti(e,t,n){var r=e.stateNode;if(!r)throw Error(g(169));n?(e=wa(e,t,zt),r.__reactInternalMemoizedMergedChildContext=e,$(pe),$(oe),O(oe,e)):$(pe),O(pe,n)}var Qe=null,kl=!1,Gl=!1;function xa(e){Qe===null?Qe=[e]:Qe.push(e)}function jd(e){kl=!0,xa(e)}function St(){if(!Gl&&Qe!==null){Gl=!0;var e=0,t=I;try{var n=Qe;for(I=1;e>=s,l-=s,Ke=1<<32-Ie(t)+l|n<E?(F=N,N=null):F=N.sibling;var T=h(d,N,p[E],y);if(T===null){N===null&&(N=F);break}e&&N&&T.alternate===null&&t(d,N),f=o(T,f,E),C===null?_=T:C.sibling=T,C=T,N=F}if(E===p.length)return n(d,N),V&&Nt(d,E),_;if(N===null){for(;EE?(F=N,N=null):F=N.sibling;var ve=h(d,N,T.value,y);if(ve===null){N===null&&(N=F);break}e&&N&&ve.alternate===null&&t(d,N),f=o(ve,f,E),C===null?_=ve:C.sibling=ve,C=ve,N=F}if(T.done)return n(d,N),V&&Nt(d,E),_;if(N===null){for(;!T.done;E++,T=p.next())T=v(d,T.value,y),T!==null&&(f=o(T,f,E),C===null?_=T:C.sibling=T,C=T);return V&&Nt(d,E),_}for(N=r(d,N);!T.done;E++,T=p.next())T=S(N,d,E,T.value,y),T!==null&&(e&&T.alternate!==null&&N.delete(T.key===null?E:T.key),f=o(T,f,E),C===null?_=T:C.sibling=T,C=T);return e&&N.forEach(function(et){return t(d,et)}),V&&Nt(d,E),_}function R(d,f,p,y){if(typeof p=="object"&&p!==null&&p.type===Ht&&p.key===null&&(p=p.props.children),typeof p=="object"&&p!==null){switch(p.$$typeof){case dr:e:{for(var _=p.key,C=f;C!==null;){if(C.key===_){if(_=p.type,_===Ht){if(C.tag===7){n(d,C.sibling),f=l(C,p.props.children),f.return=d,d=f;break e}}else if(C.elementType===_||typeof _=="object"&&_!==null&&_.$$typeof===lt&&Di(_)===C.type){n(d,C.sibling),f=l(C,p.props),f.ref=_n(d,C,p),f.return=d,d=f;break e}n(d,C);break}else t(d,C);C=C.sibling}p.type===Ht?(f=Tt(p.props.children,d.mode,y,p.key),f.return=d,d=f):(y=Br(p.type,p.key,p.props,null,d.mode,y),y.ref=_n(d,f,p),y.return=d,d=y)}return s(d);case Ut:e:{for(C=p.key;f!==null;){if(f.key===C)if(f.tag===4&&f.stateNode.containerInfo===p.containerInfo&&f.stateNode.implementation===p.implementation){n(d,f.sibling),f=l(f,p.children||[]),f.return=d,d=f;break e}else{n(d,f);break}else t(d,f);f=f.sibling}f=ro(p,d.mode,y),f.return=d,d=f}return s(d);case lt:return C=p._init,R(d,f,C(p._payload),y)}if(Mn(p))return x(d,f,p,y);if(wn(p))return k(d,f,p,y);Cr(d,p)}return typeof p=="string"&&p!==""||typeof p=="number"?(p=""+p,f!==null&&f.tag===6?(n(d,f.sibling),f=l(f,p),f.return=d,d=f):(n(d,f),f=no(p,d.mode,y),f.return=d,d=f),s(d)):n(d,f)}return R}var cn=_a(!0),Na=_a(!1),rl=kt(null),ll=null,Zt=null,Ns=null;function js(){Ns=Zt=ll=null}function Es(e){var t=rl.current;$(rl),e._currentValue=t}function Ao(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function ln(e,t){ll=e,Ns=Zt=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(de=!0),e.firstContext=null)}function Ee(e){var t=e._currentValue;if(Ns!==e)if(e={context:e,memoizedValue:t,next:null},Zt===null){if(ll===null)throw Error(g(308));Zt=e,ll.dependencies={lanes:0,firstContext:e}}else Zt=Zt.next=e;return t}var Mt=null;function Ms(e){Mt===null?Mt=[e]:Mt.push(e)}function ja(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Ms(t)):(n.next=l.next,l.next=n),t.interleaved=n,Je(e,r)}function Je(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var ot=!1;function Ls(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Ea(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Xe(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function mt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,D&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Je(e,n)}return l=r.interleaved,l===null?(t.next=t,Ms(r)):(t.next=l.next,l.next=t),r.interleaved=t,Je(e,n)}function Fr(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ms(e,n)}}function Ii(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,o=null;if(n=n.firstBaseUpdate,n!==null){do{var s={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};o===null?l=o=s:o=o.next=s,n=n.next}while(n!==null);o===null?l=o=t:o=o.next=t}else l=o=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:o,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ol(e,t,n,r){var l=e.updateQueue;ot=!1;var o=l.firstBaseUpdate,s=l.lastBaseUpdate,i=l.shared.pending;if(i!==null){l.shared.pending=null;var a=i,c=a.next;a.next=null,s===null?o=c:s.next=c,s=a;var m=e.alternate;m!==null&&(m=m.updateQueue,i=m.lastBaseUpdate,i!==s&&(i===null?m.firstBaseUpdate=c:i.next=c,m.lastBaseUpdate=a))}if(o!==null){var v=l.baseState;s=0,m=c=a=null,i=o;do{var h=i.lane,S=i.eventTime;if((r&h)===h){m!==null&&(m=m.next={eventTime:S,lane:0,tag:i.tag,payload:i.payload,callback:i.callback,next:null});e:{var x=e,k=i;switch(h=t,S=n,k.tag){case 1:if(x=k.payload,typeof x=="function"){v=x.call(S,v,h);break e}v=x;break e;case 3:x.flags=x.flags&-65537|128;case 0:if(x=k.payload,h=typeof x=="function"?x.call(S,v,h):x,h==null)break e;v=B({},v,h);break e;case 2:ot=!0}}i.callback!==null&&i.lane!==0&&(e.flags|=64,h=l.effects,h===null?l.effects=[i]:h.push(i))}else S={eventTime:S,lane:h,tag:i.tag,payload:i.payload,callback:i.callback,next:null},m===null?(c=m=S,a=v):m=m.next=S,s|=h;if(i=i.next,i===null){if(i=l.shared.pending,i===null)break;h=i,i=h.next,h.next=null,l.lastBaseUpdate=h,l.shared.pending=null}}while(!0);if(m===null&&(a=v),l.baseState=a,l.firstBaseUpdate=c,l.lastBaseUpdate=m,t=l.shared.interleaved,t!==null){l=t;do s|=l.lane,l=l.next;while(l!==t)}else o===null&&(l.shared.lanes=0);It|=s,e.lanes=s,e.memoizedState=v}}function Oi(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Jl.transition;Jl.transition={};try{e(!1),t()}finally{I=n,Jl.transition=r}}function Wa(){return Me().memoizedState}function Pd(e,t,n){var r=vt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},Qa(e))Ka(t,n);else if(n=ja(e,t,n,r),n!==null){var l=ue();Oe(n,e,r,l),Ya(n,t,r)}}function Td(e,t,n){var r=vt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(Qa(e))Ka(t,l);else{var o=e.alternate;if(e.lanes===0&&(o===null||o.lanes===0)&&(o=t.lastRenderedReducer,o!==null))try{var s=t.lastRenderedState,i=o(s,n);if(l.hasEagerState=!0,l.eagerState=i,Fe(i,s)){var a=t.interleaved;a===null?(l.next=l,Ms(t)):(l.next=a.next,a.next=l),t.interleaved=l;return}}catch{}finally{}n=ja(e,t,l,r),n!==null&&(l=ue(),Oe(n,e,r,l),Ya(n,t,r))}}function Qa(e){var t=e.alternate;return e===H||t!==null&&t===H}function Ka(e,t){On=il=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ya(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ms(e,n)}}var ul={readContext:Ee,useCallback:ne,useContext:ne,useEffect:ne,useImperativeHandle:ne,useInsertionEffect:ne,useLayoutEffect:ne,useMemo:ne,useReducer:ne,useRef:ne,useState:ne,useDebugValue:ne,useDeferredValue:ne,useTransition:ne,useMutableSource:ne,useSyncExternalStore:ne,useId:ne,unstable_isNewReconciler:!1},zd={readContext:Ee,useCallback:function(e,t){return $e().memoizedState=[e,t===void 0?null:t],e},useContext:Ee,useEffect:Ai,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,$r(4194308,4,$a.bind(null,t,e),n)},useLayoutEffect:function(e,t){return $r(4194308,4,e,t)},useInsertionEffect:function(e,t){return $r(4,2,e,t)},useMemo:function(e,t){var n=$e();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=$e();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Pd.bind(null,H,e),[r.memoizedState,e]},useRef:function(e){var t=$e();return e={current:e},t.memoizedState=e},useState:Fi,useDebugValue:Fs,useDeferredValue:function(e){return $e().memoizedState=e},useTransition:function(){var e=Fi(!1),t=e[0];return e=Ld.bind(null,e[1]),$e().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=H,l=$e();if(V){if(n===void 0)throw Error(g(407));n=n()}else{if(n=t(),q===null)throw Error(g(349));Dt&30||Ta(r,t,n)}l.memoizedState=n;var o={value:n,getSnapshot:t};return l.queue=o,Ai(Ra.bind(null,r,o,e),[e]),r.flags|=2048,tr(9,za.bind(null,r,o,n,t),void 0,null),n},useId:function(){var e=$e(),t=q.identifierPrefix;if(V){var n=Ye,r=Ke;n=(r&~(1<<32-Ie(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=bn++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=s.createElement(n,{is:r.is}):(e=s.createElement(n),n==="select"&&(s=e,r.multiple?s.multiple=!0:r.size&&(s.size=r.size))):e=s.createElementNS(e,n),e[Ve]=t,e[Zn]=r,rc(e,t,!1,!1),t.stateNode=e;e:{switch(s=xo(n,r),n){case"dialog":A("cancel",e),A("close",e),l=r;break;case"iframe":case"object":case"embed":A("load",e),l=r;break;case"video":case"audio":for(l=0;lpn&&(t.flags|=128,r=!0,Nn(o,!1),t.lanes=4194304)}else{if(!r)if(e=sl(s),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Nn(o,!0),o.tail===null&&o.tailMode==="hidden"&&!s.alternate&&!V)return re(t),null}else 2*K()-o.renderingStartTime>pn&&n!==1073741824&&(t.flags|=128,r=!0,Nn(o,!1),t.lanes=4194304);o.isBackwards?(s.sibling=t.child,t.child=s):(n=o.last,n!==null?n.sibling=s:t.child=s,o.last=s)}return o.tail!==null?(t=o.tail,o.rendering=t,o.tail=t.sibling,o.renderingStartTime=K(),t.sibling=null,n=U.current,O(U,r?n&1|2:n&1),t):(re(t),null);case 22:case 23:return Bs(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?ye&1073741824&&(re(t),t.subtreeFlags&6&&(t.flags|=8192)):re(t),null;case 24:return null;case 25:return null}throw Error(g(156,t.tag))}function Vd(e,t){switch(Cs(t),t.tag){case 1:return me(t.type)&&br(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return fn(),$(pe),$(oe),zs(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Ts(t),null;case 13:if($(U),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(g(340));an()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return $(U),null;case 4:return fn(),null;case 10:return Es(t.type._context),null;case 22:case 23:return Bs(),null;case 24:return null;default:return null}}var Nr=!1,le=!1,Ud=typeof WeakSet=="function"?WeakSet:Set,j=null;function Jt(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Q(e,t,r)}else n.current=null}function Yo(e,t,n){try{n()}catch(r){Q(e,t,r)}}var Gi=!1;function Hd(e,t){if(Po=Gr,e=ca(),ks(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break e}var s=0,i=-1,a=-1,c=0,m=0,v=e,h=null;t:for(;;){for(var S;v!==n||l!==0&&v.nodeType!==3||(i=s+l),v!==o||r!==0&&v.nodeType!==3||(a=s+r),v.nodeType===3&&(s+=v.nodeValue.length),(S=v.firstChild)!==null;)h=v,v=S;for(;;){if(v===e)break t;if(h===n&&++c===l&&(i=s),h===o&&++m===r&&(a=s),(S=v.nextSibling)!==null)break;v=h,h=v.parentNode}v=S}n=i===-1||a===-1?null:{start:i,end:a}}else n=null}n=n||{start:0,end:0}}else n=null;for(To={focusedElem:e,selectionRange:n},Gr=!1,j=t;j!==null;)if(t=j,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,j=e;else for(;j!==null;){t=j;try{var x=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(x!==null){var k=x.memoizedProps,R=x.memoizedState,d=t.stateNode,f=d.getSnapshotBeforeUpdate(t.elementType===t.type?k:Pe(t.type,k),R);d.__reactInternalSnapshotBeforeUpdate=f}break;case 3:var p=t.stateNode.containerInfo;p.nodeType===1?p.textContent="":p.nodeType===9&&p.documentElement&&p.removeChild(p.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(g(163))}}catch(y){Q(t,t.return,y)}if(e=t.sibling,e!==null){e.return=t.return,j=e;break}j=t.return}return x=Gi,Gi=!1,x}function Fn(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var o=l.destroy;l.destroy=void 0,o!==void 0&&Yo(t,n,o)}l=l.next}while(l!==r)}}function _l(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Xo(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function sc(e){var t=e.alternate;t!==null&&(e.alternate=null,sc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ve],delete t[Zn],delete t[Do],delete t[_d],delete t[Nd])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function ic(e){return e.tag===5||e.tag===3||e.tag===4}function Zi(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||ic(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Go(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=qr));else if(r!==4&&(e=e.child,e!==null))for(Go(e,t,n),e=e.sibling;e!==null;)Go(e,t,n),e=e.sibling}function Zo(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Zo(e,t,n),e=e.sibling;e!==null;)Zo(e,t,n),e=e.sibling}var b=null,Te=!1;function rt(e,t,n){for(n=n.child;n!==null;)uc(e,t,n),n=n.sibling}function uc(e,t,n){if(Ue&&typeof Ue.onCommitFiberUnmount=="function")try{Ue.onCommitFiberUnmount(vl,n)}catch{}switch(n.tag){case 5:le||Jt(n,t);case 6:var r=b,l=Te;b=null,rt(e,t,n),b=r,Te=l,b!==null&&(Te?(e=b,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):b.removeChild(n.stateNode));break;case 18:b!==null&&(Te?(e=b,n=n.stateNode,e.nodeType===8?Xl(e.parentNode,n):e.nodeType===1&&Xl(e,n),Qn(e)):Xl(b,n.stateNode));break;case 4:r=b,l=Te,b=n.stateNode.containerInfo,Te=!0,rt(e,t,n),b=r,Te=l;break;case 0:case 11:case 14:case 15:if(!le&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var o=l,s=o.destroy;o=o.tag,s!==void 0&&(o&2||o&4)&&Yo(n,t,s),l=l.next}while(l!==r)}rt(e,t,n);break;case 1:if(!le&&(Jt(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(i){Q(n,t,i)}rt(e,t,n);break;case 21:rt(e,t,n);break;case 22:n.mode&1?(le=(r=le)||n.memoizedState!==null,rt(e,t,n),le=r):rt(e,t,n);break;default:rt(e,t,n)}}function Ji(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Ud),t.forEach(function(r){var l=Jd.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function Le(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=s),r&=~o}if(r=l,r=K()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Wd(r/1960))-r,10e?16:e,at===null)var r=!1;else{if(e=at,at=null,fl=0,D&6)throw Error(g(331));var l=D;for(D|=4,j=e.current;j!==null;){var o=j,s=o.child;if(j.flags&16){var i=o.deletions;if(i!==null){for(var a=0;aK()-Us?Pt(e,0):Vs|=n),he(e,t)}function vc(e,t){t===0&&(e.mode&1?(t=vr,vr<<=1,!(vr&130023424)&&(vr=4194304)):t=1);var n=ue();e=Je(e,t),e!==null&&(lr(e,t,n),he(e,n))}function Zd(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),vc(e,n)}function Jd(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(g(314))}r!==null&&r.delete(t),vc(e,n)}var yc;yc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||pe.current)de=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return de=!1,Ad(e,t,n);de=!!(e.flags&131072)}else de=!1,V&&t.flags&1048576&&ka(t,nl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Vr(e,t),e=t.pendingProps;var l=un(t,oe.current);ln(t,n),l=Ds(null,t,r,e,l,n);var o=Is();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,me(r)?(o=!0,el(t)):o=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ls(t),l.updater=Cl,t.stateNode=l,l._reactInternals=t,Vo(t,r,e,n),t=Bo(null,t,r,!0,o,n)):(t.tag=0,V&&o&&Ss(t),se(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Vr(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=bd(r),e=Pe(r,e),l){case 0:t=Ho(null,t,r,e,n);break e;case 1:t=Ki(null,t,r,e,n);break e;case 11:t=Wi(null,t,r,e,n);break e;case 14:t=Qi(null,t,r,Pe(r.type,e),n);break e}throw Error(g(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Ho(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Ki(e,t,r,l,n);case 3:e:{if(ec(t),e===null)throw Error(g(387));r=t.pendingProps,o=t.memoizedState,l=o.element,Ea(e,t),ol(t,r,null,n);var s=t.memoizedState;if(r=s.element,o.isDehydrated)if(o={element:r,isDehydrated:!1,cache:s.cache,pendingSuspenseBoundaries:s.pendingSuspenseBoundaries,transitions:s.transitions},t.updateQueue.baseState=o,t.memoizedState=o,t.flags&256){l=dn(Error(g(423)),t),t=Yi(e,t,r,n,l);break e}else if(r!==l){l=dn(Error(g(424)),t),t=Yi(e,t,r,n,l);break e}else for(ge=pt(t.stateNode.containerInfo.firstChild),we=t,V=!0,Re=null,n=Na(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(an(),r===l){t=qe(e,t,n);break e}se(e,t,r,n)}t=t.child}return t;case 5:return Ma(t),e===null&&Fo(t),r=t.type,l=t.pendingProps,o=e!==null?e.memoizedProps:null,s=l.children,zo(r,l)?s=null:o!==null&&zo(r,o)&&(t.flags|=32),ba(e,t),se(e,t,s,n),t.child;case 6:return e===null&&Fo(t),null;case 13:return tc(e,t,n);case 4:return Ps(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=cn(t,null,r,n):se(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Wi(e,t,r,l,n);case 7:return se(e,t,t.pendingProps,n),t.child;case 8:return se(e,t,t.pendingProps.children,n),t.child;case 12:return se(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,o=t.memoizedProps,s=l.value,O(rl,r._currentValue),r._currentValue=s,o!==null)if(Fe(o.value,s)){if(o.children===l.children&&!pe.current){t=qe(e,t,n);break e}}else for(o=t.child,o!==null&&(o.return=t);o!==null;){var i=o.dependencies;if(i!==null){s=o.child;for(var a=i.firstContext;a!==null;){if(a.context===r){if(o.tag===1){a=Xe(-1,n&-n),a.tag=2;var c=o.updateQueue;if(c!==null){c=c.shared;var m=c.pending;m===null?a.next=a:(a.next=m.next,m.next=a),c.pending=a}}o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),Ao(o.return,n,t),i.lanes|=n;break}a=a.next}}else if(o.tag===10)s=o.type===t.type?null:o.child;else if(o.tag===18){if(s=o.return,s===null)throw Error(g(341));s.lanes|=n,i=s.alternate,i!==null&&(i.lanes|=n),Ao(s,n,t),s=o.sibling}else s=o.child;if(s!==null)s.return=o;else for(s=o;s!==null;){if(s===t){s=null;break}if(o=s.sibling,o!==null){o.return=s.return,s=o;break}s=s.return}o=s}se(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,ln(t,n),l=Ee(l),r=r(l),t.flags|=1,se(e,t,r,n),t.child;case 14:return r=t.type,l=Pe(r,t.pendingProps),l=Pe(r.type,l),Qi(e,t,r,l,n);case 15:return Ja(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Vr(e,t),t.tag=1,me(r)?(e=!0,el(t)):e=!1,ln(t,n),Xa(t,r,l),Vo(t,r,l,n),Bo(null,t,r,!0,e,n);case 19:return nc(e,t,n);case 22:return qa(e,t,n)}throw Error(g(156,t.tag))};function gc(e,t){return Qu(e,t)}function qd(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Ne(e,t,n,r){return new qd(e,t,n,r)}function Qs(e){return e=e.prototype,!(!e||!e.isReactComponent)}function bd(e){if(typeof e=="function")return Qs(e)?1:0;if(e!=null){if(e=e.$$typeof,e===cs)return 11;if(e===fs)return 14}return 2}function yt(e,t){var n=e.alternate;return n===null?(n=Ne(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Br(e,t,n,r,l,o){var s=2;if(r=e,typeof e=="function")Qs(e)&&(s=1);else if(typeof e=="string")s=5;else e:switch(e){case Ht:return Tt(n.children,l,o,t);case as:s=8,l|=8;break;case ao:return e=Ne(12,n,t,l|2),e.elementType=ao,e.lanes=o,e;case co:return e=Ne(13,n,t,l),e.elementType=co,e.lanes=o,e;case fo:return e=Ne(19,n,t,l),e.elementType=fo,e.lanes=o,e;case Mu:return jl(n,l,o,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case ju:s=10;break e;case Eu:s=9;break e;case cs:s=11;break e;case fs:s=14;break e;case lt:s=16,r=null;break e}throw Error(g(130,e==null?e:typeof e,""))}return t=Ne(s,n,t,l),t.elementType=e,t.type=r,t.lanes=o,t}function Tt(e,t,n,r){return e=Ne(7,e,r,t),e.lanes=n,e}function jl(e,t,n,r){return e=Ne(22,e,r,t),e.elementType=Mu,e.lanes=n,e.stateNode={isHidden:!1},e}function no(e,t,n){return e=Ne(6,e,null,t),e.lanes=n,e}function ro(e,t,n){return t=Ne(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function ep(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Fl(0),this.expirationTimes=Fl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Fl(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Ks(e,t,n,r,l,o,s,i,a){return e=new ep(e,t,n,i,a),t===1?(t=1,o===!0&&(t|=8)):t=0,o=Ne(3,null,null,t),e.current=o,o.stateNode=e,o.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ls(o),e}function tp(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Sc)}catch(e){console.error(e)}}Sc(),Su.exports=ke;var sp=Su.exports,ou=sp;io.createRoot=ou.createRoot,io.hydrateRoot=ou.hydrateRoot;function ip({strokeWidth:e=60,...t}){return u.jsx("svg",{viewBox:"0 0 1188 1773",fill:"none",xmlns:"http://www.w3.org/2000/svg",role:"img","aria-hidden":"true",...t,children:u.jsx("path",{d:"M25 561L245 694M25 561V818M245 694V951M25 961V1218M25 1357V1614M245 1489V1747M245 1093V1351M942 823V1080M1161 955V1213M1162 555V812M942 422V679M669 585V843L787 913M942 25V282M1162 158V415M25 818L245 951M244 1094L464 962M25 961L143 890M244 1352L464 1219M942 823L1162 956M942 679L1162 812M721 811L942 679M669 842L724 809M669 586L724 553M1041 883L1162 812M245 1747L1161 1213M244 1490L942 1080M25 1357L142 1289M518 1071L942 823M721 555L942 422M942 422L1162 556M942 282L1162 415M942 25L1162 158M942 1080L1161 1213M25 1218L245 1351M25 961L245 1094M464 962L519 929M464 1219L519 1186V928L403 859M25 1357L245 1490M25 1614L245 1747M25 561L942 25M244 694L941 282M1043 484L1162 415M245 951L668 704",stroke:"currentColor",strokeWidth:e,strokeLinecap:"round"})})}function up(e){return u.jsxs("svg",{viewBox:"269 80 364 110",fill:"none",xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":"Patter",...e,children:[u.jsx("path",{d:"M271.422 182.689V85.9524H317.517C324.705 85.9524 330.86 87.2064 335.982 89.7143C341.193 92.2223 345.192 95.7156 347.977 100.194C350.852 104.673 352.29 109.913 352.29 115.914C352.29 121.915 350.852 127.2 347.977 131.768C345.102 136.336 341.058 139.919 335.847 142.516C330.725 145.024 324.615 146.278 317.517 146.278H287.866V130.424H316.439C321.201 130.424 324.885 129.125 327.491 126.528C330.186 123.841 331.534 120.348 331.534 116.048C331.534 111.749 330.186 108.3 327.491 105.703C324.885 103.105 321.201 101.806 316.439 101.806H292.178V182.689H271.422Z",fill:"currentColor"}),u.jsx("path",{d:"M395.375 182.689C394.836 180.718 394.432 178.613 394.162 176.374C393.982 174.135 393.893 171.537 393.893 168.581H393.353V136.202C393.353 133.425 392.41 131.275 390.523 129.752C388.726 128.14 386.03 127.334 382.436 127.334C379.022 127.334 376.281 127.916 374.215 129.081C372.238 130.245 370.935 131.947 370.306 134.186H351.033C351.931 128.006 355.121 122.9 360.602 118.87C366.083 114.839 373.586 112.824 383.11 112.824C392.994 112.824 400.542 115.018 405.753 119.407C410.965 123.796 413.57 130.111 413.57 138.351V168.581C413.57 170.821 413.705 173.105 413.975 175.434C414.334 177.673 414.873 180.091 415.592 182.689H395.375ZM371.384 184.032C364.556 184.032 359.12 182.33 355.076 178.927C351.033 175.434 349.011 170.821 349.011 165.088C349.011 158.729 351.392 153.623 356.154 149.772C361.006 145.83 367.745 143.278 376.371 142.113L396.453 139.292V150.981L379.741 153.533C376.147 154.071 373.496 155.056 371.789 156.489C370.082 157.922 369.228 159.893 369.228 162.401C369.228 164.64 370.037 166.342 371.654 167.507C373.271 168.671 375.428 169.253 378.123 169.253C382.347 169.253 385.941 168.134 388.906 165.894C391.871 163.565 393.353 160.878 393.353 157.833L395.24 168.581C393.264 173.687 390.254 177.538 386.21 180.136C382.167 182.734 377.225 184.032 371.384 184.032Z",fill:"currentColor"}),u.jsx("path",{d:"M450.248 184.167C441.443 184.167 434.883 182.062 430.57 177.852C426.347 173.553 424.236 167.059 424.236 158.37V98.8506L444.453 91.3266V159.042C444.453 162.087 445.306 164.372 447.014 165.894C448.721 167.417 451.371 168.178 454.966 168.178C456.313 168.178 457.571 168.044 458.739 167.775C459.907 167.507 461.075 167.193 462.244 166.835V182.151C461.075 182.778 459.413 183.271 457.257 183.629C455.19 183.988 452.854 184.167 450.248 184.167ZM411.432 129.484V114.167H462.244V129.484H411.432Z",fill:"currentColor"}),u.jsx("path",{d:"M500.501 184.167C491.695 184.167 485.136 182.062 480.823 177.852C476.6 173.553 474.489 167.059 474.489 158.37V98.8506L494.705 91.3266V159.042C494.705 162.087 495.559 164.372 497.266 165.894C498.973 167.417 501.624 168.178 505.218 168.178C506.566 168.178 507.824 168.044 508.992 167.775C510.16 167.507 511.328 167.193 512.496 166.835V182.151C511.328 182.778 509.666 183.271 507.509 183.629C505.443 183.988 503.107 184.167 500.501 184.167ZM461.684 129.484V114.167H512.496V129.484H461.684Z",fill:"currentColor"}),u.jsx("path",{d:"M547.852 184.032C540.214 184.032 533.565 182.554 527.904 179.599C522.244 176.553 517.841 172.343 514.696 166.969C511.641 161.595 510.113 155.414 510.113 148.428C510.113 141.352 511.641 135.171 514.696 129.887C517.841 124.513 522.199 120.348 527.769 117.392C533.34 114.346 539.81 112.824 547.178 112.824C554.276 112.824 560.431 114.257 565.642 117.123C570.854 119.989 574.897 123.975 577.773 129.081C580.648 134.186 582.086 140.187 582.086 147.084C582.086 148.518 582.041 149.861 581.951 151.115C581.861 152.279 581.726 153.399 581.546 154.474H521.974V141.173H565.238L561.734 143.591C561.734 138.038 560.386 133.962 557.69 131.365C555.085 128.678 551.491 127.334 546.908 127.334C541.607 127.334 537.474 129.125 534.508 132.708C531.633 136.291 530.196 141.665 530.196 148.831C530.196 155.818 531.633 161.013 534.508 164.416C537.474 167.82 541.876 169.522 547.717 169.522C550.952 169.522 553.737 168.984 556.073 167.91C558.409 166.835 560.161 165.088 561.33 162.67H580.333C578.087 169.298 574.223 174.538 568.742 178.389C563.351 182.151 556.388 184.032 547.852 184.032Z",fill:"currentColor"}),u.jsx("path",{d:"M586.158 182.689V114.167H605.971V130.29H606.375V182.689H586.158ZM606.375 146.95L604.623 130.693C606.24 124.871 608.891 120.437 612.575 117.392C616.259 114.346 620.842 112.824 626.323 112.824C628.03 112.824 629.288 113.003 630.096 113.361V132.171C629.647 131.992 629.018 131.902 628.21 131.902C627.401 131.813 626.412 131.768 625.244 131.768C618.775 131.768 614.013 132.932 610.958 135.261C607.903 137.5 606.375 141.397 606.375 146.95Z",fill:"currentColor"})]})}function ap(){return u.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:8,color:"var(--ink)"},"aria-label":"Patter",children:[u.jsx(ip,{height:26}),u.jsx(up,{height:24})]})}function cp({liveCount:e,todayCount:t,phoneNumber:n,sdkVersion:r}){return u.jsxs("header",{className:"top",children:[u.jsxs("div",{className:"brand",children:[u.jsx(ap,{}),u.jsxs("span",{className:"tag",children:["dashboard · v",r]})]}),u.jsxs("div",{className:"top-r",children:[u.jsxs("span",{className:"live-chip",children:[u.jsx("span",{className:"pulse"+(e>0?" active":"")}),e," live · ",t," today"]}),n!=="—"&&u.jsx("span",{className:"num-chip",children:n})]})]})}function fp(e){return u.jsxs("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("circle",{cx:"11",cy:"11",r:"7"}),u.jsx("path",{d:"m21 21-4.3-4.3"})]})}function Cc(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.4",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M7 13l5 5 5-5"}),u.jsx("path",{d:"M12 4v14"})]})}function dp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.4",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M17 11l-5-5-5 5"}),u.jsx("path",{d:"M12 20V6"})]})}function pp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("rect",{x:"9",y:"2",width:"6",height:"12",rx:"3"}),u.jsx("path",{d:"M19 10a7 7 0 0 1-14 0"}),u.jsx("path",{d:"M12 19v3"})]})}function mp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("polyline",{points:"15 17 20 12 15 7"}),u.jsx("path",{d:"M4 18v-2a4 4 0 0 1 4-4h12"})]})}function hp(e){return u.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"currentColor",...e,children:u.jsx("circle",{cx:"12",cy:"12",r:"6"})})}function vp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67"}),u.jsx("path",{d:"M22 2 2 22"})]})}const yp=["1h","24h","7d","All"];function gp(){const e=document.createElement("a");e.href="/api/dashboard/export/calls?format=csv",e.download="patter_calls.csv",e.rel="noopener",document.body.appendChild(e),e.click(),document.body.removeChild(e)}function wp({range:e,setRange:t}){return u.jsxs("div",{className:"ph",children:[u.jsxs("div",{children:[u.jsx("h1",{children:"Calls"}),u.jsxs("p",{className:"sub",children:["Real-time view of every call routed through this Patter instance."," ",u.jsx("span",{className:"kbd",children:"⇧K"})," to focus search."]})]}),u.jsxs("div",{className:"filters",children:[u.jsx("div",{className:"seg",children:yp.map(n=>u.jsx("button",{type:"button",className:e===n?"on":"",onClick:()=>t(n),children:n},n))}),u.jsxs("button",{className:"btn",type:"button",onClick:gp,children:[u.jsx(Cc,{})," Export CSV"]})]})]})}function ml(e){const t=Math.floor(e/60),n=Math.floor(e%60);return`${String(t).padStart(2,"0")}:${String(n).padStart(2,"0")}`}function ze(e){if(e==null||!Number.isFinite(e))return"$0.00";const t=Math.abs(e);return t===0?"$0.00":t>=.01?`$${e.toFixed(2)}`:t>=.001?`$${e.toFixed(3)}`:t>=1e-4?`$${e.toFixed(4)}`:`$${e.toFixed(5)}`}const _c=60*60*1e3,xp=24*_c;function Mr(e){return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function kp(e){return new Date(e).toLocaleDateString([],{weekday:"short",month:"short",day:"numeric"})}function su(e){return new Date(e).toLocaleString([],{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function Nc(e){const t=e.toMs-e.fromMs;return t>=xp-Sp?kp(e.fromMs):t>=_c?`${Mr(e.fromMs)} → ${Mr(e.toMs)}`:t>=60*1e3?`${Mr(e.fromMs)} → ${Mr(e.toMs)}`:`${su(e.fromMs)} → ${su(e.toMs)}`}const Sp=5e3;function jc(e){return e.cost.total??(e.cost.telco??0)+(e.cost.llm??0)+(e.cost.sttTts??0)}function Cp(e){return e.calls.length===0?void 0:[...e.calls].sort((n,r)=>(r.startedAtMs??0)-(n.startedAtMs??0))[0]?.id}function _p(e,t){const n=e.calls,r=n.length;if(t==="spend"){const l=n.reduce((o,s)=>o+jc(s),0);return{label:"TOTAL COST",value:ze(l)}}if(t==="latency"){const l=n.filter(s=>typeof s.latencyP95=="number");return{label:"AVG LATENCY",value:`${l.length>0?Math.round(l.reduce((s,i)=>s+(i.latencyP95??0),0)/l.length):0} ms`}}return{label:r===1?"CALL":"CALLS",value:`${r}`}}function Np({bucket:e,kind:t}){const n=Nc(e),r=e.calls.length;if(r===0)return u.jsxs("div",{className:"spark-tooltip",children:[u.jsx("div",{className:"spark-tooltip-range",children:n}),u.jsx("div",{className:"spark-tooltip-empty",children:"no calls"})]});const l=_p(e,t),o=e.calls.slice(0,4);return u.jsxs("div",{className:"spark-tooltip",children:[u.jsx("div",{className:"spark-tooltip-range",children:n}),u.jsxs("div",{className:"spark-tooltip-headline",children:[u.jsx("span",{className:"spark-tooltip-headline-l",children:l.label}),u.jsx("span",{className:"spark-tooltip-headline-v",children:l.value})]}),u.jsx("ul",{className:"spark-tooltip-list",children:o.map(s=>{const i=s.direction==="inbound"?s.from:s.to;return u.jsxs("li",{children:[u.jsx("span",{className:"num",children:i}),u.jsx("span",{className:"status",children:s.status}),u.jsx("span",{className:"cost",children:ze(jc(s))})]},s.id)})}),r>o.length&&u.jsxs("div",{className:"spark-tooltip-more",children:["+",r-o.length," more"]})]})}function jp({bucket:e,height:t,interactive:n,kind:r,onSelect:l}){const[o,s]=L.useState(!1),i=!!e&&e.calls.length>0;return!n||!e?u.jsx("span",{className:"spark-bar-static",style:{height:t+"%"}}):u.jsxs("div",{className:"spark-bar-wrap",onMouseEnter:()=>s(!0),onMouseLeave:()=>s(!1),children:[u.jsx("button",{type:"button",className:"spark-bar"+(i?"":" empty"),style:{height:t+"%"},disabled:!i,onClick:()=>{if(!i)return;const a=Cp(e);a&&l&&l(a)},onFocus:()=>s(!0),onBlur:()=>s(!1),"aria-label":`${e.calls.length} calls in ${Nc(e)}`}),o&&u.jsx(Np,{bucket:e,kind:r})]})}function Lr({label:e,value:t,unit:n,delta:r,deltaTone:l,spark:o,buckets:s,onSelectCall:i,kind:a="count",peach:c,footer:m,badge:v}){const h=!!s&&!!i;return u.jsxs("div",{className:"metric"+(c?" peach":""),children:[u.jsxs("div",{className:"lbl",children:[u.jsx("span",{children:e}),v&&u.jsx("span",{className:"badge-now",children:"LIVE"})]}),u.jsxs("div",{className:"val",children:[t,n&&u.jsxs("span",{className:"unit",children:[" ",n]})]}),r&&u.jsx("div",{className:"delta "+(l||""),children:r}),m&&u.jsx("div",{className:"delta",children:m}),u.jsx("div",{className:"spark",children:o.map((S,x)=>u.jsx(jp,{bucket:s?.[x],height:S,interactive:h,kind:a,onSelect:i},x))})]})}function Ep({call:e,isSelected:t,onSelect:n,isNew:r}){const l=e.status==="live"&&e.durationStart?ml((Date.now()-e.durationStart)/1e3):ml(e.duration||0),o=e.latencyP95?Math.min(100,e.latencyP95/1e3*100):0,s=(e.latencyP95??0)>600,i=e.cost.total??(e.cost.telco??0)+(e.cost.llm??0)+(e.cost.sttTts??0),a=e.status.replace("-","");return u.jsxs("tr",{className:(t?"selected ":"")+(r?"new-row":""),onClick:n,children:[u.jsx("td",{children:u.jsx("span",{className:"pill "+a,children:e.status})}),u.jsxs("td",{children:[u.jsx("span",{className:"dir in",style:{marginRight:8,color:e.direction==="inbound"?"#3b6f3b":"#4a4a4a"},children:e.direction==="inbound"?u.jsx(Cc,{}):u.jsx(dp,{})}),u.jsxs("span",{className:"num-cell",children:[e.from," → ",e.to]})]}),u.jsx("td",{children:u.jsxs("span",{className:"car-tw",children:[u.jsx("span",{className:"car-dot "+(e.carrier==="twilio"?"tw":"tx")}),e.carrier==="twilio"?"Twilio":"Telnyx"]})}),u.jsx("td",{className:"num-cell",children:e.status==="no-answer"?"—":l}),u.jsx("td",{children:e.latencyP95?u.jsxs(u.Fragment,{children:[u.jsx("span",{className:"lat-bar"+(s?" warn":""),children:u.jsx("i",{style:{width:o+"%"}})}),u.jsxs("span",{className:"num-cell",children:[e.latencyP95," ms"]})]}):"—"}),u.jsx("td",{className:"num-cell",children:ze(i)})]})}function Mp({calls:e,selectedId:t,onSelect:n,newId:r,search:l,setSearch:o}){const s=L.useMemo(()=>{if(!l.trim())return e;const i=l.toLowerCase();return e.filter(a=>a.from.toLowerCase().includes(i)||a.to.toLowerCase().includes(i)||a.status.includes(i)||a.carrier.includes(i)||a.id.includes(i))},[e,l]);return u.jsxs("div",{className:"panel",children:[u.jsxs("div",{className:"panel-h",children:[u.jsxs("h3",{children:["Recent calls"," ",u.jsxs("span",{style:{fontFamily:"var(--font-mono)",fontSize:11,color:"#aaa",fontWeight:500,marginLeft:4},children:["(",s.length,")"]})]}),u.jsxs("div",{className:"search",children:[u.jsx(fp,{}),u.jsx("input",{placeholder:"Search number, status, carrier…",value:l,onChange:i=>o(i.target.value)})]}),u.jsxs("span",{className:"sse",children:[u.jsx("span",{className:"dot"}),"streaming · SSE"]})]}),u.jsx("div",{style:{maxHeight:540,overflow:"auto"},children:u.jsxs("table",{children:[u.jsx("thead",{children:u.jsxs("tr",{children:[u.jsx("th",{children:"Status"}),u.jsx("th",{children:"From → To"}),u.jsx("th",{children:"Carrier"}),u.jsx("th",{children:"Duration"}),u.jsx("th",{children:"p95 latency"}),u.jsx("th",{children:"Cost"})]})}),u.jsx("tbody",{children:s.length===0?u.jsx("tr",{children:u.jsxs("td",{colSpan:6,className:"empty",children:['No calls match "',l,'"']})}):s.map(i=>u.jsx(Ep,{call:i,isSelected:i.id===t,onSelect:()=>n(i.id),isNew:i.id===r},i.id))})]})})]})}function Lp({start:e}){const[,t]=L.useState(0);return L.useEffect(()=>{const n=setInterval(()=>t(r=>r+1),1e3);return()=>clearInterval(n)},[]),u.jsx(u.Fragment,{children:ml((Date.now()-e)/1e3)})}function Pp({call:e,transcript:t,onEnd:n,recording:r,setRecording:l,muted:o,setMuted:s}){const i=L.useRef(null);if(L.useEffect(()=>{i.current&&(i.current.scrollTop=i.current.scrollHeight)},[t]),!e)return u.jsxs("div",{className:"rr-card",children:[u.jsx("h3",{children:"No live call selected"}),u.jsx("div",{className:"meta",children:"Select a call from the table — or wait for the next ring."})]});const a=e.status==="live";return u.jsxs("div",{className:"rr-card",children:[u.jsxs("h3",{children:["Live call",u.jsx("span",{className:"pill "+(a?"live":"done"),children:e.status})]}),u.jsxs("div",{className:"meta",children:[u.jsx("strong",{children:e.direction==="inbound"?e.from:e.to}),u.jsx("span",{className:"sep",children:"·"}),e.agent]}),u.jsxs("div",{className:"duration-block",children:[u.jsx("span",{className:"l",children:"duration"}),u.jsxs("span",{className:"agent",children:[e.direction==="inbound"?"inbound":"outbound"," ·"," ",e.carrier==="twilio"?"Twilio":"Telnyx"]}),u.jsx("span",{className:"v",children:a&&e.durationStart?u.jsx(Lp,{start:e.durationStart}):ml(e.duration||0)})]}),u.jsx("div",{className:"transcript",ref:i,children:t.map((c,m)=>c.who==="tool"?u.jsxs("div",{className:"turn tool",children:[u.jsx("div",{className:"av",children:"⚙"}),u.jsxs("div",{className:"body",children:[u.jsxs("div",{className:"who",children:["tool · ",c.txt]}),c.args&&u.jsx("div",{className:"tool-call",children:Object.entries(c.args).map(([v,h])=>u.jsxs("span",{children:[u.jsxs("span",{className:"k",children:[v,":"]}),' "',String(h),'"'," "]},v))})]})]},m):u.jsxs("div",{className:"turn "+c.who,children:[u.jsx("div",{className:"av",children:c.who==="user"?"U":"P"}),u.jsxs("div",{className:"body",children:[u.jsxs("div",{className:"who",children:[c.who==="user"?"caller":"agent",c.typing&&" · typing"]}),u.jsx("div",{className:"txt",children:c.typing?u.jsxs("span",{className:"typing",children:[u.jsx("span",{}),u.jsx("span",{}),u.jsx("span",{})]}):c.txt}),c.lat&&!c.typing&&u.jsxs("div",{className:"lat",children:[c.lat.stt&&`stt ${c.lat.stt} ms`,c.lat.total&&`total ${c.lat.total} ms · llm ${c.lat.llm} · tts ${c.lat.tts}`]})]})]},m))}),a&&u.jsxs("div",{className:"controls",children:[u.jsxs("button",{type:"button",className:"ctrl"+(o?" active":""),onClick:()=>s(!o),children:[u.jsx(pp,{})," ",o?"unmute":"mute"]}),u.jsxs("button",{type:"button",className:"ctrl",children:[u.jsx(mp,{})," transfer"]}),u.jsxs("button",{type:"button",className:"ctrl"+(r?" active":""),onClick:()=>l(!r),children:[u.jsx(hp,{})," ",r?"stop rec":"record"]}),u.jsxs("button",{type:"button",className:"ctrl danger",onClick:n,children:[u.jsx(vp,{})," end"]})]})]})}const Tp=e=>!!e&&typeof e.latencyP95=="number",zp=e=>!!e&&(typeof e.cost.telco=="number"||typeof e.cost.llm=="number"||typeof e.cost.sttTts=="number"||typeof e.cost.total=="number");function Rp({call:e}){const[t,n]=L.useState("latency"),r=Tp(e),l=zp(e);if(!e||!r&&!l)return null;const o=t==="latency"&&!r?"cost":t==="cost"&&!l?"latency":t;return u.jsxs("div",{className:"rr-card metrics-panel",children:[u.jsx("div",{className:"metrics-panel-h",children:u.jsxs("div",{className:"seg",role:"tablist",children:[u.jsx("button",{type:"button",role:"tab","aria-selected":o==="latency",disabled:!r,className:o==="latency"?"on":"",onClick:()=>n("latency"),children:"Latency"}),u.jsx("button",{type:"button",role:"tab","aria-selected":o==="cost",disabled:!l,className:o==="cost"?"on":"",onClick:()=>n("cost"),children:"Cost"})]})}),o==="latency"&&r&&u.jsx(Dp,{call:e}),o==="cost"&&l&&u.jsx(Ip,{call:e})]})}function Dp({call:e}){const t=e.latencyP50??0,n=e.latencyP95??0;if(e.mode==="realtime"){const m=(e.turnCount??0)>=2;return u.jsxs(u.Fragment,{children:[u.jsxs("div",{className:"lat-grid",children:[u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"end-to-end p50"}),u.jsxs("div",{className:"v",children:[m&&t||"—",m&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox"+(m&&n>600?" warn":""),children:[u.jsx("div",{className:"l",children:"end-to-end p95"}),u.jsxs("div",{className:"v",children:[m&&n||"—",m&&u.jsx("span",{className:"u",children:"ms"})]})]})]}),u.jsx("div",{className:"waterfall",children:u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"e2e"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar llm",style:{left:0,width:Math.min(100,n/1e3*100)+"%"}})}),u.jsx("span",{className:"v",children:n})]})}),u.jsxs("div",{className:"wf-legend",children:[u.jsxs("span",{children:[u.jsx("i",{style:{background:"#DF9367"}}),"end-to-end"]}),u.jsx("span",{style:{marginLeft:"auto"},children:e.agent??"realtime"})]})]})}const l=e.sttAvg||0,o=e.llmAvg||0,s=e.ttsAvg||0,i=l+o+s,a=Math.max(i,800),c=(e.turnCount??0)>=2;return u.jsxs(u.Fragment,{children:[u.jsxs("div",{className:"lat-grid",children:[u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"p50"}),u.jsxs("div",{className:"v",children:[c?e.latencyP50??"—":"—",c&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox"+(c&&n>600?" warn":""),children:[u.jsx("div",{className:"l",children:"p95"}),u.jsxs("div",{className:"v",children:[c?n:"—",c&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"stt avg"}),u.jsxs("div",{className:"v",children:[e.sttAvg??"—",u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"tts avg"}),u.jsxs("div",{className:"v",children:[e.ttsAvg??"—",u.jsx("span",{className:"u",children:"ms"})]})]})]}),u.jsxs("div",{className:"waterfall",children:[u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"stt"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar stt",style:{left:0,width:l/a*100+"%"}})}),u.jsx("span",{className:"v",children:l})]}),u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"llm"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar llm",style:{left:l/a*100+"%",width:o/a*100+"%"}})}),u.jsx("span",{className:"v",children:o})]}),u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"tts"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar tts",style:{left:(l+o)/a*100+"%",width:s/a*100+"%"}})}),u.jsx("span",{className:"v",children:s})]})]}),u.jsxs("div",{className:"wf-legend",children:[u.jsxs("span",{children:[u.jsx("i",{style:{background:"#1a1a1a"}}),"stt"]}),u.jsxs("span",{children:[u.jsx("i",{style:{background:"#DF9367"}}),"llm"]}),u.jsxs("span",{children:[u.jsx("i",{style:{background:"#278EFF",opacity:.8}}),"tts"]}),u.jsxs("span",{style:{marginLeft:"auto"},children:["total ",i," ms"]})]})]})}function lo(e){if(e.length===0)return e;const t=e.replace(/(?:_(?:ws|rest|stt|tts|llm))+$/i,"");return t.charAt(0).toUpperCase()+t.slice(1)}function Ip({call:e}){const t=e.cost,n=t.telco??0,r=t.llm??0,l=t.stt??0,o=t.tts??0,s=t.sttTts??0,i=l===0&&o===0?s:0,a=t.cached??0,c=n+r+l+o+i,m=t.total??c-a,v=k=>c>0?k/c*100:0,h=e.sttProvider?`${lo(e.sttProvider)} STT${e.sttModel?` · ${e.sttModel}`:""}`:"STT",S=e.ttsProvider?`${lo(e.ttsProvider)} TTS${e.ttsModel?` · ${e.ttsModel}`:""}`:"TTS",x=e.llmModel?`${e.model?lo(e.model)+" · ":""}${e.llmModel}`:e.model||"LLM";return u.jsxs(u.Fragment,{children:[c>0&&u.jsxs("div",{className:"cost-bar",children:[u.jsx("i",{style:{background:"#cc0000",width:v(n)+"%"}}),u.jsx("i",{style:{background:"#DF9367",width:v(r)+"%"}}),u.jsx("i",{style:{background:"#1a1a1a",width:v(l+i)+"%"}}),u.jsx("i",{style:{background:"#6c6c6c",width:v(o)+"%"}})]}),n>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#cc0000"}}),e.carrier==="twilio"?"Twilio":"Telnyx"]}),u.jsx("span",{className:"v",children:ze(n)})]}),r>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#DF9367"}}),x]}),u.jsx("span",{className:"v",children:ze(r)}),a>0&&u.jsxs("span",{className:"saved",children:["−",ze(a)," cached"]})]}),l>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#1a1a1a"}}),h]}),u.jsx("span",{className:"v",children:ze(l)})]}),o>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#6c6c6c"}}),S]}),u.jsx("span",{className:"v",children:ze(o)})]}),i>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#1a1a1a"}}),"STT / TTS (legacy)"]}),u.jsx("span",{className:"v",children:ze(i)})]}),u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:["Total"," ",e.status==="live"&&u.jsx("span",{style:{fontFamily:"var(--font-mono)",fontSize:10,color:"#aaa",marginLeft:4},children:"(running)"})]}),u.jsx("span",{className:"v",children:ze(m)})]})]})}const $t=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),bt=e=>typeof e=="string"?e:"",De=e=>typeof e=="number"&&Number.isFinite(e)?e:0,ie=e=>typeof e=="number"&&Number.isFinite(e)?e:void 0,Be=e=>typeof e=="string"&&e.length>0?e:void 0;function Pr(e){if($t(e))return{stt_ms:ie(e.stt_ms),llm_ms:ie(e.llm_ms),tts_ms:ie(e.tts_ms),total_ms:ie(e.total_ms),agent_response_ms:ie(e.agent_response_ms),endpoint_ms:ie(e.endpoint_ms),user_speech_duration_ms:ie(e.user_speech_duration_ms)}}function Op(e){if($t(e))return{stt:ie(e.stt),tts:ie(e.tts),llm:ie(e.llm),telephony:ie(e.telephony),total:ie(e.total),llm_cached_savings:ie(e.llm_cached_savings)}}function Fp(e){if(!$t(e))return null;const t=e.turns;return{duration_seconds:ie(e.duration_seconds),provider_mode:Be(e.provider_mode),telephony_provider:Be(e.telephony_provider),stt_provider:Be(e.stt_provider),tts_provider:Be(e.tts_provider),llm_provider:Be(e.llm_provider),stt_model:Be(e.stt_model),tts_model:Be(e.tts_model),llm_model:Be(e.llm_model),cost:Op(e.cost),latency_avg:Pr(e.latency_avg),latency_p50:Pr(e.latency_p50),latency_p95:Pr(e.latency_p95),latency_p99:Pr(e.latency_p99),turns:Array.isArray(t)?t:void 0}}function Ap(e){if(!Array.isArray(e))return;const t=[];for(const n of e)$t(n)&&t.push({role:bt(n.role),text:bt(n.text),timestamp:De(n.timestamp)});return t}function Ec(e){if(!$t(e))return null;const t=bt(e.call_id);if(t.length===0)return null;const n=e.turns;return{call_id:t,caller:bt(e.caller),callee:bt(e.callee),direction:bt(e.direction),started_at:De(e.started_at),ended_at:ie(e.ended_at),status:Be(e.status),transcript:Ap(e.transcript),turns:Array.isArray(n)?n:void 0,metrics:Fp(e.metrics)}}function Mc(e){if(!Array.isArray(e))return[];const t=[];for(const n of e){const r=Ec(n);r&&t.push(r)}return t}function $p(e){return $t(e)?{stt:De(e.stt),tts:De(e.tts),llm:De(e.llm),telephony:De(e.telephony)}:{stt:0,tts:0,llm:0,telephony:0}}function Vp(e){return $t(e)?{total_calls:De(e.total_calls),total_cost:De(e.total_cost),avg_duration:De(e.avg_duration),avg_latency_ms:De(e.avg_latency_ms),cost_breakdown:$p(e.cost_breakdown),active_calls:De(e.active_calls)}:{total_calls:0,total_cost:0,avg_duration:0,avg_latency_ms:0,cost_breakdown:{stt:0,tts:0,llm:0,telephony:0},active_calls:0}}async function Zs(e){const t=await fetch(e,{headers:{Accept:"application/json"}});if(!t.ok)throw new Error(`Request to ${e} failed with status ${t.status}`);return t.json()}async function Up(e=50,t=0){const n=`/api/dashboard/calls?limit=${encodeURIComponent(e)}&offset=${encodeURIComponent(t)}`,r=await Zs(n);return Mc(r)}async function Hp(){const e=await Zs("/api/dashboard/active");return Mc(e)}async function Bp(){const e=await Zs("/api/dashboard/aggregates");return Vp(e)}async function Wp(e){const t=`/api/dashboard/calls/${encodeURIComponent(e)}`,n=await fetch(t,{headers:{Accept:"application/json"}});if(n.status===404)return null;if(!n.ok)throw new Error(`Request to ${t} failed with status ${n.status}`);const r=await n.json();return Ec(r)}const Qp=new Set(["in-progress","initiated"]);function Kp(e){if(!e)return"ended";switch(e){case"in-progress":case"initiated":return"live";case"completed":return"ended";case"no-answer":return"no-answer";case"busy":case"failed":case"canceled":case"webhook_error":return"fail";default:return"ended"}}function Yp(e){return e==="outbound"?"outbound":"inbound"}function Xp(e){return typeof e=="string"&&e.toLowerCase().includes("telnyx")?"telnyx":"twilio"}function Gp(e){if(typeof e!="string")return"unknown";const t=e.toLowerCase();return t.includes("realtime")?"realtime":t.includes("convai")?"convai":t.includes("pipeline")?"pipeline":"unknown"}function iu(e){return e.length===0?"—":e}function Zp(e){const t=e.metrics?.provider_mode;if(!t)return;const n=e.metrics?.llm_provider;return t.startsWith("pipeline")&&n?`${t} · ${n}`:t}function Jp(e){const t=e.metrics?.cost;if(!t)return{};const n={};return typeof t.telephony=="number"&&(n.telco=t.telephony),typeof t.llm=="number"&&(n.llm=t.llm),typeof t.stt=="number"&&(n.stt=t.stt),typeof t.tts=="number"&&(n.tts=t.tts),typeof t.llm_cached_savings=="number"&&(n.cached=t.llm_cached_savings),(n.stt!==void 0||n.tts!==void 0)&&(n.sttTts=(n.stt??0)+(n.tts??0)),n.telco===void 0&&n.llm===void 0&&n.sttTts===void 0&&typeof t.total=="number"&&(n.total=t.total),n}function qp(e,t){if(t)return;const n=e.metrics?.duration_seconds;return typeof n=="number"?n:typeof e.ended_at=="number"&&typeof e.started_at=="number"?Math.max(0,e.ended_at-e.started_at):0}function bp(e){if(typeof e.ended_at=="number")return Math.round(Date.now()/1e3-e.ended_at)}function uu(e){const t=Kp(e.status),n=t==="live"||e.status!==void 0&&Qp.has(e.status),r=e.metrics?.latency_avg,l=e.metrics?.latency_p50,o=e.metrics?.latency_p95,s=(Array.isArray(e.metrics?.turns)?e.metrics?.turns?.length:void 0)??(Array.isArray(e.transcript)?e.transcript.length:void 0);return{id:e.call_id,status:t,direction:Yp(e.direction),from:iu(e.caller),to:iu(e.callee),carrier:Xp(e.metrics?.telephony_provider),startedAtMs:typeof e.started_at=="number"?e.started_at*1e3:void 0,durationStart:n?e.started_at*1e3:void 0,duration:qp(e,n),latencyP95:o?.agent_response_ms??o?.total_ms??r?.total_ms,latencyP50:l?.agent_response_ms??l?.total_ms??r?.total_ms,sttAvg:r?.stt_ms,ttsAvg:r?.tts_ms,llmAvg:r?.llm_ms,turnCount:s,agentResponseP50:l?.agent_response_ms,agentResponseP95:o?.agent_response_ms,cost:Jp(e),agent:Zp(e),model:e.metrics?.llm_provider,mode:Gp(e.metrics?.provider_mode),sttProvider:e.metrics?.stt_provider,ttsProvider:e.metrics?.tts_provider,sttModel:e.metrics?.stt_model,ttsModel:e.metrics?.tts_model,llmModel:e.metrics?.llm_model,transcriptKey:e.call_id,endedAgo:bp(e)}}function em(e){const t=e.transcript;if(t&&t.length>0){const l=[];for(const o of t){const s=o.text;switch(o.role){case"user":l.push({who:"user",txt:s});break;case"assistant":l.push({who:"bot",txt:s});break;case"tool":l.push({who:"tool",txt:s});break;default:l.push({who:"bot",txt:s});break}}return l}const n=e.turns;if(!n||n.length===0)return[];const r=[];for(const l of n){if(typeof l!="object"||l===null)continue;const o=l,s=typeof o.user_text=="string"?o.user_text:"",i=typeof o.agent_text=="string"?o.agent_text:"";s.length>0&&r.push({who:"user",txt:s}),i.length>0&&i!=="[interrupted]"&&r.push({who:"bot",txt:i})}return r}const Lc=60*1e3,Pc=60*Lc,oo=24*Pc;function tm(e,t=Date.now()){switch(e){case"1h":{const n=5*Lc,r=Math.ceil(t/n)*n,l=r-12*n;return{count:12,bucketSizeMs:n,window:{fromMs:l,toMs:r}}}case"24h":{const n=Pc,r=Math.ceil(t/n)*n,l=r-24*n;return{count:24,bucketSizeMs:n,window:{fromMs:l,toMs:r}}}case"7d":{const n=new Date(t);n.setHours(0,0,0,0);const r=n.getTime()+oo,l=r-7*oo;return{count:7,bucketSizeMs:oo,window:{fromMs:l,toMs:r}}}case"All":default:return{count:9,bucketSizeMs:0,window:{fromMs:0,toMs:t}}}}function nm(e,t){const{fromMs:n,toMs:r}=t;return e.filter(l=>{const o=ts(l);return typeof o!="number"?!1:o>=n&&o<=r})}function ts(e){if(typeof e.startedAtMs=="number")return e.startedAtMs;if(typeof e.durationStart=="number")return e.durationStart;if(typeof e.endedAgo=="number")return Date.now()-e.endedAgo*1e3}function rm(e){const t=e.cost,n=(t.telco??0)+(t.llm??0)+(t.sttTts??0);return n>0?n:t.total??0}function lm(e){const t=e.reduce((n,r)=>r>n?r:n,0);return t<=0?e.map(()=>0):e.map(n=>Math.round(n/t*100))}function Tr(e,t,n=9,r){const l=typeof n=="object",o=l?n.count:n,s=Math.max(1,Math.floor(o)),i=l?n.window:r,a=l?n.bucketSizeMs:0;let c,m;if(i)c=i.fromMs,m=i.toMs;else{const d=[];for(const f of e){const p=ts(f);typeof p=="number"&&d.push(p)}if(d.length===0){const f=Date.now();return{heights:new Array(s).fill(0),buckets:new Array(s).fill(null).map(()=>[]),window:{fromMs:f,toMs:f},bucketSizeMs:0}}c=Math.min(...d),m=Math.max(...d)}const v=Math.max(1,m-c),h=a>0?a:v/s,S=new Array(s).fill(null).map(()=>[]),x=new Array(s).fill(0),k=new Array(s).fill(0);for(const d of e){const f=ts(d);if(typeof f!="number"||fm)continue;let p=Math.floor((f-c)/h);p>=s&&(p=s-1),p<0&&(p=0),S[p].push(d),t==="totalCalls"?x[p]+=1:t==="latency"?typeof d.latencyP95=="number"&&(x[p]+=d.latencyP95,k[p]+=1):x[p]+=rm(d)}const R=t==="latency"?x.map((d,f)=>k[f]>0?d/k[f]:0):x;return{heights:lm(R),buckets:S,window:{fromMs:c,toMs:m},bucketSizeMs:h}}const om=1e3,sm=3e4,im=5,um=5e3,am=["call_start","call_initiated","call_status","call_end"];function cm(e,t){const n=new Set,r=[];for(const l of e)n.has(l.call_id)||(n.add(l.call_id),r.push(uu(l)));for(const l of t)n.has(l.call_id)||(n.add(l.call_id),r.push(uu(l)));return r}function fm(e,t){const n=new Map(e.map(r=>[r.id,r]));return t.map(r=>{const l=n.get(r.id);return l?{...l,...r,latencyP95:r.latencyP95??l.latencyP95,latencyP50:r.latencyP50??l.latencyP50,sttAvg:r.sttAvg??l.sttAvg,ttsAvg:r.ttsAvg??l.ttsAvg,llmAvg:r.llmAvg??l.llmAvg,turnCount:r.turnCount??l.turnCount,agentResponseP50:r.agentResponseP50??l.agentResponseP50,agentResponseP95:r.agentResponseP95??l.agentResponseP95,cost:{...l.cost,...r.cost}}:r})}function au(e){return e instanceof Error?e.message:"Unknown error"}function dm(){const[e,t]=L.useState([]),[n,r]=L.useState(null),[l,o]=L.useState(!1),[s,i]=L.useState(null),a=L.useRef(!0),c=L.useRef(null),m=L.useRef(null),v=L.useRef(null),h=L.useRef(0),S=L.useCallback(()=>{m.current!==null&&(clearTimeout(m.current),m.current=null)},[]),x=L.useCallback(()=>{v.current!==null&&(clearInterval(v.current),v.current=null)},[]),k=L.useCallback(()=>{c.current!==null&&(c.current.close(),c.current=null)},[]),R=L.useCallback(async()=>{try{const[C,N,E]=await Promise.all([Hp(),Up(50,0),Bp()]);if(!a.current)return;t(F=>fm(F,cm(C,N))),r(E),i(null)}catch(C){if(!a.current)return;i(au(C))}},[]),d=L.useCallback(()=>{v.current===null&&(v.current=setInterval(()=>{R()},um))},[R]),f=L.useRef(()=>{}),p=L.useCallback(()=>{if(S(),h.current>=im){d();return}const C=h.current,N=Math.min(sm,om*Math.pow(2,C));h.current=C+1,m.current=setTimeout(()=>{m.current=null,a.current&&f.current()},N)},[S,d]),y=L.useCallback(()=>{R()},[R]),_=L.useCallback(()=>{k();let C;try{C=new EventSource("/api/dashboard/events")}catch(N){i(au(N)),p();return}c.current=C,C.onopen=()=>{a.current&&(h.current=0,x(),o(!0))},C.onerror=()=>{a.current&&(o(!1),k(),p())};for(const N of am)C.addEventListener(N,y);C.addEventListener("turn_complete",y)},[k,x,y,p]);return L.useEffect(()=>{f.current=_},[_]),L.useEffect(()=>(a.current=!0,R(),_(),()=>{a.current=!1,S(),x(),k()}),[]),{calls:e,aggregates:n,isStreaming:l,error:s,refresh:R}}const pm=2e3;function mm(e,t){const[n,r]=L.useState([]),l=L.useRef(!0);return L.useEffect(()=>(l.current=!0,()=>{l.current=!1}),[]),L.useEffect(()=>{if(!e){r([]);return}let o=!1,s=null,i=null;const a=async()=>{try{const m=await Wp(e);if(o||!l.current)return;if(m===null){r([]);return}r(em(m))}catch{}};a();const c=m=>{const v=m;try{return JSON.parse(v.data)?.call_id===e}catch{return!1}};try{i=new EventSource("/api/dashboard/events"),i.addEventListener("turn_complete",m=>{c(m)&&a()}),i.addEventListener("call_end",m=>{c(m)&&a()})}catch{i=null}return t&&(s=setInterval(()=>{a()},pm)),()=>{o=!0,s!==null&&clearInterval(s),i!==null&&i.close()}},[e,t]),n}const cu="0.6.0",so={"1h":"1h","24h":"24h","7d":"7d",All:"all-time"};function hm(e){const t=e.filter(r=>typeof r.latencyP95=="number");if(t.length===0)return 0;const n=t.reduce((r,l)=>r+(l.latencyP95??0),0);return Math.round(n/t.length)}function vm(e){return e.reduce((t,n)=>{if(typeof n.cost.total=="number")return t+n.cost.total;const r=(n.cost.telco??0)+(n.cost.llm??0)+(n.cost.sttTts??0);return t+r},0)}function ym(e){const n=e.find(l=>l.status==="live")??e[0];if(!n)return"";const r=n.direction==="inbound"?n.to:n.from;return r&&r!=="—"?r:""}function gm(){const{calls:e,aggregates:t,isStreaming:n,error:r,refresh:l}=dm(),[o,s]=L.useState(null),[i,a]=L.useState(""),[c,m]=L.useState("24h"),[v,h]=L.useState(!0),[S,x]=L.useState(!1),k=L.useMemo(()=>tm(c),[c]),R=k.window,d=L.useMemo(()=>{if(c==="All")return e;const w=new Set(nm(e,R).map(M=>M.id));return e.filter(M=>M.status==="live"||w.has(M.id))},[e,c,R]);L.useEffect(()=>{if(o!==null)return;const w=d.find(M=>M.status==="live")??d[0];w&&s(w.id)},[d,o]),L.useEffect(()=>{o!==null&&(d.some(w=>w.id===o)||s(null))},[d,o]),L.useEffect(()=>{const w=M=>{if(!(M.shiftKey&&M.key.toLowerCase()==="k"||M.metaKey&&M.key.toLowerCase()==="k"))return;M.preventDefault(),document.querySelector(".panel-h .search input")?.focus()};return window.addEventListener("keydown",w),()=>window.removeEventListener("keydown",w)},[]);const f=L.useMemo(()=>d.find(w=>w.id===o)??null,[d,o]),p=f?.status==="live",y=mm(f?.id??null,p),_=L.useMemo(()=>e.filter(w=>w.status==="live").length,[e]),C=L.useMemo(()=>e.filter(w=>w.status==="live"&&w.direction==="inbound").length,[e]),N=_-C,E=d.length,F=hm(d)||t?.avg_latency_ms||0,T=vm(d)||t?.total_cost||0,ve=ym(e),et=L.useMemo(()=>Tr(d,"totalCalls",k),[d,k]),tt=L.useMemo(()=>Tr(d,"latency",k),[d,k]),yn=L.useMemo(()=>Tr(d,"spend",k),[d,k]),ur=L.useMemo(()=>{const w=e.filter(M=>M.status==="live");return Tr(w,"totalCalls",k)},[e,k]),nt=w=>w.heights.map((M,P)=>({height:M,calls:w.buckets[P],fromMs:w.window.fromMs+P*w.bucketSizeMs,toMs:w.window.fromMs+(P+1)*w.bucketSizeMs})),gn=()=>{f&&l().catch(()=>{})};return u.jsxs(u.Fragment,{children:[u.jsx(cp,{liveCount:_,todayCount:E,phoneNumber:ve,sdkVersion:cu}),u.jsxs("div",{className:"page",children:[u.jsx(wp,{range:c,setRange:w=>m(w)}),u.jsxs("div",{className:"metrics",children:[u.jsx(Lr,{label:`Calls · ${so[c]}`,value:E,spark:et.heights,buckets:nt(et),onSelectCall:s,kind:"count"}),u.jsx(Lr,{label:"Avg latency p95",value:F||0,unit:"ms",spark:tt.heights,buckets:nt(tt),onSelectCall:s,kind:"latency"}),u.jsx(Lr,{label:`Spend · ${so[c]}`,value:ze(T),spark:yn.heights,buckets:nt(yn),onSelectCall:s,kind:"spend"}),u.jsx(Lr,{label:"Active now",value:_,peach:!0,badge:!0,footer:`${C} inbound · ${N} outbound`,spark:ur.heights,buckets:nt(ur),onSelectCall:s,kind:"count"})]}),u.jsxs("div",{className:"split",children:[u.jsx(Mp,{calls:d,selectedId:o,onSelect:s,newId:null,search:i,setSearch:a}),u.jsxs("div",{className:"rr",children:[u.jsx(Pp,{call:f,transcript:y,onEnd:gn,recording:v,setRecording:h,muted:S,setMuted:x}),u.jsx(Rp,{call:f})]})]}),u.jsxs("div",{className:"statusbar",children:[u.jsxs("div",{className:"group",children:[u.jsx("span",{className:n?"green":"",children:n?"streaming · sse":r?`error · ${r}`:"idle"}),u.jsxs("span",{children:["SDK · ",cu]})]}),u.jsx("div",{className:"group",children:u.jsxs("span",{children:[_," live · ",E," ",so[c]]})})]})]})]})}const Tc=document.getElementById("root");if(!Tc)throw new Error("Patter dashboard: #root element missing");io.createRoot(Tc).render(u.jsx(Xc.StrictMode,{children:u.jsx(gm,{})})); +`+o.stack}return{value:e,source:t,stack:l,digest:null}}function eo(e,t,n){return{value:e,source:null,stack:n??null,digest:t??null}}function Uo(e,t){try{console.error(t.value)}catch(n){setTimeout(function(){throw n})}}var Id=typeof WeakMap=="function"?WeakMap:Map;function Ga(e,t,n){n=Xe(-1,n),n.tag=3,n.payload={element:null};var r=t.value;return n.callback=function(){cl||(cl=!0,Jo=r),Uo(e,t)},n}function Za(e,t,n){n=Xe(-1,n),n.tag=3;var r=e.type.getDerivedStateFromError;if(typeof r=="function"){var l=t.value;n.payload=function(){return r(l)},n.callback=function(){Uo(e,t)}}var o=e.stateNode;return o!==null&&typeof o.componentDidCatch=="function"&&(n.callback=function(){Uo(e,t),typeof r!="function"&&(ht===null?ht=new Set([this]):ht.add(this));var s=t.stack;this.componentDidCatch(t.value,{componentStack:s!==null?s:""})}),n}function Ui(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Id;var l=new Set;r.set(t,l)}else l=r.get(t),l===void 0&&(l=new Set,r.set(t,l));l.has(n)||(l.add(n),e=Gd.bind(null,e,t,n),t.then(e,e))}function Hi(e){do{var t;if((t=e.tag===13)&&(t=e.memoizedState,t=t!==null?t.dehydrated!==null:!0),t)return e;e=e.return}while(e!==null);return null}function Bi(e,t,n,r,l){return e.mode&1?(e.flags|=65536,e.lanes=l,e):(e===t?e.flags|=65536:(e.flags|=128,n.flags|=131072,n.flags&=-52805,n.tag===1&&(n.alternate===null?n.tag=17:(t=Xe(-1,1),t.tag=2,mt(n,t,1))),n.lanes|=1),e)}var Od=be.ReactCurrentOwner,de=!1;function se(e,t,n,r){t.child=e===null?Na(t,null,n,r):cn(t,e.child,n,r)}function Wi(e,t,n,r,l){n=n.render;var o=t.ref;return ln(t,l),r=Ds(e,t,n,r,o,l),n=Is(),e!==null&&!de?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~l,qe(e,t,l)):(V&&n&&Ss(t),t.flags|=1,se(e,t,r,l),t.child)}function Qi(e,t,n,r,l){if(e===null){var o=n.type;return typeof o=="function"&&!Qs(o)&&o.defaultProps===void 0&&n.compare===null&&n.defaultProps===void 0?(t.tag=15,t.type=o,Ja(e,t,o,r,l)):(e=Br(n.type,null,r,t,t.mode,l),e.ref=t.ref,e.return=t,t.child=e)}if(o=e.child,!(e.lanes&l)){var s=o.memoizedProps;if(n=n.compare,n=n!==null?n:Yn,n(s,r)&&e.ref===t.ref)return qe(e,t,l)}return t.flags|=1,e=yt(o,r),e.ref=t.ref,e.return=t,t.child=e}function Ja(e,t,n,r,l){if(e!==null){var o=e.memoizedProps;if(Yn(o,r)&&e.ref===t.ref)if(de=!1,t.pendingProps=r=o,(e.lanes&l)!==0)e.flags&131072&&(de=!0);else return t.lanes=e.lanes,qe(e,t,l)}return Ho(e,t,n,r,l)}function qa(e,t,n){var r=t.pendingProps,l=r.children,o=e!==null?e.memoizedState:null;if(r.mode==="hidden")if(!(t.mode&1))t.memoizedState={baseLanes:0,cachePool:null,transitions:null},O(qt,ye),ye|=n;else{if(!(n&1073741824))return e=o!==null?o.baseLanes|n:n,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e,cachePool:null,transitions:null},t.updateQueue=null,O(qt,ye),ye|=e,null;t.memoizedState={baseLanes:0,cachePool:null,transitions:null},r=o!==null?o.baseLanes:n,O(qt,ye),ye|=r}else o!==null?(r=o.baseLanes|n,t.memoizedState=null):r=n,O(qt,ye),ye|=r;return se(e,t,l,n),t.child}function ba(e,t){var n=t.ref;(e===null&&n!==null||e!==null&&e.ref!==n)&&(t.flags|=512,t.flags|=2097152)}function Ho(e,t,n,r,l){var o=me(n)?zt:oe.current;return o=un(t,o),ln(t,l),n=Ds(e,t,n,r,o,l),r=Is(),e!==null&&!de?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~l,qe(e,t,l)):(V&&r&&Ss(t),t.flags|=1,se(e,t,n,l),t.child)}function Ki(e,t,n,r,l){if(me(n)){var o=!0;el(t)}else o=!1;if(ln(t,l),t.stateNode===null)Vr(e,t),Xa(t,n,r),Vo(t,n,r,l),r=!0;else if(e===null){var s=t.stateNode,i=t.memoizedProps;s.props=i;var a=s.context,c=n.contextType;typeof c=="object"&&c!==null?c=Ee(c):(c=me(n)?zt:oe.current,c=un(t,c));var m=n.getDerivedStateFromProps,v=typeof m=="function"||typeof s.getSnapshotBeforeUpdate=="function";v||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(i!==r||a!==c)&&Vi(t,s,r,c),ot=!1;var h=t.memoizedState;s.state=h,ol(t,r,s,l),a=t.memoizedState,i!==r||h!==a||pe.current||ot?(typeof m=="function"&&($o(t,n,m,r),a=t.memoizedState),(i=ot||$i(t,n,i,r,h,a,c))?(v||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount()),typeof s.componentDidMount=="function"&&(t.flags|=4194308)):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=r,t.memoizedState=a),s.props=r,s.state=a,s.context=c,r=i):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),r=!1)}else{s=t.stateNode,Ea(e,t),i=t.memoizedProps,c=t.type===t.elementType?i:Pe(t.type,i),s.props=c,v=t.pendingProps,h=s.context,a=n.contextType,typeof a=="object"&&a!==null?a=Ee(a):(a=me(n)?zt:oe.current,a=un(t,a));var S=n.getDerivedStateFromProps;(m=typeof S=="function"||typeof s.getSnapshotBeforeUpdate=="function")||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(i!==v||h!==a)&&Vi(t,s,r,a),ot=!1,h=t.memoizedState,s.state=h,ol(t,r,s,l);var x=t.memoizedState;i!==v||h!==x||pe.current||ot?(typeof S=="function"&&($o(t,n,S,r),x=t.memoizedState),(c=ot||$i(t,n,c,r,h,x,a)||!1)?(m||typeof s.UNSAFE_componentWillUpdate!="function"&&typeof s.componentWillUpdate!="function"||(typeof s.componentWillUpdate=="function"&&s.componentWillUpdate(r,x,a),typeof s.UNSAFE_componentWillUpdate=="function"&&s.UNSAFE_componentWillUpdate(r,x,a)),typeof s.componentDidUpdate=="function"&&(t.flags|=4),typeof s.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof s.componentDidUpdate!="function"||i===e.memoizedProps&&h===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||i===e.memoizedProps&&h===e.memoizedState||(t.flags|=1024),t.memoizedProps=r,t.memoizedState=x),s.props=r,s.state=x,s.context=a,r=c):(typeof s.componentDidUpdate!="function"||i===e.memoizedProps&&h===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||i===e.memoizedProps&&h===e.memoizedState||(t.flags|=1024),r=!1)}return Bo(e,t,n,r,o,l)}function Bo(e,t,n,r,l,o){ba(e,t);var s=(t.flags&128)!==0;if(!r&&!s)return l&&Ti(t,n,!1),qe(e,t,o);r=t.stateNode,Od.current=t;var i=s&&typeof n.getDerivedStateFromError!="function"?null:r.render();return t.flags|=1,e!==null&&s?(t.child=cn(t,e.child,null,o),t.child=cn(t,null,i,o)):se(e,t,i,o),t.memoizedState=r.state,l&&Ti(t,n,!0),t.child}function ec(e){var t=e.stateNode;t.pendingContext?Pi(e,t.pendingContext,t.pendingContext!==t.context):t.context&&Pi(e,t.context,!1),Ps(e,t.containerInfo)}function Yi(e,t,n,r,l){return an(),_s(l),t.flags|=256,se(e,t,n,r),t.child}var Wo={dehydrated:null,treeContext:null,retryLane:0};function Qo(e){return{baseLanes:e,cachePool:null,transitions:null}}function tc(e,t,n){var r=t.pendingProps,l=U.current,o=!1,s=(t.flags&128)!==0,i;if((i=s)||(i=e!==null&&e.memoizedState===null?!1:(l&2)!==0),i?(o=!0,t.flags&=-129):(e===null||e.memoizedState!==null)&&(l|=1),O(U,l&1),e===null)return Fo(t),e=t.memoizedState,e!==null&&(e=e.dehydrated,e!==null)?(t.mode&1?e.data==="$!"?t.lanes=8:t.lanes=1073741824:t.lanes=1,null):(s=r.children,e=r.fallback,o?(r=t.mode,o=t.child,s={mode:"hidden",children:s},!(r&1)&&o!==null?(o.childLanes=0,o.pendingProps=s):o=jl(s,r,0,null),e=Tt(e,r,n,null),o.return=t,e.return=t,o.sibling=e,t.child=o,t.child.memoizedState=Qo(n),t.memoizedState=Wo,e):As(t,s));if(l=e.memoizedState,l!==null&&(i=l.dehydrated,i!==null))return Fd(e,t,s,r,i,l,n);if(o){o=r.fallback,s=t.mode,l=e.child,i=l.sibling;var a={mode:"hidden",children:r.children};return!(s&1)&&t.child!==l?(r=t.child,r.childLanes=0,r.pendingProps=a,t.deletions=null):(r=yt(l,a),r.subtreeFlags=l.subtreeFlags&14680064),i!==null?o=yt(i,o):(o=Tt(o,s,n,null),o.flags|=2),o.return=t,r.return=t,r.sibling=o,t.child=r,r=o,o=t.child,s=e.child.memoizedState,s=s===null?Qo(n):{baseLanes:s.baseLanes|n,cachePool:null,transitions:s.transitions},o.memoizedState=s,o.childLanes=e.childLanes&~n,t.memoizedState=Wo,r}return o=e.child,e=o.sibling,r=yt(o,{mode:"visible",children:r.children}),!(t.mode&1)&&(r.lanes=n),r.return=t,r.sibling=null,e!==null&&(n=t.deletions,n===null?(t.deletions=[e],t.flags|=16):n.push(e)),t.child=r,t.memoizedState=null,r}function As(e,t){return t=jl({mode:"visible",children:t},e.mode,0,null),t.return=e,e.child=t}function _r(e,t,n,r){return r!==null&&_s(r),cn(t,e.child,null,n),e=As(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function Fd(e,t,n,r,l,o,s){if(n)return t.flags&256?(t.flags&=-257,r=eo(Error(g(422))),_r(e,t,s,r)):t.memoizedState!==null?(t.child=e.child,t.flags|=128,null):(o=r.fallback,l=t.mode,r=jl({mode:"visible",children:r.children},l,0,null),o=Tt(o,l,s,null),o.flags|=2,r.return=t,o.return=t,r.sibling=o,t.child=r,t.mode&1&&cn(t,e.child,null,s),t.child.memoizedState=Qo(s),t.memoizedState=Wo,o);if(!(t.mode&1))return _r(e,t,s,null);if(l.data==="$!"){if(r=l.nextSibling&&l.nextSibling.dataset,r)var i=r.dgst;return r=i,o=Error(g(419)),r=eo(o,r,void 0),_r(e,t,s,r)}if(i=(s&e.childLanes)!==0,de||i){if(r=q,r!==null){switch(s&-s){case 4:l=2;break;case 16:l=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:l=32;break;case 536870912:l=268435456;break;default:l=0}l=l&(r.suspendedLanes|s)?0:l,l!==0&&l!==o.retryLane&&(o.retryLane=l,Je(e,l),Oe(r,e,l,-1))}return Ws(),r=eo(Error(g(421))),_r(e,t,s,r)}return l.data==="$?"?(t.flags|=128,t.child=e.child,t=Zd.bind(null,e),l._reactRetry=t,null):(e=o.treeContext,ge=pt(l.nextSibling),we=t,V=!0,Re=null,e!==null&&(Ce[_e++]=Ke,Ce[_e++]=Ye,Ce[_e++]=Rt,Ke=e.id,Ye=e.overflow,Rt=t),t=As(t,r.children),t.flags|=4096,t)}function Xi(e,t,n){e.lanes|=t;var r=e.alternate;r!==null&&(r.lanes|=t),Ao(e.return,t,n)}function to(e,t,n,r,l){var o=e.memoizedState;o===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:l}:(o.isBackwards=t,o.rendering=null,o.renderingStartTime=0,o.last=r,o.tail=n,o.tailMode=l)}function nc(e,t,n){var r=t.pendingProps,l=r.revealOrder,o=r.tail;if(se(e,t,r.children,n),r=U.current,r&2)r=r&1|2,t.flags|=128;else{if(e!==null&&e.flags&128)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&Xi(e,n,t);else if(e.tag===19)Xi(e,n,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}if(O(U,r),!(t.mode&1))t.memoizedState=null;else switch(l){case"forwards":for(n=t.child,l=null;n!==null;)e=n.alternate,e!==null&&sl(e)===null&&(l=n),n=n.sibling;n=l,n===null?(l=t.child,t.child=null):(l=n.sibling,n.sibling=null),to(t,!1,l,n,o);break;case"backwards":for(n=null,l=t.child,t.child=null;l!==null;){if(e=l.alternate,e!==null&&sl(e)===null){t.child=l;break}e=l.sibling,l.sibling=n,n=l,l=e}to(t,!0,n,null,o);break;case"together":to(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function Vr(e,t){!(t.mode&1)&&e!==null&&(e.alternate=null,t.alternate=null,t.flags|=2)}function qe(e,t,n){if(e!==null&&(t.dependencies=e.dependencies),It|=t.lanes,!(n&t.childLanes))return null;if(e!==null&&t.child!==e.child)throw Error(g(153));if(t.child!==null){for(e=t.child,n=yt(e,e.pendingProps),t.child=n,n.return=t;e.sibling!==null;)e=e.sibling,n=n.sibling=yt(e,e.pendingProps),n.return=t;n.sibling=null}return t.child}function Ad(e,t,n){switch(t.tag){case 3:ec(t),an();break;case 5:Ma(t);break;case 1:me(t.type)&&el(t);break;case 4:Ps(t,t.stateNode.containerInfo);break;case 10:var r=t.type._context,l=t.memoizedProps.value;O(rl,r._currentValue),r._currentValue=l;break;case 13:if(r=t.memoizedState,r!==null)return r.dehydrated!==null?(O(U,U.current&1),t.flags|=128,null):n&t.child.childLanes?tc(e,t,n):(O(U,U.current&1),e=qe(e,t,n),e!==null?e.sibling:null);O(U,U.current&1);break;case 19:if(r=(n&t.childLanes)!==0,e.flags&128){if(r)return nc(e,t,n);t.flags|=128}if(l=t.memoizedState,l!==null&&(l.rendering=null,l.tail=null,l.lastEffect=null),O(U,U.current),r)break;return null;case 22:case 23:return t.lanes=0,qa(e,t,n)}return qe(e,t,n)}var rc,Ko,lc,oc;rc=function(e,t){for(var n=t.child;n!==null;){if(n.tag===5||n.tag===6)e.appendChild(n.stateNode);else if(n.tag!==4&&n.child!==null){n.child.return=n,n=n.child;continue}if(n===t)break;for(;n.sibling===null;){if(n.return===null||n.return===t)return;n=n.return}n.sibling.return=n.return,n=n.sibling}};Ko=function(){};lc=function(e,t,n,r){var l=e.memoizedProps;if(l!==r){e=t.stateNode,Lt(He.current);var o=null;switch(n){case"input":l=mo(e,l),r=mo(e,r),o=[];break;case"select":l=B({},l,{value:void 0}),r=B({},r,{value:void 0}),o=[];break;case"textarea":l=yo(e,l),r=yo(e,r),o=[];break;default:typeof l.onClick!="function"&&typeof r.onClick=="function"&&(e.onclick=qr)}wo(n,r);var s;n=null;for(c in l)if(!r.hasOwnProperty(c)&&l.hasOwnProperty(c)&&l[c]!=null)if(c==="style"){var i=l[c];for(s in i)i.hasOwnProperty(s)&&(n||(n={}),n[s]="")}else c!=="dangerouslySetInnerHTML"&&c!=="children"&&c!=="suppressContentEditableWarning"&&c!=="suppressHydrationWarning"&&c!=="autoFocus"&&(Vn.hasOwnProperty(c)?o||(o=[]):(o=o||[]).push(c,null));for(c in r){var a=r[c];if(i=l?.[c],r.hasOwnProperty(c)&&a!==i&&(a!=null||i!=null))if(c==="style")if(i){for(s in i)!i.hasOwnProperty(s)||a&&a.hasOwnProperty(s)||(n||(n={}),n[s]="");for(s in a)a.hasOwnProperty(s)&&i[s]!==a[s]&&(n||(n={}),n[s]=a[s])}else n||(o||(o=[]),o.push(c,n)),n=a;else c==="dangerouslySetInnerHTML"?(a=a?a.__html:void 0,i=i?i.__html:void 0,a!=null&&i!==a&&(o=o||[]).push(c,a)):c==="children"?typeof a!="string"&&typeof a!="number"||(o=o||[]).push(c,""+a):c!=="suppressContentEditableWarning"&&c!=="suppressHydrationWarning"&&(Vn.hasOwnProperty(c)?(a!=null&&c==="onScroll"&&A("scroll",e),o||i===a||(o=[])):(o=o||[]).push(c,a))}n&&(o=o||[]).push("style",n);var c=o;(t.updateQueue=c)&&(t.flags|=4)}};oc=function(e,t,n,r){n!==r&&(t.flags|=4)};function Nn(e,t){if(!V)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;t!==null;)t.alternate!==null&&(n=t),t=t.sibling;n===null?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;n!==null;)n.alternate!==null&&(r=n),n=n.sibling;r===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:r.sibling=null}}function re(e){var t=e.alternate!==null&&e.alternate.child===e.child,n=0,r=0;if(t)for(var l=e.child;l!==null;)n|=l.lanes|l.childLanes,r|=l.subtreeFlags&14680064,r|=l.flags&14680064,l.return=e,l=l.sibling;else for(l=e.child;l!==null;)n|=l.lanes|l.childLanes,r|=l.subtreeFlags,r|=l.flags,l.return=e,l=l.sibling;return e.subtreeFlags|=r,e.childLanes=n,t}function $d(e,t,n){var r=t.pendingProps;switch(Cs(t),t.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return re(t),null;case 1:return me(t.type)&&br(),re(t),null;case 3:return r=t.stateNode,fn(),$(pe),$(oe),zs(),r.pendingContext&&(r.context=r.pendingContext,r.pendingContext=null),(e===null||e.child===null)&&(Sr(t)?t.flags|=4:e===null||e.memoizedState.isDehydrated&&!(t.flags&256)||(t.flags|=1024,Re!==null&&(es(Re),Re=null))),Ko(e,t),re(t),null;case 5:Ts(t);var l=Lt(qn.current);if(n=t.type,e!==null&&t.stateNode!=null)lc(e,t,n,r,l),e.ref!==t.ref&&(t.flags|=512,t.flags|=2097152);else{if(!r){if(t.stateNode===null)throw Error(g(166));return re(t),null}if(e=Lt(He.current),Sr(t)){r=t.stateNode,n=t.type;var o=t.memoizedProps;switch(r[Ve]=t,r[Zn]=o,e=(t.mode&1)!==0,n){case"dialog":A("cancel",r),A("close",r);break;case"iframe":case"object":case"embed":A("load",r);break;case"video":case"audio":for(l=0;l<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=s.createElement(n,{is:r.is}):(e=s.createElement(n),n==="select"&&(s=e,r.multiple?s.multiple=!0:r.size&&(s.size=r.size))):e=s.createElementNS(e,n),e[Ve]=t,e[Zn]=r,rc(e,t,!1,!1),t.stateNode=e;e:{switch(s=xo(n,r),n){case"dialog":A("cancel",e),A("close",e),l=r;break;case"iframe":case"object":case"embed":A("load",e),l=r;break;case"video":case"audio":for(l=0;lpn&&(t.flags|=128,r=!0,Nn(o,!1),t.lanes=4194304)}else{if(!r)if(e=sl(s),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Nn(o,!0),o.tail===null&&o.tailMode==="hidden"&&!s.alternate&&!V)return re(t),null}else 2*K()-o.renderingStartTime>pn&&n!==1073741824&&(t.flags|=128,r=!0,Nn(o,!1),t.lanes=4194304);o.isBackwards?(s.sibling=t.child,t.child=s):(n=o.last,n!==null?n.sibling=s:t.child=s,o.last=s)}return o.tail!==null?(t=o.tail,o.rendering=t,o.tail=t.sibling,o.renderingStartTime=K(),t.sibling=null,n=U.current,O(U,r?n&1|2:n&1),t):(re(t),null);case 22:case 23:return Bs(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?ye&1073741824&&(re(t),t.subtreeFlags&6&&(t.flags|=8192)):re(t),null;case 24:return null;case 25:return null}throw Error(g(156,t.tag))}function Vd(e,t){switch(Cs(t),t.tag){case 1:return me(t.type)&&br(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return fn(),$(pe),$(oe),zs(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Ts(t),null;case 13:if($(U),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(g(340));an()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return $(U),null;case 4:return fn(),null;case 10:return Es(t.type._context),null;case 22:case 23:return Bs(),null;case 24:return null;default:return null}}var Nr=!1,le=!1,Ud=typeof WeakSet=="function"?WeakSet:Set,j=null;function Jt(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Q(e,t,r)}else n.current=null}function Yo(e,t,n){try{n()}catch(r){Q(e,t,r)}}var Gi=!1;function Hd(e,t){if(Po=Gr,e=ca(),ks(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break e}var s=0,i=-1,a=-1,c=0,m=0,v=e,h=null;t:for(;;){for(var S;v!==n||l!==0&&v.nodeType!==3||(i=s+l),v!==o||r!==0&&v.nodeType!==3||(a=s+r),v.nodeType===3&&(s+=v.nodeValue.length),(S=v.firstChild)!==null;)h=v,v=S;for(;;){if(v===e)break t;if(h===n&&++c===l&&(i=s),h===o&&++m===r&&(a=s),(S=v.nextSibling)!==null)break;v=h,h=v.parentNode}v=S}n=i===-1||a===-1?null:{start:i,end:a}}else n=null}n=n||{start:0,end:0}}else n=null;for(To={focusedElem:e,selectionRange:n},Gr=!1,j=t;j!==null;)if(t=j,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,j=e;else for(;j!==null;){t=j;try{var x=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(x!==null){var k=x.memoizedProps,R=x.memoizedState,d=t.stateNode,f=d.getSnapshotBeforeUpdate(t.elementType===t.type?k:Pe(t.type,k),R);d.__reactInternalSnapshotBeforeUpdate=f}break;case 3:var p=t.stateNode.containerInfo;p.nodeType===1?p.textContent="":p.nodeType===9&&p.documentElement&&p.removeChild(p.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(g(163))}}catch(y){Q(t,t.return,y)}if(e=t.sibling,e!==null){e.return=t.return,j=e;break}j=t.return}return x=Gi,Gi=!1,x}function Fn(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var o=l.destroy;l.destroy=void 0,o!==void 0&&Yo(t,n,o)}l=l.next}while(l!==r)}}function _l(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Xo(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function sc(e){var t=e.alternate;t!==null&&(e.alternate=null,sc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ve],delete t[Zn],delete t[Do],delete t[_d],delete t[Nd])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function ic(e){return e.tag===5||e.tag===3||e.tag===4}function Zi(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||ic(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Go(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=qr));else if(r!==4&&(e=e.child,e!==null))for(Go(e,t,n),e=e.sibling;e!==null;)Go(e,t,n),e=e.sibling}function Zo(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Zo(e,t,n),e=e.sibling;e!==null;)Zo(e,t,n),e=e.sibling}var b=null,Te=!1;function rt(e,t,n){for(n=n.child;n!==null;)uc(e,t,n),n=n.sibling}function uc(e,t,n){if(Ue&&typeof Ue.onCommitFiberUnmount=="function")try{Ue.onCommitFiberUnmount(vl,n)}catch{}switch(n.tag){case 5:le||Jt(n,t);case 6:var r=b,l=Te;b=null,rt(e,t,n),b=r,Te=l,b!==null&&(Te?(e=b,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):b.removeChild(n.stateNode));break;case 18:b!==null&&(Te?(e=b,n=n.stateNode,e.nodeType===8?Xl(e.parentNode,n):e.nodeType===1&&Xl(e,n),Qn(e)):Xl(b,n.stateNode));break;case 4:r=b,l=Te,b=n.stateNode.containerInfo,Te=!0,rt(e,t,n),b=r,Te=l;break;case 0:case 11:case 14:case 15:if(!le&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var o=l,s=o.destroy;o=o.tag,s!==void 0&&(o&2||o&4)&&Yo(n,t,s),l=l.next}while(l!==r)}rt(e,t,n);break;case 1:if(!le&&(Jt(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(i){Q(n,t,i)}rt(e,t,n);break;case 21:rt(e,t,n);break;case 22:n.mode&1?(le=(r=le)||n.memoizedState!==null,rt(e,t,n),le=r):rt(e,t,n);break;default:rt(e,t,n)}}function Ji(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Ud),t.forEach(function(r){var l=Jd.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function Le(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=s),r&=~o}if(r=l,r=K()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Wd(r/1960))-r,10e?16:e,at===null)var r=!1;else{if(e=at,at=null,fl=0,D&6)throw Error(g(331));var l=D;for(D|=4,j=e.current;j!==null;){var o=j,s=o.child;if(j.flags&16){var i=o.deletions;if(i!==null){for(var a=0;aK()-Us?Pt(e,0):Vs|=n),he(e,t)}function vc(e,t){t===0&&(e.mode&1?(t=vr,vr<<=1,!(vr&130023424)&&(vr=4194304)):t=1);var n=ue();e=Je(e,t),e!==null&&(lr(e,t,n),he(e,n))}function Zd(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),vc(e,n)}function Jd(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(g(314))}r!==null&&r.delete(t),vc(e,n)}var yc;yc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||pe.current)de=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return de=!1,Ad(e,t,n);de=!!(e.flags&131072)}else de=!1,V&&t.flags&1048576&&ka(t,nl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Vr(e,t),e=t.pendingProps;var l=un(t,oe.current);ln(t,n),l=Ds(null,t,r,e,l,n);var o=Is();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,me(r)?(o=!0,el(t)):o=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ls(t),l.updater=Cl,t.stateNode=l,l._reactInternals=t,Vo(t,r,e,n),t=Bo(null,t,r,!0,o,n)):(t.tag=0,V&&o&&Ss(t),se(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Vr(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=bd(r),e=Pe(r,e),l){case 0:t=Ho(null,t,r,e,n);break e;case 1:t=Ki(null,t,r,e,n);break e;case 11:t=Wi(null,t,r,e,n);break e;case 14:t=Qi(null,t,r,Pe(r.type,e),n);break e}throw Error(g(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Ho(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Ki(e,t,r,l,n);case 3:e:{if(ec(t),e===null)throw Error(g(387));r=t.pendingProps,o=t.memoizedState,l=o.element,Ea(e,t),ol(t,r,null,n);var s=t.memoizedState;if(r=s.element,o.isDehydrated)if(o={element:r,isDehydrated:!1,cache:s.cache,pendingSuspenseBoundaries:s.pendingSuspenseBoundaries,transitions:s.transitions},t.updateQueue.baseState=o,t.memoizedState=o,t.flags&256){l=dn(Error(g(423)),t),t=Yi(e,t,r,n,l);break e}else if(r!==l){l=dn(Error(g(424)),t),t=Yi(e,t,r,n,l);break e}else for(ge=pt(t.stateNode.containerInfo.firstChild),we=t,V=!0,Re=null,n=Na(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(an(),r===l){t=qe(e,t,n);break e}se(e,t,r,n)}t=t.child}return t;case 5:return Ma(t),e===null&&Fo(t),r=t.type,l=t.pendingProps,o=e!==null?e.memoizedProps:null,s=l.children,zo(r,l)?s=null:o!==null&&zo(r,o)&&(t.flags|=32),ba(e,t),se(e,t,s,n),t.child;case 6:return e===null&&Fo(t),null;case 13:return tc(e,t,n);case 4:return Ps(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=cn(t,null,r,n):se(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Wi(e,t,r,l,n);case 7:return se(e,t,t.pendingProps,n),t.child;case 8:return se(e,t,t.pendingProps.children,n),t.child;case 12:return se(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,o=t.memoizedProps,s=l.value,O(rl,r._currentValue),r._currentValue=s,o!==null)if(Fe(o.value,s)){if(o.children===l.children&&!pe.current){t=qe(e,t,n);break e}}else for(o=t.child,o!==null&&(o.return=t);o!==null;){var i=o.dependencies;if(i!==null){s=o.child;for(var a=i.firstContext;a!==null;){if(a.context===r){if(o.tag===1){a=Xe(-1,n&-n),a.tag=2;var c=o.updateQueue;if(c!==null){c=c.shared;var m=c.pending;m===null?a.next=a:(a.next=m.next,m.next=a),c.pending=a}}o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),Ao(o.return,n,t),i.lanes|=n;break}a=a.next}}else if(o.tag===10)s=o.type===t.type?null:o.child;else if(o.tag===18){if(s=o.return,s===null)throw Error(g(341));s.lanes|=n,i=s.alternate,i!==null&&(i.lanes|=n),Ao(s,n,t),s=o.sibling}else s=o.child;if(s!==null)s.return=o;else for(s=o;s!==null;){if(s===t){s=null;break}if(o=s.sibling,o!==null){o.return=s.return,s=o;break}s=s.return}o=s}se(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,ln(t,n),l=Ee(l),r=r(l),t.flags|=1,se(e,t,r,n),t.child;case 14:return r=t.type,l=Pe(r,t.pendingProps),l=Pe(r.type,l),Qi(e,t,r,l,n);case 15:return Ja(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Vr(e,t),t.tag=1,me(r)?(e=!0,el(t)):e=!1,ln(t,n),Xa(t,r,l),Vo(t,r,l,n),Bo(null,t,r,!0,e,n);case 19:return nc(e,t,n);case 22:return qa(e,t,n)}throw Error(g(156,t.tag))};function gc(e,t){return Qu(e,t)}function qd(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Ne(e,t,n,r){return new qd(e,t,n,r)}function Qs(e){return e=e.prototype,!(!e||!e.isReactComponent)}function bd(e){if(typeof e=="function")return Qs(e)?1:0;if(e!=null){if(e=e.$$typeof,e===cs)return 11;if(e===fs)return 14}return 2}function yt(e,t){var n=e.alternate;return n===null?(n=Ne(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Br(e,t,n,r,l,o){var s=2;if(r=e,typeof e=="function")Qs(e)&&(s=1);else if(typeof e=="string")s=5;else e:switch(e){case Ht:return Tt(n.children,l,o,t);case as:s=8,l|=8;break;case ao:return e=Ne(12,n,t,l|2),e.elementType=ao,e.lanes=o,e;case co:return e=Ne(13,n,t,l),e.elementType=co,e.lanes=o,e;case fo:return e=Ne(19,n,t,l),e.elementType=fo,e.lanes=o,e;case Mu:return jl(n,l,o,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case ju:s=10;break e;case Eu:s=9;break e;case cs:s=11;break e;case fs:s=14;break e;case lt:s=16,r=null;break e}throw Error(g(130,e==null?e:typeof e,""))}return t=Ne(s,n,t,l),t.elementType=e,t.type=r,t.lanes=o,t}function Tt(e,t,n,r){return e=Ne(7,e,r,t),e.lanes=n,e}function jl(e,t,n,r){return e=Ne(22,e,r,t),e.elementType=Mu,e.lanes=n,e.stateNode={isHidden:!1},e}function no(e,t,n){return e=Ne(6,e,null,t),e.lanes=n,e}function ro(e,t,n){return t=Ne(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function ep(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Fl(0),this.expirationTimes=Fl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Fl(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Ks(e,t,n,r,l,o,s,i,a){return e=new ep(e,t,n,i,a),t===1?(t=1,o===!0&&(t|=8)):t=0,o=Ne(3,null,null,t),e.current=o,o.stateNode=e,o.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ls(o),e}function tp(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Sc)}catch(e){console.error(e)}}Sc(),Su.exports=ke;var sp=Su.exports,ou=sp;io.createRoot=ou.createRoot,io.hydrateRoot=ou.hydrateRoot;function ip({strokeWidth:e=60,...t}){return u.jsx("svg",{viewBox:"0 0 1188 1773",fill:"none",xmlns:"http://www.w3.org/2000/svg",role:"img","aria-hidden":"true",...t,children:u.jsx("path",{d:"M25 561L245 694M25 561V818M245 694V951M25 961V1218M25 1357V1614M245 1489V1747M245 1093V1351M942 823V1080M1161 955V1213M1162 555V812M942 422V679M669 585V843L787 913M942 25V282M1162 158V415M25 818L245 951M244 1094L464 962M25 961L143 890M244 1352L464 1219M942 823L1162 956M942 679L1162 812M721 811L942 679M669 842L724 809M669 586L724 553M1041 883L1162 812M245 1747L1161 1213M244 1490L942 1080M25 1357L142 1289M518 1071L942 823M721 555L942 422M942 422L1162 556M942 282L1162 415M942 25L1162 158M942 1080L1161 1213M25 1218L245 1351M25 961L245 1094M464 962L519 929M464 1219L519 1186V928L403 859M25 1357L245 1490M25 1614L245 1747M25 561L942 25M244 694L941 282M1043 484L1162 415M245 951L668 704",stroke:"currentColor",strokeWidth:e,strokeLinecap:"round"})})}function up(e){return u.jsxs("svg",{viewBox:"269 80 364 110",fill:"none",xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":"Patter",...e,children:[u.jsx("path",{d:"M271.422 182.689V85.9524H317.517C324.705 85.9524 330.86 87.2064 335.982 89.7143C341.193 92.2223 345.192 95.7156 347.977 100.194C350.852 104.673 352.29 109.913 352.29 115.914C352.29 121.915 350.852 127.2 347.977 131.768C345.102 136.336 341.058 139.919 335.847 142.516C330.725 145.024 324.615 146.278 317.517 146.278H287.866V130.424H316.439C321.201 130.424 324.885 129.125 327.491 126.528C330.186 123.841 331.534 120.348 331.534 116.048C331.534 111.749 330.186 108.3 327.491 105.703C324.885 103.105 321.201 101.806 316.439 101.806H292.178V182.689H271.422Z",fill:"currentColor"}),u.jsx("path",{d:"M395.375 182.689C394.836 180.718 394.432 178.613 394.162 176.374C393.982 174.135 393.893 171.537 393.893 168.581H393.353V136.202C393.353 133.425 392.41 131.275 390.523 129.752C388.726 128.14 386.03 127.334 382.436 127.334C379.022 127.334 376.281 127.916 374.215 129.081C372.238 130.245 370.935 131.947 370.306 134.186H351.033C351.931 128.006 355.121 122.9 360.602 118.87C366.083 114.839 373.586 112.824 383.11 112.824C392.994 112.824 400.542 115.018 405.753 119.407C410.965 123.796 413.57 130.111 413.57 138.351V168.581C413.57 170.821 413.705 173.105 413.975 175.434C414.334 177.673 414.873 180.091 415.592 182.689H395.375ZM371.384 184.032C364.556 184.032 359.12 182.33 355.076 178.927C351.033 175.434 349.011 170.821 349.011 165.088C349.011 158.729 351.392 153.623 356.154 149.772C361.006 145.83 367.745 143.278 376.371 142.113L396.453 139.292V150.981L379.741 153.533C376.147 154.071 373.496 155.056 371.789 156.489C370.082 157.922 369.228 159.893 369.228 162.401C369.228 164.64 370.037 166.342 371.654 167.507C373.271 168.671 375.428 169.253 378.123 169.253C382.347 169.253 385.941 168.134 388.906 165.894C391.871 163.565 393.353 160.878 393.353 157.833L395.24 168.581C393.264 173.687 390.254 177.538 386.21 180.136C382.167 182.734 377.225 184.032 371.384 184.032Z",fill:"currentColor"}),u.jsx("path",{d:"M450.248 184.167C441.443 184.167 434.883 182.062 430.57 177.852C426.347 173.553 424.236 167.059 424.236 158.37V98.8506L444.453 91.3266V159.042C444.453 162.087 445.306 164.372 447.014 165.894C448.721 167.417 451.371 168.178 454.966 168.178C456.313 168.178 457.571 168.044 458.739 167.775C459.907 167.507 461.075 167.193 462.244 166.835V182.151C461.075 182.778 459.413 183.271 457.257 183.629C455.19 183.988 452.854 184.167 450.248 184.167ZM411.432 129.484V114.167H462.244V129.484H411.432Z",fill:"currentColor"}),u.jsx("path",{d:"M500.501 184.167C491.695 184.167 485.136 182.062 480.823 177.852C476.6 173.553 474.489 167.059 474.489 158.37V98.8506L494.705 91.3266V159.042C494.705 162.087 495.559 164.372 497.266 165.894C498.973 167.417 501.624 168.178 505.218 168.178C506.566 168.178 507.824 168.044 508.992 167.775C510.16 167.507 511.328 167.193 512.496 166.835V182.151C511.328 182.778 509.666 183.271 507.509 183.629C505.443 183.988 503.107 184.167 500.501 184.167ZM461.684 129.484V114.167H512.496V129.484H461.684Z",fill:"currentColor"}),u.jsx("path",{d:"M547.852 184.032C540.214 184.032 533.565 182.554 527.904 179.599C522.244 176.553 517.841 172.343 514.696 166.969C511.641 161.595 510.113 155.414 510.113 148.428C510.113 141.352 511.641 135.171 514.696 129.887C517.841 124.513 522.199 120.348 527.769 117.392C533.34 114.346 539.81 112.824 547.178 112.824C554.276 112.824 560.431 114.257 565.642 117.123C570.854 119.989 574.897 123.975 577.773 129.081C580.648 134.186 582.086 140.187 582.086 147.084C582.086 148.518 582.041 149.861 581.951 151.115C581.861 152.279 581.726 153.399 581.546 154.474H521.974V141.173H565.238L561.734 143.591C561.734 138.038 560.386 133.962 557.69 131.365C555.085 128.678 551.491 127.334 546.908 127.334C541.607 127.334 537.474 129.125 534.508 132.708C531.633 136.291 530.196 141.665 530.196 148.831C530.196 155.818 531.633 161.013 534.508 164.416C537.474 167.82 541.876 169.522 547.717 169.522C550.952 169.522 553.737 168.984 556.073 167.91C558.409 166.835 560.161 165.088 561.33 162.67H580.333C578.087 169.298 574.223 174.538 568.742 178.389C563.351 182.151 556.388 184.032 547.852 184.032Z",fill:"currentColor"}),u.jsx("path",{d:"M586.158 182.689V114.167H605.971V130.29H606.375V182.689H586.158ZM606.375 146.95L604.623 130.693C606.24 124.871 608.891 120.437 612.575 117.392C616.259 114.346 620.842 112.824 626.323 112.824C628.03 112.824 629.288 113.003 630.096 113.361V132.171C629.647 131.992 629.018 131.902 628.21 131.902C627.401 131.813 626.412 131.768 625.244 131.768C618.775 131.768 614.013 132.932 610.958 135.261C607.903 137.5 606.375 141.397 606.375 146.95Z",fill:"currentColor"})]})}function ap(){return u.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:8,color:"var(--ink)"},"aria-label":"Patter",children:[u.jsx(ip,{height:26}),u.jsx(up,{height:24})]})}function cp({liveCount:e,todayCount:t,phoneNumber:n,sdkVersion:r}){return u.jsxs("header",{className:"top",children:[u.jsxs("div",{className:"brand",children:[u.jsx(ap,{}),u.jsxs("span",{className:"tag",children:["dashboard · v",r]})]}),u.jsxs("div",{className:"top-r",children:[u.jsxs("span",{className:"live-chip",children:[u.jsx("span",{className:"pulse"+(e>0?" active":"")}),e," live · ",t," today"]}),n!=="—"&&u.jsx("span",{className:"num-chip",children:n})]})]})}function fp(e){return u.jsxs("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("circle",{cx:"11",cy:"11",r:"7"}),u.jsx("path",{d:"m21 21-4.3-4.3"})]})}function Cc(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.4",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M7 13l5 5 5-5"}),u.jsx("path",{d:"M12 4v14"})]})}function dp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.4",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M17 11l-5-5-5 5"}),u.jsx("path",{d:"M12 20V6"})]})}function pp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("rect",{x:"9",y:"2",width:"6",height:"12",rx:"3"}),u.jsx("path",{d:"M19 10a7 7 0 0 1-14 0"}),u.jsx("path",{d:"M12 19v3"})]})}function mp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("polyline",{points:"15 17 20 12 15 7"}),u.jsx("path",{d:"M4 18v-2a4 4 0 0 1 4-4h12"})]})}function hp(e){return u.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"currentColor",...e,children:u.jsx("circle",{cx:"12",cy:"12",r:"6"})})}function vp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67"}),u.jsx("path",{d:"M22 2 2 22"})]})}const yp=["1h","24h","7d","All"];function gp(){const e=document.createElement("a");e.href="/api/dashboard/export/calls?format=csv",e.download="patter_calls.csv",e.rel="noopener",document.body.appendChild(e),e.click(),document.body.removeChild(e)}function wp({range:e,setRange:t}){return u.jsxs("div",{className:"ph",children:[u.jsxs("div",{children:[u.jsx("h1",{children:"Calls"}),u.jsxs("p",{className:"sub",children:["Real-time view of every call routed through this Patter instance."," ",u.jsx("span",{className:"kbd",children:"⇧K"})," to focus search."]})]}),u.jsxs("div",{className:"filters",children:[u.jsx("div",{className:"seg",children:yp.map(n=>u.jsx("button",{type:"button",className:e===n?"on":"",onClick:()=>t(n),children:n},n))}),u.jsxs("button",{className:"btn",type:"button",onClick:gp,children:[u.jsx(Cc,{})," Export CSV"]})]})]})}function ml(e){const t=Math.floor(e/60),n=Math.floor(e%60);return`${String(t).padStart(2,"0")}:${String(n).padStart(2,"0")}`}function ze(e){if(e==null||!Number.isFinite(e))return"$0.00";const t=Math.abs(e);return t===0?"$0.00":t>=.01?`$${e.toFixed(2)}`:t>=.001?`$${e.toFixed(3)}`:t>=1e-4?`$${e.toFixed(4)}`:`$${e.toFixed(5)}`}const _c=60*60*1e3,xp=24*_c;function Mr(e){return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function kp(e){return new Date(e).toLocaleDateString([],{weekday:"short",month:"short",day:"numeric"})}function su(e){return new Date(e).toLocaleString([],{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function Nc(e){const t=e.toMs-e.fromMs;return t>=xp-Sp?kp(e.fromMs):t>=_c?`${Mr(e.fromMs)} → ${Mr(e.toMs)}`:t>=60*1e3?`${Mr(e.fromMs)} → ${Mr(e.toMs)}`:`${su(e.fromMs)} → ${su(e.toMs)}`}const Sp=5e3;function jc(e){return e.cost.total??(e.cost.telco??0)+(e.cost.llm??0)+(e.cost.sttTts??0)}function Cp(e){return e.calls.length===0?void 0:[...e.calls].sort((n,r)=>(r.startedAtMs??0)-(n.startedAtMs??0))[0]?.id}function _p(e,t){const n=e.calls,r=n.length;if(t==="spend"){const l=n.reduce((o,s)=>o+jc(s),0);return{label:"TOTAL COST",value:ze(l)}}if(t==="latency"){const l=n.filter(s=>typeof s.latencyP95=="number");return{label:"AVG LATENCY",value:`${l.length>0?Math.round(l.reduce((s,i)=>s+(i.latencyP95??0),0)/l.length):0} ms`}}return{label:r===1?"CALL":"CALLS",value:`${r}`}}function Np({bucket:e,kind:t}){const n=Nc(e),r=e.calls.length;if(r===0)return u.jsxs("div",{className:"spark-tooltip",children:[u.jsx("div",{className:"spark-tooltip-range",children:n}),u.jsx("div",{className:"spark-tooltip-empty",children:"no calls"})]});const l=_p(e,t),o=e.calls.slice(0,4);return u.jsxs("div",{className:"spark-tooltip",children:[u.jsx("div",{className:"spark-tooltip-range",children:n}),u.jsxs("div",{className:"spark-tooltip-headline",children:[u.jsx("span",{className:"spark-tooltip-headline-l",children:l.label}),u.jsx("span",{className:"spark-tooltip-headline-v",children:l.value})]}),u.jsx("ul",{className:"spark-tooltip-list",children:o.map(s=>{const i=s.direction==="inbound"?s.from:s.to;return u.jsxs("li",{children:[u.jsx("span",{className:"num",children:i}),u.jsx("span",{className:"status",children:s.status}),u.jsx("span",{className:"cost",children:ze(jc(s))})]},s.id)})}),r>o.length&&u.jsxs("div",{className:"spark-tooltip-more",children:["+",r-o.length," more"]})]})}function jp({bucket:e,height:t,interactive:n,kind:r,onSelect:l}){const[o,s]=L.useState(!1),i=!!e&&e.calls.length>0;return!n||!e?u.jsx("span",{className:"spark-bar-static",style:{height:t+"%"}}):u.jsxs("div",{className:"spark-bar-wrap",onMouseEnter:()=>s(!0),onMouseLeave:()=>s(!1),children:[u.jsx("button",{type:"button",className:"spark-bar"+(i?"":" empty"),style:{height:t+"%"},disabled:!i,onClick:()=>{if(!i)return;const a=Cp(e);a&&l&&l(a)},onFocus:()=>s(!0),onBlur:()=>s(!1),"aria-label":`${e.calls.length} calls in ${Nc(e)}`}),o&&u.jsx(Np,{bucket:e,kind:r})]})}function Lr({label:e,value:t,unit:n,delta:r,deltaTone:l,spark:o,buckets:s,onSelectCall:i,kind:a="count",peach:c,footer:m,badge:v}){const h=!!s&&!!i;return u.jsxs("div",{className:"metric"+(c?" peach":""),children:[u.jsxs("div",{className:"lbl",children:[u.jsx("span",{children:e}),v&&u.jsx("span",{className:"badge-now",children:"LIVE"})]}),u.jsxs("div",{className:"val",children:[t,n&&u.jsxs("span",{className:"unit",children:[" ",n]})]}),r&&u.jsx("div",{className:"delta "+(l||""),children:r}),m&&u.jsx("div",{className:"delta",children:m}),u.jsx("div",{className:"spark",children:o.map((S,x)=>u.jsx(jp,{bucket:s?.[x],height:S,interactive:h,kind:a,onSelect:i},x))})]})}function Ep({call:e,isSelected:t,onSelect:n,isNew:r}){const l=e.status==="live"&&e.durationStart?ml((Date.now()-e.durationStart)/1e3):ml(e.duration||0),o=e.latencyP95?Math.min(100,e.latencyP95/1e3*100):0,s=(e.latencyP95??0)>600,i=e.cost.total??(e.cost.telco??0)+(e.cost.llm??0)+(e.cost.sttTts??0),a=e.status.replace("-","");return u.jsxs("tr",{className:(t?"selected ":"")+(r?"new-row":""),onClick:n,children:[u.jsx("td",{children:u.jsx("span",{className:"pill "+a,children:e.status})}),u.jsxs("td",{children:[u.jsx("span",{className:"dir in",style:{marginRight:8,color:e.direction==="inbound"?"#3b6f3b":"#4a4a4a"},children:e.direction==="inbound"?u.jsx(Cc,{}):u.jsx(dp,{})}),u.jsxs("span",{className:"num-cell",children:[e.from," → ",e.to]})]}),u.jsx("td",{children:u.jsxs("span",{className:"car-tw",children:[u.jsx("span",{className:"car-dot "+(e.carrier==="twilio"?"tw":"tx")}),e.carrier==="twilio"?"Twilio":"Telnyx"]})}),u.jsx("td",{className:"num-cell",children:e.status==="no-answer"?"—":l}),u.jsx("td",{children:e.latencyP95?u.jsxs(u.Fragment,{children:[u.jsx("span",{className:"lat-bar"+(s?" warn":""),children:u.jsx("i",{style:{width:o+"%"}})}),u.jsxs("span",{className:"num-cell",children:[e.latencyP95," ms"]})]}):"—"}),u.jsx("td",{className:"num-cell",children:ze(i)})]})}function Mp({calls:e,selectedId:t,onSelect:n,newId:r,search:l,setSearch:o}){const s=L.useMemo(()=>{if(!l.trim())return e;const i=l.toLowerCase();return e.filter(a=>a.from.toLowerCase().includes(i)||a.to.toLowerCase().includes(i)||a.status.includes(i)||a.carrier.includes(i)||a.id.includes(i))},[e,l]);return u.jsxs("div",{className:"panel",children:[u.jsxs("div",{className:"panel-h",children:[u.jsxs("h3",{children:["Recent calls"," ",u.jsxs("span",{style:{fontFamily:"var(--font-mono)",fontSize:11,color:"#aaa",fontWeight:500,marginLeft:4},children:["(",s.length,")"]})]}),u.jsxs("div",{className:"search",children:[u.jsx(fp,{}),u.jsx("input",{placeholder:"Search number, status, carrier…",value:l,onChange:i=>o(i.target.value)})]}),u.jsxs("span",{className:"sse",children:[u.jsx("span",{className:"dot"}),"streaming · SSE"]})]}),u.jsx("div",{style:{maxHeight:540,overflow:"auto"},children:u.jsxs("table",{children:[u.jsx("thead",{children:u.jsxs("tr",{children:[u.jsx("th",{children:"Status"}),u.jsx("th",{children:"From → To"}),u.jsx("th",{children:"Carrier"}),u.jsx("th",{children:"Duration"}),u.jsx("th",{children:"p95 latency"}),u.jsx("th",{children:"Cost"})]})}),u.jsx("tbody",{children:s.length===0?u.jsx("tr",{children:u.jsxs("td",{colSpan:6,className:"empty",children:['No calls match "',l,'"']})}):s.map(i=>u.jsx(Ep,{call:i,isSelected:i.id===t,onSelect:()=>n(i.id),isNew:i.id===r},i.id))})]})})]})}function Lp({start:e}){const[,t]=L.useState(0);return L.useEffect(()=>{const n=setInterval(()=>t(r=>r+1),1e3);return()=>clearInterval(n)},[]),u.jsx(u.Fragment,{children:ml((Date.now()-e)/1e3)})}function Pp({call:e,transcript:t,onEnd:n,recording:r,setRecording:l,muted:o,setMuted:s}){const i=L.useRef(null);if(L.useEffect(()=>{i.current&&(i.current.scrollTop=i.current.scrollHeight)},[t]),!e)return u.jsxs("div",{className:"rr-card",children:[u.jsx("h3",{children:"No live call selected"}),u.jsx("div",{className:"meta",children:"Select a call from the table — or wait for the next ring."})]});const a=e.status==="live";return u.jsxs("div",{className:"rr-card",children:[u.jsxs("h3",{children:["Live call",u.jsx("span",{className:"pill "+(a?"live":"done"),children:e.status})]}),u.jsxs("div",{className:"meta",children:[u.jsx("strong",{children:e.direction==="inbound"?e.from:e.to}),u.jsx("span",{className:"sep",children:"·"}),e.agent]}),u.jsxs("div",{className:"duration-block",children:[u.jsx("span",{className:"l",children:"duration"}),u.jsxs("span",{className:"agent",children:[e.direction==="inbound"?"inbound":"outbound"," ·"," ",e.carrier==="twilio"?"Twilio":"Telnyx"]}),u.jsx("span",{className:"v",children:a&&e.durationStart?u.jsx(Lp,{start:e.durationStart}):ml(e.duration||0)})]}),u.jsx("div",{className:"transcript",ref:i,children:t.map((c,m)=>c.who==="tool"?u.jsxs("div",{className:"turn tool",children:[u.jsx("div",{className:"av",children:"⚙"}),u.jsxs("div",{className:"body",children:[u.jsxs("div",{className:"who",children:["tool · ",c.txt]}),c.args&&u.jsx("div",{className:"tool-call",children:Object.entries(c.args).map(([v,h])=>u.jsxs("span",{children:[u.jsxs("span",{className:"k",children:[v,":"]}),' "',String(h),'"'," "]},v))})]})]},m):u.jsxs("div",{className:"turn "+c.who,children:[u.jsx("div",{className:"av",children:c.who==="user"?"U":"P"}),u.jsxs("div",{className:"body",children:[u.jsxs("div",{className:"who",children:[c.who==="user"?"caller":"agent",c.typing&&" · typing"]}),u.jsx("div",{className:"txt",children:c.typing?u.jsxs("span",{className:"typing",children:[u.jsx("span",{}),u.jsx("span",{}),u.jsx("span",{})]}):c.txt}),c.lat&&!c.typing&&u.jsxs("div",{className:"lat",children:[c.lat.stt&&`stt ${c.lat.stt} ms`,c.lat.total&&`total ${c.lat.total} ms · llm ${c.lat.llm} · tts ${c.lat.tts}`]})]})]},m))}),a&&u.jsxs("div",{className:"controls",children:[u.jsxs("button",{type:"button",className:"ctrl"+(o?" active":""),onClick:()=>s(!o),children:[u.jsx(pp,{})," ",o?"unmute":"mute"]}),u.jsxs("button",{type:"button",className:"ctrl",children:[u.jsx(mp,{})," transfer"]}),u.jsxs("button",{type:"button",className:"ctrl"+(r?" active":""),onClick:()=>l(!r),children:[u.jsx(hp,{})," ",r?"stop rec":"record"]}),u.jsxs("button",{type:"button",className:"ctrl danger",onClick:n,children:[u.jsx(vp,{})," end"]})]})]})}const Tp=e=>!!e&&typeof e.latencyP95=="number",zp=e=>!!e&&(typeof e.cost.telco=="number"||typeof e.cost.llm=="number"||typeof e.cost.sttTts=="number"||typeof e.cost.total=="number");function Rp({call:e}){const[t,n]=L.useState("latency"),r=Tp(e),l=zp(e);if(!e||!r&&!l)return null;const o=t==="latency"&&!r?"cost":t==="cost"&&!l?"latency":t;return u.jsxs("div",{className:"rr-card metrics-panel",children:[u.jsx("div",{className:"metrics-panel-h",children:u.jsxs("div",{className:"seg",role:"tablist",children:[u.jsx("button",{type:"button",role:"tab","aria-selected":o==="latency",disabled:!r,className:o==="latency"?"on":"",onClick:()=>n("latency"),children:"Latency"}),u.jsx("button",{type:"button",role:"tab","aria-selected":o==="cost",disabled:!l,className:o==="cost"?"on":"",onClick:()=>n("cost"),children:"Cost"})]})}),o==="latency"&&r&&u.jsx(Dp,{call:e}),o==="cost"&&l&&u.jsx(Ip,{call:e})]})}function Dp({call:e}){const t=e.latencyP50??0,n=e.latencyP95??0;if(e.mode==="realtime"){const m=(e.turnCount??0)>=2;return u.jsxs(u.Fragment,{children:[u.jsxs("div",{className:"lat-grid",children:[u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"end-to-end p50"}),u.jsxs("div",{className:"v",children:[m&&t||"—",m&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox"+(m&&n>600?" warn":""),children:[u.jsx("div",{className:"l",children:"end-to-end p95"}),u.jsxs("div",{className:"v",children:[m&&n||"—",m&&u.jsx("span",{className:"u",children:"ms"})]})]})]}),u.jsx("div",{className:"waterfall",children:u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"e2e"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar llm",style:{left:0,width:Math.min(100,n/1e3*100)+"%"}})}),u.jsx("span",{className:"v",children:n})]})}),u.jsxs("div",{className:"wf-legend",children:[u.jsxs("span",{children:[u.jsx("i",{style:{background:"#DF9367"}}),"end-to-end"]}),u.jsx("span",{style:{marginLeft:"auto"},children:e.agent??"realtime"})]})]})}const l=e.sttAvg||0,o=e.llmAvg||0,s=e.ttsAvg||0,i=l+o+s,a=Math.max(i,800),c=(e.turnCount??0)>=2;return u.jsxs(u.Fragment,{children:[u.jsxs("div",{className:"lat-grid",children:[u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"p50"}),u.jsxs("div",{className:"v",children:[c?e.latencyP50??"—":"—",c&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox"+(c&&n>600?" warn":""),children:[u.jsx("div",{className:"l",children:"p95"}),u.jsxs("div",{className:"v",children:[c?n:"—",c&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"stt avg"}),u.jsxs("div",{className:"v",children:[e.sttAvg??"—",u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"tts avg"}),u.jsxs("div",{className:"v",children:[e.ttsAvg??"—",u.jsx("span",{className:"u",children:"ms"})]})]})]}),u.jsxs("div",{className:"waterfall",children:[u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"stt"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar stt",style:{left:0,width:l/a*100+"%"}})}),u.jsx("span",{className:"v",children:l})]}),u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"llm"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar llm",style:{left:l/a*100+"%",width:o/a*100+"%"}})}),u.jsx("span",{className:"v",children:o})]}),u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"tts"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar tts",style:{left:(l+o)/a*100+"%",width:s/a*100+"%"}})}),u.jsx("span",{className:"v",children:s})]})]}),u.jsxs("div",{className:"wf-legend",children:[u.jsxs("span",{children:[u.jsx("i",{style:{background:"#1a1a1a"}}),"stt"]}),u.jsxs("span",{children:[u.jsx("i",{style:{background:"#DF9367"}}),"llm"]}),u.jsxs("span",{children:[u.jsx("i",{style:{background:"#278EFF",opacity:.8}}),"tts"]}),u.jsxs("span",{style:{marginLeft:"auto"},children:["total ",i," ms"]})]})]})}function lo(e){if(e.length===0)return e;const t=e.replace(/(?:_(?:ws|rest|stt|tts|llm))+$/i,"");return t.charAt(0).toUpperCase()+t.slice(1)}function Ip({call:e}){const t=e.cost,n=t.telco??0,r=t.llm??0,l=t.stt??0,o=t.tts??0,s=t.sttTts??0,i=l===0&&o===0?s:0,a=t.cached??0,c=n+r+l+o+i,m=t.total??c-a,v=k=>c>0?k/c*100:0,h=e.sttProvider?`${lo(e.sttProvider)} STT${e.sttModel?` · ${e.sttModel}`:""}`:"STT",S=e.ttsProvider?`${lo(e.ttsProvider)} TTS${e.ttsModel?` · ${e.ttsModel}`:""}`:"TTS",x=e.llmModel?`${e.model?lo(e.model)+" · ":""}${e.llmModel}`:e.model||"LLM";return u.jsxs(u.Fragment,{children:[c>0&&u.jsxs("div",{className:"cost-bar",children:[u.jsx("i",{style:{background:"#cc0000",width:v(n)+"%"}}),u.jsx("i",{style:{background:"#DF9367",width:v(r)+"%"}}),u.jsx("i",{style:{background:"#1a1a1a",width:v(l+i)+"%"}}),u.jsx("i",{style:{background:"#6c6c6c",width:v(o)+"%"}})]}),n>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#cc0000"}}),e.carrier==="twilio"?"Twilio":"Telnyx"]}),u.jsx("span",{className:"v",children:ze(n)})]}),r>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#DF9367"}}),x]}),u.jsx("span",{className:"v",children:ze(r)}),a>0&&u.jsxs("span",{className:"saved",children:["−",ze(a)," cached"]})]}),l>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#1a1a1a"}}),h]}),u.jsx("span",{className:"v",children:ze(l)})]}),o>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#6c6c6c"}}),S]}),u.jsx("span",{className:"v",children:ze(o)})]}),i>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#1a1a1a"}}),"STT / TTS (legacy)"]}),u.jsx("span",{className:"v",children:ze(i)})]}),u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:["Total"," ",e.status==="live"&&u.jsx("span",{style:{fontFamily:"var(--font-mono)",fontSize:10,color:"#aaa",marginLeft:4},children:"(running)"})]}),u.jsx("span",{className:"v",children:ze(m)})]})]})}const $t=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),bt=e=>typeof e=="string"?e:"",De=e=>typeof e=="number"&&Number.isFinite(e)?e:0,ie=e=>typeof e=="number"&&Number.isFinite(e)?e:void 0,Be=e=>typeof e=="string"&&e.length>0?e:void 0;function Pr(e){if($t(e))return{stt_ms:ie(e.stt_ms),llm_ms:ie(e.llm_ms),tts_ms:ie(e.tts_ms),total_ms:ie(e.total_ms),agent_response_ms:ie(e.agent_response_ms),endpoint_ms:ie(e.endpoint_ms),user_speech_duration_ms:ie(e.user_speech_duration_ms)}}function Op(e){if($t(e))return{stt:ie(e.stt),tts:ie(e.tts),llm:ie(e.llm),telephony:ie(e.telephony),total:ie(e.total),llm_cached_savings:ie(e.llm_cached_savings)}}function Fp(e){if(!$t(e))return null;const t=e.turns;return{duration_seconds:ie(e.duration_seconds),provider_mode:Be(e.provider_mode),telephony_provider:Be(e.telephony_provider),stt_provider:Be(e.stt_provider),tts_provider:Be(e.tts_provider),llm_provider:Be(e.llm_provider),stt_model:Be(e.stt_model),tts_model:Be(e.tts_model),llm_model:Be(e.llm_model),cost:Op(e.cost),latency_avg:Pr(e.latency_avg),latency_p50:Pr(e.latency_p50),latency_p95:Pr(e.latency_p95),latency_p99:Pr(e.latency_p99),turns:Array.isArray(t)?t:void 0}}function Ap(e){if(!Array.isArray(e))return;const t=[];for(const n of e)$t(n)&&t.push({role:bt(n.role),text:bt(n.text),timestamp:De(n.timestamp)});return t}function Ec(e){if(!$t(e))return null;const t=bt(e.call_id);if(t.length===0)return null;const n=e.turns;return{call_id:t,caller:bt(e.caller),callee:bt(e.callee),direction:bt(e.direction),started_at:De(e.started_at),ended_at:ie(e.ended_at),status:Be(e.status),transcript:Ap(e.transcript),turns:Array.isArray(n)?n:void 0,metrics:Fp(e.metrics)}}function Mc(e){if(!Array.isArray(e))return[];const t=[];for(const n of e){const r=Ec(n);r&&t.push(r)}return t}function $p(e){return $t(e)?{stt:De(e.stt),tts:De(e.tts),llm:De(e.llm),telephony:De(e.telephony)}:{stt:0,tts:0,llm:0,telephony:0}}function Vp(e){return $t(e)?{total_calls:De(e.total_calls),total_cost:De(e.total_cost),avg_duration:De(e.avg_duration),avg_latency_ms:De(e.avg_latency_ms),cost_breakdown:$p(e.cost_breakdown),active_calls:De(e.active_calls)}:{total_calls:0,total_cost:0,avg_duration:0,avg_latency_ms:0,cost_breakdown:{stt:0,tts:0,llm:0,telephony:0},active_calls:0}}async function Zs(e){const t=await fetch(e,{headers:{Accept:"application/json"}});if(!t.ok)throw new Error(`Request to ${e} failed with status ${t.status}`);return t.json()}async function Up(e=50,t=0){const n=`/api/dashboard/calls?limit=${encodeURIComponent(e)}&offset=${encodeURIComponent(t)}`,r=await Zs(n);return Mc(r)}async function Hp(){const e=await Zs("/api/dashboard/active");return Mc(e)}async function Bp(){const e=await Zs("/api/dashboard/aggregates");return Vp(e)}async function Wp(e){const t=`/api/dashboard/calls/${encodeURIComponent(e)}`,n=await fetch(t,{headers:{Accept:"application/json"}});if(n.status===404)return null;if(!n.ok)throw new Error(`Request to ${t} failed with status ${n.status}`);const r=await n.json();return Ec(r)}const Qp=new Set(["in-progress","initiated"]);function Kp(e){if(!e)return"ended";switch(e){case"in-progress":case"initiated":return"live";case"completed":return"ended";case"no-answer":return"no-answer";case"busy":case"failed":case"canceled":case"webhook_error":return"fail";default:return"ended"}}function Yp(e){return e==="outbound"?"outbound":"inbound"}function Xp(e){return typeof e=="string"&&e.toLowerCase().includes("telnyx")?"telnyx":"twilio"}function Gp(e){if(typeof e!="string")return"unknown";const t=e.toLowerCase();return t.includes("realtime")?"realtime":t.includes("convai")?"convai":t.includes("pipeline")?"pipeline":"unknown"}function iu(e){return e.length===0?"—":e}function Zp(e){const t=e.metrics?.provider_mode;if(!t)return;const n=e.metrics?.llm_provider;return t.startsWith("pipeline")&&n?`${t} · ${n}`:t}function Jp(e){const t=e.metrics?.cost;if(!t)return{};const n={};return typeof t.telephony=="number"&&(n.telco=t.telephony),typeof t.llm=="number"&&(n.llm=t.llm),typeof t.stt=="number"&&(n.stt=t.stt),typeof t.tts=="number"&&(n.tts=t.tts),typeof t.llm_cached_savings=="number"&&(n.cached=t.llm_cached_savings),(n.stt!==void 0||n.tts!==void 0)&&(n.sttTts=(n.stt??0)+(n.tts??0)),n.telco===void 0&&n.llm===void 0&&n.sttTts===void 0&&typeof t.total=="number"&&(n.total=t.total),n}function qp(e,t){if(t)return;const n=e.metrics?.duration_seconds;return typeof n=="number"?n:typeof e.ended_at=="number"&&typeof e.started_at=="number"?Math.max(0,e.ended_at-e.started_at):0}function bp(e){if(typeof e.ended_at=="number")return Math.round(Date.now()/1e3-e.ended_at)}function uu(e){const t=Kp(e.status),n=t==="live"||e.status!==void 0&&Qp.has(e.status),r=e.metrics?.latency_avg,l=e.metrics?.latency_p50,o=e.metrics?.latency_p95,s=(Array.isArray(e.metrics?.turns)?e.metrics?.turns?.length:void 0)??(Array.isArray(e.transcript)?e.transcript.length:void 0);return{id:e.call_id,status:t,direction:Yp(e.direction),from:iu(e.caller),to:iu(e.callee),carrier:Xp(e.metrics?.telephony_provider),startedAtMs:typeof e.started_at=="number"?e.started_at*1e3:void 0,durationStart:n?e.started_at*1e3:void 0,duration:qp(e,n),latencyP95:o?.agent_response_ms??o?.total_ms??r?.total_ms,latencyP50:l?.agent_response_ms??l?.total_ms??r?.total_ms,sttAvg:r?.stt_ms,ttsAvg:r?.tts_ms,llmAvg:r?.llm_ms,turnCount:s,agentResponseP50:l?.agent_response_ms,agentResponseP95:o?.agent_response_ms,cost:Jp(e),agent:Zp(e),model:e.metrics?.llm_provider,mode:Gp(e.metrics?.provider_mode),sttProvider:e.metrics?.stt_provider,ttsProvider:e.metrics?.tts_provider,sttModel:e.metrics?.stt_model,ttsModel:e.metrics?.tts_model,llmModel:e.metrics?.llm_model,transcriptKey:e.call_id,endedAgo:bp(e)}}function em(e){const t=e.transcript;if(t&&t.length>0){const l=[];for(const o of t){const s=o.text;switch(o.role){case"user":l.push({who:"user",txt:s});break;case"assistant":l.push({who:"bot",txt:s});break;case"tool":l.push({who:"tool",txt:s});break;default:l.push({who:"bot",txt:s});break}}return l}const n=e.turns;if(!n||n.length===0)return[];const r=[];for(const l of n){if(typeof l!="object"||l===null)continue;const o=l,s=typeof o.user_text=="string"?o.user_text:"",i=typeof o.agent_text=="string"?o.agent_text:"";s.length>0&&r.push({who:"user",txt:s}),i.length>0&&i!=="[interrupted]"&&r.push({who:"bot",txt:i})}return r}const Lc=60*1e3,Pc=60*Lc,oo=24*Pc;function tm(e,t=Date.now()){switch(e){case"1h":{const n=5*Lc,r=Math.ceil(t/n)*n,l=r-12*n;return{count:12,bucketSizeMs:n,window:{fromMs:l,toMs:r}}}case"24h":{const n=Pc,r=Math.ceil(t/n)*n,l=r-24*n;return{count:24,bucketSizeMs:n,window:{fromMs:l,toMs:r}}}case"7d":{const n=new Date(t);n.setHours(0,0,0,0);const r=n.getTime()+oo,l=r-7*oo;return{count:7,bucketSizeMs:oo,window:{fromMs:l,toMs:r}}}case"All":default:return{count:9,bucketSizeMs:0,window:{fromMs:0,toMs:t}}}}function nm(e,t){const{fromMs:n,toMs:r}=t;return e.filter(l=>{const o=ts(l);return typeof o!="number"?!1:o>=n&&o<=r})}function ts(e){if(typeof e.startedAtMs=="number")return e.startedAtMs;if(typeof e.durationStart=="number")return e.durationStart;if(typeof e.endedAgo=="number")return Date.now()-e.endedAgo*1e3}function rm(e){const t=e.cost,n=(t.telco??0)+(t.llm??0)+(t.sttTts??0);return n>0?n:t.total??0}function lm(e){const t=e.reduce((n,r)=>r>n?r:n,0);return t<=0?e.map(()=>0):e.map(n=>Math.round(n/t*100))}function Tr(e,t,n=9,r){const l=typeof n=="object",o=l?n.count:n,s=Math.max(1,Math.floor(o)),i=l?n.window:r,a=l?n.bucketSizeMs:0;let c,m;if(i)c=i.fromMs,m=i.toMs;else{const d=[];for(const f of e){const p=ts(f);typeof p=="number"&&d.push(p)}if(d.length===0){const f=Date.now();return{heights:new Array(s).fill(0),buckets:new Array(s).fill(null).map(()=>[]),window:{fromMs:f,toMs:f},bucketSizeMs:0}}c=Math.min(...d),m=Math.max(...d)}const v=Math.max(1,m-c),h=a>0?a:v/s,S=new Array(s).fill(null).map(()=>[]),x=new Array(s).fill(0),k=new Array(s).fill(0);for(const d of e){const f=ts(d);if(typeof f!="number"||fm)continue;let p=Math.floor((f-c)/h);p>=s&&(p=s-1),p<0&&(p=0),S[p].push(d),t==="totalCalls"?x[p]+=1:t==="latency"?typeof d.latencyP95=="number"&&(x[p]+=d.latencyP95,k[p]+=1):x[p]+=rm(d)}const R=t==="latency"?x.map((d,f)=>k[f]>0?d/k[f]:0):x;return{heights:lm(R),buckets:S,window:{fromMs:c,toMs:m},bucketSizeMs:h}}function om(e,t){const n=new Set,r=[];for(const l of e)n.has(l.call_id)||(n.add(l.call_id),r.push(uu(l)));for(const l of t)n.has(l.call_id)||(n.add(l.call_id),r.push(uu(l)));return r}function sm(e,t){const n=new Map(e.map(o=>[o.id,o])),r=new Set(t.map(o=>o.id)),l=t.map(o=>{const s=n.get(o.id);return s?{...s,...o,latencyP95:o.latencyP95??s.latencyP95,latencyP50:o.latencyP50??s.latencyP50,sttAvg:o.sttAvg??s.sttAvg,ttsAvg:o.ttsAvg??s.ttsAvg,llmAvg:o.llmAvg??s.llmAvg,turnCount:o.turnCount??s.turnCount,agentResponseP50:o.agentResponseP50??s.agentResponseP50,agentResponseP95:o.agentResponseP95??s.agentResponseP95,cost:{...s.cost,...o.cost}}:o});for(const o of e)r.has(o.id)||l.push(o);return l}const im=1e3,um=3e4,am=5,cm=5e3,fm=["call_start","call_initiated","call_status","call_end"];function au(e){return e instanceof Error?e.message:"Unknown error"}function dm(){const[e,t]=L.useState([]),[n,r]=L.useState(null),[l,o]=L.useState(!1),[s,i]=L.useState(null),a=L.useRef(!0),c=L.useRef(null),m=L.useRef(null),v=L.useRef(null),h=L.useRef(0),S=L.useCallback(()=>{m.current!==null&&(clearTimeout(m.current),m.current=null)},[]),x=L.useCallback(()=>{v.current!==null&&(clearInterval(v.current),v.current=null)},[]),k=L.useCallback(()=>{c.current!==null&&(c.current.close(),c.current=null)},[]),R=L.useCallback(async()=>{try{const[C,N,E]=await Promise.all([Hp(),Up(50,0),Bp()]);if(!a.current)return;t(F=>sm(F,om(C,N))),r(E),i(null)}catch(C){if(!a.current)return;i(au(C))}},[]),d=L.useCallback(()=>{v.current===null&&(v.current=setInterval(()=>{R()},cm))},[R]),f=L.useRef(()=>{}),p=L.useCallback(()=>{if(S(),h.current>=am){d();return}const C=h.current,N=Math.min(um,im*Math.pow(2,C));h.current=C+1,m.current=setTimeout(()=>{m.current=null,a.current&&f.current()},N)},[S,d]),y=L.useCallback(()=>{R()},[R]),_=L.useCallback(()=>{k();let C;try{C=new EventSource("/api/dashboard/events")}catch(N){i(au(N)),p();return}c.current=C,C.onopen=()=>{a.current&&(h.current=0,x(),o(!0))},C.onerror=()=>{a.current&&(o(!1),k(),p())};for(const N of fm)C.addEventListener(N,y);C.addEventListener("turn_complete",y)},[k,x,y,p]);return L.useEffect(()=>{f.current=_},[_]),L.useEffect(()=>(a.current=!0,R(),_(),()=>{a.current=!1,S(),x(),k()}),[]),{calls:e,aggregates:n,isStreaming:l,error:s,refresh:R}}const pm=2e3;function mm(e,t){const[n,r]=L.useState([]),l=L.useRef(!0);return L.useEffect(()=>(l.current=!0,()=>{l.current=!1}),[]),L.useEffect(()=>{if(!e){r([]);return}let o=!1,s=null,i=null;const a=async()=>{try{const m=await Wp(e);if(o||!l.current)return;if(m===null){r([]);return}r(em(m))}catch{}};a();const c=m=>{const v=m;try{return JSON.parse(v.data)?.call_id===e}catch{return!1}};try{i=new EventSource("/api/dashboard/events"),i.addEventListener("turn_complete",m=>{c(m)&&a()}),i.addEventListener("call_end",m=>{c(m)&&a()})}catch{i=null}return t&&(s=setInterval(()=>{a()},pm)),()=>{o=!0,s!==null&&clearInterval(s),i!==null&&i.close()}},[e,t]),n}const cu="0.6.0",so={"1h":"1h","24h":"24h","7d":"7d",All:"all-time"};function hm(e){const t=e.filter(r=>typeof r.latencyP95=="number");if(t.length===0)return 0;const n=t.reduce((r,l)=>r+(l.latencyP95??0),0);return Math.round(n/t.length)}function vm(e){return e.reduce((t,n)=>{if(typeof n.cost.total=="number")return t+n.cost.total;const r=(n.cost.telco??0)+(n.cost.llm??0)+(n.cost.sttTts??0);return t+r},0)}function ym(e){const n=e.find(l=>l.status==="live")??e[0];if(!n)return"";const r=n.direction==="inbound"?n.to:n.from;return r&&r!=="—"?r:""}function gm(){const{calls:e,aggregates:t,isStreaming:n,error:r,refresh:l}=dm(),[o,s]=L.useState(null),[i,a]=L.useState(""),[c,m]=L.useState("24h"),[v,h]=L.useState(!0),[S,x]=L.useState(!1),k=L.useMemo(()=>tm(c),[c]),R=k.window,d=L.useMemo(()=>{if(c==="All")return e;const w=new Set(nm(e,R).map(M=>M.id));return e.filter(M=>M.status==="live"||w.has(M.id))},[e,c,R]);L.useEffect(()=>{if(o!==null)return;const w=d.find(M=>M.status==="live")??d[0];w&&s(w.id)},[d,o]),L.useEffect(()=>{o!==null&&(d.some(w=>w.id===o)||s(null))},[d,o]),L.useEffect(()=>{const w=M=>{if(!(M.shiftKey&&M.key.toLowerCase()==="k"||M.metaKey&&M.key.toLowerCase()==="k"))return;M.preventDefault(),document.querySelector(".panel-h .search input")?.focus()};return window.addEventListener("keydown",w),()=>window.removeEventListener("keydown",w)},[]);const f=L.useMemo(()=>d.find(w=>w.id===o)??null,[d,o]),p=f?.status==="live",y=mm(f?.id??null,p),_=L.useMemo(()=>e.filter(w=>w.status==="live").length,[e]),C=L.useMemo(()=>e.filter(w=>w.status==="live"&&w.direction==="inbound").length,[e]),N=_-C,E=d.length,F=hm(d)||t?.avg_latency_ms||0,T=vm(d)||t?.total_cost||0,ve=ym(e),et=L.useMemo(()=>Tr(d,"totalCalls",k),[d,k]),tt=L.useMemo(()=>Tr(d,"latency",k),[d,k]),yn=L.useMemo(()=>Tr(d,"spend",k),[d,k]),ur=L.useMemo(()=>{const w=e.filter(M=>M.status==="live");return Tr(w,"totalCalls",k)},[e,k]),nt=w=>w.heights.map((M,P)=>({height:M,calls:w.buckets[P],fromMs:w.window.fromMs+P*w.bucketSizeMs,toMs:w.window.fromMs+(P+1)*w.bucketSizeMs})),gn=()=>{f&&l().catch(()=>{})};return u.jsxs(u.Fragment,{children:[u.jsx(cp,{liveCount:_,todayCount:E,phoneNumber:ve,sdkVersion:cu}),u.jsxs("div",{className:"page",children:[u.jsx(wp,{range:c,setRange:w=>m(w)}),u.jsxs("div",{className:"metrics",children:[u.jsx(Lr,{label:`Calls · ${so[c]}`,value:E,spark:et.heights,buckets:nt(et),onSelectCall:s,kind:"count"}),u.jsx(Lr,{label:"Avg latency p95",value:F||0,unit:"ms",spark:tt.heights,buckets:nt(tt),onSelectCall:s,kind:"latency"}),u.jsx(Lr,{label:`Spend · ${so[c]}`,value:ze(T),spark:yn.heights,buckets:nt(yn),onSelectCall:s,kind:"spend"}),u.jsx(Lr,{label:"Active now",value:_,peach:!0,badge:!0,footer:`${C} inbound · ${N} outbound`,spark:ur.heights,buckets:nt(ur),onSelectCall:s,kind:"count"})]}),u.jsxs("div",{className:"split",children:[u.jsx(Mp,{calls:d,selectedId:o,onSelect:s,newId:null,search:i,setSearch:a}),u.jsxs("div",{className:"rr",children:[u.jsx(Pp,{call:f,transcript:y,onEnd:gn,recording:v,setRecording:h,muted:S,setMuted:x}),u.jsx(Rp,{call:f})]})]}),u.jsxs("div",{className:"statusbar",children:[u.jsxs("div",{className:"group",children:[u.jsx("span",{className:n?"green":"",children:n?"streaming · sse":r?`error · ${r}`:"idle"}),u.jsxs("span",{children:["SDK · ",cu]})]}),u.jsx("div",{className:"group",children:u.jsxs("span",{children:[_," live · ",E," ",so[c]]})})]})]})]})}const Tc=document.getElementById("root");if(!Tc)throw new Error("Patter dashboard: #root element missing");io.createRoot(Tc).render(u.jsx(Xc.StrictMode,{children:u.jsx(gm,{})})); diff --git a/libraries/typescript/package-lock.json b/libraries/typescript/package-lock.json index 10b4935e..7052d901 100644 --- a/libraries/typescript/package-lock.json +++ b/libraries/typescript/package-lock.json @@ -1,12 +1,12 @@ { "name": "getpatter", - "version": "0.6.0", + "version": "0.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "getpatter", - "version": "0.6.0", + "version": "0.6.1", "license": "MIT", "dependencies": { "express": "^5.2.1", diff --git a/libraries/typescript/src/dashboard/ui.html b/libraries/typescript/src/dashboard/ui.html index 375ad9cc..c65888e3 100644 --- a/libraries/typescript/src/dashboard/ui.html +++ b/libraries/typescript/src/dashboard/ui.html @@ -54,7 +54,7 @@ `+l[s].replace(" at new "," at ");return e.displayName&&a.includes("")&&(a=a.replace("",e.displayName)),a}while(1<=s&&0<=i);break}}}finally{Dl=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?En(e):""}function uf(e){switch(e.tag){case 5:return En(e.type);case 16:return En("Lazy");case 13:return En("Suspense");case 19:return En("SuspenseList");case 0:case 2:case 15:return e=Il(e.type,!1),e;case 11:return e=Il(e.type.render,!1),e;case 1:return e=Il(e.type,!0),e;default:return""}}function po(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Ht:return"Fragment";case Ut:return"Portal";case ao:return"Profiler";case as:return"StrictMode";case co:return"Suspense";case fo:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Eu:return(e.displayName||"Context")+".Consumer";case ju:return(e._context.displayName||"Context")+".Provider";case cs:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case fs:return t=e.displayName||null,t!==null?t:po(e.type)||"Memo";case lt:t=e._payload,e=e._init;try{return po(e(t))}catch{}}return null}function af(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return po(t);case 8:return t===as?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function gt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Lu(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function cf(e){var t=Lu(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(s){r=""+s,o.call(this,s)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(s){r=""+s},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function pr(e){e._valueTracker||(e._valueTracker=cf(e))}function Pu(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Lu(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Wr(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function mo(e,t){var n=t.checked;return B({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function ri(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=gt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Tu(e,t){t=t.checked,t!=null&&us(e,"checked",t,!1)}function ho(e,t){Tu(e,t);var n=gt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?vo(e,t.type,n):t.hasOwnProperty("defaultValue")&&vo(e,t.type,gt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function li(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function vo(e,t,n){(t!=="number"||Wr(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Mn=Array.isArray;function en(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=mr.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Un(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Tn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},ff=["Webkit","ms","Moz","O"];Object.keys(Tn).forEach(function(e){ff.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Tn[t]=Tn[e]})});function Iu(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Tn.hasOwnProperty(e)&&Tn[e]?(""+t).trim():t+"px"}function Ou(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=Iu(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var df=B({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function wo(e,t){if(t){if(df[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(g(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(g(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(g(61))}if(t.style!=null&&typeof t.style!="object")throw Error(g(62))}}function xo(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var ko=null;function ds(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var So=null,tn=null,nn=null;function ii(e){if(e=sr(e)){if(typeof So!="function")throw Error(g(280));var t=e.stateNode;t&&(t=xl(t),So(e.stateNode,e.type,t))}}function Fu(e){tn?nn?nn.push(e):nn=[e]:tn=e}function Au(){if(tn){var e=tn,t=nn;if(nn=tn=null,ii(e),t)for(e=0;e>>=0,e===0?32:31-(Cf(e)/_f|0)|0}var hr=64,vr=4194304;function Ln(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Xr(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,o=e.pingedLanes,s=n&268435455;if(s!==0){var i=s&~l;i!==0?r=Ln(i):(o&=s,o!==0&&(r=Ln(o)))}else s=n&~l,s!==0?r=Ln(s):o!==0&&(r=Ln(o));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,o=t&-t,l>=o||l===16&&(o&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function lr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Ie(t),e[t]=n}function Mf(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Rn),vi=" ",yi=!1;function la(e,t){switch(e){case"keyup":return nd.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function oa(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Bt=!1;function ld(e,t){switch(e){case"compositionend":return oa(t);case"keypress":return t.which!==32?null:(yi=!0,vi);case"textInput":return e=t.data,e===vi&&yi?null:e;default:return null}}function od(e,t){if(Bt)return e==="compositionend"||!xs&&la(e,t)?(e=na(),Ir=ys=ut=null,Bt=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=ki(n)}}function aa(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?aa(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function ca(){for(var e=window,t=Wr();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Wr(e.document)}return t}function ks(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function md(e){var t=ca(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&aa(n.ownerDocument.documentElement,n)){if(r!==null&&ks(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,o=Math.min(r.start,l);r=r.end===void 0?o:Math.min(r.end,l),!e.extend&&o>r&&(l=r,r=o,o=l),l=Si(n,o);var s=Si(n,r);l&&s&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==s.node||e.focusOffset!==s.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),o>r?(e.addRange(t),e.extend(s.node,s.offset)):(t.setEnd(s.node,s.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Wt=null,Mo=null,In=null,Lo=!1;function Ci(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Lo||Wt==null||Wt!==Wr(r)||(r=Wt,"selectionStart"in r&&ks(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),In&&Yn(In,r)||(In=r,r=Jr(Mo,"onSelect"),0Yt||(e.current=Io[Yt],Io[Yt]=null,Yt--)}function O(e,t){Yt++,Io[Yt]=e.current,e.current=t}var wt={},oe=kt(wt),pe=kt(!1),zt=wt;function un(e,t){var n=e.type.contextTypes;if(!n)return wt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},o;for(o in n)l[o]=t[o];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function me(e){return e=e.childContextTypes,e!=null}function br(){$(pe),$(oe)}function Pi(e,t,n){if(oe.current!==wt)throw Error(g(168));O(oe,t),O(pe,n)}function wa(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(g(108,af(e)||"Unknown",l));return B({},n,r)}function el(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||wt,zt=oe.current,O(oe,e),O(pe,pe.current),!0}function Ti(e,t,n){var r=e.stateNode;if(!r)throw Error(g(169));n?(e=wa(e,t,zt),r.__reactInternalMemoizedMergedChildContext=e,$(pe),$(oe),O(oe,e)):$(pe),O(pe,n)}var Qe=null,kl=!1,Gl=!1;function xa(e){Qe===null?Qe=[e]:Qe.push(e)}function jd(e){kl=!0,xa(e)}function St(){if(!Gl&&Qe!==null){Gl=!0;var e=0,t=I;try{var n=Qe;for(I=1;e>=s,l-=s,Ke=1<<32-Ie(t)+l|n<E?(F=N,N=null):F=N.sibling;var T=h(d,N,p[E],y);if(T===null){N===null&&(N=F);break}e&&N&&T.alternate===null&&t(d,N),f=o(T,f,E),C===null?_=T:C.sibling=T,C=T,N=F}if(E===p.length)return n(d,N),V&&Nt(d,E),_;if(N===null){for(;EE?(F=N,N=null):F=N.sibling;var ve=h(d,N,T.value,y);if(ve===null){N===null&&(N=F);break}e&&N&&ve.alternate===null&&t(d,N),f=o(ve,f,E),C===null?_=ve:C.sibling=ve,C=ve,N=F}if(T.done)return n(d,N),V&&Nt(d,E),_;if(N===null){for(;!T.done;E++,T=p.next())T=v(d,T.value,y),T!==null&&(f=o(T,f,E),C===null?_=T:C.sibling=T,C=T);return V&&Nt(d,E),_}for(N=r(d,N);!T.done;E++,T=p.next())T=S(N,d,E,T.value,y),T!==null&&(e&&T.alternate!==null&&N.delete(T.key===null?E:T.key),f=o(T,f,E),C===null?_=T:C.sibling=T,C=T);return e&&N.forEach(function(et){return t(d,et)}),V&&Nt(d,E),_}function R(d,f,p,y){if(typeof p=="object"&&p!==null&&p.type===Ht&&p.key===null&&(p=p.props.children),typeof p=="object"&&p!==null){switch(p.$$typeof){case dr:e:{for(var _=p.key,C=f;C!==null;){if(C.key===_){if(_=p.type,_===Ht){if(C.tag===7){n(d,C.sibling),f=l(C,p.props.children),f.return=d,d=f;break e}}else if(C.elementType===_||typeof _=="object"&&_!==null&&_.$$typeof===lt&&Di(_)===C.type){n(d,C.sibling),f=l(C,p.props),f.ref=_n(d,C,p),f.return=d,d=f;break e}n(d,C);break}else t(d,C);C=C.sibling}p.type===Ht?(f=Tt(p.props.children,d.mode,y,p.key),f.return=d,d=f):(y=Br(p.type,p.key,p.props,null,d.mode,y),y.ref=_n(d,f,p),y.return=d,d=y)}return s(d);case Ut:e:{for(C=p.key;f!==null;){if(f.key===C)if(f.tag===4&&f.stateNode.containerInfo===p.containerInfo&&f.stateNode.implementation===p.implementation){n(d,f.sibling),f=l(f,p.children||[]),f.return=d,d=f;break e}else{n(d,f);break}else t(d,f);f=f.sibling}f=ro(p,d.mode,y),f.return=d,d=f}return s(d);case lt:return C=p._init,R(d,f,C(p._payload),y)}if(Mn(p))return x(d,f,p,y);if(wn(p))return k(d,f,p,y);Cr(d,p)}return typeof p=="string"&&p!==""||typeof p=="number"?(p=""+p,f!==null&&f.tag===6?(n(d,f.sibling),f=l(f,p),f.return=d,d=f):(n(d,f),f=no(p,d.mode,y),f.return=d,d=f),s(d)):n(d,f)}return R}var cn=_a(!0),Na=_a(!1),rl=kt(null),ll=null,Zt=null,Ns=null;function js(){Ns=Zt=ll=null}function Es(e){var t=rl.current;$(rl),e._currentValue=t}function Ao(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function ln(e,t){ll=e,Ns=Zt=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(de=!0),e.firstContext=null)}function Ee(e){var t=e._currentValue;if(Ns!==e)if(e={context:e,memoizedValue:t,next:null},Zt===null){if(ll===null)throw Error(g(308));Zt=e,ll.dependencies={lanes:0,firstContext:e}}else Zt=Zt.next=e;return t}var Mt=null;function Ms(e){Mt===null?Mt=[e]:Mt.push(e)}function ja(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Ms(t)):(n.next=l.next,l.next=n),t.interleaved=n,Je(e,r)}function Je(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var ot=!1;function Ls(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Ea(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Xe(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function mt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,D&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Je(e,n)}return l=r.interleaved,l===null?(t.next=t,Ms(r)):(t.next=l.next,l.next=t),r.interleaved=t,Je(e,n)}function Fr(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ms(e,n)}}function Ii(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,o=null;if(n=n.firstBaseUpdate,n!==null){do{var s={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};o===null?l=o=s:o=o.next=s,n=n.next}while(n!==null);o===null?l=o=t:o=o.next=t}else l=o=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:o,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ol(e,t,n,r){var l=e.updateQueue;ot=!1;var o=l.firstBaseUpdate,s=l.lastBaseUpdate,i=l.shared.pending;if(i!==null){l.shared.pending=null;var a=i,c=a.next;a.next=null,s===null?o=c:s.next=c,s=a;var m=e.alternate;m!==null&&(m=m.updateQueue,i=m.lastBaseUpdate,i!==s&&(i===null?m.firstBaseUpdate=c:i.next=c,m.lastBaseUpdate=a))}if(o!==null){var v=l.baseState;s=0,m=c=a=null,i=o;do{var h=i.lane,S=i.eventTime;if((r&h)===h){m!==null&&(m=m.next={eventTime:S,lane:0,tag:i.tag,payload:i.payload,callback:i.callback,next:null});e:{var x=e,k=i;switch(h=t,S=n,k.tag){case 1:if(x=k.payload,typeof x=="function"){v=x.call(S,v,h);break e}v=x;break e;case 3:x.flags=x.flags&-65537|128;case 0:if(x=k.payload,h=typeof x=="function"?x.call(S,v,h):x,h==null)break e;v=B({},v,h);break e;case 2:ot=!0}}i.callback!==null&&i.lane!==0&&(e.flags|=64,h=l.effects,h===null?l.effects=[i]:h.push(i))}else S={eventTime:S,lane:h,tag:i.tag,payload:i.payload,callback:i.callback,next:null},m===null?(c=m=S,a=v):m=m.next=S,s|=h;if(i=i.next,i===null){if(i=l.shared.pending,i===null)break;h=i,i=h.next,h.next=null,l.lastBaseUpdate=h,l.shared.pending=null}}while(!0);if(m===null&&(a=v),l.baseState=a,l.firstBaseUpdate=c,l.lastBaseUpdate=m,t=l.shared.interleaved,t!==null){l=t;do s|=l.lane,l=l.next;while(l!==t)}else o===null&&(l.shared.lanes=0);It|=s,e.lanes=s,e.memoizedState=v}}function Oi(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Jl.transition;Jl.transition={};try{e(!1),t()}finally{I=n,Jl.transition=r}}function Wa(){return Me().memoizedState}function Pd(e,t,n){var r=vt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},Qa(e))Ka(t,n);else if(n=ja(e,t,n,r),n!==null){var l=ue();Oe(n,e,r,l),Ya(n,t,r)}}function Td(e,t,n){var r=vt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(Qa(e))Ka(t,l);else{var o=e.alternate;if(e.lanes===0&&(o===null||o.lanes===0)&&(o=t.lastRenderedReducer,o!==null))try{var s=t.lastRenderedState,i=o(s,n);if(l.hasEagerState=!0,l.eagerState=i,Fe(i,s)){var a=t.interleaved;a===null?(l.next=l,Ms(t)):(l.next=a.next,a.next=l),t.interleaved=l;return}}catch{}finally{}n=ja(e,t,l,r),n!==null&&(l=ue(),Oe(n,e,r,l),Ya(n,t,r))}}function Qa(e){var t=e.alternate;return e===H||t!==null&&t===H}function Ka(e,t){On=il=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ya(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ms(e,n)}}var ul={readContext:Ee,useCallback:ne,useContext:ne,useEffect:ne,useImperativeHandle:ne,useInsertionEffect:ne,useLayoutEffect:ne,useMemo:ne,useReducer:ne,useRef:ne,useState:ne,useDebugValue:ne,useDeferredValue:ne,useTransition:ne,useMutableSource:ne,useSyncExternalStore:ne,useId:ne,unstable_isNewReconciler:!1},zd={readContext:Ee,useCallback:function(e,t){return $e().memoizedState=[e,t===void 0?null:t],e},useContext:Ee,useEffect:Ai,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,$r(4194308,4,$a.bind(null,t,e),n)},useLayoutEffect:function(e,t){return $r(4194308,4,e,t)},useInsertionEffect:function(e,t){return $r(4,2,e,t)},useMemo:function(e,t){var n=$e();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=$e();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Pd.bind(null,H,e),[r.memoizedState,e]},useRef:function(e){var t=$e();return e={current:e},t.memoizedState=e},useState:Fi,useDebugValue:Fs,useDeferredValue:function(e){return $e().memoizedState=e},useTransition:function(){var e=Fi(!1),t=e[0];return e=Ld.bind(null,e[1]),$e().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=H,l=$e();if(V){if(n===void 0)throw Error(g(407));n=n()}else{if(n=t(),q===null)throw Error(g(349));Dt&30||Ta(r,t,n)}l.memoizedState=n;var o={value:n,getSnapshot:t};return l.queue=o,Ai(Ra.bind(null,r,o,e),[e]),r.flags|=2048,tr(9,za.bind(null,r,o,n,t),void 0,null),n},useId:function(){var e=$e(),t=q.identifierPrefix;if(V){var n=Ye,r=Ke;n=(r&~(1<<32-Ie(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=bn++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=s.createElement(n,{is:r.is}):(e=s.createElement(n),n==="select"&&(s=e,r.multiple?s.multiple=!0:r.size&&(s.size=r.size))):e=s.createElementNS(e,n),e[Ve]=t,e[Zn]=r,rc(e,t,!1,!1),t.stateNode=e;e:{switch(s=xo(n,r),n){case"dialog":A("cancel",e),A("close",e),l=r;break;case"iframe":case"object":case"embed":A("load",e),l=r;break;case"video":case"audio":for(l=0;lpn&&(t.flags|=128,r=!0,Nn(o,!1),t.lanes=4194304)}else{if(!r)if(e=sl(s),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Nn(o,!0),o.tail===null&&o.tailMode==="hidden"&&!s.alternate&&!V)return re(t),null}else 2*K()-o.renderingStartTime>pn&&n!==1073741824&&(t.flags|=128,r=!0,Nn(o,!1),t.lanes=4194304);o.isBackwards?(s.sibling=t.child,t.child=s):(n=o.last,n!==null?n.sibling=s:t.child=s,o.last=s)}return o.tail!==null?(t=o.tail,o.rendering=t,o.tail=t.sibling,o.renderingStartTime=K(),t.sibling=null,n=U.current,O(U,r?n&1|2:n&1),t):(re(t),null);case 22:case 23:return Bs(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?ye&1073741824&&(re(t),t.subtreeFlags&6&&(t.flags|=8192)):re(t),null;case 24:return null;case 25:return null}throw Error(g(156,t.tag))}function Vd(e,t){switch(Cs(t),t.tag){case 1:return me(t.type)&&br(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return fn(),$(pe),$(oe),zs(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Ts(t),null;case 13:if($(U),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(g(340));an()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return $(U),null;case 4:return fn(),null;case 10:return Es(t.type._context),null;case 22:case 23:return Bs(),null;case 24:return null;default:return null}}var Nr=!1,le=!1,Ud=typeof WeakSet=="function"?WeakSet:Set,j=null;function Jt(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Q(e,t,r)}else n.current=null}function Yo(e,t,n){try{n()}catch(r){Q(e,t,r)}}var Gi=!1;function Hd(e,t){if(Po=Gr,e=ca(),ks(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break e}var s=0,i=-1,a=-1,c=0,m=0,v=e,h=null;t:for(;;){for(var S;v!==n||l!==0&&v.nodeType!==3||(i=s+l),v!==o||r!==0&&v.nodeType!==3||(a=s+r),v.nodeType===3&&(s+=v.nodeValue.length),(S=v.firstChild)!==null;)h=v,v=S;for(;;){if(v===e)break t;if(h===n&&++c===l&&(i=s),h===o&&++m===r&&(a=s),(S=v.nextSibling)!==null)break;v=h,h=v.parentNode}v=S}n=i===-1||a===-1?null:{start:i,end:a}}else n=null}n=n||{start:0,end:0}}else n=null;for(To={focusedElem:e,selectionRange:n},Gr=!1,j=t;j!==null;)if(t=j,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,j=e;else for(;j!==null;){t=j;try{var x=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(x!==null){var k=x.memoizedProps,R=x.memoizedState,d=t.stateNode,f=d.getSnapshotBeforeUpdate(t.elementType===t.type?k:Pe(t.type,k),R);d.__reactInternalSnapshotBeforeUpdate=f}break;case 3:var p=t.stateNode.containerInfo;p.nodeType===1?p.textContent="":p.nodeType===9&&p.documentElement&&p.removeChild(p.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(g(163))}}catch(y){Q(t,t.return,y)}if(e=t.sibling,e!==null){e.return=t.return,j=e;break}j=t.return}return x=Gi,Gi=!1,x}function Fn(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var o=l.destroy;l.destroy=void 0,o!==void 0&&Yo(t,n,o)}l=l.next}while(l!==r)}}function _l(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Xo(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function sc(e){var t=e.alternate;t!==null&&(e.alternate=null,sc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ve],delete t[Zn],delete t[Do],delete t[_d],delete t[Nd])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function ic(e){return e.tag===5||e.tag===3||e.tag===4}function Zi(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||ic(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Go(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=qr));else if(r!==4&&(e=e.child,e!==null))for(Go(e,t,n),e=e.sibling;e!==null;)Go(e,t,n),e=e.sibling}function Zo(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Zo(e,t,n),e=e.sibling;e!==null;)Zo(e,t,n),e=e.sibling}var b=null,Te=!1;function rt(e,t,n){for(n=n.child;n!==null;)uc(e,t,n),n=n.sibling}function uc(e,t,n){if(Ue&&typeof Ue.onCommitFiberUnmount=="function")try{Ue.onCommitFiberUnmount(vl,n)}catch{}switch(n.tag){case 5:le||Jt(n,t);case 6:var r=b,l=Te;b=null,rt(e,t,n),b=r,Te=l,b!==null&&(Te?(e=b,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):b.removeChild(n.stateNode));break;case 18:b!==null&&(Te?(e=b,n=n.stateNode,e.nodeType===8?Xl(e.parentNode,n):e.nodeType===1&&Xl(e,n),Qn(e)):Xl(b,n.stateNode));break;case 4:r=b,l=Te,b=n.stateNode.containerInfo,Te=!0,rt(e,t,n),b=r,Te=l;break;case 0:case 11:case 14:case 15:if(!le&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var o=l,s=o.destroy;o=o.tag,s!==void 0&&(o&2||o&4)&&Yo(n,t,s),l=l.next}while(l!==r)}rt(e,t,n);break;case 1:if(!le&&(Jt(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(i){Q(n,t,i)}rt(e,t,n);break;case 21:rt(e,t,n);break;case 22:n.mode&1?(le=(r=le)||n.memoizedState!==null,rt(e,t,n),le=r):rt(e,t,n);break;default:rt(e,t,n)}}function Ji(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Ud),t.forEach(function(r){var l=Jd.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function Le(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=s),r&=~o}if(r=l,r=K()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Wd(r/1960))-r,10e?16:e,at===null)var r=!1;else{if(e=at,at=null,fl=0,D&6)throw Error(g(331));var l=D;for(D|=4,j=e.current;j!==null;){var o=j,s=o.child;if(j.flags&16){var i=o.deletions;if(i!==null){for(var a=0;aK()-Us?Pt(e,0):Vs|=n),he(e,t)}function vc(e,t){t===0&&(e.mode&1?(t=vr,vr<<=1,!(vr&130023424)&&(vr=4194304)):t=1);var n=ue();e=Je(e,t),e!==null&&(lr(e,t,n),he(e,n))}function Zd(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),vc(e,n)}function Jd(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(g(314))}r!==null&&r.delete(t),vc(e,n)}var yc;yc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||pe.current)de=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return de=!1,Ad(e,t,n);de=!!(e.flags&131072)}else de=!1,V&&t.flags&1048576&&ka(t,nl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Vr(e,t),e=t.pendingProps;var l=un(t,oe.current);ln(t,n),l=Ds(null,t,r,e,l,n);var o=Is();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,me(r)?(o=!0,el(t)):o=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ls(t),l.updater=Cl,t.stateNode=l,l._reactInternals=t,Vo(t,r,e,n),t=Bo(null,t,r,!0,o,n)):(t.tag=0,V&&o&&Ss(t),se(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Vr(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=bd(r),e=Pe(r,e),l){case 0:t=Ho(null,t,r,e,n);break e;case 1:t=Ki(null,t,r,e,n);break e;case 11:t=Wi(null,t,r,e,n);break e;case 14:t=Qi(null,t,r,Pe(r.type,e),n);break e}throw Error(g(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Ho(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Ki(e,t,r,l,n);case 3:e:{if(ec(t),e===null)throw Error(g(387));r=t.pendingProps,o=t.memoizedState,l=o.element,Ea(e,t),ol(t,r,null,n);var s=t.memoizedState;if(r=s.element,o.isDehydrated)if(o={element:r,isDehydrated:!1,cache:s.cache,pendingSuspenseBoundaries:s.pendingSuspenseBoundaries,transitions:s.transitions},t.updateQueue.baseState=o,t.memoizedState=o,t.flags&256){l=dn(Error(g(423)),t),t=Yi(e,t,r,n,l);break e}else if(r!==l){l=dn(Error(g(424)),t),t=Yi(e,t,r,n,l);break e}else for(ge=pt(t.stateNode.containerInfo.firstChild),we=t,V=!0,Re=null,n=Na(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(an(),r===l){t=qe(e,t,n);break e}se(e,t,r,n)}t=t.child}return t;case 5:return Ma(t),e===null&&Fo(t),r=t.type,l=t.pendingProps,o=e!==null?e.memoizedProps:null,s=l.children,zo(r,l)?s=null:o!==null&&zo(r,o)&&(t.flags|=32),ba(e,t),se(e,t,s,n),t.child;case 6:return e===null&&Fo(t),null;case 13:return tc(e,t,n);case 4:return Ps(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=cn(t,null,r,n):se(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Wi(e,t,r,l,n);case 7:return se(e,t,t.pendingProps,n),t.child;case 8:return se(e,t,t.pendingProps.children,n),t.child;case 12:return se(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,o=t.memoizedProps,s=l.value,O(rl,r._currentValue),r._currentValue=s,o!==null)if(Fe(o.value,s)){if(o.children===l.children&&!pe.current){t=qe(e,t,n);break e}}else for(o=t.child,o!==null&&(o.return=t);o!==null;){var i=o.dependencies;if(i!==null){s=o.child;for(var a=i.firstContext;a!==null;){if(a.context===r){if(o.tag===1){a=Xe(-1,n&-n),a.tag=2;var c=o.updateQueue;if(c!==null){c=c.shared;var m=c.pending;m===null?a.next=a:(a.next=m.next,m.next=a),c.pending=a}}o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),Ao(o.return,n,t),i.lanes|=n;break}a=a.next}}else if(o.tag===10)s=o.type===t.type?null:o.child;else if(o.tag===18){if(s=o.return,s===null)throw Error(g(341));s.lanes|=n,i=s.alternate,i!==null&&(i.lanes|=n),Ao(s,n,t),s=o.sibling}else s=o.child;if(s!==null)s.return=o;else for(s=o;s!==null;){if(s===t){s=null;break}if(o=s.sibling,o!==null){o.return=s.return,s=o;break}s=s.return}o=s}se(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,ln(t,n),l=Ee(l),r=r(l),t.flags|=1,se(e,t,r,n),t.child;case 14:return r=t.type,l=Pe(r,t.pendingProps),l=Pe(r.type,l),Qi(e,t,r,l,n);case 15:return Ja(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Vr(e,t),t.tag=1,me(r)?(e=!0,el(t)):e=!1,ln(t,n),Xa(t,r,l),Vo(t,r,l,n),Bo(null,t,r,!0,e,n);case 19:return nc(e,t,n);case 22:return qa(e,t,n)}throw Error(g(156,t.tag))};function gc(e,t){return Qu(e,t)}function qd(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Ne(e,t,n,r){return new qd(e,t,n,r)}function Qs(e){return e=e.prototype,!(!e||!e.isReactComponent)}function bd(e){if(typeof e=="function")return Qs(e)?1:0;if(e!=null){if(e=e.$$typeof,e===cs)return 11;if(e===fs)return 14}return 2}function yt(e,t){var n=e.alternate;return n===null?(n=Ne(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Br(e,t,n,r,l,o){var s=2;if(r=e,typeof e=="function")Qs(e)&&(s=1);else if(typeof e=="string")s=5;else e:switch(e){case Ht:return Tt(n.children,l,o,t);case as:s=8,l|=8;break;case ao:return e=Ne(12,n,t,l|2),e.elementType=ao,e.lanes=o,e;case co:return e=Ne(13,n,t,l),e.elementType=co,e.lanes=o,e;case fo:return e=Ne(19,n,t,l),e.elementType=fo,e.lanes=o,e;case Mu:return jl(n,l,o,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case ju:s=10;break e;case Eu:s=9;break e;case cs:s=11;break e;case fs:s=14;break e;case lt:s=16,r=null;break e}throw Error(g(130,e==null?e:typeof e,""))}return t=Ne(s,n,t,l),t.elementType=e,t.type=r,t.lanes=o,t}function Tt(e,t,n,r){return e=Ne(7,e,r,t),e.lanes=n,e}function jl(e,t,n,r){return e=Ne(22,e,r,t),e.elementType=Mu,e.lanes=n,e.stateNode={isHidden:!1},e}function no(e,t,n){return e=Ne(6,e,null,t),e.lanes=n,e}function ro(e,t,n){return t=Ne(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function ep(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Fl(0),this.expirationTimes=Fl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Fl(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Ks(e,t,n,r,l,o,s,i,a){return e=new ep(e,t,n,i,a),t===1?(t=1,o===!0&&(t|=8)):t=0,o=Ne(3,null,null,t),e.current=o,o.stateNode=e,o.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ls(o),e}function tp(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Sc)}catch(e){console.error(e)}}Sc(),Su.exports=ke;var sp=Su.exports,ou=sp;io.createRoot=ou.createRoot,io.hydrateRoot=ou.hydrateRoot;function ip({strokeWidth:e=60,...t}){return u.jsx("svg",{viewBox:"0 0 1188 1773",fill:"none",xmlns:"http://www.w3.org/2000/svg",role:"img","aria-hidden":"true",...t,children:u.jsx("path",{d:"M25 561L245 694M25 561V818M245 694V951M25 961V1218M25 1357V1614M245 1489V1747M245 1093V1351M942 823V1080M1161 955V1213M1162 555V812M942 422V679M669 585V843L787 913M942 25V282M1162 158V415M25 818L245 951M244 1094L464 962M25 961L143 890M244 1352L464 1219M942 823L1162 956M942 679L1162 812M721 811L942 679M669 842L724 809M669 586L724 553M1041 883L1162 812M245 1747L1161 1213M244 1490L942 1080M25 1357L142 1289M518 1071L942 823M721 555L942 422M942 422L1162 556M942 282L1162 415M942 25L1162 158M942 1080L1161 1213M25 1218L245 1351M25 961L245 1094M464 962L519 929M464 1219L519 1186V928L403 859M25 1357L245 1490M25 1614L245 1747M25 561L942 25M244 694L941 282M1043 484L1162 415M245 951L668 704",stroke:"currentColor",strokeWidth:e,strokeLinecap:"round"})})}function up(e){return u.jsxs("svg",{viewBox:"269 80 364 110",fill:"none",xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":"Patter",...e,children:[u.jsx("path",{d:"M271.422 182.689V85.9524H317.517C324.705 85.9524 330.86 87.2064 335.982 89.7143C341.193 92.2223 345.192 95.7156 347.977 100.194C350.852 104.673 352.29 109.913 352.29 115.914C352.29 121.915 350.852 127.2 347.977 131.768C345.102 136.336 341.058 139.919 335.847 142.516C330.725 145.024 324.615 146.278 317.517 146.278H287.866V130.424H316.439C321.201 130.424 324.885 129.125 327.491 126.528C330.186 123.841 331.534 120.348 331.534 116.048C331.534 111.749 330.186 108.3 327.491 105.703C324.885 103.105 321.201 101.806 316.439 101.806H292.178V182.689H271.422Z",fill:"currentColor"}),u.jsx("path",{d:"M395.375 182.689C394.836 180.718 394.432 178.613 394.162 176.374C393.982 174.135 393.893 171.537 393.893 168.581H393.353V136.202C393.353 133.425 392.41 131.275 390.523 129.752C388.726 128.14 386.03 127.334 382.436 127.334C379.022 127.334 376.281 127.916 374.215 129.081C372.238 130.245 370.935 131.947 370.306 134.186H351.033C351.931 128.006 355.121 122.9 360.602 118.87C366.083 114.839 373.586 112.824 383.11 112.824C392.994 112.824 400.542 115.018 405.753 119.407C410.965 123.796 413.57 130.111 413.57 138.351V168.581C413.57 170.821 413.705 173.105 413.975 175.434C414.334 177.673 414.873 180.091 415.592 182.689H395.375ZM371.384 184.032C364.556 184.032 359.12 182.33 355.076 178.927C351.033 175.434 349.011 170.821 349.011 165.088C349.011 158.729 351.392 153.623 356.154 149.772C361.006 145.83 367.745 143.278 376.371 142.113L396.453 139.292V150.981L379.741 153.533C376.147 154.071 373.496 155.056 371.789 156.489C370.082 157.922 369.228 159.893 369.228 162.401C369.228 164.64 370.037 166.342 371.654 167.507C373.271 168.671 375.428 169.253 378.123 169.253C382.347 169.253 385.941 168.134 388.906 165.894C391.871 163.565 393.353 160.878 393.353 157.833L395.24 168.581C393.264 173.687 390.254 177.538 386.21 180.136C382.167 182.734 377.225 184.032 371.384 184.032Z",fill:"currentColor"}),u.jsx("path",{d:"M450.248 184.167C441.443 184.167 434.883 182.062 430.57 177.852C426.347 173.553 424.236 167.059 424.236 158.37V98.8506L444.453 91.3266V159.042C444.453 162.087 445.306 164.372 447.014 165.894C448.721 167.417 451.371 168.178 454.966 168.178C456.313 168.178 457.571 168.044 458.739 167.775C459.907 167.507 461.075 167.193 462.244 166.835V182.151C461.075 182.778 459.413 183.271 457.257 183.629C455.19 183.988 452.854 184.167 450.248 184.167ZM411.432 129.484V114.167H462.244V129.484H411.432Z",fill:"currentColor"}),u.jsx("path",{d:"M500.501 184.167C491.695 184.167 485.136 182.062 480.823 177.852C476.6 173.553 474.489 167.059 474.489 158.37V98.8506L494.705 91.3266V159.042C494.705 162.087 495.559 164.372 497.266 165.894C498.973 167.417 501.624 168.178 505.218 168.178C506.566 168.178 507.824 168.044 508.992 167.775C510.16 167.507 511.328 167.193 512.496 166.835V182.151C511.328 182.778 509.666 183.271 507.509 183.629C505.443 183.988 503.107 184.167 500.501 184.167ZM461.684 129.484V114.167H512.496V129.484H461.684Z",fill:"currentColor"}),u.jsx("path",{d:"M547.852 184.032C540.214 184.032 533.565 182.554 527.904 179.599C522.244 176.553 517.841 172.343 514.696 166.969C511.641 161.595 510.113 155.414 510.113 148.428C510.113 141.352 511.641 135.171 514.696 129.887C517.841 124.513 522.199 120.348 527.769 117.392C533.34 114.346 539.81 112.824 547.178 112.824C554.276 112.824 560.431 114.257 565.642 117.123C570.854 119.989 574.897 123.975 577.773 129.081C580.648 134.186 582.086 140.187 582.086 147.084C582.086 148.518 582.041 149.861 581.951 151.115C581.861 152.279 581.726 153.399 581.546 154.474H521.974V141.173H565.238L561.734 143.591C561.734 138.038 560.386 133.962 557.69 131.365C555.085 128.678 551.491 127.334 546.908 127.334C541.607 127.334 537.474 129.125 534.508 132.708C531.633 136.291 530.196 141.665 530.196 148.831C530.196 155.818 531.633 161.013 534.508 164.416C537.474 167.82 541.876 169.522 547.717 169.522C550.952 169.522 553.737 168.984 556.073 167.91C558.409 166.835 560.161 165.088 561.33 162.67H580.333C578.087 169.298 574.223 174.538 568.742 178.389C563.351 182.151 556.388 184.032 547.852 184.032Z",fill:"currentColor"}),u.jsx("path",{d:"M586.158 182.689V114.167H605.971V130.29H606.375V182.689H586.158ZM606.375 146.95L604.623 130.693C606.24 124.871 608.891 120.437 612.575 117.392C616.259 114.346 620.842 112.824 626.323 112.824C628.03 112.824 629.288 113.003 630.096 113.361V132.171C629.647 131.992 629.018 131.902 628.21 131.902C627.401 131.813 626.412 131.768 625.244 131.768C618.775 131.768 614.013 132.932 610.958 135.261C607.903 137.5 606.375 141.397 606.375 146.95Z",fill:"currentColor"})]})}function ap(){return u.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:8,color:"var(--ink)"},"aria-label":"Patter",children:[u.jsx(ip,{height:26}),u.jsx(up,{height:24})]})}function cp({liveCount:e,todayCount:t,phoneNumber:n,sdkVersion:r}){return u.jsxs("header",{className:"top",children:[u.jsxs("div",{className:"brand",children:[u.jsx(ap,{}),u.jsxs("span",{className:"tag",children:["dashboard · v",r]})]}),u.jsxs("div",{className:"top-r",children:[u.jsxs("span",{className:"live-chip",children:[u.jsx("span",{className:"pulse"+(e>0?" active":"")}),e," live · ",t," today"]}),n!=="—"&&u.jsx("span",{className:"num-chip",children:n})]})]})}function fp(e){return u.jsxs("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("circle",{cx:"11",cy:"11",r:"7"}),u.jsx("path",{d:"m21 21-4.3-4.3"})]})}function Cc(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.4",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M7 13l5 5 5-5"}),u.jsx("path",{d:"M12 4v14"})]})}function dp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.4",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M17 11l-5-5-5 5"}),u.jsx("path",{d:"M12 20V6"})]})}function pp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("rect",{x:"9",y:"2",width:"6",height:"12",rx:"3"}),u.jsx("path",{d:"M19 10a7 7 0 0 1-14 0"}),u.jsx("path",{d:"M12 19v3"})]})}function mp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("polyline",{points:"15 17 20 12 15 7"}),u.jsx("path",{d:"M4 18v-2a4 4 0 0 1 4-4h12"})]})}function hp(e){return u.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"currentColor",...e,children:u.jsx("circle",{cx:"12",cy:"12",r:"6"})})}function vp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67"}),u.jsx("path",{d:"M22 2 2 22"})]})}const yp=["1h","24h","7d","All"];function gp(){const e=document.createElement("a");e.href="/api/dashboard/export/calls?format=csv",e.download="patter_calls.csv",e.rel="noopener",document.body.appendChild(e),e.click(),document.body.removeChild(e)}function wp({range:e,setRange:t}){return u.jsxs("div",{className:"ph",children:[u.jsxs("div",{children:[u.jsx("h1",{children:"Calls"}),u.jsxs("p",{className:"sub",children:["Real-time view of every call routed through this Patter instance."," ",u.jsx("span",{className:"kbd",children:"⇧K"})," to focus search."]})]}),u.jsxs("div",{className:"filters",children:[u.jsx("div",{className:"seg",children:yp.map(n=>u.jsx("button",{type:"button",className:e===n?"on":"",onClick:()=>t(n),children:n},n))}),u.jsxs("button",{className:"btn",type:"button",onClick:gp,children:[u.jsx(Cc,{})," Export CSV"]})]})]})}function ml(e){const t=Math.floor(e/60),n=Math.floor(e%60);return`${String(t).padStart(2,"0")}:${String(n).padStart(2,"0")}`}function ze(e){if(e==null||!Number.isFinite(e))return"$0.00";const t=Math.abs(e);return t===0?"$0.00":t>=.01?`$${e.toFixed(2)}`:t>=.001?`$${e.toFixed(3)}`:t>=1e-4?`$${e.toFixed(4)}`:`$${e.toFixed(5)}`}const _c=60*60*1e3,xp=24*_c;function Mr(e){return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function kp(e){return new Date(e).toLocaleDateString([],{weekday:"short",month:"short",day:"numeric"})}function su(e){return new Date(e).toLocaleString([],{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function Nc(e){const t=e.toMs-e.fromMs;return t>=xp-Sp?kp(e.fromMs):t>=_c?`${Mr(e.fromMs)} → ${Mr(e.toMs)}`:t>=60*1e3?`${Mr(e.fromMs)} → ${Mr(e.toMs)}`:`${su(e.fromMs)} → ${su(e.toMs)}`}const Sp=5e3;function jc(e){return e.cost.total??(e.cost.telco??0)+(e.cost.llm??0)+(e.cost.sttTts??0)}function Cp(e){return e.calls.length===0?void 0:[...e.calls].sort((n,r)=>(r.startedAtMs??0)-(n.startedAtMs??0))[0]?.id}function _p(e,t){const n=e.calls,r=n.length;if(t==="spend"){const l=n.reduce((o,s)=>o+jc(s),0);return{label:"TOTAL COST",value:ze(l)}}if(t==="latency"){const l=n.filter(s=>typeof s.latencyP95=="number");return{label:"AVG LATENCY",value:`${l.length>0?Math.round(l.reduce((s,i)=>s+(i.latencyP95??0),0)/l.length):0} ms`}}return{label:r===1?"CALL":"CALLS",value:`${r}`}}function Np({bucket:e,kind:t}){const n=Nc(e),r=e.calls.length;if(r===0)return u.jsxs("div",{className:"spark-tooltip",children:[u.jsx("div",{className:"spark-tooltip-range",children:n}),u.jsx("div",{className:"spark-tooltip-empty",children:"no calls"})]});const l=_p(e,t),o=e.calls.slice(0,4);return u.jsxs("div",{className:"spark-tooltip",children:[u.jsx("div",{className:"spark-tooltip-range",children:n}),u.jsxs("div",{className:"spark-tooltip-headline",children:[u.jsx("span",{className:"spark-tooltip-headline-l",children:l.label}),u.jsx("span",{className:"spark-tooltip-headline-v",children:l.value})]}),u.jsx("ul",{className:"spark-tooltip-list",children:o.map(s=>{const i=s.direction==="inbound"?s.from:s.to;return u.jsxs("li",{children:[u.jsx("span",{className:"num",children:i}),u.jsx("span",{className:"status",children:s.status}),u.jsx("span",{className:"cost",children:ze(jc(s))})]},s.id)})}),r>o.length&&u.jsxs("div",{className:"spark-tooltip-more",children:["+",r-o.length," more"]})]})}function jp({bucket:e,height:t,interactive:n,kind:r,onSelect:l}){const[o,s]=L.useState(!1),i=!!e&&e.calls.length>0;return!n||!e?u.jsx("span",{className:"spark-bar-static",style:{height:t+"%"}}):u.jsxs("div",{className:"spark-bar-wrap",onMouseEnter:()=>s(!0),onMouseLeave:()=>s(!1),children:[u.jsx("button",{type:"button",className:"spark-bar"+(i?"":" empty"),style:{height:t+"%"},disabled:!i,onClick:()=>{if(!i)return;const a=Cp(e);a&&l&&l(a)},onFocus:()=>s(!0),onBlur:()=>s(!1),"aria-label":`${e.calls.length} calls in ${Nc(e)}`}),o&&u.jsx(Np,{bucket:e,kind:r})]})}function Lr({label:e,value:t,unit:n,delta:r,deltaTone:l,spark:o,buckets:s,onSelectCall:i,kind:a="count",peach:c,footer:m,badge:v}){const h=!!s&&!!i;return u.jsxs("div",{className:"metric"+(c?" peach":""),children:[u.jsxs("div",{className:"lbl",children:[u.jsx("span",{children:e}),v&&u.jsx("span",{className:"badge-now",children:"LIVE"})]}),u.jsxs("div",{className:"val",children:[t,n&&u.jsxs("span",{className:"unit",children:[" ",n]})]}),r&&u.jsx("div",{className:"delta "+(l||""),children:r}),m&&u.jsx("div",{className:"delta",children:m}),u.jsx("div",{className:"spark",children:o.map((S,x)=>u.jsx(jp,{bucket:s?.[x],height:S,interactive:h,kind:a,onSelect:i},x))})]})}function Ep({call:e,isSelected:t,onSelect:n,isNew:r}){const l=e.status==="live"&&e.durationStart?ml((Date.now()-e.durationStart)/1e3):ml(e.duration||0),o=e.latencyP95?Math.min(100,e.latencyP95/1e3*100):0,s=(e.latencyP95??0)>600,i=e.cost.total??(e.cost.telco??0)+(e.cost.llm??0)+(e.cost.sttTts??0),a=e.status.replace("-","");return u.jsxs("tr",{className:(t?"selected ":"")+(r?"new-row":""),onClick:n,children:[u.jsx("td",{children:u.jsx("span",{className:"pill "+a,children:e.status})}),u.jsxs("td",{children:[u.jsx("span",{className:"dir in",style:{marginRight:8,color:e.direction==="inbound"?"#3b6f3b":"#4a4a4a"},children:e.direction==="inbound"?u.jsx(Cc,{}):u.jsx(dp,{})}),u.jsxs("span",{className:"num-cell",children:[e.from," → ",e.to]})]}),u.jsx("td",{children:u.jsxs("span",{className:"car-tw",children:[u.jsx("span",{className:"car-dot "+(e.carrier==="twilio"?"tw":"tx")}),e.carrier==="twilio"?"Twilio":"Telnyx"]})}),u.jsx("td",{className:"num-cell",children:e.status==="no-answer"?"—":l}),u.jsx("td",{children:e.latencyP95?u.jsxs(u.Fragment,{children:[u.jsx("span",{className:"lat-bar"+(s?" warn":""),children:u.jsx("i",{style:{width:o+"%"}})}),u.jsxs("span",{className:"num-cell",children:[e.latencyP95," ms"]})]}):"—"}),u.jsx("td",{className:"num-cell",children:ze(i)})]})}function Mp({calls:e,selectedId:t,onSelect:n,newId:r,search:l,setSearch:o}){const s=L.useMemo(()=>{if(!l.trim())return e;const i=l.toLowerCase();return e.filter(a=>a.from.toLowerCase().includes(i)||a.to.toLowerCase().includes(i)||a.status.includes(i)||a.carrier.includes(i)||a.id.includes(i))},[e,l]);return u.jsxs("div",{className:"panel",children:[u.jsxs("div",{className:"panel-h",children:[u.jsxs("h3",{children:["Recent calls"," ",u.jsxs("span",{style:{fontFamily:"var(--font-mono)",fontSize:11,color:"#aaa",fontWeight:500,marginLeft:4},children:["(",s.length,")"]})]}),u.jsxs("div",{className:"search",children:[u.jsx(fp,{}),u.jsx("input",{placeholder:"Search number, status, carrier…",value:l,onChange:i=>o(i.target.value)})]}),u.jsxs("span",{className:"sse",children:[u.jsx("span",{className:"dot"}),"streaming · SSE"]})]}),u.jsx("div",{style:{maxHeight:540,overflow:"auto"},children:u.jsxs("table",{children:[u.jsx("thead",{children:u.jsxs("tr",{children:[u.jsx("th",{children:"Status"}),u.jsx("th",{children:"From → To"}),u.jsx("th",{children:"Carrier"}),u.jsx("th",{children:"Duration"}),u.jsx("th",{children:"p95 latency"}),u.jsx("th",{children:"Cost"})]})}),u.jsx("tbody",{children:s.length===0?u.jsx("tr",{children:u.jsxs("td",{colSpan:6,className:"empty",children:['No calls match "',l,'"']})}):s.map(i=>u.jsx(Ep,{call:i,isSelected:i.id===t,onSelect:()=>n(i.id),isNew:i.id===r},i.id))})]})})]})}function Lp({start:e}){const[,t]=L.useState(0);return L.useEffect(()=>{const n=setInterval(()=>t(r=>r+1),1e3);return()=>clearInterval(n)},[]),u.jsx(u.Fragment,{children:ml((Date.now()-e)/1e3)})}function Pp({call:e,transcript:t,onEnd:n,recording:r,setRecording:l,muted:o,setMuted:s}){const i=L.useRef(null);if(L.useEffect(()=>{i.current&&(i.current.scrollTop=i.current.scrollHeight)},[t]),!e)return u.jsxs("div",{className:"rr-card",children:[u.jsx("h3",{children:"No live call selected"}),u.jsx("div",{className:"meta",children:"Select a call from the table — or wait for the next ring."})]});const a=e.status==="live";return u.jsxs("div",{className:"rr-card",children:[u.jsxs("h3",{children:["Live call",u.jsx("span",{className:"pill "+(a?"live":"done"),children:e.status})]}),u.jsxs("div",{className:"meta",children:[u.jsx("strong",{children:e.direction==="inbound"?e.from:e.to}),u.jsx("span",{className:"sep",children:"·"}),e.agent]}),u.jsxs("div",{className:"duration-block",children:[u.jsx("span",{className:"l",children:"duration"}),u.jsxs("span",{className:"agent",children:[e.direction==="inbound"?"inbound":"outbound"," ·"," ",e.carrier==="twilio"?"Twilio":"Telnyx"]}),u.jsx("span",{className:"v",children:a&&e.durationStart?u.jsx(Lp,{start:e.durationStart}):ml(e.duration||0)})]}),u.jsx("div",{className:"transcript",ref:i,children:t.map((c,m)=>c.who==="tool"?u.jsxs("div",{className:"turn tool",children:[u.jsx("div",{className:"av",children:"⚙"}),u.jsxs("div",{className:"body",children:[u.jsxs("div",{className:"who",children:["tool · ",c.txt]}),c.args&&u.jsx("div",{className:"tool-call",children:Object.entries(c.args).map(([v,h])=>u.jsxs("span",{children:[u.jsxs("span",{className:"k",children:[v,":"]}),' "',String(h),'"'," "]},v))})]})]},m):u.jsxs("div",{className:"turn "+c.who,children:[u.jsx("div",{className:"av",children:c.who==="user"?"U":"P"}),u.jsxs("div",{className:"body",children:[u.jsxs("div",{className:"who",children:[c.who==="user"?"caller":"agent",c.typing&&" · typing"]}),u.jsx("div",{className:"txt",children:c.typing?u.jsxs("span",{className:"typing",children:[u.jsx("span",{}),u.jsx("span",{}),u.jsx("span",{})]}):c.txt}),c.lat&&!c.typing&&u.jsxs("div",{className:"lat",children:[c.lat.stt&&`stt ${c.lat.stt} ms`,c.lat.total&&`total ${c.lat.total} ms · llm ${c.lat.llm} · tts ${c.lat.tts}`]})]})]},m))}),a&&u.jsxs("div",{className:"controls",children:[u.jsxs("button",{type:"button",className:"ctrl"+(o?" active":""),onClick:()=>s(!o),children:[u.jsx(pp,{})," ",o?"unmute":"mute"]}),u.jsxs("button",{type:"button",className:"ctrl",children:[u.jsx(mp,{})," transfer"]}),u.jsxs("button",{type:"button",className:"ctrl"+(r?" active":""),onClick:()=>l(!r),children:[u.jsx(hp,{})," ",r?"stop rec":"record"]}),u.jsxs("button",{type:"button",className:"ctrl danger",onClick:n,children:[u.jsx(vp,{})," end"]})]})]})}const Tp=e=>!!e&&typeof e.latencyP95=="number",zp=e=>!!e&&(typeof e.cost.telco=="number"||typeof e.cost.llm=="number"||typeof e.cost.sttTts=="number"||typeof e.cost.total=="number");function Rp({call:e}){const[t,n]=L.useState("latency"),r=Tp(e),l=zp(e);if(!e||!r&&!l)return null;const o=t==="latency"&&!r?"cost":t==="cost"&&!l?"latency":t;return u.jsxs("div",{className:"rr-card metrics-panel",children:[u.jsx("div",{className:"metrics-panel-h",children:u.jsxs("div",{className:"seg",role:"tablist",children:[u.jsx("button",{type:"button",role:"tab","aria-selected":o==="latency",disabled:!r,className:o==="latency"?"on":"",onClick:()=>n("latency"),children:"Latency"}),u.jsx("button",{type:"button",role:"tab","aria-selected":o==="cost",disabled:!l,className:o==="cost"?"on":"",onClick:()=>n("cost"),children:"Cost"})]})}),o==="latency"&&r&&u.jsx(Dp,{call:e}),o==="cost"&&l&&u.jsx(Ip,{call:e})]})}function Dp({call:e}){const t=e.latencyP50??0,n=e.latencyP95??0;if(e.mode==="realtime"){const m=(e.turnCount??0)>=2;return u.jsxs(u.Fragment,{children:[u.jsxs("div",{className:"lat-grid",children:[u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"end-to-end p50"}),u.jsxs("div",{className:"v",children:[m&&t||"—",m&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox"+(m&&n>600?" warn":""),children:[u.jsx("div",{className:"l",children:"end-to-end p95"}),u.jsxs("div",{className:"v",children:[m&&n||"—",m&&u.jsx("span",{className:"u",children:"ms"})]})]})]}),u.jsx("div",{className:"waterfall",children:u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"e2e"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar llm",style:{left:0,width:Math.min(100,n/1e3*100)+"%"}})}),u.jsx("span",{className:"v",children:n})]})}),u.jsxs("div",{className:"wf-legend",children:[u.jsxs("span",{children:[u.jsx("i",{style:{background:"#DF9367"}}),"end-to-end"]}),u.jsx("span",{style:{marginLeft:"auto"},children:e.agent??"realtime"})]})]})}const l=e.sttAvg||0,o=e.llmAvg||0,s=e.ttsAvg||0,i=l+o+s,a=Math.max(i,800),c=(e.turnCount??0)>=2;return u.jsxs(u.Fragment,{children:[u.jsxs("div",{className:"lat-grid",children:[u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"p50"}),u.jsxs("div",{className:"v",children:[c?e.latencyP50??"—":"—",c&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox"+(c&&n>600?" warn":""),children:[u.jsx("div",{className:"l",children:"p95"}),u.jsxs("div",{className:"v",children:[c?n:"—",c&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"stt avg"}),u.jsxs("div",{className:"v",children:[e.sttAvg??"—",u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"tts avg"}),u.jsxs("div",{className:"v",children:[e.ttsAvg??"—",u.jsx("span",{className:"u",children:"ms"})]})]})]}),u.jsxs("div",{className:"waterfall",children:[u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"stt"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar stt",style:{left:0,width:l/a*100+"%"}})}),u.jsx("span",{className:"v",children:l})]}),u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"llm"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar llm",style:{left:l/a*100+"%",width:o/a*100+"%"}})}),u.jsx("span",{className:"v",children:o})]}),u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"tts"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar tts",style:{left:(l+o)/a*100+"%",width:s/a*100+"%"}})}),u.jsx("span",{className:"v",children:s})]})]}),u.jsxs("div",{className:"wf-legend",children:[u.jsxs("span",{children:[u.jsx("i",{style:{background:"#1a1a1a"}}),"stt"]}),u.jsxs("span",{children:[u.jsx("i",{style:{background:"#DF9367"}}),"llm"]}),u.jsxs("span",{children:[u.jsx("i",{style:{background:"#278EFF",opacity:.8}}),"tts"]}),u.jsxs("span",{style:{marginLeft:"auto"},children:["total ",i," ms"]})]})]})}function lo(e){if(e.length===0)return e;const t=e.replace(/(?:_(?:ws|rest|stt|tts|llm))+$/i,"");return t.charAt(0).toUpperCase()+t.slice(1)}function Ip({call:e}){const t=e.cost,n=t.telco??0,r=t.llm??0,l=t.stt??0,o=t.tts??0,s=t.sttTts??0,i=l===0&&o===0?s:0,a=t.cached??0,c=n+r+l+o+i,m=t.total??c-a,v=k=>c>0?k/c*100:0,h=e.sttProvider?`${lo(e.sttProvider)} STT${e.sttModel?` · ${e.sttModel}`:""}`:"STT",S=e.ttsProvider?`${lo(e.ttsProvider)} TTS${e.ttsModel?` · ${e.ttsModel}`:""}`:"TTS",x=e.llmModel?`${e.model?lo(e.model)+" · ":""}${e.llmModel}`:e.model||"LLM";return u.jsxs(u.Fragment,{children:[c>0&&u.jsxs("div",{className:"cost-bar",children:[u.jsx("i",{style:{background:"#cc0000",width:v(n)+"%"}}),u.jsx("i",{style:{background:"#DF9367",width:v(r)+"%"}}),u.jsx("i",{style:{background:"#1a1a1a",width:v(l+i)+"%"}}),u.jsx("i",{style:{background:"#6c6c6c",width:v(o)+"%"}})]}),n>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#cc0000"}}),e.carrier==="twilio"?"Twilio":"Telnyx"]}),u.jsx("span",{className:"v",children:ze(n)})]}),r>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#DF9367"}}),x]}),u.jsx("span",{className:"v",children:ze(r)}),a>0&&u.jsxs("span",{className:"saved",children:["−",ze(a)," cached"]})]}),l>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#1a1a1a"}}),h]}),u.jsx("span",{className:"v",children:ze(l)})]}),o>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#6c6c6c"}}),S]}),u.jsx("span",{className:"v",children:ze(o)})]}),i>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#1a1a1a"}}),"STT / TTS (legacy)"]}),u.jsx("span",{className:"v",children:ze(i)})]}),u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:["Total"," ",e.status==="live"&&u.jsx("span",{style:{fontFamily:"var(--font-mono)",fontSize:10,color:"#aaa",marginLeft:4},children:"(running)"})]}),u.jsx("span",{className:"v",children:ze(m)})]})]})}const $t=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),bt=e=>typeof e=="string"?e:"",De=e=>typeof e=="number"&&Number.isFinite(e)?e:0,ie=e=>typeof e=="number"&&Number.isFinite(e)?e:void 0,Be=e=>typeof e=="string"&&e.length>0?e:void 0;function Pr(e){if($t(e))return{stt_ms:ie(e.stt_ms),llm_ms:ie(e.llm_ms),tts_ms:ie(e.tts_ms),total_ms:ie(e.total_ms),agent_response_ms:ie(e.agent_response_ms),endpoint_ms:ie(e.endpoint_ms),user_speech_duration_ms:ie(e.user_speech_duration_ms)}}function Op(e){if($t(e))return{stt:ie(e.stt),tts:ie(e.tts),llm:ie(e.llm),telephony:ie(e.telephony),total:ie(e.total),llm_cached_savings:ie(e.llm_cached_savings)}}function Fp(e){if(!$t(e))return null;const t=e.turns;return{duration_seconds:ie(e.duration_seconds),provider_mode:Be(e.provider_mode),telephony_provider:Be(e.telephony_provider),stt_provider:Be(e.stt_provider),tts_provider:Be(e.tts_provider),llm_provider:Be(e.llm_provider),stt_model:Be(e.stt_model),tts_model:Be(e.tts_model),llm_model:Be(e.llm_model),cost:Op(e.cost),latency_avg:Pr(e.latency_avg),latency_p50:Pr(e.latency_p50),latency_p95:Pr(e.latency_p95),latency_p99:Pr(e.latency_p99),turns:Array.isArray(t)?t:void 0}}function Ap(e){if(!Array.isArray(e))return;const t=[];for(const n of e)$t(n)&&t.push({role:bt(n.role),text:bt(n.text),timestamp:De(n.timestamp)});return t}function Ec(e){if(!$t(e))return null;const t=bt(e.call_id);if(t.length===0)return null;const n=e.turns;return{call_id:t,caller:bt(e.caller),callee:bt(e.callee),direction:bt(e.direction),started_at:De(e.started_at),ended_at:ie(e.ended_at),status:Be(e.status),transcript:Ap(e.transcript),turns:Array.isArray(n)?n:void 0,metrics:Fp(e.metrics)}}function Mc(e){if(!Array.isArray(e))return[];const t=[];for(const n of e){const r=Ec(n);r&&t.push(r)}return t}function $p(e){return $t(e)?{stt:De(e.stt),tts:De(e.tts),llm:De(e.llm),telephony:De(e.telephony)}:{stt:0,tts:0,llm:0,telephony:0}}function Vp(e){return $t(e)?{total_calls:De(e.total_calls),total_cost:De(e.total_cost),avg_duration:De(e.avg_duration),avg_latency_ms:De(e.avg_latency_ms),cost_breakdown:$p(e.cost_breakdown),active_calls:De(e.active_calls)}:{total_calls:0,total_cost:0,avg_duration:0,avg_latency_ms:0,cost_breakdown:{stt:0,tts:0,llm:0,telephony:0},active_calls:0}}async function Zs(e){const t=await fetch(e,{headers:{Accept:"application/json"}});if(!t.ok)throw new Error(`Request to ${e} failed with status ${t.status}`);return t.json()}async function Up(e=50,t=0){const n=`/api/dashboard/calls?limit=${encodeURIComponent(e)}&offset=${encodeURIComponent(t)}`,r=await Zs(n);return Mc(r)}async function Hp(){const e=await Zs("/api/dashboard/active");return Mc(e)}async function Bp(){const e=await Zs("/api/dashboard/aggregates");return Vp(e)}async function Wp(e){const t=`/api/dashboard/calls/${encodeURIComponent(e)}`,n=await fetch(t,{headers:{Accept:"application/json"}});if(n.status===404)return null;if(!n.ok)throw new Error(`Request to ${t} failed with status ${n.status}`);const r=await n.json();return Ec(r)}const Qp=new Set(["in-progress","initiated"]);function Kp(e){if(!e)return"ended";switch(e){case"in-progress":case"initiated":return"live";case"completed":return"ended";case"no-answer":return"no-answer";case"busy":case"failed":case"canceled":case"webhook_error":return"fail";default:return"ended"}}function Yp(e){return e==="outbound"?"outbound":"inbound"}function Xp(e){return typeof e=="string"&&e.toLowerCase().includes("telnyx")?"telnyx":"twilio"}function Gp(e){if(typeof e!="string")return"unknown";const t=e.toLowerCase();return t.includes("realtime")?"realtime":t.includes("convai")?"convai":t.includes("pipeline")?"pipeline":"unknown"}function iu(e){return e.length===0?"—":e}function Zp(e){const t=e.metrics?.provider_mode;if(!t)return;const n=e.metrics?.llm_provider;return t.startsWith("pipeline")&&n?`${t} · ${n}`:t}function Jp(e){const t=e.metrics?.cost;if(!t)return{};const n={};return typeof t.telephony=="number"&&(n.telco=t.telephony),typeof t.llm=="number"&&(n.llm=t.llm),typeof t.stt=="number"&&(n.stt=t.stt),typeof t.tts=="number"&&(n.tts=t.tts),typeof t.llm_cached_savings=="number"&&(n.cached=t.llm_cached_savings),(n.stt!==void 0||n.tts!==void 0)&&(n.sttTts=(n.stt??0)+(n.tts??0)),n.telco===void 0&&n.llm===void 0&&n.sttTts===void 0&&typeof t.total=="number"&&(n.total=t.total),n}function qp(e,t){if(t)return;const n=e.metrics?.duration_seconds;return typeof n=="number"?n:typeof e.ended_at=="number"&&typeof e.started_at=="number"?Math.max(0,e.ended_at-e.started_at):0}function bp(e){if(typeof e.ended_at=="number")return Math.round(Date.now()/1e3-e.ended_at)}function uu(e){const t=Kp(e.status),n=t==="live"||e.status!==void 0&&Qp.has(e.status),r=e.metrics?.latency_avg,l=e.metrics?.latency_p50,o=e.metrics?.latency_p95,s=(Array.isArray(e.metrics?.turns)?e.metrics?.turns?.length:void 0)??(Array.isArray(e.transcript)?e.transcript.length:void 0);return{id:e.call_id,status:t,direction:Yp(e.direction),from:iu(e.caller),to:iu(e.callee),carrier:Xp(e.metrics?.telephony_provider),startedAtMs:typeof e.started_at=="number"?e.started_at*1e3:void 0,durationStart:n?e.started_at*1e3:void 0,duration:qp(e,n),latencyP95:o?.agent_response_ms??o?.total_ms??r?.total_ms,latencyP50:l?.agent_response_ms??l?.total_ms??r?.total_ms,sttAvg:r?.stt_ms,ttsAvg:r?.tts_ms,llmAvg:r?.llm_ms,turnCount:s,agentResponseP50:l?.agent_response_ms,agentResponseP95:o?.agent_response_ms,cost:Jp(e),agent:Zp(e),model:e.metrics?.llm_provider,mode:Gp(e.metrics?.provider_mode),sttProvider:e.metrics?.stt_provider,ttsProvider:e.metrics?.tts_provider,sttModel:e.metrics?.stt_model,ttsModel:e.metrics?.tts_model,llmModel:e.metrics?.llm_model,transcriptKey:e.call_id,endedAgo:bp(e)}}function em(e){const t=e.transcript;if(t&&t.length>0){const l=[];for(const o of t){const s=o.text;switch(o.role){case"user":l.push({who:"user",txt:s});break;case"assistant":l.push({who:"bot",txt:s});break;case"tool":l.push({who:"tool",txt:s});break;default:l.push({who:"bot",txt:s});break}}return l}const n=e.turns;if(!n||n.length===0)return[];const r=[];for(const l of n){if(typeof l!="object"||l===null)continue;const o=l,s=typeof o.user_text=="string"?o.user_text:"",i=typeof o.agent_text=="string"?o.agent_text:"";s.length>0&&r.push({who:"user",txt:s}),i.length>0&&i!=="[interrupted]"&&r.push({who:"bot",txt:i})}return r}const Lc=60*1e3,Pc=60*Lc,oo=24*Pc;function tm(e,t=Date.now()){switch(e){case"1h":{const n=5*Lc,r=Math.ceil(t/n)*n,l=r-12*n;return{count:12,bucketSizeMs:n,window:{fromMs:l,toMs:r}}}case"24h":{const n=Pc,r=Math.ceil(t/n)*n,l=r-24*n;return{count:24,bucketSizeMs:n,window:{fromMs:l,toMs:r}}}case"7d":{const n=new Date(t);n.setHours(0,0,0,0);const r=n.getTime()+oo,l=r-7*oo;return{count:7,bucketSizeMs:oo,window:{fromMs:l,toMs:r}}}case"All":default:return{count:9,bucketSizeMs:0,window:{fromMs:0,toMs:t}}}}function nm(e,t){const{fromMs:n,toMs:r}=t;return e.filter(l=>{const o=ts(l);return typeof o!="number"?!1:o>=n&&o<=r})}function ts(e){if(typeof e.startedAtMs=="number")return e.startedAtMs;if(typeof e.durationStart=="number")return e.durationStart;if(typeof e.endedAgo=="number")return Date.now()-e.endedAgo*1e3}function rm(e){const t=e.cost,n=(t.telco??0)+(t.llm??0)+(t.sttTts??0);return n>0?n:t.total??0}function lm(e){const t=e.reduce((n,r)=>r>n?r:n,0);return t<=0?e.map(()=>0):e.map(n=>Math.round(n/t*100))}function Tr(e,t,n=9,r){const l=typeof n=="object",o=l?n.count:n,s=Math.max(1,Math.floor(o)),i=l?n.window:r,a=l?n.bucketSizeMs:0;let c,m;if(i)c=i.fromMs,m=i.toMs;else{const d=[];for(const f of e){const p=ts(f);typeof p=="number"&&d.push(p)}if(d.length===0){const f=Date.now();return{heights:new Array(s).fill(0),buckets:new Array(s).fill(null).map(()=>[]),window:{fromMs:f,toMs:f},bucketSizeMs:0}}c=Math.min(...d),m=Math.max(...d)}const v=Math.max(1,m-c),h=a>0?a:v/s,S=new Array(s).fill(null).map(()=>[]),x=new Array(s).fill(0),k=new Array(s).fill(0);for(const d of e){const f=ts(d);if(typeof f!="number"||fm)continue;let p=Math.floor((f-c)/h);p>=s&&(p=s-1),p<0&&(p=0),S[p].push(d),t==="totalCalls"?x[p]+=1:t==="latency"?typeof d.latencyP95=="number"&&(x[p]+=d.latencyP95,k[p]+=1):x[p]+=rm(d)}const R=t==="latency"?x.map((d,f)=>k[f]>0?d/k[f]:0):x;return{heights:lm(R),buckets:S,window:{fromMs:c,toMs:m},bucketSizeMs:h}}const om=1e3,sm=3e4,im=5,um=5e3,am=["call_start","call_initiated","call_status","call_end"];function cm(e,t){const n=new Set,r=[];for(const l of e)n.has(l.call_id)||(n.add(l.call_id),r.push(uu(l)));for(const l of t)n.has(l.call_id)||(n.add(l.call_id),r.push(uu(l)));return r}function fm(e,t){const n=new Map(e.map(r=>[r.id,r]));return t.map(r=>{const l=n.get(r.id);return l?{...l,...r,latencyP95:r.latencyP95??l.latencyP95,latencyP50:r.latencyP50??l.latencyP50,sttAvg:r.sttAvg??l.sttAvg,ttsAvg:r.ttsAvg??l.ttsAvg,llmAvg:r.llmAvg??l.llmAvg,turnCount:r.turnCount??l.turnCount,agentResponseP50:r.agentResponseP50??l.agentResponseP50,agentResponseP95:r.agentResponseP95??l.agentResponseP95,cost:{...l.cost,...r.cost}}:r})}function au(e){return e instanceof Error?e.message:"Unknown error"}function dm(){const[e,t]=L.useState([]),[n,r]=L.useState(null),[l,o]=L.useState(!1),[s,i]=L.useState(null),a=L.useRef(!0),c=L.useRef(null),m=L.useRef(null),v=L.useRef(null),h=L.useRef(0),S=L.useCallback(()=>{m.current!==null&&(clearTimeout(m.current),m.current=null)},[]),x=L.useCallback(()=>{v.current!==null&&(clearInterval(v.current),v.current=null)},[]),k=L.useCallback(()=>{c.current!==null&&(c.current.close(),c.current=null)},[]),R=L.useCallback(async()=>{try{const[C,N,E]=await Promise.all([Hp(),Up(50,0),Bp()]);if(!a.current)return;t(F=>fm(F,cm(C,N))),r(E),i(null)}catch(C){if(!a.current)return;i(au(C))}},[]),d=L.useCallback(()=>{v.current===null&&(v.current=setInterval(()=>{R()},um))},[R]),f=L.useRef(()=>{}),p=L.useCallback(()=>{if(S(),h.current>=im){d();return}const C=h.current,N=Math.min(sm,om*Math.pow(2,C));h.current=C+1,m.current=setTimeout(()=>{m.current=null,a.current&&f.current()},N)},[S,d]),y=L.useCallback(()=>{R()},[R]),_=L.useCallback(()=>{k();let C;try{C=new EventSource("/api/dashboard/events")}catch(N){i(au(N)),p();return}c.current=C,C.onopen=()=>{a.current&&(h.current=0,x(),o(!0))},C.onerror=()=>{a.current&&(o(!1),k(),p())};for(const N of am)C.addEventListener(N,y);C.addEventListener("turn_complete",y)},[k,x,y,p]);return L.useEffect(()=>{f.current=_},[_]),L.useEffect(()=>(a.current=!0,R(),_(),()=>{a.current=!1,S(),x(),k()}),[]),{calls:e,aggregates:n,isStreaming:l,error:s,refresh:R}}const pm=2e3;function mm(e,t){const[n,r]=L.useState([]),l=L.useRef(!0);return L.useEffect(()=>(l.current=!0,()=>{l.current=!1}),[]),L.useEffect(()=>{if(!e){r([]);return}let o=!1,s=null,i=null;const a=async()=>{try{const m=await Wp(e);if(o||!l.current)return;if(m===null){r([]);return}r(em(m))}catch{}};a();const c=m=>{const v=m;try{return JSON.parse(v.data)?.call_id===e}catch{return!1}};try{i=new EventSource("/api/dashboard/events"),i.addEventListener("turn_complete",m=>{c(m)&&a()}),i.addEventListener("call_end",m=>{c(m)&&a()})}catch{i=null}return t&&(s=setInterval(()=>{a()},pm)),()=>{o=!0,s!==null&&clearInterval(s),i!==null&&i.close()}},[e,t]),n}const cu="0.6.0",so={"1h":"1h","24h":"24h","7d":"7d",All:"all-time"};function hm(e){const t=e.filter(r=>typeof r.latencyP95=="number");if(t.length===0)return 0;const n=t.reduce((r,l)=>r+(l.latencyP95??0),0);return Math.round(n/t.length)}function vm(e){return e.reduce((t,n)=>{if(typeof n.cost.total=="number")return t+n.cost.total;const r=(n.cost.telco??0)+(n.cost.llm??0)+(n.cost.sttTts??0);return t+r},0)}function ym(e){const n=e.find(l=>l.status==="live")??e[0];if(!n)return"";const r=n.direction==="inbound"?n.to:n.from;return r&&r!=="—"?r:""}function gm(){const{calls:e,aggregates:t,isStreaming:n,error:r,refresh:l}=dm(),[o,s]=L.useState(null),[i,a]=L.useState(""),[c,m]=L.useState("24h"),[v,h]=L.useState(!0),[S,x]=L.useState(!1),k=L.useMemo(()=>tm(c),[c]),R=k.window,d=L.useMemo(()=>{if(c==="All")return e;const w=new Set(nm(e,R).map(M=>M.id));return e.filter(M=>M.status==="live"||w.has(M.id))},[e,c,R]);L.useEffect(()=>{if(o!==null)return;const w=d.find(M=>M.status==="live")??d[0];w&&s(w.id)},[d,o]),L.useEffect(()=>{o!==null&&(d.some(w=>w.id===o)||s(null))},[d,o]),L.useEffect(()=>{const w=M=>{if(!(M.shiftKey&&M.key.toLowerCase()==="k"||M.metaKey&&M.key.toLowerCase()==="k"))return;M.preventDefault(),document.querySelector(".panel-h .search input")?.focus()};return window.addEventListener("keydown",w),()=>window.removeEventListener("keydown",w)},[]);const f=L.useMemo(()=>d.find(w=>w.id===o)??null,[d,o]),p=f?.status==="live",y=mm(f?.id??null,p),_=L.useMemo(()=>e.filter(w=>w.status==="live").length,[e]),C=L.useMemo(()=>e.filter(w=>w.status==="live"&&w.direction==="inbound").length,[e]),N=_-C,E=d.length,F=hm(d)||t?.avg_latency_ms||0,T=vm(d)||t?.total_cost||0,ve=ym(e),et=L.useMemo(()=>Tr(d,"totalCalls",k),[d,k]),tt=L.useMemo(()=>Tr(d,"latency",k),[d,k]),yn=L.useMemo(()=>Tr(d,"spend",k),[d,k]),ur=L.useMemo(()=>{const w=e.filter(M=>M.status==="live");return Tr(w,"totalCalls",k)},[e,k]),nt=w=>w.heights.map((M,P)=>({height:M,calls:w.buckets[P],fromMs:w.window.fromMs+P*w.bucketSizeMs,toMs:w.window.fromMs+(P+1)*w.bucketSizeMs})),gn=()=>{f&&l().catch(()=>{})};return u.jsxs(u.Fragment,{children:[u.jsx(cp,{liveCount:_,todayCount:E,phoneNumber:ve,sdkVersion:cu}),u.jsxs("div",{className:"page",children:[u.jsx(wp,{range:c,setRange:w=>m(w)}),u.jsxs("div",{className:"metrics",children:[u.jsx(Lr,{label:`Calls · ${so[c]}`,value:E,spark:et.heights,buckets:nt(et),onSelectCall:s,kind:"count"}),u.jsx(Lr,{label:"Avg latency p95",value:F||0,unit:"ms",spark:tt.heights,buckets:nt(tt),onSelectCall:s,kind:"latency"}),u.jsx(Lr,{label:`Spend · ${so[c]}`,value:ze(T),spark:yn.heights,buckets:nt(yn),onSelectCall:s,kind:"spend"}),u.jsx(Lr,{label:"Active now",value:_,peach:!0,badge:!0,footer:`${C} inbound · ${N} outbound`,spark:ur.heights,buckets:nt(ur),onSelectCall:s,kind:"count"})]}),u.jsxs("div",{className:"split",children:[u.jsx(Mp,{calls:d,selectedId:o,onSelect:s,newId:null,search:i,setSearch:a}),u.jsxs("div",{className:"rr",children:[u.jsx(Pp,{call:f,transcript:y,onEnd:gn,recording:v,setRecording:h,muted:S,setMuted:x}),u.jsx(Rp,{call:f})]})]}),u.jsxs("div",{className:"statusbar",children:[u.jsxs("div",{className:"group",children:[u.jsx("span",{className:n?"green":"",children:n?"streaming · sse":r?`error · ${r}`:"idle"}),u.jsxs("span",{children:["SDK · ",cu]})]}),u.jsx("div",{className:"group",children:u.jsxs("span",{children:[_," live · ",E," ",so[c]]})})]})]})]})}const Tc=document.getElementById("root");if(!Tc)throw new Error("Patter dashboard: #root element missing");io.createRoot(Tc).render(u.jsx(Xc.StrictMode,{children:u.jsx(gm,{})})); +`+o.stack}return{value:e,source:t,stack:l,digest:null}}function eo(e,t,n){return{value:e,source:null,stack:n??null,digest:t??null}}function Uo(e,t){try{console.error(t.value)}catch(n){setTimeout(function(){throw n})}}var Id=typeof WeakMap=="function"?WeakMap:Map;function Ga(e,t,n){n=Xe(-1,n),n.tag=3,n.payload={element:null};var r=t.value;return n.callback=function(){cl||(cl=!0,Jo=r),Uo(e,t)},n}function Za(e,t,n){n=Xe(-1,n),n.tag=3;var r=e.type.getDerivedStateFromError;if(typeof r=="function"){var l=t.value;n.payload=function(){return r(l)},n.callback=function(){Uo(e,t)}}var o=e.stateNode;return o!==null&&typeof o.componentDidCatch=="function"&&(n.callback=function(){Uo(e,t),typeof r!="function"&&(ht===null?ht=new Set([this]):ht.add(this));var s=t.stack;this.componentDidCatch(t.value,{componentStack:s!==null?s:""})}),n}function Ui(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Id;var l=new Set;r.set(t,l)}else l=r.get(t),l===void 0&&(l=new Set,r.set(t,l));l.has(n)||(l.add(n),e=Gd.bind(null,e,t,n),t.then(e,e))}function Hi(e){do{var t;if((t=e.tag===13)&&(t=e.memoizedState,t=t!==null?t.dehydrated!==null:!0),t)return e;e=e.return}while(e!==null);return null}function Bi(e,t,n,r,l){return e.mode&1?(e.flags|=65536,e.lanes=l,e):(e===t?e.flags|=65536:(e.flags|=128,n.flags|=131072,n.flags&=-52805,n.tag===1&&(n.alternate===null?n.tag=17:(t=Xe(-1,1),t.tag=2,mt(n,t,1))),n.lanes|=1),e)}var Od=be.ReactCurrentOwner,de=!1;function se(e,t,n,r){t.child=e===null?Na(t,null,n,r):cn(t,e.child,n,r)}function Wi(e,t,n,r,l){n=n.render;var o=t.ref;return ln(t,l),r=Ds(e,t,n,r,o,l),n=Is(),e!==null&&!de?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~l,qe(e,t,l)):(V&&n&&Ss(t),t.flags|=1,se(e,t,r,l),t.child)}function Qi(e,t,n,r,l){if(e===null){var o=n.type;return typeof o=="function"&&!Qs(o)&&o.defaultProps===void 0&&n.compare===null&&n.defaultProps===void 0?(t.tag=15,t.type=o,Ja(e,t,o,r,l)):(e=Br(n.type,null,r,t,t.mode,l),e.ref=t.ref,e.return=t,t.child=e)}if(o=e.child,!(e.lanes&l)){var s=o.memoizedProps;if(n=n.compare,n=n!==null?n:Yn,n(s,r)&&e.ref===t.ref)return qe(e,t,l)}return t.flags|=1,e=yt(o,r),e.ref=t.ref,e.return=t,t.child=e}function Ja(e,t,n,r,l){if(e!==null){var o=e.memoizedProps;if(Yn(o,r)&&e.ref===t.ref)if(de=!1,t.pendingProps=r=o,(e.lanes&l)!==0)e.flags&131072&&(de=!0);else return t.lanes=e.lanes,qe(e,t,l)}return Ho(e,t,n,r,l)}function qa(e,t,n){var r=t.pendingProps,l=r.children,o=e!==null?e.memoizedState:null;if(r.mode==="hidden")if(!(t.mode&1))t.memoizedState={baseLanes:0,cachePool:null,transitions:null},O(qt,ye),ye|=n;else{if(!(n&1073741824))return e=o!==null?o.baseLanes|n:n,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e,cachePool:null,transitions:null},t.updateQueue=null,O(qt,ye),ye|=e,null;t.memoizedState={baseLanes:0,cachePool:null,transitions:null},r=o!==null?o.baseLanes:n,O(qt,ye),ye|=r}else o!==null?(r=o.baseLanes|n,t.memoizedState=null):r=n,O(qt,ye),ye|=r;return se(e,t,l,n),t.child}function ba(e,t){var n=t.ref;(e===null&&n!==null||e!==null&&e.ref!==n)&&(t.flags|=512,t.flags|=2097152)}function Ho(e,t,n,r,l){var o=me(n)?zt:oe.current;return o=un(t,o),ln(t,l),n=Ds(e,t,n,r,o,l),r=Is(),e!==null&&!de?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~l,qe(e,t,l)):(V&&r&&Ss(t),t.flags|=1,se(e,t,n,l),t.child)}function Ki(e,t,n,r,l){if(me(n)){var o=!0;el(t)}else o=!1;if(ln(t,l),t.stateNode===null)Vr(e,t),Xa(t,n,r),Vo(t,n,r,l),r=!0;else if(e===null){var s=t.stateNode,i=t.memoizedProps;s.props=i;var a=s.context,c=n.contextType;typeof c=="object"&&c!==null?c=Ee(c):(c=me(n)?zt:oe.current,c=un(t,c));var m=n.getDerivedStateFromProps,v=typeof m=="function"||typeof s.getSnapshotBeforeUpdate=="function";v||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(i!==r||a!==c)&&Vi(t,s,r,c),ot=!1;var h=t.memoizedState;s.state=h,ol(t,r,s,l),a=t.memoizedState,i!==r||h!==a||pe.current||ot?(typeof m=="function"&&($o(t,n,m,r),a=t.memoizedState),(i=ot||$i(t,n,i,r,h,a,c))?(v||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount()),typeof s.componentDidMount=="function"&&(t.flags|=4194308)):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=r,t.memoizedState=a),s.props=r,s.state=a,s.context=c,r=i):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),r=!1)}else{s=t.stateNode,Ea(e,t),i=t.memoizedProps,c=t.type===t.elementType?i:Pe(t.type,i),s.props=c,v=t.pendingProps,h=s.context,a=n.contextType,typeof a=="object"&&a!==null?a=Ee(a):(a=me(n)?zt:oe.current,a=un(t,a));var S=n.getDerivedStateFromProps;(m=typeof S=="function"||typeof s.getSnapshotBeforeUpdate=="function")||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(i!==v||h!==a)&&Vi(t,s,r,a),ot=!1,h=t.memoizedState,s.state=h,ol(t,r,s,l);var x=t.memoizedState;i!==v||h!==x||pe.current||ot?(typeof S=="function"&&($o(t,n,S,r),x=t.memoizedState),(c=ot||$i(t,n,c,r,h,x,a)||!1)?(m||typeof s.UNSAFE_componentWillUpdate!="function"&&typeof s.componentWillUpdate!="function"||(typeof s.componentWillUpdate=="function"&&s.componentWillUpdate(r,x,a),typeof s.UNSAFE_componentWillUpdate=="function"&&s.UNSAFE_componentWillUpdate(r,x,a)),typeof s.componentDidUpdate=="function"&&(t.flags|=4),typeof s.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof s.componentDidUpdate!="function"||i===e.memoizedProps&&h===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||i===e.memoizedProps&&h===e.memoizedState||(t.flags|=1024),t.memoizedProps=r,t.memoizedState=x),s.props=r,s.state=x,s.context=a,r=c):(typeof s.componentDidUpdate!="function"||i===e.memoizedProps&&h===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||i===e.memoizedProps&&h===e.memoizedState||(t.flags|=1024),r=!1)}return Bo(e,t,n,r,o,l)}function Bo(e,t,n,r,l,o){ba(e,t);var s=(t.flags&128)!==0;if(!r&&!s)return l&&Ti(t,n,!1),qe(e,t,o);r=t.stateNode,Od.current=t;var i=s&&typeof n.getDerivedStateFromError!="function"?null:r.render();return t.flags|=1,e!==null&&s?(t.child=cn(t,e.child,null,o),t.child=cn(t,null,i,o)):se(e,t,i,o),t.memoizedState=r.state,l&&Ti(t,n,!0),t.child}function ec(e){var t=e.stateNode;t.pendingContext?Pi(e,t.pendingContext,t.pendingContext!==t.context):t.context&&Pi(e,t.context,!1),Ps(e,t.containerInfo)}function Yi(e,t,n,r,l){return an(),_s(l),t.flags|=256,se(e,t,n,r),t.child}var Wo={dehydrated:null,treeContext:null,retryLane:0};function Qo(e){return{baseLanes:e,cachePool:null,transitions:null}}function tc(e,t,n){var r=t.pendingProps,l=U.current,o=!1,s=(t.flags&128)!==0,i;if((i=s)||(i=e!==null&&e.memoizedState===null?!1:(l&2)!==0),i?(o=!0,t.flags&=-129):(e===null||e.memoizedState!==null)&&(l|=1),O(U,l&1),e===null)return Fo(t),e=t.memoizedState,e!==null&&(e=e.dehydrated,e!==null)?(t.mode&1?e.data==="$!"?t.lanes=8:t.lanes=1073741824:t.lanes=1,null):(s=r.children,e=r.fallback,o?(r=t.mode,o=t.child,s={mode:"hidden",children:s},!(r&1)&&o!==null?(o.childLanes=0,o.pendingProps=s):o=jl(s,r,0,null),e=Tt(e,r,n,null),o.return=t,e.return=t,o.sibling=e,t.child=o,t.child.memoizedState=Qo(n),t.memoizedState=Wo,e):As(t,s));if(l=e.memoizedState,l!==null&&(i=l.dehydrated,i!==null))return Fd(e,t,s,r,i,l,n);if(o){o=r.fallback,s=t.mode,l=e.child,i=l.sibling;var a={mode:"hidden",children:r.children};return!(s&1)&&t.child!==l?(r=t.child,r.childLanes=0,r.pendingProps=a,t.deletions=null):(r=yt(l,a),r.subtreeFlags=l.subtreeFlags&14680064),i!==null?o=yt(i,o):(o=Tt(o,s,n,null),o.flags|=2),o.return=t,r.return=t,r.sibling=o,t.child=r,r=o,o=t.child,s=e.child.memoizedState,s=s===null?Qo(n):{baseLanes:s.baseLanes|n,cachePool:null,transitions:s.transitions},o.memoizedState=s,o.childLanes=e.childLanes&~n,t.memoizedState=Wo,r}return o=e.child,e=o.sibling,r=yt(o,{mode:"visible",children:r.children}),!(t.mode&1)&&(r.lanes=n),r.return=t,r.sibling=null,e!==null&&(n=t.deletions,n===null?(t.deletions=[e],t.flags|=16):n.push(e)),t.child=r,t.memoizedState=null,r}function As(e,t){return t=jl({mode:"visible",children:t},e.mode,0,null),t.return=e,e.child=t}function _r(e,t,n,r){return r!==null&&_s(r),cn(t,e.child,null,n),e=As(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function Fd(e,t,n,r,l,o,s){if(n)return t.flags&256?(t.flags&=-257,r=eo(Error(g(422))),_r(e,t,s,r)):t.memoizedState!==null?(t.child=e.child,t.flags|=128,null):(o=r.fallback,l=t.mode,r=jl({mode:"visible",children:r.children},l,0,null),o=Tt(o,l,s,null),o.flags|=2,r.return=t,o.return=t,r.sibling=o,t.child=r,t.mode&1&&cn(t,e.child,null,s),t.child.memoizedState=Qo(s),t.memoizedState=Wo,o);if(!(t.mode&1))return _r(e,t,s,null);if(l.data==="$!"){if(r=l.nextSibling&&l.nextSibling.dataset,r)var i=r.dgst;return r=i,o=Error(g(419)),r=eo(o,r,void 0),_r(e,t,s,r)}if(i=(s&e.childLanes)!==0,de||i){if(r=q,r!==null){switch(s&-s){case 4:l=2;break;case 16:l=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:l=32;break;case 536870912:l=268435456;break;default:l=0}l=l&(r.suspendedLanes|s)?0:l,l!==0&&l!==o.retryLane&&(o.retryLane=l,Je(e,l),Oe(r,e,l,-1))}return Ws(),r=eo(Error(g(421))),_r(e,t,s,r)}return l.data==="$?"?(t.flags|=128,t.child=e.child,t=Zd.bind(null,e),l._reactRetry=t,null):(e=o.treeContext,ge=pt(l.nextSibling),we=t,V=!0,Re=null,e!==null&&(Ce[_e++]=Ke,Ce[_e++]=Ye,Ce[_e++]=Rt,Ke=e.id,Ye=e.overflow,Rt=t),t=As(t,r.children),t.flags|=4096,t)}function Xi(e,t,n){e.lanes|=t;var r=e.alternate;r!==null&&(r.lanes|=t),Ao(e.return,t,n)}function to(e,t,n,r,l){var o=e.memoizedState;o===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:l}:(o.isBackwards=t,o.rendering=null,o.renderingStartTime=0,o.last=r,o.tail=n,o.tailMode=l)}function nc(e,t,n){var r=t.pendingProps,l=r.revealOrder,o=r.tail;if(se(e,t,r.children,n),r=U.current,r&2)r=r&1|2,t.flags|=128;else{if(e!==null&&e.flags&128)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&Xi(e,n,t);else if(e.tag===19)Xi(e,n,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}if(O(U,r),!(t.mode&1))t.memoizedState=null;else switch(l){case"forwards":for(n=t.child,l=null;n!==null;)e=n.alternate,e!==null&&sl(e)===null&&(l=n),n=n.sibling;n=l,n===null?(l=t.child,t.child=null):(l=n.sibling,n.sibling=null),to(t,!1,l,n,o);break;case"backwards":for(n=null,l=t.child,t.child=null;l!==null;){if(e=l.alternate,e!==null&&sl(e)===null){t.child=l;break}e=l.sibling,l.sibling=n,n=l,l=e}to(t,!0,n,null,o);break;case"together":to(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function Vr(e,t){!(t.mode&1)&&e!==null&&(e.alternate=null,t.alternate=null,t.flags|=2)}function qe(e,t,n){if(e!==null&&(t.dependencies=e.dependencies),It|=t.lanes,!(n&t.childLanes))return null;if(e!==null&&t.child!==e.child)throw Error(g(153));if(t.child!==null){for(e=t.child,n=yt(e,e.pendingProps),t.child=n,n.return=t;e.sibling!==null;)e=e.sibling,n=n.sibling=yt(e,e.pendingProps),n.return=t;n.sibling=null}return t.child}function Ad(e,t,n){switch(t.tag){case 3:ec(t),an();break;case 5:Ma(t);break;case 1:me(t.type)&&el(t);break;case 4:Ps(t,t.stateNode.containerInfo);break;case 10:var r=t.type._context,l=t.memoizedProps.value;O(rl,r._currentValue),r._currentValue=l;break;case 13:if(r=t.memoizedState,r!==null)return r.dehydrated!==null?(O(U,U.current&1),t.flags|=128,null):n&t.child.childLanes?tc(e,t,n):(O(U,U.current&1),e=qe(e,t,n),e!==null?e.sibling:null);O(U,U.current&1);break;case 19:if(r=(n&t.childLanes)!==0,e.flags&128){if(r)return nc(e,t,n);t.flags|=128}if(l=t.memoizedState,l!==null&&(l.rendering=null,l.tail=null,l.lastEffect=null),O(U,U.current),r)break;return null;case 22:case 23:return t.lanes=0,qa(e,t,n)}return qe(e,t,n)}var rc,Ko,lc,oc;rc=function(e,t){for(var n=t.child;n!==null;){if(n.tag===5||n.tag===6)e.appendChild(n.stateNode);else if(n.tag!==4&&n.child!==null){n.child.return=n,n=n.child;continue}if(n===t)break;for(;n.sibling===null;){if(n.return===null||n.return===t)return;n=n.return}n.sibling.return=n.return,n=n.sibling}};Ko=function(){};lc=function(e,t,n,r){var l=e.memoizedProps;if(l!==r){e=t.stateNode,Lt(He.current);var o=null;switch(n){case"input":l=mo(e,l),r=mo(e,r),o=[];break;case"select":l=B({},l,{value:void 0}),r=B({},r,{value:void 0}),o=[];break;case"textarea":l=yo(e,l),r=yo(e,r),o=[];break;default:typeof l.onClick!="function"&&typeof r.onClick=="function"&&(e.onclick=qr)}wo(n,r);var s;n=null;for(c in l)if(!r.hasOwnProperty(c)&&l.hasOwnProperty(c)&&l[c]!=null)if(c==="style"){var i=l[c];for(s in i)i.hasOwnProperty(s)&&(n||(n={}),n[s]="")}else c!=="dangerouslySetInnerHTML"&&c!=="children"&&c!=="suppressContentEditableWarning"&&c!=="suppressHydrationWarning"&&c!=="autoFocus"&&(Vn.hasOwnProperty(c)?o||(o=[]):(o=o||[]).push(c,null));for(c in r){var a=r[c];if(i=l?.[c],r.hasOwnProperty(c)&&a!==i&&(a!=null||i!=null))if(c==="style")if(i){for(s in i)!i.hasOwnProperty(s)||a&&a.hasOwnProperty(s)||(n||(n={}),n[s]="");for(s in a)a.hasOwnProperty(s)&&i[s]!==a[s]&&(n||(n={}),n[s]=a[s])}else n||(o||(o=[]),o.push(c,n)),n=a;else c==="dangerouslySetInnerHTML"?(a=a?a.__html:void 0,i=i?i.__html:void 0,a!=null&&i!==a&&(o=o||[]).push(c,a)):c==="children"?typeof a!="string"&&typeof a!="number"||(o=o||[]).push(c,""+a):c!=="suppressContentEditableWarning"&&c!=="suppressHydrationWarning"&&(Vn.hasOwnProperty(c)?(a!=null&&c==="onScroll"&&A("scroll",e),o||i===a||(o=[])):(o=o||[]).push(c,a))}n&&(o=o||[]).push("style",n);var c=o;(t.updateQueue=c)&&(t.flags|=4)}};oc=function(e,t,n,r){n!==r&&(t.flags|=4)};function Nn(e,t){if(!V)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;t!==null;)t.alternate!==null&&(n=t),t=t.sibling;n===null?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;n!==null;)n.alternate!==null&&(r=n),n=n.sibling;r===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:r.sibling=null}}function re(e){var t=e.alternate!==null&&e.alternate.child===e.child,n=0,r=0;if(t)for(var l=e.child;l!==null;)n|=l.lanes|l.childLanes,r|=l.subtreeFlags&14680064,r|=l.flags&14680064,l.return=e,l=l.sibling;else for(l=e.child;l!==null;)n|=l.lanes|l.childLanes,r|=l.subtreeFlags,r|=l.flags,l.return=e,l=l.sibling;return e.subtreeFlags|=r,e.childLanes=n,t}function $d(e,t,n){var r=t.pendingProps;switch(Cs(t),t.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return re(t),null;case 1:return me(t.type)&&br(),re(t),null;case 3:return r=t.stateNode,fn(),$(pe),$(oe),zs(),r.pendingContext&&(r.context=r.pendingContext,r.pendingContext=null),(e===null||e.child===null)&&(Sr(t)?t.flags|=4:e===null||e.memoizedState.isDehydrated&&!(t.flags&256)||(t.flags|=1024,Re!==null&&(es(Re),Re=null))),Ko(e,t),re(t),null;case 5:Ts(t);var l=Lt(qn.current);if(n=t.type,e!==null&&t.stateNode!=null)lc(e,t,n,r,l),e.ref!==t.ref&&(t.flags|=512,t.flags|=2097152);else{if(!r){if(t.stateNode===null)throw Error(g(166));return re(t),null}if(e=Lt(He.current),Sr(t)){r=t.stateNode,n=t.type;var o=t.memoizedProps;switch(r[Ve]=t,r[Zn]=o,e=(t.mode&1)!==0,n){case"dialog":A("cancel",r),A("close",r);break;case"iframe":case"object":case"embed":A("load",r);break;case"video":case"audio":for(l=0;l<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=s.createElement(n,{is:r.is}):(e=s.createElement(n),n==="select"&&(s=e,r.multiple?s.multiple=!0:r.size&&(s.size=r.size))):e=s.createElementNS(e,n),e[Ve]=t,e[Zn]=r,rc(e,t,!1,!1),t.stateNode=e;e:{switch(s=xo(n,r),n){case"dialog":A("cancel",e),A("close",e),l=r;break;case"iframe":case"object":case"embed":A("load",e),l=r;break;case"video":case"audio":for(l=0;lpn&&(t.flags|=128,r=!0,Nn(o,!1),t.lanes=4194304)}else{if(!r)if(e=sl(s),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Nn(o,!0),o.tail===null&&o.tailMode==="hidden"&&!s.alternate&&!V)return re(t),null}else 2*K()-o.renderingStartTime>pn&&n!==1073741824&&(t.flags|=128,r=!0,Nn(o,!1),t.lanes=4194304);o.isBackwards?(s.sibling=t.child,t.child=s):(n=o.last,n!==null?n.sibling=s:t.child=s,o.last=s)}return o.tail!==null?(t=o.tail,o.rendering=t,o.tail=t.sibling,o.renderingStartTime=K(),t.sibling=null,n=U.current,O(U,r?n&1|2:n&1),t):(re(t),null);case 22:case 23:return Bs(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?ye&1073741824&&(re(t),t.subtreeFlags&6&&(t.flags|=8192)):re(t),null;case 24:return null;case 25:return null}throw Error(g(156,t.tag))}function Vd(e,t){switch(Cs(t),t.tag){case 1:return me(t.type)&&br(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return fn(),$(pe),$(oe),zs(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Ts(t),null;case 13:if($(U),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(g(340));an()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return $(U),null;case 4:return fn(),null;case 10:return Es(t.type._context),null;case 22:case 23:return Bs(),null;case 24:return null;default:return null}}var Nr=!1,le=!1,Ud=typeof WeakSet=="function"?WeakSet:Set,j=null;function Jt(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Q(e,t,r)}else n.current=null}function Yo(e,t,n){try{n()}catch(r){Q(e,t,r)}}var Gi=!1;function Hd(e,t){if(Po=Gr,e=ca(),ks(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break e}var s=0,i=-1,a=-1,c=0,m=0,v=e,h=null;t:for(;;){for(var S;v!==n||l!==0&&v.nodeType!==3||(i=s+l),v!==o||r!==0&&v.nodeType!==3||(a=s+r),v.nodeType===3&&(s+=v.nodeValue.length),(S=v.firstChild)!==null;)h=v,v=S;for(;;){if(v===e)break t;if(h===n&&++c===l&&(i=s),h===o&&++m===r&&(a=s),(S=v.nextSibling)!==null)break;v=h,h=v.parentNode}v=S}n=i===-1||a===-1?null:{start:i,end:a}}else n=null}n=n||{start:0,end:0}}else n=null;for(To={focusedElem:e,selectionRange:n},Gr=!1,j=t;j!==null;)if(t=j,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,j=e;else for(;j!==null;){t=j;try{var x=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(x!==null){var k=x.memoizedProps,R=x.memoizedState,d=t.stateNode,f=d.getSnapshotBeforeUpdate(t.elementType===t.type?k:Pe(t.type,k),R);d.__reactInternalSnapshotBeforeUpdate=f}break;case 3:var p=t.stateNode.containerInfo;p.nodeType===1?p.textContent="":p.nodeType===9&&p.documentElement&&p.removeChild(p.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(g(163))}}catch(y){Q(t,t.return,y)}if(e=t.sibling,e!==null){e.return=t.return,j=e;break}j=t.return}return x=Gi,Gi=!1,x}function Fn(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var o=l.destroy;l.destroy=void 0,o!==void 0&&Yo(t,n,o)}l=l.next}while(l!==r)}}function _l(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Xo(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function sc(e){var t=e.alternate;t!==null&&(e.alternate=null,sc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ve],delete t[Zn],delete t[Do],delete t[_d],delete t[Nd])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function ic(e){return e.tag===5||e.tag===3||e.tag===4}function Zi(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||ic(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Go(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=qr));else if(r!==4&&(e=e.child,e!==null))for(Go(e,t,n),e=e.sibling;e!==null;)Go(e,t,n),e=e.sibling}function Zo(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Zo(e,t,n),e=e.sibling;e!==null;)Zo(e,t,n),e=e.sibling}var b=null,Te=!1;function rt(e,t,n){for(n=n.child;n!==null;)uc(e,t,n),n=n.sibling}function uc(e,t,n){if(Ue&&typeof Ue.onCommitFiberUnmount=="function")try{Ue.onCommitFiberUnmount(vl,n)}catch{}switch(n.tag){case 5:le||Jt(n,t);case 6:var r=b,l=Te;b=null,rt(e,t,n),b=r,Te=l,b!==null&&(Te?(e=b,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):b.removeChild(n.stateNode));break;case 18:b!==null&&(Te?(e=b,n=n.stateNode,e.nodeType===8?Xl(e.parentNode,n):e.nodeType===1&&Xl(e,n),Qn(e)):Xl(b,n.stateNode));break;case 4:r=b,l=Te,b=n.stateNode.containerInfo,Te=!0,rt(e,t,n),b=r,Te=l;break;case 0:case 11:case 14:case 15:if(!le&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var o=l,s=o.destroy;o=o.tag,s!==void 0&&(o&2||o&4)&&Yo(n,t,s),l=l.next}while(l!==r)}rt(e,t,n);break;case 1:if(!le&&(Jt(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(i){Q(n,t,i)}rt(e,t,n);break;case 21:rt(e,t,n);break;case 22:n.mode&1?(le=(r=le)||n.memoizedState!==null,rt(e,t,n),le=r):rt(e,t,n);break;default:rt(e,t,n)}}function Ji(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Ud),t.forEach(function(r){var l=Jd.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function Le(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=s),r&=~o}if(r=l,r=K()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Wd(r/1960))-r,10e?16:e,at===null)var r=!1;else{if(e=at,at=null,fl=0,D&6)throw Error(g(331));var l=D;for(D|=4,j=e.current;j!==null;){var o=j,s=o.child;if(j.flags&16){var i=o.deletions;if(i!==null){for(var a=0;aK()-Us?Pt(e,0):Vs|=n),he(e,t)}function vc(e,t){t===0&&(e.mode&1?(t=vr,vr<<=1,!(vr&130023424)&&(vr=4194304)):t=1);var n=ue();e=Je(e,t),e!==null&&(lr(e,t,n),he(e,n))}function Zd(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),vc(e,n)}function Jd(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(g(314))}r!==null&&r.delete(t),vc(e,n)}var yc;yc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||pe.current)de=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return de=!1,Ad(e,t,n);de=!!(e.flags&131072)}else de=!1,V&&t.flags&1048576&&ka(t,nl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Vr(e,t),e=t.pendingProps;var l=un(t,oe.current);ln(t,n),l=Ds(null,t,r,e,l,n);var o=Is();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,me(r)?(o=!0,el(t)):o=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ls(t),l.updater=Cl,t.stateNode=l,l._reactInternals=t,Vo(t,r,e,n),t=Bo(null,t,r,!0,o,n)):(t.tag=0,V&&o&&Ss(t),se(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Vr(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=bd(r),e=Pe(r,e),l){case 0:t=Ho(null,t,r,e,n);break e;case 1:t=Ki(null,t,r,e,n);break e;case 11:t=Wi(null,t,r,e,n);break e;case 14:t=Qi(null,t,r,Pe(r.type,e),n);break e}throw Error(g(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Ho(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Ki(e,t,r,l,n);case 3:e:{if(ec(t),e===null)throw Error(g(387));r=t.pendingProps,o=t.memoizedState,l=o.element,Ea(e,t),ol(t,r,null,n);var s=t.memoizedState;if(r=s.element,o.isDehydrated)if(o={element:r,isDehydrated:!1,cache:s.cache,pendingSuspenseBoundaries:s.pendingSuspenseBoundaries,transitions:s.transitions},t.updateQueue.baseState=o,t.memoizedState=o,t.flags&256){l=dn(Error(g(423)),t),t=Yi(e,t,r,n,l);break e}else if(r!==l){l=dn(Error(g(424)),t),t=Yi(e,t,r,n,l);break e}else for(ge=pt(t.stateNode.containerInfo.firstChild),we=t,V=!0,Re=null,n=Na(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(an(),r===l){t=qe(e,t,n);break e}se(e,t,r,n)}t=t.child}return t;case 5:return Ma(t),e===null&&Fo(t),r=t.type,l=t.pendingProps,o=e!==null?e.memoizedProps:null,s=l.children,zo(r,l)?s=null:o!==null&&zo(r,o)&&(t.flags|=32),ba(e,t),se(e,t,s,n),t.child;case 6:return e===null&&Fo(t),null;case 13:return tc(e,t,n);case 4:return Ps(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=cn(t,null,r,n):se(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Wi(e,t,r,l,n);case 7:return se(e,t,t.pendingProps,n),t.child;case 8:return se(e,t,t.pendingProps.children,n),t.child;case 12:return se(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,o=t.memoizedProps,s=l.value,O(rl,r._currentValue),r._currentValue=s,o!==null)if(Fe(o.value,s)){if(o.children===l.children&&!pe.current){t=qe(e,t,n);break e}}else for(o=t.child,o!==null&&(o.return=t);o!==null;){var i=o.dependencies;if(i!==null){s=o.child;for(var a=i.firstContext;a!==null;){if(a.context===r){if(o.tag===1){a=Xe(-1,n&-n),a.tag=2;var c=o.updateQueue;if(c!==null){c=c.shared;var m=c.pending;m===null?a.next=a:(a.next=m.next,m.next=a),c.pending=a}}o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),Ao(o.return,n,t),i.lanes|=n;break}a=a.next}}else if(o.tag===10)s=o.type===t.type?null:o.child;else if(o.tag===18){if(s=o.return,s===null)throw Error(g(341));s.lanes|=n,i=s.alternate,i!==null&&(i.lanes|=n),Ao(s,n,t),s=o.sibling}else s=o.child;if(s!==null)s.return=o;else for(s=o;s!==null;){if(s===t){s=null;break}if(o=s.sibling,o!==null){o.return=s.return,s=o;break}s=s.return}o=s}se(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,ln(t,n),l=Ee(l),r=r(l),t.flags|=1,se(e,t,r,n),t.child;case 14:return r=t.type,l=Pe(r,t.pendingProps),l=Pe(r.type,l),Qi(e,t,r,l,n);case 15:return Ja(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Pe(r,l),Vr(e,t),t.tag=1,me(r)?(e=!0,el(t)):e=!1,ln(t,n),Xa(t,r,l),Vo(t,r,l,n),Bo(null,t,r,!0,e,n);case 19:return nc(e,t,n);case 22:return qa(e,t,n)}throw Error(g(156,t.tag))};function gc(e,t){return Qu(e,t)}function qd(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Ne(e,t,n,r){return new qd(e,t,n,r)}function Qs(e){return e=e.prototype,!(!e||!e.isReactComponent)}function bd(e){if(typeof e=="function")return Qs(e)?1:0;if(e!=null){if(e=e.$$typeof,e===cs)return 11;if(e===fs)return 14}return 2}function yt(e,t){var n=e.alternate;return n===null?(n=Ne(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Br(e,t,n,r,l,o){var s=2;if(r=e,typeof e=="function")Qs(e)&&(s=1);else if(typeof e=="string")s=5;else e:switch(e){case Ht:return Tt(n.children,l,o,t);case as:s=8,l|=8;break;case ao:return e=Ne(12,n,t,l|2),e.elementType=ao,e.lanes=o,e;case co:return e=Ne(13,n,t,l),e.elementType=co,e.lanes=o,e;case fo:return e=Ne(19,n,t,l),e.elementType=fo,e.lanes=o,e;case Mu:return jl(n,l,o,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case ju:s=10;break e;case Eu:s=9;break e;case cs:s=11;break e;case fs:s=14;break e;case lt:s=16,r=null;break e}throw Error(g(130,e==null?e:typeof e,""))}return t=Ne(s,n,t,l),t.elementType=e,t.type=r,t.lanes=o,t}function Tt(e,t,n,r){return e=Ne(7,e,r,t),e.lanes=n,e}function jl(e,t,n,r){return e=Ne(22,e,r,t),e.elementType=Mu,e.lanes=n,e.stateNode={isHidden:!1},e}function no(e,t,n){return e=Ne(6,e,null,t),e.lanes=n,e}function ro(e,t,n){return t=Ne(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function ep(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Fl(0),this.expirationTimes=Fl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Fl(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Ks(e,t,n,r,l,o,s,i,a){return e=new ep(e,t,n,i,a),t===1?(t=1,o===!0&&(t|=8)):t=0,o=Ne(3,null,null,t),e.current=o,o.stateNode=e,o.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ls(o),e}function tp(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Sc)}catch(e){console.error(e)}}Sc(),Su.exports=ke;var sp=Su.exports,ou=sp;io.createRoot=ou.createRoot,io.hydrateRoot=ou.hydrateRoot;function ip({strokeWidth:e=60,...t}){return u.jsx("svg",{viewBox:"0 0 1188 1773",fill:"none",xmlns:"http://www.w3.org/2000/svg",role:"img","aria-hidden":"true",...t,children:u.jsx("path",{d:"M25 561L245 694M25 561V818M245 694V951M25 961V1218M25 1357V1614M245 1489V1747M245 1093V1351M942 823V1080M1161 955V1213M1162 555V812M942 422V679M669 585V843L787 913M942 25V282M1162 158V415M25 818L245 951M244 1094L464 962M25 961L143 890M244 1352L464 1219M942 823L1162 956M942 679L1162 812M721 811L942 679M669 842L724 809M669 586L724 553M1041 883L1162 812M245 1747L1161 1213M244 1490L942 1080M25 1357L142 1289M518 1071L942 823M721 555L942 422M942 422L1162 556M942 282L1162 415M942 25L1162 158M942 1080L1161 1213M25 1218L245 1351M25 961L245 1094M464 962L519 929M464 1219L519 1186V928L403 859M25 1357L245 1490M25 1614L245 1747M25 561L942 25M244 694L941 282M1043 484L1162 415M245 951L668 704",stroke:"currentColor",strokeWidth:e,strokeLinecap:"round"})})}function up(e){return u.jsxs("svg",{viewBox:"269 80 364 110",fill:"none",xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":"Patter",...e,children:[u.jsx("path",{d:"M271.422 182.689V85.9524H317.517C324.705 85.9524 330.86 87.2064 335.982 89.7143C341.193 92.2223 345.192 95.7156 347.977 100.194C350.852 104.673 352.29 109.913 352.29 115.914C352.29 121.915 350.852 127.2 347.977 131.768C345.102 136.336 341.058 139.919 335.847 142.516C330.725 145.024 324.615 146.278 317.517 146.278H287.866V130.424H316.439C321.201 130.424 324.885 129.125 327.491 126.528C330.186 123.841 331.534 120.348 331.534 116.048C331.534 111.749 330.186 108.3 327.491 105.703C324.885 103.105 321.201 101.806 316.439 101.806H292.178V182.689H271.422Z",fill:"currentColor"}),u.jsx("path",{d:"M395.375 182.689C394.836 180.718 394.432 178.613 394.162 176.374C393.982 174.135 393.893 171.537 393.893 168.581H393.353V136.202C393.353 133.425 392.41 131.275 390.523 129.752C388.726 128.14 386.03 127.334 382.436 127.334C379.022 127.334 376.281 127.916 374.215 129.081C372.238 130.245 370.935 131.947 370.306 134.186H351.033C351.931 128.006 355.121 122.9 360.602 118.87C366.083 114.839 373.586 112.824 383.11 112.824C392.994 112.824 400.542 115.018 405.753 119.407C410.965 123.796 413.57 130.111 413.57 138.351V168.581C413.57 170.821 413.705 173.105 413.975 175.434C414.334 177.673 414.873 180.091 415.592 182.689H395.375ZM371.384 184.032C364.556 184.032 359.12 182.33 355.076 178.927C351.033 175.434 349.011 170.821 349.011 165.088C349.011 158.729 351.392 153.623 356.154 149.772C361.006 145.83 367.745 143.278 376.371 142.113L396.453 139.292V150.981L379.741 153.533C376.147 154.071 373.496 155.056 371.789 156.489C370.082 157.922 369.228 159.893 369.228 162.401C369.228 164.64 370.037 166.342 371.654 167.507C373.271 168.671 375.428 169.253 378.123 169.253C382.347 169.253 385.941 168.134 388.906 165.894C391.871 163.565 393.353 160.878 393.353 157.833L395.24 168.581C393.264 173.687 390.254 177.538 386.21 180.136C382.167 182.734 377.225 184.032 371.384 184.032Z",fill:"currentColor"}),u.jsx("path",{d:"M450.248 184.167C441.443 184.167 434.883 182.062 430.57 177.852C426.347 173.553 424.236 167.059 424.236 158.37V98.8506L444.453 91.3266V159.042C444.453 162.087 445.306 164.372 447.014 165.894C448.721 167.417 451.371 168.178 454.966 168.178C456.313 168.178 457.571 168.044 458.739 167.775C459.907 167.507 461.075 167.193 462.244 166.835V182.151C461.075 182.778 459.413 183.271 457.257 183.629C455.19 183.988 452.854 184.167 450.248 184.167ZM411.432 129.484V114.167H462.244V129.484H411.432Z",fill:"currentColor"}),u.jsx("path",{d:"M500.501 184.167C491.695 184.167 485.136 182.062 480.823 177.852C476.6 173.553 474.489 167.059 474.489 158.37V98.8506L494.705 91.3266V159.042C494.705 162.087 495.559 164.372 497.266 165.894C498.973 167.417 501.624 168.178 505.218 168.178C506.566 168.178 507.824 168.044 508.992 167.775C510.16 167.507 511.328 167.193 512.496 166.835V182.151C511.328 182.778 509.666 183.271 507.509 183.629C505.443 183.988 503.107 184.167 500.501 184.167ZM461.684 129.484V114.167H512.496V129.484H461.684Z",fill:"currentColor"}),u.jsx("path",{d:"M547.852 184.032C540.214 184.032 533.565 182.554 527.904 179.599C522.244 176.553 517.841 172.343 514.696 166.969C511.641 161.595 510.113 155.414 510.113 148.428C510.113 141.352 511.641 135.171 514.696 129.887C517.841 124.513 522.199 120.348 527.769 117.392C533.34 114.346 539.81 112.824 547.178 112.824C554.276 112.824 560.431 114.257 565.642 117.123C570.854 119.989 574.897 123.975 577.773 129.081C580.648 134.186 582.086 140.187 582.086 147.084C582.086 148.518 582.041 149.861 581.951 151.115C581.861 152.279 581.726 153.399 581.546 154.474H521.974V141.173H565.238L561.734 143.591C561.734 138.038 560.386 133.962 557.69 131.365C555.085 128.678 551.491 127.334 546.908 127.334C541.607 127.334 537.474 129.125 534.508 132.708C531.633 136.291 530.196 141.665 530.196 148.831C530.196 155.818 531.633 161.013 534.508 164.416C537.474 167.82 541.876 169.522 547.717 169.522C550.952 169.522 553.737 168.984 556.073 167.91C558.409 166.835 560.161 165.088 561.33 162.67H580.333C578.087 169.298 574.223 174.538 568.742 178.389C563.351 182.151 556.388 184.032 547.852 184.032Z",fill:"currentColor"}),u.jsx("path",{d:"M586.158 182.689V114.167H605.971V130.29H606.375V182.689H586.158ZM606.375 146.95L604.623 130.693C606.24 124.871 608.891 120.437 612.575 117.392C616.259 114.346 620.842 112.824 626.323 112.824C628.03 112.824 629.288 113.003 630.096 113.361V132.171C629.647 131.992 629.018 131.902 628.21 131.902C627.401 131.813 626.412 131.768 625.244 131.768C618.775 131.768 614.013 132.932 610.958 135.261C607.903 137.5 606.375 141.397 606.375 146.95Z",fill:"currentColor"})]})}function ap(){return u.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:8,color:"var(--ink)"},"aria-label":"Patter",children:[u.jsx(ip,{height:26}),u.jsx(up,{height:24})]})}function cp({liveCount:e,todayCount:t,phoneNumber:n,sdkVersion:r}){return u.jsxs("header",{className:"top",children:[u.jsxs("div",{className:"brand",children:[u.jsx(ap,{}),u.jsxs("span",{className:"tag",children:["dashboard · v",r]})]}),u.jsxs("div",{className:"top-r",children:[u.jsxs("span",{className:"live-chip",children:[u.jsx("span",{className:"pulse"+(e>0?" active":"")}),e," live · ",t," today"]}),n!=="—"&&u.jsx("span",{className:"num-chip",children:n})]})]})}function fp(e){return u.jsxs("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("circle",{cx:"11",cy:"11",r:"7"}),u.jsx("path",{d:"m21 21-4.3-4.3"})]})}function Cc(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.4",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M7 13l5 5 5-5"}),u.jsx("path",{d:"M12 4v14"})]})}function dp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.4",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M17 11l-5-5-5 5"}),u.jsx("path",{d:"M12 20V6"})]})}function pp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("rect",{x:"9",y:"2",width:"6",height:"12",rx:"3"}),u.jsx("path",{d:"M19 10a7 7 0 0 1-14 0"}),u.jsx("path",{d:"M12 19v3"})]})}function mp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("polyline",{points:"15 17 20 12 15 7"}),u.jsx("path",{d:"M4 18v-2a4 4 0 0 1 4-4h12"})]})}function hp(e){return u.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"currentColor",...e,children:u.jsx("circle",{cx:"12",cy:"12",r:"6"})})}function vp(e){return u.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...e,children:[u.jsx("path",{d:"M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67"}),u.jsx("path",{d:"M22 2 2 22"})]})}const yp=["1h","24h","7d","All"];function gp(){const e=document.createElement("a");e.href="/api/dashboard/export/calls?format=csv",e.download="patter_calls.csv",e.rel="noopener",document.body.appendChild(e),e.click(),document.body.removeChild(e)}function wp({range:e,setRange:t}){return u.jsxs("div",{className:"ph",children:[u.jsxs("div",{children:[u.jsx("h1",{children:"Calls"}),u.jsxs("p",{className:"sub",children:["Real-time view of every call routed through this Patter instance."," ",u.jsx("span",{className:"kbd",children:"⇧K"})," to focus search."]})]}),u.jsxs("div",{className:"filters",children:[u.jsx("div",{className:"seg",children:yp.map(n=>u.jsx("button",{type:"button",className:e===n?"on":"",onClick:()=>t(n),children:n},n))}),u.jsxs("button",{className:"btn",type:"button",onClick:gp,children:[u.jsx(Cc,{})," Export CSV"]})]})]})}function ml(e){const t=Math.floor(e/60),n=Math.floor(e%60);return`${String(t).padStart(2,"0")}:${String(n).padStart(2,"0")}`}function ze(e){if(e==null||!Number.isFinite(e))return"$0.00";const t=Math.abs(e);return t===0?"$0.00":t>=.01?`$${e.toFixed(2)}`:t>=.001?`$${e.toFixed(3)}`:t>=1e-4?`$${e.toFixed(4)}`:`$${e.toFixed(5)}`}const _c=60*60*1e3,xp=24*_c;function Mr(e){return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function kp(e){return new Date(e).toLocaleDateString([],{weekday:"short",month:"short",day:"numeric"})}function su(e){return new Date(e).toLocaleString([],{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function Nc(e){const t=e.toMs-e.fromMs;return t>=xp-Sp?kp(e.fromMs):t>=_c?`${Mr(e.fromMs)} → ${Mr(e.toMs)}`:t>=60*1e3?`${Mr(e.fromMs)} → ${Mr(e.toMs)}`:`${su(e.fromMs)} → ${su(e.toMs)}`}const Sp=5e3;function jc(e){return e.cost.total??(e.cost.telco??0)+(e.cost.llm??0)+(e.cost.sttTts??0)}function Cp(e){return e.calls.length===0?void 0:[...e.calls].sort((n,r)=>(r.startedAtMs??0)-(n.startedAtMs??0))[0]?.id}function _p(e,t){const n=e.calls,r=n.length;if(t==="spend"){const l=n.reduce((o,s)=>o+jc(s),0);return{label:"TOTAL COST",value:ze(l)}}if(t==="latency"){const l=n.filter(s=>typeof s.latencyP95=="number");return{label:"AVG LATENCY",value:`${l.length>0?Math.round(l.reduce((s,i)=>s+(i.latencyP95??0),0)/l.length):0} ms`}}return{label:r===1?"CALL":"CALLS",value:`${r}`}}function Np({bucket:e,kind:t}){const n=Nc(e),r=e.calls.length;if(r===0)return u.jsxs("div",{className:"spark-tooltip",children:[u.jsx("div",{className:"spark-tooltip-range",children:n}),u.jsx("div",{className:"spark-tooltip-empty",children:"no calls"})]});const l=_p(e,t),o=e.calls.slice(0,4);return u.jsxs("div",{className:"spark-tooltip",children:[u.jsx("div",{className:"spark-tooltip-range",children:n}),u.jsxs("div",{className:"spark-tooltip-headline",children:[u.jsx("span",{className:"spark-tooltip-headline-l",children:l.label}),u.jsx("span",{className:"spark-tooltip-headline-v",children:l.value})]}),u.jsx("ul",{className:"spark-tooltip-list",children:o.map(s=>{const i=s.direction==="inbound"?s.from:s.to;return u.jsxs("li",{children:[u.jsx("span",{className:"num",children:i}),u.jsx("span",{className:"status",children:s.status}),u.jsx("span",{className:"cost",children:ze(jc(s))})]},s.id)})}),r>o.length&&u.jsxs("div",{className:"spark-tooltip-more",children:["+",r-o.length," more"]})]})}function jp({bucket:e,height:t,interactive:n,kind:r,onSelect:l}){const[o,s]=L.useState(!1),i=!!e&&e.calls.length>0;return!n||!e?u.jsx("span",{className:"spark-bar-static",style:{height:t+"%"}}):u.jsxs("div",{className:"spark-bar-wrap",onMouseEnter:()=>s(!0),onMouseLeave:()=>s(!1),children:[u.jsx("button",{type:"button",className:"spark-bar"+(i?"":" empty"),style:{height:t+"%"},disabled:!i,onClick:()=>{if(!i)return;const a=Cp(e);a&&l&&l(a)},onFocus:()=>s(!0),onBlur:()=>s(!1),"aria-label":`${e.calls.length} calls in ${Nc(e)}`}),o&&u.jsx(Np,{bucket:e,kind:r})]})}function Lr({label:e,value:t,unit:n,delta:r,deltaTone:l,spark:o,buckets:s,onSelectCall:i,kind:a="count",peach:c,footer:m,badge:v}){const h=!!s&&!!i;return u.jsxs("div",{className:"metric"+(c?" peach":""),children:[u.jsxs("div",{className:"lbl",children:[u.jsx("span",{children:e}),v&&u.jsx("span",{className:"badge-now",children:"LIVE"})]}),u.jsxs("div",{className:"val",children:[t,n&&u.jsxs("span",{className:"unit",children:[" ",n]})]}),r&&u.jsx("div",{className:"delta "+(l||""),children:r}),m&&u.jsx("div",{className:"delta",children:m}),u.jsx("div",{className:"spark",children:o.map((S,x)=>u.jsx(jp,{bucket:s?.[x],height:S,interactive:h,kind:a,onSelect:i},x))})]})}function Ep({call:e,isSelected:t,onSelect:n,isNew:r}){const l=e.status==="live"&&e.durationStart?ml((Date.now()-e.durationStart)/1e3):ml(e.duration||0),o=e.latencyP95?Math.min(100,e.latencyP95/1e3*100):0,s=(e.latencyP95??0)>600,i=e.cost.total??(e.cost.telco??0)+(e.cost.llm??0)+(e.cost.sttTts??0),a=e.status.replace("-","");return u.jsxs("tr",{className:(t?"selected ":"")+(r?"new-row":""),onClick:n,children:[u.jsx("td",{children:u.jsx("span",{className:"pill "+a,children:e.status})}),u.jsxs("td",{children:[u.jsx("span",{className:"dir in",style:{marginRight:8,color:e.direction==="inbound"?"#3b6f3b":"#4a4a4a"},children:e.direction==="inbound"?u.jsx(Cc,{}):u.jsx(dp,{})}),u.jsxs("span",{className:"num-cell",children:[e.from," → ",e.to]})]}),u.jsx("td",{children:u.jsxs("span",{className:"car-tw",children:[u.jsx("span",{className:"car-dot "+(e.carrier==="twilio"?"tw":"tx")}),e.carrier==="twilio"?"Twilio":"Telnyx"]})}),u.jsx("td",{className:"num-cell",children:e.status==="no-answer"?"—":l}),u.jsx("td",{children:e.latencyP95?u.jsxs(u.Fragment,{children:[u.jsx("span",{className:"lat-bar"+(s?" warn":""),children:u.jsx("i",{style:{width:o+"%"}})}),u.jsxs("span",{className:"num-cell",children:[e.latencyP95," ms"]})]}):"—"}),u.jsx("td",{className:"num-cell",children:ze(i)})]})}function Mp({calls:e,selectedId:t,onSelect:n,newId:r,search:l,setSearch:o}){const s=L.useMemo(()=>{if(!l.trim())return e;const i=l.toLowerCase();return e.filter(a=>a.from.toLowerCase().includes(i)||a.to.toLowerCase().includes(i)||a.status.includes(i)||a.carrier.includes(i)||a.id.includes(i))},[e,l]);return u.jsxs("div",{className:"panel",children:[u.jsxs("div",{className:"panel-h",children:[u.jsxs("h3",{children:["Recent calls"," ",u.jsxs("span",{style:{fontFamily:"var(--font-mono)",fontSize:11,color:"#aaa",fontWeight:500,marginLeft:4},children:["(",s.length,")"]})]}),u.jsxs("div",{className:"search",children:[u.jsx(fp,{}),u.jsx("input",{placeholder:"Search number, status, carrier…",value:l,onChange:i=>o(i.target.value)})]}),u.jsxs("span",{className:"sse",children:[u.jsx("span",{className:"dot"}),"streaming · SSE"]})]}),u.jsx("div",{style:{maxHeight:540,overflow:"auto"},children:u.jsxs("table",{children:[u.jsx("thead",{children:u.jsxs("tr",{children:[u.jsx("th",{children:"Status"}),u.jsx("th",{children:"From → To"}),u.jsx("th",{children:"Carrier"}),u.jsx("th",{children:"Duration"}),u.jsx("th",{children:"p95 latency"}),u.jsx("th",{children:"Cost"})]})}),u.jsx("tbody",{children:s.length===0?u.jsx("tr",{children:u.jsxs("td",{colSpan:6,className:"empty",children:['No calls match "',l,'"']})}):s.map(i=>u.jsx(Ep,{call:i,isSelected:i.id===t,onSelect:()=>n(i.id),isNew:i.id===r},i.id))})]})})]})}function Lp({start:e}){const[,t]=L.useState(0);return L.useEffect(()=>{const n=setInterval(()=>t(r=>r+1),1e3);return()=>clearInterval(n)},[]),u.jsx(u.Fragment,{children:ml((Date.now()-e)/1e3)})}function Pp({call:e,transcript:t,onEnd:n,recording:r,setRecording:l,muted:o,setMuted:s}){const i=L.useRef(null);if(L.useEffect(()=>{i.current&&(i.current.scrollTop=i.current.scrollHeight)},[t]),!e)return u.jsxs("div",{className:"rr-card",children:[u.jsx("h3",{children:"No live call selected"}),u.jsx("div",{className:"meta",children:"Select a call from the table — or wait for the next ring."})]});const a=e.status==="live";return u.jsxs("div",{className:"rr-card",children:[u.jsxs("h3",{children:["Live call",u.jsx("span",{className:"pill "+(a?"live":"done"),children:e.status})]}),u.jsxs("div",{className:"meta",children:[u.jsx("strong",{children:e.direction==="inbound"?e.from:e.to}),u.jsx("span",{className:"sep",children:"·"}),e.agent]}),u.jsxs("div",{className:"duration-block",children:[u.jsx("span",{className:"l",children:"duration"}),u.jsxs("span",{className:"agent",children:[e.direction==="inbound"?"inbound":"outbound"," ·"," ",e.carrier==="twilio"?"Twilio":"Telnyx"]}),u.jsx("span",{className:"v",children:a&&e.durationStart?u.jsx(Lp,{start:e.durationStart}):ml(e.duration||0)})]}),u.jsx("div",{className:"transcript",ref:i,children:t.map((c,m)=>c.who==="tool"?u.jsxs("div",{className:"turn tool",children:[u.jsx("div",{className:"av",children:"⚙"}),u.jsxs("div",{className:"body",children:[u.jsxs("div",{className:"who",children:["tool · ",c.txt]}),c.args&&u.jsx("div",{className:"tool-call",children:Object.entries(c.args).map(([v,h])=>u.jsxs("span",{children:[u.jsxs("span",{className:"k",children:[v,":"]}),' "',String(h),'"'," "]},v))})]})]},m):u.jsxs("div",{className:"turn "+c.who,children:[u.jsx("div",{className:"av",children:c.who==="user"?"U":"P"}),u.jsxs("div",{className:"body",children:[u.jsxs("div",{className:"who",children:[c.who==="user"?"caller":"agent",c.typing&&" · typing"]}),u.jsx("div",{className:"txt",children:c.typing?u.jsxs("span",{className:"typing",children:[u.jsx("span",{}),u.jsx("span",{}),u.jsx("span",{})]}):c.txt}),c.lat&&!c.typing&&u.jsxs("div",{className:"lat",children:[c.lat.stt&&`stt ${c.lat.stt} ms`,c.lat.total&&`total ${c.lat.total} ms · llm ${c.lat.llm} · tts ${c.lat.tts}`]})]})]},m))}),a&&u.jsxs("div",{className:"controls",children:[u.jsxs("button",{type:"button",className:"ctrl"+(o?" active":""),onClick:()=>s(!o),children:[u.jsx(pp,{})," ",o?"unmute":"mute"]}),u.jsxs("button",{type:"button",className:"ctrl",children:[u.jsx(mp,{})," transfer"]}),u.jsxs("button",{type:"button",className:"ctrl"+(r?" active":""),onClick:()=>l(!r),children:[u.jsx(hp,{})," ",r?"stop rec":"record"]}),u.jsxs("button",{type:"button",className:"ctrl danger",onClick:n,children:[u.jsx(vp,{})," end"]})]})]})}const Tp=e=>!!e&&typeof e.latencyP95=="number",zp=e=>!!e&&(typeof e.cost.telco=="number"||typeof e.cost.llm=="number"||typeof e.cost.sttTts=="number"||typeof e.cost.total=="number");function Rp({call:e}){const[t,n]=L.useState("latency"),r=Tp(e),l=zp(e);if(!e||!r&&!l)return null;const o=t==="latency"&&!r?"cost":t==="cost"&&!l?"latency":t;return u.jsxs("div",{className:"rr-card metrics-panel",children:[u.jsx("div",{className:"metrics-panel-h",children:u.jsxs("div",{className:"seg",role:"tablist",children:[u.jsx("button",{type:"button",role:"tab","aria-selected":o==="latency",disabled:!r,className:o==="latency"?"on":"",onClick:()=>n("latency"),children:"Latency"}),u.jsx("button",{type:"button",role:"tab","aria-selected":o==="cost",disabled:!l,className:o==="cost"?"on":"",onClick:()=>n("cost"),children:"Cost"})]})}),o==="latency"&&r&&u.jsx(Dp,{call:e}),o==="cost"&&l&&u.jsx(Ip,{call:e})]})}function Dp({call:e}){const t=e.latencyP50??0,n=e.latencyP95??0;if(e.mode==="realtime"){const m=(e.turnCount??0)>=2;return u.jsxs(u.Fragment,{children:[u.jsxs("div",{className:"lat-grid",children:[u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"end-to-end p50"}),u.jsxs("div",{className:"v",children:[m&&t||"—",m&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox"+(m&&n>600?" warn":""),children:[u.jsx("div",{className:"l",children:"end-to-end p95"}),u.jsxs("div",{className:"v",children:[m&&n||"—",m&&u.jsx("span",{className:"u",children:"ms"})]})]})]}),u.jsx("div",{className:"waterfall",children:u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"e2e"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar llm",style:{left:0,width:Math.min(100,n/1e3*100)+"%"}})}),u.jsx("span",{className:"v",children:n})]})}),u.jsxs("div",{className:"wf-legend",children:[u.jsxs("span",{children:[u.jsx("i",{style:{background:"#DF9367"}}),"end-to-end"]}),u.jsx("span",{style:{marginLeft:"auto"},children:e.agent??"realtime"})]})]})}const l=e.sttAvg||0,o=e.llmAvg||0,s=e.ttsAvg||0,i=l+o+s,a=Math.max(i,800),c=(e.turnCount??0)>=2;return u.jsxs(u.Fragment,{children:[u.jsxs("div",{className:"lat-grid",children:[u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"p50"}),u.jsxs("div",{className:"v",children:[c?e.latencyP50??"—":"—",c&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox"+(c&&n>600?" warn":""),children:[u.jsx("div",{className:"l",children:"p95"}),u.jsxs("div",{className:"v",children:[c?n:"—",c&&u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"stt avg"}),u.jsxs("div",{className:"v",children:[e.sttAvg??"—",u.jsx("span",{className:"u",children:"ms"})]})]}),u.jsxs("div",{className:"latbox",children:[u.jsx("div",{className:"l",children:"tts avg"}),u.jsxs("div",{className:"v",children:[e.ttsAvg??"—",u.jsx("span",{className:"u",children:"ms"})]})]})]}),u.jsxs("div",{className:"waterfall",children:[u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"stt"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar stt",style:{left:0,width:l/a*100+"%"}})}),u.jsx("span",{className:"v",children:l})]}),u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"llm"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar llm",style:{left:l/a*100+"%",width:o/a*100+"%"}})}),u.jsx("span",{className:"v",children:o})]}),u.jsxs("div",{className:"wf-row",children:[u.jsx("span",{className:"lbl",children:"tts"}),u.jsx("span",{className:"track",children:u.jsx("span",{className:"seg-bar tts",style:{left:(l+o)/a*100+"%",width:s/a*100+"%"}})}),u.jsx("span",{className:"v",children:s})]})]}),u.jsxs("div",{className:"wf-legend",children:[u.jsxs("span",{children:[u.jsx("i",{style:{background:"#1a1a1a"}}),"stt"]}),u.jsxs("span",{children:[u.jsx("i",{style:{background:"#DF9367"}}),"llm"]}),u.jsxs("span",{children:[u.jsx("i",{style:{background:"#278EFF",opacity:.8}}),"tts"]}),u.jsxs("span",{style:{marginLeft:"auto"},children:["total ",i," ms"]})]})]})}function lo(e){if(e.length===0)return e;const t=e.replace(/(?:_(?:ws|rest|stt|tts|llm))+$/i,"");return t.charAt(0).toUpperCase()+t.slice(1)}function Ip({call:e}){const t=e.cost,n=t.telco??0,r=t.llm??0,l=t.stt??0,o=t.tts??0,s=t.sttTts??0,i=l===0&&o===0?s:0,a=t.cached??0,c=n+r+l+o+i,m=t.total??c-a,v=k=>c>0?k/c*100:0,h=e.sttProvider?`${lo(e.sttProvider)} STT${e.sttModel?` · ${e.sttModel}`:""}`:"STT",S=e.ttsProvider?`${lo(e.ttsProvider)} TTS${e.ttsModel?` · ${e.ttsModel}`:""}`:"TTS",x=e.llmModel?`${e.model?lo(e.model)+" · ":""}${e.llmModel}`:e.model||"LLM";return u.jsxs(u.Fragment,{children:[c>0&&u.jsxs("div",{className:"cost-bar",children:[u.jsx("i",{style:{background:"#cc0000",width:v(n)+"%"}}),u.jsx("i",{style:{background:"#DF9367",width:v(r)+"%"}}),u.jsx("i",{style:{background:"#1a1a1a",width:v(l+i)+"%"}}),u.jsx("i",{style:{background:"#6c6c6c",width:v(o)+"%"}})]}),n>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#cc0000"}}),e.carrier==="twilio"?"Twilio":"Telnyx"]}),u.jsx("span",{className:"v",children:ze(n)})]}),r>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#DF9367"}}),x]}),u.jsx("span",{className:"v",children:ze(r)}),a>0&&u.jsxs("span",{className:"saved",children:["−",ze(a)," cached"]})]}),l>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#1a1a1a"}}),h]}),u.jsx("span",{className:"v",children:ze(l)})]}),o>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#6c6c6c"}}),S]}),u.jsx("span",{className:"v",children:ze(o)})]}),i>0&&u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:[u.jsx("span",{className:"swatch",style:{background:"#1a1a1a"}}),"STT / TTS (legacy)"]}),u.jsx("span",{className:"v",children:ze(i)})]}),u.jsxs("div",{className:"stack-row",children:[u.jsxs("span",{className:"lbl",children:["Total"," ",e.status==="live"&&u.jsx("span",{style:{fontFamily:"var(--font-mono)",fontSize:10,color:"#aaa",marginLeft:4},children:"(running)"})]}),u.jsx("span",{className:"v",children:ze(m)})]})]})}const $t=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),bt=e=>typeof e=="string"?e:"",De=e=>typeof e=="number"&&Number.isFinite(e)?e:0,ie=e=>typeof e=="number"&&Number.isFinite(e)?e:void 0,Be=e=>typeof e=="string"&&e.length>0?e:void 0;function Pr(e){if($t(e))return{stt_ms:ie(e.stt_ms),llm_ms:ie(e.llm_ms),tts_ms:ie(e.tts_ms),total_ms:ie(e.total_ms),agent_response_ms:ie(e.agent_response_ms),endpoint_ms:ie(e.endpoint_ms),user_speech_duration_ms:ie(e.user_speech_duration_ms)}}function Op(e){if($t(e))return{stt:ie(e.stt),tts:ie(e.tts),llm:ie(e.llm),telephony:ie(e.telephony),total:ie(e.total),llm_cached_savings:ie(e.llm_cached_savings)}}function Fp(e){if(!$t(e))return null;const t=e.turns;return{duration_seconds:ie(e.duration_seconds),provider_mode:Be(e.provider_mode),telephony_provider:Be(e.telephony_provider),stt_provider:Be(e.stt_provider),tts_provider:Be(e.tts_provider),llm_provider:Be(e.llm_provider),stt_model:Be(e.stt_model),tts_model:Be(e.tts_model),llm_model:Be(e.llm_model),cost:Op(e.cost),latency_avg:Pr(e.latency_avg),latency_p50:Pr(e.latency_p50),latency_p95:Pr(e.latency_p95),latency_p99:Pr(e.latency_p99),turns:Array.isArray(t)?t:void 0}}function Ap(e){if(!Array.isArray(e))return;const t=[];for(const n of e)$t(n)&&t.push({role:bt(n.role),text:bt(n.text),timestamp:De(n.timestamp)});return t}function Ec(e){if(!$t(e))return null;const t=bt(e.call_id);if(t.length===0)return null;const n=e.turns;return{call_id:t,caller:bt(e.caller),callee:bt(e.callee),direction:bt(e.direction),started_at:De(e.started_at),ended_at:ie(e.ended_at),status:Be(e.status),transcript:Ap(e.transcript),turns:Array.isArray(n)?n:void 0,metrics:Fp(e.metrics)}}function Mc(e){if(!Array.isArray(e))return[];const t=[];for(const n of e){const r=Ec(n);r&&t.push(r)}return t}function $p(e){return $t(e)?{stt:De(e.stt),tts:De(e.tts),llm:De(e.llm),telephony:De(e.telephony)}:{stt:0,tts:0,llm:0,telephony:0}}function Vp(e){return $t(e)?{total_calls:De(e.total_calls),total_cost:De(e.total_cost),avg_duration:De(e.avg_duration),avg_latency_ms:De(e.avg_latency_ms),cost_breakdown:$p(e.cost_breakdown),active_calls:De(e.active_calls)}:{total_calls:0,total_cost:0,avg_duration:0,avg_latency_ms:0,cost_breakdown:{stt:0,tts:0,llm:0,telephony:0},active_calls:0}}async function Zs(e){const t=await fetch(e,{headers:{Accept:"application/json"}});if(!t.ok)throw new Error(`Request to ${e} failed with status ${t.status}`);return t.json()}async function Up(e=50,t=0){const n=`/api/dashboard/calls?limit=${encodeURIComponent(e)}&offset=${encodeURIComponent(t)}`,r=await Zs(n);return Mc(r)}async function Hp(){const e=await Zs("/api/dashboard/active");return Mc(e)}async function Bp(){const e=await Zs("/api/dashboard/aggregates");return Vp(e)}async function Wp(e){const t=`/api/dashboard/calls/${encodeURIComponent(e)}`,n=await fetch(t,{headers:{Accept:"application/json"}});if(n.status===404)return null;if(!n.ok)throw new Error(`Request to ${t} failed with status ${n.status}`);const r=await n.json();return Ec(r)}const Qp=new Set(["in-progress","initiated"]);function Kp(e){if(!e)return"ended";switch(e){case"in-progress":case"initiated":return"live";case"completed":return"ended";case"no-answer":return"no-answer";case"busy":case"failed":case"canceled":case"webhook_error":return"fail";default:return"ended"}}function Yp(e){return e==="outbound"?"outbound":"inbound"}function Xp(e){return typeof e=="string"&&e.toLowerCase().includes("telnyx")?"telnyx":"twilio"}function Gp(e){if(typeof e!="string")return"unknown";const t=e.toLowerCase();return t.includes("realtime")?"realtime":t.includes("convai")?"convai":t.includes("pipeline")?"pipeline":"unknown"}function iu(e){return e.length===0?"—":e}function Zp(e){const t=e.metrics?.provider_mode;if(!t)return;const n=e.metrics?.llm_provider;return t.startsWith("pipeline")&&n?`${t} · ${n}`:t}function Jp(e){const t=e.metrics?.cost;if(!t)return{};const n={};return typeof t.telephony=="number"&&(n.telco=t.telephony),typeof t.llm=="number"&&(n.llm=t.llm),typeof t.stt=="number"&&(n.stt=t.stt),typeof t.tts=="number"&&(n.tts=t.tts),typeof t.llm_cached_savings=="number"&&(n.cached=t.llm_cached_savings),(n.stt!==void 0||n.tts!==void 0)&&(n.sttTts=(n.stt??0)+(n.tts??0)),n.telco===void 0&&n.llm===void 0&&n.sttTts===void 0&&typeof t.total=="number"&&(n.total=t.total),n}function qp(e,t){if(t)return;const n=e.metrics?.duration_seconds;return typeof n=="number"?n:typeof e.ended_at=="number"&&typeof e.started_at=="number"?Math.max(0,e.ended_at-e.started_at):0}function bp(e){if(typeof e.ended_at=="number")return Math.round(Date.now()/1e3-e.ended_at)}function uu(e){const t=Kp(e.status),n=t==="live"||e.status!==void 0&&Qp.has(e.status),r=e.metrics?.latency_avg,l=e.metrics?.latency_p50,o=e.metrics?.latency_p95,s=(Array.isArray(e.metrics?.turns)?e.metrics?.turns?.length:void 0)??(Array.isArray(e.transcript)?e.transcript.length:void 0);return{id:e.call_id,status:t,direction:Yp(e.direction),from:iu(e.caller),to:iu(e.callee),carrier:Xp(e.metrics?.telephony_provider),startedAtMs:typeof e.started_at=="number"?e.started_at*1e3:void 0,durationStart:n?e.started_at*1e3:void 0,duration:qp(e,n),latencyP95:o?.agent_response_ms??o?.total_ms??r?.total_ms,latencyP50:l?.agent_response_ms??l?.total_ms??r?.total_ms,sttAvg:r?.stt_ms,ttsAvg:r?.tts_ms,llmAvg:r?.llm_ms,turnCount:s,agentResponseP50:l?.agent_response_ms,agentResponseP95:o?.agent_response_ms,cost:Jp(e),agent:Zp(e),model:e.metrics?.llm_provider,mode:Gp(e.metrics?.provider_mode),sttProvider:e.metrics?.stt_provider,ttsProvider:e.metrics?.tts_provider,sttModel:e.metrics?.stt_model,ttsModel:e.metrics?.tts_model,llmModel:e.metrics?.llm_model,transcriptKey:e.call_id,endedAgo:bp(e)}}function em(e){const t=e.transcript;if(t&&t.length>0){const l=[];for(const o of t){const s=o.text;switch(o.role){case"user":l.push({who:"user",txt:s});break;case"assistant":l.push({who:"bot",txt:s});break;case"tool":l.push({who:"tool",txt:s});break;default:l.push({who:"bot",txt:s});break}}return l}const n=e.turns;if(!n||n.length===0)return[];const r=[];for(const l of n){if(typeof l!="object"||l===null)continue;const o=l,s=typeof o.user_text=="string"?o.user_text:"",i=typeof o.agent_text=="string"?o.agent_text:"";s.length>0&&r.push({who:"user",txt:s}),i.length>0&&i!=="[interrupted]"&&r.push({who:"bot",txt:i})}return r}const Lc=60*1e3,Pc=60*Lc,oo=24*Pc;function tm(e,t=Date.now()){switch(e){case"1h":{const n=5*Lc,r=Math.ceil(t/n)*n,l=r-12*n;return{count:12,bucketSizeMs:n,window:{fromMs:l,toMs:r}}}case"24h":{const n=Pc,r=Math.ceil(t/n)*n,l=r-24*n;return{count:24,bucketSizeMs:n,window:{fromMs:l,toMs:r}}}case"7d":{const n=new Date(t);n.setHours(0,0,0,0);const r=n.getTime()+oo,l=r-7*oo;return{count:7,bucketSizeMs:oo,window:{fromMs:l,toMs:r}}}case"All":default:return{count:9,bucketSizeMs:0,window:{fromMs:0,toMs:t}}}}function nm(e,t){const{fromMs:n,toMs:r}=t;return e.filter(l=>{const o=ts(l);return typeof o!="number"?!1:o>=n&&o<=r})}function ts(e){if(typeof e.startedAtMs=="number")return e.startedAtMs;if(typeof e.durationStart=="number")return e.durationStart;if(typeof e.endedAgo=="number")return Date.now()-e.endedAgo*1e3}function rm(e){const t=e.cost,n=(t.telco??0)+(t.llm??0)+(t.sttTts??0);return n>0?n:t.total??0}function lm(e){const t=e.reduce((n,r)=>r>n?r:n,0);return t<=0?e.map(()=>0):e.map(n=>Math.round(n/t*100))}function Tr(e,t,n=9,r){const l=typeof n=="object",o=l?n.count:n,s=Math.max(1,Math.floor(o)),i=l?n.window:r,a=l?n.bucketSizeMs:0;let c,m;if(i)c=i.fromMs,m=i.toMs;else{const d=[];for(const f of e){const p=ts(f);typeof p=="number"&&d.push(p)}if(d.length===0){const f=Date.now();return{heights:new Array(s).fill(0),buckets:new Array(s).fill(null).map(()=>[]),window:{fromMs:f,toMs:f},bucketSizeMs:0}}c=Math.min(...d),m=Math.max(...d)}const v=Math.max(1,m-c),h=a>0?a:v/s,S=new Array(s).fill(null).map(()=>[]),x=new Array(s).fill(0),k=new Array(s).fill(0);for(const d of e){const f=ts(d);if(typeof f!="number"||fm)continue;let p=Math.floor((f-c)/h);p>=s&&(p=s-1),p<0&&(p=0),S[p].push(d),t==="totalCalls"?x[p]+=1:t==="latency"?typeof d.latencyP95=="number"&&(x[p]+=d.latencyP95,k[p]+=1):x[p]+=rm(d)}const R=t==="latency"?x.map((d,f)=>k[f]>0?d/k[f]:0):x;return{heights:lm(R),buckets:S,window:{fromMs:c,toMs:m},bucketSizeMs:h}}function om(e,t){const n=new Set,r=[];for(const l of e)n.has(l.call_id)||(n.add(l.call_id),r.push(uu(l)));for(const l of t)n.has(l.call_id)||(n.add(l.call_id),r.push(uu(l)));return r}function sm(e,t){const n=new Map(e.map(o=>[o.id,o])),r=new Set(t.map(o=>o.id)),l=t.map(o=>{const s=n.get(o.id);return s?{...s,...o,latencyP95:o.latencyP95??s.latencyP95,latencyP50:o.latencyP50??s.latencyP50,sttAvg:o.sttAvg??s.sttAvg,ttsAvg:o.ttsAvg??s.ttsAvg,llmAvg:o.llmAvg??s.llmAvg,turnCount:o.turnCount??s.turnCount,agentResponseP50:o.agentResponseP50??s.agentResponseP50,agentResponseP95:o.agentResponseP95??s.agentResponseP95,cost:{...s.cost,...o.cost}}:o});for(const o of e)r.has(o.id)||l.push(o);return l}const im=1e3,um=3e4,am=5,cm=5e3,fm=["call_start","call_initiated","call_status","call_end"];function au(e){return e instanceof Error?e.message:"Unknown error"}function dm(){const[e,t]=L.useState([]),[n,r]=L.useState(null),[l,o]=L.useState(!1),[s,i]=L.useState(null),a=L.useRef(!0),c=L.useRef(null),m=L.useRef(null),v=L.useRef(null),h=L.useRef(0),S=L.useCallback(()=>{m.current!==null&&(clearTimeout(m.current),m.current=null)},[]),x=L.useCallback(()=>{v.current!==null&&(clearInterval(v.current),v.current=null)},[]),k=L.useCallback(()=>{c.current!==null&&(c.current.close(),c.current=null)},[]),R=L.useCallback(async()=>{try{const[C,N,E]=await Promise.all([Hp(),Up(50,0),Bp()]);if(!a.current)return;t(F=>sm(F,om(C,N))),r(E),i(null)}catch(C){if(!a.current)return;i(au(C))}},[]),d=L.useCallback(()=>{v.current===null&&(v.current=setInterval(()=>{R()},cm))},[R]),f=L.useRef(()=>{}),p=L.useCallback(()=>{if(S(),h.current>=am){d();return}const C=h.current,N=Math.min(um,im*Math.pow(2,C));h.current=C+1,m.current=setTimeout(()=>{m.current=null,a.current&&f.current()},N)},[S,d]),y=L.useCallback(()=>{R()},[R]),_=L.useCallback(()=>{k();let C;try{C=new EventSource("/api/dashboard/events")}catch(N){i(au(N)),p();return}c.current=C,C.onopen=()=>{a.current&&(h.current=0,x(),o(!0))},C.onerror=()=>{a.current&&(o(!1),k(),p())};for(const N of fm)C.addEventListener(N,y);C.addEventListener("turn_complete",y)},[k,x,y,p]);return L.useEffect(()=>{f.current=_},[_]),L.useEffect(()=>(a.current=!0,R(),_(),()=>{a.current=!1,S(),x(),k()}),[]),{calls:e,aggregates:n,isStreaming:l,error:s,refresh:R}}const pm=2e3;function mm(e,t){const[n,r]=L.useState([]),l=L.useRef(!0);return L.useEffect(()=>(l.current=!0,()=>{l.current=!1}),[]),L.useEffect(()=>{if(!e){r([]);return}let o=!1,s=null,i=null;const a=async()=>{try{const m=await Wp(e);if(o||!l.current)return;if(m===null){r([]);return}r(em(m))}catch{}};a();const c=m=>{const v=m;try{return JSON.parse(v.data)?.call_id===e}catch{return!1}};try{i=new EventSource("/api/dashboard/events"),i.addEventListener("turn_complete",m=>{c(m)&&a()}),i.addEventListener("call_end",m=>{c(m)&&a()})}catch{i=null}return t&&(s=setInterval(()=>{a()},pm)),()=>{o=!0,s!==null&&clearInterval(s),i!==null&&i.close()}},[e,t]),n}const cu="0.6.0",so={"1h":"1h","24h":"24h","7d":"7d",All:"all-time"};function hm(e){const t=e.filter(r=>typeof r.latencyP95=="number");if(t.length===0)return 0;const n=t.reduce((r,l)=>r+(l.latencyP95??0),0);return Math.round(n/t.length)}function vm(e){return e.reduce((t,n)=>{if(typeof n.cost.total=="number")return t+n.cost.total;const r=(n.cost.telco??0)+(n.cost.llm??0)+(n.cost.sttTts??0);return t+r},0)}function ym(e){const n=e.find(l=>l.status==="live")??e[0];if(!n)return"";const r=n.direction==="inbound"?n.to:n.from;return r&&r!=="—"?r:""}function gm(){const{calls:e,aggregates:t,isStreaming:n,error:r,refresh:l}=dm(),[o,s]=L.useState(null),[i,a]=L.useState(""),[c,m]=L.useState("24h"),[v,h]=L.useState(!0),[S,x]=L.useState(!1),k=L.useMemo(()=>tm(c),[c]),R=k.window,d=L.useMemo(()=>{if(c==="All")return e;const w=new Set(nm(e,R).map(M=>M.id));return e.filter(M=>M.status==="live"||w.has(M.id))},[e,c,R]);L.useEffect(()=>{if(o!==null)return;const w=d.find(M=>M.status==="live")??d[0];w&&s(w.id)},[d,o]),L.useEffect(()=>{o!==null&&(d.some(w=>w.id===o)||s(null))},[d,o]),L.useEffect(()=>{const w=M=>{if(!(M.shiftKey&&M.key.toLowerCase()==="k"||M.metaKey&&M.key.toLowerCase()==="k"))return;M.preventDefault(),document.querySelector(".panel-h .search input")?.focus()};return window.addEventListener("keydown",w),()=>window.removeEventListener("keydown",w)},[]);const f=L.useMemo(()=>d.find(w=>w.id===o)??null,[d,o]),p=f?.status==="live",y=mm(f?.id??null,p),_=L.useMemo(()=>e.filter(w=>w.status==="live").length,[e]),C=L.useMemo(()=>e.filter(w=>w.status==="live"&&w.direction==="inbound").length,[e]),N=_-C,E=d.length,F=hm(d)||t?.avg_latency_ms||0,T=vm(d)||t?.total_cost||0,ve=ym(e),et=L.useMemo(()=>Tr(d,"totalCalls",k),[d,k]),tt=L.useMemo(()=>Tr(d,"latency",k),[d,k]),yn=L.useMemo(()=>Tr(d,"spend",k),[d,k]),ur=L.useMemo(()=>{const w=e.filter(M=>M.status==="live");return Tr(w,"totalCalls",k)},[e,k]),nt=w=>w.heights.map((M,P)=>({height:M,calls:w.buckets[P],fromMs:w.window.fromMs+P*w.bucketSizeMs,toMs:w.window.fromMs+(P+1)*w.bucketSizeMs})),gn=()=>{f&&l().catch(()=>{})};return u.jsxs(u.Fragment,{children:[u.jsx(cp,{liveCount:_,todayCount:E,phoneNumber:ve,sdkVersion:cu}),u.jsxs("div",{className:"page",children:[u.jsx(wp,{range:c,setRange:w=>m(w)}),u.jsxs("div",{className:"metrics",children:[u.jsx(Lr,{label:`Calls · ${so[c]}`,value:E,spark:et.heights,buckets:nt(et),onSelectCall:s,kind:"count"}),u.jsx(Lr,{label:"Avg latency p95",value:F||0,unit:"ms",spark:tt.heights,buckets:nt(tt),onSelectCall:s,kind:"latency"}),u.jsx(Lr,{label:`Spend · ${so[c]}`,value:ze(T),spark:yn.heights,buckets:nt(yn),onSelectCall:s,kind:"spend"}),u.jsx(Lr,{label:"Active now",value:_,peach:!0,badge:!0,footer:`${C} inbound · ${N} outbound`,spark:ur.heights,buckets:nt(ur),onSelectCall:s,kind:"count"})]}),u.jsxs("div",{className:"split",children:[u.jsx(Mp,{calls:d,selectedId:o,onSelect:s,newId:null,search:i,setSearch:a}),u.jsxs("div",{className:"rr",children:[u.jsx(Pp,{call:f,transcript:y,onEnd:gn,recording:v,setRecording:h,muted:S,setMuted:x}),u.jsx(Rp,{call:f})]})]}),u.jsxs("div",{className:"statusbar",children:[u.jsxs("div",{className:"group",children:[u.jsx("span",{className:n?"green":"",children:n?"streaming · sse":r?`error · ${r}`:"idle"}),u.jsxs("span",{children:["SDK · ",cu]})]}),u.jsx("div",{className:"group",children:u.jsxs("span",{children:[_," live · ",E," ",so[c]]})})]})]})]})}const Tc=document.getElementById("root");if(!Tc)throw new Error("Patter dashboard: #root element missing");io.createRoot(Tc).render(u.jsx(Xc.StrictMode,{children:u.jsx(gm,{})})); From 6dcf990f1c8f04ef55249389dce384d8a2c8a83b Mon Sep 17 00:00:00 2001 From: nicolotognoni Date: Tue, 12 May 2026 13:05:24 +0200 Subject: [PATCH 2/6] fix(barge-in): firstMessage interruptible via per-chunk mark gating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The firstMessage TTS chunks were pushed into the carrier WebSocket as fast as the provider yielded them. Twilio's outbound buffer ended up several seconds deep, and a barge-in's sendClear was queued behind the already-enqueued media frames — the agent kept talking on the user's earpiece for up to ~2 s after the user spoke (#128). The firstMessage send path is now a paced loop: * Twilio: every chunk is followed by a unique mark; the loop waits for the oldest unconfirmed mark once FIRST_MESSAGE_MARK_WINDOW (3 chunks ≈ 120 ms) are in flight. ``onMark`` drains the FIFO on echo so the next chunk goes out. ``cancelSpeaking`` (Py: ``_run_barge_in_cancel``) resolves every pending mark waiter so the loop exits on the next tick and ``sendClear`` lands on a near-empty carrier buffer. * Telnyx (no mark concept): the loop falls back to a playout-duration- based sleep so the buffer can't out-run a clear by more than one chunk. Both SDKs stay in parity: TS ``sendPacedFirstMessageBytes`` mirrors Py ``_send_paced_first_message_bytes`` and both ``streamPrewarmBytes`` / ``_stream_prewarm_bytes`` delegate to the new helper. The existing prewarm chunking test was updated to echo marks via the mock bridge so it interoperates with the new pacing. Coverage: * libraries/typescript/tests/unit/stream-handler.test.ts — ``firstMessage mark-gated pacing`` (3 cases: window cap + barge-in, mark echo slides window, Telnyx playout pacing). * libraries/python/tests/unit/test_first_message_pacing.py — 4 cases including FIFO mark resolution. Refs #128. --- CHANGELOG.md | 28 +++ libraries/python/getpatter/stream_handler.py | 213 ++++++++++++++--- .../tests/unit/test_first_message_pacing.py | 193 +++++++++++++++ libraries/typescript/src/stream-handler.ts | 225 ++++++++++++++++-- .../typescript/tests/unit/prewarm.test.ts | 12 +- .../tests/unit/stream-handler.test.ts | 159 +++++++++++++ 6 files changed, 782 insertions(+), 48 deletions(-) create mode 100644 libraries/python/tests/unit/test_first_message_pacing.py diff --git a/CHANGELOG.md b/CHANGELOG.md index fc895ae3..c1b3e656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ ## 0.6.1 (2026-05-12) +### Fixed — firstMessage was effectively un-interruptible: barge-in lost the race against the carrier outbound buffer (#128, Python + TypeScript parity) + +`StreamHandler.streamPrewarmBytes` (TS) / +`PipelineStreamHandler._stream_prewarm_bytes` (Py) and the live-TTS +firstMessage loop pushed every chunk into the carrier WebSocket as fast +as the TTS provider yielded bytes. Twilio's outbound buffer ended up +several seconds deep, and a barge-in's `sendClear` (`send_clear`) was +queued behind the already-enqueued media frames — the agent kept +talking on the user's earpiece for up to ~2 s after the user spoke. +Filed as #128. + +Fix: route every firstMessage chunk through a paced sender that emits +a unique Twilio mark after each chunk and waits for the oldest +unconfirmed mark once `FIRST_MESSAGE_MARK_WINDOW` (3 chunks ≈ 120 ms) +are in flight. `cancelSpeaking` (`_run_barge_in_cancel` on Python) +drains every pending mark waiter so the loop exits on the next tick +and `sendClear` lands on a near-empty carrier buffer. On Telnyx +(no mark concept) the loop falls back to a playout-duration-based +sleep so the buffer can't out-run a clear by more than one chunk. + +Files: `libraries/typescript/src/stream-handler.ts`, +`libraries/python/getpatter/stream_handler.py`. Coverage: +`libraries/typescript/tests/unit/stream-handler.test.ts` +(`firstMessage mark-gated pacing`), +`libraries/python/tests/unit/test_first_message_pacing.py`. The +existing `streamPrewarmBytes` chunking test was updated to echo +marks via the mock bridge so it interoperates with the new pacing. + ### Fixed — Dashboard SPA: live snapshot refresh dropped previously-visible calls when a new call started (#124) `mergeCallPreserving` in `dashboard-app/src/hooks/useDashboardData.ts` diff --git a/libraries/python/getpatter/stream_handler.py b/libraries/python/getpatter/stream_handler.py index 35defe0b..8e7d831a 100644 --- a/libraries/python/getpatter/stream_handler.py +++ b/libraries/python/getpatter/stream_handler.py @@ -1794,6 +1794,21 @@ def __init__( self._last_commit_at: float = 0.0 # Per-handler StatefulResampler for mulaw 8 kHz -> PCM16 16 kHz transcoding. self._resampler_8k_to_16k = None + # FIFO of outstanding Twilio marks the SDK has sent but not yet seen + # echoed back. Used by the firstMessage paced sender to bound the + # carrier-side buffer depth — without this the loop pushed the entire + # TTS stream into Twilio's WebSocket in one burst and a sendClear + # racing the queued media frames was unable to interrupt the agent + # for up to ~2 s (BUG #128). ``on_mark`` pops entries when Twilio + # confirms playback; ``_drain_pending_marks`` resolves every entry on + # cancel so any awaiter exits on the next tick. Telnyx never + # populates this queue (no mark concept on Telnyx's wire protocol — + # the loop falls back to time-based pacing). + self._pending_marks: list[tuple[str, asyncio.Future[None]]] = [] + # Monotonic counter for first-message mark names. Distinct from the + # generic ``audio_*`` marks the Realtime path sends so the two paths + # can coexist without name collisions. + self._first_message_mark_counter: int = 0 async def start(self) -> None: """Initialize STT/TTS providers, hooks, and start the STT receive loop.""" @@ -2078,17 +2093,22 @@ async def _connect_stt() -> None: first_chunk_sent = True if self.metrics is not None: self.metrics.record_tts_first_byte() - # Far-end tap for the echo canceller — push the - # exact PCM the carrier-side encoder will transmit. - # Without this the AEC adapt loop has no reference - # signal during the intro, resulting in unmitigated - # bleed-through and a "first turn unresponsive" UX - # where the user's voice is masked by the agent's - # TTS in the inbound channel. - if self._aec is not None: - self._aec.push_far_end(audio_chunk) - await self.audio_sender.send_audio(audio_chunk) - self._mark_first_audio_sent() + # BUG #128: route every TTS chunk through the paced + # sender so we never push more than + # ``_FIRST_MESSAGE_MARK_WINDOW`` chunks of audio + # ahead of carrier playback. The previous burst-send + # let Twilio's outbound buffer reach several + # seconds — a barge-in's send_clear race-lost + # against the queued media frames and the agent + # kept talking on the user's earpiece for up to + # ~2 s after the user spoke. The paced sender also + # drives the AEC far-end tap and + # ``_mark_first_audio_sent`` internally so this + # path stays parity-clean with + # ``_stream_prewarm_bytes``. + sent = await self._send_paced_first_message_bytes(audio_chunk) + if not sent: + break finally: # Drop any partial int16 byte to prevent cross-turn corruption # if the stream threw before a complete sample was delivered. @@ -2657,6 +2677,16 @@ async def _do_cancel_for_barge_in(self, transcript_text: str) -> None: self._speaking_started_at = None self._first_audio_sent_at = None self._last_cancel_at = time.time() + # Unblock any firstMessage paced-send loop that's sitting in + # ``_wait_for_mark_window`` — without this the loop keeps + # awaiting echoes for up to ``_MARK_AWAIT_TIMEOUT_S`` per + # outstanding mark before observing ``_is_speaking=False``, + # which keeps the agent "speaking" from the user's perspective + # for hundreds of extra ms after barge-in (BUG #128). Defensive + # ``getattr`` is for test fixtures that build a handler shell + # via ``object.__new__`` and skip ``__init__``. + if getattr(self, "_pending_marks", None) is not None: + self._drain_pending_marks() cancel_event = getattr(self, "_llm_cancel_event", None) if cancel_event is not None: cancel_event.set() @@ -3355,33 +3385,162 @@ async def _flush_inbound_audio_ring(self) -> None: # is identical regardless of whether the firstMessage came from the # prewarm cache or a live ``tts.synthesize`` stream. _PREWARM_CHUNK_BYTES: int = 1280 + # Maximum unconfirmed Twilio marks while streaming firstMessage. Each + # chunk is 40 ms of audio at 16 kHz PCM16, so a window of 3 caps the + # in-flight queue at ~120 ms. This means a barge-in's ``send_clear`` has + # at most ~120 ms of buffered audio to flush — vs. ~2-5 s with the + # previous burst-send code (BUG #128). 3 hit the smallest barge-in cap + # without audible playback gaps under typical PSTN RTT in 2026-05 + # acceptance. + _FIRST_MESSAGE_MARK_WINDOW: int = 3 + # Per-chunk soft timeout (s) for awaiting a mark echo. Caps the + # deadlock window when a carrier (or a test double) never echoes — + # playout may glitch by one chunk on timeout but the call stays alive. + _MARK_AWAIT_TIMEOUT_S: float = 0.5 + # Bytes-per-millisecond for a 16 kHz PCM16 mono stream — used by the + # non-Twilio firstMessage pacing path to translate chunk size into a + # playout-duration sleep. 16000 samples/sec × 2 bytes = 32 bytes/ms. + _PCM16_16K_BYTES_PER_MS: int = 32 + + def _drain_pending_marks(self) -> None: + """Resolve every entry in ``_pending_marks`` and empty the FIFO. + + Idempotent — safe to call from the barge-in cancel path and again + from the grace flip without leaking unresolved futures. + """ + if not self._pending_marks: + return + for _name, fut in self._pending_marks: + if not fut.done(): + try: + fut.set_result(None) + except asyncio.InvalidStateError: + pass + self._pending_marks.clear() + + async def _send_mark_awaitable(self) -> asyncio.Future | None: + """Send a Twilio ``mark`` event and return a future that resolves + when the carrier echoes it back (via :meth:`on_mark`), or when + :meth:`_drain_pending_marks` runs. Returns ``None`` on non-Twilio + carriers — the caller should fall back to time-based pacing. + """ + if not self._for_twilio: + return None + self._first_message_mark_counter += 1 + mark_name = f"fm_{self._first_message_mark_counter}" + loop = asyncio.get_event_loop() + fut: asyncio.Future[None] = loop.create_future() + self._pending_marks.append((mark_name, fut)) + try: + await self.audio_sender.send_mark(mark_name) + except Exception as exc: # noqa: BLE001 - best effort + logger.debug("send_mark failed (%s): %s", mark_name, exc) + # Drop the waiter so the queue can't fill with orphans. + for idx, (name, f) in enumerate(self._pending_marks): + if name == mark_name: + self._pending_marks.pop(idx) + break + if not fut.done(): + fut.set_result(None) + return fut + + async def _wait_for_mark_window(self) -> None: + """Block until the in-flight mark queue depth is below + ``_FIRST_MESSAGE_MARK_WINDOW``. Returns immediately on cancel + because :meth:`_drain_pending_marks` resolves every pending future. + """ + while ( + self._is_speaking + and len(self._pending_marks) >= self._FIRST_MESSAGE_MARK_WINDOW + ): + _name, oldest = self._pending_marks[0] + try: + await asyncio.wait_for( + asyncio.shield(oldest), + timeout=self._MARK_AWAIT_TIMEOUT_S, + ) + except asyncio.TimeoutError: + # Drop the head so subsequent loops don't deadlock on the + # same mark forever. Twilio mark echo may have been lost + # in transit; carrier playback will continue regardless. + pass + # Pop the head if still present (a successful echo would have + # done it via ``on_mark``; only a timeout leaves it in place). + if self._pending_marks and self._pending_marks[0][0] == _name: + self._pending_marks.pop(0) + + async def on_mark(self, mark_name: str) -> None: + """Handle a Twilio ``mark`` echo and resolve the matching firstMessage + waiter (if any). Marks are matched FIFO: an echo for ``fm_3`` also + resolves ``fm_1`` and ``fm_2`` in case the carrier batches echoes. + """ + if not mark_name: + return + idx = -1 + for i, (name, _fut) in enumerate(self._pending_marks): + if name == mark_name: + idx = i + break + if idx < 0: + return + resolved = self._pending_marks[: idx + 1] + del self._pending_marks[: idx + 1] + for _name, fut in resolved: + if not fut.done(): + try: + fut.set_result(None) + except asyncio.InvalidStateError: + pass async def _stream_prewarm_bytes(self, prewarm_bytes: bytes) -> bool: - """Stream a cached firstMessage buffer in pacing-friendly chunks. - - Splits ``prewarm_bytes`` into ``_PREWARM_CHUNK_BYTES`` slices and - forwards each through ``self.audio_sender.send_audio`` exactly - like the live TTS path does — preserving Twilio mark/clear - granularity. A single multi-second send_audio call would push - the whole intro into the carrier in one go and a ``send_clear`` - issued mid-buffer would have nothing to clear ("agent keeps - talking after barge-in" UX bug on the very first turn). - - Returns ``True`` when at least one chunk hit the wire — the - caller uses that to decide whether to record the - TTS-first-byte / turn-complete metrics. + """Stream a cached firstMessage buffer in pacing-friendly chunks.""" + return await self._send_paced_first_message_bytes(prewarm_bytes) + + async def _send_paced_first_message_bytes(self, bytes_: bytes) -> bool: + """Iterate ``bytes_`` as ``_PREWARM_CHUNK_BYTES``-sized PCM16 slices + and forward each via ``audio_sender.send_audio`` with mark-gated + pacing (Twilio) or playout-time-based pacing (Telnyx). + + Caps the carrier-side buffer at ``_FIRST_MESSAGE_MARK_WINDOW`` + chunks so a barge-in's ``send_clear`` has at most ~120 ms (Twilio) + or zero (Telnyx, immediately after the latest sleep) of audio to + flush. The previous burst-send code let Twilio's buffer reach + several seconds — a barge-in's ``send_clear`` race-lost against + the queued media frames and the agent kept talking on the user's + earpiece for up to ~2 s after the user spoke (BUG #128). + + Bails immediately when ``_is_speaking`` flips to ``False`` — both + via the loop's pre-iter check and via :meth:`_drain_pending_marks` + (called from the barge-in cancel path) which unblocks any + in-flight :meth:`_wait_for_mark_window` await. + + Returns ``True`` when at least one chunk hit the wire — the caller + uses that to decide whether to record the TTS-first-byte / + turn-complete metrics. """ first_chunk_sent = False - for i in range(0, len(prewarm_bytes), self._PREWARM_CHUNK_BYTES): + for i in range(0, len(bytes_), self._PREWARM_CHUNK_BYTES): + if not self._is_speaking: + break # barge-in mid-buffer — stop now + # Back-pressure: if too many marks are unconfirmed, wait. + # Drains immediately on cancel. + await self._wait_for_mark_window() if not self._is_speaking: - break # barge-in mid-prewarm — stop now - chunk = prewarm_bytes[i : i + self._PREWARM_CHUNK_BYTES] + break + chunk = bytes_[i : i + self._PREWARM_CHUNK_BYTES] if not first_chunk_sent: first_chunk_sent = True if self._aec is not None: self._aec.push_far_end(chunk) await self.audio_sender.send_audio(chunk) self._mark_first_audio_sent() + mark_fut = await self._send_mark_awaitable() + if mark_fut is None: + # Telnyx (or any other carrier without marks): pace by + # chunk playout duration so the buffer can't out-run a + # send_clear by more than one chunk. + playout_ms = max(1, len(chunk) // self._PCM16_16K_BYTES_PER_MS) + await asyncio.sleep(playout_ms / 1000.0) return first_chunk_sent async def cleanup(self) -> None: diff --git a/libraries/python/tests/unit/test_first_message_pacing.py b/libraries/python/tests/unit/test_first_message_pacing.py new file mode 100644 index 00000000..7e5fcc4c --- /dev/null +++ b/libraries/python/tests/unit/test_first_message_pacing.py @@ -0,0 +1,193 @@ +"""Unit tests for the firstMessage mark-gated paced sender (BUG #128). + +Pre-fix the firstMessage TTS chunks were pushed into the carrier WebSocket +as fast as the TTS provider yielded them. A barge-in mid-buffer issued +``send_clear``, but the WebSocket queue between the SDK and the carrier +held several seconds of media frames already, and the agent kept talking +on the user's earpiece until that drained. + +Post-fix the loop sends a mark after every chunk and awaits the oldest +mark once ``_FIRST_MESSAGE_MARK_WINDOW`` chunks are unconfirmed; +``_drain_pending_marks`` (called from the cancel path) resolves every +pending future so the waiting loop exits on the next tick. On Telnyx +(no mark concept) the loop falls back to a playout-time-based sleep so +the carrier buffer never grows beyond one chunk. +""" + +from __future__ import annotations + +import asyncio +import time + +import pytest + +from getpatter.stream_handler import AudioSender, PipelineStreamHandler + + +CHUNK_BYTES = 1280 # mirrors PipelineStreamHandler._PREWARM_CHUNK_BYTES + + +class _RecordingAudioSender(AudioSender): + """In-memory AudioSender that records every call for inspection.""" + + def __init__(self) -> None: + self.audio_chunks: list[bytes] = [] + self.marks: list[str] = [] + self.clears: int = 0 + + async def send_audio(self, pcm_audio: bytes) -> None: + self.audio_chunks.append(pcm_audio) + + async def send_clear(self) -> None: + self.clears += 1 + + async def send_mark(self, mark_name: str) -> None: + self.marks.append(mark_name) + + +def _make_handler( + *, for_twilio: bool = True +) -> tuple[PipelineStreamHandler, _RecordingAudioSender]: + """Build a PipelineStreamHandler shell without exercising __init__. + + Tests need only the paced-sender / on_mark / cancel surface — we don't + want to mock 30 unrelated dependencies (STT/TTS/metrics/etc.). + """ + handler = PipelineStreamHandler.__new__(PipelineStreamHandler) + sender = _RecordingAudioSender() + handler.audio_sender = sender + handler._is_speaking = True + handler._speaking_started_at = time.time() + handler._first_audio_sent_at = time.time() + handler._aec = None + handler._for_twilio = for_twilio + handler._pending_marks = [] + handler._first_message_mark_counter = 0 + handler.call_id = "call-test" + handler.metrics = None + return handler, sender + + +def _mark_first_audio_sent_noop(self: PipelineStreamHandler) -> None: + """No-op replacement for the real ``_mark_first_audio_sent`` so we don't + need to wire the per-turn metrics accumulator into the test fixture. + """ + return None + + +@pytest.fixture(autouse=True) +def _patch_mark_first(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr( + PipelineStreamHandler, + "_mark_first_audio_sent", + _mark_first_audio_sent_noop, + ) + + +@pytest.mark.unit +class TestFirstMessageMarkGatedPacing: + """BUG #128 regression coverage: firstMessage must be cancellable.""" + + async def test_caps_in_flight_at_window_and_bails_on_barge_in(self) -> None: + handler, sender = _make_handler(for_twilio=True) + # 4 chunks. Window=3, so chunks 1–3 send back-to-back and chunk 4 + # blocks on _wait_for_mark_window until either a mark echoes OR + # _drain_pending_marks (called from cancel) resolves the futures. + bytes_ = b"\x00" * (CHUNK_BYTES * 4) + + task = asyncio.create_task(handler._send_paced_first_message_bytes(bytes_)) + + # Yield enough so the loop sends the first three chunks and enters + # the window wait. + for _ in range(20): + await asyncio.sleep(0) + + assert len(sender.audio_chunks) == 3 + assert sender.marks == ["fm_1", "fm_2", "fm_3"] + assert len(handler._pending_marks) == 3 + + # Simulate the cancel side of a confirmed barge-in. ``send_clear`` is + # the canonical signal; ``_drain_pending_marks`` unblocks the + # waiting loop so it sees ``_is_speaking=False`` on the next tick. + handler._is_speaking = False + handler._drain_pending_marks() + await sender.send_clear() + + sent = await task + + assert sent is True + assert sender.clears == 1 + # Chunk 4 must NOT have hit the wire. + assert len(sender.audio_chunks) == 3 + + async def test_echoed_mark_slides_window_and_next_chunk_goes_out(self) -> None: + handler, sender = _make_handler(for_twilio=True) + bytes_ = b"\x00" * (CHUNK_BYTES * 4) + + task = asyncio.create_task(handler._send_paced_first_message_bytes(bytes_)) + + for _ in range(20): + await asyncio.sleep(0) + assert len(sender.audio_chunks) == 3 + assert sender.marks == ["fm_1", "fm_2", "fm_3"] + + # Twilio echoes chunk 1 → loop should advance to chunk 4. + await handler.on_mark("fm_1") + for _ in range(20): + await asyncio.sleep(0) + + assert len(sender.audio_chunks) == 4 + assert sender.marks == ["fm_1", "fm_2", "fm_3", "fm_4"] + + # Drain the rest so the loop completes naturally. + await handler.on_mark("fm_2") + await handler.on_mark("fm_3") + await handler.on_mark("fm_4") + await task + assert handler._pending_marks == [] + + async def test_telnyx_paces_via_playout_time_and_bails_on_cancel(self) -> None: + handler, sender = _make_handler(for_twilio=False) + # 4 chunks. Telnyx never sends marks — every iteration awaits a + # real ``asyncio.sleep`` keyed to chunk playout duration. + bytes_ = b"\x00" * (CHUNK_BYTES * 4) + + task = asyncio.create_task(handler._send_paced_first_message_bytes(bytes_)) + + # Yield enough so at least the first chunk hits the wire. + for _ in range(5): + await asyncio.sleep(0) + sent_before_cancel = len(sender.audio_chunks) + assert sent_before_cancel >= 1 + # Telnyx must never accumulate marks. + assert sender.marks == [] + assert handler._pending_marks == [] + + # Cancel mid-loop. + handler._is_speaking = False + handler._drain_pending_marks() + await sender.send_clear() + await task + + assert sender.clears == 1 + # No further chunks may go out after cancel. + assert len(sender.audio_chunks) == sent_before_cancel + + +@pytest.mark.unit +class TestOnMarkResolvesWaiters: + """``on_mark`` matches the FIFO entry and resolves all earlier ones too.""" + + async def test_echo_for_later_mark_resolves_earlier_waiters(self) -> None: + handler, _sender = _make_handler(for_twilio=True) + + # Manually queue three marks (skipping send_audio so we test the + # matching logic in isolation). + await handler._send_mark_awaitable() + await handler._send_mark_awaitable() + await handler._send_mark_awaitable() + assert [name for name, _ in handler._pending_marks] == ["fm_1", "fm_2", "fm_3"] + + await handler.on_mark("fm_2") + # fm_1 and fm_2 are drained; fm_3 stays pending. + assert [name for name, _ in handler._pending_marks] == ["fm_3"] diff --git a/libraries/typescript/src/stream-handler.ts b/libraries/typescript/src/stream-handler.ts index c53b02e9..fceb2c03 100644 --- a/libraries/typescript/src/stream-handler.ts +++ b/libraries/typescript/src/stream-handler.ts @@ -332,6 +332,54 @@ export class StreamHandler { * the tail of the cancelled turn (~50-200 ms of doubled audio). */ private lastCancelAt: number | null = null; + /** + * Promise queue tracking outstanding Twilio marks the SDK has sent but + * not yet seen echoed back. Used by the firstMessage send loop to bound + * the depth of audio queued at the carrier — without this the loop + * pushes the entire TTS stream into Twilio's WebSocket in one burst, + * and a sendClear issued mid-buffer races against several seconds of + * already-queued media frames (BUG #128). The window depth is + * ``FIRST_MESSAGE_MARK_WINDOW``; ``onMark`` drains entries as Twilio + * confirms playback, ``cancelSpeaking`` resolves every pending entry so + * any awaiter exits immediately. Telnyx never populates this queue + * (Telnyx's media-stream protocol has no mark concept — the loop + * falls back to time-based pacing on that carrier). + */ + private pendingMarks: Array<{ + name: string; + resolve: () => void; + promise: Promise; + }> = []; + /** + * Monotonic counter for first-message mark names. Distinct from + * ``chunkCount`` (which the Realtime path uses) so the two paths can + * coexist without name collisions even when firstMessage finishes while + * a Realtime turn is still streaming. + */ + private firstMessageMarkCounter = 0; + /** + * Maximum unconfirmed Twilio marks while streaming firstMessage. Each + * chunk is 40 ms of audio at 16 kHz PCM16, so a window of 3 caps + * the in-flight queue at ~120 ms. This means a barge-in's + * ``sendClear`` has at most 120 ms of already-buffered audio to flush + * — vs. ~2-5 s with the previous burst-send code, which was the + * root cause of "firstMessage non interrompibile". Higher values + * smooth playback under jittery RTT (each mark echo adds ~150-250 ms + * RTT on PSTN) at the cost of longer barge-in latency; lower values + * risk under-buffering. 3 hit the smallest barge-in cap without + * audible gaps in 2026-05 acceptance. + */ + private static readonly FIRST_MESSAGE_MARK_WINDOW = 3; + /** + * Per-chunk soft timeout (ms) while awaiting a mark echo. Twilio's + * mark echoes typically arrive within 100-250 ms of audio playback. + * Capping at 500 ms guards against carriers (or test doubles) that + * never echo — without it a stalled echo would deadlock the loop and + * the agent would freeze mid-utterance. On timeout we drop the + * waiter from the queue and continue: playout may glitch by one + * chunk but the call stays alive. + */ + private static readonly MARK_AWAIT_TIMEOUT_MS = 500; /** * Minimum drain window (ms) between a ``cancelSpeaking`` and the next * ``beginSpeaking``. 150 ms covers a typical PSTN jitter buffer drain @@ -404,6 +452,14 @@ export class StreamHandler { this.speakingStartedAt = null; this.firstAudioSentAt = null; this.lastCancelAt = Date.now(); + // Drain any firstMessage mark waiters so a loop blocked on + // ``waitForMarkWindow`` exits on the next tick and observes + // ``!isSpeaking``. Without this the loop would stay blocked until + // each mark either echoes (carrier still draining its queue) or + // hits ``MARK_AWAIT_TIMEOUT_MS`` — keeping the agent "speaking" + // from the user's perspective for hundreds of extra ms after + // barge-in. + this.drainPendingMarks(); if (this.llmAbort !== null) { try { this.llmAbort.abort(); @@ -413,6 +469,86 @@ export class StreamHandler { } } + /** + * Resolve every entry in ``pendingMarks`` and empty the queue. Idempotent + * — safe to call from ``cancelSpeaking`` and again from the grace path + * without leaking pending promises. + */ + private drainPendingMarks(): void { + if (this.pendingMarks.length === 0) return; + for (const entry of this.pendingMarks) { + try { + entry.resolve(); + } catch { + // No-op — pending entries always own a fresh resolve fn. + } + } + this.pendingMarks.length = 0; + } + + /** + * Push a Twilio ``mark`` event AFTER the corresponding audio chunk and + * return a promise that resolves when the mark is echoed back via + * ``onMark`` (or when ``cancelSpeaking`` drains the queue, or after + * ``MARK_AWAIT_TIMEOUT_MS``). Returns null on non-Twilio carriers — the + * caller is expected to fall back to time-based pacing in that case. + */ + private sendMarkAwaitable(): Promise | null { + if (this.deps.bridge.telephonyProvider !== 'twilio') return null; + this.firstMessageMarkCounter += 1; + const markName = `fm_${this.firstMessageMarkCounter}`; + let resolve!: () => void; + const promise = new Promise((r) => { + resolve = r; + }); + this.pendingMarks.push({ name: markName, resolve, promise }); + try { + this.deps.bridge.sendMark(this.ws, markName, this.streamSid); + } catch (err) { + getLogger().debug(`sendMark failed (${markName}): ${String(err)}`); + // Drop the waiter immediately so the queue doesn't fill with + // never-resolving entries that block the window. + const idx = this.pendingMarks.findIndex((m) => m.name === markName); + if (idx >= 0) this.pendingMarks.splice(idx, 1); + return Promise.resolve(); + } + return promise; + } + + /** + * If the in-flight mark queue is at or above ``FIRST_MESSAGE_MARK_WINDOW`` + * entries, wait for the oldest entry to clear (mark echoed, agent + * cancelled, or per-mark timeout). Repeats until the queue depth is + * within the window — under high RTT the carrier may have several + * marks queued and we want every loop iteration to be naturally back- + * pressured by playback. + */ + private async waitForMarkWindow(): Promise { + while ( + this.isSpeaking && + this.pendingMarks.length >= StreamHandler.FIRST_MESSAGE_MARK_WINDOW + ) { + const oldest = this.pendingMarks[0]; + const timeout = new Promise((resolve) => + setTimeout(resolve, StreamHandler.MARK_AWAIT_TIMEOUT_MS), + ); + await Promise.race([oldest.promise, timeout]); + // Drop the head if it's still the same entry — onMark would + // have already removed it on echo; only a timeout leaves it + // in place. + if (this.pendingMarks[0] === oldest) { + this.pendingMarks.shift(); + } + } + } + + /** + * Bytes-per-millisecond for a 16 kHz PCM16 mono stream. Used by the + * non-Twilio firstMessage pacing path to translate chunk size into a + * playout-duration sleep. 16000 samples/sec × 2 bytes = 32 bytes/ms. + */ + private static readonly PCM16_16K_BYTES_PER_MS = 32; + /** Cancel and clear the pending grace timer, if any. */ private clearGraceTimer(): void { if (this.graceTimer !== null) { @@ -1145,8 +1281,23 @@ export class StreamHandler { */ /** Handle a Twilio Media Streams `mark` event acknowledging audio playback boundaries. */ async onMark(markName: string): Promise { - if (markName) { - this.lastConfirmedMark = markName; + if (!markName) return; + this.lastConfirmedMark = markName; + // Resolve the firstMessage mark waiter (if any) so the send loop + // can advance its sliding window. We resolve the matched entry AND + // every entry before it in the queue — Twilio sometimes batches + // mark echoes, and dropping earlier entries first keeps FIFO order + // even when the higher-numbered echo arrives before a lower- + // numbered one (rare but observed on degraded edges). + const idx = this.pendingMarks.findIndex((m) => m.name === markName); + if (idx < 0) return; + const resolved = this.pendingMarks.splice(0, idx + 1); + for (const entry of resolved) { + try { + entry.resolve(); + } catch { + // No-op. + } } } @@ -1253,17 +1404,51 @@ export class StreamHandler { * metrics. */ private async streamPrewarmBytes(prewarmBytes: Buffer): Promise { + return this.sendPacedFirstMessageBytes(prewarmBytes); + } + + /** + * Iterate ``bytes`` as ``PREWARM_CHUNK_BYTES``-sized PCM16 slices and + * forward each via ``deps.bridge.sendAudio`` with mark-gated pacing + * (Twilio) or playout-time-based pacing (Telnyx). Caps the carrier- + * side buffer at ``FIRST_MESSAGE_MARK_WINDOW`` chunks so a barge-in's + * ``sendClear`` has ~120 ms (Twilio) or zero (Telnyx, immediately + * after the latest sleep) of audio to flush. + * + * Bails immediately when ``isSpeaking`` flips to false — both via the + * loop's pre-iter check and via ``drainPendingMarks`` (called from + * ``cancelSpeaking``) which unblocks any in-flight ``waitForMarkWindow``. + * + * Returns ``true`` when at least one chunk hit the wire — the caller + * uses that to decide whether to record TTS-first-byte / turn-complete + * metrics. See BUG #128 for the regression this fix targets. + */ + private async sendPacedFirstMessageBytes(bytes: Buffer): Promise { let firstChunkSent = false; - for (let i = 0; i < prewarmBytes.length; i += StreamHandler.PREWARM_CHUNK_BYTES) { - if (!this.isSpeaking) break; // barge-in mid-prewarm — stop now - const chunk = prewarmBytes.subarray(i, i + StreamHandler.PREWARM_CHUNK_BYTES); - if (!firstChunkSent) { - firstChunkSent = true; - } + for (let i = 0; i < bytes.length; i += StreamHandler.PREWARM_CHUNK_BYTES) { + if (!this.isSpeaking) break; // barge-in mid-buffer — stop now + // Back-pressure: if too many marks are unconfirmed, wait. Drains + // immediately on cancelSpeaking. + await this.waitForMarkWindow(); + if (!this.isSpeaking) break; + const chunk = bytes.subarray(i, i + StreamHandler.PREWARM_CHUNK_BYTES); + if (!firstChunkSent) firstChunkSent = true; if (this.aec) this.aec.pushFarEnd(chunk); const encoded = this.encodePipelineAudio(chunk); this.deps.bridge.sendAudio(this.ws, encoded, this.streamSid); this.markFirstAudioSent(); + const markPromise = this.sendMarkAwaitable(); + if (markPromise === null) { + // Telnyx (or any future carrier without marks): pace by the + // chunk's playout duration so the carrier buffer can't out-run + // a sendClear by more than one chunk. PCM16 16 kHz is 32 + // bytes/ms, so ``chunk.length / 32`` is the playout duration. + const playoutMs = Math.max( + 1, + Math.floor(chunk.length / StreamHandler.PCM16_16K_BYTES_PER_MS), + ); + await new Promise((resolve) => setTimeout(resolve, playoutMs)); + } } return firstChunkSent; } @@ -1491,18 +1676,18 @@ export class StreamHandler { this.metricsAcc.recordTtsFirstByte(); await this.emitAudioOut(); } - // Far-end tap for the echo canceller — push the exact PCM the - // carrier-side encoder will transmit. Without this the AEC - // adapt loop has no reference signal during the intro, - // resulting in unmitigated bleed-through and a "first turn - // unresponsive" UX where the user's voice is masked by the - // agent's TTS in the inbound channel. - if (this.aec) { - this.aec.pushFarEnd(chunk); - } - const encoded = this.encodePipelineAudio(chunk); - this.deps.bridge.sendAudio(this.ws, encoded, this.streamSid); - this.markFirstAudioSent(); + // BUG #128: route every TTS chunk through the paced sender so + // we never push more than ``FIRST_MESSAGE_MARK_WINDOW`` chunks + // of audio ahead of carrier playback. The previous burst-send + // (sendAudio in a tight loop with no marks) let Twilio's + // outbound buffer reach several seconds — a barge-in's + // sendClear race-lost against the queued media frames and the + // agent kept talking on the user's earpiece for up to ~2 s + // after the user spoke. The paced sender also drives the AEC + // far-end tap and ``markFirstAudioSent`` internally so this + // path stays parity-clean with ``streamPrewarmBytes``. + const sent = await this.sendPacedFirstMessageBytes(chunk); + if (!sent) break; } } } catch (e) { diff --git a/libraries/typescript/tests/unit/prewarm.test.ts b/libraries/typescript/tests/unit/prewarm.test.ts index b2cc893c..d116fdab 100644 --- a/libraries/typescript/tests/unit/prewarm.test.ts +++ b/libraries/typescript/tests/unit/prewarm.test.ts @@ -296,11 +296,19 @@ describe('[unit] streamPrewarmBytes — chunked send for cancel granularity (FIX type WSWebSocket = import('ws').WebSocket; const sendAudio = vi.fn(); + let handlerRef: { onMark: (n: string) => Promise } | null = null; + // BUG #128: every chunk now pairs with a Twilio mark and the loop + // window-blocks until echoes arrive. Production Twilio echoes within + // 100-250 ms of playback; in this test we echo synchronously so the + // chunking assertion can complete inside the vitest timeout. + const sendMark = vi.fn((_ws: unknown, name: string) => { + if (handlerRef) void handlerRef.onMark(name); + }); const bridge = { label: 'TestBridge', telephonyProvider: 'twilio', sendAudio, - sendMark: vi.fn(), + sendMark, sendClear: vi.fn(), transferCall: vi.fn().mockResolvedValue(undefined), endCall: vi.fn().mockResolvedValue(undefined), @@ -339,11 +347,13 @@ describe('[unit] streamPrewarmBytes — chunked send for cancel granularity (FIX streamSid: string; firstAudioSentAt: number | null; streamPrewarmBytes: (bytes: Buffer) => Promise; + onMark: (n: string) => Promise; } const p = h as unknown as Priv; p.isSpeaking = true; p.streamSid = 'SM-test'; p.firstAudioSentAt = Date.now(); // gate open + handlerRef = p; // hand the handler to the mark-echo mock above // 5 s of PCM16 @ 16 kHz mono = 5 * 16000 * 2 = 160_000 bytes. const prewarmBytes = Buffer.alloc(160_000, 1); diff --git a/libraries/typescript/tests/unit/stream-handler.test.ts b/libraries/typescript/tests/unit/stream-handler.test.ts index 3cd32c3d..764b3a2f 100644 --- a/libraries/typescript/tests/unit/stream-handler.test.ts +++ b/libraries/typescript/tests/unit/stream-handler.test.ts @@ -523,4 +523,163 @@ describe('StreamHandler', () => { }); }); }); + + // ------------------------------------------------------------------------- + // firstMessage mark-gated pacing — BUG #128 regression coverage. + // + // Pre-fix the firstMessage TTS chunks were pushed into the carrier + // WebSocket as fast as the TTS provider yielded them. A barge-in + // mid-buffer issued ``sendClear``, but the WebSocket queue between the + // SDK and Twilio's edge held several seconds of media frames already, + // and the agent kept talking on the user's earpiece until that drained. + // + // Post-fix every chunk is followed by a mark; the loop awaits the + // oldest mark before sending more once ``FIRST_MESSAGE_MARK_WINDOW`` + // chunks are unconfirmed. ``cancelSpeaking`` drains every pending mark + // so the waiting loop exits on the next tick. + // ------------------------------------------------------------------------- + describe('firstMessage mark-gated pacing', () => { + interface FmPriv { + isSpeaking: boolean; + speakingStartedAt: number | null; + firstAudioSentAt: number | null; + aec: unknown; + streamSid: string; + pendingMarks: Array<{ name: string; resolve: () => void; promise: Promise }>; + firstMessageMarkCounter: number; + sendPacedFirstMessageBytes: (b: Buffer) => Promise; + onMark: (n: string) => Promise; + runBargeInCancel: (t: string) => void; + } + + function fmPriv(h: StreamHandler): FmPriv { + return h as unknown as FmPriv; + } + + function primeForFirstMessage(h: StreamHandler): FmPriv { + const p = fmPriv(h); + p.isSpeaking = true; + p.speakingStartedAt = Date.now() - 5000; + p.firstAudioSentAt = Date.now() - 5000; + p.aec = null; + p.streamSid = 'MZtest'; + return p; + } + + async function flushMicrotasks(count = 10): Promise { + for (let i = 0; i < count; i++) await Promise.resolve(); + } + + const CHUNK_BYTES = 1280; // matches StreamHandler.PREWARM_CHUNK_BYTES + + it('caps in-flight chunks at FIRST_MESSAGE_MARK_WINDOW and bails on barge-in', async () => { + const sendAudio = vi.fn(); + const sendMark = vi.fn(); + const sendClear = vi.fn(); + const bridge = makeMockBridge({ sendAudio, sendMark, sendClear }); + const h = new StreamHandler( + makeDeps({ bridge }), + makeMockWs(), + '+15551111111', + '+15552222222', + ); + const p = primeForFirstMessage(h); + // 4 chunks. Window=3, so chunks 1–3 send back-to-back and chunk 4 + // blocks on waitForMarkWindow until either a mark echoes OR + // cancelSpeaking drains the queue. + const bytes = Buffer.alloc(CHUNK_BYTES * 4, 0); + const sendPromise = p.sendPacedFirstMessageBytes(bytes); + + await flushMicrotasks(); + expect(sendAudio).toHaveBeenCalledTimes(3); + expect(sendMark).toHaveBeenCalledTimes(3); + expect(p.pendingMarks.length).toBe(3); + + // Simulate a confirmed barge-in: runBargeInCancel calls sendClear + + // cancelSpeaking, and cancelSpeaking drains pendingMarks so the + // sliding-window wait exits on the next tick. + p.runBargeInCancel('the user spoke'); + await sendPromise; + + expect(sendClear).toHaveBeenCalledTimes(1); + expect(p.isSpeaking).toBe(false); + // Chunk 4 must NOT have hit the wire. + expect(sendAudio).toHaveBeenCalledTimes(3); + }); + + it('echoed mark slides the window and the next chunk goes out', async () => { + const sendAudio = vi.fn(); + const sendMark = vi.fn(); + const bridge = makeMockBridge({ sendAudio, sendMark }); + const h = new StreamHandler( + makeDeps({ bridge }), + makeMockWs(), + '+15551111111', + '+15552222222', + ); + const p = primeForFirstMessage(h); + const bytes = Buffer.alloc(CHUNK_BYTES * 4, 0); + const sendPromise = p.sendPacedFirstMessageBytes(bytes); + + await flushMicrotasks(); + // Three chunks in flight, one waiting on the window. + expect(sendAudio).toHaveBeenCalledTimes(3); + expect(sendMark).toHaveBeenCalledTimes(3); + + // Twilio echoes the FIRST chunk's mark — the loop should advance. + await p.onMark('fm_1'); + await flushMicrotasks(); + + expect(sendAudio).toHaveBeenCalledTimes(4); + expect(sendMark).toHaveBeenCalledTimes(4); + // Let the remaining marks "play" so the loop returns. + await p.onMark('fm_2'); + await p.onMark('fm_3'); + await p.onMark('fm_4'); + await sendPromise; + expect(p.pendingMarks.length).toBe(0); + }); + + it('Telnyx (no marks): paces via playout-time and bails on cancelSpeaking', async () => { + const sendAudio = vi.fn(); + const sendMark = vi.fn(); + const sendClear = vi.fn(); + const bridge = makeMockBridge({ + telephonyProvider: 'telnyx', + sendAudio, + sendMark, + sendClear, + }); + const h = new StreamHandler( + makeDeps({ bridge }), + makeMockWs(), + '+15551111111', + '+15552222222', + ); + const p = primeForFirstMessage(h); + // 4 chunks. With time-based pacing every iteration awaits a real + // setTimeout (40 ms for a 1280-byte PCM16 chunk), so the loop + // emits the first chunk and then yields long enough for the test + // to trigger a barge-in. + const bytes = Buffer.alloc(CHUNK_BYTES * 4, 0); + const sendPromise = p.sendPacedFirstMessageBytes(bytes); + + await flushMicrotasks(); + // Telnyx never sends marks — the queue stays empty even mid-loop. + expect(sendMark).not.toHaveBeenCalled(); + expect(p.pendingMarks.length).toBe(0); + // At least the first chunk should have hit the wire by the time + // we trip the cancel. + const sentBeforeCancel = sendAudio.mock.calls.length; + expect(sentBeforeCancel).toBeGreaterThanOrEqual(1); + + p.runBargeInCancel('user spoke'); + await sendPromise; + + expect(sendClear).toHaveBeenCalledTimes(1); + expect(p.isSpeaking).toBe(false); + // After cancel no further chunks may go out. + expect(sendAudio).toHaveBeenCalledTimes(sentBeforeCancel); + }); + }); }); From 7ad4b14175c97279a5a3eb100da91cc1d3b10c2f Mon Sep 17 00:00:00 2001 From: nicolotognoni Date: Tue, 12 May 2026 14:51:08 +0200 Subject: [PATCH 3/6] fix(barge-in): drain pending marks on call cleanup/stop/ws-close MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The firstMessage paced sender accumulates one mark waiter (asyncio.Future on Python / Promise on TS) per chunk in _pending_marks / pendingMarks while audio is streaming to the carrier. The barge-in cancel path already drained these, but a call that ended without going through cancel — carrier WebSocket drop, hangup mid firstMessage, stop event arriving before the paced sender finished — left every queued future unresolved. The send loop was awaiting them, so the orphan futures leaked until the handler itself was garbage-collected. Fix: PipelineStreamHandler.cleanup (Py) now invokes _drain_pending_marks before tearing down adapters; the TS handleStop and handleWsClose do the equivalent via drainPendingMarks(). Idempotent and safe when the queue is already empty. Added regression coverage: - libraries/python/tests/unit/test_first_message_pacing.py (TestCleanupDrainsPendingMarks) - libraries/typescript/tests/unit/stream-handler.test.ts (cleanup drains pending firstMessage marks — handleStop + handleWsClose) --- CHANGELOG.md | 24 ++++++ libraries/python/getpatter/stream_handler.py | 7 ++ .../tests/unit/test_first_message_pacing.py | 36 +++++++++ libraries/typescript/src/stream-handler.ts | 9 +++ .../tests/unit/stream-handler.test.ts | 75 +++++++++++++++++++ 5 files changed, 151 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1b3e656..808be481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ ## 0.6.1 (2026-05-12) +### Fixed — firstMessage pending mark waiters leaked on abnormal call end (Python + TypeScript parity) + +`PipelineStreamHandler._send_paced_first_message_bytes` (Py) and +`StreamHandler.sendPacedFirstMessageBytes` (TS) accumulate one +`asyncio.Future` (Py) / `Promise` (TS) per chunk in `_pending_marks` / +`pendingMarks` while the firstMessage is paced through the carrier. +The cancel path (`runBargeInCancel` / barge-in confirm) already drained +these, but a call that ended without going through cancel — carrier +WebSocket drop, hangup mid firstMessage, stop event arriving before the +paced sender finished — left every queued future unresolved. The send +loop was awaiting them, so the orphan promises leaked until the handler +itself was garbage-collected. + +Fix: `PipelineStreamHandler.cleanup` now invokes `_drain_pending_marks` +before tearing down adapters; the TS `handleStop` and `handleWsClose` +do the equivalent via `drainPendingMarks()`. Idempotent and safe when +the queue is already empty. Files: +`libraries/python/getpatter/stream_handler.py`, +`libraries/typescript/src/stream-handler.ts`. Coverage: +`libraries/python/tests/unit/test_first_message_pacing.py` +(`TestCleanupDrainsPendingMarks`), +`libraries/typescript/tests/unit/stream-handler.test.ts` +(`cleanup drains pending firstMessage marks`). + ### Fixed — firstMessage was effectively un-interruptible: barge-in lost the race against the carrier outbound buffer (#128, Python + TypeScript parity) `StreamHandler.streamPrewarmBytes` (TS) / diff --git a/libraries/python/getpatter/stream_handler.py b/libraries/python/getpatter/stream_handler.py index 8e7d831a..888d349e 100644 --- a/libraries/python/getpatter/stream_handler.py +++ b/libraries/python/getpatter/stream_handler.py @@ -3554,6 +3554,13 @@ async def cleanup(self) -> None: # spurious overlap_end events. Idempotent: safe to call when no # pending state exists. self._clear_pending_barge_in() + # Resolve every pending firstMessage mark future before tearing + # down adapters. Without this, a call that ends abnormally mid + # firstMessage (carrier WS drop, hangup during the paced sender) + # leaves orphan ``asyncio.Future`` instances awaited by the send + # loop that nothing will ever resolve. + if getattr(self, "_pending_marks", None) is not None: + self._drain_pending_marks() if self._stt_task: self._stt_task.cancel() try: diff --git a/libraries/python/tests/unit/test_first_message_pacing.py b/libraries/python/tests/unit/test_first_message_pacing.py index 7e5fcc4c..b5109b94 100644 --- a/libraries/python/tests/unit/test_first_message_pacing.py +++ b/libraries/python/tests/unit/test_first_message_pacing.py @@ -191,3 +191,39 @@ async def test_echo_for_later_mark_resolves_earlier_waiters(self) -> None: await handler.on_mark("fm_2") # fm_1 and fm_2 are drained; fm_3 stays pending. assert [name for name, _ in handler._pending_marks] == ["fm_3"] + + +@pytest.mark.unit +class TestCleanupDrainsPendingMarks: + """Cleanup on abnormal call end (carrier WS drop / hangup mid + firstMessage) must resolve every pending mark future so the paced + send loop never leaves orphan ``asyncio.Future`` instances. + """ + + async def test_cleanup_drains_pending_marks(self) -> None: + handler, _sender = _make_handler(for_twilio=True) + # Wire enough stubs so PipelineStreamHandler.cleanup() does not + # crash. The actual stt/tts/remote_handler tear-down branches + # short-circuit on ``None``. + handler._barge_in_pending_task = None + handler._barge_in_pending_since = None + handler._stt_task = None + handler._stt = None + handler._tts = None + handler._remote_handler = None + handler._resampler_8k_to_16k = None + + # Queue three marks via the public send path then trigger + # cleanup to mimic an abnormal end mid-send. + await handler._send_mark_awaitable() + await handler._send_mark_awaitable() + await handler._send_mark_awaitable() + pending_futures = [fut for _name, fut in handler._pending_marks] + assert len(pending_futures) == 3 + assert all(not fut.done() for fut in pending_futures) + + await handler.cleanup() + + # Every queued future is resolved and the queue is empty. + assert handler._pending_marks == [] + assert all(fut.done() for fut in pending_futures) diff --git a/libraries/typescript/src/stream-handler.ts b/libraries/typescript/src/stream-handler.ts index fceb2c03..834f017f 100644 --- a/libraries/typescript/src/stream-handler.ts +++ b/libraries/typescript/src/stream-handler.ts @@ -1311,6 +1311,11 @@ export class StreamHandler { // metrics object — a slow leak in long-running servers and a race // producing spurious overlap_end events. Idempotent. this.clearPendingBargeIn(); + // Resolve every pending firstMessage mark waiter before tearing the + // adapter down. A call that ends mid firstMessage (carrier stop + // arriving before the paced sender finished) would otherwise leak + // unresolved promises owned by the send loop. + this.drainPendingMarks(); this.clearGraceTimer(); this.flushResamplers(); await this.closeSttOnce(); @@ -1324,6 +1329,10 @@ export class StreamHandler { // See handleStop — drop pending barge-in timer before cleanup so a // dead handler can never fire a stale recordOverlapEnd callback. this.clearPendingBargeIn(); + // See handleStop — drain pending firstMessage marks so an abnormal + // carrier WS drop during the paced sender cannot leak unresolved + // promises owned by the send loop. + this.drainPendingMarks(); this.clearGraceTimer(); this.flushResamplers(); // Drain STT first so in-flight transcripts fire before onCallEnd. diff --git a/libraries/typescript/tests/unit/stream-handler.test.ts b/libraries/typescript/tests/unit/stream-handler.test.ts index 764b3a2f..cf574d78 100644 --- a/libraries/typescript/tests/unit/stream-handler.test.ts +++ b/libraries/typescript/tests/unit/stream-handler.test.ts @@ -682,4 +682,79 @@ describe('StreamHandler', () => { expect(sendAudio).toHaveBeenCalledTimes(sentBeforeCancel); }); }); + + describe('cleanup drains pending firstMessage marks', () => { + interface CleanupPriv { + isSpeaking: boolean; + speakingStartedAt: number | null; + firstAudioSentAt: number | null; + aec: unknown; + streamSid: string; + pendingMarks: Array<{ name: string; resolve: () => void; promise: Promise }>; + firstMessageMarkCounter: number; + sendMarkAwaitable: () => Promise | null; + } + + function priv(h: StreamHandler): CleanupPriv { + return h as unknown as CleanupPriv; + } + + function primeForFirstMessage(h: StreamHandler): CleanupPriv { + const p = priv(h); + p.isSpeaking = true; + p.speakingStartedAt = Date.now() - 5000; + p.firstAudioSentAt = Date.now() - 5000; + p.aec = null; + p.streamSid = 'MZtest'; + return p; + } + + it('handleStop resolves every pending mark', async () => { + const sendMark = vi.fn(); + const bridge = makeMockBridge({ sendMark }); + const h = new StreamHandler( + makeDeps({ bridge }), + makeMockWs(), + '+15551111111', + '+15552222222', + ); + const p = primeForFirstMessage(h); + + // Queue three marks via the public send path then simulate an + // abnormal stop mid firstMessage. Capture each promise so we + // can assert they all resolve after handleStop. + const m1 = p.sendMarkAwaitable(); + const m2 = p.sendMarkAwaitable(); + const m3 = p.sendMarkAwaitable(); + expect(p.pendingMarks.length).toBe(3); + + await h.handleStop(); + + expect(p.pendingMarks.length).toBe(0); + // Every captured promise resolved (await would hang otherwise). + await Promise.all([m1, m2, m3]); + }); + + it('handleWsClose resolves every pending mark', async () => { + const sendMark = vi.fn(); + const bridge = makeMockBridge({ sendMark }); + const h = new StreamHandler( + makeDeps({ bridge }), + makeMockWs(), + '+15551111111', + '+15552222222', + ); + const p = primeForFirstMessage(h); + + const m1 = p.sendMarkAwaitable(); + const m2 = p.sendMarkAwaitable(); + const m3 = p.sendMarkAwaitable(); + expect(p.pendingMarks.length).toBe(3); + + await h.handleWsClose(); + + expect(p.pendingMarks.length).toBe(0); + await Promise.all([m1, m2, m3]); + }); + }); }); From eaa02658d78685d954a23914d4f9cf0ed55473e1 Mon Sep 17 00:00:00 2001 From: nicolotognoni Date: Tue, 12 May 2026 14:54:07 +0200 Subject: [PATCH 4/6] fix(barge-in): reset firstMessage mark counter per send + on cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PipelineStreamHandler._first_message_mark_counter (Py) and StreamHandler.firstMessageMarkCounter (TS) were never reset between turns or calls. With handler re-use, the counter incremented monotonically across turns — a paced send for the second turn issued fm_ while the carrier could still be echoing a stale fm_ from the previous turn, corrupting FIFO matching in on_mark / onMark. Fix: reset the counter to 0 at the top of _send_paced_first_message_bytes (Py) / sendPacedFirstMessageBytes (TS) so each paced send begins a fresh fm_1, fm_2, ... sequence. Also reset on cleanup (PipelineStreamHandler.cleanup Py, handleStop + handleWsClose TS) as a belt-and-braces against the cross-call boundary. Coverage: - libraries/python/tests/unit/test_first_message_pacing.py (TestFirstMessageMarkCounterReset — per-send reset + cleanup reset) - libraries/typescript/tests/unit/stream-handler.test.ts (firstMessage mark counter resets across sends + on cleanup) --- CHANGELOG.md | 20 ++++ libraries/python/getpatter/stream_handler.py | 16 +++ .../tests/unit/test_first_message_pacing.py | 66 +++++++++++ libraries/typescript/src/stream-handler.ts | 17 ++- .../tests/unit/stream-handler.test.ts | 104 ++++++++++++++++++ 5 files changed, 222 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 808be481..b691b75a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ ## 0.6.1 (2026-05-12) +### Fixed — firstMessage mark counter could persist stale numbering across re-used handler instances (Python + TypeScript parity) + +`PipelineStreamHandler._first_message_mark_counter` (Py) and +`StreamHandler.firstMessageMarkCounter` (TS) were never reset between +turns or calls. With handler re-use, the counter incremented +monotonically — a paced send for the second turn issued +`fm_` while the carrier could still echo a stale +`fm_` from the previous turn, corrupting the FIFO matching in +`on_mark` / `onMark`. + +Fix: reset the counter to 0 at the top of `_send_paced_first_message_bytes` +(Py) / `sendPacedFirstMessageBytes` (TS) so every paced send begins a +fresh `fm_1, fm_2, …` sequence. Also reset on cleanup +(`PipelineStreamHandler.cleanup` Py, `handleStop` + `handleWsClose` TS) +as a belt-and-braces against the cross-call boundary. Coverage: +`libraries/python/tests/unit/test_first_message_pacing.py` +(`TestFirstMessageMarkCounterReset`), +`libraries/typescript/tests/unit/stream-handler.test.ts` +(`firstMessage mark counter resets across sends + on cleanup`). + ### Fixed — firstMessage pending mark waiters leaked on abnormal call end (Python + TypeScript parity) `PipelineStreamHandler._send_paced_first_message_bytes` (Py) and diff --git a/libraries/python/getpatter/stream_handler.py b/libraries/python/getpatter/stream_handler.py index 888d349e..1165192a 100644 --- a/libraries/python/getpatter/stream_handler.py +++ b/libraries/python/getpatter/stream_handler.py @@ -3518,6 +3518,17 @@ async def _send_paced_first_message_bytes(self, bytes_: bytes) -> bool: uses that to decide whether to record the TTS-first-byte / turn-complete metrics. """ + # Reset the per-send mark counter so each invocation produces a + # fresh ``fm_1, fm_2, ...`` sequence. Without this the counter + # grows monotonically across turns on a re-used handler and a + # stale ``fm_N`` echo from an earlier turn could match a mark + # name issued later, corrupting the FIFO matching in + # ``on_mark``. The ``_pending_marks`` queue is also expected + # empty here by the caller's cancel / cleanup paths; if it is + # not (defensive re-entry) we drain before resetting. + if self._pending_marks: + self._drain_pending_marks() + self._first_message_mark_counter = 0 first_chunk_sent = False for i in range(0, len(bytes_), self._PREWARM_CHUNK_BYTES): if not self._is_speaking: @@ -3561,6 +3572,11 @@ async def cleanup(self) -> None: # loop that nothing will ever resolve. if getattr(self, "_pending_marks", None) is not None: self._drain_pending_marks() + # Reset the firstMessage mark counter so a re-used handler + # instance starts ``fm_`` numbering at 1 on the next call. + # See ``_send_paced_first_message_bytes`` for the per-send reset + # that protects the within-call path. + self._first_message_mark_counter = 0 if self._stt_task: self._stt_task.cancel() try: diff --git a/libraries/python/tests/unit/test_first_message_pacing.py b/libraries/python/tests/unit/test_first_message_pacing.py index b5109b94..2c5b9eb9 100644 --- a/libraries/python/tests/unit/test_first_message_pacing.py +++ b/libraries/python/tests/unit/test_first_message_pacing.py @@ -227,3 +227,69 @@ async def test_cleanup_drains_pending_marks(self) -> None: # Every queued future is resolved and the queue is empty. assert handler._pending_marks == [] assert all(fut.done() for fut in pending_futures) + + +@pytest.mark.unit +class TestFirstMessageMarkCounterReset: + """The ``_first_message_mark_counter`` must reset at the top of each + paced send AND on cleanup so a re-used handler instance never reuses + a stale ``fm_`` name across turns. + """ + + async def test_send_paced_resets_counter_between_consecutive_sends(self) -> None: + """Each ``_send_paced_first_message_bytes`` invocation re-starts + the ``fm_`` numbering at 1 — without the reset, the counter + would grow monotonically across turns and a stale echo for an + earlier turn's ``fm_N`` could match a mark name issued later. + """ + handler, sender = _make_handler(for_twilio=True) + bytes_ = b"\x00" * (CHUNK_BYTES * 2) + + # First send: two chunks ≤ window (3) so the loop yields after + # the first ``_wait_for_mark_window`` pre-check on chunk 3. + task1 = asyncio.create_task(handler._send_paced_first_message_bytes(bytes_)) + for _ in range(20): + await asyncio.sleep(0) + await handler.on_mark("fm_1") + await handler.on_mark("fm_2") + await task1 + assert handler._first_message_mark_counter == 2 + assert handler._pending_marks == [] + assert sender.marks == ["fm_1", "fm_2"] + + # Second send: counter must reset to 0 before iterating so the + # new sequence is fm_1, fm_2 — NOT fm_3, fm_4. + task2 = asyncio.create_task(handler._send_paced_first_message_bytes(bytes_)) + for _ in range(20): + await asyncio.sleep(0) + # New marks recorded by the sender are appended after the prior + # turn's two marks. + new_marks = sender.marks[2:] + assert new_marks == ["fm_1", "fm_2"] + assert handler._first_message_mark_counter == 2 + + await handler.on_mark("fm_1") + await handler.on_mark("fm_2") + await task2 + + async def test_cleanup_resets_counter(self) -> None: + """Cleanup must reset ``_first_message_mark_counter`` to 0 so a + re-used handler starts fresh on the next call. Defensive: the + per-send reset is the canonical path, but cleanup belt-and-braces + the cross-call boundary. + """ + handler, _sender = _make_handler(for_twilio=True) + handler._barge_in_pending_task = None + handler._barge_in_pending_since = None + handler._stt_task = None + handler._stt = None + handler._tts = None + handler._remote_handler = None + handler._resampler_8k_to_16k = None + + # Pretend a prior call left the counter at 7. + handler._first_message_mark_counter = 7 + + await handler.cleanup() + + assert handler._first_message_mark_counter == 0 diff --git a/libraries/typescript/src/stream-handler.ts b/libraries/typescript/src/stream-handler.ts index 834f017f..9d0279f2 100644 --- a/libraries/typescript/src/stream-handler.ts +++ b/libraries/typescript/src/stream-handler.ts @@ -1316,6 +1316,11 @@ export class StreamHandler { // arriving before the paced sender finished) would otherwise leak // unresolved promises owned by the send loop. this.drainPendingMarks(); + // Reset the firstMessage mark counter so a re-used handler starts + // ``fm_`` numbering at 1 on the next call. See + // ``sendPacedFirstMessageBytes`` for the per-send reset that + // protects the within-call path. + this.firstMessageMarkCounter = 0; this.clearGraceTimer(); this.flushResamplers(); await this.closeSttOnce(); @@ -1331,8 +1336,9 @@ export class StreamHandler { this.clearPendingBargeIn(); // See handleStop — drain pending firstMessage marks so an abnormal // carrier WS drop during the paced sender cannot leak unresolved - // promises owned by the send loop. + // promises owned by the send loop, and reset the counter. this.drainPendingMarks(); + this.firstMessageMarkCounter = 0; this.clearGraceTimer(); this.flushResamplers(); // Drain STT first so in-flight transcripts fire before onCallEnd. @@ -1433,6 +1439,15 @@ export class StreamHandler { * metrics. See BUG #128 for the regression this fix targets. */ private async sendPacedFirstMessageBytes(bytes: Buffer): Promise { + // Reset the per-send mark counter so each invocation produces a + // fresh ``fm_1, fm_2, ...`` sequence. Without this the counter + // grows monotonically across turns on a re-used handler and a + // stale ``fm_N`` echo from an earlier turn could match a mark + // name issued later, corrupting the FIFO matching in ``onMark``. + // The queue is also expected empty here by ``cancelSpeaking`` / + // ``handleStop`` / ``handleWsClose``; drain defensively if not. + if (this.pendingMarks.length > 0) this.drainPendingMarks(); + this.firstMessageMarkCounter = 0; let firstChunkSent = false; for (let i = 0; i < bytes.length; i += StreamHandler.PREWARM_CHUNK_BYTES) { if (!this.isSpeaking) break; // barge-in mid-buffer — stop now diff --git a/libraries/typescript/tests/unit/stream-handler.test.ts b/libraries/typescript/tests/unit/stream-handler.test.ts index cf574d78..24aed99e 100644 --- a/libraries/typescript/tests/unit/stream-handler.test.ts +++ b/libraries/typescript/tests/unit/stream-handler.test.ts @@ -757,4 +757,108 @@ describe('StreamHandler', () => { await Promise.all([m1, m2, m3]); }); }); + + describe('firstMessage mark counter resets across sends + on cleanup', () => { + interface CounterPriv { + isSpeaking: boolean; + speakingStartedAt: number | null; + firstAudioSentAt: number | null; + aec: unknown; + streamSid: string; + pendingMarks: Array<{ name: string; resolve: () => void; promise: Promise }>; + firstMessageMarkCounter: number; + sendPacedFirstMessageBytes: (b: Buffer) => Promise; + onMark: (n: string) => Promise; + } + + function priv(h: StreamHandler): CounterPriv { + return h as unknown as CounterPriv; + } + + function primeForFirstMessage(h: StreamHandler): CounterPriv { + const p = priv(h); + p.isSpeaking = true; + p.speakingStartedAt = Date.now() - 5000; + p.firstAudioSentAt = Date.now() - 5000; + p.aec = null; + p.streamSid = 'MZtest'; + return p; + } + + it('sendPacedFirstMessageBytes resets counter between consecutive sends', async () => { + const sendAudio = vi.fn(); + const sendMark = vi.fn(); + const bridge = makeMockBridge({ sendAudio, sendMark }); + const h = new StreamHandler( + makeDeps({ bridge }), + makeMockWs(), + '+15551111111', + '+15552222222', + ); + const p = primeForFirstMessage(h); + + // CHUNK_BYTES = 1280 matches StreamHandler.PREWARM_CHUNK_BYTES. + // Two chunks fit inside the window (3) so the loop completes + // synchronously after we resolve the marks. + const CHUNK_BYTES = 1280; + const bytes = Buffer.alloc(CHUNK_BYTES * 2, 0); + + const send1 = p.sendPacedFirstMessageBytes(bytes); + for (let i = 0; i < 10; i++) await Promise.resolve(); + await p.onMark('fm_1'); + await p.onMark('fm_2'); + await send1; + expect(p.firstMessageMarkCounter).toBe(2); + expect(p.pendingMarks.length).toBe(0); + const markCallsAfterFirst = sendMark.mock.calls.length; + expect( + sendMark.mock.calls.slice(0, markCallsAfterFirst).map((c) => c[1] as string), + ).toEqual(['fm_1', 'fm_2']); + + // Second send: counter must reset to 0 at the top of the loop, + // so the new sequence is fm_1, fm_2 — NOT fm_3, fm_4. + const send2 = p.sendPacedFirstMessageBytes(bytes); + for (let i = 0; i < 10; i++) await Promise.resolve(); + const newMarks = sendMark.mock.calls + .slice(markCallsAfterFirst) + .map((c) => c[1] as string); + expect(newMarks).toEqual(['fm_1', 'fm_2']); + expect(p.firstMessageMarkCounter).toBe(2); + + await p.onMark('fm_1'); + await p.onMark('fm_2'); + await send2; + }); + + it('handleStop resets firstMessageMarkCounter', async () => { + const h = new StreamHandler( + makeDeps(), + makeMockWs(), + '+15551111111', + '+15552222222', + ); + const p = priv(h); + // Pretend a prior turn left the counter at 7. + p.firstMessageMarkCounter = 7; + + await h.handleStop(); + + expect(p.firstMessageMarkCounter).toBe(0); + }); + + it('handleWsClose resets firstMessageMarkCounter', async () => { + const h = new StreamHandler( + makeDeps(), + makeMockWs(), + '+15551111111', + '+15552222222', + ); + const p = priv(h); + p.firstMessageMarkCounter = 7; + + await h.handleWsClose(); + + expect(p.firstMessageMarkCounter).toBe(0); + }); + }); }); From 2dc2c5cc3a0acdc4362ebb2ef4351689493b0eab Mon Sep 17 00:00:00 2001 From: nicolotognoni Date: Tue, 12 May 2026 14:56:44 +0200 Subject: [PATCH 5/6] fix(dashboard): cap merged UI calls at 500 + sort by startedAt desc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mergeCallPreserving in dashboard-app/src/hooks/mergeCalls.ts preserved prev_only calls indefinitely by appending them after the fresh snapshot block. Two consequences on a long-lived session: 1. The UI array grew unbounded — once the session cycled through more than 500 calls (the server-side MetricsStore ring buffer default), rows the server had already evicted stayed pinned by prev and were re-appended on every refresh. 2. Ordering was non-deterministic — prev_only rows always landed at the bottom regardless of their startedAtMs, so a newer call could end up below an older one if the snapshot ordering shifted. Fix: after the upsert pass, sort the merged list by startedAtMs descending and slice to MAX_UI_CALLS = 500 so the SPA mirrors the server ring buffer. Coverage: dashboard-app/src/hooks/mergeCalls.test.ts adds a 600-prev+1-fresh cap test and an explicit startedAtMs ordering test. --- CHANGELOG.md | 21 +++++++++++++ dashboard-app/src/hooks/mergeCalls.test.ts | 36 ++++++++++++++++++++++ dashboard-app/src/hooks/mergeCalls.ts | 23 +++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b691b75a..b1575da2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ ## 0.6.1 (2026-05-12) +### Fixed — Dashboard SPA call list grew unbounded and ordering was non-deterministic across SSE refreshes + +`mergeCallPreserving` in `dashboard-app/src/hooks/mergeCalls.ts` +preserved ``prev_only`` calls indefinitely by appending them after the +fresh snapshot block, with two consequences: + +1. On a long-lived session that cycled through more than 500 calls + (the server-side ``MetricsStore`` ring buffer default), the UI + array kept growing because rows the server had already evicted + stayed pinned by ``prev`` and were re-appended on every refresh. +2. Ordering was non-deterministic: live rows landed at the position + the server snapshot gave them, while ``prev_only`` rows always + landed last regardless of their actual ``startedAtMs``, so a + newer call could end up below an older one. + +Fix: after the upsert pass, sort the merged list by ``startedAtMs`` +descending (newest first) and slice to ``MAX_UI_CALLS = 500`` so the +SPA mirrors the server ring buffer. Coverage: +`dashboard-app/src/hooks/mergeCalls.test.ts` — adds a 600-prev+1-fresh +cap test and an explicit startedAtMs ordering test. + ### Fixed — firstMessage mark counter could persist stale numbering across re-used handler instances (Python + TypeScript parity) `PipelineStreamHandler._first_message_mark_counter` (Py) and diff --git a/dashboard-app/src/hooks/mergeCalls.test.ts b/dashboard-app/src/hooks/mergeCalls.test.ts index 17437a47..bfa0bec0 100644 --- a/dashboard-app/src/hooks/mergeCalls.test.ts +++ b/dashboard-app/src/hooks/mergeCalls.test.ts @@ -135,4 +135,40 @@ describe('mergeCallPreserving', () => { state = mergeCallPreserving(state, mergeCalls([record('two')], [])); expect(state.map((c) => c.id).sort()).toEqual(['one', 'two']); }); + + it('caps the merged UI list at 500 entries (mirrors server ring buffer)', () => { + // 600 prev rows with distinct ids and ascending startedAtMs so the + // sort can stably order them newest-first. The cap drops the + // oldest 100. + const prev: Call[] = Array.from({ length: 600 }, (_, i) => + makeCall(`prev-${i}`, { startedAtMs: 1000 + i }), + ); + // One fresh call from the snapshot. + const next: Call[] = [makeCall('fresh', { startedAtMs: 2000 })]; + + const result = mergeCallPreserving(prev, next); + expect(result.length).toBe(500); + // The newest (``fresh`` at 2000) lands first. + expect(result[0].id).toBe('fresh'); + // 600 prev + 1 fresh = 601 candidates → slice keeps the top 500. + // ``fresh`` (2000) plus prev-599 (1599) down to prev-101 (1101) + // survive; prev-100 (1100) and older are dropped. + const ids = new Set(result.map((c) => c.id)); + expect(ids.has('prev-0')).toBe(false); + expect(ids.has('prev-100')).toBe(false); + // The newest ``prev`` rows survive. + expect(ids.has('prev-599')).toBe(true); + expect(ids.has('prev-101')).toBe(true); + }); + + it('sorts merged calls by startedAtMs descending — newer first', () => { + // ``prev`` holds an older call A; ``next`` adds a newer call B. + // Without the sort, B (a ``next`` entry) would lead and A (a + // ``prev_only`` entry) would land at the bottom regardless of its + // start time. With the sort, ordering is purely by startedAtMs. + const prev: Call[] = [makeCall('A', { startedAtMs: 1000 })]; + const next: Call[] = [makeCall('B', { startedAtMs: 2000 })]; + const result = mergeCallPreserving(prev, next); + expect(result.map((c) => c.id)).toEqual(['B', 'A']); + }); }); diff --git a/dashboard-app/src/hooks/mergeCalls.ts b/dashboard-app/src/hooks/mergeCalls.ts index efbb4b34..8cf73ece 100644 --- a/dashboard-app/src/hooks/mergeCalls.ts +++ b/dashboard-app/src/hooks/mergeCalls.ts @@ -5,6 +5,17 @@ import type { CallRecord } from '../lib/api'; import { toUiCall, type Call } from '../lib/mappers'; +/** + * Hard cap on the number of calls retained in the SPA after a merge. + * Mirrors the server-side ``MetricsStore`` ring buffer default (500) so the + * UI cannot accumulate ``prev_only`` rows for calls the server has already + * evicted. Without this cap, ``mergeCallPreserving`` would grow the array + * indefinitely on long-lived sessions: every prior call still pinned by + * ``prev`` would be re-appended on every refresh even after the server has + * dropped it from the ring buffer. + */ +const MAX_UI_CALLS = 500; + /** * Project the server's active + recent payloads into a single UI list with * stable ordering: active calls first (live status surfaces at the top), @@ -78,5 +89,15 @@ export function mergeCallPreserving(prev: Call[], next: Call[]): Call[] { for (const pc of prev) { if (!nextIds.has(pc.id)) merged.push(pc); } - return merged; + // Sort by ``startedAtMs`` descending so the newest call always lands at + // the top, regardless of whether it came from the snapshot or from + // ``prev``. Without this, ``prev_only`` entries appended after the + // snapshot block kept ordering non-deterministic (live row first only + // when the snapshot already contained it). Calls without an + // ``startedAtMs`` (rare — synthetic terminal rows before the canonical + // write) sort to the end so they don't outrank a live call. + merged.sort((a, b) => (b.startedAtMs ?? 0) - (a.startedAtMs ?? 0)); + // Cap to ``MAX_UI_CALLS`` so a long-lived session that has cycled + // through more than 500 calls cannot grow the UI array unbounded. + return merged.slice(0, MAX_UI_CALLS); } From 53e515a5c55c0281df23f05d22b210a1e3d466ac Mon Sep 17 00:00:00 2001 From: nicolotognoni Date: Tue, 12 May 2026 14:58:26 +0200 Subject: [PATCH 6/6] fix(realtime): only update lastConfirmedMark on matched mark (parity with Python) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit StreamHandler.onMark in libraries/typescript/src/stream-handler.ts unconditionally assigned this.lastConfirmedMark = markName before checking whether the name corresponded to a queued mark. Any echo arriving after the queue was drained, or any mark name emitted by adapters outside the firstMessage queue, would overwrite the handler- level field and contaminate downstream barge-in heuristics gated on lastConfirmedMark. Python stream_handler.py's on_mark never touches a handler-level field at all — the equivalent state lives on TwilioAudioSender.last_confirmed_mark and is updated only by the carrier's own echo handler. The TS path now matches that behaviour defensively: lastConfirmedMark is updated only after the queue lookup confirms a matching entry, mirroring the safer Python semantics. Coverage: libraries/typescript/tests/unit/stream-handler.test.ts (onMark only updates lastConfirmedMark on a matched mark) asserts that an unmatched echo cannot clobber a previously-set value. --- CHANGELOG.md | 19 +++++++ libraries/typescript/src/stream-handler.ts | 13 ++++- .../tests/unit/stream-handler.test.ts | 54 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1575da2..c27a6c51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ ## 0.6.1 (2026-05-12) +### Fixed — TypeScript `onMark` clobbered `lastConfirmedMark` with stale/unknown mark names (parity with Python) + +`StreamHandler.onMark` in `libraries/typescript/src/stream-handler.ts` +unconditionally assigned `this.lastConfirmedMark = markName` before +checking whether the name corresponded to a queued mark. Any echo +arriving after the queue was drained, or any mark name from outside +the firstMessage queue, would overwrite the handler-level field and +contaminate downstream barge-in heuristics gated on +`lastConfirmedMark`. + +Python `stream_handler.py`'s `on_mark` never touches a handler-level +field at all — the equivalent state lives on +`TwilioAudioSender.last_confirmed_mark` and is updated only by the +carrier's own echo handler. The TS path now matches that behaviour +defensively: `lastConfirmedMark` is updated only after the queue +lookup confirms a matching entry. Coverage: +`libraries/typescript/tests/unit/stream-handler.test.ts` +(`onMark only updates lastConfirmedMark on a matched mark`). + ### Fixed — Dashboard SPA call list grew unbounded and ordering was non-deterministic across SSE refreshes `mergeCallPreserving` in `dashboard-app/src/hooks/mergeCalls.ts` diff --git a/libraries/typescript/src/stream-handler.ts b/libraries/typescript/src/stream-handler.ts index 9d0279f2..8c462974 100644 --- a/libraries/typescript/src/stream-handler.ts +++ b/libraries/typescript/src/stream-handler.ts @@ -1282,7 +1282,6 @@ export class StreamHandler { /** Handle a Twilio Media Streams `mark` event acknowledging audio playback boundaries. */ async onMark(markName: string): Promise { if (!markName) return; - this.lastConfirmedMark = markName; // Resolve the firstMessage mark waiter (if any) so the send loop // can advance its sliding window. We resolve the matched entry AND // every entry before it in the queue — Twilio sometimes batches @@ -1291,6 +1290,18 @@ export class StreamHandler { // numbered one (rare but observed on degraded edges). const idx = this.pendingMarks.findIndex((m) => m.name === markName); if (idx < 0) return; + // Only record the echo after we have confirmed it matches a known + // queued mark. Before this gate ``onMark`` clobbered + // ``lastConfirmedMark`` with any mark name — including stale + // echoes that no longer correspond to anything we sent, or marks + // emitted by adapters outside the firstMessage queue — which + // would contaminate any downstream barge-in heuristic gated on + // ``lastConfirmedMark``. The Python parity here is structural: + // ``stream_handler.py``'s ``on_mark`` never touches a handler- + // level field at all (the equivalent state lives on + // ``TwilioAudioSender.last_confirmed_mark``, updated only via + // the carrier's own echo handler). + this.lastConfirmedMark = markName; const resolved = this.pendingMarks.splice(0, idx + 1); for (const entry of resolved) { try { diff --git a/libraries/typescript/tests/unit/stream-handler.test.ts b/libraries/typescript/tests/unit/stream-handler.test.ts index 24aed99e..d685dc27 100644 --- a/libraries/typescript/tests/unit/stream-handler.test.ts +++ b/libraries/typescript/tests/unit/stream-handler.test.ts @@ -861,4 +861,58 @@ describe('StreamHandler', () => { expect(p.firstMessageMarkCounter).toBe(0); }); }); + + describe('onMark only updates lastConfirmedMark on a matched mark', () => { + interface OnMarkPriv { + pendingMarks: Array<{ name: string; resolve: () => void; promise: Promise }>; + lastConfirmedMark: string; + } + + it('does not overwrite lastConfirmedMark for an unknown mark name', async () => { + const h = new StreamHandler( + makeDeps(), + makeMockWs(), + '+15551111111', + '+15552222222', + ); + const p = h as unknown as OnMarkPriv; + + // Seed a real matched mark so lastConfirmedMark has a known + // baseline that the unmatched echo must not overwrite. + let resolveSeed!: () => void; + const seedPromise = new Promise((r) => { + resolveSeed = r; + }); + p.pendingMarks.push({ name: 'fm_seed', resolve: resolveSeed, promise: seedPromise }); + await h.onMark('fm_seed'); + expect(p.lastConfirmedMark).toBe('fm_seed'); + + // Emit a mark name that is NOT in pendingMarks — e.g. echo + // arrived after drain, or for an unknown identifier. The + // handler's lastConfirmedMark must NOT be clobbered. + await h.onMark('unknown_xyz'); + expect(p.lastConfirmedMark).toBe('fm_seed'); + }); + + it('updates lastConfirmedMark only after the queue match succeeds', async () => { + const h = new StreamHandler( + makeDeps(), + makeMockWs(), + '+15551111111', + '+15552222222', + ); + const p = h as unknown as OnMarkPriv; + expect(p.lastConfirmedMark).toBe(''); + + let resolveA!: () => void; + const promiseA = new Promise((r) => { + resolveA = r; + }); + p.pendingMarks.push({ name: 'fm_1', resolve: resolveA, promise: promiseA }); + + await h.onMark('fm_1'); + expect(p.lastConfirmedMark).toBe('fm_1'); + expect(p.pendingMarks.length).toBe(0); + }); + }); });