@fzbykj/ai-chat-sdk-mp 1.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.
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@fzbykj/ai-chat-sdk-mp",
3
+ "version": "1.0.0",
4
+ "description": "AI 对话助手 SDK for 微信小程序 (uni-app)",
5
+ "main": "sdk/index.js",
6
+ "files": [
7
+ "sdk/**/*"
8
+ ],
9
+ "keywords": [
10
+ "ai",
11
+ "chat",
12
+ "wechat",
13
+ "miniprogram",
14
+ "uni-app"
15
+ ],
16
+ "author": "fzbykj",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/fzbykj/ai-chat-sdk-mp"
21
+ }
22
+ }
@@ -0,0 +1,269 @@
1
+ <!-- components/ai-float/ai-float.vue -->
2
+ <template>
3
+ <view class="ai-float-wrapper">
4
+ <!-- 折叠状态 -->
5
+ <view v-if="isCollapsed" class="float-collapsed" :style="collapsedStyleStr" @click="expand">
6
+ <text class="collapse-text">AI</text>
7
+ <text class="collapse-text">助</text>
8
+ <text class="collapse-text">手</text>
9
+ </view>
10
+
11
+ <!-- 正常状态 -->
12
+ <view v-else class="float-btn" :style="btnStyleStr" @touchstart="onTouchStart" @touchmove="onTouchMove"
13
+ @touchend="onTouchEnd" @click="openChat">
14
+ <image class="btn-icon" :src="iconUrl" mode="aspectFit" />
15
+ </view>
16
+ </view>
17
+ </template>
18
+
19
+ <script>
20
+ import {
21
+ aichat
22
+ } from '@/utils/sdk.js'
23
+
24
+ export default {
25
+ name: 'AiFloat',
26
+
27
+ props: {
28
+ apiKey: {
29
+ type: String,
30
+ default: ''
31
+ },
32
+ userId: {
33
+ type: String,
34
+ default: ''
35
+ },
36
+ baseUrl: {
37
+ type: String,
38
+ default: ''
39
+ }
40
+ },
41
+
42
+ data() {
43
+ return {
44
+ isCollapsed: false,
45
+ collapseTimer: null,
46
+
47
+ x: 0,
48
+ y: 0,
49
+ screenWidth: 0,
50
+ screenHeight: 0,
51
+
52
+ iconSize: 56,
53
+ collapsedWidth: 28,
54
+ collapsedHeight: 80,
55
+
56
+ startX: 0,
57
+ startY: 0,
58
+ startOffsetX: 0,
59
+ startOffsetY: 0,
60
+ isDragging: false,
61
+
62
+ _apiKey: '',
63
+ _userId: '',
64
+ _baseUrl: ''
65
+ }
66
+ },
67
+
68
+ computed: {
69
+ iconUrl() {
70
+ return '/static/icon.png'
71
+ },
72
+
73
+ // ✅ 返回字符串模板,确保类型正确
74
+ btnStyleStr() {
75
+ return `left: ${this.x}px; top: ${this.y}px; width: ${this.iconSize}px; height: ${this.iconSize}px; border-radius: ${this.iconSize / 2}px;`
76
+ },
77
+
78
+ collapsedStyleStr() {
79
+ const isLeft = this.x < this.screenWidth / 2
80
+ const leftPos = isLeft ? 0 : this.screenWidth - this.collapsedWidth
81
+ const radius = isLeft ? '0 12px 12px 0' : '12px 0 0 12px'
82
+ return `left: ${leftPos}px; top: ${this.y}px; width: ${this.collapsedWidth}px; height: ${this.collapsedHeight}px; border-radius: ${radius};`
83
+ }
84
+ },
85
+
86
+ mounted() {
87
+ this._apiKey = this.apiKey || aichat.getApiKey()
88
+ this._userId = this.userId || aichat.getUserId()
89
+ this._baseUrl = this.baseUrl || aichat.getBaseUrl()
90
+
91
+ const systemInfo = uni.getSystemInfoSync()
92
+ this.screenWidth = systemInfo.windowWidth
93
+ this.screenHeight = systemInfo.windowHeight
94
+
95
+ this.x = this.screenWidth - this.iconSize - 20
96
+ this.y = (this.screenHeight - this.iconSize) / 2
97
+ },
98
+
99
+ beforeDestroy() {
100
+ if (this.collapseTimer) {
101
+ clearTimeout(this.collapseTimer)
102
+ this.collapseTimer = null
103
+ }
104
+ },
105
+
106
+ methods: {
107
+ onTouchStart(e) {
108
+ const touch = e.touches[0]
109
+ this.startX = touch.clientX
110
+ this.startY = touch.clientY
111
+ this.startOffsetX = this.x
112
+ this.startOffsetY = this.y
113
+ this.isDragging = false
114
+
115
+ if (this.collapseTimer) {
116
+ clearTimeout(this.collapseTimer)
117
+ this.collapseTimer = null
118
+ }
119
+ },
120
+
121
+ onTouchMove(e) {
122
+ const touch = e.touches[0]
123
+ const deltaX = touch.clientX - this.startX
124
+ const deltaY = touch.clientY - this.startY
125
+
126
+ if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
127
+ this.isDragging = true
128
+ }
129
+
130
+ if (this.isDragging) {
131
+ let newX = this.startOffsetX + deltaX
132
+ let newY = this.startOffsetY + deltaY
133
+ newX = Math.max(0, Math.min(newX, this.screenWidth - this.iconSize))
134
+ newY = Math.max(0, Math.min(newY, this.screenHeight - this.iconSize))
135
+ this.x = newX
136
+ this.y = newY
137
+ }
138
+ },
139
+
140
+ onTouchEnd() {
141
+ if (!this.isDragging) return
142
+ this.isDragging = false
143
+
144
+ const currentWidth = this.isCollapsed ? this.collapsedWidth : this.iconSize
145
+ if (this.x < this.screenWidth / 2 - currentWidth / 2) {
146
+ this.x = 0
147
+ } else {
148
+ this.x = this.screenWidth - currentWidth
149
+ }
150
+
151
+ if (!this.isCollapsed) {
152
+ const isNearEdge = this.x < 20 || this.x > this.screenWidth - this.iconSize - 20
153
+ if (isNearEdge) {
154
+ if (this.collapseTimer) clearTimeout(this.collapseTimer)
155
+ this.collapseTimer = setTimeout(() => {
156
+ this.doCollapse()
157
+ }, 2000)
158
+ } else {
159
+ if (this.collapseTimer) {
160
+ clearTimeout(this.collapseTimer)
161
+ this.collapseTimer = null
162
+ }
163
+ }
164
+ }
165
+ },
166
+
167
+ doCollapse() {
168
+ if (this.isCollapsed) return
169
+ const isLeft = this.x < this.screenWidth / 2
170
+ this.x = isLeft ? 0 : this.screenWidth - this.collapsedWidth
171
+ const deltaY = (this.iconSize - this.collapsedHeight) / 2
172
+ this.y = this.y + deltaY
173
+ this.y = Math.max(0, Math.min(this.y, this.screenHeight - this.collapsedHeight))
174
+ this.isCollapsed = true
175
+ },
176
+
177
+ expand() {
178
+ if (!this.isCollapsed) return
179
+ const isLeft = this.x < this.screenWidth / 2
180
+ this.x = isLeft ? 0 : this.screenWidth - this.iconSize
181
+ const deltaY = (this.iconSize - this.collapsedHeight) / 2
182
+ this.y = this.y - deltaY
183
+ this.y = Math.max(0, Math.min(this.y, this.screenHeight - this.iconSize))
184
+ this.isCollapsed = false
185
+
186
+ this.$nextTick(() => {
187
+ if (this.x < 20 || this.x > this.screenWidth - this.iconSize - 20) {
188
+ if (this.collapseTimer) clearTimeout(this.collapseTimer)
189
+ this.collapseTimer = setTimeout(() => {
190
+ this.doCollapse()
191
+ }, 2000)
192
+ }
193
+ })
194
+ },
195
+
196
+ openChat() {
197
+ if (this.isCollapsed) {
198
+ this.expand()
199
+ return
200
+ }
201
+ if (this.isDragging) return
202
+
203
+ if (this.collapseTimer) {
204
+ clearTimeout(this.collapseTimer)
205
+ this.collapseTimer = null
206
+ }
207
+
208
+ const baseUrl = encodeURIComponent(this._baseUrl || aichat.getBaseUrl())
209
+ const apiKey = encodeURIComponent(this._apiKey || aichat.getApiKey())
210
+ const userId = encodeURIComponent(this._userId || aichat.getUserId())
211
+ console.log(baseUrl,apiKey,userId)
212
+ uni.navigateTo({
213
+ url: `/pages/chat/chat?baseUrl=${baseUrl}&apiKey=${apiKey}&userId=${userId}`
214
+ })
215
+ }
216
+ }
217
+ }
218
+ </script>
219
+
220
+ <style scoped>
221
+ .ai-float-wrapper {
222
+ position: fixed;
223
+ top: 0;
224
+ left: 0;
225
+ width: 100%;
226
+ height: 100%;
227
+ pointer-events: none;
228
+ z-index: 9999;
229
+ }
230
+
231
+ /* ✅ 所有动态尺寸由 :style 控制,这里只定义颜色、边框等静态属性 */
232
+ .float-btn {
233
+ position: fixed;
234
+ background: linear-gradient(135deg, #4A90D9, #357ABD);
235
+ box-shadow: 0 4px 12px rgba(74, 144, 217, 0.4);
236
+ display: flex;
237
+ align-items: center;
238
+ justify-content: center;
239
+ pointer-events: auto;
240
+ overflow: hidden;
241
+ }
242
+
243
+ .float-collapsed {
244
+ position: fixed;
245
+ background: linear-gradient(180deg, #4A90D9, #357ABD);
246
+ display: flex;
247
+ flex-direction: column;
248
+ align-items: center;
249
+ justify-content: center;
250
+ pointer-events: auto;
251
+ box-shadow: 0 2px 8px rgba(74, 144, 217, 0.3);
252
+ padding: 4px 0;
253
+ }
254
+
255
+ /* ✅ 图标图片样式 */
256
+ .btn-icon {
257
+ width: 90%;
258
+ height: 90%;
259
+ border-radius: 50%;
260
+ }
261
+
262
+ /* ✅ 折叠文字样式 */
263
+ .collapse-text {
264
+ color: #FFFFFF;
265
+ font-size: 11px;
266
+ font-weight: bold;
267
+ line-height: 1.5;
268
+ }
269
+ </style>
@@ -0,0 +1,8 @@
1
+ // components/ai-float/index.js
2
+ import AiFloat from './ai-float.vue'
3
+
4
+ // ✅ 两种导出方式都加上,确保兼容
5
+ export default AiFloat
6
+ export {
7
+ AiFloat
8
+ }
package/sdk/index.js ADDED
@@ -0,0 +1,21 @@
1
+ // sdk/index.js
2
+
3
+ // 导出 SDK 核心类
4
+ export {
5
+ aichat,
6
+ AIChatSDK
7
+ }
8
+ from './utils/sdk.js'
9
+
10
+ // 导出组件
11
+ export {
12
+ default as AiFloat
13
+ }
14
+ from './components/ai-float/index.js'
15
+
16
+ // 导出页面(提供给客户参考或直接使用)
17
+ // 注意:页面需要客户在 pages.json 中注册
18
+ export {
19
+ default as ChatPage
20
+ }
21
+ from './pages/chat/chat.vue'
@@ -0,0 +1,125 @@
1
+ <!-- pages/chat/chat.vue -->
2
+ <template>
3
+ <view class="chat-page">
4
+ <!-- 自定义导航栏 -->
5
+ <view class="custom-nav">
6
+ <view class="nav-back" @click="goBack">
7
+ <text class="back-icon">‹</text>
8
+ <text class="back-text">返回</text>
9
+ </view>
10
+ <text class="nav-title">AI 对话助手</text>
11
+ <view class="nav-placeholder"></view>
12
+ </view>
13
+
14
+ <!-- web-view 填充剩余高度 -->
15
+ <web-view class="webview" :src="webViewUrl" @load="onWebLoad" @error="onWebError" />
16
+ </view>
17
+ </template>
18
+
19
+ <script>
20
+ import {
21
+ aichat
22
+ } from '@/utils/sdk.js'
23
+
24
+ export default {
25
+ data() {
26
+ return {
27
+ webViewUrl: ''
28
+ }
29
+ },
30
+
31
+ onLoad(options) {
32
+ console.log(options)
33
+ // 从 URL 参数或 SDK 配置获取
34
+ // ✅ 对传入的参数进行解码
35
+ const baseUrl = options.baseUrl ? decodeURIComponent(options.baseUrl) : aichat.getBaseUrl()
36
+ const apiKey = options.apiKey ? decodeURIComponent(options.apiKey) : aichat.getApiKey()
37
+ const userId = options.userId ? decodeURIComponent(options.userId) : aichat.getUserId()
38
+ const separator = baseUrl.includes('?') ? '&' : '?'
39
+ this.webViewUrl = `${baseUrl}${separator}apiKey=${apiKey}&userId=${userId}&platform=wechat`
40
+ },
41
+
42
+ methods: {
43
+ // 返回上一页
44
+ goBack() {
45
+ // 检查是否能返回上一页
46
+ const pages = getCurrentPages()
47
+ if (pages.length > 1) {
48
+ // 有上一页,正常返回
49
+ uni.navigateBack()
50
+ } else {
51
+ // 没有上一页,跳转到首页
52
+ uni.switchTab({
53
+ url: '/pages/index/index'
54
+ })
55
+ }
56
+ },
57
+ onWebLoad() {
58
+ console.log('WebView 加载完成')
59
+ },
60
+
61
+ onWebError(e) {
62
+ console.error('WebView 加载失败', e)
63
+ }
64
+ },
65
+ }
66
+ </script>
67
+
68
+ <style scoped>
69
+ .chat-page {
70
+ width: 100%;
71
+ height: 100vh;
72
+ display: flex;
73
+ flex-direction: column;
74
+ background: #FFFFFF;
75
+ }
76
+
77
+ /* ===== 自定义导航栏 ===== */
78
+ .custom-nav {
79
+ height: 44px;
80
+ padding: 0 16px;
81
+ background: #F5F5F5;
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: space-between;
85
+ flex-shrink: 0;
86
+ border-bottom: 1px solid #E5E5E5;
87
+ }
88
+
89
+ .nav-back {
90
+ display: flex;
91
+ align-items: center;
92
+ padding: 4px 8px;
93
+ /* 点击区域放大 */
94
+ }
95
+
96
+ .back-icon {
97
+ font-size: 28px;
98
+ font-weight: 300;
99
+ color: #007AFF;
100
+ line-height: 1;
101
+ margin-right: 2px;
102
+ }
103
+
104
+ .back-text {
105
+ font-size: 16px;
106
+ color: #007AFF;
107
+ }
108
+
109
+ .nav-title {
110
+ font-size: 17px;
111
+ font-weight: 500;
112
+ color: #333333;
113
+ }
114
+
115
+ .nav-placeholder {
116
+ width: 60px;
117
+ /* 与返回按钮宽度对称,保持标题居中 */
118
+ }
119
+
120
+ /* ===== WebView ===== */
121
+ .webview {
122
+ flex: 1;
123
+ width: 100%;
124
+ }
125
+ </style>
Binary file
@@ -0,0 +1,69 @@
1
+ // utils/sdk.js
2
+
3
+ /**
4
+ * AI 对话助手 SDK 核心类
5
+ * 单例模式管理配置
6
+ */
7
+ class AIChatSDK {
8
+ constructor() {
9
+ this.config = {
10
+ apiKey: '',
11
+ userId: '',
12
+ baseUrl: 'https://agent.fzbykj.com/app_chat.html',
13
+ enableLog: false
14
+ }
15
+ }
16
+
17
+ static getInstance() {
18
+ if (!AIChatSDK.instance) {
19
+ AIChatSDK.instance = new AIChatSDK()
20
+ }
21
+ return AIChatSDK.instance
22
+ }
23
+
24
+ /**
25
+ * 初始化 SDK
26
+ * @param {string} apiKey - API Key
27
+ * @param {string} userId - 用户 ID
28
+ * @param {string} baseUrl - 可选,自定义 H5 地址
29
+ */
30
+ init(apiKey, userId, baseUrl) {
31
+ this.config.apiKey = apiKey
32
+ this.config.userId = userId
33
+ if (baseUrl) {
34
+ this.config.baseUrl = baseUrl
35
+ }
36
+ this.log('SDK 初始化完成')
37
+ return this
38
+ }
39
+
40
+ /**
41
+ * 开启/关闭日志
42
+ */
43
+ setLogEnabled(enabled) {
44
+ this.config.enableLog = enabled
45
+ return this
46
+ }
47
+
48
+ getApiKey() {
49
+ return this.config.apiKey
50
+ }
51
+
52
+ getUserId() {
53
+ return this.config.userId
54
+ }
55
+
56
+ getBaseUrl() {
57
+ return this.config.baseUrl || 'https://agent.fzbykj.com/app_chat.html'
58
+ }
59
+
60
+ log(message) {
61
+ if (this.config.enableLog) {
62
+ console.log('[AIChatSDK] ' + message)
63
+ }
64
+ }
65
+ }
66
+
67
+ // 导出单例
68
+ export const aichat = AIChatSDK.getInstance()
69
+ export default aichat