rvis-aiui-kit 1.1.0 → 1.2.1

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.
@@ -0,0 +1,517 @@
1
+ import { Glass3Error } from '../../utils/errors.js';
2
+ import { createRequestId } from '../../utils/request-id.js';
3
+
4
+ const DEFAULT_BASE_URL = 'https://api.rokid.com';
5
+ const VISION_EXTRACT_PATH = '/ar/rvis/openapi/v1/vision/extractions';
6
+ const VISION_EXTRACT_TIMEOUT = 120000;
7
+ const OPENAPI_NAMESPACE = 'rokid.openapi';
8
+ const VISION_EXTRACT_METHOD = 'visionExtract';
9
+ const FIELD_TYPES = ['string', 'number', 'boolean'];
10
+ const MAX_IMAGE_BYTES = 10 * 1024 * 1024;
11
+
12
+ function createOpenApiError(message, options) {
13
+ const errorOptions = options || {};
14
+ return new Glass3Error(message, {
15
+ code: errorOptions.code,
16
+ stage: errorOptions.stage,
17
+ requestId: errorOptions.requestId,
18
+ namespace: OPENAPI_NAMESPACE,
19
+ method: VISION_EXTRACT_METHOD,
20
+ details: errorOptions.details,
21
+ cause: errorOptions.cause
22
+ });
23
+ }
24
+
25
+ function createParameterError(message) {
26
+ return createOpenApiError(message, {
27
+ code: 'INVALID_PARAMS',
28
+ stage: 'params'
29
+ });
30
+ }
31
+
32
+ function assertPlainObject(value, message) {
33
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
34
+ throw createParameterError(message);
35
+ }
36
+ }
37
+
38
+ function assertClosedShape(value, allowedFields, methodName) {
39
+ Object.keys(value).forEach(name => {
40
+ if (allowedFields.indexOf(name) === -1) {
41
+ throw createParameterError(`${methodName} does not support ${name}`);
42
+ }
43
+ });
44
+ }
45
+
46
+ function normalizeBaseUrl(value) {
47
+ const baseUrl = value === undefined ? DEFAULT_BASE_URL : value;
48
+ if (typeof baseUrl !== 'string' || !baseUrl.trim()) {
49
+ throw createParameterError('openapi.setConfig baseUrl must be a non-empty string');
50
+ }
51
+
52
+ const normalized = baseUrl.trim().replace(/\/+$/, '');
53
+ if (normalized.indexOf('https://') !== 0) {
54
+ throw createParameterError('openapi.setConfig baseUrl must use https');
55
+ }
56
+ return normalized;
57
+ }
58
+
59
+ function validateConfig(options) {
60
+ assertPlainObject(options, 'openapi.setConfig parameters must be an object');
61
+ assertClosedShape(options, ['baseUrl', 'apiKey'], 'openapi.setConfig');
62
+
63
+ if (typeof options.apiKey !== 'string' || !options.apiKey.trim()) {
64
+ throw createParameterError('openapi.setConfig apiKey is required');
65
+ }
66
+
67
+ return {
68
+ baseUrl: normalizeBaseUrl(options.baseUrl),
69
+ apiKey: options.apiKey.trim()
70
+ };
71
+ }
72
+
73
+ function validateField(field, index) {
74
+ assertPlainObject(
75
+ field,
76
+ `openapi.visionExtract fields[${index}] must be an object`
77
+ );
78
+ assertClosedShape(
79
+ field,
80
+ ['name', 'title', 'type'],
81
+ `openapi.visionExtract fields[${index}]`
82
+ );
83
+
84
+ if (
85
+ typeof field.name !== 'string' ||
86
+ !/^[A-Za-z][A-Za-z0-9_]*$/.test(field.name)
87
+ ) {
88
+ throw createParameterError(
89
+ `openapi.visionExtract fields[${index}].name must be a stable English identifier`
90
+ );
91
+ }
92
+ if (typeof field.title !== 'string' || !field.title.trim()) {
93
+ throw createParameterError(
94
+ `openapi.visionExtract fields[${index}].title is required`
95
+ );
96
+ }
97
+ if (FIELD_TYPES.indexOf(field.type) === -1) {
98
+ throw createParameterError(
99
+ `openapi.visionExtract fields[${index}].type must be string, number or boolean`
100
+ );
101
+ }
102
+
103
+ return {
104
+ name: field.name,
105
+ title: field.title,
106
+ type: field.type
107
+ };
108
+ }
109
+
110
+ function validateVisionExtractParams(params) {
111
+ assertPlainObject(
112
+ params,
113
+ 'openapi.visionExtract parameters must be an object'
114
+ );
115
+ assertClosedShape(
116
+ params,
117
+ ['image', 'prompt', 'uiType', 'fields'],
118
+ 'openapi.visionExtract'
119
+ );
120
+
121
+ if (typeof params.image !== 'string' || !params.image.trim()) {
122
+ throw createParameterError('openapi.visionExtract image is required');
123
+ }
124
+ const base64Payload = params.image.indexOf('data:') === 0
125
+ ? params.image.slice(params.image.indexOf(',') + 1)
126
+ : params.image;
127
+ const compactPayload = base64Payload.replace(/\s/g, '');
128
+ const paddingLength = compactPayload.endsWith('==')
129
+ ? 2
130
+ : compactPayload.endsWith('=') ? 1 : 0;
131
+ const estimatedImageBytes = Math.max(
132
+ 0,
133
+ Math.floor(compactPayload.length * 3 / 4) - paddingLength
134
+ );
135
+ if (estimatedImageBytes > MAX_IMAGE_BYTES) {
136
+ throw createParameterError(
137
+ 'openapi.visionExtract image must not exceed 10MB'
138
+ );
139
+ }
140
+ if (typeof params.prompt !== 'string' || !params.prompt.trim()) {
141
+ throw createParameterError('openapi.visionExtract prompt is required');
142
+ }
143
+ if (params.prompt.length > 8192) {
144
+ throw createParameterError(
145
+ 'openapi.visionExtract prompt must not exceed 8192 characters'
146
+ );
147
+ }
148
+ if (params.uiType !== 'text' && params.uiType !== 'table') {
149
+ throw createParameterError(
150
+ 'openapi.visionExtract uiType must be text or table'
151
+ );
152
+ }
153
+
154
+ const fields = params.fields === undefined ? [] : params.fields;
155
+ if (!Array.isArray(fields)) {
156
+ throw createParameterError('openapi.visionExtract fields must be an array');
157
+ }
158
+ if (params.uiType === 'table' && (fields.length < 1 || fields.length > 8)) {
159
+ throw createParameterError(
160
+ 'openapi.visionExtract table fields must contain 1 to 8 items'
161
+ );
162
+ }
163
+ if (params.uiType === 'text' && fields.length > 0) {
164
+ throw createParameterError(
165
+ 'openapi.visionExtract text fields must be omitted or empty'
166
+ );
167
+ }
168
+
169
+ const requestParams = {
170
+ image: params.image,
171
+ prompt: params.prompt,
172
+ uiType: params.uiType
173
+ };
174
+ if (params.uiType === 'table') {
175
+ requestParams.fields = fields.map(validateField);
176
+ }
177
+ return requestParams;
178
+ }
179
+
180
+ function validateCallOptions(options) {
181
+ if (options === undefined) return {};
182
+ assertPlainObject(
183
+ options,
184
+ 'openapi.visionExtract call options must be an object'
185
+ );
186
+ assertClosedShape(options, ['onEvent'], 'openapi.visionExtract call options');
187
+ if (
188
+ options.onEvent !== undefined &&
189
+ typeof options.onEvent !== 'function'
190
+ ) {
191
+ throw createParameterError(
192
+ 'openapi.visionExtract onEvent must be a function'
193
+ );
194
+ }
195
+ return options;
196
+ }
197
+
198
+ function describeImage(image) {
199
+ const dataUrlMatch = /^data:([^;,]+);base64,/i.exec(image);
200
+ return dataUrlMatch
201
+ ? `[base64 omitted, mime=${dataUrlMatch[1]}, length=${image.length}]`
202
+ : `[base64 omitted, length=${image.length}]`;
203
+ }
204
+
205
+ function requestParamsForLog(params) {
206
+ const sanitized = {
207
+ image: describeImage(params.image),
208
+ prompt: params.prompt,
209
+ uiType: params.uiType
210
+ };
211
+ if (params.fields) sanitized.fields = params.fields;
212
+ return sanitized;
213
+ }
214
+
215
+ function parseEventData(rawData) {
216
+ try {
217
+ return JSON.parse(rawData);
218
+ } catch (_error) {
219
+ return rawData;
220
+ }
221
+ }
222
+
223
+ function invokeEventCallback(callback, event) {
224
+ if (typeof callback !== 'function') return;
225
+ try {
226
+ const callbackResult = callback(event);
227
+ if (callbackResult && typeof callbackResult.catch === 'function') {
228
+ callbackResult.catch(error => {
229
+ console.error(
230
+ '[glass3.openapi] onEvent callback failed:',
231
+ error && error.message ? error.message : String(error)
232
+ );
233
+ });
234
+ }
235
+ } catch (error) {
236
+ console.error(
237
+ '[glass3.openapi] onEvent callback failed:',
238
+ error && error.message ? error.message : String(error)
239
+ );
240
+ }
241
+ }
242
+
243
+ function errorForLog(error) {
244
+ return {
245
+ requestId: error.requestId,
246
+ code: error.code,
247
+ stage: error.stage,
248
+ message: error.message,
249
+ details: error.details
250
+ };
251
+ }
252
+
253
+ export class OpenApiClient {
254
+ constructor(options) {
255
+ const clientOptions = options || {};
256
+ this.createEventSource = clientOptions.createEventSource;
257
+ this.config = null;
258
+ this.activeRequests = new Map();
259
+ }
260
+
261
+ setConfig(options) {
262
+ this.config = validateConfig(options);
263
+ console.log(
264
+ '[glass3.openapi] config:',
265
+ JSON.stringify({
266
+ baseUrl: this.config.baseUrl,
267
+ apiKey: '[redacted]'
268
+ })
269
+ );
270
+ }
271
+
272
+ visionExtract(params, options) {
273
+ let requestParams;
274
+ let callOptions;
275
+ try {
276
+ requestParams = validateVisionExtractParams(params);
277
+ callOptions = validateCallOptions(options);
278
+ if (!this.config) {
279
+ throw createOpenApiError(
280
+ 'openapi.setConfig must be called before openapi.visionExtract',
281
+ {
282
+ code: 'OPENAPI_NOT_CONFIGURED',
283
+ stage: 'config'
284
+ }
285
+ );
286
+ }
287
+ if (typeof this.createEventSource !== 'function') {
288
+ throw createOpenApiError(
289
+ 'The current runtime does not provide wx.createEventSource',
290
+ {
291
+ code: 'OPENAPI_RUNTIME_UNAVAILABLE',
292
+ stage: 'transport'
293
+ }
294
+ );
295
+ }
296
+ } catch (error) {
297
+ return Promise.reject(error);
298
+ }
299
+
300
+ const config = {
301
+ baseUrl: this.config.baseUrl,
302
+ apiKey: this.config.apiKey
303
+ };
304
+ const requestId = createRequestId();
305
+ const url = `${config.baseUrl}${VISION_EXTRACT_PATH}`;
306
+
307
+ console.log(
308
+ '[glass3.openapi] request:',
309
+ JSON.stringify({
310
+ requestId,
311
+ method: 'POST',
312
+ url,
313
+ params: requestParamsForLog(requestParams)
314
+ })
315
+ );
316
+
317
+ return new Promise((resolve, reject) => {
318
+ const invocation = {
319
+ requestId,
320
+ task: null,
321
+ timeoutId: null,
322
+ settled: false,
323
+ hasResult: false,
324
+ finalResult: undefined,
325
+ resolve,
326
+ reject
327
+ };
328
+
329
+ const closeTask = () => {
330
+ if (!invocation.task || typeof invocation.task.close !== 'function') {
331
+ return;
332
+ }
333
+ try {
334
+ invocation.task.close();
335
+ } catch (_error) {
336
+ // The request is already terminal; close failures do not replace it.
337
+ }
338
+ };
339
+
340
+ const finish = (error, result) => {
341
+ if (invocation.settled) return;
342
+ invocation.settled = true;
343
+ if (invocation.timeoutId !== null) {
344
+ clearTimeout(invocation.timeoutId);
345
+ invocation.timeoutId = null;
346
+ }
347
+ this.activeRequests.delete(requestId);
348
+ closeTask();
349
+
350
+ if (error) {
351
+ console.error(
352
+ '[glass3.openapi] error:',
353
+ JSON.stringify(errorForLog(error))
354
+ );
355
+ reject(error);
356
+ return;
357
+ }
358
+
359
+ console.log(
360
+ '[glass3.openapi] response:',
361
+ JSON.stringify({ requestId, result })
362
+ );
363
+ resolve(result);
364
+ };
365
+
366
+ invocation.rejectWith = error => finish(error);
367
+ this.activeRequests.set(requestId, invocation);
368
+ invocation.timeoutId = setTimeout(() => {
369
+ finish(createOpenApiError(
370
+ `OpenAPI call timed out after ${VISION_EXTRACT_TIMEOUT}ms`,
371
+ {
372
+ code: 'CALL_TIMEOUT',
373
+ stage: 'timeout',
374
+ requestId,
375
+ details: { timeout: VISION_EXTRACT_TIMEOUT }
376
+ }
377
+ ));
378
+ }, VISION_EXTRACT_TIMEOUT);
379
+
380
+ try {
381
+ invocation.task = this.createEventSource({
382
+ url,
383
+ method: 'POST',
384
+ header: {
385
+ Authorization: `Bearer ${config.apiKey}`,
386
+ 'Content-Type': 'application/json',
387
+ Accept: 'text/event-stream'
388
+ },
389
+ data: requestParams
390
+ });
391
+
392
+ invocation.task.onMessage(message => {
393
+ if (invocation.settled) return;
394
+
395
+ const rawData = message && typeof message.data === 'string'
396
+ ? message.data
397
+ : '';
398
+ if (rawData === '[DONE]') {
399
+ if (!invocation.hasResult) {
400
+ finish(createOpenApiError(
401
+ 'OpenAPI stream ended without a result event',
402
+ {
403
+ code: 'OPENAPI_PROTOCOL_ERROR',
404
+ stage: 'event',
405
+ requestId
406
+ }
407
+ ));
408
+ return;
409
+ }
410
+ finish(null, invocation.finalResult);
411
+ return;
412
+ }
413
+
414
+ const event = {
415
+ type: message && message.event ? message.event : 'message',
416
+ data: parseEventData(rawData),
417
+ rawData,
418
+ id: message && message.id ? message.id : ''
419
+ };
420
+
421
+ console.log(
422
+ '[glass3.openapi] event:',
423
+ JSON.stringify({
424
+ requestId,
425
+ type: event.type,
426
+ data: event.data,
427
+ id: event.id
428
+ })
429
+ );
430
+ invokeEventCallback(callOptions.onEvent, event);
431
+
432
+ if (event.type === 'error') {
433
+ const messageText = event.data && event.data.message
434
+ ? event.data.message
435
+ : 'OpenAPI vision extraction failed';
436
+ finish(createOpenApiError(messageText, {
437
+ code: 'OPENAPI_SERVICE_ERROR',
438
+ stage: 'event',
439
+ requestId,
440
+ details: event.data
441
+ }));
442
+ return;
443
+ }
444
+
445
+ if (event.type === 'result') {
446
+ if (
447
+ !event.data ||
448
+ typeof event.data !== 'object' ||
449
+ Array.isArray(event.data)
450
+ ) {
451
+ finish(createOpenApiError(
452
+ 'OpenAPI result event must contain a JSON object',
453
+ {
454
+ code: 'OPENAPI_PROTOCOL_ERROR',
455
+ stage: 'event',
456
+ requestId,
457
+ details: { rawData }
458
+ }
459
+ ));
460
+ return;
461
+ }
462
+ invocation.hasResult = true;
463
+ invocation.finalResult = event.data;
464
+ }
465
+ });
466
+
467
+ invocation.task.onError(error => {
468
+ if (invocation.settled) return;
469
+ const errMsg = error && error.errMsg
470
+ ? error.errMsg
471
+ : 'OpenAPI stream failed';
472
+ const isHttpError = errMsg.indexOf('HTTP Error:') === 0;
473
+ finish(createOpenApiError(errMsg, {
474
+ code: isHttpError
475
+ ? 'OPENAPI_HTTP_ERROR'
476
+ : 'OPENAPI_STREAM_ERROR',
477
+ stage: 'transport',
478
+ requestId,
479
+ details: { errMsg }
480
+ }));
481
+ });
482
+
483
+ } catch (error) {
484
+ finish(createOpenApiError(
485
+ error && error.message ? error.message : 'OpenAPI request failed',
486
+ {
487
+ code: 'OPENAPI_STREAM_ERROR',
488
+ stage: 'transport',
489
+ requestId,
490
+ cause: error
491
+ }
492
+ ));
493
+ }
494
+ });
495
+ }
496
+
497
+ dispose() {
498
+ const invocations = [];
499
+ this.activeRequests.forEach(invocation => invocations.push(invocation));
500
+ invocations.forEach(invocation => {
501
+ invocation.rejectWith(createOpenApiError(
502
+ 'Glass3 call disposed: openapi.visionExtract',
503
+ {
504
+ code: 'CALL_DISPOSED',
505
+ stage: 'dispose',
506
+ requestId: invocation.requestId
507
+ }
508
+ ));
509
+ });
510
+ }
511
+ }
512
+
513
+ export {
514
+ DEFAULT_BASE_URL,
515
+ VISION_EXTRACT_PATH,
516
+ VISION_EXTRACT_TIMEOUT
517
+ };
@@ -0,0 +1,21 @@
1
+ import wx from 'wx';
2
+ import { OpenApiClient } from './client.js';
3
+
4
+ const openApiClient = new OpenApiClient({
5
+ createEventSource(options) {
6
+ return wx.createEventSource(options);
7
+ }
8
+ });
9
+
10
+ const openapi = {
11
+ setConfig(options) {
12
+ return openApiClient.setConfig(options);
13
+ },
14
+
15
+ visionExtract(params, options) {
16
+ return openApiClient.visionExtract(params, options);
17
+ }
18
+ };
19
+
20
+ export { OpenApiClient, openApiClient, openapi };
21
+ export default openapi;
@@ -0,0 +1,83 @@
1
+ # OpenAPI
2
+
3
+ `glass3.openapi` 是全局单例,用于从 AIUI 页面直接调用 Rokid OpenAPI。
4
+ 它使用 HTTPS 与 SSE,不经过 Host RPC,因此不依赖 `glass3.handleMessage()`;页面仍需在
5
+ 卸载时调用 `glass3.dispose()`,以关闭未完成的 SSE 请求。
6
+
7
+ ## 配置
8
+
9
+ 应用启动后配置一次即可:
10
+
11
+ ```js
12
+ glass3.openapi.setConfig({
13
+ apiKey: '<AK>'
14
+ });
15
+ ```
16
+
17
+ - `apiKey` 必填。
18
+ - `baseUrl` 可选,默认 `https://api.rokid.com`。
19
+ - 测试环境显式传入 `https://api-test.rokid.com`。
20
+ - 重复调用会覆盖后续请求使用的配置,不影响已经发出的请求。
21
+ - 配置只保存在当前 JS 运行实例内,应用重新启动后需要再次配置。
22
+
23
+ ## 图片识别
24
+
25
+ ```js
26
+ const result = await glass3.openapi.visionExtract(
27
+ {
28
+ image: 'data:image/jpeg;base64,...',
29
+ prompt: '图中有什么安全隐患',
30
+ uiType: 'table',
31
+ fields: [
32
+ { name: 'hazard', title: '隐患', type: 'string' },
33
+ { name: 'suggestion', title: '修改建议', type: 'string' },
34
+ { name: 'regulation', title: '法规', type: 'string' }
35
+ ]
36
+ },
37
+ {
38
+ onEvent(event) {
39
+ console.log(event.type, JSON.stringify(event.data));
40
+ }
41
+ }
42
+ );
43
+ ```
44
+
45
+ 业务参数:
46
+
47
+ | 字段 | 类型 | 说明 |
48
+ | --- | --- | --- |
49
+ | `image` | `string` | 必填;纯 Base64 或 Base64 Data URL,原图最大 10MB。 |
50
+ | `prompt` | `string` | 必填;最长 8192 字符。 |
51
+ | `uiType` | `string` | 必填;`text` 或 `table`。 |
52
+ | `fields` | `array` | `table` 必填 1–8 项;`text` 时省略或传空数组。 |
53
+
54
+ `fields` 每项必须提供稳定的英文字段名 `name`、展示名 `title`,以及
55
+ `string`、`number` 或 `boolean` 类型。
56
+
57
+ `onEvent` 收到:
58
+
59
+ ```js
60
+ {
61
+ type: 'start' | 'snapshot' | 'discard' | 'result' | 'error',
62
+ data: {},
63
+ rawData: '{...}',
64
+ id: ''
65
+ }
66
+ ```
67
+
68
+ - `data` 优先解析为 JSON;非 JSON 数据保留为字符串。
69
+ - `rawData` 是移除 `data:` 前缀后的原始字符串。
70
+ - `[DONE]` 是内部完成标记,不进入 `onEvent`。
71
+ - Promise 收到 `[DONE]` 后 resolve 最终 `result` 数据。
72
+ - `result.success === false` 是正常业务结果,仍然 resolve。
73
+ - `event:error`、HTTP 错误、网络错误、协议错误和超时会 reject。
74
+ - 单次调用最长等待 120 秒。
75
+ - `onEvent` 自身抛错只记录日志,不改变请求结果。
76
+
77
+ ## 日志与安全
78
+
79
+ SDK 统一输出 `config`、`request`、`event`、`response` 和 `error` 日志。
80
+ `apiKey`、`Authorization` 与图片 Base64 永远不会原样写入日志;图片只记录 MIME 与长度。
81
+
82
+ 当前 AIUI `EventSourceTask` 不会分发空 `data:` 帧,因此服务端的 `discard` 事件必须携带
83
+ 非空数据,例如 `data:{}` 或 `data:null`。服务端也必须始终发送 `data:[DONE]` 结束帧。
@@ -0,0 +1,78 @@
1
+ import { RPC_CHANNEL } from '../../core/constants.js';
2
+ import { Glass3Error } from '../../utils/errors.js';
3
+
4
+ const STATUS_BAR_NAMESPACE = 'rokid.statusBar';
5
+ const STATUS_BAR_TIMEOUT_MS = 5000;
6
+
7
+ function createParameterError(message, method) {
8
+ return new Glass3Error(message, {
9
+ code: 'INVALID_PARAMS',
10
+ stage: 'params',
11
+ namespace: STATUS_BAR_NAMESPACE,
12
+ method
13
+ });
14
+ }
15
+
16
+ function buildSingleFieldArgs(field, type, method) {
17
+ return args => {
18
+ if (!args || typeof args !== 'object' || Array.isArray(args)) {
19
+ throw createParameterError(
20
+ `statusBar.${method} parameters must be an object`,
21
+ method
22
+ );
23
+ }
24
+
25
+ const fields = Object.keys(args);
26
+ if (fields.length !== 1 || fields[0] !== field) {
27
+ throw createParameterError(
28
+ `statusBar.${method} only supports ${field}`,
29
+ method
30
+ );
31
+ }
32
+
33
+ if (typeof args[field] !== type) {
34
+ throw createParameterError(
35
+ `statusBar.${method} ${field} must be a ${type}`,
36
+ method
37
+ );
38
+ }
39
+
40
+ return { [field]: args[field] };
41
+ };
42
+ }
43
+
44
+ function directParams(args) {
45
+ return args;
46
+ }
47
+
48
+ function selectResult(response) {
49
+ return response.result;
50
+ }
51
+
52
+ function createStatusBarDefinition(publicMethod, field, type) {
53
+ return {
54
+ publicModule: 'statusBar',
55
+ publicMethod,
56
+
57
+ namespace: STATUS_BAR_NAMESPACE,
58
+ method: publicMethod,
59
+ responseChannel: RPC_CHANNEL,
60
+
61
+ buildArgs: buildSingleFieldArgs(field, type, publicMethod),
62
+ buildRequestParams: directParams,
63
+ getTimeoutMs() {
64
+ return STATUS_BAR_TIMEOUT_MS;
65
+ },
66
+ isAcceptedResponseResult(result) {
67
+ if (!result || typeof result.updated !== 'boolean') return false;
68
+ return publicMethod !== 'setVisible' ||
69
+ typeof result.visible === 'boolean';
70
+ },
71
+ selectAcceptedResult: selectResult
72
+ };
73
+ }
74
+
75
+ export default [
76
+ createStatusBarDefinition('setTitle', 'title', 'string'),
77
+ createStatusBarDefinition('setVisible', 'visible', 'boolean')
78
+ ];