@adatechnology/conversations-ui 0.1.0-rc.39 → 0.1.0-rc.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-DXPSPUWF.js → chunk-DKPXKQGC.js} +1 -1
- package/dist/flows/index.d.ts +88 -5
- package/dist/flows/index.js +1104 -686
- package/dist/index.js +1 -1
- package/package.json +8 -7
- package/src/Tooltip.tsx +5 -2
- package/src/flows/FlowConnectionEdge.tsx +104 -0
- package/src/flows/FlowLegend.tsx +125 -0
- package/src/flows/FlowNodeCard.tsx +188 -30
- package/src/flows/FlowNodePanel.tsx +5 -2
- package/src/flows/FlowPalette.tsx +105 -78
- package/src/flows/FlowsWorkspace.tsx +177 -7
- package/src/flows/flowCanvasModel.test.ts +145 -1
- package/src/flows/flowCanvasModel.ts +54 -44
- package/src/flows/flowEditorOps.test.ts +35 -0
- package/src/flows/flowEditorOps.ts +25 -0
- package/src/flows/flowGraph.ts +72 -47
- package/src/flows/index.ts +4 -1
- package/src/flows/labels.ts +39 -0
package/dist/flows/index.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TooltipLayer
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-DKPXKQGC.js";
|
|
4
4
|
import {
|
|
5
5
|
parseWhatsAppFormatting,
|
|
6
6
|
useIsDarkTheme
|
|
7
7
|
} from "../chunk-WCBDXZ3X.js";
|
|
8
8
|
|
|
9
9
|
// src/flows/FlowNodeCard.tsx
|
|
10
|
+
import { useState } from "react";
|
|
10
11
|
import { Handle, Position } from "@xyflow/react";
|
|
11
12
|
import {
|
|
12
13
|
MessageCircleQuestion,
|
|
14
|
+
Plus,
|
|
13
15
|
GitBranch,
|
|
14
16
|
Zap,
|
|
15
17
|
ListTree,
|
|
@@ -18,9 +20,254 @@ import {
|
|
|
18
20
|
AlertCircle,
|
|
19
21
|
Headset,
|
|
20
22
|
Clock3,
|
|
21
|
-
ShoppingBag
|
|
23
|
+
ShoppingBag,
|
|
24
|
+
RotateCcw
|
|
22
25
|
} from "lucide-react";
|
|
26
|
+
|
|
27
|
+
// src/flows/flowGraph.ts
|
|
28
|
+
import { FLOW_ACTION_KIND } from "@adatechnology/meta-whatsapp-contracts";
|
|
29
|
+
var CONDITION_OPERATORS = [">", ">=", "<", "<=", "==", "!=", "contains"];
|
|
30
|
+
var BUILT_IN_ACTION_KINDS = FLOW_ACTION_KIND;
|
|
31
|
+
var PASS_THROUGH_ACTION_KINDS = ["send_media"];
|
|
32
|
+
var CROSS_FLOW_PREFIX = "flow:";
|
|
33
|
+
var isCrossFlowTarget = (target) => target.startsWith(CROSS_FLOW_PREFIX);
|
|
34
|
+
var crossFlowKey = (target) => target.slice(CROSS_FLOW_PREFIX.length);
|
|
35
|
+
var NODE_CARD_WIDTH = 240;
|
|
36
|
+
function estimateNodeHeight(node) {
|
|
37
|
+
const HEADER_HEIGHT = 28;
|
|
38
|
+
const BODY_HEIGHT = 56;
|
|
39
|
+
const PADDING = 16;
|
|
40
|
+
const ROW_HEIGHT = 34;
|
|
41
|
+
const rowCount = node.type === "action" ? (
|
|
42
|
+
// Ação de passagem desenha uma linha de saída; terminal não desenha nenhuma.
|
|
43
|
+
node.actionKind && PASS_THROUGH_ACTION_KINDS.includes(node.actionKind) ? 1 : 0
|
|
44
|
+
) : node.type === "condition" ? 2 : node.type === "menu" || node.questionType === "choice" ? (node.options?.length ?? 0) + 1 : 1;
|
|
45
|
+
return HEADER_HEIGHT + BODY_HEIGHT + PADDING + rowCount * ROW_HEIGHT;
|
|
46
|
+
}
|
|
47
|
+
var WHATSAPP_LIMITS = {
|
|
48
|
+
MAX_BUTTONS: 3,
|
|
49
|
+
MAX_LIST_ROWS: 10,
|
|
50
|
+
BUTTON_TITLE_MAX: 20,
|
|
51
|
+
LIST_ROW_TITLE_MAX: 24,
|
|
52
|
+
BODY_MAX: 1024
|
|
53
|
+
};
|
|
54
|
+
function rendersAsButtons(options) {
|
|
55
|
+
return (options?.length ?? 0) <= WHATSAPP_LIMITS.MAX_BUTTONS;
|
|
56
|
+
}
|
|
57
|
+
function targetsOf(node) {
|
|
58
|
+
if (!node.next) return [];
|
|
59
|
+
if (typeof node.next === "string") return [{ target: node.next }];
|
|
60
|
+
return [
|
|
61
|
+
...Object.entries(node.next.byAnswer).map(([optionId, target]) => ({ target, optionId })),
|
|
62
|
+
{ target: node.next.default, isDefault: true }
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
function validateGraph(graph, issueText) {
|
|
66
|
+
const issues = [];
|
|
67
|
+
const nodeIds = new Set(Object.keys(graph.nodes));
|
|
68
|
+
const isValidTarget = (target) => nodeIds.has(target) || isCrossFlowTarget(target);
|
|
69
|
+
if (!nodeIds.has(graph.startNodeId)) {
|
|
70
|
+
issues.push({ severity: "error", message: issueText.noStart });
|
|
71
|
+
}
|
|
72
|
+
for (const node of Object.values(graph.nodes)) {
|
|
73
|
+
for (const { target } of targetsOf(node)) {
|
|
74
|
+
if (!isValidTarget(target)) {
|
|
75
|
+
issues.push({ severity: "error", nodeId: node.id, message: issueText.brokenRef(node.id, target) });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const isChoice = node.questionType === "choice" || node.type === "menu";
|
|
79
|
+
if (isChoice) {
|
|
80
|
+
const options = node.options ?? [];
|
|
81
|
+
if (options.length === 0) {
|
|
82
|
+
issues.push({ severity: "error", nodeId: node.id, message: issueText.choiceWithoutOptions(node.id) });
|
|
83
|
+
}
|
|
84
|
+
const seen = /* @__PURE__ */ new Set();
|
|
85
|
+
for (const [optionId, label] of options) {
|
|
86
|
+
if (seen.has(optionId)) {
|
|
87
|
+
issues.push({ severity: "error", nodeId: node.id, message: issueText.duplicatedOptionId(node.id, optionId) });
|
|
88
|
+
}
|
|
89
|
+
seen.add(optionId);
|
|
90
|
+
const byAnswer = typeof node.next === "object" && node.next ? node.next.byAnswer : {};
|
|
91
|
+
if (!byAnswer[optionId]) {
|
|
92
|
+
issues.push({ severity: "warning", nodeId: node.id, message: issueText.optionWithoutTarget(node.id, label) });
|
|
93
|
+
}
|
|
94
|
+
const usesButtons = rendersAsButtons(options);
|
|
95
|
+
if (usesButtons && label.length > WHATSAPP_LIMITS.BUTTON_TITLE_MAX) {
|
|
96
|
+
issues.push({ severity: "error", nodeId: node.id, message: issueText.buttonTitleTooLong(node.id, label) });
|
|
97
|
+
}
|
|
98
|
+
if (!usesButtons && label.length > WHATSAPP_LIMITS.LIST_ROW_TITLE_MAX) {
|
|
99
|
+
issues.push({ severity: "error", nodeId: node.id, message: issueText.listTitleTooLong(node.id, label) });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (options.length > WHATSAPP_LIMITS.MAX_LIST_ROWS) {
|
|
103
|
+
issues.push({ severity: "error", nodeId: node.id, message: issueText.tooManyOptions(node.id, options.length) });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const bodyText = node.question ?? node.directMessage ?? "";
|
|
107
|
+
if (bodyText.length > WHATSAPP_LIMITS.BODY_MAX) {
|
|
108
|
+
issues.push({ severity: "error", nodeId: node.id, message: issueText.bodyTooLong(node.id) });
|
|
109
|
+
}
|
|
110
|
+
if (node.type === "question" && !node.next) {
|
|
111
|
+
issues.push({ severity: "warning", nodeId: node.id, message: issueText.deadEndQuestion(node.id) });
|
|
112
|
+
}
|
|
113
|
+
if (node.type === "condition") {
|
|
114
|
+
if (!node.conditionContextKey || !node.conditionOperator || !node.conditionValue) {
|
|
115
|
+
issues.push({ severity: "error", nodeId: node.id, message: issueText.conditionIncomplete(node.id) });
|
|
116
|
+
}
|
|
117
|
+
const byAnswer = typeof node.next === "object" && node.next ? node.next.byAnswer : {};
|
|
118
|
+
if (!byAnswer.true)
|
|
119
|
+
issues.push({
|
|
120
|
+
severity: "warning",
|
|
121
|
+
nodeId: node.id,
|
|
122
|
+
message: issueText.conditionBranchMissing(node.id, "true")
|
|
123
|
+
});
|
|
124
|
+
if (!byAnswer.false)
|
|
125
|
+
issues.push({
|
|
126
|
+
severity: "warning",
|
|
127
|
+
nodeId: node.id,
|
|
128
|
+
message: issueText.conditionBranchMissing(node.id, "false")
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const id of findUnreachable(graph)) {
|
|
133
|
+
issues.push({ severity: "warning", nodeId: id, message: issueText.unreachable(id) });
|
|
134
|
+
}
|
|
135
|
+
return issues;
|
|
136
|
+
}
|
|
137
|
+
function findUnreachable(graph) {
|
|
138
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
139
|
+
const queue = [graph.startNodeId];
|
|
140
|
+
while (queue.length > 0) {
|
|
141
|
+
const id = queue.shift();
|
|
142
|
+
if (reachable.has(id) || !graph.nodes[id]) continue;
|
|
143
|
+
reachable.add(id);
|
|
144
|
+
for (const { target } of targetsOf(graph.nodes[id])) {
|
|
145
|
+
if (!isCrossFlowTarget(target)) queue.push(target);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return Object.keys(graph.nodes).filter((id) => !reachable.has(id));
|
|
149
|
+
}
|
|
150
|
+
var LAYOUT_COLUMN_GAP = 300;
|
|
151
|
+
var LAYOUT_ROW_GAP = 40;
|
|
152
|
+
function cascadeOrder(params) {
|
|
153
|
+
const placed = /* @__PURE__ */ new Map();
|
|
154
|
+
function visit(id, depth) {
|
|
155
|
+
if (placed.has(id)) return;
|
|
156
|
+
placed.set(id, { depth, order: placed.size });
|
|
157
|
+
for (const next of params.forwardEdges(id)) visit(next, depth + 1);
|
|
158
|
+
}
|
|
159
|
+
if (params.allIds.includes(params.rootId)) visit(params.rootId, 0);
|
|
160
|
+
const strayDepth = Math.max(0, ...[...placed.values()].map((each) => each.depth)) + 1;
|
|
161
|
+
for (const id of params.allIds) {
|
|
162
|
+
if (!placed.has(id)) placed.set(id, { depth: strayDepth, order: placed.size });
|
|
163
|
+
}
|
|
164
|
+
return placed;
|
|
165
|
+
}
|
|
166
|
+
function computeAutoLayout(graph) {
|
|
167
|
+
const placed = cascadeOrder({
|
|
168
|
+
rootId: graph.startNodeId,
|
|
169
|
+
allIds: Object.keys(graph.nodes),
|
|
170
|
+
forwardEdges: (id) => targetsOf(graph.nodes[id] ?? { id, type: "action" }).map((edge) => edge.target).filter((target) => !isCrossFlowTarget(target) && Boolean(graph.nodes[target]))
|
|
171
|
+
});
|
|
172
|
+
const byOrder = [...placed.entries()].sort((a, b) => a[1].order - b[1].order);
|
|
173
|
+
const positions = {};
|
|
174
|
+
let cursorY = 0;
|
|
175
|
+
for (const [id, { depth }] of byOrder) {
|
|
176
|
+
positions[id] = { x: depth * LAYOUT_COLUMN_GAP, y: cursorY };
|
|
177
|
+
cursorY += estimateNodeHeight(graph.nodes[id]) + LAYOUT_ROW_GAP;
|
|
178
|
+
}
|
|
179
|
+
return positions;
|
|
180
|
+
}
|
|
181
|
+
function crossFlowTargetsOf(graph) {
|
|
182
|
+
const keys = /* @__PURE__ */ new Set();
|
|
183
|
+
for (const node of Object.values(graph.nodes)) {
|
|
184
|
+
for (const { target } of targetsOf(node)) {
|
|
185
|
+
if (isCrossFlowTarget(target)) keys.add(crossFlowKey(target));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return [...keys];
|
|
189
|
+
}
|
|
190
|
+
function computeFlowMapLayout(graphs, rootKey) {
|
|
191
|
+
const H_GAP = 280;
|
|
192
|
+
const V_GAP = 170;
|
|
193
|
+
const rank = {};
|
|
194
|
+
const queue = graphs[rootKey] ? [rootKey] : Object.keys(graphs);
|
|
195
|
+
if (graphs[rootKey]) rank[rootKey] = 0;
|
|
196
|
+
while (queue.length > 0) {
|
|
197
|
+
const key = queue.shift();
|
|
198
|
+
const g = graphs[key];
|
|
199
|
+
if (!g) continue;
|
|
200
|
+
for (const target of crossFlowTargetsOf(g)) {
|
|
201
|
+
if (!graphs[target]) continue;
|
|
202
|
+
if (rank[target] === void 0) {
|
|
203
|
+
rank[target] = rank[key] + 1;
|
|
204
|
+
queue.push(target);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const maxRank = Math.max(0, ...Object.values(rank));
|
|
209
|
+
let strayRank = maxRank + 1;
|
|
210
|
+
for (const key of Object.keys(graphs)) {
|
|
211
|
+
if (rank[key] === void 0) rank[key] = strayRank++;
|
|
212
|
+
}
|
|
213
|
+
const layers = {};
|
|
214
|
+
for (const [key, r] of Object.entries(rank)) {
|
|
215
|
+
layers[r] = [...layers[r] ?? [], key];
|
|
216
|
+
}
|
|
217
|
+
const positions = {};
|
|
218
|
+
for (const [r, keys] of Object.entries(layers)) {
|
|
219
|
+
const width = (keys.length - 1) * H_GAP;
|
|
220
|
+
keys.forEach((key, index) => {
|
|
221
|
+
positions[key] = { x: index * H_GAP - width / 2, y: Number(r) * V_GAP };
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return positions;
|
|
225
|
+
}
|
|
226
|
+
function findCollectionChains(graph) {
|
|
227
|
+
const actionIds = new Set(
|
|
228
|
+
Object.values(graph.nodes).filter((n) => n.type === "action").map((n) => n.id)
|
|
229
|
+
);
|
|
230
|
+
const memo = /* @__PURE__ */ new Map();
|
|
231
|
+
function reachableActions(id, stack) {
|
|
232
|
+
if (memo.has(id)) return memo.get(id);
|
|
233
|
+
if (stack.has(id)) return /* @__PURE__ */ new Set();
|
|
234
|
+
if (actionIds.has(id)) return /* @__PURE__ */ new Set([id]);
|
|
235
|
+
const node = graph.nodes[id];
|
|
236
|
+
if (!node) return /* @__PURE__ */ new Set();
|
|
237
|
+
const nextStack = new Set(stack);
|
|
238
|
+
nextStack.add(id);
|
|
239
|
+
const result = /* @__PURE__ */ new Set();
|
|
240
|
+
for (const { target } of targetsOf(node)) {
|
|
241
|
+
if (isCrossFlowTarget(target) || !graph.nodes[target]) continue;
|
|
242
|
+
for (const actionId of reachableActions(target, nextStack)) result.add(actionId);
|
|
243
|
+
}
|
|
244
|
+
memo.set(id, result);
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
const nodeIdsByAction = /* @__PURE__ */ new Map();
|
|
248
|
+
for (const node of Object.values(graph.nodes)) {
|
|
249
|
+
if (node.type !== "question") continue;
|
|
250
|
+
const reached = reachableActions(node.id, /* @__PURE__ */ new Set());
|
|
251
|
+
if (reached.size !== 1) continue;
|
|
252
|
+
const [actionNodeId] = [...reached];
|
|
253
|
+
nodeIdsByAction.set(actionNodeId, [...nodeIdsByAction.get(actionNodeId) ?? [], node.id]);
|
|
254
|
+
}
|
|
255
|
+
return [...nodeIdsByAction.entries()].filter(([, nodeIds]) => nodeIds.length >= 2).map(([actionNodeId, nodeIds]) => ({ actionNodeId, nodeIds }));
|
|
256
|
+
}
|
|
257
|
+
function slugifyNodeId(label, existing) {
|
|
258
|
+
const base = label.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 30) || "no";
|
|
259
|
+
let candidate = base;
|
|
260
|
+
let counter = 2;
|
|
261
|
+
while (existing.has(candidate)) {
|
|
262
|
+
candidate = `${base}_${counter}`;
|
|
263
|
+
counter++;
|
|
264
|
+
}
|
|
265
|
+
return candidate;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/flows/FlowNodeCard.tsx
|
|
23
269
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
270
|
+
var HANDLE_SIZE_PX = 14;
|
|
24
271
|
var NODE_TYPE_COLOR = {
|
|
25
272
|
question: "border-blue-300 bg-blue-50 dark:bg-blue-950/40 dark:border-blue-800",
|
|
26
273
|
entrada_choice: "border-purple-300 bg-purple-50 dark:bg-purple-950/40 dark:border-purple-800",
|
|
@@ -54,51 +301,124 @@ function nodeLabel(node, labels) {
|
|
|
54
301
|
}
|
|
55
302
|
return node.question || node.contextKey || node.id;
|
|
56
303
|
}
|
|
304
|
+
function selfLoopHandles(node) {
|
|
305
|
+
const loops = /* @__PURE__ */ new Set();
|
|
306
|
+
if (typeof node.next === "string") {
|
|
307
|
+
if (node.next === node.id) loops.add("next");
|
|
308
|
+
return loops;
|
|
309
|
+
}
|
|
310
|
+
if (!node.next) return loops;
|
|
311
|
+
for (const [optionId, target] of Object.entries(node.next.byAnswer ?? {})) {
|
|
312
|
+
if (target === node.id) loops.add(optionId);
|
|
313
|
+
}
|
|
314
|
+
if (node.next.default === node.id) loops.add("__default");
|
|
315
|
+
return loops;
|
|
316
|
+
}
|
|
57
317
|
function sourceRows(node, labels) {
|
|
58
|
-
if (node.type === "action")
|
|
318
|
+
if (node.type === "action") {
|
|
319
|
+
return node.actionKind && PASS_THROUGH_ACTION_KINDS.includes(node.actionKind) ? [{ id: "next", label: labels.nodePanel.nextRowLabel, isDefault: false, isSelfLoop: false }] : [];
|
|
320
|
+
}
|
|
321
|
+
const loops = selfLoopHandles(node);
|
|
59
322
|
if (node.type === "condition") {
|
|
60
323
|
return [
|
|
61
|
-
{ id: "true", label: labels.nodePanel.conditionTrue, isDefault: false },
|
|
62
|
-
{ id: "false", label: labels.nodePanel.conditionFalse, isDefault: false }
|
|
324
|
+
{ id: "true", label: labels.nodePanel.conditionTrue, isDefault: false, isSelfLoop: loops.has("true") },
|
|
325
|
+
{ id: "false", label: labels.nodePanel.conditionFalse, isDefault: false, isSelfLoop: loops.has("false") }
|
|
63
326
|
];
|
|
64
327
|
}
|
|
65
328
|
const isChoice = node.type === "menu" || node.questionType === "choice";
|
|
66
|
-
if (!isChoice)
|
|
329
|
+
if (!isChoice)
|
|
330
|
+
return [{ id: "next", label: labels.nodePanel.nextRowLabel, isDefault: false, isSelfLoop: loops.has("next") }];
|
|
67
331
|
const options = node.options ?? [];
|
|
68
332
|
return [
|
|
69
|
-
...options.map(([id, label]) => ({ id, label, isDefault: false })),
|
|
70
|
-
{ id: "__default", label: labels.edgeFallbackLabel, isDefault: true }
|
|
333
|
+
...options.map(([id, label]) => ({ id, label, isDefault: false, isSelfLoop: loops.has(id) })),
|
|
334
|
+
{ id: "__default", label: labels.edgeFallbackLabel, isDefault: true, isSelfLoop: loops.has("__default") }
|
|
71
335
|
];
|
|
72
336
|
}
|
|
73
|
-
function SourceRow({
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
337
|
+
function SourceRow({
|
|
338
|
+
label,
|
|
339
|
+
isDefault,
|
|
340
|
+
handleId,
|
|
341
|
+
addLabel,
|
|
342
|
+
isSelfLoop,
|
|
343
|
+
selfLoopLabel,
|
|
344
|
+
onQuickAdd
|
|
345
|
+
}) {
|
|
346
|
+
const [hovered, setHovered] = useState(false);
|
|
347
|
+
return /* @__PURE__ */ jsxs(
|
|
348
|
+
"div",
|
|
349
|
+
{
|
|
350
|
+
className: `relative ${onQuickAdd ? "pr-9" : ""}`,
|
|
351
|
+
onMouseEnter: () => setHovered(true),
|
|
352
|
+
onMouseLeave: () => setHovered(false),
|
|
353
|
+
children: [
|
|
354
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-1.5 rounded-md border border-gray-200 dark:border-gray-600 bg-white/70 dark:bg-gray-900/40 px-2 py-1 pr-3", children: [
|
|
355
|
+
/* @__PURE__ */ jsx(
|
|
356
|
+
"span",
|
|
357
|
+
{
|
|
358
|
+
className: `text-xs truncate flex-1 ${isDefault ? "italic text-gray-400 dark:text-gray-500" : "text-gray-700 dark:text-gray-200"}`,
|
|
359
|
+
children: label
|
|
360
|
+
}
|
|
361
|
+
),
|
|
362
|
+
isSelfLoop && /* @__PURE__ */ jsx(
|
|
363
|
+
RotateCcw,
|
|
364
|
+
{
|
|
365
|
+
size: 12,
|
|
366
|
+
strokeWidth: 2.5,
|
|
367
|
+
"data-cv-tooltip": selfLoopLabel,
|
|
368
|
+
"aria-label": selfLoopLabel,
|
|
369
|
+
className: "shrink-0 text-gray-400 dark:text-gray-500"
|
|
370
|
+
}
|
|
371
|
+
),
|
|
372
|
+
/* @__PURE__ */ jsx(
|
|
373
|
+
Handle,
|
|
374
|
+
{
|
|
375
|
+
type: "source",
|
|
376
|
+
position: Position.Right,
|
|
377
|
+
id: handleId,
|
|
378
|
+
style: {
|
|
379
|
+
position: "absolute",
|
|
380
|
+
right: -(HANDLE_SIZE_PX / 2 + 1),
|
|
381
|
+
top: "50%",
|
|
382
|
+
transform: "translateY(-50%)",
|
|
383
|
+
width: HANDLE_SIZE_PX,
|
|
384
|
+
height: HANDLE_SIZE_PX
|
|
385
|
+
},
|
|
386
|
+
className: `!border-2 !border-white dark:!border-gray-800 ${isDefault ? "!bg-gray-400 dark:!bg-gray-500" : "!bg-purple-500"}`
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
] }),
|
|
390
|
+
onQuickAdd && /* @__PURE__ */ jsx(
|
|
391
|
+
"button",
|
|
392
|
+
{
|
|
393
|
+
type: "button",
|
|
394
|
+
"data-cv-tooltip": addLabel,
|
|
395
|
+
"aria-label": addLabel,
|
|
396
|
+
onClick: (event) => {
|
|
397
|
+
event.stopPropagation();
|
|
398
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
399
|
+
onQuickAdd({ handle: handleId, anchor: { x: rect.right, y: rect.top } });
|
|
400
|
+
},
|
|
401
|
+
style: { opacity: hovered ? 1 : 0.35 },
|
|
402
|
+
className: "nodrag absolute right-0 top-1/2 flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded-full border border-blue-300 bg-white text-blue-600 shadow-sm transition-opacity hover:bg-blue-50 dark:border-blue-700 dark:bg-gray-800 dark:text-blue-400 dark:hover:bg-gray-700",
|
|
403
|
+
children: /* @__PURE__ */ jsx(Plus, { size: 12, strokeWidth: 3 })
|
|
404
|
+
}
|
|
405
|
+
)
|
|
406
|
+
]
|
|
407
|
+
}
|
|
408
|
+
);
|
|
93
409
|
}
|
|
94
410
|
function FlowNodeCard({ data }) {
|
|
95
|
-
const { node, liveCount, isStart, isSelected, isDetached, issues, labels, actionKindIcons, onSelect } = data;
|
|
411
|
+
const { node, liveCount, isStart, isSelected, isDetached, issues, labels, actionKindIcons, onSelect, onQuickAdd } = data;
|
|
96
412
|
const label = nodeLabel(node, labels);
|
|
97
413
|
const iconMap = { ...DEFAULT_ACTION_KIND_ICON, ...actionKindIcons };
|
|
98
414
|
const Icon = node.type === "action" && node.actionKind ? iconMap[node.actionKind] ?? NODE_TYPE_ICON[node.type] : NODE_TYPE_ICON[node.type];
|
|
99
415
|
const rows = sourceRows(node, labels);
|
|
100
|
-
const
|
|
101
|
-
const
|
|
416
|
+
const nodeIssues = issues.filter((issue) => issue.nodeId === node.id);
|
|
417
|
+
const errors = nodeIssues.filter((issue) => issue.severity === "error");
|
|
418
|
+
const warnings = nodeIssues.filter((issue) => issue.severity === "warning");
|
|
419
|
+
const hasError = errors.length > 0;
|
|
420
|
+
const hasWarning = !hasError && warnings.length > 0;
|
|
421
|
+
const issueTooltip = (hasError ? errors : warnings).map((issue) => issue.message).join(" \xB7 ");
|
|
102
422
|
return /* @__PURE__ */ jsxs(
|
|
103
423
|
"div",
|
|
104
424
|
{
|
|
@@ -106,7 +426,16 @@ function FlowNodeCard({ data }) {
|
|
|
106
426
|
className: `relative rounded-lg border-2 px-3 py-2 w-60 cursor-pointer shadow-sm hover:shadow-md transition-shadow ${NODE_TYPE_COLOR[node.type]} ${isSelected ? "ring-2 ring-blue-500 ring-offset-2 dark:ring-offset-gray-900" : ""} ${isDetached ? "border-dashed !border-amber-400 animate-pulse" : ""}`,
|
|
107
427
|
onClick: () => onSelect(node.id),
|
|
108
428
|
children: [
|
|
109
|
-
/* @__PURE__ */ jsx(
|
|
429
|
+
/* @__PURE__ */ jsx(
|
|
430
|
+
Handle,
|
|
431
|
+
{
|
|
432
|
+
type: "target",
|
|
433
|
+
position: Position.Top,
|
|
434
|
+
id: "target",
|
|
435
|
+
style: { width: HANDLE_SIZE_PX, height: HANDLE_SIZE_PX },
|
|
436
|
+
className: "!bg-gray-400 !border-2 !border-white dark:!bg-gray-500 dark:!border-gray-800"
|
|
437
|
+
}
|
|
438
|
+
),
|
|
110
439
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
111
440
|
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 uppercase tracking-wide font-semibold text-gray-500 dark:text-gray-400 text-xs", children: [
|
|
112
441
|
/* @__PURE__ */ jsx(Icon, { size: 12, strokeWidth: 2.5 }),
|
|
@@ -114,9 +443,25 @@ function FlowNodeCard({ data }) {
|
|
|
114
443
|
labels.legend[node.type]
|
|
115
444
|
] }),
|
|
116
445
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
117
|
-
hasError && /* @__PURE__ */ jsx(
|
|
118
|
-
|
|
119
|
-
|
|
446
|
+
hasError && /* @__PURE__ */ jsx(
|
|
447
|
+
AlertCircle,
|
|
448
|
+
{
|
|
449
|
+
size: 13,
|
|
450
|
+
"data-cv-tooltip": issueTooltip,
|
|
451
|
+
"aria-label": issueTooltip,
|
|
452
|
+
className: "text-red-600 dark:text-red-400"
|
|
453
|
+
}
|
|
454
|
+
),
|
|
455
|
+
hasWarning && /* @__PURE__ */ jsx(
|
|
456
|
+
AlertTriangle,
|
|
457
|
+
{
|
|
458
|
+
size: 13,
|
|
459
|
+
"data-cv-tooltip": issueTooltip,
|
|
460
|
+
"aria-label": issueTooltip,
|
|
461
|
+
className: "text-amber-500 dark:text-amber-400"
|
|
462
|
+
}
|
|
463
|
+
),
|
|
464
|
+
liveCount > 0 && /* @__PURE__ */ jsx(
|
|
120
465
|
"span",
|
|
121
466
|
{
|
|
122
467
|
"data-cv-tooltip": labels.liveCountTooltip(liveCount),
|
|
@@ -127,7 +472,19 @@ function FlowNodeCard({ data }) {
|
|
|
127
472
|
] })
|
|
128
473
|
] }),
|
|
129
474
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-900 dark:text-gray-100 mt-1 line-clamp-3", children: label }),
|
|
130
|
-
rows.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-1", children: rows.map((row) => /* @__PURE__ */ jsx(
|
|
475
|
+
rows.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-1", children: rows.map((row) => /* @__PURE__ */ jsx(
|
|
476
|
+
SourceRow,
|
|
477
|
+
{
|
|
478
|
+
label: row.label,
|
|
479
|
+
isDefault: row.isDefault,
|
|
480
|
+
handleId: row.id,
|
|
481
|
+
addLabel: labels.quickAdd.fromHandle,
|
|
482
|
+
isSelfLoop: row.isSelfLoop,
|
|
483
|
+
selfLoopLabel: labels.legendPanel.selfLoop,
|
|
484
|
+
...onQuickAdd ? { onQuickAdd: (params) => onQuickAdd({ nodeId: node.id, ...params }) } : {}
|
|
485
|
+
},
|
|
486
|
+
row.id
|
|
487
|
+
)) })
|
|
131
488
|
]
|
|
132
489
|
}
|
|
133
490
|
);
|
|
@@ -219,6 +576,24 @@ var DEFAULT_FLOW_EDITOR_LABELS = {
|
|
|
219
576
|
media: "Arquivos enviados neste ponto",
|
|
220
577
|
mediaUnavailable: "A biblioteca de arquivos n\xE3o est\xE1 dispon\xEDvel neste painel."
|
|
221
578
|
},
|
|
579
|
+
quickAdd: {
|
|
580
|
+
fromHandle: "Criar o pr\xF3ximo n\xF3 j\xE1 ligado aqui",
|
|
581
|
+
title: "Ligar em um n\xF3 novo",
|
|
582
|
+
disconnect: "Desligar este fio (o n\xF3 continua no fluxo)"
|
|
583
|
+
},
|
|
584
|
+
legendPanel: {
|
|
585
|
+
title: "Legenda",
|
|
586
|
+
nodes: "Cards",
|
|
587
|
+
connections: "Liga\xE7\xF5es",
|
|
588
|
+
linear: "Segue direto para o pr\xF3ximo",
|
|
589
|
+
branch: "Caminho de uma op\xE7\xE3o escolhida",
|
|
590
|
+
fallback: "Quando a resposta n\xE3o casa com nenhuma op\xE7\xE3o",
|
|
591
|
+
crossFlow: "Salta para outro fluxo",
|
|
592
|
+
live: "Tem conversa passando por aqui agora",
|
|
593
|
+
selfLoop: "Volta ao mesmo card \u2014 repete a pergunta",
|
|
594
|
+
detached: "Ningu\xE9m aponta para este card: o bot n\xE3o chega nele",
|
|
595
|
+
startNode: "Onde o fluxo come\xE7a"
|
|
596
|
+
},
|
|
222
597
|
palette: {
|
|
223
598
|
title: "Adicionar ao fluxo",
|
|
224
599
|
question: "Pergunta",
|
|
@@ -290,321 +665,73 @@ var DEFAULT_FLOW_EDITOR_LABELS = {
|
|
|
290
665
|
create: "Criar fluxo",
|
|
291
666
|
creating: "Criando\u2026",
|
|
292
667
|
deleteFlow: "Excluir fluxo",
|
|
293
|
-
deleteConfirm: (label) => `Excluir o fluxo "${label}"? Esta a\xE7\xE3o n\xE3o pode ser desfeita.`,
|
|
294
|
-
createError: "N\xE3o foi poss\xEDvel criar o fluxo.",
|
|
295
|
-
deleteError: "N\xE3o foi poss\xEDvel excluir o fluxo."
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
function mergeFlowEditorLabels(override) {
|
|
299
|
-
if (!override) return DEFAULT_FLOW_EDITOR_LABELS;
|
|
300
|
-
return {
|
|
301
|
-
...DEFAULT_FLOW_EDITOR_LABELS,
|
|
302
|
-
...override,
|
|
303
|
-
legend: { ...DEFAULT_FLOW_EDITOR_LABELS.legend, ...override.legend },
|
|
304
|
-
actionKindLabels: { ...DEFAULT_FLOW_EDITOR_LABELS.actionKindLabels, ...override.actionKindLabels },
|
|
305
|
-
conditionOperatorLabels: {
|
|
306
|
-
...DEFAULT_FLOW_EDITOR_LABELS.conditionOperatorLabels,
|
|
307
|
-
...override.conditionOperatorLabels
|
|
308
|
-
},
|
|
309
|
-
questionTypeLabels: { ...DEFAULT_FLOW_EDITOR_LABELS.questionTypeLabels, ...override.questionTypeLabels },
|
|
310
|
-
nodePanel: { ...DEFAULT_FLOW_EDITOR_LABELS.nodePanel, ...override.nodePanel },
|
|
311
|
-
palette: { ...DEFAULT_FLOW_EDITOR_LABELS.palette, ...override.palette },
|
|
312
|
-
flowMap: { ...DEFAULT_FLOW_EDITOR_LABELS.flowMap, ...override.flowMap },
|
|
313
|
-
flowGroup: { ...DEFAULT_FLOW_EDITOR_LABELS.flowGroup, ...override.flowGroup },
|
|
314
|
-
crossFlowPortal: { ...DEFAULT_FLOW_EDITOR_LABELS.crossFlowPortal, ...override.crossFlowPortal },
|
|
315
|
-
collectionChain: { ...DEFAULT_FLOW_EDITOR_LABELS.collectionChain, ...override.collectionChain },
|
|
316
|
-
validation: { ...DEFAULT_FLOW_EDITOR_LABELS.validation, ...override.validation },
|
|
317
|
-
workspace: { ...DEFAULT_FLOW_EDITOR_LABELS.workspace, ...override.workspace },
|
|
318
|
-
flowManager: { ...DEFAULT_FLOW_EDITOR_LABELS.flowManager, ...override.flowManager }
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// src/flows/FlowMapNode.tsx
|
|
323
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
324
|
-
function FlowMapNode({ data }) {
|
|
325
|
-
const { label, nodeCount, isRoot, labels = DEFAULT_FLOW_EDITOR_LABELS, onOpen } = data;
|
|
326
|
-
return /* @__PURE__ */ jsxs2("div", { className: "relative rounded-xl border-2 border-emerald-300 dark:border-emerald-700 bg-emerald-50 dark:bg-emerald-950/40 px-4 py-3 w-56 shadow-sm", children: [
|
|
327
|
-
/* @__PURE__ */ jsx2(Handle2, { type: "target", position: Position2.Top, className: "!bg-gray-400 dark:!bg-gray-500" }),
|
|
328
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5 text-emerald-700 dark:text-emerald-300", children: [
|
|
329
|
-
/* @__PURE__ */ jsx2(GitBranch2, { size: 14 }),
|
|
330
|
-
/* @__PURE__ */ jsx2("span", { className: "text-sm font-semibold truncate", children: label }),
|
|
331
|
-
isRoot && /* @__PURE__ */ jsx2("span", { className: "h-1.5 w-1.5 rounded-full bg-emerald-500 shrink-0", "data-cv-tooltip": labels.startNodeTooltip })
|
|
332
|
-
] }),
|
|
333
|
-
/* @__PURE__ */ jsx2("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.flowMap.nodeCount(nodeCount) }),
|
|
334
|
-
/* @__PURE__ */ jsxs2(
|
|
335
|
-
"button",
|
|
336
|
-
{
|
|
337
|
-
"data-cv-tooltip": labels.flowMap.openFlow,
|
|
338
|
-
"aria-label": labels.flowMap.openFlow,
|
|
339
|
-
type: "button",
|
|
340
|
-
onClick: onOpen,
|
|
341
|
-
className: "mt-2 inline-flex items-center gap-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:underline",
|
|
342
|
-
children: [
|
|
343
|
-
/* @__PURE__ */ jsx2(Maximize2, { size: 11 }),
|
|
344
|
-
" ",
|
|
345
|
-
labels.flowMap.openFlow
|
|
346
|
-
]
|
|
347
|
-
}
|
|
348
|
-
),
|
|
349
|
-
/* @__PURE__ */ jsx2(Handle2, { type: "source", position: Position2.Bottom, className: "!bg-gray-400 dark:!bg-gray-500" })
|
|
350
|
-
] });
|
|
351
|
-
}
|
|
352
|
-
var flowMapNodeTypes = { flowMapNode: FlowMapNode };
|
|
353
|
-
|
|
354
|
-
// src/flows/FlowMapCanvas.tsx
|
|
355
|
-
import { useMemo } from "react";
|
|
356
|
-
import { ReactFlow, Background, Controls, MarkerType } from "@xyflow/react";
|
|
357
|
-
import "@xyflow/react/dist/style.css";
|
|
358
|
-
|
|
359
|
-
// src/flows/flowGraph.ts
|
|
360
|
-
import { FLOW_ACTION_KIND } from "@adatechnology/meta-whatsapp-contracts";
|
|
361
|
-
var CONDITION_OPERATORS = [">", ">=", "<", "<=", "==", "!=", "contains"];
|
|
362
|
-
var BUILT_IN_ACTION_KINDS = FLOW_ACTION_KIND;
|
|
363
|
-
var CROSS_FLOW_PREFIX = "flow:";
|
|
364
|
-
var isCrossFlowTarget = (target) => target.startsWith(CROSS_FLOW_PREFIX);
|
|
365
|
-
var crossFlowKey = (target) => target.slice(CROSS_FLOW_PREFIX.length);
|
|
366
|
-
var NODE_CARD_WIDTH = 240;
|
|
367
|
-
function estimateNodeHeight(node) {
|
|
368
|
-
const HEADER_HEIGHT = 28;
|
|
369
|
-
const BODY_HEIGHT = 56;
|
|
370
|
-
const PADDING = 16;
|
|
371
|
-
const ROW_HEIGHT = 34;
|
|
372
|
-
const rowCount = node.type === "action" ? 0 : node.type === "condition" ? 2 : node.type === "menu" || node.questionType === "choice" ? (node.options?.length ?? 0) + 1 : 1;
|
|
373
|
-
return HEADER_HEIGHT + BODY_HEIGHT + PADDING + rowCount * ROW_HEIGHT;
|
|
374
|
-
}
|
|
375
|
-
var WHATSAPP_LIMITS = {
|
|
376
|
-
MAX_BUTTONS: 3,
|
|
377
|
-
MAX_LIST_ROWS: 10,
|
|
378
|
-
BUTTON_TITLE_MAX: 20,
|
|
379
|
-
LIST_ROW_TITLE_MAX: 24,
|
|
380
|
-
BODY_MAX: 1024
|
|
381
|
-
};
|
|
382
|
-
function rendersAsButtons(options) {
|
|
383
|
-
return (options?.length ?? 0) <= WHATSAPP_LIMITS.MAX_BUTTONS;
|
|
384
|
-
}
|
|
385
|
-
function targetsOf(node) {
|
|
386
|
-
if (!node.next) return [];
|
|
387
|
-
if (typeof node.next === "string") return [{ target: node.next }];
|
|
388
|
-
return [
|
|
389
|
-
...Object.entries(node.next.byAnswer).map(([optionId, target]) => ({ target, optionId })),
|
|
390
|
-
{ target: node.next.default, isDefault: true }
|
|
391
|
-
];
|
|
392
|
-
}
|
|
393
|
-
function validateGraph(graph, issueText) {
|
|
394
|
-
const issues = [];
|
|
395
|
-
const nodeIds = new Set(Object.keys(graph.nodes));
|
|
396
|
-
const isValidTarget = (target) => nodeIds.has(target) || isCrossFlowTarget(target);
|
|
397
|
-
if (!nodeIds.has(graph.startNodeId)) {
|
|
398
|
-
issues.push({ severity: "error", message: issueText.noStart });
|
|
399
|
-
}
|
|
400
|
-
for (const node of Object.values(graph.nodes)) {
|
|
401
|
-
for (const { target } of targetsOf(node)) {
|
|
402
|
-
if (!isValidTarget(target)) {
|
|
403
|
-
issues.push({ severity: "error", nodeId: node.id, message: issueText.brokenRef(node.id, target) });
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
const isChoice = node.questionType === "choice" || node.type === "menu";
|
|
407
|
-
if (isChoice) {
|
|
408
|
-
const options = node.options ?? [];
|
|
409
|
-
if (options.length === 0) {
|
|
410
|
-
issues.push({ severity: "error", nodeId: node.id, message: issueText.choiceWithoutOptions(node.id) });
|
|
411
|
-
}
|
|
412
|
-
const seen = /* @__PURE__ */ new Set();
|
|
413
|
-
for (const [optionId, label] of options) {
|
|
414
|
-
if (seen.has(optionId)) {
|
|
415
|
-
issues.push({ severity: "error", nodeId: node.id, message: issueText.duplicatedOptionId(node.id, optionId) });
|
|
416
|
-
}
|
|
417
|
-
seen.add(optionId);
|
|
418
|
-
const byAnswer = typeof node.next === "object" && node.next ? node.next.byAnswer : {};
|
|
419
|
-
if (!byAnswer[optionId]) {
|
|
420
|
-
issues.push({ severity: "warning", nodeId: node.id, message: issueText.optionWithoutTarget(node.id, label) });
|
|
421
|
-
}
|
|
422
|
-
const usesButtons = rendersAsButtons(options);
|
|
423
|
-
if (usesButtons && label.length > WHATSAPP_LIMITS.BUTTON_TITLE_MAX) {
|
|
424
|
-
issues.push({ severity: "error", nodeId: node.id, message: issueText.buttonTitleTooLong(node.id, label) });
|
|
425
|
-
}
|
|
426
|
-
if (!usesButtons && label.length > WHATSAPP_LIMITS.LIST_ROW_TITLE_MAX) {
|
|
427
|
-
issues.push({ severity: "error", nodeId: node.id, message: issueText.listTitleTooLong(node.id, label) });
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
if (options.length > WHATSAPP_LIMITS.MAX_LIST_ROWS) {
|
|
431
|
-
issues.push({ severity: "error", nodeId: node.id, message: issueText.tooManyOptions(node.id, options.length) });
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
const bodyText = node.question ?? node.directMessage ?? "";
|
|
435
|
-
if (bodyText.length > WHATSAPP_LIMITS.BODY_MAX) {
|
|
436
|
-
issues.push({ severity: "error", nodeId: node.id, message: issueText.bodyTooLong(node.id) });
|
|
437
|
-
}
|
|
438
|
-
if (node.type === "question" && !node.next) {
|
|
439
|
-
issues.push({ severity: "warning", nodeId: node.id, message: issueText.deadEndQuestion(node.id) });
|
|
440
|
-
}
|
|
441
|
-
if (node.type === "condition") {
|
|
442
|
-
if (!node.conditionContextKey || !node.conditionOperator || !node.conditionValue) {
|
|
443
|
-
issues.push({ severity: "error", nodeId: node.id, message: issueText.conditionIncomplete(node.id) });
|
|
444
|
-
}
|
|
445
|
-
const byAnswer = typeof node.next === "object" && node.next ? node.next.byAnswer : {};
|
|
446
|
-
if (!byAnswer.true)
|
|
447
|
-
issues.push({
|
|
448
|
-
severity: "warning",
|
|
449
|
-
nodeId: node.id,
|
|
450
|
-
message: issueText.conditionBranchMissing(node.id, "true")
|
|
451
|
-
});
|
|
452
|
-
if (!byAnswer.false)
|
|
453
|
-
issues.push({
|
|
454
|
-
severity: "warning",
|
|
455
|
-
nodeId: node.id,
|
|
456
|
-
message: issueText.conditionBranchMissing(node.id, "false")
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
for (const id of findUnreachable(graph)) {
|
|
461
|
-
issues.push({ severity: "warning", nodeId: id, message: issueText.unreachable(id) });
|
|
462
|
-
}
|
|
463
|
-
return issues;
|
|
464
|
-
}
|
|
465
|
-
function findUnreachable(graph) {
|
|
466
|
-
const reachable = /* @__PURE__ */ new Set();
|
|
467
|
-
const queue = [graph.startNodeId];
|
|
468
|
-
while (queue.length > 0) {
|
|
469
|
-
const id = queue.shift();
|
|
470
|
-
if (reachable.has(id) || !graph.nodes[id]) continue;
|
|
471
|
-
reachable.add(id);
|
|
472
|
-
for (const { target } of targetsOf(graph.nodes[id])) {
|
|
473
|
-
if (!isCrossFlowTarget(target)) queue.push(target);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
return Object.keys(graph.nodes).filter((id) => !reachable.has(id));
|
|
477
|
-
}
|
|
478
|
-
function computeAutoLayout(graph) {
|
|
479
|
-
const H_GAP = 300;
|
|
480
|
-
const V_GAP = 90;
|
|
481
|
-
const rank = {};
|
|
482
|
-
const queue = [graph.startNodeId];
|
|
483
|
-
rank[graph.startNodeId] = 0;
|
|
484
|
-
while (queue.length > 0) {
|
|
485
|
-
const id = queue.shift();
|
|
486
|
-
const node = graph.nodes[id];
|
|
487
|
-
if (!node) continue;
|
|
488
|
-
for (const { target } of targetsOf(node)) {
|
|
489
|
-
if (isCrossFlowTarget(target) || !graph.nodes[target]) continue;
|
|
490
|
-
if (rank[target] === void 0) {
|
|
491
|
-
rank[target] = rank[id] + 1;
|
|
492
|
-
queue.push(target);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
const maxRank = Math.max(0, ...Object.values(rank));
|
|
497
|
-
let strayRank = maxRank + 1;
|
|
498
|
-
for (const id of Object.keys(graph.nodes)) {
|
|
499
|
-
if (rank[id] === void 0) rank[id] = strayRank++;
|
|
500
|
-
}
|
|
501
|
-
const layers = {};
|
|
502
|
-
for (const [id, r] of Object.entries(rank)) {
|
|
503
|
-
layers[r] = [...layers[r] ?? [], id];
|
|
504
|
-
}
|
|
505
|
-
const positions = {};
|
|
506
|
-
const sortedRanks = Object.keys(layers).map(Number).sort((a, b) => a - b);
|
|
507
|
-
let cumulativeY = 0;
|
|
508
|
-
for (const r of sortedRanks) {
|
|
509
|
-
const ids = layers[r];
|
|
510
|
-
const width = (ids.length - 1) * H_GAP;
|
|
511
|
-
let maxHeight = 0;
|
|
512
|
-
ids.forEach((id, index) => {
|
|
513
|
-
maxHeight = Math.max(maxHeight, estimateNodeHeight(graph.nodes[id]));
|
|
514
|
-
positions[id] = { x: index * H_GAP - width / 2, y: cumulativeY };
|
|
515
|
-
});
|
|
516
|
-
cumulativeY += maxHeight + V_GAP;
|
|
517
|
-
}
|
|
518
|
-
return positions;
|
|
519
|
-
}
|
|
520
|
-
function crossFlowTargetsOf(graph) {
|
|
521
|
-
const keys = /* @__PURE__ */ new Set();
|
|
522
|
-
for (const node of Object.values(graph.nodes)) {
|
|
523
|
-
for (const { target } of targetsOf(node)) {
|
|
524
|
-
if (isCrossFlowTarget(target)) keys.add(crossFlowKey(target));
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
return [...keys];
|
|
528
|
-
}
|
|
529
|
-
function computeFlowMapLayout(graphs, rootKey) {
|
|
530
|
-
const H_GAP = 280;
|
|
531
|
-
const V_GAP = 170;
|
|
532
|
-
const rank = {};
|
|
533
|
-
const queue = graphs[rootKey] ? [rootKey] : Object.keys(graphs);
|
|
534
|
-
if (graphs[rootKey]) rank[rootKey] = 0;
|
|
535
|
-
while (queue.length > 0) {
|
|
536
|
-
const key = queue.shift();
|
|
537
|
-
const g = graphs[key];
|
|
538
|
-
if (!g) continue;
|
|
539
|
-
for (const target of crossFlowTargetsOf(g)) {
|
|
540
|
-
if (!graphs[target]) continue;
|
|
541
|
-
if (rank[target] === void 0) {
|
|
542
|
-
rank[target] = rank[key] + 1;
|
|
543
|
-
queue.push(target);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
const maxRank = Math.max(0, ...Object.values(rank));
|
|
548
|
-
let strayRank = maxRank + 1;
|
|
549
|
-
for (const key of Object.keys(graphs)) {
|
|
550
|
-
if (rank[key] === void 0) rank[key] = strayRank++;
|
|
551
|
-
}
|
|
552
|
-
const layers = {};
|
|
553
|
-
for (const [key, r] of Object.entries(rank)) {
|
|
554
|
-
layers[r] = [...layers[r] ?? [], key];
|
|
555
|
-
}
|
|
556
|
-
const positions = {};
|
|
557
|
-
for (const [r, keys] of Object.entries(layers)) {
|
|
558
|
-
const width = (keys.length - 1) * H_GAP;
|
|
559
|
-
keys.forEach((key, index) => {
|
|
560
|
-
positions[key] = { x: index * H_GAP - width / 2, y: Number(r) * V_GAP };
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
return positions;
|
|
564
|
-
}
|
|
565
|
-
function findCollectionChains(graph) {
|
|
566
|
-
const actionIds = new Set(
|
|
567
|
-
Object.values(graph.nodes).filter((n) => n.type === "action").map((n) => n.id)
|
|
568
|
-
);
|
|
569
|
-
const memo = /* @__PURE__ */ new Map();
|
|
570
|
-
function reachableActions(id, stack) {
|
|
571
|
-
if (memo.has(id)) return memo.get(id);
|
|
572
|
-
if (stack.has(id)) return /* @__PURE__ */ new Set();
|
|
573
|
-
if (actionIds.has(id)) return /* @__PURE__ */ new Set([id]);
|
|
574
|
-
const node = graph.nodes[id];
|
|
575
|
-
if (!node) return /* @__PURE__ */ new Set();
|
|
576
|
-
const nextStack = new Set(stack);
|
|
577
|
-
nextStack.add(id);
|
|
578
|
-
const result = /* @__PURE__ */ new Set();
|
|
579
|
-
for (const { target } of targetsOf(node)) {
|
|
580
|
-
if (isCrossFlowTarget(target) || !graph.nodes[target]) continue;
|
|
581
|
-
for (const actionId of reachableActions(target, nextStack)) result.add(actionId);
|
|
582
|
-
}
|
|
583
|
-
memo.set(id, result);
|
|
584
|
-
return result;
|
|
585
|
-
}
|
|
586
|
-
const nodeIdsByAction = /* @__PURE__ */ new Map();
|
|
587
|
-
for (const node of Object.values(graph.nodes)) {
|
|
588
|
-
if (node.type !== "question") continue;
|
|
589
|
-
const reached = reachableActions(node.id, /* @__PURE__ */ new Set());
|
|
590
|
-
if (reached.size !== 1) continue;
|
|
591
|
-
const [actionNodeId] = [...reached];
|
|
592
|
-
nodeIdsByAction.set(actionNodeId, [...nodeIdsByAction.get(actionNodeId) ?? [], node.id]);
|
|
668
|
+
deleteConfirm: (label) => `Excluir o fluxo "${label}"? Esta a\xE7\xE3o n\xE3o pode ser desfeita.`,
|
|
669
|
+
createError: "N\xE3o foi poss\xEDvel criar o fluxo.",
|
|
670
|
+
deleteError: "N\xE3o foi poss\xEDvel excluir o fluxo."
|
|
593
671
|
}
|
|
594
|
-
|
|
672
|
+
};
|
|
673
|
+
function mergeFlowEditorLabels(override) {
|
|
674
|
+
if (!override) return DEFAULT_FLOW_EDITOR_LABELS;
|
|
675
|
+
return {
|
|
676
|
+
...DEFAULT_FLOW_EDITOR_LABELS,
|
|
677
|
+
...override,
|
|
678
|
+
legend: { ...DEFAULT_FLOW_EDITOR_LABELS.legend, ...override.legend },
|
|
679
|
+
actionKindLabels: { ...DEFAULT_FLOW_EDITOR_LABELS.actionKindLabels, ...override.actionKindLabels },
|
|
680
|
+
conditionOperatorLabels: {
|
|
681
|
+
...DEFAULT_FLOW_EDITOR_LABELS.conditionOperatorLabels,
|
|
682
|
+
...override.conditionOperatorLabels
|
|
683
|
+
},
|
|
684
|
+
questionTypeLabels: { ...DEFAULT_FLOW_EDITOR_LABELS.questionTypeLabels, ...override.questionTypeLabels },
|
|
685
|
+
nodePanel: { ...DEFAULT_FLOW_EDITOR_LABELS.nodePanel, ...override.nodePanel },
|
|
686
|
+
quickAdd: { ...DEFAULT_FLOW_EDITOR_LABELS.quickAdd, ...override.quickAdd },
|
|
687
|
+
legendPanel: { ...DEFAULT_FLOW_EDITOR_LABELS.legendPanel, ...override.legendPanel },
|
|
688
|
+
palette: { ...DEFAULT_FLOW_EDITOR_LABELS.palette, ...override.palette },
|
|
689
|
+
flowMap: { ...DEFAULT_FLOW_EDITOR_LABELS.flowMap, ...override.flowMap },
|
|
690
|
+
flowGroup: { ...DEFAULT_FLOW_EDITOR_LABELS.flowGroup, ...override.flowGroup },
|
|
691
|
+
crossFlowPortal: { ...DEFAULT_FLOW_EDITOR_LABELS.crossFlowPortal, ...override.crossFlowPortal },
|
|
692
|
+
collectionChain: { ...DEFAULT_FLOW_EDITOR_LABELS.collectionChain, ...override.collectionChain },
|
|
693
|
+
validation: { ...DEFAULT_FLOW_EDITOR_LABELS.validation, ...override.validation },
|
|
694
|
+
workspace: { ...DEFAULT_FLOW_EDITOR_LABELS.workspace, ...override.workspace },
|
|
695
|
+
flowManager: { ...DEFAULT_FLOW_EDITOR_LABELS.flowManager, ...override.flowManager }
|
|
696
|
+
};
|
|
595
697
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
698
|
+
|
|
699
|
+
// src/flows/FlowMapNode.tsx
|
|
700
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
701
|
+
function FlowMapNode({ data }) {
|
|
702
|
+
const { label, nodeCount, isRoot, labels = DEFAULT_FLOW_EDITOR_LABELS, onOpen } = data;
|
|
703
|
+
return /* @__PURE__ */ jsxs2("div", { className: "relative rounded-xl border-2 border-emerald-300 dark:border-emerald-700 bg-emerald-50 dark:bg-emerald-950/40 px-4 py-3 w-56 shadow-sm", children: [
|
|
704
|
+
/* @__PURE__ */ jsx2(Handle2, { type: "target", position: Position2.Top, className: "!bg-gray-400 dark:!bg-gray-500" }),
|
|
705
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5 text-emerald-700 dark:text-emerald-300", children: [
|
|
706
|
+
/* @__PURE__ */ jsx2(GitBranch2, { size: 14 }),
|
|
707
|
+
/* @__PURE__ */ jsx2("span", { className: "text-sm font-semibold truncate", children: label }),
|
|
708
|
+
isRoot && /* @__PURE__ */ jsx2("span", { className: "h-1.5 w-1.5 rounded-full bg-emerald-500 shrink-0", "data-cv-tooltip": labels.startNodeTooltip })
|
|
709
|
+
] }),
|
|
710
|
+
/* @__PURE__ */ jsx2("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.flowMap.nodeCount(nodeCount) }),
|
|
711
|
+
/* @__PURE__ */ jsxs2(
|
|
712
|
+
"button",
|
|
713
|
+
{
|
|
714
|
+
"data-cv-tooltip": labels.flowMap.openFlow,
|
|
715
|
+
"aria-label": labels.flowMap.openFlow,
|
|
716
|
+
type: "button",
|
|
717
|
+
onClick: onOpen,
|
|
718
|
+
className: "mt-2 inline-flex items-center gap-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:underline",
|
|
719
|
+
children: [
|
|
720
|
+
/* @__PURE__ */ jsx2(Maximize2, { size: 11 }),
|
|
721
|
+
" ",
|
|
722
|
+
labels.flowMap.openFlow
|
|
723
|
+
]
|
|
724
|
+
}
|
|
725
|
+
),
|
|
726
|
+
/* @__PURE__ */ jsx2(Handle2, { type: "source", position: Position2.Bottom, className: "!bg-gray-400 dark:!bg-gray-500" })
|
|
727
|
+
] });
|
|
605
728
|
}
|
|
729
|
+
var flowMapNodeTypes = { flowMapNode: FlowMapNode };
|
|
606
730
|
|
|
607
731
|
// src/flows/FlowMapCanvas.tsx
|
|
732
|
+
import { useMemo } from "react";
|
|
733
|
+
import { ReactFlow, Background, Controls, MarkerType } from "@xyflow/react";
|
|
734
|
+
import "@xyflow/react/dist/style.css";
|
|
608
735
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
609
736
|
var MAP_NODE_TYPES = { ...flowMapNodeTypes };
|
|
610
737
|
var EDGE_COLOR = "#06b6d4";
|
|
@@ -731,23 +858,122 @@ function FlowPortalNode({ data }) {
|
|
|
731
858
|
var flowPortalNodeTypes = { flowPortal: FlowPortalNode };
|
|
732
859
|
|
|
733
860
|
// src/flows/FlowPalette.tsx
|
|
734
|
-
import { useEffect, useRef, useState } from "react";
|
|
735
|
-
import { Plus, MessageCircleQuestion as MessageCircleQuestion2, GitBranch as GitBranch3, Zap as Zap2, Diamond as Diamond2, ChevronRight } from "lucide-react";
|
|
736
|
-
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
861
|
+
import { useEffect, useRef, useState as useState2 } from "react";
|
|
862
|
+
import { Plus as Plus2, MessageCircleQuestion as MessageCircleQuestion2, GitBranch as GitBranch3, Zap as Zap2, Diamond as Diamond2, ChevronRight } from "lucide-react";
|
|
863
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
737
864
|
var QUESTION_TYPES = ["text", "money", "date", "int", "cpf"];
|
|
738
|
-
function
|
|
739
|
-
const labels = { ...DEFAULT_FLOW_EDITOR_LABELS, ...labelsOverride };
|
|
865
|
+
function FlowPaletteMenu({ onSelect, labels, actionOptions }) {
|
|
740
866
|
const resolvedActionOptions = actionOptions ?? [
|
|
741
867
|
{ actionKind: "handoff", label: labels.actionKindLabels.handoff ?? "Encaminhar para atendimento" }
|
|
742
868
|
];
|
|
743
|
-
const [
|
|
744
|
-
|
|
869
|
+
const [submenu, setSubmenu] = useState2(null);
|
|
870
|
+
function select(spec) {
|
|
871
|
+
onSelect(spec);
|
|
872
|
+
setSubmenu(null);
|
|
873
|
+
}
|
|
874
|
+
return /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
875
|
+
/* @__PURE__ */ jsxs6("div", { className: "relative", children: [
|
|
876
|
+
/* @__PURE__ */ jsxs6(
|
|
877
|
+
"button",
|
|
878
|
+
{
|
|
879
|
+
"data-cv-tooltip": labels.palette.question,
|
|
880
|
+
"aria-label": labels.palette.question,
|
|
881
|
+
onMouseEnter: () => setSubmenu("question"),
|
|
882
|
+
onClick: () => setSubmenu(submenu === "question" ? null : "question"),
|
|
883
|
+
className: "w-full flex items-center justify-between gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700",
|
|
884
|
+
children: [
|
|
885
|
+
/* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-2", children: [
|
|
886
|
+
/* @__PURE__ */ jsx7(MessageCircleQuestion2, { size: 15, className: "text-blue-500" }),
|
|
887
|
+
" ",
|
|
888
|
+
labels.palette.question
|
|
889
|
+
] }),
|
|
890
|
+
/* @__PURE__ */ jsx7(ChevronRight, { size: 13, className: "text-gray-400" })
|
|
891
|
+
]
|
|
892
|
+
}
|
|
893
|
+
),
|
|
894
|
+
submenu === "question" && /* @__PURE__ */ jsx7("div", { className: "absolute left-full top-0 ml-1 w-56 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-lg py-1", children: QUESTION_TYPES.map((qt) => /* @__PURE__ */ jsx7(
|
|
895
|
+
"button",
|
|
896
|
+
{
|
|
897
|
+
"data-cv-tooltip": labels.questionTypeLabels[qt],
|
|
898
|
+
"aria-label": labels.questionTypeLabels[qt],
|
|
899
|
+
onClick: () => select({ kind: "question", questionType: qt }),
|
|
900
|
+
className: "w-full text-left px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700",
|
|
901
|
+
children: labels.questionTypeLabels[qt]
|
|
902
|
+
},
|
|
903
|
+
qt
|
|
904
|
+
)) })
|
|
905
|
+
] }),
|
|
906
|
+
/* @__PURE__ */ jsxs6(
|
|
907
|
+
"button",
|
|
908
|
+
{
|
|
909
|
+
"data-cv-tooltip": labels.palette.decision,
|
|
910
|
+
"aria-label": labels.palette.decision,
|
|
911
|
+
onMouseEnter: () => setSubmenu(null),
|
|
912
|
+
onClick: () => select({ kind: "decision" }),
|
|
913
|
+
className: "w-full flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700",
|
|
914
|
+
children: [
|
|
915
|
+
/* @__PURE__ */ jsx7(GitBranch3, { size: 15, className: "text-purple-500" }),
|
|
916
|
+
" ",
|
|
917
|
+
labels.palette.decision
|
|
918
|
+
]
|
|
919
|
+
}
|
|
920
|
+
),
|
|
921
|
+
/* @__PURE__ */ jsxs6(
|
|
922
|
+
"button",
|
|
923
|
+
{
|
|
924
|
+
onMouseEnter: () => setSubmenu(null),
|
|
925
|
+
onClick: () => select({ kind: "condition" }),
|
|
926
|
+
className: "w-full flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700",
|
|
927
|
+
"data-cv-tooltip": labels.palette.conditionHint,
|
|
928
|
+
"aria-label": labels.palette.conditionHint,
|
|
929
|
+
children: [
|
|
930
|
+
/* @__PURE__ */ jsx7(Diamond2, { size: 15, className: "text-cyan-500" }),
|
|
931
|
+
" ",
|
|
932
|
+
labels.palette.condition
|
|
933
|
+
]
|
|
934
|
+
}
|
|
935
|
+
),
|
|
936
|
+
/* @__PURE__ */ jsxs6("div", { className: "relative", children: [
|
|
937
|
+
/* @__PURE__ */ jsxs6(
|
|
938
|
+
"button",
|
|
939
|
+
{
|
|
940
|
+
"data-cv-tooltip": labels.palette.action,
|
|
941
|
+
"aria-label": labels.palette.action,
|
|
942
|
+
onMouseEnter: () => setSubmenu("action"),
|
|
943
|
+
onClick: () => setSubmenu(submenu === "action" ? null : "action"),
|
|
944
|
+
className: "w-full flex items-center justify-between gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700",
|
|
945
|
+
children: [
|
|
946
|
+
/* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-2", children: [
|
|
947
|
+
/* @__PURE__ */ jsx7(Zap2, { size: 15, className: "text-orange-500" }),
|
|
948
|
+
" ",
|
|
949
|
+
labels.palette.action
|
|
950
|
+
] }),
|
|
951
|
+
/* @__PURE__ */ jsx7(ChevronRight, { size: 13, className: "text-gray-400" })
|
|
952
|
+
]
|
|
953
|
+
}
|
|
954
|
+
),
|
|
955
|
+
submenu === "action" && /* @__PURE__ */ jsx7("div", { className: "absolute left-full top-0 ml-1 w-64 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-lg py-1", children: resolvedActionOptions.map((option) => /* @__PURE__ */ jsx7(
|
|
956
|
+
"button",
|
|
957
|
+
{
|
|
958
|
+
"data-cv-tooltip": option.label,
|
|
959
|
+
"aria-label": option.label,
|
|
960
|
+
onClick: () => select({ kind: "action", actionKind: option.actionKind }),
|
|
961
|
+
className: "w-full text-left px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700",
|
|
962
|
+
children: option.label
|
|
963
|
+
},
|
|
964
|
+
option.actionKind
|
|
965
|
+
)) })
|
|
966
|
+
] })
|
|
967
|
+
] });
|
|
968
|
+
}
|
|
969
|
+
function FlowPalette({ onAdd, labels: labelsOverride, actionOptions }) {
|
|
970
|
+
const labels = { ...DEFAULT_FLOW_EDITOR_LABELS, ...labelsOverride };
|
|
971
|
+
const [open, setOpen] = useState2(false);
|
|
745
972
|
const containerRef = useRef(null);
|
|
746
973
|
useEffect(() => {
|
|
747
974
|
function handleClickOutside(event) {
|
|
748
975
|
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
749
976
|
setOpen(false);
|
|
750
|
-
setSubmenu(null);
|
|
751
977
|
}
|
|
752
978
|
}
|
|
753
979
|
document.addEventListener("mousedown", handleClickOutside);
|
|
@@ -756,7 +982,6 @@ function FlowPalette({ onAdd, labels: labelsOverride, actionOptions }) {
|
|
|
756
982
|
function select(spec) {
|
|
757
983
|
onAdd(spec);
|
|
758
984
|
setOpen(false);
|
|
759
|
-
setSubmenu(null);
|
|
760
985
|
}
|
|
761
986
|
return /* @__PURE__ */ jsxs6("div", { ref: containerRef, className: "relative", children: [
|
|
762
987
|
/* @__PURE__ */ jsxs6(
|
|
@@ -767,155 +992,214 @@ function FlowPalette({ onAdd, labels: labelsOverride, actionOptions }) {
|
|
|
767
992
|
onClick: () => setOpen((v) => !v),
|
|
768
993
|
className: "inline-flex items-center gap-1.5 rounded-lg bg-blue-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-blue-700",
|
|
769
994
|
children: [
|
|
770
|
-
/* @__PURE__ */ jsx7(
|
|
995
|
+
/* @__PURE__ */ jsx7(Plus2, { size: 14 }),
|
|
771
996
|
" ",
|
|
772
997
|
labels.palette.title
|
|
773
998
|
]
|
|
774
999
|
}
|
|
775
1000
|
),
|
|
776
|
-
open && /* @__PURE__ */
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
/* @__PURE__ */ jsx7(ChevronRight, { size: 13, className: "text-gray-400" })
|
|
854
|
-
]
|
|
855
|
-
}
|
|
856
|
-
),
|
|
857
|
-
submenu === "action" && /* @__PURE__ */ jsx7("div", { className: "absolute left-full top-0 ml-1 w-64 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-lg py-1", children: resolvedActionOptions.map((option) => /* @__PURE__ */ jsx7(
|
|
858
|
-
"button",
|
|
859
|
-
{
|
|
860
|
-
"data-cv-tooltip": option.label,
|
|
861
|
-
"aria-label": option.label,
|
|
862
|
-
onClick: () => select({ kind: "action", actionKind: option.actionKind }),
|
|
863
|
-
className: "w-full text-left px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700",
|
|
864
|
-
children: option.label
|
|
865
|
-
},
|
|
866
|
-
option.actionKind
|
|
867
|
-
)) })
|
|
1001
|
+
open && /* @__PURE__ */ jsx7("div", { className: "absolute left-0 top-full mt-1.5 w-64 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-lg z-50 py-1", children: /* @__PURE__ */ jsx7(
|
|
1002
|
+
FlowPaletteMenu,
|
|
1003
|
+
{
|
|
1004
|
+
onSelect: select,
|
|
1005
|
+
labels,
|
|
1006
|
+
...actionOptions ? { actionOptions } : {}
|
|
1007
|
+
}
|
|
1008
|
+
) })
|
|
1009
|
+
] });
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/flows/FlowLegend.tsx
|
|
1013
|
+
import { useState as useState3 } from "react";
|
|
1014
|
+
import { ChevronDown, ChevronUp, RotateCcw as RotateCcw2 } from "lucide-react";
|
|
1015
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1016
|
+
function EdgeSample({ sample }) {
|
|
1017
|
+
return /* @__PURE__ */ jsxs7("li", { className: "flex items-center gap-2", children: [
|
|
1018
|
+
/* @__PURE__ */ jsx8("svg", { width: "28", height: "12", viewBox: "0 0 28 12", "aria-hidden": "true", className: "shrink-0", children: /* @__PURE__ */ jsx8(
|
|
1019
|
+
"line",
|
|
1020
|
+
{
|
|
1021
|
+
x1: "2",
|
|
1022
|
+
y1: "6",
|
|
1023
|
+
x2: "26",
|
|
1024
|
+
y2: "6",
|
|
1025
|
+
stroke: sample.color,
|
|
1026
|
+
strokeWidth: "1.75",
|
|
1027
|
+
...sample.dash ? { strokeDasharray: sample.dash } : {}
|
|
1028
|
+
}
|
|
1029
|
+
) }),
|
|
1030
|
+
/* @__PURE__ */ jsx8("span", { className: "text-[11px] leading-tight text-gray-600 dark:text-gray-300", children: sample.label })
|
|
1031
|
+
] });
|
|
1032
|
+
}
|
|
1033
|
+
function FlowLegend({ labels, edgeSamples, nodeSwatches }) {
|
|
1034
|
+
const [open, setOpen] = useState3(false);
|
|
1035
|
+
return /* @__PURE__ */ jsxs7("div", { className: "rounded-lg border border-gray-200 bg-white/95 shadow-sm backdrop-blur dark:border-gray-700 dark:bg-gray-800/95", children: [
|
|
1036
|
+
/* @__PURE__ */ jsxs7(
|
|
1037
|
+
"button",
|
|
1038
|
+
{
|
|
1039
|
+
type: "button",
|
|
1040
|
+
onClick: () => setOpen((current) => !current),
|
|
1041
|
+
"data-cv-tooltip": labels.legendPanel.title,
|
|
1042
|
+
"aria-label": labels.legendPanel.title,
|
|
1043
|
+
"aria-expanded": open,
|
|
1044
|
+
className: "flex w-full items-center justify-between gap-2 px-2.5 py-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400",
|
|
1045
|
+
children: [
|
|
1046
|
+
labels.legendPanel.title,
|
|
1047
|
+
open ? /* @__PURE__ */ jsx8(ChevronDown, { size: 13 }) : /* @__PURE__ */ jsx8(ChevronUp, { size: 13 })
|
|
1048
|
+
]
|
|
1049
|
+
}
|
|
1050
|
+
),
|
|
1051
|
+
open && /* @__PURE__ */ jsxs7("div", { className: "max-h-72 w-64 overflow-y-auto border-t border-gray-100 px-2.5 py-2 dark:border-gray-700", children: [
|
|
1052
|
+
/* @__PURE__ */ jsx8("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500", children: labels.legendPanel.connections }),
|
|
1053
|
+
/* @__PURE__ */ jsx8("ul", { className: "space-y-1.5", children: edgeSamples.map((sample) => /* @__PURE__ */ jsx8(EdgeSample, { sample }, sample.label)) }),
|
|
1054
|
+
/* @__PURE__ */ jsx8("p", { className: "mb-1 mt-3 text-[10px] font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500", children: labels.legendPanel.nodes }),
|
|
1055
|
+
/* @__PURE__ */ jsxs7("ul", { className: "space-y-1.5", children: [
|
|
1056
|
+
nodeSwatches.map((swatch) => /* @__PURE__ */ jsxs7("li", { className: "flex items-center gap-2", children: [
|
|
1057
|
+
/* @__PURE__ */ jsx8("span", { className: `h-3 w-5 shrink-0 rounded border-2 ${swatch.className}`, "aria-hidden": "true" }),
|
|
1058
|
+
/* @__PURE__ */ jsx8("span", { className: "text-[11px] leading-tight text-gray-600 dark:text-gray-300", children: labels.legend[swatch.type] })
|
|
1059
|
+
] }, swatch.type)),
|
|
1060
|
+
/* @__PURE__ */ jsxs7("li", { className: "flex items-center gap-2", children: [
|
|
1061
|
+
/* @__PURE__ */ jsx8(
|
|
1062
|
+
"span",
|
|
1063
|
+
{
|
|
1064
|
+
className: "h-3 w-5 shrink-0 rounded border-2 border-dashed border-amber-400",
|
|
1065
|
+
"aria-hidden": "true"
|
|
1066
|
+
}
|
|
1067
|
+
),
|
|
1068
|
+
/* @__PURE__ */ jsx8("span", { className: "text-[11px] leading-tight text-gray-600 dark:text-gray-300", children: labels.legendPanel.detached })
|
|
1069
|
+
] }),
|
|
1070
|
+
/* @__PURE__ */ jsxs7("li", { className: "flex items-center gap-2", children: [
|
|
1071
|
+
/* @__PURE__ */ jsx8("span", { className: "flex h-3 w-5 shrink-0 items-center justify-center", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("span", { className: "h-1.5 w-1.5 rounded-full bg-emerald-500" }) }),
|
|
1072
|
+
/* @__PURE__ */ jsx8("span", { className: "text-[11px] leading-tight text-gray-600 dark:text-gray-300", children: labels.legendPanel.startNode })
|
|
1073
|
+
] }),
|
|
1074
|
+
/* @__PURE__ */ jsxs7("li", { className: "flex items-center gap-2", children: [
|
|
1075
|
+
/* @__PURE__ */ jsx8("span", { className: "flex h-3 w-5 shrink-0 items-center justify-center text-gray-400", "aria-hidden": "true", children: /* @__PURE__ */ jsx8(RotateCcw2, { size: 12, strokeWidth: 2.5 }) }),
|
|
1076
|
+
/* @__PURE__ */ jsx8("span", { className: "text-[11px] leading-tight text-gray-600 dark:text-gray-300", children: labels.legendPanel.selfLoop })
|
|
1077
|
+
] })
|
|
868
1078
|
] })
|
|
869
1079
|
] })
|
|
870
1080
|
] });
|
|
871
1081
|
}
|
|
872
1082
|
|
|
1083
|
+
// src/flows/FlowConnectionEdge.tsx
|
|
1084
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState4 } from "react";
|
|
1085
|
+
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from "@xyflow/react";
|
|
1086
|
+
import { X as X2 } from "lucide-react";
|
|
1087
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1088
|
+
var HIDE_DELAY_MS = 320;
|
|
1089
|
+
function FlowConnectionEdge({
|
|
1090
|
+
id,
|
|
1091
|
+
sourceX,
|
|
1092
|
+
sourceY,
|
|
1093
|
+
targetX,
|
|
1094
|
+
targetY,
|
|
1095
|
+
sourcePosition,
|
|
1096
|
+
targetPosition,
|
|
1097
|
+
markerEnd,
|
|
1098
|
+
style,
|
|
1099
|
+
data,
|
|
1100
|
+
interactionWidth
|
|
1101
|
+
}) {
|
|
1102
|
+
const [hovered, setHovered] = useState4(false);
|
|
1103
|
+
const hideTimer = useRef2(void 0);
|
|
1104
|
+
const { onDisconnect, disconnectLabel } = data ?? {};
|
|
1105
|
+
function show() {
|
|
1106
|
+
if (hideTimer.current) clearTimeout(hideTimer.current);
|
|
1107
|
+
setHovered(true);
|
|
1108
|
+
}
|
|
1109
|
+
function scheduleHide() {
|
|
1110
|
+
if (hideTimer.current) clearTimeout(hideTimer.current);
|
|
1111
|
+
hideTimer.current = setTimeout(() => setHovered(false), HIDE_DELAY_MS);
|
|
1112
|
+
}
|
|
1113
|
+
useEffect2(() => () => clearTimeout(hideTimer.current), []);
|
|
1114
|
+
const [path, labelX, labelY] = getBezierPath({
|
|
1115
|
+
sourceX,
|
|
1116
|
+
sourceY,
|
|
1117
|
+
sourcePosition,
|
|
1118
|
+
targetX,
|
|
1119
|
+
targetY,
|
|
1120
|
+
targetPosition
|
|
1121
|
+
});
|
|
1122
|
+
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1123
|
+
/* @__PURE__ */ jsx9(BaseEdge, { id, path, markerEnd, style, interactionWidth: interactionWidth ?? 20 }),
|
|
1124
|
+
/* @__PURE__ */ jsx9(
|
|
1125
|
+
"path",
|
|
1126
|
+
{
|
|
1127
|
+
d: path,
|
|
1128
|
+
fill: "none",
|
|
1129
|
+
strokeWidth: 22,
|
|
1130
|
+
stroke: "transparent",
|
|
1131
|
+
className: "react-flow__edge-interaction",
|
|
1132
|
+
onMouseEnter: show,
|
|
1133
|
+
onMouseLeave: scheduleHide
|
|
1134
|
+
}
|
|
1135
|
+
),
|
|
1136
|
+
onDisconnect && hovered && /* @__PURE__ */ jsx9(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx9(
|
|
1137
|
+
"button",
|
|
1138
|
+
{
|
|
1139
|
+
type: "button",
|
|
1140
|
+
"data-cv-tooltip": disconnectLabel,
|
|
1141
|
+
"aria-label": disconnectLabel,
|
|
1142
|
+
onMouseEnter: show,
|
|
1143
|
+
onMouseLeave: scheduleHide,
|
|
1144
|
+
onClick: (event) => {
|
|
1145
|
+
event.stopPropagation();
|
|
1146
|
+
onDisconnect();
|
|
1147
|
+
},
|
|
1148
|
+
style: { transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` },
|
|
1149
|
+
className: "nodrag nopan pointer-events-auto absolute flex h-5 w-5 items-center justify-center rounded-full border border-gray-300 bg-white text-gray-500 shadow-sm hover:border-red-400 hover:text-red-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400",
|
|
1150
|
+
children: /* @__PURE__ */ jsx9(X2, { size: 12, strokeWidth: 2.5 })
|
|
1151
|
+
}
|
|
1152
|
+
) })
|
|
1153
|
+
] });
|
|
1154
|
+
}
|
|
1155
|
+
var flowEdgeTypes = { flowConnection: FlowConnectionEdge };
|
|
1156
|
+
|
|
873
1157
|
// src/flows/FlowNodePanel.tsx
|
|
874
|
-
import { useState as
|
|
875
|
-
import { Plus as
|
|
1158
|
+
import { useState as useState5 } from "react";
|
|
1159
|
+
import { Plus as Plus3, Trash2, Save, X as X3, AlertTriangle as AlertTriangle2, AlertCircle as AlertCircle2 } from "lucide-react";
|
|
876
1160
|
|
|
877
1161
|
// src/flows/FlowWhatsAppPreview.tsx
|
|
878
1162
|
import { List } from "lucide-react";
|
|
879
|
-
import { jsx as
|
|
1163
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
880
1164
|
function FlowWhatsAppPreview({
|
|
881
1165
|
body,
|
|
882
1166
|
options,
|
|
883
1167
|
labels = DEFAULT_FLOW_EDITOR_LABELS.nodePanel
|
|
884
1168
|
}) {
|
|
885
1169
|
if (!body && (!options || options.length === 0)) {
|
|
886
|
-
return /* @__PURE__ */
|
|
1170
|
+
return /* @__PURE__ */ jsx10("p", { className: "text-xs text-gray-400 dark:text-gray-500 italic px-1", children: labels.previewPlaceholder });
|
|
887
1171
|
}
|
|
888
1172
|
const usesButtons = rendersAsButtons(options);
|
|
889
1173
|
const hasOptions = (options?.length ?? 0) > 0;
|
|
890
|
-
return /* @__PURE__ */
|
|
891
|
-
/* @__PURE__ */
|
|
892
|
-
/* @__PURE__ */
|
|
893
|
-
body ? parseWhatsAppFormatting(body) : /* @__PURE__ */
|
|
894
|
-
hasOptions && !usesButtons && /* @__PURE__ */
|
|
895
|
-
/* @__PURE__ */
|
|
1174
|
+
return /* @__PURE__ */ jsxs9("div", { className: "rounded-xl bg-[#e5ddd5] dark:bg-gray-900 p-3 space-y-1.5", children: [
|
|
1175
|
+
/* @__PURE__ */ jsxs9("div", { className: "max-w-[85%]", children: [
|
|
1176
|
+
/* @__PURE__ */ jsxs9("div", { className: "rounded-lg rounded-tl-none bg-white dark:bg-gray-700 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 shadow-sm whitespace-pre-wrap break-words", children: [
|
|
1177
|
+
body ? parseWhatsAppFormatting(body) : /* @__PURE__ */ jsx10("span", { className: "italic text-gray-400", children: labels.previewEmptyBody }),
|
|
1178
|
+
hasOptions && !usesButtons && /* @__PURE__ */ jsx10("div", { className: "mt-2 -mx-3 -mb-2 border-t border-gray-100 dark:border-gray-600", children: /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-center gap-1.5 py-2 text-sm font-medium text-cyan-600 dark:text-cyan-400", children: [
|
|
1179
|
+
/* @__PURE__ */ jsx10(List, { size: 15 }),
|
|
896
1180
|
" ",
|
|
897
1181
|
labels.previewListButton
|
|
898
1182
|
] }) })
|
|
899
1183
|
] }),
|
|
900
|
-
hasOptions && usesButtons && /* @__PURE__ */
|
|
1184
|
+
hasOptions && usesButtons && /* @__PURE__ */ jsx10("div", { className: "mt-1 space-y-1", children: options.map(([id, label]) => /* @__PURE__ */ jsx10(
|
|
901
1185
|
"div",
|
|
902
1186
|
{
|
|
903
1187
|
className: "rounded-lg bg-white dark:bg-gray-700 py-1.5 text-center text-sm font-medium text-cyan-600 dark:text-cyan-400 shadow-sm",
|
|
904
|
-
children: label || /* @__PURE__ */
|
|
1188
|
+
children: label || /* @__PURE__ */ jsx10("span", { className: "italic text-gray-400", children: labels.previewEmptyOption })
|
|
905
1189
|
},
|
|
906
1190
|
id
|
|
907
1191
|
)) }),
|
|
908
|
-
hasOptions && !usesButtons && /* @__PURE__ */
|
|
909
|
-
/* @__PURE__ */
|
|
910
|
-
label || /* @__PURE__ */
|
|
1192
|
+
hasOptions && !usesButtons && /* @__PURE__ */ jsx10("div", { className: "mt-1.5 rounded-lg bg-white dark:bg-gray-700 shadow-sm divide-y divide-gray-100 dark:divide-gray-600 overflow-hidden", children: options.slice(0, WHATSAPP_LIMITS.MAX_LIST_ROWS).map(([id, label]) => /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 px-3 py-1.5 text-sm text-gray-800 dark:text-gray-100", children: [
|
|
1193
|
+
/* @__PURE__ */ jsx10("span", { className: "h-3.5 w-3.5 rounded-full border border-gray-300 dark:border-gray-500 shrink-0" }),
|
|
1194
|
+
label || /* @__PURE__ */ jsx10("span", { className: "italic text-gray-400", children: labels.previewEmptyOption })
|
|
911
1195
|
] }, id)) })
|
|
912
1196
|
] }),
|
|
913
|
-
hasOptions && /* @__PURE__ */
|
|
1197
|
+
hasOptions && /* @__PURE__ */ jsx10("p", { className: "text-[11px] text-gray-500 dark:text-gray-400 px-1", children: usesButtons ? labels.previewModeButtons : labels.previewModeList })
|
|
914
1198
|
] });
|
|
915
1199
|
}
|
|
916
1200
|
|
|
917
1201
|
// src/flows/FlowNodePanel.tsx
|
|
918
|
-
import { Fragment, jsx as
|
|
1202
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
919
1203
|
var SELECT_CLASSNAME = "border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all";
|
|
920
1204
|
var INPUT_CLASSNAME = "border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all";
|
|
921
1205
|
function truncateLabel(value, max = 60) {
|
|
@@ -930,10 +1214,10 @@ function WhatsAppTextField({
|
|
|
930
1214
|
placeholder,
|
|
931
1215
|
labels
|
|
932
1216
|
}) {
|
|
933
|
-
return /* @__PURE__ */
|
|
934
|
-
/* @__PURE__ */
|
|
935
|
-
/* @__PURE__ */
|
|
936
|
-
/* @__PURE__ */
|
|
1217
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
|
|
1218
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
1219
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: label }),
|
|
1220
|
+
/* @__PURE__ */ jsx11(
|
|
937
1221
|
"textarea",
|
|
938
1222
|
{
|
|
939
1223
|
value,
|
|
@@ -944,18 +1228,18 @@ function WhatsAppTextField({
|
|
|
944
1228
|
}
|
|
945
1229
|
)
|
|
946
1230
|
] }),
|
|
947
|
-
/* @__PURE__ */
|
|
948
|
-
/* @__PURE__ */
|
|
949
|
-
/* @__PURE__ */
|
|
1231
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
1232
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.preview }),
|
|
1233
|
+
/* @__PURE__ */ jsx11("div", { className: "mt-1", children: /* @__PURE__ */ jsx11(FlowWhatsAppPreview, { body: value, options, labels: labels.nodePanel }) })
|
|
950
1234
|
] })
|
|
951
1235
|
] });
|
|
952
1236
|
}
|
|
953
1237
|
function IssueRow({ issue }) {
|
|
954
1238
|
const Icon = issue.severity === "error" ? AlertCircle2 : AlertTriangle2;
|
|
955
1239
|
const color = issue.severity === "error" ? "text-red-600 dark:text-red-400" : "text-amber-600 dark:text-amber-400";
|
|
956
|
-
return /* @__PURE__ */
|
|
957
|
-
/* @__PURE__ */
|
|
958
|
-
/* @__PURE__ */
|
|
1240
|
+
return /* @__PURE__ */ jsxs10("div", { className: `flex items-start gap-1.5 text-xs ${color}`, children: [
|
|
1241
|
+
/* @__PURE__ */ jsx11(Icon, { size: 13, className: "mt-0.5 shrink-0" }),
|
|
1242
|
+
/* @__PURE__ */ jsx11("span", { children: issue.message })
|
|
959
1243
|
] });
|
|
960
1244
|
}
|
|
961
1245
|
function FlowNodePanel({
|
|
@@ -970,11 +1254,12 @@ function FlowNodePanel({
|
|
|
970
1254
|
renderMediaPicker
|
|
971
1255
|
}) {
|
|
972
1256
|
const labels = { ...DEFAULT_FLOW_EDITOR_LABELS, ...labelsOverride };
|
|
973
|
-
const [draft, setDraft] =
|
|
1257
|
+
const [draft, setDraft] = useState5(node);
|
|
974
1258
|
const otherNodeIds = Object.keys(graph.nodes).filter((id) => id !== node.id);
|
|
975
1259
|
const isFixedLogic = draft.type === "entrada_choice";
|
|
976
1260
|
const isAction = draft.type === "action";
|
|
977
1261
|
const isSendMedia = isAction && draft.actionKind === BUILT_IN_ACTION_KINDS.SEND_MEDIA;
|
|
1262
|
+
const isPassThroughAction = isAction && Boolean(draft.actionKind && PASS_THROUGH_ACTION_KINDS.includes(draft.actionKind));
|
|
978
1263
|
const isCondition = draft.type === "condition";
|
|
979
1264
|
const isStart = graph.startNodeId === node.id;
|
|
980
1265
|
const nodeIssues = issues.filter((i) => i.nodeId === node.id);
|
|
@@ -1018,24 +1303,24 @@ function FlowNodePanel({
|
|
|
1018
1303
|
setDraft((prev) => ({ ...prev, options: (prev.options ?? []).filter((_, i) => i !== index) }));
|
|
1019
1304
|
}
|
|
1020
1305
|
function nextNodeOptions() {
|
|
1021
|
-
return /* @__PURE__ */
|
|
1022
|
-
otherNodeIds.map((id) => /* @__PURE__ */
|
|
1023
|
-
otherFlows.length > 0 && /* @__PURE__ */
|
|
1306
|
+
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1307
|
+
otherNodeIds.map((id) => /* @__PURE__ */ jsx11("option", { value: id, children: truncateLabel(nodeLabel(graph.nodes[id], labels)) }, id)),
|
|
1308
|
+
otherFlows.length > 0 && /* @__PURE__ */ jsx11("optgroup", { label: labels.nodePanel.otherFlowsGroup, children: otherFlows.map((flow) => /* @__PURE__ */ jsx11("option", { value: `${CROSS_FLOW_PREFIX}${flow.key}`, children: flow.label }, flow.key)) })
|
|
1024
1309
|
] });
|
|
1025
1310
|
}
|
|
1026
|
-
return /* @__PURE__ */
|
|
1027
|
-
/* @__PURE__ */
|
|
1028
|
-
/* @__PURE__ */
|
|
1029
|
-
/* @__PURE__ */
|
|
1311
|
+
return /* @__PURE__ */ jsxs10("div", { className: "fixed inset-y-0 right-0 w-96 bg-white dark:bg-gray-800 border-l border-gray-200 dark:border-gray-700 shadow-xl z-50 flex flex-col", children: [
|
|
1312
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between px-4 py-3 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1313
|
+
/* @__PURE__ */ jsx11("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.nodePanel.title }),
|
|
1314
|
+
/* @__PURE__ */ jsx11("button", { "data-cv-tooltip": labels.nodePanel.close, "aria-label": labels.nodePanel.close, onClick: onClose, className: "text-gray-400 hover:text-gray-600", children: /* @__PURE__ */ jsx11(X3, { size: 18 }) })
|
|
1030
1315
|
] }),
|
|
1031
|
-
/* @__PURE__ */
|
|
1032
|
-
nodeIssues.length > 0 && /* @__PURE__ */
|
|
1033
|
-
isFixedLogic && /* @__PURE__ */
|
|
1034
|
-
isAction && /* @__PURE__ */
|
|
1035
|
-
isCondition && /* @__PURE__ */
|
|
1036
|
-
/* @__PURE__ */
|
|
1037
|
-
/* @__PURE__ */
|
|
1038
|
-
/* @__PURE__ */
|
|
1316
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex-1 overflow-y-auto p-4 space-y-4", children: [
|
|
1317
|
+
nodeIssues.length > 0 && /* @__PURE__ */ jsx11("div", { className: "rounded-lg border border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-900/40 p-2.5 space-y-1.5", children: nodeIssues.map((issue, i) => /* @__PURE__ */ jsx11(IssueRow, { issue }, i)) }),
|
|
1318
|
+
isFixedLogic && /* @__PURE__ */ jsx11("p", { className: "text-xs text-purple-700 dark:text-purple-400 bg-purple-50 dark:bg-purple-950/30 rounded-lg p-2", children: labels.nodePanel.fixedLogicNotice }),
|
|
1319
|
+
isAction && /* @__PURE__ */ jsx11("p", { className: "text-xs text-orange-700 dark:text-orange-400 bg-orange-50 dark:bg-orange-950/30 rounded-lg p-2", children: labels.nodePanel.actionNotice }),
|
|
1320
|
+
isCondition && /* @__PURE__ */ jsx11("p", { className: "text-xs text-cyan-700 dark:text-cyan-400 bg-cyan-50 dark:bg-cyan-950/30 rounded-lg p-2", children: labels.nodePanel.conditionNotice }),
|
|
1321
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
1322
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.nodeName }),
|
|
1323
|
+
/* @__PURE__ */ jsx11(
|
|
1039
1324
|
"input",
|
|
1040
1325
|
{
|
|
1041
1326
|
value: draft.label ?? "",
|
|
@@ -1044,11 +1329,11 @@ function FlowNodePanel({
|
|
|
1044
1329
|
className: `w-full mt-1 ${INPUT_CLASSNAME}`
|
|
1045
1330
|
}
|
|
1046
1331
|
),
|
|
1047
|
-
/* @__PURE__ */
|
|
1332
|
+
/* @__PURE__ */ jsx11("p", { className: "text-[11px] text-gray-400 dark:text-gray-500 mt-1", children: labels.nodePanel.nodeNameHint })
|
|
1048
1333
|
] }),
|
|
1049
|
-
draft.contextKey && /* @__PURE__ */
|
|
1050
|
-
/* @__PURE__ */
|
|
1051
|
-
/* @__PURE__ */
|
|
1334
|
+
draft.contextKey && /* @__PURE__ */ jsxs10("div", { children: [
|
|
1335
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.contextKey }),
|
|
1336
|
+
/* @__PURE__ */ jsx11(
|
|
1052
1337
|
"input",
|
|
1053
1338
|
{
|
|
1054
1339
|
value: draft.contextKey,
|
|
@@ -1057,19 +1342,19 @@ function FlowNodePanel({
|
|
|
1057
1342
|
}
|
|
1058
1343
|
)
|
|
1059
1344
|
] }),
|
|
1060
|
-
!isFixedLogic && !isAction && !isCondition && /* @__PURE__ */
|
|
1061
|
-
/* @__PURE__ */
|
|
1062
|
-
/* @__PURE__ */
|
|
1345
|
+
!isFixedLogic && !isAction && !isCondition && /* @__PURE__ */ jsxs10("div", { children: [
|
|
1346
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.questionType }),
|
|
1347
|
+
/* @__PURE__ */ jsx11(
|
|
1063
1348
|
"select",
|
|
1064
1349
|
{
|
|
1065
1350
|
value: draft.questionType ?? "text",
|
|
1066
1351
|
onChange: (e) => setDraft((prev) => ({ ...prev, questionType: e.target.value })),
|
|
1067
1352
|
className: `w-full mt-1 ${SELECT_CLASSNAME}`,
|
|
1068
|
-
children: Object.entries(labels.questionTypeLabels).map(([key, label]) => /* @__PURE__ */
|
|
1353
|
+
children: Object.entries(labels.questionTypeLabels).map(([key, label]) => /* @__PURE__ */ jsx11("option", { value: key, children: label }, key))
|
|
1069
1354
|
}
|
|
1070
1355
|
)
|
|
1071
1356
|
] }),
|
|
1072
|
-
!isFixedLogic && !isAction && !isCondition && /* @__PURE__ */
|
|
1357
|
+
!isFixedLogic && !isAction && !isCondition && /* @__PURE__ */ jsx11(
|
|
1073
1358
|
WhatsAppTextField,
|
|
1074
1359
|
{
|
|
1075
1360
|
label: labels.nodePanel.question,
|
|
@@ -1079,10 +1364,10 @@ function FlowNodePanel({
|
|
|
1079
1364
|
labels
|
|
1080
1365
|
}
|
|
1081
1366
|
),
|
|
1082
|
-
isCondition && /* @__PURE__ */
|
|
1083
|
-
/* @__PURE__ */
|
|
1084
|
-
/* @__PURE__ */
|
|
1085
|
-
/* @__PURE__ */
|
|
1367
|
+
isCondition && /* @__PURE__ */ jsxs10("div", { className: "space-y-3", children: [
|
|
1368
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
1369
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.conditionVariable }),
|
|
1370
|
+
/* @__PURE__ */ jsx11(
|
|
1086
1371
|
"input",
|
|
1087
1372
|
{
|
|
1088
1373
|
value: draft.conditionContextKey ?? "",
|
|
@@ -1091,11 +1376,11 @@ function FlowNodePanel({
|
|
|
1091
1376
|
className: `w-full mt-1 ${INPUT_CLASSNAME}`
|
|
1092
1377
|
}
|
|
1093
1378
|
),
|
|
1094
|
-
/* @__PURE__ */
|
|
1379
|
+
/* @__PURE__ */ jsx11("datalist", { id: "condition-context-keys", children: knownContextKeys.map((key) => /* @__PURE__ */ jsx11("option", { value: key }, key)) })
|
|
1095
1380
|
] }),
|
|
1096
|
-
/* @__PURE__ */
|
|
1097
|
-
/* @__PURE__ */
|
|
1098
|
-
/* @__PURE__ */
|
|
1381
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
1382
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.conditionOperator }),
|
|
1383
|
+
/* @__PURE__ */ jsx11(
|
|
1099
1384
|
"select",
|
|
1100
1385
|
{
|
|
1101
1386
|
value: draft.conditionOperator ?? ">",
|
|
@@ -1104,13 +1389,13 @@ function FlowNodePanel({
|
|
|
1104
1389
|
conditionOperator: e.target.value
|
|
1105
1390
|
})),
|
|
1106
1391
|
className: `w-full mt-1 ${SELECT_CLASSNAME}`,
|
|
1107
|
-
children: CONDITION_OPERATORS.map((operator) => /* @__PURE__ */
|
|
1392
|
+
children: CONDITION_OPERATORS.map((operator) => /* @__PURE__ */ jsx11("option", { value: operator, children: labels.conditionOperatorLabels[operator] ?? operator }, operator))
|
|
1108
1393
|
}
|
|
1109
1394
|
)
|
|
1110
1395
|
] }),
|
|
1111
|
-
/* @__PURE__ */
|
|
1112
|
-
/* @__PURE__ */
|
|
1113
|
-
/* @__PURE__ */
|
|
1396
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
1397
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.conditionValue }),
|
|
1398
|
+
/* @__PURE__ */ jsx11(
|
|
1114
1399
|
"input",
|
|
1115
1400
|
{
|
|
1116
1401
|
value: draft.conditionValue ?? "",
|
|
@@ -1120,7 +1405,7 @@ function FlowNodePanel({
|
|
|
1120
1405
|
)
|
|
1121
1406
|
] })
|
|
1122
1407
|
] }),
|
|
1123
|
-
isAction && draft.actionKind === "send_product_list" && /* @__PURE__ */
|
|
1408
|
+
isAction && draft.actionKind === "send_product_list" && /* @__PURE__ */ jsx11(
|
|
1124
1409
|
WhatsAppTextField,
|
|
1125
1410
|
{
|
|
1126
1411
|
label: labels.nodePanel.fallbackMessage,
|
|
@@ -1130,11 +1415,11 @@ function FlowNodePanel({
|
|
|
1130
1415
|
labels
|
|
1131
1416
|
}
|
|
1132
1417
|
),
|
|
1133
|
-
isSendMedia && /* @__PURE__ */
|
|
1134
|
-
/* @__PURE__ */
|
|
1135
|
-
/* @__PURE__ */
|
|
1418
|
+
isSendMedia && /* @__PURE__ */ jsxs10("div", { children: [
|
|
1419
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.media }),
|
|
1420
|
+
/* @__PURE__ */ jsx11("div", { className: "mt-1", children: renderMediaPicker?.(node, graph) ?? /* @__PURE__ */ jsx11("p", { className: "text-xs text-gray-400", children: labels.nodePanel.mediaUnavailable }) })
|
|
1136
1421
|
] }),
|
|
1137
|
-
isAction && draft.actionKind !== "send_product_list" && !isSendMedia && /* @__PURE__ */
|
|
1422
|
+
isAction && draft.actionKind !== "send_product_list" && !isSendMedia && /* @__PURE__ */ jsx11(
|
|
1138
1423
|
WhatsAppTextField,
|
|
1139
1424
|
{
|
|
1140
1425
|
label: labels.nodePanel.directMessage,
|
|
@@ -1144,17 +1429,17 @@ function FlowNodePanel({
|
|
|
1144
1429
|
labels
|
|
1145
1430
|
}
|
|
1146
1431
|
),
|
|
1147
|
-
(draft.questionType === "choice" || draft.type === "menu") && /* @__PURE__ */
|
|
1148
|
-
/* @__PURE__ */
|
|
1149
|
-
/* @__PURE__ */
|
|
1150
|
-
/* @__PURE__ */
|
|
1151
|
-
/* @__PURE__ */
|
|
1432
|
+
(draft.questionType === "choice" || draft.type === "menu") && /* @__PURE__ */ jsxs10("div", { children: [
|
|
1433
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between mb-1.5", children: [
|
|
1434
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.options }),
|
|
1435
|
+
/* @__PURE__ */ jsxs10("button", { "data-cv-tooltip": labels.nodePanel.addOption, "aria-label": labels.nodePanel.addOption, onClick: addOption, className: "text-xs text-blue-600 hover:underline flex items-center gap-1", children: [
|
|
1436
|
+
/* @__PURE__ */ jsx11(Plus3, { size: 12 }),
|
|
1152
1437
|
" ",
|
|
1153
1438
|
labels.nodePanel.addOption
|
|
1154
1439
|
] })
|
|
1155
1440
|
] }),
|
|
1156
|
-
/* @__PURE__ */
|
|
1157
|
-
/* @__PURE__ */
|
|
1441
|
+
/* @__PURE__ */ jsx11("div", { className: "space-y-2", children: (draft.options ?? []).map(([id, label], i) => /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
1442
|
+
/* @__PURE__ */ jsx11(
|
|
1158
1443
|
"input",
|
|
1159
1444
|
{
|
|
1160
1445
|
value: id,
|
|
@@ -1163,7 +1448,7 @@ function FlowNodePanel({
|
|
|
1163
1448
|
className: `w-16 ${INPUT_CLASSNAME}`
|
|
1164
1449
|
}
|
|
1165
1450
|
),
|
|
1166
|
-
/* @__PURE__ */
|
|
1451
|
+
/* @__PURE__ */ jsx11(
|
|
1167
1452
|
"input",
|
|
1168
1453
|
{
|
|
1169
1454
|
value: label,
|
|
@@ -1172,49 +1457,49 @@ function FlowNodePanel({
|
|
|
1172
1457
|
className: `flex-1 ${INPUT_CLASSNAME}`
|
|
1173
1458
|
}
|
|
1174
1459
|
),
|
|
1175
|
-
/* @__PURE__ */
|
|
1460
|
+
/* @__PURE__ */ jsx11("button", { "data-cv-tooltip": labels.nodePanel.removeOption, "aria-label": labels.nodePanel.removeOption, onClick: () => removeOption(i), className: "text-gray-400 hover:text-red-600", children: /* @__PURE__ */ jsx11(Trash2, { size: 14 }) })
|
|
1176
1461
|
] }, i)) })
|
|
1177
1462
|
] }),
|
|
1178
|
-
!isAction && /* @__PURE__ */
|
|
1179
|
-
/* @__PURE__ */
|
|
1180
|
-
/* @__PURE__ */
|
|
1181
|
-
typeof draft.next !== "object" && !isCondition ? /* @__PURE__ */
|
|
1463
|
+
(!isAction || isPassThroughAction) && /* @__PURE__ */ jsxs10("div", { className: "pt-2 border-t border-gray-100 dark:border-gray-700", children: [
|
|
1464
|
+
/* @__PURE__ */ jsx11("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.next }),
|
|
1465
|
+
/* @__PURE__ */ jsx11("p", { className: "text-[11px] text-gray-400 dark:text-gray-500 mb-1", children: labels.nodePanel.nextHint }),
|
|
1466
|
+
typeof draft.next !== "object" && !isCondition ? /* @__PURE__ */ jsxs10(
|
|
1182
1467
|
"select",
|
|
1183
1468
|
{
|
|
1184
1469
|
value: typeof draft.next === "string" ? draft.next : "",
|
|
1185
1470
|
onChange: (e) => updateNextString(e.target.value),
|
|
1186
1471
|
className: `w-full mt-1 ${SELECT_CLASSNAME}`,
|
|
1187
1472
|
children: [
|
|
1188
|
-
/* @__PURE__ */
|
|
1473
|
+
/* @__PURE__ */ jsx11("option", { value: "", children: "\u2014" }),
|
|
1189
1474
|
nextNodeOptions()
|
|
1190
1475
|
]
|
|
1191
1476
|
}
|
|
1192
|
-
) : /* @__PURE__ */
|
|
1193
|
-
conditionAnswerIds.map((id) => /* @__PURE__ */
|
|
1194
|
-
/* @__PURE__ */
|
|
1195
|
-
/* @__PURE__ */
|
|
1477
|
+
) : /* @__PURE__ */ jsxs10("div", { className: "space-y-2 mt-1", children: [
|
|
1478
|
+
conditionAnswerIds.map((id) => /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
1479
|
+
/* @__PURE__ */ jsx11("span", { className: "text-xs text-gray-500 w-32 shrink-0", children: isCondition ? id === "true" ? labels.nodePanel.conditionTrue : labels.nodePanel.conditionFalse : labels.nodePanel.nextByAnswer(id) }),
|
|
1480
|
+
/* @__PURE__ */ jsxs10(
|
|
1196
1481
|
"select",
|
|
1197
1482
|
{
|
|
1198
1483
|
value: draft.next && typeof draft.next === "object" ? draft.next.byAnswer[id] ?? "" : "",
|
|
1199
1484
|
onChange: (e) => updateNextByAnswer(id, e.target.value),
|
|
1200
1485
|
className: `flex-1 ${SELECT_CLASSNAME}`,
|
|
1201
1486
|
children: [
|
|
1202
|
-
/* @__PURE__ */
|
|
1487
|
+
/* @__PURE__ */ jsx11("option", { value: "", children: "\u2014" }),
|
|
1203
1488
|
nextNodeOptions()
|
|
1204
1489
|
]
|
|
1205
1490
|
}
|
|
1206
1491
|
)
|
|
1207
1492
|
] }, id)),
|
|
1208
|
-
/* @__PURE__ */
|
|
1209
|
-
/* @__PURE__ */
|
|
1210
|
-
/* @__PURE__ */
|
|
1493
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
1494
|
+
/* @__PURE__ */ jsx11("span", { className: "text-xs text-gray-500 w-32 shrink-0", children: isCondition ? labels.nodePanel.conditionVariableMissing : labels.nodePanel.nextDefault }),
|
|
1495
|
+
/* @__PURE__ */ jsxs10(
|
|
1211
1496
|
"select",
|
|
1212
1497
|
{
|
|
1213
1498
|
value: draft.next && typeof draft.next === "object" ? draft.next.default : "",
|
|
1214
1499
|
onChange: (e) => updateNextDefault(e.target.value),
|
|
1215
1500
|
className: `flex-1 ${SELECT_CLASSNAME}`,
|
|
1216
1501
|
children: [
|
|
1217
|
-
/* @__PURE__ */
|
|
1502
|
+
/* @__PURE__ */ jsx11("option", { value: "", children: "\u2014" }),
|
|
1218
1503
|
nextNodeOptions()
|
|
1219
1504
|
]
|
|
1220
1505
|
}
|
|
@@ -1223,8 +1508,8 @@ function FlowNodePanel({
|
|
|
1223
1508
|
] })
|
|
1224
1509
|
] })
|
|
1225
1510
|
] }),
|
|
1226
|
-
/* @__PURE__ */
|
|
1227
|
-
/* @__PURE__ */
|
|
1511
|
+
/* @__PURE__ */ jsxs10("div", { className: "p-4 border-t border-gray-100 dark:border-gray-700 flex gap-2", children: [
|
|
1512
|
+
/* @__PURE__ */ jsxs10(
|
|
1228
1513
|
"button",
|
|
1229
1514
|
{
|
|
1230
1515
|
"data-cv-tooltip": labels.nodePanel.save,
|
|
@@ -1232,13 +1517,13 @@ function FlowNodePanel({
|
|
|
1232
1517
|
onClick: () => onChange(draft),
|
|
1233
1518
|
className: "flex-1 inline-flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700",
|
|
1234
1519
|
children: [
|
|
1235
|
-
/* @__PURE__ */
|
|
1520
|
+
/* @__PURE__ */ jsx11(Save, { size: 14 }),
|
|
1236
1521
|
" ",
|
|
1237
1522
|
labels.nodePanel.save
|
|
1238
1523
|
]
|
|
1239
1524
|
}
|
|
1240
1525
|
),
|
|
1241
|
-
!isStart && /* @__PURE__ */
|
|
1526
|
+
!isStart && /* @__PURE__ */ jsx11(
|
|
1242
1527
|
"button",
|
|
1243
1528
|
{
|
|
1244
1529
|
onClick: () => {
|
|
@@ -1247,26 +1532,27 @@ function FlowNodePanel({
|
|
|
1247
1532
|
"data-cv-tooltip": labels.nodePanel.delete,
|
|
1248
1533
|
"aria-label": labels.nodePanel.delete,
|
|
1249
1534
|
className: "px-3 py-2 text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/30 rounded-lg",
|
|
1250
|
-
children: /* @__PURE__ */
|
|
1535
|
+
children: /* @__PURE__ */ jsx11(Trash2, { size: 14 })
|
|
1251
1536
|
}
|
|
1252
1537
|
),
|
|
1253
|
-
/* @__PURE__ */
|
|
1538
|
+
/* @__PURE__ */ jsx11("button", { "data-cv-tooltip": labels.nodePanel.cancel, "aria-label": labels.nodePanel.cancel, onClick: onClose, className: "px-4 py-2 text-sm text-gray-600 dark:text-gray-300 hover:underline", children: labels.nodePanel.cancel })
|
|
1254
1539
|
] })
|
|
1255
1540
|
] });
|
|
1256
1541
|
}
|
|
1257
1542
|
|
|
1258
1543
|
// src/flows/FlowsWorkspace.tsx
|
|
1259
|
-
import { useCallback, useEffect as
|
|
1544
|
+
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState6 } from "react";
|
|
1260
1545
|
import {
|
|
1261
1546
|
ReactFlow as ReactFlow2,
|
|
1262
1547
|
Background as Background2,
|
|
1263
1548
|
Controls as Controls2,
|
|
1264
1549
|
MiniMap,
|
|
1265
1550
|
MarkerType as MarkerType2,
|
|
1551
|
+
Panel,
|
|
1266
1552
|
applyNodeChanges
|
|
1267
1553
|
} from "@xyflow/react";
|
|
1268
1554
|
import "@xyflow/react/dist/style.css";
|
|
1269
|
-
import { Plus as
|
|
1555
|
+
import { Plus as Plus4, Trash2 as Trash22, LayoutGrid, AlertTriangle as AlertTriangle3, AlertCircle as AlertCircle3, Save as Save2, Undo2, Map as MapIcon, Workflow } from "lucide-react";
|
|
1270
1556
|
|
|
1271
1557
|
// src/flows/flowEditorOps.ts
|
|
1272
1558
|
var NAMESPACE_SEPARATOR = "::";
|
|
@@ -1346,6 +1632,21 @@ function isGraphDirty(working, published) {
|
|
|
1346
1632
|
if (!working || !published) return false;
|
|
1347
1633
|
return JSON.stringify(working) !== JSON.stringify(published);
|
|
1348
1634
|
}
|
|
1635
|
+
function clearConnection(node, handle) {
|
|
1636
|
+
if (handle === "next" || typeof node.next === "string" || node.next === void 0) {
|
|
1637
|
+
return { ...node, next: "" };
|
|
1638
|
+
}
|
|
1639
|
+
if (handle === "__default") {
|
|
1640
|
+
return { ...node, next: { byAnswer: node.next.byAnswer ?? {}, default: "" } };
|
|
1641
|
+
}
|
|
1642
|
+
return {
|
|
1643
|
+
...node,
|
|
1644
|
+
next: {
|
|
1645
|
+
byAnswer: { ...node.next.byAnswer ?? {}, [handle]: "" },
|
|
1646
|
+
default: node.next.default ?? ""
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1349
1650
|
|
|
1350
1651
|
// src/flows/flowCanvasModel.ts
|
|
1351
1652
|
var COLUMN_GAP = 360;
|
|
@@ -1387,7 +1688,10 @@ function computeMergedLayout(params) {
|
|
|
1387
1688
|
nodeById.set(namespaceNodeId(key, node.id), node);
|
|
1388
1689
|
}
|
|
1389
1690
|
}
|
|
1390
|
-
function forwardEdges(
|
|
1691
|
+
function forwardEdges(id) {
|
|
1692
|
+
const node = nodeById.get(id);
|
|
1693
|
+
if (!node) return [];
|
|
1694
|
+
const flowKey = parseNamespacedId(id).flowKey;
|
|
1391
1695
|
const result = [];
|
|
1392
1696
|
for (const { target } of targetsOf(node)) {
|
|
1393
1697
|
if (isCrossFlowTarget(target)) {
|
|
@@ -1400,41 +1704,17 @@ function computeMergedLayout(params) {
|
|
|
1400
1704
|
}
|
|
1401
1705
|
return result;
|
|
1402
1706
|
}
|
|
1403
|
-
const rank = /* @__PURE__ */ new Map();
|
|
1404
1707
|
const primaryGraph = graphs[primaryFlowKey];
|
|
1405
|
-
const
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
const id = queue.shift();
|
|
1411
|
-
const node = nodeById.get(id);
|
|
1412
|
-
if (!node) continue;
|
|
1413
|
-
for (const nextId of forwardEdges(parseNamespacedId(id).flowKey, node)) {
|
|
1414
|
-
if (!rank.has(nextId)) {
|
|
1415
|
-
rank.set(nextId, rank.get(id) + 1);
|
|
1416
|
-
queue.push(nextId);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
const strayRank = Math.max(0, ...rank.values()) + 1;
|
|
1422
|
-
for (const id of nodeById.keys()) {
|
|
1423
|
-
if (!rank.has(id)) rank.set(id, strayRank);
|
|
1424
|
-
}
|
|
1425
|
-
const layers = /* @__PURE__ */ new Map();
|
|
1426
|
-
for (const [id, value] of rank) {
|
|
1427
|
-
const layer = layers.get(value);
|
|
1428
|
-
if (layer) layer.push(id);
|
|
1429
|
-
else layers.set(value, [id]);
|
|
1430
|
-
}
|
|
1708
|
+
const placed = cascadeOrder({
|
|
1709
|
+
rootId: primaryGraph ? namespaceNodeId(primaryFlowKey, primaryGraph.startNodeId) : "",
|
|
1710
|
+
allIds: [...nodeById.keys()],
|
|
1711
|
+
forwardEdges
|
|
1712
|
+
});
|
|
1431
1713
|
const positions = /* @__PURE__ */ new Map();
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
cursorY += estimateNodeHeight(nodeById.get(id)) + ROW_GAP;
|
|
1437
|
-
}
|
|
1714
|
+
let cursorY = 0;
|
|
1715
|
+
for (const [id, { depth }] of [...placed.entries()].sort((a, b) => a[1].order - b[1].order)) {
|
|
1716
|
+
positions.set(id, { x: depth * COLUMN_GAP, y: cursorY });
|
|
1717
|
+
cursorY += estimateNodeHeight(nodeById.get(id)) + ROW_GAP;
|
|
1438
1718
|
}
|
|
1439
1719
|
return positions;
|
|
1440
1720
|
}
|
|
@@ -1465,6 +1745,7 @@ function buildFlowEdges(params) {
|
|
|
1465
1745
|
}
|
|
1466
1746
|
const source = namespaceNodeId(flowKey, nodeId);
|
|
1467
1747
|
const edgeTarget = namespaceNodeId(targetFlowKey, targetNodeId);
|
|
1748
|
+
if (source === edgeTarget) continue;
|
|
1468
1749
|
if (isDefault) {
|
|
1469
1750
|
edges.push({
|
|
1470
1751
|
id: `${source}->${edgeTarget}-default`,
|
|
@@ -1550,16 +1831,42 @@ function newNodeFromSpec(spec, existingIds) {
|
|
|
1550
1831
|
const id = slugifyNodeId("nova_acao", existingIds);
|
|
1551
1832
|
return { id, type: "action", actionKind: spec.actionKind };
|
|
1552
1833
|
}
|
|
1834
|
+
var FREE_SLOT_STEP = 120;
|
|
1835
|
+
function findFreeSlot(params) {
|
|
1836
|
+
const step = params.step ?? FREE_SLOT_STEP;
|
|
1837
|
+
const isOccupied = (candidate) => params.taken.some(
|
|
1838
|
+
(each) => Math.abs(each.x - candidate.x) < NODE_CARD_WIDTH && Math.abs(each.y - candidate.y) < step
|
|
1839
|
+
);
|
|
1840
|
+
let slot = params.desired;
|
|
1841
|
+
for (let attempt = 0; attempt <= params.taken.length && isOccupied(slot); attempt += 1) {
|
|
1842
|
+
slot = { x: slot.x, y: slot.y + step };
|
|
1843
|
+
}
|
|
1844
|
+
return slot;
|
|
1845
|
+
}
|
|
1553
1846
|
|
|
1554
1847
|
// src/flows/FlowsWorkspace.tsx
|
|
1555
|
-
import { Fragment as
|
|
1848
|
+
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1556
1849
|
var RF_NODE_TYPES = {
|
|
1557
1850
|
...flowNodeTypes,
|
|
1558
1851
|
...flowPortalNodeTypes,
|
|
1559
1852
|
...flowGroupHeaderNodeTypes,
|
|
1560
1853
|
...flowGroupFrameNodeTypes
|
|
1561
1854
|
};
|
|
1855
|
+
function legendEdgeSamples(labels) {
|
|
1856
|
+
return [
|
|
1857
|
+
{ color: EDGE_COLOR_LINEAR, label: labels.legendPanel.linear },
|
|
1858
|
+
{ color: EDGE_COLOR_BRANCH, label: labels.legendPanel.branch },
|
|
1859
|
+
{ color: EDGE_COLOR_FALLBACK, dash: "5 4", label: labels.legendPanel.fallback },
|
|
1860
|
+
{ color: EDGE_COLOR_CROSS_FLOW, dash: "3 3", label: labels.legendPanel.crossFlow },
|
|
1861
|
+
{ color: EDGE_COLOR_LIVE, label: labels.legendPanel.live }
|
|
1862
|
+
];
|
|
1863
|
+
}
|
|
1864
|
+
var LEGEND_NODE_SWATCHES = Object.keys(NODE_TYPE_COLOR).map((type) => ({
|
|
1865
|
+
type,
|
|
1866
|
+
className: NODE_TYPE_COLOR[type]
|
|
1867
|
+
}));
|
|
1562
1868
|
var CHAIN_FRAME_PADDING = 36;
|
|
1869
|
+
var QUICK_ADD_COLUMN_GAP = 320;
|
|
1563
1870
|
var FLOW_KEY_PATTERN = /^[a-z0-9_]{2,40}$/;
|
|
1564
1871
|
var EDGE_COLOR_LINEAR = "#94a3b8";
|
|
1565
1872
|
var EDGE_COLOR_BRANCH = "#8b5cf6";
|
|
@@ -1568,7 +1875,7 @@ var EDGE_COLOR_LIVE = "#3b82f6";
|
|
|
1568
1875
|
var EDGE_COLOR_CROSS_FLOW = "#06b6d4";
|
|
1569
1876
|
var BACKGROUND_COLOR_LIGHT2 = "#cbd5e1";
|
|
1570
1877
|
var BACKGROUND_COLOR_DARK2 = "#334155";
|
|
1571
|
-
function styleEdge(spec) {
|
|
1878
|
+
function styleEdge(spec, params) {
|
|
1572
1879
|
const color = spec.crossFlow ? EDGE_COLOR_CROSS_FLOW : spec.kind === "fallback" ? EDGE_COLOR_FALLBACK : spec.live ? EDGE_COLOR_LIVE : spec.kind === "branch" ? EDGE_COLOR_BRANCH : EDGE_COLOR_LINEAR;
|
|
1573
1880
|
const baseWidth = spec.kind === "branch" ? 1.75 : 1.5;
|
|
1574
1881
|
const dash = spec.crossFlow ? "3 3" : spec.kind === "fallback" ? "5 4" : void 0;
|
|
@@ -1578,7 +1885,12 @@ function styleEdge(spec) {
|
|
|
1578
1885
|
source: spec.source,
|
|
1579
1886
|
target: spec.target,
|
|
1580
1887
|
...spec.sourceHandle === void 0 ? {} : { sourceHandle: spec.sourceHandle },
|
|
1581
|
-
type: "
|
|
1888
|
+
type: "flowConnection",
|
|
1889
|
+
reconnectable: "target",
|
|
1890
|
+
data: {
|
|
1891
|
+
disconnectLabel: params.disconnectLabel,
|
|
1892
|
+
onDisconnect: () => params.onDisconnect(spec)
|
|
1893
|
+
},
|
|
1582
1894
|
animated: spec.live,
|
|
1583
1895
|
style: {
|
|
1584
1896
|
stroke: color,
|
|
@@ -1607,23 +1919,24 @@ function FlowsWorkspace({
|
|
|
1607
1919
|
}) {
|
|
1608
1920
|
const labels = useMemo2(() => mergeFlowEditorLabels(labelsOverride), [labelsOverride]);
|
|
1609
1921
|
const isDark = useIsDarkTheme();
|
|
1610
|
-
const [graphs, setGraphs] =
|
|
1611
|
-
const [loadState, setLoadState] =
|
|
1612
|
-
const [livePositions, setLivePositions] =
|
|
1613
|
-
const [viewMode, setViewMode] =
|
|
1614
|
-
const [openFlowKeys, setOpenFlowKeys] =
|
|
1615
|
-
const [hasAutoMerged, setHasAutoMerged] =
|
|
1616
|
-
const [workingGraphs, setWorkingGraphs] =
|
|
1617
|
-
const [editingRef, setEditingRef] =
|
|
1618
|
-
const [saveState, setSaveState] =
|
|
1619
|
-
const [saveErrorMessage, setSaveErrorMessage] =
|
|
1620
|
-
const [showCreateDialog, setShowCreateDialog] =
|
|
1621
|
-
const [showDeleteDialog, setShowDeleteDialog] =
|
|
1622
|
-
const [
|
|
1623
|
-
const [
|
|
1624
|
-
const [
|
|
1625
|
-
const [
|
|
1626
|
-
const [
|
|
1922
|
+
const [graphs, setGraphs] = useState6(void 0);
|
|
1923
|
+
const [loadState, setLoadState] = useState6("loading");
|
|
1924
|
+
const [livePositions, setLivePositions] = useState6(void 0);
|
|
1925
|
+
const [viewMode, setViewMode] = useState6("detail");
|
|
1926
|
+
const [openFlowKeys, setOpenFlowKeys] = useState6([rootFlowKey]);
|
|
1927
|
+
const [hasAutoMerged, setHasAutoMerged] = useState6(false);
|
|
1928
|
+
const [workingGraphs, setWorkingGraphs] = useState6({});
|
|
1929
|
+
const [editingRef, setEditingRef] = useState6(null);
|
|
1930
|
+
const [saveState, setSaveState] = useState6("idle");
|
|
1931
|
+
const [saveErrorMessage, setSaveErrorMessage] = useState6(void 0);
|
|
1932
|
+
const [showCreateDialog, setShowCreateDialog] = useState6(false);
|
|
1933
|
+
const [showDeleteDialog, setShowDeleteDialog] = useState6(false);
|
|
1934
|
+
const [quickAddFrom, setQuickAddFrom] = useState6(null);
|
|
1935
|
+
const [newFlow, setNewFlow] = useState6({ key: "", label: "", showInMenu: false, menuOptionLabel: "" });
|
|
1936
|
+
const [flowMutationState, setFlowMutationState] = useState6({ pending: false });
|
|
1937
|
+
const [rfNodes, setRfNodes] = useState6([]);
|
|
1938
|
+
const [flowInstance, setFlowInstance] = useState6(null);
|
|
1939
|
+
const [pendingFocusNodeId, setPendingFocusNodeId] = useState6(null);
|
|
1627
1940
|
const reloadGraphs = useCallback(async () => {
|
|
1628
1941
|
try {
|
|
1629
1942
|
const loaded = await api.getGraphs();
|
|
@@ -1635,10 +1948,10 @@ function FlowsWorkspace({
|
|
|
1635
1948
|
return void 0;
|
|
1636
1949
|
}
|
|
1637
1950
|
}, [api]);
|
|
1638
|
-
|
|
1951
|
+
useEffect3(() => {
|
|
1639
1952
|
void reloadGraphs();
|
|
1640
1953
|
}, [reloadGraphs]);
|
|
1641
|
-
|
|
1954
|
+
useEffect3(() => {
|
|
1642
1955
|
const fetchLive = api.getLivePositions;
|
|
1643
1956
|
if (!fetchLive) return;
|
|
1644
1957
|
let active = true;
|
|
@@ -1658,12 +1971,12 @@ function FlowsWorkspace({
|
|
|
1658
1971
|
}, [api, livePollIntervalMs]);
|
|
1659
1972
|
const primaryFlowKey = openFlowKeys[0] ?? rootFlowKey;
|
|
1660
1973
|
const primaryGraph = workingGraphs[primaryFlowKey];
|
|
1661
|
-
|
|
1974
|
+
useEffect3(() => {
|
|
1662
1975
|
if (!graphs || hasAutoMerged) return;
|
|
1663
1976
|
setOpenFlowKeys(mergedFlowKeysFrom(rootFlowKey, graphs));
|
|
1664
1977
|
setHasAutoMerged(true);
|
|
1665
1978
|
}, [graphs, hasAutoMerged, rootFlowKey]);
|
|
1666
|
-
|
|
1979
|
+
useEffect3(() => {
|
|
1667
1980
|
if (!graphs) return;
|
|
1668
1981
|
setWorkingGraphs((prev) => {
|
|
1669
1982
|
let changed = false;
|
|
@@ -1740,16 +2053,17 @@ function FlowsWorkspace({
|
|
|
1740
2053
|
() => isMerged ? computeMergedLayout({ openKeys: openFlowKeys, graphs: workingGraphs, primaryFlowKey }) : null,
|
|
1741
2054
|
[isMerged, openFlowKeys, workingGraphs, primaryFlowKey]
|
|
1742
2055
|
);
|
|
1743
|
-
const renderedPositionsRef =
|
|
2056
|
+
const renderedPositionsRef = useRef3(/* @__PURE__ */ new Map());
|
|
1744
2057
|
const derivedNodes = useMemo2(() => {
|
|
1745
2058
|
const allNodes = [];
|
|
1746
2059
|
for (const flowKey of openFlowKeys) {
|
|
1747
2060
|
let resolvePosition2 = function(nodeId) {
|
|
1748
2061
|
if (mergedPositions) {
|
|
1749
|
-
const
|
|
1750
|
-
return renderedPositionsRef.current.get(
|
|
2062
|
+
const nsId2 = namespaceNodeId(flowKey, nodeId);
|
|
2063
|
+
return renderedPositionsRef.current.get(nsId2) ?? mergedPositions.get(nsId2) ?? { x: 0, y: 0 };
|
|
1751
2064
|
}
|
|
1752
|
-
|
|
2065
|
+
const nsId = namespaceNodeId(flowKey, nodeId);
|
|
2066
|
+
return graph.nodes[nodeId]?.position ?? renderedPositionsRef.current.get(nsId) ?? fallbackPositions[nodeId] ?? { x: 0, y: 0 };
|
|
1753
2067
|
};
|
|
1754
2068
|
var resolvePosition = resolvePosition2;
|
|
1755
2069
|
const graph = workingGraphs[flowKey];
|
|
@@ -1779,7 +2093,8 @@ function FlowsWorkspace({
|
|
|
1779
2093
|
isDetached: node.id !== graph.startNodeId && !connectedTargets.has(node.id),
|
|
1780
2094
|
issues: flowIssues,
|
|
1781
2095
|
labels,
|
|
1782
|
-
onSelect: (nodeId) => setEditingRef({ flowKey, nodeId })
|
|
2096
|
+
onSelect: (nodeId) => setEditingRef({ flowKey, nodeId }),
|
|
2097
|
+
onQuickAdd: ({ nodeId, handle, anchor }) => setQuickAddFrom({ flowKey, nodeId, handle, anchor })
|
|
1783
2098
|
}
|
|
1784
2099
|
});
|
|
1785
2100
|
const crossFlowTargets = [...new Set(targetsOf(node).map((edge) => edge.target).filter(isCrossFlowTarget))];
|
|
@@ -1854,17 +2169,53 @@ function FlowsWorkspace({
|
|
|
1854
2169
|
closeFlow,
|
|
1855
2170
|
labels
|
|
1856
2171
|
]);
|
|
2172
|
+
const disconnectEdge = useCallback(
|
|
2173
|
+
(spec) => {
|
|
2174
|
+
const { flowKey, nodeId } = parseNamespacedId(spec.source);
|
|
2175
|
+
updateFlow(flowKey, (graph) => {
|
|
2176
|
+
const node = graph.nodes[nodeId];
|
|
2177
|
+
if (!node) return graph;
|
|
2178
|
+
return { ...graph, nodes: { ...graph.nodes, [nodeId]: clearConnection(node, spec.sourceHandle ?? "next") } };
|
|
2179
|
+
});
|
|
2180
|
+
},
|
|
2181
|
+
[updateFlow]
|
|
2182
|
+
);
|
|
1857
2183
|
const edges = useMemo2(
|
|
1858
|
-
() => buildFlowEdges({ openKeys: openFlowKeys, graphs: workingGraphs, rootFlowKey, livePositions }).map(
|
|
1859
|
-
|
|
2184
|
+
() => buildFlowEdges({ openKeys: openFlowKeys, graphs: workingGraphs, rootFlowKey, livePositions }).map(
|
|
2185
|
+
(spec) => (
|
|
2186
|
+
// Salto entre fluxos desenhado como portal não se desliga daqui: quem manda nele é o `next`
|
|
2187
|
+
// do nó de origem, e o portal é só a caixa que representa o fluxo alvo ausente.
|
|
2188
|
+
styleEdge(spec, { disconnectLabel: labels.quickAdd.disconnect, onDisconnect: disconnectEdge })
|
|
2189
|
+
)
|
|
2190
|
+
),
|
|
2191
|
+
[openFlowKeys, workingGraphs, rootFlowKey, livePositions, labels, disconnectEdge]
|
|
2192
|
+
);
|
|
2193
|
+
const onReconnect = useCallback(
|
|
2194
|
+
(oldEdge, connection) => {
|
|
2195
|
+
const resolved = resolveConnection({
|
|
2196
|
+
connection: {
|
|
2197
|
+
source: connection.source,
|
|
2198
|
+
target: connection.target,
|
|
2199
|
+
sourceHandle: connection.sourceHandle ?? oldEdge.sourceHandle
|
|
2200
|
+
},
|
|
2201
|
+
graphs: workingGraphs
|
|
2202
|
+
});
|
|
2203
|
+
if (!resolved) return;
|
|
2204
|
+
updateFlow(resolved.flowKey, (graph) => {
|
|
2205
|
+
const node = graph.nodes[resolved.nodeId];
|
|
2206
|
+
if (!node) return graph;
|
|
2207
|
+
return { ...graph, nodes: { ...graph.nodes, [resolved.nodeId]: applyConnection(node, resolved) } };
|
|
2208
|
+
});
|
|
2209
|
+
},
|
|
2210
|
+
[workingGraphs, updateFlow]
|
|
1860
2211
|
);
|
|
1861
|
-
|
|
2212
|
+
useEffect3(() => {
|
|
1862
2213
|
setRfNodes(derivedNodes);
|
|
1863
2214
|
for (const node of derivedNodes) {
|
|
1864
2215
|
if (node.type === "flowNode") renderedPositionsRef.current.set(node.id, node.position);
|
|
1865
2216
|
}
|
|
1866
2217
|
}, [derivedNodes]);
|
|
1867
|
-
|
|
2218
|
+
useEffect3(() => {
|
|
1868
2219
|
if (!pendingFocusNodeId || !flowInstance) return;
|
|
1869
2220
|
const target = rfNodes.find((node) => node.id === pendingFocusNodeId);
|
|
1870
2221
|
if (!target) return;
|
|
@@ -1910,6 +2261,37 @@ function FlowsWorkspace({
|
|
|
1910
2261
|
setEditingRef({ flowKey: primaryFlowKey, nodeId: newNode.id });
|
|
1911
2262
|
setPendingFocusNodeId(namespaceNodeId(primaryFlowKey, newNode.id));
|
|
1912
2263
|
}
|
|
2264
|
+
function handleQuickAdd(spec) {
|
|
2265
|
+
const origin = quickAddFrom;
|
|
2266
|
+
setQuickAddFrom(null);
|
|
2267
|
+
if (!origin) return;
|
|
2268
|
+
const originGraph = workingGraphs[origin.flowKey];
|
|
2269
|
+
if (!originGraph) return;
|
|
2270
|
+
const newNode = newNodeFromSpec(spec, new Set(Object.keys(originGraph.nodes)));
|
|
2271
|
+
const originPosition = renderedPositionsRef.current.get(namespaceNodeId(origin.flowKey, origin.nodeId)) ?? originGraph.nodes[origin.nodeId]?.position ?? { x: 0, y: 0 };
|
|
2272
|
+
const taken = openFlowKeys.flatMap(
|
|
2273
|
+
(key) => Object.entries(workingGraphs[key]?.nodes ?? {}).map(
|
|
2274
|
+
([id, node]) => renderedPositionsRef.current.get(namespaceNodeId(key, id)) ?? node.position ?? { x: 0, y: 0 }
|
|
2275
|
+
)
|
|
2276
|
+
);
|
|
2277
|
+
newNode.position = findFreeSlot({
|
|
2278
|
+
desired: { x: originPosition.x + QUICK_ADD_COLUMN_GAP, y: originPosition.y },
|
|
2279
|
+
taken
|
|
2280
|
+
});
|
|
2281
|
+
renderedPositionsRef.current.set(namespaceNodeId(origin.flowKey, newNode.id), newNode.position);
|
|
2282
|
+
updateFlow(origin.flowKey, (graph) => {
|
|
2283
|
+
const sourceNode = graph.nodes[origin.nodeId];
|
|
2284
|
+
if (!sourceNode) return graph;
|
|
2285
|
+
const connected = applyConnection(sourceNode, {
|
|
2286
|
+
flowKey: origin.flowKey,
|
|
2287
|
+
nodeId: origin.nodeId,
|
|
2288
|
+
handle: origin.handle,
|
|
2289
|
+
targetValue: newNode.id
|
|
2290
|
+
});
|
|
2291
|
+
return { ...graph, nodes: { ...graph.nodes, [origin.nodeId]: connected, [newNode.id]: newNode } };
|
|
2292
|
+
});
|
|
2293
|
+
setEditingRef({ flowKey: origin.flowKey, nodeId: newNode.id });
|
|
2294
|
+
}
|
|
1913
2295
|
function handleNodePanelChange(updated) {
|
|
1914
2296
|
if (!editingRef) return;
|
|
1915
2297
|
updateFlow(editingRef.flowKey, (graph) => ({ ...graph, nodes: { ...graph.nodes, [updated.id]: updated } }));
|
|
@@ -2017,17 +2399,17 @@ function FlowsWorkspace({
|
|
|
2017
2399
|
const keyIsValid = FLOW_KEY_PATTERN.test(newFlow.key);
|
|
2018
2400
|
const canCreateFlow = Boolean(api.createFlow);
|
|
2019
2401
|
const canDeleteFlow = Boolean(api.deleteFlow) && primaryFlowKey !== rootFlowKey;
|
|
2020
|
-
return /* @__PURE__ */
|
|
2021
|
-
/* @__PURE__ */
|
|
2022
|
-
/* @__PURE__ */
|
|
2023
|
-
showHeader && /* @__PURE__ */
|
|
2024
|
-
/* @__PURE__ */
|
|
2025
|
-
/* @__PURE__ */
|
|
2402
|
+
return /* @__PURE__ */ jsxs11("div", { className: `space-y-4 h-full flex flex-col ${className ?? ""}`, children: [
|
|
2403
|
+
/* @__PURE__ */ jsx12(TooltipLayer, {}),
|
|
2404
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between flex-wrap gap-3", children: [
|
|
2405
|
+
showHeader && /* @__PURE__ */ jsxs11("div", { className: "min-w-0", children: [
|
|
2406
|
+
/* @__PURE__ */ jsx12("h2", { className: "text-2xl font-bold text-gray-900 dark:text-gray-100", children: labels.workspace.title }),
|
|
2407
|
+
/* @__PURE__ */ jsx12("p", { className: "text-gray-500 dark:text-gray-400 text-sm mt-1", children: labels.workspace.subtitle })
|
|
2026
2408
|
] }),
|
|
2027
|
-
/* @__PURE__ */
|
|
2028
|
-
saveState === "success" && /* @__PURE__ */
|
|
2029
|
-
saveState === "error" && /* @__PURE__ */
|
|
2030
|
-
/* @__PURE__ */
|
|
2409
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center justify-end gap-3 ml-auto", children: [
|
|
2410
|
+
saveState === "success" && /* @__PURE__ */ jsx12("span", { className: "text-sm text-green-600", children: labels.workspace.saveSuccess }),
|
|
2411
|
+
saveState === "error" && /* @__PURE__ */ jsx12("span", { className: "text-sm text-red-600", children: saveErrorMessage ?? labels.workspace.saveError }),
|
|
2412
|
+
/* @__PURE__ */ jsxs11(
|
|
2031
2413
|
"button",
|
|
2032
2414
|
{
|
|
2033
2415
|
"data-cv-tooltip": viewMode === "map" ? labels.flowMap.toggleToDetail : labels.flowMap.toggleToMap,
|
|
@@ -2036,13 +2418,13 @@ function FlowsWorkspace({
|
|
|
2036
2418
|
onClick: () => setViewMode((mode) => mode === "map" ? "detail" : "map"),
|
|
2037
2419
|
className: OUTLINE_BUTTON,
|
|
2038
2420
|
children: [
|
|
2039
|
-
viewMode === "map" ? /* @__PURE__ */
|
|
2421
|
+
viewMode === "map" ? /* @__PURE__ */ jsx12(Workflow, { size: 14, "aria-hidden": "true" }) : /* @__PURE__ */ jsx12(MapIcon, { size: 14, "aria-hidden": "true" }),
|
|
2040
2422
|
viewMode === "map" ? labels.flowMap.toggleToDetail : labels.flowMap.toggleToMap
|
|
2041
2423
|
]
|
|
2042
2424
|
}
|
|
2043
2425
|
),
|
|
2044
|
-
viewMode === "detail" && /* @__PURE__ */
|
|
2045
|
-
/* @__PURE__ */
|
|
2426
|
+
viewMode === "detail" && /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
2427
|
+
/* @__PURE__ */ jsxs11(
|
|
2046
2428
|
"button",
|
|
2047
2429
|
{
|
|
2048
2430
|
type: "button",
|
|
@@ -2052,13 +2434,13 @@ function FlowsWorkspace({
|
|
|
2052
2434
|
"aria-label": labels.workspace.organizeTooltip,
|
|
2053
2435
|
disabled: !primaryGraph,
|
|
2054
2436
|
children: [
|
|
2055
|
-
/* @__PURE__ */
|
|
2437
|
+
/* @__PURE__ */ jsx12(LayoutGrid, { size: 14, "aria-hidden": "true" }),
|
|
2056
2438
|
" ",
|
|
2057
2439
|
labels.workspace.organize
|
|
2058
2440
|
]
|
|
2059
2441
|
}
|
|
2060
2442
|
),
|
|
2061
|
-
/* @__PURE__ */
|
|
2443
|
+
/* @__PURE__ */ jsxs11(
|
|
2062
2444
|
"button",
|
|
2063
2445
|
{
|
|
2064
2446
|
type: "button",
|
|
@@ -2068,13 +2450,13 @@ function FlowsWorkspace({
|
|
|
2068
2450
|
"aria-label": labels.workspace.discardTooltip,
|
|
2069
2451
|
disabled: !isDirty || saveState === "saving",
|
|
2070
2452
|
children: [
|
|
2071
|
-
/* @__PURE__ */
|
|
2453
|
+
/* @__PURE__ */ jsx12(Undo2, { size: 14, "aria-hidden": "true" }),
|
|
2072
2454
|
" ",
|
|
2073
2455
|
labels.workspace.discardChanges
|
|
2074
2456
|
]
|
|
2075
2457
|
}
|
|
2076
2458
|
),
|
|
2077
|
-
/* @__PURE__ */
|
|
2459
|
+
/* @__PURE__ */ jsxs11(
|
|
2078
2460
|
"button",
|
|
2079
2461
|
{
|
|
2080
2462
|
"data-cv-tooltip": labels.workspace.saveGraph,
|
|
@@ -2084,7 +2466,7 @@ function FlowsWorkspace({
|
|
|
2084
2466
|
className: PRIMARY_BUTTON,
|
|
2085
2467
|
disabled: !isDirty || errorCount > 0 || saveState === "saving",
|
|
2086
2468
|
children: [
|
|
2087
|
-
/* @__PURE__ */
|
|
2469
|
+
/* @__PURE__ */ jsx12(Save2, { size: 14, "aria-hidden": "true" }),
|
|
2088
2470
|
saveState === "saving" ? labels.workspace.saving : labels.workspace.saveGraph
|
|
2089
2471
|
]
|
|
2090
2472
|
}
|
|
@@ -2092,9 +2474,9 @@ function FlowsWorkspace({
|
|
|
2092
2474
|
] })
|
|
2093
2475
|
] })
|
|
2094
2476
|
] }),
|
|
2095
|
-
viewMode === "detail" && /* @__PURE__ */
|
|
2096
|
-
/* @__PURE__ */
|
|
2097
|
-
graphs && Object.values(graphs).map((graph) => /* @__PURE__ */
|
|
2477
|
+
viewMode === "detail" && /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
|
|
2478
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
2479
|
+
graphs && Object.values(graphs).map((graph) => /* @__PURE__ */ jsx12(
|
|
2098
2480
|
"button",
|
|
2099
2481
|
{
|
|
2100
2482
|
"data-cv-tooltip": graph.label,
|
|
@@ -2106,7 +2488,7 @@ function FlowsWorkspace({
|
|
|
2106
2488
|
},
|
|
2107
2489
|
graph.key
|
|
2108
2490
|
)),
|
|
2109
|
-
canCreateFlow && /* @__PURE__ */
|
|
2491
|
+
canCreateFlow && /* @__PURE__ */ jsxs11(
|
|
2110
2492
|
"button",
|
|
2111
2493
|
{
|
|
2112
2494
|
"data-cv-tooltip": labels.flowManager.newFlow,
|
|
@@ -2118,15 +2500,15 @@ function FlowsWorkspace({
|
|
|
2118
2500
|
},
|
|
2119
2501
|
className: "inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-xs font-medium border border-dashed border-gray-300 dark:border-gray-600 text-gray-500 dark:text-gray-400 hover:border-blue-400 hover:text-blue-600",
|
|
2120
2502
|
children: [
|
|
2121
|
-
/* @__PURE__ */
|
|
2503
|
+
/* @__PURE__ */ jsx12(Plus4, { size: 12, "aria-hidden": "true" }),
|
|
2122
2504
|
" ",
|
|
2123
2505
|
labels.flowManager.newFlow
|
|
2124
2506
|
]
|
|
2125
2507
|
}
|
|
2126
2508
|
)
|
|
2127
2509
|
] }),
|
|
2128
|
-
/* @__PURE__ */
|
|
2129
|
-
primaryGraph && /* @__PURE__ */
|
|
2510
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
|
|
2511
|
+
primaryGraph && /* @__PURE__ */ jsx12(
|
|
2130
2512
|
FlowPalette,
|
|
2131
2513
|
{
|
|
2132
2514
|
onAdd: handleAddNode,
|
|
@@ -2134,7 +2516,7 @@ function FlowsWorkspace({
|
|
|
2134
2516
|
...actionOptions ? { actionOptions: [...actionOptions] } : {}
|
|
2135
2517
|
}
|
|
2136
2518
|
),
|
|
2137
|
-
primaryGraph && canDeleteFlow && /* @__PURE__ */
|
|
2519
|
+
primaryGraph && canDeleteFlow && /* @__PURE__ */ jsxs11(
|
|
2138
2520
|
"button",
|
|
2139
2521
|
{
|
|
2140
2522
|
"data-cv-tooltip": labels.flowManager.deleteFlow,
|
|
@@ -2146,7 +2528,7 @@ function FlowsWorkspace({
|
|
|
2146
2528
|
},
|
|
2147
2529
|
className: "inline-flex items-center gap-1.5 rounded-lg border border-red-200 dark:border-red-900 px-3 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/30",
|
|
2148
2530
|
children: [
|
|
2149
|
-
/* @__PURE__ */
|
|
2531
|
+
/* @__PURE__ */ jsx12(Trash22, { size: 13, "aria-hidden": "true" }),
|
|
2150
2532
|
" ",
|
|
2151
2533
|
labels.flowManager.deleteFlow
|
|
2152
2534
|
]
|
|
@@ -2154,26 +2536,26 @@ function FlowsWorkspace({
|
|
|
2154
2536
|
)
|
|
2155
2537
|
] })
|
|
2156
2538
|
] }),
|
|
2157
|
-
viewMode === "detail" && (errorCount > 0 || warningCount > 0) && /* @__PURE__ */
|
|
2158
|
-
/* @__PURE__ */
|
|
2539
|
+
viewMode === "detail" && (errorCount > 0 || warningCount > 0) && /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-4 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/60 px-3 py-2 text-xs", children: [
|
|
2540
|
+
/* @__PURE__ */ jsxs11("span", { className: "font-medium text-gray-600 dark:text-gray-300", children: [
|
|
2159
2541
|
labels.validation.title,
|
|
2160
2542
|
":"
|
|
2161
2543
|
] }),
|
|
2162
|
-
errorCount > 0 && /* @__PURE__ */
|
|
2163
|
-
/* @__PURE__ */
|
|
2544
|
+
errorCount > 0 && /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-1 text-red-600 dark:text-red-400 font-medium", children: [
|
|
2545
|
+
/* @__PURE__ */ jsx12(AlertCircle3, { size: 13, "aria-hidden": "true" }),
|
|
2164
2546
|
" ",
|
|
2165
2547
|
labels.validation.errors(errorCount)
|
|
2166
2548
|
] }),
|
|
2167
|
-
warningCount > 0 && /* @__PURE__ */
|
|
2168
|
-
/* @__PURE__ */
|
|
2549
|
+
warningCount > 0 && /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-1 text-amber-600 dark:text-amber-400", children: [
|
|
2550
|
+
/* @__PURE__ */ jsx12(AlertTriangle3, { size: 13, "aria-hidden": "true" }),
|
|
2169
2551
|
" ",
|
|
2170
2552
|
labels.validation.warnings(warningCount)
|
|
2171
2553
|
] })
|
|
2172
2554
|
] }),
|
|
2173
|
-
/* @__PURE__ */
|
|
2174
|
-
loadState === "loading" && /* @__PURE__ */
|
|
2175
|
-
loadState === "error" && /* @__PURE__ */
|
|
2176
|
-
loadState === "ready" && graphs && viewMode === "map" && /* @__PURE__ */
|
|
2555
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex-1 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden relative", children: [
|
|
2556
|
+
loadState === "loading" && /* @__PURE__ */ jsx12("p", { className: "text-center text-gray-400 py-12", children: labels.workspace.loading }),
|
|
2557
|
+
loadState === "error" && /* @__PURE__ */ jsx12("p", { className: "text-center text-red-500 py-12", children: labels.workspace.loadError }),
|
|
2558
|
+
loadState === "ready" && graphs && viewMode === "map" && /* @__PURE__ */ jsx12(
|
|
2177
2559
|
FlowMapCanvas,
|
|
2178
2560
|
{
|
|
2179
2561
|
graphs,
|
|
@@ -2185,28 +2567,59 @@ function FlowsWorkspace({
|
|
|
2185
2567
|
}
|
|
2186
2568
|
}
|
|
2187
2569
|
),
|
|
2188
|
-
loadState === "ready" && viewMode === "detail" && primaryGraph && /* @__PURE__ */
|
|
2570
|
+
loadState === "ready" && viewMode === "detail" && primaryGraph && /* @__PURE__ */ jsxs11(
|
|
2189
2571
|
ReactFlow2,
|
|
2190
2572
|
{
|
|
2191
2573
|
nodes: rfNodes,
|
|
2192
2574
|
edges,
|
|
2193
2575
|
nodeTypes: RF_NODE_TYPES,
|
|
2576
|
+
edgeTypes: flowEdgeTypes,
|
|
2194
2577
|
onNodesChange,
|
|
2195
2578
|
onNodeDragStop,
|
|
2196
2579
|
onConnect,
|
|
2580
|
+
onReconnect,
|
|
2197
2581
|
onInit: setFlowInstance,
|
|
2198
2582
|
fitView: true,
|
|
2199
2583
|
proOptions: { hideAttribution: true },
|
|
2200
2584
|
colorMode: isDark ? "dark" : "light",
|
|
2201
2585
|
children: [
|
|
2202
|
-
/* @__PURE__ */
|
|
2203
|
-
/* @__PURE__ */
|
|
2204
|
-
/* @__PURE__ */
|
|
2586
|
+
/* @__PURE__ */ jsx12(Background2, { color: isDark ? BACKGROUND_COLOR_DARK2 : BACKGROUND_COLOR_LIGHT2 }),
|
|
2587
|
+
/* @__PURE__ */ jsx12(Controls2, {}),
|
|
2588
|
+
/* @__PURE__ */ jsx12(Panel, { position: "top-right", children: /* @__PURE__ */ jsx12(
|
|
2589
|
+
FlowLegend,
|
|
2590
|
+
{
|
|
2591
|
+
labels,
|
|
2592
|
+
edgeSamples: legendEdgeSamples(labels),
|
|
2593
|
+
nodeSwatches: LEGEND_NODE_SWATCHES
|
|
2594
|
+
}
|
|
2595
|
+
) }),
|
|
2596
|
+
/* @__PURE__ */ jsx12(MiniMap, { pannable: true, zoomable: true, className: "!bg-white dark:!bg-gray-800" })
|
|
2597
|
+
]
|
|
2598
|
+
}
|
|
2599
|
+
)
|
|
2600
|
+
] }),
|
|
2601
|
+
quickAddFrom && /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
2602
|
+
/* @__PURE__ */ jsx12("div", { className: "fixed inset-0 z-40", onClick: () => setQuickAddFrom(null) }),
|
|
2603
|
+
/* @__PURE__ */ jsxs11(
|
|
2604
|
+
"div",
|
|
2605
|
+
{
|
|
2606
|
+
className: "fixed z-50 w-64 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-lg py-1",
|
|
2607
|
+
style: { left: quickAddFrom.anchor.x + 12, top: quickAddFrom.anchor.y },
|
|
2608
|
+
children: [
|
|
2609
|
+
/* @__PURE__ */ jsx12("p", { className: "px-3 py-1.5 text-xs font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500", children: labels.quickAdd.title }),
|
|
2610
|
+
/* @__PURE__ */ jsx12(
|
|
2611
|
+
FlowPaletteMenu,
|
|
2612
|
+
{
|
|
2613
|
+
onSelect: handleQuickAdd,
|
|
2614
|
+
labels,
|
|
2615
|
+
...actionOptions ? { actionOptions: [...actionOptions] } : {}
|
|
2616
|
+
}
|
|
2617
|
+
)
|
|
2205
2618
|
]
|
|
2206
2619
|
}
|
|
2207
2620
|
)
|
|
2208
2621
|
] }),
|
|
2209
|
-
editingNode && editingGraph && /* @__PURE__ */
|
|
2622
|
+
editingNode && editingGraph && /* @__PURE__ */ jsx12(
|
|
2210
2623
|
FlowNodePanel,
|
|
2211
2624
|
{
|
|
2212
2625
|
graph: editingGraph,
|
|
@@ -2221,11 +2634,11 @@ function FlowsWorkspace({
|
|
|
2221
2634
|
},
|
|
2222
2635
|
`${editingRef?.flowKey}:${editingRef?.nodeId}`
|
|
2223
2636
|
),
|
|
2224
|
-
showCreateDialog && canCreateFlow && /* @__PURE__ */
|
|
2225
|
-
/* @__PURE__ */
|
|
2226
|
-
/* @__PURE__ */
|
|
2227
|
-
/* @__PURE__ */
|
|
2228
|
-
/* @__PURE__ */
|
|
2637
|
+
showCreateDialog && canCreateFlow && /* @__PURE__ */ jsxs11(FlowDialog, { title: labels.flowManager.createTitle, onClose: () => setShowCreateDialog(false), children: [
|
|
2638
|
+
/* @__PURE__ */ jsxs11("div", { className: "space-y-3", children: [
|
|
2639
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
2640
|
+
/* @__PURE__ */ jsx12("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.flowManager.label }),
|
|
2641
|
+
/* @__PURE__ */ jsx12(
|
|
2229
2642
|
"input",
|
|
2230
2643
|
{
|
|
2231
2644
|
value: newFlow.label,
|
|
@@ -2234,9 +2647,9 @@ function FlowsWorkspace({
|
|
|
2234
2647
|
}
|
|
2235
2648
|
)
|
|
2236
2649
|
] }),
|
|
2237
|
-
/* @__PURE__ */
|
|
2238
|
-
/* @__PURE__ */
|
|
2239
|
-
/* @__PURE__ */
|
|
2650
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
2651
|
+
/* @__PURE__ */ jsx12("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.flowManager.key }),
|
|
2652
|
+
/* @__PURE__ */ jsx12(
|
|
2240
2653
|
"input",
|
|
2241
2654
|
{
|
|
2242
2655
|
value: newFlow.key,
|
|
@@ -2244,10 +2657,10 @@ function FlowsWorkspace({
|
|
|
2244
2657
|
className: `mt-1 ${DIALOG_INPUT}`
|
|
2245
2658
|
}
|
|
2246
2659
|
),
|
|
2247
|
-
/* @__PURE__ */
|
|
2660
|
+
/* @__PURE__ */ jsx12("p", { className: "text-[11px] text-gray-400 mt-1", children: newFlow.key && !keyIsValid ? labels.flowManager.keyInvalid : labels.flowManager.keyHint })
|
|
2248
2661
|
] }),
|
|
2249
|
-
/* @__PURE__ */
|
|
2250
|
-
/* @__PURE__ */
|
|
2662
|
+
/* @__PURE__ */ jsxs11("label", { className: "flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200", children: [
|
|
2663
|
+
/* @__PURE__ */ jsx12(
|
|
2251
2664
|
"input",
|
|
2252
2665
|
{
|
|
2253
2666
|
type: "checkbox",
|
|
@@ -2257,9 +2670,9 @@ function FlowsWorkspace({
|
|
|
2257
2670
|
),
|
|
2258
2671
|
labels.flowManager.showInMenu
|
|
2259
2672
|
] }),
|
|
2260
|
-
newFlow.showInMenu && /* @__PURE__ */
|
|
2261
|
-
/* @__PURE__ */
|
|
2262
|
-
/* @__PURE__ */
|
|
2673
|
+
newFlow.showInMenu && /* @__PURE__ */ jsxs11("div", { children: [
|
|
2674
|
+
/* @__PURE__ */ jsx12("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.flowManager.menuOptionLabel }),
|
|
2675
|
+
/* @__PURE__ */ jsx12(
|
|
2263
2676
|
"input",
|
|
2264
2677
|
{
|
|
2265
2678
|
value: newFlow.menuOptionLabel,
|
|
@@ -2269,11 +2682,11 @@ function FlowsWorkspace({
|
|
|
2269
2682
|
}
|
|
2270
2683
|
)
|
|
2271
2684
|
] }),
|
|
2272
|
-
flowMutationState.error && /* @__PURE__ */
|
|
2685
|
+
flowMutationState.error && /* @__PURE__ */ jsx12("p", { className: "text-xs text-red-600", children: flowMutationState.error })
|
|
2273
2686
|
] }),
|
|
2274
|
-
/* @__PURE__ */
|
|
2275
|
-
/* @__PURE__ */
|
|
2276
|
-
/* @__PURE__ */
|
|
2687
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex justify-end gap-2 mt-4", children: [
|
|
2688
|
+
/* @__PURE__ */ jsx12("button", { "data-cv-tooltip": labels.nodePanel.cancel, "aria-label": labels.nodePanel.cancel, type: "button", className: OUTLINE_BUTTON, onClick: () => setShowCreateDialog(false), children: labels.nodePanel.cancel }),
|
|
2689
|
+
/* @__PURE__ */ jsx12(
|
|
2277
2690
|
"button",
|
|
2278
2691
|
{
|
|
2279
2692
|
"data-cv-tooltip": labels.flowManager.create,
|
|
@@ -2287,12 +2700,12 @@ function FlowsWorkspace({
|
|
|
2287
2700
|
)
|
|
2288
2701
|
] })
|
|
2289
2702
|
] }),
|
|
2290
|
-
showDeleteDialog && canDeleteFlow && /* @__PURE__ */
|
|
2291
|
-
/* @__PURE__ */
|
|
2292
|
-
flowMutationState.error && /* @__PURE__ */
|
|
2293
|
-
/* @__PURE__ */
|
|
2294
|
-
/* @__PURE__ */
|
|
2295
|
-
/* @__PURE__ */
|
|
2703
|
+
showDeleteDialog && canDeleteFlow && /* @__PURE__ */ jsxs11(FlowDialog, { title: labels.flowManager.deleteFlow, onClose: () => setShowDeleteDialog(false), children: [
|
|
2704
|
+
/* @__PURE__ */ jsx12("p", { className: "text-sm text-gray-600 dark:text-gray-300", children: primaryGraph ? labels.flowManager.deleteConfirm(primaryGraph.label) : "" }),
|
|
2705
|
+
flowMutationState.error && /* @__PURE__ */ jsx12("p", { className: "text-xs text-red-600 mt-2", children: flowMutationState.error }),
|
|
2706
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex justify-end gap-2 mt-4", children: [
|
|
2707
|
+
/* @__PURE__ */ jsx12("button", { "data-cv-tooltip": labels.nodePanel.cancel, "aria-label": labels.nodePanel.cancel, type: "button", className: OUTLINE_BUTTON, onClick: () => setShowDeleteDialog(false), children: labels.nodePanel.cancel }),
|
|
2708
|
+
/* @__PURE__ */ jsxs11(
|
|
2296
2709
|
"button",
|
|
2297
2710
|
{
|
|
2298
2711
|
"data-cv-tooltip": labels.flowManager.deleteFlow,
|
|
@@ -2302,7 +2715,7 @@ function FlowsWorkspace({
|
|
|
2302
2715
|
onClick: () => void handleDeleteFlow(),
|
|
2303
2716
|
disabled: flowMutationState.pending,
|
|
2304
2717
|
children: [
|
|
2305
|
-
/* @__PURE__ */
|
|
2718
|
+
/* @__PURE__ */ jsx12(Trash22, { size: 13, "aria-hidden": "true" }),
|
|
2306
2719
|
" ",
|
|
2307
2720
|
labels.flowManager.deleteFlow
|
|
2308
2721
|
]
|
|
@@ -2317,9 +2730,9 @@ function FlowDialog({
|
|
|
2317
2730
|
onClose,
|
|
2318
2731
|
children
|
|
2319
2732
|
}) {
|
|
2320
|
-
return /* @__PURE__ */
|
|
2321
|
-
/* @__PURE__ */
|
|
2322
|
-
/* @__PURE__ */
|
|
2733
|
+
return /* @__PURE__ */ jsxs11("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 px-4", children: [
|
|
2734
|
+
/* @__PURE__ */ jsx12("div", { className: "absolute inset-0", onClick: onClose, "aria-hidden": "true" }),
|
|
2735
|
+
/* @__PURE__ */ jsxs11(
|
|
2323
2736
|
"div",
|
|
2324
2737
|
{
|
|
2325
2738
|
role: "dialog",
|
|
@@ -2327,7 +2740,7 @@ function FlowDialog({
|
|
|
2327
2740
|
"aria-label": title,
|
|
2328
2741
|
className: "relative w-full max-w-md rounded-2xl bg-white dark:bg-gray-800 p-5 shadow-xl",
|
|
2329
2742
|
children: [
|
|
2330
|
-
/* @__PURE__ */
|
|
2743
|
+
/* @__PURE__ */ jsx12("h3", { className: "text-base font-semibold text-gray-900 dark:text-gray-100 mb-3", children: title }),
|
|
2331
2744
|
children
|
|
2332
2745
|
]
|
|
2333
2746
|
}
|
|
@@ -2339,18 +2752,22 @@ export {
|
|
|
2339
2752
|
CONDITION_OPERATORS,
|
|
2340
2753
|
CROSS_FLOW_PREFIX,
|
|
2341
2754
|
DEFAULT_FLOW_EDITOR_LABELS,
|
|
2755
|
+
FlowConnectionEdge,
|
|
2342
2756
|
FlowGroupFrame,
|
|
2343
2757
|
FlowGroupHeader,
|
|
2758
|
+
FlowLegend,
|
|
2344
2759
|
FlowMapCanvas,
|
|
2345
2760
|
FlowMapNode,
|
|
2346
2761
|
FlowNodeCard,
|
|
2347
2762
|
FlowNodePanel,
|
|
2348
2763
|
FlowPalette,
|
|
2764
|
+
FlowPaletteMenu,
|
|
2349
2765
|
FlowPortalNode,
|
|
2350
2766
|
FlowWhatsAppPreview,
|
|
2351
2767
|
FlowsWorkspace,
|
|
2352
2768
|
GROUP_HEADER_NODE_ID,
|
|
2353
2769
|
NODE_CARD_WIDTH,
|
|
2770
|
+
PASS_THROUGH_ACTION_KINDS,
|
|
2354
2771
|
WHATSAPP_LIMITS,
|
|
2355
2772
|
applyConnection,
|
|
2356
2773
|
buildFlowEdges,
|
|
@@ -2365,6 +2782,7 @@ export {
|
|
|
2365
2782
|
detachedNodeIds,
|
|
2366
2783
|
estimateNodeHeight,
|
|
2367
2784
|
findCollectionChains,
|
|
2785
|
+
flowEdgeTypes,
|
|
2368
2786
|
flowGroupFrameNodeTypes,
|
|
2369
2787
|
flowGroupHeaderNodeTypes,
|
|
2370
2788
|
flowMapNodeTypes,
|