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) 8.15 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 { classNames as v } from "@progress/kendo-react-common"; import * as e from "react"; import { Button as N, SpeechToTextButton as fe } from "@progress/kendo-react-buttons"; import { Upload as me } from "@progress/kendo-react-upload"; import { SuggestionGroup as pe } from "./SuggestionsGroup.mjs"; import { InputSuffix as ge, TextArea as de } from "@progress/kendo-react-inputs"; import { xIcon as ke, paperPlaneIcon as he, paperclipIcon as Ce } from "@progress/kendo-svg-icons"; import { useChatContext as be } from "./ChatContext.mjs"; import { useLocalization as xe } from "@progress/kendo-react-intl"; import { getDeletedMessageText as Ee, isAuthor as Se } from "../utils.mjs"; import { closeReply as _, messages as S, newMessageSendButton as ee, attachFile as te, newMessageMessageInput as se } from "../../messages/index.mjs"; import ye from "./elements/FileBox.mjs"; const Te = e.forwardRef((ne, ae) => { const { onSendMessage: y, isDirectionRightToLeft: B, placeholder: F, MessageBox: R, suggestions: T, onSuggestionClick: oe, inputValue: d, onInputValueChange: m, suggestionTemplate: le, sendButtonConfig: D, onInputClick: L, suggestionsLayout: re, dir: ce } = ne, { replyToMessage: n, setReplyToMessage: k, user: h, messageListScrollContainerRef: f, speechToTextConfig: C, uploadConfig: a } = be(), [z] = e.useState(!1), [o, b] = e.useState(void 0), [l, M] = e.useState([]), [K, A] = e.useState(!1), p = e.useRef(null), w = e.useRef(null), r = xe(), i = e.useCallback(() => { p.current !== null && p.current.focus(); }, []); e.useEffect(() => { d !== void 0 && d !== o && (b(d), i()); }, [d, o, i]), e.useEffect(() => { n && i(); }, [n, i]), e.useLayoutEffect(() => { K && (f != null && f.current) && (f.current.scrollTop = f.current.scrollHeight - f.current.clientHeight, A(!1)); }, [K, f]); const P = e.useCallback( (t) => { const s = t.value; b(s), m && m(s); }, [m] ), g = e.useMemo( () => !!(o != null && o.trim() || a !== !1 && l.length > 0), [o, l.length, a] ), x = e.useCallback( (t) => { if (t.preventDefault(), y) { const s = o; if (g) { const c = { id: "", author: h, text: s || "", timestamp: /* @__PURE__ */ new Date(), replyToId: n ? n.id : void 0, files: l.map((u) => ({ name: u.name, size: u.size, type: u.extension, rawFile: u.getRawFile ? u.getRawFile() : u })) }; k(null), y(c, t), M([]), b(""), m && m(""), A(!0); } } }, [ y, o, h, n, l, k, m, g ] ), U = e.useCallback( (t) => { if (!(t.key === "Enter" && t.shiftKey)) switch (t.key) { case "Enter": return t.preventDefault(), x(t); case "Escape": p.current && p.current.blur(); break; default: return; } }, [x] ); e.useImperativeHandle( ae, () => ({ focusInput: i }), [i] ); const H = e.useCallback((t) => { M((s) => s.filter((c) => c.name !== t)); }, []), V = e.useCallback(() => a === !1 ? null : /* @__PURE__ */ e.createElement(ye, { files: l, onFileRemove: H, renderInTextarea: !0 }), [l, H, a]), j = e.useCallback( (t, s) => t.isDeleted ? Ee(s, r) : t.text, [r] ), G = e.useCallback(() => { if (n) { const t = Se(h, n); return /* @__PURE__ */ e.createElement( "div", { className: v( "k-message-reference", t ? "k-message-reference-sender" : "k-message-reference-receiver" ) }, /* @__PURE__ */ e.createElement("div", { className: "k-message-reference-content" }, j(n, t)), /* @__PURE__ */ e.createElement("span", { className: "k-spacer" }), /* @__PURE__ */ e.createElement( N, { fillMode: "flat", themeColor: "base", svgIcon: ke, onClick: () => k(null), "aria-label": r.toLanguageString(_, S[_]) } ) ); } return null; }, [n, h, j, k, r]), $ = e.useCallback( (t) => { try { const s = Array.from(t.newState); M((c) => [...c, ...s]), i(); } catch { } }, [i] ), q = e.useCallback(() => { w.current && a !== !1 && w.current.actionElement.click(); }, [a]), J = e.useCallback((t) => { }, []), O = e.useCallback((t) => { const { isFinal: s, alternatives: c } = t; if (c.length > 0) { const u = c[0]; s && b((E) => (E ? `${E} ` : "") + u.transcript); } }, []), Q = e.useCallback(() => /* @__PURE__ */ e.createElement(e.Fragment, null, l.length > 0 && V(), n && /* @__PURE__ */ e.createElement("span", { className: "k-input-prefix k-input-prefix-horizontal" }, G())), [l.length, n, V, G]), W = e.useCallback((t) => { t.preventDefault(); }, []), X = e.useCallback((t) => { t.key === "Escape" && t.currentTarget.blur(); }, []), I = e.useMemo( () => /* @__PURE__ */ e.createElement( N, { rounded: "full", fillMode: "solid", themeColor: "primary", className: "k-chat-send", icon: "paper-plane", svgIcon: he, onClick: x, onMouseDown: W, onKeyDown: X, "aria-label": r.toLanguageString(ee, S[ee]), dir: B ? "rtl" : void 0, disabled: !g, "aria-disabled": !g, ...D } ), [ x, W, X, r, B, g, D ] ), Y = e.useCallback(() => { const t = typeof C == "object" ? C : void 0, s = typeof a == "object" ? a : {}, { className: c, ...u } = s, E = v("k-hidden", c), ue = a !== !1, ie = C !== !1; return /* @__PURE__ */ e.createElement(ge, null, ie && /* @__PURE__ */ e.createElement( fe, { fillMode: "clear", themeColor: "base", onError: J, onResult: O, ...t } ), ue && /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement( N, { key: "attach-file", fillMode: "clear", themeColor: "base", svgIcon: Ce, onClick: q, "aria-label": r.toLanguageString(te, S[te]) } ), /* @__PURE__ */ e.createElement( me, { ref: w, files: l, onAdd: $, multiple: !0, autoUpload: !1, ...u, className: E } )), /* @__PURE__ */ e.createElement("span", { className: "k-spacer" }), I); }, [ C, a, l, J, O, q, $, r, I ]), Z = e.useMemo( () => /* @__PURE__ */ e.createElement( de, { className: v("k-message-box", "!k-flex-col", { "k-focus": z }), resizable: "none", rows: 1, placeholder: F, value: o, onChange: P, onKeyDown: U, onClick: L, prefix: Q, suffix: Y, ref: p, "aria-label": r.toLanguageString( se, S[se] ) } ), [ z, F, o, P, U, L, Q, Y, r ] ); return /* @__PURE__ */ e.createElement("div", { className: "k-message-box-wrapper" }, T && T.length > 0 && /* @__PURE__ */ e.createElement( pe, { onSuggestionClick: oe, suggestions: T, suggestionTemplate: le, suggestionsLayout: re, dir: ce } ), R ? /* @__PURE__ */ e.createElement(R, { messageInput: Z, sendButton: I }) : Z); }); Te.displayName = "NewMessage"; export { Te as default };