@composy/chat-core 0.0.1 → 0.0.2

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.
Files changed (168) hide show
  1. package/README.md +187 -123
  2. package/dist/index.js +2018 -1035
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.min.js +1370 -1644
  5. package/dist/index.min.js.map +1 -0
  6. package/es/.dts-output-manifest.json +1 -0
  7. package/es/.dts.tsbuildinfo +1 -0
  8. package/es/constants/dom.d.ts +3 -19
  9. package/es/constants/dom.js +4 -4
  10. package/es/constants/dom.js.map +1 -1
  11. package/es/core/chat-manager.d.ts +33 -22
  12. package/es/core/chat-manager.js +174 -50
  13. package/es/core/chat-manager.js.map +1 -1
  14. package/es/core/event-emitter.d.ts +0 -1
  15. package/es/core/event-emitter.js +14 -7
  16. package/es/core/event-emitter.js.map +1 -1
  17. package/es/core/mention-engine.d.ts +1 -2
  18. package/es/core/mention-engine.js +9 -9
  19. package/es/core/mention-engine.js.map +1 -1
  20. package/es/core/message-schema.d.ts +19 -0
  21. package/es/core/message-schema.js +197 -0
  22. package/es/core/message-schema.js.map +1 -0
  23. package/es/core/outbox.d.ts +63 -0
  24. package/es/core/outbox.js +160 -0
  25. package/es/core/outbox.js.map +1 -0
  26. package/es/core/transport.d.ts +79 -0
  27. package/es/core/transport.js +102 -0
  28. package/es/core/transport.js.map +1 -0
  29. package/es/index.d.ts +12 -7
  30. package/es/index.js +167 -36
  31. package/es/index.js.map +1 -1
  32. package/es/renderer/dom-renderer.d.ts +16 -4
  33. package/es/renderer/dom-renderer.js +316 -61
  34. package/es/renderer/dom-renderer.js.map +1 -1
  35. package/es/renderer/style-manager.d.ts +14 -38
  36. package/es/renderer/style-manager.js +92 -63
  37. package/es/renderer/style-manager.js.map +1 -1
  38. package/es/theme/theme-manager.d.ts +4 -3
  39. package/es/theme/theme-manager.js +19 -9
  40. package/es/theme/theme-manager.js.map +1 -1
  41. package/es/theme/tokens.d.ts +0 -1
  42. package/es/theme/tokens.js +92 -92
  43. package/es/theme/tokens.js.map +1 -1
  44. package/es/types/index.d.ts +104 -24
  45. package/es/types/index.js +8 -3
  46. package/es/types/index.js.map +1 -0
  47. package/esm/constants/dom.d.ts +3 -19
  48. package/esm/constants/dom.js +4 -4
  49. package/esm/constants/dom.js.map +1 -1
  50. package/esm/core/chat-manager.d.ts +33 -22
  51. package/esm/core/chat-manager.js +174 -50
  52. package/esm/core/chat-manager.js.map +1 -1
  53. package/esm/core/event-emitter.d.ts +0 -1
  54. package/esm/core/event-emitter.js +14 -7
  55. package/esm/core/event-emitter.js.map +1 -1
  56. package/esm/core/mention-engine.d.ts +1 -2
  57. package/esm/core/mention-engine.js +9 -9
  58. package/esm/core/mention-engine.js.map +1 -1
  59. package/esm/core/message-schema.d.ts +19 -0
  60. package/esm/core/message-schema.js +197 -0
  61. package/esm/core/message-schema.js.map +1 -0
  62. package/esm/core/outbox.d.ts +63 -0
  63. package/esm/core/outbox.js +160 -0
  64. package/esm/core/outbox.js.map +1 -0
  65. package/esm/core/transport.d.ts +79 -0
  66. package/esm/core/transport.js +102 -0
  67. package/esm/core/transport.js.map +1 -0
  68. package/esm/index.d.ts +12 -7
  69. package/esm/index.js +169 -38
  70. package/esm/index.js.map +1 -1
  71. package/esm/renderer/dom-renderer.d.ts +16 -4
  72. package/esm/renderer/dom-renderer.js +316 -61
  73. package/esm/renderer/dom-renderer.js.map +1 -1
  74. package/esm/renderer/style-manager.d.ts +14 -38
  75. package/esm/renderer/style-manager.js +92 -63
  76. package/esm/renderer/style-manager.js.map +1 -1
  77. package/esm/theme/theme-manager.d.ts +4 -3
  78. package/esm/theme/theme-manager.js +19 -9
  79. package/esm/theme/theme-manager.js.map +1 -1
  80. package/esm/theme/tokens.d.ts +0 -1
  81. package/esm/theme/tokens.js +92 -92
  82. package/esm/theme/tokens.js.map +1 -1
  83. package/esm/types/index.d.ts +104 -24
  84. package/esm/types/index.js +8 -3
  85. package/esm/types/index.js.map +1 -0
  86. package/lib/constants/dom.cjs +4 -4
  87. package/lib/constants/dom.cjs.map +1 -1
  88. package/lib/constants/dom.d.cts +25 -0
  89. package/lib/constants/dom.d.ts +3 -19
  90. package/lib/core/chat-manager.cjs +174 -50
  91. package/lib/core/chat-manager.cjs.map +1 -1
  92. package/lib/core/chat-manager.d.cts +45 -0
  93. package/lib/core/chat-manager.d.ts +33 -22
  94. package/lib/core/event-emitter.cjs +14 -7
  95. package/lib/core/event-emitter.cjs.map +1 -1
  96. package/lib/core/event-emitter.d.cts +47 -0
  97. package/lib/core/event-emitter.d.ts +0 -1
  98. package/lib/core/mention-engine.cjs +9 -9
  99. package/lib/core/mention-engine.cjs.map +1 -1
  100. package/lib/core/mention-engine.d.cts +40 -0
  101. package/lib/core/mention-engine.d.ts +1 -2
  102. package/lib/core/message-schema.cjs +202 -0
  103. package/lib/core/message-schema.cjs.map +1 -0
  104. package/lib/core/message-schema.d.cts +19 -0
  105. package/lib/core/message-schema.d.ts +19 -0
  106. package/lib/core/outbox.cjs +162 -0
  107. package/lib/core/outbox.cjs.map +1 -0
  108. package/lib/core/outbox.d.cts +63 -0
  109. package/lib/core/outbox.d.ts +63 -0
  110. package/lib/core/transport.cjs +103 -0
  111. package/lib/core/transport.cjs.map +1 -0
  112. package/lib/core/transport.d.cts +79 -0
  113. package/lib/core/transport.d.ts +79 -0
  114. package/lib/index.cjs +175 -35
  115. package/lib/index.cjs.map +1 -1
  116. package/lib/index.d.cts +33 -0
  117. package/lib/index.d.ts +12 -7
  118. package/lib/renderer/dom-renderer.cjs +316 -61
  119. package/lib/renderer/dom-renderer.cjs.map +1 -1
  120. package/lib/renderer/dom-renderer.d.cts +91 -0
  121. package/lib/renderer/dom-renderer.d.ts +16 -4
  122. package/lib/renderer/style-manager.cjs +92 -62
  123. package/lib/renderer/style-manager.cjs.map +1 -1
  124. package/lib/renderer/style-manager.d.cts +34 -0
  125. package/lib/renderer/style-manager.d.ts +14 -38
  126. package/lib/theme/theme-manager.cjs +19 -9
  127. package/lib/theme/theme-manager.cjs.map +1 -1
  128. package/lib/theme/theme-manager.d.cts +34 -0
  129. package/lib/theme/theme-manager.d.ts +4 -3
  130. package/lib/theme/tokens.cjs +92 -92
  131. package/lib/theme/tokens.cjs.map +1 -1
  132. package/lib/theme/tokens.d.cts +56 -0
  133. package/lib/theme/tokens.d.ts +0 -1
  134. package/lib/types/index.cjs +8 -3
  135. package/lib/types/index.cjs.map +1 -0
  136. package/lib/types/index.d.cts +284 -0
  137. package/lib/types/index.d.ts +104 -24
  138. package/package.json +16 -13
  139. package/es/constants/dom.d.ts.map +0 -1
  140. package/es/core/chat-manager.d.ts.map +0 -1
  141. package/es/core/event-emitter.d.ts.map +0 -1
  142. package/es/core/mention-engine.d.ts.map +0 -1
  143. package/es/index.d.ts.map +0 -1
  144. package/es/renderer/dom-renderer.d.ts.map +0 -1
  145. package/es/renderer/style-manager.d.ts.map +0 -1
  146. package/es/theme/theme-manager.d.ts.map +0 -1
  147. package/es/theme/tokens.d.ts.map +0 -1
  148. package/es/types/index.d.ts.map +0 -1
  149. package/esm/constants/dom.d.ts.map +0 -1
  150. package/esm/core/chat-manager.d.ts.map +0 -1
  151. package/esm/core/event-emitter.d.ts.map +0 -1
  152. package/esm/core/mention-engine.d.ts.map +0 -1
  153. package/esm/index.d.ts.map +0 -1
  154. package/esm/renderer/dom-renderer.d.ts.map +0 -1
  155. package/esm/renderer/style-manager.d.ts.map +0 -1
  156. package/esm/theme/theme-manager.d.ts.map +0 -1
  157. package/esm/theme/tokens.d.ts.map +0 -1
  158. package/esm/types/index.d.ts.map +0 -1
  159. package/lib/constants/dom.d.ts.map +0 -1
  160. package/lib/core/chat-manager.d.ts.map +0 -1
  161. package/lib/core/event-emitter.d.ts.map +0 -1
  162. package/lib/core/mention-engine.d.ts.map +0 -1
  163. package/lib/index.d.ts.map +0 -1
  164. package/lib/renderer/dom-renderer.d.ts.map +0 -1
  165. package/lib/renderer/style-manager.d.ts.map +0 -1
  166. package/lib/theme/theme-manager.d.ts.map +0 -1
  167. package/lib/theme/tokens.d.ts.map +0 -1
  168. package/lib/types/index.d.ts.map +0 -1
package/dist/index.min.js CHANGED
@@ -1,174 +1,377 @@
1
1
  /*!
2
2
  * ***********************************
3
- * @composy/chat-core v0.0.1 *
3
+ * @composy/chat-core v0.0.2 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:30:32 *
5
+ * Build time: 2024-10-12 21:52:18 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
9
9
  */
10
- (function(global, factory) {
11
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.LDesignChat = {}));
10
+ (function(e, t) {
11
+ "object" == typeof exports && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd ? define(["exports"], t) : t((e = "undefined" != typeof globalThis ? globalThis : e || self).LDesignChat = {});
12
12
  })(this, function(exports) {
13
13
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
14
- //#region src/core/event-emitter.ts
15
- var EventEmitter = class {
16
- constructor() {
17
- this.listeners = /* @__PURE__ */ new Map();
14
+ const e = "ldesign-chat", t = "--ldesign-chat", n = "ldesign-chat-styles";
15
+ function s(t) {
16
+ const n = t?.trim();
17
+ if (!n) return e;
18
+ const s = n.replace(/^ldesign-/i, "").replace(/[^\w-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
19
+ return s ? `ldesign-${s}` : e;
20
+ }
21
+ function i(t = e) {
22
+ const i = s(t);
23
+ return "ldesign-chat" === i ? n : `${i}-styles`;
24
+ }
25
+ function r(...e) {
26
+ return e.filter(Boolean).join(" ");
27
+ }
28
+ function a(e) {
29
+ if (!e || "object" != typeof e) throw new TypeError("Chat message must be an object.");
30
+ if ("string" != typeof e.id || !e.id.trim()) throw new TypeError("Chat message ID must be a non-empty string.");
31
+ if (!Number.isFinite(e.timestamp)) throw new TypeError("Chat message timestamp must be finite.");
32
+ const t = e.schemaVersion ?? 1;
33
+ if (t > 1) throw new Error(`Unsupported chat message schema version: ${t}.`);
34
+ return d({
35
+ ...e,
36
+ schemaVersion: 1,
37
+ ...e.contentBlocks && { contentBlocks: o(e.contentBlocks) }
38
+ });
39
+ }
40
+ function o(e) {
41
+ const t = [];
42
+ for (const n of e) {
43
+ const e = h(n);
44
+ e && t.push(e);
18
45
  }
19
- /**
20
- * 注册事件监听器
21
- * @param event - 要监听的事件名称
22
- * @param handler - 事件触发时执行的回调函数
23
- * @returns 返回一个取消订阅的函数,调用即可移除该监听器
24
- */
25
- on(event, handler) {
26
- if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
27
- this.listeners.get(event).add(handler);
28
- return () => this.off(event, handler);
29
- }
30
- /**
31
- * 移除指定事件的某个监听器
32
- * @param event - 事件名称
33
- * @param handler - 要移除的处理函数引用(必须与注册时为同一函数引用)
34
- */
35
- off(event, handler) {
36
- this.listeners.get(event)?.delete(handler);
37
- }
38
- /**
39
- * 触发(发射)指定事件,依次调用所有已注册的监听器
40
- * @param event - 要触发的事件名称
41
- * @param args - 传递给监听器函数的参数列表(剩余参数语法)
42
- */
43
- emit(event, ...args) {
44
- this.listeners.get(event)?.forEach((handler) => {
45
- try {
46
- handler(...args);
47
- } catch (err) {
48
- console.error(`[Chat] Event handler error for "${event}":`, err);
49
- }
46
+ return t;
47
+ }
48
+ function l(e) {
49
+ const t = [], n = /* @__PURE__ */ new Set(), s = /* @__PURE__ */ new Set();
50
+ for (const i of e) {
51
+ const e = a(i);
52
+ if (n.has(e.id)) throw new Error(`Duplicate chat message ID: ${e.id}`);
53
+ if (e.clientMessageId && s.has(e.clientMessageId)) throw new Error(`Duplicate chat client message ID: ${e.clientMessageId}`);
54
+ n.add(e.id), e.clientMessageId && s.add(e.clientMessageId), t.push(e);
55
+ }
56
+ return t;
57
+ }
58
+ function d(e) {
59
+ return {
60
+ ...e,
61
+ sender: (s = e.sender, { ...s }),
62
+ ...e.image && { image: (n = e.image, { ...n }) },
63
+ ...e.file && { file: (t = e.file, { ...t }) },
64
+ ...e.mentions && { mentions: e.mentions.map(p) },
65
+ ...e.contentBlocks && { contentBlocks: e.contentBlocks.map(c) },
66
+ ...e.customData && { customData: f(e.customData) }
67
+ };
68
+ var t, n, s;
69
+ }
70
+ function c(e) {
71
+ switch (e.type) {
72
+ case "text": return {
73
+ type: "text",
74
+ text: e.text
75
+ };
76
+ case "mention": return {
77
+ type: "mention",
78
+ id: e.id,
79
+ label: e.label
80
+ };
81
+ case "link": return {
82
+ type: "link",
83
+ url: e.url,
84
+ ...e.label && { label: e.label }
85
+ };
86
+ case "code": return {
87
+ type: "code",
88
+ code: e.code,
89
+ ...e.language && { language: e.language }
90
+ };
91
+ case "image": return {
92
+ type: "image",
93
+ src: e.src,
94
+ ...e.alt && { alt: e.alt },
95
+ ...void 0 !== e.width && { width: e.width },
96
+ ...void 0 !== e.height && { height: e.height }
97
+ };
98
+ case "file": return {
99
+ type: "file",
100
+ url: e.url,
101
+ name: e.name,
102
+ ...void 0 !== e.size && { size: e.size },
103
+ ...e.mimeType && { mimeType: e.mimeType }
104
+ };
105
+ case "break": return { type: "break" };
106
+ case "custom": return {
107
+ type: "custom",
108
+ kind: e.kind,
109
+ data: f(e.data),
110
+ ...e.fallback && { fallback: e.fallback }
111
+ };
112
+ }
113
+ }
114
+ function h(e) {
115
+ if (!g(e) || "string" != typeof e.type) return null;
116
+ switch (e.type) {
117
+ case "text": return "string" == typeof e.text ? {
118
+ type: "text",
119
+ text: e.text
120
+ } : null;
121
+ case "mention": return "string" == typeof e.id && "string" == typeof e.label ? {
122
+ type: "mention",
123
+ id: e.id,
124
+ label: e.label
125
+ } : null;
126
+ case "link": return "string" == typeof e.url ? {
127
+ type: "link",
128
+ url: e.url,
129
+ ..."string" == typeof e.label && { label: e.label }
130
+ } : null;
131
+ case "code": return "string" == typeof e.code ? {
132
+ type: "code",
133
+ code: e.code,
134
+ ..."string" == typeof e.language && { language: e.language }
135
+ } : null;
136
+ case "image": return "string" == typeof e.src ? {
137
+ type: "image",
138
+ src: e.src,
139
+ ..."string" == typeof e.alt && { alt: e.alt },
140
+ ...u(e.width) && { width: e.width },
141
+ ...u(e.height) && { height: e.height }
142
+ } : null;
143
+ case "file": return "string" == typeof e.url && "string" == typeof e.name ? {
144
+ type: "file",
145
+ url: e.url,
146
+ name: e.name,
147
+ ...u(e.size) && { size: e.size },
148
+ ..."string" == typeof e.mimeType && { mimeType: e.mimeType }
149
+ } : null;
150
+ case "break": return { type: "break" };
151
+ case "custom": return "string" == typeof e.kind && g(e.data) ? {
152
+ type: "custom",
153
+ kind: e.kind,
154
+ data: f(e.data),
155
+ ..."string" == typeof e.fallback && { fallback: e.fallback }
156
+ } : m(e);
157
+ default: return m(e);
158
+ }
159
+ }
160
+ function m(e) {
161
+ return "string" == typeof e.fallback ? {
162
+ type: "text",
163
+ text: e.fallback
164
+ } : null;
165
+ }
166
+ function g(e) {
167
+ return Boolean(e) && "object" == typeof e && !Array.isArray(e);
168
+ }
169
+ function u(e) {
170
+ return "number" == typeof e && Number.isFinite(e) && e > 0;
171
+ }
172
+ function p(e) {
173
+ return { ...e };
174
+ }
175
+ function f(e, t = /* @__PURE__ */ new WeakMap()) {
176
+ if (!e || "object" != typeof e) return e;
177
+ if (t.has(e)) return t.get(e);
178
+ if (Array.isArray(e)) {
179
+ const n = [];
180
+ return t.set(e, n), e.forEach((e) => n.push(f(e, t))), n;
181
+ }
182
+ const n = Object.getPrototypeOf(e);
183
+ if (n !== Object.prototype && null !== n) return e;
184
+ const s = {};
185
+ return t.set(e, s), Object.entries(e).forEach(([e, n]) => {
186
+ s[e] = f(n, t);
187
+ }), s;
188
+ }
189
+ var b = class {
190
+ constructor(e, t) {
191
+ this.messages = [], this.streams = /* @__PURE__ */ new Map(), this.emitter = e, t?.length && (this.messages = l(t));
192
+ }
193
+ getMessages() {
194
+ return this.messages.map(d);
195
+ }
196
+ getMessage(e) {
197
+ const t = this.messages.find((t) => t.id === e);
198
+ return t ? d(t) : void 0;
199
+ }
200
+ getMessageIdByClientMessageId(e) {
201
+ return this.messages.find((t) => t.clientMessageId === e)?.id;
202
+ }
203
+ addMessage(e) {
204
+ const t = a(e);
205
+ this.assertUniqueIds([t]), this.messages.push(t), this.emitAdded(t);
206
+ }
207
+ prependMessages(e) {
208
+ const t = l(e);
209
+ this.assertUniqueIds(t), this.messages.unshift(...t), this.emitter.emit("messages-prepended", t.map(d)), this.emitChanged();
210
+ }
211
+ upsertMessage(e, t = {}) {
212
+ const n = a({
213
+ ...e,
214
+ ...void 0 !== t.sequence && { sequence: t.sequence }
215
+ }), s = this.findMessageIndex(n);
216
+ if (s < 0) return this.messages.push(n), this.emitAdded(n), "added";
217
+ const i = this.messages[s];
218
+ if (Number.isFinite(n.sequence) && Number.isFinite(i.sequence) && n.sequence <= i.sequence) return "ignored";
219
+ const r = a({
220
+ ...i,
221
+ ...n,
222
+ id: i.id,
223
+ clientMessageId: n.clientMessageId ?? i.clientMessageId,
224
+ sequence: n.sequence ?? i.sequence
50
225
  });
226
+ return this.messages[s] = r, this.emitter.emit("message-updated", d(r)), this.emitChanged(), "updated";
51
227
  }
52
- /**
53
- * 注册一次性事件监听器(触发一次后自动移除)
54
- * @param event - 要监听的事件名称
55
- * @param handler - 事件触发时执行的回调函数
56
- * @returns 返回取消订阅的函数,可在触发前手动取消
57
- */
58
- once(event, handler) {
59
- const wrapper = (...args) => {
60
- this.off(event, wrapper);
61
- handler(...args);
228
+ mergeMessages(e, t = {}) {
229
+ const n = {
230
+ added: 0,
231
+ updated: 0,
232
+ ignored: 0
62
233
  };
63
- return this.on(event, wrapper);
64
- }
65
- /**
66
- * 批量移除事件监听器
67
- * @param event - 可选的事件名称
68
- * - 若传入事件名,则只移除该事件的所有监听器
69
- * - 若不传参数,则清空所有事件的所有监听器
70
- */
71
- removeAll(event) {
72
- if (event) this.listeners.delete(event);
73
- else this.listeners.clear();
234
+ for (const s of e) n[this.upsertMessage(s, t)] += 1;
235
+ return n;
236
+ }
237
+ updateMessage(e, t) {
238
+ const n = this.messages.findIndex((t) => t.id === e);
239
+ if (-1 === n) return;
240
+ if (void 0 !== t.id && t.id !== e) throw new Error("Chat message IDs are immutable.");
241
+ const s = a({
242
+ ...this.messages[n],
243
+ ...t,
244
+ id: e
245
+ });
246
+ this.assertUniqueClientMessageId(s, e), this.messages[n] = s, this.emitter.emit("message-updated", d(s)), this.emitChanged();
74
247
  }
75
- };
76
- //#endregion
77
- //#region src/core/chat-manager.ts
78
- var ChatManager = class {
79
- constructor(emitter, initialMessages) {
80
- this.messages = [];
81
- this.emitter = emitter;
82
- if (initialMessages?.length) this.messages = [...initialMessages];
83
- }
84
- /** 获取所有消息 */
85
- getMessages() {
86
- return [...this.messages];
248
+ markMessageStatus(e, t) {
249
+ this.updateMessage(e, { status: t });
87
250
  }
88
- /** 根据 ID 获取消息 */
89
- getMessage(id) {
90
- return this.messages.find((m) => m.id === id);
251
+ removeMessage(e) {
252
+ const t = this.messages.findIndex((t) => t.id === e);
253
+ if (-1 === t) return;
254
+ const [n] = this.messages.splice(t, 1);
255
+ this.streams.delete(e), this.emitter.emit("message-removed", d(n)), this.emitChanged();
91
256
  }
92
- /** 添加消息(末尾追加) */
93
- addMessage(message) {
94
- this.messages.push(message);
95
- this.emitter.emit("message-added", message);
96
- this.emitter.emit("messages-changed", this.getMessages());
257
+ setMessages(e) {
258
+ this.messages = l(e), this.streams.clear(), this.emitChanged();
97
259
  }
98
- /** 在顶部批量插入消息(用于加载更多) */
99
- prependMessages(messages) {
100
- this.messages.unshift(...messages);
101
- this.emitter.emit("messages-prepended", messages);
102
- this.emitter.emit("messages-changed", this.getMessages());
260
+ clearMessages() {
261
+ this.messages = [], this.streams.clear(), this.emitChanged();
262
+ }
263
+ beginStreaming(e, t = function() {
264
+ const e = globalThis.crypto;
265
+ return e?.randomUUID ? e.randomUUID() : `stream-${Date.now()}-${Math.random().toString(36).slice(2)}`;
266
+ }()) {
267
+ const n = this.messages.findIndex((t) => t.id === e);
268
+ return -1 === n ? null : (this.streams.set(e, {
269
+ generation: t,
270
+ lastSequence: -1
271
+ }), this.messages[n] = a({
272
+ ...this.messages[n],
273
+ streaming: !0
274
+ }), this.emitter.emit("message-updated", d(this.messages[n])), this.emitChanged(), t);
275
+ }
276
+ appendMessageContent(e, t, n = {}) {
277
+ const s = this.streams.get(e);
278
+ if (n.generation) {
279
+ if (!s || s.generation !== n.generation) return !1;
280
+ const e = n.sequence;
281
+ if (void 0 !== e && Number.isFinite(e) && e <= s.lastSequence) return !1;
282
+ void 0 !== e && Number.isFinite(e) && (s.lastSequence = e);
283
+ }
284
+ const i = this.messages.findIndex((t) => t.id === e);
285
+ if (-1 === i) return !1;
286
+ const r = this.messages[i];
287
+ return this.messages[i] = a({
288
+ ...r,
289
+ content: (r.content ?? "") + t,
290
+ streaming: !!n.generation || r.streaming
291
+ }), this.emitter.emit("message-content-appended", e, this.messages[i].content, n.generation, n.sequence), this.emitChanged(), !0;
292
+ }
293
+ finishStreaming(e, t) {
294
+ const n = this.streams.get(e);
295
+ if (t && (!n || n.generation !== t)) return !1;
296
+ const s = this.messages.findIndex((t) => t.id === e);
297
+ return -1 !== s && (this.messages[s] = a({
298
+ ...this.messages[s],
299
+ streaming: !1
300
+ }), this.streams.delete(e), this.emitter.emit("message-updated", d(this.messages[s])), this.emitChanged(), !0);
301
+ }
302
+ cancelStreaming(e, t) {
303
+ const n = this.streams.get(e);
304
+ if (t && (!n || n.generation !== t)) return !1;
305
+ const s = this.messages.findIndex((t) => t.id === e);
306
+ return -1 !== s && (this.streams.delete(e), this.messages[s] = a({
307
+ ...this.messages[s],
308
+ streaming: !1
309
+ }), this.emitter.emit("stream-cancelled", d(this.messages[s])), this.emitter.emit("message-updated", d(this.messages[s])), this.emitChanged(), !0);
310
+ }
311
+ getStreamGeneration(e) {
312
+ return this.streams.get(e)?.generation;
313
+ }
314
+ findMessageIndex(e) {
315
+ const t = this.messages.findIndex((t) => t.id === e.id);
316
+ return t >= 0 ? t : e.clientMessageId ? this.messages.findIndex((t) => t.clientMessageId === e.clientMessageId) : -1;
317
+ }
318
+ assertUniqueIds(e) {
319
+ const t = new Set(this.messages.map((e) => e.id)), n = new Set(this.messages.flatMap((e) => e.clientMessageId ? [e.clientMessageId] : []));
320
+ for (const s of e) {
321
+ if (!s.id || t.has(s.id)) throw new Error(`Chat message ID must be non-empty and unique: ${s.id}`);
322
+ if (s.clientMessageId && n.has(s.clientMessageId)) throw new Error(`Chat client message ID must be unique: ${s.clientMessageId}`);
323
+ t.add(s.id), s.clientMessageId && n.add(s.clientMessageId);
324
+ }
103
325
  }
104
- /** 更新消息 */
105
- updateMessage(id, updates) {
106
- const index = this.messages.findIndex((m) => m.id === id);
107
- if (index === -1) return;
108
- this.messages[index] = {
109
- ...this.messages[index],
110
- ...updates
111
- };
112
- this.emitter.emit("message-updated", this.messages[index]);
113
- this.emitter.emit("messages-changed", this.getMessages());
326
+ assertUniqueClientMessageId(e, t) {
327
+ if (e.clientMessageId && this.messages.some((n) => n.id !== t && n.clientMessageId === e.clientMessageId)) throw new Error(`Chat client message ID must be unique: ${e.clientMessageId}`);
114
328
  }
115
- /** 删除消息 */
116
- removeMessage(id) {
117
- const index = this.messages.findIndex((m) => m.id === id);
118
- if (index === -1) return;
119
- const removed = this.messages.splice(index, 1)[0];
120
- this.emitter.emit("message-removed", removed);
121
- this.emitter.emit("messages-changed", this.getMessages());
329
+ emitAdded(e) {
330
+ this.emitter.emit("message-added", d(e)), this.emitChanged();
122
331
  }
123
- /** 替换全部消息 */
124
- setMessages(messages) {
125
- this.messages = [...messages];
332
+ emitChanged() {
126
333
  this.emitter.emit("messages-changed", this.getMessages());
127
334
  }
128
- /** 清空所有消息 */
129
- clearMessages() {
130
- this.messages = [];
131
- this.emitter.emit("messages-changed", []);
132
- }
133
- /** 追加流式消息内容 */
134
- appendMessageContent(id, chunk) {
135
- const index = this.messages.findIndex((m) => m.id === id);
136
- if (index === -1) return;
137
- const msg = this.messages[index];
138
- this.messages[index] = {
139
- ...msg,
140
- content: (msg.content ?? "") + chunk
141
- };
142
- this.emitter.emit("message-content-appended", id, this.messages[index].content);
143
- this.emitter.emit("messages-changed", this.getMessages());
335
+ };
336
+ var v = class {
337
+ constructor() {
338
+ this.listeners = /* @__PURE__ */ new Map();
144
339
  }
145
- /** 完成流式消息 */
146
- finishStreaming(id) {
147
- const index = this.messages.findIndex((m) => m.id === id);
148
- if (index === -1) return;
149
- this.messages[index] = {
150
- ...this.messages[index],
151
- streaming: false
340
+ on(e, t) {
341
+ let n = this.listeners.get(e);
342
+ return n || (n = /* @__PURE__ */ new Set(), this.listeners.set(e, n)), n.add(t), () => this.off(e, t);
343
+ }
344
+ off(e, t) {
345
+ const n = this.listeners.get(e);
346
+ n && (n.delete(t), 0 === n.size && this.listeners.delete(e));
347
+ }
348
+ emit(e, ...t) {
349
+ [...this.listeners.get(e) ?? []].forEach((n) => {
350
+ try {
351
+ n(...t);
352
+ } catch (t) {
353
+ console.error(`[Chat] Event handler error for "${e}":`, t);
354
+ }
355
+ });
356
+ }
357
+ once(e, t) {
358
+ const n = (...s) => {
359
+ this.off(e, n), t(...s);
152
360
  };
153
- this.emitter.emit("message-updated", this.messages[index]);
154
- this.emitter.emit("messages-changed", this.getMessages());
361
+ return this.on(e, n);
155
362
  }
156
- };
157
- //#endregion
158
- //#region src/core/mention-engine.ts
159
- var MentionEngine = class {
160
- constructor(emitter, members) {
161
- this.members = [];
162
- this.state = {
163
- active: false,
363
+ removeAll(e) {
364
+ void 0 !== e ? this.listeners.delete(e) : this.listeners.clear();
365
+ }
366
+ }, y = class {
367
+ constructor(e, t) {
368
+ this.members = [], this.state = {
369
+ active: !1,
164
370
  query: "",
165
371
  filtered: [],
166
372
  activeIndex: 0,
167
373
  triggerPosition: -1
168
- };
169
- this.selectedMentions = [];
170
- this.emitter = emitter;
171
- if (members) this.members = [...members];
374
+ }, this.selectedMentions = [], this.emitter = e, t && (this.members = t.map((e) => ({ ...e })));
172
375
  }
173
376
  getState() {
174
377
  return {
@@ -177,391 +380,316 @@
177
380
  };
178
381
  }
179
382
  getSelectedMentions() {
180
- return [...this.selectedMentions];
181
- }
182
- setMembers(members) {
183
- this.members = [...members];
184
- if (this.state.active) this.filter(this.state.query);
185
- }
186
- handleInput(text, cursorPos) {
187
- const beforeCursor = text.slice(0, cursorPos);
188
- const atIndex = beforeCursor.lastIndexOf("@");
189
- if (atIndex === -1) {
190
- this.deactivate();
191
- return;
192
- }
193
- if (atIndex > 0 && !/\s/.test(beforeCursor[atIndex - 1])) {
194
- this.deactivate();
195
- return;
196
- }
197
- const query = beforeCursor.slice(atIndex + 1);
198
- if (/[^\S\r\n]/.test(query)) {
199
- this.deactivate();
200
- return;
201
- }
202
- this.state.active = true;
203
- this.state.triggerPosition = atIndex;
204
- this.filter(query);
383
+ return this.selectedMentions.map((e) => ({ ...e }));
384
+ }
385
+ setMembers(e) {
386
+ this.members = e.map((e) => ({ ...e })), this.state.active && this.filter(this.state.query);
387
+ }
388
+ handleInput(e, t) {
389
+ const n = e.slice(0, t), s = n.lastIndexOf("@");
390
+ if (-1 === s) return void this.deactivate();
391
+ if (s > 0 && !/\s/.test(n[s - 1])) return void this.deactivate();
392
+ const i = n.slice(s + 1);
393
+ /\s/.test(i) ? this.deactivate() : (this.state.active = !0, this.state.triggerPosition = s, this.filter(i));
205
394
  }
206
395
  moveUp() {
207
- if (!this.state.active || this.state.filtered.length === 0) return;
208
- this.state.activeIndex = (this.state.activeIndex - 1 + this.state.filtered.length) % this.state.filtered.length;
209
- this.emitter.emit("mention-active-change", this.state.activeIndex);
396
+ this.state.active && 0 !== this.state.filtered.length && (this.state.activeIndex = (this.state.activeIndex - 1 + this.state.filtered.length) % this.state.filtered.length, this.emitter.emit("mention-active-change", this.state.activeIndex));
210
397
  }
211
398
  moveDown() {
212
- if (!this.state.active || this.state.filtered.length === 0) return;
213
- this.state.activeIndex = (this.state.activeIndex + 1) % this.state.filtered.length;
214
- this.emitter.emit("mention-active-change", this.state.activeIndex);
399
+ this.state.active && 0 !== this.state.filtered.length && (this.state.activeIndex = (this.state.activeIndex + 1) % this.state.filtered.length, this.emitter.emit("mention-active-change", this.state.activeIndex));
215
400
  }
216
401
  selectCurrent() {
217
- if (!this.state.active || this.state.filtered.length === 0) return null;
218
- return this.select(this.state.activeIndex);
402
+ return this.state.active && 0 !== this.state.filtered.length ? this.select(this.state.activeIndex) : null;
219
403
  }
220
- select(index) {
221
- if (index < 0 || index >= this.state.filtered.length) return null;
222
- const member = this.state.filtered[index];
223
- if (!this.selectedMentions.find((m) => m.id === member.id)) this.selectedMentions.push(member);
224
- this.emitter.emit("mention-select", member);
225
- this.deactivate();
226
- return member;
404
+ select(e) {
405
+ if (e < 0 || e >= this.state.filtered.length) return null;
406
+ const t = this.state.filtered[e];
407
+ return this.selectedMentions.some((e) => e.id === t.id) || this.selectedMentions.push(t), this.emitter.emit("mention-select", t), this.deactivate(), t;
227
408
  }
228
409
  deactivate() {
229
- if (!this.state.active) return;
230
- this.state = {
231
- active: false,
410
+ this.state.active && (this.state = {
411
+ active: !1,
232
412
  query: "",
233
413
  filtered: [],
234
414
  activeIndex: 0,
235
415
  triggerPosition: -1
236
- };
237
- this.emitter.emit("mention-deactivate");
416
+ }, this.emitter.emit("mention-deactivate"));
238
417
  }
239
- parseMentions(text) {
240
- const mentions = [];
241
- const sortedMembers = [...this.members].sort((a, b) => b.name.length - a.name.length);
242
- for (const member of sortedMembers) if (new RegExp(`(^|\\s)@${this.escapeRegex(member.name)}(?=\\s|$|[\uFF0C\u3002\uFF01\uFF1F,.!?])`, "u").test(text) && !mentions.find((m) => m.id === member.id)) mentions.push(member);
243
- return mentions;
418
+ parseMentions(e) {
419
+ const t = [], n = [...this.members].sort((e, t) => t.name.length - e.name.length);
420
+ for (const s of n) new RegExp(`(^|\\s)@${this.escapeRegex(s.name)}(?=\\s|$|[,。!?,.!?])`, "u").test(e) && !t.some((e) => e.id === s.id) && t.push({ ...s });
421
+ return t;
244
422
  }
245
423
  resetSelected() {
246
424
  this.selectedMentions = [];
247
425
  }
248
- getInsertText(member) {
249
- return `@${member.name} `;
426
+ getInsertText(e) {
427
+ return `@${e.name} `;
250
428
  }
251
- filter(query) {
252
- const normalizedQuery = query.trim().toLowerCase();
253
- this.state.query = query;
254
- this.state.filtered = this.members.filter((member) => member.name.toLowerCase().includes(normalizedQuery) || member.id.toLowerCase().includes(normalizedQuery));
255
- this.state.activeIndex = 0;
256
- this.emitter.emit("mention-filter", this.getState());
429
+ filter(e) {
430
+ const t = e.trim().toLowerCase();
431
+ this.state.query = e, this.state.filtered = this.members.filter((e) => e.name.toLowerCase().includes(t) || e.id.toLowerCase().includes(t)).map((e) => ({ ...e })), this.state.activeIndex = 0, this.emitter.emit("mention-filter", this.getState());
257
432
  }
258
- escapeRegex(str) {
259
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
433
+ escapeRegex(e) {
434
+ return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
260
435
  }
261
- };
262
- //#endregion
263
- //#region src/theme/tokens.ts
264
- const lightTokens = {
265
- bgPrimary: "var(--color-gray-50)",
266
- bgSecondary: "var(--color-gray-100)",
267
- bgBubbleOutgoing: "var(--color-primary-default)",
268
- bgBubbleIncoming: "var(--color-gray-100)",
269
- bgInput: "var(--color-gray-50)",
270
- bgMention: "var(--color-gray-50)",
271
- bgMentionHover: "var(--color-primary-50)",
272
- bgMentionActive: "var(--color-primary-100)",
273
- bgLoadMore: "var(--color-gray-100)",
274
- bgSystemMessage: "var(--color-gray-100)",
275
- bgDropOverlay: "color-mix(in srgb, var(--color-primary-default) 10%, var(--color-gray-50))",
276
- bgDangerSoft: "color-mix(in srgb, var(--color-danger-default) 12%, var(--color-gray-50))",
277
- textPrimary: "var(--color-gray-900)",
278
- textSecondary: "var(--color-gray-600)",
279
- textBubbleOutgoing: "var(--color-gray-50)",
280
- textBubbleIncoming: "var(--color-gray-900)",
281
- textMention: "var(--color-gray-900)",
282
- textMentionTag: "var(--color-primary-default)",
283
- textTimestamp: "var(--color-gray-500)",
284
- textSystem: "var(--color-gray-600)",
285
- textPlaceholder: "var(--color-gray-400)",
286
- borderPrimary: "var(--color-gray-200)",
287
- borderInput: "var(--color-gray-300)",
288
- borderInputFocus: "var(--color-primary-default)",
289
- borderMention: "var(--color-gray-200)",
290
- colorSending: "var(--color-gray-400)",
291
- colorSent: "var(--color-gray-500)",
292
- colorDelivered: "var(--color-primary-default)",
293
- colorRead: "var(--color-success-default)",
294
- colorFailed: "var(--color-danger-default)",
295
- colorLink: "var(--color-primary-default)",
296
- shadowPanel: "var(--size-shadow-2)",
297
- shadowMention: "var(--size-shadow-4)",
298
- avatarBg: "var(--color-primary-default)",
299
- avatarText: "var(--color-gray-50)",
300
- btnPrimaryBg: "var(--color-primary-default)",
301
- btnPrimaryText: "var(--color-gray-50)",
302
- btnPrimaryHover: "var(--color-primary-hover)",
303
- scrollbarThumb: "var(--color-gray-300)",
304
- scrollbarTrack: "var(--color-gray-100)",
305
- bgToolbar: "var(--color-gray-100)",
306
- borderToolbar: "var(--color-gray-200)",
307
- toolbarIconColor: "var(--color-gray-600)",
308
- toolbarIconHover: "var(--color-primary-default)",
309
- toolbarVoiceActive: "var(--color-danger-default)"
310
- };
311
- const darkTokens = {
312
- bgPrimary: "var(--color-gray-900)",
313
- bgSecondary: "var(--color-gray-800)",
314
- bgBubbleOutgoing: "var(--color-primary-default)",
315
- bgBubbleIncoming: "var(--color-gray-800)",
316
- bgInput: "var(--color-gray-900)",
317
- bgMention: "var(--color-gray-900)",
318
- bgMentionHover: "var(--color-gray-800)",
319
- bgMentionActive: "var(--color-primary-900)",
320
- bgLoadMore: "var(--color-gray-800)",
321
- bgSystemMessage: "var(--color-gray-800)",
322
- bgDropOverlay: "color-mix(in srgb, var(--color-primary-default) 16%, var(--color-gray-900))",
323
- bgDangerSoft: "color-mix(in srgb, var(--color-danger-default) 18%, var(--color-gray-900))",
324
- textPrimary: "var(--color-gray-50)",
325
- textSecondary: "var(--color-gray-300)",
326
- textBubbleOutgoing: "var(--color-gray-50)",
327
- textBubbleIncoming: "var(--color-gray-50)",
328
- textMention: "var(--color-gray-50)",
329
- textMentionTag: "var(--color-primary-300)",
330
- textTimestamp: "var(--color-gray-400)",
331
- textSystem: "var(--color-gray-300)",
332
- textPlaceholder: "var(--color-gray-500)",
333
- borderPrimary: "var(--color-gray-700)",
334
- borderInput: "var(--color-gray-700)",
335
- borderInputFocus: "var(--color-primary-default)",
336
- borderMention: "var(--color-gray-700)",
337
- colorSending: "var(--color-gray-500)",
338
- colorSent: "var(--color-gray-400)",
339
- colorDelivered: "var(--color-primary-300)",
340
- colorRead: "var(--color-success-default)",
341
- colorFailed: "var(--color-danger-default)",
342
- colorLink: "var(--color-primary-300)",
343
- shadowPanel: "var(--size-shadow-3)",
344
- shadowMention: "var(--size-shadow-6)",
345
- avatarBg: "var(--color-primary-default)",
346
- avatarText: "var(--color-gray-50)",
347
- btnPrimaryBg: "var(--color-primary-default)",
348
- btnPrimaryText: "var(--color-gray-50)",
349
- btnPrimaryHover: "var(--color-primary-hover)",
350
- scrollbarThumb: "var(--color-gray-700)",
351
- scrollbarTrack: "var(--color-gray-800)",
352
- bgToolbar: "var(--color-gray-800)",
353
- borderToolbar: "var(--color-gray-700)",
354
- toolbarIconColor: "var(--color-gray-300)",
355
- toolbarIconHover: "var(--color-primary-300)",
356
- toolbarVoiceActive: "var(--color-danger-default)"
357
- };
358
- //#endregion
359
- //#region src/theme/theme-manager.ts
360
- var ThemeManager = class {
361
- constructor(emitter, mode) {
362
- this.mode = "light";
363
- this.mediaQuery = null;
364
- this.mediaHandler = null;
365
- this.emitter = emitter;
366
- if (mode) this.setTheme(mode);
367
- }
368
- /** 获取当前模式 */
369
- getMode() {
370
- return this.mode;
436
+ }, $ = class {
437
+ constructor(e, t = {}) {
438
+ this.status = "idle", this.unsubscribeEvents = null, this.unsubscribeStatus = null, this.destroyed = !1, this.connectPromise = null, this.lifecycle = 0, this.transport = e, this.options = t, this.cursor = t.cursor;
371
439
  }
372
- /** 获取当前实际主题(auto 时解析为 light/dark) */
373
- getResolvedTheme() {
374
- if (this.mode === "auto") return this.getSystemTheme();
375
- return this.mode;
440
+ getStatus() {
441
+ return this.status;
376
442
  }
377
- /** 获取当前主题令牌 */
378
- getTokens() {
379
- return this.getResolvedTheme() === "dark" ? darkTokens : lightTokens;
443
+ getCursor() {
444
+ return this.cursor;
445
+ }
446
+ async connect(e) {
447
+ if (this.destroyed) throw new Error("Chat transport controller is disposed.");
448
+ if ("connected" === this.status) return;
449
+ if (this.connectPromise) return this.connectPromise;
450
+ const t = ++this.lifecycle;
451
+ return this.setStatus("connecting"), this.connectPromise = (async () => {
452
+ this.unsubscribeEvents?.(), this.unsubscribeStatus?.(), this.unsubscribeEvents = this.transport.subscribe((e) => this.handleEvent(e)), this.unsubscribeStatus = this.transport.onStatusChange?.((e) => this.setStatus(e)) ?? null;
453
+ try {
454
+ if (await this.transport.connect({
455
+ conversationId: this.options.conversationId,
456
+ cursor: this.cursor,
457
+ signal: e
458
+ }), this.destroyed || t !== this.lifecycle) return;
459
+ this.setStatus("connected");
460
+ } catch (e) {
461
+ throw this.destroyed || t !== this.lifecycle || this.setStatus("error"), e;
462
+ } finally {
463
+ this.connectPromise = null;
464
+ }
465
+ })(), this.connectPromise;
380
466
  }
381
- /** 设置主题 */
382
- setTheme(mode) {
383
- this.cleanupMediaListener();
384
- this.mode = mode;
385
- if (mode === "auto") this.setupMediaListener();
386
- this.emitter.emit("theme-change", this.getResolvedTheme(), this.getTokens());
467
+ async disconnect() {
468
+ if (!this.destroyed) {
469
+ this.lifecycle += 1, this.connectPromise = null;
470
+ try {
471
+ await this.transport.disconnect();
472
+ } finally {
473
+ this.setStatus("disconnected");
474
+ }
475
+ }
387
476
  }
388
- /** 获取系统主题偏好 */
389
- getSystemTheme() {
390
- if (typeof window === "undefined") return "light";
391
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
477
+ async send(e, t = {}) {
478
+ if (this.destroyed) throw new Error("Chat transport controller is disposed.");
479
+ if ("connected" !== this.status) throw new Error(`Chat transport is not connected: ${this.status}`);
480
+ const n = await this.transport.send(e, t);
481
+ return n && n.cursor && (this.cursor = n.cursor), n;
392
482
  }
393
- /** 监听系统主题变化 */
394
- setupMediaListener() {
395
- if (typeof window === "undefined") return;
396
- this.mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
397
- this.mediaHandler = () => {
398
- this.emitter.emit("theme-change", this.getResolvedTheme(), this.getTokens());
399
- };
400
- this.mediaQuery.addEventListener("change", this.mediaHandler);
483
+ dispose() {
484
+ this.destroyed || (this.destroyed = !0, this.lifecycle += 1, this.unsubscribeEvents?.(), this.unsubscribeStatus?.(), this.unsubscribeEvents = null, this.unsubscribeStatus = null, this.connectPromise = null, Promise.resolve(this.transport.disconnect()).catch(() => {}), this.setStatus("disconnected"));
401
485
  }
402
- /** 清理监听器 */
403
- cleanupMediaListener() {
404
- if (this.mediaQuery && this.mediaHandler) {
405
- this.mediaQuery.removeEventListener("change", this.mediaHandler);
406
- this.mediaQuery = null;
407
- this.mediaHandler = null;
408
- }
486
+ handleEvent(e) {
487
+ this.destroyed || ("cursor" in e && e.cursor && (this.cursor = e.cursor), "ack" === e.type && e.ack.cursor && (this.cursor = e.ack.cursor), "error" !== e.type || e.recoverable || this.setStatus("error"), this.options.onEvent?.(e));
409
488
  }
410
- /** 销毁 */
411
- destroy() {
412
- this.cleanupMediaListener();
489
+ setStatus(e) {
490
+ this.status !== e && (this.status = e, this.options.onStatusChange?.(e));
413
491
  }
414
492
  };
415
- //#endregion
416
- //#region src/constants/dom.ts
417
- const DEFAULT_CLASS_PREFIX = "ldesign-chat";
418
- const COMPONENT_CSS_VAR_PREFIX = "--ldesign-chat";
419
- const STYLE_ELEMENT_ID = "ldesign-chat-styles";
420
- function normalizeClassPrefix(prefix) {
421
- const trimmed = prefix?.trim();
422
- if (!trimmed) return DEFAULT_CLASS_PREFIX;
423
- if (trimmed.startsWith("ldesign-")) return trimmed;
424
- return `ldesign-${trimmed.replace(/^-+/, "")}`;
493
+ const x = /* @__PURE__ */ new WeakMap();
494
+ function w(t, n = e, r) {
495
+ const a = s(n), o = i(a), l = I(r?.ownerDocument), d = T(l);
496
+ d.set(o, (d.get(o) ?? 0) + 1), l.getElementById(o) || k(l, o, L(t, a)), r && C(r, t);
497
+ }
498
+ function M(t = e, n) {
499
+ const s = I(n), r = T(s), a = i(t), o = r.get(a) ?? 0;
500
+ o > 1 ? r.set(a, o - 1) : (r.delete(a), s.getElementById(a)?.remove());
425
501
  }
426
- function getStyleElementId(prefix = DEFAULT_CLASS_PREFIX) {
427
- const normalized = normalizeClassPrefix(prefix);
428
- return normalized === "ldesign-chat" ? STYLE_ELEMENT_ID : `${normalized}-styles`;
502
+ const E = r;
503
+ function k(e, t, n) {
504
+ let s = e.getElementById(t);
505
+ s || (s = e.createElement("style"), s.id = t, s.dataset.ldesignChat = "true", (e.head ?? e.documentElement).appendChild(s)), s.textContent = n;
429
506
  }
430
- function cx(...classes) {
431
- return classes.filter(Boolean).join(" ");
507
+ function C(e, t) {
508
+ Object.entries(S(t)).forEach(([t, n]) => {
509
+ e.style.setProperty(t, n);
510
+ });
432
511
  }
433
- //#endregion
434
- //#region src/renderer/dom-renderer.ts
435
- const SEND_ICON_SVG = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"></line><polygon points=\"22 2 15 22 11 13 2 9 22 2\"></polygon></svg>";
436
- const ICON_IMAGE = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/></svg>";
437
- const ICON_PAPERCLIP = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\"/></svg>";
438
- const ICON_MIC = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/></svg>";
439
- const ICON_MIC_OFF = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"2\" x2=\"22\" y1=\"2\" y2=\"22\"/><path d=\"M18.89 13.23A7.12 7.12 0 0 0 19 12v-2\"/><path d=\"M5 10v2a7 7 0 0 0 12 5.29\"/><path d=\"M15 9.34V5a3 3 0 0 0-5.68-1.33\"/><path d=\"M9 9v3a3 3 0 0 0 5.12 2.12\"/><line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/></svg>";
440
- const FILE_ICON = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"/></svg>";
441
- const STATUS_MAP = {
512
+ function I(e) {
513
+ if (e) return e;
514
+ if ("undefined" != typeof document) return document;
515
+ throw new Error("[Chat] Styles require a browser Document.");
516
+ }
517
+ function T(e) {
518
+ let t = x.get(e);
519
+ return t || (t = /* @__PURE__ */ new Map(), x.set(e, t)), t;
520
+ }
521
+ function S(e) {
522
+ const n = {
523
+ bgPrimary: e.bgPrimary,
524
+ bgSecondary: e.bgSecondary,
525
+ bgBubbleOutgoing: e.bgBubbleOutgoing,
526
+ bgBubbleIncoming: e.bgBubbleIncoming,
527
+ bgInput: e.bgInput,
528
+ bgMention: e.bgMention,
529
+ bgMentionHover: e.bgMentionHover,
530
+ bgMentionActive: e.bgMentionActive,
531
+ bgLoadMore: e.bgLoadMore,
532
+ bgSystemMessage: e.bgSystemMessage,
533
+ bgDropOverlay: e.bgDropOverlay,
534
+ bgDangerSoft: e.bgDangerSoft,
535
+ textPrimary: e.textPrimary,
536
+ textSecondary: e.textSecondary,
537
+ textBubbleOutgoing: e.textBubbleOutgoing,
538
+ textBubbleIncoming: e.textBubbleIncoming,
539
+ textMention: e.textMention,
540
+ textMentionTag: e.textMentionTag,
541
+ textTimestamp: e.textTimestamp,
542
+ textSystem: e.textSystem,
543
+ textPlaceholder: e.textPlaceholder,
544
+ borderPrimary: e.borderPrimary,
545
+ borderInput: e.borderInput,
546
+ borderInputFocus: e.borderInputFocus,
547
+ borderMention: e.borderMention,
548
+ colorSending: e.colorSending,
549
+ colorSent: e.colorSent,
550
+ colorDelivered: e.colorDelivered,
551
+ colorRead: e.colorRead,
552
+ colorFailed: e.colorFailed,
553
+ colorLink: e.colorLink,
554
+ shadowPanel: e.shadowPanel,
555
+ shadowMention: e.shadowMention,
556
+ avatarBg: e.avatarBg,
557
+ avatarText: e.avatarText,
558
+ btnPrimaryBg: e.btnPrimaryBg,
559
+ btnPrimaryText: e.btnPrimaryText,
560
+ btnPrimaryHover: e.btnPrimaryHover,
561
+ scrollbarThumb: e.scrollbarThumb,
562
+ scrollbarTrack: e.scrollbarTrack,
563
+ bgToolbar: e.bgToolbar,
564
+ borderToolbar: e.borderToolbar,
565
+ toolbarIconColor: e.toolbarIconColor,
566
+ toolbarIconHover: e.toolbarIconHover,
567
+ toolbarVoiceActive: e.toolbarVoiceActive
568
+ };
569
+ return Object.fromEntries(Object.entries(n).map(([e, n]) => [`${t}-${B(e)}`, n]));
570
+ }
571
+ function L(e, n) {
572
+ return `\n/* @composy/chat-core styles */\n.${n}-container,\n.${n}-container * {\n box-sizing: border-box;\n}\n\n.${n}-container {\n${s = e, Object.entries(S(s)).map(([e, t]) => ` ${e}: ${t};`).join("\n")}\n\n ${t}-font-family: var(--ld-font-family);\n ${t}-font-size: var(--ld-font-base);\n ${t}-font-size-sm: var(--ld-font-sm);\n ${t}-font-size-xs: var(--ld-font-xs);\n ${t}-font-size-lg: var(--ld-font-lg);\n ${t}-font-weight-regular: 400;\n ${t}-font-weight-medium: 500;\n ${t}-font-weight-semibold: 600;\n ${t}-line-height: var(--ld-line-normal);\n ${t}-line-height-bubble: 1.6;\n ${t}-space-1: var(--ld-spacing-3xs);\n ${t}-space-2: var(--ld-spacing-2xs);\n ${t}-space-3: var(--ld-spacing-xs);\n ${t}-space-4: var(--ld-spacing-sm);\n ${t}-space-5: var(--ld-spacing-md);\n ${t}-space-6: var(--ld-spacing-lg);\n ${t}-space-8: var(--ld-spacing-xl);\n ${t}-avatar-size: calc(var(--ld-px-40) + var(--ld-px-4));\n ${t}-mention-avatar-size: var(--ld-px-36);\n ${t}-toolbar-button-size: var(--ld-px-36);\n ${t}-input-button-size: calc(var(--ld-px-40) + var(--ld-px-4));\n ${t}-file-icon-size: var(--ld-px-48);\n ${t}-image-max-size: var(--ld-px-260);\n ${t}-panel-max-height: var(--ld-px-220);\n ${t}-textarea-min-height: calc(var(--ld-px-40) + var(--ld-px-4));\n ${t}-textarea-max-height: calc(var(--ld-px-64) + var(--ld-px-56));\n ${t}-message-max-width: 65%;\n ${t}-system-message-max-width: 80%;\n ${t}-file-bubble-min-width: var(--ld-px-220);\n ${t}-border-width: var(--ld-border-width-thin);\n ${t}-border-width-drop: var(--ld-border-width-medium);\n ${t}-radius-xs: var(--ld-radius-sm);\n ${t}-radius-sm: var(--ld-radius-md);\n ${t}-radius-md: var(--ld-radius-lg);\n ${t}-radius-lg: var(--ld-radius-xl);\n ${t}-radius-pill: var(--ld-radius-full);\n ${t}-radius-circle: var(--ld-radius-full);\n ${t}-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);\n ${t}-transition-normal: 200ms cubic-bezier(0.4, 0, 0.2, 1);\n ${t}-z-overlay: 1000;\n ${t}-z-mention: 1100;\n ${t}-icon-size: var(--ld-px-24);\n\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n overflow: hidden;\n position: relative;\n background: ${z("bg-primary")};\n color: ${z("text-primary")};\n font-family: ${z("font-family")};\n font-size: ${z("font-size")};\n line-height: ${z("line-height")};\n border: ${z("border-width")} solid ${z("border-primary")};\n border-radius: ${z("radius-md")};\n box-shadow: ${z("shadow-panel")};\n}\n\n.${n}-message-list {\n flex: 1;\n min-width: 0;\n overflow-y: auto;\n overflow-x: hidden;\n padding: ${z("space-6")};\n scroll-behavior: smooth;\n}\n\n.${n}-message-list::-webkit-scrollbar {\n width: var(--ld-px-4);\n}\n\n.${n}-message-list::-webkit-scrollbar-thumb {\n background: ${z("scrollbar-thumb")};\n border-radius: ${z("radius-pill")};\n}\n\n.${n}-message-list::-webkit-scrollbar-track {\n background: ${z("scrollbar-track")};\n}\n\n.${n}-load-more {\n margin-bottom: ${z("space-5")};\n padding: ${z("space-5")};\n text-align: center;\n color: ${z("color-link")};\n font-size: ${z("font-size-sm")};\n background: ${z("bg-load-more")};\n border-radius: ${z("radius-sm")};\n cursor: pointer;\n user-select: none;\n transition: opacity ${z("transition-fast")}, background ${z("transition-fast")};\n}\n\n.${n}-load-more:hover {\n background: ${z("bg-mention-hover")};\n}\n\n.${n}-load-more--loading {\n cursor: default;\n opacity: .6;\n}\n\n.${n}-time-divider {\n padding: ${z("space-4")} 0;\n color: ${z("text-timestamp")};\n font-size: ${z("font-size-sm")};\n text-align: center;\n user-select: none;\n}\n\n.${n}-time-divider span {\n padding: ${z("space-1")} ${z("space-5")};\n background: ${z("bg-secondary")};\n border-radius: ${z("radius-pill")};\n}\n\n.${n}-message-row {\n display: flex;\n align-items: flex-start;\n gap: ${z("space-4")};\n margin-bottom: ${z("space-5")};\n}\n\n.${n}-message-row--outgoing {\n flex-direction: row-reverse;\n}\n\n.${n}-message-row--system {\n justify-content: center;\n}\n\n.${n}-avatar {\n display: flex;\n align-items: center;\n justify-content: center;\n width: ${z("avatar-size")};\n height: ${z("avatar-size")};\n overflow: hidden;\n flex-shrink: 0;\n color: ${z("avatar-text")};\n background: ${z("avatar-bg")};\n border-radius: ${z("radius-circle")};\n font-size: ${z("font-size")};\n font-weight: ${z("font-weight-semibold")};\n}\n\n.${n}-avatar img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.${n}-message-body {\n display: flex;\n flex-direction: column;\n max-width: ${z("message-max-width")};\n min-width: 0;\n}\n\n.${n}-message-row--outgoing .${n}-message-body {\n align-items: flex-end;\n}\n\n.${n}-nickname {\n margin-bottom: ${z("space-2")};\n padding: 0 ${z("space-2")};\n color: ${z("text-secondary")};\n font-size: ${z("font-size-sm")};\n}\n\n.${n}-bubble {\n position: relative;\n max-width: 100%;\n padding: ${z("space-5")} ${z("space-6")};\n border-radius: ${z("radius-lg")};\n word-break: break-word;\n overflow-wrap: unknownwhere;\n white-space: pre-wrap;\n line-height: ${z("line-height-bubble")};\n}\n\n.${n}-bubble--incoming {\n color: ${z("text-bubble-incoming")};\n background: ${z("bg-bubble-incoming")};\n border-top-left-radius: ${z("radius-xs")};\n}\n\n.${n}-bubble--outgoing {\n color: ${z("text-bubble-outgoing")};\n background: ${z("bg-bubble-outgoing")};\n border-top-right-radius: ${z("radius-xs")};\n}\n\n.${n}-bubble--image {\n padding: ${z("space-2")};\n background: unset;\n}\n\n.${n}-bubble--image img {\n display: block;\n max-width: ${z("image-max-size")};\n max-height: ${z("image-max-size")};\n border-radius: ${z("radius-md")};\n cursor: pointer;\n}\n\n.${n}-bubble--file {\n display: flex;\n align-items: center;\n gap: ${z("space-5")};\n min-width: ${z("file-bubble-min-width")};\n}\n\n.${n}-file-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: ${z("file-icon-size")};\n height: ${z("file-icon-size")};\n flex-shrink: 0;\n background: ${z("bg-secondary")};\n border-radius: ${z("radius-sm")};\n font-size: ${z("font-size-lg")};\n}\n\n.${n}-file-info {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n}\n\n.${n}-file-name {\n overflow: hidden;\n font-size: ${z("font-size")};\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.${n}-file-size {\n margin-top: ${z("space-1")};\n color: ${z("text-secondary")};\n font-size: ${z("font-size-sm")};\n}\n\n.${n}-system-message {\n max-width: ${z("system-message-max-width")};\n padding: ${z("space-2")} ${z("space-6")};\n color: ${z("text-system")};\n background: ${z("bg-system-message")};\n border-radius: ${z("radius-pill")};\n font-size: ${z("font-size-sm")};\n text-align: center;\n}\n\n.${n}-message-footer {\n display: flex;\n align-items: center;\n gap: ${z("space-3")};\n margin-top: ${z("space-1")};\n padding: 0 ${z("space-2")};\n}\n\n.${n}-message-row--outgoing .${n}-message-footer {\n flex-direction: row-reverse;\n}\n\n.${n}-timestamp,\n.${n}-status {\n font-size: ${z("font-size-xs")};\n}\n\n.${n}-timestamp {\n color: ${z("text-timestamp")};\n}\n\n.${n}-status--sending { color: ${z("color-sending")}; }\n.${n}-status--sent { color: ${z("color-sent")}; }\n.${n}-status--delivered { color: ${z("color-delivered")}; }\n.${n}-status--read { color: ${z("color-read")}; }\n.${n}-status--failed {\n color: ${z("color-failed")};\n cursor: pointer;\n}\n\n.${n}-mention-tag,\n.${n}-link {\n color: ${z("text-mention-tag")};\n font-weight: ${z("font-weight-medium")};\n}\n\n.${n}-mention-tag {\n cursor: pointer;\n}\n\n.${n}-mention-tag:hover,\n.${n}-link:hover {\n text-decoration: underline;\n}\n\n.${n}-input-area {\n display: flex;\n align-items: flex-end;\n gap: ${z("space-4")};\n position: relative;\n padding: ${z("space-5")} ${z("space-6")};\n border-top: ${z("border-width")} solid ${z("border-primary")};\n background: ${z("bg-input")};\n}\n\n.${n}-input-wrapper {\n flex: 1;\n min-width: 0;\n position: relative;\n}\n\n.${n}-textarea {\n width: 100%;\n min-height: ${z("textarea-min-height")};\n max-height: ${z("textarea-max-height")};\n padding: ${z("space-4")} ${z("space-5")};\n overflow-y: auto;\n resize: none;\n outline: none;\n color: ${z("text-primary")};\n background: ${z("bg-primary")};\n border: ${z("border-width")} solid ${z("border-input")};\n border-radius: ${z("radius-md")};\n font-family: inherit;\n font-size: ${z("font-size")};\n line-height: ${z("line-height")};\n transition: border-color ${z("transition-fast")}, box-shadow ${z("transition-fast")};\n}\n\n.${n}-textarea:focus {\n border-color: ${z("border-input-focus")};\n box-shadow: 0 0 0 ${z("border-width-drop")} ${z("bg-drop-overlay")};\n}\n\n.${n}-textarea::placeholder {\n color: ${z("text-placeholder")};\n}\n\n.${n}-send-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: ${z("input-button-size")};\n height: ${z("input-button-size")};\n flex-shrink: 0;\n color: ${z("btn-primary-text")};\n background: ${z("btn-primary-bg")};\n border: 0;\n border-radius: ${z("radius-md")};\n cursor: pointer;\n transition: background ${z("transition-fast")}, opacity ${z("transition-fast")};\n}\n\n.${n}-send-btn:hover {\n background: ${z("btn-primary-hover")};\n}\n\n.${n}-send-btn:disabled {\n cursor: default;\n opacity: .5;\n}\n\n.${n}-send-btn svg {\n width: var(--ldesign-chat-icon-size);\n height: var(--ldesign-chat-icon-size);\n}\n\n.${n}-mention-panel {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 100%;\n z-index: ${z("z-mention")};\n max-height: ${z("panel-max-height")};\n margin-bottom: ${z("space-2")};\n overflow-y: auto;\n background: ${z("bg-mention")};\n border: ${z("border-width")} solid ${z("border-mention")};\n border-radius: ${z("radius-md")};\n box-shadow: ${z("shadow-mention")};\n}\n\n.${n}-mention-item {\n display: flex;\n align-items: center;\n gap: ${z("space-4")};\n padding: ${z("space-4")} ${z("space-5")};\n color: ${z("text-mention")};\n cursor: pointer;\n transition: background ${z("transition-fast")};\n}\n\n.${n}-mention-item:hover {\n background: ${z("bg-mention-hover")};\n}\n\n.${n}-mention-item--active {\n background: ${z("bg-mention-active")};\n}\n\n.${n}-mention-item-avatar {\n display: flex;\n align-items: center;\n justify-content: center;\n width: ${z("mention-avatar-size")};\n height: ${z("mention-avatar-size")};\n flex-shrink: 0;\n overflow: hidden;\n color: ${z("avatar-text")};\n background: ${z("avatar-bg")};\n border-radius: ${z("radius-circle")};\n font-size: ${z("font-size-sm")};\n font-weight: ${z("font-weight-semibold")};\n}\n\n.${n}-mention-item-avatar img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.${n}-mention-item-name {\n font-size: ${z("font-size")};\n}\n\n.${n}-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: ${z("text-secondary")};\n font-size: ${z("font-size")};\n}\n\n.${n}-toolbar {\n display: flex;\n align-items: center;\n gap: ${z("space-1")};\n padding: ${z("space-3")} ${z("space-4")};\n background: ${z("bg-toolbar")};\n border-bottom: ${z("border-width")} solid ${z("border-toolbar")};\n border-radius: ${z("radius-md")} ${z("radius-md")} 0 0;\n}\n\n.${n}-toolbar-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: ${z("toolbar-button-size")};\n height: ${z("toolbar-button-size")};\n flex-shrink: 0;\n padding: 0;\n color: ${z("toolbar-icon-color")};\n background: unset;\n border: 0;\n border-radius: ${z("radius-sm")};\n cursor: pointer;\n transition: background ${z("transition-fast")}, color ${z("transition-fast")};\n}\n\n.${n}-toolbar-btn svg {\n width: var(--ldesign-chat-space-6);\n height: var(--ldesign-chat-space-6);\n}\n\n.${n}-toolbar-btn:hover {\n color: ${z("toolbar-icon-hover")};\n background: ${z("bg-drop-overlay")};\n}\n\n.${n}-toolbar-btn--active {\n color: ${z("toolbar-voice-active")};\n background: ${z("bg-danger-soft")};\n}\n\n.${n}-toolbar-btn--active:hover {\n color: ${z("toolbar-voice-active")};\n background: ${z("bg-danger-soft")};\n}\n\n.${n}-drop-overlay {\n position: absolute;\n inset: 0;\n z-index: ${z("z-overlay")};\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n background: ${z("bg-drop-overlay")};\n border: ${z("border-width-drop")} dashed ${z("toolbar-icon-hover")};\n border-radius: ${z("radius-md")};\n}\n\n.${n}-drop-overlay span {\n color: ${z("toolbar-icon-hover")};\n font-size: ${z("font-size-sm")};\n font-weight: ${z("font-weight-medium")};\n}\n\n@keyframes ${n}-blink {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0; }\n}\n\n.${n}-bubble--streaming::after {\n content: '';\n display: inline-block;\n width: ${z("border-width-drop")};\n height: 1em;\n margin-left: ${z("space-1")};\n vertical-align: text-bottom;\n background: currentColor;\n animation: ${n}-blink 800ms infinite;\n}\n`;
573
+ var s;
574
+ }
575
+ function z(e) {
576
+ return `var(${t}-${e})`;
577
+ }
578
+ function B(e) {
579
+ return e.replace(/[A-Z]/g, (e) => `-${e.toLowerCase()}`);
580
+ }
581
+ const A = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/></svg>", P = /* @__PURE__ */ new Set([
582
+ "a",
583
+ "b",
584
+ "blockquote",
585
+ "br",
586
+ "code",
587
+ "div",
588
+ "em",
589
+ "h1",
590
+ "h2",
591
+ "h3",
592
+ "h4",
593
+ "h5",
594
+ "h6",
595
+ "i",
596
+ "img",
597
+ "li",
598
+ "ol",
599
+ "p",
600
+ "pre",
601
+ "s",
602
+ "span",
603
+ "strong",
604
+ "u",
605
+ "ul"
606
+ ]), D = /* @__PURE__ */ new Set([
607
+ "iframe",
608
+ "object",
609
+ "embed",
610
+ "script",
611
+ "style",
612
+ "svg",
613
+ "math",
614
+ "template",
615
+ "form",
616
+ "input",
617
+ "button",
618
+ "link",
619
+ "meta",
620
+ "base"
621
+ ]), q = {
442
622
  sending: "...",
443
623
  sent: "✓",
444
624
  delivered: "✓✓",
445
625
  read: "✓✓",
446
626
  failed: "!"
447
627
  };
448
- var DOMRenderer = class {
449
- constructor(container, config, emitter, mentionEngine) {
450
- this.inputArea = null;
451
- this.textareaEl = null;
452
- this.sendBtn = null;
453
- this.mentionPanel = null;
454
- this.loadMoreEl = null;
455
- this.toolbarEl = null;
456
- this.dropOverlay = null;
457
- this.voiceBtn = null;
458
- this.loadingMore = false;
459
- this.isRecording = false;
460
- this.recognition = null;
461
- this.lastVoiceTranscript = "";
462
- this.container = container;
463
- this.config = config;
464
- this.emitter = emitter;
465
- this.mentionEngine = mentionEngine;
466
- this.prefix = normalizeClassPrefix(config.classPrefix ?? "ldesign-chat");
628
+ var R = class {
629
+ constructor(e, t, n, i) {
630
+ this.inputArea = null, this.textareaEl = null, this.sendBtn = null, this.mentionPanel = null, this.loadMoreEl = null, this.toolbarEl = null, this.dropOverlay = null, this.voiceBtn = null, this.loadingMore = !1, this.isRecording = !1, this.recognition = null, this.lastVoiceTranscript = "", this.destroyed = !1, this.pendingFileInputs = /* @__PURE__ */ new Set(), this.container = e, this.document = e.ownerDocument, this.config = t, this.emitter = n, this.mentionEngine = i, this.prefix = s(t.classPrefix ?? "ldesign-chat");
467
631
  }
468
632
  init() {
469
- this.container.innerHTML = "";
470
- this.root = this.el("div", `${this.prefix}-container`);
471
- this.messageListEl = this.el("div", `${this.prefix}-message-list`);
472
- this.root.appendChild(this.messageListEl);
473
- if (this.config.showInput !== false) this.renderInputArea();
474
- this.container.appendChild(this.root);
475
- this.setupEventListeners();
476
- }
477
- updateMessageContent(id, content) {
478
- const bubble = this.messageListEl.querySelector(`[data-msg-id="${this.escapeAttr(id)}"]`)?.querySelector(`.${this.prefix}-bubble`);
479
- if (!bubble) return;
480
- bubble.innerHTML = this.renderTextContent(content);
481
- }
482
- renderMessages(messages) {
483
- this.messageListEl.innerHTML = "";
484
- if (this.config.enableLoadMore) this.renderLoadMoreButton();
485
- if (messages.length === 0) {
486
- this.renderEmpty();
487
- return;
488
- }
489
- let lastTimestamp = 0;
490
- const interval = this.config.timeGroupInterval ?? 3e5;
491
- for (const message of messages) {
492
- if (this.config.showTimestamp !== false && message.timestamp - lastTimestamp > interval) {
493
- this.renderTimeDivider(message.timestamp);
494
- lastTimestamp = message.timestamp;
495
- }
496
- this.renderMessageRow(message);
497
- }
633
+ this.destroyed || (this.container.innerHTML = "", this.root = this.el("div", `${this.prefix}-container`), this.messageListEl = this.el("div", `${this.prefix}-message-list`), this.root.appendChild(this.messageListEl), !1 !== this.config.showInput && this.renderInputArea(), this.container.appendChild(this.root), this.setupEventListeners());
634
+ }
635
+ updateMessageContent(e, t) {
636
+ const n = this.getMessageRow(e)?.querySelector(`.${this.prefix}-bubble`);
637
+ n && (n.innerHTML = this.renderTextContent(t));
638
+ }
639
+ renderMessages(e) {
640
+ if (this.messageListEl.innerHTML = "", this.config.enableLoadMore && this.renderLoadMoreButton(), 0 === e.length) return void this.renderEmpty();
641
+ let t = 0;
642
+ const n = this.config.timeGroupInterval ?? 3e5;
643
+ for (const s of e) !1 !== this.config.showTimestamp && s.timestamp - t > n && (this.renderTimeDivider(s.timestamp), t = s.timestamp), this.renderMessageRow(s);
498
644
  }
499
- appendMessage(message) {
645
+ appendMessage(e) {
500
646
  this.removeEmpty();
501
- const messageEls = this.getMessageElements();
502
- const lastTime = messageEls.length > 0 ? Number.parseInt(messageEls[messageEls.length - 1].dataset.timestamp ?? "0", 10) : 0;
503
- const interval = this.config.timeGroupInterval ?? 3e5;
504
- if (this.config.showTimestamp !== false && message.timestamp - lastTime > interval) this.renderTimeDivider(message.timestamp);
505
- this.renderMessageRow(message);
506
- }
507
- prependMessages(messages) {
508
- if (!messages.length) return;
647
+ const t = this.getMessageElements(), n = t.length > 0 ? Number.parseInt(t[t.length - 1].dataset.timestamp ?? "0", 10) : 0, s = this.config.timeGroupInterval ?? 3e5;
648
+ !1 !== this.config.showTimestamp && e.timestamp - n > s && this.renderTimeDivider(e.timestamp), this.renderMessageRow(e);
649
+ }
650
+ prependMessages(e) {
651
+ if (!e.length) return;
509
652
  this.removeEmpty();
510
- const scrollHeight = this.messageListEl.scrollHeight;
511
- const scrollTop = this.messageListEl.scrollTop;
512
- const fragment = document.createDocumentFragment();
513
- let lastTimestamp = 0;
514
- const interval = this.config.timeGroupInterval ?? 3e5;
515
- for (const message of messages) {
516
- if (this.config.showTimestamp !== false && message.timestamp - lastTimestamp > interval) {
517
- fragment.appendChild(this.createTimeDivider(message.timestamp));
518
- lastTimestamp = message.timestamp;
519
- }
520
- fragment.appendChild(this.createMessageRow(message));
521
- }
522
- const firstMessage = this.messageListEl.querySelector(`.${this.prefix}-message-row, .${this.prefix}-time-divider`);
523
- if (firstMessage) firstMessage.before(fragment);
524
- else if (this.loadMoreEl) this.loadMoreEl.after(fragment);
525
- else this.messageListEl.appendChild(fragment);
526
- this.messageListEl.scrollTop = scrollTop + (this.messageListEl.scrollHeight - scrollHeight);
527
- }
528
- updateMessage(id, message) {
529
- const rowEl = this.messageListEl.querySelector(`[data-msg-id="${this.escapeAttr(id)}"]`);
530
- if (!rowEl) return;
531
- rowEl.replaceWith(this.createMessageRow(message));
532
- }
533
- removeMessage(id) {
534
- this.messageListEl.querySelector(`[data-msg-id="${this.escapeAttr(id)}"]`)?.remove();
535
- if (this.getMessageElements().length === 0) this.renderEmpty();
536
- }
537
- scrollToBottom(smooth = true) {
538
- if (typeof this.messageListEl.scrollTo === "function") {
539
- this.messageListEl.scrollTo({
540
- top: this.messageListEl.scrollHeight,
541
- behavior: smooth ? "smooth" : "auto"
542
- });
543
- return;
544
- }
545
- this.messageListEl.scrollTop = this.messageListEl.scrollHeight;
653
+ const t = this.messageListEl.scrollHeight, n = this.messageListEl.scrollTop, s = this.document.createDocumentFragment();
654
+ let i = 0;
655
+ const r = this.config.timeGroupInterval ?? 3e5;
656
+ for (const t of e) !1 !== this.config.showTimestamp && t.timestamp - i > r && (s.appendChild(this.createTimeDivider(t.timestamp)), i = t.timestamp), s.appendChild(this.createMessageRow(t));
657
+ const a = this.messageListEl.querySelector(`.${this.prefix}-message-row, .${this.prefix}-time-divider`);
658
+ a ? a.before(s) : this.loadMoreEl ? this.loadMoreEl.after(s) : this.messageListEl.appendChild(s), this.messageListEl.scrollTop = n + (this.messageListEl.scrollHeight - t);
659
+ }
660
+ updateMessage(e, t) {
661
+ const n = this.getMessageRow(e);
662
+ n && n.replaceWith(this.createMessageRow({
663
+ ...t,
664
+ id: e
665
+ }));
666
+ }
667
+ removeMessage(e) {
668
+ this.getMessageRow(e)?.remove(), this.refreshTimeDividers(), 0 === this.getMessageElements().length ? this.renderEmpty() : this.removeEmpty();
669
+ }
670
+ scrollToBottom(e = !0) {
671
+ "function" != typeof this.messageListEl.scrollTo ? this.messageListEl.scrollTop = this.messageListEl.scrollHeight : this.messageListEl.scrollTo({
672
+ top: this.messageListEl.scrollHeight,
673
+ behavior: e ? "smooth" : "auto"
674
+ });
675
+ }
676
+ isAtBottom(e = 4) {
677
+ return this.messageListEl.scrollHeight - this.messageListEl.scrollTop - this.messageListEl.clientHeight <= e;
546
678
  }
547
- isAtBottom(threshold = 4) {
548
- return this.messageListEl.scrollHeight - this.messageListEl.scrollTop - this.messageListEl.clientHeight <= threshold;
679
+ setLoadingMore(e) {
680
+ this.loadingMore = e, this.loadMoreEl && (this.loadMoreEl.textContent = e ? "加载中..." : this.config.loadMoreText ?? "加载更多", this.loadMoreEl.classList.toggle(`${this.prefix}-load-more--loading`, e));
549
681
  }
550
- setLoadingMore(loading) {
551
- this.loadingMore = loading;
552
- if (!this.loadMoreEl) return;
553
- this.loadMoreEl.textContent = loading ? "加载中..." : this.config.loadMoreText ?? "加载更多";
554
- this.loadMoreEl.classList.toggle(`${this.prefix}-load-more--loading`, loading);
682
+ setThemeTokens(e) {
683
+ this.root && C(this.root, e);
684
+ }
685
+ getRootElement() {
686
+ return this.root;
555
687
  }
556
688
  focusInput() {
557
689
  this.textareaEl?.focus();
558
690
  }
559
- setInputContent(content) {
560
- if (!this.textareaEl) return;
561
- this.textareaEl.value = this.limitInput(content);
562
- this.autoResize();
563
- this.updateSendButtonState();
564
- this.config.onInputChange?.(this.textareaEl.value);
691
+ setInputContent(e) {
692
+ this.textareaEl && (this.textareaEl.value = this.limitInput(e), this.autoResize(), this.updateSendButtonState(), this.config.onInputChange?.(this.textareaEl.value));
565
693
  }
566
694
  getInputContent() {
567
695
  return this.textareaEl?.value ?? "";
@@ -569,1300 +697,898 @@
569
697
  clearInput() {
570
698
  this.setInputContent("");
571
699
  }
572
- renderMentionPanel(members, activeIndex) {
573
- if (!this.mentionPanel) return;
574
- if (members.length === 0) {
575
- this.mentionPanel.style.display = "none";
576
- return;
577
- }
578
- this.mentionPanel.style.display = "block";
579
- this.mentionPanel.innerHTML = "";
580
- members.forEach((member, index) => {
581
- const item = this.el("div", cx(`${this.prefix}-mention-item`, index === activeIndex && `${this.prefix}-mention-item--active`));
582
- item.setAttribute("role", "option");
583
- item.setAttribute("aria-selected", String(index === activeIndex));
584
- const avatar = this.el("div", `${this.prefix}-mention-item-avatar`);
585
- if (member.avatar) {
586
- const img = document.createElement("img");
587
- img.src = member.avatar;
588
- img.alt = "";
589
- avatar.appendChild(img);
590
- } else avatar.textContent = member.name[0]?.toUpperCase() ?? "";
591
- item.appendChild(avatar);
592
- const name = this.el("span", `${this.prefix}-mention-item-name`);
593
- name.textContent = member.name;
594
- item.appendChild(name);
595
- item.addEventListener("click", () => {
596
- this.mentionEngine.select(index);
597
- });
598
- this.mentionPanel?.appendChild(item);
599
- });
700
+ renderMentionPanel(e, t) {
701
+ this.mentionPanel && (0 !== e.length ? (this.mentionPanel.style.display = "block", this.mentionPanel.innerHTML = "", e.forEach((e, n) => {
702
+ const s = this.el("div", r(`${this.prefix}-mention-item`, n === t && `${this.prefix}-mention-item--active`));
703
+ s.setAttribute("role", "option"), s.setAttribute("aria-selected", String(n === t));
704
+ const i = this.el("div", `${this.prefix}-mention-item-avatar`);
705
+ if (e.avatar) {
706
+ const t = this.document.createElement("img");
707
+ t.src = this.safeResourceUrl(e.avatar), t.alt = "", i.appendChild(t);
708
+ } else i.textContent = e.name[0]?.toUpperCase() ?? "";
709
+ s.appendChild(i);
710
+ const a = this.el("span", `${this.prefix}-mention-item-name`);
711
+ a.textContent = e.name, s.appendChild(a), s.addEventListener("mousedown", (e) => e.preventDefault()), s.addEventListener("click", () => {
712
+ const e = this.mentionEngine.getState().triggerPosition, t = this.textareaEl?.selectionEnd, s = this.mentionEngine.select(n);
713
+ s && this.insertMention(s, e, t);
714
+ }), this.mentionPanel?.appendChild(s);
715
+ })) : this.mentionPanel.style.display = "none");
600
716
  }
601
717
  hideMentionPanel() {
602
- if (this.mentionPanel) this.mentionPanel.style.display = "none";
718
+ this.mentionPanel && (this.mentionPanel.style.display = "none");
603
719
  }
604
720
  destroy() {
605
- this.stopVoice();
606
- this.container.innerHTML = "";
721
+ this.destroyed || (this.destroyed = !0, this.stopVoice(), this.finishVoice(), this.pendingFileInputs.forEach((e) => e.remove()), this.pendingFileInputs.clear(), this.root?.remove());
607
722
  }
608
723
  renderInputArea() {
609
724
  this.inputArea = this.el("div", `${this.prefix}-input-area`);
610
- const wrapper = this.el("div", `${this.prefix}-input-wrapper`);
611
- this.mentionPanel = this.el("div", `${this.prefix}-mention-panel`);
612
- this.mentionPanel.style.display = "none";
613
- this.mentionPanel.setAttribute("role", "listbox");
614
- wrapper.appendChild(this.mentionPanel);
615
- if (this.config.enableToolbar !== false) this.renderToolbar(wrapper);
616
- this.textareaEl = document.createElement("textarea");
617
- this.textareaEl.className = `${this.prefix}-textarea`;
618
- this.textareaEl.placeholder = this.config.inputPlaceholder ?? "输入消息...";
619
- this.textareaEl.rows = 1;
620
- if (this.config.maxInputLength && this.config.maxInputLength > 0) this.textareaEl.maxLength = this.config.maxInputLength;
621
- wrapper.appendChild(this.textareaEl);
622
- this.dropOverlay = this.el("div", `${this.prefix}-drop-overlay`);
623
- this.dropOverlay.innerHTML = "<span>拖放文件到这里</span>";
624
- this.dropOverlay.style.display = "none";
625
- wrapper.appendChild(this.dropOverlay);
626
- this.inputArea.appendChild(wrapper);
627
- this.sendBtn = document.createElement("button");
628
- this.sendBtn.className = `${this.prefix}-send-btn`;
629
- this.sendBtn.innerHTML = SEND_ICON_SVG;
630
- this.sendBtn.title = "发送";
631
- this.sendBtn.type = "button";
632
- this.sendBtn.disabled = true;
633
- this.inputArea.appendChild(this.sendBtn);
634
- this.root.appendChild(this.inputArea);
635
- }
636
- renderToolbar(wrapper) {
637
- const toolbar = this.config.toolbarConfig ?? {};
638
- this.toolbarEl = this.el("div", `${this.prefix}-toolbar`);
639
- if (toolbar.image !== false) {
640
- const imageBtn = this.createToolbarBtn(ICON_IMAGE, "插入图片");
641
- imageBtn.addEventListener("click", () => this.openFilePicker("image/*"));
642
- this.toolbarEl.appendChild(imageBtn);
725
+ const e = this.el("div", `${this.prefix}-input-wrapper`);
726
+ this.mentionPanel = this.el("div", `${this.prefix}-mention-panel`), this.mentionPanel.style.display = "none", this.mentionPanel.setAttribute("role", "listbox"), e.appendChild(this.mentionPanel), !1 !== this.config.enableToolbar && this.renderToolbar(e), this.textareaEl = this.document.createElement("textarea"), this.textareaEl.className = `${this.prefix}-textarea`, this.textareaEl.placeholder = this.config.inputPlaceholder ?? "输入消息...", this.textareaEl.rows = 1, this.config.maxInputLength && this.config.maxInputLength > 0 && (this.textareaEl.maxLength = this.config.maxInputLength), e.appendChild(this.textareaEl), this.dropOverlay = this.el("div", `${this.prefix}-drop-overlay`), this.dropOverlay.innerHTML = "<span>拖放文件到这里</span>", this.dropOverlay.style.display = "none", e.appendChild(this.dropOverlay), this.inputArea.appendChild(e), this.sendBtn = this.document.createElement("button"), this.sendBtn.className = `${this.prefix}-send-btn`, this.sendBtn.innerHTML = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"></line><polygon points=\"22 2 15 22 11 13 2 9 22 2\"></polygon></svg>", this.sendBtn.title = "发送", this.sendBtn.type = "button", this.sendBtn.disabled = !0, this.inputArea.appendChild(this.sendBtn), this.root.appendChild(this.inputArea);
727
+ }
728
+ renderToolbar(e) {
729
+ const t = this.config.toolbarConfig ?? {};
730
+ if (this.toolbarEl = this.el("div", `${this.prefix}-toolbar`), !1 !== t.image) {
731
+ const e = this.createToolbarBtn("<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/></svg>", "插入图片");
732
+ e.addEventListener("click", () => this.openFilePicker("image/*")), this.toolbarEl.appendChild(e);
643
733
  }
644
- if (toolbar.file !== false) {
645
- const fileBtn = this.createToolbarBtn(ICON_PAPERCLIP, "插入文件");
646
- fileBtn.addEventListener("click", () => this.openFilePicker("*/*"));
647
- this.toolbarEl.appendChild(fileBtn);
734
+ if (!1 !== t.file) {
735
+ const e = this.createToolbarBtn("<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\"/></svg>", "插入文件");
736
+ e.addEventListener("click", () => this.openFilePicker("*/*")), this.toolbarEl.appendChild(e);
648
737
  }
649
- if (toolbar.voice !== false) {
650
- this.voiceBtn = this.createToolbarBtn(ICON_MIC, "语音输入");
651
- this.voiceBtn.addEventListener("click", () => this.toggleVoice());
652
- this.toolbarEl.appendChild(this.voiceBtn);
653
- }
654
- wrapper.insertBefore(this.toolbarEl, wrapper.firstChild);
655
- }
656
- createToolbarBtn(iconSvg, title) {
657
- const btn = document.createElement("button");
658
- btn.className = `${this.prefix}-toolbar-btn`;
659
- btn.innerHTML = iconSvg;
660
- btn.title = title;
661
- btn.type = "button";
662
- return btn;
663
- }
664
- openFilePicker(accept) {
665
- const input = document.createElement("input");
666
- input.type = "file";
667
- input.accept = accept;
668
- input.multiple = true;
669
- input.style.display = "none";
670
- input.addEventListener("change", () => {
671
- const files = Array.from(input.files ?? []);
672
- input.remove();
673
- if (!files.length) return;
674
- const isImage = accept.startsWith("image");
675
- this.emitter.emit(isImage ? "toolbar-image" : "toolbar-file", files);
676
- if (isImage) this.config.onImageUpload?.(files);
677
- else this.config.onFileUpload?.(files);
678
- });
679
- document.body.appendChild(input);
680
- input.click();
738
+ !1 !== t.voice && (this.voiceBtn = this.createToolbarBtn(A, "语音输入"), this.voiceBtn.addEventListener("click", () => this.toggleVoice()), this.toolbarEl.appendChild(this.voiceBtn)), e.insertBefore(this.toolbarEl, e.firstChild);
739
+ }
740
+ createToolbarBtn(e, t) {
741
+ const n = this.document.createElement("button");
742
+ return n.className = `${this.prefix}-toolbar-btn`, n.innerHTML = e, n.title = t, n.type = "button", n;
743
+ }
744
+ openFilePicker(e) {
745
+ const t = this.document.createElement("input");
746
+ t.type = "file", t.accept = e, t.multiple = !0, t.style.display = "none", this.pendingFileInputs.add(t);
747
+ const n = () => {
748
+ t.remove(), this.pendingFileInputs.delete(t);
749
+ };
750
+ t.addEventListener("change", () => {
751
+ const s = Array.from(t.files ?? []);
752
+ if (n(), !s.length) return;
753
+ const i = e.startsWith("image");
754
+ this.emitter.emit(i ? "toolbar-image" : "toolbar-file", s), i ? this.config.onImageUpload?.(s) : this.config.onFileUpload?.(s);
755
+ }), t.addEventListener("cancel", n), (this.document.body ?? this.document.documentElement).appendChild(t), t.click();
681
756
  }
682
757
  toggleVoice() {
683
- if (this.isRecording) this.stopVoice();
684
- else this.startVoice();
758
+ this.isRecording ? this.stopVoice() : this.startVoice();
685
759
  }
686
760
  startVoice() {
687
- if (typeof window === "undefined") return;
688
- const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
689
- if (!SpeechRecognition) {
690
- this.emitter.emit("toolbar-voice-unsupported");
691
- this.config.onVoiceUnsupported?.();
692
- return;
693
- }
761
+ const e = this.document.defaultView, t = e?.SpeechRecognition || e?.webkitSpeechRecognition;
762
+ if (!t) return this.emitter.emit("toolbar-voice-unsupported"), void this.config.onVoiceUnsupported?.();
694
763
  try {
695
764
  this.lastVoiceTranscript = "";
696
- this.recognition = new SpeechRecognition();
697
- this.recognition.continuous = false;
698
- this.recognition.interimResults = true;
699
- this.recognition.lang = this.config.voiceLang ?? "zh-CN";
700
- this.recognition.onresult = (event) => {
701
- let transcript = "";
702
- for (let i = 0; i < event.results.length; i++) transcript += event.results[i][0].transcript;
703
- this.lastVoiceTranscript = transcript;
704
- if (event.results[event.results.length - 1]?.isFinal && this.textareaEl) this.insertTextAtCursor(transcript);
705
- };
706
- this.recognition.onend = () => {
707
- this.finishVoice();
708
- this.emitter.emit("toolbar-voice-end", this.lastVoiceTranscript);
709
- this.config.onVoiceEnd?.(this.lastVoiceTranscript);
710
- };
711
- this.recognition.onerror = (error) => {
712
- this.finishVoice();
713
- this.emitter.emit("toolbar-voice-error", error);
714
- this.config.onVoiceError?.(error);
715
- };
716
- this.isRecording = true;
717
- this.voiceBtn?.classList.add(`${this.prefix}-toolbar-btn--active`);
718
- if (this.voiceBtn) this.voiceBtn.innerHTML = ICON_MIC_OFF;
719
- this.recognition.start();
720
- this.emitter.emit("toolbar-voice-start");
721
- this.config.onVoiceStart?.();
722
- } catch (error) {
723
- this.finishVoice();
724
- this.emitter.emit("toolbar-voice-error", error);
725
- this.config.onVoiceError?.(error);
765
+ const e = new t(), n = /* @__PURE__ */ new Map();
766
+ e.continuous = !1, e.interimResults = !0, e.lang = this.config.voiceLang ?? "zh-CN", this.recognition = e, e.onresult = (t) => {
767
+ if (this.destroyed || this.recognition !== e) return;
768
+ const s = [];
769
+ for (let e = Math.max(0, t.resultIndex ?? 0); e < t.results.length; e++) {
770
+ const i = t.results[e];
771
+ i?.isFinal && !n.has(e) && (n.set(e, i[0].transcript), s.push(i[0].transcript));
772
+ }
773
+ this.lastVoiceTranscript = [...n.entries()].sort(([e], [t]) => e - t).map(([, e]) => e).join(""), s.length && this.textareaEl && this.insertTextAtCursor(s.join(""));
774
+ }, e.onend = () => {
775
+ if (this.destroyed || this.recognition !== e) return;
776
+ const t = this.lastVoiceTranscript;
777
+ this.finishVoice(), this.emitter.emit("toolbar-voice-end", t), this.config.onVoiceEnd?.(t);
778
+ }, e.onerror = (t) => {
779
+ this.destroyed || this.recognition !== e || (this.finishVoice(), this.emitter.emit("toolbar-voice-error", t), this.config.onVoiceError?.(t));
780
+ }, this.isRecording = !0, this.voiceBtn?.classList.add(`${this.prefix}-toolbar-btn--active`), this.voiceBtn && (this.voiceBtn.innerHTML = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"2\" x2=\"22\" y1=\"2\" y2=\"22\"/><path d=\"M18.89 13.23A7.12 7.12 0 0 0 19 12v-2\"/><path d=\"M5 10v2a7 7 0 0 0 12 5.29\"/><path d=\"M15 9.34V5a3 3 0 0 0-5.68-1.33\"/><path d=\"M9 9v3a3 3 0 0 0 5.12 2.12\"/><line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/></svg>"), e.start(), this.emitter.emit("toolbar-voice-start"), this.config.onVoiceStart?.();
781
+ } catch (e) {
782
+ this.finishVoice(), this.emitter.emit("toolbar-voice-error", e), this.config.onVoiceError?.(e);
726
783
  }
727
784
  }
728
785
  stopVoice() {
729
786
  if (!this.recognition) return;
730
- const recognition = this.recognition;
731
- this.recognition = null;
787
+ const e = this.recognition;
732
788
  try {
733
- recognition.stop();
789
+ e.stop();
734
790
  } catch {
735
791
  this.finishVoice();
736
792
  }
737
793
  }
738
794
  finishVoice() {
739
- this.isRecording = false;
740
- this.recognition = null;
741
- this.voiceBtn?.classList.remove(`${this.prefix}-toolbar-btn--active`);
742
- if (this.voiceBtn) this.voiceBtn.innerHTML = ICON_MIC;
795
+ this.isRecording = !1, this.recognition = null, this.voiceBtn?.classList.remove(`${this.prefix}-toolbar-btn--active`), this.voiceBtn && (this.voiceBtn.innerHTML = A);
743
796
  }
744
797
  setupEventListeners() {
745
- if (this.textareaEl) {
746
- this.textareaEl.addEventListener("input", () => {
747
- const limited = this.limitInput(this.textareaEl?.value ?? "");
748
- if (this.textareaEl && limited !== this.textareaEl.value) this.textareaEl.value = limited;
749
- this.autoResize();
750
- this.updateSendButtonState();
751
- this.config.onInputChange?.(this.textareaEl?.value ?? "");
752
- if (this.config.enableMention && this.textareaEl) this.mentionEngine.handleInput(this.textareaEl.value, this.textareaEl.selectionStart);
753
- });
754
- this.textareaEl.addEventListener("keydown", (event) => {
755
- if (this.mentionEngine.getState().active) {
756
- if (event.key === "ArrowUp") {
757
- event.preventDefault();
758
- this.mentionEngine.moveUp();
759
- } else if (event.key === "ArrowDown") {
760
- event.preventDefault();
761
- this.mentionEngine.moveDown();
762
- } else if (event.key === "Enter" && !event.shiftKey) {
763
- event.preventDefault();
764
- const member = this.mentionEngine.selectCurrent();
765
- if (member) this.insertMention(member);
766
- } else if (event.key === "Escape") {
767
- event.preventDefault();
768
- this.mentionEngine.deactivate();
769
- }
770
- return;
771
- }
772
- if (this.config.enterToSend !== false && event.key === "Enter" && !event.shiftKey) {
773
- event.preventDefault();
774
- this.handleSend();
775
- }
776
- });
777
- this.textareaEl.addEventListener("paste", (event) => {
778
- const items = event.clipboardData?.items;
779
- if (!items) return;
780
- const files = [];
781
- for (let i = 0; i < items.length; i++) if (items[i].kind === "file") {
782
- const file = items[i].getAsFile();
783
- if (file) files.push(file);
784
- }
785
- if (files.length > 0) {
786
- event.preventDefault();
787
- this.emitter.emit("toolbar-paste", files);
788
- this.config.onPaste?.(files);
789
- }
790
- });
791
- }
792
- if (this.inputArea) {
793
- const wrapper = this.inputArea.querySelector(`.${this.prefix}-input-wrapper`);
794
- if (wrapper) this.setupDropEvents(wrapper);
798
+ if (this.textareaEl && (this.textareaEl.addEventListener("input", () => {
799
+ const e = this.limitInput(this.textareaEl?.value ?? "");
800
+ this.textareaEl && e !== this.textareaEl.value && (this.textareaEl.value = e), this.autoResize(), this.updateSendButtonState(), this.config.onInputChange?.(this.textareaEl?.value ?? ""), this.config.enableMention && this.textareaEl && this.mentionEngine.handleInput(this.textareaEl.value, this.textareaEl.selectionStart);
801
+ }), this.textareaEl.addEventListener("keydown", (e) => {
802
+ const t = this.mentionEngine.getState();
803
+ if (t.active) if ("ArrowUp" === e.key) e.preventDefault(), this.mentionEngine.moveUp();
804
+ else if ("ArrowDown" === e.key) e.preventDefault(), this.mentionEngine.moveDown();
805
+ else if ("Enter" !== e.key || e.shiftKey) "Escape" === e.key && (e.preventDefault(), this.mentionEngine.deactivate());
806
+ else {
807
+ e.preventDefault();
808
+ const n = t.triggerPosition, s = this.textareaEl.selectionEnd, i = this.mentionEngine.selectCurrent();
809
+ i && this.insertMention(i, n, s);
810
+ }
811
+ else !1 === this.config.enterToSend || "Enter" !== e.key || e.shiftKey || (e.preventDefault(), this.handleSend());
812
+ }), this.textareaEl.addEventListener("paste", (e) => {
813
+ const t = e.clipboardData?.items;
814
+ if (!t) return;
815
+ const n = [];
816
+ for (let e = 0; e < t.length; e++) if ("file" === t[e].kind) {
817
+ const s = t[e].getAsFile();
818
+ s && n.push(s);
819
+ }
820
+ n.length > 0 && (e.preventDefault(), this.emitter.emit("toolbar-paste", n), this.config.onPaste?.(n));
821
+ })), this.inputArea) {
822
+ const e = this.inputArea.querySelector(`.${this.prefix}-input-wrapper`);
823
+ e && this.setupDropEvents(e);
795
824
  }
796
825
  this.sendBtn?.addEventListener("click", () => {
797
826
  this.handleSend();
798
- });
799
- this.emitter.on("mention-filter", (state) => {
800
- this.renderMentionPanel(state.filtered, state.activeIndex);
801
- });
802
- this.emitter.on("mention-active-change", (index) => {
803
- (this.mentionPanel?.querySelectorAll(`.${this.prefix}-mention-item`))?.forEach((el, i) => {
804
- el.classList.toggle(`${this.prefix}-mention-item--active`, i === index);
805
- el.setAttribute("aria-selected", String(i === index));
827
+ }), this.emitter.on("mention-filter", (e) => {
828
+ this.renderMentionPanel(e.filtered, e.activeIndex);
829
+ }), this.emitter.on("mention-active-change", (e) => {
830
+ (this.mentionPanel?.querySelectorAll(`.${this.prefix}-mention-item`))?.forEach((t, n) => {
831
+ t.classList.toggle(`${this.prefix}-mention-item--active`, n === e), t.setAttribute("aria-selected", String(n === e));
806
832
  });
807
- });
808
- this.emitter.on("mention-deactivate", () => {
833
+ }), this.emitter.on("mention-deactivate", () => {
809
834
  this.hideMentionPanel();
810
835
  });
811
836
  }
812
- setupDropEvents(wrapper) {
813
- let dragCounter = 0;
814
- wrapper.addEventListener("dragenter", (event) => {
815
- event.preventDefault();
816
- dragCounter++;
817
- if (this.dropOverlay) this.dropOverlay.style.display = "flex";
818
- });
819
- wrapper.addEventListener("dragleave", (event) => {
820
- event.preventDefault();
821
- dragCounter--;
822
- if (dragCounter <= 0) {
823
- dragCounter = 0;
824
- if (this.dropOverlay) this.dropOverlay.style.display = "none";
825
- }
826
- });
827
- wrapper.addEventListener("dragover", (event) => {
828
- event.preventDefault();
829
- });
830
- wrapper.addEventListener("drop", (event) => {
831
- event.preventDefault();
832
- dragCounter = 0;
833
- if (this.dropOverlay) this.dropOverlay.style.display = "none";
834
- const files = Array.from(event.dataTransfer?.files ?? []);
835
- if (files.length > 0) {
836
- this.emitter.emit("toolbar-drop", files);
837
- this.config.onDrop?.(files);
838
- }
837
+ setupDropEvents(e) {
838
+ let t = 0;
839
+ e.addEventListener("dragenter", (e) => {
840
+ e.preventDefault(), t++, this.dropOverlay && (this.dropOverlay.style.display = "flex");
841
+ }), e.addEventListener("dragleave", (e) => {
842
+ e.preventDefault(), t--, t <= 0 && (t = 0, this.dropOverlay && (this.dropOverlay.style.display = "none"));
843
+ }), e.addEventListener("dragover", (e) => {
844
+ e.preventDefault();
845
+ }), e.addEventListener("drop", (e) => {
846
+ e.preventDefault(), t = 0, this.dropOverlay && (this.dropOverlay.style.display = "none");
847
+ const n = Array.from(e.dataTransfer?.files ?? []);
848
+ n.length > 0 && (this.emitter.emit("toolbar-drop", n), this.config.onDrop?.(n));
839
849
  });
840
850
  }
841
851
  handleSend() {
842
- const content = (this.textareaEl?.value ?? "").trim();
843
- if (!content) return;
844
- const mentions = this.mentionEngine.parseMentions(content);
845
- this.emitter.emit("send", content, mentions);
846
- this.config.onSend?.(content, mentions);
847
- if (this.config.clearInputOnSend !== false) this.clearInput();
848
- this.mentionEngine.resetSelected();
849
- }
850
- insertMention(member) {
852
+ const e = (this.textareaEl?.value ?? "").trim();
853
+ if (!e) return;
854
+ const t = this.mentionEngine.parseMentions(e);
855
+ this.emitter.emit("send", e, t), this.config.onSend?.(e, t), !1 !== this.config.clearInputOnSend && this.clearInput(), this.mentionEngine.resetSelected();
856
+ }
857
+ insertMention(e, t, n) {
851
858
  if (!this.textareaEl) return;
852
- const state = this.mentionEngine.getState();
853
- const text = this.textareaEl.value;
854
- const pos = this.textareaEl.selectionStart;
855
- const insertText = this.mentionEngine.getInsertText(member);
856
- const before = text.slice(0, state.triggerPosition);
857
- const after = text.slice(pos);
858
- this.textareaEl.value = this.limitInput(before + insertText + after);
859
- const newPos = Math.min(before.length + insertText.length, this.textareaEl.value.length);
860
- this.textareaEl.setSelectionRange(newPos, newPos);
861
- this.textareaEl.focus();
862
- this.autoResize();
863
- this.updateSendButtonState();
864
- this.config.onInputChange?.(this.textareaEl.value);
865
- }
866
- insertTextAtCursor(text) {
859
+ const s = this.mentionEngine.getState(), i = this.textareaEl.value, r = n ?? this.textareaEl.selectionEnd, a = this.mentionEngine.getInsertText(e), o = i.slice(0, t ?? s.triggerPosition), l = i.slice(r);
860
+ this.textareaEl.value = this.limitInput(o + a + l);
861
+ const d = Math.min(o.length + a.length, this.textareaEl.value.length);
862
+ this.textareaEl.setSelectionRange(d, d), this.textareaEl.focus(), this.autoResize(), this.updateSendButtonState(), this.config.onInputChange?.(this.textareaEl.value);
863
+ }
864
+ insertTextAtCursor(e) {
867
865
  if (!this.textareaEl) return;
868
- const current = this.textareaEl.value;
869
- const start = this.textareaEl.selectionStart;
870
- const end = this.textareaEl.selectionEnd;
871
- this.textareaEl.value = this.limitInput(current.slice(0, start) + text + current.slice(end));
872
- const nextCursor = Math.min(start + text.length, this.textareaEl.value.length);
873
- this.textareaEl.setSelectionRange(nextCursor, nextCursor);
874
- this.autoResize();
875
- this.updateSendButtonState();
876
- this.config.onInputChange?.(this.textareaEl.value);
866
+ const t = this.textareaEl.value, n = this.textareaEl.selectionStart, s = this.textareaEl.selectionEnd;
867
+ this.textareaEl.value = this.limitInput(t.slice(0, n) + e + t.slice(s));
868
+ const i = Math.min(n + e.length, this.textareaEl.value.length);
869
+ this.textareaEl.setSelectionRange(i, i), this.autoResize(), this.updateSendButtonState(), this.config.onInputChange?.(this.textareaEl.value);
877
870
  }
878
871
  autoResize() {
879
- if (!this.textareaEl) return;
880
- this.textareaEl.style.height = "auto";
881
- const maxHeight = this.getCssSizeValue("--ldesign-chat-textarea-max-height", 120);
882
- this.textareaEl.style.height = `${Math.min(this.textareaEl.scrollHeight, maxHeight)}px`;
872
+ this.textareaEl && (this.textareaEl.style.height = "auto", this.textareaEl.style.height = `${this.textareaEl.scrollHeight}px`);
883
873
  }
884
874
  updateSendButtonState() {
885
- if (!this.sendBtn) return;
886
- this.sendBtn.disabled = !this.getInputContent().trim();
875
+ this.sendBtn && (this.sendBtn.disabled = !this.getInputContent().trim());
887
876
  }
888
877
  renderLoadMoreButton() {
889
- this.loadMoreEl = this.el("div", `${this.prefix}-load-more`);
890
- this.loadMoreEl.textContent = this.config.loadMoreText ?? "加载更多";
891
- this.loadMoreEl.addEventListener("click", () => {
892
- if (this.loadingMore) return;
893
- this.setLoadingMore(true);
894
- this.emitter.emit("load-more");
895
- Promise.resolve(this.config.onLoadMore?.()).finally(() => {
896
- this.setLoadingMore(false);
897
- });
898
- });
899
- this.messageListEl.appendChild(this.loadMoreEl);
900
- }
901
- renderTimeDivider(timestamp) {
902
- this.messageListEl.appendChild(this.createTimeDivider(timestamp));
903
- }
904
- createTimeDivider(timestamp) {
905
- const divider = this.el("div", `${this.prefix}-time-divider`);
906
- const span = document.createElement("span");
907
- span.textContent = this.formatTime(timestamp);
908
- divider.appendChild(span);
909
- return divider;
910
- }
911
- renderMessageRow(message) {
912
- this.messageListEl.appendChild(this.createMessageRow(message));
913
- }
914
- createMessageRow(message) {
915
- const p = this.prefix;
916
- if (message.type === "system") {
917
- const row2 = this.el("div", cx(`${p}-message-row`, `${p}-message-row--system`));
918
- row2.dataset.msgId = message.id;
919
- row2.dataset.timestamp = String(message.timestamp);
920
- const system = this.el("div", `${p}-system-message`);
921
- system.textContent = message.content ?? "";
922
- row2.appendChild(system);
923
- return row2;
878
+ this.loadMoreEl = this.el("div", `${this.prefix}-load-more`), this.loadMoreEl.textContent = this.config.loadMoreText ?? "加载更多", this.loadMoreEl.addEventListener("click", () => {
879
+ this.destroyed || this.loadingMore || (this.setLoadingMore(!0), this.emitter.emit("load-more"), this.loadMore());
880
+ }), this.messageListEl.appendChild(this.loadMoreEl);
881
+ }
882
+ async loadMore() {
883
+ try {
884
+ await this.config.onLoadMore?.();
885
+ } catch (e) {
886
+ this.emitter.emit("load-more-error", e);
887
+ } finally {
888
+ this.destroyed || this.setLoadingMore(!1);
924
889
  }
925
- const isOutgoing = message.direction === "outgoing";
926
- const row = this.el("div", cx(`${p}-message-row`, isOutgoing && `${p}-message-row--outgoing`));
927
- row.dataset.msgId = message.id;
928
- row.dataset.timestamp = String(message.timestamp);
929
- if (this.config.showAvatar !== false) row.appendChild(this.createAvatar(message.sender.nickname, message.sender.avatar));
930
- const body = this.el("div", `${p}-message-body`);
931
- if (this.config.showNickname !== false && !isOutgoing) {
932
- const nickname = this.el("div", `${p}-nickname`);
933
- nickname.textContent = message.sender.nickname;
934
- body.appendChild(nickname);
890
+ }
891
+ renderTimeDivider(e) {
892
+ this.messageListEl.appendChild(this.createTimeDivider(e));
893
+ }
894
+ createTimeDivider(e) {
895
+ const t = this.el("div", `${this.prefix}-time-divider`), n = this.document.createElement("span");
896
+ return n.textContent = this.formatTime(e), t.appendChild(n), t;
897
+ }
898
+ renderMessageRow(e) {
899
+ this.messageListEl.appendChild(this.createMessageRow(e));
900
+ }
901
+ createMessageRow(e) {
902
+ const t = this.prefix;
903
+ if ("system" === e.type) {
904
+ const n = this.el("div", r(`${t}-message-row`, `${t}-message-row--system`));
905
+ n.dataset.msgId = e.id, n.dataset.timestamp = String(e.timestamp);
906
+ const s = this.el("div", `${t}-system-message`);
907
+ return s.textContent = e.content ?? "", n.appendChild(s), n;
935
908
  }
936
- body.appendChild(this.createBubble(message, isOutgoing));
937
- body.appendChild(this.createMessageFooter(message, isOutgoing));
938
- row.appendChild(body);
939
- row.addEventListener("click", () => {
940
- this.emitter.emit("message-click", message);
941
- this.config.onMessageClick?.(message);
942
- });
943
- return row;
944
- }
945
- createAvatar(name, avatarUrl) {
946
- const avatar = this.el("div", `${this.prefix}-avatar`);
947
- if (avatarUrl) {
948
- const img = document.createElement("img");
949
- img.src = avatarUrl;
950
- img.alt = name;
951
- avatar.appendChild(img);
952
- } else avatar.textContent = name[0]?.toUpperCase() ?? "";
953
- return avatar;
954
- }
955
- createMessageFooter(message, isOutgoing) {
956
- const footer = this.el("div", `${this.prefix}-message-footer`);
957
- if (this.config.showTimestamp !== false) {
958
- const time = this.el("span", `${this.prefix}-timestamp`);
959
- time.textContent = this.formatShortTime(message.timestamp);
960
- footer.appendChild(time);
909
+ const n = "outgoing" === e.direction, s = this.el("div", r(`${t}-message-row`, n && `${t}-message-row--outgoing`));
910
+ s.dataset.msgId = e.id, s.dataset.timestamp = String(e.timestamp), !1 !== this.config.showAvatar && s.appendChild(this.createAvatar(e.sender.nickname, e.sender.avatar));
911
+ const i = this.el("div", `${t}-message-body`);
912
+ if (!1 !== this.config.showNickname && !n) {
913
+ const n = this.el("div", `${t}-nickname`);
914
+ n.textContent = e.sender.nickname, i.appendChild(n);
961
915
  }
962
- if (this.config.showStatus !== false && isOutgoing && message.status) {
963
- const status = this.el("span", cx(`${this.prefix}-status`, `${this.prefix}-status--${message.status}`));
964
- status.textContent = STATUS_MAP[message.status] ?? "";
965
- if (message.status === "failed") status.addEventListener("click", (event) => {
966
- event.stopPropagation();
967
- this.emitter.emit("message-retry", message);
968
- });
969
- footer.appendChild(status);
916
+ return i.appendChild(this.createBubble(e, n)), i.appendChild(this.createMessageFooter(e, n)), s.appendChild(i), s.addEventListener("click", () => {
917
+ this.emitter.emit("message-click", e), this.config.onMessageClick?.(e);
918
+ }), s;
919
+ }
920
+ createAvatar(e, t) {
921
+ const n = this.el("div", `${this.prefix}-avatar`);
922
+ if (t) {
923
+ const s = this.document.createElement("img");
924
+ s.src = this.safeResourceUrl(t), s.alt = e, n.appendChild(s);
925
+ } else n.textContent = e[0]?.toUpperCase() ?? "";
926
+ return n;
927
+ }
928
+ createMessageFooter(e, t) {
929
+ const n = this.el("div", `${this.prefix}-message-footer`);
930
+ if (!1 !== this.config.showTimestamp) {
931
+ const t = this.el("span", `${this.prefix}-timestamp`);
932
+ t.textContent = this.formatShortTime(e.timestamp), n.appendChild(t);
970
933
  }
971
- return footer;
972
- }
973
- createBubble(message, isOutgoing) {
974
- const p = this.prefix;
975
- const dirClass = isOutgoing ? `${p}-bubble--outgoing` : `${p}-bubble--incoming`;
976
- if (message.revoked) {
977
- const bubble = this.el("div", cx(`${p}-bubble`, dirClass, `${p}-bubble--revoked`));
978
- bubble.textContent = "消息已撤回";
979
- return bubble;
934
+ if (!1 !== this.config.showStatus && t && e.status) {
935
+ const t = this.el("span", r(`${this.prefix}-status`, `${this.prefix}-status--${e.status}`));
936
+ t.textContent = q[e.status] ?? "", "failed" === e.status && t.addEventListener("click", (t) => {
937
+ t.stopPropagation(), this.emitter.emit("message-retry", e);
938
+ }), n.appendChild(t);
980
939
  }
981
- switch (message.type) {
940
+ return n;
941
+ }
942
+ createBubble(e, t) {
943
+ const n = this.prefix, s = t ? `${n}-bubble--outgoing` : `${n}-bubble--incoming`;
944
+ if (e.revoked) {
945
+ const e = this.el("div", r(`${n}-bubble`, s, `${n}-bubble--revoked`));
946
+ return e.textContent = "消息已撤回", e;
947
+ }
948
+ switch (e.type) {
982
949
  case "image": {
983
- const bubble = this.el("div", cx(`${p}-bubble`, dirClass, `${p}-bubble--image`));
984
- if (message.image) {
985
- const img = document.createElement("img");
986
- img.src = message.image.thumbUrl ?? message.image.url;
987
- img.alt = message.content ?? "";
988
- if (message.image.width) img.width = Math.min(message.image.width, 240);
989
- bubble.appendChild(img);
950
+ const t = this.el("div", r(`${n}-bubble`, s, `${n}-bubble--image`));
951
+ if (e.image) {
952
+ const n = this.document.createElement("img");
953
+ n.src = this.safeResourceUrl(e.image.thumbUrl ?? e.image.url), n.alt = e.content ?? "", e.image.width && (n.width = Math.min(e.image.width, 240)), t.appendChild(n);
990
954
  }
991
- return bubble;
955
+ return t;
992
956
  }
993
957
  case "file": {
994
- const bubble = this.el("div", cx(`${p}-bubble`, dirClass, `${p}-bubble--file`));
995
- const icon = this.el("div", `${p}-file-icon`);
996
- icon.innerHTML = FILE_ICON;
997
- bubble.appendChild(icon);
998
- const info = this.el("div", `${p}-file-info`);
999
- const name = this.el("div", `${p}-file-name`);
1000
- name.textContent = message.file?.name ?? "file";
1001
- info.appendChild(name);
1002
- if (message.file?.size) {
1003
- const size = this.el("div", `${p}-file-size`);
1004
- size.textContent = this.formatFileSize(message.file.size);
1005
- info.appendChild(size);
958
+ const t = this.el("div", r(`${n}-bubble`, s, `${n}-bubble--file`)), i = this.el("div", `${n}-file-icon`);
959
+ i.innerHTML = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"/></svg>", t.appendChild(i);
960
+ const a = this.el("div", `${n}-file-info`), o = this.el("div", `${n}-file-name`);
961
+ if (o.textContent = e.file?.name ?? "file", a.appendChild(o), e.file?.size) {
962
+ const t = this.el("div", `${n}-file-size`);
963
+ t.textContent = this.formatFileSize(e.file.size), a.appendChild(t);
1006
964
  }
1007
- bubble.appendChild(info);
1008
- return bubble;
965
+ return t.appendChild(a), t;
1009
966
  }
1010
967
  case "custom": {
1011
- const bubble = this.el("div", cx(`${p}-bubble`, dirClass));
1012
- if (message.customHtml) bubble.innerHTML = message.customHtml;
1013
- else bubble.textContent = message.content ?? "";
1014
- return bubble;
968
+ const t = this.el("div", r(`${n}-bubble`, s));
969
+ return e.customHtml ? this.setSanitizedCustomHtml(t, e.customHtml) : t.textContent = e.content ?? "", t;
1015
970
  }
1016
971
  default: {
1017
- const bubble = this.el("div", cx(`${p}-bubble`, dirClass, message.streaming && `${p}-bubble--streaming`));
1018
- bubble.innerHTML = this.renderTextContent(message.content ?? "", message.mentions);
1019
- return bubble;
972
+ const t = this.el("div", r(`${n}-bubble`, s, e.streaming && `${n}-bubble--streaming`));
973
+ return e.contentBlocks?.length ? this.renderContentBlocks(t, e.contentBlocks) : t.innerHTML = this.renderTextContent(e.content ?? "", e.mentions), t;
974
+ }
975
+ }
976
+ }
977
+ renderContentBlocks(e, t) {
978
+ e.replaceChildren();
979
+ for (const n of t) switch (n.type) {
980
+ case "text":
981
+ e.appendChild(this.document.createTextNode(n.text));
982
+ break;
983
+ case "mention": {
984
+ const t = this.el("span", `${this.prefix}-mention-tag`);
985
+ t.dataset.mentionId = n.id, t.textContent = `@${n.label}`, e.appendChild(t);
986
+ break;
987
+ }
988
+ case "link": {
989
+ const t = this.document.createElement("a"), s = this.safeUrl(n.url, [
990
+ "http:",
991
+ "https:",
992
+ "mailto:"
993
+ ]);
994
+ t.className = `${this.prefix}-link`, t.textContent = n.label ?? n.url, s && (t.href = s, t.target = "_blank", t.rel = "noopener noreferrer"), e.appendChild(t);
995
+ break;
996
+ }
997
+ case "code": {
998
+ const t = this.document.createElement("code");
999
+ t.textContent = n.code, n.language && (t.dataset.language = n.language), e.appendChild(t);
1000
+ break;
1001
+ }
1002
+ case "image": {
1003
+ const t = this.document.createElement("img"), s = this.safeResourceUrl(n.src), i = n.width, r = n.height;
1004
+ s && (t.src = s), t.alt = n.alt ?? "", t.loading = "lazy", Number.isFinite(i) && i > 0 && (t.width = i), Number.isFinite(r) && r > 0 && (t.height = r), e.appendChild(t);
1005
+ break;
1006
+ }
1007
+ case "file": {
1008
+ const t = this.document.createElement("a"), s = this.safeResourceUrl(n.url);
1009
+ t.className = `${this.prefix}-file-link`, t.textContent = n.name, s && (t.href = s, t.target = "_blank", t.rel = "noopener noreferrer"), e.appendChild(t);
1010
+ break;
1020
1011
  }
1012
+ case "break":
1013
+ e.appendChild(this.document.createElement("br"));
1014
+ break;
1015
+ case "custom": e.appendChild(this.document.createTextNode(n.fallback ?? `[${n.kind}]`));
1021
1016
  }
1022
1017
  }
1023
- renderTextContent(content, mentions) {
1024
- let html = this.escapeHtml(content);
1025
- if (mentions?.length) for (const mention of mentions) {
1026
- const tag = `<span class="${this.prefix}-mention-tag" data-mention-id="${this.escapeAttr(mention.id)}">@${this.escapeHtml(mention.name)}</span>`;
1027
- html = html.replace(new RegExp(`@${this.escapeRegex(mention.name)}`, "g"), tag);
1018
+ renderTextContent(e, t) {
1019
+ let n = this.escapeHtml(e);
1020
+ if (t?.length) for (const e of t) {
1021
+ const t = `<span class="${this.prefix}-mention-tag" data-mention-id="${this.escapeAttr(e.id)}">@${this.escapeHtml(e.name)}</span>`;
1022
+ n = n.replace(new RegExp(`@${this.escapeRegex(e.name)}`, "g"), t);
1023
+ }
1024
+ return n.replace(/(https?:\/\/[^\s<]+)/g, `<a class="${this.prefix}-link" href="$1" target="_blank" rel="noopener noreferrer">$1</a>`);
1025
+ }
1026
+ setSanitizedCustomHtml(e, t) {
1027
+ const n = this.document.createElement("template");
1028
+ n.innerHTML = t;
1029
+ const s = this.document.createDocumentFragment();
1030
+ for (const e of Array.from(n.content.childNodes)) {
1031
+ const t = this.sanitizeCustomNode(e);
1032
+ t && s.appendChild(t);
1033
+ }
1034
+ e.replaceChildren(s);
1035
+ }
1036
+ sanitizeCustomNode(e) {
1037
+ if (3 === e.nodeType) return this.document.createTextNode(e.textContent ?? "");
1038
+ if (1 !== e.nodeType) return null;
1039
+ const t = e, n = t.tagName.toLowerCase();
1040
+ if (D.has(n)) return null;
1041
+ if (!P.has(n)) {
1042
+ const e = this.document.createDocumentFragment();
1043
+ for (const n of Array.from(t.childNodes)) {
1044
+ const t = this.sanitizeCustomNode(n);
1045
+ t && e.appendChild(t);
1046
+ }
1047
+ return e;
1048
+ }
1049
+ const s = this.document.createElement(n), i = t.getAttribute("title");
1050
+ i && s.setAttribute("title", i);
1051
+ const r = t.getAttribute("aria-label");
1052
+ if (r && s.setAttribute("aria-label", r), "a" === n) {
1053
+ const e = t.getAttribute("href"), n = e ? this.safeUrl(e, [
1054
+ "http:",
1055
+ "https:",
1056
+ "mailto:"
1057
+ ]) : null;
1058
+ n && s.setAttribute("href", n);
1059
+ const i = t.getAttribute("target");
1060
+ "_blank" !== i && "_self" !== i || s.setAttribute("target", i), s.setAttribute("rel", "noopener noreferrer");
1061
+ }
1062
+ if ("img" === n) {
1063
+ const e = t.getAttribute("src"), n = e ? this.safeUrl(e, [
1064
+ "http:",
1065
+ "https:",
1066
+ "blob:"
1067
+ ]) : null;
1068
+ n && s.setAttribute("src", n), s.setAttribute("alt", t.getAttribute("alt") ?? "");
1069
+ for (const e of ["width", "height"]) {
1070
+ const n = Number(t.getAttribute(e));
1071
+ Number.isFinite(n) && n > 0 && s.setAttribute(e, String(n));
1072
+ }
1073
+ s.setAttribute("loading", "lazy");
1074
+ }
1075
+ for (const e of Array.from(t.childNodes)) {
1076
+ const t = this.sanitizeCustomNode(e);
1077
+ t && s.appendChild(t);
1078
+ }
1079
+ return s;
1080
+ }
1081
+ safeResourceUrl(e) {
1082
+ return e ? this.safeUrl(e, [
1083
+ "http:",
1084
+ "https:",
1085
+ "blob:"
1086
+ ]) ?? "" : "";
1087
+ }
1088
+ safeUrl(e, t) {
1089
+ const n = e.trim();
1090
+ if (!n || [...n].some((e) => {
1091
+ const t = e.charCodeAt(0);
1092
+ return t < 32 || 127 === t;
1093
+ })) return null;
1094
+ try {
1095
+ const e = new URL(n, this.document.baseURI);
1096
+ return t.includes(e.protocol) ? n : null;
1097
+ } catch {
1098
+ return null;
1028
1099
  }
1029
- return html.replace(/(https?:\/\/[^\s<]+)/g, `<a class="${this.prefix}-link" href="$1" target="_blank" rel="noopener noreferrer">$1</a>`);
1030
1100
  }
1031
1101
  renderEmpty() {
1032
1102
  if (this.messageListEl.querySelector(`.${this.prefix}-empty`)) return;
1033
- const empty = this.el("div", `${this.prefix}-empty`);
1034
- empty.textContent = "暂无消息";
1035
- this.messageListEl.appendChild(empty);
1103
+ const e = this.el("div", `${this.prefix}-empty`);
1104
+ e.textContent = "暂无消息", this.messageListEl.appendChild(e);
1036
1105
  }
1037
1106
  removeEmpty() {
1038
1107
  this.messageListEl.querySelector(`.${this.prefix}-empty`)?.remove();
1039
1108
  }
1040
- el(tag, className) {
1041
- const el = document.createElement(tag);
1042
- el.className = className;
1043
- return el;
1109
+ el(e, t) {
1110
+ const n = this.document.createElement(e);
1111
+ return n.className = t, n;
1044
1112
  }
1045
1113
  getMessageElements() {
1046
1114
  return Array.from(this.messageListEl.querySelectorAll(`.${this.prefix}-message-row`));
1047
1115
  }
1048
- formatTime(timestamp) {
1049
- if (this.config.formatTime) return this.config.formatTime(timestamp);
1050
- const date = new Date(timestamp);
1051
- const now = /* @__PURE__ */ new Date();
1052
- if (date.toDateString() === now.toDateString()) return date.toLocaleTimeString("zh-CN", {
1116
+ getMessageRow(e) {
1117
+ return this.getMessageElements().find((t) => t.dataset.msgId === e) ?? null;
1118
+ }
1119
+ refreshTimeDividers() {
1120
+ this.messageListEl.querySelectorAll(`.${this.prefix}-time-divider`).forEach((e) => e.remove());
1121
+ const e = this.config.timeGroupInterval ?? 3e5;
1122
+ let t = 0;
1123
+ for (const n of this.getMessageElements()) {
1124
+ const s = Number(n.dataset.timestamp ?? 0);
1125
+ !1 !== this.config.showTimestamp && s - t > e && n.before(this.createTimeDivider(s)), t = s;
1126
+ }
1127
+ }
1128
+ formatTime(e) {
1129
+ if (this.config.formatTime) return this.config.formatTime(e);
1130
+ const t = new Date(e), n = /* @__PURE__ */ new Date();
1131
+ return t.toDateString() === n.toDateString() ? t.toLocaleTimeString("zh-CN", {
1053
1132
  hour: "2-digit",
1054
1133
  minute: "2-digit"
1055
- });
1056
- return date.toLocaleDateString("zh-CN", {
1134
+ }) : t.toLocaleDateString("zh-CN", {
1057
1135
  month: "2-digit",
1058
1136
  day: "2-digit",
1059
1137
  hour: "2-digit",
1060
1138
  minute: "2-digit"
1061
1139
  });
1062
1140
  }
1063
- formatShortTime(timestamp) {
1064
- return new Date(timestamp).toLocaleTimeString("zh-CN", {
1141
+ formatShortTime(e) {
1142
+ return new Date(e).toLocaleTimeString("zh-CN", {
1065
1143
  hour: "2-digit",
1066
1144
  minute: "2-digit"
1067
1145
  });
1068
1146
  }
1069
- formatFileSize(bytes) {
1070
- if (bytes < 1024) return `${bytes} B`;
1071
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1072
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1147
+ formatFileSize(e) {
1148
+ return e < 1024 ? `${e} B` : e < 1048576 ? `${(e / 1024).toFixed(1)} KB` : `${(e / 1048576).toFixed(1)} MB`;
1149
+ }
1150
+ limitInput(e) {
1151
+ const t = this.config.maxInputLength;
1152
+ return !t || t <= 0 ? e : e.slice(0, t);
1153
+ }
1154
+ escapeHtml(e) {
1155
+ const t = this.document.createElement("div");
1156
+ return t.textContent = e, t.innerHTML;
1157
+ }
1158
+ escapeAttr(e) {
1159
+ return e.replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1160
+ }
1161
+ escapeRegex(e) {
1162
+ return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1163
+ }
1164
+ };
1165
+ const H = {
1166
+ bgPrimary: "var(--ld-bg-surface)",
1167
+ bgSecondary: "var(--ld-bg-surface-soft)",
1168
+ bgBubbleOutgoing: "var(--ld-primary-default)",
1169
+ bgBubbleIncoming: "var(--ld-bg-surface-soft)",
1170
+ bgInput: "var(--ld-bg-surface)",
1171
+ bgMention: "var(--ld-bg-surface)",
1172
+ bgMentionHover: "var(--ld-primary-50)",
1173
+ bgMentionActive: "var(--ld-primary-100)",
1174
+ bgLoadMore: "var(--ld-bg-surface-soft)",
1175
+ bgSystemMessage: "var(--ld-bg-surface-soft)",
1176
+ bgDropOverlay: "color-mix(in srgb, var(--ld-primary-default) 10%, var(--ld-bg-surface))",
1177
+ bgDangerSoft: "color-mix(in srgb, var(--ld-danger-default) 12%, var(--ld-bg-surface))",
1178
+ textPrimary: "var(--ld-text-primary)",
1179
+ textSecondary: "var(--ld-text-secondary)",
1180
+ textBubbleOutgoing: "var(--ld-gray-50)",
1181
+ textBubbleIncoming: "var(--ld-text-primary)",
1182
+ textMention: "var(--ld-text-primary)",
1183
+ textMentionTag: "var(--ld-primary-default)",
1184
+ textTimestamp: "var(--ld-text-muted)",
1185
+ textSystem: "var(--ld-text-secondary)",
1186
+ textPlaceholder: "var(--ld-text-muted)",
1187
+ borderPrimary: "var(--ld-border)",
1188
+ borderInput: "var(--ld-gray-300)",
1189
+ borderInputFocus: "var(--ld-primary-default)",
1190
+ borderMention: "var(--ld-border)",
1191
+ colorSending: "var(--ld-gray-400)",
1192
+ colorSent: "var(--ld-gray-500)",
1193
+ colorDelivered: "var(--ld-primary-default)",
1194
+ colorRead: "var(--ld-success-default)",
1195
+ colorFailed: "var(--ld-danger-default)",
1196
+ colorLink: "var(--ld-primary-default)",
1197
+ shadowPanel: "none",
1198
+ shadowMention: "none",
1199
+ avatarBg: "var(--ld-primary-default)",
1200
+ avatarText: "var(--ld-gray-50)",
1201
+ btnPrimaryBg: "var(--ld-primary-default)",
1202
+ btnPrimaryText: "var(--ld-gray-50)",
1203
+ btnPrimaryHover: "var(--ld-primary-hover)",
1204
+ scrollbarThumb: "var(--ld-gray-300)",
1205
+ scrollbarTrack: "var(--ld-gray-100)",
1206
+ bgToolbar: "var(--ld-bg-surface-soft)",
1207
+ borderToolbar: "var(--ld-border)",
1208
+ toolbarIconColor: "var(--ld-text-secondary)",
1209
+ toolbarIconHover: "var(--ld-primary-default)",
1210
+ toolbarVoiceActive: "var(--ld-danger-default)"
1211
+ }, F = {
1212
+ bgPrimary: "var(--ld-gray-900)",
1213
+ bgSecondary: "var(--ld-gray-800)",
1214
+ bgBubbleOutgoing: "var(--ld-primary-default)",
1215
+ bgBubbleIncoming: "var(--ld-gray-800)",
1216
+ bgInput: "var(--ld-gray-900)",
1217
+ bgMention: "var(--ld-gray-900)",
1218
+ bgMentionHover: "var(--ld-gray-800)",
1219
+ bgMentionActive: "var(--ld-primary-900)",
1220
+ bgLoadMore: "var(--ld-gray-800)",
1221
+ bgSystemMessage: "var(--ld-gray-800)",
1222
+ bgDropOverlay: "color-mix(in srgb, var(--ld-primary-default) 16%, var(--ld-gray-900))",
1223
+ bgDangerSoft: "color-mix(in srgb, var(--ld-danger-default) 18%, var(--ld-gray-900))",
1224
+ textPrimary: "var(--ld-gray-50)",
1225
+ textSecondary: "var(--ld-gray-300)",
1226
+ textBubbleOutgoing: "var(--ld-gray-50)",
1227
+ textBubbleIncoming: "var(--ld-gray-50)",
1228
+ textMention: "var(--ld-gray-50)",
1229
+ textMentionTag: "var(--ld-primary-300)",
1230
+ textTimestamp: "var(--ld-gray-400)",
1231
+ textSystem: "var(--ld-gray-300)",
1232
+ textPlaceholder: "var(--ld-gray-500)",
1233
+ borderPrimary: "var(--ld-gray-700)",
1234
+ borderInput: "var(--ld-gray-700)",
1235
+ borderInputFocus: "var(--ld-primary-default)",
1236
+ borderMention: "var(--ld-gray-700)",
1237
+ colorSending: "var(--ld-gray-500)",
1238
+ colorSent: "var(--ld-gray-400)",
1239
+ colorDelivered: "var(--ld-primary-300)",
1240
+ colorRead: "var(--ld-success-default)",
1241
+ colorFailed: "var(--ld-danger-default)",
1242
+ colorLink: "var(--ld-primary-300)",
1243
+ shadowPanel: "none",
1244
+ shadowMention: "none",
1245
+ avatarBg: "var(--ld-primary-default)",
1246
+ avatarText: "var(--ld-gray-50)",
1247
+ btnPrimaryBg: "var(--ld-primary-default)",
1248
+ btnPrimaryText: "var(--ld-gray-50)",
1249
+ btnPrimaryHover: "var(--ld-primary-hover)",
1250
+ scrollbarThumb: "var(--ld-gray-700)",
1251
+ scrollbarTrack: "var(--ld-gray-800)",
1252
+ bgToolbar: "var(--ld-gray-800)",
1253
+ borderToolbar: "var(--ld-gray-700)",
1254
+ toolbarIconColor: "var(--ld-gray-300)",
1255
+ toolbarIconHover: "var(--ld-primary-300)",
1256
+ toolbarVoiceActive: "var(--ld-danger-default)"
1257
+ };
1258
+ var N = class {
1259
+ constructor(e, t, n) {
1260
+ this.mode = "light", this.mediaQuery = null, this.mediaHandler = null, this.destroyed = !1, this.emitter = e, this.view = void 0 === n ? "undefined" == typeof window ? null : window : n, t && this.setTheme(t);
1261
+ }
1262
+ getMode() {
1263
+ return this.mode;
1264
+ }
1265
+ getResolvedTheme() {
1266
+ return "auto" === this.mode ? this.getSystemTheme() : this.mode;
1267
+ }
1268
+ getTokens() {
1269
+ return "dark" === this.getResolvedTheme() ? F : H;
1073
1270
  }
1074
- limitInput(content) {
1075
- const max = this.config.maxInputLength;
1076
- if (!max || max <= 0) return content;
1077
- return content.slice(0, max);
1271
+ setTheme(e) {
1272
+ this.destroyed || (this.cleanupMediaListener(), this.mode = e, "auto" === e && this.setupMediaListener(), this.emitter.emit("theme-change", this.getResolvedTheme(), this.getTokens()));
1078
1273
  }
1079
- getCssSizeValue(name, fallback) {
1080
- if (typeof window === "undefined" || !this.root) return fallback;
1081
- const value = window.getComputedStyle(this.root).getPropertyValue(name).trim();
1082
- const parsed = Number.parseFloat(value);
1083
- return Number.isFinite(parsed) ? parsed : fallback;
1274
+ getSystemTheme() {
1275
+ const e = this.view?.matchMedia;
1276
+ return e && e.call(this.view, "(prefers-color-scheme: dark)").matches ? "dark" : "light";
1084
1277
  }
1085
- escapeHtml(str) {
1086
- const div = document.createElement("div");
1087
- div.textContent = str;
1088
- return div.innerHTML;
1278
+ setupMediaListener() {
1279
+ if (!this.view?.matchMedia) return;
1280
+ this.mediaQuery = this.view.matchMedia("(prefers-color-scheme: dark)"), this.mediaHandler = () => {
1281
+ this.emitter.emit("theme-change", this.getResolvedTheme(), this.getTokens());
1282
+ };
1283
+ const e = this.mediaQuery;
1284
+ "function" == typeof e.addEventListener ? e.addEventListener("change", this.mediaHandler) : e.addListener?.(this.mediaHandler);
1089
1285
  }
1090
- escapeAttr(str) {
1091
- return str.replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1286
+ cleanupMediaListener() {
1287
+ if (this.mediaQuery && this.mediaHandler) {
1288
+ const e = this.mediaQuery;
1289
+ "function" == typeof e.removeEventListener ? e.removeEventListener("change", this.mediaHandler) : e.removeListener?.(this.mediaHandler), this.mediaQuery = null, this.mediaHandler = null;
1290
+ }
1092
1291
  }
1093
- escapeRegex(str) {
1094
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1292
+ destroy() {
1293
+ this.destroyed || (this.destroyed = !0, this.cleanupMediaListener());
1294
+ }
1295
+ }, O = class {
1296
+ constructor() {
1297
+ this.entries = [];
1298
+ }
1299
+ load() {
1300
+ return j(this.entries);
1301
+ }
1302
+ save(e) {
1303
+ this.entries = j(e);
1095
1304
  }
1096
1305
  };
1097
- //#endregion
1098
- //#region src/renderer/style-manager.ts
1099
- const styleRefs = /* @__PURE__ */ new Map();
1100
- function injectStyles(tokens, prefix = DEFAULT_CLASS_PREFIX) {
1101
- const normalizedPrefix = normalizeClassPrefix(prefix);
1102
- const styleId = getStyleElementId(normalizedPrefix);
1103
- styleRefs.set(styleId, (styleRefs.get(styleId) ?? 0) + 1);
1104
- upsertStyle(styleId, generateCSS(tokens, normalizedPrefix));
1105
- }
1106
- function updateStyles(tokens, prefix = DEFAULT_CLASS_PREFIX) {
1107
- const normalizedPrefix = normalizeClassPrefix(prefix);
1108
- upsertStyle(getStyleElementId(normalizedPrefix), generateCSS(tokens, normalizedPrefix));
1109
- }
1110
- function removeStyles(prefix = DEFAULT_CLASS_PREFIX) {
1111
- const styleId = getStyleElementId(prefix);
1112
- const refs = styleRefs.get(styleId) ?? 0;
1113
- if (refs > 1) {
1114
- styleRefs.set(styleId, refs - 1);
1115
- return;
1116
- }
1117
- styleRefs.delete(styleId);
1118
- document.getElementById(styleId)?.remove();
1306
+ function U(e) {
1307
+ return e.clientMessageId ?? e.id;
1119
1308
  }
1120
- function cls(prefix, ...parts) {
1121
- const normalizedPrefix = normalizeClassPrefix(prefix);
1122
- return parts.map((part) => `${normalizedPrefix}-${part}`).join(" ");
1309
+ function V(e) {
1310
+ return {
1311
+ ...e,
1312
+ message: d(e.message)
1313
+ };
1123
1314
  }
1124
- const clsx = cx;
1125
- function upsertStyle(styleId, cssText) {
1126
- let style = document.getElementById(styleId);
1127
- if (!style) {
1128
- style = document.createElement("style");
1129
- style.id = styleId;
1130
- style.dataset.ldesignChat = "true";
1131
- document.head.appendChild(style);
1132
- }
1133
- style.textContent = cssText;
1315
+ function j(e) {
1316
+ return e.map(V);
1134
1317
  }
1135
- function tokenVars(t) {
1136
- const vars = {
1137
- bgPrimary: t.bgPrimary,
1138
- bgSecondary: t.bgSecondary,
1139
- bgBubbleOutgoing: t.bgBubbleOutgoing,
1140
- bgBubbleIncoming: t.bgBubbleIncoming,
1141
- bgInput: t.bgInput,
1142
- bgMention: t.bgMention,
1143
- bgMentionHover: t.bgMentionHover,
1144
- bgMentionActive: t.bgMentionActive,
1145
- bgLoadMore: t.bgLoadMore,
1146
- bgSystemMessage: t.bgSystemMessage,
1147
- bgDropOverlay: t.bgDropOverlay,
1148
- bgDangerSoft: t.bgDangerSoft,
1149
- textPrimary: t.textPrimary,
1150
- textSecondary: t.textSecondary,
1151
- textBubbleOutgoing: t.textBubbleOutgoing,
1152
- textBubbleIncoming: t.textBubbleIncoming,
1153
- textMention: t.textMention,
1154
- textMentionTag: t.textMentionTag,
1155
- textTimestamp: t.textTimestamp,
1156
- textSystem: t.textSystem,
1157
- textPlaceholder: t.textPlaceholder,
1158
- borderPrimary: t.borderPrimary,
1159
- borderInput: t.borderInput,
1160
- borderInputFocus: t.borderInputFocus,
1161
- borderMention: t.borderMention,
1162
- colorSending: t.colorSending,
1163
- colorSent: t.colorSent,
1164
- colorDelivered: t.colorDelivered,
1165
- colorRead: t.colorRead,
1166
- colorFailed: t.colorFailed,
1167
- colorLink: t.colorLink,
1168
- shadowPanel: t.shadowPanel,
1169
- shadowMention: t.shadowMention,
1170
- avatarBg: t.avatarBg,
1171
- avatarText: t.avatarText,
1172
- btnPrimaryBg: t.btnPrimaryBg,
1173
- btnPrimaryText: t.btnPrimaryText,
1174
- btnPrimaryHover: t.btnPrimaryHover,
1175
- scrollbarThumb: t.scrollbarThumb,
1176
- scrollbarTrack: t.scrollbarTrack,
1177
- bgToolbar: t.bgToolbar,
1178
- borderToolbar: t.borderToolbar,
1179
- toolbarIconColor: t.toolbarIconColor,
1180
- toolbarIconHover: t.toolbarIconHover,
1181
- toolbarVoiceActive: t.toolbarVoiceActive
1318
+ function _(e) {
1319
+ const t = a(e.message);
1320
+ return {
1321
+ id: "string" == typeof e.id && e.id ? e.id : U(t),
1322
+ message: t,
1323
+ enqueuedAt: Number.isFinite(e.enqueuedAt) ? e.enqueuedAt : Date.now(),
1324
+ attempts: Number.isFinite(e.attempts) ? Math.max(0, Math.floor(e.attempts)) : 0,
1325
+ state: "failed" === e.state ? "failed" : "pending",
1326
+ ..."string" == typeof e.lastError && { lastError: e.lastError }
1182
1327
  };
1183
- return Object.entries(vars).map(([key, value]) => ` ${COMPONENT_CSS_VAR_PREFIX}-${toKebab(key)}: ${value};`).join("\n");
1184
- }
1185
- function sizeVars() {
1186
- return `
1187
- ${COMPONENT_CSS_VAR_PREFIX}-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
1188
- ${COMPONENT_CSS_VAR_PREFIX}-font-size: var(--size-font-base);
1189
- ${COMPONENT_CSS_VAR_PREFIX}-font-size-sm: var(--size-font-sm);
1190
- ${COMPONENT_CSS_VAR_PREFIX}-font-size-xs: var(--size-font-xs);
1191
- ${COMPONENT_CSS_VAR_PREFIX}-font-size-lg: var(--size-font-lg);
1192
- ${COMPONENT_CSS_VAR_PREFIX}-font-weight-regular: var(--size-font-weight-regular);
1193
- ${COMPONENT_CSS_VAR_PREFIX}-font-weight-medium: var(--size-font-weight-medium);
1194
- ${COMPONENT_CSS_VAR_PREFIX}-font-weight-semibold: var(--size-font-weight-semibold);
1195
- ${COMPONENT_CSS_VAR_PREFIX}-line-height: var(--size-line-normal);
1196
- ${COMPONENT_CSS_VAR_PREFIX}-line-height-bubble: var(--size-line-relaxed);
1197
- ${COMPONENT_CSS_VAR_PREFIX}-space-1: var(--size-1);
1198
- ${COMPONENT_CSS_VAR_PREFIX}-space-2: var(--size-2);
1199
- ${COMPONENT_CSS_VAR_PREFIX}-space-3: var(--size-3);
1200
- ${COMPONENT_CSS_VAR_PREFIX}-space-4: var(--size-4);
1201
- ${COMPONENT_CSS_VAR_PREFIX}-space-5: var(--size-5);
1202
- ${COMPONENT_CSS_VAR_PREFIX}-space-6: var(--size-6);
1203
- ${COMPONENT_CSS_VAR_PREFIX}-space-8: var(--size-8);
1204
- ${COMPONENT_CSS_VAR_PREFIX}-avatar-size: var(--size-11);
1205
- ${COMPONENT_CSS_VAR_PREFIX}-mention-avatar-size: var(--size-9);
1206
- ${COMPONENT_CSS_VAR_PREFIX}-toolbar-button-size: var(--size-9);
1207
- ${COMPONENT_CSS_VAR_PREFIX}-input-button-size: var(--size-11);
1208
- ${COMPONENT_CSS_VAR_PREFIX}-file-icon-size: var(--size-12);
1209
- ${COMPONENT_CSS_VAR_PREFIX}-image-max-size: calc(var(--size-13) * 5);
1210
- ${COMPONENT_CSS_VAR_PREFIX}-panel-max-height: calc(var(--size-24) + var(--size-4));
1211
- ${COMPONENT_CSS_VAR_PREFIX}-textarea-min-height: var(--size-11);
1212
- ${COMPONENT_CSS_VAR_PREFIX}-textarea-max-height: calc(var(--size-12) * 3);
1213
- ${COMPONENT_CSS_VAR_PREFIX}-message-max-width: 65%;
1214
- ${COMPONENT_CSS_VAR_PREFIX}-system-message-max-width: 80%;
1215
- ${COMPONENT_CSS_VAR_PREFIX}-file-bubble-min-width: calc(var(--size-24) + var(--size-4));
1216
- ${COMPONENT_CSS_VAR_PREFIX}-border-width: var(--size-border-width-thin);
1217
- ${COMPONENT_CSS_VAR_PREFIX}-border-width-drop: var(--size-border-width-medium);
1218
- ${COMPONENT_CSS_VAR_PREFIX}-radius-xs: var(--size-radius-sm);
1219
- ${COMPONENT_CSS_VAR_PREFIX}-radius-sm: var(--size-radius-md);
1220
- ${COMPONENT_CSS_VAR_PREFIX}-radius-md: var(--size-radius-lg);
1221
- ${COMPONENT_CSS_VAR_PREFIX}-radius-lg: var(--size-radius-xl);
1222
- ${COMPONENT_CSS_VAR_PREFIX}-radius-pill: var(--size-radius-full);
1223
- ${COMPONENT_CSS_VAR_PREFIX}-radius-circle: var(--size-radius-circle);
1224
- ${COMPONENT_CSS_VAR_PREFIX}-transition-fast: var(--size-duration-fast) var(--size-ease-out);
1225
- ${COMPONENT_CSS_VAR_PREFIX}-transition-normal: var(--size-duration-normal) var(--size-ease-out);
1226
- ${COMPONENT_CSS_VAR_PREFIX}-z-overlay: var(--size-z-dropdown);
1227
- ${COMPONENT_CSS_VAR_PREFIX}-z-mention: var(--size-z-popover);
1228
- `;
1229
1328
  }
1230
- function generateCSS(tokens, p) {
1231
- return `
1232
- /* @composy/chat-core styles */
1233
- .${p}-container,
1234
- .${p}-container * {
1235
- box-sizing: border-box;
1236
- }
1237
-
1238
- .${p}-container {
1239
- ${tokenVars(tokens)}
1240
- ${sizeVars()}
1241
- display: flex;
1242
- flex-direction: column;
1243
- width: 100%;
1244
- height: 100%;
1245
- overflow: hidden;
1246
- position: relative;
1247
- background: ${v("bg-primary")};
1248
- color: ${v("text-primary")};
1249
- font-family: ${v("font-family")};
1250
- font-size: ${v("font-size")};
1251
- line-height: ${v("line-height")};
1252
- border: ${v("border-width")} solid ${v("border-primary")};
1253
- border-radius: ${v("radius-md")};
1254
- box-shadow: ${v("shadow-panel")};
1255
- }
1256
-
1257
- .${p}-message-list {
1258
- flex: 1;
1259
- min-width: 0;
1260
- overflow-y: auto;
1261
- overflow-x: hidden;
1262
- padding: ${v("space-6")};
1263
- scroll-behavior: smooth;
1264
- }
1265
-
1266
- .${p}-message-list::-webkit-scrollbar {
1267
- width: var(--size-3);
1268
- }
1269
-
1270
- .${p}-message-list::-webkit-scrollbar-thumb {
1271
- background: ${v("scrollbar-thumb")};
1272
- border-radius: ${v("radius-pill")};
1273
- }
1274
-
1275
- .${p}-message-list::-webkit-scrollbar-track {
1276
- background: ${v("scrollbar-track")};
1277
- }
1278
-
1279
- .${p}-load-more {
1280
- margin-bottom: ${v("space-5")};
1281
- padding: ${v("space-5")};
1282
- text-align: center;
1283
- color: ${v("color-link")};
1284
- font-size: ${v("font-size-sm")};
1285
- background: ${v("bg-load-more")};
1286
- border-radius: ${v("radius-sm")};
1287
- cursor: pointer;
1288
- user-select: none;
1289
- transition: opacity ${v("transition-fast")}, background ${v("transition-fast")};
1290
- }
1291
-
1292
- .${p}-load-more:hover {
1293
- background: ${v("bg-mention-hover")};
1294
- }
1295
-
1296
- .${p}-load-more--loading {
1297
- cursor: default;
1298
- opacity: .6;
1299
- }
1300
-
1301
- .${p}-time-divider {
1302
- padding: ${v("space-4")} 0;
1303
- color: ${v("text-timestamp")};
1304
- font-size: ${v("font-size-sm")};
1305
- text-align: center;
1306
- user-select: none;
1307
- }
1308
-
1309
- .${p}-time-divider span {
1310
- padding: ${v("space-1")} ${v("space-5")};
1311
- background: ${v("bg-secondary")};
1312
- border-radius: ${v("radius-pill")};
1313
- }
1314
-
1315
- .${p}-message-row {
1316
- display: flex;
1317
- align-items: flex-start;
1318
- gap: ${v("space-4")};
1319
- margin-bottom: ${v("space-5")};
1320
- }
1321
-
1322
- .${p}-message-row--outgoing {
1323
- flex-direction: row-reverse;
1324
- }
1325
-
1326
- .${p}-message-row--system {
1327
- justify-content: center;
1328
- }
1329
-
1330
- .${p}-avatar {
1331
- display: flex;
1332
- align-items: center;
1333
- justify-content: center;
1334
- width: ${v("avatar-size")};
1335
- height: ${v("avatar-size")};
1336
- overflow: hidden;
1337
- flex-shrink: 0;
1338
- color: ${v("avatar-text")};
1339
- background: ${v("avatar-bg")};
1340
- border-radius: ${v("radius-circle")};
1341
- font-size: ${v("font-size")};
1342
- font-weight: ${v("font-weight-semibold")};
1343
- }
1344
-
1345
- .${p}-avatar img {
1346
- width: 100%;
1347
- height: 100%;
1348
- object-fit: cover;
1349
- }
1350
-
1351
- .${p}-message-body {
1352
- display: flex;
1353
- flex-direction: column;
1354
- max-width: ${v("message-max-width")};
1355
- min-width: 0;
1356
- }
1357
-
1358
- .${p}-message-row--outgoing .${p}-message-body {
1359
- align-items: flex-end;
1360
- }
1361
-
1362
- .${p}-nickname {
1363
- margin-bottom: ${v("space-2")};
1364
- padding: 0 ${v("space-2")};
1365
- color: ${v("text-secondary")};
1366
- font-size: ${v("font-size-sm")};
1367
- }
1368
-
1369
- .${p}-bubble {
1370
- position: relative;
1371
- max-width: 100%;
1372
- padding: ${v("space-5")} ${v("space-6")};
1373
- border-radius: ${v("radius-lg")};
1374
- word-break: break-word;
1375
- overflow-wrap: unknownwhere;
1376
- white-space: pre-wrap;
1377
- line-height: ${v("line-height-bubble")};
1378
- }
1379
-
1380
- .${p}-bubble--incoming {
1381
- color: ${v("text-bubble-incoming")};
1382
- background: ${v("bg-bubble-incoming")};
1383
- border-top-left-radius: ${v("radius-xs")};
1384
- }
1385
-
1386
- .${p}-bubble--outgoing {
1387
- color: ${v("text-bubble-outgoing")};
1388
- background: ${v("bg-bubble-outgoing")};
1389
- border-top-right-radius: ${v("radius-xs")};
1390
- }
1391
-
1392
- .${p}-bubble--image {
1393
- padding: ${v("space-2")};
1394
- background: unset;
1395
- }
1396
-
1397
- .${p}-bubble--image img {
1398
- display: block;
1399
- max-width: ${v("image-max-size")};
1400
- max-height: ${v("image-max-size")};
1401
- border-radius: ${v("radius-md")};
1402
- cursor: pointer;
1403
- }
1404
-
1405
- .${p}-bubble--file {
1406
- display: flex;
1407
- align-items: center;
1408
- gap: ${v("space-5")};
1409
- min-width: ${v("file-bubble-min-width")};
1410
- }
1411
-
1412
- .${p}-file-icon {
1413
- display: flex;
1414
- align-items: center;
1415
- justify-content: center;
1416
- width: ${v("file-icon-size")};
1417
- height: ${v("file-icon-size")};
1418
- flex-shrink: 0;
1419
- background: ${v("bg-secondary")};
1420
- border-radius: ${v("radius-sm")};
1421
- font-size: ${v("font-size-lg")};
1422
- }
1423
-
1424
- .${p}-file-info {
1425
- flex: 1;
1426
- min-width: 0;
1427
- overflow: hidden;
1428
- }
1429
-
1430
- .${p}-file-name {
1431
- overflow: hidden;
1432
- font-size: ${v("font-size")};
1433
- text-overflow: ellipsis;
1434
- white-space: nowrap;
1435
- }
1436
-
1437
- .${p}-file-size {
1438
- margin-top: ${v("space-1")};
1439
- color: ${v("text-secondary")};
1440
- font-size: ${v("font-size-sm")};
1441
- }
1442
-
1443
- .${p}-system-message {
1444
- max-width: ${v("system-message-max-width")};
1445
- padding: ${v("space-2")} ${v("space-6")};
1446
- color: ${v("text-system")};
1447
- background: ${v("bg-system-message")};
1448
- border-radius: ${v("radius-pill")};
1449
- font-size: ${v("font-size-sm")};
1450
- text-align: center;
1451
- }
1452
-
1453
- .${p}-message-footer {
1454
- display: flex;
1455
- align-items: center;
1456
- gap: ${v("space-3")};
1457
- margin-top: ${v("space-1")};
1458
- padding: 0 ${v("space-2")};
1459
- }
1460
-
1461
- .${p}-message-row--outgoing .${p}-message-footer {
1462
- flex-direction: row-reverse;
1463
- }
1464
-
1465
- .${p}-timestamp,
1466
- .${p}-status {
1467
- font-size: ${v("font-size-xs")};
1468
- }
1469
-
1470
- .${p}-timestamp {
1471
- color: ${v("text-timestamp")};
1472
- }
1473
-
1474
- .${p}-status--sending { color: ${v("color-sending")}; }
1475
- .${p}-status--sent { color: ${v("color-sent")}; }
1476
- .${p}-status--delivered { color: ${v("color-delivered")}; }
1477
- .${p}-status--read { color: ${v("color-read")}; }
1478
- .${p}-status--failed {
1479
- color: ${v("color-failed")};
1480
- cursor: pointer;
1481
- }
1482
-
1483
- .${p}-mention-tag,
1484
- .${p}-link {
1485
- color: ${v("text-mention-tag")};
1486
- font-weight: ${v("font-weight-medium")};
1487
- }
1488
-
1489
- .${p}-mention-tag {
1490
- cursor: pointer;
1491
- }
1492
-
1493
- .${p}-mention-tag:hover,
1494
- .${p}-link:hover {
1495
- text-decoration: underline;
1496
- }
1497
-
1498
- .${p}-input-area {
1499
- display: flex;
1500
- align-items: flex-end;
1501
- gap: ${v("space-4")};
1502
- position: relative;
1503
- padding: ${v("space-5")} ${v("space-6")};
1504
- border-top: ${v("border-width")} solid ${v("border-primary")};
1505
- background: ${v("bg-input")};
1506
- }
1507
-
1508
- .${p}-input-wrapper {
1509
- flex: 1;
1510
- min-width: 0;
1511
- position: relative;
1512
- }
1513
-
1514
- .${p}-textarea {
1515
- width: 100%;
1516
- min-height: ${v("textarea-min-height")};
1517
- max-height: ${v("textarea-max-height")};
1518
- padding: ${v("space-4")} ${v("space-5")};
1519
- overflow-y: auto;
1520
- resize: none;
1521
- outline: none;
1522
- color: ${v("text-primary")};
1523
- background: ${v("bg-primary")};
1524
- border: ${v("border-width")} solid ${v("border-input")};
1525
- border-radius: ${v("radius-md")};
1526
- font-family: inherit;
1527
- font-size: ${v("font-size")};
1528
- line-height: ${v("line-height")};
1529
- transition: border-color ${v("transition-fast")}, box-shadow ${v("transition-fast")};
1530
- }
1531
-
1532
- .${p}-textarea:focus {
1533
- border-color: ${v("border-input-focus")};
1534
- box-shadow: 0 0 0 ${v("border-width-drop")} ${v("bg-drop-overlay")};
1535
- }
1536
-
1537
- .${p}-textarea::placeholder {
1538
- color: ${v("text-placeholder")};
1539
- }
1540
-
1541
- .${p}-send-btn {
1542
- display: flex;
1543
- align-items: center;
1544
- justify-content: center;
1545
- width: ${v("input-button-size")};
1546
- height: ${v("input-button-size")};
1547
- flex-shrink: 0;
1548
- color: ${v("btn-primary-text")};
1549
- background: ${v("btn-primary-bg")};
1550
- border: 0;
1551
- border-radius: ${v("radius-md")};
1552
- cursor: pointer;
1553
- transition: background ${v("transition-fast")}, opacity ${v("transition-fast")};
1554
- }
1555
-
1556
- .${p}-send-btn:hover {
1557
- background: ${v("btn-primary-hover")};
1558
- }
1559
-
1560
- .${p}-send-btn:disabled {
1561
- cursor: default;
1562
- opacity: .5;
1563
- }
1564
-
1565
- .${p}-send-btn svg {
1566
- width: var(--size-7);
1567
- height: var(--size-7);
1568
- }
1569
-
1570
- .${p}-mention-panel {
1571
- position: absolute;
1572
- left: 0;
1573
- right: 0;
1574
- bottom: 100%;
1575
- z-index: ${v("z-mention")};
1576
- max-height: ${v("panel-max-height")};
1577
- margin-bottom: ${v("space-2")};
1578
- overflow-y: auto;
1579
- background: ${v("bg-mention")};
1580
- border: ${v("border-width")} solid ${v("border-mention")};
1581
- border-radius: ${v("radius-md")};
1582
- box-shadow: ${v("shadow-mention")};
1583
- }
1584
-
1585
- .${p}-mention-item {
1586
- display: flex;
1587
- align-items: center;
1588
- gap: ${v("space-4")};
1589
- padding: ${v("space-4")} ${v("space-5")};
1590
- color: ${v("text-mention")};
1591
- cursor: pointer;
1592
- transition: background ${v("transition-fast")};
1593
- }
1594
-
1595
- .${p}-mention-item:hover {
1596
- background: ${v("bg-mention-hover")};
1597
- }
1598
-
1599
- .${p}-mention-item--active {
1600
- background: ${v("bg-mention-active")};
1601
- }
1602
-
1603
- .${p}-mention-item-avatar {
1604
- display: flex;
1605
- align-items: center;
1606
- justify-content: center;
1607
- width: ${v("mention-avatar-size")};
1608
- height: ${v("mention-avatar-size")};
1609
- flex-shrink: 0;
1610
- overflow: hidden;
1611
- color: ${v("avatar-text")};
1612
- background: ${v("avatar-bg")};
1613
- border-radius: ${v("radius-circle")};
1614
- font-size: ${v("font-size-sm")};
1615
- font-weight: ${v("font-weight-semibold")};
1616
- }
1617
-
1618
- .${p}-mention-item-avatar img {
1619
- width: 100%;
1620
- height: 100%;
1621
- object-fit: cover;
1622
- }
1623
-
1624
- .${p}-mention-item-name {
1625
- font-size: ${v("font-size")};
1626
- }
1627
-
1628
- .${p}-empty {
1629
- display: flex;
1630
- align-items: center;
1631
- justify-content: center;
1632
- height: 100%;
1633
- color: ${v("text-secondary")};
1634
- font-size: ${v("font-size")};
1635
- }
1636
-
1637
- .${p}-toolbar {
1638
- display: flex;
1639
- align-items: center;
1640
- gap: ${v("space-1")};
1641
- padding: ${v("space-3")} ${v("space-4")};
1642
- background: ${v("bg-toolbar")};
1643
- border-bottom: ${v("border-width")} solid ${v("border-toolbar")};
1644
- border-radius: ${v("radius-md")} ${v("radius-md")} 0 0;
1645
- }
1646
-
1647
- .${p}-toolbar-btn {
1648
- display: flex;
1649
- align-items: center;
1650
- justify-content: center;
1651
- width: ${v("toolbar-button-size")};
1652
- height: ${v("toolbar-button-size")};
1653
- flex-shrink: 0;
1654
- padding: 0;
1655
- color: ${v("toolbar-icon-color")};
1656
- background: unset;
1657
- border: 0;
1658
- border-radius: ${v("radius-sm")};
1659
- cursor: pointer;
1660
- transition: background ${v("transition-fast")}, color ${v("transition-fast")};
1661
- }
1662
-
1663
- .${p}-toolbar-btn svg {
1664
- width: var(--size-6);
1665
- height: var(--size-6);
1666
- }
1667
-
1668
- .${p}-toolbar-btn:hover {
1669
- color: ${v("toolbar-icon-hover")};
1670
- background: ${v("bg-drop-overlay")};
1671
- }
1672
-
1673
- .${p}-toolbar-btn--active {
1674
- color: ${v("toolbar-voice-active")};
1675
- background: ${v("bg-danger-soft")};
1676
- }
1677
-
1678
- .${p}-toolbar-btn--active:hover {
1679
- color: ${v("toolbar-voice-active")};
1680
- background: ${v("bg-danger-soft")};
1681
- }
1682
-
1683
- .${p}-drop-overlay {
1684
- position: absolute;
1685
- inset: 0;
1686
- z-index: ${v("z-overlay")};
1687
- display: flex;
1688
- align-items: center;
1689
- justify-content: center;
1690
- pointer-events: none;
1691
- background: ${v("bg-drop-overlay")};
1692
- border: ${v("border-width-drop")} dashed ${v("toolbar-icon-hover")};
1693
- border-radius: ${v("radius-md")};
1694
- }
1695
-
1696
- .${p}-drop-overlay span {
1697
- color: ${v("toolbar-icon-hover")};
1698
- font-size: ${v("font-size-sm")};
1699
- font-weight: ${v("font-weight-medium")};
1700
- }
1701
-
1702
- @keyframes ${p}-blink {
1703
- 0%, 100% { opacity: 1; }
1704
- 50% { opacity: 0; }
1705
- }
1706
-
1707
- .${p}-bubble--streaming::after {
1708
- content: '';
1709
- display: inline-block;
1710
- width: ${v("border-width-drop")};
1711
- height: 1em;
1712
- margin-left: ${v("space-1")};
1713
- vertical-align: text-bottom;
1714
- background: currentColor;
1715
- animation: ${p}-blink 800ms infinite;
1716
- }
1717
- `;
1329
+ function G(e) {
1330
+ return e instanceof Error ? e.message : String(e);
1718
1331
  }
1719
- function v(name) {
1720
- return `var(${COMPONENT_CSS_VAR_PREFIX}-${name})`;
1332
+ function Q() {
1333
+ return new DOMException("The operation was aborted.", "AbortError");
1721
1334
  }
1722
- function toKebab(key) {
1723
- return key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
1335
+ function W(e) {
1336
+ const t = globalThis.crypto;
1337
+ return t?.randomUUID ? t.randomUUID() : `client-${e}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1724
1338
  }
1725
- //#endregion
1726
- //#region src/index.ts
1727
- const version = "0.1.0";
1728
- function createChat(config) {
1729
- const container = typeof config.container === "string" ? document.querySelector(config.container) : config.container;
1730
- if (!container) throw new Error(`[Chat] Container not found: ${config.container}`);
1731
- const prefix = normalizeClassPrefix(config.classPrefix ?? "ldesign-chat");
1732
- const normalizedConfig = {
1733
- ...config,
1734
- classPrefix: prefix
1339
+ exports.CHAT_MESSAGE_SCHEMA_VERSION = 1, exports.COMPONENT_CSS_VAR_PREFIX = t, exports.ChatManager = b, exports.ChatOutbox = class {
1340
+ constructor(e = {}) {
1341
+ this.entries = [], this.hydrated = !1, this.writePromise = Promise.resolve(), this.storage = e.storage ?? new O(), this.now = e.now ?? Date.now;
1342
+ }
1343
+ async hydrate() {
1344
+ return this.hydrated || (this.entries = (await this.storage.load()).map(_), this.hydrated = !0), this.list();
1345
+ }
1346
+ list() {
1347
+ return j(this.entries);
1348
+ }
1349
+ async enqueue(e) {
1350
+ await this.hydrate();
1351
+ const t = a({
1352
+ ...e,
1353
+ clientMessageId: e.clientMessageId ?? e.id
1354
+ }), n = U(t), s = this.entries.find((e) => e.id === n);
1355
+ if (s) return V(s);
1356
+ const i = {
1357
+ id: n,
1358
+ message: t,
1359
+ enqueuedAt: this.now(),
1360
+ attempts: 0,
1361
+ state: "pending"
1362
+ };
1363
+ return this.entries.push(i), await this.persist(), V(i);
1364
+ }
1365
+ async remove(e) {
1366
+ await this.hydrate();
1367
+ const t = this.entries.findIndex((t) => t.id === e);
1368
+ return !(t < 0) && (this.entries.splice(t, 1), await this.persist(), !0);
1369
+ }
1370
+ async retry(e) {
1371
+ await this.hydrate();
1372
+ for (const t of this.entries) e && t.id !== e || (t.state = "pending", t.lastError = void 0);
1373
+ await this.persist();
1374
+ }
1375
+ async clear() {
1376
+ await this.hydrate(), this.entries = [], await this.persist();
1377
+ }
1378
+ async flush(e, t = {}) {
1379
+ await this.hydrate();
1380
+ const n = Math.max(1, Math.floor(t.maxAttempts ?? 3)), s = Math.min(8, Math.max(1, Math.floor(t.concurrency ?? 1))), i = this.entries.filter((e) => "pending" === e.state), r = [], a = [];
1381
+ let o = 0;
1382
+ const l = async () => {
1383
+ for (; o < i.length;) {
1384
+ if (t.signal?.aborted) throw Q();
1385
+ const s = i[o++];
1386
+ s.attempts += 1;
1387
+ try {
1388
+ await e(s.message, {
1389
+ signal: t.signal,
1390
+ clientMessageId: s.message.clientMessageId ?? s.id
1391
+ });
1392
+ const n = this.entries.findIndex((e) => e.id === s.id);
1393
+ n >= 0 && this.entries.splice(n, 1), r.push(s.id);
1394
+ } catch (e) {
1395
+ s.lastError = G(e), s.attempts >= n && (s.state = "failed"), a.push({
1396
+ id: s.id,
1397
+ error: e
1398
+ });
1399
+ }
1400
+ await this.persist();
1401
+ }
1402
+ };
1403
+ return await Promise.all(Array.from({ length: s }, () => l())), {
1404
+ sent: r,
1405
+ failed: a,
1406
+ remaining: this.list()
1407
+ };
1408
+ }
1409
+ async persist() {
1410
+ const e = j(this.entries), t = this.writePromise;
1411
+ this.writePromise = t.then(() => this.storage.save(e)), await this.writePromise;
1412
+ }
1413
+ }, exports.ChatTransportController = $, exports.DEFAULT_CLASS_PREFIX = e, exports.DOMRenderer = R, exports.EventEmitter = v, exports.MemoryChatOutboxStorage = O, exports.MentionEngine = y, exports.STYLE_ELEMENT_ID = n, exports.ThemeManager = N, exports.cloneChatMessage = d, exports.cls = function(e, ...t) {
1414
+ const n = s(e);
1415
+ return t.map((e) => `${n}-${e}`).join(" ");
1416
+ }, exports.clsx = E, exports.compareChatMessages = function(e, t) {
1417
+ return Number.isFinite(e.sequence) && Number.isFinite(t.sequence) && e.sequence !== t.sequence ? e.sequence - t.sequence : e.timestamp !== t.timestamp ? e.timestamp - t.timestamp : e.id.localeCompare(t.id);
1418
+ }, exports.createChat = function(e) {
1419
+ let t;
1420
+ if ("string" == typeof e.container) {
1421
+ if ("undefined" == typeof document) throw new Error("[Chat] A browser Document is required to resolve a container selector.");
1422
+ t = document.querySelector(e.container);
1423
+ } else t = e.container;
1424
+ if (!t) throw new Error(`[Chat] Container not found: ${e.container}`);
1425
+ const n = t.ownerDocument, i = s(e.classPrefix ?? "ldesign-chat"), r = {
1426
+ ...e,
1427
+ classPrefix: i
1428
+ }, o = new v(), l = new b(o, r.messages), d = new y(o, r.mentionMembers), c = new N(o, r.theme ?? "light", n.defaultView), h = new R(t, r, o, d);
1429
+ let m = !1;
1430
+ const g = (e) => {
1431
+ const t = () => {
1432
+ m || e();
1433
+ };
1434
+ n.defaultView?.requestAnimationFrame ? n.defaultView.requestAnimationFrame(t) : setTimeout(t, 0);
1735
1435
  };
1736
- const emitter = new EventEmitter();
1737
- const chatManager = new ChatManager(emitter, normalizedConfig.messages);
1738
- const mentionEngine = new MentionEngine(emitter, normalizedConfig.mentionMembers);
1739
- const themeManager = new ThemeManager(emitter, normalizedConfig.theme ?? "light");
1740
- const renderer = new DOMRenderer(container, normalizedConfig, emitter, mentionEngine);
1741
- injectStyles(themeManager.getTokens(), prefix);
1742
- renderer.init();
1743
- renderer.renderMessages(chatManager.getMessages());
1744
- requestAnimationFrame(() => {
1745
- renderer.scrollToBottom(false);
1746
- });
1747
- emitter.on("theme-change", (_resolved, tokens) => {
1748
- updateStyles(tokens, prefix);
1749
- });
1750
- emitter.on("message-added", (message) => {
1751
- renderer.appendMessage(message);
1752
- requestAnimationFrame(() => {
1753
- renderer.scrollToBottom(true);
1436
+ h.init(), h.renderMessages(l.getMessages()), h.setThemeTokens(c.getTokens()), w(c.getTokens(), i, h.getRootElement());
1437
+ const u = async (e) => {
1438
+ const t = l.getMessageIdByClientMessageId(e.clientMessageId);
1439
+ t && l.updateMessage(t, {
1440
+ ...e.message ?? {},
1441
+ id: t,
1442
+ clientMessageId: e.clientMessageId,
1443
+ status: "sent",
1444
+ ...void 0 !== e.sequence && { sequence: e.sequence },
1445
+ ...void 0 !== e.cursor && { cursor: e.cursor }
1446
+ }), r.outbox && await r.outbox.remove(e.clientMessageId), o.emit("transport-ack", e);
1447
+ }, p = r.transport ? new $(r.transport, {
1448
+ conversationId: r.conversationId,
1449
+ cursor: r.transportCursor,
1450
+ onEvent: (e) => {
1451
+ "message" === e.type ? l.upsertMessage({
1452
+ ...e.message,
1453
+ ...void 0 !== e.cursor && { cursor: e.cursor }
1454
+ }, {
1455
+ sequence: e.sequence,
1456
+ source: "remote"
1457
+ }) : "ack" === e.type ? u(e.ack).catch((e) => {
1458
+ o.emit("transport-error", e);
1459
+ }) : "cursor" === e.type ? o.emit("transport-cursor", e.cursor) : o.emit("transport-error", e.error, e.recoverable);
1460
+ },
1461
+ onStatusChange: (e) => {
1462
+ o.emit("transport-status", e);
1463
+ }
1464
+ }) : null;
1465
+ g(() => {
1466
+ h.scrollToBottom(!1);
1467
+ }), o.on("theme-change", (e, t) => {
1468
+ h.setThemeTokens(t);
1469
+ }), o.on("message-added", (e) => {
1470
+ h.appendMessage(e), g(() => {
1471
+ h.scrollToBottom(!0);
1754
1472
  });
1755
- });
1756
- emitter.on("messages-prepended", (messages) => {
1757
- renderer.prependMessages(messages);
1758
- });
1759
- emitter.on("message-updated", (message) => {
1760
- renderer.updateMessage(message.id, message);
1761
- });
1762
- emitter.on("message-removed", (message) => {
1763
- renderer.removeMessage(message.id);
1764
- });
1765
- emitter.on("message-content-appended", (id, content) => {
1766
- renderer.updateMessageContent(id, content);
1767
- requestAnimationFrame(() => {
1768
- renderer.scrollToBottom(false);
1473
+ }), o.on("mention-select", (e) => {
1474
+ r.onMentionSelect?.(e);
1475
+ }), o.on("messages-prepended", (e) => {
1476
+ h.prependMessages(e);
1477
+ }), o.on("message-updated", (e) => {
1478
+ h.updateMessage(e.id, e);
1479
+ }), o.on("message-removed", (e) => {
1480
+ h.removeMessage(e.id);
1481
+ }), o.on("message-content-appended", (e, t) => {
1482
+ h.updateMessageContent(e, t), g(() => {
1483
+ h.scrollToBottom(!1);
1769
1484
  });
1770
1485
  });
1771
- return {
1772
- addMessage(message) {
1773
- chatManager.addMessage(message);
1486
+ const f = {
1487
+ addMessage(e) {
1488
+ m || l.addMessage(e);
1774
1489
  },
1775
- prependMessages(messages) {
1776
- chatManager.prependMessages(messages);
1490
+ prependMessages(e) {
1491
+ m || l.prependMessages(e);
1777
1492
  },
1778
- updateMessage(id, updates) {
1779
- chatManager.updateMessage(id, updates);
1493
+ updateMessage(e, t) {
1494
+ m || l.updateMessage(e, t);
1780
1495
  },
1781
- removeMessage(id) {
1782
- chatManager.removeMessage(id);
1496
+ removeMessage(e) {
1497
+ m || l.removeMessage(e);
1783
1498
  },
1784
- setMessages(messages) {
1785
- chatManager.setMessages(messages);
1786
- renderer.renderMessages(chatManager.getMessages());
1787
- requestAnimationFrame(() => {
1788
- renderer.scrollToBottom(false);
1789
- });
1499
+ setMessages(e) {
1500
+ m || (l.setMessages(e), h.renderMessages(l.getMessages()), g(() => {
1501
+ h.scrollToBottom(!1);
1502
+ }));
1790
1503
  },
1791
1504
  clearMessages() {
1792
- chatManager.clearMessages();
1793
- renderer.renderMessages([]);
1505
+ m || (l.clearMessages(), h.renderMessages([]));
1794
1506
  },
1795
- scrollToBottom(smooth = true) {
1796
- renderer.scrollToBottom(smooth);
1507
+ scrollToBottom(e = !0) {
1508
+ m || h.scrollToBottom(e);
1797
1509
  },
1798
- setTheme(theme) {
1799
- themeManager.setTheme(theme);
1510
+ setTheme(e) {
1511
+ m || c.setTheme(e);
1800
1512
  },
1801
- setMentionMembers(members) {
1802
- mentionEngine.setMembers(members);
1513
+ setMentionMembers(e) {
1514
+ m || d.setMembers(e);
1803
1515
  },
1804
- setLoadingMore(loading) {
1805
- renderer.setLoadingMore(loading);
1516
+ setLoadingMore(e) {
1517
+ m || h.setLoadingMore(e);
1806
1518
  },
1807
1519
  focusInput() {
1808
- renderer.focusInput();
1809
- },
1810
- setInputContent(content) {
1811
- renderer.setInputContent(content);
1520
+ m || h.focusInput();
1812
1521
  },
1813
- getInputContent() {
1814
- return renderer.getInputContent();
1522
+ setInputContent(e) {
1523
+ m || h.setInputContent(e);
1815
1524
  },
1525
+ getInputContent: () => m ? "" : h.getInputContent(),
1816
1526
  clearInput() {
1817
- renderer.clearInput();
1527
+ m || h.clearInput();
1818
1528
  },
1819
- isAtBottom(threshold) {
1820
- return renderer.isAtBottom(threshold);
1529
+ isAtBottom: (e) => m || h.isAtBottom(e),
1530
+ on: (e, t) => m ? () => {} : o.on(e, t),
1531
+ getMessages: () => m ? [] : l.getMessages(),
1532
+ getMessage: (e) => m ? void 0 : l.getMessage(e),
1533
+ appendMessageContent: (e, t) => !m && l.appendMessageContent(e, t),
1534
+ finishStreaming: (e, t) => !m && l.finishStreaming(e, t),
1535
+ beginStreaming: (e, t) => m ? null : l.beginStreaming(e, t),
1536
+ cancelStreaming: (e, t) => !m && l.cancelStreaming(e, t),
1537
+ mergeMessage: (e, t) => m ? "ignored" : l.upsertMessage(e, {
1538
+ sequence: t,
1539
+ source: "remote"
1540
+ }),
1541
+ async connectTransport(e) {
1542
+ if (!p) throw new Error("No chat transport is configured.");
1543
+ await p.connect(e);
1821
1544
  },
1822
- on(event, handler) {
1823
- return emitter.on(event, handler);
1545
+ async disconnectTransport() {
1546
+ await p?.disconnect();
1824
1547
  },
1825
- getMessages() {
1826
- return chatManager.getMessages();
1827
- },
1828
- getMessage(id) {
1829
- return chatManager.getMessage(id);
1830
- },
1831
- appendMessageContent(id, chunk) {
1832
- chatManager.appendMessageContent(id, chunk);
1548
+ getTransportStatus: () => p?.getStatus() ?? "idle",
1549
+ async sendMessage(e, t = {}) {
1550
+ if (!p) throw new Error("No chat transport is configured.");
1551
+ if (m) throw new Error("Chat instance is destroyed.");
1552
+ const n = a({
1553
+ ...e,
1554
+ clientMessageId: e.clientMessageId ?? W(e.id),
1555
+ status: e.status ?? "sending"
1556
+ });
1557
+ l.addMessage(n);
1558
+ try {
1559
+ r.outbox && await r.outbox.enqueue(n);
1560
+ const e = await p.send(n, {
1561
+ signal: t.signal,
1562
+ clientMessageId: n.clientMessageId
1563
+ });
1564
+ return e ? await u(e) : l.markMessageStatus(n.id, "sent"), o.emit("message-sent", n, e), e;
1565
+ } catch (e) {
1566
+ throw l.markMessageStatus(n.id, "failed"), o.emit("message-send-failed", n, e), e;
1567
+ }
1833
1568
  },
1834
- finishStreaming(id) {
1835
- chatManager.finishStreaming(id);
1569
+ async flushOutbox(e = {}) {
1570
+ if (!r.outbox) return {
1571
+ sent: [],
1572
+ failed: [],
1573
+ remaining: []
1574
+ };
1575
+ if (!p) throw new Error("No chat transport is configured.");
1576
+ return r.outbox.flush(async (e, t) => {
1577
+ const n = await p.send(e, t);
1578
+ n ? await u(n) : e.id && l.markMessageStatus(e.id, "sent");
1579
+ }, e);
1836
1580
  },
1837
1581
  destroy() {
1838
- themeManager.destroy();
1839
- renderer.destroy();
1840
- emitter.removeAll();
1841
- removeStyles(prefix);
1582
+ m || (m = !0, p?.dispose(), c.destroy(), h.destroy(), o.removeAll(), M(i, n));
1842
1583
  }
1843
1584
  };
1844
- }
1845
- //#endregion
1846
- exports.COMPONENT_CSS_VAR_PREFIX = COMPONENT_CSS_VAR_PREFIX;
1847
- exports.ChatManager = ChatManager;
1848
- exports.DEFAULT_CLASS_PREFIX = DEFAULT_CLASS_PREFIX;
1849
- exports.DOMRenderer = DOMRenderer;
1850
- exports.EventEmitter = EventEmitter;
1851
- exports.MentionEngine = MentionEngine;
1852
- exports.STYLE_ELEMENT_ID = STYLE_ELEMENT_ID;
1853
- exports.ThemeManager = ThemeManager;
1854
- exports.cls = cls;
1855
- exports.clsx = clsx;
1856
- exports.createChat = createChat;
1857
- exports.cx = cx;
1858
- exports.darkTokens = darkTokens;
1859
- exports.getStyleElementId = getStyleElementId;
1860
- exports.injectStyles = injectStyles;
1861
- exports.lightTokens = lightTokens;
1862
- exports.normalizeClassPrefix = normalizeClassPrefix;
1863
- exports.removeStyles = removeStyles;
1864
- exports.updateStyles = updateStyles;
1865
- exports.version = version;
1585
+ return r.autoConnect && p && f.connectTransport().catch((e) => {
1586
+ o.emit("transport-error", e);
1587
+ }), f;
1588
+ }, exports.cx = r, exports.darkTokens = F, exports.getStyleElementId = i, exports.injectStyles = w, exports.lightTokens = H, exports.normalizeChatContentBlocks = o, exports.normalizeChatMessage = a, exports.normalizeChatMessages = l, exports.normalizeClassPrefix = s, exports.removeStyles = M, exports.updateStyles = function(t, n = e, r) {
1589
+ const a = s(n), o = i(a);
1590
+ r ? C(r, t) : k(I(), o, L(t, a));
1591
+ }, exports.version = "0.1.0";
1866
1592
  });
1867
1593
  /*! End of @composy/chat-core | Powered by @composy/builder */
1868
- //# sourceMappingURL=index.js.map
1594
+ //# sourceMappingURL=index.min.js.map