UNPKG

mind-link-ai-widget

Version:

A react widget for React Web and React-Native Mobile integrations.

485 lines (483 loc) 17.3 kB
import xe, { useState as x, useEffect as _, useRef as P, useCallback as z } from "react"; import ge from "dompurify"; import { marked as ye } from "marked"; import { IoSparklesSharp as we, IoHelpCircleOutline as je, IoThumbsUp as ke, IoThumbsUpOutline as ve, IoThumbsDown as Se, IoThumbsDownOutline as Ne, IoCopyOutline as Te, IoRepeatSharp as Ie, IoSendSharp as _e } from "react-icons/io5"; var J = { exports: {} }, C = {}; /** * @license React * react-jsx-runtime.production.min.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var re; function Ce() { if (re) return C; re = 1; var n = xe, s = Symbol.for("react.element"), c = Symbol.for("react.fragment"), a = Object.prototype.hasOwnProperty, u = n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, d = { key: !0, ref: !0, __self: !0, __source: !0 }; function g(w, p, y) { var m, f = {}, j = null, k = null; y !== void 0 && (j = "" + y), p.key !== void 0 && (j = "" + p.key), p.ref !== void 0 && (k = p.ref); for (m in p) a.call(p, m) && !d.hasOwnProperty(m) && (f[m] = p[m]); if (w && w.defaultProps) for (m in p = w.defaultProps, p) f[m] === void 0 && (f[m] = p[m]); return { $$typeof: s, type: w, key: j, ref: k, props: f, _owner: u.current }; } return C.Fragment = c, C.jsx = g, C.jsxs = g, C; } var ae; function Re() { return ae || (ae = 1, J.exports = Ce()), J.exports; } var t = Re(); const l = []; for (let n = 0; n < 256; ++n) l.push((n + 256).toString(16).slice(1)); function De(n, s = 0) { return (l[n[s + 0]] + l[n[s + 1]] + l[n[s + 2]] + l[n[s + 3]] + "-" + l[n[s + 4]] + l[n[s + 5]] + "-" + l[n[s + 6]] + l[n[s + 7]] + "-" + l[n[s + 8]] + l[n[s + 9]] + "-" + l[n[s + 10]] + l[n[s + 11]] + l[n[s + 12]] + l[n[s + 13]] + l[n[s + 14]] + l[n[s + 15]]).toLowerCase(); } let Z; const Ee = new Uint8Array(16); function Oe() { if (!Z) { if (typeof crypto > "u" || !crypto.getRandomValues) throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"); Z = crypto.getRandomValues.bind(crypto); } return Z(Ee); } const Ue = typeof crypto < "u" && crypto.randomUUID && crypto.randomUUID.bind(crypto), ie = { randomUUID: Ue }; function Ae(n, s, c) { if (ie.randomUUID && !n) return ie.randomUUID(); n = n || {}; const a = n.random ?? n.rng?.() ?? Oe(); if (a.length < 16) throw new Error("Random bytes length must be >= 16"); return a[6] = a[6] & 15 | 64, a[8] = a[8] & 63 | 128, De(a); } const Pe = Me(); function Me() { return Ae(); } function R() { return Math.random().toString(36).slice(2); } const B = "https://uat.api-az.carelonhealth.com/mindlink/api/v1", Fe = `${B}/auth/token`, $e = `${B}/antwell/documents/chat`, Le = `${B}/antwell/documents/chat-feedback`, G = "ai-smart-search", le = "7a3b9c2d-5e6f-4a1b-8c2d-3e9f1a7b6c4d", de = "HS256", qe = "Basic bWluZGxpbmstYW50d2VsbDo3ZTNiMmMxZC01ZjZhLTRiMWMtOGQyZS0zZjlhMWI3YzZkNGU="; function He(n) { try { const [, s] = n.split("."), c = JSON.parse(atob(s)); return typeof c.exp == "number" ? c.exp : null; } catch { return null; } } let M = null; async function ue() { const n = Math.floor(Date.now() / 1e3); if (M && n < M.exp - 20) return M.accessToken; const s = await fetch(Fe, { method: "GET", headers: { "Content-Type": "application/json", "sender-app": G, alg: de, Authorization: qe } }); if (!s.ok) { const d = await s.text().catch(() => ""); throw new Error(`Auth failed (${s.status}) ${d}`); } const c = await s.json(), a = c.access_token, u = He(a) ?? Math.floor(Date.now() / 1e3) + (c.expires_in ?? 3600); return M = { accessToken: a, exp: u }, a; } async function ze(n) { const s = await ue(), c = await fetch(Le, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${s}`, apikey: le, "sender-app": G, "x-csid": Pe }, body: JSON.stringify(n) }); if (!c.ok) throw new Error(`Feedback submission failed: ${c.statusText}`); } async function ce(n, s, c, a) { const u = await ue(), d = await fetch($e, { method: "POST", signal: a, headers: { "sender-app": G, apikey: le, "x-csid": c, alg: de, Authorization: `Bearer ${u}`, "Content-Type": "application/json" }, body: JSON.stringify({ config: n, query: s }) }); if (!d.ok) { const g = await d.text().catch(() => ""); throw new Error(`Chat failed (${d.status}) ${g}`); } return await d.json(); } const Je = ({ isOpen: n, onClose: s, onSubmit: c, rating: a }) => { const [u, d] = x(""), [g, w] = x(!1); if (!n) return null; const p = async (y) => { if (y.preventDefault(), !g) { w(!0); try { await c(u), d(""), s(); } catch (m) { console.error("Failed to submit feedback:", m); } finally { w(!1); } } }; return /* @__PURE__ */ t.jsx("div", { className: "feedback-dialog-overlay", children: /* @__PURE__ */ t.jsxs("div", { className: "feedback-dialog", children: [ /* @__PURE__ */ t.jsx("h3", { children: "Provide Additional Feedback" }), /* @__PURE__ */ t.jsx("p", { children: "Your feedback helps us improve our responses." }), /* @__PURE__ */ t.jsxs("form", { onSubmit: p, children: [ /* @__PURE__ */ t.jsxs("div", { className: "form-group", children: [ /* @__PURE__ */ t.jsx("label", { htmlFor: "comments", children: a === "like" ? "What did you like about this response?" : "How can we improve this response?" }), /* @__PURE__ */ t.jsx( "textarea", { id: "comments", value: u, onChange: (y) => d(y.target.value), rows: 4, placeholder: "Enter your feedback here...", required: !0 } ) ] }), /* @__PURE__ */ t.jsxs("div", { className: "dialog-actions", children: [ /* @__PURE__ */ t.jsx( "button", { type: "button", className: "btn-secondary", onClick: s, disabled: g, children: "Cancel" } ), /* @__PURE__ */ t.jsx( "button", { type: "submit", className: "btn-primary", disabled: g, children: g ? "Submitting..." : "Submit Feedback" } ) ] }) ] }) ] }) }); }; function Ze(n) { const s = ye.parse(n, { breaks: !0 }); return ge.sanitize(s, { ADD_ATTR: ["target", "rel"] }); } function Ke({ brand: n, stateCode: s, category: c, language: a, userQuery: u, uuid: d }) { const [, g] = x(!1), [w, p] = x(/* @__PURE__ */ new Set()), [y, m] = x(!1), [f, j] = x([]), [k, D] = x(""), [v, E] = x(!1), [V, F] = x(null), [S, $] = x({}), [T, O] = x(null), [W, L] = x(""); _(() => { if (u && f.length === 0) { const e = { id: R(), role: "user", content: u }; j([e]); } }, [u]); const [N, q] = x({ isOpen: !1, messageId: null, rating: null }), Y = 10, K = P(null), I = P(null), U = P(null), X = P(!1), pe = (e) => { const o = S[e] === "up" ? null : "up"; $((i) => ({ ...i, [e]: o })), o === "up" ? q({ isOpen: !0, messageId: e, rating: "like" }) : H(e, "like", ""); }, he = (e) => { const o = S[e] === "down" ? null : "down"; $((i) => ({ ...i, [e]: o })), o === "down" ? q({ isOpen: !0, messageId: e, rating: "dislike" }) : H(e, "dislike", ""); }, H = async (e, r, o) => { try { const i = f.findIndex((b) => b.id === e); if (i > 0) { const b = f[i], h = f[i - 1]; b && h && h.role === "user" && await ze({ message_id: e, rating: r, comments: o || `User gave a thumbs ${r}`, conversation: { query: h.content, response: b.content, context: { brand: n, state: s, category: c, language: a } } }); } } catch (i) { console.error("Failed to send feedback:", i), $((b) => ({ ...b, [e]: null })); } }, me = async (e) => { N.messageId && N.rating && await H(N.messageId, N.rating, e); }, Q = () => { K.current?.scrollIntoView({ behavior: "smooth" }); }; _(() => { Q(); }, [f, v, W]); const ee = () => ({ brand: n.toLowerCase(), state: s.toUpperCase(), category: c, language: a?.toLowerCase() }); _(() => { const e = new ResizeObserver(() => { Q(); }); return I.current && e.observe(I.current), () => { I.current && e.unobserve(I.current); }; }, []), _(() => { if (!X.current && u) { X.current = !0; const e = ee(); E(!0), ce(e, u, d).then((r) => { const o = R(), i = r?.message?.content ?? "Sorry, I couldn't parse the response.", { cleanContent: b, prompts: h } = te(i); j((A) => [ ...A, { id: o, // Use msgId here so typing effect works role: "assistant", content: b, followUpPrompts: h.length > 0 ? h : void 0 } ]), O(o); }).catch((r) => { F(r?.message || "Something went wrong."), j((o) => [ ...o, { id: R(), role: "assistant", content: "I’m having trouble reaching the service right now. Please try again." } ]), O(null); }).finally(() => { E(!1); }); } }, [n, s, c, a, u, d]); const te = (e) => { const r = /[-*] \[(.*?)\]\(#followUps\)/g, o = []; let i = e; return i = i.replace(r, (b, h) => (o.push(h), "")), i = i.replace(/\n{3,}/g, ` `).trim(), { cleanContent: i, prompts: o }; }, ne = z(async (e) => { F(null), E(!0); const r = { id: R(), role: "user", content: e }; j((o) => [...o, r]), U.current?.abort(), U.current = new AbortController(); try { const o = ee(), b = (await ce(o, e, d, U.current.signal))?.message?.content ?? "Sorry, I couldn't parse the response.", { cleanContent: h, prompts: A } = te(b), oe = { id: R(), role: "assistant", content: h, followUpPrompts: A.length > 0 ? A : void 0 }; j((be) => [...be, oe]), O(oe.id); } catch (o) { o.name !== "AbortError" && (F("Failed to get response. Please try again."), console.error("API Error:", o)); } finally { E(!1), U.current = null; } }, [n, s, c, a, d]), fe = z( (e) => { g(!0), D(e), setTimeout(() => { const r = document.querySelector( ".chat-input-wrap" ); r && r.requestSubmit(); }, 100); }, [D] ); _(() => { if (!T) return; const e = f.find((h) => h.id === T); if (!e) return; let r = 0; const o = e.content; L(""); function i() { e && (r < o.length ? (L(o.substring(0, r + 1)), r++, setTimeout(i, Y)) : (O(null), L(""), p((h) => new Set(h).add(e.id)))); } const b = setTimeout(i, Y); return () => clearTimeout(b); }, [T, f]); const se = z( (e) => { e?.preventDefault(), !(v || !k.trim()) && (g(!0), ne(k), D("")); }, [k, v, ne] ); return /* @__PURE__ */ t.jsxs("div", { className: "chat-shell", children: [ /* @__PURE__ */ t.jsx("header", { className: "chat-header", children: /* @__PURE__ */ t.jsxs("div", { className: "chat-header-title", children: [ /* @__PURE__ */ t.jsx(we, {}), /* @__PURE__ */ t.jsx("span", { className: "chat-header-title-text", children: "AI Search Assistant" }), /* @__PURE__ */ t.jsxs("div", { className: "help-button-container", children: [ /* @__PURE__ */ t.jsx( "button", { className: "help-icon-button", onClick: () => m(!y), "aria-label": "Show help information", "aria-expanded": y, children: /* @__PURE__ */ t.jsx(je, {}) } ), y && /* @__PURE__ */ t.jsx("div", { className: "help-tooltip", children: /* @__PURE__ */ t.jsx("p", { children: "The AI Search Assistant is powered by artificial intelligence. Please double-check important information." }) }) ] }) ] }) }), /* @__PURE__ */ t.jsxs("main", { className: "chat-main", children: [ /* @__PURE__ */ t.jsx( Je, { isOpen: N.isOpen, onClose: () => q({ isOpen: !1, messageId: null, rating: null }), onSubmit: me, rating: N.rating || "like" } ), /* @__PURE__ */ t.jsxs("div", { className: "messages-container", ref: I, children: [ f.map((e) => /* @__PURE__ */ t.jsx("section", { className: `message ${e.role}`, children: e.role === "assistant" ? /* @__PURE__ */ t.jsx(t.Fragment, { children: /* @__PURE__ */ t.jsxs(t.Fragment, { children: [ /* @__PURE__ */ t.jsxs("div", { className: "message-assistant", children: [ /* @__PURE__ */ t.jsx( "div", { className: "message-content", dangerouslySetInnerHTML: { __html: Ze(T === e.id ? W : e.content).replaceAll("<a ", '<a target="_blank" rel="noopener noreferrer" ') } } ), e.followUpPrompts && e.followUpPrompts.length > 0 && !T && /* @__PURE__ */ t.jsx("div", { className: "followup-prompts", children: e.followUpPrompts.map((r, o) => /* @__PURE__ */ t.jsx( "button", { type: "button", className: "followup-prompt", onClick: () => fe(r), children: r }, o )) }) ] }), w.has(e.id) && /* @__PURE__ */ t.jsx("div", { className: "message-actions-container", children: /* @__PURE__ */ t.jsxs("div", { className: "message-actions", children: [ /* @__PURE__ */ t.jsx( "button", { type: "button", className: `icon-btn ${S[e.id] === "up" ? "active" : ""}`, "aria-label": "Thumbs up", onClick: () => pe(e.id), children: S[e.id] === "up" ? /* @__PURE__ */ t.jsx(ke, { color: "#1a3673" }) : /* @__PURE__ */ t.jsx(ve, { color: "#1a3673" }) } ), /* @__PURE__ */ t.jsx( "button", { type: "button", className: `icon-btn ${S[e.id] === "down" ? "active" : ""}`, "aria-label": "Thumbs down", onClick: () => he(e.id), children: S[e.id] === "down" ? /* @__PURE__ */ t.jsx(Se, { color: "#1a3673" }) : /* @__PURE__ */ t.jsx(Ne, { color: "#1a3673" }) } ), /* @__PURE__ */ t.jsx("button", { type: "button", className: "icon-btn", "aria-label": "Copy", onClick: async () => { try { await navigator.clipboard.writeText(e.content); } catch { alert("Failed to copy text"); } }, children: /* @__PURE__ */ t.jsx(Te, { color: "#1a3673" }) }), /* @__PURE__ */ t.jsx( "button", { type: "button", className: "icon-btn", "aria-label": "Get shareable link", onClick: () => alert(`Share this response: ${e.content}`), children: /* @__PURE__ */ t.jsx(Ie, { color: "#1a3673" }) } ) ] }) }) ] }) }) : /* @__PURE__ */ t.jsx("div", { className: "message-user", style: { textAlign: "right", alignSelf: "flex-end" }, children: e.content }) }, e.id)), v && /* @__PURE__ */ t.jsx("section", { className: "message assistant", children: /* @__PURE__ */ t.jsx("div", { className: "message-assistant", children: /* @__PURE__ */ t.jsx("div", { className: "thinking-indicator", children: "Thinking..." }) }) }), V && /* @__PURE__ */ t.jsx("div", { className: "error-banner", children: V }), /* @__PURE__ */ t.jsx("div", { ref: K }) ] }) ] }), /* @__PURE__ */ t.jsxs("form", { className: "chat-input-wrap", onSubmit: se, children: [ /* @__PURE__ */ t.jsx( "input", { className: "chat-input", placeholder: "Send a message", value: k, onChange: (e) => D(e.target.value), onKeyDown: (e) => { e.key === "Enter" && !e.shiftKey && (se(), e.preventDefault()); }, disabled: v, autoFocus: !0 } ), /* @__PURE__ */ t.jsx("button", { type: "submit", className: "send-btn", disabled: !k.trim() || v, children: v ? /* @__PURE__ */ t.jsx(Be, {}) : /* @__PURE__ */ t.jsx(_e, {}) }) ] }) ] }); } function Be() { return /* @__PURE__ */ t.jsx("span", { className: "spinner", "aria-hidden": "true" }); } export { Ke as default };