@sevenfold/setto-client 0.27.0 → 0.28.1

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.
@@ -5899,21 +5899,21 @@ var PostgresTypes;
5899
5899
  PostgresTypes2["tsrange"] = "tsrange";
5900
5900
  PostgresTypes2["tstzrange"] = "tstzrange";
5901
5901
  })(PostgresTypes || (PostgresTypes = {}));
5902
- const convertChangeData = (columns, record, options = {}) => {
5902
+ const convertChangeData = (columns, record2, options = {}) => {
5903
5903
  var _a;
5904
5904
  const skipTypes = (_a = options.skipTypes) !== null && _a !== void 0 ? _a : [];
5905
- if (!record) {
5905
+ if (!record2) {
5906
5906
  return {};
5907
5907
  }
5908
- return Object.keys(record).reduce((acc, rec_key) => {
5909
- acc[rec_key] = convertColumn(rec_key, columns, record, skipTypes);
5908
+ return Object.keys(record2).reduce((acc, rec_key) => {
5909
+ acc[rec_key] = convertColumn(rec_key, columns, record2, skipTypes);
5910
5910
  return acc;
5911
5911
  }, {});
5912
5912
  };
5913
- const convertColumn = (columnName, columns, record, skipTypes) => {
5913
+ const convertColumn = (columnName, columns, record2, skipTypes) => {
5914
5914
  const column = columns.find((x) => x.name === columnName);
5915
5915
  const colType = column === null || column === void 0 ? void 0 : column.type;
5916
- const value = record[columnName];
5916
+ const value = record2[columnName];
5917
5917
  if (colType && !skipTypes.includes(colType)) {
5918
5918
  return convertCell(colType, value);
5919
5919
  }
@@ -34654,6 +34654,139 @@ function remarkGfm(options) {
34654
34654
  fromMarkdownExtensions.push(gfmFromMarkdown());
34655
34655
  toMarkdownExtensions.push(gfmToMarkdown(settings));
34656
34656
  }
34657
+ const POLL_FALLBACK_MS = 5e3;
34658
+ const POLL_SUBSCRIBED_MS = 3e4;
34659
+ const CHANGED_DEBOUNCE_MS = 250;
34660
+ const TYPING_THROTTLE_MS = 2500;
34661
+ const TYPING_EXPIRY_MS = 6e3;
34662
+ function pollIntervalMs(subscribed) {
34663
+ return subscribed ? POLL_SUBSCRIBED_MS : POLL_FALLBACK_MS;
34664
+ }
34665
+ function conversationTopic(value) {
34666
+ return typeof value === "string" && value.length <= 200 && /^chat-conversation:\S+$/.test(value) ? value : null;
34667
+ }
34668
+ function isSide(value) {
34669
+ return value === "team" || value === "visitor";
34670
+ }
34671
+ function record(payload) {
34672
+ return payload && typeof payload === "object" ? payload : null;
34673
+ }
34674
+ function parseTypingSignal(payload) {
34675
+ const value = record(payload);
34676
+ if (!value || !isSide(value.side) || typeof value.typing !== "boolean") return null;
34677
+ return { side: value.side, typing: value.typing };
34678
+ }
34679
+ function parseReadSignal(payload) {
34680
+ const value = record(payload);
34681
+ if (!value || !isSide(value.side) || typeof value.at !== "string") return null;
34682
+ return timestampValue(value.at) === null ? null : { side: value.side, at: value.at };
34683
+ }
34684
+ const TIMESTAMP = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2})(:\d{2})?(?:\.(\d+))?\s*(Z|[+-]\d{2}(?::?\d{2})?)?$/i;
34685
+ function timestampValue(value) {
34686
+ if (!value) return null;
34687
+ const match = TIMESTAMP.exec(value.trim());
34688
+ if (!match) {
34689
+ const parsed = Date.parse(value);
34690
+ return Number.isFinite(parsed) ? parsed : null;
34691
+ }
34692
+ const [, date, minutes, seconds = ":00", fraction, zone] = match;
34693
+ let offset = "Z";
34694
+ if (zone && zone.toUpperCase() !== "Z") {
34695
+ const digits = zone.slice(1).replace(":", "");
34696
+ offset = `${zone[0]}${digits.slice(0, 2)}:${digits.slice(2, 4) || "00"}`;
34697
+ }
34698
+ const whole = Date.parse(`${date}T${minutes}${seconds}${offset}`);
34699
+ if (!Number.isFinite(whole)) return null;
34700
+ return fraction ? whole + Number(`0.${fraction}`) * 1e3 : whole;
34701
+ }
34702
+ function laterTimestamp(current, next) {
34703
+ const nextValue = timestampValue(next);
34704
+ if (nextValue === null) return current;
34705
+ const currentValue = timestampValue(current);
34706
+ return currentValue === null || nextValue > currentValue ? next : current;
34707
+ }
34708
+ function createTypingSender(send, now = Date.now) {
34709
+ let typing = false;
34710
+ let lastSentAt = Number.NEGATIVE_INFINITY;
34711
+ function reset() {
34712
+ typing = false;
34713
+ lastSentAt = Number.NEGATIVE_INFINITY;
34714
+ }
34715
+ function stop() {
34716
+ if (!typing) return;
34717
+ reset();
34718
+ send(false);
34719
+ }
34720
+ function update(draft) {
34721
+ if (!draft.trim()) {
34722
+ stop();
34723
+ return;
34724
+ }
34725
+ const at = now();
34726
+ if (typing && at - lastSentAt < TYPING_THROTTLE_MS) return;
34727
+ if (send(true)) {
34728
+ typing = true;
34729
+ lastSentAt = at;
34730
+ }
34731
+ }
34732
+ return { update, stop, reset };
34733
+ }
34734
+ function nextTeamTypingDeadline(current, signal, now) {
34735
+ if (signal.side !== "team") return current;
34736
+ return signal.typing ? now + TYPING_EXPIRY_MS : null;
34737
+ }
34738
+ function latestHumanMessageKey(messages) {
34739
+ for (let index2 = messages.length - 1; index2 >= 0; index2 -= 1) {
34740
+ const message = messages[index2];
34741
+ if (message?.role === "human") return message.id ?? message.created_at ?? null;
34742
+ }
34743
+ return null;
34744
+ }
34745
+ function teamTypingIdentity(messages) {
34746
+ for (let index2 = messages.length - 1; index2 >= 0; index2 -= 1) {
34747
+ const message = messages[index2];
34748
+ if (message && (message.role === "human" || message.kind === "agent_joined")) {
34749
+ return { name: message.authorName?.trim() || null, avatarUrl: message.avatarUrl || null };
34750
+ }
34751
+ }
34752
+ return { name: null, avatarUrl: null };
34753
+ }
34754
+ function teamTypingLabel(norwegian2, name2) {
34755
+ if (norwegian2) return name2 ? `${name2} skriver …` : "Teamet skriver …";
34756
+ return name2 ? `${name2} is typing …` : "The team is typing …";
34757
+ }
34758
+ function seenVisitorMessageIndex(messages, humanJoined, teamReadAt) {
34759
+ const readValue = humanJoined ? timestampValue(teamReadAt) : null;
34760
+ if (readValue === null) return -1;
34761
+ for (let index2 = messages.length - 1; index2 >= 0; index2 -= 1) {
34762
+ if (messages[index2]?.role !== "visitor") continue;
34763
+ const created = timestampValue(messages[index2]?.created_at);
34764
+ return created !== null && created <= readValue ? index2 : -1;
34765
+ }
34766
+ return -1;
34767
+ }
34768
+ function readMarkerThrough(messages, context) {
34769
+ if (!context.humanJoined || !context.chatOpen || !context.pageVisible) return null;
34770
+ let newest = null;
34771
+ let newestValue = Number.NEGATIVE_INFINITY;
34772
+ for (const message of messages) {
34773
+ if (message.role === "visitor") continue;
34774
+ const value = timestampValue(message.created_at);
34775
+ if (value !== null && value > newestValue) {
34776
+ newest = message.created_at;
34777
+ newestValue = value;
34778
+ }
34779
+ }
34780
+ if (newest === null) return null;
34781
+ const sentValue = timestampValue(context.lastSent);
34782
+ return sentValue !== null && newestValue <= sentValue ? null : newest;
34783
+ }
34784
+ function refreshOutcome(started, current) {
34785
+ if (started.conversationId !== current.conversationId) return "drop";
34786
+ if (started.request !== current.request) return "drop";
34787
+ if (current.busy || started.turn !== current.turn) return "defer";
34788
+ return "apply";
34789
+ }
34657
34790
  const VISITOR_KEY = "setto-chat-visitor";
34658
34791
  const ACTIVE_CONVERSATION_KEY = "setto-chat-active";
34659
34792
  const NAVIGATION_KEY = "setto-chat-navigation";
@@ -34697,6 +34830,9 @@ function sineOut(progress) {
34697
34830
  function isAtChatBottom(scrollHeight, scrollTop, clientHeight) {
34698
34831
  return scrollHeight - scrollTop - clientHeight <= 4;
34699
34832
  }
34833
+ function chatComposerSpace(height) {
34834
+ return Math.max(0, Math.ceil(height)) + 16;
34835
+ }
34700
34836
  function createWordRevealer(onReveal) {
34701
34837
  let target = "";
34702
34838
  let visibleLength = 0;
@@ -34843,6 +34979,25 @@ function AgentJoinedEvent({ message }) {
34843
34979
  /* @__PURE__ */ jsx("div", { className: "setto-chat__message-stack", children: /* @__PURE__ */ jsx("div", { className: "setto-chat__message-bubble", children: /* @__PURE__ */ jsx("span", { className: "setto-chat__message-copy", children: message.content }) }) })
34844
34980
  ] });
34845
34981
  }
34982
+ function TeamTypingRow({ label, avatarUrl }) {
34983
+ return /* @__PURE__ */ jsxs(
34984
+ "div",
34985
+ {
34986
+ className: "setto-chat__message setto-chat__message--human setto-chat__message--typing",
34987
+ "aria-live": "off",
34988
+ children: [
34989
+ /* @__PURE__ */ jsx("span", { className: "setto-chat__avatar setto-chat__avatar--human", "aria-hidden": "true", children: avatarUrl ? /* @__PURE__ */ jsx("img", { src: avatarUrl, alt: "" }) : /* @__PURE__ */ jsx(PersonIcon, {}) }),
34990
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__message-stack", children: [
34991
+ /* @__PURE__ */ jsx("span", { className: "setto-chat__speaker", children: label }),
34992
+ /* @__PURE__ */ jsx("div", { className: "setto-chat__message-bubble", children: /* @__PURE__ */ jsx("div", { className: "setto-chat__message-copy", children: /* @__PURE__ */ jsx("span", { className: "setto-chat__typing", "aria-hidden": "true", children: "···" }) }) })
34993
+ ] })
34994
+ ]
34995
+ }
34996
+ );
34997
+ }
34998
+ function SeenMarker({ label }) {
34999
+ return /* @__PURE__ */ jsx("span", { className: "setto-chat__receipt", "aria-live": "off", children: label });
35000
+ }
34846
35001
  function StaggeredText({ children }) {
34847
35002
  return Children.map(children, (child) => {
34848
35003
  if (typeof child !== "string") return child;
@@ -34884,6 +35039,7 @@ function SiteChatComposer({
34884
35039
  value,
34885
35040
  onChange,
34886
35041
  onSubmit,
35042
+ onBlur,
34887
35043
  busy,
34888
35044
  queued,
34889
35045
  placeholder,
@@ -34964,7 +35120,10 @@ function SiteChatComposer({
34964
35120
  onChange: (event) => onChange(event.target.value),
34965
35121
  onKeyDown: handleKeyDown,
34966
35122
  onFocus: () => setFocused(true),
34967
- onBlur: () => setFocused(false),
35123
+ onBlur: () => {
35124
+ setFocused(false);
35125
+ onBlur?.();
35126
+ },
34968
35127
  placeholder,
34969
35128
  rows: 1,
34970
35129
  maxLength: 1500
@@ -35235,8 +35394,88 @@ function useChatAutoScroll(active, logRef, pinToBottomRef, programmaticScrollRef
35235
35394
  };
35236
35395
  }, [active, logRef, pinToBottomRef, programmaticScrollRef]);
35237
35396
  }
35397
+ function usePageVisible() {
35398
+ const [pageVisible, setPageVisible] = useState(
35399
+ () => typeof document === "undefined" || document.visibilityState !== "hidden"
35400
+ );
35401
+ useEffect(() => {
35402
+ const update = () => setPageVisible(document.visibilityState !== "hidden");
35403
+ update();
35404
+ document.addEventListener("visibilitychange", update);
35405
+ return () => document.removeEventListener("visibilitychange", update);
35406
+ }, []);
35407
+ return pageVisible;
35408
+ }
35409
+ const channelRemovals = /* @__PURE__ */ new Map();
35410
+ function useConversationChannel(supabase, topic, handlers2) {
35411
+ const handlersRef = useRef(handlers2);
35412
+ const channelRef = useRef(null);
35413
+ useEffect(() => {
35414
+ handlersRef.current = handlers2;
35415
+ });
35416
+ useEffect(() => {
35417
+ if (!topic) return;
35418
+ let active = true;
35419
+ let channel = null;
35420
+ let changedTimer = 0;
35421
+ const join2 = () => {
35422
+ if (!active) return;
35423
+ channel = supabase.channel(topic).on("broadcast", { event: "changed" }, () => {
35424
+ window.clearTimeout(changedTimer);
35425
+ changedTimer = window.setTimeout(
35426
+ () => handlersRef.current.onChanged(),
35427
+ CHANGED_DEBOUNCE_MS
35428
+ );
35429
+ }).on("broadcast", { event: "read" }, (message) => {
35430
+ const signal = parseReadSignal(message.payload);
35431
+ if (signal) handlersRef.current.onRead(signal);
35432
+ }).on("broadcast", { event: "typing" }, (message) => {
35433
+ const signal = parseTypingSignal(message.payload);
35434
+ if (signal) handlersRef.current.onTyping(signal);
35435
+ });
35436
+ channelRef.current = channel;
35437
+ channel.subscribe((status) => {
35438
+ if (active) handlersRef.current.onStatus(status === "SUBSCRIBED");
35439
+ });
35440
+ };
35441
+ const previousRemoval = channelRemovals.get(topic);
35442
+ if (previousRemoval) void previousRemoval.then(join2);
35443
+ else join2();
35444
+ return () => {
35445
+ active = false;
35446
+ window.clearTimeout(changedTimer);
35447
+ if (!channel) return;
35448
+ handlersRef.current.onLeave();
35449
+ channelRef.current = null;
35450
+ handlersRef.current.onStatus(false);
35451
+ const removal = supabase.removeChannel(channel).catch(() => void 0).finally(() => {
35452
+ if (channelRemovals.get(topic) === removal) channelRemovals.delete(topic);
35453
+ });
35454
+ channelRemovals.set(topic, removal);
35455
+ };
35456
+ }, [supabase, topic]);
35457
+ return channelRef;
35458
+ }
35459
+ function useChatComposerSpace(active, composerRef, logRef) {
35460
+ useEffect(() => {
35461
+ if (!active) return;
35462
+ const composer = composerRef.current;
35463
+ const log = logRef.current;
35464
+ if (!composer || !log) return;
35465
+ const update = () => {
35466
+ log.style.setProperty("--setto-chat-composer-space", `${chatComposerSpace(composer.getBoundingClientRect().height)}px`);
35467
+ };
35468
+ const observer = new ResizeObserver(update);
35469
+ observer.observe(composer);
35470
+ update();
35471
+ return () => {
35472
+ observer.disconnect();
35473
+ log.style.removeProperty("--setto-chat-composer-space");
35474
+ };
35475
+ }, [active, composerRef, logRef]);
35476
+ }
35238
35477
  function SettoChat() {
35239
- const { config } = useSetto();
35478
+ const { config, supabase } = useSetto();
35240
35479
  const { i18n } = useTranslation();
35241
35480
  const apiUrl = config.apiUrl.replace(/\/+$/, "");
35242
35481
  const [settings, setSettings] = useState(null);
@@ -35259,16 +35498,62 @@ function SettoChat() {
35259
35498
  const visitor = useRef(null);
35260
35499
  const chatRef = useRef(null);
35261
35500
  const logRef = useRef(null);
35501
+ const composerShellRef = useRef(null);
35262
35502
  const pinToBottomRef = useRef(true);
35263
35503
  const programmaticScrollRef = useRef(false);
35264
35504
  const filesRef = useRef([]);
35505
+ const [realtime, setRealtime] = useState(null);
35506
+ const [subscribed, setSubscribed] = useState(false);
35507
+ const [teamTypingUntil, setTeamTypingUntil] = useState(null);
35508
+ const subscribedRef = useRef(false);
35509
+ const turnRef = useRef({ busy: false, generation: 0 });
35510
+ const refreshRequestRef = useRef(0);
35511
+ const pendingRefreshRef = useRef(false);
35512
+ const loadedConversationRef = useRef(null);
35513
+ const latestHumanRef = useRef(null);
35514
+ const readMarkersRef = useRef(/* @__PURE__ */ new Map());
35515
+ const refreshRef = useRef(async () => void 0);
35516
+ const typingSenderRef = useRef(null);
35265
35517
  const formId = useId();
35266
35518
  const palette = useMemo(() => chatPalette(config.brandColors), [config.brandColors]);
35267
35519
  const desktopPopup = useDesktopPopup();
35520
+ const pageVisible = usePageVisible();
35521
+ const channelRef = useConversationChannel(supabase, mounted ? realtime?.topic ?? null : null, {
35522
+ onStatus(next) {
35523
+ subscribedRef.current = next;
35524
+ setSubscribed(next);
35525
+ if (next) void refreshRef.current();
35526
+ else setTeamTypingUntil(null);
35527
+ },
35528
+ onChanged() {
35529
+ void refreshRef.current();
35530
+ },
35531
+ onRead(signal) {
35532
+ if (signal.side !== "team") return;
35533
+ setRealtime(
35534
+ (current) => current ? { ...current, teamReadAt: laterTimestamp(current.teamReadAt, signal.at) } : current
35535
+ );
35536
+ },
35537
+ onTyping(signal) {
35538
+ setTeamTypingUntil((current) => nextTeamTypingDeadline(current, signal, Date.now()));
35539
+ },
35540
+ onLeave() {
35541
+ typingSender().stop();
35542
+ }
35543
+ });
35544
+ function typingSender() {
35545
+ return typingSenderRef.current ??= createTypingSender((typing) => {
35546
+ const channel = channelRef.current;
35547
+ if (!channel || !subscribedRef.current || channel.state !== "joined") return false;
35548
+ void channel.send({ type: "broadcast", event: "typing", payload: { side: "visitor", typing } }).catch(() => void 0);
35549
+ return true;
35550
+ });
35551
+ }
35268
35552
  useChatStyles();
35269
35553
  useChatViewport(mounted && !desktopPopup, logRef, pinToBottomRef, programmaticScrollRef);
35270
35554
  useChatPinTracking(mounted, logRef, pinToBottomRef, programmaticScrollRef);
35271
35555
  useChatAutoScroll(mounted, logRef, pinToBottomRef, programmaticScrollRef);
35556
+ useChatComposerSpace(mounted, composerShellRef, logRef);
35272
35557
  useChatInputIsolation(Boolean(settings?.enabled), mounted, chatRef, logRef);
35273
35558
  const locale = i18n.language || "no";
35274
35559
  const isNo = norwegian(locale);
@@ -35288,6 +35573,7 @@ function SettoChat() {
35288
35573
  failed: "Jeg får ikke svart akkurat nå. Kontakt gjerne teamet direkte.",
35289
35574
  image: "Bilde",
35290
35575
  team: "Teamet",
35576
+ seen: "Sett",
35291
35577
  emptyChats: "Nye samtaler du starter her vises i denne listen."
35292
35578
  } : {
35293
35579
  open: siteName ? `Open chat with ${siteName}` : "Open chat",
@@ -35304,6 +35590,7 @@ function SettoChat() {
35304
35590
  failed: "I cannot reply right now. Please contact the team directly.",
35305
35591
  image: "Image",
35306
35592
  team: "The team",
35593
+ seen: "Seen",
35307
35594
  emptyChats: "New conversations you start here will appear in this list."
35308
35595
  };
35309
35596
  function ensureVisitor() {
@@ -35318,9 +35605,24 @@ function SettoChat() {
35318
35605
  }
35319
35606
  }
35320
35607
  function applyConversation(next) {
35608
+ const sameConversation = loadedConversationRef.current === next.conversationId;
35321
35609
  rememberConversation(next.conversationId);
35610
+ loadedConversationRef.current = next.conversationId;
35322
35611
  setHumanJoined(next.humanJoined);
35323
35612
  setMessages(next.messages);
35613
+ const supported = "realtimeTopic" in next || "teamReadAt" in next || "visitorReadAt" in next;
35614
+ const serverTeamReadAt = typeof next.teamReadAt === "string" ? next.teamReadAt : null;
35615
+ setRealtime((current) => ({
35616
+ conversationId: next.conversationId,
35617
+ supported,
35618
+ topic: conversationTopic(next.realtimeTopic),
35619
+ teamReadAt: current?.conversationId === next.conversationId ? laterTimestamp(current.teamReadAt, serverTeamReadAt) : serverTeamReadAt
35620
+ }));
35621
+ const latestHuman = latestHumanMessageKey(next.messages);
35622
+ if (!sameConversation || latestHuman && latestHuman !== latestHumanRef.current) {
35623
+ setTeamTypingUntil(null);
35624
+ }
35625
+ latestHumanRef.current = latestHuman;
35324
35626
  const navigation = [...next.messages].reverse().find((message) => message.kind === "navigation" && message.action?.type === "navigate");
35325
35627
  if (!navigation?.id || !navigation.action?.url) return;
35326
35628
  try {
@@ -35348,9 +35650,51 @@ function SettoChat() {
35348
35650
  } catch {
35349
35651
  }
35350
35652
  }
35653
+ async function refreshConversation() {
35654
+ const id = conversationId.current;
35655
+ const visitorIdValue = visitor.current;
35656
+ if (!id || !visitorIdValue) return;
35657
+ if (turnRef.current.busy) {
35658
+ pendingRefreshRef.current = true;
35659
+ return;
35660
+ }
35661
+ pendingRefreshRef.current = false;
35662
+ refreshRequestRef.current += 1;
35663
+ const ticket = {
35664
+ conversationId: id,
35665
+ turn: turnRef.current.generation,
35666
+ request: refreshRequestRef.current
35667
+ };
35668
+ try {
35669
+ const query = new URLSearchParams({ visitorId: visitorIdValue });
35670
+ const response = await fetch(
35671
+ `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/${encodeURIComponent(id)}/public?${query}`,
35672
+ { headers: { Accept: "application/json" } }
35673
+ );
35674
+ if (!response.ok) return;
35675
+ const next = await response.json();
35676
+ const outcome = refreshOutcome(ticket, {
35677
+ conversationId: conversationId.current,
35678
+ turn: turnRef.current.generation,
35679
+ request: refreshRequestRef.current,
35680
+ busy: turnRef.current.busy
35681
+ });
35682
+ if (outcome === "apply") {
35683
+ applyConversation(next);
35684
+ } else if (outcome === "defer") {
35685
+ pendingRefreshRef.current = true;
35686
+ if (!turnRef.current.busy) void refreshConversation();
35687
+ }
35688
+ } catch {
35689
+ }
35690
+ }
35691
+ useEffect(() => {
35692
+ refreshRef.current = refreshConversation;
35693
+ });
35351
35694
  async function loadConversation(id) {
35352
35695
  const visitorIdValue = ensureVisitor();
35353
35696
  setLoadingConversation(true);
35697
+ refreshRequestRef.current += 1;
35354
35698
  try {
35355
35699
  const query = new URLSearchParams({ visitorId: visitorIdValue });
35356
35700
  const response = await fetch(
@@ -35413,34 +35757,71 @@ function SettoChat() {
35413
35757
  },
35414
35758
  []
35415
35759
  );
35760
+ const polledConversation = realtime?.conversationId ?? null;
35416
35761
  useEffect(() => {
35417
35762
  if (!mounted || !conversationId.current || !visitor.current || sending) return;
35418
- let cancelled = false;
35419
- const refresh = async () => {
35420
- try {
35421
- const query = new URLSearchParams({ visitorId: visitor.current });
35422
- const response = await fetch(
35423
- `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/${encodeURIComponent(conversationId.current)}/public?${query}`,
35424
- { headers: { Accept: "application/json" } }
35425
- );
35426
- if (!response.ok || cancelled) return;
35427
- const next = await response.json();
35428
- if (!cancelled) applyConversation(next);
35429
- } catch {
35763
+ const timer = window.setInterval(() => {
35764
+ if (document.visibilityState === "hidden") return;
35765
+ void refreshRef.current();
35766
+ }, pollIntervalMs(subscribed));
35767
+ return () => window.clearInterval(timer);
35768
+ }, [mounted, sending, subscribed, polledConversation]);
35769
+ const pageWasVisibleRef = useRef(pageVisible);
35770
+ useEffect(() => {
35771
+ const returned = pageVisible && !pageWasVisibleRef.current;
35772
+ pageWasVisibleRef.current = pageVisible;
35773
+ if (returned && mounted) void refreshRef.current();
35774
+ }, [pageVisible, mounted]);
35775
+ useEffect(() => {
35776
+ if (teamTypingUntil === null) return;
35777
+ const timer = window.setTimeout(
35778
+ () => setTeamTypingUntil(null),
35779
+ Math.max(0, teamTypingUntil - Date.now())
35780
+ );
35781
+ return () => window.clearTimeout(timer);
35782
+ }, [teamTypingUntil]);
35783
+ useEffect(() => {
35784
+ const id = conversationId.current;
35785
+ const visitorIdValue = visitor.current;
35786
+ if (!id || !visitorIdValue || realtime?.conversationId !== id || !realtime.supported) return;
35787
+ let marker = readMarkersRef.current.get(id);
35788
+ if (!marker) {
35789
+ marker = { sent: null, pending: null, unsupported: false };
35790
+ readMarkersRef.current.set(id, marker);
35791
+ }
35792
+ if (marker.unsupported) return;
35793
+ const through = readMarkerThrough(messages, {
35794
+ humanJoined,
35795
+ chatOpen: mounted && visible,
35796
+ pageVisible,
35797
+ lastSent: laterTimestamp(marker.sent, marker.pending)
35798
+ });
35799
+ if (!through) return;
35800
+ const state = marker;
35801
+ state.pending = through;
35802
+ fetch(
35803
+ `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/${encodeURIComponent(id)}/public/read`,
35804
+ {
35805
+ method: "POST",
35806
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
35807
+ body: JSON.stringify({ visitorId: visitorIdValue, through })
35430
35808
  }
35431
- };
35432
- const timer = window.setInterval(() => void refresh(), 5e3);
35433
- return () => {
35434
- cancelled = true;
35435
- window.clearInterval(timer);
35436
- };
35437
- }, [apiUrl, config.siteId, mounted, sending]);
35809
+ ).then((response) => {
35810
+ if (response.ok) state.sent = laterTimestamp(state.sent, through);
35811
+ else if (response.status === 404) state.unsupported = true;
35812
+ }).catch(() => void 0).finally(() => {
35813
+ if (state.pending === through) state.pending = null;
35814
+ });
35815
+ }, [apiUrl, config.siteId, messages, humanJoined, mounted, visible, pageVisible, realtime]);
35438
35816
  useEffect(() => {
35439
35817
  if (!sending && queued) {
35440
35818
  setQueued(false);
35441
35819
  void sendTurn();
35442
35820
  }
35443
35821
  }, [sending, queued]);
35822
+ useEffect(() => {
35823
+ if (!sending && pendingRefreshRef.current) void refreshRef.current();
35824
+ }, [sending]);
35444
35825
  useEffect(() => {
35445
35826
  if (!mounted) return;
35446
35827
  const closeOnEscape = (event) => {
@@ -35449,6 +35830,29 @@ function SettoChat() {
35449
35830
  window.addEventListener("keydown", closeOnEscape);
35450
35831
  return () => window.removeEventListener("keydown", closeOnEscape);
35451
35832
  }, [mounted]);
35833
+ const newestMessageKey = (() => {
35834
+ const newest = messages.at(-1);
35835
+ return newest ? `${messages.length}:${newest.id ?? newest.role}` : "empty";
35836
+ })();
35837
+ useEffect(() => {
35838
+ if (!mounted || newestMessageKey === "empty") return;
35839
+ pinToBottomRef.current = true;
35840
+ let releaseFrame = 0;
35841
+ const frame = window.requestAnimationFrame(() => {
35842
+ const log = logRef.current;
35843
+ if (!log) return;
35844
+ programmaticScrollRef.current = true;
35845
+ log.scrollTop = Math.max(0, log.scrollHeight - log.clientHeight);
35846
+ releaseFrame = window.requestAnimationFrame(() => {
35847
+ programmaticScrollRef.current = false;
35848
+ });
35849
+ });
35850
+ return () => {
35851
+ window.cancelAnimationFrame(frame);
35852
+ window.cancelAnimationFrame(releaseFrame);
35853
+ programmaticScrollRef.current = false;
35854
+ };
35855
+ }, [mounted, newestMessageKey]);
35452
35856
  if (!settings || window.location.pathname.startsWith("/setto")) return null;
35453
35857
  function show() {
35454
35858
  pinToBottomRef.current = true;
@@ -35473,7 +35877,12 @@ function SettoChat() {
35473
35877
  });
35474
35878
  }
35475
35879
  function startNew() {
35880
+ typingSender().stop();
35476
35881
  rememberConversation(null);
35882
+ loadedConversationRef.current = null;
35883
+ latestHumanRef.current = null;
35884
+ setRealtime(null);
35885
+ setTeamTypingUntil(null);
35477
35886
  setHumanJoined(false);
35478
35887
  setMessages([{ role: "assistant", content: intro(locale, settings?.displayName ?? void 0) }]);
35479
35888
  setDraft("");
@@ -35497,6 +35906,7 @@ function SettoChat() {
35497
35906
  }
35498
35907
  function queueOrSend() {
35499
35908
  if (!draft.trim() && files.length === 0) return;
35909
+ typingSender().stop();
35500
35910
  if (sending) {
35501
35911
  setQueued(true);
35502
35912
  return;
@@ -35507,6 +35917,9 @@ function SettoChat() {
35507
35917
  const content2 = draft.trim();
35508
35918
  const turnFiles = files;
35509
35919
  if (!content2 && turnFiles.length === 0) return;
35920
+ turnRef.current.busy = true;
35921
+ turnRef.current.generation += 1;
35922
+ typingSender().stop();
35510
35923
  pinToBottomRef.current = true;
35511
35924
  setDraft("");
35512
35925
  setFiles([]);
@@ -35591,9 +36004,15 @@ function SettoChat() {
35591
36004
  setMessages((previous2) => replaceLastAssistant(previous2, labels.failed));
35592
36005
  } finally {
35593
36006
  for (const image2 of turnFiles) URL.revokeObjectURL(image2.preview);
36007
+ turnRef.current.busy = false;
36008
+ if (conversationId.current && loadedConversationRef.current !== conversationId.current) {
36009
+ pendingRefreshRef.current = true;
36010
+ }
35594
36011
  setSending(false);
35595
36012
  }
35596
36013
  }
36014
+ const seenIndex = seenVisitorMessageIndex(messages, humanJoined, realtime?.teamReadAt ?? null);
36015
+ const typingIdentity = teamTypingUntil !== null ? teamTypingIdentity(messages) : null;
35597
36016
  return /* @__PURE__ */ jsxs(
35598
36017
  "aside",
35599
36018
  {
@@ -35690,51 +36109,65 @@ function SettoChat() {
35690
36109
  ref: logRef,
35691
36110
  "aria-live": "polite",
35692
36111
  "aria-busy": loadingConversation,
35693
- children: /* @__PURE__ */ jsx("div", { className: "setto-chat__message-list", children: messages.map(
35694
- (item, index2) => item.role === "system" && item.kind === "agent_joined" ? /* @__PURE__ */ jsx(AgentJoinedEvent, { message: item }, item.id ?? `agent-joined-${index2}`) : /* @__PURE__ */ jsxs(
35695
- "div",
35696
- {
35697
- className: `setto-chat__message setto-chat__message--${item.role}`,
35698
- children: [
35699
- item.role === "assistant" || item.role === "human" ? /* @__PURE__ */ jsx(
35700
- "span",
35701
- {
35702
- className: `setto-chat__avatar setto-chat__avatar--${item.role}`,
35703
- "aria-hidden": "true",
35704
- children: item.role === "human" && item.avatarUrl ? /* @__PURE__ */ jsx("img", { src: item.avatarUrl, alt: "" }) : item.role === "human" ? /* @__PURE__ */ jsx(PersonIcon, {}) : /* @__PURE__ */ jsx(BaristaIcon, {})
35705
- }
35706
- ) : null,
35707
- /* @__PURE__ */ jsxs("div", { className: "setto-chat__message-stack", children: [
35708
- item.role === "assistant" || item.role === "human" ? /* @__PURE__ */ jsx("span", { className: "setto-chat__speaker", children: item.role === "human" ? item.authorName || labels.team : "Barista" }) : null,
35709
- /* @__PURE__ */ jsxs("div", { className: "setto-chat__message-bubble", children: [
35710
- /* @__PURE__ */ jsx("div", { className: "setto-chat__message-copy", children: item.content ? item.role === "assistant" ? /* @__PURE__ */ jsx(SiteChatMarkdown, { content: item.content, reveal: item.reveal }) : item.content : /* @__PURE__ */ jsx("span", { className: "setto-chat__typing", children: "···" }) }),
35711
- item.attachments?.map((attachment) => /* @__PURE__ */ jsx(
35712
- "a",
35713
- {
35714
- className: "setto-chat__image",
35715
- href: attachment.url,
35716
- target: "_blank",
35717
- rel: "noreferrer",
35718
- children: /* @__PURE__ */ jsx("img", { src: attachment.url, alt: labels.image })
35719
- },
35720
- attachment.id
35721
- ))
36112
+ children: /* @__PURE__ */ jsxs("div", { className: "setto-chat__message-list", children: [
36113
+ messages.map(
36114
+ (item, index2) => item.role === "system" && item.kind === "agent_joined" ? /* @__PURE__ */ jsx(AgentJoinedEvent, { message: item }, item.id ?? `agent-joined-${index2}`) : /* @__PURE__ */ jsxs(
36115
+ "div",
36116
+ {
36117
+ className: `setto-chat__message setto-chat__message--${item.role}`,
36118
+ children: [
36119
+ item.role === "assistant" || item.role === "human" ? /* @__PURE__ */ jsx(
36120
+ "span",
36121
+ {
36122
+ className: `setto-chat__avatar setto-chat__avatar--${item.role}`,
36123
+ "aria-hidden": "true",
36124
+ children: item.role === "human" && item.avatarUrl ? /* @__PURE__ */ jsx("img", { src: item.avatarUrl, alt: "" }) : item.role === "human" ? /* @__PURE__ */ jsx(PersonIcon, {}) : /* @__PURE__ */ jsx(BaristaIcon, {})
36125
+ }
36126
+ ) : null,
36127
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__message-stack", children: [
36128
+ item.role === "assistant" || item.role === "human" ? /* @__PURE__ */ jsx("span", { className: "setto-chat__speaker", children: item.role === "human" ? item.authorName || labels.team : "Barista" }) : null,
36129
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__message-bubble", children: [
36130
+ /* @__PURE__ */ jsx("div", { className: "setto-chat__message-copy", children: item.content ? item.role === "assistant" ? /* @__PURE__ */ jsx(SiteChatMarkdown, { content: item.content, reveal: item.reveal }) : item.content : /* @__PURE__ */ jsx("span", { className: "setto-chat__typing", children: "···" }) }),
36131
+ item.attachments?.map((attachment) => /* @__PURE__ */ jsx(
36132
+ "a",
36133
+ {
36134
+ className: "setto-chat__image",
36135
+ href: attachment.url,
36136
+ target: "_blank",
36137
+ rel: "noreferrer",
36138
+ children: /* @__PURE__ */ jsx("img", { src: attachment.url, alt: labels.image })
36139
+ },
36140
+ attachment.id
36141
+ ))
36142
+ ] }),
36143
+ index2 === seenIndex ? /* @__PURE__ */ jsx(SeenMarker, { label: labels.seen }) : null
35722
36144
  ] })
35723
- ] })
35724
- ]
35725
- },
35726
- item.id ?? `${item.role}-${index2}`
35727
- )
35728
- ) })
36145
+ ]
36146
+ },
36147
+ item.id ?? `${item.role}-${index2}`
36148
+ )
36149
+ ),
36150
+ typingIdentity ? /* @__PURE__ */ jsx(
36151
+ TeamTypingRow,
36152
+ {
36153
+ label: teamTypingLabel(isNo, typingIdentity.name),
36154
+ avatarUrl: typingIdentity.avatarUrl
36155
+ }
36156
+ ) : null
36157
+ ] })
35729
36158
  }
35730
36159
  ),
35731
- /* @__PURE__ */ jsxs("div", { className: "setto-chat__composer-shell", children: [
36160
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__composer-shell", ref: composerShellRef, children: [
35732
36161
  /* @__PURE__ */ jsx(
35733
36162
  SiteChatComposer,
35734
36163
  {
35735
36164
  value: draft,
35736
- onChange: setDraft,
36165
+ onChange: (value) => {
36166
+ setDraft(value);
36167
+ typingSender().update(value);
36168
+ },
35737
36169
  onSubmit: queueOrSend,
36170
+ onBlur: () => typingSender().stop(),
35738
36171
  busy: sending,
35739
36172
  queued,
35740
36173
  placeholder: humanJoined ? labels.humanPlaceholder : labels.placeholder,
@@ -35799,8 +36232,9 @@ function useChatStyles() {
35799
36232
  .setto-chat{position:fixed;right:max(18px,env(safe-area-inset-right));bottom:max(18px,env(safe-area-inset-bottom));z-index:2147483000;overscroll-behavior:contain;color:var(--setto-chat-ink);font-family:"Archivo Variable",Archivo,system-ui,sans-serif;-webkit-font-smoothing:antialiased}.setto-chat *,.setto-chat *::before,.setto-chat *::after{box-sizing:border-box}.setto-chat button,.setto-chat input,.setto-chat textarea{font:inherit}.setto-chat--mounted{top:0;right:auto;bottom:auto;left:0;width:100vw;height:var(--setto-chat-layout-height,100lvh);overflow:hidden;background:var(--setto-chat-canvas);opacity:0;transition:opacity ${CLOSE_MS}ms ease}.setto-chat--visible{opacity:1}.setto-chat__shell{position:absolute;top:var(--setto-chat-top,0);left:0;width:100%;height:var(--setto-chat-height,100svh);display:flex;overflow:hidden;overscroll-behavior:contain;background:var(--setto-chat-canvas);opacity:0;transform:translateY(12px) scale(.994);transform-origin:center bottom;transition:opacity ${CLOSE_MS}ms ease,transform ${CLOSE_MS}ms cubic-bezier(.16,1,.3,1)}.setto-chat--visible .setto-chat__shell{opacity:1;transform:none}
35800
36233
  .setto-chat__scrim{position:absolute;inset:0;z-index:45;border:0;background:rgba(33,23,17,.32);opacity:0;pointer-events:none;transition:opacity 220ms ease}.setto-chat__scrim.is-open{opacity:1;pointer-events:auto}.setto-chat__chats{position:relative;z-index:50;width:min(84vw,280px);height:100%;flex:0 0 280px;padding:1rem .85rem calc(1rem + env(safe-area-inset-bottom));display:flex;flex-direction:column;gap:.9rem;background:color-mix(in srgb,var(--setto-chat-crema) 32%,var(--setto-chat-canvas));border-right:1px solid color-mix(in srgb,var(--setto-chat-ink) 12%,transparent);overflow:hidden}.setto-chat__chats-heading{display:flex;align-items:center;justify-content:space-between;gap:.5rem;min-height:34px;font-size:.66rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:color-mix(in srgb,var(--setto-chat-ink) 58%,transparent)}.setto-chat__chats-heading button{width:34px;height:34px;padding:0;border:0;border-radius:8px;background:transparent;color:color-mix(in srgb,var(--setto-chat-ink) 58%,transparent);display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.setto-chat__new{display:flex;align-items:center;gap:.55rem;width:100%;padding:.5rem .55rem;border:0;border-radius:8px;background:transparent;color:var(--setto-chat-ink);font-weight:600;font-size:.9rem;text-align:left;cursor:pointer;transition:background 140ms ease}.setto-chat__new:hover{background:color-mix(in srgb,var(--setto-chat-ink) 5%,transparent)}.setto-chat__chat-list{display:flex;flex-direction:column;gap:.1rem;flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;margin-right:-.85rem;padding-right:.3rem}.setto-chat__chat-list p{margin:.35rem .55rem;font-size:.85rem;line-height:1.5;color:color-mix(in srgb,var(--setto-chat-ink) 58%,transparent)}.setto-chat__chat-row{width:100%;display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.4rem;padding:.55rem;border:0;border-radius:8px;background:transparent;color:var(--setto-chat-ink);text-align:left;cursor:pointer}.setto-chat__chat-row:hover,.setto-chat__chat-row.is-active{background:color-mix(in srgb,var(--setto-chat-ink) 7.5%,transparent)}.setto-chat__chat-row span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.9rem;font-weight:600}.setto-chat__chat-row time{font-size:.72rem;color:color-mix(in srgb,var(--setto-chat-ink) 55%,transparent)}
35801
36234
  .setto-chat__main{position:relative;z-index:1;display:flex;flex:1;min-width:0;max-width:820px;height:100%;margin:0 auto;flex-direction:column;background:var(--setto-chat-canvas)}.setto-chat__header{position:absolute;inset:0 0 auto;z-index:30;min-height:58px;padding:.35rem .75rem;display:flex;align-items:center;gap:.5rem;background:linear-gradient(to bottom,color-mix(in srgb,var(--setto-chat-crema) 88%,transparent),color-mix(in srgb,var(--setto-chat-crema) 72%,transparent));border-bottom:1px solid color-mix(in srgb,var(--setto-chat-ink) 12%,transparent);backdrop-filter:blur(20px) saturate(150%);-webkit-backdrop-filter:blur(20px) saturate(150%);isolation:isolate}.setto-chat__round-button{flex:none;width:46px;height:46px;padding:0;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 12%,transparent);border-radius:50%;background:var(--setto-chat-raised);color:var(--setto-chat-ink);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background 160ms ease,border-color 160ms ease,color 160ms ease}.setto-chat__round-button:hover{background:var(--setto-chat-crema);border-color:var(--setto-chat-accent);color:var(--setto-chat-accent)}.setto-chat__round-button:active{transform:scale(.96)}.setto-chat__brand{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;line-height:1.15;text-align:center}.setto-chat__brand h2{margin:0;color:var(--setto-chat-ink);font-size:1.02rem;font-weight:600;line-height:1.15}.setto-chat__brand p{max-width:100%;margin:2px 0 0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:color-mix(in srgb,var(--setto-chat-ink) 56%,transparent);font-size:.78rem;line-height:1.15}
35802
- .setto-chat__body{position:relative;display:flex;flex:1 1 auto;min-height:0;height:100%;flex-direction:column}.setto-chat__log{display:flex;flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;flex-direction:column;padding:calc(58px + 1.3rem) .75rem 8rem;scroll-behavior:auto;-webkit-overflow-scrolling:touch}.setto-chat__message-list{display:flex;flex-direction:column}.setto-chat__message{--setto-chat-bubble:color-mix(in srgb,var(--setto-chat-ink) 7%,var(--setto-chat-canvas));display:flex;align-items:flex-end;gap:.55rem;width:100%;margin-bottom:1rem;color:color-mix(in srgb,var(--setto-chat-ink) 92%,transparent);font-size:16px;line-height:1.5;overflow-wrap:anywhere}.setto-chat__message-stack{display:flex;min-width:0;max-width:min(78%,36rem);flex-direction:column;align-items:flex-start}.setto-chat__message-bubble{position:relative;min-width:2.5rem;padding:.58rem .82rem;border-radius:19px 19px 19px 5px;background:var(--setto-chat-bubble);white-space:pre-wrap}.setto-chat__message--assistant .setto-chat__message-bubble::before,.setto-chat__message--human .setto-chat__message-bubble::before{content:"";position:absolute;z-index:0;left:-7px;bottom:0;width:16px;height:17px;border-bottom-right-radius:14px;background:var(--setto-chat-bubble)}.setto-chat__message--assistant .setto-chat__message-bubble::after,.setto-chat__message--human .setto-chat__message-bubble::after{content:"";position:absolute;z-index:1;left:-10px;bottom:0;width:10px;height:18px;border-bottom-right-radius:10px;background:var(--setto-chat-canvas)}.setto-chat__message-copy,.setto-chat__image{position:relative;z-index:2}.setto-chat__message--visitor{--setto-chat-bubble:#0a84ff;justify-content:flex-end;color:#fff}.setto-chat__message--visitor .setto-chat__message-stack{align-items:flex-end}.setto-chat__message--visitor .setto-chat__message-bubble{border-radius:19px 19px 5px 19px}.setto-chat__message--visitor .setto-chat__message-bubble::before{content:"";position:absolute;z-index:0;right:-7px;bottom:0;width:16px;height:17px;border-bottom-left-radius:14px;background:var(--setto-chat-bubble)}.setto-chat__message--visitor .setto-chat__message-bubble::after{content:"";position:absolute;z-index:1;right:-10px;bottom:0;width:10px;height:18px;border-bottom-left-radius:10px;background:var(--setto-chat-canvas)}.setto-chat__message--system{justify-content:center;color:color-mix(in srgb,var(--setto-chat-ink) 55%,transparent);font-size:.78rem;line-height:1.45;text-align:center}.setto-chat__message--system .setto-chat__message-stack{max-width:85%;align-items:center}.setto-chat__message--system .setto-chat__message-bubble{padding:.25rem .6rem;background:transparent}.setto-chat__avatar{position:relative;z-index:3;display:grid;place-items:center;flex:0 0 34px;width:34px;height:34px;margin-bottom:1px;overflow:hidden;border:2px solid var(--setto-chat-canvas);border-radius:50%;background:var(--setto-chat-crema);color:var(--setto-chat-ink);box-shadow:0 2px 8px color-mix(in srgb,var(--setto-chat-ink) 14%,transparent)}.setto-chat__avatar--assistant{background:var(--setto-chat-accent);color:var(--setto-chat-raised)}.setto-chat__avatar img{display:block;width:100%;height:100%;object-fit:cover}.setto-chat__speaker{display:block;margin:0 0 .2rem .4rem;color:color-mix(in srgb,var(--setto-chat-ink) 52%,transparent);font-size:.69rem;font-weight:500;line-height:1.2}.setto-chat__typing{display:inline-block;animation:setto-chat-pulse 1s ease-in-out infinite}.setto-chat__image{display:block;width:max-content;max-width:100%;margin-top:.65rem;overflow:hidden;border-radius:13px}.setto-chat__image img{display:block;width:min(280px,100%);max-height:260px;object-fit:cover}
36235
+ .setto-chat__body{position:relative;display:flex;flex:1 1 auto;min-height:0;height:100%;flex-direction:column}.setto-chat__log{display:flex;flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;flex-direction:column;padding:calc(58px + 1.3rem) .75rem var(--setto-chat-composer-space,8rem);scroll-padding-bottom:var(--setto-chat-composer-space,8rem);scroll-behavior:auto;-webkit-overflow-scrolling:touch}.setto-chat__message-list{display:flex;flex-direction:column}.setto-chat__message{--setto-chat-bubble:color-mix(in srgb,var(--setto-chat-ink) 7%,var(--setto-chat-canvas));display:flex;align-items:flex-end;gap:.55rem;width:100%;margin-bottom:1rem;color:color-mix(in srgb,var(--setto-chat-ink) 92%,transparent);font-size:16px;line-height:1.5;overflow-wrap:anywhere}.setto-chat__message-stack{display:flex;min-width:0;max-width:min(78%,36rem);flex-direction:column;align-items:flex-start}.setto-chat__message-bubble{position:relative;min-width:2.5rem;padding:.58rem .82rem;border-radius:19px 19px 19px 5px;background:var(--setto-chat-bubble);white-space:pre-wrap}.setto-chat__message--assistant .setto-chat__message-bubble::before,.setto-chat__message--human .setto-chat__message-bubble::before{content:"";position:absolute;z-index:0;left:-7px;bottom:0;width:16px;height:17px;border-bottom-right-radius:14px;background:var(--setto-chat-bubble)}.setto-chat__message--assistant .setto-chat__message-bubble::after,.setto-chat__message--human .setto-chat__message-bubble::after{content:"";position:absolute;z-index:1;left:-10px;bottom:0;width:10px;height:18px;border-bottom-right-radius:10px;background:var(--setto-chat-canvas)}.setto-chat__message-copy,.setto-chat__image{position:relative;z-index:2}.setto-chat__message--visitor{--setto-chat-bubble:#0a84ff;justify-content:flex-end;color:#fff}.setto-chat__message--visitor .setto-chat__message-stack{align-items:flex-end}.setto-chat__message--visitor .setto-chat__message-bubble{border-radius:19px 19px 5px 19px}.setto-chat__message--visitor .setto-chat__message-bubble::before{content:"";position:absolute;z-index:0;right:-7px;bottom:0;width:16px;height:17px;border-bottom-left-radius:14px;background:var(--setto-chat-bubble)}.setto-chat__message--visitor .setto-chat__message-bubble::after{content:"";position:absolute;z-index:1;right:-10px;bottom:0;width:10px;height:18px;border-bottom-left-radius:10px;background:var(--setto-chat-canvas)}.setto-chat__message--system{justify-content:center;color:color-mix(in srgb,var(--setto-chat-ink) 55%,transparent);font-size:.78rem;line-height:1.45;text-align:center}.setto-chat__message--system .setto-chat__message-stack{max-width:85%;align-items:center}.setto-chat__message--system .setto-chat__message-bubble{padding:.25rem .6rem;background:transparent}.setto-chat__avatar{position:relative;z-index:3;display:grid;place-items:center;flex:0 0 34px;width:34px;height:34px;margin-bottom:1px;overflow:hidden;border:2px solid var(--setto-chat-canvas);border-radius:50%;background:var(--setto-chat-crema);color:var(--setto-chat-ink);box-shadow:0 2px 8px color-mix(in srgb,var(--setto-chat-ink) 14%,transparent)}.setto-chat__avatar--assistant{background:var(--setto-chat-accent);color:var(--setto-chat-raised)}.setto-chat__avatar img{display:block;width:100%;height:100%;object-fit:cover}.setto-chat__speaker{display:block;margin:0 0 .2rem .4rem;color:color-mix(in srgb,var(--setto-chat-ink) 52%,transparent);font-size:.69rem;font-weight:500;line-height:1.2}.setto-chat__typing{display:inline-block;min-width:1.8rem;animation:setto-chat-pulse 1s ease-in-out infinite}.setto-chat__image{display:block;width:max-content;max-width:100%;margin-top:.65rem;overflow:hidden;border-radius:13px}.setto-chat__image img{display:block;width:min(280px,100%);max-height:260px;object-fit:cover}
35803
36236
  .setto-chat__message--agent-joined{justify-content:flex-start;align-items:center;gap:.55rem;padding:.1rem .4rem;color:color-mix(in srgb,var(--setto-chat-ink) 62%,transparent);font-size:.8rem;line-height:1.4;text-align:left}.setto-chat__message--agent-joined .setto-chat__message-stack{max-width:calc(100% - 38px);align-items:flex-start}.setto-chat__message--agent-joined .setto-chat__message-bubble{min-width:0;padding:.2rem 0;background:transparent;white-space:normal}.setto-chat__avatar--joined{flex-basis:30px;width:30px;height:30px;margin:0;border-width:1px;box-shadow:0 2px 7px color-mix(in srgb,var(--setto-chat-ink) 12%,transparent)}
36237
+ .setto-chat__receipt{display:block;margin:.26rem .45rem 0 0;color:color-mix(in srgb,var(--setto-chat-ink) 52%,transparent);font-size:.69rem;font-weight:500;line-height:1.2}.setto-chat__message--typing{animation:setto-chat-typing-in 180ms cubic-bezier(.2,.8,.2,1) both}.setto-chat__message--typing .setto-chat__message-bubble{min-width:3.25rem;text-align:center}.setto-chat__message--typing .setto-chat__typing{font-weight:700;letter-spacing:.06em}@keyframes setto-chat-typing-in{from{opacity:0;transform:translateY(4px)}}@media(prefers-reduced-motion:reduce){.setto-chat__message--typing{animation:none}}
35804
36238
  .setto-chat__composer-shell{position:absolute;left:0;right:0;bottom:0;z-index:25;display:grid;grid-template-columns:minmax(0,1fr);padding:1.5rem .75rem 0;background:transparent;isolation:isolate}.setto-chat__composer-shell::before{content:"";position:absolute;inset:0;pointer-events:none;background:linear-gradient(to bottom,transparent 0%,color-mix(in srgb,var(--setto-chat-canvas) 82%,transparent) 46%,var(--setto-chat-canvas) 66%);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);-webkit-mask-image:linear-gradient(to bottom,rgba(0,0,0,0) 0%,rgba(0,0,0,.4) 34%,rgba(0,0,0,1) 66%);mask-image:linear-gradient(to bottom,rgba(0,0,0,0) 0%,rgba(0,0,0,.4) 34%,rgba(0,0,0,1) 66%)}.setto-chat__composer{position:relative;z-index:1;display:flex;flex-direction:column;gap:.15rem;width:100%;min-width:0;margin:0;padding:.35rem .4rem;border:2px solid color-mix(in srgb,var(--setto-chat-ink) 10%,transparent);border-radius:24px;background:var(--setto-chat-canvas);box-shadow:0 8px 24px -16px color-mix(in srgb,var(--setto-chat-ink) 28%,transparent);transition:padding 220ms cubic-bezier(.16,1,.3,1),border-color 200ms ease,box-shadow 360ms cubic-bezier(.16,1,.3,1)}.setto-chat__composer.is-open{padding:.55rem .5rem}.setto-chat__composer:focus-within{border-color:var(--setto-chat-accent)}.setto-chat__composer textarea,.setto-chat__composer textarea:hover,.setto-chat__composer textarea:focus,.setto-chat__composer textarea:focus-visible{width:100%;min-height:36px;max-height:200px;padding:.45rem .25rem;resize:none;-webkit-appearance:none!important;appearance:none!important;border:0!important;border-radius:0!important;outline:0!important;box-shadow:none!important;background:transparent!important;color:var(--setto-chat-ink);font-family:"Archivo Variable",Archivo,system-ui,sans-serif;font-size:16px;line-height:1.35;transition:min-height 220ms cubic-bezier(.16,1,.3,1)}.setto-chat__composer.is-open textarea{min-height:40px}.setto-chat__composer textarea::placeholder{color:color-mix(in srgb,var(--setto-chat-ink) 52%,transparent)}.setto-chat__composer-tools{display:flex;align-items:center;gap:.4rem;width:100%;min-width:0}.setto-chat__attach{flex:none;width:34px;height:34px;padding:0;border:0;border-radius:50%;background:transparent;color:color-mix(in srgb,var(--setto-chat-ink) 58%,transparent);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background 160ms ease,color 160ms ease}.setto-chat__attach:hover:not(:disabled){background:color-mix(in srgb,var(--setto-chat-ink) 6%,transparent);color:var(--setto-chat-ink)}.setto-chat__attach:disabled{opacity:.4;cursor:default}.setto-chat__send{flex:none;width:36px;height:36px;margin-left:auto;padding:0;border:0;border-radius:50%;background:var(--setto-chat-accent);color:var(--setto-chat-raised);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:transform 360ms cubic-bezier(.16,1,.3,1),box-shadow 360ms cubic-bezier(.16,1,.3,1),background 200ms ease,opacity 200ms ease}.setto-chat__send:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 12px 24px -12px color-mix(in srgb,var(--setto-chat-ink) 45%,transparent)}.setto-chat__send:active:not(:disabled){transform:translateY(0);box-shadow:0 4px 10px -8px color-mix(in srgb,var(--setto-chat-ink) 40%,transparent);transition-duration:90ms}.setto-chat__send:disabled{opacity:.35;cursor:default}.setto-chat__send.is-queued{animation:setto-chat-queued 1.1s ease-in-out infinite}.setto-chat__attachments{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;margin:0;padding:.15rem .15rem 0}.setto-chat__attachments li{position:relative;width:56px;height:56px;border-radius:10px;overflow:hidden;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 12%,transparent);background:var(--setto-chat-crema)}.setto-chat__attachments img{display:block;width:100%;height:100%;object-fit:cover}.setto-chat__attachments button{position:absolute;top:2px;right:2px;width:18px;height:18px;padding:0;border:0;border-radius:50%;background:color-mix(in srgb,var(--setto-chat-ink) 72%,transparent);color:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.setto-chat__composer-floor{position:relative;z-index:1;width:calc(100% + 1.5rem);height:max(.35rem,env(safe-area-inset-bottom));margin-left:-.75rem;background:var(--setto-chat-canvas)}
35805
36239
  .setto-chat__launcher{width:58px;height:58px;padding:3px;border:1px solid rgba(255,255,255,.54);border-radius:50%;background:color-mix(in srgb,var(--setto-chat-raised) 35%,transparent);box-shadow:0 10px 30px color-mix(in srgb,var(--setto-chat-ink) 24%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--setto-chat-ink) 9%,transparent);backdrop-filter:blur(14px) saturate(115%);-webkit-backdrop-filter:blur(14px) saturate(115%);color:var(--setto-chat-ink);display:grid;place-items:center;cursor:pointer;transition:transform 180ms ease,box-shadow 180ms ease}.setto-chat__launcher:hover{transform:translateY(-2px);box-shadow:0 15px 35px color-mix(in srgb,var(--setto-chat-ink) 28%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--setto-chat-ink) 9%,transparent)}.setto-chat__launcher span{width:46px;height:46px;border-radius:50%;display:grid;place-items:center;background:var(--setto-chat-accent);color:var(--setto-chat-raised)}.setto-chat__launcher img{display:block;width:50px;height:50px;border-radius:50%;object-fit:cover}.setto-chat__launcher:focus-visible,.setto-chat button:focus-visible,.setto-chat input:focus-visible{outline:2px solid var(--setto-chat-accent);outline-offset:2px}@keyframes setto-chat-pulse{50%{opacity:.35}}@keyframes setto-chat-queued{50%{opacity:.55}}
35806
36240
  @media(max-width:700px){.setto-chat__chats{position:absolute;inset:0 auto 0 0;transform:translateX(-100%);transition:transform 260ms cubic-bezier(.16,1,.3,1);box-shadow:14px 0 35px color-mix(in srgb,var(--setto-chat-ink) 16%,transparent)}.setto-chat__chats--open{transform:none}.setto-chat__main{width:100%;max-width:none;margin:0}.setto-chat__log{padding-left:.75rem;padding-right:.75rem}}@media(min-width:701px){.setto-chat--mounted{top:auto;right:max(18px,env(safe-area-inset-right));bottom:max(18px,env(safe-area-inset-bottom));left:auto;width:min(440px,calc(100vw - 36px));height:min(720px,calc(100dvh - 36px));overflow:visible;background:transparent}.setto-chat__shell{position:relative;top:auto;left:auto;width:100%;height:100%;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 14%,transparent);border-radius:26px;background:var(--setto-chat-canvas);box-shadow:0 28px 80px -24px color-mix(in srgb,var(--setto-chat-ink) 42%,transparent),0 8px 24px -12px color-mix(in srgb,var(--setto-chat-ink) 24%,transparent);transform:translateY(16px) scale(.975);transform-origin:right bottom}.setto-chat--visible .setto-chat__shell{transform:none}.setto-chat__chats{position:absolute;inset:0 auto 0 0;width:min(82%,280px);height:100%;transform:translateX(-100%);transition:transform 260ms cubic-bezier(.16,1,.3,1);box-shadow:14px 0 35px color-mix(in srgb,var(--setto-chat-ink) 16%,transparent)}.setto-chat__chats--open{transform:none}.setto-chat__main{width:100%;max-width:none;margin:0}.setto-chat__header,.setto-chat__log{padding-left:.9rem;padding-right:.9rem}.setto-chat__composer-shell{padding-left:.9rem;padding-right:.9rem}.setto-chat__composer-floor{width:calc(100% + 1.8rem);height:.45rem;margin-left:-.9rem}.setto-chat__message{font-size:15.5px}.setto-chat__message-stack{max-width:82%}}@media(prefers-reduced-motion:reduce){.setto-chat,.setto-chat__shell,.setto-chat__chats,.setto-chat__launcher,.setto-chat__composer,.setto-chat__composer textarea,.setto-chat__send{transition:none!important}.setto-chat__typing,.setto-chat__send.is-queued{animation:none}}
@@ -36954,19 +37388,19 @@ function readLocaleFields(i18n, lng, formId, ns = "translation") {
36954
37388
  for (const itemKey of collectKeysUnder(bundle, prefix)) {
36955
37389
  const item = list2[itemKey];
36956
37390
  if (!item || typeof item !== "object") continue;
36957
- const record = item;
36958
- const name2 = readString(record, "name").trim();
37391
+ const record2 = item;
37392
+ const name2 = readString(record2, "name").trim();
36959
37393
  if (!name2) continue;
36960
- const type = isFormFieldType(record.type) ? record.type : "text";
37394
+ const type = isFormFieldType(record2.type) ? record2.type : "text";
36961
37395
  fields.push({
36962
37396
  itemKey,
36963
37397
  name: name2,
36964
37398
  type,
36965
37399
  labelKey: `${prefix}.${itemKey}.label`,
36966
- label: readString(record, "label"),
36967
- placeholder: readString(record, "placeholder"),
36968
- required: readString(record, "required") === "true",
36969
- options: parseOptions(readString(record, "options"))
37400
+ label: readString(record2, "label"),
37401
+ placeholder: readString(record2, "placeholder"),
37402
+ required: readString(record2, "required") === "true",
37403
+ options: parseOptions(readString(record2, "options"))
36970
37404
  });
36971
37405
  }
36972
37406
  return fields;