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

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