agent-robot-avatar 0.2.1 → 0.3.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.
- package/CHANGELOG.md +40 -0
- package/README.md +178 -30
- package/agent-robot-avatar.js +10 -1
- package/docs/README.de.md +8 -6
- package/docs/README.es.md +6 -8
- package/docs/README.fr.md +8 -8
- package/docs/README.ja.md +6 -8
- package/docs/README.ko.md +6 -8
- package/docs/README.pt.md +6 -8
- package/docs/README.zh-CN.md +194 -96
- package/docs/README.zh-TW.md +6 -8
- package/index.d.ts +30 -0
- package/package.json +1 -1
- package/src/agent-robot-avatar-antenna.js +24 -45
- package/src/agent-robot-avatar-core.js +39 -23
- package/src/agent-robot-avatar-geometry.js +40 -0
- package/src/agent-robot-avatar-head-roundness.js +2 -35
- package/src/agent-robot-avatar-inspect.js +37 -29
- package/src/agent-robot-avatar-runtime.js +875 -0
- package/src/agent-robot-avatar-version.js +1 -1
package/index.d.ts
CHANGED
|
@@ -38,8 +38,38 @@ export type AgentRobotAvatarState =
|
|
|
38
38
|
| 'inspect'
|
|
39
39
|
| 'failure';
|
|
40
40
|
|
|
41
|
+
export type AgentRobotAvatarCanonicalAction =
|
|
42
|
+
| 'idle'
|
|
43
|
+
| 'bored'
|
|
44
|
+
| 'waiting'
|
|
45
|
+
| 'input'
|
|
46
|
+
| 'send'
|
|
47
|
+
| 'success'
|
|
48
|
+
| 'failure'
|
|
49
|
+
| 'warning'
|
|
50
|
+
| 'inspect'
|
|
51
|
+
| 'angry'
|
|
52
|
+
| 'blocked'
|
|
53
|
+
| 'error'
|
|
54
|
+
| 'surprise'
|
|
55
|
+
| 'sleep'
|
|
56
|
+
| 'wake'
|
|
57
|
+
| 'reaction';
|
|
58
|
+
|
|
59
|
+
export type AgentRobotAvatarActionPhase = 'start' | 'end' | 'cancel';
|
|
60
|
+
export type AgentRobotAvatarActionSource = 'api' | 'interaction' | 'automatic';
|
|
61
|
+
export type AgentRobotAvatarWakeOn = 'activity' | 'interaction' | 'manual';
|
|
62
|
+
export type AgentRobotAvatarMotion = 'auto' | 'reduce' | 'full';
|
|
63
|
+
|
|
64
|
+
export interface AgentRobotAvatarActionStateDetail {
|
|
65
|
+
action: AgentRobotAvatarCanonicalAction;
|
|
66
|
+
phase: AgentRobotAvatarActionPhase;
|
|
67
|
+
source: AgentRobotAvatarActionSource;
|
|
68
|
+
}
|
|
69
|
+
|
|
41
70
|
export interface AgentRobotAvatarEventMap extends HTMLElementEventMap {
|
|
42
71
|
'face-state': CustomEvent<{ state: AgentRobotAvatarState }>;
|
|
72
|
+
'action-state': CustomEvent<AgentRobotAvatarActionStateDetail>;
|
|
43
73
|
'head-roundness-change': CustomEvent<{ value: number; version: string }>;
|
|
44
74
|
}
|
|
45
75
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import AgentRobotAvatar, { registerAvatarExtension } from './agent-robot-avatar-extension-host.js';
|
|
2
|
+
import { DEFAULT_HEAD_HEIGHT_SCALE, flattenHeadPoints } from './agent-robot-avatar-geometry.js';
|
|
2
3
|
|
|
3
4
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
4
|
-
const HEAD_HEIGHT_SCALE = 0.9;
|
|
5
5
|
const ANTENNA_DEFAULTS = Object.freeze({
|
|
6
6
|
x: 120,
|
|
7
7
|
y: 12,
|
|
@@ -35,40 +35,10 @@ function antennaHome() {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function smooth01(t) {
|
|
39
|
-
t = Math.max(0, Math.min(1, t));
|
|
40
|
-
return t * t * (3 - 2 * t);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
38
|
function flattenHead(instance) {
|
|
44
39
|
if (instance._headFlattenedR32 || !instance._headShape || !instance._baseHeadPoints?.length) return;
|
|
45
40
|
|
|
46
|
-
const
|
|
47
|
-
const minY = Math.min(...source.map(point => point.y));
|
|
48
|
-
const maxY = Math.max(...source.map(point => point.y));
|
|
49
|
-
const height = maxY - minY;
|
|
50
|
-
const centerY = (minY + maxY) / 2;
|
|
51
|
-
const inset = height * (1 - HEAD_HEIGHT_SCALE) / 2;
|
|
52
|
-
|
|
53
|
-
const upperLockY = minY + height * 0.40;
|
|
54
|
-
const lowerLockY = maxY - height * 0.40;
|
|
55
|
-
|
|
56
|
-
const flattened = source.map(point => {
|
|
57
|
-
let shiftY = 0;
|
|
58
|
-
if (point.y <= upperLockY) {
|
|
59
|
-
shiftY = inset;
|
|
60
|
-
} else if (point.y < centerY) {
|
|
61
|
-
const t = (centerY - point.y) / Math.max(0.001, centerY - upperLockY);
|
|
62
|
-
shiftY = inset * smooth01(t);
|
|
63
|
-
} else if (point.y >= lowerLockY) {
|
|
64
|
-
shiftY = -inset;
|
|
65
|
-
} else if (point.y > centerY) {
|
|
66
|
-
const t = (point.y - centerY) / Math.max(0.001, lowerLockY - centerY);
|
|
67
|
-
shiftY = -inset * smooth01(t);
|
|
68
|
-
}
|
|
69
|
-
return { x: point.x, y: point.y + shiftY };
|
|
70
|
-
});
|
|
71
|
-
|
|
41
|
+
const flattened = flattenHeadPoints(instance._baseHeadPoints, DEFAULT_HEAD_HEIGHT_SCALE);
|
|
72
42
|
const path = typeof instance._pointsToPath === 'function'
|
|
73
43
|
? instance._pointsToPath(flattened)
|
|
74
44
|
: `M ${flattened.map((point, index) => `${index ? 'L ' : ''}${point.x.toFixed(2)} ${point.y.toFixed(2)}`).join(' ')} Z`;
|
|
@@ -135,24 +105,33 @@ function updateAntenna(instance, now) {
|
|
|
135
105
|
const dot = instance._antennaDot;
|
|
136
106
|
const target = antennaTarget(instance);
|
|
137
107
|
|
|
138
|
-
if (
|
|
108
|
+
if (instance._isReducedMotion?.()) {
|
|
139
109
|
spring.x = target.x;
|
|
140
110
|
spring.y = target.y;
|
|
111
|
+
spring.vx = 0;
|
|
112
|
+
spring.vy = 0;
|
|
141
113
|
spring.last = now;
|
|
142
114
|
spring.initialized = true;
|
|
143
|
-
}
|
|
115
|
+
} else {
|
|
116
|
+
if (!spring.initialized) {
|
|
117
|
+
spring.x = target.x;
|
|
118
|
+
spring.y = target.y;
|
|
119
|
+
spring.last = now;
|
|
120
|
+
spring.initialized = true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const dt = Math.max(0.001, Math.min(0.05, (now - spring.last) / 1000 || 0.033));
|
|
124
|
+
spring.last = now;
|
|
144
125
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
spring.x += spring.vx * dt;
|
|
155
|
-
spring.y += spring.vy * dt;
|
|
126
|
+
const stiffness = configNumber('stiffness', ANTENNA_DEFAULTS.stiffness, 5, 300);
|
|
127
|
+
const damping = configNumber('damping', ANTENNA_DEFAULTS.damping, 0.5, 40);
|
|
128
|
+
const ax = (target.x - spring.x) * stiffness - spring.vx * damping;
|
|
129
|
+
const ay = (target.y - spring.y) * stiffness - spring.vy * damping;
|
|
130
|
+
spring.vx += ax * dt;
|
|
131
|
+
spring.vy += ay * dt;
|
|
132
|
+
spring.x += spring.vx * dt;
|
|
133
|
+
spring.y += spring.vy * dt;
|
|
134
|
+
}
|
|
156
135
|
|
|
157
136
|
dot.setAttribute('cx', spring.x.toFixed(2));
|
|
158
137
|
dot.setAttribute('cy', spring.y.toFixed(2));
|
|
@@ -73,8 +73,14 @@ const AgentRobotAvatar = (() => {
|
|
|
73
73
|
if (face._dragJelly.active) face._onDragMove(event);
|
|
74
74
|
});
|
|
75
75
|
};
|
|
76
|
-
const handleGlobalPointerDown =
|
|
77
|
-
|
|
76
|
+
const handleGlobalPointerDown = event => forEachConnectedFace(face => {
|
|
77
|
+
if (typeof face._noteEnvironmentActivity === 'function') face._noteEnvironmentActivity(event);
|
|
78
|
+
else face.noteActivity();
|
|
79
|
+
});
|
|
80
|
+
const handleGlobalKeyDown = event => forEachConnectedFace(face => {
|
|
81
|
+
if (typeof face._noteEnvironmentActivity === 'function') face._noteEnvironmentActivity(event);
|
|
82
|
+
else face.noteActivity();
|
|
83
|
+
});
|
|
78
84
|
const handleGlobalPointerEnd = event => {
|
|
79
85
|
forEachConnectedFace(face => {
|
|
80
86
|
if (face._dragJelly.active) face._onDragEnd(event);
|
|
@@ -175,26 +181,34 @@ const AgentRobotAvatar = (() => {
|
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
disconnectedCallback() {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
// This callback is captured when the custom element is defined, so the
|
|
185
|
+
// runtime teardown must be entered from the registered core lifecycle.
|
|
186
|
+
this._runtimeDisconnecting = true;
|
|
187
|
+
try {
|
|
188
|
+
unregisterConnectedFace(this);
|
|
189
|
+
this.removeEventListener('pointerdown', this._boundDragStart);
|
|
190
|
+
this.removeEventListener('click', this._boundClickCapture, true);
|
|
191
|
+
// Removal cancels work; reattachment starts idle with appearance settings intact.
|
|
192
|
+
this._resetToIdle(false);
|
|
193
|
+
this._blinkAnim = null;
|
|
194
|
+
this._blink = 1;
|
|
195
|
+
this._pointer.active = false;
|
|
196
|
+
this._pointer.influence = 0;
|
|
197
|
+
const drag = this._dragJelly;
|
|
198
|
+
if (drag.pointerId != null) {
|
|
199
|
+
try { this.releasePointerCapture(drag.pointerId); } catch (_) {}
|
|
200
|
+
}
|
|
201
|
+
drag.active = false;
|
|
202
|
+
drag.pointerId = null;
|
|
203
|
+
this._finishDragReturn(null);
|
|
204
|
+
this._suppressClick = false;
|
|
205
|
+
this._running = false;
|
|
206
|
+
cancelAnimationFrame(this._raf);
|
|
207
|
+
this._raf = 0;
|
|
208
|
+
} finally {
|
|
209
|
+
this._teardownRuntime?.();
|
|
210
|
+
this._runtimeDisconnecting = false;
|
|
190
211
|
}
|
|
191
|
-
drag.active = false;
|
|
192
|
-
drag.pointerId = null;
|
|
193
|
-
this._finishDragReturn(null);
|
|
194
|
-
this._suppressClick = false;
|
|
195
|
-
this._running = false;
|
|
196
|
-
cancelAnimationFrame(this._raf);
|
|
197
|
-
this._raf = 0;
|
|
198
212
|
}
|
|
199
213
|
|
|
200
214
|
static get observedAttributes() { return ['color','size','auto-sleep']; }
|
|
@@ -202,7 +216,10 @@ const AgentRobotAvatar = (() => {
|
|
|
202
216
|
if (oldV === newV) return;
|
|
203
217
|
if (name === 'color') this._applyColor(newV);
|
|
204
218
|
if (name === 'size') this.style.setProperty('--face-size', `${Number(newV)||112}px`);
|
|
205
|
-
if (name === 'auto-sleep')
|
|
219
|
+
if (name === 'auto-sleep') {
|
|
220
|
+
this._autoSleepMs = Number(newV) || 0;
|
|
221
|
+
this._scheduleAutoSleep?.();
|
|
222
|
+
}
|
|
206
223
|
}
|
|
207
224
|
|
|
208
225
|
_applyColor(value) {
|
|
@@ -1096,7 +1113,6 @@ const AgentRobotAvatar = (() => {
|
|
|
1096
1113
|
this._updateLook(ts, dt);
|
|
1097
1114
|
this._updateHeadFollow(ts, dt);
|
|
1098
1115
|
this._updateDragJelly(dt);
|
|
1099
|
-
this._autoSleep(ts);
|
|
1100
1116
|
this._draw(ts);
|
|
1101
1117
|
}
|
|
1102
1118
|
if (this._canPauseFrames(ts)) {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const DEFAULT_HEAD_HEIGHT_SCALE = 0.9;
|
|
2
|
+
|
|
3
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
4
|
+
const smooth01 = value => {
|
|
5
|
+
const t = clamp(value, 0, 1);
|
|
6
|
+
return t * t * (3 - 2 * t);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function flattenHeadPoints(points, heightScale = DEFAULT_HEAD_HEIGHT_SCALE) {
|
|
10
|
+
if (!points?.length) return [];
|
|
11
|
+
const source = points.map(point => ({ x: point.x, y: point.y }));
|
|
12
|
+
const minY = Math.min(...source.map(point => point.y));
|
|
13
|
+
const maxY = Math.max(...source.map(point => point.y));
|
|
14
|
+
const height = maxY - minY;
|
|
15
|
+
if (!(height > 0)) return source;
|
|
16
|
+
|
|
17
|
+
const scale = clamp(Number(heightScale), 0.1, 1);
|
|
18
|
+
const centerY = (minY + maxY) / 2;
|
|
19
|
+
const inset = height * (1 - scale) / 2;
|
|
20
|
+
const upperLockY = minY + height * 0.40;
|
|
21
|
+
const lowerLockY = maxY - height * 0.40;
|
|
22
|
+
|
|
23
|
+
return source.map(point => {
|
|
24
|
+
let shiftY = 0;
|
|
25
|
+
if (point.y <= upperLockY) {
|
|
26
|
+
shiftY = inset;
|
|
27
|
+
} else if (point.y < centerY) {
|
|
28
|
+
const t = (centerY - point.y) / Math.max(0.001, centerY - upperLockY);
|
|
29
|
+
shiftY = inset * smooth01(t);
|
|
30
|
+
} else if (point.y >= lowerLockY) {
|
|
31
|
+
shiftY = -inset;
|
|
32
|
+
} else if (point.y > centerY) {
|
|
33
|
+
const t = (point.y - centerY) / Math.max(0.001, lowerLockY - centerY);
|
|
34
|
+
shiftY = -inset * smooth01(t);
|
|
35
|
+
}
|
|
36
|
+
return { x: point.x, y: point.y + shiftY };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { DEFAULT_HEAD_HEIGHT_SCALE, flattenHeadPoints };
|
|
@@ -1,47 +1,14 @@
|
|
|
1
1
|
import AgentRobotAvatar from './agent-robot-avatar-extension-host.js';
|
|
2
|
+
import { flattenHeadPoints } from './agent-robot-avatar-geometry.js';
|
|
2
3
|
import { VERSION } from './agent-robot-avatar-version.js';
|
|
3
4
|
|
|
4
5
|
const proto = AgentRobotAvatar.prototype;
|
|
5
|
-
const HEAD_HEIGHT_SCALE = 0.9;
|
|
6
6
|
const DEFAULT_ROUNDNESS = 50;
|
|
7
7
|
|
|
8
8
|
function clamp(value, min, max) {
|
|
9
9
|
return Math.max(min, Math.min(max, value));
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
function smooth01(t) {
|
|
13
|
-
t = clamp(t, 0, 1);
|
|
14
|
-
return t * t * (3 - 2 * t);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function flattenPoints(points) {
|
|
18
|
-
if (!points?.length) return [];
|
|
19
|
-
const source = points.map(point => ({ x: point.x, y: point.y }));
|
|
20
|
-
const minY = Math.min(...source.map(point => point.y));
|
|
21
|
-
const maxY = Math.max(...source.map(point => point.y));
|
|
22
|
-
const height = maxY - minY;
|
|
23
|
-
const centerY = (minY + maxY) / 2;
|
|
24
|
-
const inset = height * (1 - HEAD_HEIGHT_SCALE) / 2;
|
|
25
|
-
const upperLockY = minY + height * 0.40;
|
|
26
|
-
const lowerLockY = maxY - height * 0.40;
|
|
27
|
-
|
|
28
|
-
return source.map(point => {
|
|
29
|
-
let shiftY = 0;
|
|
30
|
-
if (point.y <= upperLockY) {
|
|
31
|
-
shiftY = inset;
|
|
32
|
-
} else if (point.y < centerY) {
|
|
33
|
-
const t = (centerY - point.y) / Math.max(0.001, centerY - upperLockY);
|
|
34
|
-
shiftY = inset * smooth01(t);
|
|
35
|
-
} else if (point.y >= lowerLockY) {
|
|
36
|
-
shiftY = -inset;
|
|
37
|
-
} else if (point.y > centerY) {
|
|
38
|
-
const t = (point.y - centerY) / Math.max(0.001, lowerLockY - centerY);
|
|
39
|
-
shiftY = -inset * smooth01(t);
|
|
40
|
-
}
|
|
41
|
-
return { x: point.x, y: point.y + shiftY };
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
12
|
function ensureDefaultGeometry(instance) {
|
|
46
13
|
if (!instance?._headShape || !instance._baseHeadPoints?.length) return false;
|
|
47
14
|
if (instance._headRoundnessDefaultPoints?.length) return true;
|
|
@@ -49,7 +16,7 @@ function ensureDefaultGeometry(instance) {
|
|
|
49
16
|
let points = instance._baseHeadPoints.map(point => ({ x: point.x, y: point.y }));
|
|
50
17
|
|
|
51
18
|
if (!instance._headFlattenedR32) {
|
|
52
|
-
points =
|
|
19
|
+
points = flattenHeadPoints(points);
|
|
53
20
|
instance._headFlattenedR32 = true;
|
|
54
21
|
instance._baseHeadPoints = points.map(point => ({ ...point }));
|
|
55
22
|
instance._baseHeadPathD = instance._pointsToPath(points);
|
|
@@ -31,46 +31,54 @@ const INSPECT_DEFAULTS = Object.freeze({
|
|
|
31
31
|
open: 260,
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
const INSPECT_BOUNDS = Object.freeze({
|
|
35
|
+
aperture: [6, 40],
|
|
36
|
+
scanOffset: [0, 24],
|
|
37
|
+
prepPause: [0, 2400],
|
|
38
|
+
close: [40, 2400],
|
|
39
|
+
holdClosed: [0, 2400],
|
|
40
|
+
down1: [40, 2400],
|
|
41
|
+
holdDown1: [0, 2400],
|
|
42
|
+
up1: [40, 2400],
|
|
43
|
+
holdUp1: [0, 2400],
|
|
44
|
+
center1: [40, 2400],
|
|
45
|
+
holdCenter: [0, 2400],
|
|
46
|
+
down2: [40, 2400],
|
|
47
|
+
holdDown2: [0, 2400],
|
|
48
|
+
up2: [40, 2400],
|
|
49
|
+
holdUp2: [0, 2400],
|
|
50
|
+
center2: [40, 2400],
|
|
51
|
+
holdAfter: [0, 2400],
|
|
52
|
+
open: [40, 2400],
|
|
53
|
+
});
|
|
54
|
+
|
|
34
55
|
const runtimeWindow = typeof window !== 'undefined' ? window : null;
|
|
35
56
|
const inspectConfig = (runtimeWindow?.AgentRobotAvatarInspectConfig && typeof runtimeWindow.AgentRobotAvatarInspectConfig === 'object')
|
|
36
57
|
? runtimeWindow.AgentRobotAvatarInspectConfig
|
|
37
58
|
: {};
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
|
|
60
|
+
function normalizeConfigValue(key, value) {
|
|
61
|
+
const fallback = INSPECT_DEFAULTS[key];
|
|
62
|
+
const numeric = Number(value);
|
|
63
|
+
if (!Number.isFinite(numeric)) return fallback;
|
|
64
|
+
const [min, max] = INSPECT_BOUNDS[key];
|
|
65
|
+
return Math.max(min, Math.min(max, numeric));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const key of Object.keys(INSPECT_DEFAULTS)) {
|
|
69
|
+
inspectConfig[key] = normalizeConfigValue(key, inspectConfig[key]);
|
|
40
70
|
}
|
|
41
71
|
if (runtimeWindow) {
|
|
42
72
|
runtimeWindow.AgentRobotAvatarInspectConfig = inspectConfig;
|
|
43
73
|
runtimeWindow.AgentRobotAvatarInspectDefaults = INSPECT_DEFAULTS;
|
|
44
74
|
}
|
|
45
75
|
|
|
46
|
-
function configNumber(key, fallback, min, max) {
|
|
47
|
-
const value = Number(inspectConfig[key]);
|
|
48
|
-
if (!Number.isFinite(value)) return fallback;
|
|
49
|
-
return Math.max(min, Math.min(max, value));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
76
|
function currentInspectConfig() {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
close: Math.max(40, duration('close')),
|
|
59
|
-
holdClosed: duration('holdClosed'),
|
|
60
|
-
down1: Math.max(40, duration('down1')),
|
|
61
|
-
holdDown1: duration('holdDown1'),
|
|
62
|
-
up1: Math.max(40, duration('up1')),
|
|
63
|
-
holdUp1: duration('holdUp1'),
|
|
64
|
-
center1: Math.max(40, duration('center1')),
|
|
65
|
-
holdCenter: duration('holdCenter'),
|
|
66
|
-
down2: Math.max(40, duration('down2')),
|
|
67
|
-
holdDown2: duration('holdDown2'),
|
|
68
|
-
up2: Math.max(40, duration('up2')),
|
|
69
|
-
holdUp2: duration('holdUp2'),
|
|
70
|
-
center2: Math.max(40, duration('center2')),
|
|
71
|
-
holdAfter: duration('holdAfter'),
|
|
72
|
-
open: Math.max(40, duration('open')),
|
|
73
|
-
};
|
|
77
|
+
const config = {};
|
|
78
|
+
for (const key of Object.keys(INSPECT_DEFAULTS)) {
|
|
79
|
+
config[key] = normalizeConfigValue(key, inspectConfig[key]);
|
|
80
|
+
}
|
|
81
|
+
return config;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
function dispatchInspectState(instance, state) {
|