mind-link-ai-widget
Version:
A react widget for React Web and React-Native Mobile integrations.
485 lines (483 loc) • 17.3 kB
JavaScript
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
};