@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.
- package/README.md +187 -123
- package/dist/index.js +2018 -1035
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1370 -1644
- package/dist/index.min.js.map +1 -0
- package/es/.dts-output-manifest.json +1 -0
- package/es/.dts.tsbuildinfo +1 -0
- package/es/constants/dom.d.ts +3 -19
- package/es/constants/dom.js +4 -4
- package/es/constants/dom.js.map +1 -1
- package/es/core/chat-manager.d.ts +33 -22
- package/es/core/chat-manager.js +174 -50
- package/es/core/chat-manager.js.map +1 -1
- package/es/core/event-emitter.d.ts +0 -1
- package/es/core/event-emitter.js +14 -7
- package/es/core/event-emitter.js.map +1 -1
- package/es/core/mention-engine.d.ts +1 -2
- package/es/core/mention-engine.js +9 -9
- package/es/core/mention-engine.js.map +1 -1
- package/es/core/message-schema.d.ts +19 -0
- package/es/core/message-schema.js +197 -0
- package/es/core/message-schema.js.map +1 -0
- package/es/core/outbox.d.ts +63 -0
- package/es/core/outbox.js +160 -0
- package/es/core/outbox.js.map +1 -0
- package/es/core/transport.d.ts +79 -0
- package/es/core/transport.js +102 -0
- package/es/core/transport.js.map +1 -0
- package/es/index.d.ts +12 -7
- package/es/index.js +167 -36
- package/es/index.js.map +1 -1
- package/es/renderer/dom-renderer.d.ts +16 -4
- package/es/renderer/dom-renderer.js +316 -61
- package/es/renderer/dom-renderer.js.map +1 -1
- package/es/renderer/style-manager.d.ts +14 -38
- package/es/renderer/style-manager.js +92 -63
- package/es/renderer/style-manager.js.map +1 -1
- package/es/theme/theme-manager.d.ts +4 -3
- package/es/theme/theme-manager.js +19 -9
- package/es/theme/theme-manager.js.map +1 -1
- package/es/theme/tokens.d.ts +0 -1
- package/es/theme/tokens.js +92 -92
- package/es/theme/tokens.js.map +1 -1
- package/es/types/index.d.ts +104 -24
- package/es/types/index.js +8 -3
- package/es/types/index.js.map +1 -0
- package/esm/constants/dom.d.ts +3 -19
- package/esm/constants/dom.js +4 -4
- package/esm/constants/dom.js.map +1 -1
- package/esm/core/chat-manager.d.ts +33 -22
- package/esm/core/chat-manager.js +174 -50
- package/esm/core/chat-manager.js.map +1 -1
- package/esm/core/event-emitter.d.ts +0 -1
- package/esm/core/event-emitter.js +14 -7
- package/esm/core/event-emitter.js.map +1 -1
- package/esm/core/mention-engine.d.ts +1 -2
- package/esm/core/mention-engine.js +9 -9
- package/esm/core/mention-engine.js.map +1 -1
- package/esm/core/message-schema.d.ts +19 -0
- package/esm/core/message-schema.js +197 -0
- package/esm/core/message-schema.js.map +1 -0
- package/esm/core/outbox.d.ts +63 -0
- package/esm/core/outbox.js +160 -0
- package/esm/core/outbox.js.map +1 -0
- package/esm/core/transport.d.ts +79 -0
- package/esm/core/transport.js +102 -0
- package/esm/core/transport.js.map +1 -0
- package/esm/index.d.ts +12 -7
- package/esm/index.js +169 -38
- package/esm/index.js.map +1 -1
- package/esm/renderer/dom-renderer.d.ts +16 -4
- package/esm/renderer/dom-renderer.js +316 -61
- package/esm/renderer/dom-renderer.js.map +1 -1
- package/esm/renderer/style-manager.d.ts +14 -38
- package/esm/renderer/style-manager.js +92 -63
- package/esm/renderer/style-manager.js.map +1 -1
- package/esm/theme/theme-manager.d.ts +4 -3
- package/esm/theme/theme-manager.js +19 -9
- package/esm/theme/theme-manager.js.map +1 -1
- package/esm/theme/tokens.d.ts +0 -1
- package/esm/theme/tokens.js +92 -92
- package/esm/theme/tokens.js.map +1 -1
- package/esm/types/index.d.ts +104 -24
- package/esm/types/index.js +8 -3
- package/esm/types/index.js.map +1 -0
- package/lib/constants/dom.cjs +4 -4
- package/lib/constants/dom.cjs.map +1 -1
- package/lib/constants/dom.d.cts +25 -0
- package/lib/constants/dom.d.ts +3 -19
- package/lib/core/chat-manager.cjs +174 -50
- package/lib/core/chat-manager.cjs.map +1 -1
- package/lib/core/chat-manager.d.cts +45 -0
- package/lib/core/chat-manager.d.ts +33 -22
- package/lib/core/event-emitter.cjs +14 -7
- package/lib/core/event-emitter.cjs.map +1 -1
- package/lib/core/event-emitter.d.cts +47 -0
- package/lib/core/event-emitter.d.ts +0 -1
- package/lib/core/mention-engine.cjs +9 -9
- package/lib/core/mention-engine.cjs.map +1 -1
- package/lib/core/mention-engine.d.cts +40 -0
- package/lib/core/mention-engine.d.ts +1 -2
- package/lib/core/message-schema.cjs +202 -0
- package/lib/core/message-schema.cjs.map +1 -0
- package/lib/core/message-schema.d.cts +19 -0
- package/lib/core/message-schema.d.ts +19 -0
- package/lib/core/outbox.cjs +162 -0
- package/lib/core/outbox.cjs.map +1 -0
- package/lib/core/outbox.d.cts +63 -0
- package/lib/core/outbox.d.ts +63 -0
- package/lib/core/transport.cjs +103 -0
- package/lib/core/transport.cjs.map +1 -0
- package/lib/core/transport.d.cts +79 -0
- package/lib/core/transport.d.ts +79 -0
- package/lib/index.cjs +175 -35
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +33 -0
- package/lib/index.d.ts +12 -7
- package/lib/renderer/dom-renderer.cjs +316 -61
- package/lib/renderer/dom-renderer.cjs.map +1 -1
- package/lib/renderer/dom-renderer.d.cts +91 -0
- package/lib/renderer/dom-renderer.d.ts +16 -4
- package/lib/renderer/style-manager.cjs +92 -62
- package/lib/renderer/style-manager.cjs.map +1 -1
- package/lib/renderer/style-manager.d.cts +34 -0
- package/lib/renderer/style-manager.d.ts +14 -38
- package/lib/theme/theme-manager.cjs +19 -9
- package/lib/theme/theme-manager.cjs.map +1 -1
- package/lib/theme/theme-manager.d.cts +34 -0
- package/lib/theme/theme-manager.d.ts +4 -3
- package/lib/theme/tokens.cjs +92 -92
- package/lib/theme/tokens.cjs.map +1 -1
- package/lib/theme/tokens.d.cts +56 -0
- package/lib/theme/tokens.d.ts +0 -1
- package/lib/types/index.cjs +8 -3
- package/lib/types/index.cjs.map +1 -0
- package/lib/types/index.d.cts +284 -0
- package/lib/types/index.d.ts +104 -24
- package/package.json +16 -13
- package/es/constants/dom.d.ts.map +0 -1
- package/es/core/chat-manager.d.ts.map +0 -1
- package/es/core/event-emitter.d.ts.map +0 -1
- package/es/core/mention-engine.d.ts.map +0 -1
- package/es/index.d.ts.map +0 -1
- package/es/renderer/dom-renderer.d.ts.map +0 -1
- package/es/renderer/style-manager.d.ts.map +0 -1
- package/es/theme/theme-manager.d.ts.map +0 -1
- package/es/theme/tokens.d.ts.map +0 -1
- package/es/types/index.d.ts.map +0 -1
- package/esm/constants/dom.d.ts.map +0 -1
- package/esm/core/chat-manager.d.ts.map +0 -1
- package/esm/core/event-emitter.d.ts.map +0 -1
- package/esm/core/mention-engine.d.ts.map +0 -1
- package/esm/index.d.ts.map +0 -1
- package/esm/renderer/dom-renderer.d.ts.map +0 -1
- package/esm/renderer/style-manager.d.ts.map +0 -1
- package/esm/theme/theme-manager.d.ts.map +0 -1
- package/esm/theme/tokens.d.ts.map +0 -1
- package/esm/types/index.d.ts.map +0 -1
- package/lib/constants/dom.d.ts.map +0 -1
- package/lib/core/chat-manager.d.ts.map +0 -1
- package/lib/core/event-emitter.d.ts.map +0 -1
- package/lib/core/mention-engine.d.ts.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/renderer/dom-renderer.d.ts.map +0 -1
- package/lib/renderer/style-manager.d.ts.map +0 -1
- package/lib/theme/theme-manager.d.ts.map +0 -1
- package/lib/theme/tokens.d.ts.map +0 -1
- package/lib/types/index.d.ts.map +0 -1
package/es/types/index.d.ts
CHANGED
|
@@ -49,6 +49,44 @@ export interface ImageAttachment {
|
|
|
49
49
|
/** 高度 */
|
|
50
50
|
height?: number;
|
|
51
51
|
}
|
|
52
|
+
/** 结构化消息内容块。未知块应在上游归一化时降级,不得直接执行 HTML。 */
|
|
53
|
+
export type ChatContentBlock = {
|
|
54
|
+
type: 'text';
|
|
55
|
+
text: string;
|
|
56
|
+
} | {
|
|
57
|
+
type: 'mention';
|
|
58
|
+
id: string;
|
|
59
|
+
label: string;
|
|
60
|
+
} | {
|
|
61
|
+
type: 'link';
|
|
62
|
+
url: string;
|
|
63
|
+
label?: string;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'code';
|
|
66
|
+
code: string;
|
|
67
|
+
language?: string;
|
|
68
|
+
} | {
|
|
69
|
+
type: 'image';
|
|
70
|
+
src: string;
|
|
71
|
+
alt?: string;
|
|
72
|
+
width?: number;
|
|
73
|
+
height?: number;
|
|
74
|
+
} | {
|
|
75
|
+
type: 'file';
|
|
76
|
+
url: string;
|
|
77
|
+
name: string;
|
|
78
|
+
size?: number;
|
|
79
|
+
mimeType?: string;
|
|
80
|
+
} | {
|
|
81
|
+
type: 'break';
|
|
82
|
+
} | {
|
|
83
|
+
type: 'custom';
|
|
84
|
+
kind: string;
|
|
85
|
+
data: Record<string, unknown>;
|
|
86
|
+
fallback?: string;
|
|
87
|
+
};
|
|
88
|
+
/** 当前消息 schema 版本。 */
|
|
89
|
+
export declare const CHAT_MESSAGE_SCHEMA_VERSION = 1;
|
|
52
90
|
/** 聊天消息 */
|
|
53
91
|
export interface ChatMessage {
|
|
54
92
|
/** 消息唯一 ID */
|
|
@@ -67,7 +105,7 @@ export interface ChatMessage {
|
|
|
67
105
|
file?: FileAttachment;
|
|
68
106
|
/** 自定义数据(custom 类型) */
|
|
69
107
|
customData?: Record<string, unknown>;
|
|
70
|
-
/**
|
|
108
|
+
/** 自定义 HTML(custom 类型);渲染时会过滤脚本、事件属性、样式和不安全 URL,仅保留受支持的基础标签 */
|
|
71
109
|
customHtml?: string;
|
|
72
110
|
/** 提及列表 */
|
|
73
111
|
mentions?: MentionItem[];
|
|
@@ -79,6 +117,18 @@ export interface ChatMessage {
|
|
|
79
117
|
revoked?: boolean;
|
|
80
118
|
/** 是否正在流式输出(AI 风格) */
|
|
81
119
|
streaming?: boolean;
|
|
120
|
+
/** 规范化内容块;未提供时使用 content/image/file 等兼容字段。 */
|
|
121
|
+
contentBlocks?: ChatContentBlock[];
|
|
122
|
+
/** 消息 schema 版本,便于跨 transport 与持久化迁移。 */
|
|
123
|
+
schemaVersion?: number;
|
|
124
|
+
/** 会话标识,用于多会话 transport 复用。 */
|
|
125
|
+
conversationId?: string;
|
|
126
|
+
/** 客户端幂等标识,服务端 ack 与离线 outbox 使用。 */
|
|
127
|
+
clientMessageId?: string;
|
|
128
|
+
/** 服务端单调序号,用于乱序与重复消息过滤。 */
|
|
129
|
+
sequence?: number;
|
|
130
|
+
/** 服务端同步游标。 */
|
|
131
|
+
cursor?: string;
|
|
82
132
|
}
|
|
83
133
|
/** 主题模式 */
|
|
84
134
|
export type ThemeMode = 'light' | 'dark' | 'auto';
|
|
@@ -135,6 +185,16 @@ export interface ChatConfig {
|
|
|
135
185
|
enableToolbar?: boolean;
|
|
136
186
|
/** 工具栏配置 */
|
|
137
187
|
toolbarConfig?: ToolbarConfig;
|
|
188
|
+
/** 可选消息 transport;不配置时 Core 仍保持纯本地模式。 */
|
|
189
|
+
transport?: import('../core/transport').ChatTransport;
|
|
190
|
+
/** transport 使用的会话标识。 */
|
|
191
|
+
conversationId?: string;
|
|
192
|
+
/** 创建实例后自动连接 transport。 */
|
|
193
|
+
autoConnect?: boolean;
|
|
194
|
+
/** transport 初始同步游标。 */
|
|
195
|
+
transportCursor?: string;
|
|
196
|
+
/** 可选离线 outbox,用于保存发送中的消息并在连接恢复后重试。 */
|
|
197
|
+
outbox?: import('../core/outbox').ChatOutbox;
|
|
138
198
|
/** 发送消息回调 */
|
|
139
199
|
onSend?: (content: string, mentions: MentionItem[]) => void;
|
|
140
200
|
/** 加载更多回调 */
|
|
@@ -162,43 +222,63 @@ export interface ChatConfig {
|
|
|
162
222
|
/** 聊天实例 */
|
|
163
223
|
export interface ChatInstance {
|
|
164
224
|
/** 添加消息 */
|
|
165
|
-
addMessage(message: ChatMessage)
|
|
225
|
+
addMessage: (message: ChatMessage) => void;
|
|
166
226
|
/** 批量添加消息(在列表顶部插入,用于加载更多) */
|
|
167
|
-
prependMessages(messages: ChatMessage[])
|
|
227
|
+
prependMessages: (messages: ChatMessage[]) => void;
|
|
168
228
|
/** 更新消息 */
|
|
169
|
-
updateMessage(id: string, updates: Partial<ChatMessage>)
|
|
229
|
+
updateMessage: (id: string, updates: Partial<ChatMessage>) => void;
|
|
170
230
|
/** 删除消息 */
|
|
171
|
-
removeMessage(id: string)
|
|
231
|
+
removeMessage: (id: string) => void;
|
|
172
232
|
/** 设置完整消息列表 */
|
|
173
|
-
setMessages(messages: ChatMessage[])
|
|
233
|
+
setMessages: (messages: ChatMessage[]) => void;
|
|
174
234
|
/** 清空所有消息 */
|
|
175
|
-
clearMessages()
|
|
235
|
+
clearMessages: () => void;
|
|
176
236
|
/** 滚动到底部 */
|
|
177
|
-
scrollToBottom(smooth?: boolean)
|
|
237
|
+
scrollToBottom: (smooth?: boolean) => void;
|
|
178
238
|
/** 设置主题 */
|
|
179
|
-
setTheme(theme: ThemeMode)
|
|
239
|
+
setTheme: (theme: ThemeMode) => void;
|
|
180
240
|
/** 更新 @提及候选人 */
|
|
181
|
-
setMentionMembers(members: MentionItem[])
|
|
241
|
+
setMentionMembers: (members: MentionItem[]) => void;
|
|
182
242
|
/** 设置加载更多状态 */
|
|
183
|
-
setLoadingMore(loading: boolean)
|
|
243
|
+
setLoadingMore: (loading: boolean) => void;
|
|
184
244
|
/** 聚焦输入框 */
|
|
185
|
-
focusInput()
|
|
245
|
+
focusInput: () => void;
|
|
186
246
|
/** 设置输入框内容 */
|
|
187
|
-
setInputContent(content: string)
|
|
247
|
+
setInputContent: (content: string) => void;
|
|
188
248
|
/** 获取输入框内容 */
|
|
189
|
-
getInputContent()
|
|
190
|
-
clearInput()
|
|
191
|
-
isAtBottom(threshold?: number)
|
|
249
|
+
getInputContent: () => string;
|
|
250
|
+
clearInput: () => void;
|
|
251
|
+
isAtBottom: (threshold?: number) => boolean;
|
|
192
252
|
/** 监听事件 */
|
|
193
|
-
on(event: string, handler: (...args: unknown[]) => void)
|
|
253
|
+
on: (event: string, handler: (...args: unknown[]) => void) => () => void;
|
|
194
254
|
/** 获取所有消息 */
|
|
195
|
-
getMessages()
|
|
196
|
-
/** 追加流式消息内容(AI
|
|
197
|
-
appendMessageContent(id: string, chunk: string
|
|
255
|
+
getMessages: () => ChatMessage[];
|
|
256
|
+
/** 追加流式消息内容(AI 模式);可用 generation/sequence 防止旧 chunk 覆盖新流。 */
|
|
257
|
+
appendMessageContent: (id: string, chunk: string, options?: {
|
|
258
|
+
generation?: string;
|
|
259
|
+
sequence?: number;
|
|
260
|
+
}) => boolean;
|
|
198
261
|
/** 完成流式消息 */
|
|
199
|
-
finishStreaming(id: string)
|
|
200
|
-
|
|
262
|
+
finishStreaming: (id: string, generation?: string) => boolean;
|
|
263
|
+
/** 开始一个新的流式 generation,旧 generation 的 chunk 会被丢弃。 */
|
|
264
|
+
beginStreaming: (id: string, generation?: string) => string | null;
|
|
265
|
+
/** 取消当前流式 generation。 */
|
|
266
|
+
cancelStreaming: (id: string, generation?: string) => boolean;
|
|
267
|
+
/** 合并远端消息,按消息 ID/clientMessageId 去重并按 sequence 忽略旧版本。 */
|
|
268
|
+
mergeMessage: (message: ChatMessage, sequence?: number) => 'added' | 'updated' | 'ignored';
|
|
269
|
+
/** 连接 transport。 */
|
|
270
|
+
connectTransport: (signal?: AbortSignal) => Promise<void>;
|
|
271
|
+
/** 断开 transport。 */
|
|
272
|
+
disconnectTransport: () => Promise<void>;
|
|
273
|
+
/** 获取当前 transport 状态。 */
|
|
274
|
+
getTransportStatus: () => import('../core/transport').ChatTransportStatus;
|
|
275
|
+
/** 通过 transport 发送消息,失败时保留 failed 状态并交给 outbox 重试。 */
|
|
276
|
+
sendMessage: (message: ChatMessage, options?: {
|
|
277
|
+
signal?: AbortSignal;
|
|
278
|
+
}) => Promise<import('../core/transport').ChatTransportAck | void>;
|
|
279
|
+
/** 刷新离线 outbox。 */
|
|
280
|
+
flushOutbox: (options?: import('../core/outbox').ChatOutboxFlushOptions) => Promise<import('../core/outbox').ChatOutboxFlushResult>;
|
|
281
|
+
getMessage: (id: string) => ChatMessage | undefined;
|
|
201
282
|
/** 销毁实例 */
|
|
202
|
-
destroy()
|
|
283
|
+
destroy: () => void;
|
|
203
284
|
}
|
|
204
|
-
//# sourceMappingURL=index.d.ts.map
|
package/es/types/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* ***********************************
|
|
3
|
-
* @composy/chat-core v0.0.
|
|
3
|
+
* @composy/chat-core v0.0.2 *
|
|
4
4
|
* Built with rollup *
|
|
5
|
-
* Build time: 2024-
|
|
5
|
+
* Build time: 2024-10-12 21:52:18 *
|
|
6
6
|
* Build mode: production *
|
|
7
7
|
* Minified: No *
|
|
8
8
|
* ***********************************
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
//#region src/types/index.ts
|
|
11
|
+
const CHAT_MESSAGE_SCHEMA_VERSION = 1;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { CHAT_MESSAGE_SCHEMA_VERSION };
|
|
14
|
+
/*! End of @composy/chat-core | Powered by @composy/builder */
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/types/index.ts"],"sourcesContent":["/**\r\n * @composy/chat-core types\r\n */\r\n\r\n// ==================== 消息相关 ====================\r\n\r\n/** 消息类型 */\r\nexport type MessageType = 'text' | 'image' | 'file' | 'system' | 'custom'\r\n\r\n/** 工具栏按钮类型 */\r\nexport type ToolbarAction = 'image' | 'file' | 'voice'\r\n\r\n/** 消息发送状态 */\r\nexport type MessageStatus = 'sending' | 'sent' | 'delivered' | 'read' | 'failed'\r\n\r\n/** 消息方向 */\r\nexport type MessageDirection = 'incoming' | 'outgoing'\r\n\r\n/** 用户信息 */\r\nexport interface ChatUser {\r\n /** 用户 ID */\r\n id: string\r\n /** 昵称 */\r\n nickname: string\r\n /** 头像 URL */\r\n avatar?: string\r\n}\r\n\r\n/** @提及项 */\r\nexport interface MentionItem {\r\n /** 用户 ID */\r\n id: string\r\n /** 显示名称 */\r\n name: string\r\n /** 头像 */\r\n avatar?: string\r\n}\r\n\r\n/** 文件附件 */\r\nexport interface FileAttachment {\r\n /** 文件名 */\r\n name: string\r\n /** 文件大小(字节) */\r\n size: number\r\n /** 文件 URL */\r\n url: string\r\n /** MIME 类型 */\r\n mimeType?: string\r\n}\r\n\r\n/** 图片附件 */\r\nexport interface ImageAttachment {\r\n /** 图片 URL */\r\n url: string\r\n /** 缩略图 URL */\r\n thumbUrl?: string\r\n /** 宽度 */\r\n width?: number\r\n /** 高度 */\r\n height?: number\r\n}\r\n\r\n/** 结构化消息内容块。未知块应在上游归一化时降级,不得直接执行 HTML。 */\r\nexport type ChatContentBlock =\r\n | { type: 'text'; text: string }\r\n | { type: 'mention'; id: string; label: string }\r\n | { type: 'link'; url: string; label?: string }\r\n | { type: 'code'; code: string; language?: string }\r\n | {\r\n type: 'image'\r\n src: string\r\n alt?: string\r\n width?: number\r\n height?: number\r\n }\r\n | {\r\n type: 'file'\r\n url: string\r\n name: string\r\n size?: number\r\n mimeType?: string\r\n }\r\n | { type: 'break' }\r\n | {\r\n type: 'custom'\r\n kind: string\r\n data: Record<string, unknown>\r\n fallback?: string\r\n }\r\n\r\n/** 当前消息 schema 版本。 */\r\nexport const CHAT_MESSAGE_SCHEMA_VERSION = 1\r\n\r\n/** 聊天消息 */\r\nexport interface ChatMessage {\r\n /** 消息唯一 ID */\r\n id: string\r\n /** 消息类型 */\r\n type: MessageType\r\n /** 消息方向 */\r\n direction: MessageDirection\r\n /** 发送者信息 */\r\n sender: ChatUser\r\n /** 文本内容(text 类型) */\r\n content?: string\r\n /** 图片附件(image 类型) */\r\n image?: ImageAttachment\r\n /** 文件附件(file 类型) */\r\n file?: FileAttachment\r\n /** 自定义数据(custom 类型) */\r\n customData?: Record<string, unknown>\r\n /** 自定义 HTML(custom 类型);渲染时会过滤脚本、事件属性、样式和不安全 URL,仅保留受支持的基础标签 */\r\n customHtml?: string\r\n /** 提及列表 */\r\n mentions?: MentionItem[]\r\n /** 发送状态 */\r\n status?: MessageStatus\r\n /** 消息时间戳(毫秒) */\r\n timestamp: number\r\n /** 是否已撤回 */\r\n revoked?: boolean\r\n /** 是否正在流式输出(AI 风格) */\r\n streaming?: boolean\r\n /** 规范化内容块;未提供时使用 content/image/file 等兼容字段。 */\r\n contentBlocks?: ChatContentBlock[]\r\n /** 消息 schema 版本,便于跨 transport 与持久化迁移。 */\r\n schemaVersion?: number\r\n /** 会话标识,用于多会话 transport 复用。 */\r\n conversationId?: string\r\n /** 客户端幂等标识,服务端 ack 与离线 outbox 使用。 */\r\n clientMessageId?: string\r\n /** 服务端单调序号,用于乱序与重复消息过滤。 */\r\n sequence?: number\r\n /** 服务端同步游标。 */\r\n cursor?: string\r\n}\r\n\r\n// ==================== 主题相关 ====================\r\n\r\n/** 主题模式 */\r\nexport type ThemeMode = 'light' | 'dark' | 'auto'\r\n\r\n// ==================== 工具栏配置 ====================\r\n\r\n/** 工具栏配置 */\r\nexport interface ToolbarConfig {\r\n /** 启用图片上传按钮 */\r\n image?: boolean\r\n /** 启用文件上传按钮 */\r\n file?: boolean\r\n /** 启用语音输入按钮 */\r\n voice?: boolean\r\n}\r\n\r\n// ==================== 配置相关 ====================\r\n\r\n/** 聊天组件配置 */\r\nexport interface ChatConfig {\r\n /** 挂载容器 */\r\n container: HTMLElement | string\r\n /** 当前用户 ID(用于判断消息方向) */\r\n currentUserId: string\r\n /** 初始消息列表 */\r\n messages?: ChatMessage[]\r\n /** 主题模式 */\r\n theme?: ThemeMode\r\n /** 是否显示头像 */\r\n showAvatar?: boolean\r\n /** 是否显示昵称 */\r\n showNickname?: boolean\r\n /** 是否显示时间 */\r\n showTimestamp?: boolean\r\n /** 时间分组间隔(毫秒),默认 300000 (5分钟) */\r\n timeGroupInterval?: number\r\n /** 是否显示消息状态 */\r\n showStatus?: boolean\r\n /** 是否显示输入框 */\r\n showInput?: boolean\r\n /** 输入框占位文本 */\r\n inputPlaceholder?: string\r\n /** 是否启用 @提及 */\r\n enableMention?: boolean\r\n /** @提及候选人列表 */\r\n mentionMembers?: MentionItem[]\r\n /** 是否支持加载更多 */\r\n enableLoadMore?: boolean\r\n /** 加载更多文本 */\r\n loadMoreText?: string\r\n /** CSS class 前缀 */\r\n classPrefix?: string\r\n enterToSend?: boolean\r\n clearInputOnSend?: boolean\r\n maxInputLength?: number\r\n voiceLang?: string\r\n /** 日期格式化函数 */\r\n formatTime?: (timestamp: number) => string\r\n /** 是否启用工具栏 */\r\n enableToolbar?: boolean\r\n /** 工具栏配置 */\r\n toolbarConfig?: ToolbarConfig\r\n /** 可选消息 transport;不配置时 Core 仍保持纯本地模式。 */\r\n transport?: import('../core/transport').ChatTransport\r\n /** transport 使用的会话标识。 */\r\n conversationId?: string\r\n /** 创建实例后自动连接 transport。 */\r\n autoConnect?: boolean\r\n /** transport 初始同步游标。 */\r\n transportCursor?: string\r\n /** 可选离线 outbox,用于保存发送中的消息并在连接恢复后重试。 */\r\n outbox?: import('../core/outbox').ChatOutbox\r\n\r\n // 回调\r\n /** 发送消息回调 */\r\n onSend?: (content: string, mentions: MentionItem[]) => void\r\n /** 加载更多回调 */\r\n onLoadMore?: () => void | Promise<void>\r\n /** 消息点击回调 */\r\n onMessageClick?: (message: ChatMessage) => void\r\n /** @提及选择回调 */\r\n onMentionSelect?: (member: MentionItem) => void\r\n /** 图片上传回调(接收 File 列表) */\r\n onImageUpload?: (files: File[]) => void\r\n /** 文件上传回调(接收 File 列表) */\r\n onFileUpload?: (files: File[]) => void\r\n /** 粘贴回调(接收文件列表) */\r\n onPaste?: (files: File[]) => void\r\n /** 拖放回调(接收文件列表) */\r\n onDrop?: (files: File[]) => void\r\n /** 语音开始回调 */\r\n onVoiceStart?: () => void\r\n /** 语音结束回调(接收识别文本) */\r\n onVoiceEnd?: (transcript: string) => void\r\n onVoiceUnsupported?: () => void\r\n onVoiceError?: (error: unknown) => void\r\n onInputChange?: (content: string) => void\r\n}\r\n\r\n/** 聊天实例 */\r\nexport interface ChatInstance {\r\n /** 添加消息 */\r\n addMessage: (message: ChatMessage) => void\r\n /** 批量添加消息(在列表顶部插入,用于加载更多) */\r\n prependMessages: (messages: ChatMessage[]) => void\r\n /** 更新消息 */\r\n updateMessage: (id: string, updates: Partial<ChatMessage>) => void\r\n /** 删除消息 */\r\n removeMessage: (id: string) => void\r\n /** 设置完整消息列表 */\r\n setMessages: (messages: ChatMessage[]) => void\r\n /** 清空所有消息 */\r\n clearMessages: () => void\r\n /** 滚动到底部 */\r\n scrollToBottom: (smooth?: boolean) => void\r\n /** 设置主题 */\r\n setTheme: (theme: ThemeMode) => void\r\n /** 更新 @提及候选人 */\r\n setMentionMembers: (members: MentionItem[]) => void\r\n /** 设置加载更多状态 */\r\n setLoadingMore: (loading: boolean) => void\r\n /** 聚焦输入框 */\r\n focusInput: () => void\r\n /** 设置输入框内容 */\r\n setInputContent: (content: string) => void\r\n /** 获取输入框内容 */\r\n getInputContent: () => string\r\n clearInput: () => void\r\n isAtBottom: (threshold?: number) => boolean\r\n /** 监听事件 */\r\n on: (event: string, handler: (...args: unknown[]) => void) => () => void\r\n /** 获取所有消息 */\r\n getMessages: () => ChatMessage[]\r\n /** 追加流式消息内容(AI 模式);可用 generation/sequence 防止旧 chunk 覆盖新流。 */\r\n appendMessageContent: (\r\n id: string,\r\n chunk: string,\r\n options?: { generation?: string; sequence?: number }\r\n ) => boolean\r\n /** 完成流式消息 */\r\n finishStreaming: (id: string, generation?: string) => boolean\r\n /** 开始一个新的流式 generation,旧 generation 的 chunk 会被丢弃。 */\r\n beginStreaming: (id: string, generation?: string) => string | null\r\n /** 取消当前流式 generation。 */\r\n cancelStreaming: (id: string, generation?: string) => boolean\r\n /** 合并远端消息,按消息 ID/clientMessageId 去重并按 sequence 忽略旧版本。 */\r\n mergeMessage: (\r\n message: ChatMessage,\r\n sequence?: number\r\n ) => 'added' | 'updated' | 'ignored'\r\n /** 连接 transport。 */\r\n connectTransport: (signal?: AbortSignal) => Promise<void>\r\n /** 断开 transport。 */\r\n disconnectTransport: () => Promise<void>\r\n /** 获取当前 transport 状态。 */\r\n getTransportStatus: () => import('../core/transport').ChatTransportStatus\r\n /** 通过 transport 发送消息,失败时保留 failed 状态并交给 outbox 重试。 */\r\n sendMessage: (\r\n message: ChatMessage,\r\n options?: { signal?: AbortSignal }\r\n ) => Promise<import('../core/transport').ChatTransportAck | void>\r\n /** 刷新离线 outbox。 */\r\n flushOutbox: (\r\n options?: import('../core/outbox').ChatOutboxFlushOptions\r\n ) => Promise<import('../core/outbox').ChatOutboxFlushResult>\r\n getMessage: (id: string) => ChatMessage | undefined\r\n /** 销毁实例 */\r\n destroy: () => void\r\n}\r\n"],"mappings":";;;;;;;;;;AA2FO,MAAM,8BAA8B"}
|
package/esm/constants/dom.d.ts
CHANGED
|
@@ -5,37 +5,21 @@
|
|
|
5
5
|
export declare const DEFAULT_CLASS_PREFIX = "ldesign-chat";
|
|
6
6
|
export declare const COMPONENT_CSS_VAR_PREFIX = "--ldesign-chat";
|
|
7
7
|
export declare const STYLE_ELEMENT_ID = "ldesign-chat-styles";
|
|
8
|
-
/**
|
|
9
|
-
* normalizeClassPrefix 函数
|
|
10
|
-
* @param prefix? - 参数
|
|
11
|
-
* @returns 返回值
|
|
12
|
-
*/
|
|
13
8
|
/**
|
|
14
9
|
* normalizeClassPrefix - normalize Class Prefix function
|
|
15
|
-
* @param prefix
|
|
10
|
+
* @param prefix - Optional class prefix
|
|
16
11
|
* @returns 返回值说明
|
|
17
12
|
*/
|
|
18
13
|
export declare function normalizeClassPrefix(prefix?: string): string;
|
|
19
|
-
/**
|
|
20
|
-
* getStyleElementId 函数
|
|
21
|
-
* @param prefix = DEFAULT_CLASS_PREFIX - 参数
|
|
22
|
-
* @returns 返回值
|
|
23
|
-
*/
|
|
24
14
|
/**
|
|
25
15
|
* getStyleElementId - get Style Element Id function
|
|
26
|
-
* @param prefix
|
|
16
|
+
* @param prefix - Class prefix
|
|
27
17
|
* @returns 返回值说明
|
|
28
18
|
*/
|
|
29
19
|
export declare function getStyleElementId(prefix?: string): string;
|
|
30
|
-
/**
|
|
31
|
-
* cx 函数
|
|
32
|
-
* @param ...classes - 参数
|
|
33
|
-
* @returns 返回值
|
|
34
|
-
*/
|
|
35
20
|
/**
|
|
36
21
|
* cx - cx function
|
|
37
|
-
* @param
|
|
22
|
+
* @param classes - Class names to join
|
|
38
23
|
* @returns 返回值说明
|
|
39
24
|
*/
|
|
40
25
|
export declare function cx(...classes: (string | false | null | undefined)[]): string;
|
|
41
|
-
//# sourceMappingURL=dom.d.ts.map
|
package/esm/constants/dom.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* ***********************************
|
|
3
|
-
* @composy/chat-core v0.0.
|
|
3
|
+
* @composy/chat-core v0.0.2 *
|
|
4
4
|
* Built with rollup *
|
|
5
|
-
* Build time: 2024-
|
|
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
|
-
|
|
18
|
-
return `ldesign-${
|
|
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);
|
package/esm/constants/dom.js.map
CHANGED
|
@@ -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'
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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):
|
|
31
|
-
|
|
32
|
-
|
|
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
|
package/esm/core/chat-manager.js
CHANGED
|
@@ -1,92 +1,216 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* ***********************************
|
|
3
|
-
* @composy/chat-core v0.0.
|
|
3
|
+
* @composy/chat-core v0.0.2 *
|
|
4
4
|
* Built with rollup *
|
|
5
|
-
* Build time: 2024-
|
|
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 =
|
|
17
|
+
if (initialMessages?.length) this.messages = normalizeChatMessages(initialMessages);
|
|
16
18
|
}
|
|
17
|
-
/** 获取所有消息 */
|
|
18
19
|
getMessages() {
|
|
19
|
-
return
|
|
20
|
+
return this.messages.map(cloneChatMessage);
|
|
20
21
|
}
|
|
21
|
-
/** 根据 ID 获取消息 */
|
|
22
22
|
getMessage(id) {
|
|
23
|
-
|
|
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
|
-
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
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
|
-
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
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((
|
|
81
|
+
const index = this.messages.findIndex((message) => message.id === id);
|
|
40
82
|
if (index === -1) return;
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
this.
|
|
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((
|
|
98
|
+
const index = this.messages.findIndex((message) => message.id === id);
|
|
51
99
|
if (index === -1) return;
|
|
52
|
-
const removed = this.messages.splice(index, 1)
|
|
53
|
-
this.
|
|
54
|
-
this.emitter.emit("
|
|
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 =
|
|
59
|
-
this.
|
|
106
|
+
this.messages = normalizeChatMessages(messages);
|
|
107
|
+
this.streams.clear();
|
|
108
|
+
this.emitChanged();
|
|
60
109
|
}
|
|
61
|
-
/** 清空所有消息 */
|
|
62
110
|
clearMessages() {
|
|
63
111
|
this.messages = [];
|
|
64
|
-
this.
|
|
112
|
+
this.streams.clear();
|
|
113
|
+
this.emitChanged();
|
|
65
114
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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.
|
|
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 */
|