@ykurilov/agent-chat-flows 0.0.0-stage → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/CHANGELOG.md +252 -0
  2. package/LICENSE +21 -0
  3. package/README.md +124 -2
  4. package/agent-flow.schema.json +1020 -0
  5. package/dist/chunk-3PTJI2XY.js +100 -0
  6. package/dist/chunk-3PTJI2XY.js.map +1 -0
  7. package/dist/chunk-BNMQUNSI.js +9 -0
  8. package/dist/chunk-BNMQUNSI.js.map +1 -0
  9. package/dist/chunk-BOV6FJGU.js +1288 -0
  10. package/dist/chunk-BOV6FJGU.js.map +1 -0
  11. package/dist/chunk-CKY5KG7N.js +165 -0
  12. package/dist/chunk-CKY5KG7N.js.map +1 -0
  13. package/dist/chunk-DCTRMCGV.js +34 -0
  14. package/dist/chunk-DCTRMCGV.js.map +1 -0
  15. package/dist/format.js +147 -0
  16. package/dist/format.js.map +1 -0
  17. package/dist/index.js +3197 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/postgres.js +387 -0
  20. package/dist/postgres.js.map +1 -0
  21. package/dist/react.js +2650 -0
  22. package/dist/react.js.map +1 -0
  23. package/dist/sqlite.js +319 -0
  24. package/dist/sqlite.js.map +1 -0
  25. package/dist/types/check/app-checks.d.ts +16 -0
  26. package/dist/types/check/check-flow.d.ts +8 -0
  27. package/dist/types/check/index.d.ts +2 -0
  28. package/dist/types/engine/data-conditions.d.ts +10 -0
  29. package/dist/types/engine/day-frame.d.ts +17 -0
  30. package/dist/types/engine/first-messages.d.ts +21 -0
  31. package/dist/types/engine/flow-days.d.ts +39 -0
  32. package/dist/types/engine/flow-graph.d.ts +26 -0
  33. package/dist/types/engine/flow-run.d.ts +130 -0
  34. package/dist/types/engine/index.d.ts +14 -0
  35. package/dist/types/engine/migrate-run.d.ts +11 -0
  36. package/dist/types/engine/next-transition.d.ts +40 -0
  37. package/dist/types/engine/nudges.d.ts +78 -0
  38. package/dist/types/engine/run-updates.d.ts +37 -0
  39. package/dist/types/engine/side-questions.d.ts +26 -0
  40. package/dist/types/engine/side-tasks.d.ts +44 -0
  41. package/dist/types/engine/targets.d.ts +9 -0
  42. package/dist/types/engine/timers.d.ts +61 -0
  43. package/dist/types/engine/zoned-time.d.ts +27 -0
  44. package/dist/types/format/catalog-events.d.ts +5 -0
  45. package/dist/types/format/compact-flow.d.ts +8 -0
  46. package/dist/types/format/flow-format.d.ts +286 -0
  47. package/dist/types/format/flow-json-schema.d.ts +2 -0
  48. package/dist/types/format/flow-schema.d.ts +605 -0
  49. package/dist/types/format/index.d.ts +7 -0
  50. package/dist/types/format/migrate-flow.d.ts +4 -0
  51. package/dist/types/format/parse-flow.d.ts +6 -0
  52. package/dist/types/format.d.ts +3 -0
  53. package/dist/types/index.d.ts +4 -0
  54. package/dist/types/labels/client-card.d.ts +20 -0
  55. package/dist/types/labels/editor.d.ts +261 -0
  56. package/dist/types/labels/index.d.ts +14 -0
  57. package/dist/types/labels/migrate.d.ts +19 -0
  58. package/dist/types/labels/test-chat.d.ts +28 -0
  59. package/dist/types/not-yet.d.ts +2 -0
  60. package/dist/types/postgres.d.ts +10 -0
  61. package/dist/types/prompt/client-card-block.d.ts +16 -0
  62. package/dist/types/prompt/flow-rules.d.ts +9 -0
  63. package/dist/types/prompt/rules/after-end.d.ts +3 -0
  64. package/dist/types/prompt/rules/client-facts.d.ts +2 -0
  65. package/dist/types/prompt/rules/day-frame.d.ts +2 -0
  66. package/dist/types/prompt/rules/link-views.d.ts +4 -0
  67. package/dist/types/prompt/rules/rule-part.d.ts +3 -0
  68. package/dist/types/prompt/rules/side-tasks.d.ts +2 -0
  69. package/dist/types/prompt/rules/time-zone.d.ts +2 -0
  70. package/dist/types/prompt/state-card.d.ts +14 -0
  71. package/dist/types/react/client-card/client-card-panel.d.ts +8 -0
  72. package/dist/types/react/client-card/index.d.ts +2 -0
  73. package/dist/types/react/client-card/use-client-card.d.ts +19 -0
  74. package/dist/types/react/client-card-skin.d.ts +27 -0
  75. package/dist/types/react/editor/board-space.d.ts +51 -0
  76. package/dist/types/react/editor/board-styles.d.ts +1 -0
  77. package/dist/types/react/editor/editor-model.d.ts +60 -0
  78. package/dist/types/react/editor/exit-text.d.ts +11 -0
  79. package/dist/types/react/editor/flow-board.d.ts +11 -0
  80. package/dist/types/react/editor/flow-editor.d.ts +13 -0
  81. package/dist/types/react/editor/flow-simulation.d.ts +44 -0
  82. package/dist/types/react/editor/index.d.ts +6 -0
  83. package/dist/types/react/editor/schedule-model.d.ts +47 -0
  84. package/dist/types/react/editor/side-task-model.d.ts +18 -0
  85. package/dist/types/react/editor/use-flow-editor.d.ts +120 -0
  86. package/dist/types/react/flow-editor-skin.d.ts +453 -0
  87. package/dist/types/react/flow-labels.d.ts +8 -0
  88. package/dist/types/react/index.d.ts +6 -0
  89. package/dist/types/react/test-chat/flow-test-chat.d.ts +22 -0
  90. package/dist/types/react/test-chat/index.d.ts +2 -0
  91. package/dist/types/react/test-chat/use-flow-test-chat.d.ts +50 -0
  92. package/dist/types/react.d.ts +3 -0
  93. package/dist/types/runtime/admit.d.ts +15 -0
  94. package/dist/types/runtime/agent-flow.d.ts +20 -0
  95. package/dist/types/runtime/client-card.d.ts +9 -0
  96. package/dist/types/runtime/client-facts.d.ts +22 -0
  97. package/dist/types/runtime/close-day-tool.d.ts +3 -0
  98. package/dist/types/runtime/decisions-client.d.ts +33 -0
  99. package/dist/types/runtime/extra-tool.d.ts +16 -0
  100. package/dist/types/runtime/first-message-retry.d.ts +4 -0
  101. package/dist/types/runtime/first-message-rules.d.ts +55 -0
  102. package/dist/types/runtime/first-reasons-notice.d.ts +9 -0
  103. package/dist/types/runtime/flow-delivery.d.ts +53 -0
  104. package/dist/types/runtime/flow-events.d.ts +126 -0
  105. package/dist/types/runtime/flow-finish.d.ts +68 -0
  106. package/dist/types/runtime/flow-known-texts.d.ts +11 -0
  107. package/dist/types/runtime/flow-signals.d.ts +12 -0
  108. package/dist/types/runtime/flow-timers.d.ts +55 -0
  109. package/dist/types/runtime/flow-tools.d.ts +23 -0
  110. package/dist/types/runtime/flow-types.d.ts +547 -0
  111. package/dist/types/runtime/held-messages.d.ts +25 -0
  112. package/dist/types/runtime/index.d.ts +11 -0
  113. package/dist/types/runtime/judge-context.d.ts +36 -0
  114. package/dist/types/runtime/judge.d.ts +21 -0
  115. package/dist/types/runtime/migrate-chats.d.ts +20 -0
  116. package/dist/types/runtime/note-tools.d.ts +15 -0
  117. package/dist/types/runtime/pause.d.ts +21 -0
  118. package/dist/types/runtime/prepare-turn.d.ts +12 -0
  119. package/dist/types/runtime/side-task-tool.d.ts +5 -0
  120. package/dist/types/runtime/step-tail.d.ts +11 -0
  121. package/dist/types/runtime/tail/day-line.d.ts +2 -0
  122. package/dist/types/runtime/tail/frame-line.d.ts +2 -0
  123. package/dist/types/runtime/tail/side-tasks-line.d.ts +2 -0
  124. package/dist/types/runtime/tail/tail-part.d.ts +21 -0
  125. package/dist/types/runtime/test-clock.d.ts +26 -0
  126. package/dist/types/runtime/time-zone-tool.d.ts +3 -0
  127. package/dist/types/runtime/turn-gate.d.ts +8 -0
  128. package/dist/types/sqlite.d.ts +9 -0
  129. package/dist/types/store/flow-store-shared.d.ts +20 -0
  130. package/dist/types/store/flow-store.d.ts +240 -0
  131. package/dist/types/store/memory.d.ts +3 -0
  132. package/dist/types/store/migration-settings.d.ts +7 -0
  133. package/dist/types/store/postgres-schema.d.ts +775 -0
  134. package/dist/types/store/postgres.d.ts +15 -0
  135. package/dist/types/store/sqlite-schema.d.ts +824 -0
  136. package/dist/types/store/sqlite.d.ts +9 -0
  137. package/drizzle/postgres/0000_init.sql +45 -0
  138. package/drizzle/postgres/0001_days_pause_side_tasks.sql +21 -0
  139. package/drizzle/postgres/0002_chat_placement.sql +1 -0
  140. package/drizzle/postgres/meta/0000_snapshot.json +317 -0
  141. package/drizzle/postgres/meta/0001_snapshot.json +439 -0
  142. package/drizzle/postgres/meta/0002_snapshot.json +445 -0
  143. package/drizzle/postgres/meta/_journal.json +27 -0
  144. package/drizzle/sqlite/0000_init.sql +44 -0
  145. package/drizzle/sqlite/0001_days_pause_side_tasks.sql +21 -0
  146. package/drizzle/sqlite/0002_chat_placement.sql +1 -0
  147. package/drizzle/sqlite/meta/0000_snapshot.json +297 -0
  148. package/drizzle/sqlite/meta/0001_snapshot.json +427 -0
  149. package/drizzle/sqlite/meta/0002_snapshot.json +434 -0
  150. package/drizzle/sqlite/meta/_journal.json +27 -0
  151. package/package.json +98 -3
package/dist/react.js ADDED
@@ -0,0 +1,2650 @@
1
+ import {
2
+ FACT_PREFIX,
3
+ fieldValueText
4
+ } from "./chunk-3PTJI2XY.js";
5
+ import {
6
+ ANY_STATE_LAYOUT_KEY,
7
+ DEFAULT_LATE_START_HOURS,
8
+ DEFAULT_SIDE_TASK_MAX_ATTEMPTS,
9
+ FLOW_DATA_OPS,
10
+ FLOW_OPS_BY_FIELD_TYPE,
11
+ RETURN_TARGET,
12
+ activeExits,
13
+ appIssues,
14
+ applyTransition,
15
+ catalogEventLabel,
16
+ catalogEventNames,
17
+ checkFlow,
18
+ compactFlow,
19
+ fieldById,
20
+ isFieldStale,
21
+ isFinal,
22
+ nextTransition,
23
+ noteAgentAnswer,
24
+ noteHumanMessage,
25
+ noteSideQuestion,
26
+ parseFlow,
27
+ sideQuestion,
28
+ startRun,
29
+ stateById,
30
+ timerDueAt
31
+ } from "./chunk-BOV6FJGU.js";
32
+
33
+ // src/react/flow-labels.ts
34
+ import { createContext, createElement, useContext } from "react";
35
+
36
+ // src/labels/client-card.ts
37
+ var russianClientCardLabels = {
38
+ title: "\u0427\u0442\u043E \u0430\u0433\u0435\u043D\u0442 \u0437\u043D\u0430\u0435\u0442 \u043E \u0432\u0430\u0441",
39
+ empty: "\u0410\u0433\u0435\u043D\u0442 \u043F\u043E\u043A\u0430 \u043D\u0438\u0447\u0435\u0433\u043E \u043E \u0432\u0430\u0441 \u043D\u0435 \u0437\u0430\u043F\u043E\u043C\u043D\u0438\u043B.",
40
+ updatedAt: (date) => `\u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u043E ${date}`,
41
+ stale: "\u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u0443\u0436\u0435 \u043D\u0435 \u0442\u0430\u043A",
42
+ fact: "\u0424\u0430\u043A\u0442",
43
+ forgetField: "\u0421\u0442\u0435\u0440\u0435\u0442\u044C",
44
+ forgetAll: "\u0421\u0442\u0435\u0440\u0435\u0442\u044C \u0432\u0441\u0451",
45
+ forgetAllConfirm: "\u0421\u0442\u0435\u0440\u0435\u0442\u044C \u0432\u0441\u0451, \u0447\u0442\u043E \u0430\u0433\u0435\u043D\u0442 \u0437\u043D\u0430\u0435\u0442 \u043E \u0432\u0430\u0441? \u0412\u0435\u0440\u043D\u0443\u0442\u044C \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u0441\u044F.",
46
+ confirm: "\u0421\u0442\u0435\u0440\u0435\u0442\u044C",
47
+ cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
48
+ loading: "\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043C\u2026",
49
+ failed: "\u041D\u0435 \u043F\u043E\u043B\u0443\u0447\u0438\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437",
50
+ retry: "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C"
51
+ };
52
+ var englishClientCardLabels = {
53
+ title: "What the agent knows about you",
54
+ empty: "The agent has not remembered anything about you yet.",
55
+ updatedAt: (date) => `updated ${date}`,
56
+ stale: "may be out of date",
57
+ fact: "Fact",
58
+ forgetField: "Erase",
59
+ forgetAll: "Erase everything",
60
+ forgetAllConfirm: "Erase everything the agent knows about you? This cannot be undone.",
61
+ confirm: "Erase",
62
+ cancel: "Cancel",
63
+ loading: "Loading\u2026",
64
+ failed: "Could not load. Try again",
65
+ retry: "Retry"
66
+ };
67
+
68
+ // src/labels/editor.ts
69
+ var russianFlowEditorLabels = {
70
+ conditionKinds: { judge: "\u0421\u043B\u043E\u0432\u0430\u043C\u0438", data: "\u0414\u0430\u043D\u043D\u044B\u0435", event: "\u0421\u043E\u0431\u044B\u0442\u0438\u0435", timer: "\u0422\u0430\u0439\u043C\u0435\u0440" },
71
+ conditionKindHints: {
72
+ judge: "\u0421\u043B\u043E\u0432\u0430\u043C\u0438 \u2014 \u0440\u0435\u0448\u0430\u0435\u0442 \u0430\u0433\u0435\u043D\u0442, \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442 \u0441\u0443\u0434\u044C\u044F",
73
+ data: "\u041F\u043E \u0434\u0430\u043D\u043D\u044B\u043C \u2014 \u043F\u043E\u043B\u0435 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E, \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0440\u0430\u0432\u043D\u043E\u2026",
74
+ event: "\u0421\u043E\u0431\u044B\u0442\u0438\u0435 \u2014 \u043A\u043D\u043E\u043F\u043A\u0430, \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442, \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F",
75
+ timer: "\u0422\u0430\u0439\u043C\u0435\u0440 \u2014 \u043F\u043E \u0447\u0430\u0441\u0430\u043C"
76
+ },
77
+ returnKind: "\u041D\u0430\u0437\u0430\u0434",
78
+ returnTarget: "\u043D\u0430\u0437\u0430\u0434, \u0433\u0434\u0435 \u0431\u044B\u043B",
79
+ ops: { filled: "\u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E", eq: "=", ne: "\u2260", gt: ">", lt: "<", contains: "\u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442" },
80
+ events: { message: "\u041F\u0440\u0438\u0448\u043B\u043E \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435", button: "\u041D\u0430\u0436\u0430\u0442\u0430 \u043A\u043D\u043E\u043F\u043A\u0430", tool: "\u0412\u044B\u0437\u0432\u0430\u043D \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442", app: "\u0421\u043E\u0431\u044B\u0442\u0438\u0435 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F" },
81
+ timerSpans: {
82
+ in_state: "\u0412\u0440\u0435\u043C\u044F \u0432 \u044D\u0442\u043E\u043C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0438",
83
+ silence: "\u0422\u0438\u0448\u0438\u043D\u0430 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430",
84
+ since_start: "\u041F\u0440\u043E\u0448\u043B\u043E \u0441 \u043D\u0430\u0447\u0430\u043B\u0430 \u0447\u0430\u0442\u0430",
85
+ time_of_day: "\u041D\u0430\u0441\u0442\u0443\u043F\u0438\u043B\u043E \u0432\u0440\u0435\u043C\u044F \u0441\u0443\u0442\u043E\u043A"
86
+ },
87
+ units: { min: "\u043C\u0438\u043D", h: "\u0447", d: "\u0434\u043D." },
88
+ fieldTypes: { text: "\u0422\u0435\u043A\u0441\u0442", number: "\u0427\u0438\u0441\u043B\u043E", choice: "\u0412\u044B\u0431\u043E\u0440", boolean: "\u0414\u0430/\u043D\u0435\u0442" },
89
+ yes: "\u0434\u0430",
90
+ no: "\u043D\u0435\u0442",
91
+ days: (count) => {
92
+ const tens = count % 100;
93
+ const ones = count % 10;
94
+ const word = tens >= 11 && tens <= 14 ? "\u0434\u043D\u0435\u0439" : ones === 1 ? "\u0434\u0435\u043D\u044C" : ones >= 2 && ones <= 4 ? "\u0434\u043D\u044F" : "\u0434\u043D\u0435\u0439";
95
+ return `${count} ${word}`;
96
+ },
97
+ programDay: (day) => `\u0441 \u0434\u043D\u044F ${day}`,
98
+ header: {
99
+ state: "\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435",
100
+ card: "\u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0438 \u0437\u0430\u043C\u0435\u0442\u043A\u0438",
101
+ check: "\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0433\u0440\u0430\u0444",
102
+ run: "\u041F\u0440\u043E\u0433\u043E\u043D",
103
+ json: "JSON",
104
+ save: "\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C",
105
+ saved: "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E",
106
+ unsaved: "\u0415\u0441\u0442\u044C \u043D\u0435\u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0435 \u043F\u0440\u0430\u0432\u043A\u0438",
107
+ saveFailed: "\u041D\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E",
108
+ saveFailedHint: "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0435 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E. \u0427\u0442\u043E \u043D\u0435 \u0442\u0430\u043A \u2014 \u0432 \xAB\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0433\u0440\u0430\u0444\xBB \u0438 \u043D\u0430 \u043D\u043E\u0434\u0430\u0445.",
109
+ readOnly: "\u0422\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
110
+ readOnlyHint: "\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439 \u0441\u0435\u0439\u0447\u0430\u0441 \u043F\u0440\u0430\u0432\u0438\u0442 \u0430\u0433\u0435\u043D\u0442. \u0421\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0438 \u043F\u0440\u043E\u0433\u043E\u043D\u044F\u0442\u044C \u043C\u043E\u0436\u043D\u043E, \u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F.",
111
+ migrate: "\u041F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0447\u0430\u0442\u044B"
112
+ },
113
+ board: {
114
+ anyState: "\u0418\u0437 \u043B\u044E\u0431\u043E\u0433\u043E \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F",
115
+ start: "\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439 \u043D\u0430\u0447\u0438\u043D\u0430\u0435\u0442\u0441\u044F \u0437\u0434\u0435\u0441\u044C",
116
+ final: "\u041A\u043E\u043D\u0435\u0446",
117
+ noSideQuestions: "\u0411\u0435\u0437 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0435",
118
+ noTarget: "\u043A\u0443\u0434\u0430 \u2014 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E",
119
+ emptyCondition: "\u0443\u0441\u043B\u043E\u0432\u0438\u0435 \u043D\u0435 \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043E",
120
+ newState: "\u041D\u043E\u0432\u043E\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435",
121
+ zoomIn: "\u041A\u0440\u0443\u043F\u043D\u0435\u0435",
122
+ zoomOut: "\u041C\u0435\u043B\u044C\u0447\u0435",
123
+ fit: "\u0412\u0435\u0441\u044C \u0433\u0440\u0430\u0444",
124
+ errors: (count) => `\u041E\u0448\u0438\u0431\u043E\u043A: ${count}`,
125
+ warnings: (count) => `\u0417\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u0439: ${count}`,
126
+ nudges: "\u043A\u0430\u0441\u0430\u043D\u0438\u044F",
127
+ noNudges: "\u0431\u0435\u0437 \u043A\u0430\u0441\u0430\u043D\u0438\u0439",
128
+ nudgesDays: (days) => days === 1 ? "\u0432 \u0434\u0435\u043D\u044C \u0432\u0445\u043E\u0434\u0430" : `\u043F\u0435\u0440\u0432\u044B\u0435 ${days} \u0434\u043D.`,
129
+ space: {
130
+ legend: "\u041B\u0435\u0433\u0435\u043D\u0434\u0430",
131
+ showMinimap: "\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043C\u0438\u043D\u0438-\u043A\u0430\u0440\u0442\u0443",
132
+ hideMinimap: "\u0421\u043A\u0440\u044B\u0442\u044C \u043C\u0438\u043D\u0438-\u043A\u0430\u0440\u0442\u0443",
133
+ showInspector: "\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u0430\u043D\u0435\u043B\u044C \xAB\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439\xBB",
134
+ hideInspector: "\u0421\u043A\u0440\u044B\u0442\u044C \u043F\u0430\u043D\u0435\u043B\u044C \xAB\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439\xBB",
135
+ enterFullscreen: "\u041D\u0430 \u0432\u0435\u0441\u044C \u044D\u043A\u0440\u0430\u043D",
136
+ exitFullscreen: "\u0412\u044B\u0439\u0442\u0438 \u0438\u0437 \u043F\u043E\u043B\u043D\u043E\u0433\u043E \u044D\u043A\u0440\u0430\u043D\u0430",
137
+ fullscreenRegion: "\u0420\u0435\u0434\u0430\u043A\u0442\u043E\u0440 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F \u043D\u0430 \u0432\u0435\u0441\u044C \u044D\u043A\u0440\u0430\u043D"
138
+ }
139
+ },
140
+ overview: {
141
+ title: "\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439",
142
+ common: "\u041E\u0431\u0449\u0435\u0435 \u0434\u043B\u044F \u0432\u0441\u0435\u0445 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0439",
143
+ commonHint: "\u0420\u043E\u043B\u044C, \u0442\u043E\u043D, \u0446\u0435\u043D\u044B, \u0443\u0441\u043B\u043E\u0432\u0438\u044F, \u0441\u0441\u044B\u043B\u043A\u0438 \u2014 \u0442\u043E, \u0447\u0442\u043E \u0432\u0435\u0440\u043D\u043E \u0432 \u043B\u044E\u0431\u043E\u043C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0438.",
144
+ states: "\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0439",
145
+ exits: "\u0423\u0441\u043B\u043E\u0432\u0438\u0439 \u0432\u044B\u0445\u043E\u0434\u0430",
146
+ finals: "\u041A\u043E\u043D\u0435\u0447\u043D\u044B\u0445",
147
+ lists: "\u0421\u043F\u0438\u0441\u043A\u0438",
148
+ help: "\u041A\u0430\u043A \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C\u0441\u044F",
149
+ flowIssues: "\u0412\u0435\u0441\u044C \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439",
150
+ afterEnd: "\u041F\u043E\u0441\u043B\u0435 \u043A\u043E\u043D\u0446\u0430 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u0430",
151
+ afterEndModes: { reply: "\u041E\u0442\u0432\u0435\u0447\u0430\u0442\u044C", silent: "\u041D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0442\u044C" },
152
+ afterEndHints: {
153
+ reply: "\u0410\u0433\u0435\u043D\u0442 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043F\u043E \u043E\u0431\u0449\u0435\u0439 \u0447\u0430\u0441\u0442\u0438: \u0437\u043D\u0430\u0435\u0442, \u0447\u0442\u043E \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0430 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u0430\u0441\u044C, \u043D\u0435 \u0432\u0435\u0434\u0451\u0442 \u043F\u043E \u0448\u0430\u0433\u0430\u043C \u0438 \u043D\u0435 \u043F\u0440\u043E\u0434\u0430\u0451\u0442 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E.",
154
+ silent: "\u0410\u0433\u0435\u043D\u0442 \u043C\u043E\u043B\u0447\u0438\u0442, \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u0443\u0447\u0430\u0435\u0442 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435."
155
+ }
156
+ },
157
+ schedule: {
158
+ title: "\u0414\u043D\u0438 \u0438 \u0432\u0440\u0435\u043C\u044F",
159
+ on: "\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439 \u043F\u043E \u0434\u043D\u044F\u043C",
160
+ hint: "\u0414\u043D\u0438, \u043D\u043E\u0447\u044C, \u043E\u0431\u0449\u0438\u0439 \u0441\u0440\u043E\u043A \u0438 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043F\u0435\u0440\u0432\u044B\u043C. \u041D\u043E\u043C\u0435\u0440 \u0434\u043D\u044F \u0430\u0433\u0435\u043D\u0442 \u0432\u0438\u0434\u0438\u0442 \u0432 \u043A\u0430\u0436\u0434\u043E\u043C \u0445\u043E\u0434\u0435.",
161
+ dayStart: "\u041D\u0430\u0447\u0430\u043B\u043E \u0434\u043D\u044F",
162
+ dayStartHint: "\u0412 \u044D\u0442\u043E \u0432\u0440\u0435\u043C\u044F \u2014 \u0443\u0442\u0440\u0435\u043D\u043D\u0435\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435.",
163
+ lengthDays: "\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u043D\u0435\u0439",
164
+ lengthDaysNone: "\u0431\u0435\u0437 \u0441\u0440\u043E\u043A\u0430",
165
+ nightFrom: "\u041D\u043E\u0447\u044C \u0441",
166
+ nightFromHint: "\u041D\u043E\u0447\u044C\u044E \u0430\u0433\u0435\u043D\u0442 \u043F\u0435\u0440\u0432\u044B\u043C \u043D\u0435 \u043F\u0438\u0448\u0435\u0442. \u041F\u0443\u0441\u0442\u043E \u2014 \u043A\u0430\u043A \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F.",
167
+ lateStart: "\u041F\u043E\u0437\u0434\u043D\u0438\u0439 \u0441\u0442\u0430\u0440\u0442, \u0447",
168
+ lateStartHint: "\u041F\u0440\u0438\u0448\u0451\u043B \u043C\u0435\u043D\u044C\u0448\u0435 \u0447\u0435\u043C \u0437\u0430 \u0441\u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0430\u0441\u043E\u0432 \u0434\u043E \u043D\u043E\u0447\u0438 \u2014 \u043F\u0435\u0440\u0432\u044B\u0439 \u0434\u0435\u043D\u044C \u0437\u0430\u0432\u0442\u0440\u0430. 0 \u2014 \u0434\u0435\u043D\u044C \u043F\u0440\u0438\u0445\u043E\u0434\u0430 \u043F\u0435\u0440\u0432\u044B\u0439.",
169
+ deadlineState: "\u0421\u0440\u043E\u043A \u0432\u044B\u0448\u0435\u043B \u2014 \u043F\u0435\u0440\u0435\u0439\u0442\u0438 \u0432",
170
+ deadlineNone: "\u0441\u0440\u0430\u0437\u0443 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0442\u044C",
171
+ farewell: "\u041F\u043E\u043F\u0440\u043E\u0449\u0430\u0442\u044C\u0441\u044F, \u043A\u043E\u0433\u0434\u0430 \u0441\u0440\u043E\u043A \u0432\u044B\u0448\u0435\u043B",
172
+ farewellText: "\u0413\u043E\u0442\u043E\u0432\u044B\u0439 \u0442\u0435\u043A\u0441\u0442 \u043F\u0440\u043E\u0449\u0430\u043D\u0438\u044F",
173
+ farewellTextHint: "\u041F\u0443\u0441\u0442\u043E \u2014 \u043F\u0440\u043E\u0449\u0430\u043D\u0438\u0435 \u043D\u0430\u043F\u0438\u0448\u0435\u0442 \u0430\u0433\u0435\u043D\u0442.",
174
+ firstMessages: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043F\u0435\u0440\u0432\u044B\u043C",
175
+ maxPerDay: "\u041D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u0432 \u0434\u0435\u043D\u044C",
176
+ maxPerDayHint: "\u041F\u0443\u0441\u0442\u043E \u2014 \u043A\u0430\u043A \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F.",
177
+ morning: "\u0423\u0442\u0440\u0435\u043D\u043D\u0435\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
178
+ skipMorning: "\u041D\u0435 \u043F\u0438\u0441\u0430\u0442\u044C \u0443\u0442\u0440\u043E\u043C, \u0435\u0441\u043B\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043F\u0438\u0441\u0430\u043B \u0437\u0430, \u043C\u0438\u043D",
179
+ nudges: "\u041A\u0430\u0441\u0430\u043D\u0438\u044F \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E",
180
+ nudgesHint: "\u0410\u0433\u0435\u043D\u0442 \u043F\u0438\u0448\u0435\u0442 \u0432 \u0442\u043E\u0439 \u0436\u0435 \u043D\u043E\u0434\u0435, \u043A\u043E\u0433\u0434\u0430 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043C\u043E\u043B\u0447\u0438\u0442. \u0423 \u043D\u043E\u0434\u044B \u043C\u043E\u0436\u043D\u043E \u0437\u0430\u0434\u0430\u0442\u044C \u0441\u0432\u043E\u0438.",
181
+ dayFrame: "\u0420\u0430\u043C\u043A\u0438 \u0434\u043D\u044F",
182
+ dayFrameHint: "\u041F\u043E \u0447\u0438\u0441\u043B\u0443 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0437\u0430 \u0434\u0435\u043D\u044C.",
183
+ softFrom: "\u0421\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0442\u044C \u0441 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u2116",
184
+ hardAt: "\u0417\u0430\u043A\u043E\u043D\u0447\u0438\u0442\u044C \u0434\u0435\u043D\u044C \u043D\u0430 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438 \u2116",
185
+ hardText: "\u041E\u0442\u0432\u0435\u0442, \u043A\u043E\u0433\u0434\u0430 \u0434\u0435\u043D\u044C \u0437\u0430\u043A\u043E\u043D\u0447\u0435\u043D",
186
+ hardTextHint: "\u0423\u0445\u043E\u0434\u0438\u0442 \u0441\u043B\u043E\u0432\u043E \u0432 \u0441\u043B\u043E\u0432\u043E, \u0431\u0435\u0437 \u043C\u043E\u0434\u0435\u043B\u0438.",
187
+ more: "\u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u043E"
188
+ },
189
+ nudges: {
190
+ afterSilence: "\u041C\u043E\u043B\u0447\u0438\u0442 \u2014 \u043D\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0447\u0435\u0440\u0435\u0437",
191
+ addStep: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0440\u043E\u043A",
192
+ dailyAt: "\u041F\u043E\u0442\u043E\u043C \u043A\u0430\u0436\u0434\u044B\u0439 \u0434\u0435\u043D\u044C \u0432",
193
+ addTime: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0432\u0440\u0435\u043C\u044F",
194
+ giveUpAfter: "\u041C\u043E\u043B\u0447\u0438\u0442 \u0441\u0442\u043E\u043B\u044C\u043A\u043E \u2014 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u043E\u043A\u043E\u043D\u0447\u0435\u043D",
195
+ never: "\u043D\u0438\u043A\u043E\u0433\u0434\u0430",
196
+ remove: "\u0423\u0431\u0440\u0430\u0442\u044C",
197
+ on: "\u041A\u0430\u0441\u0430\u043D\u0438\u044F \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E"
198
+ },
199
+ sideTasks: {
200
+ title: "\u041F\u043E\u043F\u0443\u0442\u043D\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438",
201
+ hint: "\u0410\u0433\u0435\u043D\u0442 \u0434\u0435\u043B\u0430\u0435\u0442 \u0438\u0445 \u043C\u0435\u0436\u0434\u0443 \u0434\u0435\u043B\u043E\u043C, \u0432 \u0442\u0435\u043C\u043F\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0435. \u041D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u0437\u0430\u0434\u0430\u043D\u043D\u044B\u0445 \u043F\u043E\u043F\u044B\u0442\u043E\u043A.",
202
+ builtIn: "\xAB\u0423\u0437\u043D\u0430\u0442\u044C \u0433\u043E\u0440\u043E\u0434\xBB \u2014 \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430: \u0435\u0441\u0442\u044C \u0432\u0441\u0435\u0433\u0434\u0430, \u043A\u043E\u0433\u0434\u0430 \u0437\u0430\u0434\u0430\u043D\u044B \xAB\u0414\u043D\u0438 \u0438 \u0432\u0440\u0435\u043C\u044F\xBB.",
203
+ text: "\u0427\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C",
204
+ when: "\u041A\u043E\u0433\u0434\u0430 \u0443\u043C\u0435\u0441\u0442\u043D\u043E",
205
+ field: "\u0420\u0435\u0448\u0435\u043D\u0430, \u043A\u043E\u0433\u0434\u0430 \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E \u043F\u043E\u043B\u0435",
206
+ noField: "\u2014",
207
+ fromApp: "\u041F\u043E\u043B\u044F \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F",
208
+ maxAttempts: "\u041F\u043E\u043F\u044B\u0442\u043E\u043A",
209
+ perConversation: "\u0417\u0430\u043D\u043E\u0432\u043E \u0432 \u043A\u0430\u0436\u0434\u043E\u043C \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u0435",
210
+ add: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443",
211
+ newTask: "",
212
+ delete: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443"
213
+ },
214
+ statePanel: {
215
+ title: "\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435",
216
+ name: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435",
217
+ instructions: "\u0427\u0442\u043E \u0430\u0433\u0435\u043D\u0442 \u0437\u0434\u0435\u0441\u044C \u0434\u0435\u043B\u0430\u0435\u0442",
218
+ instructionsHint: "\u041F\u0435\u0440\u0432\u0430\u044F \u0441\u0442\u0440\u043E\u043A\u0430 \u2014 \u0446\u0435\u043B\u044C, \u0435\u0451 \u0432\u0438\u0434\u043D\u043E \u043D\u0430 \u0434\u043E\u0441\u043A\u0435. \u0414\u0430\u043B\u044C\u0448\u0435 \u2014 \u043A\u0430\u043A \u0441\u0435\u0431\u044F \u0432\u0435\u0441\u0442\u0438.",
219
+ firstMessage: "\u041F\u0435\u0440\u0432\u043E\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
220
+ learn: "\u0423\u0437\u043D\u0430\u0442\u044C \u0432 \u044D\u0442\u043E\u043C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0438",
221
+ tools: "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B",
222
+ toolsHint: "\u0410\u0433\u0435\u043D\u0442\u0443 \u0437\u0434\u0435\u0441\u044C \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u0442\u043E\u043B\u044C\u043A\u043E \u043E\u0442\u043C\u0435\u0447\u0435\u043D\u043D\u044B\u0435.",
223
+ noTools: "\u0412 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0438 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432.",
224
+ tags: "\u041C\u0435\u0442\u043A\u0438",
225
+ makeStart: "\u041D\u0430\u0447\u0438\u043D\u0430\u0442\u044C \u043E\u0442\u0441\u044E\u0434\u0430",
226
+ isStart: "\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439 \u043D\u0430\u0447\u0438\u043D\u0430\u0435\u0442\u0441\u044F \u0437\u0434\u0435\u0441\u044C",
227
+ noSideQuestions: "\u041D\u0435 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C \u0437\u0434\u0435\u0441\u044C \u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0435 \u043C\u0435\u0436\u0434\u0443 \u0434\u0435\u043B\u043E\u043C",
228
+ exits: "\u041A\u043E\u0433\u0434\u0430 \u0432\u044B\u0445\u043E\u0434\u0438\u0442\u044C \u0438 \u043A\u0443\u0434\u0430",
229
+ addExit: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0443\u0441\u043B\u043E\u0432\u0438\u0435",
230
+ more: "\u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u043E",
231
+ deleteState: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435",
232
+ duration: "\u0414\u043B\u0438\u0442\u0441\u044F",
233
+ durationHint: "\u041F\u043E\u0442\u043E\u043C \u043F\u0435\u0440\u0435\u0445\u043E\u0434 \xAB\u041A\u0443\u0434\u0430\xBB. \u0414\u043D\u0438 \u2014 \u0434\u043D\u0438 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F: \u043D\u043E\u0432\u044B\u0439 \u0434\u0435\u043D\u044C \u043D\u0430\u0447\u0438\u043D\u0430\u0435\u0442\u0441\u044F \u0432 \u043D\u0430\u0447\u0430\u043B\u0435 \u0434\u043D\u044F. \xAB\u0414\u043E \u0434\u043D\u044F \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B\xBB \u2014 \u0434\u043E \u043D\u0430\u0447\u0430\u043B\u0430 \u044D\u0442\u043E\u0433\u043E \u0434\u043D\u044F, \u043A\u043E\u0433\u0434\u0430 \u0431\u044B \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043D\u0438 \u043F\u0440\u0438\u0448\u0451\u043B \u0432 \u043D\u043E\u0434\u0443.",
234
+ durationNone: "\u043D\u0435 \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u043E",
235
+ durationUnits: { h: "\u0447\u0430\u0441\u043E\u0432", d: "\u0434\u043D\u0435\u0439", program_day: "\u0434\u043E \u0434\u043D\u044F \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B" },
236
+ durationTo: "\u041F\u043E\u0442\u043E\u043C \u2014 \u043A\u0443\u0434\u0430",
237
+ nudges: "\u041A\u0430\u0441\u0430\u043D\u0438\u044F",
238
+ nudgesModes: { inherit: "\u041A\u0430\u043A \u0443 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F", own: "\u0421\u0432\u043E\u0438", off: "\u0412\u044B\u043A\u043B" },
239
+ nudgesHint: "\u0410\u0433\u0435\u043D\u0442 \u043F\u0438\u0448\u0435\u0442 \u0437\u0434\u0435\u0441\u044C \u0441\u0430\u043C, \u043A\u043E\u0433\u0434\u0430 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043C\u043E\u043B\u0447\u0438\u0442.",
240
+ nudgesDays: "\u0421\u0432\u043E\u0438 \u043A\u0430\u0441\u0430\u043D\u0438\u044F \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044E\u0442",
241
+ nudgesDaysModes: { always: "\u0432\u0441\u0451 \u0432\u0440\u0435\u043C\u044F \u0432 \u043D\u043E\u0434\u0435", days: "\u0434\u043D., \u0441\u0447\u0438\u0442\u0430\u044F \u0434\u0435\u043D\u044C \u0432\u0445\u043E\u0434\u0430" },
242
+ nudgesDaysHint: "\u041F\u043E\u0442\u043E\u043C \u2014 \u043A\u0430\u0441\u0430\u043D\u0438\u044F \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F: \u0443\u0442\u0440\u0435\u043D\u043D\u0435\u0435 \u0438 \u043E\u0431\u0449\u0438\u0439 \u0440\u044F\u0434 \u0441 \u043D\u0430\u0447\u0430\u043B\u0430."
243
+ },
244
+ exitCard: {
245
+ when: "\u041A\u043E\u0433\u0434\u0430",
246
+ field: "\u041F\u043E\u043B\u0435",
247
+ op: "\u041A\u0430\u043A \u0441\u0440\u0430\u0432\u043D\u0438\u0442\u044C",
248
+ value: "\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435",
249
+ event: "\u0421\u043E\u0431\u044B\u0442\u0438\u0435",
250
+ eventName: "\u0427\u0442\u043E \u0438\u043C\u0435\u043D\u043D\u043E",
251
+ timer: "\u041E\u0442\u043A\u0443\u0434\u0430 \u0441\u0447\u0438\u0442\u0430\u0442\u044C",
252
+ amount: "\u0421\u043A\u043E\u043B\u044C\u043A\u043E",
253
+ at: "\u0412\u043E \u0441\u043A\u043E\u043B\u044C\u043A\u043E",
254
+ to: "\u041A\u0443\u0434\u0430",
255
+ moveUp: "\u0412\u044B\u0448\u0435",
256
+ moveDown: "\u041D\u0438\u0436\u0435",
257
+ delete: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0443\u0441\u043B\u043E\u0432\u0438\u0435"
258
+ },
259
+ anyStatePanel: {
260
+ title: "\u041E\u0431\u0449\u0438\u0435 \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u044B",
261
+ hint: "\u0420\u0430\u0431\u043E\u0442\u0430\u044E\u0442 \u0438\u0437 \u043B\u044E\u0431\u043E\u0433\u043E \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F. \u0421\u0432\u043E\u0438 \u0443\u0441\u043B\u043E\u0432\u0438\u044F \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0442\u0441\u044F \u0440\u0430\u043D\u044C\u0448\u0435.",
262
+ except: "\u041D\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044E\u0442 \u0435\u0449\u0451 \u0438 \u0437\u0434\u0435\u0441\u044C"
263
+ },
264
+ cardPanel: {
265
+ title: "\u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0438 \u0437\u0430\u043C\u0435\u0442\u043A\u0438",
266
+ clientCard: "\u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u043A\u043B\u0438\u0435\u043D\u0442\u0430",
267
+ chatNotes: "\u0417\u0430\u043C\u0435\u0442\u043A\u0438 \u044D\u0442\u043E\u0433\u043E \u0447\u0430\u0442\u0430",
268
+ chatNotesHint: "\u0416\u0438\u0432\u0443\u0442 \u0432\u0435\u0441\u044C \u0447\u0430\u0442, \u0432 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0447\u0430\u0442\u0435 \u043D\u0435 \u043F\u043E\u043C\u043D\u044F\u0442\u0441\u044F.",
269
+ empty: "\u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u043A\u043B\u0438\u0435\u043D\u0442\u0430 \u0438 \u0437\u0430\u043C\u0435\u0442\u043A\u0438 \u043F\u043E\u043A\u0430 \u043F\u0443\u0441\u0442\u044B.",
270
+ name: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435",
271
+ about: "\u0427\u0442\u043E \u044D\u0442\u043E \u0438 \u043A\u043E\u0433\u0434\u0430 \u0443\u043C\u0435\u0441\u0442\u043D\u043E \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C",
272
+ type: "\u0422\u0438\u043F",
273
+ options: "\u0412\u0430\u0440\u0438\u0430\u043D\u0442\u044B",
274
+ optionsHint: "\u0412\u0430\u0440\u0438\u0430\u043D\u0442\u044B \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E",
275
+ refreshAfter: "\u041C\u043E\u0436\u0435\u0442 \u0443\u0441\u0442\u0430\u0440\u0435\u0442\u044C",
276
+ refreshAfterMonths: (months) => `\u0447\u0435\u0440\u0435\u0437 ${months} \u043C\u0435\u0441.`,
277
+ pace: "\u0412\u043E\u043F\u0440\u043E\u0441 \u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0435 \u2014 \u0440\u0430\u0437 \u0432",
278
+ paceEvery: (messages) => `\u0440\u0430\u0437 \u0432 ${messages} \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439`,
279
+ paceHint: "\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u043F\u044B\u0442\u0430\u0435\u0442\u0441\u044F \u043F\u043E\u043D\u044F\u0442\u044C \u0438\u0437 \u0441\u043B\u043E\u0432, \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u2014 \u0435\u0441\u043B\u0438 \u043D\u0435 \u0432\u044B\u0448\u043B\u043E. \u041D\u0435 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442, \u0435\u0441\u043B\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0440\u0430\u0437\u0434\u0440\u0430\u0436\u0451\u043D \u0438\u043B\u0438 \u0442\u043E\u0440\u043E\u043F\u0438\u0442\u0441\u044F.",
280
+ addField: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u043E\u043B\u0435",
281
+ newField: "\u041D\u043E\u0432\u043E\u0435 \u043F\u043E\u043B\u0435",
282
+ fromApp: "\u041F\u043E\u043B\u0435 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F\u2026"
283
+ },
284
+ checkPanel: {
285
+ title: "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430",
286
+ clean: "\u041F\u0440\u043E\u0431\u043B\u0435\u043C \u043D\u0435 \u043D\u0430\u0448\u043B\u043E\u0441\u044C.",
287
+ errors: "\u041E\u0448\u0438\u0431\u043A\u0438",
288
+ warnings: "\u0417\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F",
289
+ goTo: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C",
290
+ appCheckFailed: "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u043D\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u043B\u0438 \u2014 \u0438\u0445 \u043D\u0430\u0445\u043E\u0434\u043E\u043A \u0437\u0434\u0435\u0441\u044C \u043D\u0435\u0442"
291
+ },
292
+ runPanel: {
293
+ title: "\u041F\u0440\u043E\u0433\u043E\u043D",
294
+ hint: "\u041F\u0440\u043E\u0433\u043E\u043D: \u0432\u044B\u0431\u0438\u0440\u0430\u0439\u0442\u0435 \u0441\u043F\u0440\u0430\u0432\u0430, \u0447\u0442\u043E \u043F\u0440\u043E\u0438\u0437\u043E\u0448\u043B\u043E",
295
+ current: "\u0413\u0434\u0435 \u0441\u0435\u0439\u0447\u0430\u0441 \u0430\u0433\u0435\u043D\u0442",
296
+ whatHappened: "\u0427\u0442\u043E \u043F\u0440\u043E\u0438\u0437\u043E\u0448\u043B\u043E?",
297
+ nothing: "\u041D\u0438\u0447\u0435\u0433\u043E \u0438\u0437 \u044D\u0442\u043E\u0433\u043E",
298
+ known: "\u0427\u0442\u043E \u0430\u0433\u0435\u043D\u0442 \u0437\u043D\u0430\u0435\u0442",
299
+ notYet: "\u041F\u043E\u043A\u0430 \u043D\u0435\u0442",
300
+ nextQuestion: "\u0421\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0435",
301
+ path: "\u041F\u0440\u043E\u0439\u0434\u0435\u043D\u043D\u044B\u0439 \u043F\u0443\u0442\u044C",
302
+ back: "\u0428\u0430\u0433 \u043D\u0430\u0437\u0430\u0434",
303
+ restart: "\u0421\u043D\u0430\u0447\u0430\u043B\u0430",
304
+ finish: "\u0417\u0430\u043A\u043E\u043D\u0447\u0438\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D",
305
+ step: (step2) => `\u041F\u0440\u043E\u0433\u043E\u043D \xB7 \u0448\u0430\u0433 ${step2}`,
306
+ stay: "\u043E\u0441\u0442\u0430\u0442\u044C\u0441\u044F \u0437\u0434\u0435\u0441\u044C",
307
+ wontFire: "\u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442",
308
+ nothingHappened: "\u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u043B\u043E",
309
+ noStart: "\u041D\u0435\u0442 \u0441\u0442\u0430\u0440\u0442\u043E\u0432\u043E\u0433\u043E \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F: \u043E\u0442\u043C\u0435\u0442\u044C\u0442\u0435, \u0433\u0434\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0435\u0442\u0441\u044F \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439"
310
+ },
311
+ jsonDialog: {
312
+ title: "\u0413\u0440\u0430\u0444 \u0432 \u0444\u043E\u0440\u043C\u0430\u0442\u0435 JSON",
313
+ copy: "\u041A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C",
314
+ copied: "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u043E",
315
+ download: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C",
316
+ load: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C",
317
+ close: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C"
318
+ }
319
+ };
320
+ var englishFlowEditorLabels = {
321
+ conditionKinds: { judge: "In words", data: "Data", event: "Event", timer: "Timer" },
322
+ conditionKindHints: {
323
+ judge: "In words \u2014 the agent decides, the judge checks",
324
+ data: "By data \u2014 a field is filled, a value equals\u2026",
325
+ event: "Event \u2014 a button, a tool, an app signal",
326
+ timer: "Timer \u2014 by the clock"
327
+ },
328
+ returnKind: "Back",
329
+ returnTarget: "back where it was",
330
+ ops: { filled: "is filled", eq: "=", ne: "\u2260", gt: ">", lt: "<", contains: "contains" },
331
+ events: { message: "Message arrived", button: "Button pressed", tool: "Tool called", app: "App event" },
332
+ timerSpans: {
333
+ in_state: "Time in this state",
334
+ silence: "Person is silent",
335
+ since_start: "Since the chat started",
336
+ time_of_day: "Time of day reached"
337
+ },
338
+ units: { min: "min", h: "h", d: "d" },
339
+ fieldTypes: { text: "Text", number: "Number", choice: "Choice", boolean: "Yes/no" },
340
+ yes: "yes",
341
+ no: "no",
342
+ days: (count) => `${count} ${count === 1 ? "day" : "days"}`,
343
+ programDay: (day) => `from day ${day}`,
344
+ header: {
345
+ state: "State",
346
+ card: "Card and notes",
347
+ check: "Check the graph",
348
+ run: "Dry run",
349
+ json: "JSON",
350
+ save: "Save",
351
+ saved: "Saved",
352
+ unsaved: "Unsaved changes",
353
+ saveFailed: "Not saved",
354
+ saveFailedHint: "Saving was rejected. See what is wrong in \u201CCheck flow\u201D and on the nodes.",
355
+ readOnly: "View only",
356
+ readOnlyHint: "An agent is editing this flow right now. You can look and run it, but not edit.",
357
+ migrate: "Move chats"
358
+ },
359
+ board: {
360
+ anyState: "From any state",
361
+ start: "The flow starts here",
362
+ final: "End",
363
+ noSideQuestions: "No questions about the person",
364
+ noTarget: "target not chosen",
365
+ emptyCondition: "condition not written",
366
+ newState: "New state",
367
+ zoomIn: "Zoom in",
368
+ zoomOut: "Zoom out",
369
+ fit: "Whole graph",
370
+ errors: (count) => `Errors: ${count}`,
371
+ warnings: (count) => `Warnings: ${count}`,
372
+ nudges: "nudges",
373
+ noNudges: "no nudges",
374
+ nudgesDays: (days) => days === 1 ? "on the entry day" : `for the first ${days} days`,
375
+ space: {
376
+ legend: "Legend",
377
+ showMinimap: "Show the minimap",
378
+ hideMinimap: "Hide the minimap",
379
+ showInspector: "Show the flow panel",
380
+ hideInspector: "Hide the flow panel",
381
+ enterFullscreen: "Full screen",
382
+ exitFullscreen: "Exit full screen",
383
+ fullscreenRegion: "Flow editor, full screen"
384
+ }
385
+ },
386
+ overview: {
387
+ title: "Flow",
388
+ common: "Common to all states",
389
+ commonHint: "Role, tone, prices, terms, links \u2014 whatever holds in every state.",
390
+ states: "States",
391
+ exits: "Exit conditions",
392
+ finals: "Final",
393
+ lists: "Lists",
394
+ help: "How to use",
395
+ flowIssues: "Whole flow",
396
+ afterEnd: "After the conversation ends",
397
+ afterEndModes: { reply: "Reply", silent: "Stay silent" },
398
+ afterEndHints: {
399
+ reply: "The agent replies from the common part: it knows the program is over, does not lead through steps or sell again.",
400
+ silent: "The agent stays silent; the app receives the message."
401
+ }
402
+ },
403
+ schedule: {
404
+ title: "Days and time",
405
+ on: "Day-by-day flow",
406
+ hint: "Days, night, total length and first messages. The agent sees the day number on every turn.",
407
+ dayStart: "Day starts at",
408
+ dayStartHint: "The morning message goes out at this time.",
409
+ lengthDays: "How many days",
410
+ lengthDaysNone: "no limit",
411
+ nightFrom: "Night from",
412
+ nightFromHint: "At night the agent never writes first. Empty \u2014 as in the app settings.",
413
+ lateStart: "Late start, h",
414
+ lateStartHint: "Arrived less than this many hours before night \u2014 day one is tomorrow. 0 \u2014 the arrival day is day one.",
415
+ deadlineState: "Time is up \u2014 go to",
416
+ deadlineNone: "end right away",
417
+ farewell: "Say goodbye when time is up",
418
+ farewellText: "Ready-made goodbye",
419
+ farewellTextHint: "Empty \u2014 the agent writes the goodbye.",
420
+ firstMessages: "Writing first",
421
+ maxPerDay: "At most per day",
422
+ maxPerDayHint: "Empty \u2014 as in the app settings.",
423
+ morning: "Morning message",
424
+ skipMorning: "Skip the morning if the person wrote within, min",
425
+ nudges: "Default nudges",
426
+ nudgesHint: "The agent writes in the same state while the person is silent. A state can have its own.",
427
+ dayFrame: "Daily limits",
428
+ dayFrameHint: "By the number of the person's messages per day.",
429
+ softFrom: "Wrap up from message #",
430
+ hardAt: "End the day at message #",
431
+ hardText: "Reply when the day is over",
432
+ hardTextHint: "Sent word for word, without the model.",
433
+ more: "More"
434
+ },
435
+ nudges: {
436
+ afterSilence: "Silent \u2014 write after",
437
+ addStep: "Add a delay",
438
+ dailyAt: "Then every day at",
439
+ addTime: "Add a time",
440
+ giveUpAfter: "Silent this long \u2014 conversation over",
441
+ never: "never",
442
+ remove: "Remove",
443
+ on: "Default nudges"
444
+ },
445
+ sideTasks: {
446
+ title: "Side tasks",
447
+ hint: "The agent does them in passing, at the pace of questions about the person. No more than the given attempts.",
448
+ builtIn: "\u201CFind out the city\u201D is built in: always on when \u201CDays and time\u201D are set.",
449
+ text: "What to do",
450
+ when: "When it fits",
451
+ field: "Done when this field is known",
452
+ noField: "\u2014",
453
+ fromApp: "App fields",
454
+ maxAttempts: "Attempts",
455
+ perConversation: "Anew in every conversation",
456
+ add: "Add a task",
457
+ newTask: "",
458
+ delete: "Delete the task"
459
+ },
460
+ statePanel: {
461
+ title: "State",
462
+ name: "Name",
463
+ instructions: "What the agent does here",
464
+ instructionsHint: "The first line is the goal, shown on the board. Then \u2014 how to behave.",
465
+ firstMessage: "First message",
466
+ learn: "Learn in this state",
467
+ tools: "Tools",
468
+ toolsHint: "Only the checked tools are available here.",
469
+ noTools: "The flow has no tools yet.",
470
+ tags: "Tags",
471
+ makeStart: "Start here",
472
+ isStart: "The flow starts here",
473
+ noSideQuestions: "No side questions about the person here",
474
+ exits: "When to leave and where to",
475
+ addExit: "Add a condition",
476
+ more: "More",
477
+ deleteState: "Delete the state",
478
+ duration: "Lasts",
479
+ durationHint: "Then goes to \u201CGo to\u201D. Days are flow days: a new day begins at the day start. \u201CUntil program day\u201D lasts until that day starts, whenever the person entered the node.",
480
+ durationNone: "no limit",
481
+ durationUnits: { h: "hours", d: "days", program_day: "until program day" },
482
+ durationTo: "Then go to",
483
+ nudges: "Nudges",
484
+ nudgesModes: { inherit: "As in the flow", own: "Own", off: "Off" },
485
+ nudgesHint: "The agent writes here by itself while the person is silent.",
486
+ nudgesDays: "Own nudges apply",
487
+ nudgesDaysModes: { always: "the whole time in the state", days: "days, counting the entry day" },
488
+ nudgesDaysHint: "Then the flow's nudges: the morning message and the common series from the start."
489
+ },
490
+ exitCard: {
491
+ when: "When",
492
+ field: "Field",
493
+ op: "Compare",
494
+ value: "Value",
495
+ event: "Event",
496
+ eventName: "Which one",
497
+ timer: "Count from",
498
+ amount: "How long",
499
+ at: "At",
500
+ to: "Go to",
501
+ moveUp: "Up",
502
+ moveDown: "Down",
503
+ delete: "Delete the condition"
504
+ },
505
+ anyStatePanel: {
506
+ title: "Common transitions",
507
+ hint: "Work from any state. The state's own conditions are checked first.",
508
+ except: "Also not active here"
509
+ },
510
+ cardPanel: {
511
+ title: "Card and notes",
512
+ clientCard: "Client card",
513
+ chatNotes: "Notes of this chat",
514
+ chatNotesHint: "Live for the whole chat and are not remembered in the next one.",
515
+ empty: "The client card and notes are empty so far.",
516
+ name: "Name",
517
+ about: "What it is and when to ask",
518
+ type: "Type",
519
+ options: "Options",
520
+ optionsHint: "Options separated by commas",
521
+ refreshAfter: "May go stale",
522
+ refreshAfterMonths: (months) => `in ${months} mo.`,
523
+ pace: "A question about the person \u2014 once per",
524
+ paceEvery: (messages) => `once per ${messages} messages`,
525
+ paceHint: "First tries to understand from what was said and asks only if that fails. Does not ask when the person is annoyed or in a hurry.",
526
+ addField: "Add a field",
527
+ newField: "New field",
528
+ fromApp: "App field\u2026"
529
+ },
530
+ checkPanel: {
531
+ title: "Check",
532
+ clean: "No problems found.",
533
+ errors: "Errors",
534
+ warnings: "Warnings",
535
+ goTo: "Open",
536
+ appCheckFailed: "The app's checks failed \u2014 their findings are missing here"
537
+ },
538
+ runPanel: {
539
+ title: "Dry run",
540
+ hint: "Dry run: choose on the right what happened",
541
+ current: "Where the agent is",
542
+ whatHappened: "What happened?",
543
+ nothing: "None of these",
544
+ known: "What the agent knows",
545
+ notYet: "Not yet",
546
+ nextQuestion: "Next question about the person",
547
+ path: "Path so far",
548
+ back: "Step back",
549
+ restart: "Restart",
550
+ finish: "Finish the dry run",
551
+ step: (step2) => `Dry run \xB7 step ${step2}`,
552
+ stay: "stay here",
553
+ wontFire: "won't fire now",
554
+ nothingHappened: "nothing fired",
555
+ noStart: "No start state: mark where the flow begins"
556
+ },
557
+ jsonDialog: {
558
+ title: "The graph as JSON",
559
+ copy: "Copy",
560
+ copied: "Copied",
561
+ download: "Download",
562
+ load: "Load",
563
+ close: "Close"
564
+ }
565
+ };
566
+
567
+ // src/labels/migrate.ts
568
+ var russianFlowMigrateLabels = {
569
+ title: "\u041F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0447\u0430\u0442\u044B \u043D\u0430 \u043D\u043E\u0432\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E",
570
+ hint: "\u0427\u0430\u0442\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442 \u0441 \u0442\u043E\u0433\u043E \u0436\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F. \u0414\u043B\u044F \u0443\u0434\u0430\u043B\u0451\u043D\u043D\u044B\u0445 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0439 \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435, \u043A\u0443\u0434\u0430 \u043F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438.",
571
+ version: "\u0412\u0435\u0440\u0441\u0438\u044F",
572
+ state: "\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435",
573
+ chats: "\u0427\u0430\u0442\u043E\u0432",
574
+ removed: "\u0443\u0434\u0430\u043B\u0435\u043D\u043E \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438",
575
+ target: "\u041A\u0443\u0434\u0430",
576
+ chooseTarget: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435",
577
+ migrate: "\u041F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438",
578
+ moved: (count) => `\u041F\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043D\u043E \u0447\u0430\u0442\u043E\u0432: ${count}`,
579
+ nothingToMigrate: "\u0412\u0441\u0435 \u0447\u0430\u0442\u044B \u0443\u0436\u0435 \u043D\u0430 \u044D\u0442\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438.",
580
+ close: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C",
581
+ failed: "\u041D\u0435 \u043F\u043E\u043B\u0443\u0447\u0438\u043B\u043E\u0441\u044C \u043F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437"
582
+ };
583
+ var englishFlowMigrateLabels = {
584
+ title: "Move chats to the new version",
585
+ hint: "Chats continue from the same state. For removed states choose where to move them.",
586
+ version: "Version",
587
+ state: "State",
588
+ chats: "Chats",
589
+ removed: "removed in the new version",
590
+ target: "Move to",
591
+ chooseTarget: "Choose a state",
592
+ migrate: "Move",
593
+ moved: (count) => `Chats moved: ${count}`,
594
+ nothingToMigrate: "All chats are already on this version.",
595
+ close: "Close",
596
+ failed: "Could not move the chats. Try again"
597
+ };
598
+
599
+ // src/labels/test-chat.ts
600
+ var russianFlowTestChatLabels = {
601
+ title: "\u0422\u0435\u0441\u0442\u043E\u0432\u044B\u0439 \u0447\u0430\u0442",
602
+ state: "\u0421\u0435\u0439\u0447\u0430\u0441 \u0432 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0438",
603
+ advance: (label) => `+${label}`,
604
+ clock: "\u0427\u0430\u0441\u044B \u0447\u0430\u0442\u0430",
605
+ noOffset: "\u043A\u0430\u043A \u0441\u0435\u0439\u0447\u0430\u0441",
606
+ units: { day: "\u0434", hour: "\u0447", minute: "\u043C\u0438\u043D" },
607
+ reset: "\u041D\u0430\u0447\u0430\u0442\u044C \u0437\u0430\u043D\u043E\u0432\u043E",
608
+ close: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C",
609
+ failed: "\u041D\u0435 \u043F\u043E\u043B\u0443\u0447\u0438\u043B\u043E\u0441\u044C \u2014 \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437",
610
+ nextDay: "\u041A \u043D\u0430\u0447\u0430\u043B\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u0434\u043D\u044F",
611
+ day: (day, totalDays) => totalDays === void 0 ? `\u0414\u0435\u043D\u044C ${day}` : `\u0414\u0435\u043D\u044C ${day} \u0438\u0437 ${totalDays}`,
612
+ beforeFirstDay: "\u0414\u043E \u043F\u0435\u0440\u0432\u043E\u0433\u043E \u0434\u043D\u044F"
613
+ };
614
+ var englishFlowTestChatLabels = {
615
+ title: "Test chat",
616
+ state: "Current state",
617
+ advance: (label) => `+${label}`,
618
+ clock: "Chat clock",
619
+ noOffset: "now",
620
+ units: { day: "d", hour: "h", minute: "min" },
621
+ reset: "Start over",
622
+ close: "Close",
623
+ failed: "Something went wrong \u2014 try again",
624
+ nextDay: "To the start of the next day",
625
+ day: (day, totalDays) => totalDays === void 0 ? `Day ${day}` : `Day ${day} of ${totalDays}`,
626
+ beforeFirstDay: "Before day one"
627
+ };
628
+
629
+ // src/labels/index.ts
630
+ var russianFlowLabels = {
631
+ editor: russianFlowEditorLabels,
632
+ clientCard: russianClientCardLabels,
633
+ migrate: russianFlowMigrateLabels,
634
+ testChat: russianFlowTestChatLabels
635
+ };
636
+ var englishFlowLabels = {
637
+ editor: englishFlowEditorLabels,
638
+ clientCard: englishClientCardLabels,
639
+ migrate: englishFlowMigrateLabels,
640
+ testChat: englishFlowTestChatLabels
641
+ };
642
+
643
+ // src/react/flow-labels.ts
644
+ var FlowLabelsContext = createContext(russianFlowLabels);
645
+ function FlowLabelsProvider({ labels, children }) {
646
+ return createElement(FlowLabelsContext.Provider, { value: labels }, children);
647
+ }
648
+ function useFlowLabels() {
649
+ return useContext(FlowLabelsContext);
650
+ }
651
+
652
+ // src/react/editor/flow-editor.tsx
653
+ import { useMemo as useMemo4, useState as useState4 } from "react";
654
+
655
+ // src/react/editor/editor-model.ts
656
+ function freeId(prefix, taken) {
657
+ const used = new Set(taken);
658
+ let index = 1;
659
+ while (used.has(`${prefix}_${index}`)) index += 1;
660
+ return `${prefix}_${index}`;
661
+ }
662
+ function allExits(flow) {
663
+ return [...flow.anyState.exits, ...flow.states.flatMap((state) => state.exits)];
664
+ }
665
+ function allFields(flow) {
666
+ return [...flow.clientCard, ...flow.chatNotes];
667
+ }
668
+ function editState(flow, stateId, edit) {
669
+ return { ...flow, states: flow.states.map((state) => state.id === stateId ? edit(state) : state) };
670
+ }
671
+ function editExits(flow, owner, edit) {
672
+ if (owner === null) return { ...flow, anyState: { ...flow.anyState, exits: edit(flow.anyState.exits) } };
673
+ return editState(flow, owner, (state) => ({ ...state, exits: edit(state.exits) }));
674
+ }
675
+ function exitsOf(flow, owner) {
676
+ if (owner === null) return flow.anyState.exits;
677
+ return flow.states.find((state) => state.id === owner)?.exits ?? [];
678
+ }
679
+ function shift(items, index, delta) {
680
+ const target = index + delta;
681
+ if (index < 0 || target < 0 || target >= items.length) return items;
682
+ const next = [...items];
683
+ const [item] = next.splice(index, 1);
684
+ next.splice(target, 0, item);
685
+ return next;
686
+ }
687
+ function addState(flow, input) {
688
+ const stateId = freeId("state", flow.states.map((state2) => state2.id));
689
+ const state = {
690
+ id: stateId,
691
+ kind: "state",
692
+ title: input.title,
693
+ instructions: "",
694
+ firstMessage: null,
695
+ learn: [],
696
+ noSideQuestions: false,
697
+ tools: [],
698
+ tags: [],
699
+ exits: []
700
+ };
701
+ return {
702
+ stateId,
703
+ flow: {
704
+ ...flow,
705
+ start: flow.states.length === 0 ? stateId : flow.start,
706
+ states: [...flow.states, state],
707
+ layout: input.at ? { ...flow.layout, [stateId]: input.at } : flow.layout
708
+ }
709
+ };
710
+ }
711
+ function removeState(flow, stateId) {
712
+ const unlink = (exits) => exits.map((exit) => exit.to === stateId ? { ...exit, to: null } : exit);
713
+ const { [stateId]: _removed, ...layout } = flow.layout;
714
+ return {
715
+ ...flow,
716
+ start: flow.start === stateId ? null : flow.start,
717
+ states: flow.states.filter((state) => state.id !== stateId).map((state) => ({ ...state, exits: unlink(state.exits) })),
718
+ anyState: { except: flow.anyState.except.filter((id) => id !== stateId), exits: unlink(flow.anyState.exits) },
719
+ layout
720
+ };
721
+ }
722
+ function updateState(flow, stateId, patch) {
723
+ return editState(flow, stateId, (state) => ({ ...state, ...patch }));
724
+ }
725
+ function setStart(flow, stateId) {
726
+ return { ...flow, start: stateId };
727
+ }
728
+ function setExcept(flow, except) {
729
+ return { ...flow, anyState: { ...flow.anyState, except } };
730
+ }
731
+ function defaultCondition(kind, flow) {
732
+ switch (kind) {
733
+ case "judge":
734
+ return { type: "judge", text: "" };
735
+ case "data":
736
+ return { type: "data", field: allFields(flow)[0]?.id ?? "field", op: "filled" };
737
+ case "event":
738
+ return { type: "event", event: "message" };
739
+ case "timer":
740
+ return { type: "timer", from: "in_state", amount: 1, unit: "h" };
741
+ }
742
+ }
743
+ function normalizeCondition(flow, previous, next) {
744
+ if (next.type !== previous.type) return defaultCondition(next.type, flow);
745
+ if (next.type === "data") {
746
+ const type = fieldById(flow, next.field)?.field.type;
747
+ if (type === void 0) return next;
748
+ if (!FLOW_OPS_BY_FIELD_TYPE[type].includes(next.op)) return { type: "data", field: next.field, op: "filled" };
749
+ if (next.op === "filled") return { type: "data", field: next.field, op: "filled" };
750
+ if (type === "boolean") return { type: "data", field: next.field, op: next.op, value: next.value !== false && next.value !== "false" };
751
+ if (type === "number" && typeof next.value === "string" && next.value.trim() !== "" && Number.isFinite(Number(next.value))) {
752
+ return { ...next, value: Number(next.value) };
753
+ }
754
+ return { ...next, value: next.value ?? "" };
755
+ }
756
+ if (next.type === "timer") {
757
+ if (next.from === "time_of_day") return { type: "timer", from: "time_of_day", at: "at" in next && next.at ? next.at : "10:00" };
758
+ const days = "days" in next && next.days ? next.days : "day" in next && next.day ? next.day : 1;
759
+ if (next.from === "state_days") return { type: "timer", from: "state_days", days };
760
+ if (next.from === "program_day") return { type: "timer", from: "program_day", day: days };
761
+ const span = next;
762
+ return { type: "timer", from: next.from, amount: span.amount ?? 1, unit: span.unit ?? "h" };
763
+ }
764
+ if (next.type === "event" && next.event === "message") return { type: "event", event: "message" };
765
+ return next;
766
+ }
767
+ function addExit(flow, owner, kind) {
768
+ const exitId = freeId(owner ?? "any", allExits(flow).map((exit2) => exit2.id));
769
+ const exit = { id: exitId, when: defaultCondition(kind, flow), to: null };
770
+ return { exitId, flow: editExits(flow, owner, (exits) => [...exits, exit]) };
771
+ }
772
+ function updateExit(flow, owner, exitId, next) {
773
+ return editExits(flow, owner, (exits) => exits.map((exit) => exit.id === exitId ? { ...exit, ...next } : exit));
774
+ }
775
+ function connectExit(flow, owner, exitId, to) {
776
+ return editExits(flow, owner, (exits) => exits.map((exit) => exit.id === exitId ? { ...exit, to } : exit));
777
+ }
778
+ function moveExit(flow, owner, exitId, delta) {
779
+ const exits = exitsOf(flow, owner);
780
+ const moved = shift(exits, exits.findIndex((exit) => exit.id === exitId), delta);
781
+ return moved === exits ? flow : editExits(flow, owner, () => moved);
782
+ }
783
+ function removeExit(flow, owner, exitId) {
784
+ return editExits(flow, owner, (exits) => exits.filter((exit) => exit.id !== exitId));
785
+ }
786
+ function addField(flow, list, name) {
787
+ const fieldId = freeId("field", allFields(flow).map((field) => field.id));
788
+ const base = { id: fieldId, name, about: "", type: "text", options: [] };
789
+ if (list === "clientCard") {
790
+ return { fieldId, flow: { ...flow, clientCard: [...flow.clientCard, { ...base, refreshAfterMonths: null }] } };
791
+ }
792
+ return { fieldId, flow: { ...flow, chatNotes: [...flow.chatNotes, base] } };
793
+ }
794
+ function updateField(flow, list, fieldId, patch) {
795
+ if (list === "clientCard") {
796
+ return { ...flow, clientCard: flow.clientCard.map((field) => field.id === fieldId ? { ...field, ...patch } : field) };
797
+ }
798
+ const { refreshAfterMonths: _cardOnly, ...notePatch } = patch;
799
+ return { ...flow, chatNotes: flow.chatNotes.map((field) => field.id === fieldId ? { ...field, ...notePatch } : field) };
800
+ }
801
+ function moveField(flow, list, fieldId, delta) {
802
+ const fields = flow[list];
803
+ const moved = shift(fields, fields.findIndex((field) => field.id === fieldId), delta);
804
+ return moved === fields ? flow : { ...flow, [list]: moved };
805
+ }
806
+ function removeField(flow, list, fieldId) {
807
+ const fields = flow[list];
808
+ return {
809
+ ...flow,
810
+ [list]: fields.filter((field) => field.id !== fieldId),
811
+ states: flow.states.map((state) => state.learn.includes(fieldId) ? { ...state, learn: state.learn.filter((id) => id !== fieldId) } : state)
812
+ };
813
+ }
814
+ function freeSpot(flow) {
815
+ const points = Object.values(flow.layout);
816
+ if (points.length === 0) return { x: 80, y: 80 };
817
+ return { x: Math.min(...points.map((point) => point.x)), y: Math.max(...points.map((point) => point.y)) + 240 };
818
+ }
819
+ function placeNode(flow, nodeId, at) {
820
+ return { ...flow, layout: { ...flow.layout, [nodeId]: { x: Math.round(at.x), y: Math.round(at.y) } } };
821
+ }
822
+ function flowToJson(flow) {
823
+ return `${JSON.stringify(keepJudgeText(compactFlow(flow)), null, 2)}
824
+ `;
825
+ }
826
+ function keepJudgeText(json) {
827
+ const fix = (exits) => exits?.forEach((exit) => {
828
+ if (exit.when?.type === "judge" && exit.when.text === void 0) exit.when.text = "";
829
+ });
830
+ json.states.forEach((state) => fix(state.exits));
831
+ fix(json.anyState?.exits);
832
+ return json;
833
+ }
834
+ function loadFlowJson(text) {
835
+ let input;
836
+ try {
837
+ input = JSON.parse(text);
838
+ } catch (error) {
839
+ return { ok: false, errors: [`JSON: ${error instanceof Error ? error.message : String(error)}`] };
840
+ }
841
+ const result = parseFlow(input);
842
+ if (result.ok) return result;
843
+ return { ok: false, errors: result.errors.map((error) => error.path ? `${error.path}: ${error.message}` : error.message) };
844
+ }
845
+
846
+ // src/react/editor/exit-text.ts
847
+ function conditionText(flow, when, labels, catalog) {
848
+ switch (when.type) {
849
+ case "judge":
850
+ return when.text.trim() || labels.board.emptyCondition;
851
+ case "data": {
852
+ const name = fieldById(flow, when.field)?.field.name || when.field;
853
+ const op = labels.ops[when.op];
854
+ if (typeof when.value === "boolean") return `${name} ${op} ${when.value ? labels.yes : labels.no}`;
855
+ return when.op === "filled" || when.value === void 0 ? `${name} ${op}` : `${name} ${op} ${when.value}`;
856
+ }
857
+ case "event":
858
+ if (when.event === "message" || !when.name) return labels.events[when.event];
859
+ return `${labels.events[when.event]} \xAB${when.event !== "tool" && catalogEventLabel(catalog, when.name) || when.name}\xBB`;
860
+ case "timer":
861
+ if (when.from === "time_of_day") return `${labels.timerSpans.time_of_day} ${when.at}`;
862
+ if (when.from === "state_days") return `${labels.statePanel.duration} ${labels.days(when.days)}`;
863
+ if (when.from === "program_day") return labels.programDay(when.day);
864
+ return `${labels.timerSpans[when.from]} ${when.amount} ${labels.units[when.unit]}`;
865
+ }
866
+ }
867
+ function edgeKind(exit) {
868
+ return exit.to === RETURN_TARGET ? "return" : exit.when.type;
869
+ }
870
+ function targetTitle(flow, to, labels) {
871
+ if (to === null) return null;
872
+ if (to === RETURN_TARGET) return labels.returnTarget;
873
+ const state = flow.states.find((candidate) => candidate.id === to);
874
+ return state ? state.title || state.id : null;
875
+ }
876
+ function stateGoal(instructions) {
877
+ return instructions.split("\n").find((line) => line.trim() !== "")?.trim() ?? "";
878
+ }
879
+
880
+ // src/react/editor/board-space.ts
881
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
882
+ var DEFAULT_SPACE_STORAGE_KEY = "agent-chat-flow-board";
883
+ function readRemembered(key) {
884
+ if (!key || typeof window === "undefined") return {};
885
+ try {
886
+ const parsed = JSON.parse(window.localStorage.getItem(key) ?? "null");
887
+ if (typeof parsed !== "object" || parsed === null) return {};
888
+ const result = {};
889
+ for (const name of ["legend", "minimap", "inspector"]) {
890
+ const value = parsed[name];
891
+ if (typeof value === "boolean") result[name] = value;
892
+ }
893
+ return result;
894
+ } catch {
895
+ return {};
896
+ }
897
+ }
898
+ function writeRemembered(key, value) {
899
+ if (!key || typeof window === "undefined") return;
900
+ try {
901
+ window.localStorage.setItem(key, JSON.stringify(value));
902
+ } catch {
903
+ }
904
+ }
905
+ var selectionKey = (selection) => {
906
+ if (selection.kind === "state") return `state:${selection.stateId}:${selection.exitId ?? ""}`;
907
+ if (selection.kind === "any") return `any:${selection.exitId ?? ""}`;
908
+ return selection.kind === "overview" ? `overview:${selection.sideTaskId ?? ""}` : selection.kind;
909
+ };
910
+ function useBoardSpace(props, editor) {
911
+ const { header = "full", legend = "collapsed", minimap = "open", inspector = "open", fullscreen: fullscreenAllowed = true } = props;
912
+ const storageKey = props.storageKey === void 0 ? DEFAULT_SPACE_STORAGE_KEY : props.storageKey;
913
+ const [remembered, setRemembered] = useState({});
914
+ useEffect(() => setRemembered(readRemembered(storageKey)), [storageKey]);
915
+ const unsaved = useRef(false);
916
+ useEffect(() => {
917
+ if (!unsaved.current) return;
918
+ unsaved.current = false;
919
+ writeRemembered(storageKey, remembered);
920
+ }, [remembered, storageKey]);
921
+ const remember = useCallback((name, value) => {
922
+ unsaved.current = true;
923
+ setRemembered((current) => ({ ...current, [name]: value }));
924
+ }, []);
925
+ const legendOpen = legend === "hidden" ? false : remembered.legend ?? legend === "open";
926
+ const minimapOpen = minimap === "hidden" ? false : remembered.minimap ?? minimap === "open";
927
+ const selected = editor.selection.kind !== "overview" || editor.selection.sideTaskId !== void 0;
928
+ const key = selectionKey(editor.selection);
929
+ const [followed, setFollowed] = useState(selected);
930
+ const [previousKey, setPreviousKey] = useState(key);
931
+ if (key !== previousKey) {
932
+ setPreviousKey(key);
933
+ setFollowed(selected);
934
+ }
935
+ const needed = editor.mode !== "edit";
936
+ const inspectorOpen = needed || (inspector === "collapsible" ? followed : remembered.inspector ?? true);
937
+ const [fullscreen, setFullscreen] = useState(false);
938
+ useEffect(() => {
939
+ if (!fullscreenAllowed) setFullscreen(false);
940
+ }, [fullscreenAllowed]);
941
+ return useMemo(
942
+ () => ({
943
+ header,
944
+ legendHidden: legend === "hidden",
945
+ legendOpen,
946
+ toggleLegend: () => remember("legend", !legendOpen),
947
+ minimapHidden: minimap === "hidden",
948
+ minimapOpen,
949
+ toggleMinimap: () => remember("minimap", !minimapOpen),
950
+ inspectorOpen,
951
+ inspectorToggleable: !needed,
952
+ toggleInspector: () => {
953
+ if (needed) return;
954
+ if (inspector === "collapsible") setFollowed(!inspectorOpen);
955
+ else remember("inspector", !inspectorOpen);
956
+ },
957
+ fullscreenAllowed,
958
+ fullscreen: fullscreenAllowed && fullscreen,
959
+ setFullscreen
960
+ }),
961
+ [header, legend, legendOpen, minimap, minimapOpen, inspector, inspectorOpen, needed, fullscreenAllowed, fullscreen, remember]
962
+ );
963
+ }
964
+ var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
965
+ function useFullscreenBehavior(active, onExit, blocked, root) {
966
+ useEffect(() => {
967
+ if (!active) return;
968
+ const previous = document.body.style.overflow;
969
+ document.body.style.overflow = "hidden";
970
+ return () => {
971
+ document.body.style.overflow = previous;
972
+ };
973
+ }, [active]);
974
+ useEffect(() => {
975
+ if (!active) return;
976
+ const onKey = (event) => {
977
+ if (event.key !== "Escape" || event.defaultPrevented || blocked) return;
978
+ const target = event.target;
979
+ if (target instanceof HTMLElement && root?.contains(target) && target.matches('input, textarea, select, [contenteditable]:not([contenteditable="false"])')) {
980
+ event.preventDefault();
981
+ target.blur();
982
+ return;
983
+ }
984
+ event.preventDefault();
985
+ onExit();
986
+ root?.querySelector("[data-flow-fullscreen-toggle]")?.focus();
987
+ };
988
+ document.addEventListener("keydown", onKey);
989
+ return () => document.removeEventListener("keydown", onKey);
990
+ }, [active, blocked, onExit, root]);
991
+ return useCallback(
992
+ (event) => {
993
+ if (!active || event.key !== "Tab" || !root || !(event.target instanceof Node) || !root.contains(event.target)) return;
994
+ const items = [...root.querySelectorAll(FOCUSABLE)].filter((item) => item.getClientRects().length > 0);
995
+ const first = items[0];
996
+ const last = items[items.length - 1];
997
+ if (!first || !last) return;
998
+ const current = document.activeElement;
999
+ if (event.shiftKey && (current === first || !root.contains(current))) {
1000
+ event.preventDefault();
1001
+ last.focus();
1002
+ } else if (!event.shiftKey && (current === last || !root.contains(current))) {
1003
+ event.preventDefault();
1004
+ first.focus();
1005
+ }
1006
+ },
1007
+ [active, root]
1008
+ );
1009
+ }
1010
+
1011
+ // src/react/editor/flow-board.tsx
1012
+ import {
1013
+ Background,
1014
+ BackgroundVariant,
1015
+ getBezierPath,
1016
+ Handle,
1017
+ MiniMap,
1018
+ Panel,
1019
+ Position,
1020
+ ReactFlow,
1021
+ ReactFlowProvider,
1022
+ applyNodeChanges,
1023
+ useReactFlow,
1024
+ useStore
1025
+ } from "@xyflow/react";
1026
+ import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
1027
+
1028
+ // src/react/editor/board-styles.ts
1029
+ var FLOW_BOARD_CSS = `
1030
+ .react-flow { direction: ltr; position: relative; width: 100%; height: 100%; overflow: hidden; z-index: 0; }
1031
+ .react-flow__container, .react-flow__edgelabel-renderer, .react-flow__viewport-portal {
1032
+ position: absolute; width: 100%; height: 100%; top: 0; left: 0;
1033
+ }
1034
+ .react-flow__pane { z-index: 1; touch-action: none; }
1035
+ .react-flow__pane.draggable { cursor: grab; }
1036
+ .react-flow__pane.dragging { cursor: grabbing; }
1037
+ .react-flow__viewport { transform-origin: 0 0; z-index: 2; pointer-events: none; }
1038
+ .react-flow__renderer { z-index: 4; }
1039
+ .react-flow__background { pointer-events: none; z-index: -1; }
1040
+ .react-flow__background-pattern.dots { fill: var(--flow-board-dots, rgba(128, 128, 128, 0.35)); }
1041
+ .react-flow .react-flow__edges { position: absolute; }
1042
+ .react-flow .react-flow__edges svg { overflow: visible; position: absolute; pointer-events: none; }
1043
+ .react-flow__edge { pointer-events: visibleStroke; }
1044
+ .react-flow__edge.inactive { pointer-events: none; }
1045
+ .react-flow__edge:focus, .react-flow__edge:focus-visible { outline: none; }
1046
+ .react-flow__connection { pointer-events: none; }
1047
+ .react-flow__connection-path { stroke: var(--flow-board-connection, currentColor); stroke-width: 2; fill: none; stroke-dasharray: 4 4; }
1048
+ svg.react-flow__connectionline { z-index: 1001; overflow: visible; position: absolute; }
1049
+ .react-flow__nodes { pointer-events: none; transform-origin: 0 0; }
1050
+ .react-flow__node {
1051
+ position: absolute; user-select: none; pointer-events: all; transform-origin: 0 0; box-sizing: border-box; cursor: default;
1052
+ }
1053
+ .react-flow__node.draggable { cursor: grab; }
1054
+ .react-flow__node.draggable.dragging { cursor: grabbing; }
1055
+ .react-flow__node:focus, .react-flow__node:focus-visible { outline: none; }
1056
+ .react-flow__handle {
1057
+ position: absolute; pointer-events: none; width: 10px; height: 10px; min-width: 5px; min-height: 5px; border-radius: 999px;
1058
+ background: var(--flow-board-handle-fill, #fff); border: 1.5px solid var(--flow-board-handle, currentColor);
1059
+ }
1060
+ .react-flow__handle.connectingfrom, .react-flow__handle.connectionindicator { pointer-events: all; cursor: crosshair; }
1061
+ .react-flow__handle-left { top: 50%; left: 0; transform: translate(-50%, -50%); }
1062
+ .react-flow__handle-right { top: 50%; right: 0; transform: translate(50%, -50%); }
1063
+ .react-flow__handle-top { top: 0; left: 50%; transform: translate(-50%, -50%); }
1064
+ .react-flow__handle-bottom { top: auto; bottom: 0; left: 50%; transform: translate(-50%, 50%); }
1065
+ .react-flow__panel { position: absolute; z-index: 5; margin: 16px; }
1066
+ .react-flow__panel.top { top: 0; }
1067
+ .react-flow__panel.bottom { bottom: 0; }
1068
+ .react-flow__panel.left { left: 0; }
1069
+ .react-flow__panel.right { right: 0; }
1070
+ .react-flow__panel.top.center, .react-flow__panel.bottom.center { left: 50%; transform: translateX(-16px) translateX(-50%); }
1071
+ .react-flow__minimap { background: var(--flow-board-minimap, #fff); border-radius: 12px; overflow: hidden; }
1072
+ .react-flow__minimap-svg { display: block; }
1073
+ .react-flow__minimap-mask { fill: var(--flow-board-minimap-mask, rgba(128, 128, 128, 0.15)); }
1074
+ .react-flow__minimap-node { fill: var(--flow-board-minimap-node, rgba(128, 128, 128, 0.35)); }
1075
+ .flow-board__new-exit.react-flow__handle {
1076
+ top: auto; bottom: 10px; right: 0; transform: translate(50%, 0); opacity: 0.55;
1077
+ }
1078
+ .flow-board__new-exit.react-flow__handle:hover { opacity: 1; }
1079
+ @media (max-width: 860px) {
1080
+ .flow-board__legend, .flow-board__minimap, .flow-board__minimap-toggle { display: none !important; }
1081
+ }
1082
+ `;
1083
+
1084
+ // src/react/editor/schedule-model.ts
1085
+ var DEFAULT_SCHEDULE = { dayStart: "09:00", lateStartHours: DEFAULT_LATE_START_HOURS, farewellOnDeadline: false };
1086
+ var DEFAULT_NUDGES = {
1087
+ afterSilence: [
1088
+ { amount: 15, unit: "min" },
1089
+ { amount: 1, unit: "h" },
1090
+ { amount: 4, unit: "h" }
1091
+ ]
1092
+ };
1093
+ function compact(value) {
1094
+ const entries = Object.entries(value).filter(([, item]) => item !== void 0 && item !== "");
1095
+ return entries.length === 0 ? void 0 : Object.fromEntries(entries);
1096
+ }
1097
+ function withOptional(target, key, value) {
1098
+ const { [key]: _old, ...rest } = target;
1099
+ return value === void 0 ? rest : { ...rest, [key]: value };
1100
+ }
1101
+ function setScheduleOn(flow, on) {
1102
+ if (on) return flow.schedule ? flow : { ...flow, schedule: { ...DEFAULT_SCHEDULE } };
1103
+ return withOptional(flow, "schedule", void 0);
1104
+ }
1105
+ function updateSchedule(flow, patch) {
1106
+ let schedule = flow.schedule ?? { ...DEFAULT_SCHEDULE };
1107
+ for (const [key, raw] of Object.entries(patch)) {
1108
+ const value = raw !== null && typeof raw === "object" && !Array.isArray(raw) && key !== "nudges" ? compact(raw) : raw === "" ? void 0 : raw;
1109
+ schedule = withOptional(schedule, key, value);
1110
+ }
1111
+ return { ...flow, schedule };
1112
+ }
1113
+ function setAfterEnd(flow, mode) {
1114
+ return { ...flow, afterEnd: mode };
1115
+ }
1116
+ function durationExit(exit) {
1117
+ const { when } = exit;
1118
+ if (when.type !== "timer") return null;
1119
+ if (when.from === "state_days") return { exitId: exit.id, amount: when.days, unit: "d", to: exit.to };
1120
+ if (when.from === "program_day") return { exitId: exit.id, amount: when.day, unit: "program_day", to: exit.to };
1121
+ if (when.from === "in_state" && when.unit === "h") return { exitId: exit.id, amount: when.amount, unit: "h", to: exit.to };
1122
+ return null;
1123
+ }
1124
+ function durationOf(state) {
1125
+ for (const exit of state.exits) {
1126
+ const found = durationExit(exit);
1127
+ if (found) return found;
1128
+ }
1129
+ return null;
1130
+ }
1131
+ function setDuration(flow, stateId, value) {
1132
+ const state = flow.states.find((candidate) => candidate.id === stateId);
1133
+ if (!state) return flow;
1134
+ const current = durationOf(state);
1135
+ if (value === null) {
1136
+ if (!current) return flow;
1137
+ return { ...flow, states: flow.states.map((candidate) => candidate.id === stateId ? { ...candidate, exits: candidate.exits.filter((exit) => exit.id !== current.exitId) } : candidate) };
1138
+ }
1139
+ const amount = Math.max(1, Math.round(value.amount) || 1);
1140
+ const when = value.unit === "d" ? { type: "timer", from: "state_days", days: amount } : value.unit === "program_day" ? { type: "timer", from: "program_day", day: amount } : { type: "timer", from: "in_state", amount, unit: "h" };
1141
+ if (current) return updateExit(flow, stateId, current.exitId, { when, to: current.to });
1142
+ const made = addExit(flow, stateId, "timer");
1143
+ return updateExit(made.flow, stateId, made.exitId, { when, to: null });
1144
+ }
1145
+ function nudgesMode(state) {
1146
+ if (state.nudges === "off") return "off";
1147
+ return state.nudges ? "own" : "inherit";
1148
+ }
1149
+ function setStateNudges(flow, stateId, mode) {
1150
+ return {
1151
+ ...flow,
1152
+ states: flow.states.map((state) => {
1153
+ if (state.id !== stateId || nudgesMode(state) === mode) return state;
1154
+ if (mode === "inherit") return withOptional(state, "nudges", void 0);
1155
+ if (mode === "off") return { ...state, nudges: "off" };
1156
+ return { ...state, nudges: structuredClone(flow.schedule?.nudges ?? DEFAULT_NUDGES) };
1157
+ })
1158
+ };
1159
+ }
1160
+ function updateStateNudges(flow, stateId, nudges) {
1161
+ return {
1162
+ ...flow,
1163
+ states: flow.states.map((state) => {
1164
+ if (state.id !== stateId) return state;
1165
+ const days = state.nudges && state.nudges !== "off" ? state.nudges.days : void 0;
1166
+ return { ...state, nudges: { ...cleanNudges(nudges), ...days !== void 0 && { days } } };
1167
+ })
1168
+ };
1169
+ }
1170
+ function setStateNudgesDays(flow, stateId, days) {
1171
+ return {
1172
+ ...flow,
1173
+ states: flow.states.map((state) => {
1174
+ if (state.id !== stateId || !state.nudges || state.nudges === "off") return state;
1175
+ const { days: _days, ...rest } = state.nudges;
1176
+ return { ...state, nudges: days === null ? rest : { ...rest, days: Math.max(1, Math.round(days) || 1) } };
1177
+ })
1178
+ };
1179
+ }
1180
+ function stateNudgesDays(state) {
1181
+ return state.nudges && state.nudges !== "off" ? state.nudges.days ?? null : null;
1182
+ }
1183
+ function updateScheduleNudges(flow, nudges) {
1184
+ return updateSchedule(flow, { nudges: nudges === null ? void 0 : cleanNudges(nudges) });
1185
+ }
1186
+ function cleanNudges(nudges) {
1187
+ let clean = { afterSilence: nudges.afterSilence };
1188
+ if (nudges.dailyAt?.length) clean = { ...clean, dailyAt: nudges.dailyAt };
1189
+ if (nudges.giveUpAfter) clean = { ...clean, giveUpAfter: nudges.giveUpAfter };
1190
+ return clean;
1191
+ }
1192
+ function durationText(duration, labels) {
1193
+ return `${duration.amount} ${labels.units[duration.unit]}`;
1194
+ }
1195
+ function stateTimingText(flow, state, labels) {
1196
+ const parts = [];
1197
+ const duration = durationOf(state);
1198
+ if (duration) {
1199
+ parts.push(duration.unit === "d" ? labels.days(duration.amount) : duration.unit === "program_day" ? labels.programDay(duration.amount) : `${duration.amount} ${labels.units.h}`);
1200
+ }
1201
+ if (state.nudges === "off") parts.push(labels.board.noNudges);
1202
+ else if (state.nudges) {
1203
+ const steps = state.nudges.afterSilence.map((step2) => durationText(step2, labels));
1204
+ const daily = state.nudges.dailyAt ?? [];
1205
+ const days = state.nudges.days === void 0 ? "" : ` ${labels.board.nudgesDays(state.nudges.days)}`;
1206
+ if (steps.length + daily.length > 0) parts.push(`${labels.board.nudges} ${[...steps, ...daily].join(" \xB7 ")}${days}`);
1207
+ }
1208
+ return parts.join(" \xB7 ");
1209
+ }
1210
+
1211
+ // src/react/editor/flow-simulation.ts
1212
+ var STEP_MS = 6e4;
1213
+ function startSimulation(flow, options) {
1214
+ const run = noteAgentAnswer(startRun(flow, options.now), options.now);
1215
+ return { run, card: {}, now: options.now, timeZone: options.timeZone, steps: [{ stateId: run.state, exitId: null, nothing: false }], previous: null };
1216
+ }
1217
+ function simulationChoices(flow, sim) {
1218
+ const state = stateById(flow, sim.run.state);
1219
+ if (!state) return [];
1220
+ return activeExits(flow, state).map(({ exit, own }) => {
1221
+ const after = chooseExit(flow, sim, exit.id);
1222
+ return { exitId: exit.id, own, target: after.steps.length > sim.steps.length ? after.run.state : null };
1223
+ });
1224
+ }
1225
+ function findExit(flow, exitId) {
1226
+ return [...flow.states.flatMap((state) => state.exits), ...flow.anyState.exits].find((exit) => exit.id === exitId);
1227
+ }
1228
+ function fittingValue(condition, field) {
1229
+ const expected = condition.value;
1230
+ switch (condition.op) {
1231
+ case "eq":
1232
+ case "contains":
1233
+ return expected;
1234
+ case "gt":
1235
+ return typeof expected === "number" ? expected + 1 : expected;
1236
+ case "lt":
1237
+ return typeof expected === "number" ? expected - 1 : expected;
1238
+ case "filled":
1239
+ if (field.type === "number") return 1;
1240
+ return field.type === "choice" && field.options.length > 0 ? field.options[0] : "\u2713";
1241
+ }
1242
+ }
1243
+ function writeValue(flow, sim, fieldId, value) {
1244
+ const found = fieldById(flow, fieldId);
1245
+ if (!found) return sim;
1246
+ if (found.place === "notes") return { ...sim, run: { ...sim.run, notes: { ...sim.run.notes, [fieldId]: value } } };
1247
+ return { ...sim, card: { ...sim.card, [fieldId]: { value, updatedAt: sim.now } } };
1248
+ }
1249
+ function cardValues(sim) {
1250
+ return Object.fromEntries(Object.entries(sim.card).map(([id, entry]) => [id, entry.value]));
1251
+ }
1252
+ function step(flow, before, prepare) {
1253
+ const asked = sideQuestion(flow, before.run, { card: before.card, now: before.now });
1254
+ const prepared = prepare({ ...before, now: before.now + STEP_MS });
1255
+ const sim = prepared.sim;
1256
+ let run = prepared.human ? noteHumanMessage(sim.run, sim.now) : sim.run;
1257
+ const transition = prepared.event ? nextTransition(flow, run, prepared.event, sim.now) : null;
1258
+ const steps = [...sim.steps];
1259
+ if (transition) {
1260
+ run = applyTransition(flow, run, transition);
1261
+ steps.push({ stateId: transition.to, exitId: transition.exitId, nothing: false });
1262
+ } else if (prepared.event === null) {
1263
+ steps.push({ stateId: run.state, exitId: null, nothing: true });
1264
+ } else {
1265
+ return before;
1266
+ }
1267
+ run = noteAgentAnswer(run, sim.now);
1268
+ if (asked && asked.reason !== "learn") run = noteSideQuestion(run);
1269
+ return { ...sim, run, steps, previous: before };
1270
+ }
1271
+ function chooseExit(flow, sim, exitId) {
1272
+ const exit = findExit(flow, exitId);
1273
+ if (!exit) return sim;
1274
+ const when = exit.when;
1275
+ return step(flow, sim, (current) => {
1276
+ switch (when.type) {
1277
+ case "judge":
1278
+ return { sim: current, human: true, event: { type: "verdict", exitId, probability: 1, decidedBy: "judge" } };
1279
+ case "event":
1280
+ return {
1281
+ sim: current,
1282
+ human: when.event === "message",
1283
+ event: { type: "event", event: when.event, ...when.name === void 0 ? {} : { name: when.name } }
1284
+ };
1285
+ case "timer": {
1286
+ const due = timerDueAt(when, current.run, current.timeZone);
1287
+ const now = due === null ? current.now : Math.max(current.now - STEP_MS, due);
1288
+ return { sim: { ...current, now }, human: false, event: { type: "timer", exitId, timeZone: current.timeZone } };
1289
+ }
1290
+ case "data": {
1291
+ const found = fieldById(flow, when.field);
1292
+ if (!found || !FLOW_OPS_BY_FIELD_TYPE[found.field.type].includes(when.op)) {
1293
+ return { sim: current, human: true, event: { type: "data", field: when.field, card: cardValues(current) } };
1294
+ }
1295
+ const filled = writeValue(flow, current, when.field, fittingValue(when, found.field));
1296
+ return { sim: filled, human: true, event: { type: "data", field: when.field, card: cardValues(filled) } };
1297
+ }
1298
+ }
1299
+ });
1300
+ }
1301
+ function chooseNothing(flow, sim) {
1302
+ return step(flow, sim, (current) => ({ sim: current, human: true, event: null }));
1303
+ }
1304
+ function setKnownValue(flow, sim, fieldId, text) {
1305
+ const found = fieldById(flow, fieldId);
1306
+ if (!found) return sim;
1307
+ const trimmed = text.trim();
1308
+ const value = trimmed === "" ? void 0 : found.field.type === "number" && Number.isFinite(Number(trimmed)) ? Number(trimmed) : trimmed;
1309
+ let written = writeValue(flow, sim, fieldId, value);
1310
+ if (value === void 0) {
1311
+ written = found.place === "notes" ? { ...written, run: { ...written.run, notes: withoutKey(written.run.notes, fieldId) } } : { ...written, card: withoutKey(written.card, fieldId) };
1312
+ return written;
1313
+ }
1314
+ const transition = nextTransition(flow, written.run, { type: "data", field: fieldId, card: cardValues(written) }, written.now);
1315
+ if (!transition) return written;
1316
+ return {
1317
+ ...written,
1318
+ run: noteAgentAnswer(applyTransition(flow, written.run, transition), written.now),
1319
+ steps: [...written.steps, { stateId: transition.to, exitId: transition.exitId, nothing: false }],
1320
+ previous: sim
1321
+ };
1322
+ }
1323
+ function withoutKey(record, key) {
1324
+ const { [key]: _removed, ...rest } = record;
1325
+ return rest;
1326
+ }
1327
+ function nextQuestionField(flow, sim) {
1328
+ return sideQuestion(flow, sim.run, { card: sim.card, now: sim.now })?.fieldId ?? null;
1329
+ }
1330
+ function stepBack(sim) {
1331
+ return sim.previous ?? sim;
1332
+ }
1333
+
1334
+ // src/react/editor/flow-board.tsx
1335
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1336
+ var NEW_EXIT_HANDLE = "@new";
1337
+ var INPUT_HANDLE = "in";
1338
+ var LEGEND_KINDS = ["judge", "data", "event", "timer", "return"];
1339
+ var SkinContext = createContext2(null);
1340
+ function useSkin() {
1341
+ const skin = useContext2(SkinContext);
1342
+ if (!skin) throw new Error("FlowBoard renders only inside FlowEditor.");
1343
+ return skin;
1344
+ }
1345
+ function exitHandle(exitId, connectable) {
1346
+ return /* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Right, id: exitId, isConnectable: connectable });
1347
+ }
1348
+ function StateNodeView({ data }) {
1349
+ const skin = useSkin();
1350
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1351
+ /* @__PURE__ */ jsx(
1352
+ skin.FlowStateNode,
1353
+ {
1354
+ ...data.view,
1355
+ renderInputHandle: () => /* @__PURE__ */ jsx(Handle, { type: "target", position: Position.Left, id: INPUT_HANDLE, isConnectable: data.draggable }),
1356
+ renderExitHandle: (exitId) => exitHandle(exitId, data.draggable)
1357
+ }
1358
+ ),
1359
+ data.draggable ? /* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Right, id: NEW_EXIT_HANDLE, className: "flow-board__new-exit" }) : null
1360
+ ] });
1361
+ }
1362
+ function AnyNodeView({ data }) {
1363
+ const skin = useSkin();
1364
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1365
+ /* @__PURE__ */ jsx(skin.FlowAnyStateNode, { ...data.view, renderExitHandle: (exitId) => exitHandle(exitId, data.draggable) }),
1366
+ data.draggable ? /* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Right, id: NEW_EXIT_HANDLE, className: "flow-board__new-exit" }) : null
1367
+ ] });
1368
+ }
1369
+ function EdgeView({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data }) {
1370
+ const skin = useSkin();
1371
+ const [path, labelX, labelY] = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
1372
+ if (!data) return null;
1373
+ return /* @__PURE__ */ jsx(skin.FlowEdge, { ...data, edgeId: id, path, labelX, labelY });
1374
+ }
1375
+ var nodeTypes = { state: StateNodeView, any: AnyNodeView };
1376
+ var edgeTypes = { flow: EdgeView };
1377
+ function nodePosition(flow, nodeId, index) {
1378
+ const saved = flow.layout[nodeId];
1379
+ if (saved) return saved;
1380
+ if (nodeId === ANY_STATE_LAYOUT_KEY) return { x: 40, y: -220 };
1381
+ return { x: 40 + index % 4 * 330, y: 40 + Math.floor(index / 4) * 260 };
1382
+ }
1383
+ function countFor(issues, match) {
1384
+ const found = issues.filter(match);
1385
+ return { errors: found.filter((issue) => issue.level === "error").length, warnings: found.filter((issue) => issue.level === "warning").length };
1386
+ }
1387
+ function exitRows(flow, catalog, exits, issues, labels) {
1388
+ return exits.map((exit) => ({
1389
+ exitId: exit.id,
1390
+ kind: edgeKind(exit),
1391
+ text: conditionText(flow, exit.when, labels, catalog),
1392
+ target: targetTitle(flow, exit.to, labels),
1393
+ issues: countFor(issues, (issue) => issue.place.exitId === exit.id)
1394
+ }));
1395
+ }
1396
+ var ownerOf = (nodeId) => nodeId === ANY_STATE_LAYOUT_KEY ? null : nodeId;
1397
+ function runMarks(editor) {
1398
+ const sim = editor.simulation;
1399
+ if (editor.mode !== "run" || !sim) return null;
1400
+ const visited = new Set(sim.steps.map((step2) => step2.stateId));
1401
+ const next = new Set(simulationChoices(editor.flow, sim).flatMap((choice) => choice.target ? [choice.target] : []));
1402
+ const traversed = new Set(sim.steps.flatMap((step2) => step2.exitId ? [step2.exitId] : []));
1403
+ const state = stateById(editor.flow, sim.run.state);
1404
+ const live = new Set(state ? activeExits(editor.flow, state).map(({ exit }) => exit.id) : []);
1405
+ return { current: sim.run.state, visited, next, traversed, live };
1406
+ }
1407
+ function useBoardModel(editor) {
1408
+ const labels = useFlowLabels().editor;
1409
+ const { flow, catalog, issues, selection, mode, select, readOnly } = editor;
1410
+ return useMemo2(() => {
1411
+ const marks = runMarks(editor);
1412
+ const selectable = mode !== "run";
1413
+ const editable = selectable && !readOnly;
1414
+ const selectedExit = selection.kind === "state" || selection.kind === "any" ? selection.exitId : void 0;
1415
+ const nodes = flow.states.map((state, index) => ({
1416
+ id: state.id,
1417
+ type: "state",
1418
+ position: nodePosition(flow, state.id, index),
1419
+ draggable: editable,
1420
+ data: {
1421
+ draggable: editable,
1422
+ view: {
1423
+ stateId: state.id,
1424
+ title: state.title || state.id,
1425
+ goal: stateGoal(state.instructions),
1426
+ exits: exitRows(flow, catalog, state.exits, issues, labels),
1427
+ tags: state.tags,
1428
+ start: flow.start === state.id,
1429
+ final: isFinal(state),
1430
+ noSideQuestions: state.noSideQuestions,
1431
+ timing: stateTimingText(flow, state, labels),
1432
+ selected: selection.kind === "state" && selection.stateId === state.id,
1433
+ dimmed: marks !== null && !marks.visited.has(state.id) && !marks.next.has(state.id),
1434
+ run: marks === null ? null : marks.current === state.id ? "current" : marks.visited.has(state.id) ? "visited" : null,
1435
+ issues: countFor(issues, (issue) => issue.place.stateId === state.id),
1436
+ onSelect: () => selectable && select({ kind: "state", stateId: state.id })
1437
+ }
1438
+ }
1439
+ }));
1440
+ if (flow.anyState.exits.length > 0 || flow.anyState.except.length > 0 || flow.layout[ANY_STATE_LAYOUT_KEY]) {
1441
+ nodes.push({
1442
+ id: ANY_STATE_LAYOUT_KEY,
1443
+ type: "any",
1444
+ position: nodePosition(flow, ANY_STATE_LAYOUT_KEY, 0),
1445
+ draggable: editable,
1446
+ data: {
1447
+ draggable: editable,
1448
+ view: {
1449
+ except: flow.anyState.except.map((id) => stateById(flow, id)?.title ?? id),
1450
+ exits: exitRows(flow, catalog, flow.anyState.exits, issues, labels),
1451
+ selected: selection.kind === "any",
1452
+ dimmed: marks !== null,
1453
+ issues: countFor(issues, (issue) => !issue.place.stateId && flow.anyState.exits.some((exit) => exit.id === issue.place.exitId)),
1454
+ onSelect: () => selectable && select({ kind: "any" })
1455
+ }
1456
+ }
1457
+ });
1458
+ }
1459
+ const owners = [
1460
+ { nodeId: ANY_STATE_LAYOUT_KEY, exits: flow.anyState.exits },
1461
+ ...flow.states.map((state) => ({ nodeId: state.id, exits: state.exits }))
1462
+ ];
1463
+ const edges = owners.flatMap(
1464
+ ({ nodeId, exits }) => exits.filter((exit) => exit.to !== null && exit.to !== RETURN_TARGET && stateById(flow, exit.to)).map((exit) => ({
1465
+ id: `${nodeId}:${exit.id}`,
1466
+ type: "flow",
1467
+ source: nodeId,
1468
+ sourceHandle: exit.id,
1469
+ target: exit.to,
1470
+ targetHandle: INPUT_HANDLE,
1471
+ data: {
1472
+ kind: edgeKind(exit),
1473
+ label: conditionText(flow, exit.when, labels, catalog),
1474
+ selected: selectedExit === exit.id,
1475
+ dimmed: marks !== null && !marks.traversed.has(exit.id) && !(marks.live.has(exit.id) && nodeId === marks.current),
1476
+ traversed: marks?.traversed.has(exit.id) ?? false,
1477
+ onSelect: () => selectable && select(nodeId === ANY_STATE_LAYOUT_KEY ? { kind: "any", exitId: exit.id } : { kind: "state", stateId: nodeId, exitId: exit.id })
1478
+ }
1479
+ }))
1480
+ );
1481
+ return { nodes, edges };
1482
+ }, [flow, catalog, issues, selection, mode, readOnly, editor.simulation, labels, select]);
1483
+ }
1484
+ function BoardToggle(props) {
1485
+ const skin = useSkin();
1486
+ if (skin.FlowBoardToggle) return /* @__PURE__ */ jsx(skin.FlowBoardToggle, { ...props });
1487
+ return /* @__PURE__ */ jsx(
1488
+ "button",
1489
+ {
1490
+ type: "button",
1491
+ "aria-label": props.label,
1492
+ title: props.label,
1493
+ "aria-expanded": props.expanded,
1494
+ "data-flow-board-toggle": props.kind,
1495
+ onClick: props.onClick,
1496
+ style: {
1497
+ font: "inherit",
1498
+ fontSize: 12,
1499
+ padding: "6px 10px",
1500
+ borderRadius: 8,
1501
+ cursor: "pointer",
1502
+ color: "inherit",
1503
+ background: "var(--flow-board-handle-fill, #fff)",
1504
+ border: "1px solid var(--flow-board-handle, currentColor)"
1505
+ },
1506
+ children: props.showText ? props.label : props.kind === "legend" ? "?" : props.expanded ? "\xD7" : "+"
1507
+ }
1508
+ );
1509
+ }
1510
+ function ZoomControls() {
1511
+ const skin = useSkin();
1512
+ const { zoomIn, zoomOut, fitView } = useReactFlow();
1513
+ const zoom = useStore((store) => store.transform[2]);
1514
+ return /* @__PURE__ */ jsx(
1515
+ skin.FlowZoomControls,
1516
+ {
1517
+ zoom,
1518
+ onZoomIn: () => void zoomIn({ duration: 150 }),
1519
+ onZoomOut: () => void zoomOut({ duration: 150 }),
1520
+ onFit: () => void fitView({ duration: 200, padding: 0.15 })
1521
+ }
1522
+ );
1523
+ }
1524
+ function clientPoint(event) {
1525
+ if ("changedTouches" in event) {
1526
+ const touch = event.changedTouches[0];
1527
+ return touch ? { x: touch.clientX, y: touch.clientY } : null;
1528
+ }
1529
+ return { x: event.clientX, y: event.clientY };
1530
+ }
1531
+ var ignoreNodeClick = () => {
1532
+ };
1533
+ function Board({ editor, space }) {
1534
+ const spaceLabels = useFlowLabels().editor.board.space;
1535
+ const skin = useSkin();
1536
+ const model = useBoardModel(editor);
1537
+ const { screenToFlowPosition } = useReactFlow();
1538
+ const [nodes, setNodes] = useState2(model.nodes);
1539
+ useEffect2(() => {
1540
+ setNodes((previous) => {
1541
+ const byId = new Map(previous.map((node) => [node.id, node]));
1542
+ return model.nodes.map((node) => {
1543
+ const old = byId.get(node.id);
1544
+ if (!old) return node;
1545
+ return { ...node, measured: old.measured, ...old.dragging ? { position: old.position, dragging: true } : {} };
1546
+ });
1547
+ });
1548
+ }, [model.nodes]);
1549
+ const onNodesChange = useCallback2((changes) => setNodes((current) => applyNodeChanges(changes, current)), []);
1550
+ const { edit, connect, connectToNewState, select } = editor;
1551
+ const onConnect = useCallback2(
1552
+ (connection) => {
1553
+ const owner = ownerOf(connection.source);
1554
+ if (connection.target === ANY_STATE_LAYOUT_KEY) return;
1555
+ if (connection.sourceHandle === NEW_EXIT_HANDLE || !connection.sourceHandle) {
1556
+ edit((flow) => {
1557
+ const made = addExit(flow, owner, "judge");
1558
+ return connectExit(made.flow, owner, made.exitId, connection.target);
1559
+ });
1560
+ return;
1561
+ }
1562
+ connect(owner, connection.sourceHandle, connection.target);
1563
+ },
1564
+ [connect, edit]
1565
+ );
1566
+ const onConnectEnd = useCallback2(
1567
+ (event, state) => {
1568
+ if (state.isValid || !state.fromNode || !state.fromHandle) return;
1569
+ const owner = ownerOf(state.fromNode.id);
1570
+ const exitId = state.fromHandle.id === NEW_EXIT_HANDLE ? null : state.fromHandle.id ?? null;
1571
+ if (state.toNode && state.toNode.id !== ANY_STATE_LAYOUT_KEY) {
1572
+ onConnect({ source: state.fromNode.id, sourceHandle: exitId ?? NEW_EXIT_HANDLE, target: state.toNode.id, targetHandle: INPUT_HANDLE });
1573
+ return;
1574
+ }
1575
+ const point = clientPoint(event);
1576
+ if (!point) return;
1577
+ const at = screenToFlowPosition(point);
1578
+ connectToNewState(owner, exitId, { x: Math.round(at.x), y: Math.round(at.y - 40) });
1579
+ },
1580
+ [connectToNewState, onConnect, screenToFlowPosition]
1581
+ );
1582
+ const onNodeDragStop = useCallback2(
1583
+ (_event, node) => edit((flow) => placeNode(flow, node.id, node.position)),
1584
+ [edit]
1585
+ );
1586
+ const selectable = editor.mode !== "run";
1587
+ const editable = selectable && !editor.readOnly;
1588
+ return /* @__PURE__ */ jsxs(
1589
+ ReactFlow,
1590
+ {
1591
+ nodes,
1592
+ edges: model.edges,
1593
+ nodeTypes,
1594
+ edgeTypes,
1595
+ onNodesChange,
1596
+ onConnect,
1597
+ onConnectEnd,
1598
+ onNodeDragStop,
1599
+ onNodeClick: ignoreNodeClick,
1600
+ onPaneClick: () => selectable && select({ kind: "overview" }),
1601
+ nodesConnectable: editable,
1602
+ elementsSelectable: false,
1603
+ deleteKeyCode: null,
1604
+ selectionKeyCode: null,
1605
+ multiSelectionKeyCode: null,
1606
+ fitView: true,
1607
+ fitViewOptions: { padding: 0.15 },
1608
+ minZoom: 0.2,
1609
+ maxZoom: 2,
1610
+ proOptions: { hideAttribution: true },
1611
+ children: [
1612
+ /* @__PURE__ */ jsx(Background, { variant: BackgroundVariant.Dots, gap: 22, size: 1.2 }),
1613
+ !space.legendHidden && /* @__PURE__ */ jsx(Panel, { position: "bottom-left", className: "flow-board__legend", children: /* @__PURE__ */ jsxs("div", { className: "flow-board__legend-stack", style: { display: "flex", flexDirection: "column", alignItems: "flex-start", gap: 8 }, children: [
1614
+ space.legendOpen && /* @__PURE__ */ jsx(skin.FlowLegend, { kinds: LEGEND_KINDS }),
1615
+ /* @__PURE__ */ jsx(BoardToggle, { kind: "legend", label: spaceLabels.legend, expanded: space.legendOpen, showText: true, onClick: space.toggleLegend })
1616
+ ] }) }),
1617
+ space.inspectorToggleable && /* @__PURE__ */ jsx(Panel, { position: "top-right", children: /* @__PURE__ */ jsx(
1618
+ BoardToggle,
1619
+ {
1620
+ kind: "inspector",
1621
+ label: space.inspectorOpen ? spaceLabels.hideInspector : spaceLabels.showInspector,
1622
+ expanded: space.inspectorOpen,
1623
+ onClick: space.toggleInspector
1624
+ }
1625
+ ) }),
1626
+ /* @__PURE__ */ jsx(Panel, { position: "bottom-right", children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1627
+ !space.minimapHidden && /* @__PURE__ */ jsx("span", { className: "flow-board__minimap-toggle", style: { display: "contents" }, children: /* @__PURE__ */ jsx(
1628
+ BoardToggle,
1629
+ {
1630
+ kind: "minimap",
1631
+ label: space.minimapOpen ? spaceLabels.hideMinimap : spaceLabels.showMinimap,
1632
+ expanded: space.minimapOpen,
1633
+ onClick: space.toggleMinimap
1634
+ }
1635
+ ) }),
1636
+ /* @__PURE__ */ jsx(ZoomControls, {})
1637
+ ] }) }),
1638
+ space.minimapOpen && /* @__PURE__ */ jsx(MiniMap, { className: "flow-board__minimap", pannable: true, zoomable: true, position: "bottom-right", style: { marginBottom: 76 } })
1639
+ ]
1640
+ }
1641
+ );
1642
+ }
1643
+ function FlowBoard({ editor, skin, space }) {
1644
+ return /* @__PURE__ */ jsxs(SkinContext.Provider, { value: skin, children: [
1645
+ /* @__PURE__ */ jsx("style", { children: FLOW_BOARD_CSS }),
1646
+ /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(Board, { editor, space }) }, editor.flow.id)
1647
+ ] });
1648
+ }
1649
+
1650
+ // src/react/editor/side-task-model.ts
1651
+ function freeTaskId(flow) {
1652
+ const used = new Set((flow.sideTasks ?? []).map((task) => task.id));
1653
+ let index = 1;
1654
+ while (used.has(`task_${index}`)) index += 1;
1655
+ return `task_${index}`;
1656
+ }
1657
+ function withTasks(flow, tasks) {
1658
+ if (tasks.length > 0) return { ...flow, sideTasks: tasks };
1659
+ const { sideTasks: _empty, ...rest } = flow;
1660
+ return rest;
1661
+ }
1662
+ function addSideTask(flow, text) {
1663
+ const sideTaskId = freeTaskId(flow);
1664
+ const task = { id: sideTaskId, text, maxAttempts: DEFAULT_SIDE_TASK_MAX_ATTEMPTS, perConversation: false };
1665
+ return { sideTaskId, flow: withTasks(flow, [...flow.sideTasks ?? [], task]) };
1666
+ }
1667
+ function updateSideTask(flow, sideTaskId, patch) {
1668
+ return withTasks(
1669
+ flow,
1670
+ (flow.sideTasks ?? []).map((task) => {
1671
+ if (task.id !== sideTaskId) return task;
1672
+ const next = { ...task, ...patch };
1673
+ for (const key of ["when", "field"]) if (next[key] === void 0 || next[key] === "") delete next[key];
1674
+ if (patch.maxAttempts !== void 0) next.maxAttempts = Math.max(1, Math.round(patch.maxAttempts) || 1);
1675
+ return next;
1676
+ })
1677
+ );
1678
+ }
1679
+ function removeSideTask(flow, sideTaskId) {
1680
+ return withTasks(flow, (flow.sideTasks ?? []).filter((task) => task.id !== sideTaskId));
1681
+ }
1682
+ function catalogFieldsToAdd(flow, catalog) {
1683
+ return (catalog.fields ?? []).filter((field) => !fieldById(flow, field.id));
1684
+ }
1685
+ function addCatalogField(flow, list, field) {
1686
+ if (fieldById(flow, field.id)) return flow;
1687
+ const base = { id: field.id, name: field.title, about: "", type: field.type, options: field.type === "choice" ? [...field.options ?? []] : [] };
1688
+ if (list === "clientCard") {
1689
+ const card = { ...base, refreshAfterMonths: null };
1690
+ return { ...flow, clientCard: [...flow.clientCard, card] };
1691
+ }
1692
+ return { ...flow, chatNotes: [...flow.chatNotes, base] };
1693
+ }
1694
+ function setSideTaskField(flow, sideTaskId, fieldId, catalog) {
1695
+ const fromApp = fieldId === null ? void 0 : catalog.fields?.find((field) => field.id === fieldId);
1696
+ const withField = fromApp ? addCatalogField(flow, "clientCard", fromApp) : flow;
1697
+ return updateSideTask(withField, sideTaskId, { field: fieldId ?? void 0 });
1698
+ }
1699
+
1700
+ // src/react/editor/use-flow-editor.ts
1701
+ import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef2, useState as useState3 } from "react";
1702
+ function countIssues(issues) {
1703
+ return {
1704
+ errors: issues.filter((issue) => issue.level === "error").length,
1705
+ warnings: issues.filter((issue) => issue.level === "warning").length
1706
+ };
1707
+ }
1708
+ function runAppChecks(flow, checks, failed) {
1709
+ if (!checks) return [];
1710
+ try {
1711
+ return appIssues(flow, checks(flow));
1712
+ } catch (error) {
1713
+ console.error("[flow] app checks failed", error);
1714
+ return [{ level: "warning", code: "app_check_failed", message: failed, place: {} }];
1715
+ }
1716
+ }
1717
+ var NO_ISSUES = [];
1718
+ var browserTimeZone = () => Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
1719
+ function useFlowEditor(options) {
1720
+ const { value: flow, onChange, catalog } = options;
1721
+ const labels = useFlowLabels().editor;
1722
+ const latest = useRef2(options);
1723
+ latest.current = options;
1724
+ const [selection, setSelection] = useState3({ kind: "overview" });
1725
+ const [mode, setModeState] = useState3("edit");
1726
+ const [dialog, setDialog] = useState3(null);
1727
+ const [jsonErrors, setJsonErrors] = useState3([]);
1728
+ const [simulation, setSimulation] = useState3(null);
1729
+ const [saving, setSaving] = useState3(false);
1730
+ const [savedJson, setSavedJson] = useState3(() => flowToJson(flow));
1731
+ const [rejection, setRejection] = useState3(null);
1732
+ const readOnly = options.readOnly === true;
1733
+ const checks = options.checks;
1734
+ const saveFailed = rejection !== null && rejection.flow === flow;
1735
+ const rejectedIssues = saveFailed ? rejection.issues : NO_ISSUES;
1736
+ const issues = useMemo3(
1737
+ () => [...checkFlow(flow, catalog), ...runAppChecks(flow, checks, labels.checkPanel.appCheckFailed), ...rejectedIssues],
1738
+ [flow, catalog, checks, labels, rejectedIssues]
1739
+ );
1740
+ const issueCount = useMemo3(() => countIssues(issues), [issues]);
1741
+ const json = useMemo3(() => flowToJson(flow), [flow]);
1742
+ const dirty = json !== savedJson;
1743
+ const visibleSelection = selection.kind === "state" && !flow.states.some((state) => state.id === selection.stateId) ? { kind: "overview" } : selection;
1744
+ const edit = useCallback3((change) => {
1745
+ if (latest.current.readOnly) return;
1746
+ onChange(change(latest.current.value));
1747
+ }, [onChange]);
1748
+ const select = useCallback3((next) => {
1749
+ setSelection(next);
1750
+ setModeState((current) => current === "check" ? "edit" : current);
1751
+ }, []);
1752
+ const addState2 = useCallback3(
1753
+ (at) => {
1754
+ if (latest.current.readOnly) return "";
1755
+ const made = addState(latest.current.value, { title: labels.board.newState, ...at ? { at } : {} });
1756
+ onChange(made.flow);
1757
+ setSelection({ kind: "state", stateId: made.stateId });
1758
+ return made.stateId;
1759
+ },
1760
+ [labels, onChange]
1761
+ );
1762
+ const connect = useCallback3(
1763
+ (owner, exitId, to) => {
1764
+ if (!latest.current.readOnly) onChange(connectExit(latest.current.value, owner, exitId, to));
1765
+ },
1766
+ [onChange]
1767
+ );
1768
+ const connectToNewState = useCallback3(
1769
+ (owner, exitId, at) => {
1770
+ if (latest.current.readOnly) return;
1771
+ const made = addState(latest.current.value, { title: labels.board.newState, at });
1772
+ let next = made.flow;
1773
+ let fromExit = exitId;
1774
+ if (fromExit === null) {
1775
+ const added = addExit(next, owner, "judge");
1776
+ next = added.flow;
1777
+ fromExit = added.exitId;
1778
+ }
1779
+ onChange(connectExit(next, owner, fromExit, made.stateId));
1780
+ setSelection({ kind: "state", stateId: made.stateId });
1781
+ },
1782
+ [labels, onChange]
1783
+ );
1784
+ const removeState2 = useCallback3(
1785
+ (stateId) => {
1786
+ if (!latest.current.readOnly) onChange(removeState(latest.current.value, stateId));
1787
+ },
1788
+ [onChange]
1789
+ );
1790
+ const restartSimulation = useCallback3(() => {
1791
+ const current = latest.current;
1792
+ setSimulation(startSimulation(current.value, { now: current.now?.() ?? Date.now(), timeZone: current.timeZone ?? browserTimeZone() }));
1793
+ }, []);
1794
+ const setMode = useCallback3(
1795
+ (next) => {
1796
+ if (next === "run") {
1797
+ const current = latest.current.value;
1798
+ if (!current.states.some((state) => state.id === current.start)) {
1799
+ setModeState("check");
1800
+ return;
1801
+ }
1802
+ restartSimulation();
1803
+ } else {
1804
+ setSimulation(null);
1805
+ }
1806
+ setModeState(next);
1807
+ },
1808
+ [restartSimulation]
1809
+ );
1810
+ const goToIssue = useCallback3((issue) => {
1811
+ const { stateId, exitId, fieldId, sideTaskId } = issue.place;
1812
+ if (stateId && flow.states.some((state) => state.id === stateId)) setSelection({ kind: "state", stateId, ...exitId ? { exitId } : {} });
1813
+ else if (exitId && flow.anyState.exits.some((exit) => exit.id === exitId)) setSelection({ kind: "any", exitId });
1814
+ else if (sideTaskId) setSelection({ kind: "overview", sideTaskId });
1815
+ else if (fieldId) setSelection({ kind: "card" });
1816
+ else setSelection({ kind: "overview" });
1817
+ setModeState("edit");
1818
+ }, [flow]);
1819
+ const simulate = useMemo3(() => {
1820
+ const change = (step2) => setSimulation((sim) => sim ? step2(sim) : sim);
1821
+ return {
1822
+ choose: (exitId) => change((sim) => chooseExit(latest.current.value, sim, exitId)),
1823
+ nothing: () => change((sim) => chooseNothing(latest.current.value, sim)),
1824
+ back: () => change(stepBack),
1825
+ restart: restartSimulation,
1826
+ setKnown: (fieldId, text) => change((sim) => setKnownValue(latest.current.value, sim, fieldId, text))
1827
+ };
1828
+ }, [restartSimulation]);
1829
+ const openJson = useCallback3(() => {
1830
+ setJsonErrors([]);
1831
+ setDialog("json");
1832
+ }, []);
1833
+ const closeDialog = useCallback3(() => setDialog(null), []);
1834
+ const loadJson = useCallback3(
1835
+ (text) => {
1836
+ if (latest.current.readOnly) return false;
1837
+ const loaded = loadFlowJson(text);
1838
+ if (!loaded.ok) {
1839
+ setJsonErrors(loaded.errors);
1840
+ return false;
1841
+ }
1842
+ setJsonErrors([]);
1843
+ setSelection({ kind: "overview" });
1844
+ setSimulation(null);
1845
+ setModeState("edit");
1846
+ setDialog(null);
1847
+ onChange(loaded.flow);
1848
+ return true;
1849
+ },
1850
+ [onChange]
1851
+ );
1852
+ const save = useCallback3(async () => {
1853
+ const { onSave, value, readOnly: viewOnly } = latest.current;
1854
+ if (!onSave || viewOnly) return;
1855
+ setSaving(true);
1856
+ try {
1857
+ const result = await onSave(value);
1858
+ if (result && !result.ok) {
1859
+ setRejection({ flow: value, issues: appIssues(value, (result.issues ?? []).map((issue) => ({ ...issue, code: "save_rejected" }))) });
1860
+ return;
1861
+ }
1862
+ setRejection(null);
1863
+ setSavedJson(flowToJson(value));
1864
+ } catch (error) {
1865
+ console.warn("[flow] onSave failed", error);
1866
+ setRejection({ flow: value, issues: [] });
1867
+ } finally {
1868
+ setSaving(false);
1869
+ }
1870
+ }, []);
1871
+ const migrate = useMigrateController(flow, options.migration, () => setDialog("migrate"));
1872
+ return {
1873
+ flow,
1874
+ catalog,
1875
+ issues,
1876
+ issueCount,
1877
+ selection: visibleSelection,
1878
+ select,
1879
+ mode,
1880
+ setMode,
1881
+ goToIssue,
1882
+ edit,
1883
+ addState: addState2,
1884
+ connect,
1885
+ connectToNewState,
1886
+ removeState: removeState2,
1887
+ simulation,
1888
+ simulate,
1889
+ dialog,
1890
+ openJson,
1891
+ closeDialog,
1892
+ json,
1893
+ jsonErrors,
1894
+ loadJson,
1895
+ save,
1896
+ saving,
1897
+ saveFailed,
1898
+ dirty,
1899
+ readOnly,
1900
+ migrate
1901
+ };
1902
+ }
1903
+ function useMigrateController(flow, migration, show) {
1904
+ const [chats, setChats] = useState3([]);
1905
+ const [targets, setTargets] = useState3({});
1906
+ const [busy, setBusy] = useState3(false);
1907
+ const [failed, setFailed] = useState3(false);
1908
+ const [moved, setMoved] = useState3(null);
1909
+ const rows = useMemo3(
1910
+ () => chats.map((entry) => {
1911
+ const current = flow.states.find((state) => state.id === entry.stateId);
1912
+ return {
1913
+ ...entry,
1914
+ stateTitle: migration?.stateTitle?.(entry.versionId, entry.stateId) ?? current?.title ?? entry.stateId,
1915
+ removed: !current,
1916
+ target: current ? null : targets[entry.stateId] ?? null
1917
+ };
1918
+ }),
1919
+ [chats, flow, migration, targets]
1920
+ );
1921
+ const canMigrate = !busy && rows.length > 0 && rows.every((row) => !row.removed || row.chats === 0 || row.target !== null);
1922
+ const open = useCallback3(() => {
1923
+ if (!migration) return;
1924
+ show();
1925
+ setMoved(null);
1926
+ setFailed(false);
1927
+ setBusy(true);
1928
+ migration.chatsByState().then(setChats, () => setFailed(true)).finally(() => setBusy(false));
1929
+ }, [migration, show]);
1930
+ const setTarget = useCallback3((stateId, target) => setTargets((current) => ({ ...current, [stateId]: target })), []);
1931
+ const run = useCallback3(async () => {
1932
+ if (!migration) return;
1933
+ const map = Object.fromEntries(rows.filter((row) => row.removed && row.target).map((row) => [row.stateId, row.target]));
1934
+ setBusy(true);
1935
+ setFailed(false);
1936
+ try {
1937
+ const result = await migration.migrate(map);
1938
+ setMoved(result.moved);
1939
+ setChats([]);
1940
+ } catch {
1941
+ setFailed(true);
1942
+ } finally {
1943
+ setBusy(false);
1944
+ }
1945
+ }, [migration, rows]);
1946
+ if (!migration) return null;
1947
+ return { rows, busy, failed, moved, canMigrate, open, setTarget, run };
1948
+ }
1949
+
1950
+ // src/react/editor/flow-editor.tsx
1951
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1952
+ function FlowEditor({ skin, className, style, header: headerMode, legend, minimap, inspector, fullscreen, storageKey, ...options }) {
1953
+ const editor = useFlowEditor(options);
1954
+ const labels = useFlowLabels().editor;
1955
+ const { flow } = editor;
1956
+ const space = useBoardSpace(
1957
+ {
1958
+ ...headerMode !== void 0 ? { header: headerMode } : {},
1959
+ ...legend !== void 0 ? { legend } : {},
1960
+ ...minimap !== void 0 ? { minimap } : {},
1961
+ ...inspector !== void 0 ? { inspector } : {},
1962
+ ...fullscreen !== void 0 ? { fullscreen } : {},
1963
+ ...storageKey !== void 0 ? { storageKey } : {}
1964
+ },
1965
+ editor
1966
+ );
1967
+ const [root, setRoot] = useState4(null);
1968
+ const { setFullscreen } = space;
1969
+ const onTab = useFullscreenBehavior(space.fullscreen, () => setFullscreen(false), editor.dialog !== null, root);
1970
+ const header = /* @__PURE__ */ jsx2(
1971
+ skin.FlowHeader,
1972
+ {
1973
+ name: flow.name,
1974
+ onRename: (name) => editor.edit((current) => ({ ...current, name })),
1975
+ mode: editor.mode,
1976
+ onModeChange: editor.setMode,
1977
+ issues: editor.issueCount,
1978
+ onAddState: () => editor.addState(freeSpot(flow)),
1979
+ onOpenCard: () => editor.select({ kind: "card" }),
1980
+ onOpenJson: editor.openJson,
1981
+ ...options.onSave && !editor.readOnly ? { onSave: () => void editor.save() } : {},
1982
+ saving: editor.saving,
1983
+ dirty: editor.dirty,
1984
+ saveFailed: editor.saveFailed,
1985
+ readOnly: editor.readOnly,
1986
+ ...editor.migrate && !editor.readOnly ? { onOpenMigrate: editor.migrate.open } : {},
1987
+ ...space.header === "compact" ? { compact: true } : {},
1988
+ ...space.fullscreenAllowed ? { fullscreen: { active: space.fullscreen, onToggle: () => setFullscreen(!space.fullscreen) } } : {}
1989
+ }
1990
+ );
1991
+ return /* @__PURE__ */ jsx2(
1992
+ "div",
1993
+ {
1994
+ ref: setRoot,
1995
+ "data-flow-fullscreen": space.fullscreen ? "" : void 0,
1996
+ ...space.fullscreen ? { role: "dialog", "aria-modal": true, "aria-label": labels.board.space.fullscreenRegion, onKeyDown: onTab } : {},
1997
+ style: space.fullscreen ? FULLSCREEN_STYLE : { display: "contents" },
1998
+ children: /* @__PURE__ */ jsx2(
1999
+ skin.FlowEditorFrame,
2000
+ {
2001
+ header,
2002
+ board: /* @__PURE__ */ jsx2(FlowBoard, { editor, skin, space }),
2003
+ panel: space.inspectorOpen ? /* @__PURE__ */ jsx2(EditorPanel, { editor, skin, labels }) : null,
2004
+ dialog: /* @__PURE__ */ jsx2(EditorDialog, { editor, skin }),
2005
+ ...space.header === "compact" ? { compact: true } : {},
2006
+ ...space.fullscreen ? { style: { height: "100%", minHeight: 0 } } : { ...className !== void 0 ? { className } : {}, ...style !== void 0 ? { style } : {} }
2007
+ }
2008
+ )
2009
+ }
2010
+ );
2011
+ }
2012
+ var FULLSCREEN_STYLE = {
2013
+ position: "fixed",
2014
+ inset: 0,
2015
+ zIndex: 2e3,
2016
+ height: "100dvh",
2017
+ overflow: "auto",
2018
+ background: "var(--canvas, Canvas)",
2019
+ color: "var(--foreground, CanvasText)"
2020
+ };
2021
+ function stateLinks(editor) {
2022
+ return editor.flow.states.map((state) => ({
2023
+ stateId: state.id,
2024
+ title: state.title || state.id,
2025
+ start: editor.flow.start === state.id,
2026
+ final: isFinal(state),
2027
+ onSelect: () => editor.select({ kind: "state", stateId: state.id })
2028
+ }));
2029
+ }
2030
+ function targetOptions(flow, labels, withReturn) {
2031
+ const states = flow.states.map((state) => ({ value: state.id, title: state.title || state.id }));
2032
+ return withReturn ? [...states, { value: RETURN_TARGET, title: labels.returnTarget }] : states;
2033
+ }
2034
+ function ExitCards({ editor, skin, labels, owner }) {
2035
+ const { flow, catalog } = editor;
2036
+ const state = owner === null ? void 0 : stateById(flow, owner);
2037
+ const durationExitId = state && skin.FlowStateTiming ? durationOf(state)?.exitId : void 0;
2038
+ const exits = (owner === null ? flow.anyState.exits : state?.exits ?? []).filter((exit) => exit.id !== durationExitId);
2039
+ const fields = [...flow.clientCard, ...flow.chatNotes];
2040
+ const eventNames = {
2041
+ button: [.../* @__PURE__ */ new Set([...catalogEventNames(catalog), ...catalog.widgets])],
2042
+ tool: [...catalog.tools],
2043
+ app: catalogEventNames(catalog)
2044
+ };
2045
+ const eventLabels = {};
2046
+ for (const name of eventNames.app) {
2047
+ const label = catalogEventLabel(catalog, name);
2048
+ if (label) eventLabels[name] = label;
2049
+ }
2050
+ const targets = targetOptions(flow, labels, owner !== null);
2051
+ return /* @__PURE__ */ jsx2(Fragment2, { children: exits.map((exit, index) => {
2052
+ const fieldType = exit.when.type === "data" ? fieldById(flow, exit.when.field)?.field.type : void 0;
2053
+ return /* @__PURE__ */ jsx2(
2054
+ skin.FlowExitCard,
2055
+ {
2056
+ exitId: exit.id,
2057
+ when: exit.when,
2058
+ to: exit.to,
2059
+ fields,
2060
+ ops: fieldType ? [...FLOW_OPS_BY_FIELD_TYPE[fieldType]] : [...FLOW_DATA_OPS],
2061
+ eventNames,
2062
+ eventLabels,
2063
+ targets,
2064
+ issues: editor.issues.filter((issue) => issue.place.exitId === exit.id),
2065
+ canMoveUp: index > 0,
2066
+ canMoveDown: index < exits.length - 1,
2067
+ onChange: (next) => editor.edit((current) => updateExit(current, owner, exit.id, { when: normalizeCondition(current, exit.when, next.when), to: next.to })),
2068
+ onMoveUp: () => editor.edit((current) => moveExit(current, owner, exit.id, -1)),
2069
+ onMoveDown: () => editor.edit((current) => moveExit(current, owner, exit.id, 1)),
2070
+ onDelete: () => editor.edit((current) => removeExit(current, owner, exit.id))
2071
+ },
2072
+ exit.id
2073
+ );
2074
+ }) });
2075
+ }
2076
+ function FieldRows({ editor, skin, list }) {
2077
+ const fields = editor.flow[list];
2078
+ return /* @__PURE__ */ jsx2(Fragment2, { children: fields.map((field, index) => /* @__PURE__ */ jsx2(
2079
+ skin.FlowFieldRow,
2080
+ {
2081
+ field,
2082
+ list,
2083
+ canMoveUp: index > 0,
2084
+ canMoveDown: index < fields.length - 1,
2085
+ issues: editor.issues.filter((issue) => issue.place.fieldId === field.id),
2086
+ onChange: (patch) => editor.edit((current) => updateField(current, list, field.id, patch)),
2087
+ onMoveUp: () => editor.edit((current) => moveField(current, list, field.id, -1)),
2088
+ onMoveDown: () => editor.edit((current) => moveField(current, list, field.id, 1)),
2089
+ onDelete: () => editor.edit((current) => removeField(current, list, field.id))
2090
+ },
2091
+ field.id
2092
+ )) });
2093
+ }
2094
+ function EditorPanel({ editor, skin, labels }) {
2095
+ const panel = /* @__PURE__ */ jsx2(EditablePanel, { editor, skin, labels });
2096
+ if (!editor.readOnly || editor.mode !== "edit") return panel;
2097
+ return /* @__PURE__ */ jsx2("fieldset", { disabled: true, "data-flow-read-only": "", className: "m-0 flex min-h-0 min-w-0 flex-1 flex-col border-0 p-0", children: panel });
2098
+ }
2099
+ function EditablePanel({ editor, skin, labels }) {
2100
+ const { flow, selection } = editor;
2101
+ if (editor.mode === "check") {
2102
+ return /* @__PURE__ */ jsx2(skin.FlowCheckPanel, { issues: editor.issues.map((issue) => ({ ...issue, onGo: () => editor.goToIssue(issue) })) });
2103
+ }
2104
+ if (editor.mode === "run" && editor.simulation) return /* @__PURE__ */ jsx2(RunPanel, { editor, skin, labels });
2105
+ if (selection.kind === "state") {
2106
+ const state = stateById(flow, selection.stateId);
2107
+ return /* @__PURE__ */ jsx2(
2108
+ skin.FlowStatePanel,
2109
+ {
2110
+ stateId: state.id,
2111
+ title: state.title,
2112
+ instructions: state.instructions,
2113
+ firstMessage: state.firstMessage,
2114
+ learn: state.learn,
2115
+ tools: state.tools,
2116
+ tags: state.tags,
2117
+ start: flow.start === state.id,
2118
+ noSideQuestions: state.noSideQuestions,
2119
+ fields: [...flow.clientCard, ...flow.chatNotes],
2120
+ catalogTools: [.../* @__PURE__ */ new Set([...flow.tools, ...editor.catalog.tools])],
2121
+ exits: /* @__PURE__ */ jsx2(ExitCards, { editor, skin, labels, owner: state.id }),
2122
+ timing: /* @__PURE__ */ jsx2(StateTiming, { editor, skin, labels, state }),
2123
+ issues: editor.issues.filter((issue) => issue.place.stateId === state.id && !issue.place.exitId),
2124
+ onChange: (patch) => editor.edit((current) => {
2125
+ const next = updateState(current, state.id, patch);
2126
+ const missing = (patch.tools ?? []).filter((tool) => !next.tools.includes(tool));
2127
+ return missing.length ? { ...next, tools: [...next.tools, ...missing] } : next;
2128
+ }),
2129
+ onMakeStart: () => editor.edit((current) => setStart(current, state.id)),
2130
+ onAddExit: (kind) => {
2131
+ const made = addExit(flow, state.id, kind);
2132
+ editor.edit(() => made.flow);
2133
+ editor.select({ kind: "state", stateId: state.id, exitId: made.exitId });
2134
+ },
2135
+ onDelete: () => {
2136
+ editor.removeState(state.id);
2137
+ editor.select({ kind: "overview" });
2138
+ }
2139
+ },
2140
+ state.id
2141
+ );
2142
+ }
2143
+ if (selection.kind === "any") {
2144
+ return /* @__PURE__ */ jsx2(
2145
+ skin.FlowAnyStatePanel,
2146
+ {
2147
+ except: flow.anyState.except,
2148
+ states: stateLinks(editor),
2149
+ onExceptChange: (except) => editor.edit((current) => setExcept(current, except)),
2150
+ exits: /* @__PURE__ */ jsx2(ExitCards, { editor, skin, labels, owner: null }),
2151
+ onAddExit: (kind) => editor.edit((current) => addExit(current, null, kind).flow)
2152
+ }
2153
+ );
2154
+ }
2155
+ if (selection.kind === "card") {
2156
+ return /* @__PURE__ */ jsx2(
2157
+ skin.FlowCardPanel,
2158
+ {
2159
+ clientCard: /* @__PURE__ */ jsx2(FieldRows, { editor, skin, list: "clientCard" }),
2160
+ chatNotes: /* @__PURE__ */ jsx2(FieldRows, { editor, skin, list: "chatNotes" }),
2161
+ empty: flow.clientCard.length === 0 && flow.chatNotes.length === 0,
2162
+ sideQuestionEveryMessages: flow.pace.sideQuestionEveryMessages,
2163
+ onPaceChange: (messages) => editor.edit((current) => ({ ...current, pace: { sideQuestionEveryMessages: Math.max(1, Math.round(messages) || 1) } })),
2164
+ onAddField: (list) => editor.edit((current) => addField(current, list, labels.cardPanel.newField).flow),
2165
+ appFields: catalogFieldsToAdd(flow, editor.catalog).map((field) => ({ id: field.id, title: field.title })),
2166
+ onAddAppField: (list, fieldId) => {
2167
+ const field = editor.catalog.fields?.find((candidate) => candidate.id === fieldId);
2168
+ if (field) editor.edit((current) => addCatalogField(current, list, field));
2169
+ }
2170
+ }
2171
+ );
2172
+ }
2173
+ return /* @__PURE__ */ jsx2(
2174
+ skin.FlowOverviewPanel,
2175
+ {
2176
+ name: flow.name,
2177
+ common: flow.common,
2178
+ onCommonChange: (common) => editor.edit((current) => ({ ...current, common })),
2179
+ counts: {
2180
+ states: flow.states.length,
2181
+ exits: flow.anyState.exits.length + flow.states.reduce((sum, state) => sum + state.exits.length, 0),
2182
+ finals: flow.states.filter(isFinal).length
2183
+ },
2184
+ states: stateLinks(editor),
2185
+ issues: editor.issueCount,
2186
+ sections: /* @__PURE__ */ jsxs2(Fragment2, { children: [
2187
+ /* @__PURE__ */ jsx2(ScheduleSection, { editor, skin, labels }),
2188
+ /* @__PURE__ */ jsx2(SideTasksSection, { editor, skin, focused: selection.kind === "overview" ? selection.sideTaskId : void 0 })
2189
+ ] })
2190
+ }
2191
+ );
2192
+ }
2193
+ var SCHEDULE_ISSUES = /* @__PURE__ */ new Set(["night_before_day_start", "unknown_deadline_state", "day_frame_soft_after_hard"]);
2194
+ var isScheduleIssue = (issue) => SCHEDULE_ISSUES.has(issue.code) || issue.code === "empty_nudges" && !issue.place.stateId;
2195
+ function NudgesEditor({ skin, nudges, optional, onChange }) {
2196
+ if (!skin.FlowNudgesEditor) return null;
2197
+ return /* @__PURE__ */ jsx2(skin.FlowNudgesEditor, { nudges, optional, onChange });
2198
+ }
2199
+ function ScheduleSection({ editor, skin, labels }) {
2200
+ if (!skin.FlowScheduleSection) return null;
2201
+ const { flow } = editor;
2202
+ return /* @__PURE__ */ jsx2(
2203
+ skin.FlowScheduleSection,
2204
+ {
2205
+ schedule: flow.schedule ?? null,
2206
+ afterEnd: flow.afterEnd ?? "reply",
2207
+ states: targetOptions(flow, labels, false),
2208
+ issues: editor.issues.filter(isScheduleIssue),
2209
+ nudges: /* @__PURE__ */ jsx2(
2210
+ NudgesEditor,
2211
+ {
2212
+ skin,
2213
+ nudges: flow.schedule?.nudges ?? null,
2214
+ optional: true,
2215
+ onChange: (nudges) => editor.edit((current) => updateScheduleNudges(current, nudges))
2216
+ }
2217
+ ),
2218
+ onToggle: (on) => editor.edit((current) => setScheduleOn(current, on)),
2219
+ onChange: (patch) => editor.edit((current) => updateSchedule(current, patch)),
2220
+ onAfterEndChange: (mode) => editor.edit((current) => setAfterEnd(current, mode))
2221
+ }
2222
+ );
2223
+ }
2224
+ function SideTasksSection({ editor, skin, focused }) {
2225
+ const labels = useFlowLabels().editor;
2226
+ if (!skin.FlowSideTasksSection) return null;
2227
+ const { flow, catalog } = editor;
2228
+ return /* @__PURE__ */ jsx2(
2229
+ skin.FlowSideTasksSection,
2230
+ {
2231
+ tasks: (flow.sideTasks ?? []).map((task) => ({
2232
+ ...task,
2233
+ issues: editor.issues.filter((issue) => issue.place.sideTaskId === task.id),
2234
+ focused: task.id === focused
2235
+ })),
2236
+ fields: [
2237
+ ...[...flow.clientCard, ...flow.chatNotes].map((field) => ({ id: field.id, title: field.name || field.id, fromApp: false })),
2238
+ ...catalogFieldsToAdd(flow, catalog).map((field) => ({ id: field.id, title: field.title, fromApp: true }))
2239
+ ],
2240
+ builtInCity: flow.schedule !== void 0,
2241
+ onAdd: () => editor.edit((current) => addSideTask(current, labels.sideTasks.newTask).flow),
2242
+ onChange: (sideTaskId, patch) => editor.edit((current) => updateSideTask(current, sideTaskId, patch)),
2243
+ onFieldChange: (sideTaskId, fieldId) => editor.edit((current) => setSideTaskField(current, sideTaskId, fieldId, catalog)),
2244
+ onDelete: (sideTaskId) => editor.edit((current) => removeSideTask(current, sideTaskId))
2245
+ }
2246
+ );
2247
+ }
2248
+ function StateTiming({ editor, skin, labels, state }) {
2249
+ if (!skin.FlowStateTiming) return null;
2250
+ const { flow } = editor;
2251
+ const duration = durationOf(state);
2252
+ const mode = nudgesMode(state);
2253
+ const own = state.nudges && state.nudges !== "off" ? state.nudges : null;
2254
+ return /* @__PURE__ */ jsx2(
2255
+ skin.FlowStateTiming,
2256
+ {
2257
+ duration: duration && { amount: duration.amount, unit: duration.unit, to: duration.to },
2258
+ daysAvailable: flow.schedule !== void 0 || duration !== null && duration.unit !== "h",
2259
+ targets: targetOptions(flow, labels, true),
2260
+ issues: duration ? editor.issues.filter((issue) => issue.place.exitId === duration.exitId) : [],
2261
+ onDurationChange: (value) => editor.edit((current) => setDuration(current, state.id, value)),
2262
+ onDurationTargetChange: (to) => {
2263
+ if (!duration) return;
2264
+ editor.edit((current) => {
2265
+ const exit = stateById(current, state.id)?.exits.find((candidate) => candidate.id === duration.exitId);
2266
+ return exit ? updateExit(current, state.id, exit.id, { when: exit.when, to }) : current;
2267
+ });
2268
+ },
2269
+ nudgesMode: mode,
2270
+ onNudgesModeChange: (next) => editor.edit((current) => setStateNudges(current, state.id, next)),
2271
+ nudgesDays: stateNudgesDays(state),
2272
+ onNudgesDaysChange: (days) => editor.edit((current) => setStateNudgesDays(current, state.id, days)),
2273
+ nudges: own && /* @__PURE__ */ jsx2(
2274
+ NudgesEditor,
2275
+ {
2276
+ skin,
2277
+ nudges: own,
2278
+ optional: false,
2279
+ onChange: (nudges) => editor.edit((current) => nudges ? updateStateNudges(current, state.id, nudges) : setStateNudges(current, state.id, "off"))
2280
+ }
2281
+ )
2282
+ }
2283
+ );
2284
+ }
2285
+ function RunPanel({ editor, skin, labels }) {
2286
+ const { flow } = editor;
2287
+ const sim = editor.simulation;
2288
+ const state = stateById(flow, sim.run.state);
2289
+ const choices = useMemo4(() => simulationChoices(flow, sim), [flow, sim]);
2290
+ const question = nextQuestionField(flow, sim);
2291
+ const exitById = (exitId) => [...flow.anyState.exits, ...flow.states.flatMap((candidate) => candidate.exits)].find((exit) => exit.id === exitId);
2292
+ const knownRow = (field, value, stale) => ({
2293
+ fieldId: field.id,
2294
+ name: field.name || field.id,
2295
+ value: value === void 0 || value === null || value === "" ? null : String(value),
2296
+ stale,
2297
+ onChange: (text) => editor.simulate.setKnown(field.id, text)
2298
+ });
2299
+ const known = [
2300
+ ...flow.clientCard.map((field) => {
2301
+ const entry = sim.card[field.id];
2302
+ return knownRow(field, entry?.value, entry !== void 0 && isFieldStale(field, entry.updatedAt, sim.now));
2303
+ }),
2304
+ ...flow.chatNotes.map((field) => knownRow(field, sim.run.notes[field.id], false))
2305
+ ];
2306
+ return /* @__PURE__ */ jsx2(
2307
+ skin.FlowRunPanel,
2308
+ {
2309
+ current: { stateId: sim.run.state, title: state?.title || sim.run.state, goal: stateGoal(state?.instructions ?? "") },
2310
+ choices: choices.map((choice) => {
2311
+ const exit = exitById(choice.exitId);
2312
+ return {
2313
+ exitId: choice.exitId,
2314
+ kind: edgeKind(exit),
2315
+ text: conditionText(flow, exit.when, labels, editor.catalog),
2316
+ target: choice.target ? targetTitle(flow, choice.target, labels) : null,
2317
+ onChoose: () => editor.simulate.choose(choice.exitId)
2318
+ };
2319
+ }),
2320
+ onNothing: editor.simulate.nothing,
2321
+ known,
2322
+ nextQuestion: question ? fieldById(flow, question)?.field.name ?? question : null,
2323
+ path: sim.steps.map((step2) => {
2324
+ const exit = step2.exitId ? exitById(step2.exitId) : void 0;
2325
+ return {
2326
+ stateId: step2.stateId,
2327
+ title: stateById(flow, step2.stateId)?.title || step2.stateId,
2328
+ via: step2.nothing ? labels.runPanel.nothingHappened : exit ? conditionText(flow, exit.when, labels, editor.catalog) : null
2329
+ };
2330
+ }),
2331
+ canGoBack: sim.previous !== null,
2332
+ onBack: editor.simulate.back,
2333
+ onRestart: editor.simulate.restart,
2334
+ onFinish: () => editor.setMode("edit")
2335
+ }
2336
+ );
2337
+ }
2338
+ function download(name, text) {
2339
+ const url = URL.createObjectURL(new Blob([text], { type: "application/json" }));
2340
+ const link = document.createElement("a");
2341
+ link.href = url;
2342
+ link.download = name;
2343
+ link.click();
2344
+ setTimeout(() => URL.revokeObjectURL(url), 0);
2345
+ }
2346
+ function EditorDialog({ editor, skin }) {
2347
+ const labels = useFlowLabels().editor;
2348
+ if (editor.dialog === "json") {
2349
+ return /* @__PURE__ */ jsx2(
2350
+ skin.FlowJsonDialog,
2351
+ {
2352
+ json: editor.json,
2353
+ errors: editor.jsonErrors,
2354
+ onCopy: () => void navigator.clipboard?.writeText(editor.json),
2355
+ onDownload: () => download(`${editor.flow.id}.json`, editor.json),
2356
+ onLoad: (text) => void editor.loadJson(text),
2357
+ onClose: editor.closeDialog,
2358
+ ...editor.readOnly ? { readOnly: true } : {}
2359
+ }
2360
+ );
2361
+ }
2362
+ if (editor.dialog === "migrate" && editor.migrate) {
2363
+ const migrate = editor.migrate;
2364
+ return /* @__PURE__ */ jsx2(
2365
+ skin.FlowMigrateDialog,
2366
+ {
2367
+ rows: migrate.rows.map((row) => ({ ...row, onTargetChange: (stateId) => migrate.setTarget(row.stateId, stateId) })),
2368
+ targets: targetOptions(editor.flow, labels, false),
2369
+ busy: migrate.busy,
2370
+ failed: migrate.failed,
2371
+ moved: migrate.moved,
2372
+ canMigrate: migrate.canMigrate,
2373
+ onMigrate: () => void migrate.run(),
2374
+ onClose: editor.closeDialog
2375
+ }
2376
+ );
2377
+ }
2378
+ return null;
2379
+ }
2380
+
2381
+ // src/react/client-card/use-client-card.ts
2382
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3, useState as useState5 } from "react";
2383
+ function useClientCard(options) {
2384
+ const latest = useRef3(options);
2385
+ latest.current = options;
2386
+ const [state, setState] = useState5("loading");
2387
+ const [entries, setEntries] = useState5([]);
2388
+ const [busy, setBusy] = useState5(/* @__PURE__ */ new Set());
2389
+ const [forgettingAll, setForgettingAll] = useState5(false);
2390
+ const alive = useRef3(true);
2391
+ const load = useCallback4(async () => {
2392
+ setState("loading");
2393
+ try {
2394
+ const loaded = await latest.current.read();
2395
+ if (!alive.current) return;
2396
+ setEntries(loaded);
2397
+ setState("ready");
2398
+ } catch {
2399
+ if (alive.current) setState("failed");
2400
+ }
2401
+ }, []);
2402
+ useEffect3(() => {
2403
+ alive.current = true;
2404
+ void load();
2405
+ return () => {
2406
+ alive.current = false;
2407
+ };
2408
+ }, [load]);
2409
+ const forgetField = async (fieldId) => {
2410
+ setBusy((current) => new Set(current).add(fieldId));
2411
+ try {
2412
+ await latest.current.forget(fieldId);
2413
+ if (alive.current) setEntries((current) => current.filter((entry) => entry.fieldId !== fieldId));
2414
+ } catch {
2415
+ if (alive.current) setState("failed");
2416
+ } finally {
2417
+ if (alive.current)
2418
+ setBusy((current) => {
2419
+ const next = new Set(current);
2420
+ next.delete(fieldId);
2421
+ return next;
2422
+ });
2423
+ }
2424
+ };
2425
+ const forgetAll = async () => {
2426
+ setForgettingAll(true);
2427
+ try {
2428
+ await latest.current.forget("all");
2429
+ if (alive.current) setEntries([]);
2430
+ } catch {
2431
+ if (alive.current) setState("failed");
2432
+ } finally {
2433
+ if (alive.current) setForgettingAll(false);
2434
+ }
2435
+ };
2436
+ const labels = useFlowLabels().clientCard;
2437
+ const { fields, now = Date.now, timeZone, factName = labels.fact } = options;
2438
+ const dateFormat = new Intl.DateTimeFormat("ru-RU", { day: "2-digit", month: "2-digit", year: "numeric", ...timeZone && { timeZone } });
2439
+ const position = (fieldId) => {
2440
+ const index = fields.findIndex((field) => field.id === fieldId);
2441
+ return index === -1 ? Number.MAX_SAFE_INTEGER : index;
2442
+ };
2443
+ const views = [...entries].sort((left, right) => position(left.fieldId) - position(right.fieldId) || left.fieldId.localeCompare(right.fieldId)).map((entry) => {
2444
+ const field = fields.find((item) => item.id === entry.fieldId);
2445
+ const updatedAt = Date.parse(entry.updatedAt);
2446
+ return {
2447
+ fieldId: entry.fieldId,
2448
+ name: field?.name ?? (entry.fieldId.startsWith(FACT_PREFIX) ? factName : entry.fieldId),
2449
+ value: fieldValueText(entry.value),
2450
+ updatedAt: dateFormat.format(updatedAt),
2451
+ stale: field ? isFieldStale(field, updatedAt, now()) : false,
2452
+ busy: busy.has(entry.fieldId),
2453
+ onForget: () => void forgetField(entry.fieldId)
2454
+ };
2455
+ });
2456
+ return { state, fields: views, forgettingAll, onForgetAll: () => void forgetAll(), onRetry: () => void load() };
2457
+ }
2458
+
2459
+ // src/react/client-card/client-card-panel.tsx
2460
+ import { jsx as jsx3 } from "react/jsx-runtime";
2461
+ function ClientCardPanel({ skin, ...options }) {
2462
+ return /* @__PURE__ */ jsx3(skin.ClientCardPanel, { ...useClientCard(options) });
2463
+ }
2464
+
2465
+ // src/react/test-chat/flow-test-chat.tsx
2466
+ import { createContext as createContext3, useContext as useContext3, useMemo as useMemo5 } from "react";
2467
+
2468
+ // src/react/test-chat/use-flow-test-chat.ts
2469
+ import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef4, useState as useState6 } from "react";
2470
+ var RUN_POLL_MS = 1e3;
2471
+ function runOf(run) {
2472
+ if (!run || typeof run.state !== "string") return null;
2473
+ const visited = new Set((run.path ?? []).flatMap((step2) => typeof step2.state === "string" && step2.state !== run.state ? [step2.state] : []));
2474
+ return { state: run.state, visited, day: run.day ?? null };
2475
+ }
2476
+ var sameDay = (left, right) => left === right || left !== null && right !== null && left.day === right.day && left.totalDays === right.totalDays && left.dayStartedAt === right.dayStartedAt;
2477
+ var sameRun = (left, right) => left === right || left !== null && right !== null && left.state === right.state && sameDay(left.day, right.day) && left.visited.size === right.visited.size && [...left.visited].every((id) => right.visited.has(id));
2478
+ var NOTHING_VISITED = /* @__PURE__ */ new Set();
2479
+ function useFlowTestChat(connection, flow) {
2480
+ const [opened, setOpened] = useState6(false);
2481
+ const [chatId, setChatId] = useState6(null);
2482
+ const [timeOffset, setTimeOffset] = useState6(0);
2483
+ const [busy, setBusy] = useState6(false);
2484
+ const [failed, setFailed] = useState6(false);
2485
+ const [run, setRun] = useState6(null);
2486
+ const latest = useRef4({ connection, flow, chatId });
2487
+ latest.current = { connection, flow, chatId };
2488
+ const refresh = useCallback5(async (id) => {
2489
+ try {
2490
+ const next = runOf(await latest.current.connection.readRun(id));
2491
+ if (latest.current.chatId === id) setRun((current) => sameRun(current, next) ? current : next);
2492
+ } catch (error) {
2493
+ console.error("[flow] test chat: run not read", error);
2494
+ }
2495
+ }, []);
2496
+ useEffect4(() => {
2497
+ if (!opened || chatId === null) return;
2498
+ void refresh(chatId);
2499
+ const timer = setInterval(() => void refresh(chatId), RUN_POLL_MS);
2500
+ return () => clearInterval(timer);
2501
+ }, [opened, chatId, refresh]);
2502
+ const work = useCallback5(async (job) => {
2503
+ setBusy(true);
2504
+ setFailed(false);
2505
+ try {
2506
+ await job();
2507
+ } catch (error) {
2508
+ console.error("[flow] test chat:", error);
2509
+ setFailed(true);
2510
+ } finally {
2511
+ setBusy(false);
2512
+ }
2513
+ }, []);
2514
+ const start = useCallback5(
2515
+ () => work(async () => {
2516
+ const { chatId: next } = await latest.current.connection.start(latest.current.flow);
2517
+ setRun(null);
2518
+ setChatId(next);
2519
+ setTimeOffset(0);
2520
+ }),
2521
+ [work]
2522
+ );
2523
+ const shift2 = (move) => {
2524
+ const current = latest.current.chatId;
2525
+ if (current === null) return;
2526
+ void work(async () => {
2527
+ const { timeOffset: next } = await move(current);
2528
+ setTimeOffset(next);
2529
+ await refresh(current);
2530
+ });
2531
+ };
2532
+ return {
2533
+ opened,
2534
+ chatId,
2535
+ timeOffset,
2536
+ busy,
2537
+ failed,
2538
+ stateId: chatId === null ? null : run?.state ?? flow.start,
2539
+ visited: run?.visited ?? NOTHING_VISITED,
2540
+ day: chatId === null ? null : run?.day ?? null,
2541
+ open() {
2542
+ setOpened(true);
2543
+ if (latest.current.chatId === null) void start();
2544
+ },
2545
+ close: () => setOpened(false),
2546
+ reset: () => void start(),
2547
+ advance: (ms) => shift2((id) => latest.current.connection.advance(id, ms)),
2548
+ nextDay: connection.advanceToNextDay ? () => shift2((id) => latest.current.connection.advanceToNextDay(id)) : null
2549
+ };
2550
+ }
2551
+
2552
+ // src/react/test-chat/flow-test-chat.tsx
2553
+ import { jsx as jsx4 } from "react/jsx-runtime";
2554
+ var MINUTE = 6e4;
2555
+ var HOUR = 60 * MINUTE;
2556
+ var DAY = 24 * HOUR;
2557
+ var DEFAULT_ADVANCE_STEPS = [20 * MINUTE, 3 * HOUR, DAY];
2558
+ function formatDuration(ms, units) {
2559
+ const minutes = Math.round(ms / MINUTE);
2560
+ const parts = [
2561
+ [Math.floor(minutes / (24 * 60)), units.day],
2562
+ [Math.floor(minutes / 60) % 24, units.hour],
2563
+ [minutes % 60, units.minute]
2564
+ ];
2565
+ return parts.filter(([amount]) => amount > 0).map(([amount, unit]) => `${amount} ${unit}`).join(" ");
2566
+ }
2567
+ function formatClockOffset(ms, labels = russianFlowTestChatLabels) {
2568
+ const { noOffset, units } = labels;
2569
+ const text = formatDuration(ms, units);
2570
+ return text ? `+${text}` : noOffset;
2571
+ }
2572
+ function formatDayLine(day, at, timeZone, labels = russianFlowTestChatLabels) {
2573
+ if (!day) return null;
2574
+ const time = new Intl.DateTimeFormat("ru-RU", { timeZone, hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(at);
2575
+ return `${day.day === 0 ? labels.beforeFirstDay : labels.day(day.day, day.totalDays)} \xB7 ${time}`;
2576
+ }
2577
+ var browserTimeZone2 = () => Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
2578
+ var TestChatContext = createContext3(null);
2579
+ function useTestChatView() {
2580
+ const view = useContext3(TestChatContext);
2581
+ if (!view) throw new Error("Test chat parts render only inside FlowTestChat.");
2582
+ return view;
2583
+ }
2584
+ function withTestChat(skin) {
2585
+ function Header(props) {
2586
+ const { controller } = useTestChatView();
2587
+ return /* @__PURE__ */ jsx4(skin.FlowHeader, { ...props, onOpenTestChat: controller.open });
2588
+ }
2589
+ function Frame(props) {
2590
+ const { panel } = useTestChatView();
2591
+ return /* @__PURE__ */ jsx4(skin.FlowEditorFrame, { ...props, panel: panel ?? props.panel });
2592
+ }
2593
+ function StateNode(props) {
2594
+ const { controller } = useTestChatView();
2595
+ if (!controller.opened || controller.stateId === null) return /* @__PURE__ */ jsx4(skin.FlowStateNode, { ...props });
2596
+ const run = controller.stateId === props.stateId ? "current" : controller.visited.has(props.stateId) ? "visited" : null;
2597
+ return /* @__PURE__ */ jsx4(skin.FlowStateNode, { ...props, run, dimmed: false });
2598
+ }
2599
+ return { ...skin, FlowHeader: Header, FlowEditorFrame: Frame, FlowStateNode: StateNode };
2600
+ }
2601
+ function FlowTestChat({ testChat, advanceSteps = DEFAULT_ADVANCE_STEPS, skin, ...editor }) {
2602
+ const labels = useFlowLabels().testChat;
2603
+ const controller = useFlowTestChat(testChat, editor.value);
2604
+ const testSkin = useMemo5(() => withTestChat(skin), [skin]);
2605
+ const state = controller.stateId === null ? void 0 : stateById(editor.value, controller.stateId);
2606
+ const panel = controller.opened ? /* @__PURE__ */ jsx4(
2607
+ skin.FlowTestChatFrame,
2608
+ {
2609
+ stateTitle: state?.title || controller.stateId || "",
2610
+ clock: formatClockOffset(controller.timeOffset, labels),
2611
+ advanceSteps: advanceSteps.map((ms) => ({ label: formatDuration(ms, labels.units), ms })),
2612
+ onAdvance: controller.advance,
2613
+ day: formatDayLine(controller.day, (editor.now?.() ?? Date.now()) + controller.timeOffset, editor.timeZone ?? browserTimeZone2(), labels),
2614
+ ...controller.nextDay && editor.value.schedule ? { onNextDay: controller.nextDay } : {},
2615
+ busy: controller.busy,
2616
+ failed: controller.failed,
2617
+ onReset: controller.reset,
2618
+ onClose: controller.close,
2619
+ children: controller.chatId === null ? null : testChat.renderChat(controller.chatId)
2620
+ }
2621
+ ) : null;
2622
+ return /* @__PURE__ */ jsx4(TestChatContext.Provider, { value: { controller, panel }, children: /* @__PURE__ */ jsx4(FlowEditor, { skin: testSkin, ...editor }) });
2623
+ }
2624
+ export {
2625
+ ClientCardPanel,
2626
+ DEFAULT_ADVANCE_STEPS,
2627
+ DEFAULT_NUDGES,
2628
+ DEFAULT_SCHEDULE,
2629
+ FlowEditor,
2630
+ FlowLabelsProvider,
2631
+ FlowTestChat,
2632
+ chooseExit,
2633
+ chooseNothing,
2634
+ conditionText,
2635
+ englishFlowLabels,
2636
+ formatClockOffset,
2637
+ formatDayLine,
2638
+ formatDuration,
2639
+ nextQuestionField,
2640
+ russianFlowLabels,
2641
+ setKnownValue,
2642
+ simulationChoices,
2643
+ startSimulation,
2644
+ stepBack,
2645
+ useClientCard,
2646
+ useFlowEditor,
2647
+ useFlowLabels,
2648
+ useFlowTestChat
2649
+ };
2650
+ //# sourceMappingURL=react.js.map