@syfei49/mini-dynamic-renderer 1.0.7 → 1.0.9
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 +39 -6
- package/components/ly-ai-assistant/ly-ai-assistant.vue +240 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/adapters/request.js +13 -5
- package/src/chat/parser.js +29 -1
- package/src/chat/service.js +79 -16
- package/src/config.js +2 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ npm install @syfei49/mini-dynamic-renderer
|
|
|
14
14
|
```json
|
|
15
15
|
{
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@syfei49/mini-dynamic-renderer": "^1.0.
|
|
17
|
+
"@syfei49/mini-dynamic-renderer": "^1.0.9"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
```
|
|
@@ -33,8 +33,10 @@ import { init } from '@syfei49/mini-dynamic-renderer'
|
|
|
33
33
|
import { baseUrl } from '@/utils/request.js'
|
|
34
34
|
|
|
35
35
|
init({
|
|
36
|
-
baseUrl:
|
|
37
|
-
|
|
36
|
+
baseUrl: 'https://lygateway.ilyixy.com/prod-api',
|
|
37
|
+
uploadBaseUrl: 'https://jky.mall.ilyixy.com/prod-api',
|
|
38
|
+
appId: 'sass-mall-xxx',
|
|
39
|
+
chatApiPath: '/openapi/mini/ai/chat',
|
|
38
40
|
uploadApiPath: '/mini/common/upload',
|
|
39
41
|
getToken: () => uni.getStorageSync('token'),
|
|
40
42
|
visiblePages: ['pages/user/user'],
|
|
@@ -89,11 +91,13 @@ function gotoPath(item) {
|
|
|
89
91
|
|
|
90
92
|
| 字段 | 类型 | 默认值 | 说明 |
|
|
91
93
|
|------|------|--------|------|
|
|
92
|
-
| `baseUrl` | `string` | `''` | API
|
|
94
|
+
| `baseUrl` | `string` | `''` | API 根地址,示例 `https://lygateway.ilyixy.com/prod-api` |
|
|
95
|
+
| `uploadBaseUrl` | `string` | `''` | 上传接口基地址,默认与 `baseUrl` 一致,示例 `https://jky.mall.ilyixy.com/prod-api` |
|
|
96
|
+
| `appId` | `string` | `''` | 应用标识,配置后每次请求自动带上请求头 `X-App-Id` |
|
|
93
97
|
| `chatApiPath` | `string` | `'/mini/ai/chat'` | 对话接口路径 |
|
|
94
98
|
| `uploadApiPath` | `string` | `'/mini/common/upload'` | 图片上传路径 |
|
|
95
99
|
| `getToken` | `Function` | `() => ''` | 返回 Authorization |
|
|
96
|
-
| `getHeaders` | `Function` | `() => ({})` |
|
|
100
|
+
| `getHeaders` | `Function` | `() => ({})` | 额外请求头,与自动头合并 |
|
|
97
101
|
| `visiblePages` | `string[]` | `['pages/user/user']` | 显示浮层图标的页面路由 |
|
|
98
102
|
| `floatIcon` | `string` | 内置默认图标 | 浮层按钮图片 URL |
|
|
99
103
|
| `requireLogin` | `boolean` | `true` | 打开前检查登录 |
|
|
@@ -101,6 +105,8 @@ function gotoPath(item) {
|
|
|
101
105
|
| `debug` | `boolean` | `false` | 调试日志 |
|
|
102
106
|
| `enableInterceptor` | `boolean` | `false` | 是否启用 wx.request 拦截(预留) |
|
|
103
107
|
|
|
108
|
+
> 注意:`appId` 非空时,所有请求头都会自动注入 `X-App-Id: <appId>`。
|
|
109
|
+
|
|
104
110
|
## API
|
|
105
111
|
|
|
106
112
|
| 方法 | 说明 |
|
|
@@ -115,7 +121,34 @@ function gotoPath(item) {
|
|
|
115
121
|
|
|
116
122
|
## 后端协议
|
|
117
123
|
|
|
118
|
-
|
|
124
|
+
### 1. 动态参数收集(`[RENDER_CONFIG]`)
|
|
125
|
+
|
|
126
|
+
后端可在 `answer` 中返回 `[RENDER_CONFIG]{...}` 来让插件渲染一个动态表单,用户填写后自动作为 `inputs` 再次发起对话请求。
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
请填写新增商品所需信息 [RENDER_CONFIG]{
|
|
130
|
+
"type": "form",
|
|
131
|
+
"title": "新增商品",
|
|
132
|
+
"description": "请补充以下字段",
|
|
133
|
+
"fields": [
|
|
134
|
+
{ "name": "productName", "label": "商品名称", "type": "input" },
|
|
135
|
+
{ "name": "price", "label": "价格", "type": "number" },
|
|
136
|
+
{ "name": "category", "label": "分类", "type": "select", "options": [{ "label": "保健品", "value": "1" }] },
|
|
137
|
+
{ "name": "cover", "label": "封面图", "type": "image" }
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
支持字段类型:
|
|
143
|
+
|
|
144
|
+
- `input`:文本输入框
|
|
145
|
+
- `number`:数字输入框
|
|
146
|
+
- `select`:`picker` 选择器,需配 `options: [{ label, value }]`
|
|
147
|
+
- `image`:拍照 / 相册选择并上传
|
|
148
|
+
|
|
149
|
+
### 2. 分类图标引导(兼容 `[NEED_ICON]`)
|
|
150
|
+
|
|
151
|
+
当需要用户上传分类图标时,后端仍可在 `answer` 中返回:
|
|
119
152
|
|
|
120
153
|
```text
|
|
121
154
|
[NEED_ICON]{"name": "张三", "sort": 0, "msg": "请上传一张图片作为「张三」的分类图标"}
|
|
@@ -37,7 +37,39 @@
|
|
|
37
37
|
</view>
|
|
38
38
|
</scroll-view>
|
|
39
39
|
|
|
40
|
-
<view v-if="
|
|
40
|
+
<view v-if="renderConfig" class="ly-ai-form-bar">
|
|
41
|
+
<view v-if="renderConfig.title" class="ly-ai-form-title">{{ renderConfig.title }}</view>
|
|
42
|
+
<view v-if="renderConfig.description" class="ly-ai-form-desc">{{ renderConfig.description }}</view>
|
|
43
|
+
<view v-for="(field, fIndex) in renderConfig.fields" :key="fIndex" class="ly-ai-form-item">
|
|
44
|
+
<text class="ly-ai-form-label">{{ field.label || field.name }}</text>
|
|
45
|
+
<template v-if="field.type === 'input' || field.type === 'number'">
|
|
46
|
+
<input class="ly-ai-form-input" :type="field.type === 'number' ? 'number' : 'text'"
|
|
47
|
+
:value="formInputs[field.name]" :placeholder="field.placeholder || '请输入' + (field.label || field.name)"
|
|
48
|
+
:disabled="loading || uploading" @input="onFormInput($event, field.name)" />
|
|
49
|
+
</template>
|
|
50
|
+
<template v-else-if="field.type === 'select'">
|
|
51
|
+
<picker class="ly-ai-form-picker" mode="selector" :range="getFieldOptions(field).labels"
|
|
52
|
+
:value="getFieldIndex(field)" :disabled="loading || uploading" @change="onFormSelect($event, field)">
|
|
53
|
+
<view class="ly-ai-form-picker-text">{{ getFieldSelectedLabel(field) }}</view>
|
|
54
|
+
</picker>
|
|
55
|
+
</template>
|
|
56
|
+
<template v-else-if="field.type === 'image'">
|
|
57
|
+
<view v-if="formInputs[field.name]" class="ly-ai-form-image-wrap">
|
|
58
|
+
<image class="ly-ai-form-image" :src="formInputs[field.name]" mode="aspectFill"
|
|
59
|
+
@click="previewImage(formInputs[field.name], [formInputs[field.name]])" />
|
|
60
|
+
<view class="ly-ai-form-image-remove" @click="onFormImageRemove(field.name)">×</view>
|
|
61
|
+
</view>
|
|
62
|
+
<view v-else class="ly-ai-form-image-btn" :class="{ disabled: loading || uploading }"
|
|
63
|
+
@click="onFormImageChoose(field.name)">拍照 / 相册</view>
|
|
64
|
+
</template>
|
|
65
|
+
</view>
|
|
66
|
+
<view class="ly-ai-form-actions">
|
|
67
|
+
<view class="ly-ai-form-btn confirm" :class="{ disabled: loading || uploading }" @click="submitForm">确认</view>
|
|
68
|
+
<view class="ly-ai-form-btn cancel" :class="{ disabled: loading || uploading }" @click="cancelForm">取消</view>
|
|
69
|
+
</view>
|
|
70
|
+
</view>
|
|
71
|
+
|
|
72
|
+
<view v-else-if="iconPickMode" class="ly-ai-icon-pick-bar">
|
|
41
73
|
<view class="ly-ai-icon-pick-title">请为「{{ pendingIconCategory && pendingIconCategory.name }}」选择分类图标</view>
|
|
42
74
|
<view class="ly-ai-icon-pick-actions">
|
|
43
75
|
<view class="ly-ai-icon-pick-btn" :class="{ disabled: loading || uploading }" @click="pickCategoryIcon('camera')">拍照</view>
|
|
@@ -91,6 +123,8 @@
|
|
|
91
123
|
const scrollIntoView = ref('')
|
|
92
124
|
const floatIconUrl = ref('')
|
|
93
125
|
const keyboardHeight = ref(0)
|
|
126
|
+
const renderConfig = ref(null)
|
|
127
|
+
const formInputs = ref({})
|
|
94
128
|
|
|
95
129
|
const canSend = computed(() => {
|
|
96
130
|
return !!(inputText.value && inputText.value.trim()) || !!pendingImageUrl.value
|
|
@@ -114,6 +148,21 @@
|
|
|
114
148
|
let closeHandler = null
|
|
115
149
|
let keyboardHandler = null
|
|
116
150
|
|
|
151
|
+
function initFormInputs() {
|
|
152
|
+
var cfg = renderConfig.value
|
|
153
|
+
var values = {}
|
|
154
|
+
if (cfg && cfg.fields && cfg.fields.length) {
|
|
155
|
+
cfg.fields.forEach(function (field) {
|
|
156
|
+
if (field.type === 'select' && field.options && field.options.length) {
|
|
157
|
+
values[field.name] = field.defaultValue !== undefined ? field.defaultValue : field.options[0].value
|
|
158
|
+
} else {
|
|
159
|
+
values[field.name] = field.defaultValue !== undefined ? field.defaultValue : ''
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
formInputs.value = values
|
|
164
|
+
}
|
|
165
|
+
|
|
117
166
|
function syncState(snapshot) {
|
|
118
167
|
messages.value = snapshot.messages || []
|
|
119
168
|
loading.value = snapshot.loading || false
|
|
@@ -122,7 +171,9 @@
|
|
|
122
171
|
pendingIconCategory.value = snapshot.pendingIconCategory || null
|
|
123
172
|
pendingImageUrl.value = snapshot.pendingImageUrl || ''
|
|
124
173
|
inputText.value = snapshot.inputText || ''
|
|
174
|
+
renderConfig.value = snapshot.renderConfig || null
|
|
125
175
|
quickQuestions.value = snapshot.quickQuestions || []
|
|
176
|
+
initFormInputs()
|
|
126
177
|
scrollToBottom()
|
|
127
178
|
}
|
|
128
179
|
|
|
@@ -208,6 +259,75 @@
|
|
|
208
259
|
chatService.cancelIconPick()
|
|
209
260
|
}
|
|
210
261
|
|
|
262
|
+
function getFieldOptions(field) {
|
|
263
|
+
var options = field.options || []
|
|
264
|
+
return {
|
|
265
|
+
labels: options.map(function (opt) {
|
|
266
|
+
return opt.label || opt.value
|
|
267
|
+
}),
|
|
268
|
+
values: options.map(function (opt) {
|
|
269
|
+
return opt.value
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function getFieldIndex(field) {
|
|
275
|
+
var opts = getFieldOptions(field)
|
|
276
|
+
var index = opts.values.indexOf(formInputs.value[field.name])
|
|
277
|
+
return index >= 0 ? index : 0
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function getFieldSelectedLabel(field) {
|
|
281
|
+
var index = getFieldIndex(field)
|
|
282
|
+
var opts = getFieldOptions(field)
|
|
283
|
+
var option = field.options ? field.options[index] : null
|
|
284
|
+
return option ? (option.label || option.value) : (opts.labels[index] || '请选择')
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function onFormInput(e, name) {
|
|
288
|
+
formInputs.value[name] = e.detail.value
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function onFormSelect(e, field) {
|
|
292
|
+
var index = e.detail.value
|
|
293
|
+
var opts = getFieldOptions(field)
|
|
294
|
+
formInputs.value[field.name] = opts.values[index]
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function onFormImageChoose(name) {
|
|
298
|
+
chatService.chooseImage()
|
|
299
|
+
.then(function (imageUrl) {
|
|
300
|
+
formInputs.value[name] = imageUrl
|
|
301
|
+
})
|
|
302
|
+
.catch(function (error) {
|
|
303
|
+
uni.showToast({
|
|
304
|
+
title: error && (error.message || error.msg) ? (error.message || error.msg) : '图片上传失败',
|
|
305
|
+
icon: 'none'
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function onFormImageRemove(name) {
|
|
311
|
+
formInputs.value[name] = ''
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function submitForm() {
|
|
315
|
+
chatService.submitInputs(formInputs.value)
|
|
316
|
+
.then(function () {
|
|
317
|
+
scrollToBottom()
|
|
318
|
+
})
|
|
319
|
+
.catch(function (error) {
|
|
320
|
+
uni.showToast({
|
|
321
|
+
title: error && (error.message || error.msg) ? (error.message || error.msg) : '提交失败',
|
|
322
|
+
icon: 'none'
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function cancelForm() {
|
|
328
|
+
chatService.cancelForm()
|
|
329
|
+
}
|
|
330
|
+
|
|
211
331
|
function clearPendingImage() {
|
|
212
332
|
chatService.clearPendingImage()
|
|
213
333
|
}
|
|
@@ -554,4 +674,123 @@
|
|
|
554
674
|
.ly-ai-send-btn.disabled {
|
|
555
675
|
opacity: 0.5;
|
|
556
676
|
}
|
|
677
|
+
|
|
678
|
+
.ly-ai-form-bar {
|
|
679
|
+
padding: 24rpx;
|
|
680
|
+
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
681
|
+
background: #fff;
|
|
682
|
+
border-top: 1rpx solid #eee;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.ly-ai-form-title {
|
|
686
|
+
font-size: 30rpx;
|
|
687
|
+
font-weight: 600;
|
|
688
|
+
color: #333;
|
|
689
|
+
margin-bottom: 12rpx;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.ly-ai-form-desc {
|
|
693
|
+
font-size: 26rpx;
|
|
694
|
+
color: #666;
|
|
695
|
+
line-height: 1.5;
|
|
696
|
+
margin-bottom: 24rpx;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.ly-ai-form-item {
|
|
700
|
+
margin-bottom: 24rpx;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.ly-ai-form-label {
|
|
704
|
+
display: block;
|
|
705
|
+
font-size: 28rpx;
|
|
706
|
+
color: #333;
|
|
707
|
+
margin-bottom: 12rpx;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.ly-ai-form-input {
|
|
711
|
+
height: 72rpx;
|
|
712
|
+
background: #f5f6f8;
|
|
713
|
+
border-radius: 12rpx;
|
|
714
|
+
padding: 0 24rpx;
|
|
715
|
+
font-size: 28rpx;
|
|
716
|
+
color: #333;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.ly-ai-form-picker {
|
|
720
|
+
height: 72rpx;
|
|
721
|
+
background: #f5f6f8;
|
|
722
|
+
border-radius: 12rpx;
|
|
723
|
+
padding: 0 24rpx;
|
|
724
|
+
display: flex;
|
|
725
|
+
align-items: center;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.ly-ai-form-picker-text {
|
|
729
|
+
font-size: 28rpx;
|
|
730
|
+
color: #333;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.ly-ai-form-image-wrap {
|
|
734
|
+
display: flex;
|
|
735
|
+
align-items: center;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.ly-ai-form-image {
|
|
739
|
+
width: 160rpx;
|
|
740
|
+
height: 160rpx;
|
|
741
|
+
border-radius: 12rpx;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
.ly-ai-form-image-remove {
|
|
745
|
+
margin-left: 16rpx;
|
|
746
|
+
width: 48rpx;
|
|
747
|
+
height: 48rpx;
|
|
748
|
+
line-height: 44rpx;
|
|
749
|
+
text-align: center;
|
|
750
|
+
font-size: 36rpx;
|
|
751
|
+
color: #999;
|
|
752
|
+
background: #f5f6f8;
|
|
753
|
+
border-radius: 50%;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.ly-ai-form-image-btn {
|
|
757
|
+
width: 200rpx;
|
|
758
|
+
height: 160rpx;
|
|
759
|
+
line-height: 160rpx;
|
|
760
|
+
text-align: center;
|
|
761
|
+
background: #f5f6f8;
|
|
762
|
+
color: #666;
|
|
763
|
+
border-radius: 12rpx;
|
|
764
|
+
font-size: 28rpx;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
.ly-ai-form-actions {
|
|
768
|
+
display: flex;
|
|
769
|
+
gap: 16rpx;
|
|
770
|
+
margin-top: 12rpx;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.ly-ai-form-btn {
|
|
774
|
+
flex: 1;
|
|
775
|
+
height: 80rpx;
|
|
776
|
+
line-height: 80rpx;
|
|
777
|
+
text-align: center;
|
|
778
|
+
border-radius: 12rpx;
|
|
779
|
+
font-size: 28rpx;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.ly-ai-form-btn.confirm {
|
|
783
|
+
background: #f84616;
|
|
784
|
+
color: #fff;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.ly-ai-form-btn.cancel {
|
|
788
|
+
background: #f5f6f8;
|
|
789
|
+
color: #666;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.ly-ai-form-btn.disabled,
|
|
793
|
+
.ly-ai-form-image-btn.disabled {
|
|
794
|
+
opacity: 0.5;
|
|
795
|
+
}
|
|
557
796
|
</style>
|
package/index.js
CHANGED
package/package.json
CHANGED
package/src/adapters/request.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
var configModule = require('../config');
|
|
2
2
|
|
|
3
|
-
function getFullUrl(path) {
|
|
3
|
+
function getFullUrl(path, useUploadBase) {
|
|
4
4
|
var config = configModule.getConfig();
|
|
5
|
-
var baseUrl = config.
|
|
5
|
+
var baseUrl = (useUploadBase && config.uploadBaseUrl) ? config.uploadBaseUrl : config.baseUrl;
|
|
6
|
+
baseUrl = baseUrl || '';
|
|
6
7
|
var normalizedPath = (path || '').replace(/^\/+/, '/');
|
|
7
8
|
if (baseUrl.slice(-1) === '/' && normalizedPath.indexOf('/') === 0) {
|
|
8
9
|
return baseUrl + normalizedPath.slice(1);
|
|
@@ -20,6 +21,9 @@ function getHeaders(extraHeaders) {
|
|
|
20
21
|
if (token) {
|
|
21
22
|
headers.Authorization = token;
|
|
22
23
|
}
|
|
24
|
+
if (config.appId) {
|
|
25
|
+
headers['X-App-Id'] = config.appId;
|
|
26
|
+
}
|
|
23
27
|
if (extraHeaders) {
|
|
24
28
|
Object.assign(headers, extraHeaders);
|
|
25
29
|
}
|
|
@@ -32,16 +36,20 @@ function sendChat(payload) {
|
|
|
32
36
|
var headers = getHeaders({
|
|
33
37
|
'Content-Type': 'application/json'
|
|
34
38
|
});
|
|
39
|
+
var data = Object.assign({}, payload || {});
|
|
40
|
+
if (!data.inputs) {
|
|
41
|
+
delete data.inputs;
|
|
42
|
+
}
|
|
35
43
|
|
|
36
44
|
if (config.debug) {
|
|
37
|
-
console.log('[mini-dynamic-renderer] sendChat', url,
|
|
45
|
+
console.log('[mini-dynamic-renderer] sendChat', url, data);
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
return new Promise(function (resolve, reject) {
|
|
41
49
|
uni.request({
|
|
42
50
|
url: url,
|
|
43
51
|
method: 'POST',
|
|
44
|
-
data:
|
|
52
|
+
data: data,
|
|
45
53
|
header: headers,
|
|
46
54
|
timeout: 120000,
|
|
47
55
|
success: function (res) {
|
|
@@ -61,7 +69,7 @@ function sendChat(payload) {
|
|
|
61
69
|
|
|
62
70
|
function uploadImage(filePath) {
|
|
63
71
|
var config = configModule.getConfig();
|
|
64
|
-
var url = getFullUrl(config.uploadApiPath);
|
|
72
|
+
var url = getFullUrl(config.uploadApiPath, true);
|
|
65
73
|
var headers = getHeaders();
|
|
66
74
|
|
|
67
75
|
if (config.debug) {
|
package/src/chat/parser.js
CHANGED
|
@@ -48,8 +48,36 @@ function parseNeedIcon(answer) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function parseRenderConfig(answer) {
|
|
52
|
+
if (!answer || typeof answer !== 'string') {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
var renderIdx = answer.indexOf('[RENDER_CONFIG]');
|
|
56
|
+
if (renderIdx !== -1) {
|
|
57
|
+
var jsonPart = answer.substring(renderIdx + '[RENDER_CONFIG]'.length);
|
|
58
|
+
var start = jsonPart.indexOf('{');
|
|
59
|
+
var end = jsonPart.lastIndexOf('}');
|
|
60
|
+
if (start === -1 || end === -1 || end < start) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(jsonPart.substring(start, end + 1));
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (answer.indexOf('[NEED_ICON]') !== -1) {
|
|
70
|
+
return {
|
|
71
|
+
type: 'iconPicker',
|
|
72
|
+
fields: [{ name: 'imageUrl', label: '分类图标', type: 'image' }]
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
51
78
|
module.exports = {
|
|
52
79
|
cleanImageUrl: cleanImageUrl,
|
|
53
80
|
parseAssistantMessage: parseAssistantMessage,
|
|
54
|
-
parseNeedIcon: parseNeedIcon
|
|
81
|
+
parseNeedIcon: parseNeedIcon,
|
|
82
|
+
parseRenderConfig: parseRenderConfig
|
|
55
83
|
};
|
package/src/chat/service.js
CHANGED
|
@@ -11,7 +11,10 @@ var state = {
|
|
|
11
11
|
iconPickMode: false,
|
|
12
12
|
pendingIconCategory: null,
|
|
13
13
|
pendingImageUrl: '',
|
|
14
|
-
inputText: ''
|
|
14
|
+
inputText: '',
|
|
15
|
+
renderConfig: null,
|
|
16
|
+
pendingInputs: {},
|
|
17
|
+
lastUserQuery: ''
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
var quickQuestions = [
|
|
@@ -29,6 +32,9 @@ function reset() {
|
|
|
29
32
|
state.pendingIconCategory = null;
|
|
30
33
|
state.pendingImageUrl = '';
|
|
31
34
|
state.inputText = '';
|
|
35
|
+
state.renderConfig = null;
|
|
36
|
+
state.pendingInputs = {};
|
|
37
|
+
state.lastUserQuery = '';
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
function ensureLogin() {
|
|
@@ -65,6 +71,8 @@ function getSnapshot() {
|
|
|
65
71
|
pendingIconCategory: state.pendingIconCategory,
|
|
66
72
|
pendingImageUrl: state.pendingImageUrl,
|
|
67
73
|
inputText: state.inputText,
|
|
74
|
+
renderConfig: state.renderConfig,
|
|
75
|
+
pendingInputs: Object.assign({}, state.pendingInputs),
|
|
68
76
|
quickQuestions: quickQuestions.slice()
|
|
69
77
|
};
|
|
70
78
|
}
|
|
@@ -86,6 +94,7 @@ function handleAssistantAnswer(answer) {
|
|
|
86
94
|
sort: needIcon.sort
|
|
87
95
|
};
|
|
88
96
|
state.iconPickMode = true;
|
|
97
|
+
state.renderConfig = null;
|
|
89
98
|
state.messages.push({
|
|
90
99
|
role: 'assistant',
|
|
91
100
|
content: needIcon.msg,
|
|
@@ -94,8 +103,19 @@ function handleAssistantAnswer(answer) {
|
|
|
94
103
|
notifyUpdate();
|
|
95
104
|
return;
|
|
96
105
|
}
|
|
106
|
+
var renderConfig = parser.parseRenderConfig(answer);
|
|
107
|
+
if (renderConfig) {
|
|
108
|
+
state.iconPickMode = false;
|
|
109
|
+
state.pendingIconCategory = null;
|
|
110
|
+
state.renderConfig = renderConfig;
|
|
111
|
+
var cleanedAnswer = answer.replace(/\[RENDER_CONFIG\]\{[\s\S]*\}/, '').replace(/\[NEED_ICON\]\{[\s\S]*\}/, '').trim();
|
|
112
|
+
state.messages.push(createAssistantMessage(cleanedAnswer || '请填写以下信息'));
|
|
113
|
+
notifyUpdate();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
97
116
|
state.iconPickMode = false;
|
|
98
117
|
state.pendingIconCategory = null;
|
|
118
|
+
state.renderConfig = null;
|
|
99
119
|
state.messages.push(createAssistantMessage(answer));
|
|
100
120
|
notifyUpdate();
|
|
101
121
|
}
|
|
@@ -206,23 +226,11 @@ function sendQuick(text) {
|
|
|
206
226
|
return handleSend();
|
|
207
227
|
}
|
|
208
228
|
|
|
209
|
-
function
|
|
210
|
-
if (!ensureLogin()) {
|
|
211
|
-
return Promise.resolve();
|
|
212
|
-
}
|
|
213
|
-
state.messages.push({
|
|
214
|
-
role: 'user',
|
|
215
|
-
content: displayContent || query,
|
|
216
|
-
imageUrls: imageUrl ? [imageUrl] : []
|
|
217
|
-
});
|
|
229
|
+
function doSendChat(payload) {
|
|
218
230
|
state.loading = true;
|
|
219
231
|
notifyUpdate();
|
|
220
232
|
|
|
221
|
-
return requestAdapter.sendChat(
|
|
222
|
-
conversationId: state.conversationId || undefined,
|
|
223
|
-
query: query || undefined,
|
|
224
|
-
imageUrl: imageUrl || undefined
|
|
225
|
-
})
|
|
233
|
+
return requestAdapter.sendChat(payload)
|
|
226
234
|
.then(function (res) {
|
|
227
235
|
var data = res && res.data ? res.data : res;
|
|
228
236
|
if (data.conversationId) {
|
|
@@ -238,10 +246,63 @@ function sendMessage(query, imageUrl, displayContent) {
|
|
|
238
246
|
});
|
|
239
247
|
}
|
|
240
248
|
|
|
249
|
+
function sendMessage(query, imageUrl, displayContent) {
|
|
250
|
+
if (!ensureLogin()) {
|
|
251
|
+
return Promise.resolve();
|
|
252
|
+
}
|
|
253
|
+
state.lastUserQuery = displayContent || query || '';
|
|
254
|
+
state.messages.push({
|
|
255
|
+
role: 'user',
|
|
256
|
+
content: displayContent || query,
|
|
257
|
+
imageUrls: imageUrl ? [imageUrl] : []
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return doSendChat({
|
|
261
|
+
conversationId: state.conversationId || undefined,
|
|
262
|
+
query: query || undefined,
|
|
263
|
+
imageUrl: imageUrl || undefined,
|
|
264
|
+
inputs: state.pendingInputs || {}
|
|
265
|
+
}).then(function () {
|
|
266
|
+
state.pendingInputs = {};
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function submitInputs(inputs) {
|
|
271
|
+
inputs = inputs || {};
|
|
272
|
+
var hasOwn = Object.prototype.hasOwnProperty;
|
|
273
|
+
for (var key in inputs) {
|
|
274
|
+
if (hasOwn.call(inputs, key)) {
|
|
275
|
+
state.pendingInputs[key] = inputs[key];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
state.renderConfig = null;
|
|
279
|
+
state.pendingImageUrl = '';
|
|
280
|
+
notifyUpdate();
|
|
281
|
+
|
|
282
|
+
if (!ensureLogin()) {
|
|
283
|
+
return Promise.resolve();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return doSendChat({
|
|
287
|
+
conversationId: state.conversationId || undefined,
|
|
288
|
+
query: state.lastUserQuery || undefined,
|
|
289
|
+
inputs: state.pendingInputs
|
|
290
|
+
}).then(function () {
|
|
291
|
+
state.pendingInputs = {};
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function cancelForm() {
|
|
296
|
+
state.renderConfig = null;
|
|
297
|
+
state.pendingInputs = {};
|
|
298
|
+
state.pendingImageUrl = '';
|
|
299
|
+
notifyUpdate();
|
|
300
|
+
}
|
|
301
|
+
|
|
241
302
|
function handleSend() {
|
|
242
303
|
var query = (state.inputText || '').trim();
|
|
243
304
|
var imageUrl = state.pendingImageUrl;
|
|
244
|
-
if ((!query && !imageUrl) || state.loading || state.uploading || state.iconPickMode) {
|
|
305
|
+
if ((!query && !imageUrl) || state.loading || state.uploading || state.iconPickMode || state.renderConfig) {
|
|
245
306
|
return Promise.resolve();
|
|
246
307
|
}
|
|
247
308
|
var displayContent = query || (imageUrl ? '[图片]' : '');
|
|
@@ -272,6 +333,8 @@ module.exports = {
|
|
|
272
333
|
chooseImage: chooseImage,
|
|
273
334
|
pickCategoryIcon: pickCategoryIcon,
|
|
274
335
|
cancelIconPick: cancelIconPick,
|
|
336
|
+
submitInputs: submitInputs,
|
|
337
|
+
cancelForm: cancelForm,
|
|
275
338
|
sendQuick: sendQuick,
|
|
276
339
|
handleSend: handleSend,
|
|
277
340
|
openConversation: openConversation,
|