@gadmin2n/schematics 0.0.144 → 0.0.146

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 (280) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/config/.types.d.ts +10 -0
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/approval.prisma +2 -0
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +140 -19
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +2 -2
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/data-mngt.seed.ts +0 -2
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +694 -327
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-outbox.ts +49 -0
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +35 -5
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.service.ts +14 -24
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-executor.spec.ts +23 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-executor.ts +16 -5
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.controller.spec.ts +98 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.controller.ts +26 -6
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.dto.ts +17 -2
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-runtime.controller.spec.ts +1 -0
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-runtime.controller.ts +3 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-runtime.dto.ts +0 -2
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-signal-outbox.service.spec.ts +30 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-signal-outbox.service.ts +28 -6
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction.service.spec.ts +63 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction.service.ts +151 -38
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-wait.handler.ts +4 -1
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/human-approval.handler.spec.ts +16 -5
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/human-approval.handler.ts +23 -6
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/http-request-runtime.controller.spec.ts +130 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/http-request-runtime.controller.ts +62 -62
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/http-request-runtime.dto.ts +15 -1
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/http-request-runtime.util.spec.ts +70 -9
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/http-request-runtime.util.ts +31 -7
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-signal-outbox.service.spec.ts +30 -0
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-signal-outbox.service.ts +28 -6
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.spec.ts +50 -3
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.ts +26 -3
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.dto.ts +53 -47
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.errors.ts +0 -1
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.service.spec.ts +541 -0
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.service.ts +442 -200
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/outbox-retry.util.ts +1 -0
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.controller.spec.ts +150 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.controller.ts +56 -40
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.dto.ts +16 -60
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.controller.ts +13 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.spec.ts +44 -0
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.ts +6 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.spec.ts +258 -12
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.ts +127 -14
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/manual-workflow-execution.spec.ts +85 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/manual-workflow-execution.ts +108 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/parameter-binding.ts +5 -5
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.spec.ts +96 -25
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +77 -34
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-callable.spec.ts +23 -34
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-callable.ts +0 -21
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +245 -0
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +639 -188
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +408 -235
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.types.ts +1 -3
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-data-edge.util.spec.ts +279 -95
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-data-edge.util.ts +360 -80
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-catalog.spec.ts +323 -0
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution-idempotency.spec.ts +773 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +7 -13
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.dto.ts +0 -16
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.service.spec.ts +184 -0
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.service.ts +69 -138
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-handle-validator.spec.ts +113 -0
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-handle-validator.ts +59 -13
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-runtime.facade.ts +86 -0
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-switch-config.spec.ts +25 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-switch-config.ts +45 -0
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +78 -43
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +103 -40
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.dto.ts +57 -0
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.module.ts +3 -1
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +1000 -240
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +2184 -317
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.spec.ts +279 -2
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.ts +5 -1
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.spec.ts +4 -1
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.ts +5 -1
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/widget-catalog.service.spec.ts +12 -3
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/widget-catalog.service.ts +10 -1
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.spec.ts +2 -24
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.ts +3 -70
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +3 -1
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/RowActions.tsx +25 -5
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/WorkflowActionButton.test.tsx +71 -0
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/WorkflowActionButton.tsx +89 -0
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +1 -0
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +52 -12
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +12 -0
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/constants/layout.ts +2 -2
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/AddDataModal.tsx +10 -33
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/AddPageModal.tsx +13 -37
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/DevShell.tsx +55 -7
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/style.css +14 -0
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/http.ts +10 -0
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/list.tsx +1 -0
  99. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/workflowAction.ts +19 -0
  100. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/index.ts +1 -0
  101. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/types.ts +3 -1
  102. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +1 -6
  103. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useReferenceUpload.ts +95 -0
  104. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +2 -2
  105. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +672 -110
  106. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +2 -2
  107. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +676 -114
  108. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/plugins/devShellPlugin.ts +13 -1
  109. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +1 -0
  110. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +38 -4
  111. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +276 -376
  112. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/CanvasAiModal.tsx +126 -6
  113. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/human-approval/DecisionModal.test.tsx +50 -0
  114. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/human-approval/DecisionModal.tsx +48 -11
  115. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/human-approval/inbox.tsx +32 -34
  116. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/RegisterBusinessEventModal.test.tsx +111 -0
  117. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/RegisterBusinessEventModal.tsx +167 -0
  118. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/business-events.css +99 -0
  119. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.test.tsx +106 -0
  120. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.tsx +324 -0
  121. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.test.tsx +73 -0
  122. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.tsx +359 -0
  123. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CanvasZoomControls.tsx +46 -0
  124. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CreateNodeInstanceDrawer.tsx +9 -1
  125. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CustomNode.test.tsx +105 -4
  126. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CustomNode.tsx +90 -48
  127. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DeletableEdge.tsx +3 -2
  128. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.test.tsx +259 -0
  129. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +167 -103
  130. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/EnhancedFlowRenderer.tsx +142 -140
  131. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ExecutionStatusNode.test.tsx +60 -15
  132. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ExecutionStatusNode.tsx +25 -12
  133. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/FlowRenderer.tsx +364 -200
  134. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NewNodeDefaults.test.tsx +220 -52
  135. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeOutputSummary.test.tsx +260 -0
  136. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeOutputSummary.tsx +300 -130
  137. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +507 -33
  138. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +630 -592
  139. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ParameterBindingEditor.tsx +67 -70
  140. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ReadonlyNodePanel.test.tsx +90 -0
  141. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ReadonlyNodePanel.tsx +325 -0
  142. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/RunWorkflowModal.test.tsx +52 -0
  143. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/RunWorkflowModal.tsx +14 -9
  144. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/StatusCards.tsx +2 -1
  145. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/VersionPanel.tsx +2 -1
  146. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.test.tsx +42 -0
  147. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.tsx +76 -0
  148. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowWorkspaceHeader.tsx +56 -0
  149. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/parameter-binding-editor.css +46 -0
  150. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/workflow-code-editor.css +34 -0
  151. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/ConnectionModal.test.tsx +53 -7
  152. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/ConnectionModal.tsx +123 -62
  153. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connections.css +85 -0
  154. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connectionsApi.ts +29 -0
  155. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.test.tsx +75 -0
  156. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +69 -36
  157. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.test.tsx +494 -0
  158. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +665 -194
  159. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/hooks/useReferencedNodeInstanceConfigs.test.tsx +92 -0
  160. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/hooks/useReferencedNodeInstanceConfigs.ts +96 -0
  161. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/hooks/useWorkflowAgent.test.tsx +297 -0
  162. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/hooks/useWorkflowAgent.ts +135 -65
  163. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +53 -42
  164. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +344 -155
  165. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instances.tsx +79 -58
  166. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/CreateNodeInstanceModal.tsx +123 -144
  167. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/NodeInstanceForm.tsx +169 -98
  168. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/node-instance-form.css +91 -0
  169. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/index.tsx +54 -31
  170. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-config.css +854 -0
  171. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/schema-form/SchemaField.tsx +41 -14
  172. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/schema-form/SchemaForm.test.tsx +75 -0
  173. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/schema-form/SchemaForm.tsx +3 -0
  174. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/schema-form/default-widgets.tsx +20 -2
  175. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/schema-form/widget-registry.tsx +2 -1
  176. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +61 -66
  177. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ApprovalRuleEditor.test.tsx +33 -1
  178. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ApprovalRuleEditor.tsx +47 -15
  179. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/BranchConfigEditors.test.tsx +104 -0
  180. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CallableInputParametersEditor.tsx +22 -52
  181. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CodeConfigEditor.test.tsx +72 -0
  182. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CodeConfigEditor.tsx +78 -70
  183. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CronTriggerConfigEditor.tsx +569 -0
  184. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +130 -0
  185. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DelayConfigEditor.tsx +100 -0
  186. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.tsx +620 -0
  187. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalRequestConfigEditor.test.tsx +22 -45
  188. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalRequestConfigEditor.tsx +25 -92
  189. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalWaitConfigEditor.tsx +3 -11
  190. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ForEachConfigEditor.test.tsx +58 -0
  191. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ForEachConfigEditor.tsx +107 -0
  192. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HttpRequestConfigEditor.test.tsx +212 -31
  193. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HttpRequestConfigEditor.tsx +214 -304
  194. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.test.tsx +175 -40
  195. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.tsx +693 -293
  196. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/IfElseConfigEditor.tsx +190 -0
  197. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ManualTriggerConfigEditor.test.tsx +152 -0
  198. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ManualTriggerConfigEditor.tsx +286 -0
  199. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ParallelConfigEditor.tsx +101 -0
  200. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ParameterBindingListEditor.tsx +99 -0
  201. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +495 -0
  202. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubWorkflowConfigEditor.test.tsx +124 -0
  203. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubWorkflowConfigEditor.tsx +222 -0
  204. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.tsx +2 -4
  205. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SwitchConfigEditor.tsx +204 -0
  206. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/UserGroupCreateModal.tsx +3 -1
  207. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/UserGroupEditModal.tsx +9 -6
  208. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/callable-workflow.ts +4 -0
  209. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/code-config.css +96 -0
  210. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/event-catalog.api.ts +117 -0
  211. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/http-request-config.css +242 -0
  212. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/human-approval-config.css +194 -0
  213. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/show.tsx +264 -167
  214. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +6 -9
  215. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types/parameter-binding.ts +19 -16
  216. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +28 -4
  217. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/useWorkflowFormValidation.test.tsx +59 -0
  218. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/useWorkflowFormValidation.ts +24 -0
  219. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/forEachConnection.test.ts +311 -0
  220. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/forEachConnection.ts +198 -0
  221. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/mainFlowConnection.test.ts +143 -0
  222. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/mainFlowConnection.ts +19 -0
  223. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +103 -5
  224. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/removeWorkflowEdges.test.ts +158 -3
  225. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/removeWorkflowEdges.ts +117 -9
  226. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveEffectiveOutput.test.ts +70 -0
  227. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveEffectiveOutput.ts +48 -0
  228. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveLookupPorts.test.ts +55 -0
  229. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveLookupPorts.ts +95 -22
  230. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveOutputs.test.ts +148 -1
  231. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveOutputs.ts +62 -11
  232. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveWorkflowDisplay.test.ts +19 -0
  233. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveWorkflowDisplay.ts +1 -1
  234. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/subWorkflowChange.test.ts +45 -0
  235. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/subWorkflowChange.ts +26 -0
  236. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/validateWorkflowDsl.test.ts +502 -0
  237. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/validateWorkflowDsl.ts +694 -0
  238. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/workflowDateTime.test.ts +27 -0
  239. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/workflowDateTime.ts +33 -0
  240. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/workflowHistory.test.ts +34 -0
  241. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/workflowHistory.ts +52 -0
  242. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/workflowPublishError.test.ts +99 -0
  243. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/workflowPublishError.ts +91 -0
  244. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/workflowSkillPrompt.test.ts +74 -0
  245. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/workflowSkillPrompt.ts +120 -0
  246. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/workflow-ui.css +481 -0
  247. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/index.css +1 -1
  248. package/package.json +1 -1
  249. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/WorkflowEventOutbox.ts +0 -62
  250. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/migrate-human-approval-configs.ts +0 -86
  251. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-event-trigger.ts +0 -60
  252. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +0 -698
  253. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.external-decision.spec.ts +0 -183
  254. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/legacy-approval-config-migration.ts +0 -227
  255. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-http-response.spec.ts +0 -82
  256. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-http-response.ts +0 -111
  257. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.spec.ts +0 -54
  258. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.ts +0 -147
  259. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.module.ts +0 -10
  260. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.spec.ts +0 -350
  261. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.ts +0 -110
  262. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.ts +0 -1
  263. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/InstanceRequiredDialog.tsx +0 -106
  264. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/LookupConnectionDialog.test.tsx +0 -45
  265. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/LookupConnectionDialog.tsx +0 -43
  266. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeTypeBadge.tsx +0 -36
  267. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/hooks/useNodeInstanceLibrary.ts +0 -41
  268. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ApproverSourceEditor.test.tsx +0 -47
  269. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ApproverSourceEditor.tsx +0 -182
  270. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ReturnResultConfigEditor.test.tsx +0 -34
  271. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ReturnResultConfigEditor.tsx +0 -16
  272. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/approver-source.test.ts +0 -25
  273. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/approver-source.ts +0 -42
  274. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/lookupConnection.test.ts +0 -208
  275. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/lookupConnection.ts +0 -246
  276. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/create.tsx +0 -111
  277. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/edit.tsx +0 -127
  278. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/index.tsx +0 -4
  279. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/list.tsx +0 -282
  280. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/show.tsx +0 -74
@@ -1,13 +1,14 @@
1
1
  import {
2
- DeleteOutlined,
3
2
  DisconnectOutlined,
4
- LinkOutlined,
5
3
  MinusCircleOutlined,
6
4
  PlusOutlined,
5
+ SearchOutlined,
7
6
  SendOutlined,
8
7
  } from '@ant-design/icons';
9
8
  import {
9
+ Alert,
10
10
  Button,
11
+ Card,
11
12
  Checkbox,
12
13
  Form,
13
14
  Input,
@@ -18,33 +19,49 @@ import {
18
19
  Space,
19
20
  Spin,
20
21
  Switch,
22
+ Tag,
21
23
  Tooltip,
22
24
  Typography,
23
25
  } from 'antd';
24
26
  import { customRequest } from 'helpers/http';
25
- import { useEffect, useState } from 'react';
27
+ import type { CSSProperties, ForwardedRef } from 'react';
28
+ import {
29
+ forwardRef,
30
+ useCallback,
31
+ useEffect,
32
+ useImperativeHandle,
33
+ useMemo,
34
+ useState,
35
+ } from 'react';
26
36
  import { useTranslation } from 'react-i18next';
27
37
  import { SchemaForm } from '../node-instances/schema-form/SchemaForm';
38
+ import { useWorkflowFormValidation } from '../useWorkflowFormValidation';
28
39
  import type {
40
+ CallableWorkflowOption,
41
+ WorkflowEdge,
29
42
  WorkflowNode,
30
43
  WorkflowNodeInstance,
31
44
  WorkflowNodeType,
32
45
  } from '../types';
33
- import type { ParameterBinding } from '../types/parameter-binding';
34
46
  import { NodeOutputSummary } from './NodeOutputSummary';
35
- import { ParameterBindingEditor } from './ParameterBindingEditor';
47
+ import { CreateNodeInstanceDrawer } from './CreateNodeInstanceDrawer';
48
+ import { WorkflowCodeEditor } from './WorkflowCodeEditor';
36
49
  import '../node-instances/node-config.css';
50
+ import type { ReferencedConfigStatus } from '../hooks/useReferencedNodeInstanceConfigs';
37
51
 
38
52
  const { Text, Title } = Typography;
39
53
  const { TextArea } = Input;
40
54
 
41
- const CATEGORY_COLORS: Record<string, { bg: string; border: string }> = {
42
- TRIGGER: { bg: '#e6f7ff', border: '#91caff' },
43
- ACTION: { bg: '#f6ffed', border: '#b7eb8f' },
44
- CONDITION: { bg: '#fff7e6', border: '#ffd591' },
45
- FLOW_CONTROL: { bg: '#f9f0ff', border: '#d3adf7' },
46
- APPROVAL: { bg: '#fff1f0', border: '#ffa39e' },
47
- SUB_WORKFLOW: { bg: '#f0f5ff', border: '#adc6ff' },
55
+ const CATEGORY_COLORS: Record<
56
+ string,
57
+ { accent: string; bg: string; border: string }
58
+ > = {
59
+ TRIGGER: { accent: '#1677ff', bg: '#e6f7ff', border: '#91caff' },
60
+ ACTION: { accent: '#389e0d', bg: '#f6ffed', border: '#b7eb8f' },
61
+ CONDITION: { accent: '#d46b08', bg: '#fff7e6', border: '#ffd591' },
62
+ FLOW_CONTROL: { accent: '#722ed1', bg: '#f9f0ff', border: '#d3adf7' },
63
+ APPROVAL: { accent: '#cf1322', bg: '#fff1f0', border: '#ffa39e' },
64
+ SUB_WORKFLOW: { accent: '#2f54eb', bg: '#f0f5ff', border: '#adc6ff' },
48
65
  };
49
66
 
50
67
  const CATEGORY_LABELS: Record<string, string> = {
@@ -65,7 +82,6 @@ const TEXTAREA_FIELDS = new Set([
65
82
  'headers',
66
83
  'inputBindings',
67
84
  'script',
68
- 'rawSql',
69
85
  'data',
70
86
  'responseBody',
71
87
  ]);
@@ -87,15 +103,11 @@ function hasUiSchemaWidget(value: unknown): boolean {
87
103
 
88
104
  // Placeholder hints for specific fields
89
105
  const FIELD_PLACEHOLDERS: Record<string, string> = {
90
- cron: '0 0 * * * (每天零点)',
91
106
  timezone: 'Asia/Shanghai',
92
- eventName: 'order.created',
93
107
  path: '/api/webhook/my-hook',
94
108
  url: 'https://api.example.com/endpoint',
95
109
  query: 'SELECT * FROM table WHERE id = $1',
96
- rawSql: 'INSERT INTO table (col) VALUES ($1)',
97
110
  condition: 'data.amount > 1000',
98
- iteratorField: 'data.items',
99
111
  message: '通知内容,支持 {{variable}} 模板变量',
100
112
  subject: '邮件主题',
101
113
  field: 'data.status',
@@ -107,59 +119,75 @@ const FIELD_PLACEHOLDERS: Record<string, string> = {
107
119
  datasource: 'default',
108
120
  };
109
121
 
122
+ type NodePropertyUpdates = {
123
+ label?: string;
124
+ config?: Record<string, any>;
125
+ configOverride?: Record<string, any> | null;
126
+ instanceRef?: WorkflowNode['instanceRef'] | null;
127
+ };
128
+
129
+ export interface NodePropertyPanelHandle {
130
+ getPendingUpdate: () => {
131
+ nodeId: string;
132
+ updates: NodePropertyUpdates;
133
+ } | null;
134
+ markSaved: () => void;
135
+ }
136
+
110
137
  interface Props {
111
138
  node: WorkflowNode | null;
112
139
  nodeTypes: WorkflowNodeType[];
140
+ nodes?: WorkflowNode[];
141
+ edges?: WorkflowEdge[];
113
142
  currentWorkflowId?: string;
114
- onSave: (
115
- nodeId: string,
116
- updates: {
117
- label?: string;
118
- config?: Record<string, any>;
119
- configOverride?: Record<string, any> | null;
120
- instanceRef?: WorkflowNode['instanceRef'] | null;
121
- },
122
- ) => void;
143
+ onSave: (nodeId: string, updates: NodePropertyUpdates) => void;
144
+ onConfigDraftChange?: (nodeId: string, config: Record<string, any>) => void;
123
145
  onLabelChange: (nodeId: string, label: string) => void;
124
146
  onAiEdit: (nodeId: string, prompt: string) => void;
125
- onDelete?: (nodeId: string) => void;
126
147
  onAbort?: () => void;
127
148
  aiLoading: boolean;
149
+ referencedConfigs?: Record<string, Record<string, any>>;
150
+ referencedConfigStatus?: ReferencedConfigStatus;
151
+ onRetryReferencedConfigs?: () => void;
152
+ callableWorkflows?: CallableWorkflowOption[];
153
+ callableCatalogStatus?: 'loading' | 'ready' | 'error';
154
+ onRetryCallableCatalog?: () => void;
128
155
  }
129
156
 
130
- export function NodePropertyPanel({
131
- node,
132
- nodeTypes,
133
- currentWorkflowId,
134
- onSave,
135
- onLabelChange,
136
- onAiEdit,
137
- onDelete,
138
- onAbort,
139
- aiLoading,
140
- }: Props) {
157
+ function NodePropertyPanelImpl(
158
+ {
159
+ node,
160
+ nodeTypes,
161
+ nodes,
162
+ edges,
163
+ currentWorkflowId,
164
+ onSave,
165
+ onConfigDraftChange,
166
+ onLabelChange,
167
+ onAiEdit,
168
+ onAbort,
169
+ aiLoading,
170
+ referencedConfigs = {},
171
+ referencedConfigStatus = 'ready',
172
+ onRetryReferencedConfigs,
173
+ callableWorkflows,
174
+ callableCatalogStatus,
175
+ onRetryCallableCatalog,
176
+ }: Props,
177
+ ref: ForwardedRef<NodePropertyPanelHandle>,
178
+ ) {
141
179
  const { t, i18n } = useTranslation('workflow');
180
+ const formValidation = useWorkflowFormValidation();
142
181
  const [label, setLabel] = useState('');
143
182
  const [config, setConfig] = useState<Record<string, any>>({});
144
183
  const [prompt, setPrompt] = useState('');
145
184
  const [dirty, setDirty] = useState(false);
146
185
  const [activeTab, setActiveTab] = useState<'config' | 'ai'>('config');
147
- const [workflowOptions, setWorkflowOptions] = useState<
148
- {
149
- label: string;
150
- value: string;
151
- inputParameters: Array<{
152
- key: string;
153
- label?: string;
154
- required?: boolean;
155
- }>;
156
- }[]
157
- >([]);
158
186
  const [instances, setInstances] = useState<WorkflowNodeInstance[]>([]);
159
- const [selectedInstanceId, setSelectedInstanceId] = useState<string | null>(
160
- null,
161
- );
162
187
  const [instanceLoading, setInstanceLoading] = useState(false);
188
+ const [instanceLoadError, setInstanceLoadError] = useState(false);
189
+ const [createInstanceOpen, setCreateInstanceOpen] = useState(false);
190
+ const [paletteQuery, setPaletteQuery] = useState('');
163
191
  const [newInstanceName, setNewInstanceName] = useState('');
164
192
  const [newInstanceDesc, setNewInstanceDesc] = useState('');
165
193
  const [saveAsLoading, setSaveAsLoading] = useState(false);
@@ -172,7 +200,7 @@ export function NodePropertyPanel({
172
200
  ? t(`nodeTypes.${currentNodeType.type}`, {
173
201
  defaultValue: currentNodeType.label,
174
202
  })
175
- : node?.type;
203
+ : node?.label;
176
204
  const currentNodeTypeColors =
177
205
  CATEGORY_COLORS[currentNodeType?.category ?? 'ACTION'] ??
178
206
  CATEGORY_COLORS.ACTION;
@@ -198,16 +226,22 @@ export function NodePropertyPanel({
198
226
  },
199
227
  };
200
228
  }
201
- if (currentNodeType?.type === 'return_result') {
229
+ if (currentNodeType?.type === 'webhook_trigger') {
230
+ return {
231
+ ...currentUiSchema,
232
+ widget: 'external-request-config',
233
+ };
234
+ }
235
+ if (currentNodeType?.type === 'manual_trigger') {
202
236
  return {
203
237
  ...currentUiSchema,
204
- widget: 'return-result-config',
238
+ widget: 'manual-trigger-config',
205
239
  };
206
240
  }
207
- if (currentNodeType?.type === 'webhook_trigger') {
241
+ if (currentNodeType?.type === 'cron_trigger') {
208
242
  return {
209
243
  ...currentUiSchema,
210
- widget: 'external-request-config',
244
+ widget: 'cron-trigger-config',
211
245
  };
212
246
  }
213
247
  if (currentNodeType?.type === 'external_wait') {
@@ -216,19 +250,93 @@ export function NodePropertyPanel({
216
250
  widget: 'external-wait-config',
217
251
  };
218
252
  }
253
+ if (
254
+ currentNodeType?.type === 'for_each' ||
255
+ currentNodeType?.type === 'sub_workflow' ||
256
+ currentNodeType?.type === 'approval'
257
+ ) {
258
+ return {
259
+ ...currentUiSchema,
260
+ widget:
261
+ currentNodeType.type === 'for_each'
262
+ ? 'for-each-config'
263
+ : currentNodeType.type === 'sub_workflow'
264
+ ? 'sub-workflow-config'
265
+ : currentUiSchema.widget,
266
+ options: {
267
+ ...(currentUiSchema.options ?? {}),
268
+ currentWorkflowId,
269
+ callableWorkflows,
270
+ callableCatalogStatus,
271
+ onRetryCallableCatalog,
272
+ },
273
+ };
274
+ }
219
275
  return currentNodeType?.uiSchema;
220
276
  })();
221
277
  const usesCustomConfigEditor = hasUiSchemaWidget(effectiveUiSchema);
222
- const referencedInstance = instances.find(
223
- (instance) => instance.id === node?.instanceRef?.instanceId,
278
+ const usesTopLevelApprovalSections =
279
+ node?.type === 'approval' && usesCustomConfigEditor;
280
+ const referencedConfig = node?.instanceRef
281
+ ? referencedConfigs[node.instanceRef.instanceId]
282
+ : undefined;
283
+ const referencedConfigUnavailable = Boolean(
284
+ node?.instanceRef &&
285
+ node.configOverride == null &&
286
+ referencedConfigStatus !== 'ready',
224
287
  );
225
288
  const effectiveNodeConfig =
226
- node?.configOverride ?? referencedInstance?.config ?? node?.config ?? {};
289
+ node?.configOverride ?? referencedConfig ?? node?.config ?? {};
290
+ const effectiveNode = node ? { ...node, config: effectiveNodeConfig } : null;
227
291
  useEffect(() => {
228
292
  if (node) {
229
293
  setLabel(node.label);
230
294
  // Merge existing config with schema defaults
231
- const mergedConfig: Record<string, any> = {};
295
+ const mergedConfig: Record<string, any> = { ...effectiveNodeConfig };
296
+ if (!usesCustomConfigEditor) {
297
+ for (const [key, prop] of Object.entries<any>(properties)) {
298
+ const existingValue = effectiveNodeConfig[key];
299
+ if (existingValue !== undefined) {
300
+ mergedConfig[key] = existingValue;
301
+ } else if (prop.type === 'string') {
302
+ mergedConfig[key] = '';
303
+ } else if (prop.type === 'number') {
304
+ mergedConfig[key] = undefined;
305
+ } else if (prop.type === 'boolean') {
306
+ mergedConfig[key] = false;
307
+ } else if (prop.type === 'array') {
308
+ mergedConfig[key] = [];
309
+ } else if (prop.type === 'object') {
310
+ mergedConfig[key] = {};
311
+ }
312
+ }
313
+ }
314
+ if (node.type === 'webhook_trigger') {
315
+ mergedConfig.method = 'POST';
316
+ }
317
+ setConfig(mergedConfig);
318
+ configForm.setFields([
319
+ {
320
+ name: ['config'],
321
+ value: mergedConfig,
322
+ errors: [],
323
+ warnings: [],
324
+ touched: false,
325
+ },
326
+ ]);
327
+ setDirty(false);
328
+ setPrompt('');
329
+ setAlsoSaveAsInstance(false);
330
+ }
331
+ }, [node?.id, currentNodeType]);
332
+
333
+ // The Workflow DSL is authoritative. Keep the panel synchronized even when
334
+ // its previous local edit was still marked dirty.
335
+ useEffect(() => {
336
+ if (!node) return;
337
+ setLabel(node.label);
338
+ const mergedConfig: Record<string, any> = { ...effectiveNodeConfig };
339
+ if (!usesCustomConfigEditor) {
232
340
  for (const [key, prop] of Object.entries<any>(properties)) {
233
341
  const existingValue = effectiveNodeConfig[key];
234
342
  if (existingValue !== undefined) {
@@ -245,100 +353,112 @@ export function NodePropertyPanel({
245
353
  mergedConfig[key] = {};
246
354
  }
247
355
  }
248
- if (node.type === 'webhook_trigger') {
249
- mergedConfig.method = 'POST';
250
- }
251
- setConfig(mergedConfig);
252
- configForm.resetFields();
253
- configForm.setFieldsValue({ config: mergedConfig });
254
- setDirty(false);
255
- setPrompt('');
256
- setAlsoSaveAsInstance(false);
257
- }
258
- }, [node?.id, currentNodeType]);
259
-
260
- // Sync form when node config/label updates externally (e.g. AI edit)
261
- useEffect(() => {
262
- if (!node || dirty) return;
263
- setLabel(node.label);
264
- const mergedConfig: Record<string, any> = {};
265
- for (const [key, prop] of Object.entries<any>(properties)) {
266
- const existingValue = effectiveNodeConfig[key];
267
- if (existingValue !== undefined) {
268
- mergedConfig[key] = existingValue;
269
- } else if (prop.type === 'string') {
270
- mergedConfig[key] = '';
271
- } else if (prop.type === 'number') {
272
- mergedConfig[key] = undefined;
273
- } else if (prop.type === 'boolean') {
274
- mergedConfig[key] = false;
275
- } else if (prop.type === 'array') {
276
- mergedConfig[key] = [];
277
- } else if (prop.type === 'object') {
278
- mergedConfig[key] = {};
279
- }
280
356
  }
281
357
  setConfig(mergedConfig);
282
- configForm.resetFields();
283
- configForm.setFieldsValue({ config: mergedConfig });
358
+ configForm.setFields([
359
+ {
360
+ name: ['config'],
361
+ value: mergedConfig,
362
+ errors: [],
363
+ warnings: [],
364
+ touched: false,
365
+ },
366
+ ]);
284
367
  // eslint-disable-next-line react-hooks/exhaustive-deps
285
368
  }, [node?.label, JSON.stringify(effectiveNodeConfig)]);
286
369
 
287
- // Load workflow list for Sub Workflow node
288
- useEffect(() => {
289
- if (node?.type === 'sub_workflow') {
290
- customRequest<any[]>('workflow/callable', 'GET')
291
- .then((res) => {
292
- const list = Array.isArray(res) ? res : [];
293
- setWorkflowOptions(
294
- list
295
- .filter((w: any) => String(w.id) !== currentWorkflowId)
296
- .map((w: any) => ({
297
- label: w.name,
298
- value: String(w.id),
299
- inputParameters: Array.isArray(w.inputParameters)
300
- ? w.inputParameters
301
- : [],
302
- })),
303
- );
304
- })
305
- .catch(() => {});
306
- }
307
- }, [node?.type, currentWorkflowId]);
308
-
309
370
  // Load instances for current node type
310
- useEffect(() => {
311
- if (
312
- !currentNodeType?.id ||
313
- currentNodeType.instancePolicy === 'INLINE_ONLY'
314
- ) {
371
+ const loadInstances = useCallback(async () => {
372
+ if (!currentNodeType?.id || node?.instanceRef) {
315
373
  setInstances([]);
374
+ setInstanceLoadError(false);
316
375
  return;
317
376
  }
318
377
  setInstanceLoading(true);
319
- customRequest<{ data: any[] }>('workflowNodeInstance/findMany', 'POST', {
320
- where: { nodeTypeId: Number(currentNodeType.id), isEnabled: true },
321
- take: 100,
322
- })
323
- .then((res) => {
324
- const list = Array.isArray(res) ? res : res?.data || [];
325
- setInstances(list);
326
- })
327
- .catch(() => setInstances([]))
328
- .finally(() => setInstanceLoading(false));
329
- }, [currentNodeType?.id, currentNodeType?.instancePolicy]);
378
+ try {
379
+ const res = await customRequest<{ data: any[] }>(
380
+ 'workflowNodeInstance/findMany',
381
+ 'POST',
382
+ {
383
+ where: { nodeTypeId: Number(currentNodeType.id), isEnabled: true },
384
+ take: 100,
385
+ },
386
+ );
387
+ const list = Array.isArray(res) ? res : res?.data || [];
388
+ setInstances(list);
389
+ setInstanceLoadError(false);
390
+ } catch {
391
+ setInstances([]);
392
+ setInstanceLoadError(true);
393
+ } finally {
394
+ setInstanceLoading(false);
395
+ }
396
+ }, [currentNodeType?.id, node?.instanceRef]);
397
+
398
+ useEffect(() => void loadInstances(), [loadInstances]);
399
+
400
+ const visibleNodeTypes = useMemo(() => {
401
+ const normalizedQuery = paletteQuery.trim().toLocaleLowerCase();
402
+ if (!normalizedQuery) return nodeTypes;
403
+ return nodeTypes.filter((nodeType) => {
404
+ const translated = t(`nodeTypes.${nodeType.type}`, {
405
+ defaultValue: nodeType.label,
406
+ });
407
+ return [translated, nodeType.label, nodeType.description, nodeType.type]
408
+ .filter(Boolean)
409
+ .some((value) =>
410
+ String(value).toLocaleLowerCase().includes(normalizedQuery),
411
+ );
412
+ });
413
+ }, [nodeTypes, paletteQuery, t]);
330
414
 
331
415
  // Reset selection when node changes
332
416
  useEffect(() => {
333
- setSelectedInstanceId(null);
334
417
  setAlsoSaveAsInstance(false);
418
+ setCreateInstanceOpen(false);
335
419
  }, [node?.id]);
336
420
 
421
+ const buildCurrentUpdates = useCallback((): NodePropertyUpdates | null => {
422
+ if (!node || referencedConfigUnavailable) return null;
423
+ const currentConfig = usesCustomConfigEditor
424
+ ? (configForm.getFieldValue('config') ?? {})
425
+ : config;
426
+ const cleanConfig: Record<string, any> = {};
427
+ for (const [key, value] of Object.entries(currentConfig)) {
428
+ if (value !== undefined && value !== '') cleanConfig[key] = value;
429
+ }
430
+ return {
431
+ label,
432
+ config: cleanConfig,
433
+ ...(node.instanceRef && { configOverride: cleanConfig }),
434
+ };
435
+ }, [
436
+ config,
437
+ configForm,
438
+ label,
439
+ node,
440
+ referencedConfigUnavailable,
441
+ usesCustomConfigEditor,
442
+ ]);
443
+
444
+ useImperativeHandle(
445
+ ref,
446
+ () => ({
447
+ getPendingUpdate: () => {
448
+ const updates = buildCurrentUpdates();
449
+ return dirty && node && updates ? { nodeId: node.id, updates } : null;
450
+ },
451
+ markSaved: () => setDirty(false),
452
+ }),
453
+ [buildCurrentUpdates, dirty, node],
454
+ );
455
+
337
456
  if (!node) {
338
457
  // Panel state: generating
339
458
  if (aiLoading) {
340
459
  return (
341
460
  <div
461
+ className="workflow-node-panel"
342
462
  style={{
343
463
  width: 320,
344
464
  display: 'flex',
@@ -368,6 +488,7 @@ export function NodePropertyPanel({
368
488
 
369
489
  return (
370
490
  <div
491
+ className="workflow-node-panel"
371
492
  style={{
372
493
  width: 320,
373
494
  display: 'flex',
@@ -379,90 +500,87 @@ export function NodePropertyPanel({
379
500
  >
380
501
  {/* Node palette */}
381
502
  <div style={{ flex: 1, overflow: 'auto', padding: 16 }}>
382
- <Text
383
- type="secondary"
384
- style={{ fontSize: 13, display: 'block', marginBottom: 12 }}
385
- >
386
- 拖拽节点到左侧画布
387
- </Text>
503
+ <div className="workflow-node-palette__intro">
504
+ <Text strong style={{ display: 'block', marginBottom: 3 }}>
505
+ {t('editor.addNode')}
506
+ </Text>
507
+ <Text
508
+ type="secondary"
509
+ style={{ fontSize: 12, display: 'block', marginBottom: 10 }}
510
+ >
511
+ {t('editor.dragNodes')}
512
+ </Text>
513
+ <Input
514
+ allowClear
515
+ prefix={<SearchOutlined />}
516
+ value={paletteQuery}
517
+ onChange={(event) => setPaletteQuery(event.target.value)}
518
+ placeholder={t('editor.searchNodes')}
519
+ />
520
+ </div>
388
521
  {Object.entries(
389
- nodeTypes.reduce<Record<string, WorkflowNodeType[]>>((acc, nt) => {
390
- const cat = nt.category || 'ACTION';
391
- if (!acc[cat]) acc[cat] = [];
392
- acc[cat].push(nt);
393
- return acc;
394
- }, {}),
522
+ visibleNodeTypes.reduce<Record<string, WorkflowNodeType[]>>(
523
+ (acc, nt) => {
524
+ const cat = nt.category || 'ACTION';
525
+ if (!acc[cat]) acc[cat] = [];
526
+ acc[cat].push(nt);
527
+ return acc;
528
+ },
529
+ {},
530
+ ),
395
531
  ).map(([category, types]) => {
396
532
  const colors = CATEGORY_COLORS[category] || CATEGORY_COLORS.ACTION;
533
+ const categoryLabel = t(`nodeCategories.${category}`, {
534
+ defaultValue: CATEGORY_LABELS[category] || category,
535
+ });
397
536
  return (
398
- <div key={category} style={{ marginBottom: 12 }}>
399
- <Text
400
- strong
401
- style={{
402
- fontSize: 12,
403
- color: 'var(--ant-color-text-secondary)',
404
- display: 'block',
405
- marginBottom: 6,
406
- }}
407
- >
408
- {CATEGORY_LABELS[category] || category}
409
- </Text>
410
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
537
+ <div key={category} className="workflow-node-palette__category">
538
+ <div className="workflow-node-palette__category-title">
539
+ <span
540
+ className="workflow-node-palette__category-dot"
541
+ style={{ background: colors.border }}
542
+ />
543
+ {categoryLabel}
544
+ </div>
545
+ <div className="workflow-node-palette__grid">
411
546
  {types.map((nt) => (
412
- <Tooltip
547
+ <div
413
548
  key={nt.type}
414
- title={
415
- <div
416
- style={{
417
- whiteSpace: 'pre-line',
418
- fontSize: 12,
419
- lineHeight: 1.6,
420
- }}
421
- >
422
- {nt.description || nt.label}
423
- </div>
424
- }
425
- placement="left"
426
- mouseEnterDelay={0.3}
427
- styles={{ root: { maxWidth: 320 } }}
549
+ draggable
550
+ className="workflow-node-palette__item"
551
+ style={{ borderColor: colors.border }}
552
+ onDragStart={(e) => {
553
+ e.dataTransfer.setData(
554
+ 'application/workflow-node-type',
555
+ JSON.stringify(nt),
556
+ );
557
+ e.dataTransfer.effectAllowed = 'move';
558
+ }}
428
559
  >
429
- <div
430
- draggable
431
- onDragStart={(e) => {
432
- e.dataTransfer.setData(
433
- 'application/workflow-node-type',
434
- JSON.stringify(nt),
435
- );
436
- e.dataTransfer.effectAllowed = 'move';
437
- }}
438
- style={{
439
- cursor: 'grab',
440
- padding: '5px 10px',
441
- background: colors.bg,
442
- border: `1px solid ${colors.border}`,
443
- borderRadius: 6,
444
- fontSize: 12,
445
- userSelect: 'none',
446
- }}
447
- >
560
+ <Text strong style={{ fontSize: 12 }}>
448
561
  {t(`nodeTypes.${nt.type}`, {
449
562
  defaultValue: nt.label,
450
563
  })}
451
- </div>
452
- </Tooltip>
564
+ </Text>
565
+ </div>
453
566
  ))}
454
567
  </div>
455
568
  </div>
456
569
  );
457
570
  })}
571
+ {visibleNodeTypes.length === 0 && (
572
+ <Text type="secondary">{t('editor.noMatchingNodes')}</Text>
573
+ )}
458
574
  </div>
459
575
  </div>
460
576
  );
461
577
  }
462
578
 
463
579
  function handleConfigChange(key: string, value: any) {
464
- setConfig((prev) => ({ ...prev, [key]: value }));
580
+ const nextConfig = { ...config, [key]: value };
581
+ setConfig(nextConfig);
465
582
  setDirty(true);
583
+ if (node) onConfigDraftChange?.(node.id, nextConfig);
466
584
  }
467
585
 
468
586
  function handleLabelChange(value: string) {
@@ -474,14 +592,9 @@ export function NodePropertyPanel({
474
592
 
475
593
  async function handleSave() {
476
594
  if (!node) return;
477
- const currentConfig = usesCustomConfigEditor
478
- ? (configForm.getFieldValue('config') ?? {})
479
- : config;
480
- // Clean undefined values
481
- const cleanConfig: Record<string, any> = {};
482
- for (const [k, v] of Object.entries(currentConfig)) {
483
- if (v !== undefined && v !== '') cleanConfig[k] = v;
484
- }
595
+ const updates = buildCurrentUpdates();
596
+ if (!updates?.config) return;
597
+ const cleanConfig = updates.config;
485
598
  if (
486
599
  !node.instanceRef &&
487
600
  alsoSaveAsInstance &&
@@ -524,11 +637,7 @@ export function NodePropertyPanel({
524
637
  setSaveAsLoading(false);
525
638
  }
526
639
  } else {
527
- onSave(node.id, {
528
- label,
529
- config: cleanConfig,
530
- ...(node.instanceRef && { configOverride: cleanConfig }),
531
- });
640
+ onSave(node.id, updates);
532
641
  message.success('节点已保存');
533
642
  }
534
643
  setDirty(false);
@@ -540,10 +649,8 @@ export function NodePropertyPanel({
540
649
  setPrompt('');
541
650
  }
542
651
 
543
- function handleReferenceInstance() {
544
- if (!node || !selectedInstanceId) return;
545
- const inst = instances.find((i) => i.id === selectedInstanceId);
546
- if (!inst) return;
652
+ function referenceInstance(inst: WorkflowNodeInstance) {
653
+ if (!node) return;
547
654
  onSave(node.id, {
548
655
  label: inst.name,
549
656
  config: inst.config,
@@ -554,21 +661,6 @@ export function NodePropertyPanel({
554
661
  },
555
662
  });
556
663
  setLabel(inst.name);
557
- setSelectedInstanceId(null);
558
- }
559
-
560
- function handleCopyAsTemplate() {
561
- if (!node || !selectedInstanceId) return;
562
- const inst = instances.find((i) => i.id === selectedInstanceId);
563
- if (!inst) return;
564
- setConfig(inst.config);
565
- setLabel(inst.name);
566
- setDirty(true);
567
- setSelectedInstanceId(null);
568
- if (node) {
569
- onLabelChange(node.id, inst.name);
570
- }
571
- message.success('已加载实例配置,可自由修改');
572
664
  }
573
665
 
574
666
  function handleDetachReference() {
@@ -592,115 +684,13 @@ export function NodePropertyPanel({
592
684
  })
593
685
  : (schema.title ?? key);
594
686
 
595
- // Special: workflowId → Select with workflow list
596
- if (key === 'workflowId') {
597
- return (
598
- <div key={key}>
599
- <Text
600
- type="secondary"
601
- style={{ fontSize: 13, display: 'block', marginBottom: 2 }}
602
- >
603
- {isRequired && <span style={{ color: '#ff4d4f' }}>* </span>}
604
- {fieldLabel}
605
- </Text>
606
- <Select
607
- value={value || undefined}
608
- onChange={(v) => {
609
- const selected = workflowOptions.find(
610
- (workflow) => workflow.value === v,
611
- );
612
- setConfig((current) => ({
613
- ...current,
614
- workflowId: v,
615
- inputBindings: Object.fromEntries(
616
- (selected?.inputParameters ?? []).map((parameter) => [
617
- parameter.key,
618
- { source: 'WORKFLOW', path: parameter.key },
619
- ]),
620
- ),
621
- }));
622
- setDirty(true);
623
- }}
624
- placeholder="选择子流程"
625
- options={workflowOptions}
626
- style={{ width: '100%' }}
627
- allowClear
628
- showSearch
629
- filterOption={(input, option) =>
630
- (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
631
- }
632
- />
633
- </div>
634
- );
635
- }
636
-
637
- if (key === 'inputBindings' && node?.type === 'sub_workflow') {
638
- const selected = workflowOptions.find(
639
- (workflow) => workflow.value === config.workflowId,
640
- );
641
- if (!selected) {
642
- return (
643
- <Text key={key} type="secondary">
644
- 请先选择要调用的 Workflow
645
- </Text>
646
- );
647
- }
648
- if (selected.inputParameters.length === 0) {
649
- return (
650
- <Text key={key} type="secondary">
651
- 这个 Workflow 不需要调用参数
652
- </Text>
653
- );
654
- }
655
- const bindings =
656
- config.inputBindings &&
657
- typeof config.inputBindings === 'object' &&
658
- !Array.isArray(config.inputBindings)
659
- ? (config.inputBindings as Record<string, ParameterBinding>)
660
- : {};
661
- return (
662
- <Space
663
- key={key}
664
- direction="vertical"
665
- size={12}
666
- style={{ width: '100%' }}
667
- >
668
- <Text strong>调用参数</Text>
669
- {selected.inputParameters.map((parameter) => (
670
- <div key={parameter.key}>
671
- <Text style={{ display: 'block', marginBottom: 6 }}>
672
- {parameter.label ?? parameter.key}
673
- {parameter.required ? ' *' : ''}
674
- </Text>
675
- <ParameterBindingEditor
676
- name={parameter.label ?? parameter.key}
677
- defaultPath={parameter.key}
678
- value={
679
- bindings[parameter.key] ?? {
680
- source: 'WORKFLOW',
681
- path: parameter.key,
682
- }
683
- }
684
- onChange={(binding) =>
685
- handleConfigChange('inputBindings', {
686
- ...bindings,
687
- [parameter.key]: binding,
688
- })
689
- }
690
- />
691
- </div>
692
- ))}
693
- </Space>
694
- );
695
- }
696
-
697
687
  // String with enum → Select
698
688
  if (schema.type === 'string' && schema.enum) {
699
689
  return (
700
690
  <div key={key}>
701
691
  <Text
702
- type="secondary"
703
- style={{ fontSize: 13, display: 'block', marginBottom: 2 }}
692
+ className="workflow-node-config__field-label"
693
+ style={{ marginBottom: 2 }}
704
694
  >
705
695
  {isRequired && <span style={{ color: '#ff4d4f' }}>* </span>}
706
696
  {fieldLabel}
@@ -722,8 +712,8 @@ export function NodePropertyPanel({
722
712
  return (
723
713
  <div key={key}>
724
714
  <Text
725
- type="secondary"
726
- style={{ fontSize: 13, display: 'block', marginBottom: 2 }}
715
+ className="workflow-node-config__field-label"
716
+ style={{ marginBottom: 2 }}
727
717
  >
728
718
  {isRequired && <span style={{ color: '#ff4d4f' }}>* </span>}
729
719
  {fieldLabel}
@@ -751,8 +741,8 @@ export function NodePropertyPanel({
751
741
  return (
752
742
  <div key={key}>
753
743
  <Text
754
- type="secondary"
755
- style={{ fontSize: 13, display: 'block', marginBottom: 2 }}
744
+ className="workflow-node-config__field-label"
745
+ style={{ marginBottom: 2 }}
756
746
  >
757
747
  {isRequired && <span style={{ color: '#ff4d4f' }}>* </span>}
758
748
  {fieldLabel}
@@ -762,7 +752,6 @@ export function NodePropertyPanel({
762
752
  onChange={(v) => handleConfigChange(key, v)}
763
753
  placeholder={placeholder}
764
754
  style={{ width: '100%' }}
765
- min={0}
766
755
  />
767
756
  </div>
768
757
  );
@@ -779,7 +768,7 @@ export function NodePropertyPanel({
779
768
  alignItems: 'center',
780
769
  }}
781
770
  >
782
- <Text type="secondary" style={{ fontSize: 13 }}>
771
+ <Text className="workflow-node-config__field-label">
783
772
  {isRequired && <span style={{ color: '#ff4d4f' }}>* </span>}
784
773
  {fieldLabel}
785
774
  </Text>
@@ -797,8 +786,8 @@ export function NodePropertyPanel({
797
786
  return (
798
787
  <div key={key}>
799
788
  <Text
800
- type="secondary"
801
- style={{ fontSize: 13, display: 'block', marginBottom: 2 }}
789
+ className="workflow-node-config__field-label"
790
+ style={{ marginBottom: 2 }}
802
791
  >
803
792
  {isRequired && <span style={{ color: '#ff4d4f' }}>* </span>}
804
793
  {fieldLabel}
@@ -822,8 +811,8 @@ export function NodePropertyPanel({
822
811
  return (
823
812
  <div key={key}>
824
813
  <Text
825
- type="secondary"
826
- style={{ fontSize: 13, display: 'block', marginBottom: 4 }}
814
+ className="workflow-node-config__field-label"
815
+ style={{ marginBottom: 4 }}
827
816
  >
828
817
  {isRequired && <span style={{ color: '#ff4d4f' }}>* </span>}
829
818
  {fieldLabel}
@@ -867,6 +856,7 @@ export function NodePropertyPanel({
867
856
  <Button
868
857
  type="dashed"
869
858
  size="small"
859
+ className="workflow-panel-action"
870
860
  icon={<PlusOutlined />}
871
861
  onClick={() => {
872
862
  const emptyItem: Record<string, string> = {};
@@ -892,8 +882,8 @@ export function NodePropertyPanel({
892
882
  return (
893
883
  <div key={key}>
894
884
  <Text
895
- type="secondary"
896
- style={{ fontSize: 13, display: 'block', marginBottom: 2 }}
885
+ className="workflow-node-config__field-label"
886
+ style={{ marginBottom: 2 }}
897
887
  >
898
888
  {isRequired && <span style={{ color: '#ff4d4f' }}>* </span>}
899
889
  {fieldLabel}
@@ -902,10 +892,10 @@ export function NodePropertyPanel({
902
892
  (JSON)
903
893
  </Text>
904
894
  </Text>
905
- <TextArea
895
+ <WorkflowCodeEditor
896
+ language="json"
906
897
  value={displayValue === '{}' ? '' : displayValue}
907
- onChange={(e) => {
908
- const raw = e.target.value;
898
+ onChange={(raw) => {
909
899
  try {
910
900
  handleConfigChange(key, JSON.parse(raw));
911
901
  } catch {
@@ -913,8 +903,8 @@ export function NodePropertyPanel({
913
903
  handleConfigChange(key, raw);
914
904
  }
915
905
  }}
916
- placeholder={`{"key": "value"}`}
917
- autoSize={{ minRows: 2, maxRows: 5 }}
906
+ height={150}
907
+ ariaLabel={`${fieldLabel} JSON`}
918
908
  />
919
909
  </div>
920
910
  );
@@ -924,8 +914,8 @@ export function NodePropertyPanel({
924
914
  return (
925
915
  <div key={key}>
926
916
  <Text
927
- type="secondary"
928
- style={{ fontSize: 13, display: 'block', marginBottom: 2 }}
917
+ className="workflow-node-config__field-label"
918
+ style={{ marginBottom: 2 }}
929
919
  >
930
920
  {isRequired && <span style={{ color: '#ff4d4f' }}>* </span>}
931
921
  {fieldLabel}
@@ -943,69 +933,41 @@ export function NodePropertyPanel({
943
933
 
944
934
  return (
945
935
  <div
946
- className="workflow-node-config"
947
- style={{
948
- width:
949
- usesCustomConfigEditor && currentNodeType?.type !== 'return_result'
950
- ? 500
951
- : 360,
952
- borderLeft: '1px solid var(--ant-color-border-secondary)',
953
- background: 'var(--ant-color-bg-container)',
954
- display: 'flex',
955
- flexDirection: 'column',
956
- overflow: 'hidden',
957
- }}
936
+ className="workflow-node-config workflow-node-panel workflow-node-panel--editor"
937
+ data-testid="workflow-editor-panel-node-property"
938
+ style={
939
+ {
940
+ width:
941
+ node?.type === 'approval'
942
+ ? 'clamp(560px, 46vw, 680px)'
943
+ : 'clamp(440px, 38vw, 520px)',
944
+ } as CSSProperties
945
+ }
958
946
  >
959
- {/* Header */}
960
- <div
961
- style={{
962
- padding: '16px 16px 12px',
963
- borderBottom: '1px solid var(--ant-color-border-secondary)',
964
- display: 'flex',
965
- justifyContent: 'space-between',
966
- alignItems: 'flex-start',
967
- }}
968
- >
969
- <Space direction="vertical" size={8} style={{ minWidth: 0 }}>
970
- <Title level={5} style={{ margin: 0, fontSize: 14 }}>
947
+ <header className="workflow-node-panel__header">
948
+ <div
949
+ className="workflow-node-panel__identity"
950
+ data-testid="workflow-node-properties-node-identity"
951
+ >
952
+ <Title level={5} className="workflow-node-panel__title">
971
953
  {t('editor.nodeConfiguration')}
972
954
  </Title>
973
- <div
955
+ <Text
956
+ className="workflow-node-panel__identity-name"
974
957
  style={{
975
- minWidth: 180,
976
- padding: '7px 10px',
958
+ color: currentNodeTypeColors.accent,
977
959
  background: currentNodeTypeColors.bg,
978
- border: `1px solid ${currentNodeTypeColors.border}`,
979
- borderRadius: 6,
960
+ borderColor: currentNodeTypeColors.border,
980
961
  }}
981
962
  >
982
- <Text
983
- type="secondary"
984
- style={{ display: 'block', fontSize: 11, lineHeight: 1.2 }}
985
- >
986
- {t('editor.nodeType')}
987
- </Text>
988
- <Text strong style={{ display: 'block', marginTop: 2 }}>
989
- {currentNodeTypeLabel}
990
- </Text>
991
- </div>
992
- </Space>
993
- <Button
994
- type="text"
995
- danger
996
- icon={<DeleteOutlined />}
997
- onClick={() => onDelete?.(node.id)}
998
- title={t('editor.deleteNode')}
999
- />
1000
- </div>
963
+ {currentNodeTypeLabel}
964
+ </Text>
965
+ </div>
966
+ {dirty && <Text type="warning">{t('editor.unsavedChanges')}</Text>}
967
+ </header>
1001
968
 
1002
969
  {/* Tab Switcher */}
1003
- <div
1004
- style={{
1005
- padding: '12px 16px 0',
1006
- borderBottom: '1px solid var(--ant-color-border-secondary)',
1007
- }}
1008
- >
970
+ <div className="workflow-node-panel__tabs">
1009
971
  <Segmented
1010
972
  value={activeTab}
1011
973
  onChange={(val) => setActiveTab(val as 'config' | 'ai')}
@@ -1014,15 +976,19 @@ export function NodePropertyPanel({
1014
976
  { label: t('editor.aiEditTab'), value: 'ai' },
1015
977
  ]}
1016
978
  block
1017
- style={{ marginBottom: 12 }}
1018
979
  />
1019
980
  </div>
1020
981
 
1021
982
  {/* Form */}
1022
- <div style={{ flex: 1, overflow: 'auto', padding: 16 }}>
983
+ <div className="workflow-node-panel__scroll">
1023
984
  {activeTab === 'config' && (
1024
- <Space direction="vertical" size={14} style={{ width: '100%' }}>
1025
- <div>
985
+ <Space
986
+ className="workflow-node-panel__stack"
987
+ direction="vertical"
988
+ size={14}
989
+ style={{ width: '100%' }}
990
+ >
991
+ <Card size="small" styles={{ body: { padding: 16 } }}>
1026
992
  <Text className="workflow-node-config__section-title">
1027
993
  {t('editor.nodeName')}
1028
994
  </Text>
@@ -1030,223 +996,295 @@ export function NodePropertyPanel({
1030
996
  value={label}
1031
997
  onChange={(e) => handleLabelChange(e.target.value)}
1032
998
  />
1033
- </div>
999
+ </Card>
1034
1000
 
1035
1001
  {/* Instance Selector / Reference Banner */}
1036
- {currentNodeType?.instancePolicy !== 'INLINE_ONLY' &&
1037
- !node.instanceRef && (
1038
- <div
1039
- style={{
1040
- padding: 12,
1041
- background: 'var(--ant-color-fill-quaternary)',
1042
- borderRadius: 6,
1043
- }}
1044
- >
1045
- <Text
1046
- type="secondary"
1047
- style={{ fontSize: 12, display: 'block', marginBottom: 6 }}
1002
+ {!node.instanceRef && (
1003
+ <Card
1004
+ size="small"
1005
+ className="workflow-node-panel__template-card"
1006
+ styles={{ body: { padding: 16 } }}
1007
+ >
1008
+ <div className="workflow-node-panel__section-head">
1009
+ <div>
1010
+ <Text className="workflow-node-panel__section-title">
1011
+ {t('editor.template')}
1012
+ </Text>
1013
+ <Text className="workflow-node-panel__section-hint">
1014
+ {t('editor.templateOptional')}
1015
+ </Text>
1016
+ </div>
1017
+ <Button
1018
+ size="small"
1019
+ className="workflow-panel-action"
1020
+ icon={<PlusOutlined />}
1021
+ onClick={() => setCreateInstanceOpen(true)}
1048
1022
  >
1049
- {t('editor.reuseInstance')}
1050
- </Text>
1051
- <Select
1052
- value={selectedInstanceId ?? undefined}
1053
- onChange={(v) => setSelectedInstanceId(v)}
1054
- placeholder={
1055
- instanceLoading
1056
- ? t('editor.loadingInstances')
1057
- : instances.length === 0
1058
- ? t('editor.noInstances')
1059
- : t('editor.selectInstance')
1060
- }
1061
- options={instances.map((i) => ({
1062
- label: i.name,
1063
- value: i.id,
1064
- }))}
1065
- style={{ width: '100%', marginBottom: 8 }}
1066
- allowClear
1067
- showSearch
1068
- loading={instanceLoading}
1069
- disabled={instances.length === 0 && !instanceLoading}
1070
- filterOption={(input, option) =>
1071
- (option?.label ?? '')
1072
- .toLowerCase()
1073
- .includes(input.toLowerCase())
1074
- }
1075
- />
1076
- {selectedInstanceId && (
1077
- <Space size={8}>
1078
- <Button
1079
- size="small"
1080
- icon={<LinkOutlined />}
1081
- onClick={handleReferenceInstance}
1082
- >
1083
- {t('editor.referenceInstance')}
1084
- </Button>
1085
- {currentNodeType?.instancePolicy === 'OPTIONAL' && (
1086
- <Button size="small" onClick={handleCopyAsTemplate}>
1087
- {t('editor.copyInstanceConfig')}
1088
- </Button>
1089
- )}
1090
- </Space>
1091
- )}
1023
+ {t('editor.createTemplate')}
1024
+ </Button>
1092
1025
  </div>
1093
- )}
1026
+ <Select
1027
+ placeholder={
1028
+ instanceLoading
1029
+ ? t('editor.loadingInstances')
1030
+ : instances.length === 0
1031
+ ? t('editor.noTemplates')
1032
+ : t('editor.selectTemplate')
1033
+ }
1034
+ options={instances.map((i) => ({
1035
+ label: i.name,
1036
+ value: i.id,
1037
+ }))}
1038
+ style={{ width: '100%' }}
1039
+ showSearch
1040
+ loading={instanceLoading}
1041
+ disabled={instances.length === 0 && !instanceLoading}
1042
+ onChange={(instanceId) => {
1043
+ const instance = instances.find(
1044
+ (item) => item.id === instanceId,
1045
+ );
1046
+ if (instance) referenceInstance(instance);
1047
+ }}
1048
+ filterOption={(input, option) =>
1049
+ (option?.label ?? '')
1050
+ .toLowerCase()
1051
+ .includes(input.toLowerCase())
1052
+ }
1053
+ />
1054
+ {instanceLoadError && (
1055
+ <div className="workflow-inline-error">
1056
+ {t('editor.instanceLoadFailed')}{' '}
1057
+ <Button type="link" size="small" onClick={loadInstances}>
1058
+ {t('common.retry')}
1059
+ </Button>
1060
+ </div>
1061
+ )}
1062
+ </Card>
1063
+ )}
1094
1064
  {node.instanceRef && (
1095
- <div
1096
- style={{
1097
- padding: '10px 12px',
1098
- background: 'var(--ant-color-primary-bg)',
1099
- borderRadius: 6,
1100
- display: 'flex',
1101
- alignItems: 'center',
1102
- justifyContent: 'space-between',
1103
- }}
1065
+ <Card
1066
+ size="small"
1067
+ className="workflow-node-panel__template-card workflow-node-panel__template-card--active"
1068
+ styles={{ body: { padding: 16 } }}
1104
1069
  >
1105
- <Text style={{ fontSize: 13 }}>
1106
- {t('editor.referencedInstance')}:{' '}
1107
- {node.instanceRef.instanceCode}
1108
- </Text>
1109
- {currentNodeType?.instancePolicy === 'OPTIONAL' && (
1070
+ <div className="workflow-node-panel__template-reference">
1071
+ <span className="workflow-node-panel__template-status" />
1072
+ <div className="workflow-node-panel__template-copy">
1073
+ <Text className="workflow-node-panel__section-title">
1074
+ {t('editor.referencedInstance')}
1075
+ </Text>
1076
+ <code>{node.instanceRef.instanceCode}</code>
1077
+ </div>
1110
1078
  <Button
1111
1079
  size="small"
1080
+ className="workflow-panel-action"
1112
1081
  icon={<DisconnectOutlined />}
1113
1082
  onClick={handleDetachReference}
1114
1083
  >
1115
1084
  {t('editor.detach')}
1116
1085
  </Button>
1117
- )}
1118
- </div>
1119
- )}
1120
- {node.instanceRef && (
1121
- <Text type="secondary" style={{ fontSize: 12 }}>
1122
- {t('editor.referenceHint')}
1123
- </Text>
1086
+ </div>
1087
+ <Text className="workflow-node-panel__section-hint">
1088
+ {t('editor.referenceHint')}
1089
+ </Text>
1090
+ </Card>
1124
1091
  )}
1125
1092
 
1126
1093
  {/* Schema-driven config fields */}
1127
- {(Object.keys(properties).length > 0 || usesCustomConfigEditor) && (
1128
- <div>
1129
- {Object.keys(properties).length > 0 && (
1130
- <Text className="workflow-node-config__section-title">
1131
- {t('editor.parameters')}
1132
- </Text>
1133
- )}
1134
- {usesCustomConfigEditor ? (
1135
- <Form
1136
- form={configForm}
1137
- layout="vertical"
1138
- onValuesChange={(_, values) => {
1139
- setConfig(values.config ?? {});
1140
- setDirty(true);
1141
- }}
1142
- >
1143
- <SchemaForm
1144
- schema={currentNodeType?.configSchema ?? null}
1145
- uiSchema={effectiveUiSchema ?? null}
1146
- />
1147
- </Form>
1148
- ) : (
1149
- <Space
1150
- direction="vertical"
1151
- size={10}
1152
- style={{ width: '100%' }}
1153
- >
1154
- {Object.entries(properties).map(([key, propSchema]) =>
1155
- renderConfigField(key, propSchema),
1156
- )}
1157
- </Space>
1158
- )}
1159
- </div>
1094
+ {referencedConfigUnavailable ? (
1095
+ <Alert
1096
+ type={referencedConfigStatus === 'error' ? 'error' : 'info'}
1097
+ showIcon
1098
+ message={
1099
+ referencedConfigStatus === 'error'
1100
+ ? t('editor.templateConfigLoadFailed')
1101
+ : t('editor.templateConfigLoading')
1102
+ }
1103
+ description={
1104
+ referencedConfigStatus === 'error'
1105
+ ? t('editor.templateConfigLoadFailedHint')
1106
+ : t('editor.templateConfigLoadingHint')
1107
+ }
1108
+ action={
1109
+ referencedConfigStatus === 'error' &&
1110
+ onRetryReferencedConfigs ? (
1111
+ <Button size="small" onClick={onRetryReferencedConfigs}>
1112
+ {t('common.retry')}
1113
+ </Button>
1114
+ ) : undefined
1115
+ }
1116
+ />
1117
+ ) : (
1118
+ (Object.keys(properties).length > 0 || usesCustomConfigEditor) &&
1119
+ (usesTopLevelApprovalSections ? (
1120
+ <Form
1121
+ form={configForm}
1122
+ layout="vertical"
1123
+ className="workflow-node-panel__approval-sections"
1124
+ {...formValidation}
1125
+ onValuesChange={(_, values) => {
1126
+ const nextConfig = values.config ?? {};
1127
+ setConfig(nextConfig);
1128
+ setDirty(true);
1129
+ onConfigDraftChange?.(node.id, nextConfig);
1130
+ }}
1131
+ >
1132
+ <SchemaForm
1133
+ schema={currentNodeType?.configSchema ?? null}
1134
+ uiSchema={effectiveUiSchema ?? null}
1135
+ validate={false}
1136
+ />
1137
+ </Form>
1138
+ ) : (
1139
+ <Card
1140
+ size="small"
1141
+ className="workflow-node-panel__parameters-card"
1142
+ styles={{ body: { padding: 16 } }}
1143
+ >
1144
+ {Object.keys(properties).length > 0 && (
1145
+ <Text className="workflow-node-config__section-title">
1146
+ {t('editor.parameters')}
1147
+ </Text>
1148
+ )}
1149
+ {usesCustomConfigEditor ? (
1150
+ <Form
1151
+ form={configForm}
1152
+ layout="vertical"
1153
+ {...formValidation}
1154
+ onValuesChange={(_, values) => {
1155
+ const nextConfig = values.config ?? {};
1156
+ setConfig(nextConfig);
1157
+ setDirty(true);
1158
+ onConfigDraftChange?.(node.id, nextConfig);
1159
+ }}
1160
+ >
1161
+ <SchemaForm
1162
+ schema={currentNodeType?.configSchema ?? null}
1163
+ uiSchema={effectiveUiSchema ?? null}
1164
+ validate={false}
1165
+ />
1166
+ </Form>
1167
+ ) : (
1168
+ <Space
1169
+ direction="vertical"
1170
+ size={10}
1171
+ style={{ width: '100%' }}
1172
+ >
1173
+ {Object.entries(properties).map(([key, propSchema]) =>
1174
+ renderConfigField(key, propSchema),
1175
+ )}
1176
+ </Space>
1177
+ )}
1178
+ </Card>
1179
+ ))
1160
1180
  )}
1161
1181
 
1162
- <NodeOutputSummary node={node} nodeType={currentNodeType} />
1163
-
1164
- {/* Save section */}
1165
- <div>
1166
- {!node.instanceRef &&
1167
- currentNodeType?.instancePolicy !== 'REQUIRED' && (
1168
- <>
1169
- <Checkbox
1170
- checked={alsoSaveAsInstance}
1171
- onChange={(e) => setAlsoSaveAsInstance(e.target.checked)}
1172
- style={{ marginBottom: 8 }}
1173
- >
1174
- <Text style={{ fontSize: 13 }}>
1175
- {t('editor.saveAsInstance')}
1176
- </Text>
1177
- </Checkbox>
1178
- {alsoSaveAsInstance && (
1179
- <Space
1180
- direction="vertical"
1181
- size={6}
1182
- style={{ width: '100%', marginBottom: 8 }}
1183
- >
1184
- <Input
1185
- value={newInstanceName}
1186
- onChange={(e) => setNewInstanceName(e.target.value)}
1187
- placeholder={t('editor.instanceNamePlaceholder')}
1188
- size="small"
1189
- />
1190
- <Input
1191
- value={newInstanceDesc}
1192
- onChange={(e) => setNewInstanceDesc(e.target.value)}
1193
- placeholder={t('common.optionalDescription')}
1194
- size="small"
1195
- />
1196
- </Space>
1197
- )}
1198
- </>
1199
- )}
1200
- <Button
1201
- type="primary"
1202
- block
1203
- loading={saveAsLoading}
1204
- disabled={
1205
- !node.instanceRef &&
1206
- alsoSaveAsInstance &&
1207
- !newInstanceName.trim()
1208
- }
1209
- onClick={handleSave}
1210
- >
1211
- {t('editor.saveChanges')}
1212
- </Button>
1213
- </div>
1182
+ {effectiveNode && !referencedConfigUnavailable && (
1183
+ <NodeOutputSummary
1184
+ node={{ ...effectiveNode, config }}
1185
+ nodeType={currentNodeType}
1186
+ nodes={nodes}
1187
+ edges={edges}
1188
+ nodeTypes={nodeTypes}
1189
+ />
1190
+ )}
1214
1191
  </Space>
1215
1192
  )}
1216
1193
 
1217
1194
  {activeTab === 'ai' && (
1218
- <div>
1219
- <Text
1220
- type="secondary"
1221
- style={{ fontSize: 13, display: 'block', marginBottom: 8 }}
1222
- >
1195
+ <Card
1196
+ size="small"
1197
+ className="workflow-node-panel__ai"
1198
+ styles={{ body: { padding: 16 } }}
1199
+ >
1200
+ <Text className="workflow-node-panel__section-title">
1201
+ {t('editor.aiEditTab')}
1202
+ </Text>
1203
+ <Text className="workflow-node-panel__section-hint workflow-node-panel__ai-hint">
1223
1204
  {t('editor.aiEditHint')}
1224
1205
  </Text>
1225
- <div style={{ display: 'flex', gap: 4 }}>
1226
- <TextArea
1227
- value={prompt}
1228
- onChange={(e) => setPrompt(e.target.value)}
1229
- placeholder={t('editor.aiEditPlaceholder')}
1230
- autoSize={{ minRows: 4, maxRows: 8 }}
1231
- onPressEnter={(e) => {
1232
- if (e.ctrlKey) handleAiSubmit();
1233
- }}
1234
- />
1235
- </div>
1206
+ <TextArea
1207
+ value={prompt}
1208
+ onChange={(e) => setPrompt(e.target.value)}
1209
+ placeholder={t('editor.aiEditPlaceholder')}
1210
+ autoSize={{ minRows: 5, maxRows: 10 }}
1211
+ onPressEnter={(e) => {
1212
+ if (e.ctrlKey) handleAiSubmit();
1213
+ }}
1214
+ />
1236
1215
  <Button
1237
- type="default"
1216
+ type="primary"
1238
1217
  icon={<SendOutlined />}
1239
1218
  loading={aiLoading}
1240
1219
  onClick={handleAiSubmit}
1241
- disabled={!prompt.trim()}
1242
- style={{ marginTop: 8 }}
1220
+ disabled={!prompt.trim() || referencedConfigUnavailable}
1243
1221
  block
1244
1222
  >
1245
1223
  {t('editor.send')}
1246
1224
  </Button>
1247
- </div>
1225
+ </Card>
1248
1226
  )}
1249
1227
  </div>
1228
+ {activeTab === 'config' && (
1229
+ <footer className="workflow-node-panel__footer">
1230
+ {!node.instanceRef && (
1231
+ <div className="workflow-node-panel__save-template">
1232
+ <Checkbox
1233
+ checked={alsoSaveAsInstance}
1234
+ onChange={(e) => setAlsoSaveAsInstance(e.target.checked)}
1235
+ >
1236
+ <Text>{t('editor.saveAsInstance')}</Text>
1237
+ </Checkbox>
1238
+ {alsoSaveAsInstance && (
1239
+ <Space direction="vertical" size={6} style={{ width: '100%' }}>
1240
+ <Input
1241
+ value={newInstanceName}
1242
+ onChange={(e) => setNewInstanceName(e.target.value)}
1243
+ placeholder={t('editor.instanceNamePlaceholder')}
1244
+ size="small"
1245
+ />
1246
+ <Input
1247
+ value={newInstanceDesc}
1248
+ onChange={(e) => setNewInstanceDesc(e.target.value)}
1249
+ placeholder={t('common.optionalDescription')}
1250
+ size="small"
1251
+ />
1252
+ </Space>
1253
+ )}
1254
+ </div>
1255
+ )}
1256
+ <Button
1257
+ type="primary"
1258
+ size="large"
1259
+ block
1260
+ loading={saveAsLoading}
1261
+ disabled={
1262
+ referencedConfigUnavailable ||
1263
+ (!node.instanceRef &&
1264
+ alsoSaveAsInstance &&
1265
+ !newInstanceName.trim())
1266
+ }
1267
+ onClick={handleSave}
1268
+ >
1269
+ {t('editor.saveChanges')}
1270
+ </Button>
1271
+ </footer>
1272
+ )}
1273
+ <CreateNodeInstanceDrawer
1274
+ open={createInstanceOpen}
1275
+ nodeType={currentNodeType}
1276
+ onCancel={() => setCreateInstanceOpen(false)}
1277
+ onCreated={(instance) => {
1278
+ setCreateInstanceOpen(false);
1279
+ setInstances((current) => [
1280
+ instance,
1281
+ ...current.filter((item) => item.id !== instance.id),
1282
+ ]);
1283
+ referenceInstance(instance);
1284
+ }}
1285
+ />
1250
1286
  </div>
1251
1287
  );
1252
1288
  }
1289
+
1290
+ export const NodePropertyPanel = forwardRef(NodePropertyPanelImpl);