@sevenfold/setto-client 0.23.0 → 0.24.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.
@@ -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,7 @@ function EditModeShell({ children, themeStore, active }) {
22033
22034
  ] });
22034
22035
  }
22035
22036
  const VISITOR_KEY = "setto-chat-visitor";
22037
+ const CLOSE_MS = 210;
22036
22038
  function visitorId() {
22037
22039
  try {
22038
22040
  const stored = window.localStorage.getItem(VISITOR_KEY);
@@ -22051,170 +22053,189 @@ function intro(locale, name) {
22051
22053
  return norwegian(locale) ? `Hei! Jeg er Barista, ${name ? `${name}s` : "nettstedets"} AI-assistent. Jeg svarer ut fra det teamet har delt her, og hjelper deg videre til dem når det trengs.` : `Hello! I’m Barista, the AI assistant for ${name ?? "this website"}. I answer from what the team has shared here and help you reach them when needed.`;
22052
22054
  }
22053
22055
  function contactTeam() {
22054
- const link = document.querySelector(
22055
- 'a[href*="kontakt" i], a[href*="contact" i], a[href^="mailto:"]'
22056
- );
22056
+ const link = document.querySelector('a[href*="kontakt" i], a[href*="contact" i], a[href^="mailto:"]');
22057
22057
  if (link) link.click();
22058
22058
  else window.location.hash = "kontakt";
22059
22059
  }
22060
+ function validColor(value, fallback) {
22061
+ return value && (/^#[0-9a-f]{3,8}$/i.test(value) || /^rgb/.test(value)) ? value : fallback;
22062
+ }
22063
+ function chatPalette(colors) {
22064
+ const values = colors?.map((color) => color.value) ?? [];
22065
+ const ink = values.find((value) => /^#(?:[0-2][0-9a-f]){3}$/i.test(value));
22066
+ return { "--setto-chat-pale": validColor(values[0], "#F2EBDF"), "--setto-chat-paper": validColor(values[1], "#FAF6EE"), "--setto-chat-ink": validColor(ink, "#211711"), "--setto-chat-accent": validColor(values[values.length - 2] ?? values.at(-1), "#C4502A") };
22067
+ }
22060
22068
  function SettoChat() {
22061
22069
  const { config } = useSetto();
22062
22070
  const { i18n } = useTranslation();
22063
22071
  const apiUrl = config.apiUrl.replace(/\/+$/, "");
22064
22072
  const [settings, setSettings] = useState(null);
22065
- const [open, setOpen] = useState(false);
22073
+ const [mounted, setMounted] = useState(false);
22074
+ const [visible, setVisible] = useState(false);
22075
+ const [drawer, setDrawer] = useState(false);
22066
22076
  const [messages, setMessages] = useState([]);
22077
+ const [conversations, setConversations] = useState([]);
22067
22078
  const [draft, setDraft] = useState("");
22079
+ const [files, setFiles] = useState([]);
22068
22080
  const [sending, setSending] = useState(false);
22081
+ const [loadingConversation, setLoadingConversation] = useState(false);
22082
+ const [availability, setAvailability] = useState({ humanAvailable: false, avatarUrl: null });
22069
22083
  const [leadOpen, setLeadOpen] = useState(false);
22070
22084
  const [leadStatus, setLeadStatus] = useState("idle");
22071
22085
  const [lead, setLead] = useState({ name: "", email: "", phone: "", interest: "", consent: false });
22072
22086
  const conversationId = useRef(null);
22073
22087
  const visitor = useRef(null);
22074
22088
  const logRef = useRef(null);
22089
+ const fileInput = useRef(null);
22075
22090
  const formId = useId();
22091
+ const palette = useMemo(() => chatPalette(config.brandColors), [config.brandColors]);
22076
22092
  useChatStyles();
22093
+ const locale = i18n.language || "no";
22094
+ const isNo = norwegian(locale);
22095
+ const siteName = settings?.displayName?.trim() || config.displayName?.trim() || null;
22096
+ const labels = isNo ? { open: siteName ? `Åpne chat med ${siteName}` : "Åpne chat", close: "Lukk chatten", title: "Spør Barista", chats: "Chats", newChat: "Ny chat", disclosure: "AI-assistent · svarer fra nettstedets godkjente informasjon", placeholder: "Hva vil du spørre om?", send: "Send", sending: "Svarer …", attach: "Legg ved bilde", contact: "Kontakt teamet", followUp: "Be om oppfølging", followUpTitle: "La teamet følge opp", followUpDetail: "Legg igjen kontaktinformasjon hvis du vil at teamet skal følge opp denne samtalen.", name: "Navn", email: "E-post", phone: "Telefon", interest: "Hva vil du ha hjelp med?", consent: "Ja, teamet kan bruke opplysningene mine til å følge opp denne henvendelsen.", leadSend: "Send forespørsel", leadSent: "Takk — forespørselen er sendt til teamet.", leadFailed: "Kunne ikke lagre forespørselen. Prøv igjen eller kontakt teamet direkte.", leadConversation: "Still et spørsmål først, så kan vi knytte oppfølgingen til samtalen.", failed: "Jeg får ikke svart akkurat nå. Kontakt gjerne teamet direkte.", image: "Bilde", team: "Teamet", emptyChats: "Nye samtaler du starter her vises i denne listen." } : { open: siteName ? `Open chat with ${siteName}` : "Open chat", close: "Close chat", title: "Ask Barista", chats: "Chats", newChat: "New chat", disclosure: "AI assistant · answers from this website’s approved information", placeholder: "What would you like to ask?", send: "Send", sending: "Replying …", attach: "Attach image", contact: "Contact the team", followUp: "Ask for follow-up", followUpTitle: "Let the team follow up", followUpDetail: "Leave contact details if you want the team to follow up on this conversation.", name: "Name", email: "Email", phone: "Phone", interest: "What would you like help with?", consent: "Yes, the team may use my details to follow up on this enquiry.", leadSend: "Send request", leadSent: "Thanks — your request has been sent to the team.", leadFailed: "We could not save your request. Please try again or contact the team directly.", leadConversation: "Ask a question first so we can link the follow-up to this conversation.", failed: "I cannot reply right now. Please contact the team directly.", image: "Image", team: "The team", emptyChats: "New conversations you start here will appear in this list." };
22097
+ const ensureVisitor = () => visitor.current ??= visitorId();
22098
+ const loadChats = async () => {
22099
+ const id = visitor.current;
22100
+ if (!id) return;
22101
+ try {
22102
+ const response = await fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/public?${new URLSearchParams({ visitorId: id })}`, { headers: { Accept: "application/json" } });
22103
+ if (response.ok) setConversations((await response.json()).conversations);
22104
+ } catch {
22105
+ }
22106
+ };
22107
+ const loadConversation = async (id) => {
22108
+ const visitorIdValue = ensureVisitor();
22109
+ setLoadingConversation(true);
22110
+ try {
22111
+ const response = await fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/${encodeURIComponent(id)}/public?${new URLSearchParams({ visitorId: visitorIdValue })}`, { headers: { Accept: "application/json" } });
22112
+ if (!response.ok) return;
22113
+ const next = await response.json();
22114
+ conversationId.current = next.conversationId;
22115
+ setMessages(next.messages);
22116
+ setLeadOpen(false);
22117
+ setDrawer(false);
22118
+ } finally {
22119
+ setLoadingConversation(false);
22120
+ }
22121
+ };
22077
22122
  useEffect(() => {
22078
22123
  if (window.location.pathname.startsWith("/setto")) return;
22079
22124
  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) => {
22125
+ fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/settings/public`, { headers: { Accept: "application/json" } }).then(async (response) => response.ok ? await response.json() : null).then((next) => {
22083
22126
  if (!cancelled && next?.enabled) setSettings(next);
22084
- }).catch(() => {
22085
- });
22127
+ }).catch(() => void 0);
22086
22128
  return () => {
22087
22129
  cancelled = true;
22088
22130
  };
22089
22131
  }, [apiUrl, config.siteId]);
22090
22132
  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]);
22133
+ if (!settings) return;
22134
+ let cancelled = false;
22135
+ const refresh = () => fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/availability/public`, { headers: { Accept: "application/json" } }).then(async (response) => response.ok ? await response.json() : null).then((next) => {
22136
+ if (!cancelled && next) setAvailability(next);
22137
+ }).catch(() => void 0);
22138
+ void refresh();
22139
+ const timer = window.setInterval(() => void refresh(), 3e4);
22140
+ return () => {
22141
+ cancelled = true;
22142
+ window.clearInterval(timer);
22143
+ };
22144
+ }, [apiUrl, config.siteId, settings]);
22145
+ useEffect(() => {
22146
+ if (mounted && messages.length === 0 && settings) setMessages([{ role: "assistant", content: intro(locale, settings.displayName ?? void 0) }]);
22147
+ }, [mounted, messages.length, settings, locale]);
22094
22148
  useEffect(() => {
22095
- if (open) logRef.current?.scrollTo({ top: logRef.current.scrollHeight });
22096
- }, [messages, open]);
22149
+ if (mounted) logRef.current?.scrollTo({ top: logRef.current.scrollHeight, behavior: "smooth" });
22150
+ }, [messages, mounted, sending]);
22151
+ useEffect(() => () => files.forEach((image) => URL.revokeObjectURL(image.preview)), [files]);
22097
22152
  useEffect(() => {
22098
- if (!open || !conversationId.current || !visitor.current || sending) return;
22153
+ if (!mounted || !conversationId.current || !visitor.current || sending) return;
22099
22154
  let cancelled = false;
22100
22155
  const refresh = async () => {
22101
22156
  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;
22157
+ const response = await fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/conversations/${encodeURIComponent(conversationId.current)}/public?${new URLSearchParams({ visitorId: visitor.current })}`, { headers: { Accept: "application/json" } });
22158
+ if (!response.ok || cancelled) return;
22108
22159
  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
- });
22160
+ if (!cancelled) setMessages(next.messages);
22115
22161
  } catch {
22116
22162
  }
22117
22163
  };
22118
- void refresh();
22119
22164
  const timer = window.setInterval(() => void refresh(), 5e3);
22120
22165
  return () => {
22121
22166
  cancelled = true;
22122
22167
  window.clearInterval(timer);
22123
22168
  };
22124
- }, [apiUrl, config.siteId, open, sending]);
22169
+ }, [apiUrl, config.siteId, mounted, sending]);
22125
22170
  if (!settings || window.location.pathname.startsWith("/setto")) return null;
22126
- 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",
22130
- close: "Lukk chatten",
22131
- title: "Spør Barista",
22132
- disclosure: "AI-assistent · svarer fra nettstedets godkjente informasjon",
22133
- placeholder: "Skriv spørsmålet ditt …",
22134
- send: "Send",
22135
- sending: "Svarer …",
22136
- contact: "Kontakt teamet",
22137
- followUp: "Be om oppfølging",
22138
- followUpTitle: "La teamet følge opp",
22139
- followUpDetail: "Legg igjen kontaktinformasjon hvis du vil at teamet skal følge opp denne samtalen.",
22140
- name: "Navn",
22141
- email: "E-post",
22142
- phone: "Telefon",
22143
- interest: "Hva vil du ha hjelp med?",
22144
- consent: "Ja, teamet kan bruke opplysningene mine til å følge opp denne henvendelsen.",
22145
- leadSend: "Send forespørsel",
22146
- leadSent: "Takk — forespørselen er sendt til teamet.",
22147
- leadFailed: "Kunne ikke lagre forespørselen. Prøv igjen eller kontakt teamet direkte.",
22148
- 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."
22150
- } : {
22151
- open: siteName ? `Chat with ${siteName}` : "Chat with us",
22152
- close: "Close chat",
22153
- title: "Ask Barista",
22154
- disclosure: "AI assistant · answers from this website’s approved information",
22155
- placeholder: "Write your question …",
22156
- send: "Send",
22157
- sending: "Replying …",
22158
- contact: "Contact the team",
22159
- followUp: "Ask for follow-up",
22160
- followUpTitle: "Let the team follow up",
22161
- followUpDetail: "Leave contact details if you want the team to follow up on this conversation.",
22162
- name: "Name",
22163
- email: "Email",
22164
- phone: "Phone",
22165
- interest: "What would you like help with?",
22166
- consent: "Yes, the team may use my details to follow up on this enquiry.",
22167
- leadSend: "Send request",
22168
- leadSent: "Thanks — your request has been sent to the team.",
22169
- leadFailed: "We could not save your request. Please try again or contact the team directly.",
22170
- 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."
22171
+ const show = () => {
22172
+ setMounted(true);
22173
+ window.requestAnimationFrame(() => setVisible(true));
22174
+ ensureVisitor();
22175
+ void loadChats();
22176
+ };
22177
+ const hide = () => {
22178
+ setVisible(false);
22179
+ window.setTimeout(() => {
22180
+ setMounted(false);
22181
+ setDrawer(false);
22182
+ }, CLOSE_MS);
22183
+ };
22184
+ const startNew = () => {
22185
+ conversationId.current = null;
22186
+ setMessages([{ role: "assistant", content: intro(locale, settings.displayName ?? void 0) }]);
22187
+ setLeadOpen(false);
22188
+ setDrawer(false);
22189
+ setFiles([]);
22172
22190
  };
22173
22191
  const submit = async (event) => {
22174
22192
  event.preventDefault();
22175
- const message = draft.trim();
22176
- if (!message || sending) return;
22193
+ const content = draft.trim();
22194
+ if (!content && !files.length || sending) return;
22177
22195
  setDraft("");
22178
22196
  setSending(true);
22179
- setMessages((previous) => [...previous, { role: "visitor", content: message }, { role: "assistant", content: "" }]);
22180
- visitor.current ??= visitorId();
22197
+ const visitorIdValue = ensureVisitor();
22181
22198
  try {
22182
- const response = await fetch(
22183
- `${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/messages`,
22184
- {
22185
- method: "POST",
22186
- headers: { "Content-Type": "application/json", Accept: "text/plain, application/json" },
22187
- body: JSON.stringify({
22188
- conversationId: conversationId.current ?? void 0,
22189
- visitorId: visitor.current,
22190
- locale,
22191
- page: window.location.href,
22192
- message
22193
- })
22194
- }
22195
- );
22199
+ const uploaded = [];
22200
+ for (const image of files) {
22201
+ const file = image.file;
22202
+ const form = new FormData();
22203
+ form.set("visitorId", visitorIdValue);
22204
+ if (conversationId.current) form.set("conversationId", conversationId.current);
22205
+ form.set("locale", locale);
22206
+ form.set("page", window.location.href);
22207
+ form.set("file", file, file.name);
22208
+ const response2 = await fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/attachments`, { method: "POST", body: form });
22209
+ if (!response2.ok) throw new Error("image_upload_failed");
22210
+ const body = await response2.json();
22211
+ conversationId.current = body.conversationId;
22212
+ uploaded.push(body.attachment);
22213
+ }
22214
+ setFiles([]);
22215
+ setMessages((previous) => [...previous, { role: "visitor", content: content || `[${labels.image}]`, attachments: uploaded }, { role: "assistant", content: "" }]);
22216
+ const response = await fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/messages`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "text/plain, application/json" }, body: JSON.stringify({ conversationId: conversationId.current ?? void 0, visitorId: visitorIdValue, locale, page: window.location.href, message: content || void 0, attachmentIds: uploaded.map((attachment) => attachment.id) }) });
22196
22217
  const headerConversation = response.headers.get("X-Setto-Conversation");
22197
22218
  if (headerConversation) conversationId.current = headerConversation;
22198
- const contentType = response.headers.get("content-type") ?? "";
22199
22219
  if (!response.ok) throw new Error(`chat_failed_${response.status}`);
22200
- if (contentType.includes("application/json")) {
22220
+ if ((response.headers.get("content-type") ?? "").includes("application/json")) {
22201
22221
  const body = await response.json();
22202
22222
  if (body.conversationId) conversationId.current = body.conversationId;
22203
22223
  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));
22224
+ } else {
22225
+ const reader = response.body?.getReader();
22226
+ if (!reader) throw new Error("chat_missing_stream");
22227
+ const decoder = new TextDecoder();
22228
+ let answer = "";
22229
+ while (true) {
22230
+ const { done, value } = await reader.read();
22231
+ if (done) break;
22232
+ answer += decoder.decode(value, { stream: true });
22233
+ setMessages((previous) => replaceLastAssistant(previous, answer));
22234
+ }
22235
+ answer += decoder.decode();
22236
+ if (!answer.trim()) setMessages((previous) => replaceLastAssistant(previous, labels.failed));
22215
22237
  }
22216
- answer += decoder.decode();
22217
- if (!answer.trim()) setMessages((previous) => replaceLastAssistant(previous, labels.failed));
22238
+ void loadChats();
22218
22239
  } catch {
22219
22240
  setMessages((previous) => replaceLastAssistant(previous, labels.failed));
22220
22241
  } finally {
@@ -22223,120 +22244,122 @@ function SettoChat() {
22223
22244
  };
22224
22245
  const submitLead = async (event) => {
22225
22246
  event.preventDefault();
22226
- if (leadStatus === "sending") return;
22227
- if (!conversationId.current || !visitor.current) {
22247
+ if (leadStatus === "sending" || !conversationId.current || !visitor.current) {
22228
22248
  setLeadStatus("failed");
22229
22249
  return;
22230
22250
  }
22231
22251
  setLeadStatus("sending");
22232
22252
  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}`);
22253
+ const response = await fetch(`${apiUrl}/sites/${encodeURIComponent(config.siteId)}/chat/leads`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify({ conversationId: conversationId.current, visitorId: visitor.current, consent: lead.consent, name: lead.name.trim() || void 0, email: lead.email.trim() || void 0, phone: lead.phone.trim() || void 0, interest: lead.interest.trim() || void 0 }) });
22254
+ if (!response.ok) throw new Error("lead_failed");
22247
22255
  setLeadStatus("sent");
22248
22256
  } catch {
22249
22257
  setLeadStatus("failed");
22250
22258
  }
22251
22259
  };
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 })
22260
+ return /* @__PURE__ */ jsxs("aside", { className: `setto-chat${mounted ? " setto-chat--mounted" : ""}${visible ? " setto-chat--visible" : ""}`, style: palette, "aria-label": labels.title, "data-setto-ui": true, children: [
22261
+ mounted ? /* @__PURE__ */ jsxs("section", { className: "setto-chat__shell", role: "dialog", "aria-modal": "true", "aria-labelledby": `${formId}-title`, children: [
22262
+ /* @__PURE__ */ jsxs("nav", { className: `setto-chat__chats${drawer ? " setto-chat__chats--open" : ""}`, "aria-label": labels.chats, children: [
22263
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__chats-heading", children: [
22264
+ /* @__PURE__ */ jsx("span", { children: labels.chats }),
22265
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: startNew, "aria-label": labels.newChat, children: "+" })
22259
22266
  ] }),
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: "↗" })
22267
+ /* @__PURE__ */ jsxs("button", { className: "setto-chat__new", type: "button", onClick: startNew, children: [
22268
+ "+ ",
22269
+ labels.newChat
22271
22270
  ] }),
22272
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
22273
- setLeadOpen((value) => !value);
22274
- setLeadStatus("idle");
22275
- }, children: labels.followUp })
22271
+ /* @__PURE__ */ jsx("div", { className: "setto-chat__chat-list", children: conversations.length ? conversations.map((chat) => /* @__PURE__ */ jsxs("button", { className: `setto-chat__chat-row${conversationId.current === chat.id ? " is-active" : ""}`, type: "button", onClick: () => void loadConversation(chat.id), children: [
22272
+ /* @__PURE__ */ jsx("span", { children: chat.preview }),
22273
+ /* @__PURE__ */ jsx("time", { children: new Date(chat.lastActivityAt).toLocaleDateString(isNo ? "nb-NO" : "en-GB", { day: "numeric", month: "short" }) })
22274
+ ] }, chat.id)) : /* @__PURE__ */ jsx("p", { children: labels.emptyChats }) })
22276
22275
  ] }),
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 })) })
22276
+ /* @__PURE__ */ jsxs("main", { className: "setto-chat__main", children: [
22277
+ /* @__PURE__ */ jsxs("header", { className: "setto-chat__header", children: [
22278
+ /* @__PURE__ */ jsx("button", { className: "setto-chat__round-button", type: "button", onClick: () => setDrawer((current) => !current), "aria-label": labels.chats, "aria-expanded": drawer, children: "☰" }),
22279
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__brand", children: [
22280
+ /* @__PURE__ */ jsx("h2", { id: `${formId}-title`, children: labels.title }),
22281
+ /* @__PURE__ */ jsx("p", { children: siteName ?? "Barista" })
22292
22282
  ] }),
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
- ] })
22283
+ /* @__PURE__ */ jsx("button", { className: "setto-chat__round-button", type: "button", onClick: hide, "aria-label": labels.close, children: "×" })
22297
22284
  ] }),
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
22285
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__disclosure", children: [
22286
+ /* @__PURE__ */ jsx("span", { children: "BARISTA" }),
22287
+ /* @__PURE__ */ jsx("p", { children: labels.disclosure })
22301
22288
  ] }),
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",
22310
- {
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) => {
22289
+ /* @__PURE__ */ jsx("div", { className: "setto-chat__log", ref: logRef, "aria-live": "polite", "aria-busy": loadingConversation, children: messages.map((item, index) => /* @__PURE__ */ jsxs("div", { className: `setto-chat__message setto-chat__message--${item.role}`, children: [
22290
+ item.role === "human" ? /* @__PURE__ */ jsx("span", { className: "setto-chat__speaker", children: labels.team }) : null,
22291
+ item.content || /* @__PURE__ */ jsx("span", { className: "setto-chat__typing", children: "···" }),
22292
+ item.attachments?.map((attachment) => /* @__PURE__ */ jsx("a", { className: "setto-chat__image", href: attachment.url, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx("img", { src: attachment.url, alt: labels.image }) }, attachment.id))
22293
+ ] }, item.id ?? `${item.role}-${index}`)) }),
22294
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__actions", children: [
22295
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: contactTeam, children: [
22296
+ labels.contact,
22297
+ " ",
22298
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "↗" })
22299
+ ] }),
22300
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
22301
+ setLeadOpen((value) => !value);
22302
+ setLeadStatus("idle");
22303
+ }, children: labels.followUp })
22304
+ ] }),
22305
+ leadOpen ? /* @__PURE__ */ jsxs("form", { className: "setto-chat__lead", onSubmit: submitLead, children: [
22306
+ /* @__PURE__ */ jsx("strong", { children: labels.followUpTitle }),
22307
+ /* @__PURE__ */ jsx("p", { children: labels.followUpDetail }),
22308
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__lead-grid", children: [
22309
+ /* @__PURE__ */ jsxs("label", { children: [
22310
+ labels.name,
22311
+ /* @__PURE__ */ jsx("input", { value: lead.name, maxLength: 160, onChange: (event) => setLead((current) => ({ ...current, name: event.target.value })) })
22312
+ ] }),
22313
+ /* @__PURE__ */ jsxs("label", { children: [
22314
+ labels.email,
22315
+ /* @__PURE__ */ jsx("input", { type: "email", value: lead.email, maxLength: 320, onChange: (event) => setLead((current) => ({ ...current, email: event.target.value })) })
22316
+ ] }),
22317
+ /* @__PURE__ */ jsxs("label", { children: [
22318
+ labels.phone,
22319
+ /* @__PURE__ */ jsx("input", { type: "tel", value: lead.phone, maxLength: 64, onChange: (event) => setLead((current) => ({ ...current, phone: event.target.value })) })
22320
+ ] }),
22321
+ /* @__PURE__ */ jsxs("label", { children: [
22322
+ labels.interest,
22323
+ /* @__PURE__ */ jsx("input", { value: lead.interest, maxLength: 500, onChange: (event) => setLead((current) => ({ ...current, interest: event.target.value })) })
22324
+ ] })
22325
+ ] }),
22326
+ /* @__PURE__ */ jsxs("label", { className: "setto-chat__consent", children: [
22327
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: lead.consent, onChange: (event) => setLead((current) => ({ ...current, consent: event.target.checked })) }),
22328
+ labels.consent
22329
+ ] }),
22330
+ leadStatus === "sent" ? /* @__PURE__ */ jsx("p", { className: "setto-chat__lead-success", children: labels.leadSent }) : null,
22331
+ leadStatus === "failed" ? /* @__PURE__ */ jsx("p", { className: "setto-chat__lead-error", children: conversationId.current ? labels.leadFailed : labels.leadConversation }) : null,
22332
+ 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
22333
+ ] }) : null,
22334
+ /* @__PURE__ */ jsxs("form", { className: "setto-chat__composer", onSubmit: submit, children: [
22335
+ files.length ? /* @__PURE__ */ jsx("div", { className: "setto-chat__previews", children: files.map((image, index) => /* @__PURE__ */ jsxs("span", { children: [
22336
+ /* @__PURE__ */ jsx("img", { src: image.preview, alt: image.file.name }),
22337
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setFiles((current) => current.filter((_, itemIndex) => itemIndex !== index)), "aria-label": `Remove ${image.file.name}`, children: "×" })
22338
+ ] }, `${image.file.name}-${index}`)) }) : null,
22339
+ /* @__PURE__ */ jsxs("div", { className: "setto-chat__composer-row", children: [
22340
+ /* @__PURE__ */ jsx("input", { ref: fileInput, className: "setto-chat__file", type: "file", accept: "image/jpeg,image/png,image/webp,image/gif", multiple: true, onChange: (event) => setFiles(Array.from(event.target.files ?? []).slice(0, 4).map((file) => ({ file, preview: URL.createObjectURL(file) }))) }),
22341
+ /* @__PURE__ */ jsx("button", { className: "setto-chat__attach", type: "button", onClick: () => fileInput.current?.click(), "aria-label": labels.attach, children: "+" }),
22342
+ /* @__PURE__ */ jsx("label", { className: "setto-chat__sr", htmlFor: `${formId}-input`, children: labels.placeholder }),
22343
+ /* @__PURE__ */ jsx("textarea", { id: `${formId}-input`, value: draft, rows: 1, maxLength: 1500, onChange: (event) => setDraft(event.target.value), placeholder: labels.placeholder, disabled: sending, onKeyDown: (event) => {
22319
22344
  if (event.key === "Enter" && !event.shiftKey) {
22320
22345
  event.preventDefault();
22321
22346
  event.currentTarget.form?.requestSubmit();
22322
22347
  }
22323
- }
22324
- }
22325
- ),
22326
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: !draft.trim() || sending, children: sending ? labels.sending : labels.send })
22348
+ } }),
22349
+ /* @__PURE__ */ jsx("button", { className: "setto-chat__send", type: "submit", disabled: !draft.trim() && !files.length || sending, "aria-label": labels.send, children: sending ? "…" : "↑" })
22350
+ ] })
22351
+ ] })
22327
22352
  ] })
22328
22353
  ] }) : 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
22354
+ !mounted ? /* @__PURE__ */ jsx("button", { className: "setto-chat__launcher", type: "button", onClick: show, "aria-label": labels.open, title: labels.open, children: availability.humanAvailable && availability.avatarUrl ? /* @__PURE__ */ jsx("img", { src: availability.avatarUrl, alt: "" }) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "✦" }) }) : null
22333
22355
  ] });
22334
22356
  }
22335
22357
  function replaceLastAssistant(messages, content) {
22336
22358
  for (let index = messages.length - 1; index >= 0; index -= 1) {
22337
- if (messages[index]?.role === "assistant") {
22359
+ const current = messages[index];
22360
+ if (current?.role === "assistant") {
22338
22361
  const next = [...messages];
22339
- next[index] = { role: "assistant", content };
22362
+ next[index] = { ...current, content };
22340
22363
  return next;
22341
22364
  }
22342
22365
  }
@@ -22347,51 +22370,15 @@ function useChatStyles() {
22347
22370
  const style = document.createElement("style");
22348
22371
  style.dataset.settoChat = "";
22349
22372
  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
- `;
22373
+ .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,ui-sans-serif,sans-serif}.setto-chat *,.setto-chat *::before,.setto-chat *::after{box-sizing:border-box}.setto-chat--mounted{inset:0;right:0;bottom:0;width:100vw;height:100dvh;background:color-mix(in srgb,var(--setto-chat-paper) 88%,transparent);backdrop-filter:blur(8px);opacity:0;transition:opacity ${CLOSE_MS}ms ease}.setto-chat--visible{opacity:1}.setto-chat__shell{width:100%;height:100%;display:flex;overflow:hidden;opacity:0;transform:translateY(14px) scale(.992);transition:opacity ${CLOSE_MS}ms ease,transform ${CLOSE_MS}ms cubic-bezier(.2,.8,.2,1);background:var(--setto-chat-paper)}.setto-chat--visible .setto-chat__shell{opacity:1;transform:none}
22374
+ .setto-chat__chats{width:274px;flex:0 0 274px;padding:calc(25px + env(safe-area-inset-top)) 14px 22px;background:color-mix(in srgb,var(--setto-chat-pale) 72%,white);border-right:1px solid color-mix(in srgb,var(--setto-chat-ink) 12%,transparent);display:flex;flex-direction:column;gap:18px}.setto-chat__chats-heading{padding:0 7px;display:flex;align-items:center;justify-content:space-between;font-size:12px;font-weight:800;letter-spacing:.13em;text-transform:uppercase}.setto-chat__chats-heading button{border:0;background:transparent;font-size:22px;cursor:pointer;color:var(--setto-chat-ink)}.setto-chat__new{border:1px solid color-mix(in srgb,var(--setto-chat-ink) 18%,transparent);border-radius:14px;padding:11px 13px;background:rgba(255,255,255,.45);color:var(--setto-chat-ink);font:600 13px Archivo,sans-serif;text-align:left;cursor:pointer}.setto-chat__chat-list{display:grid;gap:5px;overflow:auto}.setto-chat__chat-list p{margin:7px;padding:0;color:color-mix(in srgb,var(--setto-chat-ink) 57%,transparent);font-size:12px;line-height:1.45}.setto-chat__chat-row{border:0;border-radius:11px;padding:10px;background:transparent;color:var(--setto-chat-ink);display:grid;gap:4px;text-align:left;cursor:pointer}.setto-chat__chat-row:hover,.setto-chat__chat-row.is-active{background:color-mix(in srgb,var(--setto-chat-paper) 75%,transparent)}.setto-chat__chat-row span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:600 13px Archivo,sans-serif}.setto-chat__chat-row time{font-size:11px;color:color-mix(in srgb,var(--setto-chat-ink) 55%,transparent)}
22375
+ .setto-chat__main{min-width:0;flex:1;display:grid;grid-template-rows:auto auto minmax(0,1fr) auto auto auto;background:var(--setto-chat-paper)}.setto-chat__header{min-height:86px;padding:calc(14px + env(safe-area-inset-top)) 26px 12px;display:grid;grid-template-columns:46px 1fr 46px;align-items:center;border-bottom:1px solid color-mix(in srgb,var(--setto-chat-ink) 10%,transparent)}.setto-chat__round-button{width:46px;height:46px;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 11%,transparent);border-radius:50%;background:color-mix(in srgb,var(--setto-chat-paper) 72%,white);color:var(--setto-chat-ink);font:500 23px/1 Archivo,sans-serif;cursor:pointer}.setto-chat__brand{text-align:center}.setto-chat__brand h2{margin:0;font:400 26px/1.05 "Instrument Serif",Georgia,serif;letter-spacing:-.025em}.setto-chat__brand p{margin:3px 0 0;color:color-mix(in srgb,var(--setto-chat-ink) 57%,transparent);font-size:12px}.setto-chat__disclosure{padding:14px max(32px,6vw) 6px;display:flex;align-items:baseline;gap:10px}.setto-chat__disclosure span{font-size:10px;font-weight:800;letter-spacing:.16em;color:var(--setto-chat-accent)}.setto-chat__disclosure p{margin:0;color:color-mix(in srgb,var(--setto-chat-ink) 60%,transparent);font-size:12px}
22376
+ .setto-chat__log{min-height:0;overflow:auto;padding:28px max(32px,10vw);display:flex;flex-direction:column;gap:12px;scroll-behavior:smooth}.setto-chat__message{max-width:min(620px,82%);padding:14px 16px;border-radius:18px;font-size:15px;line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}.setto-chat__message--assistant{align-self:flex-start;background:color-mix(in srgb,var(--setto-chat-pale) 82%,white);border-bottom-left-radius:4px}.setto-chat__message--visitor{align-self:flex-end;background:var(--setto-chat-ink);color:var(--setto-chat-paper);border-bottom-right-radius:4px}.setto-chat__message--human{align-self:flex-start;background:color-mix(in srgb,var(--setto-chat-accent) 20%,var(--setto-chat-paper));border-bottom-left-radius:4px}.setto-chat__message--system{align-self:center;padding:3px 10px;background:transparent;color:color-mix(in srgb,var(--setto-chat-ink) 55%,transparent);font-size:12px;text-align:center}.setto-chat__speaker{display:block;margin-bottom:4px;color:var(--setto-chat-accent);font-size:10px;font-weight:800;letter-spacing:.11em;text-transform:uppercase}.setto-chat__typing{animation:setto-chat-pulse 1s ease-in-out infinite}.setto-chat__image{display:block;margin-top:9px;overflow:hidden;border-radius:11px}.setto-chat__image img{display:block;width:min(280px,100%);max-height:260px;object-fit:cover}
22377
+ .setto-chat__actions{padding:8px max(30px,6vw) 11px;display:flex;gap:20px}.setto-chat__actions button{border:0;background:transparent;color:var(--setto-chat-accent);font:700 13px Archivo,sans-serif;cursor:pointer}.setto-chat__lead{margin:0 max(30px,6vw) 10px;padding:16px;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 13%,transparent);border-radius:16px;background:color-mix(in srgb,var(--setto-chat-pale) 55%,white);display:grid;gap:9px}.setto-chat__lead strong{font-size:15px}.setto-chat__lead p{margin:0;color:color-mix(in srgb,var(--setto-chat-ink) 65%,transparent);font-size:12px;line-height:1.4}.setto-chat__lead-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.setto-chat__lead label{display:grid;gap:4px;font-size:11px;font-weight:700}.setto-chat__lead input{min-width:0;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 18%,transparent);border-radius:8px;padding:8px;background:var(--setto-chat-paper);color:var(--setto-chat-ink);font:13px Archivo,sans-serif}.setto-chat__consent{display:flex!important;grid-template-columns:auto 1fr;align-items:start;font-weight:400!important;line-height:1.35}.setto-chat__lead>button{justify-self:start;border:0;border-radius:9px;padding:9px 12px;background:var(--setto-chat-accent);color:white;font:700 12px Archivo,sans-serif;cursor:pointer}.setto-chat__lead-success{color:#28633d!important;font-weight:700}.setto-chat__lead-error{color:#a33024!important;font-weight:700}
22378
+ .setto-chat__composer{padding:0 max(24px,6vw) max(17px,env(safe-area-inset-bottom));background:var(--setto-chat-paper)}.setto-chat__composer-row{min-height:78px;padding:12px 14px;display:grid;grid-template-columns:36px 1fr 44px;gap:8px;align-items:end;border:1.5px solid color-mix(in srgb,var(--setto-chat-accent) 80%,var(--setto-chat-ink));border-radius:25px;background:color-mix(in srgb,var(--setto-chat-paper) 82%,white);box-shadow:0 9px 26px color-mix(in srgb,var(--setto-chat-ink) 8%,transparent)}.setto-chat__composer textarea{width:100%;min-height:38px;max-height:100px;resize:none;border:0;background:transparent;color:var(--setto-chat-ink);padding:8px 0;font:15px/1.4 Archivo,sans-serif;outline:none}.setto-chat__composer textarea::placeholder{color:color-mix(in srgb,var(--setto-chat-ink) 45%,transparent)}.setto-chat__file,.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}.setto-chat__attach{width:32px;height:32px;align-self:end;border:0;border-radius:50%;background:transparent;color:color-mix(in srgb,var(--setto-chat-ink) 70%,transparent);font-size:28px;line-height:1;cursor:pointer}.setto-chat__send{width:44px;height:44px;border:0;border-radius:50%;background:color-mix(in srgb,var(--setto-chat-accent) 36%,var(--setto-chat-paper));color:var(--setto-chat-accent);font:400 27px/1 Georgia,serif;cursor:pointer}.setto-chat__send:disabled{opacity:.45;cursor:default}.setto-chat__previews{display:flex;gap:8px;padding:0 10px 8px}.setto-chat__previews span{position:relative;width:42px;height:42px}.setto-chat__previews img{display:block;width:42px;height:42px;object-fit:cover;border-radius:9px}.setto-chat__previews button{position:absolute;right:-5px;top:-5px;width:17px;height:17px;border:0;border-radius:50%;background:var(--setto-chat-ink);color:white;line-height:15px;cursor:pointer}
22379
+ .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-paper) 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(1.15);color:var(--setto-chat-ink);display:grid;place-items:center;cursor:pointer;transition:transform .18s ease,box-shadow .18s 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-paper);font-size:19px}.setto-chat__launcher img{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:3px solid color-mix(in srgb,var(--setto-chat-accent) 45%,white);outline-offset:3px}@keyframes setto-chat-pulse{50%{opacity:.35}}
22380
+ @media(max-width:700px){.setto-chat__chats{position:absolute;z-index:2;top:0;bottom:0;left:0;transform:translateX(-104%);transition:transform ${CLOSE_MS}ms ease;box-shadow:14px 0 35px color-mix(in srgb,var(--setto-chat-ink) 16%,transparent)}.setto-chat__chats--open{transform:none}.setto-chat__header{padding-left:16px;padding-right:16px}.setto-chat__disclosure{padding:13px 20px 5px}.setto-chat__log{padding:24px 20px}.setto-chat__message{max-width:91%;font-size:14px}.setto-chat__actions{padding-left:20px;padding-right:20px;gap:15px}.setto-chat__composer{padding-left:14px;padding-right:14px}.setto-chat__lead{margin-left:20px;margin-right:20px}.setto-chat__lead-grid{grid-template-columns:1fr}.setto-chat__round-button{width:42px;height:42px}.setto-chat__brand h2{font-size:24px}}@media(prefers-reduced-motion:reduce){.setto-chat,.setto-chat__shell,.setto-chat__chats,.setto-chat__launcher{transition:none!important}.setto-chat__typing{animation:none}}
22381
+ `;
22395
22382
  document.head.appendChild(style);
22396
22383
  return () => style.remove();
22397
22384
  }, []);