@adatechnology/conversations-ui 0.1.0-rc.27 → 0.1.0-rc.29

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.
@@ -1966,9 +1966,9 @@ function ConversationDocumentsPanel({
1966
1966
  }
1967
1967
 
1968
1968
  // src/DocumentsLibrary.tsx
1969
- import { useEffect as useEffect4, useState as useState11 } from "react";
1970
- import { ArrowUpDown as ArrowUpDown2, Bot as Bot2, Download as Download2, Eye as Eye2, MessageSquare, Users as Users2 } from "lucide-react";
1971
- import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
1969
+ import { useEffect as useEffect4, useRef as useRef5, useState as useState11 } from "react";
1970
+ import { ArrowUpDown as ArrowUpDown2, Bot as Bot2, Download as Download2, Eye as Eye2, MessageSquare, Upload, Users as Users2 } from "lucide-react";
1971
+ import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
1972
1972
  var DEFAULT_DOCUMENTS_LIBRARY_LABELS = {
1973
1973
  title: "Documentos",
1974
1974
  searchPlaceholder: "Buscar por nome do arquivo ou telefone",
@@ -1985,6 +1985,8 @@ var DEFAULT_DOCUMENTS_LIBRARY_LABELS = {
1985
1985
  sortMostRecent: "Mais recentes",
1986
1986
  sortOldest: "Mais antigos",
1987
1987
  clearFilters: "Limpar filtros",
1988
+ upload: "Enviar documento",
1989
+ uploadError: "N\xE3o foi poss\xEDvel enviar o arquivo.",
1988
1990
  total: (count) => `${count} arquivo${count === 1 ? "" : "s"}`,
1989
1991
  page: (current, last) => `${current} / ${last}`
1990
1992
  };
@@ -2007,9 +2009,14 @@ function DocumentsLibrary({
2007
2009
  const [total, setTotal] = useState11(0);
2008
2010
  const [loading, setLoading] = useState11(false);
2009
2011
  const [failed, setFailed] = useState11(false);
2012
+ const [uploading, setUploading] = useState11(false);
2013
+ const [uploadFailed, setUploadFailed] = useState11(false);
2014
+ const [reloadToken, setReloadToken] = useState11(0);
2015
+ const fileInputRef = useRef5(null);
2010
2016
  const hasFilters = search !== "" || sourceFilter !== DOCUMENT_SOURCE_FILTER.ALL || sortDirection !== "desc";
2011
2017
  const lastPage = Math.max(1, Math.ceil(total / perPage));
2012
2018
  const fetchAll = context?.api.getAllDocuments;
2019
+ const uploadDocument = context?.api.uploadDocument;
2013
2020
  useEffect4(() => {
2014
2021
  if (!fetchAll) return;
2015
2022
  let active = true;
@@ -2033,7 +2040,7 @@ function DocumentsLibrary({
2033
2040
  return () => {
2034
2041
  active = false;
2035
2042
  };
2036
- }, [fetchAll, search, sourceFilter, sortDirection, page, perPage]);
2043
+ }, [fetchAll, search, sourceFilter, sortDirection, page, perPage, reloadToken]);
2037
2044
  function applyFilter(change) {
2038
2045
  change();
2039
2046
  setPage(1);
@@ -2042,6 +2049,19 @@ function DocumentsLibrary({
2042
2049
  const url = await context?.api.getDocumentUrl(uploadId, disposition);
2043
2050
  if (url) window.open(url, "_blank", "noopener,noreferrer");
2044
2051
  }
2052
+ async function handleUpload(file) {
2053
+ if (!uploadDocument) return;
2054
+ setUploading(true);
2055
+ setUploadFailed(false);
2056
+ try {
2057
+ await uploadDocument(file);
2058
+ setReloadToken((token) => token + 1);
2059
+ } catch {
2060
+ setUploadFailed(true);
2061
+ } finally {
2062
+ setUploading(false);
2063
+ }
2064
+ }
2045
2065
  if (!fetchAll) return null;
2046
2066
  return /* @__PURE__ */ jsxs12("div", { className: cn("space-y-3", classNames?.root, className), children: [
2047
2067
  /* @__PURE__ */ jsx17("h2", { className: cn("text-lg font-semibold", classNames?.title), children: labels.title }),
@@ -2099,10 +2119,41 @@ function DocumentsLibrary({
2099
2119
  className: cn("cv-header-action", classNames?.clearButton),
2100
2120
  children: labels.clearFilters
2101
2121
  }
2102
- ) : null
2122
+ ) : null,
2123
+ uploadDocument ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
2124
+ /* @__PURE__ */ jsx17(
2125
+ "input",
2126
+ {
2127
+ ref: fileInputRef,
2128
+ type: "file",
2129
+ hidden: true,
2130
+ onChange: (event) => {
2131
+ const file = event.target.files?.[0];
2132
+ event.target.value = "";
2133
+ if (file) void handleUpload(file);
2134
+ }
2135
+ }
2136
+ ),
2137
+ /* @__PURE__ */ jsxs12(
2138
+ "button",
2139
+ {
2140
+ "data-cv-tooltip": labels.upload,
2141
+ "aria-label": labels.upload,
2142
+ type: "button",
2143
+ onClick: () => fileInputRef.current?.click(),
2144
+ disabled: uploading,
2145
+ className: "cv-header-action ml-auto inline-flex items-center gap-1 disabled:opacity-40",
2146
+ children: [
2147
+ /* @__PURE__ */ jsx17(Upload, { size: 14, "aria-hidden": "true" }),
2148
+ labels.upload
2149
+ ]
2150
+ }
2151
+ )
2152
+ ] }) : null
2103
2153
  ] }),
2104
2154
  loading ? /* @__PURE__ */ jsx17("p", { className: cn("text-sm text-gray-500", classNames?.status), children: labels.loading }) : null,
2105
2155
  failed ? /* @__PURE__ */ jsx17("p", { role: "alert", className: cn("text-sm text-red-600 dark:text-red-400", classNames?.status), children: labels.failure }) : null,
2156
+ uploadFailed ? /* @__PURE__ */ jsx17("p", { role: "alert", className: cn("text-sm text-red-600 dark:text-red-400", classNames?.status), children: labels.uploadError }) : null,
2106
2157
  !loading && !failed && documents.length === 0 ? /* @__PURE__ */ jsx17("p", { className: cn("text-sm text-gray-500", classNames?.status), children: hasFilters ? labels.noResults : labels.empty }) : null,
2107
2158
  /* @__PURE__ */ jsx17("ul", { className: cn("space-y-2", classNames?.list), children: documents.map((document2) => {
2108
2159
  const isFromCustomer = !TEAM_SOURCES2.has(document2.source);
@@ -64,7 +64,7 @@ type CollectionChain = {
64
64
  actionNodeId: string;
65
65
  };
66
66
  declare function findCollectionChains(graph: FlowGraphData): CollectionChain[];
67
- declare function slugifyNodeId(label: string, existing: Set<string>): string;
67
+ declare function slugifyNodeId(label: string, existing: ReadonlySet<string>): string;
68
68
 
69
69
  interface FlowEditorLabels {
70
70
  legend: Record<FlowNodeType, string>;
@@ -383,4 +383,75 @@ interface FlowsWorkspaceProps {
383
383
  */
384
384
  declare function FlowsWorkspace({ api, rootFlowKey, labels: labelsOverride, actionOptions, renderMediaPicker, livePollIntervalMs, className, }: FlowsWorkspaceProps): react.JSX.Element;
385
385
 
386
- export { BUILT_IN_ACTION_KINDS, CONDITION_OPERATORS, CROSS_FLOW_PREFIX, type CollectionChain, type CreateFlowInput, DEFAULT_FLOW_EDITOR_LABELS, type FlowEditorLabels, FlowGroupFrame, type FlowGroupFrameData, FlowGroupHeader, type FlowGroupHeaderData, type FlowLivePosition, FlowMapCanvas, type FlowMapCanvasProps, FlowMapNode, type FlowMapNodeData, FlowNodeCard, type FlowNodeCardData, FlowNodePanel, type FlowNodePanelProps, FlowPalette, type FlowPaletteActionOption, type FlowPaletteProps, FlowPortalNode, type FlowPortalNodeData, type FlowValidationLabels, FlowWhatsAppPreview, type FlowWhatsAppPreviewProps, FlowsWorkspace, type FlowsWorkspaceApi, type FlowsWorkspaceProps, type GraphIssue, NODE_CARD_WIDTH, type NewNodeSpec, WHATSAPP_LIMITS, computeAutoLayout, computeFlowMapLayout, crossFlowKey, crossFlowTargetsOf, estimateNodeHeight, findCollectionChains, flowGroupFrameNodeTypes, flowGroupHeaderNodeTypes, flowMapNodeTypes, flowNodeTypes, flowPortalNodeTypes, isCrossFlowTarget, mergeFlowEditorLabels, nodeLabel, rendersAsButtons, slugifyNodeId, targetsOf, validateGraph };
386
+ /**
387
+ * Copyright (c) 2026 Ada Technology. MIT License.
388
+ *
389
+ * Operações puras de grafo que o editor de fluxo precisa, e que viviam soltas dentro da página de
390
+ * 973 linhas do financiamento.
391
+ *
392
+ * Puras e separadas do hook de propósito: são a parte que dá para testar sem navegador, sem React e
393
+ * sem estado — e três delas (`removeNodeAndCleanRefs`, `resolveConnection`, `mergedFlowKeysFrom`)
394
+ * decidem o que acontece com o fluxo que alguém desenhou. Errar ali não dá erro; dá aresta apontando
395
+ * para nó que não existe mais, ou fluxo que some do canvas.
396
+ */
397
+
398
+ /**
399
+ * Id de nó no canvas mesclado: vários fluxos dividem o mesmo espaço, e `boas-vindas` pode existir em
400
+ * dois deles. Sem o prefixo, arrastar um card moveria o homônimo do outro fluxo.
401
+ */
402
+ declare function namespaceNodeId(flowKey: string, nodeId: string): string;
403
+ declare function parseNamespacedId(value: string): {
404
+ flowKey: string;
405
+ nodeId: string;
406
+ };
407
+ /**
408
+ * Apaga o nó E as referências a ele.
409
+ *
410
+ * A limpeza não é cortesia: uma aresta apontando para nó inexistente faz o motor do bot parar a
411
+ * conversa no meio, e o sintoma aparece para o cliente, não para quem editou.
412
+ */
413
+ declare function removeNodeAndCleanRefs(nodes: Readonly<Record<string, FlowNodeData>>, removedId: string): Record<string, FlowNodeData>;
414
+ /**
415
+ * O fecho transitivo dos fluxos alcançáveis a partir de um — é o conjunto que o canvas abre junto.
416
+ *
417
+ * BFS e não recursão: fluxo que referencia a si mesmo (menu que volta ao menu) é comum, e recursão
418
+ * ingênua estouraria a pilha no caso mais banal que existe.
419
+ */
420
+ declare function mergedFlowKeysFrom(rootKey: string, graphs: Readonly<Record<string, FlowGraphData>>): readonly string[];
421
+ type ConnectionRequest = {
422
+ readonly source: string;
423
+ readonly target: string;
424
+ readonly sourceHandle?: string | null | undefined;
425
+ };
426
+ type ResolvedConnection = {
427
+ readonly flowKey: string;
428
+ readonly nodeId: string;
429
+ readonly handle: string;
430
+ /** O que gravar no `next`: id de nó local, ou `flow:<key>` quando atravessa fluxo. */
431
+ readonly targetValue: string;
432
+ };
433
+ /**
434
+ * Traduz um arraste de aresta no valor que vai para o `next` — e recusa o que o motor do bot não
435
+ * sabe executar.
436
+ *
437
+ * A regra que não é óbvia: conectar num nó de OUTRO fluxo só funciona se for o nó inicial dele,
438
+ * porque o motor só sabe pular para o começo de um fluxo, não para um nó do meio. Conectar no meio
439
+ * devolve `undefined` — recusa silenciosa é melhor que gravar um salto que o bot vai ignorar em
440
+ * produção, deixando a conversa parada sem ninguém entender por quê.
441
+ */
442
+ declare function resolveConnection(params: {
443
+ readonly connection: ConnectionRequest;
444
+ readonly graphs: Readonly<Record<string, FlowGraphData>>;
445
+ }): ResolvedConnection | undefined;
446
+ /** Aplica a conexão resolvida no nó. `next` string para saída única, objeto para ramificação. */
447
+ declare function applyConnection(node: FlowNodeData, resolved: ResolvedConnection): FlowNodeData;
448
+ /**
449
+ * Um fluxo está sujo quando o rascunho difere do publicado.
450
+ *
451
+ * Comparação estrutural por JSON: é grosseira, e é suficiente porque o grafo é dado serializável sem
452
+ * ordem significativa de chave — o servidor devolve o que gravou. Comparar campo a campo daria a
453
+ * mesma resposta com mais código para errar.
454
+ */
455
+ declare function isGraphDirty(working: FlowGraphData | undefined, published: FlowGraphData | undefined): boolean;
456
+
457
+ export { BUILT_IN_ACTION_KINDS, CONDITION_OPERATORS, CROSS_FLOW_PREFIX, type CollectionChain, type ConnectionRequest, type CreateFlowInput, DEFAULT_FLOW_EDITOR_LABELS, type FlowEditorLabels, FlowGroupFrame, type FlowGroupFrameData, FlowGroupHeader, type FlowGroupHeaderData, type FlowLivePosition, FlowMapCanvas, type FlowMapCanvasProps, FlowMapNode, type FlowMapNodeData, FlowNodeCard, type FlowNodeCardData, FlowNodePanel, type FlowNodePanelProps, FlowPalette, type FlowPaletteActionOption, type FlowPaletteProps, FlowPortalNode, type FlowPortalNodeData, type FlowValidationLabels, FlowWhatsAppPreview, type FlowWhatsAppPreviewProps, FlowsWorkspace, type FlowsWorkspaceApi, type FlowsWorkspaceProps, type GraphIssue, NODE_CARD_WIDTH, type NewNodeSpec, type ResolvedConnection, WHATSAPP_LIMITS, applyConnection, computeAutoLayout, computeFlowMapLayout, crossFlowKey, crossFlowTargetsOf, estimateNodeHeight, findCollectionChains, flowGroupFrameNodeTypes, flowGroupHeaderNodeTypes, flowMapNodeTypes, flowNodeTypes, flowPortalNodeTypes, isCrossFlowTarget, isGraphDirty, mergeFlowEditorLabels, mergedFlowKeysFrom, namespaceNodeId, nodeLabel, parseNamespacedId, removeNodeAndCleanRefs, rendersAsButtons, resolveConnection, slugifyNodeId, targetsOf, validateGraph };
@@ -1267,6 +1267,87 @@ import {
1267
1267
  } from "@xyflow/react";
1268
1268
  import "@xyflow/react/dist/style.css";
1269
1269
  import { Plus as Plus3, Trash2 as Trash22, LayoutGrid, AlertTriangle as AlertTriangle3, AlertCircle as AlertCircle3, Save as Save2, Undo2, Map as MapIcon, Workflow } from "lucide-react";
1270
+
1271
+ // src/flows/flowEditorOps.ts
1272
+ var NAMESPACE_SEPARATOR = "::";
1273
+ function namespaceNodeId(flowKey, nodeId) {
1274
+ return `${flowKey}${NAMESPACE_SEPARATOR}${nodeId}`;
1275
+ }
1276
+ function parseNamespacedId(value) {
1277
+ const index = value.indexOf(NAMESPACE_SEPARATOR);
1278
+ if (index === -1) return { flowKey: "", nodeId: value };
1279
+ return { flowKey: value.slice(0, index), nodeId: value.slice(index + NAMESPACE_SEPARATOR.length) };
1280
+ }
1281
+ function removeNodeAndCleanRefs(nodes, removedId) {
1282
+ const remaining = {};
1283
+ for (const [id, node] of Object.entries(nodes)) {
1284
+ if (id === removedId) continue;
1285
+ remaining[id] = { ...node, next: cleanNext(node.next, removedId) };
1286
+ }
1287
+ return remaining;
1288
+ }
1289
+ function cleanNext(next, removedId) {
1290
+ if (next === void 0) return void 0;
1291
+ if (typeof next === "string") return next === removedId ? "" : next;
1292
+ const byAnswer = {};
1293
+ for (const [answer, target] of Object.entries(next.byAnswer ?? {})) {
1294
+ byAnswer[answer] = target === removedId ? "" : target;
1295
+ }
1296
+ return { byAnswer, default: next.default === removedId ? "" : next.default ?? "" };
1297
+ }
1298
+ function mergedFlowKeysFrom(rootKey, graphs) {
1299
+ const visited = /* @__PURE__ */ new Set();
1300
+ const queue = [rootKey];
1301
+ while (queue.length > 0) {
1302
+ const key = queue.shift();
1303
+ if (visited.has(key) || !graphs[key]) continue;
1304
+ visited.add(key);
1305
+ for (const target of crossFlowTargetsOf(graphs[key])) {
1306
+ if (!visited.has(target) && graphs[target]) queue.push(target);
1307
+ }
1308
+ }
1309
+ return [...visited];
1310
+ }
1311
+ function resolveConnection(params) {
1312
+ const { source, target, sourceHandle } = params.connection;
1313
+ if (!source || !target || source === target) return void 0;
1314
+ const sourceRef = parseNamespacedId(source);
1315
+ const targetRef = parseNamespacedId(target);
1316
+ let targetValue;
1317
+ if (targetRef.flowKey === sourceRef.flowKey) {
1318
+ targetValue = targetRef.nodeId;
1319
+ } else {
1320
+ const targetGraph = params.graphs[targetRef.flowKey];
1321
+ if (!targetGraph || targetGraph.startNodeId !== targetRef.nodeId) return void 0;
1322
+ targetValue = `${CROSS_FLOW_PREFIX}${targetRef.flowKey}`;
1323
+ }
1324
+ return {
1325
+ flowKey: sourceRef.flowKey,
1326
+ nodeId: sourceRef.nodeId,
1327
+ handle: sourceHandle ?? "next",
1328
+ targetValue
1329
+ };
1330
+ }
1331
+ function applyConnection(node, resolved) {
1332
+ const currentNext = typeof node.next === "object" && node.next ? node.next : void 0;
1333
+ if (resolved.handle === "next") return { ...node, next: resolved.targetValue };
1334
+ if (resolved.handle === "__default") {
1335
+ return { ...node, next: { byAnswer: currentNext?.byAnswer ?? {}, default: resolved.targetValue } };
1336
+ }
1337
+ return {
1338
+ ...node,
1339
+ next: {
1340
+ byAnswer: { ...currentNext?.byAnswer ?? {}, [resolved.handle]: resolved.targetValue },
1341
+ default: currentNext?.default ?? ""
1342
+ }
1343
+ };
1344
+ }
1345
+ function isGraphDirty(working, published) {
1346
+ if (!working || !published) return false;
1347
+ return JSON.stringify(working) !== JSON.stringify(published);
1348
+ }
1349
+
1350
+ // src/flows/FlowsWorkspace.tsx
1270
1351
  import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1271
1352
  var RF_NODE_TYPES = {
1272
1353
  ...flowNodeTypes,
@@ -1277,7 +1358,6 @@ var RF_NODE_TYPES = {
1277
1358
  var CHAIN_FRAME_PADDING = 36;
1278
1359
  var COLUMN_GAP = 360;
1279
1360
  var ROW_GAP = 40;
1280
- var NS_SEP = "::";
1281
1361
  var FLOW_KEY_PATTERN = /^[a-z0-9_]{2,40}$/;
1282
1362
  var EDGE_COLOR_LINEAR = "#94a3b8";
1283
1363
  var EDGE_COLOR_BRANCH = "#8b5cf6";
@@ -1289,25 +1369,8 @@ var BACKGROUND_COLOR_DARK2 = "#334155";
1289
1369
  function portalNodeId(sourceId, target) {
1290
1370
  return `__portal__${sourceId}__${target}`;
1291
1371
  }
1292
- function ns(flowKey, nodeId) {
1293
- return `${flowKey}${NS_SEP}${nodeId}`;
1294
- }
1295
- function parseNs(id) {
1296
- const index = id.indexOf(NS_SEP);
1297
- return index === -1 ? { flowKey: "", nodeId: id } : { flowKey: id.slice(0, index), nodeId: id.slice(index + NS_SEP.length) };
1298
- }
1299
1372
  function autoMergeAll(rootKey, graphsSource) {
1300
- const visited = /* @__PURE__ */ new Set();
1301
- const queue = [rootKey];
1302
- while (queue.length > 0) {
1303
- const key = queue.shift();
1304
- if (visited.has(key) || !graphsSource[key]) continue;
1305
- visited.add(key);
1306
- for (const target of crossFlowTargetsOf(graphsSource[key])) {
1307
- if (!visited.has(target) && graphsSource[target]) queue.push(target);
1308
- }
1309
- }
1310
- return [...visited].map((key) => ({ key, offset: { x: 0, y: 0 } }));
1373
+ return mergedFlowKeysFrom(rootKey, graphsSource).map((key) => ({ key, offset: { x: 0, y: 0 } }));
1311
1374
  }
1312
1375
  function computeMergedLayout(openFlows, workingGraphs, primaryFlowKey) {
1313
1376
  const openKeys = new Set(openFlows.map((flow) => flow.key));
@@ -1315,7 +1378,7 @@ function computeMergedLayout(openFlows, workingGraphs, primaryFlowKey) {
1315
1378
  for (const { key } of openFlows) {
1316
1379
  const graph = workingGraphs[key];
1317
1380
  if (!graph) continue;
1318
- for (const node of Object.values(graph.nodes)) nodeByNsId.set(ns(key, node.id), node);
1381
+ for (const node of Object.values(graph.nodes)) nodeByNsId.set(namespaceNodeId(key, node.id), node);
1319
1382
  }
1320
1383
  function forwardEdges(flowKey, node) {
1321
1384
  const result = [];
@@ -1323,16 +1386,16 @@ function computeMergedLayout(openFlows, workingGraphs, primaryFlowKey) {
1323
1386
  if (isCrossFlowTarget(target)) {
1324
1387
  const targetFlowKey = crossFlowKey(target);
1325
1388
  const targetGraph = openKeys.has(targetFlowKey) ? workingGraphs[targetFlowKey] : void 0;
1326
- if (targetGraph) result.push(ns(targetFlowKey, targetGraph.startNodeId));
1389
+ if (targetGraph) result.push(namespaceNodeId(targetFlowKey, targetGraph.startNodeId));
1327
1390
  } else if (workingGraphs[flowKey]?.nodes[target]) {
1328
- result.push(ns(flowKey, target));
1391
+ result.push(namespaceNodeId(flowKey, target));
1329
1392
  }
1330
1393
  }
1331
1394
  return result;
1332
1395
  }
1333
1396
  const rank = /* @__PURE__ */ new Map();
1334
1397
  const primaryGraph = workingGraphs[primaryFlowKey];
1335
- const rootId = primaryGraph ? ns(primaryFlowKey, primaryGraph.startNodeId) : void 0;
1398
+ const rootId = primaryGraph ? namespaceNodeId(primaryFlowKey, primaryGraph.startNodeId) : void 0;
1336
1399
  if (rootId && nodeByNsId.has(rootId)) {
1337
1400
  rank.set(rootId, 0);
1338
1401
  const queue = [rootId];
@@ -1340,7 +1403,7 @@ function computeMergedLayout(openFlows, workingGraphs, primaryFlowKey) {
1340
1403
  const id = queue.shift();
1341
1404
  const node = nodeByNsId.get(id);
1342
1405
  if (!node) continue;
1343
- for (const nextId of forwardEdges(parseNs(id).flowKey, node)) {
1406
+ for (const nextId of forwardEdges(parseNamespacedId(id).flowKey, node)) {
1344
1407
  if (!rank.has(nextId)) {
1345
1408
  rank.set(nextId, rank.get(id) + 1);
1346
1409
  queue.push(nextId);
@@ -1387,6 +1450,7 @@ function buildAllEdges(openFlows, workingGraphs, rootFlowKey, livePositions) {
1387
1450
  for (const [id, node] of Object.entries(graph.nodes)) {
1388
1451
  const isLive = (liveCounts[id] ?? 0) > 0;
1389
1452
  for (const { target, optionId, isDefault } of targetsOf(node)) {
1453
+ if (!target) continue;
1390
1454
  const crossFlow = isCrossFlowTarget(target);
1391
1455
  let targetFlowKey = flowKey;
1392
1456
  let rawTargetId = target;
@@ -1400,8 +1464,8 @@ function buildAllEdges(openFlows, workingGraphs, rootFlowKey, livePositions) {
1400
1464
  rawTargetId = portalNodeId(id, target);
1401
1465
  }
1402
1466
  }
1403
- const source = ns(flowKey, id);
1404
- const edgeTarget = ns(targetFlowKey, rawTargetId);
1467
+ const source = namespaceNodeId(flowKey, id);
1468
+ const edgeTarget = namespaceNodeId(targetFlowKey, rawTargetId);
1405
1469
  if (optionId === void 0 && !isDefault) {
1406
1470
  const color = crossFlow ? EDGE_COLOR_CROSS_FLOW : isLive ? EDGE_COLOR_LIVE : EDGE_COLOR_LINEAR;
1407
1471
  edges.push({
@@ -1468,29 +1532,6 @@ function newNodeFromSpec(spec, existingIds) {
1468
1532
  const id = slugifyNodeId("nova_acao", existingIds);
1469
1533
  return { id, type: "action", actionKind: spec.actionKind };
1470
1534
  }
1471
- function removeNodeAndCleanRefs(nodes, nodeId) {
1472
- const { [nodeId]: _removed, ...rest } = nodes;
1473
- return Object.fromEntries(
1474
- Object.entries(rest).map(([id, node]) => {
1475
- if (!node.next) return [id, node];
1476
- if (typeof node.next === "string") {
1477
- return [id, node.next === nodeId ? { ...node, next: void 0 } : node];
1478
- }
1479
- return [
1480
- id,
1481
- {
1482
- ...node,
1483
- next: {
1484
- byAnswer: Object.fromEntries(
1485
- Object.entries(node.next.byAnswer).map(([key, value]) => [key, value === nodeId ? "" : value])
1486
- ),
1487
- default: node.next.default === nodeId ? "" : node.next.default
1488
- }
1489
- }
1490
- ];
1491
- })
1492
- );
1493
- }
1494
1535
  function extractErrorMessage(error) {
1495
1536
  if (error instanceof Error) return error.message;
1496
1537
  return void 0;
@@ -1667,7 +1708,7 @@ function FlowsWorkspace({
1667
1708
  for (const { key: flowKey, offset } of openFlows) {
1668
1709
  let resolvePosition2 = function(nodeId) {
1669
1710
  if (mergedPositions) {
1670
- const nsId = ns(flowKey, nodeId);
1711
+ const nsId = namespaceNodeId(flowKey, nodeId);
1671
1712
  return renderedPositionsRef.current.get(nsId) ?? mergedPositions.get(nsId) ?? { x: 0, y: 0 };
1672
1713
  }
1673
1714
  const local = graph.nodes[nodeId]?.position ?? fallbackPositions[nodeId] ?? { x: 0, y: 0 };
@@ -1689,7 +1730,7 @@ function FlowsWorkspace({
1689
1730
  for (const node of Object.values(graph.nodes)) {
1690
1731
  const position = resolvePosition2(node.id);
1691
1732
  allNodes.push({
1692
- id: ns(flowKey, node.id),
1733
+ id: namespaceNodeId(flowKey, node.id),
1693
1734
  type: "flowNode",
1694
1735
  position,
1695
1736
  draggable: true,
@@ -1709,7 +1750,7 @@ function FlowsWorkspace({
1709
1750
  const targetFlowKey = crossFlowKey(target);
1710
1751
  if (openFlows.some((flow) => flow.key === targetFlowKey)) return;
1711
1752
  allNodes.push({
1712
- id: ns(flowKey, portalNodeId(node.id, target)),
1753
+ id: namespaceNodeId(flowKey, portalNodeId(node.id, target)),
1713
1754
  type: "flowPortal",
1714
1755
  draggable: false,
1715
1756
  selectable: false,
@@ -1729,7 +1770,7 @@ function FlowsWorkspace({
1729
1770
  const minY = Math.min(...positions.map((point) => point.y));
1730
1771
  const maxY = Math.max(...positions.map((point) => point.y)) + estimateNodeHeight(graph.nodes[chain.actionNodeId]);
1731
1772
  allNodes.push({
1732
- id: ns(flowKey, `__chain__${chain.actionNodeId}`),
1773
+ id: namespaceNodeId(flowKey, `__chain__${chain.actionNodeId}`),
1733
1774
  type: "flowGroupFrame",
1734
1775
  draggable: false,
1735
1776
  selectable: false,
@@ -1747,7 +1788,7 @@ function FlowsWorkspace({
1747
1788
  if (!isPrimary) {
1748
1789
  const startPosition = resolvePosition2(graph.startNodeId);
1749
1790
  allNodes.push({
1750
- id: ns(flowKey, "__group_header__"),
1791
+ id: namespaceNodeId(flowKey, "__group_header__"),
1751
1792
  type: "flowGroupHeader",
1752
1793
  draggable: false,
1753
1794
  selectable: false,
@@ -1799,10 +1840,11 @@ function FlowsWorkspace({
1799
1840
  const onNodeDragStop = useCallback(
1800
1841
  (_event, node) => {
1801
1842
  renderedPositionsRef.current.set(node.id, node.position);
1802
- const { flowKey, nodeId } = parseNs(node.id);
1843
+ const { flowKey, nodeId } = parseNamespacedId(node.id);
1803
1844
  const openFlow = openFlows.find((flow) => flow.key === flowKey);
1804
1845
  if (!openFlow) return;
1805
- const localPosition = { x: node.position.x - openFlow.offset.x, y: node.position.y - openFlow.offset.y };
1846
+ const drawnWithOffset = openFlows.length === 1;
1847
+ const localPosition = drawnWithOffset ? { x: node.position.x - openFlow.offset.x, y: node.position.y - openFlow.offset.y } : node.position;
1806
1848
  updateFlow(
1807
1849
  flowKey,
1808
1850
  (graph) => graph.nodes[nodeId] ? { ...graph, nodes: { ...graph.nodes, [nodeId]: { ...graph.nodes[nodeId], position: localPosition } } } : graph
@@ -1812,26 +1854,15 @@ function FlowsWorkspace({
1812
1854
  );
1813
1855
  const onConnect = useCallback(
1814
1856
  (connection) => {
1815
- const { source, sourceHandle, target } = connection;
1816
- if (!source || !target || source === target) return;
1817
- const sourceRef = parseNs(source);
1818
- const targetRef = parseNs(target);
1819
- let targetValue;
1820
- if (targetRef.flowKey === sourceRef.flowKey) {
1821
- targetValue = targetRef.nodeId;
1822
- } else {
1823
- const targetGraph = workingGraphs[targetRef.flowKey];
1824
- if (!targetGraph || targetGraph.startNodeId !== targetRef.nodeId) return;
1825
- targetValue = `${CROSS_FLOW_PREFIX}${targetRef.flowKey}`;
1826
- }
1827
- updateFlow(sourceRef.flowKey, (graph) => {
1828
- const node = graph.nodes[sourceRef.nodeId];
1857
+ const resolved = resolveConnection({
1858
+ connection: { source: connection.source, target: connection.target, sourceHandle: connection.sourceHandle },
1859
+ graphs: workingGraphs
1860
+ });
1861
+ if (!resolved) return;
1862
+ updateFlow(resolved.flowKey, (graph) => {
1863
+ const node = graph.nodes[resolved.nodeId];
1829
1864
  if (!node) return graph;
1830
- const handle = sourceHandle ?? "next";
1831
- const currentByAnswer = typeof node.next === "object" && node.next ? node.next.byAnswer : {};
1832
- const currentDefault = typeof node.next === "object" && node.next ? node.next.default : "";
1833
- const updatedNode = handle === "next" ? { ...node, next: targetValue } : handle === "__default" ? { ...node, next: { byAnswer: currentByAnswer, default: targetValue } } : { ...node, next: { byAnswer: { ...currentByAnswer, [handle]: targetValue }, default: currentDefault } };
1834
- return { ...graph, nodes: { ...graph.nodes, [sourceRef.nodeId]: updatedNode } };
1865
+ return { ...graph, nodes: { ...graph.nodes, [resolved.nodeId]: applyConnection(node, resolved) } };
1835
1866
  });
1836
1867
  },
1837
1868
  [workingGraphs, updateFlow]
@@ -1843,7 +1874,7 @@ function FlowsWorkspace({
1843
1874
  newNode.position = { x: 0, y: maxY + 170 };
1844
1875
  updateFlow(primaryFlowKey, (graph) => ({ ...graph, nodes: { ...graph.nodes, [newNode.id]: newNode } }));
1845
1876
  setEditingRef({ flowKey: primaryFlowKey, nodeId: newNode.id });
1846
- setPendingFocusNodeId(ns(primaryFlowKey, newNode.id));
1877
+ setPendingFocusNodeId(namespaceNodeId(primaryFlowKey, newNode.id));
1847
1878
  }
1848
1879
  function handleNodePanelChange(updated) {
1849
1880
  if (!editingRef) return;
@@ -1865,7 +1896,7 @@ function FlowsWorkspace({
1865
1896
  ...graph,
1866
1897
  nodes: Object.fromEntries(
1867
1898
  Object.entries(graph.nodes).map(([id, node]) => {
1868
- const position = positions2.get(ns(key, id));
1899
+ const position = positions2.get(namespaceNodeId(key, id));
1869
1900
  return [
1870
1901
  id,
1871
1902
  position ? { ...node, position: { x: position.x - offset.x, y: position.y - offset.y } } : node
@@ -2289,6 +2320,7 @@ export {
2289
2320
  FlowsWorkspace,
2290
2321
  NODE_CARD_WIDTH,
2291
2322
  WHATSAPP_LIMITS,
2323
+ applyConnection,
2292
2324
  computeAutoLayout,
2293
2325
  computeFlowMapLayout,
2294
2326
  crossFlowKey,
@@ -2301,9 +2333,15 @@ export {
2301
2333
  flowNodeTypes,
2302
2334
  flowPortalNodeTypes,
2303
2335
  isCrossFlowTarget,
2336
+ isGraphDirty,
2304
2337
  mergeFlowEditorLabels,
2338
+ mergedFlowKeysFrom,
2339
+ namespaceNodeId,
2305
2340
  nodeLabel,
2341
+ parseNamespacedId,
2342
+ removeNodeAndCleanRefs,
2306
2343
  rendersAsButtons,
2344
+ resolveConnection,
2307
2345
  slugifyNodeId,
2308
2346
  targetsOf,
2309
2347
  validateGraph
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import react__default, { ReactNode, CSSProperties, UIEvent, FormEvent, RefObject } from 'react';
3
- import { G as MessagePayload, N as ResolveMediaUrl, z as InteractiveSelection, J as MessageTranscription, x as InteractivePayload, r as ConversationsFeatures, o as ConversationSummary, j as ConversationChannel, L as ListConversationsParams, q as ConversationsApi, S as SSEProvider, T as TranscriptionMode, B as ListDocumentsParams, k as ConversationDocument, p as ConversationTemplate, f as ChannelFilter, g as ChannelFilterOption } from './types-BfINicc-.js';
4
- export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, C as CHANNEL_CAPABILITIES, c as CHANNEL_FILTER_ALL, d as CONVERSATION_CHANNEL, e as ChannelCapabilities, h as CompanyDocument, i as CompanyDocumentPage, l as ConversationDocumentPage, m as ConversationEventSource, n as ConversationPage, s as ConversationsTheme, t as ConversationsUIConfig, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, u as DEFAULT_CONVERSATION_CHANNEL, v as DEFAULT_MAX_RECORDING_MILLISECONDS, F as FormatContactHandleParams, H as HANDLE_KIND, w as HandleKind, I as InteractiveOption, y as InteractiveSection, M as MediaRenderer, E as MediaRendererProps, R as REOPEN_MECHANISM, K as ReopenMechanism, O as TranscriptionStatus, P as capabilitiesOf, Q as channelFiltersFor, U as contactFlag, V as formatContactHandle } from './types-BfINicc-.js';
3
+ import { G as MessagePayload, N as ResolveMediaUrl, z as InteractiveSelection, J as MessageTranscription, x as InteractivePayload, r as ConversationsFeatures, o as ConversationSummary, j as ConversationChannel, L as ListConversationsParams, q as ConversationsApi, S as SSEProvider, T as TranscriptionMode, B as ListDocumentsParams, k as ConversationDocument, p as ConversationTemplate, f as ChannelFilter, g as ChannelFilterOption } from './types-De5aN-E_.js';
4
+ export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, C as CHANNEL_CAPABILITIES, c as CHANNEL_FILTER_ALL, d as CONVERSATION_CHANNEL, e as ChannelCapabilities, h as CompanyDocument, i as CompanyDocumentPage, l as ConversationDocumentPage, m as ConversationEventSource, n as ConversationPage, s as ConversationsTheme, t as ConversationsUIConfig, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, u as DEFAULT_CONVERSATION_CHANNEL, v as DEFAULT_MAX_RECORDING_MILLISECONDS, F as FormatContactHandleParams, H as HANDLE_KIND, w as HandleKind, I as InteractiveOption, y as InteractiveSection, M as MediaRenderer, E as MediaRendererProps, R as REOPEN_MECHANISM, K as ReopenMechanism, O as TranscriptionStatus, P as capabilitiesOf, Q as channelFiltersFor, U as contactFlag, V as formatContactHandle } from './types-De5aN-E_.js';
5
5
 
6
6
  interface MessageBubbleProps {
7
7
  message: MessagePayload;
@@ -727,6 +727,8 @@ interface DocumentsLibraryLabels {
727
727
  sortMostRecent: string;
728
728
  sortOldest: string;
729
729
  clearFilters: string;
730
+ upload: string;
731
+ uploadError: string;
730
732
  total: (count: number) => string;
731
733
  page: (current: number, last: number) => string;
732
734
  }
@@ -835,6 +837,8 @@ interface DocumentsWorkspaceLabels {
835
837
  readonly bulkDownloadZip: string;
836
838
  readonly bulkRemove: (count: number) => string;
837
839
  readonly bulkRemoveConfirm: (count: number) => string;
840
+ readonly upload: string;
841
+ readonly uploadError: string;
838
842
  readonly columnFilename: string;
839
843
  readonly columnContact: string;
840
844
  readonly columnType: string;