@exotel-npm-dev/exotel-ai-assist 1.3.7 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/dist/controller/index.js +8 -4
- package/dist/hooks/useExotelAIAssist.d.ts +5 -3
- package/dist/index.js +26 -5
- package/dist/react/index.js +26 -5
- package/dist/types/index.d.ts +6 -4
- package/package.json +1 -1
- package/src/components/ExotelAIAssistApp.tsx +27 -1
- package/src/controller/index.ts +14 -4
- package/src/hooks/useExotelAIAssist.ts +5 -3
- package/src/types/index.ts +13 -4
- package/dist/cjs/controller/index.js +0 -1817
- package/dist/cjs/index.js +0 -46949
- package/dist/cjs/react/index.js +0 -39143
package/README.md
CHANGED
|
@@ -121,6 +121,8 @@ function MyCustomUI({ call_sid }: { call_sid: string }) {
|
|
|
121
121
|
|
|
122
122
|
if (!isReady) return <p>Connecting…</p>;
|
|
123
123
|
|
|
124
|
+
if (streamState === "quota_exhausted") return <p>Your organisation's AI Assist quota is exhausted.</p>;
|
|
125
|
+
if (streamState === "agent_quota_exhausted") return <p>Your personal AI Assist call limit is reached.</p>;
|
|
124
126
|
if (streamState === "throttled") return <p>AI Assist is at capacity — available on your next call.</p>;
|
|
125
127
|
if (streamState === "disconnected") return <p>AI Assist disconnected.</p>;
|
|
126
128
|
if (streamState === "connection_timeout") return <p>Connection timed out.</p>;
|
|
@@ -189,7 +191,7 @@ ctrl.on("suggestion", (s) => console.log("Suggestion:", s));
|
|
|
189
191
|
ctrl.on("transcript", (t) => console.log("Transcript:", t));
|
|
190
192
|
ctrl.on("streamState", (state) => {
|
|
191
193
|
console.log("Stream state:", state);
|
|
192
|
-
//
|
|
194
|
+
// Quota blocks are reported as "quota_exhausted" or "agent_quota_exhausted".
|
|
193
195
|
});
|
|
194
196
|
ctrl.on("error", (err) => console.error("Error:", err));
|
|
195
197
|
|
|
@@ -256,7 +258,7 @@ Extends `EventEmitter`.
|
|
|
256
258
|
| `suggestion` | `Suggestion` | New AI suggestion (capped at last 50) |
|
|
257
259
|
| `transcript` | `TranscriptLine[]` | Live transcript update |
|
|
258
260
|
| `sentiment` | `Sentiment` | Sentiment label update |
|
|
259
|
-
| `streamState` | `StreamState` | Server stream state change
|
|
261
|
+
| `streamState` | `StreamState` | Server stream state change, including tenant and per-agent quota exhaustion |
|
|
260
262
|
| `onCallStart` | — | Connection opened |
|
|
261
263
|
| `onCallEnd` | — | Connection closed |
|
|
262
264
|
| `statusChange` | `ConnectionStatus` | WebSocket-level status transition (internal) |
|
|
@@ -270,7 +272,7 @@ Extends `EventEmitter`.
|
|
|
270
272
|
| Field | Type | Description |
|
|
271
273
|
| -------------------------- | ----------------------- | ---------------------------------------------------------------------------- |
|
|
272
274
|
| `isReady` | `boolean` | `true` after connected + server ack. Multi-tab safe. `false` on disconnect |
|
|
273
|
-
| `streamState` | `StreamState \| null` | Server stream state
|
|
275
|
+
| `streamState` | `StreamState \| null` | Server stream state, including `"quota_exhausted"` and `"agent_quota_exhausted"`. `null` until the first stream-state message |
|
|
274
276
|
| `suggestions` | `Suggestion[]` | AI suggestions, oldest first, capped at 50 |
|
|
275
277
|
| `transcripts` | `TranscriptLine[]` | Live transcript lines, ordered by start time |
|
|
276
278
|
| `sentiment` | `Sentiment \| null` | Latest sentiment reading |
|
|
@@ -284,7 +286,14 @@ Extends `EventEmitter`.
|
|
|
284
286
|
### TypeScript Types
|
|
285
287
|
|
|
286
288
|
```ts
|
|
287
|
-
type StreamState =
|
|
289
|
+
type StreamState =
|
|
290
|
+
| "connected"
|
|
291
|
+
| "throttled"
|
|
292
|
+
| "quota_exhausted"
|
|
293
|
+
| "agent_quota_exhausted"
|
|
294
|
+
| "pending"
|
|
295
|
+
| "disconnected"
|
|
296
|
+
| "connection_timeout";
|
|
288
297
|
|
|
289
298
|
type ConnectionStatus = "idle" | "connecting" | "connected" | "disconnected" | "error";
|
|
290
299
|
|
|
@@ -331,7 +340,7 @@ wss://<wssBaseUrl>?[customParam1=value1&customParam2=value2&...]
|
|
|
331
340
|
"type": "stream_status",
|
|
332
341
|
"config": { /* bot config, populated on connected, null otherwise */ },
|
|
333
342
|
"events": [ /* accumulated events since last message, usually [] */ ],
|
|
334
|
-
"stream_state": "connected" | "throttled" | "pending" | "disconnected" | "connection_timeout"
|
|
343
|
+
"stream_state": "connected" | "throttled" | "quota_exhausted" | "agent_quota_exhausted" | "pending" | "disconnected" | "connection_timeout"
|
|
335
344
|
}
|
|
336
345
|
```
|
|
337
346
|
|
|
@@ -339,6 +348,8 @@ wss://<wssBaseUrl>?[customParam1=value1&customParam2=value2&...]
|
|
|
339
348
|
| -------------------- | --------------------------------------------------------- |
|
|
340
349
|
| `connected` | Stream is active — suggestions, transcript, and sentiment flow normally |
|
|
341
350
|
| `throttled` | Capacity full — AI Assist cannot join this call; available on the next call |
|
|
351
|
+
| `quota_exhausted` | The organisation's AI Assist plan quota is exhausted |
|
|
352
|
+
| `agent_quota_exhausted` | The current agent's AI Assist call allowance is exhausted |
|
|
342
353
|
| `pending` | Stream is pending — waiting for a definitive state |
|
|
343
354
|
| `disconnected` | Stream ended or dropped |
|
|
344
355
|
| `connection_timeout` | Connection timeout occurred |
|
package/dist/controller/index.js
CHANGED
|
@@ -1604,6 +1604,10 @@ class Utils {
|
|
|
1604
1604
|
}
|
|
1605
1605
|
Utils.MAX_EXTRA_QUERY_PARAMS = 3;
|
|
1606
1606
|
|
|
1607
|
+
const UNAVAILABLE_STREAM_STATES = new Set(["throttled", "quota_exhausted", "agent_quota_exhausted"]);
|
|
1608
|
+
function isUnavailableStreamState(state) {
|
|
1609
|
+
return state !== null && UNAVAILABLE_STREAM_STATES.has(state);
|
|
1610
|
+
}
|
|
1607
1611
|
class ExotelAIAssistController extends EventEmitter {
|
|
1608
1612
|
constructor(params) {
|
|
1609
1613
|
super();
|
|
@@ -1637,7 +1641,7 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
1637
1641
|
var _a;
|
|
1638
1642
|
this._clearReconnectTimer();
|
|
1639
1643
|
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
1640
|
-
if (this.readyFired && this._streamState
|
|
1644
|
+
if (this.readyFired && !isUnavailableStreamState(this._streamState)) {
|
|
1641
1645
|
this.readyFired = false;
|
|
1642
1646
|
this.emit("onReady", false);
|
|
1643
1647
|
}
|
|
@@ -1719,8 +1723,8 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
1719
1723
|
break;
|
|
1720
1724
|
case "DISCONNECTED":
|
|
1721
1725
|
if (this.connectionEstablished) {
|
|
1722
|
-
const
|
|
1723
|
-
if (this.readyFired && !
|
|
1726
|
+
const wasUnavailable = isUnavailableStreamState(this._streamState);
|
|
1727
|
+
if (this.readyFired && !wasUnavailable) {
|
|
1724
1728
|
this.emit("onReady", false);
|
|
1725
1729
|
}
|
|
1726
1730
|
this.connectionEstablished = false;
|
|
@@ -1872,7 +1876,7 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
1872
1876
|
_handleStreamStatus(state) {
|
|
1873
1877
|
this._streamState = state;
|
|
1874
1878
|
this.emit("streamState", state);
|
|
1875
|
-
if (state === "connected" || state
|
|
1879
|
+
if (state === "connected" || isUnavailableStreamState(state)) {
|
|
1876
1880
|
this._fireReady();
|
|
1877
1881
|
} else if (this.readyFired) {
|
|
1878
1882
|
this.readyFired = false;
|
|
@@ -10,9 +10,11 @@ export interface UseExotelAIAssistReturn {
|
|
|
10
10
|
/**
|
|
11
11
|
* Server-reported stream state from `stream_status` messages.
|
|
12
12
|
* `null` until the first `stream_status` is received.
|
|
13
|
-
* - `"connected"`
|
|
14
|
-
* - `"throttled"`
|
|
15
|
-
* - `"
|
|
13
|
+
* - `"connected"` – stream is active and healthy
|
|
14
|
+
* - `"throttled"` – capacity full; bot cannot join this call
|
|
15
|
+
* - `"quota_exhausted"` – tenant plan quota is exhausted
|
|
16
|
+
* - `"agent_quota_exhausted"` – current agent's call quota is exhausted
|
|
17
|
+
* - `"disconnected"` – stream ended or dropped
|
|
16
18
|
*/
|
|
17
19
|
streamState: StreamState | null;
|
|
18
20
|
/** AI suggestions, oldest first, capped at 50. */
|
package/dist/index.js
CHANGED
|
@@ -22105,6 +22105,10 @@ class Utils {
|
|
|
22105
22105
|
}
|
|
22106
22106
|
Utils.MAX_EXTRA_QUERY_PARAMS = 3;
|
|
22107
22107
|
|
|
22108
|
+
const UNAVAILABLE_STREAM_STATES$1 = new Set(["throttled", "quota_exhausted", "agent_quota_exhausted"]);
|
|
22109
|
+
function isUnavailableStreamState(state) {
|
|
22110
|
+
return state !== null && UNAVAILABLE_STREAM_STATES$1.has(state);
|
|
22111
|
+
}
|
|
22108
22112
|
class ExotelAIAssistController extends EventEmitter {
|
|
22109
22113
|
constructor(params) {
|
|
22110
22114
|
super();
|
|
@@ -22138,7 +22142,7 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
22138
22142
|
var _a;
|
|
22139
22143
|
this._clearReconnectTimer();
|
|
22140
22144
|
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
22141
|
-
if (this.readyFired && this._streamState
|
|
22145
|
+
if (this.readyFired && !isUnavailableStreamState(this._streamState)) {
|
|
22142
22146
|
this.readyFired = false;
|
|
22143
22147
|
this.emit("onReady", false);
|
|
22144
22148
|
}
|
|
@@ -22220,8 +22224,8 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
22220
22224
|
break;
|
|
22221
22225
|
case "DISCONNECTED":
|
|
22222
22226
|
if (this.connectionEstablished) {
|
|
22223
|
-
const
|
|
22224
|
-
if (this.readyFired && !
|
|
22227
|
+
const wasUnavailable = isUnavailableStreamState(this._streamState);
|
|
22228
|
+
if (this.readyFired && !wasUnavailable) {
|
|
22225
22229
|
this.emit("onReady", false);
|
|
22226
22230
|
}
|
|
22227
22231
|
this.connectionEstablished = false;
|
|
@@ -22373,7 +22377,7 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
22373
22377
|
_handleStreamStatus(state) {
|
|
22374
22378
|
this._streamState = state;
|
|
22375
22379
|
this.emit("streamState", state);
|
|
22376
|
-
if (state === "connected" || state
|
|
22380
|
+
if (state === "connected" || isUnavailableStreamState(state)) {
|
|
22377
22381
|
this._fireReady();
|
|
22378
22382
|
} else if (this.readyFired) {
|
|
22379
22383
|
this.readyFired = false;
|
|
@@ -23236,6 +23240,7 @@ function TransferSummaryCard({
|
|
|
23236
23240
|
});
|
|
23237
23241
|
}
|
|
23238
23242
|
|
|
23243
|
+
const UNAVAILABLE_STREAM_STATES = new Set(["throttled", "quota_exhausted", "agent_quota_exhausted"]);
|
|
23239
23244
|
function ExotelAIAssist(_a) {
|
|
23240
23245
|
var {
|
|
23241
23246
|
className,
|
|
@@ -23260,6 +23265,22 @@ function ExotelAIAssist(_a) {
|
|
|
23260
23265
|
}, [isReady]);
|
|
23261
23266
|
const connected = streamState === "connected";
|
|
23262
23267
|
function renderProperEmptyContent() {
|
|
23268
|
+
if (streamState === "quota_exhausted") {
|
|
23269
|
+
return jsxRuntimeExports.jsx(EmptyState, {
|
|
23270
|
+
title: "AI Assist Unavailable",
|
|
23271
|
+
subtitle: "Contact your administrator to restore AI Assist.",
|
|
23272
|
+
showWarningBanner: true,
|
|
23273
|
+
warningBannerMessage: "Your organisation's AI Assist quota is exhausted. Ask your admin to renew the plan."
|
|
23274
|
+
});
|
|
23275
|
+
}
|
|
23276
|
+
if (streamState === "agent_quota_exhausted") {
|
|
23277
|
+
return jsxRuntimeExports.jsx(EmptyState, {
|
|
23278
|
+
title: "Your AI Assist limit is reached",
|
|
23279
|
+
subtitle: "Contact your administrator to raise your call allowance.",
|
|
23280
|
+
showWarningBanner: true,
|
|
23281
|
+
warningBannerMessage: "You've reached your personal AI Assist call limit."
|
|
23282
|
+
});
|
|
23283
|
+
}
|
|
23263
23284
|
if (streamState === "throttled") {
|
|
23264
23285
|
return jsxRuntimeExports.jsx(EmptyState, {
|
|
23265
23286
|
title: "AI Assist Unavailable",
|
|
@@ -23296,7 +23317,7 @@ function ExotelAIAssist(_a) {
|
|
|
23296
23317
|
botConfig: botConfig
|
|
23297
23318
|
}), transferSummary ? jsxRuntimeExports.jsx(TransferSummaryCard, {
|
|
23298
23319
|
data: transferSummary
|
|
23299
|
-
}) : null, streamState
|
|
23320
|
+
}) : null, !streamState || !UNAVAILABLE_STREAM_STATES.has(streamState) ? jsxRuntimeExports.jsxs(Root2$1, {
|
|
23300
23321
|
defaultValue: "suggestions",
|
|
23301
23322
|
className: "oa-tabs",
|
|
23302
23323
|
style: {
|
package/dist/react/index.js
CHANGED
|
@@ -7391,6 +7391,10 @@ class Utils {
|
|
|
7391
7391
|
}
|
|
7392
7392
|
Utils.MAX_EXTRA_QUERY_PARAMS = 3;
|
|
7393
7393
|
|
|
7394
|
+
const UNAVAILABLE_STREAM_STATES$1 = new Set(["throttled", "quota_exhausted", "agent_quota_exhausted"]);
|
|
7395
|
+
function isUnavailableStreamState(state) {
|
|
7396
|
+
return state !== null && UNAVAILABLE_STREAM_STATES$1.has(state);
|
|
7397
|
+
}
|
|
7394
7398
|
class ExotelAIAssistController extends EventEmitter {
|
|
7395
7399
|
constructor(params) {
|
|
7396
7400
|
super();
|
|
@@ -7424,7 +7428,7 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
7424
7428
|
var _a;
|
|
7425
7429
|
this._clearReconnectTimer();
|
|
7426
7430
|
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
7427
|
-
if (this.readyFired && this._streamState
|
|
7431
|
+
if (this.readyFired && !isUnavailableStreamState(this._streamState)) {
|
|
7428
7432
|
this.readyFired = false;
|
|
7429
7433
|
this.emit("onReady", false);
|
|
7430
7434
|
}
|
|
@@ -7506,8 +7510,8 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
7506
7510
|
break;
|
|
7507
7511
|
case "DISCONNECTED":
|
|
7508
7512
|
if (this.connectionEstablished) {
|
|
7509
|
-
const
|
|
7510
|
-
if (this.readyFired && !
|
|
7513
|
+
const wasUnavailable = isUnavailableStreamState(this._streamState);
|
|
7514
|
+
if (this.readyFired && !wasUnavailable) {
|
|
7511
7515
|
this.emit("onReady", false);
|
|
7512
7516
|
}
|
|
7513
7517
|
this.connectionEstablished = false;
|
|
@@ -7659,7 +7663,7 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
7659
7663
|
_handleStreamStatus(state) {
|
|
7660
7664
|
this._streamState = state;
|
|
7661
7665
|
this.emit("streamState", state);
|
|
7662
|
-
if (state === "connected" || state
|
|
7666
|
+
if (state === "connected" || isUnavailableStreamState(state)) {
|
|
7663
7667
|
this._fireReady();
|
|
7664
7668
|
} else if (this.readyFired) {
|
|
7665
7669
|
this.readyFired = false;
|
|
@@ -8522,6 +8526,7 @@ function TransferSummaryCard({
|
|
|
8522
8526
|
});
|
|
8523
8527
|
}
|
|
8524
8528
|
|
|
8529
|
+
const UNAVAILABLE_STREAM_STATES = new Set(["throttled", "quota_exhausted", "agent_quota_exhausted"]);
|
|
8525
8530
|
function ExotelAIAssist(_a) {
|
|
8526
8531
|
var {
|
|
8527
8532
|
className,
|
|
@@ -8546,6 +8551,22 @@ function ExotelAIAssist(_a) {
|
|
|
8546
8551
|
}, [isReady]);
|
|
8547
8552
|
const connected = streamState === "connected";
|
|
8548
8553
|
function renderProperEmptyContent() {
|
|
8554
|
+
if (streamState === "quota_exhausted") {
|
|
8555
|
+
return jsxRuntime.jsx(EmptyState, {
|
|
8556
|
+
title: "AI Assist Unavailable",
|
|
8557
|
+
subtitle: "Contact your administrator to restore AI Assist.",
|
|
8558
|
+
showWarningBanner: true,
|
|
8559
|
+
warningBannerMessage: "Your organisation's AI Assist quota is exhausted. Ask your admin to renew the plan."
|
|
8560
|
+
});
|
|
8561
|
+
}
|
|
8562
|
+
if (streamState === "agent_quota_exhausted") {
|
|
8563
|
+
return jsxRuntime.jsx(EmptyState, {
|
|
8564
|
+
title: "Your AI Assist limit is reached",
|
|
8565
|
+
subtitle: "Contact your administrator to raise your call allowance.",
|
|
8566
|
+
showWarningBanner: true,
|
|
8567
|
+
warningBannerMessage: "You've reached your personal AI Assist call limit."
|
|
8568
|
+
});
|
|
8569
|
+
}
|
|
8549
8570
|
if (streamState === "throttled") {
|
|
8550
8571
|
return jsxRuntime.jsx(EmptyState, {
|
|
8551
8572
|
title: "AI Assist Unavailable",
|
|
@@ -8582,7 +8603,7 @@ function ExotelAIAssist(_a) {
|
|
|
8582
8603
|
botConfig: botConfig
|
|
8583
8604
|
}), transferSummary ? jsxRuntime.jsx(TransferSummaryCard, {
|
|
8584
8605
|
data: transferSummary
|
|
8585
|
-
}) : null, streamState
|
|
8606
|
+
}) : null, !streamState || !UNAVAILABLE_STREAM_STATES.has(streamState) ? jsxRuntime.jsxs(Root2$1, {
|
|
8586
8607
|
defaultValue: "suggestions",
|
|
8587
8608
|
className: "oa-tabs",
|
|
8588
8609
|
style: {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -37,11 +37,13 @@ export interface Sentiment {
|
|
|
37
37
|
export type ConnectionStatus = "idle" | "connecting" | "connected" | "disconnected" | "error";
|
|
38
38
|
/**
|
|
39
39
|
* Server-side stream state, received via `stream_status` messages.
|
|
40
|
-
* - `connected`
|
|
41
|
-
* - `throttled`
|
|
42
|
-
* - `
|
|
40
|
+
* - `connected` – stream is active and healthy
|
|
41
|
+
* - `throttled` – capacity full; the bot cannot join this call
|
|
42
|
+
* - `quota_exhausted` – the tenant's plan quota is exhausted
|
|
43
|
+
* - `agent_quota_exhausted` – the current agent's call quota is exhausted
|
|
44
|
+
* - `pending` – stream is pending
|
|
43
45
|
*/
|
|
44
|
-
export type StreamState = "connected" | "throttled" | "pending" | "disconnected" | "connection_timeout";
|
|
46
|
+
export type StreamState = "connected" | "throttled" | "quota_exhausted" | "agent_quota_exhausted" | "pending" | "disconnected" | "connection_timeout";
|
|
45
47
|
/** Feature-flag config received from the AI Assist backend. */
|
|
46
48
|
export interface BotConfig {
|
|
47
49
|
sentiment: boolean;
|
package/package.json
CHANGED
|
@@ -19,6 +19,12 @@ export interface ExotelAIAssistProps extends ExotelAIAssistParams {
|
|
|
19
19
|
onReady?: (ready: boolean) => void;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const UNAVAILABLE_STREAM_STATES = new Set([
|
|
23
|
+
"throttled",
|
|
24
|
+
"quota_exhausted",
|
|
25
|
+
"agent_quota_exhausted",
|
|
26
|
+
]);
|
|
27
|
+
|
|
22
28
|
export function ExotelAIAssist({ className, onReady, ...params }: ExotelAIAssistProps): JSX.Element {
|
|
23
29
|
const {
|
|
24
30
|
streamState,
|
|
@@ -41,6 +47,26 @@ export function ExotelAIAssist({ className, onReady, ...params }: ExotelAIAssist
|
|
|
41
47
|
const connected = streamState === "connected";
|
|
42
48
|
|
|
43
49
|
function renderProperEmptyContent(): JSX.Element | null {
|
|
50
|
+
if (streamState === "quota_exhausted") {
|
|
51
|
+
return (
|
|
52
|
+
<EmptyState
|
|
53
|
+
title="AI Assist Unavailable"
|
|
54
|
+
subtitle="Contact your administrator to restore AI Assist."
|
|
55
|
+
showWarningBanner={true}
|
|
56
|
+
warningBannerMessage="Your organisation's AI Assist quota is exhausted. Ask your admin to renew the plan."
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (streamState === "agent_quota_exhausted") {
|
|
61
|
+
return (
|
|
62
|
+
<EmptyState
|
|
63
|
+
title="Your AI Assist limit is reached"
|
|
64
|
+
subtitle="Contact your administrator to raise your call allowance."
|
|
65
|
+
showWarningBanner={true}
|
|
66
|
+
warningBannerMessage="You've reached your personal AI Assist call limit."
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
44
70
|
if (streamState === "throttled") {
|
|
45
71
|
return (
|
|
46
72
|
<EmptyState
|
|
@@ -66,7 +92,7 @@ export function ExotelAIAssist({ className, onReady, ...params }: ExotelAIAssist
|
|
|
66
92
|
<Header sentiment={sentiment} botConfig={botConfig} />
|
|
67
93
|
{transferSummary ? <TransferSummaryCard data={transferSummary} /> : null}
|
|
68
94
|
|
|
69
|
-
{streamState
|
|
95
|
+
{!streamState || !UNAVAILABLE_STREAM_STATES.has(streamState) ? (
|
|
70
96
|
<Tabs.Root defaultValue="suggestions" className="oa-tabs" style={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}>
|
|
71
97
|
<Tabs.List className="oa-tabs-list">
|
|
72
98
|
<Tabs.Trigger value="suggestions" className="oa-tabs-trigger">
|
package/src/controller/index.ts
CHANGED
|
@@ -17,6 +17,16 @@ import {
|
|
|
17
17
|
import { ITransport, createTransport } from "../transport";
|
|
18
18
|
import { Utils } from "../utils";
|
|
19
19
|
|
|
20
|
+
const UNAVAILABLE_STREAM_STATES: ReadonlySet<StreamState> = new Set([
|
|
21
|
+
"throttled",
|
|
22
|
+
"quota_exhausted",
|
|
23
|
+
"agent_quota_exhausted",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function isUnavailableStreamState(state: StreamState | null): boolean {
|
|
27
|
+
return state !== null && UNAVAILABLE_STREAM_STATES.has(state);
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
export class ExotelAIAssistController extends EventEmitter<ControllerEvents> {
|
|
21
31
|
private params: ExotelAIAssistParams;
|
|
22
32
|
private transport: ITransport | null = null;
|
|
@@ -57,7 +67,7 @@ export class ExotelAIAssistController extends EventEmitter<ControllerEvents> {
|
|
|
57
67
|
disconnect(): void {
|
|
58
68
|
this._clearReconnectTimer();
|
|
59
69
|
this.transport?.disconnect();
|
|
60
|
-
if (this.readyFired && this._streamState
|
|
70
|
+
if (this.readyFired && !isUnavailableStreamState(this._streamState)) {
|
|
61
71
|
this.readyFired = false;
|
|
62
72
|
this.emit("onReady", false);
|
|
63
73
|
}
|
|
@@ -145,8 +155,8 @@ export class ExotelAIAssistController extends EventEmitter<ControllerEvents> {
|
|
|
145
155
|
|
|
146
156
|
case "DISCONNECTED":
|
|
147
157
|
if (this.connectionEstablished) {
|
|
148
|
-
const
|
|
149
|
-
if (this.readyFired && !
|
|
158
|
+
const wasUnavailable = isUnavailableStreamState(this._streamState);
|
|
159
|
+
if (this.readyFired && !wasUnavailable) {
|
|
150
160
|
this.emit("onReady", false);
|
|
151
161
|
}
|
|
152
162
|
this.connectionEstablished = false;
|
|
@@ -323,7 +333,7 @@ export class ExotelAIAssistController extends EventEmitter<ControllerEvents> {
|
|
|
323
333
|
private _handleStreamStatus(state: StreamState): void {
|
|
324
334
|
this._streamState = state;
|
|
325
335
|
this.emit("streamState", state);
|
|
326
|
-
if (state === "connected" || state
|
|
336
|
+
if (state === "connected" || isUnavailableStreamState(state)) {
|
|
327
337
|
this._fireReady();
|
|
328
338
|
} else if (this.readyFired) {
|
|
329
339
|
this.readyFired = false;
|
|
@@ -24,9 +24,11 @@ export interface UseExotelAIAssistReturn {
|
|
|
24
24
|
/**
|
|
25
25
|
* Server-reported stream state from `stream_status` messages.
|
|
26
26
|
* `null` until the first `stream_status` is received.
|
|
27
|
-
* - `"connected"`
|
|
28
|
-
* - `"throttled"`
|
|
29
|
-
* - `"
|
|
27
|
+
* - `"connected"` – stream is active and healthy
|
|
28
|
+
* - `"throttled"` – capacity full; bot cannot join this call
|
|
29
|
+
* - `"quota_exhausted"` – tenant plan quota is exhausted
|
|
30
|
+
* - `"agent_quota_exhausted"` – current agent's call quota is exhausted
|
|
31
|
+
* - `"disconnected"` – stream ended or dropped
|
|
30
32
|
*/
|
|
31
33
|
streamState: StreamState | null;
|
|
32
34
|
/** AI suggestions, oldest first, capped at 50. */
|
package/src/types/index.ts
CHANGED
|
@@ -42,11 +42,20 @@ export type ConnectionStatus = "idle" | "connecting" | "connected" | "disconnect
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Server-side stream state, received via `stream_status` messages.
|
|
45
|
-
* - `connected`
|
|
46
|
-
* - `throttled`
|
|
47
|
-
* - `
|
|
45
|
+
* - `connected` – stream is active and healthy
|
|
46
|
+
* - `throttled` – capacity full; the bot cannot join this call
|
|
47
|
+
* - `quota_exhausted` – the tenant's plan quota is exhausted
|
|
48
|
+
* - `agent_quota_exhausted` – the current agent's call quota is exhausted
|
|
49
|
+
* - `pending` – stream is pending
|
|
48
50
|
*/
|
|
49
|
-
export type StreamState =
|
|
51
|
+
export type StreamState =
|
|
52
|
+
| "connected"
|
|
53
|
+
| "throttled"
|
|
54
|
+
| "quota_exhausted"
|
|
55
|
+
| "agent_quota_exhausted"
|
|
56
|
+
| "pending"
|
|
57
|
+
| "disconnected"
|
|
58
|
+
| "connection_timeout";
|
|
50
59
|
|
|
51
60
|
// ---------------------------------------------------------------------------
|
|
52
61
|
// Internal-only backend response types
|