@vortexm/vjt 0.1.4 → 0.1.5
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/dist/index.d.ts +1 -1
- package/dist/index.js +812 -84
- package/dist/lib/action-runtime.d.ts +24 -0
- package/dist/lib/dom-state.d.ts +2 -2
- package/dist/lib/network.d.ts +2 -0
- package/dist/lib/references.d.ts +2 -0
- package/dist/lib/types.d.ts +3 -1
- package/dist/lib/voice-runtime.d.ts +48 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { renderApp, renderJson } from './lib/render.js';
|
|
2
2
|
export { ResourceManager } from './lib/resource-manager.js';
|
|
3
3
|
export { DEFAULT_STYLE_MAP } from './lib/default-styles.js';
|
|
4
|
-
export type { ActionDefinition, ActionMap, AdaptiveLayoutNode, BaseNode, ButtonNode, CheckboxNode, ComboboxNode, ConditionExpression, ConditionalContainerNode, ContextMenuNode, ContainerCell, ContainerLayoutNode, CellConstraints, DescriptionNode, GridViewNode, HeadingTag, I18nMap, ImageNode, LayoutType, LinkNode, ListElementContext, ListNode, ListboxNode, MdTextNode, ModalWindowNode, MountJsonOptions, OverlayContainerNode, OverlayLayer, PanelNode, PrimitiveRequestType, RadioGroupNode, RequestDefinition, RequestMap, RequestSchema, RenderJsonOptions, RuntimeSnapshot, SseConfig, SseConfigInput, SseEventDefinition, SpoilerNode, SplitterNode, StaticTextNode, StyleMap, TabsNode, TextAlign, TextareaNode, UiReferenceNode, Theme, VerticalAlign, WidgetEventName, WidgetState } from './lib/types.js';
|
|
4
|
+
export type { ActionDefinition, ActionMap, AdaptiveLayoutNode, BaseNode, ButtonNode, CheckboxNode, ComboboxNode, ConditionExpression, ConditionalContainerNode, ContextMenuNode, ContainerCell, ContainerLayoutNode, CellConstraints, DescriptionNode, GridViewNode, HeadingTag, I18nMap, ImageNode, LayoutType, LinkNode, ListElementContext, ListNode, ListboxNode, MdTextNode, ModalWindowNode, MountJsonOptions, OverlayContainerNode, OverlayLayer, PanelNode, PrimitiveRequestType, RadioGroupNode, RequestDefinition, RequestMap, RequestSchema, RenderJsonOptions, RuntimeSnapshot, SseConfig, SseConfigInput, SseEventDefinition, SpoilerNode, SplitterNode, StaticTextNode, StyleMap, SystemEventName, TabsNode, TextAlign, TextareaNode, UiReferenceNode, Theme, VerticalAlign, WidgetEventName, WidgetState } from './lib/types.js';
|
package/dist/index.js
CHANGED
|
@@ -3904,69 +3904,111 @@ var NetworkRuntime = class {
|
|
|
3904
3904
|
if (!definition) {
|
|
3905
3905
|
return null;
|
|
3906
3906
|
}
|
|
3907
|
-
const payload = await this.buildSchemaValue(definition.request, currentValue, void 0);
|
|
3908
|
-
const requestEnvelope = {
|
|
3909
|
-
jsonrpc: "2.0",
|
|
3910
|
-
id: Date.now(),
|
|
3911
|
-
method: requestName,
|
|
3912
|
-
params: payload
|
|
3913
|
-
};
|
|
3914
|
-
const requestUrl = definition.url ?? this.backendUrl;
|
|
3915
|
-
if (!requestUrl) {
|
|
3916
|
-
throw new Error(`Request ${requestName} has no url in config`);
|
|
3917
|
-
}
|
|
3918
|
-
const safeRequestUrl = sanitizeUrl(requestUrl, { allowRelative: true });
|
|
3919
|
-
if (!safeRequestUrl) {
|
|
3920
|
-
throw new Error(`Request ${requestName} has unsafe url`);
|
|
3921
|
-
}
|
|
3922
|
-
const requestBody = JSON.stringify(requestEnvelope);
|
|
3923
|
-
logRuntimeDebug(this.debugLogging, "request", {
|
|
3924
|
-
requestName,
|
|
3925
|
-
url: safeRequestUrl,
|
|
3926
|
-
envelope: requestEnvelope,
|
|
3927
|
-
body: requestBody
|
|
3928
|
-
});
|
|
3929
|
-
const response = await fetch(safeRequestUrl, {
|
|
3930
|
-
method: "POST",
|
|
3931
|
-
headers: {
|
|
3932
|
-
"Content-Type": "application/json"
|
|
3933
|
-
},
|
|
3934
|
-
body: requestBody
|
|
3935
|
-
});
|
|
3936
|
-
const responseText = await response.text();
|
|
3937
|
-
if (!response.ok) {
|
|
3938
|
-
throw new Error(`Request ${requestName} failed: ${response.status} ${response.statusText || responseText}`);
|
|
3939
|
-
}
|
|
3940
|
-
if (!responseText.trim()) {
|
|
3941
|
-
throw new Error(`Request ${requestName} returned empty response body`);
|
|
3942
|
-
}
|
|
3943
|
-
let json;
|
|
3944
3907
|
try {
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3908
|
+
const payload = await this.buildSchemaValue(definition.request, currentValue, void 0);
|
|
3909
|
+
const requestEnvelope = {
|
|
3910
|
+
jsonrpc: "2.0",
|
|
3911
|
+
id: Date.now(),
|
|
3912
|
+
method: requestName,
|
|
3913
|
+
params: payload
|
|
3914
|
+
};
|
|
3915
|
+
const requestUrl = definition.url ?? this.backendUrl;
|
|
3916
|
+
if (!requestUrl) {
|
|
3917
|
+
throw new Error(`Request ${requestName} has no url in config`);
|
|
3918
|
+
}
|
|
3919
|
+
const safeRequestUrl = sanitizeUrl(requestUrl, { allowRelative: true });
|
|
3920
|
+
if (!safeRequestUrl) {
|
|
3921
|
+
throw new Error(`Request ${requestName} has unsafe url`);
|
|
3922
|
+
}
|
|
3923
|
+
const requestBody = JSON.stringify(requestEnvelope);
|
|
3924
|
+
logRuntimeDebug(this.debugLogging, "request", {
|
|
3925
|
+
requestName,
|
|
3926
|
+
url: safeRequestUrl,
|
|
3927
|
+
envelope: requestEnvelope,
|
|
3928
|
+
body: requestBody
|
|
3929
|
+
});
|
|
3930
|
+
const response = await fetch(safeRequestUrl, {
|
|
3931
|
+
method: "POST",
|
|
3932
|
+
headers: {
|
|
3933
|
+
"Content-Type": "application/json"
|
|
3934
|
+
},
|
|
3935
|
+
body: requestBody
|
|
3936
|
+
});
|
|
3937
|
+
const responseText = await response.text();
|
|
3938
|
+
if (!response.ok) {
|
|
3939
|
+
throw this.createRequestError(requestName, {
|
|
3940
|
+
message: `Request ${requestName} failed: ${response.status} ${response.statusText || responseText}`,
|
|
3941
|
+
status: response.status,
|
|
3942
|
+
statusText: response.statusText,
|
|
3943
|
+
body: responseText,
|
|
3944
|
+
url: safeRequestUrl
|
|
3945
|
+
});
|
|
3946
|
+
}
|
|
3947
|
+
if (!responseText.trim()) {
|
|
3948
|
+
throw this.createRequestError(requestName, {
|
|
3949
|
+
message: `Request ${requestName} returned empty response body`,
|
|
3950
|
+
status: response.status,
|
|
3951
|
+
statusText: response.statusText,
|
|
3952
|
+
body: responseText,
|
|
3953
|
+
url: safeRequestUrl
|
|
3954
|
+
});
|
|
3955
|
+
}
|
|
3956
|
+
let json;
|
|
3957
|
+
try {
|
|
3958
|
+
json = JSON.parse(responseText);
|
|
3959
|
+
} catch {
|
|
3960
|
+
throw this.createRequestError(requestName, {
|
|
3961
|
+
message: `Request ${requestName} returned non-JSON response: ${responseText.slice(0, 120)}`,
|
|
3962
|
+
status: response.status,
|
|
3963
|
+
statusText: response.statusText,
|
|
3964
|
+
body: responseText,
|
|
3965
|
+
url: safeRequestUrl
|
|
3966
|
+
});
|
|
3967
|
+
}
|
|
3968
|
+
if (json.error) {
|
|
3969
|
+
throw this.createRequestError(requestName, {
|
|
3970
|
+
message: `Request ${requestName} returned JSON-RPC error: ${this.stringifyPrimitive(json.error.message ?? "unknown error")}`,
|
|
3971
|
+
status: response.status,
|
|
3972
|
+
statusText: response.statusText,
|
|
3973
|
+
body: responseText,
|
|
3974
|
+
url: safeRequestUrl,
|
|
3975
|
+
error: json.error
|
|
3976
|
+
});
|
|
3977
|
+
}
|
|
3978
|
+
if (!definition.response) {
|
|
3979
|
+
throw this.createRequestError(requestName, {
|
|
3980
|
+
message: `Request ${requestName} must define response schema`,
|
|
3981
|
+
status: response.status,
|
|
3982
|
+
statusText: response.statusText,
|
|
3983
|
+
body: responseText,
|
|
3984
|
+
url: safeRequestUrl
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
const result = sanitizeSchemaValue(definition.response, json.result, `response.${requestName}`);
|
|
3988
|
+
logRuntimeDebug(this.debugLogging, "response", {
|
|
3989
|
+
requestName,
|
|
3990
|
+
url: safeRequestUrl,
|
|
3991
|
+
status: response.status,
|
|
3992
|
+
json,
|
|
3993
|
+
result
|
|
3967
3994
|
});
|
|
3995
|
+
if (definition.onResponse?.length) {
|
|
3996
|
+
await this.runActions(definition.onResponse, null, {
|
|
3997
|
+
currentValue: null,
|
|
3998
|
+
responseValue: result
|
|
3999
|
+
});
|
|
4000
|
+
}
|
|
4001
|
+
return result;
|
|
4002
|
+
} catch (error) {
|
|
4003
|
+
if (definition.onError?.length) {
|
|
4004
|
+
const errorPayload = this.toRequestErrorPayload(requestName, error);
|
|
4005
|
+
await this.runActions(definition.onError, null, {
|
|
4006
|
+
currentValue: null,
|
|
4007
|
+
responseValue: errorPayload
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
throw error;
|
|
3968
4011
|
}
|
|
3969
|
-
return result;
|
|
3970
4012
|
}
|
|
3971
4013
|
async buildSchemaValue(schema, currentValue, responseValue) {
|
|
3972
4014
|
if (schema.type === "object") {
|
|
@@ -4004,6 +4046,36 @@ var NetworkRuntime = class {
|
|
|
4004
4046
|
}
|
|
4005
4047
|
return JSON.stringify(value);
|
|
4006
4048
|
}
|
|
4049
|
+
createRequestError(requestName, payload) {
|
|
4050
|
+
const error = new Error(payload.message);
|
|
4051
|
+
error.requestPayload = {
|
|
4052
|
+
requestName,
|
|
4053
|
+
message: payload.message,
|
|
4054
|
+
status: payload.status ?? null,
|
|
4055
|
+
statusText: payload.statusText ?? "",
|
|
4056
|
+
body: payload.body ?? "",
|
|
4057
|
+
url: payload.url ?? "",
|
|
4058
|
+
error: payload.error ?? null
|
|
4059
|
+
};
|
|
4060
|
+
return error;
|
|
4061
|
+
}
|
|
4062
|
+
toRequestErrorPayload(requestName, error) {
|
|
4063
|
+
if (typeof error === "object" && error !== null && "requestPayload" in error) {
|
|
4064
|
+
const payload = error.requestPayload;
|
|
4065
|
+
if (typeof payload === "object" && payload !== null && !Array.isArray(payload)) {
|
|
4066
|
+
return payload;
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
return {
|
|
4070
|
+
requestName,
|
|
4071
|
+
message: error instanceof Error ? error.message : this.stringifyPrimitive(error),
|
|
4072
|
+
status: null,
|
|
4073
|
+
statusText: "",
|
|
4074
|
+
body: "",
|
|
4075
|
+
url: "",
|
|
4076
|
+
error: null
|
|
4077
|
+
};
|
|
4078
|
+
}
|
|
4007
4079
|
coercePrimitiveSchemaValue(type, value) {
|
|
4008
4080
|
switch (type) {
|
|
4009
4081
|
case "int": {
|
|
@@ -4186,6 +4258,9 @@ var ReferenceRuntime = class {
|
|
|
4186
4258
|
if (reference.startsWith("url.")) {
|
|
4187
4259
|
return getUrlParamValue(reference.slice(4));
|
|
4188
4260
|
}
|
|
4261
|
+
if (reference.startsWith("vars.")) {
|
|
4262
|
+
return this.host.getVarValue(reference.slice(5));
|
|
4263
|
+
}
|
|
4189
4264
|
const [widgetId, ...rest] = reference.split(".");
|
|
4190
4265
|
const state = this.host.stateById.get(widgetId);
|
|
4191
4266
|
if (!state) {
|
|
@@ -4311,6 +4386,10 @@ var ReferenceRuntime = class {
|
|
|
4311
4386
|
this.host.setAppValue(reference.slice(4), inputValue);
|
|
4312
4387
|
return;
|
|
4313
4388
|
}
|
|
4389
|
+
if (reference.startsWith("vars.")) {
|
|
4390
|
+
this.host.setVarValue(reference.slice(5), inputValue);
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4314
4393
|
const [widgetId, ...rest] = reference.split(".");
|
|
4315
4394
|
const field = rest.join(".");
|
|
4316
4395
|
const state = this.host.stateById.get(widgetId);
|
|
@@ -4489,6 +4568,7 @@ var ActionRuntime = class {
|
|
|
4489
4568
|
actionFunctions;
|
|
4490
4569
|
nodeById;
|
|
4491
4570
|
stateById;
|
|
4571
|
+
stateByKey;
|
|
4492
4572
|
rerenderRoot;
|
|
4493
4573
|
dispatchWidgetEventImpl;
|
|
4494
4574
|
executeRequest;
|
|
@@ -4498,6 +4578,14 @@ var ActionRuntime = class {
|
|
|
4498
4578
|
resolveMappedValue;
|
|
4499
4579
|
setWidgetEnabled;
|
|
4500
4580
|
clearWidget;
|
|
4581
|
+
clearListElementState;
|
|
4582
|
+
focusWidget;
|
|
4583
|
+
playAudio;
|
|
4584
|
+
stopPlaying;
|
|
4585
|
+
startRecording;
|
|
4586
|
+
stopRecording;
|
|
4587
|
+
startListening;
|
|
4588
|
+
stopListening;
|
|
4501
4589
|
setUiReference;
|
|
4502
4590
|
refreshWidgetTree;
|
|
4503
4591
|
renderWidgetTree;
|
|
@@ -4514,6 +4602,7 @@ var ActionRuntime = class {
|
|
|
4514
4602
|
this.actionFunctions = options.actionFunctions;
|
|
4515
4603
|
this.nodeById = options.nodeById;
|
|
4516
4604
|
this.stateById = options.stateById;
|
|
4605
|
+
this.stateByKey = options.stateByKey;
|
|
4517
4606
|
this.rerenderRoot = options.rerenderRoot;
|
|
4518
4607
|
this.dispatchWidgetEventImpl = options.dispatchWidgetEvent;
|
|
4519
4608
|
this.executeRequest = options.executeRequest;
|
|
@@ -4523,6 +4612,14 @@ var ActionRuntime = class {
|
|
|
4523
4612
|
this.resolveMappedValue = options.resolveMappedValue;
|
|
4524
4613
|
this.setWidgetEnabled = options.setWidgetEnabled;
|
|
4525
4614
|
this.clearWidget = options.clearWidget;
|
|
4615
|
+
this.clearListElementState = options.clearListElementState;
|
|
4616
|
+
this.focusWidget = options.focusWidget;
|
|
4617
|
+
this.playAudio = options.playAudio;
|
|
4618
|
+
this.stopPlaying = options.stopPlaying;
|
|
4619
|
+
this.startRecording = options.startRecording;
|
|
4620
|
+
this.stopRecording = options.stopRecording;
|
|
4621
|
+
this.startListening = options.startListening;
|
|
4622
|
+
this.stopListening = options.stopListening;
|
|
4526
4623
|
this.setUiReference = options.setUiReference;
|
|
4527
4624
|
this.refreshWidgetTree = options.refreshWidgetTree;
|
|
4528
4625
|
this.renderWidgetTree = options.renderWidgetTree;
|
|
@@ -4571,13 +4668,26 @@ var ActionRuntime = class {
|
|
|
4571
4668
|
if (action.andThen?.length) {
|
|
4572
4669
|
if (Array.isArray(output)) {
|
|
4573
4670
|
const collected = [];
|
|
4574
|
-
for (const entry of output) {
|
|
4671
|
+
for (const [index, entry] of output.entries()) {
|
|
4575
4672
|
if (entry === null || entry === void 0) {
|
|
4576
4673
|
continue;
|
|
4577
4674
|
}
|
|
4578
|
-
const nestedOutput = await this.runActions(action.andThen, entry, {
|
|
4675
|
+
const nestedOutput = await this.runActions(action.andThen, entry, {
|
|
4676
|
+
...context,
|
|
4677
|
+
currentValue: entry,
|
|
4678
|
+
currentList: output,
|
|
4679
|
+
currentIndex: index
|
|
4680
|
+
});
|
|
4579
4681
|
if (nestedOutput !== null && nestedOutput !== void 0) {
|
|
4580
|
-
|
|
4682
|
+
if (Array.isArray(nestedOutput)) {
|
|
4683
|
+
for (const item of nestedOutput) {
|
|
4684
|
+
if (item !== null && item !== void 0) {
|
|
4685
|
+
collected.push(item);
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
} else {
|
|
4689
|
+
collected.push(nestedOutput);
|
|
4690
|
+
}
|
|
4581
4691
|
}
|
|
4582
4692
|
}
|
|
4583
4693
|
output = collected;
|
|
@@ -4641,6 +4751,30 @@ var ActionRuntime = class {
|
|
|
4641
4751
|
await this.navigateTo(name.slice(9));
|
|
4642
4752
|
return null;
|
|
4643
4753
|
}
|
|
4754
|
+
if (name.startsWith("play ")) {
|
|
4755
|
+
await this.playAudio(this.resolveReference(name.slice(5), context.currentValue, context.responseValue));
|
|
4756
|
+
return null;
|
|
4757
|
+
}
|
|
4758
|
+
if (name === "stopPlaying") {
|
|
4759
|
+
await this.stopPlaying();
|
|
4760
|
+
return null;
|
|
4761
|
+
}
|
|
4762
|
+
if (name === "startRecording") {
|
|
4763
|
+
await this.startRecording();
|
|
4764
|
+
return null;
|
|
4765
|
+
}
|
|
4766
|
+
if (name === "stopRecording") {
|
|
4767
|
+
await this.stopRecording();
|
|
4768
|
+
return null;
|
|
4769
|
+
}
|
|
4770
|
+
if (name === "startListening") {
|
|
4771
|
+
await this.startListening();
|
|
4772
|
+
return null;
|
|
4773
|
+
}
|
|
4774
|
+
if (name === "stopListening") {
|
|
4775
|
+
await this.stopListening();
|
|
4776
|
+
return null;
|
|
4777
|
+
}
|
|
4644
4778
|
if (name.startsWith("showMenu ")) {
|
|
4645
4779
|
const menuId = name.slice(9);
|
|
4646
4780
|
const menuState = this.stateById.get(menuId);
|
|
@@ -4680,6 +4814,10 @@ var ActionRuntime = class {
|
|
|
4680
4814
|
this.clearWidget(name.slice(6));
|
|
4681
4815
|
return null;
|
|
4682
4816
|
}
|
|
4817
|
+
if (name.startsWith("setFocus ")) {
|
|
4818
|
+
this.focusWidget(name.slice(9));
|
|
4819
|
+
return null;
|
|
4820
|
+
}
|
|
4683
4821
|
if (name.startsWith("setUi ")) {
|
|
4684
4822
|
const widgetId = name.slice(6);
|
|
4685
4823
|
const resourceRef = typeof inputValue === "string" ? inputValue : "";
|
|
@@ -4702,10 +4840,74 @@ var ActionRuntime = class {
|
|
|
4702
4840
|
this.assignReference(name.slice(4), inputValue, context.currentValue, args);
|
|
4703
4841
|
return inputValue;
|
|
4704
4842
|
}
|
|
4843
|
+
if (name.startsWith("append ")) {
|
|
4844
|
+
const reference = name.slice(7);
|
|
4845
|
+
const currentText = this.resolveReference(reference, context.currentValue, context.responseValue);
|
|
4846
|
+
const nextValue = `${this.stringifyValue(currentText)}${this.stringifyValue(action.args)}`;
|
|
4847
|
+
this.assignReference(reference, nextValue, context.currentValue);
|
|
4848
|
+
return nextValue;
|
|
4849
|
+
}
|
|
4705
4850
|
if (name.startsWith("filter ")) {
|
|
4706
4851
|
const value = this.resolveReference(name.slice(7), context.currentValue, context.responseValue);
|
|
4707
4852
|
return value ? inputValue : null;
|
|
4708
4853
|
}
|
|
4854
|
+
if (name === "remove") {
|
|
4855
|
+
const listState = this.getCurrentListState(context.currentValue);
|
|
4856
|
+
if (listState && typeof context.currentValue === "object" && context.currentValue !== null && "index" in context.currentValue) {
|
|
4857
|
+
const index = context.currentValue.index;
|
|
4858
|
+
if (typeof index === "number" && Array.isArray(listState.elements)) {
|
|
4859
|
+
listState.elements = listState.elements.filter((_, elementIndex) => elementIndex !== index);
|
|
4860
|
+
this.clearListElementState(listState.key);
|
|
4861
|
+
return listState.elements;
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
return null;
|
|
4865
|
+
}
|
|
4866
|
+
if (name.startsWith("add ")) {
|
|
4867
|
+
const valueToAdd = this.cloneValue(this.unwrapListValue(this.resolveReference(name.slice(4), context.currentValue, context.responseValue)));
|
|
4868
|
+
const listState = this.getCurrentListState(context.currentValue);
|
|
4869
|
+
if (listState && Array.isArray(listState.elements)) {
|
|
4870
|
+
listState.elements.push(valueToAdd);
|
|
4871
|
+
this.clearListElementState(listState.key);
|
|
4872
|
+
return listState.elements;
|
|
4873
|
+
}
|
|
4874
|
+
if (context.currentList && typeof context.currentIndex === "number") {
|
|
4875
|
+
return context.currentIndex === context.currentList.length - 1 ? [this.unwrapListValue(inputValue), valueToAdd] : this.unwrapListValue(inputValue);
|
|
4876
|
+
}
|
|
4877
|
+
if (Array.isArray(inputValue)) {
|
|
4878
|
+
const nextList = [];
|
|
4879
|
+
for (const item of inputValue) {
|
|
4880
|
+
nextList.push(item);
|
|
4881
|
+
}
|
|
4882
|
+
nextList.push(valueToAdd);
|
|
4883
|
+
return nextList;
|
|
4884
|
+
}
|
|
4885
|
+
return inputValue;
|
|
4886
|
+
}
|
|
4887
|
+
if (name.startsWith("insert ")) {
|
|
4888
|
+
const valueToInsert = this.cloneValue(this.unwrapListValue(this.resolveReference(name.slice(7), context.currentValue, context.responseValue)));
|
|
4889
|
+
const listState = this.getCurrentListState(context.currentValue);
|
|
4890
|
+
if (listState && Array.isArray(listState.elements) && typeof context.currentValue === "object" && context.currentValue !== null && "index" in context.currentValue) {
|
|
4891
|
+
const index = context.currentValue.index;
|
|
4892
|
+
if (typeof index === "number") {
|
|
4893
|
+
listState.elements.splice(index + 1, 0, valueToInsert);
|
|
4894
|
+
this.clearListElementState(listState.key);
|
|
4895
|
+
return listState.elements;
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
if (context.currentList && typeof context.currentIndex === "number") {
|
|
4899
|
+
return [this.unwrapListValue(inputValue), valueToInsert];
|
|
4900
|
+
}
|
|
4901
|
+
if (Array.isArray(inputValue)) {
|
|
4902
|
+
const nextList = [];
|
|
4903
|
+
for (const item of inputValue) {
|
|
4904
|
+
nextList.push(item);
|
|
4905
|
+
}
|
|
4906
|
+
nextList.push(valueToInsert);
|
|
4907
|
+
return nextList;
|
|
4908
|
+
}
|
|
4909
|
+
return inputValue;
|
|
4910
|
+
}
|
|
4709
4911
|
if (name === "map") {
|
|
4710
4912
|
return this.resolveMappedValue(action.args, context.currentValue, context.responseValue);
|
|
4711
4913
|
}
|
|
@@ -4722,10 +4924,40 @@ var ActionRuntime = class {
|
|
|
4722
4924
|
}
|
|
4723
4925
|
return inputValue;
|
|
4724
4926
|
}
|
|
4927
|
+
unwrapListValue(value) {
|
|
4928
|
+
return isListElementLike(value) ? value.descriptor : value;
|
|
4929
|
+
}
|
|
4930
|
+
getCurrentListState(currentValue) {
|
|
4931
|
+
if (!isListElementLike(currentValue) || typeof currentValue.listId !== "string") {
|
|
4932
|
+
return null;
|
|
4933
|
+
}
|
|
4934
|
+
return this.stateByKey.get(currentValue.listId) ?? this.stateById.get(currentValue.listId) ?? null;
|
|
4935
|
+
}
|
|
4936
|
+
cloneValue(value) {
|
|
4937
|
+
if (value === null || value === void 0) {
|
|
4938
|
+
return value;
|
|
4939
|
+
}
|
|
4940
|
+
if (typeof value === "object") {
|
|
4941
|
+
return structuredClone(value);
|
|
4942
|
+
}
|
|
4943
|
+
return value;
|
|
4944
|
+
}
|
|
4945
|
+
stringifyValue(value) {
|
|
4946
|
+
if (value == null) {
|
|
4947
|
+
return "";
|
|
4948
|
+
}
|
|
4949
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
4950
|
+
return String(value);
|
|
4951
|
+
}
|
|
4952
|
+
return JSON.stringify(value);
|
|
4953
|
+
}
|
|
4725
4954
|
};
|
|
4726
4955
|
function isIfElseArgs(value) {
|
|
4727
4956
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4728
4957
|
}
|
|
4958
|
+
function isListElementLike(value) {
|
|
4959
|
+
return typeof value === "object" && value !== null && "kind" in value && value.kind === "list-element" && "listId" in value && typeof value.listId === "string" && "index" in value && typeof value.index === "number" && "descriptor" in value;
|
|
4960
|
+
}
|
|
4729
4961
|
|
|
4730
4962
|
// src/lib/dom-state.ts
|
|
4731
4963
|
function isPlainObject3(value) {
|
|
@@ -4754,7 +4986,7 @@ function isElementInsidePendingResetModal(element, pendingResetModalIds) {
|
|
|
4754
4986
|
}
|
|
4755
4987
|
return false;
|
|
4756
4988
|
}
|
|
4757
|
-
function captureElementState(root, pendingResetModalIds
|
|
4989
|
+
function captureElementState(root, pendingResetModalIds) {
|
|
4758
4990
|
const state = {
|
|
4759
4991
|
activeId: null,
|
|
4760
4992
|
values: /* @__PURE__ */ new Map(),
|
|
@@ -4782,20 +5014,6 @@ function captureElementState(root, pendingResetModalIds, stateByKey) {
|
|
|
4782
5014
|
if (element instanceof HTMLInputElement && element.type === "checkbox" && element.id) {
|
|
4783
5015
|
state.checked.set(element.id, element.checked);
|
|
4784
5016
|
}
|
|
4785
|
-
const widgetKey = element.dataset.widgetKey;
|
|
4786
|
-
if (!widgetKey) {
|
|
4787
|
-
continue;
|
|
4788
|
-
}
|
|
4789
|
-
const widgetState = stateByKey.get(widgetKey);
|
|
4790
|
-
if (!widgetState || element.readOnly || element.disabled) {
|
|
4791
|
-
continue;
|
|
4792
|
-
}
|
|
4793
|
-
if (widgetState.widget === "edit" || widgetState.widget === "textarea") {
|
|
4794
|
-
widgetState.text = element.value;
|
|
4795
|
-
}
|
|
4796
|
-
if (widgetState.widget === "checkbox" && element instanceof HTMLInputElement && element.type === "checkbox") {
|
|
4797
|
-
widgetState.checked = element.checked;
|
|
4798
|
-
}
|
|
4799
5017
|
}
|
|
4800
5018
|
for (const element of Array.from(root.querySelectorAll("[id], [data-widget-key]"))) {
|
|
4801
5019
|
const top = element.scrollTop;
|
|
@@ -4817,16 +5035,44 @@ function captureElementState(root, pendingResetModalIds, stateByKey) {
|
|
|
4817
5035
|
}
|
|
4818
5036
|
return state;
|
|
4819
5037
|
}
|
|
4820
|
-
function
|
|
5038
|
+
function shouldRestoreTextValue(element, preservedValue, stateByKey) {
|
|
5039
|
+
const widgetKey = element.dataset.widgetKey;
|
|
5040
|
+
if (!widgetKey) {
|
|
5041
|
+
return true;
|
|
5042
|
+
}
|
|
5043
|
+
const widgetState = stateByKey.get(widgetKey);
|
|
5044
|
+
if (!widgetState || widgetState.widget !== "edit" && widgetState.widget !== "textarea") {
|
|
5045
|
+
return true;
|
|
5046
|
+
}
|
|
5047
|
+
return (widgetState.text ?? "") === preservedValue;
|
|
5048
|
+
}
|
|
5049
|
+
function shouldRestoreCheckedValue(element, preservedValue, stateByKey) {
|
|
5050
|
+
const widgetKey = element.dataset.widgetKey;
|
|
5051
|
+
if (!widgetKey) {
|
|
5052
|
+
return true;
|
|
5053
|
+
}
|
|
5054
|
+
const widgetState = stateByKey.get(widgetKey);
|
|
5055
|
+
if (!widgetState || widgetState.widget !== "checkbox") {
|
|
5056
|
+
return true;
|
|
5057
|
+
}
|
|
5058
|
+
return (widgetState.checked ?? false) === preservedValue;
|
|
5059
|
+
}
|
|
5060
|
+
function restoreElementState(root, state, stateByKey) {
|
|
4821
5061
|
for (const [id, value] of state.values.entries()) {
|
|
4822
5062
|
const element = root.querySelector(`#${CSS.escape(id)}`);
|
|
4823
5063
|
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
5064
|
+
if (!shouldRestoreTextValue(element, value, stateByKey)) {
|
|
5065
|
+
continue;
|
|
5066
|
+
}
|
|
4824
5067
|
element.value = value;
|
|
4825
5068
|
}
|
|
4826
5069
|
}
|
|
4827
5070
|
for (const [id, checked] of state.checked.entries()) {
|
|
4828
5071
|
const element = root.querySelector(`#${CSS.escape(id)}`);
|
|
4829
5072
|
if (element instanceof HTMLInputElement && element.type === "checkbox") {
|
|
5073
|
+
if (!shouldRestoreCheckedValue(element, checked, stateByKey)) {
|
|
5074
|
+
continue;
|
|
5075
|
+
}
|
|
4830
5076
|
element.checked = checked;
|
|
4831
5077
|
}
|
|
4832
5078
|
}
|
|
@@ -5129,6 +5375,430 @@ var DEFAULT_STYLE_MAP = {
|
|
|
5129
5375
|
employeeLinkDark: "display:flex; align-items:center; width:100%; height:100%; color:#b8d0ea; font-weight:600;"
|
|
5130
5376
|
};
|
|
5131
5377
|
|
|
5378
|
+
// src/lib/voice-runtime.ts
|
|
5379
|
+
var MAX_CONCURRENT_PLAYERS = 5;
|
|
5380
|
+
var MAX_RECORDING_MS = 10 * 60 * 1e3;
|
|
5381
|
+
var MAX_LISTENING_SILENCE_MS = 60 * 60 * 1e3;
|
|
5382
|
+
var SILENCE_STOP_MS = 2e3;
|
|
5383
|
+
var SPEECH_THRESHOLD = 0.035;
|
|
5384
|
+
var RECORDING_MIME_CANDIDATES = [
|
|
5385
|
+
"audio/webm;codecs=opus",
|
|
5386
|
+
"audio/webm",
|
|
5387
|
+
"audio/mp4",
|
|
5388
|
+
"audio/mp4;codecs=mp4a.40.2",
|
|
5389
|
+
"audio/wav"
|
|
5390
|
+
];
|
|
5391
|
+
function isPlayableAudioPayload(value) {
|
|
5392
|
+
return typeof value === "object" && value !== null;
|
|
5393
|
+
}
|
|
5394
|
+
function decodeBase64(base64) {
|
|
5395
|
+
const normalized = base64.includes(",") ? base64.slice(base64.indexOf(",") + 1) : base64;
|
|
5396
|
+
const binary = atob(normalized);
|
|
5397
|
+
const bytes = new Uint8Array(binary.length);
|
|
5398
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
5399
|
+
bytes[index] = binary.charCodeAt(index);
|
|
5400
|
+
}
|
|
5401
|
+
return bytes;
|
|
5402
|
+
}
|
|
5403
|
+
function detectMimeType(bytes) {
|
|
5404
|
+
if (bytes.length >= 4 && bytes[0] === 26 && bytes[1] === 69 && bytes[2] === 223 && bytes[3] === 163) {
|
|
5405
|
+
return "audio/webm";
|
|
5406
|
+
}
|
|
5407
|
+
if (bytes.length >= 12 && bytes[4] === 102 && bytes[5] === 116 && bytes[6] === 121 && bytes[7] === 112) {
|
|
5408
|
+
return "audio/mp4";
|
|
5409
|
+
}
|
|
5410
|
+
if (bytes.length >= 3 && bytes[0] === 73 && bytes[1] === 68 && bytes[2] === 51) {
|
|
5411
|
+
return "audio/mpeg";
|
|
5412
|
+
}
|
|
5413
|
+
if (bytes.length >= 2 && bytes[0] === 255 && (bytes[1] & 224) === 224) {
|
|
5414
|
+
return "audio/mpeg";
|
|
5415
|
+
}
|
|
5416
|
+
if (bytes.length >= 12 && bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 65 && bytes[10] === 86 && bytes[11] === 69) {
|
|
5417
|
+
return "audio/wav";
|
|
5418
|
+
}
|
|
5419
|
+
if (bytes.length >= 4 && bytes[0] === 79 && bytes[1] === 103 && bytes[2] === 103 && bytes[3] === 83) {
|
|
5420
|
+
return "audio/ogg";
|
|
5421
|
+
}
|
|
5422
|
+
return "audio/mpeg";
|
|
5423
|
+
}
|
|
5424
|
+
function blobToBase64(blob) {
|
|
5425
|
+
return new Promise((resolve, reject) => {
|
|
5426
|
+
const reader = new FileReader();
|
|
5427
|
+
reader.onerror = () => reject(reader.error ?? new Error("Failed to read recorded audio data"));
|
|
5428
|
+
reader.onloadend = () => {
|
|
5429
|
+
const result = typeof reader.result === "string" ? reader.result : "";
|
|
5430
|
+
const base64 = result.includes(",") ? result.slice(result.indexOf(",") + 1) : result;
|
|
5431
|
+
resolve(base64);
|
|
5432
|
+
};
|
|
5433
|
+
reader.readAsDataURL(blob);
|
|
5434
|
+
});
|
|
5435
|
+
}
|
|
5436
|
+
var VoiceRuntime = class {
|
|
5437
|
+
debugLogging;
|
|
5438
|
+
triggerSystemEvent;
|
|
5439
|
+
activePlayers = [];
|
|
5440
|
+
mediaStream = null;
|
|
5441
|
+
mediaRecorder = null;
|
|
5442
|
+
recordingChunks = [];
|
|
5443
|
+
recordingTimeoutId = null;
|
|
5444
|
+
listening = false;
|
|
5445
|
+
listenContext = null;
|
|
5446
|
+
listenAnalyser = null;
|
|
5447
|
+
listenSource = null;
|
|
5448
|
+
listenFrameId = null;
|
|
5449
|
+
lastSpeechAt = 0;
|
|
5450
|
+
listeningStartedAt = 0;
|
|
5451
|
+
recordingStartedFromListening = false;
|
|
5452
|
+
speechEventTriggered = false;
|
|
5453
|
+
visibilityHandler;
|
|
5454
|
+
pageHideHandler;
|
|
5455
|
+
constructor(options) {
|
|
5456
|
+
this.debugLogging = options.debugLogging;
|
|
5457
|
+
this.triggerSystemEvent = options.triggerSystemEvent;
|
|
5458
|
+
this.visibilityHandler = () => {
|
|
5459
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
5460
|
+
void this.handlePageDeactivation();
|
|
5461
|
+
}
|
|
5462
|
+
};
|
|
5463
|
+
this.pageHideHandler = () => {
|
|
5464
|
+
void this.handlePageDeactivation();
|
|
5465
|
+
};
|
|
5466
|
+
if (typeof document !== "undefined") {
|
|
5467
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
5468
|
+
}
|
|
5469
|
+
if (typeof window !== "undefined") {
|
|
5470
|
+
window.addEventListener("pagehide", this.pageHideHandler);
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
async play(value) {
|
|
5474
|
+
const payload = this.normalizePlayablePayload(value);
|
|
5475
|
+
if (!payload) {
|
|
5476
|
+
return;
|
|
5477
|
+
}
|
|
5478
|
+
const bytes = decodeBase64(payload.audioData);
|
|
5479
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
5480
|
+
copy.set(bytes);
|
|
5481
|
+
const blob = new Blob([copy.buffer], { type: payload.mimeType });
|
|
5482
|
+
const url = URL.createObjectURL(blob);
|
|
5483
|
+
const audio = new Audio(url);
|
|
5484
|
+
audio.preload = "auto";
|
|
5485
|
+
const player = { audio, url, payload };
|
|
5486
|
+
audio.onended = () => {
|
|
5487
|
+
void this.finishPlayback(player, "finished");
|
|
5488
|
+
};
|
|
5489
|
+
audio.onerror = () => {
|
|
5490
|
+
this.detachPlayer(player);
|
|
5491
|
+
logRuntimeError("voice.play", new Error("Audio playback failed"), { mimeType: payload.mimeType });
|
|
5492
|
+
};
|
|
5493
|
+
this.activePlayers.push(player);
|
|
5494
|
+
while (this.activePlayers.length > MAX_CONCURRENT_PLAYERS) {
|
|
5495
|
+
const oldest = this.activePlayers.shift();
|
|
5496
|
+
if (!oldest) {
|
|
5497
|
+
break;
|
|
5498
|
+
}
|
|
5499
|
+
oldest.audio.pause();
|
|
5500
|
+
void this.finishPlayback(oldest, "stopped");
|
|
5501
|
+
}
|
|
5502
|
+
logRuntimeDebug(this.debugLogging, "voice-play", {
|
|
5503
|
+
mimeType: payload.mimeType,
|
|
5504
|
+
bytes: bytes.byteLength,
|
|
5505
|
+
activePlayers: this.activePlayers.length
|
|
5506
|
+
});
|
|
5507
|
+
await audio.play();
|
|
5508
|
+
}
|
|
5509
|
+
async stopPlaying() {
|
|
5510
|
+
const players = [...this.activePlayers];
|
|
5511
|
+
for (const player of players) {
|
|
5512
|
+
player.audio.pause();
|
|
5513
|
+
player.audio.currentTime = 0;
|
|
5514
|
+
await this.finishPlayback(player, "stopped");
|
|
5515
|
+
}
|
|
5516
|
+
}
|
|
5517
|
+
async startRecording() {
|
|
5518
|
+
try {
|
|
5519
|
+
const stream = await this.ensureInputStream();
|
|
5520
|
+
this.startRecordingInternal(stream, false);
|
|
5521
|
+
} catch (error) {
|
|
5522
|
+
await this.handleRecordingError(error);
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
stopRecording() {
|
|
5526
|
+
if (!this.mediaRecorder || this.mediaRecorder.state === "inactive") {
|
|
5527
|
+
return;
|
|
5528
|
+
}
|
|
5529
|
+
this.clearRecordingTimeout();
|
|
5530
|
+
this.mediaRecorder.stop();
|
|
5531
|
+
}
|
|
5532
|
+
async startListening() {
|
|
5533
|
+
if (this.listening) {
|
|
5534
|
+
return;
|
|
5535
|
+
}
|
|
5536
|
+
try {
|
|
5537
|
+
const stream = await this.ensureInputStream();
|
|
5538
|
+
const context = new AudioContext();
|
|
5539
|
+
const analyser = context.createAnalyser();
|
|
5540
|
+
analyser.fftSize = 2048;
|
|
5541
|
+
const source = context.createMediaStreamSource(stream);
|
|
5542
|
+
source.connect(analyser);
|
|
5543
|
+
this.listening = true;
|
|
5544
|
+
this.listenContext = context;
|
|
5545
|
+
this.listenAnalyser = analyser;
|
|
5546
|
+
this.listenSource = source;
|
|
5547
|
+
this.lastSpeechAt = 0;
|
|
5548
|
+
this.listeningStartedAt = performance.now();
|
|
5549
|
+
this.speechEventTriggered = false;
|
|
5550
|
+
const sampleBuffer = new Uint8Array(analyser.fftSize);
|
|
5551
|
+
const step = async () => {
|
|
5552
|
+
if (!this.listening || !this.listenAnalyser) {
|
|
5553
|
+
return;
|
|
5554
|
+
}
|
|
5555
|
+
this.listenAnalyser.getByteTimeDomainData(sampleBuffer);
|
|
5556
|
+
let squareSum = 0;
|
|
5557
|
+
for (const sample of sampleBuffer) {
|
|
5558
|
+
const centered = (sample - 128) / 128;
|
|
5559
|
+
squareSum += centered * centered;
|
|
5560
|
+
}
|
|
5561
|
+
const rms = Math.sqrt(squareSum / sampleBuffer.length);
|
|
5562
|
+
const now = performance.now();
|
|
5563
|
+
if (rms >= SPEECH_THRESHOLD) {
|
|
5564
|
+
this.lastSpeechAt = now;
|
|
5565
|
+
this.listeningStartedAt = now;
|
|
5566
|
+
if (!this.speechEventTriggered) {
|
|
5567
|
+
this.speechEventTriggered = true;
|
|
5568
|
+
await this.triggerSystemEvent("onSpeechDetected", null);
|
|
5569
|
+
}
|
|
5570
|
+
if (!this.mediaRecorder || this.mediaRecorder.state === "inactive") {
|
|
5571
|
+
this.startRecordingInternal(stream, true);
|
|
5572
|
+
}
|
|
5573
|
+
} else if (this.recordingStartedFromListening && this.mediaRecorder && this.mediaRecorder.state !== "inactive" && this.lastSpeechAt > 0 && now - this.lastSpeechAt >= SILENCE_STOP_MS) {
|
|
5574
|
+
this.stopRecording();
|
|
5575
|
+
this.lastSpeechAt = 0;
|
|
5576
|
+
this.speechEventTriggered = false;
|
|
5577
|
+
} else if (!this.recordingStartedFromListening && now - this.listeningStartedAt >= MAX_LISTENING_SILENCE_MS) {
|
|
5578
|
+
await this.stopListening();
|
|
5579
|
+
return;
|
|
5580
|
+
}
|
|
5581
|
+
this.listenFrameId = window.requestAnimationFrame(() => {
|
|
5582
|
+
void step();
|
|
5583
|
+
});
|
|
5584
|
+
};
|
|
5585
|
+
void step();
|
|
5586
|
+
} catch (error) {
|
|
5587
|
+
await this.handleListeningError(error);
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
async stopListening() {
|
|
5591
|
+
this.listening = false;
|
|
5592
|
+
this.speechEventTriggered = false;
|
|
5593
|
+
this.lastSpeechAt = 0;
|
|
5594
|
+
this.listeningStartedAt = 0;
|
|
5595
|
+
if (this.listenFrameId !== null) {
|
|
5596
|
+
window.cancelAnimationFrame(this.listenFrameId);
|
|
5597
|
+
this.listenFrameId = null;
|
|
5598
|
+
}
|
|
5599
|
+
try {
|
|
5600
|
+
await this.listenContext?.close();
|
|
5601
|
+
} catch (error) {
|
|
5602
|
+
logRuntimeError("voice.stopListening", error);
|
|
5603
|
+
}
|
|
5604
|
+
this.listenContext = null;
|
|
5605
|
+
this.listenAnalyser = null;
|
|
5606
|
+
this.listenSource = null;
|
|
5607
|
+
if (this.recordingStartedFromListening && this.mediaRecorder && this.mediaRecorder.state !== "inactive") {
|
|
5608
|
+
this.stopRecording();
|
|
5609
|
+
}
|
|
5610
|
+
this.recordingStartedFromListening = false;
|
|
5611
|
+
this.releaseInputStreamIfIdle();
|
|
5612
|
+
}
|
|
5613
|
+
dispose() {
|
|
5614
|
+
void this.stopListening();
|
|
5615
|
+
this.stopRecording();
|
|
5616
|
+
this.clearRecordingTimeout();
|
|
5617
|
+
void this.stopPlaying();
|
|
5618
|
+
if (typeof document !== "undefined") {
|
|
5619
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
5620
|
+
}
|
|
5621
|
+
if (typeof window !== "undefined") {
|
|
5622
|
+
window.removeEventListener("pagehide", this.pageHideHandler);
|
|
5623
|
+
}
|
|
5624
|
+
this.stopStreamTracks();
|
|
5625
|
+
}
|
|
5626
|
+
startRecordingInternal(stream, fromListening) {
|
|
5627
|
+
if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") {
|
|
5628
|
+
return;
|
|
5629
|
+
}
|
|
5630
|
+
if (typeof MediaRecorder === "undefined") {
|
|
5631
|
+
throw new Error("MediaRecorder is not supported in this browser");
|
|
5632
|
+
}
|
|
5633
|
+
const preferredMimeType = this.getPreferredRecordingMimeType();
|
|
5634
|
+
const options = preferredMimeType ? { mimeType: preferredMimeType } : void 0;
|
|
5635
|
+
const recorder = options ? new MediaRecorder(stream, options) : new MediaRecorder(stream);
|
|
5636
|
+
this.mediaRecorder = recorder;
|
|
5637
|
+
this.recordingChunks = [];
|
|
5638
|
+
this.recordingStartedFromListening = fromListening;
|
|
5639
|
+
recorder.ondataavailable = (event) => {
|
|
5640
|
+
if (event.data && event.data.size > 0) {
|
|
5641
|
+
this.recordingChunks.push(event.data);
|
|
5642
|
+
}
|
|
5643
|
+
};
|
|
5644
|
+
recorder.onerror = (event) => {
|
|
5645
|
+
void this.handleRecordingError(event.error ?? new Error("Unknown recording error"));
|
|
5646
|
+
};
|
|
5647
|
+
recorder.onstart = () => {
|
|
5648
|
+
logRuntimeDebug(this.debugLogging, "voice-recording-started", {
|
|
5649
|
+
mimeType: recorder.mimeType || preferredMimeType || "unknown",
|
|
5650
|
+
fromListening
|
|
5651
|
+
});
|
|
5652
|
+
void this.triggerSystemEvent("onRecordingStarted", null);
|
|
5653
|
+
};
|
|
5654
|
+
recorder.onstop = () => {
|
|
5655
|
+
void this.handleRecorderStop(recorder);
|
|
5656
|
+
};
|
|
5657
|
+
recorder.start();
|
|
5658
|
+
this.clearRecordingTimeout();
|
|
5659
|
+
this.recordingTimeoutId = window.setTimeout(() => {
|
|
5660
|
+
void this.stopRecording();
|
|
5661
|
+
}, MAX_RECORDING_MS);
|
|
5662
|
+
}
|
|
5663
|
+
async handleRecorderStop(recorder) {
|
|
5664
|
+
this.clearRecordingTimeout();
|
|
5665
|
+
const mimeType = recorder.mimeType || this.getPreferredRecordingMimeType() || "audio/webm";
|
|
5666
|
+
const blob = new Blob(this.recordingChunks, { type: mimeType });
|
|
5667
|
+
this.recordingChunks = [];
|
|
5668
|
+
this.mediaRecorder = null;
|
|
5669
|
+
try {
|
|
5670
|
+
const audioData = await blobToBase64(blob);
|
|
5671
|
+
logRuntimeDebug(this.debugLogging, "voice-recording-stopped", {
|
|
5672
|
+
mimeType,
|
|
5673
|
+
size: blob.size,
|
|
5674
|
+
fromListening: this.recordingStartedFromListening
|
|
5675
|
+
});
|
|
5676
|
+
await this.triggerSystemEvent("onRecordingStopped", {
|
|
5677
|
+
audioData,
|
|
5678
|
+
mimeType
|
|
5679
|
+
});
|
|
5680
|
+
} catch (error) {
|
|
5681
|
+
await this.handleRecordingError(error);
|
|
5682
|
+
} finally {
|
|
5683
|
+
this.recordingStartedFromListening = false;
|
|
5684
|
+
this.releaseInputStreamIfIdle();
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
async handleRecordingError(error) {
|
|
5688
|
+
logRuntimeError("voice.recording", error);
|
|
5689
|
+
await this.triggerSystemEvent("onRecordingError", this.normalizeErrorMessage(error));
|
|
5690
|
+
}
|
|
5691
|
+
async handleListeningError(error) {
|
|
5692
|
+
logRuntimeError("voice.listening", error);
|
|
5693
|
+
const message = this.normalizeErrorMessage(error);
|
|
5694
|
+
await this.triggerSystemEvent("onListeningError", message);
|
|
5695
|
+
await this.triggerSystemEvent("onListeringError", message);
|
|
5696
|
+
}
|
|
5697
|
+
normalizePlayablePayload(value) {
|
|
5698
|
+
if (typeof value === "string" && value.trim()) {
|
|
5699
|
+
const bytes = decodeBase64(value.trim());
|
|
5700
|
+
return {
|
|
5701
|
+
audioData: value.trim(),
|
|
5702
|
+
mimeType: detectMimeType(bytes)
|
|
5703
|
+
};
|
|
5704
|
+
}
|
|
5705
|
+
if (isPlayableAudioPayload(value) && typeof value.audioData === "string" && value.audioData.trim()) {
|
|
5706
|
+
const audioData = value.audioData.trim();
|
|
5707
|
+
const mimeType = typeof value.mimeType === "string" && value.mimeType.trim() ? value.mimeType.trim() : detectMimeType(decodeBase64(audioData));
|
|
5708
|
+
return { audioData, mimeType };
|
|
5709
|
+
}
|
|
5710
|
+
return null;
|
|
5711
|
+
}
|
|
5712
|
+
getPreferredRecordingMimeType() {
|
|
5713
|
+
if (typeof MediaRecorder === "undefined" || typeof MediaRecorder.isTypeSupported !== "function") {
|
|
5714
|
+
return null;
|
|
5715
|
+
}
|
|
5716
|
+
for (const candidate of RECORDING_MIME_CANDIDATES) {
|
|
5717
|
+
if (MediaRecorder.isTypeSupported(candidate)) {
|
|
5718
|
+
return candidate;
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
return null;
|
|
5722
|
+
}
|
|
5723
|
+
async ensureInputStream() {
|
|
5724
|
+
if (this.mediaStream && this.mediaStream.active) {
|
|
5725
|
+
return this.mediaStream;
|
|
5726
|
+
}
|
|
5727
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
5728
|
+
throw new Error("Audio input is not supported in this browser");
|
|
5729
|
+
}
|
|
5730
|
+
this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
5731
|
+
return this.mediaStream;
|
|
5732
|
+
}
|
|
5733
|
+
clearRecordingTimeout() {
|
|
5734
|
+
if (this.recordingTimeoutId !== null) {
|
|
5735
|
+
window.clearTimeout(this.recordingTimeoutId);
|
|
5736
|
+
this.recordingTimeoutId = null;
|
|
5737
|
+
}
|
|
5738
|
+
}
|
|
5739
|
+
releaseInputStreamIfIdle() {
|
|
5740
|
+
if (this.listening) {
|
|
5741
|
+
return;
|
|
5742
|
+
}
|
|
5743
|
+
if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") {
|
|
5744
|
+
return;
|
|
5745
|
+
}
|
|
5746
|
+
this.stopStreamTracks();
|
|
5747
|
+
}
|
|
5748
|
+
stopStreamTracks() {
|
|
5749
|
+
if (!this.mediaStream) {
|
|
5750
|
+
return;
|
|
5751
|
+
}
|
|
5752
|
+
for (const track of this.mediaStream.getTracks()) {
|
|
5753
|
+
track.stop();
|
|
5754
|
+
}
|
|
5755
|
+
this.mediaStream = null;
|
|
5756
|
+
}
|
|
5757
|
+
normalizeErrorMessage(error) {
|
|
5758
|
+
if (error instanceof Error) {
|
|
5759
|
+
return error.message;
|
|
5760
|
+
}
|
|
5761
|
+
return String(error);
|
|
5762
|
+
}
|
|
5763
|
+
detachPlayer(player) {
|
|
5764
|
+
const index = this.activePlayers.indexOf(player);
|
|
5765
|
+
if (index >= 0) {
|
|
5766
|
+
this.activePlayers.splice(index, 1);
|
|
5767
|
+
}
|
|
5768
|
+
player.audio.onended = null;
|
|
5769
|
+
player.audio.onerror = null;
|
|
5770
|
+
URL.revokeObjectURL(player.url);
|
|
5771
|
+
}
|
|
5772
|
+
async finishPlayback(player, reason) {
|
|
5773
|
+
if (!this.activePlayers.includes(player)) {
|
|
5774
|
+
return;
|
|
5775
|
+
}
|
|
5776
|
+
const payload = {
|
|
5777
|
+
audioData: player.payload.audioData,
|
|
5778
|
+
mimeType: player.payload.mimeType
|
|
5779
|
+
};
|
|
5780
|
+
this.detachPlayer(player);
|
|
5781
|
+
await this.triggerSystemEvent(reason === "finished" ? "onPlayFinished" : "onPlayingStopped", payload);
|
|
5782
|
+
}
|
|
5783
|
+
async handlePageDeactivation() {
|
|
5784
|
+
const hadActiveRecording = Boolean(this.mediaRecorder && this.mediaRecorder.state !== "inactive");
|
|
5785
|
+
const hadActiveListening = this.listening;
|
|
5786
|
+
if (!hadActiveRecording && !hadActiveListening) {
|
|
5787
|
+
return;
|
|
5788
|
+
}
|
|
5789
|
+
if (hadActiveRecording) {
|
|
5790
|
+
await this.handleRecordingError(new Error("Recording stopped because the browser tab became inactive"));
|
|
5791
|
+
this.stopRecording();
|
|
5792
|
+
}
|
|
5793
|
+
if (hadActiveListening) {
|
|
5794
|
+
await this.stopListening();
|
|
5795
|
+
if (!hadActiveRecording) {
|
|
5796
|
+
await this.handleListeningError(new Error("Listening stopped because the browser tab became inactive"));
|
|
5797
|
+
}
|
|
5798
|
+
}
|
|
5799
|
+
}
|
|
5800
|
+
};
|
|
5801
|
+
|
|
5132
5802
|
// src/lib/widgets/adaptive-layout.ts
|
|
5133
5803
|
var renderAdaptiveLayout = (env, node, _state, key, path, itemContext) => {
|
|
5134
5804
|
const mobile = env.isMobileViewport();
|
|
@@ -5587,7 +6257,7 @@ var renderOverlayContainer = (env, node, _state, key, path, itemContext) => {
|
|
|
5587
6257
|
var renderTabs = (env, node, state, key, path, itemContext) => {
|
|
5588
6258
|
const tabs = node.tabs ?? [];
|
|
5589
6259
|
const enabled = state.enabled ?? env.normalizeBoolean(node.enabled, true);
|
|
5590
|
-
const rawActiveTab = typeof
|
|
6260
|
+
const rawActiveTab = typeof state.activeTab === "number" ? state.activeTab : typeof node.activeTab === "number" ? node.activeTab : 0;
|
|
5591
6261
|
const activeTab = Math.max(0, Math.min(rawActiveTab, Math.max(tabs.length - 1, 0)));
|
|
5592
6262
|
const headers = tabs.map((tab, index) => `<button class="vjt-tab-button${index === activeTab ? " is-active" : ""}" type="button" role="tab" aria-selected="${index === activeTab ? "true" : "false"}" data-widget="tabs" data-widget-key="${env.escapeHtml(key)}" data-tab-index="${index}"${enabled ? "" : " disabled"}>${env.escapeHtml(env.resolveI18nValue(tab.name))}</button>`).join("");
|
|
5593
6263
|
const content = tabs[activeTab]?.content ? env.renderNode(tabs[activeTab].content, `${path}.tabs.${activeTab}`, itemContext) : "";
|
|
@@ -5945,8 +6615,6 @@ function bindDelegatedUi(root, env) {
|
|
|
5945
6615
|
const state = env.stateByKey.get(key);
|
|
5946
6616
|
if (state) {
|
|
5947
6617
|
state.activeTab = Number.parseInt(tabButton.dataset.tabIndex ?? "0", 10) || 0;
|
|
5948
|
-
const tabsNode = node;
|
|
5949
|
-
tabsNode.activeTab = state.activeTab;
|
|
5950
6618
|
}
|
|
5951
6619
|
await env.rerenderRoot();
|
|
5952
6620
|
return;
|
|
@@ -6183,12 +6851,14 @@ var RuntimeRenderer = class {
|
|
|
6183
6851
|
stateByKey = /* @__PURE__ */ new Map();
|
|
6184
6852
|
nodeById = /* @__PURE__ */ new Map();
|
|
6185
6853
|
stateById = /* @__PURE__ */ new Map();
|
|
6854
|
+
vars = /* @__PURE__ */ new Map();
|
|
6186
6855
|
resizeHandler = null;
|
|
6187
6856
|
pointerHandler = null;
|
|
6188
6857
|
eventSources = [];
|
|
6189
6858
|
referenceRuntime;
|
|
6190
6859
|
networkRuntime;
|
|
6191
6860
|
actionRuntime;
|
|
6861
|
+
voiceRuntime;
|
|
6192
6862
|
splitterDragState = null;
|
|
6193
6863
|
hasTriggeredInitialRefresh = false;
|
|
6194
6864
|
activeModalId = null;
|
|
@@ -6255,6 +6925,10 @@ var RuntimeRenderer = class {
|
|
|
6255
6925
|
break;
|
|
6256
6926
|
}
|
|
6257
6927
|
},
|
|
6928
|
+
getVarValue: (name) => this.vars.get(name),
|
|
6929
|
+
setVarValue: (name, value) => {
|
|
6930
|
+
this.vars.set(name, value);
|
|
6931
|
+
},
|
|
6258
6932
|
ensureWidgetState: (node, key) => this.ensureWidgetState(node, key),
|
|
6259
6933
|
resolveItemNodeKey: (node, listKey, index, fallbackPath) => this.resolveItemNodeKey(node, listKey, index, fallbackPath),
|
|
6260
6934
|
indexListElementNodes: (listNode, element, index) => this.indexListElementNodes(listNode, element, index)
|
|
@@ -6268,12 +6942,17 @@ var RuntimeRenderer = class {
|
|
|
6268
6942
|
runActions: (actions, inputValue, context) => this.actionRuntime.runActions(actions, inputValue, context),
|
|
6269
6943
|
rerenderRoot: () => this.rerenderRoot()
|
|
6270
6944
|
});
|
|
6945
|
+
this.voiceRuntime = new VoiceRuntime({
|
|
6946
|
+
debugLogging: this.debugLogging,
|
|
6947
|
+
triggerSystemEvent: (eventName, inputValue) => this.triggerRuntimeSystemEvent(eventName, inputValue)
|
|
6948
|
+
});
|
|
6271
6949
|
this.actionRuntime = new ActionRuntime({
|
|
6272
6950
|
debugLogging: this.debugLogging,
|
|
6273
6951
|
actionsMap: this.actionsMap,
|
|
6274
6952
|
actionFunctions: this.actionFunctions,
|
|
6275
6953
|
nodeById: this.nodeById,
|
|
6276
6954
|
stateById: this.stateById,
|
|
6955
|
+
stateByKey: this.stateByKey,
|
|
6277
6956
|
rerenderRoot: () => this.rerenderRoot(),
|
|
6278
6957
|
dispatchWidgetEvent: (node, eventName, key, inputValue, pointer) => this.actionRuntime.dispatchWidgetEvent(node, eventName, key, inputValue, pointer),
|
|
6279
6958
|
executeRequest: (requestName, currentValue) => this.networkRuntime.executeRequest(requestName, currentValue),
|
|
@@ -6283,6 +6962,14 @@ var RuntimeRenderer = class {
|
|
|
6283
6962
|
resolveMappedValue: (template, currentValue, responseValue) => this.referenceRuntime.resolveMappedValue(template, currentValue, responseValue),
|
|
6284
6963
|
setWidgetEnabled: (widgetId, enabled) => this.setWidgetEnabled(widgetId, enabled),
|
|
6285
6964
|
clearWidget: (widgetId) => this.clearWidget(widgetId),
|
|
6965
|
+
clearListElementState: (listKey) => this.clearListElementState(listKey),
|
|
6966
|
+
focusWidget: (reference) => this.focusWidget(reference),
|
|
6967
|
+
playAudio: (value) => this.voiceRuntime.play(value),
|
|
6968
|
+
stopPlaying: () => this.voiceRuntime.stopPlaying(),
|
|
6969
|
+
startRecording: () => this.voiceRuntime.startRecording(),
|
|
6970
|
+
stopRecording: () => Promise.resolve(this.voiceRuntime.stopRecording()),
|
|
6971
|
+
startListening: () => this.voiceRuntime.startListening(),
|
|
6972
|
+
stopListening: () => this.voiceRuntime.stopListening(),
|
|
6286
6973
|
setUiReference: (widgetId, resourceRef) => {
|
|
6287
6974
|
const node = this.nodeById.get(widgetId);
|
|
6288
6975
|
if (!node || node.widget !== "ui-reference") {
|
|
@@ -6376,6 +7063,7 @@ var RuntimeRenderer = class {
|
|
|
6376
7063
|
source.close();
|
|
6377
7064
|
}
|
|
6378
7065
|
this.eventSources.length = 0;
|
|
7066
|
+
this.voiceRuntime.dispose();
|
|
6379
7067
|
};
|
|
6380
7068
|
}
|
|
6381
7069
|
rerenderRoot() {
|
|
@@ -6383,7 +7071,7 @@ var RuntimeRenderer = class {
|
|
|
6383
7071
|
return Promise.resolve();
|
|
6384
7072
|
}
|
|
6385
7073
|
this.reindexStaticTree();
|
|
6386
|
-
const preservedState = captureElementState(this.root, this.pendingResetModalIds
|
|
7074
|
+
const preservedState = captureElementState(this.root, this.pendingResetModalIds);
|
|
6387
7075
|
this.root.innerHTML = this.renderMarkup();
|
|
6388
7076
|
this.root.classList.toggle("vjt-root--mobile", isMobileViewport());
|
|
6389
7077
|
this.root.classList.toggle("vjt-root--desktop", !isMobileViewport());
|
|
@@ -6392,7 +7080,7 @@ var RuntimeRenderer = class {
|
|
|
6392
7080
|
}
|
|
6393
7081
|
this.attachInputBehavior(this.root);
|
|
6394
7082
|
this.attachWidgetEvents(this.root);
|
|
6395
|
-
restoreElementState(this.root, preservedState);
|
|
7083
|
+
restoreElementState(this.root, preservedState, this.stateByKey);
|
|
6396
7084
|
this.activeContextMenu = adjustContextMenuPosition(this.root, this.activeContextMenu);
|
|
6397
7085
|
this.pendingResetModalIds.clear();
|
|
6398
7086
|
if (typeof this.onRuntimeSnapshot === "function") {
|
|
@@ -6407,6 +7095,17 @@ var RuntimeRenderer = class {
|
|
|
6407
7095
|
}
|
|
6408
7096
|
await this.actionRuntime.runActions(actions, null, { currentValue: null });
|
|
6409
7097
|
}
|
|
7098
|
+
async triggerRuntimeSystemEvent(eventName, inputValue) {
|
|
7099
|
+
const actions = this.systemEvents[eventName];
|
|
7100
|
+
if (!actions?.length) {
|
|
7101
|
+
return;
|
|
7102
|
+
}
|
|
7103
|
+
await this.actionRuntime.runActions(actions, inputValue, {
|
|
7104
|
+
currentValue: inputValue,
|
|
7105
|
+
responseValue: inputValue
|
|
7106
|
+
});
|
|
7107
|
+
await this.rerenderRoot();
|
|
7108
|
+
}
|
|
6410
7109
|
async navigateTo(url) {
|
|
6411
7110
|
if (typeof window === "undefined" || !url.trim()) {
|
|
6412
7111
|
return;
|
|
@@ -6461,6 +7160,12 @@ var RuntimeRenderer = class {
|
|
|
6461
7160
|
this.stateById.set(restoredState.id, restoredState);
|
|
6462
7161
|
}
|
|
6463
7162
|
}
|
|
7163
|
+
this.vars.clear();
|
|
7164
|
+
for (const [name, value] of snapshot.vars ?? []) {
|
|
7165
|
+
if (typeof name === "string" && name.length > 0) {
|
|
7166
|
+
this.vars.set(name, deepClone(value));
|
|
7167
|
+
}
|
|
7168
|
+
}
|
|
6464
7169
|
if (snapshot.activeModalId && this.nodeById.get(snapshot.activeModalId)?.widget === "modal-window") {
|
|
6465
7170
|
this.activeModalId = snapshot.activeModalId;
|
|
6466
7171
|
}
|
|
@@ -6471,6 +7176,7 @@ var RuntimeRenderer = class {
|
|
|
6471
7176
|
createRuntimeSnapshot() {
|
|
6472
7177
|
return {
|
|
6473
7178
|
stateByKey: Array.from(this.stateByKey.entries(), ([key, state]) => [key, deepClone(state)]),
|
|
7179
|
+
vars: Array.from(this.vars.entries(), ([name, value]) => [name, deepClone(value)]),
|
|
6474
7180
|
activeModalId: this.activeModalId,
|
|
6475
7181
|
activeContextMenu: this.activeContextMenu ? deepClone(this.activeContextMenu) : null
|
|
6476
7182
|
};
|
|
@@ -7414,6 +8120,28 @@ var RuntimeRenderer = class {
|
|
|
7414
8120
|
clearListElementState(listKey) {
|
|
7415
8121
|
this.referenceRuntime.clearListElementState(listKey);
|
|
7416
8122
|
}
|
|
8123
|
+
focusWidget(reference) {
|
|
8124
|
+
if (!(this.root instanceof HTMLElement)) {
|
|
8125
|
+
return;
|
|
8126
|
+
}
|
|
8127
|
+
const widgetId = reference.split(".")[0]?.trim();
|
|
8128
|
+
if (!widgetId) {
|
|
8129
|
+
return;
|
|
8130
|
+
}
|
|
8131
|
+
const directTarget = this.root.querySelector(`#${CSS.escape(widgetId)}`);
|
|
8132
|
+
if (directTarget && typeof directTarget.focus === "function") {
|
|
8133
|
+
directTarget.focus({ preventScroll: true });
|
|
8134
|
+
return;
|
|
8135
|
+
}
|
|
8136
|
+
const widgetRoot = this.root.querySelector(`[data-widget-id="${CSS.escape(widgetId)}"]`);
|
|
8137
|
+
if (!widgetRoot) {
|
|
8138
|
+
return;
|
|
8139
|
+
}
|
|
8140
|
+
const nestedTarget = widgetRoot.matches("input, textarea, select, button, a[href], [tabindex]") ? widgetRoot : widgetRoot.querySelector("input, textarea, select, button, a[href], [tabindex]");
|
|
8141
|
+
if (nestedTarget && typeof nestedTarget.focus === "function") {
|
|
8142
|
+
nestedTarget.focus({ preventScroll: true });
|
|
8143
|
+
}
|
|
8144
|
+
}
|
|
7417
8145
|
};
|
|
7418
8146
|
function renderJson(description, options = {}) {
|
|
7419
8147
|
const renderer = new RuntimeRenderer(description, options);
|
|
@@ -6,6 +6,8 @@ export type ActionExecutionContext = {
|
|
|
6
6
|
x: number;
|
|
7
7
|
y: number;
|
|
8
8
|
} | null;
|
|
9
|
+
currentList?: unknown[];
|
|
10
|
+
currentIndex?: number;
|
|
9
11
|
};
|
|
10
12
|
type ActionRuntimeOptions = {
|
|
11
13
|
debugLogging: boolean;
|
|
@@ -13,6 +15,7 @@ type ActionRuntimeOptions = {
|
|
|
13
15
|
actionFunctions: Record<string, () => unknown>;
|
|
14
16
|
nodeById: Map<string, DescriptionNode>;
|
|
15
17
|
stateById: Map<string, WidgetState>;
|
|
18
|
+
stateByKey: Map<string, WidgetState>;
|
|
16
19
|
rerenderRoot: () => Promise<void>;
|
|
17
20
|
dispatchWidgetEvent: (node: DescriptionNode, eventName: WidgetEventName, key: string, inputValue?: unknown, pointer?: {
|
|
18
21
|
x: number;
|
|
@@ -27,6 +30,14 @@ type ActionRuntimeOptions = {
|
|
|
27
30
|
resolveMappedValue: (template: unknown, currentValue: unknown, responseValue: unknown) => unknown;
|
|
28
31
|
setWidgetEnabled: (widgetId: string, enabled: boolean) => void;
|
|
29
32
|
clearWidget: (widgetId: string) => void;
|
|
33
|
+
clearListElementState: (listKey: string) => void;
|
|
34
|
+
focusWidget: (reference: string) => void;
|
|
35
|
+
playAudio: (value: unknown) => Promise<void>;
|
|
36
|
+
stopPlaying: () => Promise<void>;
|
|
37
|
+
startRecording: () => Promise<void>;
|
|
38
|
+
stopRecording: () => Promise<void>;
|
|
39
|
+
startListening: () => Promise<void>;
|
|
40
|
+
stopListening: () => Promise<void>;
|
|
30
41
|
setUiReference: (widgetId: string, resourceRef: string) => void;
|
|
31
42
|
refreshWidgetTree: (widgetId: string) => Promise<void>;
|
|
32
43
|
renderWidgetTree: (widgetId: string) => Promise<void>;
|
|
@@ -54,6 +65,7 @@ export declare class ActionRuntime {
|
|
|
54
65
|
private readonly actionFunctions;
|
|
55
66
|
private readonly nodeById;
|
|
56
67
|
private readonly stateById;
|
|
68
|
+
private readonly stateByKey;
|
|
57
69
|
private readonly rerenderRoot;
|
|
58
70
|
private readonly dispatchWidgetEventImpl;
|
|
59
71
|
private readonly executeRequest;
|
|
@@ -63,6 +75,14 @@ export declare class ActionRuntime {
|
|
|
63
75
|
private readonly resolveMappedValue;
|
|
64
76
|
private readonly setWidgetEnabled;
|
|
65
77
|
private readonly clearWidget;
|
|
78
|
+
private readonly clearListElementState;
|
|
79
|
+
private readonly focusWidget;
|
|
80
|
+
private readonly playAudio;
|
|
81
|
+
private readonly stopPlaying;
|
|
82
|
+
private readonly startRecording;
|
|
83
|
+
private readonly stopRecording;
|
|
84
|
+
private readonly startListening;
|
|
85
|
+
private readonly stopListening;
|
|
66
86
|
private readonly setUiReference;
|
|
67
87
|
private readonly refreshWidgetTree;
|
|
68
88
|
private readonly renderWidgetTree;
|
|
@@ -82,5 +102,9 @@ export declare class ActionRuntime {
|
|
|
82
102
|
getInlineActionsForNode(node: DescriptionNode): ActionDefinition[] | undefined;
|
|
83
103
|
private runSingleAction;
|
|
84
104
|
private executeAction;
|
|
105
|
+
private unwrapListValue;
|
|
106
|
+
private getCurrentListState;
|
|
107
|
+
private cloneValue;
|
|
108
|
+
private stringifyValue;
|
|
85
109
|
}
|
|
86
110
|
export {};
|
package/dist/lib/dom-state.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ export type PreservedElementState = {
|
|
|
15
15
|
}>;
|
|
16
16
|
};
|
|
17
17
|
export declare function isElementInsidePendingResetModal(element: HTMLElement, pendingResetModalIds: Set<string>): boolean;
|
|
18
|
-
export declare function captureElementState(root: HTMLElement, pendingResetModalIds: Set<string
|
|
19
|
-
export declare function restoreElementState(root: HTMLElement, state: PreservedElementState): void;
|
|
18
|
+
export declare function captureElementState(root: HTMLElement, pendingResetModalIds: Set<string>): PreservedElementState;
|
|
19
|
+
export declare function restoreElementState(root: HTMLElement, state: PreservedElementState, stateByKey: Map<string, WidgetState>): void;
|
|
20
20
|
export declare function adjustContextMenuPosition(root: HTMLElement, activeContextMenu: {
|
|
21
21
|
id: string;
|
|
22
22
|
x: number;
|
package/dist/lib/network.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export declare class NetworkRuntime {
|
|
|
25
25
|
executeRequest(requestName: string, currentValue: unknown): Promise<unknown>;
|
|
26
26
|
buildSchemaValue(schema: RequestSchema, currentValue: unknown, responseValue: unknown): Promise<unknown>;
|
|
27
27
|
private stringifyPrimitive;
|
|
28
|
+
private createRequestError;
|
|
29
|
+
private toRequestErrorPayload;
|
|
28
30
|
coercePrimitiveSchemaValue(type: PrimitiveRequestType, value: unknown): unknown;
|
|
29
31
|
private handleSseEvent;
|
|
30
32
|
}
|
package/dist/lib/references.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ type ReferenceRuntimeHost = {
|
|
|
6
6
|
readonly nodeByKey: Map<string, DescriptionNode>;
|
|
7
7
|
getAppValue: (name: string) => unknown;
|
|
8
8
|
setAppValue: (name: string, value: unknown) => void;
|
|
9
|
+
getVarValue: (name: string) => unknown;
|
|
10
|
+
setVarValue: (name: string, value: unknown) => void;
|
|
9
11
|
ensureWidgetState: (node: DescriptionNode, key: string) => WidgetState;
|
|
10
12
|
resolveItemNodeKey: (node: DescriptionNode, listKey: string, index: number, fallbackPath: string) => string;
|
|
11
13
|
indexListElementNodes: (listNode: ListNode | GridViewNode, element: DescriptionNode, index: number) => void;
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type TextAlign = 'left' | 'center' | 'right';
|
|
|
4
4
|
export type VerticalAlign = 'top' | 'center' | 'bottom';
|
|
5
5
|
export type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
6
6
|
export type WidgetEventName = 'onClick' | 'onUserValueChange' | 'onRefresh' | 'onEnter' | 'onShiftEnter' | 'onControlEnter';
|
|
7
|
-
export type SystemEventName = 'onBeforeRender' | 'onAfterRender' | 'onBeforeNavigate' | 'onAfterNavigate';
|
|
7
|
+
export type SystemEventName = 'onBeforeRender' | 'onAfterRender' | 'onBeforeNavigate' | 'onAfterNavigate' | 'onSpeechDetected' | 'onRecordingStarted' | 'onRecordingStopped' | 'onRecordingError' | 'onListeningError' | 'onListeringError' | 'onPlayFinished' | 'onPlayingStopped';
|
|
8
8
|
export type PrimitiveRequestType = 'int' | 'float' | 'boolean' | 'string';
|
|
9
9
|
export type ActionDefinition = {
|
|
10
10
|
action: string;
|
|
@@ -34,6 +34,7 @@ export type RequestDefinition = {
|
|
|
34
34
|
request: RequestSchema;
|
|
35
35
|
response?: RequestSchema;
|
|
36
36
|
onResponse?: ActionDefinition[];
|
|
37
|
+
onError?: ActionDefinition[];
|
|
37
38
|
};
|
|
38
39
|
export type SseEventDefinition = {
|
|
39
40
|
name: string;
|
|
@@ -114,6 +115,7 @@ export type WidgetState = {
|
|
|
114
115
|
};
|
|
115
116
|
export type RuntimeSnapshot = {
|
|
116
117
|
stateByKey: Array<[string, WidgetState]>;
|
|
118
|
+
vars: Array<[string, unknown]>;
|
|
117
119
|
activeModalId: string | null;
|
|
118
120
|
activeContextMenu: {
|
|
119
121
|
id: string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SystemEventName } from './types.js';
|
|
2
|
+
type VoiceRuntimeOptions = {
|
|
3
|
+
debugLogging: boolean;
|
|
4
|
+
triggerSystemEvent: (eventName: SystemEventName, inputValue: unknown) => Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
export declare class VoiceRuntime {
|
|
7
|
+
private readonly debugLogging;
|
|
8
|
+
private readonly triggerSystemEvent;
|
|
9
|
+
private readonly activePlayers;
|
|
10
|
+
private mediaStream;
|
|
11
|
+
private mediaRecorder;
|
|
12
|
+
private recordingChunks;
|
|
13
|
+
private recordingTimeoutId;
|
|
14
|
+
private listening;
|
|
15
|
+
private listenContext;
|
|
16
|
+
private listenAnalyser;
|
|
17
|
+
private listenSource;
|
|
18
|
+
private listenFrameId;
|
|
19
|
+
private lastSpeechAt;
|
|
20
|
+
private listeningStartedAt;
|
|
21
|
+
private recordingStartedFromListening;
|
|
22
|
+
private speechEventTriggered;
|
|
23
|
+
private readonly visibilityHandler;
|
|
24
|
+
private readonly pageHideHandler;
|
|
25
|
+
constructor(options: VoiceRuntimeOptions);
|
|
26
|
+
play(value: unknown): Promise<void>;
|
|
27
|
+
stopPlaying(): Promise<void>;
|
|
28
|
+
startRecording(): Promise<void>;
|
|
29
|
+
stopRecording(): void;
|
|
30
|
+
startListening(): Promise<void>;
|
|
31
|
+
stopListening(): Promise<void>;
|
|
32
|
+
dispose(): void;
|
|
33
|
+
private startRecordingInternal;
|
|
34
|
+
private handleRecorderStop;
|
|
35
|
+
private handleRecordingError;
|
|
36
|
+
private handleListeningError;
|
|
37
|
+
private normalizePlayablePayload;
|
|
38
|
+
private getPreferredRecordingMimeType;
|
|
39
|
+
private ensureInputStream;
|
|
40
|
+
private clearRecordingTimeout;
|
|
41
|
+
private releaseInputStreamIfIdle;
|
|
42
|
+
private stopStreamTracks;
|
|
43
|
+
private normalizeErrorMessage;
|
|
44
|
+
private detachPlayer;
|
|
45
|
+
private finishPlayback;
|
|
46
|
+
private handlePageDeactivation;
|
|
47
|
+
}
|
|
48
|
+
export {};
|