tgo-widget-miniprogram 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.
Files changed (55) hide show
  1. package/README.md +81 -0
  2. package/miniprogram_dist/adapters/request.js +38 -0
  3. package/miniprogram_dist/adapters/storage.js +42 -0
  4. package/miniprogram_dist/adapters/systemInfo.js +22 -0
  5. package/miniprogram_dist/chat/index.js +164 -0
  6. package/miniprogram_dist/chat/index.json +7 -0
  7. package/miniprogram_dist/chat/index.wxml +48 -0
  8. package/miniprogram_dist/chat/index.wxss +92 -0
  9. package/miniprogram_dist/components/json-render-element/index.js +374 -0
  10. package/miniprogram_dist/components/json-render-element/index.json +6 -0
  11. package/miniprogram_dist/components/json-render-element/index.wxml +218 -0
  12. package/miniprogram_dist/components/json-render-element/index.wxss +450 -0
  13. package/miniprogram_dist/components/json-render-message/index.js +89 -0
  14. package/miniprogram_dist/components/json-render-message/index.json +7 -0
  15. package/miniprogram_dist/components/json-render-message/index.wxml +25 -0
  16. package/miniprogram_dist/components/json-render-message/index.wxss +26 -0
  17. package/miniprogram_dist/components/json-render-surface/index.js +116 -0
  18. package/miniprogram_dist/components/json-render-surface/index.json +6 -0
  19. package/miniprogram_dist/components/json-render-surface/index.wxml +10 -0
  20. package/miniprogram_dist/components/json-render-surface/index.wxss +6 -0
  21. package/miniprogram_dist/components/markdown-text/index.js +23 -0
  22. package/miniprogram_dist/components/markdown-text/index.json +3 -0
  23. package/miniprogram_dist/components/markdown-text/index.wxml +1 -0
  24. package/miniprogram_dist/components/markdown-text/index.wxss +6 -0
  25. package/miniprogram_dist/components/message-bubble/index.js +12 -0
  26. package/miniprogram_dist/components/message-bubble/index.json +3 -0
  27. package/miniprogram_dist/components/message-bubble/index.wxml +3 -0
  28. package/miniprogram_dist/components/message-bubble/index.wxss +17 -0
  29. package/miniprogram_dist/components/message-input/index.js +76 -0
  30. package/miniprogram_dist/components/message-input/index.json +3 -0
  31. package/miniprogram_dist/components/message-input/index.wxml +28 -0
  32. package/miniprogram_dist/components/message-input/index.wxss +56 -0
  33. package/miniprogram_dist/components/message-list/index.js +113 -0
  34. package/miniprogram_dist/components/message-list/index.json +9 -0
  35. package/miniprogram_dist/components/message-list/index.wxml +108 -0
  36. package/miniprogram_dist/components/message-list/index.wxss +113 -0
  37. package/miniprogram_dist/components/system-message/index.js +8 -0
  38. package/miniprogram_dist/components/system-message/index.json +3 -0
  39. package/miniprogram_dist/components/system-message/index.wxml +3 -0
  40. package/miniprogram_dist/components/system-message/index.wxss +15 -0
  41. package/miniprogram_dist/core/chatStore.js +758 -0
  42. package/miniprogram_dist/core/i18n.js +66 -0
  43. package/miniprogram_dist/core/platformStore.js +86 -0
  44. package/miniprogram_dist/core/types.js +192 -0
  45. package/miniprogram_dist/services/chat.js +67 -0
  46. package/miniprogram_dist/services/messageHistory.js +46 -0
  47. package/miniprogram_dist/services/platform.js +27 -0
  48. package/miniprogram_dist/services/upload.js +74 -0
  49. package/miniprogram_dist/services/visitor.js +67 -0
  50. package/miniprogram_dist/services/wukongim.js +183 -0
  51. package/miniprogram_dist/utils/jsonRender.js +158 -0
  52. package/miniprogram_dist/utils/markdown.js +31 -0
  53. package/miniprogram_dist/utils/time.js +85 -0
  54. package/miniprogram_dist/utils/uid.js +11 -0
  55. package/package.json +37 -0
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # tgo-widget-miniprogram
2
+
3
+ TGO 访客聊天微信小程序组件。开发者 3 步完成接入。
4
+
5
+ ## 快速接入
6
+
7
+ ### 1. 安装
8
+
9
+ ```bash
10
+ npm install tgo-widget-miniprogram
11
+ ```
12
+
13
+ 在微信开发者工具中点击 **工具 → 构建 npm**。
14
+
15
+ ### 2. 页面配置
16
+
17
+ ```json
18
+ // pages/chat/chat.json
19
+ {
20
+ "usingComponents": {
21
+ "tgo-chat": "tgo-widget-miniprogram/chat/index"
22
+ },
23
+ "navigationBarTitleText": "在线客服"
24
+ }
25
+ ```
26
+
27
+ ### 3. 使用组件
28
+
29
+ ```xml
30
+ <!-- pages/chat/chat.wxml -->
31
+ <tgo-chat api-key="ak_live_xxx" api-base="https://your-api.com" />
32
+ ```
33
+
34
+ ### 跳转到客服页
35
+
36
+ ```js
37
+ wx.navigateTo({ url: '/pages/chat/chat' })
38
+ ```
39
+
40
+ ## 域名白名单
41
+
42
+ 在小程序管理后台配置以下域名:
43
+
44
+ | 类型 | 域名 |
45
+ |------|------|
46
+ | request 合法域名 | API 服务域名 |
47
+ | socket 合法域名 | WuKongIM WSS 域名 |
48
+ | uploadFile 合法域名 | API 服务域名 |
49
+ | downloadFile 合法域名 | API 服务域名 |
50
+
51
+ ## 组件属性
52
+
53
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
54
+ |------|------|------|--------|------|
55
+ | `api-key` | String | 是 | — | 平台 API Key |
56
+ | `api-base` | String | 是 | — | API 服务地址 |
57
+ | `theme-color` | String | 否 | `#2f80ed` | 主题色 |
58
+ | `lang` | String | 否 | `zh` | 语言(zh/en) |
59
+ | `visitor-name` | String | 否 | — | 访客名称 |
60
+ | `visitor-avatar` | String | 否 | — | 访客头像 URL |
61
+ | `custom-attrs` | Object | 否 | — | 自定义访客属性 |
62
+
63
+ ## 功能支持
64
+
65
+ - 文本消息收发
66
+ - AI 流式回复(实时逐字显示)
67
+ - 图片消息发送与预览
68
+ - 历史消息加载(上拉加载更多)
69
+ - 系统消息展示(客服接入/转接/结束)
70
+ - Markdown 渲染(加粗、斜体、链接、列表、代码块)
71
+ - 多语言支持(中文/英文)
72
+ - IM 断线自动重连
73
+
74
+ ## 开发
75
+
76
+ ```bash
77
+ # 构建 miniprogram_dist
78
+ npm run build
79
+ ```
80
+
81
+ 构建产物位于 `miniprogram_dist/`,微信开发者工具通过 `package.json` 的 `miniprogram` 字段读取此目录。
@@ -0,0 +1,38 @@
1
+ /**
2
+ * wx.request Promise adapter (replaces fetch)
3
+ *
4
+ * Returns: { ok, status, statusText, data, json(), text(), headers }
5
+ */
6
+ function request({ url, method, headers, body, timeout }) {
7
+ return new Promise(function (resolve, reject) {
8
+ wx.request({
9
+ url: url,
10
+ method: (method || 'GET').toUpperCase(),
11
+ header: headers || {},
12
+ data: body,
13
+ timeout: timeout || 15000,
14
+ dataType: 'json',
15
+ success: function (res) {
16
+ var ok = res.statusCode >= 200 && res.statusCode < 300
17
+ resolve({
18
+ ok: ok,
19
+ status: res.statusCode,
20
+ statusText: ok ? 'OK' : 'Error',
21
+ data: res.data,
22
+ headers: res.header || {},
23
+ json: function () { return Promise.resolve(res.data) },
24
+ text: function () {
25
+ return Promise.resolve(
26
+ typeof res.data === 'string' ? res.data : JSON.stringify(res.data)
27
+ )
28
+ }
29
+ })
30
+ },
31
+ fail: function (err) {
32
+ reject(new Error('[request] ' + (err.errMsg || 'network error')))
33
+ }
34
+ })
35
+ })
36
+ }
37
+
38
+ module.exports = { request }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * wx storage adapter (replaces localStorage)
3
+ * Supports optional TTL expiry, same format as Web version.
4
+ */
5
+
6
+ function setJSON(key, value, ttlMs) {
7
+ var rec = { v: value }
8
+ if (ttlMs && ttlMs > 0) {
9
+ rec.e = Date.now() + ttlMs
10
+ }
11
+ try {
12
+ wx.setStorageSync(key, JSON.stringify(rec))
13
+ } catch (e) {
14
+ console.warn('[storage] setJSON failed:', key, e)
15
+ }
16
+ }
17
+
18
+ function getJSON(key) {
19
+ try {
20
+ var raw = wx.getStorageSync(key)
21
+ if (!raw) return null
22
+ var rec = JSON.parse(raw)
23
+ if (rec && typeof rec === 'object') {
24
+ if (rec.e && Date.now() > rec.e) {
25
+ wx.removeStorageSync(key)
26
+ return null
27
+ }
28
+ return rec.v
29
+ }
30
+ } catch (e) {
31
+ try { wx.removeStorageSync(key) } catch (_) {}
32
+ }
33
+ return null
34
+ }
35
+
36
+ function remove(key) {
37
+ try {
38
+ wx.removeStorageSync(key)
39
+ } catch (e) {}
40
+ }
41
+
42
+ module.exports = { setJSON: setJSON, getJSON: getJSON, remove: remove }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * wx system info adapter (replaces navigator.userAgent)
3
+ */
4
+
5
+ function collectSystemInfo() {
6
+ try {
7
+ var info = wx.getSystemInfoSync()
8
+ return {
9
+ browser: 'WeChat MiniProgram',
10
+ operating_system: (info.platform || '') + ' ' + (info.system || ''),
11
+ source_detail: 'WeChat ' + (info.version || '') + ', SDK ' + (info.SDKVersion || '')
12
+ }
13
+ } catch (e) {
14
+ return {
15
+ browser: 'WeChat MiniProgram',
16
+ operating_system: '',
17
+ source_detail: ''
18
+ }
19
+ }
20
+ }
21
+
22
+ module.exports = { collectSystemInfo: collectSystemInfo }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * <tgo-chat> main component
3
+ *
4
+ * Usage:
5
+ * <tgo-chat api-key="ak_live_xxx" api-base="https://your-api.com" />
6
+ */
7
+ var chatStore = require('../core/chatStore')
8
+ var platformStore = require('../core/platformStore')
9
+ var i18n = require('../core/i18n')
10
+
11
+ Component({
12
+ options: {
13
+ multipleSlots: true
14
+ },
15
+
16
+ properties: {
17
+ apiKey: {
18
+ type: String,
19
+ value: ''
20
+ },
21
+ apiBase: {
22
+ type: String,
23
+ value: ''
24
+ },
25
+ themeColor: {
26
+ type: String,
27
+ value: ''
28
+ },
29
+ lang: {
30
+ type: String,
31
+ value: 'zh'
32
+ },
33
+ visitorName: {
34
+ type: String,
35
+ value: ''
36
+ },
37
+ visitorAvatar: {
38
+ type: String,
39
+ value: ''
40
+ },
41
+ customAttrs: {
42
+ type: Object,
43
+ value: null
44
+ }
45
+ },
46
+
47
+ data: {
48
+ messages: [],
49
+ isStreaming: false,
50
+ streamCanceling: false,
51
+ online: false,
52
+ historyLoading: false,
53
+ historyHasMore: true,
54
+ initializing: false,
55
+ error: null,
56
+ resolvedThemeColor: '#2f80ed',
57
+ title: 'Tgo',
58
+ logoUrl: ''
59
+ },
60
+
61
+ lifetimes: {
62
+ attached: function () {
63
+ // Set i18n language
64
+ i18n.setLang(this.properties.lang)
65
+
66
+ // Subscribe to stores
67
+ this._bindStores()
68
+
69
+ // Initialize
70
+ this._init()
71
+ },
72
+
73
+ detached: function () {
74
+ if (this._unsubChat) { this._unsubChat(); this._unsubChat = null }
75
+ if (this._unsubPlatform) { this._unsubPlatform(); this._unsubPlatform = null }
76
+ chatStore.disconnect()
77
+ }
78
+ },
79
+
80
+ methods: {
81
+ _bindStores: function () {
82
+ var self = this
83
+
84
+ // Subscribe to chat store
85
+ this._unsubChat = chatStore.subscribe(function (state) {
86
+ self.setData({
87
+ messages: state.messages,
88
+ isStreaming: state.isStreaming,
89
+ streamCanceling: state.streamCanceling,
90
+ online: state.online,
91
+ historyLoading: state.historyLoading,
92
+ historyHasMore: state.historyHasMore,
93
+ initializing: state.initializing,
94
+ error: state.error
95
+ })
96
+ })
97
+
98
+ // Subscribe to platform store
99
+ this._unsubPlatform = platformStore.subscribe(function (state) {
100
+ var cfg = state.config || {}
101
+ self.setData({
102
+ title: cfg.widget_title || 'Tgo',
103
+ logoUrl: cfg.logo_url || '',
104
+ resolvedThemeColor: self.properties.themeColor || cfg.theme_color || '#2f80ed'
105
+ })
106
+
107
+ // Inject welcome message
108
+ if (cfg.welcome_message && !state.welcomeInjected) {
109
+ chatStore.ensureWelcomeMessage(cfg.welcome_message)
110
+ platformStore.markWelcomeInjected()
111
+ }
112
+ })
113
+ },
114
+
115
+ _init: function () {
116
+ var apiBase = this.properties.apiBase
117
+ var apiKey = this.properties.apiKey
118
+
119
+ if (!apiBase || !apiKey) {
120
+ console.error('[tgo-chat] api-base and api-key are required')
121
+ return
122
+ }
123
+
124
+ // Init platform config
125
+ platformStore.init(apiBase, apiKey)
126
+
127
+ // Set resolved theme color
128
+ this.setData({
129
+ resolvedThemeColor: this.properties.themeColor || '#2f80ed'
130
+ })
131
+
132
+ // Init chat IM
133
+ chatStore.initIM({
134
+ apiBase: apiBase,
135
+ platformApiKey: apiKey
136
+ })
137
+ },
138
+
139
+ // Event handlers from child components
140
+ onSendMessage: function (e) {
141
+ var text = e.detail && e.detail.text
142
+ if (text) {
143
+ chatStore.sendMessage(text)
144
+ }
145
+ },
146
+
147
+ onCancelStream: function () {
148
+ chatStore.cancelStreaming('user_cancel')
149
+ },
150
+
151
+ onLoadMore: function () {
152
+ if (!chatStore.getState().historyLoading && chatStore.getState().historyHasMore) {
153
+ chatStore.loadMoreHistory(20)
154
+ }
155
+ },
156
+
157
+ onChooseImage: function (e) {
158
+ var tempFilePath = e.detail && e.detail.tempFilePath
159
+ if (tempFilePath) {
160
+ chatStore.uploadImage(tempFilePath)
161
+ }
162
+ }
163
+ }
164
+ })
@@ -0,0 +1,7 @@
1
+ {
2
+ "component": true,
3
+ "usingComponents": {
4
+ "message-list": "../components/message-list/index",
5
+ "message-input": "../components/message-input/index"
6
+ }
7
+ }
@@ -0,0 +1,48 @@
1
+ <view class="tgo-chat">
2
+ <!-- Header -->
3
+ <view class="chat-header" style="background-color: {{resolvedThemeColor}};">
4
+ <image wx:if="{{logoUrl}}" class="header-logo" src="{{logoUrl}}" mode="aspectFit" />
5
+ <text class="header-title">{{title}}</text>
6
+ <view wx:if="{{online}}" class="status-dot online"></view>
7
+ <view wx:else class="status-dot offline"></view>
8
+ </view>
9
+
10
+ <!-- Initializing state -->
11
+ <view wx:if="{{initializing}}" class="init-loading">
12
+ <text class="init-text">连接中...</text>
13
+ </view>
14
+
15
+ <!-- Error state -->
16
+ <view wx:elif="{{error && !messages.length}}" class="init-error">
17
+ <text class="error-text">{{error}}</text>
18
+ <view class="retry-btn" bindtap="_init">
19
+ <text class="retry-text">重试</text>
20
+ </view>
21
+ </view>
22
+
23
+ <!-- Chat body -->
24
+ <view wx:else class="chat-body">
25
+ <message-list
26
+ messages="{{messages}}"
27
+ historyLoading="{{historyLoading}}"
28
+ historyHasMore="{{historyHasMore}}"
29
+ isStreaming="{{isStreaming}}"
30
+ themeColor="{{resolvedThemeColor}}"
31
+ bind:loadmore="onLoadMore"
32
+ bind:sendmessage="onSendMessage"
33
+ />
34
+ </view>
35
+
36
+ <!-- Input bar -->
37
+ <view class="chat-footer">
38
+ <message-input
39
+ isStreaming="{{isStreaming}}"
40
+ streamCanceling="{{streamCanceling}}"
41
+ themeColor="{{resolvedThemeColor}}"
42
+ disabled="{{initializing}}"
43
+ bind:send="onSendMessage"
44
+ bind:cancel="onCancelStream"
45
+ bind:image="onChooseImage"
46
+ />
47
+ </view>
48
+ </view>
@@ -0,0 +1,92 @@
1
+ .tgo-chat {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100vh;
5
+ background: #fff;
6
+ }
7
+
8
+ /* Header */
9
+ .chat-header {
10
+ display: flex;
11
+ align-items: center;
12
+ padding: 0 24rpx;
13
+ height: 88rpx;
14
+ flex-shrink: 0;
15
+ }
16
+
17
+ .header-logo {
18
+ width: 48rpx;
19
+ height: 48rpx;
20
+ border-radius: 8rpx;
21
+ margin-right: 12rpx;
22
+ }
23
+
24
+ .header-title {
25
+ font-size: 32rpx;
26
+ color: #fff;
27
+ font-weight: 600;
28
+ flex: 1;
29
+ }
30
+
31
+ .status-dot {
32
+ width: 16rpx;
33
+ height: 16rpx;
34
+ border-radius: 50%;
35
+ margin-left: 12rpx;
36
+ }
37
+
38
+ .status-dot.online {
39
+ background: #4cd964;
40
+ box-shadow: 0 0 4rpx rgba(76, 217, 100, 0.6);
41
+ }
42
+
43
+ .status-dot.offline {
44
+ background: #ccc;
45
+ }
46
+
47
+ /* Loading / Error states */
48
+ .init-loading,
49
+ .init-error {
50
+ flex: 1;
51
+ display: flex;
52
+ flex-direction: column;
53
+ align-items: center;
54
+ justify-content: center;
55
+ }
56
+
57
+ .init-text {
58
+ font-size: 28rpx;
59
+ color: #999;
60
+ }
61
+
62
+ .error-text {
63
+ font-size: 26rpx;
64
+ color: #e74c3c;
65
+ text-align: center;
66
+ padding: 0 40rpx;
67
+ margin-bottom: 24rpx;
68
+ }
69
+
70
+ .retry-btn {
71
+ padding: 12rpx 48rpx;
72
+ background: #f0f0f0;
73
+ border-radius: 32rpx;
74
+ }
75
+
76
+ .retry-text {
77
+ font-size: 28rpx;
78
+ color: #333;
79
+ }
80
+
81
+ /* Chat body */
82
+ .chat-body {
83
+ flex: 1;
84
+ overflow: hidden;
85
+ }
86
+
87
+ /* Footer */
88
+ .chat-footer {
89
+ flex-shrink: 0;
90
+ padding-bottom: env(safe-area-inset-bottom);
91
+ background: #fff;
92
+ }