@nyaruka/temba-components 0.139.0 → 0.141.0
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/.github/workflows/cla.yml +1 -1
- package/.github/workflows/copilot-setup-steps.yml +6 -1
- package/.lintstagedrc.js +10 -0
- package/CHANGELOG.md +32 -0
- package/demo/data/flows/sample-flow.json +24 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +11 -2
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +702 -338
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Chat.js +10 -7
- package/out-tsc/src/display/Chat.js.map +1 -1
- package/out-tsc/src/display/Dropdown.js +3 -1
- package/out-tsc/src/display/Dropdown.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +4 -4
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/display/Thumbnail.js +163 -5
- package/out-tsc/src/display/Thumbnail.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +65 -23
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +369 -49
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +118 -10
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +61 -14
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +13 -4
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js +4 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +4 -1
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/audio-player.js +112 -0
- package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
- package/out-tsc/src/flow/actions/enter_flow.js +43 -0
- package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
- package/out-tsc/src/flow/actions/play_audio.js +57 -4
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +86 -3
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +6 -2
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +13 -0
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +7 -5
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +10 -3
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/config.js +11 -3
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
- package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_contact_field.js +18 -5
- package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +0 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_random.js +0 -1
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_run_result.js +10 -4
- package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
- package/out-tsc/src/flow/nodes/terminal.js +7 -0
- package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
- package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/operators.js +21 -5
- package/out-tsc/src/flow/operators.js.map +1 -1
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/flow/utils.js +79 -3
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +4 -2
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +56 -0
- package/out-tsc/src/form/FieldRenderer.js.map +1 -1
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/Dialog.js +51 -7
- package/out-tsc/src/layout/Dialog.js.map +1 -1
- package/out-tsc/src/layout/Modax.js +20 -2
- package/out-tsc/src/layout/Modax.js.map +1 -1
- package/out-tsc/src/list/ContentMenu.js +14 -1
- package/out-tsc/src/list/ContentMenu.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +11 -2
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +21 -4
- package/out-tsc/src/simulator/Simulator.js.map +1 -1
- package/out-tsc/src/store/AppState.js +102 -3
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/test/actions/add_contact_groups.test.js +35 -0
- package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -1
- package/out-tsc/test/actions/add_input_labels.test.js +53 -0
- package/out-tsc/test/actions/add_input_labels.test.js.map +1 -0
- package/out-tsc/test/actions/enter_flow.test.js +71 -0
- package/out-tsc/test/actions/enter_flow.test.js.map +1 -0
- package/out-tsc/test/actions/play_audio.test.js +118 -0
- package/out-tsc/test/actions/play_audio.test.js.map +1 -0
- package/out-tsc/test/actions/remove_contact_groups.test.js +24 -0
- package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -1
- package/out-tsc/test/actions/say_msg.test.js +158 -0
- package/out-tsc/test/actions/say_msg.test.js.map +1 -0
- package/out-tsc/test/actions/send_broadcast.test.js +41 -0
- package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
- package/out-tsc/test/actions/set_contact_channel.test.js +67 -0
- package/out-tsc/test/actions/set_contact_channel.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_field.test.js +52 -0
- package/out-tsc/test/actions/set_contact_field.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_language.test.js +39 -0
- package/out-tsc/test/actions/set_contact_language.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_name.test.js +28 -0
- package/out-tsc/test/actions/set_contact_name.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_status.test.js +44 -0
- package/out-tsc/test/actions/set_contact_status.test.js.map +1 -0
- package/out-tsc/test/actions/set_run_result.test.js +47 -0
- package/out-tsc/test/actions/set_run_result.test.js.map +1 -0
- package/out-tsc/test/actions/start_session.test.js +76 -0
- package/out-tsc/test/actions/start_session.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_contact_field.test.js +50 -0
- package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_run_result.test.js +82 -0
- package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_ticket.test.js +139 -0
- package/out-tsc/test/nodes/split_by_ticket.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_webhook.test.js +111 -0
- package/out-tsc/test/nodes/split_by_webhook.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
- package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
- package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
- package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
- package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
- package/out-tsc/test/temba-flow-collision.test.js +261 -6
- package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor.test.js +187 -0
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber.test.js +19 -0
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
- package/out-tsc/test/temba-node-type-selector.test.js +6 -6
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +4 -1
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +4 -2
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +3 -9
- package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
- package/screenshots/truth/actions/add_input_labels/editor/multiple-labels.png +0 -0
- package/screenshots/truth/actions/add_input_labels/editor/single-label.png +0 -0
- package/screenshots/truth/actions/add_input_labels/render/multiple-labels.png +0 -0
- package/screenshots/truth/actions/add_input_labels/render/single-label.png +0 -0
- package/screenshots/truth/actions/enter_flow/editor/basic-flow.png +0 -0
- package/screenshots/truth/actions/enter_flow/editor/long-flow-name.png +0 -0
- package/screenshots/truth/actions/enter_flow/render/basic-flow.png +0 -0
- package/screenshots/truth/actions/enter_flow/render/long-flow-name.png +0 -0
- package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
- package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
- package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
- package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
- package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/editor/sms-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/editor/whatsapp-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/render/sms-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/render/whatsapp-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_field/editor/clear-value.png +0 -0
- package/screenshots/truth/actions/set_contact_field/editor/set-value.png +0 -0
- package/screenshots/truth/actions/set_contact_field/render/clear-value.png +0 -0
- package/screenshots/truth/actions/set_contact_field/render/set-value.png +0 -0
- package/screenshots/truth/actions/set_contact_language/editor/english.png +0 -0
- package/screenshots/truth/actions/set_contact_language/editor/french.png +0 -0
- package/screenshots/truth/actions/set_contact_language/render/english.png +0 -0
- package/screenshots/truth/actions/set_contact_language/render/french.png +0 -0
- package/screenshots/truth/actions/set_contact_name/editor/expression-name.png +0 -0
- package/screenshots/truth/actions/set_contact_name/editor/static-name.png +0 -0
- package/screenshots/truth/actions/set_contact_name/render/expression-name.png +0 -0
- package/screenshots/truth/actions/set_contact_name/render/static-name.png +0 -0
- package/screenshots/truth/actions/set_contact_status/editor/active.png +0 -0
- package/screenshots/truth/actions/set_contact_status/editor/archived.png +0 -0
- package/screenshots/truth/actions/set_contact_status/editor/blocked.png +0 -0
- package/screenshots/truth/actions/set_contact_status/render/active.png +0 -0
- package/screenshots/truth/actions/set_contact_status/render/archived.png +0 -0
- package/screenshots/truth/actions/set_contact_status/render/blocked.png +0 -0
- package/screenshots/truth/actions/set_run_result/editor/expression-value.png +0 -0
- package/screenshots/truth/actions/set_run_result/editor/with-category.png +0 -0
- package/screenshots/truth/actions/set_run_result/render/expression-value.png +0 -0
- package/screenshots/truth/actions/set_run_result/render/with-category.png +0 -0
- package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/wait_for_audio/editor/basic-audio-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
- package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
- package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/src/display/Chat.ts +13 -7
- package/src/display/Dropdown.ts +3 -1
- package/src/display/FloatingTab.ts +4 -4
- package/src/display/Thumbnail.ts +162 -2
- package/src/flow/CanvasNode.ts +70 -24
- package/src/flow/Editor.ts +440 -99
- package/src/flow/NodeEditor.ts +137 -9
- package/src/flow/Plumber.ts +89 -14
- package/src/flow/StickyNote.ts +14 -4
- package/src/flow/actions/add_contact_groups.ts +4 -1
- package/src/flow/actions/add_input_labels.ts +4 -1
- package/src/flow/actions/audio-player.ts +127 -0
- package/src/flow/actions/enter_flow.ts +44 -0
- package/src/flow/actions/play_audio.ts +64 -5
- package/src/flow/actions/remove_contact_groups.ts +6 -1
- package/src/flow/actions/say_msg.ts +94 -4
- package/src/flow/actions/send_broadcast.ts +6 -2
- package/src/flow/actions/set_contact_channel.ts +13 -1
- package/src/flow/actions/set_contact_status.ts +7 -5
- package/src/flow/actions/start_session.ts +10 -3
- package/src/flow/config.ts +11 -3
- package/src/flow/nodes/shared-rules.ts +1 -1
- package/src/flow/nodes/split_by_contact_field.ts +16 -5
- package/src/flow/nodes/split_by_expression.ts +1 -1
- package/src/flow/nodes/split_by_llm_categorize.ts +0 -1
- package/src/flow/nodes/split_by_random.ts +0 -1
- package/src/flow/nodes/split_by_run_result.ts +10 -4
- package/src/flow/nodes/terminal.ts +9 -0
- package/src/flow/nodes/wait_for_audio.ts +88 -0
- package/src/flow/nodes/wait_for_dial.ts +176 -0
- package/src/flow/nodes/wait_for_digits.ts +87 -2
- package/src/flow/nodes/wait_for_menu.ts +209 -3
- package/src/flow/nodes/wait_for_response.ts +1 -1
- package/src/flow/operators.ts +23 -5
- package/src/flow/types.ts +23 -1
- package/src/flow/utils.ts +82 -3
- package/src/form/ArrayEditor.ts +4 -2
- package/src/form/FieldRenderer.ts +71 -1
- package/src/interfaces.ts +2 -1
- package/src/layout/Dialog.ts +52 -7
- package/src/layout/Modax.ts +19 -2
- package/src/list/ContentMenu.ts +15 -1
- package/src/locales/es.ts +18 -13
- package/src/locales/fr.ts +18 -13
- package/src/locales/locale-codes.ts +11 -2
- package/src/locales/pt.ts +18 -13
- package/src/simulator/Simulator.ts +25 -4
- package/src/store/AppState.ts +120 -1
- package/src/store/flow-definition.d.ts +2 -0
- package/test/actions/add_contact_groups.test.ts +38 -0
- package/test/actions/add_input_labels.test.ts +67 -0
- package/test/actions/enter_flow.test.ts +88 -0
- package/test/actions/play_audio.test.ts +155 -0
- package/test/actions/remove_contact_groups.test.ts +29 -0
- package/test/actions/say_msg.test.ts +196 -0
- package/test/actions/send_broadcast.test.ts +44 -0
- package/test/actions/set_contact_channel.test.ts +88 -0
- package/test/actions/set_contact_field.test.ts +68 -0
- package/test/actions/set_contact_language.test.ts +55 -0
- package/test/actions/set_contact_name.test.ts +39 -0
- package/test/actions/set_contact_status.test.ts +64 -0
- package/test/actions/set_run_result.test.ts +61 -0
- package/test/actions/start_session.test.ts +82 -0
- package/test/nodes/split_by_contact_field.test.ts +59 -0
- package/test/nodes/split_by_run_result.test.ts +100 -0
- package/test/nodes/split_by_ticket.test.ts +157 -0
- package/test/nodes/split_by_webhook.test.ts +131 -0
- package/test/nodes/wait_for_audio.test.ts +182 -0
- package/test/nodes/wait_for_dial.test.ts +382 -0
- package/test/nodes/wait_for_digits.test.ts +233 -109
- package/test/nodes/wait_for_menu.test.ts +383 -0
- package/test/temba-flow-collision.test.ts +286 -6
- package/test/temba-flow-editor.test.ts +240 -0
- package/test/temba-flow-plumber.test.ts +62 -0
- package/test/temba-node-type-selector.test.ts +6 -6
- package/test/temba-select.test.ts +6 -1
- package/test/utils.test.ts +4 -2
- package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
|
@@ -1,13 +1,72 @@
|
|
|
1
1
|
import { html } from 'lit-html';
|
|
2
|
-
import { ActionConfig, ACTION_GROUPS, FlowTypes } from '../types';
|
|
2
|
+
import { ActionConfig, ACTION_GROUPS, FormData, FlowTypes } from '../types';
|
|
3
3
|
import { Node, PlayAudio } from '../../store/flow-definition';
|
|
4
4
|
|
|
5
5
|
export const play_audio: ActionConfig = {
|
|
6
|
-
name: 'Play
|
|
6
|
+
name: 'Play Recording',
|
|
7
7
|
group: ACTION_GROUPS.send,
|
|
8
8
|
flowTypes: [FlowTypes.VOICE],
|
|
9
|
-
render: (_node: Node,
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
render: (_node: Node, action: PlayAudio) => {
|
|
10
|
+
return html`
|
|
11
|
+
<div style="display: flex; align-items: center; gap: 0.3em;">
|
|
12
|
+
<temba-icon name="recording" size="1"></temba-icon>
|
|
13
|
+
<div
|
|
14
|
+
style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;"
|
|
15
|
+
title="${action.audio_url || ''}"
|
|
16
|
+
>
|
|
17
|
+
${action.audio_url || ''}
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
`;
|
|
21
|
+
},
|
|
22
|
+
form: {
|
|
23
|
+
audio_url: {
|
|
24
|
+
type: 'text',
|
|
25
|
+
label: 'Recording URL',
|
|
26
|
+
required: true,
|
|
27
|
+
evaluated: true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
layout: ['audio_url'],
|
|
31
|
+
toFormData: (action: PlayAudio) => {
|
|
32
|
+
return {
|
|
33
|
+
uuid: action.uuid,
|
|
34
|
+
audio_url: action.audio_url || ''
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
fromFormData: (data: FormData) => {
|
|
38
|
+
return {
|
|
39
|
+
uuid: data.uuid,
|
|
40
|
+
type: 'play_audio',
|
|
41
|
+
audio_url: (data.audio_url || '').trim()
|
|
42
|
+
} as PlayAudio;
|
|
43
|
+
},
|
|
44
|
+
localizable: ['audio_url'],
|
|
45
|
+
toLocalizationFormData: (
|
|
46
|
+
action: PlayAudio,
|
|
47
|
+
localization: Record<string, any>
|
|
48
|
+
) => {
|
|
49
|
+
const formData: FormData = {
|
|
50
|
+
uuid: action.uuid
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (localization.audio_url && Array.isArray(localization.audio_url)) {
|
|
54
|
+
formData.audio_url = localization.audio_url[0] || '';
|
|
55
|
+
} else {
|
|
56
|
+
formData.audio_url = '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return formData;
|
|
60
|
+
},
|
|
61
|
+
fromLocalizationFormData: (formData: FormData, action: PlayAudio) => {
|
|
62
|
+
const localization: Record<string, any> = {};
|
|
63
|
+
|
|
64
|
+
if (formData.audio_url && formData.audio_url.trim() !== '') {
|
|
65
|
+
if (formData.audio_url !== action.audio_url) {
|
|
66
|
+
localization.audio_url = [formData.audio_url];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return localization;
|
|
12
71
|
}
|
|
13
72
|
};
|
|
@@ -69,7 +69,12 @@ export const remove_contact_groups: ActionConfig = {
|
|
|
69
69
|
return {
|
|
70
70
|
uuid: formData.uuid,
|
|
71
71
|
type: 'remove_contact_groups',
|
|
72
|
-
groups: formData.all_groups
|
|
72
|
+
groups: formData.all_groups
|
|
73
|
+
? []
|
|
74
|
+
: (formData.groups || []).map((g: any) => ({
|
|
75
|
+
uuid: g.uuid,
|
|
76
|
+
name: g.name
|
|
77
|
+
})),
|
|
73
78
|
all_groups: formData.all_groups || false
|
|
74
79
|
};
|
|
75
80
|
}
|
|
@@ -1,13 +1,103 @@
|
|
|
1
1
|
import { html } from 'lit-html';
|
|
2
|
-
import {
|
|
2
|
+
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
|
3
|
+
import { ActionConfig, ACTION_GROUPS, FormData, FlowTypes } from '../types';
|
|
3
4
|
import { Node, SayMsg } from '../../store/flow-definition';
|
|
5
|
+
import { renderAudioPlayer } from './audio-player';
|
|
4
6
|
|
|
5
7
|
export const say_msg: ActionConfig = {
|
|
6
8
|
name: 'Say Message',
|
|
7
9
|
group: ACTION_GROUPS.send,
|
|
8
10
|
flowTypes: [FlowTypes.VOICE],
|
|
9
|
-
render: (_node: Node,
|
|
10
|
-
|
|
11
|
-
return html
|
|
11
|
+
render: (_node: Node, action: SayMsg) => {
|
|
12
|
+
const text = (action.text || '').replace(/\n/g, '<br>');
|
|
13
|
+
return html`
|
|
14
|
+
${unsafeHTML(text)}
|
|
15
|
+
${action.audio_url
|
|
16
|
+
? html`<div style="margin-top: 0.5em;">
|
|
17
|
+
${renderAudioPlayer(action.audio_url)}
|
|
18
|
+
</div>`
|
|
19
|
+
: null}
|
|
20
|
+
`;
|
|
21
|
+
},
|
|
22
|
+
form: {
|
|
23
|
+
text: {
|
|
24
|
+
type: 'textarea',
|
|
25
|
+
label: 'Message',
|
|
26
|
+
required: true,
|
|
27
|
+
evaluated: true,
|
|
28
|
+
placeholder: 'Enter message to speak...',
|
|
29
|
+
minHeight: 80
|
|
30
|
+
},
|
|
31
|
+
audio_url: {
|
|
32
|
+
type: 'media',
|
|
33
|
+
label: 'Recording',
|
|
34
|
+
required: false,
|
|
35
|
+
accept: 'audio/*',
|
|
36
|
+
optionalLink: 'Add a recording'
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
layout: ['text', 'audio_url'],
|
|
40
|
+
toFormData: (action: SayMsg) => {
|
|
41
|
+
return {
|
|
42
|
+
uuid: action.uuid,
|
|
43
|
+
text: action.text || '',
|
|
44
|
+
audio_url: action.audio_url || ''
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
fromFormData: (data: FormData) => {
|
|
48
|
+
const result: any = {
|
|
49
|
+
uuid: data.uuid,
|
|
50
|
+
type: 'say_msg',
|
|
51
|
+
text: data.text || ''
|
|
52
|
+
};
|
|
53
|
+
if (data.audio_url && data.audio_url.trim() !== '') {
|
|
54
|
+
result.audio_url = data.audio_url.trim();
|
|
55
|
+
}
|
|
56
|
+
return result as SayMsg;
|
|
57
|
+
},
|
|
58
|
+
sanitize: (formData: FormData): void => {
|
|
59
|
+
if (formData.text && typeof formData.text === 'string') {
|
|
60
|
+
formData.text = formData.text.trim();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
localizable: ['text', 'audio_url'],
|
|
64
|
+
toLocalizationFormData: (
|
|
65
|
+
action: SayMsg,
|
|
66
|
+
localization: Record<string, any>
|
|
67
|
+
) => {
|
|
68
|
+
const formData: FormData = {
|
|
69
|
+
uuid: action.uuid
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (localization.text && Array.isArray(localization.text)) {
|
|
73
|
+
formData.text = localization.text[0] || '';
|
|
74
|
+
} else {
|
|
75
|
+
formData.text = '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (localization.audio_url && Array.isArray(localization.audio_url)) {
|
|
79
|
+
formData.audio_url = localization.audio_url[0] || '';
|
|
80
|
+
} else {
|
|
81
|
+
formData.audio_url = '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return formData;
|
|
85
|
+
},
|
|
86
|
+
fromLocalizationFormData: (formData: FormData, action: SayMsg) => {
|
|
87
|
+
const localization: Record<string, any> = {};
|
|
88
|
+
|
|
89
|
+
if (formData.text && formData.text.trim() !== '') {
|
|
90
|
+
if (formData.text !== action.text) {
|
|
91
|
+
localization.text = [formData.text];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (formData.audio_url && formData.audio_url.trim() !== '') {
|
|
96
|
+
if (formData.audio_url !== action.audio_url) {
|
|
97
|
+
localization.audio_url = [formData.audio_url];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return localization;
|
|
12
102
|
}
|
|
13
103
|
};
|
|
@@ -70,8 +70,12 @@ export const send_broadcast: ActionConfig = {
|
|
|
70
70
|
|
|
71
71
|
fromFormData: (formData: FormData): SendBroadcast => {
|
|
72
72
|
const recipients = formData.recipients || [];
|
|
73
|
-
const contacts = recipients
|
|
74
|
-
|
|
73
|
+
const contacts = recipients
|
|
74
|
+
.filter((r: any) => !r.group)
|
|
75
|
+
.map((c: any) => ({ uuid: c.uuid, name: c.name }));
|
|
76
|
+
const groups = recipients
|
|
77
|
+
.filter((r: any) => r.group)
|
|
78
|
+
.map((g: any) => ({ uuid: g.uuid, name: g.name }));
|
|
75
79
|
|
|
76
80
|
const result: SendBroadcast = {
|
|
77
81
|
uuid: formData.uuid,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { html } from 'lit-html';
|
|
2
|
-
import { ActionConfig, ACTION_GROUPS, FlowTypes } from '../types';
|
|
2
|
+
import { ActionConfig, ACTION_GROUPS, FormData, FlowTypes } from '../types';
|
|
3
3
|
import { Node, SetContactChannel } from '../../store/flow-definition';
|
|
4
4
|
|
|
5
5
|
export const set_contact_channel: ActionConfig = {
|
|
@@ -19,6 +19,7 @@ export const set_contact_channel: ActionConfig = {
|
|
|
19
19
|
endpoint: '/api/v2/channels.json',
|
|
20
20
|
valueKey: 'uuid',
|
|
21
21
|
nameKey: 'name',
|
|
22
|
+
placeholder: 'Select channel',
|
|
22
23
|
helpText: 'Select the channel to set for the contact'
|
|
23
24
|
}
|
|
24
25
|
},
|
|
@@ -27,5 +28,16 @@ export const set_contact_channel: ActionConfig = {
|
|
|
27
28
|
uuid: action.uuid,
|
|
28
29
|
channel: action.channel ? [action.channel] : null
|
|
29
30
|
};
|
|
31
|
+
},
|
|
32
|
+
fromFormData: (formData: FormData): SetContactChannel => {
|
|
33
|
+
const channel = formData.channel?.[0];
|
|
34
|
+
return {
|
|
35
|
+
uuid: formData.uuid,
|
|
36
|
+
type: 'set_contact_channel',
|
|
37
|
+
channel: {
|
|
38
|
+
uuid: channel.uuid || channel.value,
|
|
39
|
+
name: channel.name
|
|
40
|
+
}
|
|
41
|
+
};
|
|
30
42
|
}
|
|
31
43
|
};
|
|
@@ -12,11 +12,13 @@ export const set_contact_status: ActionConfig = {
|
|
|
12
12
|
},
|
|
13
13
|
toFormData: (action: SetContactStatus) => {
|
|
14
14
|
return {
|
|
15
|
-
|
|
16
|
-
status:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
uuid: action.uuid,
|
|
16
|
+
status: [
|
|
17
|
+
{
|
|
18
|
+
name: titleCase(action.status || 'active'),
|
|
19
|
+
value: action.status || 'active'
|
|
20
|
+
}
|
|
21
|
+
]
|
|
20
22
|
};
|
|
21
23
|
},
|
|
22
24
|
fromFormData: (formData: FormData): SetContactStatus => {
|
|
@@ -186,7 +186,10 @@ export const start_session: ActionConfig = {
|
|
|
186
186
|
} = {
|
|
187
187
|
uuid: formData.uuid,
|
|
188
188
|
type: 'start_session',
|
|
189
|
-
flow:
|
|
189
|
+
flow: {
|
|
190
|
+
uuid: formData.flow[0].uuid || formData.flow[0].value,
|
|
191
|
+
name: formData.flow[0].name
|
|
192
|
+
},
|
|
190
193
|
groups: [],
|
|
191
194
|
contacts: []
|
|
192
195
|
};
|
|
@@ -202,8 +205,12 @@ export const start_session: ActionConfig = {
|
|
|
202
205
|
} else {
|
|
203
206
|
// Manual selection - separate contacts and groups
|
|
204
207
|
const recipients = formData.recipients || [];
|
|
205
|
-
action.contacts = recipients
|
|
206
|
-
|
|
208
|
+
action.contacts = recipients
|
|
209
|
+
.filter((r: any) => !r.group)
|
|
210
|
+
.map((c: any) => ({ uuid: c.uuid, name: c.name }));
|
|
211
|
+
action.groups = recipients
|
|
212
|
+
.filter((r: any) => r.group)
|
|
213
|
+
.map((g: any) => ({ uuid: g.uuid, name: g.name }));
|
|
207
214
|
}
|
|
208
215
|
|
|
209
216
|
// Add exclusions if set
|
package/src/flow/config.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { remove_contact_groups } from './actions/remove_contact_groups';
|
|
|
20
20
|
import { request_optin } from './actions/request_optin';
|
|
21
21
|
import { say_msg } from './actions/say_msg';
|
|
22
22
|
import { play_audio } from './actions/play_audio';
|
|
23
|
+
import { enter_flow } from './actions/enter_flow';
|
|
23
24
|
|
|
24
25
|
// Import all node configurations
|
|
25
26
|
import { execute_actions } from './nodes/execute_actions';
|
|
@@ -31,11 +32,14 @@ import { split_by_random } from './nodes/split_by_random';
|
|
|
31
32
|
import { split_by_run_result } from './nodes/split_by_run_result';
|
|
32
33
|
import { split_by_scheme } from './nodes/split_by_scheme';
|
|
33
34
|
import { split_by_subflow } from './nodes/split_by_subflow';
|
|
35
|
+
import { terminal } from './nodes/terminal';
|
|
34
36
|
import { split_by_ticket } from './nodes/split_by_ticket';
|
|
35
37
|
import { split_by_webhook } from './nodes/split_by_webhook';
|
|
36
38
|
import { split_by_resthook } from './nodes/split_by_resthook';
|
|
37
39
|
import { split_by_llm } from './nodes/split_by_llm';
|
|
38
40
|
import { split_by_llm_categorize } from './nodes/split_by_llm_categorize';
|
|
41
|
+
import { wait_for_audio } from './nodes/wait_for_audio';
|
|
42
|
+
import { wait_for_dial } from './nodes/wait_for_dial';
|
|
39
43
|
import { wait_for_digits } from './nodes/wait_for_digits';
|
|
40
44
|
import { wait_for_menu } from './nodes/wait_for_menu';
|
|
41
45
|
import { wait_for_response } from './nodes/wait_for_response';
|
|
@@ -59,7 +63,8 @@ export const ACTION_CONFIG: {
|
|
|
59
63
|
set_contact_status,
|
|
60
64
|
add_contact_urn,
|
|
61
65
|
add_input_labels,
|
|
62
|
-
request_optin
|
|
66
|
+
request_optin,
|
|
67
|
+
enter_flow
|
|
63
68
|
});
|
|
64
69
|
|
|
65
70
|
// Helper to register a config and its aliases
|
|
@@ -96,8 +101,11 @@ export const NODE_CONFIG: {
|
|
|
96
101
|
split_by_ticket,
|
|
97
102
|
split_by_webhook,
|
|
98
103
|
split_by_resthook,
|
|
99
|
-
wait_for_digits,
|
|
100
104
|
wait_for_menu,
|
|
105
|
+
wait_for_digits,
|
|
106
|
+
wait_for_audio,
|
|
107
|
+
wait_for_dial,
|
|
101
108
|
wait_for_response,
|
|
102
|
-
split_by_airtime
|
|
109
|
+
split_by_airtime,
|
|
110
|
+
terminal // Temporary: legacy support for terminal nodes (see AppState.ts)
|
|
103
111
|
});
|
|
@@ -38,10 +38,10 @@ const getOperandForField = (field: any): string => {
|
|
|
38
38
|
}
|
|
39
39
|
// For system properties, use the id
|
|
40
40
|
if (field.type === 'property') {
|
|
41
|
-
return `@contact.${field.id}`;
|
|
41
|
+
return `@contact.${field.id || field.value}`;
|
|
42
42
|
}
|
|
43
|
-
//
|
|
44
|
-
return `@fields.${field.key}`;
|
|
43
|
+
// For custom fields, use key with fallbacks
|
|
44
|
+
return `@fields.${field.key || field.id || field.value}`;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
export const split_by_contact_field: NodeConfig = {
|
|
@@ -79,7 +79,7 @@ export const split_by_contact_field: NodeConfig = {
|
|
|
79
79
|
},
|
|
80
80
|
rules: createRulesArrayConfig(
|
|
81
81
|
operatorsToSelectOptions(getWaitForResponseOperators()),
|
|
82
|
-
'
|
|
82
|
+
''
|
|
83
83
|
),
|
|
84
84
|
result_name: resultNameField
|
|
85
85
|
},
|
|
@@ -98,7 +98,18 @@ export const split_by_contact_field: NodeConfig = {
|
|
|
98
98
|
},
|
|
99
99
|
toFormData: (node: Node, nodeUI?: any) => {
|
|
100
100
|
// Get the field from the UI config operand (source of truth)
|
|
101
|
-
const
|
|
101
|
+
const operand = nodeUI?.config?.operand || CONTACT_PROPERTIES.name;
|
|
102
|
+
|
|
103
|
+
// Normalize the field object to include properties expected by
|
|
104
|
+
// the Select component (valueKey: 'key') and getOperandForField.
|
|
105
|
+
// toUIConfig saves only { id, name, type }, so we need to add
|
|
106
|
+
// 'key' for custom fields and 'value' for static option matching.
|
|
107
|
+
const field = { ...operand };
|
|
108
|
+
if (field.type === 'field') {
|
|
109
|
+
if (!field.key) field.key = field.id;
|
|
110
|
+
} else {
|
|
111
|
+
if (!field.value) field.value = field.id;
|
|
112
|
+
}
|
|
102
113
|
|
|
103
114
|
// Extract rules from router cases using shared function
|
|
104
115
|
const rules = casesToFormRules(node);
|
|
@@ -105,7 +105,7 @@ export const split_by_run_result: NodeConfig = {
|
|
|
105
105
|
},
|
|
106
106
|
rules: createRulesArrayConfig(
|
|
107
107
|
operatorsToSelectOptions(getWaitForResponseOperators()),
|
|
108
|
-
'
|
|
108
|
+
''
|
|
109
109
|
),
|
|
110
110
|
result_name: resultNameField
|
|
111
111
|
},
|
|
@@ -135,7 +135,12 @@ export const split_by_run_result: NodeConfig = {
|
|
|
135
135
|
},
|
|
136
136
|
toFormData: (node: Node, nodeUI?: any) => {
|
|
137
137
|
// Get the result from the UI config operand (source of truth)
|
|
138
|
-
|
|
138
|
+
// Normalize: toUIConfig saves { id, name, type } but the Select component
|
|
139
|
+
// (valueKey: 'value') and fromFormData expect a 'value' property.
|
|
140
|
+
const savedOperand = nodeUI?.config?.operand;
|
|
141
|
+
const result = savedOperand
|
|
142
|
+
? { ...savedOperand, value: savedOperand.value || savedOperand.id }
|
|
143
|
+
: null;
|
|
139
144
|
|
|
140
145
|
// Extract rules from router cases using shared function
|
|
141
146
|
const rules = casesToFormRules(node);
|
|
@@ -180,6 +185,7 @@ export const split_by_run_result: NodeConfig = {
|
|
|
180
185
|
|
|
181
186
|
// Build operand based on whether delimiter is selected
|
|
182
187
|
let operand: string;
|
|
188
|
+
const resultKey = selectedResult.value || selectedResult.id;
|
|
183
189
|
const delimitBy = formData.delimit_by?.[0]?.value;
|
|
184
190
|
const hasDelimiter = delimitBy !== undefined && delimitBy !== '';
|
|
185
191
|
|
|
@@ -188,10 +194,10 @@ export const split_by_run_result: NodeConfig = {
|
|
|
188
194
|
const delimitIndex = formData.delimit_index?.[0]?.value ?? '0';
|
|
189
195
|
|
|
190
196
|
// Build operand with field() function
|
|
191
|
-
operand = `@(field(results.${
|
|
197
|
+
operand = `@(field(results.${resultKey}, ${delimitIndex}, "${delimitBy}"))`;
|
|
192
198
|
} else {
|
|
193
199
|
// Standard operand without delimiter
|
|
194
|
-
operand = `@results.${
|
|
200
|
+
operand = `@results.${resultKey}`;
|
|
195
201
|
}
|
|
196
202
|
|
|
197
203
|
// Get user rules using shared extraction function
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Temporary: Legacy support for terminal nodes (nodes with a terminal action
|
|
2
|
+
// like enter_flow with terminal: true). This node type and its reclassification
|
|
3
|
+
// logic in AppState.ts can be removed once we stop supporting terminal nodes.
|
|
4
|
+
|
|
5
|
+
import { NodeConfig } from '../types';
|
|
6
|
+
|
|
7
|
+
export const terminal: NodeConfig = {
|
|
8
|
+
type: 'terminal'
|
|
9
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { SPLIT_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
|
|
2
|
+
import { Node, Category, Exit } from '../../store/flow-definition';
|
|
3
|
+
import { generateUUID } from '../../utils';
|
|
4
|
+
import {
|
|
5
|
+
categoriesToLocalizationFormData,
|
|
6
|
+
localizationFormDataToCategories
|
|
7
|
+
} from './shared';
|
|
8
|
+
|
|
9
|
+
export const wait_for_audio: NodeConfig = {
|
|
10
|
+
type: 'wait_for_audio',
|
|
11
|
+
name: 'Make Recording',
|
|
12
|
+
group: SPLIT_GROUPS.wait,
|
|
13
|
+
flowTypes: [FlowTypes.VOICE],
|
|
14
|
+
form: {
|
|
15
|
+
result_name: {
|
|
16
|
+
type: 'text',
|
|
17
|
+
label: 'Result Name',
|
|
18
|
+
required: false,
|
|
19
|
+
placeholder: '(optional)',
|
|
20
|
+
helpText: 'The name to use to reference this result in the flow'
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
layout: ['result_name'],
|
|
24
|
+
toFormData: (node: Node) => {
|
|
25
|
+
return {
|
|
26
|
+
uuid: node.uuid,
|
|
27
|
+
result_name: node.router?.result_name || ''
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
fromFormData: (formData: FormData, originalNode: Node): Node => {
|
|
31
|
+
// Preserve or create "All Responses" category
|
|
32
|
+
const existingCategories = originalNode.router?.categories || [];
|
|
33
|
+
const existingExits = originalNode.exits || [];
|
|
34
|
+
|
|
35
|
+
let allResponsesCategory = existingCategories.find(
|
|
36
|
+
(cat: Category) => cat.name === 'All Responses'
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
let allResponsesExit: Exit;
|
|
40
|
+
|
|
41
|
+
if (allResponsesCategory) {
|
|
42
|
+
allResponsesExit = existingExits.find(
|
|
43
|
+
(exit: Exit) => exit.uuid === allResponsesCategory!.exit_uuid
|
|
44
|
+
) || {
|
|
45
|
+
uuid: allResponsesCategory.exit_uuid,
|
|
46
|
+
destination_uuid: null
|
|
47
|
+
};
|
|
48
|
+
} else {
|
|
49
|
+
const exitUuid = generateUUID();
|
|
50
|
+
allResponsesCategory = {
|
|
51
|
+
uuid: generateUUID(),
|
|
52
|
+
name: 'All Responses',
|
|
53
|
+
exit_uuid: exitUuid
|
|
54
|
+
};
|
|
55
|
+
allResponsesExit = {
|
|
56
|
+
uuid: exitUuid,
|
|
57
|
+
destination_uuid: null
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const router: any = {
|
|
62
|
+
type: 'switch',
|
|
63
|
+
operand: '@input',
|
|
64
|
+
default_category_uuid: allResponsesCategory.uuid,
|
|
65
|
+
cases: [],
|
|
66
|
+
categories: [allResponsesCategory],
|
|
67
|
+
wait: {
|
|
68
|
+
type: 'msg',
|
|
69
|
+
hint: {
|
|
70
|
+
type: 'audio'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (formData.result_name && formData.result_name.trim() !== '') {
|
|
76
|
+
router.result_name = formData.result_name.trim();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
...originalNode,
|
|
81
|
+
router,
|
|
82
|
+
exits: [allResponsesExit]
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
localizable: 'categories',
|
|
86
|
+
toLocalizationFormData: categoriesToLocalizationFormData,
|
|
87
|
+
fromLocalizationFormData: localizationFormDataToCategories
|
|
88
|
+
};
|