@eshal-bot/chat-widget 0.1.96 → 0.1.98

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-CHqB62M7.esm.js";import"react";import"react-dom/client";import"react-dom";import"react/jsx-runtime";
1
+ export{S as default}from"./chat-widget-index-C4KMbduJ.esm.js";import"react";import"react-dom/client";import"react-dom";import"react/jsx-runtime";
@@ -16546,13 +16546,43 @@
16546
16546
  message: backendMsg
16547
16547
  };
16548
16548
  }
16549
- function putWithProgress(_ref2) {
16549
+
16550
+ // Give back session budget the backend reserved at /presign admission time
16551
+ // (EV-2791) - fired when a chip is removed or its upload fails AFTER that
16552
+ // reservation succeeded, so a not-actually-sent file doesn't permanently
16553
+ // inflate the conversation's "used" total for the rest of the session TTL.
16554
+ // Fire-and-forget: a failed release just leaves the budget a bit tighter
16555
+ // than it truly is, never more permissive, so it's safe to ignore errors.
16556
+ function releaseSessionBytes(_ref2) {
16557
+ let {
16558
+ apiEndpoint,
16559
+ conversationId,
16560
+ size,
16561
+ authHeader
16562
+ } = _ref2;
16563
+ fetch(joinUrl(apiEndpoint, "/api/v1/chat-attachments/release"), {
16564
+ method: "POST",
16565
+ credentials: "include",
16566
+ headers: _objectSpread2({
16567
+ "Content-Type": "application/json"
16568
+ }, authHeader ? {
16569
+ Authorization: authHeader
16570
+ } : {}),
16571
+ body: JSON.stringify({
16572
+ conversationId,
16573
+ size
16574
+ })
16575
+ }).catch(() => {
16576
+ /* best-effort - see comment above */
16577
+ });
16578
+ }
16579
+ function putWithProgress(_ref3) {
16550
16580
  let {
16551
16581
  url,
16552
16582
  file,
16553
16583
  onProgress,
16554
16584
  registerXhr
16555
- } = _ref2;
16585
+ } = _ref3;
16556
16586
  return new Promise((resolve, reject) => {
16557
16587
  const xhr = new XMLHttpRequest();
16558
16588
  registerXhr(xhr);
@@ -16583,7 +16613,7 @@
16583
16613
  // `url` is consumed today (READ SAS URL that knowledge-api's
16584
16614
  // attachment_ingest fetches bytes from). Malformed / empty JSON
16585
16615
  // resolves to `{}` - the upload succeeded, the read URL is best-effort.
16586
- function postMultipartWithProgress(_ref3) {
16616
+ function postMultipartWithProgress(_ref4) {
16587
16617
  let {
16588
16618
  url,
16589
16619
  file,
@@ -16591,7 +16621,7 @@
16591
16621
  authHeader,
16592
16622
  onProgress,
16593
16623
  registerXhr
16594
- } = _ref3;
16624
+ } = _ref4;
16595
16625
  return new Promise((resolve, reject) => {
16596
16626
  const xhr = new XMLHttpRequest();
16597
16627
  registerXhr(xhr);
@@ -16636,14 +16666,14 @@
16636
16666
  xhr.send(form);
16637
16667
  });
16638
16668
  }
16639
- function useFileUpload(_ref4) {
16669
+ function useFileUpload(_ref5) {
16640
16670
  let {
16641
16671
  conversationId,
16642
16672
  config,
16643
16673
  apiEndpoint,
16644
16674
  authHeader,
16645
16675
  onError
16646
- } = _ref4;
16676
+ } = _ref5;
16647
16677
  const [attachments, setAttachments] = reactExports.useState([]);
16648
16678
  const inflightRef = reactExports.useRef(new Map());
16649
16679
  const idCounterRef = reactExports.useRef(0);
@@ -16746,6 +16776,9 @@
16746
16776
  })
16747
16777
  });
16748
16778
  } catch (err) {
16779
+ // Never reached the server - no reservation was ever made, so just
16780
+ // undo this attachment's optimistic client-side estimate (EV-2791).
16781
+ sessionBytesRef.current.bytes = Math.max(0, sessionBytesRef.current.bytes - file.size);
16749
16782
  errorPatch(attachmentId, "network", "Network error while requesting upload.");
16750
16783
  return;
16751
16784
  }
@@ -16759,13 +16792,34 @@
16759
16792
  } catch (_unused4) {
16760
16793
  /* body not JSON - keep defaults */
16761
16794
  }
16795
+ // decideAttachmentPolicy rejects BEFORE reserving budget, so there's
16796
+ // nothing to release server-side - only undo the client estimate.
16797
+ sessionBytesRef.current.bytes = Math.max(0, sessionBytesRef.current.bytes - file.size);
16762
16798
  errorPatch(attachmentId, backendCode, backendMsg);
16763
16799
  return;
16764
16800
  }
16801
+
16802
+ // presignRes.ok means decideAttachmentPolicy's LAST step ran -
16803
+ // addSessionBytes already reserved this file's bytes server-side.
16804
+ // Mark it so removeAttachment (and every error path below) knows to
16805
+ // call /release rather than just correcting the client estimate.
16806
+ patchAttachment(attachmentId, {
16807
+ reservedBytes: file.size
16808
+ });
16765
16809
  let presign;
16766
16810
  try {
16767
16811
  presign = await presignRes.json();
16768
16812
  } catch (_unused5) {
16813
+ releaseSessionBytes({
16814
+ apiEndpoint,
16815
+ conversationId,
16816
+ size: file.size,
16817
+ authHeader
16818
+ });
16819
+ sessionBytesRef.current.bytes = Math.max(0, sessionBytesRef.current.bytes - file.size);
16820
+ patchAttachment(attachmentId, {
16821
+ reservedBytes: 0
16822
+ });
16769
16823
  errorPatch(attachmentId, "unknown", "Malformed presign response.");
16770
16824
  return;
16771
16825
  }
@@ -16824,6 +16878,16 @@
16824
16878
  });
16825
16879
  if (!confirmed.ok) {
16826
16880
  inflightRef.current.delete(attachmentId);
16881
+ releaseSessionBytes({
16882
+ apiEndpoint,
16883
+ conversationId,
16884
+ size: file.size,
16885
+ authHeader
16886
+ });
16887
+ sessionBytesRef.current.bytes = Math.max(0, sessionBytesRef.current.bytes - file.size);
16888
+ patchAttachment(attachmentId, {
16889
+ reservedBytes: 0
16890
+ });
16827
16891
  errorPatch(attachmentId, confirmed.code, confirmed.message);
16828
16892
  return;
16829
16893
  }
@@ -16832,7 +16896,19 @@
16832
16896
  }
16833
16897
  } catch (err) {
16834
16898
  inflightRef.current.delete(attachmentId);
16899
+ // Aborted means removeAttachment already released this reservation -
16900
+ // releasing again here would double-count the give-back.
16835
16901
  if (err && err.aborted) return;
16902
+ releaseSessionBytes({
16903
+ apiEndpoint,
16904
+ conversationId,
16905
+ size: file.size,
16906
+ authHeader
16907
+ });
16908
+ sessionBytesRef.current.bytes = Math.max(0, sessionBytesRef.current.bytes - file.size);
16909
+ patchAttachment(attachmentId, {
16910
+ reservedBytes: 0
16911
+ });
16836
16912
  errorPatch(attachmentId, "network", "Upload failed. Please retry.");
16837
16913
  return;
16838
16914
  }
@@ -16875,7 +16951,11 @@
16875
16951
  size: file.size,
16876
16952
  mimeType: file.type,
16877
16953
  status: "uploading",
16878
- progress: 0
16954
+ progress: 0,
16955
+ // Set once /presign confirms the server actually reserved this
16956
+ // file's bytes (EV-2791) - removeAttachment uses it to decide
16957
+ // whether a removal needs to call /release.
16958
+ reservedBytes: 0
16879
16959
  };
16880
16960
  setAttachments(prev => [...prev, entry]);
16881
16961
  // Reserve session budget at ADMISSION, not upload completion -
@@ -16892,8 +16972,22 @@
16892
16972
  xhr.abort();
16893
16973
  inflightRef.current.delete(id);
16894
16974
  }
16975
+ // EV-2791: a chip removed before Send had its bytes reserved server-side
16976
+ // at /presign admission time - give them back, or the session's "used"
16977
+ // total stays inflated by every file the user attached-then-removed,
16978
+ // for the rest of the 24h session TTL.
16979
+ const removed = attachmentsRef.current.find(a => a.id === id);
16980
+ if (removed && removed.reservedBytes > 0) {
16981
+ releaseSessionBytes({
16982
+ apiEndpoint,
16983
+ conversationId,
16984
+ size: removed.reservedBytes,
16985
+ authHeader
16986
+ });
16987
+ sessionBytesRef.current.bytes = Math.max(0, sessionBytesRef.current.bytes - removed.reservedBytes);
16988
+ }
16895
16989
  setAttachments(prev => prev.filter(a => a.id !== id));
16896
- }, []);
16990
+ }, [apiEndpoint, authHeader, conversationId]);
16897
16991
  const clearAll = reactExports.useCallback(() => {
16898
16992
  for (const xhr of inflightRef.current.values()) xhr.abort();
16899
16993
  inflightRef.current.clear();