@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
@@ -1,8 +1,8 @@
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
  * ***********************************
@@ -14,8 +14,8 @@ const STYLE_ELEMENT_ID = "ldesign-chat-styles";
14
14
  function normalizeClassPrefix(prefix) {
15
15
  const trimmed = prefix?.trim();
16
16
  if (!trimmed) return DEFAULT_CLASS_PREFIX;
17
- if (trimmed.startsWith("ldesign-")) return trimmed;
18
- return `ldesign-${trimmed.replace(/^-+/, "")}`;
17
+ const safeSuffix = trimmed.replace(/^ldesign-/i, "").replace(/[^\w-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
18
+ return safeSuffix ? `ldesign-${safeSuffix}` : DEFAULT_CLASS_PREFIX;
19
19
  }
20
20
  function getStyleElementId(prefix = DEFAULT_CLASS_PREFIX) {
21
21
  const normalized = normalizeClassPrefix(prefix);
@@ -1 +1 @@
1
- {"version":3,"file":"dom.js","names":[],"sources":["../../src/constants/dom.ts"],"sourcesContent":["/**\n * @file dom.ts\n * @description dom 模块\n */\n\nexport const DEFAULT_CLASS_PREFIX = 'ldesign-chat';\nexport const COMPONENT_CSS_VAR_PREFIX = '--ldesign-chat';\nexport const STYLE_ELEMENT_ID = 'ldesign-chat-styles';\n\n/**\n * normalizeClassPrefix 函数\n * @param prefix? - 参数\n * @returns 返回值\n */\n/**\n * normalizeClassPrefix - normalize Class Prefix function\n * @param prefix? - 参数说明\n * @returns 返回值说明\n */\nexport function normalizeClassPrefix(prefix?: string): string {\n const trimmed = prefix?.trim();\n if (!trimmed) return DEFAULT_CLASS_PREFIX;\n if (trimmed.startsWith('ldesign-')) return trimmed;\n return `ldesign-${trimmed.replace(/^-+/, '')}`;\n}\n\n/**\n * getStyleElementId 函数\n * @param prefix = DEFAULT_CLASS_PREFIX - 参数\n * @returns 返回值\n */\n/**\n * getStyleElementId - get Style Element Id function\n * @param prefix = DEFAULT_CLASS_PREFIX - 参数说明\n * @returns 返回值说明\n */\nexport function getStyleElementId(prefix = DEFAULT_CLASS_PREFIX): string {\n const normalized = normalizeClassPrefix(prefix);\n return normalized === DEFAULT_CLASS_PREFIX\n ? STYLE_ELEMENT_ID\n : `${normalized}-styles`;\n}\n\n/**\n * cx 函数\n * @param ...classes - 参数\n * @returns 返回值\n */\n/**\n * cx - cx function\n * @param ...classes - 参数说明\n * @returns 返回值说明\n */\nexport function cx(...classes: (string | false | null | undefined)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n"],"mappings":";;;;;;;;;;AAKO,MAAM,uBAAuB;AAC7B,MAAM,2BAA2B;AACjC,MAAM,mBAAmB;AAYzB,SAAS,qBAAqB,QAAyB;CAC5D,MAAM,UAAU,QAAQ,KAAK;CAC7B,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,QAAQ,WAAW,UAAU,GAAG,OAAO;CAC3C,OAAO,WAAW,QAAQ,QAAQ,OAAO,EAAE;AAC7C;AAYO,SAAS,kBAAkB,SAAS,sBAA8B;CACvE,MAAM,aAAa,qBAAqB,MAAM;CAC9C,OAAO,eAAA,iBACH,mBACA,GAAG,WAAU;AACnB;AAYO,SAAS,GAAA,GAAM,SAAwD;CAC5E,OAAO,QAAQ,OAAO,OAAO,CAAA,CAAE,KAAK,GAAG;AACzC"}
1
+ {"version":3,"file":"dom.js","names":[],"sources":["../../src/constants/dom.ts"],"sourcesContent":["/**\r\n * @file dom.ts\r\n * @description dom 模块\r\n */\r\n\r\nexport const DEFAULT_CLASS_PREFIX = 'ldesign-chat'\r\nexport const COMPONENT_CSS_VAR_PREFIX = '--ldesign-chat'\r\nexport const STYLE_ELEMENT_ID = 'ldesign-chat-styles'\r\n\r\n/**\r\n * normalizeClassPrefix - normalize Class Prefix function\r\n * @param prefix - Optional class prefix\r\n * @returns 返回值说明\r\n */\r\nexport function normalizeClassPrefix(prefix?: string): string {\r\n const trimmed = prefix?.trim()\r\n if (!trimmed) return DEFAULT_CLASS_PREFIX\r\n\r\n const withoutNamespace = trimmed\r\n .replace(/^ldesign-/i, '')\r\n .replace(/[^\\w-]+/g, '-')\r\n const safeSuffix = withoutNamespace.replace(/-+/g, '-').replace(/^-|-$/g, '')\r\n return safeSuffix ? `ldesign-${safeSuffix}` : DEFAULT_CLASS_PREFIX\r\n}\r\n\r\n/**\r\n * getStyleElementId - get Style Element Id function\r\n * @param prefix - Class prefix\r\n * @returns 返回值说明\r\n */\r\nexport function getStyleElementId(prefix = DEFAULT_CLASS_PREFIX): string {\r\n const normalized = normalizeClassPrefix(prefix)\r\n return normalized === DEFAULT_CLASS_PREFIX\r\n ? STYLE_ELEMENT_ID\r\n : `${normalized}-styles`\r\n}\r\n\r\n/**\r\n * cx - cx function\r\n * @param classes - Class names to join\r\n * @returns 返回值说明\r\n */\r\nexport function cx(...classes: (string | false | null | undefined)[]): string {\r\n return classes.filter(Boolean).join(' ')\r\n}\r\n"],"mappings":";;;;;;;;;;AAKO,MAAM,uBAAuB;AAC7B,MAAM,2BAA2B;AACjC,MAAM,mBAAmB;AAOzB,SAAS,qBAAqB,QAAyB;CAC5D,MAAM,UAAU,QAAQ,KAAK;CAC7B,IAAI,CAAC,SAAS,OAAO;CAKrB,MAAM,aAHmB,QACtB,QAAQ,cAAc,EAAE,CAAA,CACxB,QAAQ,YAAY,GACJ,CAAA,CAAiB,QAAQ,OAAO,GAAG,CAAA,CAAE,QAAQ,UAAU,EAAE;CAC5E,OAAO,aAAa,WAAW,eAAe;AAChD;AAOO,SAAS,kBAAkB,SAAS,sBAA8B;CACvE,MAAM,aAAa,qBAAqB,MAAM;CAC9C,OAAO,eAAA,iBACH,mBACA,GAAG,WAAU;AACnB;AAOO,SAAS,GAAA,GAAM,SAAwD;CAC5E,OAAO,QAAQ,OAAO,OAAO,CAAA,CAAE,KAAK,GAAG;AACzC"}
@@ -1,34 +1,45 @@
1
- /**
2
- * 聊天管理器 - 消息增删改查、状态管理
3
- */
4
- import type { ChatMessage } from '../types';
5
- import { EventEmitter } from './event-emitter';
6
- /**
7
- * ChatManager - Chat Manager class
8
- */
1
+ import type { ChatMessage, MessageStatus } from '../types';
2
+ import type { EventEmitter } from './event-emitter';
3
+ export type MessageMergeResult = 'added' | 'updated' | 'ignored';
4
+ export interface MessageMergeOptions {
5
+ sequence?: number;
6
+ source?: 'local' | 'remote';
7
+ }
8
+ export interface StreamAppendOptions {
9
+ generation?: string;
10
+ sequence?: number;
11
+ }
12
+ /** ChatManager - 聊天消息状态、幂等合并和流式 generation 管理。 */
9
13
  export declare class ChatManager {
10
14
  private messages;
11
- private emitter;
15
+ private readonly emitter;
16
+ private readonly streams;
12
17
  constructor(emitter: EventEmitter, initialMessages?: ChatMessage[]);
13
- /** 获取所有消息 */
14
18
  getMessages(): ChatMessage[];
15
- /** 根据 ID 获取消息 */
16
19
  getMessage(id: string): ChatMessage | undefined;
17
- /** 添加消息(末尾追加) */
20
+ getMessageIdByClientMessageId(clientMessageId: string): string | undefined;
18
21
  addMessage(message: ChatMessage): void;
19
- /** 在顶部批量插入消息(用于加载更多) */
20
22
  prependMessages(messages: ChatMessage[]): void;
21
- /** 更新消息 */
23
+ /** 合并本地 ack 或远端推送,按 id/clientMessageId 去重并拒绝旧 sequence。 */
24
+ upsertMessage(message: ChatMessage, options?: MessageMergeOptions): MessageMergeResult;
25
+ mergeMessages(messages: ChatMessage[], options?: MessageMergeOptions): {
26
+ added: number;
27
+ updated: number;
28
+ ignored: number;
29
+ };
22
30
  updateMessage(id: string, updates: Partial<ChatMessage>): void;
23
- /** 删除消息 */
31
+ markMessageStatus(id: string, status: MessageStatus): void;
24
32
  removeMessage(id: string): void;
25
- /** 替换全部消息 */
26
33
  setMessages(messages: ChatMessage[]): void;
27
- /** 清空所有消息 */
28
34
  clearMessages(): void;
29
- /** 追加流式消息内容 */
30
- appendMessageContent(id: string, chunk: string): void;
31
- /** 完成流式消息 */
32
- finishStreaming(id: string): void;
35
+ beginStreaming(id: string, generation?: string): string | null;
36
+ appendMessageContent(id: string, chunk: string, options?: StreamAppendOptions): boolean;
37
+ finishStreaming(id: string, generation?: string): boolean;
38
+ cancelStreaming(id: string, generation?: string): boolean;
39
+ getStreamGeneration(id: string): string | undefined;
40
+ private findMessageIndex;
41
+ private assertUniqueIds;
42
+ private assertUniqueClientMessageId;
43
+ private emitAdded;
44
+ private emitChanged;
33
45
  }
34
- //# sourceMappingURL=chat-manager.d.ts.map
@@ -1,92 +1,216 @@
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
+ import { cloneChatMessage, normalizeChatMessage, normalizeChatMessages } from "./message-schema.js";
10
11
  //#region src/core/chat-manager.ts
11
12
  var ChatManager = class {
12
13
  constructor(emitter, initialMessages) {
13
14
  this.messages = [];
15
+ this.streams = /* @__PURE__ */ new Map();
14
16
  this.emitter = emitter;
15
- if (initialMessages?.length) this.messages = [...initialMessages];
17
+ if (initialMessages?.length) this.messages = normalizeChatMessages(initialMessages);
16
18
  }
17
- /** 获取所有消息 */
18
19
  getMessages() {
19
- return [...this.messages];
20
+ return this.messages.map(cloneChatMessage);
20
21
  }
21
- /** 根据 ID 获取消息 */
22
22
  getMessage(id) {
23
- return this.messages.find((m) => m.id === id);
23
+ const message = this.messages.find((item) => item.id === id);
24
+ return message ? cloneChatMessage(message) : void 0;
25
+ }
26
+ getMessageIdByClientMessageId(clientMessageId) {
27
+ return this.messages.find((message) => message.clientMessageId === clientMessageId)?.id;
24
28
  }
25
- /** 添加消息(末尾追加) */
26
29
  addMessage(message) {
27
- this.messages.push(message);
28
- this.emitter.emit("message-added", message);
29
- this.emitter.emit("messages-changed", this.getMessages());
30
+ const storedMessage = normalizeChatMessage(message);
31
+ this.assertUniqueIds([storedMessage]);
32
+ this.messages.push(storedMessage);
33
+ this.emitAdded(storedMessage);
30
34
  }
31
- /** 在顶部批量插入消息(用于加载更多) */
32
35
  prependMessages(messages) {
33
- this.messages.unshift(...messages);
34
- this.emitter.emit("messages-prepended", messages);
35
- this.emitter.emit("messages-changed", this.getMessages());
36
+ const storedMessages = normalizeChatMessages(messages);
37
+ this.assertUniqueIds(storedMessages);
38
+ this.messages.unshift(...storedMessages);
39
+ this.emitter.emit("messages-prepended", storedMessages.map(cloneChatMessage));
40
+ this.emitChanged();
41
+ }
42
+ /** 合并本地 ack 或远端推送,按 id/clientMessageId 去重并拒绝旧 sequence。 */
43
+ upsertMessage(message, options = {}) {
44
+ const normalized = normalizeChatMessage({
45
+ ...message,
46
+ ...options.sequence !== void 0 && { sequence: options.sequence }
47
+ });
48
+ const index = this.findMessageIndex(normalized);
49
+ if (index < 0) {
50
+ this.messages.push(normalized);
51
+ this.emitAdded(normalized);
52
+ return "added";
53
+ }
54
+ const current = this.messages[index];
55
+ if (Number.isFinite(normalized.sequence) && Number.isFinite(current.sequence) && normalized.sequence <= current.sequence) return "ignored";
56
+ const merged = normalizeChatMessage({
57
+ ...current,
58
+ ...normalized,
59
+ id: current.id,
60
+ clientMessageId: normalized.clientMessageId ?? current.clientMessageId,
61
+ sequence: normalized.sequence ?? current.sequence
62
+ });
63
+ this.messages[index] = merged;
64
+ this.emitter.emit("message-updated", cloneChatMessage(merged));
65
+ this.emitChanged();
66
+ return "updated";
67
+ }
68
+ mergeMessages(messages, options = {}) {
69
+ const result = {
70
+ added: 0,
71
+ updated: 0,
72
+ ignored: 0
73
+ };
74
+ for (const message of messages) {
75
+ const outcome = this.upsertMessage(message, options);
76
+ result[outcome] += 1;
77
+ }
78
+ return result;
36
79
  }
37
- /** 更新消息 */
38
80
  updateMessage(id, updates) {
39
- const index = this.messages.findIndex((m) => m.id === id);
81
+ const index = this.messages.findIndex((message) => message.id === id);
40
82
  if (index === -1) return;
41
- this.messages[index] = {
83
+ if (updates.id !== void 0 && updates.id !== id) throw new Error("Chat message IDs are immutable.");
84
+ const next = normalizeChatMessage({
42
85
  ...this.messages[index],
43
- ...updates
44
- };
45
- this.emitter.emit("message-updated", this.messages[index]);
46
- this.emitter.emit("messages-changed", this.getMessages());
86
+ ...updates,
87
+ id
88
+ });
89
+ this.assertUniqueClientMessageId(next, id);
90
+ this.messages[index] = next;
91
+ this.emitter.emit("message-updated", cloneChatMessage(next));
92
+ this.emitChanged();
93
+ }
94
+ markMessageStatus(id, status) {
95
+ this.updateMessage(id, { status });
47
96
  }
48
- /** 删除消息 */
49
97
  removeMessage(id) {
50
- const index = this.messages.findIndex((m) => m.id === id);
98
+ const index = this.messages.findIndex((message) => message.id === id);
51
99
  if (index === -1) return;
52
- const removed = this.messages.splice(index, 1)[0];
53
- this.emitter.emit("message-removed", removed);
54
- this.emitter.emit("messages-changed", this.getMessages());
100
+ const [removed] = this.messages.splice(index, 1);
101
+ this.streams.delete(id);
102
+ this.emitter.emit("message-removed", cloneChatMessage(removed));
103
+ this.emitChanged();
55
104
  }
56
- /** 替换全部消息 */
57
105
  setMessages(messages) {
58
- this.messages = [...messages];
59
- this.emitter.emit("messages-changed", this.getMessages());
106
+ this.messages = normalizeChatMessages(messages);
107
+ this.streams.clear();
108
+ this.emitChanged();
60
109
  }
61
- /** 清空所有消息 */
62
110
  clearMessages() {
63
111
  this.messages = [];
64
- this.emitter.emit("messages-changed", []);
112
+ this.streams.clear();
113
+ this.emitChanged();
65
114
  }
66
- /** 追加流式消息内容 */
67
- appendMessageContent(id, chunk) {
68
- const index = this.messages.findIndex((m) => m.id === id);
69
- if (index === -1) return;
70
- const msg = this.messages[index];
71
- this.messages[index] = {
72
- ...msg,
73
- content: (msg.content ?? "") + chunk
74
- };
75
- this.emitter.emit("message-content-appended", id, this.messages[index].content);
76
- this.emitter.emit("messages-changed", this.getMessages());
115
+ beginStreaming(id, generation = createGeneration()) {
116
+ const index = this.messages.findIndex((message) => message.id === id);
117
+ if (index === -1) return null;
118
+ this.streams.set(id, {
119
+ generation,
120
+ lastSequence: -1
121
+ });
122
+ this.messages[index] = normalizeChatMessage({
123
+ ...this.messages[index],
124
+ streaming: true
125
+ });
126
+ this.emitter.emit("message-updated", cloneChatMessage(this.messages[index]));
127
+ this.emitChanged();
128
+ return generation;
77
129
  }
78
- /** 完成流式消息 */
79
- finishStreaming(id) {
80
- const index = this.messages.findIndex((m) => m.id === id);
81
- if (index === -1) return;
82
- this.messages[index] = {
130
+ appendMessageContent(id, chunk, options = {}) {
131
+ const stream = this.streams.get(id);
132
+ if (options.generation) {
133
+ if (!stream || stream.generation !== options.generation) return false;
134
+ const sequence = options.sequence;
135
+ if (sequence !== void 0 && Number.isFinite(sequence) && sequence <= stream.lastSequence) return false;
136
+ if (sequence !== void 0 && Number.isFinite(sequence)) stream.lastSequence = sequence;
137
+ }
138
+ const index = this.messages.findIndex((message2) => message2.id === id);
139
+ if (index === -1) return false;
140
+ const message = this.messages[index];
141
+ this.messages[index] = normalizeChatMessage({
142
+ ...message,
143
+ content: (message.content ?? "") + chunk,
144
+ streaming: options.generation ? true : message.streaming
145
+ });
146
+ this.emitter.emit("message-content-appended", id, this.messages[index].content, options.generation, options.sequence);
147
+ this.emitChanged();
148
+ return true;
149
+ }
150
+ finishStreaming(id, generation) {
151
+ const stream = this.streams.get(id);
152
+ if (generation && (!stream || stream.generation !== generation)) return false;
153
+ const index = this.messages.findIndex((message) => message.id === id);
154
+ if (index === -1) return false;
155
+ this.messages[index] = normalizeChatMessage({
83
156
  ...this.messages[index],
84
157
  streaming: false
85
- };
86
- this.emitter.emit("message-updated", this.messages[index]);
158
+ });
159
+ this.streams.delete(id);
160
+ this.emitter.emit("message-updated", cloneChatMessage(this.messages[index]));
161
+ this.emitChanged();
162
+ return true;
163
+ }
164
+ cancelStreaming(id, generation) {
165
+ const stream = this.streams.get(id);
166
+ if (generation && (!stream || stream.generation !== generation)) return false;
167
+ const index = this.messages.findIndex((message) => message.id === id);
168
+ if (index === -1) return false;
169
+ this.streams.delete(id);
170
+ this.messages[index] = normalizeChatMessage({
171
+ ...this.messages[index],
172
+ streaming: false
173
+ });
174
+ this.emitter.emit("stream-cancelled", cloneChatMessage(this.messages[index]));
175
+ this.emitter.emit("message-updated", cloneChatMessage(this.messages[index]));
176
+ this.emitChanged();
177
+ return true;
178
+ }
179
+ getStreamGeneration(id) {
180
+ return this.streams.get(id)?.generation;
181
+ }
182
+ findMessageIndex(message) {
183
+ const byId = this.messages.findIndex((item) => item.id === message.id);
184
+ if (byId >= 0) return byId;
185
+ if (!message.clientMessageId) return -1;
186
+ return this.messages.findIndex((item) => item.clientMessageId === message.clientMessageId);
187
+ }
188
+ assertUniqueIds(messages) {
189
+ const ids = new Set(this.messages.map((message) => message.id));
190
+ const clientIds = new Set(this.messages.flatMap((message) => message.clientMessageId ? [message.clientMessageId] : []));
191
+ for (const message of messages) {
192
+ if (!message.id || ids.has(message.id)) throw new Error(`Chat message ID must be non-empty and unique: ${message.id}`);
193
+ if (message.clientMessageId && clientIds.has(message.clientMessageId)) throw new Error(`Chat client message ID must be unique: ${message.clientMessageId}`);
194
+ ids.add(message.id);
195
+ if (message.clientMessageId) clientIds.add(message.clientMessageId);
196
+ }
197
+ }
198
+ assertUniqueClientMessageId(message, ownId) {
199
+ if (message.clientMessageId && this.messages.some((item) => item.id !== ownId && item.clientMessageId === message.clientMessageId)) throw new Error(`Chat client message ID must be unique: ${message.clientMessageId}`);
200
+ }
201
+ emitAdded(message) {
202
+ this.emitter.emit("message-added", cloneChatMessage(message));
203
+ this.emitChanged();
204
+ }
205
+ emitChanged() {
87
206
  this.emitter.emit("messages-changed", this.getMessages());
88
207
  }
89
208
  };
209
+ function createGeneration() {
210
+ const cryptoObject = globalThis.crypto;
211
+ if (cryptoObject?.randomUUID) return cryptoObject.randomUUID();
212
+ return `stream-${Date.now()}-${Math.random().toString(36).slice(2)}`;
213
+ }
90
214
  //#endregion
91
215
  export { ChatManager };
92
216
  /*! End of @composy/chat-core | Powered by @composy/builder */
@@ -1 +1 @@
1
- {"version":3,"file":"chat-manager.js","names":[],"sources":["../../src/core/chat-manager.ts"],"sourcesContent":["/**\n * 聊天管理器 - 消息增删改查、状态管理\n */\n\nimport type { ChatMessage } from '../types';\nimport { EventEmitter } from './event-emitter';\n\n/**\n * ChatManager - Chat Manager class\n */\nexport class ChatManager {\n private messages: ChatMessage[] = [];\n private emitter: EventEmitter;\n\n constructor(emitter: EventEmitter, initialMessages?: ChatMessage[]) {\n this.emitter = emitter;\n if (initialMessages?.length) {\n this.messages = [...initialMessages];\n }\n }\n\n /** 获取所有消息 */\n getMessages(): ChatMessage[] {\n return [...this.messages];\n }\n\n /** 根据 ID 获取消息 */\n getMessage(id: string): ChatMessage | undefined {\n return this.messages.find((m) => m.id === id);\n }\n\n /** 添加消息(末尾追加) */\n addMessage(message: ChatMessage): void {\n this.messages.push(message);\n this.emitter.emit('message-added', message);\n this.emitter.emit('messages-changed', this.getMessages());\n }\n\n /** 在顶部批量插入消息(用于加载更多) */\n prependMessages(messages: ChatMessage[]): void {\n this.messages.unshift(...messages);\n this.emitter.emit('messages-prepended', messages);\n this.emitter.emit('messages-changed', this.getMessages());\n }\n\n /** 更新消息 */\n updateMessage(id: string, updates: Partial<ChatMessage>): void {\n const index = this.messages.findIndex((m) => m.id === id);\n if (index === -1) return;\n\n this.messages[index] = { ...this.messages[index], ...updates };\n this.emitter.emit('message-updated', this.messages[index]);\n this.emitter.emit('messages-changed', this.getMessages());\n }\n\n /** 删除消息 */\n removeMessage(id: string): void {\n const index = this.messages.findIndex((m) => m.id === id);\n if (index === -1) return;\n\n const removed = this.messages.splice(index, 1)[0];\n this.emitter.emit('message-removed', removed);\n this.emitter.emit('messages-changed', this.getMessages());\n }\n\n /** 替换全部消息 */\n setMessages(messages: ChatMessage[]): void {\n this.messages = [...messages];\n this.emitter.emit('messages-changed', this.getMessages());\n }\n\n /** 清空所有消息 */\n clearMessages(): void {\n this.messages = [];\n this.emitter.emit('messages-changed', []);\n }\n\n /** 追加流式消息内容 */\n appendMessageContent(id: string, chunk: string): void {\n const index = this.messages.findIndex((m) => m.id === id);\n if (index === -1) return;\n\n const msg = this.messages[index];\n this.messages[index] = {\n ...msg,\n content: (msg.content ?? '') + chunk,\n };\n this.emitter.emit('message-content-appended', id, this.messages[index].content);\n this.emitter.emit('messages-changed', this.getMessages());\n }\n\n /** 完成流式消息 */\n finishStreaming(id: string): void {\n const index = this.messages.findIndex((m) => m.id === id);\n if (index === -1) return;\n\n this.messages[index] = { ...this.messages[index], streaming: false };\n this.emitter.emit('message-updated', this.messages[index]);\n this.emitter.emit('messages-changed', this.getMessages());\n }\n}\n"],"mappings":";;;;;;;;;;AAUO,IAAM,cAAN,MAAkB;CAIvB,YAAY,SAAuB,iBAAiC;EAHpE,KAAQ,WAA0B,CAAC;EAIjC,KAAK,UAAU;EACf,IAAI,iBAAiB,QACnB,KAAK,WAAW,CAAC,GAAG,eAAe;CAEvC;;CAGA,cAA6B;EAC3B,OAAO,CAAC,GAAG,KAAK,QAAQ;CAC1B;;CAGA,WAAW,IAAqC;EAC9C,OAAO,KAAK,SAAS,MAAM,MAAM,EAAE,OAAO,EAAE;CAC9C;;CAGA,WAAW,SAA4B;EACrC,KAAK,SAAS,KAAK,OAAO;EAC1B,KAAK,QAAQ,KAAK,iBAAiB,OAAO;EAC1C,KAAK,QAAQ,KAAK,oBAAoB,KAAK,YAAY,CAAC;CAC1D;;CAGA,gBAAgB,UAA+B;EAC7C,KAAK,SAAS,QAAQ,GAAG,QAAQ;EACjC,KAAK,QAAQ,KAAK,sBAAsB,QAAQ;EAChD,KAAK,QAAQ,KAAK,oBAAoB,KAAK,YAAY,CAAC;CAC1D;;CAGA,cAAc,IAAY,SAAqC;EAC7D,MAAM,QAAQ,KAAK,SAAS,WAAW,MAAM,EAAE,OAAO,EAAE;EACxD,IAAI,UAAU,IAAI;EAElB,KAAK,SAAS,SAAS;GAAE,GAAG,KAAK,SAAS;GAAQ,GAAG;EAAQ;EAC7D,KAAK,QAAQ,KAAK,mBAAmB,KAAK,SAAS,MAAM;EACzD,KAAK,QAAQ,KAAK,oBAAoB,KAAK,YAAY,CAAC;CAC1D;;CAGA,cAAc,IAAkB;EAC9B,MAAM,QAAQ,KAAK,SAAS,WAAW,MAAM,EAAE,OAAO,EAAE;EACxD,IAAI,UAAU,IAAI;EAElB,MAAM,UAAU,KAAK,SAAS,OAAO,OAAO,CAAC,CAAA,CAAE;EAC/C,KAAK,QAAQ,KAAK,mBAAmB,OAAO;EAC5C,KAAK,QAAQ,KAAK,oBAAoB,KAAK,YAAY,CAAC;CAC1D;;CAGA,YAAY,UAA+B;EACzC,KAAK,WAAW,CAAC,GAAG,QAAQ;EAC5B,KAAK,QAAQ,KAAK,oBAAoB,KAAK,YAAY,CAAC;CAC1D;;CAGA,gBAAsB;EACpB,KAAK,WAAW,CAAC;EACjB,KAAK,QAAQ,KAAK,oBAAoB,CAAC,CAAC;CAC1C;;CAGA,qBAAqB,IAAY,OAAqB;EACpD,MAAM,QAAQ,KAAK,SAAS,WAAW,MAAM,EAAE,OAAO,EAAE;EACxD,IAAI,UAAU,IAAI;EAElB,MAAM,MAAM,KAAK,SAAS;EAC1B,KAAK,SAAS,SAAS;GACrB,GAAG;GACH,UAAU,IAAI,WAAW,MAAM;EACjC;EACA,KAAK,QAAQ,KAAK,4BAA4B,IAAI,KAAK,SAAS,MAAK,CAAE,OAAO;EAC9E,KAAK,QAAQ,KAAK,oBAAoB,KAAK,YAAY,CAAC;CAC1D;;CAGA,gBAAgB,IAAkB;EAChC,MAAM,QAAQ,KAAK,SAAS,WAAW,MAAM,EAAE,OAAO,EAAE;EACxD,IAAI,UAAU,IAAI;EAElB,KAAK,SAAS,SAAS;GAAE,GAAG,KAAK,SAAS;GAAQ,WAAW;EAAM;EACnE,KAAK,QAAQ,KAAK,mBAAmB,KAAK,SAAS,MAAM;EACzD,KAAK,QAAQ,KAAK,oBAAoB,KAAK,YAAY,CAAC;CAC1D;AACF"}
1
+ {"version":3,"file":"chat-manager.js","names":["message"],"sources":["../../src/core/chat-manager.ts"],"sourcesContent":["import type { ChatMessage, MessageStatus } from '../types'\nimport type { EventEmitter } from './event-emitter'\nimport {\n cloneChatMessage,\n normalizeChatMessage,\n normalizeChatMessages,\n} from './message-schema'\n\nexport type MessageMergeResult = 'added' | 'updated' | 'ignored'\n\nexport interface MessageMergeOptions {\n sequence?: number\n source?: 'local' | 'remote'\n}\n\nexport interface StreamAppendOptions {\n generation?: string\n sequence?: number\n}\n\ninterface StreamState {\n generation: string\n lastSequence: number\n}\n\n/** ChatManager - 聊天消息状态、幂等合并和流式 generation 管理。 */\nexport class ChatManager {\n private messages: ChatMessage[] = []\n private readonly emitter: EventEmitter\n private readonly streams = new Map<string, StreamState>()\n\n constructor(emitter: EventEmitter, initialMessages?: ChatMessage[]) {\n this.emitter = emitter\n if (initialMessages?.length) {\n this.messages = normalizeChatMessages(initialMessages)\n }\n }\n\n getMessages(): ChatMessage[] {\n return this.messages.map(cloneChatMessage)\n }\n\n getMessage(id: string): ChatMessage | undefined {\n const message = this.messages.find(item => item.id === id)\n return message ? cloneChatMessage(message) : undefined\n }\n\n getMessageIdByClientMessageId(clientMessageId: string): string | undefined {\n return this.messages.find(\n message => message.clientMessageId === clientMessageId\n )?.id\n }\n\n addMessage(message: ChatMessage): void {\n const storedMessage = normalizeChatMessage(message)\n this.assertUniqueIds([storedMessage])\n this.messages.push(storedMessage)\n this.emitAdded(storedMessage)\n }\n\n prependMessages(messages: ChatMessage[]): void {\n const storedMessages = normalizeChatMessages(messages)\n this.assertUniqueIds(storedMessages)\n this.messages.unshift(...storedMessages)\n this.emitter.emit(\n 'messages-prepended',\n storedMessages.map(cloneChatMessage)\n )\n this.emitChanged()\n }\n\n /** 合并本地 ack 或远端推送,按 id/clientMessageId 去重并拒绝旧 sequence。 */\n upsertMessage(\n message: ChatMessage,\n options: MessageMergeOptions = {}\n ): MessageMergeResult {\n const normalized = normalizeChatMessage({\n ...message,\n ...(options.sequence !== undefined && { sequence: options.sequence }),\n })\n const index = this.findMessageIndex(normalized)\n\n if (index < 0) {\n this.messages.push(normalized)\n this.emitAdded(normalized)\n return 'added'\n }\n\n const current = this.messages[index]\n if (\n Number.isFinite(normalized.sequence) &&\n Number.isFinite(current.sequence) &&\n (normalized.sequence as number) <= (current.sequence as number)\n ) {\n return 'ignored'\n }\n\n const merged = normalizeChatMessage({\n ...current,\n ...normalized,\n id: current.id,\n clientMessageId: normalized.clientMessageId ?? current.clientMessageId,\n sequence: normalized.sequence ?? current.sequence,\n })\n this.messages[index] = merged\n this.emitter.emit('message-updated', cloneChatMessage(merged))\n this.emitChanged()\n return 'updated'\n }\n\n mergeMessages(\n messages: ChatMessage[],\n options: MessageMergeOptions = {}\n ): { added: number; updated: number; ignored: number } {\n const result = { added: 0, updated: 0, ignored: 0 }\n for (const message of messages) {\n const outcome = this.upsertMessage(message, options)\n result[outcome] += 1\n }\n return result\n }\n\n updateMessage(id: string, updates: Partial<ChatMessage>): void {\n const index = this.messages.findIndex(message => message.id === id)\n if (index === -1) return\n if (updates.id !== undefined && updates.id !== id) {\n throw new Error('Chat message IDs are immutable.')\n }\n\n const next = normalizeChatMessage({\n ...this.messages[index],\n ...updates,\n id,\n })\n this.assertUniqueClientMessageId(next, id)\n this.messages[index] = next\n this.emitter.emit('message-updated', cloneChatMessage(next))\n this.emitChanged()\n }\n\n markMessageStatus(id: string, status: MessageStatus): void {\n this.updateMessage(id, { status })\n }\n\n removeMessage(id: string): void {\n const index = this.messages.findIndex(message => message.id === id)\n if (index === -1) return\n\n const [removed] = this.messages.splice(index, 1)\n this.streams.delete(id)\n this.emitter.emit('message-removed', cloneChatMessage(removed))\n this.emitChanged()\n }\n\n setMessages(messages: ChatMessage[]): void {\n this.messages = normalizeChatMessages(messages)\n this.streams.clear()\n this.emitChanged()\n }\n\n clearMessages(): void {\n this.messages = []\n this.streams.clear()\n this.emitChanged()\n }\n\n beginStreaming(id: string, generation = createGeneration()): string | null {\n const index = this.messages.findIndex(message => message.id === id)\n if (index === -1) return null\n this.streams.set(id, { generation, lastSequence: -1 })\n this.messages[index] = normalizeChatMessage({\n ...this.messages[index],\n streaming: true,\n })\n this.emitter.emit('message-updated', cloneChatMessage(this.messages[index]))\n this.emitChanged()\n return generation\n }\n\n appendMessageContent(\n id: string,\n chunk: string,\n options: StreamAppendOptions = {}\n ): boolean {\n const stream = this.streams.get(id)\n if (options.generation) {\n if (!stream || stream.generation !== options.generation) return false\n const sequence = options.sequence\n if (\n sequence !== undefined &&\n Number.isFinite(sequence) &&\n sequence <= stream.lastSequence\n ) {\n return false\n }\n if (sequence !== undefined && Number.isFinite(sequence)) {\n stream.lastSequence = sequence\n }\n }\n\n const index = this.messages.findIndex(message => message.id === id)\n if (index === -1) return false\n const message = this.messages[index]\n this.messages[index] = normalizeChatMessage({\n ...message,\n content: (message.content ?? '') + chunk,\n streaming: options.generation ? true : message.streaming,\n })\n this.emitter.emit(\n 'message-content-appended',\n id,\n this.messages[index].content,\n options.generation,\n options.sequence\n )\n this.emitChanged()\n return true\n }\n\n finishStreaming(id: string, generation?: string): boolean {\n const stream = this.streams.get(id)\n if (generation && (!stream || stream.generation !== generation)) {\n return false\n }\n const index = this.messages.findIndex(message => message.id === id)\n if (index === -1) return false\n this.messages[index] = normalizeChatMessage({\n ...this.messages[index],\n streaming: false,\n })\n this.streams.delete(id)\n this.emitter.emit('message-updated', cloneChatMessage(this.messages[index]))\n this.emitChanged()\n return true\n }\n\n cancelStreaming(id: string, generation?: string): boolean {\n const stream = this.streams.get(id)\n if (generation && (!stream || stream.generation !== generation)) {\n return false\n }\n const index = this.messages.findIndex(message => message.id === id)\n if (index === -1) return false\n this.streams.delete(id)\n this.messages[index] = normalizeChatMessage({\n ...this.messages[index],\n streaming: false,\n })\n this.emitter.emit(\n 'stream-cancelled',\n cloneChatMessage(this.messages[index])\n )\n this.emitter.emit('message-updated', cloneChatMessage(this.messages[index]))\n this.emitChanged()\n return true\n }\n\n getStreamGeneration(id: string): string | undefined {\n return this.streams.get(id)?.generation\n }\n\n private findMessageIndex(message: ChatMessage): number {\n const byId = this.messages.findIndex(item => item.id === message.id)\n if (byId >= 0) return byId\n if (!message.clientMessageId) return -1\n return this.messages.findIndex(\n item => item.clientMessageId === message.clientMessageId\n )\n }\n\n private assertUniqueIds(messages: ChatMessage[]): void {\n const ids = new Set(this.messages.map(message => message.id))\n const clientIds = new Set(\n this.messages.flatMap(message =>\n message.clientMessageId ? [message.clientMessageId] : []\n )\n )\n for (const message of messages) {\n if (!message.id || ids.has(message.id)) {\n throw new Error(\n `Chat message ID must be non-empty and unique: ${message.id}`\n )\n }\n if (message.clientMessageId && clientIds.has(message.clientMessageId)) {\n throw new Error(\n `Chat client message ID must be unique: ${message.clientMessageId}`\n )\n }\n ids.add(message.id)\n if (message.clientMessageId) clientIds.add(message.clientMessageId)\n }\n }\n\n private assertUniqueClientMessageId(message: ChatMessage, ownId: string) {\n if (\n message.clientMessageId &&\n this.messages.some(\n item =>\n item.id !== ownId && item.clientMessageId === message.clientMessageId\n )\n ) {\n throw new Error(\n `Chat client message ID must be unique: ${message.clientMessageId}`\n )\n }\n }\n\n private emitAdded(message: ChatMessage): void {\n this.emitter.emit('message-added', cloneChatMessage(message))\n this.emitChanged()\n }\n\n private emitChanged(): void {\n this.emitter.emit('messages-changed', this.getMessages())\n }\n}\n\nfunction createGeneration(): string {\n const cryptoObject = globalThis.crypto\n if (cryptoObject?.randomUUID) return cryptoObject.randomUUID()\n return `stream-${Date.now()}-${Math.random().toString(36).slice(2)}`\n}\n"],"mappings":"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"}
@@ -45,4 +45,3 @@ export declare class EventEmitter {
45
45
  removeAll(event?: string): void;
46
46
  }
47
47
  export {};
48
- //# sourceMappingURL=event-emitter.d.ts.map
@@ -1,8 +1,8 @@
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
  * ***********************************
@@ -19,8 +19,12 @@ var EventEmitter = class {
19
19
  * @returns 返回一个取消订阅的函数,调用即可移除该监听器
20
20
  */
21
21
  on(event, handler) {
22
- if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
23
- this.listeners.get(event).add(handler);
22
+ let handlers = this.listeners.get(event);
23
+ if (!handlers) {
24
+ handlers = /* @__PURE__ */ new Set();
25
+ this.listeners.set(event, handlers);
26
+ }
27
+ handlers.add(handler);
24
28
  return () => this.off(event, handler);
25
29
  }
26
30
  /**
@@ -29,7 +33,10 @@ var EventEmitter = class {
29
33
  * @param handler - 要移除的处理函数引用(必须与注册时为同一函数引用)
30
34
  */
31
35
  off(event, handler) {
32
- this.listeners.get(event)?.delete(handler);
36
+ const handlers = this.listeners.get(event);
37
+ if (!handlers) return;
38
+ handlers.delete(handler);
39
+ if (handlers.size === 0) this.listeners.delete(event);
33
40
  }
34
41
  /**
35
42
  * 触发(发射)指定事件,依次调用所有已注册的监听器
@@ -37,7 +44,7 @@ var EventEmitter = class {
37
44
  * @param args - 传递给监听器函数的参数列表(剩余参数语法)
38
45
  */
39
46
  emit(event, ...args) {
40
- this.listeners.get(event)?.forEach((handler) => {
47
+ [...this.listeners.get(event) ?? []].forEach((handler) => {
41
48
  try {
42
49
  handler(...args);
43
50
  } catch (err) {
@@ -65,7 +72,7 @@ var EventEmitter = class {
65
72
  * - 若不传参数,则清空所有事件的所有监听器
66
73
  */
67
74
  removeAll(event) {
68
- if (event) this.listeners.delete(event);
75
+ if (event !== void 0) this.listeners.delete(event);
69
76
  else this.listeners.clear();
70
77
  }
71
78
  };
@@ -1 +1 @@
1
- {"version":3,"file":"event-emitter.js","names":[],"sources":["../../src/core/event-emitter.ts"],"sourcesContent":["/**\n * 轻量事件总线\n * 提供发布/订阅模式的事件管理功能,支持事件的注册、注销、触发等操作\n */\n\n// 定义事件处理函数的类型签名:接收任意数量的未知类型参数,无返回值\ntype Handler = (...args: unknown[]) => void;\n\n/**\n * EventEmitter - 事件发射器类\n * 实现了经典的发布/订阅(Pub/Sub)模式,用于模块间的解耦通信\n * 支持:一次性监听、批量移除、自动清理等功能\n */\nexport class EventEmitter {\n // 使用 Map 存储事件名到处理函数集合的映射关系\n // 键为事件名称(字符串),值为该事件对应的处理函数 Set 集合\n // 使用 Set 而非数组,可自动去重同一处理函数的重复注册\n private listeners = new Map<string, Set<Handler>>();\n\n /**\n * 注册事件监听器\n * @param event - 要监听的事件名称\n * @param handler - 事件触发时执行的回调函数\n * @returns 返回一个取消订阅的函数,调用即可移除该监听器\n */\n on(event: string, handler: Handler): () => void {\n // 如果该事件还没有任何监听器,则创建一个新的 Set 集合\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n // 将处理函数添加到该事件的监听器集合中\n // 使用非空断言(!)因为上方已确认该事件集合存在\n this.listeners.get(event)!.add(handler);\n // 返回一个取消订阅函数,方便调用者链式调用后自行移除\n return () => this.off(event, handler);\n }\n\n /**\n * 移除指定事件的某个监听器\n * @param event - 事件名称\n * @param handler - 要移除的处理函数引用(必须与注册时为同一函数引用)\n */\n off(event: string, handler: Handler): void {\n // 可选链调用:如果该事件存在监听器集合,则从中删除指定处理函数\n // 若事件不存在则安全跳过,不会抛出异常\n this.listeners.get(event)?.delete(handler);\n }\n\n /**\n * 触发(发射)指定事件,依次调用所有已注册的监听器\n * @param event - 要触发的事件名称\n * @param args - 传递给监听器函数的参数列表(剩余参数语法)\n */\n emit(event: string, ...args: unknown[]): void {\n // 获取该事件的监听器集合,若存在则遍历调用每个处理函数\n this.listeners.get(event)?.forEach((handler) => {\n try {\n // 使用展开运算符将参数数组传递给处理函数\n handler(...args);\n } catch (err) {\n // 捕获单个处理函数的异常,避免影响其他监听器的执行\n // 同时输出错误日志便于调试,标注所属模块为 [Chat]\n console.error(`[Chat] Event handler error for \"${event}\":`, err);\n }\n });\n }\n\n /**\n * 注册一次性事件监听器(触发一次后自动移除)\n * @param event - 要监听的事件名称\n * @param handler - 事件触发时执行的回调函数\n * @returns 返回取消订阅的函数,可在触发前手动取消\n */\n once(event: string, handler: Handler): () => void {\n // 创建包装函数:先移除自身,再调用原始处理函数\n // 这样保证该监听器只执行一次\n const wrapper = (...args: unknown[]) => {\n // 先从事件中移除包装函数自身,防止重复触发\n this.off(event, wrapper);\n // 再执行用户注册的原始处理函数\n handler(...args);\n };\n // 将包装函数注册为普通监听器,复用 on() 的逻辑\n return this.on(event, wrapper);\n }\n\n /**\n * 批量移除事件监听器\n * @param event - 可选的事件名称\n * - 若传入事件名,则只移除该事件的所有监听器\n * - 若不传参数,则清空所有事件的所有监听器\n */\n removeAll(event?: string): void {\n if (event) {\n // 移除指定事件的全部监听器(直接删除整个事件条目)\n this.listeners.delete(event);\n } else {\n // 不传参数时,清空整个监听器映射表,移除所有事件\n this.listeners.clear();\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAaO,IAAM,eAAN,MAAmB;CAAnB,cAAA;EAIL,KAAQ,4BAAY,IAAI,IAA0B;CAAA;;;;;;;CAQlD,GAAG,OAAe,SAA8B;EAE9C,IAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAC3B,KAAK,UAAU,IAAI,uBAAO,IAAI,IAAI,CAAC;EAIrC,KAAK,UAAU,IAAI,KAAK,CAAA,CAAG,IAAI,OAAO;EAEtC,aAAa,KAAK,IAAI,OAAO,OAAO;CACtC;;;;;;CAOA,IAAI,OAAe,SAAwB;EAGzC,KAAK,UAAU,IAAI,KAAK,CAAA,EAAG,OAAO,OAAO;CAC3C;;;;;;CAOA,KAAK,OAAA,GAAkB,MAAuB;EAE5C,KAAK,UAAU,IAAI,KAAK,CAAA,EAAG,SAAS,YAAY;GAC9C,IAAI;IAEF,QAAQ,GAAG,IAAI;GACjB,SAAS,KAAK;IAGZ,QAAQ,MAAM,mCAAmC,MAAK,KAAM,GAAG;GACjE;EACF,CAAC;CACH;;;;;;;CAQA,KAAK,OAAe,SAA8B;EAGhD,MAAM,WAAU,GAAI,SAAoB;GAEtC,KAAK,IAAI,OAAO,OAAO;GAEvB,QAAQ,GAAG,IAAI;EACjB;EAEA,OAAO,KAAK,GAAG,OAAO,OAAO;CAC/B;;;;;;;CAQA,UAAU,OAAsB;EAC9B,IAAI,OAEF,KAAK,UAAU,OAAO,KAAK;OAG3B,KAAK,UAAU,MAAM;CAEzB;AACF"}
1
+ {"version":3,"file":"event-emitter.js","names":[],"sources":["../../src/core/event-emitter.ts"],"sourcesContent":["/**\r\n * 轻量事件总线\r\n * 提供发布/订阅模式的事件管理功能,支持事件的注册、注销、触发等操作\r\n */\r\n\r\n// 定义事件处理函数的类型签名:接收任意数量的未知类型参数,无返回值\r\ntype Handler = (...args: unknown[]) => void\r\n\r\n/**\r\n * EventEmitter - 事件发射器类\r\n * 实现了经典的发布/订阅(Pub/Sub)模式,用于模块间的解耦通信\r\n * 支持:一次性监听、批量移除、自动清理等功能\r\n */\r\nexport class EventEmitter {\r\n // 使用 Map 存储事件名到处理函数集合的映射关系\r\n // 键为事件名称(字符串),值为该事件对应的处理函数 Set 集合\r\n // 使用 Set 而非数组,可自动去重同一处理函数的重复注册\r\n private listeners = new Map<string, Set<Handler>>()\r\n\r\n /**\r\n * 注册事件监听器\r\n * @param event - 要监听的事件名称\r\n * @param handler - 事件触发时执行的回调函数\r\n * @returns 返回一个取消订阅的函数,调用即可移除该监听器\r\n */\r\n on(event: string, handler: Handler): () => void {\r\n let handlers = this.listeners.get(event)\r\n if (!handlers) {\r\n handlers = new Set()\r\n this.listeners.set(event, handlers)\r\n }\r\n handlers.add(handler)\r\n // 返回一个取消订阅函数,方便调用者链式调用后自行移除\r\n return () => this.off(event, handler)\r\n }\r\n\r\n /**\r\n * 移除指定事件的某个监听器\r\n * @param event - 事件名称\r\n * @param handler - 要移除的处理函数引用(必须与注册时为同一函数引用)\r\n */\r\n off(event: string, handler: Handler): void {\r\n // 可选链调用:如果该事件存在监听器集合,则从中删除指定处理函数\r\n // 若事件不存在则安全跳过,不会抛出异常\r\n const handlers = this.listeners.get(event)\r\n if (!handlers) return\r\n handlers.delete(handler)\r\n if (handlers.size === 0) this.listeners.delete(event)\r\n }\r\n\r\n /**\r\n * 触发(发射)指定事件,依次调用所有已注册的监听器\r\n * @param event - 要触发的事件名称\r\n * @param args - 传递给监听器函数的参数列表(剩余参数语法)\r\n */\r\n emit(event: string, ...args: unknown[]): void {\r\n // 获取该事件的监听器集合,若存在则遍历调用每个处理函数\r\n ;[...(this.listeners.get(event) ?? [])].forEach(handler => {\r\n try {\r\n // 使用展开运算符将参数数组传递给处理函数\r\n handler(...args)\r\n } catch (err) {\r\n // 捕获单个处理函数的异常,避免影响其他监听器的执行\r\n // 同时输出错误日志便于调试,标注所属模块为 [Chat]\r\n console.error(`[Chat] Event handler error for \"${event}\":`, err)\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * 注册一次性事件监听器(触发一次后自动移除)\r\n * @param event - 要监听的事件名称\r\n * @param handler - 事件触发时执行的回调函数\r\n * @returns 返回取消订阅的函数,可在触发前手动取消\r\n */\r\n once(event: string, handler: Handler): () => void {\r\n // 创建包装函数:先移除自身,再调用原始处理函数\r\n // 这样保证该监听器只执行一次\r\n const wrapper = (...args: unknown[]) => {\r\n // 先从事件中移除包装函数自身,防止重复触发\r\n this.off(event, wrapper)\r\n // 再执行用户注册的原始处理函数\r\n handler(...args)\r\n }\r\n // 将包装函数注册为普通监听器,复用 on() 的逻辑\r\n return this.on(event, wrapper)\r\n }\r\n\r\n /**\r\n * 批量移除事件监听器\r\n * @param event - 可选的事件名称\r\n * - 若传入事件名,则只移除该事件的所有监听器\r\n * - 若不传参数,则清空所有事件的所有监听器\r\n */\r\n removeAll(event?: string): void {\r\n if (event !== undefined) {\r\n // 移除指定事件的全部监听器(直接删除整个事件条目)\r\n this.listeners.delete(event)\r\n } else {\r\n // 不传参数时,清空整个监听器映射表,移除所有事件\r\n this.listeners.clear()\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;AAaO,IAAM,eAAN,MAAmB;CAAnB,cAAA;EAIL,KAAQ,4BAAY,IAAI,IAA0B;CAAA;;;;;;;CAQlD,GAAG,OAAe,SAA8B;EAC9C,IAAI,WAAW,KAAK,UAAU,IAAI,KAAK;EACvC,IAAI,CAAC,UAAU;GACb,2BAAW,IAAI,IAAI;GACnB,KAAK,UAAU,IAAI,OAAO,QAAQ;EACpC;EACA,SAAS,IAAI,OAAO;EAEpB,aAAa,KAAK,IAAI,OAAO,OAAO;CACtC;;;;;;CAOA,IAAI,OAAe,SAAwB;EAGzC,MAAM,WAAW,KAAK,UAAU,IAAI,KAAK;EACzC,IAAI,CAAC,UAAU;EACf,SAAS,OAAO,OAAO;EACvB,IAAI,SAAS,SAAS,GAAG,KAAK,UAAU,OAAO,KAAK;CACtD;;;;;;CAOA,KAAK,OAAA,GAAkB,MAAuB;EAE3C,CAAC,GAAI,KAAK,UAAU,IAAI,KAAK,KAAK,CAAC,CAAE,CAAA,CAAE,SAAQ,YAAW;GACzD,IAAI;IAEF,QAAQ,GAAG,IAAI;GACjB,SAAS,KAAK;IAGZ,QAAQ,MAAM,mCAAmC,MAAK,KAAM,GAAG;GACjE;EACF,CAAC;CACH;;;;;;;CAQA,KAAK,OAAe,SAA8B;EAGhD,MAAM,WAAU,GAAI,SAAoB;GAEtC,KAAK,IAAI,OAAO,OAAO;GAEvB,QAAQ,GAAG,IAAI;EACjB;EAEA,OAAO,KAAK,GAAG,OAAO,OAAO;CAC/B;;;;;;;CAQA,UAAU,OAAsB;EAC9B,IAAI,UAAU,KAAA,GAEZ,KAAK,UAAU,OAAO,KAAK;OAG3B,KAAK,UAAU,MAAM;CAEzB;AACF"}
@@ -3,7 +3,7 @@
3
3
  * @description mention-engine 模块
4
4
  */
5
5
  import type { MentionItem } from '../types';
6
- import { EventEmitter } from './event-emitter';
6
+ import type { EventEmitter } from './event-emitter';
7
7
  /**
8
8
  * MentionState interface
9
9
  */
@@ -38,4 +38,3 @@ export declare class MentionEngine {
38
38
  private filter;
39
39
  private escapeRegex;
40
40
  }
41
- //# sourceMappingURL=mention-engine.d.ts.map
@@ -1,8 +1,8 @@
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
  * ***********************************
@@ -20,7 +20,7 @@ var MentionEngine = class {
20
20
  };
21
21
  this.selectedMentions = [];
22
22
  this.emitter = emitter;
23
- if (members) this.members = [...members];
23
+ if (members) this.members = members.map((member) => ({ ...member }));
24
24
  }
25
25
  getState() {
26
26
  return {
@@ -29,10 +29,10 @@ var MentionEngine = class {
29
29
  };
30
30
  }
31
31
  getSelectedMentions() {
32
- return [...this.selectedMentions];
32
+ return this.selectedMentions.map((member) => ({ ...member }));
33
33
  }
34
34
  setMembers(members) {
35
- this.members = [...members];
35
+ this.members = members.map((member) => ({ ...member }));
36
36
  if (this.state.active) this.filter(this.state.query);
37
37
  }
38
38
  handleInput(text, cursorPos) {
@@ -47,7 +47,7 @@ var MentionEngine = class {
47
47
  return;
48
48
  }
49
49
  const query = beforeCursor.slice(atIndex + 1);
50
- if (/[^\S\r\n]/.test(query)) {
50
+ if (/\s/.test(query)) {
51
51
  this.deactivate();
52
52
  return;
53
53
  }
@@ -72,7 +72,7 @@ var MentionEngine = class {
72
72
  select(index) {
73
73
  if (index < 0 || index >= this.state.filtered.length) return null;
74
74
  const member = this.state.filtered[index];
75
- if (!this.selectedMentions.find((m) => m.id === member.id)) this.selectedMentions.push(member);
75
+ if (!this.selectedMentions.some((m) => m.id === member.id)) this.selectedMentions.push(member);
76
76
  this.emitter.emit("mention-select", member);
77
77
  this.deactivate();
78
78
  return member;
@@ -91,7 +91,7 @@ var MentionEngine = class {
91
91
  parseMentions(text) {
92
92
  const mentions = [];
93
93
  const sortedMembers = [...this.members].sort((a, b) => b.name.length - a.name.length);
94
- 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);
94
+ for (const member of sortedMembers) if (new RegExp(`(^|\\s)@${this.escapeRegex(member.name)}(?=\\s|$|[\uFF0C\u3002\uFF01\uFF1F,.!?])`, "u").test(text) && !mentions.some((m) => m.id === member.id)) mentions.push({ ...member });
95
95
  return mentions;
96
96
  }
97
97
  resetSelected() {
@@ -103,7 +103,7 @@ var MentionEngine = class {
103
103
  filter(query) {
104
104
  const normalizedQuery = query.trim().toLowerCase();
105
105
  this.state.query = query;
106
- this.state.filtered = this.members.filter((member) => member.name.toLowerCase().includes(normalizedQuery) || member.id.toLowerCase().includes(normalizedQuery));
106
+ this.state.filtered = this.members.filter((member) => member.name.toLowerCase().includes(normalizedQuery) || member.id.toLowerCase().includes(normalizedQuery)).map((member) => ({ ...member }));
107
107
  this.state.activeIndex = 0;
108
108
  this.emitter.emit("mention-filter", this.getState());
109
109
  }