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.
- package/README.md +81 -0
- package/miniprogram_dist/adapters/request.js +38 -0
- package/miniprogram_dist/adapters/storage.js +42 -0
- package/miniprogram_dist/adapters/systemInfo.js +22 -0
- package/miniprogram_dist/chat/index.js +164 -0
- package/miniprogram_dist/chat/index.json +7 -0
- package/miniprogram_dist/chat/index.wxml +48 -0
- package/miniprogram_dist/chat/index.wxss +92 -0
- package/miniprogram_dist/components/json-render-element/index.js +374 -0
- package/miniprogram_dist/components/json-render-element/index.json +6 -0
- package/miniprogram_dist/components/json-render-element/index.wxml +218 -0
- package/miniprogram_dist/components/json-render-element/index.wxss +450 -0
- package/miniprogram_dist/components/json-render-message/index.js +89 -0
- package/miniprogram_dist/components/json-render-message/index.json +7 -0
- package/miniprogram_dist/components/json-render-message/index.wxml +25 -0
- package/miniprogram_dist/components/json-render-message/index.wxss +26 -0
- package/miniprogram_dist/components/json-render-surface/index.js +116 -0
- package/miniprogram_dist/components/json-render-surface/index.json +6 -0
- package/miniprogram_dist/components/json-render-surface/index.wxml +10 -0
- package/miniprogram_dist/components/json-render-surface/index.wxss +6 -0
- package/miniprogram_dist/components/markdown-text/index.js +23 -0
- package/miniprogram_dist/components/markdown-text/index.json +3 -0
- package/miniprogram_dist/components/markdown-text/index.wxml +1 -0
- package/miniprogram_dist/components/markdown-text/index.wxss +6 -0
- package/miniprogram_dist/components/message-bubble/index.js +12 -0
- package/miniprogram_dist/components/message-bubble/index.json +3 -0
- package/miniprogram_dist/components/message-bubble/index.wxml +3 -0
- package/miniprogram_dist/components/message-bubble/index.wxss +17 -0
- package/miniprogram_dist/components/message-input/index.js +76 -0
- package/miniprogram_dist/components/message-input/index.json +3 -0
- package/miniprogram_dist/components/message-input/index.wxml +28 -0
- package/miniprogram_dist/components/message-input/index.wxss +56 -0
- package/miniprogram_dist/components/message-list/index.js +113 -0
- package/miniprogram_dist/components/message-list/index.json +9 -0
- package/miniprogram_dist/components/message-list/index.wxml +108 -0
- package/miniprogram_dist/components/message-list/index.wxss +113 -0
- package/miniprogram_dist/components/system-message/index.js +8 -0
- package/miniprogram_dist/components/system-message/index.json +3 -0
- package/miniprogram_dist/components/system-message/index.wxml +3 -0
- package/miniprogram_dist/components/system-message/index.wxss +15 -0
- package/miniprogram_dist/core/chatStore.js +758 -0
- package/miniprogram_dist/core/i18n.js +66 -0
- package/miniprogram_dist/core/platformStore.js +86 -0
- package/miniprogram_dist/core/types.js +192 -0
- package/miniprogram_dist/services/chat.js +67 -0
- package/miniprogram_dist/services/messageHistory.js +46 -0
- package/miniprogram_dist/services/platform.js +27 -0
- package/miniprogram_dist/services/upload.js +74 -0
- package/miniprogram_dist/services/visitor.js +67 -0
- package/miniprogram_dist/services/wukongim.js +183 -0
- package/miniprogram_dist/utils/jsonRender.js +158 -0
- package/miniprogram_dist/utils/markdown.js +31 -0
- package/miniprogram_dist/utils/time.js +85 -0
- package/miniprogram_dist/utils/uid.js +11 -0
- 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
|
+
}
|