@umodeapporg/ui 0.22.2 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/assets/umode-ui.css +105 -0
  2. package/dist/components/Form/ControlledDrawer.js +1 -1
  3. package/dist/components/Form/ControlledDrawer.js.map +1 -1
  4. package/dist/components/Search/filters/FilterFieldControl.js +3 -0
  5. package/dist/components/Search/filters/FilterFieldControl.js.map +1 -1
  6. package/dist/components/Search/filters/FiltersPanel.js +1 -0
  7. package/dist/components/Search/filters/FiltersPanel.js.map +1 -1
  8. package/dist/components/UI/Flow/FlowBlockNode.d.ts +35 -0
  9. package/dist/components/UI/Flow/FlowBlockNode.js +84 -0
  10. package/dist/components/UI/Flow/FlowBlockNode.js.map +1 -0
  11. package/dist/components/UI/Flow/FlowCanvas.d.ts +22 -0
  12. package/dist/components/UI/Flow/FlowCanvas.js +263 -0
  13. package/dist/components/UI/Flow/FlowCanvas.js.map +1 -0
  14. package/dist/components/UI/Flow/FlowConnectionEdge.d.ts +10 -0
  15. package/dist/components/UI/Flow/FlowConnectionEdge.js +54 -0
  16. package/dist/components/UI/Flow/FlowConnectionEdge.js.map +1 -0
  17. package/dist/components/UI/Flow/FlowEditorProvider.d.ts +17 -0
  18. package/dist/components/UI/Flow/FlowEditorProvider.js +27 -0
  19. package/dist/components/UI/Flow/FlowEditorProvider.js.map +1 -0
  20. package/dist/components/UI/Flow/FlowInspector.d.ts +11 -0
  21. package/dist/components/UI/Flow/FlowInspector.js +124 -0
  22. package/dist/components/UI/Flow/FlowInspector.js.map +1 -0
  23. package/dist/components/UI/Flow/FlowIssueList.d.ts +8 -0
  24. package/dist/components/UI/Flow/FlowIssueList.js +55 -0
  25. package/dist/components/UI/Flow/FlowIssueList.js.map +1 -0
  26. package/dist/components/UI/Flow/FlowLaneMap.d.ts +30 -0
  27. package/dist/components/UI/Flow/FlowLaneMap.js +206 -0
  28. package/dist/components/UI/Flow/FlowLaneMap.js.map +1 -0
  29. package/dist/components/UI/Flow/FlowNodeShell.d.ts +20 -0
  30. package/dist/components/UI/Flow/FlowNodeShell.js +81 -0
  31. package/dist/components/UI/Flow/FlowNodeShell.js.map +1 -0
  32. package/dist/components/UI/Flow/FlowPalette.d.ts +7 -0
  33. package/dist/components/UI/Flow/FlowPalette.js +90 -0
  34. package/dist/components/UI/Flow/FlowPalette.js.map +1 -0
  35. package/dist/components/UI/Flow/FlowResizablePanel.d.ts +20 -0
  36. package/dist/components/UI/Flow/FlowResizablePanel.js +117 -0
  37. package/dist/components/UI/Flow/FlowResizablePanel.js.map +1 -0
  38. package/dist/components/UI/Flow/FlowZoomControls.d.ts +12 -0
  39. package/dist/components/UI/Flow/FlowZoomControls.js +53 -0
  40. package/dist/components/UI/Flow/FlowZoomControls.js.map +1 -0
  41. package/dist/components/UI/Flow/flowBlockRegistry.d.ts +12 -0
  42. package/dist/components/UI/Flow/flowBlockRegistry.js +15 -0
  43. package/dist/components/UI/Flow/flowBlockRegistry.js.map +1 -0
  44. package/dist/components/UI/Flow/flowConstants.d.ts +2 -0
  45. package/dist/components/UI/Flow/flowConstants.js +5 -0
  46. package/dist/components/UI/Flow/flowConstants.js.map +1 -0
  47. package/dist/components/UI/Flow/flowEditorStore.d.ts +44 -0
  48. package/dist/components/UI/Flow/flowEditorStore.js +258 -0
  49. package/dist/components/UI/Flow/flowEditorStore.js.map +1 -0
  50. package/dist/components/UI/Flow/flowGraph.d.ts +32 -0
  51. package/dist/components/UI/Flow/flowGraph.js +156 -0
  52. package/dist/components/UI/Flow/flowGraph.js.map +1 -0
  53. package/dist/components/UI/Flow/flowTypes.d.ts +74 -0
  54. package/dist/components/UI/Flow/flowTypes.js +2 -0
  55. package/dist/components/UI/Flow/flowTypes.js.map +1 -0
  56. package/dist/components/UI/Flow/index.d.ts +14 -0
  57. package/dist/components/UI/Flow/index.js +43 -0
  58. package/dist/components/UI/Flow/index.js.map +1 -0
  59. package/dist/components/UI/Flow/useFlowLabels.d.ts +10 -0
  60. package/dist/components/UI/Flow/useFlowLabels.js +51 -0
  61. package/dist/components/UI/Flow/useFlowLabels.js.map +1 -0
  62. package/dist/locales/en/ui.json.d.ts +67 -0
  63. package/dist/locales/en/ui.json.js +3 -0
  64. package/dist/locales/en/ui.json.js.map +1 -1
  65. package/dist/locales/index.d.ts +134 -0
  66. package/dist/locales/pt/ui.json.d.ts +67 -0
  67. package/dist/locales/pt/ui.json.js +3 -0
  68. package/dist/locales/pt/ui.json.js.map +1 -1
  69. package/package.json +10 -1
@@ -0,0 +1,32 @@
1
+ import { IFlowBlockRegistry } from './flowBlockRegistry';
2
+ import { IFlowGraph, IFlowIssue, IFlowNode, IFlowPosition, TFlowPortId } from './flowTypes';
3
+ /** The flow reads left to right: one level per column, siblings stacked in the column. */
4
+ export declare const FLOW_LEVEL_GAP = 336;
5
+ export declare const FLOW_SIBLING_GAP = 176;
6
+ export declare const triggerNodesOf: (graph: IFlowGraph, registry: IFlowBlockRegistry) => Array<IFlowNode>;
7
+ /** Node ids reachable from `startNodeId` following edges, the start included. */
8
+ export declare const reachableFrom: (graph: IFlowGraph, startNodeId: string) => Set<string>;
9
+ /** The node ids of the first cycle found, in order, or null when the graph has none. */
10
+ export declare const findCycle: (graph: IFlowGraph) => Array<string> | null;
11
+ export interface IFlowConnectionCandidate {
12
+ source: string;
13
+ sourcePort: TFlowPortId;
14
+ target: string;
15
+ }
16
+ /**
17
+ * The V0 shape is a tree with no fan-out: one input per block, one edge per output
18
+ * port, no edge into a trigger and no cycle. Keeping this rule here lets the canvas
19
+ * refuse a bad connection while it is being drawn, not only when publishing.
20
+ */
21
+ export declare const canConnect: (graph: IFlowGraph, registry: IFlowBlockRegistry, candidate: IFlowConnectionCandidate) => boolean;
22
+ /** The structural rules of the V0 shape, plus each block's own `validate`. */
23
+ export declare const validateFlowGraph: (graph: IFlowGraph, registry: IFlowBlockRegistry) => Array<IFlowIssue>;
24
+ export declare const hasBlockingIssues: (issues: Array<IFlowIssue>) => boolean;
25
+ /**
26
+ * Lays the tree out by levels from the trigger: each level one column to the right,
27
+ * siblings stacked in the column. Used only for a flow with no saved positions (one born from a rule) or when
28
+ * the user asks for it; a position the user arranged is data and is never overwritten
29
+ * implicitly.
30
+ */
31
+ export declare const layoutFlowByLevels: (graph: IFlowGraph, registry: IFlowBlockRegistry) => Record<string, IFlowPosition>;
32
+ export declare const hasSavedPositions: (graph: IFlowGraph) => boolean;
@@ -0,0 +1,156 @@
1
+ const FLOW_LEVEL_GAP = 336;
2
+ const FLOW_SIBLING_GAP = 176;
3
+ const nodesOf = (graph) => Array.isArray(graph?.nodes) ? graph.nodes : [];
4
+ const edgesOf = (graph) => Array.isArray(graph?.edges) ? graph.edges : [];
5
+ const outgoingByNode = (graph) => {
6
+ const outgoing = /* @__PURE__ */ new Map();
7
+ for (const edge of edgesOf(graph)) {
8
+ outgoing.set(edge.source, [...outgoing.get(edge.source) ?? [], edge]);
9
+ }
10
+ return outgoing;
11
+ };
12
+ const triggerNodesOf = (graph, registry) => nodesOf(graph).filter((node) => registry.get(node.type)?.category === "trigger");
13
+ const reachableFrom = (graph, startNodeId) => {
14
+ const outgoing = outgoingByNode(graph);
15
+ const visited = /* @__PURE__ */ new Set();
16
+ const pending = [startNodeId];
17
+ while (pending.length > 0) {
18
+ const nodeId = pending.pop();
19
+ if (nodeId === void 0 || visited.has(nodeId)) continue;
20
+ visited.add(nodeId);
21
+ for (const edge of outgoing.get(nodeId) ?? []) pending.push(edge.target);
22
+ }
23
+ return visited;
24
+ };
25
+ const findCycle = (graph) => {
26
+ const outgoing = outgoingByNode(graph);
27
+ const state = /* @__PURE__ */ new Map();
28
+ const path = [];
29
+ const visit = (nodeId) => {
30
+ state.set(nodeId, "visiting");
31
+ path.push(nodeId);
32
+ for (const edge of outgoing.get(nodeId) ?? []) {
33
+ const targetState = state.get(edge.target);
34
+ if (targetState === "visiting") return path.slice(path.indexOf(edge.target));
35
+ if (targetState === void 0) {
36
+ const cycle = visit(edge.target);
37
+ if (cycle) return cycle;
38
+ }
39
+ }
40
+ path.pop();
41
+ state.set(nodeId, "done");
42
+ return null;
43
+ };
44
+ for (const node of nodesOf(graph)) {
45
+ if (state.has(node.nodeId)) continue;
46
+ const cycle = visit(node.nodeId);
47
+ if (cycle) return cycle;
48
+ }
49
+ return null;
50
+ };
51
+ const canConnect = (graph, registry, candidate) => {
52
+ const { source, sourcePort, target } = candidate;
53
+ if (!source || !target || source === target) return false;
54
+ const nodes = nodesOf(graph);
55
+ const sourceNode = nodes.find((node) => node.nodeId === source);
56
+ const targetNode = nodes.find((node) => node.nodeId === target);
57
+ if (!sourceNode || !targetNode) return false;
58
+ const sourceDefinition = registry.get(sourceNode.type);
59
+ const targetDefinition = registry.get(targetNode.type);
60
+ if (!sourceDefinition?.outputs.includes(sourcePort)) return false;
61
+ if (!targetDefinition || targetDefinition.category === "trigger") return false;
62
+ const edges = edgesOf(graph);
63
+ if (edges.some((edge) => edge.source === source && edge.sourcePort === sourcePort)) return false;
64
+ if (edges.some((edge) => edge.target === target)) return false;
65
+ return !reachableFrom(graph, target).has(source);
66
+ };
67
+ const validateFlowGraph = (graph, registry) => {
68
+ const issues = [];
69
+ const nodes = nodesOf(graph);
70
+ const edges = edgesOf(graph);
71
+ for (const node of nodes) {
72
+ const definition = registry.get(node.type);
73
+ if (!definition) {
74
+ issues.push({ code: "unknownBlock", severity: "error", nodeId: node.nodeId });
75
+ continue;
76
+ }
77
+ for (const blockIssue of definition.validate(node.config) ?? []) {
78
+ issues.push({ ...blockIssue, nodeId: node.nodeId });
79
+ }
80
+ }
81
+ const triggers = triggerNodesOf(graph, registry);
82
+ if (triggers.length === 0) issues.push({ code: "missingTrigger", severity: "error" });
83
+ triggers.slice(1).forEach((trigger) => {
84
+ issues.push({ code: "multipleTriggers", severity: "error", nodeId: trigger.nodeId });
85
+ });
86
+ const firstTrigger = triggers[0];
87
+ if (firstTrigger) {
88
+ const reachable = reachableFrom(graph, firstTrigger.nodeId);
89
+ nodes.filter((node) => !reachable.has(node.nodeId) && registry.get(node.type)?.category !== "trigger").forEach((node) => issues.push({ code: "unreachableNode", severity: "error", nodeId: node.nodeId }));
90
+ }
91
+ const inputCount = /* @__PURE__ */ new Map();
92
+ const portCount = /* @__PURE__ */ new Map();
93
+ for (const edge of edges) {
94
+ inputCount.set(edge.target, (inputCount.get(edge.target) ?? 0) + 1);
95
+ const portKey = `${edge.source}|${edge.sourcePort}`;
96
+ portCount.set(portKey, (portCount.get(portKey) ?? 0) + 1);
97
+ }
98
+ inputCount.forEach((count, nodeId) => {
99
+ if (count > 1) issues.push({ code: "multipleInputs", severity: "error", nodeId });
100
+ });
101
+ edges.filter((edge) => (portCount.get(`${edge.source}|${edge.sourcePort}`) ?? 0) > 1).forEach(
102
+ (edge) => issues.push({ code: "portFanOut", severity: "error", edgeId: edge.edgeId, nodeId: edge.source })
103
+ );
104
+ const cycle = findCycle(graph);
105
+ if (cycle) cycle.forEach((nodeId) => issues.push({ code: "cycle", severity: "error", nodeId }));
106
+ nodes.filter((node) => registry.get(node.type)?.outputs.includes("true")).filter((node) => !edges.some((edge) => edge.source === node.nodeId && edge.sourcePort === "true")).forEach((node) => issues.push({ code: "missingTrueBranch", severity: "warning", nodeId: node.nodeId }));
107
+ return issues;
108
+ };
109
+ const hasBlockingIssues = (issues) => (issues ?? []).some((issue) => issue.severity === "error");
110
+ const layoutFlowByLevels = (graph, registry) => {
111
+ const nodes = nodesOf(graph);
112
+ const outgoing = outgoingByNode(graph);
113
+ const levelOf = /* @__PURE__ */ new Map();
114
+ const roots = triggerNodesOf(graph, registry);
115
+ const queue = roots.map((root) => root.nodeId);
116
+ roots.forEach((root) => levelOf.set(root.nodeId, 0));
117
+ while (queue.length > 0) {
118
+ const nodeId = queue.shift();
119
+ if (nodeId === void 0) continue;
120
+ const level = levelOf.get(nodeId) ?? 0;
121
+ for (const edge of outgoing.get(nodeId) ?? []) {
122
+ if (levelOf.has(edge.target)) continue;
123
+ levelOf.set(edge.target, level + 1);
124
+ queue.push(edge.target);
125
+ }
126
+ }
127
+ const deepestLevel = Math.max(-1, ...Array.from(levelOf.values()));
128
+ nodes.filter((node) => !levelOf.has(node.nodeId)).forEach((node) => levelOf.set(node.nodeId, deepestLevel + 1));
129
+ const nodesByLevel = /* @__PURE__ */ new Map();
130
+ nodes.forEach((node) => {
131
+ const level = levelOf.get(node.nodeId) ?? 0;
132
+ nodesByLevel.set(level, [...nodesByLevel.get(level) ?? [], node.nodeId]);
133
+ });
134
+ const positions = {};
135
+ nodesByLevel.forEach((nodeIds, level) => {
136
+ const columnHeight = (nodeIds.length - 1) * FLOW_SIBLING_GAP;
137
+ nodeIds.forEach((nodeId, index) => {
138
+ positions[nodeId] = { x: level * FLOW_LEVEL_GAP, y: index * FLOW_SIBLING_GAP - columnHeight / 2 };
139
+ });
140
+ });
141
+ return positions;
142
+ };
143
+ const hasSavedPositions = (graph) => nodesOf(graph).some((node) => node.position && (node.position.x !== 0 || node.position.y !== 0));
144
+ export {
145
+ FLOW_LEVEL_GAP,
146
+ FLOW_SIBLING_GAP,
147
+ canConnect,
148
+ findCycle,
149
+ hasBlockingIssues,
150
+ hasSavedPositions,
151
+ layoutFlowByLevels,
152
+ reachableFrom,
153
+ triggerNodesOf,
154
+ validateFlowGraph
155
+ };
156
+ //# sourceMappingURL=flowGraph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flowGraph.js","sources":["../../../../src/components/UI/Flow/flowGraph.ts"],"sourcesContent":["import type { IFlowBlockRegistry } from './flowBlockRegistry';\nimport type { IFlowEdge, IFlowGraph, IFlowIssue, IFlowNode, IFlowPosition, TFlowPortId } from './flowTypes';\n\n/** The flow reads left to right: one level per column, siblings stacked in the column. */\nexport const FLOW_LEVEL_GAP = 336;\nexport const FLOW_SIBLING_GAP = 176;\n\nconst nodesOf = (graph: IFlowGraph): Array<IFlowNode> => (Array.isArray(graph?.nodes) ? graph.nodes : []);\nconst edgesOf = (graph: IFlowGraph): Array<IFlowEdge> => (Array.isArray(graph?.edges) ? graph.edges : []);\n\nconst outgoingByNode = (graph: IFlowGraph): Map<string, Array<IFlowEdge>> => {\n const outgoing = new Map<string, Array<IFlowEdge>>();\n for (const edge of edgesOf(graph)) {\n outgoing.set(edge.source, [...(outgoing.get(edge.source) ?? []), edge]);\n }\n return outgoing;\n};\n\nexport const triggerNodesOf = (graph: IFlowGraph, registry: IFlowBlockRegistry): Array<IFlowNode> =>\n nodesOf(graph).filter((node) => registry.get(node.type)?.category === 'trigger');\n\n/** Node ids reachable from `startNodeId` following edges, the start included. */\nexport const reachableFrom = (graph: IFlowGraph, startNodeId: string): Set<string> => {\n const outgoing = outgoingByNode(graph);\n const visited = new Set<string>();\n const pending = [startNodeId];\n while (pending.length > 0) {\n const nodeId = pending.pop();\n if (nodeId === undefined || visited.has(nodeId)) continue;\n visited.add(nodeId);\n for (const edge of outgoing.get(nodeId) ?? []) pending.push(edge.target);\n }\n return visited;\n};\n\n/** The node ids of the first cycle found, in order, or null when the graph has none. */\nexport const findCycle = (graph: IFlowGraph): Array<string> | null => {\n const outgoing = outgoingByNode(graph);\n const state = new Map<string, 'visiting' | 'done'>();\n const path: Array<string> = [];\n\n const visit = (nodeId: string): Array<string> | null => {\n state.set(nodeId, 'visiting');\n path.push(nodeId);\n for (const edge of outgoing.get(nodeId) ?? []) {\n const targetState = state.get(edge.target);\n if (targetState === 'visiting') return path.slice(path.indexOf(edge.target));\n if (targetState === undefined) {\n const cycle = visit(edge.target);\n if (cycle) return cycle;\n }\n }\n path.pop();\n state.set(nodeId, 'done');\n return null;\n };\n\n for (const node of nodesOf(graph)) {\n if (state.has(node.nodeId)) continue;\n const cycle = visit(node.nodeId);\n if (cycle) return cycle;\n }\n return null;\n};\n\nexport interface IFlowConnectionCandidate {\n source: string;\n sourcePort: TFlowPortId;\n target: string;\n}\n\n/**\n * The V0 shape is a tree with no fan-out: one input per block, one edge per output\n * port, no edge into a trigger and no cycle. Keeping this rule here lets the canvas\n * refuse a bad connection while it is being drawn, not only when publishing.\n */\nexport const canConnect = (\n graph: IFlowGraph,\n registry: IFlowBlockRegistry,\n candidate: IFlowConnectionCandidate,\n): boolean => {\n const { source, sourcePort, target } = candidate;\n if (!source || !target || source === target) return false;\n\n const nodes = nodesOf(graph);\n const sourceNode = nodes.find((node) => node.nodeId === source);\n const targetNode = nodes.find((node) => node.nodeId === target);\n if (!sourceNode || !targetNode) return false;\n\n const sourceDefinition = registry.get(sourceNode.type);\n const targetDefinition = registry.get(targetNode.type);\n if (!sourceDefinition?.outputs.includes(sourcePort)) return false;\n if (!targetDefinition || targetDefinition.category === 'trigger') return false;\n\n const edges = edgesOf(graph);\n if (edges.some((edge) => edge.source === source && edge.sourcePort === sourcePort)) return false;\n if (edges.some((edge) => edge.target === target)) return false;\n\n return !reachableFrom(graph, target).has(source);\n};\n\n/** The structural rules of the V0 shape, plus each block's own `validate`. */\nexport const validateFlowGraph = (graph: IFlowGraph, registry: IFlowBlockRegistry): Array<IFlowIssue> => {\n const issues: Array<IFlowIssue> = [];\n const nodes = nodesOf(graph);\n const edges = edgesOf(graph);\n\n for (const node of nodes) {\n const definition = registry.get(node.type);\n if (!definition) {\n issues.push({ code: 'unknownBlock', severity: 'error', nodeId: node.nodeId });\n continue;\n }\n for (const blockIssue of definition.validate(node.config) ?? []) {\n issues.push({ ...blockIssue, nodeId: node.nodeId });\n }\n }\n\n const triggers = triggerNodesOf(graph, registry);\n if (triggers.length === 0) issues.push({ code: 'missingTrigger', severity: 'error' });\n triggers.slice(1).forEach((trigger) => {\n issues.push({ code: 'multipleTriggers', severity: 'error', nodeId: trigger.nodeId });\n });\n\n const firstTrigger = triggers[0];\n if (firstTrigger) {\n const reachable = reachableFrom(graph, firstTrigger.nodeId);\n nodes\n .filter((node) => !reachable.has(node.nodeId) && registry.get(node.type)?.category !== 'trigger')\n .forEach((node) => issues.push({ code: 'unreachableNode', severity: 'error', nodeId: node.nodeId }));\n }\n\n const inputCount = new Map<string, number>();\n const portCount = new Map<string, number>();\n for (const edge of edges) {\n inputCount.set(edge.target, (inputCount.get(edge.target) ?? 0) + 1);\n const portKey = `${edge.source}|${edge.sourcePort}`;\n portCount.set(portKey, (portCount.get(portKey) ?? 0) + 1);\n }\n inputCount.forEach((count, nodeId) => {\n if (count > 1) issues.push({ code: 'multipleInputs', severity: 'error', nodeId });\n });\n edges\n .filter((edge) => (portCount.get(`${edge.source}|${edge.sourcePort}`) ?? 0) > 1)\n .forEach((edge) =>\n issues.push({ code: 'portFanOut', severity: 'error', edgeId: edge.edgeId, nodeId: edge.source }),\n );\n\n const cycle = findCycle(graph);\n if (cycle) cycle.forEach((nodeId) => issues.push({ code: 'cycle', severity: 'error', nodeId }));\n\n nodes\n .filter((node) => registry.get(node.type)?.outputs.includes('true'))\n .filter((node) => !edges.some((edge) => edge.source === node.nodeId && edge.sourcePort === 'true'))\n .forEach((node) => issues.push({ code: 'missingTrueBranch', severity: 'warning', nodeId: node.nodeId }));\n\n return issues;\n};\n\nexport const hasBlockingIssues = (issues: Array<IFlowIssue>): boolean =>\n (issues ?? []).some((issue) => issue.severity === 'error');\n\n/**\n * Lays the tree out by levels from the trigger: each level one column to the right,\n * siblings stacked in the column. Used only for a flow with no saved positions (one born from a rule) or when\n * the user asks for it; a position the user arranged is data and is never overwritten\n * implicitly.\n */\nexport const layoutFlowByLevels = (graph: IFlowGraph, registry: IFlowBlockRegistry): Record<string, IFlowPosition> => {\n const nodes = nodesOf(graph);\n const outgoing = outgoingByNode(graph);\n const levelOf = new Map<string, number>();\n const roots = triggerNodesOf(graph, registry);\n const queue: Array<string> = roots.map((root) => root.nodeId);\n roots.forEach((root) => levelOf.set(root.nodeId, 0));\n\n while (queue.length > 0) {\n const nodeId = queue.shift();\n if (nodeId === undefined) continue;\n const level = levelOf.get(nodeId) ?? 0;\n for (const edge of outgoing.get(nodeId) ?? []) {\n if (levelOf.has(edge.target)) continue;\n levelOf.set(edge.target, level + 1);\n queue.push(edge.target);\n }\n }\n\n const deepestLevel = Math.max(-1, ...Array.from(levelOf.values()));\n nodes.filter((node) => !levelOf.has(node.nodeId)).forEach((node) => levelOf.set(node.nodeId, deepestLevel + 1));\n\n const nodesByLevel = new Map<number, Array<string>>();\n nodes.forEach((node) => {\n const level = levelOf.get(node.nodeId) ?? 0;\n nodesByLevel.set(level, [...(nodesByLevel.get(level) ?? []), node.nodeId]);\n });\n\n const positions: Record<string, IFlowPosition> = {};\n nodesByLevel.forEach((nodeIds, level) => {\n const columnHeight = (nodeIds.length - 1) * FLOW_SIBLING_GAP;\n nodeIds.forEach((nodeId, index) => {\n positions[nodeId] = { x: level * FLOW_LEVEL_GAP, y: index * FLOW_SIBLING_GAP - columnHeight / 2 };\n });\n });\n return positions;\n};\n\nexport const hasSavedPositions = (graph: IFlowGraph): boolean =>\n nodesOf(graph).some((node) => node.position && (node.position.x !== 0 || node.position.y !== 0));\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { ComponentType, ReactNode } from 'react';
2
+ import { SymbolCodepoints } from '../MaterialSymbol';
3
+ export type TFlowPortId = 'next' | 'true' | 'false' | 'error';
4
+ export type TFlowBlockCategory = 'trigger' | 'condition' | 'action';
5
+ export interface IFlowPosition {
6
+ x: number;
7
+ y: number;
8
+ }
9
+ export interface IFlowNode<TConfig = unknown> {
10
+ nodeId: string;
11
+ type: string;
12
+ typeVersion: number;
13
+ config: TConfig;
14
+ position: IFlowPosition;
15
+ label?: string;
16
+ disabled?: boolean;
17
+ }
18
+ export interface IFlowEdge {
19
+ edgeId: string;
20
+ source: string;
21
+ sourcePort: TFlowPortId;
22
+ target: string;
23
+ }
24
+ export interface IFlowGraph {
25
+ nodes: Array<IFlowNode>;
26
+ edges: Array<IFlowEdge>;
27
+ }
28
+ export type TFlowIssueSeverity = 'error' | 'warning';
29
+ export type TFlowGraphIssueCode = 'missingTrigger' | 'multipleTriggers' | 'unknownBlock' | 'unreachableNode' | 'multipleInputs' | 'portFanOut' | 'cycle' | 'missingTrueBranch';
30
+ export interface IFlowIssue {
31
+ /** A graph rule from `TFlowGraphIssueCode`, or a block-specific code from its `validate`. */
32
+ code: string;
33
+ severity: TFlowIssueSeverity;
34
+ /** Already translated by the block. Graph issues leave it empty and the UI names them. */
35
+ message?: string;
36
+ nodeId?: string;
37
+ edgeId?: string;
38
+ }
39
+ export type TFlowRunStepStatus = 'passed' | 'failed' | 'skipped' | 'wouldRun' | 'error' | 'blocked';
40
+ /** The result of one run (real or simulated) painted over the canvas. */
41
+ export interface IFlowRunOverlay {
42
+ steps: Record<string, TFlowRunStepStatus>;
43
+ traversedEdgeIds: Array<string>;
44
+ }
45
+ export interface IFlowBlockConfigPanelProps<TConfig> {
46
+ config: TConfig;
47
+ onChange: (next: TConfig) => void;
48
+ readOnly?: boolean;
49
+ }
50
+ export type TFlowBlockIssue = Omit<IFlowIssue, 'nodeId' | 'edgeId'>;
51
+ export interface IFlowBlockDefinition<TConfig = unknown> {
52
+ /** Namespaced and stable: `trigger.recordEvent`, `action.moveWorkflowCard`. */
53
+ type: string;
54
+ typeVersion: number;
55
+ category: TFlowBlockCategory;
56
+ icon: SymbolCodepoints;
57
+ /** Translated by the host. */
58
+ label: string;
59
+ description?: string;
60
+ outputs: Array<TFlowPortId>;
61
+ /** How many of this block a flow may hold; omit for no limit. */
62
+ maxInstances?: number;
63
+ createDefaultConfig: () => TConfig;
64
+ migrateConfig?: (config: unknown, fromVersion: number) => TConfig;
65
+ ConfigPanel: ComponentType<IFlowBlockConfigPanelProps<TConfig>>;
66
+ /** When one block stands for several kinds (chosen inside it), the icon and name of the chosen kind. */
67
+ resolveIcon?: (config: TConfig) => SymbolCodepoints | undefined;
68
+ resolveLabel?: (config: TConfig) => string | undefined;
69
+ /** One line inside the block on the canvas. */
70
+ renderSummary: (config: TConfig) => ReactNode;
71
+ /** This block's piece of the "When..., if..., then..." sentence. */
72
+ describe: (config: TConfig) => string;
73
+ validate: (config: TConfig) => Array<TFlowBlockIssue>;
74
+ }
@@ -0,0 +1,2 @@
1
+
2
+ //# sourceMappingURL=flowTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flowTypes.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ export { createFlowBlockRegistry, defineFlowBlock, type IFlowBlockRegistry } from './flowBlockRegistry';
2
+ export { default as FlowCanvas, type IFlowCanvasProps } from './FlowCanvas';
3
+ export { FLOW_BLOCK_DRAG_TYPE } from './flowConstants';
4
+ export { default as FlowEditorProvider, useFlowBlockRegistry, useFlowEditor, useFlowEditorStore, } from './FlowEditorProvider';
5
+ export { createFlowEditorStore, type IFlowEditorState, type TFlowEditorStore } from './flowEditorStore';
6
+ export { canConnect, findCycle, FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, hasBlockingIssues, hasSavedPositions, layoutFlowByLevels, reachableFrom, triggerNodesOf, validateFlowGraph, } from './flowGraph';
7
+ export { default as FlowInspector } from './FlowInspector';
8
+ export { default as FlowIssueList } from './FlowIssueList';
9
+ export { default as FlowLaneMap, type IFlowArc, type IFlowLane, type TFlowArcTone } from './FlowLaneMap';
10
+ export { default as FlowNodeShell, type IFlowNodeShellProps } from './FlowNodeShell';
11
+ export { default as FlowPalette } from './FlowPalette';
12
+ export { default as FlowResizablePanel, type IFlowResizablePanelProps } from './FlowResizablePanel';
13
+ export type * from './flowTypes';
14
+ export { FLOW_RUN_STATUS_TONE, useFlowLabels } from './useFlowLabels';
@@ -0,0 +1,43 @@
1
+ import { createFlowBlockRegistry, defineFlowBlock } from "./flowBlockRegistry.js";
2
+ import { default as default2 } from "./FlowCanvas.js";
3
+ import { FLOW_BLOCK_DRAG_TYPE } from "./flowConstants.js";
4
+ import { default as default3, useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from "./FlowEditorProvider.js";
5
+ import { createFlowEditorStore } from "./flowEditorStore.js";
6
+ import { FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, canConnect, findCycle, hasBlockingIssues, hasSavedPositions, layoutFlowByLevels, reachableFrom, triggerNodesOf, validateFlowGraph } from "./flowGraph.js";
7
+ import { default as default4 } from "./FlowInspector.js";
8
+ import { default as default5 } from "./FlowIssueList.js";
9
+ import { default as default6 } from "./FlowLaneMap.js";
10
+ import { default as default7 } from "./FlowNodeShell.js";
11
+ import { default as default8 } from "./FlowPalette.js";
12
+ import { default as default9 } from "./FlowResizablePanel.js";
13
+ import { FLOW_RUN_STATUS_TONE, useFlowLabels } from "./useFlowLabels.js";
14
+ export {
15
+ FLOW_BLOCK_DRAG_TYPE,
16
+ FLOW_LEVEL_GAP,
17
+ FLOW_RUN_STATUS_TONE,
18
+ FLOW_SIBLING_GAP,
19
+ default2 as FlowCanvas,
20
+ default3 as FlowEditorProvider,
21
+ default4 as FlowInspector,
22
+ default5 as FlowIssueList,
23
+ default6 as FlowLaneMap,
24
+ default7 as FlowNodeShell,
25
+ default8 as FlowPalette,
26
+ default9 as FlowResizablePanel,
27
+ canConnect,
28
+ createFlowBlockRegistry,
29
+ createFlowEditorStore,
30
+ defineFlowBlock,
31
+ findCycle,
32
+ hasBlockingIssues,
33
+ hasSavedPositions,
34
+ layoutFlowByLevels,
35
+ reachableFrom,
36
+ triggerNodesOf,
37
+ useFlowBlockRegistry,
38
+ useFlowEditor,
39
+ useFlowEditorStore,
40
+ useFlowLabels,
41
+ validateFlowGraph
42
+ };
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"}
@@ -0,0 +1,10 @@
1
+ import { TChipTone } from '../Chips/chipTones';
2
+ import { IFlowIssue, TFlowBlockCategory, TFlowPortId, TFlowRunStepStatus } from './flowTypes';
3
+ export declare const FLOW_RUN_STATUS_TONE: Record<TFlowRunStepStatus, TChipTone>;
4
+ /** Every label the flow UI shows, spelled as literal keys so the translation tooling can read them. */
5
+ export declare const useFlowLabels: () => {
6
+ ports: Record<TFlowPortId, string>;
7
+ categories: Record<TFlowBlockCategory, string>;
8
+ runStatus: Record<TFlowRunStepStatus, string>;
9
+ issueMessage: (issue: IFlowIssue) => string;
10
+ };
@@ -0,0 +1,51 @@
1
+ import { useMemo } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ const FLOW_RUN_STATUS_TONE = {
4
+ passed: "success",
5
+ wouldRun: "info",
6
+ skipped: "neutral",
7
+ blocked: "warning",
8
+ failed: "danger",
9
+ error: "danger"
10
+ };
11
+ const useFlowLabels = () => {
12
+ const { t } = useTranslation("ui");
13
+ return useMemo(() => {
14
+ const ports = {
15
+ next: t("flow.ports.next"),
16
+ true: t("flow.ports.true"),
17
+ false: t("flow.ports.false"),
18
+ error: t("flow.ports.error")
19
+ };
20
+ const categories = {
21
+ trigger: t("flow.categories.trigger"),
22
+ condition: t("flow.categories.condition"),
23
+ action: t("flow.categories.action")
24
+ };
25
+ const runStatus = {
26
+ passed: t("flow.run.passed"),
27
+ failed: t("flow.run.failed"),
28
+ skipped: t("flow.run.skipped"),
29
+ wouldRun: t("flow.run.wouldRun"),
30
+ error: t("flow.run.error"),
31
+ blocked: t("flow.run.blocked")
32
+ };
33
+ const graphIssues = {
34
+ missingTrigger: t("flow.issues.missingTrigger"),
35
+ multipleTriggers: t("flow.issues.multipleTriggers"),
36
+ unknownBlock: t("flow.issues.unknownBlock"),
37
+ unreachableNode: t("flow.issues.unreachableNode"),
38
+ multipleInputs: t("flow.issues.multipleInputs"),
39
+ portFanOut: t("flow.issues.portFanOut"),
40
+ cycle: t("flow.issues.cycle"),
41
+ missingTrueBranch: t("flow.issues.missingTrueBranch")
42
+ };
43
+ const issueMessage = (issue) => issue.message ?? graphIssues[issue.code] ?? issue.code;
44
+ return { ports, categories, runStatus, issueMessage };
45
+ }, [t]);
46
+ };
47
+ export {
48
+ FLOW_RUN_STATUS_TONE,
49
+ useFlowLabels
50
+ };
51
+ //# sourceMappingURL=useFlowLabels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFlowLabels.js","sources":["../../../../src/components/UI/Flow/useFlowLabels.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport type { TChipTone } from '@/components/UI/Chips/chipTones';\n\nimport type { IFlowIssue, TFlowBlockCategory, TFlowGraphIssueCode, TFlowPortId, TFlowRunStepStatus } from './flowTypes';\n\nexport const FLOW_RUN_STATUS_TONE: Record<TFlowRunStepStatus, TChipTone> = {\n passed: 'success',\n wouldRun: 'info',\n skipped: 'neutral',\n blocked: 'warning',\n failed: 'danger',\n error: 'danger',\n};\n\n/** Every label the flow UI shows, spelled as literal keys so the translation tooling can read them. */\nexport const useFlowLabels = () => {\n const { t } = useTranslation('ui');\n\n return useMemo(() => {\n const ports: Record<TFlowPortId, string> = {\n next: t('flow.ports.next'),\n true: t('flow.ports.true'),\n false: t('flow.ports.false'),\n error: t('flow.ports.error'),\n };\n const categories: Record<TFlowBlockCategory, string> = {\n trigger: t('flow.categories.trigger'),\n condition: t('flow.categories.condition'),\n action: t('flow.categories.action'),\n };\n const runStatus: Record<TFlowRunStepStatus, string> = {\n passed: t('flow.run.passed'),\n failed: t('flow.run.failed'),\n skipped: t('flow.run.skipped'),\n wouldRun: t('flow.run.wouldRun'),\n error: t('flow.run.error'),\n blocked: t('flow.run.blocked'),\n };\n const graphIssues: Record<TFlowGraphIssueCode, string> = {\n missingTrigger: t('flow.issues.missingTrigger'),\n multipleTriggers: t('flow.issues.multipleTriggers'),\n unknownBlock: t('flow.issues.unknownBlock'),\n unreachableNode: t('flow.issues.unreachableNode'),\n multipleInputs: t('flow.issues.multipleInputs'),\n portFanOut: t('flow.issues.portFanOut'),\n cycle: t('flow.issues.cycle'),\n missingTrueBranch: t('flow.issues.missingTrueBranch'),\n };\n const issueMessage = (issue: IFlowIssue): string =>\n issue.message ?? graphIssues[issue.code as TFlowGraphIssueCode] ?? issue.code;\n\n return { ports, categories, runStatus, issueMessage };\n }, [t]);\n};\n"],"names":[],"mappings":";;AAOO,MAAM,uBAA8D;AAAA,EACzE,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO;AACT;AAGO,MAAM,gBAAgB,MAAM;AACjC,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,SAAO,QAAQ,MAAM;AACnB,UAAM,QAAqC;AAAA,MACzC,MAAM,EAAE,iBAAiB;AAAA,MACzB,MAAM,EAAE,iBAAiB;AAAA,MACzB,OAAO,EAAE,kBAAkB;AAAA,MAC3B,OAAO,EAAE,kBAAkB;AAAA,IAAA;AAE7B,UAAM,aAAiD;AAAA,MACrD,SAAS,EAAE,yBAAyB;AAAA,MACpC,WAAW,EAAE,2BAA2B;AAAA,MACxC,QAAQ,EAAE,wBAAwB;AAAA,IAAA;AAEpC,UAAM,YAAgD;AAAA,MACpD,QAAQ,EAAE,iBAAiB;AAAA,MAC3B,QAAQ,EAAE,iBAAiB;AAAA,MAC3B,SAAS,EAAE,kBAAkB;AAAA,MAC7B,UAAU,EAAE,mBAAmB;AAAA,MAC/B,OAAO,EAAE,gBAAgB;AAAA,MACzB,SAAS,EAAE,kBAAkB;AAAA,IAAA;AAE/B,UAAM,cAAmD;AAAA,MACvD,gBAAgB,EAAE,4BAA4B;AAAA,MAC9C,kBAAkB,EAAE,8BAA8B;AAAA,MAClD,cAAc,EAAE,0BAA0B;AAAA,MAC1C,iBAAiB,EAAE,6BAA6B;AAAA,MAChD,gBAAgB,EAAE,4BAA4B;AAAA,MAC9C,YAAY,EAAE,wBAAwB;AAAA,MACtC,OAAO,EAAE,mBAAmB;AAAA,MAC5B,mBAAmB,EAAE,+BAA+B;AAAA,IAAA;AAEtD,UAAM,eAAe,CAAC,UACpB,MAAM,WAAW,YAAY,MAAM,IAA2B,KAAK,MAAM;AAE3E,WAAO,EAAE,OAAO,YAAY,WAAW,aAAA;AAAA,EACzC,GAAG,CAAC,CAAC,CAAC;AACR;"}
@@ -216,6 +216,73 @@ declare const _default: {
216
216
  "keepEditing": "Keep editing",
217
217
  "discard": "Discard"
218
218
  },
219
+ "flow": {
220
+ "canvas": "Flow editor",
221
+ "resizePanel": "Drag to adjust the width. Double click resets",
222
+ "ports": {
223
+ "next": "Next",
224
+ "true": "Yes",
225
+ "false": "No",
226
+ "error": "Error"
227
+ },
228
+ "categories": {
229
+ "trigger": "Triggers",
230
+ "condition": "Conditions",
231
+ "action": "Actions"
232
+ },
233
+ "palette": {
234
+ "title": "Blocks",
235
+ "search": "Search block",
236
+ "empty": "No block found",
237
+ "limitReached": "This flow already has the maximum of this block"
238
+ },
239
+ "inspector": {
240
+ "title": "Block settings",
241
+ "empty": "Select a block to configure it",
242
+ "label": "Block name",
243
+ "delete": "Delete block",
244
+ "duplicate": "Duplicate block",
245
+ "unknownBlock": "This block type no longer exists",
246
+ "close": "Close"
247
+ },
248
+ "issues": {
249
+ "title": "Issues",
250
+ "none": "No issues",
251
+ "missingTrigger": "The flow needs a trigger",
252
+ "multipleTriggers": "A flow can have only one trigger",
253
+ "unknownBlock": "Block of a type that no longer exists",
254
+ "unreachableNode": "Loose block: no path from the trigger reaches it",
255
+ "multipleInputs": "Block with more than one input",
256
+ "portFanOut": "An output can connect to one block only",
257
+ "cycle": "The blocks form a cycle",
258
+ "missingTrueBranch": "The condition has no yes path"
259
+ },
260
+ "run": {
261
+ "passed": "Passed",
262
+ "failed": "Did not pass",
263
+ "skipped": "Skipped",
264
+ "wouldRun": "Would run",
265
+ "error": "Error",
266
+ "blocked": "Blocked"
267
+ },
268
+ "controls": {
269
+ "zoomIn": "Zoom in",
270
+ "zoomOut": "Zoom out",
271
+ "fitView": "Fit view",
272
+ "undo": "Undo",
273
+ "redo": "Redo",
274
+ "layout": "Arrange blocks"
275
+ },
276
+ "node": {
277
+ "disabled": "Disabled",
278
+ "addBlock": "Add the next block",
279
+ "issueCount_one": "{{count}} issue",
280
+ "issueCount_other": "{{count}} issues"
281
+ },
282
+ "laneMap": {
283
+ "label": "Flow map"
284
+ }
285
+ },
219
286
  "followUp": {
220
287
  "messages": "Messages",
221
288
  "chat": {
@@ -12,6 +12,7 @@ const detailsTable = { "size": "Size", "quantity": "Qty" };
12
12
  const errorPage = { "message": "This screen could not load", "headline": "Something unexpected happened while building the page. Refreshing usually fixes it. If it happens again, contact support and tell them what you were doing.", "backToStart": "Back to start" };
13
13
  const filters = { "activeFilters": "Applied filters", "appliedCount_one": "{{count}} filter applied", "appliedCount_other": "{{count}} filters applied", "clearAll": "Clear all", "noFields": "No filters available for this list.", "noOptions": "No options found.", "optionsError": "Could not load the options for {{field}}.", "rangeMax": "Maximum", "rangeMin": "Minimum", "rangeSeparator": "to", "searchOptions": "Search options" };
14
14
  const filtersDrawer = { "selectedFilters": "Selected filters:", "pendingChanges": "Pending changes", "discardChangesTitle": "Discard changes?", "discardChangesMessage": "You have unapplied filter changes. Discard them?", "keepEditing": "Keep editing", "discard": "Discard" };
15
+ const flow = { "canvas": "Flow editor", "resizePanel": "Drag to adjust the width. Double click resets", "ports": { "next": "Next", "true": "Yes", "false": "No", "error": "Error" }, "categories": { "trigger": "Triggers", "condition": "Conditions", "action": "Actions" }, "palette": { "title": "Blocks", "search": "Search block", "empty": "No block found", "limitReached": "This flow already has the maximum of this block" }, "inspector": { "title": "Block settings", "empty": "Select a block to configure it", "label": "Block name", "delete": "Delete block", "duplicate": "Duplicate block", "unknownBlock": "This block type no longer exists", "close": "Close" }, "issues": { "title": "Issues", "none": "No issues", "missingTrigger": "The flow needs a trigger", "multipleTriggers": "A flow can have only one trigger", "unknownBlock": "Block of a type that no longer exists", "unreachableNode": "Loose block: no path from the trigger reaches it", "multipleInputs": "Block with more than one input", "portFanOut": "An output can connect to one block only", "cycle": "The blocks form a cycle", "missingTrueBranch": "The condition has no yes path" }, "run": { "passed": "Passed", "failed": "Did not pass", "skipped": "Skipped", "wouldRun": "Would run", "error": "Error", "blocked": "Blocked" }, "controls": { "zoomIn": "Zoom in", "zoomOut": "Zoom out", "fitView": "Fit view", "undo": "Undo", "redo": "Redo", "layout": "Arrange blocks" }, "node": { "disabled": "Disabled", "addBlock": "Add the next block", "issueCount_one": "{{count}} issue", "issueCount_other": "{{count}} issues" }, "laneMap": { "label": "Flow map" } };
15
16
  const followUp = { "messages": "Messages", "chat": { "loadingMessages": "Loading messages", "placeholder": "Write a message...", "invalidMessage": "Please enter a valid message", "noMessagesYet": "No messages yet", "startConversation": "Start a conversation by adding the first message", "loadError": "Messages could not be loaded", "sendFailed": "The message could not be sent. Try again.", "sendFailedInline": "Failed to send", "resend": "Resend", "discard": "Discard", "today": "Today", "yesterday": "Yesterday", "typingOne": "{{name}} is typing...", "typingMany": "{{count}} people are typing...", "someone": "Someone", "you": "You", "newMessages": "New messages", "backToLatest": "Back to latest", "deletedMessage": "Message deleted", "edited": "edited", "readBy": "Read by {{names}}", "copied": "Message copied", "replyingTo": "Replying to {{name}}", "attachmentFallback": "Attachment", "editTitle": "Edit message", "deleteTitle": "Delete message?", "deleteSubtitle": "The message will be shown as deleted for every participant", "sendHint": "Enter sends, Shift+Enter adds a line break", "formatting": "Formatting" }, "actions": { "reply": "Reply", "copy": "Copy text", "edit": "Edit", "delete": "Delete" }, "attachments": { "attach": "Attach", "pickFile": "File", "pickImage": "Image", "takePhoto": "Take photo", "dropHere": "Drop files to attach", "tooMany": "Limit of {{max}} attachments per message", "tooLarge": "The file {{name}} exceeds the 50 MB limit", "uploadFailed": "Upload failed", "uploading": "Uploading attachments...", "imagePreview": "Preview image", "showTranscript": "Show transcript", "hideTranscript": "Hide transcript" }, "voice": { "record": "Record audio", "recording": "Recording", "stop": "Finish recording", "cancel": "Cancel recording", "rerecord": "Record again", "attach": "Add to message", "discard": "Discard", "transcriptLabel": "Transcript", "transcriptPlaceholder": "The speech transcript appears here and can be edited" }, "camera": { "title": "Take photo", "unavailable": "Camera unavailable", "unavailableHint": "Check the browser camera permission", "switch": "Switch camera", "capture": "Capture photo" }, "search": { "open": "Search in conversation", "placeholder": "Search messages", "noResults": "No messages found", "error": "Messages could not be searched" } };
16
17
  const generatedByAI = { "tooltip": "Generated by AI" };
17
18
  const imageAiAdjust = { "action": "Adjust with AI", "title": "Adjust image with AI", "subtitle": "Describe the adjustment. The AI changes only what you ask and preserves the rest of the image.", "original": "Original", "result": "Result", "resultHint": "Describe the adjustment and click Generate to see the result here.", "promptLabel": "Desired adjustment", "promptPlaceholder": "E.g. remove the background and make it white, smooth the sleeve creases, brighten the lighting", "generate": "Generate", "generating": "Generating the image...", "tryAgain": "Generate another", "useResult": "Use this image", "errorGenerate": "Could not adjust the image with AI", "errorSave": "Could not save the adjusted image", "errorRefused": "The AI could not make this adjustment: {{reason}}", "mask": { "start": "Mark the area", "stop": "Use the whole image", "hint": "Paint the area the AI may change. The rest of the image is kept." }, "verification": { "changedMoreThanRequested": "The AI changed more than requested. Check the areas outside the adjustment before using it.", "colorMismatch": "The garment color ({{dominant}}) differs from the requested color ({{target}})." } };
@@ -50,6 +51,7 @@ const en = {
50
51
  errorPage,
51
52
  filters,
52
53
  filtersDrawer,
54
+ flow,
53
55
  followUp,
54
56
  generatedByAI,
55
57
  imageAiAdjust,
@@ -90,6 +92,7 @@ export {
90
92
  errorPage,
91
93
  filters,
92
94
  filtersDrawer,
95
+ flow,
93
96
  followUp,
94
97
  generatedByAI,
95
98
  imageAiAdjust,
@@ -1 +1 @@
1
- {"version":3,"file":"ui.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"ui.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}