@sevenfold/setto-client 0.23.0 → 0.24.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.
@@ -21059,7 +21059,8 @@ async function asError(res, fallback) {
21059
21059
  }
21060
21060
  }
21061
21061
  function createApi(args) {
21062
- const { apiUrl, supabase } = args;
21062
+ const apiUrl = args.apiUrl.replace(/\/+$/, "");
21063
+ const { supabase } = args;
21063
21064
  const request = async (path, init = {}, label = `${init.method ?? "GET"} ${path}`) => {
21064
21065
  const token = await bearer(supabase);
21065
21066
  const res = await fetch(`${apiUrl}${path}`, {
@@ -22033,6 +22034,9 @@ function EditModeShell({ children, themeStore, active }) {
22033
22034
  ] });
22034
22035
  }
22035
22036
  const VISITOR_KEY = "setto-chat-visitor";
22037
+ const CLOSE_MS = 210;
22038
+ const MIN_COMPOSER_HEIGHT = 36;
22039
+ const MAX_COMPOSER_HEIGHT = 200;
22036
22040
  function visitorId() {
22037
22041
  try {
22038
22042
  const stored = window.localStorage.getItem(VISITOR_KEY);
@@ -22057,82 +22061,316 @@ function contactTeam() {
22057
22061
  if (link) link.click();
22058
22062
  else window.location.hash = "kontakt";
22059
22063
  }
22064
+ function validColor(value, fallback) {
22065
+ return value && (/^#[0-9a-f]{3,8}$/i.test(value) || /^rgba?\(/i.test(value)) ? value : fallback;
22066
+ }
22067
+ function chatPalette(colors) {
22068
+ const values = colors?.map((color) => color.value) ?? [];
22069
+ return {
22070
+ "--setto-chat-crema": validColor(values[0], "#F2EBDF"),
22071
+ "--setto-chat-raised": validColor(values[1], "#FAF6EE"),
22072
+ "--setto-chat-canvas": validColor(values[2], "#FFFFFF"),
22073
+ "--setto-chat-ink": validColor(values[3], "#211711"),
22074
+ "--setto-chat-accent": validColor(values.at(-2) ?? values.at(-1), "#C4502A")
22075
+ };
22076
+ }
22077
+ function MenuIcon() {
22078
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "22", height: "22", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
22079
+ "path",
22080
+ {
22081
+ d: "M4 7h16M4 12h16M4 17h16",
22082
+ fill: "none",
22083
+ stroke: "currentColor",
22084
+ strokeWidth: "2",
22085
+ strokeLinecap: "round"
22086
+ }
22087
+ ) });
22088
+ }
22089
+ function CloseIcon({ size = 22 }) {
22090
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: size, height: size, "aria-hidden": "true", children: /* @__PURE__ */ jsx(
22091
+ "path",
22092
+ {
22093
+ d: "M6 6l12 12M18 6L6 18",
22094
+ fill: "none",
22095
+ stroke: "currentColor",
22096
+ strokeWidth: "2.25",
22097
+ strokeLinecap: "round"
22098
+ }
22099
+ ) });
22100
+ }
22101
+ function PlusIcon() {
22102
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
22103
+ "path",
22104
+ {
22105
+ d: "M12 5v14M5 12h14",
22106
+ fill: "none",
22107
+ stroke: "currentColor",
22108
+ strokeWidth: "2",
22109
+ strokeLinecap: "round"
22110
+ }
22111
+ ) });
22112
+ }
22113
+ function ArrowIcon() {
22114
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
22115
+ "path",
22116
+ {
22117
+ d: "M12 19V5M5 12l7-7 7 7",
22118
+ fill: "none",
22119
+ stroke: "currentColor",
22120
+ strokeWidth: "2",
22121
+ strokeLinejoin: "round",
22122
+ strokeLinecap: "round"
22123
+ }
22124
+ ) });
22125
+ }
22126
+ function BaristaIcon() {
22127
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "20", height: "20", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
22128
+ "path",
22129
+ {
22130
+ d: "M12 2.8c.65 5.25 3.95 8.55 9.2 9.2-5.25.65-8.55 3.95-9.2 9.2-.65-5.25-3.95-8.55-9.2-9.2 5.25-.65 8.55-3.95 9.2-9.2Z",
22131
+ fill: "currentColor"
22132
+ }
22133
+ ) });
22134
+ }
22135
+ function SiteChatComposer({
22136
+ value,
22137
+ onChange,
22138
+ onSubmit,
22139
+ busy,
22140
+ queued,
22141
+ placeholder,
22142
+ attachLabel,
22143
+ sendLabel,
22144
+ removeLabel,
22145
+ locale,
22146
+ attachments,
22147
+ onAttach,
22148
+ onRemoveAttachment
22149
+ }) {
22150
+ const textareaRef = useRef(null);
22151
+ const fileRef = useRef(null);
22152
+ const wasBusy = useRef(busy);
22153
+ const [focused, setFocused] = useState(false);
22154
+ useEffect(() => {
22155
+ const textarea = textareaRef.current;
22156
+ if (!textarea) return;
22157
+ textarea.style.height = "auto";
22158
+ const height = Math.min(
22159
+ Math.max(textarea.scrollHeight, MIN_COMPOSER_HEIGHT),
22160
+ MAX_COMPOSER_HEIGHT
22161
+ );
22162
+ textarea.style.height = `${height}px`;
22163
+ }, [value]);
22164
+ useEffect(() => {
22165
+ if (wasBusy.current && !busy) textareaRef.current?.focus();
22166
+ wasBusy.current = busy;
22167
+ }, [busy]);
22168
+ function send() {
22169
+ if (!value.trim() && attachments.length === 0) return;
22170
+ onSubmit();
22171
+ }
22172
+ function handleSubmit(event) {
22173
+ event.preventDefault();
22174
+ send();
22175
+ }
22176
+ function handleKeyDown(event) {
22177
+ if (event.key === "Enter" && !event.shiftKey) {
22178
+ event.preventDefault();
22179
+ send();
22180
+ }
22181
+ }
22182
+ const canSend = value.trim().length > 0 || attachments.length > 0;
22183
+ const open = focused || value.length > 0 || attachments.length > 0;
22184
+ return /* @__PURE__ */ jsxs(
22185
+ "form",
22186
+ {
22187
+ className: `setto-chat__composer${open ? " is-open" : ""}`,
22188
+ onSubmit: handleSubmit,
22189
+ onPointerDown: (event) => {
22190
+ if (event.target === event.currentTarget) textareaRef.current?.focus();
22191
+ },
22192
+ children: [
22193
+ attachments.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "setto-chat__attachments", children: attachments.map((attachment, index) => /* @__PURE__ */ jsxs("li", { children: [
22194
+ /* @__PURE__ */ jsx("img", { src: attachment.preview, alt: attachment.file.name }),
22195
+ /* @__PURE__ */ jsx(
22196
+ "button",
22197
+ {
22198
+ type: "button",
22199
+ onClick: () => onRemoveAttachment(index),
22200
+ "aria-label": `${removeLabel} ${attachment.file.name}`,
22201
+ children: /* @__PURE__ */ jsx(CloseIcon, { size: 12 })
22202
+ }
22203
+ )
22204
+ ] }, `${attachment.file.name}-${attachment.preview}`)) }) : null,
22205
+ /* @__PURE__ */ jsx(
22206
+ "textarea",
22207
+ {
22208
+ ref: textareaRef,
22209
+ lang: norwegian(locale) ? "nb" : "en",
22210
+ spellCheck: true,
22211
+ value,
22212
+ onChange: (event) => onChange(event.target.value),
22213
+ onKeyDown: handleKeyDown,
22214
+ onFocus: () => setFocused(true),
22215
+ onBlur: () => setFocused(false),
22216
+ placeholder,
22217
+ rows: 1,
22218
+ maxLength: 1500
22219
+ }
22220
+ ),
22221
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__composer-tools", children: [
22222
+ /* @__PURE__ */ jsx(
22223
+ "input",
22224
+ {
22225
+ ref: fileRef,
22226
+ type: "file",
22227
+ accept: "image/*",
22228
+ multiple: true,
22229
+ hidden: true,
22230
+ onChange: (event) => {
22231
+ const selected = Array.from(event.target.files ?? []);
22232
+ event.target.value = "";
22233
+ if (selected.length) onAttach(selected);
22234
+ }
22235
+ }
22236
+ ),
22237
+ /* @__PURE__ */ jsx(
22238
+ "button",
22239
+ {
22240
+ type: "button",
22241
+ className: "setto-chat__attach",
22242
+ onClick: () => fileRef.current?.click(),
22243
+ disabled: busy,
22244
+ "aria-label": attachLabel,
22245
+ title: attachLabel,
22246
+ children: /* @__PURE__ */ jsx(PlusIcon, {})
22247
+ }
22248
+ ),
22249
+ /* @__PURE__ */ jsx(
22250
+ "button",
22251
+ {
22252
+ type: "submit",
22253
+ className: `setto-chat__send${queued ? " is-queued" : ""}`,
22254
+ disabled: !canSend,
22255
+ "aria-busy": queued,
22256
+ "aria-label": sendLabel,
22257
+ children: /* @__PURE__ */ jsx(ArrowIcon, {})
22258
+ }
22259
+ )
22260
+ ] })
22261
+ ]
22262
+ }
22263
+ );
22264
+ }
22265
+ function useChatViewport(active, logRef) {
22266
+ useEffect(() => {
22267
+ if (!active) return;
22268
+ const root = document.documentElement;
22269
+ const body = document.body;
22270
+ const visualViewport = window.visualViewport;
22271
+ const savedScrollY = window.scrollY;
22272
+ const previous = {
22273
+ rootOverflow: root.style.overflow,
22274
+ rootOverscroll: root.style.overscrollBehavior,
22275
+ bodyPosition: body.style.position,
22276
+ bodyTop: body.style.top,
22277
+ bodyLeft: body.style.left,
22278
+ bodyRight: body.style.right,
22279
+ bodyWidth: body.style.width,
22280
+ bodyOverflow: body.style.overflow
22281
+ };
22282
+ root.style.overflow = "hidden";
22283
+ root.style.overscrollBehavior = "none";
22284
+ body.style.position = "fixed";
22285
+ body.style.top = `-${savedScrollY}px`;
22286
+ body.style.left = "0";
22287
+ body.style.right = "0";
22288
+ body.style.width = "100%";
22289
+ body.style.overflow = "hidden";
22290
+ const update = () => {
22291
+ const height = Math.round(visualViewport?.height ?? window.innerHeight);
22292
+ const top = Math.round(visualViewport?.offsetTop ?? 0);
22293
+ root.style.setProperty("--setto-chat-height", `${height}px`);
22294
+ root.style.setProperty("--setto-chat-top", `${top}px`);
22295
+ if (window.scrollY !== 0) window.scrollTo(0, 0);
22296
+ window.requestAnimationFrame(() => {
22297
+ const log = logRef.current;
22298
+ if (log) log.scrollTop = log.scrollHeight;
22299
+ });
22300
+ };
22301
+ update();
22302
+ visualViewport?.addEventListener("resize", update);
22303
+ visualViewport?.addEventListener("scroll", update);
22304
+ window.addEventListener("orientationchange", update);
22305
+ return () => {
22306
+ visualViewport?.removeEventListener("resize", update);
22307
+ visualViewport?.removeEventListener("scroll", update);
22308
+ window.removeEventListener("orientationchange", update);
22309
+ root.style.removeProperty("--setto-chat-height");
22310
+ root.style.removeProperty("--setto-chat-top");
22311
+ root.style.overflow = previous.rootOverflow;
22312
+ root.style.overscrollBehavior = previous.rootOverscroll;
22313
+ body.style.position = previous.bodyPosition;
22314
+ body.style.top = previous.bodyTop;
22315
+ body.style.left = previous.bodyLeft;
22316
+ body.style.right = previous.bodyRight;
22317
+ body.style.width = previous.bodyWidth;
22318
+ body.style.overflow = previous.bodyOverflow;
22319
+ window.scrollTo(0, savedScrollY);
22320
+ };
22321
+ }, [active, logRef]);
22322
+ }
22060
22323
  function SettoChat() {
22061
22324
  const { config } = useSetto();
22062
22325
  const { i18n } = useTranslation();
22063
22326
  const apiUrl = config.apiUrl.replace(/\/+$/, "");
22064
22327
  const [settings, setSettings] = useState(null);
22065
- const [open, setOpen] = useState(false);
22328
+ const [mounted, setMounted] = useState(false);
22329
+ const [visible, setVisible] = useState(false);
22330
+ const [drawer, setDrawer] = useState(false);
22066
22331
  const [messages, setMessages] = useState([]);
22332
+ const [conversations, setConversations] = useState([]);
22067
22333
  const [draft, setDraft] = useState("");
22334
+ const [files, setFiles] = useState([]);
22068
22335
  const [sending, setSending] = useState(false);
22336
+ const [queued, setQueued] = useState(false);
22337
+ const [loadingConversation, setLoadingConversation] = useState(false);
22338
+ const [availability, setAvailability] = useState({
22339
+ humanAvailable: false,
22340
+ avatarUrl: null
22341
+ });
22069
22342
  const [leadOpen, setLeadOpen] = useState(false);
22070
22343
  const [leadStatus, setLeadStatus] = useState("idle");
22071
- const [lead, setLead] = useState({ name: "", email: "", phone: "", interest: "", consent: false });
22344
+ const [lead, setLead] = useState({
22345
+ name: "",
22346
+ email: "",
22347
+ phone: "",
22348
+ interest: "",
22349
+ consent: false
22350
+ });
22072
22351
  const conversationId = useRef(null);
22073
22352
  const visitor = useRef(null);
22074
22353
  const logRef = useRef(null);
22354
+ const filesRef = useRef([]);
22075
22355
  const formId = useId();
22356
+ const palette = useMemo(() => chatPalette(config.brandColors), [config.brandColors]);
22076
22357
  useChatStyles();
22077
- useEffect(() => {
22078
- if (window.location.pathname.startsWith("/setto")) return;
22079
- let cancelled = false;
22080
- fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/settings/public`, {
22081
- headers: { Accept: "application/json" }
22082
- }).then(async (response) => response.ok ? await response.json() : null).then((next) => {
22083
- if (!cancelled && next?.enabled) setSettings(next);
22084
- }).catch(() => {
22085
- });
22086
- return () => {
22087
- cancelled = true;
22088
- };
22089
- }, [apiUrl, config.siteId]);
22090
- useEffect(() => {
22091
- if (!open || messages.length > 0 || !settings) return;
22092
- setMessages([{ role: "assistant", content: intro(i18n.language, settings.displayName ?? void 0) }]);
22093
- }, [open, messages.length, settings, i18n.language]);
22094
- useEffect(() => {
22095
- if (open) logRef.current?.scrollTo({ top: logRef.current.scrollHeight });
22096
- }, [messages, open]);
22097
- useEffect(() => {
22098
- if (!open || !conversationId.current || !visitor.current || sending) return;
22099
- let cancelled = false;
22100
- const refresh = async () => {
22101
- try {
22102
- const query = new URLSearchParams({ visitorId: visitor.current });
22103
- const response = await fetch(
22104
- `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/${encodeURIComponent(conversationId.current)}/public?${query}`,
22105
- { headers: { Accept: "application/json" } }
22106
- );
22107
- if (!response.ok) return;
22108
- const next = await response.json();
22109
- if (cancelled) return;
22110
- const serverMessages = next.messages.map(({ role, content }) => ({ role, content }));
22111
- setMessages((previous) => {
22112
- const greeting = previous[0]?.role === "assistant" && !serverMessages.some((message) => message.content === previous[0]?.content) ? [previous[0]] : [];
22113
- return [...greeting, ...serverMessages];
22114
- });
22115
- } catch {
22116
- }
22117
- };
22118
- void refresh();
22119
- const timer = window.setInterval(() => void refresh(), 5e3);
22120
- return () => {
22121
- cancelled = true;
22122
- window.clearInterval(timer);
22123
- };
22124
- }, [apiUrl, config.siteId, open, sending]);
22125
- if (!settings || window.location.pathname.startsWith("/setto")) return null;
22358
+ useChatViewport(mounted, logRef);
22126
22359
  const locale = i18n.language || "no";
22127
- const siteName = settings.displayName?.trim() || null;
22128
- const labels = norwegian(locale) ? {
22129
- open: siteName ? `Snakk med ${siteName}` : "Snakk med oss",
22360
+ const isNo = norwegian(locale);
22361
+ const siteName = settings?.displayName?.trim() || config.displayName?.trim() || null;
22362
+ const labels = isNo ? {
22363
+ open: siteName ? `Åpne chat med ${siteName}` : "Åpne chat",
22130
22364
  close: "Lukk chatten",
22131
22365
  title: "Spør Barista",
22366
+ chats: "Chats",
22367
+ newChat: "Ny chat",
22132
22368
  disclosure: "AI-assistent · svarer fra nettstedets godkjente informasjon",
22133
- placeholder: "Skriv spørsmålet ditt …",
22134
- send: "Send",
22369
+ placeholder: "Hva vil du spørre om?",
22370
+ send: "Send melding",
22135
22371
  sending: "Svarer …",
22372
+ attach: "Legg ved bilde",
22373
+ remove: "Fjern",
22136
22374
  contact: "Kontakt teamet",
22137
22375
  followUp: "Be om oppfølging",
22138
22376
  followUpTitle: "La teamet følge opp",
@@ -22146,15 +22384,22 @@ function SettoChat() {
22146
22384
  leadSent: "Takk — forespørselen er sendt til teamet.",
22147
22385
  leadFailed: "Kunne ikke lagre forespørselen. Prøv igjen eller kontakt teamet direkte.",
22148
22386
  leadConversation: "Still et spørsmål først, så kan vi knytte oppfølgingen til samtalen.",
22149
- failed: "Jeg får ikke svart akkurat nå. Kontakt gjerne teamet direkte."
22387
+ failed: "Jeg får ikke svart akkurat nå. Kontakt gjerne teamet direkte.",
22388
+ image: "Bilde",
22389
+ team: "Teamet",
22390
+ emptyChats: "Nye samtaler du starter her vises i denne listen."
22150
22391
  } : {
22151
- open: siteName ? `Chat with ${siteName}` : "Chat with us",
22392
+ open: siteName ? `Open chat with ${siteName}` : "Open chat",
22152
22393
  close: "Close chat",
22153
22394
  title: "Ask Barista",
22395
+ chats: "Chats",
22396
+ newChat: "New chat",
22154
22397
  disclosure: "AI assistant · answers from this website’s approved information",
22155
- placeholder: "Write your question …",
22156
- send: "Send",
22398
+ placeholder: "What would you like to ask?",
22399
+ send: "Send message",
22157
22400
  sending: "Replying …",
22401
+ attach: "Attach image",
22402
+ remove: "Remove",
22158
22403
  contact: "Contact the team",
22159
22404
  followUp: "Ask for follow-up",
22160
22405
  followUpTitle: "Let the team follow up",
@@ -22168,17 +22413,224 @@ function SettoChat() {
22168
22413
  leadSent: "Thanks — your request has been sent to the team.",
22169
22414
  leadFailed: "We could not save your request. Please try again or contact the team directly.",
22170
22415
  leadConversation: "Ask a question first so we can link the follow-up to this conversation.",
22171
- failed: "I cannot reply right now. Please contact the team directly."
22416
+ failed: "I cannot reply right now. Please contact the team directly.",
22417
+ image: "Image",
22418
+ team: "The team",
22419
+ emptyChats: "New conversations you start here will appear in this list."
22172
22420
  };
22173
- const submit = async (event) => {
22174
- event.preventDefault();
22175
- const message = draft.trim();
22176
- if (!message || sending) return;
22421
+ function ensureVisitor() {
22422
+ return visitor.current ??= visitorId();
22423
+ }
22424
+ async function loadChats() {
22425
+ const id = visitor.current;
22426
+ if (!id) return;
22427
+ try {
22428
+ const query = new URLSearchParams({ visitorId: id });
22429
+ const response = await fetch(
22430
+ `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/public?${query}`,
22431
+ { headers: { Accept: "application/json" } }
22432
+ );
22433
+ if (response.ok) {
22434
+ const body = await response.json();
22435
+ setConversations(body.conversations);
22436
+ }
22437
+ } catch {
22438
+ }
22439
+ }
22440
+ async function loadConversation(id) {
22441
+ const visitorIdValue = ensureVisitor();
22442
+ setLoadingConversation(true);
22443
+ try {
22444
+ const query = new URLSearchParams({ visitorId: visitorIdValue });
22445
+ const response = await fetch(
22446
+ `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/${encodeURIComponent(id)}/public?${query}`,
22447
+ { headers: { Accept: "application/json" } }
22448
+ );
22449
+ if (!response.ok) return;
22450
+ const next = await response.json();
22451
+ conversationId.current = next.conversationId;
22452
+ setMessages(next.messages);
22453
+ setLeadOpen(false);
22454
+ setDrawer(false);
22455
+ } finally {
22456
+ setLoadingConversation(false);
22457
+ }
22458
+ }
22459
+ useEffect(() => {
22460
+ if (window.location.pathname.startsWith("/setto")) return;
22461
+ let cancelled = false;
22462
+ fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/settings/public`, {
22463
+ headers: { Accept: "application/json" }
22464
+ }).then(async (response) => response.ok ? await response.json() : null).then((next) => {
22465
+ if (!cancelled && next?.enabled) setSettings(next);
22466
+ }).catch(() => void 0);
22467
+ return () => {
22468
+ cancelled = true;
22469
+ };
22470
+ }, [apiUrl, config.siteId]);
22471
+ useEffect(() => {
22472
+ if (!settings) return;
22473
+ let cancelled = false;
22474
+ const refresh = () => fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/availability/public`, {
22475
+ headers: { Accept: "application/json" }
22476
+ }).then(async (response) => response.ok ? await response.json() : null).then((next) => {
22477
+ if (!cancelled && next) setAvailability(next);
22478
+ }).catch(() => void 0);
22479
+ void refresh();
22480
+ const timer = window.setInterval(() => void refresh(), 3e4);
22481
+ return () => {
22482
+ cancelled = true;
22483
+ window.clearInterval(timer);
22484
+ };
22485
+ }, [apiUrl, config.siteId, settings]);
22486
+ useEffect(() => {
22487
+ if (!mounted || !settings || conversationId.current) return;
22488
+ const welcome = intro(locale, settings.displayName ?? void 0);
22489
+ setMessages((current) => {
22490
+ if (current.length === 0) return [{ role: "assistant", content: welcome }];
22491
+ if (current.length === 1 && current[0]?.role === "assistant" && !current[0].id) {
22492
+ return current[0].content === welcome ? current : [{ role: "assistant", content: welcome }];
22493
+ }
22494
+ return current;
22495
+ });
22496
+ }, [mounted, settings, locale]);
22497
+ useEffect(() => {
22498
+ if (!mounted) return;
22499
+ const log = logRef.current;
22500
+ if (log) log.scrollTo({ top: log.scrollHeight, behavior: sending ? "auto" : "smooth" });
22501
+ }, [messages, mounted, sending, leadOpen]);
22502
+ useEffect(() => {
22503
+ filesRef.current = files;
22504
+ }, [files]);
22505
+ useEffect(
22506
+ () => () => {
22507
+ for (const image of filesRef.current) URL.revokeObjectURL(image.preview);
22508
+ },
22509
+ []
22510
+ );
22511
+ useEffect(() => {
22512
+ if (!mounted || !conversationId.current || !visitor.current || sending) return;
22513
+ let cancelled = false;
22514
+ const refresh = async () => {
22515
+ try {
22516
+ const query = new URLSearchParams({ visitorId: visitor.current });
22517
+ const response = await fetch(
22518
+ `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/${encodeURIComponent(conversationId.current)}/public?${query}`,
22519
+ { headers: { Accept: "application/json" } }
22520
+ );
22521
+ if (!response.ok || cancelled) return;
22522
+ const next = await response.json();
22523
+ if (!cancelled) setMessages(next.messages);
22524
+ } catch {
22525
+ }
22526
+ };
22527
+ const timer = window.setInterval(() => void refresh(), 5e3);
22528
+ return () => {
22529
+ cancelled = true;
22530
+ window.clearInterval(timer);
22531
+ };
22532
+ }, [apiUrl, config.siteId, mounted, sending]);
22533
+ useEffect(() => {
22534
+ if (!sending && queued) {
22535
+ setQueued(false);
22536
+ void sendTurn();
22537
+ }
22538
+ }, [sending, queued]);
22539
+ useEffect(() => {
22540
+ if (!mounted) return;
22541
+ const closeOnEscape = (event) => {
22542
+ if (event.key === "Escape") hide();
22543
+ };
22544
+ window.addEventListener("keydown", closeOnEscape);
22545
+ return () => window.removeEventListener("keydown", closeOnEscape);
22546
+ }, [mounted]);
22547
+ if (!settings || window.location.pathname.startsWith("/setto")) return null;
22548
+ function show() {
22549
+ setMounted(true);
22550
+ window.requestAnimationFrame(() => setVisible(true));
22551
+ ensureVisitor();
22552
+ void loadChats();
22553
+ }
22554
+ function hide() {
22555
+ if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
22556
+ setVisible(false);
22557
+ window.setTimeout(() => {
22558
+ setMounted(false);
22559
+ setDrawer(false);
22560
+ }, CLOSE_MS);
22561
+ }
22562
+ function clearPendingFiles() {
22563
+ setFiles((current) => {
22564
+ for (const image of current) URL.revokeObjectURL(image.preview);
22565
+ return [];
22566
+ });
22567
+ }
22568
+ function startNew() {
22569
+ conversationId.current = null;
22570
+ setMessages([{ role: "assistant", content: intro(locale, settings?.displayName ?? void 0) }]);
22571
+ setDraft("");
22572
+ setQueued(false);
22573
+ setLeadOpen(false);
22574
+ setDrawer(false);
22575
+ clearPendingFiles();
22576
+ }
22577
+ function addFiles(selected) {
22578
+ setFiles((current) => {
22579
+ const available = Math.max(0, 4 - current.length);
22580
+ const accepted = selected.filter((file) => file.type.startsWith("image/")).slice(0, available).map((file) => ({ file, preview: URL.createObjectURL(file) }));
22581
+ return [...current, ...accepted];
22582
+ });
22583
+ }
22584
+ function removeFile(index) {
22585
+ setFiles((current) => {
22586
+ const removed = current[index];
22587
+ if (removed) URL.revokeObjectURL(removed.preview);
22588
+ return current.filter((_, itemIndex) => itemIndex !== index);
22589
+ });
22590
+ }
22591
+ function queueOrSend() {
22592
+ if (!draft.trim() && files.length === 0) return;
22593
+ if (sending) {
22594
+ setQueued(true);
22595
+ return;
22596
+ }
22597
+ void sendTurn();
22598
+ }
22599
+ async function sendTurn() {
22600
+ const content = draft.trim();
22601
+ const turnFiles = files;
22602
+ if (!content && turnFiles.length === 0) return;
22177
22603
  setDraft("");
22604
+ setFiles([]);
22178
22605
  setSending(true);
22179
- setMessages((previous) => [...previous, { role: "visitor", content: message }, { role: "assistant", content: "" }]);
22180
- visitor.current ??= visitorId();
22606
+ const visitorIdValue = ensureVisitor();
22181
22607
  try {
22608
+ const uploaded = [];
22609
+ for (const image of turnFiles) {
22610
+ const form = new FormData();
22611
+ form.set("visitorId", visitorIdValue);
22612
+ if (conversationId.current) form.set("conversationId", conversationId.current);
22613
+ form.set("locale", locale);
22614
+ form.set("page", window.location.href);
22615
+ form.set("file", image.file, image.file.name);
22616
+ const response2 = await fetch(
22617
+ `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/attachments`,
22618
+ { method: "POST", body: form }
22619
+ );
22620
+ if (!response2.ok) throw new Error("image_upload_failed");
22621
+ const body = await response2.json();
22622
+ conversationId.current = body.conversationId;
22623
+ uploaded.push(body.attachment);
22624
+ }
22625
+ setMessages((previous) => [
22626
+ ...previous,
22627
+ {
22628
+ role: "visitor",
22629
+ content: content || `[${labels.image}]`,
22630
+ attachments: uploaded
22631
+ },
22632
+ { role: "assistant", content: "" }
22633
+ ]);
22182
22634
  const response = await fetch(
22183
22635
  `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/messages`,
22184
22636
  {
@@ -22186,157 +22638,338 @@ function SettoChat() {
22186
22638
  headers: { "Content-Type": "application/json", Accept: "text/plain, application/json" },
22187
22639
  body: JSON.stringify({
22188
22640
  conversationId: conversationId.current ?? void 0,
22189
- visitorId: visitor.current,
22641
+ visitorId: visitorIdValue,
22190
22642
  locale,
22191
22643
  page: window.location.href,
22192
- message
22644
+ message: content || void 0,
22645
+ attachmentIds: uploaded.map((attachment) => attachment.id)
22193
22646
  })
22194
22647
  }
22195
22648
  );
22196
22649
  const headerConversation = response.headers.get("X-Setto-Conversation");
22197
22650
  if (headerConversation) conversationId.current = headerConversation;
22198
- const contentType = response.headers.get("content-type") ?? "";
22199
22651
  if (!response.ok) throw new Error(`chat_failed_${response.status}`);
22200
- if (contentType.includes("application/json")) {
22652
+ if ((response.headers.get("content-type") ?? "").includes("application/json")) {
22201
22653
  const body = await response.json();
22202
22654
  if (body.conversationId) conversationId.current = body.conversationId;
22203
22655
  setMessages((previous) => replaceLastAssistant(previous, body.answer || labels.failed));
22204
- return;
22205
- }
22206
- const reader = response.body?.getReader();
22207
- if (!reader) throw new Error("chat_missing_stream");
22208
- const decoder = new TextDecoder();
22209
- let answer = "";
22210
- while (true) {
22211
- const { done, value } = await reader.read();
22212
- if (done) break;
22213
- answer += decoder.decode(value, { stream: true });
22214
- setMessages((previous) => replaceLastAssistant(previous, answer));
22656
+ } else {
22657
+ const reader = response.body?.getReader();
22658
+ if (!reader) throw new Error("chat_missing_stream");
22659
+ const decoder = new TextDecoder();
22660
+ let answer = "";
22661
+ while (true) {
22662
+ const { done, value } = await reader.read();
22663
+ if (done) break;
22664
+ answer += decoder.decode(value, { stream: true });
22665
+ setMessages((previous) => replaceLastAssistant(previous, answer));
22666
+ }
22667
+ answer += decoder.decode();
22668
+ if (!answer.trim()) {
22669
+ setMessages((previous) => replaceLastAssistant(previous, labels.failed));
22670
+ }
22215
22671
  }
22216
- answer += decoder.decode();
22217
- if (!answer.trim()) setMessages((previous) => replaceLastAssistant(previous, labels.failed));
22672
+ void loadChats();
22218
22673
  } catch {
22219
22674
  setMessages((previous) => replaceLastAssistant(previous, labels.failed));
22220
22675
  } finally {
22676
+ for (const image of turnFiles) URL.revokeObjectURL(image.preview);
22221
22677
  setSending(false);
22222
22678
  }
22223
- };
22224
- const submitLead = async (event) => {
22679
+ }
22680
+ async function submitLead(event) {
22225
22681
  event.preventDefault();
22226
- if (leadStatus === "sending") return;
22227
- if (!conversationId.current || !visitor.current) {
22682
+ if (leadStatus === "sending" || !conversationId.current || !visitor.current) {
22228
22683
  setLeadStatus("failed");
22229
22684
  return;
22230
22685
  }
22231
22686
  setLeadStatus("sending");
22232
22687
  try {
22233
- const response = await fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/leads`, {
22234
- method: "POST",
22235
- headers: { "Content-Type": "application/json", Accept: "application/json" },
22236
- body: JSON.stringify({
22237
- conversationId: conversationId.current,
22238
- visitorId: visitor.current,
22239
- consent: lead.consent,
22240
- name: lead.name.trim() || void 0,
22241
- email: lead.email.trim() || void 0,
22242
- phone: lead.phone.trim() || void 0,
22243
- interest: lead.interest.trim() || void 0
22244
- })
22245
- });
22246
- if (!response.ok) throw new Error(`lead_failed_${response.status}`);
22688
+ const response = await fetch(
22689
+ `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/leads`,
22690
+ {
22691
+ method: "POST",
22692
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
22693
+ body: JSON.stringify({
22694
+ conversationId: conversationId.current,
22695
+ visitorId: visitor.current,
22696
+ consent: lead.consent,
22697
+ name: lead.name.trim() || void 0,
22698
+ email: lead.email.trim() || void 0,
22699
+ phone: lead.phone.trim() || void 0,
22700
+ interest: lead.interest.trim() || void 0
22701
+ })
22702
+ }
22703
+ );
22704
+ if (!response.ok) throw new Error("lead_failed");
22247
22705
  setLeadStatus("sent");
22248
22706
  } catch {
22249
22707
  setLeadStatus("failed");
22250
22708
  }
22251
- };
22252
- return /* @__PURE__ */ jsxs("aside", { className: `setto-chat${open ? " setto-chat--open" : ""}`, "aria-label": labels.title, "data-setto-ui": true, children: [
22253
- open ? /* @__PURE__ */ jsxs("section", { className: "setto-chat__panel", role: "dialog", "aria-modal": "true", "aria-labelledby": `${formId}-title`, children: [
22254
- /* @__PURE__ */ jsxs("header", { className: "setto-chat__header", children: [
22255
- /* @__PURE__ */ jsxs("div", { children: [
22256
- /* @__PURE__ */ jsx("p", { className: "setto-chat__eyebrow", children: "BARISTA" }),
22257
- /* @__PURE__ */ jsx("h2", { id: `${formId}-title`, children: labels.title }),
22258
- /* @__PURE__ */ jsx("p", { children: labels.disclosure })
22259
- ] }),
22260
- /* @__PURE__ */ jsx("button", { className: "setto-chat__close", type: "button", onClick: () => setOpen(false), "aria-label": labels.close, children: "×" })
22261
- ] }),
22262
- /* @__PURE__ */ jsx("div", { className: "setto-chat__log", ref: logRef, "aria-live": "polite", children: messages.map((item, index) => /* @__PURE__ */ jsxs("div", { className: `setto-chat__message setto-chat__message--${item.role}`, children: [
22263
- item.role === "human" ? /* @__PURE__ */ jsx("span", { className: "setto-chat__speaker", children: "Teamet" }) : null,
22264
- item.content || /* @__PURE__ */ jsx("span", { className: "setto-chat__typing", children: "···" })
22265
- ] }, `${item.role}-${index}`)) }),
22266
- /* @__PURE__ */ jsxs("div", { className: "setto-chat__contact", children: [
22267
- /* @__PURE__ */ jsxs("button", { type: "button", onClick: contactTeam, children: [
22268
- labels.contact,
22269
- " ",
22270
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "↗" })
22271
- ] }),
22272
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
22273
- setLeadOpen((value) => !value);
22274
- setLeadStatus("idle");
22275
- }, children: labels.followUp })
22276
- ] }),
22277
- leadOpen ? /* @__PURE__ */ jsxs("form", { className: "setto-chat__lead", onSubmit: submitLead, children: [
22278
- /* @__PURE__ */ jsx("strong", { children: labels.followUpTitle }),
22279
- /* @__PURE__ */ jsx("p", { children: labels.followUpDetail }),
22280
- /* @__PURE__ */ jsxs("div", { className: "setto-chat__lead-grid", children: [
22281
- /* @__PURE__ */ jsxs("label", { children: [
22282
- labels.name,
22283
- /* @__PURE__ */ jsx("input", { value: lead.name, maxLength: 160, onChange: (event) => setLead((current) => ({ ...current, name: event.target.value })) })
22284
- ] }),
22285
- /* @__PURE__ */ jsxs("label", { children: [
22286
- labels.email,
22287
- /* @__PURE__ */ jsx("input", { type: "email", value: lead.email, maxLength: 320, onChange: (event) => setLead((current) => ({ ...current, email: event.target.value })) })
22288
- ] }),
22289
- /* @__PURE__ */ jsxs("label", { children: [
22290
- labels.phone,
22291
- /* @__PURE__ */ jsx("input", { type: "tel", value: lead.phone, maxLength: 64, onChange: (event) => setLead((current) => ({ ...current, phone: event.target.value })) })
22292
- ] }),
22293
- /* @__PURE__ */ jsxs("label", { children: [
22294
- labels.interest,
22295
- /* @__PURE__ */ jsx("input", { value: lead.interest, maxLength: 500, onChange: (event) => setLead((current) => ({ ...current, interest: event.target.value })) })
22296
- ] })
22297
- ] }),
22298
- /* @__PURE__ */ jsxs("label", { className: "setto-chat__consent", children: [
22299
- /* @__PURE__ */ jsx("input", { type: "checkbox", checked: lead.consent, onChange: (event) => setLead((current) => ({ ...current, consent: event.target.checked })) }),
22300
- labels.consent
22301
- ] }),
22302
- leadStatus === "sent" ? /* @__PURE__ */ jsx("p", { className: "setto-chat__lead-success", children: labels.leadSent }) : null,
22303
- leadStatus === "failed" ? /* @__PURE__ */ jsx("p", { className: "setto-chat__lead-error", children: conversationId.current ? labels.leadFailed : labels.leadConversation }) : null,
22304
- leadStatus !== "sent" ? /* @__PURE__ */ jsx("button", { type: "submit", disabled: leadStatus === "sending" || !lead.consent || !lead.email.trim() && !lead.phone.trim(), children: leadStatus === "sending" ? labels.sending : labels.leadSend }) : null
22305
- ] }) : null,
22306
- /* @__PURE__ */ jsxs("form", { className: "setto-chat__composer", onSubmit: submit, children: [
22307
- /* @__PURE__ */ jsx("label", { className: "setto-chat__sr", htmlFor: `${formId}-input`, children: labels.placeholder }),
22308
- /* @__PURE__ */ jsx(
22309
- "textarea",
22709
+ }
22710
+ return /* @__PURE__ */ jsxs(
22711
+ "aside",
22712
+ {
22713
+ className: `setto-chat${mounted ? " setto-chat--mounted" : ""}${visible ? " setto-chat--visible" : ""}`,
22714
+ style: palette,
22715
+ "aria-label": labels.title,
22716
+ "data-setto-ui": true,
22717
+ children: [
22718
+ mounted ? /* @__PURE__ */ jsxs(
22719
+ "section",
22310
22720
  {
22311
- id: `${formId}-input`,
22312
- value: draft,
22313
- rows: 1,
22314
- maxLength: 1500,
22315
- onChange: (event) => setDraft(event.target.value),
22316
- placeholder: labels.placeholder,
22317
- disabled: sending,
22318
- onKeyDown: (event) => {
22319
- if (event.key === "Enter" && !event.shiftKey) {
22320
- event.preventDefault();
22321
- event.currentTarget.form?.requestSubmit();
22322
- }
22323
- }
22721
+ className: "setto-chat__shell",
22722
+ role: "dialog",
22723
+ "aria-modal": "true",
22724
+ "aria-labelledby": `${formId}-title`,
22725
+ children: [
22726
+ /* @__PURE__ */ jsx(
22727
+ "button",
22728
+ {
22729
+ className: `setto-chat__scrim${drawer ? " is-open" : ""}`,
22730
+ type: "button",
22731
+ onClick: () => setDrawer(false),
22732
+ "aria-label": labels.close,
22733
+ tabIndex: drawer ? 0 : -1
22734
+ }
22735
+ ),
22736
+ /* @__PURE__ */ jsxs(
22737
+ "nav",
22738
+ {
22739
+ className: `setto-chat__chats${drawer ? " setto-chat__chats--open" : ""}`,
22740
+ "aria-label": labels.chats,
22741
+ children: [
22742
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__chats-heading", children: [
22743
+ /* @__PURE__ */ jsx("span", { children: labels.chats }),
22744
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setDrawer(false), "aria-label": labels.close, children: /* @__PURE__ */ jsx(CloseIcon, { size: 18 }) })
22745
+ ] }),
22746
+ /* @__PURE__ */ jsxs("button", { className: "setto-chat__new", type: "button", onClick: startNew, children: [
22747
+ /* @__PURE__ */ jsx(PlusIcon, {}),
22748
+ labels.newChat
22749
+ ] }),
22750
+ /* @__PURE__ */ jsx("div", { className: "setto-chat__chat-list", children: conversations.length ? conversations.map((chat) => /* @__PURE__ */ jsxs(
22751
+ "button",
22752
+ {
22753
+ className: `setto-chat__chat-row${conversationId.current === chat.id ? " is-active" : ""}`,
22754
+ type: "button",
22755
+ onClick: () => void loadConversation(chat.id),
22756
+ children: [
22757
+ /* @__PURE__ */ jsx("span", { children: chat.preview }),
22758
+ /* @__PURE__ */ jsx("time", { children: new Date(chat.lastActivityAt).toLocaleDateString(
22759
+ isNo ? "nb-NO" : "en-GB",
22760
+ { day: "numeric", month: "short" }
22761
+ ) })
22762
+ ]
22763
+ },
22764
+ chat.id
22765
+ )) : /* @__PURE__ */ jsx("p", { children: labels.emptyChats }) })
22766
+ ]
22767
+ }
22768
+ ),
22769
+ /* @__PURE__ */ jsxs("main", { className: "setto-chat__main", children: [
22770
+ /* @__PURE__ */ jsxs("header", { className: "setto-chat__header", children: [
22771
+ /* @__PURE__ */ jsx(
22772
+ "button",
22773
+ {
22774
+ className: "setto-chat__round-button",
22775
+ type: "button",
22776
+ onClick: () => setDrawer((current) => !current),
22777
+ "aria-label": labels.chats,
22778
+ "aria-expanded": drawer,
22779
+ children: /* @__PURE__ */ jsx(MenuIcon, {})
22780
+ }
22781
+ ),
22782
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__brand", children: [
22783
+ /* @__PURE__ */ jsx("h2", { id: `${formId}-title`, children: labels.title }),
22784
+ /* @__PURE__ */ jsx("p", { children: siteName ?? "Barista" })
22785
+ ] }),
22786
+ /* @__PURE__ */ jsx(
22787
+ "button",
22788
+ {
22789
+ className: "setto-chat__round-button",
22790
+ type: "button",
22791
+ onClick: hide,
22792
+ "aria-label": labels.close,
22793
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
22794
+ }
22795
+ )
22796
+ ] }),
22797
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__disclosure", children: [
22798
+ /* @__PURE__ */ jsx("span", { children: "BARISTA" }),
22799
+ /* @__PURE__ */ jsx("p", { children: labels.disclosure })
22800
+ ] }),
22801
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__body", children: [
22802
+ /* @__PURE__ */ jsxs(
22803
+ "div",
22804
+ {
22805
+ className: "setto-chat__log",
22806
+ ref: logRef,
22807
+ "aria-live": "polite",
22808
+ "aria-busy": loadingConversation,
22809
+ children: [
22810
+ /* @__PURE__ */ jsx("div", { className: "setto-chat__message-list", children: messages.map((item, index) => /* @__PURE__ */ jsxs(
22811
+ "div",
22812
+ {
22813
+ className: `setto-chat__message setto-chat__message--${item.role}`,
22814
+ children: [
22815
+ item.role === "human" ? /* @__PURE__ */ jsx("span", { className: "setto-chat__speaker", children: labels.team }) : null,
22816
+ /* @__PURE__ */ jsx("span", { className: "setto-chat__message-copy", children: item.content || /* @__PURE__ */ jsx("span", { className: "setto-chat__typing", children: "···" }) }),
22817
+ item.attachments?.map((attachment) => /* @__PURE__ */ jsx(
22818
+ "a",
22819
+ {
22820
+ className: "setto-chat__image",
22821
+ href: attachment.url,
22822
+ target: "_blank",
22823
+ rel: "noreferrer",
22824
+ children: /* @__PURE__ */ jsx("img", { src: attachment.url, alt: labels.image })
22825
+ },
22826
+ attachment.id
22827
+ ))
22828
+ ]
22829
+ },
22830
+ item.id ?? `${item.role}-${index}`
22831
+ )) }),
22832
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__actions", children: [
22833
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: contactTeam, children: [
22834
+ labels.contact,
22835
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "↗" })
22836
+ ] }),
22837
+ /* @__PURE__ */ jsx(
22838
+ "button",
22839
+ {
22840
+ type: "button",
22841
+ onClick: () => {
22842
+ setLeadOpen((value) => !value);
22843
+ setLeadStatus("idle");
22844
+ },
22845
+ "aria-expanded": leadOpen,
22846
+ children: labels.followUp
22847
+ }
22848
+ )
22849
+ ] }),
22850
+ leadOpen ? /* @__PURE__ */ jsxs("form", { className: "setto-chat__lead", onSubmit: submitLead, children: [
22851
+ /* @__PURE__ */ jsx("strong", { children: labels.followUpTitle }),
22852
+ /* @__PURE__ */ jsx("p", { children: labels.followUpDetail }),
22853
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__lead-grid", children: [
22854
+ /* @__PURE__ */ jsxs("label", { children: [
22855
+ labels.name,
22856
+ /* @__PURE__ */ jsx(
22857
+ "input",
22858
+ {
22859
+ value: lead.name,
22860
+ maxLength: 160,
22861
+ onChange: (event) => setLead((current) => ({ ...current, name: event.target.value }))
22862
+ }
22863
+ )
22864
+ ] }),
22865
+ /* @__PURE__ */ jsxs("label", { children: [
22866
+ labels.email,
22867
+ /* @__PURE__ */ jsx(
22868
+ "input",
22869
+ {
22870
+ type: "email",
22871
+ value: lead.email,
22872
+ maxLength: 320,
22873
+ onChange: (event) => setLead((current) => ({ ...current, email: event.target.value }))
22874
+ }
22875
+ )
22876
+ ] }),
22877
+ /* @__PURE__ */ jsxs("label", { children: [
22878
+ labels.phone,
22879
+ /* @__PURE__ */ jsx(
22880
+ "input",
22881
+ {
22882
+ type: "tel",
22883
+ value: lead.phone,
22884
+ maxLength: 64,
22885
+ onChange: (event) => setLead((current) => ({ ...current, phone: event.target.value }))
22886
+ }
22887
+ )
22888
+ ] }),
22889
+ /* @__PURE__ */ jsxs("label", { children: [
22890
+ labels.interest,
22891
+ /* @__PURE__ */ jsx(
22892
+ "input",
22893
+ {
22894
+ value: lead.interest,
22895
+ maxLength: 500,
22896
+ onChange: (event) => setLead((current) => ({ ...current, interest: event.target.value }))
22897
+ }
22898
+ )
22899
+ ] })
22900
+ ] }),
22901
+ /* @__PURE__ */ jsxs("label", { className: "setto-chat__consent", children: [
22902
+ /* @__PURE__ */ jsx(
22903
+ "input",
22904
+ {
22905
+ type: "checkbox",
22906
+ checked: lead.consent,
22907
+ onChange: (event) => setLead((current) => ({ ...current, consent: event.target.checked }))
22908
+ }
22909
+ ),
22910
+ labels.consent
22911
+ ] }),
22912
+ leadStatus === "sent" ? /* @__PURE__ */ jsx("p", { className: "setto-chat__lead-success", children: labels.leadSent }) : null,
22913
+ leadStatus === "failed" ? /* @__PURE__ */ jsx("p", { className: "setto-chat__lead-error", children: conversationId.current ? labels.leadFailed : labels.leadConversation }) : null,
22914
+ leadStatus !== "sent" ? /* @__PURE__ */ jsx(
22915
+ "button",
22916
+ {
22917
+ type: "submit",
22918
+ disabled: leadStatus === "sending" || !lead.consent || !lead.email.trim() && !lead.phone.trim(),
22919
+ children: leadStatus === "sending" ? labels.sending : labels.leadSend
22920
+ }
22921
+ ) : null
22922
+ ] }) : null
22923
+ ]
22924
+ }
22925
+ ),
22926
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__composer-shell", children: [
22927
+ /* @__PURE__ */ jsx(
22928
+ SiteChatComposer,
22929
+ {
22930
+ value: draft,
22931
+ onChange: setDraft,
22932
+ onSubmit: queueOrSend,
22933
+ busy: sending,
22934
+ queued,
22935
+ placeholder: labels.placeholder,
22936
+ attachLabel: labels.attach,
22937
+ sendLabel: labels.send,
22938
+ removeLabel: labels.remove,
22939
+ locale,
22940
+ attachments: files,
22941
+ onAttach: addFiles,
22942
+ onRemoveAttachment: removeFile
22943
+ }
22944
+ ),
22945
+ /* @__PURE__ */ jsx("div", { className: "setto-chat__composer-floor", "aria-hidden": "true" })
22946
+ ] })
22947
+ ] })
22948
+ ] })
22949
+ ]
22324
22950
  }
22325
- ),
22326
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: !draft.trim() || sending, children: sending ? labels.sending : labels.send })
22327
- ] })
22328
- ] }) : null,
22329
- !open ? /* @__PURE__ */ jsxs("button", { className: "setto-chat__launcher", type: "button", onClick: () => setOpen(true), "aria-expanded": "false", children: [
22330
- /* @__PURE__ */ jsx("span", { className: "setto-chat__launcher-mark", "aria-hidden": "true", children: "✦" }),
22331
- /* @__PURE__ */ jsx("span", { children: labels.open })
22332
- ] }) : null
22333
- ] });
22951
+ ) : null,
22952
+ !mounted ? /* @__PURE__ */ jsx(
22953
+ "button",
22954
+ {
22955
+ className: "setto-chat__launcher",
22956
+ type: "button",
22957
+ onClick: show,
22958
+ "aria-label": labels.open,
22959
+ title: labels.open,
22960
+ children: availability.humanAvailable && availability.avatarUrl ? /* @__PURE__ */ jsx("img", { src: availability.avatarUrl, alt: "" }) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx(BaristaIcon, {}) })
22961
+ }
22962
+ ) : null
22963
+ ]
22964
+ }
22965
+ );
22334
22966
  }
22335
22967
  function replaceLastAssistant(messages, content) {
22336
22968
  for (let index = messages.length - 1; index >= 0; index -= 1) {
22337
- if (messages[index]?.role === "assistant") {
22969
+ const current = messages[index];
22970
+ if (current?.role === "assistant") {
22338
22971
  const next = [...messages];
22339
- next[index] = { role: "assistant", content };
22972
+ next[index] = { ...current, content };
22340
22973
  return next;
22341
22974
  }
22342
22975
  }
@@ -22347,51 +22980,15 @@ function useChatStyles() {
22347
22980
  const style = document.createElement("style");
22348
22981
  style.dataset.settoChat = "";
22349
22982
  style.textContent = `
22350
- .setto-chat { position: fixed; right: max(16px, env(safe-area-inset-right)); bottom: max(16px, env(safe-area-inset-bottom)); z-index: 2147483000; color: #231d17; font-family: Archivo, ui-sans-serif, sans-serif; }
22351
- .setto-chat *, .setto-chat *::before, .setto-chat *::after { box-sizing: border-box; }
22352
- .setto-chat--open { inset: 0; right: 0; bottom: 0; width: 100vw; height: 100dvh; display: block; background: #fffaf1; }
22353
- .setto-chat__launcher { border: 0; min-height: 54px; max-width: min(290px, calc(100vw - 32px)); border-radius: 999px; padding: 8px 18px 8px 9px; display: flex; align-items: center; gap: 10px; background: #251d16; color: #fff9ef; box-shadow: 0 14px 40px rgba(35,29,23,.28); font: 600 14px/1 Archivo, ui-sans-serif, sans-serif; cursor: pointer; transition: transform .18s ease, box-shadow .18s ease; }
22354
- .setto-chat__launcher:hover { transform: translateY(-2px); box-shadow: 0 18px 46px rgba(35,29,23,.36); }
22355
- .setto-chat__launcher-mark { width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center; background: #df6540; color: #fff8eb; font-size: 18px; }
22356
- .setto-chat__panel { width: min(390px, calc(100vw - 32px)); height: min(590px, calc(100dvh - 104px)); margin-bottom: 12px; overflow: hidden; display: grid; grid-template-rows: auto 1fr auto auto auto; border: 1px solid rgba(37,29,22,.15); border-radius: 20px; background: #fffaf1; box-shadow: 0 22px 70px rgba(35,29,23,.3); }
22357
- .setto-chat--open .setto-chat__panel { width: 100%; height: 100%; margin: 0; border: 0; border-radius: 0; box-shadow: none; }
22358
- .setto-chat__header { padding: 20px 20px 16px; display: flex; justify-content: space-between; gap: 14px; border-bottom: 1px solid #eadfce; background: radial-gradient(circle at 0 0, #f4d4a9, transparent 52%), #fffaf1; }
22359
- .setto-chat--open .setto-chat__header { padding-top: max(20px, env(safe-area-inset-top)); padding-right: max(20px, env(safe-area-inset-right)); padding-left: max(20px, env(safe-area-inset-left)); }
22360
- .setto-chat__eyebrow { margin: 0 0 4px; color: #b54628; font-size: 10px; font-weight: 700; letter-spacing: .14em; }
22361
- .setto-chat__header h2 { margin: 0; color: #211a14; font-family: "Instrument Serif", Georgia, serif; font-size: 29px; font-weight: 400; line-height: 1; }
22362
- .setto-chat__header p:not(.setto-chat__eyebrow) { margin: 7px 0 0; max-width: 280px; color: #70665a; font-size: 12px; line-height: 1.35; }
22363
- .setto-chat__close { width: 32px; height: 32px; border: 0; border-radius: 50%; background: rgba(64,47,31,.07); color: #4f453a; font: 300 28px/27px Georgia, serif; cursor: pointer; }
22364
- .setto-chat__log { min-height: 0; overflow: auto; padding: 18px; display: flex; flex-direction: column; gap: 10px; scroll-behavior: smooth; }
22365
- .setto-chat__message { max-width: 88%; padding: 11px 13px; border-radius: 14px; font-size: 14px; line-height: 1.48; white-space: pre-wrap; overflow-wrap: anywhere; }
22366
- .setto-chat__message--assistant { align-self: flex-start; background: #efe7da; color: #302820; border-bottom-left-radius: 3px; }
22367
- .setto-chat__message--visitor { align-self: flex-end; background: #2b221b; color: #fffaf1; border-bottom-right-radius: 3px; }
22368
- .setto-chat__message--human { align-self: flex-start; background: #f5d3c7; color: #48261d; border-bottom-left-radius: 3px; }
22369
- .setto-chat__message--system { align-self: center; max-width: 100%; padding: 2px 8px; background: transparent; color: #817568; font-size: 11px; text-align: center; }
22370
- .setto-chat__speaker { display: block; margin-bottom: 3px; color: #a13e24; font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
22371
- .setto-chat__typing { display: inline-block; min-width: 24px; animation: setto-chat-pulse 1.1s ease-in-out infinite; letter-spacing: 2px; }
22372
- @keyframes setto-chat-pulse { 50% { opacity: .3; } }
22373
- .setto-chat__contact { padding: 0 18px 12px; display: flex; gap: 14px; flex-wrap: wrap; }
22374
- .setto-chat__contact button { border: 0; padding: 0; background: transparent; color: #b54628; font: 600 12px/1.4 Archivo, ui-sans-serif, sans-serif; cursor: pointer; }
22375
- .setto-chat__lead { display: grid; gap: 8px; max-height: 255px; overflow: auto; padding: 12px 18px; border-top: 1px solid #eadfce; background: #fff7ed; font-size: 12px; line-height: 1.4; }
22376
- .setto-chat__lead p { margin: 0; color: #70665a; }
22377
- .setto-chat__lead-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
22378
- .setto-chat__lead label { display: grid; gap: 3px; color: #5f5347; font-weight: 600; }
22379
- .setto-chat__lead input:not([type="checkbox"]) { width: 100%; border: 1px solid #d8ccba; border-radius: 7px; background: #fffdf8; color: #231d17; padding: 7px; font: 13px Archivo, ui-sans-serif, sans-serif; }
22380
- .setto-chat__lead .setto-chat__consent { grid-template-columns: auto 1fr; align-items: start; font-weight: 400; }
22381
- .setto-chat__lead button { justify-self: start; min-height: 34px; border: 0; border-radius: 8px; padding: 0 12px; background: #b54628; color: #fffaf1; font: 600 12px Archivo, ui-sans-serif, sans-serif; cursor: pointer; }
22382
- .setto-chat__lead button:disabled { opacity: .5; cursor: default; }
22383
- .setto-chat__lead-success { color: #30683d !important; font-weight: 600; }
22384
- .setto-chat__lead-error { color: #9b2f1e !important; font-weight: 600; }
22385
- .setto-chat__composer { padding: 12px; display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 8px; border-top: 1px solid #eadfce; background: #fffdf8; }
22386
- .setto-chat--open .setto-chat__composer { padding-right: max(12px, env(safe-area-inset-right)); padding-bottom: max(12px, env(safe-area-inset-bottom)); padding-left: max(12px, env(safe-area-inset-left)); }
22387
- .setto-chat__composer textarea { width: 100%; max-height: 100px; resize: none; border: 1px solid #d8ccba; border-radius: 10px; background: #fffaf1; color: #231d17; padding: 10px; font: 14px/1.35 Archivo, ui-sans-serif, sans-serif; outline: none; }
22388
- .setto-chat__composer textarea:focus { border-color: #b54628; box-shadow: 0 0 0 3px rgba(181,70,40,.12); }
22389
- .setto-chat__composer button { min-height: 40px; border: 0; border-radius: 9px; padding: 0 13px; background: #b54628; color: #fffaf1; font: 600 13px Archivo, ui-sans-serif, sans-serif; cursor: pointer; }
22390
- .setto-chat__composer button:disabled { opacity: .5; cursor: default; }
22391
- .setto-chat__sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
22392
- @media (max-width: 480px) { .setto-chat:not(.setto-chat--open) { right: 12px; bottom: max(12px, env(safe-area-inset-bottom)); } .setto-chat:not(.setto-chat--open) .setto-chat__panel { width: calc(100vw - 24px); height: min(610px, calc(100dvh - 88px)); } .setto-chat__launcher { min-height: 50px; } .setto-chat__lead-grid { grid-template-columns: 1fr; } }
22393
- @media (prefers-reduced-motion: reduce) { .setto-chat *, .setto-chat *::before, .setto-chat *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; } }
22394
- `;
22983
+ .setto-chat{position:fixed;right:max(18px,env(safe-area-inset-right));bottom:max(18px,env(safe-area-inset-bottom));z-index:2147483000;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:var(--setto-chat-top,0);right:auto;bottom:auto;left:0;width:100vw;height:var(--setto-chat-height,100svh);overflow:hidden;background:var(--setto-chat-canvas);opacity:0;transition:opacity ${CLOSE_MS}ms ease}.setto-chat--visible{opacity:1}.setto-chat__shell{position:relative;width:100%;height:100%;display:flex;overflow:hidden;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}
22984
+ .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)}
22985
+ .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:relative;z-index:30;min-height:58px;padding:.35rem .75rem;display:flex;align-items:center;gap:.5rem;flex:0 0 auto;background:transparent;isolation:isolate}.setto-chat__header::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;background:color-mix(in srgb,var(--setto-chat-crema) 70%,transparent);backdrop-filter:blur(18px) saturate(150%);-webkit-backdrop-filter:blur(18px) saturate(150%);-webkit-mask-image:linear-gradient(to bottom,rgba(0,0,0,1) 45%,rgba(0,0,0,.45) 78%,rgba(0,0,0,0) 100%);mask-image:linear-gradient(to bottom,rgba(0,0,0,1) 45%,rgba(0,0,0,.45) 78%,rgba(0,0,0,0) 100%)}.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}.setto-chat__disclosure{display:flex;align-items:baseline;gap:.65rem;padding:.9rem .75rem .35rem;flex:0 0 auto}.setto-chat__disclosure span{flex:none;color:var(--setto-chat-accent);font-size:.68rem;font-weight:700;letter-spacing:.12em}.setto-chat__disclosure p{margin:0;color:color-mix(in srgb,var(--setto-chat-ink) 62%,transparent);font-size:.9rem;line-height:1.45}
22986
+ .setto-chat__body{position:relative;display:flex;flex:1 1 auto;min-height:0;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:1.3rem .75rem 8rem;scroll-behavior:smooth;-webkit-overflow-scrolling:touch}.setto-chat__message-list{display:flex;flex-direction:column}.setto-chat__message{display:block;margin-bottom:1.5rem;font-size:16px;line-height:1.65;overflow-wrap:anywhere;white-space:pre-wrap;color:color-mix(in srgb,var(--setto-chat-ink) 88%,transparent)}.setto-chat__message--assistant{align-self:stretch;max-width:100%;padding:0;background:transparent}.setto-chat__message--visitor{align-self:flex-end;max-width:85%;margin-bottom:1.25rem;padding:.65rem .95rem;background:var(--setto-chat-crema);color:var(--setto-chat-ink);border:1px solid color-mix(in srgb,var(--setto-chat-ink) 7%,transparent);border-radius:20px 20px 4px 20px;font-size:.95rem;line-height:1.55;box-shadow:0 1px 2px color-mix(in srgb,var(--setto-chat-ink) 6%,transparent)}.setto-chat__message--human{align-self:stretch;max-width:100%;padding-left:.8rem;border-left:2px solid var(--setto-chat-accent)}.setto-chat__message--system{align-self:center;max-width:85%;padding:.25rem .6rem;color:color-mix(in srgb,var(--setto-chat-ink) 55%,transparent);font-size:.78rem;line-height:1.45;text-align:center}.setto-chat__speaker{display:block;margin-bottom:.15rem;color:var(--setto-chat-accent);font-size:.68rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase}.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:14px;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 10%,transparent)}.setto-chat__image img{display:block;width:min(280px,100%);max-height:260px;object-fit:cover}
22987
+ .setto-chat__actions{display:flex;flex-wrap:wrap;gap:1.35rem;margin-top:auto;padding:1.2rem 0 .65rem}.setto-chat__actions button{display:inline-flex;align-items:center;gap:.25rem;padding:.2rem 0;border:0;background:transparent;color:var(--setto-chat-accent);font-size:.9rem;font-weight:700;cursor:pointer}.setto-chat__lead{display:grid;gap:.6rem;margin:.5rem 0 1rem;padding:1rem;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 12%,transparent);border-radius:16px;background:color-mix(in srgb,var(--setto-chat-crema) 58%,var(--setto-chat-canvas))}.setto-chat__lead strong{font-size:.95rem}.setto-chat__lead p{margin:0;color:color-mix(in srgb,var(--setto-chat-ink) 65%,transparent);font-size:.8rem;line-height:1.45}.setto-chat__lead-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.55rem}.setto-chat__lead label{display:grid;gap:.25rem;font-size:.72rem;font-weight:700}.setto-chat__lead input{min-width:0;padding:.55rem .6rem;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 18%,transparent);border-radius:8px;background:var(--setto-chat-canvas);color:var(--setto-chat-ink);font-size:.85rem}.setto-chat__consent{display:flex!important;align-items:flex-start;gap:.45rem;font-weight:400!important;line-height:1.4}.setto-chat__lead>button{justify-self:start;padding:.55rem .8rem;border:0;border-radius:9px;background:var(--setto-chat-accent);color:var(--setto-chat-raised);font-size:.78rem;font-weight:700;cursor:pointer}.setto-chat__lead>button:disabled{opacity:.4;cursor:default}.setto-chat__lead-success{color:#28633d!important;font-weight:700}.setto-chat__lead-error{color:#a33024!important;font-weight:700}
22988
+ .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;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:1px 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{width:100%;min-height:36px;max-height:200px;padding:.45rem .25rem;resize:none;border:0;outline:none;background:transparent;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}
22989
+ .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 textarea: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}}
22990
+ @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__disclosure{padding-left:.75rem;padding-right:.75rem}.setto-chat__log{padding-left:.75rem;padding-right:.75rem}.setto-chat__lead-grid{grid-template-columns:1fr}.setto-chat__actions{gap:1.1rem}.setto-chat__actions button{font-size:.88rem}}@media(min-width:700px){.setto-chat__header,.setto-chat__disclosure,.setto-chat__log{padding-left:1.25rem;padding-right:1.25rem}.setto-chat__composer-shell{padding-left:1.25rem;padding-right:1.25rem}.setto-chat__composer-floor{width:calc(100% + 2.5rem);margin-left:-1.25rem}.setto-chat__message{font-size:16.5px}}@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}}
22991
+ `;
22395
22992
  document.head.appendChild(style);
22396
22993
  return () => style.remove();
22397
22994
  }, []);