rvis-aiui-kit 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 +22 -0
- package/components/image/index.ink +44 -0
- package/components/list/index.ink +198 -0
- package/components/list/readme.md +71 -0
- package/components/markdown/index.ink +109 -0
- package/components/markdown/parser.js +149 -0
- package/components/markdown/readme.md +50 -0
- package/components/model-list/index.ink +181 -0
- package/components/model-list/readme.md +111 -0
- package/components/paragraph/index.ink +61 -0
- package/components/paragraph/readme.md +17 -0
- package/components/table/index.ink +272 -0
- package/components/table/readme.md +83 -0
- package/package.json +35 -0
- package/sdk/README.md +1013 -0
- package/sdk/api-map.js +25 -0
- package/sdk/core/client.js +641 -0
- package/sdk/core/constants.js +10 -0
- package/sdk/core/transport.js +55 -0
- package/sdk/index.js +19 -0
- package/sdk/modules/audio/index.js +177 -0
- package/sdk/modules/audio/readme.md +136 -0
- package/sdk/modules/camera/index.js +380 -0
- package/sdk/modules/camera/readme.md +302 -0
- package/sdk/modules/device-context/index.js +206 -0
- package/sdk/modules/device-context/readme.md +243 -0
- package/sdk/modules/face/index.js +108 -0
- package/sdk/modules/face/readme.md +196 -0
- package/sdk/modules/motion/index.js +116 -0
- package/sdk/modules/motion/readme.md +148 -0
- package/sdk/modules/notification/index.js +192 -0
- package/sdk/modules/notification/readme.md +175 -0
- package/sdk/modules/offline-command/index.js +265 -0
- package/sdk/modules/offline-command/readme.md +143 -0
- package/sdk/modules/screen/index.js +64 -0
- package/sdk/modules/screen/readme.md +110 -0
- package/sdk/modules/tts/index.js +75 -0
- package/sdk/modules/tts/readme.md +162 -0
- package/sdk/utils/api-builder.js +16 -0
- package/sdk/utils/case.js +19 -0
- package/sdk/utils/errors.js +32 -0
- package/sdk/utils/events.js +22 -0
- package/sdk/utils/message.js +63 -0
- package/sdk/utils/request-id.js +18 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Notification API
|
|
2
|
+
|
|
3
|
+
Notification 模块通过 `glass3.notification` 显示 Native 消息通知。
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import glass3 from 'rvis-aiui-kit';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
以上路径适用于 `pages` 下的一级页面,其他目录层级需要相应调整相对路径。
|
|
10
|
+
|
|
11
|
+
| API | 说明 |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `notification.show` | 显示一条 Native 消息通知。 |
|
|
14
|
+
| `notification.hide` | 隐藏指定的消息通知。 |
|
|
15
|
+
|
|
16
|
+
## `notification.show`
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
const result = await glass3.notification.show({
|
|
20
|
+
text: '思考中',
|
|
21
|
+
level: 'active',
|
|
22
|
+
position: 'topCenter',
|
|
23
|
+
durationMs: 8000,
|
|
24
|
+
animation: 'thinking',
|
|
25
|
+
resident: true
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 参数
|
|
30
|
+
|
|
31
|
+
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
|
32
|
+
| --- | --- | --- | --- | --- |
|
|
33
|
+
| `title` | `string` | 条件必填 | 无 | 通知标题。保留首尾空白,允许显式传空字符串。 |
|
|
34
|
+
| `text` | `string` | 条件必填 | 无 | 通知正文。保留首尾空白,允许显式传空字符串。 |
|
|
35
|
+
| `level` | `string` | 否 | `active` | 通知级别。 |
|
|
36
|
+
| `position` | `string` | 否 | Native 默认值 | 通知显示位置。 |
|
|
37
|
+
| `durationMs` | `number` | 否 | Native 默认值 | 显示时长,必须是大于 `0` 的整数;`0` 没有特殊含义。 |
|
|
38
|
+
| `animation` | `string` | 否 | `idle` | 通知使用的表情动效。JS 调用方可不传,SDK 会向 Native 补充 `idle`。 |
|
|
39
|
+
| `resident` | `boolean` | 否 | Native 默认值 | 是否常驻显示。传 `true` 时通知不会自动隐藏。 |
|
|
40
|
+
|
|
41
|
+
`title` 和 `text` 至少需要提供一个字符串字段。SDK 会原样传递字符串(包括空字符串和纯空白字符串),用于创建无文案的常驻通知;未提供这两个字段时仍会抛出参数错误。
|
|
42
|
+
|
|
43
|
+
`level` 为 `active` 或 `critical` 时原样传递;未提供时默认 `active`。传入其他值时,SDK 打印 warning 并按 `active` 处理,不抛出参数错误。
|
|
44
|
+
|
|
45
|
+
`position` 可选值:
|
|
46
|
+
|
|
47
|
+
- `topLeft`
|
|
48
|
+
- `topCenter`
|
|
49
|
+
- `topRight`
|
|
50
|
+
- `bottomLeft`
|
|
51
|
+
- `bottomCenter`
|
|
52
|
+
- `bottomRight`
|
|
53
|
+
|
|
54
|
+
`animation` 可选值:
|
|
55
|
+
|
|
56
|
+
| 值 | 中文描述 |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `idle` | 自然呼吸 |
|
|
59
|
+
| `blink` | 轻微眨眼 |
|
|
60
|
+
| `thinking` | 思考中 |
|
|
61
|
+
| `calm` | 平静状态 |
|
|
62
|
+
| `nod` | 点头 |
|
|
63
|
+
| `bow` | 鞠躬 |
|
|
64
|
+
| `salute` | 敬礼 |
|
|
65
|
+
| `photo` | 拍照动作与快门反馈 |
|
|
66
|
+
| `face` | 人脸识别动作 |
|
|
67
|
+
| `scan` | 扫码动作与扫描光束 |
|
|
68
|
+
| `success` | 工具完成动作 |
|
|
69
|
+
| `listening` | 用户说话时产生水波纹 |
|
|
70
|
+
| `speaking` | 眨眼与星光,表示乐奇正在说话 |
|
|
71
|
+
| `enter` | 传送圈打开,球体进场 |
|
|
72
|
+
| `exit` | 球体进入传送圈并退场 |
|
|
73
|
+
|
|
74
|
+
未提供 `position`、`durationMs` 或 `resident` 时,SDK 不会补充这些字段。SDK 始终会补充默认的 `level: "active"` 和 `animation: "idle"`。
|
|
75
|
+
|
|
76
|
+
### 返回值
|
|
77
|
+
|
|
78
|
+
Promise 在 fetch 已受理且收到成功的 `toolResult` 事件后完成:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
{
|
|
82
|
+
requestId: '本次通知 UUID',
|
|
83
|
+
ok: true
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Native 返回的实际 `durationMs` 当前不会包含在公开返回值中。
|
|
88
|
+
|
|
89
|
+
### Native 映射
|
|
90
|
+
|
|
91
|
+
| 字段 | 值 |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `namespace` | `rokid.tools` |
|
|
94
|
+
| `method` | `invoke` |
|
|
95
|
+
| `toolName` | `showNotification` |
|
|
96
|
+
| `toolAction` | `start` |
|
|
97
|
+
|
|
98
|
+
请求协议版本为字符串 `"2.0.0"`。
|
|
99
|
+
|
|
100
|
+
### 页面接入
|
|
101
|
+
|
|
102
|
+
页面需要把宿主消息交给 SDK,否则等待结果的 Promise 不会完成:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
import glass3 from 'rvis-aiui-kit';
|
|
106
|
+
|
|
107
|
+
export default {
|
|
108
|
+
onMessage(messageEvent) {
|
|
109
|
+
glass3.handleMessage(messageEvent);
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
onUnload() {
|
|
113
|
+
glass3.dispose();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 错误
|
|
119
|
+
|
|
120
|
+
不支持的字段、无效的 `position` / `animation`、无效时长或非布尔类型的 `resident` 会抛出错误码为 `INVALID_PARAMS` 的 `Glass3Error`。非法 `level` 仅打印 warning 并回退到 `active`。Native 返回 `ok: false` 时,Promise 会以 `NATIVE_EVENT_ERROR` 拒绝。
|
|
121
|
+
|
|
122
|
+
## `notification.hide`
|
|
123
|
+
|
|
124
|
+
隐藏一次由 `notification.show` 显示的通知:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
const hiddenResult = await glass3.notification.hide({
|
|
128
|
+
requestId: result.requestId
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 参数
|
|
133
|
+
|
|
134
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
135
|
+
| --- | --- | --- | --- |
|
|
136
|
+
| `requestId` | `string` | 是 | `notification.show` 返回的通知 requestId。只允许传该字段。 |
|
|
137
|
+
|
|
138
|
+
无论目标通知是否常驻,SDK 都会将参数转换成相同的 Native `args`:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
{
|
|
142
|
+
targetRequestId: result.requestId
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
hide 调用会生成自己的独立 requestId,不会复用目标通知的 requestId。
|
|
147
|
+
|
|
148
|
+
### 返回值
|
|
149
|
+
|
|
150
|
+
Promise 在 fetch 已受理且收到 hide 调用自身 requestId 对应的成功 `toolResult` 后完成:
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
{
|
|
154
|
+
requestId: '本次 hide 调用的 UUID',
|
|
155
|
+
ok: true
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
当前 Native 协议没有约定必须匹配的状态字符串,因此 SDK 使用 `result.ok === true` 作为隐藏完成条件。
|
|
160
|
+
|
|
161
|
+
### Native 映射
|
|
162
|
+
|
|
163
|
+
| 字段 | 值 |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| `namespace` | `rokid.tools` |
|
|
166
|
+
| `method` | `invoke` |
|
|
167
|
+
| `toolName` | `showNotification` |
|
|
168
|
+
| `toolAction` | `stop` |
|
|
169
|
+
|
|
170
|
+
请求协议版本为字符串 `"2.0.0"`。
|
|
171
|
+
|
|
172
|
+
### 错误
|
|
173
|
+
|
|
174
|
+
- requestId 缺失、不是非空字符串或包含其他字段时,Promise 会抛出错误码为 `INVALID_PARAMS` 的 `Glass3Error`。
|
|
175
|
+
- fetch 失败、响应协议不正确、Native 未受理或事件返回 `ok: false` 时,Promise 会按通用 RPC 错误规则抛出 `Glass3Error`。
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COMPLETION_EVENT,
|
|
3
|
+
COMPLETION_READY_EVENT_STREAM
|
|
4
|
+
} from '../../core/constants.js';
|
|
5
|
+
import { snakeToCamelCase } from '../../utils/case.js';
|
|
6
|
+
import { Glass3Error } from '../../utils/errors.js';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_LANGUAGE = 'ZH_CN';
|
|
9
|
+
|
|
10
|
+
function createParameterError(message) {
|
|
11
|
+
return new Glass3Error(message, {
|
|
12
|
+
code: 'INVALID_PARAMS',
|
|
13
|
+
stage: 'params',
|
|
14
|
+
namespace: 'rokid.tools',
|
|
15
|
+
method: 'invoke'
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function validatePhrase(phrase, commandIndex, phraseIndex) {
|
|
20
|
+
if (!phrase || typeof phrase !== 'object' || Array.isArray(phrase)) {
|
|
21
|
+
throw createParameterError(
|
|
22
|
+
`offlineCommand.register commands[${commandIndex}].phrases[${phraseIndex}] must be an object`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const fields = Object.keys(phrase);
|
|
27
|
+
fields.forEach(name => {
|
|
28
|
+
if (name !== 'text' && name !== 'pinyin') {
|
|
29
|
+
throw createParameterError(
|
|
30
|
+
`offlineCommand.register phrase does not support ${name}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (typeof phrase.text !== 'string' || !phrase.text.trim()) {
|
|
36
|
+
throw createParameterError(
|
|
37
|
+
`offlineCommand.register commands[${commandIndex}].phrases[${phraseIndex}].text must be a non-empty string`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
phrase.pinyin !== undefined &&
|
|
43
|
+
(typeof phrase.pinyin !== 'string' || !phrase.pinyin.trim())
|
|
44
|
+
) {
|
|
45
|
+
throw createParameterError(
|
|
46
|
+
`offlineCommand.register commands[${commandIndex}].phrases[${phraseIndex}].pinyin must be a non-empty string`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const nativePhrase = { text: phrase.text };
|
|
51
|
+
if (phrase.pinyin !== undefined) {
|
|
52
|
+
nativePhrase.pinyin = phrase.pinyin;
|
|
53
|
+
}
|
|
54
|
+
return nativePhrase;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateCommand(command, commandIndex) {
|
|
58
|
+
if (!command || typeof command !== 'object' || Array.isArray(command)) {
|
|
59
|
+
throw createParameterError(
|
|
60
|
+
`offlineCommand.register commands[${commandIndex}] must be an object`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const fields = Object.keys(command);
|
|
65
|
+
fields.forEach(name => {
|
|
66
|
+
if (name !== 'id' && name !== 'phrases') {
|
|
67
|
+
throw createParameterError(
|
|
68
|
+
`offlineCommand.register command does not support ${name}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (typeof command.id !== 'string' || !command.id.trim()) {
|
|
74
|
+
throw createParameterError(
|
|
75
|
+
`offlineCommand.register commands[${commandIndex}].id must be a non-empty string`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!Array.isArray(command.phrases) || command.phrases.length === 0) {
|
|
80
|
+
throw createParameterError(
|
|
81
|
+
`offlineCommand.register commands[${commandIndex}].phrases must be a non-empty array`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
id: command.id,
|
|
87
|
+
phrases: command.phrases.map((phrase, phraseIndex) => (
|
|
88
|
+
validatePhrase(phrase, commandIndex, phraseIndex)
|
|
89
|
+
))
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildRegisterArgs(args) {
|
|
94
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) {
|
|
95
|
+
throw createParameterError(
|
|
96
|
+
'offlineCommand.register parameters must be an object'
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const fields = Object.keys(args);
|
|
101
|
+
fields.forEach(name => {
|
|
102
|
+
if (name !== 'language' && name !== 'commands') {
|
|
103
|
+
throw createParameterError(
|
|
104
|
+
`offlineCommand.register does not support ${name}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const language = args.language === undefined
|
|
110
|
+
? DEFAULT_LANGUAGE
|
|
111
|
+
: args.language;
|
|
112
|
+
if (typeof language !== 'string' || !language.trim()) {
|
|
113
|
+
throw createParameterError(
|
|
114
|
+
'offlineCommand.register language must be a non-empty string'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!Array.isArray(args.commands) || args.commands.length === 0) {
|
|
119
|
+
throw createParameterError(
|
|
120
|
+
'offlineCommand.register commands must be a non-empty array'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
language,
|
|
126
|
+
commands: args.commands.map(validateCommand)
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function buildUnregisterArgs(args) {
|
|
131
|
+
if (
|
|
132
|
+
!args ||
|
|
133
|
+
typeof args !== 'object' ||
|
|
134
|
+
Array.isArray(args) ||
|
|
135
|
+
typeof args.requestId !== 'string' ||
|
|
136
|
+
!args.requestId.trim()
|
|
137
|
+
) {
|
|
138
|
+
throw createParameterError(
|
|
139
|
+
'offlineCommand.unregister requires a requestId'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const fields = Object.keys(args);
|
|
144
|
+
if (fields.length !== 1 || fields[0] !== 'requestId') {
|
|
145
|
+
throw createParameterError(
|
|
146
|
+
'offlineCommand.unregister only supports requestId'
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
targetRequestId: args.requestId
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function selectRegisterResponseResult(response) {
|
|
156
|
+
return Object.assign(
|
|
157
|
+
{ ok: response.ok },
|
|
158
|
+
snakeToCamelCase(response.result)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function selectRegisterReadyResult(requestId, result) {
|
|
163
|
+
const readyResult = {
|
|
164
|
+
requestId,
|
|
165
|
+
ok: result.ok !== false,
|
|
166
|
+
state: result.state
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if (result.commandCount !== undefined) {
|
|
170
|
+
readyResult.commandCount = result.commandCount;
|
|
171
|
+
}
|
|
172
|
+
if (result.phraseCount !== undefined) {
|
|
173
|
+
readyResult.phraseCount = result.phraseCount;
|
|
174
|
+
}
|
|
175
|
+
return readyResult;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function isAcceptedOrStateResponseResult(
|
|
179
|
+
result,
|
|
180
|
+
requestId,
|
|
181
|
+
toolAction,
|
|
182
|
+
state
|
|
183
|
+
) {
|
|
184
|
+
if (!result || typeof result !== 'object') return false;
|
|
185
|
+
if (result.state === state) return true;
|
|
186
|
+
|
|
187
|
+
return result.accepted === true &&
|
|
188
|
+
result.requestId === requestId &&
|
|
189
|
+
result.toolName === 'offlineCommand' &&
|
|
190
|
+
result.toolAction === toolAction;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const offlineCommandRegister = {
|
|
194
|
+
publicModule: 'offlineCommand',
|
|
195
|
+
publicMethod: 'register',
|
|
196
|
+
|
|
197
|
+
namespace: 'rokid.tools',
|
|
198
|
+
method: 'invoke',
|
|
199
|
+
toolName: 'offlineCommand',
|
|
200
|
+
toolAction: 'start',
|
|
201
|
+
|
|
202
|
+
completion: COMPLETION_READY_EVENT_STREAM,
|
|
203
|
+
eventNamespace: 'rokid.tools',
|
|
204
|
+
eventName: 'toolResult',
|
|
205
|
+
responseCompletionStates: ['started'],
|
|
206
|
+
buildArgs: buildRegisterArgs,
|
|
207
|
+
transformEventResult: snakeToCamelCase,
|
|
208
|
+
selectResponseResult: selectRegisterResponseResult,
|
|
209
|
+
isAcceptedResponseResult(result, requestId) {
|
|
210
|
+
return isAcceptedOrStateResponseResult(
|
|
211
|
+
result,
|
|
212
|
+
requestId,
|
|
213
|
+
'start',
|
|
214
|
+
'started'
|
|
215
|
+
);
|
|
216
|
+
},
|
|
217
|
+
isErrorResult(result) {
|
|
218
|
+
return Boolean(result && result.ok === false);
|
|
219
|
+
},
|
|
220
|
+
isReadyResult(result) {
|
|
221
|
+
return Boolean(result && result.ok === true && result.state === 'started');
|
|
222
|
+
},
|
|
223
|
+
isStreamTerminalResult(result) {
|
|
224
|
+
return Boolean(result && result.state === 'canceled');
|
|
225
|
+
},
|
|
226
|
+
selectReadyResult: selectRegisterReadyResult
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const offlineCommandUnregister = {
|
|
230
|
+
publicModule: 'offlineCommand',
|
|
231
|
+
publicMethod: 'unregister',
|
|
232
|
+
|
|
233
|
+
namespace: 'rokid.tools',
|
|
234
|
+
method: 'invoke',
|
|
235
|
+
toolName: 'offlineCommand',
|
|
236
|
+
toolAction: 'stop',
|
|
237
|
+
|
|
238
|
+
completion: COMPLETION_EVENT,
|
|
239
|
+
eventNamespace: 'rokid.tools',
|
|
240
|
+
eventName: 'toolResult',
|
|
241
|
+
terminalStates: ['stopped'],
|
|
242
|
+
responseCompletionStates: ['stopped'],
|
|
243
|
+
buildArgs: buildUnregisterArgs,
|
|
244
|
+
transformEventResult: snakeToCamelCase,
|
|
245
|
+
isAcceptedResponseResult(result, requestId) {
|
|
246
|
+
return isAcceptedOrStateResponseResult(
|
|
247
|
+
result,
|
|
248
|
+
requestId,
|
|
249
|
+
'stop',
|
|
250
|
+
'stopped'
|
|
251
|
+
);
|
|
252
|
+
},
|
|
253
|
+
releaseTargetInvocation: {
|
|
254
|
+
publicModule: 'offlineCommand',
|
|
255
|
+
publicMethod: 'register'
|
|
256
|
+
},
|
|
257
|
+
selectEventResult(eventData) {
|
|
258
|
+
return Object.assign(
|
|
259
|
+
{ requestId: eventData.requestId },
|
|
260
|
+
snakeToCamelCase(eventData.result)
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export default [offlineCommandRegister, offlineCommandUnregister];
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Offline Command API
|
|
2
|
+
|
|
3
|
+
Offline Command 模块通过 `glass3.offlineCommand` 注册和注销页面持有的离线语音指令批次。
|
|
4
|
+
|
|
5
|
+
页面必须将 `onMessage(messageEvent)` 转发给 `glass3.handleMessage(messageEvent)`,命中和终止事件才能关联到对应注册请求。
|
|
6
|
+
|
|
7
|
+
## API 列表
|
|
8
|
+
|
|
9
|
+
| API | 说明 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `offlineCommand.register` | 注册一批离线指令,并持续接收命中事件。 |
|
|
12
|
+
| `offlineCommand.unregister` | 注销指定的离线指令批次。 |
|
|
13
|
+
|
|
14
|
+
## `offlineCommand.register`
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const registration = await glass3.offlineCommand.register(
|
|
18
|
+
{
|
|
19
|
+
commands: [
|
|
20
|
+
{
|
|
21
|
+
id: 'next_step',
|
|
22
|
+
phrases: [
|
|
23
|
+
{ text: '下一步', pinyin: 'xia yi bu' },
|
|
24
|
+
{ text: '继续', pinyin: 'ji xu' }
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
onEvent(result) {
|
|
31
|
+
console.log('offline command event:', JSON.stringify(result));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 参数
|
|
38
|
+
|
|
39
|
+
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
|
40
|
+
| --- | --- | --- | --- | --- |
|
|
41
|
+
| `language` | `string` | 否 | `ZH_CN` | 传给 Native 的语言标识。SDK 不限制枚举值。 |
|
|
42
|
+
| `commands` | `array` | 是 | - | 非空的指令数组。 |
|
|
43
|
+
|
|
44
|
+
每个 command 包含:
|
|
45
|
+
|
|
46
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
47
|
+
| --- | --- | --- | --- |
|
|
48
|
+
| `id` | `string` | 是 | 非空业务指令 ID。 |
|
|
49
|
+
| `phrases` | `array` | 是 | 非空的触发短语数组。 |
|
|
50
|
+
|
|
51
|
+
每个 phrase 包含:
|
|
52
|
+
|
|
53
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
54
|
+
| --- | --- | --- | --- |
|
|
55
|
+
| `text` | `string` | 是 | 非空触发文本。 |
|
|
56
|
+
| `pinyin` | `string` | 否 | 非空拼音文本;省略时不会传给 Native。 |
|
|
57
|
+
|
|
58
|
+
SDK 校验对象、数组和字段类型,但不限制语言枚举、数组数量、字符串长度、重复 command ID 或重复 phrase。参数是闭集,不接受 snake_case 或未记录字段。
|
|
59
|
+
|
|
60
|
+
开发规则:每个 phrase 都必须填写非空 `pinyin`,不要因为 SDK 允许省略而省略拼音。SDK 注册的离线指令必须通过 `register` 第二个参数中的 `onEvent` 接收,以 `result.commandId` 区分命中;不能使用 `onVoiceWakeup` / `onVoiceWakeUp` 或 `event.keyword` 监听。后者属于宿主唤醒词事件,不是 SDK 离线指令命中通道。
|
|
61
|
+
|
|
62
|
+
下行映射为 `rokid.tools / invoke / offlineCommand / start`。未传 `language` 时,Native `args.language` 为 `ZH_CN`。
|
|
63
|
+
|
|
64
|
+
### 注册完成
|
|
65
|
+
|
|
66
|
+
started 可能通过 response 或 event 到达,两者到达顺序不固定。SDK 记录先到的 started,并在同一请求的 response 已受理后完成 Promise;普通 `accepted` response 本身不会结束 Promise。
|
|
67
|
+
|
|
68
|
+
JS 返回值全部使用驼峰字段:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
{
|
|
72
|
+
requestId: 'cmd-1',
|
|
73
|
+
ok: true,
|
|
74
|
+
state: 'started',
|
|
75
|
+
commandCount: 1,
|
|
76
|
+
phraseCount: 2
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
如果 started 消息没有携带数量,对应 `commandCount` 或 `phraseCount` 字段不会出现在返回值中。
|
|
81
|
+
|
|
82
|
+
### 命中事件
|
|
83
|
+
|
|
84
|
+
同一个 start requestId 可以多次进入 `onEvent`:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
{
|
|
88
|
+
ok: true,
|
|
89
|
+
commandId: 'next_step',
|
|
90
|
+
phrase: '下一步',
|
|
91
|
+
timestamp: 1785403248395
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Native 下划线字段会递归转换为驼峰。
|
|
96
|
+
|
|
97
|
+
### 页面关闭
|
|
98
|
+
|
|
99
|
+
AIX 页面关闭时,Native 在原 start requestId 上发送唯一终态事件:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
{
|
|
103
|
+
ok: true,
|
|
104
|
+
state: 'canceled',
|
|
105
|
+
reason: 'owner_release'
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
该结果进入 `onEvent` 后,SDK 清理本次注册监听。
|
|
110
|
+
|
|
111
|
+
## `offlineCommand.unregister`
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const result = await glass3.offlineCommand.unregister({
|
|
115
|
+
requestId: registration.requestId
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
JS 侧只接受 register 返回的非空 `requestId`。SDK 为 unregister 生成独立 requestId,并将参数转换为:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
{
|
|
123
|
+
targetRequestId: registration.requestId
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
下行映射为 `rokid.tools / invoke / offlineCommand / stop`。
|
|
128
|
+
|
|
129
|
+
stopped 可能通过 response 或 event 到达,两者到达顺序不固定。SDK 记录先到的 stopped,并在 response 已受理后完成 Promise:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
{
|
|
133
|
+
requestId: 'cmd-2',
|
|
134
|
+
ok: true,
|
|
135
|
+
state: 'stopped'
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
unregister 成功后,SDK 会释放目标 register 的事件监听。Native 不需要向原 start requestId 补发 stopped event。
|
|
140
|
+
|
|
141
|
+
## 生命周期
|
|
142
|
+
|
|
143
|
+
页面应保存 register 返回的 `requestId`。示例使用系统默认退出行为,在 `onUnload()` 中发起 unregister,不等待 stopped 结果,然后立即调用 `glass3.dispose()`;Native 的 `owner_release` 事件作为页面直接关闭时的终态兜底。
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { COMPLETION_EVENT } from '../../core/constants.js';
|
|
2
|
+
import { snakeToCamelCase } from '../../utils/case.js';
|
|
3
|
+
import { Glass3Error } from '../../utils/errors.js';
|
|
4
|
+
|
|
5
|
+
function createParameterError(message) {
|
|
6
|
+
return new Glass3Error(message, {
|
|
7
|
+
code: 'INVALID_PARAMS',
|
|
8
|
+
stage: 'params',
|
|
9
|
+
namespace: 'rokid.tools',
|
|
10
|
+
method: 'invoke'
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildScreenArgs(powerState, publicMethod) {
|
|
15
|
+
return args => {
|
|
16
|
+
if (args !== undefined && args !== null) {
|
|
17
|
+
if (
|
|
18
|
+
typeof args !== 'object' ||
|
|
19
|
+
Array.isArray(args) ||
|
|
20
|
+
Object.keys(args).length !== 0
|
|
21
|
+
) {
|
|
22
|
+
throw createParameterError(
|
|
23
|
+
`screen.${publicMethod} does not accept parameters`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { state: powerState };
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function selectEventResult(eventData) {
|
|
33
|
+
return Object.assign(
|
|
34
|
+
{ requestId: eventData.requestId },
|
|
35
|
+
snakeToCamelCase(eventData.result)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createScreenDefinition(publicMethod, powerState) {
|
|
40
|
+
return {
|
|
41
|
+
publicModule: 'screen',
|
|
42
|
+
publicMethod,
|
|
43
|
+
|
|
44
|
+
namespace: 'rokid.tools',
|
|
45
|
+
method: 'invoke',
|
|
46
|
+
toolName: 'screenOff',
|
|
47
|
+
toolAction: 'start',
|
|
48
|
+
|
|
49
|
+
completion: COMPLETION_EVENT,
|
|
50
|
+
eventNamespace: 'rokid.tools',
|
|
51
|
+
eventName: 'toolResult',
|
|
52
|
+
buildArgs: buildScreenArgs(powerState, publicMethod),
|
|
53
|
+
transformEventResult: snakeToCamelCase,
|
|
54
|
+
isTerminalResult(result) {
|
|
55
|
+
return Boolean(result && result.screen === powerState);
|
|
56
|
+
},
|
|
57
|
+
selectEventResult
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default [
|
|
62
|
+
createScreenDefinition('turnOff', 'off'),
|
|
63
|
+
createScreenDefinition('turnOn', 'on')
|
|
64
|
+
];
|