@nyaruka/temba-components 0.156.9 → 0.156.10
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/CHANGELOG.md +11 -0
- package/dist/temba-components.js +568 -521
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/display/Chat.ts +8 -8
- package/src/display/FloatingTab.ts +2 -2
- package/src/display/Options.ts +8 -2
- package/src/flow/CanvasMenu.ts +20 -25
- package/src/flow/CanvasNode.ts +16 -12
- package/src/flow/DragManager.ts +93 -33
- package/src/flow/Editor.ts +59 -54
- package/src/flow/EditorToolbar.ts +19 -20
- package/src/flow/FlowSearch.ts +9 -7
- package/src/flow/MessageTable.ts +181 -74
- package/src/flow/NodeEditor.ts +55 -72
- package/src/flow/RevisionsWindow.ts +2 -4
- package/src/flow/ZoomManager.ts +1 -2
- package/src/flow/actions/play_audio.ts +1 -28
- package/src/flow/actions/say_msg.ts +1 -40
- package/src/flow/actions/send_broadcast.ts +1 -2
- package/src/flow/actions/send_email.ts +5 -56
- package/src/flow/actions/send_msg.ts +10 -2
- package/src/flow/actions/start_session.ts +1 -2
- package/src/flow/categoryLocalization.ts +1 -5
- package/src/flow/categoryUtils.ts +139 -0
- package/src/flow/nodes/shared-rules.ts +6 -16
- package/src/flow/nodes/shared.ts +113 -6
- package/src/flow/nodes/split_by_airtime.ts +41 -63
- package/src/flow/nodes/split_by_contact_field.ts +8 -17
- package/src/flow/nodes/split_by_expression.ts +8 -17
- package/src/flow/nodes/split_by_groups.ts +34 -112
- package/src/flow/nodes/split_by_llm.ts +1 -7
- package/src/flow/nodes/split_by_llm_categorize.ts +27 -43
- package/src/flow/nodes/split_by_random.ts +39 -99
- package/src/flow/nodes/split_by_resthook.ts +5 -19
- package/src/flow/nodes/split_by_run_result.ts +8 -17
- package/src/flow/nodes/split_by_scheme.ts +39 -124
- package/src/flow/nodes/split_by_subflow.ts +1 -7
- package/src/flow/nodes/split_by_ticket.ts +1 -7
- package/src/flow/nodes/split_by_webhook.ts +2 -8
- package/src/flow/nodes/wait_for_audio.ts +1 -7
- package/src/flow/nodes/wait_for_dial.ts +2 -8
- package/src/flow/nodes/wait_for_digits.ts +5 -7
- package/src/flow/nodes/wait_for_menu.ts +5 -7
- package/src/flow/nodes/wait_for_response.ts +10 -18
- package/src/flow/types.ts +27 -0
- package/src/flow/utils.ts +111 -3
- package/src/form/Compose.ts +11 -4
- package/src/form/MessageEditor.ts +5 -3
- package/src/form/RichEditor.ts +3 -1
- package/src/form/TemplateEditor.ts +5 -1
- package/src/form/select/Select.ts +11 -9
- package/src/layout/AccordionSection.ts +9 -3
- package/src/layout/Modax.ts +1 -3
- package/src/live/ContactChat.ts +54 -46
- package/src/simulator/Simulator.ts +9 -3
- package/src/store/AppState.ts +1 -1
- package/src/utils.ts +21 -16
|
@@ -10,16 +10,14 @@ import {
|
|
|
10
10
|
resultNameField,
|
|
11
11
|
localizeRulesField,
|
|
12
12
|
localizeCategoriesField,
|
|
13
|
-
nodeOptionsAccordion
|
|
14
|
-
categoriesToLocalizationFormData,
|
|
15
|
-
localizationFormDataToCategories
|
|
13
|
+
nodeOptionsAccordion
|
|
16
14
|
} from './shared';
|
|
17
15
|
import {
|
|
18
16
|
createRulesArrayConfig,
|
|
19
17
|
extractUserRules,
|
|
20
18
|
casesToFormRules
|
|
21
19
|
} from './shared-rules';
|
|
22
|
-
import { SCHEMES } from '../utils';
|
|
20
|
+
import { SCHEMES, validateWith } from '../utils';
|
|
23
21
|
import { html } from 'lit';
|
|
24
22
|
|
|
25
23
|
// System contact properties that can be split on
|
|
@@ -89,18 +87,11 @@ export const split_by_contact_field: NodeConfig = {
|
|
|
89
87
|
localizeCategories: localizeCategoriesField
|
|
90
88
|
},
|
|
91
89
|
layout: ['field', 'rules', nodeOptionsAccordion],
|
|
92
|
-
validate: (formData
|
|
93
|
-
const errors: { [key: string]: string } = {};
|
|
94
|
-
|
|
90
|
+
validate: validateWith((formData, errors) => {
|
|
95
91
|
if (!formData.field || formData.field.length === 0) {
|
|
96
92
|
errors.field = 'A field is required';
|
|
97
93
|
}
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
valid: Object.keys(errors).length === 0,
|
|
101
|
-
errors
|
|
102
|
-
};
|
|
103
|
-
},
|
|
94
|
+
}),
|
|
104
95
|
toFormData: (node: Node, nodeUI?: any) => {
|
|
105
96
|
// Get the field from the UI config operand (source of truth)
|
|
106
97
|
const operand = nodeUI?.config?.operand || CONTACT_PROPERTIES.name;
|
|
@@ -206,7 +197,9 @@ export const split_by_contact_field: NodeConfig = {
|
|
|
206
197
|
}
|
|
207
198
|
};
|
|
208
199
|
config.localizeRules = !!formData.localizeRules;
|
|
209
|
-
config.localizeCategories = formData.result_name
|
|
200
|
+
config.localizeCategories = formData.result_name
|
|
201
|
+
? !!formData.localizeCategories
|
|
202
|
+
: false;
|
|
210
203
|
return config;
|
|
211
204
|
},
|
|
212
205
|
renderTitle: (node: Node, nodeUI?: any) => {
|
|
@@ -214,7 +207,5 @@ export const split_by_contact_field: NodeConfig = {
|
|
|
214
207
|
},
|
|
215
208
|
|
|
216
209
|
// Localization support for categories
|
|
217
|
-
localizable: 'categories'
|
|
218
|
-
toLocalizationFormData: categoriesToLocalizationFormData,
|
|
219
|
-
fromLocalizationFormData: localizationFormDataToCategories
|
|
210
|
+
localizable: 'categories'
|
|
220
211
|
};
|
|
@@ -10,15 +10,14 @@ import {
|
|
|
10
10
|
resultNameField,
|
|
11
11
|
localizeRulesField,
|
|
12
12
|
localizeCategoriesField,
|
|
13
|
-
nodeOptionsAccordion
|
|
14
|
-
categoriesToLocalizationFormData,
|
|
15
|
-
localizationFormDataToCategories
|
|
13
|
+
nodeOptionsAccordion
|
|
16
14
|
} from './shared';
|
|
17
15
|
import {
|
|
18
16
|
createRulesArrayConfig,
|
|
19
17
|
extractUserRules,
|
|
20
18
|
casesToFormRules
|
|
21
19
|
} from './shared-rules';
|
|
20
|
+
import { validateWith } from '../utils';
|
|
22
21
|
|
|
23
22
|
export const split_by_expression: NodeConfig = {
|
|
24
23
|
type: 'split_by_expression',
|
|
@@ -44,19 +43,11 @@ export const split_by_expression: NodeConfig = {
|
|
|
44
43
|
localizeCategories: localizeCategoriesField
|
|
45
44
|
},
|
|
46
45
|
layout: ['operand', 'rules', nodeOptionsAccordion],
|
|
47
|
-
validate: (formData
|
|
48
|
-
const errors: { [key: string]: string } = {};
|
|
49
|
-
|
|
50
|
-
// Validate operand is provided
|
|
46
|
+
validate: validateWith((formData, errors) => {
|
|
51
47
|
if (!formData.operand || formData.operand.trim() === '') {
|
|
52
48
|
errors.operand = 'Expression is required';
|
|
53
49
|
}
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
valid: Object.keys(errors).length === 0,
|
|
57
|
-
errors
|
|
58
|
-
};
|
|
59
|
-
},
|
|
50
|
+
}),
|
|
60
51
|
toFormData: (node: Node, nodeUI?: any) => {
|
|
61
52
|
// Extract rules from router cases using shared function
|
|
62
53
|
const rules = casesToFormRules(node);
|
|
@@ -73,7 +64,9 @@ export const split_by_expression: NodeConfig = {
|
|
|
73
64
|
toUIConfig: (formData: FormData) => {
|
|
74
65
|
const config: Record<string, any> = {};
|
|
75
66
|
config.localizeRules = !!formData.localizeRules;
|
|
76
|
-
config.localizeCategories = formData.result_name
|
|
67
|
+
config.localizeCategories = formData.result_name
|
|
68
|
+
? !!formData.localizeCategories
|
|
69
|
+
: false;
|
|
77
70
|
return config;
|
|
78
71
|
},
|
|
79
72
|
fromFormData: (formData: FormData, originalNode: Node): Node => {
|
|
@@ -116,7 +109,5 @@ export const split_by_expression: NodeConfig = {
|
|
|
116
109
|
},
|
|
117
110
|
|
|
118
111
|
// Localization support for categories
|
|
119
|
-
localizable: 'categories'
|
|
120
|
-
toLocalizationFormData: categoriesToLocalizationFormData,
|
|
121
|
-
fromLocalizationFormData: localizationFormDataToCategories
|
|
112
|
+
localizable: 'categories'
|
|
122
113
|
};
|
|
@@ -1,100 +1,14 @@
|
|
|
1
1
|
import { SPLIT_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
|
|
2
|
-
import { Node,
|
|
2
|
+
import { Node, Case } from '../../store/flow-definition.d';
|
|
3
3
|
import { generateUUID } from '../../utils';
|
|
4
|
+
import { validateWith } from '../utils';
|
|
4
5
|
import {
|
|
5
6
|
resultNameField,
|
|
6
7
|
nodeOptionsAccordionSimple,
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
buildCategoriesExitsCases,
|
|
9
|
+
appendOtherCategory
|
|
9
10
|
} from './shared';
|
|
10
11
|
|
|
11
|
-
// Helper function to create a switch router with group cases
|
|
12
|
-
const createGroupRouter = (
|
|
13
|
-
userGroups: { uuid: string; name: string }[],
|
|
14
|
-
existingCategories: Category[] = [],
|
|
15
|
-
existingExits: Exit[] = [],
|
|
16
|
-
existingCases: Case[] = [],
|
|
17
|
-
resultName: string = ''
|
|
18
|
-
) => {
|
|
19
|
-
const categories: Category[] = [];
|
|
20
|
-
const exits: Exit[] = [];
|
|
21
|
-
const cases: Case[] = [];
|
|
22
|
-
|
|
23
|
-
// Create categories, exits, and cases for each selected group
|
|
24
|
-
userGroups.forEach((group) => {
|
|
25
|
-
// Try to find existing category by group name
|
|
26
|
-
const existingCategory = existingCategories.find(
|
|
27
|
-
(cat) => cat.name === group.name
|
|
28
|
-
);
|
|
29
|
-
const existingExit = existingCategory
|
|
30
|
-
? existingExits.find((exit) => exit.uuid === existingCategory.exit_uuid)
|
|
31
|
-
: null;
|
|
32
|
-
const existingCase = existingCases.find(
|
|
33
|
-
(c) => c.arguments?.[0] === group.uuid
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const exitUuid = existingExit?.uuid || generateUUID();
|
|
37
|
-
const categoryUuid = existingCategory?.uuid || generateUUID();
|
|
38
|
-
const caseUuid = existingCase?.uuid || generateUUID();
|
|
39
|
-
|
|
40
|
-
categories.push({
|
|
41
|
-
uuid: categoryUuid,
|
|
42
|
-
name: group.name,
|
|
43
|
-
exit_uuid: exitUuid
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
exits.push({
|
|
47
|
-
uuid: exitUuid,
|
|
48
|
-
destination_uuid: existingExit?.destination_uuid || null
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
cases.push({
|
|
52
|
-
uuid: caseUuid,
|
|
53
|
-
type: 'has_group',
|
|
54
|
-
arguments: [group.uuid, group.name],
|
|
55
|
-
category_uuid: categoryUuid
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Add default "Other" category for contacts not in any selected group
|
|
60
|
-
const existingOtherCategory = existingCategories.find(
|
|
61
|
-
(cat) =>
|
|
62
|
-
cat.name === 'Other' &&
|
|
63
|
-
!userGroups.some((group) => group.name === cat.name)
|
|
64
|
-
);
|
|
65
|
-
const existingOtherExit = existingOtherCategory
|
|
66
|
-
? existingExits.find(
|
|
67
|
-
(exit) => exit.uuid === existingOtherCategory.exit_uuid
|
|
68
|
-
)
|
|
69
|
-
: null;
|
|
70
|
-
|
|
71
|
-
const otherExitUuid = existingOtherExit?.uuid || generateUUID();
|
|
72
|
-
const otherCategoryUuid = existingOtherCategory?.uuid || generateUUID();
|
|
73
|
-
|
|
74
|
-
categories.push({
|
|
75
|
-
uuid: otherCategoryUuid,
|
|
76
|
-
name: 'Other',
|
|
77
|
-
exit_uuid: otherExitUuid
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
exits.push({
|
|
81
|
-
uuid: otherExitUuid,
|
|
82
|
-
destination_uuid: existingOtherExit?.destination_uuid || null
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
router: {
|
|
87
|
-
type: 'switch' as const,
|
|
88
|
-
cases: cases,
|
|
89
|
-
categories: categories,
|
|
90
|
-
default_category_uuid: otherCategoryUuid,
|
|
91
|
-
operand: '@contact.groups',
|
|
92
|
-
result_name: resultName
|
|
93
|
-
},
|
|
94
|
-
exits: exits
|
|
95
|
-
};
|
|
96
|
-
};
|
|
97
|
-
|
|
98
12
|
export const split_by_groups: NodeConfig = {
|
|
99
13
|
type: 'split_by_groups',
|
|
100
14
|
name: 'Split by Group',
|
|
@@ -133,9 +47,7 @@ export const split_by_groups: NodeConfig = {
|
|
|
133
47
|
result_name: resultNameField
|
|
134
48
|
},
|
|
135
49
|
layout: ['groups', nodeOptionsAccordionSimple],
|
|
136
|
-
validate: (formData
|
|
137
|
-
const errors: { [key: string]: string } = {};
|
|
138
|
-
|
|
50
|
+
validate: validateWith((formData, errors) => {
|
|
139
51
|
if (
|
|
140
52
|
!formData.groups ||
|
|
141
53
|
!Array.isArray(formData.groups) ||
|
|
@@ -143,12 +55,7 @@ export const split_by_groups: NodeConfig = {
|
|
|
143
55
|
) {
|
|
144
56
|
errors.groups = 'At least one group is required';
|
|
145
57
|
}
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
valid: Object.keys(errors).length === 0,
|
|
149
|
-
errors
|
|
150
|
-
};
|
|
151
|
-
},
|
|
58
|
+
}),
|
|
152
59
|
toFormData: (node: Node) => {
|
|
153
60
|
// Extract groups from the existing node structure
|
|
154
61
|
const groups: { uuid: string; name: string }[] = [];
|
|
@@ -171,33 +78,50 @@ export const split_by_groups: NodeConfig = {
|
|
|
171
78
|
};
|
|
172
79
|
},
|
|
173
80
|
fromFormData: (formData: FormData, originalNode: Node): Node => {
|
|
174
|
-
// Get selected groups
|
|
175
81
|
const selectedGroups = (formData.groups || [])
|
|
176
82
|
.filter((group: any) => group?.uuid || group?.arbitrary)
|
|
177
83
|
.map((group: any) => ({
|
|
178
|
-
uuid: group.uuid || generateUUID(),
|
|
84
|
+
uuid: group.uuid || generateUUID(),
|
|
179
85
|
name: group.name
|
|
180
86
|
}));
|
|
181
87
|
|
|
182
|
-
// Create router and exits using existing data when possible
|
|
183
88
|
const existingCategories = originalNode.router?.categories || [];
|
|
184
89
|
const existingExits = originalNode.exits || [];
|
|
185
90
|
const existingCases = originalNode.router?.cases || [];
|
|
186
91
|
|
|
187
|
-
const {
|
|
188
|
-
selectedGroups
|
|
92
|
+
const { categories, exits, cases } = buildCategoriesExitsCases(
|
|
93
|
+
selectedGroups.map((group) => ({
|
|
94
|
+
name: group.name,
|
|
95
|
+
case: {
|
|
96
|
+
type: 'has_group',
|
|
97
|
+
arguments: [group.uuid, group.name]
|
|
98
|
+
}
|
|
99
|
+
})),
|
|
100
|
+
existingCategories,
|
|
101
|
+
existingExits,
|
|
102
|
+
existingCases
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const defaultCategoryUuid = appendOtherCategory(
|
|
106
|
+
categories,
|
|
107
|
+
exits,
|
|
189
108
|
existingCategories,
|
|
190
109
|
existingExits,
|
|
191
|
-
|
|
192
|
-
formData.result_name || ''
|
|
110
|
+
selectedGroups.map((g) => g.name)
|
|
193
111
|
);
|
|
194
112
|
|
|
195
|
-
// Return the complete node
|
|
196
113
|
return {
|
|
197
114
|
uuid: originalNode.uuid,
|
|
198
115
|
actions: originalNode.actions || [],
|
|
199
|
-
router:
|
|
200
|
-
|
|
116
|
+
router: {
|
|
117
|
+
type: 'switch',
|
|
118
|
+
cases,
|
|
119
|
+
categories,
|
|
120
|
+
default_category_uuid: defaultCategoryUuid,
|
|
121
|
+
operand: '@contact.groups',
|
|
122
|
+
result_name: formData.result_name || ''
|
|
123
|
+
},
|
|
124
|
+
exits
|
|
201
125
|
};
|
|
202
126
|
},
|
|
203
127
|
router: {
|
|
@@ -206,7 +130,5 @@ export const split_by_groups: NodeConfig = {
|
|
|
206
130
|
},
|
|
207
131
|
|
|
208
132
|
// Localization support for categories
|
|
209
|
-
localizable: 'categories'
|
|
210
|
-
toLocalizationFormData: categoriesToLocalizationFormData,
|
|
211
|
-
fromLocalizationFormData: localizationFormDataToCategories
|
|
133
|
+
localizable: 'categories'
|
|
212
134
|
};
|
|
@@ -2,10 +2,6 @@ import { ACTION_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
|
|
|
2
2
|
import { CallLLM, Node } from '../../store/flow-definition';
|
|
3
3
|
import { generateUUID, createSuccessFailureRouter } from '../../utils';
|
|
4
4
|
import { html } from 'lit';
|
|
5
|
-
import {
|
|
6
|
-
categoriesToLocalizationFormData,
|
|
7
|
-
localizationFormDataToCategories
|
|
8
|
-
} from './shared';
|
|
9
5
|
import {
|
|
10
6
|
renderClamped,
|
|
11
7
|
renderHighlightedText,
|
|
@@ -141,7 +137,5 @@ export const split_by_llm: NodeConfig = {
|
|
|
141
137
|
|
|
142
138
|
// Localization support for categories
|
|
143
139
|
localizable: 'categories',
|
|
144
|
-
nonTranslatableCategories: 'all'
|
|
145
|
-
toLocalizationFormData: categoriesToLocalizationFormData,
|
|
146
|
-
fromLocalizationFormData: localizationFormDataToCategories
|
|
140
|
+
nonTranslatableCategories: 'all'
|
|
147
141
|
};
|
|
@@ -2,10 +2,7 @@ import { FormData, NodeConfig, ACTION_GROUPS, Features } from '../types';
|
|
|
2
2
|
import { CallLLM, Node } from '../../store/flow-definition';
|
|
3
3
|
import { generateUUID, createMultiCategoryRouter } from '../../utils';
|
|
4
4
|
import { html } from 'lit';
|
|
5
|
-
import {
|
|
6
|
-
categoriesToLocalizationFormData,
|
|
7
|
-
localizationFormDataToCategories
|
|
8
|
-
} from './shared';
|
|
5
|
+
import { validateWith } from '../utils';
|
|
9
6
|
|
|
10
7
|
export const split_by_llm_categorize: NodeConfig = {
|
|
11
8
|
type: 'split_by_llm_categorize',
|
|
@@ -53,48 +50,37 @@ export const split_by_llm_categorize: NodeConfig = {
|
|
|
53
50
|
}
|
|
54
51
|
},
|
|
55
52
|
layout: ['llm', 'input', 'categories'],
|
|
56
|
-
validate: (formData
|
|
57
|
-
|
|
53
|
+
validate: validateWith((formData, errors) => {
|
|
54
|
+
if (!formData.categories || !Array.isArray(formData.categories)) return;
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
(item: any) => item?.name && item.name.trim() !== ''
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// Find all categories that have duplicates (case-insensitive)
|
|
66
|
-
const duplicateCategories = [];
|
|
67
|
-
const lowerCaseMap = new Map();
|
|
56
|
+
const categories = formData.categories.filter(
|
|
57
|
+
(item: any) => item?.name && item.name.trim() !== ''
|
|
58
|
+
);
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const lowerName = category.name.trim().toLowerCase();
|
|
72
|
-
if (!lowerCaseMap.has(lowerName)) {
|
|
73
|
-
lowerCaseMap.set(lowerName, []);
|
|
74
|
-
}
|
|
75
|
-
lowerCaseMap.get(lowerName).push(category.name.trim());
|
|
76
|
-
});
|
|
60
|
+
const duplicateCategories: string[] = [];
|
|
61
|
+
const lowerCaseMap = new Map<string, string[]>();
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
63
|
+
categories.forEach((category) => {
|
|
64
|
+
const lowerName = category.name.trim().toLowerCase();
|
|
65
|
+
if (!lowerCaseMap.has(lowerName)) {
|
|
66
|
+
lowerCaseMap.set(lowerName, []);
|
|
67
|
+
}
|
|
68
|
+
lowerCaseMap.get(lowerName).push(category.name.trim());
|
|
69
|
+
});
|
|
84
70
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
', '
|
|
89
|
-
)}`;
|
|
71
|
+
lowerCaseMap.forEach((originalNames) => {
|
|
72
|
+
if (originalNames.length > 1) {
|
|
73
|
+
duplicateCategories.push(...originalNames);
|
|
90
74
|
}
|
|
91
|
-
}
|
|
75
|
+
});
|
|
92
76
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
errors
|
|
96
|
-
|
|
97
|
-
|
|
77
|
+
if (duplicateCategories.length > 0) {
|
|
78
|
+
const uniqueDuplicates = [...new Set(duplicateCategories)];
|
|
79
|
+
errors.categories = `Duplicate category names found: ${uniqueDuplicates.join(
|
|
80
|
+
', '
|
|
81
|
+
)}`;
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
98
84
|
render: (node: Node) => {
|
|
99
85
|
const callLlmAction = node.actions?.find(
|
|
100
86
|
(action) => action.type === 'call_llm'
|
|
@@ -181,7 +167,5 @@ export const split_by_llm_categorize: NodeConfig = {
|
|
|
181
167
|
|
|
182
168
|
// Localization support for categories
|
|
183
169
|
localizable: 'categories',
|
|
184
|
-
nonTranslatableCategories: ['Failure']
|
|
185
|
-
toLocalizationFormData: categoriesToLocalizationFormData,
|
|
186
|
-
fromLocalizationFormData: localizationFormDataToCategories
|
|
170
|
+
nonTranslatableCategories: ['Failure']
|
|
187
171
|
};
|
|
@@ -1,53 +1,7 @@
|
|
|
1
1
|
import { SPLIT_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
|
|
2
|
-
import { Node
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
categoriesToLocalizationFormData,
|
|
6
|
-
localizationFormDataToCategories
|
|
7
|
-
} from './shared';
|
|
8
|
-
|
|
9
|
-
// Helper function to create a random router with categories
|
|
10
|
-
const createRandomRouter = (
|
|
11
|
-
userCategories: string[],
|
|
12
|
-
existingCategories: Category[] = [],
|
|
13
|
-
existingExits: Exit[] = []
|
|
14
|
-
) => {
|
|
15
|
-
const categories: Category[] = [];
|
|
16
|
-
const exits: Exit[] = [];
|
|
17
|
-
|
|
18
|
-
// Create categories and exits for user-defined buckets
|
|
19
|
-
userCategories.forEach((categoryName) => {
|
|
20
|
-
// Try to find existing category by name
|
|
21
|
-
const existingCategory = existingCategories.find(
|
|
22
|
-
(cat) => cat.name === categoryName
|
|
23
|
-
);
|
|
24
|
-
const existingExit = existingCategory
|
|
25
|
-
? existingExits.find((exit) => exit.uuid === existingCategory.exit_uuid)
|
|
26
|
-
: null;
|
|
27
|
-
|
|
28
|
-
const exitUuid = existingExit?.uuid || generateUUID();
|
|
29
|
-
const categoryUuid = existingCategory?.uuid || generateUUID();
|
|
30
|
-
|
|
31
|
-
categories.push({
|
|
32
|
-
uuid: categoryUuid,
|
|
33
|
-
name: categoryName,
|
|
34
|
-
exit_uuid: exitUuid
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
exits.push({
|
|
38
|
-
uuid: exitUuid,
|
|
39
|
-
destination_uuid: existingExit?.destination_uuid || null
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
router: {
|
|
45
|
-
type: 'random' as const,
|
|
46
|
-
categories: categories
|
|
47
|
-
},
|
|
48
|
-
exits: exits
|
|
49
|
-
};
|
|
50
|
-
};
|
|
2
|
+
import { Node } from '../../store/flow-definition.d';
|
|
3
|
+
import { validateWith } from '../utils';
|
|
4
|
+
import { buildCategoriesExitsCases } from './shared';
|
|
51
5
|
|
|
52
6
|
export const split_by_random: NodeConfig = {
|
|
53
7
|
type: 'split_by_random',
|
|
@@ -76,53 +30,41 @@ export const split_by_random: NodeConfig = {
|
|
|
76
30
|
}
|
|
77
31
|
},
|
|
78
32
|
layout: ['categories'],
|
|
79
|
-
validate: (formData
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// Check for duplicate category names
|
|
83
|
-
if (formData.categories && Array.isArray(formData.categories)) {
|
|
84
|
-
const categories = formData.categories.filter(
|
|
85
|
-
(item: any) => item?.name && item.name.trim() !== ''
|
|
86
|
-
);
|
|
33
|
+
validate: validateWith((formData, errors) => {
|
|
34
|
+
if (!formData.categories || !Array.isArray(formData.categories)) return;
|
|
87
35
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
36
|
+
const categories = formData.categories.filter(
|
|
37
|
+
(item: any) => item?.name && item.name.trim() !== ''
|
|
38
|
+
);
|
|
92
39
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
40
|
+
if (categories.length < 2) {
|
|
41
|
+
errors.categories = 'At least 2 buckets are required for random split';
|
|
42
|
+
}
|
|
96
43
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const lowerName = category.name.trim().toLowerCase();
|
|
100
|
-
if (!lowerCaseMap.has(lowerName)) {
|
|
101
|
-
lowerCaseMap.set(lowerName, []);
|
|
102
|
-
}
|
|
103
|
-
lowerCaseMap.get(lowerName).push(category.name.trim());
|
|
104
|
-
});
|
|
44
|
+
const duplicateCategories: string[] = [];
|
|
45
|
+
const lowerCaseMap = new Map<string, string[]>();
|
|
105
46
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
47
|
+
categories.forEach((category) => {
|
|
48
|
+
const lowerName = category.name.trim().toLowerCase();
|
|
49
|
+
if (!lowerCaseMap.has(lowerName)) {
|
|
50
|
+
lowerCaseMap.set(lowerName, []);
|
|
51
|
+
}
|
|
52
|
+
lowerCaseMap.get(lowerName).push(category.name.trim());
|
|
53
|
+
});
|
|
112
54
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
', '
|
|
117
|
-
)}`;
|
|
55
|
+
lowerCaseMap.forEach((originalNames) => {
|
|
56
|
+
if (originalNames.length > 1) {
|
|
57
|
+
duplicateCategories.push(...originalNames);
|
|
118
58
|
}
|
|
119
|
-
}
|
|
59
|
+
});
|
|
120
60
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
errors
|
|
124
|
-
|
|
125
|
-
|
|
61
|
+
if (duplicateCategories.length > 0) {
|
|
62
|
+
const uniqueDuplicates = [...new Set(duplicateCategories)];
|
|
63
|
+
errors.categories = `Duplicate bucket names found: ${uniqueDuplicates.join(
|
|
64
|
+
', '
|
|
65
|
+
)}`;
|
|
66
|
+
}
|
|
67
|
+
}),
|
|
126
68
|
toFormData: (node: Node) => {
|
|
127
69
|
// Extract categories from the existing node structure
|
|
128
70
|
const categories =
|
|
@@ -134,27 +76,27 @@ export const split_by_random: NodeConfig = {
|
|
|
134
76
|
};
|
|
135
77
|
},
|
|
136
78
|
fromFormData: (formData: FormData, originalNode: Node): Node => {
|
|
137
|
-
// Get user categories
|
|
138
79
|
const userCategories = (formData.categories || [])
|
|
139
80
|
.filter((item: any) => item?.name?.trim())
|
|
140
81
|
.map((item: any) => item.name.trim());
|
|
141
82
|
|
|
142
|
-
// Create router and exits using existing data when possible
|
|
143
83
|
const existingCategories = originalNode.router?.categories || [];
|
|
144
84
|
const existingExits = originalNode.exits || [];
|
|
145
85
|
|
|
146
|
-
const {
|
|
147
|
-
userCategories,
|
|
86
|
+
const { categories, exits } = buildCategoriesExitsCases(
|
|
87
|
+
userCategories.map((name) => ({ name })),
|
|
148
88
|
existingCategories,
|
|
149
89
|
existingExits
|
|
150
90
|
);
|
|
151
91
|
|
|
152
|
-
// Return the complete node
|
|
153
92
|
return {
|
|
154
93
|
uuid: originalNode.uuid,
|
|
155
94
|
actions: originalNode.actions || [],
|
|
156
|
-
router:
|
|
157
|
-
|
|
95
|
+
router: {
|
|
96
|
+
type: 'random',
|
|
97
|
+
categories
|
|
98
|
+
},
|
|
99
|
+
exits
|
|
158
100
|
};
|
|
159
101
|
},
|
|
160
102
|
router: {
|
|
@@ -162,7 +104,5 @@ export const split_by_random: NodeConfig = {
|
|
|
162
104
|
},
|
|
163
105
|
|
|
164
106
|
// Localization support for categories
|
|
165
|
-
localizable: 'categories'
|
|
166
|
-
toLocalizationFormData: categoriesToLocalizationFormData,
|
|
167
|
-
fromLocalizationFormData: localizationFormDataToCategories
|
|
107
|
+
localizable: 'categories'
|
|
168
108
|
};
|
|
@@ -2,12 +2,8 @@ import { ACTION_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
|
|
|
2
2
|
import { CallResthook, Node } from '../../store/flow-definition';
|
|
3
3
|
import { generateUUID, createSuccessFailureRouter } from '../../utils';
|
|
4
4
|
import { html } from 'lit';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
categoriesToLocalizationFormData,
|
|
8
|
-
localizationFormDataToCategories
|
|
9
|
-
} from './shared';
|
|
10
|
-
import { renderClamped } from '../utils';
|
|
5
|
+
import { resultNameField } from './shared';
|
|
6
|
+
import { renderClamped, validateWith } from '../utils';
|
|
11
7
|
|
|
12
8
|
export const split_by_resthook: NodeConfig = {
|
|
13
9
|
type: 'split_by_resthook',
|
|
@@ -31,19 +27,11 @@ export const split_by_resthook: NodeConfig = {
|
|
|
31
27
|
result_name: resultNameField
|
|
32
28
|
},
|
|
33
29
|
layout: ['resthook', 'result_name'],
|
|
34
|
-
validate: (formData
|
|
35
|
-
const errors: { [key: string]: string } = {};
|
|
36
|
-
|
|
37
|
-
// validate resthook is provided
|
|
30
|
+
validate: validateWith((formData, errors) => {
|
|
38
31
|
if (!formData.resthook || formData.resthook.length === 0) {
|
|
39
32
|
errors.resthook = 'A resthook is required';
|
|
40
33
|
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
valid: Object.keys(errors).length === 0,
|
|
44
|
-
errors
|
|
45
|
-
};
|
|
46
|
-
},
|
|
34
|
+
}),
|
|
47
35
|
render: (node: Node) => {
|
|
48
36
|
const callResthookAction = node.actions?.find(
|
|
49
37
|
(action) => action.type === 'call_resthook'
|
|
@@ -130,7 +118,5 @@ export const split_by_resthook: NodeConfig = {
|
|
|
130
118
|
|
|
131
119
|
// Localization support for categories
|
|
132
120
|
localizable: 'categories',
|
|
133
|
-
nonTranslatableCategories: 'all'
|
|
134
|
-
toLocalizationFormData: categoriesToLocalizationFormData,
|
|
135
|
-
fromLocalizationFormData: localizationFormDataToCategories
|
|
121
|
+
nonTranslatableCategories: 'all'
|
|
136
122
|
};
|