@exotel-npm-dev/exotel-ai-assist 1.2.4 → 1.2.6

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 CHANGED
@@ -123,11 +123,12 @@ function MyCustomUI({ call_sid }: { call_sid: string }) {
123
123
 
124
124
  if (streamState === "throttled") return <p>AI Assist is at capacity — available on your next call.</p>;
125
125
  if (streamState === "disconnected") return <p>AI Assist disconnected.</p>;
126
+ if (streamState === "connection_timeout") return <p>Connection timed out.</p>;
126
127
 
127
128
  return (
128
129
  <div>
129
130
  {suggestions.map((s) => (
130
- <p key={s.id}>{s.text}</p>
131
+ <p key={s.id}>{s.value}</p>
131
132
  ))}
132
133
  </div>
133
134
  );
@@ -153,7 +154,7 @@ function SuggestionsPanel() {
153
154
  return (
154
155
  <ul>
155
156
  {suggestions.map((s) => (
156
- <li key={s.id}>{s.text}</li>
157
+ <li key={s.id}>{s.value}</li>
157
158
  ))}
158
159
  </ul>
159
160
  );
@@ -188,7 +189,7 @@ ctrl.on("suggestion", (s) => console.log("Suggestion:", s));
188
189
  ctrl.on("transcript", (t) => console.log("Transcript:", t));
189
190
  ctrl.on("streamState", (state) => {
190
191
  console.log("Stream state:", state);
191
- // state: "connected" | "throttled" | "disconnected"
192
+ // state: "connected" | "throttled" | "pending" | "disconnected" | "connection_timeout"
192
193
  });
193
194
  ctrl.on("error", (err) => console.error("Error:", err));
194
195
 
@@ -238,14 +239,14 @@ Extends `EventEmitter`.
238
239
 
239
240
  #### Methods
240
241
 
241
- | Method | Description |
242
- | ------------------ | ---------------------------------------------- |
243
- | `connect()` | Open the WebSocket |
244
- | `disconnect()` | Close cleanly |
245
- | `setParams(patch)` | Merge params; reconnects if `call_sid` changes |
246
- | `destroy()` | Dispose controller and remove all listeners |
247
- | `getStatus()` | Returns current `ConnectionStatus` |
248
- | `getStreamState()` | Returns current `StreamState \| null` |
242
+ | Method | Description |
243
+ | --------------------------------------- | ---------------------------------------------- |
244
+ | `connect()` | Open the WebSocket |
245
+ | `disconnect()` | Close cleanly |
246
+ | `setParams(patch)` | Merge params; reconnects if `call_sid` changes |
247
+ | `destroy()` | Dispose controller and remove all listeners |
248
+ | `getStatus()` | Returns current `ConnectionStatus` |
249
+ | `getStreamState()` | Returns current `StreamState \| null` |
249
250
 
250
251
  #### Events
251
252
 
@@ -255,7 +256,7 @@ Extends `EventEmitter`.
255
256
  | `suggestion` | `Suggestion` | New AI suggestion (capped at last 50) |
256
257
  | `transcript` | `TranscriptLine[]` | Live transcript update |
257
258
  | `sentiment` | `Sentiment` | Sentiment label update |
258
- | `streamState` | `StreamState` | Server stream state change (`connected`, `throttled`, `disconnected`) |
259
+ | `streamState` | `StreamState` | Server stream state change (`connected`, `throttled`, `pending`, `disconnected`, `connection_timeout`) |
259
260
  | `onCallStart` | — | Connection opened |
260
261
  | `onCallEnd` | — | Connection closed |
261
262
  | `statusChange` | `ConnectionStatus` | WebSocket-level status transition (internal) |
@@ -266,36 +267,36 @@ Extends `EventEmitter`.
266
267
 
267
268
  ### `useExotelAIAssist(params)` — Hook return values
268
269
 
269
- | Field | Type | Description |
270
- | -------------- | ----------------------- | ---------------------------------------------------------------------------- |
271
- | `isReady` | `boolean` | `true` after connected + server ack. Multi-tab safe. `false` on disconnect |
272
- | `streamState` | `StreamState \| null` | Server stream state: `"connected"`, `"throttled"`, or `"disconnected"`. `null` until the first `stream_status` message |
273
- | `suggestions` | `Suggestion[]` | AI suggestions, oldest first, capped at 50 |
274
- | `transcripts` | `TranscriptLine[]` | Live transcript lines, ordered by start time |
275
- | `sentiment` | `Sentiment \| null` | Latest sentiment reading |
276
- | `lastError` | `Error \| null` | Most recent error |
277
- | `connect()` | `() => void` | Manually open the connection |
278
- | `disconnect()` | `() => void` | Manually close the connection |
279
- | `setParams()` | `(patch) => void` | Merge params; reconnects if connection params change |
270
+ | Field | Type | Description |
271
+ | -------------------------- | ----------------------- | ---------------------------------------------------------------------------- |
272
+ | `isReady` | `boolean` | `true` after connected + server ack. Multi-tab safe. `false` on disconnect |
273
+ | `streamState` | `StreamState \| null` | Server stream state: `"connected"`, `"throttled"`, `"pending"`, `"disconnected"`, or `"connection_timeout"`. `null` until the first `stream_status` message |
274
+ | `suggestions` | `Suggestion[]` | AI suggestions, oldest first, capped at 50 |
275
+ | `transcripts` | `TranscriptLine[]` | Live transcript lines, ordered by start time |
276
+ | `sentiment` | `Sentiment \| null` | Latest sentiment reading |
277
+ | `lastError` | `Error \| null` | Most recent error |
278
+ | `connect()` | `() => void` | Manually open the connection |
279
+ | `disconnect()` | `() => void` | Manually close the connection |
280
+ | `setParams()` | `(patch) => void` | Merge params; reconnects if connection params change |
280
281
 
281
282
  ---
282
283
 
283
284
  ### TypeScript Types
284
285
 
285
286
  ```ts
286
- type StreamState = "connected" | "throttled" | "disconnected";
287
+ type StreamState = "connected" | "throttled" | "pending" | "disconnected" | "connection_timeout";
287
288
 
288
289
  type ConnectionStatus = "idle" | "connecting" | "connected" | "disconnected" | "error";
289
290
 
290
291
  interface Suggestion {
291
292
  id: string;
292
- text: string;
293
+ value: string;
293
294
  timestamp: number;
294
295
  }
295
296
 
296
297
  interface TranscriptLine {
297
298
  id: string;
298
- text: string;
299
+ value: string;
299
300
  startTime: number;
300
301
  endTime: number;
301
302
  isFinal: boolean;
@@ -330,15 +331,17 @@ wss://<wssBaseUrl>?[customParam1=value1&customParam2=value2&...]
330
331
  "type": "stream_status",
331
332
  "config": { /* bot config, populated on connected, null otherwise */ },
332
333
  "events": [ /* accumulated events since last message, usually [] */ ],
333
- "stream_state": "connected" | "throttled" | "disconnected"
334
+ "stream_state": "connected" | "throttled" | "pending" | "disconnected" | "connection_timeout"
334
335
  }
335
336
  ```
336
337
 
337
- | `stream_state` | Meaning |
338
- | --------------- | --------------------------------------------------------- |
339
- | `connected` | Stream is active — suggestions, transcript, and sentiment flow normally |
340
- | `throttled` | Capacity full — AI Assist cannot join this call; available on the next call |
341
- | `disconnected` | Stream ended or dropped |
338
+ | `stream_state` | Meaning |
339
+ | -------------------- | --------------------------------------------------------- |
340
+ | `connected` | Stream is active — suggestions, transcript, and sentiment flow normally |
341
+ | `throttled` | Capacity full — AI Assist cannot join this call; available on the next call |
342
+ | `pending` | Stream is pending — waiting for a definitive state |
343
+ | `disconnected` | Stream ended or dropped |
344
+ | `connection_timeout` | Connection timeout occurred |
342
345
 
343
346
  ### Reconnection
344
347
 
@@ -1,8 +1,7 @@
1
1
  import { Suggestion, BotConfig } from "../../types";
2
2
  import "../../styles/index.css";
3
- export declare function SuggestionsTab({ suggestions, connected, botConfig, sendSuggestionFeedback, }: {
3
+ export declare function SuggestionsTab({ suggestions, connected, botConfig, }: {
4
4
  suggestions: Suggestion[];
5
5
  connected: boolean;
6
6
  botConfig: BotConfig | null;
7
- sendSuggestionFeedback: (sequence: number, feedbackType: "good" | "bad" | null, badFeedbackReason?: string | null) => boolean;
8
7
  }): JSX.Element;
@@ -19,7 +19,6 @@ export declare class ExotelAIAssistController extends EventEmitter<ControllerEve
19
19
  destroy(): void;
20
20
  getStatus(): ConnectionStatus;
21
21
  getStreamState(): StreamState | null;
22
- sendSuggestionFeedback(sequence: number, feedbackType: "good" | "bad" | null, badFeedbackReason?: string | null): boolean;
23
22
  private _ensureTransport;
24
23
  private _handleTransportMessage;
25
24
  private _scheduleReconnect;
@@ -1538,7 +1538,7 @@ class Utils {
1538
1538
  * @returns The base WebSocket URL.
1539
1539
  */
1540
1540
  static getWssBaseUrl(accountId) {
1541
- return `wss://aiassist-uat.in.exotel.com/ai-assist/ws/v1/accounts/${accountId}/ai-assistants/conversation-events`;
1541
+ return `wss://ai-assist.in.exotel.com/ai-assist/ws/v1/accounts/${accountId}/ai-assistants/conversation-events`;
1542
1542
  }
1543
1543
  /**
1544
1544
  * Builds a WebSocket URL from the given parameters.
@@ -1667,25 +1667,6 @@ class ExotelAIAssistController extends EventEmitter {
1667
1667
  getStreamState() {
1668
1668
  return this._streamState;
1669
1669
  }
1670
- sendSuggestionFeedback(sequence, feedbackType, badFeedbackReason = null) {
1671
- if (!this.transport || this.status !== "connected") {
1672
- console.warn("[ExotelAIAssist] Cannot send feedback: not connected");
1673
- return false;
1674
- }
1675
- const message = {
1676
- type: "suggestion_feedback",
1677
- sequence,
1678
- feedback_type: feedbackType,
1679
- bad_feedback_reason: badFeedbackReason
1680
- };
1681
- try {
1682
- this.transport.send(JSON.stringify(message));
1683
- return true;
1684
- } catch (error) {
1685
- console.error("[ExotelAIAssist] Failed to send feedback:", error);
1686
- return false;
1687
- }
1688
- }
1689
1670
  _ensureTransport() {
1690
1671
  if (this.transport) return;
1691
1672
  this.transport = createTransport(Utils.hash(this.params));
@@ -1793,14 +1774,14 @@ class ExotelAIAssistController extends EventEmitter {
1793
1774
  for (const event of events) {
1794
1775
  if (!event) continue;
1795
1776
  if (event.event_type === "transcript" && event.value) {
1796
- const transcripts = Array.isArray(event === null || event === void 0 ? void 0 : event.value) ? event.value : (_c = event === null || event === void 0 ? void 0 : event.transcript) !== null && _c !== void 0 ? _c : [];
1777
+ const transcripts = Array.isArray(event.value) ? event.value : (_c = event.transcript) !== null && _c !== void 0 ? _c : [];
1797
1778
  const lines = transcripts.map(msg => {
1798
- var _a, _b, _c, _d, _e;
1779
+ var _a, _b, _c;
1799
1780
  const first = (_a = msg.transcript_segments) === null || _a === void 0 ? void 0 : _a[0];
1800
1781
  const last = (_b = msg.transcript_segments) === null || _b === void 0 ? void 0 : _b[msg.transcript_segments.length - 1];
1801
1782
  return {
1802
1783
  id: String(msg.sequence),
1803
- value: (_e = (_d = (_c = msg.transcript_segments) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.text) !== null && _e !== void 0 ? _e : "",
1784
+ value: (_c = first === null || first === void 0 ? void 0 : first.text) !== null && _c !== void 0 ? _c : "",
1804
1785
  startTime: (first === null || first === void 0 ? void 0 : first.start_timestamp) ? Date.parse(first.start_timestamp) : now,
1805
1786
  endTime: (last === null || last === void 0 ? void 0 : last.end_timestamp) ? Date.parse(last.end_timestamp) : now,
1806
1787
  isFinal: msg.transcript_segments.every(s => s.is_final)
@@ -1808,27 +1789,17 @@ class ExotelAIAssistController extends EventEmitter {
1808
1789
  });
1809
1790
  this.emit("transcript", lines);
1810
1791
  }
1811
- if (event.event_type === "suggestion") {
1812
- const val = event.value;
1813
- const text = typeof val === "object" && val !== null && "text" in val ? val.text : typeof val === "string" ? val : event.text;
1814
- const sequence = typeof val === "object" && val !== null && "sequence" in val ? val.sequence : 0;
1815
- if (text) {
1816
- const suggestion = {
1817
- id: Utils.getUniqueId(),
1818
- text,
1819
- value: text,
1820
- timestamp: now,
1821
- sequence,
1822
- feedbackType: null,
1823
- badFeedbackReason: null
1824
- };
1825
- this.emit("suggestion", suggestion);
1826
- }
1792
+ if (event.event_type === "suggestion" && event.value) {
1793
+ const suggestion = {
1794
+ id: Utils.getUniqueId(),
1795
+ value: event.value.text,
1796
+ timestamp: now
1797
+ };
1798
+ this.emit("suggestion", suggestion);
1827
1799
  }
1828
1800
  if (event.event_type === "sentiment" && event.value) {
1829
- const sentimentValue = typeof event.value === "string" ? event.value : event.value.text;
1830
1801
  const sentiment = {
1831
- label: sentimentValue.toLowerCase(),
1802
+ label: event.value.toLowerCase(),
1832
1803
  timestamp: now
1833
1804
  };
1834
1805
  this.emit("sentiment", sentiment);
@@ -25,7 +25,6 @@ export interface UseExotelAIAssistReturn {
25
25
  connect: () => void;
26
26
  disconnect: () => void;
27
27
  setParams: (patch: Partial<ExotelAIAssistParams>) => void;
28
- sendSuggestionFeedback: (sequence: number, feedbackType: "good" | "bad" | null, badFeedbackReason?: string | null) => boolean;
29
28
  }
30
29
  /** Internal extended return — adds botConfig for the UI component only. */
31
30
  export interface UseExotelAIAssistInternalReturn extends UseExotelAIAssistReturn {
package/dist/index.js CHANGED
@@ -19294,30 +19294,6 @@ const Smile = createLucideIcon$1("Smile", [["circle", {
19294
19294
  key: "1p4y9e"
19295
19295
  }]]);
19296
19296
 
19297
- /**
19298
- * lucide-react v0.0.1 - ISC
19299
- */
19300
-
19301
- const ThumbsDown = createLucideIcon$1("ThumbsDown", [["path", {
19302
- d: "M17 14V2",
19303
- key: "8ymqnk"
19304
- }], ["path", {
19305
- d: "M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22h0a3.13 3.13 0 0 1-3-3.88Z",
19306
- key: "s6e0r"
19307
- }]]);
19308
-
19309
- /**
19310
- * lucide-react v0.0.1 - ISC
19311
- */
19312
-
19313
- const ThumbsUp = createLucideIcon$1("ThumbsUp", [["path", {
19314
- d: "M7 10v12",
19315
- key: "1qc93n"
19316
- }], ["path", {
19317
- d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2h0a3.13 3.13 0 0 1 3 3.88Z",
19318
- key: "y3tblf"
19319
- }]]);
19320
-
19321
19297
  const MAX_VISIBLE_TOASTS = 5;
19322
19298
  const AUTO_DISMISS_MS = 3000;
19323
19299
  const ToastContext = reactExports.createContext(null);
@@ -20980,7 +20956,7 @@ class Utils {
20980
20956
  * @returns The base WebSocket URL.
20981
20957
  */
20982
20958
  static getWssBaseUrl(accountId) {
20983
- return `wss://aiassist-uat.in.exotel.com/ai-assist/ws/v1/accounts/${accountId}/ai-assistants/conversation-events`;
20959
+ return `wss://ai-assist.in.exotel.com/ai-assist/ws/v1/accounts/${accountId}/ai-assistants/conversation-events`;
20984
20960
  }
20985
20961
  /**
20986
20962
  * Builds a WebSocket URL from the given parameters.
@@ -21109,25 +21085,6 @@ class ExotelAIAssistController extends EventEmitter {
21109
21085
  getStreamState() {
21110
21086
  return this._streamState;
21111
21087
  }
21112
- sendSuggestionFeedback(sequence, feedbackType, badFeedbackReason = null) {
21113
- if (!this.transport || this.status !== "connected") {
21114
- console.warn("[ExotelAIAssist] Cannot send feedback: not connected");
21115
- return false;
21116
- }
21117
- const message = {
21118
- type: "suggestion_feedback",
21119
- sequence,
21120
- feedback_type: feedbackType,
21121
- bad_feedback_reason: badFeedbackReason
21122
- };
21123
- try {
21124
- this.transport.send(JSON.stringify(message));
21125
- return true;
21126
- } catch (error) {
21127
- console.error("[ExotelAIAssist] Failed to send feedback:", error);
21128
- return false;
21129
- }
21130
- }
21131
21088
  _ensureTransport() {
21132
21089
  if (this.transport) return;
21133
21090
  this.transport = createTransport(Utils.hash(this.params));
@@ -21235,14 +21192,14 @@ class ExotelAIAssistController extends EventEmitter {
21235
21192
  for (const event of events) {
21236
21193
  if (!event) continue;
21237
21194
  if (event.event_type === "transcript" && event.value) {
21238
- const transcripts = Array.isArray(event === null || event === void 0 ? void 0 : event.value) ? event.value : (_c = event === null || event === void 0 ? void 0 : event.transcript) !== null && _c !== void 0 ? _c : [];
21195
+ const transcripts = Array.isArray(event.value) ? event.value : (_c = event.transcript) !== null && _c !== void 0 ? _c : [];
21239
21196
  const lines = transcripts.map(msg => {
21240
- var _a, _b, _c, _d, _e;
21197
+ var _a, _b, _c;
21241
21198
  const first = (_a = msg.transcript_segments) === null || _a === void 0 ? void 0 : _a[0];
21242
21199
  const last = (_b = msg.transcript_segments) === null || _b === void 0 ? void 0 : _b[msg.transcript_segments.length - 1];
21243
21200
  return {
21244
21201
  id: String(msg.sequence),
21245
- value: (_e = (_d = (_c = msg.transcript_segments) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.text) !== null && _e !== void 0 ? _e : "",
21202
+ value: (_c = first === null || first === void 0 ? void 0 : first.text) !== null && _c !== void 0 ? _c : "",
21246
21203
  startTime: (first === null || first === void 0 ? void 0 : first.start_timestamp) ? Date.parse(first.start_timestamp) : now,
21247
21204
  endTime: (last === null || last === void 0 ? void 0 : last.end_timestamp) ? Date.parse(last.end_timestamp) : now,
21248
21205
  isFinal: msg.transcript_segments.every(s => s.is_final)
@@ -21250,27 +21207,17 @@ class ExotelAIAssistController extends EventEmitter {
21250
21207
  });
21251
21208
  this.emit("transcript", lines);
21252
21209
  }
21253
- if (event.event_type === "suggestion") {
21254
- const val = event.value;
21255
- const text = typeof val === "object" && val !== null && "text" in val ? val.text : typeof val === "string" ? val : event.text;
21256
- const sequence = typeof val === "object" && val !== null && "sequence" in val ? val.sequence : 0;
21257
- if (text) {
21258
- const suggestion = {
21259
- id: Utils.getUniqueId(),
21260
- text,
21261
- value: text,
21262
- timestamp: now,
21263
- sequence,
21264
- feedbackType: null,
21265
- badFeedbackReason: null
21266
- };
21267
- this.emit("suggestion", suggestion);
21268
- }
21210
+ if (event.event_type === "suggestion" && event.value) {
21211
+ const suggestion = {
21212
+ id: Utils.getUniqueId(),
21213
+ value: event.value.text,
21214
+ timestamp: now
21215
+ };
21216
+ this.emit("suggestion", suggestion);
21269
21217
  }
21270
21218
  if (event.event_type === "sentiment" && event.value) {
21271
- const sentimentValue = typeof event.value === "string" ? event.value : event.value.text;
21272
21219
  const sentiment = {
21273
- label: sentimentValue.toLowerCase(),
21220
+ label: event.value.toLowerCase(),
21274
21221
  timestamp: now
21275
21222
  };
21276
21223
  this.emit("sentiment", sentiment);
@@ -21346,20 +21293,9 @@ function useExotelAIAssist(params) {
21346
21293
  var _a;
21347
21294
  return (_a = controllerRef.current) === null || _a === void 0 ? void 0 : _a.setParams(patch);
21348
21295
  }, []);
21349
- const sendSuggestionFeedback = reactExports.useCallback((sequence, feedbackType, badFeedbackReason = null) => {
21350
- var _a, _b;
21351
- const sent = (_b = (_a = controllerRef.current) === null || _a === void 0 ? void 0 : _a.sendSuggestionFeedback(sequence, feedbackType, badFeedbackReason)) !== null && _b !== void 0 ? _b : false;
21352
- if (sent) {
21353
- setSuggestions(prev => prev.map(s => s.sequence === sequence ? Object.assign(Object.assign({}, s), {
21354
- feedbackType,
21355
- badFeedbackReason
21356
- }) : s));
21357
- }
21358
- return sent;
21359
- }, []);
21360
21296
  return {
21361
- streamState,
21362
21297
  isReady,
21298
+ streamState,
21363
21299
  suggestions,
21364
21300
  transcripts,
21365
21301
  sentiment,
@@ -21367,8 +21303,7 @@ function useExotelAIAssist(params) {
21367
21303
  lastError,
21368
21304
  connect,
21369
21305
  disconnect,
21370
- setParams,
21371
- sendSuggestionFeedback
21306
+ setParams
21372
21307
  };
21373
21308
  }
21374
21309
 
@@ -21397,7 +21332,7 @@ function styleInject(css, ref) {
21397
21332
  }
21398
21333
  }
21399
21334
 
21400
- var css_248z = ".oa-panel{background:#fff;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}.oa-panel,.oa-theme-root{display:flex;flex-direction:column;height:100%;min-height:0;width:100%}.oa-theme-root{position:relative}.oa-header-title{color:#111827;font-size:16px;font-weight:600;line-height:20px;margin:0}.oa-tabs{width:100%}.oa-tabs-content{flex:1;min-height:0;overflow:hidden}.oa-tabs-content[data-state=inactive]{display:none!important}.oa-tabs-content[data-state=active]{display:flex;flex-direction:column}.oa-tabs-list{border-bottom:1px solid #e5e7eb;display:flex;gap:20px;padding:0}.oa-tabs-trigger{align-items:center;background:transparent;border:none;border-radius:0;color:#6b7280;cursor:pointer;display:inline-flex;font-size:15px;font-weight:500;gap:6px;outline:none;padding:10px 0;position:relative}.oa-tabs-trigger:hover{color:#374151}.oa-tabs-trigger[data-state=active]{color:#2563eb}.oa-tabs-trigger[data-state=active]:after{background:#2563eb;border-radius:2px;bottom:-1px;content:\"\";height:2px;left:0;position:absolute;right:0}.oa-suggestion-card{border:1px solid rgba(17,24,39,.04);border-radius:12px;padding:12px;text-align:left}.oa-suggestion-card--recent-wrapper{animation:oa-suggestion-enter .4s ease-out,oa-border-shimmer 3s ease-in-out .4s infinite;background:linear-gradient(90deg,#7c3aed,#e11d48,#7c3aed);background-size:200% 100%;border-radius:12px;padding:2px;text-align:left}.oa-suggestion-card--recent-wrapper .oa-suggestion-card--recent{border:none}.oa-suggestion-card--recent{animation:none;background:linear-gradient(90deg,#f5f3ff 0,#fef3f2 50%,#fff7ed);border-radius:9px}.oa-suggestion-card--older{background:#f5f5f5;border:1px solid #e5e7eb}.oa-suggestion-card--older .oa-suggestion-text{color:#252525}@keyframes oa-suggestion-enter{0%{opacity:.3;transform:scale(.97)}to{opacity:1;transform:scale(1)}}@keyframes oa-border-shimmer{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.oa-suggestion-text{color:#111827;font-size:13px;line-height:18px;white-space:pre-wrap}.oa-copy-icon,.oa-feedback-icon{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;color:#6b7280;cursor:pointer;display:inline-flex;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px}.oa-copy-icon:hover,.oa-feedback-icon:hover{background:#f9fafb;color:#374151}.oa-feedback-icon--positive{background:#dcfce7!important;border-color:#86efac!important;color:#16a34a!important}.oa-feedback-icon--negative{background:#fee2e2!important;border-color:#fca5a5!important;color:#dc2626!important}.oa-feedback-icon:disabled{cursor:default;opacity:.5}.oa-feedback-icon--negative:disabled,.oa-feedback-icon--positive:disabled{opacity:1}.oa-feedback-reason-btn{align-items:center;background:#fef2f2;border:1px solid #fecaca;border-radius:16px;color:#991b1b;cursor:pointer;display:inline-flex;font-family:inherit;font-size:12px;font-weight:500;line-height:18px;padding:4px 12px;transition:background .15s,border-color .15s}.oa-feedback-reason-btn:hover:not(:disabled){background:#fee2e2;border-color:#f87171}.oa-feedback-reason-btn--selected{background:#dc2626!important;border-color:#dc2626!important;color:#fff!important}.oa-feedback-reason-btn:disabled{cursor:default;opacity:.45}@keyframes oa-toast-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.oa-stream-overlay{display:flex;flex:1;flex-direction:column;min-height:0}.oa-stream-banner{align-items:flex-start;background:#fef3c7;border:1px solid #fde68a;border-radius:8px;color:#92400e;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-stream-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-stream-banner-text{flex:1}.oa-stream-empty{flex:1;gap:16px;justify-content:center;min-height:0}.oa-stream-empty,.oa-stream-empty-text{align-items:center;display:flex;flex-direction:column}.oa-stream-empty-text{gap:6px;text-align:center}.oa-stream-empty-title{color:#111827;font-size:16px;font-weight:700;line-height:20px}.oa-stream-empty-subtitle{color:#6b7280;font-size:14px;line-height:20px}.oa-throttle-banner{align-items:flex-start;background:#fcefee;border:1px solid #e8cecb;border-radius:8px;color:#833d09;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-throttle-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-throttle-banner-text{flex:1}.oa-starting-loader{align-items:center;display:flex;flex:1;flex-direction:column;gap:16px;height:100%;justify-content:center;min-height:0}.oa-starting-spinner{animation:oa-spinner-rotate 1.4s linear infinite;height:48px;width:48px}.oa-starting-spinner-track{stroke:#e5e7eb}.oa-starting-spinner-arc{stroke:#2563eb;stroke-dasharray:80,126;stroke-dashoffset:0;animation:oa-spinner-dash 1.4s ease-in-out infinite}@keyframes oa-spinner-rotate{to{transform:rotate(1turn)}}@keyframes oa-spinner-dash{0%{stroke-dasharray:1,126;stroke-dashoffset:0}50%{stroke-dasharray:80,126;stroke-dashoffset:-35}to{stroke-dasharray:80,126;stroke-dashoffset:-124}}.oa-starting-loader-text{color:#111827;font-size:16px;font-weight:600;line-height:20px}.oa-muted{color:#6b7280}";
21335
+ var css_248z = ".oa-panel{background:#fff;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}.oa-panel,.oa-theme-root{display:flex;flex-direction:column;height:100%;min-height:0;width:100%}.oa-theme-root{position:relative}.oa-header-title{color:#111827;font-size:16px;font-weight:600;line-height:20px;margin:0}.oa-tabs{width:100%}.oa-tabs-content{flex:1;min-height:0;overflow:hidden}.oa-tabs-content[data-state=inactive]{display:none!important}.oa-tabs-content[data-state=active]{display:flex;flex-direction:column}.oa-tabs-list{border-bottom:1px solid #e5e7eb;display:flex;gap:20px;padding:0}.oa-tabs-trigger{align-items:center;background:transparent;border:none;border-radius:0;color:#6b7280;cursor:pointer;display:inline-flex;font-size:15px;font-weight:500;gap:6px;outline:none;padding:10px 0;position:relative}.oa-tabs-trigger:hover{color:#374151}.oa-tabs-trigger[data-state=active]{color:#2563eb}.oa-tabs-trigger[data-state=active]:after{background:#2563eb;border-radius:2px;bottom:-1px;content:\"\";height:2px;left:0;position:absolute;right:0}.oa-suggestion-card{border:1px solid rgba(17,24,39,.04);border-radius:12px;padding:12px;text-align:left}.oa-suggestion-card--recent-wrapper{animation:oa-suggestion-enter .4s ease-out,oa-border-shimmer 3s ease-in-out .4s infinite;background:linear-gradient(90deg,#7c3aed,#e11d48,#7c3aed);background-size:200% 100%;border-radius:12px;padding:2px;text-align:left}.oa-suggestion-card--recent-wrapper .oa-suggestion-card--recent{border:none}.oa-suggestion-card--recent{animation:none;background:linear-gradient(90deg,#f5f3ff 0,#fef3f2 50%,#fff7ed);border-radius:9px}.oa-suggestion-card--older{background:#f5f5f5;border:1px solid #e5e7eb}.oa-suggestion-card--older .oa-suggestion-text{color:#252525}@keyframes oa-suggestion-enter{0%{opacity:.3;transform:scale(.97)}to{opacity:1;transform:scale(1)}}@keyframes oa-border-shimmer{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.oa-suggestion-text{color:#111827;font-size:13px;line-height:18px;white-space:pre-wrap}.oa-copy-icon{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;color:#6b7280;cursor:pointer;display:inline-flex;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px}.oa-copy-icon:hover{background:#f9fafb;color:#374151}@keyframes oa-toast-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.oa-stream-overlay{display:flex;flex:1;flex-direction:column;min-height:0}.oa-stream-banner{align-items:flex-start;background:#fef3c7;border:1px solid #fde68a;border-radius:8px;color:#92400e;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-stream-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-stream-banner-text{flex:1}.oa-stream-empty{flex:1;gap:16px;justify-content:center;min-height:0}.oa-stream-empty,.oa-stream-empty-text{align-items:center;display:flex;flex-direction:column}.oa-stream-empty-text{gap:6px;text-align:center}.oa-stream-empty-title{color:#111827;font-size:16px;font-weight:700;line-height:20px}.oa-stream-empty-subtitle{color:#6b7280;font-size:14px;line-height:20px}.oa-throttle-banner{align-items:flex-start;background:#fcefee;border:1px solid #e8cecb;border-radius:8px;color:#833d09;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-throttle-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-throttle-banner-text{flex:1}.oa-starting-loader{align-items:center;display:flex;flex:1;flex-direction:column;gap:16px;height:100%;justify-content:center;min-height:0}.oa-starting-spinner{animation:oa-spinner-rotate 1.4s linear infinite;height:48px;width:48px}.oa-starting-spinner-track{stroke:#e5e7eb}.oa-starting-spinner-arc{stroke:#2563eb;stroke-dasharray:80,126;stroke-dashoffset:0;animation:oa-spinner-dash 1.4s ease-in-out infinite}@keyframes oa-spinner-rotate{to{transform:rotate(1turn)}}@keyframes oa-spinner-dash{0%{stroke-dasharray:1,126;stroke-dashoffset:0}50%{stroke-dasharray:80,126;stroke-dashoffset:-35}to{stroke-dasharray:80,126;stroke-dashoffset:-124}}.oa-starting-loader-text{color:#111827;font-size:16px;font-weight:600;line-height:20px}.oa-muted{color:#6b7280}";
21401
21336
  styleInject(css_248z);
21402
21337
 
21403
21338
  function Sparkle() {
@@ -21745,17 +21680,9 @@ const EmptyState = ({
21745
21680
  function SuggestionsTab({
21746
21681
  suggestions,
21747
21682
  connected,
21748
- botConfig,
21749
- sendSuggestionFeedback
21683
+ botConfig
21750
21684
  }) {
21751
- var _a, _b, _c;
21752
21685
  const toast = useToast();
21753
- const [expandedSequence, setExpandedSequence] = reactExports.useState(null);
21754
- const feedbackEnabled = ((_a = botConfig === null || botConfig === void 0 ? void 0 : botConfig.suggestion) === null || _a === void 0 ? void 0 : _a.feedback_enabled) === true;
21755
- const badFeedbackOptions = reactExports.useMemo(() => {
21756
- var _a, _b;
21757
- return (_b = (_a = botConfig === null || botConfig === void 0 ? void 0 : botConfig.suggestion) === null || _a === void 0 ? void 0 : _a.bad_feedback_options) !== null && _b !== void 0 ? _b : [];
21758
- }, [(_b = botConfig === null || botConfig === void 0 ? void 0 : botConfig.suggestion) === null || _b === void 0 ? void 0 : _b.bad_feedback_options]);
21759
21686
  const handleCopy = async text => {
21760
21687
  try {
21761
21688
  await navigator.clipboard.writeText(text);
@@ -21764,36 +21691,13 @@ function SuggestionsTab({
21764
21691
  toast.error("Failed to copy");
21765
21692
  }
21766
21693
  };
21767
- const handleThumbsUp = reactExports.useCallback((sequence, currentFeedback) => {
21768
- if (!feedbackEnabled) return;
21769
- const newType = currentFeedback === "good" ? null : "good";
21770
- sendSuggestionFeedback(sequence, newType, null);
21771
- setExpandedSequence(null);
21772
- }, [sendSuggestionFeedback, feedbackEnabled]);
21773
- const handleThumbsDown = reactExports.useCallback((sequence, currentFeedback) => {
21774
- if (!feedbackEnabled) return;
21775
- if (currentFeedback === "bad") {
21776
- sendSuggestionFeedback(sequence, null, null);
21777
- setExpandedSequence(null);
21778
- return;
21779
- }
21780
- sendSuggestionFeedback(sequence, "bad", null);
21781
- if (badFeedbackOptions.length > 0) {
21782
- setExpandedSequence(sequence);
21783
- }
21784
- }, [sendSuggestionFeedback, badFeedbackOptions, feedbackEnabled]);
21785
- const handleBadFeedbackReason = reactExports.useCallback((sequence, reason) => {
21786
- if (!feedbackEnabled) return;
21787
- sendSuggestionFeedback(sequence, "bad", reason);
21788
- setExpandedSequence(null);
21789
- }, [sendSuggestionFeedback, feedbackEnabled]);
21790
21694
  if (!connected) {
21791
21695
  return jsxRuntimeExports.jsx(EmptyState, {
21792
21696
  title: "Assistant is inactive",
21793
21697
  subtitle: "Start a call to receive real-time suggestions."
21794
21698
  });
21795
21699
  }
21796
- if (((_c = botConfig === null || botConfig === void 0 ? void 0 : botConfig.suggestion) === null || _c === void 0 ? void 0 : _c.enabled) === false) {
21700
+ if ((botConfig === null || botConfig === void 0 ? void 0 : botConfig.suggestions) === false) {
21797
21701
  return jsxRuntimeExports.jsx(EmptyState, {
21798
21702
  title: "Suggestions are disabled",
21799
21703
  subtitle: "Suggestions are disabled in the bot configuration. Please contact the administrator to enable it."
@@ -21827,37 +21731,21 @@ function SuggestionsTab({
21827
21731
  children: displayed.map((suggestion, index) => {
21828
21732
  const isRecent = index === 0;
21829
21733
  const cardClass = isRecent ? "oa-suggestion-card oa-suggestion-card--recent" : "oa-suggestion-card oa-suggestion-card--older";
21830
- const showReasons = suggestion.feedbackType === "bad" || expandedSequence === suggestion.sequence;
21831
21734
  return jsxRuntimeExports.jsxs("div", {
21832
- children: [jsxRuntimeExports.jsxs("div", {
21735
+ style: {
21736
+ display: "flex",
21737
+ flexDirection: "row",
21738
+ alignItems: "flex-start",
21739
+ gap: "8px"
21740
+ },
21741
+ children: [isRecent ? jsxRuntimeExports.jsx("div", {
21742
+ className: "oa-suggestion-card--recent-wrapper",
21833
21743
  style: {
21834
- display: "flex",
21835
- flexDirection: "row",
21836
- alignItems: "flex-start",
21837
- gap: "8px"
21744
+ flex: "1 1 auto",
21745
+ maxWidth: "80%"
21838
21746
  },
21839
- children: [isRecent ? jsxRuntimeExports.jsx("div", {
21840
- className: "oa-suggestion-card--recent-wrapper",
21841
- style: {
21842
- flex: "1 1 auto",
21843
- maxWidth: "72%"
21844
- },
21845
- children: jsxRuntimeExports.jsx("div", {
21846
- className: cardClass,
21847
- children: jsxRuntimeExports.jsx("span", {
21848
- className: "oa-suggestion-text",
21849
- style: {
21850
- fontSize: "15px"
21851
- },
21852
- children: suggestion.text
21853
- })
21854
- })
21855
- }) : jsxRuntimeExports.jsx("div", {
21747
+ children: jsxRuntimeExports.jsx("div", {
21856
21748
  className: cardClass,
21857
- style: {
21858
- flex: "1 1 auto",
21859
- maxWidth: "72%"
21860
- },
21861
21749
  children: jsxRuntimeExports.jsx("span", {
21862
21750
  className: "oa-suggestion-text",
21863
21751
  style: {
@@ -21865,57 +21753,31 @@ function SuggestionsTab({
21865
21753
  },
21866
21754
  children: suggestion.value
21867
21755
  })
21868
- }), jsxRuntimeExports.jsxs("div", {
21756
+ })
21757
+ }) : jsxRuntimeExports.jsx("div", {
21758
+ className: cardClass,
21759
+ style: {
21760
+ flex: "1 1 auto",
21761
+ maxWidth: "80%"
21762
+ },
21763
+ children: jsxRuntimeExports.jsx("span", {
21764
+ className: "oa-suggestion-text",
21869
21765
  style: {
21870
- display: "flex",
21871
- flexDirection: "column",
21872
- gap: "4px",
21873
- flexShrink: 0,
21874
- marginTop: "10px"
21766
+ fontSize: "15px"
21875
21767
  },
21876
- children: [jsxRuntimeExports.jsx("button", {
21877
- className: "oa-copy-icon",
21878
- "aria-label": "Copy suggestion",
21879
- onClick: () => handleCopy(suggestion.text),
21880
- children: jsxRuntimeExports.jsx(Copy, {
21881
- size: 14
21882
- })
21883
- }), feedbackEnabled && jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
21884
- children: [jsxRuntimeExports.jsx("button", {
21885
- className: `oa-feedback-icon ${suggestion.feedbackType === "good" ? "oa-feedback-icon--positive" : ""}`,
21886
- "aria-label": "Mark as helpful",
21887
- onClick: () => handleThumbsUp(suggestion.sequence, suggestion.feedbackType),
21888
- style: {
21889
- color: suggestion.feedbackType === "good" ? "#16a34a" : undefined
21890
- },
21891
- children: jsxRuntimeExports.jsx(ThumbsUp, {
21892
- size: 14
21893
- })
21894
- }), jsxRuntimeExports.jsx("button", {
21895
- className: `oa-feedback-icon ${suggestion.feedbackType === "bad" ? "oa-feedback-icon--negative" : ""}`,
21896
- "aria-label": "Mark as not helpful",
21897
- onClick: () => handleThumbsDown(suggestion.sequence, suggestion.feedbackType),
21898
- style: {
21899
- color: suggestion.feedbackType === "bad" ? "#dc2626" : undefined
21900
- },
21901
- children: jsxRuntimeExports.jsx(ThumbsDown, {
21902
- size: 14
21903
- })
21904
- })]
21905
- })]
21906
- })]
21907
- }), showReasons && badFeedbackOptions.length > 0 && jsxRuntimeExports.jsx("div", {
21768
+ children: suggestion.value
21769
+ })
21770
+ }), jsxRuntimeExports.jsx("button", {
21771
+ className: "oa-copy-icon",
21772
+ "aria-label": "Copy suggestion",
21773
+ onClick: () => handleCopy(suggestion.value),
21908
21774
  style: {
21909
- display: "flex",
21910
- gap: "8px",
21911
- flexWrap: "wrap",
21912
- marginTop: "8px"
21775
+ flexShrink: 0,
21776
+ marginTop: "10px"
21913
21777
  },
21914
- children: badFeedbackOptions.map(reason => jsxRuntimeExports.jsx("button", {
21915
- className: `oa-feedback-reason-btn ${suggestion.badFeedbackReason === reason ? "oa-feedback-reason-btn--selected" : ""}`,
21916
- onClick: () => handleBadFeedbackReason(suggestion.sequence, reason),
21917
- children: reason
21918
- }, reason))
21778
+ children: jsxRuntimeExports.jsx(Copy, {
21779
+ size: 14
21780
+ })
21919
21781
  })]
21920
21782
  }, suggestion.id);
21921
21783
  })
@@ -22014,13 +21876,12 @@ function ExotelAIAssist(_a) {
22014
21876
  } = _a,
22015
21877
  params = __rest(_a, ["className", "onReady"]);
22016
21878
  const {
22017
- streamState,
22018
21879
  isReady,
21880
+ streamState,
22019
21881
  suggestions,
22020
21882
  transcripts,
22021
21883
  sentiment,
22022
- botConfig,
22023
- sendSuggestionFeedback
21884
+ botConfig
22024
21885
  } = useExotelAIAssist(params);
22025
21886
  const onReadyRef = reactExports.useRef(onReady);
22026
21887
  onReadyRef.current = onReady;
@@ -22041,7 +21902,7 @@ function ExotelAIAssist(_a) {
22041
21902
  if (streamState === "connection_timeout") {
22042
21903
  return jsxRuntimeExports.jsx(EmptyState, {
22043
21904
  title: "AI Assist is currently inactive",
22044
- subtitle: "we were unable to establish a connection."
21905
+ subtitle: "We were unable to establish a connection."
22045
21906
  });
22046
21907
  }
22047
21908
  return null;
@@ -22098,8 +21959,7 @@ function ExotelAIAssist(_a) {
22098
21959
  children: jsxRuntimeExports.jsx(SuggestionsTab, {
22099
21960
  suggestions: suggestions,
22100
21961
  connected: connected,
22101
- botConfig: botConfig,
22102
- sendSuggestionFeedback: sendSuggestionFeedback
21962
+ botConfig: botConfig
22103
21963
  })
22104
21964
  }), jsxRuntimeExports.jsx(Content$1, {
22105
21965
  value: "transcript",