@eshal-bot/chat-widget 0.1.106 → 0.1.108

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{S as default}from"./chat-widget-index-Ccm0r0y5.esm.js";import"react";import"react-dom/client";import"react-dom";import"react/jsx-runtime";
1
+ export{S as default}from"./chat-widget-index-BeabB7Lu.esm.js";import"react";import"react-dom/client";import"react-dom";import"react/jsx-runtime";
@@ -7887,6 +7887,10 @@
7887
7887
  // StreamEventType.FOLLOW_UP_PROMPTS (the frontend's ChatService.ts
7888
7888
  // already handled it).
7889
7889
  FOLLOW_UP_PROMPTS: 'follow_up_prompts',
7890
+ // EV-3046: preliminary sources emitted as soon as retrieval resolves
7891
+ // (before generation). Carries a `sources` array; the complete event's
7892
+ // filtered list later replaces or clears it.
7893
+ SOURCES: 'sources',
7890
7894
  ERROR: 'error',
7891
7895
  HUMAN_DECISION_REQUIRED: 'human_decision_required'
7892
7896
  };
@@ -7981,7 +7985,8 @@
7981
7985
  }
7982
7986
  };
7983
7987
 
7984
- const _excluded$1 = ["prompts"];
7988
+ const _excluded$1 = ["prompts"],
7989
+ _excluded2 = ["sourcesPreliminary"];
7985
7990
 
7986
7991
  // ============================================================================
7987
7992
  // TYPES & INTERFACES
@@ -8017,7 +8022,7 @@
8017
8022
  * @param {string} apiBaseUrl - Base URL for the API
8018
8023
  * @param {Function|undefined} getAuthToken - async fn returning a Bearer token (in-app JWT path), or undefined (Mode B proxy)
8019
8024
  */
8020
- const sendStreamingRequest = async (request, apiBaseUrl, getAuthToken) => {
8025
+ const sendStreamingRequest = async (request, apiBaseUrl, getAuthToken, signal) => {
8021
8026
  const headers = {
8022
8027
  'Content-Type': 'application/json',
8023
8028
  Accept: 'text/event-stream'
@@ -8031,12 +8036,14 @@
8031
8036
  headers['Authorization'] = "Bearer ".concat(token);
8032
8037
  }
8033
8038
  }
8034
- const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/support"), {
8039
+ const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/support"), _objectSpread2({
8035
8040
  method: 'POST',
8036
8041
  headers,
8037
8042
  credentials: 'include',
8038
8043
  body: JSON.stringify(request)
8039
- });
8044
+ }, signal ? {
8045
+ signal
8046
+ } : {}));
8040
8047
  if (!response.ok) {
8041
8048
  throw new Error("HTTP error! status: ".concat(response.status));
8042
8049
  }
@@ -8391,6 +8398,22 @@
8391
8398
  * @param {ReadableStream<Uint8Array>} stream - The response stream
8392
8399
  * @param {function} setMessages - React state setter for messages
8393
8400
  */
8401
+ // EV-3046: one source-normalization path for BOTH the early {type:'sources'}
8402
+ // event and the complete event (API may send "Sources" or "sources").
8403
+ const normalizeSources = parsed => {
8404
+ var _ref3, _parsed$Sources;
8405
+ const rawSources = (_ref3 = (_parsed$Sources = parsed.Sources) !== null && _parsed$Sources !== void 0 ? _parsed$Sources : parsed.sources) !== null && _ref3 !== void 0 ? _ref3 : [];
8406
+ return Array.isArray(rawSources) ? rawSources.map(s => {
8407
+ var _ref4, _s$source_type, _s$sourceid, _ref5, _s$source_name, _s$citation_indices;
8408
+ return {
8409
+ source_type: (_ref4 = (_s$source_type = s.source_type) !== null && _s$source_type !== void 0 ? _s$source_type : s.sourceType) !== null && _ref4 !== void 0 ? _ref4 : 'website',
8410
+ source_id: (_s$sourceid = s.sourceid) !== null && _s$sourceid !== void 0 ? _s$sourceid : s.source_id,
8411
+ source_name: (_ref5 = (_s$source_name = s.source_name) !== null && _s$source_name !== void 0 ? _s$source_name : s.sourceName) !== null && _ref5 !== void 0 ? _ref5 : '',
8412
+ url: s.url,
8413
+ citation_indices: (_s$citation_indices = s.citation_indices) !== null && _s$citation_indices !== void 0 ? _s$citation_indices : s.citationIndices
8414
+ };
8415
+ }).filter(s => s.source_id || s.url || s.source_type === 'database') : [];
8416
+ };
8394
8417
  const processStream = async (stream, setMessages) => {
8395
8418
  const updateMessage = createMessageUpdater(setMessages);
8396
8419
  const reader = stream.getReader();
@@ -8557,31 +8580,39 @@
8557
8580
  continue;
8558
8581
  }
8559
8582
 
8583
+ // EV-3046: early sources strip (the Perplexity ordering).
8584
+ // Retrieval resolves before generation; the backend emits
8585
+ // {type:'sources'} as soon as the source set is known so the
8586
+ // user sees grounding during the pre-token wait. PRELIMINARY:
8587
+ // the complete event's overlap-filtered list replaces — or
8588
+ // clears — it.
8589
+ if (parsed.type === 'sources') {
8590
+ const early = normalizeSources(parsed);
8591
+ if (early.length > 0) {
8592
+ setMessages(prev => {
8593
+ const updated = [...(prev || [])];
8594
+ const last = updated[updated.length - 1];
8595
+ if ((last === null || last === void 0 ? void 0 : last.role) === 'assistant') {
8596
+ updated[updated.length - 1] = _objectSpread2(_objectSpread2({}, last), {}, {
8597
+ sources: early,
8598
+ sourcesPreliminary: true
8599
+ });
8600
+ }
8601
+ return updated;
8602
+ });
8603
+ }
8604
+ continue;
8605
+ }
8606
+
8560
8607
  // Handle complete events
8561
8608
  if (parsed.type === 'complete') {
8562
- var _ref3, _parsed$Sources, _parsed$choices3;
8609
+ var _parsed$choices3;
8563
8610
  streamCompleted = true;
8564
- // Normalize Sources from stream (API may send "Sources" or "sources")
8565
- const rawSources = (_ref3 = (_parsed$Sources = parsed.Sources) !== null && _parsed$Sources !== void 0 ? _parsed$Sources : parsed.sources) !== null && _ref3 !== void 0 ? _ref3 : [];
8566
- const sources = Array.isArray(rawSources) ? rawSources.map(s => {
8567
- var _ref4, _s$source_type, _s$sourceid, _ref5, _s$source_name, _s$citation_indices;
8568
- return {
8569
- source_type: (_ref4 = (_s$source_type = s.source_type) !== null && _s$source_type !== void 0 ? _s$source_type : s.sourceType) !== null && _ref4 !== void 0 ? _ref4 : 'website',
8570
- source_id: (_s$sourceid = s.sourceid) !== null && _s$sourceid !== void 0 ? _s$sourceid : s.source_id,
8571
- source_name: (_ref5 = (_s$source_name = s.source_name) !== null && _s$source_name !== void 0 ? _s$source_name : s.sourceName) !== null && _ref5 !== void 0 ? _ref5 : '',
8572
- url: s.url,
8573
- // 2026-07-14 - preserve citation_indices so
8574
- // `<CitationPill>` can resolve `[N]` markers even
8575
- // when N exceeds sources.length (post inline-cite
8576
- // filter, the emit list is shorter than the LLM's
8577
- // manifest - sources[N-1] misses on high indices).
8578
- citation_indices: (_s$citation_indices = s.citation_indices) !== null && _s$citation_indices !== void 0 ? _s$citation_indices : s.citationIndices
8579
- };
8580
- })
8581
- // Database "Data used" entries (v3 PR 8) carry neither an
8582
- // id nor a url by design - keep them; drop only OTHER
8583
- // source shapes that lack both (nothing to open/download).
8584
- .filter(s => s.source_id || s.url || s.source_type === 'database') : [];
8611
+ // Normalize Sources from stream. citation_indices are
8612
+ // preserved so <CitationPill> resolves [N] markers past the
8613
+ // inline-cite filter; database "Data used" entries keep their
8614
+ // id/url-less shape (see normalizeSources).
8615
+ const sources = normalizeSources(parsed);
8585
8616
 
8586
8617
  // Finalize the message content.
8587
8618
  // 2026-07-28 - prefer `finalContent` FIRST. It's the backend's
@@ -8613,22 +8644,26 @@
8613
8644
  // Finalize message with content + prompts + conclusionDetected
8614
8645
  updateFinalMessage(setMessages, finalContent, metadata);
8615
8646
 
8616
- // Attach sources to the last assistant message if present
8617
- if (sources.length > 0) {
8618
- setMessages(prev => {
8619
- const updatedMessages = [...(prev || [])];
8620
- const lastMessage = updatedMessages[updatedMessages.length - 1];
8621
- if ((lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.role) === 'assistant') {
8622
- updatedMessages[updatedMessages.length - 1] = _objectSpread2(_objectSpread2({}, lastMessage), {}, {
8623
- sources,
8624
- // Ensure final state is fully consistent
8625
- content: finalContent,
8626
- isProcessing: false
8627
- });
8628
- }
8629
- return updatedMessages;
8630
- });
8631
- }
8647
+ // Attach the AUTHORITATIVE (overlap-filtered) sources. When an
8648
+ // EV-3046 preliminary strip was shown, an empty final list must
8649
+ // CLEAR it — the filter decided nothing was actually cited.
8650
+ setMessages(prev => {
8651
+ const updatedMessages = [...(prev || [])];
8652
+ const lastMessage = updatedMessages[updatedMessages.length - 1];
8653
+ if ((lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.role) === 'assistant' && (sources.length > 0 || lastMessage.sourcesPreliminary)) {
8654
+ const {
8655
+ sourcesPreliminary
8656
+ } = lastMessage,
8657
+ rest = _objectWithoutProperties(lastMessage, _excluded2);
8658
+ updatedMessages[updatedMessages.length - 1] = _objectSpread2(_objectSpread2({}, rest), {}, {
8659
+ sources,
8660
+ // Ensure final state is fully consistent
8661
+ content: finalContent,
8662
+ isProcessing: false
8663
+ });
8664
+ }
8665
+ return updatedMessages;
8666
+ });
8632
8667
  continue;
8633
8668
  }
8634
8669
 
@@ -8686,6 +8721,22 @@
8686
8721
  }
8687
8722
  }
8688
8723
  }
8724
+ } catch (err) {
8725
+ // EV-3045: the user pressed Stop (fetch aborted). The partial answer
8726
+ // stays on screen, finalized — NOT an error, NOT a retry (retrying a
8727
+ // deliberately-stopped answer re-asks the question the user killed).
8728
+ if (err && err.name === 'AbortError') {
8729
+ if (accumulatedText.current) {
8730
+ updateFinalMessage(setMessages, accumulatedText.current, {
8731
+ stoppedByUser: true
8732
+ });
8733
+ } else {
8734
+ // nothing arrived yet — just drop the skeleton bubble
8735
+ setMessages(prev => (prev || []).filter(msg => !msg.isProcessing));
8736
+ }
8737
+ return;
8738
+ }
8739
+ throw err;
8689
8740
  } finally {
8690
8741
  reader.releaseLock();
8691
8742
  }
@@ -9550,6 +9601,10 @@
9550
9601
  _setInputValue(next);
9551
9602
  }, []);
9552
9603
  const [isLoading, setIsLoading] = reactExports.useState(false);
9604
+ // EV-3045: aborts the in-flight streaming fetch when the user presses
9605
+ // Stop. A fresh controller is armed per streaming attempt; stale
9606
+ // controllers are inert (abort on an already-finished fetch is a no-op).
9607
+ const streamAbortRef = reactExports.useRef(null);
9553
9608
  const widgetRef = reactExports.useRef(null);
9554
9609
  const messageIdRef = reactExports.useRef(1);
9555
9610
 
@@ -10131,6 +10186,21 @@
10131
10186
  })];
10132
10187
  });
10133
10188
  }, [getNextMessageId]);
10189
+
10190
+ // EV-3045: Stop control. Aborting the fetch is the cancellation signal —
10191
+ // the server tears the generator down on disconnect (cancelling the ADK
10192
+ // run, tool calls and delegation), and persists the partial answer to
10193
+ // history with a "stopped" marker. Client-side, processStream finalizes
10194
+ // the partial bubble on AbortError.
10195
+ const stopGeneration = reactExports.useCallback(() => {
10196
+ try {
10197
+ if (streamAbortRef.current) {
10198
+ streamAbortRef.current.abort();
10199
+ }
10200
+ } catch (_unused9) {
10201
+ /* aborting a finished fetch is a no-op */
10202
+ }
10203
+ }, []);
10134
10204
  const sendMessage = reactExports.useCallback(async (rawText, fieldType, opts) => {
10135
10205
  const trimmed = String(rawText !== null && rawText !== void 0 ? rawText : '').trim();
10136
10206
  const attachmentsPayload = Array.isArray(opts === null || opts === void 0 ? void 0 : opts.attachments) ? opts.attachments : [];
@@ -10357,7 +10427,7 @@
10357
10427
  } = await reader.read();
10358
10428
  if (done) break;
10359
10429
  }
10360
- } catch (_unused9) {
10430
+ } catch (_unused0) {
10361
10431
  /* persistence is best-effort; the human reply still arrives via SSE */
10362
10432
  }
10363
10433
  updateActivity(organizationId);
@@ -10369,7 +10439,10 @@
10369
10439
  let lastErr;
10370
10440
  for (let attempt = 0; attempt <= STREAM_RETRY_DELAYS_MS.length; attempt += 1) {
10371
10441
  try {
10372
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
10442
+ // EV-3045: one controller per attempt — the Stop button aborts
10443
+ // exactly the attempt that is streaming.
10444
+ streamAbortRef.current = new AbortController();
10445
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken, streamAbortRef.current.signal);
10373
10446
  await processStream(stream, setMessages);
10374
10447
  updateActivity(organizationId);
10375
10448
  return {
@@ -10377,6 +10450,17 @@
10377
10450
  };
10378
10451
  } catch (err) {
10379
10452
  lastErr = err;
10453
+ // EV-3045: a user Stop that aborted BEFORE any bytes arrived
10454
+ // rejects the fetch itself with AbortError (processStream's
10455
+ // own AbortError handling covers mid-stream aborts). Treat as
10456
+ // a successful, deliberate end — never retry a stopped turn.
10457
+ if (err && err.name === 'AbortError') {
10458
+ setMessages(prev => (prev || []).filter(msg => !msg.isProcessing));
10459
+ return {
10460
+ success: true,
10461
+ stopped: true
10462
+ };
10463
+ }
10380
10464
  if (err instanceof IncompleteStreamError && attempt < STREAM_RETRY_DELAYS_MS.length) {
10381
10465
  // Drop the half-rendered assistant bubble before retrying so
10382
10466
  // the next attempt doesn't paint into a stale skeleton.
@@ -11757,6 +11841,7 @@
11757
11841
  // Actions
11758
11842
  setInputValue,
11759
11843
  handleSend,
11844
+ stopGeneration,
11760
11845
  handleQuickQuestion,
11761
11846
  sendMessage,
11762
11847
  toggleChat,
@@ -11907,7 +11992,7 @@
11907
11992
  * See the LICENSE file in the root directory of this source tree.
11908
11993
  */
11909
11994
 
11910
- const __iconNode$h = [["path", {
11995
+ const __iconNode$i = [["path", {
11911
11996
  d: "M2 10v3",
11912
11997
  key: "1fnikh"
11913
11998
  }], ["path", {
@@ -11926,7 +12011,7 @@
11926
12011
  d: "M22 10v3",
11927
12012
  key: "154ddg"
11928
12013
  }]];
11929
- const AudioLines = createLucideIcon("audio-lines", __iconNode$h);
12014
+ const AudioLines = createLucideIcon("audio-lines", __iconNode$i);
11930
12015
 
11931
12016
  /**
11932
12017
  * @license lucide-react v0.575.0 - ISC
@@ -11935,11 +12020,11 @@
11935
12020
  * See the LICENSE file in the root directory of this source tree.
11936
12021
  */
11937
12022
 
11938
- const __iconNode$g = [["path", {
12023
+ const __iconNode$h = [["path", {
11939
12024
  d: "m15 18-6-6 6-6",
11940
12025
  key: "1wnfg3"
11941
12026
  }]];
11942
- const ChevronLeft = createLucideIcon("chevron-left", __iconNode$g);
12027
+ const ChevronLeft = createLucideIcon("chevron-left", __iconNode$h);
11943
12028
 
11944
12029
  /**
11945
12030
  * @license lucide-react v0.575.0 - ISC
@@ -11948,11 +12033,11 @@
11948
12033
  * See the LICENSE file in the root directory of this source tree.
11949
12034
  */
11950
12035
 
11951
- const __iconNode$f = [["path", {
12036
+ const __iconNode$g = [["path", {
11952
12037
  d: "m9 18 6-6-6-6",
11953
12038
  key: "mthhwq"
11954
12039
  }]];
11955
- const ChevronRight = createLucideIcon("chevron-right", __iconNode$f);
12040
+ const ChevronRight = createLucideIcon("chevron-right", __iconNode$g);
11956
12041
 
11957
12042
  /**
11958
12043
  * @license lucide-react v0.575.0 - ISC
@@ -11961,7 +12046,7 @@
11961
12046
  * See the LICENSE file in the root directory of this source tree.
11962
12047
  */
11963
12048
 
11964
- const __iconNode$e = [["circle", {
12049
+ const __iconNode$f = [["circle", {
11965
12050
  cx: "12",
11966
12051
  cy: "12",
11967
12052
  r: "10",
@@ -11979,7 +12064,7 @@
11979
12064
  y2: "16",
11980
12065
  key: "4dfq90"
11981
12066
  }]];
11982
- const CircleAlert = createLucideIcon("circle-alert", __iconNode$e);
12067
+ const CircleAlert = createLucideIcon("circle-alert", __iconNode$f);
11983
12068
 
11984
12069
  /**
11985
12070
  * @license lucide-react v0.575.0 - ISC
@@ -11988,7 +12073,7 @@
11988
12073
  * See the LICENSE file in the root directory of this source tree.
11989
12074
  */
11990
12075
 
11991
- const __iconNode$d = [["rect", {
12076
+ const __iconNode$e = [["rect", {
11992
12077
  width: "14",
11993
12078
  height: "14",
11994
12079
  x: "8",
@@ -12000,7 +12085,7 @@
12000
12085
  d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",
12001
12086
  key: "zix9uf"
12002
12087
  }]];
12003
- const Copy = createLucideIcon("copy", __iconNode$d);
12088
+ const Copy = createLucideIcon("copy", __iconNode$e);
12004
12089
 
12005
12090
  /**
12006
12091
  * @license lucide-react v0.575.0 - ISC
@@ -12009,7 +12094,7 @@
12009
12094
  * See the LICENSE file in the root directory of this source tree.
12010
12095
  */
12011
12096
 
12012
- const __iconNode$c = [["circle", {
12097
+ const __iconNode$d = [["circle", {
12013
12098
  cx: "12",
12014
12099
  cy: "12",
12015
12100
  r: "1",
@@ -12025,7 +12110,7 @@
12025
12110
  r: "1",
12026
12111
  key: "1pcz8c"
12027
12112
  }]];
12028
- const Ellipsis = createLucideIcon("ellipsis", __iconNode$c);
12113
+ const Ellipsis = createLucideIcon("ellipsis", __iconNode$d);
12029
12114
 
12030
12115
  /**
12031
12116
  * @license lucide-react v0.575.0 - ISC
@@ -12034,7 +12119,7 @@
12034
12119
  * See the LICENSE file in the root directory of this source tree.
12035
12120
  */
12036
12121
 
12037
- const __iconNode$b = [["path", {
12122
+ const __iconNode$c = [["path", {
12038
12123
  d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
12039
12124
  key: "1oefj6"
12040
12125
  }], ["path", {
@@ -12050,7 +12135,7 @@
12050
12135
  d: "M16 17H8",
12051
12136
  key: "z1uh3a"
12052
12137
  }]];
12053
- const FileText = createLucideIcon("file-text", __iconNode$b);
12138
+ const FileText = createLucideIcon("file-text", __iconNode$c);
12054
12139
 
12055
12140
  /**
12056
12141
  * @license lucide-react v0.575.0 - ISC
@@ -12059,11 +12144,11 @@
12059
12144
  * See the LICENSE file in the root directory of this source tree.
12060
12145
  */
12061
12146
 
12062
- const __iconNode$a = [["path", {
12147
+ const __iconNode$b = [["path", {
12063
12148
  d: "M21 12a9 9 0 1 1-6.219-8.56",
12064
12149
  key: "13zald"
12065
12150
  }]];
12066
- const LoaderCircle = createLucideIcon("loader-circle", __iconNode$a);
12151
+ const LoaderCircle = createLucideIcon("loader-circle", __iconNode$b);
12067
12152
 
12068
12153
  /**
12069
12154
  * @license lucide-react v0.575.0 - ISC
@@ -12072,11 +12157,11 @@
12072
12157
  * See the LICENSE file in the root directory of this source tree.
12073
12158
  */
12074
12159
 
12075
- const __iconNode$9 = [["path", {
12160
+ const __iconNode$a = [["path", {
12076
12161
  d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719",
12077
12162
  key: "1sd12s"
12078
12163
  }]];
12079
- const MessageCircle = createLucideIcon("message-circle", __iconNode$9);
12164
+ const MessageCircle = createLucideIcon("message-circle", __iconNode$a);
12080
12165
 
12081
12166
  /**
12082
12167
  * @license lucide-react v0.575.0 - ISC
@@ -12085,11 +12170,11 @@
12085
12170
  * See the LICENSE file in the root directory of this source tree.
12086
12171
  */
12087
12172
 
12088
- const __iconNode$8 = [["path", {
12173
+ const __iconNode$9 = [["path", {
12089
12174
  d: "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401",
12090
12175
  key: "kfwtm"
12091
12176
  }]];
12092
- const Moon = createLucideIcon("moon", __iconNode$8);
12177
+ const Moon = createLucideIcon("moon", __iconNode$9);
12093
12178
 
12094
12179
  /**
12095
12180
  * @license lucide-react v0.575.0 - ISC
@@ -12098,11 +12183,11 @@
12098
12183
  * See the LICENSE file in the root directory of this source tree.
12099
12184
  */
12100
12185
 
12101
- const __iconNode$7 = [["path", {
12186
+ const __iconNode$8 = [["path", {
12102
12187
  d: "m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551",
12103
12188
  key: "1miecu"
12104
12189
  }]];
12105
- const Paperclip = createLucideIcon("paperclip", __iconNode$7);
12190
+ const Paperclip = createLucideIcon("paperclip", __iconNode$8);
12106
12191
 
12107
12192
  /**
12108
12193
  * @license lucide-react v0.575.0 - ISC
@@ -12111,14 +12196,14 @@
12111
12196
  * See the LICENSE file in the root directory of this source tree.
12112
12197
  */
12113
12198
 
12114
- const __iconNode$6 = [["path", {
12199
+ const __iconNode$7 = [["path", {
12115
12200
  d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",
12116
12201
  key: "1357e3"
12117
12202
  }], ["path", {
12118
12203
  d: "M3 3v5h5",
12119
12204
  key: "1xhq8a"
12120
12205
  }]];
12121
- const RotateCcw = createLucideIcon("rotate-ccw", __iconNode$6);
12206
+ const RotateCcw = createLucideIcon("rotate-ccw", __iconNode$7);
12122
12207
 
12123
12208
  /**
12124
12209
  * @license lucide-react v0.575.0 - ISC
@@ -12127,14 +12212,31 @@
12127
12212
  * See the LICENSE file in the root directory of this source tree.
12128
12213
  */
12129
12214
 
12130
- const __iconNode$5 = [["path", {
12215
+ const __iconNode$6 = [["path", {
12131
12216
  d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z",
12132
12217
  key: "1ffxy3"
12133
12218
  }], ["path", {
12134
12219
  d: "m21.854 2.147-10.94 10.939",
12135
12220
  key: "12cjpa"
12136
12221
  }]];
12137
- const Send = createLucideIcon("send", __iconNode$5);
12222
+ const Send = createLucideIcon("send", __iconNode$6);
12223
+
12224
+ /**
12225
+ * @license lucide-react v0.575.0 - ISC
12226
+ *
12227
+ * This source code is licensed under the ISC license.
12228
+ * See the LICENSE file in the root directory of this source tree.
12229
+ */
12230
+
12231
+ const __iconNode$5 = [["rect", {
12232
+ width: "18",
12233
+ height: "18",
12234
+ x: "3",
12235
+ y: "3",
12236
+ rx: "2",
12237
+ key: "afitv7"
12238
+ }]];
12239
+ const Square = createLucideIcon("square", __iconNode$5);
12138
12240
 
12139
12241
  /**
12140
12242
  * @license lucide-react v0.575.0 - ISC
@@ -12345,6 +12447,9 @@
12345
12447
  scrollLeft: "Scroll left",
12346
12448
  scrollRight: "Scroll right",
12347
12449
  sendMessage: "Send message",
12450
+ // EV-3045: Stop control aria-label. EN-only for now — translate() falls
12451
+ // back to EN for locales without the key.
12452
+ stopGenerating: "Stop generating",
12348
12453
  startVoiceInput: "Start voice input",
12349
12454
  stopVoiceInput: "Stop voice input",
12350
12455
  voiceSessionLive: "Voice session live - speak or type to converse.",
@@ -17332,6 +17437,9 @@
17332
17437
  };
17333
17438
  const ChatInput = _ref2 => {
17334
17439
  let {
17440
+ // EV-3045: called when the user presses Stop while a reply is streaming;
17441
+ // the send button swaps to a stop button for the duration of the turn.
17442
+ onStopGeneration,
17335
17443
  inputValue,
17336
17444
  setInputValue,
17337
17445
  onSend,
@@ -17849,7 +17957,25 @@
17849
17957
  })]
17850
17958
  })]
17851
17959
  }), document.body)]
17852
- }), /*#__PURE__*/jsxRuntimeExports.jsx("button", {
17960
+ }), isLoading && !isVoiceSessionActive && typeof onStopGeneration === "function" ?
17961
+ /*#__PURE__*/
17962
+ // EV-3045: Stop replaces Send while a reply streams — the
17963
+ // convention every benchmark assistant uses. Always enabled;
17964
+ // the partial answer stays on screen and in history.
17965
+ jsxRuntimeExports.jsx("button", {
17966
+ onClick: onStopGeneration,
17967
+ className: "h-10 w-10 flex-shrink-0 rounded-xl transition-all duration-200 flex items-center justify-center shadow-md scale-100",
17968
+ style: {
17969
+ backgroundColor: primaryColor,
17970
+ color: "white",
17971
+ border: "none"
17972
+ },
17973
+ "aria-label": t("stopGenerating"),
17974
+ children: /*#__PURE__*/jsxRuntimeExports.jsx(Square, {
17975
+ className: "w-3.5 h-3.5",
17976
+ fill: "currentColor"
17977
+ })
17978
+ }) : /*#__PURE__*/jsxRuntimeExports.jsx("button", {
17853
17979
  onClick: isVoiceSessionActive && onBidiSubmit ? onBidiSubmit : sendWithAttachments,
17854
17980
  disabled: csatVisible || isDisabled || isVoiceSessionActive && voiceStatus !== "connected",
17855
17981
  className: "h-10 w-10 flex-shrink-0 rounded-xl transition-all duration-200 flex items-center justify-center shadow-md disabled:opacity-40 disabled:cursor-not-allowed disabled:shadow-none ".concat(inputValue.trim() ? "scale-100" : "scale-95"),
@@ -19032,6 +19158,7 @@
19032
19158
  isOpen,
19033
19159
  isMinimized,
19034
19160
  isDark,
19161
+ onStopGeneration,
19035
19162
  primaryColor,
19036
19163
  textColor,
19037
19164
  conciergeName,
@@ -19885,6 +20012,7 @@
19885
20012
  })]
19886
20013
  })
19887
20014
  }), !(onboardingActive && !onboardingCompleted) && /*#__PURE__*/jsxRuntimeExports.jsx(ChatInput, {
20015
+ onStopGeneration: onStopGeneration,
19888
20016
  inputValue: inputValue,
19889
20017
  setInputValue: setInputValue,
19890
20018
  onSend: onSend,
@@ -20424,6 +20552,7 @@
20424
20552
  widgetRef,
20425
20553
  setInputValue,
20426
20554
  handleSend,
20555
+ stopGeneration,
20427
20556
  handleQuickQuestion,
20428
20557
  sendMessage,
20429
20558
  toggleChat,
@@ -20607,6 +20736,7 @@
20607
20736
  inputValue: inputValue,
20608
20737
  setInputValue: setInputValue,
20609
20738
  onSend: handleSend,
20739
+ onStopGeneration: stopGeneration,
20610
20740
  onDirectSend: sendMessage,
20611
20741
  onToggleTheme: toggleTheme,
20612
20742
  onMinimize: toggleMinimize,