rasa-pro 3.10.15__py3-none-any.whl → 3.11.0__py3-none-any.whl

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.

Potentially problematic release.


This version of rasa-pro might be problematic. Click here for more details.

Files changed (238) hide show
  1. rasa/__main__.py +31 -15
  2. rasa/api.py +12 -2
  3. rasa/cli/arguments/default_arguments.py +24 -4
  4. rasa/cli/arguments/run.py +15 -0
  5. rasa/cli/arguments/shell.py +5 -1
  6. rasa/cli/arguments/train.py +17 -9
  7. rasa/cli/evaluate.py +7 -7
  8. rasa/cli/inspect.py +19 -7
  9. rasa/cli/interactive.py +1 -0
  10. rasa/cli/project_templates/calm/config.yml +5 -7
  11. rasa/cli/project_templates/calm/endpoints.yml +15 -2
  12. rasa/cli/project_templates/tutorial/config.yml +8 -5
  13. rasa/cli/project_templates/tutorial/data/flows.yml +1 -1
  14. rasa/cli/project_templates/tutorial/data/patterns.yml +5 -0
  15. rasa/cli/project_templates/tutorial/domain.yml +14 -0
  16. rasa/cli/project_templates/tutorial/endpoints.yml +5 -0
  17. rasa/cli/run.py +7 -0
  18. rasa/cli/scaffold.py +4 -2
  19. rasa/cli/studio/upload.py +0 -15
  20. rasa/cli/train.py +14 -53
  21. rasa/cli/utils.py +14 -11
  22. rasa/cli/x.py +7 -7
  23. rasa/constants.py +3 -1
  24. rasa/core/actions/action.py +77 -33
  25. rasa/core/actions/action_hangup.py +29 -0
  26. rasa/core/actions/action_repeat_bot_messages.py +89 -0
  27. rasa/core/actions/e2e_stub_custom_action_executor.py +5 -1
  28. rasa/core/actions/http_custom_action_executor.py +4 -0
  29. rasa/core/agent.py +2 -2
  30. rasa/core/brokers/kafka.py +3 -1
  31. rasa/core/brokers/pika.py +3 -1
  32. rasa/core/channels/__init__.py +10 -6
  33. rasa/core/channels/channel.py +41 -4
  34. rasa/core/channels/development_inspector.py +150 -46
  35. rasa/core/channels/inspector/README.md +1 -1
  36. rasa/core/channels/inspector/dist/assets/{arc-b6e548fe.js → arc-bc141fb2.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-fa03ac9e.js → c4Diagram-d0fbc5ce-be2db283.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-ee67392a.js → classDiagram-936ed81e-55366915.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-9b283fae.js → classDiagram-v2-c3cb15f1-bb529518.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{createText-62fc7601-8b6fcc2a.js → createText-62fc7601-b0ec81d6.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-22e77f4f.js → edges-f2ad444c-6166330c.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-60ffc87f.js → erDiagram-9d236eb7-5ccc6a8e.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-9dd802e4.js → flowDb-1972c806-fca3bfe4.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-5fa1912f.js → flowDiagram-7ea5b25a-4739080f.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +1 -0
  46. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-622a1fd2.js → flowchart-elk-definition-abe16c3d-7c1b0e0f.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-e285a63a.js → ganttDiagram-9b5ea136-772fd050.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-f237bdca.js → gitGraphDiagram-99d0ae7c-8eae1dc9.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-4b03d70e.js → index-2c4b9a3b-f55afcdf.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/index-e7cef9de.js +1317 -0
  51. rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-72a0fa5f.js → infoDiagram-736b4530-124d4a14.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-82218c41.js → journeyDiagram-df861f2b-7c4fae44.js} +1 -1
  53. rasa/core/channels/inspector/dist/assets/{layout-78cff630.js → layout-b9885fb6.js} +1 -1
  54. rasa/core/channels/inspector/dist/assets/{line-5038b469.js → line-7c59abb6.js} +1 -1
  55. rasa/core/channels/inspector/dist/assets/{linear-c4fc4098.js → linear-4776f780.js} +1 -1
  56. rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-c33c8ea6.js → mindmap-definition-beec6740-2332c46c.js} +1 -1
  57. rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-a8d03059.js → pieDiagram-dbbf0591-8fb39303.js} +1 -1
  58. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-6a0e56b2.js → quadrantDiagram-4d7f4fd6-3c7180a2.js} +1 -1
  59. rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-2dc7c7bd.js → requirementDiagram-6fc4c22a-e910bcb8.js} +1 -1
  60. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-2360fe39.js → sankeyDiagram-8f13d901-ead16c89.js} +1 -1
  61. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-41b9f9ad.js → sequenceDiagram-b655622a-29a02a19.js} +1 -1
  62. rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-0aad326f.js → stateDiagram-59f0c015-042b3137.js} +1 -1
  63. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-9847d984.js → stateDiagram-v2-2b26beab-2178c0f3.js} +1 -1
  64. rasa/core/channels/inspector/dist/assets/{styles-080da4f6-564d890e.js → styles-080da4f6-23ffa4fc.js} +1 -1
  65. rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-38957613.js → styles-3dcbcfbf-94f59763.js} +1 -1
  66. rasa/core/channels/inspector/dist/assets/{styles-9c745c82-f0fc6921.js → styles-9c745c82-78a6bebc.js} +1 -1
  67. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-ef3c5a77.js → svgDrawCommon-4835440b-eae2a6f6.js} +1 -1
  68. rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-bf3e91c1.js → timeline-definition-5b62e21b-5c968d92.js} +1 -1
  69. rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-4d4026c0.js → xychartDiagram-2b33534f-fd3db0d5.js} +1 -1
  70. rasa/core/channels/inspector/dist/index.html +18 -15
  71. rasa/core/channels/inspector/index.html +17 -14
  72. rasa/core/channels/inspector/package.json +5 -1
  73. rasa/core/channels/inspector/src/App.tsx +118 -68
  74. rasa/core/channels/inspector/src/components/Chat.tsx +95 -0
  75. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +11 -10
  76. rasa/core/channels/inspector/src/components/DialogueStack.tsx +10 -25
  77. rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -3
  78. rasa/core/channels/inspector/src/helpers/audiostream.ts +165 -0
  79. rasa/core/channels/inspector/src/helpers/formatters.test.ts +10 -0
  80. rasa/core/channels/inspector/src/helpers/formatters.ts +107 -41
  81. rasa/core/channels/inspector/src/helpers/utils.ts +92 -7
  82. rasa/core/channels/inspector/src/types.ts +21 -1
  83. rasa/core/channels/inspector/yarn.lock +94 -1
  84. rasa/core/channels/rest.py +51 -46
  85. rasa/core/channels/socketio.py +28 -1
  86. rasa/core/channels/telegram.py +1 -1
  87. rasa/core/channels/twilio.py +1 -1
  88. rasa/core/channels/{audiocodes.py → voice_ready/audiocodes.py} +122 -69
  89. rasa/core/channels/{voice_aware → voice_ready}/jambonz.py +26 -8
  90. rasa/core/channels/{voice_aware → voice_ready}/jambonz_protocol.py +57 -5
  91. rasa/core/channels/{twilio_voice.py → voice_ready/twilio_voice.py} +64 -28
  92. rasa/core/channels/voice_ready/utils.py +37 -0
  93. rasa/core/channels/voice_stream/asr/__init__.py +0 -0
  94. rasa/core/channels/voice_stream/asr/asr_engine.py +89 -0
  95. rasa/core/channels/voice_stream/asr/asr_event.py +18 -0
  96. rasa/core/channels/voice_stream/asr/azure.py +129 -0
  97. rasa/core/channels/voice_stream/asr/deepgram.py +90 -0
  98. rasa/core/channels/voice_stream/audio_bytes.py +8 -0
  99. rasa/core/channels/voice_stream/browser_audio.py +107 -0
  100. rasa/core/channels/voice_stream/call_state.py +23 -0
  101. rasa/core/channels/voice_stream/tts/__init__.py +0 -0
  102. rasa/core/channels/voice_stream/tts/azure.py +106 -0
  103. rasa/core/channels/voice_stream/tts/cartesia.py +118 -0
  104. rasa/core/channels/voice_stream/tts/tts_cache.py +27 -0
  105. rasa/core/channels/voice_stream/tts/tts_engine.py +58 -0
  106. rasa/core/channels/voice_stream/twilio_media_streams.py +173 -0
  107. rasa/core/channels/voice_stream/util.py +57 -0
  108. rasa/core/channels/voice_stream/voice_channel.py +427 -0
  109. rasa/core/information_retrieval/qdrant.py +1 -0
  110. rasa/core/nlg/contextual_response_rephraser.py +45 -17
  111. rasa/{nlu → core}/persistor.py +203 -68
  112. rasa/core/policies/enterprise_search_policy.py +119 -63
  113. rasa/core/policies/flows/flow_executor.py +15 -22
  114. rasa/core/policies/intentless_policy.py +83 -28
  115. rasa/core/processor.py +25 -0
  116. rasa/core/run.py +12 -2
  117. rasa/core/secrets_manager/constants.py +4 -0
  118. rasa/core/secrets_manager/factory.py +8 -0
  119. rasa/core/secrets_manager/vault.py +11 -1
  120. rasa/core/training/interactive.py +33 -34
  121. rasa/core/utils.py +47 -21
  122. rasa/dialogue_understanding/coexistence/llm_based_router.py +41 -14
  123. rasa/dialogue_understanding/commands/__init__.py +6 -0
  124. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +60 -0
  125. rasa/dialogue_understanding/commands/session_end_command.py +61 -0
  126. rasa/dialogue_understanding/commands/user_silence_command.py +59 -0
  127. rasa/dialogue_understanding/commands/utils.py +5 -0
  128. rasa/dialogue_understanding/generator/constants.py +2 -0
  129. rasa/dialogue_understanding/generator/flow_retrieval.py +47 -9
  130. rasa/dialogue_understanding/generator/llm_based_command_generator.py +38 -15
  131. rasa/dialogue_understanding/generator/llm_command_generator.py +1 -1
  132. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +35 -13
  133. rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +3 -0
  134. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +60 -13
  135. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +53 -0
  136. rasa/dialogue_understanding/patterns/repeat.py +37 -0
  137. rasa/dialogue_understanding/patterns/user_silence.py +37 -0
  138. rasa/dialogue_understanding/processor/command_processor.py +21 -1
  139. rasa/e2e_test/aggregate_test_stats_calculator.py +1 -11
  140. rasa/e2e_test/assertions.py +136 -61
  141. rasa/e2e_test/assertions_schema.yml +23 -0
  142. rasa/e2e_test/e2e_test_case.py +85 -6
  143. rasa/e2e_test/e2e_test_runner.py +2 -3
  144. rasa/engine/graph.py +0 -1
  145. rasa/engine/loader.py +12 -0
  146. rasa/engine/recipes/config_files/default_config.yml +0 -3
  147. rasa/engine/recipes/default_recipe.py +0 -1
  148. rasa/engine/recipes/graph_recipe.py +0 -1
  149. rasa/engine/runner/dask.py +2 -2
  150. rasa/engine/storage/local_model_storage.py +12 -42
  151. rasa/engine/storage/storage.py +1 -5
  152. rasa/engine/validation.py +527 -74
  153. rasa/model_manager/__init__.py +0 -0
  154. rasa/model_manager/config.py +40 -0
  155. rasa/model_manager/model_api.py +559 -0
  156. rasa/model_manager/runner_service.py +286 -0
  157. rasa/model_manager/socket_bridge.py +146 -0
  158. rasa/model_manager/studio_jwt_auth.py +86 -0
  159. rasa/model_manager/trainer_service.py +325 -0
  160. rasa/model_manager/utils.py +87 -0
  161. rasa/model_manager/warm_rasa_process.py +187 -0
  162. rasa/model_service.py +112 -0
  163. rasa/model_training.py +42 -23
  164. rasa/nlu/tokenizers/whitespace_tokenizer.py +3 -14
  165. rasa/server.py +4 -2
  166. rasa/shared/constants.py +60 -8
  167. rasa/shared/core/constants.py +13 -0
  168. rasa/shared/core/domain.py +107 -50
  169. rasa/shared/core/events.py +29 -0
  170. rasa/shared/core/flows/flow.py +5 -0
  171. rasa/shared/core/flows/flows_list.py +19 -6
  172. rasa/shared/core/flows/flows_yaml_schema.json +10 -0
  173. rasa/shared/core/flows/utils.py +39 -0
  174. rasa/shared/core/flows/validation.py +121 -0
  175. rasa/shared/core/flows/yaml_flows_io.py +15 -27
  176. rasa/shared/core/slots.py +5 -0
  177. rasa/shared/importers/importer.py +59 -41
  178. rasa/shared/importers/multi_project.py +23 -11
  179. rasa/shared/importers/rasa.py +12 -3
  180. rasa/shared/importers/remote_importer.py +196 -0
  181. rasa/shared/importers/utils.py +3 -1
  182. rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
  183. rasa/shared/nlu/training_data/training_data.py +18 -19
  184. rasa/shared/providers/_configs/litellm_router_client_config.py +220 -0
  185. rasa/shared/providers/_configs/model_group_config.py +167 -0
  186. rasa/shared/providers/_configs/openai_client_config.py +1 -1
  187. rasa/shared/providers/_configs/rasa_llm_client_config.py +73 -0
  188. rasa/shared/providers/_configs/self_hosted_llm_client_config.py +1 -0
  189. rasa/shared/providers/_configs/utils.py +16 -0
  190. rasa/shared/providers/_utils.py +79 -0
  191. rasa/shared/providers/embedding/_base_litellm_embedding_client.py +13 -29
  192. rasa/shared/providers/embedding/azure_openai_embedding_client.py +54 -21
  193. rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
  194. rasa/shared/providers/embedding/litellm_router_embedding_client.py +135 -0
  195. rasa/shared/providers/llm/_base_litellm_client.py +34 -22
  196. rasa/shared/providers/llm/azure_openai_llm_client.py +50 -29
  197. rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
  198. rasa/shared/providers/llm/litellm_router_llm_client.py +182 -0
  199. rasa/shared/providers/llm/rasa_llm_client.py +112 -0
  200. rasa/shared/providers/llm/self_hosted_llm_client.py +5 -29
  201. rasa/shared/providers/mappings.py +19 -0
  202. rasa/shared/providers/router/__init__.py +0 -0
  203. rasa/shared/providers/router/_base_litellm_router_client.py +183 -0
  204. rasa/shared/providers/router/router_client.py +73 -0
  205. rasa/shared/utils/common.py +40 -24
  206. rasa/shared/utils/health_check/__init__.py +0 -0
  207. rasa/shared/utils/health_check/embeddings_health_check_mixin.py +31 -0
  208. rasa/shared/utils/health_check/health_check.py +258 -0
  209. rasa/shared/utils/health_check/llm_health_check_mixin.py +31 -0
  210. rasa/shared/utils/io.py +27 -6
  211. rasa/shared/utils/llm.py +353 -43
  212. rasa/shared/utils/schemas/events.py +2 -0
  213. rasa/shared/utils/schemas/model_config.yml +0 -10
  214. rasa/shared/utils/yaml.py +181 -38
  215. rasa/studio/data_handler.py +3 -1
  216. rasa/studio/upload.py +160 -74
  217. rasa/telemetry.py +94 -17
  218. rasa/tracing/config.py +3 -1
  219. rasa/tracing/instrumentation/attribute_extractors.py +95 -18
  220. rasa/tracing/instrumentation/instrumentation.py +121 -0
  221. rasa/utils/common.py +5 -0
  222. rasa/utils/endpoints.py +27 -1
  223. rasa/utils/io.py +8 -16
  224. rasa/utils/log_utils.py +9 -2
  225. rasa/utils/sanic_error_handler.py +32 -0
  226. rasa/validator.py +110 -4
  227. rasa/version.py +1 -1
  228. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/METADATA +14 -12
  229. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/RECORD +234 -183
  230. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-1844e5a5.js +0 -1
  231. rasa/core/channels/inspector/dist/assets/index-a5d3e69d.js +0 -1040
  232. rasa/core/channels/voice_aware/utils.py +0 -20
  233. rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +0 -407
  234. /rasa/core/channels/{voice_aware → voice_ready}/__init__.py +0 -0
  235. /rasa/core/channels/{voice_native → voice_stream}/__init__.py +0 -0
  236. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/NOTICE +0 -0
  237. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/WHEEL +0 -0
  238. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,11 @@
1
- import { Center, Spinner, Text, useColorModeValue } from "@chakra-ui/react";
1
+ import { Center, Spinner, Text, Button, useColorModeValue } from "@chakra-ui/react";
2
2
  import { useOurTheme } from "../theme";
3
+ import {createAudioConnection} from "../helpers/audiostream.ts";
3
4
 
4
5
  export const LoadingSpinner = () => {
5
6
  const { rasaSpace } = useOurTheme();
6
-
7
+ const isVoice = window.location.href.includes("browser_audio");
8
+ const text = isVoice ? "Start a new conversation" : "Waiting for a new conversation"
7
9
  return (
8
10
  <Center height={"100vh"} flexDirection="column">
9
11
  <Spinner
@@ -13,7 +15,8 @@ export const LoadingSpinner = () => {
13
15
  size="lg"
14
16
  mb={rasaSpace[1]}
15
17
  />
16
- <Text>Loading</Text>
18
+ <Text fontSize="lg">{text}</Text>
19
+ {isVoice ? <Button onClick={createAudioConnection}>Go</Button> : null}
17
20
  </Center>
18
21
  );
19
22
  };
@@ -0,0 +1,165 @@
1
+ const bufferSize = 4096
2
+ const sampleRate = 8000
3
+ const audioOptions = {
4
+ audio: {
5
+ echoCancellation: true,
6
+ noiseSuppression: true,
7
+ autoGainControl: true
8
+ }
9
+ }
10
+
11
+ const arrayBufferToBase64 = ( buffer: ArrayBuffer ): string => {
12
+ let binary = '';
13
+ const bytes = new Uint8Array( buffer );
14
+ const len = bytes.byteLength;
15
+ for (let i = 0; i < len; i++) {
16
+ binary += String.fromCharCode( bytes[ i ] );
17
+ }
18
+ return window.btoa( binary );
19
+ }
20
+
21
+ const base64ToArrayBuffer = ( s: string ): ArrayBuffer => {
22
+ const binary_string = window.atob(s);
23
+ const len = binary_string.length;
24
+ const bytes = new Uint8Array( len );
25
+ for (let i = 0; i < len; i++) {
26
+ bytes[i] = binary_string.charCodeAt(i);
27
+ }
28
+ return bytes.buffer;
29
+ }
30
+
31
+ const floatToIntArray = (arr: Float32Array): Int32Array => {
32
+ // Convert Float Array [-1, 1] to full range int array
33
+ return Int32Array.from(arr, x => x * 0x7fffffff)
34
+ }
35
+
36
+ const intToFloatArray = (arr: Int32Array): Float32Array => {
37
+ return Float32Array.from(arr, x => (x / 0x7fffffff))
38
+ }
39
+
40
+ interface Mark {
41
+ id: string
42
+ bytesToGo: number
43
+ }
44
+
45
+ interface AudioQueue {
46
+ buffer: Float32Array;
47
+ marks: Array<Mark>
48
+ socket: WebSocket,
49
+ write: (newAudio: Float32Array) => void;
50
+ read: (nSamples: number) => Float32Array;
51
+ length: () => number;
52
+ addMarker: (id: string) => void;
53
+ reduceMarkers: (bytesRead: number) => void;
54
+ popMarkers: () => void;
55
+ }
56
+
57
+
58
+ const createAudioQueue = (socket: WebSocket) : AudioQueue => {
59
+ return {
60
+ buffer: new Float32Array(0),
61
+ marks: new Array<Mark>(),
62
+ socket,
63
+
64
+ write: function(newAudio: Float32Array) {
65
+ const currentQLength = this.buffer.length;
66
+ const newBuffer = new Float32Array(currentQLength + newAudio.length);
67
+ newBuffer.set(this.buffer, 0);
68
+ newBuffer.set(newAudio, currentQLength);
69
+ this.buffer = newBuffer;
70
+ },
71
+
72
+ read: function(nSamples: number) {
73
+ const samplesToPlay = this.buffer.subarray(0, nSamples);
74
+ this.buffer = this.buffer.subarray(nSamples, this.buffer.length);
75
+ this.reduceMarkers(samplesToPlay.length)
76
+ this.popMarkers()
77
+ return samplesToPlay;
78
+ },
79
+
80
+ length: function() {
81
+ return this.buffer.length;
82
+ },
83
+
84
+ addMarker: function(id: string) {
85
+ this.marks.push({id, bytesToGo: this.length()})
86
+ },
87
+
88
+ reduceMarkers: function(bytesRead: number) {
89
+ this.marks = this.marks.map((m) => {
90
+ return {id: m.id, bytesToGo: m.bytesToGo - bytesRead}
91
+ })
92
+ },
93
+
94
+ popMarkers: function() {
95
+ // marks are ordered
96
+ let popUpTo = 0;
97
+ while (popUpTo < this.marks.length) {
98
+ if (this.marks[popUpTo].bytesToGo <= 0) {
99
+ popUpTo += 1
100
+ } else {
101
+ break
102
+ }
103
+ }
104
+ const marksToPop = this.marks.slice(0, popUpTo)
105
+ this.marks = this.marks.slice(popUpTo, this.marks.length)
106
+ marksToPop.forEach((m) => {
107
+ this.socket.send(JSON.stringify({marker: m.id}))
108
+ })
109
+ }
110
+
111
+ };
112
+ }
113
+
114
+ const streamMicrophoneToServer = async (socket: WebSocket) => {
115
+ let audioStream = null;
116
+ const audioContext = new AudioContext({sampleRate});
117
+
118
+ try {
119
+ audioStream = await navigator.mediaDevices.getUserMedia(audioOptions);
120
+ const audioInput = audioContext.createMediaStreamSource(audioStream)
121
+ const sender = audioContext.createScriptProcessor(bufferSize, 1, 1)
122
+ sender.onaudioprocess = function(event) {
123
+ const message = JSON.stringify({
124
+ "audio": arrayBufferToBase64(floatToIntArray(event.inputBuffer.getChannelData(0)).buffer)
125
+ })
126
+ socket.send(message)
127
+ }
128
+ audioInput.connect(sender)
129
+ sender.connect(audioContext.destination)
130
+ } catch (err) {
131
+ console.error(err);
132
+ }
133
+ }
134
+
135
+ const setupAudioPlayback = (socket: WebSocket): AudioQueue => {
136
+ const audioQueue = createAudioQueue(socket)
137
+ const silence = new Float32Array(bufferSize)
138
+ const audioOutputContext = new AudioContext({sampleRate})
139
+ const scriptNode = audioOutputContext.createScriptProcessor(bufferSize, 1, 1);
140
+ scriptNode.onaudioprocess = function(e) {
141
+ const audioData = audioQueue.length() ? audioQueue.read(bufferSize) : silence
142
+ e.outputBuffer.getChannelData(0).set(audioData);
143
+ }
144
+ scriptNode.connect(audioOutputContext.destination)
145
+ return audioQueue
146
+ }
147
+
148
+ const addDataToAudioQueue = (audioQueue: AudioQueue) => (message: MessageEvent<any>) => {
149
+ const data = JSON.parse(message.data.toString())
150
+ if (data["audio"]) {
151
+ const audioBytes = base64ToArrayBuffer(data["audio"])
152
+ const audioData = intToFloatArray(new Int32Array(audioBytes))
153
+ audioQueue.write(audioData);
154
+ } else if (data["marker"]) {
155
+ audioQueue.addMarker(data["marker"])
156
+ }
157
+ }
158
+
159
+ export async function createAudioConnection() {
160
+ const websocketURL = "ws://localhost:5005/webhooks/browser_audio/websocket"
161
+ const socket = new WebSocket(websocketURL)
162
+ socket.onopen = async () => { await streamMicrophoneToServer(socket)}
163
+ const audioQueue = setupAudioPlayback(socket)
164
+ socket.onmessage = addDataToAudioQueue(audioQueue)
165
+ }
@@ -75,6 +75,7 @@ describe("helpers", () => {
75
75
  {
76
76
  event: "user",
77
77
  text: "book a restaurant",
78
+ timestamp: "123",
78
79
  },
79
80
  {
80
81
  event: "bot",
@@ -82,18 +83,22 @@ describe("helpers", () => {
82
83
  utter_action: "utter_ask_book_restaurant_name_of_restaurant",
83
84
  },
84
85
  text: "What's the name of the restaurant you are interested in?",
86
+ timestamp: "124",
85
87
  },
86
88
  {
87
89
  event: "user",
88
90
  text: "simsim",
91
+ timestamp: "125",
89
92
  },
90
93
  {
91
94
  event: "bot",
92
95
  text: "How many people are we talking?",
96
+ timestamp: "126",
93
97
  },
94
98
  {
95
99
  event: "user",
96
100
  text: "100",
101
+ timestamp: "127",
97
102
  },
98
103
  {
99
104
  event: "bot",
@@ -101,6 +106,7 @@ describe("helpers", () => {
101
106
  utter_action: "utter_ask_book_restaurant_date",
102
107
  },
103
108
  text: "For which day do you want to book?",
109
+ timestamp: "128",
104
110
  },
105
111
  ];
106
112
  const result = formatTestCases(events, sessionId);
@@ -287,6 +293,7 @@ describe("helpers", () => {
287
293
  flow_id: "flow_id",
288
294
  step_id: "step_id",
289
295
  collect: fieldValue,
296
+ ended: false,
290
297
  })
291
298
  ).toEqual(fieldValue);
292
299
  });
@@ -299,6 +306,7 @@ describe("helpers", () => {
299
306
  frame_id: "frame_id",
300
307
  flow_id: "flow_id",
301
308
  step_id: "step_id",
309
+ ended: false,
302
310
  })
303
311
  ).toEqual(fieldValue);
304
312
  });
@@ -312,6 +320,7 @@ describe("helpers", () => {
312
320
  flow_id: "flow_id",
313
321
  step_id: "step_id",
314
322
  collect: fieldValue,
323
+ ended: false,
315
324
  })
316
325
  ).toEqual(`${fieldValue} is not null`);
317
326
  });
@@ -325,6 +334,7 @@ describe("helpers", () => {
325
334
  flow_id: "flow_id",
326
335
  step_id: "step_id",
327
336
  collect: fieldValue,
337
+ ended: false,
328
338
  })
329
339
  ).toEqual(`not ${fieldValue}`);
330
340
  });
@@ -48,19 +48,20 @@ ${steps.join("\n")}`;
48
48
 
49
49
  function hashCode(str: string) {
50
50
  var hash = 0,
51
- i, chr;
51
+ i,
52
+ chr;
52
53
  if (str.length === 0) return hash;
53
54
  for (i = 0; i < str.length; i++) {
54
55
  chr = str.charCodeAt(i);
55
- hash = ((hash << 5) - hash) + chr;
56
+ hash = (hash << 5) - hash + chr;
56
57
  hash |= 0; // Convert to 32bit integer
57
58
  }
58
59
  return hash;
59
- };
60
+ }
60
61
 
61
62
  function mermaidIdForTitle(title: string) {
62
- return `id${hashCode(title)}`
63
- };
63
+ return `id${hashCode(title)}`;
64
+ }
64
65
 
65
66
  const encodeDoubleQuotes = (str: string) =>
66
67
  /**
@@ -68,13 +69,14 @@ const encodeDoubleQuotes = (str: string) =>
68
69
  */
69
70
  str.replace(/"/g, `#34;`);
70
71
 
71
-
72
72
  export const formatFlow = (
73
73
  slots: Slot[],
74
74
  currentStack?: Stack,
75
75
  flow?: Flow,
76
- activeStep?: string
76
+ stepTrail?: string[]
77
77
  ) => {
78
+ const activeStep = currentStack?.step_id;
79
+
78
80
  if (!flow) {
79
81
  return "";
80
82
  }
@@ -86,18 +88,20 @@ classDef action fill:#FBFCFD,stroke:#A0B8CF
86
88
  classDef link fill:#f43
87
89
  classDef slot fill:#e8f3db,stroke:#c5e1a5
88
90
  classDef endstep fill:#ccc,stroke:#444
91
+ classDef previous stroke:${rasaColors.rasaOrange[400]},stroke-width:1px
89
92
  classDef active stroke:${rasaColors.rasaOrange[400]},stroke-width:3px,fill:${rasaColors.warning[50]}
90
93
  `,
91
94
  ];
92
-
93
95
  try {
94
96
  const text = renderStepSequence(
95
97
  flow.steps,
96
98
  slots,
97
99
  currentStack,
98
- activeStep
100
+ activeStep,
101
+ stepTrail
99
102
  );
100
103
  mermaidText.push(text);
104
+ mermaidText.push(colorDoubleEdges(mermaidText.join("")));
101
105
  return mermaidText.join("");
102
106
  } catch (e) {
103
107
  return `${mermaidText}\nA["Something went wrong!"]\nB["${e}"]`;
@@ -112,6 +116,30 @@ function truncate(str: string, limit = 35) {
112
116
  return str;
113
117
  }
114
118
 
119
+ function colorDoubleEdges(mermaidText: string) {
120
+ // go through the lines of mermaid text. keep a counter counting edges
121
+ // ("-->"" or "==>"). if "==>" is found in a line, add the line number to
122
+ // a list.
123
+ const lines = mermaidText.split("\n");
124
+ const coloredEdges = [];
125
+ let edgeCounter = 0;
126
+ for (let i = 0; i < lines.length; i++) {
127
+ if (lines[i].includes("-->")) {
128
+ edgeCounter++;
129
+ } else if (lines[i].includes("==>")) {
130
+ coloredEdges.push(edgeCounter);
131
+ edgeCounter++;
132
+ }
133
+ }
134
+ if(coloredEdges.length > 0) {
135
+ return `linkStyle ${coloredEdges.join(",")} stroke:${
136
+ rasaColors.rasaOrange[400]
137
+ }, ;\n`;
138
+ } else {
139
+ return "";
140
+ }
141
+ }
142
+
115
143
  export function parseFieldUsingStack(name: string, stack?: Stack): string {
116
144
  // name might be in the `{{context.field_in_stack}}` format so we're stripping everything except the field in the stack name
117
145
  const parsedField = name.split(/{{context\.|}}/);
@@ -122,7 +150,7 @@ export function parseFieldUsingStack(name: string, stack?: Stack): string {
122
150
  const stackField = parsedField[1];
123
151
 
124
152
  // @ts-expect-error `stack[stackField]` doesn't necessary exists this might return `undefined`
125
- const stackValue = stack ? stack[stackField]: undefined;
153
+ const stackValue = stack ? stack[stackField] : undefined;
126
154
 
127
155
  // name might also be in the `condition {{context.field_in_stack}} condition` format
128
156
  // so we want to keep that if there is any
@@ -133,11 +161,18 @@ export function parseFieldUsingStack(name: string, stack?: Stack): string {
133
161
  return `${parsedField[0]}${stackValue}`;
134
162
  }
135
163
 
164
+ function arrowTypeFor(stepId: string, nextId: string, stepTrail?: string[]) {
165
+ return stepTrail?.includes(stepId) && stepTrail?.includes(nextId)
166
+ ? "==>"
167
+ : "-->";
168
+ }
169
+
136
170
  function renderStepSequence(
137
171
  steps: Flow["steps"],
138
172
  slots: Slot[],
139
173
  currentStack?: Stack,
140
- activeStep?: string
174
+ activeStep?: string,
175
+ stepTrail?: string[]
141
176
  ) {
142
177
  let hasUsedEndStep = false;
143
178
  let mermaidTextFragment = "";
@@ -147,16 +182,19 @@ function renderStepSequence(
147
182
 
148
183
  if (step.collect) {
149
184
  const slot = slots.find((slot) => slot.name === step.collect);
150
- const slotValue = slot && typeof slot.value === "string" ? `"${encodeDoubleQuotes(truncate(slot.value))}"` : "💬";
151
- mermaidTextFragment += `${mermaidId}["${encodeDoubleQuotes(truncate(
152
- parseFieldUsingStack(step.collect, currentStack)
153
- ))}\n${slotValue}"]:::collect\n`;
185
+ const slotValue =
186
+ slot && typeof slot.value === "string"
187
+ ? `"${encodeDoubleQuotes(truncate(slot.value))}"`
188
+ : "💬";
189
+ mermaidTextFragment += `${mermaidId}["${encodeDoubleQuotes(
190
+ truncate(parseFieldUsingStack(step.collect, currentStack))
191
+ )}\n${slotValue}"]:::collect\n`;
154
192
  }
155
193
 
156
194
  if (step.action) {
157
- mermaidTextFragment += `${mermaidId}["${encodeDoubleQuotes(truncate(
158
- parseFieldUsingStack(step.action, currentStack)
159
- ))}"]:::action\n`;
195
+ mermaidTextFragment += `${mermaidId}["${encodeDoubleQuotes(
196
+ truncate(parseFieldUsingStack(step.action, currentStack))
197
+ )}"]:::action\n`;
160
198
  }
161
199
 
162
200
  if (step.link) {
@@ -167,20 +205,27 @@ function renderStepSequence(
167
205
  }
168
206
 
169
207
  if (step.set_slots) {
170
- mermaidTextFragment += `${mermaidId}["✍️ ${encodeDoubleQuotes(stepId)}"]:::slot\n`;
208
+ mermaidTextFragment += `${mermaidId}["✍️ ${encodeDoubleQuotes(
209
+ stepId
210
+ )}"]:::slot\n`;
171
211
  }
172
212
 
173
213
  if (activeStep && stepId === activeStep) {
174
214
  mermaidTextFragment += `class ${mermaidId} active\n`;
215
+ } else if (stepTrail?.includes(stepId)) {
216
+ mermaidTextFragment += `class ${mermaidId} previous\n`;
175
217
  }
176
218
 
177
219
  // if next is an id, then it is a link
178
220
  if (step.next && typeof step.next === "string") {
179
- mermaidTextFragment += `${mermaidId} --> ${mermaidIdForTitle(parseFieldUsingStack(
180
- step.next,
181
- currentStack
182
- ))}\n`;
183
- if(step.next == "END") { 
221
+ const nextId = parseFieldUsingStack(step.next, currentStack);
222
+
223
+ mermaidTextFragment += `${mermaidId} ${arrowTypeFor(
224
+ stepId,
225
+ nextId,
226
+ stepTrail
227
+ )} ${mermaidIdForTitle(nextId)}\n`;
228
+ if (step.next == "END") {
184
229
  hasUsedEndStep = true;
185
230
  }
186
231
  }
@@ -189,52 +234,73 @@ function renderStepSequence(
189
234
  if (step.next && Array.isArray(step.next)) {
190
235
  step.next.forEach((condition) => {
191
236
  if (condition.then && typeof condition.then === "string") {
192
- mermaidTextFragment += `${mermaidId} -->|"${encodeDoubleQuotes(parseFieldUsingStack(
193
- condition.if,
194
- currentStack
195
- ))}"| ${mermaidIdForTitle(condition.then)}\n`;
196
- if(condition.then == "END") { 
237
+ mermaidTextFragment += `${mermaidId} ${arrowTypeFor(
238
+ stepId,
239
+ condition.then,
240
+ stepTrail
241
+ )}|"${encodeDoubleQuotes(
242
+ parseFieldUsingStack(condition.if, currentStack)
243
+ )}"| ${mermaidIdForTitle(condition.then)}\n`;
244
+ if (condition.then == "END") {
197
245
  hasUsedEndStep = true;
198
246
  }
199
247
  } else if (condition.then) {
200
- mermaidTextFragment += `${mermaidId} -->|"${encodeDoubleQuotes(parseFieldUsingStack(
201
- condition.if,
202
- currentStack
203
- ))}"| ${mermaidIdForTitle(condition.then[0].id)}\n`;
248
+ mermaidTextFragment += `${mermaidId} ${arrowTypeFor(
249
+ stepId,
250
+ condition.then[0].id,
251
+ stepTrail
252
+ )}|"${encodeDoubleQuotes(
253
+ parseFieldUsingStack(condition.if, currentStack)
254
+ )}"| ${mermaidIdForTitle(condition.then[0].id)}\n`;
204
255
  mermaidTextFragment += renderStepSequence(
205
256
  // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
206
257
  condition.then,
207
258
  slots,
208
259
  currentStack,
209
- activeStep
260
+ activeStep,
261
+ stepTrail
210
262
  );
211
263
  }
212
264
 
213
265
  // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
214
266
  if (condition.else && typeof condition.else === "string") {
267
+ mermaidTextFragment += `${mermaidId} ${arrowTypeFor(
268
+ stepId,
269
+ // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
270
+ condition.else,
271
+ stepTrail
272
+ // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
273
+ )}|else| ${mermaidIdForTitle(condition.else)}\n`;
215
274
  // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
216
- mermaidTextFragment += `${mermaidId} -->|else| ${mermaidIdForTitle(condition.else)}\n`;
217
- // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
218
- if(condition.else == "END") { 
275
+ if (condition.else == "END") {
219
276
  hasUsedEndStep = true;
220
277
  }
221
278
  // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
222
279
  } else if (condition.else) {
223
- // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
224
- mermaidTextFragment += `${mermaidId} -->|else| ${mermaidIdForTitle(condition.else[0].id)}\n`;
280
+ mermaidTextFragment += `${mermaidId} ${arrowTypeFor(
281
+ stepId,
282
+ // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
283
+ condition.else[0].id,
284
+ stepTrail
285
+ // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
286
+ )}|else| ${mermaidIdForTitle(condition.else[0].id)}\n`;
225
287
  mermaidTextFragment += renderStepSequence(
226
288
  // @ts-expect-error Currently the param for renderStepSequence only accepts a Step, for further improvements we need to change the type to know that it can also be a then step
227
289
  condition.else,
228
290
  slots,
229
291
  currentStack,
230
- activeStep
292
+ activeStep,
293
+ stepTrail
231
294
  );
232
295
  }
233
296
  });
234
297
  }
235
298
  });
236
- if (hasUsedEndStep){
299
+ if (hasUsedEndStep) {
237
300
  mermaidTextFragment += `${mermaidIdForTitle("END")}["🏁 END"]:::endstep\n`;
301
+ if (activeStep && "END" === activeStep) {
302
+ mermaidTextFragment += `class ${mermaidIdForTitle("END")} active\n`;
303
+ }
238
304
  }
239
305
  return mermaidTextFragment;
240
306
  }
@@ -1,4 +1,5 @@
1
- import { SelectedStack, Stack } from "../types";
1
+ import { SelectedStack, Stack, Event } from "../types";
2
+ import { immutableJSONPatch} from 'immutable-json-patch'
2
3
 
3
4
  export const shouldShowTooltip = (text: string) => {
4
5
  const textLength = text.length;
@@ -10,7 +11,76 @@ export const shouldShowTooltip = (text: string) => {
10
11
  return false;
11
12
  };
12
13
 
13
- export const updatedActiveFrame = (previous: SelectedStack | undefined, updatedStack: Stack[]) => {
14
+ export const createHistoricalStack = (activeStack: Stack [], events: Event[]): Stack[] => {
15
+ let stackFrames = activeStack.map((frame) => ({
16
+ ...frame,
17
+ ended: false,
18
+ }));
19
+ // go through the events looking for flow_completed and append them to the stack
20
+ let historicalStack: Stack[] = [];
21
+ let pastStackFrames: Stack[] = [];
22
+ for (const event of events) {
23
+ if (event.event === "restart") {
24
+ historicalStack = [];
25
+ stackFrames = [];
26
+ } else if (event.event === "stack") {
27
+ let stackUpdate = JSON.parse(event.update || "");
28
+ historicalStack = immutableJSONPatch(historicalStack, stackUpdate)
29
+ for (const frame of historicalStack) {
30
+ if(!frame.flow_id){
31
+ // this is not a stack frame from the flow handler
32
+ continue;
33
+ }
34
+ // if the frame is already in stackFrames, skip it
35
+ if(stackFrames.find((f) => f.frame_id === frame.frame_id)) {
36
+ continue;
37
+ }
38
+ // if the frame is in pastStackFrames, update the step_id otherwise add it
39
+ const pastFrame = pastStackFrames.find((f) => f.frame_id === frame.frame_id)
40
+ if(pastFrame) {
41
+ pastFrame.step_id = frame.step_id;
42
+ continue;
43
+ }
44
+ pastStackFrames.push({...frame, ended: true});
45
+ }
46
+ }
47
+ }
48
+ // filter out pattern_collect_information frames
49
+ pastStackFrames = pastStackFrames.filter((frame) => frame.flow_id !== "pattern_collect_information");
50
+ return [...pastStackFrames, ...stackFrames];
51
+ }
52
+
53
+ export const flowStepTrail = (events: Event[]): Record<string, string[]> => {
54
+ let stack: Stack[] = [];
55
+ // mapping from flow id to the steps that were active in that flow
56
+ let activeSteps: { [key: string]: string[] } = {};
57
+ for (const event of events) {
58
+ if (event.event === "restart") {
59
+ stack = [];
60
+ activeSteps = {};
61
+ } else if (event.event === "stack") {
62
+ let stackUpdate = JSON.parse(event.update || "");
63
+ stack = immutableJSONPatch(stack, stackUpdate)
64
+ if (stack.length > 0) {
65
+ let topFrame = stack[stack.length - 1];
66
+ if (!topFrame.flow_id) {
67
+ // this is not a stack frame from the flow handler
68
+ continue;
69
+ }
70
+ if (!activeSteps[topFrame.flow_id] || topFrame.step_id === "START") {
71
+ activeSteps[topFrame.flow_id] = [];
72
+ }
73
+ if (!activeSteps[topFrame.flow_id].includes(topFrame.step_id)) {
74
+ activeSteps[topFrame.flow_id].push(topFrame.step_id)
75
+ }
76
+ }
77
+
78
+ }
79
+ }
80
+ return activeSteps;
81
+ };
82
+
83
+ export const updatedActiveFrame = (previous: SelectedStack | undefined, updatedStack: Stack[], events: Event[]) => {
14
84
  // try to find the currently active frame in the updated stack
15
85
  // if it isn't there anymore, we will show the first non-pattern frame
16
86
  // instead
@@ -23,20 +93,35 @@ export const updatedActiveFrame = (previous: SelectedStack | undefined, updatedS
23
93
  const activeFrame = updatedStack.find(
24
94
  (stackFrame) => stackFrame.frame_id === previous?.stack.frame_id
25
95
  );
26
- if (!activeFrame) {
27
- const updatedFrame = updatedStack
28
- .slice()
29
- .reverse()
30
- .find((frame) => !frame.flow_id?.startsWith("pattern_"));
96
+ if (!activeFrame || activeFrame.ended) {
97
+ if (!updatedStack){
98
+ return undefined;
99
+ }
100
+ // iterate over the stack. select the first frame where the name does not
101
+ // contain "pattern" and that has not
102
+ // ended yet. If there is no such frame, select the topmost frame that has
103
+ // not ended yet. If there is no such frame, select the topmost frame.
104
+ const updatedFrame = updatedStack.slice().reverse().find(
105
+ (frame) =>
106
+ !frame.flow_id?.startsWith("pattern_") && !frame.ended
107
+ ) || updatedStack.slice().reverse().find(
108
+ (frame) => !frame.ended
109
+ ) || updatedStack[updatedStack.length - 1];
110
+
31
111
  if(updatedFrame !== undefined) {
32
112
  return {
33
113
  stack: updatedFrame,
34
114
  isUserSelected: false,
115
+ activatedSteps: flowStepTrail(events)[updatedFrame.flow_id] || [],
35
116
  };
36
117
  } else {
37
118
  return undefined;
38
119
  }
39
120
  } else {
121
+ if(previous){
122
+ previous.activatedSteps = flowStepTrail(events)[previous.stack.flow_id] || [];
123
+ previous.stack = activeFrame;
124
+ }
40
125
  return previous;
41
126
  }
42
127
  };