@nyaruka/temba-components 0.129.7 → 0.129.9
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/.devcontainer/Dockerfile +11 -4
- package/.devcontainer/devcontainer.json +3 -2
- package/.github/workflows/build.yml +4 -14
- package/CHANGELOG.md +29 -0
- package/demo/components/flow/example.html +1 -1
- package/demo/components/message-editor/example.html +125 -0
- package/demo/components/textinput/completion.html +1 -0
- package/demo/data/flows/food-order.json +12 -21
- package/demo/data/flows/sample-flow.json +210 -104
- package/dist/temba-components.js +715 -364
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Thumbnail.js +2 -1
- package/out-tsc/src/display/Thumbnail.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/excellent/helpers.js +2 -2
- package/out-tsc/src/excellent/helpers.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +25 -7
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +11 -1
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +342 -276
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +40 -0
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/call_llm.js +56 -3
- package/out-tsc/src/flow/actions/call_llm.js.map +1 -1
- package/out-tsc/src/flow/actions/call_webhook.js +26 -17
- package/out-tsc/src/flow/actions/call_webhook.js.map +1 -1
- package/out-tsc/src/flow/actions/open_ticket.js +65 -3
- package/out-tsc/src/flow/actions/open_ticket.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +147 -6
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +75 -0
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/config.js +4 -0
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +227 -0
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_ticket.js +18 -0
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_response.js +27 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/types.js +0 -65
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +87 -57
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/BaseListEditor.js +19 -4
- package/out-tsc/src/form/BaseListEditor.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +305 -0
- package/out-tsc/src/form/FieldRenderer.js.map +1 -0
- package/out-tsc/src/form/FormField.js +4 -4
- package/out-tsc/src/form/FormField.js.map +1 -1
- package/out-tsc/src/form/KeyValueEditor.js +1 -1
- package/out-tsc/src/form/KeyValueEditor.js.map +1 -1
- package/out-tsc/src/form/MediaPicker.js +13 -1
- package/out-tsc/src/form/MediaPicker.js.map +1 -1
- package/out-tsc/src/form/MessageEditor.js +422 -0
- package/out-tsc/src/form/MessageEditor.js.map +1 -0
- package/out-tsc/src/form/TextInput.js +13 -6
- package/out-tsc/src/form/TextInput.js.map +1 -1
- package/out-tsc/src/form/select/Select.js +52 -24
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +66 -15
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/markdown.js +13 -11
- package/out-tsc/src/markdown.js.map +1 -1
- package/out-tsc/temba-modules.js +2 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +2 -0
- package/out-tsc/test/ActionHelper.js.map +1 -1
- package/out-tsc/test/NodeHelper.js +148 -0
- package/out-tsc/test/NodeHelper.js.map +1 -0
- package/out-tsc/test/actions/call_llm.test.js +103 -0
- package/out-tsc/test/actions/call_llm.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_llm_categorize.test.js +532 -0
- package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_random.test.js +150 -0
- package/out-tsc/test/nodes/split_by_random.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +150 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_response.test.js +171 -0
- package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -0
- package/out-tsc/test/temba-add-input-labels.test.js +70 -0
- package/out-tsc/test/temba-add-input-labels.test.js.map +1 -0
- package/out-tsc/test/temba-field-config.test.js +4 -2
- package/out-tsc/test/temba-field-config.test.js.map +1 -1
- package/out-tsc/test/temba-field-renderer.test.js +296 -0
- package/out-tsc/test/temba-field-renderer.test.js.map +1 -0
- package/out-tsc/test/temba-markdown.test.js +1 -1
- package/out-tsc/test/temba-markdown.test.js.map +1 -1
- package/out-tsc/test/temba-message-editor.test.js +194 -0
- package/out-tsc/test/temba-message-editor.test.js.map +1 -0
- package/out-tsc/test/temba-node-editor.test.js +471 -0
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +7 -4
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/temba-textinput.test.js +16 -0
- package/out-tsc/test/temba-textinput.test.js.map +1 -1
- package/out-tsc/test/temba-webchat.test.js +5 -1
- package/out-tsc/test/temba-webchat.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +2 -8
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +7 -4
- package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
- package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
- package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/send_msg.png +0 -0
- package/screenshots/truth/editor/set_contact_language.png +0 -0
- package/screenshots/truth/editor/set_contact_name.png +0 -0
- package/screenshots/truth/editor/set_run_result.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
- package/screenshots/truth/field-renderer/context-comparison.png +0 -0
- package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
- package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
- package/screenshots/truth/field-renderer/select-multi.png +0 -0
- package/screenshots/truth/field-renderer/select-no-label.png +0 -0
- package/screenshots/truth/field-renderer/select-with-label.png +0 -0
- package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
- package/screenshots/truth/field-renderer/text-no-label.png +0 -0
- package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
- package/screenshots/truth/field-renderer/text-with-label.png +0 -0
- package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
- package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
- package/screenshots/truth/formfield/markdown-errors.png +0 -0
- package/screenshots/truth/formfield/no-errors.png +0 -0
- package/screenshots/truth/formfield/plain-text-errors.png +0 -0
- package/screenshots/truth/message-editor/autogrow-initial-content.png +0 -0
- package/screenshots/truth/message-editor/default.png +0 -0
- package/screenshots/truth/message-editor/drag-highlight.png +0 -0
- package/screenshots/truth/message-editor/filtered-attachments.png +0 -0
- package/screenshots/truth/message-editor/with-completion.png +0 -0
- package/screenshots/truth/message-editor/with-properties.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_digits/editor/basic-digits-wait.png +0 -0
- 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/basic-digits-wait.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
- 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/screenshots/truth/omnibox/selected.png +0 -0
- package/screenshots/truth/select/functions.png +0 -0
- package/screenshots/truth/select/multi-with-endpoint.png +0 -0
- package/screenshots/truth/select/search-enabled.png +0 -0
- package/screenshots/truth/textinput/autogrow-initial.png +0 -0
- package/screenshots/truth/textinput/input-form.png +0 -0
- package/src/display/Thumbnail.ts +2 -1
- package/src/events.ts +13 -1
- package/src/excellent/helpers.ts +2 -2
- package/src/flow/CanvasNode.ts +22 -1
- package/src/flow/Editor.ts +12 -1
- package/src/flow/NodeEditor.ts +412 -354
- package/src/flow/actions/add_input_labels.ts +45 -0
- package/src/flow/actions/call_llm.ts +57 -3
- package/src/flow/actions/call_webhook.ts +28 -18
- package/src/flow/actions/open_ticket.ts +74 -3
- package/src/flow/actions/send_msg.ts +170 -6
- package/src/flow/actions/set_run_result.ts +83 -0
- package/src/flow/config.ts +4 -0
- package/src/flow/nodes/split_by_llm_categorize.ts +277 -0
- package/src/flow/nodes/split_by_ticket.ts +19 -0
- package/src/flow/nodes/wait_for_response.ts +28 -1
- package/src/flow/types.ts +46 -128
- package/src/form/ArrayEditor.ts +96 -66
- package/src/form/BaseListEditor.ts +22 -6
- package/src/form/FieldRenderer.ts +465 -0
- package/src/form/FormField.ts +4 -4
- package/src/form/KeyValueEditor.ts +1 -1
- package/src/form/MediaPicker.ts +13 -1
- package/src/form/MessageEditor.ts +449 -0
- package/src/form/TextInput.ts +16 -8
- package/src/form/select/Select.ts +55 -24
- package/src/live/ContactChat.ts +69 -19
- package/src/markdown.ts +19 -11
- package/src/store/flow-definition.d.ts +5 -2
- package/static/api/labels.json +31 -0
- package/static/api/topics.json +24 -9
- package/static/api/users.json +35 -16
- package/static/css/temba-components.css +5 -3
- package/static/mr/docs/en-us/editor.json +2588 -0
- package/stress-test.js +143 -0
- package/temba-modules.ts +2 -0
- package/test/ActionHelper.ts +2 -0
- package/test/NodeHelper.ts +184 -0
- package/test/actions/call_llm.test.ts +137 -0
- package/test/nodes/README.md +78 -0
- package/test/nodes/split_by_llm_categorize.test.ts +698 -0
- package/test/nodes/split_by_random.test.ts +177 -0
- package/test/nodes/wait_for_digits.test.ts +176 -0
- package/test/nodes/wait_for_response.test.ts +206 -0
- package/test/temba-add-input-labels.test.ts +87 -0
- package/test/temba-field-config.test.ts +4 -2
- package/test/temba-field-renderer.test.ts +482 -0
- package/test/temba-markdown.test.ts +1 -1
- package/test/temba-message-editor.test.ts +300 -0
- package/test/temba-node-editor.test.ts +590 -0
- package/test/temba-select.test.ts +7 -7
- package/test/temba-textinput.test.ts +26 -0
- package/test/temba-webchat.test.ts +6 -1
- package/test/utils.test.ts +2 -13
- package/test-assets/contacts/history.json +19 -0
- package/test-assets/select/llms.json +18 -0
- package/test-assets/style.css +2 -0
- package/web-dev-mock.mjs +523 -0
- package/web-dev-server.config.mjs +74 -6
- package/web-test-runner.config.mjs +9 -4
- package/test/temba-flow-editor.test.ts.backup +0 -563
- package/test/temba-utils-index.test.ts.backup +0 -1737
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { COLORS, NodeConfig } from '../types';
|
|
2
|
+
import { Node } from '../../store/flow-definition';
|
|
3
|
+
import { generateUUID } from '../../utils';
|
|
4
|
+
import { html } from 'lit';
|
|
5
|
+
|
|
6
|
+
export const split_by_llm_categorize: NodeConfig = {
|
|
7
|
+
type: 'split_by_llm_categorize',
|
|
8
|
+
name: 'Split by AI',
|
|
9
|
+
color: COLORS.call,
|
|
10
|
+
form: {
|
|
11
|
+
llm: {
|
|
12
|
+
type: 'select',
|
|
13
|
+
label: 'LLM',
|
|
14
|
+
helpText: 'Select the LLM to use for categorization',
|
|
15
|
+
required: true,
|
|
16
|
+
endpoint: '/test-assets/select/llms.json',
|
|
17
|
+
valueKey: 'uuid',
|
|
18
|
+
nameKey: 'name',
|
|
19
|
+
placeholder: 'Select an LLM...'
|
|
20
|
+
},
|
|
21
|
+
input: {
|
|
22
|
+
type: 'text',
|
|
23
|
+
label: 'Input',
|
|
24
|
+
helpText: 'The input to categorize (usually @input)',
|
|
25
|
+
required: true,
|
|
26
|
+
evaluated: true,
|
|
27
|
+
placeholder: '@input'
|
|
28
|
+
},
|
|
29
|
+
categories: {
|
|
30
|
+
type: 'array',
|
|
31
|
+
label: 'Categories',
|
|
32
|
+
helpText: 'Define the categories for classification',
|
|
33
|
+
required: true,
|
|
34
|
+
itemLabel: 'Category',
|
|
35
|
+
minItems: 1,
|
|
36
|
+
maxItems: 10,
|
|
37
|
+
isEmptyItem: (item: any) => {
|
|
38
|
+
return !item.name || item.name.trim() === '';
|
|
39
|
+
},
|
|
40
|
+
itemConfig: {
|
|
41
|
+
name: {
|
|
42
|
+
type: 'text',
|
|
43
|
+
placeholder: 'Category name',
|
|
44
|
+
required: true
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
layout: ['llm', 'input', 'categories'],
|
|
50
|
+
validate: (formData: any) => {
|
|
51
|
+
const errors: { [key: string]: string } = {};
|
|
52
|
+
|
|
53
|
+
// Check for duplicate category names
|
|
54
|
+
if (formData.categories && Array.isArray(formData.categories)) {
|
|
55
|
+
const categories = formData.categories.filter(
|
|
56
|
+
(item: any) => item?.name && item.name.trim() !== ''
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Find all categories that have duplicates (case-insensitive)
|
|
60
|
+
const duplicateCategories = [];
|
|
61
|
+
const lowerCaseMap = new Map();
|
|
62
|
+
|
|
63
|
+
// First pass: map lowercase names to all original cases
|
|
64
|
+
categories.forEach((category) => {
|
|
65
|
+
const lowerName = category.name.trim().toLowerCase();
|
|
66
|
+
if (!lowerCaseMap.has(lowerName)) {
|
|
67
|
+
lowerCaseMap.set(lowerName, []);
|
|
68
|
+
}
|
|
69
|
+
lowerCaseMap.get(lowerName).push(category.name.trim());
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Second pass: collect all names that appear more than once
|
|
73
|
+
lowerCaseMap.forEach((originalNames) => {
|
|
74
|
+
if (originalNames.length > 1) {
|
|
75
|
+
duplicateCategories.push(...originalNames);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (duplicateCategories.length > 0) {
|
|
80
|
+
const uniqueDuplicates = [...new Set(duplicateCategories)];
|
|
81
|
+
errors.categories = `Duplicate category names found: ${uniqueDuplicates.join(
|
|
82
|
+
', '
|
|
83
|
+
)}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
valid: Object.keys(errors).length === 0,
|
|
89
|
+
errors
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
render: (node: Node) => {
|
|
93
|
+
const callLlmAction = node.actions?.find(
|
|
94
|
+
(action) => action.type === 'call_llm'
|
|
95
|
+
) as any;
|
|
96
|
+
return html`
|
|
97
|
+
<div class="body">Categorize with ${callLlmAction.llm.name}</div>
|
|
98
|
+
`;
|
|
99
|
+
},
|
|
100
|
+
toFormData: (node: Node) => {
|
|
101
|
+
// Extract data from the existing node structure
|
|
102
|
+
const callLlmAction = node.actions?.find(
|
|
103
|
+
(action) => action.type === 'call_llm'
|
|
104
|
+
) as any;
|
|
105
|
+
const categories =
|
|
106
|
+
node.router?.categories
|
|
107
|
+
?.filter((cat) => cat.name !== 'Other' && cat.name !== 'Failure')
|
|
108
|
+
.map((cat) => ({ name: cat.name })) || [];
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
uuid: node.uuid,
|
|
112
|
+
llm: callLlmAction?.llm
|
|
113
|
+
? [{ value: callLlmAction.llm.uuid, name: callLlmAction.llm.name }]
|
|
114
|
+
: [],
|
|
115
|
+
input: callLlmAction?.input || '@input',
|
|
116
|
+
categories: categories
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
fromFormData: (formData: any, originalNode: Node): Node => {
|
|
120
|
+
// Get LLM selection
|
|
121
|
+
const llmSelection =
|
|
122
|
+
Array.isArray(formData.llm) && formData.llm.length > 0
|
|
123
|
+
? formData.llm[0]
|
|
124
|
+
: null;
|
|
125
|
+
|
|
126
|
+
// Get user categories
|
|
127
|
+
const userCategories = (formData.categories || [])
|
|
128
|
+
.filter((item: any) => item?.name?.trim())
|
|
129
|
+
.map((item: any) => item.name.trim());
|
|
130
|
+
|
|
131
|
+
// Find existing call_llm action to preserve its UUID
|
|
132
|
+
const existingCallLlmAction = originalNode.actions?.find(
|
|
133
|
+
(action) => action.type === 'call_llm'
|
|
134
|
+
);
|
|
135
|
+
const callLlmUuid = existingCallLlmAction?.uuid || generateUUID();
|
|
136
|
+
|
|
137
|
+
// Create call_llm action (using any type to match the example format)
|
|
138
|
+
const callLlmAction: any = {
|
|
139
|
+
type: 'call_llm',
|
|
140
|
+
uuid: callLlmUuid,
|
|
141
|
+
llm: llmSelection
|
|
142
|
+
? { uuid: llmSelection.value, name: llmSelection.name }
|
|
143
|
+
: { uuid: '', name: '' },
|
|
144
|
+
instructions: `@(prompt("categorize", slice(node.categories, 0, -2)))`,
|
|
145
|
+
input: formData.input || '@input',
|
|
146
|
+
output_local: '_llm_output'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Create categories and exits
|
|
150
|
+
const categories = [];
|
|
151
|
+
const exits = [];
|
|
152
|
+
const cases = [];
|
|
153
|
+
|
|
154
|
+
// Get existing categories from original node for UUID preservation
|
|
155
|
+
const existingCategories = originalNode.router?.categories || [];
|
|
156
|
+
const existingExits = originalNode.exits || [];
|
|
157
|
+
const existingCases = originalNode.router?.cases || [];
|
|
158
|
+
|
|
159
|
+
// Add user categories
|
|
160
|
+
userCategories.forEach((categoryName: string) => {
|
|
161
|
+
// Check if this category already exists
|
|
162
|
+
const existingCategory = existingCategories.find(
|
|
163
|
+
(cat) => cat.name === categoryName
|
|
164
|
+
);
|
|
165
|
+
const existingExit = existingCategory
|
|
166
|
+
? existingExits.find((exit) => exit.uuid === existingCategory.exit_uuid)
|
|
167
|
+
: null;
|
|
168
|
+
const existingCase = existingCategory
|
|
169
|
+
? existingCases.find(
|
|
170
|
+
(case_) => case_.category_uuid === existingCategory.uuid
|
|
171
|
+
)
|
|
172
|
+
: null;
|
|
173
|
+
|
|
174
|
+
// Use existing UUIDs if category name hasn't changed, otherwise generate new ones
|
|
175
|
+
const categoryUuid = existingCategory?.uuid || generateUUID();
|
|
176
|
+
const exitUuid = existingExit?.uuid || generateUUID();
|
|
177
|
+
const caseUuid = existingCase?.uuid || generateUUID();
|
|
178
|
+
|
|
179
|
+
categories.push({
|
|
180
|
+
uuid: categoryUuid,
|
|
181
|
+
name: categoryName,
|
|
182
|
+
exit_uuid: exitUuid
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
exits.push({
|
|
186
|
+
uuid: exitUuid,
|
|
187
|
+
destination_uuid: existingExit?.destination_uuid || null
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
cases.push({
|
|
191
|
+
uuid: caseUuid,
|
|
192
|
+
type: 'has_only_text',
|
|
193
|
+
arguments: [categoryName],
|
|
194
|
+
category_uuid: categoryUuid
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Add "Other" category (default)
|
|
199
|
+
const existingOtherCategory = existingCategories.find(
|
|
200
|
+
(cat) => cat.name === 'Other'
|
|
201
|
+
);
|
|
202
|
+
const existingOtherExit = existingOtherCategory
|
|
203
|
+
? existingExits.find(
|
|
204
|
+
(exit) => exit.uuid === existingOtherCategory.exit_uuid
|
|
205
|
+
)
|
|
206
|
+
: null;
|
|
207
|
+
|
|
208
|
+
const otherCategoryUuid = existingOtherCategory?.uuid || generateUUID();
|
|
209
|
+
const otherExitUuid = existingOtherExit?.uuid || generateUUID();
|
|
210
|
+
|
|
211
|
+
categories.push({
|
|
212
|
+
uuid: otherCategoryUuid,
|
|
213
|
+
name: 'Other',
|
|
214
|
+
exit_uuid: otherExitUuid
|
|
215
|
+
});
|
|
216
|
+
exits.push({
|
|
217
|
+
uuid: otherExitUuid,
|
|
218
|
+
destination_uuid: existingOtherExit?.destination_uuid || null
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Add "Failure" category
|
|
222
|
+
const existingFailureCategory = existingCategories.find(
|
|
223
|
+
(cat) => cat.name === 'Failure'
|
|
224
|
+
);
|
|
225
|
+
const existingFailureExit = existingFailureCategory
|
|
226
|
+
? existingExits.find(
|
|
227
|
+
(exit) => exit.uuid === existingFailureCategory.exit_uuid
|
|
228
|
+
)
|
|
229
|
+
: null;
|
|
230
|
+
const existingFailureCase = existingFailureCategory
|
|
231
|
+
? existingCases.find(
|
|
232
|
+
(case_) =>
|
|
233
|
+
case_.category_uuid === existingFailureCategory.uuid &&
|
|
234
|
+
case_.arguments?.[0] === '<ERROR>'
|
|
235
|
+
)
|
|
236
|
+
: null;
|
|
237
|
+
|
|
238
|
+
const failureCategoryUuid = existingFailureCategory?.uuid || generateUUID();
|
|
239
|
+
const failureExitUuid = existingFailureExit?.uuid || generateUUID();
|
|
240
|
+
const failureCaseUuid = existingFailureCase?.uuid || generateUUID();
|
|
241
|
+
|
|
242
|
+
categories.push({
|
|
243
|
+
uuid: failureCategoryUuid,
|
|
244
|
+
name: 'Failure',
|
|
245
|
+
exit_uuid: failureExitUuid
|
|
246
|
+
});
|
|
247
|
+
exits.push({
|
|
248
|
+
uuid: failureExitUuid,
|
|
249
|
+
destination_uuid: existingFailureExit?.destination_uuid || null
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Add failure case for <ERROR>
|
|
253
|
+
cases.push({
|
|
254
|
+
uuid: failureCaseUuid,
|
|
255
|
+
type: 'has_only_text',
|
|
256
|
+
arguments: ['<ERROR>'],
|
|
257
|
+
category_uuid: failureCategoryUuid
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Create the router
|
|
261
|
+
const router = {
|
|
262
|
+
type: 'switch' as const,
|
|
263
|
+
categories: categories,
|
|
264
|
+
default_category_uuid: otherCategoryUuid,
|
|
265
|
+
operand: '@locals._llm_output',
|
|
266
|
+
cases: cases
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// Return the complete node
|
|
270
|
+
return {
|
|
271
|
+
uuid: originalNode.uuid,
|
|
272
|
+
actions: [callLlmAction],
|
|
273
|
+
router: router,
|
|
274
|
+
exits: exits
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { open_ticket } from '../actions/open_ticket';
|
|
2
|
+
import { NodeConfig } from '../types';
|
|
3
|
+
|
|
4
|
+
export const split_by_ticket: NodeConfig = {
|
|
5
|
+
type: 'split_by_ticket',
|
|
6
|
+
action: open_ticket,
|
|
7
|
+
router: {
|
|
8
|
+
type: 'switch',
|
|
9
|
+
defaultCategory: 'Failure',
|
|
10
|
+
operand: '@locals._new_ticket',
|
|
11
|
+
rules: [
|
|
12
|
+
{
|
|
13
|
+
type: 'has_text',
|
|
14
|
+
arguments: [],
|
|
15
|
+
categoryName: 'Success'
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
import { COLORS, NodeConfig } from '../types';
|
|
2
|
+
import { Node } from '../../store/flow-definition';
|
|
2
3
|
|
|
3
4
|
export const wait_for_response: NodeConfig = {
|
|
4
5
|
type: 'wait_for_response',
|
|
5
6
|
name: 'Wait for Response',
|
|
6
|
-
color: COLORS.wait
|
|
7
|
+
color: COLORS.wait,
|
|
8
|
+
form: {
|
|
9
|
+
result_name: {
|
|
10
|
+
type: 'text',
|
|
11
|
+
label: 'Result Name',
|
|
12
|
+
helpText: 'The name to save the response as',
|
|
13
|
+
placeholder: 'response'
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
layout: ['timeout', 'result_name'],
|
|
17
|
+
toFormData: (node: Node) => {
|
|
18
|
+
return {
|
|
19
|
+
uuid: node.uuid,
|
|
20
|
+
result_name: node.router?.result_name || 'response'
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
fromFormData: (formData: any, originalNode: Node): Node => {
|
|
24
|
+
const router: any = {
|
|
25
|
+
...originalNode.router,
|
|
26
|
+
result_name: formData.result_name || 'response'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
...originalNode,
|
|
31
|
+
router
|
|
32
|
+
};
|
|
33
|
+
}
|
|
7
34
|
};
|
package/src/flow/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TemplateResult } from 'lit-html';
|
|
2
|
-
import { Action } from '../store/flow-definition';
|
|
2
|
+
import { Action, Node } from '../store/flow-definition';
|
|
3
3
|
|
|
4
4
|
export interface ValidationResult {
|
|
5
5
|
valid: boolean;
|
|
@@ -65,39 +65,16 @@ export interface SliderAttributes {
|
|
|
65
65
|
range?: boolean;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
export type WidgetConfig =
|
|
70
|
-
| { type: 'temba-textinput'; attributes?: TextInputAttributes }
|
|
71
|
-
| { type: 'temba-completion'; attributes?: CompletionAttributes }
|
|
72
|
-
| { type: 'temba-select'; attributes?: SelectAttributes }
|
|
73
|
-
| { type: 'temba-checkbox'; attributes?: CheckboxAttributes }
|
|
74
|
-
| { type: 'temba-slider'; attributes?: SliderAttributes }
|
|
75
|
-
| { type: string; attributes?: { [key: string]: any } }; // Generic fallback
|
|
68
|
+
export interface FormData extends Record<string, any> {}
|
|
76
69
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
required?: boolean;
|
|
83
|
-
maxLength?: number;
|
|
84
|
-
minLength?: number;
|
|
85
|
-
pattern?: string;
|
|
86
|
-
|
|
87
|
-
// Widget configuration
|
|
88
|
-
widget: WidgetConfig;
|
|
89
|
-
|
|
90
|
-
// Conditional behavior based on other field values
|
|
91
|
-
conditions?: {
|
|
92
|
-
// When to show this field
|
|
93
|
-
visible?: (formData: any) => boolean;
|
|
94
|
-
|
|
95
|
-
// When this field is disabled
|
|
96
|
-
disabled?: (formData: any) => boolean;
|
|
97
|
-
};
|
|
70
|
+
export interface FormConfig {
|
|
71
|
+
form?: Record<string, FieldConfig>;
|
|
72
|
+
layout?: LayoutItem[];
|
|
73
|
+
sanitize?: (formData: FormData) => void;
|
|
74
|
+
validate?: (formData: FormData) => ValidationResult;
|
|
98
75
|
}
|
|
99
76
|
|
|
100
|
-
export interface NodeConfig {
|
|
77
|
+
export interface NodeConfig extends FormConfig {
|
|
101
78
|
type: string;
|
|
102
79
|
name?: string;
|
|
103
80
|
color?: string;
|
|
@@ -108,22 +85,28 @@ export interface NodeConfig {
|
|
|
108
85
|
operand?: string;
|
|
109
86
|
configurable?: boolean; // can the rules be configured in the UI
|
|
110
87
|
rules?: {
|
|
111
|
-
type:
|
|
88
|
+
type:
|
|
89
|
+
| 'has_number_between'
|
|
90
|
+
| 'has_string'
|
|
91
|
+
| 'has_value'
|
|
92
|
+
| 'has_not_value'
|
|
93
|
+
| 'has_text';
|
|
112
94
|
arguments: string[];
|
|
113
95
|
categoryName: string;
|
|
114
96
|
}[];
|
|
115
97
|
};
|
|
116
|
-
|
|
117
|
-
toFormData?: (node:
|
|
118
|
-
fromFormData?: (formData:
|
|
98
|
+
|
|
99
|
+
toFormData?: (node: Node) => FormData;
|
|
100
|
+
fromFormData?: (formData: FormData, originalNode: Node) => Node;
|
|
101
|
+
render?: (node: Node) => TemplateResult;
|
|
119
102
|
}
|
|
120
103
|
|
|
121
104
|
// New field configuration system for generic form generation
|
|
122
105
|
export interface BaseFieldConfig {
|
|
123
106
|
label?: string;
|
|
124
107
|
required?: boolean;
|
|
125
|
-
evaluated?: boolean;
|
|
126
|
-
dependsOn?: string[];
|
|
108
|
+
evaluated?: boolean;
|
|
109
|
+
dependsOn?: string[];
|
|
127
110
|
computeValue?: (
|
|
128
111
|
values: Record<string, any>,
|
|
129
112
|
currentValue: any,
|
|
@@ -137,7 +120,7 @@ export interface BaseFieldConfig {
|
|
|
137
120
|
helpText?: string;
|
|
138
121
|
|
|
139
122
|
// Layout properties
|
|
140
|
-
maxWidth?: string;
|
|
123
|
+
maxWidth?: string;
|
|
141
124
|
|
|
142
125
|
// Conditional rendering
|
|
143
126
|
conditions?: {
|
|
@@ -160,8 +143,9 @@ export interface TextareaFieldConfig extends BaseFieldConfig {
|
|
|
160
143
|
|
|
161
144
|
export interface SelectFieldConfig extends BaseFieldConfig {
|
|
162
145
|
type: 'select';
|
|
163
|
-
options
|
|
146
|
+
options?: string[] | { value: string; label: string }[];
|
|
164
147
|
multi?: boolean;
|
|
148
|
+
clearable?: boolean;
|
|
165
149
|
searchable?: boolean;
|
|
166
150
|
tags?: boolean;
|
|
167
151
|
placeholder?: string;
|
|
@@ -170,6 +154,10 @@ export interface SelectFieldConfig extends BaseFieldConfig {
|
|
|
170
154
|
nameKey?: string;
|
|
171
155
|
endpoint?: string;
|
|
172
156
|
emails?: boolean;
|
|
157
|
+
getName?: (item: any) => string;
|
|
158
|
+
flavor?: 'small' | 'large';
|
|
159
|
+
createArbitraryOption?: (input: string, options: any[]) => any;
|
|
160
|
+
allowCreate?: boolean;
|
|
173
161
|
}
|
|
174
162
|
|
|
175
163
|
export interface KeyValueFieldConfig extends BaseFieldConfig {
|
|
@@ -193,6 +181,7 @@ export interface ArrayFieldConfig extends BaseFieldConfig {
|
|
|
193
181
|
value: any,
|
|
194
182
|
allItems: any[]
|
|
195
183
|
) => any[];
|
|
184
|
+
isEmptyItem?: (item: any) => boolean;
|
|
196
185
|
}
|
|
197
186
|
|
|
198
187
|
export interface CheckboxFieldConfig extends BaseFieldConfig {
|
|
@@ -201,13 +190,27 @@ export interface CheckboxFieldConfig extends BaseFieldConfig {
|
|
|
201
190
|
animateChange?: string;
|
|
202
191
|
}
|
|
203
192
|
|
|
193
|
+
export interface MessageEditorFieldConfig extends BaseFieldConfig {
|
|
194
|
+
type: 'message-editor';
|
|
195
|
+
placeholder?: string;
|
|
196
|
+
minHeight?: number;
|
|
197
|
+
maxAttachments?: number;
|
|
198
|
+
accept?: string;
|
|
199
|
+
endpoint?: string;
|
|
200
|
+
counter?: string;
|
|
201
|
+
gsm?: boolean;
|
|
202
|
+
autogrow?: boolean;
|
|
203
|
+
disableCompletion?: boolean;
|
|
204
|
+
}
|
|
205
|
+
|
|
204
206
|
export type FieldConfig =
|
|
205
207
|
| TextFieldConfig
|
|
206
208
|
| TextareaFieldConfig
|
|
207
209
|
| SelectFieldConfig
|
|
208
210
|
| KeyValueFieldConfig
|
|
209
211
|
| ArrayFieldConfig
|
|
210
|
-
| CheckboxFieldConfig
|
|
212
|
+
| CheckboxFieldConfig
|
|
213
|
+
| MessageEditorFieldConfig;
|
|
211
214
|
|
|
212
215
|
// Layout configurations for better form organization
|
|
213
216
|
// Recursive layout system - any layout item can contain other layout items
|
|
@@ -228,8 +231,9 @@ export interface GroupLayoutConfig {
|
|
|
228
231
|
label: string;
|
|
229
232
|
items: LayoutItem[]; // can contain fields, rows, or other groups
|
|
230
233
|
collapsible?: boolean;
|
|
231
|
-
collapsed?: boolean; // initial state if collapsible
|
|
234
|
+
collapsed?: boolean | ((formData: any) => boolean); // initial state if collapsible - can be a function
|
|
232
235
|
helpText?: string;
|
|
236
|
+
getGroupValueCount?: (formData: any) => number; // optional function to get count for bubble display
|
|
233
237
|
}
|
|
234
238
|
|
|
235
239
|
export type LayoutItem =
|
|
@@ -238,7 +242,7 @@ export type LayoutItem =
|
|
|
238
242
|
| GroupLayoutConfig
|
|
239
243
|
| string; // string is shorthand for field
|
|
240
244
|
|
|
241
|
-
export interface ActionConfig {
|
|
245
|
+
export interface ActionConfig extends FormConfig {
|
|
242
246
|
name: string;
|
|
243
247
|
color: string;
|
|
244
248
|
evaluated?: string[];
|
|
@@ -247,12 +251,8 @@ export interface ActionConfig {
|
|
|
247
251
|
form?: Record<string, FieldConfig>;
|
|
248
252
|
layout?: LayoutItem[]; // optional layout configuration - array of layout items
|
|
249
253
|
|
|
250
|
-
// Action editor configuration (legacy)
|
|
251
|
-
// Form-level transformations
|
|
252
254
|
toFormData?: (action: Action) => any;
|
|
253
255
|
fromFormData?: (formData: any) => Action;
|
|
254
|
-
|
|
255
|
-
validate?: (action: Action) => ValidationResult;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
export const COLORS = {
|
|
@@ -268,85 +268,3 @@ export const COLORS = {
|
|
|
268
268
|
add: '#309c42',
|
|
269
269
|
remove: '#e74c3c'
|
|
270
270
|
};
|
|
271
|
-
|
|
272
|
-
// Default property type mappings
|
|
273
|
-
export function getDefaultComponent(value: any): WidgetConfig['type'] {
|
|
274
|
-
if (typeof value === 'boolean') {
|
|
275
|
-
return 'temba-checkbox';
|
|
276
|
-
}
|
|
277
|
-
if (typeof value === 'number') {
|
|
278
|
-
return 'temba-textinput';
|
|
279
|
-
}
|
|
280
|
-
if (Array.isArray(value)) {
|
|
281
|
-
return 'temba-select'; // For arrays, use multi-select
|
|
282
|
-
}
|
|
283
|
-
// Default to text input for strings and unknown types
|
|
284
|
-
return 'temba-textinput';
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Get component properties for default mappings with proper typing
|
|
288
|
-
export function getDefaultComponentProps(value: any): PropertyConfig {
|
|
289
|
-
if (typeof value === 'boolean') {
|
|
290
|
-
return {
|
|
291
|
-
widget: { type: 'temba-checkbox' }
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
if (typeof value === 'number') {
|
|
295
|
-
return {
|
|
296
|
-
widget: {
|
|
297
|
-
type: 'temba-textinput',
|
|
298
|
-
attributes: { type: 'number' }
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
if (Array.isArray(value)) {
|
|
303
|
-
if (value.length > 0 && typeof value[0] === 'string') {
|
|
304
|
-
return {
|
|
305
|
-
widget: {
|
|
306
|
-
type: 'temba-select',
|
|
307
|
-
attributes: { multi: true, tags: true }
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
return {
|
|
312
|
-
widget: {
|
|
313
|
-
type: 'temba-select',
|
|
314
|
-
attributes: { multi: true }
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
return {
|
|
319
|
-
widget: { type: 'temba-textinput' }
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Type guard functions for working with WidgetConfig
|
|
324
|
-
export function isTextInputWidget(
|
|
325
|
-
config: WidgetConfig
|
|
326
|
-
): config is { type: 'temba-textinput'; attributes?: TextInputAttributes } {
|
|
327
|
-
return config.type === 'temba-textinput';
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export function isCompletionWidget(
|
|
331
|
-
config: WidgetConfig
|
|
332
|
-
): config is { type: 'temba-completion'; attributes?: CompletionAttributes } {
|
|
333
|
-
return config.type === 'temba-completion';
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export function isSelectWidget(
|
|
337
|
-
config: WidgetConfig
|
|
338
|
-
): config is { type: 'temba-select'; attributes?: SelectAttributes } {
|
|
339
|
-
return config.type === 'temba-select';
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
export function isCheckboxWidget(
|
|
343
|
-
config: WidgetConfig
|
|
344
|
-
): config is { type: 'temba-checkbox'; attributes?: CheckboxAttributes } {
|
|
345
|
-
return config.type === 'temba-checkbox';
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
export function isSliderWidget(
|
|
349
|
-
config: WidgetConfig
|
|
350
|
-
): config is { type: 'slider'; attributes?: SliderAttributes } {
|
|
351
|
-
return config.type === 'temba-slider';
|
|
352
|
-
}
|