@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.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
- json = JSON.parse(responseText);
3927
- } catch {
3928
- throw new Error(`Request ${requestName} returned non-JSON response: ${responseText.slice(0, 120)}`);
3929
- }
3930
- if (json.error) {
3931
- throw new Error(`Request ${requestName} returned JSON-RPC error: ${String(json.error.message ?? "unknown error")}`);
3932
- }
3933
- if (!definition.response) {
3934
- throw new Error(`Request ${requestName} must define response schema`);
3935
- }
3936
- const result = sanitizeSchemaValue(definition.response, json.result, `response.${requestName}`);
3937
- if (definition.onResponse?.length) {
3938
- await this.runActions(definition.onResponse, null, {
3939
- currentValue: null,
3940
- responseValue: result
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
- let output = await this.executeAction(action, inputValue, context);
4535
- if (action.andThen?.length) {
4536
- if (Array.isArray(output)) {
4537
- const collected = [];
4538
- for (const entry of output) {
4539
- if (entry === null || entry === void 0) {
4540
- continue;
4541
- }
4542
- const nestedOutput = await this.runActions(action.andThen, entry, { ...context, currentValue: entry });
4543
- if (nestedOutput !== null && nestedOutput !== void 0) {
4544
- collected.push(nestedOutput);
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, stateByKey) {
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 restoreElementState(root, state) {
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 node.activeTab === "number" ? node.activeTab : state.activeTab ?? 0;
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
- return `${this.renderNode(this.description, "root", null)}${this.renderGlobalOverlays()}`;
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, this.stateByKey);
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);