@saiboniuma/chat-ui 0.1.0

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,1016 @@
1
+ import { isSystemMessage as v, MessageType as p } from "@saiboniuma/realtime-core";
2
+ class k {
3
+ constructor(e) {
4
+ this.renderers = [], this.inputTools = [], this.plugins = /* @__PURE__ */ new Map(), this.i18n = e;
5
+ }
6
+ /** 注册插件 */
7
+ register(e) {
8
+ if (this.plugins.has(e.name)) return;
9
+ const t = {
10
+ registerRenderer: (i) => {
11
+ this.renderers.push(i);
12
+ },
13
+ registerInputTool: (i) => {
14
+ this.inputTools.push(i), this.inputTools.sort((s, n) => (s.order ?? 0) - (n.order ?? 0));
15
+ },
16
+ i18n: this.i18n,
17
+ registerLocale: (i, s) => {
18
+ this.i18n.registerLocale(i, s);
19
+ }
20
+ };
21
+ e.install(t), this.plugins.set(e.name, e);
22
+ }
23
+ /** 注销插件 */
24
+ unregister(e) {
25
+ var i;
26
+ const t = this.plugins.get(e);
27
+ if (t) {
28
+ (i = t.uninstall) == null || i.call(t), this.plugins.delete(e), this.renderers = [], this.inputTools = [];
29
+ for (const s of this.plugins.values()) {
30
+ const n = {
31
+ registerRenderer: (o) => {
32
+ this.renderers.push(o);
33
+ },
34
+ registerInputTool: (o) => {
35
+ this.inputTools.push(o);
36
+ },
37
+ i18n: this.i18n,
38
+ registerLocale: (o, c) => {
39
+ this.i18n.registerLocale(o, c);
40
+ }
41
+ };
42
+ s.install(n);
43
+ }
44
+ this.inputTools.sort((s, n) => (s.order ?? 0) - (n.order ?? 0));
45
+ }
46
+ }
47
+ /** 根据消息查找合适的渲染器 */
48
+ getRenderer(e) {
49
+ var t;
50
+ for (const i of this.renderers)
51
+ if ((t = i.canRender) != null && t.call(i, e)) return i;
52
+ for (const i of this.renderers)
53
+ if (i.contentTypes.includes(Number(e.contentType))) return i;
54
+ return null;
55
+ }
56
+ /** 获取所有输入工具按钮 */
57
+ getInputTools() {
58
+ return this.inputTools;
59
+ }
60
+ /** 销毁所有插件 */
61
+ destroy() {
62
+ var e;
63
+ for (const t of this.plugins.values())
64
+ (e = t.uninstall) == null || e.call(t);
65
+ this.plugins.clear(), this.renderers = [], this.inputTools = [];
66
+ }
67
+ }
68
+ const E = {
69
+ "--chat-bg": "#f5f7fb",
70
+ "--chat-bubble-self-start": "#667eea",
71
+ "--chat-bubble-self-end": "#764ba2",
72
+ "--chat-bubble-other": "#ffffff",
73
+ "--chat-text-primary": "#333333",
74
+ "--chat-text-secondary": "#666666",
75
+ "--chat-text-self": "#ffffff",
76
+ "--chat-border": "#e8e8e8",
77
+ "--chat-input-bg": "#ffffff",
78
+ "--chat-avatar-bg": "#e8e8e8",
79
+ "--chat-avatar-self-start": "#667eea",
80
+ "--chat-avatar-self-end": "#764ba2",
81
+ "--chat-error": "#ff4d4f",
82
+ "--chat-accent-start": "#667eea",
83
+ "--chat-accent-end": "#764ba2"
84
+ }, T = {
85
+ "--chat-bg": "#1a1a2e",
86
+ "--chat-bubble-self-start": "#667eea",
87
+ "--chat-bubble-self-end": "#764ba2",
88
+ "--chat-bubble-other": "#2d2d44",
89
+ "--chat-text-primary": "#e0e0e0",
90
+ "--chat-text-secondary": "#999999",
91
+ "--chat-text-self": "#ffffff",
92
+ "--chat-border": "#3d3d5c",
93
+ "--chat-input-bg": "#2d2d44",
94
+ "--chat-avatar-bg": "#3d3d5c",
95
+ "--chat-avatar-self-start": "#667eea",
96
+ "--chat-avatar-self-end": "#764ba2",
97
+ "--chat-error": "#ff4d4f",
98
+ "--chat-accent-start": "#667eea",
99
+ "--chat-accent-end": "#764ba2"
100
+ };
101
+ class M {
102
+ constructor() {
103
+ this.currentTheme = "light", this.container = null;
104
+ }
105
+ /** 绑定容器元素 */
106
+ bind(e) {
107
+ this.container = e, this.applyTheme();
108
+ }
109
+ /** 设置主题 */
110
+ setTheme(e) {
111
+ this.currentTheme = e, this.applyTheme();
112
+ }
113
+ /** 获取当前主题 */
114
+ getTheme() {
115
+ return this.currentTheme;
116
+ }
117
+ /** 应用主题到容器 */
118
+ applyTheme() {
119
+ if (!this.container) return;
120
+ const e = this.currentTheme === "dark" ? T : E;
121
+ for (const [t, i] of Object.entries(e))
122
+ this.container.style.setProperty(t, i);
123
+ }
124
+ }
125
+ function r(a, e, t) {
126
+ const i = document.createElement(a);
127
+ if (e && (i.className = e), t)
128
+ for (const [s, n] of Object.entries(t))
129
+ i.setAttribute(s, n);
130
+ return i;
131
+ }
132
+ function V(a, e) {
133
+ a.textContent = e;
134
+ }
135
+ function u(a) {
136
+ for (; a.firstChild; )
137
+ a.removeChild(a.firstChild);
138
+ }
139
+ function Y(a, e, t) {
140
+ a.classList.toggle(e, t);
141
+ }
142
+ function S(a, e, t = "chat-ui-avatar") {
143
+ const i = r("div", t);
144
+ if (a) {
145
+ const s = r("img", "chat-ui-avatar-img");
146
+ s.src = a, s.alt = e, s.onerror = () => {
147
+ s.style.display = "none";
148
+ const n = y(e);
149
+ i.appendChild(n);
150
+ }, i.appendChild(s);
151
+ } else {
152
+ const s = y(e);
153
+ i.appendChild(s);
154
+ }
155
+ return i;
156
+ }
157
+ function y(a) {
158
+ const e = r("div", "chat-ui-avatar-fallback");
159
+ return e.textContent = L(a), e;
160
+ }
161
+ function L(a) {
162
+ return a ? a.charAt(0).toUpperCase() : "?";
163
+ }
164
+ function D(a, e) {
165
+ const t = new Date(a), i = /* @__PURE__ */ new Date(), s = i.getTime() - t.getTime(), n = Math.floor(s / 6e4);
166
+ if (n < 1)
167
+ return (e == null ? void 0 : e.t("time.just_now")) ?? "刚刚";
168
+ if (n < 60)
169
+ return (e == null ? void 0 : e.t("time.minutes_ago", { n })) ?? `${n}分钟前`;
170
+ if (t.toDateString() === i.toDateString())
171
+ return t.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" });
172
+ const o = new Date(i);
173
+ return o.setDate(o.getDate() - 1), t.toDateString() === o.toDateString() ? `${(e == null ? void 0 : e.t("time.yesterday")) ?? "昨天"} ${t.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" })}` : t.toLocaleString("zh-CN", {
174
+ month: "2-digit",
175
+ day: "2-digit",
176
+ hour: "2-digit",
177
+ minute: "2-digit"
178
+ });
179
+ }
180
+ function m(a) {
181
+ requestAnimationFrame(() => {
182
+ a.scrollTop = a.scrollHeight;
183
+ });
184
+ }
185
+ function j(a, e = 100) {
186
+ const { scrollTop: t, scrollHeight: i, clientHeight: s } = a;
187
+ return i - t - s < e;
188
+ }
189
+ class R {
190
+ constructor(e, t, i, s, n) {
191
+ this.currentUserID = "", this.messages = [], this.renderedMsgIds = /* @__PURE__ */ new Set(), this.container = e, this.pluginManager = t, this.i18n = i, this.shadowRoot = s, this.getSenderInfo = n;
192
+ }
193
+ /** 设置当前用户 ID */
194
+ setCurrentUserID(e) {
195
+ this.currentUserID = e;
196
+ }
197
+ /** 更新消息列表 */
198
+ updateMessages(e) {
199
+ const t = e.filter((n) => !v(n)), i = [], s = /* @__PURE__ */ new Set();
200
+ for (const n of t)
201
+ s.has(n.clientMsgID) || (s.add(n.clientMsgID), i.push(n));
202
+ this.messages = i, this.render();
203
+ }
204
+ /** 追加单条新消息 */
205
+ appendMessage(e) {
206
+ if (v(e)) {
207
+ console.log("[MessageListRenderer] appendMessage 跳过: 系统消息, clientMsgID:", e.clientMsgID);
208
+ return;
209
+ }
210
+ if (this.renderedMsgIds.has(e.clientMsgID)) {
211
+ console.log("[MessageListRenderer] appendMessage 跳过: 重复消息, clientMsgID:", e.clientMsgID);
212
+ return;
213
+ }
214
+ console.log("[MessageListRenderer] appendMessage 渲染消息, clientMsgID:", e.clientMsgID, "sendID:", e.sendID), this.messages.length === 0 && u(this.container), this.messages.push(e);
215
+ const t = j(this.container);
216
+ this.renderMessageItem(e), t && m(this.container);
217
+ }
218
+ /** 滚动到底部 */
219
+ scrollToBottom() {
220
+ m(this.container);
221
+ }
222
+ /** 完整渲染消息列表 */
223
+ render() {
224
+ if (u(this.container), this.renderedMsgIds.clear(), this.messages.length === 0) {
225
+ this.renderEmptyState();
226
+ return;
227
+ }
228
+ for (const e of this.messages)
229
+ this.renderMessageItem(e);
230
+ m(this.container);
231
+ }
232
+ /** 渲染空状态 */
233
+ renderEmptyState() {
234
+ const e = r("div", "chat-ui-empty"), t = r("p");
235
+ t.textContent = this.i18n.t("message.empty");
236
+ const i = r("p", "chat-ui-empty-hint");
237
+ i.textContent = this.i18n.t("message.emptyHint"), e.appendChild(t), e.appendChild(i), this.container.appendChild(e);
238
+ }
239
+ /** 渲染单条消息 */
240
+ renderMessageItem(e) {
241
+ this.renderedMsgIds.add(e.clientMsgID);
242
+ const t = e.sendID === this.currentUserID, i = this.getSenderInfo(e), s = r("div", `chat-ui-message-item${t ? " is-self" : ""}`), n = r("div", "chat-ui-message-header"), o = S(
243
+ i.avatar,
244
+ i.nickname,
245
+ `chat-ui-avatar${t ? " avatar-self" : ""}`
246
+ ), c = r("div", "chat-ui-message-nickname");
247
+ c.textContent = i.nickname, n.appendChild(o), n.appendChild(c), s.appendChild(n);
248
+ const d = r("div", "chat-ui-bubble-row"), g = {
249
+ isSelf: t,
250
+ currentUserID: this.currentUserID,
251
+ i18n: this.i18n,
252
+ shadowRoot: this.shadowRoot
253
+ }, b = this.pluginManager.getRenderer(e);
254
+ if (b) {
255
+ const l = b.render(e, g);
256
+ d.appendChild(l);
257
+ } else {
258
+ const l = r("div", "chat-ui-bubble");
259
+ l.textContent = this.i18n.t("message.unknownType"), d.appendChild(l);
260
+ }
261
+ s.appendChild(d);
262
+ const x = r("div", "chat-ui-message-time");
263
+ x.textContent = D(e.sendTime, this.i18n), s.appendChild(x), this.container.appendChild(s);
264
+ }
265
+ /** 刷新所有消息的 UI 文本(语言切换时调用) */
266
+ refreshTexts() {
267
+ this.render();
268
+ }
269
+ /** 清空 */
270
+ clear() {
271
+ this.messages = [], this.renderedMsgIds.clear(), u(this.container);
272
+ }
273
+ }
274
+ class A {
275
+ constructor(e, t, i, s, n) {
276
+ this.core = null, this.isSending = !1, this.isDisabled = !1, this.recvID = "", this.groupId = "", this.onAppendMessage = null, this.wrapper = e, this.pluginManager = t, this.i18n = i, this.shadowRoot = s, this.onSend = n, this.build();
277
+ }
278
+ /** 设置追加消息回调 */
279
+ setOnAppendMessage(e) {
280
+ this.onAppendMessage = e;
281
+ }
282
+ /** 绑定 core 实例 */
283
+ bindCore(e) {
284
+ this.core = e;
285
+ }
286
+ /** 设置接收者 ID */
287
+ setRecvID(e) {
288
+ this.recvID = e;
289
+ }
290
+ /** 设置群组 ID(广播模式) */
291
+ setGroupId(e) {
292
+ this.groupId = e;
293
+ }
294
+ /** 设置启用/禁用状态 */
295
+ setDisabled(e) {
296
+ this.isDisabled = e, this.inputElement.disabled = e, this.updateSendButton(), this.updateToolbarButtons();
297
+ }
298
+ /** 获取输入框元素 */
299
+ getInputElement() {
300
+ return this.inputElement;
301
+ }
302
+ /** 刷新工具栏 */
303
+ refreshToolbar() {
304
+ this.buildToolbar();
305
+ }
306
+ /** 刷新 UI 文本 */
307
+ refreshTexts() {
308
+ this.inputElement.placeholder = this.i18n.t("input.placeholder"), this.updateSendButton(), this.buildToolbar();
309
+ }
310
+ /** 构建输入区域 DOM */
311
+ build() {
312
+ this.wrapper.className = "chat-ui-input-wrapper", this.toolbarContainer = r("div", "chat-ui-toolbar"), this.wrapper.appendChild(this.toolbarContainer), this.inputArea = r("div", "chat-ui-input-area"), this.inputElement = r("input", "chat-ui-message-input"), this.inputElement.type = "text", this.inputElement.placeholder = this.i18n.t("input.placeholder"), this.inputElement.addEventListener("keydown", this.handleKeydown.bind(this)), this.inputElement.addEventListener("input", () => this.updateSendButton()), this.inputArea.appendChild(this.inputElement), this.sendButton = r("button", "chat-ui-send-btn"), this.sendButton.textContent = this.i18n.t("input.send"), this.sendButton.addEventListener("click", this.handleSend.bind(this)), this.inputArea.appendChild(this.sendButton), this.wrapper.appendChild(this.inputArea), this.buildToolbar(), this.updateSendButton();
313
+ }
314
+ /** 构建工具栏按钮 */
315
+ buildToolbar() {
316
+ for (; this.toolbarContainer.firstChild; )
317
+ this.toolbarContainer.removeChild(this.toolbarContainer.firstChild);
318
+ const e = this.pluginManager.getInputTools();
319
+ for (const t of e) {
320
+ const i = r("button", "chat-ui-tool-btn");
321
+ i.innerHTML = t.icon, i.title = t.tooltip, i.disabled = this.isDisabled, i.addEventListener("click", () => {
322
+ const s = {
323
+ inputElement: this.inputElement,
324
+ insertText: (n) => {
325
+ this.inputElement.value += n, this.inputElement.focus(), this.updateSendButton();
326
+ },
327
+ getInputText: () => this.inputElement.value,
328
+ setInputText: (n) => {
329
+ this.inputElement.value = n, this.updateSendButton();
330
+ },
331
+ getRecvID: () => this.groupId || this.recvID,
332
+ isGroup: () => !!this.groupId,
333
+ core: this.core,
334
+ appendMessage: (n) => {
335
+ this.onAppendMessage && this.onAppendMessage(n);
336
+ },
337
+ shadowRoot: this.shadowRoot,
338
+ i18n: this.i18n
339
+ };
340
+ t.onActivate(s);
341
+ }), this.toolbarContainer.appendChild(i);
342
+ }
343
+ }
344
+ /** 键盘事件处理 */
345
+ handleKeydown(e) {
346
+ e.key === "Enter" && !e.shiftKey && (e.preventDefault(), this.handleSend());
347
+ }
348
+ /** 发送消息 */
349
+ async handleSend() {
350
+ const e = this.inputElement.value.trim();
351
+ if (!(!e || this.isSending || this.isDisabled)) {
352
+ this.isSending = !0, this.updateSendButton();
353
+ try {
354
+ await this.onSend(e), this.inputElement.value = "";
355
+ } finally {
356
+ this.isSending = !1, this.updateSendButton();
357
+ }
358
+ }
359
+ }
360
+ /** 更新工具栏按钮禁用状态 */
361
+ updateToolbarButtons() {
362
+ this.toolbarContainer.querySelectorAll("button").forEach((t) => {
363
+ t.disabled = this.isDisabled;
364
+ });
365
+ }
366
+ /** 更新发送按钮状态 */
367
+ updateSendButton() {
368
+ const e = this.inputElement.value.trim().length > 0;
369
+ this.sendButton.disabled = !e || this.isDisabled || this.isSending, this.sendButton.textContent = this.isSending ? this.i18n.t("input.sending") : this.i18n.t("input.send");
370
+ }
371
+ }
372
+ const z = {
373
+ "input.placeholder": "请输入消息...",
374
+ "input.send": "发送",
375
+ "input.sending": "发送中",
376
+ "message.image": "[图片]",
377
+ "message.loading": "加载中...",
378
+ "message.noMore": "没有更多消息了",
379
+ "message.failed": "发送失败",
380
+ "message.retry": "重试",
381
+ "message.empty": "暂无消息",
382
+ "message.emptyHint": "发送消息开始对话",
383
+ "message.unknownType": "[未知消息类型]",
384
+ "message.imageError": "[图片加载失败]",
385
+ "emoji.title": "表情",
386
+ "emoji.categories.recent": "常用",
387
+ "emoji.categories.emotion": "情绪",
388
+ "emoji.categories.gesture": "手势",
389
+ "emoji.categories.heart": "心形",
390
+ "emoji.categories.other": "其他",
391
+ "emoji.close": "关闭",
392
+ "image.upload": "发送图片",
393
+ "image.preview": "查看大图",
394
+ "image.uploading": "上传中...",
395
+ "status.connecting": "正在连接服务器...",
396
+ "status.connected": "已连接",
397
+ "status.disconnected": "连接失败,请检查网络后重试",
398
+ "status.reconnect": "重新连接",
399
+ "time.just_now": "刚刚",
400
+ "time.minutes_ago": "{n}分钟前",
401
+ "time.today": "今天",
402
+ "time.yesterday": "昨天",
403
+ "common.cancel": "取消",
404
+ "common.confirm": "确认",
405
+ "common.close": "关闭",
406
+ "widget.title": "在线客服"
407
+ }, B = {
408
+ "input.placeholder": "Type a message...",
409
+ "input.send": "Send",
410
+ "input.sending": "Sending",
411
+ "message.image": "[Image]",
412
+ "message.loading": "Loading...",
413
+ "message.noMore": "No more messages",
414
+ "message.failed": "Failed to send",
415
+ "message.retry": "Retry",
416
+ "message.empty": "No messages yet",
417
+ "message.emptyHint": "Send a message to start",
418
+ "message.unknownType": "[Unknown message type]",
419
+ "message.imageError": "[Image load failed]",
420
+ "emoji.title": "Emoji",
421
+ "emoji.categories.recent": "Frequent",
422
+ "emoji.categories.emotion": "Emotion",
423
+ "emoji.categories.gesture": "Gesture",
424
+ "emoji.categories.heart": "Heart",
425
+ "emoji.categories.other": "Other",
426
+ "emoji.close": "Close",
427
+ "image.upload": "Send Image",
428
+ "image.preview": "Preview",
429
+ "image.uploading": "Uploading...",
430
+ "status.connecting": "Connecting...",
431
+ "status.connected": "Connected",
432
+ "status.disconnected": "Connection failed, please check network",
433
+ "status.reconnect": "Reconnect",
434
+ "time.just_now": "Just now",
435
+ "time.minutes_ago": "{n} min ago",
436
+ "time.today": "Today",
437
+ "time.yesterday": "Yesterday",
438
+ "common.cancel": "Cancel",
439
+ "common.confirm": "Confirm",
440
+ "common.close": "Close",
441
+ "widget.title": "Customer Service"
442
+ };
443
+ class U {
444
+ constructor(e) {
445
+ this.currentLocale = "zh-CN", this.messages = /* @__PURE__ */ new Map(), this.changeListeners = /* @__PURE__ */ new Set(), this.messages.set("zh-CN", { ...z }), this.messages.set("en-US", { ...B }), e && (this.currentLocale = e);
446
+ }
447
+ /** 翻译文本 */
448
+ t(e, t) {
449
+ var n;
450
+ const i = this.messages.get(this.currentLocale);
451
+ let s = (i == null ? void 0 : i[e]) ?? ((n = this.messages.get("zh-CN")) == null ? void 0 : n[e]) ?? e;
452
+ if (t)
453
+ for (const [o, c] of Object.entries(t))
454
+ s = s.replace(`{${o}}`, String(c));
455
+ return s;
456
+ }
457
+ /** 获取当前语言 */
458
+ getLocale() {
459
+ return this.currentLocale;
460
+ }
461
+ /** 切换语言(触发所有监听器刷新 UI) */
462
+ setLocale(e) {
463
+ this.currentLocale !== e && (this.currentLocale = e, this.changeListeners.forEach((t) => t()));
464
+ }
465
+ /** 注册语言包(可用于插件扩展) */
466
+ registerLocale(e, t) {
467
+ const i = this.messages.get(e) || {};
468
+ this.messages.set(e, { ...i, ...t });
469
+ }
470
+ /** 监听语言切换 */
471
+ onChange(e) {
472
+ this.changeListeners.add(e);
473
+ }
474
+ /** 移除语言切换监听 */
475
+ offChange(e) {
476
+ this.changeListeners.delete(e);
477
+ }
478
+ /** 销毁 */
479
+ destroy() {
480
+ this.changeListeners.clear();
481
+ }
482
+ }
483
+ const N = ":host{--chat-bg: #f5f7fb;--chat-bubble-self-start: #667eea;--chat-bubble-self-end: #764ba2;--chat-bubble-other: #ffffff;--chat-text-primary: #333333;--chat-text-secondary: #666666;--chat-text-self: #ffffff;--chat-border: #e8e8e8;--chat-input-bg: #ffffff;--chat-avatar-bg: #e8e8e8;--chat-avatar-self-start: #667eea;--chat-avatar-self-end: #764ba2;--chat-error: #ff4d4f;--chat-accent-start: #667eea;--chat-accent-end: #764ba2;display:block;height:100%;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;line-height:1.5;color:var(--chat-text-primary);box-sizing:border-box}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}.chat-ui-root{position:relative;display:flex;flex-direction:column;height:100%;background:var(--chat-bg);overflow:hidden}.chat-ui-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:#f5f7fbfa;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;z-index:10}.chat-ui-spinner{width:36px;height:36px;border:3px solid var(--chat-border);border-top-color:var(--chat-accent-start);border-radius:50%;animation:chat-ui-spin .8s linear infinite}.chat-ui-overlay-text{font-size:14px;color:var(--chat-text-secondary)}.chat-ui-error-icon{width:48px;height:48px;border-radius:50%;background:var(--chat-error);color:#fff;font-size:24px;font-weight:700;display:flex;align-items:center;justify-content:center}.chat-ui-reconnect-btn{margin-top:4px;padding:8px 24px;border:none;border-radius:20px;background:linear-gradient(135deg,var(--chat-accent-start) 0%,var(--chat-accent-end) 100%);color:#fff;font-size:14px;cursor:pointer;transition:opacity .2s}.chat-ui-reconnect-btn:hover{opacity:.9}@keyframes chat-ui-spin{to{transform:rotate(360deg)}}", P = ".chat-ui-message-list{flex:1;overflow-y:auto;padding:16px}.chat-ui-empty{text-align:center;color:var(--chat-text-secondary);padding-top:60px}.chat-ui-empty p{margin:8px 0}.chat-ui-empty-hint{font-size:12px}.chat-ui-message-item{display:flex;flex-direction:column;margin-bottom:16px;align-items:flex-start}.chat-ui-message-item.is-self{align-items:flex-end}.chat-ui-message-header{display:flex;align-items:center;gap:8px;margin-bottom:6px}.chat-ui-message-item.is-self .chat-ui-message-header{flex-direction:row-reverse}.chat-ui-avatar{width:28px;height:28px;border-radius:50%;flex-shrink:0;overflow:hidden;background:var(--chat-avatar-bg);display:flex;align-items:center;justify-content:center}.chat-ui-avatar.avatar-self{background:linear-gradient(135deg,var(--chat-avatar-self-start) 0%,var(--chat-avatar-self-end) 100%)}.chat-ui-avatar-img{width:100%;height:100%;object-fit:cover}.chat-ui-avatar-fallback{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;color:#fff;background:#999}.chat-ui-avatar.avatar-self .chat-ui-avatar-fallback{background:transparent}.chat-ui-message-nickname{font-size:12px;color:var(--chat-text-secondary)}.chat-ui-bubble-row{width:100%;display:flex}.chat-ui-message-item.is-self .chat-ui-bubble-row{justify-content:flex-end}.chat-ui-bubble{padding:10px 14px;border-radius:12px;background:var(--chat-bubble-other);color:var(--chat-text-primary);word-wrap:break-word;box-shadow:0 1px 2px #0000000d;max-width:85%}.chat-ui-message-item.is-self .chat-ui-bubble{background:linear-gradient(135deg,var(--chat-bubble-self-start) 0%,var(--chat-bubble-self-end) 100%);color:var(--chat-text-self)}.chat-ui-message-time{font-size:11px;color:var(--chat-text-secondary);margin-top:4px;padding:0 4px}.chat-ui-message-item.is-self .chat-ui-message-time{text-align:right;align-self:flex-end}", O = ".chat-ui-input-wrapper{position:relative;background:var(--chat-input-bg);border-top:1px solid var(--chat-border)}.chat-ui-toolbar{display:flex;align-items:center;gap:2px;padding:6px 10px 0}.chat-ui-tool-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border:none;background:transparent;color:var(--chat-text-secondary);cursor:pointer;border-radius:6px;transition:all .2s;flex-shrink:0;font-size:18px}.chat-ui-tool-btn:hover:not(:disabled){background:#0000000d;color:var(--chat-accent-start)}.chat-ui-tool-btn.active{background:#0000000d;color:var(--chat-accent-start)}.chat-ui-tool-btn:disabled{opacity:.4;cursor:not-allowed}.chat-ui-hidden-input{display:none}.chat-ui-input-area{display:flex;align-items:center;gap:8px;padding:6px 10px 10px;box-sizing:border-box;width:100%}.chat-ui-message-input{flex:1;padding:10px 16px;border:1px solid var(--chat-border);border-radius:20px;font-size:14px;outline:none;transition:border-color .2s;background-color:var(--chat-input-bg);color:var(--chat-text-primary);font-family:inherit}.chat-ui-message-input:focus{border-color:var(--chat-accent-start)}.chat-ui-message-input:disabled{background:#0000000d;cursor:not-allowed}.chat-ui-send-btn{padding:10px 20px;background:linear-gradient(135deg,var(--chat-accent-start) 0%,var(--chat-accent-end) 100%);color:#fff;border:none;border-radius:20px;font-size:14px;cursor:pointer;transition:all .2s;white-space:nowrap;flex-shrink:0;font-family:inherit;font-weight:500}.chat-ui-send-btn:hover:not(:disabled){opacity:.9;box-shadow:0 2px 8px #667eea66}.chat-ui-send-btn:disabled{background:#d1d5db;color:#9ca3af;cursor:not-allowed;box-shadow:none}@media(max-width:480px){.chat-ui-toolbar{padding:4px 8px 0}.chat-ui-tool-btn{width:28px;height:28px;font-size:16px}.chat-ui-input-area{gap:6px;padding:4px 8px 8px}.chat-ui-message-input{padding:8px 12px;font-size:16px}.chat-ui-send-btn{padding:8px 14px;font-size:14px}}", G = ".chat-ui-message-image{padding:4px!important;background:transparent!important;box-shadow:none!important}.chat-ui-message-item.is-self .chat-ui-message-image{background:transparent!important}.chat-ui-chat-image{max-width:200px;max-height:200px;border-radius:8px;cursor:pointer;transition:transform .2s;object-fit:contain}.chat-ui-chat-image:hover{transform:scale(1.02)}.chat-ui-image-error{color:var(--chat-text-secondary);font-size:12px;padding:20px;display:block;background:#0000000d;border-radius:8px}.chat-ui-image-preview-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#000000d9;display:flex;align-items:center;justify-content:center;z-index:1000}.chat-ui-image-preview-container{position:relative;max-width:90vw;max-height:90vh}.chat-ui-preview-image{max-width:90vw;max-height:90vh;object-fit:contain;border-radius:4px}.chat-ui-close-preview-btn{position:absolute;top:-40px;right:-40px;width:36px;height:36px;border:none;background:#fff3;color:#fff;font-size:24px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s}.chat-ui-close-preview-btn:hover{background:#ffffff4d}", H = ".chat-ui-emoji-picker{position:absolute;bottom:100%;left:0;right:0;background:var(--chat-input-bg);border:1px solid var(--chat-border);border-radius:12px 12px 0 0;box-shadow:0 -4px 12px #0000001a;padding:12px;z-index:100}.chat-ui-emoji-categories{display:flex;gap:8px;margin-bottom:12px;border-bottom:1px solid var(--chat-border);padding-bottom:8px}.chat-ui-category-btn{padding:4px 12px;border:none;background:#0000000d;border-radius:16px;font-size:12px;color:var(--chat-text-secondary);cursor:pointer;transition:all .2s;font-family:inherit}.chat-ui-category-btn:hover{background:#0000001a}.chat-ui-category-btn.active{background:linear-gradient(135deg,var(--chat-accent-start) 0%,var(--chat-accent-end) 100%);color:#fff}.chat-ui-emoji-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:4px;max-height:200px;overflow-y:auto}.chat-ui-emoji-btn{width:36px;height:36px;display:flex;align-items:center;justify-content:center;border:none;background:transparent;font-size:22px;cursor:pointer;border-radius:8px;transition:background .2s}.chat-ui-emoji-btn:hover{background:#0000000d}@media(max-width:480px){.chat-ui-emoji-grid{grid-template-columns:repeat(6,1fr)}.chat-ui-emoji-btn{width:44px;height:44px;font-size:24px}}";
484
+ class Q {
485
+ constructor(e, t = {}) {
486
+ this.core = null, this.currentUserID = "", this.userInfo = { nickname: "访客", avatar: "" }, this.agentInfo = { nickname: "客服", avatar: "" }, this.agentUserID = "", this.groupId = "", this.host = r("div", "chat-ui-host"), this.shadowRoot = this.host.attachShadow({ mode: "open" }), e.appendChild(this.host);
487
+ const i = document.createElement("style");
488
+ if (i.textContent = [N, P, O, G, H].join(`
489
+ `), this.shadowRoot.appendChild(i), this.root = r("div", `chat-ui-root${t.fullPage ? " full-page" : ""}`), this.shadowRoot.appendChild(this.root), this.i18n = new U(t.locale), this.themeManager = new M(), this.themeManager.bind(this.root), t.theme && this.themeManager.setTheme(t.theme), this.pluginManager = new k(this.i18n), this.messageListEl = r("div", "chat-ui-message-list"), this.root.appendChild(this.messageListEl), this.messageListRenderer = new R(
490
+ this.messageListEl,
491
+ this.pluginManager,
492
+ this.i18n,
493
+ this.shadowRoot,
494
+ this.getSenderInfo.bind(this)
495
+ ), this.inputWrapperEl = r("div"), this.root.appendChild(this.inputWrapperEl), this.inputArea = new A(
496
+ this.inputWrapperEl,
497
+ this.pluginManager,
498
+ this.i18n,
499
+ this.shadowRoot,
500
+ this.handleSend.bind(this)
501
+ ), this.inputArea.setOnAppendMessage((s) => {
502
+ this.messageListRenderer.appendMessage(s), this.messageListRenderer.scrollToBottom();
503
+ }), this.inputArea.setDisabled(!0), t.plugins) {
504
+ for (const s of t.plugins)
505
+ this.pluginManager.register(s);
506
+ this.inputArea.refreshToolbar();
507
+ }
508
+ this.i18n.onChange(() => {
509
+ this.messageListRenderer.refreshTexts(), this.inputArea.refreshTexts();
510
+ });
511
+ }
512
+ /** 绑定 RealtimeClient 核心实例 */
513
+ bindCore(e) {
514
+ this.core = e, this.inputArea.bindCore(e), e.on("message:received", (t) => {
515
+ console.log("[ChatUI] message:received 回调触发, clientMsgID:", t.clientMsgID, "sendID:", t.sendID), this.messageListRenderer.appendMessage(t);
516
+ }), e.on("connection:success", () => {
517
+ this.inputArea.setDisabled(!1), this.hideOverlay();
518
+ }), e.on("connection:failed", () => {
519
+ this.inputArea.setDisabled(!0), this.showErrorOverlay();
520
+ }), e.on("connection:connecting", () => {
521
+ this.showLoadingOverlay();
522
+ });
523
+ }
524
+ /** 设置当前用户信息(用于消息渲染) */
525
+ setUserInfo(e, t, i) {
526
+ this.currentUserID = e, this.userInfo = { nickname: t, avatar: i }, this.messageListRenderer.setCurrentUserID(e);
527
+ }
528
+ /** 设置客服信息(用于消息渲染) */
529
+ setAgentInfo(e, t, i) {
530
+ this.agentUserID = e, this.agentInfo = { nickname: t, avatar: i }, this.inputArea.setRecvID(e);
531
+ }
532
+ /** 设置群组 ID(广播模式下消息发送到群组) */
533
+ setGroupId(e) {
534
+ this.groupId = e, this.inputArea.setGroupId(e);
535
+ }
536
+ /** 加载并渲染消息列表 */
537
+ setMessages(e) {
538
+ this.messageListRenderer.updateMessages(e);
539
+ }
540
+ /** 追加单条消息 */
541
+ appendMessage(e) {
542
+ this.messageListRenderer.appendMessage(e);
543
+ }
544
+ /** 启用/禁用输入 */
545
+ setInputEnabled(e) {
546
+ this.inputArea.setDisabled(!e);
547
+ }
548
+ /** 滚动到底部 */
549
+ scrollToBottom() {
550
+ this.messageListRenderer.scrollToBottom();
551
+ }
552
+ /** 注册插件 */
553
+ registerPlugin(e) {
554
+ this.pluginManager.register(e), this.inputArea.refreshToolbar();
555
+ }
556
+ /** 注销插件 */
557
+ unregisterPlugin(e) {
558
+ this.pluginManager.unregister(e), this.inputArea.refreshToolbar();
559
+ }
560
+ /** 切换语言 */
561
+ setLocale(e) {
562
+ this.i18n.setLocale(e);
563
+ }
564
+ /** 切换主题 */
565
+ setTheme(e) {
566
+ this.themeManager.setTheme(e);
567
+ }
568
+ /** 获取 i18n 实例(供插件使用) */
569
+ getI18n() {
570
+ return this.i18n;
571
+ }
572
+ /** 获取 Shadow Root */
573
+ getShadowRoot() {
574
+ return this.shadowRoot;
575
+ }
576
+ /** 销毁 */
577
+ destroy() {
578
+ this.pluginManager.destroy(), this.i18n.destroy(), this.messageListRenderer.clear(), this.host.remove();
579
+ }
580
+ /** 获取发送者信息 */
581
+ getSenderInfo(e) {
582
+ return e.sendID === this.currentUserID ? this.userInfo : this.agentInfo;
583
+ }
584
+ /** 发送消息回调 */
585
+ async handleSend(e) {
586
+ if (!this.core || !this.agentUserID) return;
587
+ const t = !!this.groupId, i = t ? this.groupId : this.agentUserID;
588
+ try {
589
+ const s = await this.core.message.sendText(i, e, t);
590
+ s && (this.messageListRenderer.appendMessage(s), this.messageListRenderer.scrollToBottom());
591
+ } catch (s) {
592
+ console.error("[ChatUI] 发送消息失败:", s), this.inputArea.setDisabled(!1);
593
+ }
594
+ }
595
+ /** 显示加载中覆盖层 */
596
+ showLoadingOverlay() {
597
+ this.hideOverlay();
598
+ const e = r("div", "chat-ui-overlay");
599
+ e.setAttribute("data-type", "loading");
600
+ const t = r("div", "chat-ui-spinner"), i = r("span", "chat-ui-overlay-text");
601
+ i.textContent = this.i18n.t("status.connecting"), e.appendChild(t), e.appendChild(i), this.root.appendChild(e);
602
+ }
603
+ /** 显示错误覆盖层 */
604
+ showErrorOverlay() {
605
+ this.hideOverlay();
606
+ const e = r("div", "chat-ui-overlay");
607
+ e.setAttribute("data-type", "error");
608
+ const t = r("div", "chat-ui-error-icon");
609
+ t.textContent = "!";
610
+ const i = r("span", "chat-ui-overlay-text");
611
+ i.textContent = this.i18n.t("status.disconnected");
612
+ const s = r("button", "chat-ui-reconnect-btn");
613
+ s.textContent = this.i18n.t("status.reconnect"), s.addEventListener("click", () => {
614
+ this.host.dispatchEvent(new CustomEvent("reconnect"));
615
+ }), e.appendChild(t), e.appendChild(i), e.appendChild(s), this.root.appendChild(e);
616
+ }
617
+ /** 隐藏覆盖层 */
618
+ hideOverlay() {
619
+ const e = this.root.querySelector(".chat-ui-overlay");
620
+ e && e.remove();
621
+ }
622
+ }
623
+ class K {
624
+ constructor() {
625
+ this.contentTypes = [p.TextMessage];
626
+ }
627
+ render(e, t) {
628
+ var s;
629
+ const i = r("div", "chat-ui-bubble");
630
+ return i.textContent = ((s = e.textElem) == null ? void 0 : s.content) || e.content || t.i18n.t("message.unknownType"), i;
631
+ }
632
+ }
633
+ class X {
634
+ constructor() {
635
+ this.name = "text", this.version = "0.1.0";
636
+ }
637
+ install(e) {
638
+ e.registerRenderer(new K());
639
+ }
640
+ }
641
+ const h = class h {
642
+ /** 显示图片预览 */
643
+ static show(e, t) {
644
+ h.close(t);
645
+ const i = r("div", "chat-ui-image-preview-overlay");
646
+ i.addEventListener("click", () => h.close(t));
647
+ const s = r("div", "chat-ui-image-preview-container");
648
+ s.addEventListener("click", (c) => c.stopPropagation());
649
+ const n = r("img", "chat-ui-preview-image");
650
+ n.src = e;
651
+ const o = r("button", "chat-ui-close-preview-btn");
652
+ o.innerHTML = "&times;", o.addEventListener("click", () => h.close(t)), s.appendChild(n), s.appendChild(o), i.appendChild(s), t.appendChild(i), h.overlay = i;
653
+ }
654
+ /** 关闭图片预览 */
655
+ static close(e) {
656
+ h.overlay && h.overlay.parentNode === e && (e.removeChild(h.overlay), h.overlay = null);
657
+ }
658
+ };
659
+ h.overlay = null;
660
+ let f = h;
661
+ function I(a) {
662
+ var e;
663
+ if (Number(a.contentType) !== p.CustomMessage || !((e = a.customElem) != null && e.data)) return null;
664
+ try {
665
+ const t = JSON.parse(a.customElem.data);
666
+ if (t.type === "image" && t.url) return t;
667
+ } catch {
668
+ }
669
+ return null;
670
+ }
671
+ function w(a, e = !0) {
672
+ var i, s, n;
673
+ if (a.pictureElem) {
674
+ if (e && ((i = a.pictureElem.snapshotPicture) != null && i.url)) return a.pictureElem.snapshotPicture.url;
675
+ if ((s = a.pictureElem.bigPicture) != null && s.url) return a.pictureElem.bigPicture.url;
676
+ if ((n = a.pictureElem.sourcePicture) != null && n.url) return a.pictureElem.sourcePicture.url;
677
+ }
678
+ const t = I(a);
679
+ return t ? t.url : "";
680
+ }
681
+ class $ {
682
+ constructor() {
683
+ this.contentTypes = [p.PictureMessage];
684
+ }
685
+ /** 同时匹配自定义图片消息 */
686
+ canRender(e) {
687
+ return Number(e.contentType) === p.PictureMessage ? !0 : I(e) !== null;
688
+ }
689
+ render(e, t) {
690
+ const i = r("div", "chat-ui-bubble chat-ui-message-image"), s = w(e);
691
+ if (s) {
692
+ const n = r("img", "chat-ui-chat-image");
693
+ n.src = s, n.setAttribute("referrerpolicy", "no-referrer"), n.addEventListener("click", () => {
694
+ const o = w(e, !1);
695
+ o && f.show(o, t.shadowRoot);
696
+ }), n.onerror = () => {
697
+ n.style.display = "none";
698
+ const o = r("span", "chat-ui-image-error");
699
+ o.textContent = t.i18n.t("message.imageError"), i.appendChild(o);
700
+ }, i.appendChild(n);
701
+ } else {
702
+ const n = r("span", "chat-ui-image-error");
703
+ n.textContent = t.i18n.t("message.imageError"), i.appendChild(n);
704
+ }
705
+ return i;
706
+ }
707
+ }
708
+ class Z {
709
+ constructor() {
710
+ this.name = "image", this.version = "0.1.0";
711
+ }
712
+ install(e) {
713
+ e.registerRenderer(new $()), e.registerInputTool({
714
+ name: "image-upload",
715
+ icon: "🖼",
716
+ tooltip: e.i18n.t("image.upload"),
717
+ order: 10,
718
+ onActivate(t) {
719
+ const i = document.createElement("input");
720
+ i.type = "file", i.accept = "image/*", i.style.display = "none", i.addEventListener("change", async () => {
721
+ var o;
722
+ const s = (o = i.files) == null ? void 0 : o[0];
723
+ if (!s || !s.type.startsWith("image/") || s.size > 10 * 1024 * 1024) return;
724
+ const n = t.getRecvID();
725
+ if (!(!n || !t.core)) {
726
+ try {
727
+ const c = await F(s), d = await t.core.upload.uploadFile(s), g = await t.core.message.sendImage(n, s, d.absfileName, {
728
+ width: c.width,
729
+ height: c.height,
730
+ size: s.size,
731
+ type: s.type
732
+ }, t.isGroup());
733
+ t.appendMessage(g);
734
+ } catch (c) {
735
+ console.error("[ImagePlugin] 发送图片失败:", c);
736
+ }
737
+ i.remove();
738
+ }
739
+ }), document.body.appendChild(i), i.click();
740
+ }
741
+ });
742
+ }
743
+ }
744
+ function F(a) {
745
+ return new Promise((e, t) => {
746
+ const i = new Image();
747
+ i.onload = () => {
748
+ e({ width: i.width, height: i.height }), URL.revokeObjectURL(i.src);
749
+ }, i.onerror = () => {
750
+ t(new Error("无法读取图片尺寸")), URL.revokeObjectURL(i.src);
751
+ }, i.src = URL.createObjectURL(a);
752
+ });
753
+ }
754
+ const W = [
755
+ "😀",
756
+ "😃",
757
+ "😄",
758
+ "😁",
759
+ "😆",
760
+ "😅",
761
+ "🤣",
762
+ "😂",
763
+ "🙂",
764
+ "😊",
765
+ "😇",
766
+ "🥰",
767
+ "😍",
768
+ "🤩",
769
+ "😘",
770
+ "😗",
771
+ "😚",
772
+ "😋",
773
+ "😛",
774
+ "😜",
775
+ "🤪",
776
+ "😝",
777
+ "🤑",
778
+ "🤗",
779
+ "🤭",
780
+ "🤫",
781
+ "🤔",
782
+ "🤐",
783
+ "🤨",
784
+ "😐",
785
+ "😑",
786
+ "😶",
787
+ "😏",
788
+ "😒",
789
+ "🙄",
790
+ "😬",
791
+ "😮‍💨",
792
+ "🤥",
793
+ "😌",
794
+ "😔",
795
+ "😪",
796
+ "🤤",
797
+ "😴",
798
+ "😷",
799
+ "🤒",
800
+ "🤕",
801
+ "🤢",
802
+ "🤮",
803
+ "🤧",
804
+ "🥵",
805
+ "🥶",
806
+ "🥴",
807
+ "😵",
808
+ "🤯",
809
+ "🤠",
810
+ "🥳",
811
+ "😎",
812
+ "🤓",
813
+ "🧐",
814
+ "😕",
815
+ "😟",
816
+ "🙁",
817
+ "☹️",
818
+ "😮",
819
+ "😯",
820
+ "😲",
821
+ "😳",
822
+ "🥺",
823
+ "😦",
824
+ "😧",
825
+ "😨",
826
+ "😰",
827
+ "😥",
828
+ "😢",
829
+ "😭",
830
+ "😱",
831
+ "😖",
832
+ "😣",
833
+ "😞",
834
+ "😓",
835
+ "👍",
836
+ "👎",
837
+ "👌",
838
+ "🤌",
839
+ "🤏",
840
+ "✌️",
841
+ "🤞",
842
+ "🤟",
843
+ "🤘",
844
+ "🤙",
845
+ "👈",
846
+ "👉",
847
+ "👆",
848
+ "👇",
849
+ "☝️",
850
+ "👋",
851
+ "🤚",
852
+ "🖐️",
853
+ "✋",
854
+ "🖖",
855
+ "👏",
856
+ "🙌",
857
+ "🤝",
858
+ "🙏",
859
+ "❤️",
860
+ "🧡",
861
+ "💛",
862
+ "💚",
863
+ "💙",
864
+ "💜",
865
+ "🖤",
866
+ "🤍",
867
+ "💔",
868
+ "❣️",
869
+ "💕",
870
+ "💞",
871
+ "💓",
872
+ "💗",
873
+ "💖",
874
+ "💘",
875
+ "⭐",
876
+ "✨",
877
+ "🌟",
878
+ "💫",
879
+ "🔥",
880
+ "💯",
881
+ "✅",
882
+ "❌",
883
+ "❓",
884
+ "❗",
885
+ "💤",
886
+ "💬",
887
+ "👀",
888
+ "🎉",
889
+ "🎊",
890
+ "🎁"
891
+ ], C = [
892
+ { nameKey: "emoji.categories.recent", start: 0, end: 32 },
893
+ { nameKey: "emoji.categories.emotion", start: 32, end: 56 },
894
+ { nameKey: "emoji.categories.gesture", start: 56, end: 80 },
895
+ { nameKey: "emoji.categories.heart", start: 80, end: 96 },
896
+ { nameKey: "emoji.categories.other", start: 96, end: 112 }
897
+ ];
898
+ class q {
899
+ constructor(e, t, i, s) {
900
+ this.activeCategory = 0, this.visible = !1, this.pickerEl = null, this.gridEl = null, this.categoryBtns = [], this.container = e, this.i18n = t, this.onSelect = i, this.onClose = s;
901
+ }
902
+ /** 显示/隐藏表情面板 */
903
+ toggle() {
904
+ this.visible ? this.hide() : this.show();
905
+ }
906
+ /** 显示面板 */
907
+ show() {
908
+ this.visible || (this.visible = !0, this.build());
909
+ }
910
+ /** 隐藏面板 */
911
+ hide() {
912
+ this.visible && (this.visible = !1, this.pickerEl && (this.pickerEl.remove(), this.pickerEl = null), this.onClose());
913
+ }
914
+ /** 是否可见 */
915
+ isVisible() {
916
+ return this.visible;
917
+ }
918
+ /** 刷新文本(语言切换时) */
919
+ refreshTexts() {
920
+ this.visible && this.hide();
921
+ }
922
+ /** 构建面板 DOM */
923
+ build() {
924
+ this.pickerEl = r("div", "chat-ui-emoji-picker");
925
+ const e = r("div", "chat-ui-emoji-categories");
926
+ this.categoryBtns = [], C.forEach((t, i) => {
927
+ const s = r("button", `chat-ui-category-btn${i === this.activeCategory ? " active" : ""}`);
928
+ s.textContent = this.i18n.t(t.nameKey), s.addEventListener("click", () => {
929
+ this.activeCategory = i, this.updateCategoryButtons(), this.renderGrid();
930
+ }), this.categoryBtns.push(s), e.appendChild(s);
931
+ }), this.pickerEl.appendChild(e), this.gridEl = r("div", "chat-ui-emoji-grid"), this.renderGrid(), this.pickerEl.appendChild(this.gridEl), this.container.insertBefore(this.pickerEl, this.container.firstChild);
932
+ }
933
+ /** 渲染当前分类的 Emoji 网格 */
934
+ renderGrid() {
935
+ if (!this.gridEl) return;
936
+ u(this.gridEl);
937
+ const e = C[this.activeCategory], t = W.slice(e.start, e.end);
938
+ for (const i of t) {
939
+ const s = r("button", "chat-ui-emoji-btn");
940
+ s.textContent = i, s.addEventListener("click", () => {
941
+ this.onSelect(i), this.hide();
942
+ }), this.gridEl.appendChild(s);
943
+ }
944
+ }
945
+ /** 更新分类按钮高亮 */
946
+ updateCategoryButtons() {
947
+ this.categoryBtns.forEach((e, t) => {
948
+ e.className = `chat-ui-category-btn${t === this.activeCategory ? " active" : ""}`;
949
+ });
950
+ }
951
+ /** 销毁 */
952
+ destroy() {
953
+ this.hide();
954
+ }
955
+ }
956
+ class _ {
957
+ constructor() {
958
+ this.name = "emoji", this.version = "0.1.0", this.picker = null;
959
+ }
960
+ install(e) {
961
+ e.registerInputTool({
962
+ name: "emoji",
963
+ icon: "😀",
964
+ tooltip: e.i18n.t("emoji.title"),
965
+ order: 0,
966
+ onActivate: (t) => {
967
+ if (!this.picker) {
968
+ const i = t.inputElement.closest(".chat-ui-input-wrapper");
969
+ if (!i) return;
970
+ this.picker = new q(
971
+ i,
972
+ t.i18n,
973
+ (s) => {
974
+ t.insertText(s);
975
+ },
976
+ () => {
977
+ }
978
+ );
979
+ }
980
+ this.picker.toggle();
981
+ }
982
+ });
983
+ }
984
+ uninstall() {
985
+ var e;
986
+ (e = this.picker) == null || e.destroy(), this.picker = null;
987
+ }
988
+ }
989
+ export {
990
+ Q as ChatUI,
991
+ q as EmojiPicker,
992
+ _ as EmojiPlugin,
993
+ U as I18n,
994
+ Z as ImagePlugin,
995
+ f as ImagePreview,
996
+ $ as ImageRenderer,
997
+ A as InputArea,
998
+ R as MessageListRenderer,
999
+ k as PluginManager,
1000
+ X as TextPlugin,
1001
+ K as TextRenderer,
1002
+ M as ThemeManager,
1003
+ u as clearChildren,
1004
+ S as createAvatarElement,
1005
+ r as createElement,
1006
+ C as emojiCategories,
1007
+ W as emojiList,
1008
+ B as enUS,
1009
+ D as formatMessageTime,
1010
+ L as getAvatarText,
1011
+ j as isNearBottom,
1012
+ m as scrollToBottom,
1013
+ V as setTextContent,
1014
+ Y as toggleClass,
1015
+ z as zhCN
1016
+ };