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
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Simple i18n module (zh/en)
3
+ */
4
+ var locales = {
5
+ zh: {
6
+ 'common.retry': '重试',
7
+ 'common.cancel': '取消',
8
+ 'common.loading': '加载中…',
9
+ 'messageInput.placeholder': '提出问题...',
10
+ 'messageInput.send': '发送',
11
+ 'messageInput.interrupt': '中断',
12
+ 'messageList.loadingHistory': '加载中…',
13
+ 'messageList.noMoreMessages': '没有更多消息',
14
+ 'messageList.sending': '发送中…',
15
+ 'messageList.uploading': '上传中…',
16
+ 'errors.authFail': '认证失败,无法发送消息',
17
+ 'errors.networkError': '网络异常或超时,请检查网络后重试',
18
+ 'errors.systemError': '系统错误,请稍后重试',
19
+ 'system.staffAssigned': '已为您接入人工客服,客服 {name0} 为您服务。',
20
+ 'system.sessionTransferred': '会话已转接。客服 {name0} 已将您转接给客服 {name1}。',
21
+ 'system.sessionClosed': '会话已结束。客服 {name0} 已完成本次服务。',
22
+ 'system.sessionClosedNoAgent': '会话已结束。'
23
+ },
24
+ en: {
25
+ 'common.retry': 'Retry',
26
+ 'common.cancel': 'Cancel',
27
+ 'common.loading': 'Loading...',
28
+ 'messageInput.placeholder': 'Ask a question...',
29
+ 'messageInput.send': 'Send',
30
+ 'messageInput.interrupt': 'Stop',
31
+ 'messageList.loadingHistory': 'Loading...',
32
+ 'messageList.noMoreMessages': 'No more messages',
33
+ 'messageList.sending': 'Sending...',
34
+ 'messageList.uploading': 'Uploading...',
35
+ 'errors.authFail': 'Authentication failed, cannot send message',
36
+ 'errors.networkError': 'Network error or timeout, please check your connection',
37
+ 'errors.systemError': 'System error, please try again later',
38
+ 'system.staffAssigned': 'You have been connected to customer service. Agent {name0} will assist you.',
39
+ 'system.sessionTransferred': 'Session transferred. Agent {name0} has transferred you to Agent {name1}.',
40
+ 'system.sessionClosed': 'Session ended. Agent {name0} has completed the service.',
41
+ 'system.sessionClosedNoAgent': 'Session ended.'
42
+ }
43
+ }
44
+
45
+ var currentLang = 'zh'
46
+
47
+ function setLang(lang) {
48
+ currentLang = (lang === 'en') ? 'en' : 'zh'
49
+ }
50
+
51
+ function getLang() {
52
+ return currentLang
53
+ }
54
+
55
+ function t(key, params) {
56
+ var dict = locales[currentLang] || locales.zh
57
+ var str = dict[key] || key
58
+ if (params) {
59
+ Object.keys(params).forEach(function (k) {
60
+ str = str.replace(new RegExp('\\{' + k + '\\}', 'g'), params[k])
61
+ })
62
+ }
63
+ return str
64
+ }
65
+
66
+ module.exports = { setLang: setLang, getLang: getLang, t: t }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Platform configuration store (Observable singleton)
3
+ */
4
+ var platformService = require('../services/platform')
5
+ var storage = require('../adapters/storage')
6
+
7
+ var WELCOME_KEY = function (apiBase, platformApiKey) {
8
+ return 'tgo:welcome-shown:' + apiBase + ':' + platformApiKey
9
+ }
10
+
11
+ var defaultConfig = {
12
+ position: 'bottom-right',
13
+ theme_color: '#2f80ed',
14
+ widget_title: 'Tgo',
15
+ welcome_message: undefined,
16
+ logo_url: undefined
17
+ }
18
+
19
+ function PlatformStore() {
20
+ this._state = {
21
+ loading: false,
22
+ error: null,
23
+ config: Object.assign({}, defaultConfig),
24
+ welcomeInjected: false,
25
+ _apiBase: '',
26
+ _platformApiKey: ''
27
+ }
28
+ this._listeners = []
29
+ }
30
+
31
+ PlatformStore.prototype.getState = function () {
32
+ return this._state
33
+ }
34
+
35
+ PlatformStore.prototype._setState = function (partial) {
36
+ Object.assign(this._state, partial)
37
+ var state = this._state
38
+ this._listeners.forEach(function (fn) { try { fn(state) } catch (e) {} })
39
+ }
40
+
41
+ PlatformStore.prototype.subscribe = function (fn) {
42
+ this._listeners.push(fn)
43
+ var self = this
44
+ return function () {
45
+ var idx = self._listeners.indexOf(fn)
46
+ if (idx >= 0) self._listeners.splice(idx, 1)
47
+ }
48
+ }
49
+
50
+ PlatformStore.prototype.markWelcomeInjected = function () {
51
+ this._setState({ welcomeInjected: true })
52
+ try {
53
+ var apiBase = this._state._apiBase
54
+ var apiKey = this._state._platformApiKey
55
+ if (apiBase && apiKey) storage.setJSON(WELCOME_KEY(apiBase, apiKey), true)
56
+ } catch (e) {}
57
+ }
58
+
59
+ PlatformStore.prototype.init = function (apiBase, platformApiKey) {
60
+ if (!apiBase || !platformApiKey) return Promise.resolve()
61
+ if (this._state.loading) return Promise.resolve()
62
+ var self = this
63
+ this._setState({ loading: true, error: null, _apiBase: apiBase, _platformApiKey: platformApiKey })
64
+
65
+ // Read welcome injected from storage
66
+ try {
67
+ var injected = !!storage.getJSON(WELCOME_KEY(apiBase, platformApiKey))
68
+ if (injected) self._setState({ welcomeInjected: true })
69
+ } catch (e) {}
70
+
71
+ return platformService.fetchPlatformInfo({
72
+ apiBase: apiBase,
73
+ platformApiKey: platformApiKey
74
+ }).then(function (info) {
75
+ var cfg = (info && info.config) || {}
76
+ self._setState({
77
+ config: Object.assign({}, self._state.config, cfg)
78
+ })
79
+ }).catch(function (e) {
80
+ self._setState({ error: e && e.message ? e.message : String(e) })
81
+ }).then(function () {
82
+ self._setState({ loading: false })
83
+ })
84
+ }
85
+
86
+ module.exports = new PlatformStore()
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Message type constants and helper functions
3
+ */
4
+
5
+ // Payload types
6
+ var MSG_TYPE_TEXT = 1
7
+ var MSG_TYPE_IMAGE = 2
8
+ var MSG_TYPE_FILE = 3
9
+ var MSG_TYPE_MIXED = 12
10
+ var MSG_TYPE_COMMAND = 99
11
+ var MSG_TYPE_AI_LOADING = 100
12
+
13
+ // System message types (1000-2000)
14
+ function isSystemMessageType(type) {
15
+ return typeof type === 'number' && type >= 1000 && type <= 2000
16
+ }
17
+
18
+ /**
19
+ * Try to parse a string as JSON, falling back to base64 decode + JSON parse.
20
+ */
21
+ function tryParsePayloadString(str) {
22
+ try { return JSON.parse(str) } catch (e) {}
23
+ // Mini program doesn't have atob, use wx base64 decode
24
+ try {
25
+ var buf = wx.base64ToArrayBuffer(str)
26
+ var text = String.fromCharCode.apply(null, new Uint8Array(buf))
27
+ return JSON.parse(text)
28
+ } catch (e) {}
29
+ return null
30
+ }
31
+
32
+ /**
33
+ * Normalize raw payload to structured format
34
+ */
35
+ function toPayloadFromAny(raw) {
36
+ if (typeof raw === 'string') {
37
+ var parsed = tryParsePayloadString(raw)
38
+ if (parsed && typeof parsed === 'object') return toPayloadFromAny(parsed)
39
+ return { type: MSG_TYPE_TEXT, content: raw }
40
+ }
41
+ if (!raw) return { type: MSG_TYPE_TEXT, content: '' }
42
+
43
+ var t = raw.type
44
+ if (t === MSG_TYPE_TEXT && typeof raw.content === 'string') {
45
+ return { type: MSG_TYPE_TEXT, content: raw.content }
46
+ }
47
+ if (t === MSG_TYPE_IMAGE && typeof raw.url === 'string') {
48
+ return { type: MSG_TYPE_IMAGE, url: raw.url, width: raw.width || 0, height: raw.height || 0 }
49
+ }
50
+ if (t === MSG_TYPE_FILE && typeof raw.url === 'string' && typeof raw.name === 'string') {
51
+ return { type: MSG_TYPE_FILE, content: raw.content || '[文件]', url: raw.url, name: raw.name, size: raw.size || 0 }
52
+ }
53
+ if (t === MSG_TYPE_MIXED && typeof raw.content === 'string' && Array.isArray(raw.images)) {
54
+ var images = raw.images
55
+ .filter(function (i) { return i && typeof i.url === 'string' })
56
+ .map(function (i) { return { url: i.url, width: i.width || 0, height: i.height || 0 } })
57
+ return { type: MSG_TYPE_MIXED, content: raw.content, images: images }
58
+ }
59
+ if (t === MSG_TYPE_COMMAND && typeof raw.cmd === 'string') {
60
+ return { type: MSG_TYPE_COMMAND, cmd: raw.cmd, param: raw.param || {} }
61
+ }
62
+ if (t === MSG_TYPE_AI_LOADING) {
63
+ return { type: MSG_TYPE_AI_LOADING }
64
+ }
65
+ if (isSystemMessageType(t) && typeof raw.content === 'string') {
66
+ return { type: t, content: raw.content, extra: Array.isArray(raw.extra) ? raw.extra : undefined }
67
+ }
68
+ return { type: MSG_TYPE_TEXT, content: typeof raw.content === 'string' ? raw.content : JSON.stringify(raw) }
69
+ }
70
+
71
+ /**
72
+ * Map history API message to ChatMessage format
73
+ */
74
+ var jsonRenderUtils = require('../utils/jsonRender')
75
+
76
+ function mapHistoryToChatMessage(m, myUid) {
77
+ // Check event_meta for stream content (Stream API v2)
78
+ var streamContent
79
+ if (m && m.event_meta && m.event_meta.has_events) {
80
+ var events = m.event_meta.events || []
81
+ for (var i = 0; i < events.length; i++) {
82
+ var ev = events[i]
83
+ if (ev.event_key === 'main' && ev.snapshot && ev.snapshot.kind === 'text' && ev.snapshot.text) {
84
+ streamContent = ev.snapshot.text
85
+ break
86
+ }
87
+ }
88
+ }
89
+
90
+ // Parse mixed content from historical snapshot using MixedStreamParser
91
+ var uiParts
92
+ var rawContent = streamContent
93
+ if (rawContent) {
94
+ try {
95
+ var parts = []
96
+ var parser = jsonRenderUtils.createMixedStreamParser({
97
+ onText: function (text) { parts.push({ type: 'text', text: text + '\n' }) },
98
+ onPatch: function (patch) { parts.push({ type: 'data-spec', data: { type: 'patch', patch: patch } }) }
99
+ })
100
+ var normalised = rawContent.replace(/([^\n])```spec/g, '$1\n```spec')
101
+ parser.push(normalised)
102
+ parser.flush()
103
+
104
+ // Backwards compatibility: old messages may have a separate "json_render"
105
+ // event channel whose snapshot.text contains concatenated JSON patch arrays
106
+ if (m && m.event_meta && m.event_meta.has_events) {
107
+ var evts = m.event_meta.events || []
108
+ for (var k = 0; k < evts.length; k++) {
109
+ var jrEvt = evts[k]
110
+ if (jrEvt.event_key === 'json_render' && jrEvt.snapshot && jrEvt.snapshot.text) {
111
+ try {
112
+ var norm = '[' + jrEvt.snapshot.text.replace(/\]\s*\[/g, ',') + ']'
113
+ var outer = JSON.parse(norm)
114
+ var flat = Array.isArray(outer) ? outer : [outer]
115
+ // Flatten nested arrays
116
+ var stack = flat.slice()
117
+ while (stack.length > 0) {
118
+ var item = stack.pop()
119
+ if (Array.isArray(item)) {
120
+ for (var x = 0; x < item.length; x++) stack.push(item[x])
121
+ } else if (item && typeof item === 'object' && 'op' in item && 'path' in item) {
122
+ parts.push({ type: 'data-spec', data: { type: 'patch', patch: item } })
123
+ }
124
+ }
125
+ } catch (e) { /* ignore legacy parse failures */ }
126
+ break
127
+ }
128
+ }
129
+ }
130
+
131
+ if (parts.length > 0) {
132
+ var hasNonEmpty = false
133
+ for (var p = 0; p < parts.length; p++) {
134
+ if (parts[p].type !== 'text' || (parts[p].text && parts[p].text.trim())) {
135
+ hasNonEmpty = true
136
+ break
137
+ }
138
+ }
139
+ if (hasNonEmpty) uiParts = parts
140
+ }
141
+ } catch (e) {
142
+ console.warn('Failed to parse mixed stream content for historical message:', e)
143
+ }
144
+ }
145
+
146
+ // Build display content from text parts, or use streamContent as-is
147
+ var displayContent
148
+ if (uiParts) {
149
+ displayContent = ''
150
+ for (var d = 0; d < uiParts.length; d++) {
151
+ if (uiParts[d].type === 'text') displayContent += uiParts[d].text || ''
152
+ }
153
+ } else {
154
+ displayContent = streamContent
155
+ }
156
+
157
+ var payload
158
+ if (displayContent) {
159
+ payload = { type: MSG_TYPE_TEXT, content: displayContent }
160
+ } else {
161
+ payload = toPayloadFromAny(m && m.payload)
162
+ }
163
+
164
+ var errorMessage = m && m.error ? String(m.error) : undefined
165
+
166
+ var result = {
167
+ id: (m.message_id_str || m.client_msg_no || 'h-' + m.message_seq),
168
+ role: (m.from_uid && myUid && m.from_uid === myUid) ? 'user' : 'agent',
169
+ payload: payload,
170
+ time: new Date((m.timestamp || 0) * 1000),
171
+ messageSeq: typeof m.message_seq === 'number' ? m.message_seq : undefined,
172
+ clientMsgNo: m.client_msg_no ? String(m.client_msg_no) : undefined,
173
+ fromUid: m.from_uid ? String(m.from_uid) : undefined,
174
+ channelId: m.channel_id ? String(m.channel_id) : undefined,
175
+ channelType: typeof m.channel_type === 'number' ? m.channel_type : undefined,
176
+ errorMessage: errorMessage
177
+ }
178
+ if (uiParts) result.uiParts = uiParts
179
+ return result
180
+ }
181
+
182
+ module.exports = {
183
+ MSG_TYPE_TEXT: MSG_TYPE_TEXT,
184
+ MSG_TYPE_IMAGE: MSG_TYPE_IMAGE,
185
+ MSG_TYPE_FILE: MSG_TYPE_FILE,
186
+ MSG_TYPE_MIXED: MSG_TYPE_MIXED,
187
+ MSG_TYPE_COMMAND: MSG_TYPE_COMMAND,
188
+ MSG_TYPE_AI_LOADING: MSG_TYPE_AI_LOADING,
189
+ isSystemMessageType: isSystemMessageType,
190
+ toPayloadFromAny: toPayloadFromAny,
191
+ mapHistoryToChatMessage: mapHistoryToChatMessage
192
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Chat completion & cancel services
3
+ * API: POST /v1/chat/completion
4
+ * API: POST /v1/ai/runs/cancel-by-client
5
+ */
6
+ var adapter = require('../adapters/request')
7
+
8
+ function sendChatCompletion(params) {
9
+ var apiBase = (params.apiBase || '').replace(/\/$/, '')
10
+ var url = apiBase + '/v1/chat/completion'
11
+
12
+ var body = {
13
+ api_key: params.platformApiKey,
14
+ message: params.message,
15
+ from_uid: params.fromUid,
16
+ wukongim_only: true,
17
+ forward_user_message_to_wukongim: false,
18
+ stream: false
19
+ }
20
+ if (params.channelId) body.channel_id = params.channelId
21
+ if (params.channelType != null) body.channel_type = params.channelType
22
+
23
+ return adapter.request({
24
+ url: url,
25
+ method: 'POST',
26
+ headers: { 'Content-Type': 'application/json' },
27
+ body: body,
28
+ timeout: 30000
29
+ }).then(function (res) {
30
+ return res.json().then(function (data) {
31
+ if (data && data.event_type === 'error') {
32
+ throw new Error(data.message || data.detail || 'Unknown error')
33
+ }
34
+ if (!res.ok) {
35
+ throw new Error('/v1/chat/completion failed: ' + (data.message || data.detail || res.status))
36
+ }
37
+ return data
38
+ })
39
+ })
40
+ }
41
+
42
+ function cancelStreaming(params) {
43
+ var apiBase = (params.apiBase || '').replace(/\/$/, '')
44
+ var url = apiBase + '/v1/ai/runs/cancel-by-client'
45
+
46
+ return adapter.request({
47
+ url: url,
48
+ method: 'POST',
49
+ headers: { 'Content-Type': 'application/json' },
50
+ body: {
51
+ platform_api_key: params.platformApiKey,
52
+ client_msg_no: params.clientMsgNo,
53
+ reason: params.reason || 'user_cancel'
54
+ },
55
+ timeout: 10000
56
+ }).then(function (res) {
57
+ if (!res.ok) {
58
+ console.warn('[Chat] Cancel streaming failed:', res.status)
59
+ }
60
+ return res
61
+ })
62
+ }
63
+
64
+ module.exports = {
65
+ sendChatCompletion: sendChatCompletion,
66
+ cancelStreaming: cancelStreaming
67
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Message history sync service
3
+ * API: POST /v1/visitors/messages/sync
4
+ */
5
+ var adapter = require('../adapters/request')
6
+
7
+ function syncVisitorMessages(params) {
8
+ var apiBase = (params.apiBase || '').replace(/\/$/, '')
9
+ var platformApiKey = params.platformApiKey || ''
10
+ var url = apiBase + '/v1/visitors/messages/sync'
11
+
12
+ var body = {
13
+ platform_api_key: platformApiKey,
14
+ channel_id: params.channelId,
15
+ channel_type: params.channelType,
16
+ start_message_seq: params.startSeq != null ? params.startSeq : null,
17
+ end_message_seq: params.endSeq != null ? params.endSeq : null,
18
+ limit: params.limit != null ? params.limit : null,
19
+ pull_mode: params.pullMode != null ? params.pullMode : null
20
+ }
21
+
22
+ return adapter.request({
23
+ url: url,
24
+ method: 'POST',
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ 'X-Platform-API-Key': platformApiKey
28
+ },
29
+ body: body,
30
+ timeout: 10000
31
+ }).then(function (res) {
32
+ if (!res.ok) {
33
+ return res.text().then(function (text) {
34
+ throw new Error('[History] sync failed: ' + res.status + ' ' + text)
35
+ })
36
+ }
37
+ return res.json().then(function (data) {
38
+ if (!data || !Array.isArray(data.messages)) {
39
+ throw new Error('[History] invalid response')
40
+ }
41
+ return data
42
+ })
43
+ })
44
+ }
45
+
46
+ module.exports = { syncVisitorMessages: syncVisitorMessages }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Platform info service
3
+ * API: GET /v1/platforms/info?platform_api_key=...
4
+ */
5
+ var adapter = require('../adapters/request')
6
+
7
+ function fetchPlatformInfo(params) {
8
+ var apiBase = (params.apiBase || '').replace(/\/$/, '')
9
+ var platformApiKey = params.platformApiKey || ''
10
+ var url = apiBase + '/v1/platforms/info?platform_api_key=' + encodeURIComponent(platformApiKey)
11
+
12
+ return adapter.request({
13
+ url: url,
14
+ method: 'GET',
15
+ headers: { 'X-Platform-API-Key': platformApiKey },
16
+ timeout: 10000
17
+ }).then(function (res) {
18
+ if (!res.ok) {
19
+ return res.text().then(function (text) {
20
+ throw new Error('[Platform] info failed: ' + res.status + ' ' + text)
21
+ })
22
+ }
23
+ return res.json()
24
+ })
25
+ }
26
+
27
+ module.exports = { fetchPlatformInfo: fetchPlatformInfo }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * File upload service using wx.uploadFile
3
+ * API: POST /v1/chat/upload
4
+ */
5
+
6
+ function uploadChatFile(params) {
7
+ var apiBase = (params.apiBase || '').replace(/\/$/, '')
8
+ var apiKey = params.apiKey || ''
9
+ var channelId = params.channelId || ''
10
+ var channelType = params.channelType || 251
11
+ var filePath = params.filePath
12
+ var onProgress = params.onProgress
13
+
14
+ return new Promise(function (resolve, reject) {
15
+ var task = wx.uploadFile({
16
+ url: apiBase + '/v1/chat/upload',
17
+ filePath: filePath,
18
+ name: 'file',
19
+ header: {
20
+ 'X-Platform-API-Key': apiKey
21
+ },
22
+ formData: {
23
+ channel_id: channelId,
24
+ channel_type: String(channelType)
25
+ },
26
+ success: function (res) {
27
+ if (res.statusCode >= 200 && res.statusCode < 300) {
28
+ try {
29
+ var data = JSON.parse(res.data)
30
+ if (!data || !data.file_id) {
31
+ return reject(new Error('[Upload] invalid response'))
32
+ }
33
+ resolve(data)
34
+ } catch (e) {
35
+ reject(new Error('[Upload] invalid JSON response'))
36
+ }
37
+ } else {
38
+ reject(new Error('[Upload] failed: ' + res.statusCode))
39
+ }
40
+ },
41
+ fail: function (err) {
42
+ reject(new Error('[Upload] ' + (err.errMsg || 'upload failed')))
43
+ }
44
+ })
45
+
46
+ if (onProgress && task) {
47
+ task.onProgressUpdate(function (r) {
48
+ onProgress(r.progress)
49
+ })
50
+ }
51
+ })
52
+ }
53
+
54
+ /**
55
+ * Get image dimensions using wx.getImageInfo
56
+ */
57
+ function getImageInfo(filePath) {
58
+ return new Promise(function (resolve) {
59
+ wx.getImageInfo({
60
+ src: filePath,
61
+ success: function (res) {
62
+ resolve({ width: res.width, height: res.height })
63
+ },
64
+ fail: function () {
65
+ resolve(null)
66
+ }
67
+ })
68
+ })
69
+ }
70
+
71
+ module.exports = {
72
+ uploadChatFile: uploadChatFile,
73
+ getImageInfo: getImageInfo
74
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Visitor registration & caching service
3
+ * API: POST /v1/visitors/register
4
+ */
5
+ var adapter = require('../adapters/request')
6
+ var storage = require('../adapters/storage')
7
+
8
+ function _cacheKey(apiBase, platformApiKey) {
9
+ return 'tgo:visitor:' + apiBase + ':' + platformApiKey
10
+ }
11
+
12
+ function loadCachedVisitor(apiBase, platformApiKey) {
13
+ return storage.getJSON(_cacheKey(apiBase, platformApiKey))
14
+ }
15
+
16
+ function saveCachedVisitor(apiBase, platformApiKey, v, expiresAtMs) {
17
+ var cached = {
18
+ apiBase: apiBase,
19
+ platform_api_key: platformApiKey,
20
+ visitor_id: v.id,
21
+ platform_open_id: v.platform_open_id,
22
+ channel_id: v.channel_id,
23
+ channel_type: v.channel_type,
24
+ im_token: v.im_token,
25
+ project_id: v.project_id,
26
+ platform_id: v.platform_id,
27
+ created_at: v.created_at,
28
+ updated_at: v.updated_at,
29
+ expires_at: expiresAtMs
30
+ }
31
+ storage.setJSON(_cacheKey(apiBase, platformApiKey), cached)
32
+ }
33
+
34
+ function registerVisitor(params) {
35
+ var apiBase = (params.apiBase || '').replace(/\/$/, '')
36
+ var platformApiKey = params.platformApiKey || ''
37
+ var extra = params.extra || {}
38
+ var url = apiBase + '/v1/visitors/register'
39
+
40
+ var body = Object.assign({ platform_api_key: platformApiKey }, extra)
41
+
42
+ return adapter.request({
43
+ url: url,
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: body,
47
+ timeout: 10000
48
+ }).then(function (res) {
49
+ if (!res.ok) {
50
+ return res.text().then(function (text) {
51
+ throw new Error('[Visitor] register failed: ' + res.status + ' ' + text)
52
+ })
53
+ }
54
+ return res.json().then(function (data) {
55
+ if (!data || !data.id || !data.channel_id) {
56
+ throw new Error('[Visitor] invalid register response: missing id/channel_id')
57
+ }
58
+ return data
59
+ })
60
+ })
61
+ }
62
+
63
+ module.exports = {
64
+ loadCachedVisitor: loadCachedVisitor,
65
+ saveCachedVisitor: saveCachedVisitor,
66
+ registerVisitor: registerVisitor
67
+ }