UNPKG

@progress/kendo-react-conversational-ui

Version:

React Chat component allows the user to participate in chat sessions with users or chat bots. KendoReact Conversational UI components

281 lines (280 loc) 10.2 kB
/** * @license *------------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the package root for more information *------------------------------------------------------------------------------------------- */ import * as t from "react"; import { classNames as L, IconWrap as J } from "@progress/kendo-react-common"; import { Button as Q } from "@progress/kendo-react-buttons"; import { downloadIcon as be, chevronUpIcon as ge, chevronDownIcon as ke, checkCircleIcon as Ce, undoIcon as ye, copyIcon as Ee } from "@progress/kendo-svg-icons"; import { useLocalization as xe, useInternationalization as he } from "@progress/kendo-react-intl"; import { ContextMenu as Ie, MenuItem as we } from "@progress/kendo-react-layout"; import { copyToClipboard as Me, getDeletedMessageText as ve, getMessageById as Te, isAuthor as z, convertTextToLinkedContent as Z, scrollToMessageById as De } from "../utils.mjs"; import { useChatContext as Ne } from "./ChatContext.mjs"; import Ae from "./elements/FileBox.mjs"; import { downloadAllFiles as _, messages as I, downloadAll as ee, collapseMessage as te, expandMessage as ne } from "../../messages/index.mjs"; const Se = t.forwardRef( (ae, i) => { const { item: n, template: u, contentTemplate: w, onRequestSelection: m, tabIndex: se = -1, dateFormat: K = "g", selected: l, allowMessageCollapse: d, messageToolbarActions: f, messageContextMenuActions: p, fileActions: B, isSender: b } = ae, { setReplyToMessage: g, messages: P, user: c, internalScrollContainerRef: k, onToolbarAction: M, onContextMenuAction: v, onFileAction: W, onDownload: r, setShowCopyNotification: C, statusTemplate: T, timestampVisibility: D = "onFocus" } = Ne(), [H, N] = t.useState(!1), [O, A] = t.useState(!1), [y, le] = t.useState(!0), [oe, U] = t.useState(!1), o = xe(), q = he(), X = t.useRef({ left: 0, top: 0 }), E = t.useCallback(() => { n != null && n.text && Me(n.text), C && (C(!0), setTimeout(() => { C(!1); }, 3e3)); }, [n, C]), S = t.useCallback((e) => { e.stopPropagation(), le((a) => !a); }, []), re = t.useCallback(() => { U(!1); }, []), ce = t.useCallback((e) => { (e.key === "Tab" || e.key === "Enter" || e.key === " " || e.key === "ArrowUp" || e.key === "ArrowDown") && U(!0); }, []), F = t.useRef(null); t.useEffect(() => { l && F.current && F.current.focus(); }, [l]); const R = t.useCallback((e) => { X.current = { left: e.pageX, top: e.pageY }, e.preventDefault(), A(!0), N(!0); }, []), Y = t.useCallback(() => { A(!1), N(!1); }, []), x = t.useCallback( (e, a) => e.isDeleted ? ve(a, o) : e.text ? e.text : "", [o] ), $ = t.useCallback( (e) => { var a; (a = e.item) != null && a.text && (e.item.data.id === "copy" && !n.isDeleted ? E() : e.item.data.id === "reply" && g(n), v && v(e.item.data, e, n)), A(!1), N(!1); }, [n, v, g, E] ), j = t.useCallback(() => { if (n.replyToId) { const e = Te(P, n.replyToId); if (e) { const a = (s) => { s.preventDefault(), s.stopPropagation(); const h = k == null ? void 0 : k.current; De(e.id, h); }; return /* @__PURE__ */ t.createElement( "div", { className: L( "k-message-reference", z(c, e) ? "k-message-reference-sender" : "k-message-reference-receiver" ), onClick: a }, /* @__PURE__ */ t.createElement("div", { className: "k-message-reference-content" }, Z( x(e, z(c, e)) )) ); } } return null; }, [n.replyToId, P, c, x, k]), V = t.useCallback(() => { const e = [ { id: "reply", label: "Reply", svgIcon: ye }, { id: "copy", label: "Copy", svgIcon: Ee } ], a = p ? [ ...e.map((s) => p.find( (pe) => pe.id === s.id ) || s), ...p.filter( (s) => !e.some((h) => h.id === s.id) ) ] : e; return /* @__PURE__ */ t.createElement( Ie, { onSelect: $, onClose: Y, vertical: !0, show: H, offset: X.current }, a.map( (s) => s.id === "delete" && !b ? null : /* @__PURE__ */ t.createElement( we, { key: s.id, text: s.label, icon: s.icon, svgIcon: s.svgIcon, data: s } ) ) ); }, [p, $, Y, H, b]), G = t.useCallback(() => n.files && n.files.length > 1 ? /* @__PURE__ */ t.createElement("div", { className: "k-chat-download-button-wrapper" }, /* @__PURE__ */ t.createElement( Q, { className: "k-chat-download-button", fillMode: "flat", themeColor: "base", svgIcon: be, onClick: (e) => { e.stopPropagation(), r && r(n.files, n); }, "aria-label": o.toLanguageString( _, I[_] ) }, /* @__PURE__ */ t.createElement("span", { className: "k-button-text" }, o.toLanguageString(ee, I[ee])) )) : null, [n, r, o]), ie = t.useCallback(() => { if (n.typing) return /* @__PURE__ */ t.createElement("div", { className: "k-chat-bubble k-bubble" }, /* @__PURE__ */ t.createElement("div", { className: "k-bubble-content" }, /* @__PURE__ */ t.createElement("div", { className: "k-typing-indicator" }, /* @__PURE__ */ t.createElement("span", null), /* @__PURE__ */ t.createElement("span", null), /* @__PURE__ */ t.createElement("span", null)))); if (u) { const e = u; return /* @__PURE__ */ t.createElement(t.Fragment, null, /* @__PURE__ */ t.createElement(e, { item: n }), V()); } else if (n.text || n.files && n.files.length > 0) return /* @__PURE__ */ t.createElement(t.Fragment, null, /* @__PURE__ */ t.createElement( "div", { className: L("k-chat-bubble", "k-bubble", { "k-bubble-expandable": d, "k-expanded": d && y, "k-selected": l, "k-active": O }), onClick: () => m(l ? void 0 : n.selectionIndex), onContextMenu: R }, /* @__PURE__ */ t.createElement("div", { className: "k-bubble-content" }, w ? /* @__PURE__ */ t.createElement(w, { item: n }) : /* @__PURE__ */ t.createElement(t.Fragment, null, (n.text || n.isDeleted || n.replyToId) && /* @__PURE__ */ t.createElement("span", { className: "k-chat-bubble-text" }, !n.isDeleted && j(), Z( x(n, z(c, n)) )), n.files && n.files.length > 0 && /* @__PURE__ */ t.createElement( Ae, { files: n.files, message: n, onFileAction: W, onDownload: (e) => r == null ? void 0 : r(e, n), renderInTextarea: !1, fileActions: B } ), G())), d && /* @__PURE__ */ t.createElement( "span", { className: "k-bubble-expandable-indicator", onClick: S, role: "button", "aria-label": y ? o.toLanguageString( te, I[te] ) : o.toLanguageString( ne, I[ne] ), tabIndex: 0, onKeyDown: (e) => { (e.key === "Enter" || e.key === " ") && (e.preventDefault(), S(e)); } }, /* @__PURE__ */ t.createElement(J, { icon: y ? ge : ke, size: "medium" }) ) ), V()); return null; }, [ n, u, w, d, y, l, O, m, R, j, x, c, W, B, G, S, o, V, r ]), ue = t.useCallback(() => { let e = null; if (n.timestamp) { const a = D === "onFocus" && l; D === "hidden" || (e = /* @__PURE__ */ t.createElement("time", { className: "k-message-time", "aria-hidden": !a }, q.formatDate(n.timestamp, K))); } return e; }, [n.timestamp, l, K, D, q]), me = t.useCallback(() => { let e = null; return n.status && l && (T ? e = t.createElement(T, { item: n }) : e = /* @__PURE__ */ t.createElement("span", { className: "k-message-status" }, /* @__PURE__ */ t.createElement(J, { icon: Ce, size: "xsmall" }), n.status)), e; }, [n, l, T]), de = t.useCallback(() => L({ "k-focus": l, "k-message-removed": n.isDeleted }, "k-message"), [l, n.isDeleted]), fe = t.useCallback(() => f && f.length > 0 && /* @__PURE__ */ t.createElement("div", { className: "k-chat-message-toolbar k-toolbar k-toolbar-md k-toolbar-flat" }, f.map((e) => e.id === "delete" && !b ? null : /* @__PURE__ */ t.createElement( Q, { key: e.id, fillMode: "flat", className: "k-toolbar-button", themeColor: "base", icon: e.icon, svgIcon: e.svgIcon, onClick: (a) => { a.stopPropagation(), e.id === "copy" && !n.isDeleted ? E() : e.id === "reply" && g(n), M && M(e, a, n); }, onMouseDown: (a) => a.preventDefault() } ))), [E, b, n, f, M, g]); return /* @__PURE__ */ t.createElement( "div", { "data-message-id": n.id, className: de(), tabIndex: se, onMouseDown: re, onKeyDown: ce, onFocus: (e) => { oe && m(n.selectionIndex); }, ref: (e) => { F.current = e, typeof i == "function" ? i(e) : i && (i.current = e); }, onContextMenu: u ? R : void 0 }, ue(), ie(), me(), fe() ); } ); Se.displayName = "ChatMessage"; export { Se as ChatMessage };