@adatechnology/conversations-ui 0.1.0-rc.2 → 0.1.0-rc.21

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 (106) hide show
  1. package/dist/{chunk-ZDURDZTM.js → chunk-2AYDBWNE.js} +14 -0
  2. package/dist/chunk-TV4OQRGH.js +2187 -0
  3. package/dist/flows/index.d.ts +14 -2
  4. package/dist/flows/index.js +117 -39
  5. package/dist/index.d.ts +862 -136
  6. package/dist/index.js +1746 -1124
  7. package/dist/preview/index.d.ts +470 -0
  8. package/dist/preview/index.js +1329 -0
  9. package/dist/styles.css +228 -0
  10. package/dist/types-C6A_9edv.d.ts +456 -0
  11. package/package.json +10 -3
  12. package/src/AudioRecorderButton.test.tsx +30 -0
  13. package/src/AudioRecorderButton.tsx +248 -0
  14. package/src/AudioTranscription.test.tsx +115 -0
  15. package/src/AudioTranscription.tsx +249 -0
  16. package/src/Avatar.tsx +30 -4
  17. package/src/ChannelIcon.tsx +87 -0
  18. package/src/ConversationContextPanel.tsx +280 -0
  19. package/src/ConversationDocumentsPanel.tsx +425 -0
  20. package/src/ConversationHeader.tsx +257 -0
  21. package/src/ConversationListItem.tsx +54 -7
  22. package/src/ConversationLocalesProvider.tsx +44 -0
  23. package/src/ConversationRow.tsx +137 -0
  24. package/src/DateDivider.tsx +16 -3
  25. package/src/DocumentsLibrary.tsx +322 -0
  26. package/src/EmojiPicker.tsx +69 -55
  27. package/src/FileIcon.test.ts +83 -0
  28. package/src/FileIcon.tsx +88 -11
  29. package/src/InteractiveMessage.test.tsx +41 -0
  30. package/src/InteractiveMessage.tsx +143 -0
  31. package/src/Lightbox.tsx +18 -3
  32. package/src/MediaRenderer.tsx +96 -22
  33. package/src/MessageBubble.tsx +77 -5
  34. package/src/MessageComposer.test.tsx +35 -0
  35. package/src/MessageComposer.tsx +165 -19
  36. package/src/RichMessageComposer.test.tsx +83 -0
  37. package/src/RichMessageComposer.tsx +380 -0
  38. package/src/Wallpaper.test.tsx +21 -0
  39. package/src/Wallpaper.tsx +69 -7
  40. package/src/WhatsAppMessageEditor.tsx +28 -4
  41. package/src/WindowExpiredNotice.tsx +57 -0
  42. package/src/audioRecorderFormat.test.ts +67 -0
  43. package/src/conversationChannel.test.ts +53 -0
  44. package/src/conversationChannel.ts +146 -0
  45. package/src/conversationTranscript.test.ts +122 -0
  46. package/src/conversationTranscript.ts +89 -0
  47. package/src/conversationWindow.test.ts +90 -0
  48. package/src/conversationWindow.ts +78 -0
  49. package/src/documentTypeLabel.test.ts +57 -0
  50. package/src/emojiCatalog.test.ts +35 -0
  51. package/src/emojiCatalog.ts +189 -0
  52. package/src/flows/FlowGroupHeader.tsx +12 -2
  53. package/src/flows/FlowMapCanvas.tsx +17 -14
  54. package/src/flows/FlowMapNode.tsx +3 -1
  55. package/src/flows/FlowNodeCard.tsx +22 -4
  56. package/src/flows/FlowNodePanel.tsx +132 -35
  57. package/src/flows/FlowPalette.tsx +6 -2
  58. package/src/flows/FlowWhatsAppPreview.tsx +14 -3
  59. package/src/flows/flowGraph.ts +5 -5
  60. package/src/flows/labels.ts +5 -0
  61. package/src/hooks/useConversationActions.ts +56 -0
  62. package/src/hooks/useConversationDocuments.ts +15 -9
  63. package/src/hooks/useConversationList.ts +15 -9
  64. package/src/hooks/useConversationMessages.ts +2 -2
  65. package/src/hooks/useScrollToLatestMessage.ts +127 -0
  66. package/src/index.ts +140 -16
  67. package/src/lib/cn.test.ts +29 -0
  68. package/src/lib/cn.ts +15 -0
  69. package/src/lib/createMediaUrlResolver.ts +33 -0
  70. package/src/lib/paginated.test.ts +33 -0
  71. package/src/lib/paginated.ts +26 -0
  72. package/src/lib/phone.ts +34 -0
  73. package/src/preview/ConversationPreview.tsx +334 -0
  74. package/src/preview/MediaTypesPreview.tsx +87 -0
  75. package/src/preview/conversationPreviewFailures.test.ts +64 -0
  76. package/src/preview/createMockConversationsApi.ts +271 -0
  77. package/src/preview/createMockSSEProvider.ts +40 -0
  78. package/src/preview/createPreviewBridgeClient.test.ts +92 -0
  79. package/src/preview/createPreviewBridgeClient.ts +124 -0
  80. package/src/preview/createPreviewMediaUploader.ts +82 -0
  81. package/src/preview/createPreviewWebhookClient.test.ts +194 -0
  82. package/src/preview/createPreviewWebhookClient.ts +222 -0
  83. package/src/preview/index.ts +67 -0
  84. package/src/preview/mediaTypeOf.test.ts +15 -0
  85. package/src/preview/mockDocumentsSearch.test.ts +57 -0
  86. package/src/preview/mockEventSource.ts +53 -0
  87. package/src/preview/preview.test.ts +177 -0
  88. package/src/preview/previewFileSamples.test.ts +151 -0
  89. package/src/preview/previewFileSamples.ts +74 -0
  90. package/src/preview/previewFixtures.ts +440 -0
  91. package/src/preview/previewMediaSource.test.ts +62 -0
  92. package/src/preview/previewMediaSource.ts +91 -0
  93. package/src/preview/previewMediaUploader.test.ts +61 -0
  94. package/src/preview/previewStore.ts +193 -0
  95. package/src/preview/startPreviewScript.ts +60 -0
  96. package/src/providers/types.ts +175 -12
  97. package/src/quickReply.test.ts +58 -0
  98. package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
  99. package/src/settings/TranscriptionSettingsForm.tsx +189 -0
  100. package/src/settings/WhatsAppCreateTemplateForm.tsx +3 -1
  101. package/src/settings/WhatsAppTemplatesSettings.tsx +106 -0
  102. package/src/styles.css +173 -0
  103. package/src/types.ts +72 -1
  104. package/src/useDarkMode.ts +26 -0
  105. package/src/useIsNarrow.ts +29 -0
  106. package/src/useWaitingNotifications.ts +74 -29
@@ -1,4 +1,5 @@
1
1
  import * as react from 'react';
2
+ import { ReactNode } from 'react';
2
3
  import { NodeProps } from '@xyflow/react';
3
4
  import { LucideIcon } from 'lucide-react';
4
5
  import { FlowConditionOperator, FlowGraphData, FlowNodeData, FlowNodeType, FlowQuestionType, FlowActionKind } from '@adatechnology/meta-whatsapp-contracts';
@@ -9,6 +10,7 @@ declare const BUILT_IN_ACTION_KINDS: {
9
10
  readonly HANDOFF: "handoff";
10
11
  readonly RATE_LIMITED_HANDOFF: "rate_limited_handoff";
11
12
  readonly SEND_PRODUCT_LIST: "send_product_list";
13
+ readonly SEND_MEDIA: "send_media";
12
14
  };
13
15
  declare const CROSS_FLOW_PREFIX = "flow:";
14
16
  declare const isCrossFlowTarget: (target: string) => boolean;
@@ -109,6 +111,8 @@ interface FlowEditorLabels {
109
111
  conditionTrue: string;
110
112
  conditionFalse: string;
111
113
  conditionVariableMissing: string;
114
+ media: string;
115
+ mediaUnavailable: string;
112
116
  };
113
117
  palette: {
114
118
  title: string;
@@ -233,14 +237,22 @@ interface FlowNodePanelProps {
233
237
  onChange: (updated: FlowNodeData) => void;
234
238
  onDelete: (nodeId: string) => void;
235
239
  labels?: Partial<FlowEditorLabels>;
240
+ /**
241
+ * Seletor de arquivos do nó `send_media`, renderizado no lugar da mensagem direta.
242
+ *
243
+ * Slot, e não uma lista de arquivos por prop, porque a biblioteca é do host: upload, permissão e
244
+ * URL assinada são dele, e o painel não tem como buscar nada. Ausente, o nó continua editável —
245
+ * só não dá para anexar por aqui.
246
+ */
247
+ renderMediaPicker?: (node: FlowNodeData) => ReactNode;
236
248
  }
237
- declare function FlowNodePanel({ graph, node, issues, otherFlows, onClose, onChange, onDelete, labels: labelsOverride, }: FlowNodePanelProps): react.JSX.Element;
249
+ declare function FlowNodePanel({ graph, node, issues, otherFlows, onClose, onChange, onDelete, labels: labelsOverride, renderMediaPicker, }: FlowNodePanelProps): react.JSX.Element;
238
250
 
239
251
  interface FlowWhatsAppPreviewProps {
240
252
  body: string;
241
253
  options?: [string, string][];
242
254
  labels?: FlowEditorLabels['nodePanel'];
243
255
  }
244
- declare function FlowWhatsAppPreview({ body, options, labels }: FlowWhatsAppPreviewProps): react.JSX.Element;
256
+ declare function FlowWhatsAppPreview({ body, options, labels, }: FlowWhatsAppPreviewProps): react.JSX.Element;
245
257
 
246
258
  export { BUILT_IN_ACTION_KINDS, CONDITION_OPERATORS, CROSS_FLOW_PREFIX, type CollectionChain, DEFAULT_FLOW_EDITOR_LABELS, type FlowEditorLabels, FlowGroupFrame, type FlowGroupFrameData, FlowGroupHeader, type FlowGroupHeaderData, FlowMapCanvas, type FlowMapCanvasProps, FlowMapNode, type FlowMapNodeData, FlowNodeCard, type FlowNodeCardData, FlowNodePanel, type FlowNodePanelProps, FlowPalette, type FlowPaletteActionOption, type FlowPaletteProps, FlowPortalNode, type FlowPortalNodeData, FlowWhatsAppPreview, type FlowWhatsAppPreviewProps, 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 };
@@ -1,11 +1,22 @@
1
1
  import {
2
2
  parseWhatsAppFormatting,
3
- useDarkMode
4
- } from "../chunk-ZDURDZTM.js";
3
+ useIsDarkTheme
4
+ } from "../chunk-2AYDBWNE.js";
5
5
 
6
6
  // src/flows/FlowNodeCard.tsx
7
7
  import { Handle, Position } from "@xyflow/react";
8
- import { MessageCircleQuestion, GitBranch, Zap, ListTree, Diamond, AlertTriangle, AlertCircle, Headset, Clock3, ShoppingBag } from "lucide-react";
8
+ import {
9
+ MessageCircleQuestion,
10
+ GitBranch,
11
+ Zap,
12
+ ListTree,
13
+ Diamond,
14
+ AlertTriangle,
15
+ AlertCircle,
16
+ Headset,
17
+ Clock3,
18
+ ShoppingBag
19
+ } from "lucide-react";
9
20
  import { jsx, jsxs } from "react/jsx-runtime";
10
21
  var NODE_TYPE_COLOR = {
11
22
  question: "border-blue-300 bg-blue-50 dark:bg-blue-950/40 dark:border-blue-800",
@@ -57,7 +68,13 @@ function sourceRows(node, labels) {
57
68
  }
58
69
  function SourceRow({ label, isDefault, handleId }) {
59
70
  return /* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-1.5 rounded-md border border-gray-200 dark:border-gray-600 bg-white/70 dark:bg-gray-900/40 px-2 py-1 pr-3", children: [
60
- /* @__PURE__ */ jsx("span", { className: `text-xs truncate flex-1 ${isDefault ? "italic text-gray-400 dark:text-gray-500" : "text-gray-700 dark:text-gray-200"}`, children: label }),
71
+ /* @__PURE__ */ jsx(
72
+ "span",
73
+ {
74
+ className: `text-xs truncate flex-1 ${isDefault ? "italic text-gray-400 dark:text-gray-500" : "text-gray-700 dark:text-gray-200"}`,
75
+ children: label
76
+ }
77
+ ),
61
78
  /* @__PURE__ */ jsx(
62
79
  Handle,
63
80
  {
@@ -132,7 +149,8 @@ var DEFAULT_FLOW_EDITOR_LABELS = {
132
149
  actionKindLabels: {
133
150
  handoff: "Encaminhar para atendimento",
134
151
  rate_limited_handoff: "Encaminhar (limite de simula\xE7\xF5es atingido)",
135
- send_product_list: "Enviar cat\xE1logo de produtos"
152
+ send_product_list: "Enviar cat\xE1logo de produtos",
153
+ send_media: "Enviar arquivos da biblioteca"
136
154
  },
137
155
  conditionOperatorLabels: {
138
156
  ">": "maior que",
@@ -187,7 +205,9 @@ var DEFAULT_FLOW_EDITOR_LABELS = {
187
205
  conditionValue: "Valor de compara\xE7\xE3o",
188
206
  conditionTrue: "Se verdadeiro \u2192",
189
207
  conditionFalse: "Se falso \u2192",
190
- conditionVariableMissing: "Se a vari\xE1vel ainda n\xE3o foi coletada \u2192"
208
+ conditionVariableMissing: "Se a vari\xE1vel ainda n\xE3o foi coletada \u2192",
209
+ media: "Arquivos enviados neste ponto",
210
+ mediaUnavailable: "A biblioteca de arquivos n\xE3o est\xE1 dispon\xEDvel neste painel."
191
211
  },
192
212
  palette: {
193
213
  title: "Adicionar ao fluxo",
@@ -266,12 +286,9 @@ import { ReactFlow, Background, Controls, MarkerType } from "@xyflow/react";
266
286
  import "@xyflow/react/dist/style.css";
267
287
 
268
288
  // src/flows/flowGraph.ts
289
+ import { FLOW_ACTION_KIND } from "@adatechnology/meta-whatsapp-contracts";
269
290
  var CONDITION_OPERATORS = [">", ">=", "<", "<=", "==", "!=", "contains"];
270
- var BUILT_IN_ACTION_KINDS = {
271
- HANDOFF: "handoff",
272
- RATE_LIMITED_HANDOFF: "rate_limited_handoff",
273
- SEND_PRODUCT_LIST: "send_product_list"
274
- };
291
+ var BUILT_IN_ACTION_KINDS = FLOW_ACTION_KIND;
275
292
  var CROSS_FLOW_PREFIX = "flow:";
276
293
  var isCrossFlowTarget = (target) => target.startsWith(CROSS_FLOW_PREFIX);
277
294
  var crossFlowKey = (target) => target.slice(CROSS_FLOW_PREFIX.length);
@@ -524,21 +541,23 @@ var BACKGROUND_COLOR_LIGHT = "#cbd5e1";
524
541
  var BACKGROUND_COLOR_DARK = "#334155";
525
542
  function FlowMapCanvas({ graphs, rootKey, onOpenFlow, labels: labelsOverride }) {
526
543
  const labels = { ...DEFAULT_FLOW_EDITOR_LABELS, ...labelsOverride };
527
- const { isDark } = useDarkMode();
544
+ const isDark = useIsDarkTheme();
528
545
  const positions = useMemo(() => computeFlowMapLayout(graphs, rootKey), [graphs, rootKey]);
529
546
  const nodes = useMemo(
530
- () => Object.values(graphs).map((g) => ({
531
- id: g.key,
532
- type: "flowMapNode",
533
- position: positions[g.key] ?? { x: 0, y: 0 },
534
- data: {
535
- label: g.label,
536
- nodeCount: Object.keys(g.nodes).length,
537
- isRoot: g.key === rootKey,
538
- labels,
539
- onOpen: () => onOpenFlow(g.key)
540
- }
541
- })),
547
+ () => Object.values(graphs).map(
548
+ (g) => ({
549
+ id: g.key,
550
+ type: "flowMapNode",
551
+ position: positions[g.key] ?? { x: 0, y: 0 },
552
+ data: {
553
+ label: g.label,
554
+ nodeCount: Object.keys(g.nodes).length,
555
+ isRoot: g.key === rootKey,
556
+ labels,
557
+ onOpen: () => onOpenFlow(g.key)
558
+ }
559
+ })
560
+ ),
542
561
  [graphs, positions, rootKey, onOpenFlow, labels]
543
562
  );
544
563
  const edges = useMemo(() => {
@@ -590,8 +609,26 @@ function FlowGroupHeader({ data }) {
590
609
  const { label, labels = DEFAULT_FLOW_EDITOR_LABELS, onFocus, onClose } = data;
591
610
  return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 rounded-full border border-cyan-300 dark:border-cyan-700 bg-cyan-50 dark:bg-cyan-950/50 px-3 py-1 text-xs font-medium text-cyan-800 dark:text-cyan-200 shadow-sm whitespace-nowrap", children: [
592
611
  /* @__PURE__ */ jsx5("span", { children: label }),
593
- /* @__PURE__ */ jsx5("button", { type: "button", onClick: onFocus, title: labels.flowGroup.focus, className: "hover:text-blue-600 dark:hover:text-blue-400", children: /* @__PURE__ */ jsx5(Maximize22, { size: 12 }) }),
594
- /* @__PURE__ */ jsx5("button", { type: "button", onClick: onClose, title: labels.flowGroup.close, className: "hover:text-red-600 dark:hover:text-red-400", children: /* @__PURE__ */ jsx5(X, { size: 12 }) })
612
+ /* @__PURE__ */ jsx5(
613
+ "button",
614
+ {
615
+ type: "button",
616
+ onClick: onFocus,
617
+ title: labels.flowGroup.focus,
618
+ className: "hover:text-blue-600 dark:hover:text-blue-400",
619
+ children: /* @__PURE__ */ jsx5(Maximize22, { size: 12 })
620
+ }
621
+ ),
622
+ /* @__PURE__ */ jsx5(
623
+ "button",
624
+ {
625
+ type: "button",
626
+ onClick: onClose,
627
+ title: labels.flowGroup.close,
628
+ className: "hover:text-red-600 dark:hover:text-red-400",
629
+ children: /* @__PURE__ */ jsx5(X, { size: 12 })
630
+ }
631
+ )
595
632
  ] });
596
633
  }
597
634
  var flowGroupHeaderNodeTypes = { flowGroupHeader: FlowGroupHeader };
@@ -753,7 +790,11 @@ import { Plus as Plus2, Trash2, Save, X as X2, AlertTriangle as AlertTriangle2,
753
790
  // src/flows/FlowWhatsAppPreview.tsx
754
791
  import { List } from "lucide-react";
755
792
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
756
- function FlowWhatsAppPreview({ body, options, labels = DEFAULT_FLOW_EDITOR_LABELS.nodePanel }) {
793
+ function FlowWhatsAppPreview({
794
+ body,
795
+ options,
796
+ labels = DEFAULT_FLOW_EDITOR_LABELS.nodePanel
797
+ }) {
757
798
  if (!body && (!options || options.length === 0)) {
758
799
  return /* @__PURE__ */ jsx8("p", { className: "text-xs text-gray-400 dark:text-gray-500 italic px-1", children: labels.previewPlaceholder });
759
800
  }
@@ -769,7 +810,14 @@ function FlowWhatsAppPreview({ body, options, labels = DEFAULT_FLOW_EDITOR_LABEL
769
810
  labels.previewListButton
770
811
  ] }) })
771
812
  ] }),
772
- hasOptions && usesButtons && /* @__PURE__ */ jsx8("div", { className: "mt-1 space-y-1", children: options.map(([id, label]) => /* @__PURE__ */ jsx8("div", { className: "rounded-lg bg-white dark:bg-gray-700 py-1.5 text-center text-sm font-medium text-cyan-600 dark:text-cyan-400 shadow-sm", children: label || /* @__PURE__ */ jsx8("span", { className: "italic text-gray-400", children: labels.previewEmptyOption }) }, id)) }),
813
+ hasOptions && usesButtons && /* @__PURE__ */ jsx8("div", { className: "mt-1 space-y-1", children: options.map(([id, label]) => /* @__PURE__ */ jsx8(
814
+ "div",
815
+ {
816
+ className: "rounded-lg bg-white dark:bg-gray-700 py-1.5 text-center text-sm font-medium text-cyan-600 dark:text-cyan-400 shadow-sm",
817
+ children: label || /* @__PURE__ */ jsx8("span", { className: "italic text-gray-400", children: labels.previewEmptyOption })
818
+ },
819
+ id
820
+ )) }),
773
821
  hasOptions && !usesButtons && /* @__PURE__ */ jsx8("div", { className: "mt-1.5 rounded-lg bg-white dark:bg-gray-700 shadow-sm divide-y divide-gray-100 dark:divide-gray-600 overflow-hidden", children: options.slice(0, WHATSAPP_LIMITS.MAX_LIST_ROWS).map(([id, label]) => /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 px-3 py-1.5 text-sm text-gray-800 dark:text-gray-100", children: [
774
822
  /* @__PURE__ */ jsx8("span", { className: "h-3.5 w-3.5 rounded-full border border-gray-300 dark:border-gray-500 shrink-0" }),
775
823
  label || /* @__PURE__ */ jsx8("span", { className: "italic text-gray-400", children: labels.previewEmptyOption })
@@ -831,17 +879,23 @@ function FlowNodePanel({
831
879
  onClose,
832
880
  onChange,
833
881
  onDelete,
834
- labels: labelsOverride
882
+ labels: labelsOverride,
883
+ renderMediaPicker
835
884
  }) {
836
885
  const labels = { ...DEFAULT_FLOW_EDITOR_LABELS, ...labelsOverride };
837
886
  const [draft, setDraft] = useState2(node);
838
887
  const otherNodeIds = Object.keys(graph.nodes).filter((id) => id !== node.id);
839
888
  const isFixedLogic = draft.type === "entrada_choice";
840
889
  const isAction = draft.type === "action";
890
+ const isSendMedia = isAction && draft.actionKind === BUILT_IN_ACTION_KINDS.SEND_MEDIA;
841
891
  const isCondition = draft.type === "condition";
842
892
  const isStart = graph.startNodeId === node.id;
843
893
  const nodeIssues = issues.filter((i) => i.nodeId === node.id);
844
- const knownContextKeys = [...new Set(Object.values(graph.nodes).map((n) => n.contextKey).filter((key) => !!key))];
894
+ const knownContextKeys = [
895
+ ...new Set(
896
+ Object.values(graph.nodes).map((n) => n.contextKey).filter((key) => !!key)
897
+ )
898
+ ];
845
899
  const conditionAnswerIds = isCondition ? ["true", "false"] : (draft.options ?? []).map(([id]) => id);
846
900
  function updateNextString(value) {
847
901
  setDraft((prev) => ({ ...prev, next: value }));
@@ -868,7 +922,10 @@ function FlowNodePanel({
868
922
  });
869
923
  }
870
924
  function addOption() {
871
- setDraft((prev) => ({ ...prev, options: [...prev.options ?? [], [String((prev.options?.length ?? 0) + 1), "Nova op\xE7\xE3o"]] }));
925
+ setDraft((prev) => ({
926
+ ...prev,
927
+ options: [...prev.options ?? [], [String((prev.options?.length ?? 0) + 1), "Nova op\xE7\xE3o"]]
928
+ }));
872
929
  }
873
930
  function removeOption(index) {
874
931
  setDraft((prev) => ({ ...prev, options: (prev.options ?? []).filter((_, i) => i !== index) }));
@@ -891,7 +948,14 @@ function FlowNodePanel({
891
948
  isCondition && /* @__PURE__ */ jsx9("p", { className: "text-xs text-cyan-700 dark:text-cyan-400 bg-cyan-50 dark:bg-cyan-950/30 rounded-lg p-2", children: labels.nodePanel.conditionNotice }),
892
949
  draft.contextKey && /* @__PURE__ */ jsxs8("div", { children: [
893
950
  /* @__PURE__ */ jsx9("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.contextKey }),
894
- /* @__PURE__ */ jsx9("input", { value: draft.contextKey, disabled: true, className: "w-full mt-1 rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 px-3 py-2.5 text-sm text-gray-500" })
951
+ /* @__PURE__ */ jsx9(
952
+ "input",
953
+ {
954
+ value: draft.contextKey,
955
+ disabled: true,
956
+ className: "w-full mt-1 rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 px-3 py-2.5 text-sm text-gray-500"
957
+ }
958
+ )
895
959
  ] }),
896
960
  !isFixedLogic && !isAction && !isCondition && /* @__PURE__ */ jsxs8("div", { children: [
897
961
  /* @__PURE__ */ jsx9("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.questionType }),
@@ -935,7 +999,10 @@ function FlowNodePanel({
935
999
  "select",
936
1000
  {
937
1001
  value: draft.conditionOperator ?? ">",
938
- onChange: (e) => setDraft((prev) => ({ ...prev, conditionOperator: e.target.value })),
1002
+ onChange: (e) => setDraft((prev) => ({
1003
+ ...prev,
1004
+ conditionOperator: e.target.value
1005
+ })),
939
1006
  className: `w-full mt-1 ${SELECT_CLASSNAME}`,
940
1007
  children: CONDITION_OPERATORS.map((operator) => /* @__PURE__ */ jsx9("option", { value: operator, children: labels.conditionOperatorLabels[operator] ?? operator }, operator))
941
1008
  }
@@ -963,7 +1030,11 @@ function FlowNodePanel({
963
1030
  labels
964
1031
  }
965
1032
  ),
966
- isAction && draft.actionKind !== "send_product_list" && /* @__PURE__ */ jsx9(
1033
+ isSendMedia && /* @__PURE__ */ jsxs8("div", { children: [
1034
+ /* @__PURE__ */ jsx9("label", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: labels.nodePanel.media }),
1035
+ /* @__PURE__ */ jsx9("div", { className: "mt-1", children: renderMediaPicker?.(node) ?? /* @__PURE__ */ jsx9("p", { className: "text-xs text-gray-400", children: labels.nodePanel.mediaUnavailable }) })
1036
+ ] }),
1037
+ isAction && draft.actionKind !== "send_product_list" && !isSendMedia && /* @__PURE__ */ jsx9(
967
1038
  WhatsAppTextField,
968
1039
  {
969
1040
  label: labels.nodePanel.directMessage,
@@ -1053,11 +1124,18 @@ function FlowNodePanel({
1053
1124
  ] })
1054
1125
  ] }),
1055
1126
  /* @__PURE__ */ jsxs8("div", { className: "p-4 border-t border-gray-100 dark:border-gray-700 flex gap-2", children: [
1056
- /* @__PURE__ */ jsxs8("button", { onClick: () => onChange(draft), className: "flex-1 inline-flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700", children: [
1057
- /* @__PURE__ */ jsx9(Save, { size: 14 }),
1058
- " ",
1059
- labels.nodePanel.save
1060
- ] }),
1127
+ /* @__PURE__ */ jsxs8(
1128
+ "button",
1129
+ {
1130
+ onClick: () => onChange(draft),
1131
+ className: "flex-1 inline-flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700",
1132
+ children: [
1133
+ /* @__PURE__ */ jsx9(Save, { size: 14 }),
1134
+ " ",
1135
+ labels.nodePanel.save
1136
+ ]
1137
+ }
1138
+ ),
1061
1139
  !isStart && /* @__PURE__ */ jsx9(
1062
1140
  "button",
1063
1141
  {