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,55 @@
1
+ import {
2
+ RPC_SUCCESS_STATUS_MAX,
3
+ RPC_SUCCESS_STATUS_MIN,
4
+ RPC_URL
5
+ } from './constants.js';
6
+ import { Glass3Error } from '../utils/errors.js';
7
+ import { redactSensitiveDataForLog } from '../utils/message.js';
8
+
9
+ export class HostRpcTransport {
10
+ constructor(options) {
11
+ const transportOptions = options || {};
12
+ this.url = transportOptions.url || RPC_URL;
13
+ this.fetchImpl = transportOptions.fetchImpl || null;
14
+ }
15
+
16
+ send(request) {
17
+ const requestFetch = this.fetchImpl || fetch;
18
+ const requestBody = JSON.stringify(request);
19
+
20
+ console.log(
21
+ '[glass3] Native request 7:',
22
+ JSON.stringify(redactSensitiveDataForLog(request))
23
+ );
24
+
25
+ return requestFetch(this.url, {
26
+ method: 'POST',
27
+ headers: { 'content-type': 'application/json' },
28
+ body: requestBody
29
+ }).then(response => {
30
+ if (
31
+ response.status < RPC_SUCCESS_STATUS_MIN ||
32
+ response.status > RPC_SUCCESS_STATUS_MAX
33
+ ) {
34
+ throw new Glass3Error(
35
+ `Host RPC transport rejected: ${response.status}`,
36
+ {
37
+ code: 'TRANSPORT_REJECTED',
38
+ stage: 'transport',
39
+ requestId: request.requestId,
40
+ namespace: request.namespace,
41
+ method: request.method
42
+ }
43
+ );
44
+ }
45
+
46
+ return response.json();
47
+ }).then(result => {
48
+ console.log(
49
+ '[glass3] Native fetch response:',
50
+ JSON.stringify(redactSensitiveDataForLog(result))
51
+ );
52
+ return result;
53
+ });
54
+ }
55
+ }
package/sdk/index.js ADDED
@@ -0,0 +1,19 @@
1
+ import API_MAP from './api-map.js';
2
+ import { Glass3Client } from './core/client.js';
3
+ import { createApiNamespaces } from './utils/api-builder.js';
4
+
5
+ const client = new Glass3Client();
6
+ const glass3 = createApiNamespaces(client, API_MAP);
7
+
8
+ glass3.handleMessage = function handleMessage(messageEvent) {
9
+ return client.handleMessage(messageEvent);
10
+ };
11
+
12
+ glass3.dispose = function dispose() {
13
+ client.dispose();
14
+ };
15
+
16
+ export { API_MAP } from './api-map.js';
17
+ export { Glass3Error } from './utils/errors.js';
18
+ export { glass3 };
19
+ export default glass3;
@@ -0,0 +1,177 @@
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_MAX_MINUTES = 10;
9
+ const MAX_MAX_MINUTES = 120;
10
+
11
+ function createParameterError(message) {
12
+ return new Glass3Error(message, {
13
+ code: 'INVALID_PARAMS',
14
+ stage: 'params',
15
+ namespace: 'rokid.tools',
16
+ method: 'invoke'
17
+ });
18
+ }
19
+
20
+ function buildStartRecordArgs(args) {
21
+ if (
22
+ args !== undefined &&
23
+ args !== null &&
24
+ (typeof args !== 'object' || Array.isArray(args))
25
+ ) {
26
+ throw createParameterError('audio.startRecord parameters must be an object');
27
+ }
28
+
29
+ const input = args || {};
30
+ Object.keys(input).forEach(name => {
31
+ if (
32
+ name !== 'maxMinutes' &&
33
+ name !== 'needText' &&
34
+ name !== 'needMp3' &&
35
+ name !== 'showCard' &&
36
+ name !== 'upload'
37
+ ) {
38
+ throw createParameterError(`audio.startRecord does not support ${name}`);
39
+ }
40
+ });
41
+
42
+ const maxMinutes = input.maxMinutes === undefined
43
+ ? DEFAULT_MAX_MINUTES
44
+ : input.maxMinutes;
45
+ const needText = input.needText === undefined ? true : input.needText;
46
+ const needMp3 = input.needMp3 === undefined ? false : input.needMp3;
47
+ const showCard = input.showCard === undefined ? true : input.showCard;
48
+ const upload = input.upload === undefined ? false : input.upload;
49
+
50
+ if (
51
+ !Number.isInteger(maxMinutes) ||
52
+ maxMinutes <= 0 ||
53
+ maxMinutes > MAX_MAX_MINUTES
54
+ ) {
55
+ throw createParameterError(
56
+ 'audio.startRecord maxMinutes must be an integer from 1 to 120'
57
+ );
58
+ }
59
+
60
+ if (typeof needText !== 'boolean') {
61
+ throw createParameterError('audio.startRecord needText must be a boolean');
62
+ }
63
+
64
+ if (typeof needMp3 !== 'boolean') {
65
+ throw createParameterError('audio.startRecord needMp3 must be a boolean');
66
+ }
67
+
68
+ if (typeof showCard !== 'boolean') {
69
+ throw createParameterError('audio.startRecord showCard must be a boolean');
70
+ }
71
+
72
+ if (typeof upload !== 'boolean') {
73
+ throw createParameterError('audio.startRecord upload must be a boolean');
74
+ }
75
+
76
+ return {
77
+ max_minutes: maxMinutes,
78
+ need_text: needText,
79
+ need_mp3: needMp3,
80
+ show_card: showCard,
81
+ upload
82
+ };
83
+ }
84
+
85
+ function buildStopRecordArgs(args) {
86
+ if (
87
+ !args ||
88
+ typeof args !== 'object' ||
89
+ Array.isArray(args) ||
90
+ typeof args.requestId !== 'string' ||
91
+ !args.requestId.trim()
92
+ ) {
93
+ throw createParameterError('audio.stopRecord requires a requestId');
94
+ }
95
+
96
+ const inputFields = Object.keys(args);
97
+ if (inputFields.length !== 1 || inputFields[0] !== 'requestId') {
98
+ throw createParameterError('audio.stopRecord only supports requestId');
99
+ }
100
+
101
+ return {
102
+ targetRequestId: args.requestId
103
+ };
104
+ }
105
+
106
+ const audioStartRecord = {
107
+ publicModule: 'audio',
108
+ publicMethod: 'startRecord',
109
+
110
+ namespace: 'rokid.tools',
111
+ method: 'invoke',
112
+ toolName: 'audioRecord',
113
+ toolAction: 'start',
114
+
115
+ completion: COMPLETION_READY_EVENT_STREAM,
116
+ eventNamespace: 'rokid.tools',
117
+ eventName: 'toolResult',
118
+ responseCompletionStates: ['started'],
119
+ buildArgs: buildStartRecordArgs,
120
+ transformEventResult: snakeToCamelCase,
121
+ isAcceptedResponseResult(result) {
122
+ return Boolean(
123
+ result &&
124
+ result.state === 'started' &&
125
+ result.ok !== false
126
+ );
127
+ },
128
+ isErrorResult(result) {
129
+ return Boolean(result && result.ok === false);
130
+ },
131
+ isStreamTerminalResult(result) {
132
+ return Boolean(
133
+ result &&
134
+ (
135
+ result.ok === false ||
136
+ result.state === 'finished' ||
137
+ result.state === 'canceled'
138
+ )
139
+ );
140
+ },
141
+ selectReadyResult(requestId, result) {
142
+ return {
143
+ requestId,
144
+ ok: result.ok,
145
+ state: result.state
146
+ };
147
+ }
148
+ };
149
+
150
+ const audioStopRecord = {
151
+ publicModule: 'audio',
152
+ publicMethod: 'stopRecord',
153
+
154
+ namespace: 'rokid.tools',
155
+ method: 'invoke',
156
+ toolName: 'audioRecord',
157
+ toolAction: 'stop',
158
+
159
+ completion: COMPLETION_EVENT,
160
+ eventNamespace: 'rokid.tools',
161
+ eventName: 'toolResult',
162
+ terminalStates: ['stopped'],
163
+ buildArgs: buildStopRecordArgs,
164
+ transformEventResult: snakeToCamelCase,
165
+ releaseTargetInvocation: {
166
+ publicModule: 'audio',
167
+ publicMethod: 'startRecord'
168
+ },
169
+ selectEventResult(eventData) {
170
+ return Object.assign(
171
+ { requestId: eventData.requestId },
172
+ snakeToCamelCase(eventData.result)
173
+ );
174
+ }
175
+ };
176
+
177
+ export default [audioStartRecord, audioStopRecord];
@@ -0,0 +1,136 @@
1
+ # Audio Recording API
2
+
3
+ Audio 模块通过 `glass3.audio` 暴露眼镜麦克风录音能力。录音是长任务,页面必须将 `onMessage(messageEvent)` 转发给 `glass3.handleMessage(messageEvent)`。
4
+
5
+ ## API 列表
6
+
7
+ | API | 说明 |
8
+ | --- | --- |
9
+ | `audio.startRecord` | 开始录音,等待 Native 的 started response。 |
10
+ | `audio.stopRecord` | 停止指定录音,等待 stop requestId 的 stopped event。 |
11
+
12
+ ## `audio.startRecord`
13
+
14
+ ```js
15
+ const recording = await glass3.audio.startRecord(
16
+ {
17
+ maxMinutes: 10,
18
+ needText: true,
19
+ needMp3: false,
20
+ showCard: true,
21
+ upload: false
22
+ },
23
+ {
24
+ onEvent(result) {
25
+ console.log('audio event:', JSON.stringify(result));
26
+ }
27
+ }
28
+ );
29
+ ```
30
+
31
+ | 字段 | 类型 | 默认值 | 说明 |
32
+ | --- | --- | --- | --- |
33
+ | `maxMinutes` | `number` | `10` | 最大录音分钟数,必须是 1 到 120 的整数。 |
34
+ | `needText` | `boolean` | `true` | 是否返回整段 ASR 转写字符串 `text`。 |
35
+ | `needMp3` | `boolean` | `false` | 是否保留并返回音频文件。 |
36
+ | `showCard` | `boolean` | `true` | 是否在眼镜端显示录音中的 Native UI。 |
37
+ | `upload` | `boolean` | `false` | 是否将录音结果上传云端。 |
38
+
39
+ SDK 将参数转换成 Native 的 `max_minutes`、`need_text`、`need_mp3`、`show_card` 和 `upload`。默认只转写文本,纯文本模式不落音频文件。Native 使用 16kHz / 16bit / 单声道共享采集通道,录音可与执法记录并存;`needMp3: true` 时音频按 AAC/MP4 编码并使用 `.mp3` 后缀。
40
+
41
+ `showCard: true` 时,录音期间眼镜端由 Native 显示录音小框。录音任务会监听离线语音指令“结束录音”。
42
+
43
+ start 只等待同一 requestId 的 `kind: "response"`,并要求 `result.state: "started"`。started event 不会结束 Promise。返回值为:
44
+
45
+ ```js
46
+ {
47
+ requestId: '录音 start UUID',
48
+ ok: true,
49
+ state: 'started'
50
+ }
51
+ ```
52
+
53
+ start 完成后,自动结束、用户离线指令结束和异步失败仍通过原 start requestId 进入 `onEvent`。事件结果字段会递归转换成驼峰。
54
+
55
+ maxMinutes 到点:
56
+
57
+ ```js
58
+ {
59
+ ok: true,
60
+ finished: true,
61
+ state: 'finished',
62
+ reason: 'max_duration',
63
+ durationMs: 600000,
64
+ text: '整段转写文本',
65
+ filePath: '/path/to/tool_audio.mp3',
66
+ fileUrl: 'https://example.com/tool_audio.mp3',
67
+ sizeBytes: 165164,
68
+ sampleRate: 16000
69
+ }
70
+ ```
71
+
72
+ `fileUrl` 仅在 `upload: true` 且上传成功时出现。
73
+
74
+ 用户在眼镜端说“结束录音”:
75
+
76
+ ```js
77
+ {
78
+ ok: true,
79
+ finished: true,
80
+ state: 'finished',
81
+ reason: 'offline_command',
82
+ durationMs: 5140,
83
+ text: '整段转写文本',
84
+ filePath: '/path/to/tool_audio.mp3'
85
+ }
86
+ ```
87
+
88
+ start 成功后 2 秒内采不到声音时,Native 清理录音并异步返回:
89
+
90
+ ```js
91
+ { ok: false, error: 'mic_busy' }
92
+ ```
93
+
94
+ `needText: true` 但 ASR 不可用时,本次录音作废并返回:
95
+
96
+ ```js
97
+ { ok: false, error: 'asr_unavailable' }
98
+ ```
99
+
100
+ 失败事件会先进入 `onEvent`,随后 SDK 清理该 start 的事件监听。
101
+
102
+ Native 映射:`rokid.tools / invoke / audioRecord / start`。
103
+
104
+ ## `audio.stopRecord`
105
+
106
+ ```js
107
+ const result = await glass3.audio.stopRecord({
108
+ requestId: recording.requestId
109
+ });
110
+ ```
111
+
112
+ 只接受 start 返回的非空 `requestId`。SDK 将其转换为 `{ targetRequestId }`,并为 stop 生成独立 requestId。
113
+
114
+ stop 的 Promise 只等待归属 stop requestId 的唯一 `state: "stopped"` event,不等待原 start requestId 的事件。完成结果直接包含成果:
115
+
116
+ ```js
117
+ {
118
+ requestId: '本次 stop UUID',
119
+ ok: true,
120
+ state: 'stopped',
121
+ durationMs: 5140,
122
+ text: '整段转写文本',
123
+ filePath: '/path/to/tool_audio.mp3',
124
+ fileUrl: 'https://example.com/tool_audio.mp3',
125
+ sizeBytes: 165164,
126
+ sampleRate: 16000
127
+ }
128
+ ```
129
+
130
+ stop 成功后 SDK 会释放原 start 的事件监听。
131
+
132
+ Native 映射:`rokid.tools / invoke / audioRecord / stop`。
133
+
134
+ ## 生命周期
135
+
136
+ 页面卸载时应清除计时器、尽力调用 `audio.stopRecord({ requestId })`,再调用 `glass3.dispose()`。