UNPKG

whisper-design

Version:

A React component library for building AI chat room UIs.

706 lines (705 loc) 24 kB
import O, { useState as y, useRef as Y, useEffect as ee, useMemo as de } from "react"; import { v4 as se } from "uuid"; import fe from "dayjs"; var C = { exports: {} }, k = {}; /** * @license React * react-jsx-runtime.production.js * * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var J; function me() { if (J) return k; J = 1; var t = Symbol.for("react.transitional.element"), a = Symbol.for("react.fragment"); function o(i, r, c) { var l = null; if (c !== void 0 && (l = "" + c), r.key !== void 0 && (l = "" + r.key), "key" in r) { c = {}; for (var u in r) u !== "key" && (c[u] = r[u]); } else c = r; return r = c.ref, { $$typeof: t, type: i, key: l, ref: r !== void 0 ? r : null, props: c }; } return k.Fragment = a, k.jsx = o, k.jsxs = o, k; } var S = {}; /** * @license React * react-jsx-runtime.development.js * * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var q; function pe() { return q || (q = 1, process.env.NODE_ENV !== "production" && function() { function t(e) { if (e == null) return null; if (typeof e == "function") return e.$$typeof === le ? null : e.displayName || e.name || null; if (typeof e == "string") return e; switch (e) { case v: return "Fragment"; case N: return "Profiler"; case T: return "StrictMode"; case re: return "Suspense"; case ne: return "SuspenseList"; case ce: return "Activity"; } if (typeof e == "object") switch (typeof e.tag == "number" && console.error( "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue." ), e.$$typeof) { case j: return "Portal"; case W: return (e.displayName || "Context") + ".Provider"; case P: return (e._context.displayName || "Context") + ".Consumer"; case b: var n = e.render; return e = e.displayName, e || (e = n.displayName || n.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e; case ie: return n = e.displayName || null, n !== null ? n : t(e.type) || "Memo"; case D: n = e._payload, e = e._init; try { return t(e(n)); } catch { } } return null; } function a(e) { return "" + e; } function o(e) { try { a(e); var n = !1; } catch { n = !0; } if (n) { n = console; var m = n.error, p = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object"; return m.call( n, "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.", p ), a(e); } } function i(e) { if (e === v) return "<>"; if (typeof e == "object" && e !== null && e.$$typeof === D) return "<...>"; try { var n = t(e); return n ? "<" + n + ">" : "<...>"; } catch { return "<...>"; } } function r() { var e = M.A; return e === null ? null : e.getOwner(); } function c() { return Error("react-stack-top-frame"); } function l(e) { if (U.call(e, "key")) { var n = Object.getOwnPropertyDescriptor(e, "key").get; if (n && n.isReactWarning) return !1; } return e.key !== void 0; } function u(e, n) { function m() { B || (B = !0, console.error( "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)", n )); } m.isReactWarning = !0, Object.defineProperty(e, "key", { get: m, configurable: !0 }); } function x() { var e = t(this.type); return G[e] || (G[e] = !0, console.error( "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release." )), e = this.props.ref, e !== void 0 ? e : null; } function g(e, n, m, p, R, E, I, L) { return m = E.ref, e = { $$typeof: f, type: e, key: n, props: E, _owner: R }, (m !== void 0 ? m : null) !== null ? Object.defineProperty(e, "ref", { enumerable: !1, get: x }) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", { configurable: !1, enumerable: !1, writable: !0, value: 0 }), Object.defineProperty(e, "_debugInfo", { configurable: !1, enumerable: !1, writable: !0, value: null }), Object.defineProperty(e, "_debugStack", { configurable: !1, enumerable: !1, writable: !0, value: I }), Object.defineProperty(e, "_debugTask", { configurable: !1, enumerable: !1, writable: !0, value: L }), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e; } function _(e, n, m, p, R, E, I, L) { var h = n.children; if (h !== void 0) if (p) if (oe(h)) { for (p = 0; p < h.length; p++) w(h[p]); Object.freeze && Object.freeze(h); } else console.error( "React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead." ); else w(h); if (U.call(n, "key")) { h = t(e); var A = Object.keys(n).filter(function(ue) { return ue !== "key"; }); p = 0 < A.length ? "{key: someKey, " + A.join(": ..., ") + ": ...}" : "{key: someKey}", H[h + p] || (A = 0 < A.length ? "{" + A.join(": ..., ") + ": ...}" : "{}", console.error( `A props object containing a "key" prop is being spread into JSX: let props = %s; <%s {...props} /> React keys must be passed directly to JSX without using spread: let props = %s; <%s key={someKey} {...props} />`, p, h, A, h ), H[h + p] = !0); } if (h = null, m !== void 0 && (o(m), h = "" + m), l(n) && (o(n.key), h = "" + n.key), "key" in n) { m = {}; for (var $ in n) $ !== "key" && (m[$] = n[$]); } else m = n; return h && u( m, typeof e == "function" ? e.displayName || e.name || "Unknown" : e ), g( e, h, E, R, r(), m, I, L ); } function w(e) { typeof e == "object" && e !== null && e.$$typeof === f && e._store && (e._store.validated = 1); } var d = O, f = Symbol.for("react.transitional.element"), j = Symbol.for("react.portal"), v = Symbol.for("react.fragment"), T = Symbol.for("react.strict_mode"), N = Symbol.for("react.profiler"), P = Symbol.for("react.consumer"), W = Symbol.for("react.context"), b = Symbol.for("react.forward_ref"), re = Symbol.for("react.suspense"), ne = Symbol.for("react.suspense_list"), ie = Symbol.for("react.memo"), D = Symbol.for("react.lazy"), ce = Symbol.for("react.activity"), le = Symbol.for("react.client.reference"), M = d.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, U = Object.prototype.hasOwnProperty, oe = Array.isArray, F = console.createTask ? console.createTask : function() { return null; }; d = { "react-stack-bottom-frame": function(e) { return e(); } }; var B, G = {}, V = d["react-stack-bottom-frame"].bind( d, c )(), X = F(i(c)), H = {}; S.Fragment = v, S.jsx = function(e, n, m, p, R) { var E = 1e4 > M.recentlyCreatedOwnerStacks++; return _( e, n, m, !1, p, R, E ? Error("react-stack-top-frame") : V, E ? F(i(e)) : X ); }, S.jsxs = function(e, n, m, p, R) { var E = 1e4 > M.recentlyCreatedOwnerStacks++; return _( e, n, m, !0, p, R, E ? Error("react-stack-top-frame") : V, E ? F(i(e)) : X ); }; }()), S; } var Q; function he() { return Q || (Q = 1, process.env.NODE_ENV === "production" ? C.exports = me() : C.exports = pe()), C.exports; } var s = he(); const xe = O.memo( ({ src: t, alt: a, size: o = "medium", shape: i = "circle", gradient: r, children: c }) => { const l = r ? `avatar--${r}-gradient` : ""; return /* @__PURE__ */ s.jsx( "div", { className: `avatar avatar--${o} avatar--${i} ${l}`, role: "img", "aria-label": a, children: t ? /* @__PURE__ */ s.jsx("img", { src: t, alt: a }) : c } ); } ), z = ({ onClick: t, children: a, variant: o = "primary", disabled: i = !1, type: r = "button", isCircle: c = !1, className: l = "" }) => { const x = `button ${c ? "button--circle" : `button--${o}`} ${l}`.trim(); return /* @__PURE__ */ s.jsx("button", { type: r, className: x, onClick: t, disabled: i, children: a }); }, te = (t) => { switch (t.split(".").pop()?.toLowerCase()) { case "pdf": return "fas fa-file-pdf"; case "xlsx": case "xls": return "fas fa-file-excel"; case "docx": case "doc": return "fas fa-file-word"; case "pptx": case "ppt": return "fas fa-file-powerpoint"; case "jpg": case "jpeg": case "png": case "gif": case "bmp": case "svg": case "webp": return "fas fa-file-image"; case "mp4": case "avi": case "mov": case "wmv": case "flv": return "fas fa-file-video"; case "mp3": case "wav": case "flac": case "aac": return "fas fa-file-audio"; case "zip": case "rar": case "7z": case "tar": case "gz": return "fas fa-file-archive"; case "txt": case "md": return "fas fa-file-alt"; case "js": case "ts": case "jsx": case "tsx": case "html": case "css": case "scss": case "json": case "xml": case "py": case "java": case "cpp": case "c": return "fas fa-file-code"; default: return "fas fa-file"; } }, ae = (t) => { if (t === 0) return "0 B"; const a = 1024, o = ["B", "KB", "MB", "GB"], i = Math.floor(Math.log(t) / Math.log(a)); return `${parseFloat((t / Math.pow(a, i)).toFixed(2))} ${o[i]}`; }, ve = O.memo( ({ id: t, content: a, sender: o, type: i = "text", isLoading: r, files: c }) => { const l = () => { switch (i) { case "image": return /* @__PURE__ */ s.jsx("img", { src: a, alt: "" }); case "code": return /* @__PURE__ */ s.jsx("pre", { children: /* @__PURE__ */ s.jsx("code", { children: a }) }); case "text": default: return /* @__PURE__ */ s.jsx("p", { children: a }); } }; return /* @__PURE__ */ s.jsxs("div", { className: `message-bubble ${o} message-bubble--${i}`, children: [ l(), c && c.length > 0 && /* @__PURE__ */ s.jsx("div", { className: "file-attachments", children: c.map((u) => /* @__PURE__ */ s.jsxs("div", { className: "file-attachment", children: [ /* @__PURE__ */ s.jsx("div", { className: "file-icon", children: /* @__PURE__ */ s.jsx("i", { className: te(u.name) }) }), /* @__PURE__ */ s.jsxs("div", { className: "file-info", children: [ /* @__PURE__ */ s.jsx("div", { className: "file-name", children: u.name }), /* @__PURE__ */ s.jsx("div", { className: "file-size", children: ae(u.size) }) ] }) ] }, u.id)) }), r && /* @__PURE__ */ s.jsx("span", { className: "loading-indicator", children: "..." }), " " ] }, t); } ), je = ({ onSendMessage: t, isSending: a = !1, placeholder: o = "输入消息...", onFilesChange: i }) => { const [r, c] = y(""), [l, u] = y([]), x = Y(null), g = (d) => { u(d), i && i(d); }, _ = (d) => { d.preventDefault(), (r.trim() || l.length > 0) && (t(r.trim(), l), c(""), u([])); }, w = (d) => { d.key === "Enter" && !d.shiftKey && (d.preventDefault(), _(d)); }; return /* @__PURE__ */ s.jsx("form", { className: "chat-input-area", onSubmit: _, children: /* @__PURE__ */ s.jsxs("div", { className: "input-container", children: [ /* @__PURE__ */ s.jsx("div", { className: "input-toolbar", children: /* @__PURE__ */ s.jsx(z, { isCircle: !0, onClick: () => x.current?.click(), disabled: a, children: /* @__PURE__ */ s.jsx("i", { className: "fas fa-paperclip" }) }) }), /* @__PURE__ */ s.jsx( "input", { type: "file", ref: x, multiple: !0, accept: "image/*,.pdf,.xlsx,.docx", onChange: (d) => { const j = Array.from(d.target.files || []).map( (v) => Object.assign(v, { id: se() }) ); g([...l, ...j]), d.target.value = ""; }, style: { display: "none" } } ), l.length > 0 && /* @__PURE__ */ s.jsxs("div", { className: "file-list", children: [ " ", l.map((d) => /* @__PURE__ */ s.jsxs("div", { className: "file-card", children: [ /* @__PURE__ */ s.jsx("span", { className: "file-icon", children: /* @__PURE__ */ s.jsx("i", { className: te(d.name) }) }), /* @__PURE__ */ s.jsxs("div", { className: "file-info", children: [ /* @__PURE__ */ s.jsx("span", { className: "file-name", children: d.name }), /* @__PURE__ */ s.jsx("span", { className: "file-size", children: ae(d.size) }) ] }), /* @__PURE__ */ s.jsx( "button", { type: "button", className: "file-remove-button", onClick: () => g(l.filter((f) => f.id !== d.id)), disabled: a, children: "×" } ) ] }, d.id)) ] }), /* @__PURE__ */ s.jsx( "textarea", { className: "message-input", placeholder: o, value: r, onChange: (d) => c(d.target.value), onKeyDown: w, "aria-label": "消息输入框", disabled: a } ), /* @__PURE__ */ s.jsxs("div", { className: "input-actions", children: [ /* @__PURE__ */ s.jsxs("div", { className: "input-hints", children: [ /* @__PURE__ */ s.jsx("i", { className: "fas fa-lightbulb" }), " 按 Enter 发送,Shift + Enter 换行" ] }), /* @__PURE__ */ s.jsxs(z, { type: "submit", variant: "primary", disabled: a, onClick: () => { }, children: [ /* @__PURE__ */ s.jsx("i", { className: "fas fa-paper-plane" }), "发送" ] }) ] }) ] }) }); }, we = ({ onFilesChange: t, acceptedFileTypes: a = "image/*,.pdf,.xlsx,.docx", maxFiles: o = 5, value: i, // Controlled component value disabled: r = !1 }) => { const c = Y(null), [l, u] = y(i || []); ee(() => { i !== void 0 && u(i); }, [i]); const x = (f) => { if (r) return; const j = Array.from(f.target.files || []), v = []; j.forEach((N) => { if (l.length + v.length < o) { const P = "." + N.name.split(".").pop()?.toLowerCase(); if (a.split(",").some((b) => (b = b.trim().toLowerCase(), b.startsWith(".") ? P === b : b.endsWith("/*") ? N.type.startsWith(b.slice(0, -1)) : N.type === b))) { const b = { ...N, id: se(), ...N.type.startsWith("image/") && { preview: URL.createObjectURL(N) } }; v.push(b); } else console.warn(`File type not accepted: ${N.name} (${N.type})`); } else console.warn(`Max files reached (${o}). Skipping ${N.name}.`); }); const T = [...l, ...v]; u(T), t?.(T), c.current && (c.current.value = ""); }, g = (f) => { if (r) return; const j = l.filter((v) => v.id !== f); u(j), t?.(j); }, _ = (f) => f.type.startsWith("image/") ? "🖼️" : f.type === "application/pdf" ? "📄" : f.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || f.name.endsWith(".xlsx") ? "📊" : "📎", w = (f) => { if (f === 0) return "0 Bytes"; const j = 1024, v = ["Bytes", "KB", "MB", "GB", "TB"], T = Math.floor(Math.log(f) / Math.log(j)); return parseFloat((f / Math.pow(j, T)).toFixed(2)) + " " + v[T]; }, d = l.length >= o; return /* @__PURE__ */ s.jsxs("div", { className: "file-upload-container", children: [ /* @__PURE__ */ s.jsx( "input", { type: "file", ref: c, multiple: !0, accept: a, onChange: x, disabled: r || d, style: { display: "none" } } ), /* @__PURE__ */ s.jsx( "button", { type: "button", className: "file-upload-button", onClick: () => c.current?.click(), disabled: r || d, children: d ? "Max files reached" : "Select Files" } ), l.length > 0 && /* @__PURE__ */ s.jsx("div", { className: "file-list", children: l.map((f) => /* @__PURE__ */ s.jsxs("div", { className: "file-card", children: [ /* @__PURE__ */ s.jsx("span", { className: "file-icon", children: _(f) }), f.preview && /* @__PURE__ */ s.jsx("img", { src: f.preview, alt: "preview", className: "file-preview" }), /* @__PURE__ */ s.jsxs("div", { className: "file-info", children: [ /* @__PURE__ */ s.jsx("span", { className: "file-name", children: f.name }), /* @__PURE__ */ s.jsx("span", { className: "file-size", children: w(f.size) }) ] }), /* @__PURE__ */ s.jsx( "button", { type: "button", className: "file-remove-button", onClick: () => g(f.id), disabled: r, children: "×" } ) ] }, f.id)) }) ] }); }, Ne = O.memo(({ message: t, config: a }) => { const o = t.sender === "user", i = o ? a?.userAvatar : a?.aiAvatar, r = o ? "User Avatar" : "AI Avatar", c = o ? "primary" : "secondary", l = (u) => new Date(u).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); return /* @__PURE__ */ s.jsxs("div", { className: `message-row ${o ? "user" : ""}`, children: [ /* @__PURE__ */ s.jsx( xe, { ...i && { src: i }, alt: r, size: "medium", gradient: c, children: !i && /* @__PURE__ */ s.jsx("i", { className: `fas fa-${o ? "user" : "robot"}` }) } ), /* @__PURE__ */ s.jsxs("div", { className: "message-content", children: [ /* @__PURE__ */ s.jsx( ve, { id: t.id, content: t.content, sender: t.sender, type: t.type || "text", ...t.isLoading !== void 0 && { isLoading: t.isLoading }, ...t.files && { files: t.files } } ), /* @__PURE__ */ s.jsx("div", { className: "message-time", children: l(t.timestamp) }) ] }) ] }); }), be = ({ messages: t, config: a, isLoadingMore: o }) => { const i = Y(null), r = () => { i.current?.scrollIntoView({ behavior: "smooth" }); }; return ee(() => { r(); }, [t]), /* @__PURE__ */ s.jsxs("div", { className: "chat-messages", children: [ " ", t.map((c) => /* @__PURE__ */ s.jsx(Ne, { message: c, ...a && { config: a } }, c.id)), o && /* @__PURE__ */ s.jsx("div", { className: "loading-more", children: "Loading more messages..." }), /* @__PURE__ */ s.jsx("div", { ref: i }), " " ] }); }, ge = (t, a = "HH:mm") => fe(t).format(a), K = (t, a) => t.length <= a ? t : t.substring(0, a) + "...", Z = { CONVERSATION_TITLE_MAX_LENGTH: 30, MESSAGE_PREVIEW_MAX_LENGTH: 50 }, Ee = ({ conversation: t, isActive: a, onClick: o // onDelete, // Removed }) => { const i = t.messages.length > 0 && t.messages[0] ? K(t.messages[0].content, Z.CONVERSATION_TITLE_MAX_LENGTH) : t.title, r = t.messages[t.messages.length - 1], c = t.messages.length > 0 && r ? K(r.content, Z.MESSAGE_PREVIEW_MAX_LENGTH) : "No messages yet.", l = ge(t.lastUpdated); return /* @__PURE__ */ s.jsx( "div", { className: `conversation-item ${a ? "active" : ""}`, onClick: () => o(t.id), children: /* @__PURE__ */ s.jsxs("div", { className: "conversation-info", children: [ /* @__PURE__ */ s.jsxs("div", { className: "conversation-title", children: [ /* @__PURE__ */ s.jsx("span", { children: i }), /* @__PURE__ */ s.jsx("span", { className: "conversation-time", children: l }) ] }), /* @__PURE__ */ s.jsx("div", { className: "conversation-preview", children: c }) ] }) } ); }, Ae = ({ messages: t, onSendMessage: a, isAITyping: o = !1, config: i, conversationTitle: r }) => { const [c, l] = y(!1), u = async (x, g) => { l(!0), await a(x, g), l(!1); }; return /* @__PURE__ */ s.jsxs("div", { className: "chat-container", children: [ " ", /* @__PURE__ */ s.jsx("div", { className: "chat-header", children: /* @__PURE__ */ s.jsx("div", { className: "chat-title", children: r || "新对话" }) }), /* @__PURE__ */ s.jsx(be, { messages: t, ...i && { config: i } }), /* @__PURE__ */ s.jsx( je, { onSendMessage: u, isSending: c || o, placeholder: "输入消息..." } ) ] }); }, ke = ({ conversations: t, activeConversationId: a, onSelectConversation: o, onNewConversation: i }) => { const [r, c] = y(""), l = de(() => { if (!r) return t; const u = r.toLowerCase(); return t.filter( (x) => x.title.toLowerCase().includes(u) || x.messages.some((g) => g.content.toLowerCase().includes(u)) ); }, [t, r]); return /* @__PURE__ */ s.jsxs("div", { className: "conversations-panel", children: [ " ", /* @__PURE__ */ s.jsxs("div", { className: "panel-header", children: [ " ", /* @__PURE__ */ s.jsxs("div", { className: "panel-title", children: [ /* @__PURE__ */ s.jsx("i", { className: "fas fa-comments" }), "对话列表", /* @__PURE__ */ s.jsx(z, { isCircle: !0, onClick: i, className: "new-conversation-btn", children: /* @__PURE__ */ s.jsx("i", { className: "fas fa-plus" }) }) ] }), /* @__PURE__ */ s.jsxs("div", { className: "search-container", children: [ /* @__PURE__ */ s.jsx("i", { className: "fas fa-search search-icon" }), /* @__PURE__ */ s.jsx( "input", { type: "text", className: "search-input", placeholder: "搜索对话...", value: r, onChange: (u) => c(u.target.value) } ) ] }) ] }), /* @__PURE__ */ s.jsxs("div", { className: "conversation-list", children: [ " ", l.length > 0 ? l.map((u) => /* @__PURE__ */ s.jsx( Ee, { conversation: u, isActive: u.id === a, onClick: o }, u.id )) : /* @__PURE__ */ s.jsx("div", { className: "conversation-list__empty", children: "No conversations found." }) ] }) ] }); }; export { Ae as AIChatRoom, xe as Avatar, z as Button, je as ChatInputArea, Ne as ChatMessage, be as ChatMessagesList, Ee as ConversationItem, ke as ConversationList, we as FileUpload, ve as MessageBubble }; //# sourceMappingURL=ai-chat-room-components.mjs.map