@eshal-bot/chat-widget 0.2.18 → 0.2.20

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.
@@ -1 +1 @@
1
- export{e as default}from"./chat-widget-index-CwZq4zL4.esm.js";import"react";import"react-dom/client";import"react/jsx-runtime";import"react-dom";
1
+ export{e as default}from"./chat-widget-index-CjJ4shXp.esm.js";import"react";import"react-dom/client";import"react/jsx-runtime";import"react-dom";
@@ -7955,6 +7955,22 @@
7955
7955
  }
7956
7956
  }
7957
7957
 
7958
+ /** HTTP status the backend answers for an erased conversation. */
7959
+ const CONVERSATION_ERASED_STATUS = 410;
7960
+
7961
+ /**
7962
+ * Thrown when the conversation this widget holds was erased in the console
7963
+ * ("Erase permanently", on the conversation or its contact). The backend
7964
+ * answers every widget call on it with 410 `conversation_erased`; the caller
7965
+ * starts a new conversation instead of treating it as a failed send.
7966
+ */
7967
+ class ConversationErasedError extends Error {
7968
+ constructor(message = 'This conversation was erased') {
7969
+ super(message);
7970
+ this.name = 'ConversationErasedError';
7971
+ }
7972
+ }
7973
+
7958
7974
  // ============================================================================
7959
7975
  // NETWORK
7960
7976
  // ============================================================================
@@ -7991,6 +8007,9 @@
7991
8007
  signal
7992
8008
  } : {})
7993
8009
  });
8010
+ if (response.status === CONVERSATION_ERASED_STATUS) {
8011
+ throw new ConversationErasedError();
8012
+ }
7994
8013
  if (!response.ok) {
7995
8014
  throw new Error(`HTTP error! status: ${response.status}`);
7996
8015
  }
@@ -8798,9 +8817,21 @@
8798
8817
  this.buffer = new Float32Array(this.bufferSize);
8799
8818
  this.writeIndex = 0;
8800
8819
  this.readIndex = 0;
8820
+ // Paused while the caller may be speaking over the reply (lib/audio/bargeIn.js).
8821
+ this.paused = false;
8801
8822
  this.port.onmessage = (event) => {
8802
- if (event.data?.command === 'endOfAudio') {
8823
+ const command = event.data?.command;
8824
+ if (command === 'endOfAudio') {
8803
8825
  this.readIndex = this.writeIndex;
8826
+ this.paused = false;
8827
+ return;
8828
+ }
8829
+ if (command === 'pause') {
8830
+ this.paused = true;
8831
+ return;
8832
+ }
8833
+ if (command === 'resume') {
8834
+ this.paused = false;
8804
8835
  return;
8805
8836
  }
8806
8837
  const int16Samples = new Int16Array(event.data);
@@ -8823,11 +8854,14 @@
8823
8854
  const output = outputs[0];
8824
8855
  const framesPerBlock = output[0].length;
8825
8856
  for (let frame = 0; frame < framesPerBlock; frame += 1) {
8826
- output[0][frame] = this.buffer[this.readIndex];
8857
+ // Silence when paused or with nothing buffered.
8858
+ const playing = !this.paused && this.readIndex !== this.writeIndex;
8859
+ const sample = playing ? this.buffer[this.readIndex] : 0;
8860
+ output[0][frame] = sample;
8827
8861
  if (output.length > 1) {
8828
- output[1][frame] = this.buffer[this.readIndex];
8862
+ output[1][frame] = sample;
8829
8863
  }
8830
- if (this.readIndex !== this.writeIndex) {
8864
+ if (playing) {
8831
8865
  this.readIndex = (this.readIndex + 1) % this.bufferSize;
8832
8866
  }
8833
8867
  }
@@ -8884,9 +8918,16 @@
8884
8918
 
8885
8919
  // Clean up the Blob URL after the worklet is loaded
8886
8920
  URL.revokeObjectURL(workletUrl);
8921
+
8922
+ // The reply plays through the speakers while the mic is open: without echo
8923
+ // cancellation it comes back in, and the voice gate takes it for the caller
8924
+ // barging in (dev, 16 Oct 2026).
8887
8925
  const micStream = await navigator.mediaDevices.getUserMedia({
8888
8926
  audio: {
8889
- channelCount: 1
8927
+ channelCount: 1,
8928
+ echoCancellation: true,
8929
+ noiseSuppression: true,
8930
+ autoGainControl: true
8890
8931
  }
8891
8932
  });
8892
8933
  const source = audioRecorderContext.createMediaStreamSource(micStream);
@@ -9059,6 +9100,34 @@
9059
9100
  };
9060
9101
  }
9061
9102
 
9103
+ /**
9104
+ * What becomes of a reply's audio when the caller may be speaking over it.
9105
+ *
9106
+ * The voice gate hears sound over a reply (the caller, a cough, the reply
9107
+ * itself through the speakers). It used to empty the player at once, so
9108
+ * everything already buffered was lost; the server sends audio faster than it
9109
+ * plays, so that was most of the reply. Dev, 16 Oct 2026 (Greenleaf voice): a
9110
+ * noise during the checkout reply cut it off, and the server, finding no
9111
+ * speech, sent only the audio it had still been holding.
9112
+ *
9113
+ * Now the player pauses, and the server's verdict on what it heard decides:
9114
+ * an empty transcription is no speech (resume), the caller's words or
9115
+ * `interrupted` are a real barge-in (discard). With no verdict at all the
9116
+ * reply resumes BARGE_IN_RESUME_AFTER_MS after the sound ends.
9117
+ * Mirrors packages/frontend/src/lib/audio/bargeIn.ts.
9118
+ */
9119
+ const BARGE_IN_RESUME_AFTER_MS = 4000;
9120
+
9121
+ /** "resume" | "discard" | null (the event says nothing about the sound). */
9122
+ function bargeInVerdict(event) {
9123
+ if (!event) return null;
9124
+ if (event.interrupted) return 'discard';
9125
+ const heard = event.inputTranscription;
9126
+ if (!heard) return null;
9127
+ if (String(heard.text ?? '').trim()) return 'discard';
9128
+ return heard.finished ? 'resume' : null;
9129
+ }
9130
+
9062
9131
  // Lightweight i18n for the embeddable chat widget.
9063
9132
  // Translations are bundled so the widget stays dependency-free.
9064
9133
  // Locale is resolved from `defaultLanguage` (e.g. "zh", "ar") configured
@@ -11392,6 +11461,31 @@
11392
11461
  return "voiceUnavailable";
11393
11462
  }
11394
11463
 
11464
+ /**
11465
+ * Replies the widget never streamed itself.
11466
+ *
11467
+ * The widget holds its own copy of every /support turn, under its own ids, so
11468
+ * on reload (and on the event stream's backfill) it merges from the server
11469
+ * only what reached the conversation another way: what a person sent from the
11470
+ * Inbox (contentType human_reply, handoff_ack) and a Skill's reply to a staff
11471
+ * turn (staffTurn: Instruct Skill, or a hand-back that resumes the Skill;
11472
+ * backend services/staffTurn.ts). Dev, 15 Oct 2026: a Skill reply sent while
11473
+ * the Testing lab was closed never showed there, because only a person's
11474
+ * replies were merged. Mirrors packages/frontend/src/lib/messages/repliedOutsideStream.ts.
11475
+ */
11476
+ function isRepliedOutsideStream(m) {
11477
+ return m?.contentType === 'human_reply' || m?.contentType === 'handoff_ack' || m?.staffTurn === true;
11478
+ }
11479
+
11480
+ /**
11481
+ * Who owns the conversation once a pushed message is out, or null when the
11482
+ * message does not change that. A staff turn's reply says: the AI answers
11483
+ * again, or a person still has it when the Skill handed off again.
11484
+ */
11485
+ function responderAfterPush(m) {
11486
+ return m?.responder === 'ai' || m?.responder === 'human' ? m.responder : null;
11487
+ }
11488
+
11395
11489
  /**
11396
11490
  * Clean CJK (Chinese, Japanese, Korean) spaces
11397
11491
  */
@@ -12167,6 +12261,16 @@
12167
12261
  return generateWidgetSessionId();
12168
12262
  });
12169
12263
 
12264
+ // Erase permanently (console): the conversation this widget holds was
12265
+ // erased, and every widget call on it now answers 410 conversation_erased.
12266
+ // Set below once resetConversation exists; the history fetch, the live
12267
+ // stream, the hand-off and sendMessage all call it to start a new
12268
+ // conversation instead of showing or reviving the erased one.
12269
+ const conversationErasedRef = reactExports.useRef(() => {});
12270
+ // The visitor's message whose send was refused because the conversation
12271
+ // was erased. Re-sent on the new conversation once it exists.
12272
+ const pendingResendRef = reactExports.useRef(null);
12273
+
12170
12274
  // Handoff EventSource - receives human agent messages in real-time
12171
12275
  const handoffEventSourceRef = reactExports.useRef(null);
12172
12276
  const HANDOFF_TRIGGER = "Connect with a human representative";
@@ -12182,11 +12286,31 @@
12182
12286
  isHandedOffRef.current = true;
12183
12287
  setIsHandedOff(true);
12184
12288
  }, []);
12289
+ // The AI owns the conversation again: a Skill replied to an Instruct Skill.
12290
+ // Left handed off, the customer's next message was sent and the AI's
12291
+ // answer to it drained and dropped below.
12292
+ const markAiOwned = reactExports.useCallback(() => {
12293
+ isHandedOffRef.current = false;
12294
+ setIsHandedOff(false);
12295
+ }, []);
12185
12296
 
12186
12297
  // Bidi refs
12187
12298
  const websocketRef = reactExports.useRef(null);
12188
12299
  const audioPlayerNodeRef = reactExports.useRef(null);
12189
12300
  const audioPlayerContextRef = reactExports.useRef(null);
12301
+ // Sound over a reply pauses its audio until the server says what it heard
12302
+ // (lib/audio/bargeIn.js); the timer resumes it when no verdict comes.
12303
+ const bargeInPendingRef = reactExports.useRef(false);
12304
+ const bargeInTimerRef = reactExports.useRef(null);
12305
+ const settleBargeIn = reactExports.useCallback(verdict => {
12306
+ if (bargeInTimerRef.current) clearTimeout(bargeInTimerRef.current);
12307
+ bargeInTimerRef.current = null;
12308
+ if (!bargeInPendingRef.current) return;
12309
+ bargeInPendingRef.current = false;
12310
+ audioPlayerNodeRef.current?.port.postMessage({
12311
+ command: verdict === "resume" ? "resume" : "endOfAudio"
12312
+ });
12313
+ }, []);
12190
12314
  const audioRecorderNodeRef = reactExports.useRef(null);
12191
12315
  const audioRecorderContextRef = reactExports.useRef(null);
12192
12316
  const micStreamRef = reactExports.useRef(null);
@@ -12318,10 +12442,14 @@
12318
12442
  headers,
12319
12443
  credentials: 'include'
12320
12444
  });
12445
+ if (res.status === CONVERSATION_ERASED_STATUS) {
12446
+ conversationErasedRef.current(null);
12447
+ return;
12448
+ }
12321
12449
  if (!res.ok) return;
12322
12450
  const data = await res.json();
12323
12451
  const serverMsgs = Array.isArray(data?.messages) ? data.messages : [];
12324
- mergeAgentMessages(serverMsgs.filter(m => m?.contentType === 'human_reply' || m?.contentType === 'handoff_ack'));
12452
+ mergeAgentMessages(serverMsgs.filter(isRepliedOutsideStream));
12325
12453
  } catch {
12326
12454
  /* network/parse failure - the live stream still carries new messages */
12327
12455
  }
@@ -12354,10 +12482,19 @@
12354
12482
  es.onmessage = event => {
12355
12483
  try {
12356
12484
  const data = JSON.parse(event.data);
12485
+ // Erased in the console while open: stop listening and start over.
12486
+ if (data.type === 'conversation_erased') {
12487
+ es.close();
12488
+ if (handoffEventSourceRef.current === es) handoffEventSourceRef.current = null;
12489
+ conversationErasedRef.current(null);
12490
+ return;
12491
+ }
12357
12492
  if (data.type === 'message_created' && data.message) {
12358
12493
  // Advance the backfill watermark so a later reconnect doesn't
12359
12494
  // re-request everything we have already rendered live.
12360
12495
  noteAgentMessageAt(data.message.createdAt);
12496
+ const owner = responderAfterPush(data.message);
12497
+ if (owner === 'ai') markAiOwned();else if (owner === 'human') markHandedOff();
12361
12498
  setMessages(prev => {
12362
12499
  if (prev.some(m => m.id === data.message.id)) return prev;
12363
12500
  return [...prev, createMessage({
@@ -12386,7 +12523,7 @@
12386
12523
  // would disable that recovery.
12387
12524
  es.onerror = () => {};
12388
12525
  handoffEventSourceRef.current = es;
12389
- }, [apiBaseUrl, bidiSessionId, getAuthToken, backfillAgentMessages]);
12526
+ }, [apiBaseUrl, bidiSessionId, getAuthToken, backfillAgentMessages, markAiOwned, markHandedOff]);
12390
12527
  reactExports.useEffect(() => {
12391
12528
  return () => {
12392
12529
  if (handoffEventSourceRef.current) {
@@ -12632,10 +12769,16 @@
12632
12769
  headers,
12633
12770
  credentials: 'include'
12634
12771
  });
12635
- if (!res.ok || cancelled) return;
12772
+ if (cancelled) return;
12773
+ // Erased in the console: the saved transcript must not be shown.
12774
+ if (res.status === CONVERSATION_ERASED_STATUS) {
12775
+ conversationErasedRef.current(null);
12776
+ return;
12777
+ }
12778
+ if (!res.ok) return;
12636
12779
  const data = await res.json();
12637
12780
  const serverMsgs = Array.isArray(data?.messages) ? data.messages : [];
12638
- const agentMsgs = serverMsgs.filter(m => m?.contentType === 'human_reply' || m?.contentType === 'handoff_ack');
12781
+ const agentMsgs = serverMsgs.filter(isRepliedOutsideStream);
12639
12782
 
12640
12783
  // Merge past human-agent replies into the transcript (regardless of
12641
12784
  // current responder - they're real history worth showing). Shares
@@ -12845,6 +12988,10 @@
12845
12988
  body: JSON.stringify({
12846
12989
  reason: 'User clicked handoff button'
12847
12990
  })
12991
+ }).then(res => {
12992
+ // Erased: nobody to hand off to. Start a new conversation,
12993
+ // where the visitor can ask for a person again.
12994
+ if (res.status === CONVERSATION_ERASED_STATUS) conversationErasedRef.current(null);
12848
12995
  }).catch(() => {});
12849
12996
  }
12850
12997
  };
@@ -13087,6 +13234,8 @@
13087
13234
  if (done) break;
13088
13235
  }
13089
13236
  } catch (err) {
13237
+ // Erased: handled once, below, like any other send.
13238
+ if (err instanceof ConversationErasedError) throw err;
13090
13239
  // L4: this catch used to swallow the failure - but in a handed-off
13091
13240
  // conversation this POST is the ONLY path to the human agent, so a
13092
13241
  // dropped send meant the customer's message never reached anyone
@@ -13168,6 +13317,21 @@
13168
13317
  success: true
13169
13318
  };
13170
13319
  } catch (error) {
13320
+ // Erased in the console: not a failed send. Start a new conversation
13321
+ // and send this message again there, with no error bubble.
13322
+ if (error instanceof ConversationErasedError) {
13323
+ const isChat = apiType === 'inquiry' || apiType === 'complaint' || apiType === 'technical';
13324
+ conversationErasedRef.current(isChat ? {
13325
+ text: trimmed,
13326
+ attachments: attachmentsPayload,
13327
+ sessionId: bidiSessionId
13328
+ } : null);
13329
+ return {
13330
+ success: false,
13331
+ error: 'conversation_erased',
13332
+ erased: true
13333
+ };
13334
+ }
13171
13335
  console.error("Error sending message:", error);
13172
13336
  if (apiType === 'inquiry' || apiType === 'complaint' || apiType === 'technical') {
13173
13337
  setIsLoading(false);
@@ -13220,6 +13384,16 @@
13220
13384
  // bubble first, then routes back through sendMessage so the retry gets the
13221
13385
  // full pipeline (concurrency guard, stream retries, delivery state) instead
13222
13386
  // of a bespoke resend path.
13387
+ // A send refused because the conversation was erased goes out again on the
13388
+ // new conversation, once the reset has rotated the session id.
13389
+ reactExports.useEffect(() => {
13390
+ const pending = pendingResendRef.current;
13391
+ if (!pending || pending.sessionId === bidiSessionId) return;
13392
+ pendingResendRef.current = null;
13393
+ void sendMessage(pending.text, undefined, {
13394
+ attachments: pending.attachments
13395
+ });
13396
+ }, [bidiSessionId, sendMessage]);
13223
13397
  const retryMessage = reactExports.useCallback(failedMessage => {
13224
13398
  if (!failedMessage || failedMessage.status !== 'failed') {
13225
13399
  return Promise.resolve({
@@ -13902,6 +14076,9 @@
13902
14076
  // Reset userHasSpoken flag when stopping session
13903
14077
  userHasSpokenRef.current = false;
13904
14078
  pendingVoiceSourcesRef.current = null;
14079
+ if (bargeInTimerRef.current) clearTimeout(bargeInTimerRef.current);
14080
+ bargeInTimerRef.current = null;
14081
+ bargeInPendingRef.current = false;
13905
14082
 
13906
14083
  // Cancel a pending reconnect, drop the ref FIRST (so this socket's own
13907
14084
  // onclose sees it is no longer current), then close it in any state.
@@ -13958,6 +14135,11 @@
13958
14135
  setVoiceError("voiceUnavailable");
13959
14136
  }
13960
14137
 
14138
+ // The backend's verdict on the sound that paused the reply: play on, or
14139
+ // drop what is left of it (lib/audio/bargeIn.js).
14140
+ const bargeIn = bargeInVerdict(event);
14141
+ if (bargeIn) settleBargeIn(bargeIn);
14142
+
13961
14143
  // Handle conversation conclusion for voice (bidi) sessions
13962
14144
  const conclusionDetected = event.conclusion_detected ?? event.conclusionDetected;
13963
14145
  if (typeof conclusionDetected === "boolean" && conclusionDetected) {
@@ -14152,7 +14334,7 @@
14152
14334
  if (!event.interrupted && !event.inputTranscription && !event.outputTranscription && !event.content && !event.prompts) {
14153
14335
  console.warn("[BIDI] handleBidiEvent: event had no recognized fields", event);
14154
14336
  }
14155
- }, [appendAssistantContent, appendUserTranscription, handleContentParts, handleInterrupted, handleTurnComplete, stopVoiceSession]);
14337
+ }, [appendAssistantContent, appendUserTranscription, handleContentParts, handleInterrupted, handleTurnComplete, stopVoiceSession, settleBargeIn]);
14156
14338
  const handleRecorderAudio = reactExports.useCallback(pcmData => {
14157
14339
  if (!isVoiceSessionActiveRef.current) {
14158
14340
  return;
@@ -14194,17 +14376,18 @@
14194
14376
  speechGateRef.current = createSpeechGate({
14195
14377
  sampleRate: 16000
14196
14378
  }, {
14197
- // Barge-in: the instant the user starts talking, stop any assistant
14198
- // TTS still playing so we don't talk over them. endOfAudio drops the
14199
- // player's buffered audio immediately (readIndex → writeIndex). No-op
14200
- // when nothing is playing (e.g. the user's first utterance).
14379
+ // Barge-in: the instant the user may be talking, pause any assistant
14380
+ // TTS still playing so we don't talk over them, and tell the backend
14381
+ // to hold the in-flight reply. The backend's verdict on what it heard
14382
+ // then resumes the audio or drops it (lib/audio/bargeIn.js): emptying
14383
+ // the player here lost the rest of a reply to a cough (dev, 16 Oct).
14201
14384
  onSpeechStart: () => {
14202
- // Barge-in: user started talking → (1) stop assistant TTS already
14203
- // buffered locally, and (2) tell the backend to cancel the in-flight
14204
- // reply so its remaining TTS doesn't clobber the next turn's audio.
14205
14385
  audioPlayerNodeRef.current?.port.postMessage({
14206
- command: "endOfAudio"
14386
+ command: "pause"
14207
14387
  });
14388
+ bargeInPendingRef.current = true;
14389
+ if (bargeInTimerRef.current) clearTimeout(bargeInTimerRef.current);
14390
+ bargeInTimerRef.current = null;
14208
14391
  const ws = websocketRef.current;
14209
14392
  if (ws && ws.readyState === WebSocket.OPEN) {
14210
14393
  ws.send(JSON.stringify({
@@ -14220,6 +14403,11 @@
14220
14403
  type: "audio_end"
14221
14404
  }));
14222
14405
  }
14406
+ // no verdict on the sound: the reply plays on
14407
+ if (bargeInPendingRef.current) {
14408
+ if (bargeInTimerRef.current) clearTimeout(bargeInTimerRef.current);
14409
+ bargeInTimerRef.current = setTimeout(() => settleBargeIn("resume"), BARGE_IN_RESUME_AFTER_MS);
14410
+ }
14223
14411
  }
14224
14412
  });
14225
14413
  const [recorderNode, recorderContext, micStream] = await startAudioRecorderWorklet(handleRecorderAudio);
@@ -14232,7 +14420,7 @@
14232
14420
  console.error("Failed to initialize audio pipeline", error);
14233
14421
  throw error;
14234
14422
  }
14235
- }, [handleRecorderAudio]);
14423
+ }, [handleRecorderAudio, settleBargeIn]);
14236
14424
  const connectWebsocket = reactExports.useCallback(async () => {
14237
14425
  if (typeof window === "undefined" || !websocketUrl) {
14238
14426
  return;
@@ -14577,6 +14765,22 @@
14577
14765
  resetConversationRef.current = resetConversation;
14578
14766
  }, [resetConversation]);
14579
14767
 
14768
+ // The conversation was erased in the console: drop the saved transcript and
14769
+ // start a new conversation, the same reset as after CSAT. Idempotent per
14770
+ // conversation id, since the history fetch, the stream and a send can all
14771
+ // learn it at once. `resend` is the visitor's refused message, sent again
14772
+ // on the new conversation unless onboarding has to run first.
14773
+ const erasedHandledForRef = reactExports.useRef(null);
14774
+ reactExports.useEffect(() => {
14775
+ conversationErasedRef.current = resend => {
14776
+ if (erasedHandledForRef.current === bidiSessionId) return;
14777
+ erasedHandledForRef.current = bidiSessionId;
14778
+ const needsOnboarding = onboardingEnabled && getOnboardingQuestions(onboardingQuestions).length > 0;
14779
+ pendingResendRef.current = resend && !needsOnboarding ? resend : null;
14780
+ resetConversationRef.current();
14781
+ };
14782
+ }, [bidiSessionId, onboardingEnabled, onboardingQuestions]);
14783
+
14580
14784
  // Precise inactivity timeout: schedules a setTimeout for exact remaining time,
14581
14785
  // then reschedules itself after each check (handles activity updates in localStorage).
14582
14786
  //
@@ -16889,6 +17093,10 @@
16889
17093
  isRtl = false,
16890
17094
  questionTextColor,
16891
17095
  questionBoxColor,
17096
+ // User bubble colours, same props ChatInput's follow-up chips take.
17097
+ resolvedUserMessageColor,
17098
+ userMessageBoxColor,
17099
+ userTextColor,
16892
17100
  // Optional localized heading rendered above the vertical pill list,
16893
17101
  // styled to match the inline category picker's helper-text heading.
16894
17102
  headingLabel
@@ -16918,9 +17126,14 @@
16918
17126
  });
16919
17127
  }
16920
17128
 
16921
- // Vertical pills: the text takes the agent bubble's own text colour (same
16922
- // fallback chain MessageBubble uses), the border keeps the header colour.
16923
- const chipTextColor = questionTextColor ?? textColor ?? (isDark ? "#f3f4f6" : "#1A1A1A");
17129
+ // Vertical pills take their fill and text colour from the user bubble, with
17130
+ // the same fallback chains as the follow-up chips (ChatInput's
17131
+ // PromptSuggestionsRow): a click sends the question as the user's own
17132
+ // message. The agent bubble's text colour sat on a white pill, so a white
17133
+ // assistant text colour made the questions vanish. No border, like the
17134
+ // follow-up chips.
17135
+ const chipBackground = resolvedUserMessageColor ?? userMessageBoxColor ?? primaryColor ?? "#2563eb";
17136
+ const chipTextColor = userTextColor ?? textColor ?? "#FFFFFF";
16924
17137
 
16925
17138
  // Vertical layout (default) - pill style matching chat-preview
16926
17139
  return /*#__PURE__*/jsxRuntimeExports.jsxs("div", {
@@ -16947,8 +17160,8 @@
16947
17160
  fontFamily,
16948
17161
  fontSize,
16949
17162
  color: chipTextColor,
16950
- backgroundColor: isDark ? "transparent" : "#ffffff",
16951
- border: `1.5px solid ${primaryColor}`,
17163
+ backgroundColor: chipBackground,
17164
+ border: "none",
16952
17165
  borderRadius: "9999px",
16953
17166
  padding: "5px 14px",
16954
17167
  fontWeight: 500,
@@ -16956,12 +17169,6 @@
16956
17169
  whiteSpace: "normal",
16957
17170
  textAlign: questionIsRtl ? "right" : "left"
16958
17171
  },
16959
- onMouseEnter: e => {
16960
- e.currentTarget.style.backgroundColor = `${primaryColor}14`;
16961
- },
16962
- onMouseLeave: e => {
16963
- e.currentTarget.style.backgroundColor = isDark ? "transparent" : "#ffffff";
16964
- },
16965
17172
  children: question
16966
17173
  }, index);
16967
17174
  })
@@ -17763,6 +17970,9 @@
17763
17970
  isRtl: isRtl,
17764
17971
  questionTextColor: assistantTextColor,
17765
17972
  questionBoxColor: agentMessageBubbleColor,
17973
+ resolvedUserMessageColor: resolvedUserMessageColor,
17974
+ userMessageBoxColor: userMessageBoxColor,
17975
+ userTextColor: userTextColor,
17766
17976
  headingLabel: quickQuestionsHeadingLabel
17767
17977
  })
17768
17978
  }), isVoiceSessionActive && voiceStatus === "connected" && !isLoading && /*#__PURE__*/jsxRuntimeExports.jsx("div", {
@@ -21656,7 +21866,7 @@
21656
21866
  * value by its own test (that constant sat at 0.1.113 while this package was
21657
21867
  * at 0.2.2 precisely because nothing enforced it - EV-2431).
21658
21868
  */
21659
- const WIDGET_VERSION = '0.2.18';
21869
+ const WIDGET_VERSION = '0.2.20';
21660
21870
 
21661
21871
  /**
21662
21872
  * API utility functions