@sevenfold/setto-client 0.26.0 → 0.28.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/SettoMap.d.ts +27 -0
- package/dist/index.d.ts +3 -0
- package/dist/lib/map-location.d.ts +80 -0
- package/dist/map-tools.d.ts +24 -0
- package/dist/modules/chat/SettoChat.d.ts +15 -0
- package/dist/modules/chat/realtime.d.ts +104 -0
- package/dist/setto-client.js +595 -70
- package/dist/setto-client.js.map +1 -1
- package/package.json +1 -1
package/dist/setto-client.js
CHANGED
|
@@ -5899,21 +5899,21 @@ var PostgresTypes;
|
|
|
5899
5899
|
PostgresTypes2["tsrange"] = "tsrange";
|
|
5900
5900
|
PostgresTypes2["tstzrange"] = "tstzrange";
|
|
5901
5901
|
})(PostgresTypes || (PostgresTypes = {}));
|
|
5902
|
-
const convertChangeData = (columns,
|
|
5902
|
+
const convertChangeData = (columns, record2, options = {}) => {
|
|
5903
5903
|
var _a;
|
|
5904
5904
|
const skipTypes = (_a = options.skipTypes) !== null && _a !== void 0 ? _a : [];
|
|
5905
|
-
if (!
|
|
5905
|
+
if (!record2) {
|
|
5906
5906
|
return {};
|
|
5907
5907
|
}
|
|
5908
|
-
return Object.keys(
|
|
5909
|
-
acc[rec_key] = convertColumn(rec_key, columns,
|
|
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,
|
|
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 =
|
|
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";
|
|
@@ -34843,6 +34976,25 @@ function AgentJoinedEvent({ message }) {
|
|
|
34843
34976
|
/* @__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
34977
|
] });
|
|
34845
34978
|
}
|
|
34979
|
+
function TeamTypingRow({ label, avatarUrl }) {
|
|
34980
|
+
return /* @__PURE__ */ jsxs(
|
|
34981
|
+
"div",
|
|
34982
|
+
{
|
|
34983
|
+
className: "setto-chat__message setto-chat__message--human setto-chat__message--typing",
|
|
34984
|
+
"aria-live": "off",
|
|
34985
|
+
children: [
|
|
34986
|
+
/* @__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, {}) }),
|
|
34987
|
+
/* @__PURE__ */ jsxs("div", { className: "setto-chat__message-stack", children: [
|
|
34988
|
+
/* @__PURE__ */ jsx("span", { className: "setto-chat__speaker", children: label }),
|
|
34989
|
+
/* @__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: "···" }) }) })
|
|
34990
|
+
] })
|
|
34991
|
+
]
|
|
34992
|
+
}
|
|
34993
|
+
);
|
|
34994
|
+
}
|
|
34995
|
+
function SeenMarker({ label }) {
|
|
34996
|
+
return /* @__PURE__ */ jsx("span", { className: "setto-chat__receipt", "aria-live": "off", children: label });
|
|
34997
|
+
}
|
|
34846
34998
|
function StaggeredText({ children }) {
|
|
34847
34999
|
return Children.map(children, (child) => {
|
|
34848
35000
|
if (typeof child !== "string") return child;
|
|
@@ -34884,6 +35036,7 @@ function SiteChatComposer({
|
|
|
34884
35036
|
value,
|
|
34885
35037
|
onChange,
|
|
34886
35038
|
onSubmit,
|
|
35039
|
+
onBlur,
|
|
34887
35040
|
busy,
|
|
34888
35041
|
queued,
|
|
34889
35042
|
placeholder,
|
|
@@ -34964,7 +35117,10 @@ function SiteChatComposer({
|
|
|
34964
35117
|
onChange: (event) => onChange(event.target.value),
|
|
34965
35118
|
onKeyDown: handleKeyDown,
|
|
34966
35119
|
onFocus: () => setFocused(true),
|
|
34967
|
-
onBlur: () =>
|
|
35120
|
+
onBlur: () => {
|
|
35121
|
+
setFocused(false);
|
|
35122
|
+
onBlur?.();
|
|
35123
|
+
},
|
|
34968
35124
|
placeholder,
|
|
34969
35125
|
rows: 1,
|
|
34970
35126
|
maxLength: 1500
|
|
@@ -35235,8 +35391,70 @@ function useChatAutoScroll(active, logRef, pinToBottomRef, programmaticScrollRef
|
|
|
35235
35391
|
};
|
|
35236
35392
|
}, [active, logRef, pinToBottomRef, programmaticScrollRef]);
|
|
35237
35393
|
}
|
|
35394
|
+
function usePageVisible() {
|
|
35395
|
+
const [pageVisible, setPageVisible] = useState(
|
|
35396
|
+
() => typeof document === "undefined" || document.visibilityState !== "hidden"
|
|
35397
|
+
);
|
|
35398
|
+
useEffect(() => {
|
|
35399
|
+
const update = () => setPageVisible(document.visibilityState !== "hidden");
|
|
35400
|
+
update();
|
|
35401
|
+
document.addEventListener("visibilitychange", update);
|
|
35402
|
+
return () => document.removeEventListener("visibilitychange", update);
|
|
35403
|
+
}, []);
|
|
35404
|
+
return pageVisible;
|
|
35405
|
+
}
|
|
35406
|
+
const channelRemovals = /* @__PURE__ */ new Map();
|
|
35407
|
+
function useConversationChannel(supabase, topic, handlers2) {
|
|
35408
|
+
const handlersRef = useRef(handlers2);
|
|
35409
|
+
const channelRef = useRef(null);
|
|
35410
|
+
useEffect(() => {
|
|
35411
|
+
handlersRef.current = handlers2;
|
|
35412
|
+
});
|
|
35413
|
+
useEffect(() => {
|
|
35414
|
+
if (!topic) return;
|
|
35415
|
+
let active = true;
|
|
35416
|
+
let channel = null;
|
|
35417
|
+
let changedTimer = 0;
|
|
35418
|
+
const join2 = () => {
|
|
35419
|
+
if (!active) return;
|
|
35420
|
+
channel = supabase.channel(topic).on("broadcast", { event: "changed" }, () => {
|
|
35421
|
+
window.clearTimeout(changedTimer);
|
|
35422
|
+
changedTimer = window.setTimeout(
|
|
35423
|
+
() => handlersRef.current.onChanged(),
|
|
35424
|
+
CHANGED_DEBOUNCE_MS
|
|
35425
|
+
);
|
|
35426
|
+
}).on("broadcast", { event: "read" }, (message) => {
|
|
35427
|
+
const signal = parseReadSignal(message.payload);
|
|
35428
|
+
if (signal) handlersRef.current.onRead(signal);
|
|
35429
|
+
}).on("broadcast", { event: "typing" }, (message) => {
|
|
35430
|
+
const signal = parseTypingSignal(message.payload);
|
|
35431
|
+
if (signal) handlersRef.current.onTyping(signal);
|
|
35432
|
+
});
|
|
35433
|
+
channelRef.current = channel;
|
|
35434
|
+
channel.subscribe((status) => {
|
|
35435
|
+
if (active) handlersRef.current.onStatus(status === "SUBSCRIBED");
|
|
35436
|
+
});
|
|
35437
|
+
};
|
|
35438
|
+
const previousRemoval = channelRemovals.get(topic);
|
|
35439
|
+
if (previousRemoval) void previousRemoval.then(join2);
|
|
35440
|
+
else join2();
|
|
35441
|
+
return () => {
|
|
35442
|
+
active = false;
|
|
35443
|
+
window.clearTimeout(changedTimer);
|
|
35444
|
+
if (!channel) return;
|
|
35445
|
+
handlersRef.current.onLeave();
|
|
35446
|
+
channelRef.current = null;
|
|
35447
|
+
handlersRef.current.onStatus(false);
|
|
35448
|
+
const removal = supabase.removeChannel(channel).catch(() => void 0).finally(() => {
|
|
35449
|
+
if (channelRemovals.get(topic) === removal) channelRemovals.delete(topic);
|
|
35450
|
+
});
|
|
35451
|
+
channelRemovals.set(topic, removal);
|
|
35452
|
+
};
|
|
35453
|
+
}, [supabase, topic]);
|
|
35454
|
+
return channelRef;
|
|
35455
|
+
}
|
|
35238
35456
|
function SettoChat() {
|
|
35239
|
-
const { config } = useSetto();
|
|
35457
|
+
const { config, supabase } = useSetto();
|
|
35240
35458
|
const { i18n } = useTranslation();
|
|
35241
35459
|
const apiUrl = config.apiUrl.replace(/\/+$/, "");
|
|
35242
35460
|
const [settings, setSettings] = useState(null);
|
|
@@ -35262,9 +35480,53 @@ function SettoChat() {
|
|
|
35262
35480
|
const pinToBottomRef = useRef(true);
|
|
35263
35481
|
const programmaticScrollRef = useRef(false);
|
|
35264
35482
|
const filesRef = useRef([]);
|
|
35483
|
+
const [realtime, setRealtime] = useState(null);
|
|
35484
|
+
const [subscribed, setSubscribed] = useState(false);
|
|
35485
|
+
const [teamTypingUntil, setTeamTypingUntil] = useState(null);
|
|
35486
|
+
const subscribedRef = useRef(false);
|
|
35487
|
+
const turnRef = useRef({ busy: false, generation: 0 });
|
|
35488
|
+
const refreshRequestRef = useRef(0);
|
|
35489
|
+
const pendingRefreshRef = useRef(false);
|
|
35490
|
+
const loadedConversationRef = useRef(null);
|
|
35491
|
+
const latestHumanRef = useRef(null);
|
|
35492
|
+
const readMarkersRef = useRef(/* @__PURE__ */ new Map());
|
|
35493
|
+
const refreshRef = useRef(async () => void 0);
|
|
35494
|
+
const typingSenderRef = useRef(null);
|
|
35265
35495
|
const formId = useId();
|
|
35266
35496
|
const palette = useMemo(() => chatPalette(config.brandColors), [config.brandColors]);
|
|
35267
35497
|
const desktopPopup = useDesktopPopup();
|
|
35498
|
+
const pageVisible = usePageVisible();
|
|
35499
|
+
const channelRef = useConversationChannel(supabase, mounted ? realtime?.topic ?? null : null, {
|
|
35500
|
+
onStatus(next) {
|
|
35501
|
+
subscribedRef.current = next;
|
|
35502
|
+
setSubscribed(next);
|
|
35503
|
+
if (next) void refreshRef.current();
|
|
35504
|
+
else setTeamTypingUntil(null);
|
|
35505
|
+
},
|
|
35506
|
+
onChanged() {
|
|
35507
|
+
void refreshRef.current();
|
|
35508
|
+
},
|
|
35509
|
+
onRead(signal) {
|
|
35510
|
+
if (signal.side !== "team") return;
|
|
35511
|
+
setRealtime(
|
|
35512
|
+
(current) => current ? { ...current, teamReadAt: laterTimestamp(current.teamReadAt, signal.at) } : current
|
|
35513
|
+
);
|
|
35514
|
+
},
|
|
35515
|
+
onTyping(signal) {
|
|
35516
|
+
setTeamTypingUntil((current) => nextTeamTypingDeadline(current, signal, Date.now()));
|
|
35517
|
+
},
|
|
35518
|
+
onLeave() {
|
|
35519
|
+
typingSender().stop();
|
|
35520
|
+
}
|
|
35521
|
+
});
|
|
35522
|
+
function typingSender() {
|
|
35523
|
+
return typingSenderRef.current ??= createTypingSender((typing) => {
|
|
35524
|
+
const channel = channelRef.current;
|
|
35525
|
+
if (!channel || !subscribedRef.current || channel.state !== "joined") return false;
|
|
35526
|
+
void channel.send({ type: "broadcast", event: "typing", payload: { side: "visitor", typing } }).catch(() => void 0);
|
|
35527
|
+
return true;
|
|
35528
|
+
});
|
|
35529
|
+
}
|
|
35268
35530
|
useChatStyles();
|
|
35269
35531
|
useChatViewport(mounted && !desktopPopup, logRef, pinToBottomRef, programmaticScrollRef);
|
|
35270
35532
|
useChatPinTracking(mounted, logRef, pinToBottomRef, programmaticScrollRef);
|
|
@@ -35288,6 +35550,7 @@ function SettoChat() {
|
|
|
35288
35550
|
failed: "Jeg får ikke svart akkurat nå. Kontakt gjerne teamet direkte.",
|
|
35289
35551
|
image: "Bilde",
|
|
35290
35552
|
team: "Teamet",
|
|
35553
|
+
seen: "Sett",
|
|
35291
35554
|
emptyChats: "Nye samtaler du starter her vises i denne listen."
|
|
35292
35555
|
} : {
|
|
35293
35556
|
open: siteName ? `Open chat with ${siteName}` : "Open chat",
|
|
@@ -35304,6 +35567,7 @@ function SettoChat() {
|
|
|
35304
35567
|
failed: "I cannot reply right now. Please contact the team directly.",
|
|
35305
35568
|
image: "Image",
|
|
35306
35569
|
team: "The team",
|
|
35570
|
+
seen: "Seen",
|
|
35307
35571
|
emptyChats: "New conversations you start here will appear in this list."
|
|
35308
35572
|
};
|
|
35309
35573
|
function ensureVisitor() {
|
|
@@ -35318,9 +35582,24 @@ function SettoChat() {
|
|
|
35318
35582
|
}
|
|
35319
35583
|
}
|
|
35320
35584
|
function applyConversation(next) {
|
|
35585
|
+
const sameConversation = loadedConversationRef.current === next.conversationId;
|
|
35321
35586
|
rememberConversation(next.conversationId);
|
|
35587
|
+
loadedConversationRef.current = next.conversationId;
|
|
35322
35588
|
setHumanJoined(next.humanJoined);
|
|
35323
35589
|
setMessages(next.messages);
|
|
35590
|
+
const supported = "realtimeTopic" in next || "teamReadAt" in next || "visitorReadAt" in next;
|
|
35591
|
+
const serverTeamReadAt = typeof next.teamReadAt === "string" ? next.teamReadAt : null;
|
|
35592
|
+
setRealtime((current) => ({
|
|
35593
|
+
conversationId: next.conversationId,
|
|
35594
|
+
supported,
|
|
35595
|
+
topic: conversationTopic(next.realtimeTopic),
|
|
35596
|
+
teamReadAt: current?.conversationId === next.conversationId ? laterTimestamp(current.teamReadAt, serverTeamReadAt) : serverTeamReadAt
|
|
35597
|
+
}));
|
|
35598
|
+
const latestHuman = latestHumanMessageKey(next.messages);
|
|
35599
|
+
if (!sameConversation || latestHuman && latestHuman !== latestHumanRef.current) {
|
|
35600
|
+
setTeamTypingUntil(null);
|
|
35601
|
+
}
|
|
35602
|
+
latestHumanRef.current = latestHuman;
|
|
35324
35603
|
const navigation = [...next.messages].reverse().find((message) => message.kind === "navigation" && message.action?.type === "navigate");
|
|
35325
35604
|
if (!navigation?.id || !navigation.action?.url) return;
|
|
35326
35605
|
try {
|
|
@@ -35348,9 +35627,51 @@ function SettoChat() {
|
|
|
35348
35627
|
} catch {
|
|
35349
35628
|
}
|
|
35350
35629
|
}
|
|
35630
|
+
async function refreshConversation() {
|
|
35631
|
+
const id = conversationId.current;
|
|
35632
|
+
const visitorIdValue = visitor.current;
|
|
35633
|
+
if (!id || !visitorIdValue) return;
|
|
35634
|
+
if (turnRef.current.busy) {
|
|
35635
|
+
pendingRefreshRef.current = true;
|
|
35636
|
+
return;
|
|
35637
|
+
}
|
|
35638
|
+
pendingRefreshRef.current = false;
|
|
35639
|
+
refreshRequestRef.current += 1;
|
|
35640
|
+
const ticket = {
|
|
35641
|
+
conversationId: id,
|
|
35642
|
+
turn: turnRef.current.generation,
|
|
35643
|
+
request: refreshRequestRef.current
|
|
35644
|
+
};
|
|
35645
|
+
try {
|
|
35646
|
+
const query = new URLSearchParams({ visitorId: visitorIdValue });
|
|
35647
|
+
const response = await fetch(
|
|
35648
|
+
`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/${encodeURIComponent(id)}/public?${query}`,
|
|
35649
|
+
{ headers: { Accept: "application/json" } }
|
|
35650
|
+
);
|
|
35651
|
+
if (!response.ok) return;
|
|
35652
|
+
const next = await response.json();
|
|
35653
|
+
const outcome = refreshOutcome(ticket, {
|
|
35654
|
+
conversationId: conversationId.current,
|
|
35655
|
+
turn: turnRef.current.generation,
|
|
35656
|
+
request: refreshRequestRef.current,
|
|
35657
|
+
busy: turnRef.current.busy
|
|
35658
|
+
});
|
|
35659
|
+
if (outcome === "apply") {
|
|
35660
|
+
applyConversation(next);
|
|
35661
|
+
} else if (outcome === "defer") {
|
|
35662
|
+
pendingRefreshRef.current = true;
|
|
35663
|
+
if (!turnRef.current.busy) void refreshConversation();
|
|
35664
|
+
}
|
|
35665
|
+
} catch {
|
|
35666
|
+
}
|
|
35667
|
+
}
|
|
35668
|
+
useEffect(() => {
|
|
35669
|
+
refreshRef.current = refreshConversation;
|
|
35670
|
+
});
|
|
35351
35671
|
async function loadConversation(id) {
|
|
35352
35672
|
const visitorIdValue = ensureVisitor();
|
|
35353
35673
|
setLoadingConversation(true);
|
|
35674
|
+
refreshRequestRef.current += 1;
|
|
35354
35675
|
try {
|
|
35355
35676
|
const query = new URLSearchParams({ visitorId: visitorIdValue });
|
|
35356
35677
|
const response = await fetch(
|
|
@@ -35413,34 +35734,71 @@ function SettoChat() {
|
|
|
35413
35734
|
},
|
|
35414
35735
|
[]
|
|
35415
35736
|
);
|
|
35737
|
+
const polledConversation = realtime?.conversationId ?? null;
|
|
35416
35738
|
useEffect(() => {
|
|
35417
35739
|
if (!mounted || !conversationId.current || !visitor.current || sending) return;
|
|
35418
|
-
|
|
35419
|
-
|
|
35420
|
-
|
|
35421
|
-
|
|
35422
|
-
|
|
35423
|
-
|
|
35424
|
-
|
|
35425
|
-
|
|
35426
|
-
|
|
35427
|
-
|
|
35428
|
-
|
|
35429
|
-
|
|
35740
|
+
const timer = window.setInterval(() => {
|
|
35741
|
+
if (document.visibilityState === "hidden") return;
|
|
35742
|
+
void refreshRef.current();
|
|
35743
|
+
}, pollIntervalMs(subscribed));
|
|
35744
|
+
return () => window.clearInterval(timer);
|
|
35745
|
+
}, [mounted, sending, subscribed, polledConversation]);
|
|
35746
|
+
const pageWasVisibleRef = useRef(pageVisible);
|
|
35747
|
+
useEffect(() => {
|
|
35748
|
+
const returned = pageVisible && !pageWasVisibleRef.current;
|
|
35749
|
+
pageWasVisibleRef.current = pageVisible;
|
|
35750
|
+
if (returned && mounted) void refreshRef.current();
|
|
35751
|
+
}, [pageVisible, mounted]);
|
|
35752
|
+
useEffect(() => {
|
|
35753
|
+
if (teamTypingUntil === null) return;
|
|
35754
|
+
const timer = window.setTimeout(
|
|
35755
|
+
() => setTeamTypingUntil(null),
|
|
35756
|
+
Math.max(0, teamTypingUntil - Date.now())
|
|
35757
|
+
);
|
|
35758
|
+
return () => window.clearTimeout(timer);
|
|
35759
|
+
}, [teamTypingUntil]);
|
|
35760
|
+
useEffect(() => {
|
|
35761
|
+
const id = conversationId.current;
|
|
35762
|
+
const visitorIdValue = visitor.current;
|
|
35763
|
+
if (!id || !visitorIdValue || realtime?.conversationId !== id || !realtime.supported) return;
|
|
35764
|
+
let marker = readMarkersRef.current.get(id);
|
|
35765
|
+
if (!marker) {
|
|
35766
|
+
marker = { sent: null, pending: null, unsupported: false };
|
|
35767
|
+
readMarkersRef.current.set(id, marker);
|
|
35768
|
+
}
|
|
35769
|
+
if (marker.unsupported) return;
|
|
35770
|
+
const through = readMarkerThrough(messages, {
|
|
35771
|
+
humanJoined,
|
|
35772
|
+
chatOpen: mounted && visible,
|
|
35773
|
+
pageVisible,
|
|
35774
|
+
lastSent: laterTimestamp(marker.sent, marker.pending)
|
|
35775
|
+
});
|
|
35776
|
+
if (!through) return;
|
|
35777
|
+
const state = marker;
|
|
35778
|
+
state.pending = through;
|
|
35779
|
+
fetch(
|
|
35780
|
+
`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/${encodeURIComponent(id)}/public/read`,
|
|
35781
|
+
{
|
|
35782
|
+
method: "POST",
|
|
35783
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
35784
|
+
body: JSON.stringify({ visitorId: visitorIdValue, through })
|
|
35430
35785
|
}
|
|
35431
|
-
|
|
35432
|
-
|
|
35433
|
-
|
|
35434
|
-
|
|
35435
|
-
|
|
35436
|
-
};
|
|
35437
|
-
}, [apiUrl, config.siteId, mounted,
|
|
35786
|
+
).then((response) => {
|
|
35787
|
+
if (response.ok) state.sent = laterTimestamp(state.sent, through);
|
|
35788
|
+
else if (response.status === 404) state.unsupported = true;
|
|
35789
|
+
}).catch(() => void 0).finally(() => {
|
|
35790
|
+
if (state.pending === through) state.pending = null;
|
|
35791
|
+
});
|
|
35792
|
+
}, [apiUrl, config.siteId, messages, humanJoined, mounted, visible, pageVisible, realtime]);
|
|
35438
35793
|
useEffect(() => {
|
|
35439
35794
|
if (!sending && queued) {
|
|
35440
35795
|
setQueued(false);
|
|
35441
35796
|
void sendTurn();
|
|
35442
35797
|
}
|
|
35443
35798
|
}, [sending, queued]);
|
|
35799
|
+
useEffect(() => {
|
|
35800
|
+
if (!sending && pendingRefreshRef.current) void refreshRef.current();
|
|
35801
|
+
}, [sending]);
|
|
35444
35802
|
useEffect(() => {
|
|
35445
35803
|
if (!mounted) return;
|
|
35446
35804
|
const closeOnEscape = (event) => {
|
|
@@ -35473,7 +35831,12 @@ function SettoChat() {
|
|
|
35473
35831
|
});
|
|
35474
35832
|
}
|
|
35475
35833
|
function startNew() {
|
|
35834
|
+
typingSender().stop();
|
|
35476
35835
|
rememberConversation(null);
|
|
35836
|
+
loadedConversationRef.current = null;
|
|
35837
|
+
latestHumanRef.current = null;
|
|
35838
|
+
setRealtime(null);
|
|
35839
|
+
setTeamTypingUntil(null);
|
|
35477
35840
|
setHumanJoined(false);
|
|
35478
35841
|
setMessages([{ role: "assistant", content: intro(locale, settings?.displayName ?? void 0) }]);
|
|
35479
35842
|
setDraft("");
|
|
@@ -35497,6 +35860,7 @@ function SettoChat() {
|
|
|
35497
35860
|
}
|
|
35498
35861
|
function queueOrSend() {
|
|
35499
35862
|
if (!draft.trim() && files.length === 0) return;
|
|
35863
|
+
typingSender().stop();
|
|
35500
35864
|
if (sending) {
|
|
35501
35865
|
setQueued(true);
|
|
35502
35866
|
return;
|
|
@@ -35507,6 +35871,9 @@ function SettoChat() {
|
|
|
35507
35871
|
const content2 = draft.trim();
|
|
35508
35872
|
const turnFiles = files;
|
|
35509
35873
|
if (!content2 && turnFiles.length === 0) return;
|
|
35874
|
+
turnRef.current.busy = true;
|
|
35875
|
+
turnRef.current.generation += 1;
|
|
35876
|
+
typingSender().stop();
|
|
35510
35877
|
pinToBottomRef.current = true;
|
|
35511
35878
|
setDraft("");
|
|
35512
35879
|
setFiles([]);
|
|
@@ -35591,9 +35958,15 @@ function SettoChat() {
|
|
|
35591
35958
|
setMessages((previous2) => replaceLastAssistant(previous2, labels.failed));
|
|
35592
35959
|
} finally {
|
|
35593
35960
|
for (const image2 of turnFiles) URL.revokeObjectURL(image2.preview);
|
|
35961
|
+
turnRef.current.busy = false;
|
|
35962
|
+
if (conversationId.current && loadedConversationRef.current !== conversationId.current) {
|
|
35963
|
+
pendingRefreshRef.current = true;
|
|
35964
|
+
}
|
|
35594
35965
|
setSending(false);
|
|
35595
35966
|
}
|
|
35596
35967
|
}
|
|
35968
|
+
const seenIndex = seenVisitorMessageIndex(messages, humanJoined, realtime?.teamReadAt ?? null);
|
|
35969
|
+
const typingIdentity = teamTypingUntil !== null ? teamTypingIdentity(messages) : null;
|
|
35597
35970
|
return /* @__PURE__ */ jsxs(
|
|
35598
35971
|
"aside",
|
|
35599
35972
|
{
|
|
@@ -35690,42 +36063,52 @@ function SettoChat() {
|
|
|
35690
36063
|
ref: logRef,
|
|
35691
36064
|
"aria-live": "polite",
|
|
35692
36065
|
"aria-busy": loadingConversation,
|
|
35693
|
-
children: /* @__PURE__ */
|
|
35694
|
-
|
|
35695
|
-
"
|
|
35696
|
-
|
|
35697
|
-
|
|
35698
|
-
|
|
35699
|
-
|
|
35700
|
-
"
|
|
35701
|
-
|
|
35702
|
-
|
|
35703
|
-
|
|
35704
|
-
|
|
35705
|
-
|
|
35706
|
-
|
|
35707
|
-
|
|
35708
|
-
|
|
35709
|
-
|
|
35710
|
-
/* @__PURE__ */
|
|
35711
|
-
|
|
35712
|
-
|
|
35713
|
-
|
|
35714
|
-
|
|
35715
|
-
|
|
35716
|
-
|
|
35717
|
-
|
|
35718
|
-
|
|
35719
|
-
|
|
35720
|
-
|
|
35721
|
-
|
|
36066
|
+
children: /* @__PURE__ */ jsxs("div", { className: "setto-chat__message-list", children: [
|
|
36067
|
+
messages.map(
|
|
36068
|
+
(item, index2) => item.role === "system" && item.kind === "agent_joined" ? /* @__PURE__ */ jsx(AgentJoinedEvent, { message: item }, item.id ?? `agent-joined-${index2}`) : /* @__PURE__ */ jsxs(
|
|
36069
|
+
"div",
|
|
36070
|
+
{
|
|
36071
|
+
className: `setto-chat__message setto-chat__message--${item.role}`,
|
|
36072
|
+
children: [
|
|
36073
|
+
item.role === "assistant" || item.role === "human" ? /* @__PURE__ */ jsx(
|
|
36074
|
+
"span",
|
|
36075
|
+
{
|
|
36076
|
+
className: `setto-chat__avatar setto-chat__avatar--${item.role}`,
|
|
36077
|
+
"aria-hidden": "true",
|
|
36078
|
+
children: item.role === "human" && item.avatarUrl ? /* @__PURE__ */ jsx("img", { src: item.avatarUrl, alt: "" }) : item.role === "human" ? /* @__PURE__ */ jsx(PersonIcon, {}) : /* @__PURE__ */ jsx(BaristaIcon, {})
|
|
36079
|
+
}
|
|
36080
|
+
) : null,
|
|
36081
|
+
/* @__PURE__ */ jsxs("div", { className: "setto-chat__message-stack", children: [
|
|
36082
|
+
item.role === "assistant" || item.role === "human" ? /* @__PURE__ */ jsx("span", { className: "setto-chat__speaker", children: item.role === "human" ? item.authorName || labels.team : "Barista" }) : null,
|
|
36083
|
+
/* @__PURE__ */ jsxs("div", { className: "setto-chat__message-bubble", children: [
|
|
36084
|
+
/* @__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: "···" }) }),
|
|
36085
|
+
item.attachments?.map((attachment) => /* @__PURE__ */ jsx(
|
|
36086
|
+
"a",
|
|
36087
|
+
{
|
|
36088
|
+
className: "setto-chat__image",
|
|
36089
|
+
href: attachment.url,
|
|
36090
|
+
target: "_blank",
|
|
36091
|
+
rel: "noreferrer",
|
|
36092
|
+
children: /* @__PURE__ */ jsx("img", { src: attachment.url, alt: labels.image })
|
|
36093
|
+
},
|
|
36094
|
+
attachment.id
|
|
36095
|
+
))
|
|
36096
|
+
] }),
|
|
36097
|
+
index2 === seenIndex ? /* @__PURE__ */ jsx(SeenMarker, { label: labels.seen }) : null
|
|
35722
36098
|
] })
|
|
35723
|
-
]
|
|
35724
|
-
|
|
35725
|
-
|
|
35726
|
-
|
|
35727
|
-
)
|
|
35728
|
-
|
|
36099
|
+
]
|
|
36100
|
+
},
|
|
36101
|
+
item.id ?? `${item.role}-${index2}`
|
|
36102
|
+
)
|
|
36103
|
+
),
|
|
36104
|
+
typingIdentity ? /* @__PURE__ */ jsx(
|
|
36105
|
+
TeamTypingRow,
|
|
36106
|
+
{
|
|
36107
|
+
label: teamTypingLabel(isNo, typingIdentity.name),
|
|
36108
|
+
avatarUrl: typingIdentity.avatarUrl
|
|
36109
|
+
}
|
|
36110
|
+
) : null
|
|
36111
|
+
] })
|
|
35729
36112
|
}
|
|
35730
36113
|
),
|
|
35731
36114
|
/* @__PURE__ */ jsxs("div", { className: "setto-chat__composer-shell", children: [
|
|
@@ -35733,8 +36116,12 @@ function SettoChat() {
|
|
|
35733
36116
|
SiteChatComposer,
|
|
35734
36117
|
{
|
|
35735
36118
|
value: draft,
|
|
35736
|
-
onChange:
|
|
36119
|
+
onChange: (value) => {
|
|
36120
|
+
setDraft(value);
|
|
36121
|
+
typingSender().update(value);
|
|
36122
|
+
},
|
|
35737
36123
|
onSubmit: queueOrSend,
|
|
36124
|
+
onBlur: () => typingSender().stop(),
|
|
35738
36125
|
busy: sending,
|
|
35739
36126
|
queued,
|
|
35740
36127
|
placeholder: humanJoined ? labels.humanPlaceholder : labels.placeholder,
|
|
@@ -35801,6 +36188,7 @@ function useChatStyles() {
|
|
|
35801
36188
|
.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
36189
|
.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}
|
|
35803
36190
|
.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)}
|
|
36191
|
+
.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
36192
|
.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
36193
|
.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
36194
|
@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 +37342,19 @@ function readLocaleFields(i18n, lng, formId, ns = "translation") {
|
|
|
36954
37342
|
for (const itemKey of collectKeysUnder(bundle, prefix)) {
|
|
36955
37343
|
const item = list2[itemKey];
|
|
36956
37344
|
if (!item || typeof item !== "object") continue;
|
|
36957
|
-
const
|
|
36958
|
-
const name2 = readString(
|
|
37345
|
+
const record2 = item;
|
|
37346
|
+
const name2 = readString(record2, "name").trim();
|
|
36959
37347
|
if (!name2) continue;
|
|
36960
|
-
const type = isFormFieldType(
|
|
37348
|
+
const type = isFormFieldType(record2.type) ? record2.type : "text";
|
|
36961
37349
|
fields.push({
|
|
36962
37350
|
itemKey,
|
|
36963
37351
|
name: name2,
|
|
36964
37352
|
type,
|
|
36965
37353
|
labelKey: `${prefix}.${itemKey}.label`,
|
|
36966
|
-
label: readString(
|
|
36967
|
-
placeholder: readString(
|
|
36968
|
-
required: readString(
|
|
36969
|
-
options: parseOptions(readString(
|
|
37354
|
+
label: readString(record2, "label"),
|
|
37355
|
+
placeholder: readString(record2, "placeholder"),
|
|
37356
|
+
required: readString(record2, "required") === "true",
|
|
37357
|
+
options: parseOptions(readString(record2, "options"))
|
|
36970
37358
|
});
|
|
36971
37359
|
}
|
|
36972
37360
|
return fields;
|
|
@@ -37755,6 +38143,142 @@ function toEmbedUrl(url) {
|
|
|
37755
38143
|
return null;
|
|
37756
38144
|
}
|
|
37757
38145
|
}
|
|
38146
|
+
const MAP_MIN_ZOOM = 1;
|
|
38147
|
+
const MAP_MAX_ZOOM = 19;
|
|
38148
|
+
const MAP_DEFAULT_ZOOM = 15;
|
|
38149
|
+
const MAP_DEFAULT_TITLE = "Kart";
|
|
38150
|
+
const MERCATOR_MAX_LAT = 85.0511287798066;
|
|
38151
|
+
const MAP_ASSUMED_WIDTH = 320;
|
|
38152
|
+
const MAP_ASSUMED_HEIGHT = 240;
|
|
38153
|
+
const TILE_SIZE = 256;
|
|
38154
|
+
function parseCoordinate(value) {
|
|
38155
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
38156
|
+
if (typeof value !== "string") return null;
|
|
38157
|
+
const text2 = value.trim().replace(",", ".");
|
|
38158
|
+
if (!/^[+-]?(\d+(\.\d*)?|\.\d+)$/.test(text2)) return null;
|
|
38159
|
+
const n = Number(text2);
|
|
38160
|
+
return Number.isFinite(n) ? n : null;
|
|
38161
|
+
}
|
|
38162
|
+
function parseZoom(value) {
|
|
38163
|
+
const n = parseCoordinate(value);
|
|
38164
|
+
if (n === null) return MAP_DEFAULT_ZOOM;
|
|
38165
|
+
return Math.min(MAP_MAX_ZOOM, Math.max(MAP_MIN_ZOOM, Math.round(n)));
|
|
38166
|
+
}
|
|
38167
|
+
function parseMapLocation(raw) {
|
|
38168
|
+
if (!raw) return null;
|
|
38169
|
+
const lat = parseCoordinate(raw.lat);
|
|
38170
|
+
const lng = parseCoordinate(raw.lng);
|
|
38171
|
+
if (lat === null || lng === null) return null;
|
|
38172
|
+
if (Math.abs(lat) > MERCATOR_MAX_LAT || Math.abs(lng) > 180) return null;
|
|
38173
|
+
return { lat, lng, zoom: parseZoom(raw.zoom) };
|
|
38174
|
+
}
|
|
38175
|
+
function formatCoordinate(n) {
|
|
38176
|
+
const fixed = n.toFixed(6);
|
|
38177
|
+
const trimmed = fixed.includes(".") ? fixed.replace(/0+$/, "").replace(/\.$/, "") : fixed;
|
|
38178
|
+
return trimmed === "-0" ? "0" : trimmed;
|
|
38179
|
+
}
|
|
38180
|
+
function serialiseMapLocation(location2) {
|
|
38181
|
+
return {
|
|
38182
|
+
lat: formatCoordinate(location2.lat),
|
|
38183
|
+
lng: formatCoordinate(location2.lng),
|
|
38184
|
+
zoom: String(parseZoom(location2.zoom))
|
|
38185
|
+
};
|
|
38186
|
+
}
|
|
38187
|
+
function worldSize(zoom) {
|
|
38188
|
+
return TILE_SIZE * 2 ** zoom;
|
|
38189
|
+
}
|
|
38190
|
+
function latToWorldY(lat, zoom) {
|
|
38191
|
+
const clamped = Math.max(-MERCATOR_MAX_LAT, Math.min(MERCATOR_MAX_LAT, lat));
|
|
38192
|
+
const sin = Math.sin(clamped * Math.PI / 180);
|
|
38193
|
+
return (0.5 - Math.log((1 + sin) / (1 - sin)) / (4 * Math.PI)) * worldSize(zoom);
|
|
38194
|
+
}
|
|
38195
|
+
function worldYToLat(y, zoom) {
|
|
38196
|
+
const n = Math.PI - 2 * Math.PI * y / worldSize(zoom);
|
|
38197
|
+
return 180 / Math.PI * Math.atan(Math.sinh(n));
|
|
38198
|
+
}
|
|
38199
|
+
function mapBbox(location2, width = MAP_ASSUMED_WIDTH, height = MAP_ASSUMED_HEIGHT) {
|
|
38200
|
+
const degreesPerPixel = 360 / worldSize(location2.zoom);
|
|
38201
|
+
const halfLng = width / 2 * degreesPerPixel;
|
|
38202
|
+
const y = latToWorldY(location2.lat, location2.zoom);
|
|
38203
|
+
const max = worldSize(location2.zoom);
|
|
38204
|
+
return {
|
|
38205
|
+
minLng: Math.max(-180, location2.lng - halfLng),
|
|
38206
|
+
maxLng: Math.min(180, location2.lng + halfLng),
|
|
38207
|
+
minLat: worldYToLat(Math.min(max, y + height / 2), location2.zoom),
|
|
38208
|
+
maxLat: worldYToLat(Math.max(0, y - height / 2), location2.zoom)
|
|
38209
|
+
};
|
|
38210
|
+
}
|
|
38211
|
+
function mapEmbedUrl(location2) {
|
|
38212
|
+
const box = mapBbox(location2);
|
|
38213
|
+
const bbox = [box.minLng, box.minLat, box.maxLng, box.maxLat].map(formatCoordinate).join(",");
|
|
38214
|
+
const marker = `${formatCoordinate(location2.lat)},${formatCoordinate(location2.lng)}`;
|
|
38215
|
+
return `https://www.openstreetmap.org/export/embed.html?bbox=${bbox}&layer=mapnik&marker=${marker}`;
|
|
38216
|
+
}
|
|
38217
|
+
function writeMapLocation(store, languages, key, location2) {
|
|
38218
|
+
const values = serialiseMapLocation(location2);
|
|
38219
|
+
store.batch(() => {
|
|
38220
|
+
for (const lng of languages) {
|
|
38221
|
+
store.set(`${key}.lat`, lng, values.lat);
|
|
38222
|
+
store.set(`${key}.lng`, lng, values.lng);
|
|
38223
|
+
store.set(`${key}.zoom`, lng, values.zoom);
|
|
38224
|
+
}
|
|
38225
|
+
});
|
|
38226
|
+
}
|
|
38227
|
+
function SettoMap({ k, className, style, loading = "lazy" }) {
|
|
38228
|
+
const { t, i18n } = useTranslation();
|
|
38229
|
+
const { editMode, store } = useSetto();
|
|
38230
|
+
const guestEdit = useGuestEdit();
|
|
38231
|
+
const editable = editMode || guestEdit.active;
|
|
38232
|
+
const MapTools = useSettoChromeSlot("map-tools");
|
|
38233
|
+
const [, force] = useState(0);
|
|
38234
|
+
useEffect(() => {
|
|
38235
|
+
if (!store) return;
|
|
38236
|
+
return store.subscribe(() => force((x) => x + 1));
|
|
38237
|
+
}, [store]);
|
|
38238
|
+
const lng = i18n.language;
|
|
38239
|
+
const read = (field) => {
|
|
38240
|
+
const key = `${k}.${field}`;
|
|
38241
|
+
return store?.get(key, lng) || t(key, { defaultValue: "" });
|
|
38242
|
+
};
|
|
38243
|
+
const location2 = parseMapLocation({ lat: read("lat"), lng: read("lng"), zoom: read("zoom") });
|
|
38244
|
+
const title = read("title") || MAP_DEFAULT_TITLE;
|
|
38245
|
+
const tools = editable && store && MapTools ? /* @__PURE__ */ jsx(
|
|
38246
|
+
MapTools,
|
|
38247
|
+
{
|
|
38248
|
+
location: location2,
|
|
38249
|
+
onChange: (next) => writeMapLocation(store, store.languages(lng), k, next)
|
|
38250
|
+
}
|
|
38251
|
+
) : null;
|
|
38252
|
+
if (!location2) {
|
|
38253
|
+
return /* @__PURE__ */ jsx("div", { className, style, "data-setto-map": k, "data-setto-map-empty": "1", children: tools ? /* @__PURE__ */ jsx("div", { style: fillStyle, children: tools }) : null });
|
|
38254
|
+
}
|
|
38255
|
+
return /* @__PURE__ */ jsx("div", { className, style, "data-setto-map": k, children: /* @__PURE__ */ jsxs("div", { style: fillStyle, children: [
|
|
38256
|
+
/* @__PURE__ */ jsx(
|
|
38257
|
+
"iframe",
|
|
38258
|
+
{
|
|
38259
|
+
src: mapEmbedUrl(location2),
|
|
38260
|
+
title,
|
|
38261
|
+
loading,
|
|
38262
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
38263
|
+
style: iframeStyle
|
|
38264
|
+
}
|
|
38265
|
+
),
|
|
38266
|
+
tools
|
|
38267
|
+
] }) });
|
|
38268
|
+
}
|
|
38269
|
+
const fillStyle = {
|
|
38270
|
+
position: "relative",
|
|
38271
|
+
width: "100%",
|
|
38272
|
+
height: "100%",
|
|
38273
|
+
borderRadius: "inherit",
|
|
38274
|
+
overflow: "hidden"
|
|
38275
|
+
};
|
|
38276
|
+
const iframeStyle = {
|
|
38277
|
+
display: "block",
|
|
38278
|
+
width: "100%",
|
|
38279
|
+
height: "100%",
|
|
38280
|
+
border: 0
|
|
38281
|
+
};
|
|
37758
38282
|
const DEFAULT_DURATION = {
|
|
37759
38283
|
fadeIn: 600,
|
|
37760
38284
|
slideUp: 700,
|
|
@@ -38739,6 +39263,7 @@ export {
|
|
|
38739
39263
|
SettoIcon,
|
|
38740
39264
|
SettoImage,
|
|
38741
39265
|
SettoLanguages,
|
|
39266
|
+
SettoMap,
|
|
38742
39267
|
SettoMark,
|
|
38743
39268
|
SettoProvider,
|
|
38744
39269
|
SettoRepeater,
|