@nyaruka/temba-components 0.130.1 → 0.130.3

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.
Files changed (250) hide show
  1. package/CHANGELOG.md +34 -4
  2. package/DEV_DATA.md +89 -0
  3. package/demo/data/flows/food-order.json +4 -4
  4. package/demo/data/flows/sample-flow.json +132 -147
  5. package/dist/temba-components.js +787 -659
  6. package/dist/temba-components.js.map +1 -1
  7. package/out-tsc/src/display/Chat.js +5 -3
  8. package/out-tsc/src/display/Chat.js.map +1 -1
  9. package/out-tsc/src/events.js.map +1 -1
  10. package/out-tsc/src/flow/CanvasNode.js +83 -78
  11. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  12. package/out-tsc/src/flow/Editor.js +1 -0
  13. package/out-tsc/src/flow/Editor.js.map +1 -1
  14. package/out-tsc/src/flow/NodeEditor.js +47 -3
  15. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  16. package/out-tsc/src/flow/actions/add_contact_urn.js +1 -1
  17. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  18. package/out-tsc/src/flow/actions/set_contact_channel.js +1 -1
  19. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  20. package/out-tsc/src/flow/actions/set_contact_field.js +2 -1
  21. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  22. package/out-tsc/src/flow/actions/set_contact_language.js +3 -1
  23. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  24. package/out-tsc/src/flow/actions/set_contact_name.js +1 -1
  25. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  26. package/out-tsc/src/flow/actions/set_contact_status.js +17 -14
  27. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  28. package/out-tsc/src/flow/actions/set_run_result.js +1 -1
  29. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  30. package/out-tsc/src/flow/nodes/split_by_llm.js +12 -12
  31. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  32. package/out-tsc/src/flow/nodes/wait_for_response.js +609 -6
  33. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  34. package/out-tsc/src/flow/operators.js +194 -0
  35. package/out-tsc/src/flow/operators.js.map +1 -0
  36. package/out-tsc/src/flow/types.js.map +1 -1
  37. package/out-tsc/src/form/ArrayEditor.js +84 -19
  38. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  39. package/out-tsc/src/form/Checkbox.js +12 -0
  40. package/out-tsc/src/form/Checkbox.js.map +1 -1
  41. package/out-tsc/src/form/FieldRenderer.js +13 -3
  42. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  43. package/out-tsc/src/form/TextInput.js +20 -1
  44. package/out-tsc/src/form/TextInput.js.map +1 -1
  45. package/out-tsc/src/form/select/Select.js +7 -0
  46. package/out-tsc/src/form/select/Select.js.map +1 -1
  47. package/out-tsc/src/interfaces.js.map +1 -1
  48. package/out-tsc/src/layout/Dialog.js +3 -4
  49. package/out-tsc/src/layout/Dialog.js.map +1 -1
  50. package/out-tsc/src/list/RunList.js +2 -2
  51. package/out-tsc/src/list/RunList.js.map +1 -1
  52. package/out-tsc/src/live/ContactChat.js +101 -44
  53. package/out-tsc/src/live/ContactChat.js.map +1 -1
  54. package/out-tsc/src/live/ContactDetails.js +7 -0
  55. package/out-tsc/src/live/ContactDetails.js.map +1 -1
  56. package/out-tsc/src/live/ContactNameFetch.js +1 -1
  57. package/out-tsc/src/live/ContactNameFetch.js.map +1 -1
  58. package/out-tsc/src/webchat/index.js +0 -11
  59. package/out-tsc/src/webchat/index.js.map +1 -1
  60. package/out-tsc/test/NodeHelper.js +25 -27
  61. package/out-tsc/test/NodeHelper.js.map +1 -1
  62. package/out-tsc/test/nodes/split_by_llm.test.js +12 -4
  63. package/out-tsc/test/nodes/split_by_llm.test.js.map +1 -1
  64. package/out-tsc/test/nodes/split_by_llm_categorize.test.js +101 -91
  65. package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -1
  66. package/out-tsc/test/nodes/split_by_random.test.js +120 -112
  67. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  68. package/out-tsc/test/nodes/wait_for_digits.test.js +131 -111
  69. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  70. package/out-tsc/test/nodes/wait_for_response.test.js +549 -85
  71. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  72. package/out-tsc/test/temba-checkbox.test.js +32 -32
  73. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  74. package/out-tsc/test/temba-contact-chat.test.js +2 -1
  75. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  76. package/out-tsc/test/temba-dropdown.test.js +0 -4
  77. package/out-tsc/test/temba-dropdown.test.js.map +1 -1
  78. package/out-tsc/test/temba-flow-editor-node.test.js +9 -4
  79. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  80. package/out-tsc/test/temba-integration-markdown.test.js +13 -15
  81. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  82. package/out-tsc/test/temba-node-editor.test.js +5 -38
  83. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  84. package/out-tsc/test/temba-run-list.test.js +2 -2
  85. package/out-tsc/test/temba-run-list.test.js.map +1 -1
  86. package/out-tsc/test/utils.test.js +2 -1
  87. package/out-tsc/test/utils.test.js.map +1 -1
  88. package/package.json +6 -2
  89. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  90. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  91. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  92. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  93. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  94. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  95. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  96. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  97. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  98. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  99. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  100. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  101. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  102. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  103. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  104. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  105. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  106. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  107. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  108. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  109. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  110. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  111. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  112. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  113. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  114. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  115. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  116. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  117. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  118. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  119. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  120. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  121. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  122. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  123. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  124. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  125. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  126. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  127. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  128. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  129. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  130. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  131. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  132. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  133. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  134. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  135. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  136. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  137. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  138. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  139. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  140. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  141. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  142. package/screenshots/truth/checkbox/checkbox-whitespace-label-no-background-hover.png +0 -0
  143. package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
  144. package/screenshots/truth/checkbox/checked.png +0 -0
  145. package/screenshots/truth/checkbox/default.png +0 -0
  146. package/screenshots/truth/editor/wait.png +0 -0
  147. package/screenshots/truth/integration/textinput-markdown-errors.png +0 -0
  148. package/screenshots/truth/lightbox/img-zoomed.png +0 -0
  149. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  150. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  151. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  152. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  153. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  154. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  155. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  156. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  157. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  158. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  159. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  160. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  161. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  162. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  163. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  164. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  165. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  166. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  167. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  168. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  169. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  170. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  171. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  172. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  173. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  174. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  175. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  176. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  177. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  178. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  179. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  180. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  181. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  182. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  183. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  184. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  185. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  186. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  187. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  188. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  189. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  190. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  191. package/screenshots/truth/run-list/basic.png +0 -0
  192. package/screenshots/truth/templates/default.png +0 -0
  193. package/screenshots/truth/wait-for-response/rules-editor.png +0 -0
  194. package/screenshots/truth/wait-for-response/timeout-editor-unchecked.png +0 -0
  195. package/screenshots/truth/wait-for-response/timeout-editor.png +0 -0
  196. package/scripts/dev-data-sync.mjs +182 -0
  197. package/src/display/Chat.ts +6 -4
  198. package/src/events.ts +6 -6
  199. package/src/flow/CanvasNode.ts +89 -79
  200. package/src/flow/Editor.ts +1 -0
  201. package/src/flow/NodeEditor.ts +55 -3
  202. package/src/flow/actions/add_contact_urn.ts +1 -1
  203. package/src/flow/actions/set_contact_channel.ts +1 -1
  204. package/src/flow/actions/set_contact_field.ts +2 -1
  205. package/src/flow/actions/set_contact_language.ts +3 -1
  206. package/src/flow/actions/set_contact_name.ts +1 -1
  207. package/src/flow/actions/set_contact_status.ts +18 -18
  208. package/src/flow/actions/set_run_result.ts +1 -1
  209. package/src/flow/nodes/split_by_llm.ts +14 -13
  210. package/src/flow/nodes/wait_for_response.ts +717 -5
  211. package/src/flow/operators.ts +215 -0
  212. package/src/flow/types.ts +10 -2
  213. package/src/form/ArrayEditor.ts +117 -37
  214. package/src/form/Checkbox.ts +12 -0
  215. package/src/form/FieldRenderer.ts +24 -3
  216. package/src/form/TextInput.ts +19 -1
  217. package/src/form/select/Select.ts +7 -0
  218. package/src/interfaces.ts +1 -1
  219. package/src/layout/Dialog.ts +4 -4
  220. package/src/list/RunList.ts +2 -2
  221. package/src/live/ContactChat.ts +128 -67
  222. package/src/live/ContactDetails.ts +7 -0
  223. package/src/live/ContactNameFetch.ts +1 -1
  224. package/src/webchat/index.ts +0 -16
  225. package/static/api/labels.json +6 -1
  226. package/test/NodeHelper.ts +38 -40
  227. package/test/nodes/split_by_llm.test.ts +43 -32
  228. package/test/nodes/split_by_llm_categorize.test.ts +130 -120
  229. package/test/nodes/split_by_random.test.ts +136 -128
  230. package/test/nodes/wait_for_digits.test.ts +147 -127
  231. package/test/nodes/wait_for_response.test.ts +657 -104
  232. package/test/temba-checkbox.test.ts +36 -32
  233. package/test/temba-contact-chat.test.ts +2 -1
  234. package/test/temba-dropdown.test.ts +0 -12
  235. package/test/temba-flow-editor-node.test.ts +11 -4
  236. package/test/temba-integration-markdown.test.ts +16 -17
  237. package/test/temba-node-editor.test.ts +5 -43
  238. package/test/temba-run-list.test.ts +2 -2
  239. package/test/utils.test.ts +2 -1
  240. package/test-assets/contacts/history.json +4 -7
  241. package/test-assets/list/runs.json +8 -8
  242. package/web-dev-mock.mjs +86 -30
  243. package/web-dev-server.config.mjs +272 -31
  244. package/screenshots/truth/dropdown/bottom-edge-collision.png +0 -0
  245. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  246. package/screenshots/truth/editor/send_msg.png +0 -0
  247. package/screenshots/truth/editor/set_contact_language.png +0 -0
  248. package/screenshots/truth/editor/set_contact_name.png +0 -0
  249. package/screenshots/truth/editor/set_run_result.png +0 -0
  250. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
@@ -2,12 +2,219 @@ import replace from '@rollup/plugin-replace';
2
2
  import { fromRollup } from '@web/dev-server-rollup';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
+ import crypto from 'crypto';
5
6
 
6
7
  // Import the shared flow info generator and Minio functionality
7
- import { generateFlowInfo, handleMinioUpload, handleLabelCreation } from './web-dev-mock.mjs';
8
+ import { generateFlowInfo, handleMinioUpload, handleEntityCreation } from './web-dev-mock.mjs';
9
+
10
+ // Development data directory outside project structure
11
+ const DEV_DATA_DIR = '/tmp/temba-dev-data';
12
+ const DEV_FLOWS_DIR = path.join(DEV_DATA_DIR, 'flows');
13
+ const DEV_API_DIR = path.join(DEV_DATA_DIR, 'api');
14
+
15
+ // Setup development data directories and copy defaults if needed
16
+ function setupDevData() {
17
+ // Ensure directories exist
18
+ if (!fs.existsSync(DEV_DATA_DIR)) {
19
+ fs.mkdirSync(DEV_DATA_DIR, { recursive: true });
20
+ }
21
+ if (!fs.existsSync(DEV_FLOWS_DIR)) {
22
+ fs.mkdirSync(DEV_FLOWS_DIR, { recursive: true });
23
+ }
24
+ if (!fs.existsSync(DEV_API_DIR)) {
25
+ fs.mkdirSync(DEV_API_DIR, { recursive: true });
26
+ }
27
+
28
+ // Copy default flows if dev flows directory is empty
29
+ const defaultFlowsDir = path.resolve('./demo/data/flows');
30
+ if (fs.existsSync(defaultFlowsDir)) {
31
+ const devFlowFiles = fs.readdirSync(DEV_FLOWS_DIR).filter(f => f.endsWith('.json'));
32
+ if (devFlowFiles.length === 0) {
33
+ const defaultFlowFiles = fs.readdirSync(defaultFlowsDir).filter(f => f.endsWith('.json'));
34
+ console.log(`Copying ${defaultFlowFiles.length} default flow files to development directory...`);
35
+ defaultFlowFiles.forEach(file => {
36
+ fs.copyFileSync(
37
+ path.join(defaultFlowsDir, file),
38
+ path.join(DEV_FLOWS_DIR, file)
39
+ );
40
+ });
41
+ }
42
+ }
43
+
44
+ // Copy default API files if dev API directory is empty
45
+ const defaultApiDir = path.resolve('./static/api');
46
+ if (fs.existsSync(defaultApiDir)) {
47
+ const devApiFiles = fs.readdirSync(DEV_API_DIR).filter(f => f.endsWith('.json'));
48
+ if (devApiFiles.length === 0) {
49
+ const defaultApiFiles = fs.readdirSync(defaultApiDir).filter(f => f.endsWith('.json'));
50
+ console.log(`Copying ${defaultApiFiles.length} default API files to development directory...`);
51
+ defaultApiFiles.forEach(file => {
52
+ fs.copyFileSync(
53
+ path.join(defaultApiDir, file),
54
+ path.join(DEV_API_DIR, file)
55
+ );
56
+ });
57
+ }
58
+ }
59
+ }
60
+
61
+ // Get the appropriate file path, preferring dev directory over defaults
62
+ function getDataFilePath(type, filename) {
63
+ const devPath = type === 'flows'
64
+ ? path.join(DEV_FLOWS_DIR, filename)
65
+ : path.join(DEV_API_DIR, filename);
66
+
67
+ const defaultPath = type === 'flows'
68
+ ? path.resolve(`./demo/data/flows/${filename}`)
69
+ : path.resolve(`./static/api/${filename}`);
70
+
71
+ // Use dev file if it exists, otherwise fall back to default
72
+ return fs.existsSync(devPath) ? devPath : defaultPath;
73
+ }
74
+
75
+ // Handle entity creation using development directory
76
+ function handleDevEntityCreation(entityType, context) {
77
+ const entityConfigs = {
78
+ labels: {
79
+ filename: 'labels.json',
80
+ createEntity: (name) => ({
81
+ uuid: crypto.randomUUID(),
82
+ name: name.trim(),
83
+ count: 0
84
+ }),
85
+ nameField: 'name',
86
+ entityType: 'Label'
87
+ },
88
+ fields: {
89
+ filename: 'fields.json',
90
+ createEntity: (name) => ({
91
+ key: name.trim().toLowerCase().replace(/\s+/g, '_'),
92
+ name: name.trim(),
93
+ type: 'text',
94
+ featured: false,
95
+ priority: 0,
96
+ usages: {
97
+ flows: 0,
98
+ groups: 0,
99
+ campaign_events: 0
100
+ },
101
+ agent_access: 'view',
102
+ label: name.trim(),
103
+ value_type: 'text'
104
+ }),
105
+ nameField: 'name',
106
+ uniqueField: 'key',
107
+ entityType: 'Field'
108
+ },
109
+ groups: {
110
+ filename: 'groups.json',
111
+ createEntity: (name) => ({
112
+ uuid: crypto.randomUUID(),
113
+ name: name.trim(),
114
+ query: null,
115
+ status: 'ready',
116
+ count: 0
117
+ }),
118
+ nameField: 'name',
119
+ entityType: 'Group'
120
+ }
121
+ };
122
+
123
+ const config = entityConfigs[entityType];
124
+
125
+ if (!config) {
126
+ context.status = 400;
127
+ context.body = JSON.stringify({ error: `Unsupported entity type: ${entityType}` });
128
+ return Promise.resolve();
129
+ }
130
+
131
+ return new Promise((resolve) => {
132
+ let body = '';
133
+
134
+ context.req.on('data', chunk => {
135
+ body += chunk.toString();
136
+ });
137
+
138
+ context.req.on('end', () => {
139
+ try {
140
+ const requestData = JSON.parse(body);
141
+ const entityName = requestData[config.nameField] || '';
142
+
143
+ if (!entityName.trim()) {
144
+ context.status = 400;
145
+ context.body = JSON.stringify({ error: `${config.entityType} name is required` });
146
+ resolve();
147
+ return;
148
+ }
149
+
150
+ // Get the correct file path (dev directory preferred)
151
+ const filePath = getDataFilePath('api', config.filename);
152
+
153
+ // Read existing data file
154
+ let entityData;
155
+
156
+ try {
157
+ entityData = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
158
+ } catch (error) {
159
+ // If file doesn't exist, create basic structure
160
+ entityData = {
161
+ next: null,
162
+ previous: null,
163
+ results: []
164
+ };
165
+ }
166
+
167
+ // Check if entity already exists
168
+ const uniqueField = config.uniqueField || config.nameField;
169
+ const checkValue = uniqueField === 'key'
170
+ ? entityName.trim().toLowerCase().replace(/\s+/g, '_')
171
+ : entityName.trim().toLowerCase();
172
+
173
+ const existingEntity = entityData.results.find(
174
+ entity => entity[uniqueField].toLowerCase() === checkValue
175
+ );
176
+
177
+ if (existingEntity) {
178
+ // Return existing entity
179
+ context.contentType = 'application/json';
180
+ context.body = JSON.stringify(existingEntity);
181
+ resolve();
182
+ return;
183
+ }
184
+
185
+ // Create new entity
186
+ const newEntity = config.createEntity(entityName);
187
+
188
+ // Add to entity data
189
+ entityData.results.push(newEntity);
190
+
191
+ // Write back to the dev directory file
192
+ const devFilePath = path.join(DEV_API_DIR, config.filename);
193
+ fs.writeFileSync(devFilePath, JSON.stringify(entityData, null, 2));
194
+
195
+ // Return the new entity
196
+ context.contentType = 'application/json';
197
+ context.body = JSON.stringify(newEntity);
198
+ context.status = 201;
199
+
200
+ console.log(`📝 ${config.entityType} created:`, newEntity);
201
+ resolve();
202
+
203
+ } catch (error) {
204
+ console.error(`Error creating ${entityType}:`, error);
205
+ context.status = 500;
206
+ context.body = JSON.stringify({ error: 'Internal server error' });
207
+ resolve();
208
+ }
209
+ });
210
+ });
211
+ }
8
212
 
9
213
  const replacePlugin = fromRollup(replace);
10
214
 
215
+ // Initialize development data on server startup
216
+ setupDevData();
217
+
11
218
  // Simple wrapper function to use the shared flow info generator
12
219
  function generateFlowMetadata(flowDefinition) {
13
220
  return generateFlowInfo(flowDefinition);
@@ -31,37 +238,51 @@ export default {
31
238
  if (context.request.method === 'GET' && context.path.startsWith('/api/')) {
32
239
  // Map API endpoints to static files
33
240
  const apiMappings = {
34
- '/api/v2/groups.json': './static/api/groups.json',
35
- '/api/v2/labels.json': './static/api/labels.json',
36
- '/api/v2/fields.json': './static/api/fields.json',
37
- '/api/v2/globals.json': './static/api/globals.json',
38
- '/api/v2/completion.json': './static/mr/docs/en-us/editor.json',
39
- '/api/v2/functions.json': './static/api/functions.json',
40
- '/api/internal/templates.json': './static/api/templates.json',
41
- '/api/v2/media.json': './static/api/media.json',
42
- '/api/v2/users.json': './static/api/users.json',
43
- '/api/v2/contacts.json': './static/api/contacts.json',
44
- '/api/v2/optins.json': './static/api/optins.json',
45
- '/api/v2/topics.json': './static/api/topics.json',
46
- '/api/v2/languages.json': './static/api/languages.json',
47
- '/api/v2/workspace.json': './static/api/workspace.json',
48
- '/api/internal/locations.json': './static/api/locations.json',
49
- '/api/internal/orgs.json': './static/api/orgs.json'
241
+ '/api/v2/groups.json': 'groups.json',
242
+ '/api/v2/labels.json': 'labels.json',
243
+ '/api/v2/fields.json': 'fields.json',
244
+ '/api/v2/globals.json': 'globals.json',
245
+ '/api/v2/completion.json': '../mr/docs/en-us/editor.json', // Special case
246
+ '/api/v2/functions.json': 'functions.json',
247
+ '/api/internal/templates.json': 'templates.json',
248
+ '/api/v2/media.json': 'media.json',
249
+ '/api/v2/users.json': 'users.json',
250
+ '/api/v2/contacts.json': 'contacts.json',
251
+ '/api/v2/optins.json': 'optins.json',
252
+ '/api/v2/topics.json': 'topics.json',
253
+ '/api/v2/languages.json': 'languages.json',
254
+ '/api/v2/workspace.json': 'workspace.json',
255
+ '/api/internal/locations.json': 'locations.json',
256
+ '/api/internal/orgs.json': 'orgs.json'
50
257
  };
51
258
 
52
259
  // Handle base path without query parameters
53
260
  const basePath = context.path.split('?')[0];
54
- const staticFile = apiMappings[basePath];
261
+ const filename = apiMappings[basePath];
55
262
 
56
- if (staticFile && fs.existsSync(path.resolve(staticFile))) {
57
- context.contentType = 'application/json';
58
- context.body = fs.readFileSync(path.resolve(staticFile), 'utf-8');
59
- return;
263
+ if (filename) {
264
+ let filePath;
265
+ if (filename === '../mr/docs/en-us/editor.json') {
266
+ // Special case for completion endpoint
267
+ filePath = path.resolve('./static/mr/docs/en-us/editor.json');
268
+ } else {
269
+ filePath = getDataFilePath('api', filename);
270
+ }
271
+
272
+ if (fs.existsSync(filePath)) {
273
+ context.contentType = 'application/json';
274
+ context.body = fs.readFileSync(filePath, 'utf-8');
275
+ return;
276
+ }
60
277
  }
61
278
 
62
279
  // Handle dynamic flows endpoint
63
280
  if (basePath === '/api/v2/flows.json') {
64
- const flowsDir = path.resolve('./demo/data/flows');
281
+ // Check dev flows directory first, then fall back to default
282
+ let flowsDir = DEV_FLOWS_DIR;
283
+ if (!fs.existsSync(flowsDir) || fs.readdirSync(flowsDir).filter(f => f.endsWith('.json')).length === 0) {
284
+ flowsDir = path.resolve('./demo/data/flows');
285
+ }
65
286
 
66
287
  if (fs.existsSync(flowsDir)) {
67
288
  const files = fs.readdirSync(flowsDir).filter(file => file.endsWith('.json'));
@@ -93,7 +314,17 @@ export default {
93
314
 
94
315
  // Handle label creation
95
316
  if (context.request.method === 'POST' && context.path === '/api/v2/labels.json') {
96
- return handleLabelCreation(context);
317
+ return handleDevEntityCreation('labels', context);
318
+ }
319
+
320
+ // Handle field creation
321
+ if (context.request.method === 'POST' && context.path === '/api/v2/fields.json') {
322
+ return handleDevEntityCreation('fields', context);
323
+ }
324
+
325
+ // Handle group creation
326
+ if (context.request.method === 'POST' && context.path === '/api/v2/groups.json') {
327
+ return handleDevEntityCreation('groups', context);
97
328
  }
98
329
  }
99
330
  },
@@ -104,7 +335,11 @@ export default {
104
335
 
105
336
  if (context.request.method === 'GET' && context.path === '/api/flows-list') {
106
337
 
107
- const flowsDir = path.resolve('./demo/data/flows');
338
+ // Check dev flows directory first, then fall back to default
339
+ let flowsDir = DEV_FLOWS_DIR;
340
+ if (!fs.existsSync(flowsDir) || fs.readdirSync(flowsDir).filter(f => f.endsWith('.json')).length === 0) {
341
+ flowsDir = path.resolve('./demo/data/flows');
342
+ }
108
343
 
109
344
  if (fs.existsSync(flowsDir)) {
110
345
  const files = fs.readdirSync(flowsDir).filter(file => file.endsWith('.json'));
@@ -132,8 +367,9 @@ export default {
132
367
  context.contentType = 'application/json';
133
368
  if (body) {
134
369
  const flowDefinition = JSON.parse(body);
370
+ const flowPath = path.join(DEV_FLOWS_DIR, `${uuid}.json`);
135
371
  fs.writeFileSync(
136
- path.resolve(`./demo/data/flows/${uuid}.json`),
372
+ flowPath,
137
373
  JSON.stringify({ definition: flowDefinition }, null, 2)
138
374
  );
139
375
 
@@ -192,11 +428,16 @@ export default {
192
428
  const uuid = parts[3];
193
429
  context.contentType = 'application/json';
194
430
 
195
- // Read the flow definition from file
196
- const flowFileContent = fs.readFileSync(
197
- path.resolve(`./demo/data/flows/${uuid}.json`),
198
- 'utf-8',
199
- );
431
+ // Read the flow definition from file (try dev directory first)
432
+ const flowPath = getDataFilePath('flows', `${uuid}.json`);
433
+
434
+ if (!fs.existsSync(flowPath)) {
435
+ context.status = 404;
436
+ context.body = JSON.stringify({ error: 'Flow not found' });
437
+ return;
438
+ }
439
+
440
+ const flowFileContent = fs.readFileSync(flowPath, 'utf-8');
200
441
 
201
442
  const flowData = JSON.parse(flowFileContent);
202
443