@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.
- package/dist/{chunk-UZJBYD5O.js → chunk-GY472G6E.js} +56 -5
- package/dist/flows/index.d.ts +73 -2
- package/dist/flows/index.js +115 -77
- package/dist/index.d.ts +6 -2
- package/dist/index.js +68 -18
- package/dist/preview/index.d.ts +2 -2
- package/dist/preview/index.js +1 -1
- package/dist/{types-BfINicc-.d.ts → types-De5aN-E_.d.ts} +11 -0
- package/package.json +2 -2
- package/src/DocumentsLibrary.tsx +58 -3
- package/src/buildOutput.test.ts +79 -0
- package/src/documents/DocumentsWorkspace.tsx +52 -2
- package/src/documents/labels.ts +4 -0
- package/src/flows/FlowsWorkspace.tsx +56 -98
- package/src/flows/flowEditorOps.test.ts +241 -0
- package/src/flows/flowEditorOps.ts +177 -0
- package/src/flows/flowGraph.ts +1 -1
- package/src/flows/index.ts +16 -0
- package/src/flows/workspaceContract.test.ts +95 -0
- package/src/providers/types.ts +11 -0
|
@@ -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);
|
package/dist/flows/index.d.ts
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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 };
|
package/dist/flows/index.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
1389
|
+
if (targetGraph) result.push(namespaceNodeId(targetFlowKey, targetGraph.startNodeId));
|
|
1327
1390
|
} else if (workingGraphs[flowKey]?.nodes[target]) {
|
|
1328
|
-
result.push(
|
|
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 ?
|
|
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(
|
|
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 =
|
|
1404
|
-
const edgeTarget =
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 } =
|
|
1843
|
+
const { flowKey, nodeId } = parseNamespacedId(node.id);
|
|
1803
1844
|
const openFlow = openFlows.find((flow) => flow.key === flowKey);
|
|
1804
1845
|
if (!openFlow) return;
|
|
1805
|
-
const
|
|
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
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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-
|
|
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-
|
|
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;
|