@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
@@ -7,7 +7,6 @@ import React, {
7
7
  } from 'react';
8
8
  import {
9
9
  Button,
10
- Card,
11
10
  Input,
12
11
  message,
13
12
  Modal,
@@ -19,53 +18,241 @@ import {
19
18
  Typography,
20
19
  } from 'antd';
21
20
  import {
22
- ArrowLeftOutlined,
21
+ ApartmentOutlined,
22
+ CodeOutlined,
23
+ DeleteOutlined,
23
24
  EditOutlined,
24
25
  RobotOutlined,
25
26
  RocketOutlined,
26
27
  SaveOutlined,
28
+ UploadOutlined,
27
29
  } from '@ant-design/icons';
28
- import { useNavigate, useParams } from 'react-router-dom';
30
+ import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
29
31
  import { useTranslation } from 'react-i18next';
32
+ import stableStringify from 'json-stable-stringify';
30
33
  import { customRequest } from 'helpers/http';
31
34
  import { FlowRenderer } from './components/FlowRenderer';
32
- import { NodePropertyPanel } from './components/NodePropertyPanel';
35
+ import {
36
+ NodePropertyPanel,
37
+ type NodePropertyPanelHandle,
38
+ } from './components/NodePropertyPanel';
39
+ import { WorkflowWorkspaceHeader } from './components/WorkflowWorkspaceHeader';
33
40
  import { DslView, type DslViewHandle } from './components/DslView';
34
41
  import { useWorkflowAgent } from './hooks/useWorkflowAgent';
42
+ import { useReferencedNodeInstanceConfigs } from './hooks/useReferencedNodeInstanceConfigs';
35
43
  import { useIsAgentAllowed } from 'config/agentAllowed';
44
+ import { useReferenceUpload } from 'hooks';
45
+ import { removeDeletedParallelBranchPipelines } from './utils/removeWorkflowEdges';
46
+ import { resolveWorkflowDisplay } from './utils/resolveWorkflowDisplay';
47
+ import {
48
+ emptyWorkflowHistory,
49
+ pushWorkflowHistory,
50
+ redoWorkflowHistory,
51
+ undoWorkflowHistory,
52
+ } from './utils/workflowHistory';
36
53
  import type {
54
+ CallableWorkflowOption,
37
55
  Workflow,
38
56
  WorkflowDSL,
39
57
  WorkflowNode,
40
58
  WorkflowNodeType,
41
59
  WorkflowVersion,
42
60
  } from './types';
61
+ import './workflow-ui.css';
43
62
 
44
63
  const { Title } = Typography;
45
64
  const { TextArea } = Input;
46
65
 
47
66
  const NEW_DRAFT_KEY = 'workflow_new_draft';
48
- const EDIT_DRAFT_KEY = (id: string) => `workflow_draft_${id}`;
67
+
68
+ type NodePropertyUpdates = Parameters<
69
+ React.ComponentProps<typeof NodePropertyPanel>['onSave']
70
+ >[1];
71
+
72
+ function applyNodePropertyUpdates(
73
+ dsl: WorkflowDSL,
74
+ nodeId: string,
75
+ updates: NodePropertyUpdates,
76
+ ): WorkflowDSL {
77
+ let changed = false;
78
+ const nodes = dsl.nodes.map((node) => {
79
+ if (node.id !== nodeId) return node;
80
+ const updated = {
81
+ ...node,
82
+ label: updates.label ?? node.label,
83
+ config: updates.config ?? node.config,
84
+ };
85
+ if (updates.instanceRef === null) {
86
+ delete updated.instanceRef;
87
+ } else if (updates.instanceRef !== undefined) {
88
+ updated.instanceRef = updates.instanceRef;
89
+ }
90
+ if (updates.configOverride === null) {
91
+ delete updated.configOverride;
92
+ } else if (updates.configOverride !== undefined) {
93
+ updated.configOverride = updates.configOverride;
94
+ }
95
+ if (stableStringify(updated) === stableStringify(node)) return node;
96
+ changed = true;
97
+ return updated;
98
+ });
99
+ return changed ? { ...dsl, nodes } : dsl;
100
+ }
49
101
 
50
102
  export default function WorkflowEditorPage() {
51
103
  const { t } = useTranslation('workflow');
52
- const { id } = useParams<{ id: string }>();
53
- const isNewMode = !id;
104
+ const { id: routeWorkflowId } = useParams<{ id: string }>();
105
+ const [searchParams] = useSearchParams();
106
+ const requestedVersion = useMemo(() => {
107
+ const value = Number(searchParams.get('version'));
108
+ return Number.isInteger(value) && value > 0 ? value : null;
109
+ }, [searchParams]);
110
+ const [workflowId, setWorkflowId] = useState(routeWorkflowId);
111
+ const isNewMode = !workflowId;
54
112
  const navigate = useNavigate();
55
113
 
56
114
  const [workflow, setWorkflow] = useState<Workflow | null>(null);
57
115
  const [versions, setVersions] = useState<WorkflowVersion[]>([]);
58
116
  const [nodeTypes, setNodeTypes] = useState<WorkflowNodeType[]>([]);
117
+ const [nodeTypesError, setNodeTypesError] = useState(false);
118
+ const [callableWorkflows, setCallableWorkflows] = useState<
119
+ CallableWorkflowOption[]
120
+ >([]);
121
+ const [callableCatalogStatus, setCallableCatalogStatus] = useState<
122
+ 'loading' | 'ready' | 'error'
123
+ >('loading');
59
124
  const [loading, setLoading] = useState(!isNewMode);
60
125
  const isDev = useIsAgentAllowed();
61
126
 
62
127
  const [dsl, setDsl] = useState<WorkflowDSL | null>(null);
128
+ const dslRef = useRef<WorkflowDSL | null>(null);
129
+ const [dslHistory, setDslHistory] = useState(() =>
130
+ emptyWorkflowHistory<WorkflowDSL>(),
131
+ );
132
+ const dslHistoryRef = useRef(dslHistory);
133
+ const dslTransactionStartRef = useRef<WorkflowDSL | null>(null);
63
134
  const [currentVersion, setCurrentVersion] = useState<number | null>(null);
64
135
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
136
+ const referencedNodeConfigs = useReferencedNodeInstanceConfigs(dsl);
65
137
 
66
138
  const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
67
139
  const [viewMode, setViewMode] = useState<'canvas' | 'dsl'>('canvas');
140
+ const [dslTextDirty, setDslTextDirty] = useState(false);
68
141
  const dslViewRef = useRef<DslViewHandle>(null);
142
+ const nodePropertyPanelRef = useRef<NodePropertyPanelHandle>(null);
143
+
144
+ const updateDslHistory = useCallback(
145
+ (nextHistory: ReturnType<typeof emptyWorkflowHistory<WorkflowDSL>>) => {
146
+ dslHistoryRef.current = nextHistory;
147
+ setDslHistory(nextHistory);
148
+ },
149
+ [],
150
+ );
151
+
152
+ const replaceDslWithoutHistory = useCallback(
153
+ (nextDsl: WorkflowDSL | null) => {
154
+ dslRef.current = nextDsl;
155
+ setDsl(nextDsl);
156
+ dslTransactionStartRef.current = null;
157
+ updateDslHistory(emptyWorkflowHistory());
158
+ },
159
+ [updateDslHistory],
160
+ );
161
+
162
+ const applyDslChange = useCallback(
163
+ (nextDsl: WorkflowDSL) => {
164
+ const currentDsl = dslRef.current;
165
+ if (currentDsl === nextDsl) return;
166
+ if (!dslTransactionStartRef.current) {
167
+ updateDslHistory(
168
+ pushWorkflowHistory(
169
+ dslHistoryRef.current,
170
+ currentDsl ?? { nodes: [], edges: [] },
171
+ ),
172
+ );
173
+ }
174
+ dslRef.current = nextDsl;
175
+ setDsl(nextDsl);
176
+ setHasUnsavedChanges(true);
177
+ },
178
+ [updateDslHistory],
179
+ );
180
+
181
+ const beginDslTransaction = useCallback(() => {
182
+ if (dslTransactionStartRef.current) return;
183
+ dslTransactionStartRef.current = dslRef.current ?? {
184
+ nodes: [],
185
+ edges: [],
186
+ };
187
+ }, []);
188
+
189
+ const finishDslTransaction = useCallback(() => {
190
+ const startDsl = dslTransactionStartRef.current;
191
+ const currentDsl = dslRef.current;
192
+ dslTransactionStartRef.current = null;
193
+ if (!startDsl || !currentDsl) return;
194
+ if (JSON.stringify(startDsl) === JSON.stringify(currentDsl)) return;
195
+ updateDslHistory(pushWorkflowHistory(dslHistoryRef.current, startDsl));
196
+ }, [updateDslHistory]);
197
+
198
+ const handleUndo = useCallback(() => {
199
+ const currentDsl = dslRef.current ?? { nodes: [], edges: [] };
200
+ const result = undoWorkflowHistory(dslHistoryRef.current, currentDsl);
201
+ if (!result) return;
202
+ dslRef.current = result.value;
203
+ setDsl(result.value);
204
+ updateDslHistory(result.history);
205
+ setHasUnsavedChanges(true);
206
+ setSelectedNodeId((current) =>
207
+ current && result.value.nodes.some((node) => node.id === current)
208
+ ? current
209
+ : null,
210
+ );
211
+ }, [updateDslHistory]);
212
+
213
+ const handleRedo = useCallback(() => {
214
+ const currentDsl = dslRef.current ?? { nodes: [], edges: [] };
215
+ const result = redoWorkflowHistory(dslHistoryRef.current, currentDsl);
216
+ if (!result) return;
217
+ dslRef.current = result.value;
218
+ setDsl(result.value);
219
+ updateDslHistory(result.history);
220
+ setHasUnsavedChanges(true);
221
+ setSelectedNodeId((current) =>
222
+ current && result.value.nodes.some((node) => node.id === current)
223
+ ? current
224
+ : null,
225
+ );
226
+ }, [updateDslHistory]);
227
+
228
+ useEffect(() => {
229
+ dslRef.current = dsl;
230
+ }, [dsl]);
231
+
232
+ useEffect(() => {
233
+ if (viewMode !== 'canvas') return;
234
+ const handleKeyDown = (event: KeyboardEvent) => {
235
+ const target = event.target as HTMLElement | null;
236
+ if (
237
+ target?.closest(
238
+ 'input, textarea, [contenteditable="true"], .monaco-editor',
239
+ )
240
+ )
241
+ return;
242
+ const modifier = event.metaKey || event.ctrlKey;
243
+ if (!modifier) return;
244
+ if (event.key.toLowerCase() === 'z') {
245
+ event.preventDefault();
246
+ if (event.shiftKey) handleRedo();
247
+ else handleUndo();
248
+ } else if (event.key.toLowerCase() === 'y') {
249
+ event.preventDefault();
250
+ handleRedo();
251
+ }
252
+ };
253
+ window.addEventListener('keydown', handleKeyDown);
254
+ return () => window.removeEventListener('keydown', handleKeyDown);
255
+ }, [handleRedo, handleUndo, viewMode]);
69
256
 
70
257
  // Title editing state
71
258
  const [title, setTitle] = useState(isNewMode ? t('editor.untitled') : '');
@@ -80,6 +267,18 @@ export default function WorkflowEditorPage() {
80
267
  // AI generate modal
81
268
  const [aiModalOpen, setAiModalOpen] = useState(false);
82
269
  const [aiPromptText, setAiPromptText] = useState('');
270
+ const aiFileInputRef = useRef<HTMLInputElement>(null);
271
+ // ── 工作流参考图(可选,仅一张)──
272
+ const {
273
+ file: aiImgFile,
274
+ previewUrl: aiImgPreviewUrl,
275
+ uploadedPath: aiUploadedImgPath,
276
+ uploading: aiImgUploading,
277
+ error: aiImgUploadError,
278
+ select: selectAiImgFile,
279
+ upload: handleAiImgUpload,
280
+ reset: resetAiImg,
281
+ } = useReferenceUpload({ withPreview: true });
83
282
 
84
283
  const nodeTypeMap = useMemo(() => {
85
284
  const map: Record<
@@ -106,24 +305,52 @@ export default function WorkflowEditorPage() {
106
305
  abort: abortAgent,
107
306
  loading: agentLoading,
108
307
  error: agentError,
109
- } = useWorkflowAgent({ nodeTypes });
308
+ } = useWorkflowAgent({
309
+ nodeTypes,
310
+ referencedConfigs: referencedNodeConfigs.configs,
311
+ callableWorkflows:
312
+ callableCatalogStatus === 'ready' ? callableWorkflows : undefined,
313
+ });
110
314
 
111
315
  // --- Load logic ---
112
- useEffect(() => {
113
- // Load node types for both modes
114
- customRequest<WorkflowNodeType[]>('workflow-node-types', 'GET')
115
- .then(setNodeTypes)
116
- .catch(() => {});
316
+ const loadNodeTypes = useCallback(async () => {
317
+ try {
318
+ setNodeTypes(
319
+ await customRequest<WorkflowNodeType[]>('workflow-node-types', 'GET'),
320
+ );
321
+ setNodeTypesError(false);
322
+ } catch {
323
+ setNodeTypesError(true);
324
+ }
325
+ }, []);
326
+
327
+ useEffect(() => void loadNodeTypes(), [loadNodeTypes]);
328
+
329
+ const loadCallableWorkflows = useCallback(async () => {
330
+ setCallableCatalogStatus('loading');
331
+ try {
332
+ const items = await customRequest<CallableWorkflowOption[]>(
333
+ 'workflow/callable',
334
+ 'GET',
335
+ );
336
+ setCallableWorkflows(Array.isArray(items) ? items : []);
337
+ setCallableCatalogStatus('ready');
338
+ } catch {
339
+ setCallableWorkflows([]);
340
+ setCallableCatalogStatus('error');
341
+ }
117
342
  }, []);
118
343
 
344
+ useEffect(() => void loadCallableWorkflows(), [loadCallableWorkflows]);
345
+
119
346
  useEffect(() => {
120
- if (isNewMode) {
347
+ if (!routeWorkflowId) {
121
348
  // New mode: load from localStorage
122
349
  const saved = localStorage.getItem(NEW_DRAFT_KEY);
123
350
  if (saved) {
124
351
  try {
125
352
  const parsed = JSON.parse(saved);
126
- setDsl(parsed.dsl || null);
353
+ replaceDslWithoutHistory(parsed.dsl || null);
127
354
  setTitle(parsed.title || 'Untitled Workflow');
128
355
  } catch {
129
356
  localStorage.removeItem(NEW_DRAFT_KEY);
@@ -136,26 +363,28 @@ export default function WorkflowEditorPage() {
136
363
  setLoading(true);
137
364
  setHasUnsavedChanges(false);
138
365
  Promise.all([
139
- customRequest<Workflow>(`workflow/${id}`, 'GET'),
140
- customRequest<WorkflowVersion[]>(`workflow/${id}/versions`, 'GET'),
366
+ customRequest<Workflow>(`workflow/${routeWorkflowId}`, 'GET'),
367
+ customRequest<WorkflowVersion[]>(
368
+ `workflow/${routeWorkflowId}/versions`,
369
+ 'GET',
370
+ ),
371
+ requestedVersion === null
372
+ ? Promise.resolve(null)
373
+ : customRequest<WorkflowVersion>(
374
+ `workflow/${routeWorkflowId}/versions/${requestedVersion}`,
375
+ 'GET',
376
+ ),
141
377
  ])
142
- .then(([wf, vers]) => {
143
- setWorkflow(wf);
378
+ .then(([wf, vers, selectedVersion]) => {
144
379
  setVersions(vers);
145
380
  setTitle(wf.name);
146
- // getVersions only returns metadata (no dsl) for the version selector
147
- const latestVersion = (wf as any).versions?.[0];
148
- // wf.dsl is the draft (source of truth for in-progress edits, same
149
- // precedence as backend publish()); fall back to the last published
150
- // version only when no draft has ever been saved.
151
- if (wf.dsl) {
152
- setDsl(wf.dsl);
153
- } else if (latestVersion?.dsl) {
154
- setDsl(latestVersion.dsl);
155
- }
156
- if (latestVersion?.version) {
157
- setCurrentVersion(latestVersion.version);
158
- }
381
+ const display = resolveWorkflowDisplay(wf);
382
+ const databaseDsl = selectedVersion?.dsl ?? display.dsl;
383
+ const publishedVersion = selectedVersion?.version ?? display.version;
384
+ setWorkflow(wf);
385
+ replaceDslWithoutHistory(databaseDsl);
386
+ setCurrentVersion(publishedVersion);
387
+ setHasUnsavedChanges(publishedVersion !== null);
159
388
  })
160
389
  .catch((e) => {
161
390
  message.error(e?.message ?? t('editor.loadFailed'));
@@ -163,46 +392,52 @@ export default function WorkflowEditorPage() {
163
392
  .finally(() => {
164
393
  setLoading(false);
165
394
  });
166
- }, [id, isNewMode]);
395
+ }, [replaceDslWithoutHistory, requestedVersion, routeWorkflowId, t]);
167
396
 
168
- // --- Edit buffer: write to localStorage on every change ---
397
+ // A new Workflow has no database row yet, so keep only that temporary draft
398
+ // in the browser. Existing Workflows are persisted exclusively by Save.
169
399
  useEffect(() => {
170
- if (!hasUnsavedChanges) return;
171
- if (isNewMode) {
172
- localStorage.setItem(NEW_DRAFT_KEY, JSON.stringify({ dsl, title }));
173
- } else if (id && dsl) {
174
- localStorage.setItem(EDIT_DRAFT_KEY(id), JSON.stringify(dsl));
175
- }
176
- }, [dsl, title, hasUnsavedChanges, isNewMode, id]);
400
+ if (!hasUnsavedChanges || !isNewMode) return;
401
+ localStorage.setItem(NEW_DRAFT_KEY, JSON.stringify({ dsl, title }));
402
+ }, [dsl, title, hasUnsavedChanges, isNewMode]);
177
403
 
178
404
  // --- Canvas edit callback ---
179
- const handleDslChange = useCallback((newDsl: WorkflowDSL) => {
180
- setDsl(newDsl);
181
- setHasUnsavedChanges(true);
182
- }, []);
405
+ const handleDslChange = useCallback(
406
+ (newDsl: WorkflowDSL) => {
407
+ applyDslChange(newDsl);
408
+ },
409
+ [applyDslChange],
410
+ );
183
411
 
184
412
  // --- AI edit workflow ---
185
413
  const handlePromptSubmit = useCallback(
186
414
  async (promptText: string) => {
187
415
  if (!promptText.trim()) return;
416
+ if (referencedNodeConfigs.status !== 'ready') {
417
+ message.error(t('editor.templateConfigLoadingHint'));
418
+ return;
419
+ }
188
420
  const result = await generate({
189
421
  action: dsl ? 'edit_workflow' : 'create',
190
422
  prompt: promptText.trim(),
191
423
  currentDsl: dsl || undefined,
192
424
  });
193
425
  if (result && 'dsl' in result) {
194
- setDsl(result.dsl);
195
- setHasUnsavedChanges(true);
426
+ applyDslChange(result.dsl);
196
427
  message.success(result.changeSummary || t('editor.updated'));
197
428
  }
198
429
  },
199
- [dsl, generate],
430
+ [applyDslChange, dsl, generate, referencedNodeConfigs.status, t],
200
431
  );
201
432
 
202
433
  // --- AI edit single node ---
203
434
  const handleNodeAiEdit = useCallback(
204
435
  async (nodeId: string, nodePrompt: string) => {
205
436
  if (!dsl) return;
437
+ if (referencedNodeConfigs.status !== 'ready') {
438
+ message.error(t('editor.templateConfigLoadingHint'));
439
+ return;
440
+ }
206
441
  const result = await generate({
207
442
  action: 'edit_node',
208
443
  prompt: nodePrompt,
@@ -210,12 +445,11 @@ export default function WorkflowEditorPage() {
210
445
  targetNodeId: nodeId,
211
446
  });
212
447
  if (result && 'dsl' in result) {
213
- setDsl(result.dsl);
214
- setHasUnsavedChanges(true);
448
+ applyDslChange(result.dsl);
215
449
  message.success(result.changeSummary || t('editor.nodeUpdated'));
216
450
  }
217
451
  },
218
- [dsl, generate],
452
+ [applyDslChange, dsl, generate, referencedNodeConfigs.status, t],
219
453
  );
220
454
 
221
455
  // --- Node label change ---
@@ -225,25 +459,9 @@ export default function WorkflowEditorPage() {
225
459
  const newNodes = dsl.nodes.map((n) =>
226
460
  n.id === nodeId ? { ...n, label: newLabel } : n,
227
461
  );
228
- setDsl({ ...dsl, nodes: newNodes });
229
- setHasUnsavedChanges(true);
230
- },
231
- [dsl],
232
- );
233
-
234
- // --- Delete node ---
235
- const handleNodeDelete = useCallback(
236
- (nodeId: string) => {
237
- if (!dsl) return;
238
- const newNodes = dsl.nodes.filter((n) => n.id !== nodeId);
239
- const newEdges = dsl.edges.filter(
240
- (e) => e.source !== nodeId && e.target !== nodeId,
241
- );
242
- setDsl({ nodes: newNodes, edges: newEdges });
243
- setHasUnsavedChanges(true);
244
- setSelectedNodeId(null);
462
+ applyDslChange({ ...dsl, nodes: newNodes });
245
463
  },
246
- [dsl],
464
+ [applyDslChange, dsl],
247
465
  );
248
466
 
249
467
  // --- Node property save ---
@@ -257,31 +475,42 @@ export default function WorkflowEditorPage() {
257
475
  instanceRef?: WorkflowNode['instanceRef'] | null;
258
476
  },
259
477
  ) => {
260
- if (!dsl) return;
261
- const newNodes = dsl.nodes.map((n) => {
262
- if (n.id !== nodeId) return n;
263
- const updated = {
264
- ...n,
265
- label: updates.label ?? n.label,
266
- config: updates.config ?? n.config,
267
- };
268
- if (updates.instanceRef === null) {
269
- delete updated.instanceRef;
270
- } else if (updates.instanceRef !== undefined) {
271
- updated.instanceRef = updates.instanceRef;
272
- }
273
- if (updates.configOverride === null) {
274
- delete updated.configOverride;
275
- } else if (updates.configOverride !== undefined) {
276
- updated.configOverride = updates.configOverride;
277
- }
278
- return updated;
279
- });
280
- setDsl({ ...dsl, nodes: newNodes });
281
- setHasUnsavedChanges(true);
478
+ const currentDsl = dslRef.current ?? dsl;
479
+ if (!currentDsl) return;
480
+ const nextConfig = updates.configOverride ?? updates.config;
481
+ const cleanedDsl = nextConfig
482
+ ? removeDeletedParallelBranchPipelines(
483
+ currentDsl,
484
+ nodeId,
485
+ nextConfig,
486
+ referencedNodeConfigs.configs,
487
+ )
488
+ : currentDsl;
489
+ applyDslChange(applyNodePropertyUpdates(cleanedDsl, nodeId, updates));
282
490
  message.success('节点属性已更新');
283
491
  },
284
- [dsl],
492
+ [applyDslChange, dsl, referencedNodeConfigs.configs],
493
+ );
494
+
495
+ const handleNodeConfigDraftChange = useCallback(
496
+ (nodeId: string, config: Record<string, any>) => {
497
+ const currentDsl = dslRef.current;
498
+ if (!currentDsl) return;
499
+ const node = currentDsl.nodes.find((item) => item.id === nodeId);
500
+ const nextDsl = removeDeletedParallelBranchPipelines(
501
+ currentDsl,
502
+ nodeId,
503
+ config,
504
+ referencedNodeConfigs.configs,
505
+ );
506
+ applyDslChange(
507
+ applyNodePropertyUpdates(nextDsl, nodeId, {
508
+ config,
509
+ ...(node?.instanceRef && { configOverride: config }),
510
+ }),
511
+ );
512
+ },
513
+ [applyDslChange, referencedNodeConfigs.configs],
285
514
  );
286
515
 
287
516
  // --- Title editing ---
@@ -303,6 +532,34 @@ export default function WorkflowEditorPage() {
303
532
  async function handleSave() {
304
533
  if (!dsl) return;
305
534
 
535
+ if (viewMode === 'dsl') {
536
+ if (referencedNodeConfigs.status !== 'ready') {
537
+ message.error(
538
+ referencedNodeConfigs.status === 'error'
539
+ ? t('editor.templateConfigLoadFailedHint')
540
+ : t('editor.templateConfigLoadingHint'),
541
+ );
542
+ return;
543
+ }
544
+ if (!(dslViewRef.current?.apply() ?? true)) return;
545
+ }
546
+
547
+ if (currentVersion !== null) {
548
+ const confirmed = await new Promise<boolean>((resolve) => {
549
+ Modal.confirm({
550
+ title: t('editor.replaceDraftTitle'),
551
+ content: t('editor.replaceDraftContent', {
552
+ version: currentVersion,
553
+ }),
554
+ okText: t('editor.replaceDraftConfirm'),
555
+ cancelText: t('common.cancel'),
556
+ onOk: () => resolve(true),
557
+ onCancel: () => resolve(false),
558
+ });
559
+ });
560
+ if (!confirmed) return;
561
+ }
562
+
306
563
  if (title === 'Untitled Workflow') {
307
564
  setSaveName('');
308
565
  setNameModalOpen(true);
@@ -325,30 +582,54 @@ export default function WorkflowEditorPage() {
325
582
 
326
583
  async function doSave(name: string) {
327
584
  if (!dsl) return;
585
+ const dslToSave = dslRef.current ?? dsl;
328
586
  setSaving(true);
329
587
  try {
330
588
  if (isNewMode) {
331
589
  // Create new workflow with DSL
332
- const wf = await customRequest<{ id: number }>('workflow', 'POST', {
590
+ const wf = await customRequest<Workflow>('workflow', 'POST', {
333
591
  name,
334
592
  description: '',
335
- dsl,
593
+ dsl: dslToSave,
336
594
  });
595
+ const createdWorkflowId = String(wf.id);
596
+ setWorkflowId(createdWorkflowId);
597
+ setWorkflow({ ...wf, id: createdWorkflowId, name, dsl: dslToSave });
598
+ setHasUnsavedChanges(false);
337
599
  localStorage.removeItem(NEW_DRAFT_KEY);
338
600
  message.success(t('editor.saveSuccess'));
339
- navigate(`/admin/workflow/edit/${wf.id}`, { replace: true });
601
+ window.history.replaceState(
602
+ window.history.state,
603
+ '',
604
+ `/admin/workflow/edit/${createdWorkflowId}`,
605
+ );
340
606
  } else {
341
607
  // Update existing: save draft
342
608
  // If name changed from DB, patch it
343
609
  if (workflow && name !== workflow.name) {
344
- await customRequest(`workflow/${id}`, 'PATCH', { name });
610
+ await customRequest(`workflow/${workflowId}`, 'PATCH', { name });
345
611
  setWorkflow((prev) => (prev ? { ...prev, name } : prev));
346
612
  }
347
- await customRequest(`workflow/${id}/save`, 'PATCH', { dsl });
348
- localStorage.removeItem(EDIT_DRAFT_KEY(id!));
613
+ const savedWorkflow = await customRequest<Workflow>(
614
+ `workflow/${workflowId}/save`,
615
+ 'PATCH',
616
+ { dsl: dslToSave },
617
+ );
618
+ setWorkflow((previous) =>
619
+ previous
620
+ ? {
621
+ ...previous,
622
+ ...savedWorkflow,
623
+ dsl: dslToSave,
624
+ }
625
+ : savedWorkflow,
626
+ );
627
+ setCurrentVersion(null);
349
628
  setHasUnsavedChanges(false);
629
+ navigate(`/admin/workflow/edit/${workflowId}`, { replace: true });
350
630
  message.success(t('editor.saved'));
351
631
  }
632
+ nodePropertyPanelRef.current?.markSaved();
352
633
  } catch (e: any) {
353
634
  message.error(e?.message ?? t('editor.saveFailed'));
354
635
  } finally {
@@ -358,11 +639,22 @@ export default function WorkflowEditorPage() {
358
639
 
359
640
  // --- Version selector ---
360
641
  async function handleSelectVersion(version: number) {
361
- if (!id) return;
642
+ if (!workflowId) return;
362
643
 
363
644
  const dslDirty =
364
645
  viewMode === 'dsl' && (dslViewRef.current?.isDirty() ?? false);
365
646
 
647
+ const loadSelectedVersion = () => {
648
+ if (version === 0 && workflow?.dsl) {
649
+ replaceDslWithoutHistory(workflow.dsl);
650
+ setCurrentVersion(null);
651
+ setHasUnsavedChanges(false);
652
+ setSelectedNodeId(null);
653
+ return;
654
+ }
655
+ return doLoadVersion(version);
656
+ };
657
+
366
658
  if (hasUnsavedChanges || dslDirty) {
367
659
  Modal.confirm({
368
660
  title: hasUnsavedChanges ? '未保存的修改' : 'DSL 有未应用的编辑',
@@ -371,26 +663,25 @@ export default function WorkflowEditorPage() {
371
663
  : 'DSL 视图中有未应用到画布的编辑,切换版本将丢弃这些内容。确定要继续吗?',
372
664
  okText: '确定切换',
373
665
  cancelText: '取消',
374
- onOk: () => doLoadVersion(version),
666
+ onOk: loadSelectedVersion,
375
667
  });
376
668
  return;
377
669
  }
378
670
 
379
- await doLoadVersion(version);
671
+ await loadSelectedVersion();
380
672
  }
381
673
 
382
674
  async function doLoadVersion(version: number) {
383
- if (!id) return;
675
+ if (!workflowId) return;
384
676
  try {
385
677
  const v = await customRequest<WorkflowVersion>(
386
- `workflow/${id}/versions/${version}`,
678
+ `workflow/${workflowId}/versions/${version}`,
387
679
  'GET',
388
680
  );
389
- setDsl(v.dsl);
681
+ replaceDslWithoutHistory(v.dsl);
390
682
  setCurrentVersion(v.version);
391
- setHasUnsavedChanges(false);
683
+ setHasUnsavedChanges(true);
392
684
  setSelectedNodeId(null);
393
- localStorage.removeItem(EDIT_DRAFT_KEY(id));
394
685
  } catch (e: any) {
395
686
  message.error(e?.message ?? t('editor.versionLoadFailed'));
396
687
  }
@@ -402,6 +693,14 @@ export default function WorkflowEditorPage() {
402
693
 
403
694
  function handleViewModeChange(next: 'canvas' | 'dsl') {
404
695
  if (next === 'canvas' && viewMode === 'dsl') {
696
+ if (referencedNodeConfigs.status !== 'ready') {
697
+ message.error(
698
+ referencedNodeConfigs.status === 'error'
699
+ ? t('editor.templateConfigLoadFailedHint')
700
+ : t('editor.templateConfigLoadingHint'),
701
+ );
702
+ return;
703
+ }
405
704
  // 切回画布前自动应用 textarea 中的 DSL;校验失败则阻止切换
406
705
  const success = dslViewRef.current?.apply() ?? true;
407
706
  if (!success) {
@@ -421,16 +720,39 @@ export default function WorkflowEditorPage() {
421
720
  '当前已有工作流内容,AI 自动生成将覆盖现有内容。确定要继续吗?',
422
721
  okText: '确定继续',
423
722
  cancelText: '取消',
424
- onOk: () => setAiModalOpen(true),
723
+ onOk: () => {
724
+ resetAiImg();
725
+ setAiModalOpen(true);
726
+ },
425
727
  });
426
728
  return;
427
729
  }
730
+ resetAiImg();
428
731
  setAiModalOpen(true);
429
732
  }
430
733
 
734
+ // 提交 AI 生成:选了参考图但未上传时拦截;已上传则把路径附进 prompt
735
+ function submitAiPrompt() {
736
+ const trimmed = aiPromptText.trim();
737
+ if (!trimmed) return;
738
+ if (aiImgFile && !aiUploadedImgPath) {
739
+ message.warning('请先点击"上传",待参考图上传成功后再生成');
740
+ return;
741
+ }
742
+ let prompt = trimmed;
743
+ if (aiUploadedImgPath) {
744
+ prompt +=
745
+ '\n\n我提供了一张工作流参考图,请用 Read 工具读取它,作为工作流结构和节点设计的参考:\n' +
746
+ aiUploadedImgPath;
747
+ }
748
+ handlePromptSubmit(prompt);
749
+ setAiPromptText('');
750
+ resetAiImg();
751
+ setAiModalOpen(false);
752
+ }
753
+
431
754
  function handleDslApply(newDsl: WorkflowDSL) {
432
- setDsl(newDsl);
433
- setHasUnsavedChanges(true);
755
+ applyDslChange(newDsl);
434
756
  setSelectedNodeId(null);
435
757
  }
436
758
 
@@ -450,30 +772,13 @@ export default function WorkflowEditorPage() {
450
772
  }
451
773
 
452
774
  return (
453
- <div
454
- style={{
455
- height: 'calc(100vh - 96px)',
456
- display: 'flex',
457
- flexDirection: 'column',
458
- background: 'var(--ant-color-bg-layout)',
459
- overflow: 'hidden',
460
- }}
461
- >
462
- {/* Header */}
463
- <Card style={{ marginBottom: 16, flexShrink: 0 }}>
464
- <div
465
- style={{
466
- display: 'flex',
467
- justifyContent: 'space-between',
468
- alignItems: 'center',
469
- }}
470
- >
471
- <Space>
472
- <Button
473
- type="text"
474
- icon={<ArrowLeftOutlined />}
475
- onClick={() => navigate('/admin/workflow')}
476
- ></Button>
775
+ <div className="workflow-workspace">
776
+ <WorkflowWorkspaceHeader
777
+ eyebrow={t('editor.workspaceEyebrow')}
778
+ onBack={() => navigate('/admin/workflow')}
779
+ backLabel={t('common.back')}
780
+ title={
781
+ <div className="workflow-workspace__title-row">
477
782
  {editingTitle ? (
478
783
  <Input
479
784
  ref={titleInputRef}
@@ -499,18 +804,25 @@ export default function WorkflowEditorPage() {
499
804
  {hasUnsavedChanges && (
500
805
  <Tag color="orange">{t('editor.unsaved')}</Tag>
501
806
  )}
502
- </Space>
503
- <Space>
807
+ </div>
808
+ }
809
+ actions={
810
+ <Space wrap={false}>
504
811
  {!isNewMode && versions.length > 0 && (
505
812
  <Select
506
- value={currentVersion}
813
+ value={currentVersion ?? 0}
507
814
  onChange={handleSelectVersion}
508
815
  style={{ width: 200 }}
509
816
  placeholder={t('editor.selectVersion')}
510
- options={versions.map((v) => ({
511
- label: `v${v.version} - ${v.changeSummary?.slice(0, 20) || t('editor.saved')}`,
512
- value: v.version,
513
- }))}
817
+ options={[
818
+ ...(workflow?.hasUnpublishedChanges && workflow.dsl
819
+ ? [{ label: t('viewer.savedDraft'), value: 0 }]
820
+ : []),
821
+ ...versions.map((v) => ({
822
+ label: `v${v.version} - ${v.changeSummary?.slice(0, 20) || t('editor.saved')}`,
823
+ value: v.version,
824
+ })),
825
+ ]}
514
826
  />
515
827
  )}
516
828
  {isDev && (
@@ -525,81 +837,153 @@ export default function WorkflowEditorPage() {
525
837
  <Button
526
838
  type="primary"
527
839
  icon={<SaveOutlined />}
528
- disabled={!dsl || !hasUnsavedChanges}
840
+ disabled={!dsl || (!hasUnsavedChanges && !dslTextDirty)}
529
841
  loading={saving}
530
842
  onClick={handleSave}
531
843
  >
532
844
  {t('common.save')}
533
845
  </Button>
534
846
  </Space>
535
- </div>
536
- </Card>
847
+ }
848
+ />
537
849
 
538
850
  {/* View toggle row */}
539
- <div
540
- style={{
541
- marginBottom: 12,
542
- flexShrink: 0,
543
- display: 'flex',
544
- alignItems: 'center',
545
- justifyContent: 'space-between',
546
- }}
547
- >
548
- <Segmented
549
- value={viewMode}
550
- onChange={(v) => handleViewModeChange(v as 'canvas' | 'dsl')}
551
- options={[
552
- { label: '画布', value: 'canvas' },
553
- { label: 'DSL', value: 'dsl' },
554
- ]}
555
- />
556
- <Button
557
- danger
558
- size="small"
559
- style={{ marginRight: 8 }}
560
- onClick={() => {
561
- if (viewMode === 'dsl') {
562
- dslViewRef.current?.clear();
563
- } else {
564
- setDsl({ nodes: [], edges: [] });
565
- setHasUnsavedChanges(true);
566
- setSelectedNodeId(null);
567
- }
568
- }}
569
- >
570
- 清空
571
- </Button>
851
+ <div className="workflow-workspace__toolbar">
852
+ <div className="workflow-workspace__view-switcher">
853
+ <Segmented
854
+ className="workflow-workspace__segmented"
855
+ aria-label={t('editor.viewMode')}
856
+ value={viewMode}
857
+ onChange={(v) => handleViewModeChange(v as 'canvas' | 'dsl')}
858
+ options={[
859
+ {
860
+ label: t('editor.canvas'),
861
+ value: 'canvas',
862
+ icon: <ApartmentOutlined />,
863
+ },
864
+ {
865
+ label: t('editor.dsl'),
866
+ value: 'dsl',
867
+ icon: <CodeOutlined />,
868
+ },
869
+ ]}
870
+ />
871
+ </div>
872
+ <Space className="workflow-workspace__toolbar-actions" size={8}>
873
+ {nodeTypesError && (
874
+ <span className="workflow-inline-error">
875
+ {t('editor.nodeTypesLoadFailed')}{' '}
876
+ <Button type="link" size="small" onClick={loadNodeTypes}>
877
+ {t('common.retry')}
878
+ </Button>
879
+ </span>
880
+ )}
881
+ {referencedNodeConfigs.status === 'loading' && (
882
+ <span className="workflow-template-sync-state">
883
+ <Spin size="small" />
884
+ {t('editor.templateConfigLoading')}
885
+ </span>
886
+ )}
887
+ {referencedNodeConfigs.status === 'error' && (
888
+ <span className="workflow-inline-error">
889
+ {t('editor.templateConfigLoadFailed')}{' '}
890
+ <Button
891
+ type="link"
892
+ size="small"
893
+ onClick={referencedNodeConfigs.retry}
894
+ >
895
+ {t('common.retry')}
896
+ </Button>
897
+ </span>
898
+ )}
899
+ {callableCatalogStatus === 'error' && (
900
+ <span className="workflow-inline-error">
901
+ {t('editor.callableCatalogLoadFailed')}{' '}
902
+ <Button type="link" size="small" onClick={loadCallableWorkflows}>
903
+ {t('common.retry')}
904
+ </Button>
905
+ </span>
906
+ )}
907
+ {agentError && (
908
+ <span className="workflow-inline-error">{agentError}</span>
909
+ )}
910
+ <Button
911
+ className="workflow-workspace__clear"
912
+ type="text"
913
+ danger
914
+ size="small"
915
+ icon={<DeleteOutlined />}
916
+ onClick={() => {
917
+ if (viewMode === 'dsl') {
918
+ dslViewRef.current?.clear();
919
+ } else {
920
+ applyDslChange({ nodes: [], edges: [] });
921
+ setSelectedNodeId(null);
922
+ }
923
+ }}
924
+ >
925
+ {t('editor.clear')}
926
+ </Button>
927
+ </Space>
572
928
  </div>
573
929
 
574
930
  {/* Main area: Flow + Property Panel */}
575
- <div
576
- style={{ flex: 1, display: 'flex', overflow: 'hidden', minHeight: 0 }}
577
- >
931
+ <div className="workflow-workspace__body">
578
932
  {viewMode === 'canvas' ? (
579
933
  <>
580
- <div style={{ flex: 1, position: 'relative' }}>
934
+ <div className="workflow-workspace__canvas">
581
935
  <FlowRenderer
582
936
  dsl={dsl}
583
937
  nodeTypeMap={nodeTypeMap}
584
938
  selectedNodeId={selectedNodeId}
585
939
  onNodeClick={handleNodeClick}
586
940
  onDslChange={handleDslChange}
941
+ canUndo={dslHistory.past.length > 0}
942
+ canRedo={dslHistory.future.length > 0}
943
+ onUndo={handleUndo}
944
+ onRedo={handleRedo}
945
+ onNodeDragStart={beginDslTransaction}
946
+ onNodeDragStop={finishDslTransaction}
947
+ referencedConfigs={referencedNodeConfigs.configs}
948
+ referencedConfigStatus={referencedNodeConfigs.status}
949
+ onRetryReferencedConfigs={referencedNodeConfigs.retry}
950
+ readonly={false}
587
951
  />
588
952
  </div>
589
953
  <NodePropertyPanel
954
+ ref={nodePropertyPanelRef}
590
955
  node={selectedNode}
591
956
  nodeTypes={nodeTypes}
592
- currentWorkflowId={id}
957
+ nodes={dsl?.nodes ?? []}
958
+ edges={dsl?.edges ?? []}
959
+ currentWorkflowId={workflowId}
960
+ referencedConfigs={referencedNodeConfigs.configs}
961
+ referencedConfigStatus={referencedNodeConfigs.status}
962
+ onRetryReferencedConfigs={referencedNodeConfigs.retry}
963
+ callableWorkflows={callableWorkflows}
964
+ callableCatalogStatus={callableCatalogStatus}
965
+ onRetryCallableCatalog={loadCallableWorkflows}
593
966
  onSave={handleNodePropertySave}
967
+ onConfigDraftChange={handleNodeConfigDraftChange}
594
968
  onLabelChange={handleNodeLabelChange}
595
969
  onAiEdit={handleNodeAiEdit}
596
- onDelete={handleNodeDelete}
597
970
  onAbort={abortAgent}
598
971
  aiLoading={agentLoading}
599
972
  />
600
973
  </>
601
974
  ) : (
602
- <DslView ref={dslViewRef} dsl={dsl} onApply={handleDslApply} />
975
+ <DslView
976
+ ref={dslViewRef}
977
+ dsl={dsl}
978
+ nodeTypes={nodeTypes}
979
+ referencedConfigs={referencedNodeConfigs.configs}
980
+ callableWorkflows={
981
+ callableCatalogStatus === 'ready' ? callableWorkflows : undefined
982
+ }
983
+ onApply={handleDslApply}
984
+ onDirtyChange={setDslTextDirty}
985
+ readonly={false}
986
+ />
603
987
  )}
604
988
  </div>
605
989
 
@@ -643,23 +1027,110 @@ export default function WorkflowEditorPage() {
643
1027
  placeholder={t('editor.changePlaceholder')}
644
1028
  autoSize={{ minRows: 6, maxRows: 12 }}
645
1029
  onPressEnter={(e) => {
646
- if (e.ctrlKey && aiPromptText.trim()) {
647
- handlePromptSubmit(aiPromptText.trim());
648
- setAiPromptText('');
649
- setAiModalOpen(false);
1030
+ if (e.ctrlKey) {
1031
+ submitAiPrompt();
650
1032
  }
651
1033
  }}
652
1034
  />
1035
+ {/* 工作流参考图(可选,仅一张) */}
1036
+ <div style={{ marginTop: 12 }}>
1037
+ <Typography.Text strong style={{ fontSize: 14 }}>
1038
+ 工作流参考图
1039
+ </Typography.Text>
1040
+ <Typography.Text
1041
+ type="secondary"
1042
+ style={{ fontSize: 12, marginLeft: 4 }}
1043
+ >
1044
+ (可选)
1045
+ </Typography.Text>
1046
+ <div
1047
+ style={{
1048
+ display: 'flex',
1049
+ alignItems: 'center',
1050
+ gap: 8,
1051
+ marginTop: 8,
1052
+ }}
1053
+ >
1054
+ <input
1055
+ ref={aiFileInputRef}
1056
+ type="file"
1057
+ accept=".png,.jpg,.jpeg,.webp,.gif"
1058
+ style={{ display: 'none' }}
1059
+ onChange={(e) => {
1060
+ const f = e.target.files?.[0] ?? null;
1061
+ e.target.value = '';
1062
+ selectAiImgFile(f);
1063
+ }}
1064
+ />
1065
+ <Button
1066
+ icon={<UploadOutlined />}
1067
+ onClick={() => aiFileInputRef.current?.click()}
1068
+ >
1069
+ 选择文件
1070
+ </Button>
1071
+ <Typography.Text
1072
+ type={aiImgFile ? undefined : 'secondary'}
1073
+ style={{
1074
+ fontSize: 14,
1075
+ flex: 1,
1076
+ overflow: 'hidden',
1077
+ textOverflow: 'ellipsis',
1078
+ whiteSpace: 'nowrap',
1079
+ }}
1080
+ >
1081
+ {aiImgFile ? aiImgFile.name : '未选择任何文件'}
1082
+ </Typography.Text>
1083
+ <Button
1084
+ onClick={handleAiImgUpload}
1085
+ loading={aiImgUploading}
1086
+ disabled={!aiImgFile || !!aiUploadedImgPath}
1087
+ >
1088
+ {aiUploadedImgPath ? '已上传' : '上传'}
1089
+ </Button>
1090
+ </div>
1091
+ {aiUploadedImgPath && (
1092
+ <Typography.Text
1093
+ type="success"
1094
+ style={{ fontSize: 12, display: 'block', marginTop: 4 }}
1095
+ >
1096
+ ✓ 已上传:{aiImgFile?.name}
1097
+ </Typography.Text>
1098
+ )}
1099
+ {aiImgUploadError && (
1100
+ <Typography.Text
1101
+ type="danger"
1102
+ style={{ fontSize: 12, display: 'block', marginTop: 4 }}
1103
+ >
1104
+ {aiImgUploadError}
1105
+ </Typography.Text>
1106
+ )}
1107
+ {aiImgPreviewUrl && (
1108
+ <img
1109
+ src={aiImgPreviewUrl}
1110
+ alt="工作流参考图预览"
1111
+ style={{
1112
+ marginTop: 8,
1113
+ maxWidth: '100%',
1114
+ maxHeight: 160,
1115
+ borderRadius: 6,
1116
+ border: '1px solid var(--ant-color-border-secondary)',
1117
+ objectFit: 'contain',
1118
+ display: 'block',
1119
+ }}
1120
+ />
1121
+ )}
1122
+ <Typography.Text
1123
+ type="secondary"
1124
+ style={{ fontSize: 12, display: 'block', marginTop: 4 }}
1125
+ >
1126
+ 仅支持一张图片(png/jpg/webp/gif),不超过 5MB,AI
1127
+ 会读取它作为工作流设计参考
1128
+ </Typography.Text>
1129
+ </div>
653
1130
  <Button
654
1131
  type="primary"
655
1132
  icon={<RocketOutlined />}
656
- onClick={() => {
657
- if (aiPromptText.trim()) {
658
- handlePromptSubmit(aiPromptText.trim());
659
- setAiPromptText('');
660
- setAiModalOpen(false);
661
- }
662
- }}
1133
+ onClick={submitAiPrompt}
663
1134
  disabled={!aiPromptText.trim()}
664
1135
  loading={agentLoading}
665
1136
  style={{ marginTop: 12 }}