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,433 @@
1
+ // AI Chat Dialog 微信小程序组件
2
+ const defaultConfig = require('../config/defaultConfig')
3
+ const AIChatClient = require('../core/AIChatClient')
4
+ const { parseMarkdown, clearMarkdownCache } = require('../utils/markdownParser')
5
+ const { generateMessageId } = require('../utils/messageIdGenerator')
6
+
7
+ Component({
8
+ options: {
9
+ styleIsolation: 'apply-shared'
10
+ },
11
+
12
+ properties: {
13
+ config: {
14
+ type: Object,
15
+ value: {}
16
+ }
17
+ },
18
+
19
+ data: {
20
+ dialogVisible: false,
21
+ inputMessage: '',
22
+ messages: [],
23
+ loading: false,
24
+ isExpanded: false,
25
+ conversationId: null,
26
+ isDragging: false,
27
+ dialogX: 0,
28
+ dialogY: 0,
29
+ isMobile: true,
30
+ touchStartX: 0,
31
+ touchStartY: 0,
32
+ touchStartDialogX: 0,
33
+ touchStartDialogY: 0,
34
+ screenWidth: 0,
35
+ mergedConfig: {},
36
+ dialogStyle: 'width: 100vw; height: 100vh; left: 0; top: 0;',
37
+ maxMessages: 50,
38
+ lastSendTime: 0,
39
+ scrollToView: '',
40
+ messageLength: 0,
41
+ maxLength: 500
42
+ },
43
+
44
+ lifetimes: {
45
+ attached() {
46
+ this.detectEnvironment()
47
+ this.updateMergedConfig()
48
+ this.updateDialogStyle()
49
+ }
50
+ },
51
+
52
+ observers: {
53
+ 'config': function(newVal) {
54
+ this.updateMergedConfig()
55
+ this.initClient()
56
+ }
57
+ },
58
+
59
+ methods: {
60
+ // 更新合并配置
61
+ updateMergedConfig() {
62
+ const merged = {
63
+ ...defaultConfig,
64
+ ...(this.data.config || {})
65
+ }
66
+ this.setData({ mergedConfig: merged })
67
+ },
68
+
69
+ // 更新弹窗样式
70
+ updateDialogStyle() {
71
+ let style = ''
72
+ if (this.data.isMobile) {
73
+ style = 'width: 100vw; height: 100vh; left: 0; top: 0;'
74
+ } else if (this.data.isExpanded) {
75
+ style = 'width: 800px; height: calc(100vh - 100px); left: calc(50% - 400px); top: 50px;'
76
+ } else {
77
+ const left = this.data.dialogX ? `${this.data.dialogX}px` : 'auto'
78
+ const top = this.data.dialogY ? `${this.data.dialogY}px` : 'auto'
79
+ const right = this.data.dialogX ? 'auto' : '0'
80
+ const bottom = this.data.dialogY ? 'auto' : '60px'
81
+ style = `width: 380px; height: 520px; left: ${left}; top: ${top}; right: ${right}; bottom: ${bottom};`
82
+ }
83
+ this.setData({ dialogStyle: style })
84
+ },
85
+
86
+ // 环境检测
87
+ detectEnvironment() {
88
+ try {
89
+ const systemInfo = wx.getSystemInfoSync()
90
+ this.setData({
91
+ screenWidth: systemInfo.windowWidth || systemInfo.screenWidth,
92
+ isMobile: true // 微信小程序默认移动端
93
+ })
94
+ console.log('[Environment] WeChat Mini Program detected, screenWidth:', systemInfo.windowWidth)
95
+ } catch (e) {
96
+ console.warn('[Environment] Failed to get system info:', e)
97
+ }
98
+ },
99
+
100
+ // 初始化客户端
101
+ initClient() {
102
+ const merged = this.data.mergedConfig
103
+ const config = {
104
+ apiEndpoint: merged.apiEndpoint,
105
+ agentAppId: merged.agentAppId,
106
+ userId: merged.userId,
107
+ bizType: merged.bizType,
108
+ token: merged.token,
109
+ timeout: merged.timeout,
110
+ headers: merged.headers,
111
+ extendInfo: merged.extendInfo,
112
+ authFn: merged.authFn
113
+ }
114
+
115
+ // 保存 chatClient 到组件实例(不在 data 中)
116
+ this.chatClient = new AIChatClient(config)
117
+ console.log('[Init] AI Chat Client initialized')
118
+ },
119
+
120
+ // 输入处理
121
+ onInput(e) {
122
+ const value = e.detail.value
123
+ const length = value.length
124
+
125
+ // 字数限制检查
126
+ if (length > this.data.maxLength) {
127
+ wx.showToast({
128
+ title: `最多输入${this.data.maxLength}个字`,
129
+ icon: 'none',
130
+ duration: 1500
131
+ })
132
+ return
133
+ }
134
+
135
+ this.setData({
136
+ inputMessage: value,
137
+ messageLength: length
138
+ })
139
+ },
140
+
141
+ // 发送消息
142
+ sendMessage() {
143
+ const message = this.data.inputMessage.trim()
144
+ if (!message || this.data.loading) return
145
+
146
+ const now = Date.now()
147
+ if (now - this.data.lastSendTime < 500) return
148
+ this.setData({ lastSendTime: now })
149
+
150
+ const msgId = generateMessageId()
151
+ const newMessages = [...this.data.messages, {
152
+ type: 'user',
153
+ content: message,
154
+ renderedContent: this.renderMarkdown(message),
155
+ time: this.formatTime(),
156
+ id: msgId
157
+ }]
158
+
159
+ if (newMessages.length > this.data.maxMessages) {
160
+ newMessages.splice(0, newMessages.length - this.data.maxMessages)
161
+ }
162
+
163
+ this.setData({
164
+ messages: newMessages,
165
+ inputMessage: '',
166
+ loading: true,
167
+ scrollToView: `msg-${msgId}`
168
+ })
169
+
170
+ this.triggerEvent('message-send', { message, index: newMessages.length - 1 })
171
+ this.callAI(message)
172
+ },
173
+
174
+ // 滚动到底部
175
+ scrollToBottom() {
176
+ const lastMsg = this.data.messages[this.data.messages.length - 1]
177
+ if (lastMsg) {
178
+ this.setData({
179
+ scrollToView: `msg-${lastMsg.id}`
180
+ })
181
+ }
182
+ },
183
+
184
+ // 调用 AI
185
+ callAI(message) {
186
+ if (!this.chatClient) {
187
+ this.initClient()
188
+ }
189
+
190
+ console.log('[AI] Calling AI with message:', message)
191
+
192
+ this.chatClient.sendMessage(message, this.data.mergedConfig.token)
193
+ .then(response => {
194
+ console.log('[AI] Response:', response)
195
+
196
+ if (response && response.success) {
197
+ const content = (response.data && (response.data.content || response.data.replyText)) || '暂无回复'
198
+ const aiMsgId = generateMessageId()
199
+
200
+ if (response.data && response.data.conversationId) {
201
+ this.chatClient.setConversationId(response.data.conversationId)
202
+ }
203
+
204
+ // 修复:使用最新的 messages(包含用户消息),而不是闭包中的旧快照
205
+ const currentMessages = this.data.messages
206
+ const newMessages = [...currentMessages, {
207
+ type: 'ai',
208
+ content: content,
209
+ time: this.formatTime(),
210
+ id: aiMsgId,
211
+ renderedContent: this.renderMarkdown(content)
212
+ }]
213
+
214
+ this.setData({
215
+ messages: newMessages,
216
+ loading: false,
217
+ scrollToView: `msg-${aiMsgId}`
218
+ })
219
+
220
+ this.triggerEvent('message-receive', { response, index: newMessages.length - 1 })
221
+ this.triggerEvent('loading-change', false)
222
+ } else {
223
+ const errorMsg = (response && response.message) || '请求失败'
224
+ const aiMsgId = generateMessageId()
225
+ // 修复:使用最新的 messages(包含用户消息),而不是闭包中的旧快照
226
+ const currentMessages = this.data.messages
227
+ const newMessages = [...currentMessages, {
228
+ type: 'ai',
229
+ content: '抱歉,请求失败:' + errorMsg,
230
+ time: this.formatTime(),
231
+ id: aiMsgId,
232
+ error: true,
233
+ renderedContent: this.renderMarkdown('抱歉,请求失败:' + errorMsg)
234
+ }]
235
+
236
+ this.setData({
237
+ messages: newMessages,
238
+ loading: false,
239
+ scrollToView: `msg-${aiMsgId}`
240
+ })
241
+
242
+ this.triggerEvent('loading-change', false)
243
+ }
244
+ })
245
+ .catch(error => {
246
+ console.error('[AI] Error:', error)
247
+
248
+ const errorMsg = (error && error.message) || error || '网络请求失败'
249
+ const aiMsgId = generateMessageId()
250
+ // 修复:使用最新的 messages(包含用户消息),而不是闭包中的旧快照
251
+ const currentMessages = this.data.messages
252
+ const newMessages = [...currentMessages, {
253
+ type: 'ai',
254
+ content: '抱歉,请求失败:' + errorMsg,
255
+ time: this.formatTime(),
256
+ id: aiMsgId,
257
+ error: true,
258
+ renderedContent: this.renderMarkdown('抱歉,请求失败:' + errorMsg)
259
+ }]
260
+
261
+ this.setData({
262
+ messages: newMessages,
263
+ loading: false,
264
+ scrollToView: `msg-${aiMsgId}`
265
+ })
266
+
267
+ this.triggerEvent('loading-change', false)
268
+
269
+ const merged = this.data.mergedConfig
270
+ if (merged && merged.onError) {
271
+ merged.onError(error)
272
+ }
273
+ })
274
+ },
275
+
276
+ formatTime() {
277
+ const now = new Date()
278
+ const hours = now.getHours().toString().padStart(2, '0')
279
+ const minutes = now.getMinutes().toString().padStart(2, '0')
280
+ return `${hours}:${minutes}`
281
+ },
282
+
283
+ renderMarkdown(text) {
284
+ if (!text) return ''
285
+ return parseMarkdown(text)
286
+ },
287
+
288
+ // 重试失败的请求
289
+ retryMessage(e) {
290
+ const { index } = e.currentTarget.dataset
291
+ if (this.data.loading) return
292
+
293
+ // 查找失败消息的前一条用户消息
294
+ let userQuestion = ''
295
+ for (let i = index - 1; i >= 0; i--) {
296
+ if (this.data.messages[i] && this.data.messages[i].type === 'user') {
297
+ userQuestion = this.data.messages[i].content
298
+ break
299
+ }
300
+ }
301
+
302
+ if (!userQuestion) {
303
+ wx.showToast({ title: '找不到原始消息', icon: 'none' })
304
+ return
305
+ }
306
+
307
+ // 删除失败的 AI 消息
308
+ const newMessages = this.data.messages.slice(0, index)
309
+ this.setData({
310
+ messages: newMessages,
311
+ loading: true
312
+ })
313
+
314
+ // 重新调用 AI
315
+ this.callAI(userQuestion)
316
+ },
317
+
318
+ startTouchDrag(e) {
319
+ if (this.data.isExpanded) return
320
+ const touch = e.touches[0]
321
+ this.setData({
322
+ isDragging: true,
323
+ touchStartX: touch.clientX,
324
+ touchStartY: touch.clientY,
325
+ touchStartDialogX: this.data.dialogX || 0,
326
+ touchStartDialogY: this.data.dialogY || 0
327
+ })
328
+ },
329
+
330
+ onTouchDrag(e) {
331
+ if (!this.data.isDragging) return
332
+ e.preventDefault()
333
+ const touch = e.touches[0]
334
+ this.setData({
335
+ dialogX: this.data.touchStartDialogX + (touch.clientX - this.data.touchStartX),
336
+ dialogY: this.data.touchStartDialogY + (touch.clientY - this.data.touchStartY)
337
+ })
338
+ },
339
+
340
+ endTouchDrag() {
341
+ this.setData({
342
+ isDragging: false
343
+ })
344
+ },
345
+
346
+ toggleExpand() {
347
+ const isExpanded = !this.data.isExpanded
348
+ this.setData({
349
+ isExpanded: isExpanded,
350
+ dialogX: isExpanded ? 0 : this.data.dialogX,
351
+ dialogY: isExpanded ? 0 : this.data.dialogY
352
+ })
353
+ this.updateDialogStyle()
354
+ this.triggerEvent('expand', isExpanded)
355
+ },
356
+
357
+ handleClose() {
358
+ this.setData({
359
+ dialogVisible: false,
360
+ isExpanded: false,
361
+ dialogX: 0,
362
+ dialogY: 0
363
+ })
364
+ this.triggerEvent('close')
365
+ },
366
+
367
+ openDialog() {
368
+ this.setData({
369
+ dialogVisible: true
370
+ })
371
+ },
372
+
373
+ copyMessage(e) {
374
+ const { content, index } = e.currentTarget.dataset
375
+ if (!content || index === undefined) return
376
+
377
+ wx.setClipboardData({
378
+ data: content,
379
+ success: () => {
380
+ wx.showToast({
381
+ title: '已复制',
382
+ icon: 'success',
383
+ duration: 2000
384
+ })
385
+ },
386
+ fail: (err) => {
387
+ console.error('[Copy] Failed:', err)
388
+ }
389
+ })
390
+ },
391
+
392
+ regenerate(e) {
393
+ const { index } = e.currentTarget.dataset
394
+ if (this.data.loading) return
395
+
396
+ let userQuestion = ''
397
+ for (let i = index - 1; i >= 0; i--) {
398
+ if (this.data.messages[i] && this.data.messages[i].type === 'user') {
399
+ userQuestion = this.data.messages[i].content
400
+ break
401
+ }
402
+ }
403
+
404
+ if (!userQuestion) return
405
+
406
+ const newMessages = this.data.messages.slice(0, index)
407
+
408
+ if (newMessages.length > this.data.maxMessages) {
409
+ newMessages.splice(0, newMessages.length - this.data.maxMessages)
410
+ }
411
+
412
+ this.setData({
413
+ messages: newMessages,
414
+ loading: true
415
+ })
416
+
417
+ this.callAI(userQuestion)
418
+ },
419
+
420
+ clearChat() {
421
+ this.setData({
422
+ messages: [],
423
+ conversationId: null
424
+ })
425
+ if (this.chatClient) {
426
+ this.chatClient.clearConversation()
427
+ }
428
+ // 清除 Markdown 缓存
429
+ clearMarkdownCache()
430
+ this.triggerEvent('clear')
431
+ }
432
+ }
433
+ })
@@ -0,0 +1,4 @@
1
+ {
2
+ "component": true,
3
+ "usingComponents": {}
4
+ }
@@ -0,0 +1,82 @@
1
+ <view class="ai-float-container" wx:if="{{dialogVisible}}">
2
+ <view class="ai-chat-dialog {{isExpanded ? 'expanded' : ''}} {{isDragging ? 'dragging' : ''}}"
3
+ style="{{dialogStyle}}">
4
+ <view class="dialog-header" bindtouchstart="startTouchDrag" bindtouchmove="onTouchDrag" bindtouchend="endTouchDrag">
5
+ <view class="header-left">
6
+ <text class="robot-icon">🤖</text>
7
+ <text class="header-title">{{mergedConfig.title}}</text>
8
+ </view>
9
+ <view class="header-right">
10
+ <view class="header-icon close-btn" bindtap="handleClose">
11
+ <text>✕</text>
12
+ </view>
13
+ </view>
14
+ </view>
15
+
16
+ <scroll-view class="dialog-body" scroll-y="true" scroll-into-view="{{scrollToView}}" scroll-with-animation>
17
+ <view wx:if="{{messages.length === 0}}" class="empty-state">
18
+ <view class="welcome-content">
19
+ <view class="welcome-icon">AI</view>
20
+ <text class="welcome-text">{{mergedConfig.welcomeText}}</text>
21
+ <text class="welcome-desc">{{mergedConfig.welcomeDesc}}</text>
22
+ </view>
23
+ </view>
24
+
25
+ <view wx:else class="message-list">
26
+ <view wx:for="{{messages}}" wx:key="id" class="message-item {{item.type}} {{item.error ? 'error' : ''}}">
27
+ <view class="message-avatar">
28
+ <text class="avatar-icon">{{item.type === 'user' ? '👤' : '🤖'}}</text>
29
+ </view>
30
+ <view class="message-content">
31
+ <view class="message-bubble {{item.error ? 'error' : ''}}">
32
+ <rich-text nodes="{{item.renderedContent}}"></rich-text>
33
+ </view>
34
+ <view class="message-actions-bar" wx:if="{{item.type === 'ai'}}">
35
+ <view class="action-icons">
36
+ <view class="action-icon" bindtap="copyMessage" data-content="{{item.content}}" data-index="{{index}}">
37
+ <text>⎘</text>
38
+ </view>
39
+ <view class="action-icon" bindtap="regenerate" data-index="{{index}}" wx:if="{{!item.error}}">
40
+ <text>↻</text>
41
+ </view>
42
+ <view class="action-icon retry" bindtap="retryMessage" data-index="{{index}}" wx:if="{{item.error}}">
43
+ <text>🔄</text>
44
+ <text class="retry-text">重试</text>
45
+ </view>
46
+ </view>
47
+ <text class="message-time">{{item.time}}</text>
48
+ </view>
49
+ </view>
50
+ </view>
51
+
52
+ <view wx:if="{{loading}}" class="message-item ai">
53
+ <view class="message-avatar"><text class="avatar-icon">🤖</text></view>
54
+ <view class="message-content">
55
+ <view class="message-bubble loading">
56
+ <text>⏳</text><text>思考中...</text>
57
+ </view>
58
+ </view>
59
+ </view>
60
+ </view>
61
+ </scroll-view>
62
+
63
+ <view class="dialog-footer">
64
+ <view class="input-container">
65
+ <input
66
+ class="message-input"
67
+ placeholder="请输入您的问题..."
68
+ value="{{inputMessage}}"
69
+ bindinput="onInput"
70
+ bindconfirm="sendMessage"
71
+ disabled="{{loading}}"
72
+ />
73
+ <view class="input-footer">
74
+ <text class="message-count">{{messageLength}}/{{maxLength}}</text>
75
+ <view class="send-btn {{loading || !inputMessage.trim() ? 'disabled' : ''}}" bindtap="sendMessage">
76
+ <text>➤</text>
77
+ </view>
78
+ </view>
79
+ </view>
80
+ </view>
81
+ </view>
82
+ </view>
@@ -0,0 +1,88 @@
1
+ /* 基础样式 */
2
+ .ai-float-container { position: fixed; bottom: 24rpx; right: 24rpx; z-index: 10000; }
3
+ .ai-chat-dialog { position: fixed; background: rgba(255, 255, 255, 0.98); border-radius: 32rpx; border: 2rpx solid rgba(24, 144, 255, 0.4); box-shadow: 0 0 60rpx rgba(24, 144, 255, 0.12), 0 16rpx 64rpx rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; overflow: hidden; }
4
+ .ai-chat-dialog:not(.expanded) { cursor: move; }
5
+ .ai-chat-dialog.dragging { transition: none; }
6
+
7
+ /* Header */
8
+ .dialog-header { display: flex; justify-content: space-between; align-items: center; padding: 28rpx 36rpx; background: linear-gradient(90deg, rgba(24, 144, 255, 0.08), rgba(114, 46, 209, 0.08)); border-bottom: 2rpx solid rgba(24, 144, 255, 0.15); position: relative; }
9
+ .dialog-header::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4rpx; background: linear-gradient(90deg, #1890ff, #722ed1); }
10
+ .header-left { display: flex; align-items: center; gap: 20rpx; }
11
+ .robot-icon { font-size: 44rpx; color: #1890ff; }
12
+ .header-title { font-size: 30rpx; font-weight: 600; background: linear-gradient(90deg, #1890ff, #722ed1); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
13
+ .header-right { display: flex; gap: 24rpx; align-items: center; }
14
+ .header-icon { display: flex; align-items: center; justify-content: center; font-size: 32rpx; color: #64748b; min-width: 88rpx; min-height: 88rpx; }
15
+ .close-btn { color: #ef4444; }
16
+
17
+ /* Body */
18
+ .dialog-body { flex: 1; overflow-y: auto; padding: 32rpx; background: linear-gradient(180deg, #f8fafc, #ffffff); min-height: 200rpx; box-sizing: border-box;}
19
+ .empty-state { height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; }
20
+ .welcome-content { margin-bottom: 48rpx; display: flex; flex-direction: column; align-items: center; }
21
+ .welcome-icon { width: 180rpx; height: 180rpx; border-radius: 50%; background: linear-gradient(135deg, #1890ff, #722ed1); display: flex; align-items: center; justify-content: center; font-size: 80rpx; font-weight: bold; color: #ffffff; margin-bottom: 32rpx; box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.3); }
22
+ .welcome-text { font-size: 34rpx; color: #1e293b; margin-bottom: 16rpx; font-weight: 500; }
23
+ .welcome-desc { font-size: 26rpx; color: #64748b; line-height: 1.6; }
24
+
25
+ /* Messages */
26
+ .message-list { display: flex; flex-direction: column; gap: 32rpx; }
27
+ .message-item { display: flex; gap: 20rpx; max-width: 85%; }
28
+ .message-item.user { flex-direction: row-reverse; align-self: flex-end; }
29
+ .message-item.ai { flex-direction: row; align-self: flex-start; }
30
+ .message-avatar { width: 68rpx; height: 68rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; flex-shrink: 0; }
31
+ .message-item.user .message-avatar { background: linear-gradient(135deg, #1890ff, #40a9ff); color: #fff; }
32
+ .message-item.ai .message-avatar { background: linear-gradient(135deg, #722ed1, #9254de); color: #fff; }
33
+ .message-content { display: flex; flex-direction: column; gap: 8rpx; }
34
+ .message-item.user .message-content { align-items: flex-end; }
35
+ .message-bubble { padding: 24rpx 32rpx; border-radius: 24rpx; font-size: 28rpx; line-height: 1.6; }
36
+ .message-item.user .message-bubble { background: linear-gradient(135deg, #1890ff, #40a9ff); color: #fff; border-bottom-right-radius: 8rpx; box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.25); }
37
+ .message-item.ai .message-bubble { background: #ffffff; color: #334155; border: 2rpx solid rgba(114, 46, 209, 0.15); border-bottom-left-radius: 8rpx; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06); }
38
+ .message-bubble.loading { display: flex; align-items: center; gap: 16rpx; color: #64748b; }
39
+ .message-bubble.loading text:first-child { animation: hourglass-spin 1.5s ease-in-out infinite; }
40
+ @keyframes hourglass-spin {
41
+ 0% { transform: rotate(0deg); }
42
+ 25% { transform: rotate(180deg); }
43
+ 50% { transform: rotate(180deg); }
44
+ 75% { transform: rotate(360deg); }
45
+ 100% { transform: rotate(360deg); }
46
+ }
47
+ .message-actions-bar { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; margin-top: 16rpx; padding-top: 16rpx; border-top: 2rpx solid rgba(0, 0, 0, 0.04); }
48
+ .message-time { font-size: 22rpx; color: #94a3b8; }
49
+ .action-icons { display: flex; gap: 16rpx; }
50
+ .action-icon { display: flex; align-items: center; justify-content: center; width: 56rpx; height: 56rpx; border-radius: 12rpx; color: #94a3b8; min-width: 88rpx; min-height: 88rpx; }
51
+ .action-icon.copied { color: #52c41a; }
52
+ .action-icon.retry { color: #f59e0b; background: rgba(245, 158, 11, 0.1); }
53
+ .action-icon.retry .retry-text { font-size: 20rpx; margin-left: 4rpx; }
54
+ .message-item.error .message-bubble { border: 2rpx solid rgba(239, 68, 68, 0.3); background: rgba(239, 68, 68, 0.05); }
55
+
56
+ /* Footer */
57
+ .dialog-footer { padding: 32rpx 36rpx 36rpx; background: #ffffff; }
58
+ .input-container { display: flex; flex-direction: column; align-items: center; background: #f8fafc; border: 2rpx solid #e2e8f0; border-radius: 56rpx; padding: 8rpx 8rpx 16rpx 36rpx; }
59
+ .message-input { flex: 1; border: none; background: transparent; font-size: 28rpx; color: #1e293b; padding: 16rpx 0; width: 100%; }
60
+ .input-footer { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 0 8rpx; }
61
+ .message-count { font-size: 22rpx; color: #94a3b8; }
62
+ .send-btn { flex-shrink: 0; width: 88rpx; height: 88rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #1890ff, #40a9ff); color: #fff; }
63
+ .send-btn.disabled { background: #e2e8f0; color: #94a3b8; }
64
+
65
+ /* Float Button */
66
+ .float-button { width: 112rpx; height: 112rpx; border-radius: 50%; background: linear-gradient(135deg, rgba(24, 144, 255, 0.1), rgba(114, 46, 209, 0.08)); border: 4rpx solid rgba(24, 144, 255, 0.25); display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 24rpx rgba(24, 144, 255, 0.12); }
67
+ .float-icon { width: 84rpx; height: auto; }
68
+
69
+ /* 移动端适配 */
70
+ @media screen and (max-width: 768px) {
71
+ .ai-float-container { bottom: 32rpx; right: 32rpx; }
72
+ .ai-chat-dialog { border-radius: 0 !important; }
73
+ .dialog-header { padding: 32rpx; padding-top: max(32rpx, env(safe-area-inset-top)); }
74
+ .header-title { font-size: 32rpx; }
75
+ .header-icon { min-width: 88rpx; min-height: 88rpx; }
76
+ .dialog-body { padding: 24rpx; }
77
+ .message-list { gap: 24rpx; }
78
+ .message-item { max-width: 90%; }
79
+ .message-bubble { padding: 20rpx 28rpx; font-size: 30rpx; }
80
+ .message-avatar { width: 64rpx; height: 64rpx; font-size: 30rpx; }
81
+ .welcome-icon { width: 140rpx; height: 140rpx; font-size: 70rpx; }
82
+ .welcome-text { font-size: 32rpx; }
83
+ .welcome-desc { font-size: 28rpx; }
84
+ .dialog-footer { padding: 24rpx; padding-bottom: max(24rpx, env(safe-area-inset-bottom)); }
85
+ .input-container { padding: 8rpx 8rpx 8rpx 28rpx; border-radius: 48rpx; }
86
+ .message-input { font-size: 32rpx; padding: 20rpx 0; }
87
+ .send-btn { width: 88rpx; height: 88rpx; min-width: 88rpx; min-height: 88rpx; }
88
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * 默认配置(微信小程序版本)
3
+ */
4
+ const defaultConfig = {
5
+ // API 配置(必须)
6
+ apiEndpoint: '/v1/app/completion',
7
+ appListEndpoint: '/v1/app/list',
8
+ runtimePermissionReplyEndpoint: '/v1/app/runtime/permission/reply',
9
+ runtimeQuestionReplyEndpoint: '/v1/app/runtime/question/reply',
10
+ runtimeQuestionRejectEndpoint: '/v1/app/runtime/question/reject',
11
+ appId: '',
12
+ agentAppId: '',
13
+ userId: '',
14
+ bizType: '',
15
+ requestId: '',
16
+ token: '',
17
+ timeout: 60000,
18
+ headers: {},
19
+ bizParams: null,
20
+ extendInfo: null,
21
+
22
+ // UI 配置
23
+ title: 'AI 智能助理',
24
+ welcomeText: '您好!我是 AI 智能助理',
25
+ welcomeDesc: '我可以帮您解答系统使用问题',
26
+
27
+ // 功能开关
28
+ enableDrag: true,
29
+
30
+ // 可选鉴权函数:SDK 将 token 和 userId 传入用户系统校验,通过后才允许调用智能体
31
+ // 返回 Promise<boolean> 或 Promise<void>,resolve 表示通过,reject/throw 表示拒绝
32
+ // 如果不配置此函数,则跳过鉴权直接调用
33
+ authFn: null,
34
+
35
+ // 事件回调
36
+ onLoad: null,
37
+ onError: null
38
+ }
39
+
40
+ module.exports = defaultConfig