@cimplify/sdk 0.96.0 → 0.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var chunkHKN3YG76_js = require('./chunk-HKN3YG76.js');
4
+ var chunkQPRI2NII_js = require('./chunk-QPRI2NII.js');
5
5
  var chunkXREIZ56K_js = require('./chunk-XREIZ56K.js');
6
6
  var chunkW5PH7SPK_js = require('./chunk-W5PH7SPK.js');
7
7
  var chunkVEOAMBEX_js = require('./chunk-VEOAMBEX.js');
@@ -1298,7 +1298,7 @@ function CimplifyProvider({
1298
1298
  const apiCredentials = resolvedClient.getCredentialsMode() === "include" ? "use-credentials" : "anonymous";
1299
1299
  preconnectOrigin(resolvedClient.getBaseUrl(), apiCredentials);
1300
1300
  preconnectOrigin(resolvedClient.getLinkApiUrl(), apiCredentials);
1301
- preconnectOrigin(chunkHKN3YG76_js.DEFAULT_CDN_BASE_URL);
1301
+ preconnectOrigin(chunkQPRI2NII_js.DEFAULT_CDN_BASE_URL);
1302
1302
  }
1303
1303
  const setCurrentLocation = React14.useCallback(
1304
1304
  (location) => {
@@ -3115,8 +3115,8 @@ function fmtScheduleWindow(start, end) {
3115
3115
  const startStr = dateFns.format(s, "EEE, MMM d \xB7 h:mm a");
3116
3116
  const e = end ? dateFns.parseISO(end) : null;
3117
3117
  if (e && dateFns.isValid(e)) {
3118
- const sameDay = s.toDateString() === e.toDateString();
3119
- return `${startStr} \u2013 ${dateFns.format(e, sameDay ? "h:mm a" : "EEE, MMM d \xB7 h:mm a")}`;
3118
+ const sameDay2 = s.toDateString() === e.toDateString();
3119
+ return `${startStr} \u2013 ${dateFns.format(e, sameDay2 ? "h:mm a" : "EEE, MMM d \xB7 h:mm a")}`;
3120
3120
  }
3121
3121
  return startStr;
3122
3122
  }
@@ -5907,6 +5907,33 @@ function useBootstrap(options = {}) {
5907
5907
  const { data, isLoading, error, refetch } = bootstrapQuery(options);
5908
5908
  return { bootstrap: data, isLoading, error, refetch };
5909
5909
  }
5910
+
5911
+ // src/react/chat-model.ts
5912
+ var OPTIMISTIC_ID_PREFIX = "opt_";
5913
+ function optimisticId(clientId) {
5914
+ return `${OPTIMISTIC_ID_PREFIX}${clientId}`;
5915
+ }
5916
+ function isOptimistic(message) {
5917
+ return message.id.startsWith(OPTIMISTIC_ID_PREFIX);
5918
+ }
5919
+ function partitionMessages(messages) {
5920
+ const bubbles = [];
5921
+ const reactionsByTarget = /* @__PURE__ */ new Map();
5922
+ for (const message of messages) {
5923
+ if (message.content_type === "reaction") {
5924
+ const targetId = message.reply_to?.id;
5925
+ if (!targetId) continue;
5926
+ const list = reactionsByTarget.get(targetId);
5927
+ if (list) list.push(message);
5928
+ else reactionsByTarget.set(targetId, [message]);
5929
+ continue;
5930
+ }
5931
+ bubbles.push(message);
5932
+ }
5933
+ return { bubbles, reactionsByTarget };
5934
+ }
5935
+
5936
+ // src/react/hooks/use-chat.ts
5910
5937
  var POLL_INTERVAL_MS = 3e3;
5911
5938
  var POLL_IDLE_INTERVAL_MS = 15e3;
5912
5939
  var IDLE_TIMEOUT_MS = 6e4;
@@ -5921,10 +5948,10 @@ function mergeMessages(prev, incoming) {
5921
5948
  (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
5922
5949
  );
5923
5950
  const confirmed = new Set(
5924
- merged.filter((m) => !m.id.startsWith("opt_") && m.client_id).map((m) => m.client_id)
5951
+ merged.filter((m) => !isOptimistic(m) && m.client_id).map((m) => m.client_id)
5925
5952
  );
5926
5953
  return merged.filter(
5927
- (m) => !(m.id.startsWith("opt_") && m.client_id && confirmed.has(m.client_id))
5954
+ (m) => !(isOptimistic(m) && m.client_id && confirmed.has(m.client_id))
5928
5955
  );
5929
5956
  }
5930
5957
  function useChat(options = {}) {
@@ -5987,7 +6014,7 @@ function useChat(options = {}) {
5987
6014
  const lastConfirmedMessage = React14.useCallback(() => {
5988
6015
  const list = messagesRef.current;
5989
6016
  for (let i = list.length - 1; i >= 0; i--) {
5990
- if (!list[i].id.startsWith("opt_")) return list[i];
6017
+ if (!isOptimistic(list[i])) return list[i];
5991
6018
  }
5992
6019
  return void 0;
5993
6020
  }, []);
@@ -6035,7 +6062,7 @@ function useChat(options = {}) {
6035
6062
  const snapshotIds = new Set(snapshot.map((m) => m.id));
6036
6063
  const tail = snapshot.length > 0 ? new Date(snapshot[snapshot.length - 1].created_at).getTime() : 0;
6037
6064
  const extras = prev.filter(
6038
- (m) => !snapshotIds.has(m.id) && (m.id.startsWith("opt_") || new Date(m.created_at).getTime() > tail)
6065
+ (m) => !snapshotIds.has(m.id) && (isOptimistic(m) || new Date(m.created_at).getTime() > tail)
6039
6066
  );
6040
6067
  return mergeMessages(snapshot, extras);
6041
6068
  });
@@ -6221,9 +6248,9 @@ function useChat(options = {}) {
6221
6248
  }
6222
6249
  const replyTo = opts?.replyTo;
6223
6250
  const clientId = chunkVEOAMBEX_js.generateIdempotencyKey();
6224
- const optimisticId = `opt_${clientId}`;
6251
+ const localId = optimisticId(clientId);
6225
6252
  const optimistic = {
6226
- id: optimisticId,
6253
+ id: localId,
6227
6254
  sender_type: "customer",
6228
6255
  content: content.trim(),
6229
6256
  content_type: attachments.length > 0 ? "image" : "text",
@@ -6253,14 +6280,14 @@ function useChat(options = {}) {
6253
6280
  // The websocket may have already streamed the confirmed message
6254
6281
  // in while the POST was in flight; drop the optimistic bubble
6255
6282
  // instead of inserting a duplicate.
6256
- prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !== optimisticId) : prev.map((m) => m.id === optimisticId ? confirmed : m)
6283
+ prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !== localId) : prev.map((m) => m.id === localId ? confirmed : m)
6257
6284
  )
6258
6285
  );
6259
6286
  if (aiEnabledRef.current && !humanRequestedRef.current) {
6260
6287
  startAwaitingReply();
6261
6288
  }
6262
6289
  } else {
6263
- setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
6290
+ setMessages((prev) => prev.filter((m) => m.id !== localId));
6264
6291
  setError(result.error);
6265
6292
  }
6266
6293
  setIsSending(false);
@@ -6279,7 +6306,7 @@ function useChat(options = {}) {
6279
6306
  );
6280
6307
  const react = React14.useCallback(
6281
6308
  async (message, emoji) => {
6282
- if (!client || message.id.startsWith("opt_")) return false;
6309
+ if (!client || isOptimistic(message)) return false;
6283
6310
  const result = await client.support.reactToMessage(message.id, emoji);
6284
6311
  if (result.ok) appendMessages([result.value]);
6285
6312
  return result.ok;
@@ -6783,7 +6810,7 @@ function VariantSelector({
6783
6810
  {
6784
6811
  "data-cimplify-variant-name": true,
6785
6812
  className: cn("flex-1 min-w-0 text-sm", classNames?.name),
6786
- children: chunkHKN3YG76_js.getVariantDisplayName(variant, productName)
6813
+ children: chunkQPRI2NII_js.getVariantDisplayName(variant, productName)
6787
6814
  }
6788
6815
  ),
6789
6816
  /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-cimplify-variant-pricing": true, className: cn("text-sm text-muted-foreground", classNames?.pricing), children: [
@@ -8576,12 +8603,12 @@ function FieldInput({
8576
8603
  classNames?.input
8577
8604
  );
8578
8605
  switch (field.field_type) {
8579
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Text:
8580
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Url:
8606
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Text:
8607
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Url:
8581
8608
  return /* @__PURE__ */ jsxRuntime.jsx(
8582
8609
  "input",
8583
8610
  {
8584
- type: field.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.Url ? "url" : "text",
8611
+ type: field.field_type === chunkQPRI2NII_js.INPUT_FIELD_TYPE.Url ? "url" : "text",
8585
8612
  value: typeof value === "string" ? value : "",
8586
8613
  onChange: (e) => onValueChange(e.target.value),
8587
8614
  placeholder: field.placeholder,
@@ -8590,7 +8617,7 @@ function FieldInput({
8590
8617
  className: inputClass
8591
8618
  }
8592
8619
  );
8593
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Textarea:
8620
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Textarea:
8594
8621
  return /* @__PURE__ */ jsxRuntime.jsx(
8595
8622
  "textarea",
8596
8623
  {
@@ -8603,7 +8630,7 @@ function FieldInput({
8603
8630
  className: cn(inputClass, "resize-none", classNames?.textarea)
8604
8631
  }
8605
8632
  );
8606
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Number:
8633
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Number:
8607
8634
  return /* @__PURE__ */ jsxRuntime.jsx(
8608
8635
  "input",
8609
8636
  {
@@ -8617,7 +8644,7 @@ function FieldInput({
8617
8644
  className: inputClass
8618
8645
  }
8619
8646
  );
8620
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Select:
8647
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Select:
8621
8648
  return /* @__PURE__ */ jsxRuntime.jsxs(
8622
8649
  "select",
8623
8650
  {
@@ -8631,7 +8658,7 @@ function FieldInput({
8631
8658
  ]
8632
8659
  }
8633
8660
  );
8634
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Radio:
8661
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Radio:
8635
8662
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("space-y-2", classNames?.radioGroup), children: (field.options || []).map((opt) => /* @__PURE__ */ jsxRuntime.jsxs(
8636
8663
  "label",
8637
8664
  {
@@ -8656,7 +8683,7 @@ function FieldInput({
8656
8683
  },
8657
8684
  opt
8658
8685
  )) });
8659
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Checkbox:
8686
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Checkbox:
8660
8687
  return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: cn("flex items-center gap-2 text-sm cursor-pointer", classNames?.checkboxLabel), children: [
8661
8688
  /* @__PURE__ */ jsxRuntime.jsx(
8662
8689
  "input",
@@ -8669,7 +8696,7 @@ function FieldInput({
8669
8696
  ),
8670
8697
  field.placeholder || field.name
8671
8698
  ] });
8672
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Color:
8699
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Color:
8673
8700
  return /* @__PURE__ */ jsxRuntime.jsx(
8674
8701
  "input",
8675
8702
  {
@@ -8679,7 +8706,7 @@ function FieldInput({
8679
8706
  className: cn("w-12 h-10 rounded-md border border-input cursor-pointer", classNames?.colorInput)
8680
8707
  }
8681
8708
  );
8682
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Date:
8709
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Date:
8683
8710
  return /* @__PURE__ */ jsxRuntime.jsx(
8684
8711
  DatePicker,
8685
8712
  {
@@ -8690,8 +8717,8 @@ function FieldInput({
8690
8717
  "aria-label": field.name
8691
8718
  }
8692
8719
  );
8693
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.File:
8694
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Image:
8720
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.File:
8721
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Image:
8695
8722
  return /* @__PURE__ */ jsxRuntime.jsx(
8696
8723
  FileUploadInput,
8697
8724
  {
@@ -8702,7 +8729,7 @@ function FieldInput({
8702
8729
  classNames
8703
8730
  }
8704
8731
  );
8705
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Email:
8732
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Email:
8706
8733
  return /* @__PURE__ */ jsxRuntime.jsx(
8707
8734
  "input",
8708
8735
  {
@@ -8714,7 +8741,7 @@ function FieldInput({
8714
8741
  className: inputClass
8715
8742
  }
8716
8743
  );
8717
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.DateTime: {
8744
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.DateTime: {
8718
8745
  const stringValue = typeof value === "string" ? value : "";
8719
8746
  const [datePart, timePartRaw] = stringValue.includes("T") ? stringValue.split("T", 2) : [stringValue, ""];
8720
8747
  const timePart = (timePartRaw ?? "").slice(0, 5);
@@ -8753,7 +8780,7 @@ function FieldInput({
8753
8780
  )
8754
8781
  ] });
8755
8782
  }
8756
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Time:
8783
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Time:
8757
8784
  return /* @__PURE__ */ jsxRuntime.jsx(
8758
8785
  TimePicker,
8759
8786
  {
@@ -8764,7 +8791,7 @@ function FieldInput({
8764
8791
  "aria-label": field.name
8765
8792
  }
8766
8793
  );
8767
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.MultiSelect:
8794
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.MultiSelect:
8768
8795
  return /* @__PURE__ */ jsxRuntime.jsx(
8769
8796
  MultiSelectInput,
8770
8797
  {
@@ -8774,7 +8801,7 @@ function FieldInput({
8774
8801
  classNames
8775
8802
  }
8776
8803
  );
8777
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.DateRange:
8804
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.DateRange:
8778
8805
  return /* @__PURE__ */ jsxRuntime.jsx(
8779
8806
  DateRangeInput,
8780
8807
  {
@@ -8784,7 +8811,7 @@ function FieldInput({
8784
8811
  classNames
8785
8812
  }
8786
8813
  );
8787
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Address:
8814
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Address:
8788
8815
  return /* @__PURE__ */ jsxRuntime.jsx(
8789
8816
  AddressInput,
8790
8817
  {
@@ -8795,7 +8822,7 @@ function FieldInput({
8795
8822
  inputClass
8796
8823
  }
8797
8824
  );
8798
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Location:
8825
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Location:
8799
8826
  return /* @__PURE__ */ jsxRuntime.jsx(
8800
8827
  LocationInput,
8801
8828
  {
@@ -8806,7 +8833,7 @@ function FieldInput({
8806
8833
  inputClass
8807
8834
  }
8808
8835
  );
8809
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Phone:
8836
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Phone:
8810
8837
  return /* @__PURE__ */ jsxRuntime.jsx(
8811
8838
  PhoneInput,
8812
8839
  {
@@ -8817,7 +8844,7 @@ function FieldInput({
8817
8844
  inputClass
8818
8845
  }
8819
8846
  );
8820
- case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Signature:
8847
+ case chunkQPRI2NII_js.INPUT_FIELD_TYPE.Signature:
8821
8848
  return /* @__PURE__ */ jsxRuntime.jsx(
8822
8849
  SignatureInput,
8823
8850
  {
@@ -8850,7 +8877,7 @@ function FileUploadInput({
8850
8877
  const [isUploading, setIsUploading] = React14__default.default.useState(false);
8851
8878
  const fileUrl = typeof value === "string" ? value : void 0;
8852
8879
  const acceptedFormats = field.validation?.accepted_formats;
8853
- const accept = acceptedFormats ? acceptedFormats.map((f) => `.${f}`).join(",") : field.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.Image ? "image/*" : void 0;
8880
+ const accept = acceptedFormats ? acceptedFormats.map((f) => `.${f}`).join(",") : field.field_type === chunkQPRI2NII_js.INPUT_FIELD_TYPE.Image ? "image/*" : void 0;
8854
8881
  const handleFile = async (file) => {
8855
8882
  if (!onFileUpload) return;
8856
8883
  if (field.validation?.max_size_mb) {
@@ -8868,7 +8895,7 @@ function FileUploadInput({
8868
8895
  }
8869
8896
  };
8870
8897
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("space-y-2", classNames?.fileInput), children: fileUrl ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
8871
- field.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.Image && /* @__PURE__ */ jsxRuntime.jsx("img", { src: fileUrl, alt: "Uploaded", className: "w-16 h-16 object-cover rounded-md border border-border" }),
8898
+ field.field_type === chunkQPRI2NII_js.INPUT_FIELD_TYPE.Image && /* @__PURE__ */ jsxRuntime.jsx("img", { src: fileUrl, alt: "Uploaded", className: "w-16 h-16 object-cover rounded-md border border-border" }),
8872
8899
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground truncate", children: fileUrl.split("/").pop() }) }),
8873
8900
  /* @__PURE__ */ jsxRuntime.jsx(
8874
8901
  "button",
@@ -8894,7 +8921,7 @@ function FileUploadInput({
8894
8921
  }
8895
8922
  ),
8896
8923
  isUploading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "Uploading..." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8897
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: field.placeholder || `Upload ${field.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.Image ? "image" : "file"}` }),
8924
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: field.placeholder || `Upload ${field.field_type === chunkQPRI2NII_js.INPUT_FIELD_TYPE.Image ? "image" : "file"}` }),
8898
8925
  acceptedFormats && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground/60 mt-1", children: [
8899
8926
  acceptedFormats.map((f) => f.toUpperCase()).join(", "),
8900
8927
  field.validation?.max_size_mb && ` \xB7 Max ${field.validation.max_size_mb}MB`
@@ -9628,7 +9655,7 @@ function ProductCustomizer({
9628
9655
  const isBundle = productType === "bundle";
9629
9656
  const isDigital = productType === "digital";
9630
9657
  const isService = productType === "service";
9631
- const offersUnits = isService && chunkHKN3YG76_js.isMultiDayService(product);
9658
+ const offersUnits = isService && chunkQPRI2NII_js.isMultiDayService(product);
9632
9659
  const isStandard = !isComposite && !isBundle;
9633
9660
  const hasVariants = isStandard && product.variants && product.variants.length > 0;
9634
9661
  const hasAddOns = isStandard && product.add_ons && product.add_ons.length > 0;
@@ -9742,7 +9769,7 @@ function ProductCustomizer({
9742
9769
  }, [quote, quantity]);
9743
9770
  const displayTotalPrice = React14.useMemo(() => {
9744
9771
  const total = quotedTotalPrice ?? localTotalPrice;
9745
- const multiplier = chunkHKN3YG76_js.serviceBillingMultiplier(product, {
9772
+ const multiplier = chunkQPRI2NII_js.serviceBillingMultiplier(product, {
9746
9773
  quantity,
9747
9774
  units: unitsCount,
9748
9775
  scheduledStart: selectedSlot?.start_time,
@@ -10453,18 +10480,18 @@ function CartLineItemRow({
10453
10480
  });
10454
10481
  const hasComposite = item.compositeSelections && item.compositeSelections.length > 0;
10455
10482
  const hasBundle = item.bundleSelections && item.bundleSelections.length > 0;
10456
- const isOptimistic = item.isOptimistic === true;
10483
+ const isOptimistic2 = item.isOptimistic === true;
10457
10484
  const lineTotal = chunkPTPOQVXS_js.parsePrice(unitPrice) * item.quantity;
10458
10485
  const imageUrl = item.product.image_url;
10459
- const variantLabel2 = item.variant ? chunkHKN3YG76_js.getVariantDisplayName(item.variant, item.product.name) : null;
10486
+ const variantLabel2 = item.variant ? chunkQPRI2NII_js.getVariantDisplayName(item.variant, item.product.name) : null;
10460
10487
  return /* @__PURE__ */ jsxRuntime.jsxs(
10461
10488
  "div",
10462
10489
  {
10463
10490
  "data-cimplify-cart-item": true,
10464
- "data-optimistic": isOptimistic ? "true" : void 0,
10491
+ "data-optimistic": isOptimistic2 ? "true" : void 0,
10465
10492
  className: cn(
10466
10493
  "group relative flex gap-3 py-4 border-b border-border last:border-b-0 transition-opacity",
10467
- isOptimistic && "opacity-60",
10494
+ isOptimistic2 && "opacity-60",
10468
10495
  classNames?.item
10469
10496
  ),
10470
10497
  children: [
@@ -10556,7 +10583,7 @@ function CartLineItemRow({
10556
10583
  input.field_name,
10557
10584
  ":"
10558
10585
  ] }),
10559
- /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-cimplify-cart-input-value": true, className: "text-foreground m-0 truncate", children: input.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.File || input.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.Image ? String(input.value).split("/").pop() || "Uploaded file" : String(input.value) })
10586
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-cimplify-cart-input-value": true, className: "text-foreground m-0 truncate", children: input.field_type === chunkQPRI2NII_js.INPUT_FIELD_TYPE.File || input.field_type === chunkQPRI2NII_js.INPUT_FIELD_TYPE.Image ? String(input.value).split("/").pop() || "Uploaded file" : String(input.value) })
10560
10587
  ] }, input.field_id)) }),
10561
10588
  (item.lineType === "service" || item.product.type === "service") && item.scheduledStart && /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-cart-service-info": true, className: cn("mt-1.5 flex items-center gap-1.5 text-xs text-muted-foreground", classNames?.serviceInfo), children: [
10562
10589
  /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-3.5 h-3.5 shrink-0", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
@@ -10631,7 +10658,7 @@ function CartSummary({
10631
10658
  tax,
10632
10659
  total,
10633
10660
  isEmpty,
10634
- isOptimistic,
10661
+ isOptimistic: isOptimistic2,
10635
10662
  pendingOpCount,
10636
10663
  removeItem,
10637
10664
  updateQuantity
@@ -10657,7 +10684,7 @@ function CartSummary({
10657
10684
  "div",
10658
10685
  {
10659
10686
  "data-cimplify-cart-summary": true,
10660
- "data-optimistic": isOptimistic ? "true" : void 0,
10687
+ "data-optimistic": isOptimistic2 ? "true" : void 0,
10661
10688
  "data-pending-ops": pendingOpCount > 0 ? pendingOpCount : void 0,
10662
10689
  className: cn(className, classNames?.root),
10663
10690
  children: isEmpty ? /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cimplify-cart-empty": true, className: classNames?.empty, children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: emptyMessage }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -11441,7 +11468,7 @@ function FoodProductCard({
11441
11468
  const tags = product.tags || [];
11442
11469
  const isSignature = product.metadata?.is_signature === true;
11443
11470
  const isNew = product.metadata?.is_new === true;
11444
- const isComposite = product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Composite;
11471
+ const isComposite = product.type === chunkQPRI2NII_js.PRODUCT_TYPE.Composite;
11445
11472
  return /* @__PURE__ */ jsxRuntime.jsxs(CardShell, { product, renderLink, className, children: [
11446
11473
  /* @__PURE__ */ jsxRuntime.jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "square", renderImage, children: [
11447
11474
  isSignature && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "absolute top-3 left-3 inline-flex items-center gap-1 text-[11px] font-semibold tracking-wide bg-amber-50 text-amber-700 border border-amber-200/60 px-2 py-0.5 rounded-md", children: [
@@ -12208,36 +12235,36 @@ var BUILT_IN_CARDS = {
12208
12235
  ["composite" /* Composite */]: CompositeProductCard,
12209
12236
  ["subscription" /* Subscription */]: SubscriptionCard
12210
12237
  };
12211
- var RENTAL_UNITS = /* @__PURE__ */ new Set([chunkHKN3YG76_js.DURATION_UNIT.Days, chunkHKN3YG76_js.DURATION_UNIT.Hours]);
12212
- var LEASE_UNITS = /* @__PURE__ */ new Set([chunkHKN3YG76_js.DURATION_UNIT.Weeks, chunkHKN3YG76_js.DURATION_UNIT.Months, chunkHKN3YG76_js.DURATION_UNIT.Years]);
12238
+ var RENTAL_UNITS = /* @__PURE__ */ new Set([chunkQPRI2NII_js.DURATION_UNIT.Days, chunkQPRI2NII_js.DURATION_UNIT.Hours]);
12239
+ var LEASE_UNITS = /* @__PURE__ */ new Set([chunkQPRI2NII_js.DURATION_UNIT.Weeks, chunkQPRI2NII_js.DURATION_UNIT.Months, chunkQPRI2NII_js.DURATION_UNIT.Years]);
12213
12240
  function resolveCardVariant(product) {
12214
- if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Bundle) {
12241
+ if (product.type === chunkQPRI2NII_js.PRODUCT_TYPE.Bundle) {
12215
12242
  return "bundle" /* Bundle */;
12216
12243
  }
12217
- if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Composite) {
12244
+ if (product.type === chunkQPRI2NII_js.PRODUCT_TYPE.Composite) {
12218
12245
  return "composite" /* Composite */;
12219
12246
  }
12220
12247
  if (product.quantity_pricing && product.quantity_pricing.length > 1) {
12221
12248
  return "wholesale" /* Wholesale */;
12222
12249
  }
12223
- if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Digital) {
12250
+ if (product.type === chunkQPRI2NII_js.PRODUCT_TYPE.Digital) {
12224
12251
  return "digital" /* Digital */;
12225
12252
  }
12226
- if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Service) {
12253
+ if (product.type === chunkQPRI2NII_js.PRODUCT_TYPE.Service) {
12227
12254
  switch (product.boundary_kind) {
12228
- case chunkHKN3YG76_js.BOUNDARY_KIND.CheckInOut:
12255
+ case chunkQPRI2NII_js.BOUNDARY_KIND.CheckInOut:
12229
12256
  return "accommodation" /* Accommodation */;
12230
- case chunkHKN3YG76_js.BOUNDARY_KIND.MoveInOut:
12257
+ case chunkQPRI2NII_js.BOUNDARY_KIND.MoveInOut:
12231
12258
  return "lease" /* Lease */;
12232
- case chunkHKN3YG76_js.BOUNDARY_KIND.PickupReturn:
12233
- case chunkHKN3YG76_js.BOUNDARY_KIND.DeliveryCollection:
12234
- case chunkHKN3YG76_js.BOUNDARY_KIND.AccessVacate:
12259
+ case chunkQPRI2NII_js.BOUNDARY_KIND.PickupReturn:
12260
+ case chunkQPRI2NII_js.BOUNDARY_KIND.DeliveryCollection:
12261
+ case chunkQPRI2NII_js.BOUNDARY_KIND.AccessVacate:
12235
12262
  return "rental" /* Rental */;
12236
12263
  }
12237
12264
  if (product.duration_unit && RENTAL_UNITS.has(product.duration_unit)) {
12238
12265
  return "rental" /* Rental */;
12239
12266
  }
12240
- if (product.duration_unit === chunkHKN3YG76_js.DURATION_UNIT.Nights) {
12267
+ if (product.duration_unit === chunkQPRI2NII_js.DURATION_UNIT.Nights) {
12241
12268
  return "accommodation" /* Accommodation */;
12242
12269
  }
12243
12270
  if (product.duration_unit && LEASE_UNITS.has(product.duration_unit)) {
@@ -12248,10 +12275,10 @@ function resolveCardVariant(product) {
12248
12275
  }
12249
12276
  return "standard" /* Standard */;
12250
12277
  }
12251
- if (product.render_hint === chunkHKN3YG76_js.RENDER_HINT.Food) {
12278
+ if (product.render_hint === chunkQPRI2NII_js.RENDER_HINT.Food) {
12252
12279
  return "food" /* Food */;
12253
12280
  }
12254
- if (product.render_hint === chunkHKN3YG76_js.RENDER_HINT.Physical) {
12281
+ if (product.render_hint === chunkQPRI2NII_js.RENDER_HINT.Physical) {
12255
12282
  return "retail" /* Retail */;
12256
12283
  }
12257
12284
  return "retail" /* Retail */;
@@ -12531,14 +12558,14 @@ var PRESET_LABELS = {
12531
12558
  move_in_out: { start: "Move-in", end: "Move-out" }
12532
12559
  };
12533
12560
  function defaultBoundaryKind(unit) {
12534
- return unit === chunkHKN3YG76_js.DURATION_UNIT.Nights ? chunkHKN3YG76_js.BOUNDARY_KIND.CheckInOut : chunkHKN3YG76_js.BOUNDARY_KIND.PickupReturn;
12561
+ return unit === chunkQPRI2NII_js.DURATION_UNIT.Nights ? chunkQPRI2NII_js.BOUNDARY_KIND.CheckInOut : chunkQPRI2NII_js.BOUNDARY_KIND.PickupReturn;
12535
12562
  }
12536
12563
  function resolveBoundaryKind(product) {
12537
12564
  return product.boundary_kind ?? defaultBoundaryKind(product.duration_unit);
12538
12565
  }
12539
12566
  function getBoundaryLabels(product) {
12540
12567
  const kind = resolveBoundaryKind(product);
12541
- if (kind === chunkHKN3YG76_js.BOUNDARY_KIND.Custom) {
12568
+ if (kind === chunkQPRI2NII_js.BOUNDARY_KIND.Custom) {
12542
12569
  return {
12543
12570
  start: product.boundary_start_label?.trim() || "Start",
12544
12571
  end: product.boundary_end_label?.trim() || "End"
@@ -14626,7 +14653,7 @@ function DefaultProductLayout({
14626
14653
  className
14627
14654
  }) {
14628
14655
  const [selectedVariantId, setSelectedVariantId] = React14__default.default.useState();
14629
- const images = chunkHKN3YG76_js.resolveGallery(product, selectedVariantId);
14656
+ const images = chunkQPRI2NII_js.resolveGallery(product, selectedVariantId);
14630
14657
  const currency = chunkPTPOQVXS_js.getProductCurrency(product);
14631
14658
  const hasRange = product.quantity_pricing && product.quantity_pricing.length > 1 || product.variants && product.variants.length > 1;
14632
14659
  const onSale = chunkPTPOQVXS_js.isOnSale(product);
@@ -15273,25 +15300,25 @@ function resolveTemplateKey(product) {
15273
15300
  if (typeof metaTemplate === "string" && metaTemplate.trim()) {
15274
15301
  return metaTemplate.trim();
15275
15302
  }
15276
- if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Bundle) {
15303
+ if (product.type === chunkQPRI2NII_js.PRODUCT_TYPE.Bundle) {
15277
15304
  return "bundle" /* Bundle */;
15278
15305
  }
15279
- if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Composite) {
15306
+ if (product.type === chunkQPRI2NII_js.PRODUCT_TYPE.Composite) {
15280
15307
  return "composite" /* Composite */;
15281
15308
  }
15282
15309
  if (product.quantity_pricing && product.quantity_pricing.length > 1) {
15283
15310
  return "wholesale" /* Wholesale */;
15284
15311
  }
15285
- if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Service) {
15312
+ if (product.type === chunkQPRI2NII_js.PRODUCT_TYPE.Service) {
15286
15313
  return "service" /* Service */;
15287
15314
  }
15288
- if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Digital) {
15315
+ if (product.type === chunkQPRI2NII_js.PRODUCT_TYPE.Digital) {
15289
15316
  return "digital" /* Digital */;
15290
15317
  }
15291
- if (product.render_hint === chunkHKN3YG76_js.RENDER_HINT.Food) {
15318
+ if (product.render_hint === chunkQPRI2NII_js.RENDER_HINT.Food) {
15292
15319
  return "food" /* Food */;
15293
15320
  }
15294
- if (product.render_hint === chunkHKN3YG76_js.RENDER_HINT.Physical) {
15321
+ if (product.render_hint === chunkQPRI2NII_js.RENDER_HINT.Physical) {
15295
15322
  return "physical" /* Physical */;
15296
15323
  }
15297
15324
  return "default" /* Default */;
@@ -17144,24 +17171,581 @@ var ChatMarkdown = React14.memo(function ChatMarkdown2({
17144
17171
  return /* @__PURE__ */ jsxRuntime.jsx(SettledMarkdown, { text, className });
17145
17172
  });
17146
17173
 
17147
- // src/react/chat-model.ts
17148
- function partitionMessages(messages) {
17149
- const bubbles = [];
17150
- const reactionsByTarget = /* @__PURE__ */ new Map();
17151
- for (const message of messages) {
17152
- if (message.content_type === "reaction") {
17153
- const targetId = message.reply_to?.id;
17154
- if (!targetId) continue;
17155
- const list = reactionsByTarget.get(targetId);
17156
- if (list) list.push(message);
17157
- else reactionsByTarget.set(targetId, [message]);
17158
- continue;
17174
+ // src/react/chat-kit/motion.ts
17175
+ function prefersReducedMotion() {
17176
+ return typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
17177
+ }
17178
+ function scrollBehavior() {
17179
+ return prefersReducedMotion() ? "auto" : "smooth";
17180
+ }
17181
+ function QuoteBlock({
17182
+ quote,
17183
+ inCustomerBubble,
17184
+ businessName,
17185
+ onMissing
17186
+ }) {
17187
+ const quotedIsCustomer = quote.sender_type === "customer";
17188
+ const jumpToOriginal = async () => {
17189
+ let original = document.getElementById(`chat-msg-${quote.id}`);
17190
+ if (!original && onMissing && await onMissing()) {
17191
+ original = document.getElementById(`chat-msg-${quote.id}`);
17192
+ }
17193
+ if (!original) return;
17194
+ original.scrollIntoView({ behavior: scrollBehavior(), block: "center" });
17195
+ original.classList.remove("animate-quote-flash");
17196
+ void original.offsetWidth;
17197
+ original.classList.add("animate-quote-flash");
17198
+ };
17199
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17200
+ "button",
17201
+ {
17202
+ type: "button",
17203
+ onClick: () => void jumpToOriginal(),
17204
+ className: cn(
17205
+ "flex w-full gap-2 rounded-[10px] px-2.5 py-1.5 text-left",
17206
+ inCustomerBubble ? "bg-primary-foreground/15" : "bg-background/70"
17207
+ ),
17208
+ children: [
17209
+ /* @__PURE__ */ jsxRuntime.jsx(
17210
+ "span",
17211
+ {
17212
+ className: cn(
17213
+ "w-[3px] shrink-0 self-stretch rounded-full",
17214
+ inCustomerBubble ? "bg-primary-foreground/50" : quotedIsCustomer ? "bg-primary" : "bg-muted-foreground/50"
17215
+ )
17216
+ }
17217
+ ),
17218
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col", children: [
17219
+ /* @__PURE__ */ jsxRuntime.jsx(
17220
+ "span",
17221
+ {
17222
+ className: cn(
17223
+ "text-[11px] leading-4 font-semibold",
17224
+ inCustomerBubble ? "text-primary-foreground" : "text-foreground"
17225
+ ),
17226
+ children: quotedIsCustomer ? "You" : businessName
17227
+ }
17228
+ ),
17229
+ /* @__PURE__ */ jsxRuntime.jsx(
17230
+ "span",
17231
+ {
17232
+ className: cn(
17233
+ "truncate text-xs leading-4",
17234
+ inCustomerBubble ? "text-primary-foreground/75" : "text-muted-foreground"
17235
+ ),
17236
+ children: quote.preview
17237
+ }
17238
+ )
17239
+ ] })
17240
+ ]
17159
17241
  }
17160
- bubbles.push(message);
17242
+ );
17243
+ }
17244
+ var SWIPE_COMMIT_PX = 48;
17245
+ var SWIPE_ARM_PX = 10;
17246
+ var OVERDRAG_RATE = 0.25;
17247
+ var LONG_PRESS_MS = 450;
17248
+ var RETURN_TRANSITION = "transform 0.25s cubic-bezier(0.2, 0, 0, 1)";
17249
+ function swipeTravel(distance) {
17250
+ if (distance <= 0) return 0;
17251
+ if (distance <= SWIPE_COMMIT_PX) return distance;
17252
+ return SWIPE_COMMIT_PX + (distance - SWIPE_COMMIT_PX) * OVERDRAG_RATE;
17253
+ }
17254
+ function swipeCommitted(distance) {
17255
+ return distance >= SWIPE_COMMIT_PX;
17256
+ }
17257
+ function swipeProgress(distance) {
17258
+ return Math.min(Math.max(distance / SWIPE_COMMIT_PX, 0), 1);
17259
+ }
17260
+ function useSwipeReply({ onReply, onLongPress }) {
17261
+ const rowRef = React14.useRef(null);
17262
+ const glyphRef = React14.useRef(null);
17263
+ const gestureRef = React14.useRef(null);
17264
+ const suppressClickRef = React14.useRef(false);
17265
+ const optionsRef = React14.useRef({ onReply, onLongPress });
17266
+ optionsRef.current = { onReply, onLongPress };
17267
+ const settle = React14.useCallback((glyphVisible) => {
17268
+ const row = rowRef.current;
17269
+ const glyph = glyphRef.current;
17270
+ if (row) {
17271
+ row.style.transition = RETURN_TRANSITION;
17272
+ row.style.transform = "translateX(0px)";
17273
+ }
17274
+ if (glyph && !glyphVisible) {
17275
+ glyph.style.transition = "opacity 0.2s ease-out";
17276
+ glyph.style.opacity = "0";
17277
+ }
17278
+ }, []);
17279
+ const clearGesture = React14.useCallback(() => {
17280
+ const gesture = gestureRef.current;
17281
+ if (gesture?.longPressTimer) clearTimeout(gesture.longPressTimer);
17282
+ gestureRef.current = null;
17283
+ }, []);
17284
+ const onPointerDown = React14.useCallback((event) => {
17285
+ if (!event.isPrimary) return;
17286
+ suppressClickRef.current = false;
17287
+ gestureRef.current = {
17288
+ pointerId: event.pointerId,
17289
+ startX: event.clientX,
17290
+ startY: event.clientY,
17291
+ armed: false,
17292
+ distance: 0,
17293
+ longPressTimer: setTimeout(() => {
17294
+ gestureRef.current = null;
17295
+ suppressClickRef.current = true;
17296
+ optionsRef.current.onLongPress();
17297
+ }, LONG_PRESS_MS)
17298
+ };
17299
+ }, []);
17300
+ const onPointerMove = React14.useCallback((event) => {
17301
+ const gesture = gestureRef.current;
17302
+ if (!gesture || gesture.pointerId !== event.pointerId) return;
17303
+ const leftward = gesture.startX - event.clientX;
17304
+ const vertical = Math.abs(event.clientY - gesture.startY);
17305
+ if (!gesture.armed) {
17306
+ const scrolling = vertical > SWIPE_ARM_PX && vertical >= leftward;
17307
+ const rightward = leftward < -SWIPE_ARM_PX;
17308
+ if (scrolling || rightward) {
17309
+ clearGesture();
17310
+ return;
17311
+ }
17312
+ if (leftward <= SWIPE_ARM_PX || leftward <= vertical) return;
17313
+ if (prefersReducedMotion()) {
17314
+ clearGesture();
17315
+ return;
17316
+ }
17317
+ gesture.armed = true;
17318
+ if (gesture.longPressTimer) clearTimeout(gesture.longPressTimer);
17319
+ gesture.longPressTimer = null;
17320
+ event.currentTarget.setPointerCapture(event.pointerId);
17321
+ if (rowRef.current) rowRef.current.style.transition = "none";
17322
+ if (glyphRef.current) glyphRef.current.style.transition = "none";
17323
+ }
17324
+ gesture.distance = leftward;
17325
+ if (rowRef.current) {
17326
+ rowRef.current.style.transform = `translateX(-${swipeTravel(leftward)}px)`;
17327
+ }
17328
+ if (glyphRef.current) {
17329
+ glyphRef.current.style.opacity = String(swipeProgress(leftward));
17330
+ }
17331
+ }, [clearGesture]);
17332
+ const onPointerUp = React14.useCallback(
17333
+ (event) => {
17334
+ const gesture = gestureRef.current;
17335
+ if (!gesture || gesture.pointerId !== event.pointerId) return;
17336
+ const commit = gesture.armed && swipeCommitted(gesture.distance);
17337
+ const wasArmed = gesture.armed;
17338
+ clearGesture();
17339
+ if (wasArmed) {
17340
+ suppressClickRef.current = true;
17341
+ settle(false);
17342
+ }
17343
+ if (commit) optionsRef.current.onReply();
17344
+ },
17345
+ [clearGesture, settle]
17346
+ );
17347
+ const onPointerCancel = React14.useCallback(
17348
+ (event) => {
17349
+ const gesture = gestureRef.current;
17350
+ if (!gesture || gesture.pointerId !== event.pointerId) return;
17351
+ const wasArmed = gesture.armed;
17352
+ clearGesture();
17353
+ if (wasArmed) settle(false);
17354
+ },
17355
+ [clearGesture, settle]
17356
+ );
17357
+ const onClickCapture = React14.useCallback((event) => {
17358
+ if (!suppressClickRef.current) return;
17359
+ suppressClickRef.current = false;
17360
+ event.preventDefault();
17361
+ event.stopPropagation();
17362
+ }, []);
17363
+ return {
17364
+ rowRef,
17365
+ glyphRef,
17366
+ handlers: { onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onClickCapture }
17367
+ };
17368
+ }
17369
+ var PROSE_CLASS = "[&_p]:my-0 [&_p+p]:mt-1.5 [&_ul]:my-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:my-1 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:my-0.5 [&_h1]:text-[15px] [&_h1]:font-bold [&_h2]:text-[15px] [&_h2]:font-bold [&_h3]:text-sm [&_h3]:font-semibold [&_hr]:my-2 [&_hr]:border-border [&_table]:my-1 [&_table]:w-full [&_th]:pb-1 [&_th]:text-left [&_th]:text-[11px] [&_th]:font-semibold [&_th]:uppercase [&_th]:text-muted-foreground [&_td]:border-b [&_td]:border-border/50 [&_td]:py-1 [&_td:last-child]:text-right [&_td:last-child]:font-semibold [&_td:last-child]:tabular-nums [&_tr:last-child_td]:border-b-0 [&_a]:underline [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:text-[13px]";
17370
+ function bubbleShape(isCustomer, group) {
17371
+ if (!hasTail(group)) return "rounded-[18px]";
17372
+ return isCustomer ? "rounded-[18px] rounded-br-none" : "rounded-[18px] rounded-bl-none";
17373
+ }
17374
+ function hasTail(group) {
17375
+ return group === "single" || group === "last";
17376
+ }
17377
+ var TAIL_SWEEP = "M21 0 L21 5 C21 15 25 16 25 16 C20.93 16.43 16.84 14.94 13.96 11.96 C9 16 5 16 0 16";
17378
+ var TAIL_FILL = `${TAIL_SWEEP} Z`;
17379
+ var TAIL_NOTCH = "M25 16 C20.93 16.43 16.84 14.94 13.96 11.96 C9 16 5 16 0 16 Z";
17380
+ function BubbleTail({ isCustomer }) {
17381
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17382
+ "svg",
17383
+ {
17384
+ width: "25",
17385
+ height: "16",
17386
+ viewBox: "0 0 25 16",
17387
+ "aria-hidden": "true",
17388
+ className: cn(
17389
+ "absolute bottom-0",
17390
+ isCustomer ? "-right-[4px] fill-primary" : "-left-[4px] -scale-x-100 fill-background"
17391
+ ),
17392
+ children: [
17393
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: TAIL_FILL }),
17394
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: TAIL_NOTCH, style: { fill: "var(--color-chat-canvas, var(--color-muted, #f3f5f3))" } }),
17395
+ !isCustomer && /* @__PURE__ */ jsxRuntime.jsx("path", { d: TAIL_SWEEP, fill: "none", strokeWidth: "1", className: "stroke-foreground/[0.07]" })
17396
+ ]
17397
+ }
17398
+ );
17399
+ }
17400
+ function clockLabel(at) {
17401
+ const date = new Date(at);
17402
+ if (Number.isNaN(date.getTime())) return "";
17403
+ const hour = date.getHours() % 12 || 12;
17404
+ return `${hour}:${String(date.getMinutes()).padStart(2, "0")}`;
17405
+ }
17406
+ function DoubleTick() {
17407
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "15", height: "9", viewBox: "0 0 15 9", fill: "none", "aria-hidden": "true", children: [
17408
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M1 4.5L4 7.5L9.5 1.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }),
17409
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6.5 4.5L9.5 7.5L14 2.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" })
17410
+ ] });
17411
+ }
17412
+ function SendingClock() {
17413
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "11", height: "11", viewBox: "0 0 11 11", fill: "none", "aria-hidden": "true", children: [
17414
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "5.5", cy: "5.5", r: "4.5", stroke: "currentColor", strokeWidth: "1.2" }),
17415
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5.5 3v2.7l1.8 1", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
17416
+ ] });
17417
+ }
17418
+ function PaperclipIcon() {
17419
+ return /* @__PURE__ */ jsxRuntime.jsx(
17420
+ "svg",
17421
+ {
17422
+ width: "14",
17423
+ height: "14",
17424
+ viewBox: "0 0 24 24",
17425
+ fill: "none",
17426
+ stroke: "currentColor",
17427
+ strokeWidth: "2",
17428
+ strokeLinecap: "round",
17429
+ strokeLinejoin: "round",
17430
+ "aria-hidden": "true",
17431
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" })
17432
+ }
17433
+ );
17434
+ }
17435
+ function ReplyGlyph() {
17436
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17437
+ "svg",
17438
+ {
17439
+ width: "18",
17440
+ height: "18",
17441
+ viewBox: "0 0 24 24",
17442
+ fill: "none",
17443
+ stroke: "currentColor",
17444
+ strokeWidth: "2.2",
17445
+ strokeLinecap: "round",
17446
+ strokeLinejoin: "round",
17447
+ "aria-hidden": "true",
17448
+ children: [
17449
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 17 4 12 9 7" }),
17450
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
17451
+ ]
17452
+ }
17453
+ );
17454
+ }
17455
+ var DELIVERY_LABELS = {
17456
+ sending: "Sending",
17457
+ delivered: "Delivered",
17458
+ seen: "Seen"
17459
+ };
17460
+ function BubbleMeta({
17461
+ at,
17462
+ isCustomer,
17463
+ delivery
17464
+ }) {
17465
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17466
+ "span",
17467
+ {
17468
+ className: cn(
17469
+ "ml-auto flex shrink-0 items-center gap-[3px] self-end pb-px whitespace-nowrap",
17470
+ isCustomer ? "text-primary-foreground/70" : "text-muted-foreground"
17471
+ ),
17472
+ children: [
17473
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10.5px] leading-[13px] font-medium", children: clockLabel(at) }),
17474
+ isCustomer && delivery && /* @__PURE__ */ jsxRuntime.jsx(
17475
+ "span",
17476
+ {
17477
+ role: "img",
17478
+ "aria-label": DELIVERY_LABELS[delivery],
17479
+ className: cn(
17480
+ "flex items-center text-primary-foreground",
17481
+ delivery !== "seen" && "opacity-55"
17482
+ ),
17483
+ children: delivery === "sending" ? /* @__PURE__ */ jsxRuntime.jsx(SendingClock, {}) : /* @__PURE__ */ jsxRuntime.jsx(DoubleTick, {})
17484
+ }
17485
+ )
17486
+ ]
17487
+ }
17488
+ );
17489
+ }
17490
+ function reactionPills(reactions) {
17491
+ const counts = /* @__PURE__ */ new Map();
17492
+ for (const reaction of reactions) {
17493
+ const emoji = reaction.content.trim();
17494
+ if (emoji) counts.set(emoji, (counts.get(emoji) ?? 0) + 1);
17161
17495
  }
17162
- return { bubbles, reactionsByTarget };
17496
+ return [...counts.entries()].map(([emoji, count]) => ({ emoji, count }));
17497
+ }
17498
+ var MessageBubble = React14.memo(function MessageBubble2({
17499
+ message,
17500
+ reactions,
17501
+ businessName,
17502
+ group = "single",
17503
+ delivery,
17504
+ onReply,
17505
+ onLongPress,
17506
+ avatar,
17507
+ overlay,
17508
+ renderCards,
17509
+ renderProgram,
17510
+ onQuoteMiss
17511
+ }) {
17512
+ const showAvatar = group === "single" || group === "last";
17513
+ const isCustomer = message.sender_type === "customer";
17514
+ const isBusiness = !isCustomer;
17515
+ const segments = React14.useMemo(
17516
+ () => isCustomer ? [{ kind: "prose", text: message.content }] : chunkXREIZ56K_js.splitGenUIContent(message.content),
17517
+ [isCustomer, message.content]
17518
+ );
17519
+ const drawable = React14.useMemo(
17520
+ () => segments.filter(
17521
+ (segment) => segment.kind === "prose" && segment.text.trim() !== "" || segment.kind === "program" && renderProgram != null
17522
+ ),
17523
+ // eslint-disable-next-line react-hooks/exhaustive-deps
17524
+ [segments, renderProgram != null]
17525
+ );
17526
+ const swipe = useSwipeReply({
17527
+ onReply: () => onReply(message),
17528
+ onLongPress: () => onLongPress?.(message)
17529
+ });
17530
+ const quote = message.reply_to ?? null;
17531
+ const pills = reactions ? reactionPills(reactions) : [];
17532
+ const cards = renderCards?.(message);
17533
+ const proseSegments = drawable.filter((segment) => segment.kind === "prose");
17534
+ const lastProseIndex = drawable.lastIndexOf(proseSegments[proseSegments.length - 1]);
17535
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: `chat-msg-${message.id}`, className: "relative rounded-xl", children: [
17536
+ /* @__PURE__ */ jsxRuntime.jsx(
17537
+ "span",
17538
+ {
17539
+ ref: swipe.glyphRef,
17540
+ "aria-hidden": "true",
17541
+ className: "absolute top-1/2 right-1 -translate-y-1/2 text-muted-foreground opacity-0",
17542
+ children: /* @__PURE__ */ jsxRuntime.jsx(ReplyGlyph, {})
17543
+ }
17544
+ ),
17545
+ overlay,
17546
+ /* @__PURE__ */ jsxRuntime.jsxs(
17547
+ "div",
17548
+ {
17549
+ ref: swipe.rowRef,
17550
+ ...swipe.handlers,
17551
+ onContextMenu: onLongPress ? (event) => {
17552
+ event.preventDefault();
17553
+ onLongPress(message);
17554
+ } : void 0,
17555
+ className: cn(
17556
+ "flex touch-pan-y flex-col gap-[3px] select-none [-webkit-touch-callout:none]",
17557
+ isCustomer ? "items-end" : "items-start"
17558
+ ),
17559
+ children: [
17560
+ quote && drawable[0]?.kind !== "prose" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[280px] rounded-[14px] border border-foreground/[0.07] bg-background p-1.5 shadow-[0_1px_1px_rgba(15,23,42,0.03)]", children: /* @__PURE__ */ jsxRuntime.jsx(QuoteBlock, { quote, inCustomerBubble: false, businessName, onMissing: onQuoteMiss }) }),
17561
+ message.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex max-w-[280px] flex-wrap gap-1.5", children: message.attachments.map((attachment, index) => {
17562
+ const url = safeAttachmentUrl(attachment.url);
17563
+ if (!url) return null;
17564
+ return isImageAttachment(attachment) ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: url, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsxRuntime.jsx(
17565
+ "img",
17566
+ {
17567
+ src: url,
17568
+ alt: attachment.filename ?? "Photo",
17569
+ loading: "lazy",
17570
+ className: "max-h-[240px] max-w-[240px] rounded-[16px] object-cover"
17571
+ }
17572
+ ) }, index) : /* @__PURE__ */ jsxRuntime.jsxs(
17573
+ "a",
17574
+ {
17575
+ href: url,
17576
+ target: "_blank",
17577
+ rel: "noreferrer",
17578
+ className: "flex items-center gap-2 rounded-[14px] border border-foreground/[0.07] bg-background px-3 py-2 text-[13px] font-medium text-foreground shadow-[0_1px_1px_rgba(15,23,42,0.03)]",
17579
+ children: [
17580
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(PaperclipIcon, {}) }),
17581
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-[200px] truncate", children: attachment.filename ?? "Attachment" })
17582
+ ]
17583
+ },
17584
+ index
17585
+ );
17586
+ }) }),
17587
+ drawable.map(
17588
+ (segment, index) => segment.kind === "program" ? /* @__PURE__ */ jsxRuntime.jsx(React14__default.default.Fragment, { children: renderProgram?.(segment.source) }, index) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex max-w-full items-end gap-1.5", children: [
17589
+ isBusiness && avatar && /* @__PURE__ */ jsxRuntime.jsx(
17590
+ "span",
17591
+ {
17592
+ "aria-label": businessName,
17593
+ className: cn(
17594
+ "shrink-0",
17595
+ // One avatar per run, anchored to its last bubble.
17596
+ showAvatar && index === lastProseIndex ? "visible" : "invisible"
17597
+ ),
17598
+ children: avatar
17599
+ }
17600
+ ),
17601
+ /* @__PURE__ */ jsxRuntime.jsxs(
17602
+ "div",
17603
+ {
17604
+ className: cn(
17605
+ "relative flex max-w-[280px] min-w-0 flex-col gap-1.5 py-2 pr-[11px] pl-3.5 text-[15px] leading-[21px] font-[450] break-words",
17606
+ bubbleShape(isCustomer, index === lastProseIndex ? group : "middle"),
17607
+ isCustomer ? "bg-primary text-primary-foreground" : "border border-foreground/[0.07] bg-background text-foreground shadow-[0_1px_1px_rgba(15,23,42,0.03)]"
17608
+ ),
17609
+ children: [
17610
+ index === 0 && quote && /* @__PURE__ */ jsxRuntime.jsx(QuoteBlock, { quote, inCustomerBubble: isCustomer, businessName, onMissing: onQuoteMiss }),
17611
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-wrap items-end gap-x-2 gap-y-0.5", children: [
17612
+ isCustomer ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-pre-wrap", children: segment.text }) : /* @__PURE__ */ jsxRuntime.jsx(ChatMarkdown, { text: segment.text, className: cn("min-w-0", PROSE_CLASS) }),
17613
+ index === lastProseIndex && /* @__PURE__ */ jsxRuntime.jsx(BubbleMeta, { at: message.created_at, isCustomer, delivery })
17614
+ ] }),
17615
+ index === lastProseIndex && hasTail(group) && /* @__PURE__ */ jsxRuntime.jsx(BubbleTail, { isCustomer })
17616
+ ]
17617
+ }
17618
+ )
17619
+ ] }, index)
17620
+ ),
17621
+ cards,
17622
+ pills.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
17623
+ "div",
17624
+ {
17625
+ className: cn(
17626
+ "z-[1] -mt-3 flex gap-1",
17627
+ // Overlap the bubble corner by ~9px, tucked inside the tail side.
17628
+ isCustomer ? "mr-2.5" : "ml-10"
17629
+ ),
17630
+ children: pills.map((pill) => /* @__PURE__ */ jsxRuntime.jsxs(
17631
+ "span",
17632
+ {
17633
+ className: "flex items-center gap-[3px] rounded-full border border-foreground/[0.08] bg-background px-[7px] py-[2px] shadow-[0_1px_3px_rgba(15,23,42,0.08)]",
17634
+ children: [
17635
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs leading-4", children: pill.emoji }),
17636
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] leading-[13px] font-semibold text-muted-foreground", children: pill.count })
17637
+ ]
17638
+ },
17639
+ pill.emoji
17640
+ ))
17641
+ }
17642
+ )
17643
+ ]
17644
+ }
17645
+ )
17646
+ ] });
17647
+ });
17648
+ function Seam({
17649
+ label,
17650
+ variant = "line"
17651
+ }) {
17652
+ if (variant === "chip") {
17653
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center py-1.5", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full border border-foreground/[0.06] bg-background px-3 py-1 text-[11px] leading-[14px] font-semibold tracking-[0.04em] text-muted-foreground uppercase shadow-[0_1px_2px_rgba(15,23,42,0.04)]", children: label }) });
17654
+ }
17655
+ const unread = variant === "unread";
17656
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 py-1.5", children: [
17657
+ /* @__PURE__ */ jsxRuntime.jsx(
17658
+ "span",
17659
+ {
17660
+ className: cn("h-px flex-1", unread ? "bg-primary/35" : "bg-foreground/[0.08]"),
17661
+ "aria-hidden": "true"
17662
+ }
17663
+ ),
17664
+ /* @__PURE__ */ jsxRuntime.jsx(
17665
+ "span",
17666
+ {
17667
+ className: cn(
17668
+ "whitespace-nowrap",
17669
+ unread ? "text-[11px] leading-[14px] font-semibold tracking-[0.04em] text-primary uppercase" : "text-[11.5px] leading-[15px] font-medium text-muted-foreground"
17670
+ ),
17671
+ children: label
17672
+ }
17673
+ ),
17674
+ /* @__PURE__ */ jsxRuntime.jsx(
17675
+ "span",
17676
+ {
17677
+ className: cn("h-px flex-1", unread ? "bg-primary/35" : "bg-foreground/[0.08]"),
17678
+ "aria-hidden": "true"
17679
+ }
17680
+ )
17681
+ ] });
17682
+ }
17683
+
17684
+ // src/react/chat-kit/timeline.ts
17685
+ function sameDay(a, b) {
17686
+ return new Date(a).toDateString() === new Date(b).toDateString();
17687
+ }
17688
+ function dayLabel(at) {
17689
+ const date = new Date(at);
17690
+ const today = /* @__PURE__ */ new Date();
17691
+ if (date.toDateString() === today.toDateString()) return "Today";
17692
+ const yesterday = new Date(today);
17693
+ yesterday.setDate(today.getDate() - 1);
17694
+ if (date.toDateString() === yesterday.toDateString()) return "Yesterday";
17695
+ return date.toLocaleDateString([], { weekday: "short", day: "numeric", month: "short" });
17696
+ }
17697
+ function proseOf(content) {
17698
+ return chunkXREIZ56K_js.splitGenUIContent(content).filter((segment) => segment.kind === "prose").map((segment) => segment.text).join("\n\n").trim();
17699
+ }
17700
+ function hasProgram(content) {
17701
+ return chunkXREIZ56K_js.splitGenUIContent(content).some((segment) => segment.kind === "program");
17702
+ }
17703
+ function chatTimeline(messages, options = {}) {
17704
+ const { readAt = null, rendersPrograms = false } = options;
17705
+ const partitioned = partitionMessages(messages);
17706
+ const bubbles = partitioned.bubbles.filter(
17707
+ (message) => message.sender_type === "system" || message.attachments.length > 0 || chunkQPRI2NII_js.chatMessageCards(message).length > 0 || rendersPrograms && hasProgram(message.content) || (message.sender_type === "customer" ? message.content.trim() !== "" : proseOf(message.content) !== "")
17708
+ );
17709
+ let firstUnreadId;
17710
+ let unreadOnOpen = 0;
17711
+ if (readAt) {
17712
+ const readAtMs = new Date(readAt).getTime();
17713
+ for (const message of bubbles) {
17714
+ if (message.sender_type === "customer" || new Date(message.created_at).getTime() <= readAtMs)
17715
+ continue;
17716
+ firstUnreadId ?? (firstUnreadId = message.id);
17717
+ unreadOnOpen += 1;
17718
+ }
17719
+ }
17720
+ return {
17721
+ bubbles,
17722
+ reactionsByTarget: partitioned.reactionsByTarget,
17723
+ firstUnreadId,
17724
+ unreadOnOpen,
17725
+ firstAgentMessageId: bubbles.find((m) => m.sender_type === "agent")?.id
17726
+ };
17727
+ }
17728
+ function groupPosition(timeline, index) {
17729
+ const { bubbles, firstAgentMessageId, firstUnreadId } = timeline;
17730
+ const message = bubbles[index];
17731
+ const previous = bubbles[index - 1];
17732
+ const next = bubbles[index + 1];
17733
+ const startsRun = !previous || !sameDay(previous.created_at, message.created_at) || previous.sender_type !== message.sender_type || message.id === firstAgentMessageId || message.id === firstUnreadId || previous.sender_type === "system";
17734
+ const endsRun = !next || next.sender_type !== message.sender_type || next.sender_type === "system" || !sameDay(message.created_at, next.created_at);
17735
+ if (startsRun && endsRun) return "single";
17736
+ if (startsRun) return "first";
17737
+ if (endsRun) return "last";
17738
+ return "middle";
17739
+ }
17740
+ function deliveryState(message, timeline, seenAt, isOptimistic2) {
17741
+ if (message.sender_type !== "customer") return void 0;
17742
+ if (isOptimistic2(message)) return "sending";
17743
+ const seenCutoff = timeline.firstAgentMessageId ? seenAt : null;
17744
+ return seenCutoff && message.created_at <= seenCutoff ? "seen" : "delivered";
17163
17745
  }
17164
17746
  var MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
17747
+ var KEEP_DISMISSED_KEY = "cimplify_keep_chat_dismissed";
17748
+ var AT_BOTTOM_SLACK_PX = 48;
17165
17749
  function ChatWidget({
17166
17750
  client: clientProp,
17167
17751
  businessName = "Support",
@@ -17180,6 +17764,7 @@ function ChatWidget({
17180
17764
  const [open, setOpen] = React14.useState(defaultOpen);
17181
17765
  const [readAt, setReadAt] = React14.useState(null);
17182
17766
  const [attachError, setAttachError] = React14.useState(null);
17767
+ const [replyTo, setReplyTo] = React14.useState(null);
17183
17768
  const {
17184
17769
  messages,
17185
17770
  isLoading,
@@ -17188,11 +17773,15 @@ function ChatWidget({
17188
17773
  seenAt,
17189
17774
  error,
17190
17775
  isActive,
17776
+ humanRequested,
17191
17777
  claimedAs,
17192
17778
  signOut,
17193
17779
  send,
17194
17780
  react,
17781
+ requestHuman,
17195
17782
  startConversation,
17783
+ loadOlder,
17784
+ mayHaveOlder,
17196
17785
  openIfExisting
17197
17786
  } = useChat({ client, pollInterval });
17198
17787
  const showTyping = isSending || awaitingReply;
@@ -17227,9 +17816,11 @@ function ChatWidget({
17227
17816
  const handleSend = React14.useCallback(
17228
17817
  (content) => {
17229
17818
  setAttachError(null);
17230
- return send(content);
17819
+ const target = replyTo;
17820
+ setReplyTo(null);
17821
+ return send(content, target ? { replyTo: target } : void 0);
17231
17822
  },
17232
- [send]
17823
+ [send, replyTo]
17233
17824
  );
17234
17825
  const unread = open ? 0 : messages.filter(
17235
17826
  (m) => m.sender_type !== "customer" && (!readAt || m.created_at > readAt)
@@ -17268,6 +17859,7 @@ function ChatWidget({
17268
17859
  open && /* @__PURE__ */ jsxRuntime.jsx(
17269
17860
  Panel,
17270
17861
  {
17862
+ client,
17271
17863
  businessName,
17272
17864
  greeting,
17273
17865
  subtitle,
@@ -17278,14 +17870,22 @@ function ChatWidget({
17278
17870
  isSending,
17279
17871
  isTyping: showTyping,
17280
17872
  seenAt,
17873
+ readAt,
17281
17874
  error: error ?? attachError,
17282
17875
  isActive,
17283
17876
  isLeft,
17877
+ humanRequested,
17878
+ mayHaveOlder,
17879
+ replyTo,
17284
17880
  claimedAs,
17285
17881
  onSignOut: () => void signOut(),
17286
17882
  onClose: handleClose,
17287
17883
  onSend: handleSend,
17288
17884
  onReact: react,
17885
+ onReply: setReplyTo,
17886
+ onCancelReply: () => setReplyTo(null),
17887
+ onRequestHuman: () => void requestHuman(),
17888
+ onLoadOlder: () => void loadOlder(),
17289
17889
  onAttach: attachPhoto,
17290
17890
  onStartConversation: startConversation,
17291
17891
  classNames
@@ -17320,7 +17920,7 @@ function Bubble({
17320
17920
  {
17321
17921
  className: cn(
17322
17922
  "absolute -right-0.5 -top-0.5 flex h-5 w-5 items-center justify-center",
17323
- "rounded-full bg-red-500 text-[10px] font-bold text-white",
17923
+ "rounded-full bg-destructive text-[10px] font-bold text-white",
17324
17924
  "ring-2 ring-background"
17325
17925
  ),
17326
17926
  children: unread > 9 ? "9+" : unread
@@ -17331,6 +17931,7 @@ function Bubble({
17331
17931
  );
17332
17932
  }
17333
17933
  function Panel({
17934
+ client,
17334
17935
  businessName,
17335
17936
  greeting,
17336
17937
  subtitle,
@@ -17341,14 +17942,22 @@ function Panel({
17341
17942
  isSending,
17342
17943
  isTyping,
17343
17944
  seenAt,
17945
+ readAt,
17344
17946
  error,
17345
17947
  isActive,
17346
17948
  isLeft,
17949
+ humanRequested,
17950
+ mayHaveOlder,
17951
+ replyTo,
17347
17952
  claimedAs,
17348
17953
  onSignOut,
17349
17954
  onClose,
17350
17955
  onSend,
17351
17956
  onReact,
17957
+ onReply,
17958
+ onCancelReply,
17959
+ onRequestHuman,
17960
+ onLoadOlder,
17352
17961
  onAttach,
17353
17962
  onStartConversation,
17354
17963
  classNames
@@ -17388,20 +17997,57 @@ function Panel({
17388
17997
  MessageList,
17389
17998
  {
17390
17999
  messages,
18000
+ businessName,
17391
18001
  isLoading,
17392
18002
  isSending: isTyping,
17393
18003
  seenAt,
18004
+ readAt,
18005
+ humanRequested,
18006
+ mayHaveOlder,
17394
18007
  onReact,
18008
+ onReply,
18009
+ onRequestHuman,
18010
+ onLoadOlder,
17395
18011
  className: classNames?.messages
17396
18012
  }
17397
18013
  ),
17398
- error != null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-red-200 bg-red-50 px-4 py-2 text-xs text-red-600", children: error.userMessage || "Something went wrong. Please try again." }),
18014
+ isActive && !claimedAs && client && /* @__PURE__ */ jsxRuntime.jsx(KeepConversation, { client }),
18015
+ humanRequested && isActive && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-t border-border bg-muted/50 px-4 py-2 text-[11px] text-muted-foreground", children: [
18016
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-1.5 w-1.5 flex-shrink-0 animate-pulse rounded-full bg-amber-500" }),
18017
+ "Waiting for ",
18018
+ businessName,
18019
+ " \u2014 usually replies in minutes"
18020
+ ] }),
18021
+ error != null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-destructive/20 bg-destructive/10 px-4 py-2 text-xs text-destructive", children: error.userMessage || "Something went wrong. Please try again." }),
18022
+ replyTo && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 border-t border-border bg-muted/50 px-4 py-2", children: [
18023
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-[3px] shrink-0 self-stretch rounded-full bg-foreground/40" }),
18024
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
18025
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-[11px] font-semibold text-foreground", children: [
18026
+ "Replying to ",
18027
+ replyTo.sender_type === "customer" ? "yourself" : businessName
18028
+ ] }),
18029
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: proseOf(replyTo.content) || "Photo" })
18030
+ ] }),
18031
+ /* @__PURE__ */ jsxRuntime.jsx(
18032
+ "button",
18033
+ {
18034
+ type: "button",
18035
+ "aria-label": "Cancel reply",
18036
+ onClick: onCancelReply,
18037
+ className: "flex-shrink-0 p-0.5 text-muted-foreground hover:text-foreground",
18038
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { className: "h-3.5 w-3.5" })
18039
+ }
18040
+ )
18041
+ ] }),
17399
18042
  /* @__PURE__ */ jsxRuntime.jsx(
17400
18043
  ChatInput,
17401
18044
  {
17402
18045
  placeholder,
17403
18046
  isSending,
17404
- onSend: isActive ? onSend : onStartConversation,
18047
+ onSend: isActive ? onSend : async (text) => {
18048
+ await onStartConversation(text);
18049
+ return true;
18050
+ },
17405
18051
  onAttach,
17406
18052
  className: classNames?.input
17407
18053
  }
@@ -17430,10 +18076,10 @@ function Header({
17430
18076
  className
17431
18077
  ),
17432
18078
  children: [
17433
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-white/15", children: /* @__PURE__ */ jsxRuntime.jsx(ChatIcon, { className: "h-5 w-5" }) }),
18079
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-background/15", children: /* @__PURE__ */ jsxRuntime.jsx(ChatIcon, { className: "h-5 w-5" }) }),
17434
18080
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
17435
18081
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
17436
- claimedAs ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-white/60", children: [
18082
+ claimedAs ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-background/60", children: [
17437
18083
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate", children: [
17438
18084
  "Chatting as ",
17439
18085
  claimedAs
@@ -17444,11 +18090,11 @@ function Header({
17444
18090
  {
17445
18091
  type: "button",
17446
18092
  onClick: onSignOut,
17447
- className: "flex-shrink-0 underline underline-offset-2 hover:text-white",
18093
+ className: "flex-shrink-0 underline underline-offset-2 hover:text-background",
17448
18094
  children: "Not you?"
17449
18095
  }
17450
18096
  )
17451
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-white/60", children: [
18097
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-background/60", children: [
17452
18098
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
17453
18099
  "Online"
17454
18100
  ] })
@@ -17461,7 +18107,7 @@ function Header({
17461
18107
  "aria-label": "Close chat",
17462
18108
  className: cn(
17463
18109
  "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg",
17464
- "bg-white/10 text-white/70 transition-colors hover:bg-white/20 hover:text-white"
18110
+ "bg-background/10 text-background/70 transition-colors hover:bg-background/20 hover:text-background"
17465
18111
  ),
17466
18112
  children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { className: "h-[18px] w-[18px]" })
17467
18113
  }
@@ -17516,148 +18162,109 @@ function Welcome({
17516
18162
  );
17517
18163
  }
17518
18164
  var QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F"];
17519
- function lastSeenCustomerMessageId(bubbles, seenAt) {
17520
- if (!seenAt || !bubbles.some((m) => m.sender_type === "agent")) return null;
17521
- for (let i = bubbles.length - 1; i >= 0; i--) {
17522
- if (bubbles[i].sender_type !== "customer") continue;
17523
- return bubbles[i].created_at <= seenAt ? bubbles[i].id : null;
17524
- }
17525
- return null;
18165
+ function LetterAvatar({ name }) {
18166
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex size-6 items-center justify-center rounded-full bg-foreground text-[10px] font-bold text-background", children: name.charAt(0).toUpperCase() });
17526
18167
  }
17527
18168
  function MessageList({
17528
18169
  messages,
18170
+ businessName,
17529
18171
  isLoading,
17530
18172
  isSending,
17531
18173
  seenAt,
18174
+ readAt,
18175
+ humanRequested,
18176
+ mayHaveOlder,
17532
18177
  onReact,
18178
+ onReply,
18179
+ onRequestHuman,
18180
+ onLoadOlder,
17533
18181
  className
17534
18182
  }) {
17535
- const { bubbles, reactionsByTarget } = partitionMessages(messages);
17536
- const seenMessageId = lastSeenCustomerMessageId(bubbles, seenAt);
18183
+ const scrolledUpRef = React14.useRef(false);
18184
+ const timeline = chatTimeline(messages, { readAt });
18185
+ const { bubbles, reactionsByTarget, firstUnreadId, unreadOnOpen, firstAgentMessageId } = timeline;
18186
+ const lastBubble = bubbles[bubbles.length - 1];
18187
+ const offerHuman = !humanRequested && lastBubble?.sender_type === "bot" && !isSending;
18188
+ const handleScroll = (event) => {
18189
+ const node = event.currentTarget;
18190
+ scrolledUpRef.current = node.scrollHeight - node.scrollTop - node.clientHeight >= AT_BOTTOM_SLACK_PX;
18191
+ };
17537
18192
  return /* @__PURE__ */ jsxRuntime.jsxs(
17538
18193
  "div",
17539
18194
  {
17540
- className: cn(
17541
- "flex flex-1 flex-col gap-1 overflow-y-auto bg-muted/50 px-5 py-5",
17542
- className
17543
- ),
18195
+ onScroll: handleScroll,
18196
+ style: { background: "var(--color-chat-canvas, var(--color-muted, #f3f5f3))" },
18197
+ className: cn("flex flex-1 flex-col gap-0.5 overflow-y-auto px-3.5 py-3.5", className),
17544
18198
  children: [
17545
18199
  isLoading && messages.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(TypingDots, {}) }),
18200
+ mayHaveOlder && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center pb-1", children: /* @__PURE__ */ jsxRuntime.jsx(
18201
+ "button",
18202
+ {
18203
+ type: "button",
18204
+ onClick: onLoadOlder,
18205
+ className: "rounded-full border border-foreground/[0.06] bg-background px-3.5 py-[7px] text-xs font-semibold text-muted-foreground shadow-[0_1px_2px_rgba(15,23,42,0.04)]",
18206
+ children: "Show earlier messages"
18207
+ }
18208
+ ) }),
17546
18209
  bubbles.map((msg, i) => {
17547
- const isCustomer = msg.sender_type === "customer";
17548
18210
  const prev = bubbles[i - 1];
17549
- const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
17550
- const isOptimistic = msg.id.startsWith("opt_");
17551
- const reactions = reactionsByTarget.get(msg.id);
17552
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
17553
- !sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3" }),
17554
- /* @__PURE__ */ jsxRuntime.jsxs(
17555
- "div",
17556
- {
17557
- className: cn(
17558
- "group relative flex max-w-[85%]",
17559
- isCustomer ? "ml-auto" : "mr-auto"
18211
+ const newDay = !prev || !sameDay(prev.created_at, msg.created_at);
18212
+ const group = groupPosition(timeline, i);
18213
+ const startsRun = group === "single" || group === "first";
18214
+ const delivery = deliveryState(msg, timeline, seenAt, isOptimistic);
18215
+ return /* @__PURE__ */ jsxRuntime.jsxs(
18216
+ "div",
18217
+ {
18218
+ className: cn("flex flex-col gap-1.5", startsRun && i > 0 && "mt-2.5"),
18219
+ children: [
18220
+ newDay && /* @__PURE__ */ jsxRuntime.jsx(Seam, { variant: "chip", label: dayLabel(msg.created_at) }),
18221
+ msg.id === firstUnreadId && /* @__PURE__ */ jsxRuntime.jsx(
18222
+ Seam,
18223
+ {
18224
+ variant: "unread",
18225
+ label: `${unreadOnOpen} new message${unreadOnOpen === 1 ? "" : "s"}`
18226
+ }
17560
18227
  ),
17561
- children: [
17562
- !isCustomer && !isOptimistic && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute -top-3 right-0 z-10 hidden gap-0.5 rounded-full border border-border bg-background px-1 py-0.5 shadow-sm group-hover:flex", children: QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsxRuntime.jsx(
17563
- "button",
17564
- {
17565
- type: "button",
17566
- "aria-label": `React ${emoji}`,
17567
- onClick: () => void onReact(msg, emoji),
17568
- className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
17569
- children: emoji
17570
- },
17571
- emoji
17572
- )) }),
17573
- /* @__PURE__ */ jsxRuntime.jsxs(
17574
- "div",
18228
+ msg.id === firstAgentMessageId && /* @__PURE__ */ jsxRuntime.jsx(Seam, { label: "A person joined" }),
18229
+ msg.sender_type === "system" ? /* @__PURE__ */ jsxRuntime.jsx(Seam, { label: "Passing you to the team" }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "group relative", children: [
18230
+ msg.sender_type !== "customer" && !isOptimistic(msg) && /* @__PURE__ */ jsxRuntime.jsx(HoverRail, { message: msg, onReact, onReply }),
18231
+ /* @__PURE__ */ jsxRuntime.jsx(
18232
+ MessageBubble,
17575
18233
  {
17576
- className: cn(
17577
- "rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
17578
- isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
17579
- isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
17580
- isOptimistic && "opacity-70"
17581
- ),
17582
- children: [
17583
- !isCustomer && !sameSenderAsPrev && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mb-1 inline-flex items-center gap-1 rounded bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold text-violet-600", children: [
17584
- /* @__PURE__ */ jsxRuntime.jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
17585
- "AI"
17586
- ] }),
17587
- msg.reply_to && /* @__PURE__ */ jsxRuntime.jsxs(
17588
- "div",
17589
- {
17590
- className: cn(
17591
- "mb-1.5 flex gap-1.5 rounded-lg px-2 py-1 text-xs",
17592
- isCustomer ? "bg-background/15" : "bg-muted"
17593
- ),
17594
- children: [
17595
- /* @__PURE__ */ jsxRuntime.jsx(
17596
- "span",
17597
- {
17598
- className: cn(
17599
- "w-[3px] shrink-0 self-stretch rounded-full",
17600
- isCustomer ? "bg-background/40" : "bg-muted-foreground/40"
17601
- )
17602
- }
17603
- ),
17604
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate opacity-80", children: msg.reply_to.preview })
17605
- ]
17606
- }
17607
- ),
17608
- msg.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-wrap gap-1.5", msg.content && "mb-1.5"), children: msg.attachments.map((attachment, index) => {
17609
- const url = safeAttachmentUrl(attachment.url);
17610
- if (!url) return null;
17611
- return attachment.mime_type?.startsWith("image/") ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: url, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsxRuntime.jsx(
17612
- "img",
17613
- {
17614
- src: url,
17615
- alt: attachment.filename ?? "Photo",
17616
- loading: "lazy",
17617
- className: "max-h-48 max-w-full rounded-lg object-cover"
17618
- }
17619
- ) }, index) : /* @__PURE__ */ jsxRuntime.jsx(
17620
- "a",
17621
- {
17622
- href: url,
17623
- target: "_blank",
17624
- rel: "noreferrer",
17625
- className: "truncate text-xs underline",
17626
- children: attachment.filename ?? "Attachment"
17627
- },
17628
- index
17629
- );
17630
- }) }),
17631
- isCustomer ? msg.content && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-pre-wrap", children: msg.content }) : /* @__PURE__ */ jsxRuntime.jsx(
17632
- ChatMarkdown,
17633
- {
17634
- text: msg.content,
17635
- className: "[&_p]:my-0 [&_p+p]:mt-1.5 [&_ul]:my-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:my-1 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:my-0.5 [&_h1]:text-sm [&_h1]:font-bold [&_h2]:text-sm [&_h2]:font-bold [&_h3]:text-sm [&_h3]:font-semibold [&_hr]:my-2 [&_table]:my-1 [&_table]:w-full [&_th]:pb-1 [&_th]:text-left [&_th]:text-[10px] [&_th]:font-semibold [&_th]:uppercase [&_td]:py-1 [&_td:last-child]:text-right [&_td:last-child]:font-semibold [&_td:last-child]:tabular-nums [&_a]:underline"
17636
- }
17637
- )
17638
- ]
18234
+ message: msg,
18235
+ reactions: reactionsByTarget.get(msg.id),
18236
+ businessName,
18237
+ group,
18238
+ delivery,
18239
+ onReply,
18240
+ avatar: /* @__PURE__ */ jsxRuntime.jsx(LetterAvatar, { name: businessName }),
18241
+ onQuoteMiss: async () => {
18242
+ onLoadOlder();
18243
+ return true;
18244
+ }
17639
18245
  }
17640
18246
  )
17641
- ]
17642
- }
17643
- ),
17644
- reactions && reactions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("mt-0.5 flex gap-1", isCustomer ? "justify-end" : "justify-start"), children: reactions.map((reaction) => /* @__PURE__ */ jsxRuntime.jsx(
17645
- "span",
17646
- {
17647
- className: "rounded-full border border-border bg-background px-1.5 py-0.5 text-xs shadow-sm",
17648
- children: reaction.content
17649
- },
17650
- reaction.id
17651
- )) }),
17652
- msg.id === seenMessageId && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 text-right text-[10px] font-medium text-muted-foreground", children: "Seen" })
17653
- ] }, msg.id);
18247
+ ] })
18248
+ ]
18249
+ },
18250
+ msg.id
18251
+ );
17654
18252
  }),
17655
18253
  isSending && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsxRuntime.jsx(TypingDots, {}) }),
18254
+ offerHuman && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
18255
+ "button",
18256
+ {
18257
+ type: "button",
18258
+ onClick: onRequestHuman,
18259
+ className: "rounded-full border border-border bg-background px-3 py-1.5 text-xs font-semibold text-foreground hover:border-foreground",
18260
+ children: "Talk to a person"
18261
+ }
18262
+ ) }),
17656
18263
  /* @__PURE__ */ jsxRuntime.jsx(
17657
18264
  "div",
17658
18265
  {
17659
18266
  ref: (node) => {
17660
- node?.scrollIntoView({ behavior: "smooth" });
18267
+ if (node && !scrolledUpRef.current) node.scrollIntoView({ behavior: "smooth" });
17661
18268
  }
17662
18269
  },
17663
18270
  `${messages.length}:${isSending}`
@@ -17666,6 +18273,172 @@ function MessageList({
17666
18273
  }
17667
18274
  );
17668
18275
  }
18276
+ function HoverRail({
18277
+ message,
18278
+ onReact,
18279
+ onReply
18280
+ }) {
18281
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute -top-3 right-0 z-10 hidden gap-0.5 rounded-full border border-border bg-background px-1 py-0.5 shadow-sm group-hover:flex", children: [
18282
+ QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsxRuntime.jsx(
18283
+ "button",
18284
+ {
18285
+ type: "button",
18286
+ "aria-label": `React ${emoji}`,
18287
+ onClick: () => void onReact(message, emoji),
18288
+ className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
18289
+ children: emoji
18290
+ },
18291
+ emoji
18292
+ )),
18293
+ /* @__PURE__ */ jsxRuntime.jsx(
18294
+ "button",
18295
+ {
18296
+ type: "button",
18297
+ "aria-label": "Reply",
18298
+ onClick: () => onReply(message),
18299
+ className: "flex h-6 w-6 items-center justify-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground",
18300
+ children: /* @__PURE__ */ jsxRuntime.jsx(ReplyIcon, { className: "h-3.5 w-3.5" })
18301
+ }
18302
+ )
18303
+ ] });
18304
+ }
18305
+ function readKeepDismissed() {
18306
+ try {
18307
+ return typeof window !== "undefined" && window.localStorage.getItem(KEEP_DISMISSED_KEY) === "1";
18308
+ } catch {
18309
+ return false;
18310
+ }
18311
+ }
18312
+ function writeKeepDismissed() {
18313
+ try {
18314
+ window.localStorage.setItem(KEEP_DISMISSED_KEY, "1");
18315
+ } catch {
18316
+ }
18317
+ }
18318
+ function KeepConversation({ client }) {
18319
+ const [step, setStep] = React14.useState("row");
18320
+ const [dismissed, setDismissed] = React14.useState(readKeepDismissed);
18321
+ const [contact, setContact] = React14.useState("");
18322
+ const [code, setCode] = React14.useState("");
18323
+ const [busy, setBusy] = React14.useState(false);
18324
+ const [verifyError, setVerifyError] = React14.useState(null);
18325
+ if (dismissed) return null;
18326
+ const contactType = contact.includes("@") ? "email" : "phone";
18327
+ const requestCode = async () => {
18328
+ const trimmed = contact.trim();
18329
+ if (!trimmed || busy) return;
18330
+ setBusy(true);
18331
+ setVerifyError(null);
18332
+ const result = await client.link.requestOtp({ contact: trimmed, contact_type: contactType });
18333
+ setBusy(false);
18334
+ if (result.ok) {
18335
+ setCode("");
18336
+ setStep("code");
18337
+ } else {
18338
+ setVerifyError(result.error.userMessage || "Couldn't send a code \u2014 try again.");
18339
+ }
18340
+ };
18341
+ const verify = async (value) => {
18342
+ if (value.length !== 6 || busy) return;
18343
+ setBusy(true);
18344
+ setVerifyError(null);
18345
+ const result = await client.link.verifyOtp({
18346
+ contact: contact.trim(),
18347
+ contact_type: contactType,
18348
+ otp_code: value
18349
+ });
18350
+ setBusy(false);
18351
+ if (!result.ok) {
18352
+ setCode("");
18353
+ setVerifyError(result.error.userMessage || "Invalid or expired code.");
18354
+ }
18355
+ };
18356
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 border-t border-border bg-muted/50 px-4 py-2.5", children: [
18357
+ step === "row" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
18358
+ /* @__PURE__ */ jsxRuntime.jsxs(
18359
+ "button",
18360
+ {
18361
+ type: "button",
18362
+ onClick: () => setStep("contact"),
18363
+ className: "flex min-w-0 flex-1 flex-col text-left",
18364
+ children: [
18365
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-foreground", children: "Keep this conversation" }),
18366
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: "Verify your number and it follows you \u2014 any device" })
18367
+ ]
18368
+ }
18369
+ ),
18370
+ /* @__PURE__ */ jsxRuntime.jsx(
18371
+ "button",
18372
+ {
18373
+ type: "button",
18374
+ "aria-label": "Dismiss",
18375
+ onClick: () => {
18376
+ writeKeepDismissed();
18377
+ setDismissed(true);
18378
+ },
18379
+ className: "flex-shrink-0 p-1 text-muted-foreground hover:text-foreground",
18380
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { className: "h-3.5 w-3.5" })
18381
+ }
18382
+ )
18383
+ ] }),
18384
+ step === "contact" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
18385
+ /* @__PURE__ */ jsxRuntime.jsx(
18386
+ "input",
18387
+ {
18388
+ type: "text",
18389
+ inputMode: "email",
18390
+ value: contact,
18391
+ onChange: (e) => setContact(e.target.value),
18392
+ onKeyDown: (e) => e.key === "Enter" && void requestCode(),
18393
+ placeholder: "Phone or email",
18394
+ autoFocus: true,
18395
+ className: "h-9 min-w-0 flex-1 rounded-lg border border-border bg-background px-3 text-xs text-foreground outline-none placeholder:text-muted-foreground focus:border-foreground"
18396
+ }
18397
+ ),
18398
+ /* @__PURE__ */ jsxRuntime.jsx(
18399
+ "button",
18400
+ {
18401
+ type: "button",
18402
+ onClick: () => void requestCode(),
18403
+ disabled: !contact.trim() || busy,
18404
+ className: "h-9 flex-shrink-0 rounded-lg bg-foreground px-3 text-xs font-semibold text-background disabled:opacity-50",
18405
+ children: busy ? "Sending\u2026" : "Send code"
18406
+ }
18407
+ )
18408
+ ] }),
18409
+ step === "code" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
18410
+ /* @__PURE__ */ jsxRuntime.jsx(
18411
+ "input",
18412
+ {
18413
+ type: "text",
18414
+ inputMode: "numeric",
18415
+ autoComplete: "one-time-code",
18416
+ maxLength: 6,
18417
+ value: code,
18418
+ onChange: (e) => {
18419
+ const digits = e.target.value.replace(/\D/g, "").slice(0, 6);
18420
+ setCode(digits);
18421
+ if (digits.length === 6) void verify(digits);
18422
+ },
18423
+ placeholder: "6-digit code",
18424
+ autoFocus: true,
18425
+ className: "h-9 min-w-0 flex-1 rounded-lg border border-border bg-background px-3 text-xs tracking-[0.2em] text-foreground outline-none placeholder:tracking-normal placeholder:text-muted-foreground focus:border-foreground"
18426
+ }
18427
+ ),
18428
+ /* @__PURE__ */ jsxRuntime.jsx(
18429
+ "button",
18430
+ {
18431
+ type: "button",
18432
+ onClick: () => void requestCode(),
18433
+ disabled: busy,
18434
+ className: "h-9 flex-shrink-0 rounded-lg px-2 text-[11px] font-medium text-muted-foreground hover:text-foreground disabled:opacity-50",
18435
+ children: "Resend"
18436
+ }
18437
+ )
18438
+ ] }),
18439
+ verifyError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1.5 text-[11px] text-destructive", children: verifyError })
18440
+ ] });
18441
+ }
17669
18442
  function ChatInput({
17670
18443
  placeholder,
17671
18444
  isSending,
@@ -17688,14 +18461,15 @@ function ChatInput({
17688
18461
  },
17689
18462
  [onAttach]
17690
18463
  );
17691
- const handleSend = React14.useCallback(() => {
18464
+ const handleSend = React14.useCallback(async () => {
17692
18465
  const trimmed = value.trim();
17693
18466
  if (!trimmed || isSending) return;
17694
18467
  setValue("");
17695
18468
  if (textareaRef.current) {
17696
18469
  textareaRef.current.style.height = "auto";
17697
18470
  }
17698
- onSend(trimmed);
18471
+ const sent = await onSend(trimmed);
18472
+ if (!sent) setValue((current) => current || trimmed);
17699
18473
  }, [value, isSending, onSend]);
17700
18474
  const handleKeyDown = React14.useCallback(
17701
18475
  (e) => {
@@ -17873,16 +18647,21 @@ function ImageIcon({ className }) {
17873
18647
  }
17874
18648
  );
17875
18649
  }
17876
- function SparkleIcon({ className }) {
17877
- return /* @__PURE__ */ jsxRuntime.jsx(
18650
+ function ReplyIcon({ className }) {
18651
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17878
18652
  "svg",
17879
18653
  {
17880
18654
  className,
17881
18655
  viewBox: "0 0 24 24",
17882
18656
  fill: "none",
17883
18657
  stroke: "currentColor",
17884
- strokeWidth: 2.5,
17885
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" })
18658
+ strokeWidth: 2,
18659
+ strokeLinecap: "round",
18660
+ strokeLinejoin: "round",
18661
+ children: [
18662
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 17 4 12 9 7" }),
18663
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
18664
+ ]
17886
18665
  }
17887
18666
  );
17888
18667
  }
@@ -19710,7 +20489,7 @@ async function genUIFingerprint(manifest) {
19710
20489
 
19711
20490
  Object.defineProperty(exports, "getVariantDisplayName", {
19712
20491
  enumerable: true,
19713
- get: function () { return chunkHKN3YG76_js.getVariantDisplayName; }
20492
+ get: function () { return chunkQPRI2NII_js.getVariantDisplayName; }
19714
20493
  });
19715
20494
  Object.defineProperty(exports, "messagePreview", {
19716
20495
  enumerable: true,
@@ -19751,6 +20530,7 @@ exports.BookingPage = BookingPage;
19751
20530
  exports.BookingSummary = BookingSummary;
19752
20531
  exports.BookingsPage = BookingsPage;
19753
20532
  exports.BoxIcon24 = BoxIcon24;
20533
+ exports.BubbleTail = BubbleTail;
19754
20534
  exports.BundleProductCard = BundleProductCard;
19755
20535
  exports.BundleProductLayout = BundleProductLayout;
19756
20536
  exports.BundleSelector = BundleSelector;
@@ -19825,6 +20605,7 @@ exports.LeaseServiceCard = LeaseServiceCard;
19825
20605
  exports.LineItemDetails = LineItemDetails;
19826
20606
  exports.LocationPicker = LocationPicker;
19827
20607
  exports.MediaGallery = MediaGallery;
20608
+ exports.MessageBubble = MessageBubble;
19828
20609
  exports.MetadataStringList = MetadataStringList;
19829
20610
  exports.MobileAccountShell = MobileAccountShell;
19830
20611
  exports.MobileBackButton = MobileBackButton;
@@ -19859,6 +20640,7 @@ exports.ProductTemplate = ProductTemplate;
19859
20640
  exports.PropertiesTable = PropertiesTable;
19860
20641
  exports.QuantitySelector = QuantitySelector;
19861
20642
  exports.QuickAddButton = QuickAddButton;
20643
+ exports.QuoteBlock = QuoteBlock;
19862
20644
  exports.RecentlyViewed = RecentlyViewed;
19863
20645
  exports.RecommendationCarousel = RecommendationCarousel;
19864
20646
  exports.RelatedProductsSection = RelatedProductsSection;
@@ -19866,10 +20648,12 @@ exports.RentalServiceCard = RentalServiceCard;
19866
20648
  exports.ResourcePicker = ResourcePicker;
19867
20649
  exports.RetailProductCard = RetailProductCard;
19868
20650
  exports.RichText = RichText;
20651
+ exports.SWIPE_COMMIT_PX = SWIPE_COMMIT_PX;
19869
20652
  exports.SaleBadge = SaleBadge;
19870
20653
  exports.SalePrice = SalePrice;
19871
20654
  exports.SaleTeaser = SaleTeaser;
19872
20655
  exports.ScheduleServiceCard = ScheduleServiceCard;
20656
+ exports.Seam = Seam;
19873
20657
  exports.SearchInput = SearchInput;
19874
20658
  exports.SearchPage = SearchPage;
19875
20659
  exports.SearchTemplate = SearchTemplate;
@@ -19901,26 +20685,39 @@ exports.WelcomeCard = WelcomeCard;
19901
20685
  exports.WholesaleProductCard = WholesaleProductCard;
19902
20686
  exports.WholesaleProductLayout = WholesaleProductLayout;
19903
20687
  exports.WishlistButton = WishlistButton;
20688
+ exports.chatTimeline = chatTimeline;
19904
20689
  exports.clearOrdersCache = clearOrdersCache;
19905
20690
  exports.cn = cn;
19906
20691
  exports.createPlacesSessionToken = createPlacesSessionToken;
20692
+ exports.dayLabel = dayLabel;
19907
20693
  exports.defaultBoundaryKind = defaultBoundaryKind;
20694
+ exports.deliveryState = deliveryState;
19908
20695
  exports.genUICatalog = genUICatalog;
19909
20696
  exports.genUIFingerprint = genUIFingerprint;
19910
20697
  exports.genUIManifest = genUIManifest;
19911
20698
  exports.genUIManifestJson = genUIManifestJson;
19912
20699
  exports.getBoundaryLabels = getBoundaryLabels;
20700
+ exports.groupPosition = groupPosition;
19913
20701
  exports.isImageAttachment = isImageAttachment;
20702
+ exports.isOptimistic = isOptimistic;
19914
20703
  exports.isTerminalOrderStatus = isTerminalOrderStatus;
20704
+ exports.optimisticId = optimisticId;
19915
20705
  exports.parseGenUIProgram = parse;
19916
20706
  exports.partitionMessages = partitionMessages;
20707
+ exports.prefersReducedMotion = prefersReducedMotion;
19917
20708
  exports.prefetchProduct = prefetchProduct;
19918
20709
  exports.prefetchProducts = prefetchProducts;
20710
+ exports.proseOf = proseOf;
19919
20711
  exports.resolveBoundaryKind = resolveBoundaryKind;
19920
20712
  exports.roomToResource = roomToResource;
19921
20713
  exports.safeAttachmentUrl = safeAttachmentUrl;
20714
+ exports.sameDay = sameDay;
19922
20715
  exports.sanitizeRichTextHtml = sanitizeRichTextHtml;
20716
+ exports.scrollBehavior = scrollBehavior;
19923
20717
  exports.stripHtmlToText = stripHtmlToText;
20718
+ exports.swipeCommitted = swipeCommitted;
20719
+ exports.swipeProgress = swipeProgress;
20720
+ exports.swipeTravel = swipeTravel;
19924
20721
  exports.toAccountLineItem = toAccountLineItem;
19925
20722
  exports.toAccountLineItems = toAccountLineItems;
19926
20723
  exports.toAccountOrderView = toAccountOrderView;
@@ -19969,6 +20766,7 @@ exports.useServices = useServices;
19969
20766
  exports.useStoreCredit = useStoreCredit;
19970
20767
  exports.useSubscription = useSubscription;
19971
20768
  exports.useSubscriptions = useSubscriptions;
20769
+ exports.useSwipeReply = useSwipeReply;
19972
20770
  exports.useTaxonomies = useTaxonomies;
19973
20771
  exports.useTaxonomy = useTaxonomy;
19974
20772
  exports.useTaxonomyPath = useTaxonomyPath;