@vortexm/vjt 0.1.3 → 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 +878 -85
- package/dist/lib/action-runtime.d.ts +26 -0
- package/dist/lib/dom-state.d.ts +2 -2
- package/dist/lib/logging.d.ts +1 -0
- package/dist/lib/network.d.ts +4 -0
- package/dist/lib/references.d.ts +2 -0
- package/dist/lib/types.d.ts +4 -1
- package/dist/lib/voice-runtime.d.ts +48 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3682,6 +3682,16 @@ function logRuntimeError(scope, error, details) {
|
|
|
3682
3682
|
}
|
|
3683
3683
|
console.error("[VJT error]", payload);
|
|
3684
3684
|
}
|
|
3685
|
+
function logRuntimeDebug(enabled, scope, payload) {
|
|
3686
|
+
if (!enabled) {
|
|
3687
|
+
return;
|
|
3688
|
+
}
|
|
3689
|
+
if (payload === void 0) {
|
|
3690
|
+
console.log("[VJT debug]", scope);
|
|
3691
|
+
return;
|
|
3692
|
+
}
|
|
3693
|
+
console.log("[VJT debug]", scope, payload);
|
|
3694
|
+
}
|
|
3685
3695
|
|
|
3686
3696
|
// src/lib/security.ts
|
|
3687
3697
|
var BLOCKED_OBJECT_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
@@ -3838,6 +3848,7 @@ function sanitizeSchemaValue(schema, value, path = "value") {
|
|
|
3838
3848
|
|
|
3839
3849
|
// src/lib/network.ts
|
|
3840
3850
|
var NetworkRuntime = class {
|
|
3851
|
+
debugLogging;
|
|
3841
3852
|
requestsMap;
|
|
3842
3853
|
sseConfigs;
|
|
3843
3854
|
backendUrl;
|
|
@@ -3845,6 +3856,7 @@ var NetworkRuntime = class {
|
|
|
3845
3856
|
runActions;
|
|
3846
3857
|
rerenderRoot;
|
|
3847
3858
|
constructor(options) {
|
|
3859
|
+
this.debugLogging = options.debugLogging;
|
|
3848
3860
|
this.requestsMap = options.requestsMap;
|
|
3849
3861
|
this.sseConfigs = options.sseConfigs;
|
|
3850
3862
|
this.backendUrl = options.backendUrl;
|
|
@@ -3892,55 +3904,111 @@ var NetworkRuntime = class {
|
|
|
3892
3904
|
if (!definition) {
|
|
3893
3905
|
return null;
|
|
3894
3906
|
}
|
|
3895
|
-
const payload = await this.buildSchemaValue(definition.request, currentValue, void 0);
|
|
3896
|
-
const requestEnvelope = {
|
|
3897
|
-
jsonrpc: "2.0",
|
|
3898
|
-
id: Date.now(),
|
|
3899
|
-
method: requestName,
|
|
3900
|
-
params: payload
|
|
3901
|
-
};
|
|
3902
|
-
const requestUrl = definition.url ?? this.backendUrl;
|
|
3903
|
-
if (!requestUrl) {
|
|
3904
|
-
throw new Error(`Request ${requestName} has no url in config`);
|
|
3905
|
-
}
|
|
3906
|
-
const safeRequestUrl = sanitizeUrl(requestUrl, { allowRelative: true });
|
|
3907
|
-
if (!safeRequestUrl) {
|
|
3908
|
-
throw new Error(`Request ${requestName} has unsafe url`);
|
|
3909
|
-
}
|
|
3910
|
-
const response = await fetch(safeRequestUrl, {
|
|
3911
|
-
method: "POST",
|
|
3912
|
-
headers: {
|
|
3913
|
-
"Content-Type": "application/json"
|
|
3914
|
-
},
|
|
3915
|
-
body: JSON.stringify(requestEnvelope)
|
|
3916
|
-
});
|
|
3917
|
-
const responseText = await response.text();
|
|
3918
|
-
if (!response.ok) {
|
|
3919
|
-
throw new Error(`Request ${requestName} failed: ${response.status} ${response.statusText || responseText}`);
|
|
3920
|
-
}
|
|
3921
|
-
if (!responseText.trim()) {
|
|
3922
|
-
throw new Error(`Request ${requestName} returned empty response body`);
|
|
3923
|
-
}
|
|
3924
|
-
let json;
|
|
3925
3907
|
try {
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
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
|
|
3941
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
|
|
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;
|
|
3942
4011
|
}
|
|
3943
|
-
return result;
|
|
3944
4012
|
}
|
|
3945
4013
|
async buildSchemaValue(schema, currentValue, responseValue) {
|
|
3946
4014
|
if (schema.type === "object") {
|
|
@@ -3978,6 +4046,36 @@ var NetworkRuntime = class {
|
|
|
3978
4046
|
}
|
|
3979
4047
|
return JSON.stringify(value);
|
|
3980
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
|
+
}
|
|
3981
4079
|
coercePrimitiveSchemaValue(type, value) {
|
|
3982
4080
|
switch (type) {
|
|
3983
4081
|
case "int": {
|
|
@@ -4011,6 +4109,12 @@ var NetworkRuntime = class {
|
|
|
4011
4109
|
const message = eventDefinition.message ? sanitizeSchemaValue(eventDefinition.message, parsedMessage, `sse.${eventDefinition.name}`) : (() => {
|
|
4012
4110
|
throw new Error(`SSE event ${eventDefinition.name} must define message schema`);
|
|
4013
4111
|
})();
|
|
4112
|
+
logRuntimeDebug(this.debugLogging, "sse", {
|
|
4113
|
+
eventName: eventDefinition.name,
|
|
4114
|
+
raw: event.data,
|
|
4115
|
+
parsedMessage,
|
|
4116
|
+
message
|
|
4117
|
+
});
|
|
4014
4118
|
if (!eventDefinition.onEvent?.length) {
|
|
4015
4119
|
return;
|
|
4016
4120
|
}
|
|
@@ -4154,6 +4258,9 @@ var ReferenceRuntime = class {
|
|
|
4154
4258
|
if (reference.startsWith("url.")) {
|
|
4155
4259
|
return getUrlParamValue(reference.slice(4));
|
|
4156
4260
|
}
|
|
4261
|
+
if (reference.startsWith("vars.")) {
|
|
4262
|
+
return this.host.getVarValue(reference.slice(5));
|
|
4263
|
+
}
|
|
4157
4264
|
const [widgetId, ...rest] = reference.split(".");
|
|
4158
4265
|
const state = this.host.stateById.get(widgetId);
|
|
4159
4266
|
if (!state) {
|
|
@@ -4279,6 +4386,10 @@ var ReferenceRuntime = class {
|
|
|
4279
4386
|
this.host.setAppValue(reference.slice(4), inputValue);
|
|
4280
4387
|
return;
|
|
4281
4388
|
}
|
|
4389
|
+
if (reference.startsWith("vars.")) {
|
|
4390
|
+
this.host.setVarValue(reference.slice(5), inputValue);
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4282
4393
|
const [widgetId, ...rest] = reference.split(".");
|
|
4283
4394
|
const field = rest.join(".");
|
|
4284
4395
|
const state = this.host.stateById.get(widgetId);
|
|
@@ -4452,10 +4563,12 @@ var ReferenceRuntime = class {
|
|
|
4452
4563
|
|
|
4453
4564
|
// src/lib/action-runtime.ts
|
|
4454
4565
|
var ActionRuntime = class {
|
|
4566
|
+
debugLogging;
|
|
4455
4567
|
actionsMap;
|
|
4456
4568
|
actionFunctions;
|
|
4457
4569
|
nodeById;
|
|
4458
4570
|
stateById;
|
|
4571
|
+
stateByKey;
|
|
4459
4572
|
rerenderRoot;
|
|
4460
4573
|
dispatchWidgetEventImpl;
|
|
4461
4574
|
executeRequest;
|
|
@@ -4465,6 +4578,14 @@ var ActionRuntime = class {
|
|
|
4465
4578
|
resolveMappedValue;
|
|
4466
4579
|
setWidgetEnabled;
|
|
4467
4580
|
clearWidget;
|
|
4581
|
+
clearListElementState;
|
|
4582
|
+
focusWidget;
|
|
4583
|
+
playAudio;
|
|
4584
|
+
stopPlaying;
|
|
4585
|
+
startRecording;
|
|
4586
|
+
stopRecording;
|
|
4587
|
+
startListening;
|
|
4588
|
+
stopListening;
|
|
4468
4589
|
setUiReference;
|
|
4469
4590
|
refreshWidgetTree;
|
|
4470
4591
|
renderWidgetTree;
|
|
@@ -4476,10 +4597,12 @@ var ActionRuntime = class {
|
|
|
4476
4597
|
getInlineActions;
|
|
4477
4598
|
isWidgetEnabled;
|
|
4478
4599
|
constructor(options) {
|
|
4600
|
+
this.debugLogging = options.debugLogging;
|
|
4479
4601
|
this.actionsMap = options.actionsMap;
|
|
4480
4602
|
this.actionFunctions = options.actionFunctions;
|
|
4481
4603
|
this.nodeById = options.nodeById;
|
|
4482
4604
|
this.stateById = options.stateById;
|
|
4605
|
+
this.stateByKey = options.stateByKey;
|
|
4483
4606
|
this.rerenderRoot = options.rerenderRoot;
|
|
4484
4607
|
this.dispatchWidgetEventImpl = options.dispatchWidgetEvent;
|
|
4485
4608
|
this.executeRequest = options.executeRequest;
|
|
@@ -4489,6 +4612,14 @@ var ActionRuntime = class {
|
|
|
4489
4612
|
this.resolveMappedValue = options.resolveMappedValue;
|
|
4490
4613
|
this.setWidgetEnabled = options.setWidgetEnabled;
|
|
4491
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;
|
|
4492
4623
|
this.setUiReference = options.setUiReference;
|
|
4493
4624
|
this.refreshWidgetTree = options.refreshWidgetTree;
|
|
4494
4625
|
this.renderWidgetTree = options.renderWidgetTree;
|
|
@@ -4531,30 +4662,68 @@ var ActionRuntime = class {
|
|
|
4531
4662
|
return this.getInlineActions(node);
|
|
4532
4663
|
}
|
|
4533
4664
|
async runSingleAction(action, inputValue, context) {
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4665
|
+
const actionName = action.action.trim();
|
|
4666
|
+
try {
|
|
4667
|
+
let output = await this.executeAction(action, inputValue, context);
|
|
4668
|
+
if (action.andThen?.length) {
|
|
4669
|
+
if (Array.isArray(output)) {
|
|
4670
|
+
const collected = [];
|
|
4671
|
+
for (const [index, entry] of output.entries()) {
|
|
4672
|
+
if (entry === null || entry === void 0) {
|
|
4673
|
+
continue;
|
|
4674
|
+
}
|
|
4675
|
+
const nestedOutput = await this.runActions(action.andThen, entry, {
|
|
4676
|
+
...context,
|
|
4677
|
+
currentValue: entry,
|
|
4678
|
+
currentList: output,
|
|
4679
|
+
currentIndex: index
|
|
4680
|
+
});
|
|
4681
|
+
if (nestedOutput !== null && nestedOutput !== void 0) {
|
|
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
|
+
}
|
|
4691
|
+
}
|
|
4545
4692
|
}
|
|
4693
|
+
output = collected;
|
|
4694
|
+
} else if (output !== null && output !== void 0) {
|
|
4695
|
+
output = await this.runActions(action.andThen, output, { ...context, currentValue: output });
|
|
4696
|
+
} else {
|
|
4697
|
+
output = null;
|
|
4546
4698
|
}
|
|
4547
|
-
output = collected;
|
|
4548
|
-
} else if (output !== null && output !== void 0) {
|
|
4549
|
-
output = await this.runActions(action.andThen, output, { ...context, currentValue: output });
|
|
4550
|
-
} else {
|
|
4551
|
-
output = null;
|
|
4552
4699
|
}
|
|
4700
|
+
logRuntimeDebug(this.debugLogging, "action-result", {
|
|
4701
|
+
action: actionName,
|
|
4702
|
+
output
|
|
4703
|
+
});
|
|
4704
|
+
return output;
|
|
4705
|
+
} catch (error) {
|
|
4706
|
+
logRuntimeError("action", error, {
|
|
4707
|
+
action: actionName,
|
|
4708
|
+
args: action.args,
|
|
4709
|
+
inputValue,
|
|
4710
|
+
currentValue: context.currentValue,
|
|
4711
|
+
responseValue: context.responseValue,
|
|
4712
|
+
pointer: context.pointer ?? null
|
|
4713
|
+
});
|
|
4714
|
+
throw error;
|
|
4553
4715
|
}
|
|
4554
|
-
return output;
|
|
4555
4716
|
}
|
|
4556
4717
|
async executeAction(action, inputValue, context) {
|
|
4557
4718
|
const name = action.action.trim();
|
|
4719
|
+
logRuntimeDebug(this.debugLogging, "action", {
|
|
4720
|
+
action: name,
|
|
4721
|
+
args: action.args,
|
|
4722
|
+
inputValue,
|
|
4723
|
+
currentValue: context.currentValue,
|
|
4724
|
+
responseValue: context.responseValue,
|
|
4725
|
+
pointer: context.pointer ?? null
|
|
4726
|
+
});
|
|
4558
4727
|
if (this.actionsMap[name]) {
|
|
4559
4728
|
return this.runActions(this.actionsMap[name], inputValue, context);
|
|
4560
4729
|
}
|
|
@@ -4582,6 +4751,30 @@ var ActionRuntime = class {
|
|
|
4582
4751
|
await this.navigateTo(name.slice(9));
|
|
4583
4752
|
return null;
|
|
4584
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
|
+
}
|
|
4585
4778
|
if (name.startsWith("showMenu ")) {
|
|
4586
4779
|
const menuId = name.slice(9);
|
|
4587
4780
|
const menuState = this.stateById.get(menuId);
|
|
@@ -4621,6 +4814,10 @@ var ActionRuntime = class {
|
|
|
4621
4814
|
this.clearWidget(name.slice(6));
|
|
4622
4815
|
return null;
|
|
4623
4816
|
}
|
|
4817
|
+
if (name.startsWith("setFocus ")) {
|
|
4818
|
+
this.focusWidget(name.slice(9));
|
|
4819
|
+
return null;
|
|
4820
|
+
}
|
|
4624
4821
|
if (name.startsWith("setUi ")) {
|
|
4625
4822
|
const widgetId = name.slice(6);
|
|
4626
4823
|
const resourceRef = typeof inputValue === "string" ? inputValue : "";
|
|
@@ -4643,10 +4840,74 @@ var ActionRuntime = class {
|
|
|
4643
4840
|
this.assignReference(name.slice(4), inputValue, context.currentValue, args);
|
|
4644
4841
|
return inputValue;
|
|
4645
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
|
+
}
|
|
4646
4850
|
if (name.startsWith("filter ")) {
|
|
4647
4851
|
const value = this.resolveReference(name.slice(7), context.currentValue, context.responseValue);
|
|
4648
4852
|
return value ? inputValue : null;
|
|
4649
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
|
+
}
|
|
4650
4911
|
if (name === "map") {
|
|
4651
4912
|
return this.resolveMappedValue(action.args, context.currentValue, context.responseValue);
|
|
4652
4913
|
}
|
|
@@ -4663,10 +4924,40 @@ var ActionRuntime = class {
|
|
|
4663
4924
|
}
|
|
4664
4925
|
return inputValue;
|
|
4665
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
|
+
}
|
|
4666
4954
|
};
|
|
4667
4955
|
function isIfElseArgs(value) {
|
|
4668
4956
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4669
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
|
+
}
|
|
4670
4961
|
|
|
4671
4962
|
// src/lib/dom-state.ts
|
|
4672
4963
|
function isPlainObject3(value) {
|
|
@@ -4695,7 +4986,7 @@ function isElementInsidePendingResetModal(element, pendingResetModalIds) {
|
|
|
4695
4986
|
}
|
|
4696
4987
|
return false;
|
|
4697
4988
|
}
|
|
4698
|
-
function captureElementState(root, pendingResetModalIds
|
|
4989
|
+
function captureElementState(root, pendingResetModalIds) {
|
|
4699
4990
|
const state = {
|
|
4700
4991
|
activeId: null,
|
|
4701
4992
|
values: /* @__PURE__ */ new Map(),
|
|
@@ -4723,20 +5014,6 @@ function captureElementState(root, pendingResetModalIds, stateByKey) {
|
|
|
4723
5014
|
if (element instanceof HTMLInputElement && element.type === "checkbox" && element.id) {
|
|
4724
5015
|
state.checked.set(element.id, element.checked);
|
|
4725
5016
|
}
|
|
4726
|
-
const widgetKey = element.dataset.widgetKey;
|
|
4727
|
-
if (!widgetKey) {
|
|
4728
|
-
continue;
|
|
4729
|
-
}
|
|
4730
|
-
const widgetState = stateByKey.get(widgetKey);
|
|
4731
|
-
if (!widgetState || element.readOnly || element.disabled) {
|
|
4732
|
-
continue;
|
|
4733
|
-
}
|
|
4734
|
-
if (widgetState.widget === "edit" || widgetState.widget === "textarea") {
|
|
4735
|
-
widgetState.text = element.value;
|
|
4736
|
-
}
|
|
4737
|
-
if (widgetState.widget === "checkbox" && element instanceof HTMLInputElement && element.type === "checkbox") {
|
|
4738
|
-
widgetState.checked = element.checked;
|
|
4739
|
-
}
|
|
4740
5017
|
}
|
|
4741
5018
|
for (const element of Array.from(root.querySelectorAll("[id], [data-widget-key]"))) {
|
|
4742
5019
|
const top = element.scrollTop;
|
|
@@ -4758,16 +5035,44 @@ function captureElementState(root, pendingResetModalIds, stateByKey) {
|
|
|
4758
5035
|
}
|
|
4759
5036
|
return state;
|
|
4760
5037
|
}
|
|
4761
|
-
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) {
|
|
4762
5061
|
for (const [id, value] of state.values.entries()) {
|
|
4763
5062
|
const element = root.querySelector(`#${CSS.escape(id)}`);
|
|
4764
5063
|
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
5064
|
+
if (!shouldRestoreTextValue(element, value, stateByKey)) {
|
|
5065
|
+
continue;
|
|
5066
|
+
}
|
|
4765
5067
|
element.value = value;
|
|
4766
5068
|
}
|
|
4767
5069
|
}
|
|
4768
5070
|
for (const [id, checked] of state.checked.entries()) {
|
|
4769
5071
|
const element = root.querySelector(`#${CSS.escape(id)}`);
|
|
4770
5072
|
if (element instanceof HTMLInputElement && element.type === "checkbox") {
|
|
5073
|
+
if (!shouldRestoreCheckedValue(element, checked, stateByKey)) {
|
|
5074
|
+
continue;
|
|
5075
|
+
}
|
|
4771
5076
|
element.checked = checked;
|
|
4772
5077
|
}
|
|
4773
5078
|
}
|
|
@@ -5070,6 +5375,430 @@ var DEFAULT_STYLE_MAP = {
|
|
|
5070
5375
|
employeeLinkDark: "display:flex; align-items:center; width:100%; height:100%; color:#b8d0ea; font-weight:600;"
|
|
5071
5376
|
};
|
|
5072
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
|
+
|
|
5073
5802
|
// src/lib/widgets/adaptive-layout.ts
|
|
5074
5803
|
var renderAdaptiveLayout = (env, node, _state, key, path, itemContext) => {
|
|
5075
5804
|
const mobile = env.isMobileViewport();
|
|
@@ -5528,7 +6257,7 @@ var renderOverlayContainer = (env, node, _state, key, path, itemContext) => {
|
|
|
5528
6257
|
var renderTabs = (env, node, state, key, path, itemContext) => {
|
|
5529
6258
|
const tabs = node.tabs ?? [];
|
|
5530
6259
|
const enabled = state.enabled ?? env.normalizeBoolean(node.enabled, true);
|
|
5531
|
-
const rawActiveTab = typeof
|
|
6260
|
+
const rawActiveTab = typeof state.activeTab === "number" ? state.activeTab : typeof node.activeTab === "number" ? node.activeTab : 0;
|
|
5532
6261
|
const activeTab = Math.max(0, Math.min(rawActiveTab, Math.max(tabs.length - 1, 0)));
|
|
5533
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("");
|
|
5534
6263
|
const content = tabs[activeTab]?.content ? env.renderNode(tabs[activeTab].content, `${path}.tabs.${activeTab}`, itemContext) : "";
|
|
@@ -5886,8 +6615,6 @@ function bindDelegatedUi(root, env) {
|
|
|
5886
6615
|
const state = env.stateByKey.get(key);
|
|
5887
6616
|
if (state) {
|
|
5888
6617
|
state.activeTab = Number.parseInt(tabButton.dataset.tabIndex ?? "0", 10) || 0;
|
|
5889
|
-
const tabsNode = node;
|
|
5890
|
-
tabsNode.activeTab = state.activeTab;
|
|
5891
6618
|
}
|
|
5892
6619
|
await env.rerenderRoot();
|
|
5893
6620
|
return;
|
|
@@ -6112,6 +6839,7 @@ var RuntimeRenderer = class {
|
|
|
6112
6839
|
systemEvents;
|
|
6113
6840
|
resourceManager;
|
|
6114
6841
|
actionFunctions;
|
|
6842
|
+
debugLogging;
|
|
6115
6843
|
backendUrl;
|
|
6116
6844
|
initialRuntimeSnapshot;
|
|
6117
6845
|
onRuntimeSnapshot;
|
|
@@ -6123,12 +6851,14 @@ var RuntimeRenderer = class {
|
|
|
6123
6851
|
stateByKey = /* @__PURE__ */ new Map();
|
|
6124
6852
|
nodeById = /* @__PURE__ */ new Map();
|
|
6125
6853
|
stateById = /* @__PURE__ */ new Map();
|
|
6854
|
+
vars = /* @__PURE__ */ new Map();
|
|
6126
6855
|
resizeHandler = null;
|
|
6127
6856
|
pointerHandler = null;
|
|
6128
6857
|
eventSources = [];
|
|
6129
6858
|
referenceRuntime;
|
|
6130
6859
|
networkRuntime;
|
|
6131
6860
|
actionRuntime;
|
|
6861
|
+
voiceRuntime;
|
|
6132
6862
|
splitterDragState = null;
|
|
6133
6863
|
hasTriggeredInitialRefresh = false;
|
|
6134
6864
|
activeModalId = null;
|
|
@@ -6152,6 +6882,7 @@ var RuntimeRenderer = class {
|
|
|
6152
6882
|
this.sseConfigs = Array.isArray(resolvedSseConfigs) ? resolvedSseConfigs : resolvedSseConfigs ? [resolvedSseConfigs] : [];
|
|
6153
6883
|
this.systemEvents = options.systemEvents ?? this.resourceManager?.getSystemEvents() ?? {};
|
|
6154
6884
|
this.actionFunctions = options.actionFunctions ?? {};
|
|
6885
|
+
this.debugLogging = options.debugLogging === true;
|
|
6155
6886
|
this.backendUrl = options.backendUrl;
|
|
6156
6887
|
this.initialRuntimeSnapshot = options.runtimeSnapshot ? deepClone(options.runtimeSnapshot) : null;
|
|
6157
6888
|
this.onRuntimeSnapshot = options.onRuntimeSnapshot;
|
|
@@ -6194,11 +6925,16 @@ var RuntimeRenderer = class {
|
|
|
6194
6925
|
break;
|
|
6195
6926
|
}
|
|
6196
6927
|
},
|
|
6928
|
+
getVarValue: (name) => this.vars.get(name),
|
|
6929
|
+
setVarValue: (name, value) => {
|
|
6930
|
+
this.vars.set(name, value);
|
|
6931
|
+
},
|
|
6197
6932
|
ensureWidgetState: (node, key) => this.ensureWidgetState(node, key),
|
|
6198
6933
|
resolveItemNodeKey: (node, listKey, index, fallbackPath) => this.resolveItemNodeKey(node, listKey, index, fallbackPath),
|
|
6199
6934
|
indexListElementNodes: (listNode, element, index) => this.indexListElementNodes(listNode, element, index)
|
|
6200
6935
|
});
|
|
6201
6936
|
this.networkRuntime = new NetworkRuntime({
|
|
6937
|
+
debugLogging: this.debugLogging,
|
|
6202
6938
|
requestsMap: this.requestsMap,
|
|
6203
6939
|
sseConfigs: this.sseConfigs,
|
|
6204
6940
|
backendUrl: this.backendUrl,
|
|
@@ -6206,11 +6942,17 @@ var RuntimeRenderer = class {
|
|
|
6206
6942
|
runActions: (actions, inputValue, context) => this.actionRuntime.runActions(actions, inputValue, context),
|
|
6207
6943
|
rerenderRoot: () => this.rerenderRoot()
|
|
6208
6944
|
});
|
|
6945
|
+
this.voiceRuntime = new VoiceRuntime({
|
|
6946
|
+
debugLogging: this.debugLogging,
|
|
6947
|
+
triggerSystemEvent: (eventName, inputValue) => this.triggerRuntimeSystemEvent(eventName, inputValue)
|
|
6948
|
+
});
|
|
6209
6949
|
this.actionRuntime = new ActionRuntime({
|
|
6950
|
+
debugLogging: this.debugLogging,
|
|
6210
6951
|
actionsMap: this.actionsMap,
|
|
6211
6952
|
actionFunctions: this.actionFunctions,
|
|
6212
6953
|
nodeById: this.nodeById,
|
|
6213
6954
|
stateById: this.stateById,
|
|
6955
|
+
stateByKey: this.stateByKey,
|
|
6214
6956
|
rerenderRoot: () => this.rerenderRoot(),
|
|
6215
6957
|
dispatchWidgetEvent: (node, eventName, key, inputValue, pointer) => this.actionRuntime.dispatchWidgetEvent(node, eventName, key, inputValue, pointer),
|
|
6216
6958
|
executeRequest: (requestName, currentValue) => this.networkRuntime.executeRequest(requestName, currentValue),
|
|
@@ -6220,6 +6962,14 @@ var RuntimeRenderer = class {
|
|
|
6220
6962
|
resolveMappedValue: (template, currentValue, responseValue) => this.referenceRuntime.resolveMappedValue(template, currentValue, responseValue),
|
|
6221
6963
|
setWidgetEnabled: (widgetId, enabled) => this.setWidgetEnabled(widgetId, enabled),
|
|
6222
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(),
|
|
6223
6973
|
setUiReference: (widgetId, resourceRef) => {
|
|
6224
6974
|
const node = this.nodeById.get(widgetId);
|
|
6225
6975
|
if (!node || node.widget !== "ui-reference") {
|
|
@@ -6252,7 +7002,9 @@ var RuntimeRenderer = class {
|
|
|
6252
7002
|
this.applyRuntimeSnapshot(this.initialRuntimeSnapshot);
|
|
6253
7003
|
}
|
|
6254
7004
|
renderMarkup() {
|
|
6255
|
-
|
|
7005
|
+
const markup = `${this.renderNode(this.description, "root", null)}${this.renderGlobalOverlays()}`;
|
|
7006
|
+
logRuntimeDebug(this.debugLogging, "html", markup);
|
|
7007
|
+
return markup;
|
|
6256
7008
|
}
|
|
6257
7009
|
mount(root, options = {}) {
|
|
6258
7010
|
if (!(root instanceof HTMLElement)) {
|
|
@@ -6311,6 +7063,7 @@ var RuntimeRenderer = class {
|
|
|
6311
7063
|
source.close();
|
|
6312
7064
|
}
|
|
6313
7065
|
this.eventSources.length = 0;
|
|
7066
|
+
this.voiceRuntime.dispose();
|
|
6314
7067
|
};
|
|
6315
7068
|
}
|
|
6316
7069
|
rerenderRoot() {
|
|
@@ -6318,7 +7071,7 @@ var RuntimeRenderer = class {
|
|
|
6318
7071
|
return Promise.resolve();
|
|
6319
7072
|
}
|
|
6320
7073
|
this.reindexStaticTree();
|
|
6321
|
-
const preservedState = captureElementState(this.root, this.pendingResetModalIds
|
|
7074
|
+
const preservedState = captureElementState(this.root, this.pendingResetModalIds);
|
|
6322
7075
|
this.root.innerHTML = this.renderMarkup();
|
|
6323
7076
|
this.root.classList.toggle("vjt-root--mobile", isMobileViewport());
|
|
6324
7077
|
this.root.classList.toggle("vjt-root--desktop", !isMobileViewport());
|
|
@@ -6327,7 +7080,7 @@ var RuntimeRenderer = class {
|
|
|
6327
7080
|
}
|
|
6328
7081
|
this.attachInputBehavior(this.root);
|
|
6329
7082
|
this.attachWidgetEvents(this.root);
|
|
6330
|
-
restoreElementState(this.root, preservedState);
|
|
7083
|
+
restoreElementState(this.root, preservedState, this.stateByKey);
|
|
6331
7084
|
this.activeContextMenu = adjustContextMenuPosition(this.root, this.activeContextMenu);
|
|
6332
7085
|
this.pendingResetModalIds.clear();
|
|
6333
7086
|
if (typeof this.onRuntimeSnapshot === "function") {
|
|
@@ -6342,6 +7095,17 @@ var RuntimeRenderer = class {
|
|
|
6342
7095
|
}
|
|
6343
7096
|
await this.actionRuntime.runActions(actions, null, { currentValue: null });
|
|
6344
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
|
+
}
|
|
6345
7109
|
async navigateTo(url) {
|
|
6346
7110
|
if (typeof window === "undefined" || !url.trim()) {
|
|
6347
7111
|
return;
|
|
@@ -6396,6 +7160,12 @@ var RuntimeRenderer = class {
|
|
|
6396
7160
|
this.stateById.set(restoredState.id, restoredState);
|
|
6397
7161
|
}
|
|
6398
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
|
+
}
|
|
6399
7169
|
if (snapshot.activeModalId && this.nodeById.get(snapshot.activeModalId)?.widget === "modal-window") {
|
|
6400
7170
|
this.activeModalId = snapshot.activeModalId;
|
|
6401
7171
|
}
|
|
@@ -6406,6 +7176,7 @@ var RuntimeRenderer = class {
|
|
|
6406
7176
|
createRuntimeSnapshot() {
|
|
6407
7177
|
return {
|
|
6408
7178
|
stateByKey: Array.from(this.stateByKey.entries(), ([key, state]) => [key, deepClone(state)]),
|
|
7179
|
+
vars: Array.from(this.vars.entries(), ([name, value]) => [name, deepClone(value)]),
|
|
6409
7180
|
activeModalId: this.activeModalId,
|
|
6410
7181
|
activeContextMenu: this.activeContextMenu ? deepClone(this.activeContextMenu) : null
|
|
6411
7182
|
};
|
|
@@ -7349,6 +8120,28 @@ var RuntimeRenderer = class {
|
|
|
7349
8120
|
clearListElementState(listKey) {
|
|
7350
8121
|
this.referenceRuntime.clearListElementState(listKey);
|
|
7351
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
|
+
}
|
|
7352
8145
|
};
|
|
7353
8146
|
function renderJson(description, options = {}) {
|
|
7354
8147
|
const renderer = new RuntimeRenderer(description, options);
|