# 商旅对接AI语音前端技术方案
# 背景
- ecs通过语音机器人收集用户的意图后转换成指令和参数发送给商旅,商旅完成后续的操作。
# 难点
- ecs和商旅数据如何传输
- 如何利用现有的组件和方法完成用户的意图
- 如何同步操作和异步操作按顺序执行
- 有资源没有指令或者有指令资源还没加载完成怎么办
# ecs和商旅页面通过postMessage来传输数据
- 当ecs平台解析完用户的语音之后,会通过postMessage将指令和参数发给商旅,商旅监听message,当收到消息时就执行指令对应的操作
- robotData 是ecs传过来的数据,ivInstruction是指令,ivIns 是约定好的指令集和每个指令对应的操作,isCurInsCanExec判断当前指令是否可以执行。
startViIns(robotData: AnyObject) {
this.robotData = robotData;
const { ivInstruction } = robotData;
let insArr: AnyObject[] = [];
insArr = ivIns[ivInstruction];
if (!insArr) {
console.log(`无语音${ivInstruction}指令对应的操作`);
return;
}
this.needExecInsArr = insArr;
if (this.isCurInsCanExec()) {
this.curExecInsIndex = 0;
this.execAct();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 如何利用现有的组件和方法完成用户的意图
- 现有的组件代码不动,语音的方法重新写一个.ts文件,在该ts文件中调原组件中的方法,然后用mixins混入到对应的组件中,这样实现了目的。
// ts文件中 调用原组件中的方法getTravelStandard
cmdGetTravelStandard() {
this.getTravelStandard();
return new Promise(resolve => {
this.$on("standardPostmessageSuc", (res: AnyObject) => {
resolve(res);
});
});
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 有资源没有指令或者有指令资源还没加载完成怎么办
- 新创建.ts文件要做的事情就是实现某个指令对应的小操作,然后将这些小操作收集起来,以便接收到指令时执行指令对应的操作。小操作收集完成还需要通知 调度中心,该资源加载完成,随时可以执行。
// 收集小操作
addCmdAndAct() {
const componentName = this.$options.name;
const acts = {
actGoOrderFlight: {
componentName,
fn: this.actGoOrderFlight
}
};
this.$iv.addAct(componentName, acts);
}
// 收集小操作,并通知调度中心资源已加载完成
mounted() {
const componentName = this.$options.name;
this.addCmdAndAct();
this.$iv.actLoadedNotify(componentName);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 如何同步操作和异步操作按顺序执行
- 这里通过yield函数 和 promise来实现
// 某个指令下的操作所包含的小操作
*actGoOrderFlight() {
const persons = [this.currentUserInfo];
const {
travelBeginDate: startDate,
endDate = "",
originCity: startCityName,
destinationCity: endCityName,
travelType: businessType
} = this.$iv.robotData;
this.fiterIv = "1";
const startCity: AnyObject = yield this.cmdGetCityInfo({
searchValue: startCityName,
businessType
});
yield this.cmdSetCityFrom({ city: startCity });
const endCity: AnyObject = yield this.cmdGetCityInfo({
searchValue: endCityName,
businessType
});
yield this.cmdSetCityTo({ city: endCity });
yield this.cmdSetCheckInOut({ startDate, endDate });
yield this.cmdSetSelectedPersons({ persons });
yield this.cmdGetTravelStandard();
yield this.cmdOnGoOrder();
this.fiterIv = "2";
}
// 执行收集的小操作
execAct() {
const curIns = this.needExecInsArr[this.curExecInsIndex];
const { exec, componentName, key } = curIns;
if (exec === "0" && this.actionPoor[componentName]) {
// 未执行,且对应的action已加载好
curIns.exec = "1";
const curAct = this.actionPoor[componentName][key];
const { fn: Fun } = curAct;
const gen = Fun();
this.flow(gen, {});
}
}
// 让同步和异步执行
flow(gen: AnyObject, res: AnyObject) {
const item = gen.next(res);
const { value, done } = item;
if (done) {
this.doneAndNext();
return { value, done };
}
if (value instanceof Promise) {
value.then(e => this.flow(gen, e));
} else {
this.flow(gen, {});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 语音指令及对应的商旅页面路由,当下面的路由页面被加载了就通知ecs,然后ecs就知道当前哪些指令可以被执行
| 语音指令 | 页面路由(path) |
|---|---|
| jumpHotel | /trip/create/triphotel |
| jumpTrain | /trip/create/triptrain |
| jumpFlight | /trip/create/tripflight |
| flightListFilter | /flight/list , /flight/roundtrip |
| trainListFilter | /train/trainlist, /train/backtrainlist |
| hotelListFilter | /hotel/list |