@saiboniuma/chat-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @saiboniuma/chat-ui
2
+
3
+ > 可插拔聊天 UI 组件库 — Shadow DOM 隔离,插件化消息渲染。
4
+
5
+ 基于 `@saiboniuma/realtime-core` 提供 UI 组件。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ npm install @saiboniuma/chat-ui @saiboniuma/realtime-core
11
+ ```
12
+
13
+ ## 快速使用
14
+
15
+ ```typescript
16
+ import { ChatUI } from '@saiboniuma/chat-ui';
17
+
18
+ const chatUI = new ChatUI({
19
+ container: document.getElementById('chat-container'),
20
+ // Shadow DOM 隔离,避免宿主页面样式污染
21
+ });
22
+ chatUI.mount();
23
+ ```
24
+
25
+ ## 特性
26
+
27
+ - **Shadow DOM 隔离**:宿主页面样式不影响聊天 UI
28
+ - **插件化消息渲染**:文本、图片、表情等可扩展
29
+ - **轻量**:原生 DOM 实现,无框架依赖
30
+
31
+ ## 构建
32
+
33
+ ```bash
34
+ npm install
35
+ npm run build # 输出到 dist/
36
+ npm run type-check # 类型检查
37
+ ```
38
+
39
+ ## 依赖
40
+
41
+ - `@saiboniuma/realtime-core`
42
+ - TypeScript ~5.6.3
43
+ - Vite ^6.2.0
44
+
45
+ ## 发布
46
+
47
+ ```bash
48
+ pnpm build
49
+ pnpm publish --access public # 发布到 npmjs.com
50
+ ```
51
+
52
+ Registry: `https://registry.npmjs.org/`
@@ -0,0 +1,428 @@
1
+ import { Message } from '@saiboniuma/realtime-core';
2
+ import { RealtimeClient } from '@saiboniuma/realtime-core';
3
+
4
+ /** 聊天 UI 容器主类 */
5
+ export declare class ChatUI {
6
+ private host;
7
+ private shadowRoot;
8
+ private root;
9
+ private messageListEl;
10
+ private inputWrapperEl;
11
+ private pluginManager;
12
+ private themeManager;
13
+ private messageListRenderer;
14
+ private inputArea;
15
+ private i18n;
16
+ private core;
17
+ private currentUserID;
18
+ private userInfo;
19
+ private agentInfo;
20
+ private agentUserID;
21
+ private groupId;
22
+ constructor(container: HTMLElement, options?: ChatUIOptions);
23
+ /** 绑定 RealtimeClient 核心实例 */
24
+ bindCore(core: RealtimeClient): void;
25
+ /** 设置当前用户信息(用于消息渲染) */
26
+ setUserInfo(userID: string, nickname: string, avatar: string): void;
27
+ /** 设置客服信息(用于消息渲染) */
28
+ setAgentInfo(agentUserID: string, nickname: string, avatar: string): void;
29
+ /** 设置群组 ID(广播模式下消息发送到群组) */
30
+ setGroupId(groupId: string): void;
31
+ /** 加载并渲染消息列表 */
32
+ setMessages(messages: Message[]): void;
33
+ /** 追加单条消息 */
34
+ appendMessage(message: Message): void;
35
+ /** 启用/禁用输入 */
36
+ setInputEnabled(enabled: boolean): void;
37
+ /** 滚动到底部 */
38
+ scrollToBottom(): void;
39
+ /** 注册插件 */
40
+ registerPlugin(plugin: ChatUIPlugin): void;
41
+ /** 注销插件 */
42
+ unregisterPlugin(pluginName: string): void;
43
+ /** 切换语言 */
44
+ setLocale(locale: Locale): void;
45
+ /** 切换主题 */
46
+ setTheme(theme: 'light' | 'dark'): void;
47
+ /** 获取 i18n 实例(供插件使用) */
48
+ getI18n(): I18n;
49
+ /** 获取 Shadow Root */
50
+ getShadowRoot(): ShadowRoot;
51
+ /** 销毁 */
52
+ destroy(): void;
53
+ /** 获取发送者信息 */
54
+ private getSenderInfo;
55
+ /** 发送消息回调 */
56
+ private handleSend;
57
+ /** 显示加载中覆盖层 */
58
+ private showLoadingOverlay;
59
+ /** 显示错误覆盖层 */
60
+ private showErrorOverlay;
61
+ /** 隐藏覆盖层 */
62
+ private hideOverlay;
63
+ }
64
+
65
+ /** 聊天 UI 配置选项 */
66
+ export declare interface ChatUIOptions {
67
+ /** 注册的插件列表 */
68
+ plugins?: ChatUIPlugin[];
69
+ /** 主题(light/dark) */
70
+ theme?: 'light' | 'dark';
71
+ /** 语言 */
72
+ locale?: string;
73
+ /** 是否全屏模式 */
74
+ fullPage?: boolean;
75
+ }
76
+
77
+ /** 聊天 UI 插件接口 */
78
+ export declare interface ChatUIPlugin {
79
+ /** 插件名称 */
80
+ name: string;
81
+ /** 插件版本 */
82
+ version: string;
83
+ /** 安装插件(注册渲染器和工具按钮) */
84
+ install(context: PluginContext): void;
85
+ /** 卸载插件 */
86
+ uninstall?(): void;
87
+ }
88
+
89
+ /** 清空元素子节点 */
90
+ export declare function clearChildren(el: HTMLElement): void;
91
+
92
+ /** 创建头像元素(含加载失败 fallback) */
93
+ export declare function createAvatarElement(avatarUrl: string, nickname: string, className?: string): HTMLElement;
94
+
95
+ /** 创建 DOM 元素并设置属性和 class */
96
+ export declare function createElement<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string, attrs?: Record<string, string>): HTMLElementTagNameMap[K];
97
+
98
+ /** Emoji 分类 */
99
+ export declare const emojiCategories: {
100
+ nameKey: string;
101
+ start: number;
102
+ end: number;
103
+ }[];
104
+
105
+ /** 完整 Emoji 列表(112 个) */
106
+ export declare const emojiList: string[];
107
+
108
+ /** 表情选择器 UI */
109
+ export declare class EmojiPicker {
110
+ private container;
111
+ private i18n;
112
+ private activeCategory;
113
+ private onSelect;
114
+ private onClose;
115
+ private visible;
116
+ private pickerEl;
117
+ private gridEl;
118
+ private categoryBtns;
119
+ constructor(container: HTMLElement, i18n: I18n, onSelect: (emoji: string) => void, onClose: () => void);
120
+ /** 显示/隐藏表情面板 */
121
+ toggle(): void;
122
+ /** 显示面板 */
123
+ show(): void;
124
+ /** 隐藏面板 */
125
+ hide(): void;
126
+ /** 是否可见 */
127
+ isVisible(): boolean;
128
+ /** 刷新文本(语言切换时) */
129
+ refreshTexts(): void;
130
+ /** 构建面板 DOM */
131
+ private build;
132
+ /** 渲染当前分类的 Emoji 网格 */
133
+ private renderGrid;
134
+ /** 更新分类按钮高亮 */
135
+ private updateCategoryButtons;
136
+ /** 销毁 */
137
+ destroy(): void;
138
+ }
139
+
140
+ /** 表情插件 */
141
+ export declare class EmojiPlugin implements ChatUIPlugin {
142
+ name: string;
143
+ version: string;
144
+ private picker;
145
+ install(context: PluginContext): void;
146
+ uninstall(): void;
147
+ }
148
+
149
+ export declare const enUS: Record<string, string>;
150
+
151
+ /** 格式化消息时间 */
152
+ export declare function formatMessageTime(timestamp: number, i18n?: I18n): string;
153
+
154
+ /** 获取头像显示文本(首字母大写) */
155
+ export declare function getAvatarText(nickname: string): string;
156
+
157
+ /** 获取发送者信息的回调 */
158
+ declare type GetSenderInfoFn = (message: Message) => SenderInfo;
159
+
160
+ /** 多语言管理器 */
161
+ export declare class I18n {
162
+ private currentLocale;
163
+ private messages;
164
+ private changeListeners;
165
+ constructor(locale?: Locale);
166
+ /** 翻译文本 */
167
+ t(key: string, params?: Record<string, string | number>): string;
168
+ /** 获取当前语言 */
169
+ getLocale(): Locale;
170
+ /** 切换语言(触发所有监听器刷新 UI) */
171
+ setLocale(locale: Locale): void;
172
+ /** 注册语言包(可用于插件扩展) */
173
+ registerLocale(locale: string, messages: Record<string, string>): void;
174
+ /** 监听语言切换 */
175
+ onChange(callback: () => void): void;
176
+ /** 移除语言切换监听 */
177
+ offChange(callback: () => void): void;
178
+ /** 销毁 */
179
+ destroy(): void;
180
+ }
181
+
182
+ /** 图片消息插件 */
183
+ export declare class ImagePlugin implements ChatUIPlugin {
184
+ name: string;
185
+ version: string;
186
+ install(context: PluginContext): void;
187
+ }
188
+
189
+ /** 图片预览(全屏遮罩层) */
190
+ export declare class ImagePreview {
191
+ private static overlay;
192
+ /** 显示图片预览 */
193
+ static show(imageUrl: string, shadowRoot: ShadowRoot): void;
194
+ /** 关闭图片预览 */
195
+ static close(shadowRoot: ShadowRoot): void;
196
+ }
197
+
198
+ /** 图片消息渲染器 */
199
+ export declare class ImageRenderer implements MessageRenderer {
200
+ contentTypes: 102[];
201
+ /** 同时匹配自定义图片消息 */
202
+ canRender(message: Message): boolean;
203
+ render(message: Message, context: RenderContext): HTMLElement;
204
+ }
205
+
206
+ /** 输入区域 */
207
+ export declare class InputArea {
208
+ private wrapper;
209
+ private inputArea;
210
+ private inputElement;
211
+ private sendButton;
212
+ private toolbarContainer;
213
+ private pluginManager;
214
+ private i18n;
215
+ private shadowRoot;
216
+ private core;
217
+ private onSend;
218
+ private isSending;
219
+ private isDisabled;
220
+ private recvID;
221
+ private groupId;
222
+ private onAppendMessage;
223
+ constructor(container: HTMLElement, pluginManager: PluginManager, i18n: I18n, shadowRoot: ShadowRoot, onSend: OnSendFn);
224
+ /** 设置追加消息回调 */
225
+ setOnAppendMessage(fn: (message: Message) => void): void;
226
+ /** 绑定 core 实例 */
227
+ bindCore(core: RealtimeClient): void;
228
+ /** 设置接收者 ID */
229
+ setRecvID(recvID: string): void;
230
+ /** 设置群组 ID(广播模式) */
231
+ setGroupId(groupId: string): void;
232
+ /** 设置启用/禁用状态 */
233
+ setDisabled(disabled: boolean): void;
234
+ /** 获取输入框元素 */
235
+ getInputElement(): HTMLInputElement;
236
+ /** 刷新工具栏 */
237
+ refreshToolbar(): void;
238
+ /** 刷新 UI 文本 */
239
+ refreshTexts(): void;
240
+ /** 构建输入区域 DOM */
241
+ private build;
242
+ /** 构建工具栏按钮 */
243
+ private buildToolbar;
244
+ /** 键盘事件处理 */
245
+ private handleKeydown;
246
+ /** 发送消息 */
247
+ private handleSend;
248
+ /** 更新工具栏按钮禁用状态 */
249
+ private updateToolbarButtons;
250
+ /** 更新发送按钮状态 */
251
+ private updateSendButton;
252
+ }
253
+
254
+ /** 输入区工具按钮接口 */
255
+ export declare interface InputTool {
256
+ /** 工具名称 */
257
+ name: string;
258
+ /** 图标(emoji 或 HTML) */
259
+ icon: string;
260
+ /** 提示文字 */
261
+ tooltip: string;
262
+ /** 排序优先级(越小越靠前) */
263
+ order?: number;
264
+ /** 点击回调 */
265
+ onActivate(context: InputToolContext): void;
266
+ }
267
+
268
+ /** 输入工具上下文 */
269
+ export declare interface InputToolContext {
270
+ /** 输入框元素 */
271
+ inputElement: HTMLInputElement;
272
+ /** 插入文本到输入框 */
273
+ insertText(text: string): void;
274
+ /** 获取当前输入文本 */
275
+ getInputText(): string;
276
+ /** 设置输入文本 */
277
+ setInputText(text: string): void;
278
+ /** 获取接收者 ID(群聊模式下返回 groupId) */
279
+ getRecvID(): string;
280
+ /** 是否群聊模式 */
281
+ isGroup(): boolean;
282
+ /** core 实例 */
283
+ core: RealtimeClient | null;
284
+ /** 追加消息到聊天列表 */
285
+ appendMessage(message: Message): void;
286
+ /** Shadow Root */
287
+ shadowRoot: ShadowRoot;
288
+ /** i18n 实例 */
289
+ i18n: I18n;
290
+ }
291
+
292
+ /** 检查容器是否接近底部(用于判断是否自动滚动) */
293
+ export declare function isNearBottom(container: HTMLElement, threshold?: number): boolean;
294
+
295
+ export declare type Locale = 'zh-CN' | 'en-US' | string;
296
+
297
+ /** 消息列表渲染器 */
298
+ export declare class MessageListRenderer {
299
+ private container;
300
+ private pluginManager;
301
+ private i18n;
302
+ private shadowRoot;
303
+ private currentUserID;
304
+ private getSenderInfo;
305
+ private messages;
306
+ private renderedMsgIds;
307
+ constructor(container: HTMLElement, pluginManager: PluginManager, i18n: I18n, shadowRoot: ShadowRoot, getSenderInfo: GetSenderInfoFn);
308
+ /** 设置当前用户 ID */
309
+ setCurrentUserID(userID: string): void;
310
+ /** 更新消息列表 */
311
+ updateMessages(messages: Message[]): void;
312
+ /** 追加单条新消息 */
313
+ appendMessage(message: Message): void;
314
+ /** 滚动到底部 */
315
+ scrollToBottom(): void;
316
+ /** 完整渲染消息列表 */
317
+ private render;
318
+ /** 渲染空状态 */
319
+ private renderEmptyState;
320
+ /** 渲染单条消息 */
321
+ private renderMessageItem;
322
+ /** 刷新所有消息的 UI 文本(语言切换时调用) */
323
+ refreshTexts(): void;
324
+ /** 清空 */
325
+ clear(): void;
326
+ }
327
+
328
+ /** 消息渲染器接口 */
329
+ export declare interface MessageRenderer {
330
+ /** 支持的 contentType 列表 */
331
+ contentTypes: number[];
332
+ /** 渲染消息内容,返回 DOM 元素 */
333
+ render(message: Message, context: RenderContext): HTMLElement;
334
+ /** 自定义消息匹配器(用于 CustomMessage 子类型判断) */
335
+ canRender?(message: Message): boolean;
336
+ }
337
+
338
+ /** 发送回调 */
339
+ declare type OnSendFn = (text: string) => Promise<void>;
340
+
341
+ /** 插件安装上下文 */
342
+ export declare interface PluginContext {
343
+ /** 注册消息渲染器 */
344
+ registerRenderer(renderer: MessageRenderer): void;
345
+ /** 注册输入工具按钮 */
346
+ registerInputTool(tool: InputTool): void;
347
+ /** i18n 实例 */
348
+ i18n: I18n;
349
+ /** 注册插件语言包 */
350
+ registerLocale(locale: string, messages: Record<string, string>): void;
351
+ }
352
+
353
+ /** 插件管理器 */
354
+ export declare class PluginManager {
355
+ private renderers;
356
+ private inputTools;
357
+ private plugins;
358
+ private i18n;
359
+ constructor(i18n: I18n);
360
+ /** 注册插件 */
361
+ register(plugin: ChatUIPlugin): void;
362
+ /** 注销插件 */
363
+ unregister(pluginName: string): void;
364
+ /** 根据消息查找合适的渲染器 */
365
+ getRenderer(message: Message): MessageRenderer | null;
366
+ /** 获取所有输入工具按钮 */
367
+ getInputTools(): InputTool[];
368
+ /** 销毁所有插件 */
369
+ destroy(): void;
370
+ }
371
+
372
+ /** 渲染上下文 */
373
+ export declare interface RenderContext {
374
+ /** 是否是自己发的消息 */
375
+ isSelf: boolean;
376
+ /** 当前用户 ID */
377
+ currentUserID: string;
378
+ /** i18n 实例 */
379
+ i18n: I18n;
380
+ /** Shadow Root(用于插入弹窗等) */
381
+ shadowRoot: ShadowRoot;
382
+ }
383
+
384
+ /** 将容器滚动到底部 */
385
+ export declare function scrollToBottom(container: HTMLElement): void;
386
+
387
+ /** 用户信息(用于渲染消息头像和昵称) */
388
+ export declare interface SenderInfo {
389
+ nickname: string;
390
+ avatar: string;
391
+ }
392
+
393
+ /** 安全设置文本内容 */
394
+ export declare function setTextContent(el: HTMLElement, text: string): void;
395
+
396
+ /** 文本消息插件 */
397
+ export declare class TextPlugin implements ChatUIPlugin {
398
+ name: string;
399
+ version: string;
400
+ install(context: PluginContext): void;
401
+ }
402
+
403
+ /** 文本消息渲染器 */
404
+ export declare class TextRenderer implements MessageRenderer {
405
+ contentTypes: 101[];
406
+ render(message: Message, context: RenderContext): HTMLElement;
407
+ }
408
+
409
+ /** 主题管理器 */
410
+ export declare class ThemeManager {
411
+ private currentTheme;
412
+ private container;
413
+ /** 绑定容器元素 */
414
+ bind(container: HTMLElement): void;
415
+ /** 设置主题 */
416
+ setTheme(theme: 'light' | 'dark'): void;
417
+ /** 获取当前主题 */
418
+ getTheme(): 'light' | 'dark';
419
+ /** 应用主题到容器 */
420
+ private applyTheme;
421
+ }
422
+
423
+ /** 切换 CSS class */
424
+ export declare function toggleClass(el: HTMLElement, className: string, force?: boolean): void;
425
+
426
+ export declare const zhCN: Record<string, string>;
427
+
428
+ export { }