@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.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  "use client";
2
- import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, isMultiDayService, serviceBillingMultiplier, PRODUCT_TYPE, BOUNDARY_KIND, resolveGallery, RENDER_HINT, DEFAULT_CDN_BASE_URL } from './chunk-RRBY35TJ.mjs';
3
- export { getVariantDisplayName } from './chunk-RRBY35TJ.mjs';
4
- import { messagePreview } from './chunk-U4MW6PYU.mjs';
2
+ import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, isMultiDayService, serviceBillingMultiplier, PRODUCT_TYPE, BOUNDARY_KIND, resolveGallery, chatMessageCards, RENDER_HINT, DEFAULT_CDN_BASE_URL } from './chunk-4G5UWFC3.mjs';
3
+ export { getVariantDisplayName } from './chunk-4G5UWFC3.mjs';
4
+ import { splitGenUIContent, messagePreview } from './chunk-U4MW6PYU.mjs';
5
5
  export { messagePreview, splitGenUIContent } from './chunk-U4MW6PYU.mjs';
6
6
  import { SESSION_CLAIMED, SESSION_CLEARED, createCimplifyClient, MUTATION_ERROR } from './chunk-XMDLF5Y6.mjs';
7
7
  import { generateIdempotencyKey, ELEMENT_TYPES, EVENT_TYPES, startSignIn } from './chunk-VUSHG3HA.mjs';
8
8
  import { parsePrice, formatMoney, formatPrice, getProductCurrency, getUnitPriceAtQuantity, isOnSale, getDiscountPercentage, getBasePrice, getPriceRange, formatPriceRange } from './chunk-KFLGQGMC.mjs';
9
9
  import { isSupportedCurrency, CimplifyError } from './chunk-K5EN2W2Q.mjs';
10
10
  import './chunk-3G6RQLXK.mjs';
11
- import React14, { createContext, lazy, memo, Suspense, useContext, useMemo, useRef, useState, useCallback, useEffect, useId, useSyncExternalStore } from 'react';
12
- import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
11
+ import React14, { createContext, lazy, memo, Suspense, useMemo, useContext, useRef, useCallback, useState, useEffect, useId, useSyncExternalStore } from 'react';
12
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
13
  import * as ReactDOM from 'react-dom';
14
14
  import { clsx } from 'clsx';
15
15
  import { twMerge } from 'tailwind-merge';
@@ -3091,8 +3091,8 @@ function fmtScheduleWindow(start, end) {
3091
3091
  const startStr = format(s, "EEE, MMM d \xB7 h:mm a");
3092
3092
  const e = end ? parseISO(end) : null;
3093
3093
  if (e && isValid(e)) {
3094
- const sameDay = s.toDateString() === e.toDateString();
3095
- return `${startStr} \u2013 ${format(e, sameDay ? "h:mm a" : "EEE, MMM d \xB7 h:mm a")}`;
3094
+ const sameDay2 = s.toDateString() === e.toDateString();
3095
+ return `${startStr} \u2013 ${format(e, sameDay2 ? "h:mm a" : "EEE, MMM d \xB7 h:mm a")}`;
3096
3096
  }
3097
3097
  return startStr;
3098
3098
  }
@@ -5883,6 +5883,33 @@ function useBootstrap(options = {}) {
5883
5883
  const { data, isLoading, error, refetch } = bootstrapQuery(options);
5884
5884
  return { bootstrap: data, isLoading, error, refetch };
5885
5885
  }
5886
+
5887
+ // src/react/chat-model.ts
5888
+ var OPTIMISTIC_ID_PREFIX = "opt_";
5889
+ function optimisticId(clientId) {
5890
+ return `${OPTIMISTIC_ID_PREFIX}${clientId}`;
5891
+ }
5892
+ function isOptimistic(message) {
5893
+ return message.id.startsWith(OPTIMISTIC_ID_PREFIX);
5894
+ }
5895
+ function partitionMessages(messages) {
5896
+ const bubbles = [];
5897
+ const reactionsByTarget = /* @__PURE__ */ new Map();
5898
+ for (const message of messages) {
5899
+ if (message.content_type === "reaction") {
5900
+ const targetId = message.reply_to?.id;
5901
+ if (!targetId) continue;
5902
+ const list = reactionsByTarget.get(targetId);
5903
+ if (list) list.push(message);
5904
+ else reactionsByTarget.set(targetId, [message]);
5905
+ continue;
5906
+ }
5907
+ bubbles.push(message);
5908
+ }
5909
+ return { bubbles, reactionsByTarget };
5910
+ }
5911
+
5912
+ // src/react/hooks/use-chat.ts
5886
5913
  var POLL_INTERVAL_MS = 3e3;
5887
5914
  var POLL_IDLE_INTERVAL_MS = 15e3;
5888
5915
  var IDLE_TIMEOUT_MS = 6e4;
@@ -5897,10 +5924,10 @@ function mergeMessages(prev, incoming) {
5897
5924
  (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
5898
5925
  );
5899
5926
  const confirmed = new Set(
5900
- merged.filter((m) => !m.id.startsWith("opt_") && m.client_id).map((m) => m.client_id)
5927
+ merged.filter((m) => !isOptimistic(m) && m.client_id).map((m) => m.client_id)
5901
5928
  );
5902
5929
  return merged.filter(
5903
- (m) => !(m.id.startsWith("opt_") && m.client_id && confirmed.has(m.client_id))
5930
+ (m) => !(isOptimistic(m) && m.client_id && confirmed.has(m.client_id))
5904
5931
  );
5905
5932
  }
5906
5933
  function useChat(options = {}) {
@@ -5963,7 +5990,7 @@ function useChat(options = {}) {
5963
5990
  const lastConfirmedMessage = useCallback(() => {
5964
5991
  const list = messagesRef.current;
5965
5992
  for (let i = list.length - 1; i >= 0; i--) {
5966
- if (!list[i].id.startsWith("opt_")) return list[i];
5993
+ if (!isOptimistic(list[i])) return list[i];
5967
5994
  }
5968
5995
  return void 0;
5969
5996
  }, []);
@@ -6011,7 +6038,7 @@ function useChat(options = {}) {
6011
6038
  const snapshotIds = new Set(snapshot.map((m) => m.id));
6012
6039
  const tail = snapshot.length > 0 ? new Date(snapshot[snapshot.length - 1].created_at).getTime() : 0;
6013
6040
  const extras = prev.filter(
6014
- (m) => !snapshotIds.has(m.id) && (m.id.startsWith("opt_") || new Date(m.created_at).getTime() > tail)
6041
+ (m) => !snapshotIds.has(m.id) && (isOptimistic(m) || new Date(m.created_at).getTime() > tail)
6015
6042
  );
6016
6043
  return mergeMessages(snapshot, extras);
6017
6044
  });
@@ -6197,9 +6224,9 @@ function useChat(options = {}) {
6197
6224
  }
6198
6225
  const replyTo = opts?.replyTo;
6199
6226
  const clientId = generateIdempotencyKey();
6200
- const optimisticId = `opt_${clientId}`;
6227
+ const localId = optimisticId(clientId);
6201
6228
  const optimistic = {
6202
- id: optimisticId,
6229
+ id: localId,
6203
6230
  sender_type: "customer",
6204
6231
  content: content.trim(),
6205
6232
  content_type: attachments.length > 0 ? "image" : "text",
@@ -6229,14 +6256,14 @@ function useChat(options = {}) {
6229
6256
  // The websocket may have already streamed the confirmed message
6230
6257
  // in while the POST was in flight; drop the optimistic bubble
6231
6258
  // instead of inserting a duplicate.
6232
- prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !== optimisticId) : prev.map((m) => m.id === optimisticId ? confirmed : m)
6259
+ prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !== localId) : prev.map((m) => m.id === localId ? confirmed : m)
6233
6260
  )
6234
6261
  );
6235
6262
  if (aiEnabledRef.current && !humanRequestedRef.current) {
6236
6263
  startAwaitingReply();
6237
6264
  }
6238
6265
  } else {
6239
- setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
6266
+ setMessages((prev) => prev.filter((m) => m.id !== localId));
6240
6267
  setError(result.error);
6241
6268
  }
6242
6269
  setIsSending(false);
@@ -6255,7 +6282,7 @@ function useChat(options = {}) {
6255
6282
  );
6256
6283
  const react = useCallback(
6257
6284
  async (message, emoji) => {
6258
- if (!client || message.id.startsWith("opt_")) return false;
6285
+ if (!client || isOptimistic(message)) return false;
6259
6286
  const result = await client.support.reactToMessage(message.id, emoji);
6260
6287
  if (result.ok) appendMessages([result.value]);
6261
6288
  return result.ok;
@@ -10429,7 +10456,7 @@ function CartLineItemRow({
10429
10456
  });
10430
10457
  const hasComposite = item.compositeSelections && item.compositeSelections.length > 0;
10431
10458
  const hasBundle = item.bundleSelections && item.bundleSelections.length > 0;
10432
- const isOptimistic = item.isOptimistic === true;
10459
+ const isOptimistic2 = item.isOptimistic === true;
10433
10460
  const lineTotal = parsePrice(unitPrice) * item.quantity;
10434
10461
  const imageUrl = item.product.image_url;
10435
10462
  const variantLabel2 = item.variant ? getVariantDisplayName(item.variant, item.product.name) : null;
@@ -10437,10 +10464,10 @@ function CartLineItemRow({
10437
10464
  "div",
10438
10465
  {
10439
10466
  "data-cimplify-cart-item": true,
10440
- "data-optimistic": isOptimistic ? "true" : void 0,
10467
+ "data-optimistic": isOptimistic2 ? "true" : void 0,
10441
10468
  className: cn(
10442
10469
  "group relative flex gap-3 py-4 border-b border-border last:border-b-0 transition-opacity",
10443
- isOptimistic && "opacity-60",
10470
+ isOptimistic2 && "opacity-60",
10444
10471
  classNames?.item
10445
10472
  ),
10446
10473
  children: [
@@ -10607,7 +10634,7 @@ function CartSummary({
10607
10634
  tax,
10608
10635
  total,
10609
10636
  isEmpty,
10610
- isOptimistic,
10637
+ isOptimistic: isOptimistic2,
10611
10638
  pendingOpCount,
10612
10639
  removeItem,
10613
10640
  updateQuantity
@@ -10633,7 +10660,7 @@ function CartSummary({
10633
10660
  "div",
10634
10661
  {
10635
10662
  "data-cimplify-cart-summary": true,
10636
- "data-optimistic": isOptimistic ? "true" : void 0,
10663
+ "data-optimistic": isOptimistic2 ? "true" : void 0,
10637
10664
  "data-pending-ops": pendingOpCount > 0 ? pendingOpCount : void 0,
10638
10665
  className: cn(className, classNames?.root),
10639
10666
  children: isEmpty ? /* @__PURE__ */ jsx("div", { "data-cimplify-cart-empty": true, className: classNames?.empty, children: /* @__PURE__ */ jsx("p", { children: emptyMessage }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -17120,24 +17147,581 @@ var ChatMarkdown = memo(function ChatMarkdown2({
17120
17147
  return /* @__PURE__ */ jsx(SettledMarkdown, { text, className });
17121
17148
  });
17122
17149
 
17123
- // src/react/chat-model.ts
17124
- function partitionMessages(messages) {
17125
- const bubbles = [];
17126
- const reactionsByTarget = /* @__PURE__ */ new Map();
17127
- for (const message of messages) {
17128
- if (message.content_type === "reaction") {
17129
- const targetId = message.reply_to?.id;
17130
- if (!targetId) continue;
17131
- const list = reactionsByTarget.get(targetId);
17132
- if (list) list.push(message);
17133
- else reactionsByTarget.set(targetId, [message]);
17134
- continue;
17150
+ // src/react/chat-kit/motion.ts
17151
+ function prefersReducedMotion() {
17152
+ return typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
17153
+ }
17154
+ function scrollBehavior() {
17155
+ return prefersReducedMotion() ? "auto" : "smooth";
17156
+ }
17157
+ function QuoteBlock({
17158
+ quote,
17159
+ inCustomerBubble,
17160
+ businessName,
17161
+ onMissing
17162
+ }) {
17163
+ const quotedIsCustomer = quote.sender_type === "customer";
17164
+ const jumpToOriginal = async () => {
17165
+ let original = document.getElementById(`chat-msg-${quote.id}`);
17166
+ if (!original && onMissing && await onMissing()) {
17167
+ original = document.getElementById(`chat-msg-${quote.id}`);
17168
+ }
17169
+ if (!original) return;
17170
+ original.scrollIntoView({ behavior: scrollBehavior(), block: "center" });
17171
+ original.classList.remove("animate-quote-flash");
17172
+ void original.offsetWidth;
17173
+ original.classList.add("animate-quote-flash");
17174
+ };
17175
+ return /* @__PURE__ */ jsxs(
17176
+ "button",
17177
+ {
17178
+ type: "button",
17179
+ onClick: () => void jumpToOriginal(),
17180
+ className: cn(
17181
+ "flex w-full gap-2 rounded-[10px] px-2.5 py-1.5 text-left",
17182
+ inCustomerBubble ? "bg-primary-foreground/15" : "bg-background/70"
17183
+ ),
17184
+ children: [
17185
+ /* @__PURE__ */ jsx(
17186
+ "span",
17187
+ {
17188
+ className: cn(
17189
+ "w-[3px] shrink-0 self-stretch rounded-full",
17190
+ inCustomerBubble ? "bg-primary-foreground/50" : quotedIsCustomer ? "bg-primary" : "bg-muted-foreground/50"
17191
+ )
17192
+ }
17193
+ ),
17194
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col", children: [
17195
+ /* @__PURE__ */ jsx(
17196
+ "span",
17197
+ {
17198
+ className: cn(
17199
+ "text-[11px] leading-4 font-semibold",
17200
+ inCustomerBubble ? "text-primary-foreground" : "text-foreground"
17201
+ ),
17202
+ children: quotedIsCustomer ? "You" : businessName
17203
+ }
17204
+ ),
17205
+ /* @__PURE__ */ jsx(
17206
+ "span",
17207
+ {
17208
+ className: cn(
17209
+ "truncate text-xs leading-4",
17210
+ inCustomerBubble ? "text-primary-foreground/75" : "text-muted-foreground"
17211
+ ),
17212
+ children: quote.preview
17213
+ }
17214
+ )
17215
+ ] })
17216
+ ]
17135
17217
  }
17136
- bubbles.push(message);
17218
+ );
17219
+ }
17220
+ var SWIPE_COMMIT_PX = 48;
17221
+ var SWIPE_ARM_PX = 10;
17222
+ var OVERDRAG_RATE = 0.25;
17223
+ var LONG_PRESS_MS = 450;
17224
+ var RETURN_TRANSITION = "transform 0.25s cubic-bezier(0.2, 0, 0, 1)";
17225
+ function swipeTravel(distance) {
17226
+ if (distance <= 0) return 0;
17227
+ if (distance <= SWIPE_COMMIT_PX) return distance;
17228
+ return SWIPE_COMMIT_PX + (distance - SWIPE_COMMIT_PX) * OVERDRAG_RATE;
17229
+ }
17230
+ function swipeCommitted(distance) {
17231
+ return distance >= SWIPE_COMMIT_PX;
17232
+ }
17233
+ function swipeProgress(distance) {
17234
+ return Math.min(Math.max(distance / SWIPE_COMMIT_PX, 0), 1);
17235
+ }
17236
+ function useSwipeReply({ onReply, onLongPress }) {
17237
+ const rowRef = useRef(null);
17238
+ const glyphRef = useRef(null);
17239
+ const gestureRef = useRef(null);
17240
+ const suppressClickRef = useRef(false);
17241
+ const optionsRef = useRef({ onReply, onLongPress });
17242
+ optionsRef.current = { onReply, onLongPress };
17243
+ const settle = useCallback((glyphVisible) => {
17244
+ const row = rowRef.current;
17245
+ const glyph = glyphRef.current;
17246
+ if (row) {
17247
+ row.style.transition = RETURN_TRANSITION;
17248
+ row.style.transform = "translateX(0px)";
17249
+ }
17250
+ if (glyph && !glyphVisible) {
17251
+ glyph.style.transition = "opacity 0.2s ease-out";
17252
+ glyph.style.opacity = "0";
17253
+ }
17254
+ }, []);
17255
+ const clearGesture = useCallback(() => {
17256
+ const gesture = gestureRef.current;
17257
+ if (gesture?.longPressTimer) clearTimeout(gesture.longPressTimer);
17258
+ gestureRef.current = null;
17259
+ }, []);
17260
+ const onPointerDown = useCallback((event) => {
17261
+ if (!event.isPrimary) return;
17262
+ suppressClickRef.current = false;
17263
+ gestureRef.current = {
17264
+ pointerId: event.pointerId,
17265
+ startX: event.clientX,
17266
+ startY: event.clientY,
17267
+ armed: false,
17268
+ distance: 0,
17269
+ longPressTimer: setTimeout(() => {
17270
+ gestureRef.current = null;
17271
+ suppressClickRef.current = true;
17272
+ optionsRef.current.onLongPress();
17273
+ }, LONG_PRESS_MS)
17274
+ };
17275
+ }, []);
17276
+ const onPointerMove = useCallback((event) => {
17277
+ const gesture = gestureRef.current;
17278
+ if (!gesture || gesture.pointerId !== event.pointerId) return;
17279
+ const leftward = gesture.startX - event.clientX;
17280
+ const vertical = Math.abs(event.clientY - gesture.startY);
17281
+ if (!gesture.armed) {
17282
+ const scrolling = vertical > SWIPE_ARM_PX && vertical >= leftward;
17283
+ const rightward = leftward < -SWIPE_ARM_PX;
17284
+ if (scrolling || rightward) {
17285
+ clearGesture();
17286
+ return;
17287
+ }
17288
+ if (leftward <= SWIPE_ARM_PX || leftward <= vertical) return;
17289
+ if (prefersReducedMotion()) {
17290
+ clearGesture();
17291
+ return;
17292
+ }
17293
+ gesture.armed = true;
17294
+ if (gesture.longPressTimer) clearTimeout(gesture.longPressTimer);
17295
+ gesture.longPressTimer = null;
17296
+ event.currentTarget.setPointerCapture(event.pointerId);
17297
+ if (rowRef.current) rowRef.current.style.transition = "none";
17298
+ if (glyphRef.current) glyphRef.current.style.transition = "none";
17299
+ }
17300
+ gesture.distance = leftward;
17301
+ if (rowRef.current) {
17302
+ rowRef.current.style.transform = `translateX(-${swipeTravel(leftward)}px)`;
17303
+ }
17304
+ if (glyphRef.current) {
17305
+ glyphRef.current.style.opacity = String(swipeProgress(leftward));
17306
+ }
17307
+ }, [clearGesture]);
17308
+ const onPointerUp = useCallback(
17309
+ (event) => {
17310
+ const gesture = gestureRef.current;
17311
+ if (!gesture || gesture.pointerId !== event.pointerId) return;
17312
+ const commit = gesture.armed && swipeCommitted(gesture.distance);
17313
+ const wasArmed = gesture.armed;
17314
+ clearGesture();
17315
+ if (wasArmed) {
17316
+ suppressClickRef.current = true;
17317
+ settle(false);
17318
+ }
17319
+ if (commit) optionsRef.current.onReply();
17320
+ },
17321
+ [clearGesture, settle]
17322
+ );
17323
+ const onPointerCancel = useCallback(
17324
+ (event) => {
17325
+ const gesture = gestureRef.current;
17326
+ if (!gesture || gesture.pointerId !== event.pointerId) return;
17327
+ const wasArmed = gesture.armed;
17328
+ clearGesture();
17329
+ if (wasArmed) settle(false);
17330
+ },
17331
+ [clearGesture, settle]
17332
+ );
17333
+ const onClickCapture = useCallback((event) => {
17334
+ if (!suppressClickRef.current) return;
17335
+ suppressClickRef.current = false;
17336
+ event.preventDefault();
17337
+ event.stopPropagation();
17338
+ }, []);
17339
+ return {
17340
+ rowRef,
17341
+ glyphRef,
17342
+ handlers: { onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onClickCapture }
17343
+ };
17344
+ }
17345
+ 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]";
17346
+ function bubbleShape(isCustomer, group) {
17347
+ if (!hasTail(group)) return "rounded-[18px]";
17348
+ return isCustomer ? "rounded-[18px] rounded-br-none" : "rounded-[18px] rounded-bl-none";
17349
+ }
17350
+ function hasTail(group) {
17351
+ return group === "single" || group === "last";
17352
+ }
17353
+ 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";
17354
+ var TAIL_FILL = `${TAIL_SWEEP} Z`;
17355
+ var TAIL_NOTCH = "M25 16 C20.93 16.43 16.84 14.94 13.96 11.96 C9 16 5 16 0 16 Z";
17356
+ function BubbleTail({ isCustomer }) {
17357
+ return /* @__PURE__ */ jsxs(
17358
+ "svg",
17359
+ {
17360
+ width: "25",
17361
+ height: "16",
17362
+ viewBox: "0 0 25 16",
17363
+ "aria-hidden": "true",
17364
+ className: cn(
17365
+ "absolute bottom-0",
17366
+ isCustomer ? "-right-[4px] fill-primary" : "-left-[4px] -scale-x-100 fill-background"
17367
+ ),
17368
+ children: [
17369
+ /* @__PURE__ */ jsx("path", { d: TAIL_FILL }),
17370
+ /* @__PURE__ */ jsx("path", { d: TAIL_NOTCH, style: { fill: "var(--color-chat-canvas, var(--color-muted, #f3f5f3))" } }),
17371
+ !isCustomer && /* @__PURE__ */ jsx("path", { d: TAIL_SWEEP, fill: "none", strokeWidth: "1", className: "stroke-foreground/[0.07]" })
17372
+ ]
17373
+ }
17374
+ );
17375
+ }
17376
+ function clockLabel(at) {
17377
+ const date = new Date(at);
17378
+ if (Number.isNaN(date.getTime())) return "";
17379
+ const hour = date.getHours() % 12 || 12;
17380
+ return `${hour}:${String(date.getMinutes()).padStart(2, "0")}`;
17381
+ }
17382
+ function DoubleTick() {
17383
+ return /* @__PURE__ */ jsxs("svg", { width: "15", height: "9", viewBox: "0 0 15 9", fill: "none", "aria-hidden": "true", children: [
17384
+ /* @__PURE__ */ jsx("path", { d: "M1 4.5L4 7.5L9.5 1.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }),
17385
+ /* @__PURE__ */ jsx("path", { d: "M6.5 4.5L9.5 7.5L14 2.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" })
17386
+ ] });
17387
+ }
17388
+ function SendingClock() {
17389
+ return /* @__PURE__ */ jsxs("svg", { width: "11", height: "11", viewBox: "0 0 11 11", fill: "none", "aria-hidden": "true", children: [
17390
+ /* @__PURE__ */ jsx("circle", { cx: "5.5", cy: "5.5", r: "4.5", stroke: "currentColor", strokeWidth: "1.2" }),
17391
+ /* @__PURE__ */ jsx("path", { d: "M5.5 3v2.7l1.8 1", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
17392
+ ] });
17393
+ }
17394
+ function PaperclipIcon() {
17395
+ return /* @__PURE__ */ jsx(
17396
+ "svg",
17397
+ {
17398
+ width: "14",
17399
+ height: "14",
17400
+ viewBox: "0 0 24 24",
17401
+ fill: "none",
17402
+ stroke: "currentColor",
17403
+ strokeWidth: "2",
17404
+ strokeLinecap: "round",
17405
+ strokeLinejoin: "round",
17406
+ "aria-hidden": "true",
17407
+ children: /* @__PURE__ */ 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" })
17408
+ }
17409
+ );
17410
+ }
17411
+ function ReplyGlyph() {
17412
+ return /* @__PURE__ */ jsxs(
17413
+ "svg",
17414
+ {
17415
+ width: "18",
17416
+ height: "18",
17417
+ viewBox: "0 0 24 24",
17418
+ fill: "none",
17419
+ stroke: "currentColor",
17420
+ strokeWidth: "2.2",
17421
+ strokeLinecap: "round",
17422
+ strokeLinejoin: "round",
17423
+ "aria-hidden": "true",
17424
+ children: [
17425
+ /* @__PURE__ */ jsx("polyline", { points: "9 17 4 12 9 7" }),
17426
+ /* @__PURE__ */ jsx("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
17427
+ ]
17428
+ }
17429
+ );
17430
+ }
17431
+ var DELIVERY_LABELS = {
17432
+ sending: "Sending",
17433
+ delivered: "Delivered",
17434
+ seen: "Seen"
17435
+ };
17436
+ function BubbleMeta({
17437
+ at,
17438
+ isCustomer,
17439
+ delivery
17440
+ }) {
17441
+ return /* @__PURE__ */ jsxs(
17442
+ "span",
17443
+ {
17444
+ className: cn(
17445
+ "ml-auto flex shrink-0 items-center gap-[3px] self-end pb-px whitespace-nowrap",
17446
+ isCustomer ? "text-primary-foreground/70" : "text-muted-foreground"
17447
+ ),
17448
+ children: [
17449
+ /* @__PURE__ */ jsx("span", { className: "text-[10.5px] leading-[13px] font-medium", children: clockLabel(at) }),
17450
+ isCustomer && delivery && /* @__PURE__ */ jsx(
17451
+ "span",
17452
+ {
17453
+ role: "img",
17454
+ "aria-label": DELIVERY_LABELS[delivery],
17455
+ className: cn(
17456
+ "flex items-center text-primary-foreground",
17457
+ delivery !== "seen" && "opacity-55"
17458
+ ),
17459
+ children: delivery === "sending" ? /* @__PURE__ */ jsx(SendingClock, {}) : /* @__PURE__ */ jsx(DoubleTick, {})
17460
+ }
17461
+ )
17462
+ ]
17463
+ }
17464
+ );
17465
+ }
17466
+ function reactionPills(reactions) {
17467
+ const counts = /* @__PURE__ */ new Map();
17468
+ for (const reaction of reactions) {
17469
+ const emoji = reaction.content.trim();
17470
+ if (emoji) counts.set(emoji, (counts.get(emoji) ?? 0) + 1);
17137
17471
  }
17138
- return { bubbles, reactionsByTarget };
17472
+ return [...counts.entries()].map(([emoji, count]) => ({ emoji, count }));
17473
+ }
17474
+ var MessageBubble = memo(function MessageBubble2({
17475
+ message,
17476
+ reactions,
17477
+ businessName,
17478
+ group = "single",
17479
+ delivery,
17480
+ onReply,
17481
+ onLongPress,
17482
+ avatar,
17483
+ overlay,
17484
+ renderCards,
17485
+ renderProgram,
17486
+ onQuoteMiss
17487
+ }) {
17488
+ const showAvatar = group === "single" || group === "last";
17489
+ const isCustomer = message.sender_type === "customer";
17490
+ const isBusiness = !isCustomer;
17491
+ const segments = useMemo(
17492
+ () => isCustomer ? [{ kind: "prose", text: message.content }] : splitGenUIContent(message.content),
17493
+ [isCustomer, message.content]
17494
+ );
17495
+ const drawable = useMemo(
17496
+ () => segments.filter(
17497
+ (segment) => segment.kind === "prose" && segment.text.trim() !== "" || segment.kind === "program" && renderProgram != null
17498
+ ),
17499
+ // eslint-disable-next-line react-hooks/exhaustive-deps
17500
+ [segments, renderProgram != null]
17501
+ );
17502
+ const swipe = useSwipeReply({
17503
+ onReply: () => onReply(message),
17504
+ onLongPress: () => onLongPress?.(message)
17505
+ });
17506
+ const quote = message.reply_to ?? null;
17507
+ const pills = reactions ? reactionPills(reactions) : [];
17508
+ const cards = renderCards?.(message);
17509
+ const proseSegments = drawable.filter((segment) => segment.kind === "prose");
17510
+ const lastProseIndex = drawable.lastIndexOf(proseSegments[proseSegments.length - 1]);
17511
+ return /* @__PURE__ */ jsxs("div", { id: `chat-msg-${message.id}`, className: "relative rounded-xl", children: [
17512
+ /* @__PURE__ */ jsx(
17513
+ "span",
17514
+ {
17515
+ ref: swipe.glyphRef,
17516
+ "aria-hidden": "true",
17517
+ className: "absolute top-1/2 right-1 -translate-y-1/2 text-muted-foreground opacity-0",
17518
+ children: /* @__PURE__ */ jsx(ReplyGlyph, {})
17519
+ }
17520
+ ),
17521
+ overlay,
17522
+ /* @__PURE__ */ jsxs(
17523
+ "div",
17524
+ {
17525
+ ref: swipe.rowRef,
17526
+ ...swipe.handlers,
17527
+ onContextMenu: onLongPress ? (event) => {
17528
+ event.preventDefault();
17529
+ onLongPress(message);
17530
+ } : void 0,
17531
+ className: cn(
17532
+ "flex touch-pan-y flex-col gap-[3px] select-none [-webkit-touch-callout:none]",
17533
+ isCustomer ? "items-end" : "items-start"
17534
+ ),
17535
+ children: [
17536
+ quote && drawable[0]?.kind !== "prose" && /* @__PURE__ */ 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__ */ jsx(QuoteBlock, { quote, inCustomerBubble: false, businessName, onMissing: onQuoteMiss }) }),
17537
+ message.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex max-w-[280px] flex-wrap gap-1.5", children: message.attachments.map((attachment, index) => {
17538
+ const url = safeAttachmentUrl(attachment.url);
17539
+ if (!url) return null;
17540
+ return isImageAttachment(attachment) ? /* @__PURE__ */ jsx("a", { href: url, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx(
17541
+ "img",
17542
+ {
17543
+ src: url,
17544
+ alt: attachment.filename ?? "Photo",
17545
+ loading: "lazy",
17546
+ className: "max-h-[240px] max-w-[240px] rounded-[16px] object-cover"
17547
+ }
17548
+ ) }, index) : /* @__PURE__ */ jsxs(
17549
+ "a",
17550
+ {
17551
+ href: url,
17552
+ target: "_blank",
17553
+ rel: "noreferrer",
17554
+ 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)]",
17555
+ children: [
17556
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx(PaperclipIcon, {}) }),
17557
+ /* @__PURE__ */ jsx("span", { className: "max-w-[200px] truncate", children: attachment.filename ?? "Attachment" })
17558
+ ]
17559
+ },
17560
+ index
17561
+ );
17562
+ }) }),
17563
+ drawable.map(
17564
+ (segment, index) => segment.kind === "program" ? /* @__PURE__ */ jsx(React14.Fragment, { children: renderProgram?.(segment.source) }, index) : /* @__PURE__ */ jsxs("div", { className: "flex max-w-full items-end gap-1.5", children: [
17565
+ isBusiness && avatar && /* @__PURE__ */ jsx(
17566
+ "span",
17567
+ {
17568
+ "aria-label": businessName,
17569
+ className: cn(
17570
+ "shrink-0",
17571
+ // One avatar per run, anchored to its last bubble.
17572
+ showAvatar && index === lastProseIndex ? "visible" : "invisible"
17573
+ ),
17574
+ children: avatar
17575
+ }
17576
+ ),
17577
+ /* @__PURE__ */ jsxs(
17578
+ "div",
17579
+ {
17580
+ className: cn(
17581
+ "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",
17582
+ bubbleShape(isCustomer, index === lastProseIndex ? group : "middle"),
17583
+ 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)]"
17584
+ ),
17585
+ children: [
17586
+ index === 0 && quote && /* @__PURE__ */ jsx(QuoteBlock, { quote, inCustomerBubble: isCustomer, businessName, onMissing: onQuoteMiss }),
17587
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-end gap-x-2 gap-y-0.5", children: [
17588
+ isCustomer ? /* @__PURE__ */ jsx("span", { className: "whitespace-pre-wrap", children: segment.text }) : /* @__PURE__ */ jsx(ChatMarkdown, { text: segment.text, className: cn("min-w-0", PROSE_CLASS) }),
17589
+ index === lastProseIndex && /* @__PURE__ */ jsx(BubbleMeta, { at: message.created_at, isCustomer, delivery })
17590
+ ] }),
17591
+ index === lastProseIndex && hasTail(group) && /* @__PURE__ */ jsx(BubbleTail, { isCustomer })
17592
+ ]
17593
+ }
17594
+ )
17595
+ ] }, index)
17596
+ ),
17597
+ cards,
17598
+ pills.length > 0 && /* @__PURE__ */ jsx(
17599
+ "div",
17600
+ {
17601
+ className: cn(
17602
+ "z-[1] -mt-3 flex gap-1",
17603
+ // Overlap the bubble corner by ~9px, tucked inside the tail side.
17604
+ isCustomer ? "mr-2.5" : "ml-10"
17605
+ ),
17606
+ children: pills.map((pill) => /* @__PURE__ */ jsxs(
17607
+ "span",
17608
+ {
17609
+ 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)]",
17610
+ children: [
17611
+ /* @__PURE__ */ jsx("span", { className: "text-xs leading-4", children: pill.emoji }),
17612
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-[13px] font-semibold text-muted-foreground", children: pill.count })
17613
+ ]
17614
+ },
17615
+ pill.emoji
17616
+ ))
17617
+ }
17618
+ )
17619
+ ]
17620
+ }
17621
+ )
17622
+ ] });
17623
+ });
17624
+ function Seam({
17625
+ label,
17626
+ variant = "line"
17627
+ }) {
17628
+ if (variant === "chip") {
17629
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-center py-1.5", children: /* @__PURE__ */ 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 }) });
17630
+ }
17631
+ const unread = variant === "unread";
17632
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 py-1.5", children: [
17633
+ /* @__PURE__ */ jsx(
17634
+ "span",
17635
+ {
17636
+ className: cn("h-px flex-1", unread ? "bg-primary/35" : "bg-foreground/[0.08]"),
17637
+ "aria-hidden": "true"
17638
+ }
17639
+ ),
17640
+ /* @__PURE__ */ jsx(
17641
+ "span",
17642
+ {
17643
+ className: cn(
17644
+ "whitespace-nowrap",
17645
+ unread ? "text-[11px] leading-[14px] font-semibold tracking-[0.04em] text-primary uppercase" : "text-[11.5px] leading-[15px] font-medium text-muted-foreground"
17646
+ ),
17647
+ children: label
17648
+ }
17649
+ ),
17650
+ /* @__PURE__ */ jsx(
17651
+ "span",
17652
+ {
17653
+ className: cn("h-px flex-1", unread ? "bg-primary/35" : "bg-foreground/[0.08]"),
17654
+ "aria-hidden": "true"
17655
+ }
17656
+ )
17657
+ ] });
17658
+ }
17659
+
17660
+ // src/react/chat-kit/timeline.ts
17661
+ function sameDay(a, b) {
17662
+ return new Date(a).toDateString() === new Date(b).toDateString();
17663
+ }
17664
+ function dayLabel(at) {
17665
+ const date = new Date(at);
17666
+ const today = /* @__PURE__ */ new Date();
17667
+ if (date.toDateString() === today.toDateString()) return "Today";
17668
+ const yesterday = new Date(today);
17669
+ yesterday.setDate(today.getDate() - 1);
17670
+ if (date.toDateString() === yesterday.toDateString()) return "Yesterday";
17671
+ return date.toLocaleDateString([], { weekday: "short", day: "numeric", month: "short" });
17672
+ }
17673
+ function proseOf(content) {
17674
+ return splitGenUIContent(content).filter((segment) => segment.kind === "prose").map((segment) => segment.text).join("\n\n").trim();
17675
+ }
17676
+ function hasProgram(content) {
17677
+ return splitGenUIContent(content).some((segment) => segment.kind === "program");
17678
+ }
17679
+ function chatTimeline(messages, options = {}) {
17680
+ const { readAt = null, rendersPrograms = false } = options;
17681
+ const partitioned = partitionMessages(messages);
17682
+ const bubbles = partitioned.bubbles.filter(
17683
+ (message) => message.sender_type === "system" || message.attachments.length > 0 || chatMessageCards(message).length > 0 || rendersPrograms && hasProgram(message.content) || (message.sender_type === "customer" ? message.content.trim() !== "" : proseOf(message.content) !== "")
17684
+ );
17685
+ let firstUnreadId;
17686
+ let unreadOnOpen = 0;
17687
+ if (readAt) {
17688
+ const readAtMs = new Date(readAt).getTime();
17689
+ for (const message of bubbles) {
17690
+ if (message.sender_type === "customer" || new Date(message.created_at).getTime() <= readAtMs)
17691
+ continue;
17692
+ firstUnreadId ?? (firstUnreadId = message.id);
17693
+ unreadOnOpen += 1;
17694
+ }
17695
+ }
17696
+ return {
17697
+ bubbles,
17698
+ reactionsByTarget: partitioned.reactionsByTarget,
17699
+ firstUnreadId,
17700
+ unreadOnOpen,
17701
+ firstAgentMessageId: bubbles.find((m) => m.sender_type === "agent")?.id
17702
+ };
17703
+ }
17704
+ function groupPosition(timeline, index) {
17705
+ const { bubbles, firstAgentMessageId, firstUnreadId } = timeline;
17706
+ const message = bubbles[index];
17707
+ const previous = bubbles[index - 1];
17708
+ const next = bubbles[index + 1];
17709
+ 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";
17710
+ const endsRun = !next || next.sender_type !== message.sender_type || next.sender_type === "system" || !sameDay(message.created_at, next.created_at);
17711
+ if (startsRun && endsRun) return "single";
17712
+ if (startsRun) return "first";
17713
+ if (endsRun) return "last";
17714
+ return "middle";
17715
+ }
17716
+ function deliveryState(message, timeline, seenAt, isOptimistic2) {
17717
+ if (message.sender_type !== "customer") return void 0;
17718
+ if (isOptimistic2(message)) return "sending";
17719
+ const seenCutoff = timeline.firstAgentMessageId ? seenAt : null;
17720
+ return seenCutoff && message.created_at <= seenCutoff ? "seen" : "delivered";
17139
17721
  }
17140
17722
  var MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
17723
+ var KEEP_DISMISSED_KEY = "cimplify_keep_chat_dismissed";
17724
+ var AT_BOTTOM_SLACK_PX = 48;
17141
17725
  function ChatWidget({
17142
17726
  client: clientProp,
17143
17727
  businessName = "Support",
@@ -17156,6 +17740,7 @@ function ChatWidget({
17156
17740
  const [open, setOpen] = useState(defaultOpen);
17157
17741
  const [readAt, setReadAt] = useState(null);
17158
17742
  const [attachError, setAttachError] = useState(null);
17743
+ const [replyTo, setReplyTo] = useState(null);
17159
17744
  const {
17160
17745
  messages,
17161
17746
  isLoading,
@@ -17164,11 +17749,15 @@ function ChatWidget({
17164
17749
  seenAt,
17165
17750
  error,
17166
17751
  isActive,
17752
+ humanRequested,
17167
17753
  claimedAs,
17168
17754
  signOut,
17169
17755
  send,
17170
17756
  react,
17757
+ requestHuman,
17171
17758
  startConversation,
17759
+ loadOlder,
17760
+ mayHaveOlder,
17172
17761
  openIfExisting
17173
17762
  } = useChat({ client, pollInterval });
17174
17763
  const showTyping = isSending || awaitingReply;
@@ -17203,9 +17792,11 @@ function ChatWidget({
17203
17792
  const handleSend = useCallback(
17204
17793
  (content) => {
17205
17794
  setAttachError(null);
17206
- return send(content);
17795
+ const target = replyTo;
17796
+ setReplyTo(null);
17797
+ return send(content, target ? { replyTo: target } : void 0);
17207
17798
  },
17208
- [send]
17799
+ [send, replyTo]
17209
17800
  );
17210
17801
  const unread = open ? 0 : messages.filter(
17211
17802
  (m) => m.sender_type !== "customer" && (!readAt || m.created_at > readAt)
@@ -17244,6 +17835,7 @@ function ChatWidget({
17244
17835
  open && /* @__PURE__ */ jsx(
17245
17836
  Panel,
17246
17837
  {
17838
+ client,
17247
17839
  businessName,
17248
17840
  greeting,
17249
17841
  subtitle,
@@ -17254,14 +17846,22 @@ function ChatWidget({
17254
17846
  isSending,
17255
17847
  isTyping: showTyping,
17256
17848
  seenAt,
17849
+ readAt,
17257
17850
  error: error ?? attachError,
17258
17851
  isActive,
17259
17852
  isLeft,
17853
+ humanRequested,
17854
+ mayHaveOlder,
17855
+ replyTo,
17260
17856
  claimedAs,
17261
17857
  onSignOut: () => void signOut(),
17262
17858
  onClose: handleClose,
17263
17859
  onSend: handleSend,
17264
17860
  onReact: react,
17861
+ onReply: setReplyTo,
17862
+ onCancelReply: () => setReplyTo(null),
17863
+ onRequestHuman: () => void requestHuman(),
17864
+ onLoadOlder: () => void loadOlder(),
17265
17865
  onAttach: attachPhoto,
17266
17866
  onStartConversation: startConversation,
17267
17867
  classNames
@@ -17296,7 +17896,7 @@ function Bubble({
17296
17896
  {
17297
17897
  className: cn(
17298
17898
  "absolute -right-0.5 -top-0.5 flex h-5 w-5 items-center justify-center",
17299
- "rounded-full bg-red-500 text-[10px] font-bold text-white",
17899
+ "rounded-full bg-destructive text-[10px] font-bold text-white",
17300
17900
  "ring-2 ring-background"
17301
17901
  ),
17302
17902
  children: unread > 9 ? "9+" : unread
@@ -17307,6 +17907,7 @@ function Bubble({
17307
17907
  );
17308
17908
  }
17309
17909
  function Panel({
17910
+ client,
17310
17911
  businessName,
17311
17912
  greeting,
17312
17913
  subtitle,
@@ -17317,14 +17918,22 @@ function Panel({
17317
17918
  isSending,
17318
17919
  isTyping,
17319
17920
  seenAt,
17921
+ readAt,
17320
17922
  error,
17321
17923
  isActive,
17322
17924
  isLeft,
17925
+ humanRequested,
17926
+ mayHaveOlder,
17927
+ replyTo,
17323
17928
  claimedAs,
17324
17929
  onSignOut,
17325
17930
  onClose,
17326
17931
  onSend,
17327
17932
  onReact,
17933
+ onReply,
17934
+ onCancelReply,
17935
+ onRequestHuman,
17936
+ onLoadOlder,
17328
17937
  onAttach,
17329
17938
  onStartConversation,
17330
17939
  classNames
@@ -17364,20 +17973,57 @@ function Panel({
17364
17973
  MessageList,
17365
17974
  {
17366
17975
  messages,
17976
+ businessName,
17367
17977
  isLoading,
17368
17978
  isSending: isTyping,
17369
17979
  seenAt,
17980
+ readAt,
17981
+ humanRequested,
17982
+ mayHaveOlder,
17370
17983
  onReact,
17984
+ onReply,
17985
+ onRequestHuman,
17986
+ onLoadOlder,
17371
17987
  className: classNames?.messages
17372
17988
  }
17373
17989
  ),
17374
- error != null && /* @__PURE__ */ 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." }),
17990
+ isActive && !claimedAs && client && /* @__PURE__ */ jsx(KeepConversation, { client }),
17991
+ humanRequested && isActive && /* @__PURE__ */ 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: [
17992
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 flex-shrink-0 animate-pulse rounded-full bg-amber-500" }),
17993
+ "Waiting for ",
17994
+ businessName,
17995
+ " \u2014 usually replies in minutes"
17996
+ ] }),
17997
+ error != null && /* @__PURE__ */ 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." }),
17998
+ replyTo && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 border-t border-border bg-muted/50 px-4 py-2", children: [
17999
+ /* @__PURE__ */ jsx("span", { className: "w-[3px] shrink-0 self-stretch rounded-full bg-foreground/40" }),
18000
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
18001
+ /* @__PURE__ */ jsxs("div", { className: "text-[11px] font-semibold text-foreground", children: [
18002
+ "Replying to ",
18003
+ replyTo.sender_type === "customer" ? "yourself" : businessName
18004
+ ] }),
18005
+ /* @__PURE__ */ jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: proseOf(replyTo.content) || "Photo" })
18006
+ ] }),
18007
+ /* @__PURE__ */ jsx(
18008
+ "button",
18009
+ {
18010
+ type: "button",
18011
+ "aria-label": "Cancel reply",
18012
+ onClick: onCancelReply,
18013
+ className: "flex-shrink-0 p-0.5 text-muted-foreground hover:text-foreground",
18014
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3.5 w-3.5" })
18015
+ }
18016
+ )
18017
+ ] }),
17375
18018
  /* @__PURE__ */ jsx(
17376
18019
  ChatInput,
17377
18020
  {
17378
18021
  placeholder,
17379
18022
  isSending,
17380
- onSend: isActive ? onSend : onStartConversation,
18023
+ onSend: isActive ? onSend : async (text) => {
18024
+ await onStartConversation(text);
18025
+ return true;
18026
+ },
17381
18027
  onAttach,
17382
18028
  className: classNames?.input
17383
18029
  }
@@ -17406,10 +18052,10 @@ function Header({
17406
18052
  className
17407
18053
  ),
17408
18054
  children: [
17409
- /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-white/15", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-5 w-5" }) }),
18055
+ /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-background/15", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-5 w-5" }) }),
17410
18056
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
17411
18057
  /* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
17412
- claimedAs ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-white/60", children: [
18058
+ claimedAs ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-background/60", children: [
17413
18059
  /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
17414
18060
  "Chatting as ",
17415
18061
  claimedAs
@@ -17420,11 +18066,11 @@ function Header({
17420
18066
  {
17421
18067
  type: "button",
17422
18068
  onClick: onSignOut,
17423
- className: "flex-shrink-0 underline underline-offset-2 hover:text-white",
18069
+ className: "flex-shrink-0 underline underline-offset-2 hover:text-background",
17424
18070
  children: "Not you?"
17425
18071
  }
17426
18072
  )
17427
- ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-white/60", children: [
18073
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-background/60", children: [
17428
18074
  /* @__PURE__ */ jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
17429
18075
  "Online"
17430
18076
  ] })
@@ -17437,7 +18083,7 @@ function Header({
17437
18083
  "aria-label": "Close chat",
17438
18084
  className: cn(
17439
18085
  "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg",
17440
- "bg-white/10 text-white/70 transition-colors hover:bg-white/20 hover:text-white"
18086
+ "bg-background/10 text-background/70 transition-colors hover:bg-background/20 hover:text-background"
17441
18087
  ),
17442
18088
  children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-[18px] w-[18px]" })
17443
18089
  }
@@ -17492,148 +18138,109 @@ function Welcome({
17492
18138
  );
17493
18139
  }
17494
18140
  var QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F"];
17495
- function lastSeenCustomerMessageId(bubbles, seenAt) {
17496
- if (!seenAt || !bubbles.some((m) => m.sender_type === "agent")) return null;
17497
- for (let i = bubbles.length - 1; i >= 0; i--) {
17498
- if (bubbles[i].sender_type !== "customer") continue;
17499
- return bubbles[i].created_at <= seenAt ? bubbles[i].id : null;
17500
- }
17501
- return null;
18141
+ function LetterAvatar({ name }) {
18142
+ return /* @__PURE__ */ 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() });
17502
18143
  }
17503
18144
  function MessageList({
17504
18145
  messages,
18146
+ businessName,
17505
18147
  isLoading,
17506
18148
  isSending,
17507
18149
  seenAt,
18150
+ readAt,
18151
+ humanRequested,
18152
+ mayHaveOlder,
17508
18153
  onReact,
18154
+ onReply,
18155
+ onRequestHuman,
18156
+ onLoadOlder,
17509
18157
  className
17510
18158
  }) {
17511
- const { bubbles, reactionsByTarget } = partitionMessages(messages);
17512
- const seenMessageId = lastSeenCustomerMessageId(bubbles, seenAt);
18159
+ const scrolledUpRef = useRef(false);
18160
+ const timeline = chatTimeline(messages, { readAt });
18161
+ const { bubbles, reactionsByTarget, firstUnreadId, unreadOnOpen, firstAgentMessageId } = timeline;
18162
+ const lastBubble = bubbles[bubbles.length - 1];
18163
+ const offerHuman = !humanRequested && lastBubble?.sender_type === "bot" && !isSending;
18164
+ const handleScroll = (event) => {
18165
+ const node = event.currentTarget;
18166
+ scrolledUpRef.current = node.scrollHeight - node.scrollTop - node.clientHeight >= AT_BOTTOM_SLACK_PX;
18167
+ };
17513
18168
  return /* @__PURE__ */ jsxs(
17514
18169
  "div",
17515
18170
  {
17516
- className: cn(
17517
- "flex flex-1 flex-col gap-1 overflow-y-auto bg-muted/50 px-5 py-5",
17518
- className
17519
- ),
18171
+ onScroll: handleScroll,
18172
+ style: { background: "var(--color-chat-canvas, var(--color-muted, #f3f5f3))" },
18173
+ className: cn("flex flex-1 flex-col gap-0.5 overflow-y-auto px-3.5 py-3.5", className),
17520
18174
  children: [
17521
18175
  isLoading && messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
18176
+ mayHaveOlder && /* @__PURE__ */ jsx("div", { className: "flex justify-center pb-1", children: /* @__PURE__ */ jsx(
18177
+ "button",
18178
+ {
18179
+ type: "button",
18180
+ onClick: onLoadOlder,
18181
+ 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)]",
18182
+ children: "Show earlier messages"
18183
+ }
18184
+ ) }),
17522
18185
  bubbles.map((msg, i) => {
17523
- const isCustomer = msg.sender_type === "customer";
17524
18186
  const prev = bubbles[i - 1];
17525
- const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
17526
- const isOptimistic = msg.id.startsWith("opt_");
17527
- const reactions = reactionsByTarget.get(msg.id);
17528
- return /* @__PURE__ */ jsxs("div", { children: [
17529
- !sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsx("div", { className: "h-3" }),
17530
- /* @__PURE__ */ jsxs(
17531
- "div",
17532
- {
17533
- className: cn(
17534
- "group relative flex max-w-[85%]",
17535
- isCustomer ? "ml-auto" : "mr-auto"
18187
+ const newDay = !prev || !sameDay(prev.created_at, msg.created_at);
18188
+ const group = groupPosition(timeline, i);
18189
+ const startsRun = group === "single" || group === "first";
18190
+ const delivery = deliveryState(msg, timeline, seenAt, isOptimistic);
18191
+ return /* @__PURE__ */ jsxs(
18192
+ "div",
18193
+ {
18194
+ className: cn("flex flex-col gap-1.5", startsRun && i > 0 && "mt-2.5"),
18195
+ children: [
18196
+ newDay && /* @__PURE__ */ jsx(Seam, { variant: "chip", label: dayLabel(msg.created_at) }),
18197
+ msg.id === firstUnreadId && /* @__PURE__ */ jsx(
18198
+ Seam,
18199
+ {
18200
+ variant: "unread",
18201
+ label: `${unreadOnOpen} new message${unreadOnOpen === 1 ? "" : "s"}`
18202
+ }
17536
18203
  ),
17537
- children: [
17538
- !isCustomer && !isOptimistic && /* @__PURE__ */ 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__ */ jsx(
17539
- "button",
17540
- {
17541
- type: "button",
17542
- "aria-label": `React ${emoji}`,
17543
- onClick: () => void onReact(msg, emoji),
17544
- className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
17545
- children: emoji
17546
- },
17547
- emoji
17548
- )) }),
17549
- /* @__PURE__ */ jsxs(
17550
- "div",
18204
+ msg.id === firstAgentMessageId && /* @__PURE__ */ jsx(Seam, { label: "A person joined" }),
18205
+ msg.sender_type === "system" ? /* @__PURE__ */ jsx(Seam, { label: "Passing you to the team" }) : /* @__PURE__ */ jsxs("div", { className: "group relative", children: [
18206
+ msg.sender_type !== "customer" && !isOptimistic(msg) && /* @__PURE__ */ jsx(HoverRail, { message: msg, onReact, onReply }),
18207
+ /* @__PURE__ */ jsx(
18208
+ MessageBubble,
17551
18209
  {
17552
- className: cn(
17553
- "rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
17554
- isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
17555
- isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
17556
- isOptimistic && "opacity-70"
17557
- ),
17558
- children: [
17559
- !isCustomer && !sameSenderAsPrev && /* @__PURE__ */ 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: [
17560
- /* @__PURE__ */ jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
17561
- "AI"
17562
- ] }),
17563
- msg.reply_to && /* @__PURE__ */ jsxs(
17564
- "div",
17565
- {
17566
- className: cn(
17567
- "mb-1.5 flex gap-1.5 rounded-lg px-2 py-1 text-xs",
17568
- isCustomer ? "bg-background/15" : "bg-muted"
17569
- ),
17570
- children: [
17571
- /* @__PURE__ */ jsx(
17572
- "span",
17573
- {
17574
- className: cn(
17575
- "w-[3px] shrink-0 self-stretch rounded-full",
17576
- isCustomer ? "bg-background/40" : "bg-muted-foreground/40"
17577
- )
17578
- }
17579
- ),
17580
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate opacity-80", children: msg.reply_to.preview })
17581
- ]
17582
- }
17583
- ),
17584
- msg.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-1.5", msg.content && "mb-1.5"), children: msg.attachments.map((attachment, index) => {
17585
- const url = safeAttachmentUrl(attachment.url);
17586
- if (!url) return null;
17587
- return attachment.mime_type?.startsWith("image/") ? /* @__PURE__ */ jsx("a", { href: url, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx(
17588
- "img",
17589
- {
17590
- src: url,
17591
- alt: attachment.filename ?? "Photo",
17592
- loading: "lazy",
17593
- className: "max-h-48 max-w-full rounded-lg object-cover"
17594
- }
17595
- ) }, index) : /* @__PURE__ */ jsx(
17596
- "a",
17597
- {
17598
- href: url,
17599
- target: "_blank",
17600
- rel: "noreferrer",
17601
- className: "truncate text-xs underline",
17602
- children: attachment.filename ?? "Attachment"
17603
- },
17604
- index
17605
- );
17606
- }) }),
17607
- isCustomer ? msg.content && /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap", children: msg.content }) : /* @__PURE__ */ jsx(
17608
- ChatMarkdown,
17609
- {
17610
- text: msg.content,
17611
- 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"
17612
- }
17613
- )
17614
- ]
18210
+ message: msg,
18211
+ reactions: reactionsByTarget.get(msg.id),
18212
+ businessName,
18213
+ group,
18214
+ delivery,
18215
+ onReply,
18216
+ avatar: /* @__PURE__ */ jsx(LetterAvatar, { name: businessName }),
18217
+ onQuoteMiss: async () => {
18218
+ onLoadOlder();
18219
+ return true;
18220
+ }
17615
18221
  }
17616
18222
  )
17617
- ]
17618
- }
17619
- ),
17620
- reactions && reactions.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("mt-0.5 flex gap-1", isCustomer ? "justify-end" : "justify-start"), children: reactions.map((reaction) => /* @__PURE__ */ jsx(
17621
- "span",
17622
- {
17623
- className: "rounded-full border border-border bg-background px-1.5 py-0.5 text-xs shadow-sm",
17624
- children: reaction.content
17625
- },
17626
- reaction.id
17627
- )) }),
17628
- msg.id === seenMessageId && /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-right text-[10px] font-medium text-muted-foreground", children: "Seen" })
17629
- ] }, msg.id);
18223
+ ] })
18224
+ ]
18225
+ },
18226
+ msg.id
18227
+ );
17630
18228
  }),
17631
18229
  isSending && /* @__PURE__ */ jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
18230
+ offerHuman && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
18231
+ "button",
18232
+ {
18233
+ type: "button",
18234
+ onClick: onRequestHuman,
18235
+ className: "rounded-full border border-border bg-background px-3 py-1.5 text-xs font-semibold text-foreground hover:border-foreground",
18236
+ children: "Talk to a person"
18237
+ }
18238
+ ) }),
17632
18239
  /* @__PURE__ */ jsx(
17633
18240
  "div",
17634
18241
  {
17635
18242
  ref: (node) => {
17636
- node?.scrollIntoView({ behavior: "smooth" });
18243
+ if (node && !scrolledUpRef.current) node.scrollIntoView({ behavior: "smooth" });
17637
18244
  }
17638
18245
  },
17639
18246
  `${messages.length}:${isSending}`
@@ -17642,6 +18249,172 @@ function MessageList({
17642
18249
  }
17643
18250
  );
17644
18251
  }
18252
+ function HoverRail({
18253
+ message,
18254
+ onReact,
18255
+ onReply
18256
+ }) {
18257
+ return /* @__PURE__ */ 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: [
18258
+ QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsx(
18259
+ "button",
18260
+ {
18261
+ type: "button",
18262
+ "aria-label": `React ${emoji}`,
18263
+ onClick: () => void onReact(message, emoji),
18264
+ className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
18265
+ children: emoji
18266
+ },
18267
+ emoji
18268
+ )),
18269
+ /* @__PURE__ */ jsx(
18270
+ "button",
18271
+ {
18272
+ type: "button",
18273
+ "aria-label": "Reply",
18274
+ onClick: () => onReply(message),
18275
+ className: "flex h-6 w-6 items-center justify-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground",
18276
+ children: /* @__PURE__ */ jsx(ReplyIcon, { className: "h-3.5 w-3.5" })
18277
+ }
18278
+ )
18279
+ ] });
18280
+ }
18281
+ function readKeepDismissed() {
18282
+ try {
18283
+ return typeof window !== "undefined" && window.localStorage.getItem(KEEP_DISMISSED_KEY) === "1";
18284
+ } catch {
18285
+ return false;
18286
+ }
18287
+ }
18288
+ function writeKeepDismissed() {
18289
+ try {
18290
+ window.localStorage.setItem(KEEP_DISMISSED_KEY, "1");
18291
+ } catch {
18292
+ }
18293
+ }
18294
+ function KeepConversation({ client }) {
18295
+ const [step, setStep] = useState("row");
18296
+ const [dismissed, setDismissed] = useState(readKeepDismissed);
18297
+ const [contact, setContact] = useState("");
18298
+ const [code, setCode] = useState("");
18299
+ const [busy, setBusy] = useState(false);
18300
+ const [verifyError, setVerifyError] = useState(null);
18301
+ if (dismissed) return null;
18302
+ const contactType = contact.includes("@") ? "email" : "phone";
18303
+ const requestCode = async () => {
18304
+ const trimmed = contact.trim();
18305
+ if (!trimmed || busy) return;
18306
+ setBusy(true);
18307
+ setVerifyError(null);
18308
+ const result = await client.link.requestOtp({ contact: trimmed, contact_type: contactType });
18309
+ setBusy(false);
18310
+ if (result.ok) {
18311
+ setCode("");
18312
+ setStep("code");
18313
+ } else {
18314
+ setVerifyError(result.error.userMessage || "Couldn't send a code \u2014 try again.");
18315
+ }
18316
+ };
18317
+ const verify = async (value) => {
18318
+ if (value.length !== 6 || busy) return;
18319
+ setBusy(true);
18320
+ setVerifyError(null);
18321
+ const result = await client.link.verifyOtp({
18322
+ contact: contact.trim(),
18323
+ contact_type: contactType,
18324
+ otp_code: value
18325
+ });
18326
+ setBusy(false);
18327
+ if (!result.ok) {
18328
+ setCode("");
18329
+ setVerifyError(result.error.userMessage || "Invalid or expired code.");
18330
+ }
18331
+ };
18332
+ return /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-t border-border bg-muted/50 px-4 py-2.5", children: [
18333
+ step === "row" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
18334
+ /* @__PURE__ */ jsxs(
18335
+ "button",
18336
+ {
18337
+ type: "button",
18338
+ onClick: () => setStep("contact"),
18339
+ className: "flex min-w-0 flex-1 flex-col text-left",
18340
+ children: [
18341
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-foreground", children: "Keep this conversation" }),
18342
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: "Verify your number and it follows you \u2014 any device" })
18343
+ ]
18344
+ }
18345
+ ),
18346
+ /* @__PURE__ */ jsx(
18347
+ "button",
18348
+ {
18349
+ type: "button",
18350
+ "aria-label": "Dismiss",
18351
+ onClick: () => {
18352
+ writeKeepDismissed();
18353
+ setDismissed(true);
18354
+ },
18355
+ className: "flex-shrink-0 p-1 text-muted-foreground hover:text-foreground",
18356
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3.5 w-3.5" })
18357
+ }
18358
+ )
18359
+ ] }),
18360
+ step === "contact" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
18361
+ /* @__PURE__ */ jsx(
18362
+ "input",
18363
+ {
18364
+ type: "text",
18365
+ inputMode: "email",
18366
+ value: contact,
18367
+ onChange: (e) => setContact(e.target.value),
18368
+ onKeyDown: (e) => e.key === "Enter" && void requestCode(),
18369
+ placeholder: "Phone or email",
18370
+ autoFocus: true,
18371
+ 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"
18372
+ }
18373
+ ),
18374
+ /* @__PURE__ */ jsx(
18375
+ "button",
18376
+ {
18377
+ type: "button",
18378
+ onClick: () => void requestCode(),
18379
+ disabled: !contact.trim() || busy,
18380
+ className: "h-9 flex-shrink-0 rounded-lg bg-foreground px-3 text-xs font-semibold text-background disabled:opacity-50",
18381
+ children: busy ? "Sending\u2026" : "Send code"
18382
+ }
18383
+ )
18384
+ ] }),
18385
+ step === "code" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
18386
+ /* @__PURE__ */ jsx(
18387
+ "input",
18388
+ {
18389
+ type: "text",
18390
+ inputMode: "numeric",
18391
+ autoComplete: "one-time-code",
18392
+ maxLength: 6,
18393
+ value: code,
18394
+ onChange: (e) => {
18395
+ const digits = e.target.value.replace(/\D/g, "").slice(0, 6);
18396
+ setCode(digits);
18397
+ if (digits.length === 6) void verify(digits);
18398
+ },
18399
+ placeholder: "6-digit code",
18400
+ autoFocus: true,
18401
+ 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"
18402
+ }
18403
+ ),
18404
+ /* @__PURE__ */ jsx(
18405
+ "button",
18406
+ {
18407
+ type: "button",
18408
+ onClick: () => void requestCode(),
18409
+ disabled: busy,
18410
+ className: "h-9 flex-shrink-0 rounded-lg px-2 text-[11px] font-medium text-muted-foreground hover:text-foreground disabled:opacity-50",
18411
+ children: "Resend"
18412
+ }
18413
+ )
18414
+ ] }),
18415
+ verifyError && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-[11px] text-destructive", children: verifyError })
18416
+ ] });
18417
+ }
17645
18418
  function ChatInput({
17646
18419
  placeholder,
17647
18420
  isSending,
@@ -17664,14 +18437,15 @@ function ChatInput({
17664
18437
  },
17665
18438
  [onAttach]
17666
18439
  );
17667
- const handleSend = useCallback(() => {
18440
+ const handleSend = useCallback(async () => {
17668
18441
  const trimmed = value.trim();
17669
18442
  if (!trimmed || isSending) return;
17670
18443
  setValue("");
17671
18444
  if (textareaRef.current) {
17672
18445
  textareaRef.current.style.height = "auto";
17673
18446
  }
17674
- onSend(trimmed);
18447
+ const sent = await onSend(trimmed);
18448
+ if (!sent) setValue((current) => current || trimmed);
17675
18449
  }, [value, isSending, onSend]);
17676
18450
  const handleKeyDown = useCallback(
17677
18451
  (e) => {
@@ -17849,16 +18623,21 @@ function ImageIcon({ className }) {
17849
18623
  }
17850
18624
  );
17851
18625
  }
17852
- function SparkleIcon({ className }) {
17853
- return /* @__PURE__ */ jsx(
18626
+ function ReplyIcon({ className }) {
18627
+ return /* @__PURE__ */ jsxs(
17854
18628
  "svg",
17855
18629
  {
17856
18630
  className,
17857
18631
  viewBox: "0 0 24 24",
17858
18632
  fill: "none",
17859
18633
  stroke: "currentColor",
17860
- strokeWidth: 2.5,
17861
- children: /* @__PURE__ */ 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" })
18634
+ strokeWidth: 2,
18635
+ strokeLinecap: "round",
18636
+ strokeLinejoin: "round",
18637
+ children: [
18638
+ /* @__PURE__ */ jsx("polyline", { points: "9 17 4 12 9 7" }),
18639
+ /* @__PURE__ */ jsx("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
18640
+ ]
17862
18641
  }
17863
18642
  );
17864
18643
  }
@@ -19684,4 +20463,4 @@ async function genUIFingerprint(manifest) {
19684
20463
  return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 16);
19685
20464
  }
19686
20465
 
19687
- export { AccommodationCard, AccountContent, AccountDashboardPage, AccountHero, AccountIdentityStrip, AccountLineItem, AccountLineItems, AccountNav, AccountOrderDetailPage, AccountOrderView, AccountOrdersPage, AccountShell, AccountSidebar, AccountSignedOutPrompt, AccountSubscriptionDetailPage, AccountSubscriptionsPage, Ad, AdProvider, AddOnSelector, AddressAutocomplete, AddressElement, AuthElement, AvailabilityBadge, BackLink, BillingPlanSelector, BookingCard, BookingList, BookingPage, BookingSummary, BookingsPage, BoxIcon24, BundleProductCard, BundleProductLayout, BundleSelector, Button, CIMPLIFY_GENUI_REGISTRY, CardImage, CardShell, CardVariant, CartDrawer, CartDrawerProvider, CartPage, CartSummary, CartTemplate, CatalogueCollectionLayout, CataloguePage, CatalogueTemplate, CategoryFilter, CategoryGrid, ChatMarkdown, ChatWidget, CheckIcon, CheckoutPage, CimplifyCheckout, CimplifyProvider, CimplifySignInButton, CimplifyUserButton, CollectionPage, CollectionTemplate, CompactCartLayout, CompactCatalogueLayout, CompactSearchLayout, CompactServiceCard, CompositeProductCard, CompositeProductLayout, CompositeSelector, ContextCard, ContinuityCard, CurrencySelector, CustomAttributesTable, CustomerInputFields, DashboardIcon, DashboardIcon20, DatePicker, DateSlotPicker, DealBanner, DealsPage, DefaultCartLayout, DefaultCatalogueLayout, DefaultCollectionLayout, DefaultProductLayout, DefaultSearchLayout, DeliveryEstimate, DeliveryTracker, DeliveryTruckIcon, DigitalProductCard, DigitalProductLayout, DiscountInput, ElementsProvider, EmptyState, ErrorState, FeaturedCollectionLayout, FilterTabs, FoodProductCard, FoodProductLayout, GenUIView, HappeningCard, IdentityIcon, IdentityOfframp, InventoryBadge, ItemsCard, LeaseServiceCard, LineItemDetails, LocationPicker, MediaGallery, MetadataStringList, MobileAccountShell, MobileBackButton, MobileMoreButton, MobileTabBar, NextDeliveryCard, OrderDetailHeader, OrderDetailMetaPipe, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderList, OrderRow, OrderSummary, OrderTimeline, OrderTotals, OrdersIcon, OrdersIcon20, OvenIcon, PackageIcon, PaymentElement, Price, PriceRange, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductModel3D, ProductPage, ProductSheet, ProductTemplate, PropertiesTable, QuantitySelector, QuickAddButton, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, RichText, SaleBadge, SalePrice, SaleTeaser, ScheduleServiceCard, SearchInput, SearchPage, SearchTemplate, Section, SectionMore, ServiceProductLayout, SessionMessageBanner, SignOutIcon, Skeleton, SlotPicker, SoldOutOverlay, StaffPicker, StandardServiceCard, Status, StoreNav, StoreVideo, SubscriptionCard, SubscriptionList, SubscriptionRow, SubscriptionsIcon, SubscriptionsIcon20, TagPills, TimePicker, Toast, TwoColumnGrid, VariantSelector, VolumePricing, WelcomeCard, WholesaleProductCard, WholesaleProductLayout, WishlistButton, clearOrdersCache, cn, createPlacesSessionToken, defaultBoundaryKind, genUICatalog, genUIFingerprint, genUIManifest, genUIManifestJson, getBoundaryLabels, isImageAttachment, isTerminalOrderStatus, parse as parseGenUIProgram, partitionMessages, prefetchProduct, prefetchProducts, resolveBoundaryKind, roomToResource, safeAttachmentUrl, sanitizeRichTextHtml, stripHtmlToText, toAccountLineItem, toAccountLineItems, toAccountOrderView, useActivityState, useAds, useAttributeDefinitions, useAvailableSlots, useBillingPlans, useBookings, useBootstrap, useBundles, useCart, useCartDrawer, useCategories, useChat, useCheckout, useCimplify, useCimplifyClient, useCimplifySession, useCollection, useCollections, useDeals, useDeliveryFee, useDeliveryOptions, useElements, useElementsReady, useFxRate, useGenUIProgram, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, usePropertyFacets, useQuote, useRecommendations, useSearch, useServiceAvailability, useServices, useStoreCredit, useSubscription, useSubscriptions, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };
20466
+ export { AccommodationCard, AccountContent, AccountDashboardPage, AccountHero, AccountIdentityStrip, AccountLineItem, AccountLineItems, AccountNav, AccountOrderDetailPage, AccountOrderView, AccountOrdersPage, AccountShell, AccountSidebar, AccountSignedOutPrompt, AccountSubscriptionDetailPage, AccountSubscriptionsPage, Ad, AdProvider, AddOnSelector, AddressAutocomplete, AddressElement, AuthElement, AvailabilityBadge, BackLink, BillingPlanSelector, BookingCard, BookingList, BookingPage, BookingSummary, BookingsPage, BoxIcon24, BubbleTail, BundleProductCard, BundleProductLayout, BundleSelector, Button, CIMPLIFY_GENUI_REGISTRY, CardImage, CardShell, CardVariant, CartDrawer, CartDrawerProvider, CartPage, CartSummary, CartTemplate, CatalogueCollectionLayout, CataloguePage, CatalogueTemplate, CategoryFilter, CategoryGrid, ChatMarkdown, ChatWidget, CheckIcon, CheckoutPage, CimplifyCheckout, CimplifyProvider, CimplifySignInButton, CimplifyUserButton, CollectionPage, CollectionTemplate, CompactCartLayout, CompactCatalogueLayout, CompactSearchLayout, CompactServiceCard, CompositeProductCard, CompositeProductLayout, CompositeSelector, ContextCard, ContinuityCard, CurrencySelector, CustomAttributesTable, CustomerInputFields, DashboardIcon, DashboardIcon20, DatePicker, DateSlotPicker, DealBanner, DealsPage, DefaultCartLayout, DefaultCatalogueLayout, DefaultCollectionLayout, DefaultProductLayout, DefaultSearchLayout, DeliveryEstimate, DeliveryTracker, DeliveryTruckIcon, DigitalProductCard, DigitalProductLayout, DiscountInput, ElementsProvider, EmptyState, ErrorState, FeaturedCollectionLayout, FilterTabs, FoodProductCard, FoodProductLayout, GenUIView, HappeningCard, IdentityIcon, IdentityOfframp, InventoryBadge, ItemsCard, LeaseServiceCard, LineItemDetails, LocationPicker, MediaGallery, MessageBubble, MetadataStringList, MobileAccountShell, MobileBackButton, MobileMoreButton, MobileTabBar, NextDeliveryCard, OrderDetailHeader, OrderDetailMetaPipe, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderList, OrderRow, OrderSummary, OrderTimeline, OrderTotals, OrdersIcon, OrdersIcon20, OvenIcon, PackageIcon, PaymentElement, Price, PriceRange, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductModel3D, ProductPage, ProductSheet, ProductTemplate, PropertiesTable, QuantitySelector, QuickAddButton, QuoteBlock, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, RichText, SWIPE_COMMIT_PX, SaleBadge, SalePrice, SaleTeaser, ScheduleServiceCard, Seam, SearchInput, SearchPage, SearchTemplate, Section, SectionMore, ServiceProductLayout, SessionMessageBanner, SignOutIcon, Skeleton, SlotPicker, SoldOutOverlay, StaffPicker, StandardServiceCard, Status, StoreNav, StoreVideo, SubscriptionCard, SubscriptionList, SubscriptionRow, SubscriptionsIcon, SubscriptionsIcon20, TagPills, TimePicker, Toast, TwoColumnGrid, VariantSelector, VolumePricing, WelcomeCard, WholesaleProductCard, WholesaleProductLayout, WishlistButton, chatTimeline, clearOrdersCache, cn, createPlacesSessionToken, dayLabel, defaultBoundaryKind, deliveryState, genUICatalog, genUIFingerprint, genUIManifest, genUIManifestJson, getBoundaryLabels, groupPosition, isImageAttachment, isOptimistic, isTerminalOrderStatus, optimisticId, parse as parseGenUIProgram, partitionMessages, prefersReducedMotion, prefetchProduct, prefetchProducts, proseOf, resolveBoundaryKind, roomToResource, safeAttachmentUrl, sameDay, sanitizeRichTextHtml, scrollBehavior, stripHtmlToText, swipeCommitted, swipeProgress, swipeTravel, toAccountLineItem, toAccountLineItems, toAccountOrderView, useActivityState, useAds, useAttributeDefinitions, useAvailableSlots, useBillingPlans, useBookings, useBootstrap, useBundles, useCart, useCartDrawer, useCategories, useChat, useCheckout, useCimplify, useCimplifyClient, useCimplifySession, useCollection, useCollections, useDeals, useDeliveryFee, useDeliveryOptions, useElements, useElementsReady, useFxRate, useGenUIProgram, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, usePropertyFacets, useQuote, useRecommendations, useSearch, useServiceAvailability, useServices, useStoreCredit, useSubscription, useSubscriptions, useSwipeReply, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };