UNPKG

@klever-one/web-sdk

Version:

Web SDK for integrating real-time room management and streaming functionality into web applications

674 lines (673 loc) 21.9 kB
"use client"; import * as R from "react"; import O, { useRef as T, useState as j, useEffect as H, useCallback as g, useLayoutEffect as oe, useMemo as P } from "react"; import { K as se, _ as ie, a as ae } from "../index-DncH3OIr.js"; import '../react-lib.css';let W = 0; function ce(n) { const { apiKey: t, container: s, callbacks: i, conversation: a, ...c } = n, r = T(i); r.current = i; const e = T(null), [f, u] = j({ connection: "disconnected", recording: "idle", isStreaming: !1, isStreamingReady: !1, isConversationReady: !1, lastUpdated: /* @__PURE__ */ new Date() }), [h, p] = j({ reconnectAttempts: 0, messagesSent: 0, messagesReceived: 0, recordingSessions: 0, errorCount: 0 }), [m, S] = j({ messages: [], isStreaming: !1, isCompleted: !1 }), [L, b] = j([]); H(() => { try { const l = new se({ apiKey: t, container: s, conversation: a, ...c, callbacks: { ...r.current, // 기존 콜백과 상태 업데이트를 결합 onConnectionStatusChange: (d) => { u((k) => ({ ...k, connection: d })), r.current?.onConnectionStatusChange?.(d); }, onRecordingStatusChange: (d) => { u((k) => ({ ...k, recording: d })), r.current?.onRecordingStatusChange?.(d); }, onStreamingStatusChange: (d) => { u((k) => ({ ...k, isStreaming: d })), r.current?.onStreamingStatusChange?.(d); }, onMessageReceived: (d) => { b((k) => { const B = k.findIndex((N) => N.id === d.id); if (B >= 0) { const N = [...k]; return N[B] = d, N; } else return [...k, d]; }), r.current?.onMessageReceived?.(d); }, onReady: () => { u((d) => ({ ...d, isStreamingReady: !0, isConversationReady: !0 })), r.current?.onReady?.(); }, onDisconnect: () => { u({ connection: "disconnected", recording: "idle", isStreaming: !1, isStreamingReady: !1, isConversationReady: !1, lastUpdated: /* @__PURE__ */ new Date() }), b([]), r.current?.onDisconnect?.(); }, onError: (d) => { p((k) => ({ ...k, errorCount: k.errorCount + 1, lastErrorTime: /* @__PURE__ */ new Date() })), r.current?.onError?.(d); }, onRoomInfoUpdated: (d) => { r.current?.onRoomInfoUpdated?.(d); }, onLog: (d) => { r.current?.onLog?.(d); } } }); e.current = l, W++, u(l.getState()), p(l.getMetrics()), S(l.getConversationState()), b(l.getMessages()); } catch { } return () => { W--, W === 0 && e.current && e.current.getState().connection !== "disconnected" && e.current.disconnect(); }; }, [t, s]), H(() => { if (!e.current) return; const d = setInterval(() => { e.current && e.current.getState().connection !== "disconnected" && (u(e.current.getState()), p(e.current.getMetrics()), S(e.current.getConversationState()), b(e.current.getMessages())); }, 1e3); return () => clearInterval(d); }, []); const y = g(async () => { if (e.current) return e.current.connect(); throw new Error("KleverOneClient not initialized"); }, []), A = g(() => { e.current && e.current.disconnect(); }, []), M = g(async () => { if (e.current) return e.current.reconnect(); throw new Error("KleverOneClient not initialized"); }, []), v = g(async (l) => { if (e.current) return e.current.sendText(l); throw new Error("KleverOneClient not initialized"); }, []), z = g(async () => { if (e.current) return e.current.startRecording(); throw new Error("KleverOneClient not initialized"); }, []), I = g(async () => { if (e.current) return e.current.stopRecording(); throw new Error("KleverOneClient not initialized"); }, []), w = g(() => e.current ? e.current.isReady() : !1, []), F = g(() => e.current ? e.current.isRecording() : !1, []), V = g((l, d) => { e.current && e.current.resize(l, d); }, []), X = g((l) => { e.current && e.current.sendAvatarNum(l); }, []), Z = g((l) => { e.current && e.current.sendAvatarAppearanceChange(l); }, []), Q = g((l) => { e.current && e.current.sendPlaceBackgroundChange(l); }, []), ee = g((l) => { e.current && e.current.sendVoiceSetting(l); }, []), ne = g( (l) => { e.current && e.current.sendMessage(l); }, [] ), te = g( async (l) => { if (e.current) return e.current.sendDataInChunks(l); throw new Error("KleverOneClient not initialized"); }, [] ), re = g( ((l) => { e.current && (Array.isArray(l), e.current.speak(l)); }), [] ); return { client: e.current, state: f, metrics: h, conversationState: m, messages: L, connect: y, disconnect: A, reconnect: M, sendText: v, speak: re, startRecording: z, stopRecording: I, isReady: w, isRecording: F, // 고급 기능들 resize: V, sendAvatarNum: X, sendAvatarAppearanceChange: Z, sendPlaceBackgroundChange: Q, sendVoiceSetting: ee, sendMessage: ne, sendDataInChunks: te }; } var $ = { exports: {} }, E = {}; var D; function le() { if (D) return E; D = 1; var n = Symbol.for("react.transitional.element"), t = Symbol.for("react.fragment"); function s(i, a, c) { var r = null; if (c !== void 0 && (r = "" + c), a.key !== void 0 && (r = "" + a.key), "key" in a) { c = {}; for (var e in a) e !== "key" && (c[e] = a[e]); } else c = a; return a = c.ref, { $$typeof: n, type: i, key: r, ref: a !== void 0 ? a : null, props: c }; } return E.Fragment = t, E.jsx = s, E.jsxs = s, E; } var U; function de() { return U || (U = 1, $.exports = /* @__PURE__ */ le()), $.exports; } var o = /* @__PURE__ */ de(); const C = ({ size: n = 24, color: t = "currentColor", stroke: s = 2, className: i = "", style: a, children: c, name: r }) => /* @__PURE__ */ o.jsx( "svg", { width: n, height: n, viewBox: "0 0 24 24", fill: "none", stroke: t, strokeWidth: s, strokeLinecap: "round", strokeLinejoin: "round", className: i, style: a, role: "img", "aria-label": r, children: c } ), ue = (n) => /* @__PURE__ */ o.jsxs( C, { name: "Send", ...n, children: [ /* @__PURE__ */ o.jsx("path", { d: "M10 14l11 -11" }), /* @__PURE__ */ o.jsx("path", { d: "M21 3l-6.5 18a.55 .55 0 0 1 -1 0l-3.5 -7l-7 -3.5a.55 .55 0 0 1 0 -1l18 -6.5" }) ] } ), fe = (n) => /* @__PURE__ */ o.jsxs( C, { name: "Microphone", ...n, children: [ /* @__PURE__ */ o.jsx("path", { d: "M9 2m0 3a3 3 0 0 1 3 -3h0a3 3 0 0 1 3 3v5a3 3 0 0 1 -3 3h0a3 3 0 0 1 -3 -3z" }), /* @__PURE__ */ o.jsx("path", { d: "M5 10a7 7 0 0 0 14 0" }), /* @__PURE__ */ o.jsx("path", { d: "M8 21l8 0" }), /* @__PURE__ */ o.jsx("path", { d: "M12 17l0 4" }) ] } ), he = (n) => /* @__PURE__ */ o.jsx( C, { name: "PlayerStop", ...n, children: /* @__PURE__ */ o.jsx("path", { d: "M5 5m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z" }) } ), ge = (n) => /* @__PURE__ */ o.jsxs( C, { name: "PhoneCall", ...n, children: [ /* @__PURE__ */ o.jsx("path", { d: "M5 4h4l2 5l-2.5 1.5a11 11 0 0 0 5 5l1.5 -2.5l5 2v4a2 2 0 0 1 -2 2a16 16 0 0 1 -15 -15a2 2 0 0 1 2 -2" }), /* @__PURE__ */ o.jsx("path", { d: "M15 7a2 2 0 0 1 2 2" }), /* @__PURE__ */ o.jsx("path", { d: "M15 3a6 6 0 0 1 6 6" }) ] } ), xe = (n) => /* @__PURE__ */ o.jsxs( C, { name: "PhoneOff", ...n, children: [ /* @__PURE__ */ o.jsx("path", { d: "M3 21l18 -18" }), /* @__PURE__ */ o.jsx("path", { d: "M5.831 14.161a15.946 15.946 0 0 1 -2.831 -8.161a2 2 0 0 1 2 -2h4l2 5l-2.5 1.5c.108 .22 .223 .435 .345 .645m1.751 2.277c.843 .84 1.822 1.544 2.904 2.078l1.5 -2.5l5 2v4a2 2 0 0 1 -2 2a15.963 15.963 0 0 1 -10.344 -4.657" }) ] } ), ke = (n) => /* @__PURE__ */ o.jsxs( C, { name: "Wifi", ...n, children: [ /* @__PURE__ */ o.jsx("path", { d: "M12 18l.01 0" }), /* @__PURE__ */ o.jsx("path", { d: "M9.172 15.172a4 4 0 0 1 5.656 0" }), /* @__PURE__ */ o.jsx("path", { d: "M6.343 12.343a8 8 0 0 1 11.314 0" }), /* @__PURE__ */ o.jsx("path", { d: "M3.515 9.515c4.686 -4.687 12.284 -4.687 17 0" }) ] } ), pe = (n) => /* @__PURE__ */ o.jsxs( C, { name: "WifiOff", ...n, children: [ /* @__PURE__ */ o.jsx("path", { d: "M12 18l.01 0" }), /* @__PURE__ */ o.jsx("path", { d: "M9.172 15.172a4 4 0 0 1 5.656 0" }), /* @__PURE__ */ o.jsx("path", { d: "M6.343 12.343a7.963 7.963 0 0 1 3.864 -2.14m4.163 .155a7.965 7.965 0 0 1 3.287 2" }), /* @__PURE__ */ o.jsx("path", { d: "M3.515 9.515a12 12 0 0 1 3.544 -2.455m3.101 -.92a12 12 0 0 1 10.325 3.374" }), /* @__PURE__ */ o.jsx("path", { d: "M3 3l18 18" }) ] } ), ve = ({ state: n }) => { const t = n.connection === "connected", s = n.connection === "connecting", i = t ? "kos:text-green-400" : s ? "kos:text-yellow-400" : "kos:text-red-400", a = t ? "연결됨" : s ? "연결 중..." : "연결 안됨", c = t || s ? ke : pe; return /* @__PURE__ */ o.jsxs( "div", { className: `kos:flex kos:items-center kos:gap-2 kos:px-3 kos:py-1 kos:bg-black kos:bg-opacity-50 kos:rounded-full kos:text-white kos:text-xs ${i}`, children: [ /* @__PURE__ */ o.jsx( c, { className: `kos:w-4 kos:h-4 ${s ? "kos:animate-pulse" : ""}` } ), /* @__PURE__ */ o.jsx("span", { children: a }), n.isStreaming && /* @__PURE__ */ o.jsx("span", { className: "kos:text-sm kos:text-blue-400", children: "응답 생성 중..." }) ] } ); }, me = ({ state: n, connect: t, disconnect: s }) => { const i = n.connection === "connected", a = n.connection === "connecting", c = () => { i || a ? s() : t(); }; return /* @__PURE__ */ o.jsx( "button", { onClick: c, disabled: a, className: `kos:p-3 kos:rounded-full kos:transition-colors ${i ? "kos:bg-red-500 kos:hover:bg-red-600 kos:text-white kos:cursor-pointer" : a ? "kos:bg-yellow-500 kos:text-white kos:animate-pulse kos:cursor-not-allowed" : "kos:bg-green-500 kos:hover:bg-green-600 kos:text-white kos:cursor-pointer"}`, children: i || a ? /* @__PURE__ */ o.jsx(xe, { size: 20 }) : /* @__PURE__ */ o.jsx(ge, { size: 20 }) } ); }; var be = oe, Se = function(t) { var s = O.useRef(t); return be(function() { s.current = t; }), s; }, q = function(t, s) { if (typeof t == "function") { t(s); return; } t.current = s; }, ye = function(t, s) { var i = O.useRef(); return O.useCallback(function(a) { t.current = a, i.current && q(i.current, null), i.current = s, s && q(s, a); }, [s]); }, K = { "min-height": "0", "max-height": "none", height: "0", visibility: "hidden", overflow: "hidden", position: "absolute", "z-index": "-1000", top: "0", right: "0", display: "block" }, Re = function(t) { Object.keys(K).forEach(function(s) { t.style.setProperty(s, K[s], "important"); }); }, J = Re, x = null, Y = function(t, s) { var i = t.scrollHeight; return s.sizingStyle.boxSizing === "border-box" ? i + s.borderSize : i - s.paddingSize; }; function je(n, t, s, i) { s === void 0 && (s = 1), i === void 0 && (i = 1 / 0), x || (x = document.createElement("textarea"), x.setAttribute("tabindex", "-1"), x.setAttribute("aria-hidden", "true"), J(x)), x.parentNode === null && document.body.appendChild(x); var a = n.paddingSize, c = n.borderSize, r = n.sizingStyle, e = r.boxSizing; Object.keys(r).forEach(function(m) { var S = m; x.style[S] = r[S]; }), J(x), x.value = t; var f = Y(x, n); x.value = t, f = Y(x, n), x.value = "x"; var u = x.scrollHeight - a, h = u * s; e === "border-box" && (h = h + a + c), f = Math.max(h, f); var p = u * i; return e === "border-box" && (p = p + a + c), f = Math.min(p, f), [f, u]; } var G = function() { }, Ce = function(t, s) { return t.reduce(function(i, a) { return i[a] = s[a], i; }, {}); }, we = [ "borderBottomWidth", "borderLeftWidth", "borderRightWidth", "borderTopWidth", "boxSizing", "fontFamily", "fontSize", "fontStyle", "fontWeight", "letterSpacing", "lineHeight", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", // non-standard "tabSize", "textIndent", // non-standard "textRendering", "textTransform", "width", "wordBreak", "wordSpacing", "scrollbarGutter" ], Me = !!document.documentElement.currentStyle, ze = function(t) { var s = window.getComputedStyle(t); if (s === null) return null; var i = Ce(we, s), a = i.boxSizing; if (a === "") return null; Me && a === "border-box" && (i.width = parseFloat(i.width) + parseFloat(i.borderRightWidth) + parseFloat(i.borderLeftWidth) + parseFloat(i.paddingRight) + parseFloat(i.paddingLeft) + "px"); var c = parseFloat(i.paddingBottom) + parseFloat(i.paddingTop), r = parseFloat(i.borderBottomWidth) + parseFloat(i.borderTopWidth); return { sizingStyle: i, paddingSize: c, borderSize: r }; }, Ee = ze; function _(n, t, s) { var i = Se(s); R.useLayoutEffect(function() { var a = function(r) { return i.current(r); }; if (n) return n.addEventListener(t, a), function() { return n.removeEventListener(t, a); }; }, []); } var Te = function(t, s) { _(document.body, "reset", function(i) { t.current.form === i.target && s(i); }); }, Le = function(t) { _(window, "resize", t); }, Ie = function(t) { _(document.fonts, "loadingdone", t); }, Ne = ["cacheMeasurements", "maxRows", "minRows", "onChange", "onHeightChange"], He = function(t, s) { var i = t.cacheMeasurements, a = t.maxRows, c = t.minRows, r = t.onChange, e = r === void 0 ? G : r, f = t.onHeightChange, u = f === void 0 ? G : f, h = ie(t, Ne), p = h.value !== void 0, m = R.useRef(null), S = ye(m, s), L = R.useRef(0), b = R.useRef(), y = function() { var v = m.current, z = i && b.current ? b.current : Ee(v); if (z) { b.current = z; var I = je(z, v.value || v.placeholder || "x", c, a), w = I[0], F = I[1]; L.current !== w && (L.current = w, v.style.setProperty("height", w + "px", "important"), u(w, { rowHeight: F })); } }, A = function(v) { p || y(), e(v); }; return R.useLayoutEffect(y), Te(m, function() { if (!p) { var M = m.current.value; requestAnimationFrame(function() { var v = m.current; v && M !== v.value && y(); }); } }), Le(y), Ie(y), /* @__PURE__ */ R.createElement("textarea", ae({}, h, { onChange: A, ref: S })); }, Ae = /* @__PURE__ */ R.forwardRef(He); const Fe = ({ state: n, isReady: t, sendText: s, startRecording: i, stopRecording: a }) => { const c = T(null), [r, e] = j(""), f = async (h) => { if (h.preventDefault(), !(!r.trim() || n.isStreaming)) try { await s(r), e(""); } catch { } }, u = async () => { try { n.recording === "recording" ? await a() : await i(); } catch { } }; return /* @__PURE__ */ o.jsxs( "form", { onSubmit: f, className: "kos:flex-1 kos:flex kos:items-center kos:gap-4", children: [ /* @__PURE__ */ o.jsx( Ae, { ref: c, className: "kos:flex-1 kos:bg-gray-700 kos:text-white kos:p-3 kos:rounded-lg kos:resize-none kos:outline-none focus:kos:ring-2 focus:kos:ring-blue-500 kos:transition-all", placeholder: "메시지를 입력하세요...", value: r, onChange: (h) => e(h.target.value), disabled: n.isStreaming, maxRows: 5, minRows: 1, onKeyDown: (h) => { h.key === "Enter" && !h.shiftKey && (h.preventDefault(), f(h)); } } ), /* @__PURE__ */ o.jsxs("div", { className: "kos:flex kos:items-center kos:gap-2", children: [ /* @__PURE__ */ o.jsx( "button", { type: "button", onClick: u, disabled: n.isStreaming || !t(), className: `kos:p-3 kos:rounded-full kos:transition-colors ${n.recording === "recording" ? "kos:bg-red-500 kos:text-white kos:animate-pulse kos:cursor-pointer" : "kos:bg-gray-700 kos:hover:bg-gray-600 kos:text-white kos:cursor-pointer"} kos:disabled:opacity-50 kos:disabled:cursor-not-allowed! kos:disabled:hover:bg-gray-700`, children: n.recording === "recording" ? /* @__PURE__ */ o.jsx(he, { size: 20 }) : /* @__PURE__ */ o.jsx(fe, { size: 20 }) } ), /* @__PURE__ */ o.jsx( "button", { type: "submit", disabled: !r.trim() || n.isStreaming || !t(), className: "kos:p-3 kos:bg-blue-500 kos:text-white kos:rounded-full kos:cursor-pointer kos:hover:bg-blue-600 kos:disabled:opacity-50 kos:disabled:cursor-not-allowed! kos:disabled:hover:bg-blue-500 kos:transition-colors", children: /* @__PURE__ */ o.jsx(ue, { size: 20 }) } ) ] }) ] } ); }, We = ({ messages: n }) => { const t = T(null), [s, i] = j(!1), a = P(() => { const r = /* @__PURE__ */ new Map(); return n.forEach((e) => { e.id ? r.set(e.id, e) : r.set(Symbol("unique-msg"), e); }), Array.from(r.values()); }, [n]); H(() => { t.current && !s && (t.current.scrollTop = t.current.scrollHeight); }, [a, s]); const c = () => { if (t.current) { const { scrollTop: r, scrollHeight: e, clientHeight: f } = t.current, u = e - r - f < 10; i(!u); } }; return /* @__PURE__ */ o.jsx( "div", { ref: t, onScroll: c, className: "kos:flex-1 kos:overflow-y-auto kos:space-y-4 kos:pr-2 kos:scroll-smooth", children: a.map((r, e) => /* @__PURE__ */ o.jsx( "div", { className: `kos:flex kos:items-end kos:gap-2 ${r.role === "user" ? "kos:justify-end" : "kos:justify-start"}`, children: /* @__PURE__ */ o.jsx( "div", { className: `kos:px-4 kos:py-2 kos:rounded-2xl kos:max-w-md ${r.role === "user" ? "kos:bg-blue-600 kos:text-white kos:rounded-br-none" : "kos:bg-gray-700 kos:text-white kos:rounded-bl-none"}`, children: /* @__PURE__ */ o.jsx("span", { className: "kos:text-sm", children: r.fullContent || r.content }) } ) }, r.id || `msg-${e}` )) } ); }, $e = ({ apiKey: n, width: t = 800, className: s = "" }) => { const i = T(null), a = P(() => document.createElement("div"), []), c = P( () => ({ onReady: () => /* @__PURE__ */ console.log("디지털 휴먼 준비 완료!"), onMessageReceived: (u) => /* @__PURE__ */ console.log("받은 메시지:", u), onError: (u) => /* @__PURE__ */ console.error("오류:", u) }), [] ), r = ce({ apiKey: n, container: i.current || a, callbacks: c }); H(() => () => { r.client && r.disconnect(); }, [r.client]); const e = async () => { if (i.current && r.client) try { await r.connect(); } catch { } }, f = () => { r.client && r.disconnect(); }; return /* @__PURE__ */ o.jsxs("div", { className: `kos:relative kos:w-full kos:flex kos:gap-4 ${s}`, children: [ /* @__PURE__ */ o.jsxs("div", { className: "kos:relative kos:flex-1", children: [ /* @__PURE__ */ o.jsx( "div", { ref: i, className: "kos:w-full kos:h-96 kos:bg-black kos:rounded-lg", style: { width: `${t}px`, height: "400px" } } ), /* @__PURE__ */ o.jsxs("div", { className: "kos:absolute kos:top-0 kos:left-0 kos:w-full kos:h-full kos:p-4 kos:flex kos:flex-col kos:justify-between kos:z-10 pointer-events-none", children: [ /* @__PURE__ */ o.jsxs("div", { className: "kos:flex kos:justify-between kos:items-start", children: [ /* @__PURE__ */ o.jsx(ve, { state: r.state }), /* @__PURE__ */ o.jsx( me, { state: r.state, connect: e, disconnect: f } ) ] }), /* @__PURE__ */ o.jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ o.jsx( Fe, { state: r.state, isReady: r.isReady, sendText: r.sendText, startRecording: r.startRecording, stopRecording: r.stopRecording } ) }) ] }) ] }), /* @__PURE__ */ o.jsx("div", { className: "kos:w-80 kos:h-96 kos:bg-gray-100 kos:rounded-lg kos:p-4 kos:flex kos:flex-col", children: /* @__PURE__ */ o.jsx(We, { messages: r.messages }) }) ] }); }, _e = ({ apiKey: n }) => { const [t, s] = j(!1); return /* @__PURE__ */ o.jsx("div", { className: "kos:p-4", children: /* @__PURE__ */ o.jsx( $e, { apiKey: n, width: 800, isFullscreen: t, onToggleFullscreen: () => s(!t), className: "kos:max-w-4xl kos:mx-auto" } ) }); }; export { ve as ConnectionStatusIndicator, me as ConnectionToggleButton, se as KleverOneClient, Fe as MessageInput, We as MessageList, $e as UnifiedConversation, _e as UnifiedConversationExample, ce as useKleverOneClient };