@fuzionx/helpdesk 0.1.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,930 @@
1
+ import { inject as G, ref as C, computed as Z, watch as K, nextTick as A, onMounted as ee, onUnmounted as te, openBlock as c, createElementBlock as h, normalizeStyle as se, unref as g, normalizeClass as B, createElementVNode as a, createCommentVNode as v, createVNode as ne, Transition as ie, withCtx as oe, toDisplayString as f, createTextVNode as I, Fragment as V, renderList as ae, withDirectives as de, withKeys as re, vModelText as le, reactive as ce } from "vue";
2
+ const he = {
3
+ /**
4
+ * WebSocket URL (선택)
5
+ * 빈 문자열이면 현재 도메인 기준 자동 구성.
6
+ * 외부 사이트에서는 'wss://example.com/ws' 형태로 지정.
7
+ */
8
+ wsUrl: "",
9
+ /** WS 네임스페이스 경로 */
10
+ wsNamespace: "/support-chat",
11
+ // ── ASP/WASM 설정 ──
12
+ /**
13
+ * ASP 암/복호화 활성화 여부
14
+ * true: FuzionXSocket 내부에서 자동 ASP 암/복호화
15
+ * false: 평문 통신 (개발/테스트용)
16
+ */
17
+ aspEnabled: !0,
18
+ /**
19
+ * ASP masterSecret (동일 도메인 SPA에서 provide로 전달)
20
+ * 빈 문자열이면 connect() 시 자동 추출 시도.
21
+ */
22
+ masterSecret: "",
23
+ /**
24
+ * WASM 파일 URL
25
+ * 동일 도메인에서는 '/wasm/fuzionx_client_wasm.js' 자동 사용.
26
+ * 외부 사이트에서는 CDN URL 지정 필요.
27
+ */
28
+ wasmUrl: "/wasm/fuzionx_client_wasm.js",
29
+ /** 템플릿 이름 (default / minimal / bubble) */
30
+ template: "default",
31
+ /** 테마 (light / dark / auto) */
32
+ theme: "auto",
33
+ /** FAB 위치 (bottom-right / bottom-left) */
34
+ position: "bottom-right",
35
+ /** 위젯 타이틀 */
36
+ title: "고객상담",
37
+ /** 입장안내 환영 메시지 */
38
+ welcomeMessage: "안녕하세요! 무엇을 도와드릴까요?",
39
+ /** 상담 가능 시간 안내 */
40
+ operatingHours: "09:00~18:00",
41
+ /** 주말/공휴일 안내 */
42
+ offDayNotice: "주말/공휴일 휴무",
43
+ /** WS 자동 연결 여부 */
44
+ autoConnect: !1,
45
+ /** 인증 토큰 (외부 사이트 전용 JWT) */
46
+ authToken: "",
47
+ /** 팝업 모드 (true: window.open, false: 인라인 위젯) */
48
+ popupMode: !1,
49
+ /** 다국어 메시지 오버라이드 */
50
+ i18n: null,
51
+ /** 위젯 z-index */
52
+ zIndex: 9990
53
+ };
54
+ function ue(n = {}) {
55
+ const e = { ...he, ...n };
56
+ return ["light", "dark", "auto"].includes(e.theme) || (console.warn(`[fuzionx-helpdesk] 알 수 없는 theme: "${e.theme}", 기본값 "auto" 사용`), e.theme = "auto"), ["bottom-right", "bottom-left"].includes(e.position) || (console.warn(`[fuzionx-helpdesk] 알 수 없는 position: "${e.position}", 기본값 "bottom-right" 사용`), e.position = "bottom-right"), e;
57
+ }
58
+ const k = {
59
+ /** 상담사 온라인 여부 확인 (입장 버튼 클릭 시) */
60
+ CHECK_AGENTS: "checkAgents",
61
+ /** 상담 요청 → 세션 생성 */
62
+ REQUEST_CHAT: "requestChat",
63
+ /** 메시지 전송 */
64
+ MESSAGE: "message",
65
+ /** 회원이 상담 종료 */
66
+ CLOSE_CHAT: "closeChat",
67
+ /** 입력 중 상태 전송 */
68
+ TYPING: "typing",
69
+ /** 과거 세션 복구 요청 */
70
+ RESUME_SESSION: "resumeSession"
71
+ }, p = {
72
+ /** 상담사 온라인 상태 응답 */
73
+ AGENT_STATUS: "agentStatus",
74
+ /** 대기열 등록 완료 */
75
+ CHAT_QUEUED: "chatQueued",
76
+ /** 상담원 입장 (첫 번째) */
77
+ AGENT_JOINED: "agentJoined",
78
+ /** 메시지 수신 (FuzionXSocket 'message' 예약 이벤트 충돌 방지) */
79
+ MESSAGE: "newMessage",
80
+ /** Primary agent 퇴장 */
81
+ AGENT_LEFT: "agentLeft",
82
+ /** 상담 종료 */
83
+ CHAT_CLOSED: "chatClosed",
84
+ /** 에러 */
85
+ ERROR: "error",
86
+ /** 상대방의 입력 중 상태 수신 */
87
+ TYPING: "typing"
88
+ }, b = {
89
+ /** 위젯 열기 */
90
+ OPEN: "helpdesk:open",
91
+ /** 위젯 닫기 */
92
+ CLOSE: "helpdesk:close",
93
+ /** 화면 전환: welcome → waiting → chat → closed */
94
+ VIEW_CHANGE: "helpdesk:viewChange",
95
+ /** 연결 상태 변경 */
96
+ CONNECTION_CHANGE: "helpdesk:connectionChange"
97
+ };
98
+ class P {
99
+ /**
100
+ * @param {Object} opts - 사용자 옵션 (HelpdeskConfig 참조)
101
+ */
102
+ constructor(e) {
103
+ this.config = ue(e), this._ws = null, this.connected = !1, this.sessionKey = null, this.viewState = "welcome", this.messages = [], this.queuePosition = 0, this.agentName = null, this.isAgentTyping = !1, this._typingTimer = null, this.agentStatus = { available: !1, count: 0 }, this.history = [], this._listeners = /* @__PURE__ */ new Map(), this._masterSecret = this.config.masterSecret || "", this._aspEnabled = this.config.aspEnabled;
104
+ }
105
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
106
+ // EventEmitter
107
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
108
+ /**
109
+ * 이벤트 리스너 등록
110
+ *
111
+ * @param {string} event - 이벤트명
112
+ * @param {Function} handler - 핸들러 함수
113
+ * @returns {HelpdeskClient} 체이닝용
114
+ */
115
+ on(e, t) {
116
+ return this._listeners.has(e) || this._listeners.set(e, /* @__PURE__ */ new Set()), this._listeners.get(e).add(t), this;
117
+ }
118
+ /**
119
+ * 이벤트 리스너 제거
120
+ *
121
+ * @param {string} event - 이벤트명
122
+ * @param {Function} handler - 핸들러 함수
123
+ */
124
+ off(e, t) {
125
+ var o;
126
+ (o = this._listeners.get(e)) == null || o.delete(t);
127
+ }
128
+ /**
129
+ * 이벤트 발행
130
+ *
131
+ * @param {string} event - 이벤트명
132
+ * @param {*} data - 페이로드
133
+ * @private
134
+ */
135
+ _emit(e, t) {
136
+ var o;
137
+ (o = this._listeners.get(e)) == null || o.forEach((s) => {
138
+ try {
139
+ s(t);
140
+ } catch (i) {
141
+ console.error(`[Helpdesk] 이벤트 에러 (${e}):`, i);
142
+ }
143
+ });
144
+ }
145
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
146
+ // WebSocket 연결 관리
147
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
148
+ /**
149
+ * WS 연결 (FuzionXSocket 기반)
150
+ *
151
+ * useWebSocket.js connect() 패턴 동일:
152
+ * 1. _aspReady 대기
153
+ * 2. FuzionXSocket 클래스 로드
154
+ * 3. new FuzionXSocket(url, masterSecret, aspEnabled)
155
+ * 4. 이벤트 바인딩 + connect()
156
+ *
157
+ * @returns {Promise<void>}
158
+ */
159
+ async connect() {
160
+ if (this._ws && this.connected)
161
+ return;
162
+ if (window._aspReady && await window._aspReady, !window.FuzionXSocket)
163
+ try {
164
+ const s = Date.now(), i = this.config.wasmUrl || "/wasm/fuzionx_client_wasm.js", m = i.replace(".js", "_bg.wasm"), w = await import(
165
+ /* @vite-ignore */
166
+ `${i}?v=${s}`
167
+ );
168
+ await w.default({ module_or_path: `${m}?v=${s}` }), window.FuzionXSocket = w.FuzionXSocket;
169
+ } catch (s) {
170
+ console.error("[Helpdesk] WASM 로드 실패:", s);
171
+ return;
172
+ }
173
+ const e = location.protocol === "https:" ? "wss:" : "ws:", o = `${this.config.wsUrl || `${e}//${location.host}/ws`}${this.config.wsNamespace}`;
174
+ this._ws = new window.FuzionXSocket(o, this._masterSecret, this._aspEnabled), this._ws.on("connect", () => {
175
+ this.connected = !0, this._emit(b.CONNECTION_CHANGE, { connected: !0 }), console.log("[Helpdesk] WS 연결됨");
176
+ }), this._ws.on("disconnect", (s) => {
177
+ this.connected = !1, this._emit(b.CONNECTION_CHANGE, { connected: !1 }), console.log("[Helpdesk] WS 연결 해제:", s);
178
+ }), this._ws.on("error", () => {
179
+ console.error("[Helpdesk] WS 에러");
180
+ }), this._ws.on("welcome", (s) => {
181
+ console.log("[Helpdesk] welcome:", s);
182
+ }), this._ws.on("message", (s) => {
183
+ this._handleMessage(s);
184
+ }), this._ws.connect();
185
+ }
186
+ /** WS 연결 해제 */
187
+ disconnect() {
188
+ this._ws && (this._ws.disconnect(), this._ws = null), this.connected = !1;
189
+ }
190
+ /**
191
+ * WS 메시지 전송 (FuzionXSocket.send 사용)
192
+ *
193
+ * @param {string} type - 이벤트 타입
194
+ * @param {Object} data - 페이로드
195
+ * @private
196
+ */
197
+ _send(e, t = {}) {
198
+ this._ws && this._ws.is_connected() ? this._ws.send(e, t) : console.warn("[Helpdesk] WS 연결 안됨, 메시지 전송 불가:", e);
199
+ }
200
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
201
+ // WS 수신 메시지 처리
202
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
203
+ /**
204
+ * 서버 메시지 핸들러
205
+ *
206
+ * FuzionXSocket의 'message' 이벤트에서 호출됨.
207
+ * ASP 복호화는 FuzionXSocket 내부에서 자동 처리.
208
+ *
209
+ * @param {Object|string} raw - 서버 메시지 (자동 JSON 파싱 or 문자열)
210
+ * @private
211
+ */
212
+ _handleMessage(e) {
213
+ let t;
214
+ try {
215
+ t = typeof e == "string" ? JSON.parse(e) : e;
216
+ } catch (i) {
217
+ console.error("[Helpdesk] 메시지 파싱 오류:", i);
218
+ return;
219
+ }
220
+ const { type: o, data: s } = t;
221
+ switch (o) {
222
+ case p.AGENT_STATUS:
223
+ this.agentStatus = s, s.available ? this._setView("checking") : this._setView("unavailable"), this._emit(o, s);
224
+ break;
225
+ case p.CHAT_QUEUED:
226
+ this.sessionKey = s.sessionKey, this.queuePosition = s.position, this.sessionKey && localStorage.setItem("fx_support_session", this.sessionKey), s.messages && s.messages.length > 0 && (this.messages = s.messages.map((i) => ({
227
+ userId: i.user_id,
228
+ userName: i.nickname || i.username || (i.role_code ? "상담사" : "나"),
229
+ content: i.content,
230
+ messageType: i.message_type || "text",
231
+ createdAt: i.created_at,
232
+ isAgent: i.role_code === "admin" || i.role_code === "developer"
233
+ }))), this._setView("waiting"), this._emit(o, s);
234
+ break;
235
+ case p.AGENT_JOINED:
236
+ this.sessionKey = s.sessionKey || this.sessionKey, this.agentName = s.nickname || s.username || s.agentName || "상담사", this.queuePosition = 0, this.sessionKey && localStorage.setItem("fx_support_session", this.sessionKey), this._setView("chatting"), s.messages && s.messages.length > 0 ? (this.messages = s.messages.map((i) => ({
237
+ userId: i.user_id,
238
+ userName: i.nickname || i.username || (i.role_code ? this.agentName : "나"),
239
+ content: i.content,
240
+ messageType: i.message_type || "text",
241
+ createdAt: i.created_at,
242
+ isAgent: i.role_code === "admin" || i.role_code === "developer"
243
+ })), this._addSystemMessage("이전 대화 내역을 불러왔습니다.")) : this._addSystemMessage("상담원이 입장했습니다."), this._emit(o, s);
244
+ break;
245
+ case p.TYPING:
246
+ this.isAgentTyping = s.isTyping, this._typingTimer && clearTimeout(this._typingTimer), this.isAgentTyping && (this._typingTimer = setTimeout(() => {
247
+ this.isAgentTyping = !1;
248
+ }, 3e3)), this._emit(o, s);
249
+ break;
250
+ case p.MESSAGE:
251
+ this.messages.push({
252
+ userId: s.userId,
253
+ userName: s.userName,
254
+ content: s.content,
255
+ messageType: s.messageType || "text",
256
+ createdAt: s.createdAt,
257
+ isAgent: !0
258
+ }), this._emit(o, s);
259
+ break;
260
+ case p.AGENT_LEFT:
261
+ this._addSystemMessage("상담원 연결이 해제되었습니다. 잠시 후 다시 연결됩니다."), this._setView("waiting"), this._emit(o, s);
262
+ break;
263
+ case p.CHAT_CLOSED:
264
+ s && s.messages && s.messages.length > 0 && (this.messages = s.messages.map((i) => ({
265
+ userId: i.user_id,
266
+ userName: i.nickname || i.username || (i.role_code ? "상담사" : "나"),
267
+ content: i.content,
268
+ messageType: i.message_type || "text",
269
+ createdAt: i.created_at,
270
+ isAgent: i.role_code === "admin" || i.role_code === "developer"
271
+ }))), this._addSystemMessage("상담이 종료되었습니다."), this._setView("closed"), this._emit(o, s);
272
+ break;
273
+ case "resumeFailed":
274
+ localStorage.removeItem("fx_support_session"), this.sessionKey = null, this._setView("welcome"), this._emit(o, s);
275
+ break;
276
+ case p.ERROR:
277
+ console.error("[Helpdesk] 서버 에러:", s.message), this._emit(o, s);
278
+ break;
279
+ default:
280
+ this._emit(o, s);
281
+ }
282
+ }
283
+ /**
284
+ * 서버 이벤트 직접 핸들러
285
+ *
286
+ * FuzionXSocket이 type별로 직접 dispatch하는 경우 처리.
287
+ * 예: ws.on('agentStatus', data) → _handleServerEvent('agentStatus', data)
288
+ *
289
+ * @param {string} type - 이벤트 타입
290
+ * @param {Object} data - 페이로드
291
+ * @private
292
+ */
293
+ _handleServerEvent(e, t) {
294
+ this._handleMessage({ type: e, data: t });
295
+ }
296
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
297
+ // 공개 API (회원 액션)
298
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
299
+ /**
300
+ * 저장소에 과거 연결된 세션 키가 있는지 검사
301
+ * @returns {boolean}
302
+ */
303
+ hasSavedSession() {
304
+ return !!localStorage.getItem("fx_support_session");
305
+ }
306
+ /**
307
+ * 이전 세션 정보로 다시 조인 시도
308
+ * 서버에서 성공 시 agentJoined나 chatQueued 반환
309
+ */
310
+ resumeSession() {
311
+ const e = localStorage.getItem("fx_support_session");
312
+ e && this._send(k.RESUME_SESSION, {
313
+ sessionKey: e,
314
+ userId: this.config._userId || null
315
+ });
316
+ }
317
+ /**
318
+ * 상담사 가용 여부 확인
319
+ *
320
+ * "상담 입장하기" 클릭 시 호출.
321
+ * 결과는 agentStatus 이벤트로 수신.
322
+ */
323
+ checkAgents() {
324
+ this._send(k.CHECK_AGENTS, {});
325
+ }
326
+ /**
327
+ * 상담 요청 (세션 생성)
328
+ *
329
+ * checkAgents → available=true 확인 후 호출.
330
+ *
331
+ * @param {Object} opts
332
+ * @param {number} [opts.userId] - 회원 ID
333
+ * @param {string} [opts.subject] - 상담 주제
334
+ */
335
+ requestChat({ userId: e = null, subject: t = null } = {}) {
336
+ this._requestChatLock || (this._requestChatLock = !0, this._send(k.REQUEST_CHAT, {
337
+ userId: e,
338
+ subject: t,
339
+ sessionKey: this.sessionKey || localStorage.getItem("fx_support_session")
340
+ }), setTimeout(() => {
341
+ this._requestChatLock = !1;
342
+ }, 1e3));
343
+ }
344
+ /**
345
+ * 메시지 전송
346
+ *
347
+ * @param {string} content - 메시지 내용
348
+ * @param {Object} [userInfo] - 발신자 정보 { userId, username, nickname }
349
+ */
350
+ sendMessage(e, t = {}) {
351
+ if (!this.sessionKey || !e.trim()) return;
352
+ const o = e.trim();
353
+ this._send(k.MESSAGE, {
354
+ sessionKey: this.sessionKey,
355
+ content: o,
356
+ userId: t.userId || null,
357
+ username: t.username || null,
358
+ nickname: t.nickname || null,
359
+ roleCode: "member"
360
+ }), this.messages.push({
361
+ userId: t.userId,
362
+ userName: t.nickname || t.username || "나",
363
+ content: o,
364
+ messageType: "text",
365
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
366
+ isAgent: !1,
367
+ _isLocal: !0
368
+ }), this._emit(b.VIEW_CHANGE, { view: this.viewState, messages: this.messages });
369
+ }
370
+ /**
371
+ * 타이핑 상태 전송
372
+ * @param {boolean} isTyping - 입력 중 상태
373
+ */
374
+ sendTyping(e) {
375
+ this.sessionKey && this._send(k.TYPING, {
376
+ sessionKey: this.sessionKey,
377
+ isTyping: e
378
+ });
379
+ }
380
+ /**
381
+ * 상담 종료 (회원 측)
382
+ */
383
+ closeChat() {
384
+ this.sessionKey && this._send(k.CLOSE_CHAT, { sessionKey: this.sessionKey });
385
+ }
386
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
387
+ // 내부 헬퍼
388
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
389
+ /**
390
+ * 화면 상태 전환
391
+ *
392
+ * @param {ViewState} view
393
+ * @private
394
+ */
395
+ _setView(e) {
396
+ this.viewState = e, this._emit(b.VIEW_CHANGE, { view: e, messages: this.messages });
397
+ }
398
+ /**
399
+ * 시스템 메시지 추가
400
+ *
401
+ * @param {string} content
402
+ * @private
403
+ */
404
+ _addSystemMessage(e) {
405
+ this.messages.push({
406
+ content: e,
407
+ messageType: "system",
408
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
409
+ });
410
+ }
411
+ /**
412
+ * 상태 초기화 (새 상담 시작 전)
413
+ */
414
+ reset() {
415
+ this.sessionKey = null, this.viewState = "welcome", this.messages = [], this.queuePosition = 0, this.agentName = "", this.agentStatus = { available: !1, count: 0 };
416
+ }
417
+ /** 리소스 정리 */
418
+ destroy() {
419
+ this.disconnect(), this._listeners.clear(), this.reset();
420
+ }
421
+ }
422
+ const ge = ["title"], me = {
423
+ key: 0,
424
+ class: "hd-panel"
425
+ }, pe = { class: "hd-panel-hd" }, fe = { class: "hd-panel-title" }, be = {
426
+ key: 0,
427
+ class: "hd-view hd-view-welcome"
428
+ }, ve = { class: "hd-welcome-msg" }, _e = { class: "hd-welcome-hours" }, we = { key: 0 }, ye = {
429
+ key: 1,
430
+ class: "hd-view hd-view-checking"
431
+ }, xe = {
432
+ key: 2,
433
+ class: "hd-view hd-view-unavail"
434
+ }, ke = { class: "hd-unavail-sub" }, Ee = {
435
+ key: 3,
436
+ class: "hd-view hd-view-waiting"
437
+ }, Se = {
438
+ key: 0,
439
+ class: "hd-wait-pos"
440
+ }, Te = {
441
+ key: 4,
442
+ class: "hd-view hd-view-chat"
443
+ }, Ce = {
444
+ key: 0,
445
+ class: "hd-chat-agent"
446
+ }, Ne = {
447
+ key: 0,
448
+ class: "hd-msg-sys"
449
+ }, Ae = { class: "hd-msg-bubble" }, Ie = { class: "hd-msg-time" }, He = {
450
+ key: 0,
451
+ class: "hd-msg hd-msg-agent"
452
+ }, ze = {
453
+ key: 1,
454
+ class: "hd-chat-input"
455
+ }, Me = {
456
+ key: 2,
457
+ class: "hd-chat-ended"
458
+ }, $e = {
459
+ __name: "HelpdeskWidget",
460
+ props: {
461
+ user: {
462
+ type: Object,
463
+ default: null
464
+ }
465
+ },
466
+ setup(n) {
467
+ const e = n, t = G("helpdeskClient"), o = G("helpdeskConfig"), s = C(!1), i = C("welcome"), m = C(""), w = C(null), L = Z(() => {
468
+ var l;
469
+ return o.theme !== "auto" ? o.theme : (l = window.matchMedia) != null && l.call(window, "(prefers-color-scheme: dark)").matches ? "dark" : "light";
470
+ });
471
+ function z(l) {
472
+ i.value = l.view, l.view === "chatting" && A(() => N());
473
+ }
474
+ function M(l) {
475
+ l.connected && t.hasSavedSession() && (i.value = "checking", t.resumeSession());
476
+ }
477
+ K(() => e.user, (l) => {
478
+ l && l._id ? (o._userId = l._id, o._username = l.username, o._nickname = l.nickname || l.name) : (o._userId = null, o._username = null, o._nickname = null);
479
+ }, { immediate: !0 });
480
+ function N() {
481
+ w.value && (w.value.scrollTop = w.value.scrollHeight);
482
+ }
483
+ async function R() {
484
+ if (s.value = !0, !t.connected)
485
+ try {
486
+ await t.connect();
487
+ } catch (l) {
488
+ console.error(l);
489
+ }
490
+ }
491
+ function W() {
492
+ (i.value === "chatting" || i.value === "waiting") && t.disconnect(), s.value = !1;
493
+ }
494
+ function $() {
495
+ i.value = "checking", t.checkAgents();
496
+ }
497
+ function j() {
498
+ i.value = "welcome";
499
+ }
500
+ function O() {
501
+ m.value.trim() && (t.sendMessage(m.value, {
502
+ userId: o._userId,
503
+ username: o._username,
504
+ nickname: o._nickname
505
+ }), t.sendTyping(!1), T = !1, S && clearTimeout(S), m.value = "", A(() => N()));
506
+ }
507
+ let S = null, T = !1;
508
+ function F() {
509
+ T || (t.sendTyping(!0), T = !0), S && clearTimeout(S), S = setTimeout(() => {
510
+ t.sendTyping(!1), T = !1;
511
+ }, 2e3);
512
+ }
513
+ function J() {
514
+ t.reset(), i.value = "welcome";
515
+ }
516
+ function Q(l) {
517
+ return {
518
+ "hd-msg-mine": !l.isAgent && l.messageType !== "system",
519
+ "hd-msg-agent": l.isAgent,
520
+ "hd-msg-sys-wrap": l.messageType === "system"
521
+ };
522
+ }
523
+ function X(l) {
524
+ if (!l) return "";
525
+ const d = new Date(l);
526
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
527
+ }
528
+ return K(
529
+ () => t.messages.length,
530
+ () => A(() => N())
531
+ ), ee(() => {
532
+ t.on(b.VIEW_CHANGE, z), t.on(b.CONNECTION_CHANGE, M), t.on(p.AGENT_STATUS, (l) => {
533
+ l.available && t.requestChat({
534
+ userId: o._userId,
535
+ subject: null
536
+ });
537
+ }), o.autoConnect && t.connect().catch(() => {
538
+ });
539
+ }), te(() => {
540
+ t.off(b.VIEW_CHANGE, z), t.off(b.CONNECTION_CHANGE, M);
541
+ }), (l, d) => (c(), h("div", {
542
+ class: B(["hd-widget", `hd-pos-${g(o).position}`, `hd-theme-${L.value}`]),
543
+ style: se({ zIndex: g(o).zIndex })
544
+ }, [
545
+ s.value ? v("", !0) : (c(), h("button", {
546
+ key: 0,
547
+ class: "hd-fab",
548
+ onClick: R,
549
+ title: g(o).title
550
+ }, [...d[1] || (d[1] = [
551
+ a("svg", {
552
+ width: "24",
553
+ height: "24",
554
+ viewBox: "0 0 24 24",
555
+ fill: "none",
556
+ stroke: "currentColor",
557
+ "stroke-width": "2"
558
+ }, [
559
+ a("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
560
+ ], -1)
561
+ ])], 8, ge)),
562
+ ne(ie, { name: "hd-slide" }, {
563
+ default: oe(() => [
564
+ s.value ? (c(), h("div", me, [
565
+ a("div", pe, [
566
+ a("span", fe, f(g(o).title), 1),
567
+ a("button", {
568
+ class: "hd-panel-close",
569
+ onClick: W
570
+ }, "×")
571
+ ]),
572
+ i.value === "welcome" ? (c(), h("div", be, [
573
+ d[3] || (d[3] = a("div", { class: "hd-welcome-icon" }, "💬", -1)),
574
+ a("p", ve, f(g(o).welcomeMessage), 1),
575
+ a("p", _e, [
576
+ d[2] || (d[2] = a("i", null, "🕐", -1)),
577
+ I(" " + f(g(o).operatingHours) + " ", 1),
578
+ g(o).offDayNotice ? (c(), h("span", we, " · " + f(g(o).offDayNotice), 1)) : v("", !0)
579
+ ]),
580
+ a("button", {
581
+ class: "hd-btn-enter",
582
+ onClick: $
583
+ }, "상담 입장하기")
584
+ ])) : i.value === "checking" ? (c(), h("div", ye, [...d[4] || (d[4] = [
585
+ a("div", { class: "hd-spinner" }, null, -1),
586
+ a("p", null, "상담사를 확인하고 있습니다...", -1)
587
+ ])])) : i.value === "unavailable" ? (c(), h("div", xe, [
588
+ d[5] || (d[5] = a("div", { class: "hd-unavail-icon" }, "😔", -1)),
589
+ d[6] || (d[6] = a("p", { class: "hd-unavail-msg" }, "현재 상담 가능한 상담사가 없습니다.", -1)),
590
+ a("p", ke, "운영시간: " + f(g(o).operatingHours), 1),
591
+ a("button", {
592
+ class: "hd-btn-retry",
593
+ onClick: $
594
+ }, "다시 확인"),
595
+ a("button", {
596
+ class: "hd-btn-back",
597
+ onClick: j
598
+ }, "돌아가기")
599
+ ])) : i.value === "waiting" ? (c(), h("div", Ee, [
600
+ d[8] || (d[8] = a("div", { class: "hd-spinner" }, null, -1)),
601
+ d[9] || (d[9] = a("p", { class: "hd-wait-msg" }, "상담사 연결 대기 중...", -1)),
602
+ g(t).queuePosition ? (c(), h("p", Se, [
603
+ d[7] || (d[7] = I("대기 순번: ", -1)),
604
+ a("strong", null, f(g(t).queuePosition), 1)
605
+ ])) : v("", !0)
606
+ ])) : i.value === "chatting" || i.value === "closed" ? (c(), h("div", Te, [
607
+ g(t).agentName ? (c(), h("div", Ce, [
608
+ d[10] || (d[10] = I(" 상담원: ", -1)),
609
+ a("strong", null, f(g(t).agentName), 1)
610
+ ])) : v("", !0),
611
+ a("div", {
612
+ class: "hd-chat-msgs",
613
+ ref_key: "msgBox",
614
+ ref: w
615
+ }, [
616
+ (c(!0), h(V, null, ae(g(t).messages, (y, Y) => (c(), h("div", {
617
+ key: Y,
618
+ class: B(["hd-msg", Q(y)])
619
+ }, [
620
+ y.messageType === "system" ? (c(), h("div", Ne, f(y.content), 1)) : (c(), h(V, { key: 1 }, [
621
+ a("div", Ae, f(y.content), 1),
622
+ a("div", Ie, f(X(y.createdAt)), 1)
623
+ ], 64))
624
+ ], 2))), 128)),
625
+ g(t).isAgentTyping ? (c(), h("div", He, [...d[11] || (d[11] = [
626
+ a("div", { class: "hd-msg-bubble hd-typing-indicator" }, [
627
+ a("span", null, "."),
628
+ a("span", null, "."),
629
+ a("span", null, ".")
630
+ ], -1)
631
+ ])])) : v("", !0)
632
+ ], 512),
633
+ i.value === "chatting" ? (c(), h("div", ze, [
634
+ de(a("input", {
635
+ type: "text",
636
+ "onUpdate:modelValue": d[0] || (d[0] = (y) => m.value = y),
637
+ onInput: F,
638
+ onKeyup: re(O, ["enter"]),
639
+ placeholder: "메시지를 입력하세요..."
640
+ }, null, 544), [
641
+ [le, m.value]
642
+ ]),
643
+ a("button", {
644
+ class: "hd-btn-send",
645
+ onClick: O
646
+ }, [...d[12] || (d[12] = [
647
+ a("svg", {
648
+ width: "16",
649
+ height: "16",
650
+ viewBox: "0 0 24 24",
651
+ fill: "currentColor"
652
+ }, [
653
+ a("path", { d: "M2 21l21-9L2 3v7l15 2-15 2z" })
654
+ ], -1)
655
+ ])])
656
+ ])) : v("", !0),
657
+ i.value === "closed" ? (c(), h("div", Me, [
658
+ d[13] || (d[13] = a("p", null, "상담이 종료되었습니다.", -1)),
659
+ a("button", {
660
+ class: "hd-btn-new",
661
+ onClick: J
662
+ }, "새 상담 시작")
663
+ ])) : v("", !0)
664
+ ])) : v("", !0)
665
+ ])) : v("", !0)
666
+ ]),
667
+ _: 1
668
+ })
669
+ ], 6));
670
+ }
671
+ }, We = {
672
+ /**
673
+ * Vue Plugin 설치
674
+ *
675
+ * @param {import('vue').App} app - Vue 앱 인스턴스
676
+ * @param {Object} options - 위젯 옵션 (HelpdeskConfig 참조)
677
+ */
678
+ install(n, e = {}) {
679
+ const t = new P(e), o = ce(t);
680
+ n.provide("helpdeskClient", o), n.provide("helpdeskConfig", o.config), n.component("HelpdeskWidget", $e), n.config.globalProperties.$helpdesk = o;
681
+ }
682
+ };
683
+ let r = null, u = null, E = !1, x = "welcome";
684
+ function Oe(n) {
685
+ return r ? (console.warn("[HelpdeskEmbed] 이미 초기화됨"), r) : (r = new P(n), Le(r.config), Be(r.config), r.on(b.VIEW_CHANGE, (e) => {
686
+ x = e.view, _();
687
+ }), r.on(b.CONNECTION_CHANGE, () => {
688
+ _();
689
+ }), r.on(p.AGENT_STATUS, (e) => {
690
+ e.available && r.requestChat({
691
+ userId: r.config._userId,
692
+ subject: null
693
+ });
694
+ }), r.on(p.MESSAGE, () => {
695
+ _(), D();
696
+ }), r);
697
+ }
698
+ function Ge() {
699
+ r && (r.destroy(), r = null), u && (u.remove(), u = null);
700
+ const n = document.getElementById("hd-embed-styles");
701
+ n && n.remove();
702
+ }
703
+ function Ke() {
704
+ return r;
705
+ }
706
+ function Be(n) {
707
+ u = document.createElement("div"), u.id = "hd-embed-root", u.className = `hd-widget hd-pos-${n.position} hd-theme-${qe(n)}`, u.style.zIndex = n.zIndex;
708
+ const e = document.createElement("button");
709
+ e.className = "hd-fab", e.id = "hd-fab-btn", e.title = n.title, e.innerHTML = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>', e.onclick = () => Ve(), u.appendChild(e), document.body.appendChild(u);
710
+ }
711
+ async function Ve() {
712
+ if (E = !E, E) {
713
+ if (!r.connected)
714
+ try {
715
+ await r.connect();
716
+ } catch (e) {
717
+ console.error(e);
718
+ }
719
+ _();
720
+ } else
721
+ q();
722
+ const n = u.querySelector("#hd-fab-btn");
723
+ n && (n.style.display = E ? "none" : "flex");
724
+ }
725
+ function _() {
726
+ if (!E || !u) return;
727
+ let n = u.querySelector(".hd-panel");
728
+ n || (n = document.createElement("div"), n.className = "hd-panel", u.appendChild(n));
729
+ const e = r.config;
730
+ let t = `
731
+ <div class="hd-panel-hd">
732
+ <span class="hd-panel-title">${e.title}</span>
733
+ <button class="hd-panel-close" onclick="document.getElementById('hd-embed-root')._hdClose()">&times;</button>
734
+ </div>
735
+ `;
736
+ switch (x) {
737
+ case "welcome":
738
+ t += `
739
+ <div class="hd-view hd-view-welcome">
740
+ <div class="hd-welcome-icon">💬</div>
741
+ <p class="hd-welcome-msg">${e.welcomeMessage}</p>
742
+ <p class="hd-welcome-hours">🕐 ${e.operatingHours}${e.offDayNotice ? " · " + e.offDayNotice : ""}</p>
743
+ <button class="hd-btn-enter" id="hd-enter-btn">상담 입장하기</button>
744
+ </div>`;
745
+ break;
746
+ case "checking":
747
+ t += `
748
+ <div class="hd-view hd-view-checking">
749
+ <div class="hd-spinner"></div>
750
+ <p>상담사를 확인하고 있습니다...</p>
751
+ </div>`;
752
+ break;
753
+ case "unavailable":
754
+ t += `
755
+ <div class="hd-view hd-view-unavail">
756
+ <div class="hd-unavail-icon">😔</div>
757
+ <p class="hd-unavail-msg">현재 상담 가능한 상담사가 없습니다.</p>
758
+ <p class="hd-unavail-sub">운영시간: ${e.operatingHours}</p>
759
+ <button class="hd-btn-retry" id="hd-retry-btn">다시 확인</button>
760
+ <button class="hd-btn-back" id="hd-back-btn">돌아가기</button>
761
+ </div>`;
762
+ break;
763
+ case "waiting":
764
+ t += `
765
+ <div class="hd-view hd-view-waiting">
766
+ <div class="hd-spinner"></div>
767
+ <p class="hd-wait-msg">상담사 연결 대기 중...</p>
768
+ ${r.queuePosition ? `<p class="hd-wait-pos">대기 순번: <strong>${r.queuePosition}</strong></p>` : ""}
769
+ </div>`;
770
+ break;
771
+ case "chatting":
772
+ case "closed":
773
+ t += Ue();
774
+ break;
775
+ }
776
+ n.innerHTML = t, Pe();
777
+ }
778
+ function Ue() {
779
+ let n = "";
780
+ for (const t of r.messages)
781
+ if (t.messageType === "system")
782
+ n += `<div class="hd-msg hd-msg-sys-wrap"><div class="hd-msg-sys">${H(t.content)}</div></div>`;
783
+ else {
784
+ const o = t.isAgent ? "hd-msg-agent" : "hd-msg-mine";
785
+ n += `
786
+ <div class="hd-msg ${o}">
787
+ <div class="hd-msg-bubble">${H(t.content)}</div>
788
+ <div class="hd-msg-time">${De(t.createdAt)}</div>
789
+ </div>`;
790
+ }
791
+ let e = '<div class="hd-view hd-view-chat">';
792
+ return r.agentName && (e += `<div class="hd-chat-agent">상담원: <strong>${H(r.agentName)}</strong></div>`), e += `<div class="hd-chat-msgs" id="hd-msgs-box">${n}</div>`, x === "chatting" ? e += `
793
+ <div class="hd-chat-input">
794
+ <input type="text" id="hd-msg-input" placeholder="메시지를 입력하세요..." />
795
+ <button class="hd-btn-send" id="hd-send-btn">
796
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M2 21l21-9L2 3v7l15 2-15 2z"/></svg>
797
+ </button>
798
+ </div>` : e += `
799
+ <div class="hd-chat-ended">
800
+ <p>상담이 종료되었습니다.</p>
801
+ <button class="hd-btn-new" id="hd-new-btn">새 상담 시작</button>
802
+ </div>`, e += "</div>", e;
803
+ }
804
+ function Pe() {
805
+ u._hdClose = () => {
806
+ E = !1, q();
807
+ const m = u.querySelector("#hd-fab-btn");
808
+ m && (m.style.display = "flex");
809
+ };
810
+ const n = document.getElementById("hd-enter-btn");
811
+ n && (n.onclick = () => {
812
+ x = "checking", _(), r.checkAgents();
813
+ });
814
+ const e = document.getElementById("hd-retry-btn");
815
+ e && (e.onclick = () => {
816
+ x = "checking", _(), r.checkAgents();
817
+ });
818
+ const t = document.getElementById("hd-back-btn");
819
+ t && (t.onclick = () => {
820
+ x = "welcome", _();
821
+ });
822
+ const o = document.getElementById("hd-send-btn"), s = document.getElementById("hd-msg-input");
823
+ o && s && (o.onclick = () => U(s), s.onkeyup = (m) => {
824
+ m.key === "Enter" && U(s);
825
+ }, s.focus());
826
+ const i = document.getElementById("hd-new-btn");
827
+ i && (i.onclick = () => {
828
+ r.reset(), x = "welcome", _();
829
+ }), D();
830
+ }
831
+ function U(n) {
832
+ const e = n.value.trim();
833
+ e && (r.sendMessage(e, {
834
+ userId: r.config._userId,
835
+ username: r.config._username,
836
+ nickname: r.config._nickname
837
+ }), n.value = "", _());
838
+ }
839
+ function q() {
840
+ const n = u == null ? void 0 : u.querySelector(".hd-panel");
841
+ n && n.remove();
842
+ }
843
+ function D() {
844
+ const n = document.getElementById("hd-msgs-box");
845
+ n && (n.scrollTop = n.scrollHeight);
846
+ }
847
+ function qe(n) {
848
+ var e;
849
+ return n.theme !== "auto" ? n.theme : (e = window.matchMedia) != null && e.call(window, "(prefers-color-scheme: dark)").matches ? "dark" : "light";
850
+ }
851
+ function H(n) {
852
+ return n ? n.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;") : "";
853
+ }
854
+ function De(n) {
855
+ if (!n) return "";
856
+ const e = new Date(n);
857
+ return `${String(e.getHours()).padStart(2, "0")}:${String(e.getMinutes()).padStart(2, "0")}`;
858
+ }
859
+ function Le(n) {
860
+ if (document.getElementById("hd-embed-styles")) return;
861
+ const e = document.createElement("style");
862
+ e.id = "hd-embed-styles", e.textContent = `
863
+ /* ── 테마 변수 ── */
864
+ .hd-theme-dark{--hd-bg:#0c1219;--hd-bg2:#111b26;--hd-bg3:#162130;--hd-border:#1c2e42;--hd-txt:#b8d4e8;--hd-txt2:#8aaecd;--hd-txt3:#547a9c;--hd-acc:#00d9ff;--hd-acc-dim:rgba(0,217,255,.1);--hd-mine:#00d9ff;--hd-mine-fg:#000;--hd-agent-bg:#162130;--hd-agent-fg:#b8d4e8;--hd-shadow:rgba(0,0,0,.5)}
865
+ .hd-theme-light{--hd-bg:#fff;--hd-bg2:#f5f7fa;--hd-bg3:#eaeff5;--hd-border:#d4dbe5;--hd-txt:#0f1923;--hd-txt2:#2d4050;--hd-txt3:#5a6d7e;--hd-acc:#0072a8;--hd-acc-dim:rgba(0,114,168,.1);--hd-mine:#0072a8;--hd-mine-fg:#fff;--hd-agent-bg:#eaeff5;--hd-agent-fg:#0f1923;--hd-shadow:rgba(0,0,0,.15)}
866
+
867
+ .hd-widget{position:fixed;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Noto Sans KR',sans-serif;font-size:14px;line-height:1.5}
868
+ .hd-pos-bottom-right{bottom:24px;right:24px}
869
+ .hd-pos-bottom-left{bottom:24px;left:24px}
870
+ .hd-fab{width:56px;height:56px;border-radius:50%;background:var(--hd-acc);color:var(--hd-mine-fg);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px var(--hd-shadow);transition:transform .2s}
871
+ .hd-fab:hover{transform:scale(1.08)}
872
+ .hd-panel{width:360px;max-height:520px;background:var(--hd-bg);border:1px solid var(--hd-border);border-radius:12px;box-shadow:0 8px 32px var(--hd-shadow);display:flex;flex-direction:column;overflow:hidden;position:absolute;bottom:0;right:0}
873
+ .hd-panel-hd{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--hd-acc);color:var(--hd-mine-fg)}
874
+ .hd-panel-title{font-weight:700;font-size:.95rem}
875
+ .hd-panel-close{background:none;border:none;font-size:1.3rem;color:inherit;cursor:pointer;opacity:.7}
876
+ .hd-panel-close:hover{opacity:1}
877
+ .hd-view{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px 20px;text-align:center;color:var(--hd-txt);min-height:300px}
878
+ .hd-welcome-icon{font-size:2.5rem;margin-bottom:8px}
879
+ .hd-welcome-msg{font-size:.95rem;margin-bottom:6px}
880
+ .hd-welcome-hours{font-size:.78rem;color:var(--hd-txt3);margin-bottom:16px}
881
+ .hd-btn-enter{width:100%;padding:10px;background:var(--hd-acc);color:var(--hd-mine-fg);border:none;border-radius:8px;font-weight:700;font-size:.9rem;cursor:pointer}
882
+ .hd-btn-enter:hover{opacity:.85}
883
+ .hd-spinner{width:32px;height:32px;border:3px solid var(--hd-border);border-top-color:var(--hd-acc);border-radius:50%;animation:hd-spin .8s linear infinite;margin-bottom:12px}
884
+ @keyframes hd-spin{to{transform:rotate(360deg)}}
885
+ .hd-unavail-icon{font-size:2rem;margin-bottom:8px}
886
+ .hd-unavail-msg{font-size:.9rem;margin-bottom:4px}
887
+ .hd-unavail-sub{font-size:.78rem;color:var(--hd-txt3);margin-bottom:16px}
888
+ .hd-btn-retry,.hd-btn-back{width:100%;padding:8px;border-radius:6px;font-size:.82rem;font-weight:600;cursor:pointer;margin-bottom:6px}
889
+ .hd-btn-retry{background:var(--hd-acc);color:var(--hd-mine-fg);border:none}
890
+ .hd-btn-back{background:transparent;color:var(--hd-txt3);border:1px solid var(--hd-border)}
891
+ .hd-wait-msg{font-size:.9rem}
892
+ .hd-wait-pos{font-size:.82rem;color:var(--hd-txt2);margin-top:6px}
893
+ .hd-view-chat{padding:0;align-items:stretch;justify-content:flex-start}
894
+ .hd-chat-agent{padding:6px 14px;font-size:.75rem;color:var(--hd-txt3);background:var(--hd-bg2);border-bottom:1px solid var(--hd-border)}
895
+ .hd-chat-msgs{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:6px;min-height:200px;max-height:320px}
896
+ .hd-msg{max-width:85%}.hd-msg-mine{align-self:flex-end;text-align:right}.hd-msg-agent{align-self:flex-start}.hd-msg-sys-wrap{align-self:center}
897
+ .hd-msg-sys{font-size:.7rem;color:var(--hd-txt3);background:var(--hd-bg3);padding:2px 10px;border-radius:999px}
898
+ .hd-msg-bubble{display:inline-block;padding:8px 12px;border-radius:12px;font-size:.85rem;line-height:1.4;word-break:break-word}
899
+ .hd-msg-mine .hd-msg-bubble{background:var(--hd-mine);color:var(--hd-mine-fg);border-bottom-right-radius:4px}
900
+ .hd-msg-agent .hd-msg-bubble{background:var(--hd-agent-bg);color:var(--hd-agent-fg);border-bottom-left-radius:4px}
901
+ .hd-msg-time{font-size:.6rem;color:var(--hd-txt3);margin-top:2px;opacity:.6}
902
+ .hd-chat-input{display:flex;gap:6px;padding:8px 12px;border-top:1px solid var(--hd-border);background:var(--hd-bg2)}
903
+ .hd-chat-input input{flex:1;padding:7px 10px;border:1px solid var(--hd-border);border-radius:6px;font-size:.82rem;background:var(--hd-bg);color:var(--hd-txt);outline:none}
904
+ .hd-chat-input input:focus{border-color:var(--hd-acc)}
905
+ .hd-chat-input input::placeholder{color:var(--hd-txt3)}
906
+ .hd-btn-send{padding:7px 10px;background:var(--hd-acc);color:var(--hd-mine-fg);border:none;border-radius:6px;cursor:pointer;display:flex;align-items:center}
907
+ .hd-chat-ended{padding:12px;text-align:center;border-top:1px solid var(--hd-border);background:var(--hd-bg2)}
908
+ .hd-chat-ended p{font-size:.82rem;color:var(--hd-txt3);margin:0 0 8px}
909
+ .hd-btn-new{padding:6px 16px;background:var(--hd-acc-dim);color:var(--hd-acc);border:1px solid var(--hd-acc);border-radius:6px;font-size:.78rem;font-weight:600;cursor:pointer}
910
+ .hd-btn-new:hover{background:var(--hd-acc);color:var(--hd-mine-fg)}
911
+ `, document.head.appendChild(e);
912
+ }
913
+ const je = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
914
+ __proto__: null,
915
+ destroy: Ge,
916
+ getClient: Ke,
917
+ init: Oe
918
+ }, Symbol.toStringTag, { value: "Module" }));
919
+ export {
920
+ k as CLIENT_EVENTS,
921
+ he as DEFAULTS,
922
+ P as HelpdeskClient,
923
+ je as HelpdeskEmbed,
924
+ We as HelpdeskPlugin,
925
+ $e as HelpdeskWidget,
926
+ p as SERVER_EVENTS,
927
+ b as UI_EVENTS,
928
+ ue as resolveConfig
929
+ };
930
+ //# sourceMappingURL=helpdesk.es.js.map