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.
Files changed (44) hide show
  1. package/README.md +22 -0
  2. package/components/image/index.ink +44 -0
  3. package/components/list/index.ink +198 -0
  4. package/components/list/readme.md +71 -0
  5. package/components/markdown/index.ink +109 -0
  6. package/components/markdown/parser.js +149 -0
  7. package/components/markdown/readme.md +50 -0
  8. package/components/model-list/index.ink +181 -0
  9. package/components/model-list/readme.md +111 -0
  10. package/components/paragraph/index.ink +61 -0
  11. package/components/paragraph/readme.md +17 -0
  12. package/components/table/index.ink +272 -0
  13. package/components/table/readme.md +83 -0
  14. package/package.json +35 -0
  15. package/sdk/README.md +1013 -0
  16. package/sdk/api-map.js +25 -0
  17. package/sdk/core/client.js +641 -0
  18. package/sdk/core/constants.js +10 -0
  19. package/sdk/core/transport.js +55 -0
  20. package/sdk/index.js +19 -0
  21. package/sdk/modules/audio/index.js +177 -0
  22. package/sdk/modules/audio/readme.md +136 -0
  23. package/sdk/modules/camera/index.js +380 -0
  24. package/sdk/modules/camera/readme.md +302 -0
  25. package/sdk/modules/device-context/index.js +206 -0
  26. package/sdk/modules/device-context/readme.md +243 -0
  27. package/sdk/modules/face/index.js +108 -0
  28. package/sdk/modules/face/readme.md +196 -0
  29. package/sdk/modules/motion/index.js +116 -0
  30. package/sdk/modules/motion/readme.md +148 -0
  31. package/sdk/modules/notification/index.js +192 -0
  32. package/sdk/modules/notification/readme.md +175 -0
  33. package/sdk/modules/offline-command/index.js +265 -0
  34. package/sdk/modules/offline-command/readme.md +143 -0
  35. package/sdk/modules/screen/index.js +64 -0
  36. package/sdk/modules/screen/readme.md +110 -0
  37. package/sdk/modules/tts/index.js +75 -0
  38. package/sdk/modules/tts/readme.md +162 -0
  39. package/sdk/utils/api-builder.js +16 -0
  40. package/sdk/utils/case.js +19 -0
  41. package/sdk/utils/errors.js +32 -0
  42. package/sdk/utils/events.js +22 -0
  43. package/sdk/utils/message.js +63 -0
  44. package/sdk/utils/request-id.js +18 -0
@@ -0,0 +1,110 @@
1
+ # Screen API
2
+
3
+ Screen 模块通过 `glass3.screen` 控制眼镜息屏和亮屏。
4
+
5
+ ## 方法
6
+
7
+ | JavaScript API | 说明 |
8
+ | --- | --- |
9
+ | `screen.turnOff()` | 关闭屏幕,并等待 Native 确认操作成功。 |
10
+ | `screen.turnOn()` | 点亮屏幕,并等待 Native 确认操作成功。 |
11
+
12
+ ## `screen.turnOff`
13
+
14
+ ```js
15
+ const result = await glass3.screen.turnOff();
16
+ ```
17
+
18
+ Native 映射:
19
+
20
+ ```json
21
+ {
22
+ "toolName": "screenOff",
23
+ "toolAction": "start",
24
+ "args": { "state": "off" },
25
+ "extra": {}
26
+ }
27
+ ```
28
+
29
+ ## `screen.turnOn`
30
+
31
+ ```js
32
+ const result = await glass3.screen.turnOn();
33
+ ```
34
+
35
+ Native 映射:
36
+
37
+ ```json
38
+ {
39
+ "toolName": "screenOff",
40
+ "toolAction": "start",
41
+ "args": { "state": "on" },
42
+ "extra": {}
43
+ }
44
+ ```
45
+
46
+ 两个方法都不接收参数。Native 的 accepted response 只表示请求已受理;收到
47
+ 匹配 requestId 的 `toolResult` event,并且 `result.ok: true`、`result.screen`
48
+ 等于本次请求的目标状态时,Promise 才会完成。返回值为:
49
+
50
+ ```js
51
+ {
52
+ requestId: '...',
53
+ ok: true,
54
+ changed: true,
55
+ screen: 'off'
56
+ }
57
+ ```
58
+
59
+ `changed` 表示 Native 是否实际改变了屏幕状态。如果屏幕原本已经处于目标
60
+ 状态,可能返回 `changed: false`,仍然视为调用成功。
61
+
62
+ ## 系统亮屏/息屏状态事件
63
+
64
+ 由系统负责的普通亮屏和息屏会通过页面 `onMessage(messageEvent)` 下发原始 Host RPC 事件:
65
+
66
+ ```js
67
+ {
68
+ channel: 'rokid.host.rpc',
69
+ version: '2.0.0',
70
+ kind: 'event',
71
+ namespace: 'rokid.device',
72
+ event: 'screenStateChanged',
73
+ sequence: 17,
74
+ data: {
75
+ state: 'on',
76
+ source: 'sleep_overlay',
77
+ revision: 5,
78
+ changedAtElapsedMs: 888558
79
+ }
80
+ }
81
+ ```
82
+
83
+ `data.state` 只使用 `on` 和 `off`。页面进入时可默认按亮屏处理,之后以该事件
84
+ 作为系统亮屏/息屏状态的来源。页面仍需先调用 `glass3.handleMessage(messageEvent)`
85
+ 转发 SDK 消息,再从 `messageEvent.data` 读取此事件。
86
+
87
+ 系统接管普通电源行为时,不要在 `RokidActionSingleClick` 中调用 Screen API,
88
+ 也不要调用 `event.preventDefault()`。息屏状态下页面无法依赖 `Enter` 回调;应让
89
+ Host 完成亮屏,再处理 `state: 'on'` 事件。离线“查看”等明确的业务动作仍可主动
90
+ 调用 `glass3.screen.turnOn()`。
91
+
92
+ ## 与 Notification 的显示优先级
93
+
94
+ Native notification 的显示优先级高于屏幕的息屏状态。调用
95
+ `glass3.screen.turnOff()` 不会自动隐藏已经显示的 notification;即使息屏调用
96
+ 成功,普通页面内容关闭后,notification 仍可能继续显示。
97
+
98
+ 如果业务要求息屏后不保留任何 notification,需要先保存
99
+ `glass3.notification.show()` 返回的 requestId,并主动调用:
100
+
101
+ ```js
102
+ await glass3.notification.hide({ requestId: notificationRequestId });
103
+ await glass3.screen.turnOff();
104
+ ```
105
+
106
+ Screen 模块不会自动管理或销毁 notification。
107
+
108
+ 页面需要把 `onMessage(messageEvent)` 转发给
109
+ `glass3.handleMessage(messageEvent)`,并在 `onUnload()` 中调用
110
+ `glass3.dispose()`。
@@ -0,0 +1,75 @@
1
+ import {
2
+ COMPLETION_ACCEPTED,
3
+ COMPLETION_EVENT
4
+ } from '../../core/constants.js';
5
+ import { Glass3Error } from '../../utils/errors.js';
6
+
7
+ function createParameterError(message) {
8
+ return new Glass3Error(message, {
9
+ code: 'INVALID_PARAMS',
10
+ stage: 'params',
11
+ namespace: 'rokid.tools',
12
+ method: 'invoke'
13
+ });
14
+ }
15
+
16
+ function buildStopArgs(args) {
17
+ if (
18
+ !args ||
19
+ typeof args !== 'object' ||
20
+ Array.isArray(args) ||
21
+ typeof args.requestId !== 'string' ||
22
+ !args.requestId.trim()
23
+ ) {
24
+ throw createParameterError('tts.stop requires a requestId');
25
+ }
26
+
27
+ const inputFields = Object.keys(args);
28
+ if (inputFields.length !== 1 || inputFields[0] !== 'requestId') {
29
+ throw createParameterError('tts.stop only supports requestId');
30
+ }
31
+
32
+ return {
33
+ targetRequestId: args.requestId
34
+ };
35
+ }
36
+
37
+ const ttsSpeak = {
38
+ publicModule: 'tts',
39
+ publicMethod: 'speak',
40
+
41
+ namespace: 'rokid.tools',
42
+ method: 'invoke',
43
+ toolName: 'tts',
44
+ toolAction: 'start',
45
+
46
+ completion: COMPLETION_ACCEPTED,
47
+ eventNamespace: 'rokid.tools',
48
+ eventName: 'toolResult',
49
+ terminalStates: ['finished', 'canceled']
50
+ };
51
+
52
+ const ttsStop = {
53
+ publicModule: 'tts',
54
+ publicMethod: 'stop',
55
+
56
+ namespace: 'rokid.tools',
57
+ method: 'invoke',
58
+ toolName: 'tts',
59
+ toolAction: 'stop',
60
+
61
+ completion: COMPLETION_EVENT,
62
+ eventNamespace: 'rokid.tools',
63
+ eventName: 'toolResult',
64
+ terminalStates: ['stopped'],
65
+ responseCompletionStates: ['stopped'],
66
+ buildArgs: buildStopArgs,
67
+ selectEventResult(eventData) {
68
+ return Object.assign(
69
+ { requestId: eventData.requestId },
70
+ eventData.result
71
+ );
72
+ }
73
+ };
74
+
75
+ export default [ttsSpeak, ttsStop];
@@ -0,0 +1,162 @@
1
+ # TTS API
2
+
3
+ TTS 模块通过 `glass3.tts` 暴露语音播报能力。
4
+
5
+ | API | 说明 |
6
+ | --- | --- |
7
+ | `tts.speak` | 开始播放一段 TTS。 |
8
+ | `tts.stop` | 停止指定的 TTS 播放。 |
9
+
10
+ 使用 SDK 的页面必须在 `onMessage` 中把宿主消息交给 `glass3.handleMessage`,否则无法收到播放事件。
11
+
12
+ ```js
13
+ import glass3 from 'rvis-aiui-kit';
14
+
15
+ export default {
16
+ onMessage(messageEvent) {
17
+ glass3.handleMessage(messageEvent);
18
+ },
19
+
20
+ onUnload() {
21
+ glass3.dispose();
22
+ }
23
+ };
24
+ ```
25
+
26
+ ## `tts.speak`
27
+
28
+ 开始播放一段 TTS。
29
+
30
+ ```js
31
+ const result = await glass3.tts.speak(
32
+ {
33
+ text: '你好,这是一段播报。',
34
+ queueMode: 'flush'
35
+ },
36
+ {
37
+ onEvent(eventResult) {
38
+ console.log('TTS event:', JSON.stringify(eventResult));
39
+ }
40
+ }
41
+ );
42
+ ```
43
+
44
+ ### 参数
45
+
46
+ | 字段 | 类型 | 必填 | 说明 |
47
+ | --- | --- | --- | --- |
48
+ | `text` | `string` | 是 | 需要播报的文本。当前 SDK 直接透传,不做本地校验。 |
49
+ | `queueMode` | `string` | 否 | 播放队列策略,例如 `flush`。当前 SDK 直接透传。 |
50
+
51
+ 第二个参数是可选的调用选项:
52
+
53
+ | 字段 | 类型 | 必填 | 说明 |
54
+ | --- | --- | --- | --- |
55
+ | `onEvent` | `function` | 否 | 接收当前播报过程中的事件结果。 |
56
+
57
+ ### 返回值
58
+
59
+ `tts.speak` 在 fetch 返回 Native 已受理结果后完成,不等待播报结束。
60
+
61
+ ```js
62
+ {
63
+ requestId: '本次播放的 UUID',
64
+ ok: true
65
+ }
66
+ ```
67
+
68
+ ### 事件
69
+
70
+ `onEvent` 只接收 Native 消息中的 `message.data.result`。
71
+
72
+ 常见事件:
73
+
74
+ ```js
75
+ { ok: true, state: 'started' }
76
+ { ok: true, state: 'finished' }
77
+ { ok: true, state: 'canceled' }
78
+ ```
79
+
80
+ 收到 `finished` 或 `canceled` 后,SDK 会清理本次调用的事件监听。未传 `onEvent` 时,SDK 不保留事件监听。
81
+
82
+ ### Native 映射
83
+
84
+ | 字段 | 值 |
85
+ | --- | --- |
86
+ | `namespace` | `rokid.tools` |
87
+ | `method` | `invoke` |
88
+ | `toolName` | `tts` |
89
+ | `toolAction` | `start` |
90
+
91
+ 请求协议版本为字符串 `"2.0.0"`。
92
+
93
+ ### 错误
94
+
95
+ fetch 失败、响应协议不正确或 Native 未受理时,Promise 会抛出 `Glass3Error`。常见错误码包括 `NATIVE_REQUEST_ERROR` 和 `NATIVE_REQUEST_NOT_ACCEPTED`。
96
+
97
+ ## `tts.stop`
98
+
99
+ 停止一次由 `tts.speak` 发起的播放。
100
+
101
+ ```js
102
+ const playback = await glass3.tts.speak({
103
+ text: '你好,这是一段播报。',
104
+ queueMode: 'flush'
105
+ });
106
+
107
+ const result = await glass3.tts.stop({
108
+ requestId: playback.requestId
109
+ });
110
+ ```
111
+
112
+ ### 参数
113
+
114
+ | 字段 | 类型 | 必填 | 说明 |
115
+ | --- | --- | --- | --- |
116
+ | `requestId` | `string` | 是 | `tts.speak` 返回的播放 requestId。只允许传该字段。 |
117
+
118
+ SDK 会把参数转换为 Native 的 `args`:
119
+
120
+ ```js
121
+ {
122
+ targetRequestId: playback.requestId
123
+ }
124
+ ```
125
+
126
+ `tts.stop` 会为停止调用创建一个新的 requestId。停止调用的 requestId 与 `targetRequestId` 相互独立。
127
+
128
+ ### 返回值
129
+
130
+ 请求通过校验后,Promise 在收到停止调用自身 requestId 对应的以下任一消息时完成:
131
+
132
+ - `kind: "event"` 且 `data.result.state: "stopped"`;
133
+ - `kind: "response"` 且 `result.state: "stopped"`。
134
+
135
+ SDK 会将两种消息结构统一为:
136
+
137
+ ```js
138
+ {
139
+ requestId: '本次 stop 调用的 UUID',
140
+ ok: true,
141
+ state: 'stopped'
142
+ }
143
+ ```
144
+
145
+ `tts.stop` 不提供 `onEvent`。它只等待自己的停止结果,不会主动清理或修改原 `tts.speak` 的事件监听。原播放过程仍可通过自己的 `onEvent` 接收 `canceled` 等事件。
146
+
147
+ ### Native 映射
148
+
149
+ | 字段 | 值 |
150
+ | --- | --- |
151
+ | `namespace` | `rokid.tools` |
152
+ | `method` | `invoke` |
153
+ | `toolName` | `tts` |
154
+ | `toolAction` | `stop` |
155
+
156
+ 请求协议版本为字符串 `"2.0.0"`。
157
+
158
+ ### 错误
159
+
160
+ - 参数缺失、类型错误或包含 `requestId` 以外的字段时,Promise 会抛出错误码为 `INVALID_PARAMS` 的 `Glass3Error`。
161
+ - fetch 失败、响应协议不正确或 Native 未受理时,Promise 会按通用 RPC 错误规则抛出 `Glass3Error`。
162
+ - Native 事件返回 `ok: false` 时,Promise 会抛出错误码为 `NATIVE_EVENT_ERROR` 的 `Glass3Error`。
@@ -0,0 +1,16 @@
1
+ export function createApiNamespaces(client, apiMap) {
2
+ const api = {};
3
+
4
+ apiMap.forEach(definition => {
5
+ if (!api[definition.publicModule]) {
6
+ api[definition.publicModule] = {};
7
+ }
8
+
9
+ api[definition.publicModule][definition.publicMethod] =
10
+ function invokeMappedMethod(args, options) {
11
+ return client.invoke(definition, args, options);
12
+ };
13
+ });
14
+
15
+ return api;
16
+ }
@@ -0,0 +1,19 @@
1
+ export function snakeStringToCamelCase(value) {
2
+ return value.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
3
+ }
4
+
5
+ export function snakeToCamelCase(value) {
6
+ if (Array.isArray(value)) {
7
+ return value.map(item => snakeToCamelCase(item));
8
+ }
9
+
10
+ if (!value || typeof value !== 'object') {
11
+ return value;
12
+ }
13
+
14
+ const converted = {};
15
+ Object.keys(value).forEach(key => {
16
+ converted[snakeStringToCamelCase(key)] = snakeToCamelCase(value[key]);
17
+ });
18
+ return converted;
19
+ }
@@ -0,0 +1,32 @@
1
+ export class Glass3Error extends Error {
2
+ constructor(message, options) {
3
+ super(message);
4
+ const errorOptions = options || {};
5
+
6
+ this.name = 'Glass3Error';
7
+ this.code = errorOptions.code || 'GLASS3_ERROR';
8
+ this.stage = errorOptions.stage;
9
+ this.requestId = errorOptions.requestId;
10
+ this.namespace = errorOptions.namespace;
11
+ this.method = errorOptions.method;
12
+ this.details = errorOptions.details;
13
+ this.cause = errorOptions.cause;
14
+ }
15
+ }
16
+
17
+ export function normalizeError(error, context) {
18
+ if (error instanceof Glass3Error) return error;
19
+
20
+ const errorContext = context || {};
21
+ return new Glass3Error(
22
+ error && error.message ? error.message : 'Glass3 call failed',
23
+ {
24
+ code: errorContext.code,
25
+ stage: errorContext.stage,
26
+ requestId: errorContext.requestId,
27
+ namespace: errorContext.namespace,
28
+ method: errorContext.method,
29
+ cause: error
30
+ }
31
+ );
32
+ }
@@ -0,0 +1,22 @@
1
+ export function invokeEventCallback(callback, message) {
2
+ if (typeof callback !== 'function') return;
3
+
4
+ console.log('[glass3] onEvent result:', JSON.stringify(message));
5
+
6
+ try {
7
+ const callbackResult = callback(message);
8
+ if (callbackResult && typeof callbackResult.catch === 'function') {
9
+ callbackResult.catch(error => {
10
+ console.error(
11
+ '[glass3] onEvent callback failed:',
12
+ JSON.stringify(error)
13
+ );
14
+ });
15
+ }
16
+ } catch (error) {
17
+ console.error(
18
+ '[glass3] onEvent callback failed:',
19
+ JSON.stringify(error)
20
+ );
21
+ }
22
+ }
@@ -0,0 +1,63 @@
1
+ import {
2
+ RPC_ORIGIN,
3
+ RPC_VERSION
4
+ } from '../core/constants.js';
5
+
6
+ export function extractHostRpcMessage(messageEvent) {
7
+ if (!messageEvent || messageEvent.origin !== RPC_ORIGIN) return null;
8
+
9
+ const rpcMessage = messageEvent.data;
10
+
11
+ if (!rpcMessage || rpcMessage.version !== RPC_VERSION) {
12
+ return null;
13
+ }
14
+
15
+ return rpcMessage;
16
+ }
17
+
18
+ function isSensitiveFieldName(name) {
19
+ const normalizedName = String(name).toLowerCase().replace(/[^a-z0-9]/g, '');
20
+ return normalizedName.indexOf('token') !== -1 ||
21
+ normalizedName.indexOf('authorization') !== -1 ||
22
+ normalizedName.indexOf('cookie') !== -1 ||
23
+ normalizedName.indexOf('secretkey') !== -1 ||
24
+ normalizedName === 'customerprofile';
25
+ }
26
+
27
+ function cloneWithoutSensitiveFields(value, redactPhotoBase64) {
28
+ if (Array.isArray(value)) {
29
+ return value.map(item => (
30
+ cloneWithoutSensitiveFields(item, redactPhotoBase64)
31
+ ));
32
+ }
33
+
34
+ if (!value || typeof value !== 'object') return value;
35
+
36
+ const sanitized = {};
37
+ Object.keys(value).forEach(key => {
38
+ if (isSensitiveFieldName(key)) return;
39
+
40
+ if (
41
+ redactPhotoBase64 &&
42
+ (key === 'photo_base64' || key === 'photoBase64') &&
43
+ typeof value[key] === 'string'
44
+ ) {
45
+ sanitized[key] = `[omitted ${value[key].length} base64 chars]`;
46
+ return;
47
+ }
48
+
49
+ sanitized[key] = cloneWithoutSensitiveFields(
50
+ value[key],
51
+ redactPhotoBase64
52
+ );
53
+ });
54
+ return sanitized;
55
+ }
56
+
57
+ export function stripSensitiveFields(value) {
58
+ return cloneWithoutSensitiveFields(value, false);
59
+ }
60
+
61
+ export function redactSensitiveDataForLog(value) {
62
+ return cloneWithoutSensitiveFields(value, true);
63
+ }
@@ -0,0 +1,18 @@
1
+ const HEX_DIGITS = '0123456789abcdef';
2
+
3
+ export function createRequestId() {
4
+ const uuid = new Array(36);
5
+
6
+ for (let index = 0; index < uuid.length; index += 1) {
7
+ uuid[index] = HEX_DIGITS[Math.floor(Math.random() * 16)];
8
+ }
9
+
10
+ uuid[8] = '-';
11
+ uuid[13] = '-';
12
+ uuid[18] = '-';
13
+ uuid[23] = '-';
14
+ uuid[14] = '4';
15
+ uuid[19] = HEX_DIGITS[(parseInt(uuid[19], 16) & 0x3) | 0x8];
16
+
17
+ return uuid.join('');
18
+ }