ibc-ai-web-sdk 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,326 @@
1
+ /**
2
+ * AI 聊天客户端 - 微信小程序版本
3
+ * 使用 wx.request 发送请求
4
+ */
5
+
6
+ // 默认超时时间(60 秒,AI 调用可能较慢)
7
+ const DEFAULT_TIMEOUT = 60000
8
+ const ERROR_CODES = require('../utils/errorCodes')
9
+ const { maskSensitiveData } = require('../utils/logger')
10
+
11
+ function createRequestId() {
12
+ return 'req_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8)
13
+ }
14
+
15
+ class AIChatClient {
16
+ /**
17
+ * 创建客户端实例
18
+ * @param {Object} config - 配置对象
19
+ */
20
+ constructor(config) {
21
+ this.config = config
22
+ this.conversationId = null
23
+ }
24
+
25
+ /**
26
+ * 发送聊天消息
27
+ * @param {string} prompt - 用户输入
28
+ * @param {string} token - 可选,Token
29
+ * @returns {Promise<Object>} 标准响应格式 { success, code, message, data }
30
+ */
31
+ sendMessage(prompt, token) {
32
+ return new Promise((resolve, reject) => {
33
+ // 参数校验
34
+ if (!prompt || !prompt.trim()) {
35
+ resolve(this.createError(ERROR_CODES.PROMPT_EMPTY, '提示词不能为空'))
36
+ return
37
+ }
38
+
39
+ // 可选鉴权:如果配置了 authFn,先调用用户系统的鉴权接口
40
+ if (this.config.authFn) {
41
+ Promise.resolve(this.config.authFn(token || this.config.token, this.config.userId))
42
+ .then(() => {
43
+ this._doSend(prompt.trim(), token)
44
+ .then(resolve)
45
+ .catch((error) => {
46
+ // 网络错误统一转换为标准格式
47
+ if (typeof error === 'string') {
48
+ resolve(this.createError(ERROR_CODES.NETWORK_ERROR, error))
49
+ } else {
50
+ resolve(error)
51
+ }
52
+ })
53
+ })
54
+ .catch(error => {
55
+ resolve(this.createError(ERROR_CODES.AUTH_FAILED, '鉴权失败,无法使用智能体:' + (error.message || '未知错误')))
56
+ })
57
+ } else {
58
+ this._doSend(prompt.trim(), token)
59
+ .then(resolve)
60
+ .catch((error) => {
61
+ // 网络错误统一转换为标准格式
62
+ if (typeof error === 'string') {
63
+ resolve(this.createError(ERROR_CODES.NETWORK_ERROR, error))
64
+ } else {
65
+ resolve(error)
66
+ }
67
+ })
68
+ }
69
+ })
70
+ }
71
+
72
+ /**
73
+ * 执行实际请求
74
+ */
75
+ _doSend(prompt, token) {
76
+ return new Promise((resolve, reject) => {
77
+ const that = this
78
+
79
+ // 构建请求参数
80
+ const payload = {
81
+ appId: this.config.appId || this.config.agentAppId,
82
+ prompt: prompt,
83
+ userId: this.config.userId,
84
+ bizType: this.config.bizType,
85
+ requestId: this.config.requestId || createRequestId()
86
+ }
87
+
88
+ // 多轮对话时添加 conversationId
89
+ if (this.conversationId) {
90
+ payload.conversationId = this.conversationId
91
+ }
92
+
93
+ // 添加扩展信息
94
+ const bizParams = this.config.bizParams || this.config.extendInfo
95
+ if (bizParams) {
96
+ payload.bizParams = typeof bizParams === 'string' ? bizParams : JSON.stringify(bizParams)
97
+ }
98
+
99
+ // 构建请求头
100
+ const headers = {
101
+ 'Content-Type': 'application/json'
102
+ }
103
+
104
+ // 添加自定义请求头
105
+ if (this.config.headers) {
106
+ Object.assign(headers, this.config.headers)
107
+ }
108
+
109
+ // 添加 Token
110
+ const authToken = token || this.config.token
111
+ if (authToken) {
112
+ headers['Authorization'] = 'Bearer ' + authToken
113
+ }
114
+
115
+ console.log('[AI Request] Sending request to:', this.config.apiEndpoint)
116
+ console.log('[AI Request] Payload (masked):', JSON.stringify(maskSensitiveData(payload)))
117
+
118
+ // 使用 wx.request 发送请求
119
+ wx.request({
120
+ url: this.config.apiEndpoint,
121
+ method: 'POST',
122
+ data: payload,
123
+ header: headers,
124
+ timeout: this.config.timeout || DEFAULT_TIMEOUT,
125
+ success(res) {
126
+ console.log('[AI Response] Success:', res.data)
127
+ resolve(that.parseResponse(res.data))
128
+ },
129
+ fail(err) {
130
+ console.error('[AI Response] Failed:', err)
131
+ const errorMsg = err.errMsg || '网络请求失败'
132
+ reject(that.createError(ERROR_CODES.NETWORK_ERROR, errorMsg))
133
+ }
134
+ })
135
+ })
136
+ }
137
+
138
+ /**
139
+ * 创建错误对象
140
+ */
141
+ createError(code, message) {
142
+ return {
143
+ success: false,
144
+ code: code,
145
+ message: message,
146
+ data: null
147
+ }
148
+ }
149
+
150
+ /**
151
+ * 解析响应为统一标准格式
152
+ * @param {Object} raw - 原始响应数据
153
+ * @returns {Object} 标准格式 { success, code, message, data }
154
+ */
155
+ parseResponse(raw) {
156
+ // 如果已经是标准格式,直接返回
157
+ if (raw.hasOwnProperty('success') && raw.hasOwnProperty('code')) {
158
+ return {
159
+ success: raw.success === true,
160
+ code: raw.code || 200,
161
+ message: raw.message || '',
162
+ data: raw.data || null
163
+ }
164
+ }
165
+
166
+ // 兼容旧格式(rtnCode/bizContent)
167
+ return {
168
+ success: raw.rtnCode === 'SUCCESS' || raw.errCode === '000000',
169
+ code: raw.code || raw.errCode || 0,
170
+ message: raw.message || raw.errMsg || raw.rtnMsg || '',
171
+ data: raw.data || raw.bizContent || null
172
+ }
173
+ }
174
+
175
+ /**
176
+ * 设置会话 ID
177
+ * @param {string} conversationId - 会话 ID
178
+ */
179
+ setConversationId(conversationId) {
180
+ this.conversationId = conversationId
181
+ }
182
+
183
+ /**
184
+ * 查询可用智能体应用列表
185
+ * @param {Object} options - 查询选项
186
+ * @param {number} options.applicationType - 应用类型过滤
187
+ * @param {number} options.pageIndex - 页码,默认1
188
+ * @param {number} options.pageSize - 每页大小,默认20
189
+ * @returns {Promise<Object>} 标准响应格式 { success, code, message, data }
190
+ */
191
+ listApplications(options = {}) {
192
+ return new Promise((resolve, reject) => {
193
+ const appListEndpoint = this.config.appListEndpoint || '/v1/app/list'
194
+ const payload = {
195
+ applicationType: options.applicationType,
196
+ pageIndex: options.pageIndex || 1,
197
+ pageSize: options.pageSize || 20,
198
+ userId: this.config.userId
199
+ }
200
+ const headers = { 'Content-Type': 'application/json' }
201
+ if (this.config.headers) {
202
+ Object.assign(headers, this.config.headers)
203
+ }
204
+ const authToken = this.config.token
205
+ if (authToken) {
206
+ headers['Authorization'] = 'Bearer ' + authToken
207
+ }
208
+ wx.request({
209
+ url: appListEndpoint,
210
+ method: 'POST',
211
+ data: payload,
212
+ header: headers,
213
+ timeout: this.config.timeout || DEFAULT_TIMEOUT,
214
+ success: (res) => resolve(this.parseResponse(res.data)),
215
+ fail: (err) => {
216
+ const errorMsg = err.errMsg || '网络请求失败'
217
+ reject(this.createError(500, errorMsg))
218
+ }
219
+ })
220
+ })
221
+ }
222
+
223
+ /**
224
+ * 回复权限确认请求(HITL权限交互)
225
+ */
226
+ replyPermission(request) {
227
+ return new Promise((resolve, reject) => {
228
+ if (!request.sessionId || !request.requestId) {
229
+ resolve(this.createError(40002, 'sessionId和requestId不能为空'))
230
+ return
231
+ }
232
+ const endpoint = this.config.runtimePermissionReplyEndpoint || '/v1/app/runtime/permission/reply'
233
+ const payload = {
234
+ sessionId: request.sessionId,
235
+ requestId: request.requestId,
236
+ reply: request.reply || 'once',
237
+ message: request.message || '',
238
+ userId: this.config.userId
239
+ }
240
+ const headers = { 'Content-Type': 'application/json' }
241
+ if (this.config.headers) Object.assign(headers, this.config.headers)
242
+ const authToken = this.config.token
243
+ if (authToken) headers['Authorization'] = 'Bearer ' + authToken
244
+ wx.request({
245
+ url: endpoint, method: 'POST', data: payload, header: headers,
246
+ timeout: this.config.timeout || DEFAULT_TIMEOUT,
247
+ success: (res) => resolve(this.parseResponse(res.data)),
248
+ fail: (err) => reject(this.createError(500, err.errMsg || '网络请求失败'))
249
+ })
250
+ })
251
+ }
252
+
253
+ /**
254
+ * 回复智能体问题(HITL问答交互)
255
+ */
256
+ replyQuestion(request) {
257
+ return new Promise((resolve, reject) => {
258
+ if (!request.sessionId || !request.requestId) {
259
+ resolve(this.createError(40002, 'sessionId和requestId不能为空'))
260
+ return
261
+ }
262
+ const endpoint = this.config.runtimeQuestionReplyEndpoint || '/v1/app/runtime/question/reply'
263
+ const payload = {
264
+ sessionId: request.sessionId,
265
+ requestId: request.requestId,
266
+ answers: request.answers || [],
267
+ userId: this.config.userId
268
+ }
269
+ const headers = { 'Content-Type': 'application/json' }
270
+ if (this.config.headers) Object.assign(headers, this.config.headers)
271
+ const authToken = this.config.token
272
+ if (authToken) headers['Authorization'] = 'Bearer ' + authToken
273
+ wx.request({
274
+ url: endpoint, method: 'POST', data: payload, header: headers,
275
+ timeout: this.config.timeout || DEFAULT_TIMEOUT,
276
+ success: (res) => resolve(this.parseResponse(res.data)),
277
+ fail: (err) => reject(this.createError(500, err.errMsg || '网络请求失败'))
278
+ })
279
+ })
280
+ }
281
+
282
+ /**
283
+ * 拒绝智能体问题(HITL问答拒绝)
284
+ */
285
+ rejectQuestion(request) {
286
+ return new Promise((resolve, reject) => {
287
+ if (!request.sessionId || !request.requestId) {
288
+ resolve(this.createError(40002, 'sessionId和requestId不能为空'))
289
+ return
290
+ }
291
+ const endpoint = this.config.runtimeQuestionRejectEndpoint || '/v1/app/runtime/question/reject'
292
+ const payload = {
293
+ sessionId: request.sessionId,
294
+ requestId: request.requestId,
295
+ userId: this.config.userId
296
+ }
297
+ const headers = { 'Content-Type': 'application/json' }
298
+ if (this.config.headers) Object.assign(headers, this.config.headers)
299
+ const authToken = this.config.token
300
+ if (authToken) headers['Authorization'] = 'Bearer ' + authToken
301
+ wx.request({
302
+ url: endpoint, method: 'POST', data: payload, header: headers,
303
+ timeout: this.config.timeout || DEFAULT_TIMEOUT,
304
+ success: (res) => resolve(this.parseResponse(res.data)),
305
+ fail: (err) => reject(this.createError(500, err.errMsg || '网络请求失败'))
306
+ })
307
+ })
308
+ }
309
+
310
+ /**
311
+ * 清除会话 ID(开始新对话)
312
+ */
313
+ clearConversation() {
314
+ this.conversationId = null
315
+ }
316
+
317
+ /**
318
+ * 更新配置
319
+ * @param {Object} newConfig - 新配置
320
+ */
321
+ updateConfig(newConfig) {
322
+ Object.assign(this.config, newConfig)
323
+ }
324
+ }
325
+
326
+ module.exports = AIChatClient
@@ -0,0 +1,178 @@
1
+ /**
2
+ * IBC AI Web SDK - 微信小程序版本 TypeScript 类型定义
3
+ * 统一定义所有类型和接口,提供完整的类型提示
4
+ */
5
+
6
+ /**
7
+ * AI 聊天配置接口
8
+ */
9
+ export interface AIChatConfig {
10
+ // ===== 必填配置 =====
11
+
12
+ /** API 接口地址 */
13
+ apiEndpoint: string
14
+
15
+ /** 应用列表接口地址,默认 /v1/app/list */
16
+ appListEndpoint?: string
17
+
18
+ /** 权限确认回复接口地址,默认 /v1/app/runtime/permission/reply */
19
+ runtimePermissionReplyEndpoint?: string
20
+
21
+ /** 问题回复接口地址,默认 /v1/app/runtime/question/reply */
22
+ runtimeQuestionReplyEndpoint?: string
23
+
24
+ /** 问题拒绝接口地址,默认 /v1/app/runtime/question/reject */
25
+ runtimeQuestionRejectEndpoint?: string
26
+
27
+ /** 智能体应用 ID */
28
+ agentAppId: string
29
+
30
+ /** 用户 ID */
31
+ userId: string
32
+
33
+ /** 业务类型标识 */
34
+ bizType: string
35
+
36
+ /** 认证 Token */
37
+ token?: string
38
+
39
+ // ===== 可选配置 =====
40
+
41
+ /** 请求超时时间(毫秒),默认 60000 */
42
+ timeout?: number
43
+
44
+ /** 自定义请求头 */
45
+ headers?: Record<string, string>
46
+
47
+ /** 扩展信息 */
48
+ extendInfo?: Record<string, any> | null
49
+
50
+ // ===== UI 配置 =====
51
+
52
+ /** 对话框标题,默认 'AI 智能助理' */
53
+ title?: string
54
+
55
+ /** 欢迎文本,默认 '您好!我是 AI 智能助理' */
56
+ welcomeText?: string
57
+
58
+ /** 欢迎描述,默认 '我可以帮您解答系统使用问题' */
59
+ welcomeDesc?: string
60
+
61
+ /** 最大消息数量,默认 50 */
62
+ maxMessages?: number
63
+
64
+ /** 输入框最大字数,默认 500 */
65
+ maxLength?: number
66
+
67
+ /** 是否允许拖拽,默认 true */
68
+ enableDrag?: boolean
69
+
70
+ // ===== 可选鉴权 =====
71
+
72
+ /**
73
+ * 可选鉴权函数
74
+ * @param token - 当前用户的 token
75
+ * @param userId - 用户 ID
76
+ * @returns Promise<void>,resolve 表示通过,reject/throw 表示拒绝
77
+ */
78
+ authFn?: (token: string, userId: string) => Promise<void>
79
+
80
+ // ===== 事件回调 =====
81
+
82
+ /** SDK 加载完成回调 */
83
+ onLoad?: (client: any) => void
84
+
85
+ /** 错误回调 */
86
+ onError?: (error: Error | string) => void
87
+
88
+ // ===== 主题配置(可选) =====
89
+
90
+ /** 主题配置 */
91
+ theme?: {
92
+ /** 主色调 */
93
+ primaryColor?: string
94
+ /** 背景色 */
95
+ backgroundColor?: string
96
+ /** 气泡背景色 */
97
+ bubbleColor?: string
98
+ }
99
+ }
100
+
101
+ /**
102
+ * 消息类型枚举
103
+ */
104
+ export enum MessageType {
105
+ /** 用户消息 */
106
+ USER = 'user',
107
+ /** AI 消息 */
108
+ AI = 'ai'
109
+ }
110
+
111
+ /**
112
+ * 消息接口
113
+ */
114
+ export interface Message {
115
+ /** 消息 ID */
116
+ id: string
117
+
118
+ /** 消息类型 */
119
+ type: MessageType
120
+
121
+ /** 消息内容 */
122
+ content: string
123
+
124
+ /** 渲染后的内容(HTML) */
125
+ renderedContent: string
126
+
127
+ /** 消息时间 */
128
+ time: string
129
+
130
+ /** 是否失败 */
131
+ error?: boolean
132
+ }
133
+
134
+ /**
135
+ * SDK 事件参数
136
+ */
137
+ export interface SDKEvents {
138
+ /** 用户发送消息 */
139
+ 'message-send': { message: string; index: number }
140
+
141
+ /** AI 回复消息 */
142
+ 'message-receive': { response: any; index: number }
143
+
144
+ /** Loading 状态变化 */
145
+ 'loading-change': boolean
146
+
147
+ /** 关闭对话框 */
148
+ close: void
149
+
150
+ /** 展开/收起 */
151
+ expand: boolean
152
+ }
153
+
154
+ /**
155
+ * 错误码枚举
156
+ */
157
+ export const ERROR_CODES = {
158
+ // 成功
159
+ SUCCESS: 200,
160
+
161
+ // 客户端错误 (40000-49999)
162
+ AGENT_NOT_FOUND: 40001,
163
+ PROMPT_EMPTY: 40002,
164
+ AUTH_FAILED: 40301,
165
+
166
+ // 服务器错误 (50000-59999)
167
+ NETWORK_ERROR: 500,
168
+ SERVER_ERROR: 50001,
169
+ TIMEOUT_ERROR: 50002,
170
+
171
+ // 取消
172
+ REQUEST_CANCELLED: 0
173
+ } as const
174
+
175
+ /**
176
+ * 导出错误码类型
177
+ */
178
+ export type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES]
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 错误码常量
3
+ * 统一定义所有错误码,方便维护
4
+ */
5
+
6
+ const ERROR_CODES = {
7
+ // 成功
8
+ SUCCESS: 200,
9
+
10
+ // 客户端错误 (40000-49999)
11
+ AGENT_NOT_FOUND: 40001, // 智能体不存在或已禁用
12
+ PROMPT_EMPTY: 40002, // 提示词不能为空
13
+ AUTH_FAILED: 40301, // 鉴权失败
14
+
15
+ // 服务器错误 (50000-59999)
16
+ NETWORK_ERROR: 500, // 网络请求失败
17
+ SERVER_ERROR: 50001, // 服务器内部错误
18
+ TIMEOUT_ERROR: 50002, // 请求超时
19
+
20
+ // 取消
21
+ REQUEST_CANCELLED: 0 // 请求已取消
22
+ }
23
+
24
+ // 导出为模块
25
+ module.exports = ERROR_CODES
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 日志脱敏工具
3
+ * 防止敏感信息(如 Token)打印到控制台
4
+ */
5
+
6
+ /**
7
+ * 脱敏对象中的敏感字段
8
+ * @param {Object} obj - 需要脱敏的对象
9
+ * @returns {Object} 脱敏后的对象
10
+ */
11
+ function maskSensitiveData(obj) {
12
+ if (!obj || typeof obj !== 'object') {
13
+ return obj
14
+ }
15
+
16
+ // 需要脱敏的字段列表
17
+ const sensitiveFields = ['token', 'authorization', 'password', 'secret', 'key']
18
+
19
+ const masked = {}
20
+ for (const key in obj) {
21
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
22
+ const lowerKey = key.toLowerCase()
23
+ // 检查是否是敏感字段
24
+ if (sensitiveFields.some(field => lowerKey.includes(field))) {
25
+ masked[key] = '******'
26
+ } else {
27
+ masked[key] = obj[key]
28
+ }
29
+ }
30
+ }
31
+
32
+ return masked
33
+ }
34
+
35
+ /**
36
+ * 脱敏字符串中的 Token
37
+ * @param {string} str - 需要脱敏的字符串
38
+ * @returns {string} 脱敏后的字符串
39
+ */
40
+ function maskToken(str) {
41
+ if (!str || typeof str !== 'string') {
42
+ return str
43
+ }
44
+ // 替换 Bearer Token 格式
45
+ return str.replace(/(Bearer\s+)([a-zA-Z0-9\-._~+\/]+=*)/g, '$1******')
46
+ }
47
+
48
+ module.exports = {
49
+ maskSensitiveData,
50
+ maskToken
51
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Markdown 解析工具 - 微信小程序版本
3
+ * 将 Markdown 文本转换为 rich-text 可用的 HTML
4
+ */
5
+
6
+ // Markdown 解析缓存(最多缓存 100 条)
7
+ const markdownCache = new Map()
8
+ const MAX_CACHE_SIZE = 100
9
+
10
+ function parseMarkdown(text) {
11
+ if (!text) return ''
12
+
13
+ // 1. 检查缓存
14
+ if (markdownCache.has(text)) {
15
+ return markdownCache.get(text)
16
+ }
17
+
18
+ let html = text
19
+
20
+ // 2. 转义 HTML 特殊字符(防止 XSS)- 最先处理
21
+ html = html.replace(/&/g, '&amp;')
22
+ html = html.replace(/</g, '&lt;')
23
+ html = html.replace(/>/g, '&gt;')
24
+
25
+ // 3. 水平线(--- 或 ***)- 在粗体/斜体前处理
26
+ html = html.replace(/^---$/gm, '<hr />')
27
+ html = html.replace(/^\*\*\*$/gm, '<hr />')
28
+
29
+ // 4. 标题(# H1, ## H2, ...)
30
+ html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>')
31
+ html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>')
32
+ html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>')
33
+ html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>')
34
+
35
+ // 5. 粗体(**text**)
36
+ html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
37
+
38
+ // 6. 斜体(*text*)
39
+ html = html.replace(/\*(.+?)\*/g, '<em>$1</em>')
40
+
41
+ // 7. 删除线(~~text~~)
42
+ html = html.replace(/~~(.+?)~~/g, '<del>$1</del>')
43
+
44
+ // 8. 图片(![alt](url))- 在链接前处理
45
+ html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" />')
46
+
47
+ // 9. 链接([text](url))
48
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
49
+
50
+ // 10. 无序列表(- item 或 * item)
51
+ html = html.replace(/^[\-\*] (.+)$/gm, '<li>$1</li>')
52
+ html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
53
+
54
+ // 11. 有序列表(1. item)
55
+ html = html.replace(/^\d+\. (.+)$/gm, '<li>$1</li>')
56
+
57
+ // 12. 引用(> text)
58
+ html = html.replace(/^&gt; (.+)$/gm, '<blockquote>$1</blockquote>')
59
+
60
+ // 13. 代码块(```code```)- 最后处理,恢复代码内容
61
+ html = html.replace(/&lt;pre&gt;&lt;code&gt;([\s\S]*?)&lt;\/code&gt;&lt;\/pre&gt;/g, function(match, code) {
62
+ return '<pre><code>' + code.trim().replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>') + '</code></pre>'
63
+ })
64
+
65
+ // 14. 行内代码(`code`)- 最后处理,恢复代码内容
66
+ html = html.replace(/&lt;code&gt;([\s\S]*?)&lt;\/code&gt;/g, function(match, code) {
67
+ return '<code>' + code.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>') + '</code>'
68
+ })
69
+
70
+ // 15. 换行
71
+ html = html.replace(/\n/g, '<br>')
72
+
73
+ // 16. 清理多余的空行
74
+ html = html.replace(/<br>\s*<br>/g, '<br>')
75
+
76
+ // 17. 存入缓存(限制缓存大小)
77
+ if (markdownCache.size >= MAX_CACHE_SIZE) {
78
+ // 删除最旧的缓存项
79
+ const firstKey = markdownCache.keys().next().value
80
+ markdownCache.delete(firstKey)
81
+ }
82
+ markdownCache.set(text, html)
83
+
84
+ return html
85
+ }
86
+
87
+ /**
88
+ * 清空缓存
89
+ */
90
+ function clearMarkdownCache() {
91
+ markdownCache.clear()
92
+ }
93
+
94
+ module.exports = {
95
+ parseMarkdown,
96
+ clearMarkdownCache
97
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 唯一 ID 生成器
3
+ * 避免消息 ID 冲突问题
4
+ */
5
+
6
+ let idCounter = 0
7
+
8
+ /**
9
+ * 生成唯一消息 ID
10
+ * 格式:{timestamp}-{counter}-{random}
11
+ * 例如:1713926400000-1-abc123
12
+ * @returns {string} 唯一 ID
13
+ */
14
+ function generateMessageId() {
15
+ const timestamp = Date.now()
16
+ const counter = ++idCounter
17
+ const random = Math.random().toString(36).substring(2, 8)
18
+ return `${timestamp}-${counter}-${random}`
19
+ }
20
+
21
+ module.exports = {
22
+ generateMessageId
23
+ }